From 8c0984e5a75337df513047ec92a6c09d78e3e5cd Mon Sep 17 00:00:00 2001 From: Sebastian Reichel Date: Fri, 17 Jun 2016 13:54:32 +0200 Subject: power: move power supply drivers to power/supply This moves all power supply drivers from drivers/power/ to drivers/power/supply/. The intention is a cleaner source tree, since drivers/power/ also contains frameworks unrelated to power supply, like adaptive voltage scaling. Signed-off-by: Sebastian Reichel --- drivers/power/88pm860x_battery.c | 1021 ------- drivers/power/88pm860x_charger.c | 760 ------ drivers/power/Kconfig | 518 +--- drivers/power/Makefile | 75 +- drivers/power/ab8500_bmdata.c | 605 ----- drivers/power/ab8500_btemp.c | 1212 --------- drivers/power/ab8500_charger.c | 3765 -------------------------- drivers/power/ab8500_fg.c | 3272 ---------------------- drivers/power/abx500_chargalg.c | 2166 --------------- drivers/power/act8945a_charger.c | 359 --- drivers/power/apm_power.c | 376 --- drivers/power/axp20x_usb_power.c | 294 -- drivers/power/axp288_charger.c | 970 ------- drivers/power/axp288_fuel_gauge.c | 1155 -------- drivers/power/bq2415x_charger.c | 1815 ------------- drivers/power/bq24190_charger.c | 1546 ----------- drivers/power/bq24257_charger.c | 1196 -------- drivers/power/bq24735-charger.c | 500 ---- drivers/power/bq25890_charger.c | 994 ------- drivers/power/bq27xxx_battery.c | 1102 -------- drivers/power/bq27xxx_battery_i2c.c | 205 -- drivers/power/charger-manager.c | 2074 -------------- drivers/power/collie_battery.c | 422 --- drivers/power/da9030_battery.c | 596 ---- drivers/power/da9052-battery.c | 669 ----- drivers/power/da9150-charger.c | 694 ----- drivers/power/da9150-fg.c | 579 ---- drivers/power/ds2760_battery.c | 647 ----- drivers/power/ds2780_battery.c | 838 ------ drivers/power/ds2781_battery.c | 839 ------ drivers/power/ds2782_battery.c | 475 ---- drivers/power/generic-adc-battery.c | 432 --- drivers/power/goldfish_battery.c | 256 -- drivers/power/gpio-charger.c | 280 -- drivers/power/intel_mid_battery.c | 796 ------ drivers/power/ipaq_micro_battery.c | 316 --- drivers/power/isp1704_charger.c | 559 ---- drivers/power/jz4740-battery.c | 425 --- drivers/power/lp8727_charger.c | 631 ----- drivers/power/lp8788-charger.c | 764 ------ drivers/power/ltc2941-battery-gauge.c | 514 ---- drivers/power/max14577_charger.c | 648 ----- drivers/power/max17040_battery.c | 305 --- drivers/power/max17042_battery.c | 1016 ------- drivers/power/max77693_charger.c | 771 ------ drivers/power/max8903_charger.c | 459 ---- drivers/power/max8925_power.c | 596 ---- drivers/power/max8997_charger.c | 211 -- drivers/power/max8998_charger.c | 202 -- drivers/power/olpc_battery.c | 692 ----- drivers/power/pcf50633-charger.c | 488 ---- drivers/power/pda_power.c | 514 ---- drivers/power/pm2301_charger.c | 1257 --------- drivers/power/pm2301_charger.h | 493 ---- drivers/power/pmu_battery.c | 226 -- drivers/power/power_supply.h | 42 - drivers/power/power_supply_core.c | 989 ------- drivers/power/power_supply_leds.c | 170 -- drivers/power/power_supply_sysfs.c | 337 --- drivers/power/qcom_smbb.c | 972 ------- drivers/power/rt5033_battery.c | 182 -- drivers/power/rt9455_charger.c | 1763 ------------ drivers/power/rx51_battery.c | 297 -- drivers/power/s3c_adc_battery.c | 459 ---- drivers/power/sbs-battery.c | 998 ------- drivers/power/smb347-charger.c | 1334 --------- drivers/power/supply/88pm860x_battery.c | 1021 +++++++ drivers/power/supply/88pm860x_charger.c | 760 ++++++ drivers/power/supply/Kconfig | 514 ++++ drivers/power/supply/Makefile | 74 + drivers/power/supply/ab8500_bmdata.c | 605 +++++ drivers/power/supply/ab8500_btemp.c | 1212 +++++++++ drivers/power/supply/ab8500_charger.c | 3765 ++++++++++++++++++++++++++ drivers/power/supply/ab8500_fg.c | 3272 ++++++++++++++++++++++ drivers/power/supply/abx500_chargalg.c | 2166 +++++++++++++++ drivers/power/supply/act8945a_charger.c | 359 +++ drivers/power/supply/apm_power.c | 376 +++ drivers/power/supply/axp20x_usb_power.c | 294 ++ drivers/power/supply/axp288_charger.c | 970 +++++++ drivers/power/supply/axp288_fuel_gauge.c | 1155 ++++++++ drivers/power/supply/bq2415x_charger.c | 1815 +++++++++++++ drivers/power/supply/bq24190_charger.c | 1546 +++++++++++ drivers/power/supply/bq24257_charger.c | 1196 ++++++++ drivers/power/supply/bq24735-charger.c | 500 ++++ drivers/power/supply/bq25890_charger.c | 994 +++++++ drivers/power/supply/bq27xxx_battery.c | 1102 ++++++++ drivers/power/supply/bq27xxx_battery_i2c.c | 205 ++ drivers/power/supply/charger-manager.c | 2074 ++++++++++++++ drivers/power/supply/collie_battery.c | 422 +++ drivers/power/supply/da9030_battery.c | 596 ++++ drivers/power/supply/da9052-battery.c | 669 +++++ drivers/power/supply/da9150-charger.c | 694 +++++ drivers/power/supply/da9150-fg.c | 579 ++++ drivers/power/supply/ds2760_battery.c | 647 +++++ drivers/power/supply/ds2780_battery.c | 838 ++++++ drivers/power/supply/ds2781_battery.c | 839 ++++++ drivers/power/supply/ds2782_battery.c | 475 ++++ drivers/power/supply/generic-adc-battery.c | 432 +++ drivers/power/supply/goldfish_battery.c | 256 ++ drivers/power/supply/gpio-charger.c | 280 ++ drivers/power/supply/intel_mid_battery.c | 796 ++++++ drivers/power/supply/ipaq_micro_battery.c | 316 +++ drivers/power/supply/isp1704_charger.c | 559 ++++ drivers/power/supply/jz4740-battery.c | 425 +++ drivers/power/supply/lp8727_charger.c | 631 +++++ drivers/power/supply/lp8788-charger.c | 764 ++++++ drivers/power/supply/ltc2941-battery-gauge.c | 514 ++++ drivers/power/supply/max14577_charger.c | 648 +++++ drivers/power/supply/max17040_battery.c | 305 +++ drivers/power/supply/max17042_battery.c | 1016 +++++++ drivers/power/supply/max77693_charger.c | 771 ++++++ drivers/power/supply/max8903_charger.c | 459 ++++ drivers/power/supply/max8925_power.c | 596 ++++ drivers/power/supply/max8997_charger.c | 211 ++ drivers/power/supply/max8998_charger.c | 202 ++ drivers/power/supply/olpc_battery.c | 692 +++++ drivers/power/supply/pcf50633-charger.c | 488 ++++ drivers/power/supply/pda_power.c | 514 ++++ drivers/power/supply/pm2301_charger.c | 1257 +++++++++ drivers/power/supply/pm2301_charger.h | 493 ++++ drivers/power/supply/pmu_battery.c | 226 ++ drivers/power/supply/power_supply.h | 42 + drivers/power/supply/power_supply_core.c | 989 +++++++ drivers/power/supply/power_supply_leds.c | 170 ++ drivers/power/supply/power_supply_sysfs.c | 337 +++ drivers/power/supply/qcom_smbb.c | 972 +++++++ drivers/power/supply/rt5033_battery.c | 182 ++ drivers/power/supply/rt9455_charger.c | 1763 ++++++++++++ drivers/power/supply/rx51_battery.c | 297 ++ drivers/power/supply/s3c_adc_battery.c | 459 ++++ drivers/power/supply/sbs-battery.c | 998 +++++++ drivers/power/supply/smb347-charger.c | 1334 +++++++++ drivers/power/supply/test_power.c | 533 ++++ drivers/power/supply/tosa_battery.c | 470 ++++ drivers/power/supply/tps65090-charger.c | 370 +++ drivers/power/supply/tps65217_charger.c | 268 ++ drivers/power/supply/twl4030_charger.c | 1162 ++++++++ drivers/power/supply/twl4030_madc_battery.c | 278 ++ drivers/power/supply/wm831x_backup.c | 225 ++ drivers/power/supply/wm831x_power.c | 673 +++++ drivers/power/supply/wm8350_power.c | 540 ++++ drivers/power/supply/wm97xx_battery.c | 297 ++ drivers/power/supply/z2_battery.c | 331 +++ drivers/power/test_power.c | 533 ---- drivers/power/tosa_battery.c | 470 ---- drivers/power/tps65090-charger.c | 370 --- drivers/power/tps65217_charger.c | 268 -- drivers/power/twl4030_charger.c | 1162 -------- drivers/power/twl4030_madc_battery.c | 278 -- drivers/power/wm831x_backup.c | 225 -- drivers/power/wm831x_power.c | 673 ----- drivers/power/wm8350_power.c | 540 ---- drivers/power/wm97xx_battery.c | 299 -- drivers/power/z2_battery.c | 331 --- 154 files changed, 57278 insertions(+), 57279 deletions(-) delete mode 100644 drivers/power/88pm860x_battery.c delete mode 100644 drivers/power/88pm860x_charger.c delete mode 100644 drivers/power/ab8500_bmdata.c delete mode 100644 drivers/power/ab8500_btemp.c delete mode 100644 drivers/power/ab8500_charger.c delete mode 100644 drivers/power/ab8500_fg.c delete mode 100644 drivers/power/abx500_chargalg.c delete mode 100644 drivers/power/act8945a_charger.c delete mode 100644 drivers/power/apm_power.c delete mode 100644 drivers/power/axp20x_usb_power.c delete mode 100644 drivers/power/axp288_charger.c delete mode 100644 drivers/power/axp288_fuel_gauge.c delete mode 100644 drivers/power/bq2415x_charger.c delete mode 100644 drivers/power/bq24190_charger.c delete mode 100644 drivers/power/bq24257_charger.c delete mode 100644 drivers/power/bq24735-charger.c delete mode 100644 drivers/power/bq25890_charger.c delete mode 100644 drivers/power/bq27xxx_battery.c delete mode 100644 drivers/power/bq27xxx_battery_i2c.c delete mode 100644 drivers/power/charger-manager.c delete mode 100644 drivers/power/collie_battery.c delete mode 100644 drivers/power/da9030_battery.c delete mode 100644 drivers/power/da9052-battery.c delete mode 100644 drivers/power/da9150-charger.c delete mode 100644 drivers/power/da9150-fg.c delete mode 100644 drivers/power/ds2760_battery.c delete mode 100644 drivers/power/ds2780_battery.c delete mode 100644 drivers/power/ds2781_battery.c delete mode 100644 drivers/power/ds2782_battery.c delete mode 100644 drivers/power/generic-adc-battery.c delete mode 100644 drivers/power/goldfish_battery.c delete mode 100644 drivers/power/gpio-charger.c delete mode 100644 drivers/power/intel_mid_battery.c delete mode 100644 drivers/power/ipaq_micro_battery.c delete mode 100644 drivers/power/isp1704_charger.c delete mode 100644 drivers/power/jz4740-battery.c delete mode 100644 drivers/power/lp8727_charger.c delete mode 100644 drivers/power/lp8788-charger.c delete mode 100644 drivers/power/ltc2941-battery-gauge.c delete mode 100644 drivers/power/max14577_charger.c delete mode 100644 drivers/power/max17040_battery.c delete mode 100644 drivers/power/max17042_battery.c delete mode 100644 drivers/power/max77693_charger.c delete mode 100644 drivers/power/max8903_charger.c delete mode 100644 drivers/power/max8925_power.c delete mode 100644 drivers/power/max8997_charger.c delete mode 100644 drivers/power/max8998_charger.c delete mode 100644 drivers/power/olpc_battery.c delete mode 100644 drivers/power/pcf50633-charger.c delete mode 100644 drivers/power/pda_power.c delete mode 100644 drivers/power/pm2301_charger.c delete mode 100644 drivers/power/pm2301_charger.h delete mode 100644 drivers/power/pmu_battery.c delete mode 100644 drivers/power/power_supply.h delete mode 100644 drivers/power/power_supply_core.c delete mode 100644 drivers/power/power_supply_leds.c delete mode 100644 drivers/power/power_supply_sysfs.c delete mode 100644 drivers/power/qcom_smbb.c delete mode 100644 drivers/power/rt5033_battery.c delete mode 100644 drivers/power/rt9455_charger.c delete mode 100644 drivers/power/rx51_battery.c delete mode 100644 drivers/power/s3c_adc_battery.c delete mode 100644 drivers/power/sbs-battery.c delete mode 100644 drivers/power/smb347-charger.c create mode 100644 drivers/power/supply/88pm860x_battery.c create mode 100644 drivers/power/supply/88pm860x_charger.c create mode 100644 drivers/power/supply/Kconfig create mode 100644 drivers/power/supply/Makefile create mode 100644 drivers/power/supply/ab8500_bmdata.c create mode 100644 drivers/power/supply/ab8500_btemp.c create mode 100644 drivers/power/supply/ab8500_charger.c create mode 100644 drivers/power/supply/ab8500_fg.c create mode 100644 drivers/power/supply/abx500_chargalg.c create mode 100644 drivers/power/supply/act8945a_charger.c create mode 100644 drivers/power/supply/apm_power.c create mode 100644 drivers/power/supply/axp20x_usb_power.c create mode 100644 drivers/power/supply/axp288_charger.c create mode 100644 drivers/power/supply/axp288_fuel_gauge.c create mode 100644 drivers/power/supply/bq2415x_charger.c create mode 100644 drivers/power/supply/bq24190_charger.c create mode 100644 drivers/power/supply/bq24257_charger.c create mode 100644 drivers/power/supply/bq24735-charger.c create mode 100644 drivers/power/supply/bq25890_charger.c create mode 100644 drivers/power/supply/bq27xxx_battery.c create mode 100644 drivers/power/supply/bq27xxx_battery_i2c.c create mode 100644 drivers/power/supply/charger-manager.c create mode 100644 drivers/power/supply/collie_battery.c create mode 100644 drivers/power/supply/da9030_battery.c create mode 100644 drivers/power/supply/da9052-battery.c create mode 100644 drivers/power/supply/da9150-charger.c create mode 100644 drivers/power/supply/da9150-fg.c create mode 100644 drivers/power/supply/ds2760_battery.c create mode 100644 drivers/power/supply/ds2780_battery.c create mode 100644 drivers/power/supply/ds2781_battery.c create mode 100644 drivers/power/supply/ds2782_battery.c create mode 100644 drivers/power/supply/generic-adc-battery.c create mode 100644 drivers/power/supply/goldfish_battery.c create mode 100644 drivers/power/supply/gpio-charger.c create mode 100644 drivers/power/supply/intel_mid_battery.c create mode 100644 drivers/power/supply/ipaq_micro_battery.c create mode 100644 drivers/power/supply/isp1704_charger.c create mode 100644 drivers/power/supply/jz4740-battery.c create mode 100644 drivers/power/supply/lp8727_charger.c create mode 100644 drivers/power/supply/lp8788-charger.c create mode 100644 drivers/power/supply/ltc2941-battery-gauge.c create mode 100644 drivers/power/supply/max14577_charger.c create mode 100644 drivers/power/supply/max17040_battery.c create mode 100644 drivers/power/supply/max17042_battery.c create mode 100644 drivers/power/supply/max77693_charger.c create mode 100644 drivers/power/supply/max8903_charger.c create mode 100644 drivers/power/supply/max8925_power.c create mode 100644 drivers/power/supply/max8997_charger.c create mode 100644 drivers/power/supply/max8998_charger.c create mode 100644 drivers/power/supply/olpc_battery.c create mode 100644 drivers/power/supply/pcf50633-charger.c create mode 100644 drivers/power/supply/pda_power.c create mode 100644 drivers/power/supply/pm2301_charger.c create mode 100644 drivers/power/supply/pm2301_charger.h create mode 100644 drivers/power/supply/pmu_battery.c create mode 100644 drivers/power/supply/power_supply.h create mode 100644 drivers/power/supply/power_supply_core.c create mode 100644 drivers/power/supply/power_supply_leds.c create mode 100644 drivers/power/supply/power_supply_sysfs.c create mode 100644 drivers/power/supply/qcom_smbb.c create mode 100644 drivers/power/supply/rt5033_battery.c create mode 100644 drivers/power/supply/rt9455_charger.c create mode 100644 drivers/power/supply/rx51_battery.c create mode 100644 drivers/power/supply/s3c_adc_battery.c create mode 100644 drivers/power/supply/sbs-battery.c create mode 100644 drivers/power/supply/smb347-charger.c create mode 100644 drivers/power/supply/test_power.c create mode 100644 drivers/power/supply/tosa_battery.c create mode 100644 drivers/power/supply/tps65090-charger.c create mode 100644 drivers/power/supply/tps65217_charger.c create mode 100644 drivers/power/supply/twl4030_charger.c create mode 100644 drivers/power/supply/twl4030_madc_battery.c create mode 100644 drivers/power/supply/wm831x_backup.c create mode 100644 drivers/power/supply/wm831x_power.c create mode 100644 drivers/power/supply/wm8350_power.c create mode 100644 drivers/power/supply/wm97xx_battery.c create mode 100644 drivers/power/supply/z2_battery.c delete mode 100644 drivers/power/test_power.c delete mode 100644 drivers/power/tosa_battery.c delete mode 100644 drivers/power/tps65090-charger.c delete mode 100644 drivers/power/tps65217_charger.c delete mode 100644 drivers/power/twl4030_charger.c delete mode 100644 drivers/power/twl4030_madc_battery.c delete mode 100644 drivers/power/wm831x_backup.c delete mode 100644 drivers/power/wm831x_power.c delete mode 100644 drivers/power/wm8350_power.c delete mode 100644 drivers/power/wm97xx_battery.c delete mode 100644 drivers/power/z2_battery.c (limited to 'drivers/power') diff --git a/drivers/power/88pm860x_battery.c b/drivers/power/88pm860x_battery.c deleted file mode 100644 index 63c57dc82ac1..000000000000 --- a/drivers/power/88pm860x_battery.c +++ /dev/null @@ -1,1021 +0,0 @@ -/* - * Battery driver for Marvell 88PM860x PMIC - * - * Copyright (c) 2012 Marvell International Ltd. - * Author: Jett Zhou - * Haojian Zhuang - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/* bit definitions of Status Query Interface 2 */ -#define STATUS2_CHG (1 << 2) -#define STATUS2_BAT (1 << 3) -#define STATUS2_VBUS (1 << 4) - -/* bit definitions of Measurement Enable 1 Register */ -#define MEAS1_TINT (1 << 3) -#define MEAS1_GP1 (1 << 5) - -/* bit definitions of Measurement Enable 3 Register */ -#define MEAS3_IBAT (1 << 0) -#define MEAS3_BAT_DET (1 << 1) -#define MEAS3_CC (1 << 2) - -/* bit definitions of Measurement Off Time Register */ -#define MEAS_OFF_SLEEP_EN (1 << 1) - -/* bit definitions of GPADC Bias Current 2 Register */ -#define GPBIAS2_GPADC1_SET (2 << 4) -/* GPADC1 Bias Current value in uA unit */ -#define GPBIAS2_GPADC1_UA ((GPBIAS2_GPADC1_SET >> 4) * 5 + 1) - -/* bit definitions of GPADC Misc 1 Register */ -#define GPMISC1_GPADC_EN (1 << 0) - -/* bit definitions of Charger Control 6 Register */ -#define CC6_BAT_DET_GPADC1 1 - -/* bit definitions of Coulomb Counter Reading Register */ -#define CCNT_AVG_SEL (4 << 3) - -/* bit definitions of RTC miscellaneous Register1 */ -#define RTC_SOC_5LSB (0x1F << 3) - -/* bit definitions of RTC Register1 */ -#define RTC_SOC_3MSB (0x7) - -/* bit definitions of Power up Log register */ -#define BAT_WU_LOG (1<<6) - -/* coulomb counter index */ -#define CCNT_POS1 0 -#define CCNT_POS2 1 -#define CCNT_NEG1 2 -#define CCNT_NEG2 3 -#define CCNT_SPOS 4 -#define CCNT_SNEG 5 - -/* OCV -- Open Circuit Voltage */ -#define OCV_MODE_ACTIVE 0 -#define OCV_MODE_SLEEP 1 - -/* Vbat range of CC for measuring Rbat */ -#define LOW_BAT_THRESHOLD 3600 -#define VBATT_RESISTOR_MIN 3800 -#define VBATT_RESISTOR_MAX 4100 - -/* TBAT for batt, TINT for chip itself */ -#define PM860X_TEMP_TINT (0) -#define PM860X_TEMP_TBAT (1) - -/* - * Battery temperature based on NTC resistor, defined - * corresponding resistor value -- Ohm / C degeree. - */ -#define TBAT_NEG_25D 127773 /* -25 */ -#define TBAT_NEG_10D 54564 /* -10 */ -#define TBAT_0D 32330 /* 0 */ -#define TBAT_10D 19785 /* 10 */ -#define TBAT_20D 12468 /* 20 */ -#define TBAT_30D 8072 /* 30 */ -#define TBAT_40D 5356 /* 40 */ - -struct pm860x_battery_info { - struct pm860x_chip *chip; - struct i2c_client *i2c; - struct device *dev; - - struct power_supply *battery; - struct mutex lock; - int status; - int irq_cc; - int irq_batt; - int max_capacity; - int resistor; /* Battery Internal Resistor */ - int last_capacity; - int start_soc; - unsigned present:1; - unsigned temp_type:1; /* TINT or TBAT */ -}; - -struct ccnt { - unsigned long long int pos; - unsigned long long int neg; - unsigned int spos; - unsigned int sneg; - - int total_chg; /* mAh(3.6C) */ - int total_dischg; /* mAh(3.6C) */ -}; - -/* - * State of Charge. - * The first number is mAh(=3.6C), and the second number is percent point. - */ -static int array_soc[][2] = { - {4170, 100}, {4154, 99}, {4136, 98}, {4122, 97}, {4107, 96}, - {4102, 95}, {4088, 94}, {4081, 93}, {4070, 92}, {4060, 91}, - {4053, 90}, {4044, 89}, {4035, 88}, {4028, 87}, {4019, 86}, - {4013, 85}, {4006, 84}, {3995, 83}, {3987, 82}, {3982, 81}, - {3976, 80}, {3968, 79}, {3962, 78}, {3954, 77}, {3946, 76}, - {3941, 75}, {3934, 74}, {3929, 73}, {3922, 72}, {3916, 71}, - {3910, 70}, {3904, 69}, {3898, 68}, {3892, 67}, {3887, 66}, - {3880, 65}, {3874, 64}, {3868, 63}, {3862, 62}, {3854, 61}, - {3849, 60}, {3843, 59}, {3840, 58}, {3833, 57}, {3829, 56}, - {3824, 55}, {3818, 54}, {3815, 53}, {3810, 52}, {3808, 51}, - {3804, 50}, {3801, 49}, {3798, 48}, {3796, 47}, {3792, 46}, - {3789, 45}, {3785, 44}, {3784, 43}, {3782, 42}, {3780, 41}, - {3777, 40}, {3776, 39}, {3774, 38}, {3772, 37}, {3771, 36}, - {3769, 35}, {3768, 34}, {3764, 33}, {3763, 32}, {3760, 31}, - {3760, 30}, {3754, 29}, {3750, 28}, {3749, 27}, {3744, 26}, - {3740, 25}, {3734, 24}, {3732, 23}, {3728, 22}, {3726, 21}, - {3720, 20}, {3716, 19}, {3709, 18}, {3703, 17}, {3698, 16}, - {3692, 15}, {3683, 14}, {3675, 13}, {3670, 12}, {3665, 11}, - {3661, 10}, {3649, 9}, {3637, 8}, {3622, 7}, {3609, 6}, - {3580, 5}, {3558, 4}, {3540, 3}, {3510, 2}, {3429, 1}, -}; - -static struct ccnt ccnt_data; - -/* - * register 1 bit[7:0] -- bit[11:4] of measured value of voltage - * register 0 bit[3:0] -- bit[3:0] of measured value of voltage - */ -static int measure_12bit_voltage(struct pm860x_battery_info *info, - int offset, int *data) -{ - unsigned char buf[2]; - int ret; - - ret = pm860x_bulk_read(info->i2c, offset, 2, buf); - if (ret < 0) - return ret; - - *data = ((buf[0] & 0xff) << 4) | (buf[1] & 0x0f); - /* V_MEAS(mV) = data * 1.8 * 1000 / (2^12) */ - *data = ((*data & 0xfff) * 9 * 25) >> 9; - return 0; -} - -static int measure_vbatt(struct pm860x_battery_info *info, int state, - int *data) -{ - unsigned char buf[5]; - int ret; - - switch (state) { - case OCV_MODE_ACTIVE: - ret = measure_12bit_voltage(info, PM8607_VBAT_MEAS1, data); - if (ret) - return ret; - /* V_BATT_MEAS(mV) = value * 3 * 1.8 * 1000 / (2^12) */ - *data *= 3; - break; - case OCV_MODE_SLEEP: - /* - * voltage value of VBATT in sleep mode is saved in different - * registers. - * bit[11:10] -- bit[7:6] of LDO9(0x18) - * bit[9:8] -- bit[7:6] of LDO8(0x17) - * bit[7:6] -- bit[7:6] of LDO7(0x16) - * bit[5:4] -- bit[7:6] of LDO6(0x15) - * bit[3:0] -- bit[7:4] of LDO5(0x14) - */ - ret = pm860x_bulk_read(info->i2c, PM8607_LDO5, 5, buf); - if (ret < 0) - return ret; - ret = ((buf[4] >> 6) << 10) | ((buf[3] >> 6) << 8) - | ((buf[2] >> 6) << 6) | ((buf[1] >> 6) << 4) - | (buf[0] >> 4); - /* V_BATT_MEAS(mV) = data * 3 * 1.8 * 1000 / (2^12) */ - *data = ((*data & 0xff) * 27 * 25) >> 9; - break; - default: - return -EINVAL; - } - return 0; -} - -/* - * Return value is signed data. - * Negative value means discharging, and positive value means charging. - */ -static int measure_current(struct pm860x_battery_info *info, int *data) -{ - unsigned char buf[2]; - short s; - int ret; - - ret = pm860x_bulk_read(info->i2c, PM8607_IBAT_MEAS1, 2, buf); - if (ret < 0) - return ret; - - s = ((buf[0] & 0xff) << 8) | (buf[1] & 0xff); - /* current(mA) = value * 0.125 */ - *data = s >> 3; - return 0; -} - -static int set_charger_current(struct pm860x_battery_info *info, int data, - int *old) -{ - int ret; - - if (data < 50 || data > 1600 || !old) - return -EINVAL; - - data = ((data - 50) / 50) & 0x1f; - *old = pm860x_reg_read(info->i2c, PM8607_CHG_CTRL2); - *old = (*old & 0x1f) * 50 + 50; - ret = pm860x_set_bits(info->i2c, PM8607_CHG_CTRL2, 0x1f, data); - if (ret < 0) - return ret; - return 0; -} - -static int read_ccnt(struct pm860x_battery_info *info, int offset, - int *ccnt) -{ - unsigned char buf[2]; - int ret; - - ret = pm860x_set_bits(info->i2c, PM8607_CCNT, 7, offset & 7); - if (ret < 0) - goto out; - ret = pm860x_bulk_read(info->i2c, PM8607_CCNT_MEAS1, 2, buf); - if (ret < 0) - goto out; - *ccnt = ((buf[0] & 0xff) << 8) | (buf[1] & 0xff); - return 0; -out: - return ret; -} - -static int calc_ccnt(struct pm860x_battery_info *info, struct ccnt *ccnt) -{ - unsigned int sum; - int ret; - int data; - - ret = read_ccnt(info, CCNT_POS1, &data); - if (ret) - goto out; - sum = data & 0xffff; - ret = read_ccnt(info, CCNT_POS2, &data); - if (ret) - goto out; - sum |= (data & 0xffff) << 16; - ccnt->pos += sum; - - ret = read_ccnt(info, CCNT_NEG1, &data); - if (ret) - goto out; - sum = data & 0xffff; - ret = read_ccnt(info, CCNT_NEG2, &data); - if (ret) - goto out; - sum |= (data & 0xffff) << 16; - sum = ~sum + 1; /* since it's negative */ - ccnt->neg += sum; - - ret = read_ccnt(info, CCNT_SPOS, &data); - if (ret) - goto out; - ccnt->spos += data; - ret = read_ccnt(info, CCNT_SNEG, &data); - if (ret) - goto out; - - /* - * charge(mAh) = count * 1.6984 * 1e(-8) - * = count * 16984 * 1.024 * 1.024 * 1.024 / (2 ^ 40) - * = count * 18236 / (2 ^ 40) - */ - ccnt->total_chg = (int) ((ccnt->pos * 18236) >> 40); - ccnt->total_dischg = (int) ((ccnt->neg * 18236) >> 40); - return 0; -out: - return ret; -} - -static int clear_ccnt(struct pm860x_battery_info *info, struct ccnt *ccnt) -{ - int data; - - memset(ccnt, 0, sizeof(*ccnt)); - /* read to clear ccnt */ - read_ccnt(info, CCNT_POS1, &data); - read_ccnt(info, CCNT_POS2, &data); - read_ccnt(info, CCNT_NEG1, &data); - read_ccnt(info, CCNT_NEG2, &data); - read_ccnt(info, CCNT_SPOS, &data); - read_ccnt(info, CCNT_SNEG, &data); - return 0; -} - -/* Calculate Open Circuit Voltage */ -static int calc_ocv(struct pm860x_battery_info *info, int *ocv) -{ - int ret; - int i; - int data; - int vbatt_avg; - int vbatt_sum; - int ibatt_avg; - int ibatt_sum; - - if (!ocv) - return -EINVAL; - - for (i = 0, ibatt_sum = 0, vbatt_sum = 0; i < 10; i++) { - ret = measure_vbatt(info, OCV_MODE_ACTIVE, &data); - if (ret) - goto out; - vbatt_sum += data; - ret = measure_current(info, &data); - if (ret) - goto out; - ibatt_sum += data; - } - vbatt_avg = vbatt_sum / 10; - ibatt_avg = ibatt_sum / 10; - - mutex_lock(&info->lock); - if (info->present) - *ocv = vbatt_avg - ibatt_avg * info->resistor / 1000; - else - *ocv = vbatt_avg; - mutex_unlock(&info->lock); - dev_dbg(info->dev, "VBAT average:%d, OCV:%d\n", vbatt_avg, *ocv); - return 0; -out: - return ret; -} - -/* Calculate State of Charge (percent points) */ -static int calc_soc(struct pm860x_battery_info *info, int state, int *soc) -{ - int i; - int ocv; - int count; - int ret = -EINVAL; - - if (!soc) - return -EINVAL; - - switch (state) { - case OCV_MODE_ACTIVE: - ret = calc_ocv(info, &ocv); - break; - case OCV_MODE_SLEEP: - ret = measure_vbatt(info, OCV_MODE_SLEEP, &ocv); - break; - } - if (ret) - return ret; - - count = ARRAY_SIZE(array_soc); - if (ocv < array_soc[count - 1][0]) { - *soc = 0; - return 0; - } - - for (i = 0; i < count; i++) { - if (ocv >= array_soc[i][0]) { - *soc = array_soc[i][1]; - break; - } - } - return 0; -} - -static irqreturn_t pm860x_coulomb_handler(int irq, void *data) -{ - struct pm860x_battery_info *info = data; - - calc_ccnt(info, &ccnt_data); - return IRQ_HANDLED; -} - -static irqreturn_t pm860x_batt_handler(int irq, void *data) -{ - struct pm860x_battery_info *info = data; - int ret; - - mutex_lock(&info->lock); - ret = pm860x_reg_read(info->i2c, PM8607_STATUS_2); - if (ret & STATUS2_BAT) { - info->present = 1; - info->temp_type = PM860X_TEMP_TBAT; - } else { - info->present = 0; - info->temp_type = PM860X_TEMP_TINT; - } - mutex_unlock(&info->lock); - /* clear ccnt since battery is attached or dettached */ - clear_ccnt(info, &ccnt_data); - return IRQ_HANDLED; -} - -static void pm860x_init_battery(struct pm860x_battery_info *info) -{ - unsigned char buf[2]; - int ret; - int data; - int bat_remove; - int soc; - - /* measure enable on GPADC1 */ - data = MEAS1_GP1; - if (info->temp_type == PM860X_TEMP_TINT) - data |= MEAS1_TINT; - ret = pm860x_set_bits(info->i2c, PM8607_MEAS_EN1, data, data); - if (ret) - goto out; - - /* measure enable on IBAT, BAT_DET, CC. IBAT is depend on CC. */ - data = MEAS3_IBAT | MEAS3_BAT_DET | MEAS3_CC; - ret = pm860x_set_bits(info->i2c, PM8607_MEAS_EN3, data, data); - if (ret) - goto out; - - /* measure disable CC in sleep time */ - ret = pm860x_reg_write(info->i2c, PM8607_MEAS_OFF_TIME1, 0x82); - if (ret) - goto out; - ret = pm860x_reg_write(info->i2c, PM8607_MEAS_OFF_TIME2, 0x6c); - if (ret) - goto out; - - /* enable GPADC */ - ret = pm860x_set_bits(info->i2c, PM8607_GPADC_MISC1, - GPMISC1_GPADC_EN, GPMISC1_GPADC_EN); - if (ret < 0) - goto out; - - /* detect battery via GPADC1 */ - ret = pm860x_set_bits(info->i2c, PM8607_CHG_CTRL6, - CC6_BAT_DET_GPADC1, CC6_BAT_DET_GPADC1); - if (ret < 0) - goto out; - - ret = pm860x_set_bits(info->i2c, PM8607_CCNT, 7 << 3, - CCNT_AVG_SEL); - if (ret < 0) - goto out; - - /* set GPADC1 bias */ - ret = pm860x_set_bits(info->i2c, PM8607_GP_BIAS2, 0xF << 4, - GPBIAS2_GPADC1_SET); - if (ret < 0) - goto out; - - /* check whether battery present) */ - mutex_lock(&info->lock); - ret = pm860x_reg_read(info->i2c, PM8607_STATUS_2); - if (ret < 0) { - mutex_unlock(&info->lock); - goto out; - } - if (ret & STATUS2_BAT) { - info->present = 1; - info->temp_type = PM860X_TEMP_TBAT; - } else { - info->present = 0; - info->temp_type = PM860X_TEMP_TINT; - } - mutex_unlock(&info->lock); - - calc_soc(info, OCV_MODE_ACTIVE, &soc); - - data = pm860x_reg_read(info->i2c, PM8607_POWER_UP_LOG); - bat_remove = data & BAT_WU_LOG; - - dev_dbg(info->dev, "battery wake up? %s\n", - bat_remove != 0 ? "yes" : "no"); - - /* restore SOC from RTC domain register */ - if (bat_remove == 0) { - buf[0] = pm860x_reg_read(info->i2c, PM8607_RTC_MISC2); - buf[1] = pm860x_reg_read(info->i2c, PM8607_RTC1); - data = ((buf[1] & 0x3) << 5) | ((buf[0] >> 3) & 0x1F); - if (data > soc + 15) - info->start_soc = soc; - else if (data < soc - 15) - info->start_soc = soc; - else - info->start_soc = data; - dev_dbg(info->dev, "soc_rtc %d, soc_ocv :%d\n", data, soc); - } else { - pm860x_set_bits(info->i2c, PM8607_POWER_UP_LOG, - BAT_WU_LOG, BAT_WU_LOG); - info->start_soc = soc; - } - info->last_capacity = info->start_soc; - dev_dbg(info->dev, "init soc : %d\n", info->last_capacity); -out: - return; -} - -static void set_temp_threshold(struct pm860x_battery_info *info, - int min, int max) -{ - int data; - - /* (tmp << 8) / 1800 */ - if (min <= 0) - data = 0; - else - data = (min << 8) / 1800; - pm860x_reg_write(info->i2c, PM8607_GPADC1_HIGHTH, data); - dev_dbg(info->dev, "TEMP_HIGHTH : min: %d, 0x%x\n", min, data); - - if (max <= 0) - data = 0xff; - else - data = (max << 8) / 1800; - pm860x_reg_write(info->i2c, PM8607_GPADC1_LOWTH, data); - dev_dbg(info->dev, "TEMP_LOWTH:max : %d, 0x%x\n", max, data); -} - -static int measure_temp(struct pm860x_battery_info *info, int *data) -{ - int ret; - int temp; - int min; - int max; - - if (info->temp_type == PM860X_TEMP_TINT) { - ret = measure_12bit_voltage(info, PM8607_TINT_MEAS1, data); - if (ret) - return ret; - *data = (*data - 884) * 1000 / 3611; - } else { - ret = measure_12bit_voltage(info, PM8607_GPADC1_MEAS1, data); - if (ret) - return ret; - /* meausered Vtbat(mV) / Ibias_current(11uA)*/ - *data = (*data * 1000) / GPBIAS2_GPADC1_UA; - - if (*data > TBAT_NEG_25D) { - temp = -30; /* over cold , suppose -30 roughly */ - max = TBAT_NEG_10D * GPBIAS2_GPADC1_UA / 1000; - set_temp_threshold(info, 0, max); - } else if (*data > TBAT_NEG_10D) { - temp = -15; /* -15 degree, code */ - max = TBAT_NEG_10D * GPBIAS2_GPADC1_UA / 1000; - set_temp_threshold(info, 0, max); - } else if (*data > TBAT_0D) { - temp = -5; /* -5 degree */ - min = TBAT_NEG_10D * GPBIAS2_GPADC1_UA / 1000; - max = TBAT_40D * GPBIAS2_GPADC1_UA / 1000; - set_temp_threshold(info, min, max); - } else if (*data > TBAT_10D) { - temp = 5; /* in range of (0, 10) */ - min = TBAT_NEG_10D * GPBIAS2_GPADC1_UA / 1000; - max = TBAT_40D * GPBIAS2_GPADC1_UA / 1000; - set_temp_threshold(info, min, max); - } else if (*data > TBAT_20D) { - temp = 15; /* in range of (10, 20) */ - min = TBAT_NEG_10D * GPBIAS2_GPADC1_UA / 1000; - max = TBAT_40D * GPBIAS2_GPADC1_UA / 1000; - set_temp_threshold(info, min, max); - } else if (*data > TBAT_30D) { - temp = 25; /* in range of (20, 30) */ - min = TBAT_NEG_10D * GPBIAS2_GPADC1_UA / 1000; - max = TBAT_40D * GPBIAS2_GPADC1_UA / 1000; - set_temp_threshold(info, min, max); - } else if (*data > TBAT_40D) { - temp = 35; /* in range of (30, 40) */ - min = TBAT_NEG_10D * GPBIAS2_GPADC1_UA / 1000; - max = TBAT_40D * GPBIAS2_GPADC1_UA / 1000; - set_temp_threshold(info, min, max); - } else { - min = TBAT_40D * GPBIAS2_GPADC1_UA / 1000; - set_temp_threshold(info, min, 0); - temp = 45; /* over heat ,suppose 45 roughly */ - } - - dev_dbg(info->dev, "temp_C:%d C,temp_mv:%d mv\n", temp, *data); - *data = temp; - } - return 0; -} - -static int calc_resistor(struct pm860x_battery_info *info) -{ - int vbatt_sum1; - int vbatt_sum2; - int chg_current; - int ibatt_sum1; - int ibatt_sum2; - int data; - int ret; - int i; - - ret = measure_current(info, &data); - /* make sure that charging is launched by data > 0 */ - if (ret || data < 0) - goto out; - - ret = measure_vbatt(info, OCV_MODE_ACTIVE, &data); - if (ret) - goto out; - /* calculate resistor only in CC charge mode */ - if (data < VBATT_RESISTOR_MIN || data > VBATT_RESISTOR_MAX) - goto out; - - /* current is saved */ - if (set_charger_current(info, 500, &chg_current)) - goto out; - - /* - * set charge current as 500mA, wait about 500ms till charging - * process is launched and stable with the newer charging current. - */ - msleep(500); - - for (i = 0, vbatt_sum1 = 0, ibatt_sum1 = 0; i < 10; i++) { - ret = measure_vbatt(info, OCV_MODE_ACTIVE, &data); - if (ret) - goto out_meas; - vbatt_sum1 += data; - ret = measure_current(info, &data); - if (ret) - goto out_meas; - - if (data < 0) - ibatt_sum1 = ibatt_sum1 - data; /* discharging */ - else - ibatt_sum1 = ibatt_sum1 + data; /* charging */ - } - - if (set_charger_current(info, 100, &ret)) - goto out_meas; - /* - * set charge current as 100mA, wait about 500ms till charging - * process is launched and stable with the newer charging current. - */ - msleep(500); - - for (i = 0, vbatt_sum2 = 0, ibatt_sum2 = 0; i < 10; i++) { - ret = measure_vbatt(info, OCV_MODE_ACTIVE, &data); - if (ret) - goto out_meas; - vbatt_sum2 += data; - ret = measure_current(info, &data); - if (ret) - goto out_meas; - - if (data < 0) - ibatt_sum2 = ibatt_sum2 - data; /* discharging */ - else - ibatt_sum2 = ibatt_sum2 + data; /* charging */ - } - - /* restore current setting */ - if (set_charger_current(info, chg_current, &ret)) - goto out_meas; - - if ((vbatt_sum1 > vbatt_sum2) && (ibatt_sum1 > ibatt_sum2) && - (ibatt_sum2 > 0)) { - /* calculate resistor in discharging case */ - data = 1000 * (vbatt_sum1 - vbatt_sum2) - / (ibatt_sum1 - ibatt_sum2); - if ((data - info->resistor > 0) && - (data - info->resistor < info->resistor)) - info->resistor = data; - if ((info->resistor - data > 0) && - (info->resistor - data < data)) - info->resistor = data; - } - return 0; - -out_meas: - set_charger_current(info, chg_current, &ret); -out: - return -EINVAL; -} - -static int calc_capacity(struct pm860x_battery_info *info, int *cap) -{ - int ret; - int data; - int ibat; - int cap_ocv = 0; - int cap_cc = 0; - - ret = calc_ccnt(info, &ccnt_data); - if (ret) - goto out; -soc: - data = info->max_capacity * info->start_soc / 100; - if (ccnt_data.total_dischg - ccnt_data.total_chg <= data) { - cap_cc = - data + ccnt_data.total_chg - ccnt_data.total_dischg; - } else { - clear_ccnt(info, &ccnt_data); - calc_soc(info, OCV_MODE_ACTIVE, &info->start_soc); - dev_dbg(info->dev, "restart soc = %d !\n", - info->start_soc); - goto soc; - } - - cap_cc = cap_cc * 100 / info->max_capacity; - if (cap_cc < 0) - cap_cc = 0; - else if (cap_cc > 100) - cap_cc = 100; - - dev_dbg(info->dev, "%s, last cap : %d", __func__, - info->last_capacity); - - ret = measure_current(info, &ibat); - if (ret) - goto out; - /* Calculate the capacity when discharging(ibat < 0) */ - if (ibat < 0) { - ret = calc_soc(info, OCV_MODE_ACTIVE, &cap_ocv); - if (ret) - cap_ocv = info->last_capacity; - ret = measure_vbatt(info, OCV_MODE_ACTIVE, &data); - if (ret) - goto out; - if (data <= LOW_BAT_THRESHOLD) { - /* choose the lower capacity value to report - * between vbat and CC when vbat < 3.6v; - * than 3.6v; - */ - *cap = min(cap_ocv, cap_cc); - } else { - /* when detect vbat > 3.6v, but cap_cc < 15,and - * cap_ocv is 10% larger than cap_cc, we can think - * CC have some accumulation error, switch to OCV - * to estimate capacity; - * */ - if (cap_cc < 15 && cap_ocv - cap_cc > 10) - *cap = cap_ocv; - else - *cap = cap_cc; - } - /* when discharging, make sure current capacity - * is lower than last*/ - if (*cap > info->last_capacity) - *cap = info->last_capacity; - } else { - *cap = cap_cc; - } - info->last_capacity = *cap; - - dev_dbg(info->dev, "%s, cap_ocv:%d cap_cc:%d, cap:%d\n", - (ibat < 0) ? "discharging" : "charging", - cap_ocv, cap_cc, *cap); - /* - * store the current capacity to RTC domain register, - * after next power up , it will be restored. - */ - pm860x_set_bits(info->i2c, PM8607_RTC_MISC2, RTC_SOC_5LSB, - (*cap & 0x1F) << 3); - pm860x_set_bits(info->i2c, PM8607_RTC1, RTC_SOC_3MSB, - ((*cap >> 5) & 0x3)); - return 0; -out: - return ret; -} - -static void pm860x_external_power_changed(struct power_supply *psy) -{ - struct pm860x_battery_info *info = dev_get_drvdata(psy->dev.parent); - - calc_resistor(info); -} - -static int pm860x_batt_get_prop(struct power_supply *psy, - enum power_supply_property psp, - union power_supply_propval *val) -{ - struct pm860x_battery_info *info = dev_get_drvdata(psy->dev.parent); - int data; - int ret; - - switch (psp) { - case POWER_SUPPLY_PROP_PRESENT: - val->intval = info->present; - break; - case POWER_SUPPLY_PROP_CAPACITY: - ret = calc_capacity(info, &data); - if (ret) - return ret; - if (data < 0) - data = 0; - else if (data > 100) - data = 100; - /* return 100 if battery is not attached */ - if (!info->present) - data = 100; - val->intval = data; - break; - case POWER_SUPPLY_PROP_TECHNOLOGY: - val->intval = POWER_SUPPLY_TECHNOLOGY_LION; - break; - case POWER_SUPPLY_PROP_VOLTAGE_NOW: - /* return real vbatt Voltage */ - ret = measure_vbatt(info, OCV_MODE_ACTIVE, &data); - if (ret) - return ret; - val->intval = data * 1000; - break; - case POWER_SUPPLY_PROP_VOLTAGE_AVG: - /* return Open Circuit Voltage (not measured voltage) */ - ret = calc_ocv(info, &data); - if (ret) - return ret; - val->intval = data * 1000; - break; - case POWER_SUPPLY_PROP_CURRENT_NOW: - ret = measure_current(info, &data); - if (ret) - return ret; - val->intval = data; - break; - case POWER_SUPPLY_PROP_TEMP: - if (info->present) { - ret = measure_temp(info, &data); - if (ret) - return ret; - data *= 10; - } else { - /* Fake Temp 25C Without Battery */ - data = 250; - } - val->intval = data; - break; - default: - return -ENODEV; - } - return 0; -} - -static int pm860x_batt_set_prop(struct power_supply *psy, - enum power_supply_property psp, - const union power_supply_propval *val) -{ - struct pm860x_battery_info *info = dev_get_drvdata(psy->dev.parent); - - switch (psp) { - case POWER_SUPPLY_PROP_CHARGE_FULL: - clear_ccnt(info, &ccnt_data); - info->start_soc = 100; - dev_dbg(info->dev, "chg done, update soc = %d\n", - info->start_soc); - break; - default: - return -EPERM; - } - - return 0; -} - - -static enum power_supply_property pm860x_batt_props[] = { - POWER_SUPPLY_PROP_PRESENT, - POWER_SUPPLY_PROP_CAPACITY, - POWER_SUPPLY_PROP_TECHNOLOGY, - POWER_SUPPLY_PROP_VOLTAGE_NOW, - POWER_SUPPLY_PROP_VOLTAGE_AVG, - POWER_SUPPLY_PROP_CURRENT_NOW, - POWER_SUPPLY_PROP_TEMP, -}; - -static const struct power_supply_desc pm860x_battery_desc = { - .name = "battery-monitor", - .type = POWER_SUPPLY_TYPE_BATTERY, - .properties = pm860x_batt_props, - .num_properties = ARRAY_SIZE(pm860x_batt_props), - .get_property = pm860x_batt_get_prop, - .set_property = pm860x_batt_set_prop, - .external_power_changed = pm860x_external_power_changed, -}; - -static int pm860x_battery_probe(struct platform_device *pdev) -{ - struct pm860x_chip *chip = dev_get_drvdata(pdev->dev.parent); - struct pm860x_battery_info *info; - struct pm860x_power_pdata *pdata; - int ret; - - info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL); - if (!info) - return -ENOMEM; - - info->irq_cc = platform_get_irq(pdev, 0); - if (info->irq_cc <= 0) { - dev_err(&pdev->dev, "No IRQ resource!\n"); - return -EINVAL; - } - - info->irq_batt = platform_get_irq(pdev, 1); - if (info->irq_batt <= 0) { - dev_err(&pdev->dev, "No IRQ resource!\n"); - return -EINVAL; - } - - info->chip = chip; - info->i2c = - (chip->id == CHIP_PM8607) ? chip->client : chip->companion; - info->dev = &pdev->dev; - info->status = POWER_SUPPLY_STATUS_UNKNOWN; - pdata = pdev->dev.platform_data; - - mutex_init(&info->lock); - platform_set_drvdata(pdev, info); - - pm860x_init_battery(info); - - if (pdata && pdata->max_capacity) - info->max_capacity = pdata->max_capacity; - else - info->max_capacity = 1500; /* set default capacity */ - if (pdata && pdata->resistor) - info->resistor = pdata->resistor; - else - info->resistor = 300; /* set default internal resistor */ - - 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 = 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); - return ret; - } - - 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); - return ret; - } - - - return 0; -} - -#ifdef CONFIG_PM_SLEEP -static int pm860x_battery_suspend(struct device *dev) -{ - struct platform_device *pdev = to_platform_device(dev); - struct pm860x_chip *chip = dev_get_drvdata(pdev->dev.parent); - - if (device_may_wakeup(dev)) - chip->wakeup_flag |= 1 << PM8607_IRQ_CC; - return 0; -} - -static int pm860x_battery_resume(struct device *dev) -{ - struct platform_device *pdev = to_platform_device(dev); - struct pm860x_chip *chip = dev_get_drvdata(pdev->dev.parent); - - if (device_may_wakeup(dev)) - chip->wakeup_flag &= ~(1 << PM8607_IRQ_CC); - return 0; -} -#endif - -static SIMPLE_DEV_PM_OPS(pm860x_battery_pm_ops, - pm860x_battery_suspend, pm860x_battery_resume); - -static struct platform_driver pm860x_battery_driver = { - .driver = { - .name = "88pm860x-battery", - .pm = &pm860x_battery_pm_ops, - }, - .probe = pm860x_battery_probe, -}; -module_platform_driver(pm860x_battery_driver); - -MODULE_DESCRIPTION("Marvell 88PM860x Battery driver"); -MODULE_LICENSE("GPL"); diff --git a/drivers/power/88pm860x_charger.c b/drivers/power/88pm860x_charger.c deleted file mode 100644 index 2b82e44d9027..000000000000 --- a/drivers/power/88pm860x_charger.c +++ /dev/null @@ -1,760 +0,0 @@ -/* - * Battery driver for Marvell 88PM860x PMIC - * - * Copyright (c) 2012 Marvell International Ltd. - * Author: Jett Zhou - * Haojian Zhuang - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/* bit definitions of Status Query Interface 2 */ -#define STATUS2_CHG (1 << 2) - -/* bit definitions of Reset Out Register */ -#define RESET_SW_PD (1 << 7) - -/* bit definitions of PreReg 1 */ -#define PREREG1_90MA (0x0) -#define PREREG1_180MA (0x1) -#define PREREG1_450MA (0x4) -#define PREREG1_540MA (0x5) -#define PREREG1_1350MA (0xE) -#define PREREG1_VSYS_4_5V (3 << 4) - -/* bit definitions of Charger Control 1 Register */ -#define CC1_MODE_OFF (0) -#define CC1_MODE_PRECHARGE (1) -#define CC1_MODE_FASTCHARGE (2) -#define CC1_MODE_PULSECHARGE (3) -#define CC1_ITERM_20MA (0 << 2) -#define CC1_ITERM_60MA (2 << 2) -#define CC1_VFCHG_4_2V (9 << 4) - -/* bit definitions of Charger Control 2 Register */ -#define CC2_ICHG_100MA (0x1) -#define CC2_ICHG_500MA (0x9) -#define CC2_ICHG_1000MA (0x13) - -/* bit definitions of Charger Control 3 Register */ -#define CC3_180MIN_TIMEOUT (0x6 << 4) -#define CC3_270MIN_TIMEOUT (0x7 << 4) -#define CC3_360MIN_TIMEOUT (0xA << 4) -#define CC3_DISABLE_TIMEOUT (0xF << 4) - -/* bit definitions of Charger Control 4 Register */ -#define CC4_IPRE_40MA (7) -#define CC4_VPCHG_3_2V (3 << 4) -#define CC4_IFCHG_MON_EN (1 << 6) -#define CC4_BTEMP_MON_EN (1 << 7) - -/* bit definitions of Charger Control 6 Register */ -#define CC6_BAT_OV_EN (1 << 2) -#define CC6_BAT_UV_EN (1 << 3) -#define CC6_UV_VBAT_SET (0x3 << 6) /* 2.8v */ - -/* bit definitions of Charger Control 7 Register */ -#define CC7_BAT_REM_EN (1 << 3) -#define CC7_IFSM_EN (1 << 7) - -/* bit definitions of Measurement Enable 1 Register */ -#define MEAS1_VBAT (1 << 0) - -/* bit definitions of Measurement Enable 3 Register */ -#define MEAS3_IBAT_EN (1 << 0) -#define MEAS3_CC_EN (1 << 2) - -#define FSM_INIT 0 -#define FSM_DISCHARGE 1 -#define FSM_PRECHARGE 2 -#define FSM_FASTCHARGE 3 - -#define PRECHARGE_THRESHOLD 3100 -#define POWEROFF_THRESHOLD 3400 -#define CHARGE_THRESHOLD 4000 -#define DISCHARGE_THRESHOLD 4180 - -/* over-temperature on PM8606 setting */ -#define OVER_TEMP_FLAG (1 << 6) -#define OVTEMP_AUTORECOVER (1 << 3) - -/* over-voltage protect on vchg setting mv */ -#define VCHG_NORMAL_LOW 4200 -#define VCHG_NORMAL_CHECK 5800 -#define VCHG_NORMAL_HIGH 6000 -#define VCHG_OVP_LOW 5500 - -struct pm860x_charger_info { - struct pm860x_chip *chip; - struct i2c_client *i2c; - struct i2c_client *i2c_8606; - struct device *dev; - - struct power_supply *usb; - struct mutex lock; - int irq_nums; - int irq[7]; - unsigned state:3; /* fsm state */ - unsigned online:1; /* usb charger */ - unsigned present:1; /* battery present */ - unsigned allowed:1; -}; - -static char *pm860x_supplied_to[] = { - "battery-monitor", -}; - -static int measure_vchg(struct pm860x_charger_info *info, int *data) -{ - unsigned char buf[2]; - int ret = 0; - - ret = pm860x_bulk_read(info->i2c, PM8607_VCHG_MEAS1, 2, buf); - if (ret < 0) - return ret; - - *data = ((buf[0] & 0xff) << 4) | (buf[1] & 0x0f); - /* V_BATT_MEAS(mV) = value * 5 * 1.8 * 1000 / (2^12) */ - *data = ((*data & 0xfff) * 9 * 125) >> 9; - - dev_dbg(info->dev, "%s, vchg: %d mv\n", __func__, *data); - - return ret; -} - -static void set_vchg_threshold(struct pm860x_charger_info *info, - int min, int max) -{ - int data; - - /* (tmp << 8) * / 5 / 1800 */ - if (min <= 0) - data = 0; - else - data = (min << 5) / 1125; - pm860x_reg_write(info->i2c, PM8607_VCHG_LOWTH, data); - dev_dbg(info->dev, "VCHG_LOWTH:%dmv, 0x%x\n", min, data); - - if (max <= 0) - data = 0xff; - else - data = (max << 5) / 1125; - pm860x_reg_write(info->i2c, PM8607_VCHG_HIGHTH, data); - dev_dbg(info->dev, "VCHG_HIGHTH:%dmv, 0x%x\n", max, data); - -} - -static void set_vbatt_threshold(struct pm860x_charger_info *info, - int min, int max) -{ - int data; - - /* (tmp << 8) * 3 / 1800 */ - if (min <= 0) - data = 0; - else - data = (min << 5) / 675; - pm860x_reg_write(info->i2c, PM8607_VBAT_LOWTH, data); - dev_dbg(info->dev, "VBAT Min:%dmv, LOWTH:0x%x\n", min, data); - - if (max <= 0) - data = 0xff; - else - data = (max << 5) / 675; - pm860x_reg_write(info->i2c, PM8607_VBAT_HIGHTH, data); - dev_dbg(info->dev, "VBAT Max:%dmv, HIGHTH:0x%x\n", max, data); - - return; -} - -static int start_precharge(struct pm860x_charger_info *info) -{ - int ret; - - dev_dbg(info->dev, "Start Pre-charging!\n"); - set_vbatt_threshold(info, 0, 0); - - ret = pm860x_reg_write(info->i2c_8606, PM8606_PREREGULATORA, - PREREG1_1350MA | PREREG1_VSYS_4_5V); - if (ret < 0) - goto out; - /* stop charging */ - ret = pm860x_set_bits(info->i2c, PM8607_CHG_CTRL1, 3, - CC1_MODE_OFF); - if (ret < 0) - goto out; - /* set 270 minutes timeout */ - ret = pm860x_set_bits(info->i2c, PM8607_CHG_CTRL3, (0xf << 4), - CC3_270MIN_TIMEOUT); - if (ret < 0) - goto out; - /* set precharge current, termination voltage, IBAT & TBAT monitor */ - ret = pm860x_reg_write(info->i2c, PM8607_CHG_CTRL4, - CC4_IPRE_40MA | CC4_VPCHG_3_2V | - CC4_IFCHG_MON_EN | CC4_BTEMP_MON_EN); - if (ret < 0) - goto out; - ret = pm860x_set_bits(info->i2c, PM8607_CHG_CTRL7, - CC7_BAT_REM_EN | CC7_IFSM_EN, - CC7_BAT_REM_EN | CC7_IFSM_EN); - if (ret < 0) - goto out; - /* trigger precharge */ - ret = pm860x_set_bits(info->i2c, PM8607_CHG_CTRL1, 3, - CC1_MODE_PRECHARGE); -out: - return ret; -} - -static int start_fastcharge(struct pm860x_charger_info *info) -{ - int ret; - - dev_dbg(info->dev, "Start Fast-charging!\n"); - - /* set fastcharge termination current & voltage, disable charging */ - ret = pm860x_reg_write(info->i2c, PM8607_CHG_CTRL1, - CC1_MODE_OFF | CC1_ITERM_60MA | - CC1_VFCHG_4_2V); - if (ret < 0) - goto out; - ret = pm860x_reg_write(info->i2c_8606, PM8606_PREREGULATORA, - PREREG1_540MA | PREREG1_VSYS_4_5V); - if (ret < 0) - goto out; - ret = pm860x_set_bits(info->i2c, PM8607_CHG_CTRL2, 0x1f, - CC2_ICHG_500MA); - if (ret < 0) - goto out; - /* set 270 minutes timeout */ - ret = pm860x_set_bits(info->i2c, PM8607_CHG_CTRL3, (0xf << 4), - CC3_270MIN_TIMEOUT); - if (ret < 0) - goto out; - /* set IBAT & TBAT monitor */ - ret = pm860x_set_bits(info->i2c, PM8607_CHG_CTRL4, - CC4_IFCHG_MON_EN | CC4_BTEMP_MON_EN, - CC4_IFCHG_MON_EN | CC4_BTEMP_MON_EN); - if (ret < 0) - goto out; - ret = pm860x_set_bits(info->i2c, PM8607_CHG_CTRL6, - CC6_BAT_OV_EN | CC6_BAT_UV_EN | - CC6_UV_VBAT_SET, - CC6_BAT_OV_EN | CC6_BAT_UV_EN | - CC6_UV_VBAT_SET); - if (ret < 0) - goto out; - ret = pm860x_set_bits(info->i2c, PM8607_CHG_CTRL7, - CC7_BAT_REM_EN | CC7_IFSM_EN, - CC7_BAT_REM_EN | CC7_IFSM_EN); - if (ret < 0) - goto out; - /* launch fast-charge */ - ret = pm860x_set_bits(info->i2c, PM8607_CHG_CTRL1, 3, - CC1_MODE_FASTCHARGE); - /* vchg threshold setting */ - set_vchg_threshold(info, VCHG_NORMAL_LOW, VCHG_NORMAL_HIGH); -out: - return ret; -} - -static void stop_charge(struct pm860x_charger_info *info, int vbatt) -{ - dev_dbg(info->dev, "Stop charging!\n"); - pm860x_set_bits(info->i2c, PM8607_CHG_CTRL1, 3, CC1_MODE_OFF); - if (vbatt > CHARGE_THRESHOLD && info->online) - set_vbatt_threshold(info, CHARGE_THRESHOLD, 0); -} - -static void power_off_notification(struct pm860x_charger_info *info) -{ - dev_dbg(info->dev, "Power-off notification!\n"); -} - -static int set_charging_fsm(struct pm860x_charger_info *info) -{ - struct power_supply *psy; - union power_supply_propval data; - unsigned char fsm_state[][16] = { "init", "discharge", "precharge", - "fastcharge", - }; - int ret; - int vbatt; - - psy = power_supply_get_by_name(pm860x_supplied_to[0]); - if (!psy) - return -EINVAL; - ret = power_supply_get_property(psy, POWER_SUPPLY_PROP_VOLTAGE_NOW, - &data); - if (ret) { - power_supply_put(psy); - return ret; - } - vbatt = data.intval / 1000; - - ret = power_supply_get_property(psy, POWER_SUPPLY_PROP_PRESENT, &data); - if (ret) { - power_supply_put(psy); - return ret; - } - power_supply_put(psy); - - mutex_lock(&info->lock); - info->present = data.intval; - - dev_dbg(info->dev, "Entering FSM:%s, Charger:%s, Battery:%s, " - "Allowed:%d\n", - &fsm_state[info->state][0], - (info->online) ? "online" : "N/A", - (info->present) ? "present" : "N/A", info->allowed); - dev_dbg(info->dev, "set_charging_fsm:vbatt:%d(mV)\n", vbatt); - - switch (info->state) { - case FSM_INIT: - if (info->online && info->present && info->allowed) { - if (vbatt < PRECHARGE_THRESHOLD) { - info->state = FSM_PRECHARGE; - start_precharge(info); - } else if (vbatt > DISCHARGE_THRESHOLD) { - info->state = FSM_DISCHARGE; - stop_charge(info, vbatt); - } else if (vbatt < DISCHARGE_THRESHOLD) { - info->state = FSM_FASTCHARGE; - start_fastcharge(info); - } - } else { - if (vbatt < POWEROFF_THRESHOLD) { - power_off_notification(info); - } else { - info->state = FSM_DISCHARGE; - stop_charge(info, vbatt); - } - } - break; - case FSM_PRECHARGE: - if (info->online && info->present && info->allowed) { - if (vbatt > PRECHARGE_THRESHOLD) { - info->state = FSM_FASTCHARGE; - start_fastcharge(info); - } - } else { - info->state = FSM_DISCHARGE; - stop_charge(info, vbatt); - } - break; - case FSM_FASTCHARGE: - if (info->online && info->present && info->allowed) { - if (vbatt < PRECHARGE_THRESHOLD) { - info->state = FSM_PRECHARGE; - start_precharge(info); - } - } else { - info->state = FSM_DISCHARGE; - stop_charge(info, vbatt); - } - break; - case FSM_DISCHARGE: - if (info->online && info->present && info->allowed) { - if (vbatt < PRECHARGE_THRESHOLD) { - info->state = FSM_PRECHARGE; - start_precharge(info); - } else if (vbatt < DISCHARGE_THRESHOLD) { - info->state = FSM_FASTCHARGE; - start_fastcharge(info); - } - } else { - if (vbatt < POWEROFF_THRESHOLD) - power_off_notification(info); - else if (vbatt > CHARGE_THRESHOLD && info->online) - set_vbatt_threshold(info, CHARGE_THRESHOLD, 0); - } - break; - default: - dev_warn(info->dev, "FSM meets wrong state:%d\n", - info->state); - break; - } - dev_dbg(info->dev, - "Out FSM:%s, Charger:%s, Battery:%s, Allowed:%d\n", - &fsm_state[info->state][0], - (info->online) ? "online" : "N/A", - (info->present) ? "present" : "N/A", info->allowed); - mutex_unlock(&info->lock); - - return 0; -} - -static irqreturn_t pm860x_charger_handler(int irq, void *data) -{ - struct pm860x_charger_info *info = data; - int ret; - - mutex_lock(&info->lock); - ret = pm860x_reg_read(info->i2c, PM8607_STATUS_2); - if (ret < 0) { - mutex_unlock(&info->lock); - goto out; - } - if (ret & STATUS2_CHG) { - info->online = 1; - info->allowed = 1; - } else { - info->online = 0; - info->allowed = 0; - } - mutex_unlock(&info->lock); - dev_dbg(info->dev, "%s, Charger:%s, Allowed:%d\n", __func__, - (info->online) ? "online" : "N/A", info->allowed); - - set_charging_fsm(info); - - power_supply_changed(info->usb); -out: - return IRQ_HANDLED; -} - -static irqreturn_t pm860x_temp_handler(int irq, void *data) -{ - struct power_supply *psy; - struct pm860x_charger_info *info = data; - union power_supply_propval temp; - int value; - int ret; - - psy = power_supply_get_by_name(pm860x_supplied_to[0]); - if (!psy) - return IRQ_HANDLED; - ret = power_supply_get_property(psy, POWER_SUPPLY_PROP_TEMP, &temp); - if (ret) - goto out; - value = temp.intval / 10; - - mutex_lock(&info->lock); - /* Temperature < -10 C or >40 C, Will not allow charge */ - if (value < -10 || value > 40) - info->allowed = 0; - else - info->allowed = 1; - dev_dbg(info->dev, "%s, Allowed: %d\n", __func__, info->allowed); - mutex_unlock(&info->lock); - - set_charging_fsm(info); -out: - power_supply_put(psy); - return IRQ_HANDLED; -} - -static irqreturn_t pm860x_exception_handler(int irq, void *data) -{ - struct pm860x_charger_info *info = data; - - mutex_lock(&info->lock); - info->allowed = 0; - mutex_unlock(&info->lock); - dev_dbg(info->dev, "%s, irq: %d\n", __func__, irq); - - set_charging_fsm(info); - return IRQ_HANDLED; -} - -static irqreturn_t pm860x_done_handler(int irq, void *data) -{ - struct pm860x_charger_info *info = data; - struct power_supply *psy; - union power_supply_propval val; - int ret; - int vbatt; - - mutex_lock(&info->lock); - /* pre-charge done, will transimit to fast-charge stage */ - if (info->state == FSM_PRECHARGE) { - info->allowed = 1; - goto out; - } - /* - * Fast charge done, delay to read - * the correct status of CHG_DET. - */ - mdelay(5); - info->allowed = 0; - psy = power_supply_get_by_name(pm860x_supplied_to[0]); - if (!psy) - goto out; - ret = power_supply_get_property(psy, POWER_SUPPLY_PROP_VOLTAGE_NOW, - &val); - if (ret) - goto out_psy_put; - vbatt = val.intval / 1000; - /* - * CHG_DONE interrupt is faster than CHG_DET interrupt when - * plug in/out usb, So we can not rely on info->online, we - * need check pm8607 status register to check usb is online - * or not, then we can decide it is real charge done - * automatically or it is triggered by usb plug out; - */ - ret = pm860x_reg_read(info->i2c, PM8607_STATUS_2); - if (ret < 0) - goto out_psy_put; - if (vbatt > CHARGE_THRESHOLD && ret & STATUS2_CHG) - power_supply_set_property(psy, POWER_SUPPLY_PROP_CHARGE_FULL, - &val); - -out_psy_put: - power_supply_put(psy); -out: - mutex_unlock(&info->lock); - dev_dbg(info->dev, "%s, Allowed: %d\n", __func__, info->allowed); - set_charging_fsm(info); - - return IRQ_HANDLED; -} - -static irqreturn_t pm860x_vbattery_handler(int irq, void *data) -{ - struct pm860x_charger_info *info = data; - - mutex_lock(&info->lock); - - set_vbatt_threshold(info, 0, 0); - - if (info->present && info->online) - info->allowed = 1; - else - info->allowed = 0; - mutex_unlock(&info->lock); - dev_dbg(info->dev, "%s, Allowed: %d\n", __func__, info->allowed); - - set_charging_fsm(info); - - return IRQ_HANDLED; -} - -static irqreturn_t pm860x_vchg_handler(int irq, void *data) -{ - struct pm860x_charger_info *info = data; - int vchg = 0; - - if (info->present) - goto out; - - measure_vchg(info, &vchg); - - mutex_lock(&info->lock); - if (!info->online) { - int status; - /* check if over-temp on pm8606 or not */ - status = pm860x_reg_read(info->i2c_8606, PM8606_FLAGS); - if (status & OVER_TEMP_FLAG) { - /* clear over temp flag and set auto recover */ - pm860x_set_bits(info->i2c_8606, PM8606_FLAGS, - OVER_TEMP_FLAG, OVER_TEMP_FLAG); - pm860x_set_bits(info->i2c_8606, - PM8606_VSYS, - OVTEMP_AUTORECOVER, - OVTEMP_AUTORECOVER); - dev_dbg(info->dev, - "%s, pm8606 over-temp occurred\n", __func__); - } - } - - if (vchg > VCHG_NORMAL_CHECK) { - set_vchg_threshold(info, VCHG_OVP_LOW, 0); - info->allowed = 0; - dev_dbg(info->dev, - "%s,pm8607 over-vchg occurred,vchg = %dmv\n", - __func__, vchg); - } else if (vchg < VCHG_OVP_LOW) { - set_vchg_threshold(info, VCHG_NORMAL_LOW, - VCHG_NORMAL_HIGH); - info->allowed = 1; - dev_dbg(info->dev, - "%s,pm8607 over-vchg recover,vchg = %dmv\n", - __func__, vchg); - } - mutex_unlock(&info->lock); - - dev_dbg(info->dev, "%s, Allowed: %d\n", __func__, info->allowed); - set_charging_fsm(info); -out: - return IRQ_HANDLED; -} - -static int pm860x_usb_get_prop(struct power_supply *psy, - enum power_supply_property psp, - union power_supply_propval *val) -{ - struct pm860x_charger_info *info = power_supply_get_drvdata(psy); - - switch (psp) { - case POWER_SUPPLY_PROP_STATUS: - if (info->state == FSM_FASTCHARGE || - info->state == FSM_PRECHARGE) - val->intval = POWER_SUPPLY_STATUS_CHARGING; - else - val->intval = POWER_SUPPLY_STATUS_DISCHARGING; - break; - case POWER_SUPPLY_PROP_ONLINE: - val->intval = info->online; - break; - default: - return -ENODEV; - } - return 0; -} - -static enum power_supply_property pm860x_usb_props[] = { - POWER_SUPPLY_PROP_STATUS, - POWER_SUPPLY_PROP_ONLINE, -}; - -static int pm860x_init_charger(struct pm860x_charger_info *info) -{ - int ret; - - ret = pm860x_reg_read(info->i2c, PM8607_STATUS_2); - if (ret < 0) - return ret; - - mutex_lock(&info->lock); - info->state = FSM_INIT; - if (ret & STATUS2_CHG) { - info->online = 1; - info->allowed = 1; - } else { - info->online = 0; - info->allowed = 0; - } - mutex_unlock(&info->lock); - - set_charging_fsm(info); - return 0; -} - -static struct pm860x_irq_desc { - const char *name; - irqreturn_t (*handler)(int irq, void *data); -} pm860x_irq_descs[] = { - { "usb supply detect", pm860x_charger_handler }, - { "charge done", pm860x_done_handler }, - { "charge timeout", pm860x_exception_handler }, - { "charge fault", pm860x_exception_handler }, - { "temperature", pm860x_temp_handler }, - { "vbatt", pm860x_vbattery_handler }, - { "vchg", pm860x_vchg_handler }, -}; - -static const struct power_supply_desc pm860x_charger_desc = { - .name = "usb", - .type = POWER_SUPPLY_TYPE_USB, - .properties = pm860x_usb_props, - .num_properties = ARRAY_SIZE(pm860x_usb_props), - .get_property = pm860x_usb_get_prop, -}; - -static int pm860x_charger_probe(struct platform_device *pdev) -{ - struct pm860x_chip *chip = dev_get_drvdata(pdev->dev.parent); - struct power_supply_config psy_cfg = {}; - struct pm860x_charger_info *info; - int ret; - int count; - int i; - int j; - - info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL); - if (!info) - return -ENOMEM; - - count = pdev->num_resources; - for (i = 0, j = 0; i < count; i++) { - info->irq[j] = platform_get_irq(pdev, i); - if (info->irq[j] < 0) - continue; - j++; - } - info->irq_nums = j; - - info->chip = chip; - info->i2c = - (chip->id == CHIP_PM8607) ? chip->client : chip->companion; - info->i2c_8606 = - (chip->id == CHIP_PM8607) ? chip->companion : chip->client; - if (!info->i2c_8606) { - dev_err(&pdev->dev, "Missed I2C address of 88PM8606!\n"); - ret = -EINVAL; - goto out; - } - info->dev = &pdev->dev; - - /* set init value for the case we are not using battery */ - set_vchg_threshold(info, VCHG_NORMAL_LOW, VCHG_OVP_LOW); - - mutex_init(&info->lock); - platform_set_drvdata(pdev, info); - - psy_cfg.drv_data = info; - psy_cfg.supplied_to = pm860x_supplied_to; - psy_cfg.num_supplicants = ARRAY_SIZE(pm860x_supplied_to); - info->usb = power_supply_register(&pdev->dev, &pm860x_charger_desc, - &psy_cfg); - if (IS_ERR(info->usb)) { - ret = PTR_ERR(info->usb); - goto out; - } - - pm860x_init_charger(info); - - for (i = 0; i < ARRAY_SIZE(info->irq); i++) { - ret = request_threaded_irq(info->irq[i], NULL, - pm860x_irq_descs[i].handler, - IRQF_ONESHOT, pm860x_irq_descs[i].name, info); - if (ret < 0) { - dev_err(chip->dev, "Failed to request IRQ: #%d: %d\n", - info->irq[i], ret); - goto out_irq; - } - } - return 0; - -out_irq: - power_supply_unregister(info->usb); - while (--i >= 0) - free_irq(info->irq[i], info); -out: - return ret; -} - -static int pm860x_charger_remove(struct platform_device *pdev) -{ - struct pm860x_charger_info *info = platform_get_drvdata(pdev); - int i; - - power_supply_unregister(info->usb); - for (i = 0; i < info->irq_nums; i++) - free_irq(info->irq[i], info); - return 0; -} - -static struct platform_driver pm860x_charger_driver = { - .driver = { - .name = "88pm860x-charger", - }, - .probe = pm860x_charger_probe, - .remove = pm860x_charger_remove, -}; -module_platform_driver(pm860x_charger_driver); - -MODULE_DESCRIPTION("Marvell 88PM860x Charger driver"); -MODULE_LICENSE("GPL"); diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig index acd4a1524a1e..63454b5cac27 100644 --- a/drivers/power/Kconfig +++ b/drivers/power/Kconfig @@ -1,517 +1,3 @@ -menuconfig POWER_SUPPLY - bool "Power supply class support" - help - Say Y here to enable power supply class support. This allows - power supply (batteries, AC, USB) monitoring by userspace - via sysfs and uevent (if available) and/or APM kernel interface - (if selected below). - -if POWER_SUPPLY - -config POWER_SUPPLY_DEBUG - bool "Power supply debug" - help - Say Y here to enable debugging messages for power supply class - and drivers. - -config PDA_POWER - tristate "Generic PDA/phone power driver" - depends on !S390 - help - Say Y here to enable generic power driver for PDAs and phones with - one or two external power supplies (AC/USB) connected to main and - backup batteries, and optional builtin charger. - -config APM_POWER - tristate "APM emulation for class batteries" - depends on APM_EMULATION - help - Say Y here to enable support APM status emulation using - battery class devices. - -config GENERIC_ADC_BATTERY - tristate "Generic battery support using IIO" - depends on IIO - help - Say Y here to enable support for the generic battery driver - which uses IIO framework to read adc. - -config MAX8925_POWER - tristate "MAX8925 battery charger support" - depends on MFD_MAX8925 - help - Say Y here to enable support for the battery charger in the Maxim - MAX8925 PMIC. - -config WM831X_BACKUP - tristate "WM831X backup battery charger support" - depends on MFD_WM831X - help - Say Y here to enable support for the backup battery charger - in the Wolfson Microelectronics WM831x PMICs. - -config WM831X_POWER - tristate "WM831X PMU support" - depends on MFD_WM831X - help - Say Y here to enable support for the power management unit - provided by Wolfson Microelectronics WM831x PMICs. - -config WM8350_POWER - tristate "WM8350 PMU support" - depends on MFD_WM8350 - help - Say Y here to enable support for the power management unit - provided by the Wolfson Microelectronics WM8350 PMIC. - -config TEST_POWER - tristate "Test power driver" - help - This driver is used for testing. It's safe to say M here. - -config BATTERY_88PM860X - tristate "Marvell 88PM860x battery driver" - depends on MFD_88PM860X - help - Say Y here to enable battery monitor for Marvell 88PM860x chip. - -config BATTERY_ACT8945A - tristate "Active-semi ACT8945A charger driver" - depends on MFD_ACT8945A || COMPILE_TEST - help - Say Y here to enable support for power supply provided by - Active-semi ActivePath ACT8945A charger. - -config BATTERY_DS2760 - tristate "DS2760 battery driver (HP iPAQ & others)" - depends on W1 && W1_SLAVE_DS2760 - help - Say Y here to enable support for batteries with ds2760 chip. - -config BATTERY_DS2780 - tristate "DS2780 battery driver" - depends on HAS_IOMEM - select W1 - select W1_SLAVE_DS2780 - help - Say Y here to enable support for batteries with ds2780 chip. - -config BATTERY_DS2781 - tristate "DS2781 battery driver" - depends on HAS_IOMEM - select W1 - select W1_SLAVE_DS2781 - help - If you enable this you will have the DS2781 battery driver support. - - The battery monitor chip is used in many batteries/devices - as the one who is responsible for charging/discharging/monitoring - Li+ batteries. - - If you are unsure, say N. - -config BATTERY_DS2782 - tristate "DS2782/DS2786 standalone gas-gauge" - depends on I2C - help - Say Y here to enable support for the DS2782/DS2786 standalone battery - gas-gauge. - -config BATTERY_PMU - tristate "Apple PMU battery" - depends on PPC32 && ADB_PMU - help - Say Y here to expose battery information on Apple machines - through the generic battery class. - -config BATTERY_OLPC - tristate "One Laptop Per Child battery" - depends on X86_32 && OLPC - help - Say Y to enable support for the battery on the OLPC laptop. - -config BATTERY_TOSA - tristate "Sharp SL-6000 (tosa) battery" - depends on MACH_TOSA && MFD_TC6393XB && TOUCHSCREEN_WM97XX - help - Say Y to enable support for the battery on the Sharp Zaurus - SL-6000 (tosa) models. - -config BATTERY_COLLIE - tristate "Sharp SL-5500 (collie) battery" - depends on SA1100_COLLIE && MCP_UCB1200 - help - Say Y to enable support for the battery on the Sharp Zaurus - SL-5500 (collie) models. - -config BATTERY_IPAQ_MICRO - tristate "iPAQ Atmel Micro ASIC battery driver" - depends on MFD_IPAQ_MICRO - help - Choose this option if you want to monitor battery status on - Compaq/HP iPAQ h3100 and h3600. - -config BATTERY_WM97XX - bool "WM97xx generic battery driver" - depends on TOUCHSCREEN_WM97XX=y - help - Say Y to enable support for battery measured by WM97xx aux port. - -config BATTERY_SBS - tristate "SBS Compliant gas gauge" - depends on I2C - help - Say Y to include support for SBS battery driver for SBS-compliant - gas gauges. - -config BATTERY_BQ27XXX - tristate "BQ27xxx battery driver" - help - Say Y here to enable support for batteries with BQ27xxx chips. - -config BATTERY_BQ27XXX_I2C - tristate "BQ27xxx I2C support" - depends on BATTERY_BQ27XXX - depends on I2C - default y - help - Say Y here to enable support for batteries with BQ27xxx chips - connected over an I2C bus. - -config BATTERY_DA9030 - tristate "DA9030 battery driver" - depends on PMIC_DA903X - help - Say Y here to enable support for batteries charger integrated into - DA9030 PMIC. - -config BATTERY_DA9052 - tristate "Dialog DA9052 Battery" - depends on PMIC_DA9052 - help - Say Y here to enable support for batteries charger integrated into - DA9052 PMIC. - -config CHARGER_DA9150 - tristate "Dialog Semiconductor DA9150 Charger support" - depends on MFD_DA9150 - depends on DA9150_GPADC - depends on IIO - help - Say Y here to enable support for charger unit of the DA9150 - Integrated Charger & Fuel-Gauge IC. - - This driver can also be built as a module. If so, the module will be - called da9150-charger. - -config BATTERY_DA9150 - tristate "Dialog Semiconductor DA9150 Fuel Gauge support" - depends on MFD_DA9150 - help - Say Y here to enable support for the Fuel-Gauge unit of the DA9150 - Integrated Charger & Fuel-Gauge IC - - This driver can also be built as a module. If so, the module will be - called da9150-fg. - -config AXP288_CHARGER - tristate "X-Powers AXP288 Charger" - depends on MFD_AXP20X && EXTCON_AXP288 - help - Say yes here to have support X-Power AXP288 power management IC (PMIC) - integrated charger. - -config AXP288_FUEL_GAUGE - tristate "X-Powers AXP288 Fuel Gauge" - depends on MFD_AXP20X && IIO - help - Say yes here to have support for X-Power power management IC (PMIC) - Fuel Gauge. The device provides battery statistics and status - monitoring as well as alerts for battery over/under voltage and - over/under temperature. - -config BATTERY_MAX17040 - tristate "Maxim MAX17040 Fuel Gauge" - depends on I2C - help - MAX17040 is fuel-gauge systems for lithium-ion (Li+) batteries - in handheld and portable equipment. The MAX17040 is configured - to operate with a single lithium cell - -config BATTERY_MAX17042 - tristate "Maxim MAX17042/17047/17050/8997/8966 Fuel Gauge" - depends on I2C - select REGMAP_I2C - help - MAX17042 is fuel-gauge systems for lithium-ion (Li+) batteries - in handheld and portable equipment. The MAX17042 is configured - to operate with a single lithium cell. MAX8997 and MAX8966 are - multi-function devices that include fuel gauages that are compatible - with MAX17042. This driver also supports max17047/50 chips which are - improved version of max17042. - -config BATTERY_Z2 - tristate "Z2 battery driver" - depends on I2C && MACH_ZIPIT2 - help - Say Y to include support for the battery on the Zipit Z2. - -config BATTERY_S3C_ADC - tristate "Battery driver for Samsung ADC based monitoring" - depends on S3C_ADC - help - Say Y here to enable support for iPAQ h1930/h1940/rx1950 battery - -config BATTERY_TWL4030_MADC - tristate "TWL4030 MADC battery driver" - depends on TWL4030_MADC - help - Say Y here to enable this dumb driver for batteries managed - through the TWL4030 MADC. - -config CHARGER_88PM860X - tristate "Marvell 88PM860x Charger driver" - depends on MFD_88PM860X && BATTERY_88PM860X - help - Say Y here to enable charger for Marvell 88PM860x chip. - -config CHARGER_PCF50633 - tristate "NXP PCF50633 MBC" - depends on MFD_PCF50633 - help - Say Y to include support for NXP PCF50633 Main Battery Charger. - -config BATTERY_JZ4740 - tristate "Ingenic JZ4740 battery" - depends on MACH_JZ4740 - depends on MFD_JZ4740_ADC - help - Say Y to enable support for the battery on Ingenic JZ4740 based - boards. - - 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 - help - Say Y here to enable support for battery information on Nokia - RX-51, also known as N900 tablet. - -config CHARGER_ISP1704 - tristate "ISP1704 USB Charger Detection" - depends on USB_PHY - depends on USB_GADGET || !USB_GADGET # if USB_GADGET=m, this can't be 'y' - help - Say Y to enable support for USB Charger Detection with - ISP1707/ISP1704 USB transceivers. - -config CHARGER_MAX8903 - tristate "MAX8903 Battery DC-DC Charger for USB and Adapter Power" - help - Say Y to enable support for the MAX8903 DC-DC charger and sysfs. - The driver supports controlling charger-enable and current-limit - pins based on the status of charger connections with interrupt - handlers. - -config CHARGER_TWL4030 - tristate "OMAP TWL4030 BCI charger driver" - depends on IIO && TWL4030_CORE - help - Say Y here to enable support for TWL4030 Battery Charge Interface. - -config CHARGER_LP8727 - tristate "TI/National Semiconductor LP8727 charger driver" - depends on I2C - help - Say Y here to enable support for LP8727 Charger Driver. - -config CHARGER_LP8788 - tristate "TI LP8788 charger driver" - depends on MFD_LP8788 - depends on LP8788_ADC - depends on IIO - help - Say Y to enable support for the LP8788 linear charger. - -config CHARGER_GPIO - tristate "GPIO charger" - depends on GPIOLIB || COMPILE_TEST - help - Say Y to include support for chargers which report their online status - through a GPIO pin. - - This driver can be build as a module. If so, the module will be - called gpio-charger. - -config CHARGER_MANAGER - bool "Battery charger manager for multiple chargers" - depends on REGULATOR - select EXTCON - help - Say Y to enable charger-manager support, which allows multiple - chargers attached to a battery and multiple batteries attached to a - system. The charger-manager also can monitor charging status in - runtime and in suspend-to-RAM by waking up the system periodically - with help of suspend_again support. - -config CHARGER_MAX14577 - tristate "Maxim MAX14577/77836 battery charger driver" - depends on MFD_MAX14577 - help - Say Y to enable support for the battery charger control sysfs and - platform data of MAX14577/77836 MUICs. - -config CHARGER_MAX77693 - tristate "Maxim MAX77693 battery charger driver" - depends on MFD_MAX77693 - help - Say Y to enable support for the Maxim MAX77693 battery charger. - -config CHARGER_MAX8997 - tristate "Maxim MAX8997/MAX8966 PMIC battery charger driver" - depends on MFD_MAX8997 && REGULATOR_MAX8997 - help - Say Y to enable support for the battery charger control sysfs and - platform data of MAX8997/LP3974 PMICs. - -config CHARGER_MAX8998 - tristate "Maxim MAX8998/LP3974 PMIC battery charger driver" - depends on MFD_MAX8998 && REGULATOR_MAX8998 - help - 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 - depends on EXTCON - 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 - help - Say Y to enable support for the TI BQ2415x battery charger - PMICs. - - You'll need this driver to charge batteries on e.g. Nokia - RX-51/N900. - -config CHARGER_BQ24190 - tristate "TI BQ24190 battery charger driver" - depends on I2C - depends on GPIOLIB || COMPILE_TEST - help - Say Y to enable support for the TI BQ24190 battery charger. - -config CHARGER_BQ24257 - 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 BQ24250, BQ24251, and BQ24257 battery - chargers. - -config CHARGER_BQ24735 - tristate "TI BQ24735 battery charger support" - depends on I2C - depends on GPIOLIB || COMPILE_TEST - help - Say Y to enable support for the TI BQ24735 battery charger. - -config CHARGER_BQ25890 - tristate "TI BQ25890 battery charger driver" - depends on I2C - depends on GPIOLIB || COMPILE_TEST - select REGMAP_I2C - help - Say Y to enable support for the TI BQ25890 battery charger. - -config CHARGER_SMB347 - tristate "Summit Microelectronics SMB347 Battery Charger" - depends on I2C - select REGMAP_I2C - help - Say Y to include support for Summit Microelectronics SMB347 - Battery Charger. - -config CHARGER_TPS65090 - tristate "TPS65090 battery charger driver" - depends on MFD_TPS65090 - help - 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 - help - Say Y here to include support for LTC2941 and LTC2943 Battery - Gauge IC. The driver reports the charge count continuously, and - measures the voltage and temperature every 10 seconds. - -config AB8500_BM - bool "AB8500 Battery Management Driver" - depends on AB8500_CORE && AB8500_GPADC - help - Say Y to include support for AB8500 battery management. - -config BATTERY_GOLDFISH - tristate "Goldfish battery driver" - depends on GOLDFISH || COMPILE_TEST - depends on HAS_IOMEM - help - Say Y to enable support for the battery and AC power in the - Goldfish emulator. - -config BATTERY_RT5033 - tristate "RT5033 fuel gauge support" - depends on MFD_RT5033 - help - This adds support for battery fuel gauge in Richtek RT5033 PMIC. - The fuelgauge calculates and determines the battery state of charge - according to battery open circuit voltage. - -config CHARGER_RT9455 - tristate "Richtek RT9455 battery charger driver" - depends on I2C - depends on GPIOLIB || COMPILE_TEST - select REGMAP_I2C - 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. - -endif # POWER_SUPPLY - -source "drivers/power/reset/Kconfig" source "drivers/power/avs/Kconfig" +source "drivers/power/reset/Kconfig" +source "drivers/power/supply/Kconfig" diff --git a/drivers/power/Makefile b/drivers/power/Makefile index e46b75d448a5..ff35c712d824 100644 --- a/drivers/power/Makefile +++ b/drivers/power/Makefile @@ -1,76 +1,3 @@ -subdir-ccflags-$(CONFIG_POWER_SUPPLY_DEBUG) := -DDEBUG - -power_supply-y := power_supply_core.o -power_supply-$(CONFIG_SYSFS) += power_supply_sysfs.o -power_supply-$(CONFIG_LEDS_TRIGGERS) += power_supply_leds.o - -obj-$(CONFIG_POWER_SUPPLY) += power_supply.o -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 -obj-$(CONFIG_WM8350_POWER) += wm8350_power.o -obj-$(CONFIG_TEST_POWER) += test_power.o - -obj-$(CONFIG_BATTERY_88PM860X) += 88pm860x_battery.o -obj-$(CONFIG_BATTERY_ACT8945A) += act8945a_charger.o -obj-$(CONFIG_BATTERY_DS2760) += ds2760_battery.o -obj-$(CONFIG_BATTERY_DS2780) += ds2780_battery.o -obj-$(CONFIG_BATTERY_DS2781) += ds2781_battery.o -obj-$(CONFIG_BATTERY_DS2782) += ds2782_battery.o -obj-$(CONFIG_BATTERY_GAUGE_LTC2941) += ltc2941-battery-gauge.o -obj-$(CONFIG_BATTERY_GOLDFISH) += goldfish_battery.o -obj-$(CONFIG_BATTERY_PMU) += pmu_battery.o -obj-$(CONFIG_BATTERY_OLPC) += olpc_battery.o -obj-$(CONFIG_BATTERY_TOSA) += tosa_battery.o -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_BQ27XXX) += bq27xxx_battery.o -obj-$(CONFIG_BATTERY_BQ27XXX_I2C) += bq27xxx_battery_i2c.o -obj-$(CONFIG_BATTERY_DA9030) += da9030_battery.o -obj-$(CONFIG_BATTERY_DA9052) += da9052-battery.o -obj-$(CONFIG_CHARGER_DA9150) += da9150-charger.o -obj-$(CONFIG_BATTERY_DA9150) += da9150-fg.o -obj-$(CONFIG_BATTERY_MAX17040) += max17040_battery.o -obj-$(CONFIG_BATTERY_MAX17042) += max17042_battery.o -obj-$(CONFIG_BATTERY_Z2) += z2_battery.o -obj-$(CONFIG_BATTERY_RT5033) += rt5033_battery.o -obj-$(CONFIG_CHARGER_RT9455) += rt9455_charger.o -obj-$(CONFIG_BATTERY_S3C_ADC) += s3c_adc_battery.o -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 -obj-$(CONFIG_CHARGER_MAX8903) += max8903_charger.o -obj-$(CONFIG_CHARGER_TWL4030) += twl4030_charger.o -obj-$(CONFIG_CHARGER_LP8727) += lp8727_charger.o -obj-$(CONFIG_CHARGER_LP8788) += lp8788-charger.o -obj-$(CONFIG_CHARGER_GPIO) += gpio-charger.o -obj-$(CONFIG_CHARGER_MANAGER) += charger-manager.o -obj-$(CONFIG_CHARGER_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 -obj-$(CONFIG_CHARGER_BQ24735) += bq24735-charger.o -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 +obj-$(CONFIG_POWER_SUPPLY) += supply/ diff --git a/drivers/power/ab8500_bmdata.c b/drivers/power/ab8500_bmdata.c deleted file mode 100644 index d29864533093..000000000000 --- a/drivers/power/ab8500_bmdata.c +++ /dev/null @@ -1,605 +0,0 @@ -#include -#include -#include -#include -#include -#include - -/* - * These are the defined batteries that uses a NTC and ID resistor placed - * inside of the battery pack. - * Note that the res_to_temp table must be strictly sorted by falling resistance - * values to work. - */ -const struct abx500_res_to_temp ab8500_temp_tbl_a_thermistor[] = { - {-5, 53407}, - { 0, 48594}, - { 5, 43804}, - {10, 39188}, - {15, 34870}, - {20, 30933}, - {25, 27422}, - {30, 24347}, - {35, 21694}, - {40, 19431}, - {45, 17517}, - {50, 15908}, - {55, 14561}, - {60, 13437}, - {65, 12500}, -}; -EXPORT_SYMBOL(ab8500_temp_tbl_a_thermistor); - -const int ab8500_temp_tbl_a_size = ARRAY_SIZE(ab8500_temp_tbl_a_thermistor); -EXPORT_SYMBOL(ab8500_temp_tbl_a_size); - -const struct abx500_res_to_temp ab8500_temp_tbl_b_thermistor[] = { - {-5, 200000}, - { 0, 159024}, - { 5, 151921}, - {10, 144300}, - {15, 136424}, - {20, 128565}, - {25, 120978}, - {30, 113875}, - {35, 107397}, - {40, 101629}, - {45, 96592}, - {50, 92253}, - {55, 88569}, - {60, 85461}, - {65, 82869}, -}; -EXPORT_SYMBOL(ab8500_temp_tbl_b_thermistor); - -const int ab8500_temp_tbl_b_size = ARRAY_SIZE(ab8500_temp_tbl_b_thermistor); -EXPORT_SYMBOL(ab8500_temp_tbl_b_size); - -static const struct abx500_v_to_cap cap_tbl_a_thermistor[] = { - {4171, 100}, - {4114, 95}, - {4009, 83}, - {3947, 74}, - {3907, 67}, - {3863, 59}, - {3830, 56}, - {3813, 53}, - {3791, 46}, - {3771, 33}, - {3754, 25}, - {3735, 20}, - {3717, 17}, - {3681, 13}, - {3664, 8}, - {3651, 6}, - {3635, 5}, - {3560, 3}, - {3408, 1}, - {3247, 0}, -}; - -static const struct abx500_v_to_cap cap_tbl_b_thermistor[] = { - {4161, 100}, - {4124, 98}, - {4044, 90}, - {4003, 85}, - {3966, 80}, - {3933, 75}, - {3888, 67}, - {3849, 60}, - {3813, 55}, - {3787, 47}, - {3772, 30}, - {3751, 25}, - {3718, 20}, - {3681, 16}, - {3660, 14}, - {3589, 10}, - {3546, 7}, - {3495, 4}, - {3404, 2}, - {3250, 0}, -}; - -static const struct abx500_v_to_cap cap_tbl[] = { - {4186, 100}, - {4163, 99}, - {4114, 95}, - {4068, 90}, - {3990, 80}, - {3926, 70}, - {3898, 65}, - {3866, 60}, - {3833, 55}, - {3812, 50}, - {3787, 40}, - {3768, 30}, - {3747, 25}, - {3730, 20}, - {3705, 15}, - {3699, 14}, - {3684, 12}, - {3672, 9}, - {3657, 7}, - {3638, 6}, - {3556, 4}, - {3424, 2}, - {3317, 1}, - {3094, 0}, -}; - -/* - * Note that the res_to_temp table must be strictly sorted by falling - * resistance values to work. - */ -static const struct abx500_res_to_temp temp_tbl[] = { - {-5, 214834}, - { 0, 162943}, - { 5, 124820}, - {10, 96520}, - {15, 75306}, - {20, 59254}, - {25, 47000}, - {30, 37566}, - {35, 30245}, - {40, 24520}, - {45, 20010}, - {50, 16432}, - {55, 13576}, - {60, 11280}, - {65, 9425}, -}; - -/* - * Note that the batres_vs_temp table must be strictly sorted by falling - * temperature values to work. - */ -static const struct batres_vs_temp temp_to_batres_tbl_thermistor[] = { - { 40, 120}, - { 30, 135}, - { 20, 165}, - { 10, 230}, - { 00, 325}, - {-10, 445}, - {-20, 595}, -}; - -/* - * Note that the batres_vs_temp table must be strictly sorted by falling - * temperature values to work. - */ -static const struct batres_vs_temp temp_to_batres_tbl_ext_thermistor[] = { - { 60, 300}, - { 30, 300}, - { 20, 300}, - { 10, 300}, - { 00, 300}, - {-10, 300}, - {-20, 300}, -}; - -/* battery resistance table for LI ION 9100 battery */ -static const struct batres_vs_temp temp_to_batres_tbl_9100[] = { - { 60, 180}, - { 30, 180}, - { 20, 180}, - { 10, 180}, - { 00, 180}, - {-10, 180}, - {-20, 180}, -}; - -static struct abx500_battery_type bat_type_thermistor[] = { - [BATTERY_UNKNOWN] = { - /* First element always represent the UNKNOWN battery */ - .name = POWER_SUPPLY_TECHNOLOGY_UNKNOWN, - .resis_high = 0, - .resis_low = 0, - .battery_resistance = 300, - .charge_full_design = 612, - .nominal_voltage = 3700, - .termination_vol = 4050, - .termination_curr = 200, - .recharge_cap = 95, - .normal_cur_lvl = 400, - .normal_vol_lvl = 4100, - .maint_a_cur_lvl = 400, - .maint_a_vol_lvl = 4050, - .maint_a_chg_timer_h = 60, - .maint_b_cur_lvl = 400, - .maint_b_vol_lvl = 4000, - .maint_b_chg_timer_h = 200, - .low_high_cur_lvl = 300, - .low_high_vol_lvl = 4000, - .n_temp_tbl_elements = ARRAY_SIZE(temp_tbl), - .r_to_t_tbl = temp_tbl, - .n_v_cap_tbl_elements = ARRAY_SIZE(cap_tbl), - .v_to_cap_tbl = cap_tbl, - .n_batres_tbl_elements = ARRAY_SIZE(temp_to_batres_tbl_thermistor), - .batres_tbl = temp_to_batres_tbl_thermistor, - }, - { - .name = POWER_SUPPLY_TECHNOLOGY_LIPO, - .resis_high = 53407, - .resis_low = 12500, - .battery_resistance = 300, - .charge_full_design = 900, - .nominal_voltage = 3600, - .termination_vol = 4150, - .termination_curr = 80, - .recharge_cap = 95, - .normal_cur_lvl = 700, - .normal_vol_lvl = 4200, - .maint_a_cur_lvl = 600, - .maint_a_vol_lvl = 4150, - .maint_a_chg_timer_h = 60, - .maint_b_cur_lvl = 600, - .maint_b_vol_lvl = 4100, - .maint_b_chg_timer_h = 200, - .low_high_cur_lvl = 300, - .low_high_vol_lvl = 4000, - .n_temp_tbl_elements = ARRAY_SIZE(ab8500_temp_tbl_a_thermistor), - .r_to_t_tbl = ab8500_temp_tbl_a_thermistor, - .n_v_cap_tbl_elements = ARRAY_SIZE(cap_tbl_a_thermistor), - .v_to_cap_tbl = cap_tbl_a_thermistor, - .n_batres_tbl_elements = ARRAY_SIZE(temp_to_batres_tbl_thermistor), - .batres_tbl = temp_to_batres_tbl_thermistor, - - }, - { - .name = POWER_SUPPLY_TECHNOLOGY_LIPO, - .resis_high = 200000, - .resis_low = 82869, - .battery_resistance = 300, - .charge_full_design = 900, - .nominal_voltage = 3600, - .termination_vol = 4150, - .termination_curr = 80, - .recharge_cap = 95, - .normal_cur_lvl = 700, - .normal_vol_lvl = 4200, - .maint_a_cur_lvl = 600, - .maint_a_vol_lvl = 4150, - .maint_a_chg_timer_h = 60, - .maint_b_cur_lvl = 600, - .maint_b_vol_lvl = 4100, - .maint_b_chg_timer_h = 200, - .low_high_cur_lvl = 300, - .low_high_vol_lvl = 4000, - .n_temp_tbl_elements = ARRAY_SIZE(ab8500_temp_tbl_b_thermistor), - .r_to_t_tbl = ab8500_temp_tbl_b_thermistor, - .n_v_cap_tbl_elements = ARRAY_SIZE(cap_tbl_b_thermistor), - .v_to_cap_tbl = cap_tbl_b_thermistor, - .n_batres_tbl_elements = ARRAY_SIZE(temp_to_batres_tbl_thermistor), - .batres_tbl = temp_to_batres_tbl_thermistor, - }, -}; - -static struct abx500_battery_type bat_type_ext_thermistor[] = { - [BATTERY_UNKNOWN] = { - /* First element always represent the UNKNOWN battery */ - .name = POWER_SUPPLY_TECHNOLOGY_UNKNOWN, - .resis_high = 0, - .resis_low = 0, - .battery_resistance = 300, - .charge_full_design = 612, - .nominal_voltage = 3700, - .termination_vol = 4050, - .termination_curr = 200, - .recharge_cap = 95, - .normal_cur_lvl = 400, - .normal_vol_lvl = 4100, - .maint_a_cur_lvl = 400, - .maint_a_vol_lvl = 4050, - .maint_a_chg_timer_h = 60, - .maint_b_cur_lvl = 400, - .maint_b_vol_lvl = 4000, - .maint_b_chg_timer_h = 200, - .low_high_cur_lvl = 300, - .low_high_vol_lvl = 4000, - .n_temp_tbl_elements = ARRAY_SIZE(temp_tbl), - .r_to_t_tbl = temp_tbl, - .n_v_cap_tbl_elements = ARRAY_SIZE(cap_tbl), - .v_to_cap_tbl = cap_tbl, - .n_batres_tbl_elements = ARRAY_SIZE(temp_to_batres_tbl_thermistor), - .batres_tbl = temp_to_batres_tbl_thermistor, - }, -/* - * These are the batteries that doesn't have an internal NTC resistor to measure - * its temperature. The temperature in this case is measure with a NTC placed - * near the battery but on the PCB. - */ - { - .name = POWER_SUPPLY_TECHNOLOGY_LIPO, - .resis_high = 76000, - .resis_low = 53000, - .battery_resistance = 300, - .charge_full_design = 900, - .nominal_voltage = 3700, - .termination_vol = 4150, - .termination_curr = 100, - .recharge_cap = 95, - .normal_cur_lvl = 700, - .normal_vol_lvl = 4200, - .maint_a_cur_lvl = 600, - .maint_a_vol_lvl = 4150, - .maint_a_chg_timer_h = 60, - .maint_b_cur_lvl = 600, - .maint_b_vol_lvl = 4100, - .maint_b_chg_timer_h = 200, - .low_high_cur_lvl = 300, - .low_high_vol_lvl = 4000, - .n_temp_tbl_elements = ARRAY_SIZE(temp_tbl), - .r_to_t_tbl = temp_tbl, - .n_v_cap_tbl_elements = ARRAY_SIZE(cap_tbl), - .v_to_cap_tbl = cap_tbl, - .n_batres_tbl_elements = ARRAY_SIZE(temp_to_batres_tbl_thermistor), - .batres_tbl = temp_to_batres_tbl_thermistor, - }, - { - .name = POWER_SUPPLY_TECHNOLOGY_LION, - .resis_high = 30000, - .resis_low = 10000, - .battery_resistance = 300, - .charge_full_design = 950, - .nominal_voltage = 3700, - .termination_vol = 4150, - .termination_curr = 100, - .recharge_cap = 95, - .normal_cur_lvl = 700, - .normal_vol_lvl = 4200, - .maint_a_cur_lvl = 600, - .maint_a_vol_lvl = 4150, - .maint_a_chg_timer_h = 60, - .maint_b_cur_lvl = 600, - .maint_b_vol_lvl = 4100, - .maint_b_chg_timer_h = 200, - .low_high_cur_lvl = 300, - .low_high_vol_lvl = 4000, - .n_temp_tbl_elements = ARRAY_SIZE(temp_tbl), - .r_to_t_tbl = temp_tbl, - .n_v_cap_tbl_elements = ARRAY_SIZE(cap_tbl), - .v_to_cap_tbl = cap_tbl, - .n_batres_tbl_elements = ARRAY_SIZE(temp_to_batres_tbl_thermistor), - .batres_tbl = temp_to_batres_tbl_thermistor, - }, - { - .name = POWER_SUPPLY_TECHNOLOGY_LION, - .resis_high = 95000, - .resis_low = 76001, - .battery_resistance = 300, - .charge_full_design = 950, - .nominal_voltage = 3700, - .termination_vol = 4150, - .termination_curr = 100, - .recharge_cap = 95, - .normal_cur_lvl = 700, - .normal_vol_lvl = 4200, - .maint_a_cur_lvl = 600, - .maint_a_vol_lvl = 4150, - .maint_a_chg_timer_h = 60, - .maint_b_cur_lvl = 600, - .maint_b_vol_lvl = 4100, - .maint_b_chg_timer_h = 200, - .low_high_cur_lvl = 300, - .low_high_vol_lvl = 4000, - .n_temp_tbl_elements = ARRAY_SIZE(temp_tbl), - .r_to_t_tbl = temp_tbl, - .n_v_cap_tbl_elements = ARRAY_SIZE(cap_tbl), - .v_to_cap_tbl = cap_tbl, - .n_batres_tbl_elements = ARRAY_SIZE(temp_to_batres_tbl_thermistor), - .batres_tbl = temp_to_batres_tbl_thermistor, - }, -}; - -static const struct abx500_bm_capacity_levels cap_levels = { - .critical = 2, - .low = 10, - .normal = 70, - .high = 95, - .full = 100, -}; - -static const struct abx500_fg_parameters fg = { - .recovery_sleep_timer = 10, - .recovery_total_time = 100, - .init_timer = 1, - .init_discard_time = 5, - .init_total_time = 40, - .high_curr_time = 60, - .accu_charging = 30, - .accu_high_curr = 30, - .high_curr_threshold = 50, - .lowbat_threshold = 3100, - .battok_falling_th_sel0 = 2860, - .battok_raising_th_sel1 = 2860, - .maint_thres = 95, - .user_cap_limit = 15, - .pcut_enable = 1, - .pcut_max_time = 127, - .pcut_flag_time = 112, - .pcut_max_restart = 15, - .pcut_debounce_time = 2, -}; - -static const struct abx500_maxim_parameters ab8500_maxi_params = { - .ena_maxi = true, - .chg_curr = 910, - .wait_cycles = 10, - .charger_curr_step = 100, -}; - -static const struct abx500_maxim_parameters abx540_maxi_params = { - .ena_maxi = true, - .chg_curr = 3000, - .wait_cycles = 10, - .charger_curr_step = 200, -}; - -static const struct abx500_bm_charger_parameters chg = { - .usb_volt_max = 5500, - .usb_curr_max = 1500, - .ac_volt_max = 7500, - .ac_curr_max = 1500, -}; - -/* - * This array maps the raw hex value to charger output current used by the - * AB8500 values - */ -static int ab8500_charge_output_curr_map[] = { - 100, 200, 300, 400, 500, 600, 700, 800, - 900, 1000, 1100, 1200, 1300, 1400, 1500, 1500, -}; - -static int ab8540_charge_output_curr_map[] = { - 0, 0, 0, 75, 100, 125, 150, 175, - 200, 225, 250, 275, 300, 325, 350, 375, - 400, 425, 450, 475, 500, 525, 550, 575, - 600, 625, 650, 675, 700, 725, 750, 775, - 800, 825, 850, 875, 900, 925, 950, 975, - 1000, 1025, 1050, 1075, 1100, 1125, 1150, 1175, - 1200, 1225, 1250, 1275, 1300, 1325, 1350, 1375, - 1400, 1425, 1450, 1500, 1600, 1700, 1900, 2000, -}; - -/* - * This array maps the raw hex value to charger input current used by the - * AB8500 values - */ -static int ab8500_charge_input_curr_map[] = { - 50, 98, 193, 290, 380, 450, 500, 600, - 700, 800, 900, 1000, 1100, 1300, 1400, 1500, -}; - -static int ab8540_charge_input_curr_map[] = { - 25, 50, 75, 100, 125, 150, 175, 200, - 225, 250, 275, 300, 325, 350, 375, 400, - 425, 450, 475, 500, 525, 550, 575, 600, - 625, 650, 675, 700, 725, 750, 775, 800, - 825, 850, 875, 900, 925, 950, 975, 1000, - 1025, 1050, 1075, 1100, 1125, 1150, 1175, 1200, - 1225, 1250, 1275, 1300, 1325, 1350, 1375, 1400, - 1425, 1450, 1475, 1500, 1500, 1500, 1500, 1500, -}; - -struct abx500_bm_data ab8500_bm_data = { - .temp_under = 3, - .temp_low = 8, - .temp_high = 43, - .temp_over = 48, - .main_safety_tmr_h = 4, - .temp_interval_chg = 20, - .temp_interval_nochg = 120, - .usb_safety_tmr_h = 4, - .bkup_bat_v = BUP_VCH_SEL_2P6V, - .bkup_bat_i = BUP_ICH_SEL_150UA, - .no_maintenance = false, - .capacity_scaling = false, - .adc_therm = ABx500_ADC_THERM_BATCTRL, - .chg_unknown_bat = false, - .enable_overshoot = false, - .fg_res = 100, - .cap_levels = &cap_levels, - .bat_type = bat_type_thermistor, - .n_btypes = ARRAY_SIZE(bat_type_thermistor), - .batt_id = 0, - .interval_charging = 5, - .interval_not_charging = 120, - .temp_hysteresis = 3, - .gnd_lift_resistance = 34, - .chg_output_curr = ab8500_charge_output_curr_map, - .n_chg_out_curr = ARRAY_SIZE(ab8500_charge_output_curr_map), - .maxi = &ab8500_maxi_params, - .chg_params = &chg, - .fg_params = &fg, - .chg_input_curr = ab8500_charge_input_curr_map, - .n_chg_in_curr = ARRAY_SIZE(ab8500_charge_input_curr_map), -}; - -struct abx500_bm_data ab8540_bm_data = { - .temp_under = 3, - .temp_low = 8, - .temp_high = 43, - .temp_over = 48, - .main_safety_tmr_h = 4, - .temp_interval_chg = 20, - .temp_interval_nochg = 120, - .usb_safety_tmr_h = 4, - .bkup_bat_v = BUP_VCH_SEL_2P6V, - .bkup_bat_i = BUP_ICH_SEL_150UA, - .no_maintenance = false, - .capacity_scaling = false, - .adc_therm = ABx500_ADC_THERM_BATCTRL, - .chg_unknown_bat = false, - .enable_overshoot = false, - .fg_res = 100, - .cap_levels = &cap_levels, - .bat_type = bat_type_thermistor, - .n_btypes = ARRAY_SIZE(bat_type_thermistor), - .batt_id = 0, - .interval_charging = 5, - .interval_not_charging = 120, - .temp_hysteresis = 3, - .gnd_lift_resistance = 0, - .maxi = &abx540_maxi_params, - .chg_params = &chg, - .fg_params = &fg, - .chg_output_curr = ab8540_charge_output_curr_map, - .n_chg_out_curr = ARRAY_SIZE(ab8540_charge_output_curr_map), - .chg_input_curr = ab8540_charge_input_curr_map, - .n_chg_in_curr = ARRAY_SIZE(ab8540_charge_input_curr_map), -}; - -int ab8500_bm_of_probe(struct device *dev, - struct device_node *np, - struct abx500_bm_data *bm) -{ - const struct batres_vs_temp *tmp_batres_tbl; - struct device_node *battery_node; - const char *btech; - int i; - - /* get phandle to 'battery-info' node */ - battery_node = of_parse_phandle(np, "battery", 0); - if (!battery_node) { - dev_err(dev, "battery node or reference missing\n"); - return -EINVAL; - } - - btech = of_get_property(battery_node, "stericsson,battery-type", NULL); - if (!btech) { - dev_warn(dev, "missing property battery-name/type\n"); - return -EINVAL; - } - - if (strncmp(btech, "LION", 4) == 0) { - bm->no_maintenance = true; - bm->chg_unknown_bat = true; - bm->bat_type[BATTERY_UNKNOWN].charge_full_design = 2600; - bm->bat_type[BATTERY_UNKNOWN].termination_vol = 4150; - bm->bat_type[BATTERY_UNKNOWN].recharge_cap = 95; - bm->bat_type[BATTERY_UNKNOWN].normal_cur_lvl = 520; - bm->bat_type[BATTERY_UNKNOWN].normal_vol_lvl = 4200; - } - - if (of_property_read_bool(battery_node, "thermistor-on-batctrl")) { - if (strncmp(btech, "LION", 4) == 0) - tmp_batres_tbl = temp_to_batres_tbl_9100; - else - tmp_batres_tbl = temp_to_batres_tbl_thermistor; - } else { - bm->n_btypes = 4; - bm->bat_type = bat_type_ext_thermistor; - bm->adc_therm = ABx500_ADC_THERM_BATTEMP; - tmp_batres_tbl = temp_to_batres_tbl_ext_thermistor; - } - - /* select the battery resolution table */ - for (i = 0; i < bm->n_btypes; ++i) - bm->bat_type[i].batres_tbl = tmp_batres_tbl; - - of_node_put(battery_node); - - return 0; -} diff --git a/drivers/power/ab8500_btemp.c b/drivers/power/ab8500_btemp.c deleted file mode 100644 index bf2e5dd301e7..000000000000 --- a/drivers/power/ab8500_btemp.c +++ /dev/null @@ -1,1212 +0,0 @@ -/* - * Copyright (C) ST-Ericsson SA 2012 - * - * Battery temperature driver for AB8500 - * - * License Terms: GNU General Public License v2 - * Author: - * Johan Palsson - * Karl Komierowski - * Arun R Murthy - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define VTVOUT_V 1800 - -#define BTEMP_THERMAL_LOW_LIMIT -10 -#define BTEMP_THERMAL_MED_LIMIT 0 -#define BTEMP_THERMAL_HIGH_LIMIT_52 52 -#define BTEMP_THERMAL_HIGH_LIMIT_57 57 -#define BTEMP_THERMAL_HIGH_LIMIT_62 62 - -#define BTEMP_BATCTRL_CURR_SRC_7UA 7 -#define BTEMP_BATCTRL_CURR_SRC_20UA 20 - -#define BTEMP_BATCTRL_CURR_SRC_16UA 16 -#define BTEMP_BATCTRL_CURR_SRC_18UA 18 - -#define BTEMP_BATCTRL_CURR_SRC_60UA 60 -#define BTEMP_BATCTRL_CURR_SRC_120UA 120 - -/** - * struct ab8500_btemp_interrupts - ab8500 interrupts - * @name: name of the interrupt - * @isr function pointer to the isr - */ -struct ab8500_btemp_interrupts { - char *name; - irqreturn_t (*isr)(int irq, void *data); -}; - -struct ab8500_btemp_events { - bool batt_rem; - bool btemp_high; - bool btemp_medhigh; - bool btemp_lowmed; - bool btemp_low; - bool ac_conn; - bool usb_conn; -}; - -struct ab8500_btemp_ranges { - int btemp_high_limit; - int btemp_med_limit; - int btemp_low_limit; -}; - -/** - * struct ab8500_btemp - ab8500 BTEMP device information - * @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 - * @parent: Pointer to the struct ab8500 - * @gpadc: Pointer to the struct gpadc - * @fg: Pointer to the struct fg - * @bm: Platform specific battery management information - * @btemp_psy: Structure for BTEMP specific battery properties - * @events: Structure for information about events triggered - * @btemp_ranges: Battery temperature range structure - * @btemp_wq: Work queue for measuring the temperature periodically - * @btemp_periodic_work: Work for measuring the temperature periodically - * @initialized: True if battery id read. - */ -struct ab8500_btemp { - struct device *dev; - struct list_head node; - int curr_source; - int bat_temp; - int prev_bat_temp; - struct ab8500 *parent; - struct ab8500_gpadc *gpadc; - struct ab8500_fg *fg; - struct abx500_bm_data *bm; - struct power_supply *btemp_psy; - struct ab8500_btemp_events events; - struct ab8500_btemp_ranges btemp_ranges; - struct workqueue_struct *btemp_wq; - struct delayed_work btemp_periodic_work; - bool initialized; -}; - -/* BTEMP power supply properties */ -static enum power_supply_property ab8500_btemp_props[] = { - POWER_SUPPLY_PROP_PRESENT, - POWER_SUPPLY_PROP_ONLINE, - POWER_SUPPLY_PROP_TECHNOLOGY, - POWER_SUPPLY_PROP_TEMP, -}; - -static LIST_HEAD(ab8500_btemp_list); - -/** - * ab8500_btemp_get() - returns a reference to the primary AB8500 BTEMP - * (i.e. the first BTEMP in the instance 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; -} -EXPORT_SYMBOL(ab8500_btemp_get); - -/** - * ab8500_btemp_batctrl_volt_to_res() - convert batctrl voltage to resistance - * @di: pointer to the ab8500_btemp structure - * @v_batctrl: measured batctrl voltage - * @inst_curr: measured instant current - * - * This function returns the battery resistance that is - * derived from the BATCTRL voltage. - * Returns value in Ohms. - */ -static int ab8500_btemp_batctrl_volt_to_res(struct ab8500_btemp *di, - int v_batctrl, int inst_curr) -{ - int rbs; - - if (is_ab8500_1p1_or_earlier(di->parent)) { - /* - * For ABB cut1.0 and 1.1 BAT_CTRL is internally - * connected to 1.8V through a 450k resistor - */ - return (450000 * (v_batctrl)) / (1800 - v_batctrl); - } - - if (di->bm->adc_therm == ABx500_ADC_THERM_BATCTRL) { - /* - * If the battery has internal NTC, we use the current - * source to calculate the resistance. - */ - rbs = (v_batctrl * 1000 - - di->bm->gnd_lift_resistance * inst_curr) - / di->curr_source; - } else { - /* - * BAT_CTRL is internally - * connected to 1.8V through a 80k resistor - */ - rbs = (80000 * (v_batctrl)) / (1800 - v_batctrl); - } - - return rbs; -} - -/** - * ab8500_btemp_read_batctrl_voltage() - measure batctrl voltage - * @di: pointer to the ab8500_btemp structure - * - * This function returns the voltage on BATCTRL. Returns value in mV. - */ -static int ab8500_btemp_read_batctrl_voltage(struct ab8500_btemp *di) -{ - int vbtemp; - static int prev; - - vbtemp = ab8500_gpadc_convert(di->gpadc, BAT_CTRL); - if (vbtemp < 0) { - dev_err(di->dev, - "%s gpadc conversion failed, using previous value", - __func__); - return prev; - } - prev = vbtemp; - return vbtemp; -} - -/** - * ab8500_btemp_curr_source_enable() - enable/disable batctrl current source - * @di: pointer to the ab8500_btemp structure - * @enable: enable or disable the current source - * - * Enable or disable the current sources for the BatCtrl AD channel - */ -static int ab8500_btemp_curr_source_enable(struct ab8500_btemp *di, - bool enable) -{ - int curr; - int ret = 0; - - /* - * BATCTRL current sources are included on AB8500 cut2.0 - * and future versions - */ - if (is_ab8500_1p1_or_earlier(di->parent)) - return 0; - - /* Only do this for batteries with internal NTC */ - if (di->bm->adc_therm == ABx500_ADC_THERM_BATCTRL && enable) { - - if (is_ab8540(di->parent)) { - if (di->curr_source == BTEMP_BATCTRL_CURR_SRC_60UA) - curr = BAT_CTRL_60U_ENA; - else - curr = BAT_CTRL_120U_ENA; - } else if (is_ab9540(di->parent) || is_ab8505(di->parent)) { - if (di->curr_source == BTEMP_BATCTRL_CURR_SRC_16UA) - curr = BAT_CTRL_16U_ENA; - else - curr = BAT_CTRL_18U_ENA; - } else { - if (di->curr_source == BTEMP_BATCTRL_CURR_SRC_7UA) - curr = BAT_CTRL_7U_ENA; - else - curr = BAT_CTRL_20U_ENA; - } - - dev_dbg(di->dev, "Set BATCTRL %duA\n", di->curr_source); - - ret = abx500_mask_and_set_register_interruptible(di->dev, - AB8500_CHARGER, AB8500_BAT_CTRL_CURRENT_SOURCE, - FORCE_BAT_CTRL_CMP_HIGH, FORCE_BAT_CTRL_CMP_HIGH); - if (ret) { - dev_err(di->dev, "%s failed setting cmp_force\n", - __func__); - return ret; - } - - /* - * We have to wait one 32kHz cycle before enabling - * the current source, since ForceBatCtrlCmpHigh needs - * to be written in a separate cycle - */ - udelay(32); - - ret = abx500_set_register_interruptible(di->dev, - AB8500_CHARGER, AB8500_BAT_CTRL_CURRENT_SOURCE, - FORCE_BAT_CTRL_CMP_HIGH | curr); - if (ret) { - dev_err(di->dev, "%s failed enabling current source\n", - __func__); - goto disable_curr_source; - } - } else if (di->bm->adc_therm == ABx500_ADC_THERM_BATCTRL && !enable) { - dev_dbg(di->dev, "Disable BATCTRL curr source\n"); - - if (is_ab8540(di->parent)) { - /* Write 0 to the curr bits */ - ret = abx500_mask_and_set_register_interruptible( - di->dev, - AB8500_CHARGER, AB8500_BAT_CTRL_CURRENT_SOURCE, - BAT_CTRL_60U_ENA | BAT_CTRL_120U_ENA, - ~(BAT_CTRL_60U_ENA | BAT_CTRL_120U_ENA)); - } else if (is_ab9540(di->parent) || is_ab8505(di->parent)) { - /* Write 0 to the curr bits */ - ret = abx500_mask_and_set_register_interruptible( - di->dev, - AB8500_CHARGER, AB8500_BAT_CTRL_CURRENT_SOURCE, - BAT_CTRL_16U_ENA | BAT_CTRL_18U_ENA, - ~(BAT_CTRL_16U_ENA | BAT_CTRL_18U_ENA)); - } else { - /* Write 0 to the curr bits */ - ret = abx500_mask_and_set_register_interruptible( - di->dev, - AB8500_CHARGER, AB8500_BAT_CTRL_CURRENT_SOURCE, - BAT_CTRL_7U_ENA | BAT_CTRL_20U_ENA, - ~(BAT_CTRL_7U_ENA | BAT_CTRL_20U_ENA)); - } - - if (ret) { - dev_err(di->dev, "%s failed disabling current source\n", - __func__); - goto disable_curr_source; - } - - /* Enable Pull-Up and comparator */ - ret = abx500_mask_and_set_register_interruptible(di->dev, - AB8500_CHARGER, AB8500_BAT_CTRL_CURRENT_SOURCE, - BAT_CTRL_PULL_UP_ENA | BAT_CTRL_CMP_ENA, - BAT_CTRL_PULL_UP_ENA | BAT_CTRL_CMP_ENA); - if (ret) { - dev_err(di->dev, "%s failed enabling PU and comp\n", - __func__); - goto enable_pu_comp; - } - - /* - * We have to wait one 32kHz cycle before disabling - * ForceBatCtrlCmpHigh since this needs to be written - * in a separate cycle - */ - udelay(32); - - /* Disable 'force comparator' */ - ret = abx500_mask_and_set_register_interruptible(di->dev, - AB8500_CHARGER, AB8500_BAT_CTRL_CURRENT_SOURCE, - FORCE_BAT_CTRL_CMP_HIGH, ~FORCE_BAT_CTRL_CMP_HIGH); - if (ret) { - dev_err(di->dev, "%s failed disabling force comp\n", - __func__); - goto disable_force_comp; - } - } - return ret; - - /* - * We have to try unsetting FORCE_BAT_CTRL_CMP_HIGH one more time - * if we got an error above - */ -disable_curr_source: - if (is_ab8540(di->parent)) { - /* Write 0 to the curr bits */ - ret = abx500_mask_and_set_register_interruptible(di->dev, - AB8500_CHARGER, AB8500_BAT_CTRL_CURRENT_SOURCE, - BAT_CTRL_60U_ENA | BAT_CTRL_120U_ENA, - ~(BAT_CTRL_60U_ENA | BAT_CTRL_120U_ENA)); - } else if (is_ab9540(di->parent) || is_ab8505(di->parent)) { - /* Write 0 to the curr bits */ - ret = abx500_mask_and_set_register_interruptible(di->dev, - AB8500_CHARGER, AB8500_BAT_CTRL_CURRENT_SOURCE, - BAT_CTRL_16U_ENA | BAT_CTRL_18U_ENA, - ~(BAT_CTRL_16U_ENA | BAT_CTRL_18U_ENA)); - } else { - /* Write 0 to the curr bits */ - ret = abx500_mask_and_set_register_interruptible(di->dev, - AB8500_CHARGER, AB8500_BAT_CTRL_CURRENT_SOURCE, - BAT_CTRL_7U_ENA | BAT_CTRL_20U_ENA, - ~(BAT_CTRL_7U_ENA | BAT_CTRL_20U_ENA)); - } - - if (ret) { - dev_err(di->dev, "%s failed disabling current source\n", - __func__); - return ret; - } -enable_pu_comp: - /* Enable Pull-Up and comparator */ - ret = abx500_mask_and_set_register_interruptible(di->dev, - AB8500_CHARGER, AB8500_BAT_CTRL_CURRENT_SOURCE, - BAT_CTRL_PULL_UP_ENA | BAT_CTRL_CMP_ENA, - BAT_CTRL_PULL_UP_ENA | BAT_CTRL_CMP_ENA); - if (ret) { - dev_err(di->dev, "%s failed enabling PU and comp\n", - __func__); - return ret; - } - -disable_force_comp: - /* - * We have to wait one 32kHz cycle before disabling - * ForceBatCtrlCmpHigh since this needs to be written - * in a separate cycle - */ - udelay(32); - - /* Disable 'force comparator' */ - ret = abx500_mask_and_set_register_interruptible(di->dev, - AB8500_CHARGER, AB8500_BAT_CTRL_CURRENT_SOURCE, - FORCE_BAT_CTRL_CMP_HIGH, ~FORCE_BAT_CTRL_CMP_HIGH); - if (ret) { - dev_err(di->dev, "%s failed disabling force comp\n", - __func__); - return ret; - } - - return ret; -} - -/** - * ab8500_btemp_get_batctrl_res() - get battery resistance - * @di: pointer to the ab8500_btemp structure - * - * This function returns the battery pack identification resistance. - * Returns value in Ohms. - */ -static int ab8500_btemp_get_batctrl_res(struct ab8500_btemp *di) -{ - int ret; - int batctrl = 0; - int res; - int inst_curr; - int i; - - /* - * BATCTRL current sources are included on AB8500 cut2.0 - * and future versions - */ - ret = ab8500_btemp_curr_source_enable(di, true); - if (ret) { - dev_err(di->dev, "%s curr source enabled failed\n", __func__); - return ret; - } - - if (!di->fg) - di->fg = ab8500_fg_get(); - if (!di->fg) { - dev_err(di->dev, "No fg found\n"); - return -EINVAL; - } - - ret = ab8500_fg_inst_curr_start(di->fg); - - if (ret) { - dev_err(di->dev, "Failed to start current measurement\n"); - return ret; - } - - do { - msleep(20); - } while (!ab8500_fg_inst_curr_started(di->fg)); - - i = 0; - - do { - batctrl += ab8500_btemp_read_batctrl_voltage(di); - i++; - msleep(20); - } while (!ab8500_fg_inst_curr_done(di->fg)); - batctrl /= i; - - ret = ab8500_fg_inst_curr_finalize(di->fg, &inst_curr); - if (ret) { - dev_err(di->dev, "Failed to finalize current measurement\n"); - return ret; - } - - res = ab8500_btemp_batctrl_volt_to_res(di, batctrl, inst_curr); - - ret = ab8500_btemp_curr_source_enable(di, false); - if (ret) { - dev_err(di->dev, "%s curr source disable failed\n", __func__); - return ret; - } - - dev_dbg(di->dev, "%s batctrl: %d res: %d inst_curr: %d samples: %d\n", - __func__, batctrl, res, inst_curr, i); - - return res; -} - -/** - * ab8500_btemp_res_to_temp() - resistance to temperature - * @di: pointer to the ab8500_btemp structure - * @tbl: pointer to the resiatance to temperature table - * @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 - * 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; - /* - * Calculate the formula for the straight line - * Simple interpolation if we are within - * the resistance table limits, extrapolate - * if resistance is outside the limits. - */ - if (res > tbl[0].resist) - i = 0; - else if (res <= tbl[tbl_size - 1].resist) - i = tbl_size - 2; - else { - i = 0; - while (!(res <= tbl[i].resist && - res > tbl[i + 1].resist)) - i++; - } - - temp = tbl[i].temp + ((tbl[i + 1].temp - tbl[i].temp) * - (res - tbl[i].resist)) / (tbl[i + 1].resist - tbl[i].resist); - return temp; -} - -/** - * ab8500_btemp_measure_temp() - measure battery temperature - * @di: pointer to the ab8500_btemp structure - * - * Returns battery temperature (on success) else the previous temperature - */ -static int ab8500_btemp_measure_temp(struct ab8500_btemp *di) -{ - int temp; - static int prev; - int rbat, rntc, vntc; - u8 id; - - id = di->bm->batt_id; - - if (di->bm->adc_therm == ABx500_ADC_THERM_BATCTRL && - id != BATTERY_UNKNOWN) { - - rbat = ab8500_btemp_get_batctrl_res(di); - if (rbat < 0) { - dev_err(di->dev, "%s get batctrl res failed\n", - __func__); - /* - * Return out-of-range temperature so that - * charging is stopped - */ - return BTEMP_THERMAL_LOW_LIMIT; - } - - temp = ab8500_btemp_res_to_temp(di, - di->bm->bat_type[id].r_to_t_tbl, - di->bm->bat_type[id].n_temp_tbl_elements, rbat); - } else { - vntc = ab8500_gpadc_convert(di->gpadc, BTEMP_BALL); - if (vntc < 0) { - dev_err(di->dev, - "%s gpadc conversion failed," - " using previous value\n", __func__); - return prev; - } - /* - * The PCB NTC is sourced from VTVOUT via a 230kOhm - * resistor. - */ - rntc = 230000 * vntc / (VTVOUT_V - vntc); - - temp = ab8500_btemp_res_to_temp(di, - di->bm->bat_type[id].r_to_t_tbl, - di->bm->bat_type[id].n_temp_tbl_elements, rntc); - prev = temp; - } - dev_dbg(di->dev, "Battery temperature is %d\n", temp); - return temp; -} - -/** - * ab8500_btemp_id() - Identify the connected battery - * @di: pointer to the ab8500_btemp structure - * - * This function will try to identify the battery by reading the ID - * resistor. Some brands use a combined ID resistor with a NTC resistor to - * both be able to identify and to read the temperature of it. - */ -static int ab8500_btemp_id(struct ab8500_btemp *di) -{ - int res; - u8 i; - if (is_ab8540(di->parent)) - di->curr_source = BTEMP_BATCTRL_CURR_SRC_60UA; - else if (is_ab9540(di->parent) || is_ab8505(di->parent)) - di->curr_source = BTEMP_BATCTRL_CURR_SRC_16UA; - else - di->curr_source = BTEMP_BATCTRL_CURR_SRC_7UA; - - di->bm->batt_id = BATTERY_UNKNOWN; - - res = ab8500_btemp_get_batctrl_res(di); - if (res < 0) { - dev_err(di->dev, "%s get batctrl res failed\n", __func__); - return -ENXIO; - } - - /* BATTERY_UNKNOWN is defined on position 0, skip it! */ - for (i = BATTERY_UNKNOWN + 1; i < di->bm->n_btypes; i++) { - if ((res <= di->bm->bat_type[i].resis_high) && - (res >= di->bm->bat_type[i].resis_low)) { - dev_dbg(di->dev, "Battery detected on %s" - " low %d < res %d < high: %d" - " index: %d\n", - di->bm->adc_therm == ABx500_ADC_THERM_BATCTRL ? - "BATCTRL" : "BATTEMP", - di->bm->bat_type[i].resis_low, res, - di->bm->bat_type[i].resis_high, i); - - di->bm->batt_id = i; - break; - } - } - - if (di->bm->batt_id == BATTERY_UNKNOWN) { - dev_warn(di->dev, "Battery identified as unknown" - ", resistance %d Ohm\n", res); - return -ENXIO; - } - - /* - * We only have to change current source if the - * detected type is Type 1. - */ - if (di->bm->adc_therm == ABx500_ADC_THERM_BATCTRL && - di->bm->batt_id == 1) { - if (is_ab8540(di->parent)) { - dev_dbg(di->dev, - "Set BATCTRL current source to 60uA\n"); - di->curr_source = BTEMP_BATCTRL_CURR_SRC_60UA; - } else if (is_ab9540(di->parent) || is_ab8505(di->parent)) { - dev_dbg(di->dev, - "Set BATCTRL current source to 16uA\n"); - di->curr_source = BTEMP_BATCTRL_CURR_SRC_16UA; - } else { - dev_dbg(di->dev, "Set BATCTRL current source to 20uA\n"); - di->curr_source = BTEMP_BATCTRL_CURR_SRC_20UA; - } - } - - return di->bm->batt_id; -} - -/** - * ab8500_btemp_periodic_work() - Measuring the temperature periodically - * @work: pointer to the work_struct structure - * - * Work function for measuring the temperature periodically - */ -static void ab8500_btemp_periodic_work(struct work_struct *work) -{ - int interval; - int bat_temp; - struct ab8500_btemp *di = container_of(work, - struct ab8500_btemp, btemp_periodic_work.work); - - if (!di->initialized) { - /* Identify the battery */ - if (ab8500_btemp_id(di) < 0) - dev_warn(di->dev, "failed to identify the battery\n"); - } - - bat_temp = ab8500_btemp_measure_temp(di); - /* - * Filter battery temperature. - * Allow direct updates on temperature only if two samples result in - * same temperature. Else only allow 1 degree change from previous - * reported value in the direction of the new measurement. - */ - if ((bat_temp == di->prev_bat_temp) || !di->initialized) { - if ((di->bat_temp != di->prev_bat_temp) || !di->initialized) { - di->initialized = true; - di->bat_temp = bat_temp; - power_supply_changed(di->btemp_psy); - } - } else if (bat_temp < di->prev_bat_temp) { - di->bat_temp--; - power_supply_changed(di->btemp_psy); - } else if (bat_temp > di->prev_bat_temp) { - di->bat_temp++; - power_supply_changed(di->btemp_psy); - } - di->prev_bat_temp = bat_temp; - - if (di->events.ac_conn || di->events.usb_conn) - interval = di->bm->temp_interval_chg; - else - interval = di->bm->temp_interval_nochg; - - /* Schedule a new measurement */ - queue_delayed_work(di->btemp_wq, - &di->btemp_periodic_work, - round_jiffies(interval * HZ)); -} - -/** - * ab8500_btemp_batctrlindb_handler() - battery removal detected - * @irq: interrupt number - * @_di: void pointer that has to address of ab8500_btemp - * - * Returns IRQ status(IRQ_HANDLED) - */ -static irqreturn_t ab8500_btemp_batctrlindb_handler(int irq, void *_di) -{ - struct ab8500_btemp *di = _di; - dev_err(di->dev, "Battery removal detected!\n"); - - di->events.batt_rem = true; - power_supply_changed(di->btemp_psy); - - return IRQ_HANDLED; -} - -/** - * ab8500_btemp_templow_handler() - battery temp lower than 10 degrees - * @irq: interrupt number - * @_di: void pointer that has to address of ab8500_btemp - * - * Returns IRQ status(IRQ_HANDLED) - */ -static irqreturn_t ab8500_btemp_templow_handler(int irq, void *_di) -{ - struct ab8500_btemp *di = _di; - - if (is_ab8500_3p3_or_earlier(di->parent)) { - dev_dbg(di->dev, "Ignore false btemp low irq" - " for ABB cut 1.0, 1.1, 2.0 and 3.3\n"); - } else { - dev_crit(di->dev, "Battery temperature lower than -10deg c\n"); - - di->events.btemp_low = true; - di->events.btemp_high = false; - di->events.btemp_medhigh = false; - di->events.btemp_lowmed = false; - power_supply_changed(di->btemp_psy); - } - - return IRQ_HANDLED; -} - -/** - * ab8500_btemp_temphigh_handler() - battery temp higher than max temp - * @irq: interrupt number - * @_di: void pointer that has to address of ab8500_btemp - * - * Returns IRQ status(IRQ_HANDLED) - */ -static irqreturn_t ab8500_btemp_temphigh_handler(int irq, void *_di) -{ - struct ab8500_btemp *di = _di; - - dev_crit(di->dev, "Battery temperature is higher than MAX temp\n"); - - di->events.btemp_high = true; - di->events.btemp_medhigh = false; - di->events.btemp_lowmed = false; - di->events.btemp_low = false; - power_supply_changed(di->btemp_psy); - - return IRQ_HANDLED; -} - -/** - * ab8500_btemp_lowmed_handler() - battery temp between low and medium - * @irq: interrupt number - * @_di: void pointer that has to address of ab8500_btemp - * - * Returns IRQ status(IRQ_HANDLED) - */ -static irqreturn_t ab8500_btemp_lowmed_handler(int irq, void *_di) -{ - struct ab8500_btemp *di = _di; - - dev_dbg(di->dev, "Battery temperature is between low and medium\n"); - - di->events.btemp_lowmed = true; - di->events.btemp_medhigh = false; - di->events.btemp_high = false; - di->events.btemp_low = false; - power_supply_changed(di->btemp_psy); - - return IRQ_HANDLED; -} - -/** - * ab8500_btemp_medhigh_handler() - battery temp between medium and high - * @irq: interrupt number - * @_di: void pointer that has to address of ab8500_btemp - * - * Returns IRQ status(IRQ_HANDLED) - */ -static irqreturn_t ab8500_btemp_medhigh_handler(int irq, void *_di) -{ - struct ab8500_btemp *di = _di; - - dev_dbg(di->dev, "Battery temperature is between medium and high\n"); - - di->events.btemp_medhigh = true; - di->events.btemp_lowmed = false; - di->events.btemp_high = false; - di->events.btemp_low = false; - power_supply_changed(di->btemp_psy); - - return IRQ_HANDLED; -} - -/** - * ab8500_btemp_periodic() - Periodic temperature measurements - * @di: pointer to the ab8500_btemp structure - * @enable: enable or disable periodic temperature measurements - * - * Starts of stops periodic temperature measurements. Periodic measurements - * should only be done when a charger is connected. - */ -static void ab8500_btemp_periodic(struct ab8500_btemp *di, - bool enable) -{ - dev_dbg(di->dev, "Enable periodic temperature measurements: %d\n", - enable); - /* - * Make sure a new measurement is done directly by cancelling - * any pending work - */ - cancel_delayed_work_sync(&di->btemp_periodic_work); - - if (enable) - queue_delayed_work(di->btemp_wq, &di->btemp_periodic_work, 0); -} - -/** - * ab8500_btemp_get_temp() - get battery temperature - * @di: pointer to the ab8500_btemp structure - * - * Returns battery temperature - */ -int ab8500_btemp_get_temp(struct ab8500_btemp *di) -{ - int temp = 0; - - /* - * The BTEMP events are not reliabe on AB8500 cut3.3 - * and prior versions - */ - if (is_ab8500_3p3_or_earlier(di->parent)) { - temp = di->bat_temp * 10; - } else { - if (di->events.btemp_low) { - if (temp > di->btemp_ranges.btemp_low_limit) - temp = di->btemp_ranges.btemp_low_limit * 10; - else - temp = di->bat_temp * 10; - } else if (di->events.btemp_high) { - if (temp < di->btemp_ranges.btemp_high_limit) - temp = di->btemp_ranges.btemp_high_limit * 10; - else - temp = di->bat_temp * 10; - } else if (di->events.btemp_lowmed) { - if (temp > di->btemp_ranges.btemp_med_limit) - temp = di->btemp_ranges.btemp_med_limit * 10; - else - temp = di->bat_temp * 10; - } else if (di->events.btemp_medhigh) { - if (temp < di->btemp_ranges.btemp_med_limit) - temp = di->btemp_ranges.btemp_med_limit * 10; - else - temp = di->bat_temp * 10; - } else - temp = di->bat_temp * 10; - } - return temp; -} -EXPORT_SYMBOL(ab8500_btemp_get_temp); - -/** - * ab8500_btemp_get_batctrl_temp() - get the temperature - * @btemp: pointer to the btemp structure - * - * Returns the batctrl temperature in millidegrees - */ -int ab8500_btemp_get_batctrl_temp(struct ab8500_btemp *btemp) -{ - return btemp->bat_temp * 1000; -} -EXPORT_SYMBOL(ab8500_btemp_get_batctrl_temp); - -/** - * ab8500_btemp_get_property() - get the btemp properties - * @psy: pointer to the power_supply structure - * @psp: pointer to the power_supply_property structure - * @val: pointer to the power_supply_propval union - * - * This function gets called when an application tries to get the btemp - * properties by reading the sysfs files. - * online: presence of the battery - * present: presence of the battery - * technology: battery technology - * temp: battery temperature - * Returns error code in case of failure else 0(on success) - */ -static int ab8500_btemp_get_property(struct power_supply *psy, - enum power_supply_property psp, - union power_supply_propval *val) -{ - struct ab8500_btemp *di = power_supply_get_drvdata(psy); - - switch (psp) { - case POWER_SUPPLY_PROP_PRESENT: - case POWER_SUPPLY_PROP_ONLINE: - if (di->events.batt_rem) - val->intval = 0; - else - val->intval = 1; - break; - case POWER_SUPPLY_PROP_TECHNOLOGY: - val->intval = di->bm->bat_type[di->bm->batt_id].name; - break; - case POWER_SUPPLY_PROP_TEMP: - val->intval = ab8500_btemp_get_temp(di); - break; - default: - return -EINVAL; - } - return 0; -} - -static int ab8500_btemp_get_ext_psy_data(struct device *dev, void *data) -{ - struct power_supply *psy; - struct power_supply *ext = dev_get_drvdata(dev); - const char **supplicants = (const char **)ext->supplied_to; - struct ab8500_btemp *di; - union power_supply_propval ret; - int j; - - psy = (struct power_supply *)data; - di = power_supply_get_drvdata(psy); - - /* - * For all psy where the name of your driver - * appears in any supplied_to - */ - j = match_string(supplicants, ext->num_supplicants, psy->desc->name); - if (j < 0) - return 0; - - /* Go through all properties for the psy */ - for (j = 0; j < ext->desc->num_properties; j++) { - enum power_supply_property prop; - prop = ext->desc->properties[j]; - - if (power_supply_get_property(ext, prop, &ret)) - continue; - - switch (prop) { - case POWER_SUPPLY_PROP_PRESENT: - switch (ext->desc->type) { - case POWER_SUPPLY_TYPE_MAINS: - /* AC disconnected */ - if (!ret.intval && di->events.ac_conn) { - di->events.ac_conn = false; - } - /* AC connected */ - else if (ret.intval && !di->events.ac_conn) { - di->events.ac_conn = true; - if (!di->events.usb_conn) - ab8500_btemp_periodic(di, true); - } - break; - case POWER_SUPPLY_TYPE_USB: - /* USB disconnected */ - if (!ret.intval && di->events.usb_conn) { - di->events.usb_conn = false; - } - /* USB connected */ - else if (ret.intval && !di->events.usb_conn) { - di->events.usb_conn = true; - if (!di->events.ac_conn) - ab8500_btemp_periodic(di, true); - } - break; - default: - break; - } - break; - default: - break; - } - } - return 0; -} - -/** - * ab8500_btemp_external_power_changed() - callback for power supply changes - * @psy: pointer to the structure power_supply - * - * This function is pointing to the function pointer external_power_changed - * of the structure power_supply. - * This function gets executed when there is a change in the external power - * supply to the btemp. - */ -static void ab8500_btemp_external_power_changed(struct power_supply *psy) -{ - struct ab8500_btemp *di = power_supply_get_drvdata(psy); - - class_for_each_device(power_supply_class, NULL, - di->btemp_psy, ab8500_btemp_get_ext_psy_data); -} - -/* ab8500 btemp driver interrupts and their respective isr */ -static struct ab8500_btemp_interrupts ab8500_btemp_irq[] = { - {"BAT_CTRL_INDB", ab8500_btemp_batctrlindb_handler}, - {"BTEMP_LOW", ab8500_btemp_templow_handler}, - {"BTEMP_HIGH", ab8500_btemp_temphigh_handler}, - {"BTEMP_LOW_MEDIUM", ab8500_btemp_lowmed_handler}, - {"BTEMP_MEDIUM_HIGH", ab8500_btemp_medhigh_handler}, -}; - -#if defined(CONFIG_PM) -static int ab8500_btemp_resume(struct platform_device *pdev) -{ - struct ab8500_btemp *di = platform_get_drvdata(pdev); - - ab8500_btemp_periodic(di, true); - - return 0; -} - -static int ab8500_btemp_suspend(struct platform_device *pdev, - pm_message_t state) -{ - struct ab8500_btemp *di = platform_get_drvdata(pdev); - - ab8500_btemp_periodic(di, false); - - return 0; -} -#else -#define ab8500_btemp_suspend NULL -#define ab8500_btemp_resume NULL -#endif - -static int ab8500_btemp_remove(struct platform_device *pdev) -{ - struct ab8500_btemp *di = platform_get_drvdata(pdev); - int i, irq; - - /* Disable interrupts */ - for (i = 0; i < ARRAY_SIZE(ab8500_btemp_irq); i++) { - irq = platform_get_irq_byname(pdev, ab8500_btemp_irq[i].name); - free_irq(irq, di); - } - - /* Delete the work queue */ - destroy_workqueue(di->btemp_wq); - - flush_scheduled_work(); - power_supply_unregister(di->btemp_psy); - - return 0; -} - -static char *supply_interface[] = { - "ab8500_chargalg", - "ab8500_fg", -}; - -static const struct power_supply_desc ab8500_btemp_desc = { - .name = "ab8500_btemp", - .type = POWER_SUPPLY_TYPE_BATTERY, - .properties = ab8500_btemp_props, - .num_properties = ARRAY_SIZE(ab8500_btemp_props), - .get_property = ab8500_btemp_get_property, - .external_power_changed = ab8500_btemp_external_power_changed, -}; - -static int ab8500_btemp_probe(struct platform_device *pdev) -{ - struct device_node *np = pdev->dev.of_node; - struct abx500_bm_data *plat = pdev->dev.platform_data; - struct power_supply_config psy_cfg = {}; - struct ab8500_btemp *di; - int irq, i, ret = 0; - u8 val; - - di = devm_kzalloc(&pdev->dev, sizeof(*di), GFP_KERNEL); - if (!di) { - dev_err(&pdev->dev, "%s no mem for ab8500_btemp\n", __func__); - return -ENOMEM; - } - - if (!plat) { - dev_err(&pdev->dev, "no battery management data supplied\n"); - return -EINVAL; - } - di->bm = plat; - - if (np) { - ret = ab8500_bm_of_probe(&pdev->dev, np, di->bm); - if (ret) { - dev_err(&pdev->dev, "failed to get battery information\n"); - return ret; - } - } - - /* get parent data */ - di->dev = &pdev->dev; - di->parent = dev_get_drvdata(pdev->dev.parent); - di->gpadc = ab8500_gpadc_get("ab8500-gpadc.0"); - - di->initialized = false; - - psy_cfg.supplied_to = supply_interface; - psy_cfg.num_supplicants = ARRAY_SIZE(supply_interface); - psy_cfg.drv_data = di; - - /* Create a work queue for the btemp */ - di->btemp_wq = - create_singlethread_workqueue("ab8500_btemp_wq"); - if (di->btemp_wq == NULL) { - dev_err(di->dev, "failed to create work queue\n"); - return -ENOMEM; - } - - /* Init work for measuring temperature periodically */ - INIT_DEFERRABLE_WORK(&di->btemp_periodic_work, - ab8500_btemp_periodic_work); - - /* Set BTEMP thermal limits. Low and Med are fixed */ - di->btemp_ranges.btemp_low_limit = BTEMP_THERMAL_LOW_LIMIT; - di->btemp_ranges.btemp_med_limit = BTEMP_THERMAL_MED_LIMIT; - - ret = abx500_get_register_interruptible(di->dev, AB8500_CHARGER, - AB8500_BTEMP_HIGH_TH, &val); - if (ret < 0) { - dev_err(di->dev, "%s ab8500 read failed\n", __func__); - goto free_btemp_wq; - } - switch (val) { - case BTEMP_HIGH_TH_57_0: - case BTEMP_HIGH_TH_57_1: - di->btemp_ranges.btemp_high_limit = - BTEMP_THERMAL_HIGH_LIMIT_57; - break; - case BTEMP_HIGH_TH_52: - di->btemp_ranges.btemp_high_limit = - BTEMP_THERMAL_HIGH_LIMIT_52; - break; - case BTEMP_HIGH_TH_62: - di->btemp_ranges.btemp_high_limit = - BTEMP_THERMAL_HIGH_LIMIT_62; - break; - } - - /* Register BTEMP power supply class */ - di->btemp_psy = power_supply_register(di->dev, &ab8500_btemp_desc, - &psy_cfg); - if (IS_ERR(di->btemp_psy)) { - dev_err(di->dev, "failed to register BTEMP psy\n"); - ret = PTR_ERR(di->btemp_psy); - goto free_btemp_wq; - } - - /* Register interrupts */ - for (i = 0; i < ARRAY_SIZE(ab8500_btemp_irq); i++) { - irq = platform_get_irq_byname(pdev, ab8500_btemp_irq[i].name); - ret = request_threaded_irq(irq, NULL, ab8500_btemp_irq[i].isr, - IRQF_SHARED | IRQF_NO_SUSPEND, - ab8500_btemp_irq[i].name, di); - - if (ret) { - dev_err(di->dev, "failed to request %s IRQ %d: %d\n" - , ab8500_btemp_irq[i].name, irq, ret); - goto free_irq; - } - dev_dbg(di->dev, "Requested %s IRQ %d: %d\n", - ab8500_btemp_irq[i].name, irq, ret); - } - - platform_set_drvdata(pdev, di); - - /* Kick off periodic temperature measurements */ - ab8500_btemp_periodic(di, true); - list_add_tail(&di->node, &ab8500_btemp_list); - - return ret; - -free_irq: - power_supply_unregister(di->btemp_psy); - - /* We also have to free all successfully registered irqs */ - for (i = i - 1; i >= 0; i--) { - irq = platform_get_irq_byname(pdev, ab8500_btemp_irq[i].name); - free_irq(irq, di); - } -free_btemp_wq: - destroy_workqueue(di->btemp_wq); - return ret; -} - -static const struct of_device_id ab8500_btemp_match[] = { - { .compatible = "stericsson,ab8500-btemp", }, - { }, -}; - -static struct platform_driver ab8500_btemp_driver = { - .probe = ab8500_btemp_probe, - .remove = ab8500_btemp_remove, - .suspend = ab8500_btemp_suspend, - .resume = ab8500_btemp_resume, - .driver = { - .name = "ab8500-btemp", - .of_match_table = ab8500_btemp_match, - }, -}; - -static int __init ab8500_btemp_init(void) -{ - return platform_driver_register(&ab8500_btemp_driver); -} - -static void __exit ab8500_btemp_exit(void) -{ - platform_driver_unregister(&ab8500_btemp_driver); -} - -device_initcall(ab8500_btemp_init); -module_exit(ab8500_btemp_exit); - -MODULE_LICENSE("GPL v2"); -MODULE_AUTHOR("Johan Palsson, Karl Komierowski, Arun R Murthy"); -MODULE_ALIAS("platform:ab8500-btemp"); -MODULE_DESCRIPTION("AB8500 battery temperature driver"); diff --git a/drivers/power/ab8500_charger.c b/drivers/power/ab8500_charger.c deleted file mode 100644 index 30de5d42b26a..000000000000 --- a/drivers/power/ab8500_charger.c +++ /dev/null @@ -1,3765 +0,0 @@ -/* - * Copyright (C) ST-Ericsson SA 2012 - * - * Charger driver for AB8500 - * - * License Terms: GNU General Public License v2 - * Author: - * Johan Palsson - * Karl Komierowski - * Arun R Murthy - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/* Charger constants */ -#define NO_PW_CONN 0 -#define AC_PW_CONN 1 -#define USB_PW_CONN 2 - -#define MAIN_WDOG_ENA 0x01 -#define MAIN_WDOG_KICK 0x02 -#define MAIN_WDOG_DIS 0x00 -#define CHARG_WD_KICK 0x01 -#define MAIN_CH_ENA 0x01 -#define MAIN_CH_NO_OVERSHOOT_ENA_N 0x02 -#define USB_CH_ENA 0x01 -#define USB_CHG_NO_OVERSHOOT_ENA_N 0x02 -#define MAIN_CH_DET 0x01 -#define MAIN_CH_CV_ON 0x04 -#define USB_CH_CV_ON 0x08 -#define VBUS_DET_DBNC100 0x02 -#define VBUS_DET_DBNC1 0x01 -#define OTP_ENABLE_WD 0x01 -#define DROP_COUNT_RESET 0x01 -#define USB_CH_DET 0x01 - -#define MAIN_CH_INPUT_CURR_SHIFT 4 -#define VBUS_IN_CURR_LIM_SHIFT 4 -#define AB8540_VBUS_IN_CURR_LIM_SHIFT 2 -#define AUTO_VBUS_IN_CURR_LIM_SHIFT 4 -#define AB8540_AUTO_VBUS_IN_CURR_MASK 0x3F -#define VBUS_IN_CURR_LIM_RETRY_SET_TIME 30 /* seconds */ - -#define LED_INDICATOR_PWM_ENA 0x01 -#define LED_INDICATOR_PWM_DIS 0x00 -#define LED_IND_CUR_5MA 0x04 -#define LED_INDICATOR_PWM_DUTY_252_256 0xBF - -/* HW failure constants */ -#define MAIN_CH_TH_PROT 0x02 -#define VBUS_CH_NOK 0x08 -#define USB_CH_TH_PROT 0x02 -#define VBUS_OVV_TH 0x01 -#define MAIN_CH_NOK 0x01 -#define VBUS_DET 0x80 - -#define MAIN_CH_STATUS2_MAINCHGDROP 0x80 -#define MAIN_CH_STATUS2_MAINCHARGERDETDBNC 0x40 -#define USB_CH_VBUSDROP 0x40 -#define USB_CH_VBUSDETDBNC 0x01 - -/* UsbLineStatus register bit masks */ -#define AB8500_USB_LINK_STATUS 0x78 -#define AB8505_USB_LINK_STATUS 0xF8 -#define AB8500_STD_HOST_SUSP 0x18 -#define USB_LINK_STATUS_SHIFT 3 - -/* Watchdog timeout constant */ -#define WD_TIMER 0x30 /* 4min */ -#define WD_KICK_INTERVAL (60 * HZ) - -/* Lowest charger voltage is 3.39V -> 0x4E */ -#define LOW_VOLT_REG 0x4E - -/* Step up/down delay in us */ -#define STEP_UDELAY 1000 - -#define CHARGER_STATUS_POLL 10 /* in ms */ - -#define CHG_WD_INTERVAL (60 * HZ) - -#define AB8500_SW_CONTROL_FALLBACK 0x03 -/* Wait for enumeration before charing in us */ -#define WAIT_ACA_RID_ENUMERATION (5 * 1000) -/*External charger control*/ -#define AB8500_SYS_CHARGER_CONTROL_REG 0x52 -#define EXTERNAL_CHARGER_DISABLE_REG_VAL 0x03 -#define EXTERNAL_CHARGER_ENABLE_REG_VAL 0x07 - -/* UsbLineStatus register - usb types */ -enum ab8500_charger_link_status { - USB_STAT_NOT_CONFIGURED, - USB_STAT_STD_HOST_NC, - USB_STAT_STD_HOST_C_NS, - USB_STAT_STD_HOST_C_S, - USB_STAT_HOST_CHG_NM, - USB_STAT_HOST_CHG_HS, - USB_STAT_HOST_CHG_HS_CHIRP, - USB_STAT_DEDICATED_CHG, - USB_STAT_ACA_RID_A, - USB_STAT_ACA_RID_B, - USB_STAT_ACA_RID_C_NM, - USB_STAT_ACA_RID_C_HS, - USB_STAT_ACA_RID_C_HS_CHIRP, - USB_STAT_HM_IDGND, - USB_STAT_RESERVED, - USB_STAT_NOT_VALID_LINK, - USB_STAT_PHY_EN, - USB_STAT_SUP_NO_IDGND_VBUS, - USB_STAT_SUP_IDGND_VBUS, - USB_STAT_CHARGER_LINE_1, - USB_STAT_CARKIT_1, - USB_STAT_CARKIT_2, - USB_STAT_ACA_DOCK_CHARGER, -}; - -enum ab8500_usb_state { - AB8500_BM_USB_STATE_RESET_HS, /* HighSpeed Reset */ - AB8500_BM_USB_STATE_RESET_FS, /* FullSpeed/LowSpeed Reset */ - AB8500_BM_USB_STATE_CONFIGURED, - AB8500_BM_USB_STATE_SUSPEND, - AB8500_BM_USB_STATE_RESUME, - AB8500_BM_USB_STATE_MAX, -}; - -/* VBUS input current limits supported in AB8500 in mA */ -#define USB_CH_IP_CUR_LVL_0P05 50 -#define USB_CH_IP_CUR_LVL_0P09 98 -#define USB_CH_IP_CUR_LVL_0P19 193 -#define USB_CH_IP_CUR_LVL_0P29 290 -#define USB_CH_IP_CUR_LVL_0P38 380 -#define USB_CH_IP_CUR_LVL_0P45 450 -#define USB_CH_IP_CUR_LVL_0P5 500 -#define USB_CH_IP_CUR_LVL_0P6 600 -#define USB_CH_IP_CUR_LVL_0P7 700 -#define USB_CH_IP_CUR_LVL_0P8 800 -#define USB_CH_IP_CUR_LVL_0P9 900 -#define USB_CH_IP_CUR_LVL_1P0 1000 -#define USB_CH_IP_CUR_LVL_1P1 1100 -#define USB_CH_IP_CUR_LVL_1P3 1300 -#define USB_CH_IP_CUR_LVL_1P4 1400 -#define USB_CH_IP_CUR_LVL_1P5 1500 - -#define VBAT_TRESH_IP_CUR_RED 3800 - -#define to_ab8500_charger_usb_device_info(x) container_of((x), \ - struct ab8500_charger, usb_chg) -#define to_ab8500_charger_ac_device_info(x) container_of((x), \ - struct ab8500_charger, ac_chg) - -/** - * struct ab8500_charger_interrupts - ab8500 interupts - * @name: name of the interrupt - * @isr function pointer to the isr - */ -struct ab8500_charger_interrupts { - char *name; - irqreturn_t (*isr)(int irq, void *data); -}; - -struct ab8500_charger_info { - int charger_connected; - int charger_online; - int charger_voltage; - int cv_active; - bool wd_expired; - int charger_current; -}; - -struct ab8500_charger_event_flags { - bool mainextchnotok; - bool main_thermal_prot; - bool usb_thermal_prot; - bool vbus_ovv; - bool usbchargernotok; - bool chgwdexp; - bool vbus_collapse; - bool vbus_drop_end; -}; - -struct ab8500_charger_usb_state { - int usb_current; - int usb_current_tmp; - enum ab8500_usb_state state; - enum ab8500_usb_state state_tmp; - spinlock_t usb_lock; -}; - -struct ab8500_charger_max_usb_in_curr { - int usb_type_max; - int set_max; - int calculated_max; -}; - -/** - * struct ab8500_charger - ab8500 Charger device information - * @dev: Pointer to the structure device - * @vbus_detected: VBUS detected - * @vbus_detected_start: - * VBUS detected during startup - * @ac_conn: This will be true when the AC charger has been plugged - * @vddadc_en_ac: Indicate if VDD ADC supply is enabled because AC - * charger is enabled - * @vddadc_en_usb: Indicate if VDD ADC supply is enabled because USB - * charger is enabled - * @vbat Battery voltage - * @old_vbat Previously measured battery voltage - * @usb_device_is_unrecognised USB device is unrecognised by the hardware - * @autopower Indicate if we should have automatic pwron after pwrloss - * @autopower_cfg platform specific power config support for "pwron after pwrloss" - * @invalid_charger_detect_state State when forcing AB to use invalid charger - * @is_aca_rid: Incicate if accessory is ACA type - * @current_stepping_sessions: - * Counter for current stepping sessions - * @parent: Pointer to the struct ab8500 - * @gpadc: Pointer to the struct gpadc - * @bm: Platform specific battery management information - * @flags: Structure for information about events triggered - * @usb_state: Structure for usb stack information - * @max_usb_in_curr: Max USB charger input current - * @ac_chg: AC charger power supply - * @usb_chg: USB charger power supply - * @ac: Structure that holds the AC charger properties - * @usb: Structure that holds the USB charger properties - * @regu: Pointer to the struct regulator - * @charger_wq: Work queue for the IRQs and checking HW state - * @usb_ipt_crnt_lock: Lock to protect VBUS input current setting from mutuals - * @pm_lock: Lock to prevent system to suspend - * @check_vbat_work Work for checking vbat threshold to adjust vbus current - * @check_hw_failure_work: Work for checking HW state - * @check_usbchgnotok_work: Work for checking USB charger not ok status - * @kick_wd_work: Work for kicking the charger watchdog in case - * of ABB rev 1.* due to the watchog logic bug - * @ac_charger_attached_work: Work for checking if AC charger is still - * connected - * @usb_charger_attached_work: Work for checking if USB charger is still - * connected - * @ac_work: Work for checking AC charger connection - * @detect_usb_type_work: Work for detecting the USB type connected - * @usb_link_status_work: Work for checking the new USB link status - * @usb_state_changed_work: Work for checking USB state - * @attach_work: Work for detecting USB type - * @vbus_drop_end_work: Work for detecting VBUS drop end - * @check_main_thermal_prot_work: - * Work for checking Main thermal status - * @check_usb_thermal_prot_work: - * Work for checking USB thermal status - * @charger_attached_mutex: For controlling the wakelock - */ -struct ab8500_charger { - struct device *dev; - bool vbus_detected; - bool vbus_detected_start; - bool ac_conn; - bool vddadc_en_ac; - bool vddadc_en_usb; - int vbat; - int old_vbat; - bool usb_device_is_unrecognised; - bool autopower; - bool autopower_cfg; - int invalid_charger_detect_state; - int is_aca_rid; - atomic_t current_stepping_sessions; - struct ab8500 *parent; - struct ab8500_gpadc *gpadc; - struct abx500_bm_data *bm; - struct ab8500_charger_event_flags flags; - struct ab8500_charger_usb_state usb_state; - struct ab8500_charger_max_usb_in_curr max_usb_in_curr; - struct ux500_charger ac_chg; - struct ux500_charger usb_chg; - struct ab8500_charger_info ac; - struct ab8500_charger_info usb; - struct regulator *regu; - struct workqueue_struct *charger_wq; - struct mutex usb_ipt_crnt_lock; - struct delayed_work check_vbat_work; - struct delayed_work check_hw_failure_work; - struct delayed_work check_usbchgnotok_work; - struct delayed_work kick_wd_work; - struct delayed_work usb_state_changed_work; - struct delayed_work attach_work; - struct delayed_work ac_charger_attached_work; - struct delayed_work usb_charger_attached_work; - struct delayed_work vbus_drop_end_work; - struct work_struct ac_work; - struct work_struct detect_usb_type_work; - struct work_struct usb_link_status_work; - struct work_struct check_main_thermal_prot_work; - struct work_struct check_usb_thermal_prot_work; - struct usb_phy *usb_phy; - struct notifier_block nb; - struct mutex charger_attached_mutex; -}; - -/* AC properties */ -static enum power_supply_property ab8500_charger_ac_props[] = { - POWER_SUPPLY_PROP_HEALTH, - POWER_SUPPLY_PROP_PRESENT, - POWER_SUPPLY_PROP_ONLINE, - POWER_SUPPLY_PROP_VOLTAGE_NOW, - POWER_SUPPLY_PROP_VOLTAGE_AVG, - POWER_SUPPLY_PROP_CURRENT_NOW, -}; - -/* USB properties */ -static enum power_supply_property ab8500_charger_usb_props[] = { - POWER_SUPPLY_PROP_HEALTH, - POWER_SUPPLY_PROP_CURRENT_AVG, - POWER_SUPPLY_PROP_PRESENT, - POWER_SUPPLY_PROP_ONLINE, - POWER_SUPPLY_PROP_VOLTAGE_NOW, - POWER_SUPPLY_PROP_VOLTAGE_AVG, - POWER_SUPPLY_PROP_CURRENT_NOW, -}; - -/* - * Function for enabling and disabling sw fallback mode - * should always be disabled when no charger is connected. - */ -static void ab8500_enable_disable_sw_fallback(struct ab8500_charger *di, - bool fallback) -{ - u8 val; - u8 reg; - u8 bank; - u8 bit; - int ret; - - dev_dbg(di->dev, "SW Fallback: %d\n", fallback); - - if (is_ab8500(di->parent)) { - bank = 0x15; - reg = 0x0; - bit = 3; - } else { - bank = AB8500_SYS_CTRL1_BLOCK; - reg = AB8500_SW_CONTROL_FALLBACK; - bit = 0; - } - - /* read the register containing fallback bit */ - ret = abx500_get_register_interruptible(di->dev, bank, reg, &val); - if (ret < 0) { - dev_err(di->dev, "%d read failed\n", __LINE__); - return; - } - - if (is_ab8500(di->parent)) { - /* enable the OPT emulation registers */ - ret = abx500_set_register_interruptible(di->dev, 0x11, 0x00, 0x2); - if (ret) { - dev_err(di->dev, "%d write failed\n", __LINE__); - goto disable_otp; - } - } - - if (fallback) - val |= (1 << bit); - else - val &= ~(1 << bit); - - /* write back the changed fallback bit value to register */ - ret = abx500_set_register_interruptible(di->dev, bank, reg, val); - if (ret) { - dev_err(di->dev, "%d write failed\n", __LINE__); - } - -disable_otp: - if (is_ab8500(di->parent)) { - /* disable the set OTP registers again */ - ret = abx500_set_register_interruptible(di->dev, 0x11, 0x00, 0x0); - if (ret) { - dev_err(di->dev, "%d write failed\n", __LINE__); - } - } -} - -/** - * ab8500_power_supply_changed - a wrapper with local extentions for - * power_supply_changed - * @di: pointer to the ab8500_charger structure - * @psy: pointer to power_supply_that have changed. - * - */ -static void ab8500_power_supply_changed(struct ab8500_charger *di, - struct power_supply *psy) -{ - if (di->autopower_cfg) { - if (!di->usb.charger_connected && - !di->ac.charger_connected && - di->autopower) { - di->autopower = false; - ab8500_enable_disable_sw_fallback(di, false); - } else if (!di->autopower && - (di->ac.charger_connected || - di->usb.charger_connected)) { - di->autopower = true; - ab8500_enable_disable_sw_fallback(di, true); - } - } - power_supply_changed(psy); -} - -static void ab8500_charger_set_usb_connected(struct ab8500_charger *di, - bool connected) -{ - if (connected != di->usb.charger_connected) { - dev_dbg(di->dev, "USB connected:%i\n", connected); - di->usb.charger_connected = connected; - - if (!connected) - di->flags.vbus_drop_end = false; - - sysfs_notify(&di->usb_chg.psy->dev.kobj, NULL, "present"); - - if (connected) { - mutex_lock(&di->charger_attached_mutex); - mutex_unlock(&di->charger_attached_mutex); - - if (is_ab8500(di->parent)) - queue_delayed_work(di->charger_wq, - &di->usb_charger_attached_work, - HZ); - } else { - cancel_delayed_work_sync(&di->usb_charger_attached_work); - mutex_lock(&di->charger_attached_mutex); - mutex_unlock(&di->charger_attached_mutex); - } - } -} - -/** - * ab8500_charger_get_ac_voltage() - get ac charger voltage - * @di: pointer to the ab8500_charger structure - * - * Returns ac charger voltage (on success) - */ -static int ab8500_charger_get_ac_voltage(struct ab8500_charger *di) -{ - int vch; - - /* Only measure voltage if the charger is connected */ - if (di->ac.charger_connected) { - vch = ab8500_gpadc_convert(di->gpadc, MAIN_CHARGER_V); - if (vch < 0) - dev_err(di->dev, "%s gpadc conv failed,\n", __func__); - } else { - vch = 0; - } - return vch; -} - -/** - * ab8500_charger_ac_cv() - check if the main charger is in CV mode - * @di: pointer to the ab8500_charger structure - * - * Returns ac charger CV mode (on success) else error code - */ -static int ab8500_charger_ac_cv(struct ab8500_charger *di) -{ - u8 val; - int ret = 0; - - /* Only check CV mode if the charger is online */ - if (di->ac.charger_online) { - ret = abx500_get_register_interruptible(di->dev, AB8500_CHARGER, - AB8500_CH_STATUS1_REG, &val); - if (ret < 0) { - dev_err(di->dev, "%s ab8500 read failed\n", __func__); - return 0; - } - - if (val & MAIN_CH_CV_ON) - ret = 1; - else - ret = 0; - } - - return ret; -} - -/** - * ab8500_charger_get_vbus_voltage() - get vbus voltage - * @di: pointer to the ab8500_charger structure - * - * This function returns the vbus voltage. - * Returns vbus voltage (on success) - */ -static int ab8500_charger_get_vbus_voltage(struct ab8500_charger *di) -{ - int vch; - - /* Only measure voltage if the charger is connected */ - if (di->usb.charger_connected) { - vch = ab8500_gpadc_convert(di->gpadc, VBUS_V); - if (vch < 0) - dev_err(di->dev, "%s gpadc conv failed\n", __func__); - } else { - vch = 0; - } - return vch; -} - -/** - * ab8500_charger_get_usb_current() - get usb charger current - * @di: pointer to the ab8500_charger structure - * - * This function returns the usb charger current. - * Returns usb current (on success) and error code on failure - */ -static int ab8500_charger_get_usb_current(struct ab8500_charger *di) -{ - int ich; - - /* Only measure current if the charger is online */ - if (di->usb.charger_online) { - ich = ab8500_gpadc_convert(di->gpadc, USB_CHARGER_C); - if (ich < 0) - dev_err(di->dev, "%s gpadc conv failed\n", __func__); - } else { - ich = 0; - } - return ich; -} - -/** - * ab8500_charger_get_ac_current() - get ac charger current - * @di: pointer to the ab8500_charger structure - * - * This function returns the ac charger current. - * Returns ac current (on success) and error code on failure. - */ -static int ab8500_charger_get_ac_current(struct ab8500_charger *di) -{ - int ich; - - /* Only measure current if the charger is online */ - if (di->ac.charger_online) { - ich = ab8500_gpadc_convert(di->gpadc, MAIN_CHARGER_C); - if (ich < 0) - dev_err(di->dev, "%s gpadc conv failed\n", __func__); - } else { - ich = 0; - } - return ich; -} - -/** - * ab8500_charger_usb_cv() - check if the usb charger is in CV mode - * @di: pointer to the ab8500_charger structure - * - * Returns ac charger CV mode (on success) else error code - */ -static int ab8500_charger_usb_cv(struct ab8500_charger *di) -{ - int ret; - u8 val; - - /* Only check CV mode if the charger is online */ - if (di->usb.charger_online) { - ret = abx500_get_register_interruptible(di->dev, AB8500_CHARGER, - AB8500_CH_USBCH_STAT1_REG, &val); - if (ret < 0) { - dev_err(di->dev, "%s ab8500 read failed\n", __func__); - return 0; - } - - if (val & USB_CH_CV_ON) - ret = 1; - else - ret = 0; - } else { - ret = 0; - } - - return ret; -} - -/** - * ab8500_charger_detect_chargers() - Detect the connected chargers - * @di: pointer to the ab8500_charger structure - * @probe: if probe, don't delay and wait for HW - * - * Returns the type of charger connected. - * For USB it will not mean we can actually charge from it - * but that there is a USB cable connected that we have to - * identify. This is used during startup when we don't get - * interrupts of the charger detection - * - * Returns an integer value, that means, - * NO_PW_CONN no power supply is connected - * AC_PW_CONN if the AC power supply is connected - * USB_PW_CONN if the USB power supply is connected - * AC_PW_CONN + USB_PW_CONN if USB and AC power supplies are both connected - */ -static int ab8500_charger_detect_chargers(struct ab8500_charger *di, bool probe) -{ - int result = NO_PW_CONN; - int ret; - u8 val; - - /* Check for AC charger */ - ret = abx500_get_register_interruptible(di->dev, AB8500_CHARGER, - AB8500_CH_STATUS1_REG, &val); - if (ret < 0) { - dev_err(di->dev, "%s ab8500 read failed\n", __func__); - return ret; - } - - if (val & MAIN_CH_DET) - result = AC_PW_CONN; - - /* Check for USB charger */ - - if (!probe) { - /* - * AB8500 says VBUS_DET_DBNC1 & VBUS_DET_DBNC100 - * when disconnecting ACA even though no - * charger was connected. Try waiting a little - * longer than the 100 ms of VBUS_DET_DBNC100... - */ - msleep(110); - } - ret = abx500_get_register_interruptible(di->dev, AB8500_CHARGER, - AB8500_CH_USBCH_STAT1_REG, &val); - if (ret < 0) { - dev_err(di->dev, "%s ab8500 read failed\n", __func__); - return ret; - } - dev_dbg(di->dev, - "%s AB8500_CH_USBCH_STAT1_REG %x\n", __func__, - val); - if ((val & VBUS_DET_DBNC1) && (val & VBUS_DET_DBNC100)) - result |= USB_PW_CONN; - - return result; -} - -/** - * ab8500_charger_max_usb_curr() - get the max curr for the USB type - * @di: pointer to the ab8500_charger structure - * @link_status: the identified USB type - * - * Get the maximum current that is allowed to be drawn from the host - * based on the USB type. - * Returns error code in case of failure else 0 on success - */ -static int ab8500_charger_max_usb_curr(struct ab8500_charger *di, - enum ab8500_charger_link_status link_status) -{ - int ret = 0; - - di->usb_device_is_unrecognised = false; - - /* - * Platform only supports USB 2.0. - * This means that charging current from USB source - * is maximum 500 mA. Every occurence of USB_STAT_*_HOST_* - * should set USB_CH_IP_CUR_LVL_0P5. - */ - - switch (link_status) { - case USB_STAT_STD_HOST_NC: - case USB_STAT_STD_HOST_C_NS: - case USB_STAT_STD_HOST_C_S: - dev_dbg(di->dev, "USB Type - Standard host is " - "detected through USB driver\n"); - di->max_usb_in_curr.usb_type_max = USB_CH_IP_CUR_LVL_0P5; - di->is_aca_rid = 0; - break; - case USB_STAT_HOST_CHG_HS_CHIRP: - di->max_usb_in_curr.usb_type_max = USB_CH_IP_CUR_LVL_0P5; - di->is_aca_rid = 0; - break; - case USB_STAT_HOST_CHG_HS: - di->max_usb_in_curr.usb_type_max = USB_CH_IP_CUR_LVL_0P5; - di->is_aca_rid = 0; - break; - case USB_STAT_ACA_RID_C_HS: - di->max_usb_in_curr.usb_type_max = USB_CH_IP_CUR_LVL_0P9; - di->is_aca_rid = 0; - break; - case USB_STAT_ACA_RID_A: - /* - * Dedicated charger level minus maximum current accessory - * can consume (900mA). Closest level is 500mA - */ - dev_dbg(di->dev, "USB_STAT_ACA_RID_A detected\n"); - di->max_usb_in_curr.usb_type_max = USB_CH_IP_CUR_LVL_0P5; - di->is_aca_rid = 1; - break; - case USB_STAT_ACA_RID_B: - /* - * Dedicated charger level minus 120mA (20mA for ACA and - * 100mA for potential accessory). Closest level is 1300mA - */ - di->max_usb_in_curr.usb_type_max = USB_CH_IP_CUR_LVL_1P3; - dev_dbg(di->dev, "USB Type - 0x%02x MaxCurr: %d", link_status, - di->max_usb_in_curr.usb_type_max); - di->is_aca_rid = 1; - break; - case USB_STAT_HOST_CHG_NM: - di->max_usb_in_curr.usb_type_max = USB_CH_IP_CUR_LVL_0P5; - di->is_aca_rid = 0; - break; - case USB_STAT_DEDICATED_CHG: - di->max_usb_in_curr.usb_type_max = USB_CH_IP_CUR_LVL_1P5; - di->is_aca_rid = 0; - break; - case USB_STAT_ACA_RID_C_HS_CHIRP: - case USB_STAT_ACA_RID_C_NM: - di->max_usb_in_curr.usb_type_max = USB_CH_IP_CUR_LVL_1P5; - di->is_aca_rid = 1; - break; - case USB_STAT_NOT_CONFIGURED: - if (di->vbus_detected) { - di->usb_device_is_unrecognised = true; - dev_dbg(di->dev, "USB Type - Legacy charger.\n"); - di->max_usb_in_curr.usb_type_max = - USB_CH_IP_CUR_LVL_1P5; - break; - } - case USB_STAT_HM_IDGND: - dev_err(di->dev, "USB Type - Charging not allowed\n"); - di->max_usb_in_curr.usb_type_max = USB_CH_IP_CUR_LVL_0P05; - ret = -ENXIO; - break; - case USB_STAT_RESERVED: - if (is_ab8500(di->parent)) { - di->flags.vbus_collapse = true; - dev_err(di->dev, "USB Type - USB_STAT_RESERVED " - "VBUS has collapsed\n"); - ret = -ENXIO; - break; - } else { - dev_dbg(di->dev, "USB Type - Charging not allowed\n"); - di->max_usb_in_curr.usb_type_max = - USB_CH_IP_CUR_LVL_0P05; - dev_dbg(di->dev, "USB Type - 0x%02x MaxCurr: %d", - link_status, - di->max_usb_in_curr.usb_type_max); - ret = -ENXIO; - break; - } - case USB_STAT_CARKIT_1: - case USB_STAT_CARKIT_2: - case USB_STAT_ACA_DOCK_CHARGER: - case USB_STAT_CHARGER_LINE_1: - di->max_usb_in_curr.usb_type_max = USB_CH_IP_CUR_LVL_0P5; - dev_dbg(di->dev, "USB Type - 0x%02x MaxCurr: %d", link_status, - di->max_usb_in_curr.usb_type_max); - break; - case USB_STAT_NOT_VALID_LINK: - dev_err(di->dev, "USB Type invalid - try charging anyway\n"); - di->max_usb_in_curr.usb_type_max = USB_CH_IP_CUR_LVL_0P5; - break; - - default: - dev_err(di->dev, "USB Type - Unknown\n"); - di->max_usb_in_curr.usb_type_max = USB_CH_IP_CUR_LVL_0P05; - ret = -ENXIO; - break; - }; - - di->max_usb_in_curr.set_max = di->max_usb_in_curr.usb_type_max; - dev_dbg(di->dev, "USB Type - 0x%02x MaxCurr: %d", - link_status, di->max_usb_in_curr.set_max); - - return ret; -} - -/** - * ab8500_charger_read_usb_type() - read the type of usb connected - * @di: pointer to the ab8500_charger structure - * - * Detect the type of the plugged USB - * Returns error code in case of failure else 0 on success - */ -static int ab8500_charger_read_usb_type(struct ab8500_charger *di) -{ - int ret; - u8 val; - - ret = abx500_get_register_interruptible(di->dev, - AB8500_INTERRUPT, AB8500_IT_SOURCE21_REG, &val); - if (ret < 0) { - dev_err(di->dev, "%s ab8500 read failed\n", __func__); - return ret; - } - if (is_ab8500(di->parent)) - ret = abx500_get_register_interruptible(di->dev, AB8500_USB, - AB8500_USB_LINE_STAT_REG, &val); - else - ret = abx500_get_register_interruptible(di->dev, - AB8500_USB, AB8500_USB_LINK1_STAT_REG, &val); - if (ret < 0) { - dev_err(di->dev, "%s ab8500 read failed\n", __func__); - return ret; - } - - /* get the USB type */ - if (is_ab8500(di->parent)) - val = (val & AB8500_USB_LINK_STATUS) >> USB_LINK_STATUS_SHIFT; - else - val = (val & AB8505_USB_LINK_STATUS) >> USB_LINK_STATUS_SHIFT; - ret = ab8500_charger_max_usb_curr(di, - (enum ab8500_charger_link_status) val); - - return ret; -} - -/** - * ab8500_charger_detect_usb_type() - get the type of usb connected - * @di: pointer to the ab8500_charger structure - * - * Detect the type of the plugged USB - * Returns error code in case of failure else 0 on success - */ -static int ab8500_charger_detect_usb_type(struct ab8500_charger *di) -{ - int i, ret; - u8 val; - - /* - * On getting the VBUS rising edge detect interrupt there - * is a 250ms delay after which the register UsbLineStatus - * is filled with valid data. - */ - for (i = 0; i < 10; i++) { - msleep(250); - ret = abx500_get_register_interruptible(di->dev, - AB8500_INTERRUPT, AB8500_IT_SOURCE21_REG, - &val); - dev_dbg(di->dev, "%s AB8500_IT_SOURCE21_REG %x\n", - __func__, val); - if (ret < 0) { - dev_err(di->dev, "%s ab8500 read failed\n", __func__); - return ret; - } - - if (is_ab8500(di->parent)) - ret = abx500_get_register_interruptible(di->dev, - AB8500_USB, AB8500_USB_LINE_STAT_REG, &val); - else - ret = abx500_get_register_interruptible(di->dev, - AB8500_USB, AB8500_USB_LINK1_STAT_REG, &val); - if (ret < 0) { - dev_err(di->dev, "%s ab8500 read failed\n", __func__); - return ret; - } - dev_dbg(di->dev, "%s AB8500_USB_LINE_STAT_REG %x\n", __func__, - val); - /* - * Until the IT source register is read the UsbLineStatus - * register is not updated, hence doing the same - * Revisit this: - */ - - /* get the USB type */ - if (is_ab8500(di->parent)) - val = (val & AB8500_USB_LINK_STATUS) >> - USB_LINK_STATUS_SHIFT; - else - val = (val & AB8505_USB_LINK_STATUS) >> - USB_LINK_STATUS_SHIFT; - if (val) - break; - } - ret = ab8500_charger_max_usb_curr(di, - (enum ab8500_charger_link_status) val); - - return ret; -} - -/* - * This array maps the raw hex value to charger voltage used by the AB8500 - * Values taken from the UM0836 - */ -static int ab8500_charger_voltage_map[] = { - 3500 , - 3525 , - 3550 , - 3575 , - 3600 , - 3625 , - 3650 , - 3675 , - 3700 , - 3725 , - 3750 , - 3775 , - 3800 , - 3825 , - 3850 , - 3875 , - 3900 , - 3925 , - 3950 , - 3975 , - 4000 , - 4025 , - 4050 , - 4060 , - 4070 , - 4080 , - 4090 , - 4100 , - 4110 , - 4120 , - 4130 , - 4140 , - 4150 , - 4160 , - 4170 , - 4180 , - 4190 , - 4200 , - 4210 , - 4220 , - 4230 , - 4240 , - 4250 , - 4260 , - 4270 , - 4280 , - 4290 , - 4300 , - 4310 , - 4320 , - 4330 , - 4340 , - 4350 , - 4360 , - 4370 , - 4380 , - 4390 , - 4400 , - 4410 , - 4420 , - 4430 , - 4440 , - 4450 , - 4460 , - 4470 , - 4480 , - 4490 , - 4500 , - 4510 , - 4520 , - 4530 , - 4540 , - 4550 , - 4560 , - 4570 , - 4580 , - 4590 , - 4600 , -}; - -static int ab8500_voltage_to_regval(int voltage) -{ - int i; - - /* Special case for voltage below 3.5V */ - if (voltage < ab8500_charger_voltage_map[0]) - return LOW_VOLT_REG; - - for (i = 1; i < ARRAY_SIZE(ab8500_charger_voltage_map); i++) { - if (voltage < ab8500_charger_voltage_map[i]) - return i - 1; - } - - /* If not last element, return error */ - i = ARRAY_SIZE(ab8500_charger_voltage_map) - 1; - if (voltage == ab8500_charger_voltage_map[i]) - return i; - else - return -1; -} - -static int ab8500_current_to_regval(struct ab8500_charger *di, int curr) -{ - int i; - - if (curr < di->bm->chg_output_curr[0]) - return 0; - - for (i = 0; i < di->bm->n_chg_out_curr; i++) { - if (curr < di->bm->chg_output_curr[i]) - return i - 1; - } - - /* If not last element, return error */ - i = di->bm->n_chg_out_curr - 1; - if (curr == di->bm->chg_output_curr[i]) - return i; - else - return -1; -} - -static int ab8500_vbus_in_curr_to_regval(struct ab8500_charger *di, int curr) -{ - int i; - - if (curr < di->bm->chg_input_curr[0]) - return 0; - - for (i = 0; i < di->bm->n_chg_in_curr; i++) { - if (curr < di->bm->chg_input_curr[i]) - return i - 1; - } - - /* If not last element, return error */ - i = di->bm->n_chg_in_curr - 1; - if (curr == di->bm->chg_input_curr[i]) - return i; - else - return -1; -} - -/** - * ab8500_charger_get_usb_cur() - get usb current - * @di: pointer to the ab8500_charger structre - * - * The usb stack provides the maximum current that can be drawn from - * the standard usb host. This will be in mA. - * This function converts current in mA to a value that can be written - * to the register. Returns -1 if charging is not allowed - */ -static int ab8500_charger_get_usb_cur(struct ab8500_charger *di) -{ - int ret = 0; - switch (di->usb_state.usb_current) { - case 100: - di->max_usb_in_curr.usb_type_max = USB_CH_IP_CUR_LVL_0P09; - break; - case 200: - di->max_usb_in_curr.usb_type_max = USB_CH_IP_CUR_LVL_0P19; - break; - case 300: - di->max_usb_in_curr.usb_type_max = USB_CH_IP_CUR_LVL_0P29; - break; - case 400: - di->max_usb_in_curr.usb_type_max = USB_CH_IP_CUR_LVL_0P38; - break; - case 500: - di->max_usb_in_curr.usb_type_max = USB_CH_IP_CUR_LVL_0P5; - break; - default: - di->max_usb_in_curr.usb_type_max = USB_CH_IP_CUR_LVL_0P05; - ret = -EPERM; - break; - }; - di->max_usb_in_curr.set_max = di->max_usb_in_curr.usb_type_max; - return ret; -} - -/** - * ab8500_charger_check_continue_stepping() - Check to allow stepping - * @di: pointer to the ab8500_charger structure - * @reg: select what charger register to check - * - * Check if current stepping should be allowed to continue. - * Checks if charger source has not collapsed. If it has, further stepping - * is not allowed. - */ -static bool ab8500_charger_check_continue_stepping(struct ab8500_charger *di, - int reg) -{ - if (reg == AB8500_USBCH_IPT_CRNTLVL_REG) - return !di->flags.vbus_drop_end; - else - return true; -} - -/** - * ab8500_charger_set_current() - set charger current - * @di: pointer to the ab8500_charger structure - * @ich: charger current, in mA - * @reg: select what charger register to set - * - * Set charger current. - * There is no state machine in the AB to step up/down the charger - * current to avoid dips and spikes on MAIN, VBUS and VBAT when - * charging is started. Instead we need to implement - * this charger current step-up/down here. - * Returns error code in case of failure else 0(on success) - */ -static int ab8500_charger_set_current(struct ab8500_charger *di, - int ich, int reg) -{ - int ret = 0; - int curr_index, prev_curr_index, shift_value, i; - u8 reg_value; - u32 step_udelay; - bool no_stepping = false; - - atomic_inc(&di->current_stepping_sessions); - - ret = abx500_get_register_interruptible(di->dev, AB8500_CHARGER, - reg, ®_value); - if (ret < 0) { - dev_err(di->dev, "%s read failed\n", __func__); - goto exit_set_current; - } - - switch (reg) { - case AB8500_MCH_IPT_CURLVL_REG: - shift_value = MAIN_CH_INPUT_CURR_SHIFT; - prev_curr_index = (reg_value >> shift_value); - curr_index = ab8500_current_to_regval(di, ich); - step_udelay = STEP_UDELAY; - if (!di->ac.charger_connected) - no_stepping = true; - break; - case AB8500_USBCH_IPT_CRNTLVL_REG: - if (is_ab8540(di->parent)) - shift_value = AB8540_VBUS_IN_CURR_LIM_SHIFT; - else - shift_value = VBUS_IN_CURR_LIM_SHIFT; - prev_curr_index = (reg_value >> shift_value); - curr_index = ab8500_vbus_in_curr_to_regval(di, ich); - step_udelay = STEP_UDELAY * 100; - - if (!di->usb.charger_connected) - no_stepping = true; - break; - case AB8500_CH_OPT_CRNTLVL_REG: - shift_value = 0; - prev_curr_index = (reg_value >> shift_value); - curr_index = ab8500_current_to_regval(di, ich); - step_udelay = STEP_UDELAY; - if (curr_index && (curr_index - prev_curr_index) > 1) - step_udelay *= 100; - - if (!di->usb.charger_connected && !di->ac.charger_connected) - no_stepping = true; - - break; - default: - dev_err(di->dev, "%s current register not valid\n", __func__); - ret = -ENXIO; - goto exit_set_current; - } - - if (curr_index < 0) { - dev_err(di->dev, "requested current limit out-of-range\n"); - ret = -ENXIO; - goto exit_set_current; - } - - /* only update current if it's been changed */ - if (prev_curr_index == curr_index) { - dev_dbg(di->dev, "%s current not changed for reg: 0x%02x\n", - __func__, reg); - ret = 0; - goto exit_set_current; - } - - dev_dbg(di->dev, "%s set charger current: %d mA for reg: 0x%02x\n", - __func__, ich, reg); - - if (no_stepping) { - ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER, - reg, (u8)curr_index << shift_value); - if (ret) - dev_err(di->dev, "%s write failed\n", __func__); - } else if (prev_curr_index > curr_index) { - for (i = prev_curr_index - 1; i >= curr_index; i--) { - dev_dbg(di->dev, "curr change_1 to: %x for 0x%02x\n", - (u8) i << shift_value, reg); - ret = abx500_set_register_interruptible(di->dev, - AB8500_CHARGER, reg, (u8)i << shift_value); - if (ret) { - dev_err(di->dev, "%s write failed\n", __func__); - goto exit_set_current; - } - if (i != curr_index) - usleep_range(step_udelay, step_udelay * 2); - } - } else { - bool allow = true; - for (i = prev_curr_index + 1; i <= curr_index && allow; i++) { - dev_dbg(di->dev, "curr change_2 to: %x for 0x%02x\n", - (u8)i << shift_value, reg); - ret = abx500_set_register_interruptible(di->dev, - AB8500_CHARGER, reg, (u8)i << shift_value); - if (ret) { - dev_err(di->dev, "%s write failed\n", __func__); - goto exit_set_current; - } - if (i != curr_index) - usleep_range(step_udelay, step_udelay * 2); - - allow = ab8500_charger_check_continue_stepping(di, reg); - } - } - -exit_set_current: - atomic_dec(&di->current_stepping_sessions); - - return ret; -} - -/** - * ab8500_charger_set_vbus_in_curr() - set VBUS input current limit - * @di: pointer to the ab8500_charger structure - * @ich_in: charger input current limit - * - * Sets the current that can be drawn from the USB host - * Returns error code in case of failure else 0(on success) - */ -static int ab8500_charger_set_vbus_in_curr(struct ab8500_charger *di, - int ich_in) -{ - int min_value; - int ret; - - /* We should always use to lowest current limit */ - min_value = min(di->bm->chg_params->usb_curr_max, ich_in); - if (di->max_usb_in_curr.set_max > 0) - min_value = min(di->max_usb_in_curr.set_max, min_value); - - if (di->usb_state.usb_current >= 0) - min_value = min(di->usb_state.usb_current, min_value); - - switch (min_value) { - case 100: - if (di->vbat < VBAT_TRESH_IP_CUR_RED) - min_value = USB_CH_IP_CUR_LVL_0P05; - break; - case 500: - if (di->vbat < VBAT_TRESH_IP_CUR_RED) - min_value = USB_CH_IP_CUR_LVL_0P45; - break; - default: - break; - } - - dev_info(di->dev, "VBUS input current limit set to %d mA\n", min_value); - - mutex_lock(&di->usb_ipt_crnt_lock); - ret = ab8500_charger_set_current(di, min_value, - AB8500_USBCH_IPT_CRNTLVL_REG); - mutex_unlock(&di->usb_ipt_crnt_lock); - - return ret; -} - -/** - * ab8500_charger_set_main_in_curr() - set main charger input current - * @di: pointer to the ab8500_charger structure - * @ich_in: input charger current, in mA - * - * Set main charger input current. - * Returns error code in case of failure else 0(on success) - */ -static int ab8500_charger_set_main_in_curr(struct ab8500_charger *di, - int ich_in) -{ - return ab8500_charger_set_current(di, ich_in, - AB8500_MCH_IPT_CURLVL_REG); -} - -/** - * ab8500_charger_set_output_curr() - set charger output current - * @di: pointer to the ab8500_charger structure - * @ich_out: output charger current, in mA - * - * Set charger output current. - * Returns error code in case of failure else 0(on success) - */ -static int ab8500_charger_set_output_curr(struct ab8500_charger *di, - int ich_out) -{ - return ab8500_charger_set_current(di, ich_out, - AB8500_CH_OPT_CRNTLVL_REG); -} - -/** - * ab8500_charger_led_en() - turn on/off chargign led - * @di: pointer to the ab8500_charger structure - * @on: flag to turn on/off the chargign led - * - * Power ON/OFF charging LED indication - * Returns error code in case of failure else 0(on success) - */ -static int ab8500_charger_led_en(struct ab8500_charger *di, int on) -{ - int ret; - - if (on) { - /* Power ON charging LED indicator, set LED current to 5mA */ - ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER, - AB8500_LED_INDICATOR_PWM_CTRL, - (LED_IND_CUR_5MA | LED_INDICATOR_PWM_ENA)); - if (ret) { - dev_err(di->dev, "Power ON LED failed\n"); - return ret; - } - /* LED indicator PWM duty cycle 252/256 */ - ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER, - AB8500_LED_INDICATOR_PWM_DUTY, - LED_INDICATOR_PWM_DUTY_252_256); - if (ret) { - dev_err(di->dev, "Set LED PWM duty cycle failed\n"); - return ret; - } - } else { - /* Power off charging LED indicator */ - ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER, - AB8500_LED_INDICATOR_PWM_CTRL, - LED_INDICATOR_PWM_DIS); - if (ret) { - dev_err(di->dev, "Power-off LED failed\n"); - return ret; - } - } - - return ret; -} - -/** - * ab8500_charger_ac_en() - enable or disable ac charging - * @di: pointer to the ab8500_charger structure - * @enable: enable/disable flag - * @vset: charging voltage - * @iset: charging current - * - * Enable/Disable AC/Mains charging and turns on/off the charging led - * respectively. - **/ -static int ab8500_charger_ac_en(struct ux500_charger *charger, - int enable, int vset, int iset) -{ - int ret; - int volt_index; - int curr_index; - int input_curr_index; - u8 overshoot = 0; - - struct ab8500_charger *di = to_ab8500_charger_ac_device_info(charger); - - if (enable) { - /* Check if AC is connected */ - if (!di->ac.charger_connected) { - dev_err(di->dev, "AC charger not connected\n"); - return -ENXIO; - } - - /* Enable AC charging */ - dev_dbg(di->dev, "Enable AC: %dmV %dmA\n", vset, iset); - - /* - * Due to a bug in AB8500, BTEMP_HIGH/LOW interrupts - * will be triggered everytime we enable the VDD ADC supply. - * This will turn off charging for a short while. - * It can be avoided by having the supply on when - * there is a charger enabled. Normally the VDD ADC supply - * is enabled everytime a GPADC conversion is triggered. We will - * force it to be enabled from this driver to have - * the GPADC module independant of the AB8500 chargers - */ - if (!di->vddadc_en_ac) { - ret = regulator_enable(di->regu); - if (ret) - dev_warn(di->dev, - "Failed to enable regulator\n"); - else - di->vddadc_en_ac = true; - } - - /* Check if the requested voltage or current is valid */ - volt_index = ab8500_voltage_to_regval(vset); - curr_index = ab8500_current_to_regval(di, iset); - input_curr_index = ab8500_current_to_regval(di, - di->bm->chg_params->ac_curr_max); - if (volt_index < 0 || curr_index < 0 || input_curr_index < 0) { - dev_err(di->dev, - "Charger voltage or current too high, " - "charging not started\n"); - return -ENXIO; - } - - /* ChVoltLevel: maximum battery charging voltage */ - ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER, - AB8500_CH_VOLT_LVL_REG, (u8) volt_index); - if (ret) { - dev_err(di->dev, "%s write failed\n", __func__); - return ret; - } - /* MainChInputCurr: current that can be drawn from the charger*/ - ret = ab8500_charger_set_main_in_curr(di, - di->bm->chg_params->ac_curr_max); - if (ret) { - dev_err(di->dev, "%s Failed to set MainChInputCurr\n", - __func__); - return ret; - } - /* ChOutputCurentLevel: protected output current */ - ret = ab8500_charger_set_output_curr(di, iset); - if (ret) { - dev_err(di->dev, "%s " - "Failed to set ChOutputCurentLevel\n", - __func__); - return ret; - } - - /* Check if VBAT overshoot control should be enabled */ - if (!di->bm->enable_overshoot) - overshoot = MAIN_CH_NO_OVERSHOOT_ENA_N; - - /* Enable Main Charger */ - ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER, - AB8500_MCH_CTRL1, MAIN_CH_ENA | overshoot); - if (ret) { - dev_err(di->dev, "%s write failed\n", __func__); - return ret; - } - - /* Power on charging LED indication */ - ret = ab8500_charger_led_en(di, true); - if (ret < 0) - dev_err(di->dev, "failed to enable LED\n"); - - di->ac.charger_online = 1; - } else { - /* Disable AC charging */ - if (is_ab8500_1p1_or_earlier(di->parent)) { - /* - * For ABB revision 1.0 and 1.1 there is a bug in the - * watchdog logic. That means we have to continously - * kick the charger watchdog even when no charger is - * connected. This is only valid once the AC charger - * has been enabled. This is a bug that is not handled - * by the algorithm and the watchdog have to be kicked - * by the charger driver when the AC charger - * is disabled - */ - if (di->ac_conn) { - queue_delayed_work(di->charger_wq, - &di->kick_wd_work, - round_jiffies(WD_KICK_INTERVAL)); - } - - /* - * We can't turn off charging completely - * due to a bug in AB8500 cut1. - * If we do, charging will not start again. - * That is why we set the lowest voltage - * and current possible - */ - ret = abx500_set_register_interruptible(di->dev, - AB8500_CHARGER, - AB8500_CH_VOLT_LVL_REG, CH_VOL_LVL_3P5); - if (ret) { - dev_err(di->dev, - "%s write failed\n", __func__); - return ret; - } - - ret = ab8500_charger_set_output_curr(di, 0); - if (ret) { - dev_err(di->dev, "%s " - "Failed to set ChOutputCurentLevel\n", - __func__); - return ret; - } - } else { - ret = abx500_set_register_interruptible(di->dev, - AB8500_CHARGER, - AB8500_MCH_CTRL1, 0); - if (ret) { - dev_err(di->dev, - "%s write failed\n", __func__); - return ret; - } - } - - ret = ab8500_charger_led_en(di, false); - if (ret < 0) - dev_err(di->dev, "failed to disable LED\n"); - - di->ac.charger_online = 0; - di->ac.wd_expired = false; - - /* Disable regulator if enabled */ - if (di->vddadc_en_ac) { - regulator_disable(di->regu); - di->vddadc_en_ac = false; - } - - dev_dbg(di->dev, "%s Disabled AC charging\n", __func__); - } - ab8500_power_supply_changed(di, di->ac_chg.psy); - - return ret; -} - -/** - * ab8500_charger_usb_en() - enable usb charging - * @di: pointer to the ab8500_charger structure - * @enable: enable/disable flag - * @vset: charging voltage - * @ich_out: charger output current - * - * Enable/Disable USB charging and turns on/off the charging led respectively. - * Returns error code in case of failure else 0(on success) - */ -static int ab8500_charger_usb_en(struct ux500_charger *charger, - int enable, int vset, int ich_out) -{ - int ret; - int volt_index; - int curr_index; - u8 overshoot = 0; - - struct ab8500_charger *di = to_ab8500_charger_usb_device_info(charger); - - if (enable) { - /* Check if USB is connected */ - if (!di->usb.charger_connected) { - dev_err(di->dev, "USB charger not connected\n"); - return -ENXIO; - } - - /* - * Due to a bug in AB8500, BTEMP_HIGH/LOW interrupts - * will be triggered everytime we enable the VDD ADC supply. - * This will turn off charging for a short while. - * It can be avoided by having the supply on when - * there is a charger enabled. Normally the VDD ADC supply - * is enabled everytime a GPADC conversion is triggered. We will - * force it to be enabled from this driver to have - * the GPADC module independant of the AB8500 chargers - */ - if (!di->vddadc_en_usb) { - ret = regulator_enable(di->regu); - if (ret) - dev_warn(di->dev, - "Failed to enable regulator\n"); - else - di->vddadc_en_usb = true; - } - - /* Enable USB charging */ - dev_dbg(di->dev, "Enable USB: %dmV %dmA\n", vset, ich_out); - - /* Check if the requested voltage or current is valid */ - volt_index = ab8500_voltage_to_regval(vset); - curr_index = ab8500_current_to_regval(di, ich_out); - if (volt_index < 0 || curr_index < 0) { - dev_err(di->dev, - "Charger voltage or current too high, " - "charging not started\n"); - return -ENXIO; - } - - /* ChVoltLevel: max voltage upto which battery can be charged */ - ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER, - AB8500_CH_VOLT_LVL_REG, (u8) volt_index); - if (ret) { - dev_err(di->dev, "%s write failed\n", __func__); - return ret; - } - /* Check if VBAT overshoot control should be enabled */ - if (!di->bm->enable_overshoot) - overshoot = USB_CHG_NO_OVERSHOOT_ENA_N; - - /* Enable USB Charger */ - dev_dbg(di->dev, - "Enabling USB with write to AB8500_USBCH_CTRL1_REG\n"); - ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER, - AB8500_USBCH_CTRL1_REG, USB_CH_ENA | overshoot); - if (ret) { - dev_err(di->dev, "%s write failed\n", __func__); - return ret; - } - - /* If success power on charging LED indication */ - ret = ab8500_charger_led_en(di, true); - if (ret < 0) - dev_err(di->dev, "failed to enable LED\n"); - - di->usb.charger_online = 1; - - /* USBChInputCurr: current that can be drawn from the usb */ - ret = ab8500_charger_set_vbus_in_curr(di, - di->max_usb_in_curr.usb_type_max); - if (ret) { - dev_err(di->dev, "setting USBChInputCurr failed\n"); - return ret; - } - - /* ChOutputCurentLevel: protected output current */ - ret = ab8500_charger_set_output_curr(di, ich_out); - if (ret) { - dev_err(di->dev, "%s " - "Failed to set ChOutputCurentLevel\n", - __func__); - return ret; - } - - queue_delayed_work(di->charger_wq, &di->check_vbat_work, HZ); - - } else { - /* Disable USB charging */ - dev_dbg(di->dev, "%s Disabled USB charging\n", __func__); - ret = abx500_set_register_interruptible(di->dev, - AB8500_CHARGER, - AB8500_USBCH_CTRL1_REG, 0); - if (ret) { - dev_err(di->dev, - "%s write failed\n", __func__); - return ret; - } - - ret = ab8500_charger_led_en(di, false); - if (ret < 0) - dev_err(di->dev, "failed to disable LED\n"); - /* USBChInputCurr: current that can be drawn from the usb */ - ret = ab8500_charger_set_vbus_in_curr(di, 0); - if (ret) { - dev_err(di->dev, "setting USBChInputCurr failed\n"); - return ret; - } - - /* ChOutputCurentLevel: protected output current */ - ret = ab8500_charger_set_output_curr(di, 0); - if (ret) { - dev_err(di->dev, "%s " - "Failed to reset ChOutputCurentLevel\n", - __func__); - return ret; - } - di->usb.charger_online = 0; - di->usb.wd_expired = false; - - /* Disable regulator if enabled */ - if (di->vddadc_en_usb) { - regulator_disable(di->regu); - di->vddadc_en_usb = false; - } - - dev_dbg(di->dev, "%s Disabled USB charging\n", __func__); - - /* Cancel any pending Vbat check work */ - cancel_delayed_work(&di->check_vbat_work); - - } - ab8500_power_supply_changed(di, di->usb_chg.psy); - - return ret; -} - -static int ab8500_external_charger_prepare(struct notifier_block *charger_nb, - unsigned long event, void *data) -{ - int ret; - struct device *dev = data; - /*Toggle External charger control pin*/ - ret = abx500_set_register_interruptible(dev, AB8500_SYS_CTRL1_BLOCK, - AB8500_SYS_CHARGER_CONTROL_REG, - EXTERNAL_CHARGER_DISABLE_REG_VAL); - if (ret < 0) { - dev_err(dev, "write reg failed %d\n", ret); - goto out; - } - ret = abx500_set_register_interruptible(dev, AB8500_SYS_CTRL1_BLOCK, - AB8500_SYS_CHARGER_CONTROL_REG, - EXTERNAL_CHARGER_ENABLE_REG_VAL); - if (ret < 0) - dev_err(dev, "Write reg failed %d\n", ret); - -out: - return ret; -} - -/** - * ab8500_charger_usb_check_enable() - enable usb charging - * @charger: pointer to the ux500_charger structure - * @vset: charging voltage - * @iset: charger output current - * - * Check if the VBUS charger has been disconnected and reconnected without - * AB8500 rising an interrupt. Returns 0 on success. - */ -static int ab8500_charger_usb_check_enable(struct ux500_charger *charger, - int vset, int iset) -{ - u8 usbch_ctrl1 = 0; - int ret = 0; - - struct ab8500_charger *di = to_ab8500_charger_usb_device_info(charger); - - if (!di->usb.charger_connected) - return ret; - - ret = abx500_get_register_interruptible(di->dev, AB8500_CHARGER, - AB8500_USBCH_CTRL1_REG, &usbch_ctrl1); - if (ret < 0) { - dev_err(di->dev, "ab8500 read failed %d\n", __LINE__); - return ret; - } - dev_dbg(di->dev, "USB charger ctrl: 0x%02x\n", usbch_ctrl1); - - if (!(usbch_ctrl1 & USB_CH_ENA)) { - dev_info(di->dev, "Charging has been disabled abnormally and will be re-enabled\n"); - - ret = abx500_mask_and_set_register_interruptible(di->dev, - AB8500_CHARGER, AB8500_CHARGER_CTRL, - DROP_COUNT_RESET, DROP_COUNT_RESET); - if (ret < 0) { - dev_err(di->dev, "ab8500 write failed %d\n", __LINE__); - return ret; - } - - ret = ab8500_charger_usb_en(&di->usb_chg, true, vset, iset); - if (ret < 0) { - dev_err(di->dev, "Failed to enable VBUS charger %d\n", - __LINE__); - return ret; - } - } - return ret; -} - -/** - * ab8500_charger_ac_check_enable() - enable usb charging - * @charger: pointer to the ux500_charger structure - * @vset: charging voltage - * @iset: charger output current - * - * Check if the AC charger has been disconnected and reconnected without - * AB8500 rising an interrupt. Returns 0 on success. - */ -static int ab8500_charger_ac_check_enable(struct ux500_charger *charger, - int vset, int iset) -{ - u8 mainch_ctrl1 = 0; - int ret = 0; - - struct ab8500_charger *di = to_ab8500_charger_ac_device_info(charger); - - if (!di->ac.charger_connected) - return ret; - - ret = abx500_get_register_interruptible(di->dev, AB8500_CHARGER, - AB8500_MCH_CTRL1, &mainch_ctrl1); - if (ret < 0) { - dev_err(di->dev, "ab8500 read failed %d\n", __LINE__); - return ret; - } - dev_dbg(di->dev, "AC charger ctrl: 0x%02x\n", mainch_ctrl1); - - if (!(mainch_ctrl1 & MAIN_CH_ENA)) { - dev_info(di->dev, "Charging has been disabled abnormally and will be re-enabled\n"); - - ret = abx500_mask_and_set_register_interruptible(di->dev, - AB8500_CHARGER, AB8500_CHARGER_CTRL, - DROP_COUNT_RESET, DROP_COUNT_RESET); - - if (ret < 0) { - dev_err(di->dev, "ab8500 write failed %d\n", __LINE__); - return ret; - } - - ret = ab8500_charger_ac_en(&di->usb_chg, true, vset, iset); - if (ret < 0) { - dev_err(di->dev, "failed to enable AC charger %d\n", - __LINE__); - return ret; - } - } - return ret; -} - -/** - * ab8500_charger_watchdog_kick() - kick charger watchdog - * @di: pointer to the ab8500_charger structure - * - * Kick charger watchdog - * Returns error code in case of failure else 0(on success) - */ -static int ab8500_charger_watchdog_kick(struct ux500_charger *charger) -{ - int ret; - struct ab8500_charger *di; - - if (charger->psy->desc->type == POWER_SUPPLY_TYPE_MAINS) - di = to_ab8500_charger_ac_device_info(charger); - else if (charger->psy->desc->type == POWER_SUPPLY_TYPE_USB) - di = to_ab8500_charger_usb_device_info(charger); - else - return -ENXIO; - - ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER, - AB8500_CHARG_WD_CTRL, CHARG_WD_KICK); - if (ret) - dev_err(di->dev, "Failed to kick WD!\n"); - - return ret; -} - -/** - * ab8500_charger_update_charger_current() - update charger current - * @di: pointer to the ab8500_charger structure - * - * Update the charger output current for the specified charger - * Returns error code in case of failure else 0(on success) - */ -static int ab8500_charger_update_charger_current(struct ux500_charger *charger, - int ich_out) -{ - int ret; - struct ab8500_charger *di; - - if (charger->psy->desc->type == POWER_SUPPLY_TYPE_MAINS) - di = to_ab8500_charger_ac_device_info(charger); - else if (charger->psy->desc->type == POWER_SUPPLY_TYPE_USB) - di = to_ab8500_charger_usb_device_info(charger); - else - return -ENXIO; - - ret = ab8500_charger_set_output_curr(di, ich_out); - if (ret) { - dev_err(di->dev, "%s " - "Failed to set ChOutputCurentLevel\n", - __func__); - return ret; - } - - /* Reset the main and usb drop input current measurement counter */ - ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER, - AB8500_CHARGER_CTRL, DROP_COUNT_RESET); - if (ret) { - dev_err(di->dev, "%s write failed\n", __func__); - return ret; - } - - return ret; -} - -/** - * ab8540_charger_power_path_enable() - enable usb power path mode - * @charger: pointer to the ux500_charger structure - * @enable: enable/disable flag - * - * Enable or disable the power path for usb mode - * Returns error code in case of failure else 0(on success) - */ -static int ab8540_charger_power_path_enable(struct ux500_charger *charger, - bool enable) -{ - int ret; - struct ab8500_charger *di; - - if (charger->psy->desc->type == POWER_SUPPLY_TYPE_USB) - di = to_ab8500_charger_usb_device_info(charger); - else - return -ENXIO; - - ret = abx500_mask_and_set_register_interruptible(di->dev, - AB8500_CHARGER, AB8540_USB_PP_MODE_REG, - BUS_POWER_PATH_MODE_ENA, enable); - if (ret) { - dev_err(di->dev, "%s write failed\n", __func__); - return ret; - } - - return ret; -} - - -/** - * ab8540_charger_usb_pre_chg_enable() - enable usb pre change - * @charger: pointer to the ux500_charger structure - * @enable: enable/disable flag - * - * Enable or disable the pre-chage for usb mode - * Returns error code in case of failure else 0(on success) - */ -static int ab8540_charger_usb_pre_chg_enable(struct ux500_charger *charger, - bool enable) -{ - int ret; - struct ab8500_charger *di; - - if (charger->psy->desc->type == POWER_SUPPLY_TYPE_USB) - di = to_ab8500_charger_usb_device_info(charger); - else - return -ENXIO; - - ret = abx500_mask_and_set_register_interruptible(di->dev, - AB8500_CHARGER, AB8540_USB_PP_CHR_REG, - BUS_POWER_PATH_PRECHG_ENA, enable); - if (ret) { - dev_err(di->dev, "%s write failed\n", __func__); - return ret; - } - - return ret; -} - -static int ab8500_charger_get_ext_psy_data(struct device *dev, void *data) -{ - struct power_supply *psy; - struct power_supply *ext = dev_get_drvdata(dev); - const char **supplicants = (const char **)ext->supplied_to; - struct ab8500_charger *di; - union power_supply_propval ret; - int j; - struct ux500_charger *usb_chg; - - usb_chg = (struct ux500_charger *)data; - psy = usb_chg->psy; - - di = to_ab8500_charger_usb_device_info(usb_chg); - - /* For all psy where the driver name appears in any supplied_to */ - j = match_string(supplicants, ext->num_supplicants, psy->desc->name); - if (j < 0) - return 0; - - /* Go through all properties for the psy */ - for (j = 0; j < ext->desc->num_properties; j++) { - enum power_supply_property prop; - prop = ext->desc->properties[j]; - - if (power_supply_get_property(ext, prop, &ret)) - continue; - - switch (prop) { - case POWER_SUPPLY_PROP_VOLTAGE_NOW: - switch (ext->desc->type) { - case POWER_SUPPLY_TYPE_BATTERY: - di->vbat = ret.intval / 1000; - break; - default: - break; - } - break; - default: - break; - } - } - return 0; -} - -/** - * ab8500_charger_check_vbat_work() - keep vbus current within spec - * @work pointer to the work_struct structure - * - * Due to a asic bug it is necessary to lower the input current to the vbus - * charger when charging with at some specific levels. This issue is only valid - * for below a certain battery voltage. This function makes sure that the - * the allowed current limit isn't exceeded. - */ -static void ab8500_charger_check_vbat_work(struct work_struct *work) -{ - int t = 10; - struct ab8500_charger *di = container_of(work, - struct ab8500_charger, check_vbat_work.work); - - class_for_each_device(power_supply_class, NULL, - di->usb_chg.psy, ab8500_charger_get_ext_psy_data); - - /* First run old_vbat is 0. */ - if (di->old_vbat == 0) - di->old_vbat = di->vbat; - - if (!((di->old_vbat <= VBAT_TRESH_IP_CUR_RED && - di->vbat <= VBAT_TRESH_IP_CUR_RED) || - (di->old_vbat > VBAT_TRESH_IP_CUR_RED && - di->vbat > VBAT_TRESH_IP_CUR_RED))) { - - dev_dbg(di->dev, "Vbat did cross threshold, curr: %d, new: %d," - " old: %d\n", di->max_usb_in_curr.usb_type_max, - di->vbat, di->old_vbat); - ab8500_charger_set_vbus_in_curr(di, - di->max_usb_in_curr.usb_type_max); - power_supply_changed(di->usb_chg.psy); - } - - di->old_vbat = di->vbat; - - /* - * No need to check the battery voltage every second when not close to - * the threshold. - */ - if (di->vbat < (VBAT_TRESH_IP_CUR_RED + 100) && - (di->vbat > (VBAT_TRESH_IP_CUR_RED - 100))) - t = 1; - - queue_delayed_work(di->charger_wq, &di->check_vbat_work, t * HZ); -} - -/** - * ab8500_charger_check_hw_failure_work() - check main charger failure - * @work: pointer to the work_struct structure - * - * Work queue function for checking the main charger status - */ -static void ab8500_charger_check_hw_failure_work(struct work_struct *work) -{ - int ret; - u8 reg_value; - - struct ab8500_charger *di = container_of(work, - struct ab8500_charger, check_hw_failure_work.work); - - /* Check if the status bits for HW failure is still active */ - if (di->flags.mainextchnotok) { - ret = abx500_get_register_interruptible(di->dev, - AB8500_CHARGER, AB8500_CH_STATUS2_REG, ®_value); - if (ret < 0) { - dev_err(di->dev, "%s ab8500 read failed\n", __func__); - return; - } - if (!(reg_value & MAIN_CH_NOK)) { - di->flags.mainextchnotok = false; - ab8500_power_supply_changed(di, di->ac_chg.psy); - } - } - if (di->flags.vbus_ovv) { - ret = abx500_get_register_interruptible(di->dev, - AB8500_CHARGER, AB8500_CH_USBCH_STAT2_REG, - ®_value); - if (ret < 0) { - dev_err(di->dev, "%s ab8500 read failed\n", __func__); - return; - } - if (!(reg_value & VBUS_OVV_TH)) { - di->flags.vbus_ovv = false; - ab8500_power_supply_changed(di, di->usb_chg.psy); - } - } - /* If we still have a failure, schedule a new check */ - if (di->flags.mainextchnotok || di->flags.vbus_ovv) { - queue_delayed_work(di->charger_wq, - &di->check_hw_failure_work, round_jiffies(HZ)); - } -} - -/** - * ab8500_charger_kick_watchdog_work() - kick the watchdog - * @work: pointer to the work_struct structure - * - * Work queue function for kicking the charger watchdog. - * - * For ABB revision 1.0 and 1.1 there is a bug in the watchdog - * logic. That means we have to continously kick the charger - * watchdog even when no charger is connected. This is only - * valid once the AC charger has been enabled. This is - * a bug that is not handled by the algorithm and the - * watchdog have to be kicked by the charger driver - * when the AC charger is disabled - */ -static void ab8500_charger_kick_watchdog_work(struct work_struct *work) -{ - int ret; - - struct ab8500_charger *di = container_of(work, - struct ab8500_charger, kick_wd_work.work); - - ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER, - AB8500_CHARG_WD_CTRL, CHARG_WD_KICK); - if (ret) - dev_err(di->dev, "Failed to kick WD!\n"); - - /* Schedule a new watchdog kick */ - queue_delayed_work(di->charger_wq, - &di->kick_wd_work, round_jiffies(WD_KICK_INTERVAL)); -} - -/** - * ab8500_charger_ac_work() - work to get and set main charger status - * @work: pointer to the work_struct structure - * - * Work queue function for checking the main charger status - */ -static void ab8500_charger_ac_work(struct work_struct *work) -{ - int ret; - - struct ab8500_charger *di = container_of(work, - struct ab8500_charger, ac_work); - - /* - * Since we can't be sure that the events are received - * synchronously, we have the check if the main charger is - * connected by reading the status register - */ - ret = ab8500_charger_detect_chargers(di, false); - if (ret < 0) - return; - - if (ret & AC_PW_CONN) { - di->ac.charger_connected = 1; - di->ac_conn = true; - } else { - di->ac.charger_connected = 0; - } - - ab8500_power_supply_changed(di, di->ac_chg.psy); - sysfs_notify(&di->ac_chg.psy->dev.kobj, NULL, "present"); -} - -static void ab8500_charger_usb_attached_work(struct work_struct *work) -{ - struct ab8500_charger *di = container_of(work, - struct ab8500_charger, - usb_charger_attached_work.work); - int usbch = (USB_CH_VBUSDROP | USB_CH_VBUSDETDBNC); - int ret, i; - u8 statval; - - for (i = 0; i < 10; i++) { - ret = abx500_get_register_interruptible(di->dev, - AB8500_CHARGER, - AB8500_CH_USBCH_STAT1_REG, - &statval); - if (ret < 0) { - dev_err(di->dev, "ab8500 read failed %d\n", __LINE__); - goto reschedule; - } - if ((statval & usbch) != usbch) - goto reschedule; - - msleep(CHARGER_STATUS_POLL); - } - - ab8500_charger_usb_en(&di->usb_chg, 0, 0, 0); - - mutex_lock(&di->charger_attached_mutex); - mutex_unlock(&di->charger_attached_mutex); - - return; - -reschedule: - queue_delayed_work(di->charger_wq, - &di->usb_charger_attached_work, - HZ); -} - -static void ab8500_charger_ac_attached_work(struct work_struct *work) -{ - - struct ab8500_charger *di = container_of(work, - struct ab8500_charger, - ac_charger_attached_work.work); - int mainch = (MAIN_CH_STATUS2_MAINCHGDROP | - MAIN_CH_STATUS2_MAINCHARGERDETDBNC); - int ret, i; - u8 statval; - - for (i = 0; i < 10; i++) { - ret = abx500_get_register_interruptible(di->dev, - AB8500_CHARGER, - AB8500_CH_STATUS2_REG, - &statval); - if (ret < 0) { - dev_err(di->dev, "ab8500 read failed %d\n", __LINE__); - goto reschedule; - } - - if ((statval & mainch) != mainch) - goto reschedule; - - msleep(CHARGER_STATUS_POLL); - } - - ab8500_charger_ac_en(&di->ac_chg, 0, 0, 0); - queue_work(di->charger_wq, &di->ac_work); - - mutex_lock(&di->charger_attached_mutex); - mutex_unlock(&di->charger_attached_mutex); - - return; - -reschedule: - queue_delayed_work(di->charger_wq, - &di->ac_charger_attached_work, - HZ); -} - -/** - * ab8500_charger_detect_usb_type_work() - work to detect USB type - * @work: Pointer to the work_struct structure - * - * Detect the type of USB plugged - */ -static void ab8500_charger_detect_usb_type_work(struct work_struct *work) -{ - int ret; - - struct ab8500_charger *di = container_of(work, - struct ab8500_charger, detect_usb_type_work); - - /* - * Since we can't be sure that the events are received - * synchronously, we have the check if is - * connected by reading the status register - */ - ret = ab8500_charger_detect_chargers(di, false); - if (ret < 0) - return; - - if (!(ret & USB_PW_CONN)) { - dev_dbg(di->dev, "%s di->vbus_detected = false\n", __func__); - di->vbus_detected = false; - ab8500_charger_set_usb_connected(di, false); - ab8500_power_supply_changed(di, di->usb_chg.psy); - } else { - dev_dbg(di->dev, "%s di->vbus_detected = true\n", __func__); - di->vbus_detected = true; - - if (is_ab8500_1p1_or_earlier(di->parent)) { - ret = ab8500_charger_detect_usb_type(di); - if (!ret) { - ab8500_charger_set_usb_connected(di, true); - ab8500_power_supply_changed(di, - di->usb_chg.psy); - } - } else { - /* - * For ABB cut2.0 and onwards we have an IRQ, - * USB_LINK_STATUS that will be triggered when the USB - * link status changes. The exception is USB connected - * during startup. Then we don't get a - * USB_LINK_STATUS IRQ - */ - if (di->vbus_detected_start) { - di->vbus_detected_start = false; - ret = ab8500_charger_detect_usb_type(di); - if (!ret) { - ab8500_charger_set_usb_connected(di, - true); - ab8500_power_supply_changed(di, - di->usb_chg.psy); - } - } - } - } -} - -/** - * ab8500_charger_usb_link_attach_work() - work to detect USB type - * @work: pointer to the work_struct structure - * - * Detect the type of USB plugged - */ -static void ab8500_charger_usb_link_attach_work(struct work_struct *work) -{ - struct ab8500_charger *di = - container_of(work, struct ab8500_charger, attach_work.work); - int ret; - - /* Update maximum input current if USB enumeration is not detected */ - if (!di->usb.charger_online) { - ret = ab8500_charger_set_vbus_in_curr(di, - di->max_usb_in_curr.usb_type_max); - if (ret) - return; - } - - ab8500_charger_set_usb_connected(di, true); - ab8500_power_supply_changed(di, di->usb_chg.psy); -} - -/** - * ab8500_charger_usb_link_status_work() - work to detect USB type - * @work: pointer to the work_struct structure - * - * Detect the type of USB plugged - */ -static void ab8500_charger_usb_link_status_work(struct work_struct *work) -{ - int detected_chargers; - int ret; - u8 val; - u8 link_status; - - struct ab8500_charger *di = container_of(work, - struct ab8500_charger, usb_link_status_work); - - /* - * Since we can't be sure that the events are received - * synchronously, we have the check if is - * connected by reading the status register - */ - detected_chargers = ab8500_charger_detect_chargers(di, false); - if (detected_chargers < 0) - return; - - /* - * Some chargers that breaks the USB spec is - * identified as invalid by AB8500 and it refuse - * to start the charging process. but by jumping - * thru a few hoops it can be forced to start. - */ - if (is_ab8500(di->parent)) - ret = abx500_get_register_interruptible(di->dev, AB8500_USB, - AB8500_USB_LINE_STAT_REG, &val); - else - ret = abx500_get_register_interruptible(di->dev, AB8500_USB, - AB8500_USB_LINK1_STAT_REG, &val); - - if (ret >= 0) - dev_dbg(di->dev, "UsbLineStatus register = 0x%02x\n", val); - else - dev_dbg(di->dev, "Error reading USB link status\n"); - - if (is_ab8500(di->parent)) - link_status = AB8500_USB_LINK_STATUS; - else - link_status = AB8505_USB_LINK_STATUS; - - if (detected_chargers & USB_PW_CONN) { - if (((val & link_status) >> USB_LINK_STATUS_SHIFT) == - USB_STAT_NOT_VALID_LINK && - di->invalid_charger_detect_state == 0) { - dev_dbg(di->dev, - "Invalid charger detected, state= 0\n"); - /*Enable charger*/ - abx500_mask_and_set_register_interruptible(di->dev, - AB8500_CHARGER, AB8500_USBCH_CTRL1_REG, - USB_CH_ENA, USB_CH_ENA); - /*Enable charger detection*/ - abx500_mask_and_set_register_interruptible(di->dev, - AB8500_USB, AB8500_USB_LINE_CTRL2_REG, - USB_CH_DET, USB_CH_DET); - di->invalid_charger_detect_state = 1; - /*exit and wait for new link status interrupt.*/ - return; - - } - if (di->invalid_charger_detect_state == 1) { - dev_dbg(di->dev, - "Invalid charger detected, state= 1\n"); - /*Stop charger detection*/ - abx500_mask_and_set_register_interruptible(di->dev, - AB8500_USB, AB8500_USB_LINE_CTRL2_REG, - USB_CH_DET, 0x00); - /*Check link status*/ - if (is_ab8500(di->parent)) - ret = abx500_get_register_interruptible(di->dev, - AB8500_USB, AB8500_USB_LINE_STAT_REG, - &val); - else - ret = abx500_get_register_interruptible(di->dev, - AB8500_USB, AB8500_USB_LINK1_STAT_REG, - &val); - - dev_dbg(di->dev, "USB link status= 0x%02x\n", - (val & link_status) >> USB_LINK_STATUS_SHIFT); - di->invalid_charger_detect_state = 2; - } - } else { - di->invalid_charger_detect_state = 0; - } - - if (!(detected_chargers & USB_PW_CONN)) { - di->vbus_detected = false; - ab8500_charger_set_usb_connected(di, false); - ab8500_power_supply_changed(di, di->usb_chg.psy); - return; - } - - dev_dbg(di->dev,"%s di->vbus_detected = true\n",__func__); - di->vbus_detected = true; - ret = ab8500_charger_read_usb_type(di); - if (ret) { - if (ret == -ENXIO) { - /* No valid charger type detected */ - ab8500_charger_set_usb_connected(di, false); - ab8500_power_supply_changed(di, di->usb_chg.psy); - } - return; - } - - if (di->usb_device_is_unrecognised) { - dev_dbg(di->dev, - "Potential Legacy Charger device. " - "Delay work for %d msec for USB enum " - "to finish", - WAIT_ACA_RID_ENUMERATION); - queue_delayed_work(di->charger_wq, - &di->attach_work, - msecs_to_jiffies(WAIT_ACA_RID_ENUMERATION)); - } else if (di->is_aca_rid == 1) { - /* Only wait once */ - di->is_aca_rid++; - dev_dbg(di->dev, - "%s Wait %d msec for USB enum to finish", - __func__, WAIT_ACA_RID_ENUMERATION); - queue_delayed_work(di->charger_wq, - &di->attach_work, - msecs_to_jiffies(WAIT_ACA_RID_ENUMERATION)); - } else { - queue_delayed_work(di->charger_wq, - &di->attach_work, - 0); - } -} - -static void ab8500_charger_usb_state_changed_work(struct work_struct *work) -{ - int ret; - unsigned long flags; - - struct ab8500_charger *di = container_of(work, - struct ab8500_charger, usb_state_changed_work.work); - - if (!di->vbus_detected) { - dev_dbg(di->dev, - "%s !di->vbus_detected\n", - __func__); - return; - } - - spin_lock_irqsave(&di->usb_state.usb_lock, flags); - di->usb_state.state = di->usb_state.state_tmp; - di->usb_state.usb_current = di->usb_state.usb_current_tmp; - spin_unlock_irqrestore(&di->usb_state.usb_lock, flags); - - dev_dbg(di->dev, "%s USB state: 0x%02x mA: %d\n", - __func__, di->usb_state.state, di->usb_state.usb_current); - - switch (di->usb_state.state) { - case AB8500_BM_USB_STATE_RESET_HS: - case AB8500_BM_USB_STATE_RESET_FS: - case AB8500_BM_USB_STATE_SUSPEND: - case AB8500_BM_USB_STATE_MAX: - ab8500_charger_set_usb_connected(di, false); - ab8500_power_supply_changed(di, di->usb_chg.psy); - break; - - case AB8500_BM_USB_STATE_RESUME: - /* - * when suspend->resume there should be delay - * of 1sec for enabling charging - */ - msleep(1000); - /* Intentional fall through */ - case AB8500_BM_USB_STATE_CONFIGURED: - /* - * USB is configured, enable charging with the charging - * input current obtained from USB driver - */ - if (!ab8500_charger_get_usb_cur(di)) { - /* Update maximum input current */ - ret = ab8500_charger_set_vbus_in_curr(di, - di->max_usb_in_curr.usb_type_max); - if (ret) - return; - - ab8500_charger_set_usb_connected(di, true); - ab8500_power_supply_changed(di, di->usb_chg.psy); - } - break; - - default: - break; - }; -} - -/** - * ab8500_charger_check_usbchargernotok_work() - check USB chg not ok status - * @work: pointer to the work_struct structure - * - * Work queue function for checking the USB charger Not OK status - */ -static void ab8500_charger_check_usbchargernotok_work(struct work_struct *work) -{ - int ret; - u8 reg_value; - bool prev_status; - - struct ab8500_charger *di = container_of(work, - struct ab8500_charger, check_usbchgnotok_work.work); - - /* Check if the status bit for usbchargernotok is still active */ - ret = abx500_get_register_interruptible(di->dev, - AB8500_CHARGER, AB8500_CH_USBCH_STAT2_REG, ®_value); - if (ret < 0) { - dev_err(di->dev, "%s ab8500 read failed\n", __func__); - return; - } - prev_status = di->flags.usbchargernotok; - - if (reg_value & VBUS_CH_NOK) { - di->flags.usbchargernotok = true; - /* Check again in 1sec */ - queue_delayed_work(di->charger_wq, - &di->check_usbchgnotok_work, HZ); - } else { - di->flags.usbchargernotok = false; - di->flags.vbus_collapse = false; - } - - if (prev_status != di->flags.usbchargernotok) - ab8500_power_supply_changed(di, di->usb_chg.psy); -} - -/** - * ab8500_charger_check_main_thermal_prot_work() - check main thermal status - * @work: pointer to the work_struct structure - * - * Work queue function for checking the Main thermal prot status - */ -static void ab8500_charger_check_main_thermal_prot_work( - struct work_struct *work) -{ - int ret; - u8 reg_value; - - struct ab8500_charger *di = container_of(work, - struct ab8500_charger, check_main_thermal_prot_work); - - /* Check if the status bit for main_thermal_prot is still active */ - ret = abx500_get_register_interruptible(di->dev, - AB8500_CHARGER, AB8500_CH_STATUS2_REG, ®_value); - if (ret < 0) { - dev_err(di->dev, "%s ab8500 read failed\n", __func__); - return; - } - if (reg_value & MAIN_CH_TH_PROT) - di->flags.main_thermal_prot = true; - else - di->flags.main_thermal_prot = false; - - ab8500_power_supply_changed(di, di->ac_chg.psy); -} - -/** - * ab8500_charger_check_usb_thermal_prot_work() - check usb thermal status - * @work: pointer to the work_struct structure - * - * Work queue function for checking the USB thermal prot status - */ -static void ab8500_charger_check_usb_thermal_prot_work( - struct work_struct *work) -{ - int ret; - u8 reg_value; - - struct ab8500_charger *di = container_of(work, - struct ab8500_charger, check_usb_thermal_prot_work); - - /* Check if the status bit for usb_thermal_prot is still active */ - ret = abx500_get_register_interruptible(di->dev, - AB8500_CHARGER, AB8500_CH_USBCH_STAT2_REG, ®_value); - if (ret < 0) { - dev_err(di->dev, "%s ab8500 read failed\n", __func__); - return; - } - if (reg_value & USB_CH_TH_PROT) - di->flags.usb_thermal_prot = true; - else - di->flags.usb_thermal_prot = false; - - ab8500_power_supply_changed(di, di->usb_chg.psy); -} - -/** - * ab8500_charger_mainchunplugdet_handler() - main charger unplugged - * @irq: interrupt number - * @_di: pointer to the ab8500_charger structure - * - * Returns IRQ status(IRQ_HANDLED) - */ -static irqreturn_t ab8500_charger_mainchunplugdet_handler(int irq, void *_di) -{ - struct ab8500_charger *di = _di; - - dev_dbg(di->dev, "Main charger unplugged\n"); - queue_work(di->charger_wq, &di->ac_work); - - cancel_delayed_work_sync(&di->ac_charger_attached_work); - mutex_lock(&di->charger_attached_mutex); - mutex_unlock(&di->charger_attached_mutex); - - return IRQ_HANDLED; -} - -/** - * ab8500_charger_mainchplugdet_handler() - main charger plugged - * @irq: interrupt number - * @_di: pointer to the ab8500_charger structure - * - * Returns IRQ status(IRQ_HANDLED) - */ -static irqreturn_t ab8500_charger_mainchplugdet_handler(int irq, void *_di) -{ - struct ab8500_charger *di = _di; - - dev_dbg(di->dev, "Main charger plugged\n"); - queue_work(di->charger_wq, &di->ac_work); - - mutex_lock(&di->charger_attached_mutex); - mutex_unlock(&di->charger_attached_mutex); - - if (is_ab8500(di->parent)) - queue_delayed_work(di->charger_wq, - &di->ac_charger_attached_work, - HZ); - return IRQ_HANDLED; -} - -/** - * ab8500_charger_mainextchnotok_handler() - main charger not ok - * @irq: interrupt number - * @_di: pointer to the ab8500_charger structure - * - * Returns IRQ status(IRQ_HANDLED) - */ -static irqreturn_t ab8500_charger_mainextchnotok_handler(int irq, void *_di) -{ - struct ab8500_charger *di = _di; - - dev_dbg(di->dev, "Main charger not ok\n"); - di->flags.mainextchnotok = true; - ab8500_power_supply_changed(di, di->ac_chg.psy); - - /* Schedule a new HW failure check */ - queue_delayed_work(di->charger_wq, &di->check_hw_failure_work, 0); - - return IRQ_HANDLED; -} - -/** - * ab8500_charger_mainchthprotr_handler() - Die temp is above main charger - * thermal protection threshold - * @irq: interrupt number - * @_di: pointer to the ab8500_charger structure - * - * Returns IRQ status(IRQ_HANDLED) - */ -static irqreturn_t ab8500_charger_mainchthprotr_handler(int irq, void *_di) -{ - struct ab8500_charger *di = _di; - - dev_dbg(di->dev, - "Die temp above Main charger thermal protection threshold\n"); - queue_work(di->charger_wq, &di->check_main_thermal_prot_work); - - return IRQ_HANDLED; -} - -/** - * ab8500_charger_mainchthprotf_handler() - Die temp is below main charger - * thermal protection threshold - * @irq: interrupt number - * @_di: pointer to the ab8500_charger structure - * - * Returns IRQ status(IRQ_HANDLED) - */ -static irqreturn_t ab8500_charger_mainchthprotf_handler(int irq, void *_di) -{ - struct ab8500_charger *di = _di; - - dev_dbg(di->dev, - "Die temp ok for Main charger thermal protection threshold\n"); - queue_work(di->charger_wq, &di->check_main_thermal_prot_work); - - return IRQ_HANDLED; -} - -static void ab8500_charger_vbus_drop_end_work(struct work_struct *work) -{ - struct ab8500_charger *di = container_of(work, - struct ab8500_charger, vbus_drop_end_work.work); - int ret, curr; - u8 reg_value; - - di->flags.vbus_drop_end = false; - - /* Reset the drop counter */ - abx500_set_register_interruptible(di->dev, - AB8500_CHARGER, AB8500_CHARGER_CTRL, 0x01); - - if (is_ab8540(di->parent)) - ret = abx500_get_register_interruptible(di->dev, AB8500_CHARGER, - AB8540_CH_USBCH_STAT3_REG, ®_value); - else - ret = abx500_get_register_interruptible(di->dev, AB8500_CHARGER, - AB8500_CH_USBCH_STAT2_REG, ®_value); - if (ret < 0) { - dev_err(di->dev, "%s read failed\n", __func__); - return; - } - - if (is_ab8540(di->parent)) - curr = di->bm->chg_input_curr[ - reg_value & AB8540_AUTO_VBUS_IN_CURR_MASK]; - else - curr = di->bm->chg_input_curr[ - reg_value >> AUTO_VBUS_IN_CURR_LIM_SHIFT]; - - if (di->max_usb_in_curr.calculated_max != curr) { - /* USB source is collapsing */ - di->max_usb_in_curr.calculated_max = curr; - dev_dbg(di->dev, - "VBUS input current limiting to %d mA\n", - di->max_usb_in_curr.calculated_max); - } else { - /* - * USB source can not give more than this amount. - * Taking more will collapse the source. - */ - di->max_usb_in_curr.set_max = - di->max_usb_in_curr.calculated_max; - dev_dbg(di->dev, - "VBUS input current limited to %d mA\n", - di->max_usb_in_curr.set_max); - } - - if (di->usb.charger_connected) - ab8500_charger_set_vbus_in_curr(di, - di->max_usb_in_curr.usb_type_max); -} - -/** - * ab8500_charger_vbusdetf_handler() - VBUS falling detected - * @irq: interrupt number - * @_di: pointer to the ab8500_charger structure - * - * Returns IRQ status(IRQ_HANDLED) - */ -static irqreturn_t ab8500_charger_vbusdetf_handler(int irq, void *_di) -{ - struct ab8500_charger *di = _di; - - di->vbus_detected = false; - dev_dbg(di->dev, "VBUS falling detected\n"); - queue_work(di->charger_wq, &di->detect_usb_type_work); - - return IRQ_HANDLED; -} - -/** - * ab8500_charger_vbusdetr_handler() - VBUS rising detected - * @irq: interrupt number - * @_di: pointer to the ab8500_charger structure - * - * Returns IRQ status(IRQ_HANDLED) - */ -static irqreturn_t ab8500_charger_vbusdetr_handler(int irq, void *_di) -{ - struct ab8500_charger *di = _di; - - di->vbus_detected = true; - dev_dbg(di->dev, "VBUS rising detected\n"); - - queue_work(di->charger_wq, &di->detect_usb_type_work); - - return IRQ_HANDLED; -} - -/** - * ab8500_charger_usblinkstatus_handler() - USB link status has changed - * @irq: interrupt number - * @_di: pointer to the ab8500_charger structure - * - * Returns IRQ status(IRQ_HANDLED) - */ -static irqreturn_t ab8500_charger_usblinkstatus_handler(int irq, void *_di) -{ - struct ab8500_charger *di = _di; - - dev_dbg(di->dev, "USB link status changed\n"); - - queue_work(di->charger_wq, &di->usb_link_status_work); - - return IRQ_HANDLED; -} - -/** - * ab8500_charger_usbchthprotr_handler() - Die temp is above usb charger - * thermal protection threshold - * @irq: interrupt number - * @_di: pointer to the ab8500_charger structure - * - * Returns IRQ status(IRQ_HANDLED) - */ -static irqreturn_t ab8500_charger_usbchthprotr_handler(int irq, void *_di) -{ - struct ab8500_charger *di = _di; - - dev_dbg(di->dev, - "Die temp above USB charger thermal protection threshold\n"); - queue_work(di->charger_wq, &di->check_usb_thermal_prot_work); - - return IRQ_HANDLED; -} - -/** - * ab8500_charger_usbchthprotf_handler() - Die temp is below usb charger - * thermal protection threshold - * @irq: interrupt number - * @_di: pointer to the ab8500_charger structure - * - * Returns IRQ status(IRQ_HANDLED) - */ -static irqreturn_t ab8500_charger_usbchthprotf_handler(int irq, void *_di) -{ - struct ab8500_charger *di = _di; - - dev_dbg(di->dev, - "Die temp ok for USB charger thermal protection threshold\n"); - queue_work(di->charger_wq, &di->check_usb_thermal_prot_work); - - return IRQ_HANDLED; -} - -/** - * ab8500_charger_usbchargernotokr_handler() - USB charger not ok detected - * @irq: interrupt number - * @_di: pointer to the ab8500_charger structure - * - * Returns IRQ status(IRQ_HANDLED) - */ -static irqreturn_t ab8500_charger_usbchargernotokr_handler(int irq, void *_di) -{ - struct ab8500_charger *di = _di; - - dev_dbg(di->dev, "Not allowed USB charger detected\n"); - queue_delayed_work(di->charger_wq, &di->check_usbchgnotok_work, 0); - - return IRQ_HANDLED; -} - -/** - * ab8500_charger_chwdexp_handler() - Charger watchdog expired - * @irq: interrupt number - * @_di: pointer to the ab8500_charger structure - * - * Returns IRQ status(IRQ_HANDLED) - */ -static irqreturn_t ab8500_charger_chwdexp_handler(int irq, void *_di) -{ - struct ab8500_charger *di = _di; - - dev_dbg(di->dev, "Charger watchdog expired\n"); - - /* - * The charger that was online when the watchdog expired - * needs to be restarted for charging to start again - */ - if (di->ac.charger_online) { - di->ac.wd_expired = true; - ab8500_power_supply_changed(di, di->ac_chg.psy); - } - if (di->usb.charger_online) { - di->usb.wd_expired = true; - ab8500_power_supply_changed(di, di->usb_chg.psy); - } - - return IRQ_HANDLED; -} - -/** - * ab8500_charger_vbuschdropend_handler() - VBUS drop removed - * @irq: interrupt number - * @_di: pointer to the ab8500_charger structure - * - * Returns IRQ status(IRQ_HANDLED) - */ -static irqreturn_t ab8500_charger_vbuschdropend_handler(int irq, void *_di) -{ - struct ab8500_charger *di = _di; - - dev_dbg(di->dev, "VBUS charger drop ended\n"); - di->flags.vbus_drop_end = true; - - /* - * VBUS might have dropped due to bad connection. - * Schedule a new input limit set to the value SW requests. - */ - queue_delayed_work(di->charger_wq, &di->vbus_drop_end_work, - round_jiffies(VBUS_IN_CURR_LIM_RETRY_SET_TIME * HZ)); - - return IRQ_HANDLED; -} - -/** - * ab8500_charger_vbusovv_handler() - VBUS overvoltage detected - * @irq: interrupt number - * @_di: pointer to the ab8500_charger structure - * - * Returns IRQ status(IRQ_HANDLED) - */ -static irqreturn_t ab8500_charger_vbusovv_handler(int irq, void *_di) -{ - struct ab8500_charger *di = _di; - - dev_dbg(di->dev, "VBUS overvoltage detected\n"); - di->flags.vbus_ovv = true; - ab8500_power_supply_changed(di, di->usb_chg.psy); - - /* Schedule a new HW failure check */ - queue_delayed_work(di->charger_wq, &di->check_hw_failure_work, 0); - - return IRQ_HANDLED; -} - -/** - * ab8500_charger_ac_get_property() - get the ac/mains properties - * @psy: pointer to the power_supply structure - * @psp: pointer to the power_supply_property structure - * @val: pointer to the power_supply_propval union - * - * This function gets called when an application tries to get the ac/mains - * properties by reading the sysfs files. - * AC/Mains properties are online, present and voltage. - * online: ac/mains charging is in progress or not - * present: presence of the ac/mains - * voltage: AC/Mains voltage - * Returns error code in case of failure else 0(on success) - */ -static int ab8500_charger_ac_get_property(struct power_supply *psy, - enum power_supply_property psp, - union power_supply_propval *val) -{ - struct ab8500_charger *di; - int ret; - - di = to_ab8500_charger_ac_device_info(psy_to_ux500_charger(psy)); - - switch (psp) { - case POWER_SUPPLY_PROP_HEALTH: - if (di->flags.mainextchnotok) - val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; - else if (di->ac.wd_expired || di->usb.wd_expired) - val->intval = POWER_SUPPLY_HEALTH_DEAD; - else if (di->flags.main_thermal_prot) - val->intval = POWER_SUPPLY_HEALTH_OVERHEAT; - else - val->intval = POWER_SUPPLY_HEALTH_GOOD; - break; - case POWER_SUPPLY_PROP_ONLINE: - val->intval = di->ac.charger_online; - break; - case POWER_SUPPLY_PROP_PRESENT: - val->intval = di->ac.charger_connected; - break; - case POWER_SUPPLY_PROP_VOLTAGE_NOW: - ret = ab8500_charger_get_ac_voltage(di); - if (ret >= 0) - di->ac.charger_voltage = ret; - /* On error, use previous value */ - val->intval = di->ac.charger_voltage * 1000; - break; - case POWER_SUPPLY_PROP_VOLTAGE_AVG: - /* - * This property is used to indicate when CV mode is entered - * for the AC charger - */ - di->ac.cv_active = ab8500_charger_ac_cv(di); - val->intval = di->ac.cv_active; - break; - case POWER_SUPPLY_PROP_CURRENT_NOW: - ret = ab8500_charger_get_ac_current(di); - if (ret >= 0) - di->ac.charger_current = ret; - val->intval = di->ac.charger_current * 1000; - break; - default: - return -EINVAL; - } - return 0; -} - -/** - * ab8500_charger_usb_get_property() - get the usb properties - * @psy: pointer to the power_supply structure - * @psp: pointer to the power_supply_property structure - * @val: pointer to the power_supply_propval union - * - * This function gets called when an application tries to get the usb - * properties by reading the sysfs files. - * USB properties are online, present and voltage. - * online: usb charging is in progress or not - * present: presence of the usb - * voltage: vbus voltage - * Returns error code in case of failure else 0(on success) - */ -static int ab8500_charger_usb_get_property(struct power_supply *psy, - enum power_supply_property psp, - union power_supply_propval *val) -{ - struct ab8500_charger *di; - int ret; - - di = to_ab8500_charger_usb_device_info(psy_to_ux500_charger(psy)); - - switch (psp) { - case POWER_SUPPLY_PROP_HEALTH: - if (di->flags.usbchargernotok) - val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; - else if (di->ac.wd_expired || di->usb.wd_expired) - val->intval = POWER_SUPPLY_HEALTH_DEAD; - else if (di->flags.usb_thermal_prot) - val->intval = POWER_SUPPLY_HEALTH_OVERHEAT; - else if (di->flags.vbus_ovv) - val->intval = POWER_SUPPLY_HEALTH_OVERVOLTAGE; - else - val->intval = POWER_SUPPLY_HEALTH_GOOD; - break; - case POWER_SUPPLY_PROP_ONLINE: - val->intval = di->usb.charger_online; - break; - case POWER_SUPPLY_PROP_PRESENT: - val->intval = di->usb.charger_connected; - break; - case POWER_SUPPLY_PROP_VOLTAGE_NOW: - ret = ab8500_charger_get_vbus_voltage(di); - if (ret >= 0) - di->usb.charger_voltage = ret; - val->intval = di->usb.charger_voltage * 1000; - break; - case POWER_SUPPLY_PROP_VOLTAGE_AVG: - /* - * This property is used to indicate when CV mode is entered - * for the USB charger - */ - di->usb.cv_active = ab8500_charger_usb_cv(di); - val->intval = di->usb.cv_active; - break; - case POWER_SUPPLY_PROP_CURRENT_NOW: - ret = ab8500_charger_get_usb_current(di); - if (ret >= 0) - di->usb.charger_current = ret; - val->intval = di->usb.charger_current * 1000; - break; - case POWER_SUPPLY_PROP_CURRENT_AVG: - /* - * This property is used to indicate when VBUS has collapsed - * due to too high output current from the USB charger - */ - if (di->flags.vbus_collapse) - val->intval = 1; - else - val->intval = 0; - break; - default: - return -EINVAL; - } - return 0; -} - -/** - * ab8500_charger_init_hw_registers() - Set up charger related registers - * @di: pointer to the ab8500_charger structure - * - * Set up charger OVV, watchdog and maximum voltage registers as well as - * charging of the backup battery - */ -static int ab8500_charger_init_hw_registers(struct ab8500_charger *di) -{ - int ret = 0; - u8 bup_vch_range = 0, vbup33_vrtcn = 0; - - /* Setup maximum charger current and voltage for ABB cut2.0 */ - if (!is_ab8500_1p1_or_earlier(di->parent)) { - ret = abx500_set_register_interruptible(di->dev, - AB8500_CHARGER, - AB8500_CH_VOLT_LVL_MAX_REG, CH_VOL_LVL_4P6); - if (ret) { - dev_err(di->dev, - "failed to set CH_VOLT_LVL_MAX_REG\n"); - goto out; - } - - if (is_ab8540(di->parent)) - ret = abx500_set_register_interruptible(di->dev, - AB8500_CHARGER, AB8500_CH_OPT_CRNTLVL_MAX_REG, - CH_OP_CUR_LVL_2P); - else - ret = abx500_set_register_interruptible(di->dev, - AB8500_CHARGER, AB8500_CH_OPT_CRNTLVL_MAX_REG, - CH_OP_CUR_LVL_1P6); - if (ret) { - dev_err(di->dev, - "failed to set CH_OPT_CRNTLVL_MAX_REG\n"); - goto out; - } - } - - if (is_ab9540_2p0(di->parent) || is_ab9540_3p0(di->parent) - || is_ab8505_2p0(di->parent) || is_ab8540(di->parent)) - ret = abx500_mask_and_set_register_interruptible(di->dev, - AB8500_CHARGER, - AB8500_USBCH_CTRL2_REG, - VBUS_AUTO_IN_CURR_LIM_ENA, - VBUS_AUTO_IN_CURR_LIM_ENA); - else - /* - * VBUS OVV set to 6.3V and enable automatic current limitation - */ - ret = abx500_set_register_interruptible(di->dev, - AB8500_CHARGER, - AB8500_USBCH_CTRL2_REG, - VBUS_OVV_SELECT_6P3V | VBUS_AUTO_IN_CURR_LIM_ENA); - if (ret) { - dev_err(di->dev, - "failed to set automatic current limitation\n"); - goto out; - } - - /* Enable main watchdog in OTP */ - ret = abx500_set_register_interruptible(di->dev, - AB8500_OTP_EMUL, AB8500_OTP_CONF_15, OTP_ENABLE_WD); - if (ret) { - dev_err(di->dev, "failed to enable main WD in OTP\n"); - goto out; - } - - /* Enable main watchdog */ - ret = abx500_set_register_interruptible(di->dev, - AB8500_SYS_CTRL2_BLOCK, - AB8500_MAIN_WDOG_CTRL_REG, MAIN_WDOG_ENA); - if (ret) { - dev_err(di->dev, "faile to enable main watchdog\n"); - goto out; - } - - /* - * Due to internal synchronisation, Enable and Kick watchdog bits - * cannot be enabled in a single write. - * A minimum delay of 2*32 kHz period (62.5µs) must be inserted - * between writing Enable then Kick bits. - */ - udelay(63); - - /* Kick main watchdog */ - ret = abx500_set_register_interruptible(di->dev, - AB8500_SYS_CTRL2_BLOCK, - AB8500_MAIN_WDOG_CTRL_REG, - (MAIN_WDOG_ENA | MAIN_WDOG_KICK)); - if (ret) { - dev_err(di->dev, "failed to kick main watchdog\n"); - goto out; - } - - /* Disable main watchdog */ - ret = abx500_set_register_interruptible(di->dev, - AB8500_SYS_CTRL2_BLOCK, - AB8500_MAIN_WDOG_CTRL_REG, MAIN_WDOG_DIS); - if (ret) { - dev_err(di->dev, "failed to disable main watchdog\n"); - goto out; - } - - /* Set watchdog timeout */ - ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER, - AB8500_CH_WD_TIMER_REG, WD_TIMER); - if (ret) { - dev_err(di->dev, "failed to set charger watchdog timeout\n"); - goto out; - } - - ret = ab8500_charger_led_en(di, false); - if (ret < 0) { - dev_err(di->dev, "failed to disable LED\n"); - goto out; - } - - /* Backup battery voltage and current */ - if (di->bm->bkup_bat_v > BUP_VCH_SEL_3P1V) - bup_vch_range = BUP_VCH_RANGE; - if (di->bm->bkup_bat_v == BUP_VCH_SEL_3P3V) - vbup33_vrtcn = VBUP33_VRTCN; - - ret = abx500_set_register_interruptible(di->dev, - AB8500_RTC, - AB8500_RTC_BACKUP_CHG_REG, - (di->bm->bkup_bat_v & 0x3) | di->bm->bkup_bat_i); - if (ret) { - dev_err(di->dev, "failed to setup backup battery charging\n"); - goto out; - } - if (is_ab8540(di->parent)) { - ret = abx500_set_register_interruptible(di->dev, - AB8500_RTC, - AB8500_RTC_CTRL1_REG, - bup_vch_range | vbup33_vrtcn); - if (ret) { - dev_err(di->dev, - "failed to setup backup battery charging\n"); - goto out; - } - } - - /* Enable backup battery charging */ - abx500_mask_and_set_register_interruptible(di->dev, - AB8500_RTC, AB8500_RTC_CTRL_REG, - RTC_BUP_CH_ENA, RTC_BUP_CH_ENA); - if (ret < 0) - dev_err(di->dev, "%s mask and set failed\n", __func__); - - if (is_ab8540(di->parent)) { - ret = abx500_mask_and_set_register_interruptible(di->dev, - AB8500_CHARGER, AB8540_USB_PP_MODE_REG, - BUS_VSYS_VOL_SELECT_MASK, BUS_VSYS_VOL_SELECT_3P6V); - if (ret) { - dev_err(di->dev, - "failed to setup usb power path vsys voltage\n"); - goto out; - } - ret = abx500_mask_and_set_register_interruptible(di->dev, - AB8500_CHARGER, AB8540_USB_PP_CHR_REG, - BUS_PP_PRECHG_CURRENT_MASK, 0); - if (ret) { - dev_err(di->dev, - "failed to setup usb power path prechage current\n"); - goto out; - } - } - -out: - return ret; -} - -/* - * ab8500 charger driver interrupts and their respective isr - */ -static struct ab8500_charger_interrupts ab8500_charger_irq[] = { - {"MAIN_CH_UNPLUG_DET", ab8500_charger_mainchunplugdet_handler}, - {"MAIN_CHARGE_PLUG_DET", ab8500_charger_mainchplugdet_handler}, - {"MAIN_EXT_CH_NOT_OK", ab8500_charger_mainextchnotok_handler}, - {"MAIN_CH_TH_PROT_R", ab8500_charger_mainchthprotr_handler}, - {"MAIN_CH_TH_PROT_F", ab8500_charger_mainchthprotf_handler}, - {"VBUS_DET_F", ab8500_charger_vbusdetf_handler}, - {"VBUS_DET_R", ab8500_charger_vbusdetr_handler}, - {"USB_LINK_STATUS", ab8500_charger_usblinkstatus_handler}, - {"USB_CH_TH_PROT_R", ab8500_charger_usbchthprotr_handler}, - {"USB_CH_TH_PROT_F", ab8500_charger_usbchthprotf_handler}, - {"USB_CHARGER_NOT_OKR", ab8500_charger_usbchargernotokr_handler}, - {"VBUS_OVV", ab8500_charger_vbusovv_handler}, - {"CH_WD_EXP", ab8500_charger_chwdexp_handler}, - {"VBUS_CH_DROP_END", ab8500_charger_vbuschdropend_handler}, -}; - -static int ab8500_charger_usb_notifier_call(struct notifier_block *nb, - unsigned long event, void *power) -{ - struct ab8500_charger *di = - container_of(nb, struct ab8500_charger, nb); - enum ab8500_usb_state bm_usb_state; - unsigned mA = *((unsigned *)power); - - if (!di) - return NOTIFY_DONE; - - if (event != USB_EVENT_VBUS) { - dev_dbg(di->dev, "not a standard host, returning\n"); - return NOTIFY_DONE; - } - - /* TODO: State is fabricate here. See if charger really needs USB - * state or if mA is enough - */ - if ((di->usb_state.usb_current == 2) && (mA > 2)) - bm_usb_state = AB8500_BM_USB_STATE_RESUME; - else if (mA == 0) - bm_usb_state = AB8500_BM_USB_STATE_RESET_HS; - else if (mA == 2) - bm_usb_state = AB8500_BM_USB_STATE_SUSPEND; - else if (mA >= 8) /* 8, 100, 500 */ - bm_usb_state = AB8500_BM_USB_STATE_CONFIGURED; - else /* Should never occur */ - bm_usb_state = AB8500_BM_USB_STATE_RESET_FS; - - dev_dbg(di->dev, "%s usb_state: 0x%02x mA: %d\n", - __func__, bm_usb_state, mA); - - spin_lock(&di->usb_state.usb_lock); - di->usb_state.state_tmp = bm_usb_state; - di->usb_state.usb_current_tmp = mA; - spin_unlock(&di->usb_state.usb_lock); - - /* - * wait for some time until you get updates from the usb stack - * and negotiations are completed - */ - queue_delayed_work(di->charger_wq, &di->usb_state_changed_work, HZ/2); - - return NOTIFY_OK; -} - -#if defined(CONFIG_PM) -static int ab8500_charger_resume(struct platform_device *pdev) -{ - int ret; - struct ab8500_charger *di = platform_get_drvdata(pdev); - - /* - * For ABB revision 1.0 and 1.1 there is a bug in the watchdog - * logic. That means we have to continously kick the charger - * watchdog even when no charger is connected. This is only - * valid once the AC charger has been enabled. This is - * a bug that is not handled by the algorithm and the - * watchdog have to be kicked by the charger driver - * when the AC charger is disabled - */ - if (di->ac_conn && is_ab8500_1p1_or_earlier(di->parent)) { - ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER, - AB8500_CHARG_WD_CTRL, CHARG_WD_KICK); - if (ret) - dev_err(di->dev, "Failed to kick WD!\n"); - - /* If not already pending start a new timer */ - queue_delayed_work(di->charger_wq, &di->kick_wd_work, - round_jiffies(WD_KICK_INTERVAL)); - } - - /* If we still have a HW failure, schedule a new check */ - if (di->flags.mainextchnotok || di->flags.vbus_ovv) { - queue_delayed_work(di->charger_wq, - &di->check_hw_failure_work, 0); - } - - if (di->flags.vbus_drop_end) - queue_delayed_work(di->charger_wq, &di->vbus_drop_end_work, 0); - - return 0; -} - -static int ab8500_charger_suspend(struct platform_device *pdev, - pm_message_t state) -{ - struct ab8500_charger *di = platform_get_drvdata(pdev); - - /* Cancel any pending jobs */ - cancel_delayed_work(&di->check_hw_failure_work); - cancel_delayed_work(&di->vbus_drop_end_work); - - flush_delayed_work(&di->attach_work); - flush_delayed_work(&di->usb_charger_attached_work); - flush_delayed_work(&di->ac_charger_attached_work); - flush_delayed_work(&di->check_usbchgnotok_work); - flush_delayed_work(&di->check_vbat_work); - flush_delayed_work(&di->kick_wd_work); - - flush_work(&di->usb_link_status_work); - flush_work(&di->ac_work); - flush_work(&di->detect_usb_type_work); - - if (atomic_read(&di->current_stepping_sessions)) - return -EAGAIN; - - return 0; -} -#else -#define ab8500_charger_suspend NULL -#define ab8500_charger_resume NULL -#endif - -static struct notifier_block charger_nb = { - .notifier_call = ab8500_external_charger_prepare, -}; - -static int ab8500_charger_remove(struct platform_device *pdev) -{ - struct ab8500_charger *di = platform_get_drvdata(pdev); - int i, irq, ret; - - /* Disable AC charging */ - ab8500_charger_ac_en(&di->ac_chg, false, 0, 0); - - /* Disable USB charging */ - ab8500_charger_usb_en(&di->usb_chg, false, 0, 0); - - /* Disable interrupts */ - for (i = 0; i < ARRAY_SIZE(ab8500_charger_irq); i++) { - irq = platform_get_irq_byname(pdev, ab8500_charger_irq[i].name); - free_irq(irq, di); - } - - /* Backup battery voltage and current disable */ - ret = abx500_mask_and_set_register_interruptible(di->dev, - AB8500_RTC, AB8500_RTC_CTRL_REG, RTC_BUP_CH_ENA, 0); - if (ret < 0) - dev_err(di->dev, "%s mask and set failed\n", __func__); - - usb_unregister_notifier(di->usb_phy, &di->nb); - usb_put_phy(di->usb_phy); - - /* Delete the work queue */ - destroy_workqueue(di->charger_wq); - - /* Unregister external charger enable notifier */ - if (!di->ac_chg.enabled) - blocking_notifier_chain_unregister( - &charger_notifier_list, &charger_nb); - - flush_scheduled_work(); - if (di->usb_chg.enabled) - power_supply_unregister(di->usb_chg.psy); - - if (di->ac_chg.enabled && !di->ac_chg.external) - power_supply_unregister(di->ac_chg.psy); - - return 0; -} - -static char *supply_interface[] = { - "ab8500_chargalg", - "ab8500_fg", - "ab8500_btemp", -}; - -static const struct power_supply_desc ab8500_ac_chg_desc = { - .name = "ab8500_ac", - .type = POWER_SUPPLY_TYPE_MAINS, - .properties = ab8500_charger_ac_props, - .num_properties = ARRAY_SIZE(ab8500_charger_ac_props), - .get_property = ab8500_charger_ac_get_property, -}; - -static const struct power_supply_desc ab8500_usb_chg_desc = { - .name = "ab8500_usb", - .type = POWER_SUPPLY_TYPE_USB, - .properties = ab8500_charger_usb_props, - .num_properties = ARRAY_SIZE(ab8500_charger_usb_props), - .get_property = ab8500_charger_usb_get_property, -}; - -static int ab8500_charger_probe(struct platform_device *pdev) -{ - struct device_node *np = pdev->dev.of_node; - struct abx500_bm_data *plat = pdev->dev.platform_data; - struct power_supply_config ac_psy_cfg = {}, usb_psy_cfg = {}; - struct ab8500_charger *di; - int irq, i, charger_status, ret = 0, ch_stat; - - di = devm_kzalloc(&pdev->dev, sizeof(*di), GFP_KERNEL); - if (!di) { - dev_err(&pdev->dev, "%s no mem for ab8500_charger\n", __func__); - return -ENOMEM; - } - - if (!plat) { - dev_err(&pdev->dev, "no battery management data supplied\n"); - return -EINVAL; - } - di->bm = plat; - - if (np) { - ret = ab8500_bm_of_probe(&pdev->dev, np, di->bm); - if (ret) { - dev_err(&pdev->dev, "failed to get battery information\n"); - return ret; - } - di->autopower_cfg = of_property_read_bool(np, "autopower_cfg"); - } else - di->autopower_cfg = false; - - /* get parent data */ - di->dev = &pdev->dev; - di->parent = dev_get_drvdata(pdev->dev.parent); - di->gpadc = ab8500_gpadc_get("ab8500-gpadc.0"); - - /* initialize lock */ - spin_lock_init(&di->usb_state.usb_lock); - mutex_init(&di->usb_ipt_crnt_lock); - - di->autopower = false; - di->invalid_charger_detect_state = 0; - - /* AC and USB supply config */ - ac_psy_cfg.supplied_to = supply_interface; - ac_psy_cfg.num_supplicants = ARRAY_SIZE(supply_interface); - ac_psy_cfg.drv_data = &di->ac_chg; - usb_psy_cfg.supplied_to = supply_interface; - usb_psy_cfg.num_supplicants = ARRAY_SIZE(supply_interface); - usb_psy_cfg.drv_data = &di->usb_chg; - - /* AC supply */ - /* ux500_charger sub-class */ - di->ac_chg.ops.enable = &ab8500_charger_ac_en; - di->ac_chg.ops.check_enable = &ab8500_charger_ac_check_enable; - di->ac_chg.ops.kick_wd = &ab8500_charger_watchdog_kick; - di->ac_chg.ops.update_curr = &ab8500_charger_update_charger_current; - di->ac_chg.max_out_volt = ab8500_charger_voltage_map[ - ARRAY_SIZE(ab8500_charger_voltage_map) - 1]; - di->ac_chg.max_out_curr = - di->bm->chg_output_curr[di->bm->n_chg_out_curr - 1]; - di->ac_chg.wdt_refresh = CHG_WD_INTERVAL; - di->ac_chg.enabled = di->bm->ac_enabled; - di->ac_chg.external = false; - - /*notifier for external charger enabling*/ - if (!di->ac_chg.enabled) - blocking_notifier_chain_register( - &charger_notifier_list, &charger_nb); - - /* USB supply */ - /* ux500_charger sub-class */ - di->usb_chg.ops.enable = &ab8500_charger_usb_en; - di->usb_chg.ops.check_enable = &ab8500_charger_usb_check_enable; - di->usb_chg.ops.kick_wd = &ab8500_charger_watchdog_kick; - di->usb_chg.ops.update_curr = &ab8500_charger_update_charger_current; - di->usb_chg.ops.pp_enable = &ab8540_charger_power_path_enable; - di->usb_chg.ops.pre_chg_enable = &ab8540_charger_usb_pre_chg_enable; - di->usb_chg.max_out_volt = ab8500_charger_voltage_map[ - ARRAY_SIZE(ab8500_charger_voltage_map) - 1]; - di->usb_chg.max_out_curr = - di->bm->chg_output_curr[di->bm->n_chg_out_curr - 1]; - di->usb_chg.wdt_refresh = CHG_WD_INTERVAL; - di->usb_chg.enabled = di->bm->usb_enabled; - di->usb_chg.external = false; - di->usb_chg.power_path = di->bm->usb_power_path; - di->usb_state.usb_current = -1; - - /* Create a work queue for the charger */ - di->charger_wq = - create_singlethread_workqueue("ab8500_charger_wq"); - if (di->charger_wq == NULL) { - dev_err(di->dev, "failed to create work queue\n"); - return -ENOMEM; - } - - mutex_init(&di->charger_attached_mutex); - - /* Init work for HW failure check */ - INIT_DEFERRABLE_WORK(&di->check_hw_failure_work, - ab8500_charger_check_hw_failure_work); - INIT_DEFERRABLE_WORK(&di->check_usbchgnotok_work, - ab8500_charger_check_usbchargernotok_work); - - INIT_DELAYED_WORK(&di->ac_charger_attached_work, - ab8500_charger_ac_attached_work); - INIT_DELAYED_WORK(&di->usb_charger_attached_work, - ab8500_charger_usb_attached_work); - - /* - * For ABB revision 1.0 and 1.1 there is a bug in the watchdog - * logic. That means we have to continously kick the charger - * watchdog even when no charger is connected. This is only - * valid once the AC charger has been enabled. This is - * a bug that is not handled by the algorithm and the - * watchdog have to be kicked by the charger driver - * when the AC charger is disabled - */ - INIT_DEFERRABLE_WORK(&di->kick_wd_work, - ab8500_charger_kick_watchdog_work); - - INIT_DEFERRABLE_WORK(&di->check_vbat_work, - ab8500_charger_check_vbat_work); - - INIT_DELAYED_WORK(&di->attach_work, - ab8500_charger_usb_link_attach_work); - - INIT_DELAYED_WORK(&di->usb_state_changed_work, - ab8500_charger_usb_state_changed_work); - - INIT_DELAYED_WORK(&di->vbus_drop_end_work, - ab8500_charger_vbus_drop_end_work); - - /* Init work for charger detection */ - INIT_WORK(&di->usb_link_status_work, - ab8500_charger_usb_link_status_work); - INIT_WORK(&di->ac_work, ab8500_charger_ac_work); - INIT_WORK(&di->detect_usb_type_work, - ab8500_charger_detect_usb_type_work); - - /* Init work for checking HW status */ - INIT_WORK(&di->check_main_thermal_prot_work, - ab8500_charger_check_main_thermal_prot_work); - INIT_WORK(&di->check_usb_thermal_prot_work, - ab8500_charger_check_usb_thermal_prot_work); - - /* - * VDD ADC supply needs to be enabled from this driver when there - * is a charger connected to avoid erroneous BTEMP_HIGH/LOW - * interrupts during charging - */ - di->regu = devm_regulator_get(di->dev, "vddadc"); - if (IS_ERR(di->regu)) { - ret = PTR_ERR(di->regu); - dev_err(di->dev, "failed to get vddadc regulator\n"); - goto free_charger_wq; - } - - - /* Initialize OVV, and other registers */ - ret = ab8500_charger_init_hw_registers(di); - if (ret) { - dev_err(di->dev, "failed to initialize ABB registers\n"); - goto free_charger_wq; - } - - /* Register AC charger class */ - if (di->ac_chg.enabled) { - di->ac_chg.psy = power_supply_register(di->dev, - &ab8500_ac_chg_desc, - &ac_psy_cfg); - if (IS_ERR(di->ac_chg.psy)) { - dev_err(di->dev, "failed to register AC charger\n"); - ret = PTR_ERR(di->ac_chg.psy); - goto free_charger_wq; - } - } - - /* Register USB charger class */ - if (di->usb_chg.enabled) { - di->usb_chg.psy = power_supply_register(di->dev, - &ab8500_usb_chg_desc, - &usb_psy_cfg); - if (IS_ERR(di->usb_chg.psy)) { - dev_err(di->dev, "failed to register USB charger\n"); - ret = PTR_ERR(di->usb_chg.psy); - goto free_ac; - } - } - - di->usb_phy = usb_get_phy(USB_PHY_TYPE_USB2); - if (IS_ERR_OR_NULL(di->usb_phy)) { - dev_err(di->dev, "failed to get usb transceiver\n"); - ret = -EINVAL; - goto free_usb; - } - di->nb.notifier_call = ab8500_charger_usb_notifier_call; - ret = usb_register_notifier(di->usb_phy, &di->nb); - if (ret) { - dev_err(di->dev, "failed to register usb notifier\n"); - goto put_usb_phy; - } - - /* Identify the connected charger types during startup */ - charger_status = ab8500_charger_detect_chargers(di, true); - if (charger_status & AC_PW_CONN) { - di->ac.charger_connected = 1; - di->ac_conn = true; - ab8500_power_supply_changed(di, di->ac_chg.psy); - sysfs_notify(&di->ac_chg.psy->dev.kobj, NULL, "present"); - } - - if (charger_status & USB_PW_CONN) { - di->vbus_detected = true; - di->vbus_detected_start = true; - queue_work(di->charger_wq, - &di->detect_usb_type_work); - } - - /* Register interrupts */ - for (i = 0; i < ARRAY_SIZE(ab8500_charger_irq); i++) { - irq = platform_get_irq_byname(pdev, ab8500_charger_irq[i].name); - ret = request_threaded_irq(irq, NULL, ab8500_charger_irq[i].isr, - IRQF_SHARED | IRQF_NO_SUSPEND, - ab8500_charger_irq[i].name, di); - - if (ret != 0) { - dev_err(di->dev, "failed to request %s IRQ %d: %d\n" - , ab8500_charger_irq[i].name, irq, ret); - goto free_irq; - } - dev_dbg(di->dev, "Requested %s IRQ %d: %d\n", - ab8500_charger_irq[i].name, irq, ret); - } - - platform_set_drvdata(pdev, di); - - mutex_lock(&di->charger_attached_mutex); - - ch_stat = ab8500_charger_detect_chargers(di, false); - - if ((ch_stat & AC_PW_CONN) == AC_PW_CONN) { - if (is_ab8500(di->parent)) - queue_delayed_work(di->charger_wq, - &di->ac_charger_attached_work, - HZ); - } - if ((ch_stat & USB_PW_CONN) == USB_PW_CONN) { - if (is_ab8500(di->parent)) - queue_delayed_work(di->charger_wq, - &di->usb_charger_attached_work, - HZ); - } - - mutex_unlock(&di->charger_attached_mutex); - - return ret; - -free_irq: - usb_unregister_notifier(di->usb_phy, &di->nb); - - /* We also have to free all successfully registered irqs */ - for (i = i - 1; i >= 0; i--) { - irq = platform_get_irq_byname(pdev, ab8500_charger_irq[i].name); - free_irq(irq, di); - } -put_usb_phy: - usb_put_phy(di->usb_phy); -free_usb: - if (di->usb_chg.enabled) - power_supply_unregister(di->usb_chg.psy); -free_ac: - if (di->ac_chg.enabled) - power_supply_unregister(di->ac_chg.psy); -free_charger_wq: - destroy_workqueue(di->charger_wq); - return ret; -} - -static const struct of_device_id ab8500_charger_match[] = { - { .compatible = "stericsson,ab8500-charger", }, - { }, -}; - -static struct platform_driver ab8500_charger_driver = { - .probe = ab8500_charger_probe, - .remove = ab8500_charger_remove, - .suspend = ab8500_charger_suspend, - .resume = ab8500_charger_resume, - .driver = { - .name = "ab8500-charger", - .of_match_table = ab8500_charger_match, - }, -}; - -static int __init ab8500_charger_init(void) -{ - return platform_driver_register(&ab8500_charger_driver); -} - -static void __exit ab8500_charger_exit(void) -{ - platform_driver_unregister(&ab8500_charger_driver); -} - -subsys_initcall_sync(ab8500_charger_init); -module_exit(ab8500_charger_exit); - -MODULE_LICENSE("GPL v2"); -MODULE_AUTHOR("Johan Palsson, Karl Komierowski, Arun R Murthy"); -MODULE_ALIAS("platform:ab8500-charger"); -MODULE_DESCRIPTION("AB8500 charger management driver"); diff --git a/drivers/power/ab8500_fg.c b/drivers/power/ab8500_fg.c deleted file mode 100644 index 5a36cf88578a..000000000000 --- a/drivers/power/ab8500_fg.c +++ /dev/null @@ -1,3272 +0,0 @@ -/* - * Copyright (C) ST-Ericsson AB 2012 - * - * Main and Back-up battery management driver. - * - * Note: Backup battery management is required in case of Li-Ion battery and not - * for capacitive battery. HREF boards have capacitive battery and hence backup - * battery management is not used and the supported code is available in this - * driver. - * - * License Terms: GNU General Public License v2 - * Author: - * Johan Palsson - * Karl Komierowski - * Arun R Murthy - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define MILLI_TO_MICRO 1000 -#define FG_LSB_IN_MA 1627 -#define QLSB_NANO_AMP_HOURS_X10 1071 -#define INS_CURR_TIMEOUT (3 * HZ) - -#define SEC_TO_SAMPLE(S) (S * 4) - -#define NBR_AVG_SAMPLES 20 - -#define LOW_BAT_CHECK_INTERVAL (HZ / 16) /* 62.5 ms */ - -#define VALID_CAPACITY_SEC (45 * 60) /* 45 minutes */ -#define BATT_OK_MIN 2360 /* mV */ -#define BATT_OK_INCREMENT 50 /* mV */ -#define BATT_OK_MAX_NR_INCREMENTS 0xE - -/* FG constants */ -#define BATT_OVV 0x01 - -#define interpolate(x, x1, y1, x2, y2) \ - ((y1) + ((((y2) - (y1)) * ((x) - (x1))) / ((x2) - (x1)))); - -/** - * struct ab8500_fg_interrupts - ab8500 fg interupts - * @name: name of the interrupt - * @isr function pointer to the isr - */ -struct ab8500_fg_interrupts { - char *name; - irqreturn_t (*isr)(int irq, void *data); -}; - -enum ab8500_fg_discharge_state { - AB8500_FG_DISCHARGE_INIT, - AB8500_FG_DISCHARGE_INITMEASURING, - AB8500_FG_DISCHARGE_INIT_RECOVERY, - AB8500_FG_DISCHARGE_RECOVERY, - AB8500_FG_DISCHARGE_READOUT_INIT, - AB8500_FG_DISCHARGE_READOUT, - AB8500_FG_DISCHARGE_WAKEUP, -}; - -static char *discharge_state[] = { - "DISCHARGE_INIT", - "DISCHARGE_INITMEASURING", - "DISCHARGE_INIT_RECOVERY", - "DISCHARGE_RECOVERY", - "DISCHARGE_READOUT_INIT", - "DISCHARGE_READOUT", - "DISCHARGE_WAKEUP", -}; - -enum ab8500_fg_charge_state { - AB8500_FG_CHARGE_INIT, - AB8500_FG_CHARGE_READOUT, -}; - -static char *charge_state[] = { - "CHARGE_INIT", - "CHARGE_READOUT", -}; - -enum ab8500_fg_calibration_state { - AB8500_FG_CALIB_INIT, - AB8500_FG_CALIB_WAIT, - AB8500_FG_CALIB_END, -}; - -struct ab8500_fg_avg_cap { - int avg; - int samples[NBR_AVG_SAMPLES]; - time64_t time_stamps[NBR_AVG_SAMPLES]; - int pos; - int nbr_samples; - int sum; -}; - -struct ab8500_fg_cap_scaling { - bool enable; - int cap_to_scale[2]; - int disable_cap_level; - int scaled_cap; -}; - -struct ab8500_fg_battery_capacity { - int max_mah_design; - int max_mah; - int mah; - int permille; - int level; - int prev_mah; - int prev_percent; - int prev_level; - int user_mah; - struct ab8500_fg_cap_scaling cap_scale; -}; - -struct ab8500_fg_flags { - bool fg_enabled; - bool conv_done; - bool charging; - bool fully_charged; - bool force_full; - bool low_bat_delay; - bool low_bat; - bool bat_ovv; - bool batt_unknown; - bool calibrate; - bool user_cap; - bool batt_id_received; -}; - -struct inst_curr_result_list { - struct list_head list; - int *result; -}; - -/** - * struct ab8500_fg - ab8500 FG device information - * @dev: Pointer to the structure device - * @node: a list of AB8500 FGs, hence prepared for reentrance - * @irq holds the CCEOC interrupt number - * @vbat: Battery voltage in mV - * @vbat_nom: Nominal battery voltage in mV - * @inst_curr: Instantenous battery current in mA - * @avg_curr: Average battery current in mA - * @bat_temp battery temperature - * @fg_samples: Number of samples used in the FG accumulation - * @accu_charge: Accumulated charge from the last conversion - * @recovery_cnt: Counter for recovery mode - * @high_curr_cnt: Counter for high current mode - * @init_cnt: Counter for init mode - * @low_bat_cnt Counter for number of consecutive low battery measures - * @nbr_cceoc_irq_cnt Counter for number of CCEOC irqs received since enabled - * @recovery_needed: Indicate if recovery is needed - * @high_curr_mode: Indicate if we're in high current mode - * @init_capacity: Indicate if initial capacity measuring should be done - * @turn_off_fg: True if fg was off before current measurement - * @calib_state State during offset calibration - * @discharge_state: Current discharge state - * @charge_state: Current charge state - * @ab8500_fg_started Completion struct used for the instant current start - * @ab8500_fg_complete Completion struct used for the instant current reading - * @flags: Structure for information about events triggered - * @bat_cap: Structure for battery capacity specific parameters - * @avg_cap: Average capacity filter - * @parent: Pointer to the struct ab8500 - * @gpadc: Pointer to the struct gpadc - * @bm: Platform specific battery management information - * @fg_psy: Structure that holds the FG specific battery properties - * @fg_wq: Work queue for running the FG algorithm - * @fg_periodic_work: Work to run the FG algorithm periodically - * @fg_low_bat_work: Work to check low bat condition - * @fg_reinit_work Work used to reset and reinitialise the FG algorithm - * @fg_work: Work to run the FG algorithm instantly - * @fg_acc_cur_work: Work to read the FG accumulator - * @fg_check_hw_failure_work: Work for checking HW state - * @cc_lock: Mutex for locking the CC - * @fg_kobject: Structure of type kobject - */ -struct ab8500_fg { - struct device *dev; - struct list_head node; - int irq; - int vbat; - int vbat_nom; - int inst_curr; - int avg_curr; - int bat_temp; - int fg_samples; - int accu_charge; - int recovery_cnt; - int high_curr_cnt; - int init_cnt; - int low_bat_cnt; - int nbr_cceoc_irq_cnt; - bool recovery_needed; - bool high_curr_mode; - bool init_capacity; - bool turn_off_fg; - enum ab8500_fg_calibration_state calib_state; - enum ab8500_fg_discharge_state discharge_state; - enum ab8500_fg_charge_state charge_state; - struct completion ab8500_fg_started; - struct completion ab8500_fg_complete; - struct ab8500_fg_flags flags; - struct ab8500_fg_battery_capacity bat_cap; - struct ab8500_fg_avg_cap avg_cap; - struct ab8500 *parent; - struct ab8500_gpadc *gpadc; - struct abx500_bm_data *bm; - struct power_supply *fg_psy; - struct workqueue_struct *fg_wq; - struct delayed_work fg_periodic_work; - struct delayed_work fg_low_bat_work; - struct delayed_work fg_reinit_work; - struct work_struct fg_work; - struct work_struct fg_acc_cur_work; - struct delayed_work fg_check_hw_failure_work; - struct mutex cc_lock; - struct kobject fg_kobject; -}; -static LIST_HEAD(ab8500_fg_list); - -/** - * ab8500_fg_get() - returns a reference to the primary AB8500 fuel gauge - * (i.e. the first fuel gauge in the instance list) - */ -struct ab8500_fg *ab8500_fg_get(void) -{ - struct ab8500_fg *fg; - - if (list_empty(&ab8500_fg_list)) - return NULL; - - fg = list_first_entry(&ab8500_fg_list, struct ab8500_fg, node); - return fg; -} - -/* Main battery properties */ -static enum power_supply_property ab8500_fg_props[] = { - POWER_SUPPLY_PROP_VOLTAGE_NOW, - POWER_SUPPLY_PROP_CURRENT_NOW, - POWER_SUPPLY_PROP_CURRENT_AVG, - POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN, - POWER_SUPPLY_PROP_ENERGY_FULL, - POWER_SUPPLY_PROP_ENERGY_NOW, - POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, - POWER_SUPPLY_PROP_CHARGE_FULL, - POWER_SUPPLY_PROP_CHARGE_NOW, - POWER_SUPPLY_PROP_CAPACITY, - POWER_SUPPLY_PROP_CAPACITY_LEVEL, -}; - -/* - * This array maps the raw hex value to lowbat voltage used by the AB8500 - * Values taken from the UM0836 - */ -static int ab8500_fg_lowbat_voltage_map[] = { - 2300 , - 2325 , - 2350 , - 2375 , - 2400 , - 2425 , - 2450 , - 2475 , - 2500 , - 2525 , - 2550 , - 2575 , - 2600 , - 2625 , - 2650 , - 2675 , - 2700 , - 2725 , - 2750 , - 2775 , - 2800 , - 2825 , - 2850 , - 2875 , - 2900 , - 2925 , - 2950 , - 2975 , - 3000 , - 3025 , - 3050 , - 3075 , - 3100 , - 3125 , - 3150 , - 3175 , - 3200 , - 3225 , - 3250 , - 3275 , - 3300 , - 3325 , - 3350 , - 3375 , - 3400 , - 3425 , - 3450 , - 3475 , - 3500 , - 3525 , - 3550 , - 3575 , - 3600 , - 3625 , - 3650 , - 3675 , - 3700 , - 3725 , - 3750 , - 3775 , - 3800 , - 3825 , - 3850 , - 3850 , -}; - -static u8 ab8500_volt_to_regval(int voltage) -{ - int i; - - if (voltage < ab8500_fg_lowbat_voltage_map[0]) - return 0; - - for (i = 0; i < ARRAY_SIZE(ab8500_fg_lowbat_voltage_map); i++) { - if (voltage < ab8500_fg_lowbat_voltage_map[i]) - return (u8) i - 1; - } - - /* If not captured above, return index of last element */ - return (u8) ARRAY_SIZE(ab8500_fg_lowbat_voltage_map) - 1; -} - -/** - * ab8500_fg_is_low_curr() - Low or high current mode - * @di: pointer to the ab8500_fg structure - * @curr: the current to base or our decision on - * - * Low current mode if the current consumption is below a certain threshold - */ -static int ab8500_fg_is_low_curr(struct ab8500_fg *di, int curr) -{ - /* - * We want to know if we're in low current mode - */ - if (curr > -di->bm->fg_params->high_curr_threshold) - return true; - else - return false; -} - -/** - * ab8500_fg_add_cap_sample() - Add capacity to average filter - * @di: pointer to the ab8500_fg structure - * @sample: the capacity in mAh to add to the filter - * - * A capacity is added to the filter and a new mean capacity is calculated and - * returned - */ -static int ab8500_fg_add_cap_sample(struct ab8500_fg *di, int sample) -{ - struct timespec64 ts64; - struct ab8500_fg_avg_cap *avg = &di->avg_cap; - - getnstimeofday64(&ts64); - - do { - avg->sum += sample - avg->samples[avg->pos]; - avg->samples[avg->pos] = sample; - avg->time_stamps[avg->pos] = ts64.tv_sec; - avg->pos++; - - if (avg->pos == NBR_AVG_SAMPLES) - avg->pos = 0; - - if (avg->nbr_samples < NBR_AVG_SAMPLES) - avg->nbr_samples++; - - /* - * Check the time stamp for each sample. If too old, - * replace with latest sample - */ - } while (ts64.tv_sec - VALID_CAPACITY_SEC > avg->time_stamps[avg->pos]); - - avg->avg = avg->sum / avg->nbr_samples; - - return avg->avg; -} - -/** - * ab8500_fg_clear_cap_samples() - Clear average filter - * @di: pointer to the ab8500_fg structure - * - * The capacity filter is is reset to zero. - */ -static void ab8500_fg_clear_cap_samples(struct ab8500_fg *di) -{ - int i; - struct ab8500_fg_avg_cap *avg = &di->avg_cap; - - avg->pos = 0; - avg->nbr_samples = 0; - avg->sum = 0; - avg->avg = 0; - - for (i = 0; i < NBR_AVG_SAMPLES; i++) { - avg->samples[i] = 0; - avg->time_stamps[i] = 0; - } -} - -/** - * ab8500_fg_fill_cap_sample() - Fill average filter - * @di: pointer to the ab8500_fg structure - * @sample: the capacity in mAh to fill the filter with - * - * The capacity filter is filled with a capacity in mAh - */ -static void ab8500_fg_fill_cap_sample(struct ab8500_fg *di, int sample) -{ - int i; - struct timespec64 ts64; - struct ab8500_fg_avg_cap *avg = &di->avg_cap; - - getnstimeofday64(&ts64); - - for (i = 0; i < NBR_AVG_SAMPLES; i++) { - avg->samples[i] = sample; - avg->time_stamps[i] = ts64.tv_sec; - } - - avg->pos = 0; - avg->nbr_samples = NBR_AVG_SAMPLES; - avg->sum = sample * NBR_AVG_SAMPLES; - avg->avg = sample; -} - -/** - * ab8500_fg_coulomb_counter() - enable coulomb counter - * @di: pointer to the ab8500_fg structure - * @enable: enable/disable - * - * Enable/Disable coulomb counter. - * On failure returns negative value. - */ -static int ab8500_fg_coulomb_counter(struct ab8500_fg *di, bool enable) -{ - int ret = 0; - mutex_lock(&di->cc_lock); - if (enable) { - /* To be able to reprogram the number of samples, we have to - * first stop the CC and then enable it again */ - ret = abx500_set_register_interruptible(di->dev, AB8500_RTC, - AB8500_RTC_CC_CONF_REG, 0x00); - if (ret) - goto cc_err; - - /* Program the samples */ - ret = abx500_set_register_interruptible(di->dev, - AB8500_GAS_GAUGE, AB8500_GASG_CC_NCOV_ACCU, - di->fg_samples); - if (ret) - goto cc_err; - - /* Start the CC */ - ret = abx500_set_register_interruptible(di->dev, AB8500_RTC, - AB8500_RTC_CC_CONF_REG, - (CC_DEEP_SLEEP_ENA | CC_PWR_UP_ENA)); - if (ret) - goto cc_err; - - di->flags.fg_enabled = true; - } else { - /* Clear any pending read requests */ - ret = abx500_mask_and_set_register_interruptible(di->dev, - AB8500_GAS_GAUGE, AB8500_GASG_CC_CTRL_REG, - (RESET_ACCU | READ_REQ), 0); - if (ret) - goto cc_err; - - ret = abx500_set_register_interruptible(di->dev, - AB8500_GAS_GAUGE, AB8500_GASG_CC_NCOV_ACCU_CTRL, 0); - if (ret) - goto cc_err; - - /* Stop the CC */ - ret = abx500_set_register_interruptible(di->dev, AB8500_RTC, - AB8500_RTC_CC_CONF_REG, 0); - if (ret) - goto cc_err; - - di->flags.fg_enabled = false; - - } - dev_dbg(di->dev, " CC enabled: %d Samples: %d\n", - enable, di->fg_samples); - - mutex_unlock(&di->cc_lock); - - return ret; -cc_err: - dev_err(di->dev, "%s Enabling coulomb counter failed\n", __func__); - mutex_unlock(&di->cc_lock); - return ret; -} - -/** - * ab8500_fg_inst_curr_start() - start battery instantaneous current - * @di: pointer to the ab8500_fg structure - * - * Returns 0 or error code - * Note: This is part "one" and has to be called before - * ab8500_fg_inst_curr_finalize() - */ -int ab8500_fg_inst_curr_start(struct ab8500_fg *di) -{ - u8 reg_val; - int ret; - - mutex_lock(&di->cc_lock); - - di->nbr_cceoc_irq_cnt = 0; - ret = abx500_get_register_interruptible(di->dev, AB8500_RTC, - AB8500_RTC_CC_CONF_REG, ®_val); - if (ret < 0) - goto fail; - - if (!(reg_val & CC_PWR_UP_ENA)) { - dev_dbg(di->dev, "%s Enable FG\n", __func__); - di->turn_off_fg = true; - - /* Program the samples */ - ret = abx500_set_register_interruptible(di->dev, - AB8500_GAS_GAUGE, AB8500_GASG_CC_NCOV_ACCU, - SEC_TO_SAMPLE(10)); - if (ret) - goto fail; - - /* Start the CC */ - ret = abx500_set_register_interruptible(di->dev, AB8500_RTC, - AB8500_RTC_CC_CONF_REG, - (CC_DEEP_SLEEP_ENA | CC_PWR_UP_ENA)); - if (ret) - goto fail; - } else { - di->turn_off_fg = false; - } - - /* Return and WFI */ - reinit_completion(&di->ab8500_fg_started); - reinit_completion(&di->ab8500_fg_complete); - enable_irq(di->irq); - - /* Note: cc_lock is still locked */ - return 0; -fail: - mutex_unlock(&di->cc_lock); - return ret; -} - -/** - * ab8500_fg_inst_curr_started() - check if fg conversion has started - * @di: pointer to the ab8500_fg structure - * - * Returns 1 if conversion started, 0 if still waiting - */ -int ab8500_fg_inst_curr_started(struct ab8500_fg *di) -{ - return completion_done(&di->ab8500_fg_started); -} - -/** - * ab8500_fg_inst_curr_done() - check if fg conversion is done - * @di: pointer to the ab8500_fg structure - * - * Returns 1 if conversion done, 0 if still waiting - */ -int ab8500_fg_inst_curr_done(struct ab8500_fg *di) -{ - return completion_done(&di->ab8500_fg_complete); -} - -/** - * ab8500_fg_inst_curr_finalize() - battery instantaneous current - * @di: pointer to the ab8500_fg structure - * @res: battery instantenous current(on success) - * - * Returns 0 or an error code - * Note: This is part "two" and has to be called at earliest 250 ms - * after ab8500_fg_inst_curr_start() - */ -int ab8500_fg_inst_curr_finalize(struct ab8500_fg *di, int *res) -{ - u8 low, high; - int val; - int ret; - unsigned long timeout; - - if (!completion_done(&di->ab8500_fg_complete)) { - timeout = wait_for_completion_timeout( - &di->ab8500_fg_complete, - INS_CURR_TIMEOUT); - dev_dbg(di->dev, "Finalize time: %d ms\n", - jiffies_to_msecs(INS_CURR_TIMEOUT - timeout)); - if (!timeout) { - ret = -ETIME; - disable_irq(di->irq); - di->nbr_cceoc_irq_cnt = 0; - dev_err(di->dev, "completion timed out [%d]\n", - __LINE__); - goto fail; - } - } - - disable_irq(di->irq); - di->nbr_cceoc_irq_cnt = 0; - - ret = abx500_mask_and_set_register_interruptible(di->dev, - AB8500_GAS_GAUGE, AB8500_GASG_CC_CTRL_REG, - READ_REQ, READ_REQ); - - /* 100uS between read request and read is needed */ - usleep_range(100, 100); - - /* Read CC Sample conversion value Low and high */ - ret = abx500_get_register_interruptible(di->dev, AB8500_GAS_GAUGE, - AB8500_GASG_CC_SMPL_CNVL_REG, &low); - if (ret < 0) - goto fail; - - ret = abx500_get_register_interruptible(di->dev, AB8500_GAS_GAUGE, - AB8500_GASG_CC_SMPL_CNVH_REG, &high); - if (ret < 0) - goto fail; - - /* - * negative value for Discharging - * convert 2's compliment into decimal - */ - if (high & 0x10) - val = (low | (high << 8) | 0xFFFFE000); - else - val = (low | (high << 8)); - - /* - * Convert to unit value in mA - * Full scale input voltage is - * 63.160mV => LSB = 63.160mV/(4096*res) = 1.542mA - * Given a 250ms conversion cycle time the LSB corresponds - * to 107.1 nAh. Convert to current by dividing by the conversion - * time in hours (250ms = 1 / (3600 * 4)h) - * 107.1nAh assumes 10mOhm, but fg_res is in 0.1mOhm - */ - val = (val * QLSB_NANO_AMP_HOURS_X10 * 36 * 4) / - (1000 * di->bm->fg_res); - - if (di->turn_off_fg) { - dev_dbg(di->dev, "%s Disable FG\n", __func__); - - /* Clear any pending read requests */ - ret = abx500_set_register_interruptible(di->dev, - AB8500_GAS_GAUGE, AB8500_GASG_CC_CTRL_REG, 0); - if (ret) - goto fail; - - /* Stop the CC */ - ret = abx500_set_register_interruptible(di->dev, AB8500_RTC, - AB8500_RTC_CC_CONF_REG, 0); - if (ret) - goto fail; - } - mutex_unlock(&di->cc_lock); - (*res) = val; - - return 0; -fail: - mutex_unlock(&di->cc_lock); - return ret; -} - -/** - * ab8500_fg_inst_curr_blocking() - battery instantaneous current - * @di: pointer to the ab8500_fg structure - * @res: battery instantenous current(on success) - * - * Returns 0 else error code - */ -int ab8500_fg_inst_curr_blocking(struct ab8500_fg *di) -{ - int ret; - unsigned long timeout; - int res = 0; - - ret = ab8500_fg_inst_curr_start(di); - if (ret) { - dev_err(di->dev, "Failed to initialize fg_inst\n"); - return 0; - } - - /* Wait for CC to actually start */ - if (!completion_done(&di->ab8500_fg_started)) { - timeout = wait_for_completion_timeout( - &di->ab8500_fg_started, - INS_CURR_TIMEOUT); - dev_dbg(di->dev, "Start time: %d ms\n", - jiffies_to_msecs(INS_CURR_TIMEOUT - timeout)); - if (!timeout) { - ret = -ETIME; - dev_err(di->dev, "completion timed out [%d]\n", - __LINE__); - goto fail; - } - } - - ret = ab8500_fg_inst_curr_finalize(di, &res); - if (ret) { - dev_err(di->dev, "Failed to finalize fg_inst\n"); - return 0; - } - - dev_dbg(di->dev, "%s instant current: %d", __func__, res); - return res; -fail: - disable_irq(di->irq); - mutex_unlock(&di->cc_lock); - return ret; -} - -/** - * ab8500_fg_acc_cur_work() - average battery current - * @work: pointer to the work_struct structure - * - * Updated the average battery current obtained from the - * coulomb counter. - */ -static void ab8500_fg_acc_cur_work(struct work_struct *work) -{ - int val; - int ret; - u8 low, med, high; - - struct ab8500_fg *di = container_of(work, - struct ab8500_fg, fg_acc_cur_work); - - mutex_lock(&di->cc_lock); - ret = abx500_set_register_interruptible(di->dev, AB8500_GAS_GAUGE, - AB8500_GASG_CC_NCOV_ACCU_CTRL, RD_NCONV_ACCU_REQ); - if (ret) - goto exit; - - ret = abx500_get_register_interruptible(di->dev, AB8500_GAS_GAUGE, - AB8500_GASG_CC_NCOV_ACCU_LOW, &low); - if (ret < 0) - goto exit; - - ret = abx500_get_register_interruptible(di->dev, AB8500_GAS_GAUGE, - AB8500_GASG_CC_NCOV_ACCU_MED, &med); - if (ret < 0) - goto exit; - - ret = abx500_get_register_interruptible(di->dev, AB8500_GAS_GAUGE, - AB8500_GASG_CC_NCOV_ACCU_HIGH, &high); - if (ret < 0) - goto exit; - - /* Check for sign bit in case of negative value, 2's compliment */ - if (high & 0x10) - val = (low | (med << 8) | (high << 16) | 0xFFE00000); - else - val = (low | (med << 8) | (high << 16)); - - /* - * Convert to uAh - * Given a 250ms conversion cycle time the LSB corresponds - * to 112.9 nAh. - * 112.9nAh assumes 10mOhm, but fg_res is in 0.1mOhm - */ - di->accu_charge = (val * QLSB_NANO_AMP_HOURS_X10) / - (100 * di->bm->fg_res); - - /* - * Convert to unit value in mA - * by dividing by the conversion - * time in hours (= samples / (3600 * 4)h) - * and multiply with 1000 - */ - di->avg_curr = (val * QLSB_NANO_AMP_HOURS_X10 * 36) / - (1000 * di->bm->fg_res * (di->fg_samples / 4)); - - di->flags.conv_done = true; - - mutex_unlock(&di->cc_lock); - - queue_work(di->fg_wq, &di->fg_work); - - dev_dbg(di->dev, "fg_res: %d, fg_samples: %d, gasg: %d, accu_charge: %d \n", - di->bm->fg_res, di->fg_samples, val, di->accu_charge); - return; -exit: - dev_err(di->dev, - "Failed to read or write gas gauge registers\n"); - mutex_unlock(&di->cc_lock); - queue_work(di->fg_wq, &di->fg_work); -} - -/** - * ab8500_fg_bat_voltage() - get battery voltage - * @di: pointer to the ab8500_fg structure - * - * Returns battery voltage(on success) else error code - */ -static int ab8500_fg_bat_voltage(struct ab8500_fg *di) -{ - int vbat; - static int prev; - - vbat = ab8500_gpadc_convert(di->gpadc, MAIN_BAT_V); - if (vbat < 0) { - dev_err(di->dev, - "%s gpadc conversion failed, using previous value\n", - __func__); - return prev; - } - - prev = vbat; - return vbat; -} - -/** - * ab8500_fg_volt_to_capacity() - Voltage based capacity - * @di: pointer to the ab8500_fg structure - * @voltage: The voltage to convert to a capacity - * - * Returns battery capacity in per mille based on voltage - */ -static int ab8500_fg_volt_to_capacity(struct ab8500_fg *di, int voltage) -{ - int i, tbl_size; - const struct abx500_v_to_cap *tbl; - int cap = 0; - - tbl = di->bm->bat_type[di->bm->batt_id].v_to_cap_tbl, - tbl_size = di->bm->bat_type[di->bm->batt_id].n_v_cap_tbl_elements; - - for (i = 0; i < tbl_size; ++i) { - if (voltage > tbl[i].voltage) - break; - } - - if ((i > 0) && (i < tbl_size)) { - cap = interpolate(voltage, - tbl[i].voltage, - tbl[i].capacity * 10, - tbl[i-1].voltage, - tbl[i-1].capacity * 10); - } else if (i == 0) { - cap = 1000; - } else { - cap = 0; - } - - dev_dbg(di->dev, "%s Vbat: %d, Cap: %d per mille", - __func__, voltage, cap); - - return cap; -} - -/** - * ab8500_fg_uncomp_volt_to_capacity() - Uncompensated voltage based capacity - * @di: pointer to the ab8500_fg structure - * - * Returns battery capacity based on battery voltage that is not compensated - * for the voltage drop due to the load - */ -static int ab8500_fg_uncomp_volt_to_capacity(struct ab8500_fg *di) -{ - di->vbat = ab8500_fg_bat_voltage(di); - return ab8500_fg_volt_to_capacity(di, di->vbat); -} - -/** - * ab8500_fg_battery_resistance() - Returns the battery inner resistance - * @di: pointer to the ab8500_fg structure - * - * Returns battery inner resistance added with the fuel gauge resistor value - * to get the total resistance in the whole link from gnd to bat+ node. - */ -static int ab8500_fg_battery_resistance(struct ab8500_fg *di) -{ - int i, tbl_size; - const struct batres_vs_temp *tbl; - int resist = 0; - - tbl = di->bm->bat_type[di->bm->batt_id].batres_tbl; - tbl_size = di->bm->bat_type[di->bm->batt_id].n_batres_tbl_elements; - - for (i = 0; i < tbl_size; ++i) { - if (di->bat_temp / 10 > tbl[i].temp) - break; - } - - if ((i > 0) && (i < tbl_size)) { - resist = interpolate(di->bat_temp / 10, - tbl[i].temp, - tbl[i].resist, - tbl[i-1].temp, - tbl[i-1].resist); - } else if (i == 0) { - resist = tbl[0].resist; - } else { - resist = tbl[tbl_size - 1].resist; - } - - dev_dbg(di->dev, "%s Temp: %d battery internal resistance: %d" - " fg resistance %d, total: %d (mOhm)\n", - __func__, di->bat_temp, resist, di->bm->fg_res / 10, - (di->bm->fg_res / 10) + resist); - - /* fg_res variable is in 0.1mOhm */ - resist += di->bm->fg_res / 10; - - return resist; -} - -/** - * ab8500_fg_load_comp_volt_to_capacity() - Load compensated voltage based capacity - * @di: pointer to the ab8500_fg structure - * - * Returns battery capacity based on battery voltage that is load compensated - * for the voltage drop - */ -static int ab8500_fg_load_comp_volt_to_capacity(struct ab8500_fg *di) -{ - int vbat_comp, res; - int i = 0; - int vbat = 0; - - ab8500_fg_inst_curr_start(di); - - do { - vbat += ab8500_fg_bat_voltage(di); - i++; - usleep_range(5000, 6000); - } while (!ab8500_fg_inst_curr_done(di)); - - ab8500_fg_inst_curr_finalize(di, &di->inst_curr); - - di->vbat = vbat / i; - res = ab8500_fg_battery_resistance(di); - - /* Use Ohms law to get the load compensated voltage */ - vbat_comp = di->vbat - (di->inst_curr * res) / 1000; - - dev_dbg(di->dev, "%s Measured Vbat: %dmV,Compensated Vbat %dmV, " - "R: %dmOhm, Current: %dmA Vbat Samples: %d\n", - __func__, di->vbat, vbat_comp, res, di->inst_curr, i); - - return ab8500_fg_volt_to_capacity(di, vbat_comp); -} - -/** - * ab8500_fg_convert_mah_to_permille() - Capacity in mAh to permille - * @di: pointer to the ab8500_fg structure - * @cap_mah: capacity in mAh - * - * Converts capacity in mAh to capacity in permille - */ -static int ab8500_fg_convert_mah_to_permille(struct ab8500_fg *di, int cap_mah) -{ - return (cap_mah * 1000) / di->bat_cap.max_mah_design; -} - -/** - * ab8500_fg_convert_permille_to_mah() - Capacity in permille to mAh - * @di: pointer to the ab8500_fg structure - * @cap_pm: capacity in permille - * - * Converts capacity in permille to capacity in mAh - */ -static int ab8500_fg_convert_permille_to_mah(struct ab8500_fg *di, int cap_pm) -{ - return cap_pm * di->bat_cap.max_mah_design / 1000; -} - -/** - * ab8500_fg_convert_mah_to_uwh() - Capacity in mAh to uWh - * @di: pointer to the ab8500_fg structure - * @cap_mah: capacity in mAh - * - * Converts capacity in mAh to capacity in uWh - */ -static int ab8500_fg_convert_mah_to_uwh(struct ab8500_fg *di, int cap_mah) -{ - u64 div_res; - u32 div_rem; - - div_res = ((u64) cap_mah) * ((u64) di->vbat_nom); - div_rem = do_div(div_res, 1000); - - /* Make sure to round upwards if necessary */ - if (div_rem >= 1000 / 2) - div_res++; - - return (int) div_res; -} - -/** - * ab8500_fg_calc_cap_charging() - Calculate remaining capacity while charging - * @di: pointer to the ab8500_fg structure - * - * Return the capacity in mAh based on previous calculated capcity and the FG - * accumulator register value. The filter is filled with this capacity - */ -static int ab8500_fg_calc_cap_charging(struct ab8500_fg *di) -{ - dev_dbg(di->dev, "%s cap_mah %d accu_charge %d\n", - __func__, - di->bat_cap.mah, - di->accu_charge); - - /* Capacity should not be less than 0 */ - if (di->bat_cap.mah + di->accu_charge > 0) - di->bat_cap.mah += di->accu_charge; - else - di->bat_cap.mah = 0; - /* - * We force capacity to 100% once when the algorithm - * reports that it's full. - */ - if (di->bat_cap.mah >= di->bat_cap.max_mah_design || - di->flags.force_full) { - di->bat_cap.mah = di->bat_cap.max_mah_design; - } - - ab8500_fg_fill_cap_sample(di, di->bat_cap.mah); - di->bat_cap.permille = - ab8500_fg_convert_mah_to_permille(di, di->bat_cap.mah); - - /* We need to update battery voltage and inst current when charging */ - di->vbat = ab8500_fg_bat_voltage(di); - di->inst_curr = ab8500_fg_inst_curr_blocking(di); - - return di->bat_cap.mah; -} - -/** - * ab8500_fg_calc_cap_discharge_voltage() - Capacity in discharge with voltage - * @di: pointer to the ab8500_fg structure - * @comp: if voltage should be load compensated before capacity calc - * - * Return the capacity in mAh based on the battery voltage. The voltage can - * either be load compensated or not. This value is added to the filter and a - * new mean value is calculated and returned. - */ -static int ab8500_fg_calc_cap_discharge_voltage(struct ab8500_fg *di, bool comp) -{ - int permille, mah; - - if (comp) - permille = ab8500_fg_load_comp_volt_to_capacity(di); - else - permille = ab8500_fg_uncomp_volt_to_capacity(di); - - mah = ab8500_fg_convert_permille_to_mah(di, permille); - - di->bat_cap.mah = ab8500_fg_add_cap_sample(di, mah); - di->bat_cap.permille = - ab8500_fg_convert_mah_to_permille(di, di->bat_cap.mah); - - return di->bat_cap.mah; -} - -/** - * ab8500_fg_calc_cap_discharge_fg() - Capacity in discharge with FG - * @di: pointer to the ab8500_fg structure - * - * Return the capacity in mAh based on previous calculated capcity and the FG - * accumulator register value. This value is added to the filter and a - * new mean value is calculated and returned. - */ -static int ab8500_fg_calc_cap_discharge_fg(struct ab8500_fg *di) -{ - int permille_volt, permille; - - dev_dbg(di->dev, "%s cap_mah %d accu_charge %d\n", - __func__, - di->bat_cap.mah, - di->accu_charge); - - /* Capacity should not be less than 0 */ - if (di->bat_cap.mah + di->accu_charge > 0) - di->bat_cap.mah += di->accu_charge; - else - di->bat_cap.mah = 0; - - if (di->bat_cap.mah >= di->bat_cap.max_mah_design) - di->bat_cap.mah = di->bat_cap.max_mah_design; - - /* - * Check against voltage based capacity. It can not be lower - * than what the uncompensated voltage says - */ - permille = ab8500_fg_convert_mah_to_permille(di, di->bat_cap.mah); - permille_volt = ab8500_fg_uncomp_volt_to_capacity(di); - - if (permille < permille_volt) { - di->bat_cap.permille = permille_volt; - di->bat_cap.mah = ab8500_fg_convert_permille_to_mah(di, - di->bat_cap.permille); - - dev_dbg(di->dev, "%s voltage based: perm %d perm_volt %d\n", - __func__, - permille, - permille_volt); - - ab8500_fg_fill_cap_sample(di, di->bat_cap.mah); - } else { - ab8500_fg_fill_cap_sample(di, di->bat_cap.mah); - di->bat_cap.permille = - ab8500_fg_convert_mah_to_permille(di, di->bat_cap.mah); - } - - return di->bat_cap.mah; -} - -/** - * ab8500_fg_capacity_level() - Get the battery capacity level - * @di: pointer to the ab8500_fg structure - * - * Get the battery capacity level based on the capacity in percent - */ -static int ab8500_fg_capacity_level(struct ab8500_fg *di) -{ - int ret, percent; - - percent = DIV_ROUND_CLOSEST(di->bat_cap.permille, 10); - - if (percent <= di->bm->cap_levels->critical || - di->flags.low_bat) - ret = POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL; - else if (percent <= di->bm->cap_levels->low) - ret = POWER_SUPPLY_CAPACITY_LEVEL_LOW; - else if (percent <= di->bm->cap_levels->normal) - ret = POWER_SUPPLY_CAPACITY_LEVEL_NORMAL; - else if (percent <= di->bm->cap_levels->high) - ret = POWER_SUPPLY_CAPACITY_LEVEL_HIGH; - else - ret = POWER_SUPPLY_CAPACITY_LEVEL_FULL; - - return ret; -} - -/** - * ab8500_fg_calculate_scaled_capacity() - Capacity scaling - * @di: pointer to the ab8500_fg structure - * - * Calculates the capacity to be shown to upper layers. Scales the capacity - * to have 100% as a reference from the actual capacity upon removal of charger - * when charging is in maintenance mode. - */ -static int ab8500_fg_calculate_scaled_capacity(struct ab8500_fg *di) -{ - struct ab8500_fg_cap_scaling *cs = &di->bat_cap.cap_scale; - int capacity = di->bat_cap.prev_percent; - - if (!cs->enable) - return capacity; - - /* - * As long as we are in fully charge mode scale the capacity - * to show 100%. - */ - if (di->flags.fully_charged) { - cs->cap_to_scale[0] = 100; - cs->cap_to_scale[1] = - max(capacity, di->bm->fg_params->maint_thres); - dev_dbg(di->dev, "Scale cap with %d/%d\n", - cs->cap_to_scale[0], cs->cap_to_scale[1]); - } - - /* Calculates the scaled capacity. */ - if ((cs->cap_to_scale[0] != cs->cap_to_scale[1]) - && (cs->cap_to_scale[1] > 0)) - capacity = min(100, - DIV_ROUND_CLOSEST(di->bat_cap.prev_percent * - cs->cap_to_scale[0], - cs->cap_to_scale[1])); - - if (di->flags.charging) { - if (capacity < cs->disable_cap_level) { - cs->disable_cap_level = capacity; - dev_dbg(di->dev, "Cap to stop scale lowered %d%%\n", - cs->disable_cap_level); - } else if (!di->flags.fully_charged) { - if (di->bat_cap.prev_percent >= - cs->disable_cap_level) { - dev_dbg(di->dev, "Disabling scaled capacity\n"); - cs->enable = false; - capacity = di->bat_cap.prev_percent; - } else { - dev_dbg(di->dev, - "Waiting in cap to level %d%%\n", - cs->disable_cap_level); - capacity = cs->disable_cap_level; - } - } - } - - return capacity; -} - -/** - * ab8500_fg_update_cap_scalers() - Capacity scaling - * @di: pointer to the ab8500_fg structure - * - * To be called when state change from charge<->discharge to update - * the capacity scalers. - */ -static void ab8500_fg_update_cap_scalers(struct ab8500_fg *di) -{ - struct ab8500_fg_cap_scaling *cs = &di->bat_cap.cap_scale; - - if (!cs->enable) - return; - if (di->flags.charging) { - di->bat_cap.cap_scale.disable_cap_level = - di->bat_cap.cap_scale.scaled_cap; - dev_dbg(di->dev, "Cap to stop scale at charge %d%%\n", - di->bat_cap.cap_scale.disable_cap_level); - } else { - if (cs->scaled_cap != 100) { - cs->cap_to_scale[0] = cs->scaled_cap; - cs->cap_to_scale[1] = di->bat_cap.prev_percent; - } else { - cs->cap_to_scale[0] = 100; - cs->cap_to_scale[1] = - max(di->bat_cap.prev_percent, - di->bm->fg_params->maint_thres); - } - - dev_dbg(di->dev, "Cap to scale at discharge %d/%d\n", - cs->cap_to_scale[0], cs->cap_to_scale[1]); - } -} - -/** - * ab8500_fg_check_capacity_limits() - Check if capacity has changed - * @di: pointer to the ab8500_fg structure - * @init: capacity is allowed to go up in init mode - * - * Check if capacity or capacity limit has changed and notify the system - * about it using the power_supply framework - */ -static void ab8500_fg_check_capacity_limits(struct ab8500_fg *di, bool init) -{ - bool changed = false; - int percent = DIV_ROUND_CLOSEST(di->bat_cap.permille, 10); - - di->bat_cap.level = ab8500_fg_capacity_level(di); - - if (di->bat_cap.level != di->bat_cap.prev_level) { - /* - * We do not allow reported capacity level to go up - * unless we're charging or if we're in init - */ - if (!(!di->flags.charging && di->bat_cap.level > - di->bat_cap.prev_level) || init) { - dev_dbg(di->dev, "level changed from %d to %d\n", - di->bat_cap.prev_level, - di->bat_cap.level); - di->bat_cap.prev_level = di->bat_cap.level; - changed = true; - } else { - dev_dbg(di->dev, "level not allowed to go up " - "since no charger is connected: %d to %d\n", - di->bat_cap.prev_level, - di->bat_cap.level); - } - } - - /* - * If we have received the LOW_BAT IRQ, set capacity to 0 to initiate - * shutdown - */ - if (di->flags.low_bat) { - dev_dbg(di->dev, "Battery low, set capacity to 0\n"); - di->bat_cap.prev_percent = 0; - di->bat_cap.permille = 0; - percent = 0; - di->bat_cap.prev_mah = 0; - di->bat_cap.mah = 0; - changed = true; - } else if (di->flags.fully_charged) { - /* - * We report 100% if algorithm reported fully charged - * and show 100% during maintenance charging (scaling). - */ - if (di->flags.force_full) { - di->bat_cap.prev_percent = percent; - di->bat_cap.prev_mah = di->bat_cap.mah; - - changed = true; - - if (!di->bat_cap.cap_scale.enable && - di->bm->capacity_scaling) { - di->bat_cap.cap_scale.enable = true; - di->bat_cap.cap_scale.cap_to_scale[0] = 100; - di->bat_cap.cap_scale.cap_to_scale[1] = - di->bat_cap.prev_percent; - di->bat_cap.cap_scale.disable_cap_level = 100; - } - } else if (di->bat_cap.prev_percent != percent) { - dev_dbg(di->dev, - "battery reported full " - "but capacity dropping: %d\n", - percent); - di->bat_cap.prev_percent = percent; - di->bat_cap.prev_mah = di->bat_cap.mah; - - changed = true; - } - } else if (di->bat_cap.prev_percent != percent) { - if (percent == 0) { - /* - * We will not report 0% unless we've got - * the LOW_BAT IRQ, no matter what the FG - * algorithm says. - */ - di->bat_cap.prev_percent = 1; - percent = 1; - - changed = true; - } else if (!(!di->flags.charging && - percent > di->bat_cap.prev_percent) || init) { - /* - * We do not allow reported capacity to go up - * unless we're charging or if we're in init - */ - dev_dbg(di->dev, - "capacity changed from %d to %d (%d)\n", - di->bat_cap.prev_percent, - percent, - di->bat_cap.permille); - di->bat_cap.prev_percent = percent; - di->bat_cap.prev_mah = di->bat_cap.mah; - - changed = true; - } else { - dev_dbg(di->dev, "capacity not allowed to go up since " - "no charger is connected: %d to %d (%d)\n", - di->bat_cap.prev_percent, - percent, - di->bat_cap.permille); - } - } - - if (changed) { - if (di->bm->capacity_scaling) { - di->bat_cap.cap_scale.scaled_cap = - ab8500_fg_calculate_scaled_capacity(di); - - dev_info(di->dev, "capacity=%d (%d)\n", - di->bat_cap.prev_percent, - di->bat_cap.cap_scale.scaled_cap); - } - power_supply_changed(di->fg_psy); - if (di->flags.fully_charged && di->flags.force_full) { - dev_dbg(di->dev, "Battery full, notifying.\n"); - di->flags.force_full = false; - sysfs_notify(&di->fg_kobject, NULL, "charge_full"); - } - sysfs_notify(&di->fg_kobject, NULL, "charge_now"); - } -} - -static void ab8500_fg_charge_state_to(struct ab8500_fg *di, - enum ab8500_fg_charge_state new_state) -{ - dev_dbg(di->dev, "Charge state from %d [%s] to %d [%s]\n", - di->charge_state, - charge_state[di->charge_state], - new_state, - charge_state[new_state]); - - di->charge_state = new_state; -} - -static void ab8500_fg_discharge_state_to(struct ab8500_fg *di, - enum ab8500_fg_discharge_state new_state) -{ - dev_dbg(di->dev, "Disharge state from %d [%s] to %d [%s]\n", - di->discharge_state, - discharge_state[di->discharge_state], - new_state, - discharge_state[new_state]); - - di->discharge_state = new_state; -} - -/** - * ab8500_fg_algorithm_charging() - FG algorithm for when charging - * @di: pointer to the ab8500_fg structure - * - * Battery capacity calculation state machine for when we're charging - */ -static void ab8500_fg_algorithm_charging(struct ab8500_fg *di) -{ - /* - * If we change to discharge mode - * we should start with recovery - */ - if (di->discharge_state != AB8500_FG_DISCHARGE_INIT_RECOVERY) - ab8500_fg_discharge_state_to(di, - AB8500_FG_DISCHARGE_INIT_RECOVERY); - - switch (di->charge_state) { - case AB8500_FG_CHARGE_INIT: - di->fg_samples = SEC_TO_SAMPLE( - di->bm->fg_params->accu_charging); - - ab8500_fg_coulomb_counter(di, true); - ab8500_fg_charge_state_to(di, AB8500_FG_CHARGE_READOUT); - - break; - - case AB8500_FG_CHARGE_READOUT: - /* - * Read the FG and calculate the new capacity - */ - mutex_lock(&di->cc_lock); - if (!di->flags.conv_done && !di->flags.force_full) { - /* Wasn't the CC IRQ that got us here */ - mutex_unlock(&di->cc_lock); - dev_dbg(di->dev, "%s CC conv not done\n", - __func__); - - break; - } - di->flags.conv_done = false; - mutex_unlock(&di->cc_lock); - - ab8500_fg_calc_cap_charging(di); - - break; - - default: - break; - } - - /* Check capacity limits */ - ab8500_fg_check_capacity_limits(di, false); -} - -static void force_capacity(struct ab8500_fg *di) -{ - int cap; - - ab8500_fg_clear_cap_samples(di); - cap = di->bat_cap.user_mah; - if (cap > di->bat_cap.max_mah_design) { - dev_dbg(di->dev, "Remaining cap %d can't be bigger than total" - " %d\n", cap, di->bat_cap.max_mah_design); - cap = di->bat_cap.max_mah_design; - } - ab8500_fg_fill_cap_sample(di, di->bat_cap.user_mah); - di->bat_cap.permille = ab8500_fg_convert_mah_to_permille(di, cap); - di->bat_cap.mah = cap; - ab8500_fg_check_capacity_limits(di, true); -} - -static bool check_sysfs_capacity(struct ab8500_fg *di) -{ - int cap, lower, upper; - int cap_permille; - - cap = di->bat_cap.user_mah; - - cap_permille = ab8500_fg_convert_mah_to_permille(di, - di->bat_cap.user_mah); - - lower = di->bat_cap.permille - di->bm->fg_params->user_cap_limit * 10; - upper = di->bat_cap.permille + di->bm->fg_params->user_cap_limit * 10; - - if (lower < 0) - lower = 0; - /* 1000 is permille, -> 100 percent */ - if (upper > 1000) - upper = 1000; - - dev_dbg(di->dev, "Capacity limits:" - " (Lower: %d User: %d Upper: %d) [user: %d, was: %d]\n", - lower, cap_permille, upper, cap, di->bat_cap.mah); - - /* If within limits, use the saved capacity and exit estimation...*/ - if (cap_permille > lower && cap_permille < upper) { - dev_dbg(di->dev, "OK! Using users cap %d uAh now\n", cap); - force_capacity(di); - return true; - } - dev_dbg(di->dev, "Capacity from user out of limits, ignoring"); - return false; -} - -/** - * ab8500_fg_algorithm_discharging() - FG algorithm for when discharging - * @di: pointer to the ab8500_fg structure - * - * Battery capacity calculation state machine for when we're discharging - */ -static void ab8500_fg_algorithm_discharging(struct ab8500_fg *di) -{ - int sleep_time; - - /* If we change to charge mode we should start with init */ - if (di->charge_state != AB8500_FG_CHARGE_INIT) - ab8500_fg_charge_state_to(di, AB8500_FG_CHARGE_INIT); - - switch (di->discharge_state) { - case AB8500_FG_DISCHARGE_INIT: - /* We use the FG IRQ to work on */ - di->init_cnt = 0; - di->fg_samples = SEC_TO_SAMPLE(di->bm->fg_params->init_timer); - ab8500_fg_coulomb_counter(di, true); - ab8500_fg_discharge_state_to(di, - AB8500_FG_DISCHARGE_INITMEASURING); - - /* Intentional fallthrough */ - case AB8500_FG_DISCHARGE_INITMEASURING: - /* - * Discard a number of samples during startup. - * After that, use compensated voltage for a few - * samples to get an initial capacity. - * Then go to READOUT - */ - sleep_time = di->bm->fg_params->init_timer; - - /* Discard the first [x] seconds */ - if (di->init_cnt > di->bm->fg_params->init_discard_time) { - ab8500_fg_calc_cap_discharge_voltage(di, true); - - ab8500_fg_check_capacity_limits(di, true); - } - - di->init_cnt += sleep_time; - if (di->init_cnt > di->bm->fg_params->init_total_time) - ab8500_fg_discharge_state_to(di, - AB8500_FG_DISCHARGE_READOUT_INIT); - - break; - - case AB8500_FG_DISCHARGE_INIT_RECOVERY: - di->recovery_cnt = 0; - di->recovery_needed = true; - ab8500_fg_discharge_state_to(di, - AB8500_FG_DISCHARGE_RECOVERY); - - /* Intentional fallthrough */ - - case AB8500_FG_DISCHARGE_RECOVERY: - sleep_time = di->bm->fg_params->recovery_sleep_timer; - - /* - * We should check the power consumption - * If low, go to READOUT (after x min) or - * RECOVERY_SLEEP if time left. - * If high, go to READOUT - */ - di->inst_curr = ab8500_fg_inst_curr_blocking(di); - - if (ab8500_fg_is_low_curr(di, di->inst_curr)) { - if (di->recovery_cnt > - di->bm->fg_params->recovery_total_time) { - di->fg_samples = SEC_TO_SAMPLE( - di->bm->fg_params->accu_high_curr); - ab8500_fg_coulomb_counter(di, true); - ab8500_fg_discharge_state_to(di, - AB8500_FG_DISCHARGE_READOUT); - di->recovery_needed = false; - } else { - queue_delayed_work(di->fg_wq, - &di->fg_periodic_work, - sleep_time * HZ); - } - di->recovery_cnt += sleep_time; - } else { - di->fg_samples = SEC_TO_SAMPLE( - di->bm->fg_params->accu_high_curr); - ab8500_fg_coulomb_counter(di, true); - ab8500_fg_discharge_state_to(di, - AB8500_FG_DISCHARGE_READOUT); - } - break; - - case AB8500_FG_DISCHARGE_READOUT_INIT: - di->fg_samples = SEC_TO_SAMPLE( - di->bm->fg_params->accu_high_curr); - ab8500_fg_coulomb_counter(di, true); - ab8500_fg_discharge_state_to(di, - AB8500_FG_DISCHARGE_READOUT); - break; - - case AB8500_FG_DISCHARGE_READOUT: - di->inst_curr = ab8500_fg_inst_curr_blocking(di); - - if (ab8500_fg_is_low_curr(di, di->inst_curr)) { - /* Detect mode change */ - if (di->high_curr_mode) { - di->high_curr_mode = false; - di->high_curr_cnt = 0; - } - - if (di->recovery_needed) { - ab8500_fg_discharge_state_to(di, - AB8500_FG_DISCHARGE_INIT_RECOVERY); - - queue_delayed_work(di->fg_wq, - &di->fg_periodic_work, 0); - - break; - } - - ab8500_fg_calc_cap_discharge_voltage(di, true); - } else { - mutex_lock(&di->cc_lock); - if (!di->flags.conv_done) { - /* Wasn't the CC IRQ that got us here */ - mutex_unlock(&di->cc_lock); - dev_dbg(di->dev, "%s CC conv not done\n", - __func__); - - break; - } - di->flags.conv_done = false; - mutex_unlock(&di->cc_lock); - - /* Detect mode change */ - if (!di->high_curr_mode) { - di->high_curr_mode = true; - di->high_curr_cnt = 0; - } - - di->high_curr_cnt += - di->bm->fg_params->accu_high_curr; - if (di->high_curr_cnt > - di->bm->fg_params->high_curr_time) - di->recovery_needed = true; - - ab8500_fg_calc_cap_discharge_fg(di); - } - - ab8500_fg_check_capacity_limits(di, false); - - break; - - case AB8500_FG_DISCHARGE_WAKEUP: - ab8500_fg_calc_cap_discharge_voltage(di, true); - - di->fg_samples = SEC_TO_SAMPLE( - di->bm->fg_params->accu_high_curr); - ab8500_fg_coulomb_counter(di, true); - ab8500_fg_discharge_state_to(di, - AB8500_FG_DISCHARGE_READOUT); - - ab8500_fg_check_capacity_limits(di, false); - - break; - - default: - break; - } -} - -/** - * ab8500_fg_algorithm_calibrate() - Internal columb counter offset calibration - * @di: pointer to the ab8500_fg structure - * - */ -static void ab8500_fg_algorithm_calibrate(struct ab8500_fg *di) -{ - int ret; - - switch (di->calib_state) { - case AB8500_FG_CALIB_INIT: - dev_dbg(di->dev, "Calibration ongoing...\n"); - - ret = abx500_mask_and_set_register_interruptible(di->dev, - AB8500_GAS_GAUGE, AB8500_GASG_CC_CTRL_REG, - CC_INT_CAL_N_AVG_MASK, CC_INT_CAL_SAMPLES_8); - if (ret < 0) - goto err; - - ret = abx500_mask_and_set_register_interruptible(di->dev, - AB8500_GAS_GAUGE, AB8500_GASG_CC_CTRL_REG, - CC_INTAVGOFFSET_ENA, CC_INTAVGOFFSET_ENA); - if (ret < 0) - goto err; - di->calib_state = AB8500_FG_CALIB_WAIT; - break; - case AB8500_FG_CALIB_END: - ret = abx500_mask_and_set_register_interruptible(di->dev, - AB8500_GAS_GAUGE, AB8500_GASG_CC_CTRL_REG, - CC_MUXOFFSET, CC_MUXOFFSET); - if (ret < 0) - goto err; - di->flags.calibrate = false; - dev_dbg(di->dev, "Calibration done...\n"); - queue_delayed_work(di->fg_wq, &di->fg_periodic_work, 0); - break; - case AB8500_FG_CALIB_WAIT: - dev_dbg(di->dev, "Calibration WFI\n"); - default: - break; - } - return; -err: - /* Something went wrong, don't calibrate then */ - dev_err(di->dev, "failed to calibrate the CC\n"); - di->flags.calibrate = false; - di->calib_state = AB8500_FG_CALIB_INIT; - queue_delayed_work(di->fg_wq, &di->fg_periodic_work, 0); -} - -/** - * ab8500_fg_algorithm() - Entry point for the FG algorithm - * @di: pointer to the ab8500_fg structure - * - * Entry point for the battery capacity calculation state machine - */ -static void ab8500_fg_algorithm(struct ab8500_fg *di) -{ - if (di->flags.calibrate) - ab8500_fg_algorithm_calibrate(di); - else { - if (di->flags.charging) - ab8500_fg_algorithm_charging(di); - else - ab8500_fg_algorithm_discharging(di); - } - - dev_dbg(di->dev, "[FG_DATA] %d %d %d %d %d %d %d %d %d %d " - "%d %d %d %d %d %d %d\n", - di->bat_cap.max_mah_design, - di->bat_cap.max_mah, - di->bat_cap.mah, - di->bat_cap.permille, - di->bat_cap.level, - di->bat_cap.prev_mah, - di->bat_cap.prev_percent, - di->bat_cap.prev_level, - di->vbat, - di->inst_curr, - di->avg_curr, - di->accu_charge, - di->flags.charging, - di->charge_state, - di->discharge_state, - di->high_curr_mode, - di->recovery_needed); -} - -/** - * ab8500_fg_periodic_work() - Run the FG state machine periodically - * @work: pointer to the work_struct structure - * - * Work queue function for periodic work - */ -static void ab8500_fg_periodic_work(struct work_struct *work) -{ - struct ab8500_fg *di = container_of(work, struct ab8500_fg, - fg_periodic_work.work); - - if (di->init_capacity) { - /* Get an initial capacity calculation */ - ab8500_fg_calc_cap_discharge_voltage(di, true); - ab8500_fg_check_capacity_limits(di, true); - di->init_capacity = false; - - queue_delayed_work(di->fg_wq, &di->fg_periodic_work, 0); - } else if (di->flags.user_cap) { - if (check_sysfs_capacity(di)) { - ab8500_fg_check_capacity_limits(di, true); - if (di->flags.charging) - ab8500_fg_charge_state_to(di, - AB8500_FG_CHARGE_INIT); - else - ab8500_fg_discharge_state_to(di, - AB8500_FG_DISCHARGE_READOUT_INIT); - } - di->flags.user_cap = false; - queue_delayed_work(di->fg_wq, &di->fg_periodic_work, 0); - } else - ab8500_fg_algorithm(di); - -} - -/** - * ab8500_fg_check_hw_failure_work() - Check OVV_BAT condition - * @work: pointer to the work_struct structure - * - * Work queue function for checking the OVV_BAT condition - */ -static void ab8500_fg_check_hw_failure_work(struct work_struct *work) -{ - int ret; - u8 reg_value; - - struct ab8500_fg *di = container_of(work, struct ab8500_fg, - fg_check_hw_failure_work.work); - - /* - * If we have had a battery over-voltage situation, - * check ovv-bit to see if it should be reset. - */ - ret = abx500_get_register_interruptible(di->dev, - AB8500_CHARGER, AB8500_CH_STAT_REG, - ®_value); - if (ret < 0) { - dev_err(di->dev, "%s ab8500 read failed\n", __func__); - return; - } - if ((reg_value & BATT_OVV) == BATT_OVV) { - if (!di->flags.bat_ovv) { - dev_dbg(di->dev, "Battery OVV\n"); - di->flags.bat_ovv = true; - power_supply_changed(di->fg_psy); - } - /* Not yet recovered from ovv, reschedule this test */ - queue_delayed_work(di->fg_wq, &di->fg_check_hw_failure_work, - HZ); - } else { - dev_dbg(di->dev, "Battery recovered from OVV\n"); - di->flags.bat_ovv = false; - power_supply_changed(di->fg_psy); - } -} - -/** - * ab8500_fg_low_bat_work() - Check LOW_BAT condition - * @work: pointer to the work_struct structure - * - * Work queue function for checking the LOW_BAT condition - */ -static void ab8500_fg_low_bat_work(struct work_struct *work) -{ - int vbat; - - struct ab8500_fg *di = container_of(work, struct ab8500_fg, - fg_low_bat_work.work); - - vbat = ab8500_fg_bat_voltage(di); - - /* Check if LOW_BAT still fulfilled */ - if (vbat < di->bm->fg_params->lowbat_threshold) { - /* Is it time to shut down? */ - if (di->low_bat_cnt < 1) { - di->flags.low_bat = true; - dev_warn(di->dev, "Shut down pending...\n"); - } else { - /* - * Else we need to re-schedule this check to be able to detect - * if the voltage increases again during charging or - * due to decreasing load. - */ - di->low_bat_cnt--; - dev_warn(di->dev, "Battery voltage still LOW\n"); - queue_delayed_work(di->fg_wq, &di->fg_low_bat_work, - round_jiffies(LOW_BAT_CHECK_INTERVAL)); - } - } else { - di->flags.low_bat_delay = false; - di->low_bat_cnt = 10; - dev_warn(di->dev, "Battery voltage OK again\n"); - } - - /* This is needed to dispatch LOW_BAT */ - ab8500_fg_check_capacity_limits(di, false); -} - -/** - * ab8500_fg_battok_calc - calculate the bit pattern corresponding - * to the target voltage. - * @di: pointer to the ab8500_fg structure - * @target target voltage - * - * Returns bit pattern closest to the target voltage - * valid return values are 0-14. (0-BATT_OK_MAX_NR_INCREMENTS) - */ - -static int ab8500_fg_battok_calc(struct ab8500_fg *di, int target) -{ - if (target > BATT_OK_MIN + - (BATT_OK_INCREMENT * BATT_OK_MAX_NR_INCREMENTS)) - return BATT_OK_MAX_NR_INCREMENTS; - if (target < BATT_OK_MIN) - return 0; - return (target - BATT_OK_MIN) / BATT_OK_INCREMENT; -} - -/** - * ab8500_fg_battok_init_hw_register - init battok levels - * @di: pointer to the ab8500_fg structure - * - */ - -static int ab8500_fg_battok_init_hw_register(struct ab8500_fg *di) -{ - int selected; - int sel0; - int sel1; - int cbp_sel0; - int cbp_sel1; - int ret; - int new_val; - - sel0 = di->bm->fg_params->battok_falling_th_sel0; - sel1 = di->bm->fg_params->battok_raising_th_sel1; - - cbp_sel0 = ab8500_fg_battok_calc(di, sel0); - cbp_sel1 = ab8500_fg_battok_calc(di, sel1); - - selected = BATT_OK_MIN + cbp_sel0 * BATT_OK_INCREMENT; - - if (selected != sel0) - dev_warn(di->dev, "Invalid voltage step:%d, using %d %d\n", - sel0, selected, cbp_sel0); - - selected = BATT_OK_MIN + cbp_sel1 * BATT_OK_INCREMENT; - - if (selected != sel1) - dev_warn(di->dev, "Invalid voltage step:%d, using %d %d\n", - sel1, selected, cbp_sel1); - - new_val = cbp_sel0 | (cbp_sel1 << 4); - - dev_dbg(di->dev, "using: %x %d %d\n", new_val, cbp_sel0, cbp_sel1); - ret = abx500_set_register_interruptible(di->dev, AB8500_SYS_CTRL2_BLOCK, - AB8500_BATT_OK_REG, new_val); - return ret; -} - -/** - * ab8500_fg_instant_work() - Run the FG state machine instantly - * @work: pointer to the work_struct structure - * - * Work queue function for instant work - */ -static void ab8500_fg_instant_work(struct work_struct *work) -{ - struct ab8500_fg *di = container_of(work, struct ab8500_fg, fg_work); - - ab8500_fg_algorithm(di); -} - -/** - * ab8500_fg_cc_data_end_handler() - end of data conversion isr. - * @irq: interrupt number - * @_di: pointer to the ab8500_fg structure - * - * Returns IRQ status(IRQ_HANDLED) - */ -static irqreturn_t ab8500_fg_cc_data_end_handler(int irq, void *_di) -{ - struct ab8500_fg *di = _di; - if (!di->nbr_cceoc_irq_cnt) { - di->nbr_cceoc_irq_cnt++; - complete(&di->ab8500_fg_started); - } else { - di->nbr_cceoc_irq_cnt = 0; - complete(&di->ab8500_fg_complete); - } - return IRQ_HANDLED; -} - -/** - * ab8500_fg_cc_int_calib_handler () - end of calibration isr. - * @irq: interrupt number - * @_di: pointer to the ab8500_fg structure - * - * Returns IRQ status(IRQ_HANDLED) - */ -static irqreturn_t ab8500_fg_cc_int_calib_handler(int irq, void *_di) -{ - struct ab8500_fg *di = _di; - di->calib_state = AB8500_FG_CALIB_END; - queue_delayed_work(di->fg_wq, &di->fg_periodic_work, 0); - return IRQ_HANDLED; -} - -/** - * ab8500_fg_cc_convend_handler() - isr to get battery avg current. - * @irq: interrupt number - * @_di: pointer to the ab8500_fg structure - * - * Returns IRQ status(IRQ_HANDLED) - */ -static irqreturn_t ab8500_fg_cc_convend_handler(int irq, void *_di) -{ - struct ab8500_fg *di = _di; - - queue_work(di->fg_wq, &di->fg_acc_cur_work); - - return IRQ_HANDLED; -} - -/** - * ab8500_fg_batt_ovv_handler() - Battery OVV occured - * @irq: interrupt number - * @_di: pointer to the ab8500_fg structure - * - * Returns IRQ status(IRQ_HANDLED) - */ -static irqreturn_t ab8500_fg_batt_ovv_handler(int irq, void *_di) -{ - struct ab8500_fg *di = _di; - - dev_dbg(di->dev, "Battery OVV\n"); - - /* Schedule a new HW failure check */ - queue_delayed_work(di->fg_wq, &di->fg_check_hw_failure_work, 0); - - return IRQ_HANDLED; -} - -/** - * ab8500_fg_lowbatf_handler() - Battery voltage is below LOW threshold - * @irq: interrupt number - * @_di: pointer to the ab8500_fg structure - * - * Returns IRQ status(IRQ_HANDLED) - */ -static irqreturn_t ab8500_fg_lowbatf_handler(int irq, void *_di) -{ - struct ab8500_fg *di = _di; - - /* Initiate handling in ab8500_fg_low_bat_work() if not already initiated. */ - if (!di->flags.low_bat_delay) { - dev_warn(di->dev, "Battery voltage is below LOW threshold\n"); - di->flags.low_bat_delay = true; - /* - * Start a timer to check LOW_BAT again after some time - * This is done to avoid shutdown on single voltage dips - */ - queue_delayed_work(di->fg_wq, &di->fg_low_bat_work, - round_jiffies(LOW_BAT_CHECK_INTERVAL)); - } - return IRQ_HANDLED; -} - -/** - * ab8500_fg_get_property() - get the fg properties - * @psy: pointer to the power_supply structure - * @psp: pointer to the power_supply_property structure - * @val: pointer to the power_supply_propval union - * - * This function gets called when an application tries to get the - * fg properties by reading the sysfs files. - * voltage_now: battery voltage - * current_now: battery instant current - * current_avg: battery average current - * charge_full_design: capacity where battery is considered full - * charge_now: battery capacity in nAh - * capacity: capacity in percent - * capacity_level: capacity level - * - * Returns error code in case of failure else 0 on success - */ -static int ab8500_fg_get_property(struct power_supply *psy, - enum power_supply_property psp, - union power_supply_propval *val) -{ - struct ab8500_fg *di = power_supply_get_drvdata(psy); - - /* - * If battery is identified as unknown and charging of unknown - * batteries is disabled, we always report 100% capacity and - * capacity level UNKNOWN, since we can't calculate - * remaining capacity - */ - - switch (psp) { - case POWER_SUPPLY_PROP_VOLTAGE_NOW: - if (di->flags.bat_ovv) - val->intval = BATT_OVV_VALUE * 1000; - else - val->intval = di->vbat * 1000; - break; - case POWER_SUPPLY_PROP_CURRENT_NOW: - val->intval = di->inst_curr * 1000; - break; - case POWER_SUPPLY_PROP_CURRENT_AVG: - val->intval = di->avg_curr * 1000; - break; - case POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN: - val->intval = ab8500_fg_convert_mah_to_uwh(di, - di->bat_cap.max_mah_design); - break; - case POWER_SUPPLY_PROP_ENERGY_FULL: - val->intval = ab8500_fg_convert_mah_to_uwh(di, - di->bat_cap.max_mah); - break; - case POWER_SUPPLY_PROP_ENERGY_NOW: - if (di->flags.batt_unknown && !di->bm->chg_unknown_bat && - di->flags.batt_id_received) - val->intval = ab8500_fg_convert_mah_to_uwh(di, - di->bat_cap.max_mah); - else - val->intval = ab8500_fg_convert_mah_to_uwh(di, - di->bat_cap.prev_mah); - break; - case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: - val->intval = di->bat_cap.max_mah_design; - break; - case POWER_SUPPLY_PROP_CHARGE_FULL: - val->intval = di->bat_cap.max_mah; - break; - case POWER_SUPPLY_PROP_CHARGE_NOW: - if (di->flags.batt_unknown && !di->bm->chg_unknown_bat && - di->flags.batt_id_received) - val->intval = di->bat_cap.max_mah; - else - val->intval = di->bat_cap.prev_mah; - break; - case POWER_SUPPLY_PROP_CAPACITY: - if (di->flags.batt_unknown && !di->bm->chg_unknown_bat && - di->flags.batt_id_received) - val->intval = 100; - else - val->intval = di->bat_cap.prev_percent; - break; - case POWER_SUPPLY_PROP_CAPACITY_LEVEL: - if (di->flags.batt_unknown && !di->bm->chg_unknown_bat && - di->flags.batt_id_received) - val->intval = POWER_SUPPLY_CAPACITY_LEVEL_UNKNOWN; - else - val->intval = di->bat_cap.prev_level; - break; - default: - return -EINVAL; - } - return 0; -} - -static int ab8500_fg_get_ext_psy_data(struct device *dev, void *data) -{ - struct power_supply *psy; - struct power_supply *ext = dev_get_drvdata(dev); - const char **supplicants = (const char **)ext->supplied_to; - struct ab8500_fg *di; - union power_supply_propval ret; - int j; - - psy = (struct power_supply *)data; - di = power_supply_get_drvdata(psy); - - /* - * For all psy where the name of your driver - * appears in any supplied_to - */ - j = match_string(supplicants, ext->num_supplicants, psy->desc->name); - if (j < 0) - return 0; - - /* Go through all properties for the psy */ - for (j = 0; j < ext->desc->num_properties; j++) { - enum power_supply_property prop; - prop = ext->desc->properties[j]; - - if (power_supply_get_property(ext, prop, &ret)) - continue; - - switch (prop) { - case POWER_SUPPLY_PROP_STATUS: - switch (ext->desc->type) { - case POWER_SUPPLY_TYPE_BATTERY: - switch (ret.intval) { - case POWER_SUPPLY_STATUS_UNKNOWN: - case POWER_SUPPLY_STATUS_DISCHARGING: - case POWER_SUPPLY_STATUS_NOT_CHARGING: - if (!di->flags.charging) - break; - di->flags.charging = false; - di->flags.fully_charged = false; - if (di->bm->capacity_scaling) - ab8500_fg_update_cap_scalers(di); - queue_work(di->fg_wq, &di->fg_work); - break; - case POWER_SUPPLY_STATUS_FULL: - if (di->flags.fully_charged) - break; - di->flags.fully_charged = true; - di->flags.force_full = true; - /* Save current capacity as maximum */ - di->bat_cap.max_mah = di->bat_cap.mah; - queue_work(di->fg_wq, &di->fg_work); - break; - case POWER_SUPPLY_STATUS_CHARGING: - if (di->flags.charging && - !di->flags.fully_charged) - break; - di->flags.charging = true; - di->flags.fully_charged = false; - if (di->bm->capacity_scaling) - ab8500_fg_update_cap_scalers(di); - queue_work(di->fg_wq, &di->fg_work); - break; - }; - default: - break; - }; - break; - case POWER_SUPPLY_PROP_TECHNOLOGY: - switch (ext->desc->type) { - case POWER_SUPPLY_TYPE_BATTERY: - if (!di->flags.batt_id_received && - di->bm->batt_id != BATTERY_UNKNOWN) { - const struct abx500_battery_type *b; - - b = &(di->bm->bat_type[di->bm->batt_id]); - - di->flags.batt_id_received = true; - - di->bat_cap.max_mah_design = - MILLI_TO_MICRO * - b->charge_full_design; - - di->bat_cap.max_mah = - di->bat_cap.max_mah_design; - - di->vbat_nom = b->nominal_voltage; - } - - if (ret.intval) - di->flags.batt_unknown = false; - else - di->flags.batt_unknown = true; - break; - default: - break; - } - break; - case POWER_SUPPLY_PROP_TEMP: - switch (ext->desc->type) { - case POWER_SUPPLY_TYPE_BATTERY: - if (di->flags.batt_id_received) - di->bat_temp = ret.intval; - break; - default: - break; - } - break; - default: - break; - } - } - return 0; -} - -/** - * ab8500_fg_init_hw_registers() - Set up FG related registers - * @di: pointer to the ab8500_fg structure - * - * Set up battery OVV, low battery voltage registers - */ -static int ab8500_fg_init_hw_registers(struct ab8500_fg *di) -{ - int ret; - - /* Set VBAT OVV threshold */ - ret = abx500_mask_and_set_register_interruptible(di->dev, - AB8500_CHARGER, - AB8500_BATT_OVV, - BATT_OVV_TH_4P75, - BATT_OVV_TH_4P75); - if (ret) { - dev_err(di->dev, "failed to set BATT_OVV\n"); - goto out; - } - - /* Enable VBAT OVV detection */ - ret = abx500_mask_and_set_register_interruptible(di->dev, - AB8500_CHARGER, - AB8500_BATT_OVV, - BATT_OVV_ENA, - BATT_OVV_ENA); - if (ret) { - dev_err(di->dev, "failed to enable BATT_OVV\n"); - goto out; - } - - /* Low Battery Voltage */ - ret = abx500_set_register_interruptible(di->dev, - AB8500_SYS_CTRL2_BLOCK, - AB8500_LOW_BAT_REG, - ab8500_volt_to_regval( - di->bm->fg_params->lowbat_threshold) << 1 | - LOW_BAT_ENABLE); - if (ret) { - dev_err(di->dev, "%s write failed\n", __func__); - goto out; - } - - /* Battery OK threshold */ - ret = ab8500_fg_battok_init_hw_register(di); - if (ret) { - dev_err(di->dev, "BattOk init write failed.\n"); - goto out; - } - - if (((is_ab8505(di->parent) || is_ab9540(di->parent)) && - abx500_get_chip_id(di->dev) >= AB8500_CUT2P0) - || is_ab8540(di->parent)) { - ret = abx500_set_register_interruptible(di->dev, AB8500_RTC, - AB8505_RTC_PCUT_MAX_TIME_REG, di->bm->fg_params->pcut_max_time); - - if (ret) { - dev_err(di->dev, "%s write failed AB8505_RTC_PCUT_MAX_TIME_REG\n", __func__); - goto out; - }; - - ret = abx500_set_register_interruptible(di->dev, AB8500_RTC, - AB8505_RTC_PCUT_FLAG_TIME_REG, di->bm->fg_params->pcut_flag_time); - - if (ret) { - dev_err(di->dev, "%s write failed AB8505_RTC_PCUT_FLAG_TIME_REG\n", __func__); - goto out; - }; - - ret = abx500_set_register_interruptible(di->dev, AB8500_RTC, - AB8505_RTC_PCUT_RESTART_REG, di->bm->fg_params->pcut_max_restart); - - if (ret) { - dev_err(di->dev, "%s write failed AB8505_RTC_PCUT_RESTART_REG\n", __func__); - goto out; - }; - - ret = abx500_set_register_interruptible(di->dev, AB8500_RTC, - AB8505_RTC_PCUT_DEBOUNCE_REG, di->bm->fg_params->pcut_debounce_time); - - if (ret) { - dev_err(di->dev, "%s write failed AB8505_RTC_PCUT_DEBOUNCE_REG\n", __func__); - goto out; - }; - - ret = abx500_set_register_interruptible(di->dev, AB8500_RTC, - AB8505_RTC_PCUT_CTL_STATUS_REG, di->bm->fg_params->pcut_enable); - - if (ret) { - dev_err(di->dev, "%s write failed AB8505_RTC_PCUT_CTL_STATUS_REG\n", __func__); - goto out; - }; - } -out: - return ret; -} - -/** - * ab8500_fg_external_power_changed() - callback for power supply changes - * @psy: pointer to the structure power_supply - * - * This function is the entry point of the pointer external_power_changed - * of the structure power_supply. - * This function gets executed when there is a change in any external power - * supply that this driver needs to be notified of. - */ -static void ab8500_fg_external_power_changed(struct power_supply *psy) -{ - struct ab8500_fg *di = power_supply_get_drvdata(psy); - - class_for_each_device(power_supply_class, NULL, - di->fg_psy, ab8500_fg_get_ext_psy_data); -} - -/** - * abab8500_fg_reinit_work() - work to reset the FG algorithm - * @work: pointer to the work_struct structure - * - * Used to reset the current battery capacity to be able to - * retrigger a new voltage base capacity calculation. For - * test and verification purpose. - */ -static void ab8500_fg_reinit_work(struct work_struct *work) -{ - struct ab8500_fg *di = container_of(work, struct ab8500_fg, - fg_reinit_work.work); - - if (di->flags.calibrate == false) { - dev_dbg(di->dev, "Resetting FG state machine to init.\n"); - ab8500_fg_clear_cap_samples(di); - ab8500_fg_calc_cap_discharge_voltage(di, true); - ab8500_fg_charge_state_to(di, AB8500_FG_CHARGE_INIT); - ab8500_fg_discharge_state_to(di, AB8500_FG_DISCHARGE_INIT); - queue_delayed_work(di->fg_wq, &di->fg_periodic_work, 0); - - } else { - dev_err(di->dev, "Residual offset calibration ongoing " - "retrying..\n"); - /* Wait one second until next try*/ - queue_delayed_work(di->fg_wq, &di->fg_reinit_work, - round_jiffies(1)); - } -} - -/* Exposure to the sysfs interface */ - -struct ab8500_fg_sysfs_entry { - struct attribute attr; - ssize_t (*show)(struct ab8500_fg *, char *); - ssize_t (*store)(struct ab8500_fg *, const char *, size_t); -}; - -static ssize_t charge_full_show(struct ab8500_fg *di, char *buf) -{ - return sprintf(buf, "%d\n", di->bat_cap.max_mah); -} - -static ssize_t charge_full_store(struct ab8500_fg *di, const char *buf, - size_t count) -{ - unsigned long charge_full; - ssize_t ret; - - ret = kstrtoul(buf, 10, &charge_full); - - dev_dbg(di->dev, "Ret %zd charge_full %lu", ret, charge_full); - - if (!ret) { - di->bat_cap.max_mah = (int) charge_full; - ret = count; - } - return ret; -} - -static ssize_t charge_now_show(struct ab8500_fg *di, char *buf) -{ - return sprintf(buf, "%d\n", di->bat_cap.prev_mah); -} - -static ssize_t charge_now_store(struct ab8500_fg *di, const char *buf, - size_t count) -{ - unsigned long charge_now; - ssize_t ret; - - ret = kstrtoul(buf, 10, &charge_now); - - dev_dbg(di->dev, "Ret %zd charge_now %lu was %d", - ret, charge_now, di->bat_cap.prev_mah); - - if (!ret) { - di->bat_cap.user_mah = (int) charge_now; - di->flags.user_cap = true; - ret = count; - queue_delayed_work(di->fg_wq, &di->fg_periodic_work, 0); - } - return ret; -} - -static struct ab8500_fg_sysfs_entry charge_full_attr = - __ATTR(charge_full, 0644, charge_full_show, charge_full_store); - -static struct ab8500_fg_sysfs_entry charge_now_attr = - __ATTR(charge_now, 0644, charge_now_show, charge_now_store); - -static ssize_t -ab8500_fg_show(struct kobject *kobj, struct attribute *attr, char *buf) -{ - struct ab8500_fg_sysfs_entry *entry; - struct ab8500_fg *di; - - entry = container_of(attr, struct ab8500_fg_sysfs_entry, attr); - di = container_of(kobj, struct ab8500_fg, fg_kobject); - - if (!entry->show) - return -EIO; - - return entry->show(di, buf); -} -static ssize_t -ab8500_fg_store(struct kobject *kobj, struct attribute *attr, const char *buf, - size_t count) -{ - struct ab8500_fg_sysfs_entry *entry; - struct ab8500_fg *di; - - entry = container_of(attr, struct ab8500_fg_sysfs_entry, attr); - di = container_of(kobj, struct ab8500_fg, fg_kobject); - - if (!entry->store) - return -EIO; - - return entry->store(di, buf, count); -} - -static const struct sysfs_ops ab8500_fg_sysfs_ops = { - .show = ab8500_fg_show, - .store = ab8500_fg_store, -}; - -static struct attribute *ab8500_fg_attrs[] = { - &charge_full_attr.attr, - &charge_now_attr.attr, - NULL, -}; - -static struct kobj_type ab8500_fg_ktype = { - .sysfs_ops = &ab8500_fg_sysfs_ops, - .default_attrs = ab8500_fg_attrs, -}; - -/** - * ab8500_chargalg_sysfs_exit() - de-init of sysfs entry - * @di: pointer to the struct ab8500_chargalg - * - * This function removes the entry in sysfs. - */ -static void ab8500_fg_sysfs_exit(struct ab8500_fg *di) -{ - kobject_del(&di->fg_kobject); -} - -/** - * ab8500_chargalg_sysfs_init() - init of sysfs entry - * @di: pointer to the struct ab8500_chargalg - * - * This function adds an entry in sysfs. - * Returns error code in case of failure else 0(on success) - */ -static int ab8500_fg_sysfs_init(struct ab8500_fg *di) -{ - int ret = 0; - - ret = kobject_init_and_add(&di->fg_kobject, - &ab8500_fg_ktype, - NULL, "battery"); - if (ret < 0) - dev_err(di->dev, "failed to create sysfs entry\n"); - - return ret; -} - -static ssize_t ab8505_powercut_flagtime_read(struct device *dev, - struct device_attribute *attr, - char *buf) -{ - int ret; - u8 reg_value; - struct power_supply *psy = dev_get_drvdata(dev); - struct ab8500_fg *di = power_supply_get_drvdata(psy); - - ret = abx500_get_register_interruptible(di->dev, AB8500_RTC, - AB8505_RTC_PCUT_FLAG_TIME_REG, ®_value); - - if (ret < 0) { - dev_err(dev, "Failed to read AB8505_RTC_PCUT_FLAG_TIME_REG\n"); - goto fail; - } - - return scnprintf(buf, PAGE_SIZE, "%d\n", (reg_value & 0x7F)); - -fail: - return ret; -} - -static ssize_t ab8505_powercut_flagtime_write(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t count) -{ - int ret; - long unsigned reg_value; - struct power_supply *psy = dev_get_drvdata(dev); - struct ab8500_fg *di = power_supply_get_drvdata(psy); - - reg_value = simple_strtoul(buf, NULL, 10); - - if (reg_value > 0x7F) { - dev_err(dev, "Incorrect parameter, echo 0 (1.98s) - 127 (15.625ms) for flagtime\n"); - goto fail; - } - - ret = abx500_set_register_interruptible(di->dev, AB8500_RTC, - AB8505_RTC_PCUT_FLAG_TIME_REG, (u8)reg_value); - - if (ret < 0) - dev_err(dev, "Failed to set AB8505_RTC_PCUT_FLAG_TIME_REG\n"); - -fail: - return count; -} - -static ssize_t ab8505_powercut_maxtime_read(struct device *dev, - struct device_attribute *attr, - char *buf) -{ - int ret; - u8 reg_value; - struct power_supply *psy = dev_get_drvdata(dev); - struct ab8500_fg *di = power_supply_get_drvdata(psy); - - ret = abx500_get_register_interruptible(di->dev, AB8500_RTC, - AB8505_RTC_PCUT_MAX_TIME_REG, ®_value); - - if (ret < 0) { - dev_err(dev, "Failed to read AB8505_RTC_PCUT_MAX_TIME_REG\n"); - goto fail; - } - - return scnprintf(buf, PAGE_SIZE, "%d\n", (reg_value & 0x7F)); - -fail: - return ret; - -} - -static ssize_t ab8505_powercut_maxtime_write(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t count) -{ - int ret; - int reg_value; - struct power_supply *psy = dev_get_drvdata(dev); - struct ab8500_fg *di = power_supply_get_drvdata(psy); - - reg_value = simple_strtoul(buf, NULL, 10); - if (reg_value > 0x7F) { - dev_err(dev, "Incorrect parameter, echo 0 (0.0s) - 127 (1.98s) for maxtime\n"); - goto fail; - } - - ret = abx500_set_register_interruptible(di->dev, AB8500_RTC, - AB8505_RTC_PCUT_MAX_TIME_REG, (u8)reg_value); - - if (ret < 0) - dev_err(dev, "Failed to set AB8505_RTC_PCUT_MAX_TIME_REG\n"); - -fail: - return count; -} - -static ssize_t ab8505_powercut_restart_read(struct device *dev, - struct device_attribute *attr, - char *buf) -{ - int ret; - u8 reg_value; - struct power_supply *psy = dev_get_drvdata(dev); - struct ab8500_fg *di = power_supply_get_drvdata(psy); - - ret = abx500_get_register_interruptible(di->dev, AB8500_RTC, - AB8505_RTC_PCUT_RESTART_REG, ®_value); - - if (ret < 0) { - dev_err(dev, "Failed to read AB8505_RTC_PCUT_RESTART_REG\n"); - goto fail; - } - - return scnprintf(buf, PAGE_SIZE, "%d\n", (reg_value & 0xF)); - -fail: - return ret; -} - -static ssize_t ab8505_powercut_restart_write(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t count) -{ - int ret; - int reg_value; - struct power_supply *psy = dev_get_drvdata(dev); - struct ab8500_fg *di = power_supply_get_drvdata(psy); - - reg_value = simple_strtoul(buf, NULL, 10); - if (reg_value > 0xF) { - dev_err(dev, "Incorrect parameter, echo 0 - 15 for number of restart\n"); - goto fail; - } - - ret = abx500_set_register_interruptible(di->dev, AB8500_RTC, - AB8505_RTC_PCUT_RESTART_REG, (u8)reg_value); - - if (ret < 0) - dev_err(dev, "Failed to set AB8505_RTC_PCUT_RESTART_REG\n"); - -fail: - return count; - -} - -static ssize_t ab8505_powercut_timer_read(struct device *dev, - struct device_attribute *attr, - char *buf) -{ - int ret; - u8 reg_value; - struct power_supply *psy = dev_get_drvdata(dev); - struct ab8500_fg *di = power_supply_get_drvdata(psy); - - ret = abx500_get_register_interruptible(di->dev, AB8500_RTC, - AB8505_RTC_PCUT_TIME_REG, ®_value); - - if (ret < 0) { - dev_err(dev, "Failed to read AB8505_RTC_PCUT_TIME_REG\n"); - goto fail; - } - - return scnprintf(buf, PAGE_SIZE, "%d\n", (reg_value & 0x7F)); - -fail: - return ret; -} - -static ssize_t ab8505_powercut_restart_counter_read(struct device *dev, - struct device_attribute *attr, - char *buf) -{ - int ret; - u8 reg_value; - struct power_supply *psy = dev_get_drvdata(dev); - struct ab8500_fg *di = power_supply_get_drvdata(psy); - - ret = abx500_get_register_interruptible(di->dev, AB8500_RTC, - AB8505_RTC_PCUT_RESTART_REG, ®_value); - - if (ret < 0) { - dev_err(dev, "Failed to read AB8505_RTC_PCUT_RESTART_REG\n"); - goto fail; - } - - return scnprintf(buf, PAGE_SIZE, "%d\n", (reg_value & 0xF0) >> 4); - -fail: - return ret; -} - -static ssize_t ab8505_powercut_read(struct device *dev, - struct device_attribute *attr, - char *buf) -{ - int ret; - u8 reg_value; - struct power_supply *psy = dev_get_drvdata(dev); - struct ab8500_fg *di = power_supply_get_drvdata(psy); - - ret = abx500_get_register_interruptible(di->dev, AB8500_RTC, - AB8505_RTC_PCUT_CTL_STATUS_REG, ®_value); - - if (ret < 0) - goto fail; - - return scnprintf(buf, PAGE_SIZE, "%d\n", (reg_value & 0x1)); - -fail: - return ret; -} - -static ssize_t ab8505_powercut_write(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t count) -{ - int ret; - int reg_value; - struct power_supply *psy = dev_get_drvdata(dev); - struct ab8500_fg *di = power_supply_get_drvdata(psy); - - reg_value = simple_strtoul(buf, NULL, 10); - if (reg_value > 0x1) { - dev_err(dev, "Incorrect parameter, echo 0/1 to disable/enable Pcut feature\n"); - goto fail; - } - - ret = abx500_set_register_interruptible(di->dev, AB8500_RTC, - AB8505_RTC_PCUT_CTL_STATUS_REG, (u8)reg_value); - - if (ret < 0) - dev_err(dev, "Failed to set AB8505_RTC_PCUT_CTL_STATUS_REG\n"); - -fail: - return count; -} - -static ssize_t ab8505_powercut_flag_read(struct device *dev, - struct device_attribute *attr, - char *buf) -{ - - int ret; - u8 reg_value; - struct power_supply *psy = dev_get_drvdata(dev); - struct ab8500_fg *di = power_supply_get_drvdata(psy); - - ret = abx500_get_register_interruptible(di->dev, AB8500_RTC, - AB8505_RTC_PCUT_CTL_STATUS_REG, ®_value); - - if (ret < 0) { - dev_err(dev, "Failed to read AB8505_RTC_PCUT_CTL_STATUS_REG\n"); - goto fail; - } - - return scnprintf(buf, PAGE_SIZE, "%d\n", ((reg_value & 0x10) >> 4)); - -fail: - return ret; -} - -static ssize_t ab8505_powercut_debounce_read(struct device *dev, - struct device_attribute *attr, - char *buf) -{ - int ret; - u8 reg_value; - struct power_supply *psy = dev_get_drvdata(dev); - struct ab8500_fg *di = power_supply_get_drvdata(psy); - - ret = abx500_get_register_interruptible(di->dev, AB8500_RTC, - AB8505_RTC_PCUT_DEBOUNCE_REG, ®_value); - - if (ret < 0) { - dev_err(dev, "Failed to read AB8505_RTC_PCUT_DEBOUNCE_REG\n"); - goto fail; - } - - return scnprintf(buf, PAGE_SIZE, "%d\n", (reg_value & 0x7)); - -fail: - return ret; -} - -static ssize_t ab8505_powercut_debounce_write(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t count) -{ - int ret; - int reg_value; - struct power_supply *psy = dev_get_drvdata(dev); - struct ab8500_fg *di = power_supply_get_drvdata(psy); - - reg_value = simple_strtoul(buf, NULL, 10); - if (reg_value > 0x7) { - dev_err(dev, "Incorrect parameter, echo 0 to 7 for debounce setting\n"); - goto fail; - } - - ret = abx500_set_register_interruptible(di->dev, AB8500_RTC, - AB8505_RTC_PCUT_DEBOUNCE_REG, (u8)reg_value); - - if (ret < 0) - dev_err(dev, "Failed to set AB8505_RTC_PCUT_DEBOUNCE_REG\n"); - -fail: - return count; -} - -static ssize_t ab8505_powercut_enable_status_read(struct device *dev, - struct device_attribute *attr, - char *buf) -{ - int ret; - u8 reg_value; - struct power_supply *psy = dev_get_drvdata(dev); - struct ab8500_fg *di = power_supply_get_drvdata(psy); - - ret = abx500_get_register_interruptible(di->dev, AB8500_RTC, - AB8505_RTC_PCUT_CTL_STATUS_REG, ®_value); - - if (ret < 0) { - dev_err(dev, "Failed to read AB8505_RTC_PCUT_CTL_STATUS_REG\n"); - goto fail; - } - - return scnprintf(buf, PAGE_SIZE, "%d\n", ((reg_value & 0x20) >> 5)); - -fail: - return ret; -} - -static struct device_attribute ab8505_fg_sysfs_psy_attrs[] = { - __ATTR(powercut_flagtime, (S_IRUGO | S_IWUSR | S_IWGRP), - ab8505_powercut_flagtime_read, ab8505_powercut_flagtime_write), - __ATTR(powercut_maxtime, (S_IRUGO | S_IWUSR | S_IWGRP), - ab8505_powercut_maxtime_read, ab8505_powercut_maxtime_write), - __ATTR(powercut_restart_max, (S_IRUGO | S_IWUSR | S_IWGRP), - ab8505_powercut_restart_read, ab8505_powercut_restart_write), - __ATTR(powercut_timer, S_IRUGO, ab8505_powercut_timer_read, NULL), - __ATTR(powercut_restart_counter, S_IRUGO, - ab8505_powercut_restart_counter_read, NULL), - __ATTR(powercut_enable, (S_IRUGO | S_IWUSR | S_IWGRP), - ab8505_powercut_read, ab8505_powercut_write), - __ATTR(powercut_flag, S_IRUGO, ab8505_powercut_flag_read, NULL), - __ATTR(powercut_debounce_time, (S_IRUGO | S_IWUSR | S_IWGRP), - ab8505_powercut_debounce_read, ab8505_powercut_debounce_write), - __ATTR(powercut_enable_status, S_IRUGO, - ab8505_powercut_enable_status_read, NULL), -}; - -static int ab8500_fg_sysfs_psy_create_attrs(struct ab8500_fg *di) -{ - unsigned int i; - - if (((is_ab8505(di->parent) || is_ab9540(di->parent)) && - abx500_get_chip_id(di->dev) >= AB8500_CUT2P0) - || is_ab8540(di->parent)) { - for (i = 0; i < ARRAY_SIZE(ab8505_fg_sysfs_psy_attrs); i++) - if (device_create_file(&di->fg_psy->dev, - &ab8505_fg_sysfs_psy_attrs[i])) - goto sysfs_psy_create_attrs_failed_ab8505; - } - return 0; -sysfs_psy_create_attrs_failed_ab8505: - dev_err(&di->fg_psy->dev, "Failed creating sysfs psy attrs for ab8505.\n"); - while (i--) - device_remove_file(&di->fg_psy->dev, - &ab8505_fg_sysfs_psy_attrs[i]); - - return -EIO; -} - -static void ab8500_fg_sysfs_psy_remove_attrs(struct ab8500_fg *di) -{ - unsigned int i; - - if (((is_ab8505(di->parent) || is_ab9540(di->parent)) && - abx500_get_chip_id(di->dev) >= AB8500_CUT2P0) - || is_ab8540(di->parent)) { - for (i = 0; i < ARRAY_SIZE(ab8505_fg_sysfs_psy_attrs); i++) - (void)device_remove_file(&di->fg_psy->dev, - &ab8505_fg_sysfs_psy_attrs[i]); - } -} - -/* Exposure to the sysfs interface <> */ - -#if defined(CONFIG_PM) -static int ab8500_fg_resume(struct platform_device *pdev) -{ - struct ab8500_fg *di = platform_get_drvdata(pdev); - - /* - * Change state if we're not charging. If we're charging we will wake - * up on the FG IRQ - */ - if (!di->flags.charging) { - ab8500_fg_discharge_state_to(di, AB8500_FG_DISCHARGE_WAKEUP); - queue_work(di->fg_wq, &di->fg_work); - } - - return 0; -} - -static int ab8500_fg_suspend(struct platform_device *pdev, - pm_message_t state) -{ - struct ab8500_fg *di = platform_get_drvdata(pdev); - - flush_delayed_work(&di->fg_periodic_work); - flush_work(&di->fg_work); - flush_work(&di->fg_acc_cur_work); - flush_delayed_work(&di->fg_reinit_work); - flush_delayed_work(&di->fg_low_bat_work); - flush_delayed_work(&di->fg_check_hw_failure_work); - - /* - * If the FG is enabled we will disable it before going to suspend - * only if we're not charging - */ - if (di->flags.fg_enabled && !di->flags.charging) - ab8500_fg_coulomb_counter(di, false); - - return 0; -} -#else -#define ab8500_fg_suspend NULL -#define ab8500_fg_resume NULL -#endif - -static int ab8500_fg_remove(struct platform_device *pdev) -{ - int ret = 0; - struct ab8500_fg *di = platform_get_drvdata(pdev); - - list_del(&di->node); - - /* Disable coulomb counter */ - ret = ab8500_fg_coulomb_counter(di, false); - if (ret) - dev_err(di->dev, "failed to disable coulomb counter\n"); - - destroy_workqueue(di->fg_wq); - ab8500_fg_sysfs_exit(di); - - flush_scheduled_work(); - ab8500_fg_sysfs_psy_remove_attrs(di); - power_supply_unregister(di->fg_psy); - return ret; -} - -/* ab8500 fg driver interrupts and their respective isr */ -static struct ab8500_fg_interrupts ab8500_fg_irq_th[] = { - {"NCONV_ACCU", ab8500_fg_cc_convend_handler}, - {"BATT_OVV", ab8500_fg_batt_ovv_handler}, - {"LOW_BAT_F", ab8500_fg_lowbatf_handler}, - {"CC_INT_CALIB", ab8500_fg_cc_int_calib_handler}, -}; - -static struct ab8500_fg_interrupts ab8500_fg_irq_bh[] = { - {"CCEOC", ab8500_fg_cc_data_end_handler}, -}; - -static char *supply_interface[] = { - "ab8500_chargalg", - "ab8500_usb", -}; - -static const struct power_supply_desc ab8500_fg_desc = { - .name = "ab8500_fg", - .type = POWER_SUPPLY_TYPE_BATTERY, - .properties = ab8500_fg_props, - .num_properties = ARRAY_SIZE(ab8500_fg_props), - .get_property = ab8500_fg_get_property, - .external_power_changed = ab8500_fg_external_power_changed, -}; - -static int ab8500_fg_probe(struct platform_device *pdev) -{ - struct device_node *np = pdev->dev.of_node; - struct abx500_bm_data *plat = pdev->dev.platform_data; - struct power_supply_config psy_cfg = {}; - struct ab8500_fg *di; - int i, irq; - int ret = 0; - - di = devm_kzalloc(&pdev->dev, sizeof(*di), GFP_KERNEL); - if (!di) { - dev_err(&pdev->dev, "%s no mem for ab8500_fg\n", __func__); - return -ENOMEM; - } - - if (!plat) { - dev_err(&pdev->dev, "no battery management data supplied\n"); - return -EINVAL; - } - di->bm = plat; - - if (np) { - ret = ab8500_bm_of_probe(&pdev->dev, np, di->bm); - if (ret) { - dev_err(&pdev->dev, "failed to get battery information\n"); - return ret; - } - } - - mutex_init(&di->cc_lock); - - /* get parent data */ - di->dev = &pdev->dev; - di->parent = dev_get_drvdata(pdev->dev.parent); - di->gpadc = ab8500_gpadc_get("ab8500-gpadc.0"); - - psy_cfg.supplied_to = supply_interface; - psy_cfg.num_supplicants = ARRAY_SIZE(supply_interface); - psy_cfg.drv_data = di; - - di->bat_cap.max_mah_design = MILLI_TO_MICRO * - di->bm->bat_type[di->bm->batt_id].charge_full_design; - - di->bat_cap.max_mah = di->bat_cap.max_mah_design; - - di->vbat_nom = di->bm->bat_type[di->bm->batt_id].nominal_voltage; - - di->init_capacity = true; - - ab8500_fg_charge_state_to(di, AB8500_FG_CHARGE_INIT); - ab8500_fg_discharge_state_to(di, AB8500_FG_DISCHARGE_INIT); - - /* Create a work queue for running the FG algorithm */ - di->fg_wq = create_singlethread_workqueue("ab8500_fg_wq"); - if (di->fg_wq == NULL) { - dev_err(di->dev, "failed to create work queue\n"); - return -ENOMEM; - } - - /* Init work for running the fg algorithm instantly */ - INIT_WORK(&di->fg_work, ab8500_fg_instant_work); - - /* Init work for getting the battery accumulated current */ - INIT_WORK(&di->fg_acc_cur_work, ab8500_fg_acc_cur_work); - - /* Init work for reinitialising the fg algorithm */ - INIT_DEFERRABLE_WORK(&di->fg_reinit_work, - ab8500_fg_reinit_work); - - /* Work delayed Queue to run the state machine */ - INIT_DEFERRABLE_WORK(&di->fg_periodic_work, - ab8500_fg_periodic_work); - - /* Work to check low battery condition */ - INIT_DEFERRABLE_WORK(&di->fg_low_bat_work, - ab8500_fg_low_bat_work); - - /* Init work for HW failure check */ - INIT_DEFERRABLE_WORK(&di->fg_check_hw_failure_work, - ab8500_fg_check_hw_failure_work); - - /* Reset battery low voltage flag */ - di->flags.low_bat = false; - - /* Initialize low battery counter */ - di->low_bat_cnt = 10; - - /* Initialize OVV, and other registers */ - ret = ab8500_fg_init_hw_registers(di); - if (ret) { - dev_err(di->dev, "failed to initialize registers\n"); - goto free_inst_curr_wq; - } - - /* Consider battery unknown until we're informed otherwise */ - di->flags.batt_unknown = true; - di->flags.batt_id_received = false; - - /* Register FG power supply class */ - di->fg_psy = power_supply_register(di->dev, &ab8500_fg_desc, &psy_cfg); - if (IS_ERR(di->fg_psy)) { - dev_err(di->dev, "failed to register FG psy\n"); - ret = PTR_ERR(di->fg_psy); - goto free_inst_curr_wq; - } - - di->fg_samples = SEC_TO_SAMPLE(di->bm->fg_params->init_timer); - ab8500_fg_coulomb_counter(di, true); - - /* - * Initialize completion used to notify completion and start - * of inst current - */ - init_completion(&di->ab8500_fg_started); - init_completion(&di->ab8500_fg_complete); - - /* Register primary interrupt handlers */ - for (i = 0; i < ARRAY_SIZE(ab8500_fg_irq_th); i++) { - irq = platform_get_irq_byname(pdev, ab8500_fg_irq_th[i].name); - ret = request_irq(irq, ab8500_fg_irq_th[i].isr, - IRQF_SHARED | IRQF_NO_SUSPEND, - ab8500_fg_irq_th[i].name, di); - - if (ret != 0) { - dev_err(di->dev, "failed to request %s IRQ %d: %d\n", - ab8500_fg_irq_th[i].name, irq, ret); - goto free_irq; - } - dev_dbg(di->dev, "Requested %s IRQ %d: %d\n", - ab8500_fg_irq_th[i].name, irq, ret); - } - - /* Register threaded interrupt handler */ - irq = platform_get_irq_byname(pdev, ab8500_fg_irq_bh[0].name); - ret = request_threaded_irq(irq, NULL, ab8500_fg_irq_bh[0].isr, - IRQF_SHARED | IRQF_NO_SUSPEND | IRQF_ONESHOT, - ab8500_fg_irq_bh[0].name, di); - - if (ret != 0) { - dev_err(di->dev, "failed to request %s IRQ %d: %d\n", - ab8500_fg_irq_bh[0].name, irq, ret); - goto free_irq; - } - dev_dbg(di->dev, "Requested %s IRQ %d: %d\n", - ab8500_fg_irq_bh[0].name, irq, ret); - - di->irq = platform_get_irq_byname(pdev, "CCEOC"); - disable_irq(di->irq); - di->nbr_cceoc_irq_cnt = 0; - - platform_set_drvdata(pdev, di); - - ret = ab8500_fg_sysfs_init(di); - if (ret) { - dev_err(di->dev, "failed to create sysfs entry\n"); - goto free_irq; - } - - ret = ab8500_fg_sysfs_psy_create_attrs(di); - if (ret) { - dev_err(di->dev, "failed to create FG psy\n"); - ab8500_fg_sysfs_exit(di); - goto free_irq; - } - - /* Calibrate the fg first time */ - di->flags.calibrate = true; - di->calib_state = AB8500_FG_CALIB_INIT; - - /* Use room temp as default value until we get an update from driver. */ - di->bat_temp = 210; - - /* Run the FG algorithm */ - queue_delayed_work(di->fg_wq, &di->fg_periodic_work, 0); - - list_add_tail(&di->node, &ab8500_fg_list); - - return ret; - -free_irq: - power_supply_unregister(di->fg_psy); - - /* We also have to free all registered irqs */ - for (i = 0; i < ARRAY_SIZE(ab8500_fg_irq_th); i++) { - irq = platform_get_irq_byname(pdev, ab8500_fg_irq_th[i].name); - free_irq(irq, di); - } - irq = platform_get_irq_byname(pdev, ab8500_fg_irq_bh[0].name); - free_irq(irq, di); -free_inst_curr_wq: - destroy_workqueue(di->fg_wq); - return ret; -} - -static const struct of_device_id ab8500_fg_match[] = { - { .compatible = "stericsson,ab8500-fg", }, - { }, -}; - -static struct platform_driver ab8500_fg_driver = { - .probe = ab8500_fg_probe, - .remove = ab8500_fg_remove, - .suspend = ab8500_fg_suspend, - .resume = ab8500_fg_resume, - .driver = { - .name = "ab8500-fg", - .of_match_table = ab8500_fg_match, - }, -}; - -static int __init ab8500_fg_init(void) -{ - return platform_driver_register(&ab8500_fg_driver); -} - -static void __exit ab8500_fg_exit(void) -{ - platform_driver_unregister(&ab8500_fg_driver); -} - -subsys_initcall_sync(ab8500_fg_init); -module_exit(ab8500_fg_exit); - -MODULE_LICENSE("GPL v2"); -MODULE_AUTHOR("Johan Palsson, Karl Komierowski"); -MODULE_ALIAS("platform:ab8500-fg"); -MODULE_DESCRIPTION("AB8500 Fuel Gauge driver"); diff --git a/drivers/power/abx500_chargalg.c b/drivers/power/abx500_chargalg.c deleted file mode 100644 index d9104b1ab7cf..000000000000 --- a/drivers/power/abx500_chargalg.c +++ /dev/null @@ -1,2166 +0,0 @@ -/* - * Copyright (C) ST-Ericsson SA 2012 - * Copyright (c) 2012 Sony Mobile Communications AB - * - * Charging algorithm driver for abx500 variants - * - * License Terms: GNU General Public License v2 - * Authors: - * Johan Palsson - * Karl Komierowski - * Arun R Murthy - * Author: Imre Sunyi - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/* Watchdog kick interval */ -#define CHG_WD_INTERVAL (6 * HZ) - -/* End-of-charge criteria counter */ -#define EOC_COND_CNT 10 - -/* One hour expressed in seconds */ -#define ONE_HOUR_IN_SECONDS 3600 - -/* Five minutes expressed in seconds */ -#define FIVE_MINUTES_IN_SECONDS 300 - -/* Plus margin for the low battery threshold */ -#define BAT_PLUS_MARGIN (100) - -#define CHARGALG_CURR_STEP_LOW 0 -#define CHARGALG_CURR_STEP_HIGH 100 - -enum abx500_chargers { - NO_CHG, - AC_CHG, - USB_CHG, -}; - -struct abx500_chargalg_charger_info { - enum abx500_chargers conn_chg; - enum abx500_chargers prev_conn_chg; - enum abx500_chargers online_chg; - enum abx500_chargers prev_online_chg; - enum abx500_chargers charger_type; - bool usb_chg_ok; - bool ac_chg_ok; - int usb_volt; - int usb_curr; - int ac_volt; - int ac_curr; - int usb_vset; - int usb_iset; - int ac_vset; - int ac_iset; -}; - -struct abx500_chargalg_suspension_status { - bool suspended_change; - bool ac_suspended; - bool usb_suspended; -}; - -struct abx500_chargalg_current_step_status { - bool curr_step_change; - int curr_step; -}; - -struct abx500_chargalg_battery_data { - int temp; - int volt; - int avg_curr; - int inst_curr; - int percent; -}; - -enum abx500_chargalg_states { - STATE_HANDHELD_INIT, - STATE_HANDHELD, - STATE_CHG_NOT_OK_INIT, - STATE_CHG_NOT_OK, - STATE_HW_TEMP_PROTECT_INIT, - STATE_HW_TEMP_PROTECT, - STATE_NORMAL_INIT, - STATE_USB_PP_PRE_CHARGE, - STATE_NORMAL, - STATE_WAIT_FOR_RECHARGE_INIT, - STATE_WAIT_FOR_RECHARGE, - STATE_MAINTENANCE_A_INIT, - STATE_MAINTENANCE_A, - STATE_MAINTENANCE_B_INIT, - STATE_MAINTENANCE_B, - STATE_TEMP_UNDEROVER_INIT, - STATE_TEMP_UNDEROVER, - STATE_TEMP_LOWHIGH_INIT, - STATE_TEMP_LOWHIGH, - STATE_SUSPENDED_INIT, - STATE_SUSPENDED, - STATE_OVV_PROTECT_INIT, - STATE_OVV_PROTECT, - STATE_SAFETY_TIMER_EXPIRED_INIT, - STATE_SAFETY_TIMER_EXPIRED, - STATE_BATT_REMOVED_INIT, - STATE_BATT_REMOVED, - STATE_WD_EXPIRED_INIT, - STATE_WD_EXPIRED, -}; - -static const char *states[] = { - "HANDHELD_INIT", - "HANDHELD", - "CHG_NOT_OK_INIT", - "CHG_NOT_OK", - "HW_TEMP_PROTECT_INIT", - "HW_TEMP_PROTECT", - "NORMAL_INIT", - "USB_PP_PRE_CHARGE", - "NORMAL", - "WAIT_FOR_RECHARGE_INIT", - "WAIT_FOR_RECHARGE", - "MAINTENANCE_A_INIT", - "MAINTENANCE_A", - "MAINTENANCE_B_INIT", - "MAINTENANCE_B", - "TEMP_UNDEROVER_INIT", - "TEMP_UNDEROVER", - "TEMP_LOWHIGH_INIT", - "TEMP_LOWHIGH", - "SUSPENDED_INIT", - "SUSPENDED", - "OVV_PROTECT_INIT", - "OVV_PROTECT", - "SAFETY_TIMER_EXPIRED_INIT", - "SAFETY_TIMER_EXPIRED", - "BATT_REMOVED_INIT", - "BATT_REMOVED", - "WD_EXPIRED_INIT", - "WD_EXPIRED", -}; - -struct abx500_chargalg_events { - bool batt_unknown; - bool mainextchnotok; - bool batt_ovv; - bool batt_rem; - bool btemp_underover; - bool btemp_lowhigh; - bool main_thermal_prot; - bool usb_thermal_prot; - bool main_ovv; - bool vbus_ovv; - bool usbchargernotok; - bool safety_timer_expired; - bool maintenance_timer_expired; - bool ac_wd_expired; - bool usb_wd_expired; - bool ac_cv_active; - bool usb_cv_active; - bool vbus_collapsed; -}; - -/** - * struct abx500_charge_curr_maximization - Charger maximization parameters - * @original_iset: the non optimized/maximised charger current - * @current_iset: the charging current used at this moment - * @test_delta_i: the delta between the current we want to charge and the - current that is really going into the battery - * @condition_cnt: number of iterations needed before a new charger current - is set - * @max_current: maximum charger current - * @wait_cnt: to avoid too fast current step down in case of charger - * voltage collapse, we insert this delay between step - * down - * @level: tells in how many steps the charging current has been - increased - */ -struct abx500_charge_curr_maximization { - int original_iset; - int current_iset; - int test_delta_i; - int condition_cnt; - int max_current; - int wait_cnt; - u8 level; -}; - -enum maxim_ret { - MAXIM_RET_NOACTION, - MAXIM_RET_CHANGE, - MAXIM_RET_IBAT_TOO_HIGH, -}; - -/** - * struct abx500_chargalg - abx500 Charging algorithm device information - * @dev: pointer to the structure device - * @charge_status: battery operating status - * @eoc_cnt: counter used to determine end-of_charge - * @maintenance_chg: indicate if maintenance charge is active - * @t_hyst_norm temperature hysteresis when the temperature has been - * over or under normal limits - * @t_hyst_lowhigh temperature hysteresis when the temperature has been - * over or under the high or low limits - * @charge_state: current state of the charging algorithm - * @ccm charging current maximization parameters - * @chg_info: information about connected charger types - * @batt_data: data of the battery - * @susp_status: current charger suspension status - * @bm: Platform specific battery management information - * @curr_status: Current step status for over-current protection - * @parent: pointer to the struct abx500 - * @chargalg_psy: structure that holds the battery properties exposed by - * the charging algorithm - * @events: structure for information about events triggered - * @chargalg_wq: work queue for running the charging algorithm - * @chargalg_periodic_work: work to run the charging algorithm periodically - * @chargalg_wd_work: work to kick the charger watchdog periodically - * @chargalg_work: work to run the charging algorithm instantly - * @safety_timer: charging safety timer - * @maintenance_timer: maintenance charging timer - * @chargalg_kobject: structure of type kobject - */ -struct abx500_chargalg { - struct device *dev; - int charge_status; - int eoc_cnt; - bool maintenance_chg; - int t_hyst_norm; - int t_hyst_lowhigh; - enum abx500_chargalg_states charge_state; - struct abx500_charge_curr_maximization ccm; - struct abx500_chargalg_charger_info chg_info; - struct abx500_chargalg_battery_data batt_data; - struct abx500_chargalg_suspension_status susp_status; - struct ab8500 *parent; - struct abx500_chargalg_current_step_status curr_status; - struct abx500_bm_data *bm; - struct power_supply *chargalg_psy; - struct ux500_charger *ac_chg; - struct ux500_charger *usb_chg; - struct abx500_chargalg_events events; - struct workqueue_struct *chargalg_wq; - struct delayed_work chargalg_periodic_work; - struct delayed_work chargalg_wd_work; - struct work_struct chargalg_work; - struct hrtimer safety_timer; - struct hrtimer maintenance_timer; - struct kobject chargalg_kobject; -}; - -/*External charger prepare notifier*/ -BLOCKING_NOTIFIER_HEAD(charger_notifier_list); - -/* Main battery properties */ -static enum power_supply_property abx500_chargalg_props[] = { - POWER_SUPPLY_PROP_STATUS, - POWER_SUPPLY_PROP_HEALTH, -}; - -struct abx500_chargalg_sysfs_entry { - struct attribute attr; - ssize_t (*show)(struct abx500_chargalg *, char *); - ssize_t (*store)(struct abx500_chargalg *, const char *, size_t); -}; - -/** - * abx500_chargalg_safety_timer_expired() - Expiration of the safety timer - * @timer: pointer to the hrtimer structure - * - * This function gets called when the safety timer for the charger - * expires - */ -static enum hrtimer_restart -abx500_chargalg_safety_timer_expired(struct hrtimer *timer) -{ - struct abx500_chargalg *di = container_of(timer, struct abx500_chargalg, - safety_timer); - dev_err(di->dev, "Safety timer expired\n"); - di->events.safety_timer_expired = true; - - /* Trigger execution of the algorithm instantly */ - queue_work(di->chargalg_wq, &di->chargalg_work); - - return HRTIMER_NORESTART; -} - -/** - * abx500_chargalg_maintenance_timer_expired() - Expiration of - * the maintenance timer - * @timer: pointer to the timer structure - * - * This function gets called when the maintenence timer - * expires - */ -static enum hrtimer_restart -abx500_chargalg_maintenance_timer_expired(struct hrtimer *timer) -{ - - struct abx500_chargalg *di = container_of(timer, struct abx500_chargalg, - maintenance_timer); - - dev_dbg(di->dev, "Maintenance timer expired\n"); - di->events.maintenance_timer_expired = true; - - /* Trigger execution of the algorithm instantly */ - queue_work(di->chargalg_wq, &di->chargalg_work); - - return HRTIMER_NORESTART; -} - -/** - * abx500_chargalg_state_to() - Change charge state - * @di: pointer to the abx500_chargalg structure - * - * This function gets called when a charge state change should occur - */ -static void abx500_chargalg_state_to(struct abx500_chargalg *di, - enum abx500_chargalg_states state) -{ - dev_dbg(di->dev, - "State changed: %s (From state: [%d] %s =to=> [%d] %s )\n", - di->charge_state == state ? "NO" : "YES", - di->charge_state, - states[di->charge_state], - state, - states[state]); - - di->charge_state = state; -} - -static int abx500_chargalg_check_charger_enable(struct abx500_chargalg *di) -{ - switch (di->charge_state) { - case STATE_NORMAL: - case STATE_MAINTENANCE_A: - case STATE_MAINTENANCE_B: - break; - default: - return 0; - } - - if (di->chg_info.charger_type & USB_CHG) { - return di->usb_chg->ops.check_enable(di->usb_chg, - di->bm->bat_type[di->bm->batt_id].normal_vol_lvl, - di->bm->bat_type[di->bm->batt_id].normal_cur_lvl); - } else if ((di->chg_info.charger_type & AC_CHG) && - !(di->ac_chg->external)) { - return di->ac_chg->ops.check_enable(di->ac_chg, - di->bm->bat_type[di->bm->batt_id].normal_vol_lvl, - di->bm->bat_type[di->bm->batt_id].normal_cur_lvl); - } - return 0; -} - -/** - * abx500_chargalg_check_charger_connection() - Check charger connection change - * @di: pointer to the abx500_chargalg structure - * - * This function will check if there is a change in the charger connection - * and change charge state accordingly. AC has precedence over USB. - */ -static int abx500_chargalg_check_charger_connection(struct abx500_chargalg *di) -{ - if (di->chg_info.conn_chg != di->chg_info.prev_conn_chg || - di->susp_status.suspended_change) { - /* - * Charger state changed or suspension - * has changed since last update - */ - if ((di->chg_info.conn_chg & AC_CHG) && - !di->susp_status.ac_suspended) { - dev_dbg(di->dev, "Charging source is AC\n"); - if (di->chg_info.charger_type != AC_CHG) { - di->chg_info.charger_type = AC_CHG; - abx500_chargalg_state_to(di, STATE_NORMAL_INIT); - } - } else if ((di->chg_info.conn_chg & USB_CHG) && - !di->susp_status.usb_suspended) { - dev_dbg(di->dev, "Charging source is USB\n"); - di->chg_info.charger_type = USB_CHG; - abx500_chargalg_state_to(di, STATE_NORMAL_INIT); - } else if (di->chg_info.conn_chg && - (di->susp_status.ac_suspended || - di->susp_status.usb_suspended)) { - dev_dbg(di->dev, "Charging is suspended\n"); - di->chg_info.charger_type = NO_CHG; - abx500_chargalg_state_to(di, STATE_SUSPENDED_INIT); - } else { - dev_dbg(di->dev, "Charging source is OFF\n"); - di->chg_info.charger_type = NO_CHG; - abx500_chargalg_state_to(di, STATE_HANDHELD_INIT); - } - di->chg_info.prev_conn_chg = di->chg_info.conn_chg; - di->susp_status.suspended_change = false; - } - return di->chg_info.conn_chg; -} - -/** - * abx500_chargalg_check_current_step_status() - Check charging current - * step status. - * @di: pointer to the abx500_chargalg structure - * - * This function will check if there is a change in the charging current step - * and change charge state accordingly. - */ -static void abx500_chargalg_check_current_step_status - (struct abx500_chargalg *di) -{ - if (di->curr_status.curr_step_change) - abx500_chargalg_state_to(di, STATE_NORMAL_INIT); - di->curr_status.curr_step_change = false; -} - -/** - * abx500_chargalg_start_safety_timer() - Start charging safety timer - * @di: pointer to the abx500_chargalg structure - * - * The safety timer is used to avoid overcharging of old or bad batteries. - * There are different timers for AC and USB - */ -static void abx500_chargalg_start_safety_timer(struct abx500_chargalg *di) -{ - /* Charger-dependent expiration time in hours*/ - int timer_expiration = 0; - - switch (di->chg_info.charger_type) { - case AC_CHG: - timer_expiration = di->bm->main_safety_tmr_h; - break; - - case USB_CHG: - timer_expiration = di->bm->usb_safety_tmr_h; - break; - - default: - dev_err(di->dev, "Unknown charger to charge from\n"); - break; - } - - di->events.safety_timer_expired = false; - hrtimer_set_expires_range(&di->safety_timer, - ktime_set(timer_expiration * ONE_HOUR_IN_SECONDS, 0), - ktime_set(FIVE_MINUTES_IN_SECONDS, 0)); - hrtimer_start_expires(&di->safety_timer, HRTIMER_MODE_REL); -} - -/** - * abx500_chargalg_stop_safety_timer() - Stop charging safety timer - * @di: pointer to the abx500_chargalg structure - * - * The safety timer is stopped whenever the NORMAL state is exited - */ -static void abx500_chargalg_stop_safety_timer(struct abx500_chargalg *di) -{ - if (hrtimer_try_to_cancel(&di->safety_timer) >= 0) - di->events.safety_timer_expired = false; -} - -/** - * abx500_chargalg_start_maintenance_timer() - Start charging maintenance timer - * @di: pointer to the abx500_chargalg structure - * @duration: duration of ther maintenance timer in hours - * - * The maintenance timer is used to maintain the charge in the battery once - * the battery is considered full. These timers are chosen to match the - * discharge curve of the battery - */ -static void abx500_chargalg_start_maintenance_timer(struct abx500_chargalg *di, - int duration) -{ - hrtimer_set_expires_range(&di->maintenance_timer, - ktime_set(duration * ONE_HOUR_IN_SECONDS, 0), - ktime_set(FIVE_MINUTES_IN_SECONDS, 0)); - di->events.maintenance_timer_expired = false; - hrtimer_start_expires(&di->maintenance_timer, HRTIMER_MODE_REL); -} - -/** - * abx500_chargalg_stop_maintenance_timer() - Stop maintenance timer - * @di: pointer to the abx500_chargalg structure - * - * The maintenance timer is stopped whenever maintenance ends or when another - * state is entered - */ -static void abx500_chargalg_stop_maintenance_timer(struct abx500_chargalg *di) -{ - if (hrtimer_try_to_cancel(&di->maintenance_timer) >= 0) - di->events.maintenance_timer_expired = false; -} - -/** - * abx500_chargalg_kick_watchdog() - Kick charger watchdog - * @di: pointer to the abx500_chargalg structure - * - * The charger watchdog have to be kicked periodically whenever the charger is - * on, else the ABB will reset the system - */ -static int abx500_chargalg_kick_watchdog(struct abx500_chargalg *di) -{ - /* Check if charger exists and kick watchdog if charging */ - if (di->ac_chg && di->ac_chg->ops.kick_wd && - di->chg_info.online_chg & AC_CHG) { - /* - * If AB charger watchdog expired, pm2xxx charging - * gets disabled. To be safe, kick both AB charger watchdog - * and pm2xxx watchdog. - */ - if (di->ac_chg->external && - di->usb_chg && di->usb_chg->ops.kick_wd) - di->usb_chg->ops.kick_wd(di->usb_chg); - - return di->ac_chg->ops.kick_wd(di->ac_chg); - } - else if (di->usb_chg && di->usb_chg->ops.kick_wd && - di->chg_info.online_chg & USB_CHG) - return di->usb_chg->ops.kick_wd(di->usb_chg); - - return -ENXIO; -} - -/** - * abx500_chargalg_ac_en() - Turn on/off the AC charger - * @di: pointer to the abx500_chargalg structure - * @enable: charger on/off - * @vset: requested charger output voltage - * @iset: requested charger output current - * - * The AC charger will be turned on/off with the requested charge voltage and - * current - */ -static int abx500_chargalg_ac_en(struct abx500_chargalg *di, int enable, - int vset, int iset) -{ - static int abx500_chargalg_ex_ac_enable_toggle; - - if (!di->ac_chg || !di->ac_chg->ops.enable) - return -ENXIO; - - /* Select maximum of what both the charger and the battery supports */ - if (di->ac_chg->max_out_volt) - vset = min(vset, di->ac_chg->max_out_volt); - if (di->ac_chg->max_out_curr) - iset = min(iset, di->ac_chg->max_out_curr); - - di->chg_info.ac_iset = iset; - di->chg_info.ac_vset = vset; - - /* Enable external charger */ - if (enable && di->ac_chg->external && - !abx500_chargalg_ex_ac_enable_toggle) { - blocking_notifier_call_chain(&charger_notifier_list, - 0, di->dev); - abx500_chargalg_ex_ac_enable_toggle++; - } - - return di->ac_chg->ops.enable(di->ac_chg, enable, vset, iset); -} - -/** - * abx500_chargalg_usb_en() - Turn on/off the USB charger - * @di: pointer to the abx500_chargalg structure - * @enable: charger on/off - * @vset: requested charger output voltage - * @iset: requested charger output current - * - * The USB charger will be turned on/off with the requested charge voltage and - * current - */ -static int abx500_chargalg_usb_en(struct abx500_chargalg *di, int enable, - int vset, int iset) -{ - if (!di->usb_chg || !di->usb_chg->ops.enable) - return -ENXIO; - - /* Select maximum of what both the charger and the battery supports */ - if (di->usb_chg->max_out_volt) - vset = min(vset, di->usb_chg->max_out_volt); - if (di->usb_chg->max_out_curr) - iset = min(iset, di->usb_chg->max_out_curr); - - di->chg_info.usb_iset = iset; - di->chg_info.usb_vset = vset; - - return di->usb_chg->ops.enable(di->usb_chg, enable, vset, iset); -} - - /** - * ab8540_chargalg_usb_pp_en() - Enable/ disable USB power path - * @di: pointer to the abx500_chargalg structure - * @enable: power path enable/disable - * - * The USB power path will be enable/ disable - */ -static int ab8540_chargalg_usb_pp_en(struct abx500_chargalg *di, bool enable) -{ - if (!di->usb_chg || !di->usb_chg->ops.pp_enable) - return -ENXIO; - - return di->usb_chg->ops.pp_enable(di->usb_chg, enable); -} - -/** - * ab8540_chargalg_usb_pre_chg_en() - Enable/ disable USB pre-charge - * @di: pointer to the abx500_chargalg structure - * @enable: USB pre-charge enable/disable - * - * The USB USB pre-charge will be enable/ disable - */ -static int ab8540_chargalg_usb_pre_chg_en(struct abx500_chargalg *di, - bool enable) -{ - if (!di->usb_chg || !di->usb_chg->ops.pre_chg_enable) - return -ENXIO; - - return di->usb_chg->ops.pre_chg_enable(di->usb_chg, enable); -} - -/** - * abx500_chargalg_update_chg_curr() - Update charger current - * @di: pointer to the abx500_chargalg structure - * @iset: requested charger output current - * - * The charger output current will be updated for the charger - * that is currently in use - */ -static int abx500_chargalg_update_chg_curr(struct abx500_chargalg *di, - int iset) -{ - /* Check if charger exists and update current if charging */ - if (di->ac_chg && di->ac_chg->ops.update_curr && - di->chg_info.charger_type & AC_CHG) { - /* - * Select maximum of what both the charger - * and the battery supports - */ - if (di->ac_chg->max_out_curr) - iset = min(iset, di->ac_chg->max_out_curr); - - di->chg_info.ac_iset = iset; - - return di->ac_chg->ops.update_curr(di->ac_chg, iset); - } else if (di->usb_chg && di->usb_chg->ops.update_curr && - di->chg_info.charger_type & USB_CHG) { - /* - * Select maximum of what both the charger - * and the battery supports - */ - if (di->usb_chg->max_out_curr) - iset = min(iset, di->usb_chg->max_out_curr); - - di->chg_info.usb_iset = iset; - - return di->usb_chg->ops.update_curr(di->usb_chg, iset); - } - - return -ENXIO; -} - -/** - * abx500_chargalg_stop_charging() - Stop charging - * @di: pointer to the abx500_chargalg structure - * - * This function is called from any state where charging should be stopped. - * All charging is disabled and all status parameters and timers are changed - * accordingly - */ -static void abx500_chargalg_stop_charging(struct abx500_chargalg *di) -{ - abx500_chargalg_ac_en(di, false, 0, 0); - abx500_chargalg_usb_en(di, false, 0, 0); - abx500_chargalg_stop_safety_timer(di); - abx500_chargalg_stop_maintenance_timer(di); - di->charge_status = POWER_SUPPLY_STATUS_NOT_CHARGING; - di->maintenance_chg = false; - cancel_delayed_work(&di->chargalg_wd_work); - power_supply_changed(di->chargalg_psy); -} - -/** - * abx500_chargalg_hold_charging() - Pauses charging - * @di: pointer to the abx500_chargalg structure - * - * This function is called in the case where maintenance charging has been - * disabled and instead a battery voltage mode is entered to check when the - * battery voltage has reached a certain recharge voltage - */ -static void abx500_chargalg_hold_charging(struct abx500_chargalg *di) -{ - abx500_chargalg_ac_en(di, false, 0, 0); - abx500_chargalg_usb_en(di, false, 0, 0); - abx500_chargalg_stop_safety_timer(di); - abx500_chargalg_stop_maintenance_timer(di); - di->charge_status = POWER_SUPPLY_STATUS_CHARGING; - di->maintenance_chg = false; - cancel_delayed_work(&di->chargalg_wd_work); - power_supply_changed(di->chargalg_psy); -} - -/** - * abx500_chargalg_start_charging() - Start the charger - * @di: pointer to the abx500_chargalg structure - * @vset: requested charger output voltage - * @iset: requested charger output current - * - * A charger will be enabled depending on the requested charger type that was - * detected previously. - */ -static void abx500_chargalg_start_charging(struct abx500_chargalg *di, - int vset, int iset) -{ - switch (di->chg_info.charger_type) { - case AC_CHG: - dev_dbg(di->dev, - "AC parameters: Vset %d, Ich %d\n", vset, iset); - abx500_chargalg_usb_en(di, false, 0, 0); - abx500_chargalg_ac_en(di, true, vset, iset); - break; - - case USB_CHG: - dev_dbg(di->dev, - "USB parameters: Vset %d, Ich %d\n", vset, iset); - abx500_chargalg_ac_en(di, false, 0, 0); - abx500_chargalg_usb_en(di, true, vset, iset); - break; - - default: - dev_err(di->dev, "Unknown charger to charge from\n"); - break; - } -} - -/** - * abx500_chargalg_check_temp() - Check battery temperature ranges - * @di: pointer to the abx500_chargalg structure - * - * The battery temperature is checked against the predefined limits and the - * charge state is changed accordingly - */ -static void abx500_chargalg_check_temp(struct abx500_chargalg *di) -{ - if (di->batt_data.temp > (di->bm->temp_low + di->t_hyst_norm) && - di->batt_data.temp < (di->bm->temp_high - di->t_hyst_norm)) { - /* Temp OK! */ - di->events.btemp_underover = false; - di->events.btemp_lowhigh = false; - di->t_hyst_norm = 0; - di->t_hyst_lowhigh = 0; - } else { - if (((di->batt_data.temp >= di->bm->temp_high) && - (di->batt_data.temp < - (di->bm->temp_over - di->t_hyst_lowhigh))) || - ((di->batt_data.temp > - (di->bm->temp_under + di->t_hyst_lowhigh)) && - (di->batt_data.temp <= di->bm->temp_low))) { - /* TEMP minor!!!!! */ - di->events.btemp_underover = false; - di->events.btemp_lowhigh = true; - di->t_hyst_norm = di->bm->temp_hysteresis; - di->t_hyst_lowhigh = 0; - } else if (di->batt_data.temp <= di->bm->temp_under || - di->batt_data.temp >= di->bm->temp_over) { - /* TEMP major!!!!! */ - di->events.btemp_underover = true; - di->events.btemp_lowhigh = false; - di->t_hyst_norm = 0; - di->t_hyst_lowhigh = di->bm->temp_hysteresis; - } else { - /* Within hysteresis */ - dev_dbg(di->dev, "Within hysteresis limit temp: %d " - "hyst_lowhigh %d, hyst normal %d\n", - di->batt_data.temp, di->t_hyst_lowhigh, - di->t_hyst_norm); - } - } -} - -/** - * abx500_chargalg_check_charger_voltage() - Check charger voltage - * @di: pointer to the abx500_chargalg structure - * - * Charger voltage is checked against maximum limit - */ -static void abx500_chargalg_check_charger_voltage(struct abx500_chargalg *di) -{ - if (di->chg_info.usb_volt > di->bm->chg_params->usb_volt_max) - di->chg_info.usb_chg_ok = false; - else - di->chg_info.usb_chg_ok = true; - - if (di->chg_info.ac_volt > di->bm->chg_params->ac_volt_max) - di->chg_info.ac_chg_ok = false; - else - di->chg_info.ac_chg_ok = true; - -} - -/** - * abx500_chargalg_end_of_charge() - Check if end-of-charge criteria is fulfilled - * @di: pointer to the abx500_chargalg structure - * - * End-of-charge criteria is fulfilled when the battery voltage is above a - * certain limit and the battery current is below a certain limit for a - * predefined number of consecutive seconds. If true, the battery is full - */ -static void abx500_chargalg_end_of_charge(struct abx500_chargalg *di) -{ - if (di->charge_status == POWER_SUPPLY_STATUS_CHARGING && - di->charge_state == STATE_NORMAL && - !di->maintenance_chg && (di->batt_data.volt >= - di->bm->bat_type[di->bm->batt_id].termination_vol || - di->events.usb_cv_active || di->events.ac_cv_active) && - di->batt_data.avg_curr < - di->bm->bat_type[di->bm->batt_id].termination_curr && - di->batt_data.avg_curr > 0) { - if (++di->eoc_cnt >= EOC_COND_CNT) { - di->eoc_cnt = 0; - if ((di->chg_info.charger_type & USB_CHG) && - (di->usb_chg->power_path)) - ab8540_chargalg_usb_pp_en(di, true); - di->charge_status = POWER_SUPPLY_STATUS_FULL; - di->maintenance_chg = true; - dev_dbg(di->dev, "EOC reached!\n"); - power_supply_changed(di->chargalg_psy); - } else { - dev_dbg(di->dev, - " EOC limit reached for the %d" - " time, out of %d before EOC\n", - di->eoc_cnt, - EOC_COND_CNT); - } - } else { - di->eoc_cnt = 0; - } -} - -static void init_maxim_chg_curr(struct abx500_chargalg *di) -{ - di->ccm.original_iset = - di->bm->bat_type[di->bm->batt_id].normal_cur_lvl; - di->ccm.current_iset = - di->bm->bat_type[di->bm->batt_id].normal_cur_lvl; - di->ccm.test_delta_i = di->bm->maxi->charger_curr_step; - di->ccm.max_current = di->bm->maxi->chg_curr; - di->ccm.condition_cnt = di->bm->maxi->wait_cycles; - di->ccm.level = 0; -} - -/** - * abx500_chargalg_chg_curr_maxim - increases the charger current to - * compensate for the system load - * @di pointer to the abx500_chargalg structure - * - * This maximization function is used to raise the charger current to get the - * battery current as close to the optimal value as possible. The battery - * current during charging is affected by the system load - */ -static enum maxim_ret abx500_chargalg_chg_curr_maxim(struct abx500_chargalg *di) -{ - int delta_i; - - if (!di->bm->maxi->ena_maxi) - return MAXIM_RET_NOACTION; - - delta_i = di->ccm.original_iset - di->batt_data.inst_curr; - - if (di->events.vbus_collapsed) { - dev_dbg(di->dev, "Charger voltage has collapsed %d\n", - di->ccm.wait_cnt); - if (di->ccm.wait_cnt == 0) { - dev_dbg(di->dev, "lowering current\n"); - di->ccm.wait_cnt++; - di->ccm.condition_cnt = di->bm->maxi->wait_cycles; - di->ccm.max_current = - di->ccm.current_iset - di->ccm.test_delta_i; - di->ccm.current_iset = di->ccm.max_current; - di->ccm.level--; - return MAXIM_RET_CHANGE; - } else { - dev_dbg(di->dev, "waiting\n"); - /* Let's go in here twice before lowering curr again */ - di->ccm.wait_cnt = (di->ccm.wait_cnt + 1) % 3; - return MAXIM_RET_NOACTION; - } - } - - di->ccm.wait_cnt = 0; - - if ((di->batt_data.inst_curr > di->ccm.original_iset)) { - dev_dbg(di->dev, " Maximization Ibat (%dmA) too high" - " (limit %dmA) (current iset: %dmA)!\n", - di->batt_data.inst_curr, di->ccm.original_iset, - di->ccm.current_iset); - - if (di->ccm.current_iset == di->ccm.original_iset) - return MAXIM_RET_NOACTION; - - di->ccm.condition_cnt = di->bm->maxi->wait_cycles; - di->ccm.current_iset = di->ccm.original_iset; - di->ccm.level = 0; - - return MAXIM_RET_IBAT_TOO_HIGH; - } - - if (delta_i > di->ccm.test_delta_i && - (di->ccm.current_iset + di->ccm.test_delta_i) < - di->ccm.max_current) { - if (di->ccm.condition_cnt-- == 0) { - /* Increse the iset with cco.test_delta_i */ - di->ccm.condition_cnt = di->bm->maxi->wait_cycles; - di->ccm.current_iset += di->ccm.test_delta_i; - di->ccm.level++; - dev_dbg(di->dev, " Maximization needed, increase" - " with %d mA to %dmA (Optimal ibat: %d)" - " Level %d\n", - di->ccm.test_delta_i, - di->ccm.current_iset, - di->ccm.original_iset, - di->ccm.level); - return MAXIM_RET_CHANGE; - } else { - return MAXIM_RET_NOACTION; - } - } else { - di->ccm.condition_cnt = di->bm->maxi->wait_cycles; - return MAXIM_RET_NOACTION; - } -} - -static void handle_maxim_chg_curr(struct abx500_chargalg *di) -{ - enum maxim_ret ret; - int result; - - ret = abx500_chargalg_chg_curr_maxim(di); - switch (ret) { - case MAXIM_RET_CHANGE: - result = abx500_chargalg_update_chg_curr(di, - di->ccm.current_iset); - if (result) - dev_err(di->dev, "failed to set chg curr\n"); - break; - case MAXIM_RET_IBAT_TOO_HIGH: - result = abx500_chargalg_update_chg_curr(di, - di->bm->bat_type[di->bm->batt_id].normal_cur_lvl); - if (result) - dev_err(di->dev, "failed to set chg curr\n"); - break; - - case MAXIM_RET_NOACTION: - default: - /* Do nothing..*/ - break; - } -} - -static int abx500_chargalg_get_ext_psy_data(struct device *dev, void *data) -{ - struct power_supply *psy; - struct power_supply *ext = dev_get_drvdata(dev); - const char **supplicants = (const char **)ext->supplied_to; - struct abx500_chargalg *di; - union power_supply_propval ret; - int j; - bool capacity_updated = false; - - psy = (struct power_supply *)data; - di = power_supply_get_drvdata(psy); - /* For all psy where the driver name appears in any supplied_to */ - j = match_string(supplicants, ext->num_supplicants, psy->desc->name); - if (j < 0) - return 0; - - /* - * If external is not registering 'POWER_SUPPLY_PROP_CAPACITY' to its - * property because of handling that sysfs entry on its own, this is - * the place to get the battery capacity. - */ - if (!power_supply_get_property(ext, POWER_SUPPLY_PROP_CAPACITY, &ret)) { - di->batt_data.percent = ret.intval; - capacity_updated = true; - } - - /* Go through all properties for the psy */ - for (j = 0; j < ext->desc->num_properties; j++) { - enum power_supply_property prop; - prop = ext->desc->properties[j]; - - /* - * Initialize chargers if not already done. - * The ab8500_charger*/ - if (!di->ac_chg && - ext->desc->type == POWER_SUPPLY_TYPE_MAINS) - di->ac_chg = psy_to_ux500_charger(ext); - else if (!di->usb_chg && - ext->desc->type == POWER_SUPPLY_TYPE_USB) - di->usb_chg = psy_to_ux500_charger(ext); - - if (power_supply_get_property(ext, prop, &ret)) - continue; - switch (prop) { - case POWER_SUPPLY_PROP_PRESENT: - switch (ext->desc->type) { - case POWER_SUPPLY_TYPE_BATTERY: - /* Battery present */ - if (ret.intval) - di->events.batt_rem = false; - /* Battery removed */ - else - di->events.batt_rem = true; - break; - case POWER_SUPPLY_TYPE_MAINS: - /* AC disconnected */ - if (!ret.intval && - (di->chg_info.conn_chg & AC_CHG)) { - di->chg_info.prev_conn_chg = - di->chg_info.conn_chg; - di->chg_info.conn_chg &= ~AC_CHG; - } - /* AC connected */ - else if (ret.intval && - !(di->chg_info.conn_chg & AC_CHG)) { - di->chg_info.prev_conn_chg = - di->chg_info.conn_chg; - di->chg_info.conn_chg |= AC_CHG; - } - break; - case POWER_SUPPLY_TYPE_USB: - /* USB disconnected */ - if (!ret.intval && - (di->chg_info.conn_chg & USB_CHG)) { - di->chg_info.prev_conn_chg = - di->chg_info.conn_chg; - di->chg_info.conn_chg &= ~USB_CHG; - } - /* USB connected */ - else if (ret.intval && - !(di->chg_info.conn_chg & USB_CHG)) { - di->chg_info.prev_conn_chg = - di->chg_info.conn_chg; - di->chg_info.conn_chg |= USB_CHG; - } - break; - default: - break; - } - break; - - case POWER_SUPPLY_PROP_ONLINE: - switch (ext->desc->type) { - case POWER_SUPPLY_TYPE_BATTERY: - break; - case POWER_SUPPLY_TYPE_MAINS: - /* AC offline */ - if (!ret.intval && - (di->chg_info.online_chg & AC_CHG)) { - di->chg_info.prev_online_chg = - di->chg_info.online_chg; - di->chg_info.online_chg &= ~AC_CHG; - } - /* AC online */ - else if (ret.intval && - !(di->chg_info.online_chg & AC_CHG)) { - di->chg_info.prev_online_chg = - di->chg_info.online_chg; - di->chg_info.online_chg |= AC_CHG; - queue_delayed_work(di->chargalg_wq, - &di->chargalg_wd_work, 0); - } - break; - case POWER_SUPPLY_TYPE_USB: - /* USB offline */ - if (!ret.intval && - (di->chg_info.online_chg & USB_CHG)) { - di->chg_info.prev_online_chg = - di->chg_info.online_chg; - di->chg_info.online_chg &= ~USB_CHG; - } - /* USB online */ - else if (ret.intval && - !(di->chg_info.online_chg & USB_CHG)) { - di->chg_info.prev_online_chg = - di->chg_info.online_chg; - di->chg_info.online_chg |= USB_CHG; - queue_delayed_work(di->chargalg_wq, - &di->chargalg_wd_work, 0); - } - break; - default: - break; - } - break; - - case POWER_SUPPLY_PROP_HEALTH: - switch (ext->desc->type) { - case POWER_SUPPLY_TYPE_BATTERY: - break; - case POWER_SUPPLY_TYPE_MAINS: - switch (ret.intval) { - case POWER_SUPPLY_HEALTH_UNSPEC_FAILURE: - di->events.mainextchnotok = true; - di->events.main_thermal_prot = false; - di->events.main_ovv = false; - di->events.ac_wd_expired = false; - break; - case POWER_SUPPLY_HEALTH_DEAD: - di->events.ac_wd_expired = true; - di->events.mainextchnotok = false; - di->events.main_ovv = false; - di->events.main_thermal_prot = false; - break; - case POWER_SUPPLY_HEALTH_COLD: - case POWER_SUPPLY_HEALTH_OVERHEAT: - di->events.main_thermal_prot = true; - di->events.mainextchnotok = false; - di->events.main_ovv = false; - di->events.ac_wd_expired = false; - break; - case POWER_SUPPLY_HEALTH_OVERVOLTAGE: - di->events.main_ovv = true; - di->events.mainextchnotok = false; - di->events.main_thermal_prot = false; - di->events.ac_wd_expired = false; - break; - case POWER_SUPPLY_HEALTH_GOOD: - di->events.main_thermal_prot = false; - di->events.mainextchnotok = false; - di->events.main_ovv = false; - di->events.ac_wd_expired = false; - break; - default: - break; - } - break; - - case POWER_SUPPLY_TYPE_USB: - switch (ret.intval) { - case POWER_SUPPLY_HEALTH_UNSPEC_FAILURE: - di->events.usbchargernotok = true; - di->events.usb_thermal_prot = false; - di->events.vbus_ovv = false; - di->events.usb_wd_expired = false; - break; - case POWER_SUPPLY_HEALTH_DEAD: - di->events.usb_wd_expired = true; - di->events.usbchargernotok = false; - di->events.usb_thermal_prot = false; - di->events.vbus_ovv = false; - break; - case POWER_SUPPLY_HEALTH_COLD: - case POWER_SUPPLY_HEALTH_OVERHEAT: - di->events.usb_thermal_prot = true; - di->events.usbchargernotok = false; - di->events.vbus_ovv = false; - di->events.usb_wd_expired = false; - break; - case POWER_SUPPLY_HEALTH_OVERVOLTAGE: - di->events.vbus_ovv = true; - di->events.usbchargernotok = false; - di->events.usb_thermal_prot = false; - di->events.usb_wd_expired = false; - break; - case POWER_SUPPLY_HEALTH_GOOD: - di->events.usbchargernotok = false; - di->events.usb_thermal_prot = false; - di->events.vbus_ovv = false; - di->events.usb_wd_expired = false; - break; - default: - break; - } - default: - break; - } - break; - - case POWER_SUPPLY_PROP_VOLTAGE_NOW: - switch (ext->desc->type) { - case POWER_SUPPLY_TYPE_BATTERY: - di->batt_data.volt = ret.intval / 1000; - break; - case POWER_SUPPLY_TYPE_MAINS: - di->chg_info.ac_volt = ret.intval / 1000; - break; - case POWER_SUPPLY_TYPE_USB: - di->chg_info.usb_volt = ret.intval / 1000; - break; - default: - break; - } - break; - - case POWER_SUPPLY_PROP_VOLTAGE_AVG: - switch (ext->desc->type) { - case POWER_SUPPLY_TYPE_MAINS: - /* AVG is used to indicate when we are - * in CV mode */ - if (ret.intval) - di->events.ac_cv_active = true; - else - di->events.ac_cv_active = false; - - break; - case POWER_SUPPLY_TYPE_USB: - /* AVG is used to indicate when we are - * in CV mode */ - if (ret.intval) - di->events.usb_cv_active = true; - else - di->events.usb_cv_active = false; - - break; - default: - break; - } - break; - - case POWER_SUPPLY_PROP_TECHNOLOGY: - switch (ext->desc->type) { - case POWER_SUPPLY_TYPE_BATTERY: - if (ret.intval) - di->events.batt_unknown = false; - else - di->events.batt_unknown = true; - - break; - default: - break; - } - break; - - case POWER_SUPPLY_PROP_TEMP: - di->batt_data.temp = ret.intval / 10; - break; - - case POWER_SUPPLY_PROP_CURRENT_NOW: - switch (ext->desc->type) { - case POWER_SUPPLY_TYPE_MAINS: - di->chg_info.ac_curr = - ret.intval / 1000; - break; - case POWER_SUPPLY_TYPE_USB: - di->chg_info.usb_curr = - ret.intval / 1000; - break; - case POWER_SUPPLY_TYPE_BATTERY: - di->batt_data.inst_curr = ret.intval / 1000; - break; - default: - break; - } - break; - - case POWER_SUPPLY_PROP_CURRENT_AVG: - switch (ext->desc->type) { - case POWER_SUPPLY_TYPE_BATTERY: - di->batt_data.avg_curr = ret.intval / 1000; - break; - case POWER_SUPPLY_TYPE_USB: - if (ret.intval) - di->events.vbus_collapsed = true; - else - di->events.vbus_collapsed = false; - break; - default: - break; - } - break; - case POWER_SUPPLY_PROP_CAPACITY: - if (!capacity_updated) - di->batt_data.percent = ret.intval; - break; - default: - break; - } - } - return 0; -} - -/** - * abx500_chargalg_external_power_changed() - callback for power supply changes - * @psy: pointer to the structure power_supply - * - * This function is the entry point of the pointer external_power_changed - * of the structure power_supply. - * This function gets executed when there is a change in any external power - * supply that this driver needs to be notified of. - */ -static void abx500_chargalg_external_power_changed(struct power_supply *psy) -{ - struct abx500_chargalg *di = power_supply_get_drvdata(psy); - - /* - * Trigger execution of the algorithm instantly and read - * all power_supply properties there instead - */ - queue_work(di->chargalg_wq, &di->chargalg_work); -} - -/** - * abx500_chargalg_algorithm() - Main function for the algorithm - * @di: pointer to the abx500_chargalg structure - * - * This is the main control function for the charging algorithm. - * It is called periodically or when something happens that will - * trigger a state change - */ -static void abx500_chargalg_algorithm(struct abx500_chargalg *di) -{ - int charger_status; - int ret; - int curr_step_lvl; - - /* Collect data from all power_supply class devices */ - class_for_each_device(power_supply_class, NULL, - di->chargalg_psy, abx500_chargalg_get_ext_psy_data); - - abx500_chargalg_end_of_charge(di); - abx500_chargalg_check_temp(di); - abx500_chargalg_check_charger_voltage(di); - - charger_status = abx500_chargalg_check_charger_connection(di); - abx500_chargalg_check_current_step_status(di); - - if (is_ab8500(di->parent)) { - ret = abx500_chargalg_check_charger_enable(di); - if (ret < 0) - dev_err(di->dev, "Checking charger is enabled error" - ": Returned Value %d\n", ret); - } - - /* - * First check if we have a charger connected. - * Also we don't allow charging of unknown batteries if configured - * this way - */ - if (!charger_status || - (di->events.batt_unknown && !di->bm->chg_unknown_bat)) { - if (di->charge_state != STATE_HANDHELD) { - di->events.safety_timer_expired = false; - abx500_chargalg_state_to(di, STATE_HANDHELD_INIT); - } - } - - /* If suspended, we should not continue checking the flags */ - else if (di->charge_state == STATE_SUSPENDED_INIT || - di->charge_state == STATE_SUSPENDED) { - /* We don't do anything here, just don,t continue */ - } - - /* Safety timer expiration */ - else if (di->events.safety_timer_expired) { - if (di->charge_state != STATE_SAFETY_TIMER_EXPIRED) - abx500_chargalg_state_to(di, - STATE_SAFETY_TIMER_EXPIRED_INIT); - } - /* - * Check if any interrupts has occured - * that will prevent us from charging - */ - - /* Battery removed */ - else if (di->events.batt_rem) { - if (di->charge_state != STATE_BATT_REMOVED) - abx500_chargalg_state_to(di, STATE_BATT_REMOVED_INIT); - } - /* Main or USB charger not ok. */ - else if (di->events.mainextchnotok || di->events.usbchargernotok) { - /* - * If vbus_collapsed is set, we have to lower the charger - * current, which is done in the normal state below - */ - if (di->charge_state != STATE_CHG_NOT_OK && - !di->events.vbus_collapsed) - abx500_chargalg_state_to(di, STATE_CHG_NOT_OK_INIT); - } - /* VBUS, Main or VBAT OVV. */ - else if (di->events.vbus_ovv || - di->events.main_ovv || - di->events.batt_ovv || - !di->chg_info.usb_chg_ok || - !di->chg_info.ac_chg_ok) { - if (di->charge_state != STATE_OVV_PROTECT) - abx500_chargalg_state_to(di, STATE_OVV_PROTECT_INIT); - } - /* USB Thermal, stop charging */ - else if (di->events.main_thermal_prot || - di->events.usb_thermal_prot) { - if (di->charge_state != STATE_HW_TEMP_PROTECT) - abx500_chargalg_state_to(di, - STATE_HW_TEMP_PROTECT_INIT); - } - /* Battery temp over/under */ - else if (di->events.btemp_underover) { - if (di->charge_state != STATE_TEMP_UNDEROVER) - abx500_chargalg_state_to(di, - STATE_TEMP_UNDEROVER_INIT); - } - /* Watchdog expired */ - else if (di->events.ac_wd_expired || - di->events.usb_wd_expired) { - if (di->charge_state != STATE_WD_EXPIRED) - abx500_chargalg_state_to(di, STATE_WD_EXPIRED_INIT); - } - /* Battery temp high/low */ - else if (di->events.btemp_lowhigh) { - if (di->charge_state != STATE_TEMP_LOWHIGH) - abx500_chargalg_state_to(di, STATE_TEMP_LOWHIGH_INIT); - } - - dev_dbg(di->dev, - "[CHARGALG] Vb %d Ib_avg %d Ib_inst %d Tb %d Cap %d Maint %d " - "State %s Active_chg %d Chg_status %d AC %d USB %d " - "AC_online %d USB_online %d AC_CV %d USB_CV %d AC_I %d " - "USB_I %d AC_Vset %d AC_Iset %d USB_Vset %d USB_Iset %d\n", - di->batt_data.volt, - di->batt_data.avg_curr, - di->batt_data.inst_curr, - di->batt_data.temp, - di->batt_data.percent, - di->maintenance_chg, - states[di->charge_state], - di->chg_info.charger_type, - di->charge_status, - di->chg_info.conn_chg & AC_CHG, - di->chg_info.conn_chg & USB_CHG, - di->chg_info.online_chg & AC_CHG, - di->chg_info.online_chg & USB_CHG, - di->events.ac_cv_active, - di->events.usb_cv_active, - di->chg_info.ac_curr, - di->chg_info.usb_curr, - di->chg_info.ac_vset, - di->chg_info.ac_iset, - di->chg_info.usb_vset, - di->chg_info.usb_iset); - - switch (di->charge_state) { - case STATE_HANDHELD_INIT: - abx500_chargalg_stop_charging(di); - di->charge_status = POWER_SUPPLY_STATUS_DISCHARGING; - abx500_chargalg_state_to(di, STATE_HANDHELD); - /* Intentional fallthrough */ - - case STATE_HANDHELD: - break; - - case STATE_SUSPENDED_INIT: - if (di->susp_status.ac_suspended) - abx500_chargalg_ac_en(di, false, 0, 0); - if (di->susp_status.usb_suspended) - abx500_chargalg_usb_en(di, false, 0, 0); - abx500_chargalg_stop_safety_timer(di); - abx500_chargalg_stop_maintenance_timer(di); - di->charge_status = POWER_SUPPLY_STATUS_NOT_CHARGING; - di->maintenance_chg = false; - abx500_chargalg_state_to(di, STATE_SUSPENDED); - power_supply_changed(di->chargalg_psy); - /* Intentional fallthrough */ - - case STATE_SUSPENDED: - /* CHARGING is suspended */ - break; - - case STATE_BATT_REMOVED_INIT: - abx500_chargalg_stop_charging(di); - abx500_chargalg_state_to(di, STATE_BATT_REMOVED); - /* Intentional fallthrough */ - - case STATE_BATT_REMOVED: - if (!di->events.batt_rem) - abx500_chargalg_state_to(di, STATE_NORMAL_INIT); - break; - - case STATE_HW_TEMP_PROTECT_INIT: - abx500_chargalg_stop_charging(di); - abx500_chargalg_state_to(di, STATE_HW_TEMP_PROTECT); - /* Intentional fallthrough */ - - case STATE_HW_TEMP_PROTECT: - if (!di->events.main_thermal_prot && - !di->events.usb_thermal_prot) - abx500_chargalg_state_to(di, STATE_NORMAL_INIT); - break; - - case STATE_OVV_PROTECT_INIT: - abx500_chargalg_stop_charging(di); - abx500_chargalg_state_to(di, STATE_OVV_PROTECT); - /* Intentional fallthrough */ - - case STATE_OVV_PROTECT: - if (!di->events.vbus_ovv && - !di->events.main_ovv && - !di->events.batt_ovv && - di->chg_info.usb_chg_ok && - di->chg_info.ac_chg_ok) - abx500_chargalg_state_to(di, STATE_NORMAL_INIT); - break; - - case STATE_CHG_NOT_OK_INIT: - abx500_chargalg_stop_charging(di); - abx500_chargalg_state_to(di, STATE_CHG_NOT_OK); - /* Intentional fallthrough */ - - case STATE_CHG_NOT_OK: - if (!di->events.mainextchnotok && - !di->events.usbchargernotok) - abx500_chargalg_state_to(di, STATE_NORMAL_INIT); - break; - - case STATE_SAFETY_TIMER_EXPIRED_INIT: - abx500_chargalg_stop_charging(di); - abx500_chargalg_state_to(di, STATE_SAFETY_TIMER_EXPIRED); - /* Intentional fallthrough */ - - case STATE_SAFETY_TIMER_EXPIRED: - /* We exit this state when charger is removed */ - break; - - case STATE_NORMAL_INIT: - if ((di->chg_info.charger_type & USB_CHG) && - di->usb_chg->power_path) { - if (di->batt_data.volt > - (di->bm->fg_params->lowbat_threshold + - BAT_PLUS_MARGIN)) { - ab8540_chargalg_usb_pre_chg_en(di, false); - ab8540_chargalg_usb_pp_en(di, false); - } else { - ab8540_chargalg_usb_pp_en(di, true); - ab8540_chargalg_usb_pre_chg_en(di, true); - abx500_chargalg_state_to(di, - STATE_USB_PP_PRE_CHARGE); - break; - } - } - - if (di->curr_status.curr_step == CHARGALG_CURR_STEP_LOW) - abx500_chargalg_stop_charging(di); - else { - curr_step_lvl = di->bm->bat_type[ - di->bm->batt_id].normal_cur_lvl - * di->curr_status.curr_step - / CHARGALG_CURR_STEP_HIGH; - abx500_chargalg_start_charging(di, - di->bm->bat_type[di->bm->batt_id] - .normal_vol_lvl, curr_step_lvl); - } - - abx500_chargalg_state_to(di, STATE_NORMAL); - abx500_chargalg_start_safety_timer(di); - abx500_chargalg_stop_maintenance_timer(di); - init_maxim_chg_curr(di); - di->charge_status = POWER_SUPPLY_STATUS_CHARGING; - di->eoc_cnt = 0; - di->maintenance_chg = false; - power_supply_changed(di->chargalg_psy); - - break; - - case STATE_USB_PP_PRE_CHARGE: - if (di->batt_data.volt > - (di->bm->fg_params->lowbat_threshold + - BAT_PLUS_MARGIN)) - abx500_chargalg_state_to(di, STATE_NORMAL_INIT); - break; - - case STATE_NORMAL: - handle_maxim_chg_curr(di); - if (di->charge_status == POWER_SUPPLY_STATUS_FULL && - di->maintenance_chg) { - if (di->bm->no_maintenance) - abx500_chargalg_state_to(di, - STATE_WAIT_FOR_RECHARGE_INIT); - else - abx500_chargalg_state_to(di, - STATE_MAINTENANCE_A_INIT); - } - break; - - /* This state will be used when the maintenance state is disabled */ - case STATE_WAIT_FOR_RECHARGE_INIT: - abx500_chargalg_hold_charging(di); - abx500_chargalg_state_to(di, STATE_WAIT_FOR_RECHARGE); - /* Intentional fallthrough */ - - case STATE_WAIT_FOR_RECHARGE: - if (di->batt_data.percent <= - di->bm->bat_type[di->bm->batt_id]. - recharge_cap) - abx500_chargalg_state_to(di, STATE_NORMAL_INIT); - break; - - case STATE_MAINTENANCE_A_INIT: - abx500_chargalg_stop_safety_timer(di); - abx500_chargalg_start_maintenance_timer(di, - di->bm->bat_type[ - di->bm->batt_id].maint_a_chg_timer_h); - abx500_chargalg_start_charging(di, - di->bm->bat_type[ - di->bm->batt_id].maint_a_vol_lvl, - di->bm->bat_type[ - di->bm->batt_id].maint_a_cur_lvl); - abx500_chargalg_state_to(di, STATE_MAINTENANCE_A); - power_supply_changed(di->chargalg_psy); - /* Intentional fallthrough*/ - - case STATE_MAINTENANCE_A: - if (di->events.maintenance_timer_expired) { - abx500_chargalg_stop_maintenance_timer(di); - abx500_chargalg_state_to(di, STATE_MAINTENANCE_B_INIT); - } - break; - - case STATE_MAINTENANCE_B_INIT: - abx500_chargalg_start_maintenance_timer(di, - di->bm->bat_type[ - di->bm->batt_id].maint_b_chg_timer_h); - abx500_chargalg_start_charging(di, - di->bm->bat_type[ - di->bm->batt_id].maint_b_vol_lvl, - di->bm->bat_type[ - di->bm->batt_id].maint_b_cur_lvl); - abx500_chargalg_state_to(di, STATE_MAINTENANCE_B); - power_supply_changed(di->chargalg_psy); - /* Intentional fallthrough*/ - - case STATE_MAINTENANCE_B: - if (di->events.maintenance_timer_expired) { - abx500_chargalg_stop_maintenance_timer(di); - abx500_chargalg_state_to(di, STATE_NORMAL_INIT); - } - break; - - case STATE_TEMP_LOWHIGH_INIT: - abx500_chargalg_start_charging(di, - di->bm->bat_type[ - di->bm->batt_id].low_high_vol_lvl, - di->bm->bat_type[ - di->bm->batt_id].low_high_cur_lvl); - abx500_chargalg_stop_maintenance_timer(di); - di->charge_status = POWER_SUPPLY_STATUS_CHARGING; - abx500_chargalg_state_to(di, STATE_TEMP_LOWHIGH); - power_supply_changed(di->chargalg_psy); - /* Intentional fallthrough */ - - case STATE_TEMP_LOWHIGH: - if (!di->events.btemp_lowhigh) - abx500_chargalg_state_to(di, STATE_NORMAL_INIT); - break; - - case STATE_WD_EXPIRED_INIT: - abx500_chargalg_stop_charging(di); - abx500_chargalg_state_to(di, STATE_WD_EXPIRED); - /* Intentional fallthrough */ - - case STATE_WD_EXPIRED: - if (!di->events.ac_wd_expired && - !di->events.usb_wd_expired) - abx500_chargalg_state_to(di, STATE_NORMAL_INIT); - break; - - case STATE_TEMP_UNDEROVER_INIT: - abx500_chargalg_stop_charging(di); - abx500_chargalg_state_to(di, STATE_TEMP_UNDEROVER); - /* Intentional fallthrough */ - - case STATE_TEMP_UNDEROVER: - if (!di->events.btemp_underover) - abx500_chargalg_state_to(di, STATE_NORMAL_INIT); - break; - } - - /* Start charging directly if the new state is a charge state */ - if (di->charge_state == STATE_NORMAL_INIT || - di->charge_state == STATE_MAINTENANCE_A_INIT || - di->charge_state == STATE_MAINTENANCE_B_INIT) - queue_work(di->chargalg_wq, &di->chargalg_work); -} - -/** - * abx500_chargalg_periodic_work() - Periodic work for the algorithm - * @work: pointer to the work_struct structure - * - * Work queue function for the charging algorithm - */ -static void abx500_chargalg_periodic_work(struct work_struct *work) -{ - struct abx500_chargalg *di = container_of(work, - struct abx500_chargalg, chargalg_periodic_work.work); - - abx500_chargalg_algorithm(di); - - /* - * If a charger is connected then the battery has to be monitored - * frequently, else the work can be delayed. - */ - if (di->chg_info.conn_chg) - queue_delayed_work(di->chargalg_wq, - &di->chargalg_periodic_work, - di->bm->interval_charging * HZ); - else - queue_delayed_work(di->chargalg_wq, - &di->chargalg_periodic_work, - di->bm->interval_not_charging * HZ); -} - -/** - * abx500_chargalg_wd_work() - periodic work to kick the charger watchdog - * @work: pointer to the work_struct structure - * - * Work queue function for kicking the charger watchdog - */ -static void abx500_chargalg_wd_work(struct work_struct *work) -{ - int ret; - struct abx500_chargalg *di = container_of(work, - struct abx500_chargalg, chargalg_wd_work.work); - - dev_dbg(di->dev, "abx500_chargalg_wd_work\n"); - - ret = abx500_chargalg_kick_watchdog(di); - if (ret < 0) - dev_err(di->dev, "failed to kick watchdog\n"); - - queue_delayed_work(di->chargalg_wq, - &di->chargalg_wd_work, CHG_WD_INTERVAL); -} - -/** - * abx500_chargalg_work() - Work to run the charging algorithm instantly - * @work: pointer to the work_struct structure - * - * Work queue function for calling the charging algorithm - */ -static void abx500_chargalg_work(struct work_struct *work) -{ - struct abx500_chargalg *di = container_of(work, - struct abx500_chargalg, chargalg_work); - - abx500_chargalg_algorithm(di); -} - -/** - * abx500_chargalg_get_property() - get the chargalg properties - * @psy: pointer to the power_supply structure - * @psp: pointer to the power_supply_property structure - * @val: pointer to the power_supply_propval union - * - * This function gets called when an application tries to get the - * chargalg properties by reading the sysfs files. - * status: charging/discharging/full/unknown - * health: health of the battery - * Returns error code in case of failure else 0 on success - */ -static int abx500_chargalg_get_property(struct power_supply *psy, - enum power_supply_property psp, - union power_supply_propval *val) -{ - struct abx500_chargalg *di = power_supply_get_drvdata(psy); - - switch (psp) { - case POWER_SUPPLY_PROP_STATUS: - val->intval = di->charge_status; - break; - case POWER_SUPPLY_PROP_HEALTH: - if (di->events.batt_ovv) { - val->intval = POWER_SUPPLY_HEALTH_OVERVOLTAGE; - } else if (di->events.btemp_underover) { - if (di->batt_data.temp <= di->bm->temp_under) - val->intval = POWER_SUPPLY_HEALTH_COLD; - else - val->intval = POWER_SUPPLY_HEALTH_OVERHEAT; - } else if (di->charge_state == STATE_SAFETY_TIMER_EXPIRED || - di->charge_state == STATE_SAFETY_TIMER_EXPIRED_INIT) { - val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; - } else { - val->intval = POWER_SUPPLY_HEALTH_GOOD; - } - break; - default: - return -EINVAL; - } - return 0; -} - -/* Exposure to the sysfs interface */ - -static ssize_t abx500_chargalg_curr_step_show(struct abx500_chargalg *di, - char *buf) -{ - return sprintf(buf, "%d\n", di->curr_status.curr_step); -} - -static ssize_t abx500_chargalg_curr_step_store(struct abx500_chargalg *di, - const char *buf, size_t length) -{ - long int param; - int ret; - - ret = kstrtol(buf, 10, ¶m); - if (ret < 0) - return ret; - - di->curr_status.curr_step = param; - if (di->curr_status.curr_step >= CHARGALG_CURR_STEP_LOW && - di->curr_status.curr_step <= CHARGALG_CURR_STEP_HIGH) { - di->curr_status.curr_step_change = true; - queue_work(di->chargalg_wq, &di->chargalg_work); - } else - dev_info(di->dev, "Wrong current step\n" - "Enter 0. Disable AC/USB Charging\n" - "1--100. Set AC/USB charging current step\n" - "100. Enable AC/USB Charging\n"); - - return strlen(buf); -} - - -static ssize_t abx500_chargalg_en_show(struct abx500_chargalg *di, - char *buf) -{ - return sprintf(buf, "%d\n", - di->susp_status.ac_suspended && - di->susp_status.usb_suspended); -} - -static ssize_t abx500_chargalg_en_store(struct abx500_chargalg *di, - const char *buf, size_t length) -{ - long int param; - int ac_usb; - int ret; - - ret = kstrtol(buf, 10, ¶m); - if (ret < 0) - return ret; - - ac_usb = param; - switch (ac_usb) { - case 0: - /* Disable charging */ - di->susp_status.ac_suspended = true; - di->susp_status.usb_suspended = true; - di->susp_status.suspended_change = true; - /* Trigger a state change */ - queue_work(di->chargalg_wq, - &di->chargalg_work); - break; - case 1: - /* Enable AC Charging */ - di->susp_status.ac_suspended = false; - di->susp_status.suspended_change = true; - /* Trigger a state change */ - queue_work(di->chargalg_wq, - &di->chargalg_work); - break; - case 2: - /* Enable USB charging */ - di->susp_status.usb_suspended = false; - di->susp_status.suspended_change = true; - /* Trigger a state change */ - queue_work(di->chargalg_wq, - &di->chargalg_work); - break; - default: - dev_info(di->dev, "Wrong input\n" - "Enter 0. Disable AC/USB Charging\n" - "1. Enable AC charging\n" - "2. Enable USB Charging\n"); - }; - return strlen(buf); -} - -static struct abx500_chargalg_sysfs_entry abx500_chargalg_en_charger = - __ATTR(chargalg, 0644, abx500_chargalg_en_show, - abx500_chargalg_en_store); - -static struct abx500_chargalg_sysfs_entry abx500_chargalg_curr_step = - __ATTR(chargalg_curr_step, 0644, abx500_chargalg_curr_step_show, - abx500_chargalg_curr_step_store); - -static ssize_t abx500_chargalg_sysfs_show(struct kobject *kobj, - struct attribute *attr, char *buf) -{ - struct abx500_chargalg_sysfs_entry *entry = container_of(attr, - struct abx500_chargalg_sysfs_entry, attr); - - struct abx500_chargalg *di = container_of(kobj, - struct abx500_chargalg, chargalg_kobject); - - if (!entry->show) - return -EIO; - - return entry->show(di, buf); -} - -static ssize_t abx500_chargalg_sysfs_charger(struct kobject *kobj, - struct attribute *attr, const char *buf, size_t length) -{ - struct abx500_chargalg_sysfs_entry *entry = container_of(attr, - struct abx500_chargalg_sysfs_entry, attr); - - struct abx500_chargalg *di = container_of(kobj, - struct abx500_chargalg, chargalg_kobject); - - if (!entry->store) - return -EIO; - - return entry->store(di, buf, length); -} - -static struct attribute *abx500_chargalg_chg[] = { - &abx500_chargalg_en_charger.attr, - &abx500_chargalg_curr_step.attr, - NULL, -}; - -static const struct sysfs_ops abx500_chargalg_sysfs_ops = { - .show = abx500_chargalg_sysfs_show, - .store = abx500_chargalg_sysfs_charger, -}; - -static struct kobj_type abx500_chargalg_ktype = { - .sysfs_ops = &abx500_chargalg_sysfs_ops, - .default_attrs = abx500_chargalg_chg, -}; - -/** - * abx500_chargalg_sysfs_exit() - de-init of sysfs entry - * @di: pointer to the struct abx500_chargalg - * - * This function removes the entry in sysfs. - */ -static void abx500_chargalg_sysfs_exit(struct abx500_chargalg *di) -{ - kobject_del(&di->chargalg_kobject); -} - -/** - * abx500_chargalg_sysfs_init() - init of sysfs entry - * @di: pointer to the struct abx500_chargalg - * - * This function adds an entry in sysfs. - * Returns error code in case of failure else 0(on success) - */ -static int abx500_chargalg_sysfs_init(struct abx500_chargalg *di) -{ - int ret = 0; - - ret = kobject_init_and_add(&di->chargalg_kobject, - &abx500_chargalg_ktype, - NULL, "abx500_chargalg"); - if (ret < 0) - dev_err(di->dev, "failed to create sysfs entry\n"); - - return ret; -} -/* Exposure to the sysfs interface <> */ - -#if defined(CONFIG_PM) -static int abx500_chargalg_resume(struct platform_device *pdev) -{ - struct abx500_chargalg *di = platform_get_drvdata(pdev); - - /* Kick charger watchdog if charging (any charger online) */ - if (di->chg_info.online_chg) - queue_delayed_work(di->chargalg_wq, &di->chargalg_wd_work, 0); - - /* - * Run the charging algorithm directly to be sure we don't - * do it too seldom - */ - queue_delayed_work(di->chargalg_wq, &di->chargalg_periodic_work, 0); - - return 0; -} - -static int abx500_chargalg_suspend(struct platform_device *pdev, - pm_message_t state) -{ - struct abx500_chargalg *di = platform_get_drvdata(pdev); - - if (di->chg_info.online_chg) - cancel_delayed_work_sync(&di->chargalg_wd_work); - - cancel_delayed_work_sync(&di->chargalg_periodic_work); - - return 0; -} -#else -#define abx500_chargalg_suspend NULL -#define abx500_chargalg_resume NULL -#endif - -static int abx500_chargalg_remove(struct platform_device *pdev) -{ - struct abx500_chargalg *di = platform_get_drvdata(pdev); - - /* sysfs interface to enable/disbale charging from user space */ - abx500_chargalg_sysfs_exit(di); - - hrtimer_cancel(&di->safety_timer); - hrtimer_cancel(&di->maintenance_timer); - - cancel_delayed_work_sync(&di->chargalg_periodic_work); - cancel_delayed_work_sync(&di->chargalg_wd_work); - cancel_work_sync(&di->chargalg_work); - - /* Delete the work queue */ - destroy_workqueue(di->chargalg_wq); - - power_supply_unregister(di->chargalg_psy); - - return 0; -} - -static char *supply_interface[] = { - "ab8500_fg", -}; - -static const struct power_supply_desc abx500_chargalg_desc = { - .name = "abx500_chargalg", - .type = POWER_SUPPLY_TYPE_BATTERY, - .properties = abx500_chargalg_props, - .num_properties = ARRAY_SIZE(abx500_chargalg_props), - .get_property = abx500_chargalg_get_property, - .external_power_changed = abx500_chargalg_external_power_changed, -}; - -static int abx500_chargalg_probe(struct platform_device *pdev) -{ - struct device_node *np = pdev->dev.of_node; - struct abx500_bm_data *plat = pdev->dev.platform_data; - struct power_supply_config psy_cfg = {}; - struct abx500_chargalg *di; - int ret = 0; - - di = devm_kzalloc(&pdev->dev, sizeof(*di), GFP_KERNEL); - if (!di) { - dev_err(&pdev->dev, "%s no mem for ab8500_chargalg\n", __func__); - return -ENOMEM; - } - - if (!plat) { - dev_err(&pdev->dev, "no battery management data supplied\n"); - return -EINVAL; - } - di->bm = plat; - - if (np) { - ret = ab8500_bm_of_probe(&pdev->dev, np, di->bm); - if (ret) { - dev_err(&pdev->dev, "failed to get battery information\n"); - return ret; - } - } - - /* get device struct and parent */ - di->dev = &pdev->dev; - di->parent = dev_get_drvdata(pdev->dev.parent); - - psy_cfg.supplied_to = supply_interface; - psy_cfg.num_supplicants = ARRAY_SIZE(supply_interface); - psy_cfg.drv_data = di; - - /* Initilialize safety timer */ - hrtimer_init(&di->safety_timer, CLOCK_REALTIME, HRTIMER_MODE_ABS); - di->safety_timer.function = abx500_chargalg_safety_timer_expired; - - /* Initilialize maintenance timer */ - hrtimer_init(&di->maintenance_timer, CLOCK_REALTIME, HRTIMER_MODE_ABS); - di->maintenance_timer.function = - abx500_chargalg_maintenance_timer_expired; - - /* Create a work queue for the chargalg */ - di->chargalg_wq = - create_singlethread_workqueue("abx500_chargalg_wq"); - if (di->chargalg_wq == NULL) { - dev_err(di->dev, "failed to create work queue\n"); - return -ENOMEM; - } - - /* Init work for chargalg */ - INIT_DEFERRABLE_WORK(&di->chargalg_periodic_work, - abx500_chargalg_periodic_work); - INIT_DEFERRABLE_WORK(&di->chargalg_wd_work, - abx500_chargalg_wd_work); - - /* Init work for chargalg */ - INIT_WORK(&di->chargalg_work, abx500_chargalg_work); - - /* To detect charger at startup */ - di->chg_info.prev_conn_chg = -1; - - /* Register chargalg power supply class */ - di->chargalg_psy = power_supply_register(di->dev, &abx500_chargalg_desc, - &psy_cfg); - if (IS_ERR(di->chargalg_psy)) { - dev_err(di->dev, "failed to register chargalg psy\n"); - ret = PTR_ERR(di->chargalg_psy); - goto free_chargalg_wq; - } - - platform_set_drvdata(pdev, di); - - /* sysfs interface to enable/disable charging from user space */ - ret = abx500_chargalg_sysfs_init(di); - if (ret) { - dev_err(di->dev, "failed to create sysfs entry\n"); - goto free_psy; - } - di->curr_status.curr_step = CHARGALG_CURR_STEP_HIGH; - - /* Run the charging algorithm */ - queue_delayed_work(di->chargalg_wq, &di->chargalg_periodic_work, 0); - - dev_info(di->dev, "probe success\n"); - return ret; - -free_psy: - power_supply_unregister(di->chargalg_psy); -free_chargalg_wq: - destroy_workqueue(di->chargalg_wq); - return ret; -} - -static const struct of_device_id ab8500_chargalg_match[] = { - { .compatible = "stericsson,ab8500-chargalg", }, - { }, -}; - -static struct platform_driver abx500_chargalg_driver = { - .probe = abx500_chargalg_probe, - .remove = abx500_chargalg_remove, - .suspend = abx500_chargalg_suspend, - .resume = abx500_chargalg_resume, - .driver = { - .name = "ab8500-chargalg", - .of_match_table = ab8500_chargalg_match, - }, -}; - -module_platform_driver(abx500_chargalg_driver); - -MODULE_LICENSE("GPL v2"); -MODULE_AUTHOR("Johan Palsson, Karl Komierowski"); -MODULE_ALIAS("platform:abx500-chargalg"); -MODULE_DESCRIPTION("abx500 battery charging algorithm"); diff --git a/drivers/power/act8945a_charger.c b/drivers/power/act8945a_charger.c deleted file mode 100644 index b5c00e45741e..000000000000 --- a/drivers/power/act8945a_charger.c +++ /dev/null @@ -1,359 +0,0 @@ -/* - * Power supply driver for the Active-semi ACT8945A PMIC - * - * Copyright (C) 2015 Atmel Corporation - * - * Author: Wenyou Yang - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - * - */ -#include -#include -#include -#include -#include -#include - -static const char *act8945a_charger_model = "ACT8945A"; -static const char *act8945a_charger_manufacturer = "Active-semi"; - -/** - * ACT8945A Charger Register Map - */ - -/* 0x70: Reserved */ -#define ACT8945A_APCH_CFG 0x71 -#define ACT8945A_APCH_STATUS 0x78 -#define ACT8945A_APCH_CTRL 0x79 -#define ACT8945A_APCH_STATE 0x7A - -/* ACT8945A_APCH_CFG */ -#define APCH_CFG_OVPSET (0x3 << 0) -#define APCH_CFG_OVPSET_6V6 (0x0 << 0) -#define APCH_CFG_OVPSET_7V (0x1 << 0) -#define APCH_CFG_OVPSET_7V5 (0x2 << 0) -#define APCH_CFG_OVPSET_8V (0x3 << 0) -#define APCH_CFG_PRETIMO (0x3 << 2) -#define APCH_CFG_PRETIMO_40_MIN (0x0 << 2) -#define APCH_CFG_PRETIMO_60_MIN (0x1 << 2) -#define APCH_CFG_PRETIMO_80_MIN (0x2 << 2) -#define APCH_CFG_PRETIMO_DISABLED (0x3 << 2) -#define APCH_CFG_TOTTIMO (0x3 << 4) -#define APCH_CFG_TOTTIMO_3_HOUR (0x0 << 4) -#define APCH_CFG_TOTTIMO_4_HOUR (0x1 << 4) -#define APCH_CFG_TOTTIMO_5_HOUR (0x2 << 4) -#define APCH_CFG_TOTTIMO_DISABLED (0x3 << 4) -#define APCH_CFG_SUSCHG (0x1 << 7) - -#define APCH_STATUS_CHGDAT BIT(0) -#define APCH_STATUS_INDAT BIT(1) -#define APCH_STATUS_TEMPDAT BIT(2) -#define APCH_STATUS_TIMRDAT BIT(3) -#define APCH_STATUS_CHGSTAT BIT(4) -#define APCH_STATUS_INSTAT BIT(5) -#define APCH_STATUS_TEMPSTAT BIT(6) -#define APCH_STATUS_TIMRSTAT BIT(7) - -#define APCH_CTRL_CHGEOCOUT BIT(0) -#define APCH_CTRL_INDIS BIT(1) -#define APCH_CTRL_TEMPOUT BIT(2) -#define APCH_CTRL_TIMRPRE BIT(3) -#define APCH_CTRL_CHGEOCIN BIT(4) -#define APCH_CTRL_INCON BIT(5) -#define APCH_CTRL_TEMPIN BIT(6) -#define APCH_CTRL_TIMRTOT BIT(7) - -#define APCH_STATE_ACINSTAT (0x1 << 1) -#define APCH_STATE_CSTATE (0x3 << 4) -#define APCH_STATE_CSTATE_SHIFT 4 -#define APCH_STATE_CSTATE_DISABLED 0x00 -#define APCH_STATE_CSTATE_EOC 0x01 -#define APCH_STATE_CSTATE_FAST 0x02 -#define APCH_STATE_CSTATE_PRE 0x03 - -struct act8945a_charger { - struct regmap *regmap; - bool battery_temperature; -}; - -static int act8945a_get_charger_state(struct regmap *regmap, int *val) -{ - int ret; - unsigned int status, state; - - ret = regmap_read(regmap, ACT8945A_APCH_STATUS, &status); - if (ret < 0) - return ret; - - ret = regmap_read(regmap, ACT8945A_APCH_STATE, &state); - if (ret < 0) - return ret; - - state &= APCH_STATE_CSTATE; - state >>= APCH_STATE_CSTATE_SHIFT; - - if (state == APCH_STATE_CSTATE_EOC) { - if (status & APCH_STATUS_CHGDAT) - *val = POWER_SUPPLY_STATUS_FULL; - else - *val = POWER_SUPPLY_STATUS_NOT_CHARGING; - } else if ((state == APCH_STATE_CSTATE_FAST) || - (state == APCH_STATE_CSTATE_PRE)) { - *val = POWER_SUPPLY_STATUS_CHARGING; - } else { - *val = POWER_SUPPLY_STATUS_NOT_CHARGING; - } - - return 0; -} - -static int act8945a_get_charge_type(struct regmap *regmap, int *val) -{ - int ret; - unsigned int state; - - ret = regmap_read(regmap, ACT8945A_APCH_STATE, &state); - if (ret < 0) - return ret; - - state &= APCH_STATE_CSTATE; - state >>= APCH_STATE_CSTATE_SHIFT; - - switch (state) { - case APCH_STATE_CSTATE_PRE: - *val = POWER_SUPPLY_CHARGE_TYPE_TRICKLE; - break; - case APCH_STATE_CSTATE_FAST: - *val = POWER_SUPPLY_CHARGE_TYPE_FAST; - break; - case APCH_STATE_CSTATE_EOC: - case APCH_STATE_CSTATE_DISABLED: - default: - *val = POWER_SUPPLY_CHARGE_TYPE_NONE; - } - - return 0; -} - -static int act8945a_get_battery_health(struct act8945a_charger *charger, - struct regmap *regmap, int *val) -{ - int ret; - unsigned int status; - - ret = regmap_read(regmap, ACT8945A_APCH_STATUS, &status); - if (ret < 0) - return ret; - - if (charger->battery_temperature && !(status & APCH_STATUS_TEMPDAT)) - *val = POWER_SUPPLY_HEALTH_OVERHEAT; - else if (!(status & APCH_STATUS_INDAT)) - *val = POWER_SUPPLY_HEALTH_OVERVOLTAGE; - else if (status & APCH_STATUS_TIMRDAT) - *val = POWER_SUPPLY_HEALTH_SAFETY_TIMER_EXPIRE; - else - *val = POWER_SUPPLY_HEALTH_GOOD; - - return 0; -} - -static enum power_supply_property act8945a_charger_props[] = { - POWER_SUPPLY_PROP_STATUS, - POWER_SUPPLY_PROP_CHARGE_TYPE, - POWER_SUPPLY_PROP_TECHNOLOGY, - POWER_SUPPLY_PROP_HEALTH, - POWER_SUPPLY_PROP_MODEL_NAME, - POWER_SUPPLY_PROP_MANUFACTURER -}; - -static int act8945a_charger_get_property(struct power_supply *psy, - enum power_supply_property prop, - union power_supply_propval *val) -{ - struct act8945a_charger *charger = power_supply_get_drvdata(psy); - struct regmap *regmap = charger->regmap; - int ret = 0; - - switch (prop) { - case POWER_SUPPLY_PROP_STATUS: - ret = act8945a_get_charger_state(regmap, &val->intval); - break; - case POWER_SUPPLY_PROP_CHARGE_TYPE: - ret = act8945a_get_charge_type(regmap, &val->intval); - break; - case POWER_SUPPLY_PROP_TECHNOLOGY: - val->intval = POWER_SUPPLY_TECHNOLOGY_LION; - break; - case POWER_SUPPLY_PROP_HEALTH: - ret = act8945a_get_battery_health(charger, - regmap, &val->intval); - break; - case POWER_SUPPLY_PROP_MODEL_NAME: - val->strval = act8945a_charger_model; - break; - case POWER_SUPPLY_PROP_MANUFACTURER: - val->strval = act8945a_charger_manufacturer; - break; - default: - return -EINVAL; - } - - return ret; -} - -static const struct power_supply_desc act8945a_charger_desc = { - .name = "act8945a-charger", - .type = POWER_SUPPLY_TYPE_BATTERY, - .get_property = act8945a_charger_get_property, - .properties = act8945a_charger_props, - .num_properties = ARRAY_SIZE(act8945a_charger_props), -}; - -#define DEFAULT_TOTAL_TIME_OUT 3 -#define DEFAULT_PRE_TIME_OUT 40 -#define DEFAULT_INPUT_OVP_THRESHOLD 6600 - -static int act8945a_charger_config(struct device *dev, - struct act8945a_charger *charger) -{ - struct device_node *np = dev->of_node; - enum of_gpio_flags flags; - struct regmap *regmap = charger->regmap; - - u32 total_time_out; - u32 pre_time_out; - u32 input_voltage_threshold; - int chglev_pin; - - unsigned int value = 0; - - if (!np) { - dev_err(dev, "no charger of node\n"); - return -EINVAL; - } - - charger->battery_temperature = of_property_read_bool(np, - "active-semi,check-battery-temperature"); - - chglev_pin = of_get_named_gpio_flags(np, - "active-semi,chglev-gpios", 0, &flags); - - if (gpio_is_valid(chglev_pin)) { - gpio_set_value(chglev_pin, - ((flags == OF_GPIO_ACTIVE_LOW) ? 0 : 1)); - } - - if (of_property_read_u32(np, - "active-semi,input-voltage-threshold-microvolt", - &input_voltage_threshold)) - input_voltage_threshold = DEFAULT_INPUT_OVP_THRESHOLD; - - if (of_property_read_u32(np, - "active-semi,precondition-timeout", - &pre_time_out)) - pre_time_out = DEFAULT_PRE_TIME_OUT; - - if (of_property_read_u32(np, "active-semi,total-timeout", - &total_time_out)) - total_time_out = DEFAULT_TOTAL_TIME_OUT; - - switch (input_voltage_threshold) { - case 8000: - value |= APCH_CFG_OVPSET_8V; - break; - case 7500: - value |= APCH_CFG_OVPSET_7V5; - break; - case 7000: - value |= APCH_CFG_OVPSET_7V; - break; - case 6600: - default: - value |= APCH_CFG_OVPSET_6V6; - break; - } - - switch (pre_time_out) { - case 60: - value |= APCH_CFG_PRETIMO_60_MIN; - break; - case 80: - value |= APCH_CFG_PRETIMO_80_MIN; - break; - case 0: - value |= APCH_CFG_PRETIMO_DISABLED; - break; - case 40: - default: - value |= APCH_CFG_PRETIMO_40_MIN; - break; - } - - switch (total_time_out) { - case 4: - value |= APCH_CFG_TOTTIMO_4_HOUR; - break; - case 5: - value |= APCH_CFG_TOTTIMO_5_HOUR; - break; - case 0: - value |= APCH_CFG_TOTTIMO_DISABLED; - break; - case 3: - default: - value |= APCH_CFG_TOTTIMO_3_HOUR; - break; - } - - return regmap_write(regmap, ACT8945A_APCH_CFG, value); -} - -static int act8945a_charger_probe(struct platform_device *pdev) -{ - struct act8945a_charger *charger; - struct power_supply *psy; - struct power_supply_config psy_cfg = {}; - int ret; - - charger = devm_kzalloc(&pdev->dev, sizeof(*charger), GFP_KERNEL); - if (!charger) - return -ENOMEM; - - charger->regmap = dev_get_regmap(pdev->dev.parent, NULL); - if (!charger->regmap) { - dev_err(&pdev->dev, "Parent did not provide regmap\n"); - return -EINVAL; - } - - ret = act8945a_charger_config(pdev->dev.parent, charger); - if (ret) - return ret; - - psy_cfg.of_node = pdev->dev.parent->of_node; - psy_cfg.drv_data = charger; - - psy = devm_power_supply_register(&pdev->dev, - &act8945a_charger_desc, - &psy_cfg); - if (IS_ERR(psy)) { - dev_err(&pdev->dev, "failed to register power supply\n"); - return PTR_ERR(psy); - } - - return 0; -} - -static struct platform_driver act8945a_charger_driver = { - .driver = { - .name = "act8945a-charger", - }, - .probe = act8945a_charger_probe, -}; -module_platform_driver(act8945a_charger_driver); - -MODULE_DESCRIPTION("Active-semi ACT8945A ActivePath charger driver"); -MODULE_AUTHOR("Wenyou Yang "); -MODULE_LICENSE("GPL"); diff --git a/drivers/power/apm_power.c b/drivers/power/apm_power.c deleted file mode 100644 index 9d1a7fbcaed4..000000000000 --- a/drivers/power/apm_power.c +++ /dev/null @@ -1,376 +0,0 @@ -/* - * Copyright © 2007 Anton Vorontsov - * Copyright © 2007 Eugeny Boger - * - * Author: Eugeny Boger - * - * Use consistent with the GNU GPL is permitted, - * provided that this copyright notice is - * preserved in its entirety in all copies and derived works. - */ - -#include -#include -#include -#include - - -#define PSY_PROP(psy, prop, val) (power_supply_get_property(psy, \ - POWER_SUPPLY_PROP_##prop, val)) - -#define _MPSY_PROP(prop, val) (power_supply_get_property(main_battery, \ - prop, val)) - -#define MPSY_PROP(prop, val) _MPSY_PROP(POWER_SUPPLY_PROP_##prop, val) - -static DEFINE_MUTEX(apm_mutex); -static struct power_supply *main_battery; - -enum apm_source { - SOURCE_ENERGY, - SOURCE_CHARGE, - SOURCE_VOLTAGE, -}; - -struct find_bat_param { - struct power_supply *main; - struct power_supply *bat; - struct power_supply *max_charge_bat; - struct power_supply *max_energy_bat; - union power_supply_propval full; - int max_charge; - int max_energy; -}; - -static int __find_main_battery(struct device *dev, void *data) -{ - struct find_bat_param *bp = (struct find_bat_param *)data; - - bp->bat = dev_get_drvdata(dev); - - if (bp->bat->desc->use_for_apm) { - /* nice, we explicitly asked to report this battery. */ - bp->main = bp->bat; - return 1; - } - - if (!PSY_PROP(bp->bat, CHARGE_FULL_DESIGN, &bp->full) || - !PSY_PROP(bp->bat, CHARGE_FULL, &bp->full)) { - if (bp->full.intval > bp->max_charge) { - bp->max_charge_bat = bp->bat; - bp->max_charge = bp->full.intval; - } - } else if (!PSY_PROP(bp->bat, ENERGY_FULL_DESIGN, &bp->full) || - !PSY_PROP(bp->bat, ENERGY_FULL, &bp->full)) { - if (bp->full.intval > bp->max_energy) { - bp->max_energy_bat = bp->bat; - bp->max_energy = bp->full.intval; - } - } - return 0; -} - -static void find_main_battery(void) -{ - struct find_bat_param bp; - int error; - - memset(&bp, 0, sizeof(struct find_bat_param)); - main_battery = NULL; - bp.main = main_battery; - - error = class_for_each_device(power_supply_class, NULL, &bp, - __find_main_battery); - if (error) { - main_battery = bp.main; - return; - } - - if ((bp.max_energy_bat && bp.max_charge_bat) && - (bp.max_energy_bat != bp.max_charge_bat)) { - /* try guess battery with more capacity */ - if (!PSY_PROP(bp.max_charge_bat, VOLTAGE_MAX_DESIGN, - &bp.full)) { - if (bp.max_energy > bp.max_charge * bp.full.intval) - main_battery = bp.max_energy_bat; - else - main_battery = bp.max_charge_bat; - } else if (!PSY_PROP(bp.max_energy_bat, VOLTAGE_MAX_DESIGN, - &bp.full)) { - if (bp.max_charge > bp.max_energy / bp.full.intval) - main_battery = bp.max_charge_bat; - else - main_battery = bp.max_energy_bat; - } else { - /* give up, choice any */ - main_battery = bp.max_energy_bat; - } - } else if (bp.max_charge_bat) { - main_battery = bp.max_charge_bat; - } else if (bp.max_energy_bat) { - main_battery = bp.max_energy_bat; - } else { - /* give up, try the last if any */ - main_battery = bp.bat; - } -} - -static int do_calculate_time(int status, enum apm_source source) -{ - union power_supply_propval full; - union power_supply_propval empty; - union power_supply_propval cur; - union power_supply_propval I; - enum power_supply_property full_prop; - enum power_supply_property full_design_prop; - enum power_supply_property empty_prop; - enum power_supply_property empty_design_prop; - enum power_supply_property cur_avg_prop; - enum power_supply_property cur_now_prop; - - if (MPSY_PROP(CURRENT_AVG, &I)) { - /* if battery can't report average value, use momentary */ - if (MPSY_PROP(CURRENT_NOW, &I)) - return -1; - } - - if (!I.intval) - return 0; - - switch (source) { - case SOURCE_CHARGE: - full_prop = POWER_SUPPLY_PROP_CHARGE_FULL; - full_design_prop = POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN; - empty_prop = POWER_SUPPLY_PROP_CHARGE_EMPTY; - empty_design_prop = POWER_SUPPLY_PROP_CHARGE_EMPTY; - cur_avg_prop = POWER_SUPPLY_PROP_CHARGE_AVG; - cur_now_prop = POWER_SUPPLY_PROP_CHARGE_NOW; - break; - case SOURCE_ENERGY: - full_prop = POWER_SUPPLY_PROP_ENERGY_FULL; - full_design_prop = POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN; - empty_prop = POWER_SUPPLY_PROP_ENERGY_EMPTY; - empty_design_prop = POWER_SUPPLY_PROP_CHARGE_EMPTY; - cur_avg_prop = POWER_SUPPLY_PROP_ENERGY_AVG; - cur_now_prop = POWER_SUPPLY_PROP_ENERGY_NOW; - break; - case SOURCE_VOLTAGE: - full_prop = POWER_SUPPLY_PROP_VOLTAGE_MAX; - full_design_prop = POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN; - empty_prop = POWER_SUPPLY_PROP_VOLTAGE_MIN; - empty_design_prop = POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN; - cur_avg_prop = POWER_SUPPLY_PROP_VOLTAGE_AVG; - cur_now_prop = POWER_SUPPLY_PROP_VOLTAGE_NOW; - break; - default: - printk(KERN_ERR "Unsupported source: %d\n", source); - return -1; - } - - if (_MPSY_PROP(full_prop, &full)) { - /* if battery can't report this property, use design value */ - if (_MPSY_PROP(full_design_prop, &full)) - return -1; - } - - if (_MPSY_PROP(empty_prop, &empty)) { - /* if battery can't report this property, use design value */ - if (_MPSY_PROP(empty_design_prop, &empty)) - empty.intval = 0; - } - - if (_MPSY_PROP(cur_avg_prop, &cur)) { - /* if battery can't report average value, use momentary */ - if (_MPSY_PROP(cur_now_prop, &cur)) - return -1; - } - - if (status == POWER_SUPPLY_STATUS_CHARGING) - return ((cur.intval - full.intval) * 60L) / I.intval; - else - return -((cur.intval - empty.intval) * 60L) / I.intval; -} - -static int calculate_time(int status) -{ - int time; - - time = do_calculate_time(status, SOURCE_ENERGY); - if (time != -1) - return time; - - time = do_calculate_time(status, SOURCE_CHARGE); - if (time != -1) - return time; - - time = do_calculate_time(status, SOURCE_VOLTAGE); - if (time != -1) - return time; - - return -1; -} - -static int calculate_capacity(enum apm_source source) -{ - enum power_supply_property full_prop, empty_prop; - enum power_supply_property full_design_prop, empty_design_prop; - enum power_supply_property now_prop, avg_prop; - union power_supply_propval empty, full, cur; - int ret; - - switch (source) { - case SOURCE_CHARGE: - full_prop = POWER_SUPPLY_PROP_CHARGE_FULL; - empty_prop = POWER_SUPPLY_PROP_CHARGE_EMPTY; - full_design_prop = POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN; - empty_design_prop = POWER_SUPPLY_PROP_CHARGE_EMPTY_DESIGN; - now_prop = POWER_SUPPLY_PROP_CHARGE_NOW; - avg_prop = POWER_SUPPLY_PROP_CHARGE_AVG; - break; - case SOURCE_ENERGY: - full_prop = POWER_SUPPLY_PROP_ENERGY_FULL; - empty_prop = POWER_SUPPLY_PROP_ENERGY_EMPTY; - full_design_prop = POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN; - empty_design_prop = POWER_SUPPLY_PROP_ENERGY_EMPTY_DESIGN; - now_prop = POWER_SUPPLY_PROP_ENERGY_NOW; - avg_prop = POWER_SUPPLY_PROP_ENERGY_AVG; - break; - case SOURCE_VOLTAGE: - full_prop = POWER_SUPPLY_PROP_VOLTAGE_MAX; - empty_prop = POWER_SUPPLY_PROP_VOLTAGE_MIN; - full_design_prop = POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN; - empty_design_prop = POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN; - now_prop = POWER_SUPPLY_PROP_VOLTAGE_NOW; - avg_prop = POWER_SUPPLY_PROP_VOLTAGE_AVG; - break; - default: - printk(KERN_ERR "Unsupported source: %d\n", source); - return -1; - } - - if (_MPSY_PROP(full_prop, &full)) { - /* if battery can't report this property, use design value */ - if (_MPSY_PROP(full_design_prop, &full)) - return -1; - } - - if (_MPSY_PROP(avg_prop, &cur)) { - /* if battery can't report average value, use momentary */ - if (_MPSY_PROP(now_prop, &cur)) - return -1; - } - - if (_MPSY_PROP(empty_prop, &empty)) { - /* if battery can't report this property, use design value */ - if (_MPSY_PROP(empty_design_prop, &empty)) - empty.intval = 0; - } - - if (full.intval - empty.intval) - ret = ((cur.intval - empty.intval) * 100L) / - (full.intval - empty.intval); - else - return -1; - - if (ret > 100) - return 100; - else if (ret < 0) - return 0; - - return ret; -} - -static void apm_battery_apm_get_power_status(struct apm_power_info *info) -{ - union power_supply_propval status; - union power_supply_propval capacity, time_to_full, time_to_empty; - - mutex_lock(&apm_mutex); - find_main_battery(); - if (!main_battery) { - mutex_unlock(&apm_mutex); - return; - } - - /* status */ - - if (MPSY_PROP(STATUS, &status)) - status.intval = POWER_SUPPLY_STATUS_UNKNOWN; - - /* ac line status */ - - if ((status.intval == POWER_SUPPLY_STATUS_CHARGING) || - (status.intval == POWER_SUPPLY_STATUS_NOT_CHARGING) || - (status.intval == POWER_SUPPLY_STATUS_FULL)) - info->ac_line_status = APM_AC_ONLINE; - else - info->ac_line_status = APM_AC_OFFLINE; - - /* battery life (i.e. capacity, in percents) */ - - if (MPSY_PROP(CAPACITY, &capacity) == 0) { - info->battery_life = capacity.intval; - } else { - /* try calculate using energy */ - info->battery_life = calculate_capacity(SOURCE_ENERGY); - /* if failed try calculate using charge instead */ - if (info->battery_life == -1) - info->battery_life = calculate_capacity(SOURCE_CHARGE); - if (info->battery_life == -1) - info->battery_life = calculate_capacity(SOURCE_VOLTAGE); - } - - /* charging status */ - - if (status.intval == POWER_SUPPLY_STATUS_CHARGING) { - info->battery_status = APM_BATTERY_STATUS_CHARGING; - } else { - if (info->battery_life > 50) - info->battery_status = APM_BATTERY_STATUS_HIGH; - else if (info->battery_life > 5) - info->battery_status = APM_BATTERY_STATUS_LOW; - else - info->battery_status = APM_BATTERY_STATUS_CRITICAL; - } - info->battery_flag = info->battery_status; - - /* time */ - - info->units = APM_UNITS_MINS; - - if (status.intval == POWER_SUPPLY_STATUS_CHARGING) { - if (!MPSY_PROP(TIME_TO_FULL_AVG, &time_to_full) || - !MPSY_PROP(TIME_TO_FULL_NOW, &time_to_full)) - info->time = time_to_full.intval / 60; - else - info->time = calculate_time(status.intval); - } else { - if (!MPSY_PROP(TIME_TO_EMPTY_AVG, &time_to_empty) || - !MPSY_PROP(TIME_TO_EMPTY_NOW, &time_to_empty)) - info->time = time_to_empty.intval / 60; - else - info->time = calculate_time(status.intval); - } - - mutex_unlock(&apm_mutex); -} - -static int __init apm_battery_init(void) -{ - printk(KERN_INFO "APM Battery Driver\n"); - - apm_get_power_status = apm_battery_apm_get_power_status; - return 0; -} - -static void __exit apm_battery_exit(void) -{ - apm_get_power_status = NULL; -} - -module_init(apm_battery_init); -module_exit(apm_battery_exit); - -MODULE_AUTHOR("Eugeny Boger "); -MODULE_DESCRIPTION("APM emulation driver for battery monitoring class"); -MODULE_LICENSE("GPL"); diff --git a/drivers/power/axp20x_usb_power.c b/drivers/power/axp20x_usb_power.c deleted file mode 100644 index 6af6feb7058d..000000000000 --- a/drivers/power/axp20x_usb_power.c +++ /dev/null @@ -1,294 +0,0 @@ -/* - * AXP20x PMIC USB power supply status driver - * - * Copyright (C) 2015 Hans de Goede - * Copyright (C) 2014 Bruno Prémont - * - * 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#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 device_node *np; - 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: - if (of_device_is_compatible(power->np, - "x-powers,axp202-usb-power-supply")) { - val->intval = 100000; - } else { - val->intval = -1; /* No 100mA limit */ - } - 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; - } - - val->intval = POWER_SUPPLY_HEALTH_GOOD; - - if (of_device_is_compatible(power->np, - "x-powers,axp202-usb-power-supply")) { - 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; - 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 enum power_supply_property axp22x_usb_power_properties[] = { - POWER_SUPPLY_PROP_HEALTH, - POWER_SUPPLY_PROP_PRESENT, - POWER_SUPPLY_PROP_ONLINE, - POWER_SUPPLY_PROP_VOLTAGE_MIN, - POWER_SUPPLY_PROP_CURRENT_MAX, -}; - -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 const struct power_supply_desc axp22x_usb_power_desc = { - .name = "axp20x-usb", - .type = POWER_SUPPLY_TYPE_USB, - .properties = axp22x_usb_power_properties, - .num_properties = ARRAY_SIZE(axp22x_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 axp20x_irq_names[] = { "VBUS_PLUGIN", - "VBUS_REMOVAL", "VBUS_VALID", "VBUS_NOT_VALID", NULL }; - static const char * const axp22x_irq_names[] = { - "VBUS_PLUGIN", "VBUS_REMOVAL", NULL }; - static const char * const *irq_names; - const struct power_supply_desc *usb_power_desc; - 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->np = pdev->dev.of_node; - power->regmap = axp20x->regmap; - - if (of_device_is_compatible(power->np, - "x-powers,axp202-usb-power-supply")) { - /* 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; - - 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")) { - usb_power_desc = &axp22x_usb_power_desc; - irq_names = axp22x_irq_names; - } else { - dev_err(&pdev->dev, "Unsupported AXP variant: %ld\n", - axp20x->variant); - return -EINVAL; - } - - psy_cfg.of_node = pdev->dev.of_node; - psy_cfg.drv_data = power; - - power->supply = devm_power_supply_register(&pdev->dev, 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; 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_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" }, - { .compatible = "x-powers,axp221-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 "); -MODULE_DESCRIPTION("AXP20x PMIC USB power supply status driver"); -MODULE_LICENSE("GPL"); diff --git a/drivers/power/axp288_charger.c b/drivers/power/axp288_charger.c deleted file mode 100644 index 4030eeb7cf65..000000000000 --- a/drivers/power/axp288_charger.c +++ /dev/null @@ -1,970 +0,0 @@ -/* - * axp288_charger.c - X-power AXP288 PMIC Charger driver - * - * Copyright (C) 2014 Intel Corporation - * Author: Ramakrishna Pallala - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define PS_STAT_VBUS_TRIGGER (1 << 0) -#define PS_STAT_BAT_CHRG_DIR (1 << 2) -#define PS_STAT_VBAT_ABOVE_VHOLD (1 << 3) -#define PS_STAT_VBUS_VALID (1 << 4) -#define PS_STAT_VBUS_PRESENT (1 << 5) - -#define CHRG_STAT_BAT_SAFE_MODE (1 << 3) -#define CHRG_STAT_BAT_VALID (1 << 4) -#define CHRG_STAT_BAT_PRESENT (1 << 5) -#define CHRG_STAT_CHARGING (1 << 6) -#define CHRG_STAT_PMIC_OTP (1 << 7) - -#define VBUS_ISPOUT_CUR_LIM_MASK 0x03 -#define VBUS_ISPOUT_CUR_LIM_BIT_POS 0 -#define VBUS_ISPOUT_CUR_LIM_900MA 0x0 /* 900mA */ -#define VBUS_ISPOUT_CUR_LIM_1500MA 0x1 /* 1500mA */ -#define VBUS_ISPOUT_CUR_LIM_2000MA 0x2 /* 2000mA */ -#define VBUS_ISPOUT_CUR_NO_LIM 0x3 /* 2500mA */ -#define VBUS_ISPOUT_VHOLD_SET_MASK 0x31 -#define VBUS_ISPOUT_VHOLD_SET_BIT_POS 0x3 -#define VBUS_ISPOUT_VHOLD_SET_OFFSET 4000 /* 4000mV */ -#define VBUS_ISPOUT_VHOLD_SET_LSB_RES 100 /* 100mV */ -#define VBUS_ISPOUT_VHOLD_SET_4300MV 0x3 /* 4300mV */ -#define VBUS_ISPOUT_VBUS_PATH_DIS (1 << 7) - -#define CHRG_CCCV_CC_MASK 0xf /* 4 bits */ -#define CHRG_CCCV_CC_BIT_POS 0 -#define CHRG_CCCV_CC_OFFSET 200 /* 200mA */ -#define CHRG_CCCV_CC_LSB_RES 200 /* 200mA */ -#define CHRG_CCCV_ITERM_20P (1 << 4) /* 20% of CC */ -#define CHRG_CCCV_CV_MASK 0x60 /* 2 bits */ -#define CHRG_CCCV_CV_BIT_POS 5 -#define CHRG_CCCV_CV_4100MV 0x0 /* 4.10V */ -#define CHRG_CCCV_CV_4150MV 0x1 /* 4.15V */ -#define CHRG_CCCV_CV_4200MV 0x2 /* 4.20V */ -#define CHRG_CCCV_CV_4350MV 0x3 /* 4.35V */ -#define CHRG_CCCV_CHG_EN (1 << 7) - -#define CNTL2_CC_TIMEOUT_MASK 0x3 /* 2 bits */ -#define CNTL2_CC_TIMEOUT_OFFSET 6 /* 6 Hrs */ -#define CNTL2_CC_TIMEOUT_LSB_RES 2 /* 2 Hrs */ -#define CNTL2_CC_TIMEOUT_12HRS 0x3 /* 12 Hrs */ -#define CNTL2_CHGLED_TYPEB (1 << 4) -#define CNTL2_CHG_OUT_TURNON (1 << 5) -#define CNTL2_PC_TIMEOUT_MASK 0xC0 -#define CNTL2_PC_TIMEOUT_OFFSET 40 /* 40 mins */ -#define CNTL2_PC_TIMEOUT_LSB_RES 10 /* 10 mins */ -#define CNTL2_PC_TIMEOUT_70MINS 0x3 - -#define CHRG_ILIM_TEMP_LOOP_EN (1 << 3) -#define CHRG_VBUS_ILIM_MASK 0xf0 -#define CHRG_VBUS_ILIM_BIT_POS 4 -#define CHRG_VBUS_ILIM_100MA 0x0 /* 100mA */ -#define CHRG_VBUS_ILIM_500MA 0x1 /* 500mA */ -#define CHRG_VBUS_ILIM_900MA 0x2 /* 900mA */ -#define CHRG_VBUS_ILIM_1500MA 0x3 /* 1500mA */ -#define CHRG_VBUS_ILIM_2000MA 0x4 /* 2000mA */ -#define CHRG_VBUS_ILIM_2500MA 0x5 /* 2500mA */ -#define CHRG_VBUS_ILIM_3000MA 0x6 /* 3000mA */ - -#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 */ -#define CV_4150MV 4150 /* 4150mV */ -#define CV_4200MV 4200 /* 4200mV */ -#define CV_4350MV 4350 /* 4350mV */ - -#define CC_200MA 200 /* 200mA */ -#define CC_600MA 600 /* 600mA */ -#define CC_800MA 800 /* 800mA */ -#define CC_1000MA 1000 /* 1000mA */ -#define CC_1600MA 1600 /* 1600mA */ -#define CC_2000MA 2000 /* 2000mA */ - -#define ILIM_100MA 100 /* 100mA */ -#define ILIM_500MA 500 /* 500mA */ -#define ILIM_900MA 900 /* 900mA */ -#define ILIM_1500MA 1500 /* 1500mA */ -#define ILIM_2000MA 2000 /* 2000mA */ -#define ILIM_2500MA 2500 /* 2500mA */ -#define ILIM_3000MA 3000 /* 3000mA */ - -#define AXP288_EXTCON_DEV_NAME "axp288_extcon" - -enum { - VBUS_OV_IRQ = 0, - CHARGE_DONE_IRQ, - CHARGE_CHARGING_IRQ, - BAT_SAFE_QUIT_IRQ, - BAT_SAFE_ENTER_IRQ, - QCBTU_IRQ, - CBTU_IRQ, - QCBTO_IRQ, - CBTO_IRQ, - CHRG_INTR_END, -}; - -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]; - struct power_supply *psy_usb; - struct mutex lock; - - /* OTG/Host mode */ - struct { - struct work_struct work; - struct extcon_dev *cable; - struct notifier_block id_nb; - bool id_short; - } otg; - - /* SDP/CDP/DCP USB charging cable notifications */ - struct { - struct extcon_dev *edev; - bool connected; - enum power_supply_type chg_type; - struct notifier_block nb; - 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; -}; - -static inline int axp288_charger_set_cc(struct axp288_chrg_info *info, int cc) -{ - u8 reg_val; - int ret; - - if (cc < CHRG_CCCV_CC_OFFSET) - cc = CHRG_CCCV_CC_OFFSET; - else if (cc > info->max_cc) - cc = info->max_cc; - - reg_val = (cc - CHRG_CCCV_CC_OFFSET) / CHRG_CCCV_CC_LSB_RES; - cc = (reg_val * CHRG_CCCV_CC_LSB_RES) + CHRG_CCCV_CC_OFFSET; - reg_val = reg_val << CHRG_CCCV_CC_BIT_POS; - - ret = regmap_update_bits(info->regmap, - AXP20X_CHRG_CTRL1, - CHRG_CCCV_CC_MASK, reg_val); - if (ret >= 0) - info->cc = cc; - - return ret; -} - -static inline int axp288_charger_set_cv(struct axp288_chrg_info *info, int cv) -{ - u8 reg_val; - int ret; - - if (cv <= CV_4100MV) { - reg_val = CHRG_CCCV_CV_4100MV; - cv = CV_4100MV; - } else if (cv <= CV_4150MV) { - reg_val = CHRG_CCCV_CV_4150MV; - cv = CV_4150MV; - } else if (cv <= CV_4200MV) { - reg_val = CHRG_CCCV_CV_4200MV; - cv = CV_4200MV; - } else { - reg_val = CHRG_CCCV_CV_4350MV; - cv = CV_4350MV; - } - - reg_val = reg_val << CHRG_CCCV_CV_BIT_POS; - - ret = regmap_update_bits(info->regmap, - AXP20X_CHRG_CTRL1, - CHRG_CCCV_CV_MASK, reg_val); - - if (ret >= 0) - info->cv = cv; - - return ret; -} - -static inline int axp288_charger_set_vbus_inlmt(struct axp288_chrg_info *info, - int inlmt) -{ - int ret; - unsigned int val; - u8 reg_val; - - /* Read in limit register */ - ret = regmap_read(info->regmap, AXP20X_CHRG_BAK_CTRL, &val); - if (ret < 0) - goto set_inlmt_fail; - - if (inlmt <= ILIM_100MA) { - reg_val = CHRG_VBUS_ILIM_100MA; - inlmt = ILIM_100MA; - } else if (inlmt <= ILIM_500MA) { - reg_val = CHRG_VBUS_ILIM_500MA; - inlmt = ILIM_500MA; - } else if (inlmt <= ILIM_900MA) { - reg_val = CHRG_VBUS_ILIM_900MA; - inlmt = ILIM_900MA; - } else if (inlmt <= ILIM_1500MA) { - reg_val = CHRG_VBUS_ILIM_1500MA; - inlmt = ILIM_1500MA; - } else if (inlmt <= ILIM_2000MA) { - reg_val = CHRG_VBUS_ILIM_2000MA; - inlmt = ILIM_2000MA; - } else if (inlmt <= ILIM_2500MA) { - reg_val = CHRG_VBUS_ILIM_2500MA; - inlmt = ILIM_2500MA; - } else { - reg_val = CHRG_VBUS_ILIM_3000MA; - inlmt = ILIM_3000MA; - } - - reg_val = (val & ~CHRG_VBUS_ILIM_MASK) - | (reg_val << CHRG_VBUS_ILIM_BIT_POS); - ret = regmap_write(info->regmap, AXP20X_CHRG_BAK_CTRL, reg_val); - if (ret >= 0) - info->inlmt = inlmt; - else - dev_err(&info->pdev->dev, "charger BAK control %d\n", ret); - - -set_inlmt_fail: - return ret; -} - -static int axp288_charger_vbus_path_select(struct axp288_chrg_info *info, - bool enable) -{ - int ret; - - if (enable) - ret = regmap_update_bits(info->regmap, AXP20X_VBUS_IPSOUT_MGMT, - VBUS_ISPOUT_VBUS_PATH_DIS, 0); - else - ret = regmap_update_bits(info->regmap, AXP20X_VBUS_IPSOUT_MGMT, - VBUS_ISPOUT_VBUS_PATH_DIS, VBUS_ISPOUT_VBUS_PATH_DIS); - - if (ret < 0) - dev_err(&info->pdev->dev, "axp288 vbus path select %d\n", ret); - - - return ret; -} - -static int axp288_charger_enable_charger(struct axp288_chrg_info *info, - bool enable) -{ - int ret; - - if (enable) - ret = regmap_update_bits(info->regmap, AXP20X_CHRG_CTRL1, - CHRG_CCCV_CHG_EN, CHRG_CCCV_CHG_EN); - else - ret = regmap_update_bits(info->regmap, AXP20X_CHRG_CTRL1, - CHRG_CCCV_CHG_EN, 0); - if (ret < 0) - dev_err(&info->pdev->dev, "axp288 enable charger %d\n", ret); - else - info->is_charger_enabled = enable; - - return ret; -} - -static int axp288_charger_is_present(struct axp288_chrg_info *info) -{ - int ret, present = 0; - unsigned int val; - - ret = regmap_read(info->regmap, AXP20X_PWR_INPUT_STATUS, &val); - if (ret < 0) - return ret; - - if (val & PS_STAT_VBUS_PRESENT) - present = 1; - return present; -} - -static int axp288_charger_is_online(struct axp288_chrg_info *info) -{ - int ret, online = 0; - unsigned int val; - - ret = regmap_read(info->regmap, AXP20X_PWR_INPUT_STATUS, &val); - if (ret < 0) - return ret; - - if (val & PS_STAT_VBUS_VALID) - online = 1; - return online; -} - -static int axp288_get_charger_health(struct axp288_chrg_info *info) -{ - int ret, pwr_stat, chrg_stat; - int health = POWER_SUPPLY_HEALTH_UNKNOWN; - unsigned int val; - - ret = regmap_read(info->regmap, AXP20X_PWR_INPUT_STATUS, &val); - if ((ret < 0) || !(val & PS_STAT_VBUS_PRESENT)) - goto health_read_fail; - else - pwr_stat = val; - - ret = regmap_read(info->regmap, AXP20X_PWR_OP_MODE, &val); - if (ret < 0) - goto health_read_fail; - else - chrg_stat = val; - - if (!(pwr_stat & PS_STAT_VBUS_VALID)) - health = POWER_SUPPLY_HEALTH_DEAD; - else if (chrg_stat & CHRG_STAT_PMIC_OTP) - health = POWER_SUPPLY_HEALTH_OVERHEAT; - else if (chrg_stat & CHRG_STAT_BAT_SAFE_MODE) - health = POWER_SUPPLY_HEALTH_SAFETY_TIMER_EXPIRE; - else - health = POWER_SUPPLY_HEALTH_GOOD; - -health_read_fail: - return health; -} - -static int axp288_charger_usb_set_property(struct power_supply *psy, - enum power_supply_property psp, - const union power_supply_propval *val) -{ - struct axp288_chrg_info *info = power_supply_get_drvdata(psy); - int ret = 0; - int scaled_val; - - mutex_lock(&info->lock); - - switch (psp) { - case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: - scaled_val = min(val->intval, info->max_cc); - scaled_val = DIV_ROUND_CLOSEST(scaled_val, 1000); - ret = axp288_charger_set_cc(info, scaled_val); - if (ret < 0) - dev_warn(&info->pdev->dev, "set charge current failed\n"); - break; - case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: - scaled_val = min(val->intval, info->max_cv); - scaled_val = DIV_ROUND_CLOSEST(scaled_val, 1000); - ret = axp288_charger_set_cv(info, scaled_val); - if (ret < 0) - dev_warn(&info->pdev->dev, "set charge voltage failed\n"); - break; - default: - ret = -EINVAL; - } - - mutex_unlock(&info->lock); - return ret; -} - -static int axp288_charger_usb_get_property(struct power_supply *psy, - enum power_supply_property psp, - union power_supply_propval *val) -{ - struct axp288_chrg_info *info = power_supply_get_drvdata(psy); - int ret = 0; - - mutex_lock(&info->lock); - - switch (psp) { - case POWER_SUPPLY_PROP_PRESENT: - /* Check for OTG case first */ - if (info->otg.id_short) { - val->intval = 0; - break; - } - ret = axp288_charger_is_present(info); - if (ret < 0) - goto psy_get_prop_fail; - info->present = ret; - val->intval = info->present; - break; - case POWER_SUPPLY_PROP_ONLINE: - /* Check for OTG case first */ - if (info->otg.id_short) { - val->intval = 0; - break; - } - ret = axp288_charger_is_online(info); - if (ret < 0) - goto psy_get_prop_fail; - info->online = ret; - val->intval = info->online; - break; - case POWER_SUPPLY_PROP_HEALTH: - val->intval = axp288_get_charger_health(info); - break; - case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: - val->intval = info->cc * 1000; - break; - case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX: - val->intval = info->max_cc * 1000; - break; - case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: - val->intval = info->cv * 1000; - break; - case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX: - val->intval = info->max_cv * 1000; - break; - case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT: - val->intval = info->inlmt * 1000; - break; - default: - ret = -EINVAL; - goto psy_get_prop_fail; - } - -psy_get_prop_fail: - mutex_unlock(&info->lock); - return ret; -} - -static int axp288_charger_property_is_writeable(struct power_supply *psy, - enum power_supply_property psp) -{ - int ret; - - switch (psp) { - case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: - case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: - ret = 1; - break; - default: - ret = 0; - } - - return ret; -} - -static enum power_supply_property axp288_usb_props[] = { - POWER_SUPPLY_PROP_PRESENT, - POWER_SUPPLY_PROP_ONLINE, - POWER_SUPPLY_PROP_TYPE, - POWER_SUPPLY_PROP_HEALTH, - POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT, - POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX, - POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE, - POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX, - POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT, -}; - -static const struct power_supply_desc axp288_charger_desc = { - .name = "axp288_charger", - .type = POWER_SUPPLY_TYPE_USB, - .properties = axp288_usb_props, - .num_properties = ARRAY_SIZE(axp288_usb_props), - .get_property = axp288_charger_usb_get_property, - .set_property = axp288_charger_usb_set_property, - .property_is_writeable = axp288_charger_property_is_writeable, -}; - -static irqreturn_t axp288_charger_irq_thread_handler(int irq, void *dev) -{ - struct axp288_chrg_info *info = dev; - int i; - - for (i = 0; i < CHRG_INTR_END; i++) { - if (info->irq[i] == irq) - break; - } - - if (i >= CHRG_INTR_END) { - dev_warn(&info->pdev->dev, "spurious interrupt!!\n"); - return IRQ_NONE; - } - - switch (i) { - case VBUS_OV_IRQ: - dev_dbg(&info->pdev->dev, "VBUS Over Voltage INTR\n"); - break; - case CHARGE_DONE_IRQ: - dev_dbg(&info->pdev->dev, "Charging Done INTR\n"); - break; - case CHARGE_CHARGING_IRQ: - dev_dbg(&info->pdev->dev, "Start Charging IRQ\n"); - break; - case BAT_SAFE_QUIT_IRQ: - dev_dbg(&info->pdev->dev, - "Quit Safe Mode(restart timer) Charging IRQ\n"); - break; - case BAT_SAFE_ENTER_IRQ: - dev_dbg(&info->pdev->dev, - "Enter Safe Mode(timer expire) Charging IRQ\n"); - break; - case QCBTU_IRQ: - dev_dbg(&info->pdev->dev, - "Quit Battery Under Temperature(CHRG) INTR\n"); - break; - case CBTU_IRQ: - dev_dbg(&info->pdev->dev, - "Hit Battery Under Temperature(CHRG) INTR\n"); - break; - case QCBTO_IRQ: - dev_dbg(&info->pdev->dev, - "Quit Battery Over Temperature(CHRG) INTR\n"); - break; - case CBTO_IRQ: - dev_dbg(&info->pdev->dev, - "Hit Battery Over Temperature(CHRG) INTR\n"); - break; - default: - dev_warn(&info->pdev->dev, "Spurious Interrupt!!!\n"); - goto out; - } - - power_supply_changed(info->psy_usb); -out: - return IRQ_HANDLED; -} - -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; - - /* Determine cable/charger type */ - if (extcon_get_cable_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) { - 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) { - dev_dbg(&info->pdev->dev, "USB DCP charger is connected"); - info->cable.connected = true; - info->cable.chg_type = POWER_SUPPLY_TYPE_USB_DCP; - } else { - if (old_connected) - dev_dbg(&info->pdev->dev, "USB charger disconnected"); - info->cable.connected = false; - info->cable.chg_type = POWER_SUPPLY_TYPE_USB; - } - - /* Cable status changed */ - if (old_connected != info->cable.connected) - changed = true; - - if (!changed) - 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); - - } else if (!info->is_charger_enabled && info->cable.connected) { - switch (info->cable.chg_type) { - case POWER_SUPPLY_TYPE_USB: - current_limit = ILIM_500MA; - break; - case POWER_SUPPLY_TYPE_USB_CDP: - current_limit = ILIM_1500MA; - break; - case POWER_SUPPLY_TYPE_USB_DCP: - current_limit = ILIM_2000MA; - break; - default: - /* Unknown */ - current_limit = 0; - break; - } - - /* Set vbus current limit first, then enable charger */ - ret = axp288_charger_set_vbus_inlmt(info, current_limit); - if (ret < 0) { - 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); - } - } - - 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, - unsigned long event, void *param) -{ - struct axp288_chrg_info *info = - container_of(nb, struct axp288_chrg_info, cable.nb); - - schedule_work(&info->cable.work); - - return NOTIFY_OK; -} - -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; - - /* Disable VBUS path before enabling the 5V boost */ - ret = axp288_charger_vbus_path_select(info, !info->otg.id_short); - if (ret < 0) - dev_warn(&info->pdev->dev, "vbus path disable failed\n"); -} - -static int axp288_charger_handle_otg_evt(struct notifier_block *nb, - unsigned long event, void *param) -{ - 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) -{ - 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", - AXP20X_V_LTF_CHRG, 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", - AXP20X_V_HTF_CHRG, 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", - 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); - - /* 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", - AXP20X_CHRG_CTRL1, 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", - AXP20X_CC_CTRL, ret); - - /* Init charging current and voltage */ - info->max_cc = info->pdata->max_cc; - info->max_cv = info->pdata->max_cv; - - /* 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 { - /* Determine charge voltage */ - cv = (val & CHRG_CCCV_CV_MASK) >> CHRG_CCCV_CV_BIT_POS; - switch (cv) { - case CHRG_CCCV_CV_4100MV: - info->cv = CV_4100MV; - break; - case CHRG_CCCV_CV_4150MV: - info->cv = CV_4150MV; - break; - case CHRG_CCCV_CV_4200MV: - info->cv = CV_4200MV; - break; - case CHRG_CCCV_CV_4350MV: - info->cv = CV_4350MV; - break; - default: - info->cv = INT_MAX; - break; - } - - /* Determine charge current limit */ - cc = (ret & CHRG_CCCV_CC_MASK) >> CHRG_CCCV_CC_BIT_POS; - 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); - - ret = axp288_charger_set_cv(info, cv); - if (ret < 0) - dev_warn(&info->pdev->dev, - "error(%d) in setting CV\n", ret); - } -} - -static int axp288_charger_probe(struct platform_device *pdev) -{ - int ret, i, pirq; - struct axp288_chrg_info *info; - 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; - - 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.edev = extcon_get_extcon_dev(AXP288_EXTCON_DEV_NAME); - if (info->cable.edev == NULL) { - dev_dbg(&pdev->dev, "%s is not ready, probe deferred\n", - AXP288_EXTCON_DEV_NAME); - 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; - } - - platform_set_drvdata(pdev, info); - mutex_init(&info->lock); - - /* Register with power supply class */ - charger_cfg.drv_data = info; - info->psy_usb = power_supply_register(&pdev->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; - } - - /* 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); - - /* Register charger interrupts */ - for (i = 0; i < CHRG_INTR_END; i++) { - pirq = platform_get_irq(info->pdev, i); - info->irq[i] = regmap_irq_get_virq(info->regmap_irqc, pirq); - 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; - } - ret = devm_request_threaded_irq(&info->pdev->dev, info->irq[i], - NULL, axp288_charger_irq_thread_handler, - IRQF_ONESHOT, info->pdev->name, info); - if (ret) { - dev_err(&pdev->dev, "failed to request interrupt=%d\n", - info->irq[i]); - goto intr_reg_failed; - } - } - - 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 struct platform_driver axp288_charger_driver = { - .probe = axp288_charger_probe, - .remove = axp288_charger_remove, - .driver = { - .name = "axp288_charger", - }, -}; - -module_platform_driver(axp288_charger_driver); - -MODULE_AUTHOR("Ramakrishna Pallala "); -MODULE_DESCRIPTION("X-power AXP288 Charger Driver"); -MODULE_LICENSE("GPL v2"); diff --git a/drivers/power/axp288_fuel_gauge.c b/drivers/power/axp288_fuel_gauge.c deleted file mode 100644 index 50c0110d6b58..000000000000 --- a/drivers/power/axp288_fuel_gauge.c +++ /dev/null @@ -1,1155 +0,0 @@ -/* - * axp288_fuel_gauge.c - Xpower AXP288 PMIC Fuel Gauge Driver - * - * Copyright (C) 2014 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. - * - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define CHRG_STAT_BAT_SAFE_MODE (1 << 3) -#define CHRG_STAT_BAT_VALID (1 << 4) -#define CHRG_STAT_BAT_PRESENT (1 << 5) -#define CHRG_STAT_CHARGING (1 << 6) -#define CHRG_STAT_PMIC_OTP (1 << 7) - -#define CHRG_CCCV_CC_MASK 0xf /* 4 bits */ -#define CHRG_CCCV_CC_BIT_POS 0 -#define CHRG_CCCV_CC_OFFSET 200 /* 200mA */ -#define CHRG_CCCV_CC_LSB_RES 200 /* 200mA */ -#define CHRG_CCCV_ITERM_20P (1 << 4) /* 20% of CC */ -#define CHRG_CCCV_CV_MASK 0x60 /* 2 bits */ -#define CHRG_CCCV_CV_BIT_POS 5 -#define CHRG_CCCV_CV_4100MV 0x0 /* 4.10V */ -#define CHRG_CCCV_CV_4150MV 0x1 /* 4.15V */ -#define CHRG_CCCV_CV_4200MV 0x2 /* 4.20V */ -#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) -#define FG_CNTL_CAP_ADJ_EN (1 << 5) -#define FG_CNTL_CC_EN (1 << 6) -#define FG_CNTL_GAUGE_EN (1 << 7) - -#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) -#define FG_OCV_CAP_VAL_MASK 0x7F -#define FG_CC_CAP_VALID (1 << 7) -#define FG_CC_CAP_VAL_MASK 0x7F - -#define FG_LOW_CAP_THR1_MASK 0xf0 /* 5% tp 20% */ -#define FG_LOW_CAP_THR1_VAL 0xa0 /* 15 perc */ -#define FG_LOW_CAP_THR2_MASK 0x0f /* 0% to 15% */ -#define FG_LOW_CAP_WARN_THR 14 /* 14 perc */ -#define FG_LOW_CAP_CRIT_THR 4 /* 4 perc */ -#define FG_LOW_CAP_SHDN_THR 0 /* 0 perc */ - -#define STATUS_MON_DELAY_JIFFIES (HZ * 60) /*60 sec */ -#define NR_RETRY_CNT 3 -#define DEV_NAME "axp288_fuel_gauge" - -/* 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) -#define PROP_VOLT(a) ((a) * 1000) -#define PROP_CURR(a) ((a) * 1000) - -#define AXP288_FG_INTR_NUM 6 -enum { - QWBTU_IRQ = 0, - WBTU_IRQ, - QWBTO_IRQ, - WBTO_IRQ, - WL2_IRQ, - WL1_IRQ, -}; - -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; - struct delayed_work status_monitor; - struct dentry *debug_file; -}; - -static enum power_supply_property fuel_gauge_props[] = { - POWER_SUPPLY_PROP_STATUS, - 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) -{ - int ret, i; - unsigned int val; - - for (i = 0; i < NR_RETRY_CNT; i++) { - ret = regmap_read(info->regmap, reg, &val); - if (ret == -EBUSY) - continue; - else - break; - } - - if (ret < 0) - dev_err(&info->pdev->dev, "axp288 reg read err:%d\n", ret); - - return val; -} - -static int fuel_gauge_reg_writeb(struct axp288_fg_info *info, int reg, u8 val) -{ - int ret; - - ret = regmap_write(info->regmap, reg, (unsigned int)val); - - if (ret < 0) - dev_err(&info->pdev->dev, "axp288 reg write err:%d\n", ret); - - return ret; -} - -static int pmic_read_adc_val(const char *name, int *raw_val, - struct axp288_fg_info *info) -{ - int ret, val = 0; - struct iio_channel *indio_chan; - - indio_chan = iio_channel_get(NULL, name); - if (IS_ERR_OR_NULL(indio_chan)) { - ret = PTR_ERR(indio_chan); - goto exit; - } - ret = iio_read_channel_raw(indio_chan, &val); - if (ret < 0) { - dev_err(&info->pdev->dev, - "IIO channel read error: %x, %x\n", ret, val); - goto err_exit; - } - - dev_dbg(&info->pdev->dev, "adc raw val=%x\n", val); - *raw_val = val; - -err_exit: - iio_channel_release(indio_chan); -exit: - return ret; -} - -#ifdef CONFIG_DEBUG_FS -static int fuel_gauge_debug_show(struct seq_file *s, void *data) -{ - struct axp288_fg_info *info = s->private; - int raw_val, ret; - - seq_printf(s, " PWR_STATUS[%02x] : %02x\n", - AXP20X_PWR_INPUT_STATUS, - fuel_gauge_reg_readb(info, AXP20X_PWR_INPUT_STATUS)); - seq_printf(s, "PWR_OP_MODE[%02x] : %02x\n", - AXP20X_PWR_OP_MODE, - fuel_gauge_reg_readb(info, AXP20X_PWR_OP_MODE)); - seq_printf(s, " CHRG_CTRL1[%02x] : %02x\n", - AXP20X_CHRG_CTRL1, - fuel_gauge_reg_readb(info, AXP20X_CHRG_CTRL1)); - seq_printf(s, " VLTF[%02x] : %02x\n", - AXP20X_V_LTF_DISCHRG, - fuel_gauge_reg_readb(info, AXP20X_V_LTF_DISCHRG)); - seq_printf(s, " VHTF[%02x] : %02x\n", - AXP20X_V_HTF_DISCHRG, - fuel_gauge_reg_readb(info, AXP20X_V_HTF_DISCHRG)); - seq_printf(s, " CC_CTRL[%02x] : %02x\n", - AXP20X_CC_CTRL, - fuel_gauge_reg_readb(info, AXP20X_CC_CTRL)); - seq_printf(s, "BATTERY CAP[%02x] : %02x\n", - AXP20X_FG_RES, - fuel_gauge_reg_readb(info, AXP20X_FG_RES)); - seq_printf(s, " FG_RDC1[%02x] : %02x\n", - AXP288_FG_RDC1_REG, - fuel_gauge_reg_readb(info, AXP288_FG_RDC1_REG)); - 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", - 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", - 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", - 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)); - seq_printf(s, " FG_OCV_CAP[%02x] : %02x\n", - AXP288_FG_OCV_CAP_REG, - fuel_gauge_reg_readb(info, AXP288_FG_OCV_CAP_REG)); - seq_printf(s, " FG_CC_CAP[%02x] : %02x\n", - AXP288_FG_CC_CAP_REG, - fuel_gauge_reg_readb(info, AXP288_FG_CC_CAP_REG)); - seq_printf(s, " FG_LOW_CAP[%02x] : %02x\n", - AXP288_FG_LOW_CAP_REG, - fuel_gauge_reg_readb(info, AXP288_FG_LOW_CAP_REG)); - seq_printf(s, "TUNING_CTL0[%02x] : %02x\n", - AXP288_FG_TUNE0, - fuel_gauge_reg_readb(info, AXP288_FG_TUNE0)); - seq_printf(s, "TUNING_CTL1[%02x] : %02x\n", - AXP288_FG_TUNE1, - fuel_gauge_reg_readb(info, AXP288_FG_TUNE1)); - seq_printf(s, "TUNING_CTL2[%02x] : %02x\n", - AXP288_FG_TUNE2, - fuel_gauge_reg_readb(info, AXP288_FG_TUNE2)); - seq_printf(s, "TUNING_CTL3[%02x] : %02x\n", - AXP288_FG_TUNE3, - fuel_gauge_reg_readb(info, AXP288_FG_TUNE3)); - seq_printf(s, "TUNING_CTL4[%02x] : %02x\n", - AXP288_FG_TUNE4, - fuel_gauge_reg_readb(info, AXP288_FG_TUNE4)); - seq_printf(s, "TUNING_CTL5[%02x] : %02x\n", - AXP288_FG_TUNE5, - fuel_gauge_reg_readb(info, AXP288_FG_TUNE5)); - - ret = pmic_read_adc_val("axp288-batt-temp", &raw_val, info); - if (ret >= 0) - seq_printf(s, "axp288-batttemp : %d\n", raw_val); - ret = pmic_read_adc_val("axp288-pmic-temp", &raw_val, info); - if (ret >= 0) - seq_printf(s, "axp288-pmictemp : %d\n", raw_val); - ret = pmic_read_adc_val("axp288-system-temp", &raw_val, info); - if (ret >= 0) - seq_printf(s, "axp288-systtemp : %d\n", raw_val); - ret = pmic_read_adc_val("axp288-chrg-curr", &raw_val, info); - if (ret >= 0) - seq_printf(s, "axp288-chrgcurr : %d\n", raw_val); - ret = pmic_read_adc_val("axp288-chrg-d-curr", &raw_val, info); - if (ret >= 0) - seq_printf(s, "axp288-dchrgcur : %d\n", raw_val); - ret = pmic_read_adc_val("axp288-batt-volt", &raw_val, info); - if (ret >= 0) - seq_printf(s, "axp288-battvolt : %d\n", raw_val); - - return 0; -} - -static int debug_open(struct inode *inode, struct file *file) -{ - return single_open(file, fuel_gauge_debug_show, inode->i_private); -} - -static const struct file_operations fg_debug_fops = { - .open = debug_open, - .read = seq_read, - .llseek = seq_lseek, - .release = single_release, -}; - -static void fuel_gauge_create_debugfs(struct axp288_fg_info *info) -{ - info->debug_file = debugfs_create_file("fuelgauge", 0666, NULL, - info, &fg_debug_fops); -} - -static void fuel_gauge_remove_debugfs(struct axp288_fg_info *info) -{ - debugfs_remove(info->debug_file); -} -#else -static inline void fuel_gauge_create_debugfs(struct axp288_fg_info *info) -{ -} -static inline void fuel_gauge_remove_debugfs(struct axp288_fg_info *info) -{ -} -#endif - -static void fuel_gauge_get_status(struct axp288_fg_info *info) -{ - int pwr_stat, ret; - int charge, discharge; - - pwr_stat = fuel_gauge_reg_readb(info, AXP20X_PWR_INPUT_STATUS); - if (pwr_stat < 0) { - dev_err(&info->pdev->dev, - "PWR STAT read failed:%d\n", pwr_stat); - return; - } - ret = pmic_read_adc_val("axp288-chrg-curr", &charge, info); - if (ret < 0) { - dev_err(&info->pdev->dev, - "ADC charge current read failed:%d\n", ret); - return; - } - ret = pmic_read_adc_val("axp288-chrg-d-curr", &discharge, info); - if (ret < 0) { - dev_err(&info->pdev->dev, - "ADC discharge current read failed:%d\n", ret); - return; - } - - if (charge > 0) - info->status = POWER_SUPPLY_STATUS_CHARGING; - else if (discharge > 0) - info->status = POWER_SUPPLY_STATUS_DISCHARGING; - else { - if (pwr_stat & CHRG_STAT_BAT_PRESENT) - info->status = POWER_SUPPLY_STATUS_FULL; - else - info->status = POWER_SUPPLY_STATUS_NOT_CHARGING; - } -} - -static int fuel_gauge_get_vbatt(struct axp288_fg_info *info, int *vbatt) -{ - int ret = 0, raw_val; - - ret = pmic_read_adc_val("axp288-batt-volt", &raw_val, info); - if (ret < 0) - goto vbatt_read_fail; - - *vbatt = VOLTAGE_FROM_ADC(raw_val); -vbatt_read_fail: - return ret; -} - -static int fuel_gauge_get_current(struct axp288_fg_info *info, int *cur) -{ - int ret, value = 0; - int charge, discharge; - - ret = pmic_read_adc_val("axp288-chrg-curr", &charge, info); - if (ret < 0) - goto current_read_fail; - ret = pmic_read_adc_val("axp288-chrg-d-curr", &discharge, info); - if (ret < 0) - goto current_read_fail; - - if (charge > 0) - value = charge; - else if (discharge > 0) - value = -1 * discharge; - - *cur = value; -current_read_fail: - 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; - - ret = fuel_gauge_reg_readb(info, AXP288_FG_OCVL_REG); - if (ret < 0) - goto vocv_read_fail; - value |= (ret & 0xf); - - *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; - - ret = fuel_gauge_get_vocv(info, &vocv); - if (ret < 0) - goto health_read_fail; - - if (vocv > info->pdata->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; - -health_read_fail: - 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) -{ - struct axp288_fg_info *info = power_supply_get_drvdata(ps); - int ret = 0, value; - - mutex_lock(&info->lock); - switch (prop) { - case POWER_SUPPLY_PROP_STATUS: - fuel_gauge_get_status(info); - val->intval = info->status; - break; - case POWER_SUPPLY_PROP_HEALTH: - val->intval = fuel_gauge_battery_health(info); - break; - case POWER_SUPPLY_PROP_VOLTAGE_NOW: - ret = fuel_gauge_get_vbatt(info, &value); - if (ret < 0) - goto fuel_gauge_read_err; - val->intval = PROP_VOLT(value); - break; - case POWER_SUPPLY_PROP_VOLTAGE_OCV: - ret = fuel_gauge_get_vocv(info, &value); - if (ret < 0) - goto fuel_gauge_read_err; - val->intval = PROP_VOLT(value); - break; - case POWER_SUPPLY_PROP_CURRENT_NOW: - ret = fuel_gauge_get_current(info, &value); - if (ret < 0) - goto fuel_gauge_read_err; - val->intval = PROP_CURR(value); - break; - case POWER_SUPPLY_PROP_PRESENT: - ret = fuel_gauge_reg_readb(info, AXP20X_PWR_OP_MODE); - if (ret < 0) - goto fuel_gauge_read_err; - - if (ret & CHRG_STAT_BAT_PRESENT) - val->intval = 1; - else - val->intval = 0; - break; - case POWER_SUPPLY_PROP_CAPACITY: - ret = fuel_gauge_reg_readb(info, AXP20X_FG_RES); - if (ret < 0) - goto fuel_gauge_read_err; - - if (!(ret & FG_REP_CAP_VALID)) - dev_err(&info->pdev->dev, - "capacity measurement not valid\n"); - val->intval = (ret & FG_REP_CAP_VAL_MASK); - break; - case POWER_SUPPLY_PROP_CAPACITY_ALERT_MIN: - ret = fuel_gauge_reg_readb(info, AXP288_FG_LOW_CAP_REG); - if (ret < 0) - 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); - 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; - break; - case POWER_SUPPLY_PROP_CHARGE_FULL: - ret = fuel_gauge_reg_readb(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); - 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; - break; - default: - mutex_unlock(&info->lock); - return -EINVAL; - } - - mutex_unlock(&info->lock); - return 0; - -fuel_gauge_read_err: - mutex_unlock(&info->lock); - return ret; -} - -static int fuel_gauge_set_property(struct power_supply *ps, - enum power_supply_property prop, - const union power_supply_propval *val) -{ - struct axp288_fg_info *info = power_supply_get_drvdata(ps); - int ret = 0; - - 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; - break; - } - ret = fuel_gauge_reg_readb(info, AXP288_FG_LOW_CAP_REG); - if (ret < 0) - break; - ret &= 0xf0; - ret |= (val->intval & 0xf); - ret = fuel_gauge_reg_writeb(info, AXP288_FG_LOW_CAP_REG, ret); - break; - default: - ret = -EINVAL; - break; - } - - mutex_unlock(&info->lock); - return ret; -} - -static int fuel_gauge_property_is_writeable(struct power_supply *psy, - enum power_supply_property psp) -{ - 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; - default: - ret = 0; - } - - return ret; -} - -static void fuel_gauge_status_monitor(struct work_struct *work) -{ - struct axp288_fg_info *info = container_of(work, - struct axp288_fg_info, status_monitor.work); - - fuel_gauge_get_status(info); - power_supply_changed(info->bat); - schedule_delayed_work(&info->status_monitor, STATUS_MON_DELAY_JIFFIES); -} - -static irqreturn_t fuel_gauge_thread_handler(int irq, void *dev) -{ - struct axp288_fg_info *info = dev; - int i; - - for (i = 0; i < AXP288_FG_INTR_NUM; i++) { - if (info->irq[i] == irq) - break; - } - - if (i >= AXP288_FG_INTR_NUM) { - dev_warn(&info->pdev->dev, "spurious interrupt!!\n"); - return IRQ_NONE; - } - - switch (i) { - case QWBTU_IRQ: - dev_info(&info->pdev->dev, - "Quit Battery under temperature in work mode IRQ (QWBTU)\n"); - break; - case WBTU_IRQ: - dev_info(&info->pdev->dev, - "Battery under temperature in work mode IRQ (WBTU)\n"); - break; - case QWBTO_IRQ: - dev_info(&info->pdev->dev, - "Quit Battery over temperature in work mode IRQ (QWBTO)\n"); - break; - case WBTO_IRQ: - dev_info(&info->pdev->dev, - "Battery over temperature in work mode IRQ (WBTO)\n"); - break; - case WL2_IRQ: - dev_info(&info->pdev->dev, "Low Batt Warning(2) INTR\n"); - break; - case WL1_IRQ: - dev_info(&info->pdev->dev, "Low Batt Warning(1) INTR\n"); - break; - default: - dev_warn(&info->pdev->dev, "Spurious Interrupt!!!\n"); - } - - power_supply_changed(info->bat); - return IRQ_HANDLED; -} - -static void fuel_gauge_external_power_changed(struct power_supply *psy) -{ - struct axp288_fg_info *info = power_supply_get_drvdata(psy); - - power_supply_changed(info->bat); -} - -static const struct power_supply_desc fuel_gauge_desc = { - .name = DEV_NAME, - .type = POWER_SUPPLY_TYPE_BATTERY, - .properties = fuel_gauge_props, - .num_properties = ARRAY_SIZE(fuel_gauge_props), - .get_property = fuel_gauge_get_property, - .set_property = fuel_gauge_set_property, - .property_is_writeable = fuel_gauge_property_is_writeable, - .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; - - for (i = 0; i < AXP288_FG_INTR_NUM; i++) { - pirq = platform_get_irq(info->pdev, i); - info->irq[i] = regmap_irq_get_virq(info->regmap_irqc, pirq); - if (info->irq[i] < 0) { - dev_warn(&info->pdev->dev, - "regmap_irq get virq failed for IRQ %d: %d\n", - pirq, info->irq[i]); - info->irq[i] = -1; - goto intr_failed; - } - ret = request_threaded_irq(info->irq[i], - NULL, fuel_gauge_thread_handler, - IRQF_ONESHOT, DEV_NAME, info); - if (ret) { - dev_warn(&info->pdev->dev, - "request irq failed for IRQ %d: %d\n", - pirq, info->irq[i]); - info->irq[i] = -1; - goto intr_failed; - } else { - dev_info(&info->pdev->dev, "HW IRQ %d -> VIRQ %d\n", - pirq, info->irq[i]); - } - } - return; - -intr_failed: - for (; i > 0; i--) { - free_irq(info->irq[i - 1], info); - info->irq[i - 1] = -1; - } -} - -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; - struct axp288_fg_info *info; - struct axp20x_dev *axp20x = dev_get_drvdata(pdev->dev.parent); - struct power_supply_config psy_cfg = {}; - - info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL); - if (!info) - return -ENOMEM; - - info->pdev = 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); - - psy_cfg.drv_data = info; - info->bat = power_supply_register(&pdev->dev, &fuel_gauge_desc, &psy_cfg); - if (IS_ERR(info->bat)) { - ret = PTR_ERR(info->bat); - dev_err(&pdev->dev, "failed to register battery: %d\n", ret); - return ret; - } - - 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; -} - -static const struct platform_device_id axp288_fg_id_table[] = { - { .name = DEV_NAME }, - {}, -}; - -static int axp288_fuel_gauge_remove(struct platform_device *pdev) -{ - struct axp288_fg_info *info = platform_get_drvdata(pdev); - int i; - - cancel_delayed_work_sync(&info->status_monitor); - power_supply_unregister(info->bat); - fuel_gauge_remove_debugfs(info); - - for (i = 0; i < AXP288_FG_INTR_NUM; i++) - if (info->irq[i] >= 0) - free_irq(info->irq[i], info); - - return 0; -} - -static struct platform_driver axp288_fuel_gauge_driver = { - .probe = axp288_fuel_gauge_probe, - .remove = axp288_fuel_gauge_remove, - .id_table = axp288_fg_id_table, - .driver = { - .name = DEV_NAME, - }, -}; - -module_platform_driver(axp288_fuel_gauge_driver); - -MODULE_AUTHOR("Ramakrishna Pallala "); -MODULE_AUTHOR("Todd Brandt "); -MODULE_DESCRIPTION("Xpower AXP288 Fuel Gauge Driver"); -MODULE_LICENSE("GPL"); diff --git a/drivers/power/bq2415x_charger.c b/drivers/power/bq2415x_charger.c deleted file mode 100644 index 73e2f0b79dd4..000000000000 --- a/drivers/power/bq2415x_charger.c +++ /dev/null @@ -1,1815 +0,0 @@ -/* - * bq2415x charger driver - * - * Copyright (C) 2011-2013 Pali Rohár - * - * 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 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. - * - * Datasheets: - * http://www.ti.com/product/bq24150 - * http://www.ti.com/product/bq24150a - * http://www.ti.com/product/bq24152 - * http://www.ti.com/product/bq24153 - * http://www.ti.com/product/bq24153a - * http://www.ti.com/product/bq24155 - * http://www.ti.com/product/bq24157s - * http://www.ti.com/product/bq24158 - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -/* timeout for resetting chip timer */ -#define BQ2415X_TIMER_TIMEOUT 10 - -#define BQ2415X_REG_STATUS 0x00 -#define BQ2415X_REG_CONTROL 0x01 -#define BQ2415X_REG_VOLTAGE 0x02 -#define BQ2415X_REG_VENDER 0x03 -#define BQ2415X_REG_CURRENT 0x04 - -/* reset state for all registers */ -#define BQ2415X_RESET_STATUS BIT(6) -#define BQ2415X_RESET_CONTROL (BIT(4)|BIT(5)) -#define BQ2415X_RESET_VOLTAGE (BIT(1)|BIT(3)) -#define BQ2415X_RESET_CURRENT (BIT(0)|BIT(3)|BIT(7)) - -/* status register */ -#define BQ2415X_BIT_TMR_RST 7 -#define BQ2415X_BIT_OTG 7 -#define BQ2415X_BIT_EN_STAT 6 -#define BQ2415X_MASK_STAT (BIT(4)|BIT(5)) -#define BQ2415X_SHIFT_STAT 4 -#define BQ2415X_BIT_BOOST 3 -#define BQ2415X_MASK_FAULT (BIT(0)|BIT(1)|BIT(2)) -#define BQ2415X_SHIFT_FAULT 0 - -/* control register */ -#define BQ2415X_MASK_LIMIT (BIT(6)|BIT(7)) -#define BQ2415X_SHIFT_LIMIT 6 -#define BQ2415X_MASK_VLOWV (BIT(4)|BIT(5)) -#define BQ2415X_SHIFT_VLOWV 4 -#define BQ2415X_BIT_TE 3 -#define BQ2415X_BIT_CE 2 -#define BQ2415X_BIT_HZ_MODE 1 -#define BQ2415X_BIT_OPA_MODE 0 - -/* voltage register */ -#define BQ2415X_MASK_VO (BIT(2)|BIT(3)|BIT(4)|BIT(5)|BIT(6)|BIT(7)) -#define BQ2415X_SHIFT_VO 2 -#define BQ2415X_BIT_OTG_PL 1 -#define BQ2415X_BIT_OTG_EN 0 - -/* vender register */ -#define BQ2415X_MASK_VENDER (BIT(5)|BIT(6)|BIT(7)) -#define BQ2415X_SHIFT_VENDER 5 -#define BQ2415X_MASK_PN (BIT(3)|BIT(4)) -#define BQ2415X_SHIFT_PN 3 -#define BQ2415X_MASK_REVISION (BIT(0)|BIT(1)|BIT(2)) -#define BQ2415X_SHIFT_REVISION 0 - -/* current register */ -#define BQ2415X_MASK_RESET BIT(7) -#define BQ2415X_MASK_VI_CHRG (BIT(4)|BIT(5)|BIT(6)) -#define BQ2415X_SHIFT_VI_CHRG 4 -/* N/A BIT(3) */ -#define BQ2415X_MASK_VI_TERM (BIT(0)|BIT(1)|BIT(2)) -#define BQ2415X_SHIFT_VI_TERM 0 - - -enum bq2415x_command { - BQ2415X_TIMER_RESET, - BQ2415X_OTG_STATUS, - BQ2415X_STAT_PIN_STATUS, - BQ2415X_STAT_PIN_ENABLE, - BQ2415X_STAT_PIN_DISABLE, - BQ2415X_CHARGE_STATUS, - BQ2415X_BOOST_STATUS, - BQ2415X_FAULT_STATUS, - - BQ2415X_CHARGE_TERMINATION_STATUS, - BQ2415X_CHARGE_TERMINATION_ENABLE, - BQ2415X_CHARGE_TERMINATION_DISABLE, - BQ2415X_CHARGER_STATUS, - BQ2415X_CHARGER_ENABLE, - BQ2415X_CHARGER_DISABLE, - BQ2415X_HIGH_IMPEDANCE_STATUS, - BQ2415X_HIGH_IMPEDANCE_ENABLE, - BQ2415X_HIGH_IMPEDANCE_DISABLE, - BQ2415X_BOOST_MODE_STATUS, - BQ2415X_BOOST_MODE_ENABLE, - BQ2415X_BOOST_MODE_DISABLE, - - BQ2415X_OTG_LEVEL, - BQ2415X_OTG_ACTIVATE_HIGH, - BQ2415X_OTG_ACTIVATE_LOW, - BQ2415X_OTG_PIN_STATUS, - BQ2415X_OTG_PIN_ENABLE, - BQ2415X_OTG_PIN_DISABLE, - - BQ2415X_VENDER_CODE, - BQ2415X_PART_NUMBER, - BQ2415X_REVISION, -}; - -enum bq2415x_chip { - BQUNKNOWN, - BQ24150, - BQ24150A, - BQ24151, - BQ24151A, - BQ24152, - BQ24153, - BQ24153A, - BQ24155, - BQ24156, - BQ24156A, - BQ24157S, - BQ24158, -}; - -static char *bq2415x_chip_name[] = { - "unknown", - "bq24150", - "bq24150a", - "bq24151", - "bq24151a", - "bq24152", - "bq24153", - "bq24153a", - "bq24155", - "bq24156", - "bq24156a", - "bq24157s", - "bq24158", -}; - -struct bq2415x_device { - struct device *dev; - struct bq2415x_platform_data init_data; - struct power_supply *charger; - struct power_supply_desc charger_desc; - struct delayed_work work; - struct device_node *notify_node; - struct notifier_block nb; - enum bq2415x_mode reported_mode;/* mode reported by hook function */ - enum bq2415x_mode mode; /* currently configured mode */ - enum bq2415x_chip chip; - const char *timer_error; - char *model; - char *name; - int autotimer; /* 1 - if driver automatically reset timer, 0 - not */ - int automode; /* 1 - enabled, 0 - disabled; -1 - not supported */ - int id; -}; - -/* each registered chip must have unique id */ -static DEFINE_IDR(bq2415x_id); - -static DEFINE_MUTEX(bq2415x_id_mutex); -static DEFINE_MUTEX(bq2415x_timer_mutex); -static DEFINE_MUTEX(bq2415x_i2c_mutex); - -/**** i2c read functions ****/ - -/* read value from register */ -static int bq2415x_i2c_read(struct bq2415x_device *bq, u8 reg) -{ - struct i2c_client *client = to_i2c_client(bq->dev); - struct i2c_msg msg[2]; - u8 val; - int ret; - - if (!client->adapter) - return -ENODEV; - - msg[0].addr = client->addr; - msg[0].flags = 0; - msg[0].buf = ® - msg[0].len = sizeof(reg); - msg[1].addr = client->addr; - msg[1].flags = I2C_M_RD; - msg[1].buf = &val; - msg[1].len = sizeof(val); - - mutex_lock(&bq2415x_i2c_mutex); - ret = i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg)); - mutex_unlock(&bq2415x_i2c_mutex); - - if (ret < 0) - return ret; - - return val; -} - -/* read value from register, apply mask and right shift it */ -static int bq2415x_i2c_read_mask(struct bq2415x_device *bq, u8 reg, - u8 mask, u8 shift) -{ - int ret; - - if (shift > 8) - return -EINVAL; - - ret = bq2415x_i2c_read(bq, reg); - if (ret < 0) - return ret; - return (ret & mask) >> shift; -} - -/* read value from register and return one specified bit */ -static int bq2415x_i2c_read_bit(struct bq2415x_device *bq, u8 reg, u8 bit) -{ - if (bit > 8) - return -EINVAL; - return bq2415x_i2c_read_mask(bq, reg, BIT(bit), bit); -} - -/**** i2c write functions ****/ - -/* write value to register */ -static int bq2415x_i2c_write(struct bq2415x_device *bq, u8 reg, u8 val) -{ - struct i2c_client *client = to_i2c_client(bq->dev); - struct i2c_msg msg[1]; - u8 data[2]; - int ret; - - data[0] = reg; - data[1] = val; - - msg[0].addr = client->addr; - msg[0].flags = 0; - msg[0].buf = data; - msg[0].len = ARRAY_SIZE(data); - - mutex_lock(&bq2415x_i2c_mutex); - ret = i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg)); - mutex_unlock(&bq2415x_i2c_mutex); - - /* i2c_transfer returns number of messages transferred */ - if (ret < 0) - return ret; - else if (ret != 1) - return -EIO; - - return 0; -} - -/* read value from register, change it with mask left shifted and write back */ -static int bq2415x_i2c_write_mask(struct bq2415x_device *bq, u8 reg, u8 val, - u8 mask, u8 shift) -{ - int ret; - - if (shift > 8) - return -EINVAL; - - ret = bq2415x_i2c_read(bq, reg); - if (ret < 0) - return ret; - - ret &= ~mask; - ret |= val << shift; - - return bq2415x_i2c_write(bq, reg, ret); -} - -/* change only one bit in register */ -static int bq2415x_i2c_write_bit(struct bq2415x_device *bq, u8 reg, - bool val, u8 bit) -{ - if (bit > 8) - return -EINVAL; - return bq2415x_i2c_write_mask(bq, reg, val, BIT(bit), bit); -} - -/**** global functions ****/ - -/* exec command function */ -static int bq2415x_exec_command(struct bq2415x_device *bq, - enum bq2415x_command command) -{ - int ret; - - switch (command) { - case BQ2415X_TIMER_RESET: - return bq2415x_i2c_write_bit(bq, BQ2415X_REG_STATUS, - 1, BQ2415X_BIT_TMR_RST); - case BQ2415X_OTG_STATUS: - return bq2415x_i2c_read_bit(bq, BQ2415X_REG_STATUS, - BQ2415X_BIT_OTG); - case BQ2415X_STAT_PIN_STATUS: - return bq2415x_i2c_read_bit(bq, BQ2415X_REG_STATUS, - BQ2415X_BIT_EN_STAT); - case BQ2415X_STAT_PIN_ENABLE: - return bq2415x_i2c_write_bit(bq, BQ2415X_REG_STATUS, 1, - BQ2415X_BIT_EN_STAT); - case BQ2415X_STAT_PIN_DISABLE: - return bq2415x_i2c_write_bit(bq, BQ2415X_REG_STATUS, 0, - BQ2415X_BIT_EN_STAT); - case BQ2415X_CHARGE_STATUS: - return bq2415x_i2c_read_mask(bq, BQ2415X_REG_STATUS, - BQ2415X_MASK_STAT, BQ2415X_SHIFT_STAT); - case BQ2415X_BOOST_STATUS: - return bq2415x_i2c_read_bit(bq, BQ2415X_REG_STATUS, - BQ2415X_BIT_BOOST); - case BQ2415X_FAULT_STATUS: - return bq2415x_i2c_read_mask(bq, BQ2415X_REG_STATUS, - BQ2415X_MASK_FAULT, BQ2415X_SHIFT_FAULT); - - case BQ2415X_CHARGE_TERMINATION_STATUS: - return bq2415x_i2c_read_bit(bq, BQ2415X_REG_CONTROL, - BQ2415X_BIT_TE); - case BQ2415X_CHARGE_TERMINATION_ENABLE: - return bq2415x_i2c_write_bit(bq, BQ2415X_REG_CONTROL, - 1, BQ2415X_BIT_TE); - case BQ2415X_CHARGE_TERMINATION_DISABLE: - return bq2415x_i2c_write_bit(bq, BQ2415X_REG_CONTROL, - 0, BQ2415X_BIT_TE); - case BQ2415X_CHARGER_STATUS: - ret = bq2415x_i2c_read_bit(bq, BQ2415X_REG_CONTROL, - BQ2415X_BIT_CE); - if (ret < 0) - return ret; - return ret > 0 ? 0 : 1; - case BQ2415X_CHARGER_ENABLE: - return bq2415x_i2c_write_bit(bq, BQ2415X_REG_CONTROL, - 0, BQ2415X_BIT_CE); - case BQ2415X_CHARGER_DISABLE: - return bq2415x_i2c_write_bit(bq, BQ2415X_REG_CONTROL, - 1, BQ2415X_BIT_CE); - case BQ2415X_HIGH_IMPEDANCE_STATUS: - return bq2415x_i2c_read_bit(bq, BQ2415X_REG_CONTROL, - BQ2415X_BIT_HZ_MODE); - case BQ2415X_HIGH_IMPEDANCE_ENABLE: - return bq2415x_i2c_write_bit(bq, BQ2415X_REG_CONTROL, - 1, BQ2415X_BIT_HZ_MODE); - case BQ2415X_HIGH_IMPEDANCE_DISABLE: - return bq2415x_i2c_write_bit(bq, BQ2415X_REG_CONTROL, - 0, BQ2415X_BIT_HZ_MODE); - case BQ2415X_BOOST_MODE_STATUS: - return bq2415x_i2c_read_bit(bq, BQ2415X_REG_CONTROL, - BQ2415X_BIT_OPA_MODE); - case BQ2415X_BOOST_MODE_ENABLE: - return bq2415x_i2c_write_bit(bq, BQ2415X_REG_CONTROL, - 1, BQ2415X_BIT_OPA_MODE); - case BQ2415X_BOOST_MODE_DISABLE: - return bq2415x_i2c_write_bit(bq, BQ2415X_REG_CONTROL, - 0, BQ2415X_BIT_OPA_MODE); - - case BQ2415X_OTG_LEVEL: - return bq2415x_i2c_read_bit(bq, BQ2415X_REG_VOLTAGE, - BQ2415X_BIT_OTG_PL); - case BQ2415X_OTG_ACTIVATE_HIGH: - return bq2415x_i2c_write_bit(bq, BQ2415X_REG_VOLTAGE, - 1, BQ2415X_BIT_OTG_PL); - case BQ2415X_OTG_ACTIVATE_LOW: - return bq2415x_i2c_write_bit(bq, BQ2415X_REG_VOLTAGE, - 0, BQ2415X_BIT_OTG_PL); - case BQ2415X_OTG_PIN_STATUS: - return bq2415x_i2c_read_bit(bq, BQ2415X_REG_VOLTAGE, - BQ2415X_BIT_OTG_EN); - case BQ2415X_OTG_PIN_ENABLE: - return bq2415x_i2c_write_bit(bq, BQ2415X_REG_VOLTAGE, - 1, BQ2415X_BIT_OTG_EN); - case BQ2415X_OTG_PIN_DISABLE: - return bq2415x_i2c_write_bit(bq, BQ2415X_REG_VOLTAGE, - 0, BQ2415X_BIT_OTG_EN); - - case BQ2415X_VENDER_CODE: - return bq2415x_i2c_read_mask(bq, BQ2415X_REG_VENDER, - BQ2415X_MASK_VENDER, BQ2415X_SHIFT_VENDER); - case BQ2415X_PART_NUMBER: - return bq2415x_i2c_read_mask(bq, BQ2415X_REG_VENDER, - BQ2415X_MASK_PN, BQ2415X_SHIFT_PN); - case BQ2415X_REVISION: - return bq2415x_i2c_read_mask(bq, BQ2415X_REG_VENDER, - BQ2415X_MASK_REVISION, BQ2415X_SHIFT_REVISION); - } - return -EINVAL; -} - -/* detect chip type */ -static enum bq2415x_chip bq2415x_detect_chip(struct bq2415x_device *bq) -{ - struct i2c_client *client = to_i2c_client(bq->dev); - int ret = bq2415x_exec_command(bq, BQ2415X_PART_NUMBER); - - if (ret < 0) - return ret; - - switch (client->addr) { - case 0x6b: - switch (ret) { - case 0: - if (bq->chip == BQ24151A) - return bq->chip; - return BQ24151; - case 1: - if (bq->chip == BQ24150A || - bq->chip == BQ24152 || - bq->chip == BQ24155) - return bq->chip; - return BQ24150; - case 2: - if (bq->chip == BQ24153A) - return bq->chip; - return BQ24153; - default: - return BQUNKNOWN; - } - break; - - case 0x6a: - switch (ret) { - case 0: - if (bq->chip == BQ24156A) - return bq->chip; - return BQ24156; - case 2: - if (bq->chip == BQ24157S) - return bq->chip; - return BQ24158; - default: - return BQUNKNOWN; - } - break; - } - - return BQUNKNOWN; -} - -/* detect chip revision */ -static int bq2415x_detect_revision(struct bq2415x_device *bq) -{ - int ret = bq2415x_exec_command(bq, BQ2415X_REVISION); - int chip = bq2415x_detect_chip(bq); - - if (ret < 0 || chip < 0) - return -1; - - switch (chip) { - case BQ24150: - case BQ24150A: - case BQ24151: - case BQ24151A: - case BQ24152: - if (ret >= 0 && ret <= 3) - return ret; - return -1; - case BQ24153: - case BQ24153A: - case BQ24156: - case BQ24156A: - case BQ24157S: - case BQ24158: - if (ret == 3) - return 0; - else if (ret == 1) - return 1; - return -1; - case BQ24155: - if (ret == 3) - return 3; - return -1; - case BQUNKNOWN: - return -1; - } - - return -1; -} - -/* return chip vender code */ -static int bq2415x_get_vender_code(struct bq2415x_device *bq) -{ - int ret; - - ret = bq2415x_exec_command(bq, BQ2415X_VENDER_CODE); - if (ret < 0) - return 0; - - /* convert to binary */ - return (ret & 0x1) + - ((ret >> 1) & 0x1) * 10 + - ((ret >> 2) & 0x1) * 100; -} - -/* reset all chip registers to default state */ -static void bq2415x_reset_chip(struct bq2415x_device *bq) -{ - bq2415x_i2c_write(bq, BQ2415X_REG_CURRENT, BQ2415X_RESET_CURRENT); - bq2415x_i2c_write(bq, BQ2415X_REG_VOLTAGE, BQ2415X_RESET_VOLTAGE); - bq2415x_i2c_write(bq, BQ2415X_REG_CONTROL, BQ2415X_RESET_CONTROL); - bq2415x_i2c_write(bq, BQ2415X_REG_STATUS, BQ2415X_RESET_STATUS); - bq->timer_error = NULL; -} - -/**** properties functions ****/ - -/* set current limit in mA */ -static int bq2415x_set_current_limit(struct bq2415x_device *bq, int mA) -{ - int val; - - if (mA <= 100) - val = 0; - else if (mA <= 500) - val = 1; - else if (mA <= 800) - val = 2; - else - val = 3; - - return bq2415x_i2c_write_mask(bq, BQ2415X_REG_CONTROL, val, - BQ2415X_MASK_LIMIT, BQ2415X_SHIFT_LIMIT); -} - -/* get current limit in mA */ -static int bq2415x_get_current_limit(struct bq2415x_device *bq) -{ - int ret; - - ret = bq2415x_i2c_read_mask(bq, BQ2415X_REG_CONTROL, - BQ2415X_MASK_LIMIT, BQ2415X_SHIFT_LIMIT); - if (ret < 0) - return ret; - else if (ret == 0) - return 100; - else if (ret == 1) - return 500; - else if (ret == 2) - return 800; - else if (ret == 3) - return 1800; - return -EINVAL; -} - -/* set weak battery voltage in mV */ -static int bq2415x_set_weak_battery_voltage(struct bq2415x_device *bq, int mV) -{ - int val; - - /* round to 100mV */ - if (mV <= 3400 + 50) - val = 0; - else if (mV <= 3500 + 50) - val = 1; - else if (mV <= 3600 + 50) - val = 2; - else - val = 3; - - return bq2415x_i2c_write_mask(bq, BQ2415X_REG_CONTROL, val, - BQ2415X_MASK_VLOWV, BQ2415X_SHIFT_VLOWV); -} - -/* get weak battery voltage in mV */ -static int bq2415x_get_weak_battery_voltage(struct bq2415x_device *bq) -{ - int ret; - - ret = bq2415x_i2c_read_mask(bq, BQ2415X_REG_CONTROL, - BQ2415X_MASK_VLOWV, BQ2415X_SHIFT_VLOWV); - if (ret < 0) - return ret; - return 100 * (34 + ret); -} - -/* set battery regulation voltage in mV */ -static int bq2415x_set_battery_regulation_voltage(struct bq2415x_device *bq, - int mV) -{ - int val = (mV/10 - 350) / 2; - - /* - * According to datasheet, maximum battery regulation voltage is - * 4440mV which is b101111 = 47. - */ - if (val < 0) - val = 0; - else if (val > 47) - return -EINVAL; - - return bq2415x_i2c_write_mask(bq, BQ2415X_REG_VOLTAGE, val, - BQ2415X_MASK_VO, BQ2415X_SHIFT_VO); -} - -/* get battery regulation voltage in mV */ -static int bq2415x_get_battery_regulation_voltage(struct bq2415x_device *bq) -{ - int ret = bq2415x_i2c_read_mask(bq, BQ2415X_REG_VOLTAGE, - BQ2415X_MASK_VO, BQ2415X_SHIFT_VO); - - if (ret < 0) - return ret; - return 10 * (350 + 2*ret); -} - -/* set charge current in mA (platform data must provide resistor sense) */ -static int bq2415x_set_charge_current(struct bq2415x_device *bq, int mA) -{ - int val; - - if (bq->init_data.resistor_sense <= 0) - return -EINVAL; - - val = (mA * bq->init_data.resistor_sense - 37400) / 6800; - if (val < 0) - val = 0; - else if (val > 7) - val = 7; - - return bq2415x_i2c_write_mask(bq, BQ2415X_REG_CURRENT, val, - BQ2415X_MASK_VI_CHRG | BQ2415X_MASK_RESET, - BQ2415X_SHIFT_VI_CHRG); -} - -/* get charge current in mA (platform data must provide resistor sense) */ -static int bq2415x_get_charge_current(struct bq2415x_device *bq) -{ - int ret; - - if (bq->init_data.resistor_sense <= 0) - return -EINVAL; - - ret = bq2415x_i2c_read_mask(bq, BQ2415X_REG_CURRENT, - BQ2415X_MASK_VI_CHRG, BQ2415X_SHIFT_VI_CHRG); - if (ret < 0) - return ret; - return (37400 + 6800*ret) / bq->init_data.resistor_sense; -} - -/* set termination current in mA (platform data must provide resistor sense) */ -static int bq2415x_set_termination_current(struct bq2415x_device *bq, int mA) -{ - int val; - - if (bq->init_data.resistor_sense <= 0) - return -EINVAL; - - val = (mA * bq->init_data.resistor_sense - 3400) / 3400; - if (val < 0) - val = 0; - else if (val > 7) - val = 7; - - return bq2415x_i2c_write_mask(bq, BQ2415X_REG_CURRENT, val, - BQ2415X_MASK_VI_TERM | BQ2415X_MASK_RESET, - BQ2415X_SHIFT_VI_TERM); -} - -/* get termination current in mA (platform data must provide resistor sense) */ -static int bq2415x_get_termination_current(struct bq2415x_device *bq) -{ - int ret; - - if (bq->init_data.resistor_sense <= 0) - return -EINVAL; - - ret = bq2415x_i2c_read_mask(bq, BQ2415X_REG_CURRENT, - BQ2415X_MASK_VI_TERM, BQ2415X_SHIFT_VI_TERM); - if (ret < 0) - return ret; - return (3400 + 3400*ret) / bq->init_data.resistor_sense; -} - -/* set default value of property */ -#define bq2415x_set_default_value(bq, prop) \ - do { \ - int ret = 0; \ - if (bq->init_data.prop != -1) \ - ret = bq2415x_set_##prop(bq, bq->init_data.prop); \ - if (ret < 0) \ - return ret; \ - } while (0) - -/* set default values of all properties */ -static int bq2415x_set_defaults(struct bq2415x_device *bq) -{ - bq2415x_exec_command(bq, BQ2415X_BOOST_MODE_DISABLE); - bq2415x_exec_command(bq, BQ2415X_CHARGER_DISABLE); - bq2415x_exec_command(bq, BQ2415X_CHARGE_TERMINATION_DISABLE); - - bq2415x_set_default_value(bq, current_limit); - bq2415x_set_default_value(bq, weak_battery_voltage); - bq2415x_set_default_value(bq, battery_regulation_voltage); - - if (bq->init_data.resistor_sense > 0) { - bq2415x_set_default_value(bq, charge_current); - bq2415x_set_default_value(bq, termination_current); - bq2415x_exec_command(bq, BQ2415X_CHARGE_TERMINATION_ENABLE); - } - - bq2415x_exec_command(bq, BQ2415X_CHARGER_ENABLE); - return 0; -} - -/**** charger mode functions ****/ - -/* set charger mode */ -static int bq2415x_set_mode(struct bq2415x_device *bq, enum bq2415x_mode mode) -{ - int ret = 0; - int charger = 0; - int boost = 0; - - if (mode == BQ2415X_MODE_BOOST) - boost = 1; - else if (mode != BQ2415X_MODE_OFF) - charger = 1; - - if (!charger) - ret = bq2415x_exec_command(bq, BQ2415X_CHARGER_DISABLE); - - if (!boost) - ret = bq2415x_exec_command(bq, BQ2415X_BOOST_MODE_DISABLE); - - if (ret < 0) - return ret; - - switch (mode) { - case BQ2415X_MODE_OFF: - dev_dbg(bq->dev, "changing mode to: Offline\n"); - ret = bq2415x_set_current_limit(bq, 100); - break; - case BQ2415X_MODE_NONE: - dev_dbg(bq->dev, "changing mode to: N/A\n"); - ret = bq2415x_set_current_limit(bq, 100); - break; - case BQ2415X_MODE_HOST_CHARGER: - dev_dbg(bq->dev, "changing mode to: Host/HUB charger\n"); - ret = bq2415x_set_current_limit(bq, 500); - break; - case BQ2415X_MODE_DEDICATED_CHARGER: - dev_dbg(bq->dev, "changing mode to: Dedicated charger\n"); - ret = bq2415x_set_current_limit(bq, 1800); - break; - case BQ2415X_MODE_BOOST: /* Boost mode */ - dev_dbg(bq->dev, "changing mode to: Boost\n"); - ret = bq2415x_set_current_limit(bq, 100); - break; - } - - if (ret < 0) - return ret; - - if (charger) - ret = bq2415x_exec_command(bq, BQ2415X_CHARGER_ENABLE); - else if (boost) - ret = bq2415x_exec_command(bq, BQ2415X_BOOST_MODE_ENABLE); - - if (ret < 0) - return ret; - - bq2415x_set_default_value(bq, weak_battery_voltage); - bq2415x_set_default_value(bq, battery_regulation_voltage); - - bq->mode = mode; - sysfs_notify(&bq->charger->dev.kobj, NULL, "mode"); - - return 0; - -} - -static bool bq2415x_update_reported_mode(struct bq2415x_device *bq, int mA) -{ - enum bq2415x_mode mode; - - if (mA == 0) - mode = BQ2415X_MODE_OFF; - else if (mA < 500) - mode = BQ2415X_MODE_NONE; - else if (mA < 1800) - mode = BQ2415X_MODE_HOST_CHARGER; - else - mode = BQ2415X_MODE_DEDICATED_CHARGER; - - if (bq->reported_mode == mode) - return false; - - bq->reported_mode = mode; - return true; -} - -static int bq2415x_notifier_call(struct notifier_block *nb, - unsigned long val, void *v) -{ - struct bq2415x_device *bq = - container_of(nb, struct bq2415x_device, nb); - struct power_supply *psy = v; - union power_supply_propval prop; - int ret; - - if (val != PSY_EVENT_PROP_CHANGED) - return NOTIFY_OK; - - /* Ignore event if it was not send by notify_node/notify_device */ - if (bq->notify_node) { - if (!psy->dev.parent || - psy->dev.parent->of_node != bq->notify_node) - return NOTIFY_OK; - } else if (bq->init_data.notify_device) { - if (strcmp(psy->desc->name, bq->init_data.notify_device) != 0) - return NOTIFY_OK; - } - - dev_dbg(bq->dev, "notifier call was called\n"); - - ret = power_supply_get_property(psy, POWER_SUPPLY_PROP_CURRENT_MAX, - &prop); - if (ret != 0) - return NOTIFY_OK; - - if (!bq2415x_update_reported_mode(bq, prop.intval)) - return NOTIFY_OK; - - /* if automode is not enabled do not tell about reported_mode */ - if (bq->automode < 1) - return NOTIFY_OK; - - schedule_delayed_work(&bq->work, 0); - - return NOTIFY_OK; -} - -/**** timer functions ****/ - -/* enable/disable auto resetting chip timer */ -static void bq2415x_set_autotimer(struct bq2415x_device *bq, int state) -{ - mutex_lock(&bq2415x_timer_mutex); - - if (bq->autotimer == state) { - mutex_unlock(&bq2415x_timer_mutex); - return; - } - - bq->autotimer = state; - - if (state) { - schedule_delayed_work(&bq->work, BQ2415X_TIMER_TIMEOUT * HZ); - bq2415x_exec_command(bq, BQ2415X_TIMER_RESET); - bq->timer_error = NULL; - } else { - cancel_delayed_work_sync(&bq->work); - } - - mutex_unlock(&bq2415x_timer_mutex); -} - -/* called by bq2415x_timer_work on timer error */ -static void bq2415x_timer_error(struct bq2415x_device *bq, const char *msg) -{ - bq->timer_error = msg; - sysfs_notify(&bq->charger->dev.kobj, NULL, "timer"); - dev_err(bq->dev, "%s\n", msg); - if (bq->automode > 0) - bq->automode = 0; - bq2415x_set_mode(bq, BQ2415X_MODE_OFF); - bq2415x_set_autotimer(bq, 0); -} - -/* delayed work function for auto resetting chip timer */ -static void bq2415x_timer_work(struct work_struct *work) -{ - struct bq2415x_device *bq = container_of(work, struct bq2415x_device, - work.work); - int ret; - int error; - int boost; - - if (bq->automode > 0 && (bq->reported_mode != bq->mode)) { - sysfs_notify(&bq->charger->dev.kobj, NULL, "reported_mode"); - bq2415x_set_mode(bq, bq->reported_mode); - } - - if (!bq->autotimer) - return; - - ret = bq2415x_exec_command(bq, BQ2415X_TIMER_RESET); - if (ret < 0) { - bq2415x_timer_error(bq, "Resetting timer failed"); - return; - } - - boost = bq2415x_exec_command(bq, BQ2415X_BOOST_MODE_STATUS); - if (boost < 0) { - bq2415x_timer_error(bq, "Unknown error"); - return; - } - - error = bq2415x_exec_command(bq, BQ2415X_FAULT_STATUS); - if (error < 0) { - bq2415x_timer_error(bq, "Unknown error"); - return; - } - - if (boost) { - switch (error) { - /* Non fatal errors, chip is OK */ - case 0: /* No error */ - break; - case 6: /* Timer expired */ - dev_err(bq->dev, "Timer expired\n"); - break; - case 3: /* Battery voltage too low */ - dev_err(bq->dev, "Battery voltage to low\n"); - break; - - /* Fatal errors, disable and reset chip */ - case 1: /* Overvoltage protection (chip fried) */ - bq2415x_timer_error(bq, - "Overvoltage protection (chip fried)"); - return; - case 2: /* Overload */ - bq2415x_timer_error(bq, "Overload"); - return; - case 4: /* Battery overvoltage protection */ - bq2415x_timer_error(bq, - "Battery overvoltage protection"); - return; - case 5: /* Thermal shutdown (too hot) */ - bq2415x_timer_error(bq, - "Thermal shutdown (too hot)"); - return; - case 7: /* N/A */ - bq2415x_timer_error(bq, "Unknown error"); - return; - } - } else { - switch (error) { - /* Non fatal errors, chip is OK */ - case 0: /* No error */ - break; - case 2: /* Sleep mode */ - dev_err(bq->dev, "Sleep mode\n"); - break; - case 3: /* Poor input source */ - dev_err(bq->dev, "Poor input source\n"); - break; - case 6: /* Timer expired */ - dev_err(bq->dev, "Timer expired\n"); - break; - case 7: /* No battery */ - dev_err(bq->dev, "No battery\n"); - break; - - /* Fatal errors, disable and reset chip */ - case 1: /* Overvoltage protection (chip fried) */ - bq2415x_timer_error(bq, - "Overvoltage protection (chip fried)"); - return; - case 4: /* Battery overvoltage protection */ - bq2415x_timer_error(bq, - "Battery overvoltage protection"); - return; - case 5: /* Thermal shutdown (too hot) */ - bq2415x_timer_error(bq, - "Thermal shutdown (too hot)"); - return; - } - } - - schedule_delayed_work(&bq->work, BQ2415X_TIMER_TIMEOUT * HZ); -} - -/**** power supply interface code ****/ - -static enum power_supply_property bq2415x_power_supply_props[] = { - /* TODO: maybe add more power supply properties */ - POWER_SUPPLY_PROP_STATUS, - POWER_SUPPLY_PROP_MODEL_NAME, -}; - -static int bq2415x_power_supply_get_property(struct power_supply *psy, - enum power_supply_property psp, - union power_supply_propval *val) -{ - struct bq2415x_device *bq = power_supply_get_drvdata(psy); - int ret; - - switch (psp) { - case POWER_SUPPLY_PROP_STATUS: - ret = bq2415x_exec_command(bq, BQ2415X_CHARGE_STATUS); - if (ret < 0) - return ret; - else if (ret == 0) /* Ready */ - val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; - else if (ret == 1) /* Charge in progress */ - val->intval = POWER_SUPPLY_STATUS_CHARGING; - else if (ret == 2) /* Charge done */ - val->intval = POWER_SUPPLY_STATUS_FULL; - else - val->intval = POWER_SUPPLY_STATUS_UNKNOWN; - break; - case POWER_SUPPLY_PROP_MODEL_NAME: - val->strval = bq->model; - break; - default: - return -EINVAL; - } - return 0; -} - -static int bq2415x_power_supply_init(struct bq2415x_device *bq) -{ - int ret; - int chip; - char revstr[8]; - struct power_supply_config psy_cfg = { .drv_data = bq, }; - - bq->charger_desc.name = bq->name; - bq->charger_desc.type = POWER_SUPPLY_TYPE_USB; - bq->charger_desc.properties = bq2415x_power_supply_props; - bq->charger_desc.num_properties = - ARRAY_SIZE(bq2415x_power_supply_props); - bq->charger_desc.get_property = bq2415x_power_supply_get_property; - - ret = bq2415x_detect_chip(bq); - if (ret < 0) - chip = BQUNKNOWN; - else - chip = ret; - - ret = bq2415x_detect_revision(bq); - if (ret < 0) - strcpy(revstr, "unknown"); - else - sprintf(revstr, "1.%d", ret); - - bq->model = kasprintf(GFP_KERNEL, - "chip %s, revision %s, vender code %.3d", - bq2415x_chip_name[chip], revstr, - bq2415x_get_vender_code(bq)); - if (!bq->model) { - dev_err(bq->dev, "failed to allocate model name\n"); - return -ENOMEM; - } - - bq->charger = power_supply_register(bq->dev, &bq->charger_desc, - &psy_cfg); - if (IS_ERR(bq->charger)) { - kfree(bq->model); - return PTR_ERR(bq->charger); - } - - return 0; -} - -static void bq2415x_power_supply_exit(struct bq2415x_device *bq) -{ - bq->autotimer = 0; - if (bq->automode > 0) - bq->automode = 0; - cancel_delayed_work_sync(&bq->work); - power_supply_unregister(bq->charger); - kfree(bq->model); -} - -/**** additional sysfs entries for power supply interface ****/ - -/* show *_status entries */ -static ssize_t bq2415x_sysfs_show_status(struct device *dev, - struct device_attribute *attr, - char *buf) -{ - struct power_supply *psy = dev_get_drvdata(dev); - struct bq2415x_device *bq = power_supply_get_drvdata(psy); - enum bq2415x_command command; - int ret; - - if (strcmp(attr->attr.name, "otg_status") == 0) - command = BQ2415X_OTG_STATUS; - else if (strcmp(attr->attr.name, "charge_status") == 0) - command = BQ2415X_CHARGE_STATUS; - else if (strcmp(attr->attr.name, "boost_status") == 0) - command = BQ2415X_BOOST_STATUS; - else if (strcmp(attr->attr.name, "fault_status") == 0) - command = BQ2415X_FAULT_STATUS; - else - return -EINVAL; - - ret = bq2415x_exec_command(bq, command); - if (ret < 0) - return ret; - return sprintf(buf, "%d\n", ret); -} - -/* - * set timer entry: - * auto - enable auto mode - * off - disable auto mode - * (other values) - reset chip timer - */ -static ssize_t bq2415x_sysfs_set_timer(struct device *dev, - struct device_attribute *attr, - const char *buf, - size_t count) -{ - struct power_supply *psy = dev_get_drvdata(dev); - struct bq2415x_device *bq = power_supply_get_drvdata(psy); - int ret = 0; - - if (strncmp(buf, "auto", 4) == 0) - bq2415x_set_autotimer(bq, 1); - else if (strncmp(buf, "off", 3) == 0) - bq2415x_set_autotimer(bq, 0); - else - ret = bq2415x_exec_command(bq, BQ2415X_TIMER_RESET); - - if (ret < 0) - return ret; - return count; -} - -/* show timer entry (auto or off) */ -static ssize_t bq2415x_sysfs_show_timer(struct device *dev, - struct device_attribute *attr, - char *buf) -{ - struct power_supply *psy = dev_get_drvdata(dev); - struct bq2415x_device *bq = power_supply_get_drvdata(psy); - - if (bq->timer_error) - return sprintf(buf, "%s\n", bq->timer_error); - - if (bq->autotimer) - return sprintf(buf, "auto\n"); - return sprintf(buf, "off\n"); -} - -/* - * set mode entry: - * auto - if automode is supported, enable it and set mode to reported - * none - disable charger and boost mode - * host - charging mode for host/hub chargers (current limit 500mA) - * dedicated - charging mode for dedicated chargers (unlimited current limit) - * boost - disable charger and enable boost mode - */ -static ssize_t bq2415x_sysfs_set_mode(struct device *dev, - struct device_attribute *attr, - const char *buf, - size_t count) -{ - struct power_supply *psy = dev_get_drvdata(dev); - struct bq2415x_device *bq = power_supply_get_drvdata(psy); - enum bq2415x_mode mode; - int ret = 0; - - if (strncmp(buf, "auto", 4) == 0) { - if (bq->automode < 0) - return -EINVAL; - bq->automode = 1; - mode = bq->reported_mode; - } else if (strncmp(buf, "off", 3) == 0) { - if (bq->automode > 0) - bq->automode = 0; - mode = BQ2415X_MODE_OFF; - } else if (strncmp(buf, "none", 4) == 0) { - if (bq->automode > 0) - bq->automode = 0; - mode = BQ2415X_MODE_NONE; - } else if (strncmp(buf, "host", 4) == 0) { - if (bq->automode > 0) - bq->automode = 0; - mode = BQ2415X_MODE_HOST_CHARGER; - } else if (strncmp(buf, "dedicated", 9) == 0) { - if (bq->automode > 0) - bq->automode = 0; - mode = BQ2415X_MODE_DEDICATED_CHARGER; - } else if (strncmp(buf, "boost", 5) == 0) { - if (bq->automode > 0) - bq->automode = 0; - mode = BQ2415X_MODE_BOOST; - } else if (strncmp(buf, "reset", 5) == 0) { - bq2415x_reset_chip(bq); - bq2415x_set_defaults(bq); - if (bq->automode <= 0) - return count; - bq->automode = 1; - mode = bq->reported_mode; - } else { - return -EINVAL; - } - - ret = bq2415x_set_mode(bq, mode); - if (ret < 0) - return ret; - return count; -} - -/* show mode entry (auto, none, host, dedicated or boost) */ -static ssize_t bq2415x_sysfs_show_mode(struct device *dev, - struct device_attribute *attr, - char *buf) -{ - struct power_supply *psy = dev_get_drvdata(dev); - struct bq2415x_device *bq = power_supply_get_drvdata(psy); - ssize_t ret = 0; - - if (bq->automode > 0) - ret += sprintf(buf+ret, "auto ("); - - switch (bq->mode) { - case BQ2415X_MODE_OFF: - ret += sprintf(buf+ret, "off"); - break; - case BQ2415X_MODE_NONE: - ret += sprintf(buf+ret, "none"); - break; - case BQ2415X_MODE_HOST_CHARGER: - ret += sprintf(buf+ret, "host"); - break; - case BQ2415X_MODE_DEDICATED_CHARGER: - ret += sprintf(buf+ret, "dedicated"); - break; - case BQ2415X_MODE_BOOST: - ret += sprintf(buf+ret, "boost"); - break; - } - - if (bq->automode > 0) - ret += sprintf(buf+ret, ")"); - - ret += sprintf(buf+ret, "\n"); - return ret; -} - -/* show reported_mode entry (none, host, dedicated or boost) */ -static ssize_t bq2415x_sysfs_show_reported_mode(struct device *dev, - struct device_attribute *attr, - char *buf) -{ - struct power_supply *psy = dev_get_drvdata(dev); - struct bq2415x_device *bq = power_supply_get_drvdata(psy); - - if (bq->automode < 0) - return -EINVAL; - - switch (bq->reported_mode) { - case BQ2415X_MODE_OFF: - return sprintf(buf, "off\n"); - case BQ2415X_MODE_NONE: - return sprintf(buf, "none\n"); - case BQ2415X_MODE_HOST_CHARGER: - return sprintf(buf, "host\n"); - case BQ2415X_MODE_DEDICATED_CHARGER: - return sprintf(buf, "dedicated\n"); - case BQ2415X_MODE_BOOST: - return sprintf(buf, "boost\n"); - } - - return -EINVAL; -} - -/* directly set raw value to chip register, format: 'register value' */ -static ssize_t bq2415x_sysfs_set_registers(struct device *dev, - struct device_attribute *attr, - const char *buf, - size_t count) -{ - struct power_supply *psy = dev_get_drvdata(dev); - struct bq2415x_device *bq = power_supply_get_drvdata(psy); - ssize_t ret = 0; - unsigned int reg; - unsigned int val; - - if (sscanf(buf, "%x %x", ®, &val) != 2) - return -EINVAL; - - if (reg > 4 || val > 255) - return -EINVAL; - - ret = bq2415x_i2c_write(bq, reg, val); - if (ret < 0) - return ret; - return count; -} - -/* print value of chip register, format: 'register=value' */ -static ssize_t bq2415x_sysfs_print_reg(struct bq2415x_device *bq, - u8 reg, - char *buf) -{ - int ret = bq2415x_i2c_read(bq, reg); - - if (ret < 0) - return sprintf(buf, "%#.2x=error %d\n", reg, ret); - return sprintf(buf, "%#.2x=%#.2x\n", reg, ret); -} - -/* show all raw values of chip register, format per line: 'register=value' */ -static ssize_t bq2415x_sysfs_show_registers(struct device *dev, - struct device_attribute *attr, - char *buf) -{ - struct power_supply *psy = dev_get_drvdata(dev); - struct bq2415x_device *bq = power_supply_get_drvdata(psy); - ssize_t ret = 0; - - ret += bq2415x_sysfs_print_reg(bq, BQ2415X_REG_STATUS, buf+ret); - ret += bq2415x_sysfs_print_reg(bq, BQ2415X_REG_CONTROL, buf+ret); - ret += bq2415x_sysfs_print_reg(bq, BQ2415X_REG_VOLTAGE, buf+ret); - ret += bq2415x_sysfs_print_reg(bq, BQ2415X_REG_VENDER, buf+ret); - ret += bq2415x_sysfs_print_reg(bq, BQ2415X_REG_CURRENT, buf+ret); - return ret; -} - -/* set current and voltage limit entries (in mA or mV) */ -static ssize_t bq2415x_sysfs_set_limit(struct device *dev, - struct device_attribute *attr, - const char *buf, - size_t count) -{ - struct power_supply *psy = dev_get_drvdata(dev); - struct bq2415x_device *bq = power_supply_get_drvdata(psy); - long val; - int ret; - - if (kstrtol(buf, 10, &val) < 0) - return -EINVAL; - - if (strcmp(attr->attr.name, "current_limit") == 0) - ret = bq2415x_set_current_limit(bq, val); - else if (strcmp(attr->attr.name, "weak_battery_voltage") == 0) - ret = bq2415x_set_weak_battery_voltage(bq, val); - else if (strcmp(attr->attr.name, "battery_regulation_voltage") == 0) - ret = bq2415x_set_battery_regulation_voltage(bq, val); - else if (strcmp(attr->attr.name, "charge_current") == 0) - ret = bq2415x_set_charge_current(bq, val); - else if (strcmp(attr->attr.name, "termination_current") == 0) - ret = bq2415x_set_termination_current(bq, val); - else - return -EINVAL; - - if (ret < 0) - return ret; - return count; -} - -/* show current and voltage limit entries (in mA or mV) */ -static ssize_t bq2415x_sysfs_show_limit(struct device *dev, - struct device_attribute *attr, - char *buf) -{ - struct power_supply *psy = dev_get_drvdata(dev); - struct bq2415x_device *bq = power_supply_get_drvdata(psy); - int ret; - - if (strcmp(attr->attr.name, "current_limit") == 0) - ret = bq2415x_get_current_limit(bq); - else if (strcmp(attr->attr.name, "weak_battery_voltage") == 0) - ret = bq2415x_get_weak_battery_voltage(bq); - else if (strcmp(attr->attr.name, "battery_regulation_voltage") == 0) - ret = bq2415x_get_battery_regulation_voltage(bq); - else if (strcmp(attr->attr.name, "charge_current") == 0) - ret = bq2415x_get_charge_current(bq); - else if (strcmp(attr->attr.name, "termination_current") == 0) - ret = bq2415x_get_termination_current(bq); - else - return -EINVAL; - - if (ret < 0) - return ret; - return sprintf(buf, "%d\n", ret); -} - -/* set *_enable entries */ -static ssize_t bq2415x_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 bq2415x_device *bq = power_supply_get_drvdata(psy); - enum bq2415x_command command; - long val; - int ret; - - if (kstrtol(buf, 10, &val) < 0) - return -EINVAL; - - if (strcmp(attr->attr.name, "charge_termination_enable") == 0) - command = val ? BQ2415X_CHARGE_TERMINATION_ENABLE : - BQ2415X_CHARGE_TERMINATION_DISABLE; - else if (strcmp(attr->attr.name, "high_impedance_enable") == 0) - command = val ? BQ2415X_HIGH_IMPEDANCE_ENABLE : - BQ2415X_HIGH_IMPEDANCE_DISABLE; - else if (strcmp(attr->attr.name, "otg_pin_enable") == 0) - command = val ? BQ2415X_OTG_PIN_ENABLE : - BQ2415X_OTG_PIN_DISABLE; - else if (strcmp(attr->attr.name, "stat_pin_enable") == 0) - command = val ? BQ2415X_STAT_PIN_ENABLE : - BQ2415X_STAT_PIN_DISABLE; - else - return -EINVAL; - - ret = bq2415x_exec_command(bq, command); - if (ret < 0) - return ret; - return count; -} - -/* show *_enable entries */ -static ssize_t bq2415x_sysfs_show_enable(struct device *dev, - struct device_attribute *attr, - char *buf) -{ - struct power_supply *psy = dev_get_drvdata(dev); - struct bq2415x_device *bq = power_supply_get_drvdata(psy); - enum bq2415x_command command; - int ret; - - if (strcmp(attr->attr.name, "charge_termination_enable") == 0) - command = BQ2415X_CHARGE_TERMINATION_STATUS; - else if (strcmp(attr->attr.name, "high_impedance_enable") == 0) - command = BQ2415X_HIGH_IMPEDANCE_STATUS; - else if (strcmp(attr->attr.name, "otg_pin_enable") == 0) - command = BQ2415X_OTG_PIN_STATUS; - else if (strcmp(attr->attr.name, "stat_pin_enable") == 0) - command = BQ2415X_STAT_PIN_STATUS; - else - return -EINVAL; - - ret = bq2415x_exec_command(bq, command); - if (ret < 0) - return ret; - return sprintf(buf, "%d\n", ret); -} - -static DEVICE_ATTR(current_limit, S_IWUSR | S_IRUGO, - bq2415x_sysfs_show_limit, bq2415x_sysfs_set_limit); -static DEVICE_ATTR(weak_battery_voltage, S_IWUSR | S_IRUGO, - bq2415x_sysfs_show_limit, bq2415x_sysfs_set_limit); -static DEVICE_ATTR(battery_regulation_voltage, S_IWUSR | S_IRUGO, - bq2415x_sysfs_show_limit, bq2415x_sysfs_set_limit); -static DEVICE_ATTR(charge_current, S_IWUSR | S_IRUGO, - bq2415x_sysfs_show_limit, bq2415x_sysfs_set_limit); -static DEVICE_ATTR(termination_current, S_IWUSR | S_IRUGO, - bq2415x_sysfs_show_limit, bq2415x_sysfs_set_limit); - -static DEVICE_ATTR(charge_termination_enable, S_IWUSR | S_IRUGO, - bq2415x_sysfs_show_enable, bq2415x_sysfs_set_enable); -static DEVICE_ATTR(high_impedance_enable, S_IWUSR | S_IRUGO, - bq2415x_sysfs_show_enable, bq2415x_sysfs_set_enable); -static DEVICE_ATTR(otg_pin_enable, S_IWUSR | S_IRUGO, - bq2415x_sysfs_show_enable, bq2415x_sysfs_set_enable); -static DEVICE_ATTR(stat_pin_enable, S_IWUSR | S_IRUGO, - bq2415x_sysfs_show_enable, bq2415x_sysfs_set_enable); - -static DEVICE_ATTR(reported_mode, S_IRUGO, - bq2415x_sysfs_show_reported_mode, NULL); -static DEVICE_ATTR(mode, S_IWUSR | S_IRUGO, - bq2415x_sysfs_show_mode, bq2415x_sysfs_set_mode); -static DEVICE_ATTR(timer, S_IWUSR | S_IRUGO, - bq2415x_sysfs_show_timer, bq2415x_sysfs_set_timer); - -static DEVICE_ATTR(registers, S_IWUSR | S_IRUGO, - bq2415x_sysfs_show_registers, bq2415x_sysfs_set_registers); - -static DEVICE_ATTR(otg_status, S_IRUGO, bq2415x_sysfs_show_status, NULL); -static DEVICE_ATTR(charge_status, S_IRUGO, bq2415x_sysfs_show_status, NULL); -static DEVICE_ATTR(boost_status, S_IRUGO, bq2415x_sysfs_show_status, NULL); -static DEVICE_ATTR(fault_status, S_IRUGO, bq2415x_sysfs_show_status, NULL); - -static struct attribute *bq2415x_sysfs_attributes[] = { - /* - * TODO: some (appropriate) of these attrs should be switched to - * use power supply class props. - */ - &dev_attr_current_limit.attr, - &dev_attr_weak_battery_voltage.attr, - &dev_attr_battery_regulation_voltage.attr, - &dev_attr_charge_current.attr, - &dev_attr_termination_current.attr, - - &dev_attr_charge_termination_enable.attr, - &dev_attr_high_impedance_enable.attr, - &dev_attr_otg_pin_enable.attr, - &dev_attr_stat_pin_enable.attr, - - &dev_attr_reported_mode.attr, - &dev_attr_mode.attr, - &dev_attr_timer.attr, - - &dev_attr_registers.attr, - - &dev_attr_otg_status.attr, - &dev_attr_charge_status.attr, - &dev_attr_boost_status.attr, - &dev_attr_fault_status.attr, - NULL, -}; - -static const struct attribute_group bq2415x_sysfs_attr_group = { - .attrs = bq2415x_sysfs_attributes, -}; - -static int bq2415x_sysfs_init(struct bq2415x_device *bq) -{ - return sysfs_create_group(&bq->charger->dev.kobj, - &bq2415x_sysfs_attr_group); -} - -static void bq2415x_sysfs_exit(struct bq2415x_device *bq) -{ - sysfs_remove_group(&bq->charger->dev.kobj, &bq2415x_sysfs_attr_group); -} - -/* main bq2415x probe function */ -static int bq2415x_probe(struct i2c_client *client, - const struct i2c_device_id *id) -{ - int ret; - int num; - char *name = NULL; - struct bq2415x_device *bq; - struct device_node *np = client->dev.of_node; - struct bq2415x_platform_data *pdata = client->dev.platform_data; - const struct acpi_device_id *acpi_id = NULL; - struct power_supply *notify_psy = NULL; - union power_supply_propval prop; - - if (!np && !pdata && !ACPI_HANDLE(&client->dev)) { - dev_err(&client->dev, "Neither devicetree, nor platform data, nor ACPI support\n"); - return -ENODEV; - } - - /* Get new ID for the new device */ - mutex_lock(&bq2415x_id_mutex); - num = idr_alloc(&bq2415x_id, client, 0, 0, GFP_KERNEL); - mutex_unlock(&bq2415x_id_mutex); - if (num < 0) - return num; - - if (id) { - name = kasprintf(GFP_KERNEL, "%s-%d", id->name, num); - } else if (ACPI_HANDLE(&client->dev)) { - acpi_id = - acpi_match_device(client->dev.driver->acpi_match_table, - &client->dev); - name = kasprintf(GFP_KERNEL, "%s-%d", acpi_id->id, num); - } - if (!name) { - dev_err(&client->dev, "failed to allocate device name\n"); - ret = -ENOMEM; - goto error_1; - } - - bq = devm_kzalloc(&client->dev, sizeof(*bq), GFP_KERNEL); - if (!bq) { - ret = -ENOMEM; - goto error_2; - } - - i2c_set_clientdata(client, bq); - - bq->id = num; - bq->dev = &client->dev; - if (id) - bq->chip = id->driver_data; - else if (ACPI_HANDLE(bq->dev)) - bq->chip = acpi_id->driver_data; - bq->name = name; - bq->mode = BQ2415X_MODE_OFF; - bq->reported_mode = BQ2415X_MODE_OFF; - bq->autotimer = 0; - bq->automode = 0; - - if (np || ACPI_HANDLE(bq->dev)) { - ret = device_property_read_u32(bq->dev, - "ti,current-limit", - &bq->init_data.current_limit); - if (ret) - goto error_2; - ret = device_property_read_u32(bq->dev, - "ti,weak-battery-voltage", - &bq->init_data.weak_battery_voltage); - if (ret) - goto error_2; - ret = device_property_read_u32(bq->dev, - "ti,battery-regulation-voltage", - &bq->init_data.battery_regulation_voltage); - if (ret) - goto error_2; - ret = device_property_read_u32(bq->dev, - "ti,charge-current", - &bq->init_data.charge_current); - if (ret) - goto error_2; - ret = device_property_read_u32(bq->dev, - "ti,termination-current", - &bq->init_data.termination_current); - if (ret) - goto error_2; - ret = device_property_read_u32(bq->dev, - "ti,resistor-sense", - &bq->init_data.resistor_sense); - if (ret) - goto error_2; - if (np) - bq->notify_node = of_parse_phandle(np, - "ti,usb-charger-detection", 0); - } else { - memcpy(&bq->init_data, pdata, sizeof(bq->init_data)); - } - - bq2415x_reset_chip(bq); - - ret = bq2415x_power_supply_init(bq); - if (ret) { - dev_err(bq->dev, "failed to register power supply: %d\n", ret); - goto error_2; - } - - ret = bq2415x_sysfs_init(bq); - if (ret) { - dev_err(bq->dev, "failed to create sysfs entries: %d\n", ret); - goto error_3; - } - - ret = bq2415x_set_defaults(bq); - if (ret) { - dev_err(bq->dev, "failed to set default values: %d\n", ret); - goto error_4; - } - - if (bq->notify_node || bq->init_data.notify_device) { - bq->nb.notifier_call = bq2415x_notifier_call; - ret = power_supply_reg_notifier(&bq->nb); - if (ret) { - dev_err(bq->dev, "failed to reg notifier: %d\n", ret); - goto error_4; - } - - bq->automode = 1; - dev_info(bq->dev, "automode supported, waiting for events\n"); - } else { - bq->automode = -1; - dev_info(bq->dev, "automode not supported\n"); - } - - /* Query for initial reported_mode and set it */ - if (bq->nb.notifier_call) { - if (np) { - notify_psy = power_supply_get_by_phandle(np, - "ti,usb-charger-detection"); - if (IS_ERR(notify_psy)) - notify_psy = NULL; - } else if (bq->init_data.notify_device) { - notify_psy = power_supply_get_by_name( - bq->init_data.notify_device); - } - } - if (notify_psy) { - ret = power_supply_get_property(notify_psy, - POWER_SUPPLY_PROP_CURRENT_MAX, &prop); - power_supply_put(notify_psy); - - if (ret == 0) { - bq2415x_update_reported_mode(bq, prop.intval); - bq2415x_set_mode(bq, bq->reported_mode); - } - } - - INIT_DELAYED_WORK(&bq->work, bq2415x_timer_work); - bq2415x_set_autotimer(bq, 1); - - dev_info(bq->dev, "driver registered\n"); - return 0; - -error_4: - bq2415x_sysfs_exit(bq); -error_3: - bq2415x_power_supply_exit(bq); -error_2: - if (bq) - of_node_put(bq->notify_node); - kfree(name); -error_1: - mutex_lock(&bq2415x_id_mutex); - idr_remove(&bq2415x_id, num); - mutex_unlock(&bq2415x_id_mutex); - - return ret; -} - -/* main bq2415x remove function */ - -static int bq2415x_remove(struct i2c_client *client) -{ - struct bq2415x_device *bq = i2c_get_clientdata(client); - - if (bq->nb.notifier_call) - power_supply_unreg_notifier(&bq->nb); - - of_node_put(bq->notify_node); - bq2415x_sysfs_exit(bq); - bq2415x_power_supply_exit(bq); - - bq2415x_reset_chip(bq); - - mutex_lock(&bq2415x_id_mutex); - idr_remove(&bq2415x_id, bq->id); - mutex_unlock(&bq2415x_id_mutex); - - dev_info(bq->dev, "driver unregistered\n"); - - kfree(bq->name); - - return 0; -} - -static const struct i2c_device_id bq2415x_i2c_id_table[] = { - { "bq2415x", BQUNKNOWN }, - { "bq24150", BQ24150 }, - { "bq24150a", BQ24150A }, - { "bq24151", BQ24151 }, - { "bq24151a", BQ24151A }, - { "bq24152", BQ24152 }, - { "bq24153", BQ24153 }, - { "bq24153a", BQ24153A }, - { "bq24155", BQ24155 }, - { "bq24156", BQ24156 }, - { "bq24156a", BQ24156A }, - { "bq24157s", BQ24157S }, - { "bq24158", BQ24158 }, - {}, -}; -MODULE_DEVICE_TABLE(i2c, bq2415x_i2c_id_table); - -#ifdef CONFIG_ACPI -static const struct acpi_device_id bq2415x_i2c_acpi_match[] = { - { "BQ2415X", BQUNKNOWN }, - { "BQ241500", BQ24150 }, - { "BQA24150", BQ24150A }, - { "BQ241510", BQ24151 }, - { "BQA24151", BQ24151A }, - { "BQ241520", BQ24152 }, - { "BQ241530", BQ24153 }, - { "BQA24153", BQ24153A }, - { "BQ241550", BQ24155 }, - { "BQ241560", BQ24156 }, - { "BQA24156", BQ24156A }, - { "BQS24157", BQ24157S }, - { "BQ241580", BQ24158 }, - {}, -}; -MODULE_DEVICE_TABLE(acpi, bq2415x_i2c_acpi_match); -#endif - -#ifdef CONFIG_OF -static const struct of_device_id bq2415x_of_match_table[] = { - { .compatible = "ti,bq24150" }, - { .compatible = "ti,bq24150a" }, - { .compatible = "ti,bq24151" }, - { .compatible = "ti,bq24151a" }, - { .compatible = "ti,bq24152" }, - { .compatible = "ti,bq24153" }, - { .compatible = "ti,bq24153a" }, - { .compatible = "ti,bq24155" }, - { .compatible = "ti,bq24156" }, - { .compatible = "ti,bq24156a" }, - { .compatible = "ti,bq24157s" }, - { .compatible = "ti,bq24158" }, - {}, -}; -MODULE_DEVICE_TABLE(of, bq2415x_of_match_table); -#endif - -static struct i2c_driver bq2415x_driver = { - .driver = { - .name = "bq2415x-charger", - .of_match_table = of_match_ptr(bq2415x_of_match_table), - .acpi_match_table = ACPI_PTR(bq2415x_i2c_acpi_match), - }, - .probe = bq2415x_probe, - .remove = bq2415x_remove, - .id_table = bq2415x_i2c_id_table, -}; -module_i2c_driver(bq2415x_driver); - -MODULE_AUTHOR("Pali Rohár "); -MODULE_DESCRIPTION("bq2415x charger driver"); -MODULE_LICENSE("GPL"); diff --git a/drivers/power/bq24190_charger.c b/drivers/power/bq24190_charger.c deleted file mode 100644 index f5746b9f4e83..000000000000 --- a/drivers/power/bq24190_charger.c +++ /dev/null @@ -1,1546 +0,0 @@ -/* - * Driver for the TI bq24190 battery charger. - * - * Author: Mark A. Greer - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - - -#define BQ24190_MANUFACTURER "Texas Instruments" - -#define BQ24190_REG_ISC 0x00 /* Input Source Control */ -#define BQ24190_REG_ISC_EN_HIZ_MASK BIT(7) -#define BQ24190_REG_ISC_EN_HIZ_SHIFT 7 -#define BQ24190_REG_ISC_VINDPM_MASK (BIT(6) | BIT(5) | BIT(4) | \ - BIT(3)) -#define BQ24190_REG_ISC_VINDPM_SHIFT 3 -#define BQ24190_REG_ISC_IINLIM_MASK (BIT(2) | BIT(1) | BIT(0)) -#define BQ24190_REG_ISC_IINLIM_SHIFT 0 - -#define BQ24190_REG_POC 0x01 /* Power-On Configuration */ -#define BQ24190_REG_POC_RESET_MASK BIT(7) -#define BQ24190_REG_POC_RESET_SHIFT 7 -#define BQ24190_REG_POC_WDT_RESET_MASK BIT(6) -#define BQ24190_REG_POC_WDT_RESET_SHIFT 6 -#define BQ24190_REG_POC_CHG_CONFIG_MASK (BIT(5) | BIT(4)) -#define BQ24190_REG_POC_CHG_CONFIG_SHIFT 4 -#define BQ24190_REG_POC_SYS_MIN_MASK (BIT(3) | BIT(2) | BIT(1)) -#define BQ24190_REG_POC_SYS_MIN_SHIFT 1 -#define BQ24190_REG_POC_BOOST_LIM_MASK BIT(0) -#define BQ24190_REG_POC_BOOST_LIM_SHIFT 0 - -#define BQ24190_REG_CCC 0x02 /* Charge Current Control */ -#define BQ24190_REG_CCC_ICHG_MASK (BIT(7) | BIT(6) | BIT(5) | \ - BIT(4) | BIT(3) | BIT(2)) -#define BQ24190_REG_CCC_ICHG_SHIFT 2 -#define BQ24190_REG_CCC_FORCE_20PCT_MASK BIT(0) -#define BQ24190_REG_CCC_FORCE_20PCT_SHIFT 0 - -#define BQ24190_REG_PCTCC 0x03 /* Pre-charge/Termination Current Cntl */ -#define BQ24190_REG_PCTCC_IPRECHG_MASK (BIT(7) | BIT(6) | BIT(5) | \ - BIT(4)) -#define BQ24190_REG_PCTCC_IPRECHG_SHIFT 4 -#define BQ24190_REG_PCTCC_ITERM_MASK (BIT(3) | BIT(2) | BIT(1) | \ - BIT(0)) -#define BQ24190_REG_PCTCC_ITERM_SHIFT 0 - -#define BQ24190_REG_CVC 0x04 /* Charge Voltage Control */ -#define BQ24190_REG_CVC_VREG_MASK (BIT(7) | BIT(6) | BIT(5) | \ - BIT(4) | BIT(3) | BIT(2)) -#define BQ24190_REG_CVC_VREG_SHIFT 2 -#define BQ24190_REG_CVC_BATLOWV_MASK BIT(1) -#define BQ24190_REG_CVC_BATLOWV_SHIFT 1 -#define BQ24190_REG_CVC_VRECHG_MASK BIT(0) -#define BQ24190_REG_CVC_VRECHG_SHIFT 0 - -#define BQ24190_REG_CTTC 0x05 /* Charge Term/Timer Control */ -#define BQ24190_REG_CTTC_EN_TERM_MASK BIT(7) -#define BQ24190_REG_CTTC_EN_TERM_SHIFT 7 -#define BQ24190_REG_CTTC_TERM_STAT_MASK BIT(6) -#define BQ24190_REG_CTTC_TERM_STAT_SHIFT 6 -#define BQ24190_REG_CTTC_WATCHDOG_MASK (BIT(5) | BIT(4)) -#define BQ24190_REG_CTTC_WATCHDOG_SHIFT 4 -#define BQ24190_REG_CTTC_EN_TIMER_MASK BIT(3) -#define BQ24190_REG_CTTC_EN_TIMER_SHIFT 3 -#define BQ24190_REG_CTTC_CHG_TIMER_MASK (BIT(2) | BIT(1)) -#define BQ24190_REG_CTTC_CHG_TIMER_SHIFT 1 -#define BQ24190_REG_CTTC_JEITA_ISET_MASK BIT(0) -#define BQ24190_REG_CTTC_JEITA_ISET_SHIFT 0 - -#define BQ24190_REG_ICTRC 0x06 /* IR Comp/Thermal Regulation Control */ -#define BQ24190_REG_ICTRC_BAT_COMP_MASK (BIT(7) | BIT(6) | BIT(5)) -#define BQ24190_REG_ICTRC_BAT_COMP_SHIFT 5 -#define BQ24190_REG_ICTRC_VCLAMP_MASK (BIT(4) | BIT(3) | BIT(2)) -#define BQ24190_REG_ICTRC_VCLAMP_SHIFT 2 -#define BQ24190_REG_ICTRC_TREG_MASK (BIT(1) | BIT(0)) -#define BQ24190_REG_ICTRC_TREG_SHIFT 0 - -#define BQ24190_REG_MOC 0x07 /* Misc. Operation Control */ -#define BQ24190_REG_MOC_DPDM_EN_MASK BIT(7) -#define BQ24190_REG_MOC_DPDM_EN_SHIFT 7 -#define BQ24190_REG_MOC_TMR2X_EN_MASK BIT(6) -#define BQ24190_REG_MOC_TMR2X_EN_SHIFT 6 -#define BQ24190_REG_MOC_BATFET_DISABLE_MASK BIT(5) -#define BQ24190_REG_MOC_BATFET_DISABLE_SHIFT 5 -#define BQ24190_REG_MOC_JEITA_VSET_MASK BIT(4) -#define BQ24190_REG_MOC_JEITA_VSET_SHIFT 4 -#define BQ24190_REG_MOC_INT_MASK_MASK (BIT(1) | BIT(0)) -#define BQ24190_REG_MOC_INT_MASK_SHIFT 0 - -#define BQ24190_REG_SS 0x08 /* System Status */ -#define BQ24190_REG_SS_VBUS_STAT_MASK (BIT(7) | BIT(6)) -#define BQ24190_REG_SS_VBUS_STAT_SHIFT 6 -#define BQ24190_REG_SS_CHRG_STAT_MASK (BIT(5) | BIT(4)) -#define BQ24190_REG_SS_CHRG_STAT_SHIFT 4 -#define BQ24190_REG_SS_DPM_STAT_MASK BIT(3) -#define BQ24190_REG_SS_DPM_STAT_SHIFT 3 -#define BQ24190_REG_SS_PG_STAT_MASK BIT(2) -#define BQ24190_REG_SS_PG_STAT_SHIFT 2 -#define BQ24190_REG_SS_THERM_STAT_MASK BIT(1) -#define BQ24190_REG_SS_THERM_STAT_SHIFT 1 -#define BQ24190_REG_SS_VSYS_STAT_MASK BIT(0) -#define BQ24190_REG_SS_VSYS_STAT_SHIFT 0 - -#define BQ24190_REG_F 0x09 /* Fault */ -#define BQ24190_REG_F_WATCHDOG_FAULT_MASK BIT(7) -#define BQ24190_REG_F_WATCHDOG_FAULT_SHIFT 7 -#define BQ24190_REG_F_BOOST_FAULT_MASK BIT(6) -#define BQ24190_REG_F_BOOST_FAULT_SHIFT 6 -#define BQ24190_REG_F_CHRG_FAULT_MASK (BIT(5) | BIT(4)) -#define BQ24190_REG_F_CHRG_FAULT_SHIFT 4 -#define BQ24190_REG_F_BAT_FAULT_MASK BIT(3) -#define BQ24190_REG_F_BAT_FAULT_SHIFT 3 -#define BQ24190_REG_F_NTC_FAULT_MASK (BIT(2) | BIT(1) | BIT(0)) -#define BQ24190_REG_F_NTC_FAULT_SHIFT 0 - -#define BQ24190_REG_VPRS 0x0A /* Vendor/Part/Revision Status */ -#define BQ24190_REG_VPRS_PN_MASK (BIT(5) | BIT(4) | BIT(3)) -#define BQ24190_REG_VPRS_PN_SHIFT 3 -#define BQ24190_REG_VPRS_PN_24190 0x4 -#define BQ24190_REG_VPRS_PN_24192 0x5 /* Also 24193 */ -#define BQ24190_REG_VPRS_PN_24192I 0x3 -#define BQ24190_REG_VPRS_TS_PROFILE_MASK BIT(2) -#define BQ24190_REG_VPRS_TS_PROFILE_SHIFT 2 -#define BQ24190_REG_VPRS_DEV_REG_MASK (BIT(1) | BIT(0)) -#define BQ24190_REG_VPRS_DEV_REG_SHIFT 0 - -/* - * The FAULT register is latched by the bq24190 (except for NTC_FAULT) - * 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. - */ -struct bq24190_dev_info { - struct i2c_client *client; - struct device *dev; - struct power_supply *charger; - struct power_supply *battery; - char model_name[I2C_NAME_SIZE]; - kernel_ulong_t model; - 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; -}; - -/* - * The tables below provide a 2-way mapping for the value that goes in - * the register field and the real-world value that it represents. - * The index of the array is the value that goes in the register; the - * number at that index in the array is the real-world value that it - * represents. - */ -/* REG02[7:2] (ICHG) in uAh */ -static const int bq24190_ccc_ichg_values[] = { - 512000, 576000, 640000, 704000, 768000, 832000, 896000, 960000, - 1024000, 1088000, 1152000, 1216000, 1280000, 1344000, 1408000, 1472000, - 1536000, 1600000, 1664000, 1728000, 1792000, 1856000, 1920000, 1984000, - 2048000, 2112000, 2176000, 2240000, 2304000, 2368000, 2432000, 2496000, - 2560000, 2624000, 2688000, 2752000, 2816000, 2880000, 2944000, 3008000, - 3072000, 3136000, 3200000, 3264000, 3328000, 3392000, 3456000, 3520000, - 3584000, 3648000, 3712000, 3776000, 3840000, 3904000, 3968000, 4032000, - 4096000, 4160000, 4224000, 4288000, 4352000, 4416000, 4480000, 4544000 -}; - -/* REG04[7:2] (VREG) in uV */ -static const int bq24190_cvc_vreg_values[] = { - 3504000, 3520000, 3536000, 3552000, 3568000, 3584000, 3600000, 3616000, - 3632000, 3648000, 3664000, 3680000, 3696000, 3712000, 3728000, 3744000, - 3760000, 3776000, 3792000, 3808000, 3824000, 3840000, 3856000, 3872000, - 3888000, 3904000, 3920000, 3936000, 3952000, 3968000, 3984000, 4000000, - 4016000, 4032000, 4048000, 4064000, 4080000, 4096000, 4112000, 4128000, - 4144000, 4160000, 4176000, 4192000, 4208000, 4224000, 4240000, 4256000, - 4272000, 4288000, 4304000, 4320000, 4336000, 4352000, 4368000, 4384000, - 4400000 -}; - -/* REG06[1:0] (TREG) in tenths of degrees Celcius */ -static const int bq24190_ictrc_treg_values[] = { - 600, 800, 1000, 1200 -}; - -/* - * Return the index in 'tbl' of greatest value that is less than or equal to - * 'val'. The index range returned is 0 to 'tbl_size' - 1. Assumes that - * the values in 'tbl' are sorted from smallest to largest and 'tbl_size' - * is less than 2^8. - */ -static u8 bq24190_find_idx(const int tbl[], int tbl_size, int v) -{ - int i; - - for (i = 1; i < tbl_size; i++) - if (v < tbl[i]) - break; - - return i - 1; -} - -/* Basic driver I/O routines */ - -static int bq24190_read(struct bq24190_dev_info *bdi, u8 reg, u8 *data) -{ - int ret; - - ret = i2c_smbus_read_byte_data(bdi->client, reg); - if (ret < 0) - return ret; - - *data = ret; - return 0; -} - -static int bq24190_write(struct bq24190_dev_info *bdi, u8 reg, u8 data) -{ - return i2c_smbus_write_byte_data(bdi->client, reg, data); -} - -static int bq24190_read_mask(struct bq24190_dev_info *bdi, u8 reg, - u8 mask, u8 shift, u8 *data) -{ - u8 v; - int ret; - - ret = bq24190_read(bdi, reg, &v); - if (ret < 0) - return ret; - - v &= mask; - v >>= shift; - *data = v; - - return 0; -} - -static int bq24190_write_mask(struct bq24190_dev_info *bdi, u8 reg, - u8 mask, u8 shift, u8 data) -{ - u8 v; - int ret; - - ret = bq24190_read(bdi, reg, &v); - if (ret < 0) - return ret; - - v &= ~mask; - v |= ((data << shift) & mask); - - return bq24190_write(bdi, reg, v); -} - -static int bq24190_get_field_val(struct bq24190_dev_info *bdi, - u8 reg, u8 mask, u8 shift, - const int tbl[], int tbl_size, - int *val) -{ - u8 v; - int ret; - - ret = bq24190_read_mask(bdi, reg, mask, shift, &v); - if (ret < 0) - return ret; - - v = (v >= tbl_size) ? (tbl_size - 1) : v; - *val = tbl[v]; - - return 0; -} - -static int bq24190_set_field_val(struct bq24190_dev_info *bdi, - u8 reg, u8 mask, u8 shift, - const int tbl[], int tbl_size, - int val) -{ - u8 idx; - - idx = bq24190_find_idx(tbl, tbl_size, val); - - return bq24190_write_mask(bdi, reg, mask, shift, idx); -} - -#ifdef CONFIG_SYSFS -/* - * There are a numerous options that are configurable on the bq24190 - * that go well beyond what the power_supply properties provide access to. - * Provide sysfs access to them so they can be examined and possibly modified - * on the fly. They will be provided for the charger power_supply object only - * and will be prefixed by 'f_' to make them easier to recognize. - */ - -#define BQ24190_SYSFS_FIELD(_name, r, f, m, store) \ -{ \ - .attr = __ATTR(f_##_name, m, bq24190_sysfs_show, store), \ - .reg = BQ24190_REG_##r, \ - .mask = BQ24190_REG_##r##_##f##_MASK, \ - .shift = BQ24190_REG_##r##_##f##_SHIFT, \ -} - -#define BQ24190_SYSFS_FIELD_RW(_name, r, f) \ - BQ24190_SYSFS_FIELD(_name, r, f, S_IWUSR | S_IRUGO, \ - bq24190_sysfs_store) - -#define BQ24190_SYSFS_FIELD_RO(_name, r, f) \ - BQ24190_SYSFS_FIELD(_name, r, f, S_IRUGO, NULL) - -static ssize_t bq24190_sysfs_show(struct device *dev, - struct device_attribute *attr, char *buf); -static ssize_t bq24190_sysfs_store(struct device *dev, - struct device_attribute *attr, const char *buf, size_t count); - -struct bq24190_sysfs_field_info { - struct device_attribute attr; - u8 reg; - u8 mask; - u8 shift; -}; - -/* On i386 ptrace-abi.h defines SS that breaks the macro calls below. */ -#undef SS - -static struct bq24190_sysfs_field_info bq24190_sysfs_field_tbl[] = { - /* sysfs name reg field in reg */ - BQ24190_SYSFS_FIELD_RW(en_hiz, ISC, EN_HIZ), - BQ24190_SYSFS_FIELD_RW(vindpm, ISC, VINDPM), - BQ24190_SYSFS_FIELD_RW(iinlim, ISC, IINLIM), - BQ24190_SYSFS_FIELD_RW(chg_config, POC, CHG_CONFIG), - BQ24190_SYSFS_FIELD_RW(sys_min, POC, SYS_MIN), - BQ24190_SYSFS_FIELD_RW(boost_lim, POC, BOOST_LIM), - BQ24190_SYSFS_FIELD_RW(ichg, CCC, ICHG), - BQ24190_SYSFS_FIELD_RW(force_20_pct, CCC, FORCE_20PCT), - BQ24190_SYSFS_FIELD_RW(iprechg, PCTCC, IPRECHG), - BQ24190_SYSFS_FIELD_RW(iterm, PCTCC, ITERM), - BQ24190_SYSFS_FIELD_RW(vreg, CVC, VREG), - BQ24190_SYSFS_FIELD_RW(batlowv, CVC, BATLOWV), - BQ24190_SYSFS_FIELD_RW(vrechg, CVC, VRECHG), - BQ24190_SYSFS_FIELD_RW(en_term, CTTC, EN_TERM), - BQ24190_SYSFS_FIELD_RW(term_stat, CTTC, TERM_STAT), - BQ24190_SYSFS_FIELD_RO(watchdog, CTTC, WATCHDOG), - BQ24190_SYSFS_FIELD_RW(en_timer, CTTC, EN_TIMER), - BQ24190_SYSFS_FIELD_RW(chg_timer, CTTC, CHG_TIMER), - BQ24190_SYSFS_FIELD_RW(jeta_iset, CTTC, JEITA_ISET), - BQ24190_SYSFS_FIELD_RW(bat_comp, ICTRC, BAT_COMP), - BQ24190_SYSFS_FIELD_RW(vclamp, ICTRC, VCLAMP), - BQ24190_SYSFS_FIELD_RW(treg, ICTRC, TREG), - BQ24190_SYSFS_FIELD_RW(dpdm_en, MOC, DPDM_EN), - BQ24190_SYSFS_FIELD_RW(tmr2x_en, MOC, TMR2X_EN), - BQ24190_SYSFS_FIELD_RW(batfet_disable, MOC, BATFET_DISABLE), - BQ24190_SYSFS_FIELD_RW(jeita_vset, MOC, JEITA_VSET), - BQ24190_SYSFS_FIELD_RO(int_mask, MOC, INT_MASK), - BQ24190_SYSFS_FIELD_RO(vbus_stat, SS, VBUS_STAT), - BQ24190_SYSFS_FIELD_RO(chrg_stat, SS, CHRG_STAT), - BQ24190_SYSFS_FIELD_RO(dpm_stat, SS, DPM_STAT), - BQ24190_SYSFS_FIELD_RO(pg_stat, SS, PG_STAT), - BQ24190_SYSFS_FIELD_RO(therm_stat, SS, THERM_STAT), - BQ24190_SYSFS_FIELD_RO(vsys_stat, SS, VSYS_STAT), - BQ24190_SYSFS_FIELD_RO(watchdog_fault, F, WATCHDOG_FAULT), - BQ24190_SYSFS_FIELD_RO(boost_fault, F, BOOST_FAULT), - BQ24190_SYSFS_FIELD_RO(chrg_fault, F, CHRG_FAULT), - BQ24190_SYSFS_FIELD_RO(bat_fault, F, BAT_FAULT), - BQ24190_SYSFS_FIELD_RO(ntc_fault, F, NTC_FAULT), - BQ24190_SYSFS_FIELD_RO(pn, VPRS, PN), - BQ24190_SYSFS_FIELD_RO(ts_profile, VPRS, TS_PROFILE), - BQ24190_SYSFS_FIELD_RO(dev_reg, VPRS, DEV_REG), -}; - -static struct attribute * - bq24190_sysfs_attrs[ARRAY_SIZE(bq24190_sysfs_field_tbl) + 1]; - -static const struct attribute_group bq24190_sysfs_attr_group = { - .attrs = bq24190_sysfs_attrs, -}; - -static void bq24190_sysfs_init_attrs(void) -{ - int i, limit = ARRAY_SIZE(bq24190_sysfs_field_tbl); - - for (i = 0; i < limit; i++) - bq24190_sysfs_attrs[i] = &bq24190_sysfs_field_tbl[i].attr.attr; - - bq24190_sysfs_attrs[limit] = NULL; /* Has additional entry for this */ -} - -static struct bq24190_sysfs_field_info *bq24190_sysfs_field_lookup( - const char *name) -{ - int i, limit = ARRAY_SIZE(bq24190_sysfs_field_tbl); - - for (i = 0; i < limit; i++) - if (!strcmp(name, bq24190_sysfs_field_tbl[i].attr.attr.name)) - break; - - if (i >= limit) - return NULL; - - return &bq24190_sysfs_field_tbl[i]; -} - -static ssize_t bq24190_sysfs_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - struct power_supply *psy = dev_get_drvdata(dev); - struct bq24190_dev_info *bdi = power_supply_get_drvdata(psy); - struct bq24190_sysfs_field_info *info; - int ret; - u8 v; - - info = bq24190_sysfs_field_lookup(attr->attr.name); - if (!info) - return -EINVAL; - - ret = bq24190_read_mask(bdi, info->reg, info->mask, info->shift, &v); - if (ret) - return ret; - - return scnprintf(buf, PAGE_SIZE, "%hhx\n", v); -} - -static ssize_t bq24190_sysfs_store(struct device *dev, - struct device_attribute *attr, const char *buf, size_t count) -{ - struct power_supply *psy = dev_get_drvdata(dev); - struct bq24190_dev_info *bdi = power_supply_get_drvdata(psy); - struct bq24190_sysfs_field_info *info; - int ret; - u8 v; - - info = bq24190_sysfs_field_lookup(attr->attr.name); - if (!info) - return -EINVAL; - - ret = kstrtou8(buf, 0, &v); - if (ret < 0) - return ret; - - ret = bq24190_write_mask(bdi, info->reg, info->mask, info->shift, v); - if (ret) - return ret; - - return count; -} - -static int bq24190_sysfs_create_group(struct bq24190_dev_info *bdi) -{ - bq24190_sysfs_init_attrs(); - - return sysfs_create_group(&bdi->charger->dev.kobj, - &bq24190_sysfs_attr_group); -} - -static void bq24190_sysfs_remove_group(struct bq24190_dev_info *bdi) -{ - sysfs_remove_group(&bdi->charger->dev.kobj, &bq24190_sysfs_attr_group); -} -#else -static int bq24190_sysfs_create_group(struct bq24190_dev_info *bdi) -{ - return 0; -} - -static inline void bq24190_sysfs_remove_group(struct bq24190_dev_info *bdi) {} -#endif - -/* - * According to the "Host Mode and default Mode" section of the - * manual, a write to any register causes the bq24190 to switch - * from default mode to host mode. It will switch back to default - * mode after a WDT timeout unless the WDT is turned off as well. - * So, by simply turning off the WDT, we accomplish both with the - * same write. - */ -static int bq24190_set_mode_host(struct bq24190_dev_info *bdi) -{ - int ret; - u8 v; - - ret = bq24190_read(bdi, BQ24190_REG_CTTC, &v); - if (ret < 0) - return ret; - - bdi->watchdog = ((v & BQ24190_REG_CTTC_WATCHDOG_MASK) >> - BQ24190_REG_CTTC_WATCHDOG_SHIFT); - v &= ~BQ24190_REG_CTTC_WATCHDOG_MASK; - - return bq24190_write(bdi, BQ24190_REG_CTTC, v); -} - -static int bq24190_register_reset(struct bq24190_dev_info *bdi) -{ - int ret, limit = 100; - u8 v; - - /* Reset the registers */ - ret = bq24190_write_mask(bdi, BQ24190_REG_POC, - BQ24190_REG_POC_RESET_MASK, - BQ24190_REG_POC_RESET_SHIFT, - 0x1); - if (ret < 0) - return ret; - - /* Reset bit will be cleared by hardware so poll until it is */ - do { - ret = bq24190_read_mask(bdi, BQ24190_REG_POC, - BQ24190_REG_POC_RESET_MASK, - BQ24190_REG_POC_RESET_SHIFT, - &v); - if (ret < 0) - return ret; - - if (!v) - break; - - udelay(10); - } while (--limit); - - if (!limit) - return -EIO; - - return 0; -} - -/* Charger power supply property routines */ - -static int bq24190_charger_get_charge_type(struct bq24190_dev_info *bdi, - union power_supply_propval *val) -{ - u8 v; - int type, ret; - - ret = bq24190_read_mask(bdi, BQ24190_REG_POC, - BQ24190_REG_POC_CHG_CONFIG_MASK, - BQ24190_REG_POC_CHG_CONFIG_SHIFT, - &v); - if (ret < 0) - return ret; - - /* If POC[CHG_CONFIG] (REG01[5:4]) == 0, charge is disabled */ - if (!v) { - type = POWER_SUPPLY_CHARGE_TYPE_NONE; - } else { - ret = bq24190_read_mask(bdi, BQ24190_REG_CCC, - BQ24190_REG_CCC_FORCE_20PCT_MASK, - BQ24190_REG_CCC_FORCE_20PCT_SHIFT, - &v); - if (ret < 0) - return ret; - - type = (v) ? POWER_SUPPLY_CHARGE_TYPE_TRICKLE : - POWER_SUPPLY_CHARGE_TYPE_FAST; - } - - val->intval = type; - - return 0; -} - -static int bq24190_charger_set_charge_type(struct bq24190_dev_info *bdi, - const union power_supply_propval *val) -{ - u8 chg_config, force_20pct, en_term; - int ret; - - /* - * According to the "Termination when REG02[0] = 1" section of - * the bq24190 manual, the trickle charge could be less than the - * termination current so it recommends turning off the termination - * function. - * - * Note: AFAICT from the datasheet, the user will have to manually - * turn off the charging when in 20% mode. If its not turned off, - * there could be battery damage. So, use this mode at your own risk. - */ - switch (val->intval) { - case POWER_SUPPLY_CHARGE_TYPE_NONE: - chg_config = 0x0; - break; - case POWER_SUPPLY_CHARGE_TYPE_TRICKLE: - chg_config = 0x1; - force_20pct = 0x1; - en_term = 0x0; - break; - case POWER_SUPPLY_CHARGE_TYPE_FAST: - chg_config = 0x1; - force_20pct = 0x0; - en_term = 0x1; - break; - default: - return -EINVAL; - } - - if (chg_config) { /* Enabling the charger */ - ret = bq24190_write_mask(bdi, BQ24190_REG_CCC, - BQ24190_REG_CCC_FORCE_20PCT_MASK, - BQ24190_REG_CCC_FORCE_20PCT_SHIFT, - force_20pct); - if (ret < 0) - return ret; - - ret = bq24190_write_mask(bdi, BQ24190_REG_CTTC, - BQ24190_REG_CTTC_EN_TERM_MASK, - BQ24190_REG_CTTC_EN_TERM_SHIFT, - en_term); - if (ret < 0) - return ret; - } - - return bq24190_write_mask(bdi, BQ24190_REG_POC, - BQ24190_REG_POC_CHG_CONFIG_MASK, - BQ24190_REG_POC_CHG_CONFIG_SHIFT, chg_config); -} - -static int bq24190_charger_get_health(struct bq24190_dev_info *bdi, - union power_supply_propval *val) -{ - u8 v; - int health, ret; - - 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 - * no way to tell which. Return 'OVERVOLTAGE' since there - * isn't an 'OVERCURRENT' value defined that we can return - * even if it was over-current. - */ - health = POWER_SUPPLY_HEALTH_OVERVOLTAGE; - } else { - v &= BQ24190_REG_F_CHRG_FAULT_MASK; - v >>= BQ24190_REG_F_CHRG_FAULT_SHIFT; - - switch (v) { - case 0x0: /* Normal */ - health = POWER_SUPPLY_HEALTH_GOOD; - break; - case 0x1: /* Input Fault (VBUS OVP or VBATintval = health; - - return 0; -} - -static int bq24190_charger_get_online(struct bq24190_dev_info *bdi, - union power_supply_propval *val) -{ - u8 v; - int ret; - - ret = bq24190_read_mask(bdi, BQ24190_REG_SS, - BQ24190_REG_SS_PG_STAT_MASK, - BQ24190_REG_SS_PG_STAT_SHIFT, &v); - if (ret < 0) - return ret; - - val->intval = v; - return 0; -} - -static int bq24190_charger_get_current(struct bq24190_dev_info *bdi, - union power_supply_propval *val) -{ - u8 v; - int curr, ret; - - ret = bq24190_get_field_val(bdi, BQ24190_REG_CCC, - BQ24190_REG_CCC_ICHG_MASK, BQ24190_REG_CCC_ICHG_SHIFT, - bq24190_ccc_ichg_values, - ARRAY_SIZE(bq24190_ccc_ichg_values), &curr); - if (ret < 0) - return ret; - - ret = bq24190_read_mask(bdi, BQ24190_REG_CCC, - BQ24190_REG_CCC_FORCE_20PCT_MASK, - BQ24190_REG_CCC_FORCE_20PCT_SHIFT, &v); - if (ret < 0) - return ret; - - /* If FORCE_20PCT is enabled, then current is 20% of ICHG value */ - if (v) - curr /= 5; - - val->intval = curr; - return 0; -} - -static int bq24190_charger_get_current_max(struct bq24190_dev_info *bdi, - union power_supply_propval *val) -{ - int idx = ARRAY_SIZE(bq24190_ccc_ichg_values) - 1; - - val->intval = bq24190_ccc_ichg_values[idx]; - return 0; -} - -static int bq24190_charger_set_current(struct bq24190_dev_info *bdi, - const union power_supply_propval *val) -{ - u8 v; - int ret, curr = val->intval; - - ret = bq24190_read_mask(bdi, BQ24190_REG_CCC, - BQ24190_REG_CCC_FORCE_20PCT_MASK, - BQ24190_REG_CCC_FORCE_20PCT_SHIFT, &v); - if (ret < 0) - return ret; - - /* If FORCE_20PCT is enabled, have to multiply value passed in by 5 */ - if (v) - curr *= 5; - - return bq24190_set_field_val(bdi, BQ24190_REG_CCC, - BQ24190_REG_CCC_ICHG_MASK, BQ24190_REG_CCC_ICHG_SHIFT, - bq24190_ccc_ichg_values, - ARRAY_SIZE(bq24190_ccc_ichg_values), curr); -} - -static int bq24190_charger_get_voltage(struct bq24190_dev_info *bdi, - union power_supply_propval *val) -{ - int voltage, ret; - - ret = bq24190_get_field_val(bdi, BQ24190_REG_CVC, - BQ24190_REG_CVC_VREG_MASK, BQ24190_REG_CVC_VREG_SHIFT, - bq24190_cvc_vreg_values, - ARRAY_SIZE(bq24190_cvc_vreg_values), &voltage); - if (ret < 0) - return ret; - - val->intval = voltage; - return 0; -} - -static int bq24190_charger_get_voltage_max(struct bq24190_dev_info *bdi, - union power_supply_propval *val) -{ - int idx = ARRAY_SIZE(bq24190_cvc_vreg_values) - 1; - - val->intval = bq24190_cvc_vreg_values[idx]; - return 0; -} - -static int bq24190_charger_set_voltage(struct bq24190_dev_info *bdi, - const union power_supply_propval *val) -{ - return bq24190_set_field_val(bdi, BQ24190_REG_CVC, - BQ24190_REG_CVC_VREG_MASK, BQ24190_REG_CVC_VREG_SHIFT, - bq24190_cvc_vreg_values, - ARRAY_SIZE(bq24190_cvc_vreg_values), val->intval); -} - -static int bq24190_charger_get_property(struct power_supply *psy, - enum power_supply_property psp, union power_supply_propval *val) -{ - struct bq24190_dev_info *bdi = power_supply_get_drvdata(psy); - int ret; - - dev_dbg(bdi->dev, "prop: %d\n", psp); - - pm_runtime_get_sync(bdi->dev); - - switch (psp) { - case POWER_SUPPLY_PROP_CHARGE_TYPE: - ret = bq24190_charger_get_charge_type(bdi, val); - break; - case POWER_SUPPLY_PROP_HEALTH: - ret = bq24190_charger_get_health(bdi, val); - break; - case POWER_SUPPLY_PROP_ONLINE: - ret = bq24190_charger_get_online(bdi, val); - break; - case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: - ret = bq24190_charger_get_current(bdi, val); - break; - case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX: - ret = bq24190_charger_get_current_max(bdi, val); - break; - case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: - ret = bq24190_charger_get_voltage(bdi, val); - break; - case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX: - ret = bq24190_charger_get_voltage_max(bdi, val); - break; - case POWER_SUPPLY_PROP_SCOPE: - val->intval = POWER_SUPPLY_SCOPE_SYSTEM; - ret = 0; - break; - case POWER_SUPPLY_PROP_MODEL_NAME: - val->strval = bdi->model_name; - ret = 0; - break; - case POWER_SUPPLY_PROP_MANUFACTURER: - val->strval = BQ24190_MANUFACTURER; - ret = 0; - break; - default: - ret = -ENODATA; - } - - pm_runtime_put_sync(bdi->dev); - return ret; -} - -static int bq24190_charger_set_property(struct power_supply *psy, - enum power_supply_property psp, - const union power_supply_propval *val) -{ - struct bq24190_dev_info *bdi = power_supply_get_drvdata(psy); - int ret; - - dev_dbg(bdi->dev, "prop: %d\n", psp); - - pm_runtime_get_sync(bdi->dev); - - switch (psp) { - case POWER_SUPPLY_PROP_CHARGE_TYPE: - ret = bq24190_charger_set_charge_type(bdi, val); - break; - case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: - ret = bq24190_charger_set_current(bdi, val); - break; - case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: - ret = bq24190_charger_set_voltage(bdi, val); - break; - default: - ret = -EINVAL; - } - - pm_runtime_put_sync(bdi->dev); - return ret; -} - -static int bq24190_charger_property_is_writeable(struct power_supply *psy, - enum power_supply_property psp) -{ - int ret; - - switch (psp) { - case POWER_SUPPLY_PROP_CHARGE_TYPE: - case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: - case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: - ret = 1; - break; - default: - ret = 0; - } - - return ret; -} - -static enum power_supply_property bq24190_charger_properties[] = { - POWER_SUPPLY_PROP_CHARGE_TYPE, - POWER_SUPPLY_PROP_HEALTH, - POWER_SUPPLY_PROP_ONLINE, - POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT, - POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX, - POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE, - POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX, - POWER_SUPPLY_PROP_SCOPE, - POWER_SUPPLY_PROP_MODEL_NAME, - POWER_SUPPLY_PROP_MANUFACTURER, -}; - -static char *bq24190_charger_supplied_to[] = { - "main-battery", -}; - -static const struct power_supply_desc bq24190_charger_desc = { - .name = "bq24190-charger", - .type = POWER_SUPPLY_TYPE_USB, - .properties = bq24190_charger_properties, - .num_properties = ARRAY_SIZE(bq24190_charger_properties), - .get_property = bq24190_charger_get_property, - .set_property = bq24190_charger_set_property, - .property_is_writeable = bq24190_charger_property_is_writeable, -}; - -/* Battery power supply property routines */ - -static int bq24190_battery_get_status(struct bq24190_dev_info *bdi, - union power_supply_propval *val) -{ - u8 ss_reg, chrg_fault; - 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; - - ret = bq24190_read(bdi, BQ24190_REG_SS, &ss_reg); - if (ret < 0) - return ret; - - /* - * The battery must be discharging when any of these are true: - * - there is no good power source; - * - there is a charge fault. - * Could also be discharging when in "supplement mode" but - * there is no way to tell when its in that mode. - */ - if (!(ss_reg & BQ24190_REG_SS_PG_STAT_MASK) || chrg_fault) { - status = POWER_SUPPLY_STATUS_DISCHARGING; - } else { - ss_reg &= BQ24190_REG_SS_CHRG_STAT_MASK; - ss_reg >>= BQ24190_REG_SS_CHRG_STAT_SHIFT; - - switch (ss_reg) { - case 0x0: /* Not Charging */ - status = POWER_SUPPLY_STATUS_NOT_CHARGING; - break; - case 0x1: /* Pre-charge */ - case 0x2: /* Fast Charging */ - status = POWER_SUPPLY_STATUS_CHARGING; - break; - case 0x3: /* Charge Termination Done */ - status = POWER_SUPPLY_STATUS_FULL; - break; - default: - ret = -EIO; - } - } - - if (!ret) - val->intval = status; - - return ret; -} - -static int bq24190_battery_get_health(struct bq24190_dev_info *bdi, - union power_supply_propval *val) -{ - u8 v; - int health, ret; - - 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; - } else { - v &= BQ24190_REG_F_NTC_FAULT_MASK; - v >>= BQ24190_REG_F_NTC_FAULT_SHIFT; - - switch (v) { - case 0x0: /* Normal */ - health = POWER_SUPPLY_HEALTH_GOOD; - break; - case 0x1: /* TS1 Cold */ - case 0x3: /* TS2 Cold */ - case 0x5: /* Both Cold */ - health = POWER_SUPPLY_HEALTH_COLD; - break; - case 0x2: /* TS1 Hot */ - case 0x4: /* TS2 Hot */ - case 0x6: /* Both Hot */ - health = POWER_SUPPLY_HEALTH_OVERHEAT; - break; - default: - health = POWER_SUPPLY_HEALTH_UNKNOWN; - } - } - - val->intval = health; - return 0; -} - -static int bq24190_battery_get_online(struct bq24190_dev_info *bdi, - union power_supply_propval *val) -{ - u8 batfet_disable; - int ret; - - ret = bq24190_read_mask(bdi, BQ24190_REG_MOC, - BQ24190_REG_MOC_BATFET_DISABLE_MASK, - BQ24190_REG_MOC_BATFET_DISABLE_SHIFT, &batfet_disable); - if (ret < 0) - return ret; - - val->intval = !batfet_disable; - return 0; -} - -static int bq24190_battery_set_online(struct bq24190_dev_info *bdi, - const union power_supply_propval *val) -{ - return bq24190_write_mask(bdi, BQ24190_REG_MOC, - BQ24190_REG_MOC_BATFET_DISABLE_MASK, - BQ24190_REG_MOC_BATFET_DISABLE_SHIFT, !val->intval); -} - -static int bq24190_battery_get_temp_alert_max(struct bq24190_dev_info *bdi, - union power_supply_propval *val) -{ - int temp, ret; - - ret = bq24190_get_field_val(bdi, BQ24190_REG_ICTRC, - BQ24190_REG_ICTRC_TREG_MASK, - BQ24190_REG_ICTRC_TREG_SHIFT, - bq24190_ictrc_treg_values, - ARRAY_SIZE(bq24190_ictrc_treg_values), &temp); - if (ret < 0) - return ret; - - val->intval = temp; - return 0; -} - -static int bq24190_battery_set_temp_alert_max(struct bq24190_dev_info *bdi, - const union power_supply_propval *val) -{ - return bq24190_set_field_val(bdi, BQ24190_REG_ICTRC, - BQ24190_REG_ICTRC_TREG_MASK, - BQ24190_REG_ICTRC_TREG_SHIFT, - bq24190_ictrc_treg_values, - ARRAY_SIZE(bq24190_ictrc_treg_values), val->intval); -} - -static int bq24190_battery_get_property(struct power_supply *psy, - enum power_supply_property psp, union power_supply_propval *val) -{ - struct bq24190_dev_info *bdi = power_supply_get_drvdata(psy); - int ret; - - dev_dbg(bdi->dev, "prop: %d\n", psp); - - pm_runtime_get_sync(bdi->dev); - - switch (psp) { - case POWER_SUPPLY_PROP_STATUS: - ret = bq24190_battery_get_status(bdi, val); - break; - case POWER_SUPPLY_PROP_HEALTH: - ret = bq24190_battery_get_health(bdi, val); - break; - case POWER_SUPPLY_PROP_ONLINE: - ret = bq24190_battery_get_online(bdi, val); - break; - case POWER_SUPPLY_PROP_TECHNOLOGY: - /* Could be Li-on or Li-polymer but no way to tell which */ - val->intval = POWER_SUPPLY_TECHNOLOGY_UNKNOWN; - ret = 0; - break; - case POWER_SUPPLY_PROP_TEMP_ALERT_MAX: - ret = bq24190_battery_get_temp_alert_max(bdi, val); - break; - case POWER_SUPPLY_PROP_SCOPE: - val->intval = POWER_SUPPLY_SCOPE_SYSTEM; - ret = 0; - break; - default: - ret = -ENODATA; - } - - pm_runtime_put_sync(bdi->dev); - return ret; -} - -static int bq24190_battery_set_property(struct power_supply *psy, - enum power_supply_property psp, - const union power_supply_propval *val) -{ - struct bq24190_dev_info *bdi = power_supply_get_drvdata(psy); - int ret; - - dev_dbg(bdi->dev, "prop: %d\n", psp); - - pm_runtime_put_sync(bdi->dev); - - switch (psp) { - case POWER_SUPPLY_PROP_ONLINE: - ret = bq24190_battery_set_online(bdi, val); - break; - case POWER_SUPPLY_PROP_TEMP_ALERT_MAX: - ret = bq24190_battery_set_temp_alert_max(bdi, val); - break; - default: - ret = -EINVAL; - } - - pm_runtime_put_sync(bdi->dev); - return ret; -} - -static int bq24190_battery_property_is_writeable(struct power_supply *psy, - enum power_supply_property psp) -{ - int ret; - - switch (psp) { - case POWER_SUPPLY_PROP_ONLINE: - case POWER_SUPPLY_PROP_TEMP_ALERT_MAX: - ret = 1; - break; - default: - ret = 0; - } - - return ret; -} - -static enum power_supply_property bq24190_battery_properties[] = { - POWER_SUPPLY_PROP_STATUS, - POWER_SUPPLY_PROP_HEALTH, - POWER_SUPPLY_PROP_ONLINE, - POWER_SUPPLY_PROP_TECHNOLOGY, - POWER_SUPPLY_PROP_TEMP_ALERT_MAX, - POWER_SUPPLY_PROP_SCOPE, -}; - -static const struct power_supply_desc bq24190_battery_desc = { - .name = "bq24190-battery", - .type = POWER_SUPPLY_TYPE_BATTERY, - .properties = bq24190_battery_properties, - .num_properties = ARRAY_SIZE(bq24190_battery_properties), - .get_property = bq24190_battery_get_property, - .set_property = bq24190_battery_set_property, - .property_is_writeable = bq24190_battery_property_is_writeable, -}; - -static irqreturn_t bq24190_irq_handler_thread(int irq, void *data) -{ - struct bq24190_dev_info *bdi = data; - bool alert_userspace = false; - u8 ss_reg = 0, f_reg = 0; - int ret; - - pm_runtime_get_sync(bdi->dev); - - ret = bq24190_read(bdi, BQ24190_REG_SS, &ss_reg); - if (ret < 0) { - dev_err(bdi->dev, "Can't read SS reg: %d\n", ret); - goto out; - } - - if (ss_reg != bdi->ss_reg) { - /* - * The device is in host mode so when PG_STAT goes from 1->0 - * (i.e., power removed) HIZ needs to be disabled. - */ - if ((bdi->ss_reg & BQ24190_REG_SS_PG_STAT_MASK) && - !(ss_reg & BQ24190_REG_SS_PG_STAT_MASK)) { - ret = bq24190_write_mask(bdi, BQ24190_REG_ISC, - BQ24190_REG_ISC_EN_HIZ_MASK, - BQ24190_REG_ISC_EN_HIZ_SHIFT, - 0); - if (ret < 0) - dev_err(bdi->dev, "Can't access ISC reg: %d\n", - ret); - } - - 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) { - power_supply_changed(bdi->charger); - power_supply_changed(bdi->battery); - } else { - bdi->first_time = false; - } - } - -out: - pm_runtime_put_sync(bdi->dev); - - dev_dbg(bdi->dev, "ss_reg: 0x%02x, f_reg: 0x%02x\n", ss_reg, f_reg); - - return IRQ_HANDLED; -} - -static int bq24190_hw_init(struct bq24190_dev_info *bdi) -{ - u8 v; - int ret; - - pm_runtime_get_sync(bdi->dev); - - /* First check that the device really is what its supposed to be */ - ret = bq24190_read_mask(bdi, BQ24190_REG_VPRS, - BQ24190_REG_VPRS_PN_MASK, - BQ24190_REG_VPRS_PN_SHIFT, - &v); - if (ret < 0) - goto out; - - if (v != bdi->model) { - ret = -ENODEV; - goto out; - } - - ret = bq24190_register_reset(bdi); - if (ret < 0) - goto out; - - ret = bq24190_set_mode_host(bdi); -out: - pm_runtime_put_sync(bdi->dev); - return ret; -} - -#ifdef CONFIG_OF -static int bq24190_setup_dt(struct bq24190_dev_info *bdi) -{ - bdi->irq = irq_of_parse_and_map(bdi->dev->of_node, 0); - if (bdi->irq <= 0) - return -1; - - return 0; -} -#else -static int bq24190_setup_dt(struct bq24190_dev_info *bdi) -{ - return -1; -} -#endif - -static int bq24190_setup_pdata(struct bq24190_dev_info *bdi, - struct bq24190_platform_data *pdata) -{ - int ret; - - if (!gpio_is_valid(pdata->gpio_int)) - return -1; - - ret = gpio_request(pdata->gpio_int, dev_name(bdi->dev)); - if (ret < 0) - return -1; - - ret = gpio_direction_input(pdata->gpio_int); - if (ret < 0) - goto out; - - bdi->irq = gpio_to_irq(pdata->gpio_int); - if (!bdi->irq) - goto out; - - bdi->gpio_int = pdata->gpio_int; - return 0; - -out: - gpio_free(pdata->gpio_int); - return -1; -} - -static int bq24190_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 bq24190_platform_data *pdata = client->dev.platform_data; - struct power_supply_config charger_cfg = {}, battery_cfg = {}; - struct bq24190_dev_info *bdi; - int ret; - - if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) { - dev_err(dev, "No support for SMBUS_BYTE_DATA\n"); - return -ENODEV; - } - - bdi = devm_kzalloc(dev, sizeof(*bdi), GFP_KERNEL); - if (!bdi) { - dev_err(dev, "Can't alloc bdi struct\n"); - return -ENOMEM; - } - - bdi->client = client; - bdi->dev = dev; - bdi->model = id->driver_data; - strncpy(bdi->model_name, id->name, I2C_NAME_SIZE); - mutex_init(&bdi->f_reg_lock); - bdi->first_time = true; - bdi->charger_health_valid = false; - bdi->battery_health_valid = false; - bdi->battery_status_valid = false; - - i2c_set_clientdata(client, bdi); - - if (dev->of_node) - ret = bq24190_setup_dt(bdi); - else - ret = bq24190_setup_pdata(bdi, pdata); - - if (ret) { - dev_err(dev, "Can't get irq info\n"); - 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; - } - - charger_cfg.drv_data = bdi; - charger_cfg.supplied_to = bq24190_charger_supplied_to; - charger_cfg.num_supplicants = ARRAY_SIZE(bq24190_charger_supplied_to), - bdi->charger = power_supply_register(dev, &bq24190_charger_desc, - &charger_cfg); - if (IS_ERR(bdi->charger)) { - dev_err(dev, "Can't register charger\n"); - ret = PTR_ERR(bdi->charger); - goto out2; - } - - battery_cfg.drv_data = bdi; - bdi->battery = power_supply_register(dev, &bq24190_battery_desc, - &battery_cfg); - if (IS_ERR(bdi->battery)) { - dev_err(dev, "Can't register battery\n"); - ret = PTR_ERR(bdi->battery); - goto out3; - } - - ret = bq24190_sysfs_create_group(bdi); - if (ret) { - dev_err(dev, "Can't create sysfs entries\n"); - goto out4; - } - - return 0; - -out4: - power_supply_unregister(bdi->battery); -out3: - power_supply_unregister(bdi->charger); -out2: - pm_runtime_disable(dev); -out1: - if (bdi->gpio_int) - gpio_free(bdi->gpio_int); - - return ret; -} - -static int bq24190_remove(struct i2c_client *client) -{ - struct bq24190_dev_info *bdi = i2c_get_clientdata(client); - - pm_runtime_get_sync(bdi->dev); - bq24190_register_reset(bdi); - pm_runtime_put_sync(bdi->dev); - - bq24190_sysfs_remove_group(bdi); - power_supply_unregister(bdi->battery); - power_supply_unregister(bdi->charger); - pm_runtime_disable(bdi->dev); - - if (bdi->gpio_int) - gpio_free(bdi->gpio_int); - - return 0; -} - -#ifdef CONFIG_PM_SLEEP -static int bq24190_pm_suspend(struct device *dev) -{ - struct i2c_client *client = to_i2c_client(dev); - struct bq24190_dev_info *bdi = i2c_get_clientdata(client); - - pm_runtime_get_sync(bdi->dev); - bq24190_register_reset(bdi); - pm_runtime_put_sync(bdi->dev); - - return 0; -} - -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; - - pm_runtime_get_sync(bdi->dev); - bq24190_register_reset(bdi); - pm_runtime_put_sync(bdi->dev); - - /* Things may have changed while suspended so alert upper layer */ - power_supply_changed(bdi->charger); - power_supply_changed(bdi->battery); - - return 0; -} -#endif - -static SIMPLE_DEV_PM_OPS(bq24190_pm_ops, bq24190_pm_suspend, bq24190_pm_resume); - -/* - * Only support the bq24190 right now. The bq24192, bq24192i, and bq24193 - * are similar but not identical so the driver needs to be extended to - * support them. - */ -static const struct i2c_device_id bq24190_i2c_ids[] = { - { "bq24190", BQ24190_REG_VPRS_PN_24190 }, - { }, -}; -MODULE_DEVICE_TABLE(i2c, bq24190_i2c_ids); - -#ifdef CONFIG_OF -static const struct of_device_id bq24190_of_match[] = { - { .compatible = "ti,bq24190", }, - { }, -}; -MODULE_DEVICE_TABLE(of, bq24190_of_match); -#else -static const struct of_device_id bq24190_of_match[] = { - { }, -}; -#endif - -static struct i2c_driver bq24190_driver = { - .probe = bq24190_probe, - .remove = bq24190_remove, - .id_table = bq24190_i2c_ids, - .driver = { - .name = "bq24190-charger", - .pm = &bq24190_pm_ops, - .of_match_table = of_match_ptr(bq24190_of_match), - }, -}; -module_i2c_driver(bq24190_driver); - -MODULE_LICENSE("GPL"); -MODULE_AUTHOR("Mark A. Greer "); -MODULE_DESCRIPTION("TI BQ24190 Charger Driver"); diff --git a/drivers/power/bq24257_charger.c b/drivers/power/bq24257_charger.c deleted file mode 100644 index 1fea2c7ef97f..000000000000 --- a/drivers/power/bq24257_charger.c +++ /dev/null @@ -1,1196 +0,0 @@ -/* - * TI BQ24257 charger driver - * - * Copyright (C) 2015 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; either version 2 of the License, or - * (at your option) any later version. - * - * 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. - * - * Datasheets: - * http://www.ti.com/product/bq24250 - * http://www.ti.com/product/bq24251 - * http://www.ti.com/product/bq24257 - */ - -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -#define BQ24257_REG_1 0x00 -#define BQ24257_REG_2 0x01 -#define BQ24257_REG_3 0x02 -#define BQ24257_REG_4 0x03 -#define BQ24257_REG_5 0x04 -#define BQ24257_REG_6 0x05 -#define BQ24257_REG_7 0x06 - -#define BQ24257_MANUFACTURER "Texas Instruments" -#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_EN, F_TS_STAT, /* REG 6 */ - F_VOVP, F_CLR_VDP, F_FORCE_BATDET, F_FORCE_PTM, /* REG 7 */ - - F_MAX_FIELDS -}; - -/* initial field values, converted from uV/uA */ -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 { - u8 status; - u8 fault; - bool power_good; -}; - -struct bq24257_device { - struct i2c_client *client; - struct device *dev; - struct power_supply *charger; - - enum bq2425x_chip chip; - - struct regmap *rmap; - struct regmap_field *rmap_fields[F_MAX_FIELDS]; - - struct gpio_desc *pg; - - struct delayed_work iilimit_setup_work; - - struct bq24257_init_data init_data; - 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) -{ - switch (reg) { - case BQ24257_REG_2: - case BQ24257_REG_4: - return false; - - default: - return true; - } -} - -static const struct regmap_config bq24257_regmap_config = { - .reg_bits = 8, - .val_bits = 8, - - .max_register = BQ24257_REG_7, - .cache_type = REGCACHE_RBTREE, - - .volatile_reg = bq24257_is_volatile_reg, -}; - -static const struct reg_field bq24257_reg_fields[] = { - /* REG 1 */ - [F_WD_FAULT] = REG_FIELD(BQ24257_REG_1, 7, 7), - [F_WD_EN] = REG_FIELD(BQ24257_REG_1, 6, 6), - [F_STAT] = REG_FIELD(BQ24257_REG_1, 4, 5), - [F_FAULT] = REG_FIELD(BQ24257_REG_1, 0, 3), - /* REG 2 */ - [F_RESET] = REG_FIELD(BQ24257_REG_2, 7, 7), - [F_IILIMIT] = REG_FIELD(BQ24257_REG_2, 4, 6), - [F_EN_STAT] = REG_FIELD(BQ24257_REG_2, 3, 3), - [F_EN_TERM] = REG_FIELD(BQ24257_REG_2, 2, 2), - [F_CE] = REG_FIELD(BQ24257_REG_2, 1, 1), - [F_HZ_MODE] = REG_FIELD(BQ24257_REG_2, 0, 0), - /* REG 3 */ - [F_VBAT] = REG_FIELD(BQ24257_REG_3, 2, 7), - [F_USB_DET] = REG_FIELD(BQ24257_REG_3, 0, 1), - /* REG 4 */ - [F_ICHG] = REG_FIELD(BQ24257_REG_4, 3, 7), - [F_ITERM] = REG_FIELD(BQ24257_REG_4, 0, 2), - /* REG 5 */ - [F_LOOP_STATUS] = REG_FIELD(BQ24257_REG_5, 6, 7), - [F_LOW_CHG] = REG_FIELD(BQ24257_REG_5, 5, 5), - [F_DPDM_EN] = REG_FIELD(BQ24257_REG_5, 4, 4), - [F_CE_STATUS] = REG_FIELD(BQ24257_REG_5, 3, 3), - [F_VINDPM] = REG_FIELD(BQ24257_REG_5, 0, 2), - /* REG 6 */ - [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), - [F_CLR_VDP] = REG_FIELD(BQ24257_REG_7, 4, 4), - [F_FORCE_BATDET] = REG_FIELD(BQ24257_REG_7, 3, 3), - [F_FORCE_PTM] = REG_FIELD(BQ24257_REG_7, 2, 2) -}; - -static const u32 bq24257_vbat_map[] = { - 3500000, 3520000, 3540000, 3560000, 3580000, 3600000, 3620000, 3640000, - 3660000, 3680000, 3700000, 3720000, 3740000, 3760000, 3780000, 3800000, - 3820000, 3840000, 3860000, 3880000, 3900000, 3920000, 3940000, 3960000, - 3980000, 4000000, 4020000, 4040000, 4060000, 4080000, 4100000, 4120000, - 4140000, 4160000, 4180000, 4200000, 4220000, 4240000, 4260000, 4280000, - 4300000, 4320000, 4340000, 4360000, 4380000, 4400000, 4420000, 4440000 -}; - -#define BQ24257_VBAT_MAP_SIZE ARRAY_SIZE(bq24257_vbat_map) - -static const u32 bq24257_ichg_map[] = { - 500000, 550000, 600000, 650000, 700000, 750000, 800000, 850000, 900000, - 950000, 1000000, 1050000, 1100000, 1150000, 1200000, 1250000, 1300000, - 1350000, 1400000, 1450000, 1500000, 1550000, 1600000, 1650000, 1700000, - 1750000, 1800000, 1850000, 1900000, 1950000, 2000000 -}; - -#define BQ24257_ICHG_MAP_SIZE ARRAY_SIZE(bq24257_ichg_map) - -static const u32 bq24257_iterm_map[] = { - 50000, 75000, 100000, 125000, 150000, 175000, 200000, 225000 -}; - -#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) -{ - int ret; - int val; - - ret = regmap_field_read(bq->rmap_fields[field_id], &val); - if (ret < 0) - return ret; - - return val; -} - -static int bq24257_field_write(struct bq24257_device *bq, - enum bq24257_fields field_id, u8 val) -{ - return regmap_field_write(bq->rmap_fields[field_id], val); -} - -static u8 bq24257_find_idx(u32 value, const u32 *map, u8 map_size) -{ - u8 idx; - - for (idx = 1; idx < map_size; idx++) - if (value < map[idx]) - break; - - return idx - 1; -} - -enum bq24257_status { - STATUS_READY, - STATUS_CHARGE_IN_PROGRESS, - STATUS_CHARGE_DONE, - STATUS_FAULT, -}; - -enum bq24257_fault { - FAULT_NORMAL, - FAULT_INPUT_OVP, - FAULT_INPUT_UVLO, - FAULT_SLEEP, - FAULT_BAT_TS, - FAULT_BAT_OVP, - FAULT_TS, - FAULT_TIMER, - FAULT_NO_BAT, - FAULT_ISET, - 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) -{ - struct bq24257_device *bq = power_supply_get_drvdata(psy); - struct bq24257_state state; - - mutex_lock(&bq->lock); - state = bq->state; - mutex_unlock(&bq->lock); - - switch (psp) { - case POWER_SUPPLY_PROP_STATUS: - if (!state.power_good) - val->intval = POWER_SUPPLY_STATUS_DISCHARGING; - else if (state.status == STATUS_READY) - val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; - else if (state.status == STATUS_CHARGE_IN_PROGRESS) - val->intval = POWER_SUPPLY_STATUS_CHARGING; - else if (state.status == STATUS_CHARGE_DONE) - val->intval = POWER_SUPPLY_STATUS_FULL; - else - val->intval = POWER_SUPPLY_STATUS_UNKNOWN; - break; - - case POWER_SUPPLY_PROP_MANUFACTURER: - 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; - - case POWER_SUPPLY_PROP_HEALTH: - switch (state.fault) { - case FAULT_NORMAL: - val->intval = POWER_SUPPLY_HEALTH_GOOD; - break; - - case FAULT_INPUT_OVP: - case FAULT_BAT_OVP: - val->intval = POWER_SUPPLY_HEALTH_OVERVOLTAGE; - break; - - case FAULT_TS: - case FAULT_BAT_TS: - val->intval = POWER_SUPPLY_HEALTH_OVERHEAT; - break; - - case FAULT_TIMER: - val->intval = POWER_SUPPLY_HEALTH_SAFETY_TIMER_EXPIRE; - break; - - default: - val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; - break; - } - - break; - - case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: - val->intval = bq24257_ichg_map[bq->init_data.ichg]; - break; - - case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX: - val->intval = bq24257_ichg_map[BQ24257_ICHG_MAP_SIZE - 1]; - break; - - case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: - val->intval = bq24257_vbat_map[bq->init_data.vbat]; - break; - - case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX: - val->intval = bq24257_vbat_map[BQ24257_VBAT_MAP_SIZE - 1]; - break; - - case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT: - 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; - } - - 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) -{ - int ret; - - ret = bq24257_field_read(bq, F_STAT); - if (ret < 0) - return ret; - - state->status = ret; - - ret = bq24257_field_read(bq, F_FAULT); - if (ret < 0) - return ret; - - state->fault = ret; - - 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; -} - -static bool bq24257_state_changed(struct bq24257_device *bq, - struct bq24257_state *new_state) -{ - int ret; - - mutex_lock(&bq->lock); - ret = (bq->state.status != new_state->status || - bq->state.fault != new_state->fault || - bq->state.power_good != new_state->power_good); - mutex_unlock(&bq->lock); - - return ret; -} - -enum bq24257_loop_status { - LOOP_STATUS_NONE, - LOOP_STATUS_IN_DPM, - LOOP_STATUS_IN_CURRENT_LIMIT, - LOOP_STATUS_THERMAL, -}; - -enum bq24257_in_ilimit { - IILIMIT_100, - IILIMIT_150, - IILIMIT_500, - IILIMIT_900, - IILIMIT_1500, - IILIMIT_2000, - IILIMIT_EXT, - 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 */ - PORT_TYPE_SDP, /* Standard Downstream Port */ - PORT_TYPE_NON_STANDARD, -}; - -enum bq24257_safety_timer { - SAFETY_TIMER_45, - SAFETY_TIMER_360, - SAFETY_TIMER_540, - SAFETY_TIMER_NONE, -}; - -static int bq24257_iilimit_autoset(struct bq24257_device *bq) -{ - int loop_status; - int iilimit; - int port_type; - int ret; - const u8 new_iilimit[] = { - [PORT_TYPE_DCP] = IILIMIT_2000, - [PORT_TYPE_CDP] = IILIMIT_2000, - [PORT_TYPE_SDP] = IILIMIT_500, - [PORT_TYPE_NON_STANDARD] = IILIMIT_500 - }; - - ret = bq24257_field_read(bq, F_LOOP_STATUS); - if (ret < 0) - goto error; - - loop_status = ret; - - ret = bq24257_field_read(bq, F_IILIMIT); - if (ret < 0) - goto error; - - iilimit = ret; - - /* - * All USB ports should be able to handle 500mA. If not, DPM will lower - * the charging current to accommodate the power source. No need to set - * a lower IILIMIT value. - */ - if (loop_status == LOOP_STATUS_IN_DPM && iilimit == IILIMIT_500) - return 0; - - ret = bq24257_field_read(bq, F_USB_DET); - if (ret < 0) - goto error; - - port_type = ret; - - ret = bq24257_field_write(bq, F_IILIMIT, new_iilimit[port_type]); - if (ret < 0) - goto error; - - ret = bq24257_field_write(bq, F_TMR, SAFETY_TIMER_360); - if (ret < 0) - goto error; - - ret = bq24257_field_write(bq, F_CLR_VDP, 1); - if (ret < 0) - goto error; - - dev_dbg(bq->dev, "port/loop = %d/%d -> iilimit = %d\n", - port_type, loop_status, new_iilimit[port_type]); - - return 0; - -error: - dev_err(bq->dev, "%s: Error communicating with the chip.\n", __func__); - return ret; -} - -static void bq24257_iilimit_setup_work(struct work_struct *work) -{ - struct bq24257_device *bq = container_of(work, struct bq24257_device, - iilimit_setup_work.work); - - bq24257_iilimit_autoset(bq); -} - -static void bq24257_handle_state_change(struct bq24257_device *bq, - struct bq24257_state *new_state) -{ - int ret; - struct bq24257_state old_state; - - mutex_lock(&bq->lock); - old_state = bq->state; - mutex_unlock(&bq->lock); - - /* - * 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); - 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"); - - 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; - -error: - dev_err(bq->dev, "%s: Error communicating with the chip.\n", __func__); -} - -static irqreturn_t bq24257_irq_handler_thread(int irq, void *private) -{ - int ret; - struct bq24257_device *bq = private; - struct bq24257_state state; - - ret = bq24257_get_chip_state(bq, &state); - if (ret < 0) - return IRQ_HANDLED; - - if (!bq24257_state_changed(bq, &state)) - return IRQ_HANDLED; - - dev_dbg(bq->dev, "irq(state changed): status/fault/pg = %d/%d/%d\n", - state.status, state.fault, state.power_good); - - bq24257_handle_state_change(bq, &state); - - mutex_lock(&bq->lock); - bq->state = state; - mutex_unlock(&bq->lock); - - power_supply_changed(bq->charger); - - return IRQ_HANDLED; -} - -static int bq24257_hw_init(struct bq24257_device *bq) -{ - int ret; - int i; - struct bq24257_state state; - - const struct { - int field; - u32 value; - } init_data[] = { - {F_ICHG, bq->init_data.ichg}, - {F_VBAT, bq->init_data.vbat}, - {F_ITERM, bq->init_data.iterm}, - {F_VOVP, bq->init_data.vovp}, - {F_VINDPM, bq->init_data.vindpm}, - }; - - /* - * Disable the watchdog timer to prevent the IC from going back to - * default settings after 50 seconds of I2C inactivity. - */ - ret = bq24257_field_write(bq, F_WD_EN, 0); - if (ret < 0) - return ret; - - /* configure the charge currents and voltages */ - for (i = 0; i < ARRAY_SIZE(init_data); i++) { - ret = bq24257_field_write(bq, init_data[i].field, - init_data[i].value); - if (ret < 0) - return ret; - } - - ret = bq24257_get_chip_state(bq, &state); - if (ret < 0) - return ret; - - mutex_lock(&bq->lock); - bq->state = state; - mutex_unlock(&bq->lock); - - 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) - ret = bq24257_iilimit_autoset(bq); - - return ret; -} - -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, - POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT, - POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX, - 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[] = { - "main-battery", -}; - -static const struct power_supply_desc bq24257_power_supply_desc = { - .name = "bq24257-charger", - .type = POWER_SUPPLY_TYPE_USB, - .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) -{ - struct power_supply_config psy_cfg = { .drv_data = bq, }; - - psy_cfg.supplied_to = bq24257_charger_supplied_to; - psy_cfg.num_supplicants = ARRAY_SIZE(bq24257_charger_supplied_to); - - bq->charger = devm_power_supply_register(bq->dev, - &bq24257_power_supply_desc, - &psy_cfg); - - return PTR_ERR_OR_ZERO(bq->charger); -} - -static void bq24257_pg_gpio_probe(struct bq24257_device *bq) -{ - bq->pg = devm_gpiod_get_optional(bq->dev, BQ24257_PG_GPIO, GPIOD_IN); - - 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; - } - - 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) -{ - int ret; - u32 property; - - /* Required properties */ - ret = device_property_read_u32(bq->dev, "ti,charge-current", &property); - if (ret < 0) - return ret; - - bq->init_data.ichg = bq24257_find_idx(property, bq24257_ichg_map, - BQ24257_ICHG_MAP_SIZE); - - ret = device_property_read_u32(bq->dev, "ti,battery-regulation-voltage", - &property); - if (ret < 0) - return ret; - - bq->init_data.vbat = bq24257_find_idx(property, bq24257_vbat_map, - BQ24257_VBAT_MAP_SIZE); - - ret = device_property_read_u32(bq->dev, "ti,termination-current", - &property); - if (ret < 0) - return ret; - - 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; -} - -static int bq24257_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; - const struct acpi_device_id *acpi_id; - struct bq24257_device *bq; - int ret; - int i; - - if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) { - dev_err(dev, "No support for SMBUS_BYTE_DATA\n"); - return -ENODEV; - } - - bq = devm_kzalloc(dev, sizeof(*bq), GFP_KERNEL); - if (!bq) - return -ENOMEM; - - 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); - if (IS_ERR(bq->rmap)) { - dev_err(dev, "failed to allocate register map\n"); - return PTR_ERR(bq->rmap); - } - - for (i = 0; i < ARRAY_SIZE(bq24257_reg_fields); i++) { - const struct reg_field *reg_fields = bq24257_reg_fields; - - bq->rmap_fields[i] = devm_regmap_field_alloc(dev, bq->rmap, - reg_fields[i]); - if (IS_ERR(bq->rmap_fields[i])) { - dev_err(dev, "cannot allocate regmap field\n"); - return PTR_ERR(bq->rmap_fields[i]); - } - } - - i2c_set_clientdata(client, bq); - - if (!dev->platform_data) { - ret = bq24257_fw_probe(bq); - if (ret < 0) { - dev_err(dev, "Cannot read device properties.\n"); - return ret; - } - } else { - return -ENODEV; - } - - /* - * 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); - if (ret < 0) - return ret; - - /* - * Put the RESET bit back to 0, in cache. For some reason the HW always - * returns 1 on this bit, so this is the only way to avoid resetting the - * chip every time we update another field in this register. - */ - ret = bq24257_field_write(bq, F_RESET, 0); - if (ret < 0) - return ret; - - ret = bq24257_hw_init(bq); - if (ret < 0) { - dev_err(dev, "Cannot initialize the chip.\n"); - return ret; - } - - ret = devm_request_threaded_irq(dev, client->irq, NULL, - bq24257_irq_handler_thread, - IRQF_TRIGGER_FALLING | - IRQF_TRIGGER_RISING | IRQF_ONESHOT, - 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) { - dev_err(dev, "Failed to register power supply\n"); - 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); - - if (bq->iilimit_autoset_enable) - cancel_delayed_work_sync(&bq->iilimit_setup_work); - - sysfs_remove_group(&bq->charger->dev.kobj, &bq24257_attr_group); - - bq24257_field_write(bq, F_RESET, 1); /* reset to defaults */ - - return 0; -} - -#ifdef CONFIG_PM_SLEEP -static int bq24257_suspend(struct device *dev) -{ - struct bq24257_device *bq = dev_get_drvdata(dev); - int ret = 0; - - 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); - if (ret < 0) - dev_err(bq->dev, "Cannot reset chip to standalone mode.\n"); - - return ret; -} - -static int bq24257_resume(struct device *dev) -{ - int ret; - struct bq24257_device *bq = dev_get_drvdata(dev); - - ret = regcache_drop_region(bq->rmap, BQ24257_REG_1, BQ24257_REG_7); - if (ret < 0) - return ret; - - ret = bq24257_field_write(bq, F_RESET, 0); - if (ret < 0) - return ret; - - ret = bq24257_hw_init(bq); - if (ret < 0) { - dev_err(bq->dev, "Cannot init chip after resume.\n"); - return ret; - } - - /* signal userspace, maybe state changed while suspended */ - power_supply_changed(bq->charger); - - return 0; -} -#endif - -static const struct dev_pm_ops bq24257_pm = { - SET_SYSTEM_SLEEP_PM_OPS(bq24257_suspend, bq24257_resume) -}; - -static const struct i2c_device_id bq24257_i2c_ids[] = { - { "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[] = { - { "BQ242500", BQ24250 }, - { "BQ242510", BQ24251 }, - { "BQ242570", BQ24257 }, - {}, -}; -MODULE_DEVICE_TABLE(acpi, bq24257_acpi_match); - -static struct i2c_driver bq24257_driver = { - .driver = { - .name = "bq24257-charger", - .of_match_table = of_match_ptr(bq24257_of_match), - .acpi_match_table = ACPI_PTR(bq24257_acpi_match), - .pm = &bq24257_pm, - }, - .probe = bq24257_probe, - .remove = bq24257_remove, - .id_table = bq24257_i2c_ids, -}; -module_i2c_driver(bq24257_driver); - -MODULE_AUTHOR("Laurentiu Palcu "); -MODULE_DESCRIPTION("bq24257 charger driver"); -MODULE_LICENSE("GPL"); diff --git a/drivers/power/bq24735-charger.c b/drivers/power/bq24735-charger.c deleted file mode 100644 index fa454c19ce17..000000000000 --- a/drivers/power/bq24735-charger.c +++ /dev/null @@ -1,500 +0,0 @@ -/* - * Battery charger driver for TI BQ24735 - * - * Copyright (c) 2013, NVIDIA CORPORATION. All rights reserved. - * - * 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; - * - * 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., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#define BQ24735_CHG_OPT 0x12 -#define BQ24735_CHG_OPT_CHARGE_DISABLE (1 << 0) -#define BQ24735_CHG_OPT_AC_PRESENT (1 << 4) -#define BQ24735_CHARGE_CURRENT 0x14 -#define BQ24735_CHARGE_CURRENT_MASK 0x1fc0 -#define BQ24735_CHARGE_VOLTAGE 0x15 -#define BQ24735_CHARGE_VOLTAGE_MASK 0x7ff0 -#define BQ24735_INPUT_CURRENT 0x3f -#define BQ24735_INPUT_CURRENT_MASK 0x1f80 -#define BQ24735_MANUFACTURER_ID 0xfe -#define BQ24735_DEVICE_ID 0xff - -struct bq24735 { - struct power_supply *charger; - struct power_supply_desc charger_desc; - struct i2c_client *client; - struct bq24735_platform *pdata; - struct mutex lock; - bool charging; -}; - -static inline struct bq24735 *to_bq24735(struct power_supply *psy) -{ - return power_supply_get_drvdata(psy); -} - -static enum power_supply_property bq24735_charger_properties[] = { - POWER_SUPPLY_PROP_STATUS, - POWER_SUPPLY_PROP_ONLINE, -}; - -static int bq24735_charger_property_is_writeable(struct power_supply *psy, - enum power_supply_property psp) -{ - switch (psp) { - case POWER_SUPPLY_PROP_STATUS: - return 1; - default: - break; - } - - return 0; -} - -static inline int bq24735_write_word(struct i2c_client *client, u8 reg, - u16 value) -{ - return i2c_smbus_write_word_data(client, reg, le16_to_cpu(value)); -} - -static inline int bq24735_read_word(struct i2c_client *client, u8 reg) -{ - s32 ret = i2c_smbus_read_word_data(client, reg); - - return ret < 0 ? ret : le16_to_cpu(ret); -} - -static int bq24735_update_word(struct i2c_client *client, u8 reg, - u16 mask, u16 value) -{ - unsigned int tmp; - int ret; - - ret = bq24735_read_word(client, reg); - if (ret < 0) - return ret; - - tmp = ret & ~mask; - tmp |= value & mask; - - 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; - int ret; - u16 value; - - if (pdata->ext_control) - return 0; - - if (pdata->charge_current) { - value = pdata->charge_current & BQ24735_CHARGE_CURRENT_MASK; - - ret = bq24735_write_word(charger->client, - BQ24735_CHARGE_CURRENT, value); - if (ret < 0) { - dev_err(&charger->client->dev, - "Failed to write charger current : %d\n", - ret); - return ret; - } - } - - if (pdata->charge_voltage) { - value = pdata->charge_voltage & BQ24735_CHARGE_VOLTAGE_MASK; - - ret = bq24735_write_word(charger->client, - BQ24735_CHARGE_VOLTAGE, value); - if (ret < 0) { - dev_err(&charger->client->dev, - "Failed to write charger voltage : %d\n", - ret); - return ret; - } - } - - if (pdata->input_current) { - value = pdata->input_current & BQ24735_INPUT_CURRENT_MASK; - - ret = bq24735_write_word(charger->client, - BQ24735_INPUT_CURRENT, value); - if (ret < 0) { - dev_err(&charger->client->dev, - "Failed to write input current : %d\n", - ret); - return ret; - } - } - - return 0; -} - -static bool bq24735_charger_is_present(struct bq24735 *charger) -{ - struct bq24735_platform *pdata = charger->pdata; - int ret; - - if (pdata->status_gpio_valid) { - ret = gpio_get_value_cansleep(pdata->status_gpio); - return ret ^= pdata->status_gpio_active_low == 0; - } else { - int ac = 0; - - ac = bq24735_read_word(charger->client, BQ24735_CHG_OPT); - if (ac < 0) { - dev_err(&charger->client->dev, - "Failed to read charger options : %d\n", - ac); - return false; - } - return (ac & BQ24735_CHG_OPT_AC_PRESENT) ? true : false; - } - - return false; -} - -static int bq24735_charger_is_charging(struct bq24735 *charger) -{ - int ret = bq24735_read_word(charger->client, BQ24735_CHG_OPT); - - if (ret < 0) - return ret; - - return !(ret & BQ24735_CHG_OPT_CHARGE_DISABLE); -} - -static irqreturn_t bq24735_charger_isr(int irq, void *devid) -{ - struct power_supply *psy = devid; - struct bq24735 *charger = to_bq24735(psy); - - mutex_lock(&charger->lock); - - if (charger->charging && bq24735_charger_is_present(charger)) - bq24735_enable_charging(charger); - else - bq24735_disable_charging(charger); - - mutex_unlock(&charger->lock); - - power_supply_changed(psy); - - return IRQ_HANDLED; -} - -static int bq24735_charger_get_property(struct power_supply *psy, - enum power_supply_property psp, - union power_supply_propval *val) -{ - struct bq24735 *charger = to_bq24735(psy); - - switch (psp) { - case POWER_SUPPLY_PROP_ONLINE: - val->intval = bq24735_charger_is_present(charger) ? 1 : 0; - break; - case POWER_SUPPLY_PROP_STATUS: - switch (bq24735_charger_is_charging(charger)) { - case 1: - val->intval = POWER_SUPPLY_STATUS_CHARGING; - break; - case 0: - val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; - break; - default: - val->intval = POWER_SUPPLY_STATUS_UNKNOWN; - break; - } - break; - default: - return -EINVAL; - } - - return 0; -} - -static int bq24735_charger_set_property(struct power_supply *psy, - enum power_supply_property psp, - const union power_supply_propval *val) -{ - struct bq24735 *charger = to_bq24735(psy); - int ret; - - switch (psp) { - case POWER_SUPPLY_PROP_STATUS: - switch (val->intval) { - case POWER_SUPPLY_STATUS_CHARGING: - mutex_lock(&charger->lock); - charger->charging = true; - ret = bq24735_enable_charging(charger); - mutex_unlock(&charger->lock); - if (ret) - return ret; - bq24735_config_charger(charger); - break; - case POWER_SUPPLY_STATUS_DISCHARGING: - case POWER_SUPPLY_STATUS_NOT_CHARGING: - mutex_lock(&charger->lock); - charger->charging = false; - ret = bq24735_disable_charging(charger); - mutex_unlock(&charger->lock); - if (ret) - return ret; - break; - default: - return -EINVAL; - } - power_supply_changed(psy); - break; - default: - return -EPERM; - } - - return 0; -} - -static struct bq24735_platform *bq24735_parse_dt_data(struct i2c_client *client) -{ - struct bq24735_platform *pdata; - struct device_node *np = client->dev.of_node; - u32 val; - int ret; - enum of_gpio_flags flags; - - pdata = devm_kzalloc(&client->dev, sizeof(*pdata), GFP_KERNEL); - if (!pdata) { - dev_err(&client->dev, - "Memory alloc for bq24735 pdata failed\n"); - return NULL; - } - - pdata->status_gpio = of_get_named_gpio_flags(np, "ti,ac-detect-gpios", - 0, &flags); - - if (flags & OF_GPIO_ACTIVE_LOW) - pdata->status_gpio_active_low = 1; - - ret = of_property_read_u32(np, "ti,charge-current", &val); - if (!ret) - pdata->charge_current = val; - - ret = of_property_read_u32(np, "ti,charge-voltage", &val); - if (!ret) - pdata->charge_voltage = val; - - ret = of_property_read_u32(np, "ti,input-current", &val); - if (!ret) - pdata->input_current = val; - - pdata->ext_control = of_property_read_bool(np, "ti,external-control"); - - return pdata; -} - -static int bq24735_charger_probe(struct i2c_client *client, - const struct i2c_device_id *id) -{ - int ret; - struct bq24735 *charger; - struct power_supply_desc *supply_desc; - struct power_supply_config psy_cfg = {}; - char *name; - - charger = devm_kzalloc(&client->dev, sizeof(*charger), GFP_KERNEL); - if (!charger) - return -ENOMEM; - - mutex_init(&charger->lock); - charger->charging = true; - charger->pdata = client->dev.platform_data; - - if (IS_ENABLED(CONFIG_OF) && !charger->pdata && client->dev.of_node) - charger->pdata = bq24735_parse_dt_data(client); - - if (!charger->pdata) { - dev_err(&client->dev, "no platform data provided\n"); - return -EINVAL; - } - - name = (char *)charger->pdata->name; - if (!name) { - name = devm_kasprintf(&client->dev, GFP_KERNEL, - "bq24735@%s", - dev_name(&client->dev)); - if (!name) { - dev_err(&client->dev, "Failed to alloc device name\n"); - return -ENOMEM; - } - } - - charger->client = client; - - supply_desc = &charger->charger_desc; - - supply_desc->name = name; - supply_desc->type = POWER_SUPPLY_TYPE_MAINS; - supply_desc->properties = bq24735_charger_properties; - supply_desc->num_properties = ARRAY_SIZE(bq24735_charger_properties); - supply_desc->get_property = bq24735_charger_get_property; - supply_desc->set_property = bq24735_charger_set_property; - supply_desc->property_is_writeable = - bq24735_charger_property_is_writeable; - - psy_cfg.supplied_to = charger->pdata->supplied_to; - psy_cfg.num_supplicants = charger->pdata->num_supplicants; - psy_cfg.of_node = client->dev.of_node; - psy_cfg.drv_data = charger; - - i2c_set_clientdata(client, charger); - - if (gpio_is_valid(charger->pdata->status_gpio)) { - ret = devm_gpio_request(&client->dev, - charger->pdata->status_gpio, - name); - if (ret) { - dev_err(&client->dev, - "Failed GPIO request for GPIO %d: %d\n", - charger->pdata->status_gpio, ret); - } - - charger->pdata->status_gpio_valid = !ret; - } - - if (!charger->pdata->status_gpio_valid - || 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", - ret); - return ret; - } else if (ret != 0x0040) { - dev_err(&client->dev, - "manufacturer id mismatch. 0x0040 != 0x%04x\n", ret); - return -ENODEV; - } - - ret = bq24735_read_word(client, BQ24735_DEVICE_ID); - if (ret < 0) { - dev_err(&client->dev, "Failed to read device id : %d\n", ret); - return ret; - } else if (ret != 0x000B) { - dev_err(&client->dev, - "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"); - return ret; - } - } - - charger->charger = devm_power_supply_register(&client->dev, supply_desc, - &psy_cfg); - if (IS_ERR(charger->charger)) { - ret = PTR_ERR(charger->charger); - dev_err(&client->dev, "Failed to register power supply: %d\n", - ret); - return ret; - } - - if (client->irq) { - ret = devm_request_threaded_irq(&client->dev, client->irq, - NULL, bq24735_charger_isr, - IRQF_TRIGGER_RISING | - IRQF_TRIGGER_FALLING | - IRQF_ONESHOT, - supply_desc->name, - charger->charger); - if (ret) { - dev_err(&client->dev, - "Unable to register IRQ %d err %d\n", - client->irq, ret); - return ret; - } - } - - return 0; -} - -static const struct i2c_device_id bq24735_charger_id[] = { - { "bq24735-charger", 0 }, - {} -}; -MODULE_DEVICE_TABLE(i2c, bq24735_charger_id); - -static const struct of_device_id bq24735_match_ids[] = { - { .compatible = "ti,bq24735", }, - { /* end */ } -}; -MODULE_DEVICE_TABLE(of, bq24735_match_ids); - -static struct i2c_driver bq24735_charger_driver = { - .driver = { - .name = "bq24735-charger", - .of_match_table = bq24735_match_ids, - }, - .probe = bq24735_charger_probe, - .id_table = bq24735_charger_id, -}; - -module_i2c_driver(bq24735_charger_driver); - -MODULE_DESCRIPTION("bq24735 battery charging driver"); -MODULE_AUTHOR("Darbha Sriharsha "); -MODULE_LICENSE("GPL v2"); diff --git a/drivers/power/bq25890_charger.c b/drivers/power/bq25890_charger.c deleted file mode 100644 index f993a55cde20..000000000000 --- a/drivers/power/bq25890_charger.c +++ /dev/null @@ -1,994 +0,0 @@ -/* - * TI BQ25890 charger driver - * - * Copyright (C) 2015 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; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -#define BQ25890_MANUFACTURER "Texas Instruments" -#define BQ25890_IRQ_PIN "bq25890_irq" - -#define BQ25890_ID 3 - -enum bq25890_fields { - F_EN_HIZ, F_EN_ILIM, F_IILIM, /* Reg00 */ - F_BHOT, F_BCOLD, F_VINDPM_OFS, /* Reg01 */ - F_CONV_START, F_CONV_RATE, F_BOOSTF, F_ICO_EN, - F_HVDCP_EN, F_MAXC_EN, F_FORCE_DPM, F_AUTO_DPDM_EN, /* Reg02 */ - F_BAT_LOAD_EN, F_WD_RST, F_OTG_CFG, F_CHG_CFG, F_SYSVMIN, /* Reg03 */ - F_PUMPX_EN, F_ICHG, /* Reg04 */ - F_IPRECHG, F_ITERM, /* Reg05 */ - F_VREG, F_BATLOWV, F_VRECHG, /* Reg06 */ - F_TERM_EN, F_STAT_DIS, F_WD, F_TMR_EN, F_CHG_TMR, - F_JEITA_ISET, /* Reg07 */ - F_BATCMP, F_VCLAMP, F_TREG, /* Reg08 */ - F_FORCE_ICO, F_TMR2X_EN, F_BATFET_DIS, F_JEITA_VSET, - F_BATFET_DLY, F_BATFET_RST_EN, F_PUMPX_UP, F_PUMPX_DN, /* Reg09 */ - F_BOOSTV, F_BOOSTI, /* Reg0A */ - F_VBUS_STAT, F_CHG_STAT, F_PG_STAT, F_SDP_STAT, F_VSYS_STAT, /* Reg0B */ - F_WD_FAULT, F_BOOST_FAULT, F_CHG_FAULT, F_BAT_FAULT, - F_NTC_FAULT, /* Reg0C */ - F_FORCE_VINDPM, F_VINDPM, /* Reg0D */ - F_THERM_STAT, F_BATV, /* Reg0E */ - F_SYSV, /* Reg0F */ - F_TSPCT, /* Reg10 */ - F_VBUS_GD, F_VBUSV, /* Reg11 */ - F_ICHGR, /* Reg12 */ - F_VDPM_STAT, F_IDPM_STAT, F_IDPM_LIM, /* Reg13 */ - F_REG_RST, F_ICO_OPTIMIZED, F_PN, F_TS_PROFILE, F_DEV_REV, /* Reg14 */ - - F_MAX_FIELDS -}; - -/* initial field values, converted to register values */ -struct bq25890_init_data { - u8 ichg; /* charge current */ - u8 vreg; /* regulation voltage */ - u8 iterm; /* termination current */ - u8 iprechg; /* precharge current */ - u8 sysvmin; /* minimum system voltage limit */ - u8 boostv; /* boost regulation voltage */ - u8 boosti; /* boost current limit */ - u8 boostf; /* boost frequency */ - u8 ilim_en; /* enable ILIM pin */ - u8 treg; /* thermal regulation threshold */ -}; - -struct bq25890_state { - u8 online; - u8 chrg_status; - u8 chrg_fault; - u8 vsys_status; - u8 boost_fault; - u8 bat_fault; -}; - -struct bq25890_device { - struct i2c_client *client; - struct device *dev; - struct power_supply *charger; - - struct usb_phy *usb_phy; - struct notifier_block usb_nb; - struct work_struct usb_work; - unsigned long usb_event; - - struct regmap *rmap; - struct regmap_field *rmap_fields[F_MAX_FIELDS]; - - int chip_id; - struct bq25890_init_data init_data; - struct bq25890_state state; - - struct mutex lock; /* protect state data */ -}; - -static const struct regmap_range bq25890_readonly_reg_ranges[] = { - regmap_reg_range(0x0b, 0x0c), - regmap_reg_range(0x0e, 0x13), -}; - -static const struct regmap_access_table bq25890_writeable_regs = { - .no_ranges = bq25890_readonly_reg_ranges, - .n_no_ranges = ARRAY_SIZE(bq25890_readonly_reg_ranges), -}; - -static const struct regmap_range bq25890_volatile_reg_ranges[] = { - regmap_reg_range(0x00, 0x00), - regmap_reg_range(0x09, 0x09), - regmap_reg_range(0x0b, 0x0c), - regmap_reg_range(0x0e, 0x14), -}; - -static const struct regmap_access_table bq25890_volatile_regs = { - .yes_ranges = bq25890_volatile_reg_ranges, - .n_yes_ranges = ARRAY_SIZE(bq25890_volatile_reg_ranges), -}; - -static const struct regmap_config bq25890_regmap_config = { - .reg_bits = 8, - .val_bits = 8, - - .max_register = 0x14, - .cache_type = REGCACHE_RBTREE, - - .wr_table = &bq25890_writeable_regs, - .volatile_table = &bq25890_volatile_regs, -}; - -static const struct reg_field bq25890_reg_fields[] = { - /* REG00 */ - [F_EN_HIZ] = REG_FIELD(0x00, 7, 7), - [F_EN_ILIM] = REG_FIELD(0x00, 6, 6), - [F_IILIM] = REG_FIELD(0x00, 0, 5), - /* REG01 */ - [F_BHOT] = REG_FIELD(0x01, 6, 7), - [F_BCOLD] = REG_FIELD(0x01, 5, 5), - [F_VINDPM_OFS] = REG_FIELD(0x01, 0, 4), - /* REG02 */ - [F_CONV_START] = REG_FIELD(0x02, 7, 7), - [F_CONV_RATE] = REG_FIELD(0x02, 6, 6), - [F_BOOSTF] = REG_FIELD(0x02, 5, 5), - [F_ICO_EN] = REG_FIELD(0x02, 4, 4), - [F_HVDCP_EN] = REG_FIELD(0x02, 3, 3), - [F_MAXC_EN] = REG_FIELD(0x02, 2, 2), - [F_FORCE_DPM] = REG_FIELD(0x02, 1, 1), - [F_AUTO_DPDM_EN] = REG_FIELD(0x02, 0, 0), - /* REG03 */ - [F_BAT_LOAD_EN] = REG_FIELD(0x03, 7, 7), - [F_WD_RST] = REG_FIELD(0x03, 6, 6), - [F_OTG_CFG] = REG_FIELD(0x03, 5, 5), - [F_CHG_CFG] = REG_FIELD(0x03, 4, 4), - [F_SYSVMIN] = REG_FIELD(0x03, 1, 3), - /* REG04 */ - [F_PUMPX_EN] = REG_FIELD(0x04, 7, 7), - [F_ICHG] = REG_FIELD(0x04, 0, 6), - /* REG05 */ - [F_IPRECHG] = REG_FIELD(0x05, 4, 7), - [F_ITERM] = REG_FIELD(0x05, 0, 3), - /* REG06 */ - [F_VREG] = REG_FIELD(0x06, 2, 7), - [F_BATLOWV] = REG_FIELD(0x06, 1, 1), - [F_VRECHG] = REG_FIELD(0x06, 0, 0), - /* REG07 */ - [F_TERM_EN] = REG_FIELD(0x07, 7, 7), - [F_STAT_DIS] = REG_FIELD(0x07, 6, 6), - [F_WD] = REG_FIELD(0x07, 4, 5), - [F_TMR_EN] = REG_FIELD(0x07, 3, 3), - [F_CHG_TMR] = REG_FIELD(0x07, 1, 2), - [F_JEITA_ISET] = REG_FIELD(0x07, 0, 0), - /* REG08 */ - [F_BATCMP] = REG_FIELD(0x08, 6, 7), - [F_VCLAMP] = REG_FIELD(0x08, 2, 4), - [F_TREG] = REG_FIELD(0x08, 0, 1), - /* REG09 */ - [F_FORCE_ICO] = REG_FIELD(0x09, 7, 7), - [F_TMR2X_EN] = REG_FIELD(0x09, 6, 6), - [F_BATFET_DIS] = REG_FIELD(0x09, 5, 5), - [F_JEITA_VSET] = REG_FIELD(0x09, 4, 4), - [F_BATFET_DLY] = REG_FIELD(0x09, 3, 3), - [F_BATFET_RST_EN] = REG_FIELD(0x09, 2, 2), - [F_PUMPX_UP] = REG_FIELD(0x09, 1, 1), - [F_PUMPX_DN] = REG_FIELD(0x09, 0, 0), - /* REG0A */ - [F_BOOSTV] = REG_FIELD(0x0A, 4, 7), - [F_BOOSTI] = REG_FIELD(0x0A, 0, 2), - /* REG0B */ - [F_VBUS_STAT] = REG_FIELD(0x0B, 5, 7), - [F_CHG_STAT] = REG_FIELD(0x0B, 3, 4), - [F_PG_STAT] = REG_FIELD(0x0B, 2, 2), - [F_SDP_STAT] = REG_FIELD(0x0B, 1, 1), - [F_VSYS_STAT] = REG_FIELD(0x0B, 0, 0), - /* REG0C */ - [F_WD_FAULT] = REG_FIELD(0x0C, 7, 7), - [F_BOOST_FAULT] = REG_FIELD(0x0C, 6, 6), - [F_CHG_FAULT] = REG_FIELD(0x0C, 4, 5), - [F_BAT_FAULT] = REG_FIELD(0x0C, 3, 3), - [F_NTC_FAULT] = REG_FIELD(0x0C, 0, 2), - /* REG0D */ - [F_FORCE_VINDPM] = REG_FIELD(0x0D, 7, 7), - [F_VINDPM] = REG_FIELD(0x0D, 0, 6), - /* REG0E */ - [F_THERM_STAT] = REG_FIELD(0x0E, 7, 7), - [F_BATV] = REG_FIELD(0x0E, 0, 6), - /* REG0F */ - [F_SYSV] = REG_FIELD(0x0F, 0, 6), - /* REG10 */ - [F_TSPCT] = REG_FIELD(0x10, 0, 6), - /* REG11 */ - [F_VBUS_GD] = REG_FIELD(0x11, 7, 7), - [F_VBUSV] = REG_FIELD(0x11, 0, 6), - /* REG12 */ - [F_ICHGR] = REG_FIELD(0x12, 0, 6), - /* REG13 */ - [F_VDPM_STAT] = REG_FIELD(0x13, 7, 7), - [F_IDPM_STAT] = REG_FIELD(0x13, 6, 6), - [F_IDPM_LIM] = REG_FIELD(0x13, 0, 5), - /* REG14 */ - [F_REG_RST] = REG_FIELD(0x14, 7, 7), - [F_ICO_OPTIMIZED] = REG_FIELD(0x14, 6, 6), - [F_PN] = REG_FIELD(0x14, 3, 5), - [F_TS_PROFILE] = REG_FIELD(0x14, 2, 2), - [F_DEV_REV] = REG_FIELD(0x14, 0, 1) -}; - -/* - * Most of the val -> idx conversions can be computed, given the minimum, - * maximum and the step between values. For the rest of conversions, we use - * lookup tables. - */ -enum bq25890_table_ids { - /* range tables */ - TBL_ICHG, - TBL_ITERM, - TBL_IPRECHG, - TBL_VREG, - TBL_BATCMP, - TBL_VCLAMP, - TBL_BOOSTV, - TBL_SYSVMIN, - - /* lookup tables */ - TBL_TREG, - TBL_BOOSTI, -}; - -/* Thermal Regulation Threshold lookup table, in degrees Celsius */ -static const u32 bq25890_treg_tbl[] = { 60, 80, 100, 120 }; - -#define BQ25890_TREG_TBL_SIZE ARRAY_SIZE(bq25890_treg_tbl) - -/* Boost mode current limit lookup table, in uA */ -static const u32 bq25890_boosti_tbl[] = { - 500000, 700000, 1100000, 1300000, 1600000, 1800000, 2100000, 2400000 -}; - -#define BQ25890_BOOSTI_TBL_SIZE ARRAY_SIZE(bq25890_boosti_tbl) - -struct bq25890_range { - u32 min; - u32 max; - u32 step; -}; - -struct bq25890_lookup { - const u32 *tbl; - u32 size; -}; - -static const union { - struct bq25890_range rt; - struct bq25890_lookup lt; -} bq25890_tables[] = { - /* range tables */ - [TBL_ICHG] = { .rt = {0, 5056000, 64000} }, /* uA */ - [TBL_ITERM] = { .rt = {64000, 1024000, 64000} }, /* uA */ - [TBL_VREG] = { .rt = {3840000, 4608000, 16000} }, /* uV */ - [TBL_BATCMP] = { .rt = {0, 140, 20} }, /* mOhm */ - [TBL_VCLAMP] = { .rt = {0, 224000, 32000} }, /* uV */ - [TBL_BOOSTV] = { .rt = {4550000, 5510000, 64000} }, /* uV */ - [TBL_SYSVMIN] = { .rt = {3000000, 3700000, 100000} }, /* uV */ - - /* lookup tables */ - [TBL_TREG] = { .lt = {bq25890_treg_tbl, BQ25890_TREG_TBL_SIZE} }, - [TBL_BOOSTI] = { .lt = {bq25890_boosti_tbl, BQ25890_BOOSTI_TBL_SIZE} } -}; - -static int bq25890_field_read(struct bq25890_device *bq, - enum bq25890_fields field_id) -{ - int ret; - int val; - - ret = regmap_field_read(bq->rmap_fields[field_id], &val); - if (ret < 0) - return ret; - - return val; -} - -static int bq25890_field_write(struct bq25890_device *bq, - enum bq25890_fields field_id, u8 val) -{ - return regmap_field_write(bq->rmap_fields[field_id], val); -} - -static u8 bq25890_find_idx(u32 value, enum bq25890_table_ids id) -{ - u8 idx; - - if (id >= TBL_TREG) { - const u32 *tbl = bq25890_tables[id].lt.tbl; - u32 tbl_size = bq25890_tables[id].lt.size; - - for (idx = 1; idx < tbl_size && tbl[idx] <= value; idx++) - ; - } else { - const struct bq25890_range *rtbl = &bq25890_tables[id].rt; - u8 rtbl_size; - - rtbl_size = (rtbl->max - rtbl->min) / rtbl->step + 1; - - for (idx = 1; - idx < rtbl_size && (idx * rtbl->step + rtbl->min <= value); - idx++) - ; - } - - return idx - 1; -} - -static u32 bq25890_find_val(u8 idx, enum bq25890_table_ids id) -{ - const struct bq25890_range *rtbl; - - /* lookup table? */ - if (id >= TBL_TREG) - return bq25890_tables[id].lt.tbl[idx]; - - /* range table */ - rtbl = &bq25890_tables[id].rt; - - return (rtbl->min + idx * rtbl->step); -} - -enum bq25890_status { - STATUS_NOT_CHARGING, - STATUS_PRE_CHARGING, - STATUS_FAST_CHARGING, - STATUS_TERMINATION_DONE, -}; - -enum bq25890_chrg_fault { - CHRG_FAULT_NORMAL, - CHRG_FAULT_INPUT, - CHRG_FAULT_THERMAL_SHUTDOWN, - CHRG_FAULT_TIMER_EXPIRED, -}; - -static int bq25890_power_supply_get_property(struct power_supply *psy, - enum power_supply_property psp, - union power_supply_propval *val) -{ - int ret; - struct bq25890_device *bq = power_supply_get_drvdata(psy); - struct bq25890_state state; - - mutex_lock(&bq->lock); - state = bq->state; - mutex_unlock(&bq->lock); - - switch (psp) { - case POWER_SUPPLY_PROP_STATUS: - if (!state.online) - val->intval = POWER_SUPPLY_STATUS_DISCHARGING; - else if (state.chrg_status == STATUS_NOT_CHARGING) - val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; - else if (state.chrg_status == STATUS_PRE_CHARGING || - state.chrg_status == STATUS_FAST_CHARGING) - val->intval = POWER_SUPPLY_STATUS_CHARGING; - else if (state.chrg_status == STATUS_TERMINATION_DONE) - val->intval = POWER_SUPPLY_STATUS_FULL; - else - val->intval = POWER_SUPPLY_STATUS_UNKNOWN; - - break; - - case POWER_SUPPLY_PROP_MANUFACTURER: - val->strval = BQ25890_MANUFACTURER; - break; - - case POWER_SUPPLY_PROP_ONLINE: - val->intval = state.online; - break; - - case POWER_SUPPLY_PROP_HEALTH: - if (!state.chrg_fault && !state.bat_fault && !state.boost_fault) - val->intval = POWER_SUPPLY_HEALTH_GOOD; - else if (state.bat_fault) - val->intval = POWER_SUPPLY_HEALTH_OVERVOLTAGE; - else if (state.chrg_fault == CHRG_FAULT_TIMER_EXPIRED) - val->intval = POWER_SUPPLY_HEALTH_SAFETY_TIMER_EXPIRE; - else if (state.chrg_fault == CHRG_FAULT_THERMAL_SHUTDOWN) - val->intval = POWER_SUPPLY_HEALTH_OVERHEAT; - else - val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; - break; - - case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: - ret = bq25890_field_read(bq, F_ICHGR); /* read measured value */ - if (ret < 0) - return ret; - - /* converted_val = ADC_val * 50mA (table 10.3.19) */ - val->intval = ret * 50000; - break; - - case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX: - val->intval = bq25890_tables[TBL_ICHG].rt.max; - break; - - case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: - if (!state.online) { - val->intval = 0; - break; - } - - ret = bq25890_field_read(bq, F_BATV); /* read measured value */ - if (ret < 0) - return ret; - - /* converted_val = 2.304V + ADC_val * 20mV (table 10.3.15) */ - val->intval = 2304000 + ret * 20000; - break; - - case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX: - val->intval = bq25890_tables[TBL_VREG].rt.max; - break; - - case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT: - val->intval = bq25890_find_val(bq->init_data.iterm, TBL_ITERM); - break; - - default: - return -EINVAL; - } - - return 0; -} - -static int bq25890_get_chip_state(struct bq25890_device *bq, - struct bq25890_state *state) -{ - int i, ret; - - struct { - enum bq25890_fields id; - u8 *data; - } state_fields[] = { - {F_CHG_STAT, &state->chrg_status}, - {F_PG_STAT, &state->online}, - {F_VSYS_STAT, &state->vsys_status}, - {F_BOOST_FAULT, &state->boost_fault}, - {F_BAT_FAULT, &state->bat_fault}, - {F_CHG_FAULT, &state->chrg_fault} - }; - - for (i = 0; i < ARRAY_SIZE(state_fields); i++) { - ret = bq25890_field_read(bq, state_fields[i].id); - if (ret < 0) - return ret; - - *state_fields[i].data = ret; - } - - dev_dbg(bq->dev, "S:CHG/PG/VSYS=%d/%d/%d, F:CHG/BOOST/BAT=%d/%d/%d\n", - state->chrg_status, state->online, state->vsys_status, - state->chrg_fault, state->boost_fault, state->bat_fault); - - return 0; -} - -static bool bq25890_state_changed(struct bq25890_device *bq, - struct bq25890_state *new_state) -{ - struct bq25890_state old_state; - - mutex_lock(&bq->lock); - old_state = bq->state; - mutex_unlock(&bq->lock); - - return (old_state.chrg_status != new_state->chrg_status || - old_state.chrg_fault != new_state->chrg_fault || - old_state.online != new_state->online || - old_state.bat_fault != new_state->bat_fault || - old_state.boost_fault != new_state->boost_fault || - old_state.vsys_status != new_state->vsys_status); -} - -static void bq25890_handle_state_change(struct bq25890_device *bq, - struct bq25890_state *new_state) -{ - int ret; - struct bq25890_state old_state; - - mutex_lock(&bq->lock); - old_state = bq->state; - mutex_unlock(&bq->lock); - - if (!new_state->online) { /* power removed */ - /* disable ADC */ - ret = bq25890_field_write(bq, F_CONV_START, 0); - if (ret < 0) - goto error; - } else if (!old_state.online) { /* power inserted */ - /* enable ADC, to have control of charge current/voltage */ - ret = bq25890_field_write(bq, F_CONV_START, 1); - if (ret < 0) - goto error; - } - - return; - -error: - dev_err(bq->dev, "Error communicating with the chip.\n"); -} - -static irqreturn_t bq25890_irq_handler_thread(int irq, void *private) -{ - struct bq25890_device *bq = private; - int ret; - struct bq25890_state state; - - ret = bq25890_get_chip_state(bq, &state); - if (ret < 0) - goto handled; - - if (!bq25890_state_changed(bq, &state)) - goto handled; - - bq25890_handle_state_change(bq, &state); - - mutex_lock(&bq->lock); - bq->state = state; - mutex_unlock(&bq->lock); - - power_supply_changed(bq->charger); - -handled: - return IRQ_HANDLED; -} - -static int bq25890_chip_reset(struct bq25890_device *bq) -{ - int ret; - int rst_check_counter = 10; - - ret = bq25890_field_write(bq, F_REG_RST, 1); - if (ret < 0) - return ret; - - do { - ret = bq25890_field_read(bq, F_REG_RST); - if (ret < 0) - return ret; - - usleep_range(5, 10); - } while (ret == 1 && --rst_check_counter); - - if (!rst_check_counter) - return -ETIMEDOUT; - - return 0; -} - -static int bq25890_hw_init(struct bq25890_device *bq) -{ - int ret; - int i; - struct bq25890_state state; - - const struct { - enum bq25890_fields id; - u32 value; - } init_data[] = { - {F_ICHG, bq->init_data.ichg}, - {F_VREG, bq->init_data.vreg}, - {F_ITERM, bq->init_data.iterm}, - {F_IPRECHG, bq->init_data.iprechg}, - {F_SYSVMIN, bq->init_data.sysvmin}, - {F_BOOSTV, bq->init_data.boostv}, - {F_BOOSTI, bq->init_data.boosti}, - {F_BOOSTF, bq->init_data.boostf}, - {F_EN_ILIM, bq->init_data.ilim_en}, - {F_TREG, bq->init_data.treg} - }; - - ret = bq25890_chip_reset(bq); - if (ret < 0) - return ret; - - /* disable watchdog */ - ret = bq25890_field_write(bq, F_WD, 0); - if (ret < 0) - return ret; - - /* initialize currents/voltages and other parameters */ - for (i = 0; i < ARRAY_SIZE(init_data); i++) { - ret = bq25890_field_write(bq, init_data[i].id, - init_data[i].value); - if (ret < 0) - return ret; - } - - /* Configure ADC for continuous conversions. This does not enable it. */ - ret = bq25890_field_write(bq, F_CONV_RATE, 1); - if (ret < 0) - return ret; - - ret = bq25890_get_chip_state(bq, &state); - if (ret < 0) - return ret; - - mutex_lock(&bq->lock); - bq->state = state; - mutex_unlock(&bq->lock); - - return 0; -} - -static enum power_supply_property bq25890_power_supply_props[] = { - POWER_SUPPLY_PROP_MANUFACTURER, - POWER_SUPPLY_PROP_STATUS, - POWER_SUPPLY_PROP_ONLINE, - POWER_SUPPLY_PROP_HEALTH, - POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT, - POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX, - POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE, - POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX, - POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT, -}; - -static char *bq25890_charger_supplied_to[] = { - "main-battery", -}; - -static const struct power_supply_desc bq25890_power_supply_desc = { - .name = "bq25890-charger", - .type = POWER_SUPPLY_TYPE_USB, - .properties = bq25890_power_supply_props, - .num_properties = ARRAY_SIZE(bq25890_power_supply_props), - .get_property = bq25890_power_supply_get_property, -}; - -static int bq25890_power_supply_init(struct bq25890_device *bq) -{ - struct power_supply_config psy_cfg = { .drv_data = bq, }; - - psy_cfg.supplied_to = bq25890_charger_supplied_to; - psy_cfg.num_supplicants = ARRAY_SIZE(bq25890_charger_supplied_to); - - bq->charger = power_supply_register(bq->dev, &bq25890_power_supply_desc, - &psy_cfg); - - return PTR_ERR_OR_ZERO(bq->charger); -} - -static void bq25890_usb_work(struct work_struct *data) -{ - int ret; - struct bq25890_device *bq = - container_of(data, struct bq25890_device, usb_work); - - switch (bq->usb_event) { - case USB_EVENT_ID: - /* Enable boost mode */ - ret = bq25890_field_write(bq, F_OTG_CFG, 1); - if (ret < 0) - goto error; - break; - - case USB_EVENT_NONE: - /* Disable boost mode */ - ret = bq25890_field_write(bq, F_OTG_CFG, 0); - if (ret < 0) - goto error; - - power_supply_changed(bq->charger); - break; - } - - return; - -error: - dev_err(bq->dev, "Error switching to boost/charger mode.\n"); -} - -static int bq25890_usb_notifier(struct notifier_block *nb, unsigned long val, - void *priv) -{ - struct bq25890_device *bq = - container_of(nb, struct bq25890_device, usb_nb); - - bq->usb_event = val; - queue_work(system_power_efficient_wq, &bq->usb_work); - - return NOTIFY_OK; -} - -static int bq25890_irq_probe(struct bq25890_device *bq) -{ - struct gpio_desc *irq; - - irq = devm_gpiod_get_index(bq->dev, BQ25890_IRQ_PIN, 0, GPIOD_IN); - if (IS_ERR(irq)) { - dev_err(bq->dev, "Could not probe irq pin.\n"); - return PTR_ERR(irq); - } - - return gpiod_to_irq(irq); -} - -static int bq25890_fw_read_u32_props(struct bq25890_device *bq) -{ - int ret; - u32 property; - int i; - struct bq25890_init_data *init = &bq->init_data; - struct { - char *name; - bool optional; - enum bq25890_table_ids tbl_id; - u8 *conv_data; /* holds converted value from given property */ - } props[] = { - /* required properties */ - {"ti,charge-current", false, TBL_ICHG, &init->ichg}, - {"ti,battery-regulation-voltage", false, TBL_VREG, &init->vreg}, - {"ti,termination-current", false, TBL_ITERM, &init->iterm}, - {"ti,precharge-current", false, TBL_ITERM, &init->iprechg}, - {"ti,minimum-sys-voltage", false, TBL_SYSVMIN, &init->sysvmin}, - {"ti,boost-voltage", false, TBL_BOOSTV, &init->boostv}, - {"ti,boost-max-current", false, TBL_BOOSTI, &init->boosti}, - - /* optional properties */ - {"ti,thermal-regulation-threshold", true, TBL_TREG, &init->treg} - }; - - /* initialize data for optional properties */ - init->treg = 3; /* 120 degrees Celsius */ - - for (i = 0; i < ARRAY_SIZE(props); i++) { - ret = device_property_read_u32(bq->dev, props[i].name, - &property); - if (ret < 0) { - if (props[i].optional) - continue; - - return ret; - } - - *props[i].conv_data = bq25890_find_idx(property, - props[i].tbl_id); - } - - return 0; -} - -static int bq25890_fw_probe(struct bq25890_device *bq) -{ - int ret; - struct bq25890_init_data *init = &bq->init_data; - - ret = bq25890_fw_read_u32_props(bq); - if (ret < 0) - return ret; - - init->ilim_en = device_property_read_bool(bq->dev, "ti,use-ilim-pin"); - init->boostf = device_property_read_bool(bq->dev, "ti,boost-low-freq"); - - return 0; -} - -static int bq25890_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 bq25890_device *bq; - int ret; - int i; - - if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) { - dev_err(dev, "No support for SMBUS_BYTE_DATA\n"); - return -ENODEV; - } - - bq = devm_kzalloc(dev, sizeof(*bq), GFP_KERNEL); - if (!bq) - return -ENOMEM; - - bq->client = client; - bq->dev = dev; - - mutex_init(&bq->lock); - - bq->rmap = devm_regmap_init_i2c(client, &bq25890_regmap_config); - if (IS_ERR(bq->rmap)) { - dev_err(dev, "failed to allocate register map\n"); - return PTR_ERR(bq->rmap); - } - - for (i = 0; i < ARRAY_SIZE(bq25890_reg_fields); i++) { - const struct reg_field *reg_fields = bq25890_reg_fields; - - bq->rmap_fields[i] = devm_regmap_field_alloc(dev, bq->rmap, - reg_fields[i]); - if (IS_ERR(bq->rmap_fields[i])) { - dev_err(dev, "cannot allocate regmap field\n"); - return PTR_ERR(bq->rmap_fields[i]); - } - } - - i2c_set_clientdata(client, bq); - - bq->chip_id = bq25890_field_read(bq, F_PN); - if (bq->chip_id < 0) { - dev_err(dev, "Cannot read chip ID.\n"); - return bq->chip_id; - } - - if (bq->chip_id != BQ25890_ID) { - dev_err(dev, "Chip with ID=%d, not supported!\n", bq->chip_id); - return -ENODEV; - } - - if (!dev->platform_data) { - ret = bq25890_fw_probe(bq); - if (ret < 0) { - dev_err(dev, "Cannot read device properties.\n"); - return ret; - } - } else { - return -ENODEV; - } - - ret = bq25890_hw_init(bq); - if (ret < 0) { - dev_err(dev, "Cannot initialize the chip.\n"); - return ret; - } - - if (client->irq <= 0) - client->irq = bq25890_irq_probe(bq); - - if (client->irq < 0) { - dev_err(dev, "No irq resource found.\n"); - return client->irq; - } - - /* OTG reporting */ - bq->usb_phy = devm_usb_get_phy(dev, USB_PHY_TYPE_USB2); - if (!IS_ERR_OR_NULL(bq->usb_phy)) { - INIT_WORK(&bq->usb_work, bq25890_usb_work); - bq->usb_nb.notifier_call = bq25890_usb_notifier; - usb_register_notifier(bq->usb_phy, &bq->usb_nb); - } - - ret = devm_request_threaded_irq(dev, client->irq, NULL, - bq25890_irq_handler_thread, - IRQF_TRIGGER_FALLING | IRQF_ONESHOT, - BQ25890_IRQ_PIN, bq); - if (ret) - goto irq_fail; - - ret = bq25890_power_supply_init(bq); - if (ret < 0) { - dev_err(dev, "Failed to register power supply\n"); - goto irq_fail; - } - - return 0; - -irq_fail: - if (!IS_ERR_OR_NULL(bq->usb_phy)) - usb_unregister_notifier(bq->usb_phy, &bq->usb_nb); - - return ret; -} - -static int bq25890_remove(struct i2c_client *client) -{ - struct bq25890_device *bq = i2c_get_clientdata(client); - - power_supply_unregister(bq->charger); - - if (!IS_ERR_OR_NULL(bq->usb_phy)) - usb_unregister_notifier(bq->usb_phy, &bq->usb_nb); - - /* reset all registers to default values */ - bq25890_chip_reset(bq); - - return 0; -} - -#ifdef CONFIG_PM_SLEEP -static int bq25890_suspend(struct device *dev) -{ - struct bq25890_device *bq = dev_get_drvdata(dev); - - /* - * If charger is removed, while in suspend, make sure ADC is diabled - * since it consumes slightly more power. - */ - return bq25890_field_write(bq, F_CONV_START, 0); -} - -static int bq25890_resume(struct device *dev) -{ - int ret; - struct bq25890_state state; - struct bq25890_device *bq = dev_get_drvdata(dev); - - ret = bq25890_get_chip_state(bq, &state); - if (ret < 0) - return ret; - - mutex_lock(&bq->lock); - bq->state = state; - mutex_unlock(&bq->lock); - - /* Re-enable ADC only if charger is plugged in. */ - if (state.online) { - ret = bq25890_field_write(bq, F_CONV_START, 1); - if (ret < 0) - return ret; - } - - /* signal userspace, maybe state changed while suspended */ - power_supply_changed(bq->charger); - - return 0; -} -#endif - -static const struct dev_pm_ops bq25890_pm = { - SET_SYSTEM_SLEEP_PM_OPS(bq25890_suspend, bq25890_resume) -}; - -static const struct i2c_device_id bq25890_i2c_ids[] = { - { "bq25890", 0 }, - {}, -}; -MODULE_DEVICE_TABLE(i2c, bq25890_i2c_ids); - -static const struct of_device_id bq25890_of_match[] = { - { .compatible = "ti,bq25890", }, - { }, -}; -MODULE_DEVICE_TABLE(of, bq25890_of_match); - -static const struct acpi_device_id bq25890_acpi_match[] = { - {"BQ258900", 0}, - {}, -}; -MODULE_DEVICE_TABLE(acpi, bq25890_acpi_match); - -static struct i2c_driver bq25890_driver = { - .driver = { - .name = "bq25890-charger", - .of_match_table = of_match_ptr(bq25890_of_match), - .acpi_match_table = ACPI_PTR(bq25890_acpi_match), - .pm = &bq25890_pm, - }, - .probe = bq25890_probe, - .remove = bq25890_remove, - .id_table = bq25890_i2c_ids, -}; -module_i2c_driver(bq25890_driver); - -MODULE_AUTHOR("Laurentiu Palcu "); -MODULE_DESCRIPTION("bq25890 charger driver"); -MODULE_LICENSE("GPL"); diff --git a/drivers/power/bq27xxx_battery.c b/drivers/power/bq27xxx_battery.c deleted file mode 100644 index 323d05a12f9b..000000000000 --- a/drivers/power/bq27xxx_battery.c +++ /dev/null @@ -1,1102 +0,0 @@ -/* - * BQ27xxx battery driver - * - * Copyright (C) 2008 Rodolfo Giometti - * Copyright (C) 2008 Eurotech S.p.A. - * Copyright (C) 2010-2011 Lars-Peter Clausen - * Copyright (C) 2011 Pali Rohár - * - * Based on a previous work by Copyright (C) 2008 Texas Instruments, Inc. - * - * This package is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - * - * THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR - * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED - * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. - * - * Datasheets: - * http://www.ti.com/product/bq27000 - * http://www.ti.com/product/bq27200 - * http://www.ti.com/product/bq27010 - * http://www.ti.com/product/bq27210 - * http://www.ti.com/product/bq27500 - * http://www.ti.com/product/bq27510-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 - * http://www.ti.com/product/bq27542-g1 - * http://www.ti.com/product/bq27546-g1 - * http://www.ti.com/product/bq27742-g1 - * http://www.ti.com/product/bq27545-g1 - * http://www.ti.com/product/bq27421-g1 - * http://www.ti.com/product/bq27425-g1 - * http://www.ti.com/product/bq27411-g1 - * http://www.ti.com/product/bq27621-g1 - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#define DRIVER_VERSION "1.2.0" - -#define BQ27XXX_MANUFACTURER "Texas Instruments" - -/* BQ27XXX Flags */ -#define BQ27XXX_FLAG_DSC BIT(0) -#define BQ27XXX_FLAG_SOCF BIT(1) /* State-of-Charge threshold final */ -#define BQ27XXX_FLAG_SOC1 BIT(2) /* State-of-Charge threshold 1 */ -#define BQ27XXX_FLAG_FC BIT(9) -#define BQ27XXX_FLAG_OTD BIT(14) -#define BQ27XXX_FLAG_OTC BIT(15) -#define BQ27XXX_FLAG_UT BIT(14) -#define BQ27XXX_FLAG_OT BIT(15) - -/* BQ27000 has different layout for Flags register */ -#define BQ27000_FLAG_EDVF BIT(0) /* Final End-of-Discharge-Voltage flag */ -#define BQ27000_FLAG_EDV1 BIT(1) /* First End-of-Discharge-Voltage flag */ -#define BQ27000_FLAG_CI BIT(4) /* Capacity Inaccurate flag */ -#define BQ27000_FLAG_FC BIT(5) -#define BQ27000_FLAG_CHGS BIT(7) /* Charge state flag */ - -#define BQ27XXX_RS (20) /* Resistor sense mOhm */ -#define BQ27XXX_POWER_CONSTANT (29200) /* 29.2 µV^2 * 1000 */ -#define BQ27XXX_CURRENT_CONSTANT (3570) /* 3.57 µV * 1000 */ - -#define INVALID_REG_ADDR 0xff - -/* - * bq27xxx_reg_index - Register names - * - * These are indexes into a device's register mapping array. - */ - -enum bq27xxx_reg_index { - BQ27XXX_REG_CTRL = 0, /* Control */ - BQ27XXX_REG_TEMP, /* Temperature */ - BQ27XXX_REG_INT_TEMP, /* Internal Temperature */ - BQ27XXX_REG_VOLT, /* Voltage */ - BQ27XXX_REG_AI, /* Average Current */ - BQ27XXX_REG_FLAGS, /* Flags */ - BQ27XXX_REG_TTE, /* Time-to-Empty */ - BQ27XXX_REG_TTF, /* Time-to-Full */ - BQ27XXX_REG_TTES, /* Time-to-Empty Standby */ - BQ27XXX_REG_TTECP, /* Time-to-Empty at Constant Power */ - BQ27XXX_REG_NAC, /* Nominal Available Capacity */ - BQ27XXX_REG_FCC, /* Full Charge Capacity */ - BQ27XXX_REG_CYCT, /* Cycle Count */ - BQ27XXX_REG_AE, /* Available Energy */ - BQ27XXX_REG_SOC, /* State-of-Charge */ - BQ27XXX_REG_DCAP, /* Design Capacity */ - BQ27XXX_REG_AP, /* Average Power */ - BQ27XXX_REG_MAX, /* sentinel */ -}; - -/* Register mappings */ -static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = { - [BQ27000] = { - [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] = 0x0b, - [BQ27XXX_REG_DCAP] = 0x76, - [BQ27XXX_REG_AP] = 0x24, - }, - [BQ27010] = { - [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] = INVALID_REG_ADDR, - [BQ27XXX_REG_SOC] = 0x0b, - [BQ27XXX_REG_DCAP] = 0x76, - [BQ27XXX_REG_AP] = INVALID_REG_ADDR, - }, - [BQ27500] = { - [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] = 0x2a, - [BQ27XXX_REG_AE] = INVALID_REG_ADDR, - [BQ27XXX_REG_SOC] = 0x2c, - [BQ27XXX_REG_DCAP] = 0x3c, - [BQ27XXX_REG_AP] = INVALID_REG_ADDR, - }, - [BQ27530] = { - [BQ27XXX_REG_CTRL] = 0x00, - [BQ27XXX_REG_TEMP] = 0x06, - [BQ27XXX_REG_INT_TEMP] = 0x32, - [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] = INVALID_REG_ADDR, - [BQ27XXX_REG_TTECP] = INVALID_REG_ADDR, - [BQ27XXX_REG_NAC] = 0x0c, - [BQ27XXX_REG_FCC] = 0x12, - [BQ27XXX_REG_CYCT] = 0x2a, - [BQ27XXX_REG_AE] = INVALID_REG_ADDR, - [BQ27XXX_REG_SOC] = 0x2c, - [BQ27XXX_REG_DCAP] = INVALID_REG_ADDR, - [BQ27XXX_REG_AP] = 0x24, - }, - [BQ27541] = { - [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] = INVALID_REG_ADDR, - [BQ27XXX_REG_TTECP] = INVALID_REG_ADDR, - [BQ27XXX_REG_NAC] = 0x0c, - [BQ27XXX_REG_FCC] = 0x12, - [BQ27XXX_REG_CYCT] = 0x2a, - [BQ27XXX_REG_AE] = INVALID_REG_ADDR, - [BQ27XXX_REG_SOC] = 0x2c, - [BQ27XXX_REG_DCAP] = 0x3c, - [BQ27XXX_REG_AP] = 0x24, - }, - [BQ27545] = { - [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] = INVALID_REG_ADDR, - [BQ27XXX_REG_TTECP] = INVALID_REG_ADDR, - [BQ27XXX_REG_NAC] = 0x0c, - [BQ27XXX_REG_FCC] = 0x12, - [BQ27XXX_REG_CYCT] = 0x2a, - [BQ27XXX_REG_AE] = INVALID_REG_ADDR, - [BQ27XXX_REG_SOC] = 0x2c, - [BQ27XXX_REG_DCAP] = INVALID_REG_ADDR, - [BQ27XXX_REG_AP] = 0x24, - }, - [BQ27421] = { - [BQ27XXX_REG_CTRL] = 0x00, - [BQ27XXX_REG_TEMP] = 0x02, - [BQ27XXX_REG_INT_TEMP] = 0x1e, - [BQ27XXX_REG_VOLT] = 0x04, - [BQ27XXX_REG_AI] = 0x10, - [BQ27XXX_REG_FLAGS] = 0x06, - [BQ27XXX_REG_TTE] = INVALID_REG_ADDR, - [BQ27XXX_REG_TTF] = INVALID_REG_ADDR, - [BQ27XXX_REG_TTES] = INVALID_REG_ADDR, - [BQ27XXX_REG_TTECP] = INVALID_REG_ADDR, - [BQ27XXX_REG_NAC] = 0x08, - [BQ27XXX_REG_FCC] = 0x0e, - [BQ27XXX_REG_CYCT] = INVALID_REG_ADDR, - [BQ27XXX_REG_AE] = INVALID_REG_ADDR, - [BQ27XXX_REG_SOC] = 0x1c, - [BQ27XXX_REG_DCAP] = 0x3c, - [BQ27XXX_REG_AP] = 0x18, - }, -}; - -static enum power_supply_property bq27000_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_EMPTY_AVG, - 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 bq27010_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_EMPTY_AVG, - 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_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_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 bq27530_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_POWER_AVG, - POWER_SUPPLY_PROP_HEALTH, - POWER_SUPPLY_PROP_CYCLE_COUNT, - POWER_SUPPLY_PROP_MANUFACTURER, -}; - -static enum power_supply_property bq27541_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_POWER_AVG, - POWER_SUPPLY_PROP_HEALTH, - POWER_SUPPLY_PROP_MANUFACTURER, -}; - -static enum power_supply_property bq27545_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_HEALTH, - POWER_SUPPLY_PROP_CYCLE_COUNT, - POWER_SUPPLY_PROP_POWER_AVG, - POWER_SUPPLY_PROP_MANUFACTURER, -}; - -static enum power_supply_property bq27421_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_TECHNOLOGY, - POWER_SUPPLY_PROP_CHARGE_FULL, - POWER_SUPPLY_PROP_CHARGE_NOW, - POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, - POWER_SUPPLY_PROP_MANUFACTURER, -}; - -#define BQ27XXX_PROP(_id, _prop) \ - [_id] = { \ - .props = _prop, \ - .size = ARRAY_SIZE(_prop), \ - } - -static struct { - enum power_supply_property *props; - size_t size; -} bq27xxx_battery_props[] = { - BQ27XXX_PROP(BQ27000, bq27000_battery_props), - BQ27XXX_PROP(BQ27010, bq27010_battery_props), - BQ27XXX_PROP(BQ27500, bq27500_battery_props), - BQ27XXX_PROP(BQ27530, bq27530_battery_props), - BQ27XXX_PROP(BQ27541, bq27541_battery_props), - BQ27XXX_PROP(BQ27545, bq27545_battery_props), - BQ27XXX_PROP(BQ27421, bq27421_battery_props), -}; - -static unsigned int poll_interval = 360; -module_param(poll_interval, uint, 0644); -MODULE_PARM_DESC(poll_interval, - "battery poll interval in seconds - 0 disables polling"); - -/* - * Common code for BQ27xxx devices - */ - -static inline int bq27xxx_read(struct bq27xxx_device_info *di, int reg_index, - bool single) -{ - /* Reports EINVAL for invalid/missing registers */ - if (!di || di->regs[reg_index] == INVALID_REG_ADDR) - return -EINVAL; - - return di->bus.read(di, di->regs[reg_index], single); -} - -/* - * Return the battery State-of-Charge - * Or < 0 if something fails. - */ -static int bq27xxx_battery_read_soc(struct bq27xxx_device_info *di) -{ - int soc; - - if (di->chip == BQ27000 || di->chip == BQ27010) - soc = bq27xxx_read(di, BQ27XXX_REG_SOC, true); - else - soc = bq27xxx_read(di, BQ27XXX_REG_SOC, false); - - if (soc < 0) - dev_dbg(di->dev, "error reading State-of-Charge\n"); - - return soc; -} - -/* - * Return a battery charge value in µAh - * Or < 0 if something fails. - */ -static int bq27xxx_battery_read_charge(struct bq27xxx_device_info *di, u8 reg) -{ - int charge; - - charge = bq27xxx_read(di, reg, false); - if (charge < 0) { - dev_dbg(di->dev, "error reading charge register %02x: %d\n", - reg, charge); - return charge; - } - - if (di->chip == BQ27000 || di->chip == BQ27010) - charge *= BQ27XXX_CURRENT_CONSTANT / BQ27XXX_RS; - else - charge *= 1000; - - return charge; -} - -/* - * Return the battery Nominal available capacity in µAh - * Or < 0 if something fails. - */ -static inline int bq27xxx_battery_read_nac(struct bq27xxx_device_info *di) -{ - int flags; - - if (di->chip == BQ27000 || di->chip == BQ27010) { - flags = bq27xxx_read(di, BQ27XXX_REG_FLAGS, true); - if (flags >= 0 && (flags & BQ27000_FLAG_CI)) - return -ENODATA; - } - - return bq27xxx_battery_read_charge(di, BQ27XXX_REG_NAC); -} - -/* - * Return the battery Full Charge Capacity in µAh - * Or < 0 if something fails. - */ -static inline int bq27xxx_battery_read_fcc(struct bq27xxx_device_info *di) -{ - return bq27xxx_battery_read_charge(di, BQ27XXX_REG_FCC); -} - -/* - * Return the Design Capacity in µAh - * Or < 0 if something fails. - */ -static int bq27xxx_battery_read_dcap(struct bq27xxx_device_info *di) -{ - int dcap; - - if (di->chip == BQ27000 || di->chip == BQ27010) - dcap = bq27xxx_read(di, BQ27XXX_REG_DCAP, true); - else - dcap = bq27xxx_read(di, BQ27XXX_REG_DCAP, false); - - if (dcap < 0) { - dev_dbg(di->dev, "error reading initial last measured discharge\n"); - return dcap; - } - - if (di->chip == BQ27000 || di->chip == BQ27010) - dcap = (dcap << 8) * BQ27XXX_CURRENT_CONSTANT / BQ27XXX_RS; - else - dcap *= 1000; - - return dcap; -} - -/* - * Return the battery Available energy in µWh - * Or < 0 if something fails. - */ -static int bq27xxx_battery_read_energy(struct bq27xxx_device_info *di) -{ - int ae; - - ae = bq27xxx_read(di, BQ27XXX_REG_AE, false); - if (ae < 0) { - dev_dbg(di->dev, "error reading available energy\n"); - return ae; - } - - if (di->chip == BQ27000 || di->chip == BQ27010) - ae *= BQ27XXX_POWER_CONSTANT / BQ27XXX_RS; - else - ae *= 1000; - - return ae; -} - -/* - * Return the battery temperature in tenths of degree Kelvin - * Or < 0 if something fails. - */ -static int bq27xxx_battery_read_temperature(struct bq27xxx_device_info *di) -{ - int temp; - - temp = bq27xxx_read(di, BQ27XXX_REG_TEMP, false); - if (temp < 0) { - dev_err(di->dev, "error reading temperature\n"); - return temp; - } - - if (di->chip == BQ27000 || di->chip == BQ27010) - temp = 5 * temp / 2; - - return temp; -} - -/* - * Return the battery Cycle count total - * Or < 0 if something fails. - */ -static int bq27xxx_battery_read_cyct(struct bq27xxx_device_info *di) -{ - int cyct; - - cyct = bq27xxx_read(di, BQ27XXX_REG_CYCT, false); - if (cyct < 0) - dev_err(di->dev, "error reading cycle count total\n"); - - return cyct; -} - -/* - * Read a time register. - * Return < 0 if something fails. - */ -static int bq27xxx_battery_read_time(struct bq27xxx_device_info *di, u8 reg) -{ - int tval; - - tval = bq27xxx_read(di, reg, false); - if (tval < 0) { - dev_dbg(di->dev, "error reading time register %02x: %d\n", - reg, tval); - return tval; - } - - if (tval == 65535) - return -ENODATA; - - return tval * 60; -} - -/* - * Read an average power register. - * Return < 0 if something fails. - */ -static int bq27xxx_battery_read_pwr_avg(struct bq27xxx_device_info *di) -{ - int tval; - - tval = bq27xxx_read(di, BQ27XXX_REG_AP, false); - if (tval < 0) { - dev_err(di->dev, "error reading average power register %02x: %d\n", - BQ27XXX_REG_AP, tval); - return tval; - } - - if (di->chip == BQ27000 || di->chip == BQ27010) - return (tval * BQ27XXX_POWER_CONSTANT) / BQ27XXX_RS; - else - return tval; -} - -/* - * Returns true if a battery over temperature condition is detected - */ -static bool bq27xxx_battery_overtemp(struct bq27xxx_device_info *di, u16 flags) -{ - if (di->chip == BQ27500 || di->chip == BQ27541 || di->chip == BQ27545) - return flags & (BQ27XXX_FLAG_OTC | BQ27XXX_FLAG_OTD); - if (di->chip == BQ27530 || di->chip == BQ27421) - return flags & BQ27XXX_FLAG_OT; - - return false; -} - -/* - * Returns true if a battery under temperature condition is detected - */ -static bool bq27xxx_battery_undertemp(struct bq27xxx_device_info *di, u16 flags) -{ - if (di->chip == BQ27530 || di->chip == BQ27421) - return flags & BQ27XXX_FLAG_UT; - - return false; -} - -/* - * Returns true if a low state of charge condition is detected - */ -static bool bq27xxx_battery_dead(struct bq27xxx_device_info *di, u16 flags) -{ - if (di->chip == BQ27000 || di->chip == BQ27010) - return flags & (BQ27000_FLAG_EDV1 | BQ27000_FLAG_EDVF); - else - return flags & (BQ27XXX_FLAG_SOC1 | BQ27XXX_FLAG_SOCF); -} - -/* - * Read flag register. - * Return < 0 if something fails. - */ -static int bq27xxx_battery_read_health(struct bq27xxx_device_info *di) -{ - int flags; - - flags = bq27xxx_read(di, BQ27XXX_REG_FLAGS, false); - if (flags < 0) { - dev_err(di->dev, "error reading flag register:%d\n", flags); - return flags; - } - - /* Unlikely but important to return first */ - if (unlikely(bq27xxx_battery_overtemp(di, flags))) - return POWER_SUPPLY_HEALTH_OVERHEAT; - if (unlikely(bq27xxx_battery_undertemp(di, flags))) - return POWER_SUPPLY_HEALTH_COLD; - if (unlikely(bq27xxx_battery_dead(di, flags))) - return POWER_SUPPLY_HEALTH_DEAD; - - return POWER_SUPPLY_HEALTH_GOOD; -} - -void bq27xxx_battery_update(struct bq27xxx_device_info *di) -{ - struct bq27xxx_reg_cache cache = {0, }; - bool has_ci_flag = di->chip == BQ27000 || di->chip == BQ27010; - bool has_singe_flag = di->chip == BQ27000 || di->chip == BQ27010; - - cache.flags = bq27xxx_read(di, BQ27XXX_REG_FLAGS, has_singe_flag); - if ((cache.flags & 0xff) == 0xff) - cache.flags = -1; /* read error */ - if (cache.flags >= 0) { - cache.temperature = bq27xxx_battery_read_temperature(di); - if (has_ci_flag && (cache.flags & BQ27000_FLAG_CI)) { - dev_info_once(di->dev, "battery is not calibrated! ignoring capacity values\n"); - cache.capacity = -ENODATA; - cache.energy = -ENODATA; - cache.time_to_empty = -ENODATA; - cache.time_to_empty_avg = -ENODATA; - cache.time_to_full = -ENODATA; - cache.charge_full = -ENODATA; - cache.health = -ENODATA; - } else { - if (di->regs[BQ27XXX_REG_TTE] != INVALID_REG_ADDR) - cache.time_to_empty = bq27xxx_battery_read_time(di, BQ27XXX_REG_TTE); - if (di->regs[BQ27XXX_REG_TTECP] != INVALID_REG_ADDR) - cache.time_to_empty_avg = bq27xxx_battery_read_time(di, BQ27XXX_REG_TTECP); - if (di->regs[BQ27XXX_REG_TTF] != INVALID_REG_ADDR) - cache.time_to_full = bq27xxx_battery_read_time(di, BQ27XXX_REG_TTF); - cache.charge_full = bq27xxx_battery_read_fcc(di); - cache.capacity = bq27xxx_battery_read_soc(di); - if (di->regs[BQ27XXX_REG_AE] != INVALID_REG_ADDR) - cache.energy = bq27xxx_battery_read_energy(di); - cache.health = bq27xxx_battery_read_health(di); - } - if (di->regs[BQ27XXX_REG_CYCT] != INVALID_REG_ADDR) - cache.cycle_count = bq27xxx_battery_read_cyct(di); - if (di->regs[BQ27XXX_REG_AP] != INVALID_REG_ADDR) - cache.power_avg = bq27xxx_battery_read_pwr_avg(di); - - /* We only have to read charge design full once */ - if (di->charge_design_full <= 0) - di->charge_design_full = bq27xxx_battery_read_dcap(di); - } - - if (di->cache.capacity != cache.capacity) - power_supply_changed(di->bat); - - if (memcmp(&di->cache, &cache, sizeof(cache)) != 0) - di->cache = cache; - - di->last_update = jiffies; -} -EXPORT_SYMBOL_GPL(bq27xxx_battery_update); - -static void bq27xxx_battery_poll(struct work_struct *work) -{ - struct bq27xxx_device_info *di = - container_of(work, struct bq27xxx_device_info, - work.work); - - bq27xxx_battery_update(di); - - if (poll_interval > 0) - schedule_delayed_work(&di->work, poll_interval * HZ); -} - -/* - * Return the battery average current in µA - * Note that current can be negative signed as well - * Or 0 if something fails. - */ -static int bq27xxx_battery_current(struct bq27xxx_device_info *di, - union power_supply_propval *val) -{ - int curr; - int flags; - - curr = bq27xxx_read(di, BQ27XXX_REG_AI, false); - if (curr < 0) { - dev_err(di->dev, "error reading current\n"); - return curr; - } - - if (di->chip == BQ27000 || di->chip == BQ27010) { - flags = bq27xxx_read(di, BQ27XXX_REG_FLAGS, false); - if (flags & BQ27000_FLAG_CHGS) { - dev_dbg(di->dev, "negative current!\n"); - curr = -curr; - } - - val->intval = curr * BQ27XXX_CURRENT_CONSTANT / BQ27XXX_RS; - } else { - /* Other gauges return signed value */ - val->intval = (int)((s16)curr) * 1000; - } - - return 0; -} - -static int bq27xxx_battery_status(struct bq27xxx_device_info *di, - union power_supply_propval *val) -{ - int status; - - if (di->chip == BQ27000 || di->chip == BQ27010) { - if (di->cache.flags & BQ27000_FLAG_FC) - status = POWER_SUPPLY_STATUS_FULL; - else if (di->cache.flags & BQ27000_FLAG_CHGS) - status = POWER_SUPPLY_STATUS_CHARGING; - else if (power_supply_am_i_supplied(di->bat)) - status = POWER_SUPPLY_STATUS_NOT_CHARGING; - else - status = POWER_SUPPLY_STATUS_DISCHARGING; - } else { - if (di->cache.flags & BQ27XXX_FLAG_FC) - status = POWER_SUPPLY_STATUS_FULL; - else if (di->cache.flags & BQ27XXX_FLAG_DSC) - status = POWER_SUPPLY_STATUS_DISCHARGING; - else - status = POWER_SUPPLY_STATUS_CHARGING; - } - - val->intval = status; - - return 0; -} - -static int bq27xxx_battery_capacity_level(struct bq27xxx_device_info *di, - union power_supply_propval *val) -{ - int level; - - if (di->chip == BQ27000 || di->chip == BQ27010) { - if (di->cache.flags & BQ27000_FLAG_FC) - level = POWER_SUPPLY_CAPACITY_LEVEL_FULL; - else if (di->cache.flags & BQ27000_FLAG_EDV1) - level = POWER_SUPPLY_CAPACITY_LEVEL_LOW; - else if (di->cache.flags & BQ27000_FLAG_EDVF) - level = POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL; - else - level = POWER_SUPPLY_CAPACITY_LEVEL_NORMAL; - } else { - if (di->cache.flags & BQ27XXX_FLAG_FC) - level = POWER_SUPPLY_CAPACITY_LEVEL_FULL; - else if (di->cache.flags & BQ27XXX_FLAG_SOC1) - level = POWER_SUPPLY_CAPACITY_LEVEL_LOW; - else if (di->cache.flags & BQ27XXX_FLAG_SOCF) - level = POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL; - else - level = POWER_SUPPLY_CAPACITY_LEVEL_NORMAL; - } - - val->intval = level; - - return 0; -} - -/* - * Return the battery Voltage in millivolts - * Or < 0 if something fails. - */ -static int bq27xxx_battery_voltage(struct bq27xxx_device_info *di, - union power_supply_propval *val) -{ - int volt; - - volt = bq27xxx_read(di, BQ27XXX_REG_VOLT, false); - if (volt < 0) { - dev_err(di->dev, "error reading voltage\n"); - return volt; - } - - val->intval = volt * 1000; - - return 0; -} - -static int bq27xxx_simple_value(int value, - union power_supply_propval *val) -{ - if (value < 0) - return value; - - val->intval = value; - - return 0; -} - -static int bq27xxx_battery_get_property(struct power_supply *psy, - enum power_supply_property psp, - union power_supply_propval *val) -{ - int ret = 0; - struct bq27xxx_device_info *di = power_supply_get_drvdata(psy); - - mutex_lock(&di->lock); - if (time_is_before_jiffies(di->last_update + 5 * HZ)) { - cancel_delayed_work_sync(&di->work); - bq27xxx_battery_poll(&di->work.work); - } - mutex_unlock(&di->lock); - - if (psp != POWER_SUPPLY_PROP_PRESENT && di->cache.flags < 0) - return -ENODEV; - - switch (psp) { - case POWER_SUPPLY_PROP_STATUS: - ret = bq27xxx_battery_status(di, val); - break; - case POWER_SUPPLY_PROP_VOLTAGE_NOW: - ret = bq27xxx_battery_voltage(di, val); - break; - case POWER_SUPPLY_PROP_PRESENT: - val->intval = di->cache.flags < 0 ? 0 : 1; - break; - case POWER_SUPPLY_PROP_CURRENT_NOW: - ret = bq27xxx_battery_current(di, val); - break; - case POWER_SUPPLY_PROP_CAPACITY: - ret = bq27xxx_simple_value(di->cache.capacity, val); - break; - case POWER_SUPPLY_PROP_CAPACITY_LEVEL: - ret = bq27xxx_battery_capacity_level(di, val); - break; - case POWER_SUPPLY_PROP_TEMP: - ret = bq27xxx_simple_value(di->cache.temperature, val); - if (ret == 0) - val->intval -= 2731; /* convert decidegree k to c */ - break; - case POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW: - ret = bq27xxx_simple_value(di->cache.time_to_empty, val); - break; - case POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG: - ret = bq27xxx_simple_value(di->cache.time_to_empty_avg, val); - break; - case POWER_SUPPLY_PROP_TIME_TO_FULL_NOW: - ret = bq27xxx_simple_value(di->cache.time_to_full, val); - break; - case POWER_SUPPLY_PROP_TECHNOLOGY: - val->intval = POWER_SUPPLY_TECHNOLOGY_LION; - break; - case POWER_SUPPLY_PROP_CHARGE_NOW: - ret = bq27xxx_simple_value(bq27xxx_battery_read_nac(di), val); - break; - case POWER_SUPPLY_PROP_CHARGE_FULL: - ret = bq27xxx_simple_value(di->cache.charge_full, val); - break; - case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: - ret = bq27xxx_simple_value(di->charge_design_full, val); - break; - case POWER_SUPPLY_PROP_CYCLE_COUNT: - ret = bq27xxx_simple_value(di->cache.cycle_count, val); - break; - case POWER_SUPPLY_PROP_ENERGY_NOW: - ret = bq27xxx_simple_value(di->cache.energy, val); - break; - case POWER_SUPPLY_PROP_POWER_AVG: - ret = bq27xxx_simple_value(di->cache.power_avg, val); - break; - case POWER_SUPPLY_PROP_HEALTH: - ret = bq27xxx_simple_value(di->cache.health, val); - break; - case POWER_SUPPLY_PROP_MANUFACTURER: - val->strval = BQ27XXX_MANUFACTURER; - break; - default: - return -EINVAL; - } - - return ret; -} - -static void bq27xxx_external_power_changed(struct power_supply *psy) -{ - struct bq27xxx_device_info *di = power_supply_get_drvdata(psy); - - cancel_delayed_work_sync(&di->work); - schedule_delayed_work(&di->work, 0); -} - -int bq27xxx_battery_setup(struct bq27xxx_device_info *di) -{ - struct power_supply_desc *psy_desc; - struct power_supply_config psy_cfg = { .drv_data = di, }; - - INIT_DELAYED_WORK(&di->work, bq27xxx_battery_poll); - mutex_init(&di->lock); - di->regs = bq27xxx_regs[di->chip]; - - psy_desc = devm_kzalloc(di->dev, sizeof(*psy_desc), GFP_KERNEL); - if (!psy_desc) - return -ENOMEM; - - psy_desc->name = di->name; - psy_desc->type = POWER_SUPPLY_TYPE_BATTERY; - psy_desc->properties = bq27xxx_battery_props[di->chip].props; - psy_desc->num_properties = bq27xxx_battery_props[di->chip].size; - psy_desc->get_property = bq27xxx_battery_get_property; - psy_desc->external_power_changed = bq27xxx_external_power_changed; - - di->bat = power_supply_register_no_ws(di->dev, psy_desc, &psy_cfg); - if (IS_ERR(di->bat)) { - dev_err(di->dev, "failed to register battery\n"); - return PTR_ERR(di->bat); - } - - dev_info(di->dev, "support ver. %s enabled\n", DRIVER_VERSION); - - bq27xxx_battery_update(di); - - return 0; -} -EXPORT_SYMBOL_GPL(bq27xxx_battery_setup); - -void bq27xxx_battery_teardown(struct bq27xxx_device_info *di) -{ - /* - * power_supply_unregister call bq27xxx_battery_get_property which - * call bq27xxx_battery_poll. - * Make sure that bq27xxx_battery_poll will not call - * schedule_delayed_work again after unregister (which cause OOPS). - */ - poll_interval = 0; - - cancel_delayed_work_sync(&di->work); - - power_supply_unregister(di->bat); - - mutex_destroy(&di->lock); -} -EXPORT_SYMBOL_GPL(bq27xxx_battery_teardown); - -static int bq27xxx_battery_platform_read(struct bq27xxx_device_info *di, u8 reg, - bool single) -{ - struct device *dev = di->dev; - struct bq27xxx_platform_data *pdata = dev->platform_data; - unsigned int timeout = 3; - int upper, lower; - int temp; - - if (!single) { - /* Make sure the value has not changed in between reading the - * lower and the upper part */ - upper = pdata->read(dev, reg + 1); - do { - temp = upper; - if (upper < 0) - return upper; - - lower = pdata->read(dev, reg); - if (lower < 0) - return lower; - - upper = pdata->read(dev, reg + 1); - } while (temp != upper && --timeout); - - if (timeout == 0) - return -EIO; - - return (upper << 8) | lower; - } - - return pdata->read(dev, reg); -} - -static int bq27xxx_battery_platform_probe(struct platform_device *pdev) -{ - struct bq27xxx_device_info *di; - struct bq27xxx_platform_data *pdata = pdev->dev.platform_data; - - if (!pdata) { - dev_err(&pdev->dev, "no platform_data supplied\n"); - return -EINVAL; - } - - if (!pdata->read) { - dev_err(&pdev->dev, "no hdq read callback supplied\n"); - return -EINVAL; - } - - if (!pdata->chip) { - dev_err(&pdev->dev, "no device supplied\n"); - return -EINVAL; - } - - di = devm_kzalloc(&pdev->dev, sizeof(*di), GFP_KERNEL); - if (!di) - return -ENOMEM; - - platform_set_drvdata(pdev, di); - - di->dev = &pdev->dev; - di->chip = pdata->chip; - di->name = pdata->name ?: dev_name(&pdev->dev); - di->bus.read = bq27xxx_battery_platform_read; - - return bq27xxx_battery_setup(di); -} - -static int bq27xxx_battery_platform_remove(struct platform_device *pdev) -{ - struct bq27xxx_device_info *di = platform_get_drvdata(pdev); - - bq27xxx_battery_teardown(di); - - return 0; -} - -static const struct platform_device_id bq27xxx_battery_platform_id_table[] = { - { "bq27000-battery", }, - { /* sentinel */ } -}; -MODULE_DEVICE_TABLE(platform, bq27xxx_battery_platform_id_table); - -#ifdef CONFIG_OF -static const struct of_device_id bq27xxx_battery_platform_of_match_table[] = { - { .compatible = "ti,bq27000" }, - {}, -}; -MODULE_DEVICE_TABLE(of, bq27xxx_battery_platform_of_match_table); -#endif - -static struct platform_driver bq27xxx_battery_platform_driver = { - .probe = bq27xxx_battery_platform_probe, - .remove = bq27xxx_battery_platform_remove, - .driver = { - .name = "bq27000-battery", - .of_match_table = of_match_ptr(bq27xxx_battery_platform_of_match_table), - }, - .id_table = bq27xxx_battery_platform_id_table, -}; -module_platform_driver(bq27xxx_battery_platform_driver); - -MODULE_ALIAS("platform:bq27000-battery"); - -MODULE_AUTHOR("Rodolfo Giometti "); -MODULE_DESCRIPTION("BQ27xxx battery monitor driver"); -MODULE_LICENSE("GPL"); diff --git a/drivers/power/bq27xxx_battery_i2c.c b/drivers/power/bq27xxx_battery_i2c.c deleted file mode 100644 index 85d4ea2a9c20..000000000000 --- a/drivers/power/bq27xxx_battery_i2c.c +++ /dev/null @@ -1,205 +0,0 @@ -/* - * BQ27xxx battery monitor I2C driver - * - * Copyright (C) 2015 Texas Instruments Incorporated - http://www.ti.com/ - * Andrew F. Davis - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - * - * This program is distributed "as is" WITHOUT ANY WARRANTY of any - * kind, whether express or implied; without even the implied warranty - * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - */ - -#include -#include -#include -#include - -#include - -static DEFINE_IDR(battery_id); -static DEFINE_MUTEX(battery_mutex); - -static irqreturn_t bq27xxx_battery_irq_handler_thread(int irq, void *data) -{ - struct bq27xxx_device_info *di = data; - - bq27xxx_battery_update(di); - - return IRQ_HANDLED; -} - -static int bq27xxx_battery_i2c_read(struct bq27xxx_device_info *di, u8 reg, - bool single) -{ - struct i2c_client *client = to_i2c_client(di->dev); - struct i2c_msg msg[2]; - unsigned char data[2]; - int ret; - - if (!client->adapter) - return -ENODEV; - - msg[0].addr = client->addr; - msg[0].flags = 0; - msg[0].buf = ® - msg[0].len = sizeof(reg); - msg[1].addr = client->addr; - msg[1].flags = I2C_M_RD; - msg[1].buf = data; - if (single) - msg[1].len = 1; - else - msg[1].len = 2; - - ret = i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg)); - if (ret < 0) - return ret; - - if (!single) - ret = get_unaligned_le16(data); - else - ret = data[0]; - - return ret; -} - -static int bq27xxx_battery_i2c_probe(struct i2c_client *client, - const struct i2c_device_id *id) -{ - struct bq27xxx_device_info *di; - int ret; - char *name; - int num; - - /* Get new ID for the new battery device */ - mutex_lock(&battery_mutex); - num = idr_alloc(&battery_id, client, 0, 0, GFP_KERNEL); - mutex_unlock(&battery_mutex); - if (num < 0) - return num; - - name = devm_kasprintf(&client->dev, GFP_KERNEL, "%s-%d", id->name, num); - if (!name) - goto err_mem; - - di = devm_kzalloc(&client->dev, sizeof(*di), GFP_KERNEL); - if (!di) - goto err_mem; - - di->id = num; - di->dev = &client->dev; - di->chip = id->driver_data; - di->name = name; - di->bus.read = bq27xxx_battery_i2c_read; - - ret = bq27xxx_battery_setup(di); - if (ret) - goto err_failed; - - /* Schedule a polling after about 1 min */ - schedule_delayed_work(&di->work, 60 * HZ); - - i2c_set_clientdata(client, di); - - if (client->irq) { - ret = devm_request_threaded_irq(&client->dev, client->irq, - NULL, bq27xxx_battery_irq_handler_thread, - IRQF_ONESHOT, - di->name, di); - if (ret) { - dev_err(&client->dev, - "Unable to register IRQ %d error %d\n", - client->irq, ret); - return ret; - } - } - - return 0; - -err_mem: - ret = -ENOMEM; - -err_failed: - mutex_lock(&battery_mutex); - idr_remove(&battery_id, num); - mutex_unlock(&battery_mutex); - - return ret; -} - -static int bq27xxx_battery_i2c_remove(struct i2c_client *client) -{ - struct bq27xxx_device_info *di = i2c_get_clientdata(client); - - bq27xxx_battery_teardown(di); - - mutex_lock(&battery_mutex); - idr_remove(&battery_id, di->id); - mutex_unlock(&battery_mutex); - - return 0; -} - -static const struct i2c_device_id bq27xxx_i2c_id_table[] = { - { "bq27200", BQ27000 }, - { "bq27210", BQ27010 }, - { "bq27500", BQ27500 }, - { "bq27510", BQ27500 }, - { "bq27520", BQ27500 }, - { "bq27530", BQ27530 }, - { "bq27531", BQ27530 }, - { "bq27541", BQ27541 }, - { "bq27542", BQ27541 }, - { "bq27546", BQ27541 }, - { "bq27742", BQ27541 }, - { "bq27545", BQ27545 }, - { "bq27421", BQ27421 }, - { "bq27425", BQ27421 }, - { "bq27441", BQ27421 }, - { "bq27621", BQ27421 }, - {}, -}; -MODULE_DEVICE_TABLE(i2c, bq27xxx_i2c_id_table); - -#ifdef CONFIG_OF -static const struct of_device_id bq27xxx_battery_i2c_of_match_table[] = { - { .compatible = "ti,bq27200" }, - { .compatible = "ti,bq27210" }, - { .compatible = "ti,bq27500" }, - { .compatible = "ti,bq27510" }, - { .compatible = "ti,bq27520" }, - { .compatible = "ti,bq27530" }, - { .compatible = "ti,bq27531" }, - { .compatible = "ti,bq27541" }, - { .compatible = "ti,bq27542" }, - { .compatible = "ti,bq27546" }, - { .compatible = "ti,bq27742" }, - { .compatible = "ti,bq27545" }, - { .compatible = "ti,bq27421" }, - { .compatible = "ti,bq27425" }, - { .compatible = "ti,bq27441" }, - { .compatible = "ti,bq27621" }, - {}, -}; -MODULE_DEVICE_TABLE(of, bq27xxx_battery_i2c_of_match_table); -#endif - -static struct i2c_driver bq27xxx_battery_i2c_driver = { - .driver = { - .name = "bq27xxx-battery", - .of_match_table = of_match_ptr(bq27xxx_battery_i2c_of_match_table), - }, - .probe = bq27xxx_battery_i2c_probe, - .remove = bq27xxx_battery_i2c_remove, - .id_table = bq27xxx_i2c_id_table, -}; -module_i2c_driver(bq27xxx_battery_i2c_driver); - -MODULE_AUTHOR("Andrew F. Davis "); -MODULE_DESCRIPTION("BQ27xxx battery monitor i2c driver"); -MODULE_LICENSE("GPL"); diff --git a/drivers/power/charger-manager.c b/drivers/power/charger-manager.c deleted file mode 100644 index e664ca7c0afd..000000000000 --- a/drivers/power/charger-manager.c +++ /dev/null @@ -1,2074 +0,0 @@ -/* - * Copyright (C) 2011 Samsung Electronics Co., Ltd. - * MyungJoo Ham - * - * This driver enables to monitor battery health and control charger - * during suspend-to-mem. - * Charger manager depends on other devices. register this later than - * the depending devices. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. -**/ - -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/* - * Default termperature threshold for charging. - * Every temperature units are in tenth of centigrade. - */ -#define CM_DEFAULT_RECHARGE_TEMP_DIFF 50 -#define CM_DEFAULT_CHARGE_TEMP_MAX 500 - -static const char * const default_event_names[] = { - [CM_EVENT_UNKNOWN] = "Unknown", - [CM_EVENT_BATT_FULL] = "Battery Full", - [CM_EVENT_BATT_IN] = "Battery Inserted", - [CM_EVENT_BATT_OUT] = "Battery Pulled Out", - [CM_EVENT_BATT_OVERHEAT] = "Battery Overheat", - [CM_EVENT_BATT_COLD] = "Battery Cold", - [CM_EVENT_EXT_PWR_IN_OUT] = "External Power Attach/Detach", - [CM_EVENT_CHG_START_STOP] = "Charging Start/Stop", - [CM_EVENT_OTHERS] = "Other battery events" -}; - -/* - * Regard CM_JIFFIES_SMALL jiffies is small enough to ignore for - * delayed works so that we can run delayed works with CM_JIFFIES_SMALL - * without any delays. - */ -#define CM_JIFFIES_SMALL (2) - -/* If y is valid (> 0) and smaller than x, do x = y */ -#define CM_MIN_VALID(x, y) x = (((y > 0) && ((x) > (y))) ? (y) : (x)) - -/* - * Regard CM_RTC_SMALL (sec) is small enough to ignore error in invoking - * rtc alarm. It should be 2 or larger - */ -#define CM_RTC_SMALL (2) - -#define UEVENT_BUF_SIZE 32 - -static LIST_HEAD(cm_list); -static DEFINE_MUTEX(cm_list_mtx); - -/* About in-suspend (suspend-again) monitoring */ -static struct alarm *cm_timer; - -static bool cm_suspended; -static bool cm_timer_set; -static unsigned long cm_suspend_duration_ms; - -/* About normal (not suspended) monitoring */ -static unsigned long polling_jiffy = ULONG_MAX; /* ULONG_MAX: no polling */ -static unsigned long next_polling; /* Next appointed polling time */ -static struct workqueue_struct *cm_wq; /* init at driver add */ -static struct delayed_work cm_monitor_work; /* init at driver add */ - -/** - * is_batt_present - See if the battery presents in place. - * @cm: the Charger Manager representing the battery. - */ -static bool is_batt_present(struct charger_manager *cm) -{ - union power_supply_propval val; - struct power_supply *psy; - bool present = false; - int i, ret; - - switch (cm->desc->battery_present) { - case CM_BATTERY_PRESENT: - present = true; - break; - case CM_NO_BATTERY: - break; - case CM_FUEL_GAUGE: - psy = power_supply_get_by_name(cm->desc->psy_fuel_gauge); - if (!psy) - break; - - ret = power_supply_get_property(psy, POWER_SUPPLY_PROP_PRESENT, - &val); - if (ret == 0 && val.intval) - present = true; - power_supply_put(psy); - break; - case CM_CHARGER_STAT: - for (i = 0; cm->desc->psy_charger_stat[i]; i++) { - psy = power_supply_get_by_name( - cm->desc->psy_charger_stat[i]); - if (!psy) { - dev_err(cm->dev, "Cannot find power supply \"%s\"\n", - cm->desc->psy_charger_stat[i]); - continue; - } - - ret = power_supply_get_property(psy, - POWER_SUPPLY_PROP_PRESENT, &val); - power_supply_put(psy); - if (ret == 0 && val.intval) { - present = true; - break; - } - } - break; - } - - return present; -} - -/** - * is_ext_pwr_online - See if an external power source is attached to charge - * @cm: the Charger Manager representing the battery. - * - * Returns true if at least one of the chargers of the battery has an external - * power source attached to charge the battery regardless of whether it is - * actually charging or not. - */ -static bool is_ext_pwr_online(struct charger_manager *cm) -{ - union power_supply_propval val; - struct power_supply *psy; - bool online = false; - int i, ret; - - /* If at least one of them has one, it's yes. */ - for (i = 0; cm->desc->psy_charger_stat[i]; i++) { - psy = power_supply_get_by_name(cm->desc->psy_charger_stat[i]); - if (!psy) { - dev_err(cm->dev, "Cannot find power supply \"%s\"\n", - cm->desc->psy_charger_stat[i]); - continue; - } - - ret = power_supply_get_property(psy, POWER_SUPPLY_PROP_ONLINE, - &val); - power_supply_put(psy); - if (ret == 0 && val.intval) { - online = true; - break; - } - } - - return online; -} - -/** - * get_batt_uV - Get the voltage level of the battery - * @cm: the Charger Manager representing the battery. - * @uV: the voltage level returned. - * - * Returns 0 if there is no error. - * Returns a negative value on error. - */ -static int get_batt_uV(struct charger_manager *cm, int *uV) -{ - union power_supply_propval val; - struct power_supply *fuel_gauge; - int ret; - - fuel_gauge = power_supply_get_by_name(cm->desc->psy_fuel_gauge); - if (!fuel_gauge) - return -ENODEV; - - ret = power_supply_get_property(fuel_gauge, - POWER_SUPPLY_PROP_VOLTAGE_NOW, &val); - power_supply_put(fuel_gauge); - if (ret) - return ret; - - *uV = val.intval; - return 0; -} - -/** - * is_charging - Returns true if the battery is being charged. - * @cm: the Charger Manager representing the battery. - */ -static bool is_charging(struct charger_manager *cm) -{ - int i, ret; - bool charging = false; - struct power_supply *psy; - union power_supply_propval val; - - /* If there is no battery, it cannot be charged */ - if (!is_batt_present(cm)) - return false; - - /* If at least one of the charger is charging, return yes */ - for (i = 0; cm->desc->psy_charger_stat[i]; i++) { - /* 1. The charger sholuld not be DISABLED */ - if (cm->emergency_stop) - continue; - if (!cm->charger_enabled) - continue; - - psy = power_supply_get_by_name(cm->desc->psy_charger_stat[i]); - if (!psy) { - dev_err(cm->dev, "Cannot find power supply \"%s\"\n", - cm->desc->psy_charger_stat[i]); - continue; - } - - /* 2. The charger should be online (ext-power) */ - ret = power_supply_get_property(psy, POWER_SUPPLY_PROP_ONLINE, - &val); - if (ret) { - dev_warn(cm->dev, "Cannot read ONLINE value from %s\n", - cm->desc->psy_charger_stat[i]); - power_supply_put(psy); - continue; - } - if (val.intval == 0) { - power_supply_put(psy); - continue; - } - - /* - * 3. The charger should not be FULL, DISCHARGING, - * or NOT_CHARGING. - */ - ret = power_supply_get_property(psy, POWER_SUPPLY_PROP_STATUS, - &val); - power_supply_put(psy); - if (ret) { - dev_warn(cm->dev, "Cannot read STATUS value from %s\n", - cm->desc->psy_charger_stat[i]); - continue; - } - if (val.intval == POWER_SUPPLY_STATUS_FULL || - val.intval == POWER_SUPPLY_STATUS_DISCHARGING || - val.intval == POWER_SUPPLY_STATUS_NOT_CHARGING) - continue; - - /* Then, this is charging. */ - charging = true; - break; - } - - return charging; -} - -/** - * is_full_charged - Returns true if the battery is fully charged. - * @cm: the Charger Manager representing the battery. - */ -static bool is_full_charged(struct charger_manager *cm) -{ - struct charger_desc *desc = cm->desc; - union power_supply_propval val; - struct power_supply *fuel_gauge; - bool is_full = false; - int ret = 0; - int uV; - - /* If there is no battery, it cannot be charged */ - if (!is_batt_present(cm)) - return false; - - fuel_gauge = power_supply_get_by_name(cm->desc->psy_fuel_gauge); - if (!fuel_gauge) - return false; - - if (desc->fullbatt_full_capacity > 0) { - val.intval = 0; - - /* Not full if capacity of fuel gauge isn't full */ - ret = power_supply_get_property(fuel_gauge, - POWER_SUPPLY_PROP_CHARGE_FULL, &val); - if (!ret && val.intval > desc->fullbatt_full_capacity) { - is_full = true; - goto out; - } - } - - /* Full, if it's over the fullbatt voltage */ - if (desc->fullbatt_uV > 0) { - ret = get_batt_uV(cm, &uV); - if (!ret && uV >= desc->fullbatt_uV) { - is_full = true; - goto out; - } - } - - /* Full, if the capacity is more than fullbatt_soc */ - if (desc->fullbatt_soc > 0) { - val.intval = 0; - - ret = power_supply_get_property(fuel_gauge, - POWER_SUPPLY_PROP_CAPACITY, &val); - if (!ret && val.intval >= desc->fullbatt_soc) { - is_full = true; - goto out; - } - } - -out: - power_supply_put(fuel_gauge); - return is_full; -} - -/** - * is_polling_required - Return true if need to continue polling for this CM. - * @cm: the Charger Manager representing the battery. - */ -static bool is_polling_required(struct charger_manager *cm) -{ - switch (cm->desc->polling_mode) { - case CM_POLL_DISABLE: - return false; - case CM_POLL_ALWAYS: - return true; - case CM_POLL_EXTERNAL_POWER_ONLY: - return is_ext_pwr_online(cm); - case CM_POLL_CHARGING_ONLY: - return is_charging(cm); - default: - dev_warn(cm->dev, "Incorrect polling_mode (%d)\n", - cm->desc->polling_mode); - } - - return false; -} - -/** - * try_charger_enable - Enable/Disable chargers altogether - * @cm: the Charger Manager representing the battery. - * @enable: true: enable / false: disable - * - * Note that Charger Manager keeps the charger enabled regardless whether - * the charger is charging or not (because battery is full or no external - * power source exists) except when CM needs to disable chargers forcibly - * bacause of emergency causes; when the battery is overheated or too cold. - */ -static int try_charger_enable(struct charger_manager *cm, bool enable) -{ - int err = 0, i; - struct charger_desc *desc = cm->desc; - - /* Ignore if it's redundent command */ - if (enable == cm->charger_enabled) - return 0; - - if (enable) { - if (cm->emergency_stop) - return -EAGAIN; - - /* - * Save start time of charging to limit - * maximum possible charging time. - */ - cm->charging_start_time = ktime_to_ms(ktime_get()); - cm->charging_end_time = 0; - - for (i = 0 ; i < desc->num_charger_regulators ; i++) { - if (desc->charger_regulators[i].externally_control) - continue; - - err = regulator_enable(desc->charger_regulators[i].consumer); - if (err < 0) { - dev_warn(cm->dev, "Cannot enable %s regulator\n", - desc->charger_regulators[i].regulator_name); - } - } - } else { - /* - * Save end time of charging to maintain fully charged state - * of battery after full-batt. - */ - cm->charging_start_time = 0; - cm->charging_end_time = ktime_to_ms(ktime_get()); - - for (i = 0 ; i < desc->num_charger_regulators ; i++) { - if (desc->charger_regulators[i].externally_control) - continue; - - err = regulator_disable(desc->charger_regulators[i].consumer); - if (err < 0) { - dev_warn(cm->dev, "Cannot disable %s regulator\n", - desc->charger_regulators[i].regulator_name); - } - } - - /* - * Abnormal battery state - Stop charging forcibly, - * even if charger was enabled at the other places - */ - for (i = 0; i < desc->num_charger_regulators; i++) { - if (regulator_is_enabled( - desc->charger_regulators[i].consumer)) { - regulator_force_disable( - desc->charger_regulators[i].consumer); - dev_warn(cm->dev, "Disable regulator(%s) forcibly\n", - desc->charger_regulators[i].regulator_name); - } - } - } - - if (!err) - cm->charger_enabled = enable; - - return err; -} - -/** - * try_charger_restart - Restart charging. - * @cm: the Charger Manager representing the battery. - * - * Restart charging by turning off and on the charger. - */ -static int try_charger_restart(struct charger_manager *cm) -{ - int err; - - if (cm->emergency_stop) - return -EAGAIN; - - err = try_charger_enable(cm, false); - if (err) - return err; - - return try_charger_enable(cm, true); -} - -/** - * uevent_notify - Let users know something has changed. - * @cm: the Charger Manager representing the battery. - * @event: the event string. - * - * If @event is null, it implies that uevent_notify is called - * by resume function. When called in the resume function, cm_suspended - * should be already reset to false in order to let uevent_notify - * notify the recent event during the suspend to users. While - * suspended, uevent_notify does not notify users, but tracks - * events so that uevent_notify can notify users later after resumed. - */ -static void uevent_notify(struct charger_manager *cm, const char *event) -{ - static char env_str[UEVENT_BUF_SIZE + 1] = ""; - static char env_str_save[UEVENT_BUF_SIZE + 1] = ""; - - if (cm_suspended) { - /* Nothing in suspended-event buffer */ - if (env_str_save[0] == 0) { - if (!strncmp(env_str, event, UEVENT_BUF_SIZE)) - return; /* status not changed */ - strncpy(env_str_save, event, UEVENT_BUF_SIZE); - return; - } - - if (!strncmp(env_str_save, event, UEVENT_BUF_SIZE)) - return; /* Duplicated. */ - strncpy(env_str_save, event, UEVENT_BUF_SIZE); - return; - } - - if (event == NULL) { - /* No messages pending */ - if (!env_str_save[0]) - return; - - strncpy(env_str, env_str_save, UEVENT_BUF_SIZE); - kobject_uevent(&cm->dev->kobj, KOBJ_CHANGE); - env_str_save[0] = 0; - - return; - } - - /* status not changed */ - if (!strncmp(env_str, event, UEVENT_BUF_SIZE)) - return; - - /* save the status and notify the update */ - strncpy(env_str, event, UEVENT_BUF_SIZE); - kobject_uevent(&cm->dev->kobj, KOBJ_CHANGE); - - dev_info(cm->dev, "%s\n", event); -} - -/** - * fullbatt_vchk - Check voltage drop some times after "FULL" event. - * @work: the work_struct appointing the function - * - * If a user has designated "fullbatt_vchkdrop_ms/uV" values with - * charger_desc, Charger Manager checks voltage drop after the battery - * "FULL" event. It checks whether the voltage has dropped more than - * fullbatt_vchkdrop_uV by calling this function after fullbatt_vchkrop_ms. - */ -static void fullbatt_vchk(struct work_struct *work) -{ - struct delayed_work *dwork = to_delayed_work(work); - struct charger_manager *cm = container_of(dwork, - struct charger_manager, fullbatt_vchk_work); - struct charger_desc *desc = cm->desc; - int batt_uV, err, diff; - - /* remove the appointment for fullbatt_vchk */ - cm->fullbatt_vchk_jiffies_at = 0; - - if (!desc->fullbatt_vchkdrop_uV || !desc->fullbatt_vchkdrop_ms) - return; - - err = get_batt_uV(cm, &batt_uV); - if (err) { - dev_err(cm->dev, "%s: get_batt_uV error(%d)\n", __func__, err); - return; - } - - diff = desc->fullbatt_uV - batt_uV; - if (diff < 0) - return; - - dev_info(cm->dev, "VBATT dropped %duV after full-batt\n", diff); - - if (diff > desc->fullbatt_vchkdrop_uV) { - try_charger_restart(cm); - uevent_notify(cm, "Recharging"); - } -} - -/** - * check_charging_duration - Monitor charging/discharging duration - * @cm: the Charger Manager representing the battery. - * - * If whole charging duration exceed 'charging_max_duration_ms', - * cm stop charging to prevent overcharge/overheat. If discharging - * duration exceed 'discharging _max_duration_ms', charger cable is - * attached, after full-batt, cm start charging to maintain fully - * charged state for battery. - */ -static int check_charging_duration(struct charger_manager *cm) -{ - struct charger_desc *desc = cm->desc; - u64 curr = ktime_to_ms(ktime_get()); - u64 duration; - int ret = false; - - if (!desc->charging_max_duration_ms && - !desc->discharging_max_duration_ms) - return ret; - - if (cm->charger_enabled) { - duration = curr - cm->charging_start_time; - - if (duration > desc->charging_max_duration_ms) { - dev_info(cm->dev, "Charging duration exceed %ums\n", - desc->charging_max_duration_ms); - uevent_notify(cm, "Discharging"); - try_charger_enable(cm, false); - ret = true; - } - } else if (is_ext_pwr_online(cm) && !cm->charger_enabled) { - duration = curr - cm->charging_end_time; - - if (duration > desc->charging_max_duration_ms && - is_ext_pwr_online(cm)) { - dev_info(cm->dev, "Discharging duration exceed %ums\n", - desc->discharging_max_duration_ms); - uevent_notify(cm, "Recharging"); - try_charger_enable(cm, true); - ret = true; - } - } - - return ret; -} - -static int cm_get_battery_temperature_by_psy(struct charger_manager *cm, - int *temp) -{ - struct power_supply *fuel_gauge; - int ret; - - fuel_gauge = power_supply_get_by_name(cm->desc->psy_fuel_gauge); - if (!fuel_gauge) - return -ENODEV; - - ret = power_supply_get_property(fuel_gauge, - POWER_SUPPLY_PROP_TEMP, - (union power_supply_propval *)temp); - power_supply_put(fuel_gauge); - - return ret; -} - -static int cm_get_battery_temperature(struct charger_manager *cm, - int *temp) -{ - int ret; - - if (!cm->desc->measure_battery_temp) - return -ENODEV; - -#ifdef CONFIG_THERMAL - if (cm->tzd_batt) { - ret = thermal_zone_get_temp(cm->tzd_batt, temp); - if (!ret) - /* Calibrate temperature unit */ - *temp /= 100; - } else -#endif - { - /* if-else continued from CONFIG_THERMAL */ - ret = cm_get_battery_temperature_by_psy(cm, temp); - } - - return ret; -} - -static int cm_check_thermal_status(struct charger_manager *cm) -{ - struct charger_desc *desc = cm->desc; - int temp, upper_limit, lower_limit; - int ret = 0; - - ret = cm_get_battery_temperature(cm, &temp); - if (ret) { - /* FIXME: - * No information of battery temperature might - * occur hazadous result. We have to handle it - * depending on battery type. - */ - dev_err(cm->dev, "Failed to get battery temperature\n"); - return 0; - } - - upper_limit = desc->temp_max; - lower_limit = desc->temp_min; - - if (cm->emergency_stop) { - upper_limit -= desc->temp_diff; - lower_limit += desc->temp_diff; - } - - if (temp > upper_limit) - ret = CM_EVENT_BATT_OVERHEAT; - else if (temp < lower_limit) - ret = CM_EVENT_BATT_COLD; - - return ret; -} - -/** - * _cm_monitor - Monitor the temperature and return true for exceptions. - * @cm: the Charger Manager representing the battery. - * - * Returns true if there is an event to notify for the battery. - * (True if the status of "emergency_stop" changes) - */ -static bool _cm_monitor(struct charger_manager *cm) -{ - int temp_alrt; - - temp_alrt = cm_check_thermal_status(cm); - - /* It has been stopped already */ - if (temp_alrt && cm->emergency_stop) - return false; - - /* - * Check temperature whether overheat or cold. - * If temperature is out of range normal state, stop charging. - */ - if (temp_alrt) { - cm->emergency_stop = temp_alrt; - if (!try_charger_enable(cm, false)) - uevent_notify(cm, default_event_names[temp_alrt]); - - /* - * Check whole charging duration and discharing duration - * after full-batt. - */ - } else if (!cm->emergency_stop && check_charging_duration(cm)) { - dev_dbg(cm->dev, - "Charging/Discharging duration is out of range\n"); - /* - * Check dropped voltage of battery. If battery voltage is more - * dropped than fullbatt_vchkdrop_uV after fully charged state, - * charger-manager have to recharge battery. - */ - } else if (!cm->emergency_stop && is_ext_pwr_online(cm) && - !cm->charger_enabled) { - fullbatt_vchk(&cm->fullbatt_vchk_work.work); - - /* - * Check whether fully charged state to protect overcharge - * if charger-manager is charging for battery. - */ - } else if (!cm->emergency_stop && is_full_charged(cm) && - cm->charger_enabled) { - dev_info(cm->dev, "EVENT_HANDLE: Battery Fully Charged\n"); - uevent_notify(cm, default_event_names[CM_EVENT_BATT_FULL]); - - try_charger_enable(cm, false); - - fullbatt_vchk(&cm->fullbatt_vchk_work.work); - } else { - cm->emergency_stop = 0; - if (is_ext_pwr_online(cm)) { - if (!try_charger_enable(cm, true)) - uevent_notify(cm, "CHARGING"); - } - } - - return true; -} - -/** - * cm_monitor - Monitor every battery. - * - * Returns true if there is an event to notify from any of the batteries. - * (True if the status of "emergency_stop" changes) - */ -static bool cm_monitor(void) -{ - bool stop = false; - struct charger_manager *cm; - - mutex_lock(&cm_list_mtx); - - list_for_each_entry(cm, &cm_list, entry) { - if (_cm_monitor(cm)) - stop = true; - } - - mutex_unlock(&cm_list_mtx); - - return stop; -} - -/** - * _setup_polling - Setup the next instance of polling. - * @work: work_struct of the function _setup_polling. - */ -static void _setup_polling(struct work_struct *work) -{ - unsigned long min = ULONG_MAX; - struct charger_manager *cm; - bool keep_polling = false; - unsigned long _next_polling; - - mutex_lock(&cm_list_mtx); - - list_for_each_entry(cm, &cm_list, entry) { - if (is_polling_required(cm) && cm->desc->polling_interval_ms) { - keep_polling = true; - - if (min > cm->desc->polling_interval_ms) - min = cm->desc->polling_interval_ms; - } - } - - polling_jiffy = msecs_to_jiffies(min); - if (polling_jiffy <= CM_JIFFIES_SMALL) - polling_jiffy = CM_JIFFIES_SMALL + 1; - - if (!keep_polling) - polling_jiffy = ULONG_MAX; - if (polling_jiffy == ULONG_MAX) - goto out; - - WARN(cm_wq == NULL, "charger-manager: workqueue not initialized" - ". try it later. %s\n", __func__); - - /* - * Use mod_delayed_work() iff the next polling interval should - * occur before the currently scheduled one. If @cm_monitor_work - * isn't active, the end result is the same, so no need to worry - * about stale @next_polling. - */ - _next_polling = jiffies + polling_jiffy; - - if (time_before(_next_polling, next_polling)) { - mod_delayed_work(cm_wq, &cm_monitor_work, polling_jiffy); - next_polling = _next_polling; - } else { - if (queue_delayed_work(cm_wq, &cm_monitor_work, polling_jiffy)) - next_polling = _next_polling; - } -out: - mutex_unlock(&cm_list_mtx); -} -static DECLARE_WORK(setup_polling, _setup_polling); - -/** - * cm_monitor_poller - The Monitor / Poller. - * @work: work_struct of the function cm_monitor_poller - * - * During non-suspended state, cm_monitor_poller is used to poll and monitor - * the batteries. - */ -static void cm_monitor_poller(struct work_struct *work) -{ - cm_monitor(); - schedule_work(&setup_polling); -} - -/** - * fullbatt_handler - Event handler for CM_EVENT_BATT_FULL - * @cm: the Charger Manager representing the battery. - */ -static void fullbatt_handler(struct charger_manager *cm) -{ - struct charger_desc *desc = cm->desc; - - if (!desc->fullbatt_vchkdrop_uV || !desc->fullbatt_vchkdrop_ms) - goto out; - - if (cm_suspended) - device_set_wakeup_capable(cm->dev, true); - - mod_delayed_work(cm_wq, &cm->fullbatt_vchk_work, - msecs_to_jiffies(desc->fullbatt_vchkdrop_ms)); - cm->fullbatt_vchk_jiffies_at = jiffies + msecs_to_jiffies( - desc->fullbatt_vchkdrop_ms); - - if (cm->fullbatt_vchk_jiffies_at == 0) - cm->fullbatt_vchk_jiffies_at = 1; - -out: - dev_info(cm->dev, "EVENT_HANDLE: Battery Fully Charged\n"); - uevent_notify(cm, default_event_names[CM_EVENT_BATT_FULL]); -} - -/** - * battout_handler - Event handler for CM_EVENT_BATT_OUT - * @cm: the Charger Manager representing the battery. - */ -static void battout_handler(struct charger_manager *cm) -{ - if (cm_suspended) - device_set_wakeup_capable(cm->dev, true); - - if (!is_batt_present(cm)) { - dev_emerg(cm->dev, "Battery Pulled Out!\n"); - uevent_notify(cm, default_event_names[CM_EVENT_BATT_OUT]); - } else { - uevent_notify(cm, "Battery Reinserted?"); - } -} - -/** - * misc_event_handler - Handler for other evnets - * @cm: the Charger Manager representing the battery. - * @type: the Charger Manager representing the battery. - */ -static void misc_event_handler(struct charger_manager *cm, - enum cm_event_types type) -{ - if (cm_suspended) - device_set_wakeup_capable(cm->dev, true); - - if (is_polling_required(cm) && cm->desc->polling_interval_ms) - schedule_work(&setup_polling); - uevent_notify(cm, default_event_names[type]); -} - -static int charger_get_property(struct power_supply *psy, - enum power_supply_property psp, - union power_supply_propval *val) -{ - struct charger_manager *cm = power_supply_get_drvdata(psy); - struct charger_desc *desc = cm->desc; - struct power_supply *fuel_gauge = NULL; - int ret = 0; - int uV; - - switch (psp) { - case POWER_SUPPLY_PROP_STATUS: - if (is_charging(cm)) - val->intval = POWER_SUPPLY_STATUS_CHARGING; - else if (is_ext_pwr_online(cm)) - val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; - else - val->intval = POWER_SUPPLY_STATUS_DISCHARGING; - break; - case POWER_SUPPLY_PROP_HEALTH: - if (cm->emergency_stop > 0) - val->intval = POWER_SUPPLY_HEALTH_OVERHEAT; - else if (cm->emergency_stop < 0) - val->intval = POWER_SUPPLY_HEALTH_COLD; - else - val->intval = POWER_SUPPLY_HEALTH_GOOD; - break; - case POWER_SUPPLY_PROP_PRESENT: - if (is_batt_present(cm)) - val->intval = 1; - else - val->intval = 0; - break; - case POWER_SUPPLY_PROP_VOLTAGE_NOW: - ret = get_batt_uV(cm, &val->intval); - break; - case POWER_SUPPLY_PROP_CURRENT_NOW: - fuel_gauge = power_supply_get_by_name(cm->desc->psy_fuel_gauge); - if (!fuel_gauge) { - ret = -ENODEV; - break; - } - ret = power_supply_get_property(fuel_gauge, - POWER_SUPPLY_PROP_CURRENT_NOW, val); - break; - case POWER_SUPPLY_PROP_TEMP: - case POWER_SUPPLY_PROP_TEMP_AMBIENT: - return cm_get_battery_temperature(cm, &val->intval); - case POWER_SUPPLY_PROP_CAPACITY: - if (!is_batt_present(cm)) { - /* There is no battery. Assume 100% */ - val->intval = 100; - break; - } - - fuel_gauge = power_supply_get_by_name(cm->desc->psy_fuel_gauge); - if (!fuel_gauge) { - ret = -ENODEV; - break; - } - - ret = power_supply_get_property(fuel_gauge, - POWER_SUPPLY_PROP_CAPACITY, val); - if (ret) - break; - - if (val->intval > 100) { - val->intval = 100; - break; - } - if (val->intval < 0) - val->intval = 0; - - /* Do not adjust SOC when charging: voltage is overrated */ - if (is_charging(cm)) - break; - - /* - * If the capacity value is inconsistent, calibrate it base on - * the battery voltage values and the thresholds given as desc - */ - ret = get_batt_uV(cm, &uV); - if (ret) { - /* Voltage information not available. No calibration */ - ret = 0; - break; - } - - if (desc->fullbatt_uV > 0 && uV >= desc->fullbatt_uV && - !is_charging(cm)) { - val->intval = 100; - break; - } - - break; - case POWER_SUPPLY_PROP_ONLINE: - if (is_ext_pwr_online(cm)) - val->intval = 1; - else - val->intval = 0; - break; - case POWER_SUPPLY_PROP_CHARGE_FULL: - if (is_full_charged(cm)) - val->intval = 1; - else - val->intval = 0; - ret = 0; - break; - case POWER_SUPPLY_PROP_CHARGE_NOW: - if (is_charging(cm)) { - fuel_gauge = power_supply_get_by_name( - cm->desc->psy_fuel_gauge); - if (!fuel_gauge) { - ret = -ENODEV; - break; - } - - ret = power_supply_get_property(fuel_gauge, - POWER_SUPPLY_PROP_CHARGE_NOW, - val); - if (ret) { - val->intval = 1; - ret = 0; - } else { - /* If CHARGE_NOW is supplied, use it */ - val->intval = (val->intval > 0) ? - val->intval : 1; - } - } else { - val->intval = 0; - } - break; - default: - return -EINVAL; - } - if (fuel_gauge) - power_supply_put(fuel_gauge); - return ret; -} - -#define NUM_CHARGER_PSY_OPTIONAL (4) -static enum power_supply_property default_charger_props[] = { - /* Guaranteed to provide */ - POWER_SUPPLY_PROP_STATUS, - POWER_SUPPLY_PROP_HEALTH, - POWER_SUPPLY_PROP_PRESENT, - POWER_SUPPLY_PROP_VOLTAGE_NOW, - POWER_SUPPLY_PROP_CAPACITY, - POWER_SUPPLY_PROP_ONLINE, - POWER_SUPPLY_PROP_CHARGE_FULL, - /* - * Optional properties are: - * POWER_SUPPLY_PROP_CHARGE_NOW, - * POWER_SUPPLY_PROP_CURRENT_NOW, - * POWER_SUPPLY_PROP_TEMP, and - * POWER_SUPPLY_PROP_TEMP_AMBIENT, - */ -}; - -static const struct power_supply_desc psy_default = { - .name = "battery", - .type = POWER_SUPPLY_TYPE_BATTERY, - .properties = default_charger_props, - .num_properties = ARRAY_SIZE(default_charger_props), - .get_property = charger_get_property, - .no_thermal = true, -}; - -/** - * cm_setup_timer - For in-suspend monitoring setup wakeup alarm - * for suspend_again. - * - * Returns true if the alarm is set for Charger Manager to use. - * Returns false if - * cm_setup_timer fails to set an alarm, - * cm_setup_timer does not need to set an alarm for Charger Manager, - * or an alarm previously configured is to be used. - */ -static bool cm_setup_timer(void) -{ - struct charger_manager *cm; - unsigned int wakeup_ms = UINT_MAX; - int timer_req = 0; - - if (time_after(next_polling, jiffies)) - CM_MIN_VALID(wakeup_ms, - jiffies_to_msecs(next_polling - jiffies)); - - mutex_lock(&cm_list_mtx); - list_for_each_entry(cm, &cm_list, entry) { - unsigned int fbchk_ms = 0; - - /* fullbatt_vchk is required. setup timer for that */ - if (cm->fullbatt_vchk_jiffies_at) { - fbchk_ms = jiffies_to_msecs(cm->fullbatt_vchk_jiffies_at - - jiffies); - if (time_is_before_eq_jiffies( - cm->fullbatt_vchk_jiffies_at) || - msecs_to_jiffies(fbchk_ms) < CM_JIFFIES_SMALL) { - fullbatt_vchk(&cm->fullbatt_vchk_work.work); - fbchk_ms = 0; - } - } - CM_MIN_VALID(wakeup_ms, fbchk_ms); - - /* Skip if polling is not required for this CM */ - if (!is_polling_required(cm) && !cm->emergency_stop) - continue; - timer_req++; - if (cm->desc->polling_interval_ms == 0) - continue; - CM_MIN_VALID(wakeup_ms, cm->desc->polling_interval_ms); - } - mutex_unlock(&cm_list_mtx); - - if (timer_req && cm_timer) { - ktime_t now, add; - - /* - * Set alarm with the polling interval (wakeup_ms) - * The alarm time should be NOW + CM_RTC_SMALL or later. - */ - if (wakeup_ms == UINT_MAX || - wakeup_ms < CM_RTC_SMALL * MSEC_PER_SEC) - wakeup_ms = 2 * CM_RTC_SMALL * MSEC_PER_SEC; - - pr_info("Charger Manager wakeup timer: %u ms\n", wakeup_ms); - - now = ktime_get_boottime(); - add = ktime_set(wakeup_ms / MSEC_PER_SEC, - (wakeup_ms % MSEC_PER_SEC) * NSEC_PER_MSEC); - alarm_start(cm_timer, ktime_add(now, add)); - - cm_suspend_duration_ms = wakeup_ms; - - return true; - } - return false; -} - -/** - * charger_extcon_work - enable/diable charger according to the state - * of charger cable - * - * @work: work_struct of the function charger_extcon_work. - */ -static void charger_extcon_work(struct work_struct *work) -{ - struct charger_cable *cable = - container_of(work, struct charger_cable, wq); - int ret; - - if (cable->attached && cable->min_uA != 0 && cable->max_uA != 0) { - ret = regulator_set_current_limit(cable->charger->consumer, - cable->min_uA, cable->max_uA); - if (ret < 0) { - pr_err("Cannot set current limit of %s (%s)\n", - cable->charger->regulator_name, cable->name); - return; - } - - pr_info("Set current limit of %s : %duA ~ %duA\n", - cable->charger->regulator_name, - cable->min_uA, cable->max_uA); - } - - try_charger_enable(cable->cm, cable->attached); -} - -/** - * charger_extcon_notifier - receive the state of charger cable - * when registered cable is attached or detached. - * - * @self: the notifier block of the charger_extcon_notifier. - * @event: the cable state. - * @ptr: the data pointer of notifier block. - */ -static int charger_extcon_notifier(struct notifier_block *self, - unsigned long event, void *ptr) -{ - struct charger_cable *cable = - container_of(self, struct charger_cable, nb); - - /* - * The newly state of charger cable. - * If cable is attached, cable->attached is true. - */ - cable->attached = event; - - /* - * Setup monitoring to check battery state - * when charger cable is attached. - */ - if (cable->attached && is_polling_required(cable->cm)) { - cancel_work_sync(&setup_polling); - schedule_work(&setup_polling); - } - - /* - * Setup work for controlling charger(regulator) - * according to charger cable. - */ - schedule_work(&cable->wq); - - return NOTIFY_DONE; -} - -/** - * charger_extcon_init - register external connector to use it - * as the charger cable - * - * @cm: the Charger Manager representing the battery. - * @cable: the Charger cable representing the external connector. - */ -static int charger_extcon_init(struct charger_manager *cm, - struct charger_cable *cable) -{ - int ret = 0; - - /* - * Charger manager use Extcon framework to identify - * the charger cable among various external connector - * cable (e.g., TA, USB, MHL, Dock). - */ - INIT_WORK(&cable->wq, charger_extcon_work); - cable->nb.notifier_call = charger_extcon_notifier; - ret = extcon_register_interest(&cable->extcon_dev, - cable->extcon_name, cable->name, &cable->nb); - if (ret < 0) { - pr_info("Cannot register extcon_dev for %s(cable: %s)\n", - cable->extcon_name, cable->name); - ret = -EINVAL; - } - - return ret; -} - -/** - * charger_manager_register_extcon - Register extcon device to recevie state - * of charger cable. - * @cm: the Charger Manager representing the battery. - * - * This function support EXTCON(External Connector) subsystem to detect the - * state of charger cables for enabling or disabling charger(regulator) and - * select the charger cable for charging among a number of external cable - * according to policy of H/W board. - */ -static int charger_manager_register_extcon(struct charger_manager *cm) -{ - struct charger_desc *desc = cm->desc; - struct charger_regulator *charger; - int ret = 0; - int i; - int j; - - for (i = 0; i < desc->num_charger_regulators; i++) { - charger = &desc->charger_regulators[i]; - - charger->consumer = regulator_get(cm->dev, - charger->regulator_name); - if (IS_ERR(charger->consumer)) { - dev_err(cm->dev, "Cannot find charger(%s)\n", - charger->regulator_name); - return PTR_ERR(charger->consumer); - } - charger->cm = cm; - - for (j = 0; j < charger->num_cables; j++) { - struct charger_cable *cable = &charger->cables[j]; - - ret = charger_extcon_init(cm, cable); - if (ret < 0) { - dev_err(cm->dev, "Cannot initialize charger(%s)\n", - charger->regulator_name); - goto err; - } - cable->charger = charger; - cable->cm = cm; - } - } - -err: - return ret; -} - -/* help function of sysfs node to control charger(regulator) */ -static ssize_t charger_name_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - struct charger_regulator *charger - = container_of(attr, struct charger_regulator, attr_name); - - return sprintf(buf, "%s\n", charger->regulator_name); -} - -static ssize_t charger_state_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - struct charger_regulator *charger - = container_of(attr, struct charger_regulator, attr_state); - int state = 0; - - if (!charger->externally_control) - state = regulator_is_enabled(charger->consumer); - - return sprintf(buf, "%s\n", state ? "enabled" : "disabled"); -} - -static ssize_t charger_externally_control_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - struct charger_regulator *charger = container_of(attr, - struct charger_regulator, attr_externally_control); - - return sprintf(buf, "%d\n", charger->externally_control); -} - -static ssize_t charger_externally_control_store(struct device *dev, - struct device_attribute *attr, const char *buf, - size_t count) -{ - struct charger_regulator *charger - = container_of(attr, struct charger_regulator, - attr_externally_control); - struct charger_manager *cm = charger->cm; - struct charger_desc *desc = cm->desc; - int i; - int ret; - int externally_control; - int chargers_externally_control = 1; - - ret = sscanf(buf, "%d", &externally_control); - if (ret == 0) { - ret = -EINVAL; - return ret; - } - - if (!externally_control) { - charger->externally_control = 0; - return count; - } - - for (i = 0; i < desc->num_charger_regulators; i++) { - if (&desc->charger_regulators[i] != charger && - !desc->charger_regulators[i].externally_control) { - /* - * At least, one charger is controlled by - * charger-manager - */ - chargers_externally_control = 0; - break; - } - } - - if (!chargers_externally_control) { - if (cm->charger_enabled) { - try_charger_enable(charger->cm, false); - charger->externally_control = externally_control; - try_charger_enable(charger->cm, true); - } else { - charger->externally_control = externally_control; - } - } else { - dev_warn(cm->dev, - "'%s' regulator should be controlled in charger-manager because charger-manager must need at least one charger for charging\n", - charger->regulator_name); - } - - return count; -} - -/** - * charger_manager_register_sysfs - Register sysfs entry for each charger - * @cm: the Charger Manager representing the battery. - * - * This function add sysfs entry for charger(regulator) to control charger from - * user-space. If some development board use one more chargers for charging - * but only need one charger on specific case which is dependent on user - * scenario or hardware restrictions, the user enter 1 or 0(zero) to '/sys/ - * class/power_supply/battery/charger.[index]/externally_control'. For example, - * if user enter 1 to 'sys/class/power_supply/battery/charger.[index]/ - * externally_control, this charger isn't controlled from charger-manager and - * always stay off state of regulator. - */ -static int charger_manager_register_sysfs(struct charger_manager *cm) -{ - struct charger_desc *desc = cm->desc; - struct charger_regulator *charger; - int chargers_externally_control = 1; - char buf[11]; - char *str; - int ret = 0; - int i; - - /* Create sysfs entry to control charger(regulator) */ - for (i = 0; i < desc->num_charger_regulators; i++) { - charger = &desc->charger_regulators[i]; - - snprintf(buf, 10, "charger.%d", i); - str = devm_kzalloc(cm->dev, - sizeof(char) * (strlen(buf) + 1), GFP_KERNEL); - if (!str) { - ret = -ENOMEM; - goto err; - } - strcpy(str, buf); - - charger->attrs[0] = &charger->attr_name.attr; - charger->attrs[1] = &charger->attr_state.attr; - charger->attrs[2] = &charger->attr_externally_control.attr; - charger->attrs[3] = NULL; - charger->attr_g.name = str; - charger->attr_g.attrs = charger->attrs; - - sysfs_attr_init(&charger->attr_name.attr); - charger->attr_name.attr.name = "name"; - charger->attr_name.attr.mode = 0444; - charger->attr_name.show = charger_name_show; - - sysfs_attr_init(&charger->attr_state.attr); - charger->attr_state.attr.name = "state"; - charger->attr_state.attr.mode = 0444; - charger->attr_state.show = charger_state_show; - - sysfs_attr_init(&charger->attr_externally_control.attr); - charger->attr_externally_control.attr.name - = "externally_control"; - charger->attr_externally_control.attr.mode = 0644; - charger->attr_externally_control.show - = charger_externally_control_show; - charger->attr_externally_control.store - = charger_externally_control_store; - - if (!desc->charger_regulators[i].externally_control || - !chargers_externally_control) - chargers_externally_control = 0; - - dev_info(cm->dev, "'%s' regulator's externally_control is %d\n", - charger->regulator_name, charger->externally_control); - - ret = sysfs_create_group(&cm->charger_psy->dev.kobj, - &charger->attr_g); - if (ret < 0) { - dev_err(cm->dev, "Cannot create sysfs entry of %s regulator\n", - charger->regulator_name); - ret = -EINVAL; - goto err; - } - } - - if (chargers_externally_control) { - dev_err(cm->dev, "Cannot register regulator because charger-manager must need at least one charger for charging battery\n"); - ret = -EINVAL; - goto err; - } - -err: - return ret; -} - -static int cm_init_thermal_data(struct charger_manager *cm, - struct power_supply *fuel_gauge) -{ - struct charger_desc *desc = cm->desc; - union power_supply_propval val; - int ret; - - /* Verify whether fuel gauge provides battery temperature */ - ret = power_supply_get_property(fuel_gauge, - POWER_SUPPLY_PROP_TEMP, &val); - - if (!ret) { - cm->charger_psy_desc.properties[cm->charger_psy_desc.num_properties] = - POWER_SUPPLY_PROP_TEMP; - cm->charger_psy_desc.num_properties++; - cm->desc->measure_battery_temp = true; - } -#ifdef CONFIG_THERMAL - if (ret && desc->thermal_zone) { - cm->tzd_batt = - thermal_zone_get_zone_by_name(desc->thermal_zone); - if (IS_ERR(cm->tzd_batt)) - return PTR_ERR(cm->tzd_batt); - - /* Use external thermometer */ - cm->charger_psy_desc.properties[cm->charger_psy_desc.num_properties] = - POWER_SUPPLY_PROP_TEMP_AMBIENT; - cm->charger_psy_desc.num_properties++; - cm->desc->measure_battery_temp = true; - ret = 0; - } -#endif - if (cm->desc->measure_battery_temp) { - /* NOTICE : Default allowable minimum charge temperature is 0 */ - if (!desc->temp_max) - desc->temp_max = CM_DEFAULT_CHARGE_TEMP_MAX; - if (!desc->temp_diff) - desc->temp_diff = CM_DEFAULT_RECHARGE_TEMP_DIFF; - } - - return ret; -} - -static const struct of_device_id charger_manager_match[] = { - { - .compatible = "charger-manager", - }, - {}, -}; - -static struct charger_desc *of_cm_parse_desc(struct device *dev) -{ - struct charger_desc *desc; - struct device_node *np = dev->of_node; - u32 poll_mode = CM_POLL_DISABLE; - u32 battery_stat = CM_NO_BATTERY; - int num_chgs = 0; - - desc = devm_kzalloc(dev, sizeof(*desc), GFP_KERNEL); - if (!desc) - return ERR_PTR(-ENOMEM); - - of_property_read_string(np, "cm-name", &desc->psy_name); - - of_property_read_u32(np, "cm-poll-mode", &poll_mode); - desc->polling_mode = poll_mode; - - of_property_read_u32(np, "cm-poll-interval", - &desc->polling_interval_ms); - - of_property_read_u32(np, "cm-fullbatt-vchkdrop-ms", - &desc->fullbatt_vchkdrop_ms); - of_property_read_u32(np, "cm-fullbatt-vchkdrop-volt", - &desc->fullbatt_vchkdrop_uV); - of_property_read_u32(np, "cm-fullbatt-voltage", &desc->fullbatt_uV); - of_property_read_u32(np, "cm-fullbatt-soc", &desc->fullbatt_soc); - of_property_read_u32(np, "cm-fullbatt-capacity", - &desc->fullbatt_full_capacity); - - of_property_read_u32(np, "cm-battery-stat", &battery_stat); - desc->battery_present = battery_stat; - - /* chargers */ - of_property_read_u32(np, "cm-num-chargers", &num_chgs); - if (num_chgs) { - /* Allocate empty bin at the tail of array */ - desc->psy_charger_stat = devm_kzalloc(dev, sizeof(char *) - * (num_chgs + 1), GFP_KERNEL); - if (desc->psy_charger_stat) { - int i; - for (i = 0; i < num_chgs; i++) - of_property_read_string_index(np, "cm-chargers", - i, &desc->psy_charger_stat[i]); - } else { - return ERR_PTR(-ENOMEM); - } - } - - of_property_read_string(np, "cm-fuel-gauge", &desc->psy_fuel_gauge); - - of_property_read_string(np, "cm-thermal-zone", &desc->thermal_zone); - - of_property_read_u32(np, "cm-battery-cold", &desc->temp_min); - if (of_get_property(np, "cm-battery-cold-in-minus", NULL)) - desc->temp_min *= -1; - of_property_read_u32(np, "cm-battery-hot", &desc->temp_max); - of_property_read_u32(np, "cm-battery-temp-diff", &desc->temp_diff); - - of_property_read_u32(np, "cm-charging-max", - &desc->charging_max_duration_ms); - of_property_read_u32(np, "cm-discharging-max", - &desc->discharging_max_duration_ms); - - /* battery charger regualtors */ - desc->num_charger_regulators = of_get_child_count(np); - if (desc->num_charger_regulators) { - struct charger_regulator *chg_regs; - struct device_node *child; - - chg_regs = devm_kzalloc(dev, sizeof(*chg_regs) - * desc->num_charger_regulators, - GFP_KERNEL); - if (!chg_regs) - return ERR_PTR(-ENOMEM); - - desc->charger_regulators = chg_regs; - - for_each_child_of_node(np, child) { - struct charger_cable *cables; - struct device_node *_child; - - of_property_read_string(child, "cm-regulator-name", - &chg_regs->regulator_name); - - /* charger cables */ - chg_regs->num_cables = of_get_child_count(child); - if (chg_regs->num_cables) { - cables = devm_kzalloc(dev, sizeof(*cables) - * chg_regs->num_cables, - GFP_KERNEL); - if (!cables) { - of_node_put(child); - return ERR_PTR(-ENOMEM); - } - - chg_regs->cables = cables; - - for_each_child_of_node(child, _child) { - of_property_read_string(_child, - "cm-cable-name", &cables->name); - of_property_read_string(_child, - "cm-cable-extcon", - &cables->extcon_name); - of_property_read_u32(_child, - "cm-cable-min", - &cables->min_uA); - of_property_read_u32(_child, - "cm-cable-max", - &cables->max_uA); - cables++; - } - } - chg_regs++; - } - } - return desc; -} - -static inline struct charger_desc *cm_get_drv_data(struct platform_device *pdev) -{ - if (pdev->dev.of_node) - return of_cm_parse_desc(&pdev->dev); - return dev_get_platdata(&pdev->dev); -} - -static enum alarmtimer_restart cm_timer_func(struct alarm *alarm, ktime_t now) -{ - cm_timer_set = false; - return ALARMTIMER_NORESTART; -} - -static int charger_manager_probe(struct platform_device *pdev) -{ - struct charger_desc *desc = cm_get_drv_data(pdev); - struct charger_manager *cm; - int ret = 0, i = 0; - int j = 0; - union power_supply_propval val; - struct power_supply *fuel_gauge; - struct power_supply_config psy_cfg = {}; - - if (IS_ERR(desc)) { - dev_err(&pdev->dev, "No platform data (desc) found\n"); - return -ENODEV; - } - - cm = devm_kzalloc(&pdev->dev, - sizeof(struct charger_manager), GFP_KERNEL); - if (!cm) - return -ENOMEM; - - /* Basic Values. Unspecified are Null or 0 */ - cm->dev = &pdev->dev; - cm->desc = desc; - psy_cfg.drv_data = cm; - - /* Initialize alarm timer */ - if (alarmtimer_get_rtcdev()) { - cm_timer = devm_kzalloc(cm->dev, sizeof(*cm_timer), GFP_KERNEL); - alarm_init(cm_timer, ALARM_BOOTTIME, cm_timer_func); - } - - /* - * The following two do not need to be errors. - * Users may intentionally ignore those two features. - */ - if (desc->fullbatt_uV == 0) { - dev_info(&pdev->dev, "Ignoring full-battery voltage threshold as it is not supplied\n"); - } - if (!desc->fullbatt_vchkdrop_ms || !desc->fullbatt_vchkdrop_uV) { - dev_info(&pdev->dev, "Disabling full-battery voltage drop checking mechanism as it is not supplied\n"); - desc->fullbatt_vchkdrop_ms = 0; - desc->fullbatt_vchkdrop_uV = 0; - } - if (desc->fullbatt_soc == 0) { - dev_info(&pdev->dev, "Ignoring full-battery soc(state of charge) threshold as it is not supplied\n"); - } - if (desc->fullbatt_full_capacity == 0) { - dev_info(&pdev->dev, "Ignoring full-battery full capacity threshold as it is not supplied\n"); - } - - if (!desc->charger_regulators || desc->num_charger_regulators < 1) { - dev_err(&pdev->dev, "charger_regulators undefined\n"); - return -EINVAL; - } - - if (!desc->psy_charger_stat || !desc->psy_charger_stat[0]) { - dev_err(&pdev->dev, "No power supply defined\n"); - return -EINVAL; - } - - if (!desc->psy_fuel_gauge) { - dev_err(&pdev->dev, "No fuel gauge power supply defined\n"); - return -EINVAL; - } - - /* Counting index only */ - while (desc->psy_charger_stat[i]) - i++; - - /* Check if charger's supplies are present at probe */ - for (i = 0; desc->psy_charger_stat[i]; i++) { - struct power_supply *psy; - - psy = power_supply_get_by_name(desc->psy_charger_stat[i]); - if (!psy) { - dev_err(&pdev->dev, "Cannot find power supply \"%s\"\n", - desc->psy_charger_stat[i]); - return -ENODEV; - } - power_supply_put(psy); - } - - if (desc->polling_interval_ms == 0 || - msecs_to_jiffies(desc->polling_interval_ms) <= CM_JIFFIES_SMALL) { - dev_err(&pdev->dev, "polling_interval_ms is too small\n"); - return -EINVAL; - } - - if (!desc->charging_max_duration_ms || - !desc->discharging_max_duration_ms) { - dev_info(&pdev->dev, "Cannot limit charging duration checking mechanism to prevent overcharge/overheat and control discharging duration\n"); - desc->charging_max_duration_ms = 0; - desc->discharging_max_duration_ms = 0; - } - - platform_set_drvdata(pdev, cm); - - memcpy(&cm->charger_psy_desc, &psy_default, sizeof(psy_default)); - - if (!desc->psy_name) - strncpy(cm->psy_name_buf, psy_default.name, PSY_NAME_MAX); - else - strncpy(cm->psy_name_buf, desc->psy_name, PSY_NAME_MAX); - cm->charger_psy_desc.name = cm->psy_name_buf; - - /* Allocate for psy properties because they may vary */ - cm->charger_psy_desc.properties = devm_kzalloc(&pdev->dev, - sizeof(enum power_supply_property) - * (ARRAY_SIZE(default_charger_props) + - NUM_CHARGER_PSY_OPTIONAL), GFP_KERNEL); - if (!cm->charger_psy_desc.properties) - return -ENOMEM; - - memcpy(cm->charger_psy_desc.properties, default_charger_props, - sizeof(enum power_supply_property) * - ARRAY_SIZE(default_charger_props)); - cm->charger_psy_desc.num_properties = psy_default.num_properties; - - /* Find which optional psy-properties are available */ - fuel_gauge = power_supply_get_by_name(desc->psy_fuel_gauge); - if (!fuel_gauge) { - dev_err(&pdev->dev, "Cannot find power supply \"%s\"\n", - desc->psy_fuel_gauge); - return -ENODEV; - } - if (!power_supply_get_property(fuel_gauge, - POWER_SUPPLY_PROP_CHARGE_NOW, &val)) { - cm->charger_psy_desc.properties[cm->charger_psy_desc.num_properties] = - POWER_SUPPLY_PROP_CHARGE_NOW; - cm->charger_psy_desc.num_properties++; - } - if (!power_supply_get_property(fuel_gauge, - POWER_SUPPLY_PROP_CURRENT_NOW, - &val)) { - cm->charger_psy_desc.properties[cm->charger_psy_desc.num_properties] = - POWER_SUPPLY_PROP_CURRENT_NOW; - cm->charger_psy_desc.num_properties++; - } - - ret = cm_init_thermal_data(cm, fuel_gauge); - if (ret) { - dev_err(&pdev->dev, "Failed to initialize thermal data\n"); - cm->desc->measure_battery_temp = false; - } - power_supply_put(fuel_gauge); - - INIT_DELAYED_WORK(&cm->fullbatt_vchk_work, fullbatt_vchk); - - cm->charger_psy = power_supply_register(&pdev->dev, - &cm->charger_psy_desc, - &psy_cfg); - if (IS_ERR(cm->charger_psy)) { - dev_err(&pdev->dev, "Cannot register charger-manager with name \"%s\"\n", - cm->charger_psy_desc.name); - return PTR_ERR(cm->charger_psy); - } - - /* Register extcon device for charger cable */ - ret = charger_manager_register_extcon(cm); - if (ret < 0) { - dev_err(&pdev->dev, "Cannot initialize extcon device\n"); - goto err_reg_extcon; - } - - /* Register sysfs entry for charger(regulator) */ - ret = charger_manager_register_sysfs(cm); - if (ret < 0) { - dev_err(&pdev->dev, - "Cannot initialize sysfs entry of regulator\n"); - goto err_reg_sysfs; - } - - /* Add to the list */ - mutex_lock(&cm_list_mtx); - list_add(&cm->entry, &cm_list); - mutex_unlock(&cm_list_mtx); - - /* - * Charger-manager is capable of waking up the systme from sleep - * when event is happend through cm_notify_event() - */ - device_init_wakeup(&pdev->dev, true); - device_set_wakeup_capable(&pdev->dev, false); - - /* - * Charger-manager have to check the charging state right after - * tialization of charger-manager and then update current charging - * state. - */ - cm_monitor(); - - schedule_work(&setup_polling); - - return 0; - -err_reg_sysfs: - for (i = 0; i < desc->num_charger_regulators; i++) { - struct charger_regulator *charger; - - charger = &desc->charger_regulators[i]; - sysfs_remove_group(&cm->charger_psy->dev.kobj, - &charger->attr_g); - } -err_reg_extcon: - for (i = 0; i < desc->num_charger_regulators; i++) { - struct charger_regulator *charger; - - charger = &desc->charger_regulators[i]; - for (j = 0; j < charger->num_cables; j++) { - struct charger_cable *cable = &charger->cables[j]; - /* Remove notifier block if only edev exists */ - if (cable->extcon_dev.edev) - extcon_unregister_interest(&cable->extcon_dev); - } - - regulator_put(desc->charger_regulators[i].consumer); - } - - power_supply_unregister(cm->charger_psy); - - return ret; -} - -static int charger_manager_remove(struct platform_device *pdev) -{ - struct charger_manager *cm = platform_get_drvdata(pdev); - struct charger_desc *desc = cm->desc; - int i = 0; - int j = 0; - - /* Remove from the list */ - mutex_lock(&cm_list_mtx); - list_del(&cm->entry); - mutex_unlock(&cm_list_mtx); - - cancel_work_sync(&setup_polling); - cancel_delayed_work_sync(&cm_monitor_work); - - for (i = 0 ; i < desc->num_charger_regulators ; i++) { - struct charger_regulator *charger - = &desc->charger_regulators[i]; - for (j = 0 ; j < charger->num_cables ; j++) { - struct charger_cable *cable = &charger->cables[j]; - extcon_unregister_interest(&cable->extcon_dev); - } - } - - for (i = 0 ; i < desc->num_charger_regulators ; i++) - regulator_put(desc->charger_regulators[i].consumer); - - power_supply_unregister(cm->charger_psy); - - try_charger_enable(cm, false); - - return 0; -} - -static const struct platform_device_id charger_manager_id[] = { - { "charger-manager", 0 }, - { }, -}; -MODULE_DEVICE_TABLE(platform, charger_manager_id); - -static int cm_suspend_noirq(struct device *dev) -{ - int ret = 0; - - if (device_may_wakeup(dev)) { - device_set_wakeup_capable(dev, false); - ret = -EAGAIN; - } - - return ret; -} - -static bool cm_need_to_awake(void) -{ - struct charger_manager *cm; - - if (cm_timer) - return false; - - mutex_lock(&cm_list_mtx); - list_for_each_entry(cm, &cm_list, entry) { - if (is_charging(cm)) { - mutex_unlock(&cm_list_mtx); - return true; - } - } - mutex_unlock(&cm_list_mtx); - - return false; -} - -static int cm_suspend_prepare(struct device *dev) -{ - struct charger_manager *cm = dev_get_drvdata(dev); - - if (cm_need_to_awake()) - return -EBUSY; - - if (!cm_suspended) - cm_suspended = true; - - cm_timer_set = cm_setup_timer(); - - if (cm_timer_set) { - cancel_work_sync(&setup_polling); - cancel_delayed_work_sync(&cm_monitor_work); - cancel_delayed_work(&cm->fullbatt_vchk_work); - } - - return 0; -} - -static void cm_suspend_complete(struct device *dev) -{ - struct charger_manager *cm = dev_get_drvdata(dev); - - if (cm_suspended) - cm_suspended = false; - - if (cm_timer_set) { - ktime_t remain; - - alarm_cancel(cm_timer); - cm_timer_set = false; - remain = alarm_expires_remaining(cm_timer); - cm_suspend_duration_ms -= ktime_to_ms(remain); - schedule_work(&setup_polling); - } - - _cm_monitor(cm); - - /* Re-enqueue delayed work (fullbatt_vchk_work) */ - if (cm->fullbatt_vchk_jiffies_at) { - unsigned long delay = 0; - unsigned long now = jiffies + CM_JIFFIES_SMALL; - - if (time_after_eq(now, cm->fullbatt_vchk_jiffies_at)) { - delay = (unsigned long)((long)now - - (long)(cm->fullbatt_vchk_jiffies_at)); - delay = jiffies_to_msecs(delay); - } else { - delay = 0; - } - - /* - * Account for cm_suspend_duration_ms with assuming that - * timer stops in suspend. - */ - if (delay > cm_suspend_duration_ms) - delay -= cm_suspend_duration_ms; - else - delay = 0; - - queue_delayed_work(cm_wq, &cm->fullbatt_vchk_work, - msecs_to_jiffies(delay)); - } - device_set_wakeup_capable(cm->dev, false); -} - -static const struct dev_pm_ops charger_manager_pm = { - .prepare = cm_suspend_prepare, - .suspend_noirq = cm_suspend_noirq, - .complete = cm_suspend_complete, -}; - -static struct platform_driver charger_manager_driver = { - .driver = { - .name = "charger-manager", - .pm = &charger_manager_pm, - .of_match_table = charger_manager_match, - }, - .probe = charger_manager_probe, - .remove = charger_manager_remove, - .id_table = charger_manager_id, -}; - -static int __init charger_manager_init(void) -{ - cm_wq = create_freezable_workqueue("charger_manager"); - INIT_DELAYED_WORK(&cm_monitor_work, cm_monitor_poller); - - return platform_driver_register(&charger_manager_driver); -} -late_initcall(charger_manager_init); - -static void __exit charger_manager_cleanup(void) -{ - destroy_workqueue(cm_wq); - cm_wq = NULL; - - platform_driver_unregister(&charger_manager_driver); -} -module_exit(charger_manager_cleanup); - -/** - * cm_notify_event - charger driver notify Charger Manager of charger event - * @psy: pointer to instance of charger's power_supply - * @type: type of charger event - * @msg: optional message passed to uevent_notify fuction - */ -void cm_notify_event(struct power_supply *psy, enum cm_event_types type, - char *msg) -{ - struct charger_manager *cm; - bool found_power_supply = false; - - if (psy == NULL) - return; - - mutex_lock(&cm_list_mtx); - list_for_each_entry(cm, &cm_list, entry) { - if (match_string(cm->desc->psy_charger_stat, -1, - psy->desc->name) >= 0) { - found_power_supply = true; - break; - } - } - mutex_unlock(&cm_list_mtx); - - if (!found_power_supply) - return; - - switch (type) { - case CM_EVENT_BATT_FULL: - fullbatt_handler(cm); - break; - case CM_EVENT_BATT_OUT: - battout_handler(cm); - break; - case CM_EVENT_BATT_IN: - case CM_EVENT_EXT_PWR_IN_OUT ... CM_EVENT_CHG_START_STOP: - misc_event_handler(cm, type); - break; - case CM_EVENT_UNKNOWN: - case CM_EVENT_OTHERS: - uevent_notify(cm, msg ? msg : default_event_names[type]); - break; - default: - dev_err(cm->dev, "%s: type not specified\n", __func__); - break; - } -} -EXPORT_SYMBOL_GPL(cm_notify_event); - -MODULE_AUTHOR("MyungJoo Ham "); -MODULE_DESCRIPTION("Charger Manager"); -MODULE_LICENSE("GPL"); diff --git a/drivers/power/collie_battery.c b/drivers/power/collie_battery.c deleted file mode 100644 index 3a0bc608d4b5..000000000000 --- a/drivers/power/collie_battery.c +++ /dev/null @@ -1,422 +0,0 @@ -/* - * Battery and Power Management code for the Sharp SL-5x00 - * - * Copyright (C) 2009 Thomas Kunze - * - * based on tosa_battery.c - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - * - */ -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -static DEFINE_MUTEX(bat_lock); /* protects gpio pins */ -static struct work_struct bat_work; -static struct ucb1x00 *ucb; - -struct collie_bat { - int status; - struct power_supply *psy; - int full_chrg; - - struct mutex work_lock; /* protects data */ - - bool (*is_present)(struct collie_bat *bat); - int gpio_full; - int gpio_charge_on; - - int technology; - - int gpio_bat; - int adc_bat; - int adc_bat_divider; - int bat_max; - int bat_min; - - int gpio_temp; - int adc_temp; - int adc_temp_divider; -}; - -static struct collie_bat collie_bat_main; - -static unsigned long collie_read_bat(struct collie_bat *bat) -{ - unsigned long value = 0; - - if (bat->gpio_bat < 0 || bat->adc_bat < 0) - return 0; - mutex_lock(&bat_lock); - gpio_set_value(bat->gpio_bat, 1); - msleep(5); - ucb1x00_adc_enable(ucb); - value = ucb1x00_adc_read(ucb, bat->adc_bat, UCB_SYNC); - ucb1x00_adc_disable(ucb); - gpio_set_value(bat->gpio_bat, 0); - mutex_unlock(&bat_lock); - value = value * 1000000 / bat->adc_bat_divider; - - return value; -} - -static unsigned long collie_read_temp(struct collie_bat *bat) -{ - unsigned long value = 0; - if (bat->gpio_temp < 0 || bat->adc_temp < 0) - return 0; - - mutex_lock(&bat_lock); - gpio_set_value(bat->gpio_temp, 1); - msleep(5); - ucb1x00_adc_enable(ucb); - value = ucb1x00_adc_read(ucb, bat->adc_temp, UCB_SYNC); - ucb1x00_adc_disable(ucb); - gpio_set_value(bat->gpio_temp, 0); - mutex_unlock(&bat_lock); - - value = value * 10000 / bat->adc_temp_divider; - - return value; -} - -static int collie_bat_get_property(struct power_supply *psy, - enum power_supply_property psp, - union power_supply_propval *val) -{ - int ret = 0; - struct collie_bat *bat = power_supply_get_drvdata(psy); - - if (bat->is_present && !bat->is_present(bat) - && psp != POWER_SUPPLY_PROP_PRESENT) { - return -ENODEV; - } - - switch (psp) { - case POWER_SUPPLY_PROP_STATUS: - val->intval = bat->status; - break; - case POWER_SUPPLY_PROP_TECHNOLOGY: - val->intval = bat->technology; - break; - case POWER_SUPPLY_PROP_VOLTAGE_NOW: - val->intval = collie_read_bat(bat); - break; - case POWER_SUPPLY_PROP_VOLTAGE_MAX: - if (bat->full_chrg == -1) - val->intval = bat->bat_max; - else - val->intval = bat->full_chrg; - break; - case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: - val->intval = bat->bat_max; - break; - case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: - val->intval = bat->bat_min; - break; - case POWER_SUPPLY_PROP_TEMP: - val->intval = collie_read_temp(bat); - break; - case POWER_SUPPLY_PROP_PRESENT: - val->intval = bat->is_present ? bat->is_present(bat) : 1; - break; - default: - ret = -EINVAL; - break; - } - return ret; -} - -static void collie_bat_external_power_changed(struct power_supply *psy) -{ - schedule_work(&bat_work); -} - -static irqreturn_t collie_bat_gpio_isr(int irq, void *data) -{ - pr_info("collie_bat_gpio irq\n"); - schedule_work(&bat_work); - return IRQ_HANDLED; -} - -static void collie_bat_update(struct collie_bat *bat) -{ - int old; - struct power_supply *psy = bat->psy; - - mutex_lock(&bat->work_lock); - - old = bat->status; - - if (bat->is_present && !bat->is_present(bat)) { - printk(KERN_NOTICE "%s not present\n", psy->desc->name); - bat->status = POWER_SUPPLY_STATUS_UNKNOWN; - bat->full_chrg = -1; - } else if (power_supply_am_i_supplied(psy)) { - if (bat->status == POWER_SUPPLY_STATUS_DISCHARGING) { - gpio_set_value(bat->gpio_charge_on, 1); - mdelay(15); - } - - if (gpio_get_value(bat->gpio_full)) { - if (old == POWER_SUPPLY_STATUS_CHARGING || - bat->full_chrg == -1) - bat->full_chrg = collie_read_bat(bat); - - gpio_set_value(bat->gpio_charge_on, 0); - bat->status = POWER_SUPPLY_STATUS_FULL; - } else { - gpio_set_value(bat->gpio_charge_on, 1); - bat->status = POWER_SUPPLY_STATUS_CHARGING; - } - } else { - gpio_set_value(bat->gpio_charge_on, 0); - bat->status = POWER_SUPPLY_STATUS_DISCHARGING; - } - - if (old != bat->status) - power_supply_changed(psy); - - mutex_unlock(&bat->work_lock); -} - -static void collie_bat_work(struct work_struct *work) -{ - collie_bat_update(&collie_bat_main); -} - - -static enum power_supply_property collie_bat_main_props[] = { - POWER_SUPPLY_PROP_STATUS, - POWER_SUPPLY_PROP_TECHNOLOGY, - POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, - POWER_SUPPLY_PROP_VOLTAGE_NOW, - POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, - POWER_SUPPLY_PROP_VOLTAGE_MAX, - POWER_SUPPLY_PROP_PRESENT, - POWER_SUPPLY_PROP_TEMP, -}; - -static enum power_supply_property collie_bat_bu_props[] = { - POWER_SUPPLY_PROP_STATUS, - POWER_SUPPLY_PROP_TECHNOLOGY, - POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, - POWER_SUPPLY_PROP_VOLTAGE_NOW, - POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, - POWER_SUPPLY_PROP_VOLTAGE_MAX, - POWER_SUPPLY_PROP_PRESENT, -}; - -static const struct power_supply_desc collie_bat_main_desc = { - .name = "main-battery", - .type = POWER_SUPPLY_TYPE_BATTERY, - .properties = collie_bat_main_props, - .num_properties = ARRAY_SIZE(collie_bat_main_props), - .get_property = collie_bat_get_property, - .external_power_changed = collie_bat_external_power_changed, - .use_for_apm = 1, -}; - -static struct collie_bat collie_bat_main = { - .status = POWER_SUPPLY_STATUS_DISCHARGING, - .full_chrg = -1, - .psy = NULL, - - .gpio_full = COLLIE_GPIO_CO, - .gpio_charge_on = COLLIE_GPIO_CHARGE_ON, - - .technology = POWER_SUPPLY_TECHNOLOGY_LIPO, - - .gpio_bat = COLLIE_GPIO_MBAT_ON, - .adc_bat = UCB_ADC_INP_AD1, - .adc_bat_divider = 155, - .bat_max = 4310000, - .bat_min = 1551 * 1000000 / 414, - - .gpio_temp = COLLIE_GPIO_TMP_ON, - .adc_temp = UCB_ADC_INP_AD0, - .adc_temp_divider = 10000, -}; - -static const struct power_supply_desc collie_bat_bu_desc = { - .name = "backup-battery", - .type = POWER_SUPPLY_TYPE_BATTERY, - .properties = collie_bat_bu_props, - .num_properties = ARRAY_SIZE(collie_bat_bu_props), - .get_property = collie_bat_get_property, - .external_power_changed = collie_bat_external_power_changed, -}; - -static struct collie_bat collie_bat_bu = { - .status = POWER_SUPPLY_STATUS_UNKNOWN, - .full_chrg = -1, - .psy = NULL, - - .gpio_full = -1, - .gpio_charge_on = -1, - - .technology = POWER_SUPPLY_TECHNOLOGY_LiMn, - - .gpio_bat = COLLIE_GPIO_BBAT_ON, - .adc_bat = UCB_ADC_INP_AD1, - .adc_bat_divider = 155, - .bat_max = 3000000, - .bat_min = 1900000, - - .gpio_temp = -1, - .adc_temp = -1, - .adc_temp_divider = -1, -}; - -static struct gpio collie_batt_gpios[] = { - { COLLIE_GPIO_CO, GPIOF_IN, "main battery full" }, - { COLLIE_GPIO_MAIN_BAT_LOW, GPIOF_IN, "main battery low" }, - { COLLIE_GPIO_CHARGE_ON, GPIOF_OUT_INIT_LOW, "main charge on" }, - { COLLIE_GPIO_MBAT_ON, GPIOF_OUT_INIT_LOW, "main battery" }, - { COLLIE_GPIO_TMP_ON, GPIOF_OUT_INIT_LOW, "main battery temp" }, - { COLLIE_GPIO_BBAT_ON, GPIOF_OUT_INIT_LOW, "backup battery" }, -}; - -#ifdef CONFIG_PM -static int wakeup_enabled; - -static int collie_bat_suspend(struct ucb1x00_dev *dev) -{ - /* flush all pending status updates */ - flush_work(&bat_work); - - if (device_may_wakeup(&dev->ucb->dev) && - collie_bat_main.status == POWER_SUPPLY_STATUS_CHARGING) - wakeup_enabled = !enable_irq_wake(gpio_to_irq(COLLIE_GPIO_CO)); - else - wakeup_enabled = 0; - - return 0; -} - -static int collie_bat_resume(struct ucb1x00_dev *dev) -{ - if (wakeup_enabled) - disable_irq_wake(gpio_to_irq(COLLIE_GPIO_CO)); - - /* things may have changed while we were away */ - schedule_work(&bat_work); - return 0; -} -#else -#define collie_bat_suspend NULL -#define collie_bat_resume NULL -#endif - -static int collie_bat_probe(struct ucb1x00_dev *dev) -{ - int ret; - struct power_supply_config psy_main_cfg = {}, psy_bu_cfg = {}; - - if (!machine_is_collie()) - return -ENODEV; - - ucb = dev->ucb; - - ret = gpio_request_array(collie_batt_gpios, - ARRAY_SIZE(collie_batt_gpios)); - if (ret) - return ret; - - mutex_init(&collie_bat_main.work_lock); - - INIT_WORK(&bat_work, collie_bat_work); - - psy_main_cfg.drv_data = &collie_bat_main; - collie_bat_main.psy = power_supply_register(&dev->ucb->dev, - &collie_bat_main_desc, - &psy_main_cfg); - if (IS_ERR(collie_bat_main.psy)) { - ret = PTR_ERR(collie_bat_main.psy); - goto err_psy_reg_main; - } - - psy_bu_cfg.drv_data = &collie_bat_bu; - collie_bat_bu.psy = power_supply_register(&dev->ucb->dev, - &collie_bat_bu_desc, - &psy_bu_cfg); - if (IS_ERR(collie_bat_bu.psy)) { - ret = PTR_ERR(collie_bat_bu.psy); - goto err_psy_reg_bu; - } - - ret = request_irq(gpio_to_irq(COLLIE_GPIO_CO), - collie_bat_gpio_isr, - IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, - "main full", &collie_bat_main); - if (ret) - goto err_irq; - - device_init_wakeup(&ucb->dev, 1); - schedule_work(&bat_work); - - return 0; - -err_irq: - power_supply_unregister(collie_bat_bu.psy); -err_psy_reg_bu: - power_supply_unregister(collie_bat_main.psy); -err_psy_reg_main: - - /* see comment in collie_bat_remove */ - cancel_work_sync(&bat_work); - gpio_free_array(collie_batt_gpios, ARRAY_SIZE(collie_batt_gpios)); - return ret; -} - -static void collie_bat_remove(struct ucb1x00_dev *dev) -{ - free_irq(gpio_to_irq(COLLIE_GPIO_CO), &collie_bat_main); - - power_supply_unregister(collie_bat_bu.psy); - power_supply_unregister(collie_bat_main.psy); - - /* - * Now cancel the bat_work. We won't get any more schedules, - * since all sources (isr and external_power_changed) are - * unregistered now. - */ - cancel_work_sync(&bat_work); - gpio_free_array(collie_batt_gpios, ARRAY_SIZE(collie_batt_gpios)); -} - -static struct ucb1x00_driver collie_bat_driver = { - .add = collie_bat_probe, - .remove = collie_bat_remove, - .suspend = collie_bat_suspend, - .resume = collie_bat_resume, -}; - -static int __init collie_bat_init(void) -{ - return ucb1x00_register_driver(&collie_bat_driver); -} - -static void __exit collie_bat_exit(void) -{ - ucb1x00_unregister_driver(&collie_bat_driver); -} - -module_init(collie_bat_init); -module_exit(collie_bat_exit); - -MODULE_LICENSE("GPL"); -MODULE_AUTHOR("Thomas Kunze"); -MODULE_DESCRIPTION("Collie battery driver"); diff --git a/drivers/power/da9030_battery.c b/drivers/power/da9030_battery.c deleted file mode 100644 index 5ca0f4d90792..000000000000 --- a/drivers/power/da9030_battery.c +++ /dev/null @@ -1,596 +0,0 @@ -/* - * Battery charger driver for Dialog Semiconductor DA9030 - * - * Copyright (C) 2008 Compulab, Ltd. - * Mike Rapoport - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#define DA9030_FAULT_LOG 0x0a -#define DA9030_FAULT_LOG_OVER_TEMP (1 << 7) -#define DA9030_FAULT_LOG_VBAT_OVER (1 << 4) - -#define DA9030_CHARGE_CONTROL 0x28 -#define DA9030_CHRG_CHARGER_ENABLE (1 << 7) - -#define DA9030_ADC_MAN_CONTROL 0x30 -#define DA9030_ADC_TBATREF_ENABLE (1 << 5) -#define DA9030_ADC_LDO_INT_ENABLE (1 << 4) - -#define DA9030_ADC_AUTO_CONTROL 0x31 -#define DA9030_ADC_TBAT_ENABLE (1 << 5) -#define DA9030_ADC_VBAT_IN_TXON (1 << 4) -#define DA9030_ADC_VCH_ENABLE (1 << 3) -#define DA9030_ADC_ICH_ENABLE (1 << 2) -#define DA9030_ADC_VBAT_ENABLE (1 << 1) -#define DA9030_ADC_AUTO_SLEEP_ENABLE (1 << 0) - -#define DA9030_VBATMON 0x32 -#define DA9030_VBATMONTXON 0x33 -#define DA9030_TBATHIGHP 0x34 -#define DA9030_TBATHIGHN 0x35 -#define DA9030_TBATLOW 0x36 - -#define DA9030_VBAT_RES 0x41 -#define DA9030_VBATMIN_RES 0x42 -#define DA9030_VBATMINTXON_RES 0x43 -#define DA9030_ICHMAX_RES 0x44 -#define DA9030_ICHMIN_RES 0x45 -#define DA9030_ICHAVERAGE_RES 0x46 -#define DA9030_VCHMAX_RES 0x47 -#define DA9030_VCHMIN_RES 0x48 -#define DA9030_TBAT_RES 0x49 - -struct da9030_adc_res { - uint8_t vbat_res; - uint8_t vbatmin_res; - uint8_t vbatmintxon; - uint8_t ichmax_res; - uint8_t ichmin_res; - uint8_t ichaverage_res; - uint8_t vchmax_res; - uint8_t vchmin_res; - uint8_t tbat_res; - uint8_t adc_in4_res; - uint8_t adc_in5_res; -}; - -struct da9030_battery_thresholds { - int tbat_low; - int tbat_high; - int tbat_restart; - - int vbat_low; - int vbat_crit; - int vbat_charge_start; - int vbat_charge_stop; - int vbat_charge_restart; - - int vcharge_min; - int vcharge_max; -}; - -struct da9030_charger { - struct power_supply *psy; - struct power_supply_desc psy_desc; - - struct device *master; - - struct da9030_adc_res adc; - struct delayed_work work; - unsigned int interval; - - struct power_supply_info *battery_info; - - struct da9030_battery_thresholds thresholds; - - unsigned int charge_milliamp; - unsigned int charge_millivolt; - - /* charger status */ - bool chdet; - uint8_t fault; - int mA; - int mV; - bool is_on; - - struct notifier_block nb; - - /* platform callbacks for battery low and critical events */ - void (*battery_low)(void); - void (*battery_critical)(void); - - struct dentry *debug_file; -}; - -static inline int da9030_reg_to_mV(int reg) -{ - return ((reg * 2650) >> 8) + 2650; -} - -static inline int da9030_millivolt_to_reg(int mV) -{ - return ((mV - 2650) << 8) / 2650; -} - -static inline int da9030_reg_to_mA(int reg) -{ - return ((reg * 24000) >> 8) / 15; -} - -#ifdef CONFIG_DEBUG_FS -static int bat_debug_show(struct seq_file *s, void *data) -{ - struct da9030_charger *charger = s->private; - - seq_printf(s, "charger is %s\n", charger->is_on ? "on" : "off"); - if (charger->chdet) { - seq_printf(s, "iset = %dmA, vset = %dmV\n", - charger->mA, charger->mV); - } - - seq_printf(s, "vbat_res = %d (%dmV)\n", - charger->adc.vbat_res, - da9030_reg_to_mV(charger->adc.vbat_res)); - seq_printf(s, "vbatmin_res = %d (%dmV)\n", - charger->adc.vbatmin_res, - da9030_reg_to_mV(charger->adc.vbatmin_res)); - seq_printf(s, "vbatmintxon = %d (%dmV)\n", - charger->adc.vbatmintxon, - da9030_reg_to_mV(charger->adc.vbatmintxon)); - seq_printf(s, "ichmax_res = %d (%dmA)\n", - charger->adc.ichmax_res, - da9030_reg_to_mV(charger->adc.ichmax_res)); - seq_printf(s, "ichmin_res = %d (%dmA)\n", - charger->adc.ichmin_res, - da9030_reg_to_mA(charger->adc.ichmin_res)); - seq_printf(s, "ichaverage_res = %d (%dmA)\n", - charger->adc.ichaverage_res, - da9030_reg_to_mA(charger->adc.ichaverage_res)); - seq_printf(s, "vchmax_res = %d (%dmV)\n", - charger->adc.vchmax_res, - da9030_reg_to_mA(charger->adc.vchmax_res)); - seq_printf(s, "vchmin_res = %d (%dmV)\n", - charger->adc.vchmin_res, - da9030_reg_to_mV(charger->adc.vchmin_res)); - - return 0; -} - -static int debug_open(struct inode *inode, struct file *file) -{ - return single_open(file, bat_debug_show, inode->i_private); -} - -static const struct file_operations bat_debug_fops = { - .open = debug_open, - .read = seq_read, - .llseek = seq_lseek, - .release = single_release, -}; - -static struct dentry *da9030_bat_create_debugfs(struct da9030_charger *charger) -{ - charger->debug_file = debugfs_create_file("charger", 0666, NULL, - charger, &bat_debug_fops); - return charger->debug_file; -} - -static void da9030_bat_remove_debugfs(struct da9030_charger *charger) -{ - debugfs_remove(charger->debug_file); -} -#else -static inline struct dentry *da9030_bat_create_debugfs(struct da9030_charger *charger) -{ - return NULL; -} -static inline void da9030_bat_remove_debugfs(struct da9030_charger *charger) -{ -} -#endif - -static inline void da9030_read_adc(struct da9030_charger *charger, - struct da9030_adc_res *adc) -{ - da903x_reads(charger->master, DA9030_VBAT_RES, - sizeof(*adc), (uint8_t *)adc); -} - -static void da9030_charger_update_state(struct da9030_charger *charger) -{ - uint8_t val; - - da903x_read(charger->master, DA9030_CHARGE_CONTROL, &val); - charger->is_on = (val & DA9030_CHRG_CHARGER_ENABLE) ? 1 : 0; - charger->mA = ((val >> 3) & 0xf) * 100; - charger->mV = (val & 0x7) * 50 + 4000; - - da9030_read_adc(charger, &charger->adc); - da903x_read(charger->master, DA9030_FAULT_LOG, &charger->fault); - charger->chdet = da903x_query_status(charger->master, - DA9030_STATUS_CHDET); -} - -static void da9030_set_charge(struct da9030_charger *charger, int on) -{ - uint8_t val; - - if (on) { - val = DA9030_CHRG_CHARGER_ENABLE; - val |= (charger->charge_milliamp / 100) << 3; - val |= (charger->charge_millivolt - 4000) / 50; - charger->is_on = 1; - } else { - val = 0; - charger->is_on = 0; - } - - da903x_write(charger->master, DA9030_CHARGE_CONTROL, val); - - power_supply_changed(charger->psy); -} - -static void da9030_charger_check_state(struct da9030_charger *charger) -{ - da9030_charger_update_state(charger); - - /* we wake or boot with external power on */ - if (!charger->is_on) { - if ((charger->chdet) && - (charger->adc.vbat_res < - charger->thresholds.vbat_charge_start)) { - da9030_set_charge(charger, 1); - } - } else { - /* Charger has been pulled out */ - if (!charger->chdet) { - da9030_set_charge(charger, 0); - return; - } - - if (charger->adc.vbat_res >= - charger->thresholds.vbat_charge_stop) { - da9030_set_charge(charger, 0); - da903x_write(charger->master, DA9030_VBATMON, - charger->thresholds.vbat_charge_restart); - } else if (charger->adc.vbat_res > - charger->thresholds.vbat_low) { - /* we are charging and passed LOW_THRESH, - so upate DA9030 VBAT threshold - */ - da903x_write(charger->master, DA9030_VBATMON, - charger->thresholds.vbat_low); - } - if (charger->adc.vchmax_res > charger->thresholds.vcharge_max || - charger->adc.vchmin_res < charger->thresholds.vcharge_min || - /* Tempreture readings are negative */ - charger->adc.tbat_res < charger->thresholds.tbat_high || - charger->adc.tbat_res > charger->thresholds.tbat_low) { - /* disable charger */ - da9030_set_charge(charger, 0); - } - } -} - -static void da9030_charging_monitor(struct work_struct *work) -{ - struct da9030_charger *charger; - - charger = container_of(work, struct da9030_charger, work.work); - - da9030_charger_check_state(charger); - - /* reschedule for the next time */ - schedule_delayed_work(&charger->work, charger->interval); -} - -static enum power_supply_property da9030_battery_props[] = { - POWER_SUPPLY_PROP_MODEL_NAME, - POWER_SUPPLY_PROP_STATUS, - POWER_SUPPLY_PROP_HEALTH, - POWER_SUPPLY_PROP_TECHNOLOGY, - POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, - POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, - POWER_SUPPLY_PROP_VOLTAGE_NOW, - POWER_SUPPLY_PROP_CURRENT_AVG, -}; - -static void da9030_battery_check_status(struct da9030_charger *charger, - union power_supply_propval *val) -{ - if (charger->chdet) { - if (charger->is_on) - val->intval = POWER_SUPPLY_STATUS_CHARGING; - else - val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; - } else { - val->intval = POWER_SUPPLY_STATUS_DISCHARGING; - } -} - -static void da9030_battery_check_health(struct da9030_charger *charger, - union power_supply_propval *val) -{ - if (charger->fault & DA9030_FAULT_LOG_OVER_TEMP) - val->intval = POWER_SUPPLY_HEALTH_OVERHEAT; - else if (charger->fault & DA9030_FAULT_LOG_VBAT_OVER) - val->intval = POWER_SUPPLY_HEALTH_OVERVOLTAGE; - else - val->intval = POWER_SUPPLY_HEALTH_GOOD; -} - -static int da9030_battery_get_property(struct power_supply *psy, - enum power_supply_property psp, - union power_supply_propval *val) -{ - struct da9030_charger *charger = power_supply_get_drvdata(psy); - - switch (psp) { - case POWER_SUPPLY_PROP_STATUS: - da9030_battery_check_status(charger, val); - break; - case POWER_SUPPLY_PROP_HEALTH: - da9030_battery_check_health(charger, val); - break; - case POWER_SUPPLY_PROP_TECHNOLOGY: - val->intval = charger->battery_info->technology; - break; - case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: - val->intval = charger->battery_info->voltage_max_design; - break; - case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: - val->intval = charger->battery_info->voltage_min_design; - break; - case POWER_SUPPLY_PROP_VOLTAGE_NOW: - val->intval = da9030_reg_to_mV(charger->adc.vbat_res) * 1000; - break; - case POWER_SUPPLY_PROP_CURRENT_AVG: - val->intval = - da9030_reg_to_mA(charger->adc.ichaverage_res) * 1000; - break; - case POWER_SUPPLY_PROP_MODEL_NAME: - val->strval = charger->battery_info->name; - break; - default: - break; - } - - return 0; -} - -static void da9030_battery_vbat_event(struct da9030_charger *charger) -{ - da9030_read_adc(charger, &charger->adc); - - if (charger->is_on) - return; - - if (charger->adc.vbat_res < charger->thresholds.vbat_low) { - /* set VBAT threshold for critical */ - da903x_write(charger->master, DA9030_VBATMON, - charger->thresholds.vbat_crit); - if (charger->battery_low) - charger->battery_low(); - } else if (charger->adc.vbat_res < - charger->thresholds.vbat_crit) { - /* notify the system of battery critical */ - if (charger->battery_critical) - charger->battery_critical(); - } -} - -static int da9030_battery_event(struct notifier_block *nb, unsigned long event, - void *data) -{ - struct da9030_charger *charger = - container_of(nb, struct da9030_charger, nb); - - switch (event) { - case DA9030_EVENT_CHDET: - cancel_delayed_work_sync(&charger->work); - schedule_work(&charger->work.work); - break; - case DA9030_EVENT_VBATMON: - da9030_battery_vbat_event(charger); - break; - case DA9030_EVENT_CHIOVER: - case DA9030_EVENT_TBAT: - da9030_set_charge(charger, 0); - break; - } - - return 0; -} - -static void da9030_battery_convert_thresholds(struct da9030_charger *charger, - struct da9030_battery_info *pdata) -{ - charger->thresholds.tbat_low = pdata->tbat_low; - charger->thresholds.tbat_high = pdata->tbat_high; - charger->thresholds.tbat_restart = pdata->tbat_restart; - - charger->thresholds.vbat_low = - da9030_millivolt_to_reg(pdata->vbat_low); - charger->thresholds.vbat_crit = - da9030_millivolt_to_reg(pdata->vbat_crit); - charger->thresholds.vbat_charge_start = - da9030_millivolt_to_reg(pdata->vbat_charge_start); - charger->thresholds.vbat_charge_stop = - da9030_millivolt_to_reg(pdata->vbat_charge_stop); - charger->thresholds.vbat_charge_restart = - da9030_millivolt_to_reg(pdata->vbat_charge_restart); - - charger->thresholds.vcharge_min = - da9030_millivolt_to_reg(pdata->vcharge_min); - charger->thresholds.vcharge_max = - da9030_millivolt_to_reg(pdata->vcharge_max); -} - -static void da9030_battery_setup_psy(struct da9030_charger *charger) -{ - struct power_supply_desc *psy_desc = &charger->psy_desc; - struct power_supply_info *info = charger->battery_info; - - psy_desc->name = info->name; - psy_desc->use_for_apm = info->use_for_apm; - psy_desc->type = POWER_SUPPLY_TYPE_BATTERY; - psy_desc->get_property = da9030_battery_get_property; - - psy_desc->properties = da9030_battery_props; - psy_desc->num_properties = ARRAY_SIZE(da9030_battery_props); -}; - -static int da9030_battery_charger_init(struct da9030_charger *charger) -{ - char v[5]; - int ret; - - v[0] = v[1] = charger->thresholds.vbat_low; - v[2] = charger->thresholds.tbat_high; - v[3] = charger->thresholds.tbat_restart; - v[4] = charger->thresholds.tbat_low; - - ret = da903x_writes(charger->master, DA9030_VBATMON, 5, v); - if (ret) - return ret; - - /* - * Enable reference voltage supply for ADC from the LDO_INTERNAL - * regulator. Must be set before ADC measurements can be made. - */ - ret = da903x_write(charger->master, DA9030_ADC_MAN_CONTROL, - DA9030_ADC_LDO_INT_ENABLE | - DA9030_ADC_TBATREF_ENABLE); - if (ret) - return ret; - - /* enable auto ADC measuremnts */ - return da903x_write(charger->master, DA9030_ADC_AUTO_CONTROL, - DA9030_ADC_TBAT_ENABLE | DA9030_ADC_VBAT_IN_TXON | - DA9030_ADC_VCH_ENABLE | DA9030_ADC_ICH_ENABLE | - DA9030_ADC_VBAT_ENABLE | - DA9030_ADC_AUTO_SLEEP_ENABLE); -} - -static int da9030_battery_probe(struct platform_device *pdev) -{ - struct da9030_charger *charger; - struct power_supply_config psy_cfg = {}; - struct da9030_battery_info *pdata = pdev->dev.platform_data; - int ret; - - if (pdata == NULL) - return -EINVAL; - - if (pdata->charge_milliamp >= 1500 || - pdata->charge_millivolt < 4000 || - pdata->charge_millivolt > 4350) - return -EINVAL; - - charger = devm_kzalloc(&pdev->dev, sizeof(*charger), GFP_KERNEL); - if (charger == NULL) - return -ENOMEM; - - charger->master = pdev->dev.parent; - - /* 10 seconds between monitor runs unless platform defines other - interval */ - charger->interval = msecs_to_jiffies( - (pdata->batmon_interval ? : 10) * 1000); - - charger->charge_milliamp = pdata->charge_milliamp; - charger->charge_millivolt = pdata->charge_millivolt; - charger->battery_info = pdata->battery_info; - charger->battery_low = pdata->battery_low; - charger->battery_critical = pdata->battery_critical; - - da9030_battery_convert_thresholds(charger, pdata); - - ret = da9030_battery_charger_init(charger); - if (ret) - goto err_charger_init; - - INIT_DELAYED_WORK(&charger->work, da9030_charging_monitor); - schedule_delayed_work(&charger->work, charger->interval); - - charger->nb.notifier_call = da9030_battery_event; - ret = da903x_register_notifier(charger->master, &charger->nb, - DA9030_EVENT_CHDET | - DA9030_EVENT_VBATMON | - DA9030_EVENT_CHIOVER | - DA9030_EVENT_TBAT); - if (ret) - goto err_notifier; - - da9030_battery_setup_psy(charger); - psy_cfg.drv_data = charger; - charger->psy = power_supply_register(&pdev->dev, &charger->psy_desc, - &psy_cfg); - if (IS_ERR(charger->psy)) { - ret = PTR_ERR(charger->psy); - goto err_ps_register; - } - - charger->debug_file = da9030_bat_create_debugfs(charger); - platform_set_drvdata(pdev, charger); - return 0; - -err_ps_register: - da903x_unregister_notifier(charger->master, &charger->nb, - DA9030_EVENT_CHDET | DA9030_EVENT_VBATMON | - DA9030_EVENT_CHIOVER | DA9030_EVENT_TBAT); -err_notifier: - cancel_delayed_work(&charger->work); - -err_charger_init: - return ret; -} - -static int da9030_battery_remove(struct platform_device *dev) -{ - struct da9030_charger *charger = platform_get_drvdata(dev); - - da9030_bat_remove_debugfs(charger); - - da903x_unregister_notifier(charger->master, &charger->nb, - DA9030_EVENT_CHDET | DA9030_EVENT_VBATMON | - DA9030_EVENT_CHIOVER | DA9030_EVENT_TBAT); - cancel_delayed_work_sync(&charger->work); - da9030_set_charge(charger, 0); - power_supply_unregister(charger->psy); - - return 0; -} - -static struct platform_driver da903x_battery_driver = { - .driver = { - .name = "da903x-battery", - }, - .probe = da9030_battery_probe, - .remove = da9030_battery_remove, -}; - -module_platform_driver(da903x_battery_driver); - -MODULE_DESCRIPTION("DA9030 battery charger driver"); -MODULE_AUTHOR("Mike Rapoport, CompuLab"); -MODULE_LICENSE("GPL"); diff --git a/drivers/power/da9052-battery.c b/drivers/power/da9052-battery.c deleted file mode 100644 index 830ec46fe7d0..000000000000 --- a/drivers/power/da9052-battery.c +++ /dev/null @@ -1,669 +0,0 @@ -/* - * Batttery Driver for Dialog DA9052 PMICs - * - * Copyright(c) 2011 Dialog Semiconductor Ltd. - * - * Author: David Dajun Chen - * - * 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 -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -/* STATIC CONFIGURATION */ -#define DA9052_BAT_CUTOFF_VOLT 2800 -#define DA9052_BAT_TSH 62000 -#define DA9052_BAT_LOW_CAP 4 -#define DA9052_AVG_SZ 4 -#define DA9052_VC_TBL_SZ 68 -#define DA9052_VC_TBL_REF_SZ 3 - -#define DA9052_ISET_USB_MASK 0x0F -#define DA9052_CHG_USB_ILIM_MASK 0x40 -#define DA9052_CHG_LIM_COLS 16 - -#define DA9052_MEAN(x, y) ((x + y) / 2) - -enum charger_type_enum { - DA9052_NOCHARGER = 1, - DA9052_CHARGER, -}; - -static const u16 da9052_chg_current_lim[2][DA9052_CHG_LIM_COLS] = { - {70, 80, 90, 100, 110, 120, 400, 450, - 500, 550, 600, 650, 700, 900, 1100, 1300}, - {80, 90, 100, 110, 120, 400, 450, 500, - 550, 600, 800, 1000, 1200, 1400, 1600, 1800}, -}; - -static const u16 vc_tbl_ref[3] = {10, 25, 40}; -/* Lookup table for voltage vs capacity */ -static u32 const vc_tbl[3][68][2] = { - /* For temperature 10 degree Celsius */ - { - {4082, 100}, {4036, 98}, - {4020, 96}, {4008, 95}, - {3997, 93}, {3983, 91}, - {3964, 90}, {3943, 88}, - {3926, 87}, {3912, 85}, - {3900, 84}, {3890, 82}, - {3881, 80}, {3873, 79}, - {3865, 77}, {3857, 76}, - {3848, 74}, {3839, 73}, - {3829, 71}, {3820, 70}, - {3811, 68}, {3802, 67}, - {3794, 65}, {3785, 64}, - {3778, 62}, {3770, 61}, - {3763, 59}, {3756, 58}, - {3750, 56}, {3744, 55}, - {3738, 53}, {3732, 52}, - {3727, 50}, {3722, 49}, - {3717, 47}, {3712, 46}, - {3708, 44}, {3703, 43}, - {3700, 41}, {3696, 40}, - {3693, 38}, {3691, 37}, - {3688, 35}, {3686, 34}, - {3683, 32}, {3681, 31}, - {3678, 29}, {3675, 28}, - {3672, 26}, {3669, 25}, - {3665, 23}, {3661, 22}, - {3656, 21}, {3651, 19}, - {3645, 18}, {3639, 16}, - {3631, 15}, {3622, 13}, - {3611, 12}, {3600, 10}, - {3587, 9}, {3572, 7}, - {3548, 6}, {3503, 5}, - {3420, 3}, {3268, 2}, - {2992, 1}, {2746, 0} - }, - /* For temperature 25 degree Celsius */ - { - {4102, 100}, {4065, 98}, - {4048, 96}, {4034, 95}, - {4021, 93}, {4011, 92}, - {4001, 90}, {3986, 88}, - {3968, 87}, {3952, 85}, - {3938, 84}, {3926, 82}, - {3916, 81}, {3908, 79}, - {3900, 77}, {3892, 76}, - {3883, 74}, {3874, 73}, - {3864, 71}, {3855, 70}, - {3846, 68}, {3836, 67}, - {3827, 65}, {3819, 64}, - {3810, 62}, {3801, 61}, - {3793, 59}, {3786, 58}, - {3778, 56}, {3772, 55}, - {3765, 53}, {3759, 52}, - {3754, 50}, {3748, 49}, - {3743, 47}, {3738, 46}, - {3733, 44}, {3728, 43}, - {3724, 41}, {3720, 40}, - {3716, 38}, {3712, 37}, - {3709, 35}, {3706, 34}, - {3703, 33}, {3701, 31}, - {3698, 30}, {3696, 28}, - {3693, 27}, {3690, 25}, - {3687, 24}, {3683, 22}, - {3680, 21}, {3675, 19}, - {3671, 18}, {3666, 17}, - {3660, 15}, {3654, 14}, - {3647, 12}, {3639, 11}, - {3630, 9}, {3621, 8}, - {3613, 6}, {3606, 5}, - {3597, 4}, {3582, 2}, - {3546, 1}, {2747, 0} - }, - /* For temperature 40 degree Celsius */ - { - {4114, 100}, {4081, 98}, - {4065, 96}, {4050, 95}, - {4036, 93}, {4024, 92}, - {4013, 90}, {4002, 88}, - {3990, 87}, {3976, 85}, - {3962, 84}, {3950, 82}, - {3939, 81}, {3930, 79}, - {3921, 77}, {3912, 76}, - {3902, 74}, {3893, 73}, - {3883, 71}, {3874, 70}, - {3865, 68}, {3856, 67}, - {3847, 65}, {3838, 64}, - {3829, 62}, {3820, 61}, - {3812, 59}, {3803, 58}, - {3795, 56}, {3787, 55}, - {3780, 53}, {3773, 52}, - {3767, 50}, {3761, 49}, - {3756, 47}, {3751, 46}, - {3746, 44}, {3741, 43}, - {3736, 41}, {3732, 40}, - {3728, 38}, {3724, 37}, - {3720, 35}, {3716, 34}, - {3713, 33}, {3710, 31}, - {3707, 30}, {3704, 28}, - {3701, 27}, {3698, 25}, - {3695, 24}, {3691, 22}, - {3686, 21}, {3681, 19}, - {3676, 18}, {3671, 17}, - {3666, 15}, {3661, 14}, - {3655, 12}, {3648, 11}, - {3640, 9}, {3632, 8}, - {3622, 6}, {3616, 5}, - {3611, 4}, {3604, 2}, - {3594, 1}, {2747, 0} - } -}; - -struct da9052_battery { - struct da9052 *da9052; - struct power_supply *psy; - struct notifier_block nb; - int charger_type; - int status; - int health; -}; - -static inline int volt_reg_to_mV(int value) -{ - return ((value * 1000) / 512) + 2500; -} - -static inline int ichg_reg_to_mA(int value) -{ - return (value * 3900) / 1000; -} - -static int da9052_read_chgend_current(struct da9052_battery *bat, - int *current_mA) -{ - int ret; - - if (bat->status == POWER_SUPPLY_STATUS_DISCHARGING) - return -EINVAL; - - ret = da9052_reg_read(bat->da9052, DA9052_ICHG_END_REG); - if (ret < 0) - return ret; - - *current_mA = ichg_reg_to_mA(ret & DA9052_ICHGEND_ICHGEND); - - return 0; -} - -static int da9052_read_chg_current(struct da9052_battery *bat, int *current_mA) -{ - int ret; - - if (bat->status == POWER_SUPPLY_STATUS_DISCHARGING) - return -EINVAL; - - ret = da9052_reg_read(bat->da9052, DA9052_ICHG_AV_REG); - if (ret < 0) - return ret; - - *current_mA = ichg_reg_to_mA(ret & DA9052_ICHGAV_ICHGAV); - - return 0; -} - -static int da9052_bat_check_status(struct da9052_battery *bat, int *status) -{ - u8 v[2] = {0, 0}; - u8 bat_status; - u8 chg_end; - int ret; - int chg_current; - int chg_end_current; - bool dcinsel; - bool dcindet; - bool vbussel; - bool vbusdet; - bool dc; - bool vbus; - - ret = da9052_group_read(bat->da9052, DA9052_STATUS_A_REG, 2, v); - if (ret < 0) - return ret; - - bat_status = v[0]; - chg_end = v[1]; - - dcinsel = bat_status & DA9052_STATUSA_DCINSEL; - dcindet = bat_status & DA9052_STATUSA_DCINDET; - vbussel = bat_status & DA9052_STATUSA_VBUSSEL; - vbusdet = bat_status & DA9052_STATUSA_VBUSDET; - dc = dcinsel && dcindet; - vbus = vbussel && vbusdet; - - /* Preference to WALL(DCIN) charger unit */ - if (dc || vbus) { - bat->charger_type = DA9052_CHARGER; - - /* If charging end flag is set and Charging current is greater - * than charging end limit then battery is charging - */ - if ((chg_end & DA9052_STATUSB_CHGEND) != 0) { - ret = da9052_read_chg_current(bat, &chg_current); - if (ret < 0) - return ret; - ret = da9052_read_chgend_current(bat, &chg_end_current); - if (ret < 0) - return ret; - - if (chg_current >= chg_end_current) - bat->status = POWER_SUPPLY_STATUS_CHARGING; - else - bat->status = POWER_SUPPLY_STATUS_NOT_CHARGING; - } else { - /* If Charging end flag is cleared then battery is - * charging - */ - bat->status = POWER_SUPPLY_STATUS_CHARGING; - } - } else if (dcindet || vbusdet) { - bat->charger_type = DA9052_CHARGER; - bat->status = POWER_SUPPLY_STATUS_NOT_CHARGING; - } else { - bat->charger_type = DA9052_NOCHARGER; - bat->status = POWER_SUPPLY_STATUS_DISCHARGING; - } - - if (status != NULL) - *status = bat->status; - return 0; -} - -static int da9052_bat_read_volt(struct da9052_battery *bat, int *volt_mV) -{ - int volt; - - volt = da9052_adc_manual_read(bat->da9052, DA9052_ADC_MAN_MUXSEL_VBAT); - if (volt < 0) - return volt; - - *volt_mV = volt_reg_to_mV(volt); - - return 0; -} - -static int da9052_bat_check_presence(struct da9052_battery *bat, int *illegal) -{ - int bat_temp; - - bat_temp = da9052_adc_read_temp(bat->da9052); - if (bat_temp < 0) - return bat_temp; - - if (bat_temp > DA9052_BAT_TSH) - *illegal = 1; - else - *illegal = 0; - - return 0; -} - -static int da9052_bat_interpolate(int vbat_lower, int vbat_upper, - int level_lower, int level_upper, - int bat_voltage) -{ - int tmp; - - tmp = ((level_upper - level_lower) * 1000) / (vbat_upper - vbat_lower); - tmp = level_lower + (((bat_voltage - vbat_lower) * tmp) / 1000); - - return tmp; -} - -static unsigned char da9052_determine_vc_tbl_index(unsigned char adc_temp) -{ - int i; - - if (adc_temp <= vc_tbl_ref[0]) - return 0; - - if (adc_temp > vc_tbl_ref[DA9052_VC_TBL_REF_SZ - 1]) - return DA9052_VC_TBL_REF_SZ - 1; - - for (i = 0; i < DA9052_VC_TBL_REF_SZ - 1; i++) { - if ((adc_temp > vc_tbl_ref[i]) && - (adc_temp <= DA9052_MEAN(vc_tbl_ref[i], vc_tbl_ref[i + 1]))) - return i; - if ((adc_temp > DA9052_MEAN(vc_tbl_ref[i], vc_tbl_ref[i + 1])) - && (adc_temp <= vc_tbl_ref[i])) - return i + 1; - } - /* - * For some reason authors of the driver didn't presume that we can - * end up here. It might be OK, but might be not, no one knows for - * sure. Go check your battery, is it on fire? - */ - WARN_ON(1); - return 0; -} - -static int da9052_bat_read_capacity(struct da9052_battery *bat, int *capacity) -{ - int adc_temp; - int bat_voltage; - int vbat_lower; - int vbat_upper; - int level_upper; - int level_lower; - int ret; - int flag; - int i = 0; - int j; - - ret = da9052_bat_read_volt(bat, &bat_voltage); - if (ret < 0) - return ret; - - adc_temp = da9052_adc_read_temp(bat->da9052); - if (adc_temp < 0) - return adc_temp; - - i = da9052_determine_vc_tbl_index(adc_temp); - - if (bat_voltage >= vc_tbl[i][0][0]) { - *capacity = 100; - return 0; - } - if (bat_voltage <= vc_tbl[i][DA9052_VC_TBL_SZ - 1][0]) { - *capacity = 0; - return 0; - } - flag = 0; - - for (j = 0; j < (DA9052_VC_TBL_SZ-1); j++) { - if ((bat_voltage <= vc_tbl[i][j][0]) && - (bat_voltage >= vc_tbl[i][j + 1][0])) { - vbat_upper = vc_tbl[i][j][0]; - vbat_lower = vc_tbl[i][j + 1][0]; - level_upper = vc_tbl[i][j][1]; - level_lower = vc_tbl[i][j + 1][1]; - flag = 1; - break; - } - } - if (!flag) - return -EIO; - - *capacity = da9052_bat_interpolate(vbat_lower, vbat_upper, level_lower, - level_upper, bat_voltage); - - return 0; -} - -static int da9052_bat_check_health(struct da9052_battery *bat, int *health) -{ - int ret; - int bat_illegal; - int capacity; - - ret = da9052_bat_check_presence(bat, &bat_illegal); - if (ret < 0) - return ret; - - if (bat_illegal) { - bat->health = POWER_SUPPLY_HEALTH_UNKNOWN; - return 0; - } - - if (bat->health != POWER_SUPPLY_HEALTH_OVERHEAT) { - ret = da9052_bat_read_capacity(bat, &capacity); - if (ret < 0) - return ret; - if (capacity < DA9052_BAT_LOW_CAP) - bat->health = POWER_SUPPLY_HEALTH_DEAD; - else - bat->health = POWER_SUPPLY_HEALTH_GOOD; - } - - *health = bat->health; - - return 0; -} - -static irqreturn_t da9052_bat_irq(int irq, void *data) -{ - struct da9052_battery *bat = data; - int virq; - - virq = regmap_irq_get_virq(bat->da9052->irq_data, irq); - irq -= virq; - - if (irq == DA9052_IRQ_CHGEND) - bat->status = POWER_SUPPLY_STATUS_FULL; - else - da9052_bat_check_status(bat, NULL); - - if (irq == DA9052_IRQ_CHGEND || irq == DA9052_IRQ_DCIN || - irq == DA9052_IRQ_VBUS || irq == DA9052_IRQ_TBAT) { - power_supply_changed(bat->psy); - } - - return IRQ_HANDLED; -} - -static int da9052_USB_current_notifier(struct notifier_block *nb, - unsigned long events, void *data) -{ - u8 row; - u8 col; - int *current_mA = data; - int ret; - struct da9052_battery *bat = container_of(nb, struct da9052_battery, - nb); - - if (bat->status == POWER_SUPPLY_STATUS_DISCHARGING) - return -EPERM; - - ret = da9052_reg_read(bat->da9052, DA9052_CHGBUCK_REG); - if (ret & DA9052_CHG_USB_ILIM_MASK) - return -EPERM; - - if (bat->da9052->chip_id == DA9052) - row = 0; - else - row = 1; - - if (*current_mA < da9052_chg_current_lim[row][0] || - *current_mA > da9052_chg_current_lim[row][DA9052_CHG_LIM_COLS - 1]) - return -EINVAL; - - for (col = 0; col <= DA9052_CHG_LIM_COLS - 1 ; col++) { - if (*current_mA <= da9052_chg_current_lim[row][col]) - break; - } - - return da9052_reg_update(bat->da9052, DA9052_ISET_REG, - DA9052_ISET_USB_MASK, col); -} - -static int da9052_bat_get_property(struct power_supply *psy, - enum power_supply_property psp, - union power_supply_propval *val) -{ - int ret; - int illegal; - struct da9052_battery *bat = power_supply_get_drvdata(psy); - - ret = da9052_bat_check_presence(bat, &illegal); - if (ret < 0) - return ret; - - if (illegal && psp != POWER_SUPPLY_PROP_PRESENT) - return -ENODEV; - - switch (psp) { - case POWER_SUPPLY_PROP_STATUS: - ret = da9052_bat_check_status(bat, &val->intval); - break; - case POWER_SUPPLY_PROP_ONLINE: - val->intval = - (bat->charger_type == DA9052_NOCHARGER) ? 0 : 1; - break; - case POWER_SUPPLY_PROP_PRESENT: - ret = da9052_bat_check_presence(bat, &val->intval); - break; - case POWER_SUPPLY_PROP_HEALTH: - ret = da9052_bat_check_health(bat, &val->intval); - break; - case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: - val->intval = DA9052_BAT_CUTOFF_VOLT * 1000; - break; - case POWER_SUPPLY_PROP_VOLTAGE_AVG: - ret = da9052_bat_read_volt(bat, &val->intval); - break; - case POWER_SUPPLY_PROP_CURRENT_AVG: - ret = da9052_read_chg_current(bat, &val->intval); - break; - case POWER_SUPPLY_PROP_CAPACITY: - ret = da9052_bat_read_capacity(bat, &val->intval); - break; - case POWER_SUPPLY_PROP_TEMP: - val->intval = da9052_adc_read_temp(bat->da9052); - ret = val->intval; - break; - case POWER_SUPPLY_PROP_TECHNOLOGY: - val->intval = POWER_SUPPLY_TECHNOLOGY_LION; - break; - default: - return -EINVAL; - } - return ret; -} - -static enum power_supply_property da9052_bat_props[] = { - POWER_SUPPLY_PROP_STATUS, - POWER_SUPPLY_PROP_ONLINE, - POWER_SUPPLY_PROP_PRESENT, - POWER_SUPPLY_PROP_HEALTH, - POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, - POWER_SUPPLY_PROP_VOLTAGE_AVG, - POWER_SUPPLY_PROP_CURRENT_AVG, - POWER_SUPPLY_PROP_CAPACITY, - POWER_SUPPLY_PROP_TEMP, - POWER_SUPPLY_PROP_TECHNOLOGY, -}; - -static struct power_supply_desc psy_desc = { - .name = "da9052-bat", - .type = POWER_SUPPLY_TYPE_BATTERY, - .properties = da9052_bat_props, - .num_properties = ARRAY_SIZE(da9052_bat_props), - .get_property = da9052_bat_get_property, -}; - -static char *da9052_bat_irqs[] = { - "BATT TEMP", - "DCIN DET", - "DCIN REM", - "VBUS DET", - "VBUS REM", - "CHG END", -}; - -static int da9052_bat_irq_bits[] = { - DA9052_IRQ_TBAT, - DA9052_IRQ_DCIN, - DA9052_IRQ_DCINREM, - DA9052_IRQ_VBUS, - DA9052_IRQ_VBUSREM, - DA9052_IRQ_CHGEND, -}; - -static s32 da9052_bat_probe(struct platform_device *pdev) -{ - struct da9052_pdata *pdata; - struct da9052_battery *bat; - struct power_supply_config psy_cfg = {}; - int ret; - int i; - - bat = devm_kzalloc(&pdev->dev, sizeof(struct da9052_battery), - GFP_KERNEL); - if (!bat) - return -ENOMEM; - - psy_cfg.drv_data = bat; - - bat->da9052 = dev_get_drvdata(pdev->dev.parent); - bat->charger_type = DA9052_NOCHARGER; - bat->status = POWER_SUPPLY_STATUS_UNKNOWN; - bat->health = POWER_SUPPLY_HEALTH_UNKNOWN; - bat->nb.notifier_call = da9052_USB_current_notifier; - - pdata = bat->da9052->dev->platform_data; - if (pdata != NULL && pdata->use_for_apm) - psy_desc.use_for_apm = pdata->use_for_apm; - else - psy_desc.use_for_apm = 1; - - for (i = 0; i < ARRAY_SIZE(da9052_bat_irqs); i++) { - ret = da9052_request_irq(bat->da9052, - da9052_bat_irq_bits[i], da9052_bat_irqs[i], - da9052_bat_irq, bat); - - if (ret != 0) { - dev_err(bat->da9052->dev, - "DA9052 failed to request %s IRQ: %d\n", - da9052_bat_irqs[i], ret); - goto err; - } - } - - bat->psy = power_supply_register(&pdev->dev, &psy_desc, &psy_cfg); - if (IS_ERR(bat->psy)) { - ret = PTR_ERR(bat->psy); - goto err; - } - - platform_set_drvdata(pdev, bat); - return 0; - -err: - while (--i >= 0) - da9052_free_irq(bat->da9052, da9052_bat_irq_bits[i], bat); - - return ret; -} -static int da9052_bat_remove(struct platform_device *pdev) -{ - int i; - struct da9052_battery *bat = platform_get_drvdata(pdev); - - for (i = 0; i < ARRAY_SIZE(da9052_bat_irqs); i++) - da9052_free_irq(bat->da9052, da9052_bat_irq_bits[i], bat); - - power_supply_unregister(bat->psy); - - return 0; -} - -static struct platform_driver da9052_bat_driver = { - .probe = da9052_bat_probe, - .remove = da9052_bat_remove, - .driver = { - .name = "da9052-bat", - }, -}; -module_platform_driver(da9052_bat_driver); - -MODULE_DESCRIPTION("DA9052 BAT Device Driver"); -MODULE_AUTHOR("David Dajun Chen "); -MODULE_LICENSE("GPL"); -MODULE_ALIAS("platform:da9052-bat"); diff --git a/drivers/power/da9150-charger.c b/drivers/power/da9150-charger.c deleted file mode 100644 index 60099815296e..000000000000 --- a/drivers/power/da9150-charger.c +++ /dev/null @@ -1,694 +0,0 @@ -/* - * DA9150 Charger Driver - * - * Copyright (c) 2014 Dialog Semiconductor - * - * Author: Adam Thomson - * - * 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/* Private data */ -struct da9150_charger { - struct da9150 *da9150; - struct device *dev; - - struct power_supply *usb; - struct power_supply *battery; - struct power_supply *supply_online; - - struct usb_phy *usb_phy; - struct notifier_block otg_nb; - struct work_struct otg_work; - unsigned long usb_event; - - struct iio_channel *ibus_chan; - struct iio_channel *vbus_chan; - struct iio_channel *tjunc_chan; - struct iio_channel *vbat_chan; -}; - -static inline int da9150_charger_supply_online(struct da9150_charger *charger, - struct power_supply *psy, - union power_supply_propval *val) -{ - val->intval = (psy == charger->supply_online) ? 1 : 0; - - return 0; -} - -/* Charger Properties */ -static int da9150_charger_vbus_voltage_now(struct da9150_charger *charger, - union power_supply_propval *val) -{ - int v_val, ret; - - /* Read processed value - mV units */ - ret = iio_read_channel_processed(charger->vbus_chan, &v_val); - if (ret < 0) - return ret; - - /* Convert voltage to expected uV units */ - val->intval = v_val * 1000; - - return 0; -} - -static int da9150_charger_ibus_current_avg(struct da9150_charger *charger, - union power_supply_propval *val) -{ - int i_val, ret; - - /* Read processed value - mA units */ - ret = iio_read_channel_processed(charger->ibus_chan, &i_val); - if (ret < 0) - return ret; - - /* Convert current to expected uA units */ - val->intval = i_val * 1000; - - return 0; -} - -static int da9150_charger_tjunc_temp(struct da9150_charger *charger, - union power_supply_propval *val) -{ - int t_val, ret; - - /* Read processed value - 0.001 degrees C units */ - ret = iio_read_channel_processed(charger->tjunc_chan, &t_val); - if (ret < 0) - return ret; - - /* Convert temp to expect 0.1 degrees C units */ - val->intval = t_val / 100; - - return 0; -} - -static enum power_supply_property da9150_charger_props[] = { - POWER_SUPPLY_PROP_ONLINE, - POWER_SUPPLY_PROP_VOLTAGE_NOW, - POWER_SUPPLY_PROP_CURRENT_AVG, - POWER_SUPPLY_PROP_TEMP, -}; - -static int da9150_charger_get_prop(struct power_supply *psy, - enum power_supply_property psp, - union power_supply_propval *val) -{ - struct da9150_charger *charger = dev_get_drvdata(psy->dev.parent); - int ret; - - switch (psp) { - case POWER_SUPPLY_PROP_ONLINE: - ret = da9150_charger_supply_online(charger, psy, val); - break; - case POWER_SUPPLY_PROP_VOLTAGE_NOW: - ret = da9150_charger_vbus_voltage_now(charger, val); - break; - case POWER_SUPPLY_PROP_CURRENT_AVG: - ret = da9150_charger_ibus_current_avg(charger, val); - break; - case POWER_SUPPLY_PROP_TEMP: - ret = da9150_charger_tjunc_temp(charger, val); - break; - default: - ret = -EINVAL; - break; - } - - return ret; -} - -/* Battery Properties */ -static int da9150_charger_battery_status(struct da9150_charger *charger, - union power_supply_propval *val) -{ - u8 reg; - - /* Check to see if battery is discharging */ - reg = da9150_reg_read(charger->da9150, DA9150_STATUS_H); - - if (((reg & DA9150_VBUS_STAT_MASK) == DA9150_VBUS_STAT_OFF) || - ((reg & DA9150_VBUS_STAT_MASK) == DA9150_VBUS_STAT_WAIT)) { - val->intval = POWER_SUPPLY_STATUS_DISCHARGING; - - return 0; - } - - reg = da9150_reg_read(charger->da9150, DA9150_STATUS_J); - - /* Now check for other states */ - switch (reg & DA9150_CHG_STAT_MASK) { - case DA9150_CHG_STAT_ACT: - case DA9150_CHG_STAT_PRE: - case DA9150_CHG_STAT_CC: - case DA9150_CHG_STAT_CV: - val->intval = POWER_SUPPLY_STATUS_CHARGING; - break; - case DA9150_CHG_STAT_OFF: - case DA9150_CHG_STAT_SUSP: - case DA9150_CHG_STAT_TEMP: - case DA9150_CHG_STAT_TIME: - case DA9150_CHG_STAT_BAT: - val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; - break; - case DA9150_CHG_STAT_FULL: - val->intval = POWER_SUPPLY_STATUS_FULL; - break; - default: - val->intval = POWER_SUPPLY_STATUS_UNKNOWN; - break; - } - - return 0; -} - -static int da9150_charger_battery_health(struct da9150_charger *charger, - union power_supply_propval *val) -{ - u8 reg; - - reg = da9150_reg_read(charger->da9150, DA9150_STATUS_J); - - /* Check if temperature limit reached */ - switch (reg & DA9150_CHG_TEMP_MASK) { - case DA9150_CHG_TEMP_UNDER: - val->intval = POWER_SUPPLY_HEALTH_COLD; - return 0; - case DA9150_CHG_TEMP_OVER: - val->intval = POWER_SUPPLY_HEALTH_OVERHEAT; - return 0; - default: - break; - } - - /* Check for other health states */ - switch (reg & DA9150_CHG_STAT_MASK) { - case DA9150_CHG_STAT_ACT: - case DA9150_CHG_STAT_PRE: - val->intval = POWER_SUPPLY_HEALTH_DEAD; - break; - case DA9150_CHG_STAT_TIME: - val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; - break; - default: - val->intval = POWER_SUPPLY_HEALTH_GOOD; - break; - } - - return 0; -} - -static int da9150_charger_battery_present(struct da9150_charger *charger, - union power_supply_propval *val) -{ - u8 reg; - - /* Check if battery present or removed */ - reg = da9150_reg_read(charger->da9150, DA9150_STATUS_J); - if ((reg & DA9150_CHG_STAT_MASK) == DA9150_CHG_STAT_BAT) - val->intval = 0; - else - val->intval = 1; - - return 0; -} - -static int da9150_charger_battery_charge_type(struct da9150_charger *charger, - union power_supply_propval *val) -{ - u8 reg; - - reg = da9150_reg_read(charger->da9150, DA9150_STATUS_J); - - switch (reg & DA9150_CHG_STAT_MASK) { - case DA9150_CHG_STAT_CC: - val->intval = POWER_SUPPLY_CHARGE_TYPE_FAST; - break; - case DA9150_CHG_STAT_ACT: - case DA9150_CHG_STAT_PRE: - case DA9150_CHG_STAT_CV: - val->intval = POWER_SUPPLY_CHARGE_TYPE_TRICKLE; - break; - default: - val->intval = POWER_SUPPLY_CHARGE_TYPE_NONE; - break; - } - - return 0; -} - -static int da9150_charger_battery_voltage_min(struct da9150_charger *charger, - union power_supply_propval *val) -{ - u8 reg; - - reg = da9150_reg_read(charger->da9150, DA9150_PPR_CHGCTRL_C); - - /* Value starts at 2500 mV, 50 mV increments, presented in uV */ - val->intval = ((reg & DA9150_CHG_VFAULT_MASK) * 50000) + 2500000; - - return 0; -} - -static int da9150_charger_battery_voltage_now(struct da9150_charger *charger, - union power_supply_propval *val) -{ - int v_val, ret; - - /* Read processed value - mV units */ - ret = iio_read_channel_processed(charger->vbat_chan, &v_val); - if (ret < 0) - return ret; - - val->intval = v_val * 1000; - - return 0; -} - -static int da9150_charger_battery_current_max(struct da9150_charger *charger, - union power_supply_propval *val) -{ - int reg; - - reg = da9150_reg_read(charger->da9150, DA9150_PPR_CHGCTRL_D); - - /* 25mA increments */ - val->intval = reg * 25000; - - return 0; -} - -static int da9150_charger_battery_voltage_max(struct da9150_charger *charger, - union power_supply_propval *val) -{ - u8 reg; - - reg = da9150_reg_read(charger->da9150, DA9150_PPR_CHGCTRL_B); - - /* Value starts at 3650 mV, 25 mV increments, presented in uV */ - val->intval = ((reg & DA9150_CHG_VBAT_MASK) * 25000) + 3650000; - return 0; -} - -static enum power_supply_property da9150_charger_bat_props[] = { - POWER_SUPPLY_PROP_STATUS, - POWER_SUPPLY_PROP_ONLINE, - POWER_SUPPLY_PROP_HEALTH, - POWER_SUPPLY_PROP_PRESENT, - POWER_SUPPLY_PROP_CHARGE_TYPE, - POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, - POWER_SUPPLY_PROP_VOLTAGE_NOW, - POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX, - POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX, -}; - -static int da9150_charger_battery_get_prop(struct power_supply *psy, - enum power_supply_property psp, - union power_supply_propval *val) -{ - struct da9150_charger *charger = dev_get_drvdata(psy->dev.parent); - int ret; - - switch (psp) { - case POWER_SUPPLY_PROP_STATUS: - ret = da9150_charger_battery_status(charger, val); - break; - case POWER_SUPPLY_PROP_ONLINE: - ret = da9150_charger_supply_online(charger, psy, val); - break; - case POWER_SUPPLY_PROP_HEALTH: - ret = da9150_charger_battery_health(charger, val); - break; - case POWER_SUPPLY_PROP_PRESENT: - ret = da9150_charger_battery_present(charger, val); - break; - case POWER_SUPPLY_PROP_CHARGE_TYPE: - ret = da9150_charger_battery_charge_type(charger, val); - break; - case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: - ret = da9150_charger_battery_voltage_min(charger, val); - break; - case POWER_SUPPLY_PROP_VOLTAGE_NOW: - ret = da9150_charger_battery_voltage_now(charger, val); - break; - case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX: - ret = da9150_charger_battery_current_max(charger, val); - break; - case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX: - ret = da9150_charger_battery_voltage_max(charger, val); - break; - default: - ret = -EINVAL; - break; - } - - return ret; -} - -static irqreturn_t da9150_charger_chg_irq(int irq, void *data) -{ - struct da9150_charger *charger = data; - - power_supply_changed(charger->battery); - - return IRQ_HANDLED; -} - -static irqreturn_t da9150_charger_tjunc_irq(int irq, void *data) -{ - struct da9150_charger *charger = data; - - /* Nothing we can really do except report this. */ - dev_crit(charger->dev, "TJunc over temperature!!!\n"); - power_supply_changed(charger->usb); - - return IRQ_HANDLED; -} - -static irqreturn_t da9150_charger_vfault_irq(int irq, void *data) -{ - struct da9150_charger *charger = data; - - /* Nothing we can really do except report this. */ - dev_crit(charger->dev, "VSYS under voltage!!!\n"); - power_supply_changed(charger->usb); - power_supply_changed(charger->battery); - - return IRQ_HANDLED; -} - -static irqreturn_t da9150_charger_vbus_irq(int irq, void *data) -{ - struct da9150_charger *charger = data; - u8 reg; - - reg = da9150_reg_read(charger->da9150, DA9150_STATUS_H); - - /* Charger plugged in or battery only */ - switch (reg & DA9150_VBUS_STAT_MASK) { - case DA9150_VBUS_STAT_OFF: - case DA9150_VBUS_STAT_WAIT: - charger->supply_online = charger->battery; - break; - case DA9150_VBUS_STAT_CHG: - charger->supply_online = charger->usb; - break; - default: - dev_warn(charger->dev, "Unknown VBUS state - reg = 0x%x\n", - reg); - charger->supply_online = NULL; - break; - } - - power_supply_changed(charger->usb); - power_supply_changed(charger->battery); - - return IRQ_HANDLED; -} - -static void da9150_charger_otg_work(struct work_struct *data) -{ - struct da9150_charger *charger = - container_of(data, struct da9150_charger, otg_work); - - switch (charger->usb_event) { - case USB_EVENT_ID: - /* Enable OTG Boost */ - da9150_set_bits(charger->da9150, DA9150_PPR_BKCTRL_A, - DA9150_VBUS_MODE_MASK, DA9150_VBUS_MODE_OTG); - break; - case USB_EVENT_NONE: - /* Revert to charge mode */ - power_supply_changed(charger->usb); - power_supply_changed(charger->battery); - da9150_set_bits(charger->da9150, DA9150_PPR_BKCTRL_A, - DA9150_VBUS_MODE_MASK, DA9150_VBUS_MODE_CHG); - break; - } -} - -static int da9150_charger_otg_ncb(struct notifier_block *nb, unsigned long val, - void *priv) -{ - struct da9150_charger *charger = - container_of(nb, struct da9150_charger, otg_nb); - - dev_dbg(charger->dev, "DA9150 OTG notify %lu\n", val); - - charger->usb_event = val; - schedule_work(&charger->otg_work); - - return NOTIFY_OK; -} - -static int da9150_charger_register_irq(struct platform_device *pdev, - irq_handler_t handler, - const char *irq_name) -{ - struct device *dev = &pdev->dev; - struct da9150_charger *charger = platform_get_drvdata(pdev); - int irq, ret; - - irq = platform_get_irq_byname(pdev, irq_name); - if (irq < 0) { - dev_err(dev, "Failed to get IRQ CHG_STATUS: %d\n", irq); - return irq; - } - - ret = request_threaded_irq(irq, NULL, handler, IRQF_ONESHOT, irq_name, - charger); - if (ret) - dev_err(dev, "Failed to request IRQ %d: %d\n", irq, ret); - - return ret; -} - -static void da9150_charger_unregister_irq(struct platform_device *pdev, - const char *irq_name) -{ - struct device *dev = &pdev->dev; - struct da9150_charger *charger = platform_get_drvdata(pdev); - int irq; - - irq = platform_get_irq_byname(pdev, irq_name); - if (irq < 0) { - dev_err(dev, "Failed to get IRQ CHG_STATUS: %d\n", irq); - return; - } - - free_irq(irq, charger); -} - -static const struct power_supply_desc usb_desc = { - .name = "da9150-usb", - .type = POWER_SUPPLY_TYPE_USB, - .properties = da9150_charger_props, - .num_properties = ARRAY_SIZE(da9150_charger_props), - .get_property = da9150_charger_get_prop, -}; - -static const struct power_supply_desc battery_desc = { - .name = "da9150-battery", - .type = POWER_SUPPLY_TYPE_BATTERY, - .properties = da9150_charger_bat_props, - .num_properties = ARRAY_SIZE(da9150_charger_bat_props), - .get_property = da9150_charger_battery_get_prop, -}; - -static int da9150_charger_probe(struct platform_device *pdev) -{ - struct device *dev = &pdev->dev; - struct da9150 *da9150 = dev_get_drvdata(dev->parent); - struct da9150_charger *charger; - u8 reg; - int ret; - - charger = devm_kzalloc(dev, sizeof(struct da9150_charger), GFP_KERNEL); - if (!charger) - return -ENOMEM; - - platform_set_drvdata(pdev, charger); - charger->da9150 = da9150; - charger->dev = dev; - - /* Acquire ADC channels */ - charger->ibus_chan = iio_channel_get(dev, "CHAN_IBUS"); - if (IS_ERR(charger->ibus_chan)) { - ret = PTR_ERR(charger->ibus_chan); - goto ibus_chan_fail; - } - - charger->vbus_chan = iio_channel_get(dev, "CHAN_VBUS"); - if (IS_ERR(charger->vbus_chan)) { - ret = PTR_ERR(charger->vbus_chan); - goto vbus_chan_fail; - } - - charger->tjunc_chan = iio_channel_get(dev, "CHAN_TJUNC"); - if (IS_ERR(charger->tjunc_chan)) { - ret = PTR_ERR(charger->tjunc_chan); - goto tjunc_chan_fail; - } - - charger->vbat_chan = iio_channel_get(dev, "CHAN_VBAT"); - if (IS_ERR(charger->vbat_chan)) { - ret = PTR_ERR(charger->vbat_chan); - goto vbat_chan_fail; - } - - /* Register power supplies */ - charger->usb = power_supply_register(dev, &usb_desc, NULL); - if (IS_ERR(charger->usb)) { - ret = PTR_ERR(charger->usb); - goto usb_fail; - } - - charger->battery = power_supply_register(dev, &battery_desc, NULL); - if (IS_ERR(charger->battery)) { - ret = PTR_ERR(charger->battery); - goto battery_fail; - } - - /* Get initial online supply */ - reg = da9150_reg_read(da9150, DA9150_STATUS_H); - - switch (reg & DA9150_VBUS_STAT_MASK) { - case DA9150_VBUS_STAT_OFF: - case DA9150_VBUS_STAT_WAIT: - charger->supply_online = charger->battery; - break; - case DA9150_VBUS_STAT_CHG: - charger->supply_online = charger->usb; - break; - default: - dev_warn(dev, "Unknown VBUS state - reg = 0x%x\n", reg); - charger->supply_online = NULL; - break; - } - - /* Setup OTG reporting & configuration */ - charger->usb_phy = devm_usb_get_phy(dev, USB_PHY_TYPE_USB2); - if (!IS_ERR_OR_NULL(charger->usb_phy)) { - INIT_WORK(&charger->otg_work, da9150_charger_otg_work); - charger->otg_nb.notifier_call = da9150_charger_otg_ncb; - usb_register_notifier(charger->usb_phy, &charger->otg_nb); - } - - /* Register IRQs */ - ret = da9150_charger_register_irq(pdev, da9150_charger_chg_irq, - "CHG_STATUS"); - if (ret < 0) - goto chg_irq_fail; - - ret = da9150_charger_register_irq(pdev, da9150_charger_tjunc_irq, - "CHG_TJUNC"); - if (ret < 0) - goto tjunc_irq_fail; - - ret = da9150_charger_register_irq(pdev, da9150_charger_vfault_irq, - "CHG_VFAULT"); - if (ret < 0) - goto vfault_irq_fail; - - ret = da9150_charger_register_irq(pdev, da9150_charger_vbus_irq, - "CHG_VBUS"); - if (ret < 0) - goto vbus_irq_fail; - - return 0; - - -vbus_irq_fail: - da9150_charger_unregister_irq(pdev, "CHG_VFAULT"); -vfault_irq_fail: - da9150_charger_unregister_irq(pdev, "CHG_TJUNC"); -tjunc_irq_fail: - da9150_charger_unregister_irq(pdev, "CHG_STATUS"); -chg_irq_fail: - if (!IS_ERR_OR_NULL(charger->usb_phy)) - usb_unregister_notifier(charger->usb_phy, &charger->otg_nb); -battery_fail: - power_supply_unregister(charger->usb); - -usb_fail: - iio_channel_release(charger->vbat_chan); - -vbat_chan_fail: - iio_channel_release(charger->tjunc_chan); - -tjunc_chan_fail: - iio_channel_release(charger->vbus_chan); - -vbus_chan_fail: - iio_channel_release(charger->ibus_chan); - -ibus_chan_fail: - return ret; -} - -static int da9150_charger_remove(struct platform_device *pdev) -{ - struct da9150_charger *charger = platform_get_drvdata(pdev); - int irq; - - /* Make sure IRQs are released before unregistering power supplies */ - irq = platform_get_irq_byname(pdev, "CHG_VBUS"); - free_irq(irq, charger); - - irq = platform_get_irq_byname(pdev, "CHG_VFAULT"); - free_irq(irq, charger); - - irq = platform_get_irq_byname(pdev, "CHG_TJUNC"); - free_irq(irq, charger); - - irq = platform_get_irq_byname(pdev, "CHG_STATUS"); - free_irq(irq, charger); - - if (!IS_ERR_OR_NULL(charger->usb_phy)) - usb_unregister_notifier(charger->usb_phy, &charger->otg_nb); - - power_supply_unregister(charger->battery); - power_supply_unregister(charger->usb); - - /* Release ADC channels */ - iio_channel_release(charger->ibus_chan); - iio_channel_release(charger->vbus_chan); - iio_channel_release(charger->tjunc_chan); - iio_channel_release(charger->vbat_chan); - - return 0; -} - -static struct platform_driver da9150_charger_driver = { - .driver = { - .name = "da9150-charger", - }, - .probe = da9150_charger_probe, - .remove = da9150_charger_remove, -}; - -module_platform_driver(da9150_charger_driver); - -MODULE_DESCRIPTION("Charger Driver for DA9150"); -MODULE_AUTHOR("Adam Thomson "); -MODULE_LICENSE("GPL"); diff --git a/drivers/power/da9150-fg.c b/drivers/power/da9150-fg.c deleted file mode 100644 index 8b8ce978656a..000000000000 --- a/drivers/power/da9150-fg.c +++ /dev/null @@ -1,579 +0,0 @@ -/* - * DA9150 Fuel-Gauge Driver - * - * Copyright (c) 2015 Dialog Semiconductor - * - * Author: Adam Thomson - * - * 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/* Core2Wire */ -#define DA9150_QIF_READ (0x0 << 7) -#define DA9150_QIF_WRITE (0x1 << 7) -#define DA9150_QIF_CODE_MASK 0x7F - -#define DA9150_QIF_BYTE_SIZE 8 -#define DA9150_QIF_BYTE_MASK 0xFF -#define DA9150_QIF_SHORT_SIZE 2 -#define DA9150_QIF_LONG_SIZE 4 - -/* QIF Codes */ -#define DA9150_QIF_UAVG 6 -#define DA9150_QIF_UAVG_SIZE DA9150_QIF_LONG_SIZE -#define DA9150_QIF_IAVG 8 -#define DA9150_QIF_IAVG_SIZE DA9150_QIF_LONG_SIZE -#define DA9150_QIF_NTCAVG 12 -#define DA9150_QIF_NTCAVG_SIZE DA9150_QIF_LONG_SIZE -#define DA9150_QIF_SHUNT_VAL 36 -#define DA9150_QIF_SHUNT_VAL_SIZE DA9150_QIF_SHORT_SIZE -#define DA9150_QIF_SD_GAIN 38 -#define DA9150_QIF_SD_GAIN_SIZE DA9150_QIF_LONG_SIZE -#define DA9150_QIF_FCC_MAH 40 -#define DA9150_QIF_FCC_MAH_SIZE DA9150_QIF_SHORT_SIZE -#define DA9150_QIF_SOC_PCT 43 -#define DA9150_QIF_SOC_PCT_SIZE DA9150_QIF_SHORT_SIZE -#define DA9150_QIF_CHARGE_LIMIT 44 -#define DA9150_QIF_CHARGE_LIMIT_SIZE DA9150_QIF_SHORT_SIZE -#define DA9150_QIF_DISCHARGE_LIMIT 45 -#define DA9150_QIF_DISCHARGE_LIMIT_SIZE DA9150_QIF_SHORT_SIZE -#define DA9150_QIF_FW_MAIN_VER 118 -#define DA9150_QIF_FW_MAIN_VER_SIZE DA9150_QIF_SHORT_SIZE -#define DA9150_QIF_E_FG_STATUS 126 -#define DA9150_QIF_E_FG_STATUS_SIZE DA9150_QIF_SHORT_SIZE -#define DA9150_QIF_SYNC 127 -#define DA9150_QIF_SYNC_SIZE DA9150_QIF_SHORT_SIZE -#define DA9150_QIF_MAX_CODES 128 - -/* QIF Sync Timeout */ -#define DA9150_QIF_SYNC_TIMEOUT 1000 -#define DA9150_QIF_SYNC_RETRIES 10 - -/* QIF E_FG_STATUS */ -#define DA9150_FG_IRQ_LOW_SOC_MASK (1 << 0) -#define DA9150_FG_IRQ_HIGH_SOC_MASK (1 << 1) -#define DA9150_FG_IRQ_SOC_MASK \ - (DA9150_FG_IRQ_LOW_SOC_MASK | DA9150_FG_IRQ_HIGH_SOC_MASK) - -/* Private data */ -struct da9150_fg { - struct da9150 *da9150; - struct device *dev; - - struct mutex io_lock; - - struct power_supply *battery; - struct delayed_work work; - u32 interval; - - int warn_soc; - int crit_soc; - int soc; -}; - -/* Battery Properties */ -static u32 da9150_fg_read_attr(struct da9150_fg *fg, u8 code, u8 size) - -{ - u8 buf[size]; - u8 read_addr; - u32 res = 0; - int i; - - /* Set QIF code (READ mode) */ - read_addr = (code & DA9150_QIF_CODE_MASK) | DA9150_QIF_READ; - - da9150_read_qif(fg->da9150, read_addr, size, buf); - for (i = 0; i < size; ++i) - res |= (buf[i] << (i * DA9150_QIF_BYTE_SIZE)); - - return res; -} - -static void da9150_fg_write_attr(struct da9150_fg *fg, u8 code, u8 size, - u32 val) - -{ - u8 buf[size]; - u8 write_addr; - int i; - - /* Set QIF code (WRITE mode) */ - write_addr = (code & DA9150_QIF_CODE_MASK) | DA9150_QIF_WRITE; - - for (i = 0; i < size; ++i) { - buf[i] = (val >> (i * DA9150_QIF_BYTE_SIZE)) & - DA9150_QIF_BYTE_MASK; - } - da9150_write_qif(fg->da9150, write_addr, size, buf); -} - -/* Trigger QIF Sync to update QIF readable data */ -static void da9150_fg_read_sync_start(struct da9150_fg *fg) -{ - int i = 0; - u32 res = 0; - - mutex_lock(&fg->io_lock); - - /* Check if QIF sync already requested, and write to sync if not */ - res = da9150_fg_read_attr(fg, DA9150_QIF_SYNC, - DA9150_QIF_SYNC_SIZE); - if (res > 0) - da9150_fg_write_attr(fg, DA9150_QIF_SYNC, - DA9150_QIF_SYNC_SIZE, 0); - - /* Wait for sync to complete */ - res = 0; - while ((res == 0) && (i++ < DA9150_QIF_SYNC_RETRIES)) { - usleep_range(DA9150_QIF_SYNC_TIMEOUT, - DA9150_QIF_SYNC_TIMEOUT * 2); - res = da9150_fg_read_attr(fg, DA9150_QIF_SYNC, - DA9150_QIF_SYNC_SIZE); - } - - /* Check if sync completed */ - if (res == 0) - dev_err(fg->dev, "Failed to perform QIF read sync!\n"); -} - -/* - * Should always be called after QIF sync read has been performed, and all - * attributes required have been accessed. - */ -static inline void da9150_fg_read_sync_end(struct da9150_fg *fg) -{ - mutex_unlock(&fg->io_lock); -} - -/* Sync read of single QIF attribute */ -static u32 da9150_fg_read_attr_sync(struct da9150_fg *fg, u8 code, u8 size) -{ - u32 val; - - da9150_fg_read_sync_start(fg); - val = da9150_fg_read_attr(fg, code, size); - da9150_fg_read_sync_end(fg); - - return val; -} - -/* Wait for QIF Sync, write QIF data and wait for ack */ -static void da9150_fg_write_attr_sync(struct da9150_fg *fg, u8 code, u8 size, - u32 val) -{ - int i = 0; - u32 res = 0, sync_val; - - mutex_lock(&fg->io_lock); - - /* Check if QIF sync already requested */ - res = da9150_fg_read_attr(fg, DA9150_QIF_SYNC, - DA9150_QIF_SYNC_SIZE); - - /* Wait for an existing sync to complete */ - while ((res == 0) && (i++ < DA9150_QIF_SYNC_RETRIES)) { - usleep_range(DA9150_QIF_SYNC_TIMEOUT, - DA9150_QIF_SYNC_TIMEOUT * 2); - res = da9150_fg_read_attr(fg, DA9150_QIF_SYNC, - DA9150_QIF_SYNC_SIZE); - } - - if (res == 0) { - dev_err(fg->dev, "Timeout waiting for existing QIF sync!\n"); - mutex_unlock(&fg->io_lock); - return; - } - - /* Write value for QIF code */ - da9150_fg_write_attr(fg, code, size, val); - - /* Wait for write acknowledgment */ - i = 0; - sync_val = res; - while ((res == sync_val) && (i++ < DA9150_QIF_SYNC_RETRIES)) { - usleep_range(DA9150_QIF_SYNC_TIMEOUT, - DA9150_QIF_SYNC_TIMEOUT * 2); - res = da9150_fg_read_attr(fg, DA9150_QIF_SYNC, - DA9150_QIF_SYNC_SIZE); - } - - mutex_unlock(&fg->io_lock); - - /* Check write was actually successful */ - if (res != (sync_val + 1)) - dev_err(fg->dev, "Error performing QIF sync write for code %d\n", - code); -} - -/* Power Supply attributes */ -static int da9150_fg_capacity(struct da9150_fg *fg, - union power_supply_propval *val) -{ - val->intval = da9150_fg_read_attr_sync(fg, DA9150_QIF_SOC_PCT, - DA9150_QIF_SOC_PCT_SIZE); - - if (val->intval > 100) - val->intval = 100; - - return 0; -} - -static int da9150_fg_current_avg(struct da9150_fg *fg, - union power_supply_propval *val) -{ - u32 iavg, sd_gain, shunt_val; - u64 div, res; - - da9150_fg_read_sync_start(fg); - iavg = da9150_fg_read_attr(fg, DA9150_QIF_IAVG, - DA9150_QIF_IAVG_SIZE); - shunt_val = da9150_fg_read_attr(fg, DA9150_QIF_SHUNT_VAL, - DA9150_QIF_SHUNT_VAL_SIZE); - sd_gain = da9150_fg_read_attr(fg, DA9150_QIF_SD_GAIN, - DA9150_QIF_SD_GAIN_SIZE); - da9150_fg_read_sync_end(fg); - - div = (u64) (sd_gain * shunt_val * 65536ULL); - do_div(div, 1000000); - res = (u64) (iavg * 1000000ULL); - do_div(res, div); - - val->intval = (int) res; - - return 0; -} - -static int da9150_fg_voltage_avg(struct da9150_fg *fg, - union power_supply_propval *val) -{ - u64 res; - - val->intval = da9150_fg_read_attr_sync(fg, DA9150_QIF_UAVG, - DA9150_QIF_UAVG_SIZE); - - res = (u64) (val->intval * 186ULL); - do_div(res, 10000); - val->intval = (int) res; - - return 0; -} - -static int da9150_fg_charge_full(struct da9150_fg *fg, - union power_supply_propval *val) -{ - val->intval = da9150_fg_read_attr_sync(fg, DA9150_QIF_FCC_MAH, - DA9150_QIF_FCC_MAH_SIZE); - - val->intval = val->intval * 1000; - - return 0; -} - -/* - * Temperature reading from device is only valid if battery/system provides - * valid NTC to associated pin of DA9150 chip. - */ -static int da9150_fg_temp(struct da9150_fg *fg, - union power_supply_propval *val) -{ - val->intval = da9150_fg_read_attr_sync(fg, DA9150_QIF_NTCAVG, - DA9150_QIF_NTCAVG_SIZE); - - val->intval = (val->intval * 10) / 1048576; - - return 0; -} - -static enum power_supply_property da9150_fg_props[] = { - POWER_SUPPLY_PROP_CAPACITY, - POWER_SUPPLY_PROP_CURRENT_AVG, - POWER_SUPPLY_PROP_VOLTAGE_AVG, - POWER_SUPPLY_PROP_CHARGE_FULL, - POWER_SUPPLY_PROP_TEMP, -}; - -static int da9150_fg_get_prop(struct power_supply *psy, - enum power_supply_property psp, - union power_supply_propval *val) -{ - struct da9150_fg *fg = dev_get_drvdata(psy->dev.parent); - int ret; - - switch (psp) { - case POWER_SUPPLY_PROP_CAPACITY: - ret = da9150_fg_capacity(fg, val); - break; - case POWER_SUPPLY_PROP_CURRENT_AVG: - ret = da9150_fg_current_avg(fg, val); - break; - case POWER_SUPPLY_PROP_VOLTAGE_AVG: - ret = da9150_fg_voltage_avg(fg, val); - break; - case POWER_SUPPLY_PROP_CHARGE_FULL: - ret = da9150_fg_charge_full(fg, val); - break; - case POWER_SUPPLY_PROP_TEMP: - ret = da9150_fg_temp(fg, val); - break; - default: - ret = -EINVAL; - break; - } - - return ret; -} - -/* Repeated SOC check */ -static bool da9150_fg_soc_changed(struct da9150_fg *fg) -{ - union power_supply_propval val; - - da9150_fg_capacity(fg, &val); - if (val.intval != fg->soc) { - fg->soc = val.intval; - return true; - } - - return false; -} - -static void da9150_fg_work(struct work_struct *work) -{ - struct da9150_fg *fg = container_of(work, struct da9150_fg, work.work); - - /* Report if SOC has changed */ - if (da9150_fg_soc_changed(fg)) - power_supply_changed(fg->battery); - - schedule_delayed_work(&fg->work, msecs_to_jiffies(fg->interval)); -} - -/* SOC level event configuration */ -static void da9150_fg_soc_event_config(struct da9150_fg *fg) -{ - int soc; - - soc = da9150_fg_read_attr_sync(fg, DA9150_QIF_SOC_PCT, - DA9150_QIF_SOC_PCT_SIZE); - - if (soc > fg->warn_soc) { - /* If SOC > warn level, set discharge warn level event */ - da9150_fg_write_attr_sync(fg, DA9150_QIF_DISCHARGE_LIMIT, - DA9150_QIF_DISCHARGE_LIMIT_SIZE, - fg->warn_soc + 1); - } else if ((soc <= fg->warn_soc) && (soc > fg->crit_soc)) { - /* - * If SOC <= warn level, set discharge crit level event, - * and set charge warn level event. - */ - da9150_fg_write_attr_sync(fg, DA9150_QIF_DISCHARGE_LIMIT, - DA9150_QIF_DISCHARGE_LIMIT_SIZE, - fg->crit_soc + 1); - - da9150_fg_write_attr_sync(fg, DA9150_QIF_CHARGE_LIMIT, - DA9150_QIF_CHARGE_LIMIT_SIZE, - fg->warn_soc); - } else if (soc <= fg->crit_soc) { - /* If SOC <= crit level, set charge crit level event */ - da9150_fg_write_attr_sync(fg, DA9150_QIF_CHARGE_LIMIT, - DA9150_QIF_CHARGE_LIMIT_SIZE, - fg->crit_soc); - } -} - -static irqreturn_t da9150_fg_irq(int irq, void *data) -{ - struct da9150_fg *fg = data; - u32 e_fg_status; - - /* Read FG IRQ status info */ - e_fg_status = da9150_fg_read_attr(fg, DA9150_QIF_E_FG_STATUS, - DA9150_QIF_E_FG_STATUS_SIZE); - - /* Handle warning/critical threhold events */ - if (e_fg_status & DA9150_FG_IRQ_SOC_MASK) - da9150_fg_soc_event_config(fg); - - /* Clear any FG IRQs */ - da9150_fg_write_attr(fg, DA9150_QIF_E_FG_STATUS, - DA9150_QIF_E_FG_STATUS_SIZE, e_fg_status); - - return IRQ_HANDLED; -} - -static struct da9150_fg_pdata *da9150_fg_dt_pdata(struct device *dev) -{ - struct device_node *fg_node = dev->of_node; - struct da9150_fg_pdata *pdata; - - pdata = devm_kzalloc(dev, sizeof(struct da9150_fg_pdata), GFP_KERNEL); - if (!pdata) - return NULL; - - of_property_read_u32(fg_node, "dlg,update-interval", - &pdata->update_interval); - of_property_read_u8(fg_node, "dlg,warn-soc-level", - &pdata->warn_soc_lvl); - of_property_read_u8(fg_node, "dlg,crit-soc-level", - &pdata->crit_soc_lvl); - - return pdata; -} - -static const struct power_supply_desc fg_desc = { - .name = "da9150-fg", - .type = POWER_SUPPLY_TYPE_BATTERY, - .properties = da9150_fg_props, - .num_properties = ARRAY_SIZE(da9150_fg_props), - .get_property = da9150_fg_get_prop, -}; - -static int da9150_fg_probe(struct platform_device *pdev) -{ - struct device *dev = &pdev->dev; - struct da9150 *da9150 = dev_get_drvdata(dev->parent); - struct da9150_fg_pdata *fg_pdata = dev_get_platdata(dev); - struct da9150_fg *fg; - int ver, irq, ret = 0; - - fg = devm_kzalloc(dev, sizeof(*fg), GFP_KERNEL); - if (fg == NULL) - return -ENOMEM; - - platform_set_drvdata(pdev, fg); - fg->da9150 = da9150; - fg->dev = dev; - - mutex_init(&fg->io_lock); - - /* Enable QIF */ - da9150_set_bits(da9150, DA9150_CORE2WIRE_CTRL_A, DA9150_FG_QIF_EN_MASK, - DA9150_FG_QIF_EN_MASK); - - fg->battery = devm_power_supply_register(dev, &fg_desc, NULL); - if (IS_ERR(fg->battery)) { - ret = PTR_ERR(fg->battery); - return ret; - } - - ver = da9150_fg_read_attr(fg, DA9150_QIF_FW_MAIN_VER, - DA9150_QIF_FW_MAIN_VER_SIZE); - dev_info(dev, "Version: 0x%x\n", ver); - - /* Handle DT data if provided */ - if (dev->of_node) { - fg_pdata = da9150_fg_dt_pdata(dev); - dev->platform_data = fg_pdata; - } - - /* Handle any pdata provided */ - if (fg_pdata) { - fg->interval = fg_pdata->update_interval; - - if (fg_pdata->warn_soc_lvl > 100) - dev_warn(dev, "Invalid SOC warning level provided, Ignoring"); - else - fg->warn_soc = fg_pdata->warn_soc_lvl; - - if ((fg_pdata->crit_soc_lvl > 100) || - (fg_pdata->crit_soc_lvl >= fg_pdata->warn_soc_lvl)) - dev_warn(dev, "Invalid SOC critical level provided, Ignoring"); - else - fg->crit_soc = fg_pdata->crit_soc_lvl; - - - } - - /* Configure initial SOC level events */ - da9150_fg_soc_event_config(fg); - - /* - * If an interval period has been provided then setup repeating - * work for reporting data updates. - */ - if (fg->interval) { - INIT_DELAYED_WORK(&fg->work, da9150_fg_work); - schedule_delayed_work(&fg->work, - msecs_to_jiffies(fg->interval)); - } - - /* Register IRQ */ - irq = platform_get_irq_byname(pdev, "FG"); - if (irq < 0) { - dev_err(dev, "Failed to get IRQ FG: %d\n", irq); - ret = irq; - goto irq_fail; - } - - ret = devm_request_threaded_irq(dev, irq, NULL, da9150_fg_irq, - IRQF_ONESHOT, "FG", fg); - if (ret) { - dev_err(dev, "Failed to request IRQ %d: %d\n", irq, ret); - goto irq_fail; - } - - return 0; - -irq_fail: - if (fg->interval) - cancel_delayed_work(&fg->work); - - return ret; -} - -static int da9150_fg_remove(struct platform_device *pdev) -{ - struct da9150_fg *fg = platform_get_drvdata(pdev); - - if (fg->interval) - cancel_delayed_work(&fg->work); - - return 0; -} - -static int da9150_fg_resume(struct platform_device *pdev) -{ - struct da9150_fg *fg = platform_get_drvdata(pdev); - - /* - * Trigger SOC check to happen now so as to indicate any value change - * since last check before suspend. - */ - if (fg->interval) - flush_delayed_work(&fg->work); - - return 0; -} - -static struct platform_driver da9150_fg_driver = { - .driver = { - .name = "da9150-fuel-gauge", - }, - .probe = da9150_fg_probe, - .remove = da9150_fg_remove, - .resume = da9150_fg_resume, -}; - -module_platform_driver(da9150_fg_driver); - -MODULE_DESCRIPTION("Fuel-Gauge Driver for DA9150"); -MODULE_AUTHOR("Adam Thomson "); -MODULE_LICENSE("GPL"); diff --git a/drivers/power/ds2760_battery.c b/drivers/power/ds2760_battery.c deleted file mode 100644 index 80f73ccb77ab..000000000000 --- a/drivers/power/ds2760_battery.c +++ /dev/null @@ -1,647 +0,0 @@ -/* - * Driver for batteries with DS2760 chips inside. - * - * Copyright © 2007 Anton Vorontsov - * 2004-2007 Matt Reimer - * 2004 Szabolcs Gyurko - * - * Use consistent with the GNU GPL is permitted, - * provided that this copyright notice is - * preserved in its entirety in all copies and derived works. - * - * Author: Anton Vorontsov - * February 2007 - * - * Matt Reimer - * April 2004, 2005, 2007 - * - * Szabolcs Gyurko - * September 2004 - */ - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "../w1/w1.h" -#include "../w1/slaves/w1_ds2760.h" - -struct ds2760_device_info { - struct device *dev; - - /* DS2760 data, valid after calling ds2760_battery_read_status() */ - unsigned long update_time; /* jiffies when data read */ - char raw[DS2760_DATA_SIZE]; /* raw DS2760 data */ - int voltage_raw; /* units of 4.88 mV */ - int voltage_uV; /* units of µV */ - int current_raw; /* units of 0.625 mA */ - int current_uA; /* units of µA */ - int accum_current_raw; /* units of 0.25 mAh */ - int accum_current_uAh; /* units of µAh */ - int temp_raw; /* units of 0.125 °C */ - int temp_C; /* units of 0.1 °C */ - int rated_capacity; /* units of µAh */ - int rem_capacity; /* percentage */ - int full_active_uAh; /* units of µAh */ - int empty_uAh; /* units of µAh */ - int life_sec; /* units of seconds */ - int charge_status; /* POWER_SUPPLY_STATUS_* */ - - int full_counter; - struct power_supply *bat; - struct power_supply_desc bat_desc; - struct device *w1_dev; - struct workqueue_struct *monitor_wqueue; - struct delayed_work monitor_work; - struct delayed_work set_charged_work; -}; - -static unsigned int cache_time = 1000; -module_param(cache_time, uint, 0644); -MODULE_PARM_DESC(cache_time, "cache time in milliseconds"); - -static bool pmod_enabled; -module_param(pmod_enabled, bool, 0644); -MODULE_PARM_DESC(pmod_enabled, "PMOD enable bit"); - -static unsigned int rated_capacity; -module_param(rated_capacity, uint, 0644); -MODULE_PARM_DESC(rated_capacity, "rated battery capacity, 10*mAh or index"); - -static unsigned int current_accum; -module_param(current_accum, uint, 0644); -MODULE_PARM_DESC(current_accum, "current accumulator value"); - -/* Some batteries have their rated capacity stored a N * 10 mAh, while - * others use an index into this table. */ -static int rated_capacities[] = { - 0, - 920, /* Samsung */ - 920, /* BYD */ - 920, /* Lishen */ - 920, /* NEC */ - 1440, /* Samsung */ - 1440, /* BYD */ -#ifdef CONFIG_MACH_H4700 - 1800, /* HP iPAQ hx4700 3.7V 1800mAh (359113-001) */ -#else - 1440, /* Lishen */ -#endif - 1440, /* NEC */ - 2880, /* Samsung */ - 2880, /* BYD */ - 2880, /* Lishen */ - 2880, /* NEC */ -#ifdef CONFIG_MACH_H4700 - 0, - 3600, /* HP iPAQ hx4700 3.7V 3600mAh (359114-001) */ -#endif -}; - -/* array is level at temps 0°C, 10°C, 20°C, 30°C, 40°C - * temp is in Celsius */ -static int battery_interpolate(int array[], int temp) -{ - int index, dt; - - if (temp <= 0) - return array[0]; - if (temp >= 40) - return array[4]; - - index = temp / 10; - dt = temp % 10; - - return array[index] + (((array[index + 1] - array[index]) * dt) / 10); -} - -static int ds2760_battery_read_status(struct ds2760_device_info *di) -{ - int ret, i, start, count, scale[5]; - - if (di->update_time && time_before(jiffies, di->update_time + - msecs_to_jiffies(cache_time))) - return 0; - - /* The first time we read the entire contents of SRAM/EEPROM, - * but after that we just read the interesting bits that change. */ - if (di->update_time == 0) { - start = 0; - count = DS2760_DATA_SIZE; - } else { - start = DS2760_VOLTAGE_MSB; - count = DS2760_TEMP_LSB - start + 1; - } - - ret = w1_ds2760_read(di->w1_dev, di->raw + start, start, count); - if (ret != count) { - dev_warn(di->dev, "call to w1_ds2760_read failed (0x%p)\n", - di->w1_dev); - return 1; - } - - di->update_time = jiffies; - - /* DS2760 reports voltage in units of 4.88mV, but the battery class - * reports in units of uV, so convert by multiplying by 4880. */ - di->voltage_raw = (di->raw[DS2760_VOLTAGE_MSB] << 3) | - (di->raw[DS2760_VOLTAGE_LSB] >> 5); - di->voltage_uV = di->voltage_raw * 4880; - - /* DS2760 reports current in signed units of 0.625mA, but the battery - * class reports in units of µA, so convert by multiplying by 625. */ - di->current_raw = - (((signed char)di->raw[DS2760_CURRENT_MSB]) << 5) | - (di->raw[DS2760_CURRENT_LSB] >> 3); - di->current_uA = di->current_raw * 625; - - /* DS2760 reports accumulated current in signed units of 0.25mAh. */ - di->accum_current_raw = - (((signed char)di->raw[DS2760_CURRENT_ACCUM_MSB]) << 8) | - di->raw[DS2760_CURRENT_ACCUM_LSB]; - di->accum_current_uAh = di->accum_current_raw * 250; - - /* DS2760 reports temperature in signed units of 0.125°C, but the - * battery class reports in units of 1/10 °C, so we convert by - * multiplying by .125 * 10 = 1.25. */ - di->temp_raw = (((signed char)di->raw[DS2760_TEMP_MSB]) << 3) | - (di->raw[DS2760_TEMP_LSB] >> 5); - di->temp_C = di->temp_raw + (di->temp_raw / 4); - - /* At least some battery monitors (e.g. HP iPAQ) store the battery's - * maximum rated capacity. */ - if (di->raw[DS2760_RATED_CAPACITY] < ARRAY_SIZE(rated_capacities)) - di->rated_capacity = rated_capacities[ - (unsigned int)di->raw[DS2760_RATED_CAPACITY]]; - else - di->rated_capacity = di->raw[DS2760_RATED_CAPACITY] * 10; - - di->rated_capacity *= 1000; /* convert to µAh */ - - /* Calculate the full level at the present temperature. */ - di->full_active_uAh = di->raw[DS2760_ACTIVE_FULL] << 8 | - di->raw[DS2760_ACTIVE_FULL + 1]; - - /* If the full_active_uAh value is not given, fall back to the rated - * capacity. This is likely to happen when chips are not part of the - * battery pack and is therefore not bootstrapped. */ - if (di->full_active_uAh == 0) - di->full_active_uAh = di->rated_capacity / 1000L; - - scale[0] = di->full_active_uAh; - for (i = 1; i < 5; i++) - scale[i] = scale[i - 1] + di->raw[DS2760_ACTIVE_FULL + 1 + i]; - - di->full_active_uAh = battery_interpolate(scale, di->temp_C / 10); - di->full_active_uAh *= 1000; /* convert to µAh */ - - /* Calculate the empty level at the present temperature. */ - scale[4] = di->raw[DS2760_ACTIVE_EMPTY + 4]; - for (i = 3; i >= 0; i--) - scale[i] = scale[i + 1] + di->raw[DS2760_ACTIVE_EMPTY + i]; - - di->empty_uAh = battery_interpolate(scale, di->temp_C / 10); - di->empty_uAh *= 1000; /* convert to µAh */ - - if (di->full_active_uAh == di->empty_uAh) - di->rem_capacity = 0; - else - /* From Maxim Application Note 131: remaining capacity = - * ((ICA - Empty Value) / (Full Value - Empty Value)) x 100% */ - di->rem_capacity = ((di->accum_current_uAh - di->empty_uAh) * 100L) / - (di->full_active_uAh - di->empty_uAh); - - if (di->rem_capacity < 0) - di->rem_capacity = 0; - if (di->rem_capacity > 100) - di->rem_capacity = 100; - - if (di->current_uA < -100L) - di->life_sec = -((di->accum_current_uAh - di->empty_uAh) * 36L) - / (di->current_uA / 100L); - else - di->life_sec = 0; - - return 0; -} - -static void ds2760_battery_set_current_accum(struct ds2760_device_info *di, - unsigned int acr_val) -{ - unsigned char acr[2]; - - /* acr is in units of 0.25 mAh */ - acr_val *= 4L; - acr_val /= 1000; - - acr[0] = acr_val >> 8; - acr[1] = acr_val & 0xff; - - if (w1_ds2760_write(di->w1_dev, acr, DS2760_CURRENT_ACCUM_MSB, 2) < 2) - dev_warn(di->dev, "ACR write failed\n"); -} - -static void ds2760_battery_update_status(struct ds2760_device_info *di) -{ - int old_charge_status = di->charge_status; - - ds2760_battery_read_status(di); - - if (di->charge_status == POWER_SUPPLY_STATUS_UNKNOWN) - di->full_counter = 0; - - if (power_supply_am_i_supplied(di->bat)) { - if (di->current_uA > 10000) { - di->charge_status = POWER_SUPPLY_STATUS_CHARGING; - di->full_counter = 0; - } else if (di->current_uA < -5000) { - if (di->charge_status != POWER_SUPPLY_STATUS_NOT_CHARGING) - dev_notice(di->dev, "not enough power to " - "charge\n"); - di->charge_status = POWER_SUPPLY_STATUS_NOT_CHARGING; - di->full_counter = 0; - } else if (di->current_uA < 10000 && - di->charge_status != POWER_SUPPLY_STATUS_FULL) { - - /* Don't consider the battery to be full unless - * we've seen the current < 10 mA at least two - * consecutive times. */ - - di->full_counter++; - - if (di->full_counter < 2) { - di->charge_status = POWER_SUPPLY_STATUS_CHARGING; - } else { - di->charge_status = POWER_SUPPLY_STATUS_FULL; - ds2760_battery_set_current_accum(di, - di->full_active_uAh); - } - } - } else { - di->charge_status = POWER_SUPPLY_STATUS_DISCHARGING; - di->full_counter = 0; - } - - if (di->charge_status != old_charge_status) - power_supply_changed(di->bat); -} - -static void ds2760_battery_write_status(struct ds2760_device_info *di, - char status) -{ - if (status == di->raw[DS2760_STATUS_REG]) - return; - - w1_ds2760_write(di->w1_dev, &status, DS2760_STATUS_WRITE_REG, 1); - w1_ds2760_store_eeprom(di->w1_dev, DS2760_EEPROM_BLOCK1); - w1_ds2760_recall_eeprom(di->w1_dev, DS2760_EEPROM_BLOCK1); -} - -static void ds2760_battery_write_rated_capacity(struct ds2760_device_info *di, - unsigned char rated_capacity) -{ - if (rated_capacity == di->raw[DS2760_RATED_CAPACITY]) - return; - - w1_ds2760_write(di->w1_dev, &rated_capacity, DS2760_RATED_CAPACITY, 1); - w1_ds2760_store_eeprom(di->w1_dev, DS2760_EEPROM_BLOCK1); - w1_ds2760_recall_eeprom(di->w1_dev, DS2760_EEPROM_BLOCK1); -} - -static void ds2760_battery_write_active_full(struct ds2760_device_info *di, - int active_full) -{ - unsigned char tmp[2] = { - active_full >> 8, - active_full & 0xff - }; - - if (tmp[0] == di->raw[DS2760_ACTIVE_FULL] && - tmp[1] == di->raw[DS2760_ACTIVE_FULL + 1]) - return; - - w1_ds2760_write(di->w1_dev, tmp, DS2760_ACTIVE_FULL, sizeof(tmp)); - w1_ds2760_store_eeprom(di->w1_dev, DS2760_EEPROM_BLOCK0); - w1_ds2760_recall_eeprom(di->w1_dev, DS2760_EEPROM_BLOCK0); - - /* Write to the di->raw[] buffer directly - the DS2760_ACTIVE_FULL - * values won't be read back by ds2760_battery_read_status() */ - di->raw[DS2760_ACTIVE_FULL] = tmp[0]; - di->raw[DS2760_ACTIVE_FULL + 1] = tmp[1]; -} - -static void ds2760_battery_work(struct work_struct *work) -{ - struct ds2760_device_info *di = container_of(work, - struct ds2760_device_info, monitor_work.work); - const int interval = HZ * 60; - - dev_dbg(di->dev, "%s\n", __func__); - - ds2760_battery_update_status(di); - queue_delayed_work(di->monitor_wqueue, &di->monitor_work, interval); -} - -static void ds2760_battery_external_power_changed(struct power_supply *psy) -{ - struct ds2760_device_info *di = power_supply_get_drvdata(psy); - - dev_dbg(di->dev, "%s\n", __func__); - - mod_delayed_work(di->monitor_wqueue, &di->monitor_work, HZ/10); -} - - -static void ds2760_battery_set_charged_work(struct work_struct *work) -{ - char bias; - struct ds2760_device_info *di = container_of(work, - struct ds2760_device_info, set_charged_work.work); - - dev_dbg(di->dev, "%s\n", __func__); - - ds2760_battery_read_status(di); - - /* When we get notified by external circuitry that the battery is - * considered fully charged now, we know that there is no current - * flow any more. However, the ds2760's internal current meter is - * too inaccurate to rely on - spec say something ~15% failure. - * Hence, we use the current offset bias register to compensate - * that error. - */ - - if (!power_supply_am_i_supplied(di->bat)) - return; - - bias = (signed char) di->current_raw + - (signed char) di->raw[DS2760_CURRENT_OFFSET_BIAS]; - - dev_dbg(di->dev, "%s: bias = %d\n", __func__, bias); - - w1_ds2760_write(di->w1_dev, &bias, DS2760_CURRENT_OFFSET_BIAS, 1); - w1_ds2760_store_eeprom(di->w1_dev, DS2760_EEPROM_BLOCK1); - w1_ds2760_recall_eeprom(di->w1_dev, DS2760_EEPROM_BLOCK1); - - /* Write to the di->raw[] buffer directly - the CURRENT_OFFSET_BIAS - * value won't be read back by ds2760_battery_read_status() */ - di->raw[DS2760_CURRENT_OFFSET_BIAS] = bias; -} - -static void ds2760_battery_set_charged(struct power_supply *psy) -{ - struct ds2760_device_info *di = power_supply_get_drvdata(psy); - - /* postpone the actual work by 20 secs. This is for debouncing GPIO - * signals and to let the current value settle. See AN4188. */ - mod_delayed_work(di->monitor_wqueue, &di->set_charged_work, HZ * 20); -} - -static int ds2760_battery_get_property(struct power_supply *psy, - enum power_supply_property psp, - union power_supply_propval *val) -{ - struct ds2760_device_info *di = power_supply_get_drvdata(psy); - - switch (psp) { - case POWER_SUPPLY_PROP_STATUS: - val->intval = di->charge_status; - return 0; - default: - break; - } - - ds2760_battery_read_status(di); - - switch (psp) { - case POWER_SUPPLY_PROP_VOLTAGE_NOW: - val->intval = di->voltage_uV; - break; - case POWER_SUPPLY_PROP_CURRENT_NOW: - val->intval = di->current_uA; - break; - case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: - val->intval = di->rated_capacity; - break; - case POWER_SUPPLY_PROP_CHARGE_FULL: - val->intval = di->full_active_uAh; - break; - case POWER_SUPPLY_PROP_CHARGE_EMPTY: - val->intval = di->empty_uAh; - break; - case POWER_SUPPLY_PROP_CHARGE_NOW: - val->intval = di->accum_current_uAh; - break; - case POWER_SUPPLY_PROP_TEMP: - val->intval = di->temp_C; - break; - case POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW: - val->intval = di->life_sec; - break; - case POWER_SUPPLY_PROP_CAPACITY: - val->intval = di->rem_capacity; - break; - default: - return -EINVAL; - } - - return 0; -} - -static int ds2760_battery_set_property(struct power_supply *psy, - enum power_supply_property psp, - const union power_supply_propval *val) -{ - struct ds2760_device_info *di = power_supply_get_drvdata(psy); - - switch (psp) { - case POWER_SUPPLY_PROP_CHARGE_FULL: - /* the interface counts in uAh, convert the value */ - ds2760_battery_write_active_full(di, val->intval / 1000L); - break; - - case POWER_SUPPLY_PROP_CHARGE_NOW: - /* ds2760_battery_set_current_accum() does the conversion */ - ds2760_battery_set_current_accum(di, val->intval); - break; - - default: - return -EPERM; - } - - return 0; -} - -static int ds2760_battery_property_is_writeable(struct power_supply *psy, - enum power_supply_property psp) -{ - switch (psp) { - case POWER_SUPPLY_PROP_CHARGE_FULL: - case POWER_SUPPLY_PROP_CHARGE_NOW: - return 1; - - default: - break; - } - - return 0; -} - -static enum power_supply_property ds2760_battery_props[] = { - POWER_SUPPLY_PROP_STATUS, - POWER_SUPPLY_PROP_VOLTAGE_NOW, - POWER_SUPPLY_PROP_CURRENT_NOW, - POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, - POWER_SUPPLY_PROP_CHARGE_FULL, - POWER_SUPPLY_PROP_CHARGE_EMPTY, - POWER_SUPPLY_PROP_CHARGE_NOW, - POWER_SUPPLY_PROP_TEMP, - POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW, - POWER_SUPPLY_PROP_CAPACITY, -}; - -static int ds2760_battery_probe(struct platform_device *pdev) -{ - struct power_supply_config psy_cfg = {}; - char status; - int retval = 0; - struct ds2760_device_info *di; - - di = devm_kzalloc(&pdev->dev, sizeof(*di), GFP_KERNEL); - if (!di) { - retval = -ENOMEM; - goto di_alloc_failed; - } - - platform_set_drvdata(pdev, di); - - di->dev = &pdev->dev; - di->w1_dev = pdev->dev.parent; - di->bat_desc.name = dev_name(&pdev->dev); - di->bat_desc.type = POWER_SUPPLY_TYPE_BATTERY; - di->bat_desc.properties = ds2760_battery_props; - di->bat_desc.num_properties = ARRAY_SIZE(ds2760_battery_props); - di->bat_desc.get_property = ds2760_battery_get_property; - di->bat_desc.set_property = ds2760_battery_set_property; - di->bat_desc.property_is_writeable = - ds2760_battery_property_is_writeable; - di->bat_desc.set_charged = ds2760_battery_set_charged; - di->bat_desc.external_power_changed = - ds2760_battery_external_power_changed; - - psy_cfg.drv_data = di; - - di->charge_status = POWER_SUPPLY_STATUS_UNKNOWN; - - /* enable sleep mode feature */ - ds2760_battery_read_status(di); - status = di->raw[DS2760_STATUS_REG]; - if (pmod_enabled) - status |= DS2760_STATUS_PMOD; - else - status &= ~DS2760_STATUS_PMOD; - - ds2760_battery_write_status(di, status); - - /* set rated capacity from module param */ - if (rated_capacity) - ds2760_battery_write_rated_capacity(di, rated_capacity); - - /* set current accumulator if given as parameter. - * this should only be done for bootstrapping the value */ - if (current_accum) - ds2760_battery_set_current_accum(di, current_accum); - - di->bat = power_supply_register(&pdev->dev, &di->bat_desc, &psy_cfg); - if (IS_ERR(di->bat)) { - dev_err(di->dev, "failed to register battery\n"); - retval = PTR_ERR(di->bat); - goto batt_failed; - } - - INIT_DELAYED_WORK(&di->monitor_work, ds2760_battery_work); - INIT_DELAYED_WORK(&di->set_charged_work, - ds2760_battery_set_charged_work); - di->monitor_wqueue = create_singlethread_workqueue(dev_name(&pdev->dev)); - if (!di->monitor_wqueue) { - retval = -ESRCH; - goto workqueue_failed; - } - queue_delayed_work(di->monitor_wqueue, &di->monitor_work, HZ * 1); - - goto success; - -workqueue_failed: - power_supply_unregister(di->bat); -batt_failed: -di_alloc_failed: -success: - return retval; -} - -static int ds2760_battery_remove(struct platform_device *pdev) -{ - struct ds2760_device_info *di = platform_get_drvdata(pdev); - - cancel_delayed_work_sync(&di->monitor_work); - cancel_delayed_work_sync(&di->set_charged_work); - destroy_workqueue(di->monitor_wqueue); - power_supply_unregister(di->bat); - - return 0; -} - -#ifdef CONFIG_PM - -static int ds2760_battery_suspend(struct platform_device *pdev, - pm_message_t state) -{ - struct ds2760_device_info *di = platform_get_drvdata(pdev); - - di->charge_status = POWER_SUPPLY_STATUS_UNKNOWN; - - return 0; -} - -static int ds2760_battery_resume(struct platform_device *pdev) -{ - struct ds2760_device_info *di = platform_get_drvdata(pdev); - - di->charge_status = POWER_SUPPLY_STATUS_UNKNOWN; - power_supply_changed(di->bat); - - mod_delayed_work(di->monitor_wqueue, &di->monitor_work, HZ); - - return 0; -} - -#else - -#define ds2760_battery_suspend NULL -#define ds2760_battery_resume NULL - -#endif /* CONFIG_PM */ - -MODULE_ALIAS("platform:ds2760-battery"); - -static struct platform_driver ds2760_battery_driver = { - .driver = { - .name = "ds2760-battery", - }, - .probe = ds2760_battery_probe, - .remove = ds2760_battery_remove, - .suspend = ds2760_battery_suspend, - .resume = ds2760_battery_resume, -}; - -module_platform_driver(ds2760_battery_driver); - -MODULE_LICENSE("GPL"); -MODULE_AUTHOR("Szabolcs Gyurko , " - "Matt Reimer , " - "Anton Vorontsov "); -MODULE_DESCRIPTION("ds2760 battery driver"); diff --git a/drivers/power/ds2780_battery.c b/drivers/power/ds2780_battery.c deleted file mode 100644 index d3743d0ad55b..000000000000 --- a/drivers/power/ds2780_battery.c +++ /dev/null @@ -1,838 +0,0 @@ -/* - * 1-wire client/driver for the Maxim/Dallas DS2780 Stand-Alone Fuel Gauge IC - * - * Copyright (C) 2010 Indesign, LLC - * - * Author: Clifton Barnes - * - * Based on ds2760_battery and ds2782_battery drivers - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - * - */ - -#include -#include -#include -#include -#include -#include -#include - -#include "../w1/w1.h" -#include "../w1/slaves/w1_ds2780.h" - -/* Current unit measurement in uA for a 1 milli-ohm sense resistor */ -#define DS2780_CURRENT_UNITS 1563 -/* Charge unit measurement in uAh for a 1 milli-ohm sense resistor */ -#define DS2780_CHARGE_UNITS 6250 -/* Number of bytes in user EEPROM space */ -#define DS2780_USER_EEPROM_SIZE (DS2780_EEPROM_BLOCK0_END - \ - DS2780_EEPROM_BLOCK0_START + 1) -/* Number of bytes in parameter EEPROM space */ -#define DS2780_PARAM_EEPROM_SIZE (DS2780_EEPROM_BLOCK1_END - \ - DS2780_EEPROM_BLOCK1_START + 1) - -struct ds2780_device_info { - struct device *dev; - struct power_supply *bat; - struct power_supply_desc bat_desc; - struct device *w1_dev; -}; - -enum current_types { - CURRENT_NOW, - CURRENT_AVG, -}; - -static const char model[] = "DS2780"; -static const char manufacturer[] = "Maxim/Dallas"; - -static inline struct ds2780_device_info * -to_ds2780_device_info(struct power_supply *psy) -{ - return power_supply_get_drvdata(psy); -} - -static inline struct power_supply *to_power_supply(struct device *dev) -{ - return dev_get_drvdata(dev); -} - -static inline int ds2780_battery_io(struct ds2780_device_info *dev_info, - char *buf, int addr, size_t count, int io) -{ - return w1_ds2780_io(dev_info->w1_dev, buf, addr, count, io); -} - -static inline int ds2780_read8(struct ds2780_device_info *dev_info, u8 *val, - int addr) -{ - return ds2780_battery_io(dev_info, val, addr, sizeof(u8), 0); -} - -static int ds2780_read16(struct ds2780_device_info *dev_info, s16 *val, - int addr) -{ - int ret; - u8 raw[2]; - - ret = ds2780_battery_io(dev_info, raw, addr, sizeof(raw), 0); - if (ret < 0) - return ret; - - *val = (raw[0] << 8) | raw[1]; - - return 0; -} - -static inline int ds2780_read_block(struct ds2780_device_info *dev_info, - u8 *val, int addr, size_t count) -{ - return ds2780_battery_io(dev_info, val, addr, count, 0); -} - -static inline int ds2780_write(struct ds2780_device_info *dev_info, u8 *val, - int addr, size_t count) -{ - return ds2780_battery_io(dev_info, val, addr, count, 1); -} - -static inline int ds2780_store_eeprom(struct device *dev, int addr) -{ - return w1_ds2780_eeprom_cmd(dev, addr, W1_DS2780_COPY_DATA); -} - -static inline int ds2780_recall_eeprom(struct device *dev, int addr) -{ - return w1_ds2780_eeprom_cmd(dev, addr, W1_DS2780_RECALL_DATA); -} - -static int ds2780_save_eeprom(struct ds2780_device_info *dev_info, int reg) -{ - int ret; - - ret = ds2780_store_eeprom(dev_info->w1_dev, reg); - if (ret < 0) - return ret; - - ret = ds2780_recall_eeprom(dev_info->w1_dev, reg); - if (ret < 0) - return ret; - - return 0; -} - -/* Set sense resistor value in mhos */ -static int ds2780_set_sense_register(struct ds2780_device_info *dev_info, - u8 conductance) -{ - int ret; - - ret = ds2780_write(dev_info, &conductance, - DS2780_RSNSP_REG, sizeof(u8)); - if (ret < 0) - return ret; - - return ds2780_save_eeprom(dev_info, DS2780_RSNSP_REG); -} - -/* Get RSGAIN value from 0 to 1.999 in steps of 0.001 */ -static int ds2780_get_rsgain_register(struct ds2780_device_info *dev_info, - u16 *rsgain) -{ - return ds2780_read16(dev_info, rsgain, DS2780_RSGAIN_MSB_REG); -} - -/* Set RSGAIN value from 0 to 1.999 in steps of 0.001 */ -static int ds2780_set_rsgain_register(struct ds2780_device_info *dev_info, - u16 rsgain) -{ - int ret; - u8 raw[] = {rsgain >> 8, rsgain & 0xFF}; - - ret = ds2780_write(dev_info, raw, - DS2780_RSGAIN_MSB_REG, sizeof(raw)); - if (ret < 0) - return ret; - - return ds2780_save_eeprom(dev_info, DS2780_RSGAIN_MSB_REG); -} - -static int ds2780_get_voltage(struct ds2780_device_info *dev_info, - int *voltage_uV) -{ - int ret; - s16 voltage_raw; - - /* - * The voltage value is located in 10 bits across the voltage MSB - * and LSB registers in two's compliment form - * Sign bit of the voltage value is in bit 7 of the voltage MSB register - * Bits 9 - 3 of the voltage value are in bits 6 - 0 of the - * voltage MSB register - * Bits 2 - 0 of the voltage value are in bits 7 - 5 of the - * voltage LSB register - */ - ret = ds2780_read16(dev_info, &voltage_raw, - DS2780_VOLT_MSB_REG); - if (ret < 0) - return ret; - - /* - * DS2780 reports voltage in units of 4.88mV, but the battery class - * reports in units of uV, so convert by multiplying by 4880. - */ - *voltage_uV = (voltage_raw / 32) * 4880; - return 0; -} - -static int ds2780_get_temperature(struct ds2780_device_info *dev_info, - int *temperature) -{ - int ret; - s16 temperature_raw; - - /* - * The temperature value is located in 10 bits across the temperature - * MSB and LSB registers in two's compliment form - * Sign bit of the temperature value is in bit 7 of the temperature - * MSB register - * Bits 9 - 3 of the temperature value are in bits 6 - 0 of the - * temperature MSB register - * Bits 2 - 0 of the temperature value are in bits 7 - 5 of the - * temperature LSB register - */ - ret = ds2780_read16(dev_info, &temperature_raw, - DS2780_TEMP_MSB_REG); - if (ret < 0) - return ret; - - /* - * Temperature is measured in units of 0.125 degrees celcius, the - * power_supply class measures temperature in tenths of degrees - * celsius. The temperature value is stored as a 10 bit number, plus - * sign in the upper bits of a 16 bit register. - */ - *temperature = ((temperature_raw / 32) * 125) / 100; - return 0; -} - -static int ds2780_get_current(struct ds2780_device_info *dev_info, - enum current_types type, int *current_uA) -{ - int ret, sense_res; - s16 current_raw; - u8 sense_res_raw, reg_msb; - - /* - * The units of measurement for current are dependent on the value of - * the sense resistor. - */ - ret = ds2780_read8(dev_info, &sense_res_raw, DS2780_RSNSP_REG); - if (ret < 0) - return ret; - - if (sense_res_raw == 0) { - dev_err(dev_info->dev, "sense resistor value is 0\n"); - return -EINVAL; - } - sense_res = 1000 / sense_res_raw; - - if (type == CURRENT_NOW) - reg_msb = DS2780_CURRENT_MSB_REG; - else if (type == CURRENT_AVG) - reg_msb = DS2780_IAVG_MSB_REG; - else - return -EINVAL; - - /* - * The current value is located in 16 bits across the current MSB - * and LSB registers in two's compliment form - * Sign bit of the current value is in bit 7 of the current MSB register - * Bits 14 - 8 of the current value are in bits 6 - 0 of the current - * MSB register - * Bits 7 - 0 of the current value are in bits 7 - 0 of the current - * LSB register - */ - ret = ds2780_read16(dev_info, ¤t_raw, reg_msb); - if (ret < 0) - return ret; - - *current_uA = current_raw * (DS2780_CURRENT_UNITS / sense_res); - return 0; -} - -static int ds2780_get_accumulated_current(struct ds2780_device_info *dev_info, - int *accumulated_current) -{ - int ret, sense_res; - s16 current_raw; - u8 sense_res_raw; - - /* - * The units of measurement for accumulated current are dependent on - * the value of the sense resistor. - */ - ret = ds2780_read8(dev_info, &sense_res_raw, DS2780_RSNSP_REG); - if (ret < 0) - return ret; - - if (sense_res_raw == 0) { - dev_err(dev_info->dev, "sense resistor value is 0\n"); - return -ENXIO; - } - sense_res = 1000 / sense_res_raw; - - /* - * The ACR value is located in 16 bits across the ACR MSB and - * LSB registers - * Bits 15 - 8 of the ACR value are in bits 7 - 0 of the ACR - * MSB register - * Bits 7 - 0 of the ACR value are in bits 7 - 0 of the ACR - * LSB register - */ - ret = ds2780_read16(dev_info, ¤t_raw, DS2780_ACR_MSB_REG); - if (ret < 0) - return ret; - - *accumulated_current = current_raw * (DS2780_CHARGE_UNITS / sense_res); - return 0; -} - -static int ds2780_get_capacity(struct ds2780_device_info *dev_info, - int *capacity) -{ - int ret; - u8 raw; - - ret = ds2780_read8(dev_info, &raw, DS2780_RARC_REG); - if (ret < 0) - return ret; - - *capacity = raw; - return raw; -} - -static int ds2780_get_status(struct ds2780_device_info *dev_info, int *status) -{ - int ret, current_uA, capacity; - - ret = ds2780_get_current(dev_info, CURRENT_NOW, ¤t_uA); - if (ret < 0) - return ret; - - ret = ds2780_get_capacity(dev_info, &capacity); - if (ret < 0) - return ret; - - if (capacity == 100) - *status = POWER_SUPPLY_STATUS_FULL; - else if (current_uA == 0) - *status = POWER_SUPPLY_STATUS_NOT_CHARGING; - else if (current_uA < 0) - *status = POWER_SUPPLY_STATUS_DISCHARGING; - else - *status = POWER_SUPPLY_STATUS_CHARGING; - - return 0; -} - -static int ds2780_get_charge_now(struct ds2780_device_info *dev_info, - int *charge_now) -{ - int ret; - u16 charge_raw; - - /* - * The RAAC value is located in 16 bits across the RAAC MSB and - * LSB registers - * Bits 15 - 8 of the RAAC value are in bits 7 - 0 of the RAAC - * MSB register - * Bits 7 - 0 of the RAAC value are in bits 7 - 0 of the RAAC - * LSB register - */ - ret = ds2780_read16(dev_info, &charge_raw, DS2780_RAAC_MSB_REG); - if (ret < 0) - return ret; - - *charge_now = charge_raw * 1600; - return 0; -} - -static int ds2780_get_control_register(struct ds2780_device_info *dev_info, - u8 *control_reg) -{ - return ds2780_read8(dev_info, control_reg, DS2780_CONTROL_REG); -} - -static int ds2780_set_control_register(struct ds2780_device_info *dev_info, - u8 control_reg) -{ - int ret; - - ret = ds2780_write(dev_info, &control_reg, - DS2780_CONTROL_REG, sizeof(u8)); - if (ret < 0) - return ret; - - return ds2780_save_eeprom(dev_info, DS2780_CONTROL_REG); -} - -static int ds2780_battery_get_property(struct power_supply *psy, - enum power_supply_property psp, - union power_supply_propval *val) -{ - int ret = 0; - struct ds2780_device_info *dev_info = to_ds2780_device_info(psy); - - switch (psp) { - case POWER_SUPPLY_PROP_VOLTAGE_NOW: - ret = ds2780_get_voltage(dev_info, &val->intval); - break; - - case POWER_SUPPLY_PROP_TEMP: - ret = ds2780_get_temperature(dev_info, &val->intval); - break; - - case POWER_SUPPLY_PROP_MODEL_NAME: - val->strval = model; - break; - - case POWER_SUPPLY_PROP_MANUFACTURER: - val->strval = manufacturer; - break; - - case POWER_SUPPLY_PROP_CURRENT_NOW: - ret = ds2780_get_current(dev_info, CURRENT_NOW, &val->intval); - break; - - case POWER_SUPPLY_PROP_CURRENT_AVG: - ret = ds2780_get_current(dev_info, CURRENT_AVG, &val->intval); - break; - - case POWER_SUPPLY_PROP_STATUS: - ret = ds2780_get_status(dev_info, &val->intval); - break; - - case POWER_SUPPLY_PROP_CAPACITY: - ret = ds2780_get_capacity(dev_info, &val->intval); - break; - - case POWER_SUPPLY_PROP_CHARGE_COUNTER: - ret = ds2780_get_accumulated_current(dev_info, &val->intval); - break; - - case POWER_SUPPLY_PROP_CHARGE_NOW: - ret = ds2780_get_charge_now(dev_info, &val->intval); - break; - - default: - ret = -EINVAL; - } - - return ret; -} - -static enum power_supply_property ds2780_battery_props[] = { - POWER_SUPPLY_PROP_STATUS, - POWER_SUPPLY_PROP_VOLTAGE_NOW, - POWER_SUPPLY_PROP_TEMP, - POWER_SUPPLY_PROP_MODEL_NAME, - POWER_SUPPLY_PROP_MANUFACTURER, - POWER_SUPPLY_PROP_CURRENT_NOW, - POWER_SUPPLY_PROP_CURRENT_AVG, - POWER_SUPPLY_PROP_CAPACITY, - POWER_SUPPLY_PROP_CHARGE_COUNTER, - POWER_SUPPLY_PROP_CHARGE_NOW, -}; - -static ssize_t ds2780_get_pmod_enabled(struct device *dev, - struct device_attribute *attr, - char *buf) -{ - int ret; - u8 control_reg; - struct power_supply *psy = to_power_supply(dev); - struct ds2780_device_info *dev_info = to_ds2780_device_info(psy); - - /* Get power mode */ - ret = ds2780_get_control_register(dev_info, &control_reg); - if (ret < 0) - return ret; - - return sprintf(buf, "%d\n", - !!(control_reg & DS2780_CONTROL_REG_PMOD)); -} - -static ssize_t ds2780_set_pmod_enabled(struct device *dev, - struct device_attribute *attr, - const char *buf, - size_t count) -{ - int ret; - u8 control_reg, new_setting; - struct power_supply *psy = to_power_supply(dev); - struct ds2780_device_info *dev_info = to_ds2780_device_info(psy); - - /* Set power mode */ - ret = ds2780_get_control_register(dev_info, &control_reg); - if (ret < 0) - return ret; - - ret = kstrtou8(buf, 0, &new_setting); - if (ret < 0) - return ret; - - if ((new_setting != 0) && (new_setting != 1)) { - dev_err(dev_info->dev, "Invalid pmod setting (0 or 1)\n"); - return -EINVAL; - } - - if (new_setting) - control_reg |= DS2780_CONTROL_REG_PMOD; - else - control_reg &= ~DS2780_CONTROL_REG_PMOD; - - ret = ds2780_set_control_register(dev_info, control_reg); - if (ret < 0) - return ret; - - return count; -} - -static ssize_t ds2780_get_sense_resistor_value(struct device *dev, - struct device_attribute *attr, - char *buf) -{ - int ret; - u8 sense_resistor; - struct power_supply *psy = to_power_supply(dev); - struct ds2780_device_info *dev_info = to_ds2780_device_info(psy); - - ret = ds2780_read8(dev_info, &sense_resistor, DS2780_RSNSP_REG); - if (ret < 0) - return ret; - - ret = sprintf(buf, "%d\n", sense_resistor); - return ret; -} - -static ssize_t ds2780_set_sense_resistor_value(struct device *dev, - struct device_attribute *attr, - const char *buf, - size_t count) -{ - int ret; - u8 new_setting; - struct power_supply *psy = to_power_supply(dev); - struct ds2780_device_info *dev_info = to_ds2780_device_info(psy); - - ret = kstrtou8(buf, 0, &new_setting); - if (ret < 0) - return ret; - - ret = ds2780_set_sense_register(dev_info, new_setting); - if (ret < 0) - return ret; - - return count; -} - -static ssize_t ds2780_get_rsgain_setting(struct device *dev, - struct device_attribute *attr, - char *buf) -{ - int ret; - u16 rsgain; - struct power_supply *psy = to_power_supply(dev); - struct ds2780_device_info *dev_info = to_ds2780_device_info(psy); - - ret = ds2780_get_rsgain_register(dev_info, &rsgain); - if (ret < 0) - return ret; - - return sprintf(buf, "%d\n", rsgain); -} - -static ssize_t ds2780_set_rsgain_setting(struct device *dev, - struct device_attribute *attr, - const char *buf, - size_t count) -{ - int ret; - u16 new_setting; - struct power_supply *psy = to_power_supply(dev); - struct ds2780_device_info *dev_info = to_ds2780_device_info(psy); - - ret = kstrtou16(buf, 0, &new_setting); - if (ret < 0) - return ret; - - /* Gain can only be from 0 to 1.999 in steps of .001 */ - if (new_setting > 1999) { - dev_err(dev_info->dev, "Invalid rsgain setting (0 - 1999)\n"); - return -EINVAL; - } - - ret = ds2780_set_rsgain_register(dev_info, new_setting); - if (ret < 0) - return ret; - - return count; -} - -static ssize_t ds2780_get_pio_pin(struct device *dev, - struct device_attribute *attr, - char *buf) -{ - int ret; - u8 sfr; - struct power_supply *psy = to_power_supply(dev); - struct ds2780_device_info *dev_info = to_ds2780_device_info(psy); - - ret = ds2780_read8(dev_info, &sfr, DS2780_SFR_REG); - if (ret < 0) - return ret; - - ret = sprintf(buf, "%d\n", sfr & DS2780_SFR_REG_PIOSC); - return ret; -} - -static ssize_t ds2780_set_pio_pin(struct device *dev, - struct device_attribute *attr, - const char *buf, - size_t count) -{ - int ret; - u8 new_setting; - struct power_supply *psy = to_power_supply(dev); - struct ds2780_device_info *dev_info = to_ds2780_device_info(psy); - - ret = kstrtou8(buf, 0, &new_setting); - if (ret < 0) - return ret; - - if ((new_setting != 0) && (new_setting != 1)) { - dev_err(dev_info->dev, "Invalid pio_pin setting (0 or 1)\n"); - return -EINVAL; - } - - ret = ds2780_write(dev_info, &new_setting, - DS2780_SFR_REG, sizeof(u8)); - if (ret < 0) - return ret; - - return count; -} - -static ssize_t ds2780_read_param_eeprom_bin(struct file *filp, - struct kobject *kobj, - struct bin_attribute *bin_attr, - char *buf, loff_t off, size_t count) -{ - struct device *dev = container_of(kobj, struct device, kobj); - struct power_supply *psy = to_power_supply(dev); - struct ds2780_device_info *dev_info = to_ds2780_device_info(psy); - - return ds2780_read_block(dev_info, buf, - DS2780_EEPROM_BLOCK1_START + off, count); -} - -static ssize_t ds2780_write_param_eeprom_bin(struct file *filp, - struct kobject *kobj, - struct bin_attribute *bin_attr, - char *buf, loff_t off, size_t count) -{ - struct device *dev = container_of(kobj, struct device, kobj); - struct power_supply *psy = to_power_supply(dev); - struct ds2780_device_info *dev_info = to_ds2780_device_info(psy); - int ret; - - ret = ds2780_write(dev_info, buf, - DS2780_EEPROM_BLOCK1_START + off, count); - if (ret < 0) - return ret; - - ret = ds2780_save_eeprom(dev_info, DS2780_EEPROM_BLOCK1_START); - if (ret < 0) - return ret; - - return count; -} - -static struct bin_attribute ds2780_param_eeprom_bin_attr = { - .attr = { - .name = "param_eeprom", - .mode = S_IRUGO | S_IWUSR, - }, - .size = DS2780_PARAM_EEPROM_SIZE, - .read = ds2780_read_param_eeprom_bin, - .write = ds2780_write_param_eeprom_bin, -}; - -static ssize_t ds2780_read_user_eeprom_bin(struct file *filp, - struct kobject *kobj, - struct bin_attribute *bin_attr, - char *buf, loff_t off, size_t count) -{ - struct device *dev = container_of(kobj, struct device, kobj); - struct power_supply *psy = to_power_supply(dev); - struct ds2780_device_info *dev_info = to_ds2780_device_info(psy); - - return ds2780_read_block(dev_info, buf, - DS2780_EEPROM_BLOCK0_START + off, count); -} - -static ssize_t ds2780_write_user_eeprom_bin(struct file *filp, - struct kobject *kobj, - struct bin_attribute *bin_attr, - char *buf, loff_t off, size_t count) -{ - struct device *dev = container_of(kobj, struct device, kobj); - struct power_supply *psy = to_power_supply(dev); - struct ds2780_device_info *dev_info = to_ds2780_device_info(psy); - int ret; - - ret = ds2780_write(dev_info, buf, - DS2780_EEPROM_BLOCK0_START + off, count); - if (ret < 0) - return ret; - - ret = ds2780_save_eeprom(dev_info, DS2780_EEPROM_BLOCK0_START); - if (ret < 0) - return ret; - - return count; -} - -static struct bin_attribute ds2780_user_eeprom_bin_attr = { - .attr = { - .name = "user_eeprom", - .mode = S_IRUGO | S_IWUSR, - }, - .size = DS2780_USER_EEPROM_SIZE, - .read = ds2780_read_user_eeprom_bin, - .write = ds2780_write_user_eeprom_bin, -}; - -static DEVICE_ATTR(pmod_enabled, S_IRUGO | S_IWUSR, ds2780_get_pmod_enabled, - ds2780_set_pmod_enabled); -static DEVICE_ATTR(sense_resistor_value, S_IRUGO | S_IWUSR, - ds2780_get_sense_resistor_value, ds2780_set_sense_resistor_value); -static DEVICE_ATTR(rsgain_setting, S_IRUGO | S_IWUSR, ds2780_get_rsgain_setting, - ds2780_set_rsgain_setting); -static DEVICE_ATTR(pio_pin, S_IRUGO | S_IWUSR, ds2780_get_pio_pin, - ds2780_set_pio_pin); - - -static struct attribute *ds2780_attributes[] = { - &dev_attr_pmod_enabled.attr, - &dev_attr_sense_resistor_value.attr, - &dev_attr_rsgain_setting.attr, - &dev_attr_pio_pin.attr, - NULL -}; - -static const struct attribute_group ds2780_attr_group = { - .attrs = ds2780_attributes, -}; - -static int ds2780_battery_probe(struct platform_device *pdev) -{ - struct power_supply_config psy_cfg = {}; - int ret = 0; - struct ds2780_device_info *dev_info; - - dev_info = devm_kzalloc(&pdev->dev, sizeof(*dev_info), GFP_KERNEL); - if (!dev_info) { - ret = -ENOMEM; - goto fail; - } - - platform_set_drvdata(pdev, dev_info); - - dev_info->dev = &pdev->dev; - dev_info->w1_dev = pdev->dev.parent; - dev_info->bat_desc.name = dev_name(&pdev->dev); - dev_info->bat_desc.type = POWER_SUPPLY_TYPE_BATTERY; - dev_info->bat_desc.properties = ds2780_battery_props; - dev_info->bat_desc.num_properties = ARRAY_SIZE(ds2780_battery_props); - dev_info->bat_desc.get_property = ds2780_battery_get_property; - - psy_cfg.drv_data = dev_info; - - dev_info->bat = power_supply_register(&pdev->dev, &dev_info->bat_desc, - &psy_cfg); - if (IS_ERR(dev_info->bat)) { - dev_err(dev_info->dev, "failed to register battery\n"); - ret = PTR_ERR(dev_info->bat); - goto fail; - } - - ret = sysfs_create_group(&dev_info->bat->dev.kobj, &ds2780_attr_group); - if (ret) { - dev_err(dev_info->dev, "failed to create sysfs group\n"); - goto fail_unregister; - } - - ret = sysfs_create_bin_file(&dev_info->bat->dev.kobj, - &ds2780_param_eeprom_bin_attr); - if (ret) { - dev_err(dev_info->dev, - "failed to create param eeprom bin file"); - goto fail_remove_group; - } - - ret = sysfs_create_bin_file(&dev_info->bat->dev.kobj, - &ds2780_user_eeprom_bin_attr); - if (ret) { - dev_err(dev_info->dev, - "failed to create user eeprom bin file"); - goto fail_remove_bin_file; - } - - return 0; - -fail_remove_bin_file: - sysfs_remove_bin_file(&dev_info->bat->dev.kobj, - &ds2780_param_eeprom_bin_attr); -fail_remove_group: - sysfs_remove_group(&dev_info->bat->dev.kobj, &ds2780_attr_group); -fail_unregister: - power_supply_unregister(dev_info->bat); -fail: - return ret; -} - -static int ds2780_battery_remove(struct platform_device *pdev) -{ - struct ds2780_device_info *dev_info = platform_get_drvdata(pdev); - - /* - * Remove attributes before unregistering power supply - * because 'bat' will be freed on power_supply_unregister() call. - */ - sysfs_remove_group(&dev_info->bat->dev.kobj, &ds2780_attr_group); - - power_supply_unregister(dev_info->bat); - - return 0; -} - -static struct platform_driver ds2780_battery_driver = { - .driver = { - .name = "ds2780-battery", - }, - .probe = ds2780_battery_probe, - .remove = ds2780_battery_remove, -}; - -module_platform_driver(ds2780_battery_driver); - -MODULE_LICENSE("GPL"); -MODULE_AUTHOR("Clifton Barnes "); -MODULE_DESCRIPTION("Maxim/Dallas DS2780 Stand-Alone Fuel Gauage IC driver"); -MODULE_ALIAS("platform:ds2780-battery"); diff --git a/drivers/power/ds2781_battery.c b/drivers/power/ds2781_battery.c deleted file mode 100644 index c3680024f399..000000000000 --- a/drivers/power/ds2781_battery.c +++ /dev/null @@ -1,839 +0,0 @@ -/* - * 1-wire client/driver for the Maxim/Dallas DS2781 Stand-Alone Fuel Gauge IC - * - * Author: Renata Sayakhova - * - * Based on ds2780_battery drivers - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - * - */ - -#include -#include -#include -#include -#include -#include -#include - -#include "../w1/w1.h" -#include "../w1/slaves/w1_ds2781.h" - -/* Current unit measurement in uA for a 1 milli-ohm sense resistor */ -#define DS2781_CURRENT_UNITS 1563 -/* Charge unit measurement in uAh for a 1 milli-ohm sense resistor */ -#define DS2781_CHARGE_UNITS 6250 -/* Number of bytes in user EEPROM space */ -#define DS2781_USER_EEPROM_SIZE (DS2781_EEPROM_BLOCK0_END - \ - DS2781_EEPROM_BLOCK0_START + 1) -/* Number of bytes in parameter EEPROM space */ -#define DS2781_PARAM_EEPROM_SIZE (DS2781_EEPROM_BLOCK1_END - \ - DS2781_EEPROM_BLOCK1_START + 1) - -struct ds2781_device_info { - struct device *dev; - struct power_supply *bat; - struct power_supply_desc bat_desc; - struct device *w1_dev; -}; - -enum current_types { - CURRENT_NOW, - CURRENT_AVG, -}; - -static const char model[] = "DS2781"; -static const char manufacturer[] = "Maxim/Dallas"; - -static inline struct ds2781_device_info * -to_ds2781_device_info(struct power_supply *psy) -{ - return power_supply_get_drvdata(psy); -} - -static inline struct power_supply *to_power_supply(struct device *dev) -{ - return dev_get_drvdata(dev); -} - -static inline int ds2781_battery_io(struct ds2781_device_info *dev_info, - char *buf, int addr, size_t count, int io) -{ - return w1_ds2781_io(dev_info->w1_dev, buf, addr, count, io); -} - -static int w1_ds2781_read(struct ds2781_device_info *dev_info, char *buf, - int addr, size_t count) -{ - return ds2781_battery_io(dev_info, buf, addr, count, 0); -} - -static inline int ds2781_read8(struct ds2781_device_info *dev_info, u8 *val, - int addr) -{ - return ds2781_battery_io(dev_info, val, addr, sizeof(u8), 0); -} - -static int ds2781_read16(struct ds2781_device_info *dev_info, s16 *val, - int addr) -{ - int ret; - u8 raw[2]; - - ret = ds2781_battery_io(dev_info, raw, addr, sizeof(raw), 0); - if (ret < 0) - return ret; - - *val = (raw[0] << 8) | raw[1]; - - return 0; -} - -static inline int ds2781_read_block(struct ds2781_device_info *dev_info, - u8 *val, int addr, size_t count) -{ - return ds2781_battery_io(dev_info, val, addr, count, 0); -} - -static inline int ds2781_write(struct ds2781_device_info *dev_info, u8 *val, - int addr, size_t count) -{ - return ds2781_battery_io(dev_info, val, addr, count, 1); -} - -static inline int ds2781_store_eeprom(struct device *dev, int addr) -{ - return w1_ds2781_eeprom_cmd(dev, addr, W1_DS2781_COPY_DATA); -} - -static inline int ds2781_recall_eeprom(struct device *dev, int addr) -{ - return w1_ds2781_eeprom_cmd(dev, addr, W1_DS2781_RECALL_DATA); -} - -static int ds2781_save_eeprom(struct ds2781_device_info *dev_info, int reg) -{ - int ret; - - ret = ds2781_store_eeprom(dev_info->w1_dev, reg); - if (ret < 0) - return ret; - - ret = ds2781_recall_eeprom(dev_info->w1_dev, reg); - if (ret < 0) - return ret; - - return 0; -} - -/* Set sense resistor value in mhos */ -static int ds2781_set_sense_register(struct ds2781_device_info *dev_info, - u8 conductance) -{ - int ret; - - ret = ds2781_write(dev_info, &conductance, - DS2781_RSNSP, sizeof(u8)); - if (ret < 0) - return ret; - - return ds2781_save_eeprom(dev_info, DS2781_RSNSP); -} - -/* Get RSGAIN value from 0 to 1.999 in steps of 0.001 */ -static int ds2781_get_rsgain_register(struct ds2781_device_info *dev_info, - u16 *rsgain) -{ - return ds2781_read16(dev_info, rsgain, DS2781_RSGAIN_MSB); -} - -/* Set RSGAIN value from 0 to 1.999 in steps of 0.001 */ -static int ds2781_set_rsgain_register(struct ds2781_device_info *dev_info, - u16 rsgain) -{ - int ret; - u8 raw[] = {rsgain >> 8, rsgain & 0xFF}; - - ret = ds2781_write(dev_info, raw, - DS2781_RSGAIN_MSB, sizeof(raw)); - if (ret < 0) - return ret; - - return ds2781_save_eeprom(dev_info, DS2781_RSGAIN_MSB); -} - -static int ds2781_get_voltage(struct ds2781_device_info *dev_info, - int *voltage_uV) -{ - int ret; - char val[2]; - int voltage_raw; - - ret = w1_ds2781_read(dev_info, val, DS2781_VOLT_MSB, 2 * sizeof(u8)); - if (ret < 0) - return ret; - /* - * The voltage value is located in 10 bits across the voltage MSB - * and LSB registers in two's compliment form - * Sign bit of the voltage value is in bit 7 of the voltage MSB register - * Bits 9 - 3 of the voltage value are in bits 6 - 0 of the - * voltage MSB register - * Bits 2 - 0 of the voltage value are in bits 7 - 5 of the - * voltage LSB register - */ - voltage_raw = (val[0] << 3) | - (val[1] >> 5); - - /* DS2781 reports voltage in units of 9.76mV, but the battery class - * reports in units of uV, so convert by multiplying by 9760. */ - *voltage_uV = voltage_raw * 9760; - - return 0; -} - -static int ds2781_get_temperature(struct ds2781_device_info *dev_info, - int *temp) -{ - int ret; - char val[2]; - int temp_raw; - - ret = w1_ds2781_read(dev_info, val, DS2781_TEMP_MSB, 2 * sizeof(u8)); - if (ret < 0) - return ret; - /* - * The temperature value is located in 10 bits across the temperature - * MSB and LSB registers in two's compliment form - * Sign bit of the temperature value is in bit 7 of the temperature - * MSB register - * Bits 9 - 3 of the temperature value are in bits 6 - 0 of the - * temperature MSB register - * Bits 2 - 0 of the temperature value are in bits 7 - 5 of the - * temperature LSB register - */ - temp_raw = ((val[0]) << 3) | - (val[1] >> 5); - *temp = temp_raw + (temp_raw / 4); - - return 0; -} - -static int ds2781_get_current(struct ds2781_device_info *dev_info, - enum current_types type, int *current_uA) -{ - int ret, sense_res; - s16 current_raw; - u8 sense_res_raw, reg_msb; - - /* - * The units of measurement for current are dependent on the value of - * the sense resistor. - */ - ret = ds2781_read8(dev_info, &sense_res_raw, DS2781_RSNSP); - if (ret < 0) - return ret; - - if (sense_res_raw == 0) { - dev_err(dev_info->dev, "sense resistor value is 0\n"); - return -EINVAL; - } - sense_res = 1000 / sense_res_raw; - - if (type == CURRENT_NOW) - reg_msb = DS2781_CURRENT_MSB; - else if (type == CURRENT_AVG) - reg_msb = DS2781_IAVG_MSB; - else - return -EINVAL; - - /* - * The current value is located in 16 bits across the current MSB - * and LSB registers in two's compliment form - * Sign bit of the current value is in bit 7 of the current MSB register - * Bits 14 - 8 of the current value are in bits 6 - 0 of the current - * MSB register - * Bits 7 - 0 of the current value are in bits 7 - 0 of the current - * LSB register - */ - ret = ds2781_read16(dev_info, ¤t_raw, reg_msb); - if (ret < 0) - return ret; - - *current_uA = current_raw * (DS2781_CURRENT_UNITS / sense_res); - return 0; -} - -static int ds2781_get_accumulated_current(struct ds2781_device_info *dev_info, - int *accumulated_current) -{ - int ret, sense_res; - s16 current_raw; - u8 sense_res_raw; - - /* - * The units of measurement for accumulated current are dependent on - * the value of the sense resistor. - */ - ret = ds2781_read8(dev_info, &sense_res_raw, DS2781_RSNSP); - if (ret < 0) - return ret; - - if (sense_res_raw == 0) { - dev_err(dev_info->dev, "sense resistor value is 0\n"); - return -EINVAL; - } - sense_res = 1000 / sense_res_raw; - - /* - * The ACR value is located in 16 bits across the ACR MSB and - * LSB registers - * Bits 15 - 8 of the ACR value are in bits 7 - 0 of the ACR - * MSB register - * Bits 7 - 0 of the ACR value are in bits 7 - 0 of the ACR - * LSB register - */ - ret = ds2781_read16(dev_info, ¤t_raw, DS2781_ACR_MSB); - if (ret < 0) - return ret; - - *accumulated_current = current_raw * (DS2781_CHARGE_UNITS / sense_res); - return 0; -} - -static int ds2781_get_capacity(struct ds2781_device_info *dev_info, - int *capacity) -{ - int ret; - u8 raw; - - ret = ds2781_read8(dev_info, &raw, DS2781_RARC); - if (ret < 0) - return ret; - - *capacity = raw; - return 0; -} - -static int ds2781_get_status(struct ds2781_device_info *dev_info, int *status) -{ - int ret, current_uA, capacity; - - ret = ds2781_get_current(dev_info, CURRENT_NOW, ¤t_uA); - if (ret < 0) - return ret; - - ret = ds2781_get_capacity(dev_info, &capacity); - if (ret < 0) - return ret; - - if (power_supply_am_i_supplied(dev_info->bat)) { - if (capacity == 100) - *status = POWER_SUPPLY_STATUS_FULL; - else if (current_uA > 50000) - *status = POWER_SUPPLY_STATUS_CHARGING; - else - *status = POWER_SUPPLY_STATUS_NOT_CHARGING; - } else { - *status = POWER_SUPPLY_STATUS_DISCHARGING; - } - return 0; -} - -static int ds2781_get_charge_now(struct ds2781_device_info *dev_info, - int *charge_now) -{ - int ret; - u16 charge_raw; - - /* - * The RAAC value is located in 16 bits across the RAAC MSB and - * LSB registers - * Bits 15 - 8 of the RAAC value are in bits 7 - 0 of the RAAC - * MSB register - * Bits 7 - 0 of the RAAC value are in bits 7 - 0 of the RAAC - * LSB register - */ - ret = ds2781_read16(dev_info, &charge_raw, DS2781_RAAC_MSB); - if (ret < 0) - return ret; - - *charge_now = charge_raw * 1600; - return 0; -} - -static int ds2781_get_control_register(struct ds2781_device_info *dev_info, - u8 *control_reg) -{ - return ds2781_read8(dev_info, control_reg, DS2781_CONTROL); -} - -static int ds2781_set_control_register(struct ds2781_device_info *dev_info, - u8 control_reg) -{ - int ret; - - ret = ds2781_write(dev_info, &control_reg, - DS2781_CONTROL, sizeof(u8)); - if (ret < 0) - return ret; - - return ds2781_save_eeprom(dev_info, DS2781_CONTROL); -} - -static int ds2781_battery_get_property(struct power_supply *psy, - enum power_supply_property psp, - union power_supply_propval *val) -{ - int ret = 0; - struct ds2781_device_info *dev_info = to_ds2781_device_info(psy); - - switch (psp) { - case POWER_SUPPLY_PROP_VOLTAGE_NOW: - ret = ds2781_get_voltage(dev_info, &val->intval); - break; - - case POWER_SUPPLY_PROP_TEMP: - ret = ds2781_get_temperature(dev_info, &val->intval); - break; - - case POWER_SUPPLY_PROP_MODEL_NAME: - val->strval = model; - break; - - case POWER_SUPPLY_PROP_MANUFACTURER: - val->strval = manufacturer; - break; - - case POWER_SUPPLY_PROP_CURRENT_NOW: - ret = ds2781_get_current(dev_info, CURRENT_NOW, &val->intval); - break; - - case POWER_SUPPLY_PROP_CURRENT_AVG: - ret = ds2781_get_current(dev_info, CURRENT_AVG, &val->intval); - break; - - case POWER_SUPPLY_PROP_STATUS: - ret = ds2781_get_status(dev_info, &val->intval); - break; - - case POWER_SUPPLY_PROP_CAPACITY: - ret = ds2781_get_capacity(dev_info, &val->intval); - break; - - case POWER_SUPPLY_PROP_CHARGE_COUNTER: - ret = ds2781_get_accumulated_current(dev_info, &val->intval); - break; - - case POWER_SUPPLY_PROP_CHARGE_NOW: - ret = ds2781_get_charge_now(dev_info, &val->intval); - break; - - default: - ret = -EINVAL; - } - - return ret; -} - -static enum power_supply_property ds2781_battery_props[] = { - POWER_SUPPLY_PROP_STATUS, - POWER_SUPPLY_PROP_VOLTAGE_NOW, - POWER_SUPPLY_PROP_TEMP, - POWER_SUPPLY_PROP_MODEL_NAME, - POWER_SUPPLY_PROP_MANUFACTURER, - POWER_SUPPLY_PROP_CURRENT_NOW, - POWER_SUPPLY_PROP_CURRENT_AVG, - POWER_SUPPLY_PROP_CAPACITY, - POWER_SUPPLY_PROP_CHARGE_COUNTER, - POWER_SUPPLY_PROP_CHARGE_NOW, -}; - -static ssize_t ds2781_get_pmod_enabled(struct device *dev, - struct device_attribute *attr, - char *buf) -{ - int ret; - u8 control_reg; - struct power_supply *psy = to_power_supply(dev); - struct ds2781_device_info *dev_info = to_ds2781_device_info(psy); - - /* Get power mode */ - ret = ds2781_get_control_register(dev_info, &control_reg); - if (ret < 0) - return ret; - - return sprintf(buf, "%d\n", - !!(control_reg & DS2781_CONTROL_PMOD)); -} - -static ssize_t ds2781_set_pmod_enabled(struct device *dev, - struct device_attribute *attr, - const char *buf, - size_t count) -{ - int ret; - u8 control_reg, new_setting; - struct power_supply *psy = to_power_supply(dev); - struct ds2781_device_info *dev_info = to_ds2781_device_info(psy); - - /* Set power mode */ - ret = ds2781_get_control_register(dev_info, &control_reg); - if (ret < 0) - return ret; - - ret = kstrtou8(buf, 0, &new_setting); - if (ret < 0) - return ret; - - if ((new_setting != 0) && (new_setting != 1)) { - dev_err(dev_info->dev, "Invalid pmod setting (0 or 1)\n"); - return -EINVAL; - } - - if (new_setting) - control_reg |= DS2781_CONTROL_PMOD; - else - control_reg &= ~DS2781_CONTROL_PMOD; - - ret = ds2781_set_control_register(dev_info, control_reg); - if (ret < 0) - return ret; - - return count; -} - -static ssize_t ds2781_get_sense_resistor_value(struct device *dev, - struct device_attribute *attr, - char *buf) -{ - int ret; - u8 sense_resistor; - struct power_supply *psy = to_power_supply(dev); - struct ds2781_device_info *dev_info = to_ds2781_device_info(psy); - - ret = ds2781_read8(dev_info, &sense_resistor, DS2781_RSNSP); - if (ret < 0) - return ret; - - ret = sprintf(buf, "%d\n", sense_resistor); - return ret; -} - -static ssize_t ds2781_set_sense_resistor_value(struct device *dev, - struct device_attribute *attr, - const char *buf, - size_t count) -{ - int ret; - u8 new_setting; - struct power_supply *psy = to_power_supply(dev); - struct ds2781_device_info *dev_info = to_ds2781_device_info(psy); - - ret = kstrtou8(buf, 0, &new_setting); - if (ret < 0) - return ret; - - ret = ds2781_set_sense_register(dev_info, new_setting); - if (ret < 0) - return ret; - - return count; -} - -static ssize_t ds2781_get_rsgain_setting(struct device *dev, - struct device_attribute *attr, - char *buf) -{ - int ret; - u16 rsgain; - struct power_supply *psy = to_power_supply(dev); - struct ds2781_device_info *dev_info = to_ds2781_device_info(psy); - - ret = ds2781_get_rsgain_register(dev_info, &rsgain); - if (ret < 0) - return ret; - - return sprintf(buf, "%d\n", rsgain); -} - -static ssize_t ds2781_set_rsgain_setting(struct device *dev, - struct device_attribute *attr, - const char *buf, - size_t count) -{ - int ret; - u16 new_setting; - struct power_supply *psy = to_power_supply(dev); - struct ds2781_device_info *dev_info = to_ds2781_device_info(psy); - - ret = kstrtou16(buf, 0, &new_setting); - if (ret < 0) - return ret; - - /* Gain can only be from 0 to 1.999 in steps of .001 */ - if (new_setting > 1999) { - dev_err(dev_info->dev, "Invalid rsgain setting (0 - 1999)\n"); - return -EINVAL; - } - - ret = ds2781_set_rsgain_register(dev_info, new_setting); - if (ret < 0) - return ret; - - return count; -} - -static ssize_t ds2781_get_pio_pin(struct device *dev, - struct device_attribute *attr, - char *buf) -{ - int ret; - u8 sfr; - struct power_supply *psy = to_power_supply(dev); - struct ds2781_device_info *dev_info = to_ds2781_device_info(psy); - - ret = ds2781_read8(dev_info, &sfr, DS2781_SFR); - if (ret < 0) - return ret; - - ret = sprintf(buf, "%d\n", sfr & DS2781_SFR_PIOSC); - return ret; -} - -static ssize_t ds2781_set_pio_pin(struct device *dev, - struct device_attribute *attr, - const char *buf, - size_t count) -{ - int ret; - u8 new_setting; - struct power_supply *psy = to_power_supply(dev); - struct ds2781_device_info *dev_info = to_ds2781_device_info(psy); - - ret = kstrtou8(buf, 0, &new_setting); - if (ret < 0) - return ret; - - if ((new_setting != 0) && (new_setting != 1)) { - dev_err(dev_info->dev, "Invalid pio_pin setting (0 or 1)\n"); - return -EINVAL; - } - - ret = ds2781_write(dev_info, &new_setting, - DS2781_SFR, sizeof(u8)); - if (ret < 0) - return ret; - - return count; -} - -static ssize_t ds2781_read_param_eeprom_bin(struct file *filp, - struct kobject *kobj, - struct bin_attribute *bin_attr, - char *buf, loff_t off, size_t count) -{ - struct device *dev = container_of(kobj, struct device, kobj); - struct power_supply *psy = to_power_supply(dev); - struct ds2781_device_info *dev_info = to_ds2781_device_info(psy); - - return ds2781_read_block(dev_info, buf, - DS2781_EEPROM_BLOCK1_START + off, count); -} - -static ssize_t ds2781_write_param_eeprom_bin(struct file *filp, - struct kobject *kobj, - struct bin_attribute *bin_attr, - char *buf, loff_t off, size_t count) -{ - struct device *dev = container_of(kobj, struct device, kobj); - struct power_supply *psy = to_power_supply(dev); - struct ds2781_device_info *dev_info = to_ds2781_device_info(psy); - int ret; - - ret = ds2781_write(dev_info, buf, - DS2781_EEPROM_BLOCK1_START + off, count); - if (ret < 0) - return ret; - - ret = ds2781_save_eeprom(dev_info, DS2781_EEPROM_BLOCK1_START); - if (ret < 0) - return ret; - - return count; -} - -static struct bin_attribute ds2781_param_eeprom_bin_attr = { - .attr = { - .name = "param_eeprom", - .mode = S_IRUGO | S_IWUSR, - }, - .size = DS2781_PARAM_EEPROM_SIZE, - .read = ds2781_read_param_eeprom_bin, - .write = ds2781_write_param_eeprom_bin, -}; - -static ssize_t ds2781_read_user_eeprom_bin(struct file *filp, - struct kobject *kobj, - struct bin_attribute *bin_attr, - char *buf, loff_t off, size_t count) -{ - struct device *dev = container_of(kobj, struct device, kobj); - struct power_supply *psy = to_power_supply(dev); - struct ds2781_device_info *dev_info = to_ds2781_device_info(psy); - - return ds2781_read_block(dev_info, buf, - DS2781_EEPROM_BLOCK0_START + off, count); - -} - -static ssize_t ds2781_write_user_eeprom_bin(struct file *filp, - struct kobject *kobj, - struct bin_attribute *bin_attr, - char *buf, loff_t off, size_t count) -{ - struct device *dev = container_of(kobj, struct device, kobj); - struct power_supply *psy = to_power_supply(dev); - struct ds2781_device_info *dev_info = to_ds2781_device_info(psy); - int ret; - - ret = ds2781_write(dev_info, buf, - DS2781_EEPROM_BLOCK0_START + off, count); - if (ret < 0) - return ret; - - ret = ds2781_save_eeprom(dev_info, DS2781_EEPROM_BLOCK0_START); - if (ret < 0) - return ret; - - return count; -} - -static struct bin_attribute ds2781_user_eeprom_bin_attr = { - .attr = { - .name = "user_eeprom", - .mode = S_IRUGO | S_IWUSR, - }, - .size = DS2781_USER_EEPROM_SIZE, - .read = ds2781_read_user_eeprom_bin, - .write = ds2781_write_user_eeprom_bin, -}; - -static DEVICE_ATTR(pmod_enabled, S_IRUGO | S_IWUSR, ds2781_get_pmod_enabled, - ds2781_set_pmod_enabled); -static DEVICE_ATTR(sense_resistor_value, S_IRUGO | S_IWUSR, - ds2781_get_sense_resistor_value, ds2781_set_sense_resistor_value); -static DEVICE_ATTR(rsgain_setting, S_IRUGO | S_IWUSR, ds2781_get_rsgain_setting, - ds2781_set_rsgain_setting); -static DEVICE_ATTR(pio_pin, S_IRUGO | S_IWUSR, ds2781_get_pio_pin, - ds2781_set_pio_pin); - - -static struct attribute *ds2781_attributes[] = { - &dev_attr_pmod_enabled.attr, - &dev_attr_sense_resistor_value.attr, - &dev_attr_rsgain_setting.attr, - &dev_attr_pio_pin.attr, - NULL -}; - -static const struct attribute_group ds2781_attr_group = { - .attrs = ds2781_attributes, -}; - -static int ds2781_battery_probe(struct platform_device *pdev) -{ - struct power_supply_config psy_cfg = {}; - int ret = 0; - struct ds2781_device_info *dev_info; - - dev_info = devm_kzalloc(&pdev->dev, sizeof(*dev_info), GFP_KERNEL); - if (!dev_info) - return -ENOMEM; - - platform_set_drvdata(pdev, dev_info); - - dev_info->dev = &pdev->dev; - dev_info->w1_dev = pdev->dev.parent; - dev_info->bat_desc.name = dev_name(&pdev->dev); - dev_info->bat_desc.type = POWER_SUPPLY_TYPE_BATTERY; - dev_info->bat_desc.properties = ds2781_battery_props; - dev_info->bat_desc.num_properties = ARRAY_SIZE(ds2781_battery_props); - dev_info->bat_desc.get_property = ds2781_battery_get_property; - - psy_cfg.drv_data = dev_info; - - dev_info->bat = power_supply_register(&pdev->dev, &dev_info->bat_desc, - &psy_cfg); - if (IS_ERR(dev_info->bat)) { - dev_err(dev_info->dev, "failed to register battery\n"); - ret = PTR_ERR(dev_info->bat); - goto fail; - } - - ret = sysfs_create_group(&dev_info->bat->dev.kobj, &ds2781_attr_group); - if (ret) { - dev_err(dev_info->dev, "failed to create sysfs group\n"); - goto fail_unregister; - } - - ret = sysfs_create_bin_file(&dev_info->bat->dev.kobj, - &ds2781_param_eeprom_bin_attr); - if (ret) { - dev_err(dev_info->dev, - "failed to create param eeprom bin file"); - goto fail_remove_group; - } - - ret = sysfs_create_bin_file(&dev_info->bat->dev.kobj, - &ds2781_user_eeprom_bin_attr); - if (ret) { - dev_err(dev_info->dev, - "failed to create user eeprom bin file"); - goto fail_remove_bin_file; - } - - return 0; - -fail_remove_bin_file: - sysfs_remove_bin_file(&dev_info->bat->dev.kobj, - &ds2781_param_eeprom_bin_attr); -fail_remove_group: - sysfs_remove_group(&dev_info->bat->dev.kobj, &ds2781_attr_group); -fail_unregister: - power_supply_unregister(dev_info->bat); -fail: - return ret; -} - -static int ds2781_battery_remove(struct platform_device *pdev) -{ - struct ds2781_device_info *dev_info = platform_get_drvdata(pdev); - - /* - * Remove attributes before unregistering power supply - * because 'bat' will be freed on power_supply_unregister() call. - */ - sysfs_remove_group(&dev_info->bat->dev.kobj, &ds2781_attr_group); - - power_supply_unregister(dev_info->bat); - - return 0; -} - -static struct platform_driver ds2781_battery_driver = { - .driver = { - .name = "ds2781-battery", - }, - .probe = ds2781_battery_probe, - .remove = ds2781_battery_remove, -}; -module_platform_driver(ds2781_battery_driver); - -MODULE_LICENSE("GPL"); -MODULE_AUTHOR("Renata Sayakhova "); -MODULE_DESCRIPTION("Maxim/Dallas DS2781 Stand-Alone Fuel Gauage IC driver"); -MODULE_ALIAS("platform:ds2781-battery"); - diff --git a/drivers/power/ds2782_battery.c b/drivers/power/ds2782_battery.c deleted file mode 100644 index a1b7e0592245..000000000000 --- a/drivers/power/ds2782_battery.c +++ /dev/null @@ -1,475 +0,0 @@ -/* - * I2C client/driver for the Maxim/Dallas DS2782 Stand-Alone Fuel Gauge IC - * - * Copyright (C) 2009 Bluewater Systems Ltd - * - * Author: Ryan Mallon - * - * DS2786 added by Yulia Vilensky - * - * UEvent sending added by Evgeny Romanov - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - * - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define DS2782_REG_RARC 0x06 /* Remaining active relative capacity */ - -#define DS278x_REG_VOLT_MSB 0x0c -#define DS278x_REG_TEMP_MSB 0x0a -#define DS278x_REG_CURRENT_MSB 0x0e - -/* EEPROM Block */ -#define DS2782_REG_RSNSP 0x69 /* Sense resistor value */ - -/* Current unit measurement in uA for a 1 milli-ohm sense resistor */ -#define DS2782_CURRENT_UNITS 1563 - -#define DS2786_REG_RARC 0x02 /* Remaining active relative capacity */ - -#define DS2786_CURRENT_UNITS 25 - -#define DS278x_DELAY 1000 - -struct ds278x_info; - -struct ds278x_battery_ops { - int (*get_battery_current)(struct ds278x_info *info, int *current_uA); - int (*get_battery_voltage)(struct ds278x_info *info, int *voltage_uV); - int (*get_battery_capacity)(struct ds278x_info *info, int *capacity); -}; - -#define to_ds278x_info(x) power_supply_get_drvdata(x) - -struct ds278x_info { - struct i2c_client *client; - struct power_supply *battery; - struct power_supply_desc battery_desc; - const struct ds278x_battery_ops *ops; - struct delayed_work bat_work; - int id; - int rsns; - int capacity; - int status; /* State Of Charge */ -}; - -static DEFINE_IDR(battery_id); -static DEFINE_MUTEX(battery_lock); - -static inline int ds278x_read_reg(struct ds278x_info *info, int reg, u8 *val) -{ - int ret; - - ret = i2c_smbus_read_byte_data(info->client, reg); - if (ret < 0) { - dev_err(&info->client->dev, "register read failed\n"); - return ret; - } - - *val = ret; - return 0; -} - -static inline int ds278x_read_reg16(struct ds278x_info *info, int reg_msb, - s16 *val) -{ - int ret; - - ret = i2c_smbus_read_word_data(info->client, reg_msb); - if (ret < 0) { - dev_err(&info->client->dev, "register read failed\n"); - return ret; - } - - *val = swab16(ret); - return 0; -} - -static int ds278x_get_temp(struct ds278x_info *info, int *temp) -{ - s16 raw; - int err; - - /* - * Temperature is measured in units of 0.125 degrees celcius, the - * power_supply class measures temperature in tenths of degrees - * celsius. The temperature value is stored as a 10 bit number, plus - * sign in the upper bits of a 16 bit register. - */ - err = ds278x_read_reg16(info, DS278x_REG_TEMP_MSB, &raw); - if (err) - return err; - *temp = ((raw / 32) * 125) / 100; - return 0; -} - -static int ds2782_get_current(struct ds278x_info *info, int *current_uA) -{ - int sense_res; - int err; - u8 sense_res_raw; - s16 raw; - - /* - * The units of measurement for current are dependent on the value of - * the sense resistor. - */ - err = ds278x_read_reg(info, DS2782_REG_RSNSP, &sense_res_raw); - if (err) - return err; - if (sense_res_raw == 0) { - dev_err(&info->client->dev, "sense resistor value is 0\n"); - return -ENXIO; - } - sense_res = 1000 / sense_res_raw; - - dev_dbg(&info->client->dev, "sense resistor = %d milli-ohms\n", - sense_res); - err = ds278x_read_reg16(info, DS278x_REG_CURRENT_MSB, &raw); - if (err) - return err; - *current_uA = raw * (DS2782_CURRENT_UNITS / sense_res); - return 0; -} - -static int ds2782_get_voltage(struct ds278x_info *info, int *voltage_uV) -{ - s16 raw; - int err; - - /* - * Voltage is measured in units of 4.88mV. The voltage is stored as - * a 10-bit number plus sign, in the upper bits of a 16-bit register - */ - err = ds278x_read_reg16(info, DS278x_REG_VOLT_MSB, &raw); - if (err) - return err; - *voltage_uV = (raw / 32) * 4800; - return 0; -} - -static int ds2782_get_capacity(struct ds278x_info *info, int *capacity) -{ - int err; - u8 raw; - - err = ds278x_read_reg(info, DS2782_REG_RARC, &raw); - if (err) - return err; - *capacity = raw; - return 0; -} - -static int ds2786_get_current(struct ds278x_info *info, int *current_uA) -{ - int err; - s16 raw; - - err = ds278x_read_reg16(info, DS278x_REG_CURRENT_MSB, &raw); - if (err) - return err; - *current_uA = (raw / 16) * (DS2786_CURRENT_UNITS / info->rsns); - return 0; -} - -static int ds2786_get_voltage(struct ds278x_info *info, int *voltage_uV) -{ - s16 raw; - int err; - - /* - * Voltage is measured in units of 1.22mV. The voltage is stored as - * a 12-bit number plus sign, in the upper bits of a 16-bit register - */ - err = ds278x_read_reg16(info, DS278x_REG_VOLT_MSB, &raw); - if (err) - return err; - *voltage_uV = (raw / 8) * 1220; - return 0; -} - -static int ds2786_get_capacity(struct ds278x_info *info, int *capacity) -{ - int err; - u8 raw; - - err = ds278x_read_reg(info, DS2786_REG_RARC, &raw); - if (err) - return err; - /* Relative capacity is displayed with resolution 0.5 % */ - *capacity = raw/2 ; - return 0; -} - -static int ds278x_get_status(struct ds278x_info *info, int *status) -{ - int err; - int current_uA; - int capacity; - - err = info->ops->get_battery_current(info, ¤t_uA); - if (err) - return err; - - err = info->ops->get_battery_capacity(info, &capacity); - if (err) - return err; - - info->capacity = capacity; - - if (capacity == 100) - *status = POWER_SUPPLY_STATUS_FULL; - else if (current_uA == 0) - *status = POWER_SUPPLY_STATUS_NOT_CHARGING; - else if (current_uA < 0) - *status = POWER_SUPPLY_STATUS_DISCHARGING; - else - *status = POWER_SUPPLY_STATUS_CHARGING; - - return 0; -} - -static int ds278x_battery_get_property(struct power_supply *psy, - enum power_supply_property prop, - union power_supply_propval *val) -{ - struct ds278x_info *info = to_ds278x_info(psy); - int ret; - - switch (prop) { - case POWER_SUPPLY_PROP_STATUS: - ret = ds278x_get_status(info, &val->intval); - break; - - case POWER_SUPPLY_PROP_CAPACITY: - ret = info->ops->get_battery_capacity(info, &val->intval); - break; - - case POWER_SUPPLY_PROP_VOLTAGE_NOW: - ret = info->ops->get_battery_voltage(info, &val->intval); - break; - - case POWER_SUPPLY_PROP_CURRENT_NOW: - ret = info->ops->get_battery_current(info, &val->intval); - break; - - case POWER_SUPPLY_PROP_TEMP: - ret = ds278x_get_temp(info, &val->intval); - break; - - default: - ret = -EINVAL; - } - - return ret; -} - -static void ds278x_bat_update(struct ds278x_info *info) -{ - int old_status = info->status; - int old_capacity = info->capacity; - - ds278x_get_status(info, &info->status); - - if ((old_status != info->status) || (old_capacity != info->capacity)) - power_supply_changed(info->battery); -} - -static void ds278x_bat_work(struct work_struct *work) -{ - struct ds278x_info *info; - - info = container_of(work, struct ds278x_info, bat_work.work); - ds278x_bat_update(info); - - schedule_delayed_work(&info->bat_work, DS278x_DELAY); -} - -static enum power_supply_property ds278x_battery_props[] = { - POWER_SUPPLY_PROP_STATUS, - POWER_SUPPLY_PROP_CAPACITY, - POWER_SUPPLY_PROP_VOLTAGE_NOW, - POWER_SUPPLY_PROP_CURRENT_NOW, - POWER_SUPPLY_PROP_TEMP, -}; - -static void ds278x_power_supply_init(struct power_supply_desc *battery) -{ - battery->type = POWER_SUPPLY_TYPE_BATTERY; - battery->properties = ds278x_battery_props; - battery->num_properties = ARRAY_SIZE(ds278x_battery_props); - battery->get_property = ds278x_battery_get_property; - battery->external_power_changed = NULL; -} - -static int ds278x_battery_remove(struct i2c_client *client) -{ - struct ds278x_info *info = i2c_get_clientdata(client); - - power_supply_unregister(info->battery); - kfree(info->battery_desc.name); - - mutex_lock(&battery_lock); - idr_remove(&battery_id, info->id); - mutex_unlock(&battery_lock); - - cancel_delayed_work(&info->bat_work); - - kfree(info); - return 0; -} - -#ifdef CONFIG_PM_SLEEP - -static int ds278x_suspend(struct device *dev) -{ - struct i2c_client *client = to_i2c_client(dev); - struct ds278x_info *info = i2c_get_clientdata(client); - - cancel_delayed_work(&info->bat_work); - return 0; -} - -static int ds278x_resume(struct device *dev) -{ - struct i2c_client *client = to_i2c_client(dev); - struct ds278x_info *info = i2c_get_clientdata(client); - - schedule_delayed_work(&info->bat_work, DS278x_DELAY); - return 0; -} -#endif /* CONFIG_PM_SLEEP */ - -static SIMPLE_DEV_PM_OPS(ds278x_battery_pm_ops, ds278x_suspend, ds278x_resume); - -enum ds278x_num_id { - DS2782 = 0, - DS2786, -}; - -static const struct ds278x_battery_ops ds278x_ops[] = { - [DS2782] = { - .get_battery_current = ds2782_get_current, - .get_battery_voltage = ds2782_get_voltage, - .get_battery_capacity = ds2782_get_capacity, - }, - [DS2786] = { - .get_battery_current = ds2786_get_current, - .get_battery_voltage = ds2786_get_voltage, - .get_battery_capacity = ds2786_get_capacity, - } -}; - -static int ds278x_battery_probe(struct i2c_client *client, - const struct i2c_device_id *id) -{ - struct ds278x_platform_data *pdata = client->dev.platform_data; - struct power_supply_config psy_cfg = {}; - struct ds278x_info *info; - int ret; - int num; - - /* - * ds2786 should have the sense resistor value set - * in the platform data - */ - if (id->driver_data == DS2786 && !pdata) { - dev_err(&client->dev, "missing platform data for ds2786\n"); - return -EINVAL; - } - - /* Get an ID for this battery */ - mutex_lock(&battery_lock); - ret = idr_alloc(&battery_id, client, 0, 0, GFP_KERNEL); - mutex_unlock(&battery_lock); - if (ret < 0) - goto fail_id; - num = ret; - - info = kzalloc(sizeof(*info), GFP_KERNEL); - if (!info) { - ret = -ENOMEM; - goto fail_info; - } - - info->battery_desc.name = kasprintf(GFP_KERNEL, "%s-%d", - client->name, num); - if (!info->battery_desc.name) { - ret = -ENOMEM; - goto fail_name; - } - - if (id->driver_data == DS2786) - info->rsns = pdata->rsns; - - i2c_set_clientdata(client, info); - info->client = client; - info->id = num; - info->ops = &ds278x_ops[id->driver_data]; - ds278x_power_supply_init(&info->battery_desc); - psy_cfg.drv_data = info; - - info->capacity = 100; - info->status = POWER_SUPPLY_STATUS_FULL; - - INIT_DELAYED_WORK(&info->bat_work, ds278x_bat_work); - - info->battery = power_supply_register(&client->dev, - &info->battery_desc, &psy_cfg); - if (IS_ERR(info->battery)) { - dev_err(&client->dev, "failed to register battery\n"); - ret = PTR_ERR(info->battery); - goto fail_register; - } else { - schedule_delayed_work(&info->bat_work, DS278x_DELAY); - } - - return 0; - -fail_register: - kfree(info->battery_desc.name); -fail_name: - kfree(info); -fail_info: - mutex_lock(&battery_lock); - idr_remove(&battery_id, num); - mutex_unlock(&battery_lock); -fail_id: - return ret; -} - -static const struct i2c_device_id ds278x_id[] = { - {"ds2782", DS2782}, - {"ds2786", DS2786}, - {}, -}; -MODULE_DEVICE_TABLE(i2c, ds278x_id); - -static struct i2c_driver ds278x_battery_driver = { - .driver = { - .name = "ds2782-battery", - .pm = &ds278x_battery_pm_ops, - }, - .probe = ds278x_battery_probe, - .remove = ds278x_battery_remove, - .id_table = ds278x_id, -}; -module_i2c_driver(ds278x_battery_driver); - -MODULE_AUTHOR("Ryan Mallon"); -MODULE_DESCRIPTION("Maxim/Dallas DS2782 Stand-Alone Fuel Gauage IC driver"); -MODULE_LICENSE("GPL"); diff --git a/drivers/power/generic-adc-battery.c b/drivers/power/generic-adc-battery.c deleted file mode 100644 index edb36bf781b0..000000000000 --- a/drivers/power/generic-adc-battery.c +++ /dev/null @@ -1,432 +0,0 @@ -/* - * Generic battery driver code using IIO - * Copyright (C) 2012, Anish Kumar - * based on jz4740-battery.c - * based on s3c_adc_battery.c - * - * This file is subject to the terms and conditions of the GNU General Public - * License. See the file COPYING in the main directory of this archive for - * more details. - * - */ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define JITTER_DEFAULT 10 /* hope 10ms is enough */ - -enum gab_chan_type { - GAB_VOLTAGE = 0, - GAB_CURRENT, - GAB_POWER, - GAB_MAX_CHAN_TYPE -}; - -/* - * gab_chan_name suggests the standard channel names for commonly used - * channel types. - */ -static const char *const gab_chan_name[] = { - [GAB_VOLTAGE] = "voltage", - [GAB_CURRENT] = "current", - [GAB_POWER] = "power", -}; - -struct gab { - struct power_supply *psy; - struct power_supply_desc psy_desc; - struct iio_channel *channel[GAB_MAX_CHAN_TYPE]; - struct gab_platform_data *pdata; - struct delayed_work bat_work; - int level; - int status; - bool cable_plugged; -}; - -static struct gab *to_generic_bat(struct power_supply *psy) -{ - return power_supply_get_drvdata(psy); -} - -static void gab_ext_power_changed(struct power_supply *psy) -{ - struct gab *adc_bat = to_generic_bat(psy); - - schedule_delayed_work(&adc_bat->bat_work, msecs_to_jiffies(0)); -} - -static const enum power_supply_property gab_props[] = { - POWER_SUPPLY_PROP_STATUS, - POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, - POWER_SUPPLY_PROP_CHARGE_EMPTY_DESIGN, - POWER_SUPPLY_PROP_CHARGE_NOW, - POWER_SUPPLY_PROP_VOLTAGE_NOW, - POWER_SUPPLY_PROP_CURRENT_NOW, - POWER_SUPPLY_PROP_TECHNOLOGY, - POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, - POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, - POWER_SUPPLY_PROP_MODEL_NAME, -}; - -/* - * This properties are set based on the received platform data and this - * should correspond one-to-one with enum chan_type. - */ -static const enum power_supply_property gab_dyn_props[] = { - POWER_SUPPLY_PROP_VOLTAGE_NOW, - POWER_SUPPLY_PROP_CURRENT_NOW, - POWER_SUPPLY_PROP_POWER_NOW, -}; - -static bool gab_charge_finished(struct gab *adc_bat) -{ - struct gab_platform_data *pdata = adc_bat->pdata; - bool ret = gpio_get_value(pdata->gpio_charge_finished); - bool inv = pdata->gpio_inverted; - - if (!gpio_is_valid(pdata->gpio_charge_finished)) - return false; - return ret ^ inv; -} - -static int gab_get_status(struct gab *adc_bat) -{ - struct gab_platform_data *pdata = adc_bat->pdata; - struct power_supply_info *bat_info; - - bat_info = &pdata->battery_info; - if (adc_bat->level == bat_info->charge_full_design) - return POWER_SUPPLY_STATUS_FULL; - return adc_bat->status; -} - -static enum gab_chan_type gab_prop_to_chan(enum power_supply_property psp) -{ - switch (psp) { - case POWER_SUPPLY_PROP_POWER_NOW: - return GAB_POWER; - case POWER_SUPPLY_PROP_VOLTAGE_NOW: - return GAB_VOLTAGE; - case POWER_SUPPLY_PROP_CURRENT_NOW: - return GAB_CURRENT; - default: - WARN_ON(1); - break; - } - return GAB_POWER; -} - -static int read_channel(struct gab *adc_bat, enum power_supply_property psp, - int *result) -{ - int ret; - int chan_index; - - chan_index = gab_prop_to_chan(psp); - ret = iio_read_channel_processed(adc_bat->channel[chan_index], - result); - if (ret < 0) - pr_err("read channel error\n"); - return ret; -} - -static int gab_get_property(struct power_supply *psy, - enum power_supply_property psp, union power_supply_propval *val) -{ - struct gab *adc_bat; - struct gab_platform_data *pdata; - struct power_supply_info *bat_info; - int result = 0; - int ret = 0; - - adc_bat = to_generic_bat(psy); - if (!adc_bat) { - dev_err(&psy->dev, "no battery infos ?!\n"); - return -EINVAL; - } - pdata = adc_bat->pdata; - bat_info = &pdata->battery_info; - - switch (psp) { - case POWER_SUPPLY_PROP_STATUS: - val->intval = gab_get_status(adc_bat); - break; - case POWER_SUPPLY_PROP_CHARGE_EMPTY_DESIGN: - val->intval = 0; - break; - case POWER_SUPPLY_PROP_CHARGE_NOW: - val->intval = pdata->cal_charge(result); - break; - case POWER_SUPPLY_PROP_VOLTAGE_NOW: - case POWER_SUPPLY_PROP_CURRENT_NOW: - case POWER_SUPPLY_PROP_POWER_NOW: - ret = read_channel(adc_bat, psp, &result); - if (ret < 0) - goto err; - val->intval = result; - break; - case POWER_SUPPLY_PROP_TECHNOLOGY: - val->intval = bat_info->technology; - break; - case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: - val->intval = bat_info->voltage_min_design; - break; - case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: - val->intval = bat_info->voltage_max_design; - break; - case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: - val->intval = bat_info->charge_full_design; - break; - case POWER_SUPPLY_PROP_MODEL_NAME: - val->strval = bat_info->name; - break; - default: - return -EINVAL; - } -err: - return ret; -} - -static void gab_work(struct work_struct *work) -{ - struct gab *adc_bat; - struct gab_platform_data *pdata; - struct delayed_work *delayed_work; - bool is_plugged; - int status; - - delayed_work = to_delayed_work(work); - adc_bat = container_of(delayed_work, struct gab, bat_work); - pdata = adc_bat->pdata; - status = adc_bat->status; - - is_plugged = power_supply_am_i_supplied(adc_bat->psy); - adc_bat->cable_plugged = is_plugged; - - if (!is_plugged) - adc_bat->status = POWER_SUPPLY_STATUS_DISCHARGING; - else if (gab_charge_finished(adc_bat)) - adc_bat->status = POWER_SUPPLY_STATUS_NOT_CHARGING; - else - adc_bat->status = POWER_SUPPLY_STATUS_CHARGING; - - if (status != adc_bat->status) - power_supply_changed(adc_bat->psy); -} - -static irqreturn_t gab_charged(int irq, void *dev_id) -{ - struct gab *adc_bat = dev_id; - struct gab_platform_data *pdata = adc_bat->pdata; - int delay; - - delay = pdata->jitter_delay ? pdata->jitter_delay : JITTER_DEFAULT; - schedule_delayed_work(&adc_bat->bat_work, - msecs_to_jiffies(delay)); - return IRQ_HANDLED; -} - -static int gab_probe(struct platform_device *pdev) -{ - struct gab *adc_bat; - struct power_supply_desc *psy_desc; - struct power_supply_config psy_cfg = {}; - struct gab_platform_data *pdata = pdev->dev.platform_data; - enum power_supply_property *properties; - int ret = 0; - int chan; - int index = 0; - - adc_bat = devm_kzalloc(&pdev->dev, sizeof(*adc_bat), GFP_KERNEL); - if (!adc_bat) { - dev_err(&pdev->dev, "failed to allocate memory\n"); - return -ENOMEM; - } - - psy_cfg.drv_data = adc_bat; - psy_desc = &adc_bat->psy_desc; - psy_desc->name = pdata->battery_info.name; - - /* bootup default values for the battery */ - adc_bat->cable_plugged = false; - adc_bat->status = POWER_SUPPLY_STATUS_DISCHARGING; - psy_desc->type = POWER_SUPPLY_TYPE_BATTERY; - psy_desc->get_property = gab_get_property; - psy_desc->external_power_changed = gab_ext_power_changed; - adc_bat->pdata = pdata; - - /* - * copying the static properties and allocating extra memory for holding - * the extra configurable properties received from platform data. - */ - psy_desc->properties = kcalloc(ARRAY_SIZE(gab_props) + - ARRAY_SIZE(gab_chan_name), - sizeof(*psy_desc->properties), - GFP_KERNEL); - if (!psy_desc->properties) { - ret = -ENOMEM; - goto first_mem_fail; - } - - memcpy(psy_desc->properties, gab_props, sizeof(gab_props)); - properties = (enum power_supply_property *) - ((char *)psy_desc->properties + sizeof(gab_props)); - - /* - * getting channel from iio and copying the battery properties - * based on the channel supported by consumer device. - */ - for (chan = 0; chan < ARRAY_SIZE(gab_chan_name); chan++) { - adc_bat->channel[chan] = iio_channel_get(&pdev->dev, - gab_chan_name[chan]); - if (IS_ERR(adc_bat->channel[chan])) { - ret = PTR_ERR(adc_bat->channel[chan]); - adc_bat->channel[chan] = NULL; - } else { - /* copying properties for supported channels only */ - memcpy(properties + sizeof(*(psy_desc->properties)) * index, - &gab_dyn_props[chan], - sizeof(gab_dyn_props[chan])); - index++; - } - } - - /* none of the channels are supported so let's bail out */ - if (index == 0) { - ret = -ENODEV; - goto second_mem_fail; - } - - /* - * Total number of properties is equal to static properties - * plus the dynamic properties.Some properties may not be set - * as come channels may be not be supported by the device.So - * we need to take care of that. - */ - psy_desc->num_properties = ARRAY_SIZE(gab_props) + index; - - adc_bat->psy = power_supply_register(&pdev->dev, psy_desc, &psy_cfg); - if (IS_ERR(adc_bat->psy)) { - ret = PTR_ERR(adc_bat->psy); - goto err_reg_fail; - } - - INIT_DELAYED_WORK(&adc_bat->bat_work, gab_work); - - if (gpio_is_valid(pdata->gpio_charge_finished)) { - int irq; - ret = gpio_request(pdata->gpio_charge_finished, "charged"); - if (ret) - goto gpio_req_fail; - - irq = gpio_to_irq(pdata->gpio_charge_finished); - ret = request_any_context_irq(irq, gab_charged, - IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, - "battery charged", adc_bat); - if (ret < 0) - goto err_gpio; - } - - platform_set_drvdata(pdev, adc_bat); - - /* Schedule timer to check current status */ - schedule_delayed_work(&adc_bat->bat_work, - msecs_to_jiffies(0)); - return 0; - -err_gpio: - gpio_free(pdata->gpio_charge_finished); -gpio_req_fail: - power_supply_unregister(adc_bat->psy); -err_reg_fail: - for (chan = 0; chan < ARRAY_SIZE(gab_chan_name); chan++) { - if (adc_bat->channel[chan]) - iio_channel_release(adc_bat->channel[chan]); - } -second_mem_fail: - kfree(psy_desc->properties); -first_mem_fail: - return ret; -} - -static int gab_remove(struct platform_device *pdev) -{ - int chan; - struct gab *adc_bat = platform_get_drvdata(pdev); - struct gab_platform_data *pdata = adc_bat->pdata; - - power_supply_unregister(adc_bat->psy); - - if (gpio_is_valid(pdata->gpio_charge_finished)) { - free_irq(gpio_to_irq(pdata->gpio_charge_finished), adc_bat); - gpio_free(pdata->gpio_charge_finished); - } - - for (chan = 0; chan < ARRAY_SIZE(gab_chan_name); chan++) { - if (adc_bat->channel[chan]) - iio_channel_release(adc_bat->channel[chan]); - } - - kfree(adc_bat->psy_desc.properties); - cancel_delayed_work(&adc_bat->bat_work); - return 0; -} - -#ifdef CONFIG_PM -static int gab_suspend(struct device *dev) -{ - struct gab *adc_bat = dev_get_drvdata(dev); - - cancel_delayed_work_sync(&adc_bat->bat_work); - adc_bat->status = POWER_SUPPLY_STATUS_UNKNOWN; - return 0; -} - -static int gab_resume(struct device *dev) -{ - struct gab *adc_bat = dev_get_drvdata(dev); - struct gab_platform_data *pdata = adc_bat->pdata; - int delay; - - delay = pdata->jitter_delay ? pdata->jitter_delay : JITTER_DEFAULT; - - /* Schedule timer to check current status */ - schedule_delayed_work(&adc_bat->bat_work, - msecs_to_jiffies(delay)); - return 0; -} - -static const struct dev_pm_ops gab_pm_ops = { - .suspend = gab_suspend, - .resume = gab_resume, -}; - -#define GAB_PM_OPS (&gab_pm_ops) -#else -#define GAB_PM_OPS (NULL) -#endif - -static struct platform_driver gab_driver = { - .driver = { - .name = "generic-adc-battery", - .pm = GAB_PM_OPS - }, - .probe = gab_probe, - .remove = gab_remove, -}; -module_platform_driver(gab_driver); - -MODULE_AUTHOR("anish kumar "); -MODULE_DESCRIPTION("generic battery driver using IIO"); -MODULE_LICENSE("GPL"); diff --git a/drivers/power/goldfish_battery.c b/drivers/power/goldfish_battery.c deleted file mode 100644 index f5c525e4482a..000000000000 --- a/drivers/power/goldfish_battery.c +++ /dev/null @@ -1,256 +0,0 @@ -/* - * Power supply driver for the goldfish emulator - * - * Copyright (C) 2008 Google, Inc. - * Copyright (C) 2012 Intel, Inc. - * Copyright (C) 2013 Intel, Inc. - * Author: Mike Lockwood - * - * This software is licensed under the terms of the GNU General Public - * License version 2, as published by the Free Software Foundation, and - * may be copied, distributed, and modified under those terms. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -struct goldfish_battery_data { - void __iomem *reg_base; - int irq; - spinlock_t lock; - - struct power_supply *battery; - struct power_supply *ac; -}; - -#define GOLDFISH_BATTERY_READ(data, addr) \ - (readl(data->reg_base + addr)) -#define GOLDFISH_BATTERY_WRITE(data, addr, x) \ - (writel(x, data->reg_base + addr)) - -/* - * Temporary variable used between goldfish_battery_probe() and - * goldfish_battery_open(). - */ -static struct goldfish_battery_data *battery_data; - -enum { - /* status register */ - BATTERY_INT_STATUS = 0x00, - /* set this to enable IRQ */ - BATTERY_INT_ENABLE = 0x04, - - BATTERY_AC_ONLINE = 0x08, - BATTERY_STATUS = 0x0C, - BATTERY_HEALTH = 0x10, - BATTERY_PRESENT = 0x14, - BATTERY_CAPACITY = 0x18, - - BATTERY_STATUS_CHANGED = 1U << 0, - AC_STATUS_CHANGED = 1U << 1, - BATTERY_INT_MASK = BATTERY_STATUS_CHANGED | AC_STATUS_CHANGED, -}; - - -static int goldfish_ac_get_property(struct power_supply *psy, - enum power_supply_property psp, - union power_supply_propval *val) -{ - struct goldfish_battery_data *data = power_supply_get_drvdata(psy); - int ret = 0; - - switch (psp) { - case POWER_SUPPLY_PROP_ONLINE: - val->intval = GOLDFISH_BATTERY_READ(data, BATTERY_AC_ONLINE); - break; - default: - ret = -EINVAL; - break; - } - return ret; -} - -static int goldfish_battery_get_property(struct power_supply *psy, - enum power_supply_property psp, - union power_supply_propval *val) -{ - struct goldfish_battery_data *data = power_supply_get_drvdata(psy); - int ret = 0; - - switch (psp) { - case POWER_SUPPLY_PROP_STATUS: - val->intval = GOLDFISH_BATTERY_READ(data, BATTERY_STATUS); - break; - case POWER_SUPPLY_PROP_HEALTH: - val->intval = GOLDFISH_BATTERY_READ(data, BATTERY_HEALTH); - break; - case POWER_SUPPLY_PROP_PRESENT: - val->intval = GOLDFISH_BATTERY_READ(data, BATTERY_PRESENT); - break; - case POWER_SUPPLY_PROP_TECHNOLOGY: - val->intval = POWER_SUPPLY_TECHNOLOGY_LION; - break; - case POWER_SUPPLY_PROP_CAPACITY: - val->intval = GOLDFISH_BATTERY_READ(data, BATTERY_CAPACITY); - break; - default: - ret = -EINVAL; - break; - } - - return ret; -} - -static enum power_supply_property goldfish_battery_props[] = { - POWER_SUPPLY_PROP_STATUS, - POWER_SUPPLY_PROP_HEALTH, - POWER_SUPPLY_PROP_PRESENT, - POWER_SUPPLY_PROP_TECHNOLOGY, - POWER_SUPPLY_PROP_CAPACITY, -}; - -static enum power_supply_property goldfish_ac_props[] = { - POWER_SUPPLY_PROP_ONLINE, -}; - -static irqreturn_t goldfish_battery_interrupt(int irq, void *dev_id) -{ - unsigned long irq_flags; - struct goldfish_battery_data *data = dev_id; - uint32_t status; - - spin_lock_irqsave(&data->lock, irq_flags); - - /* read status flags, which will clear the interrupt */ - status = GOLDFISH_BATTERY_READ(data, BATTERY_INT_STATUS); - status &= BATTERY_INT_MASK; - - if (status & BATTERY_STATUS_CHANGED) - power_supply_changed(data->battery); - if (status & AC_STATUS_CHANGED) - power_supply_changed(data->ac); - - spin_unlock_irqrestore(&data->lock, irq_flags); - return status ? IRQ_HANDLED : IRQ_NONE; -} - -static const struct power_supply_desc battery_desc = { - .properties = goldfish_battery_props, - .num_properties = ARRAY_SIZE(goldfish_battery_props), - .get_property = goldfish_battery_get_property, - .name = "battery", - .type = POWER_SUPPLY_TYPE_BATTERY, -}; - -static const struct power_supply_desc ac_desc = { - .properties = goldfish_ac_props, - .num_properties = ARRAY_SIZE(goldfish_ac_props), - .get_property = goldfish_ac_get_property, - .name = "ac", - .type = POWER_SUPPLY_TYPE_MAINS, -}; - -static int goldfish_battery_probe(struct platform_device *pdev) -{ - int ret; - struct resource *r; - struct goldfish_battery_data *data; - struct power_supply_config psy_cfg = {}; - - data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); - if (data == NULL) - return -ENOMEM; - - spin_lock_init(&data->lock); - - r = platform_get_resource(pdev, IORESOURCE_MEM, 0); - if (r == NULL) { - dev_err(&pdev->dev, "platform_get_resource failed\n"); - return -ENODEV; - } - - data->reg_base = devm_ioremap(&pdev->dev, r->start, resource_size(r)); - if (data->reg_base == NULL) { - dev_err(&pdev->dev, "unable to remap MMIO\n"); - return -ENOMEM; - } - - data->irq = platform_get_irq(pdev, 0); - if (data->irq < 0) { - dev_err(&pdev->dev, "platform_get_irq failed\n"); - return -ENODEV; - } - - ret = devm_request_irq(&pdev->dev, data->irq, goldfish_battery_interrupt, - IRQF_SHARED, pdev->name, data); - if (ret) - return ret; - - psy_cfg.drv_data = data; - - data->ac = power_supply_register(&pdev->dev, &ac_desc, &psy_cfg); - if (IS_ERR(data->ac)) - return PTR_ERR(data->ac); - - data->battery = power_supply_register(&pdev->dev, &battery_desc, - &psy_cfg); - if (IS_ERR(data->battery)) { - power_supply_unregister(data->ac); - return PTR_ERR(data->battery); - } - - platform_set_drvdata(pdev, data); - battery_data = data; - - GOLDFISH_BATTERY_WRITE(data, BATTERY_INT_ENABLE, BATTERY_INT_MASK); - return 0; -} - -static int goldfish_battery_remove(struct platform_device *pdev) -{ - struct goldfish_battery_data *data = platform_get_drvdata(pdev); - - power_supply_unregister(data->battery); - power_supply_unregister(data->ac); - battery_data = NULL; - return 0; -} - -static const struct of_device_id goldfish_battery_of_match[] = { - { .compatible = "google,goldfish-battery", }, - {}, -}; -MODULE_DEVICE_TABLE(of, goldfish_battery_of_match); - -static const struct acpi_device_id goldfish_battery_acpi_match[] = { - { "GFSH0001", 0 }, - { }, -}; -MODULE_DEVICE_TABLE(acpi, goldfish_battery_acpi_match); - -static struct platform_driver goldfish_battery_device = { - .probe = goldfish_battery_probe, - .remove = goldfish_battery_remove, - .driver = { - .name = "goldfish-battery", - .of_match_table = goldfish_battery_of_match, - .acpi_match_table = ACPI_PTR(goldfish_battery_acpi_match), - } -}; -module_platform_driver(goldfish_battery_device); - -MODULE_AUTHOR("Mike Lockwood lockwood@android.com"); -MODULE_LICENSE("GPL"); -MODULE_DESCRIPTION("Battery driver for the Goldfish emulator"); diff --git a/drivers/power/gpio-charger.c b/drivers/power/gpio-charger.c deleted file mode 100644 index c5869b1941ac..000000000000 --- a/drivers/power/gpio-charger.c +++ /dev/null @@ -1,280 +0,0 @@ -/* - * Copyright (C) 2010, Lars-Peter Clausen - * Driver for chargers which report their online status through a GPIO pin - * - * 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. - * - * 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., - * 675 Mass Ave, Cambridge, MA 02139, USA. - * - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -struct gpio_charger { - const struct gpio_charger_platform_data *pdata; - unsigned int irq; - bool wakeup_enabled; - - struct power_supply *charger; - struct power_supply_desc charger_desc; -}; - -static irqreturn_t gpio_charger_irq(int irq, void *devid) -{ - struct power_supply *charger = devid; - - power_supply_changed(charger); - - return IRQ_HANDLED; -} - -static inline struct gpio_charger *psy_to_gpio_charger(struct power_supply *psy) -{ - return power_supply_get_drvdata(psy); -} - -static int gpio_charger_get_property(struct power_supply *psy, - enum power_supply_property psp, union power_supply_propval *val) -{ - struct gpio_charger *gpio_charger = psy_to_gpio_charger(psy); - const struct gpio_charger_platform_data *pdata = gpio_charger->pdata; - - switch (psp) { - case POWER_SUPPLY_PROP_ONLINE: - val->intval = !!gpio_get_value_cansleep(pdata->gpio); - val->intval ^= pdata->gpio_active_low; - break; - default: - return -EINVAL; - } - - return 0; -} - -static enum power_supply_property gpio_charger_properties[] = { - POWER_SUPPLY_PROP_ONLINE, -}; - -static -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) - return ERR_PTR(-ENOENT); - - pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); - if (!pdata) - 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) { - if (!strncmp("unknown", chargetype, 7)) - pdata->type = POWER_SUPPLY_TYPE_UNKNOWN; - else if (!strncmp("battery", chargetype, 7)) - pdata->type = POWER_SUPPLY_TYPE_BATTERY; - else if (!strncmp("ups", chargetype, 3)) - pdata->type = POWER_SUPPLY_TYPE_UPS; - else if (!strncmp("mains", chargetype, 5)) - pdata->type = POWER_SUPPLY_TYPE_MAINS; - else if (!strncmp("usb-sdp", chargetype, 7)) - pdata->type = POWER_SUPPLY_TYPE_USB; - else if (!strncmp("usb-dcp", chargetype, 7)) - pdata->type = POWER_SUPPLY_TYPE_USB_DCP; - else if (!strncmp("usb-cdp", chargetype, 7)) - pdata->type = POWER_SUPPLY_TYPE_USB_CDP; - else if (!strncmp("usb-aca", chargetype, 7)) - pdata->type = POWER_SUPPLY_TYPE_USB_ACA; - else - dev_warn(dev, "unknown charger type %s\n", chargetype); - } - - return pdata; -} - -static int gpio_charger_probe(struct platform_device *pdev) -{ - const struct gpio_charger_platform_data *pdata = pdev->dev.platform_data; - struct power_supply_config psy_cfg = {}; - struct gpio_charger *gpio_charger; - struct power_supply_desc *charger_desc; - int ret; - int irq; - - if (!pdata) { - pdata = gpio_charger_parse_dt(&pdev->dev); - if (IS_ERR(pdata)) { - ret = PTR_ERR(pdata); - if (ret != -EPROBE_DEFER) - dev_err(&pdev->dev, "No platform data\n"); - return ret; - } - } - - 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) { - dev_err(&pdev->dev, "Failed to alloc driver structure\n"); - return -ENOMEM; - } - - charger_desc = &gpio_charger->charger_desc; - - charger_desc->name = pdata->name ? pdata->name : "gpio-charger"; - charger_desc->type = pdata->type; - charger_desc->properties = gpio_charger_properties; - charger_desc->num_properties = ARRAY_SIZE(gpio_charger_properties); - charger_desc->get_property = gpio_charger_get_property; - - psy_cfg.supplied_to = pdata->supplied_to; - psy_cfg.num_supplicants = pdata->num_supplicants; - 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, - charger_desc, &psy_cfg); - if (IS_ERR(gpio_charger->charger)) { - ret = PTR_ERR(gpio_charger->charger); - dev_err(&pdev->dev, "Failed to register power supply: %d\n", - ret); - goto err_gpio_free; - } - - irq = gpio_to_irq(pdata->gpio); - if (irq > 0) { - ret = request_any_context_irq(irq, gpio_charger_irq, - IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, - dev_name(&pdev->dev), gpio_charger->charger); - if (ret < 0) - dev_warn(&pdev->dev, "Failed to request irq: %d\n", ret); - else - gpio_charger->irq = irq; - } - - platform_set_drvdata(pdev, gpio_charger); - - device_init_wakeup(&pdev->dev, 1); - - return 0; - -err_gpio_free: - gpio_free(pdata->gpio); -err_free: - return ret; -} - -static int gpio_charger_remove(struct platform_device *pdev) -{ - struct gpio_charger *gpio_charger = platform_get_drvdata(pdev); - - if (gpio_charger->irq) - free_irq(gpio_charger->irq, gpio_charger->charger); - - power_supply_unregister(gpio_charger->charger); - - gpio_free(gpio_charger->pdata->gpio); - - return 0; -} - -#ifdef CONFIG_PM_SLEEP -static int gpio_charger_suspend(struct device *dev) -{ - struct gpio_charger *gpio_charger = dev_get_drvdata(dev); - - if (device_may_wakeup(dev)) - gpio_charger->wakeup_enabled = - !enable_irq_wake(gpio_charger->irq); - - return 0; -} - -static int gpio_charger_resume(struct device *dev) -{ - struct platform_device *pdev = to_platform_device(dev); - struct gpio_charger *gpio_charger = platform_get_drvdata(pdev); - - if (device_may_wakeup(dev) && gpio_charger->wakeup_enabled) - disable_irq_wake(gpio_charger->irq); - power_supply_changed(gpio_charger->charger); - - return 0; -} -#endif - -static SIMPLE_DEV_PM_OPS(gpio_charger_pm_ops, - gpio_charger_suspend, gpio_charger_resume); - -static const struct of_device_id gpio_charger_match[] = { - { .compatible = "gpio-charger" }, - { } -}; -MODULE_DEVICE_TABLE(of, gpio_charger_match); - -static struct platform_driver gpio_charger_driver = { - .probe = gpio_charger_probe, - .remove = gpio_charger_remove, - .driver = { - .name = "gpio-charger", - .pm = &gpio_charger_pm_ops, - .of_match_table = gpio_charger_match, - }, -}; - -module_platform_driver(gpio_charger_driver); - -MODULE_AUTHOR("Lars-Peter Clausen "); -MODULE_DESCRIPTION("Driver for chargers which report their online status through a GPIO"); -MODULE_LICENSE("GPL"); -MODULE_ALIAS("platform:gpio-charger"); diff --git a/drivers/power/intel_mid_battery.c b/drivers/power/intel_mid_battery.c deleted file mode 100644 index 9fa4acc107ca..000000000000 --- a/drivers/power/intel_mid_battery.c +++ /dev/null @@ -1,796 +0,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 - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#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 = - create_singlethread_workqueue(dev_name(dev)); - 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 "); -MODULE_DESCRIPTION("Intel Moorestown PMIC Battery Driver"); -MODULE_LICENSE("GPL"); diff --git a/drivers/power/ipaq_micro_battery.c b/drivers/power/ipaq_micro_battery.c deleted file mode 100644 index 35b01c7d775b..000000000000 --- a/drivers/power/ipaq_micro_battery.c +++ /dev/null @@ -1,316 +0,0 @@ -/* - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - * - * h3xxx atmel micro companion support, battery subdevice - * based on previous kernel 2.4 version - * Author : Alessandro Gardich - * Author : Linus Walleij - * - */ - -#include -#include -#include -#include -#include -#include - -#define BATT_PERIOD 100000 /* 100 seconds in milliseconds */ - -#define MICRO_BATT_CHEM_ALKALINE 0x01 -#define MICRO_BATT_CHEM_NICD 0x02 -#define MICRO_BATT_CHEM_NIMH 0x03 -#define MICRO_BATT_CHEM_LION 0x04 -#define MICRO_BATT_CHEM_LIPOLY 0x05 -#define MICRO_BATT_CHEM_NOT_INSTALLED 0x06 -#define MICRO_BATT_CHEM_UNKNOWN 0xff - -#define MICRO_BATT_STATUS_HIGH 0x01 -#define MICRO_BATT_STATUS_LOW 0x02 -#define MICRO_BATT_STATUS_CRITICAL 0x04 -#define MICRO_BATT_STATUS_CHARGING 0x08 -#define MICRO_BATT_STATUS_CHARGEMAIN 0x10 -#define MICRO_BATT_STATUS_DEAD 0x20 /* Battery will not charge */ -#define MICRO_BATT_STATUS_NOTINSTALLED 0x20 /* For expansion pack batteries */ -#define MICRO_BATT_STATUS_FULL 0x40 /* Battery fully charged */ -#define MICRO_BATT_STATUS_NOBATTERY 0x80 -#define MICRO_BATT_STATUS_UNKNOWN 0xff - -struct micro_battery { - struct ipaq_micro *micro; - struct workqueue_struct *wq; - struct delayed_work update; - u8 ac; - u8 chemistry; - unsigned int voltage; - u16 temperature; - u8 flag; -}; - -static void micro_battery_work(struct work_struct *work) -{ - struct micro_battery *mb = container_of(work, - struct micro_battery, update.work); - struct ipaq_micro_msg msg_battery = { - .id = MSG_BATTERY, - }; - struct ipaq_micro_msg msg_sensor = { - .id = MSG_THERMAL_SENSOR, - }; - - /* First send battery message */ - ipaq_micro_tx_msg_sync(mb->micro, &msg_battery); - if (msg_battery.rx_len < 4) - pr_info("ERROR"); - - /* - * Returned message format: - * byte 0: 0x00 = Not plugged in - * 0x01 = AC adapter plugged in - * byte 1: chemistry - * byte 2: voltage LSB - * byte 3: voltage MSB - * byte 4: flags - * byte 5-9: same for battery 2 - */ - mb->ac = msg_battery.rx_data[0]; - mb->chemistry = msg_battery.rx_data[1]; - mb->voltage = ((((unsigned short)msg_battery.rx_data[3] << 8) + - msg_battery.rx_data[2]) * 5000L) * 1000 / 1024; - mb->flag = msg_battery.rx_data[4]; - - if (msg_battery.rx_len == 9) - pr_debug("second battery ignored\n"); - - /* Then read the sensor */ - ipaq_micro_tx_msg_sync(mb->micro, &msg_sensor); - mb->temperature = msg_sensor.rx_data[1] << 8 | msg_sensor.rx_data[0]; - - queue_delayed_work(mb->wq, &mb->update, msecs_to_jiffies(BATT_PERIOD)); -} - -static int get_capacity(struct power_supply *b) -{ - struct micro_battery *mb = dev_get_drvdata(b->dev.parent); - - switch (mb->flag & 0x07) { - case MICRO_BATT_STATUS_HIGH: - return 100; - break; - case MICRO_BATT_STATUS_LOW: - return 50; - break; - case MICRO_BATT_STATUS_CRITICAL: - return 5; - break; - default: - break; - } - return 0; -} - -static int get_status(struct power_supply *b) -{ - struct micro_battery *mb = dev_get_drvdata(b->dev.parent); - - if (mb->flag == MICRO_BATT_STATUS_UNKNOWN) - return POWER_SUPPLY_STATUS_UNKNOWN; - - if (mb->flag & MICRO_BATT_STATUS_FULL) - return POWER_SUPPLY_STATUS_FULL; - - if ((mb->flag & MICRO_BATT_STATUS_CHARGING) || - (mb->flag & MICRO_BATT_STATUS_CHARGEMAIN)) - return POWER_SUPPLY_STATUS_CHARGING; - - return POWER_SUPPLY_STATUS_DISCHARGING; -} - -static int micro_batt_get_property(struct power_supply *b, - enum power_supply_property psp, - union power_supply_propval *val) -{ - struct micro_battery *mb = dev_get_drvdata(b->dev.parent); - - switch (psp) { - case POWER_SUPPLY_PROP_TECHNOLOGY: - switch (mb->chemistry) { - case MICRO_BATT_CHEM_NICD: - val->intval = POWER_SUPPLY_TECHNOLOGY_NiCd; - break; - case MICRO_BATT_CHEM_NIMH: - val->intval = POWER_SUPPLY_TECHNOLOGY_NiMH; - break; - case MICRO_BATT_CHEM_LION: - val->intval = POWER_SUPPLY_TECHNOLOGY_LION; - break; - case MICRO_BATT_CHEM_LIPOLY: - val->intval = POWER_SUPPLY_TECHNOLOGY_LIPO; - break; - default: - val->intval = POWER_SUPPLY_TECHNOLOGY_UNKNOWN; - break; - }; - break; - case POWER_SUPPLY_PROP_STATUS: - val->intval = get_status(b); - break; - case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: - val->intval = 4700000; - break; - case POWER_SUPPLY_PROP_CAPACITY: - val->intval = get_capacity(b); - break; - case POWER_SUPPLY_PROP_TEMP: - val->intval = mb->temperature; - break; - case POWER_SUPPLY_PROP_VOLTAGE_NOW: - val->intval = mb->voltage; - break; - default: - return -EINVAL; - }; - - return 0; -} - -static int micro_ac_get_property(struct power_supply *b, - enum power_supply_property psp, - union power_supply_propval *val) -{ - struct micro_battery *mb = dev_get_drvdata(b->dev.parent); - - switch (psp) { - case POWER_SUPPLY_PROP_ONLINE: - val->intval = mb->ac; - break; - default: - return -EINVAL; - }; - - return 0; -} - -static enum power_supply_property micro_batt_power_props[] = { - POWER_SUPPLY_PROP_TECHNOLOGY, - POWER_SUPPLY_PROP_STATUS, - POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, - POWER_SUPPLY_PROP_CAPACITY, - POWER_SUPPLY_PROP_TEMP, - POWER_SUPPLY_PROP_VOLTAGE_NOW, -}; - -static const struct power_supply_desc micro_batt_power_desc = { - .name = "main-battery", - .type = POWER_SUPPLY_TYPE_BATTERY, - .properties = micro_batt_power_props, - .num_properties = ARRAY_SIZE(micro_batt_power_props), - .get_property = micro_batt_get_property, - .use_for_apm = 1, -}; - -static enum power_supply_property micro_ac_power_props[] = { - POWER_SUPPLY_PROP_ONLINE, -}; - -static const struct power_supply_desc micro_ac_power_desc = { - .name = "ac", - .type = POWER_SUPPLY_TYPE_MAINS, - .properties = micro_ac_power_props, - .num_properties = ARRAY_SIZE(micro_ac_power_props), - .get_property = micro_ac_get_property, -}; - -static struct power_supply *micro_batt_power, *micro_ac_power; - -static int micro_batt_probe(struct platform_device *pdev) -{ - struct micro_battery *mb; - int ret; - - mb = devm_kzalloc(&pdev->dev, sizeof(*mb), GFP_KERNEL); - if (!mb) - return -ENOMEM; - - mb->micro = dev_get_drvdata(pdev->dev.parent); - mb->wq = create_singlethread_workqueue("ipaq-battery-wq"); - if (!mb->wq) - return -ENOMEM; - - INIT_DELAYED_WORK(&mb->update, micro_battery_work); - platform_set_drvdata(pdev, mb); - queue_delayed_work(mb->wq, &mb->update, 1); - - micro_batt_power = power_supply_register(&pdev->dev, - µ_batt_power_desc, NULL); - if (IS_ERR(micro_batt_power)) { - ret = PTR_ERR(micro_batt_power); - goto batt_err; - } - - micro_ac_power = power_supply_register(&pdev->dev, - µ_ac_power_desc, NULL); - if (IS_ERR(micro_ac_power)) { - ret = PTR_ERR(micro_ac_power); - goto ac_err; - } - - dev_info(&pdev->dev, "iPAQ micro battery driver\n"); - return 0; - -ac_err: - power_supply_unregister(micro_batt_power); -batt_err: - cancel_delayed_work_sync(&mb->update); - destroy_workqueue(mb->wq); - return ret; -} - -static int micro_batt_remove(struct platform_device *pdev) - -{ - struct micro_battery *mb = platform_get_drvdata(pdev); - - power_supply_unregister(micro_ac_power); - power_supply_unregister(micro_batt_power); - cancel_delayed_work_sync(&mb->update); - destroy_workqueue(mb->wq); - - return 0; -} - -static int __maybe_unused micro_batt_suspend(struct device *dev) -{ - struct micro_battery *mb = dev_get_drvdata(dev); - - cancel_delayed_work_sync(&mb->update); - return 0; -} - -static int __maybe_unused micro_batt_resume(struct device *dev) -{ - struct micro_battery *mb = dev_get_drvdata(dev); - - queue_delayed_work(mb->wq, &mb->update, msecs_to_jiffies(BATT_PERIOD)); - return 0; -} - -static const struct dev_pm_ops micro_batt_dev_pm_ops = { - SET_SYSTEM_SLEEP_PM_OPS(micro_batt_suspend, micro_batt_resume) -}; - -static struct platform_driver micro_batt_device_driver = { - .driver = { - .name = "ipaq-micro-battery", - .pm = µ_batt_dev_pm_ops, - }, - .probe = micro_batt_probe, - .remove = micro_batt_remove, -}; -module_platform_driver(micro_batt_device_driver); - -MODULE_LICENSE("GPL"); -MODULE_DESCRIPTION("driver for iPAQ Atmel micro battery"); -MODULE_ALIAS("platform:battery-ipaq-micro"); diff --git a/drivers/power/isp1704_charger.c b/drivers/power/isp1704_charger.c deleted file mode 100644 index 4cd6899b961e..000000000000 --- a/drivers/power/isp1704_charger.c +++ /dev/null @@ -1,559 +0,0 @@ -/* - * ISP1704 USB Charger Detection driver - * - * Copyright (C) 2010 Nokia Corporation - * Copyright (C) 2012 - 2013 Pali Rohár - * - * 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 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 - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -/* Vendor specific Power Control register */ -#define ISP1704_PWR_CTRL 0x3d -#define ISP1704_PWR_CTRL_SWCTRL (1 << 0) -#define ISP1704_PWR_CTRL_DET_COMP (1 << 1) -#define ISP1704_PWR_CTRL_BVALID_RISE (1 << 2) -#define ISP1704_PWR_CTRL_BVALID_FALL (1 << 3) -#define ISP1704_PWR_CTRL_DP_WKPU_EN (1 << 4) -#define ISP1704_PWR_CTRL_VDAT_DET (1 << 5) -#define ISP1704_PWR_CTRL_DPVSRC_EN (1 << 6) -#define ISP1704_PWR_CTRL_HWDETECT (1 << 7) - -#define NXP_VENDOR_ID 0x04cc - -static u16 isp170x_id[] = { - 0x1704, - 0x1707, -}; - -struct isp1704_charger { - struct device *dev; - struct power_supply *psy; - struct power_supply_desc psy_desc; - struct usb_phy *phy; - struct notifier_block nb; - struct work_struct work; - - /* properties */ - char model[8]; - unsigned present:1; - unsigned online:1; - unsigned current_max; -}; - -static inline int isp1704_read(struct isp1704_charger *isp, u32 reg) -{ - return usb_phy_io_read(isp->phy, reg); -} - -static inline int isp1704_write(struct isp1704_charger *isp, u32 reg, u32 val) -{ - return usb_phy_io_write(isp->phy, val, reg); -} - -/* - * Disable/enable the power from the isp1704 if a function for it - * has been provided with platform data. - */ -static void isp1704_charger_set_power(struct isp1704_charger *isp, bool on) -{ - struct isp1704_charger_data *board = isp->dev->platform_data; - - if (board && board->set_power) - board->set_power(on); - else if (board) - gpio_set_value(board->enable_gpio, on); -} - -/* - * Determine is the charging port DCP (dedicated charger) or CDP (Host/HUB - * chargers). - * - * REVISIT: The method is defined in Battery Charging Specification and is - * applicable to any ULPI transceiver. Nothing isp170x specific here. - */ -static inline int isp1704_charger_type(struct isp1704_charger *isp) -{ - u8 reg; - u8 func_ctrl; - u8 otg_ctrl; - int type = POWER_SUPPLY_TYPE_USB_DCP; - - func_ctrl = isp1704_read(isp, ULPI_FUNC_CTRL); - otg_ctrl = isp1704_read(isp, ULPI_OTG_CTRL); - - /* disable pulldowns */ - reg = ULPI_OTG_CTRL_DM_PULLDOWN | ULPI_OTG_CTRL_DP_PULLDOWN; - isp1704_write(isp, ULPI_CLR(ULPI_OTG_CTRL), reg); - - /* full speed */ - isp1704_write(isp, ULPI_CLR(ULPI_FUNC_CTRL), - ULPI_FUNC_CTRL_XCVRSEL_MASK); - isp1704_write(isp, ULPI_SET(ULPI_FUNC_CTRL), - ULPI_FUNC_CTRL_FULL_SPEED); - - /* Enable strong pull-up on DP (1.5K) and reset */ - reg = ULPI_FUNC_CTRL_TERMSELECT | ULPI_FUNC_CTRL_RESET; - isp1704_write(isp, ULPI_SET(ULPI_FUNC_CTRL), reg); - usleep_range(1000, 2000); - - reg = isp1704_read(isp, ULPI_DEBUG); - if ((reg & 3) != 3) - type = POWER_SUPPLY_TYPE_USB_CDP; - - /* recover original state */ - isp1704_write(isp, ULPI_FUNC_CTRL, func_ctrl); - isp1704_write(isp, ULPI_OTG_CTRL, otg_ctrl); - - return type; -} - -/* - * ISP1704 detects PS/2 adapters as charger. To make sure the detected charger - * is actually a dedicated charger, the following steps need to be taken. - */ -static inline int isp1704_charger_verify(struct isp1704_charger *isp) -{ - int ret = 0; - u8 r; - - /* Reset the transceiver */ - r = isp1704_read(isp, ULPI_FUNC_CTRL); - r |= ULPI_FUNC_CTRL_RESET; - isp1704_write(isp, ULPI_FUNC_CTRL, r); - usleep_range(1000, 2000); - - /* Set normal mode */ - r &= ~(ULPI_FUNC_CTRL_RESET | ULPI_FUNC_CTRL_OPMODE_MASK); - isp1704_write(isp, ULPI_FUNC_CTRL, r); - - /* Clear the DP and DM pull-down bits */ - r = ULPI_OTG_CTRL_DP_PULLDOWN | ULPI_OTG_CTRL_DM_PULLDOWN; - isp1704_write(isp, ULPI_CLR(ULPI_OTG_CTRL), r); - - /* Enable strong pull-up on DP (1.5K) and reset */ - r = ULPI_FUNC_CTRL_TERMSELECT | ULPI_FUNC_CTRL_RESET; - isp1704_write(isp, ULPI_SET(ULPI_FUNC_CTRL), r); - usleep_range(1000, 2000); - - /* Read the line state */ - if (!isp1704_read(isp, ULPI_DEBUG)) { - /* Disable strong pull-up on DP (1.5K) */ - isp1704_write(isp, ULPI_CLR(ULPI_FUNC_CTRL), - ULPI_FUNC_CTRL_TERMSELECT); - return 1; - } - - /* Is it a charger or PS/2 connection */ - - /* Enable weak pull-up resistor on DP */ - isp1704_write(isp, ULPI_SET(ISP1704_PWR_CTRL), - ISP1704_PWR_CTRL_DP_WKPU_EN); - - /* Disable strong pull-up on DP (1.5K) */ - isp1704_write(isp, ULPI_CLR(ULPI_FUNC_CTRL), - ULPI_FUNC_CTRL_TERMSELECT); - - /* Enable weak pull-down resistor on DM */ - isp1704_write(isp, ULPI_SET(ULPI_OTG_CTRL), - ULPI_OTG_CTRL_DM_PULLDOWN); - - /* It's a charger if the line states are clear */ - if (!(isp1704_read(isp, ULPI_DEBUG))) - ret = 1; - - /* Disable weak pull-up resistor on DP */ - isp1704_write(isp, ULPI_CLR(ISP1704_PWR_CTRL), - ISP1704_PWR_CTRL_DP_WKPU_EN); - - return ret; -} - -static inline int isp1704_charger_detect(struct isp1704_charger *isp) -{ - unsigned long timeout; - u8 pwr_ctrl; - int ret = 0; - - pwr_ctrl = isp1704_read(isp, ISP1704_PWR_CTRL); - - /* set SW control bit in PWR_CTRL register */ - isp1704_write(isp, ISP1704_PWR_CTRL, - ISP1704_PWR_CTRL_SWCTRL); - - /* enable manual charger detection */ - isp1704_write(isp, ULPI_SET(ISP1704_PWR_CTRL), - ISP1704_PWR_CTRL_SWCTRL - | ISP1704_PWR_CTRL_DPVSRC_EN); - usleep_range(1000, 2000); - - timeout = jiffies + msecs_to_jiffies(300); - do { - /* Check if there is a charger */ - if (isp1704_read(isp, ISP1704_PWR_CTRL) - & ISP1704_PWR_CTRL_VDAT_DET) { - ret = isp1704_charger_verify(isp); - break; - } - } while (!time_after(jiffies, timeout) && isp->online); - - /* recover original state */ - isp1704_write(isp, ISP1704_PWR_CTRL, pwr_ctrl); - - return ret; -} - -static inline int isp1704_charger_detect_dcp(struct isp1704_charger *isp) -{ - if (isp1704_charger_detect(isp) && - isp1704_charger_type(isp) == POWER_SUPPLY_TYPE_USB_DCP) - return true; - else - return false; -} - -static void isp1704_charger_work(struct work_struct *data) -{ - struct isp1704_charger *isp = - container_of(data, struct isp1704_charger, work); - static DEFINE_MUTEX(lock); - - mutex_lock(&lock); - - switch (isp->phy->last_event) { - case USB_EVENT_VBUS: - /* do not call wall charger detection more times */ - if (!isp->present) { - isp->online = true; - isp->present = 1; - isp1704_charger_set_power(isp, 1); - - /* detect wall charger */ - if (isp1704_charger_detect_dcp(isp)) { - isp->psy_desc.type = POWER_SUPPLY_TYPE_USB_DCP; - isp->current_max = 1800; - } else { - isp->psy_desc.type = POWER_SUPPLY_TYPE_USB; - isp->current_max = 500; - } - - /* enable data pullups */ - if (isp->phy->otg->gadget) - usb_gadget_connect(isp->phy->otg->gadget); - } - - if (isp->psy_desc.type != POWER_SUPPLY_TYPE_USB_DCP) { - /* - * Only 500mA here or high speed chirp - * handshaking may break - */ - if (isp->current_max > 500) - isp->current_max = 500; - - if (isp->current_max > 100) - isp->psy_desc.type = POWER_SUPPLY_TYPE_USB_CDP; - } - break; - case USB_EVENT_NONE: - isp->online = false; - isp->present = 0; - isp->current_max = 0; - isp->psy_desc.type = POWER_SUPPLY_TYPE_USB; - - /* - * Disable data pullups. We need to prevent the controller from - * enumerating. - * - * FIXME: This is here to allow charger detection with Host/HUB - * chargers. The pullups may be enabled elsewhere, so this can - * not be the final solution. - */ - if (isp->phy->otg->gadget) - usb_gadget_disconnect(isp->phy->otg->gadget); - - isp1704_charger_set_power(isp, 0); - break; - default: - goto out; - } - - power_supply_changed(isp->psy); -out: - mutex_unlock(&lock); -} - -static int isp1704_notifier_call(struct notifier_block *nb, - unsigned long val, void *v) -{ - struct isp1704_charger *isp = - container_of(nb, struct isp1704_charger, nb); - - schedule_work(&isp->work); - - return NOTIFY_OK; -} - -static int isp1704_charger_get_property(struct power_supply *psy, - enum power_supply_property psp, - union power_supply_propval *val) -{ - struct isp1704_charger *isp = power_supply_get_drvdata(psy); - - switch (psp) { - case POWER_SUPPLY_PROP_PRESENT: - val->intval = isp->present; - break; - case POWER_SUPPLY_PROP_ONLINE: - val->intval = isp->online; - break; - case POWER_SUPPLY_PROP_CURRENT_MAX: - val->intval = isp->current_max; - break; - case POWER_SUPPLY_PROP_MODEL_NAME: - val->strval = isp->model; - break; - case POWER_SUPPLY_PROP_MANUFACTURER: - val->strval = "NXP"; - break; - default: - return -EINVAL; - } - return 0; -} - -static enum power_supply_property power_props[] = { - POWER_SUPPLY_PROP_PRESENT, - POWER_SUPPLY_PROP_ONLINE, - POWER_SUPPLY_PROP_CURRENT_MAX, - POWER_SUPPLY_PROP_MODEL_NAME, - POWER_SUPPLY_PROP_MANUFACTURER, -}; - -static inline int isp1704_test_ulpi(struct isp1704_charger *isp) -{ - int vendor; - int product; - int i; - int ret = -ENODEV; - - /* Test ULPI interface */ - ret = isp1704_write(isp, ULPI_SCRATCH, 0xaa); - if (ret < 0) - return ret; - - ret = isp1704_read(isp, ULPI_SCRATCH); - if (ret < 0) - return ret; - - if (ret != 0xaa) - return -ENODEV; - - /* Verify the product and vendor id matches */ - vendor = isp1704_read(isp, ULPI_VENDOR_ID_LOW); - vendor |= isp1704_read(isp, ULPI_VENDOR_ID_HIGH) << 8; - if (vendor != NXP_VENDOR_ID) - return -ENODEV; - - product = isp1704_read(isp, ULPI_PRODUCT_ID_LOW); - product |= isp1704_read(isp, ULPI_PRODUCT_ID_HIGH) << 8; - - for (i = 0; i < ARRAY_SIZE(isp170x_id); i++) { - if (product == isp170x_id[i]) { - sprintf(isp->model, "isp%x", product); - return product; - } - } - - dev_err(isp->dev, "product id %x not matching known ids", product); - - return -ENODEV; -} - -static int isp1704_charger_probe(struct platform_device *pdev) -{ - struct isp1704_charger *isp; - int ret = -ENODEV; - struct power_supply_config psy_cfg = {}; - - struct isp1704_charger_data *pdata = dev_get_platdata(&pdev->dev); - struct device_node *np = pdev->dev.of_node; - - if (np) { - int gpio = of_get_named_gpio(np, "nxp,enable-gpio", 0); - - if (gpio < 0) { - dev_err(&pdev->dev, "missing DT GPIO nxp,enable-gpio\n"); - return gpio; - } - - pdata = devm_kzalloc(&pdev->dev, - sizeof(struct isp1704_charger_data), GFP_KERNEL); - pdata->enable_gpio = gpio; - - dev_info(&pdev->dev, "init gpio %d\n", pdata->enable_gpio); - - ret = devm_gpio_request_one(&pdev->dev, pdata->enable_gpio, - GPIOF_OUT_INIT_HIGH, "isp1704_reset"); - if (ret) { - dev_err(&pdev->dev, "gpio request failed\n"); - goto fail0; - } - } - - if (!pdata) { - dev_err(&pdev->dev, "missing platform data!\n"); - return -ENODEV; - } - - - isp = devm_kzalloc(&pdev->dev, sizeof(*isp), GFP_KERNEL); - if (!isp) - return -ENOMEM; - - if (np) - isp->phy = devm_usb_get_phy_by_phandle(&pdev->dev, "usb-phy", 0); - else - isp->phy = devm_usb_get_phy(&pdev->dev, USB_PHY_TYPE_USB2); - - if (IS_ERR(isp->phy)) { - ret = PTR_ERR(isp->phy); - dev_err(&pdev->dev, "usb_get_phy failed\n"); - goto fail0; - } - - isp->dev = &pdev->dev; - platform_set_drvdata(pdev, isp); - - isp1704_charger_set_power(isp, 1); - - ret = isp1704_test_ulpi(isp); - if (ret < 0) { - dev_err(&pdev->dev, "isp1704_test_ulpi failed\n"); - goto fail1; - } - - isp->psy_desc.name = "isp1704"; - isp->psy_desc.type = POWER_SUPPLY_TYPE_USB; - isp->psy_desc.properties = power_props; - isp->psy_desc.num_properties = ARRAY_SIZE(power_props); - isp->psy_desc.get_property = isp1704_charger_get_property; - - psy_cfg.drv_data = isp; - - isp->psy = power_supply_register(isp->dev, &isp->psy_desc, &psy_cfg); - if (IS_ERR(isp->psy)) { - ret = PTR_ERR(isp->psy); - dev_err(&pdev->dev, "power_supply_register failed\n"); - goto fail1; - } - - /* - * REVISIT: using work in order to allow the usb notifications to be - * made atomically in the future. - */ - INIT_WORK(&isp->work, isp1704_charger_work); - - isp->nb.notifier_call = isp1704_notifier_call; - - ret = usb_register_notifier(isp->phy, &isp->nb); - if (ret) { - dev_err(&pdev->dev, "usb_register_notifier failed\n"); - goto fail2; - } - - dev_info(isp->dev, "registered with product id %s\n", isp->model); - - /* - * Taking over the D+ pullup. - * - * FIXME: The device will be disconnected if it was already - * enumerated. The charger driver should be always loaded before any - * gadget is loaded. - */ - if (isp->phy->otg->gadget) - usb_gadget_disconnect(isp->phy->otg->gadget); - - if (isp->phy->last_event == USB_EVENT_NONE) - isp1704_charger_set_power(isp, 0); - - /* Detect charger if VBUS is valid (the cable was already plugged). */ - if (isp->phy->last_event == USB_EVENT_VBUS && - !isp->phy->otg->default_a) - schedule_work(&isp->work); - - return 0; -fail2: - power_supply_unregister(isp->psy); -fail1: - isp1704_charger_set_power(isp, 0); -fail0: - dev_err(&pdev->dev, "failed to register isp1704 with error %d\n", ret); - - return ret; -} - -static int isp1704_charger_remove(struct platform_device *pdev) -{ - struct isp1704_charger *isp = platform_get_drvdata(pdev); - - usb_unregister_notifier(isp->phy, &isp->nb); - power_supply_unregister(isp->psy); - isp1704_charger_set_power(isp, 0); - - return 0; -} - -#ifdef CONFIG_OF -static const struct of_device_id omap_isp1704_of_match[] = { - { .compatible = "nxp,isp1704", }, - { .compatible = "nxp,isp1707", }, - {}, -}; -MODULE_DEVICE_TABLE(of, omap_isp1704_of_match); -#endif - -static struct platform_driver isp1704_charger_driver = { - .driver = { - .name = "isp1704_charger", - .of_match_table = of_match_ptr(omap_isp1704_of_match), - }, - .probe = isp1704_charger_probe, - .remove = isp1704_charger_remove, -}; - -module_platform_driver(isp1704_charger_driver); - -MODULE_ALIAS("platform:isp1704_charger"); -MODULE_AUTHOR("Nokia Corporation"); -MODULE_DESCRIPTION("ISP170x USB Charger driver"); -MODULE_LICENSE("GPL"); diff --git a/drivers/power/jz4740-battery.c b/drivers/power/jz4740-battery.c deleted file mode 100644 index 88f04f4d1a70..000000000000 --- a/drivers/power/jz4740-battery.c +++ /dev/null @@ -1,425 +0,0 @@ -/* - * Battery measurement code for Ingenic JZ SOC. - * - * Copyright (C) 2009 Jiejing Zhang - * Copyright (C) 2010, Lars-Peter Clausen - * - * based on tosa_battery.c - * - * Copyright (C) 2008 Marek Vasut -* - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - * - */ - -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -#include -#include - -struct jz_battery { - struct jz_battery_platform_data *pdata; - struct platform_device *pdev; - - void __iomem *base; - - int irq; - int charge_irq; - - const struct mfd_cell *cell; - - int status; - long voltage; - - struct completion read_completion; - - struct power_supply *battery; - struct power_supply_desc battery_desc; - struct delayed_work work; - - struct mutex lock; -}; - -static inline struct jz_battery *psy_to_jz_battery(struct power_supply *psy) -{ - return power_supply_get_drvdata(psy); -} - -static irqreturn_t jz_battery_irq_handler(int irq, void *devid) -{ - struct jz_battery *battery = devid; - - complete(&battery->read_completion); - return IRQ_HANDLED; -} - -static long jz_battery_read_voltage(struct jz_battery *battery) -{ - long t; - unsigned long val; - long voltage; - - mutex_lock(&battery->lock); - - reinit_completion(&battery->read_completion); - - enable_irq(battery->irq); - battery->cell->enable(battery->pdev); - - t = wait_for_completion_interruptible_timeout(&battery->read_completion, - HZ); - - if (t > 0) { - val = readw(battery->base) & 0xfff; - - if (battery->pdata->info.voltage_max_design <= 2500000) - val = (val * 78125UL) >> 7UL; - else - val = ((val * 924375UL) >> 9UL) + 33000; - voltage = (long)val; - } else { - voltage = t ? t : -ETIMEDOUT; - } - - battery->cell->disable(battery->pdev); - disable_irq(battery->irq); - - mutex_unlock(&battery->lock); - - return voltage; -} - -static int jz_battery_get_capacity(struct power_supply *psy) -{ - struct jz_battery *jz_battery = psy_to_jz_battery(psy); - struct power_supply_info *info = &jz_battery->pdata->info; - long voltage; - int ret; - int voltage_span; - - voltage = jz_battery_read_voltage(jz_battery); - - if (voltage < 0) - return voltage; - - voltage_span = info->voltage_max_design - info->voltage_min_design; - ret = ((voltage - info->voltage_min_design) * 100) / voltage_span; - - if (ret > 100) - ret = 100; - else if (ret < 0) - ret = 0; - - return ret; -} - -static int jz_battery_get_property(struct power_supply *psy, - enum power_supply_property psp, union power_supply_propval *val) -{ - struct jz_battery *jz_battery = psy_to_jz_battery(psy); - struct power_supply_info *info = &jz_battery->pdata->info; - long voltage; - - switch (psp) { - case POWER_SUPPLY_PROP_STATUS: - val->intval = jz_battery->status; - break; - case POWER_SUPPLY_PROP_TECHNOLOGY: - val->intval = jz_battery->pdata->info.technology; - break; - case POWER_SUPPLY_PROP_HEALTH: - voltage = jz_battery_read_voltage(jz_battery); - if (voltage < info->voltage_min_design) - val->intval = POWER_SUPPLY_HEALTH_DEAD; - else - val->intval = POWER_SUPPLY_HEALTH_GOOD; - break; - case POWER_SUPPLY_PROP_CAPACITY: - val->intval = jz_battery_get_capacity(psy); - break; - case POWER_SUPPLY_PROP_VOLTAGE_NOW: - val->intval = jz_battery_read_voltage(jz_battery); - if (val->intval < 0) - return val->intval; - break; - case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: - val->intval = info->voltage_max_design; - break; - case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: - val->intval = info->voltage_min_design; - break; - case POWER_SUPPLY_PROP_PRESENT: - val->intval = 1; - break; - default: - return -EINVAL; - } - return 0; -} - -static void jz_battery_external_power_changed(struct power_supply *psy) -{ - struct jz_battery *jz_battery = psy_to_jz_battery(psy); - - mod_delayed_work(system_wq, &jz_battery->work, 0); -} - -static irqreturn_t jz_battery_charge_irq(int irq, void *data) -{ - struct jz_battery *jz_battery = data; - - mod_delayed_work(system_wq, &jz_battery->work, 0); - - return IRQ_HANDLED; -} - -static void jz_battery_update(struct jz_battery *jz_battery) -{ - int status; - long voltage; - bool has_changed = false; - int is_charging; - - if (gpio_is_valid(jz_battery->pdata->gpio_charge)) { - is_charging = gpio_get_value(jz_battery->pdata->gpio_charge); - is_charging ^= jz_battery->pdata->gpio_charge_active_low; - if (is_charging) - status = POWER_SUPPLY_STATUS_CHARGING; - else - status = POWER_SUPPLY_STATUS_NOT_CHARGING; - - if (status != jz_battery->status) { - jz_battery->status = status; - has_changed = true; - } - } - - voltage = jz_battery_read_voltage(jz_battery); - if (voltage >= 0 && abs(voltage - jz_battery->voltage) > 50000) { - jz_battery->voltage = voltage; - has_changed = true; - } - - if (has_changed) - power_supply_changed(jz_battery->battery); -} - -static enum power_supply_property jz_battery_properties[] = { - POWER_SUPPLY_PROP_STATUS, - POWER_SUPPLY_PROP_TECHNOLOGY, - POWER_SUPPLY_PROP_HEALTH, - POWER_SUPPLY_PROP_CAPACITY, - POWER_SUPPLY_PROP_VOLTAGE_NOW, - POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, - POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, - POWER_SUPPLY_PROP_PRESENT, -}; - -static void jz_battery_work(struct work_struct *work) -{ - /* Too small interval will increase system workload */ - const int interval = HZ * 30; - struct jz_battery *jz_battery = container_of(work, struct jz_battery, - work.work); - - jz_battery_update(jz_battery); - schedule_delayed_work(&jz_battery->work, interval); -} - -static int jz_battery_probe(struct platform_device *pdev) -{ - int ret = 0; - struct jz_battery_platform_data *pdata = pdev->dev.parent->platform_data; - struct power_supply_config psy_cfg = {}; - struct jz_battery *jz_battery; - struct power_supply_desc *battery_desc; - struct resource *mem; - - if (!pdata) { - dev_err(&pdev->dev, "No platform_data supplied\n"); - return -ENXIO; - } - - jz_battery = devm_kzalloc(&pdev->dev, sizeof(*jz_battery), GFP_KERNEL); - if (!jz_battery) { - dev_err(&pdev->dev, "Failed to allocate driver structure\n"); - return -ENOMEM; - } - - jz_battery->cell = mfd_get_cell(pdev); - - jz_battery->irq = platform_get_irq(pdev, 0); - if (jz_battery->irq < 0) { - dev_err(&pdev->dev, "Failed to get platform irq: %d\n", ret); - return jz_battery->irq; - } - - mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); - - jz_battery->base = devm_ioremap_resource(&pdev->dev, mem); - if (IS_ERR(jz_battery->base)) - return PTR_ERR(jz_battery->base); - - battery_desc = &jz_battery->battery_desc; - battery_desc->name = pdata->info.name; - battery_desc->type = POWER_SUPPLY_TYPE_BATTERY; - battery_desc->properties = jz_battery_properties; - battery_desc->num_properties = ARRAY_SIZE(jz_battery_properties); - battery_desc->get_property = jz_battery_get_property; - battery_desc->external_power_changed = - jz_battery_external_power_changed; - battery_desc->use_for_apm = 1; - - psy_cfg.drv_data = jz_battery; - - jz_battery->pdata = pdata; - jz_battery->pdev = pdev; - - init_completion(&jz_battery->read_completion); - mutex_init(&jz_battery->lock); - - INIT_DELAYED_WORK(&jz_battery->work, jz_battery_work); - - ret = request_irq(jz_battery->irq, jz_battery_irq_handler, 0, pdev->name, - jz_battery); - if (ret) { - dev_err(&pdev->dev, "Failed to request irq %d\n", ret); - return ret; - } - disable_irq(jz_battery->irq); - - if (gpio_is_valid(pdata->gpio_charge)) { - ret = gpio_request(pdata->gpio_charge, dev_name(&pdev->dev)); - if (ret) { - dev_err(&pdev->dev, "charger state gpio request failed.\n"); - goto err_free_irq; - } - ret = gpio_direction_input(pdata->gpio_charge); - if (ret) { - dev_err(&pdev->dev, "charger state gpio set direction failed.\n"); - goto err_free_gpio; - } - - jz_battery->charge_irq = gpio_to_irq(pdata->gpio_charge); - - if (jz_battery->charge_irq >= 0) { - ret = request_irq(jz_battery->charge_irq, - jz_battery_charge_irq, - IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, - dev_name(&pdev->dev), jz_battery); - if (ret) { - dev_err(&pdev->dev, "Failed to request charge irq: %d\n", ret); - goto err_free_gpio; - } - } - } else { - jz_battery->charge_irq = -1; - } - - if (jz_battery->pdata->info.voltage_max_design <= 2500000) - jz4740_adc_set_config(pdev->dev.parent, JZ_ADC_CONFIG_BAT_MB, - JZ_ADC_CONFIG_BAT_MB); - else - jz4740_adc_set_config(pdev->dev.parent, JZ_ADC_CONFIG_BAT_MB, 0); - - jz_battery->battery = power_supply_register(&pdev->dev, battery_desc, - &psy_cfg); - if (IS_ERR(jz_battery->battery)) { - dev_err(&pdev->dev, "power supply battery register failed.\n"); - ret = PTR_ERR(jz_battery->battery); - goto err_free_charge_irq; - } - - platform_set_drvdata(pdev, jz_battery); - schedule_delayed_work(&jz_battery->work, 0); - - return 0; - -err_free_charge_irq: - if (jz_battery->charge_irq >= 0) - free_irq(jz_battery->charge_irq, jz_battery); -err_free_gpio: - if (gpio_is_valid(pdata->gpio_charge)) - gpio_free(jz_battery->pdata->gpio_charge); -err_free_irq: - free_irq(jz_battery->irq, jz_battery); - return ret; -} - -static int jz_battery_remove(struct platform_device *pdev) -{ - struct jz_battery *jz_battery = platform_get_drvdata(pdev); - - cancel_delayed_work_sync(&jz_battery->work); - - if (gpio_is_valid(jz_battery->pdata->gpio_charge)) { - if (jz_battery->charge_irq >= 0) - free_irq(jz_battery->charge_irq, jz_battery); - gpio_free(jz_battery->pdata->gpio_charge); - } - - power_supply_unregister(jz_battery->battery); - - free_irq(jz_battery->irq, jz_battery); - - return 0; -} - -#ifdef CONFIG_PM -static int jz_battery_suspend(struct device *dev) -{ - struct jz_battery *jz_battery = dev_get_drvdata(dev); - - cancel_delayed_work_sync(&jz_battery->work); - jz_battery->status = POWER_SUPPLY_STATUS_UNKNOWN; - - return 0; -} - -static int jz_battery_resume(struct device *dev) -{ - struct jz_battery *jz_battery = dev_get_drvdata(dev); - - schedule_delayed_work(&jz_battery->work, 0); - - return 0; -} - -static const struct dev_pm_ops jz_battery_pm_ops = { - .suspend = jz_battery_suspend, - .resume = jz_battery_resume, -}; - -#define JZ_BATTERY_PM_OPS (&jz_battery_pm_ops) -#else -#define JZ_BATTERY_PM_OPS NULL -#endif - -static struct platform_driver jz_battery_driver = { - .probe = jz_battery_probe, - .remove = jz_battery_remove, - .driver = { - .name = "jz4740-battery", - .pm = JZ_BATTERY_PM_OPS, - }, -}; - -module_platform_driver(jz_battery_driver); - -MODULE_ALIAS("platform:jz4740-battery"); -MODULE_LICENSE("GPL"); -MODULE_AUTHOR("Lars-Peter Clausen "); -MODULE_DESCRIPTION("JZ4740 SoC battery driver"); diff --git a/drivers/power/lp8727_charger.c b/drivers/power/lp8727_charger.c deleted file mode 100644 index 042fb3dacb46..000000000000 --- a/drivers/power/lp8727_charger.c +++ /dev/null @@ -1,631 +0,0 @@ -/* - * Driver for LP8727 Micro/Mini USB IC with integrated charger - * - * Copyright (C) 2011 Texas Instruments - * Copyright (C) 2011 National Semiconductor - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - * - */ - -#include -#include -#include -#include -#include -#include -#include - -#define LP8788_NUM_INTREGS 2 -#define DEFAULT_DEBOUNCE_MSEC 270 - -/* Registers */ -#define LP8727_CTRL1 0x1 -#define LP8727_CTRL2 0x2 -#define LP8727_SWCTRL 0x3 -#define LP8727_INT1 0x4 -#define LP8727_INT2 0x5 -#define LP8727_STATUS1 0x6 -#define LP8727_STATUS2 0x7 -#define LP8727_CHGCTRL2 0x9 - -/* CTRL1 register */ -#define LP8727_CP_EN BIT(0) -#define LP8727_ADC_EN BIT(1) -#define LP8727_ID200_EN BIT(4) - -/* CTRL2 register */ -#define LP8727_CHGDET_EN BIT(1) -#define LP8727_INT_EN BIT(6) - -/* SWCTRL register */ -#define LP8727_SW_DM1_DM (0x0 << 0) -#define LP8727_SW_DM1_HiZ (0x7 << 0) -#define LP8727_SW_DP2_DP (0x0 << 3) -#define LP8727_SW_DP2_HiZ (0x7 << 3) - -/* INT1 register */ -#define LP8727_IDNO (0xF << 0) -#define LP8727_VBUS BIT(4) - -/* STATUS1 register */ -#define LP8727_CHGSTAT (3 << 4) -#define LP8727_CHPORT BIT(6) -#define LP8727_DCPORT BIT(7) -#define LP8727_STAT_EOC 0x30 - -/* STATUS2 register */ -#define LP8727_TEMP_STAT (3 << 5) -#define LP8727_TEMP_SHIFT 5 - -/* CHGCTRL2 register */ -#define LP8727_ICHG_SHIFT 4 - -enum lp8727_dev_id { - LP8727_ID_NONE, - LP8727_ID_TA, - LP8727_ID_DEDICATED_CHG, - LP8727_ID_USB_CHG, - LP8727_ID_USB_DS, - LP8727_ID_MAX, -}; - -enum lp8727_die_temp { - LP8788_TEMP_75C, - LP8788_TEMP_95C, - LP8788_TEMP_115C, - LP8788_TEMP_135C, -}; - -struct lp8727_psy { - struct power_supply *ac; - struct power_supply *usb; - struct power_supply *batt; -}; - -struct lp8727_chg { - struct device *dev; - struct i2c_client *client; - struct mutex xfer_lock; - struct lp8727_psy *psy; - struct lp8727_platform_data *pdata; - - /* Charger Data */ - enum lp8727_dev_id devid; - struct lp8727_chg_param *chg_param; - - /* Interrupt Handling */ - int irq; - struct delayed_work work; - unsigned long debounce_jiffies; -}; - -static int lp8727_read_bytes(struct lp8727_chg *pchg, u8 reg, u8 *data, u8 len) -{ - s32 ret; - - mutex_lock(&pchg->xfer_lock); - ret = i2c_smbus_read_i2c_block_data(pchg->client, reg, len, data); - mutex_unlock(&pchg->xfer_lock); - - return (ret != len) ? -EIO : 0; -} - -static inline int lp8727_read_byte(struct lp8727_chg *pchg, u8 reg, u8 *data) -{ - return lp8727_read_bytes(pchg, reg, data, 1); -} - -static int lp8727_write_byte(struct lp8727_chg *pchg, u8 reg, u8 data) -{ - int ret; - - mutex_lock(&pchg->xfer_lock); - ret = i2c_smbus_write_byte_data(pchg->client, reg, data); - mutex_unlock(&pchg->xfer_lock); - - return ret; -} - -static bool lp8727_is_charger_attached(const char *name, int id) -{ - if (!strcmp(name, "ac")) - return id == LP8727_ID_TA || id == LP8727_ID_DEDICATED_CHG; - else if (!strcmp(name, "usb")) - return id == LP8727_ID_USB_CHG; - - return id >= LP8727_ID_TA && id <= LP8727_ID_USB_CHG; -} - -static int lp8727_init_device(struct lp8727_chg *pchg) -{ - u8 val; - int ret; - u8 intstat[LP8788_NUM_INTREGS]; - - /* clear interrupts */ - ret = lp8727_read_bytes(pchg, LP8727_INT1, intstat, LP8788_NUM_INTREGS); - if (ret) - return ret; - - val = LP8727_ID200_EN | LP8727_ADC_EN | LP8727_CP_EN; - ret = lp8727_write_byte(pchg, LP8727_CTRL1, val); - if (ret) - return ret; - - val = LP8727_INT_EN | LP8727_CHGDET_EN; - return lp8727_write_byte(pchg, LP8727_CTRL2, val); -} - -static int lp8727_is_dedicated_charger(struct lp8727_chg *pchg) -{ - u8 val; - - lp8727_read_byte(pchg, LP8727_STATUS1, &val); - return val & LP8727_DCPORT; -} - -static int lp8727_is_usb_charger(struct lp8727_chg *pchg) -{ - u8 val; - - lp8727_read_byte(pchg, LP8727_STATUS1, &val); - return val & LP8727_CHPORT; -} - -static inline void lp8727_ctrl_switch(struct lp8727_chg *pchg, u8 sw) -{ - lp8727_write_byte(pchg, LP8727_SWCTRL, sw); -} - -static void lp8727_id_detection(struct lp8727_chg *pchg, u8 id, int vbusin) -{ - struct lp8727_platform_data *pdata = pchg->pdata; - u8 devid = LP8727_ID_NONE; - u8 swctrl = LP8727_SW_DM1_HiZ | LP8727_SW_DP2_HiZ; - - switch (id) { - case 0x5: - devid = LP8727_ID_TA; - pchg->chg_param = pdata ? pdata->ac : NULL; - break; - case 0xB: - if (lp8727_is_dedicated_charger(pchg)) { - pchg->chg_param = pdata ? pdata->ac : NULL; - devid = LP8727_ID_DEDICATED_CHG; - } else if (lp8727_is_usb_charger(pchg)) { - pchg->chg_param = pdata ? pdata->usb : NULL; - devid = LP8727_ID_USB_CHG; - swctrl = LP8727_SW_DM1_DM | LP8727_SW_DP2_DP; - } else if (vbusin) { - devid = LP8727_ID_USB_DS; - swctrl = LP8727_SW_DM1_DM | LP8727_SW_DP2_DP; - } - break; - default: - devid = LP8727_ID_NONE; - pchg->chg_param = NULL; - break; - } - - pchg->devid = devid; - lp8727_ctrl_switch(pchg, swctrl); -} - -static void lp8727_enable_chgdet(struct lp8727_chg *pchg) -{ - u8 val; - - lp8727_read_byte(pchg, LP8727_CTRL2, &val); - val |= LP8727_CHGDET_EN; - lp8727_write_byte(pchg, LP8727_CTRL2, val); -} - -static void lp8727_delayed_func(struct work_struct *_work) -{ - struct lp8727_chg *pchg = container_of(_work, struct lp8727_chg, - work.work); - u8 intstat[LP8788_NUM_INTREGS]; - u8 idno; - u8 vbus; - - if (lp8727_read_bytes(pchg, LP8727_INT1, intstat, LP8788_NUM_INTREGS)) { - dev_err(pchg->dev, "can not read INT registers\n"); - return; - } - - idno = intstat[0] & LP8727_IDNO; - vbus = intstat[0] & LP8727_VBUS; - - lp8727_id_detection(pchg, idno, vbus); - lp8727_enable_chgdet(pchg); - - power_supply_changed(pchg->psy->ac); - power_supply_changed(pchg->psy->usb); - power_supply_changed(pchg->psy->batt); -} - -static irqreturn_t lp8727_isr_func(int irq, void *ptr) -{ - struct lp8727_chg *pchg = ptr; - - schedule_delayed_work(&pchg->work, pchg->debounce_jiffies); - return IRQ_HANDLED; -} - -static int lp8727_setup_irq(struct lp8727_chg *pchg) -{ - int ret; - int irq = pchg->client->irq; - unsigned delay_msec = pchg->pdata ? pchg->pdata->debounce_msec : - DEFAULT_DEBOUNCE_MSEC; - - INIT_DELAYED_WORK(&pchg->work, lp8727_delayed_func); - - if (irq <= 0) { - dev_warn(pchg->dev, "invalid irq number: %d\n", irq); - return 0; - } - - ret = request_threaded_irq(irq, NULL, lp8727_isr_func, - IRQF_TRIGGER_FALLING | IRQF_ONESHOT, - "lp8727_irq", pchg); - - if (ret) - return ret; - - pchg->irq = irq; - pchg->debounce_jiffies = msecs_to_jiffies(delay_msec); - - return 0; -} - -static void lp8727_release_irq(struct lp8727_chg *pchg) -{ - cancel_delayed_work_sync(&pchg->work); - - if (pchg->irq) - free_irq(pchg->irq, pchg); -} - -static enum power_supply_property lp8727_charger_prop[] = { - POWER_SUPPLY_PROP_ONLINE, -}; - -static enum power_supply_property lp8727_battery_prop[] = { - POWER_SUPPLY_PROP_STATUS, - POWER_SUPPLY_PROP_HEALTH, - POWER_SUPPLY_PROP_PRESENT, - POWER_SUPPLY_PROP_VOLTAGE_NOW, - POWER_SUPPLY_PROP_CAPACITY, - POWER_SUPPLY_PROP_TEMP, -}; - -static char *battery_supplied_to[] = { - "main_batt", -}; - -static int lp8727_charger_get_property(struct power_supply *psy, - enum power_supply_property psp, - union power_supply_propval *val) -{ - struct lp8727_chg *pchg = dev_get_drvdata(psy->dev.parent); - - if (psp != POWER_SUPPLY_PROP_ONLINE) - return -EINVAL; - - val->intval = lp8727_is_charger_attached(psy->desc->name, pchg->devid); - - return 0; -} - -static bool lp8727_is_high_temperature(enum lp8727_die_temp temp) -{ - switch (temp) { - case LP8788_TEMP_95C: - case LP8788_TEMP_115C: - case LP8788_TEMP_135C: - return true; - default: - return false; - } -} - -static int lp8727_battery_get_property(struct power_supply *psy, - enum power_supply_property psp, - union power_supply_propval *val) -{ - struct lp8727_chg *pchg = dev_get_drvdata(psy->dev.parent); - struct lp8727_platform_data *pdata = pchg->pdata; - enum lp8727_die_temp temp; - u8 read; - - switch (psp) { - case POWER_SUPPLY_PROP_STATUS: - if (!lp8727_is_charger_attached(psy->desc->name, pchg->devid)) { - val->intval = POWER_SUPPLY_STATUS_DISCHARGING; - return 0; - } - - lp8727_read_byte(pchg, LP8727_STATUS1, &read); - - val->intval = (read & LP8727_CHGSTAT) == LP8727_STAT_EOC ? - POWER_SUPPLY_STATUS_FULL : - POWER_SUPPLY_STATUS_CHARGING; - break; - case POWER_SUPPLY_PROP_HEALTH: - lp8727_read_byte(pchg, LP8727_STATUS2, &read); - temp = (read & LP8727_TEMP_STAT) >> LP8727_TEMP_SHIFT; - - val->intval = lp8727_is_high_temperature(temp) ? - POWER_SUPPLY_HEALTH_OVERHEAT : - POWER_SUPPLY_HEALTH_GOOD; - break; - case POWER_SUPPLY_PROP_PRESENT: - if (!pdata) - return -EINVAL; - - if (pdata->get_batt_present) - val->intval = pdata->get_batt_present(); - break; - case POWER_SUPPLY_PROP_VOLTAGE_NOW: - if (!pdata) - return -EINVAL; - - if (pdata->get_batt_level) - val->intval = pdata->get_batt_level(); - break; - case POWER_SUPPLY_PROP_CAPACITY: - if (!pdata) - return -EINVAL; - - if (pdata->get_batt_capacity) - val->intval = pdata->get_batt_capacity(); - break; - case POWER_SUPPLY_PROP_TEMP: - if (!pdata) - return -EINVAL; - - if (pdata->get_batt_temp) - val->intval = pdata->get_batt_temp(); - break; - default: - break; - } - - return 0; -} - -static void lp8727_charger_changed(struct power_supply *psy) -{ - struct lp8727_chg *pchg = dev_get_drvdata(psy->dev.parent); - u8 eoc_level; - u8 ichg; - u8 val; - - /* skip if no charger exists */ - if (!lp8727_is_charger_attached(psy->desc->name, pchg->devid)) - return; - - /* update charging parameters */ - if (pchg->chg_param) { - eoc_level = pchg->chg_param->eoc_level; - ichg = pchg->chg_param->ichg; - val = (ichg << LP8727_ICHG_SHIFT) | eoc_level; - lp8727_write_byte(pchg, LP8727_CHGCTRL2, val); - } -} - -static const struct power_supply_desc lp8727_ac_desc = { - .name = "ac", - .type = POWER_SUPPLY_TYPE_MAINS, - .properties = lp8727_charger_prop, - .num_properties = ARRAY_SIZE(lp8727_charger_prop), - .get_property = lp8727_charger_get_property, -}; - -static const struct power_supply_desc lp8727_usb_desc = { - .name = "usb", - .type = POWER_SUPPLY_TYPE_USB, - .properties = lp8727_charger_prop, - .num_properties = ARRAY_SIZE(lp8727_charger_prop), - .get_property = lp8727_charger_get_property, -}; - -static const struct power_supply_desc lp8727_batt_desc = { - .name = "main_batt", - .type = POWER_SUPPLY_TYPE_BATTERY, - .properties = lp8727_battery_prop, - .num_properties = ARRAY_SIZE(lp8727_battery_prop), - .get_property = lp8727_battery_get_property, - .external_power_changed = lp8727_charger_changed, -}; - -static int lp8727_register_psy(struct lp8727_chg *pchg) -{ - struct power_supply_config psy_cfg = {}; /* Only for ac and usb */ - struct lp8727_psy *psy; - - psy = devm_kzalloc(pchg->dev, sizeof(*psy), GFP_KERNEL); - if (!psy) - return -ENOMEM; - - pchg->psy = psy; - - psy_cfg.supplied_to = battery_supplied_to; - psy_cfg.num_supplicants = ARRAY_SIZE(battery_supplied_to); - - psy->ac = power_supply_register(pchg->dev, &lp8727_ac_desc, &psy_cfg); - if (IS_ERR(psy->ac)) - goto err_psy_ac; - - psy->usb = power_supply_register(pchg->dev, &lp8727_usb_desc, - &psy_cfg); - if (IS_ERR(psy->usb)) - goto err_psy_usb; - - psy->batt = power_supply_register(pchg->dev, &lp8727_batt_desc, NULL); - if (IS_ERR(psy->batt)) - goto err_psy_batt; - - return 0; - -err_psy_batt: - power_supply_unregister(psy->usb); -err_psy_usb: - power_supply_unregister(psy->ac); -err_psy_ac: - return -EPERM; -} - -static void lp8727_unregister_psy(struct lp8727_chg *pchg) -{ - struct lp8727_psy *psy = pchg->psy; - - if (!psy) - return; - - power_supply_unregister(psy->ac); - power_supply_unregister(psy->usb); - power_supply_unregister(psy->batt); -} - -#ifdef CONFIG_OF -static struct lp8727_chg_param -*lp8727_parse_charge_pdata(struct device *dev, struct device_node *np) -{ - struct lp8727_chg_param *param; - - param = devm_kzalloc(dev, sizeof(*param), GFP_KERNEL); - if (!param) - goto out; - - of_property_read_u8(np, "eoc-level", (u8 *)¶m->eoc_level); - of_property_read_u8(np, "charging-current", (u8 *)¶m->ichg); -out: - return param; -} - -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; - - pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); - if (!pdata) - 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); - - if (!strcmp(type, "ac")) - pdata->ac = lp8727_parse_charge_pdata(dev, child); - - if (!strcmp(type, "usb")) - pdata->usb = lp8727_parse_charge_pdata(dev, child); - } - - return pdata; -} -#else -static struct lp8727_platform_data *lp8727_parse_dt(struct device *dev) -{ - 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) { - 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); - if (!pchg) - return -ENOMEM; - - pchg->client = cl; - pchg->dev = &cl->dev; - pchg->pdata = pdata; - i2c_set_clientdata(cl, pchg); - - mutex_init(&pchg->xfer_lock); - - ret = lp8727_init_device(pchg); - if (ret) { - dev_err(pchg->dev, "i2c communication err: %d", ret); - return ret; - } - - ret = lp8727_register_psy(pchg); - if (ret) { - dev_err(pchg->dev, "power supplies register err: %d", ret); - return ret; - } - - ret = lp8727_setup_irq(pchg); - if (ret) { - dev_err(pchg->dev, "irq handler err: %d", ret); - lp8727_unregister_psy(pchg); - return ret; - } - - return 0; -} - -static int lp8727_remove(struct i2c_client *cl) -{ - struct lp8727_chg *pchg = i2c_get_clientdata(cl); - - lp8727_release_irq(pchg); - lp8727_unregister_psy(pchg); - return 0; -} - -static const struct of_device_id lp8727_dt_ids[] = { - { .compatible = "ti,lp8727", }, - { } -}; -MODULE_DEVICE_TABLE(of, lp8727_dt_ids); - -static const struct i2c_device_id lp8727_ids[] = { - {"lp8727", 0}, - { } -}; -MODULE_DEVICE_TABLE(i2c, lp8727_ids); - -static struct i2c_driver lp8727_driver = { - .driver = { - .name = "lp8727", - .of_match_table = of_match_ptr(lp8727_dt_ids), - }, - .probe = lp8727_probe, - .remove = lp8727_remove, - .id_table = lp8727_ids, -}; -module_i2c_driver(lp8727_driver); - -MODULE_DESCRIPTION("TI/National Semiconductor LP8727 charger driver"); -MODULE_AUTHOR("Milo Kim , Daniel Jeong "); -MODULE_LICENSE("GPL"); diff --git a/drivers/power/lp8788-charger.c b/drivers/power/lp8788-charger.c deleted file mode 100644 index 7321b727d484..000000000000 --- a/drivers/power/lp8788-charger.c +++ /dev/null @@ -1,764 +0,0 @@ -/* - * TI LP8788 MFD - battery charger driver - * - * Copyright 2012 Texas Instruments - * - * Author: Milo(Woogyom) Kim - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - * - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/* register address */ -#define LP8788_CHG_STATUS 0x07 -#define LP8788_CHG_IDCIN 0x13 -#define LP8788_CHG_IBATT 0x14 -#define LP8788_CHG_VTERM 0x15 -#define LP8788_CHG_EOC 0x16 - -/* mask/shift bits */ -#define LP8788_CHG_INPUT_STATE_M 0x03 /* Addr 07h */ -#define LP8788_CHG_STATE_M 0x3C -#define LP8788_CHG_STATE_S 2 -#define LP8788_NO_BATT_M BIT(6) -#define LP8788_BAD_BATT_M BIT(7) -#define LP8788_CHG_IBATT_M 0x1F /* Addr 14h */ -#define LP8788_CHG_VTERM_M 0x0F /* Addr 15h */ -#define LP8788_CHG_EOC_LEVEL_M 0x30 /* Addr 16h */ -#define LP8788_CHG_EOC_LEVEL_S 4 -#define LP8788_CHG_EOC_TIME_M 0x0E -#define LP8788_CHG_EOC_TIME_S 1 -#define LP8788_CHG_EOC_MODE_M BIT(0) - -#define LP8788_CHARGER_NAME "charger" -#define LP8788_BATTERY_NAME "main_batt" - -#define LP8788_CHG_START 0x11 -#define LP8788_CHG_END 0x1C - -#define LP8788_ISEL_MAX 23 -#define LP8788_ISEL_STEP 50 -#define LP8788_VTERM_MIN 4100 -#define LP8788_VTERM_STEP 25 -#define LP8788_MAX_BATT_CAPACITY 100 -#define LP8788_MAX_CHG_IRQS 11 - -enum lp8788_charging_state { - LP8788_OFF, - LP8788_WARM_UP, - LP8788_LOW_INPUT = 0x3, - LP8788_PRECHARGE, - LP8788_CC, - LP8788_CV, - LP8788_MAINTENANCE, - LP8788_BATTERY_FAULT, - LP8788_SYSTEM_SUPPORT = 0xC, - LP8788_HIGH_CURRENT = 0xF, - LP8788_MAX_CHG_STATE, -}; - -enum lp8788_charger_adc_sel { - LP8788_VBATT, - LP8788_BATT_TEMP, - LP8788_NUM_CHG_ADC, -}; - -enum lp8788_charger_input_state { - LP8788_SYSTEM_SUPPLY = 1, - LP8788_FULL_FUNCTION, -}; - -/* - * struct lp8788_chg_irq - * @which : lp8788 interrupt id - * @virq : Linux IRQ number from irq_domain - */ -struct lp8788_chg_irq { - enum lp8788_int_id which; - int virq; -}; - -/* - * struct lp8788_charger - * @lp : used for accessing the registers of mfd lp8788 device - * @charger : power supply driver for the battery charger - * @battery : power supply driver for the battery - * @charger_work : work queue for charger input interrupts - * @chan : iio channels for getting adc values - * eg) battery voltage, capacity and temperature - * @irqs : charger dedicated interrupts - * @num_irqs : total numbers of charger interrupts - * @pdata : charger platform specific data - */ -struct lp8788_charger { - struct lp8788 *lp; - struct power_supply *charger; - struct power_supply *battery; - struct work_struct charger_work; - struct iio_channel *chan[LP8788_NUM_CHG_ADC]; - struct lp8788_chg_irq irqs[LP8788_MAX_CHG_IRQS]; - int num_irqs; - struct lp8788_charger_platform_data *pdata; -}; - -static char *battery_supplied_to[] = { - LP8788_BATTERY_NAME, -}; - -static enum power_supply_property lp8788_charger_prop[] = { - POWER_SUPPLY_PROP_ONLINE, - POWER_SUPPLY_PROP_CURRENT_MAX, -}; - -static enum power_supply_property lp8788_battery_prop[] = { - POWER_SUPPLY_PROP_STATUS, - POWER_SUPPLY_PROP_HEALTH, - POWER_SUPPLY_PROP_PRESENT, - POWER_SUPPLY_PROP_VOLTAGE_NOW, - POWER_SUPPLY_PROP_CAPACITY, - POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT, - POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX, - POWER_SUPPLY_PROP_TEMP, -}; - -static bool lp8788_is_charger_detected(struct lp8788_charger *pchg) -{ - u8 data; - - lp8788_read_byte(pchg->lp, LP8788_CHG_STATUS, &data); - data &= LP8788_CHG_INPUT_STATE_M; - - return data == LP8788_SYSTEM_SUPPLY || data == LP8788_FULL_FUNCTION; -} - -static int lp8788_charger_get_property(struct power_supply *psy, - enum power_supply_property psp, - union power_supply_propval *val) -{ - struct lp8788_charger *pchg = dev_get_drvdata(psy->dev.parent); - u8 read; - - switch (psp) { - case POWER_SUPPLY_PROP_ONLINE: - val->intval = lp8788_is_charger_detected(pchg); - break; - case POWER_SUPPLY_PROP_CURRENT_MAX: - lp8788_read_byte(pchg->lp, LP8788_CHG_IDCIN, &read); - val->intval = LP8788_ISEL_STEP * - (min_t(int, read, LP8788_ISEL_MAX) + 1); - break; - default: - return -EINVAL; - } - - return 0; -} - -static int lp8788_get_battery_status(struct lp8788_charger *pchg, - union power_supply_propval *val) -{ - enum lp8788_charging_state state; - u8 data; - int ret; - - ret = lp8788_read_byte(pchg->lp, LP8788_CHG_STATUS, &data); - if (ret) - return ret; - - state = (data & LP8788_CHG_STATE_M) >> LP8788_CHG_STATE_S; - switch (state) { - case LP8788_OFF: - val->intval = POWER_SUPPLY_STATUS_DISCHARGING; - break; - case LP8788_PRECHARGE: - case LP8788_CC: - case LP8788_CV: - case LP8788_HIGH_CURRENT: - val->intval = POWER_SUPPLY_STATUS_CHARGING; - break; - case LP8788_MAINTENANCE: - val->intval = POWER_SUPPLY_STATUS_FULL; - break; - default: - val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; - break; - } - - return 0; -} - -static int lp8788_get_battery_health(struct lp8788_charger *pchg, - union power_supply_propval *val) -{ - u8 data; - int ret; - - ret = lp8788_read_byte(pchg->lp, LP8788_CHG_STATUS, &data); - if (ret) - return ret; - - if (data & LP8788_NO_BATT_M) - val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; - else if (data & LP8788_BAD_BATT_M) - val->intval = POWER_SUPPLY_HEALTH_DEAD; - else - val->intval = POWER_SUPPLY_HEALTH_GOOD; - - return 0; -} - -static int lp8788_get_battery_present(struct lp8788_charger *pchg, - union power_supply_propval *val) -{ - u8 data; - int ret; - - ret = lp8788_read_byte(pchg->lp, LP8788_CHG_STATUS, &data); - if (ret) - return ret; - - val->intval = !(data & LP8788_NO_BATT_M); - return 0; -} - -static int lp8788_get_vbatt_adc(struct lp8788_charger *pchg, int *result) -{ - struct iio_channel *channel = pchg->chan[LP8788_VBATT]; - - if (!channel) - return -EINVAL; - - return iio_read_channel_processed(channel, result); -} - -static int lp8788_get_battery_voltage(struct lp8788_charger *pchg, - union power_supply_propval *val) -{ - return lp8788_get_vbatt_adc(pchg, &val->intval); -} - -static int lp8788_get_battery_capacity(struct lp8788_charger *pchg, - union power_supply_propval *val) -{ - struct lp8788 *lp = pchg->lp; - struct lp8788_charger_platform_data *pdata = pchg->pdata; - unsigned int max_vbatt; - int vbatt; - enum lp8788_charging_state state; - u8 data; - int ret; - - if (!pdata) - return -EINVAL; - - max_vbatt = pdata->max_vbatt_mv; - if (max_vbatt == 0) - return -EINVAL; - - ret = lp8788_read_byte(lp, LP8788_CHG_STATUS, &data); - if (ret) - return ret; - - state = (data & LP8788_CHG_STATE_M) >> LP8788_CHG_STATE_S; - - if (state == LP8788_MAINTENANCE) { - val->intval = LP8788_MAX_BATT_CAPACITY; - } else { - ret = lp8788_get_vbatt_adc(pchg, &vbatt); - if (ret) - return ret; - - val->intval = (vbatt * LP8788_MAX_BATT_CAPACITY) / max_vbatt; - val->intval = min(val->intval, LP8788_MAX_BATT_CAPACITY); - } - - return 0; -} - -static int lp8788_get_battery_temperature(struct lp8788_charger *pchg, - union power_supply_propval *val) -{ - struct iio_channel *channel = pchg->chan[LP8788_BATT_TEMP]; - int result; - int ret; - - if (!channel) - return -EINVAL; - - ret = iio_read_channel_processed(channel, &result); - if (ret < 0) - return -EINVAL; - - /* unit: 0.1 'C */ - val->intval = result * 10; - - return 0; -} - -static int lp8788_get_battery_charging_current(struct lp8788_charger *pchg, - union power_supply_propval *val) -{ - u8 read; - - lp8788_read_byte(pchg->lp, LP8788_CHG_IBATT, &read); - read &= LP8788_CHG_IBATT_M; - val->intval = LP8788_ISEL_STEP * - (min_t(int, read, LP8788_ISEL_MAX) + 1); - - return 0; -} - -static int lp8788_get_charging_termination_voltage(struct lp8788_charger *pchg, - union power_supply_propval *val) -{ - u8 read; - - lp8788_read_byte(pchg->lp, LP8788_CHG_VTERM, &read); - read &= LP8788_CHG_VTERM_M; - val->intval = LP8788_VTERM_MIN + LP8788_VTERM_STEP * read; - - return 0; -} - -static int lp8788_battery_get_property(struct power_supply *psy, - enum power_supply_property psp, - union power_supply_propval *val) -{ - struct lp8788_charger *pchg = dev_get_drvdata(psy->dev.parent); - - switch (psp) { - case POWER_SUPPLY_PROP_STATUS: - return lp8788_get_battery_status(pchg, val); - case POWER_SUPPLY_PROP_HEALTH: - return lp8788_get_battery_health(pchg, val); - case POWER_SUPPLY_PROP_PRESENT: - return lp8788_get_battery_present(pchg, val); - case POWER_SUPPLY_PROP_VOLTAGE_NOW: - return lp8788_get_battery_voltage(pchg, val); - case POWER_SUPPLY_PROP_CAPACITY: - return lp8788_get_battery_capacity(pchg, val); - case POWER_SUPPLY_PROP_TEMP: - return lp8788_get_battery_temperature(pchg, val); - case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: - return lp8788_get_battery_charging_current(pchg, val); - case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX: - return lp8788_get_charging_termination_voltage(pchg, val); - default: - return -EINVAL; - } -} - -static inline bool lp8788_is_valid_charger_register(u8 addr) -{ - return addr >= LP8788_CHG_START && addr <= LP8788_CHG_END; -} - -static int lp8788_update_charger_params(struct platform_device *pdev, - struct lp8788_charger *pchg) -{ - struct lp8788 *lp = pchg->lp; - struct lp8788_charger_platform_data *pdata = pchg->pdata; - struct lp8788_chg_param *param; - int i; - int ret; - - if (!pdata || !pdata->chg_params) { - dev_info(&pdev->dev, "skip updating charger parameters\n"); - return 0; - } - - /* settting charging parameters */ - for (i = 0; i < pdata->num_chg_params; i++) { - param = pdata->chg_params + i; - - if (!param) - continue; - - if (lp8788_is_valid_charger_register(param->addr)) { - ret = lp8788_write_byte(lp, param->addr, param->val); - if (ret) - return ret; - } - } - - return 0; -} - -static const struct power_supply_desc lp8788_psy_charger_desc = { - .name = LP8788_CHARGER_NAME, - .type = POWER_SUPPLY_TYPE_MAINS, - .properties = lp8788_charger_prop, - .num_properties = ARRAY_SIZE(lp8788_charger_prop), - .get_property = lp8788_charger_get_property, -}; - -static const struct power_supply_desc lp8788_psy_battery_desc = { - .name = LP8788_BATTERY_NAME, - .type = POWER_SUPPLY_TYPE_BATTERY, - .properties = lp8788_battery_prop, - .num_properties = ARRAY_SIZE(lp8788_battery_prop), - .get_property = lp8788_battery_get_property, -}; - -static int lp8788_psy_register(struct platform_device *pdev, - struct lp8788_charger *pchg) -{ - struct power_supply_config charger_cfg = {}; - - charger_cfg.supplied_to = battery_supplied_to; - charger_cfg.num_supplicants = ARRAY_SIZE(battery_supplied_to); - - pchg->charger = power_supply_register(&pdev->dev, - &lp8788_psy_charger_desc, - &charger_cfg); - if (IS_ERR(pchg->charger)) - return -EPERM; - - pchg->battery = power_supply_register(&pdev->dev, - &lp8788_psy_battery_desc, NULL); - if (IS_ERR(pchg->battery)) { - power_supply_unregister(pchg->charger); - return -EPERM; - } - - return 0; -} - -static void lp8788_psy_unregister(struct lp8788_charger *pchg) -{ - power_supply_unregister(pchg->battery); - power_supply_unregister(pchg->charger); -} - -static void lp8788_charger_event(struct work_struct *work) -{ - struct lp8788_charger *pchg = - container_of(work, struct lp8788_charger, charger_work); - struct lp8788_charger_platform_data *pdata = pchg->pdata; - enum lp8788_charger_event event = lp8788_is_charger_detected(pchg); - - pdata->charger_event(pchg->lp, event); -} - -static bool lp8788_find_irq_id(struct lp8788_charger *pchg, int virq, int *id) -{ - bool found = false; - int i; - - for (i = 0; i < pchg->num_irqs; i++) { - if (pchg->irqs[i].virq == virq) { - *id = pchg->irqs[i].which; - found = true; - break; - } - } - - return found; -} - -static irqreturn_t lp8788_charger_irq_thread(int virq, void *ptr) -{ - struct lp8788_charger *pchg = ptr; - struct lp8788_charger_platform_data *pdata = pchg->pdata; - int id = -1; - - if (!lp8788_find_irq_id(pchg, virq, &id)) - return IRQ_NONE; - - switch (id) { - case LP8788_INT_CHG_INPUT_STATE: - case LP8788_INT_CHG_STATE: - case LP8788_INT_EOC: - case LP8788_INT_BATT_LOW: - case LP8788_INT_NO_BATT: - power_supply_changed(pchg->charger); - power_supply_changed(pchg->battery); - break; - default: - break; - } - - /* report charger dectection event if used */ - if (!pdata) - goto irq_handled; - - if (pdata->charger_event && id == LP8788_INT_CHG_INPUT_STATE) - schedule_work(&pchg->charger_work); - -irq_handled: - return IRQ_HANDLED; -} - -static int lp8788_set_irqs(struct platform_device *pdev, - struct lp8788_charger *pchg, const char *name) -{ - struct resource *r; - struct irq_domain *irqdm = pchg->lp->irqdm; - int irq_start; - int irq_end; - int virq; - int nr_irq; - int i; - int ret; - - /* no error even if no irq resource */ - r = platform_get_resource_byname(pdev, IORESOURCE_IRQ, name); - if (!r) - return 0; - - irq_start = r->start; - irq_end = r->end; - - for (i = irq_start; i <= irq_end; i++) { - nr_irq = pchg->num_irqs; - - virq = irq_create_mapping(irqdm, i); - pchg->irqs[nr_irq].virq = virq; - pchg->irqs[nr_irq].which = i; - pchg->num_irqs++; - - ret = request_threaded_irq(virq, NULL, - lp8788_charger_irq_thread, - 0, name, pchg); - if (ret) - break; - } - - if (i <= irq_end) - goto err_free_irq; - - return 0; - -err_free_irq: - for (i = 0; i < pchg->num_irqs; i++) - free_irq(pchg->irqs[i].virq, pchg); - return ret; -} - -static int lp8788_irq_register(struct platform_device *pdev, - struct lp8788_charger *pchg) -{ - const char *name[] = { - LP8788_CHG_IRQ, LP8788_PRSW_IRQ, LP8788_BATT_IRQ - }; - int i; - int ret; - - INIT_WORK(&pchg->charger_work, lp8788_charger_event); - pchg->num_irqs = 0; - - for (i = 0; i < ARRAY_SIZE(name); i++) { - ret = lp8788_set_irqs(pdev, pchg, name[i]); - if (ret) { - dev_warn(&pdev->dev, "irq setup failed: %s\n", name[i]); - return ret; - } - } - - if (pchg->num_irqs > LP8788_MAX_CHG_IRQS) { - dev_err(&pdev->dev, "invalid total number of irqs: %d\n", - pchg->num_irqs); - return -EINVAL; - } - - - return 0; -} - -static void lp8788_irq_unregister(struct platform_device *pdev, - struct lp8788_charger *pchg) -{ - int i; - int irq; - - for (i = 0; i < pchg->num_irqs; i++) { - irq = pchg->irqs[i].virq; - if (!irq) - continue; - - free_irq(irq, pchg); - } -} - -static void lp8788_setup_adc_channel(struct device *dev, - struct lp8788_charger *pchg) -{ - struct lp8788_charger_platform_data *pdata = pchg->pdata; - struct iio_channel *chan; - - if (!pdata) - return; - - /* ADC channel for battery voltage */ - chan = iio_channel_get(dev, pdata->adc_vbatt); - pchg->chan[LP8788_VBATT] = IS_ERR(chan) ? NULL : chan; - - /* ADC channel for battery temperature */ - chan = iio_channel_get(dev, pdata->adc_batt_temp); - pchg->chan[LP8788_BATT_TEMP] = IS_ERR(chan) ? NULL : chan; -} - -static void lp8788_release_adc_channel(struct lp8788_charger *pchg) -{ - int i; - - for (i = 0; i < LP8788_NUM_CHG_ADC; i++) { - if (!pchg->chan[i]) - continue; - - iio_channel_release(pchg->chan[i]); - pchg->chan[i] = NULL; - } -} - -static ssize_t lp8788_show_charger_status(struct device *dev, - struct device_attribute *attr, char *buf) -{ - struct lp8788_charger *pchg = dev_get_drvdata(dev); - enum lp8788_charging_state state; - char *desc[LP8788_MAX_CHG_STATE] = { - [LP8788_OFF] = "CHARGER OFF", - [LP8788_WARM_UP] = "WARM UP", - [LP8788_LOW_INPUT] = "LOW INPUT STATE", - [LP8788_PRECHARGE] = "CHARGING - PRECHARGE", - [LP8788_CC] = "CHARGING - CC", - [LP8788_CV] = "CHARGING - CV", - [LP8788_MAINTENANCE] = "NO CHARGING - MAINTENANCE", - [LP8788_BATTERY_FAULT] = "BATTERY FAULT", - [LP8788_SYSTEM_SUPPORT] = "SYSTEM SUPPORT", - [LP8788_HIGH_CURRENT] = "HIGH CURRENT", - }; - u8 data; - - lp8788_read_byte(pchg->lp, LP8788_CHG_STATUS, &data); - state = (data & LP8788_CHG_STATE_M) >> LP8788_CHG_STATE_S; - - return scnprintf(buf, PAGE_SIZE, "%s\n", desc[state]); -} - -static ssize_t lp8788_show_eoc_time(struct device *dev, - struct device_attribute *attr, char *buf) -{ - struct lp8788_charger *pchg = dev_get_drvdata(dev); - char *stime[] = { "400ms", "5min", "10min", "15min", - "20min", "25min", "30min" "No timeout" }; - u8 val; - - lp8788_read_byte(pchg->lp, LP8788_CHG_EOC, &val); - val = (val & LP8788_CHG_EOC_TIME_M) >> LP8788_CHG_EOC_TIME_S; - - return scnprintf(buf, PAGE_SIZE, "End Of Charge Time: %s\n", - stime[val]); -} - -static ssize_t lp8788_show_eoc_level(struct device *dev, - struct device_attribute *attr, char *buf) -{ - struct lp8788_charger *pchg = dev_get_drvdata(dev); - char *abs_level[] = { "25mA", "49mA", "75mA", "98mA" }; - char *relative_level[] = { "5%", "10%", "15%", "20%" }; - char *level; - u8 val; - u8 mode; - - lp8788_read_byte(pchg->lp, LP8788_CHG_EOC, &val); - - mode = val & LP8788_CHG_EOC_MODE_M; - val = (val & LP8788_CHG_EOC_LEVEL_M) >> LP8788_CHG_EOC_LEVEL_S; - level = mode ? abs_level[val] : relative_level[val]; - - return scnprintf(buf, PAGE_SIZE, "End Of Charge Level: %s\n", level); -} - -static DEVICE_ATTR(charger_status, S_IRUSR, lp8788_show_charger_status, NULL); -static DEVICE_ATTR(eoc_time, S_IRUSR, lp8788_show_eoc_time, NULL); -static DEVICE_ATTR(eoc_level, S_IRUSR, lp8788_show_eoc_level, NULL); - -static struct attribute *lp8788_charger_attr[] = { - &dev_attr_charger_status.attr, - &dev_attr_eoc_time.attr, - &dev_attr_eoc_level.attr, - NULL, -}; - -static const struct attribute_group lp8788_attr_group = { - .attrs = lp8788_charger_attr, -}; - -static int lp8788_charger_probe(struct platform_device *pdev) -{ - struct lp8788 *lp = dev_get_drvdata(pdev->dev.parent); - struct lp8788_charger *pchg; - struct device *dev = &pdev->dev; - int ret; - - pchg = devm_kzalloc(dev, sizeof(struct lp8788_charger), GFP_KERNEL); - if (!pchg) - return -ENOMEM; - - pchg->lp = lp; - pchg->pdata = lp->pdata ? lp->pdata->chg_pdata : NULL; - platform_set_drvdata(pdev, pchg); - - ret = lp8788_update_charger_params(pdev, pchg); - if (ret) - return ret; - - lp8788_setup_adc_channel(&pdev->dev, pchg); - - ret = lp8788_psy_register(pdev, pchg); - if (ret) - return ret; - - ret = sysfs_create_group(&pdev->dev.kobj, &lp8788_attr_group); - if (ret) { - lp8788_psy_unregister(pchg); - return ret; - } - - ret = lp8788_irq_register(pdev, pchg); - if (ret) - dev_warn(dev, "failed to register charger irq: %d\n", ret); - - return 0; -} - -static int lp8788_charger_remove(struct platform_device *pdev) -{ - struct lp8788_charger *pchg = platform_get_drvdata(pdev); - - flush_work(&pchg->charger_work); - lp8788_irq_unregister(pdev, pchg); - sysfs_remove_group(&pdev->dev.kobj, &lp8788_attr_group); - lp8788_psy_unregister(pchg); - lp8788_release_adc_channel(pchg); - - return 0; -} - -static struct platform_driver lp8788_charger_driver = { - .probe = lp8788_charger_probe, - .remove = lp8788_charger_remove, - .driver = { - .name = LP8788_DEV_CHARGER, - }, -}; -module_platform_driver(lp8788_charger_driver); - -MODULE_DESCRIPTION("TI LP8788 Charger Driver"); -MODULE_AUTHOR("Milo Kim"); -MODULE_LICENSE("GPL"); -MODULE_ALIAS("platform:lp8788-charger"); diff --git a/drivers/power/ltc2941-battery-gauge.c b/drivers/power/ltc2941-battery-gauge.c deleted file mode 100644 index 4adf2ba021ce..000000000000 --- a/drivers/power/ltc2941-battery-gauge.c +++ /dev/null @@ -1,514 +0,0 @@ -/* - * I2C client/driver for the Linear Technology LTC2941 and LTC2943 - * Battery Gas Gauge IC - * - * Copyright (C) 2014 Topic Embedded Systems - * - * Author: Auryn Verwegen - * Author: Mike Looijmans - */ -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define I16_MSB(x) ((x >> 8) & 0xFF) -#define I16_LSB(x) (x & 0xFF) - -#define LTC294X_WORK_DELAY 10 /* Update delay in seconds */ - -#define LTC294X_MAX_VALUE 0xFFFF -#define LTC294X_MID_SUPPLY 0x7FFF - -#define LTC2941_MAX_PRESCALER_EXP 7 -#define LTC2943_MAX_PRESCALER_EXP 6 - -enum ltc294x_reg { - LTC294X_REG_STATUS = 0x00, - LTC294X_REG_CONTROL = 0x01, - LTC294X_REG_ACC_CHARGE_MSB = 0x02, - LTC294X_REG_ACC_CHARGE_LSB = 0x03, - LTC294X_REG_THRESH_HIGH_MSB = 0x04, - LTC294X_REG_THRESH_HIGH_LSB = 0x05, - LTC294X_REG_THRESH_LOW_MSB = 0x06, - LTC294X_REG_THRESH_LOW_LSB = 0x07, - LTC294X_REG_VOLTAGE_MSB = 0x08, - LTC294X_REG_VOLTAGE_LSB = 0x09, - LTC294X_REG_CURRENT_MSB = 0x0E, - LTC294X_REG_CURRENT_LSB = 0x0F, - LTC294X_REG_TEMPERATURE_MSB = 0x14, - LTC294X_REG_TEMPERATURE_LSB = 0x15, -}; - -#define LTC2943_REG_CONTROL_MODE_MASK (BIT(7) | BIT(6)) -#define LTC2943_REG_CONTROL_MODE_SCAN BIT(7) -#define LTC294X_REG_CONTROL_PRESCALER_MASK (BIT(5) | BIT(4) | BIT(3)) -#define LTC294X_REG_CONTROL_SHUTDOWN_MASK (BIT(0)) -#define LTC294X_REG_CONTROL_PRESCALER_SET(x) \ - ((x << 3) & LTC294X_REG_CONTROL_PRESCALER_MASK) -#define LTC294X_REG_CONTROL_ALCC_CONFIG_DISABLED 0 - -#define LTC2941_NUM_REGS 0x08 -#define LTC2943_NUM_REGS 0x18 - -struct ltc294x_info { - struct i2c_client *client; /* I2C Client pointer */ - struct power_supply *supply; /* Supply pointer */ - struct power_supply_desc supply_desc; /* Supply description */ - struct delayed_work work; /* Work scheduler */ - int num_regs; /* Number of registers (chip type) */ - int charge; /* Last charge register content */ - int r_sense; /* mOhm */ - int Qlsb; /* nAh */ -}; - -static inline int convert_bin_to_uAh( - const struct ltc294x_info *info, int Q) -{ - return ((Q * (info->Qlsb / 10))) / 100; -} - -static inline int convert_uAh_to_bin( - const struct ltc294x_info *info, int uAh) -{ - int Q; - - Q = (uAh * 100) / (info->Qlsb/10); - return (Q < LTC294X_MAX_VALUE) ? Q : LTC294X_MAX_VALUE; -} - -static int ltc294x_read_regs(struct i2c_client *client, - enum ltc294x_reg reg, u8 *buf, int num_regs) -{ - int ret; - struct i2c_msg msgs[2] = { }; - u8 reg_start = reg; - - msgs[0].addr = client->addr; - msgs[0].len = 1; - msgs[0].buf = ®_start; - - msgs[1].addr = client->addr; - msgs[1].len = num_regs; - msgs[1].buf = buf; - msgs[1].flags = I2C_M_RD; - - ret = i2c_transfer(client->adapter, &msgs[0], 2); - if (ret < 0) { - dev_err(&client->dev, "ltc2941 read_reg failed!\n"); - return ret; - } - - dev_dbg(&client->dev, "%s (%#x, %d) -> %#x\n", - __func__, reg, num_regs, *buf); - - return 0; -} - -static int ltc294x_write_regs(struct i2c_client *client, - enum ltc294x_reg reg, const u8 *buf, int num_regs) -{ - int ret; - u8 reg_start = reg; - - ret = i2c_smbus_write_i2c_block_data(client, reg_start, num_regs, buf); - if (ret < 0) { - dev_err(&client->dev, "ltc2941 write_reg failed!\n"); - return ret; - } - - dev_dbg(&client->dev, "%s (%#x, %d) -> %#x\n", - __func__, reg, num_regs, *buf); - - return 0; -} - -static int ltc294x_reset(const struct ltc294x_info *info, int prescaler_exp) -{ - int ret; - u8 value; - u8 control; - - /* Read status and control registers */ - ret = ltc294x_read_regs(info->client, LTC294X_REG_CONTROL, &value, 1); - if (ret < 0) { - dev_err(&info->client->dev, - "Could not read registers from device\n"); - goto error_exit; - } - - control = LTC294X_REG_CONTROL_PRESCALER_SET(prescaler_exp) | - LTC294X_REG_CONTROL_ALCC_CONFIG_DISABLED; - /* Put the 2943 into "monitor" mode, so it measures every 10 sec */ - if (info->num_regs == LTC2943_NUM_REGS) - control |= LTC2943_REG_CONTROL_MODE_SCAN; - - if (value != control) { - ret = ltc294x_write_regs(info->client, - LTC294X_REG_CONTROL, &control, 1); - if (ret < 0) { - dev_err(&info->client->dev, - "Could not write register\n"); - goto error_exit; - } - } - - return 0; - -error_exit: - return ret; -} - -static int ltc294x_read_charge_register(const struct ltc294x_info *info) -{ - int ret; - u8 datar[2]; - - ret = ltc294x_read_regs(info->client, - LTC294X_REG_ACC_CHARGE_MSB, &datar[0], 2); - if (ret < 0) - return ret; - return (datar[0] << 8) + datar[1]; -} - -static int ltc294x_get_charge_now(const struct ltc294x_info *info, int *val) -{ - int value = ltc294x_read_charge_register(info); - - if (value < 0) - return value; - /* When r_sense < 0, this counts up when the battery discharges */ - if (info->Qlsb < 0) - value -= 0xFFFF; - *val = convert_bin_to_uAh(info, value); - return 0; -} - -static int ltc294x_set_charge_now(const struct ltc294x_info *info, int val) -{ - int ret; - u8 dataw[2]; - u8 ctrl_reg; - s32 value; - - value = convert_uAh_to_bin(info, val); - /* Direction depends on how sense+/- were connected */ - if (info->Qlsb < 0) - value += 0xFFFF; - if ((value < 0) || (value > 0xFFFF)) /* input validation */ - return -EINVAL; - - /* Read control register */ - ret = ltc294x_read_regs(info->client, - LTC294X_REG_CONTROL, &ctrl_reg, 1); - if (ret < 0) - return ret; - /* Disable analog section */ - ctrl_reg |= LTC294X_REG_CONTROL_SHUTDOWN_MASK; - ret = ltc294x_write_regs(info->client, - LTC294X_REG_CONTROL, &ctrl_reg, 1); - if (ret < 0) - return ret; - /* Set new charge value */ - dataw[0] = I16_MSB(value); - dataw[1] = I16_LSB(value); - ret = ltc294x_write_regs(info->client, - LTC294X_REG_ACC_CHARGE_MSB, &dataw[0], 2); - if (ret < 0) - goto error_exit; - /* Enable analog section */ -error_exit: - ctrl_reg &= ~LTC294X_REG_CONTROL_SHUTDOWN_MASK; - ret = ltc294x_write_regs(info->client, - LTC294X_REG_CONTROL, &ctrl_reg, 1); - - return ret < 0 ? ret : 0; -} - -static int ltc294x_get_charge_counter( - const struct ltc294x_info *info, int *val) -{ - int value = ltc294x_read_charge_register(info); - - if (value < 0) - return value; - value -= LTC294X_MID_SUPPLY; - *val = convert_bin_to_uAh(info, value); - return 0; -} - -static int ltc294x_get_voltage(const struct ltc294x_info *info, int *val) -{ - int ret; - u8 datar[2]; - u32 value; - - ret = ltc294x_read_regs(info->client, - LTC294X_REG_VOLTAGE_MSB, &datar[0], 2); - value = (datar[0] << 8) | datar[1]; - *val = ((value * 23600) / 0xFFFF) * 1000; /* in uV */ - return ret; -} - -static int ltc294x_get_current(const struct ltc294x_info *info, int *val) -{ - int ret; - u8 datar[2]; - s32 value; - - ret = ltc294x_read_regs(info->client, - LTC294X_REG_CURRENT_MSB, &datar[0], 2); - value = (datar[0] << 8) | datar[1]; - value -= 0x7FFF; - /* Value is in range -32k..+32k, r_sense is usually 10..50 mOhm, - * the formula below keeps everything in s32 range while preserving - * enough digits */ - *val = 1000 * ((60000 * value) / (info->r_sense * 0x7FFF)); /* in uA */ - return ret; -} - -static int ltc294x_get_temperature(const struct ltc294x_info *info, int *val) -{ - int ret; - u8 datar[2]; - u32 value; - - ret = ltc294x_read_regs(info->client, - LTC294X_REG_TEMPERATURE_MSB, &datar[0], 2); - value = (datar[0] << 8) | datar[1]; - /* Full-scale is 510 Kelvin, convert to centidegrees */ - *val = (((51000 * value) / 0xFFFF) - 27215); - return ret; -} - -static int ltc294x_get_property(struct power_supply *psy, - enum power_supply_property prop, - union power_supply_propval *val) -{ - struct ltc294x_info *info = power_supply_get_drvdata(psy); - - switch (prop) { - case POWER_SUPPLY_PROP_CHARGE_NOW: - return ltc294x_get_charge_now(info, &val->intval); - case POWER_SUPPLY_PROP_CHARGE_COUNTER: - return ltc294x_get_charge_counter(info, &val->intval); - case POWER_SUPPLY_PROP_VOLTAGE_NOW: - return ltc294x_get_voltage(info, &val->intval); - case POWER_SUPPLY_PROP_CURRENT_NOW: - return ltc294x_get_current(info, &val->intval); - case POWER_SUPPLY_PROP_TEMP: - return ltc294x_get_temperature(info, &val->intval); - default: - return -EINVAL; - } -} - -static int ltc294x_set_property(struct power_supply *psy, - enum power_supply_property psp, - const union power_supply_propval *val) -{ - struct ltc294x_info *info = power_supply_get_drvdata(psy); - - switch (psp) { - case POWER_SUPPLY_PROP_CHARGE_NOW: - return ltc294x_set_charge_now(info, val->intval); - default: - return -EPERM; - } -} - -static int ltc294x_property_is_writeable( - struct power_supply *psy, enum power_supply_property psp) -{ - switch (psp) { - case POWER_SUPPLY_PROP_CHARGE_NOW: - return 1; - default: - return 0; - } -} - -static void ltc294x_update(struct ltc294x_info *info) -{ - int charge = ltc294x_read_charge_register(info); - - if (charge != info->charge) { - info->charge = charge; - power_supply_changed(info->supply); - } -} - -static void ltc294x_work(struct work_struct *work) -{ - struct ltc294x_info *info; - - info = container_of(work, struct ltc294x_info, work.work); - ltc294x_update(info); - schedule_delayed_work(&info->work, LTC294X_WORK_DELAY * HZ); -} - -static enum power_supply_property ltc294x_properties[] = { - POWER_SUPPLY_PROP_CHARGE_COUNTER, - POWER_SUPPLY_PROP_CHARGE_NOW, - POWER_SUPPLY_PROP_VOLTAGE_NOW, - POWER_SUPPLY_PROP_CURRENT_NOW, - POWER_SUPPLY_PROP_TEMP, -}; - -static int ltc294x_i2c_remove(struct i2c_client *client) -{ - struct ltc294x_info *info = i2c_get_clientdata(client); - - cancel_delayed_work(&info->work); - power_supply_unregister(info->supply); - return 0; -} - -static int ltc294x_i2c_probe(struct i2c_client *client, - const struct i2c_device_id *id) -{ - struct power_supply_config psy_cfg = {}; - struct ltc294x_info *info; - int ret; - u32 prescaler_exp; - s32 r_sense; - struct device_node *np; - - info = devm_kzalloc(&client->dev, sizeof(*info), GFP_KERNEL); - if (info == NULL) - return -ENOMEM; - - i2c_set_clientdata(client, info); - - np = of_node_get(client->dev.of_node); - - info->num_regs = id->driver_data; - info->supply_desc.name = np->name; - - /* r_sense can be negative, when sense+ is connected to the battery - * instead of the sense-. This results in reversed measurements. */ - ret = of_property_read_u32(np, "lltc,resistor-sense", &r_sense); - if (ret < 0) { - dev_err(&client->dev, - "Could not find lltc,resistor-sense in devicetree\n"); - return ret; - } - info->r_sense = r_sense; - - ret = of_property_read_u32(np, "lltc,prescaler-exponent", - &prescaler_exp); - if (ret < 0) { - dev_warn(&client->dev, - "lltc,prescaler-exponent not in devicetree\n"); - prescaler_exp = LTC2941_MAX_PRESCALER_EXP; - } - - if (info->num_regs == LTC2943_NUM_REGS) { - if (prescaler_exp > LTC2943_MAX_PRESCALER_EXP) - prescaler_exp = LTC2943_MAX_PRESCALER_EXP; - info->Qlsb = ((340 * 50000) / r_sense) / - (4096 / (1 << (2*prescaler_exp))); - } else { - if (prescaler_exp > LTC2941_MAX_PRESCALER_EXP) - prescaler_exp = LTC2941_MAX_PRESCALER_EXP; - info->Qlsb = ((85 * 50000) / r_sense) / - (128 / (1 << prescaler_exp)); - } - - info->client = client; - info->supply_desc.type = POWER_SUPPLY_TYPE_BATTERY; - info->supply_desc.properties = ltc294x_properties; - if (info->num_regs >= LTC294X_REG_TEMPERATURE_LSB) - info->supply_desc.num_properties = - ARRAY_SIZE(ltc294x_properties); - else if (info->num_regs >= LTC294X_REG_CURRENT_LSB) - info->supply_desc.num_properties = - ARRAY_SIZE(ltc294x_properties) - 1; - else if (info->num_regs >= LTC294X_REG_VOLTAGE_LSB) - info->supply_desc.num_properties = - ARRAY_SIZE(ltc294x_properties) - 2; - else - info->supply_desc.num_properties = - ARRAY_SIZE(ltc294x_properties) - 3; - info->supply_desc.get_property = ltc294x_get_property; - info->supply_desc.set_property = ltc294x_set_property; - info->supply_desc.property_is_writeable = ltc294x_property_is_writeable; - info->supply_desc.external_power_changed = NULL; - - psy_cfg.drv_data = info; - - INIT_DELAYED_WORK(&info->work, ltc294x_work); - - ret = ltc294x_reset(info, prescaler_exp); - if (ret < 0) { - dev_err(&client->dev, "Communication with chip failed\n"); - return ret; - } - - info->supply = power_supply_register(&client->dev, &info->supply_desc, - &psy_cfg); - if (IS_ERR(info->supply)) { - dev_err(&client->dev, "failed to register ltc2941\n"); - return PTR_ERR(info->supply); - } else { - schedule_delayed_work(&info->work, LTC294X_WORK_DELAY * HZ); - } - - return 0; -} - -#ifdef CONFIG_PM_SLEEP - -static int ltc294x_suspend(struct device *dev) -{ - struct i2c_client *client = to_i2c_client(dev); - struct ltc294x_info *info = i2c_get_clientdata(client); - - cancel_delayed_work(&info->work); - return 0; -} - -static int ltc294x_resume(struct device *dev) -{ - struct i2c_client *client = to_i2c_client(dev); - struct ltc294x_info *info = i2c_get_clientdata(client); - - schedule_delayed_work(&info->work, LTC294X_WORK_DELAY * HZ); - return 0; -} - -static SIMPLE_DEV_PM_OPS(ltc294x_pm_ops, ltc294x_suspend, ltc294x_resume); -#define LTC294X_PM_OPS (<c294x_pm_ops) - -#else -#define LTC294X_PM_OPS NULL -#endif /* CONFIG_PM_SLEEP */ - - -static const struct i2c_device_id ltc294x_i2c_id[] = { - {"ltc2941", LTC2941_NUM_REGS}, - {"ltc2943", LTC2943_NUM_REGS}, - { }, -}; -MODULE_DEVICE_TABLE(i2c, ltc294x_i2c_id); - -static struct i2c_driver ltc294x_driver = { - .driver = { - .name = "LTC2941", - .pm = LTC294X_PM_OPS, - }, - .probe = ltc294x_i2c_probe, - .remove = ltc294x_i2c_remove, - .id_table = ltc294x_i2c_id, -}; -module_i2c_driver(ltc294x_driver); - -MODULE_AUTHOR("Auryn Verwegen, Topic Embedded Systems"); -MODULE_AUTHOR("Mike Looijmans, Topic Embedded Products"); -MODULE_DESCRIPTION("LTC2941/LTC2943 Battery Gas Gauge IC driver"); -MODULE_LICENSE("GPL"); diff --git a/drivers/power/max14577_charger.c b/drivers/power/max14577_charger.c deleted file mode 100644 index a36bcaf62dd4..000000000000 --- a/drivers/power/max14577_charger.c +++ /dev/null @@ -1,648 +0,0 @@ -/* - * max14577_charger.c - Battery charger driver for the Maxim 14577/77836 - * - * Copyright (C) 2013,2014 Samsung Electronics - * Krzysztof Kozlowski - * - * 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 program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - */ - -#include -#include -#include -#include -#include - -struct max14577_charger { - struct device *dev; - struct max14577 *max14577; - struct power_supply *charger; - - struct max14577_charger_platform_data *pdata; -}; - -/* - * Helper function for mapping values of STATUS2/CHGTYP register on max14577 - * and max77836 chipsets to enum maxim_muic_charger_type. - */ -static enum max14577_muic_charger_type maxim_get_charger_type( - enum maxim_device_type dev_type, u8 val) { - switch (val) { - case MAX14577_CHARGER_TYPE_NONE: - case MAX14577_CHARGER_TYPE_USB: - case MAX14577_CHARGER_TYPE_DOWNSTREAM_PORT: - case MAX14577_CHARGER_TYPE_DEDICATED_CHG: - case MAX14577_CHARGER_TYPE_SPECIAL_500MA: - case MAX14577_CHARGER_TYPE_SPECIAL_1A: - return val; - case MAX14577_CHARGER_TYPE_DEAD_BATTERY: - case MAX14577_CHARGER_TYPE_RESERVED: - if (dev_type == MAXIM_DEVICE_TYPE_MAX77836) - val |= 0x8; - return val; - default: - WARN_ONCE(1, "max14577: Unsupported chgtyp register value 0x%02x", val); - return val; - } -} - -static int max14577_get_charger_state(struct max14577_charger *chg, int *val) -{ - struct regmap *rmap = chg->max14577->regmap; - int ret; - u8 reg_data; - - /* - * Charging occurs only if: - * - CHGCTRL2/MBCHOSTEN == 1 - * - STATUS2/CGMBC == 1 - * - * TODO: - * - handle FULL after Top-off timer (EOC register may be off - * and the charger won't be charging although MBCHOSTEN is on) - * - handle properly dead-battery charging (respect timer) - * - handle timers (fast-charge and prequal) /MBCCHGERR/ - */ - ret = max14577_read_reg(rmap, MAX14577_CHG_REG_CHG_CTRL2, ®_data); - if (ret < 0) - goto out; - - if ((reg_data & CHGCTRL2_MBCHOSTEN_MASK) == 0) { - *val = POWER_SUPPLY_STATUS_DISCHARGING; - goto out; - } - - ret = max14577_read_reg(rmap, MAX14577_CHG_REG_STATUS3, ®_data); - if (ret < 0) - goto out; - - if (reg_data & STATUS3_CGMBC_MASK) { - /* Charger or USB-cable is connected */ - if (reg_data & STATUS3_EOC_MASK) - *val = POWER_SUPPLY_STATUS_FULL; - else - *val = POWER_SUPPLY_STATUS_CHARGING; - goto out; - } - - *val = POWER_SUPPLY_STATUS_DISCHARGING; - -out: - return ret; -} - -/* - * Supported charge types: - * - POWER_SUPPLY_CHARGE_TYPE_NONE - * - POWER_SUPPLY_CHARGE_TYPE_FAST - */ -static int max14577_get_charge_type(struct max14577_charger *chg, int *val) -{ - int ret, charging; - - /* - * TODO: CHARGE_TYPE_TRICKLE (VCHGR_RC or EOC)? - * As spec says: - * [after reaching EOC interrupt] - * "When the battery is fully charged, the 30-minute (typ) - * top-off timer starts. The device continues to trickle - * charge the battery until the top-off timer runs out." - */ - ret = max14577_get_charger_state(chg, &charging); - if (ret < 0) - return ret; - - if (charging == POWER_SUPPLY_STATUS_CHARGING) - *val = POWER_SUPPLY_CHARGE_TYPE_FAST; - else - *val = POWER_SUPPLY_CHARGE_TYPE_NONE; - - return 0; -} - -static int max14577_get_online(struct max14577_charger *chg, int *val) -{ - struct regmap *rmap = chg->max14577->regmap; - u8 reg_data; - int ret; - enum max14577_muic_charger_type chg_type; - - ret = max14577_read_reg(rmap, MAX14577_MUIC_REG_STATUS2, ®_data); - if (ret < 0) - return ret; - - reg_data = ((reg_data & STATUS2_CHGTYP_MASK) >> STATUS2_CHGTYP_SHIFT); - chg_type = maxim_get_charger_type(chg->max14577->dev_type, reg_data); - switch (chg_type) { - case MAX14577_CHARGER_TYPE_USB: - case MAX14577_CHARGER_TYPE_DEDICATED_CHG: - case MAX14577_CHARGER_TYPE_SPECIAL_500MA: - case MAX14577_CHARGER_TYPE_SPECIAL_1A: - case MAX14577_CHARGER_TYPE_DEAD_BATTERY: - case MAX77836_CHARGER_TYPE_SPECIAL_BIAS: - *val = 1; - break; - case MAX14577_CHARGER_TYPE_NONE: - case MAX14577_CHARGER_TYPE_DOWNSTREAM_PORT: - case MAX14577_CHARGER_TYPE_RESERVED: - case MAX77836_CHARGER_TYPE_RESERVED: - default: - *val = 0; - } - - return 0; -} - -/* - * Supported health statuses: - * - POWER_SUPPLY_HEALTH_DEAD - * - POWER_SUPPLY_HEALTH_OVERVOLTAGE - * - POWER_SUPPLY_HEALTH_GOOD - */ -static int max14577_get_battery_health(struct max14577_charger *chg, int *val) -{ - struct regmap *rmap = chg->max14577->regmap; - int ret; - u8 reg_data; - enum max14577_muic_charger_type chg_type; - - ret = max14577_read_reg(rmap, MAX14577_MUIC_REG_STATUS2, ®_data); - if (ret < 0) - goto out; - - reg_data = ((reg_data & STATUS2_CHGTYP_MASK) >> STATUS2_CHGTYP_SHIFT); - chg_type = maxim_get_charger_type(chg->max14577->dev_type, reg_data); - if (chg_type == MAX14577_CHARGER_TYPE_DEAD_BATTERY) { - *val = POWER_SUPPLY_HEALTH_DEAD; - goto out; - } - - ret = max14577_read_reg(rmap, MAX14577_CHG_REG_STATUS3, ®_data); - if (ret < 0) - goto out; - - if (reg_data & STATUS3_OVP_MASK) { - *val = POWER_SUPPLY_HEALTH_OVERVOLTAGE; - goto out; - } - - /* Not dead, not overvoltage */ - *val = POWER_SUPPLY_HEALTH_GOOD; - -out: - return ret; -} - -/* - * Always returns 1. - * The max14577 chip doesn't report any status of battery presence. - * Lets assume that it will always be used with some battery. - */ -static int max14577_get_present(struct max14577_charger *chg, int *val) -{ - *val = 1; - - return 0; -} - -static int max14577_set_fast_charge_timer(struct max14577_charger *chg, - unsigned long hours) -{ - u8 reg_data; - - switch (hours) { - case 5 ... 7: - reg_data = hours - 3; - break; - case 0: - /* Disable */ - reg_data = 0x7; - break; - default: - dev_err(chg->dev, "Wrong value for Fast-Charge Timer: %lu\n", - hours); - return -EINVAL; - } - reg_data <<= CHGCTRL1_TCHW_SHIFT; - - return max14577_update_reg(chg->max14577->regmap, - MAX14577_REG_CHGCTRL1, CHGCTRL1_TCHW_MASK, reg_data); -} - -static int max14577_init_constant_voltage(struct max14577_charger *chg, - unsigned int uvolt) -{ - u8 reg_data; - - if (uvolt < MAXIM_CHARGER_CONSTANT_VOLTAGE_MIN || - uvolt > MAXIM_CHARGER_CONSTANT_VOLTAGE_MAX) - return -EINVAL; - - if (uvolt == 4200000) - reg_data = 0x0; - else if (uvolt == MAXIM_CHARGER_CONSTANT_VOLTAGE_MAX) - reg_data = 0x1f; - else if (uvolt <= 4280000) { - unsigned int val = uvolt; - - val -= MAXIM_CHARGER_CONSTANT_VOLTAGE_MIN; - val /= MAXIM_CHARGER_CONSTANT_VOLTAGE_STEP; - if (uvolt <= 4180000) - reg_data = 0x1 + val; - else - reg_data = val; /* Fix for gap between 4.18V and 4.22V */ - } else - return -EINVAL; - - reg_data <<= CHGCTRL3_MBCCVWRC_SHIFT; - - return max14577_write_reg(chg->max14577->regmap, - MAX14577_CHG_REG_CHG_CTRL3, reg_data); -} - -static int max14577_init_eoc(struct max14577_charger *chg, - unsigned int uamp) -{ - unsigned int current_bits = 0xf; - u8 reg_data; - - switch (chg->max14577->dev_type) { - case MAXIM_DEVICE_TYPE_MAX77836: - if (uamp < 5000) - return -EINVAL; /* Requested current is too low */ - - if (uamp >= 7500 && uamp < 10000) - current_bits = 0x0; - else if (uamp <= 50000) { - /* <5000, 7499> and <10000, 50000> */ - current_bits = uamp / 5000; - } else { - uamp = min(uamp, 100000U) - 50000U; - current_bits = 0xa + uamp / 10000; - } - break; - - case MAXIM_DEVICE_TYPE_MAX14577: - default: - if (uamp < MAX14577_CHARGER_EOC_CURRENT_LIMIT_MIN) - return -EINVAL; /* Requested current is too low */ - - uamp = min(uamp, MAX14577_CHARGER_EOC_CURRENT_LIMIT_MAX); - uamp -= MAX14577_CHARGER_EOC_CURRENT_LIMIT_MIN; - current_bits = uamp / MAX14577_CHARGER_EOC_CURRENT_LIMIT_STEP; - break; - } - - reg_data = current_bits << CHGCTRL5_EOCS_SHIFT; - - return max14577_update_reg(chg->max14577->regmap, - MAX14577_CHG_REG_CHG_CTRL5, CHGCTRL5_EOCS_MASK, - reg_data); -} - -static int max14577_init_fast_charge(struct max14577_charger *chg, - unsigned int uamp) -{ - u8 reg_data; - int ret; - const struct maxim_charger_current *limits = - &maxim_charger_currents[chg->max14577->dev_type]; - - ret = maxim_charger_calc_reg_current(limits, uamp, uamp, ®_data); - if (ret) { - dev_err(chg->dev, "Wrong value for fast charge: %u\n", uamp); - return ret; - } - - return max14577_update_reg(chg->max14577->regmap, - MAX14577_CHG_REG_CHG_CTRL4, - CHGCTRL4_MBCICHWRCL_MASK | CHGCTRL4_MBCICHWRCH_MASK, - reg_data); -} - -/* - * Sets charger registers to proper and safe default values. - * Some of these values are equal to defaults in MAX14577E - * data sheet but there are minor differences. - */ -static int max14577_charger_reg_init(struct max14577_charger *chg) -{ - struct regmap *rmap = chg->max14577->regmap; - u8 reg_data; - int ret; - - /* - * Charger-Type Manual Detection, default off (set CHGTYPMAN to 0) - * Charger-Detection Enable, default on (set CHGDETEN to 1) - * Combined mask of CHGDETEN and CHGTYPMAN will zero the CHGTYPMAN bit - */ - reg_data = 0x1 << CDETCTRL1_CHGDETEN_SHIFT; - max14577_update_reg(rmap, MAX14577_REG_CDETCTRL1, - CDETCTRL1_CHGDETEN_MASK | CDETCTRL1_CHGTYPMAN_MASK, - reg_data); - - /* - * Wall-Adapter Rapid Charge, default on - * Battery-Charger, default on - */ - reg_data = 0x1 << CHGCTRL2_VCHGR_RC_SHIFT; - reg_data |= 0x1 << CHGCTRL2_MBCHOSTEN_SHIFT; - max14577_write_reg(rmap, MAX14577_REG_CHGCTRL2, reg_data); - - /* Auto Charging Stop, default off */ - reg_data = 0x0 << CHGCTRL6_AUTOSTOP_SHIFT; - max14577_write_reg(rmap, MAX14577_REG_CHGCTRL6, reg_data); - - ret = max14577_init_constant_voltage(chg, chg->pdata->constant_uvolt); - if (ret) - return ret; - - ret = max14577_init_eoc(chg, chg->pdata->eoc_uamp); - if (ret) - return ret; - - ret = max14577_init_fast_charge(chg, chg->pdata->fast_charge_uamp); - if (ret) - return ret; - - ret = max14577_set_fast_charge_timer(chg, - MAXIM_CHARGER_FAST_CHARGE_TIMER_DEFAULT); - if (ret) - return ret; - - /* Initialize Overvoltage-Protection Threshold */ - switch (chg->pdata->ovp_uvolt) { - case 7500000: - reg_data = 0x0; - break; - case 6000000: - case 6500000: - case 7000000: - reg_data = 0x1 + (chg->pdata->ovp_uvolt - 6000000) / 500000; - break; - default: - dev_err(chg->dev, "Wrong value for OVP: %u\n", - chg->pdata->ovp_uvolt); - return -EINVAL; - } - reg_data <<= CHGCTRL7_OTPCGHCVS_SHIFT; - max14577_write_reg(rmap, MAX14577_REG_CHGCTRL7, reg_data); - - return 0; -} - -/* Support property from charger */ -static enum power_supply_property max14577_charger_props[] = { - POWER_SUPPLY_PROP_STATUS, - POWER_SUPPLY_PROP_CHARGE_TYPE, - POWER_SUPPLY_PROP_HEALTH, - POWER_SUPPLY_PROP_PRESENT, - POWER_SUPPLY_PROP_ONLINE, - POWER_SUPPLY_PROP_MODEL_NAME, - POWER_SUPPLY_PROP_MANUFACTURER, -}; - -static const char * const model_names[] = { - [MAXIM_DEVICE_TYPE_UNKNOWN] = "MAX14577-like", - [MAXIM_DEVICE_TYPE_MAX14577] = "MAX14577", - [MAXIM_DEVICE_TYPE_MAX77836] = "MAX77836", -}; -static const char *manufacturer = "Maxim Integrated"; - -static int max14577_charger_get_property(struct power_supply *psy, - enum power_supply_property psp, - union power_supply_propval *val) -{ - struct max14577_charger *chg = power_supply_get_drvdata(psy); - int ret = 0; - - switch (psp) { - case POWER_SUPPLY_PROP_STATUS: - ret = max14577_get_charger_state(chg, &val->intval); - break; - case POWER_SUPPLY_PROP_CHARGE_TYPE: - ret = max14577_get_charge_type(chg, &val->intval); - break; - case POWER_SUPPLY_PROP_HEALTH: - ret = max14577_get_battery_health(chg, &val->intval); - break; - case POWER_SUPPLY_PROP_PRESENT: - ret = max14577_get_present(chg, &val->intval); - break; - case POWER_SUPPLY_PROP_ONLINE: - ret = max14577_get_online(chg, &val->intval); - break; - case POWER_SUPPLY_PROP_MODEL_NAME: - BUILD_BUG_ON(ARRAY_SIZE(model_names) != MAXIM_DEVICE_TYPE_NUM); - val->strval = model_names[chg->max14577->dev_type]; - break; - case POWER_SUPPLY_PROP_MANUFACTURER: - val->strval = manufacturer; - break; - default: - return -EINVAL; - } - - return ret; -} - -static const struct power_supply_desc max14577_charger_desc = { - .name = "max14577-charger", - .type = POWER_SUPPLY_TYPE_BATTERY, - .properties = max14577_charger_props, - .num_properties = ARRAY_SIZE(max14577_charger_props), - .get_property = max14577_charger_get_property, -}; - -#ifdef CONFIG_OF -static struct max14577_charger_platform_data *max14577_charger_dt_init( - struct platform_device *pdev) -{ - struct max14577_charger_platform_data *pdata; - struct device_node *np = pdev->dev.of_node; - int ret; - - if (!np) { - dev_err(&pdev->dev, "No charger OF node\n"); - return ERR_PTR(-EINVAL); - } - - pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL); - if (!pdata) - return ERR_PTR(-ENOMEM); - - ret = of_property_read_u32(np, "maxim,constant-uvolt", - &pdata->constant_uvolt); - if (ret) { - dev_err(&pdev->dev, "Cannot parse maxim,constant-uvolt field from DT\n"); - return ERR_PTR(ret); - } - - ret = of_property_read_u32(np, "maxim,fast-charge-uamp", - &pdata->fast_charge_uamp); - if (ret) { - dev_err(&pdev->dev, "Cannot parse maxim,fast-charge-uamp field from DT\n"); - return ERR_PTR(ret); - } - - ret = of_property_read_u32(np, "maxim,eoc-uamp", &pdata->eoc_uamp); - if (ret) { - dev_err(&pdev->dev, "Cannot parse maxim,eoc-uamp field from DT\n"); - return ERR_PTR(ret); - } - - ret = of_property_read_u32(np, "maxim,ovp-uvolt", &pdata->ovp_uvolt); - if (ret) { - dev_err(&pdev->dev, "Cannot parse maxim,ovp-uvolt field from DT\n"); - return ERR_PTR(ret); - } - - return pdata; -} -#else /* CONFIG_OF */ -static struct max14577_charger_platform_data *max14577_charger_dt_init( - struct platform_device *pdev) -{ - return NULL; -} -#endif /* CONFIG_OF */ - -static ssize_t show_fast_charge_timer(struct device *dev, - struct device_attribute *attr, char *buf) -{ - struct max14577_charger *chg = dev_get_drvdata(dev); - u8 reg_data; - int ret; - unsigned int val; - - ret = max14577_read_reg(chg->max14577->regmap, MAX14577_REG_CHGCTRL1, - ®_data); - if (ret) - return ret; - - reg_data &= CHGCTRL1_TCHW_MASK; - reg_data >>= CHGCTRL1_TCHW_SHIFT; - switch (reg_data) { - case 0x2 ... 0x4: - val = reg_data + 3; - break; - case 0x7: - val = 0; - break; - default: - val = 5; - break; - } - - return scnprintf(buf, PAGE_SIZE, "%u\n", val); -} - -static ssize_t store_fast_charge_timer(struct device *dev, - struct device_attribute *attr, const char *buf, size_t count) -{ - struct max14577_charger *chg = dev_get_drvdata(dev); - unsigned long val; - int ret; - - ret = kstrtoul(buf, 10, &val); - if (ret) - return ret; - - ret = max14577_set_fast_charge_timer(chg, val); - if (ret) - return ret; - - return count; -} - -static DEVICE_ATTR(fast_charge_timer, S_IRUGO | S_IWUSR, - show_fast_charge_timer, store_fast_charge_timer); - -static int max14577_charger_probe(struct platform_device *pdev) -{ - struct max14577_charger *chg; - struct power_supply_config psy_cfg = {}; - struct max14577 *max14577 = dev_get_drvdata(pdev->dev.parent); - int ret; - - chg = devm_kzalloc(&pdev->dev, sizeof(*chg), GFP_KERNEL); - if (!chg) - return -ENOMEM; - - platform_set_drvdata(pdev, chg); - chg->dev = &pdev->dev; - chg->max14577 = max14577; - - chg->pdata = max14577_charger_dt_init(pdev); - if (IS_ERR_OR_NULL(chg->pdata)) - return PTR_ERR(chg->pdata); - - ret = max14577_charger_reg_init(chg); - if (ret) - return ret; - - ret = device_create_file(&pdev->dev, &dev_attr_fast_charge_timer); - if (ret) { - dev_err(&pdev->dev, "failed: create sysfs entry\n"); - return ret; - } - - psy_cfg.drv_data = chg; - chg->charger = power_supply_register(&pdev->dev, &max14577_charger_desc, - &psy_cfg); - if (IS_ERR(chg->charger)) { - dev_err(&pdev->dev, "failed: power supply register\n"); - ret = PTR_ERR(chg->charger); - goto err; - } - - /* Check for valid values for charger */ - BUILD_BUG_ON(MAX14577_CHARGER_EOC_CURRENT_LIMIT_MIN + - MAX14577_CHARGER_EOC_CURRENT_LIMIT_STEP * 0xf != - MAX14577_CHARGER_EOC_CURRENT_LIMIT_MAX); - return 0; - -err: - device_remove_file(&pdev->dev, &dev_attr_fast_charge_timer); - - return ret; -} - -static int max14577_charger_remove(struct platform_device *pdev) -{ - struct max14577_charger *chg = platform_get_drvdata(pdev); - - device_remove_file(&pdev->dev, &dev_attr_fast_charge_timer); - power_supply_unregister(chg->charger); - - return 0; -} - -static const struct platform_device_id max14577_charger_id[] = { - { "max14577-charger", MAXIM_DEVICE_TYPE_MAX14577, }, - { "max77836-charger", MAXIM_DEVICE_TYPE_MAX77836, }, - { } -}; -MODULE_DEVICE_TABLE(platform, max14577_charger_id); - -static struct platform_driver max14577_charger_driver = { - .driver = { - .name = "max14577-charger", - }, - .probe = max14577_charger_probe, - .remove = max14577_charger_remove, - .id_table = max14577_charger_id, -}; -module_platform_driver(max14577_charger_driver); - -MODULE_AUTHOR("Krzysztof Kozlowski "); -MODULE_DESCRIPTION("Maxim 14577/77836 charger driver"); -MODULE_LICENSE("GPL"); diff --git a/drivers/power/max17040_battery.c b/drivers/power/max17040_battery.c deleted file mode 100644 index 8689c80202b5..000000000000 --- a/drivers/power/max17040_battery.c +++ /dev/null @@ -1,305 +0,0 @@ -/* - * max17040_battery.c - * fuel-gauge systems for lithium-ion (Li+) batteries - * - * Copyright (C) 2009 Samsung Electronics - * Minkyu Kang - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define MAX17040_VCELL_MSB 0x02 -#define MAX17040_VCELL_LSB 0x03 -#define MAX17040_SOC_MSB 0x04 -#define MAX17040_SOC_LSB 0x05 -#define MAX17040_MODE_MSB 0x06 -#define MAX17040_MODE_LSB 0x07 -#define MAX17040_VER_MSB 0x08 -#define MAX17040_VER_LSB 0x09 -#define MAX17040_RCOMP_MSB 0x0C -#define MAX17040_RCOMP_LSB 0x0D -#define MAX17040_CMD_MSB 0xFE -#define MAX17040_CMD_LSB 0xFF - -#define MAX17040_DELAY 1000 -#define MAX17040_BATTERY_FULL 95 - -struct max17040_chip { - struct i2c_client *client; - struct delayed_work work; - struct power_supply *battery; - struct max17040_platform_data *pdata; - - /* State Of Connect */ - int online; - /* battery voltage */ - int vcell; - /* battery capacity */ - int soc; - /* State Of Charge */ - int status; -}; - -static int max17040_get_property(struct power_supply *psy, - enum power_supply_property psp, - union power_supply_propval *val) -{ - struct max17040_chip *chip = power_supply_get_drvdata(psy); - - switch (psp) { - case POWER_SUPPLY_PROP_STATUS: - val->intval = chip->status; - break; - case POWER_SUPPLY_PROP_ONLINE: - val->intval = chip->online; - break; - case POWER_SUPPLY_PROP_VOLTAGE_NOW: - val->intval = chip->vcell; - break; - case POWER_SUPPLY_PROP_CAPACITY: - val->intval = chip->soc; - break; - default: - return -EINVAL; - } - return 0; -} - -static int max17040_write_reg(struct i2c_client *client, int reg, u8 value) -{ - int ret; - - ret = i2c_smbus_write_byte_data(client, reg, value); - - if (ret < 0) - dev_err(&client->dev, "%s: err %d\n", __func__, ret); - - return ret; -} - -static int max17040_read_reg(struct i2c_client *client, int reg) -{ - int ret; - - ret = i2c_smbus_read_byte_data(client, reg); - - if (ret < 0) - dev_err(&client->dev, "%s: err %d\n", __func__, ret); - - return ret; -} - -static void max17040_reset(struct i2c_client *client) -{ - max17040_write_reg(client, MAX17040_CMD_MSB, 0x54); - max17040_write_reg(client, MAX17040_CMD_LSB, 0x00); -} - -static void max17040_get_vcell(struct i2c_client *client) -{ - struct max17040_chip *chip = i2c_get_clientdata(client); - u8 msb; - u8 lsb; - - msb = max17040_read_reg(client, MAX17040_VCELL_MSB); - lsb = max17040_read_reg(client, MAX17040_VCELL_LSB); - - chip->vcell = (msb << 4) + (lsb >> 4); -} - -static void max17040_get_soc(struct i2c_client *client) -{ - struct max17040_chip *chip = i2c_get_clientdata(client); - u8 msb; - u8 lsb; - - msb = max17040_read_reg(client, MAX17040_SOC_MSB); - lsb = max17040_read_reg(client, MAX17040_SOC_LSB); - - chip->soc = msb; -} - -static void max17040_get_version(struct i2c_client *client) -{ - u8 msb; - u8 lsb; - - msb = max17040_read_reg(client, MAX17040_VER_MSB); - lsb = max17040_read_reg(client, MAX17040_VER_LSB); - - dev_info(&client->dev, "MAX17040 Fuel-Gauge Ver %d%d\n", msb, lsb); -} - -static void max17040_get_online(struct i2c_client *client) -{ - struct max17040_chip *chip = i2c_get_clientdata(client); - - if (chip->pdata && chip->pdata->battery_online) - chip->online = chip->pdata->battery_online(); - else - chip->online = 1; -} - -static void max17040_get_status(struct i2c_client *client) -{ - struct max17040_chip *chip = i2c_get_clientdata(client); - - if (!chip->pdata || !chip->pdata->charger_online - || !chip->pdata->charger_enable) { - chip->status = POWER_SUPPLY_STATUS_UNKNOWN; - return; - } - - if (chip->pdata->charger_online()) { - if (chip->pdata->charger_enable()) - chip->status = POWER_SUPPLY_STATUS_CHARGING; - else - chip->status = POWER_SUPPLY_STATUS_NOT_CHARGING; - } else { - chip->status = POWER_SUPPLY_STATUS_DISCHARGING; - } - - if (chip->soc > MAX17040_BATTERY_FULL) - chip->status = POWER_SUPPLY_STATUS_FULL; -} - -static void max17040_work(struct work_struct *work) -{ - struct max17040_chip *chip; - - chip = container_of(work, struct max17040_chip, work.work); - - max17040_get_vcell(chip->client); - max17040_get_soc(chip->client); - max17040_get_online(chip->client); - max17040_get_status(chip->client); - - queue_delayed_work(system_power_efficient_wq, &chip->work, - MAX17040_DELAY); -} - -static enum power_supply_property max17040_battery_props[] = { - POWER_SUPPLY_PROP_STATUS, - POWER_SUPPLY_PROP_ONLINE, - POWER_SUPPLY_PROP_VOLTAGE_NOW, - POWER_SUPPLY_PROP_CAPACITY, -}; - -static const struct power_supply_desc max17040_battery_desc = { - .name = "battery", - .type = POWER_SUPPLY_TYPE_BATTERY, - .get_property = max17040_get_property, - .properties = max17040_battery_props, - .num_properties = ARRAY_SIZE(max17040_battery_props), -}; - -static int max17040_probe(struct i2c_client *client, - const struct i2c_device_id *id) -{ - struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent); - struct power_supply_config psy_cfg = {}; - struct max17040_chip *chip; - - if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE)) - return -EIO; - - chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL); - if (!chip) - return -ENOMEM; - - chip->client = client; - chip->pdata = client->dev.platform_data; - - i2c_set_clientdata(client, chip); - psy_cfg.drv_data = chip; - - chip->battery = power_supply_register(&client->dev, - &max17040_battery_desc, &psy_cfg); - if (IS_ERR(chip->battery)) { - dev_err(&client->dev, "failed: power supply register\n"); - return PTR_ERR(chip->battery); - } - - max17040_reset(client); - max17040_get_version(client); - - INIT_DEFERRABLE_WORK(&chip->work, max17040_work); - queue_delayed_work(system_power_efficient_wq, &chip->work, - MAX17040_DELAY); - - return 0; -} - -static int max17040_remove(struct i2c_client *client) -{ - struct max17040_chip *chip = i2c_get_clientdata(client); - - power_supply_unregister(chip->battery); - cancel_delayed_work(&chip->work); - return 0; -} - -#ifdef CONFIG_PM_SLEEP - -static int max17040_suspend(struct device *dev) -{ - struct i2c_client *client = to_i2c_client(dev); - struct max17040_chip *chip = i2c_get_clientdata(client); - - cancel_delayed_work(&chip->work); - return 0; -} - -static int max17040_resume(struct device *dev) -{ - struct i2c_client *client = to_i2c_client(dev); - struct max17040_chip *chip = i2c_get_clientdata(client); - - queue_delayed_work(system_power_efficient_wq, &chip->work, - MAX17040_DELAY); - return 0; -} - -static SIMPLE_DEV_PM_OPS(max17040_pm_ops, max17040_suspend, max17040_resume); -#define MAX17040_PM_OPS (&max17040_pm_ops) - -#else - -#define MAX17040_PM_OPS NULL - -#endif /* CONFIG_PM_SLEEP */ - -static const struct i2c_device_id max17040_id[] = { - { "max17040" }, - { "max77836-battery" }, - { } -}; -MODULE_DEVICE_TABLE(i2c, max17040_id); - -static struct i2c_driver max17040_i2c_driver = { - .driver = { - .name = "max17040", - .pm = MAX17040_PM_OPS, - }, - .probe = max17040_probe, - .remove = max17040_remove, - .id_table = max17040_id, -}; -module_i2c_driver(max17040_i2c_driver); - -MODULE_AUTHOR("Minkyu Kang "); -MODULE_DESCRIPTION("MAX17040 Fuel Gauge"); -MODULE_LICENSE("GPL"); diff --git a/drivers/power/max17042_battery.c b/drivers/power/max17042_battery.c deleted file mode 100644 index 9c65f134d447..000000000000 --- a/drivers/power/max17042_battery.c +++ /dev/null @@ -1,1016 +0,0 @@ -/* - * Fuel gauge driver for Maxim 17042 / 8966 / 8997 - * Note that Maxim 8966 and 8997 are mfd and this is its subdevice. - * - * Copyright (C) 2011 Samsung Electronics - * MyungJoo Ham - * - * 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 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 - * - * This driver is based on max17040_battery.c - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/* Status register bits */ -#define STATUS_POR_BIT (1 << 1) -#define STATUS_BST_BIT (1 << 3) -#define STATUS_VMN_BIT (1 << 8) -#define STATUS_TMN_BIT (1 << 9) -#define STATUS_SMN_BIT (1 << 10) -#define STATUS_BI_BIT (1 << 11) -#define STATUS_VMX_BIT (1 << 12) -#define STATUS_TMX_BIT (1 << 13) -#define STATUS_SMX_BIT (1 << 14) -#define STATUS_BR_BIT (1 << 15) - -/* Interrupt mask bits */ -#define CONFIG_ALRT_BIT_ENBL (1 << 2) -#define STATUS_INTR_SOCMIN_BIT (1 << 10) -#define STATUS_INTR_SOCMAX_BIT (1 << 14) - -#define VFSOC0_LOCK 0x0000 -#define VFSOC0_UNLOCK 0x0080 -#define MODEL_UNLOCK1 0X0059 -#define MODEL_UNLOCK2 0X00C4 -#define MODEL_LOCK1 0X0000 -#define MODEL_LOCK2 0X0000 - -#define dQ_ACC_DIV 0x4 -#define dP_ACC_100 0x1900 -#define dP_ACC_200 0x3200 - -#define MAX17042_VMAX_TOLERANCE 50 /* 50 mV */ - -struct max17042_chip { - struct i2c_client *client; - struct regmap *regmap; - struct power_supply *battery; - enum max170xx_chip_type chip_type; - struct max17042_platform_data *pdata; - struct work_struct work; - int init_complete; -}; - -static enum power_supply_property max17042_battery_props[] = { - POWER_SUPPLY_PROP_PRESENT, - POWER_SUPPLY_PROP_CYCLE_COUNT, - POWER_SUPPLY_PROP_VOLTAGE_MAX, - POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, - POWER_SUPPLY_PROP_VOLTAGE_NOW, - POWER_SUPPLY_PROP_VOLTAGE_AVG, - POWER_SUPPLY_PROP_VOLTAGE_OCV, - POWER_SUPPLY_PROP_CAPACITY, - POWER_SUPPLY_PROP_CHARGE_FULL, - POWER_SUPPLY_PROP_CHARGE_COUNTER, - POWER_SUPPLY_PROP_TEMP, - POWER_SUPPLY_PROP_TEMP_ALERT_MIN, - POWER_SUPPLY_PROP_TEMP_ALERT_MAX, - POWER_SUPPLY_PROP_TEMP_MIN, - POWER_SUPPLY_PROP_TEMP_MAX, - POWER_SUPPLY_PROP_HEALTH, - POWER_SUPPLY_PROP_CURRENT_NOW, - POWER_SUPPLY_PROP_CURRENT_AVG, -}; - -static int max17042_get_temperature(struct max17042_chip *chip, int *temp) -{ - int ret; - u32 data; - struct regmap *map = chip->regmap; - - ret = regmap_read(map, MAX17042_TEMP, &data); - if (ret < 0) - return ret; - - *temp = data; - /* The value is signed. */ - if (*temp & 0x8000) { - *temp = (0x7fff & ~*temp) + 1; - *temp *= -1; - } - - /* The value is converted into deci-centigrade scale */ - /* Units of LSB = 1 / 256 degree Celsius */ - *temp = *temp * 10 / 256; - return 0; -} - -static int max17042_get_battery_health(struct max17042_chip *chip, int *health) -{ - int temp, vavg, vbatt, ret; - u32 val; - - ret = regmap_read(chip->regmap, MAX17042_AvgVCELL, &val); - if (ret < 0) - goto health_error; - - /* bits [0-3] unused */ - vavg = val * 625 / 8; - /* Convert to millivolts */ - vavg /= 1000; - - ret = regmap_read(chip->regmap, MAX17042_VCELL, &val); - if (ret < 0) - goto health_error; - - /* bits [0-3] unused */ - vbatt = val * 625 / 8; - /* Convert to millivolts */ - vbatt /= 1000; - - if (vavg < chip->pdata->vmin) { - *health = POWER_SUPPLY_HEALTH_DEAD; - goto out; - } - - if (vbatt > chip->pdata->vmax + MAX17042_VMAX_TOLERANCE) { - *health = POWER_SUPPLY_HEALTH_OVERVOLTAGE; - goto out; - } - - ret = max17042_get_temperature(chip, &temp); - if (ret < 0) - goto health_error; - - if (temp <= chip->pdata->temp_min) { - *health = POWER_SUPPLY_HEALTH_COLD; - goto out; - } - - if (temp >= chip->pdata->temp_max) { - *health = POWER_SUPPLY_HEALTH_OVERHEAT; - goto out; - } - - *health = POWER_SUPPLY_HEALTH_GOOD; - -out: - return 0; - -health_error: - return ret; -} - -static int max17042_get_property(struct power_supply *psy, - enum power_supply_property psp, - union power_supply_propval *val) -{ - struct max17042_chip *chip = power_supply_get_drvdata(psy); - struct regmap *map = chip->regmap; - int ret; - u32 data; - - if (!chip->init_complete) - return -EAGAIN; - - switch (psp) { - case POWER_SUPPLY_PROP_PRESENT: - ret = regmap_read(map, MAX17042_STATUS, &data); - if (ret < 0) - return ret; - - if (data & MAX17042_STATUS_BattAbsent) - val->intval = 0; - else - val->intval = 1; - break; - case POWER_SUPPLY_PROP_CYCLE_COUNT: - ret = regmap_read(map, MAX17042_Cycles, &data); - if (ret < 0) - return ret; - - val->intval = data; - break; - case POWER_SUPPLY_PROP_VOLTAGE_MAX: - ret = regmap_read(map, MAX17042_MinMaxVolt, &data); - if (ret < 0) - return ret; - - val->intval = data >> 8; - val->intval *= 20000; /* Units of LSB = 20mV */ - break; - case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: - if (chip->chip_type == MAXIM_DEVICE_TYPE_MAX17042) - ret = regmap_read(map, MAX17042_V_empty, &data); - else - ret = regmap_read(map, MAX17047_V_empty, &data); - if (ret < 0) - return ret; - - val->intval = data >> 7; - val->intval *= 10000; /* Units of LSB = 10mV */ - break; - case POWER_SUPPLY_PROP_VOLTAGE_NOW: - ret = regmap_read(map, MAX17042_VCELL, &data); - if (ret < 0) - return ret; - - val->intval = data * 625 / 8; - break; - case POWER_SUPPLY_PROP_VOLTAGE_AVG: - ret = regmap_read(map, MAX17042_AvgVCELL, &data); - if (ret < 0) - return ret; - - val->intval = data * 625 / 8; - break; - case POWER_SUPPLY_PROP_VOLTAGE_OCV: - ret = regmap_read(map, MAX17042_OCVInternal, &data); - if (ret < 0) - return ret; - - val->intval = data * 625 / 8; - break; - case POWER_SUPPLY_PROP_CAPACITY: - ret = regmap_read(map, MAX17042_RepSOC, &data); - if (ret < 0) - return ret; - - val->intval = data >> 8; - break; - case POWER_SUPPLY_PROP_CHARGE_FULL: - ret = regmap_read(map, MAX17042_FullCAP, &data); - if (ret < 0) - return ret; - - val->intval = data * 1000 / 2; - break; - case POWER_SUPPLY_PROP_CHARGE_COUNTER: - ret = regmap_read(map, MAX17042_QH, &data); - if (ret < 0) - return ret; - - val->intval = data * 1000 / 2; - break; - case POWER_SUPPLY_PROP_TEMP: - ret = max17042_get_temperature(chip, &val->intval); - if (ret < 0) - return ret; - break; - case POWER_SUPPLY_PROP_TEMP_ALERT_MIN: - ret = regmap_read(map, MAX17042_TALRT_Th, &data); - if (ret < 0) - return ret; - /* LSB is Alert Minimum. In deci-centigrade */ - val->intval = (data & 0xff) * 10; - break; - case POWER_SUPPLY_PROP_TEMP_ALERT_MAX: - ret = regmap_read(map, MAX17042_TALRT_Th, &data); - if (ret < 0) - return ret; - /* MSB is Alert Maximum. In deci-centigrade */ - val->intval = (data >> 8) * 10; - break; - case POWER_SUPPLY_PROP_TEMP_MIN: - val->intval = chip->pdata->temp_min; - break; - case POWER_SUPPLY_PROP_TEMP_MAX: - val->intval = chip->pdata->temp_max; - break; - case POWER_SUPPLY_PROP_HEALTH: - ret = max17042_get_battery_health(chip, &val->intval); - if (ret < 0) - return ret; - break; - case POWER_SUPPLY_PROP_CURRENT_NOW: - if (chip->pdata->enable_current_sense) { - ret = regmap_read(map, MAX17042_Current, &data); - if (ret < 0) - return ret; - - val->intval = data; - if (val->intval & 0x8000) { - /* Negative */ - val->intval = ~val->intval & 0x7fff; - val->intval++; - val->intval *= -1; - } - val->intval *= 1562500 / chip->pdata->r_sns; - } else { - return -EINVAL; - } - break; - case POWER_SUPPLY_PROP_CURRENT_AVG: - if (chip->pdata->enable_current_sense) { - ret = regmap_read(map, MAX17042_AvgCurrent, &data); - if (ret < 0) - return ret; - - val->intval = data; - if (val->intval & 0x8000) { - /* Negative */ - val->intval = ~val->intval & 0x7fff; - val->intval++; - val->intval *= -1; - } - val->intval *= 1562500 / chip->pdata->r_sns; - } else { - return -EINVAL; - } - break; - default: - return -EINVAL; - } - return 0; -} - -static int max17042_set_property(struct power_supply *psy, - enum power_supply_property psp, - const union power_supply_propval *val) -{ - struct max17042_chip *chip = power_supply_get_drvdata(psy); - struct regmap *map = chip->regmap; - int ret = 0; - u32 data; - int8_t temp; - - switch (psp) { - case POWER_SUPPLY_PROP_TEMP_ALERT_MIN: - ret = regmap_read(map, MAX17042_TALRT_Th, &data); - if (ret < 0) - return ret; - - /* Input in deci-centigrade, convert to centigrade */ - temp = val->intval / 10; - /* force min < max */ - if (temp >= (int8_t)(data >> 8)) - temp = (int8_t)(data >> 8) - 1; - /* Write both MAX and MIN ALERT */ - data = (data & 0xff00) + temp; - ret = regmap_write(map, MAX17042_TALRT_Th, data); - break; - case POWER_SUPPLY_PROP_TEMP_ALERT_MAX: - ret = regmap_read(map, MAX17042_TALRT_Th, &data); - if (ret < 0) - return ret; - - /* Input in Deci-Centigrade, convert to centigrade */ - temp = val->intval / 10; - /* force max > min */ - if (temp <= (int8_t)(data & 0xff)) - temp = (int8_t)(data & 0xff) + 1; - /* Write both MAX and MIN ALERT */ - data = (data & 0xff) + (temp << 8); - ret = regmap_write(map, MAX17042_TALRT_Th, data); - break; - default: - ret = -EINVAL; - } - - return ret; -} - -static int max17042_property_is_writeable(struct power_supply *psy, - enum power_supply_property psp) -{ - int ret; - - switch (psp) { - case POWER_SUPPLY_PROP_TEMP_ALERT_MIN: - case POWER_SUPPLY_PROP_TEMP_ALERT_MAX: - ret = 1; - break; - default: - ret = 0; - } - - return ret; -} - -static int max17042_write_verify_reg(struct regmap *map, u8 reg, u32 value) -{ - int retries = 8; - int ret; - u32 read_value; - - do { - ret = regmap_write(map, reg, value); - regmap_read(map, reg, &read_value); - if (read_value != value) { - ret = -EIO; - retries--; - } - } while (retries && read_value != value); - - if (ret < 0) - pr_err("%s: err %d\n", __func__, ret); - - return ret; -} - -static inline void max17042_override_por(struct regmap *map, - u8 reg, u16 value) -{ - if (value) - regmap_write(map, reg, value); -} - -static inline void max10742_unlock_model(struct max17042_chip *chip) -{ - struct regmap *map = chip->regmap; - - regmap_write(map, MAX17042_MLOCKReg1, MODEL_UNLOCK1); - regmap_write(map, MAX17042_MLOCKReg2, MODEL_UNLOCK2); -} - -static inline void max10742_lock_model(struct max17042_chip *chip) -{ - struct regmap *map = chip->regmap; - - regmap_write(map, MAX17042_MLOCKReg1, MODEL_LOCK1); - regmap_write(map, MAX17042_MLOCKReg2, MODEL_LOCK2); -} - -static inline void max17042_write_model_data(struct max17042_chip *chip, - u8 addr, int size) -{ - struct regmap *map = chip->regmap; - int i; - - for (i = 0; i < size; i++) - regmap_write(map, addr + i, - chip->pdata->config_data->cell_char_tbl[i]); -} - -static inline void max17042_read_model_data(struct max17042_chip *chip, - u8 addr, u32 *data, int size) -{ - struct regmap *map = chip->regmap; - int i; - - for (i = 0; i < size; i++) - regmap_read(map, addr + i, &data[i]); -} - -static inline int max17042_model_data_compare(struct max17042_chip *chip, - u16 *data1, u16 *data2, int size) -{ - int i; - - if (memcmp(data1, data2, size)) { - dev_err(&chip->client->dev, "%s compare failed\n", __func__); - for (i = 0; i < size; i++) - dev_info(&chip->client->dev, "0x%x, 0x%x", - data1[i], data2[i]); - dev_info(&chip->client->dev, "\n"); - return -EINVAL; - } - return 0; -} - -static int max17042_init_model(struct max17042_chip *chip) -{ - int ret; - int table_size = ARRAY_SIZE(chip->pdata->config_data->cell_char_tbl); - u32 *temp_data; - - temp_data = kcalloc(table_size, sizeof(*temp_data), GFP_KERNEL); - if (!temp_data) - return -ENOMEM; - - max10742_unlock_model(chip); - max17042_write_model_data(chip, MAX17042_MODELChrTbl, - table_size); - max17042_read_model_data(chip, MAX17042_MODELChrTbl, temp_data, - table_size); - - ret = max17042_model_data_compare( - chip, - chip->pdata->config_data->cell_char_tbl, - (u16 *)temp_data, - table_size); - - max10742_lock_model(chip); - kfree(temp_data); - - return ret; -} - -static int max17042_verify_model_lock(struct max17042_chip *chip) -{ - int i; - int table_size = ARRAY_SIZE(chip->pdata->config_data->cell_char_tbl); - u32 *temp_data; - int ret = 0; - - temp_data = kcalloc(table_size, sizeof(*temp_data), GFP_KERNEL); - if (!temp_data) - return -ENOMEM; - - max17042_read_model_data(chip, MAX17042_MODELChrTbl, temp_data, - table_size); - for (i = 0; i < table_size; i++) - if (temp_data[i]) - ret = -EINVAL; - - kfree(temp_data); - return ret; -} - -static void max17042_write_config_regs(struct max17042_chip *chip) -{ - struct max17042_config_data *config = chip->pdata->config_data; - struct regmap *map = chip->regmap; - - regmap_write(map, MAX17042_CONFIG, config->config); - regmap_write(map, MAX17042_LearnCFG, config->learn_cfg); - regmap_write(map, MAX17042_FilterCFG, - config->filter_cfg); - regmap_write(map, MAX17042_RelaxCFG, config->relax_cfg); - if (chip->chip_type == MAXIM_DEVICE_TYPE_MAX17047 || - chip->chip_type == MAXIM_DEVICE_TYPE_MAX17050) - regmap_write(map, MAX17047_FullSOCThr, - config->full_soc_thresh); -} - -static void max17042_write_custom_regs(struct max17042_chip *chip) -{ - struct max17042_config_data *config = chip->pdata->config_data; - struct regmap *map = chip->regmap; - - max17042_write_verify_reg(map, MAX17042_RCOMP0, config->rcomp0); - max17042_write_verify_reg(map, MAX17042_TempCo, config->tcompc0); - max17042_write_verify_reg(map, MAX17042_ICHGTerm, config->ichgt_term); - if (chip->chip_type == MAXIM_DEVICE_TYPE_MAX17042) { - regmap_write(map, MAX17042_EmptyTempCo, config->empty_tempco); - max17042_write_verify_reg(map, MAX17042_K_empty0, - config->kempty0); - } else { - max17042_write_verify_reg(map, MAX17047_QRTbl00, - config->qrtbl00); - max17042_write_verify_reg(map, MAX17047_QRTbl10, - config->qrtbl10); - max17042_write_verify_reg(map, MAX17047_QRTbl20, - config->qrtbl20); - max17042_write_verify_reg(map, MAX17047_QRTbl30, - config->qrtbl30); - } -} - -static void max17042_update_capacity_regs(struct max17042_chip *chip) -{ - struct max17042_config_data *config = chip->pdata->config_data; - struct regmap *map = chip->regmap; - - max17042_write_verify_reg(map, MAX17042_FullCAP, - config->fullcap); - regmap_write(map, MAX17042_DesignCap, config->design_cap); - max17042_write_verify_reg(map, MAX17042_FullCAPNom, - config->fullcapnom); -} - -static void max17042_reset_vfsoc0_reg(struct max17042_chip *chip) -{ - unsigned int vfSoc; - struct regmap *map = chip->regmap; - - regmap_read(map, MAX17042_VFSOC, &vfSoc); - regmap_write(map, MAX17042_VFSOC0Enable, VFSOC0_UNLOCK); - max17042_write_verify_reg(map, MAX17042_VFSOC0, vfSoc); - regmap_write(map, MAX17042_VFSOC0Enable, VFSOC0_LOCK); -} - -static void max17042_load_new_capacity_params(struct max17042_chip *chip) -{ - u32 full_cap0, rep_cap, dq_acc, vfSoc; - u32 rem_cap; - - struct max17042_config_data *config = chip->pdata->config_data; - struct regmap *map = chip->regmap; - - regmap_read(map, MAX17042_FullCAP0, &full_cap0); - regmap_read(map, MAX17042_VFSOC, &vfSoc); - - /* fg_vfSoc needs to shifted by 8 bits to get the - * perc in 1% accuracy, to get the right rem_cap multiply - * full_cap0, fg_vfSoc and devide by 100 - */ - rem_cap = ((vfSoc >> 8) * full_cap0) / 100; - max17042_write_verify_reg(map, MAX17042_RemCap, rem_cap); - - rep_cap = rem_cap; - max17042_write_verify_reg(map, MAX17042_RepCap, rep_cap); - - /* Write dQ_acc to 200% of Capacity and dP_acc to 200% */ - dq_acc = config->fullcap / dQ_ACC_DIV; - max17042_write_verify_reg(map, MAX17042_dQacc, dq_acc); - max17042_write_verify_reg(map, MAX17042_dPacc, dP_ACC_200); - - max17042_write_verify_reg(map, MAX17042_FullCAP, - config->fullcap); - regmap_write(map, MAX17042_DesignCap, - config->design_cap); - max17042_write_verify_reg(map, MAX17042_FullCAPNom, - config->fullcapnom); - /* Update SOC register with new SOC */ - regmap_write(map, MAX17042_RepSOC, vfSoc); -} - -/* - * Block write all the override values coming from platform data. - * This function MUST be called before the POR initialization proceedure - * specified by maxim. - */ -static inline void max17042_override_por_values(struct max17042_chip *chip) -{ - struct regmap *map = chip->regmap; - struct max17042_config_data *config = chip->pdata->config_data; - - max17042_override_por(map, MAX17042_TGAIN, config->tgain); - max17042_override_por(map, MAx17042_TOFF, config->toff); - max17042_override_por(map, MAX17042_CGAIN, config->cgain); - max17042_override_por(map, MAX17042_COFF, config->coff); - - max17042_override_por(map, MAX17042_VALRT_Th, config->valrt_thresh); - max17042_override_por(map, MAX17042_TALRT_Th, config->talrt_thresh); - max17042_override_por(map, MAX17042_SALRT_Th, - config->soc_alrt_thresh); - max17042_override_por(map, MAX17042_CONFIG, config->config); - max17042_override_por(map, MAX17042_SHDNTIMER, config->shdntimer); - - max17042_override_por(map, MAX17042_DesignCap, config->design_cap); - max17042_override_por(map, MAX17042_ICHGTerm, config->ichgt_term); - - max17042_override_por(map, MAX17042_AtRate, config->at_rate); - max17042_override_por(map, MAX17042_LearnCFG, config->learn_cfg); - max17042_override_por(map, MAX17042_FilterCFG, config->filter_cfg); - max17042_override_por(map, MAX17042_RelaxCFG, config->relax_cfg); - max17042_override_por(map, MAX17042_MiscCFG, config->misc_cfg); - max17042_override_por(map, MAX17042_MaskSOC, config->masksoc); - - max17042_override_por(map, MAX17042_FullCAP, config->fullcap); - max17042_override_por(map, MAX17042_FullCAPNom, config->fullcapnom); - if (chip->chip_type == MAXIM_DEVICE_TYPE_MAX17042) - max17042_override_por(map, MAX17042_SOC_empty, - config->socempty); - max17042_override_por(map, MAX17042_LAvg_empty, config->lavg_empty); - max17042_override_por(map, MAX17042_dQacc, config->dqacc); - max17042_override_por(map, MAX17042_dPacc, config->dpacc); - - if (chip->chip_type == MAXIM_DEVICE_TYPE_MAX17042) - max17042_override_por(map, MAX17042_V_empty, config->vempty); - else - max17042_override_por(map, MAX17047_V_empty, config->vempty); - max17042_override_por(map, MAX17042_TempNom, config->temp_nom); - max17042_override_por(map, MAX17042_TempLim, config->temp_lim); - max17042_override_por(map, MAX17042_FCTC, config->fctc); - max17042_override_por(map, MAX17042_RCOMP0, config->rcomp0); - max17042_override_por(map, MAX17042_TempCo, config->tcompc0); - if (chip->chip_type) { - max17042_override_por(map, MAX17042_EmptyTempCo, - config->empty_tempco); - max17042_override_por(map, MAX17042_K_empty0, - config->kempty0); - } -} - -static int max17042_init_chip(struct max17042_chip *chip) -{ - struct regmap *map = chip->regmap; - int ret; - - max17042_override_por_values(chip); - /* After Power up, the MAX17042 requires 500mS in order - * to perform signal debouncing and initial SOC reporting - */ - msleep(500); - - /* Initialize configaration */ - max17042_write_config_regs(chip); - - /* write cell characterization data */ - ret = max17042_init_model(chip); - if (ret) { - dev_err(&chip->client->dev, "%s init failed\n", - __func__); - return -EIO; - } - - ret = max17042_verify_model_lock(chip); - if (ret) { - dev_err(&chip->client->dev, "%s lock verify failed\n", - __func__); - return -EIO; - } - /* write custom parameters */ - max17042_write_custom_regs(chip); - - /* update capacity params */ - max17042_update_capacity_regs(chip); - - /* delay must be atleast 350mS to allow VFSOC - * to be calculated from the new configuration - */ - msleep(350); - - /* reset vfsoc0 reg */ - max17042_reset_vfsoc0_reg(chip); - - /* load new capacity params */ - max17042_load_new_capacity_params(chip); - - /* Init complete, Clear the POR bit */ - regmap_update_bits(map, MAX17042_STATUS, STATUS_POR_BIT, 0x0); - return 0; -} - -static void max17042_set_soc_threshold(struct max17042_chip *chip, u16 off) -{ - struct regmap *map = chip->regmap; - u32 soc, soc_tr; - - /* program interrupt thesholds such that we should - * get interrupt for every 'off' perc change in the soc - */ - regmap_read(map, MAX17042_RepSOC, &soc); - soc >>= 8; - soc_tr = (soc + off) << 8; - soc_tr |= (soc - off); - regmap_write(map, MAX17042_SALRT_Th, soc_tr); -} - -static irqreturn_t max17042_thread_handler(int id, void *dev) -{ - struct max17042_chip *chip = dev; - u32 val; - - regmap_read(chip->regmap, MAX17042_STATUS, &val); - if ((val & STATUS_INTR_SOCMIN_BIT) || - (val & STATUS_INTR_SOCMAX_BIT)) { - dev_info(&chip->client->dev, "SOC threshold INTR\n"); - max17042_set_soc_threshold(chip, 1); - } - - power_supply_changed(chip->battery); - return IRQ_HANDLED; -} - -static void max17042_init_worker(struct work_struct *work) -{ - struct max17042_chip *chip = container_of(work, - struct max17042_chip, work); - int ret; - - /* Initialize registers according to values from the platform data */ - if (chip->pdata->enable_por_init && chip->pdata->config_data) { - ret = max17042_init_chip(chip); - if (ret) - return; - } - - chip->init_complete = 1; -} - -#ifdef CONFIG_OF -static struct max17042_platform_data * -max17042_get_pdata(struct device *dev) -{ - struct device_node *np = dev->of_node; - u32 prop; - struct max17042_platform_data *pdata; - - if (!np) - return dev->platform_data; - - pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); - if (!pdata) - return NULL; - - /* - * Require current sense resistor value to be specified for - * current-sense functionality to be enabled at all. - */ - if (of_property_read_u32(np, "maxim,rsns-microohm", &prop) == 0) { - pdata->r_sns = prop; - pdata->enable_current_sense = true; - } - - if (of_property_read_s32(np, "maxim,cold-temp", &pdata->temp_min)) - pdata->temp_min = INT_MIN; - if (of_property_read_s32(np, "maxim,over-heat-temp", &pdata->temp_max)) - pdata->temp_max = INT_MAX; - if (of_property_read_s32(np, "maxim,dead-volt", &pdata->vmin)) - pdata->vmin = INT_MIN; - if (of_property_read_s32(np, "maxim,over-volt", &pdata->vmax)) - pdata->vmax = INT_MAX; - - return pdata; -} -#else -static struct max17042_platform_data * -max17042_get_pdata(struct device *dev) -{ - return dev->platform_data; -} -#endif - -static const struct regmap_config max17042_regmap_config = { - .reg_bits = 8, - .val_bits = 16, - .val_format_endian = REGMAP_ENDIAN_NATIVE, -}; - -static const struct power_supply_desc max17042_psy_desc = { - .name = "max170xx_battery", - .type = POWER_SUPPLY_TYPE_BATTERY, - .get_property = max17042_get_property, - .set_property = max17042_set_property, - .property_is_writeable = max17042_property_is_writeable, - .properties = max17042_battery_props, - .num_properties = ARRAY_SIZE(max17042_battery_props), -}; - -static const struct power_supply_desc max17042_no_current_sense_psy_desc = { - .name = "max170xx_battery", - .type = POWER_SUPPLY_TYPE_BATTERY, - .get_property = max17042_get_property, - .set_property = max17042_set_property, - .property_is_writeable = max17042_property_is_writeable, - .properties = max17042_battery_props, - .num_properties = ARRAY_SIZE(max17042_battery_props) - 2, -}; - -static int max17042_probe(struct i2c_client *client, - const struct i2c_device_id *id) -{ - struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent); - const struct power_supply_desc *max17042_desc = &max17042_psy_desc; - struct power_supply_config psy_cfg = {}; - struct max17042_chip *chip; - int ret; - int i; - u32 val; - - if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_WORD_DATA)) - return -EIO; - - chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL); - if (!chip) - return -ENOMEM; - - chip->client = client; - chip->regmap = devm_regmap_init_i2c(client, &max17042_regmap_config); - if (IS_ERR(chip->regmap)) { - dev_err(&client->dev, "Failed to initialize regmap\n"); - return -EINVAL; - } - - chip->pdata = max17042_get_pdata(&client->dev); - if (!chip->pdata) { - dev_err(&client->dev, "no platform data provided\n"); - return -EINVAL; - } - - i2c_set_clientdata(client, chip); - chip->chip_type = id->driver_data; - psy_cfg.drv_data = chip; - - /* When current is not measured, - * CURRENT_NOW and CURRENT_AVG properties should be invisible. */ - if (!chip->pdata->enable_current_sense) - max17042_desc = &max17042_no_current_sense_psy_desc; - - if (chip->pdata->r_sns == 0) - chip->pdata->r_sns = MAX17042_DEFAULT_SNS_RESISTOR; - - if (chip->pdata->init_data) - for (i = 0; i < chip->pdata->num_init_data; i++) - regmap_write(chip->regmap, - chip->pdata->init_data[i].addr, - chip->pdata->init_data[i].data); - - if (!chip->pdata->enable_current_sense) { - regmap_write(chip->regmap, MAX17042_CGAIN, 0x0000); - regmap_write(chip->regmap, MAX17042_MiscCFG, 0x0003); - regmap_write(chip->regmap, MAX17042_LearnCFG, 0x0007); - } - - 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 = 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, - CONFIG_ALRT_BIT_ENBL); - max17042_set_soc_threshold(chip, 1); - } else { - client->irq = 0; - dev_err(&client->dev, "%s(): cannot get IRQ\n", - __func__); - } - } - - regmap_read(chip->regmap, MAX17042_STATUS, &val); - if (val & STATUS_POR_BIT) { - INIT_WORK(&chip->work, max17042_init_worker); - schedule_work(&chip->work); - } else { - chip->init_complete = 1; - } - - return 0; -} - -#ifdef CONFIG_PM_SLEEP -static int max17042_suspend(struct device *dev) -{ - struct max17042_chip *chip = dev_get_drvdata(dev); - - /* - * disable the irq and enable irq_wake - * capability to the interrupt line. - */ - if (chip->client->irq) { - disable_irq(chip->client->irq); - enable_irq_wake(chip->client->irq); - } - - return 0; -} - -static int max17042_resume(struct device *dev) -{ - struct max17042_chip *chip = dev_get_drvdata(dev); - - if (chip->client->irq) { - disable_irq_wake(chip->client->irq); - enable_irq(chip->client->irq); - /* re-program the SOC thresholds to 1% change */ - max17042_set_soc_threshold(chip, 1); - } - - return 0; -} -#endif - -static SIMPLE_DEV_PM_OPS(max17042_pm_ops, max17042_suspend, - max17042_resume); - -#ifdef CONFIG_OF -static const struct of_device_id max17042_dt_match[] = { - { .compatible = "maxim,max17042" }, - { .compatible = "maxim,max17047" }, - { .compatible = "maxim,max17050" }, - { }, -}; -MODULE_DEVICE_TABLE(of, max17042_dt_match); -#endif - -static const struct i2c_device_id max17042_id[] = { - { "max17042", MAXIM_DEVICE_TYPE_MAX17042 }, - { "max17047", MAXIM_DEVICE_TYPE_MAX17047 }, - { "max17050", MAXIM_DEVICE_TYPE_MAX17050 }, - { } -}; -MODULE_DEVICE_TABLE(i2c, max17042_id); - -static struct i2c_driver max17042_i2c_driver = { - .driver = { - .name = "max17042", - .of_match_table = of_match_ptr(max17042_dt_match), - .pm = &max17042_pm_ops, - }, - .probe = max17042_probe, - .id_table = max17042_id, -}; -module_i2c_driver(max17042_i2c_driver); - -MODULE_AUTHOR("MyungJoo Ham "); -MODULE_DESCRIPTION("MAX17042 Fuel Gauge"); -MODULE_LICENSE("GPL"); diff --git a/drivers/power/max77693_charger.c b/drivers/power/max77693_charger.c deleted file mode 100644 index 060cab5ae3aa..000000000000 --- a/drivers/power/max77693_charger.c +++ /dev/null @@ -1,771 +0,0 @@ -/* - * max77693_charger.c - Battery charger driver for the Maxim 77693 - * - * Copyright (C) 2014 Samsung Electronics - * Krzysztof Kozlowski - * - * 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 program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - */ - -#include -#include -#include -#include -#include -#include -#include - -#define MAX77693_CHARGER_NAME "max77693-charger" -static const char *max77693_charger_model = "MAX77693"; -static const char *max77693_charger_manufacturer = "Maxim Integrated"; - -struct max77693_charger { - struct device *dev; - struct max77693_dev *max77693; - struct power_supply *charger; - - u32 constant_volt; - u32 min_system_volt; - u32 thermal_regulation_temp; - u32 batttery_overcurrent; - u32 charge_input_threshold_volt; -}; - -static int max77693_get_charger_state(struct regmap *regmap, int *val) -{ - int ret; - unsigned int data; - - ret = regmap_read(regmap, MAX77693_CHG_REG_CHG_DETAILS_01, &data); - if (ret < 0) - return ret; - - data &= CHG_DETAILS_01_CHG_MASK; - data >>= CHG_DETAILS_01_CHG_SHIFT; - - switch (data) { - case MAX77693_CHARGING_PREQUALIFICATION: - case MAX77693_CHARGING_FAST_CONST_CURRENT: - case MAX77693_CHARGING_FAST_CONST_VOLTAGE: - case MAX77693_CHARGING_TOP_OFF: - /* In high temp the charging current is reduced, but still charging */ - case MAX77693_CHARGING_HIGH_TEMP: - *val = POWER_SUPPLY_STATUS_CHARGING; - break; - case MAX77693_CHARGING_DONE: - *val = POWER_SUPPLY_STATUS_FULL; - break; - case MAX77693_CHARGING_TIMER_EXPIRED: - case MAX77693_CHARGING_THERMISTOR_SUSPEND: - *val = POWER_SUPPLY_STATUS_NOT_CHARGING; - break; - case MAX77693_CHARGING_OFF: - case MAX77693_CHARGING_OVER_TEMP: - case MAX77693_CHARGING_WATCHDOG_EXPIRED: - *val = POWER_SUPPLY_STATUS_DISCHARGING; - break; - case MAX77693_CHARGING_RESERVED: - default: - *val = POWER_SUPPLY_STATUS_UNKNOWN; - } - - return 0; -} - -static int max77693_get_charge_type(struct regmap *regmap, int *val) -{ - int ret; - unsigned int data; - - ret = regmap_read(regmap, MAX77693_CHG_REG_CHG_DETAILS_01, &data); - if (ret < 0) - return ret; - - data &= CHG_DETAILS_01_CHG_MASK; - data >>= CHG_DETAILS_01_CHG_SHIFT; - - switch (data) { - case MAX77693_CHARGING_PREQUALIFICATION: - /* - * Top-off: trickle or fast? In top-off the current varies between - * 100 and 250 mA. It is higher than prequalification current. - */ - case MAX77693_CHARGING_TOP_OFF: - *val = POWER_SUPPLY_CHARGE_TYPE_TRICKLE; - break; - case MAX77693_CHARGING_FAST_CONST_CURRENT: - case MAX77693_CHARGING_FAST_CONST_VOLTAGE: - /* In high temp the charging current is reduced, but still charging */ - case MAX77693_CHARGING_HIGH_TEMP: - *val = POWER_SUPPLY_CHARGE_TYPE_FAST; - break; - case MAX77693_CHARGING_DONE: - case MAX77693_CHARGING_TIMER_EXPIRED: - case MAX77693_CHARGING_THERMISTOR_SUSPEND: - case MAX77693_CHARGING_OFF: - case MAX77693_CHARGING_OVER_TEMP: - case MAX77693_CHARGING_WATCHDOG_EXPIRED: - *val = POWER_SUPPLY_CHARGE_TYPE_NONE; - break; - case MAX77693_CHARGING_RESERVED: - default: - *val = POWER_SUPPLY_CHARGE_TYPE_UNKNOWN; - } - - return 0; -} - -/* - * Supported health statuses: - * - POWER_SUPPLY_HEALTH_DEAD - * - POWER_SUPPLY_HEALTH_GOOD - * - POWER_SUPPLY_HEALTH_OVERVOLTAGE - * - POWER_SUPPLY_HEALTH_SAFETY_TIMER_EXPIRE - * - POWER_SUPPLY_HEALTH_UNKNOWN - * - POWER_SUPPLY_HEALTH_UNSPEC_FAILURE - */ -static int max77693_get_battery_health(struct regmap *regmap, int *val) -{ - int ret; - unsigned int data; - - ret = regmap_read(regmap, MAX77693_CHG_REG_CHG_DETAILS_01, &data); - if (ret < 0) - return ret; - - data &= CHG_DETAILS_01_BAT_MASK; - data >>= CHG_DETAILS_01_BAT_SHIFT; - - switch (data) { - case MAX77693_BATTERY_NOBAT: - *val = POWER_SUPPLY_HEALTH_DEAD; - break; - case MAX77693_BATTERY_PREQUALIFICATION: - case MAX77693_BATTERY_GOOD: - case MAX77693_BATTERY_LOWVOLTAGE: - *val = POWER_SUPPLY_HEALTH_GOOD; - break; - case MAX77693_BATTERY_TIMER_EXPIRED: - /* - * Took longer to charge than expected, charging suspended. - * Damaged battery? - */ - *val = POWER_SUPPLY_HEALTH_SAFETY_TIMER_EXPIRE; - break; - case MAX77693_BATTERY_OVERVOLTAGE: - *val = POWER_SUPPLY_HEALTH_OVERVOLTAGE; - break; - case MAX77693_BATTERY_OVERCURRENT: - *val = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; - break; - case MAX77693_BATTERY_RESERVED: - default: - *val = POWER_SUPPLY_HEALTH_UNKNOWN; - break; - } - - return 0; -} - -static int max77693_get_present(struct regmap *regmap, int *val) -{ - unsigned int data; - int ret; - - /* - * Read CHG_INT_OK register. High DETBAT bit here should be - * equal to value 0x0 in CHG_DETAILS_01/BAT field. - */ - ret = regmap_read(regmap, MAX77693_CHG_REG_CHG_INT_OK, &data); - if (ret < 0) - return ret; - - *val = (data & CHG_INT_OK_DETBAT_MASK) ? 0 : 1; - - return 0; -} - -static int max77693_get_online(struct regmap *regmap, int *val) -{ - unsigned int data; - int ret; - - ret = regmap_read(regmap, MAX77693_CHG_REG_CHG_INT_OK, &data); - if (ret < 0) - return ret; - - *val = (data & CHG_INT_OK_CHGIN_MASK) ? 1 : 0; - - return 0; -} - -static enum power_supply_property max77693_charger_props[] = { - POWER_SUPPLY_PROP_STATUS, - POWER_SUPPLY_PROP_CHARGE_TYPE, - POWER_SUPPLY_PROP_HEALTH, - POWER_SUPPLY_PROP_PRESENT, - POWER_SUPPLY_PROP_ONLINE, - POWER_SUPPLY_PROP_MODEL_NAME, - POWER_SUPPLY_PROP_MANUFACTURER, -}; - -static int max77693_charger_get_property(struct power_supply *psy, - enum power_supply_property psp, - union power_supply_propval *val) -{ - struct max77693_charger *chg = power_supply_get_drvdata(psy); - struct regmap *regmap = chg->max77693->regmap; - int ret = 0; - - switch (psp) { - case POWER_SUPPLY_PROP_STATUS: - ret = max77693_get_charger_state(regmap, &val->intval); - break; - case POWER_SUPPLY_PROP_CHARGE_TYPE: - ret = max77693_get_charge_type(regmap, &val->intval); - break; - case POWER_SUPPLY_PROP_HEALTH: - ret = max77693_get_battery_health(regmap, &val->intval); - break; - case POWER_SUPPLY_PROP_PRESENT: - ret = max77693_get_present(regmap, &val->intval); - break; - case POWER_SUPPLY_PROP_ONLINE: - ret = max77693_get_online(regmap, &val->intval); - break; - case POWER_SUPPLY_PROP_MODEL_NAME: - val->strval = max77693_charger_model; - break; - case POWER_SUPPLY_PROP_MANUFACTURER: - val->strval = max77693_charger_manufacturer; - break; - default: - return -EINVAL; - } - - return ret; -} - -static const struct power_supply_desc max77693_charger_desc = { - .name = MAX77693_CHARGER_NAME, - .type = POWER_SUPPLY_TYPE_BATTERY, - .properties = max77693_charger_props, - .num_properties = ARRAY_SIZE(max77693_charger_props), - .get_property = max77693_charger_get_property, -}; - -static ssize_t device_attr_store(struct device *dev, - struct device_attribute *attr, const char *buf, size_t count, - int (*fn)(struct max77693_charger *, unsigned long)) -{ - struct max77693_charger *chg = dev_get_drvdata(dev); - unsigned long val; - int ret; - - ret = kstrtoul(buf, 10, &val); - if (ret) - return ret; - - ret = fn(chg, val); - if (ret) - return ret; - - return count; -} - -static ssize_t fast_charge_timer_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - struct max77693_charger *chg = dev_get_drvdata(dev); - unsigned int data, val; - int ret; - - ret = regmap_read(chg->max77693->regmap, MAX77693_CHG_REG_CHG_CNFG_01, - &data); - if (ret < 0) - return ret; - - data &= CHG_CNFG_01_FCHGTIME_MASK; - data >>= CHG_CNFG_01_FCHGTIME_SHIFT; - switch (data) { - case 0x1 ... 0x7: - /* Starting from 4 hours, step by 2 hours */ - val = 4 + (data - 1) * 2; - break; - case 0x0: - default: - val = 0; - break; - } - - return scnprintf(buf, PAGE_SIZE, "%u\n", val); -} - -static int max77693_set_fast_charge_timer(struct max77693_charger *chg, - unsigned long hours) -{ - unsigned int data; - - /* - * 0x00 - disable - * 0x01 - 4h - * 0x02 - 6h - * ... - * 0x07 - 16h - * Round down odd values. - */ - switch (hours) { - case 4 ... 16: - data = (hours - 4) / 2 + 1; - break; - case 0: - /* Disable */ - data = 0; - break; - default: - return -EINVAL; - } - data <<= CHG_CNFG_01_FCHGTIME_SHIFT; - - return regmap_update_bits(chg->max77693->regmap, - MAX77693_CHG_REG_CHG_CNFG_01, - CHG_CNFG_01_FCHGTIME_MASK, data); -} - -static ssize_t fast_charge_timer_store(struct device *dev, - struct device_attribute *attr, const char *buf, size_t count) -{ - return device_attr_store(dev, attr, buf, count, - max77693_set_fast_charge_timer); -} - -static ssize_t top_off_threshold_current_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - struct max77693_charger *chg = dev_get_drvdata(dev); - unsigned int data, val; - int ret; - - ret = regmap_read(chg->max77693->regmap, MAX77693_CHG_REG_CHG_CNFG_03, - &data); - if (ret < 0) - return ret; - - data &= CHG_CNFG_03_TOITH_MASK; - data >>= CHG_CNFG_03_TOITH_SHIFT; - - if (data <= 0x04) - val = 100000 + data * 25000; - else - val = data * 50000; - - return scnprintf(buf, PAGE_SIZE, "%u\n", val); -} - -static int max77693_set_top_off_threshold_current(struct max77693_charger *chg, - unsigned long uamp) -{ - unsigned int data; - - if (uamp < 100000 || uamp > 350000) - return -EINVAL; - - if (uamp <= 200000) - data = (uamp - 100000) / 25000; - else - /* (200000, 350000> */ - data = uamp / 50000; - - data <<= CHG_CNFG_03_TOITH_SHIFT; - - return regmap_update_bits(chg->max77693->regmap, - MAX77693_CHG_REG_CHG_CNFG_03, - CHG_CNFG_03_TOITH_MASK, data); -} - -static ssize_t top_off_threshold_current_store(struct device *dev, - struct device_attribute *attr, const char *buf, size_t count) -{ - return device_attr_store(dev, attr, buf, count, - max77693_set_top_off_threshold_current); -} - -static ssize_t top_off_timer_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - struct max77693_charger *chg = dev_get_drvdata(dev); - unsigned int data, val; - int ret; - - ret = regmap_read(chg->max77693->regmap, MAX77693_CHG_REG_CHG_CNFG_03, - &data); - if (ret < 0) - return ret; - - data &= CHG_CNFG_03_TOTIME_MASK; - data >>= CHG_CNFG_03_TOTIME_SHIFT; - - val = data * 10; - - return scnprintf(buf, PAGE_SIZE, "%u\n", val); -} - -static int max77693_set_top_off_timer(struct max77693_charger *chg, - unsigned long minutes) -{ - unsigned int data; - - if (minutes > 70) - return -EINVAL; - - data = minutes / 10; - data <<= CHG_CNFG_03_TOTIME_SHIFT; - - return regmap_update_bits(chg->max77693->regmap, - MAX77693_CHG_REG_CHG_CNFG_03, - CHG_CNFG_03_TOTIME_MASK, data); -} - -static ssize_t top_off_timer_store(struct device *dev, - struct device_attribute *attr, const char *buf, size_t count) -{ - return device_attr_store(dev, attr, buf, count, - max77693_set_top_off_timer); -} - -static DEVICE_ATTR_RW(fast_charge_timer); -static DEVICE_ATTR_RW(top_off_threshold_current); -static DEVICE_ATTR_RW(top_off_timer); - -static int max77693_set_constant_volt(struct max77693_charger *chg, - unsigned int uvolt) -{ - unsigned int data; - - /* - * 0x00 - 3.650 V - * 0x01 - 3.675 V - * ... - * 0x1b - 4.325 V - * 0x1c - 4.340 V - * 0x1d - 4.350 V - * 0x1e - 4.375 V - * 0x1f - 4.400 V - */ - if (uvolt >= 3650000 && uvolt < 4340000) - data = (uvolt - 3650000) / 25000; - else if (uvolt >= 4340000 && uvolt < 4350000) - data = 0x1c; - else if (uvolt >= 4350000 && uvolt <= 4400000) - data = 0x1d + (uvolt - 4350000) / 25000; - else { - dev_err(chg->dev, "Wrong value for charging constant voltage\n"); - return -EINVAL; - } - - data <<= CHG_CNFG_04_CHGCVPRM_SHIFT; - - dev_dbg(chg->dev, "Charging constant voltage: %u (0x%x)\n", uvolt, - data); - - return regmap_update_bits(chg->max77693->regmap, - MAX77693_CHG_REG_CHG_CNFG_04, - CHG_CNFG_04_CHGCVPRM_MASK, data); -} - -static int max77693_set_min_system_volt(struct max77693_charger *chg, - unsigned int uvolt) -{ - unsigned int data; - - if (uvolt < 3000000 || uvolt > 3700000) { - dev_err(chg->dev, "Wrong value for minimum system regulation voltage\n"); - return -EINVAL; - } - - data = (uvolt - 3000000) / 100000; - - data <<= CHG_CNFG_04_MINVSYS_SHIFT; - - dev_dbg(chg->dev, "Minimum system regulation voltage: %u (0x%x)\n", - uvolt, data); - - return regmap_update_bits(chg->max77693->regmap, - MAX77693_CHG_REG_CHG_CNFG_04, - CHG_CNFG_04_MINVSYS_MASK, data); -} - -static int max77693_set_thermal_regulation_temp(struct max77693_charger *chg, - unsigned int cels) -{ - unsigned int data; - - switch (cels) { - case 70: - case 85: - case 100: - case 115: - data = (cels - 70) / 15; - break; - default: - dev_err(chg->dev, "Wrong value for thermal regulation loop temperature\n"); - return -EINVAL; - } - - data <<= CHG_CNFG_07_REGTEMP_SHIFT; - - dev_dbg(chg->dev, "Thermal regulation loop temperature: %u (0x%x)\n", - cels, data); - - return regmap_update_bits(chg->max77693->regmap, - MAX77693_CHG_REG_CHG_CNFG_07, - CHG_CNFG_07_REGTEMP_MASK, data); -} - -static int max77693_set_batttery_overcurrent(struct max77693_charger *chg, - unsigned int uamp) -{ - unsigned int data; - - if (uamp && (uamp < 2000000 || uamp > 3500000)) { - dev_err(chg->dev, "Wrong value for battery overcurrent\n"); - return -EINVAL; - } - - if (uamp) - data = ((uamp - 2000000) / 250000) + 1; - else - data = 0; /* disable */ - - data <<= CHG_CNFG_12_B2SOVRC_SHIFT; - - dev_dbg(chg->dev, "Battery overcurrent: %u (0x%x)\n", uamp, data); - - return regmap_update_bits(chg->max77693->regmap, - MAX77693_CHG_REG_CHG_CNFG_12, - CHG_CNFG_12_B2SOVRC_MASK, data); -} - -static int max77693_set_charge_input_threshold_volt(struct max77693_charger *chg, - unsigned int uvolt) -{ - unsigned int data; - - switch (uvolt) { - case 4300000: - data = 0x0; - break; - case 4700000: - case 4800000: - case 4900000: - data = (uvolt - 4700000) / 100000; - default: - dev_err(chg->dev, "Wrong value for charge input voltage regulation threshold\n"); - return -EINVAL; - } - - data <<= CHG_CNFG_12_VCHGINREG_SHIFT; - - dev_dbg(chg->dev, "Charge input voltage regulation threshold: %u (0x%x)\n", - uvolt, data); - - return regmap_update_bits(chg->max77693->regmap, - MAX77693_CHG_REG_CHG_CNFG_12, - CHG_CNFG_12_VCHGINREG_MASK, data); -} - -/* - * Sets charger registers to proper and safe default values. - */ -static int max77693_reg_init(struct max77693_charger *chg) -{ - int ret; - unsigned int data; - - /* Unlock charger register protection */ - data = (0x3 << CHG_CNFG_06_CHGPROT_SHIFT); - ret = regmap_update_bits(chg->max77693->regmap, - MAX77693_CHG_REG_CHG_CNFG_06, - CHG_CNFG_06_CHGPROT_MASK, data); - if (ret) { - dev_err(chg->dev, "Error unlocking registers: %d\n", ret); - return ret; - } - - ret = max77693_set_fast_charge_timer(chg, DEFAULT_FAST_CHARGE_TIMER); - if (ret) - return ret; - - ret = max77693_set_top_off_threshold_current(chg, - DEFAULT_TOP_OFF_THRESHOLD_CURRENT); - if (ret) - return ret; - - ret = max77693_set_top_off_timer(chg, DEFAULT_TOP_OFF_TIMER); - if (ret) - return ret; - - ret = max77693_set_constant_volt(chg, chg->constant_volt); - if (ret) - return ret; - - ret = max77693_set_min_system_volt(chg, chg->min_system_volt); - if (ret) - return ret; - - ret = max77693_set_thermal_regulation_temp(chg, - chg->thermal_regulation_temp); - if (ret) - return ret; - - ret = max77693_set_batttery_overcurrent(chg, chg->batttery_overcurrent); - if (ret) - return ret; - - return max77693_set_charge_input_threshold_volt(chg, - chg->charge_input_threshold_volt); -} - -#ifdef CONFIG_OF -static int max77693_dt_init(struct device *dev, struct max77693_charger *chg) -{ - struct device_node *np = dev->of_node; - - if (!np) { - dev_err(dev, "no charger OF node\n"); - return -EINVAL; - } - - if (of_property_read_u32(np, "maxim,constant-microvolt", - &chg->constant_volt)) - chg->constant_volt = DEFAULT_CONSTANT_VOLT; - - if (of_property_read_u32(np, "maxim,min-system-microvolt", - &chg->min_system_volt)) - chg->min_system_volt = DEFAULT_MIN_SYSTEM_VOLT; - - if (of_property_read_u32(np, "maxim,thermal-regulation-celsius", - &chg->thermal_regulation_temp)) - chg->thermal_regulation_temp = DEFAULT_THERMAL_REGULATION_TEMP; - - if (of_property_read_u32(np, "maxim,battery-overcurrent-microamp", - &chg->batttery_overcurrent)) - chg->batttery_overcurrent = DEFAULT_BATTERY_OVERCURRENT; - - if (of_property_read_u32(np, "maxim,charge-input-threshold-microvolt", - &chg->charge_input_threshold_volt)) - chg->charge_input_threshold_volt = - DEFAULT_CHARGER_INPUT_THRESHOLD_VOLT; - - return 0; -} -#else /* CONFIG_OF */ -static int max77693_dt_init(struct device *dev, struct max77693_charger *chg) -{ - return 0; -} -#endif /* CONFIG_OF */ - -static int max77693_charger_probe(struct platform_device *pdev) -{ - struct max77693_charger *chg; - struct power_supply_config psy_cfg = {}; - struct max77693_dev *max77693 = dev_get_drvdata(pdev->dev.parent); - int ret; - - chg = devm_kzalloc(&pdev->dev, sizeof(*chg), GFP_KERNEL); - if (!chg) - return -ENOMEM; - - platform_set_drvdata(pdev, chg); - chg->dev = &pdev->dev; - chg->max77693 = max77693; - - ret = max77693_dt_init(&pdev->dev, chg); - if (ret) - return ret; - - ret = max77693_reg_init(chg); - if (ret) - return ret; - - psy_cfg.drv_data = chg; - - ret = device_create_file(&pdev->dev, &dev_attr_fast_charge_timer); - if (ret) { - dev_err(&pdev->dev, "failed: create fast charge timer sysfs entry\n"); - goto err; - } - - ret = device_create_file(&pdev->dev, - &dev_attr_top_off_threshold_current); - if (ret) { - dev_err(&pdev->dev, "failed: create top off current sysfs entry\n"); - goto err; - } - - ret = device_create_file(&pdev->dev, &dev_attr_top_off_timer); - if (ret) { - dev_err(&pdev->dev, "failed: create top off timer sysfs entry\n"); - goto err; - } - - chg->charger = power_supply_register(&pdev->dev, - &max77693_charger_desc, - &psy_cfg); - if (IS_ERR(chg->charger)) { - dev_err(&pdev->dev, "failed: power supply register\n"); - ret = PTR_ERR(chg->charger); - goto err; - } - - return 0; - -err: - device_remove_file(&pdev->dev, &dev_attr_top_off_timer); - device_remove_file(&pdev->dev, &dev_attr_top_off_threshold_current); - device_remove_file(&pdev->dev, &dev_attr_fast_charge_timer); - - return ret; -} - -static int max77693_charger_remove(struct platform_device *pdev) -{ - struct max77693_charger *chg = platform_get_drvdata(pdev); - - device_remove_file(&pdev->dev, &dev_attr_top_off_timer); - device_remove_file(&pdev->dev, &dev_attr_top_off_threshold_current); - device_remove_file(&pdev->dev, &dev_attr_fast_charge_timer); - - power_supply_unregister(chg->charger); - - return 0; -} - -static const struct platform_device_id max77693_charger_id[] = { - { "max77693-charger", 0, }, - { } -}; -MODULE_DEVICE_TABLE(platform, max77693_charger_id); - -static struct platform_driver max77693_charger_driver = { - .driver = { - .name = "max77693-charger", - }, - .probe = max77693_charger_probe, - .remove = max77693_charger_remove, - .id_table = max77693_charger_id, -}; -module_platform_driver(max77693_charger_driver); - -MODULE_AUTHOR("Krzysztof Kozlowski "); -MODULE_DESCRIPTION("Maxim 77693 charger driver"); -MODULE_LICENSE("GPL"); diff --git a/drivers/power/max8903_charger.c b/drivers/power/max8903_charger.c deleted file mode 100644 index fdc73d686153..000000000000 --- a/drivers/power/max8903_charger.c +++ /dev/null @@ -1,459 +0,0 @@ -/* - * max8903_charger.c - Maxim 8903 USB/Adapter Charger Driver - * - * Copyright (C) 2011 Samsung Electronics - * MyungJoo Ham - * - * 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 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 - * - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -struct max8903_data { - struct max8903_pdata *pdata; - struct device *dev; - struct power_supply *psy; - struct power_supply_desc psy_desc; - bool fault; - bool usb_in; - bool ta_in; -}; - -static enum power_supply_property max8903_charger_props[] = { - POWER_SUPPLY_PROP_STATUS, /* Charger status output */ - POWER_SUPPLY_PROP_ONLINE, /* External power source */ - POWER_SUPPLY_PROP_HEALTH, /* Fault or OK */ -}; - -static int max8903_get_property(struct power_supply *psy, - enum power_supply_property psp, - union power_supply_propval *val) -{ - struct max8903_data *data = power_supply_get_drvdata(psy); - - switch (psp) { - case POWER_SUPPLY_PROP_STATUS: - val->intval = POWER_SUPPLY_STATUS_UNKNOWN; - if (gpio_is_valid(data->pdata->chg)) { - if (gpio_get_value(data->pdata->chg) == 0) - val->intval = POWER_SUPPLY_STATUS_CHARGING; - else if (data->usb_in || data->ta_in) - val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; - else - val->intval = POWER_SUPPLY_STATUS_DISCHARGING; - } - break; - case POWER_SUPPLY_PROP_ONLINE: - val->intval = 0; - if (data->usb_in || data->ta_in) - val->intval = 1; - break; - case POWER_SUPPLY_PROP_HEALTH: - val->intval = POWER_SUPPLY_HEALTH_GOOD; - if (data->fault) - val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; - break; - default: - return -EINVAL; - } - - return 0; -} - -static irqreturn_t max8903_dcin(int irq, void *_data) -{ - struct max8903_data *data = _data; - struct max8903_pdata *pdata = data->pdata; - bool ta_in; - enum power_supply_type old_type; - - ta_in = gpio_get_value(pdata->dok) ? false : true; - - if (ta_in == data->ta_in) - return IRQ_HANDLED; - - data->ta_in = ta_in; - - /* Set Current-Limit-Mode 1:DC 0:USB */ - if (gpio_is_valid(pdata->dcm)) - gpio_set_value(pdata->dcm, ta_in ? 1 : 0); - - /* Charger Enable / Disable (cen is negated) */ - if (gpio_is_valid(pdata->cen)) - gpio_set_value(pdata->cen, ta_in ? 0 : - (data->usb_in ? 0 : 1)); - - dev_dbg(data->dev, "TA(DC-IN) Charger %s.\n", ta_in ? - "Connected" : "Disconnected"); - - old_type = data->psy_desc.type; - - if (data->ta_in) - data->psy_desc.type = POWER_SUPPLY_TYPE_MAINS; - else if (data->usb_in) - data->psy_desc.type = POWER_SUPPLY_TYPE_USB; - else - data->psy_desc.type = POWER_SUPPLY_TYPE_BATTERY; - - if (old_type != data->psy_desc.type) - power_supply_changed(data->psy); - - return IRQ_HANDLED; -} - -static irqreturn_t max8903_usbin(int irq, void *_data) -{ - struct max8903_data *data = _data; - struct max8903_pdata *pdata = data->pdata; - bool usb_in; - enum power_supply_type old_type; - - usb_in = gpio_get_value(pdata->uok) ? false : true; - - if (usb_in == data->usb_in) - return IRQ_HANDLED; - - data->usb_in = usb_in; - - /* Do not touch Current-Limit-Mode */ - - /* Charger Enable / Disable (cen is negated) */ - if (gpio_is_valid(pdata->cen)) - gpio_set_value(pdata->cen, usb_in ? 0 : - (data->ta_in ? 0 : 1)); - - dev_dbg(data->dev, "USB Charger %s.\n", usb_in ? - "Connected" : "Disconnected"); - - old_type = data->psy_desc.type; - - if (data->ta_in) - data->psy_desc.type = POWER_SUPPLY_TYPE_MAINS; - else if (data->usb_in) - data->psy_desc.type = POWER_SUPPLY_TYPE_USB; - else - data->psy_desc.type = POWER_SUPPLY_TYPE_BATTERY; - - if (old_type != data->psy_desc.type) - power_supply_changed(data->psy); - - return IRQ_HANDLED; -} - -static irqreturn_t max8903_fault(int irq, void *_data) -{ - struct max8903_data *data = _data; - struct max8903_pdata *pdata = data->pdata; - bool fault; - - fault = gpio_get_value(pdata->flt) ? false : true; - - if (fault == data->fault) - return IRQ_HANDLED; - - data->fault = fault; - - if (fault) - dev_err(data->dev, "Charger suffers a fault and stops.\n"); - else - dev_err(data->dev, "Charger recovered from a fault.\n"); - - return IRQ_HANDLED; -} - -static struct max8903_pdata *max8903_parse_dt_data(struct device *dev) -{ - struct device_node *np = dev->of_node; - struct max8903_pdata *pdata = NULL; - - if (!np) - return NULL; - - pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); - if (!pdata) - return NULL; - - pdata->dc_valid = false; - pdata->usb_valid = false; - - pdata->cen = of_get_named_gpio(np, "cen-gpios", 0); - if (!gpio_is_valid(pdata->cen)) - pdata->cen = -EINVAL; - - pdata->chg = of_get_named_gpio(np, "chg-gpios", 0); - if (!gpio_is_valid(pdata->chg)) - pdata->chg = -EINVAL; - - pdata->flt = of_get_named_gpio(np, "flt-gpios", 0); - if (!gpio_is_valid(pdata->flt)) - pdata->flt = -EINVAL; - - pdata->usus = of_get_named_gpio(np, "usus-gpios", 0); - if (!gpio_is_valid(pdata->usus)) - pdata->usus = -EINVAL; - - pdata->dcm = of_get_named_gpio(np, "dcm-gpios", 0); - if (!gpio_is_valid(pdata->dcm)) - pdata->dcm = -EINVAL; - - pdata->dok = of_get_named_gpio(np, "dok-gpios", 0); - if (!gpio_is_valid(pdata->dok)) - pdata->dok = -EINVAL; - else - pdata->dc_valid = true; - - pdata->uok = of_get_named_gpio(np, "uok-gpios", 0); - if (!gpio_is_valid(pdata->uok)) - pdata->uok = -EINVAL; - else - pdata->usb_valid = true; - - return pdata; -} - -static int max8903_setup_gpios(struct platform_device *pdev) -{ - struct max8903_data *data = platform_get_drvdata(pdev); - struct device *dev = &pdev->dev; - struct max8903_pdata *pdata = pdev->dev.platform_data; - int ret = 0; - int gpio; - int ta_in = 0; - int usb_in = 0; - - if (pdata->dc_valid) { - if (gpio_is_valid(pdata->dok)) { - ret = devm_gpio_request(dev, pdata->dok, - data->psy_desc.name); - if (ret) { - dev_err(dev, - "Failed GPIO request for dok: %d err %d\n", - pdata->dok, ret); - return ret; - } - - gpio = pdata->dok; /* PULL_UPed Interrupt */ - ta_in = gpio_get_value(gpio) ? 0 : 1; - } else { - dev_err(dev, "When DC is wired, DOK should be wired as well.\n"); - return -EINVAL; - } - } - - if (gpio_is_valid(pdata->dcm)) { - ret = devm_gpio_request(dev, pdata->dcm, data->psy_desc.name); - if (ret) { - dev_err(dev, - "Failed GPIO request for dcm: %d err %d\n", - pdata->dcm, ret); - return ret; - } - - gpio = pdata->dcm; /* Output */ - gpio_set_value(gpio, ta_in); - } - - if (pdata->usb_valid) { - if (gpio_is_valid(pdata->uok)) { - ret = devm_gpio_request(dev, pdata->uok, - data->psy_desc.name); - if (ret) { - dev_err(dev, - "Failed GPIO request for uok: %d err %d\n", - pdata->uok, ret); - return ret; - } - - gpio = pdata->uok; - usb_in = gpio_get_value(gpio) ? 0 : 1; - } else { - dev_err(dev, "When USB is wired, UOK should be wired." - "as well.\n"); - return -EINVAL; - } - } - - if (gpio_is_valid(pdata->cen)) { - ret = devm_gpio_request(dev, pdata->cen, data->psy_desc.name); - if (ret) { - dev_err(dev, - "Failed GPIO request for cen: %d err %d\n", - pdata->cen, ret); - return ret; - } - - gpio_set_value(pdata->cen, (ta_in || usb_in) ? 0 : 1); - } - - if (gpio_is_valid(pdata->chg)) { - ret = devm_gpio_request(dev, pdata->chg, data->psy_desc.name); - if (ret) { - dev_err(dev, - "Failed GPIO request for chg: %d err %d\n", - pdata->chg, ret); - return ret; - } - } - - if (gpio_is_valid(pdata->flt)) { - ret = devm_gpio_request(dev, pdata->flt, data->psy_desc.name); - if (ret) { - dev_err(dev, - "Failed GPIO request for flt: %d err %d\n", - pdata->flt, ret); - return ret; - } - } - - if (gpio_is_valid(pdata->usus)) { - ret = devm_gpio_request(dev, pdata->usus, data->psy_desc.name); - if (ret) { - dev_err(dev, - "Failed GPIO request for usus: %d err %d\n", - pdata->usus, ret); - return ret; - } - } - - data->fault = false; - data->ta_in = ta_in; - data->usb_in = usb_in; - - return 0; -} - -static int max8903_probe(struct platform_device *pdev) -{ - struct max8903_data *data; - struct device *dev = &pdev->dev; - struct max8903_pdata *pdata = pdev->dev.platform_data; - struct power_supply_config psy_cfg = {}; - int ret = 0; - - data = devm_kzalloc(dev, sizeof(struct max8903_data), GFP_KERNEL); - if (!data) - return -ENOMEM; - - if (IS_ENABLED(CONFIG_OF) && !pdata && dev->of_node) - pdata = max8903_parse_dt_data(dev); - - if (!pdata) { - dev_err(dev, "No platform data.\n"); - return -EINVAL; - } - - pdev->dev.platform_data = pdata; - data->pdata = pdata; - data->dev = dev; - platform_set_drvdata(pdev, data); - - if (pdata->dc_valid == false && pdata->usb_valid == false) { - dev_err(dev, "No valid power sources.\n"); - return -EINVAL; - } - - ret = max8903_setup_gpios(pdev); - if (ret) - return ret; - - data->psy_desc.name = "max8903_charger"; - data->psy_desc.type = (data->ta_in) ? POWER_SUPPLY_TYPE_MAINS : - ((data->usb_in) ? POWER_SUPPLY_TYPE_USB : - POWER_SUPPLY_TYPE_BATTERY); - data->psy_desc.get_property = max8903_get_property; - data->psy_desc.properties = max8903_charger_props; - data->psy_desc.num_properties = ARRAY_SIZE(max8903_charger_props); - - psy_cfg.of_node = dev->of_node; - psy_cfg.drv_data = data; - - 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"); - return PTR_ERR(data->psy); - } - - if (pdata->dc_valid) { - ret = devm_request_threaded_irq(dev, gpio_to_irq(pdata->dok), - NULL, max8903_dcin, - IRQF_TRIGGER_FALLING | - IRQF_TRIGGER_RISING | IRQF_ONESHOT, - "MAX8903 DC IN", data); - if (ret) { - dev_err(dev, "Cannot request irq %d for DC (%d)\n", - gpio_to_irq(pdata->dok), ret); - return ret; - } - } - - if (pdata->usb_valid) { - ret = devm_request_threaded_irq(dev, gpio_to_irq(pdata->uok), - NULL, max8903_usbin, - IRQF_TRIGGER_FALLING | - IRQF_TRIGGER_RISING | IRQF_ONESHOT, - "MAX8903 USB IN", data); - if (ret) { - dev_err(dev, "Cannot request irq %d for USB (%d)\n", - gpio_to_irq(pdata->uok), ret); - return ret; - } - } - - if (gpio_is_valid(pdata->flt)) { - ret = devm_request_threaded_irq(dev, gpio_to_irq(pdata->flt), - NULL, max8903_fault, - IRQF_TRIGGER_FALLING | - IRQF_TRIGGER_RISING | IRQF_ONESHOT, - "MAX8903 Fault", data); - if (ret) { - dev_err(dev, "Cannot request irq %d for Fault (%d)\n", - gpio_to_irq(pdata->flt), ret); - return ret; - } - } - - return 0; -} - -static const struct of_device_id max8903_match_ids[] = { - { .compatible = "maxim,max8903", }, - { /* sentinel */ } -}; -MODULE_DEVICE_TABLE(of, max8903_match_ids); - -static struct platform_driver max8903_driver = { - .probe = max8903_probe, - .driver = { - .name = "max8903-charger", - .of_match_table = max8903_match_ids - }, -}; - -module_platform_driver(max8903_driver); - -MODULE_LICENSE("GPL"); -MODULE_DESCRIPTION("MAX8903 Charger Driver"); -MODULE_AUTHOR("MyungJoo Ham "); -MODULE_ALIAS("platform:max8903-charger"); diff --git a/drivers/power/max8925_power.c b/drivers/power/max8925_power.c deleted file mode 100644 index 3b94620ce5c1..000000000000 --- a/drivers/power/max8925_power.c +++ /dev/null @@ -1,596 +0,0 @@ -/* - * Battery driver for Maxim MAX8925 - * - * Copyright (c) 2009-2010 Marvell International Ltd. - * Haojian Zhuang - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/* registers in GPM */ -#define MAX8925_OUT5VEN 0x54 -#define MAX8925_OUT3VEN 0x58 -#define MAX8925_CHG_CNTL1 0x7c - -/* bits definition */ -#define MAX8925_CHG_STAT_VSYSLOW (1 << 0) -#define MAX8925_CHG_STAT_MODE_MASK (3 << 2) -#define MAX8925_CHG_STAT_EN_MASK (1 << 4) -#define MAX8925_CHG_MBDET (1 << 1) -#define MAX8925_CHG_AC_RANGE_MASK (3 << 6) - -/* registers in ADC */ -#define MAX8925_ADC_RES_CNFG1 0x06 -#define MAX8925_ADC_AVG_CNFG1 0x07 -#define MAX8925_ADC_ACQ_CNFG1 0x08 -#define MAX8925_ADC_ACQ_CNFG2 0x09 -/* 2 bytes registers in below. MSB is 1st, LSB is 2nd. */ -#define MAX8925_ADC_AUX2 0x62 -#define MAX8925_ADC_VCHG 0x64 -#define MAX8925_ADC_VBBATT 0x66 -#define MAX8925_ADC_VMBATT 0x68 -#define MAX8925_ADC_ISNS 0x6a -#define MAX8925_ADC_THM 0x6c -#define MAX8925_ADC_TDIE 0x6e -#define MAX8925_CMD_AUX2 0xc8 -#define MAX8925_CMD_VCHG 0xd0 -#define MAX8925_CMD_VBBATT 0xd8 -#define MAX8925_CMD_VMBATT 0xe0 -#define MAX8925_CMD_ISNS 0xe8 -#define MAX8925_CMD_THM 0xf0 -#define MAX8925_CMD_TDIE 0xf8 - -enum { - MEASURE_AUX2, - MEASURE_VCHG, - MEASURE_VBBATT, - MEASURE_VMBATT, - MEASURE_ISNS, - MEASURE_THM, - MEASURE_TDIE, - MEASURE_MAX, -}; - -struct max8925_power_info { - struct max8925_chip *chip; - struct i2c_client *gpm; - struct i2c_client *adc; - - struct power_supply *ac; - struct power_supply *usb; - struct power_supply *battery; - int irq_base; - unsigned ac_online:1; - unsigned usb_online:1; - unsigned bat_online:1; - unsigned chg_mode:2; - unsigned batt_detect:1; /* detecing MB by ID pin */ - unsigned topoff_threshold:2; - unsigned fast_charge:3; - unsigned no_temp_support:1; - unsigned no_insert_detect:1; - - int (*set_charger) (int); -}; - -static int __set_charger(struct max8925_power_info *info, int enable) -{ - struct max8925_chip *chip = info->chip; - if (enable) { - /* enable charger in platform */ - if (info->set_charger) - info->set_charger(1); - /* enable charger */ - max8925_set_bits(info->gpm, MAX8925_CHG_CNTL1, 1 << 7, 0); - } else { - /* disable charge */ - max8925_set_bits(info->gpm, MAX8925_CHG_CNTL1, 1 << 7, 1 << 7); - if (info->set_charger) - info->set_charger(0); - } - dev_dbg(chip->dev, "%s\n", (enable) ? "Enable charger" - : "Disable charger"); - return 0; -} - -static irqreturn_t max8925_charger_handler(int irq, void *data) -{ - struct max8925_power_info *info = (struct max8925_power_info *)data; - struct max8925_chip *chip = info->chip; - - switch (irq - chip->irq_base) { - case MAX8925_IRQ_VCHG_DC_R: - info->ac_online = 1; - __set_charger(info, 1); - dev_dbg(chip->dev, "Adapter inserted\n"); - break; - case MAX8925_IRQ_VCHG_DC_F: - info->ac_online = 0; - __set_charger(info, 0); - dev_dbg(chip->dev, "Adapter removed\n"); - break; - case MAX8925_IRQ_VCHG_THM_OK_F: - /* Battery is not ready yet */ - dev_dbg(chip->dev, "Battery temperature is out of range\n"); - case MAX8925_IRQ_VCHG_DC_OVP: - dev_dbg(chip->dev, "Error detection\n"); - __set_charger(info, 0); - break; - case MAX8925_IRQ_VCHG_THM_OK_R: - /* Battery is ready now */ - dev_dbg(chip->dev, "Battery temperature is in range\n"); - break; - case MAX8925_IRQ_VCHG_SYSLOW_R: - /* VSYS is low */ - dev_info(chip->dev, "Sys power is too low\n"); - break; - case MAX8925_IRQ_VCHG_SYSLOW_F: - dev_dbg(chip->dev, "Sys power is above low threshold\n"); - break; - case MAX8925_IRQ_VCHG_DONE: - __set_charger(info, 0); - dev_dbg(chip->dev, "Charging is done\n"); - break; - case MAX8925_IRQ_VCHG_TOPOFF: - dev_dbg(chip->dev, "Charging in top-off mode\n"); - break; - case MAX8925_IRQ_VCHG_TMR_FAULT: - __set_charger(info, 0); - dev_dbg(chip->dev, "Safe timer is expired\n"); - break; - case MAX8925_IRQ_VCHG_RST: - __set_charger(info, 0); - dev_dbg(chip->dev, "Charger is reset\n"); - break; - } - return IRQ_HANDLED; -} - -static int start_measure(struct max8925_power_info *info, int type) -{ - unsigned char buf[2] = {0, 0}; - int meas_cmd; - int meas_reg = 0, ret; - - switch (type) { - case MEASURE_VCHG: - meas_cmd = MAX8925_CMD_VCHG; - meas_reg = MAX8925_ADC_VCHG; - break; - case MEASURE_VBBATT: - meas_cmd = MAX8925_CMD_VBBATT; - meas_reg = MAX8925_ADC_VBBATT; - break; - case MEASURE_VMBATT: - meas_cmd = MAX8925_CMD_VMBATT; - meas_reg = MAX8925_ADC_VMBATT; - break; - case MEASURE_ISNS: - meas_cmd = MAX8925_CMD_ISNS; - meas_reg = MAX8925_ADC_ISNS; - break; - default: - return -EINVAL; - } - - max8925_reg_write(info->adc, meas_cmd, 0); - max8925_bulk_read(info->adc, meas_reg, 2, buf); - ret = ((buf[0]<<8) | buf[1]) >> 4; - - return ret; -} - -static int max8925_ac_get_prop(struct power_supply *psy, - enum power_supply_property psp, - union power_supply_propval *val) -{ - struct max8925_power_info *info = dev_get_drvdata(psy->dev.parent); - int ret = 0; - - switch (psp) { - case POWER_SUPPLY_PROP_ONLINE: - val->intval = info->ac_online; - break; - case POWER_SUPPLY_PROP_VOLTAGE_NOW: - if (info->ac_online) { - ret = start_measure(info, MEASURE_VCHG); - if (ret >= 0) { - val->intval = ret * 2000; /* unit is uV */ - goto out; - } - } - ret = -ENODATA; - break; - default: - ret = -ENODEV; - break; - } -out: - return ret; -} - -static enum power_supply_property max8925_ac_props[] = { - POWER_SUPPLY_PROP_ONLINE, - POWER_SUPPLY_PROP_VOLTAGE_NOW, -}; - -static int max8925_usb_get_prop(struct power_supply *psy, - enum power_supply_property psp, - union power_supply_propval *val) -{ - struct max8925_power_info *info = dev_get_drvdata(psy->dev.parent); - int ret = 0; - - switch (psp) { - case POWER_SUPPLY_PROP_ONLINE: - val->intval = info->usb_online; - break; - case POWER_SUPPLY_PROP_VOLTAGE_NOW: - if (info->usb_online) { - ret = start_measure(info, MEASURE_VCHG); - if (ret >= 0) { - val->intval = ret * 2000; /* unit is uV */ - goto out; - } - } - ret = -ENODATA; - break; - default: - ret = -ENODEV; - break; - } -out: - return ret; -} - -static enum power_supply_property max8925_usb_props[] = { - POWER_SUPPLY_PROP_ONLINE, - POWER_SUPPLY_PROP_VOLTAGE_NOW, -}; - -static int max8925_bat_get_prop(struct power_supply *psy, - enum power_supply_property psp, - union power_supply_propval *val) -{ - struct max8925_power_info *info = dev_get_drvdata(psy->dev.parent); - int ret = 0; - - switch (psp) { - case POWER_SUPPLY_PROP_ONLINE: - val->intval = info->bat_online; - break; - case POWER_SUPPLY_PROP_VOLTAGE_NOW: - if (info->bat_online) { - ret = start_measure(info, MEASURE_VMBATT); - if (ret >= 0) { - val->intval = ret * 2000; /* unit is uV */ - ret = 0; - break; - } - } - ret = -ENODATA; - break; - case POWER_SUPPLY_PROP_CURRENT_NOW: - if (info->bat_online) { - ret = start_measure(info, MEASURE_ISNS); - if (ret >= 0) { - /* assume r_sns is 0.02 */ - ret = ((ret * 6250) - 3125) /* uA */; - val->intval = 0; - if (ret > 0) - val->intval = ret; /* unit is mA */ - ret = 0; - break; - } - } - ret = -ENODATA; - break; - case POWER_SUPPLY_PROP_CHARGE_TYPE: - if (!info->bat_online) { - ret = -ENODATA; - break; - } - ret = max8925_reg_read(info->gpm, MAX8925_CHG_STATUS); - ret = (ret & MAX8925_CHG_STAT_MODE_MASK) >> 2; - switch (ret) { - case 1: - val->intval = POWER_SUPPLY_CHARGE_TYPE_FAST; - break; - case 0: - case 2: - val->intval = POWER_SUPPLY_CHARGE_TYPE_TRICKLE; - break; - case 3: - val->intval = POWER_SUPPLY_CHARGE_TYPE_NONE; - break; - } - ret = 0; - break; - case POWER_SUPPLY_PROP_STATUS: - if (!info->bat_online) { - ret = -ENODATA; - break; - } - ret = max8925_reg_read(info->gpm, MAX8925_CHG_STATUS); - if (info->usb_online || info->ac_online) { - val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; - if (ret & MAX8925_CHG_STAT_EN_MASK) - val->intval = POWER_SUPPLY_STATUS_CHARGING; - } else - val->intval = POWER_SUPPLY_STATUS_DISCHARGING; - ret = 0; - break; - default: - ret = -ENODEV; - break; - } - return ret; -} - -static enum power_supply_property max8925_battery_props[] = { - POWER_SUPPLY_PROP_ONLINE, - POWER_SUPPLY_PROP_VOLTAGE_NOW, - POWER_SUPPLY_PROP_CURRENT_NOW, - POWER_SUPPLY_PROP_CHARGE_TYPE, - POWER_SUPPLY_PROP_STATUS, -}; - -static const struct power_supply_desc ac_desc = { - .name = "max8925-ac", - .type = POWER_SUPPLY_TYPE_MAINS, - .properties = max8925_ac_props, - .num_properties = ARRAY_SIZE(max8925_ac_props), - .get_property = max8925_ac_get_prop, -}; - -static const struct power_supply_desc usb_desc = { - .name = "max8925-usb", - .type = POWER_SUPPLY_TYPE_USB, - .properties = max8925_usb_props, - .num_properties = ARRAY_SIZE(max8925_usb_props), - .get_property = max8925_usb_get_prop, -}; - -static const struct power_supply_desc battery_desc = { - .name = "max8925-battery", - .type = POWER_SUPPLY_TYPE_BATTERY, - .properties = max8925_battery_props, - .num_properties = ARRAY_SIZE(max8925_battery_props), - .get_property = max8925_bat_get_prop, -}; - -#define REQUEST_IRQ(_irq, _name) \ -do { \ - ret = request_threaded_irq(chip->irq_base + _irq, NULL, \ - max8925_charger_handler, \ - IRQF_ONESHOT, _name, info); \ - if (ret) \ - dev_err(chip->dev, "Failed to request IRQ #%d: %d\n", \ - _irq, ret); \ -} while (0) - -static int max8925_init_charger(struct max8925_chip *chip, - struct max8925_power_info *info) -{ - int ret; - - REQUEST_IRQ(MAX8925_IRQ_VCHG_DC_OVP, "ac-ovp"); - if (!info->no_insert_detect) { - REQUEST_IRQ(MAX8925_IRQ_VCHG_DC_F, "ac-remove"); - REQUEST_IRQ(MAX8925_IRQ_VCHG_DC_R, "ac-insert"); - } - if (!info->no_temp_support) { - REQUEST_IRQ(MAX8925_IRQ_VCHG_THM_OK_R, "batt-temp-in-range"); - REQUEST_IRQ(MAX8925_IRQ_VCHG_THM_OK_F, "batt-temp-out-range"); - } - REQUEST_IRQ(MAX8925_IRQ_VCHG_SYSLOW_F, "vsys-high"); - REQUEST_IRQ(MAX8925_IRQ_VCHG_SYSLOW_R, "vsys-low"); - REQUEST_IRQ(MAX8925_IRQ_VCHG_RST, "charger-reset"); - REQUEST_IRQ(MAX8925_IRQ_VCHG_DONE, "charger-done"); - REQUEST_IRQ(MAX8925_IRQ_VCHG_TOPOFF, "charger-topoff"); - REQUEST_IRQ(MAX8925_IRQ_VCHG_TMR_FAULT, "charger-timer-expire"); - - info->usb_online = 0; - info->bat_online = 0; - - /* check for power - can miss interrupt at boot time */ - if (start_measure(info, MEASURE_VCHG) * 2000 > 500000) - info->ac_online = 1; - else - info->ac_online = 0; - - ret = max8925_reg_read(info->gpm, MAX8925_CHG_STATUS); - if (ret >= 0) { - /* - * If battery detection is enabled, ID pin of battery is - * connected to MBDET pin of MAX8925. It could be used to - * detect battery presence. - * Otherwise, we have to assume that battery is always on. - */ - if (info->batt_detect) - info->bat_online = (ret & MAX8925_CHG_MBDET) ? 0 : 1; - else - info->bat_online = 1; - if (ret & MAX8925_CHG_AC_RANGE_MASK) - info->ac_online = 1; - else - info->ac_online = 0; - } - /* disable charge */ - max8925_set_bits(info->gpm, MAX8925_CHG_CNTL1, 1 << 7, 1 << 7); - /* set charging current in charge topoff mode */ - max8925_set_bits(info->gpm, MAX8925_CHG_CNTL1, 3 << 5, - info->topoff_threshold << 5); - /* set charing current in fast charge mode */ - max8925_set_bits(info->gpm, MAX8925_CHG_CNTL1, 7, info->fast_charge); - - return 0; -} - -static int max8925_deinit_charger(struct max8925_power_info *info) -{ - struct max8925_chip *chip = info->chip; - int irq; - - irq = chip->irq_base + MAX8925_IRQ_VCHG_DC_OVP; - for (; irq <= chip->irq_base + MAX8925_IRQ_VCHG_TMR_FAULT; irq++) - free_irq(irq, info); - - return 0; -} - -#ifdef CONFIG_OF -static struct max8925_power_pdata * -max8925_power_dt_init(struct platform_device *pdev) -{ - struct device_node *nproot = pdev->dev.parent->of_node; - struct device_node *np; - int batt_detect; - int topoff_threshold; - int fast_charge; - int no_temp_support; - int no_insert_detect; - struct max8925_power_pdata *pdata; - - if (!nproot) - return pdev->dev.platform_data; - - np = of_get_child_by_name(nproot, "charger"); - if (!np) { - dev_err(&pdev->dev, "failed to find charger node\n"); - return NULL; - } - - pdata = devm_kzalloc(&pdev->dev, - sizeof(struct max8925_power_pdata), - GFP_KERNEL); - if (!pdata) - goto ret; - - of_property_read_u32(np, "topoff-threshold", &topoff_threshold); - of_property_read_u32(np, "batt-detect", &batt_detect); - of_property_read_u32(np, "fast-charge", &fast_charge); - of_property_read_u32(np, "no-insert-detect", &no_insert_detect); - of_property_read_u32(np, "no-temp-support", &no_temp_support); - - pdata->batt_detect = batt_detect; - pdata->fast_charge = fast_charge; - pdata->topoff_threshold = topoff_threshold; - pdata->no_insert_detect = no_insert_detect; - pdata->no_temp_support = no_temp_support; - -ret: - of_node_put(np); - return pdata; -} -#else -static struct max8925_power_pdata * -max8925_power_dt_init(struct platform_device *pdev) -{ - return pdev->dev.platform_data; -} -#endif - -static int max8925_power_probe(struct platform_device *pdev) -{ - struct max8925_chip *chip = dev_get_drvdata(pdev->dev.parent); - struct power_supply_config psy_cfg = {}; /* Only for ac and usb */ - struct max8925_power_pdata *pdata = NULL; - struct max8925_power_info *info; - int ret; - - pdata = max8925_power_dt_init(pdev); - if (!pdata) { - dev_err(&pdev->dev, "platform data isn't assigned to " - "power supply\n"); - return -EINVAL; - } - - info = devm_kzalloc(&pdev->dev, sizeof(struct max8925_power_info), - GFP_KERNEL); - if (!info) - return -ENOMEM; - info->chip = chip; - info->gpm = chip->i2c; - info->adc = chip->adc; - platform_set_drvdata(pdev, info); - - psy_cfg.supplied_to = pdata->supplied_to; - psy_cfg.num_supplicants = pdata->num_supplicants; - - info->ac = power_supply_register(&pdev->dev, &ac_desc, &psy_cfg); - if (IS_ERR(info->ac)) { - ret = PTR_ERR(info->ac); - goto out; - } - info->ac->dev.parent = &pdev->dev; - - info->usb = power_supply_register(&pdev->dev, &usb_desc, &psy_cfg); - if (IS_ERR(info->usb)) { - ret = PTR_ERR(info->usb); - goto out_unregister_ac; - } - info->usb->dev.parent = &pdev->dev; - - info->battery = power_supply_register(&pdev->dev, &battery_desc, NULL); - if (IS_ERR(info->battery)) { - ret = PTR_ERR(info->battery); - goto out_unregister_usb; - } - info->battery->dev.parent = &pdev->dev; - - info->batt_detect = pdata->batt_detect; - info->topoff_threshold = pdata->topoff_threshold; - info->fast_charge = pdata->fast_charge; - info->set_charger = pdata->set_charger; - info->no_temp_support = pdata->no_temp_support; - info->no_insert_detect = pdata->no_insert_detect; - - max8925_init_charger(chip, info); - return 0; -out_unregister_usb: - power_supply_unregister(info->usb); -out_unregister_ac: - power_supply_unregister(info->ac); -out: - return ret; -} - -static int max8925_power_remove(struct platform_device *pdev) -{ - struct max8925_power_info *info = platform_get_drvdata(pdev); - - if (info) { - power_supply_unregister(info->ac); - power_supply_unregister(info->usb); - power_supply_unregister(info->battery); - max8925_deinit_charger(info); - } - return 0; -} - -static struct platform_driver max8925_power_driver = { - .probe = max8925_power_probe, - .remove = max8925_power_remove, - .driver = { - .name = "max8925-power", - }, -}; - -module_platform_driver(max8925_power_driver); - -MODULE_LICENSE("GPL"); -MODULE_DESCRIPTION("Power supply driver for MAX8925"); -MODULE_ALIAS("platform:max8925-power"); diff --git a/drivers/power/max8997_charger.c b/drivers/power/max8997_charger.c deleted file mode 100644 index 0b2eab571528..000000000000 --- a/drivers/power/max8997_charger.c +++ /dev/null @@ -1,211 +0,0 @@ -/* - * max8997_charger.c - Power supply consumer driver for the Maxim 8997/8966 - * - * Copyright (C) 2011 Samsung Electronics - * MyungJoo Ham - * - * 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 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 - */ - -#include -#include -#include -#include -#include -#include -#include - -struct charger_data { - struct device *dev; - struct max8997_dev *iodev; - struct power_supply *battery; -}; - -static enum power_supply_property max8997_battery_props[] = { - POWER_SUPPLY_PROP_STATUS, /* "FULL" or "NOT FULL" only. */ - POWER_SUPPLY_PROP_PRESENT, /* the presence of battery */ - POWER_SUPPLY_PROP_ONLINE, /* charger is active or not */ -}; - -/* Note that the charger control is done by a current regulator "CHARGER" */ -static int max8997_battery_get_property(struct power_supply *psy, - enum power_supply_property psp, - union power_supply_propval *val) -{ - struct charger_data *charger = power_supply_get_drvdata(psy); - struct i2c_client *i2c = charger->iodev->i2c; - int ret; - u8 reg; - - switch (psp) { - case POWER_SUPPLY_PROP_STATUS: - val->intval = 0; - ret = max8997_read_reg(i2c, MAX8997_REG_STATUS4, ®); - if (ret) - return ret; - if ((reg & (1 << 0)) == 0x1) - val->intval = POWER_SUPPLY_STATUS_FULL; - - break; - case POWER_SUPPLY_PROP_PRESENT: - val->intval = 0; - ret = max8997_read_reg(i2c, MAX8997_REG_STATUS4, ®); - if (ret) - return ret; - if ((reg & (1 << 2)) == 0x0) - val->intval = 1; - - break; - case POWER_SUPPLY_PROP_ONLINE: - val->intval = 0; - ret = max8997_read_reg(i2c, MAX8997_REG_STATUS4, ®); - if (ret) - return ret; - /* DCINOK */ - if (reg & (1 << 1)) - val->intval = 1; - - break; - default: - return -EINVAL; - } - - return 0; -} - -static const struct power_supply_desc max8997_battery_desc = { - .name = "max8997_pmic", - .type = POWER_SUPPLY_TYPE_BATTERY, - .get_property = max8997_battery_get_property, - .properties = max8997_battery_props, - .num_properties = ARRAY_SIZE(max8997_battery_props), -}; - -static int max8997_battery_probe(struct platform_device *pdev) -{ - int ret = 0; - struct charger_data *charger; - struct max8997_dev *iodev = dev_get_drvdata(pdev->dev.parent); - struct max8997_platform_data *pdata = dev_get_platdata(iodev->dev); - struct power_supply_config psy_cfg = {}; - - if (!pdata) - return -EINVAL; - - if (pdata->eoc_mA) { - int val = (pdata->eoc_mA - 50) / 10; - if (val < 0) - val = 0; - if (val > 0xf) - val = 0xf; - - ret = max8997_update_reg(iodev->i2c, - MAX8997_REG_MBCCTRL5, val, 0xf); - if (ret < 0) { - dev_err(&pdev->dev, "Cannot use i2c bus.\n"); - return ret; - } - } - - switch (pdata->timeout) { - case 5: - ret = max8997_update_reg(iodev->i2c, MAX8997_REG_MBCCTRL1, - 0x2 << 4, 0x7 << 4); - break; - case 6: - ret = max8997_update_reg(iodev->i2c, MAX8997_REG_MBCCTRL1, - 0x3 << 4, 0x7 << 4); - break; - case 7: - ret = max8997_update_reg(iodev->i2c, MAX8997_REG_MBCCTRL1, - 0x4 << 4, 0x7 << 4); - break; - case 0: - ret = max8997_update_reg(iodev->i2c, MAX8997_REG_MBCCTRL1, - 0x7 << 4, 0x7 << 4); - break; - default: - dev_err(&pdev->dev, "incorrect timeout value (%d)\n", - pdata->timeout); - return -EINVAL; - } - if (ret < 0) { - dev_err(&pdev->dev, "Cannot use i2c bus.\n"); - return ret; - } - - charger = devm_kzalloc(&pdev->dev, sizeof(struct charger_data), - GFP_KERNEL); - if (charger == NULL) { - dev_err(&pdev->dev, "Cannot allocate memory.\n"); - return -ENOMEM; - } - - platform_set_drvdata(pdev, charger); - - - charger->dev = &pdev->dev; - charger->iodev = iodev; - - psy_cfg.drv_data = charger; - - charger->battery = power_supply_register(&pdev->dev, - &max8997_battery_desc, - &psy_cfg); - if (IS_ERR(charger->battery)) { - dev_err(&pdev->dev, "failed: power supply register\n"); - return PTR_ERR(charger->battery); - } - - 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 }, - { } -}; - -static struct platform_driver max8997_battery_driver = { - .driver = { - .name = "max8997-battery", - }, - .probe = max8997_battery_probe, - .remove = max8997_battery_remove, - .id_table = max8997_battery_id, -}; - -static int __init max8997_battery_init(void) -{ - return platform_driver_register(&max8997_battery_driver); -} -subsys_initcall(max8997_battery_init); - -static void __exit max8997_battery_cleanup(void) -{ - platform_driver_unregister(&max8997_battery_driver); -} -module_exit(max8997_battery_cleanup); - -MODULE_DESCRIPTION("MAXIM 8997/8966 battery control driver"); -MODULE_AUTHOR("MyungJoo Ham "); -MODULE_LICENSE("GPL"); diff --git a/drivers/power/max8998_charger.c b/drivers/power/max8998_charger.c deleted file mode 100644 index b64cf0f14142..000000000000 --- a/drivers/power/max8998_charger.c +++ /dev/null @@ -1,202 +0,0 @@ -/* - * max8998_charger.c - Power supply consumer driver for the Maxim 8998/LP3974 - * - * Copyright (C) 2009-2010 Samsung Electronics - * MyungJoo Ham - * - * 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 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 - */ - -#include -#include -#include -#include -#include -#include -#include - -struct max8998_battery_data { - struct device *dev; - struct max8998_dev *iodev; - struct power_supply *battery; -}; - -static enum power_supply_property max8998_battery_props[] = { - POWER_SUPPLY_PROP_PRESENT, /* the presence of battery */ - POWER_SUPPLY_PROP_ONLINE, /* charger is active or not */ -}; - -/* Note that the charger control is done by a current regulator "CHARGER" */ -static int max8998_battery_get_property(struct power_supply *psy, - enum power_supply_property psp, - union power_supply_propval *val) -{ - struct max8998_battery_data *max8998 = power_supply_get_drvdata(psy); - struct i2c_client *i2c = max8998->iodev->i2c; - int ret; - u8 reg; - - switch (psp) { - case POWER_SUPPLY_PROP_PRESENT: - ret = max8998_read_reg(i2c, MAX8998_REG_STATUS2, ®); - if (ret) - return ret; - if (reg & (1 << 4)) - val->intval = 0; - else - val->intval = 1; - break; - case POWER_SUPPLY_PROP_ONLINE: - ret = max8998_read_reg(i2c, MAX8998_REG_STATUS2, ®); - if (ret) - return ret; - if (reg & (1 << 3)) - val->intval = 0; - else - val->intval = 1; - break; - default: - return -EINVAL; - } - - return 0; -} - -static const struct power_supply_desc max8998_battery_desc = { - .name = "max8998_pmic", - .type = POWER_SUPPLY_TYPE_BATTERY, - .get_property = max8998_battery_get_property, - .properties = max8998_battery_props, - .num_properties = ARRAY_SIZE(max8998_battery_props), -}; - -static int max8998_battery_probe(struct platform_device *pdev) -{ - struct max8998_dev *iodev = dev_get_drvdata(pdev->dev.parent); - struct max8998_platform_data *pdata = dev_get_platdata(iodev->dev); - struct power_supply_config psy_cfg = {}; - struct max8998_battery_data *max8998; - struct i2c_client *i2c; - int ret = 0; - - if (!pdata) { - dev_err(pdev->dev.parent, "No platform init data supplied\n"); - return -ENODEV; - } - - max8998 = devm_kzalloc(&pdev->dev, sizeof(struct max8998_battery_data), - GFP_KERNEL); - if (!max8998) - return -ENOMEM; - - max8998->dev = &pdev->dev; - max8998->iodev = iodev; - platform_set_drvdata(pdev, max8998); - i2c = max8998->iodev->i2c; - - /* Setup "End of Charge" */ - /* If EOC value equals 0, - * remain value set from bootloader or default value */ - if (pdata->eoc >= 10 && pdata->eoc <= 45) { - max8998_update_reg(i2c, MAX8998_REG_CHGR1, - (pdata->eoc / 5 - 2) << 5, 0x7 << 5); - } else if (pdata->eoc == 0) { - dev_dbg(max8998->dev, - "EOC value not set: leave it unchanged.\n"); - } else { - dev_err(max8998->dev, "Invalid EOC value\n"); - return -EINVAL; - } - - /* Setup Charge Restart Level */ - switch (pdata->restart) { - case 100: - max8998_update_reg(i2c, MAX8998_REG_CHGR1, 0x1 << 3, 0x3 << 3); - break; - case 150: - max8998_update_reg(i2c, MAX8998_REG_CHGR1, 0x0 << 3, 0x3 << 3); - break; - case 200: - max8998_update_reg(i2c, MAX8998_REG_CHGR1, 0x2 << 3, 0x3 << 3); - break; - case -1: - max8998_update_reg(i2c, MAX8998_REG_CHGR1, 0x3 << 3, 0x3 << 3); - break; - case 0: - dev_dbg(max8998->dev, - "Restart Level not set: leave it unchanged.\n"); - break; - default: - dev_err(max8998->dev, "Invalid Restart Level\n"); - return -EINVAL; - } - - /* Setup Charge Full Timeout */ - switch (pdata->timeout) { - case 5: - max8998_update_reg(i2c, MAX8998_REG_CHGR2, 0x0 << 4, 0x3 << 4); - break; - case 6: - max8998_update_reg(i2c, MAX8998_REG_CHGR2, 0x1 << 4, 0x3 << 4); - break; - case 7: - max8998_update_reg(i2c, MAX8998_REG_CHGR2, 0x2 << 4, 0x3 << 4); - break; - case -1: - max8998_update_reg(i2c, MAX8998_REG_CHGR2, 0x3 << 4, 0x3 << 4); - break; - case 0: - dev_dbg(max8998->dev, - "Full Timeout not set: leave it unchanged.\n"); - break; - default: - dev_err(max8998->dev, "Invalid Full Timeout value\n"); - return -EINVAL; - } - - psy_cfg.drv_data = max8998; - - 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); - return ret; - } - - return 0; -} - -static const struct platform_device_id max8998_battery_id[] = { - { "max8998-battery", TYPE_MAX8998 }, - { } -}; - -static struct platform_driver max8998_battery_driver = { - .driver = { - .name = "max8998-battery", - }, - .probe = max8998_battery_probe, - .id_table = max8998_battery_id, -}; - -module_platform_driver(max8998_battery_driver); - -MODULE_DESCRIPTION("MAXIM 8998 battery control driver"); -MODULE_AUTHOR("MyungJoo Ham "); -MODULE_LICENSE("GPL"); -MODULE_ALIAS("platform:max8998-battery"); diff --git a/drivers/power/olpc_battery.c b/drivers/power/olpc_battery.c deleted file mode 100644 index 9e29b1321648..000000000000 --- a/drivers/power/olpc_battery.c +++ /dev/null @@ -1,692 +0,0 @@ -/* - * Battery driver for One Laptop Per Child board. - * - * Copyright © 2006-2010 David Woodhouse - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - - -#define EC_BAT_VOLTAGE 0x10 /* uint16_t, *9.76/32, mV */ -#define EC_BAT_CURRENT 0x11 /* int16_t, *15.625/120, mA */ -#define EC_BAT_ACR 0x12 /* int16_t, *6250/15, µAh */ -#define EC_BAT_TEMP 0x13 /* uint16_t, *100/256, °C */ -#define EC_AMB_TEMP 0x14 /* uint16_t, *100/256, °C */ -#define EC_BAT_STATUS 0x15 /* uint8_t, bitmask */ -#define EC_BAT_SOC 0x16 /* uint8_t, percentage */ -#define EC_BAT_SERIAL 0x17 /* uint8_t[6] */ -#define EC_BAT_EEPROM 0x18 /* uint8_t adr as input, uint8_t output */ -#define EC_BAT_ERRCODE 0x1f /* uint8_t, bitmask */ - -#define BAT_STAT_PRESENT 0x01 -#define BAT_STAT_FULL 0x02 -#define BAT_STAT_LOW 0x04 -#define BAT_STAT_DESTROY 0x08 -#define BAT_STAT_AC 0x10 -#define BAT_STAT_CHARGING 0x20 -#define BAT_STAT_DISCHARGING 0x40 -#define BAT_STAT_TRICKLE 0x80 - -#define BAT_ERR_INFOFAIL 0x02 -#define BAT_ERR_OVERVOLTAGE 0x04 -#define BAT_ERR_OVERTEMP 0x05 -#define BAT_ERR_GAUGESTOP 0x06 -#define BAT_ERR_OUT_OF_CONTROL 0x07 -#define BAT_ERR_ID_FAIL 0x09 -#define BAT_ERR_ACR_FAIL 0x10 - -#define BAT_ADDR_MFR_TYPE 0x5F - -/********************************************************************* - * Power - *********************************************************************/ - -static int olpc_ac_get_prop(struct power_supply *psy, - enum power_supply_property psp, - union power_supply_propval *val) -{ - int ret = 0; - uint8_t status; - - switch (psp) { - case POWER_SUPPLY_PROP_ONLINE: - ret = olpc_ec_cmd(EC_BAT_STATUS, NULL, 0, &status, 1); - if (ret) - return ret; - - val->intval = !!(status & BAT_STAT_AC); - break; - default: - ret = -EINVAL; - break; - } - return ret; -} - -static enum power_supply_property olpc_ac_props[] = { - POWER_SUPPLY_PROP_ONLINE, -}; - -static const struct power_supply_desc olpc_ac_desc = { - .name = "olpc-ac", - .type = POWER_SUPPLY_TYPE_MAINS, - .properties = olpc_ac_props, - .num_properties = ARRAY_SIZE(olpc_ac_props), - .get_property = olpc_ac_get_prop, -}; - -static struct power_supply *olpc_ac; - -static char bat_serial[17]; /* Ick */ - -static int olpc_bat_get_status(union power_supply_propval *val, uint8_t ec_byte) -{ - if (olpc_platform_info.ecver > 0x44) { - if (ec_byte & (BAT_STAT_CHARGING | BAT_STAT_TRICKLE)) - val->intval = POWER_SUPPLY_STATUS_CHARGING; - else if (ec_byte & BAT_STAT_DISCHARGING) - val->intval = POWER_SUPPLY_STATUS_DISCHARGING; - else if (ec_byte & BAT_STAT_FULL) - val->intval = POWER_SUPPLY_STATUS_FULL; - else /* er,... */ - val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; - } else { - /* Older EC didn't report charge/discharge bits */ - if (!(ec_byte & BAT_STAT_AC)) /* No AC means discharging */ - val->intval = POWER_SUPPLY_STATUS_DISCHARGING; - else if (ec_byte & BAT_STAT_FULL) - val->intval = POWER_SUPPLY_STATUS_FULL; - else /* Not _necessarily_ true but EC doesn't tell all yet */ - val->intval = POWER_SUPPLY_STATUS_CHARGING; - } - - return 0; -} - -static int olpc_bat_get_health(union power_supply_propval *val) -{ - uint8_t ec_byte; - int ret; - - ret = olpc_ec_cmd(EC_BAT_ERRCODE, NULL, 0, &ec_byte, 1); - if (ret) - return ret; - - switch (ec_byte) { - case 0: - val->intval = POWER_SUPPLY_HEALTH_GOOD; - break; - - case BAT_ERR_OVERTEMP: - val->intval = POWER_SUPPLY_HEALTH_OVERHEAT; - break; - - case BAT_ERR_OVERVOLTAGE: - val->intval = POWER_SUPPLY_HEALTH_OVERVOLTAGE; - break; - - case BAT_ERR_INFOFAIL: - case BAT_ERR_OUT_OF_CONTROL: - case BAT_ERR_ID_FAIL: - case BAT_ERR_ACR_FAIL: - val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; - break; - - default: - /* Eep. We don't know this failure code */ - ret = -EIO; - } - - return ret; -} - -static int olpc_bat_get_mfr(union power_supply_propval *val) -{ - uint8_t ec_byte; - int ret; - - ec_byte = BAT_ADDR_MFR_TYPE; - ret = olpc_ec_cmd(EC_BAT_EEPROM, &ec_byte, 1, &ec_byte, 1); - if (ret) - return ret; - - switch (ec_byte >> 4) { - case 1: - val->strval = "Gold Peak"; - break; - case 2: - val->strval = "BYD"; - break; - default: - val->strval = "Unknown"; - break; - } - - return ret; -} - -static int olpc_bat_get_tech(union power_supply_propval *val) -{ - uint8_t ec_byte; - int ret; - - ec_byte = BAT_ADDR_MFR_TYPE; - ret = olpc_ec_cmd(EC_BAT_EEPROM, &ec_byte, 1, &ec_byte, 1); - if (ret) - return ret; - - switch (ec_byte & 0xf) { - case 1: - val->intval = POWER_SUPPLY_TECHNOLOGY_NiMH; - break; - case 2: - val->intval = POWER_SUPPLY_TECHNOLOGY_LiFe; - break; - default: - val->intval = POWER_SUPPLY_TECHNOLOGY_UNKNOWN; - break; - } - - return ret; -} - -static int olpc_bat_get_charge_full_design(union power_supply_propval *val) -{ - uint8_t ec_byte; - union power_supply_propval tech; - int ret, mfr; - - ret = olpc_bat_get_tech(&tech); - if (ret) - return ret; - - ec_byte = BAT_ADDR_MFR_TYPE; - ret = olpc_ec_cmd(EC_BAT_EEPROM, &ec_byte, 1, &ec_byte, 1); - if (ret) - return ret; - - mfr = ec_byte >> 4; - - switch (tech.intval) { - case POWER_SUPPLY_TECHNOLOGY_NiMH: - switch (mfr) { - case 1: /* Gold Peak */ - val->intval = 3000000*.8; - break; - default: - return -EIO; - } - break; - - case POWER_SUPPLY_TECHNOLOGY_LiFe: - switch (mfr) { - case 1: /* Gold Peak, fall through */ - case 2: /* BYD */ - val->intval = 2800000; - break; - default: - return -EIO; - } - break; - - default: - return -EIO; - } - - return ret; -} - -static int olpc_bat_get_charge_now(union power_supply_propval *val) -{ - uint8_t soc; - union power_supply_propval full; - int ret; - - ret = olpc_ec_cmd(EC_BAT_SOC, NULL, 0, &soc, 1); - if (ret) - return ret; - - ret = olpc_bat_get_charge_full_design(&full); - if (ret) - return ret; - - val->intval = soc * (full.intval / 100); - return 0; -} - -static int olpc_bat_get_voltage_max_design(union power_supply_propval *val) -{ - uint8_t ec_byte; - union power_supply_propval tech; - int mfr; - int ret; - - ret = olpc_bat_get_tech(&tech); - if (ret) - return ret; - - ec_byte = BAT_ADDR_MFR_TYPE; - ret = olpc_ec_cmd(EC_BAT_EEPROM, &ec_byte, 1, &ec_byte, 1); - if (ret) - return ret; - - mfr = ec_byte >> 4; - - switch (tech.intval) { - case POWER_SUPPLY_TECHNOLOGY_NiMH: - switch (mfr) { - case 1: /* Gold Peak */ - val->intval = 6000000; - break; - default: - return -EIO; - } - break; - - case POWER_SUPPLY_TECHNOLOGY_LiFe: - switch (mfr) { - case 1: /* Gold Peak */ - val->intval = 6400000; - break; - case 2: /* BYD */ - val->intval = 6500000; - break; - default: - return -EIO; - } - break; - - default: - return -EIO; - } - - return ret; -} - -/********************************************************************* - * Battery properties - *********************************************************************/ -static int olpc_bat_get_property(struct power_supply *psy, - enum power_supply_property psp, - union power_supply_propval *val) -{ - int ret = 0; - __be16 ec_word; - uint8_t ec_byte; - __be64 ser_buf; - - ret = olpc_ec_cmd(EC_BAT_STATUS, NULL, 0, &ec_byte, 1); - if (ret) - return ret; - - /* Theoretically there's a race here -- the battery could be - removed immediately after we check whether it's present, and - then we query for some other property of the now-absent battery. - It doesn't matter though -- the EC will return the last-known - information, and it's as if we just ran that _little_ bit faster - and managed to read it out before the battery went away. */ - if (!(ec_byte & (BAT_STAT_PRESENT | BAT_STAT_TRICKLE)) && - psp != POWER_SUPPLY_PROP_PRESENT) - return -ENODEV; - - switch (psp) { - case POWER_SUPPLY_PROP_STATUS: - ret = olpc_bat_get_status(val, ec_byte); - if (ret) - return ret; - break; - case POWER_SUPPLY_PROP_CHARGE_TYPE: - if (ec_byte & BAT_STAT_TRICKLE) - val->intval = POWER_SUPPLY_CHARGE_TYPE_TRICKLE; - else if (ec_byte & BAT_STAT_CHARGING) - val->intval = POWER_SUPPLY_CHARGE_TYPE_FAST; - else - val->intval = POWER_SUPPLY_CHARGE_TYPE_NONE; - break; - case POWER_SUPPLY_PROP_PRESENT: - val->intval = !!(ec_byte & (BAT_STAT_PRESENT | - BAT_STAT_TRICKLE)); - break; - - case POWER_SUPPLY_PROP_HEALTH: - if (ec_byte & BAT_STAT_DESTROY) - val->intval = POWER_SUPPLY_HEALTH_DEAD; - else { - ret = olpc_bat_get_health(val); - if (ret) - return ret; - } - break; - - case POWER_SUPPLY_PROP_MANUFACTURER: - ret = olpc_bat_get_mfr(val); - if (ret) - return ret; - break; - case POWER_SUPPLY_PROP_TECHNOLOGY: - ret = olpc_bat_get_tech(val); - if (ret) - return ret; - break; - case POWER_SUPPLY_PROP_VOLTAGE_AVG: - case POWER_SUPPLY_PROP_VOLTAGE_NOW: - ret = olpc_ec_cmd(EC_BAT_VOLTAGE, NULL, 0, (void *)&ec_word, 2); - if (ret) - return ret; - - val->intval = (s16)be16_to_cpu(ec_word) * 9760L / 32; - break; - case POWER_SUPPLY_PROP_CURRENT_AVG: - case POWER_SUPPLY_PROP_CURRENT_NOW: - ret = olpc_ec_cmd(EC_BAT_CURRENT, NULL, 0, (void *)&ec_word, 2); - if (ret) - return ret; - - val->intval = (s16)be16_to_cpu(ec_word) * 15625L / 120; - break; - case POWER_SUPPLY_PROP_CAPACITY: - ret = olpc_ec_cmd(EC_BAT_SOC, NULL, 0, &ec_byte, 1); - if (ret) - return ret; - val->intval = ec_byte; - break; - case POWER_SUPPLY_PROP_CAPACITY_LEVEL: - if (ec_byte & BAT_STAT_FULL) - val->intval = POWER_SUPPLY_CAPACITY_LEVEL_FULL; - else if (ec_byte & BAT_STAT_LOW) - val->intval = POWER_SUPPLY_CAPACITY_LEVEL_LOW; - else - val->intval = POWER_SUPPLY_CAPACITY_LEVEL_NORMAL; - break; - case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: - ret = olpc_bat_get_charge_full_design(val); - if (ret) - return ret; - break; - case POWER_SUPPLY_PROP_CHARGE_NOW: - ret = olpc_bat_get_charge_now(val); - if (ret) - return ret; - break; - case POWER_SUPPLY_PROP_TEMP: - ret = olpc_ec_cmd(EC_BAT_TEMP, NULL, 0, (void *)&ec_word, 2); - if (ret) - return ret; - - val->intval = (s16)be16_to_cpu(ec_word) * 100 / 256; - break; - case POWER_SUPPLY_PROP_TEMP_AMBIENT: - ret = olpc_ec_cmd(EC_AMB_TEMP, NULL, 0, (void *)&ec_word, 2); - if (ret) - return ret; - - val->intval = (int)be16_to_cpu(ec_word) * 100 / 256; - break; - case POWER_SUPPLY_PROP_CHARGE_COUNTER: - ret = olpc_ec_cmd(EC_BAT_ACR, NULL, 0, (void *)&ec_word, 2); - if (ret) - return ret; - - val->intval = (s16)be16_to_cpu(ec_word) * 6250 / 15; - break; - case POWER_SUPPLY_PROP_SERIAL_NUMBER: - ret = olpc_ec_cmd(EC_BAT_SERIAL, NULL, 0, (void *)&ser_buf, 8); - if (ret) - return ret; - - sprintf(bat_serial, "%016llx", (long long)be64_to_cpu(ser_buf)); - val->strval = bat_serial; - break; - case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: - ret = olpc_bat_get_voltage_max_design(val); - if (ret) - return ret; - break; - default: - ret = -EINVAL; - break; - } - - return ret; -} - -static enum power_supply_property olpc_xo1_bat_props[] = { - POWER_SUPPLY_PROP_STATUS, - POWER_SUPPLY_PROP_CHARGE_TYPE, - POWER_SUPPLY_PROP_PRESENT, - POWER_SUPPLY_PROP_HEALTH, - POWER_SUPPLY_PROP_TECHNOLOGY, - POWER_SUPPLY_PROP_VOLTAGE_AVG, - POWER_SUPPLY_PROP_VOLTAGE_NOW, - POWER_SUPPLY_PROP_CURRENT_AVG, - POWER_SUPPLY_PROP_CURRENT_NOW, - POWER_SUPPLY_PROP_CAPACITY, - POWER_SUPPLY_PROP_CAPACITY_LEVEL, - POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, - POWER_SUPPLY_PROP_CHARGE_NOW, - POWER_SUPPLY_PROP_TEMP, - POWER_SUPPLY_PROP_TEMP_AMBIENT, - POWER_SUPPLY_PROP_MANUFACTURER, - POWER_SUPPLY_PROP_SERIAL_NUMBER, - POWER_SUPPLY_PROP_CHARGE_COUNTER, - POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, -}; - -/* XO-1.5 does not have ambient temperature property */ -static enum power_supply_property olpc_xo15_bat_props[] = { - POWER_SUPPLY_PROP_STATUS, - POWER_SUPPLY_PROP_CHARGE_TYPE, - POWER_SUPPLY_PROP_PRESENT, - POWER_SUPPLY_PROP_HEALTH, - POWER_SUPPLY_PROP_TECHNOLOGY, - POWER_SUPPLY_PROP_VOLTAGE_AVG, - POWER_SUPPLY_PROP_VOLTAGE_NOW, - POWER_SUPPLY_PROP_CURRENT_AVG, - POWER_SUPPLY_PROP_CURRENT_NOW, - POWER_SUPPLY_PROP_CAPACITY, - POWER_SUPPLY_PROP_CAPACITY_LEVEL, - POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, - POWER_SUPPLY_PROP_CHARGE_NOW, - POWER_SUPPLY_PROP_TEMP, - POWER_SUPPLY_PROP_MANUFACTURER, - POWER_SUPPLY_PROP_SERIAL_NUMBER, - POWER_SUPPLY_PROP_CHARGE_COUNTER, - POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, -}; - -/* EEPROM reading goes completely around the power_supply API, sadly */ - -#define EEPROM_START 0x20 -#define EEPROM_END 0x80 -#define EEPROM_SIZE (EEPROM_END - EEPROM_START) - -static ssize_t olpc_bat_eeprom_read(struct file *filp, struct kobject *kobj, - struct bin_attribute *attr, char *buf, loff_t off, size_t count) -{ - uint8_t ec_byte; - int ret; - int i; - - for (i = 0; i < count; i++) { - ec_byte = EEPROM_START + off + i; - ret = olpc_ec_cmd(EC_BAT_EEPROM, &ec_byte, 1, &buf[i], 1); - if (ret) { - pr_err("olpc-battery: " - "EC_BAT_EEPROM cmd @ 0x%x failed - %d!\n", - ec_byte, ret); - return -EIO; - } - } - - return count; -} - -static struct bin_attribute olpc_bat_eeprom = { - .attr = { - .name = "eeprom", - .mode = S_IRUGO, - }, - .size = EEPROM_SIZE, - .read = olpc_bat_eeprom_read, -}; - -/* Allow userspace to see the specific error value pulled from the EC */ - -static ssize_t olpc_bat_error_read(struct device *dev, - struct device_attribute *attr, char *buf) -{ - uint8_t ec_byte; - ssize_t ret; - - ret = olpc_ec_cmd(EC_BAT_ERRCODE, NULL, 0, &ec_byte, 1); - if (ret < 0) - return ret; - - return sprintf(buf, "%d\n", ec_byte); -} - -static struct device_attribute olpc_bat_error = { - .attr = { - .name = "error", - .mode = S_IRUGO, - }, - .show = olpc_bat_error_read, -}; - -/********************************************************************* - * Initialisation - *********************************************************************/ - -static struct power_supply_desc olpc_bat_desc = { - .name = "olpc-battery", - .get_property = olpc_bat_get_property, - .use_for_apm = 1, -}; - -static struct power_supply *olpc_bat; - -static int olpc_battery_suspend(struct platform_device *pdev, - pm_message_t state) -{ - if (device_may_wakeup(&olpc_ac->dev)) - olpc_ec_wakeup_set(EC_SCI_SRC_ACPWR); - else - olpc_ec_wakeup_clear(EC_SCI_SRC_ACPWR); - - if (device_may_wakeup(&olpc_bat->dev)) - olpc_ec_wakeup_set(EC_SCI_SRC_BATTERY | EC_SCI_SRC_BATSOC - | EC_SCI_SRC_BATERR); - else - olpc_ec_wakeup_clear(EC_SCI_SRC_BATTERY | EC_SCI_SRC_BATSOC - | EC_SCI_SRC_BATERR); - - return 0; -} - -static int olpc_battery_probe(struct platform_device *pdev) -{ - int ret; - uint8_t status; - - /* - * We've seen a number of EC protocol changes; this driver requires - * the latest EC protocol, supported by 0x44 and above. - */ - if (olpc_platform_info.ecver < 0x44) { - printk(KERN_NOTICE "OLPC EC version 0x%02x too old for " - "battery driver.\n", olpc_platform_info.ecver); - return -ENXIO; - } - - ret = olpc_ec_cmd(EC_BAT_STATUS, NULL, 0, &status, 1); - if (ret) - return ret; - - /* Ignore the status. It doesn't actually matter */ - - olpc_ac = power_supply_register(&pdev->dev, &olpc_ac_desc, NULL); - if (IS_ERR(olpc_ac)) - return PTR_ERR(olpc_ac); - - if (olpc_board_at_least(olpc_board_pre(0xd0))) { /* XO-1.5 */ - olpc_bat_desc.properties = olpc_xo15_bat_props; - olpc_bat_desc.num_properties = ARRAY_SIZE(olpc_xo15_bat_props); - } else { /* XO-1 */ - olpc_bat_desc.properties = olpc_xo1_bat_props; - olpc_bat_desc.num_properties = ARRAY_SIZE(olpc_xo1_bat_props); - } - - olpc_bat = power_supply_register(&pdev->dev, &olpc_bat_desc, NULL); - if (IS_ERR(olpc_bat)) { - ret = PTR_ERR(olpc_bat); - goto battery_failed; - } - - ret = device_create_bin_file(&olpc_bat->dev, &olpc_bat_eeprom); - if (ret) - goto eeprom_failed; - - ret = device_create_file(&olpc_bat->dev, &olpc_bat_error); - if (ret) - goto error_failed; - - if (olpc_ec_wakeup_available()) { - device_set_wakeup_capable(&olpc_ac->dev, true); - device_set_wakeup_capable(&olpc_bat->dev, true); - } - - return 0; - -error_failed: - device_remove_bin_file(&olpc_bat->dev, &olpc_bat_eeprom); -eeprom_failed: - power_supply_unregister(olpc_bat); -battery_failed: - power_supply_unregister(olpc_ac); - return ret; -} - -static int olpc_battery_remove(struct platform_device *pdev) -{ - device_remove_file(&olpc_bat->dev, &olpc_bat_error); - device_remove_bin_file(&olpc_bat->dev, &olpc_bat_eeprom); - power_supply_unregister(olpc_bat); - power_supply_unregister(olpc_ac); - return 0; -} - -static const struct of_device_id olpc_battery_ids[] = { - { .compatible = "olpc,xo1-battery" }, - {} -}; -MODULE_DEVICE_TABLE(of, olpc_battery_ids); - -static struct platform_driver olpc_battery_driver = { - .driver = { - .name = "olpc-battery", - .of_match_table = olpc_battery_ids, - }, - .probe = olpc_battery_probe, - .remove = olpc_battery_remove, - .suspend = olpc_battery_suspend, -}; - -module_platform_driver(olpc_battery_driver); - -MODULE_AUTHOR("David Woodhouse "); -MODULE_LICENSE("GPL"); -MODULE_DESCRIPTION("Battery driver for One Laptop Per Child 'XO' machine"); diff --git a/drivers/power/pcf50633-charger.c b/drivers/power/pcf50633-charger.c deleted file mode 100644 index d05597b4e40f..000000000000 --- a/drivers/power/pcf50633-charger.c +++ /dev/null @@ -1,488 +0,0 @@ -/* NXP PCF50633 Main Battery Charger Driver - * - * (C) 2006-2008 by Openmoko, Inc. - * Author: Balaji Rao - * All rights reserved. - * - * Broken down from monstrous PCF50633 driver mainly by - * Harald Welte, Andy Green and Werner Almesberger - * - * 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 -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -struct pcf50633_mbc { - struct pcf50633 *pcf; - - int adapter_online; - int usb_online; - - struct power_supply *usb; - struct power_supply *adapter; - struct power_supply *ac; -}; - -int pcf50633_mbc_usb_curlim_set(struct pcf50633 *pcf, int ma) -{ - struct pcf50633_mbc *mbc = platform_get_drvdata(pcf->mbc_pdev); - int ret = 0; - u8 bits; - int charging_start = 1; - u8 mbcs2, chgmod; - unsigned int mbcc5; - - if (ma >= 1000) { - bits = PCF50633_MBCC7_USB_1000mA; - ma = 1000; - } else if (ma >= 500) { - bits = PCF50633_MBCC7_USB_500mA; - ma = 500; - } else if (ma >= 100) { - bits = PCF50633_MBCC7_USB_100mA; - ma = 100; - } else { - bits = PCF50633_MBCC7_USB_SUSPEND; - charging_start = 0; - ma = 0; - } - - ret = pcf50633_reg_set_bit_mask(pcf, PCF50633_REG_MBCC7, - PCF50633_MBCC7_USB_MASK, bits); - if (ret) - dev_err(pcf->dev, "error setting usb curlim to %d mA\n", ma); - else - dev_info(pcf->dev, "usb curlim to %d mA\n", ma); - - /* - * We limit the charging current to be the USB current limit. - * The reason is that on pcf50633, when it enters PMU Standby mode, - * which it does when the device goes "off", the USB current limit - * reverts to the variant default. In at least one common case, that - * default is 500mA. By setting the charging current to be the same - * as the USB limit we set here before PMU standby, we enforce it only - * using the correct amount of current even when the USB current limit - * gets reset to the wrong thing - */ - - if (mbc->pcf->pdata->charger_reference_current_ma) { - mbcc5 = (ma << 8) / mbc->pcf->pdata->charger_reference_current_ma; - if (mbcc5 > 255) - mbcc5 = 255; - pcf50633_reg_write(mbc->pcf, PCF50633_REG_MBCC5, mbcc5); - } - - mbcs2 = pcf50633_reg_read(mbc->pcf, PCF50633_REG_MBCS2); - chgmod = (mbcs2 & PCF50633_MBCS2_MBC_MASK); - - /* If chgmod == BATFULL, setting chgena has no effect. - * Datasheet says we need to set resume instead but when autoresume is - * used resume doesn't work. Clear and set chgena instead. - */ - if (chgmod != PCF50633_MBCS2_MBC_BAT_FULL) - pcf50633_reg_set_bit_mask(pcf, PCF50633_REG_MBCC1, - PCF50633_MBCC1_CHGENA, PCF50633_MBCC1_CHGENA); - else { - pcf50633_reg_clear_bits(pcf, PCF50633_REG_MBCC1, - PCF50633_MBCC1_CHGENA); - pcf50633_reg_set_bit_mask(pcf, PCF50633_REG_MBCC1, - PCF50633_MBCC1_CHGENA, PCF50633_MBCC1_CHGENA); - } - - power_supply_changed(mbc->usb); - - return ret; -} -EXPORT_SYMBOL_GPL(pcf50633_mbc_usb_curlim_set); - -int pcf50633_mbc_get_status(struct pcf50633 *pcf) -{ - struct pcf50633_mbc *mbc = platform_get_drvdata(pcf->mbc_pdev); - int status = 0; - u8 chgmod; - - if (!mbc) - return 0; - - chgmod = pcf50633_reg_read(mbc->pcf, PCF50633_REG_MBCS2) - & PCF50633_MBCS2_MBC_MASK; - - if (mbc->usb_online) - status |= PCF50633_MBC_USB_ONLINE; - if (chgmod == PCF50633_MBCS2_MBC_USB_PRE || - chgmod == PCF50633_MBCS2_MBC_USB_PRE_WAIT || - chgmod == PCF50633_MBCS2_MBC_USB_FAST || - chgmod == PCF50633_MBCS2_MBC_USB_FAST_WAIT) - status |= PCF50633_MBC_USB_ACTIVE; - if (mbc->adapter_online) - status |= PCF50633_MBC_ADAPTER_ONLINE; - if (chgmod == PCF50633_MBCS2_MBC_ADP_PRE || - chgmod == PCF50633_MBCS2_MBC_ADP_PRE_WAIT || - chgmod == PCF50633_MBCS2_MBC_ADP_FAST || - chgmod == PCF50633_MBCS2_MBC_ADP_FAST_WAIT) - status |= PCF50633_MBC_ADAPTER_ACTIVE; - - return status; -} -EXPORT_SYMBOL_GPL(pcf50633_mbc_get_status); - -int pcf50633_mbc_get_usb_online_status(struct pcf50633 *pcf) -{ - struct pcf50633_mbc *mbc = platform_get_drvdata(pcf->mbc_pdev); - - if (!mbc) - return 0; - - return mbc->usb_online; -} -EXPORT_SYMBOL_GPL(pcf50633_mbc_get_usb_online_status); - -static ssize_t -show_chgmode(struct device *dev, struct device_attribute *attr, char *buf) -{ - struct pcf50633_mbc *mbc = dev_get_drvdata(dev); - - u8 mbcs2 = pcf50633_reg_read(mbc->pcf, PCF50633_REG_MBCS2); - u8 chgmod = (mbcs2 & PCF50633_MBCS2_MBC_MASK); - - return sprintf(buf, "%d\n", chgmod); -} -static DEVICE_ATTR(chgmode, S_IRUGO, show_chgmode, NULL); - -static ssize_t -show_usblim(struct device *dev, struct device_attribute *attr, char *buf) -{ - struct pcf50633_mbc *mbc = dev_get_drvdata(dev); - u8 usblim = pcf50633_reg_read(mbc->pcf, PCF50633_REG_MBCC7) & - PCF50633_MBCC7_USB_MASK; - unsigned int ma; - - if (usblim == PCF50633_MBCC7_USB_1000mA) - ma = 1000; - else if (usblim == PCF50633_MBCC7_USB_500mA) - ma = 500; - else if (usblim == PCF50633_MBCC7_USB_100mA) - ma = 100; - else - ma = 0; - - return sprintf(buf, "%u\n", ma); -} - -static ssize_t set_usblim(struct device *dev, - struct device_attribute *attr, const char *buf, size_t count) -{ - struct pcf50633_mbc *mbc = dev_get_drvdata(dev); - unsigned long ma; - int ret; - - ret = kstrtoul(buf, 10, &ma); - if (ret) - return ret; - - pcf50633_mbc_usb_curlim_set(mbc->pcf, ma); - - return count; -} - -static DEVICE_ATTR(usb_curlim, S_IRUGO | S_IWUSR, show_usblim, set_usblim); - -static ssize_t -show_chglim(struct device *dev, struct device_attribute *attr, char *buf) -{ - struct pcf50633_mbc *mbc = dev_get_drvdata(dev); - u8 mbcc5 = pcf50633_reg_read(mbc->pcf, PCF50633_REG_MBCC5); - unsigned int ma; - - if (!mbc->pcf->pdata->charger_reference_current_ma) - return -ENODEV; - - ma = (mbc->pcf->pdata->charger_reference_current_ma * mbcc5) >> 8; - - return sprintf(buf, "%u\n", ma); -} - -static ssize_t set_chglim(struct device *dev, - struct device_attribute *attr, const char *buf, size_t count) -{ - struct pcf50633_mbc *mbc = dev_get_drvdata(dev); - unsigned long ma; - unsigned int mbcc5; - int ret; - - if (!mbc->pcf->pdata->charger_reference_current_ma) - return -ENODEV; - - ret = kstrtoul(buf, 10, &ma); - if (ret) - return ret; - - mbcc5 = (ma << 8) / mbc->pcf->pdata->charger_reference_current_ma; - if (mbcc5 > 255) - mbcc5 = 255; - pcf50633_reg_write(mbc->pcf, PCF50633_REG_MBCC5, mbcc5); - - return count; -} - -/* - * This attribute allows to change MBC charging limit on the fly - * independently of usb current limit. It also gets set automatically every - * time usb current limit is changed. - */ -static DEVICE_ATTR(chg_curlim, S_IRUGO | S_IWUSR, show_chglim, set_chglim); - -static struct attribute *pcf50633_mbc_sysfs_entries[] = { - &dev_attr_chgmode.attr, - &dev_attr_usb_curlim.attr, - &dev_attr_chg_curlim.attr, - NULL, -}; - -static struct attribute_group mbc_attr_group = { - .name = NULL, /* put in device directory */ - .attrs = pcf50633_mbc_sysfs_entries, -}; - -static void -pcf50633_mbc_irq_handler(int irq, void *data) -{ - struct pcf50633_mbc *mbc = data; - - /* USB */ - if (irq == PCF50633_IRQ_USBINS) { - mbc->usb_online = 1; - } else if (irq == PCF50633_IRQ_USBREM) { - mbc->usb_online = 0; - pcf50633_mbc_usb_curlim_set(mbc->pcf, 0); - } - - /* Adapter */ - if (irq == PCF50633_IRQ_ADPINS) - mbc->adapter_online = 1; - else if (irq == PCF50633_IRQ_ADPREM) - mbc->adapter_online = 0; - - power_supply_changed(mbc->ac); - power_supply_changed(mbc->usb); - power_supply_changed(mbc->adapter); - - if (mbc->pcf->pdata->mbc_event_callback) - mbc->pcf->pdata->mbc_event_callback(mbc->pcf, irq); -} - -static int adapter_get_property(struct power_supply *psy, - enum power_supply_property psp, - union power_supply_propval *val) -{ - struct pcf50633_mbc *mbc = power_supply_get_drvdata(psy); - int ret = 0; - - switch (psp) { - case POWER_SUPPLY_PROP_ONLINE: - val->intval = mbc->adapter_online; - break; - default: - ret = -EINVAL; - break; - } - return ret; -} - -static int usb_get_property(struct power_supply *psy, - enum power_supply_property psp, - union power_supply_propval *val) -{ - struct pcf50633_mbc *mbc = power_supply_get_drvdata(psy); - int ret = 0; - u8 usblim = pcf50633_reg_read(mbc->pcf, PCF50633_REG_MBCC7) & - PCF50633_MBCC7_USB_MASK; - - switch (psp) { - case POWER_SUPPLY_PROP_ONLINE: - val->intval = mbc->usb_online && - (usblim <= PCF50633_MBCC7_USB_500mA); - break; - default: - ret = -EINVAL; - break; - } - return ret; -} - -static int ac_get_property(struct power_supply *psy, - enum power_supply_property psp, - union power_supply_propval *val) -{ - struct pcf50633_mbc *mbc = power_supply_get_drvdata(psy); - int ret = 0; - u8 usblim = pcf50633_reg_read(mbc->pcf, PCF50633_REG_MBCC7) & - PCF50633_MBCC7_USB_MASK; - - switch (psp) { - case POWER_SUPPLY_PROP_ONLINE: - val->intval = mbc->usb_online && - (usblim == PCF50633_MBCC7_USB_1000mA); - break; - default: - ret = -EINVAL; - break; - } - return ret; -} - -static enum power_supply_property power_props[] = { - POWER_SUPPLY_PROP_ONLINE, -}; - -static const u8 mbc_irq_handlers[] = { - PCF50633_IRQ_ADPINS, - PCF50633_IRQ_ADPREM, - PCF50633_IRQ_USBINS, - PCF50633_IRQ_USBREM, - PCF50633_IRQ_BATFULL, - PCF50633_IRQ_CHGHALT, - PCF50633_IRQ_THLIMON, - PCF50633_IRQ_THLIMOFF, - PCF50633_IRQ_USBLIMON, - PCF50633_IRQ_USBLIMOFF, - PCF50633_IRQ_LOWSYS, - PCF50633_IRQ_LOWBAT, -}; - -static const struct power_supply_desc pcf50633_mbc_adapter_desc = { - .name = "adapter", - .type = POWER_SUPPLY_TYPE_MAINS, - .properties = power_props, - .num_properties = ARRAY_SIZE(power_props), - .get_property = &adapter_get_property, -}; - -static const struct power_supply_desc pcf50633_mbc_usb_desc = { - .name = "usb", - .type = POWER_SUPPLY_TYPE_USB, - .properties = power_props, - .num_properties = ARRAY_SIZE(power_props), - .get_property = usb_get_property, -}; - -static const struct power_supply_desc pcf50633_mbc_ac_desc = { - .name = "ac", - .type = POWER_SUPPLY_TYPE_MAINS, - .properties = power_props, - .num_properties = ARRAY_SIZE(power_props), - .get_property = ac_get_property, -}; - -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; - - mbc = devm_kzalloc(&pdev->dev, sizeof(*mbc), GFP_KERNEL); - if (!mbc) - return -ENOMEM; - - platform_set_drvdata(pdev, mbc); - mbc->pcf = dev_to_pcf50633(pdev->dev.parent); - - /* Set up IRQ handlers */ - for (i = 0; i < ARRAY_SIZE(mbc_irq_handlers); i++) - pcf50633_register_irq(mbc->pcf, mbc_irq_handlers[i], - pcf50633_mbc_irq_handler, mbc); - - psy_cfg.supplied_to = mbc->pcf->pdata->batteries; - psy_cfg.num_supplicants = mbc->pcf->pdata->num_batteries; - psy_cfg.drv_data = mbc; - - /* Create power supplies */ - mbc->adapter = power_supply_register(&pdev->dev, - &pcf50633_mbc_adapter_desc, - &psy_cfg); - if (IS_ERR(mbc->adapter)) { - dev_err(mbc->pcf->dev, "failed to register adapter\n"); - ret = PTR_ERR(mbc->adapter); - return ret; - } - - mbc->usb = power_supply_register(&pdev->dev, &pcf50633_mbc_usb_desc, - &psy_cfg); - 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; - } - - mbc->ac = power_supply_register(&pdev->dev, &pcf50633_mbc_ac_desc, - &psy_cfg); - if (IS_ERR(mbc->ac)) { - 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; - } - - ret = sysfs_create_group(&pdev->dev.kobj, &mbc_attr_group); - if (ret) - dev_err(mbc->pcf->dev, "failed to create sysfs entries\n"); - - mbcs1 = pcf50633_reg_read(mbc->pcf, PCF50633_REG_MBCS1); - if (mbcs1 & PCF50633_MBCS1_USBPRES) - pcf50633_mbc_irq_handler(PCF50633_IRQ_USBINS, mbc); - if (mbcs1 & PCF50633_MBCS1_ADAPTPRES) - pcf50633_mbc_irq_handler(PCF50633_IRQ_ADPINS, mbc); - - return 0; -} - -static int pcf50633_mbc_remove(struct platform_device *pdev) -{ - struct pcf50633_mbc *mbc = platform_get_drvdata(pdev); - int i; - - /* Remove IRQ handlers */ - for (i = 0; i < ARRAY_SIZE(mbc_irq_handlers); i++) - pcf50633_free_irq(mbc->pcf, mbc_irq_handlers[i]); - - sysfs_remove_group(&pdev->dev.kobj, &mbc_attr_group); - power_supply_unregister(mbc->usb); - power_supply_unregister(mbc->adapter); - power_supply_unregister(mbc->ac); - - return 0; -} - -static struct platform_driver pcf50633_mbc_driver = { - .driver = { - .name = "pcf50633-mbc", - }, - .probe = pcf50633_mbc_probe, - .remove = pcf50633_mbc_remove, -}; - -module_platform_driver(pcf50633_mbc_driver); - -MODULE_AUTHOR("Balaji Rao "); -MODULE_DESCRIPTION("PCF50633 mbc driver"); -MODULE_LICENSE("GPL"); -MODULE_ALIAS("platform:pcf50633-mbc"); diff --git a/drivers/power/pda_power.c b/drivers/power/pda_power.c deleted file mode 100644 index dfe1ee89f7c7..000000000000 --- a/drivers/power/pda_power.c +++ /dev/null @@ -1,514 +0,0 @@ -/* - * Common power driver for PDAs and phones with one or two external - * power supplies (AC/USB) connected to main and backup batteries, - * and optional builtin charger. - * - * Copyright © 2007 Anton Vorontsov - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -static inline unsigned int get_irq_flags(struct resource *res) -{ - return IRQF_SHARED | (res->flags & IRQF_TRIGGER_MASK); -} - -static struct device *dev; -static struct pda_power_pdata *pdata; -static struct resource *ac_irq, *usb_irq; -static struct timer_list charger_timer; -static struct timer_list supply_timer; -static struct timer_list polling_timer; -static int polling; -static struct power_supply *pda_psy_ac, *pda_psy_usb; - -#if IS_ENABLED(CONFIG_USB_PHY) -static struct usb_phy *transceiver; -static struct notifier_block otg_nb; -#endif - -static struct regulator *ac_draw; - -enum { - PDA_PSY_OFFLINE = 0, - PDA_PSY_ONLINE = 1, - PDA_PSY_TO_CHANGE, -}; -static int new_ac_status = -1; -static int new_usb_status = -1; -static int ac_status = -1; -static int usb_status = -1; - -static int pda_power_get_property(struct power_supply *psy, - enum power_supply_property psp, - union power_supply_propval *val) -{ - switch (psp) { - case POWER_SUPPLY_PROP_ONLINE: - if (psy->desc->type == POWER_SUPPLY_TYPE_MAINS) - val->intval = pdata->is_ac_online ? - pdata->is_ac_online() : 0; - else - val->intval = pdata->is_usb_online ? - pdata->is_usb_online() : 0; - break; - default: - return -EINVAL; - } - return 0; -} - -static enum power_supply_property pda_power_props[] = { - POWER_SUPPLY_PROP_ONLINE, -}; - -static char *pda_power_supplied_to[] = { - "main-battery", - "backup-battery", -}; - -static const struct power_supply_desc pda_psy_ac_desc = { - .name = "ac", - .type = POWER_SUPPLY_TYPE_MAINS, - .properties = pda_power_props, - .num_properties = ARRAY_SIZE(pda_power_props), - .get_property = pda_power_get_property, -}; - -static const struct power_supply_desc pda_psy_usb_desc = { - .name = "usb", - .type = POWER_SUPPLY_TYPE_USB, - .properties = pda_power_props, - .num_properties = ARRAY_SIZE(pda_power_props), - .get_property = pda_power_get_property, -}; - -static void update_status(void) -{ - if (pdata->is_ac_online) - new_ac_status = !!pdata->is_ac_online(); - - if (pdata->is_usb_online) - new_usb_status = !!pdata->is_usb_online(); -} - -static void update_charger(void) -{ - static int regulator_enabled; - int max_uA = pdata->ac_max_uA; - - if (pdata->set_charge) { - if (new_ac_status > 0) { - dev_dbg(dev, "charger on (AC)\n"); - pdata->set_charge(PDA_POWER_CHARGE_AC); - } else if (new_usb_status > 0) { - dev_dbg(dev, "charger on (USB)\n"); - pdata->set_charge(PDA_POWER_CHARGE_USB); - } else { - dev_dbg(dev, "charger off\n"); - pdata->set_charge(0); - } - } else if (ac_draw) { - if (new_ac_status > 0) { - regulator_set_current_limit(ac_draw, max_uA, max_uA); - if (!regulator_enabled) { - dev_dbg(dev, "charger on (AC)\n"); - WARN_ON(regulator_enable(ac_draw)); - regulator_enabled = 1; - } - } else { - if (regulator_enabled) { - dev_dbg(dev, "charger off\n"); - WARN_ON(regulator_disable(ac_draw)); - regulator_enabled = 0; - } - } - } -} - -static void supply_timer_func(unsigned long unused) -{ - if (ac_status == PDA_PSY_TO_CHANGE) { - ac_status = new_ac_status; - power_supply_changed(pda_psy_ac); - } - - if (usb_status == PDA_PSY_TO_CHANGE) { - usb_status = new_usb_status; - power_supply_changed(pda_psy_usb); - } -} - -static void psy_changed(void) -{ - update_charger(); - - /* - * Okay, charger set. Now wait a bit before notifying supplicants, - * charge power should stabilize. - */ - mod_timer(&supply_timer, - jiffies + msecs_to_jiffies(pdata->wait_for_charger)); -} - -static void charger_timer_func(unsigned long unused) -{ - update_status(); - psy_changed(); -} - -static irqreturn_t power_changed_isr(int irq, void *power_supply) -{ - if (power_supply == pda_psy_ac) - ac_status = PDA_PSY_TO_CHANGE; - else if (power_supply == pda_psy_usb) - usb_status = PDA_PSY_TO_CHANGE; - else - return IRQ_NONE; - - /* - * Wait a bit before reading ac/usb line status and setting charger, - * because ac/usb status readings may lag from irq. - */ - mod_timer(&charger_timer, - jiffies + msecs_to_jiffies(pdata->wait_for_status)); - - return IRQ_HANDLED; -} - -static void polling_timer_func(unsigned long unused) -{ - int changed = 0; - - dev_dbg(dev, "polling...\n"); - - update_status(); - - if (!ac_irq && new_ac_status != ac_status) { - ac_status = PDA_PSY_TO_CHANGE; - changed = 1; - } - - if (!usb_irq && new_usb_status != usb_status) { - usb_status = PDA_PSY_TO_CHANGE; - changed = 1; - } - - if (changed) - psy_changed(); - - mod_timer(&polling_timer, - jiffies + msecs_to_jiffies(pdata->polling_interval)); -} - -#if IS_ENABLED(CONFIG_USB_PHY) -static int otg_is_usb_online(void) -{ - return (transceiver->last_event == USB_EVENT_VBUS || - transceiver->last_event == USB_EVENT_ENUMERATED); -} - -static int otg_is_ac_online(void) -{ - return (transceiver->last_event == USB_EVENT_CHARGER); -} - -static int otg_handle_notification(struct notifier_block *nb, - unsigned long event, void *unused) -{ - switch (event) { - case USB_EVENT_CHARGER: - ac_status = PDA_PSY_TO_CHANGE; - break; - case USB_EVENT_VBUS: - case USB_EVENT_ENUMERATED: - usb_status = PDA_PSY_TO_CHANGE; - break; - case USB_EVENT_NONE: - ac_status = PDA_PSY_TO_CHANGE; - usb_status = PDA_PSY_TO_CHANGE; - break; - default: - return NOTIFY_OK; - } - - /* - * Wait a bit before reading ac/usb line status and setting charger, - * because ac/usb status readings may lag from irq. - */ - mod_timer(&charger_timer, - jiffies + msecs_to_jiffies(pdata->wait_for_status)); - - return NOTIFY_OK; -} -#endif - -static int pda_power_probe(struct platform_device *pdev) -{ - struct power_supply_config psy_cfg = {}; - int ret = 0; - - dev = &pdev->dev; - - if (pdev->id != -1) { - dev_err(dev, "it's meaningless to register several " - "pda_powers; use id = -1\n"); - ret = -EINVAL; - goto wrongid; - } - - pdata = pdev->dev.platform_data; - - if (pdata->init) { - ret = pdata->init(dev); - if (ret < 0) - goto init_failed; - } - - ac_draw = regulator_get(dev, "ac_draw"); - if (IS_ERR(ac_draw)) { - dev_dbg(dev, "couldn't get ac_draw regulator\n"); - ac_draw = NULL; - } - - update_status(); - update_charger(); - - if (!pdata->wait_for_status) - pdata->wait_for_status = 500; - - if (!pdata->wait_for_charger) - pdata->wait_for_charger = 500; - - if (!pdata->polling_interval) - pdata->polling_interval = 2000; - - if (!pdata->ac_max_uA) - pdata->ac_max_uA = 500000; - - setup_timer(&charger_timer, charger_timer_func, 0); - setup_timer(&supply_timer, supply_timer_func, 0); - - ac_irq = platform_get_resource_byname(pdev, IORESOURCE_IRQ, "ac"); - usb_irq = platform_get_resource_byname(pdev, IORESOURCE_IRQ, "usb"); - - if (pdata->supplied_to) { - psy_cfg.supplied_to = pdata->supplied_to; - psy_cfg.num_supplicants = pdata->num_supplicants; - } else { - psy_cfg.supplied_to = pda_power_supplied_to; - psy_cfg.num_supplicants = ARRAY_SIZE(pda_power_supplied_to); - } - -#if IS_ENABLED(CONFIG_USB_PHY) - transceiver = usb_get_phy(USB_PHY_TYPE_USB2); - if (!IS_ERR_OR_NULL(transceiver)) { - if (!pdata->is_usb_online) - pdata->is_usb_online = otg_is_usb_online; - if (!pdata->is_ac_online) - pdata->is_ac_online = otg_is_ac_online; - } -#endif - - if (pdata->is_ac_online) { - pda_psy_ac = power_supply_register(&pdev->dev, - &pda_psy_ac_desc, &psy_cfg); - if (IS_ERR(pda_psy_ac)) { - dev_err(dev, "failed to register %s power supply\n", - pda_psy_ac_desc.name); - ret = PTR_ERR(pda_psy_ac); - goto ac_supply_failed; - } - - if (ac_irq) { - ret = request_irq(ac_irq->start, power_changed_isr, - get_irq_flags(ac_irq), ac_irq->name, - pda_psy_ac); - if (ret) { - dev_err(dev, "request ac irq failed\n"); - goto ac_irq_failed; - } - } else { - polling = 1; - } - } - - if (pdata->is_usb_online) { - pda_psy_usb = power_supply_register(&pdev->dev, - &pda_psy_usb_desc, - &psy_cfg); - if (IS_ERR(pda_psy_usb)) { - dev_err(dev, "failed to register %s power supply\n", - pda_psy_usb_desc.name); - ret = PTR_ERR(pda_psy_usb); - goto usb_supply_failed; - } - - if (usb_irq) { - ret = request_irq(usb_irq->start, power_changed_isr, - get_irq_flags(usb_irq), - usb_irq->name, pda_psy_usb); - if (ret) { - dev_err(dev, "request usb irq failed\n"); - goto usb_irq_failed; - } - } else { - polling = 1; - } - } - -#if IS_ENABLED(CONFIG_USB_PHY) - if (!IS_ERR_OR_NULL(transceiver) && pdata->use_otg_notifier) { - otg_nb.notifier_call = otg_handle_notification; - ret = usb_register_notifier(transceiver, &otg_nb); - if (ret) { - dev_err(dev, "failure to register otg notifier\n"); - goto otg_reg_notifier_failed; - } - polling = 0; - } -#endif - - if (polling) { - dev_dbg(dev, "will poll for status\n"); - setup_timer(&polling_timer, polling_timer_func, 0); - mod_timer(&polling_timer, - jiffies + msecs_to_jiffies(pdata->polling_interval)); - } - - if (ac_irq || usb_irq) - device_init_wakeup(&pdev->dev, 1); - - return 0; - -#if IS_ENABLED(CONFIG_USB_PHY) -otg_reg_notifier_failed: - if (pdata->is_usb_online && usb_irq) - free_irq(usb_irq->start, pda_psy_usb); -#endif -usb_irq_failed: - if (pdata->is_usb_online) - power_supply_unregister(pda_psy_usb); -usb_supply_failed: - if (pdata->is_ac_online && ac_irq) - free_irq(ac_irq->start, pda_psy_ac); -#if IS_ENABLED(CONFIG_USB_PHY) - if (!IS_ERR_OR_NULL(transceiver)) - usb_put_phy(transceiver); -#endif -ac_irq_failed: - if (pdata->is_ac_online) - power_supply_unregister(pda_psy_ac); -ac_supply_failed: - if (ac_draw) { - regulator_put(ac_draw); - ac_draw = NULL; - } - if (pdata->exit) - pdata->exit(dev); -init_failed: -wrongid: - return ret; -} - -static int pda_power_remove(struct platform_device *pdev) -{ - if (pdata->is_usb_online && usb_irq) - free_irq(usb_irq->start, pda_psy_usb); - if (pdata->is_ac_online && ac_irq) - free_irq(ac_irq->start, pda_psy_ac); - - if (polling) - del_timer_sync(&polling_timer); - del_timer_sync(&charger_timer); - del_timer_sync(&supply_timer); - - if (pdata->is_usb_online) - power_supply_unregister(pda_psy_usb); - if (pdata->is_ac_online) - power_supply_unregister(pda_psy_ac); -#if IS_ENABLED(CONFIG_USB_PHY) - if (!IS_ERR_OR_NULL(transceiver)) - usb_put_phy(transceiver); -#endif - if (ac_draw) { - regulator_put(ac_draw); - ac_draw = NULL; - } - if (pdata->exit) - pdata->exit(dev); - - return 0; -} - -#ifdef CONFIG_PM -static int ac_wakeup_enabled; -static int usb_wakeup_enabled; - -static int pda_power_suspend(struct platform_device *pdev, pm_message_t state) -{ - if (pdata->suspend) { - int ret = pdata->suspend(state); - - if (ret) - return ret; - } - - if (device_may_wakeup(&pdev->dev)) { - if (ac_irq) - ac_wakeup_enabled = !enable_irq_wake(ac_irq->start); - if (usb_irq) - usb_wakeup_enabled = !enable_irq_wake(usb_irq->start); - } - - return 0; -} - -static int pda_power_resume(struct platform_device *pdev) -{ - if (device_may_wakeup(&pdev->dev)) { - if (usb_irq && usb_wakeup_enabled) - disable_irq_wake(usb_irq->start); - if (ac_irq && ac_wakeup_enabled) - disable_irq_wake(ac_irq->start); - } - - if (pdata->resume) - return pdata->resume(); - - return 0; -} -#else -#define pda_power_suspend NULL -#define pda_power_resume NULL -#endif /* CONFIG_PM */ - -static struct platform_driver pda_power_pdrv = { - .driver = { - .name = "pda-power", - }, - .probe = pda_power_probe, - .remove = pda_power_remove, - .suspend = pda_power_suspend, - .resume = pda_power_resume, -}; - -module_platform_driver(pda_power_pdrv); - -MODULE_LICENSE("GPL"); -MODULE_AUTHOR("Anton Vorontsov "); -MODULE_ALIAS("platform:pda-power"); diff --git a/drivers/power/pm2301_charger.c b/drivers/power/pm2301_charger.c deleted file mode 100644 index fb62ed3fc38c..000000000000 --- a/drivers/power/pm2301_charger.c +++ /dev/null @@ -1,1257 +0,0 @@ -/* - * Copyright 2012 ST Ericsson. - * - * Power supply driver for ST Ericsson pm2xxx_charger charger - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "pm2301_charger.h" - -#define to_pm2xxx_charger_ac_device_info(x) container_of((x), \ - struct pm2xxx_charger, ac_chg) -#define SLEEP_MIN 50 -#define SLEEP_MAX 100 -#define PM2XXX_AUTOSUSPEND_DELAY 500 - -static int pm2xxx_interrupt_registers[] = { - PM2XXX_REG_INT1, - PM2XXX_REG_INT2, - PM2XXX_REG_INT3, - PM2XXX_REG_INT4, - PM2XXX_REG_INT5, - PM2XXX_REG_INT6, -}; - -static enum power_supply_property pm2xxx_charger_ac_props[] = { - POWER_SUPPLY_PROP_HEALTH, - POWER_SUPPLY_PROP_PRESENT, - POWER_SUPPLY_PROP_ONLINE, - POWER_SUPPLY_PROP_VOLTAGE_AVG, -}; - -static int pm2xxx_charger_voltage_map[] = { - 3500, - 3525, - 3550, - 3575, - 3600, - 3625, - 3650, - 3675, - 3700, - 3725, - 3750, - 3775, - 3800, - 3825, - 3850, - 3875, - 3900, - 3925, - 3950, - 3975, - 4000, - 4025, - 4050, - 4075, - 4100, - 4125, - 4150, - 4175, - 4200, - 4225, - 4250, - 4275, - 4300, -}; - -static int pm2xxx_charger_current_map[] = { - 200, - 200, - 400, - 600, - 800, - 1000, - 1200, - 1400, - 1600, - 1800, - 2000, - 2200, - 2400, - 2600, - 2800, - 3000, -}; - -static const struct i2c_device_id pm2xxx_ident[] = { - { "pm2301", 0 }, - { } -}; - -static void set_lpn_pin(struct pm2xxx_charger *pm2) -{ - if (!pm2->ac.charger_connected && gpio_is_valid(pm2->lpn_pin)) { - gpio_set_value(pm2->lpn_pin, 1); - usleep_range(SLEEP_MIN, SLEEP_MAX); - } -} - -static void clear_lpn_pin(struct pm2xxx_charger *pm2) -{ - if (!pm2->ac.charger_connected && gpio_is_valid(pm2->lpn_pin)) - gpio_set_value(pm2->lpn_pin, 0); -} - -static int pm2xxx_reg_read(struct pm2xxx_charger *pm2, int reg, u8 *val) -{ - int ret; - - /* wake up the device */ - pm_runtime_get_sync(pm2->dev); - - ret = i2c_smbus_read_i2c_block_data(pm2->config.pm2xxx_i2c, reg, - 1, val); - if (ret < 0) - dev_err(pm2->dev, "Error reading register at 0x%x\n", reg); - else - ret = 0; - - pm_runtime_put_sync(pm2->dev); - - return ret; -} - -static int pm2xxx_reg_write(struct pm2xxx_charger *pm2, int reg, u8 val) -{ - int ret; - - /* wake up the device */ - pm_runtime_get_sync(pm2->dev); - - ret = i2c_smbus_write_i2c_block_data(pm2->config.pm2xxx_i2c, reg, - 1, &val); - if (ret < 0) - dev_err(pm2->dev, "Error writing register at 0x%x\n", reg); - else - ret = 0; - - pm_runtime_put_sync(pm2->dev); - - return ret; -} - -static int pm2xxx_charging_enable_mngt(struct pm2xxx_charger *pm2) -{ - int ret; - - /* Enable charging */ - ret = pm2xxx_reg_write(pm2, PM2XXX_BATT_CTRL_REG2, - (PM2XXX_CH_AUTO_RESUME_EN | PM2XXX_CHARGER_ENA)); - - return ret; -} - -static int pm2xxx_charging_disable_mngt(struct pm2xxx_charger *pm2) -{ - int ret; - - /* Disable SW EOC ctrl */ - ret = pm2xxx_reg_write(pm2, PM2XXX_SW_CTRL_REG, PM2XXX_SWCTRL_HW); - if (ret < 0) { - dev_err(pm2->dev, "%s pm2xxx write failed\n", __func__); - return ret; - } - - /* Disable charging */ - ret = pm2xxx_reg_write(pm2, PM2XXX_BATT_CTRL_REG2, - (PM2XXX_CH_AUTO_RESUME_DIS | PM2XXX_CHARGER_DIS)); - if (ret < 0) { - dev_err(pm2->dev, "%s pm2xxx write failed\n", __func__); - return ret; - } - - return 0; -} - -static int pm2xxx_charger_batt_therm_mngt(struct pm2xxx_charger *pm2, int val) -{ - queue_work(pm2->charger_wq, &pm2->check_main_thermal_prot_work); - - return 0; -} - - -static int pm2xxx_charger_die_therm_mngt(struct pm2xxx_charger *pm2, int val) -{ - queue_work(pm2->charger_wq, &pm2->check_main_thermal_prot_work); - - return 0; -} - -static int pm2xxx_charger_ovv_mngt(struct pm2xxx_charger *pm2, int val) -{ - dev_err(pm2->dev, "Overvoltage detected\n"); - pm2->flags.ovv = true; - power_supply_changed(pm2->ac_chg.psy); - - /* Schedule a new HW failure check */ - queue_delayed_work(pm2->charger_wq, &pm2->check_hw_failure_work, 0); - - return 0; -} - -static int pm2xxx_charger_wd_exp_mngt(struct pm2xxx_charger *pm2, int val) -{ - dev_dbg(pm2->dev , "20 minutes watchdog expired\n"); - - pm2->ac.wd_expired = true; - power_supply_changed(pm2->ac_chg.psy); - - return 0; -} - -static int pm2xxx_charger_vbat_lsig_mngt(struct pm2xxx_charger *pm2, int val) -{ - int ret; - - switch (val) { - case PM2XXX_INT1_ITVBATLOWR: - dev_dbg(pm2->dev, "VBAT grows above VBAT_LOW level\n"); - /* Enable SW EOC ctrl */ - ret = pm2xxx_reg_write(pm2, PM2XXX_SW_CTRL_REG, - PM2XXX_SWCTRL_SW); - if (ret < 0) { - dev_err(pm2->dev, "%s pm2xxx write failed\n", __func__); - return ret; - } - break; - - case PM2XXX_INT1_ITVBATLOWF: - dev_dbg(pm2->dev, "VBAT drops below VBAT_LOW level\n"); - /* Disable SW EOC ctrl */ - ret = pm2xxx_reg_write(pm2, PM2XXX_SW_CTRL_REG, - PM2XXX_SWCTRL_HW); - if (ret < 0) { - dev_err(pm2->dev, "%s pm2xxx write failed\n", __func__); - return ret; - } - break; - - default: - dev_err(pm2->dev, "Unknown VBAT level\n"); - } - - return 0; -} - -static int pm2xxx_charger_bat_disc_mngt(struct pm2xxx_charger *pm2, int val) -{ - dev_dbg(pm2->dev, "battery disconnected\n"); - - return 0; -} - -static int pm2xxx_charger_detection(struct pm2xxx_charger *pm2, u8 *val) -{ - int ret; - - ret = pm2xxx_reg_read(pm2, PM2XXX_SRCE_REG_INT2, val); - - if (ret < 0) { - dev_err(pm2->dev, "Charger detection failed\n"); - goto out; - } - - *val &= (PM2XXX_INT2_S_ITVPWR1PLUG | PM2XXX_INT2_S_ITVPWR2PLUG); - -out: - return ret; -} - -static int pm2xxx_charger_itv_pwr_plug_mngt(struct pm2xxx_charger *pm2, int val) -{ - - int ret; - u8 read_val; - - /* - * Since we can't be sure that the events are received - * synchronously, we have the check if the main charger is - * connected by reading the interrupt source register. - */ - ret = pm2xxx_charger_detection(pm2, &read_val); - - if ((ret == 0) && read_val) { - pm2->ac.charger_connected = 1; - pm2->ac_conn = true; - queue_work(pm2->charger_wq, &pm2->ac_work); - } - - - return ret; -} - -static int pm2xxx_charger_itv_pwr_unplug_mngt(struct pm2xxx_charger *pm2, - int val) -{ - pm2->ac.charger_connected = 0; - queue_work(pm2->charger_wq, &pm2->ac_work); - - return 0; -} - -static int pm2_int_reg0(void *pm2_data, int val) -{ - struct pm2xxx_charger *pm2 = pm2_data; - int ret = 0; - - if (val & PM2XXX_INT1_ITVBATLOWR) { - ret = pm2xxx_charger_vbat_lsig_mngt(pm2, - PM2XXX_INT1_ITVBATLOWR); - if (ret < 0) - goto out; - } - - if (val & PM2XXX_INT1_ITVBATLOWF) { - ret = pm2xxx_charger_vbat_lsig_mngt(pm2, - PM2XXX_INT1_ITVBATLOWF); - if (ret < 0) - goto out; - } - - if (val & PM2XXX_INT1_ITVBATDISCONNECT) { - ret = pm2xxx_charger_bat_disc_mngt(pm2, - PM2XXX_INT1_ITVBATDISCONNECT); - if (ret < 0) - goto out; - } -out: - return ret; -} - -static int pm2_int_reg1(void *pm2_data, int val) -{ - struct pm2xxx_charger *pm2 = pm2_data; - int ret = 0; - - if (val & (PM2XXX_INT2_ITVPWR1PLUG | PM2XXX_INT2_ITVPWR2PLUG)) { - dev_dbg(pm2->dev , "Main charger plugged\n"); - ret = pm2xxx_charger_itv_pwr_plug_mngt(pm2, val & - (PM2XXX_INT2_ITVPWR1PLUG | PM2XXX_INT2_ITVPWR2PLUG)); - } - - if (val & - (PM2XXX_INT2_ITVPWR1UNPLUG | PM2XXX_INT2_ITVPWR2UNPLUG)) { - dev_dbg(pm2->dev , "Main charger unplugged\n"); - ret = pm2xxx_charger_itv_pwr_unplug_mngt(pm2, val & - (PM2XXX_INT2_ITVPWR1UNPLUG | - PM2XXX_INT2_ITVPWR2UNPLUG)); - } - - return ret; -} - -static int pm2_int_reg2(void *pm2_data, int val) -{ - struct pm2xxx_charger *pm2 = pm2_data; - int ret = 0; - - if (val & PM2XXX_INT3_ITAUTOTIMEOUTWD) - ret = pm2xxx_charger_wd_exp_mngt(pm2, val); - - if (val & (PM2XXX_INT3_ITCHPRECHARGEWD | - PM2XXX_INT3_ITCHCCWD | PM2XXX_INT3_ITCHCVWD)) { - dev_dbg(pm2->dev, - "Watchdog occurred for precharge, CC and CV charge\n"); - } - - return ret; -} - -static int pm2_int_reg3(void *pm2_data, int val) -{ - struct pm2xxx_charger *pm2 = pm2_data; - int ret = 0; - - if (val & (PM2XXX_INT4_ITCHARGINGON)) { - dev_dbg(pm2->dev , - "chargind operation has started\n"); - } - - if (val & (PM2XXX_INT4_ITVRESUME)) { - dev_dbg(pm2->dev, - "battery discharged down to VResume threshold\n"); - } - - if (val & (PM2XXX_INT4_ITBATTFULL)) { - dev_dbg(pm2->dev , "battery fully detected\n"); - } - - if (val & (PM2XXX_INT4_ITCVPHASE)) { - dev_dbg(pm2->dev, "CV phase enter with 0.5C charging\n"); - } - - if (val & (PM2XXX_INT4_ITVPWR2OVV | PM2XXX_INT4_ITVPWR1OVV)) { - pm2->failure_case = VPWR_OVV; - ret = pm2xxx_charger_ovv_mngt(pm2, val & - (PM2XXX_INT4_ITVPWR2OVV | PM2XXX_INT4_ITVPWR1OVV)); - dev_dbg(pm2->dev, "VPWR/VSYSTEM overvoltage detected\n"); - } - - if (val & (PM2XXX_INT4_S_ITBATTEMPCOLD | - PM2XXX_INT4_S_ITBATTEMPHOT)) { - ret = pm2xxx_charger_batt_therm_mngt(pm2, val & - (PM2XXX_INT4_S_ITBATTEMPCOLD | - PM2XXX_INT4_S_ITBATTEMPHOT)); - dev_dbg(pm2->dev, "BTEMP is too Low/High\n"); - } - - return ret; -} - -static int pm2_int_reg4(void *pm2_data, int val) -{ - struct pm2xxx_charger *pm2 = pm2_data; - int ret = 0; - - if (val & PM2XXX_INT5_ITVSYSTEMOVV) { - pm2->failure_case = VSYSTEM_OVV; - ret = pm2xxx_charger_ovv_mngt(pm2, val & - PM2XXX_INT5_ITVSYSTEMOVV); - dev_dbg(pm2->dev, "VSYSTEM overvoltage detected\n"); - } - - if (val & (PM2XXX_INT5_ITTHERMALWARNINGFALL | - PM2XXX_INT5_ITTHERMALWARNINGRISE | - PM2XXX_INT5_ITTHERMALSHUTDOWNFALL | - PM2XXX_INT5_ITTHERMALSHUTDOWNRISE)) { - dev_dbg(pm2->dev, "BTEMP die temperature is too Low/High\n"); - ret = pm2xxx_charger_die_therm_mngt(pm2, val & - (PM2XXX_INT5_ITTHERMALWARNINGFALL | - PM2XXX_INT5_ITTHERMALWARNINGRISE | - PM2XXX_INT5_ITTHERMALSHUTDOWNFALL | - PM2XXX_INT5_ITTHERMALSHUTDOWNRISE)); - } - - return ret; -} - -static int pm2_int_reg5(void *pm2_data, int val) -{ - struct pm2xxx_charger *pm2 = pm2_data; - int ret = 0; - - if (val & (PM2XXX_INT6_ITVPWR2DROP | PM2XXX_INT6_ITVPWR1DROP)) { - dev_dbg(pm2->dev, "VMPWR drop to VBAT level\n"); - } - - if (val & (PM2XXX_INT6_ITVPWR2VALIDRISE | - PM2XXX_INT6_ITVPWR1VALIDRISE | - PM2XXX_INT6_ITVPWR2VALIDFALL | - PM2XXX_INT6_ITVPWR1VALIDFALL)) { - dev_dbg(pm2->dev, "Falling/Rising edge on WPWR1/2\n"); - } - - return ret; -} - -static irqreturn_t pm2xxx_irq_int(int irq, void *data) -{ - struct pm2xxx_charger *pm2 = data; - struct pm2xxx_interrupts *interrupt = pm2->pm2_int; - int i; - - /* wake up the device */ - pm_runtime_get_sync(pm2->dev); - - do { - for (i = 0; i < PM2XXX_NUM_INT_REG; i++) { - pm2xxx_reg_read(pm2, - pm2xxx_interrupt_registers[i], - &(interrupt->reg[i])); - - if (interrupt->reg[i] > 0) - interrupt->handler[i](pm2, interrupt->reg[i]); - } - } while (gpio_get_value(pm2->pdata->gpio_irq_number) == 0); - - pm_runtime_mark_last_busy(pm2->dev); - pm_runtime_put_autosuspend(pm2->dev); - - return IRQ_HANDLED; -} - -static int pm2xxx_charger_get_ac_cv(struct pm2xxx_charger *pm2) -{ - int ret = 0; - u8 val; - - if (pm2->ac.charger_connected && pm2->ac.charger_online) { - - ret = pm2xxx_reg_read(pm2, PM2XXX_SRCE_REG_INT4, &val); - if (ret < 0) { - dev_err(pm2->dev, "%s pm2xxx read failed\n", __func__); - goto out; - } - - if (val & PM2XXX_INT4_S_ITCVPHASE) - ret = PM2XXX_CONST_VOLT; - else - ret = PM2XXX_CONST_CURR; - } -out: - return ret; -} - -static int pm2xxx_current_to_regval(int curr) -{ - int i; - - if (curr < pm2xxx_charger_current_map[0]) - return 0; - - for (i = 1; i < ARRAY_SIZE(pm2xxx_charger_current_map); i++) { - if (curr < pm2xxx_charger_current_map[i]) - return (i - 1); - } - - i = ARRAY_SIZE(pm2xxx_charger_current_map) - 1; - if (curr == pm2xxx_charger_current_map[i]) - return i; - else - return -EINVAL; -} - -static int pm2xxx_voltage_to_regval(int curr) -{ - int i; - - if (curr < pm2xxx_charger_voltage_map[0]) - return 0; - - for (i = 1; i < ARRAY_SIZE(pm2xxx_charger_voltage_map); i++) { - if (curr < pm2xxx_charger_voltage_map[i]) - return i - 1; - } - - i = ARRAY_SIZE(pm2xxx_charger_voltage_map) - 1; - if (curr == pm2xxx_charger_voltage_map[i]) - return i; - else - return -EINVAL; -} - -static int pm2xxx_charger_update_charger_current(struct ux500_charger *charger, - int ich_out) -{ - int ret; - int curr_index; - struct pm2xxx_charger *pm2; - u8 val; - - if (charger->psy->desc->type == POWER_SUPPLY_TYPE_MAINS) - pm2 = to_pm2xxx_charger_ac_device_info(charger); - else - return -ENXIO; - - curr_index = pm2xxx_current_to_regval(ich_out); - if (curr_index < 0) { - dev_err(pm2->dev, - "Charger current too high, charging not started\n"); - return -ENXIO; - } - - ret = pm2xxx_reg_read(pm2, PM2XXX_BATT_CTRL_REG6, &val); - if (ret >= 0) { - val &= ~PM2XXX_DIR_CH_CC_CURRENT_MASK; - val |= curr_index; - ret = pm2xxx_reg_write(pm2, PM2XXX_BATT_CTRL_REG6, val); - if (ret < 0) { - dev_err(pm2->dev, - "%s write failed\n", __func__); - } - } - else - dev_err(pm2->dev, "%s read failed\n", __func__); - - return ret; -} - -static int pm2xxx_charger_ac_get_property(struct power_supply *psy, - enum power_supply_property psp, - union power_supply_propval *val) -{ - struct pm2xxx_charger *pm2; - - pm2 = to_pm2xxx_charger_ac_device_info(psy_to_ux500_charger(psy)); - - switch (psp) { - case POWER_SUPPLY_PROP_HEALTH: - if (pm2->flags.mainextchnotok) - val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; - else if (pm2->ac.wd_expired) - val->intval = POWER_SUPPLY_HEALTH_DEAD; - else if (pm2->flags.main_thermal_prot) - val->intval = POWER_SUPPLY_HEALTH_OVERHEAT; - else if (pm2->flags.ovv) - val->intval = POWER_SUPPLY_HEALTH_OVERVOLTAGE; - else - val->intval = POWER_SUPPLY_HEALTH_GOOD; - break; - case POWER_SUPPLY_PROP_ONLINE: - val->intval = pm2->ac.charger_online; - break; - case POWER_SUPPLY_PROP_PRESENT: - val->intval = pm2->ac.charger_connected; - break; - case POWER_SUPPLY_PROP_VOLTAGE_AVG: - pm2->ac.cv_active = pm2xxx_charger_get_ac_cv(pm2); - val->intval = pm2->ac.cv_active; - break; - default: - return -EINVAL; - } - return 0; -} - -static int pm2xxx_charging_init(struct pm2xxx_charger *pm2) -{ - int ret = 0; - - /* enable CC and CV watchdog */ - ret = pm2xxx_reg_write(pm2, PM2XXX_BATT_CTRL_REG3, - (PM2XXX_CH_WD_CV_PHASE_60MIN | PM2XXX_CH_WD_CC_PHASE_60MIN)); - if( ret < 0) - return ret; - - /* enable precharge watchdog */ - ret = pm2xxx_reg_write(pm2, PM2XXX_BATT_CTRL_REG4, - PM2XXX_CH_WD_PRECH_PHASE_60MIN); - - /* Disable auto timeout */ - ret = pm2xxx_reg_write(pm2, PM2XXX_BATT_CTRL_REG5, - PM2XXX_CH_WD_AUTO_TIMEOUT_20MIN); - - /* - * EOC current level = 100mA - * Precharge current level = 100mA - * CC current level = 1000mA - */ - ret = pm2xxx_reg_write(pm2, PM2XXX_BATT_CTRL_REG6, - (PM2XXX_DIR_CH_CC_CURRENT_1000MA | - PM2XXX_CH_PRECH_CURRENT_100MA | - PM2XXX_CH_EOC_CURRENT_100MA)); - - /* - * recharge threshold = 3.8V - * Precharge to CC threshold = 2.9V - */ - ret = pm2xxx_reg_write(pm2, PM2XXX_BATT_CTRL_REG7, - (PM2XXX_CH_PRECH_VOL_2_9 | PM2XXX_CH_VRESUME_VOL_3_8)); - - /* float voltage charger level = 4.2V */ - ret = pm2xxx_reg_write(pm2, PM2XXX_BATT_CTRL_REG8, - PM2XXX_CH_VOLT_4_2); - - /* Voltage drop between VBAT and VSYS in HW charging = 300mV */ - ret = pm2xxx_reg_write(pm2, PM2XXX_BATT_CTRL_REG9, - (PM2XXX_CH_150MV_DROP_300MV | PM2XXX_CHARCHING_INFO_DIS | - PM2XXX_CH_CC_REDUCED_CURRENT_IDENT | - PM2XXX_CH_CC_MODEDROP_DIS)); - - /* Input charger level of over voltage = 10V */ - ret = pm2xxx_reg_write(pm2, PM2XXX_INP_VOLT_VPWR2, - PM2XXX_VPWR2_OVV_10); - ret = pm2xxx_reg_write(pm2, PM2XXX_INP_VOLT_VPWR1, - PM2XXX_VPWR1_OVV_10); - - /* Input charger drop */ - ret = pm2xxx_reg_write(pm2, PM2XXX_INP_DROP_VPWR2, - (PM2XXX_VPWR2_HW_OPT_DIS | PM2XXX_VPWR2_VALID_DIS | - PM2XXX_VPWR2_DROP_DIS)); - ret = pm2xxx_reg_write(pm2, PM2XXX_INP_DROP_VPWR1, - (PM2XXX_VPWR1_HW_OPT_DIS | PM2XXX_VPWR1_VALID_DIS | - PM2XXX_VPWR1_DROP_DIS)); - - /* Disable battery low monitoring */ - ret = pm2xxx_reg_write(pm2, PM2XXX_BATT_LOW_LEV_COMP_REG, - PM2XXX_VBAT_LOW_MONITORING_ENA); - - return ret; -} - -static int pm2xxx_charger_ac_en(struct ux500_charger *charger, - int enable, int vset, int iset) -{ - int ret; - int volt_index; - int curr_index; - u8 val; - - struct pm2xxx_charger *pm2 = to_pm2xxx_charger_ac_device_info(charger); - - if (enable) { - if (!pm2->ac.charger_connected) { - dev_dbg(pm2->dev, "AC charger not connected\n"); - return -ENXIO; - } - - dev_dbg(pm2->dev, "Enable AC: %dmV %dmA\n", vset, iset); - if (!pm2->vddadc_en_ac) { - ret = regulator_enable(pm2->regu); - if (ret) - dev_warn(pm2->dev, - "Failed to enable vddadc regulator\n"); - else - pm2->vddadc_en_ac = true; - } - - ret = pm2xxx_charging_init(pm2); - if (ret < 0) { - dev_err(pm2->dev, "%s charging init failed\n", - __func__); - goto error_occured; - } - - volt_index = pm2xxx_voltage_to_regval(vset); - curr_index = pm2xxx_current_to_regval(iset); - - if (volt_index < 0 || curr_index < 0) { - dev_err(pm2->dev, - "Charger voltage or current too high, " - "charging not started\n"); - return -ENXIO; - } - - ret = pm2xxx_reg_read(pm2, PM2XXX_BATT_CTRL_REG8, &val); - if (ret < 0) { - dev_err(pm2->dev, "%s pm2xxx read failed\n", __func__); - goto error_occured; - } - val &= ~PM2XXX_CH_VOLT_MASK; - val |= volt_index; - ret = pm2xxx_reg_write(pm2, PM2XXX_BATT_CTRL_REG8, val); - if (ret < 0) { - dev_err(pm2->dev, "%s pm2xxx write failed\n", __func__); - goto error_occured; - } - - ret = pm2xxx_reg_read(pm2, PM2XXX_BATT_CTRL_REG6, &val); - if (ret < 0) { - dev_err(pm2->dev, "%s pm2xxx read failed\n", __func__); - goto error_occured; - } - val &= ~PM2XXX_DIR_CH_CC_CURRENT_MASK; - val |= curr_index; - ret = pm2xxx_reg_write(pm2, PM2XXX_BATT_CTRL_REG6, val); - if (ret < 0) { - dev_err(pm2->dev, "%s pm2xxx write failed\n", __func__); - goto error_occured; - } - - if (!pm2->bat->enable_overshoot) { - ret = pm2xxx_reg_read(pm2, PM2XXX_LED_CTRL_REG, &val); - if (ret < 0) { - dev_err(pm2->dev, "%s pm2xxx read failed\n", - __func__); - goto error_occured; - } - val |= PM2XXX_ANTI_OVERSHOOT_EN; - ret = pm2xxx_reg_write(pm2, PM2XXX_LED_CTRL_REG, val); - if (ret < 0) { - dev_err(pm2->dev, "%s pm2xxx write failed\n", - __func__); - goto error_occured; - } - } - - ret = pm2xxx_charging_enable_mngt(pm2); - if (ret < 0) { - dev_err(pm2->dev, "Failed to enable" - "pm2xxx ac charger\n"); - goto error_occured; - } - - pm2->ac.charger_online = 1; - } else { - pm2->ac.charger_online = 0; - pm2->ac.wd_expired = false; - - /* Disable regulator if enabled */ - if (pm2->vddadc_en_ac) { - regulator_disable(pm2->regu); - pm2->vddadc_en_ac = false; - } - - ret = pm2xxx_charging_disable_mngt(pm2); - if (ret < 0) { - dev_err(pm2->dev, "failed to disable" - "pm2xxx ac charger\n"); - goto error_occured; - } - - dev_dbg(pm2->dev, "PM2301: " "Disabled AC charging\n"); - } - power_supply_changed(pm2->ac_chg.psy); - -error_occured: - return ret; -} - -static int pm2xxx_charger_watchdog_kick(struct ux500_charger *charger) -{ - int ret; - struct pm2xxx_charger *pm2; - - if (charger->psy->desc->type == POWER_SUPPLY_TYPE_MAINS) - pm2 = to_pm2xxx_charger_ac_device_info(charger); - else - return -ENXIO; - - ret = pm2xxx_reg_write(pm2, PM2XXX_BATT_WD_KICK, WD_TIMER); - if (ret) - dev_err(pm2->dev, "Failed to kick WD!\n"); - - return ret; -} - -static void pm2xxx_charger_ac_work(struct work_struct *work) -{ - struct pm2xxx_charger *pm2 = container_of(work, - struct pm2xxx_charger, ac_work); - - - power_supply_changed(pm2->ac_chg.psy); - sysfs_notify(&pm2->ac_chg.psy->dev.kobj, NULL, "present"); -}; - -static void pm2xxx_charger_check_hw_failure_work(struct work_struct *work) -{ - u8 reg_value; - - struct pm2xxx_charger *pm2 = container_of(work, - struct pm2xxx_charger, check_hw_failure_work.work); - - if (pm2->flags.ovv) { - pm2xxx_reg_read(pm2, PM2XXX_SRCE_REG_INT4, ®_value); - - if (!(reg_value & (PM2XXX_INT4_S_ITVPWR1OVV | - PM2XXX_INT4_S_ITVPWR2OVV))) { - pm2->flags.ovv = false; - power_supply_changed(pm2->ac_chg.psy); - } - } - - /* If we still have a failure, schedule a new check */ - if (pm2->flags.ovv) { - queue_delayed_work(pm2->charger_wq, - &pm2->check_hw_failure_work, round_jiffies(HZ)); - } -} - -static void pm2xxx_charger_check_main_thermal_prot_work( - struct work_struct *work) -{ - int ret; - u8 val; - - struct pm2xxx_charger *pm2 = container_of(work, struct pm2xxx_charger, - check_main_thermal_prot_work); - - /* Check if die temp warning is still active */ - ret = pm2xxx_reg_read(pm2, PM2XXX_SRCE_REG_INT5, &val); - if (ret < 0) { - dev_err(pm2->dev, "%s pm2xxx read failed\n", __func__); - return; - } - if (val & (PM2XXX_INT5_S_ITTHERMALWARNINGRISE - | PM2XXX_INT5_S_ITTHERMALSHUTDOWNRISE)) - pm2->flags.main_thermal_prot = true; - else if (val & (PM2XXX_INT5_S_ITTHERMALWARNINGFALL - | PM2XXX_INT5_S_ITTHERMALSHUTDOWNFALL)) - pm2->flags.main_thermal_prot = false; - - power_supply_changed(pm2->ac_chg.psy); -} - -static struct pm2xxx_interrupts pm2xxx_int = { - .handler[0] = pm2_int_reg0, - .handler[1] = pm2_int_reg1, - .handler[2] = pm2_int_reg2, - .handler[3] = pm2_int_reg3, - .handler[4] = pm2_int_reg4, - .handler[5] = pm2_int_reg5, -}; - -static struct pm2xxx_irq pm2xxx_charger_irq[] = { - {"PM2XXX_IRQ_INT", pm2xxx_irq_int}, -}; - -static int __maybe_unused pm2xxx_wall_charger_resume(struct device *dev) -{ - struct i2c_client *i2c_client = to_i2c_client(dev); - struct pm2xxx_charger *pm2; - - pm2 = (struct pm2xxx_charger *)i2c_get_clientdata(i2c_client); - set_lpn_pin(pm2); - - /* If we still have a HW failure, schedule a new check */ - if (pm2->flags.ovv) - queue_delayed_work(pm2->charger_wq, - &pm2->check_hw_failure_work, 0); - - return 0; -} - -static int __maybe_unused pm2xxx_wall_charger_suspend(struct device *dev) -{ - struct i2c_client *i2c_client = to_i2c_client(dev); - struct pm2xxx_charger *pm2; - - pm2 = (struct pm2xxx_charger *)i2c_get_clientdata(i2c_client); - clear_lpn_pin(pm2); - - /* Cancel any pending HW failure check */ - if (delayed_work_pending(&pm2->check_hw_failure_work)) - cancel_delayed_work(&pm2->check_hw_failure_work); - - flush_work(&pm2->ac_work); - flush_work(&pm2->check_main_thermal_prot_work); - - return 0; -} - -static int __maybe_unused pm2xxx_runtime_suspend(struct device *dev) -{ - struct i2c_client *pm2xxx_i2c_client = to_i2c_client(dev); - struct pm2xxx_charger *pm2; - - pm2 = (struct pm2xxx_charger *)i2c_get_clientdata(pm2xxx_i2c_client); - clear_lpn_pin(pm2); - - return 0; -} - -static int __maybe_unused pm2xxx_runtime_resume(struct device *dev) -{ - struct i2c_client *pm2xxx_i2c_client = to_i2c_client(dev); - struct pm2xxx_charger *pm2; - - pm2 = (struct pm2xxx_charger *)i2c_get_clientdata(pm2xxx_i2c_client); - - if (gpio_is_valid(pm2->lpn_pin) && gpio_get_value(pm2->lpn_pin) == 0) - set_lpn_pin(pm2); - - return 0; -} - -static const struct dev_pm_ops pm2xxx_pm_ops __maybe_unused = { - SET_SYSTEM_SLEEP_PM_OPS(pm2xxx_wall_charger_suspend, - pm2xxx_wall_charger_resume) - SET_RUNTIME_PM_OPS(pm2xxx_runtime_suspend, pm2xxx_runtime_resume, NULL) -}; - -static int pm2xxx_wall_charger_probe(struct i2c_client *i2c_client, - const struct i2c_device_id *id) -{ - struct pm2xxx_platform_data *pl_data = i2c_client->dev.platform_data; - struct power_supply_config psy_cfg = {}; - struct pm2xxx_charger *pm2; - int ret = 0; - u8 val; - int i; - - if (!pl_data) { - dev_err(&i2c_client->dev, "No platform data supplied\n"); - return -EINVAL; - } - - pm2 = kzalloc(sizeof(struct pm2xxx_charger), GFP_KERNEL); - if (!pm2) { - dev_err(&i2c_client->dev, "pm2xxx_charger allocation failed\n"); - return -ENOMEM; - } - - /* get parent data */ - pm2->dev = &i2c_client->dev; - - pm2->pm2_int = &pm2xxx_int; - - /* get charger spcific platform data */ - if (!pl_data->wall_charger) { - dev_err(pm2->dev, "no charger platform data supplied\n"); - ret = -EINVAL; - goto free_device_info; - } - - pm2->pdata = pl_data->wall_charger; - - /* get battery specific platform data */ - if (!pl_data->battery) { - dev_err(pm2->dev, "no battery platform data supplied\n"); - ret = -EINVAL; - goto free_device_info; - } - - pm2->bat = pl_data->battery; - - if (!i2c_check_functionality(i2c_client->adapter, - I2C_FUNC_SMBUS_BYTE_DATA | - I2C_FUNC_SMBUS_READ_WORD_DATA)) { - ret = -ENODEV; - dev_info(pm2->dev, "pm2301 i2c_check_functionality failed\n"); - goto free_device_info; - } - - pm2->config.pm2xxx_i2c = i2c_client; - pm2->config.pm2xxx_id = (struct i2c_device_id *) id; - i2c_set_clientdata(i2c_client, pm2); - - /* AC supply */ - /* power_supply base class */ - pm2->ac_chg_desc.name = pm2->pdata->label; - pm2->ac_chg_desc.type = POWER_SUPPLY_TYPE_MAINS; - pm2->ac_chg_desc.properties = pm2xxx_charger_ac_props; - pm2->ac_chg_desc.num_properties = ARRAY_SIZE(pm2xxx_charger_ac_props); - pm2->ac_chg_desc.get_property = pm2xxx_charger_ac_get_property; - - psy_cfg.supplied_to = pm2->pdata->supplied_to; - psy_cfg.num_supplicants = pm2->pdata->num_supplicants; - /* pm2xxx_charger sub-class */ - pm2->ac_chg.ops.enable = &pm2xxx_charger_ac_en; - pm2->ac_chg.ops.kick_wd = &pm2xxx_charger_watchdog_kick; - pm2->ac_chg.ops.update_curr = &pm2xxx_charger_update_charger_current; - pm2->ac_chg.max_out_volt = pm2xxx_charger_voltage_map[ - ARRAY_SIZE(pm2xxx_charger_voltage_map) - 1]; - pm2->ac_chg.max_out_curr = pm2xxx_charger_current_map[ - ARRAY_SIZE(pm2xxx_charger_current_map) - 1]; - pm2->ac_chg.wdt_refresh = WD_KICK_INTERVAL; - pm2->ac_chg.enabled = true; - pm2->ac_chg.external = true; - - /* Create a work queue for the charger */ - pm2->charger_wq = create_singlethread_workqueue("pm2xxx_charger_wq"); - if (pm2->charger_wq == NULL) { - ret = -ENOMEM; - dev_err(pm2->dev, "failed to create work queue\n"); - goto free_device_info; - } - - /* Init work for charger detection */ - INIT_WORK(&pm2->ac_work, pm2xxx_charger_ac_work); - - /* Init work for checking HW status */ - INIT_WORK(&pm2->check_main_thermal_prot_work, - pm2xxx_charger_check_main_thermal_prot_work); - - /* Init work for HW failure check */ - INIT_DEFERRABLE_WORK(&pm2->check_hw_failure_work, - pm2xxx_charger_check_hw_failure_work); - - /* - * VDD ADC supply needs to be enabled from this driver when there - * is a charger connected to avoid erroneous BTEMP_HIGH/LOW - * interrupts during charging - */ - pm2->regu = regulator_get(pm2->dev, "vddadc"); - if (IS_ERR(pm2->regu)) { - ret = PTR_ERR(pm2->regu); - dev_err(pm2->dev, "failed to get vddadc regulator\n"); - goto free_charger_wq; - } - - /* Register AC charger class */ - pm2->ac_chg.psy = power_supply_register(pm2->dev, &pm2->ac_chg_desc, - &psy_cfg); - if (IS_ERR(pm2->ac_chg.psy)) { - dev_err(pm2->dev, "failed to register AC charger\n"); - ret = PTR_ERR(pm2->ac_chg.psy); - goto free_regulator; - } - - /* Register interrupts */ - ret = request_threaded_irq(gpio_to_irq(pm2->pdata->gpio_irq_number), - NULL, - pm2xxx_charger_irq[0].isr, - pm2->pdata->irq_type, - pm2xxx_charger_irq[0].name, pm2); - - if (ret != 0) { - dev_err(pm2->dev, "failed to request %s IRQ %d: %d\n", - pm2xxx_charger_irq[0].name, - gpio_to_irq(pm2->pdata->gpio_irq_number), ret); - goto unregister_pm2xxx_charger; - } - - ret = pm_runtime_set_active(pm2->dev); - if (ret) - dev_err(pm2->dev, "set active Error\n"); - - pm_runtime_enable(pm2->dev); - pm_runtime_set_autosuspend_delay(pm2->dev, PM2XXX_AUTOSUSPEND_DELAY); - pm_runtime_use_autosuspend(pm2->dev); - pm_runtime_resume(pm2->dev); - - /* pm interrupt can wake up system */ - ret = enable_irq_wake(gpio_to_irq(pm2->pdata->gpio_irq_number)); - if (ret) { - dev_err(pm2->dev, "failed to set irq wake\n"); - goto unregister_pm2xxx_interrupt; - } - - mutex_init(&pm2->lock); - - if (gpio_is_valid(pm2->pdata->lpn_gpio)) { - /* get lpn GPIO from platform data */ - pm2->lpn_pin = pm2->pdata->lpn_gpio; - - /* - * Charger detection mechanism requires pulling up the LPN pin - * while i2c communication if Charger is not connected - * LPN pin of PM2301 is GPIO60 of AB9540 - */ - ret = gpio_request(pm2->lpn_pin, "pm2301_lpm_gpio"); - - if (ret < 0) { - dev_err(pm2->dev, "pm2301_lpm_gpio request failed\n"); - goto disable_pm2_irq_wake; - } - ret = gpio_direction_output(pm2->lpn_pin, 0); - if (ret < 0) { - dev_err(pm2->dev, "pm2301_lpm_gpio direction failed\n"); - goto free_gpio; - } - set_lpn_pin(pm2); - } - - /* read interrupt registers */ - for (i = 0; i < PM2XXX_NUM_INT_REG; i++) - pm2xxx_reg_read(pm2, - pm2xxx_interrupt_registers[i], - &val); - - ret = pm2xxx_charger_detection(pm2, &val); - - if ((ret == 0) && val) { - pm2->ac.charger_connected = 1; - ab8500_override_turn_on_stat(~AB8500_POW_KEY_1_ON, - AB8500_MAIN_CH_DET); - pm2->ac_conn = true; - power_supply_changed(pm2->ac_chg.psy); - sysfs_notify(&pm2->ac_chg.psy->dev.kobj, NULL, "present"); - } - - return 0; - -free_gpio: - if (gpio_is_valid(pm2->lpn_pin)) - gpio_free(pm2->lpn_pin); -disable_pm2_irq_wake: - disable_irq_wake(gpio_to_irq(pm2->pdata->gpio_irq_number)); -unregister_pm2xxx_interrupt: - /* disable interrupt */ - free_irq(gpio_to_irq(pm2->pdata->gpio_irq_number), pm2); -unregister_pm2xxx_charger: - /* unregister power supply */ - power_supply_unregister(pm2->ac_chg.psy); -free_regulator: - /* disable the regulator */ - regulator_put(pm2->regu); -free_charger_wq: - destroy_workqueue(pm2->charger_wq); -free_device_info: - kfree(pm2); - - return ret; -} - -static int pm2xxx_wall_charger_remove(struct i2c_client *i2c_client) -{ - struct pm2xxx_charger *pm2 = i2c_get_clientdata(i2c_client); - - /* Disable pm_runtime */ - pm_runtime_disable(pm2->dev); - /* Disable AC charging */ - pm2xxx_charger_ac_en(&pm2->ac_chg, false, 0, 0); - - /* Disable wake by pm interrupt */ - disable_irq_wake(gpio_to_irq(pm2->pdata->gpio_irq_number)); - - /* Disable interrupts */ - free_irq(gpio_to_irq(pm2->pdata->gpio_irq_number), pm2); - - /* Delete the work queue */ - destroy_workqueue(pm2->charger_wq); - - flush_scheduled_work(); - - /* disable the regulator */ - regulator_put(pm2->regu); - - power_supply_unregister(pm2->ac_chg.psy); - - if (gpio_is_valid(pm2->lpn_pin)) - gpio_free(pm2->lpn_pin); - - kfree(pm2); - - return 0; -} - -static const struct i2c_device_id pm2xxx_id[] = { - { "pm2301", 0 }, - { } -}; - -MODULE_DEVICE_TABLE(i2c, pm2xxx_id); - -static struct i2c_driver pm2xxx_charger_driver = { - .probe = pm2xxx_wall_charger_probe, - .remove = pm2xxx_wall_charger_remove, - .driver = { - .name = "pm2xxx-wall_charger", - .pm = IS_ENABLED(CONFIG_PM) ? &pm2xxx_pm_ops : NULL, - }, - .id_table = pm2xxx_id, -}; - -static int __init pm2xxx_charger_init(void) -{ - return i2c_add_driver(&pm2xxx_charger_driver); -} - -static void __exit pm2xxx_charger_exit(void) -{ - i2c_del_driver(&pm2xxx_charger_driver); -} - -device_initcall_sync(pm2xxx_charger_init); -module_exit(pm2xxx_charger_exit); - -MODULE_LICENSE("GPL v2"); -MODULE_AUTHOR("Rajkumar kasirajan, Olivier Launay"); -MODULE_DESCRIPTION("PM2xxx charger management driver"); diff --git a/drivers/power/pm2301_charger.h b/drivers/power/pm2301_charger.h deleted file mode 100644 index 24181cf9717b..000000000000 --- a/drivers/power/pm2301_charger.h +++ /dev/null @@ -1,493 +0,0 @@ -/* - * Copyright (C) ST-Ericsson SA 2012 - * - * PM2301 power supply interface - * - * License terms: GNU General Public License (GPL), version 2 - */ - -#ifndef PM2301_CHARGER_H -#define PM2301_CHARGER_H - -/* Watchdog timeout constant */ -#define WD_TIMER 0x30 /* 4min */ -#define WD_KICK_INTERVAL (30 * HZ) - -#define PM2XXX_NUM_INT_REG 0x6 - -/* Constant voltage/current */ -#define PM2XXX_CONST_CURR 0x0 -#define PM2XXX_CONST_VOLT 0x1 - -/* Lowest charger voltage is 3.39V -> 0x4E */ -#define LOW_VOLT_REG 0x4E - -#define PM2XXX_BATT_CTRL_REG1 0x00 -#define PM2XXX_BATT_CTRL_REG2 0x01 -#define PM2XXX_BATT_CTRL_REG3 0x02 -#define PM2XXX_BATT_CTRL_REG4 0x03 -#define PM2XXX_BATT_CTRL_REG5 0x04 -#define PM2XXX_BATT_CTRL_REG6 0x05 -#define PM2XXX_BATT_CTRL_REG7 0x06 -#define PM2XXX_BATT_CTRL_REG8 0x07 -#define PM2XXX_NTC_CTRL_REG1 0x08 -#define PM2XXX_NTC_CTRL_REG2 0x09 -#define PM2XXX_BATT_CTRL_REG9 0x0A -#define PM2XXX_BATT_STAT_REG1 0x0B -#define PM2XXX_INP_VOLT_VPWR2 0x11 -#define PM2XXX_INP_DROP_VPWR2 0x13 -#define PM2XXX_INP_VOLT_VPWR1 0x15 -#define PM2XXX_INP_DROP_VPWR1 0x17 -#define PM2XXX_INP_MODE_VPWR 0x18 -#define PM2XXX_BATT_WD_KICK 0x70 -#define PM2XXX_DEV_VER_STAT 0x0C -#define PM2XXX_THERM_WARN_CTRL_REG 0x20 -#define PM2XXX_BATT_DISC_REG 0x21 -#define PM2XXX_BATT_LOW_LEV_COMP_REG 0x22 -#define PM2XXX_BATT_LOW_LEV_VAL_REG 0x23 -#define PM2XXX_I2C_PAD_CTRL_REG 0x24 -#define PM2XXX_SW_CTRL_REG 0x26 -#define PM2XXX_LED_CTRL_REG 0x28 - -#define PM2XXX_REG_INT1 0x40 -#define PM2XXX_MASK_REG_INT1 0x50 -#define PM2XXX_SRCE_REG_INT1 0x60 -#define PM2XXX_REG_INT2 0x41 -#define PM2XXX_MASK_REG_INT2 0x51 -#define PM2XXX_SRCE_REG_INT2 0x61 -#define PM2XXX_REG_INT3 0x42 -#define PM2XXX_MASK_REG_INT3 0x52 -#define PM2XXX_SRCE_REG_INT3 0x62 -#define PM2XXX_REG_INT4 0x43 -#define PM2XXX_MASK_REG_INT4 0x53 -#define PM2XXX_SRCE_REG_INT4 0x63 -#define PM2XXX_REG_INT5 0x44 -#define PM2XXX_MASK_REG_INT5 0x54 -#define PM2XXX_SRCE_REG_INT5 0x64 -#define PM2XXX_REG_INT6 0x45 -#define PM2XXX_MASK_REG_INT6 0x55 -#define PM2XXX_SRCE_REG_INT6 0x65 - -#define VPWR_OVV 0x0 -#define VSYSTEM_OVV 0x1 - -/* control Reg 1 */ -#define PM2XXX_CH_RESUME_EN 0x1 -#define PM2XXX_CH_RESUME_DIS 0x0 - -/* control Reg 2 */ -#define PM2XXX_CH_AUTO_RESUME_EN 0X2 -#define PM2XXX_CH_AUTO_RESUME_DIS 0X0 -#define PM2XXX_CHARGER_ENA 0x4 -#define PM2XXX_CHARGER_DIS 0x0 - -/* control Reg 3 */ -#define PM2XXX_CH_WD_CC_PHASE_OFF 0x0 -#define PM2XXX_CH_WD_CC_PHASE_5MIN 0x1 -#define PM2XXX_CH_WD_CC_PHASE_10MIN 0x2 -#define PM2XXX_CH_WD_CC_PHASE_30MIN 0x3 -#define PM2XXX_CH_WD_CC_PHASE_60MIN 0x4 -#define PM2XXX_CH_WD_CC_PHASE_120MIN 0x5 -#define PM2XXX_CH_WD_CC_PHASE_240MIN 0x6 -#define PM2XXX_CH_WD_CC_PHASE_360MIN 0x7 - -#define PM2XXX_CH_WD_CV_PHASE_OFF (0x0<<3) -#define PM2XXX_CH_WD_CV_PHASE_5MIN (0x1<<3) -#define PM2XXX_CH_WD_CV_PHASE_10MIN (0x2<<3) -#define PM2XXX_CH_WD_CV_PHASE_30MIN (0x3<<3) -#define PM2XXX_CH_WD_CV_PHASE_60MIN (0x4<<3) -#define PM2XXX_CH_WD_CV_PHASE_120MIN (0x5<<3) -#define PM2XXX_CH_WD_CV_PHASE_240MIN (0x6<<3) -#define PM2XXX_CH_WD_CV_PHASE_360MIN (0x7<<3) - -/* control Reg 4 */ -#define PM2XXX_CH_WD_PRECH_PHASE_OFF 0x0 -#define PM2XXX_CH_WD_PRECH_PHASE_1MIN 0x1 -#define PM2XXX_CH_WD_PRECH_PHASE_5MIN 0x2 -#define PM2XXX_CH_WD_PRECH_PHASE_10MIN 0x3 -#define PM2XXX_CH_WD_PRECH_PHASE_30MIN 0x4 -#define PM2XXX_CH_WD_PRECH_PHASE_60MIN 0x5 -#define PM2XXX_CH_WD_PRECH_PHASE_120MIN 0x6 -#define PM2XXX_CH_WD_PRECH_PHASE_240MIN 0x7 - -/* control Reg 5 */ -#define PM2XXX_CH_WD_AUTO_TIMEOUT_NONE 0x0 -#define PM2XXX_CH_WD_AUTO_TIMEOUT_20MIN 0x1 - -/* control Reg 6 */ -#define PM2XXX_DIR_CH_CC_CURRENT_MASK 0x0F -#define PM2XXX_DIR_CH_CC_CURRENT_200MA 0x0 -#define PM2XXX_DIR_CH_CC_CURRENT_400MA 0x2 -#define PM2XXX_DIR_CH_CC_CURRENT_600MA 0x3 -#define PM2XXX_DIR_CH_CC_CURRENT_800MA 0x4 -#define PM2XXX_DIR_CH_CC_CURRENT_1000MA 0x5 -#define PM2XXX_DIR_CH_CC_CURRENT_1200MA 0x6 -#define PM2XXX_DIR_CH_CC_CURRENT_1400MA 0x7 -#define PM2XXX_DIR_CH_CC_CURRENT_1600MA 0x8 -#define PM2XXX_DIR_CH_CC_CURRENT_1800MA 0x9 -#define PM2XXX_DIR_CH_CC_CURRENT_2000MA 0xA -#define PM2XXX_DIR_CH_CC_CURRENT_2200MA 0xB -#define PM2XXX_DIR_CH_CC_CURRENT_2400MA 0xC -#define PM2XXX_DIR_CH_CC_CURRENT_2600MA 0xD -#define PM2XXX_DIR_CH_CC_CURRENT_2800MA 0xE -#define PM2XXX_DIR_CH_CC_CURRENT_3000MA 0xF - -#define PM2XXX_CH_PRECH_CURRENT_MASK 0x30 -#define PM2XXX_CH_PRECH_CURRENT_25MA (0x0<<4) -#define PM2XXX_CH_PRECH_CURRENT_50MA (0x1<<4) -#define PM2XXX_CH_PRECH_CURRENT_75MA (0x2<<4) -#define PM2XXX_CH_PRECH_CURRENT_100MA (0x3<<4) - -#define PM2XXX_CH_EOC_CURRENT_MASK 0xC0 -#define PM2XXX_CH_EOC_CURRENT_100MA (0x0<<6) -#define PM2XXX_CH_EOC_CURRENT_150MA (0x1<<6) -#define PM2XXX_CH_EOC_CURRENT_300MA (0x2<<6) -#define PM2XXX_CH_EOC_CURRENT_400MA (0x3<<6) - -/* control Reg 7 */ -#define PM2XXX_CH_PRECH_VOL_2_5 0x0 -#define PM2XXX_CH_PRECH_VOL_2_7 0x1 -#define PM2XXX_CH_PRECH_VOL_2_9 0x2 -#define PM2XXX_CH_PRECH_VOL_3_1 0x3 - -#define PM2XXX_CH_VRESUME_VOL_3_2 (0x0<<2) -#define PM2XXX_CH_VRESUME_VOL_3_4 (0x1<<2) -#define PM2XXX_CH_VRESUME_VOL_3_6 (0x2<<2) -#define PM2XXX_CH_VRESUME_VOL_3_8 (0x3<<2) - -/* control Reg 8 */ -#define PM2XXX_CH_VOLT_MASK 0x3F -#define PM2XXX_CH_VOLT_3_5 0x0 -#define PM2XXX_CH_VOLT_3_5225 0x1 -#define PM2XXX_CH_VOLT_3_6 0x4 -#define PM2XXX_CH_VOLT_3_7 0x8 -#define PM2XXX_CH_VOLT_4_0 0x14 -#define PM2XXX_CH_VOLT_4_175 0x1B -#define PM2XXX_CH_VOLT_4_2 0x1C -#define PM2XXX_CH_VOLT_4_275 0x1F -#define PM2XXX_CH_VOLT_4_3 0x20 - -/*NTC control register 1*/ -#define PM2XXX_BTEMP_HIGH_TH_45 0x0 -#define PM2XXX_BTEMP_HIGH_TH_50 0x1 -#define PM2XXX_BTEMP_HIGH_TH_55 0x2 -#define PM2XXX_BTEMP_HIGH_TH_60 0x3 -#define PM2XXX_BTEMP_HIGH_TH_65 0x4 - -#define PM2XXX_BTEMP_LOW_TH_N5 (0x0<<3) -#define PM2XXX_BTEMP_LOW_TH_0 (0x1<<3) -#define PM2XXX_BTEMP_LOW_TH_5 (0x2<<3) -#define PM2XXX_BTEMP_LOW_TH_10 (0x3<<3) - -/*NTC control register 2*/ -#define PM2XXX_NTC_BETA_COEFF_3477 0x0 -#define PM2XXX_NTC_BETA_COEFF_3964 0x1 - -#define PM2XXX_NTC_RES_10K (0x0<<2) -#define PM2XXX_NTC_RES_47K (0x1<<2) -#define PM2XXX_NTC_RES_100K (0x2<<2) -#define PM2XXX_NTC_RES_NO_NTC (0x3<<2) - -/* control Reg 9 */ -#define PM2XXX_CH_CC_MODEDROP_EN 1 -#define PM2XXX_CH_CC_MODEDROP_DIS 0 - -#define PM2XXX_CH_CC_REDUCED_CURRENT_100MA (0x0<<1) -#define PM2XXX_CH_CC_REDUCED_CURRENT_200MA (0x1<<1) -#define PM2XXX_CH_CC_REDUCED_CURRENT_400MA (0x2<<1) -#define PM2XXX_CH_CC_REDUCED_CURRENT_IDENT (0x3<<1) - -#define PM2XXX_CHARCHING_INFO_DIS (0<<3) -#define PM2XXX_CHARCHING_INFO_EN (1<<3) - -#define PM2XXX_CH_150MV_DROP_300MV (0<<4) -#define PM2XXX_CH_150MV_DROP_150MV (1<<4) - - -/* charger status register */ -#define PM2XXX_CHG_STATUS_OFF 0x0 -#define PM2XXX_CHG_STATUS_ON 0x1 -#define PM2XXX_CHG_STATUS_FULL 0x2 -#define PM2XXX_CHG_STATUS_ERR 0x3 -#define PM2XXX_CHG_STATUS_WAIT 0x4 -#define PM2XXX_CHG_STATUS_NOBAT 0x5 - -/* Input charger voltage VPWR2 */ -#define PM2XXX_VPWR2_OVV_6_0 0x0 -#define PM2XXX_VPWR2_OVV_6_3 0x1 -#define PM2XXX_VPWR2_OVV_10 0x2 -#define PM2XXX_VPWR2_OVV_NONE 0x3 - -/* Input charger drop VPWR2 */ -#define PM2XXX_VPWR2_HW_OPT_EN (0x1<<4) -#define PM2XXX_VPWR2_HW_OPT_DIS (0x0<<4) - -#define PM2XXX_VPWR2_VALID_EN (0x1<<3) -#define PM2XXX_VPWR2_VALID_DIS (0x0<<3) - -#define PM2XXX_VPWR2_DROP_EN (0x1<<2) -#define PM2XXX_VPWR2_DROP_DIS (0x0<<2) - -/* Input charger voltage VPWR1 */ -#define PM2XXX_VPWR1_OVV_6_0 0x0 -#define PM2XXX_VPWR1_OVV_6_3 0x1 -#define PM2XXX_VPWR1_OVV_10 0x2 -#define PM2XXX_VPWR1_OVV_NONE 0x3 - -/* Input charger drop VPWR1 */ -#define PM2XXX_VPWR1_HW_OPT_EN (0x1<<4) -#define PM2XXX_VPWR1_HW_OPT_DIS (0x0<<4) - -#define PM2XXX_VPWR1_VALID_EN (0x1<<3) -#define PM2XXX_VPWR1_VALID_DIS (0x0<<3) - -#define PM2XXX_VPWR1_DROP_EN (0x1<<2) -#define PM2XXX_VPWR1_DROP_DIS (0x0<<2) - -/* Battery low level comparator control register */ -#define PM2XXX_VBAT_LOW_MONITORING_DIS 0x0 -#define PM2XXX_VBAT_LOW_MONITORING_ENA 0x1 - -/* Battery low level value control register */ -#define PM2XXX_VBAT_LOW_LEVEL_2_3 0x0 -#define PM2XXX_VBAT_LOW_LEVEL_2_4 0x1 -#define PM2XXX_VBAT_LOW_LEVEL_2_5 0x2 -#define PM2XXX_VBAT_LOW_LEVEL_2_6 0x3 -#define PM2XXX_VBAT_LOW_LEVEL_2_7 0x4 -#define PM2XXX_VBAT_LOW_LEVEL_2_8 0x5 -#define PM2XXX_VBAT_LOW_LEVEL_2_9 0x6 -#define PM2XXX_VBAT_LOW_LEVEL_3_0 0x7 -#define PM2XXX_VBAT_LOW_LEVEL_3_1 0x8 -#define PM2XXX_VBAT_LOW_LEVEL_3_2 0x9 -#define PM2XXX_VBAT_LOW_LEVEL_3_3 0xA -#define PM2XXX_VBAT_LOW_LEVEL_3_4 0xB -#define PM2XXX_VBAT_LOW_LEVEL_3_5 0xC -#define PM2XXX_VBAT_LOW_LEVEL_3_6 0xD -#define PM2XXX_VBAT_LOW_LEVEL_3_7 0xE -#define PM2XXX_VBAT_LOW_LEVEL_3_8 0xF -#define PM2XXX_VBAT_LOW_LEVEL_3_9 0x10 -#define PM2XXX_VBAT_LOW_LEVEL_4_0 0x11 -#define PM2XXX_VBAT_LOW_LEVEL_4_1 0x12 -#define PM2XXX_VBAT_LOW_LEVEL_4_2 0x13 - -/* SW CTRL */ -#define PM2XXX_SWCTRL_HW 0x0 -#define PM2XXX_SWCTRL_SW 0x1 - - -/* LED Driver Control */ -#define PM2XXX_LED_CURRENT_MASK 0x0C -#define PM2XXX_LED_CURRENT_2_5MA (0X0<<2) -#define PM2XXX_LED_CURRENT_1MA (0X1<<2) -#define PM2XXX_LED_CURRENT_5MA (0X2<<2) -#define PM2XXX_LED_CURRENT_10MA (0X3<<2) - -#define PM2XXX_LED_SELECT_MASK 0x02 -#define PM2XXX_LED_SELECT_EN (0X0<<1) -#define PM2XXX_LED_SELECT_DIS (0X1<<1) - -#define PM2XXX_ANTI_OVERSHOOT_MASK 0x01 -#define PM2XXX_ANTI_OVERSHOOT_DIS 0X0 -#define PM2XXX_ANTI_OVERSHOOT_EN 0X1 - -enum pm2xxx_reg_int1 { - PM2XXX_INT1_ITVBATDISCONNECT = 0x02, - PM2XXX_INT1_ITVBATLOWR = 0x04, - PM2XXX_INT1_ITVBATLOWF = 0x08, -}; - -enum pm2xxx_mask_reg_int1 { - PM2XXX_INT1_M_ITVBATDISCONNECT = 0x02, - PM2XXX_INT1_M_ITVBATLOWR = 0x04, - PM2XXX_INT1_M_ITVBATLOWF = 0x08, -}; - -enum pm2xxx_source_reg_int1 { - PM2XXX_INT1_S_ITVBATDISCONNECT = 0x02, - PM2XXX_INT1_S_ITVBATLOWR = 0x04, - PM2XXX_INT1_S_ITVBATLOWF = 0x08, -}; - -enum pm2xxx_reg_int2 { - PM2XXX_INT2_ITVPWR2PLUG = 0x01, - PM2XXX_INT2_ITVPWR2UNPLUG = 0x02, - PM2XXX_INT2_ITVPWR1PLUG = 0x04, - PM2XXX_INT2_ITVPWR1UNPLUG = 0x08, -}; - -enum pm2xxx_mask_reg_int2 { - PM2XXX_INT2_M_ITVPWR2PLUG = 0x01, - PM2XXX_INT2_M_ITVPWR2UNPLUG = 0x02, - PM2XXX_INT2_M_ITVPWR1PLUG = 0x04, - PM2XXX_INT2_M_ITVPWR1UNPLUG = 0x08, -}; - -enum pm2xxx_source_reg_int2 { - PM2XXX_INT2_S_ITVPWR2PLUG = 0x03, - PM2XXX_INT2_S_ITVPWR1PLUG = 0x0c, -}; - -enum pm2xxx_reg_int3 { - PM2XXX_INT3_ITCHPRECHARGEWD = 0x01, - PM2XXX_INT3_ITCHCCWD = 0x02, - PM2XXX_INT3_ITCHCVWD = 0x04, - PM2XXX_INT3_ITAUTOTIMEOUTWD = 0x08, -}; - -enum pm2xxx_mask_reg_int3 { - PM2XXX_INT3_M_ITCHPRECHARGEWD = 0x01, - PM2XXX_INT3_M_ITCHCCWD = 0x02, - PM2XXX_INT3_M_ITCHCVWD = 0x04, - PM2XXX_INT3_M_ITAUTOTIMEOUTWD = 0x08, -}; - -enum pm2xxx_source_reg_int3 { - PM2XXX_INT3_S_ITCHPRECHARGEWD = 0x01, - PM2XXX_INT3_S_ITCHCCWD = 0x02, - PM2XXX_INT3_S_ITCHCVWD = 0x04, - PM2XXX_INT3_S_ITAUTOTIMEOUTWD = 0x08, -}; - -enum pm2xxx_reg_int4 { - PM2XXX_INT4_ITBATTEMPCOLD = 0x01, - PM2XXX_INT4_ITBATTEMPHOT = 0x02, - PM2XXX_INT4_ITVPWR2OVV = 0x04, - PM2XXX_INT4_ITVPWR1OVV = 0x08, - PM2XXX_INT4_ITCHARGINGON = 0x10, - PM2XXX_INT4_ITVRESUME = 0x20, - PM2XXX_INT4_ITBATTFULL = 0x40, - PM2XXX_INT4_ITCVPHASE = 0x80, -}; - -enum pm2xxx_mask_reg_int4 { - PM2XXX_INT4_M_ITBATTEMPCOLD = 0x01, - PM2XXX_INT4_M_ITBATTEMPHOT = 0x02, - PM2XXX_INT4_M_ITVPWR2OVV = 0x04, - PM2XXX_INT4_M_ITVPWR1OVV = 0x08, - PM2XXX_INT4_M_ITCHARGINGON = 0x10, - PM2XXX_INT4_M_ITVRESUME = 0x20, - PM2XXX_INT4_M_ITBATTFULL = 0x40, - PM2XXX_INT4_M_ITCVPHASE = 0x80, -}; - -enum pm2xxx_source_reg_int4 { - PM2XXX_INT4_S_ITBATTEMPCOLD = 0x01, - PM2XXX_INT4_S_ITBATTEMPHOT = 0x02, - PM2XXX_INT4_S_ITVPWR2OVV = 0x04, - PM2XXX_INT4_S_ITVPWR1OVV = 0x08, - PM2XXX_INT4_S_ITCHARGINGON = 0x10, - PM2XXX_INT4_S_ITVRESUME = 0x20, - PM2XXX_INT4_S_ITBATTFULL = 0x40, - PM2XXX_INT4_S_ITCVPHASE = 0x80, -}; - -enum pm2xxx_reg_int5 { - PM2XXX_INT5_ITTHERMALSHUTDOWNRISE = 0x01, - PM2XXX_INT5_ITTHERMALSHUTDOWNFALL = 0x02, - PM2XXX_INT5_ITTHERMALWARNINGRISE = 0x04, - PM2XXX_INT5_ITTHERMALWARNINGFALL = 0x08, - PM2XXX_INT5_ITVSYSTEMOVV = 0x10, -}; - -enum pm2xxx_mask_reg_int5 { - PM2XXX_INT5_M_ITTHERMALSHUTDOWNRISE = 0x01, - PM2XXX_INT5_M_ITTHERMALSHUTDOWNFALL = 0x02, - PM2XXX_INT5_M_ITTHERMALWARNINGRISE = 0x04, - PM2XXX_INT5_M_ITTHERMALWARNINGFALL = 0x08, - PM2XXX_INT5_M_ITVSYSTEMOVV = 0x10, -}; - -enum pm2xxx_source_reg_int5 { - PM2XXX_INT5_S_ITTHERMALSHUTDOWNRISE = 0x01, - PM2XXX_INT5_S_ITTHERMALSHUTDOWNFALL = 0x02, - PM2XXX_INT5_S_ITTHERMALWARNINGRISE = 0x04, - PM2XXX_INT5_S_ITTHERMALWARNINGFALL = 0x08, - PM2XXX_INT5_S_ITVSYSTEMOVV = 0x10, -}; - -enum pm2xxx_reg_int6 { - PM2XXX_INT6_ITVPWR2DROP = 0x01, - PM2XXX_INT6_ITVPWR1DROP = 0x02, - PM2XXX_INT6_ITVPWR2VALIDRISE = 0x04, - PM2XXX_INT6_ITVPWR2VALIDFALL = 0x08, - PM2XXX_INT6_ITVPWR1VALIDRISE = 0x10, - PM2XXX_INT6_ITVPWR1VALIDFALL = 0x20, -}; - -enum pm2xxx_mask_reg_int6 { - PM2XXX_INT6_M_ITVPWR2DROP = 0x01, - PM2XXX_INT6_M_ITVPWR1DROP = 0x02, - PM2XXX_INT6_M_ITVPWR2VALIDRISE = 0x04, - PM2XXX_INT6_M_ITVPWR2VALIDFALL = 0x08, - PM2XXX_INT6_M_ITVPWR1VALIDRISE = 0x10, - PM2XXX_INT6_M_ITVPWR1VALIDFALL = 0x20, -}; - -enum pm2xxx_source_reg_int6 { - PM2XXX_INT6_S_ITVPWR2DROP = 0x01, - PM2XXX_INT6_S_ITVPWR1DROP = 0x02, - PM2XXX_INT6_S_ITVPWR2VALIDRISE = 0x04, - PM2XXX_INT6_S_ITVPWR2VALIDFALL = 0x08, - PM2XXX_INT6_S_ITVPWR1VALIDRISE = 0x10, - PM2XXX_INT6_S_ITVPWR1VALIDFALL = 0x20, -}; - -struct pm2xxx_charger_info { - int charger_connected; - int charger_online; - int cv_active; - bool wd_expired; -}; - -struct pm2xxx_charger_event_flags { - bool mainextchnotok; - bool main_thermal_prot; - bool ovv; - bool chgwdexp; -}; - -struct pm2xxx_interrupts { - u8 reg[PM2XXX_NUM_INT_REG]; - int (*handler[PM2XXX_NUM_INT_REG])(void *, int); -}; - -struct pm2xxx_config { - struct i2c_client *pm2xxx_i2c; - struct i2c_device_id *pm2xxx_id; -}; - -struct pm2xxx_irq { - char *name; - irqreturn_t (*isr)(int irq, void *data); -}; - -struct pm2xxx_charger { - struct device *dev; - u8 chip_id; - bool vddadc_en_ac; - struct pm2xxx_config config; - bool ac_conn; - unsigned int gpio_irq; - int vbat; - int old_vbat; - int failure_case; - int failure_input_ovv; - unsigned int lpn_pin; - struct pm2xxx_interrupts *pm2_int; - struct regulator *regu; - struct pm2xxx_bm_data *bat; - struct mutex lock; - struct ab8500 *parent; - struct pm2xxx_charger_info ac; - struct pm2xxx_charger_platform_data *pdata; - struct workqueue_struct *charger_wq; - struct delayed_work check_vbat_work; - struct work_struct ac_work; - struct work_struct check_main_thermal_prot_work; - struct delayed_work check_hw_failure_work; - struct ux500_charger ac_chg; - struct power_supply_desc ac_chg_desc; - struct pm2xxx_charger_event_flags flags; -}; - -#endif /* PM2301_CHARGER_H */ diff --git a/drivers/power/pmu_battery.c b/drivers/power/pmu_battery.c deleted file mode 100644 index 9c8d5253812c..000000000000 --- a/drivers/power/pmu_battery.c +++ /dev/null @@ -1,226 +0,0 @@ -/* - * Battery class driver for Apple PMU - * - * Copyright © 2006 David Woodhouse - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - */ - -#include -#include -#include -#include -#include -#include -#include - -static struct pmu_battery_dev { - struct power_supply *bat; - struct power_supply_desc bat_desc; - struct pmu_battery_info *pbi; - char name[16]; - int propval; -} *pbats[PMU_MAX_BATTERIES]; - -#define to_pmu_battery_dev(x) power_supply_get_drvdata(x) - -/********************************************************************* - * Power - *********************************************************************/ - -static int pmu_get_ac_prop(struct power_supply *psy, - enum power_supply_property psp, - union power_supply_propval *val) -{ - switch (psp) { - case POWER_SUPPLY_PROP_ONLINE: - val->intval = (!!(pmu_power_flags & PMU_PWR_AC_PRESENT)) || - (pmu_battery_count == 0); - break; - default: - return -EINVAL; - } - - return 0; -} - -static enum power_supply_property pmu_ac_props[] = { - POWER_SUPPLY_PROP_ONLINE, -}; - -static const struct power_supply_desc pmu_ac_desc = { - .name = "pmu-ac", - .type = POWER_SUPPLY_TYPE_MAINS, - .properties = pmu_ac_props, - .num_properties = ARRAY_SIZE(pmu_ac_props), - .get_property = pmu_get_ac_prop, -}; - -static struct power_supply *pmu_ac; - -/********************************************************************* - * Battery properties - *********************************************************************/ - -static char *pmu_batt_types[] = { - "Smart", "Comet", "Hooper", "Unknown" -}; - -static char *pmu_bat_get_model_name(struct pmu_battery_info *pbi) -{ - switch (pbi->flags & PMU_BATT_TYPE_MASK) { - case PMU_BATT_TYPE_SMART: - return pmu_batt_types[0]; - case PMU_BATT_TYPE_COMET: - return pmu_batt_types[1]; - case PMU_BATT_TYPE_HOOPER: - return pmu_batt_types[2]; - default: break; - } - return pmu_batt_types[3]; -} - -static int pmu_bat_get_property(struct power_supply *psy, - enum power_supply_property psp, - union power_supply_propval *val) -{ - struct pmu_battery_dev *pbat = to_pmu_battery_dev(psy); - struct pmu_battery_info *pbi = pbat->pbi; - - switch (psp) { - case POWER_SUPPLY_PROP_STATUS: - if (pbi->flags & PMU_BATT_CHARGING) - val->intval = POWER_SUPPLY_STATUS_CHARGING; - else if (pmu_power_flags & PMU_PWR_AC_PRESENT) - val->intval = POWER_SUPPLY_STATUS_FULL; - else - val->intval = POWER_SUPPLY_STATUS_DISCHARGING; - break; - case POWER_SUPPLY_PROP_PRESENT: - val->intval = !!(pbi->flags & PMU_BATT_PRESENT); - break; - case POWER_SUPPLY_PROP_MODEL_NAME: - val->strval = pmu_bat_get_model_name(pbi); - break; - case POWER_SUPPLY_PROP_ENERGY_AVG: - val->intval = pbi->charge * 1000; /* mWh -> µWh */ - break; - case POWER_SUPPLY_PROP_ENERGY_FULL: - val->intval = pbi->max_charge * 1000; /* mWh -> µWh */ - break; - case POWER_SUPPLY_PROP_CURRENT_AVG: - val->intval = pbi->amperage * 1000; /* mA -> µA */ - break; - case POWER_SUPPLY_PROP_VOLTAGE_AVG: - val->intval = pbi->voltage * 1000; /* mV -> µV */ - break; - case POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG: - val->intval = pbi->time_remaining; - break; - default: - return -EINVAL; - } - - return 0; -} - -static enum power_supply_property pmu_bat_props[] = { - POWER_SUPPLY_PROP_STATUS, - POWER_SUPPLY_PROP_PRESENT, - POWER_SUPPLY_PROP_MODEL_NAME, - POWER_SUPPLY_PROP_ENERGY_AVG, - POWER_SUPPLY_PROP_ENERGY_FULL, - POWER_SUPPLY_PROP_CURRENT_AVG, - POWER_SUPPLY_PROP_VOLTAGE_AVG, - POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG, -}; - -/********************************************************************* - * Initialisation - *********************************************************************/ - -static struct platform_device *bat_pdev; - -static int __init pmu_bat_init(void) -{ - int ret = 0; - int i; - - bat_pdev = platform_device_register_simple("pmu-battery", - 0, NULL, 0); - if (IS_ERR(bat_pdev)) { - ret = PTR_ERR(bat_pdev); - goto pdev_register_failed; - } - - pmu_ac = power_supply_register(&bat_pdev->dev, &pmu_ac_desc, NULL); - if (IS_ERR(pmu_ac)) { - ret = PTR_ERR(pmu_ac); - goto ac_register_failed; - } - - for (i = 0; i < pmu_battery_count; i++) { - struct power_supply_config psy_cfg = {}; - struct pmu_battery_dev *pbat = kzalloc(sizeof(*pbat), - GFP_KERNEL); - if (!pbat) - break; - - sprintf(pbat->name, "PMU_battery_%d", i); - pbat->bat_desc.name = pbat->name; - pbat->bat_desc.properties = pmu_bat_props; - pbat->bat_desc.num_properties = ARRAY_SIZE(pmu_bat_props); - pbat->bat_desc.get_property = pmu_bat_get_property; - pbat->pbi = &pmu_batteries[i]; - psy_cfg.drv_data = pbat; - - pbat->bat = power_supply_register(&bat_pdev->dev, - &pbat->bat_desc, - &psy_cfg); - if (IS_ERR(pbat->bat)) { - ret = PTR_ERR(pbat->bat); - kfree(pbat); - goto battery_register_failed; - } - pbats[i] = pbat; - } - - goto success; - -battery_register_failed: - while (i--) { - if (!pbats[i]) - continue; - power_supply_unregister(pbats[i]->bat); - kfree(pbats[i]); - } - power_supply_unregister(pmu_ac); -ac_register_failed: - platform_device_unregister(bat_pdev); -pdev_register_failed: -success: - return ret; -} - -static void __exit pmu_bat_exit(void) -{ - int i; - - for (i = 0; i < PMU_MAX_BATTERIES; i++) { - if (!pbats[i]) - continue; - power_supply_unregister(pbats[i]->bat); - kfree(pbats[i]); - } - power_supply_unregister(pmu_ac); - platform_device_unregister(bat_pdev); -} - -module_init(pmu_bat_init); -module_exit(pmu_bat_exit); - -MODULE_AUTHOR("David Woodhouse "); -MODULE_LICENSE("GPL"); -MODULE_DESCRIPTION("PMU battery driver"); diff --git a/drivers/power/power_supply.h b/drivers/power/power_supply.h deleted file mode 100644 index cc439fd89d8d..000000000000 --- a/drivers/power/power_supply.h +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Functions private to power supply class - * - * Copyright © 2007 Anton Vorontsov - * Copyright © 2004 Szabolcs Gyurko - * Copyright © 2003 Ian Molton - * - * Modified: 2004, Oct Szabolcs Gyurko - * - * You may use this code as per GPL version 2 - */ - -struct device; -struct device_type; -struct power_supply; - -#ifdef CONFIG_SYSFS - -extern void power_supply_init_attrs(struct device_type *dev_type); -extern int power_supply_uevent(struct device *dev, struct kobj_uevent_env *env); - -#else - -static inline void power_supply_init_attrs(struct device_type *dev_type) {} -#define power_supply_uevent NULL - -#endif /* CONFIG_SYSFS */ - -#ifdef CONFIG_LEDS_TRIGGERS - -extern void power_supply_update_leds(struct power_supply *psy); -extern int power_supply_create_triggers(struct power_supply *psy); -extern void power_supply_remove_triggers(struct power_supply *psy); - -#else - -static inline void power_supply_update_leds(struct power_supply *psy) {} -static inline int power_supply_create_triggers(struct power_supply *psy) -{ return 0; } -static inline void power_supply_remove_triggers(struct power_supply *psy) {} - -#endif /* CONFIG_LEDS_TRIGGERS */ diff --git a/drivers/power/power_supply_core.c b/drivers/power/power_supply_core.c deleted file mode 100644 index a74d8ca383a1..000000000000 --- a/drivers/power/power_supply_core.c +++ /dev/null @@ -1,989 +0,0 @@ -/* - * Universal power supply monitor class - * - * Copyright © 2007 Anton Vorontsov - * Copyright © 2004 Szabolcs Gyurko - * Copyright © 2003 Ian Molton - * - * Modified: 2004, Oct Szabolcs Gyurko - * - * You may use this code as per GPL version 2 - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "power_supply.h" - -/* exported for the APM Power driver, APM emulation */ -struct class *power_supply_class; -EXPORT_SYMBOL_GPL(power_supply_class); - -ATOMIC_NOTIFIER_HEAD(power_supply_notifier); -EXPORT_SYMBOL_GPL(power_supply_notifier); - -static struct device_type power_supply_dev_type; - -#define POWER_SUPPLY_DEFERRED_REGISTER_TIME msecs_to_jiffies(10) - -static bool __power_supply_is_supplied_by(struct power_supply *supplier, - struct power_supply *supply) -{ - int i; - - if (!supply->supplied_from && !supplier->supplied_to) - return false; - - /* Support both supplied_to and supplied_from modes */ - if (supply->supplied_from) { - if (!supplier->desc->name) - return false; - for (i = 0; i < supply->num_supplies; i++) - if (!strcmp(supplier->desc->name, supply->supplied_from[i])) - return true; - } else { - if (!supply->desc->name) - return false; - for (i = 0; i < supplier->num_supplicants; i++) - if (!strcmp(supplier->supplied_to[i], supply->desc->name)) - return true; - } - - return false; -} - -static int __power_supply_changed_work(struct device *dev, void *data) -{ - struct power_supply *psy = data; - struct power_supply *pst = dev_get_drvdata(dev); - - if (__power_supply_is_supplied_by(psy, pst)) { - if (pst->desc->external_power_changed) - pst->desc->external_power_changed(pst); - } - - return 0; -} - -static void power_supply_changed_work(struct work_struct *work) -{ - unsigned long flags; - struct power_supply *psy = container_of(work, struct power_supply, - changed_work); - - dev_dbg(&psy->dev, "%s\n", __func__); - - spin_lock_irqsave(&psy->changed_lock, flags); - /* - * Check 'changed' here to avoid issues due to race between - * power_supply_changed() and this routine. In worst case - * power_supply_changed() can be called again just before we take above - * lock. During the first call of this routine we will mark 'changed' as - * false and it will stay false for the next call as well. - */ - if (likely(psy->changed)) { - psy->changed = false; - spin_unlock_irqrestore(&psy->changed_lock, flags); - class_for_each_device(power_supply_class, NULL, psy, - __power_supply_changed_work); - power_supply_update_leds(psy); - atomic_notifier_call_chain(&power_supply_notifier, - PSY_EVENT_PROP_CHANGED, psy); - kobject_uevent(&psy->dev.kobj, KOBJ_CHANGE); - spin_lock_irqsave(&psy->changed_lock, flags); - } - - /* - * Hold the wakeup_source until all events are processed. - * power_supply_changed() might have called again and have set 'changed' - * to true. - */ - if (likely(!psy->changed)) - pm_relax(&psy->dev); - spin_unlock_irqrestore(&psy->changed_lock, flags); -} - -void power_supply_changed(struct power_supply *psy) -{ - unsigned long flags; - - dev_dbg(&psy->dev, "%s\n", __func__); - - spin_lock_irqsave(&psy->changed_lock, flags); - psy->changed = true; - pm_stay_awake(&psy->dev); - spin_unlock_irqrestore(&psy->changed_lock, flags); - schedule_work(&psy->changed_work); -} -EXPORT_SYMBOL_GPL(power_supply_changed); - -/* - * Notify that power supply was registered after parent finished the probing. - * - * Often power supply is registered from driver's probe function. However - * calling power_supply_changed() directly from power_supply_register() - * would lead to execution of get_property() function provided by the driver - * too early - before the probe ends. - * - * Avoid that by waiting on parent's mutex. - */ -static void power_supply_deferred_register_work(struct work_struct *work) -{ - struct power_supply *psy = container_of(work, struct power_supply, - deferred_register_work.work); - - if (psy->dev.parent) - mutex_lock(&psy->dev.parent->mutex); - - power_supply_changed(psy); - - if (psy->dev.parent) - mutex_unlock(&psy->dev.parent->mutex); -} - -#ifdef CONFIG_OF -#include - -static int __power_supply_populate_supplied_from(struct device *dev, - void *data) -{ - struct power_supply *psy = data; - struct power_supply *epsy = dev_get_drvdata(dev); - struct device_node *np; - int i = 0; - - do { - np = of_parse_phandle(psy->of_node, "power-supplies", i++); - if (!np) - break; - - if (np == epsy->of_node) { - dev_info(&psy->dev, "%s: Found supply : %s\n", - psy->desc->name, epsy->desc->name); - psy->supplied_from[i-1] = (char *)epsy->desc->name; - psy->num_supplies++; - of_node_put(np); - break; - } - of_node_put(np); - } while (np); - - return 0; -} - -static int power_supply_populate_supplied_from(struct power_supply *psy) -{ - int error; - - error = class_for_each_device(power_supply_class, NULL, psy, - __power_supply_populate_supplied_from); - - dev_dbg(&psy->dev, "%s %d\n", __func__, error); - - return error; -} - -static int __power_supply_find_supply_from_node(struct device *dev, - void *data) -{ - struct device_node *np = data; - struct power_supply *epsy = dev_get_drvdata(dev); - - /* returning non-zero breaks out of class_for_each_device loop */ - if (epsy->of_node == np) - return 1; - - return 0; -} - -static int power_supply_find_supply_from_node(struct device_node *supply_node) -{ - int error; - - /* - * class_for_each_device() either returns its own errors or values - * returned by __power_supply_find_supply_from_node(). - * - * __power_supply_find_supply_from_node() will return 0 (no match) - * or 1 (match). - * - * We return 0 if class_for_each_device() returned 1, -EPROBE_DEFER if - * it returned 0, or error as returned by it. - */ - error = class_for_each_device(power_supply_class, NULL, supply_node, - __power_supply_find_supply_from_node); - - return error ? (error == 1 ? 0 : error) : -EPROBE_DEFER; -} - -static int power_supply_check_supplies(struct power_supply *psy) -{ - struct device_node *np; - int cnt = 0; - - /* If there is already a list honor it */ - if (psy->supplied_from && psy->num_supplies > 0) - return 0; - - /* No device node found, nothing to do */ - if (!psy->of_node) - return 0; - - do { - int ret; - - np = of_parse_phandle(psy->of_node, "power-supplies", cnt++); - if (!np) - break; - - ret = power_supply_find_supply_from_node(np); - of_node_put(np); - - if (ret) { - dev_dbg(&psy->dev, "Failed to find supply!\n"); - return ret; - } - } while (np); - - /* Missing valid "power-supplies" entries */ - if (cnt == 1) - return 0; - - /* All supplies found, allocate char ** array for filling */ - psy->supplied_from = devm_kzalloc(&psy->dev, sizeof(psy->supplied_from), - GFP_KERNEL); - if (!psy->supplied_from) { - dev_err(&psy->dev, "Couldn't allocate memory for supply list\n"); - return -ENOMEM; - } - - *psy->supplied_from = devm_kzalloc(&psy->dev, - sizeof(char *) * (cnt - 1), - GFP_KERNEL); - if (!*psy->supplied_from) { - dev_err(&psy->dev, "Couldn't allocate memory for supply list\n"); - return -ENOMEM; - } - - return power_supply_populate_supplied_from(psy); -} -#else -static inline int power_supply_check_supplies(struct power_supply *psy) -{ - return 0; -} -#endif - -static int __power_supply_am_i_supplied(struct device *dev, void *data) -{ - union power_supply_propval ret = {0,}; - struct power_supply *psy = data; - struct power_supply *epsy = dev_get_drvdata(dev); - - if (__power_supply_is_supplied_by(epsy, psy)) - if (!epsy->desc->get_property(epsy, POWER_SUPPLY_PROP_ONLINE, - &ret)) - return ret.intval; - - return 0; -} - -int power_supply_am_i_supplied(struct power_supply *psy) -{ - int error; - - error = class_for_each_device(power_supply_class, NULL, psy, - __power_supply_am_i_supplied); - - dev_dbg(&psy->dev, "%s %d\n", __func__, error); - - return error; -} -EXPORT_SYMBOL_GPL(power_supply_am_i_supplied); - -static int __power_supply_is_system_supplied(struct device *dev, void *data) -{ - union power_supply_propval ret = {0,}; - struct power_supply *psy = dev_get_drvdata(dev); - unsigned int *count = data; - - (*count)++; - if (psy->desc->type != POWER_SUPPLY_TYPE_BATTERY) - if (!psy->desc->get_property(psy, POWER_SUPPLY_PROP_ONLINE, - &ret)) - return ret.intval; - - return 0; -} - -int power_supply_is_system_supplied(void) -{ - int error; - unsigned int count = 0; - - error = class_for_each_device(power_supply_class, NULL, &count, - __power_supply_is_system_supplied); - - /* - * If no power class device was found at all, most probably we are - * running on a desktop system, so assume we are on mains power. - */ - if (count == 0) - return 1; - - return error; -} -EXPORT_SYMBOL_GPL(power_supply_is_system_supplied); - -int power_supply_set_battery_charged(struct power_supply *psy) -{ - if (atomic_read(&psy->use_cnt) >= 0 && - psy->desc->type == POWER_SUPPLY_TYPE_BATTERY && - psy->desc->set_charged) { - psy->desc->set_charged(psy); - return 0; - } - - return -EINVAL; -} -EXPORT_SYMBOL_GPL(power_supply_set_battery_charged); - -static int power_supply_match_device_by_name(struct device *dev, const void *data) -{ - const char *name = data; - struct power_supply *psy = dev_get_drvdata(dev); - - return strcmp(psy->desc->name, name) == 0; -} - -/** - * power_supply_get_by_name() - Search for a power supply and returns its ref - * @name: Power supply name to fetch - * - * If power supply was found, it increases reference count for the - * internal power supply's device. The user should power_supply_put() - * after usage. - * - * Return: On success returns a reference to a power supply with - * matching name equals to @name, a NULL otherwise. - */ -struct power_supply *power_supply_get_by_name(const char *name) -{ - struct power_supply *psy = NULL; - struct device *dev = class_find_device(power_supply_class, NULL, name, - power_supply_match_device_by_name); - - if (dev) { - psy = dev_get_drvdata(dev); - atomic_inc(&psy->use_cnt); - } - - return psy; -} -EXPORT_SYMBOL_GPL(power_supply_get_by_name); - -/** - * power_supply_put() - Drop reference obtained with power_supply_get_by_name - * @psy: Reference to put - * - * The reference to power supply should be put before unregistering - * the power supply. - */ -void power_supply_put(struct power_supply *psy) -{ - might_sleep(); - - atomic_dec(&psy->use_cnt); - put_device(&psy->dev); -} -EXPORT_SYMBOL_GPL(power_supply_put); - -#ifdef CONFIG_OF -static int power_supply_match_device_node(struct device *dev, const void *data) -{ - return dev->parent && dev->parent->of_node == data; -} - -/** - * power_supply_get_by_phandle() - Search for a power supply and returns its ref - * @np: Pointer to device node holding phandle property - * @phandle_name: Name of property holding a power supply name - * - * If power supply was found, it increases reference count for the - * internal power supply's device. The user should power_supply_put() - * after usage. - * - * Return: On success returns a reference to a power supply with - * matching name equals to value under @property, NULL or ERR_PTR otherwise. - */ -struct power_supply *power_supply_get_by_phandle(struct device_node *np, - const char *property) -{ - struct device_node *power_supply_np; - struct power_supply *psy = NULL; - struct device *dev; - - power_supply_np = of_parse_phandle(np, property, 0); - if (!power_supply_np) - return ERR_PTR(-ENODEV); - - dev = class_find_device(power_supply_class, NULL, power_supply_np, - power_supply_match_device_node); - - of_node_put(power_supply_np); - - if (dev) { - psy = dev_get_drvdata(dev); - atomic_inc(&psy->use_cnt); - } - - return psy; -} -EXPORT_SYMBOL_GPL(power_supply_get_by_phandle); - -static void devm_power_supply_put(struct device *dev, void *res) -{ - struct power_supply **psy = res; - - power_supply_put(*psy); -} - -/** - * devm_power_supply_get_by_phandle() - Resource managed version of - * power_supply_get_by_phandle() - * @dev: Pointer to device holding phandle property - * @phandle_name: Name of property holding a power supply phandle - * - * Return: On success returns a reference to a power supply with - * matching name equals to value under @property, NULL or ERR_PTR otherwise. - */ -struct power_supply *devm_power_supply_get_by_phandle(struct device *dev, - const char *property) -{ - struct power_supply **ptr, *psy; - - if (!dev->of_node) - return ERR_PTR(-ENODEV); - - ptr = devres_alloc(devm_power_supply_put, sizeof(*ptr), GFP_KERNEL); - if (!ptr) - return ERR_PTR(-ENOMEM); - - psy = power_supply_get_by_phandle(dev->of_node, property); - if (IS_ERR_OR_NULL(psy)) { - devres_free(ptr); - } else { - *ptr = psy; - devres_add(dev, ptr); - } - return psy; -} -EXPORT_SYMBOL_GPL(devm_power_supply_get_by_phandle); -#endif /* CONFIG_OF */ - -int power_supply_get_property(struct power_supply *psy, - enum power_supply_property psp, - union power_supply_propval *val) -{ - if (atomic_read(&psy->use_cnt) <= 0) { - if (!psy->initialized) - return -EAGAIN; - return -ENODEV; - } - - return psy->desc->get_property(psy, psp, val); -} -EXPORT_SYMBOL_GPL(power_supply_get_property); - -int power_supply_set_property(struct power_supply *psy, - enum power_supply_property psp, - const union power_supply_propval *val) -{ - if (atomic_read(&psy->use_cnt) <= 0 || !psy->desc->set_property) - return -ENODEV; - - return psy->desc->set_property(psy, psp, val); -} -EXPORT_SYMBOL_GPL(power_supply_set_property); - -int power_supply_property_is_writeable(struct power_supply *psy, - enum power_supply_property psp) -{ - if (atomic_read(&psy->use_cnt) <= 0 || - !psy->desc->property_is_writeable) - return -ENODEV; - - return psy->desc->property_is_writeable(psy, psp); -} -EXPORT_SYMBOL_GPL(power_supply_property_is_writeable); - -void power_supply_external_power_changed(struct power_supply *psy) -{ - if (atomic_read(&psy->use_cnt) <= 0 || - !psy->desc->external_power_changed) - return; - - psy->desc->external_power_changed(psy); -} -EXPORT_SYMBOL_GPL(power_supply_external_power_changed); - -int power_supply_powers(struct power_supply *psy, struct device *dev) -{ - return sysfs_create_link(&psy->dev.kobj, &dev->kobj, "powers"); -} -EXPORT_SYMBOL_GPL(power_supply_powers); - -static void power_supply_dev_release(struct device *dev) -{ - struct power_supply *psy = container_of(dev, struct power_supply, dev); - pr_debug("device: '%s': %s\n", dev_name(dev), __func__); - kfree(psy); -} - -int power_supply_reg_notifier(struct notifier_block *nb) -{ - return atomic_notifier_chain_register(&power_supply_notifier, nb); -} -EXPORT_SYMBOL_GPL(power_supply_reg_notifier); - -void power_supply_unreg_notifier(struct notifier_block *nb) -{ - atomic_notifier_chain_unregister(&power_supply_notifier, nb); -} -EXPORT_SYMBOL_GPL(power_supply_unreg_notifier); - -#ifdef CONFIG_THERMAL -static int power_supply_read_temp(struct thermal_zone_device *tzd, - int *temp) -{ - struct power_supply *psy; - union power_supply_propval val; - int ret; - - WARN_ON(tzd == NULL); - psy = tzd->devdata; - ret = power_supply_get_property(psy, POWER_SUPPLY_PROP_TEMP, &val); - if (ret) - return ret; - - /* Convert tenths of degree Celsius to milli degree Celsius. */ - *temp = val.intval * 100; - - return ret; -} - -static struct thermal_zone_device_ops psy_tzd_ops = { - .get_temp = power_supply_read_temp, -}; - -static int psy_register_thermal(struct power_supply *psy) -{ - int i; - - if (psy->desc->no_thermal) - return 0; - - /* Register battery zone device psy reports temperature */ - for (i = 0; i < psy->desc->num_properties; i++) { - if (psy->desc->properties[i] == POWER_SUPPLY_PROP_TEMP) { - psy->tzd = thermal_zone_device_register(psy->desc->name, - 0, 0, psy, &psy_tzd_ops, NULL, 0, 0); - return PTR_ERR_OR_ZERO(psy->tzd); - } - } - return 0; -} - -static void psy_unregister_thermal(struct power_supply *psy) -{ - if (IS_ERR_OR_NULL(psy->tzd)) - return; - thermal_zone_device_unregister(psy->tzd); -} - -/* thermal cooling device callbacks */ -static int ps_get_max_charge_cntl_limit(struct thermal_cooling_device *tcd, - unsigned long *state) -{ - struct power_supply *psy; - union power_supply_propval val; - int ret; - - psy = tcd->devdata; - ret = power_supply_get_property(psy, - POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT_MAX, &val); - if (ret) - return ret; - - *state = val.intval; - - return ret; -} - -static int ps_get_cur_chrage_cntl_limit(struct thermal_cooling_device *tcd, - unsigned long *state) -{ - struct power_supply *psy; - union power_supply_propval val; - int ret; - - psy = tcd->devdata; - ret = power_supply_get_property(psy, - POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT, &val); - if (ret) - return ret; - - *state = val.intval; - - return ret; -} - -static int ps_set_cur_charge_cntl_limit(struct thermal_cooling_device *tcd, - unsigned long state) -{ - struct power_supply *psy; - union power_supply_propval val; - int ret; - - psy = tcd->devdata; - val.intval = state; - ret = psy->desc->set_property(psy, - POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT, &val); - - return ret; -} - -static struct thermal_cooling_device_ops psy_tcd_ops = { - .get_max_state = ps_get_max_charge_cntl_limit, - .get_cur_state = ps_get_cur_chrage_cntl_limit, - .set_cur_state = ps_set_cur_charge_cntl_limit, -}; - -static int psy_register_cooler(struct power_supply *psy) -{ - int i; - - /* Register for cooling device if psy can control charging */ - for (i = 0; i < psy->desc->num_properties; i++) { - if (psy->desc->properties[i] == - POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT) { - psy->tcd = thermal_cooling_device_register( - (char *)psy->desc->name, - psy, &psy_tcd_ops); - return PTR_ERR_OR_ZERO(psy->tcd); - } - } - return 0; -} - -static void psy_unregister_cooler(struct power_supply *psy) -{ - if (IS_ERR_OR_NULL(psy->tcd)) - return; - thermal_cooling_device_unregister(psy->tcd); -} -#else -static int psy_register_thermal(struct power_supply *psy) -{ - return 0; -} - -static void psy_unregister_thermal(struct power_supply *psy) -{ -} - -static int psy_register_cooler(struct power_supply *psy) -{ - return 0; -} - -static void psy_unregister_cooler(struct power_supply *psy) -{ -} -#endif - -static struct power_supply *__must_check -__power_supply_register(struct device *parent, - const struct power_supply_desc *desc, - const struct power_supply_config *cfg, - bool ws) -{ - struct device *dev; - struct power_supply *psy; - int rc; - - if (!parent) - pr_warn("%s: Expected proper parent device for '%s'\n", - __func__, desc->name); - - psy = kzalloc(sizeof(*psy), GFP_KERNEL); - if (!psy) - return ERR_PTR(-ENOMEM); - - dev = &psy->dev; - - device_initialize(dev); - - dev->class = power_supply_class; - dev->type = &power_supply_dev_type; - dev->parent = parent; - dev->release = power_supply_dev_release; - dev_set_drvdata(dev, psy); - psy->desc = desc; - if (cfg) { - psy->drv_data = cfg->drv_data; - psy->of_node = cfg->of_node; - psy->supplied_to = cfg->supplied_to; - psy->num_supplicants = cfg->num_supplicants; - } - - rc = dev_set_name(dev, "%s", desc->name); - if (rc) - goto dev_set_name_failed; - - INIT_WORK(&psy->changed_work, power_supply_changed_work); - INIT_DELAYED_WORK(&psy->deferred_register_work, - power_supply_deferred_register_work); - - rc = power_supply_check_supplies(psy); - if (rc) { - dev_info(dev, "Not all required supplies found, defer probe\n"); - goto check_supplies_failed; - } - - spin_lock_init(&psy->changed_lock); - rc = device_init_wakeup(dev, ws); - if (rc) - goto wakeup_init_failed; - - rc = device_add(dev); - if (rc) - goto device_add_failed; - - rc = psy_register_thermal(psy); - if (rc) - goto register_thermal_failed; - - rc = psy_register_cooler(psy); - if (rc) - goto register_cooler_failed; - - rc = power_supply_create_triggers(psy); - if (rc) - goto create_triggers_failed; - - /* - * Update use_cnt after any uevents (most notably from device_add()). - * We are here still during driver's probe but - * the power_supply_uevent() calls back driver's get_property - * method so: - * 1. Driver did not assigned the returned struct power_supply, - * 2. Driver could not finish initialization (anything in its probe - * after calling power_supply_register()). - */ - atomic_inc(&psy->use_cnt); - psy->initialized = true; - - queue_delayed_work(system_power_efficient_wq, - &psy->deferred_register_work, - POWER_SUPPLY_DEFERRED_REGISTER_TIME); - - return psy; - -create_triggers_failed: - psy_unregister_cooler(psy); -register_cooler_failed: - psy_unregister_thermal(psy); -register_thermal_failed: - device_del(dev); -device_add_failed: -wakeup_init_failed: -check_supplies_failed: -dev_set_name_failed: - put_device(dev); - return ERR_PTR(rc); -} - -/** - * power_supply_register() - Register new power supply - * @parent: Device to be a parent of power supply's device, usually - * the device which probe function calls this - * @desc: Description of power supply, must be valid through whole - * lifetime of this power supply - * @cfg: Run-time specific configuration accessed during registering, - * may be NULL - * - * Return: A pointer to newly allocated power_supply on success - * or ERR_PTR otherwise. - * Use power_supply_unregister() on returned power_supply pointer to release - * resources. - */ -struct power_supply *__must_check power_supply_register(struct device *parent, - const struct power_supply_desc *desc, - const struct power_supply_config *cfg) -{ - return __power_supply_register(parent, desc, cfg, true); -} -EXPORT_SYMBOL_GPL(power_supply_register); - -/** - * power_supply_register_no_ws() - Register new non-waking-source power supply - * @parent: Device to be a parent of power supply's device, usually - * the device which probe function calls this - * @desc: Description of power supply, must be valid through whole - * lifetime of this power supply - * @cfg: Run-time specific configuration accessed during registering, - * may be NULL - * - * Return: A pointer to newly allocated power_supply on success - * or ERR_PTR otherwise. - * Use power_supply_unregister() on returned power_supply pointer to release - * resources. - */ -struct power_supply *__must_check -power_supply_register_no_ws(struct device *parent, - const struct power_supply_desc *desc, - const struct power_supply_config *cfg) -{ - return __power_supply_register(parent, desc, cfg, false); -} -EXPORT_SYMBOL_GPL(power_supply_register_no_ws); - -static void devm_power_supply_release(struct device *dev, void *res) -{ - struct power_supply **psy = res; - - power_supply_unregister(*psy); -} - -/** - * devm_power_supply_register() - Register managed power supply - * @parent: Device to be a parent of power supply's device, usually - * the device which probe function calls this - * @desc: Description of power supply, must be valid through whole - * lifetime of this power supply - * @cfg: Run-time specific configuration accessed during registering, - * may be NULL - * - * Return: A pointer to newly allocated power_supply on success - * or ERR_PTR otherwise. - * The returned power_supply pointer will be automatically unregistered - * on driver detach. - */ -struct power_supply *__must_check -devm_power_supply_register(struct device *parent, - const struct power_supply_desc *desc, - const struct power_supply_config *cfg) -{ - struct power_supply **ptr, *psy; - - ptr = devres_alloc(devm_power_supply_release, sizeof(*ptr), GFP_KERNEL); - - if (!ptr) - return ERR_PTR(-ENOMEM); - psy = __power_supply_register(parent, desc, cfg, true); - if (IS_ERR(psy)) { - devres_free(ptr); - } else { - *ptr = psy; - devres_add(parent, ptr); - } - return psy; -} -EXPORT_SYMBOL_GPL(devm_power_supply_register); - -/** - * devm_power_supply_register_no_ws() - Register managed non-waking-source power supply - * @parent: Device to be a parent of power supply's device, usually - * the device which probe function calls this - * @desc: Description of power supply, must be valid through whole - * lifetime of this power supply - * @cfg: Run-time specific configuration accessed during registering, - * may be NULL - * - * Return: A pointer to newly allocated power_supply on success - * or ERR_PTR otherwise. - * The returned power_supply pointer will be automatically unregistered - * on driver detach. - */ -struct power_supply *__must_check -devm_power_supply_register_no_ws(struct device *parent, - const struct power_supply_desc *desc, - const struct power_supply_config *cfg) -{ - struct power_supply **ptr, *psy; - - ptr = devres_alloc(devm_power_supply_release, sizeof(*ptr), GFP_KERNEL); - - if (!ptr) - return ERR_PTR(-ENOMEM); - psy = __power_supply_register(parent, desc, cfg, false); - if (IS_ERR(psy)) { - devres_free(ptr); - } else { - *ptr = psy; - devres_add(parent, ptr); - } - return psy; -} -EXPORT_SYMBOL_GPL(devm_power_supply_register_no_ws); - -/** - * power_supply_unregister() - Remove this power supply from system - * @psy: Pointer to power supply to unregister - * - * Remove this power supply from the system. The resources of power supply - * will be freed here or on last power_supply_put() call. - */ -void power_supply_unregister(struct power_supply *psy) -{ - WARN_ON(atomic_dec_return(&psy->use_cnt)); - cancel_work_sync(&psy->changed_work); - cancel_delayed_work_sync(&psy->deferred_register_work); - sysfs_remove_link(&psy->dev.kobj, "powers"); - power_supply_remove_triggers(psy); - psy_unregister_cooler(psy); - psy_unregister_thermal(psy); - device_init_wakeup(&psy->dev, false); - device_unregister(&psy->dev); -} -EXPORT_SYMBOL_GPL(power_supply_unregister); - -void *power_supply_get_drvdata(struct power_supply *psy) -{ - return psy->drv_data; -} -EXPORT_SYMBOL_GPL(power_supply_get_drvdata); - -static int __init power_supply_class_init(void) -{ - power_supply_class = class_create(THIS_MODULE, "power_supply"); - - if (IS_ERR(power_supply_class)) - return PTR_ERR(power_supply_class); - - power_supply_class->dev_uevent = power_supply_uevent; - power_supply_init_attrs(&power_supply_dev_type); - - return 0; -} - -static void __exit power_supply_class_exit(void) -{ - class_destroy(power_supply_class); -} - -subsys_initcall(power_supply_class_init); -module_exit(power_supply_class_exit); - -MODULE_DESCRIPTION("Universal power supply monitor class"); -MODULE_AUTHOR("Ian Molton , " - "Szabolcs Gyurko, " - "Anton Vorontsov "); -MODULE_LICENSE("GPL"); diff --git a/drivers/power/power_supply_leds.c b/drivers/power/power_supply_leds.c deleted file mode 100644 index 2277ad9c2f68..000000000000 --- a/drivers/power/power_supply_leds.c +++ /dev/null @@ -1,170 +0,0 @@ -/* - * LEDs triggers for power supply class - * - * Copyright © 2007 Anton Vorontsov - * Copyright © 2004 Szabolcs Gyurko - * Copyright © 2003 Ian Molton - * - * Modified: 2004, Oct Szabolcs Gyurko - * - * You may use this code as per GPL version 2 - */ - -#include -#include -#include -#include - -#include "power_supply.h" - -/* Battery specific LEDs triggers. */ - -static void power_supply_update_bat_leds(struct power_supply *psy) -{ - union power_supply_propval status; - unsigned long delay_on = 0; - unsigned long delay_off = 0; - - if (power_supply_get_property(psy, POWER_SUPPLY_PROP_STATUS, &status)) - return; - - dev_dbg(&psy->dev, "%s %d\n", __func__, status.intval); - - switch (status.intval) { - case POWER_SUPPLY_STATUS_FULL: - led_trigger_event(psy->charging_full_trig, LED_FULL); - led_trigger_event(psy->charging_trig, LED_OFF); - led_trigger_event(psy->full_trig, LED_FULL); - led_trigger_event(psy->charging_blink_full_solid_trig, - LED_FULL); - break; - case POWER_SUPPLY_STATUS_CHARGING: - led_trigger_event(psy->charging_full_trig, LED_FULL); - led_trigger_event(psy->charging_trig, LED_FULL); - led_trigger_event(psy->full_trig, LED_OFF); - led_trigger_blink(psy->charging_blink_full_solid_trig, - &delay_on, &delay_off); - break; - default: - led_trigger_event(psy->charging_full_trig, LED_OFF); - led_trigger_event(psy->charging_trig, LED_OFF); - led_trigger_event(psy->full_trig, LED_OFF); - led_trigger_event(psy->charging_blink_full_solid_trig, - LED_OFF); - break; - } -} - -static int power_supply_create_bat_triggers(struct power_supply *psy) -{ - psy->charging_full_trig_name = kasprintf(GFP_KERNEL, - "%s-charging-or-full", psy->desc->name); - if (!psy->charging_full_trig_name) - goto charging_full_failed; - - psy->charging_trig_name = kasprintf(GFP_KERNEL, - "%s-charging", psy->desc->name); - if (!psy->charging_trig_name) - goto charging_failed; - - psy->full_trig_name = kasprintf(GFP_KERNEL, "%s-full", psy->desc->name); - if (!psy->full_trig_name) - goto full_failed; - - psy->charging_blink_full_solid_trig_name = kasprintf(GFP_KERNEL, - "%s-charging-blink-full-solid", psy->desc->name); - if (!psy->charging_blink_full_solid_trig_name) - goto charging_blink_full_solid_failed; - - led_trigger_register_simple(psy->charging_full_trig_name, - &psy->charging_full_trig); - led_trigger_register_simple(psy->charging_trig_name, - &psy->charging_trig); - led_trigger_register_simple(psy->full_trig_name, - &psy->full_trig); - led_trigger_register_simple(psy->charging_blink_full_solid_trig_name, - &psy->charging_blink_full_solid_trig); - - return 0; - -charging_blink_full_solid_failed: - kfree(psy->full_trig_name); -full_failed: - kfree(psy->charging_trig_name); -charging_failed: - kfree(psy->charging_full_trig_name); -charging_full_failed: - return -ENOMEM; -} - -static void power_supply_remove_bat_triggers(struct power_supply *psy) -{ - led_trigger_unregister_simple(psy->charging_full_trig); - led_trigger_unregister_simple(psy->charging_trig); - led_trigger_unregister_simple(psy->full_trig); - led_trigger_unregister_simple(psy->charging_blink_full_solid_trig); - kfree(psy->charging_blink_full_solid_trig_name); - kfree(psy->full_trig_name); - kfree(psy->charging_trig_name); - kfree(psy->charging_full_trig_name); -} - -/* Generated power specific LEDs triggers. */ - -static void power_supply_update_gen_leds(struct power_supply *psy) -{ - union power_supply_propval online; - - if (power_supply_get_property(psy, POWER_SUPPLY_PROP_ONLINE, &online)) - return; - - dev_dbg(&psy->dev, "%s %d\n", __func__, online.intval); - - if (online.intval) - led_trigger_event(psy->online_trig, LED_FULL); - else - led_trigger_event(psy->online_trig, LED_OFF); -} - -static int power_supply_create_gen_triggers(struct power_supply *psy) -{ - psy->online_trig_name = kasprintf(GFP_KERNEL, "%s-online", - psy->desc->name); - if (!psy->online_trig_name) - return -ENOMEM; - - led_trigger_register_simple(psy->online_trig_name, &psy->online_trig); - - return 0; -} - -static void power_supply_remove_gen_triggers(struct power_supply *psy) -{ - led_trigger_unregister_simple(psy->online_trig); - kfree(psy->online_trig_name); -} - -/* Choice what triggers to create&update. */ - -void power_supply_update_leds(struct power_supply *psy) -{ - if (psy->desc->type == POWER_SUPPLY_TYPE_BATTERY) - power_supply_update_bat_leds(psy); - else - power_supply_update_gen_leds(psy); -} - -int power_supply_create_triggers(struct power_supply *psy) -{ - if (psy->desc->type == POWER_SUPPLY_TYPE_BATTERY) - return power_supply_create_bat_triggers(psy); - return power_supply_create_gen_triggers(psy); -} - -void power_supply_remove_triggers(struct power_supply *psy) -{ - if (psy->desc->type == POWER_SUPPLY_TYPE_BATTERY) - power_supply_remove_bat_triggers(psy); - else - power_supply_remove_gen_triggers(psy); -} diff --git a/drivers/power/power_supply_sysfs.c b/drivers/power/power_supply_sysfs.c deleted file mode 100644 index bcde8d13476a..000000000000 --- a/drivers/power/power_supply_sysfs.c +++ /dev/null @@ -1,337 +0,0 @@ -/* - * Sysfs interface for the universal power supply monitor class - * - * Copyright © 2007 David Woodhouse - * Copyright © 2007 Anton Vorontsov - * Copyright © 2004 Szabolcs Gyurko - * Copyright © 2003 Ian Molton - * - * Modified: 2004, Oct Szabolcs Gyurko - * - * You may use this code as per GPL version 2 - */ - -#include -#include -#include -#include -#include - -#include "power_supply.h" - -/* - * This is because the name "current" breaks the device attr macro. - * The "current" word resolves to "(get_current())" so instead of - * "current" "(get_current())" appears in the sysfs. - * - * The source of this definition is the device.h which calls __ATTR - * macro in sysfs.h which calls the __stringify macro. - * - * Only modification that the name is not tried to be resolved - * (as a macro let's say). - */ - -#define POWER_SUPPLY_ATTR(_name) \ -{ \ - .attr = { .name = #_name }, \ - .show = power_supply_show_property, \ - .store = power_supply_store_property, \ -} - -static struct device_attribute power_supply_attrs[]; - -static ssize_t power_supply_show_property(struct device *dev, - struct device_attribute *attr, - char *buf) { - static char *type_text[] = { - "Unknown", "Battery", "UPS", "Mains", "USB", - "USB_DCP", "USB_CDP", "USB_ACA", "USB_C", - "USB_PD", "USB_PD_DRP" - }; - static char *status_text[] = { - "Unknown", "Charging", "Discharging", "Not charging", "Full" - }; - static char *charge_type[] = { - "Unknown", "N/A", "Trickle", "Fast" - }; - static char *health_text[] = { - "Unknown", "Good", "Overheat", "Dead", "Over voltage", - "Unspecified failure", "Cold", "Watchdog timer expire", - "Safety timer expire" - }; - static char *technology_text[] = { - "Unknown", "NiMH", "Li-ion", "Li-poly", "LiFe", "NiCd", - "LiMn" - }; - static char *capacity_level_text[] = { - "Unknown", "Critical", "Low", "Normal", "High", "Full" - }; - static char *scope_text[] = { - "Unknown", "System", "Device" - }; - ssize_t ret = 0; - struct power_supply *psy = dev_get_drvdata(dev); - const ptrdiff_t off = attr - power_supply_attrs; - union power_supply_propval value; - - if (off == POWER_SUPPLY_PROP_TYPE) { - value.intval = psy->desc->type; - } else { - ret = power_supply_get_property(psy, off, &value); - - if (ret < 0) { - if (ret == -ENODATA) - dev_dbg(dev, "driver has no data for `%s' property\n", - attr->attr.name); - else if (ret != -ENODEV && ret != -EAGAIN) - dev_err(dev, "driver failed to report `%s' property: %zd\n", - attr->attr.name, ret); - return ret; - } - } - - if (off == POWER_SUPPLY_PROP_STATUS) - return sprintf(buf, "%s\n", status_text[value.intval]); - else if (off == POWER_SUPPLY_PROP_CHARGE_TYPE) - return sprintf(buf, "%s\n", charge_type[value.intval]); - else if (off == POWER_SUPPLY_PROP_HEALTH) - return sprintf(buf, "%s\n", health_text[value.intval]); - else if (off == POWER_SUPPLY_PROP_TECHNOLOGY) - return sprintf(buf, "%s\n", technology_text[value.intval]); - else if (off == POWER_SUPPLY_PROP_CAPACITY_LEVEL) - return sprintf(buf, "%s\n", capacity_level_text[value.intval]); - else if (off == POWER_SUPPLY_PROP_TYPE) - return sprintf(buf, "%s\n", type_text[value.intval]); - else if (off == POWER_SUPPLY_PROP_SCOPE) - return sprintf(buf, "%s\n", scope_text[value.intval]); - else if (off >= POWER_SUPPLY_PROP_MODEL_NAME) - return sprintf(buf, "%s\n", value.strval); - - return sprintf(buf, "%d\n", value.intval); -} - -static ssize_t power_supply_store_property(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t count) { - ssize_t ret; - struct power_supply *psy = dev_get_drvdata(dev); - const ptrdiff_t off = attr - power_supply_attrs; - union power_supply_propval value; - long long_val; - - /* TODO: support other types than int */ - ret = kstrtol(buf, 10, &long_val); - if (ret < 0) - return ret; - - value.intval = long_val; - - ret = power_supply_set_property(psy, off, &value); - if (ret < 0) - return ret; - - return count; -} - -/* Must be in the same order as POWER_SUPPLY_PROP_* */ -static struct device_attribute power_supply_attrs[] = { - /* Properties of type `int' */ - POWER_SUPPLY_ATTR(status), - POWER_SUPPLY_ATTR(charge_type), - POWER_SUPPLY_ATTR(health), - POWER_SUPPLY_ATTR(present), - POWER_SUPPLY_ATTR(online), - POWER_SUPPLY_ATTR(authentic), - POWER_SUPPLY_ATTR(technology), - POWER_SUPPLY_ATTR(cycle_count), - POWER_SUPPLY_ATTR(voltage_max), - POWER_SUPPLY_ATTR(voltage_min), - POWER_SUPPLY_ATTR(voltage_max_design), - POWER_SUPPLY_ATTR(voltage_min_design), - POWER_SUPPLY_ATTR(voltage_now), - POWER_SUPPLY_ATTR(voltage_avg), - POWER_SUPPLY_ATTR(voltage_ocv), - POWER_SUPPLY_ATTR(voltage_boot), - POWER_SUPPLY_ATTR(current_max), - POWER_SUPPLY_ATTR(current_now), - POWER_SUPPLY_ATTR(current_avg), - POWER_SUPPLY_ATTR(current_boot), - POWER_SUPPLY_ATTR(power_now), - POWER_SUPPLY_ATTR(power_avg), - POWER_SUPPLY_ATTR(charge_full_design), - POWER_SUPPLY_ATTR(charge_empty_design), - POWER_SUPPLY_ATTR(charge_full), - POWER_SUPPLY_ATTR(charge_empty), - POWER_SUPPLY_ATTR(charge_now), - POWER_SUPPLY_ATTR(charge_avg), - POWER_SUPPLY_ATTR(charge_counter), - POWER_SUPPLY_ATTR(constant_charge_current), - POWER_SUPPLY_ATTR(constant_charge_current_max), - POWER_SUPPLY_ATTR(constant_charge_voltage), - POWER_SUPPLY_ATTR(constant_charge_voltage_max), - POWER_SUPPLY_ATTR(charge_control_limit), - POWER_SUPPLY_ATTR(charge_control_limit_max), - POWER_SUPPLY_ATTR(input_current_limit), - POWER_SUPPLY_ATTR(energy_full_design), - POWER_SUPPLY_ATTR(energy_empty_design), - POWER_SUPPLY_ATTR(energy_full), - POWER_SUPPLY_ATTR(energy_empty), - POWER_SUPPLY_ATTR(energy_now), - POWER_SUPPLY_ATTR(energy_avg), - POWER_SUPPLY_ATTR(capacity), - POWER_SUPPLY_ATTR(capacity_alert_min), - POWER_SUPPLY_ATTR(capacity_alert_max), - POWER_SUPPLY_ATTR(capacity_level), - POWER_SUPPLY_ATTR(temp), - POWER_SUPPLY_ATTR(temp_max), - POWER_SUPPLY_ATTR(temp_min), - POWER_SUPPLY_ATTR(temp_alert_min), - POWER_SUPPLY_ATTR(temp_alert_max), - POWER_SUPPLY_ATTR(temp_ambient), - POWER_SUPPLY_ATTR(temp_ambient_alert_min), - POWER_SUPPLY_ATTR(temp_ambient_alert_max), - POWER_SUPPLY_ATTR(time_to_empty_now), - POWER_SUPPLY_ATTR(time_to_empty_avg), - POWER_SUPPLY_ATTR(time_to_full_now), - POWER_SUPPLY_ATTR(time_to_full_avg), - POWER_SUPPLY_ATTR(type), - POWER_SUPPLY_ATTR(scope), - POWER_SUPPLY_ATTR(charge_term_current), - POWER_SUPPLY_ATTR(calibrate), - /* Properties of type `const char *' */ - POWER_SUPPLY_ATTR(model_name), - POWER_SUPPLY_ATTR(manufacturer), - POWER_SUPPLY_ATTR(serial_number), -}; - -static struct attribute * -__power_supply_attrs[ARRAY_SIZE(power_supply_attrs) + 1]; - -static umode_t power_supply_attr_is_visible(struct kobject *kobj, - struct attribute *attr, - int attrno) -{ - struct device *dev = container_of(kobj, struct device, kobj); - struct power_supply *psy = dev_get_drvdata(dev); - umode_t mode = S_IRUSR | S_IRGRP | S_IROTH; - int i; - - if (attrno == POWER_SUPPLY_PROP_TYPE) - return mode; - - for (i = 0; i < psy->desc->num_properties; i++) { - int property = psy->desc->properties[i]; - - if (property == attrno) { - if (psy->desc->property_is_writeable && - psy->desc->property_is_writeable(psy, property) > 0) - mode |= S_IWUSR; - - return mode; - } - } - - return 0; -} - -static struct attribute_group power_supply_attr_group = { - .attrs = __power_supply_attrs, - .is_visible = power_supply_attr_is_visible, -}; - -static const struct attribute_group *power_supply_attr_groups[] = { - &power_supply_attr_group, - NULL, -}; - -void power_supply_init_attrs(struct device_type *dev_type) -{ - int i; - - dev_type->groups = power_supply_attr_groups; - - for (i = 0; i < ARRAY_SIZE(power_supply_attrs); i++) - __power_supply_attrs[i] = &power_supply_attrs[i].attr; -} - -static char *kstruprdup(const char *str, gfp_t gfp) -{ - char *ret, *ustr; - - ustr = ret = kmalloc(strlen(str) + 1, gfp); - - if (!ret) - return NULL; - - while (*str) - *ustr++ = toupper(*str++); - - *ustr = 0; - - return ret; -} - -int power_supply_uevent(struct device *dev, struct kobj_uevent_env *env) -{ - struct power_supply *psy = dev_get_drvdata(dev); - int ret = 0, j; - char *prop_buf; - char *attrname; - - dev_dbg(dev, "uevent\n"); - - if (!psy || !psy->desc) { - dev_dbg(dev, "No power supply yet\n"); - return ret; - } - - dev_dbg(dev, "POWER_SUPPLY_NAME=%s\n", psy->desc->name); - - ret = add_uevent_var(env, "POWER_SUPPLY_NAME=%s", psy->desc->name); - if (ret) - return ret; - - prop_buf = (char *)get_zeroed_page(GFP_KERNEL); - if (!prop_buf) - return -ENOMEM; - - for (j = 0; j < psy->desc->num_properties; j++) { - struct device_attribute *attr; - char *line; - - attr = &power_supply_attrs[psy->desc->properties[j]]; - - ret = power_supply_show_property(dev, attr, prop_buf); - if (ret == -ENODEV || ret == -ENODATA) { - /* When a battery is absent, we expect -ENODEV. Don't abort; - send the uevent with at least the the PRESENT=0 property */ - ret = 0; - continue; - } - - if (ret < 0) - goto out; - - line = strchr(prop_buf, '\n'); - if (line) - *line = 0; - - attrname = kstruprdup(attr->attr.name, GFP_KERNEL); - if (!attrname) { - ret = -ENOMEM; - goto out; - } - - dev_dbg(dev, "prop %s=%s\n", attrname, prop_buf); - - ret = add_uevent_var(env, "POWER_SUPPLY_%s=%s", attrname, prop_buf); - kfree(attrname); - if (ret) - goto out; - } - -out: - free_page((unsigned long)prop_buf); - - return ret; -} diff --git a/drivers/power/qcom_smbb.c b/drivers/power/qcom_smbb.c deleted file mode 100644 index b5896ba2a602..000000000000 --- a/drivers/power/qcom_smbb.c +++ /dev/null @@ -1,972 +0,0 @@ -/* 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#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; - struct extcon_dev *edev; - - 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 const unsigned int smbb_usb_extcon_cable[] = { - EXTCON_USB, - EXTCON_NONE, -}; - -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); - extcon_set_cable_state_(chg->edev, EXTCON_USB, - chg->status & 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); - } - - chg->edev = devm_extcon_dev_allocate(&pdev->dev, smbb_usb_extcon_cable); - if (IS_ERR(chg->edev)) { - dev_err(&pdev->dev, "failed to allocate extcon device\n"); - return -ENOMEM; - } - - rc = devm_extcon_dev_register(&pdev->dev, chg->edev); - if (rc < 0) { - dev_err(&pdev->dev, "failed to register extcon device\n"); - return rc; - } - - 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"); diff --git a/drivers/power/rt5033_battery.c b/drivers/power/rt5033_battery.c deleted file mode 100644 index bcdd83048492..000000000000 --- a/drivers/power/rt5033_battery.c +++ /dev/null @@ -1,182 +0,0 @@ -/* - * Fuel gauge driver for Richtek RT5033 - * - * Copyright (C) 2014 Samsung Electronics, Co., Ltd. - * Author: Beomho Seo - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published bythe Free Software Foundation. - */ - -#include -#include -#include -#include -#include - -static int rt5033_battery_get_capacity(struct i2c_client *client) -{ - struct rt5033_battery *battery = i2c_get_clientdata(client); - u32 msb; - - regmap_read(battery->regmap, RT5033_FUEL_REG_SOC_H, &msb); - - return msb; -} - -static int rt5033_battery_get_present(struct i2c_client *client) -{ - struct rt5033_battery *battery = i2c_get_clientdata(client); - u32 val; - - regmap_read(battery->regmap, RT5033_FUEL_REG_CONFIG_L, &val); - - return (val & RT5033_FUEL_BAT_PRESENT) ? true : false; -} - -static int rt5033_battery_get_watt_prop(struct i2c_client *client, - enum power_supply_property psp) -{ - struct rt5033_battery *battery = i2c_get_clientdata(client); - unsigned int regh, regl; - int ret; - u32 msb, lsb; - - switch (psp) { - case POWER_SUPPLY_PROP_VOLTAGE_NOW: - regh = RT5033_FUEL_REG_VBAT_H; - regl = RT5033_FUEL_REG_VBAT_L; - break; - case POWER_SUPPLY_PROP_VOLTAGE_AVG: - regh = RT5033_FUEL_REG_AVG_VOLT_H; - regl = RT5033_FUEL_REG_AVG_VOLT_L; - break; - case POWER_SUPPLY_PROP_VOLTAGE_OCV: - regh = RT5033_FUEL_REG_OCV_H; - regl = RT5033_FUEL_REG_OCV_L; - break; - default: - return -EINVAL; - } - - regmap_read(battery->regmap, regh, &msb); - regmap_read(battery->regmap, regl, &lsb); - - ret = ((msb << 4) + (lsb >> 4)) * 1250 / 1000; - - return ret; -} - -static int rt5033_battery_get_property(struct power_supply *psy, - enum power_supply_property psp, - union power_supply_propval *val) -{ - struct rt5033_battery *battery = power_supply_get_drvdata(psy); - - switch (psp) { - case POWER_SUPPLY_PROP_VOLTAGE_NOW: - case POWER_SUPPLY_PROP_VOLTAGE_AVG: - case POWER_SUPPLY_PROP_VOLTAGE_OCV: - val->intval = rt5033_battery_get_watt_prop(battery->client, - psp); - break; - case POWER_SUPPLY_PROP_PRESENT: - val->intval = rt5033_battery_get_present(battery->client); - break; - case POWER_SUPPLY_PROP_CAPACITY: - val->intval = rt5033_battery_get_capacity(battery->client); - break; - default: - return -EINVAL; - } - return 0; -} - -static enum power_supply_property rt5033_battery_props[] = { - POWER_SUPPLY_PROP_VOLTAGE_NOW, - POWER_SUPPLY_PROP_VOLTAGE_AVG, - POWER_SUPPLY_PROP_VOLTAGE_OCV, - POWER_SUPPLY_PROP_PRESENT, - POWER_SUPPLY_PROP_CAPACITY, -}; - -static const struct regmap_config rt5033_battery_regmap_config = { - .reg_bits = 8, - .val_bits = 8, - .max_register = RT5033_FUEL_REG_END, -}; - -static const struct power_supply_desc rt5033_battery_desc = { - .name = "rt5033-battery", - .type = POWER_SUPPLY_TYPE_BATTERY, - .get_property = rt5033_battery_get_property, - .properties = rt5033_battery_props, - .num_properties = ARRAY_SIZE(rt5033_battery_props), -}; - -static int rt5033_battery_probe(struct i2c_client *client, - const struct i2c_device_id *id) -{ - struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent); - struct power_supply_config psy_cfg = {}; - struct rt5033_battery *battery; - u32 ret; - - if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE)) - return -EIO; - - battery = devm_kzalloc(&client->dev, sizeof(*battery), GFP_KERNEL); - if (!battery) - return -EINVAL; - - battery->client = client; - battery->regmap = devm_regmap_init_i2c(client, - &rt5033_battery_regmap_config); - if (IS_ERR(battery->regmap)) { - dev_err(&client->dev, "Failed to initialize regmap\n"); - return -EINVAL; - } - - i2c_set_clientdata(client, battery); - psy_cfg.drv_data = battery; - - battery->psy = power_supply_register(&client->dev, - &rt5033_battery_desc, &psy_cfg); - if (IS_ERR(battery->psy)) { - dev_err(&client->dev, "Failed to register power supply\n"); - ret = PTR_ERR(battery->psy); - return ret; - } - - return 0; -} - -static int rt5033_battery_remove(struct i2c_client *client) -{ - struct rt5033_battery *battery = i2c_get_clientdata(client); - - power_supply_unregister(battery->psy); - - return 0; -} - -static const struct i2c_device_id rt5033_battery_id[] = { - { "rt5033-battery", }, - { } -}; -MODULE_DEVICE_TABLE(i2c, rt5033_battery_id); - -static struct i2c_driver rt5033_battery_driver = { - .driver = { - .name = "rt5033-battery", - }, - .probe = rt5033_battery_probe, - .remove = rt5033_battery_remove, - .id_table = rt5033_battery_id, -}; -module_i2c_driver(rt5033_battery_driver); - -MODULE_DESCRIPTION("Richtek RT5033 fuel gauge driver"); -MODULE_AUTHOR("Beomho Seo "); -MODULE_LICENSE("GPL"); diff --git a/drivers/power/rt9455_charger.c b/drivers/power/rt9455_charger.c deleted file mode 100644 index cfdbde9daf94..000000000000 --- a/drivers/power/rt9455_charger.c +++ /dev/null @@ -1,1763 +0,0 @@ -/* - * Driver for Richtek RT9455WSC battery charger. - * - * Copyright (C) 2015 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; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define RT9455_MANUFACTURER "Richtek" -#define RT9455_MODEL_NAME "RT9455" -#define RT9455_DRIVER_NAME "rt9455-charger" - -#define RT9455_IRQ_NAME "interrupt" - -#define RT9455_PWR_RDY_DELAY 1 /* 1 second */ -#define RT9455_MAX_CHARGING_TIME 21600 /* 6 hrs */ -#define RT9455_BATT_PRESENCE_DELAY 60 /* 60 seconds */ - -#define RT9455_CHARGE_MODE 0x00 -#define RT9455_BOOST_MODE 0x01 - -#define RT9455_FAULT 0x03 - -#define RT9455_IAICR_100MA 0x00 -#define RT9455_IAICR_500MA 0x01 -#define RT9455_IAICR_NO_LIMIT 0x03 - -#define RT9455_CHARGE_DISABLE 0x00 -#define RT9455_CHARGE_ENABLE 0x01 - -#define RT9455_PWR_FAULT 0x00 -#define RT9455_PWR_GOOD 0x01 - -#define RT9455_REG_CTRL1 0x00 /* CTRL1 reg address */ -#define RT9455_REG_CTRL2 0x01 /* CTRL2 reg address */ -#define RT9455_REG_CTRL3 0x02 /* CTRL3 reg address */ -#define RT9455_REG_DEV_ID 0x03 /* DEV_ID reg address */ -#define RT9455_REG_CTRL4 0x04 /* CTRL4 reg address */ -#define RT9455_REG_CTRL5 0x05 /* CTRL5 reg address */ -#define RT9455_REG_CTRL6 0x06 /* CTRL6 reg address */ -#define RT9455_REG_CTRL7 0x07 /* CTRL7 reg address */ -#define RT9455_REG_IRQ1 0x08 /* IRQ1 reg address */ -#define RT9455_REG_IRQ2 0x09 /* IRQ2 reg address */ -#define RT9455_REG_IRQ3 0x0A /* IRQ3 reg address */ -#define RT9455_REG_MASK1 0x0B /* MASK1 reg address */ -#define RT9455_REG_MASK2 0x0C /* MASK2 reg address */ -#define RT9455_REG_MASK3 0x0D /* MASK3 reg address */ - -enum rt9455_fields { - F_STAT, F_BOOST, F_PWR_RDY, F_OTG_PIN_POLARITY, /* CTRL1 reg fields */ - - F_IAICR, F_TE_SHDN_EN, F_HIGHER_OCP, F_TE, F_IAICR_INT, F_HIZ, - F_OPA_MODE, /* CTRL2 reg fields */ - - F_VOREG, F_OTG_PL, F_OTG_EN, /* CTRL3 reg fields */ - - F_VENDOR_ID, F_CHIP_REV, /* DEV_ID reg fields */ - - F_RST, /* CTRL4 reg fields */ - - F_TMR_EN, F_MIVR, F_IPREC, F_IEOC_PERCENTAGE, /* CTRL5 reg fields*/ - - F_IAICR_SEL, F_ICHRG, F_VPREC, /* CTRL6 reg fields */ - - F_BATD_EN, F_CHG_EN, F_VMREG, /* CTRL7 reg fields */ - - F_TSDI, F_VINOVPI, F_BATAB, /* IRQ1 reg fields */ - - F_CHRVPI, F_CHBATOVI, F_CHTERMI, F_CHRCHGI, F_CH32MI, F_CHTREGI, - F_CHMIVRI, /* IRQ2 reg fields */ - - F_BSTBUSOVI, F_BSTOLI, F_BSTLOWVI, F_BST32SI, /* IRQ3 reg fields */ - - F_TSDM, F_VINOVPIM, F_BATABM, /* MASK1 reg fields */ - - F_CHRVPIM, F_CHBATOVIM, F_CHTERMIM, F_CHRCHGIM, F_CH32MIM, F_CHTREGIM, - F_CHMIVRIM, /* MASK2 reg fields */ - - F_BSTVINOVIM, F_BSTOLIM, F_BSTLOWVIM, F_BST32SIM, /* MASK3 reg fields */ - - F_MAX_FIELDS -}; - -static const struct reg_field rt9455_reg_fields[] = { - [F_STAT] = REG_FIELD(RT9455_REG_CTRL1, 4, 5), - [F_BOOST] = REG_FIELD(RT9455_REG_CTRL1, 3, 3), - [F_PWR_RDY] = REG_FIELD(RT9455_REG_CTRL1, 2, 2), - [F_OTG_PIN_POLARITY] = REG_FIELD(RT9455_REG_CTRL1, 1, 1), - - [F_IAICR] = REG_FIELD(RT9455_REG_CTRL2, 6, 7), - [F_TE_SHDN_EN] = REG_FIELD(RT9455_REG_CTRL2, 5, 5), - [F_HIGHER_OCP] = REG_FIELD(RT9455_REG_CTRL2, 4, 4), - [F_TE] = REG_FIELD(RT9455_REG_CTRL2, 3, 3), - [F_IAICR_INT] = REG_FIELD(RT9455_REG_CTRL2, 2, 2), - [F_HIZ] = REG_FIELD(RT9455_REG_CTRL2, 1, 1), - [F_OPA_MODE] = REG_FIELD(RT9455_REG_CTRL2, 0, 0), - - [F_VOREG] = REG_FIELD(RT9455_REG_CTRL3, 2, 7), - [F_OTG_PL] = REG_FIELD(RT9455_REG_CTRL3, 1, 1), - [F_OTG_EN] = REG_FIELD(RT9455_REG_CTRL3, 0, 0), - - [F_VENDOR_ID] = REG_FIELD(RT9455_REG_DEV_ID, 4, 7), - [F_CHIP_REV] = REG_FIELD(RT9455_REG_DEV_ID, 0, 3), - - [F_RST] = REG_FIELD(RT9455_REG_CTRL4, 7, 7), - - [F_TMR_EN] = REG_FIELD(RT9455_REG_CTRL5, 7, 7), - [F_MIVR] = REG_FIELD(RT9455_REG_CTRL5, 4, 5), - [F_IPREC] = REG_FIELD(RT9455_REG_CTRL5, 2, 3), - [F_IEOC_PERCENTAGE] = REG_FIELD(RT9455_REG_CTRL5, 0, 1), - - [F_IAICR_SEL] = REG_FIELD(RT9455_REG_CTRL6, 7, 7), - [F_ICHRG] = REG_FIELD(RT9455_REG_CTRL6, 4, 6), - [F_VPREC] = REG_FIELD(RT9455_REG_CTRL6, 0, 2), - - [F_BATD_EN] = REG_FIELD(RT9455_REG_CTRL7, 6, 6), - [F_CHG_EN] = REG_FIELD(RT9455_REG_CTRL7, 4, 4), - [F_VMREG] = REG_FIELD(RT9455_REG_CTRL7, 0, 3), - - [F_TSDI] = REG_FIELD(RT9455_REG_IRQ1, 7, 7), - [F_VINOVPI] = REG_FIELD(RT9455_REG_IRQ1, 6, 6), - [F_BATAB] = REG_FIELD(RT9455_REG_IRQ1, 0, 0), - - [F_CHRVPI] = REG_FIELD(RT9455_REG_IRQ2, 7, 7), - [F_CHBATOVI] = REG_FIELD(RT9455_REG_IRQ2, 5, 5), - [F_CHTERMI] = REG_FIELD(RT9455_REG_IRQ2, 4, 4), - [F_CHRCHGI] = REG_FIELD(RT9455_REG_IRQ2, 3, 3), - [F_CH32MI] = REG_FIELD(RT9455_REG_IRQ2, 2, 2), - [F_CHTREGI] = REG_FIELD(RT9455_REG_IRQ2, 1, 1), - [F_CHMIVRI] = REG_FIELD(RT9455_REG_IRQ2, 0, 0), - - [F_BSTBUSOVI] = REG_FIELD(RT9455_REG_IRQ3, 7, 7), - [F_BSTOLI] = REG_FIELD(RT9455_REG_IRQ3, 6, 6), - [F_BSTLOWVI] = REG_FIELD(RT9455_REG_IRQ3, 5, 5), - [F_BST32SI] = REG_FIELD(RT9455_REG_IRQ3, 3, 3), - - [F_TSDM] = REG_FIELD(RT9455_REG_MASK1, 7, 7), - [F_VINOVPIM] = REG_FIELD(RT9455_REG_MASK1, 6, 6), - [F_BATABM] = REG_FIELD(RT9455_REG_MASK1, 0, 0), - - [F_CHRVPIM] = REG_FIELD(RT9455_REG_MASK2, 7, 7), - [F_CHBATOVIM] = REG_FIELD(RT9455_REG_MASK2, 5, 5), - [F_CHTERMIM] = REG_FIELD(RT9455_REG_MASK2, 4, 4), - [F_CHRCHGIM] = REG_FIELD(RT9455_REG_MASK2, 3, 3), - [F_CH32MIM] = REG_FIELD(RT9455_REG_MASK2, 2, 2), - [F_CHTREGIM] = REG_FIELD(RT9455_REG_MASK2, 1, 1), - [F_CHMIVRIM] = REG_FIELD(RT9455_REG_MASK2, 0, 0), - - [F_BSTVINOVIM] = REG_FIELD(RT9455_REG_MASK3, 7, 7), - [F_BSTOLIM] = REG_FIELD(RT9455_REG_MASK3, 6, 6), - [F_BSTLOWVIM] = REG_FIELD(RT9455_REG_MASK3, 5, 5), - [F_BST32SIM] = REG_FIELD(RT9455_REG_MASK3, 3, 3), -}; - -#define GET_MASK(fid) (BIT(rt9455_reg_fields[fid].msb + 1) - \ - BIT(rt9455_reg_fields[fid].lsb)) - -/* - * Each array initialised below shows the possible real-world values for a - * group of bits belonging to RT9455 registers. The arrays are sorted in - * ascending order. The index of each real-world value represents the value - * that is encoded in the group of bits belonging to RT9455 registers. - */ -/* REG06[6:4] (ICHRG) in uAh */ -static const int rt9455_ichrg_values[] = { - 500000, 650000, 800000, 950000, 1100000, 1250000, 1400000, 1550000 -}; - -/* - * When the charger is in charge mode, REG02[7:2] represent battery regulation - * voltage. - */ -/* REG02[7:2] (VOREG) in uV */ -static const int rt9455_voreg_values[] = { - 3500000, 3520000, 3540000, 3560000, 3580000, 3600000, 3620000, 3640000, - 3660000, 3680000, 3700000, 3720000, 3740000, 3760000, 3780000, 3800000, - 3820000, 3840000, 3860000, 3880000, 3900000, 3920000, 3940000, 3960000, - 3980000, 4000000, 4020000, 4040000, 4060000, 4080000, 4100000, 4120000, - 4140000, 4160000, 4180000, 4200000, 4220000, 4240000, 4260000, 4280000, - 4300000, 4330000, 4350000, 4370000, 4390000, 4410000, 4430000, 4450000, - 4450000, 4450000, 4450000, 4450000, 4450000, 4450000, 4450000, 4450000, - 4450000, 4450000, 4450000, 4450000, 4450000, 4450000, 4450000, 4450000 -}; - -/* - * When the charger is in boost mode, REG02[7:2] represent boost output - * voltage. - */ -/* REG02[7:2] (Boost output voltage) in uV */ -static const int rt9455_boost_voltage_values[] = { - 4425000, 4450000, 4475000, 4500000, 4525000, 4550000, 4575000, 4600000, - 4625000, 4650000, 4675000, 4700000, 4725000, 4750000, 4775000, 4800000, - 4825000, 4850000, 4875000, 4900000, 4925000, 4950000, 4975000, 5000000, - 5025000, 5050000, 5075000, 5100000, 5125000, 5150000, 5175000, 5200000, - 5225000, 5250000, 5275000, 5300000, 5325000, 5350000, 5375000, 5400000, - 5425000, 5450000, 5475000, 5500000, 5525000, 5550000, 5575000, 5600000, - 5600000, 5600000, 5600000, 5600000, 5600000, 5600000, 5600000, 5600000, - 5600000, 5600000, 5600000, 5600000, 5600000, 5600000, 5600000, 5600000, -}; - -/* REG07[3:0] (VMREG) in uV */ -static const int rt9455_vmreg_values[] = { - 4200000, 4220000, 4240000, 4260000, 4280000, 4300000, 4320000, 4340000, - 4360000, 4380000, 4400000, 4430000, 4450000, 4450000, 4450000, 4450000 -}; - -/* REG05[5:4] (IEOC_PERCENTAGE) */ -static const int rt9455_ieoc_percentage_values[] = { - 10, 30, 20, 30 -}; - -/* REG05[1:0] (MIVR) in uV */ -static const int rt9455_mivr_values[] = { - 4000000, 4250000, 4500000, 5000000 -}; - -/* REG05[1:0] (IAICR) in uA */ -static const int rt9455_iaicr_values[] = { - 100000, 500000, 1000000, 2000000 -}; - -struct rt9455_info { - struct i2c_client *client; - struct regmap *regmap; - struct regmap_field *regmap_fields[F_MAX_FIELDS]; - struct power_supply *charger; -#if IS_ENABLED(CONFIG_USB_PHY) - struct usb_phy *usb_phy; - struct notifier_block nb; -#endif - struct delayed_work pwr_rdy_work; - struct delayed_work max_charging_time_work; - struct delayed_work batt_presence_work; - u32 voreg; - u32 boost_voltage; -}; - -/* - * Iterate through each element of the 'tbl' array until an element whose value - * is greater than v is found. Return the index of the respective element, - * or the index of the last element in the array, if no such element is found. - */ -static unsigned int rt9455_find_idx(const int tbl[], int tbl_size, int v) -{ - int i; - - /* - * No need to iterate until the last index in the table because - * if no element greater than v is found in the table, - * or if only the last element is greater than v, - * function returns the index of the last element. - */ - for (i = 0; i < tbl_size - 1; i++) - if (v <= tbl[i]) - return i; - - return (tbl_size - 1); -} - -static int rt9455_get_field_val(struct rt9455_info *info, - enum rt9455_fields field, - const int tbl[], int tbl_size, int *val) -{ - unsigned int v; - int ret; - - ret = regmap_field_read(info->regmap_fields[field], &v); - if (ret) - return ret; - - v = (v >= tbl_size) ? (tbl_size - 1) : v; - *val = tbl[v]; - - return 0; -} - -static int rt9455_set_field_val(struct rt9455_info *info, - enum rt9455_fields field, - const int tbl[], int tbl_size, int val) -{ - unsigned int idx = rt9455_find_idx(tbl, tbl_size, val); - - return regmap_field_write(info->regmap_fields[field], idx); -} - -static int rt9455_register_reset(struct rt9455_info *info) -{ - struct device *dev = &info->client->dev; - unsigned int v; - int ret, limit = 100; - - ret = regmap_field_write(info->regmap_fields[F_RST], 0x01); - if (ret) { - dev_err(dev, "Failed to set RST bit\n"); - return ret; - } - - /* - * To make sure that reset operation has finished, loop until RST bit - * is set to 0. - */ - do { - ret = regmap_field_read(info->regmap_fields[F_RST], &v); - if (ret) { - dev_err(dev, "Failed to read RST bit\n"); - return ret; - } - - if (!v) - break; - - usleep_range(10, 100); - } while (--limit); - - if (!limit) - return -EIO; - - return 0; -} - -/* Charger power supply property routines */ -static enum power_supply_property rt9455_charger_properties[] = { - POWER_SUPPLY_PROP_STATUS, - POWER_SUPPLY_PROP_HEALTH, - POWER_SUPPLY_PROP_PRESENT, - POWER_SUPPLY_PROP_ONLINE, - POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT, - POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX, - POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE, - POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX, - POWER_SUPPLY_PROP_SCOPE, - POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT, - POWER_SUPPLY_PROP_MODEL_NAME, - POWER_SUPPLY_PROP_MANUFACTURER, -}; - -static char *rt9455_charger_supplied_to[] = { - "main-battery", -}; - -static int rt9455_charger_get_status(struct rt9455_info *info, - union power_supply_propval *val) -{ - unsigned int v, pwr_rdy; - int ret; - - ret = regmap_field_read(info->regmap_fields[F_PWR_RDY], - &pwr_rdy); - if (ret) { - dev_err(&info->client->dev, "Failed to read PWR_RDY bit\n"); - return ret; - } - - /* - * If PWR_RDY bit is unset, the battery is discharging. Otherwise, - * STAT bits value must be checked. - */ - if (!pwr_rdy) { - val->intval = POWER_SUPPLY_STATUS_DISCHARGING; - return 0; - } - - ret = regmap_field_read(info->regmap_fields[F_STAT], &v); - if (ret) { - dev_err(&info->client->dev, "Failed to read STAT bits\n"); - return ret; - } - - switch (v) { - case 0: - /* - * If PWR_RDY bit is set, but STAT bits value is 0, the charger - * may be in one of the following cases: - * 1. CHG_EN bit is 0. - * 2. CHG_EN bit is 1 but the battery is not connected. - * In any of these cases, POWER_SUPPLY_STATUS_NOT_CHARGING is - * returned. - */ - val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; - return 0; - case 1: - val->intval = POWER_SUPPLY_STATUS_CHARGING; - return 0; - case 2: - val->intval = POWER_SUPPLY_STATUS_FULL; - return 0; - default: - val->intval = POWER_SUPPLY_STATUS_UNKNOWN; - return 0; - } -} - -static int rt9455_charger_get_health(struct rt9455_info *info, - union power_supply_propval *val) -{ - struct device *dev = &info->client->dev; - unsigned int v; - int ret; - - val->intval = POWER_SUPPLY_HEALTH_GOOD; - - ret = regmap_read(info->regmap, RT9455_REG_IRQ1, &v); - if (ret) { - dev_err(dev, "Failed to read IRQ1 register\n"); - return ret; - } - - if (v & GET_MASK(F_TSDI)) { - val->intval = POWER_SUPPLY_HEALTH_OVERHEAT; - return 0; - } - if (v & GET_MASK(F_VINOVPI)) { - val->intval = POWER_SUPPLY_HEALTH_OVERVOLTAGE; - return 0; - } - if (v & GET_MASK(F_BATAB)) { - val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; - return 0; - } - - ret = regmap_read(info->regmap, RT9455_REG_IRQ2, &v); - if (ret) { - dev_err(dev, "Failed to read IRQ2 register\n"); - return ret; - } - - if (v & GET_MASK(F_CHBATOVI)) { - val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; - return 0; - } - if (v & GET_MASK(F_CH32MI)) { - val->intval = POWER_SUPPLY_HEALTH_SAFETY_TIMER_EXPIRE; - return 0; - } - - ret = regmap_read(info->regmap, RT9455_REG_IRQ3, &v); - if (ret) { - dev_err(dev, "Failed to read IRQ3 register\n"); - return ret; - } - - if (v & GET_MASK(F_BSTBUSOVI)) { - val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; - return 0; - } - if (v & GET_MASK(F_BSTOLI)) { - val->intval = POWER_SUPPLY_HEALTH_OVERVOLTAGE; - return 0; - } - if (v & GET_MASK(F_BSTLOWVI)) { - val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; - return 0; - } - if (v & GET_MASK(F_BST32SI)) { - val->intval = POWER_SUPPLY_HEALTH_SAFETY_TIMER_EXPIRE; - return 0; - } - - ret = regmap_field_read(info->regmap_fields[F_STAT], &v); - if (ret) { - dev_err(dev, "Failed to read STAT bits\n"); - return ret; - } - - if (v == RT9455_FAULT) { - val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; - return 0; - } - - return 0; -} - -static int rt9455_charger_get_battery_presence(struct rt9455_info *info, - union power_supply_propval *val) -{ - unsigned int v; - int ret; - - ret = regmap_field_read(info->regmap_fields[F_BATAB], &v); - if (ret) { - dev_err(&info->client->dev, "Failed to read BATAB bit\n"); - return ret; - } - - /* - * Since BATAB is 1 when battery is NOT present and 0 otherwise, - * !BATAB is returned. - */ - val->intval = !v; - - return 0; -} - -static int rt9455_charger_get_online(struct rt9455_info *info, - union power_supply_propval *val) -{ - unsigned int v; - int ret; - - ret = regmap_field_read(info->regmap_fields[F_PWR_RDY], &v); - if (ret) { - dev_err(&info->client->dev, "Failed to read PWR_RDY bit\n"); - return ret; - } - - val->intval = (int)v; - - return 0; -} - -static int rt9455_charger_get_current(struct rt9455_info *info, - union power_supply_propval *val) -{ - int curr; - int ret; - - ret = rt9455_get_field_val(info, F_ICHRG, - rt9455_ichrg_values, - ARRAY_SIZE(rt9455_ichrg_values), - &curr); - if (ret) { - dev_err(&info->client->dev, "Failed to read ICHRG value\n"); - return ret; - } - - val->intval = curr; - - return 0; -} - -static int rt9455_charger_get_current_max(struct rt9455_info *info, - union power_supply_propval *val) -{ - int idx = ARRAY_SIZE(rt9455_ichrg_values) - 1; - - val->intval = rt9455_ichrg_values[idx]; - - return 0; -} - -static int rt9455_charger_get_voltage(struct rt9455_info *info, - union power_supply_propval *val) -{ - int voltage; - int ret; - - ret = rt9455_get_field_val(info, F_VOREG, - rt9455_voreg_values, - ARRAY_SIZE(rt9455_voreg_values), - &voltage); - if (ret) { - dev_err(&info->client->dev, "Failed to read VOREG value\n"); - return ret; - } - - val->intval = voltage; - - return 0; -} - -static int rt9455_charger_get_voltage_max(struct rt9455_info *info, - union power_supply_propval *val) -{ - int idx = ARRAY_SIZE(rt9455_vmreg_values) - 1; - - val->intval = rt9455_vmreg_values[idx]; - - return 0; -} - -static int rt9455_charger_get_term_current(struct rt9455_info *info, - union power_supply_propval *val) -{ - struct device *dev = &info->client->dev; - int ichrg, ieoc_percentage, ret; - - ret = rt9455_get_field_val(info, F_ICHRG, - rt9455_ichrg_values, - ARRAY_SIZE(rt9455_ichrg_values), - &ichrg); - if (ret) { - dev_err(dev, "Failed to read ICHRG value\n"); - return ret; - } - - ret = rt9455_get_field_val(info, F_IEOC_PERCENTAGE, - rt9455_ieoc_percentage_values, - ARRAY_SIZE(rt9455_ieoc_percentage_values), - &ieoc_percentage); - if (ret) { - dev_err(dev, "Failed to read IEOC value\n"); - return ret; - } - - val->intval = ichrg * ieoc_percentage / 100; - - return 0; -} - -static int rt9455_charger_get_property(struct power_supply *psy, - enum power_supply_property psp, - union power_supply_propval *val) -{ - struct rt9455_info *info = power_supply_get_drvdata(psy); - - switch (psp) { - case POWER_SUPPLY_PROP_STATUS: - return rt9455_charger_get_status(info, val); - case POWER_SUPPLY_PROP_HEALTH: - return rt9455_charger_get_health(info, val); - case POWER_SUPPLY_PROP_PRESENT: - return rt9455_charger_get_battery_presence(info, val); - case POWER_SUPPLY_PROP_ONLINE: - return rt9455_charger_get_online(info, val); - case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: - return rt9455_charger_get_current(info, val); - case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX: - return rt9455_charger_get_current_max(info, val); - case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: - return rt9455_charger_get_voltage(info, val); - case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX: - return rt9455_charger_get_voltage_max(info, val); - case POWER_SUPPLY_PROP_SCOPE: - val->intval = POWER_SUPPLY_SCOPE_SYSTEM; - return 0; - case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT: - return rt9455_charger_get_term_current(info, val); - case POWER_SUPPLY_PROP_MODEL_NAME: - val->strval = RT9455_MODEL_NAME; - return 0; - case POWER_SUPPLY_PROP_MANUFACTURER: - val->strval = RT9455_MANUFACTURER; - return 0; - default: - return -ENODATA; - } -} - -static int rt9455_hw_init(struct rt9455_info *info, u32 ichrg, - u32 ieoc_percentage, - u32 mivr, u32 iaicr) -{ - struct device *dev = &info->client->dev; - int idx, ret; - - ret = rt9455_register_reset(info); - if (ret) { - dev_err(dev, "Power On Reset failed\n"); - return ret; - } - - /* Set TE bit in order to enable end of charge detection */ - ret = regmap_field_write(info->regmap_fields[F_TE], 1); - if (ret) { - dev_err(dev, "Failed to set TE bit\n"); - return ret; - } - - /* Set TE_SHDN_EN bit in order to enable end of charge detection */ - ret = regmap_field_write(info->regmap_fields[F_TE_SHDN_EN], 1); - if (ret) { - dev_err(dev, "Failed to set TE_SHDN_EN bit\n"); - return ret; - } - - /* - * Set BATD_EN bit in order to enable battery detection - * when charging is done - */ - ret = regmap_field_write(info->regmap_fields[F_BATD_EN], 1); - if (ret) { - dev_err(dev, "Failed to set BATD_EN bit\n"); - return ret; - } - - /* - * Disable Safety Timer. In charge mode, this timer terminates charging - * if no read or write via I2C is done within 32 minutes. This timer - * avoids overcharging the baterry when the OS is not loaded and the - * charger is connected to a power source. - * In boost mode, this timer triggers BST32SI interrupt if no read or - * write via I2C is done within 32 seconds. - * When the OS is loaded and the charger driver is inserted, it is used - * delayed_work, named max_charging_time_work, to avoid overcharging - * the battery. - */ - ret = regmap_field_write(info->regmap_fields[F_TMR_EN], 0x00); - if (ret) { - dev_err(dev, "Failed to disable Safety Timer\n"); - return ret; - } - - /* Set ICHRG to value retrieved from device-specific data */ - ret = rt9455_set_field_val(info, F_ICHRG, - rt9455_ichrg_values, - ARRAY_SIZE(rt9455_ichrg_values), ichrg); - if (ret) { - dev_err(dev, "Failed to set ICHRG value\n"); - return ret; - } - - /* Set IEOC Percentage to value retrieved from device-specific data */ - ret = rt9455_set_field_val(info, F_IEOC_PERCENTAGE, - rt9455_ieoc_percentage_values, - ARRAY_SIZE(rt9455_ieoc_percentage_values), - ieoc_percentage); - if (ret) { - dev_err(dev, "Failed to set IEOC Percentage value\n"); - return ret; - } - - /* Set VOREG to value retrieved from device-specific data */ - ret = rt9455_set_field_val(info, F_VOREG, - rt9455_voreg_values, - ARRAY_SIZE(rt9455_voreg_values), - info->voreg); - if (ret) { - dev_err(dev, "Failed to set VOREG value\n"); - return ret; - } - - /* Set VMREG value to maximum (4.45V). */ - idx = ARRAY_SIZE(rt9455_vmreg_values) - 1; - ret = rt9455_set_field_val(info, F_VMREG, - rt9455_vmreg_values, - ARRAY_SIZE(rt9455_vmreg_values), - rt9455_vmreg_values[idx]); - if (ret) { - dev_err(dev, "Failed to set VMREG value\n"); - return ret; - } - - /* - * Set MIVR to value retrieved from device-specific data. - * If no value is specified, default value for MIVR is 4.5V. - */ - if (mivr == -1) - mivr = 4500000; - - ret = rt9455_set_field_val(info, F_MIVR, - rt9455_mivr_values, - ARRAY_SIZE(rt9455_mivr_values), mivr); - if (ret) { - dev_err(dev, "Failed to set MIVR value\n"); - return ret; - } - - /* - * Set IAICR to value retrieved from device-specific data. - * If no value is specified, default value for IAICR is 500 mA. - */ - if (iaicr == -1) - iaicr = 500000; - - ret = rt9455_set_field_val(info, F_IAICR, - rt9455_iaicr_values, - ARRAY_SIZE(rt9455_iaicr_values), iaicr); - if (ret) { - dev_err(dev, "Failed to set IAICR value\n"); - return ret; - } - - /* - * Set IAICR_INT bit so that IAICR value is determined by IAICR bits - * and not by OTG pin. - */ - ret = regmap_field_write(info->regmap_fields[F_IAICR_INT], 0x01); - if (ret) { - dev_err(dev, "Failed to set IAICR_INT bit\n"); - return ret; - } - - /* - * Disable CHMIVRI interrupt. Because the driver sets MIVR value, - * CHMIVRI is triggered, but there is no action to be taken by the - * driver when CHMIVRI is triggered. - */ - ret = regmap_field_write(info->regmap_fields[F_CHMIVRIM], 0x01); - if (ret) { - dev_err(dev, "Failed to mask CHMIVRI interrupt\n"); - return ret; - } - - return 0; -} - -#if IS_ENABLED(CONFIG_USB_PHY) -/* - * Before setting the charger into boost mode, boost output voltage is - * set. This is needed because boost output voltage may differ from battery - * regulation voltage. F_VOREG bits represent either battery regulation voltage - * or boost output voltage, depending on the mode the charger is. Both battery - * regulation voltage and boost output voltage are read from DT/ACPI during - * probe. - */ -static int rt9455_set_boost_voltage_before_boost_mode(struct rt9455_info *info) -{ - struct device *dev = &info->client->dev; - int ret; - - ret = rt9455_set_field_val(info, F_VOREG, - rt9455_boost_voltage_values, - ARRAY_SIZE(rt9455_boost_voltage_values), - info->boost_voltage); - if (ret) { - dev_err(dev, "Failed to set boost output voltage value\n"); - return ret; - } - - return 0; -} -#endif - -/* - * Before setting the charger into charge mode, battery regulation voltage is - * set. This is needed because boost output voltage may differ from battery - * regulation voltage. F_VOREG bits represent either battery regulation voltage - * or boost output voltage, depending on the mode the charger is. Both battery - * regulation voltage and boost output voltage are read from DT/ACPI during - * probe. - */ -static int rt9455_set_voreg_before_charge_mode(struct rt9455_info *info) -{ - struct device *dev = &info->client->dev; - int ret; - - ret = rt9455_set_field_val(info, F_VOREG, - rt9455_voreg_values, - ARRAY_SIZE(rt9455_voreg_values), - info->voreg); - if (ret) { - dev_err(dev, "Failed to set VOREG value\n"); - return ret; - } - - return 0; -} - -static int rt9455_irq_handler_check_irq1_register(struct rt9455_info *info, - bool *_is_battery_absent, - bool *_alert_userspace) -{ - unsigned int irq1, mask1, mask2; - struct device *dev = &info->client->dev; - bool is_battery_absent = false; - bool alert_userspace = false; - int ret; - - ret = regmap_read(info->regmap, RT9455_REG_IRQ1, &irq1); - if (ret) { - dev_err(dev, "Failed to read IRQ1 register\n"); - return ret; - } - - ret = regmap_read(info->regmap, RT9455_REG_MASK1, &mask1); - if (ret) { - dev_err(dev, "Failed to read MASK1 register\n"); - return ret; - } - - if (irq1 & GET_MASK(F_TSDI)) { - dev_err(dev, "Thermal shutdown fault occurred\n"); - alert_userspace = true; - } - - if (irq1 & GET_MASK(F_VINOVPI)) { - dev_err(dev, "Overvoltage input occurred\n"); - alert_userspace = true; - } - - if (irq1 & GET_MASK(F_BATAB)) { - dev_err(dev, "Battery absence occurred\n"); - is_battery_absent = true; - alert_userspace = true; - - if ((mask1 & GET_MASK(F_BATABM)) == 0) { - ret = regmap_field_write(info->regmap_fields[F_BATABM], - 0x01); - if (ret) { - dev_err(dev, "Failed to mask BATAB interrupt\n"); - return ret; - } - } - - ret = regmap_read(info->regmap, RT9455_REG_MASK2, &mask2); - if (ret) { - dev_err(dev, "Failed to read MASK2 register\n"); - return ret; - } - - if (mask2 & GET_MASK(F_CHTERMIM)) { - ret = regmap_field_write( - info->regmap_fields[F_CHTERMIM], 0x00); - if (ret) { - dev_err(dev, "Failed to unmask CHTERMI interrupt\n"); - return ret; - } - } - - if (mask2 & GET_MASK(F_CHRCHGIM)) { - ret = regmap_field_write( - info->regmap_fields[F_CHRCHGIM], 0x00); - if (ret) { - dev_err(dev, "Failed to unmask CHRCHGI interrupt\n"); - return ret; - } - } - - /* - * When the battery is absent, max_charging_time_work is - * cancelled, since no charging is done. - */ - cancel_delayed_work_sync(&info->max_charging_time_work); - /* - * Since no interrupt is triggered when the battery is - * reconnected, max_charging_time_work is not rescheduled. - * Therefore, batt_presence_work is scheduled to check whether - * the battery is still absent or not. - */ - queue_delayed_work(system_power_efficient_wq, - &info->batt_presence_work, - RT9455_BATT_PRESENCE_DELAY * HZ); - } - - *_is_battery_absent = is_battery_absent; - - if (alert_userspace) - *_alert_userspace = alert_userspace; - - return 0; -} - -static int rt9455_irq_handler_check_irq2_register(struct rt9455_info *info, - bool is_battery_absent, - bool *_alert_userspace) -{ - unsigned int irq2, mask2; - struct device *dev = &info->client->dev; - bool alert_userspace = false; - int ret; - - ret = regmap_read(info->regmap, RT9455_REG_IRQ2, &irq2); - if (ret) { - dev_err(dev, "Failed to read IRQ2 register\n"); - return ret; - } - - ret = regmap_read(info->regmap, RT9455_REG_MASK2, &mask2); - if (ret) { - dev_err(dev, "Failed to read MASK2 register\n"); - return ret; - } - - if (irq2 & GET_MASK(F_CHRVPI)) { - dev_dbg(dev, "Charger fault occurred\n"); - /* - * CHRVPI bit is set in 2 cases: - * 1. when the power source is connected to the charger. - * 2. when the power source is disconnected from the charger. - * To identify the case, PWR_RDY bit is checked. Because - * PWR_RDY bit is set / cleared after CHRVPI interrupt is - * triggered, it is used delayed_work to later read PWR_RDY bit. - * Also, do not set to true alert_userspace, because there is no - * need to notify userspace when CHRVPI interrupt has occurred. - * Userspace will be notified after PWR_RDY bit is read. - */ - queue_delayed_work(system_power_efficient_wq, - &info->pwr_rdy_work, - RT9455_PWR_RDY_DELAY * HZ); - } - if (irq2 & GET_MASK(F_CHBATOVI)) { - dev_err(dev, "Battery OVP occurred\n"); - alert_userspace = true; - } - if (irq2 & GET_MASK(F_CHTERMI)) { - dev_dbg(dev, "Charge terminated\n"); - if (!is_battery_absent) { - if ((mask2 & GET_MASK(F_CHTERMIM)) == 0) { - ret = regmap_field_write( - info->regmap_fields[F_CHTERMIM], 0x01); - if (ret) { - dev_err(dev, "Failed to mask CHTERMI interrupt\n"); - return ret; - } - /* - * Update MASK2 value, since CHTERMIM bit is - * set. - */ - mask2 = mask2 | GET_MASK(F_CHTERMIM); - } - cancel_delayed_work_sync(&info->max_charging_time_work); - alert_userspace = true; - } - } - if (irq2 & GET_MASK(F_CHRCHGI)) { - dev_dbg(dev, "Recharge request\n"); - ret = regmap_field_write(info->regmap_fields[F_CHG_EN], - RT9455_CHARGE_ENABLE); - if (ret) { - dev_err(dev, "Failed to enable charging\n"); - return ret; - } - if (mask2 & GET_MASK(F_CHTERMIM)) { - ret = regmap_field_write( - info->regmap_fields[F_CHTERMIM], 0x00); - if (ret) { - dev_err(dev, "Failed to unmask CHTERMI interrupt\n"); - return ret; - } - /* Update MASK2 value, since CHTERMIM bit is cleared. */ - mask2 = mask2 & ~GET_MASK(F_CHTERMIM); - } - if (!is_battery_absent) { - /* - * No need to check whether the charger is connected to - * power source when CHRCHGI is received, since CHRCHGI - * is not triggered if the charger is not connected to - * the power source. - */ - queue_delayed_work(system_power_efficient_wq, - &info->max_charging_time_work, - RT9455_MAX_CHARGING_TIME * HZ); - alert_userspace = true; - } - } - if (irq2 & GET_MASK(F_CH32MI)) { - dev_err(dev, "Charger fault. 32 mins timeout occurred\n"); - alert_userspace = true; - } - if (irq2 & GET_MASK(F_CHTREGI)) { - dev_warn(dev, - "Charger warning. Thermal regulation loop active\n"); - alert_userspace = true; - } - if (irq2 & GET_MASK(F_CHMIVRI)) { - dev_dbg(dev, - "Charger warning. Input voltage MIVR loop active\n"); - } - - if (alert_userspace) - *_alert_userspace = alert_userspace; - - return 0; -} - -static int rt9455_irq_handler_check_irq3_register(struct rt9455_info *info, - bool *_alert_userspace) -{ - unsigned int irq3, mask3; - struct device *dev = &info->client->dev; - bool alert_userspace = false; - int ret; - - ret = regmap_read(info->regmap, RT9455_REG_IRQ3, &irq3); - if (ret) { - dev_err(dev, "Failed to read IRQ3 register\n"); - return ret; - } - - ret = regmap_read(info->regmap, RT9455_REG_MASK3, &mask3); - if (ret) { - dev_err(dev, "Failed to read MASK3 register\n"); - return ret; - } - - if (irq3 & GET_MASK(F_BSTBUSOVI)) { - dev_err(dev, "Boost fault. Overvoltage input occurred\n"); - alert_userspace = true; - } - if (irq3 & GET_MASK(F_BSTOLI)) { - dev_err(dev, "Boost fault. Overload\n"); - alert_userspace = true; - } - if (irq3 & GET_MASK(F_BSTLOWVI)) { - dev_err(dev, "Boost fault. Battery voltage too low\n"); - alert_userspace = true; - } - if (irq3 & GET_MASK(F_BST32SI)) { - dev_err(dev, "Boost fault. 32 seconds timeout occurred.\n"); - alert_userspace = true; - } - - if (alert_userspace) { - dev_info(dev, "Boost fault occurred, therefore the charger goes into charge mode\n"); - ret = rt9455_set_voreg_before_charge_mode(info); - if (ret) { - dev_err(dev, "Failed to set VOREG before entering charge mode\n"); - return ret; - } - ret = regmap_field_write(info->regmap_fields[F_OPA_MODE], - RT9455_CHARGE_MODE); - if (ret) { - dev_err(dev, "Failed to set charger in charge mode\n"); - return ret; - } - *_alert_userspace = alert_userspace; - } - - return 0; -} - -static irqreturn_t rt9455_irq_handler_thread(int irq, void *data) -{ - struct rt9455_info *info = data; - struct device *dev; - bool alert_userspace = false; - bool is_battery_absent = false; - unsigned int status; - int ret; - - if (!info) - return IRQ_NONE; - - dev = &info->client->dev; - - if (irq != info->client->irq) { - dev_err(dev, "Interrupt is not for RT9455 charger\n"); - return IRQ_NONE; - } - - ret = regmap_field_read(info->regmap_fields[F_STAT], &status); - if (ret) { - dev_err(dev, "Failed to read STAT bits\n"); - return IRQ_HANDLED; - } - dev_dbg(dev, "Charger status is %d\n", status); - - /* - * Each function that processes an IRQ register receives as output - * parameter alert_userspace pointer. alert_userspace is set to true - * in such a function only if an interrupt has occurred in the - * respective interrupt register. This way, it is avoided the following - * case: interrupt occurs only in IRQ1 register, - * rt9455_irq_handler_check_irq1_register() function sets to true - * alert_userspace, but rt9455_irq_handler_check_irq2_register() - * and rt9455_irq_handler_check_irq3_register() functions set to false - * alert_userspace and power_supply_changed() is never called. - */ - ret = rt9455_irq_handler_check_irq1_register(info, &is_battery_absent, - &alert_userspace); - if (ret) { - dev_err(dev, "Failed to handle IRQ1 register\n"); - return IRQ_HANDLED; - } - - ret = rt9455_irq_handler_check_irq2_register(info, is_battery_absent, - &alert_userspace); - if (ret) { - dev_err(dev, "Failed to handle IRQ2 register\n"); - return IRQ_HANDLED; - } - - ret = rt9455_irq_handler_check_irq3_register(info, &alert_userspace); - if (ret) { - dev_err(dev, "Failed to handle IRQ3 register\n"); - return IRQ_HANDLED; - } - - if (alert_userspace) { - /* - * Sometimes, an interrupt occurs while rt9455_probe() function - * is executing and power_supply_register() is not yet called. - * Do not call power_supply_changed() in this case. - */ - if (info->charger) - power_supply_changed(info->charger); - } - - return IRQ_HANDLED; -} - -static int rt9455_discover_charger(struct rt9455_info *info, u32 *ichrg, - u32 *ieoc_percentage, - u32 *mivr, u32 *iaicr) -{ - struct device *dev = &info->client->dev; - int ret; - - if (!dev->of_node && !ACPI_HANDLE(dev)) { - dev_err(dev, "No support for either device tree or ACPI\n"); - return -EINVAL; - } - /* - * ICHRG, IEOC_PERCENTAGE, VOREG and boost output voltage are mandatory - * parameters. - */ - ret = device_property_read_u32(dev, "richtek,output-charge-current", - ichrg); - if (ret) { - dev_err(dev, "Error: missing \"output-charge-current\" property\n"); - return ret; - } - - ret = device_property_read_u32(dev, "richtek,end-of-charge-percentage", - ieoc_percentage); - if (ret) { - dev_err(dev, "Error: missing \"end-of-charge-percentage\" property\n"); - return ret; - } - - ret = device_property_read_u32(dev, - "richtek,battery-regulation-voltage", - &info->voreg); - if (ret) { - dev_err(dev, "Error: missing \"battery-regulation-voltage\" property\n"); - return ret; - } - - ret = device_property_read_u32(dev, "richtek,boost-output-voltage", - &info->boost_voltage); - if (ret) { - dev_err(dev, "Error: missing \"boost-output-voltage\" property\n"); - return ret; - } - - /* - * MIVR and IAICR are optional parameters. Do not return error if one of - * them is not present in ACPI table or device tree specification. - */ - device_property_read_u32(dev, "richtek,min-input-voltage-regulation", - mivr); - device_property_read_u32(dev, "richtek,avg-input-current-regulation", - iaicr); - - return 0; -} - -#if IS_ENABLED(CONFIG_USB_PHY) -static int rt9455_usb_event_none(struct rt9455_info *info, - u8 opa_mode, u8 iaicr) -{ - struct device *dev = &info->client->dev; - int ret; - - if (opa_mode == RT9455_BOOST_MODE) { - ret = rt9455_set_voreg_before_charge_mode(info); - if (ret) { - dev_err(dev, "Failed to set VOREG before entering charge mode\n"); - return ret; - } - /* - * If the charger is in boost mode, and it has received - * USB_EVENT_NONE, this means the consumer device powered by the - * charger is not connected anymore. - * In this case, the charger goes into charge mode. - */ - dev_dbg(dev, "USB_EVENT_NONE received, therefore the charger goes into charge mode\n"); - ret = regmap_field_write(info->regmap_fields[F_OPA_MODE], - RT9455_CHARGE_MODE); - if (ret) { - dev_err(dev, "Failed to set charger in charge mode\n"); - return NOTIFY_DONE; - } - } - - dev_dbg(dev, "USB_EVENT_NONE received, therefore IAICR is set to its minimum value\n"); - if (iaicr != RT9455_IAICR_100MA) { - ret = regmap_field_write(info->regmap_fields[F_IAICR], - RT9455_IAICR_100MA); - if (ret) { - dev_err(dev, "Failed to set IAICR value\n"); - return NOTIFY_DONE; - } - } - - return NOTIFY_OK; -} - -static int rt9455_usb_event_vbus(struct rt9455_info *info, - u8 opa_mode, u8 iaicr) -{ - struct device *dev = &info->client->dev; - int ret; - - if (opa_mode == RT9455_BOOST_MODE) { - ret = rt9455_set_voreg_before_charge_mode(info); - if (ret) { - dev_err(dev, "Failed to set VOREG before entering charge mode\n"); - return ret; - } - /* - * If the charger is in boost mode, and it has received - * USB_EVENT_VBUS, this means the consumer device powered by the - * charger is not connected anymore. - * In this case, the charger goes into charge mode. - */ - dev_dbg(dev, "USB_EVENT_VBUS received, therefore the charger goes into charge mode\n"); - ret = regmap_field_write(info->regmap_fields[F_OPA_MODE], - RT9455_CHARGE_MODE); - if (ret) { - dev_err(dev, "Failed to set charger in charge mode\n"); - return NOTIFY_DONE; - } - } - - dev_dbg(dev, "USB_EVENT_VBUS received, therefore IAICR is set to 500 mA\n"); - if (iaicr != RT9455_IAICR_500MA) { - ret = regmap_field_write(info->regmap_fields[F_IAICR], - RT9455_IAICR_500MA); - if (ret) { - dev_err(dev, "Failed to set IAICR value\n"); - return NOTIFY_DONE; - } - } - - return NOTIFY_OK; -} - -static int rt9455_usb_event_id(struct rt9455_info *info, - u8 opa_mode, u8 iaicr) -{ - struct device *dev = &info->client->dev; - int ret; - - if (opa_mode == RT9455_CHARGE_MODE) { - ret = rt9455_set_boost_voltage_before_boost_mode(info); - if (ret) { - dev_err(dev, "Failed to set boost output voltage before entering boost mode\n"); - return ret; - } - /* - * If the charger is in charge mode, and it has received - * USB_EVENT_ID, this means a consumer device is connected and - * it should be powered by the charger. - * In this case, the charger goes into boost mode. - */ - dev_dbg(dev, "USB_EVENT_ID received, therefore the charger goes into boost mode\n"); - ret = regmap_field_write(info->regmap_fields[F_OPA_MODE], - RT9455_BOOST_MODE); - if (ret) { - dev_err(dev, "Failed to set charger in boost mode\n"); - return NOTIFY_DONE; - } - } - - dev_dbg(dev, "USB_EVENT_ID received, therefore IAICR is set to its minimum value\n"); - if (iaicr != RT9455_IAICR_100MA) { - ret = regmap_field_write(info->regmap_fields[F_IAICR], - RT9455_IAICR_100MA); - if (ret) { - dev_err(dev, "Failed to set IAICR value\n"); - return NOTIFY_DONE; - } - } - - return NOTIFY_OK; -} - -static int rt9455_usb_event_charger(struct rt9455_info *info, - u8 opa_mode, u8 iaicr) -{ - struct device *dev = &info->client->dev; - int ret; - - if (opa_mode == RT9455_BOOST_MODE) { - ret = rt9455_set_voreg_before_charge_mode(info); - if (ret) { - dev_err(dev, "Failed to set VOREG before entering charge mode\n"); - return ret; - } - /* - * If the charger is in boost mode, and it has received - * USB_EVENT_CHARGER, this means the consumer device powered by - * the charger is not connected anymore. - * In this case, the charger goes into charge mode. - */ - dev_dbg(dev, "USB_EVENT_CHARGER received, therefore the charger goes into charge mode\n"); - ret = regmap_field_write(info->regmap_fields[F_OPA_MODE], - RT9455_CHARGE_MODE); - if (ret) { - dev_err(dev, "Failed to set charger in charge mode\n"); - return NOTIFY_DONE; - } - } - - dev_dbg(dev, "USB_EVENT_CHARGER received, therefore IAICR is set to no current limit\n"); - if (iaicr != RT9455_IAICR_NO_LIMIT) { - ret = regmap_field_write(info->regmap_fields[F_IAICR], - RT9455_IAICR_NO_LIMIT); - if (ret) { - dev_err(dev, "Failed to set IAICR value\n"); - return NOTIFY_DONE; - } - } - - return NOTIFY_OK; -} - -static int rt9455_usb_event(struct notifier_block *nb, - unsigned long event, void *power) -{ - struct rt9455_info *info = container_of(nb, struct rt9455_info, nb); - struct device *dev = &info->client->dev; - unsigned int opa_mode, iaicr; - int ret; - - /* - * Determine whether the charger is in charge mode - * or in boost mode. - */ - ret = regmap_field_read(info->regmap_fields[F_OPA_MODE], - &opa_mode); - if (ret) { - dev_err(dev, "Failed to read OPA_MODE value\n"); - return NOTIFY_DONE; - } - - ret = regmap_field_read(info->regmap_fields[F_IAICR], - &iaicr); - if (ret) { - dev_err(dev, "Failed to read IAICR value\n"); - return NOTIFY_DONE; - } - - dev_dbg(dev, "Received USB event %lu\n", event); - switch (event) { - case USB_EVENT_NONE: - return rt9455_usb_event_none(info, opa_mode, iaicr); - case USB_EVENT_VBUS: - return rt9455_usb_event_vbus(info, opa_mode, iaicr); - case USB_EVENT_ID: - return rt9455_usb_event_id(info, opa_mode, iaicr); - case USB_EVENT_CHARGER: - return rt9455_usb_event_charger(info, opa_mode, iaicr); - default: - dev_err(dev, "Unknown USB event\n"); - } - return NOTIFY_DONE; -} -#endif - -static void rt9455_pwr_rdy_work_callback(struct work_struct *work) -{ - struct rt9455_info *info = container_of(work, struct rt9455_info, - pwr_rdy_work.work); - struct device *dev = &info->client->dev; - unsigned int pwr_rdy; - int ret; - - ret = regmap_field_read(info->regmap_fields[F_PWR_RDY], &pwr_rdy); - if (ret) { - dev_err(dev, "Failed to read PWR_RDY bit\n"); - return; - } - switch (pwr_rdy) { - case RT9455_PWR_FAULT: - dev_dbg(dev, "Charger disconnected from power source\n"); - cancel_delayed_work_sync(&info->max_charging_time_work); - break; - case RT9455_PWR_GOOD: - dev_dbg(dev, "Charger connected to power source\n"); - ret = regmap_field_write(info->regmap_fields[F_CHG_EN], - RT9455_CHARGE_ENABLE); - if (ret) { - dev_err(dev, "Failed to enable charging\n"); - return; - } - queue_delayed_work(system_power_efficient_wq, - &info->max_charging_time_work, - RT9455_MAX_CHARGING_TIME * HZ); - break; - } - /* - * Notify userspace that the charger has been either connected to or - * disconnected from the power source. - */ - power_supply_changed(info->charger); -} - -static void rt9455_max_charging_time_work_callback(struct work_struct *work) -{ - struct rt9455_info *info = container_of(work, struct rt9455_info, - max_charging_time_work.work); - struct device *dev = &info->client->dev; - int ret; - - dev_err(dev, "Battery has been charging for at least 6 hours and is not yet fully charged. Battery is dead, therefore charging is disabled.\n"); - ret = regmap_field_write(info->regmap_fields[F_CHG_EN], - RT9455_CHARGE_DISABLE); - if (ret) - dev_err(dev, "Failed to disable charging\n"); -} - -static void rt9455_batt_presence_work_callback(struct work_struct *work) -{ - struct rt9455_info *info = container_of(work, struct rt9455_info, - batt_presence_work.work); - struct device *dev = &info->client->dev; - unsigned int irq1, mask1; - int ret; - - ret = regmap_read(info->regmap, RT9455_REG_IRQ1, &irq1); - if (ret) { - dev_err(dev, "Failed to read IRQ1 register\n"); - return; - } - - /* - * If the battery is still absent, batt_presence_work is rescheduled. - * Otherwise, max_charging_time is scheduled. - */ - if (irq1 & GET_MASK(F_BATAB)) { - queue_delayed_work(system_power_efficient_wq, - &info->batt_presence_work, - RT9455_BATT_PRESENCE_DELAY * HZ); - } else { - queue_delayed_work(system_power_efficient_wq, - &info->max_charging_time_work, - RT9455_MAX_CHARGING_TIME * HZ); - - ret = regmap_read(info->regmap, RT9455_REG_MASK1, &mask1); - if (ret) { - dev_err(dev, "Failed to read MASK1 register\n"); - return; - } - - if (mask1 & GET_MASK(F_BATABM)) { - ret = regmap_field_write(info->regmap_fields[F_BATABM], - 0x00); - if (ret) - dev_err(dev, "Failed to unmask BATAB interrupt\n"); - } - /* - * Notify userspace that the battery is now connected to the - * charger. - */ - power_supply_changed(info->charger); - } -} - -static const struct power_supply_desc rt9455_charger_desc = { - .name = RT9455_DRIVER_NAME, - .type = POWER_SUPPLY_TYPE_USB, - .properties = rt9455_charger_properties, - .num_properties = ARRAY_SIZE(rt9455_charger_properties), - .get_property = rt9455_charger_get_property, -}; - -static bool rt9455_is_writeable_reg(struct device *dev, unsigned int reg) -{ - switch (reg) { - case RT9455_REG_DEV_ID: - case RT9455_REG_IRQ1: - case RT9455_REG_IRQ2: - case RT9455_REG_IRQ3: - return false; - default: - return true; - } -} - -static bool rt9455_is_volatile_reg(struct device *dev, unsigned int reg) -{ - switch (reg) { - case RT9455_REG_DEV_ID: - case RT9455_REG_CTRL5: - case RT9455_REG_CTRL6: - return false; - default: - return true; - } -} - -static const struct regmap_config rt9455_regmap_config = { - .reg_bits = 8, - .val_bits = 8, - .writeable_reg = rt9455_is_writeable_reg, - .volatile_reg = rt9455_is_volatile_reg, - .max_register = RT9455_REG_MASK3, - .cache_type = REGCACHE_RBTREE, -}; - -static int rt9455_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 rt9455_info *info; - struct power_supply_config rt9455_charger_config = {}; - /* - * Mandatory device-specific data values. Also, VOREG and boost output - * voltage are mandatory values, but they are stored in rt9455_info - * structure. - */ - u32 ichrg, ieoc_percentage; - /* Optional device-specific data values. */ - u32 mivr = -1, iaicr = -1; - int i, ret; - - if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) { - dev_err(dev, "No support for SMBUS_BYTE_DATA\n"); - return -ENODEV; - } - info = devm_kzalloc(dev, sizeof(*info), GFP_KERNEL); - if (!info) - return -ENOMEM; - - info->client = client; - i2c_set_clientdata(client, info); - - info->regmap = devm_regmap_init_i2c(client, - &rt9455_regmap_config); - if (IS_ERR(info->regmap)) { - dev_err(dev, "Failed to initialize register map\n"); - return -EINVAL; - } - - for (i = 0; i < F_MAX_FIELDS; i++) { - info->regmap_fields[i] = - devm_regmap_field_alloc(dev, info->regmap, - rt9455_reg_fields[i]); - if (IS_ERR(info->regmap_fields[i])) { - dev_err(dev, - "Failed to allocate regmap field = %d\n", i); - return PTR_ERR(info->regmap_fields[i]); - } - } - - ret = rt9455_discover_charger(info, &ichrg, &ieoc_percentage, - &mivr, &iaicr); - if (ret) { - dev_err(dev, "Failed to discover charger\n"); - return ret; - } - -#if IS_ENABLED(CONFIG_USB_PHY) - info->usb_phy = devm_usb_get_phy(dev, USB_PHY_TYPE_USB2); - if (IS_ERR(info->usb_phy)) { - dev_err(dev, "Failed to get USB transceiver\n"); - } else { - info->nb.notifier_call = rt9455_usb_event; - ret = usb_register_notifier(info->usb_phy, &info->nb); - if (ret) { - dev_err(dev, "Failed to register USB notifier\n"); - /* - * If usb_register_notifier() fails, set notifier_call - * to NULL, to avoid calling usb_unregister_notifier(). - */ - info->nb.notifier_call = NULL; - } - } -#endif - - INIT_DEFERRABLE_WORK(&info->pwr_rdy_work, rt9455_pwr_rdy_work_callback); - INIT_DEFERRABLE_WORK(&info->max_charging_time_work, - rt9455_max_charging_time_work_callback); - INIT_DEFERRABLE_WORK(&info->batt_presence_work, - rt9455_batt_presence_work_callback); - - rt9455_charger_config.of_node = dev->of_node; - rt9455_charger_config.drv_data = info; - rt9455_charger_config.supplied_to = rt9455_charger_supplied_to; - rt9455_charger_config.num_supplicants = - ARRAY_SIZE(rt9455_charger_supplied_to); - ret = devm_request_threaded_irq(dev, client->irq, NULL, - rt9455_irq_handler_thread, - IRQF_TRIGGER_LOW | IRQF_ONESHOT, - RT9455_DRIVER_NAME, info); - if (ret) { - dev_err(dev, "Failed to register IRQ handler\n"); - goto put_usb_notifier; - } - - ret = rt9455_hw_init(info, ichrg, ieoc_percentage, mivr, iaicr); - if (ret) { - dev_err(dev, "Failed to set charger to its default values\n"); - goto put_usb_notifier; - } - - info->charger = devm_power_supply_register(dev, &rt9455_charger_desc, - &rt9455_charger_config); - if (IS_ERR(info->charger)) { - dev_err(dev, "Failed to register charger\n"); - ret = PTR_ERR(info->charger); - goto put_usb_notifier; - } - - return 0; - -put_usb_notifier: -#if IS_ENABLED(CONFIG_USB_PHY) - if (info->nb.notifier_call) { - usb_unregister_notifier(info->usb_phy, &info->nb); - info->nb.notifier_call = NULL; - } -#endif - return ret; -} - -static int rt9455_remove(struct i2c_client *client) -{ - int ret; - struct rt9455_info *info = i2c_get_clientdata(client); - - ret = rt9455_register_reset(info); - if (ret) - dev_err(&info->client->dev, "Failed to set charger to its default values\n"); - -#if IS_ENABLED(CONFIG_USB_PHY) - if (info->nb.notifier_call) - usb_unregister_notifier(info->usb_phy, &info->nb); -#endif - - cancel_delayed_work_sync(&info->pwr_rdy_work); - cancel_delayed_work_sync(&info->max_charging_time_work); - cancel_delayed_work_sync(&info->batt_presence_work); - - return ret; -} - -static const struct i2c_device_id rt9455_i2c_id_table[] = { - { RT9455_DRIVER_NAME, 0 }, - { }, -}; -MODULE_DEVICE_TABLE(i2c, rt9455_i2c_id_table); - -static const struct of_device_id rt9455_of_match[] = { - { .compatible = "richtek,rt9455", }, - { }, -}; -MODULE_DEVICE_TABLE(of, rt9455_of_match); - -static const struct acpi_device_id rt9455_i2c_acpi_match[] = { - { "RT945500", 0 }, - { } -}; -MODULE_DEVICE_TABLE(acpi, rt9455_i2c_acpi_match); - -static struct i2c_driver rt9455_driver = { - .probe = rt9455_probe, - .remove = rt9455_remove, - .id_table = rt9455_i2c_id_table, - .driver = { - .name = RT9455_DRIVER_NAME, - .of_match_table = of_match_ptr(rt9455_of_match), - .acpi_match_table = ACPI_PTR(rt9455_i2c_acpi_match), - }, -}; -module_i2c_driver(rt9455_driver); - -MODULE_LICENSE("GPL"); -MODULE_AUTHOR("Anda-Maria Nicolae "); -MODULE_DESCRIPTION("Richtek RT9455 Charger Driver"); diff --git a/drivers/power/rx51_battery.c b/drivers/power/rx51_battery.c deleted file mode 100644 index af9383d23d12..000000000000 --- a/drivers/power/rx51_battery.c +++ /dev/null @@ -1,297 +0,0 @@ -/* - * Nokia RX-51 battery driver - * - * Copyright (C) 2012 Pali Rohár - * - * 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 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., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include -#include -#include -#include -#include -#include -#include -#include - -struct rx51_device_info { - struct device *dev; - struct power_supply *bat; - struct power_supply_desc bat_desc; - struct iio_channel *channel_temp; - struct iio_channel *channel_bsi; - struct iio_channel *channel_vbat; -}; - -/* - * Read ADCIN channel value, code copied from maemo kernel - */ -static int rx51_battery_read_adc(struct iio_channel *channel) -{ - int val, err; - err = iio_read_channel_average_raw(channel, &val); - if (err < 0) - return err; - return val; -} - -/* - * Read ADCIN channel 12 (voltage) and convert RAW value to micro voltage - * This conversion formula was extracted from maemo program bsi-read - */ -static int rx51_battery_read_voltage(struct rx51_device_info *di) -{ - int voltage = rx51_battery_read_adc(di->channel_vbat); - - if (voltage < 0) { - dev_err(di->dev, "Could not read ADC: %d\n", voltage); - return voltage; - } - - return 1000 * (10000 * voltage / 1705); -} - -/* - * Temperature look-up tables - * TEMP = (1/(t1 + 1/298) - 273.15) - * Where t1 = (1/B) * ln((RAW_ADC_U * 2.5)/(R * I * 255)) - * Formula is based on experimental data, RX-51 CAL data, maemo program bme - * and formula from da9052 driver with values R = 100, B = 3380, I = 0.00671 - */ - -/* - * Table1 (temperature for first 25 RAW values) - * Usage: TEMP = rx51_temp_table1[RAW] - * RAW is between 1 and 24 - * TEMP is between 201 C and 55 C - */ -static u8 rx51_temp_table1[] = { - 255, 201, 159, 138, 124, 114, 106, 99, 94, 89, 85, 82, 78, 75, - 73, 70, 68, 66, 64, 62, 61, 59, 57, 56, 55 -}; - -/* - * Table2 (lowest RAW value for temperature) - * Usage: RAW = rx51_temp_table2[TEMP-rx51_temp_table2_first] - * TEMP is between 53 C and -32 C - * RAW is between 25 and 993 - */ -#define rx51_temp_table2_first 53 -static u16 rx51_temp_table2[] = { - 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 39, - 40, 41, 43, 44, 46, 48, 49, 51, 53, 55, 57, 59, 61, 64, - 66, 69, 71, 74, 77, 80, 83, 86, 90, 94, 97, 101, 106, 110, - 115, 119, 125, 130, 136, 141, 148, 154, 161, 168, 176, 184, 202, 211, - 221, 231, 242, 254, 266, 279, 293, 308, 323, 340, 357, 375, 395, 415, - 437, 460, 485, 511, 539, 568, 600, 633, 669, 706, 747, 790, 836, 885, - 937, 993, 1024 -}; - -/* - * Read ADCIN channel 0 (battery temp) and convert value to tenths of Celsius - * Use Temperature look-up tables for conversation - */ -static int rx51_battery_read_temperature(struct rx51_device_info *di) -{ - int min = 0; - int max = ARRAY_SIZE(rx51_temp_table2) - 1; - int raw = rx51_battery_read_adc(di->channel_temp); - - if (raw < 0) - dev_err(di->dev, "Could not read ADC: %d\n", raw); - - /* Zero and negative values are undefined */ - if (raw <= 0) - return INT_MAX; - - /* ADC channels are 10 bit, higher value are undefined */ - if (raw >= (1 << 10)) - return INT_MIN; - - /* First check for temperature in first direct table */ - if (raw < ARRAY_SIZE(rx51_temp_table1)) - return rx51_temp_table1[raw] * 10; - - /* Binary search RAW value in second inverse table */ - while (max - min > 1) { - int mid = (max + min) / 2; - if (rx51_temp_table2[mid] <= raw) - min = mid; - else if (rx51_temp_table2[mid] > raw) - max = mid; - if (rx51_temp_table2[mid] == raw) - break; - } - - return (rx51_temp_table2_first - min) * 10; -} - -/* - * Read ADCIN channel 4 (BSI) and convert RAW value to micro Ah - * This conversion formula was extracted from maemo program bsi-read - */ -static int rx51_battery_read_capacity(struct rx51_device_info *di) -{ - int capacity = rx51_battery_read_adc(di->channel_bsi); - - if (capacity < 0) { - dev_err(di->dev, "Could not read ADC: %d\n", capacity); - return capacity; - } - - return 1280 * (1200 * capacity)/(1024 - capacity); -} - -/* - * Return power_supply property - */ -static int rx51_battery_get_property(struct power_supply *psy, - enum power_supply_property psp, - union power_supply_propval *val) -{ - struct rx51_device_info *di = power_supply_get_drvdata(psy); - - switch (psp) { - case POWER_SUPPLY_PROP_TECHNOLOGY: - val->intval = POWER_SUPPLY_TECHNOLOGY_LION; - break; - case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: - val->intval = 4200000; - break; - case POWER_SUPPLY_PROP_PRESENT: - val->intval = rx51_battery_read_voltage(di) ? 1 : 0; - break; - case POWER_SUPPLY_PROP_VOLTAGE_NOW: - val->intval = rx51_battery_read_voltage(di); - break; - case POWER_SUPPLY_PROP_TEMP: - val->intval = rx51_battery_read_temperature(di); - break; - case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: - val->intval = rx51_battery_read_capacity(di); - break; - default: - return -EINVAL; - } - - if (val->intval == INT_MAX || val->intval == INT_MIN) - return -EINVAL; - - return 0; -} - -static enum power_supply_property rx51_battery_props[] = { - POWER_SUPPLY_PROP_TECHNOLOGY, - POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, - POWER_SUPPLY_PROP_PRESENT, - POWER_SUPPLY_PROP_VOLTAGE_NOW, - POWER_SUPPLY_PROP_TEMP, - POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, -}; - -static int rx51_battery_probe(struct platform_device *pdev) -{ - struct power_supply_config psy_cfg = {}; - struct rx51_device_info *di; - int ret; - - di = devm_kzalloc(&pdev->dev, sizeof(*di), GFP_KERNEL); - if (!di) - return -ENOMEM; - - platform_set_drvdata(pdev, di); - - di->dev = &pdev->dev; - di->bat_desc.name = "rx51-battery"; - di->bat_desc.type = POWER_SUPPLY_TYPE_BATTERY; - di->bat_desc.properties = rx51_battery_props; - di->bat_desc.num_properties = ARRAY_SIZE(rx51_battery_props); - di->bat_desc.get_property = rx51_battery_get_property; - - psy_cfg.drv_data = di; - - di->channel_temp = iio_channel_get(di->dev, "temp"); - if (IS_ERR(di->channel_temp)) { - ret = PTR_ERR(di->channel_temp); - goto error; - } - - di->channel_bsi = iio_channel_get(di->dev, "bsi"); - if (IS_ERR(di->channel_bsi)) { - ret = PTR_ERR(di->channel_bsi); - goto error_channel_temp; - } - - di->channel_vbat = iio_channel_get(di->dev, "vbat"); - if (IS_ERR(di->channel_vbat)) { - ret = PTR_ERR(di->channel_vbat); - goto error_channel_bsi; - } - - di->bat = power_supply_register(di->dev, &di->bat_desc, &psy_cfg); - if (IS_ERR(di->bat)) { - ret = PTR_ERR(di->bat); - goto error_channel_vbat; - } - - return 0; - -error_channel_vbat: - iio_channel_release(di->channel_vbat); -error_channel_bsi: - iio_channel_release(di->channel_bsi); -error_channel_temp: - iio_channel_release(di->channel_temp); -error: - - return ret; -} - -static int rx51_battery_remove(struct platform_device *pdev) -{ - struct rx51_device_info *di = platform_get_drvdata(pdev); - - power_supply_unregister(di->bat); - - iio_channel_release(di->channel_vbat); - iio_channel_release(di->channel_bsi); - iio_channel_release(di->channel_temp); - - return 0; -} - -#ifdef CONFIG_OF -static const struct of_device_id n900_battery_of_match[] = { - {.compatible = "nokia,n900-battery", }, - { }, -}; -MODULE_DEVICE_TABLE(of, n900_battery_of_match); -#endif - -static struct platform_driver rx51_battery_driver = { - .probe = rx51_battery_probe, - .remove = rx51_battery_remove, - .driver = { - .name = "rx51-battery", - .of_match_table = of_match_ptr(n900_battery_of_match), - }, -}; -module_platform_driver(rx51_battery_driver); - -MODULE_ALIAS("platform:rx51-battery"); -MODULE_AUTHOR("Pali Rohár "); -MODULE_DESCRIPTION("Nokia RX-51 battery driver"); -MODULE_LICENSE("GPL"); diff --git a/drivers/power/s3c_adc_battery.c b/drivers/power/s3c_adc_battery.c deleted file mode 100644 index 0ffe5cd3abf6..000000000000 --- a/drivers/power/s3c_adc_battery.c +++ /dev/null @@ -1,459 +0,0 @@ -/* - * iPAQ h1930/h1940/rx1950 battery controller driver - * Copyright (c) Vasily Khoruzhick - * Based on h1940_battery.c by Arnaud Patard - * - * This file is subject to the terms and conditions of the GNU General Public - * License. See the file COPYING in the main directory of this archive for - * more details. - * - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#define BAT_POLL_INTERVAL 10000 /* ms */ -#define JITTER_DELAY 500 /* ms */ - -struct s3c_adc_bat { - struct power_supply *psy; - struct s3c_adc_client *client; - struct s3c_adc_bat_pdata *pdata; - int volt_value; - int cur_value; - unsigned int timestamp; - int level; - int status; - int cable_plugged:1; -}; - -static struct delayed_work bat_work; - -static void s3c_adc_bat_ext_power_changed(struct power_supply *psy) -{ - schedule_delayed_work(&bat_work, - msecs_to_jiffies(JITTER_DELAY)); -} - -static int gather_samples(struct s3c_adc_client *client, int num, int channel) -{ - int value, i; - - /* default to 1 if nothing is set */ - if (num < 1) - num = 1; - - value = 0; - for (i = 0; i < num; i++) - value += s3c_adc_read(client, channel); - value /= num; - - return value; -} - -static enum power_supply_property s3c_adc_backup_bat_props[] = { - POWER_SUPPLY_PROP_VOLTAGE_NOW, - POWER_SUPPLY_PROP_VOLTAGE_MIN, - POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, -}; - -static int s3c_adc_backup_bat_get_property(struct power_supply *psy, - enum power_supply_property psp, - union power_supply_propval *val) -{ - struct s3c_adc_bat *bat = power_supply_get_drvdata(psy); - - if (!bat) { - dev_err(&psy->dev, "%s: no battery infos ?!\n", __func__); - return -EINVAL; - } - - if (bat->volt_value < 0 || - jiffies_to_msecs(jiffies - bat->timestamp) > - BAT_POLL_INTERVAL) { - bat->volt_value = gather_samples(bat->client, - bat->pdata->backup_volt_samples, - bat->pdata->backup_volt_channel); - bat->volt_value *= bat->pdata->backup_volt_mult; - bat->timestamp = jiffies; - } - - switch (psp) { - case POWER_SUPPLY_PROP_VOLTAGE_NOW: - val->intval = bat->volt_value; - return 0; - case POWER_SUPPLY_PROP_VOLTAGE_MIN: - val->intval = bat->pdata->backup_volt_min; - return 0; - case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: - val->intval = bat->pdata->backup_volt_max; - return 0; - default: - return -EINVAL; - } -} - -static const struct power_supply_desc backup_bat_desc = { - .name = "backup-battery", - .type = POWER_SUPPLY_TYPE_BATTERY, - .properties = s3c_adc_backup_bat_props, - .num_properties = ARRAY_SIZE(s3c_adc_backup_bat_props), - .get_property = s3c_adc_backup_bat_get_property, - .use_for_apm = 1, -}; - -static struct s3c_adc_bat backup_bat; - -static enum power_supply_property s3c_adc_main_bat_props[] = { - POWER_SUPPLY_PROP_STATUS, - POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, - POWER_SUPPLY_PROP_CHARGE_EMPTY_DESIGN, - POWER_SUPPLY_PROP_CHARGE_NOW, - POWER_SUPPLY_PROP_VOLTAGE_NOW, - POWER_SUPPLY_PROP_CURRENT_NOW, -}; - -static int calc_full_volt(int volt_val, int cur_val, int impedance) -{ - return volt_val + cur_val * impedance / 1000; -} - -static int charge_finished(struct s3c_adc_bat *bat) -{ - return bat->pdata->gpio_inverted ? - !gpio_get_value(bat->pdata->gpio_charge_finished) : - gpio_get_value(bat->pdata->gpio_charge_finished); -} - -static int s3c_adc_bat_get_property(struct power_supply *psy, - enum power_supply_property psp, - union power_supply_propval *val) -{ - struct s3c_adc_bat *bat = power_supply_get_drvdata(psy); - - int new_level; - int full_volt; - const struct s3c_adc_bat_thresh *lut; - unsigned int lut_size; - - if (!bat) { - dev_err(&psy->dev, "no battery infos ?!\n"); - return -EINVAL; - } - - lut = bat->pdata->lut_noac; - lut_size = bat->pdata->lut_noac_cnt; - - if (bat->volt_value < 0 || bat->cur_value < 0 || - jiffies_to_msecs(jiffies - bat->timestamp) > - BAT_POLL_INTERVAL) { - bat->volt_value = gather_samples(bat->client, - bat->pdata->volt_samples, - bat->pdata->volt_channel) * bat->pdata->volt_mult; - bat->cur_value = gather_samples(bat->client, - bat->pdata->current_samples, - bat->pdata->current_channel) * bat->pdata->current_mult; - bat->timestamp = jiffies; - } - - if (bat->cable_plugged && - ((bat->pdata->gpio_charge_finished < 0) || - !charge_finished(bat))) { - lut = bat->pdata->lut_acin; - lut_size = bat->pdata->lut_acin_cnt; - } - - new_level = 100000; - full_volt = calc_full_volt((bat->volt_value / 1000), - (bat->cur_value / 1000), bat->pdata->internal_impedance); - - if (full_volt < calc_full_volt(lut->volt, lut->cur, - bat->pdata->internal_impedance)) { - lut_size--; - while (lut_size--) { - int lut_volt1; - int lut_volt2; - - lut_volt1 = calc_full_volt(lut[0].volt, lut[0].cur, - bat->pdata->internal_impedance); - lut_volt2 = calc_full_volt(lut[1].volt, lut[1].cur, - bat->pdata->internal_impedance); - if (full_volt < lut_volt1 && full_volt >= lut_volt2) { - new_level = (lut[1].level + - (lut[0].level - lut[1].level) * - (full_volt - lut_volt2) / - (lut_volt1 - lut_volt2)) * 1000; - break; - } - new_level = lut[1].level * 1000; - lut++; - } - } - - bat->level = new_level; - - switch (psp) { - case POWER_SUPPLY_PROP_STATUS: - if (bat->pdata->gpio_charge_finished < 0) - val->intval = bat->level == 100000 ? - POWER_SUPPLY_STATUS_FULL : bat->status; - else - val->intval = bat->status; - return 0; - case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: - val->intval = 100000; - return 0; - case POWER_SUPPLY_PROP_CHARGE_EMPTY_DESIGN: - val->intval = 0; - return 0; - case POWER_SUPPLY_PROP_CHARGE_NOW: - val->intval = bat->level; - return 0; - case POWER_SUPPLY_PROP_VOLTAGE_NOW: - val->intval = bat->volt_value; - return 0; - case POWER_SUPPLY_PROP_CURRENT_NOW: - val->intval = bat->cur_value; - return 0; - default: - return -EINVAL; - } -} - -static const struct power_supply_desc main_bat_desc = { - .name = "main-battery", - .type = POWER_SUPPLY_TYPE_BATTERY, - .properties = s3c_adc_main_bat_props, - .num_properties = ARRAY_SIZE(s3c_adc_main_bat_props), - .get_property = s3c_adc_bat_get_property, - .external_power_changed = s3c_adc_bat_ext_power_changed, - .use_for_apm = 1, -}; - -static struct s3c_adc_bat main_bat; - -static void s3c_adc_bat_work(struct work_struct *work) -{ - struct s3c_adc_bat *bat = &main_bat; - int is_charged; - int is_plugged; - static int was_plugged; - - is_plugged = power_supply_am_i_supplied(bat->psy); - bat->cable_plugged = is_plugged; - if (is_plugged != was_plugged) { - was_plugged = is_plugged; - if (is_plugged) { - if (bat->pdata->enable_charger) - bat->pdata->enable_charger(); - bat->status = POWER_SUPPLY_STATUS_CHARGING; - } else { - if (bat->pdata->disable_charger) - bat->pdata->disable_charger(); - bat->status = POWER_SUPPLY_STATUS_DISCHARGING; - } - } else { - if ((bat->pdata->gpio_charge_finished >= 0) && is_plugged) { - is_charged = charge_finished(&main_bat); - if (is_charged) { - if (bat->pdata->disable_charger) - bat->pdata->disable_charger(); - bat->status = POWER_SUPPLY_STATUS_FULL; - } else { - if (bat->pdata->enable_charger) - bat->pdata->enable_charger(); - bat->status = POWER_SUPPLY_STATUS_CHARGING; - } - } - } - - power_supply_changed(bat->psy); -} - -static irqreturn_t s3c_adc_bat_charged(int irq, void *dev_id) -{ - schedule_delayed_work(&bat_work, - msecs_to_jiffies(JITTER_DELAY)); - return IRQ_HANDLED; -} - -static int s3c_adc_bat_probe(struct platform_device *pdev) -{ - struct s3c_adc_client *client; - struct s3c_adc_bat_pdata *pdata = pdev->dev.platform_data; - int ret; - - client = s3c_adc_register(pdev, NULL, NULL, 0); - if (IS_ERR(client)) { - dev_err(&pdev->dev, "cannot register adc\n"); - return PTR_ERR(client); - } - - platform_set_drvdata(pdev, client); - - main_bat.client = client; - main_bat.pdata = pdata; - main_bat.volt_value = -1; - main_bat.cur_value = -1; - main_bat.cable_plugged = 0; - main_bat.status = POWER_SUPPLY_STATUS_DISCHARGING; - - main_bat.psy = power_supply_register(&pdev->dev, &main_bat_desc, NULL); - if (IS_ERR(main_bat.psy)) { - ret = PTR_ERR(main_bat.psy); - goto err_reg_main; - } - if (pdata->backup_volt_mult) { - const struct power_supply_config psy_cfg - = { .drv_data = &backup_bat, }; - - backup_bat.client = client; - backup_bat.pdata = pdev->dev.platform_data; - backup_bat.volt_value = -1; - backup_bat.psy = power_supply_register(&pdev->dev, - &backup_bat_desc, - &psy_cfg); - if (IS_ERR(backup_bat.psy)) { - ret = PTR_ERR(backup_bat.psy); - goto err_reg_backup; - } - } - - INIT_DELAYED_WORK(&bat_work, s3c_adc_bat_work); - - if (pdata->gpio_charge_finished >= 0) { - ret = gpio_request(pdata->gpio_charge_finished, "charged"); - if (ret) - goto err_gpio; - - ret = request_irq(gpio_to_irq(pdata->gpio_charge_finished), - s3c_adc_bat_charged, - IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, - "battery charged", NULL); - if (ret) - goto err_irq; - } - - if (pdata->init) { - ret = pdata->init(); - if (ret) - goto err_platform; - } - - dev_info(&pdev->dev, "successfully loaded\n"); - device_init_wakeup(&pdev->dev, 1); - - /* Schedule timer to check current status */ - schedule_delayed_work(&bat_work, - msecs_to_jiffies(JITTER_DELAY)); - - return 0; - -err_platform: - if (pdata->gpio_charge_finished >= 0) - free_irq(gpio_to_irq(pdata->gpio_charge_finished), NULL); -err_irq: - if (pdata->gpio_charge_finished >= 0) - gpio_free(pdata->gpio_charge_finished); -err_gpio: - if (pdata->backup_volt_mult) - power_supply_unregister(backup_bat.psy); -err_reg_backup: - power_supply_unregister(main_bat.psy); -err_reg_main: - return ret; -} - -static int s3c_adc_bat_remove(struct platform_device *pdev) -{ - struct s3c_adc_client *client = platform_get_drvdata(pdev); - struct s3c_adc_bat_pdata *pdata = pdev->dev.platform_data; - - power_supply_unregister(main_bat.psy); - if (pdata->backup_volt_mult) - power_supply_unregister(backup_bat.psy); - - s3c_adc_release(client); - - if (pdata->gpio_charge_finished >= 0) { - free_irq(gpio_to_irq(pdata->gpio_charge_finished), NULL); - gpio_free(pdata->gpio_charge_finished); - } - - cancel_delayed_work(&bat_work); - - if (pdata->exit) - pdata->exit(); - - return 0; -} - -#ifdef CONFIG_PM -static int s3c_adc_bat_suspend(struct platform_device *pdev, - pm_message_t state) -{ - struct s3c_adc_bat_pdata *pdata = pdev->dev.platform_data; - - if (pdata->gpio_charge_finished >= 0) { - if (device_may_wakeup(&pdev->dev)) - enable_irq_wake( - gpio_to_irq(pdata->gpio_charge_finished)); - else { - disable_irq(gpio_to_irq(pdata->gpio_charge_finished)); - main_bat.pdata->disable_charger(); - } - } - - return 0; -} - -static int s3c_adc_bat_resume(struct platform_device *pdev) -{ - struct s3c_adc_bat_pdata *pdata = pdev->dev.platform_data; - - if (pdata->gpio_charge_finished >= 0) { - if (device_may_wakeup(&pdev->dev)) - disable_irq_wake( - gpio_to_irq(pdata->gpio_charge_finished)); - else - enable_irq(gpio_to_irq(pdata->gpio_charge_finished)); - } - - /* Schedule timer to check current status */ - schedule_delayed_work(&bat_work, - msecs_to_jiffies(JITTER_DELAY)); - - return 0; -} -#else -#define s3c_adc_bat_suspend NULL -#define s3c_adc_bat_resume NULL -#endif - -static struct platform_driver s3c_adc_bat_driver = { - .driver = { - .name = "s3c-adc-battery", - }, - .probe = s3c_adc_bat_probe, - .remove = s3c_adc_bat_remove, - .suspend = s3c_adc_bat_suspend, - .resume = s3c_adc_bat_resume, -}; - -module_platform_driver(s3c_adc_bat_driver); - -MODULE_AUTHOR("Vasily Khoruzhick "); -MODULE_DESCRIPTION("iPAQ H1930/H1940/RX1950 battery controller driver"); -MODULE_LICENSE("GPL"); diff --git a/drivers/power/sbs-battery.c b/drivers/power/sbs-battery.c deleted file mode 100644 index 768b9fcb58ea..000000000000 --- a/drivers/power/sbs-battery.c +++ /dev/null @@ -1,998 +0,0 @@ -/* - * Gas Gauge driver for SBS Compliant Batteries - * - * Copyright (c) 2010, NVIDIA 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; either version 2 of the License, or - * (at your option) any later version. - * - * 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., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -enum { - REG_MANUFACTURER_DATA, - REG_TEMPERATURE, - REG_VOLTAGE, - REG_CURRENT, - REG_CAPACITY, - REG_TIME_TO_EMPTY, - REG_TIME_TO_FULL, - REG_STATUS, - REG_CYCLE_COUNT, - REG_SERIAL_NUMBER, - REG_REMAINING_CAPACITY, - REG_REMAINING_CAPACITY_CHARGE, - REG_FULL_CHARGE_CAPACITY, - REG_FULL_CHARGE_CAPACITY_CHARGE, - REG_DESIGN_CAPACITY, - REG_DESIGN_CAPACITY_CHARGE, - REG_DESIGN_VOLTAGE_MIN, - REG_DESIGN_VOLTAGE_MAX, - REG_MANUFACTURER, - REG_MODEL_NAME, -}; - -/* Battery Mode defines */ -#define BATTERY_MODE_OFFSET 0x03 -#define BATTERY_MODE_MASK 0x8000 -enum sbs_battery_mode { - BATTERY_MODE_AMPS, - BATTERY_MODE_WATTS -}; - -/* manufacturer access defines */ -#define MANUFACTURER_ACCESS_STATUS 0x0006 -#define MANUFACTURER_ACCESS_SLEEP 0x0011 - -/* battery status value bits */ -#define BATTERY_DISCHARGING 0x40 -#define BATTERY_FULL_CHARGED 0x20 -#define BATTERY_FULL_DISCHARGED 0x10 - -/* min_value and max_value are only valid for numerical data */ -#define SBS_DATA(_psp, _addr, _min_value, _max_value) { \ - .psp = _psp, \ - .addr = _addr, \ - .min_value = _min_value, \ - .max_value = _max_value, \ -} - -static const struct chip_data { - enum power_supply_property psp; - u8 addr; - int min_value; - int max_value; -} sbs_data[] = { - [REG_MANUFACTURER_DATA] = - SBS_DATA(POWER_SUPPLY_PROP_PRESENT, 0x00, 0, 65535), - [REG_TEMPERATURE] = - SBS_DATA(POWER_SUPPLY_PROP_TEMP, 0x08, 0, 65535), - [REG_VOLTAGE] = - SBS_DATA(POWER_SUPPLY_PROP_VOLTAGE_NOW, 0x09, 0, 20000), - [REG_CURRENT] = - SBS_DATA(POWER_SUPPLY_PROP_CURRENT_NOW, 0x0A, -32768, 32767), - [REG_CAPACITY] = - SBS_DATA(POWER_SUPPLY_PROP_CAPACITY, 0x0D, 0, 100), - [REG_REMAINING_CAPACITY] = - SBS_DATA(POWER_SUPPLY_PROP_ENERGY_NOW, 0x0F, 0, 65535), - [REG_REMAINING_CAPACITY_CHARGE] = - SBS_DATA(POWER_SUPPLY_PROP_CHARGE_NOW, 0x0F, 0, 65535), - [REG_FULL_CHARGE_CAPACITY] = - SBS_DATA(POWER_SUPPLY_PROP_ENERGY_FULL, 0x10, 0, 65535), - [REG_FULL_CHARGE_CAPACITY_CHARGE] = - SBS_DATA(POWER_SUPPLY_PROP_CHARGE_FULL, 0x10, 0, 65535), - [REG_TIME_TO_EMPTY] = - SBS_DATA(POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG, 0x12, 0, 65535), - [REG_TIME_TO_FULL] = - SBS_DATA(POWER_SUPPLY_PROP_TIME_TO_FULL_AVG, 0x13, 0, 65535), - [REG_STATUS] = - SBS_DATA(POWER_SUPPLY_PROP_STATUS, 0x16, 0, 65535), - [REG_CYCLE_COUNT] = - SBS_DATA(POWER_SUPPLY_PROP_CYCLE_COUNT, 0x17, 0, 65535), - [REG_DESIGN_CAPACITY] = - SBS_DATA(POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN, 0x18, 0, 65535), - [REG_DESIGN_CAPACITY_CHARGE] = - SBS_DATA(POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, 0x18, 0, 65535), - [REG_DESIGN_VOLTAGE_MIN] = - SBS_DATA(POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, 0x19, 0, 65535), - [REG_DESIGN_VOLTAGE_MAX] = - SBS_DATA(POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, 0x19, 0, 65535), - [REG_SERIAL_NUMBER] = - SBS_DATA(POWER_SUPPLY_PROP_SERIAL_NUMBER, 0x1C, 0, 65535), - /* Properties of type `const char *' */ - [REG_MANUFACTURER] = - SBS_DATA(POWER_SUPPLY_PROP_MANUFACTURER, 0x20, 0, 65535), - [REG_MODEL_NAME] = - SBS_DATA(POWER_SUPPLY_PROP_MODEL_NAME, 0x21, 0, 65535) -}; - -static enum power_supply_property sbs_properties[] = { - POWER_SUPPLY_PROP_STATUS, - POWER_SUPPLY_PROP_HEALTH, - POWER_SUPPLY_PROP_PRESENT, - POWER_SUPPLY_PROP_TECHNOLOGY, - POWER_SUPPLY_PROP_CYCLE_COUNT, - POWER_SUPPLY_PROP_VOLTAGE_NOW, - POWER_SUPPLY_PROP_CURRENT_NOW, - POWER_SUPPLY_PROP_CAPACITY, - POWER_SUPPLY_PROP_TEMP, - POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG, - POWER_SUPPLY_PROP_TIME_TO_FULL_AVG, - POWER_SUPPLY_PROP_SERIAL_NUMBER, - POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, - POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, - POWER_SUPPLY_PROP_ENERGY_NOW, - POWER_SUPPLY_PROP_ENERGY_FULL, - POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN, - POWER_SUPPLY_PROP_CHARGE_NOW, - POWER_SUPPLY_PROP_CHARGE_FULL, - POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, - /* Properties of type `const char *' */ - POWER_SUPPLY_PROP_MANUFACTURER, - POWER_SUPPLY_PROP_MODEL_NAME -}; - -struct sbs_info { - struct i2c_client *client; - struct power_supply *power_supply; - struct sbs_platform_data *pdata; - bool is_present; - bool gpio_detect; - bool enable_detection; - int irq; - int last_state; - int poll_time; - struct delayed_work work; - int ignore_changes; -}; - -static char model_name[I2C_SMBUS_BLOCK_MAX + 1]; -static char manufacturer[I2C_SMBUS_BLOCK_MAX + 1]; -static bool force_load; - -static int sbs_read_word_data(struct i2c_client *client, u8 address) -{ - struct sbs_info *chip = i2c_get_clientdata(client); - s32 ret = 0; - int retries = 1; - - if (chip->pdata) - retries = max(chip->pdata->i2c_retry_count + 1, 1); - - while (retries > 0) { - ret = i2c_smbus_read_word_data(client, address); - if (ret >= 0) - break; - retries--; - } - - if (ret < 0) { - dev_dbg(&client->dev, - "%s: i2c read at address 0x%x failed\n", - __func__, address); - return ret; - } - - return le16_to_cpu(ret); -} - -static int sbs_read_string_data(struct i2c_client *client, u8 address, - char *values) -{ - struct sbs_info *chip = i2c_get_clientdata(client); - s32 ret = 0, block_length = 0; - int retries_length = 1, retries_block = 1; - u8 block_buffer[I2C_SMBUS_BLOCK_MAX + 1]; - - if (chip->pdata) { - retries_length = max(chip->pdata->i2c_retry_count + 1, 1); - retries_block = max(chip->pdata->i2c_retry_count + 1, 1); - } - - /* Adapter needs to support these two functions */ - if (!i2c_check_functionality(client->adapter, - I2C_FUNC_SMBUS_BYTE_DATA | - I2C_FUNC_SMBUS_I2C_BLOCK)){ - return -ENODEV; - } - - /* Get the length of block data */ - while (retries_length > 0) { - ret = i2c_smbus_read_byte_data(client, address); - if (ret >= 0) - break; - retries_length--; - } - - if (ret < 0) { - dev_dbg(&client->dev, - "%s: i2c read at address 0x%x failed\n", - __func__, address); - return ret; - } - - /* block_length does not include NULL terminator */ - block_length = ret; - if (block_length > I2C_SMBUS_BLOCK_MAX) { - dev_err(&client->dev, - "%s: Returned block_length is longer than 0x%x\n", - __func__, I2C_SMBUS_BLOCK_MAX); - return -EINVAL; - } - - /* Get the block data */ - while (retries_block > 0) { - ret = i2c_smbus_read_i2c_block_data( - client, address, - block_length + 1, block_buffer); - if (ret >= 0) - break; - retries_block--; - } - - if (ret < 0) { - dev_dbg(&client->dev, - "%s: i2c read at address 0x%x failed\n", - __func__, address); - return ret; - } - - /* block_buffer[0] == block_length */ - memcpy(values, block_buffer + 1, block_length); - values[block_length] = '\0'; - - return le16_to_cpu(ret); -} - -static int sbs_write_word_data(struct i2c_client *client, u8 address, - u16 value) -{ - struct sbs_info *chip = i2c_get_clientdata(client); - s32 ret = 0; - int retries = 1; - - if (chip->pdata) - retries = max(chip->pdata->i2c_retry_count + 1, 1); - - while (retries > 0) { - ret = i2c_smbus_write_word_data(client, address, - le16_to_cpu(value)); - if (ret >= 0) - break; - retries--; - } - - if (ret < 0) { - dev_dbg(&client->dev, - "%s: i2c write to address 0x%x failed\n", - __func__, address); - return ret; - } - - return 0; -} - -static int sbs_get_battery_presence_and_health( - struct i2c_client *client, enum power_supply_property psp, - union power_supply_propval *val) -{ - s32 ret; - struct sbs_info *chip = i2c_get_clientdata(client); - - if (psp == POWER_SUPPLY_PROP_PRESENT && - chip->gpio_detect) { - ret = gpio_get_value(chip->pdata->battery_detect); - if (ret == chip->pdata->battery_detect_present) - val->intval = 1; - else - val->intval = 0; - chip->is_present = val->intval; - return ret; - } - - /* Write to ManufacturerAccess with - * ManufacturerAccess command and then - * read the status */ - ret = sbs_write_word_data(client, sbs_data[REG_MANUFACTURER_DATA].addr, - MANUFACTURER_ACCESS_STATUS); - if (ret < 0) { - if (psp == POWER_SUPPLY_PROP_PRESENT) - val->intval = 0; /* battery removed */ - return ret; - } - - ret = sbs_read_word_data(client, sbs_data[REG_MANUFACTURER_DATA].addr); - if (ret < 0) - return ret; - - if (ret < sbs_data[REG_MANUFACTURER_DATA].min_value || - ret > sbs_data[REG_MANUFACTURER_DATA].max_value) { - val->intval = 0; - return 0; - } - - /* Mask the upper nibble of 2nd byte and - * lower byte of response then - * shift the result by 8 to get status*/ - ret &= 0x0F00; - ret >>= 8; - if (psp == POWER_SUPPLY_PROP_PRESENT) { - if (ret == 0x0F) - /* battery removed */ - val->intval = 0; - else - val->intval = 1; - } else if (psp == POWER_SUPPLY_PROP_HEALTH) { - if (ret == 0x09) - val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; - else if (ret == 0x0B) - val->intval = POWER_SUPPLY_HEALTH_OVERHEAT; - else if (ret == 0x0C) - val->intval = POWER_SUPPLY_HEALTH_DEAD; - else - val->intval = POWER_SUPPLY_HEALTH_GOOD; - } - - return 0; -} - -static int sbs_get_battery_property(struct i2c_client *client, - int reg_offset, enum power_supply_property psp, - union power_supply_propval *val) -{ - struct sbs_info *chip = i2c_get_clientdata(client); - s32 ret; - - ret = sbs_read_word_data(client, sbs_data[reg_offset].addr); - if (ret < 0) - return ret; - - /* returned values are 16 bit */ - if (sbs_data[reg_offset].min_value < 0) - ret = (s16)ret; - - if (ret >= sbs_data[reg_offset].min_value && - ret <= sbs_data[reg_offset].max_value) { - val->intval = ret; - if (psp != POWER_SUPPLY_PROP_STATUS) - return 0; - - if (ret & BATTERY_FULL_CHARGED) - val->intval = POWER_SUPPLY_STATUS_FULL; - else if (ret & BATTERY_DISCHARGING) - val->intval = POWER_SUPPLY_STATUS_DISCHARGING; - else - val->intval = POWER_SUPPLY_STATUS_CHARGING; - - if (chip->poll_time == 0) - chip->last_state = val->intval; - else if (chip->last_state != val->intval) { - cancel_delayed_work_sync(&chip->work); - power_supply_changed(chip->power_supply); - chip->poll_time = 0; - } - } else { - if (psp == POWER_SUPPLY_PROP_STATUS) - val->intval = POWER_SUPPLY_STATUS_UNKNOWN; - else - val->intval = 0; - } - - return 0; -} - -static int sbs_get_battery_string_property(struct i2c_client *client, - int reg_offset, enum power_supply_property psp, char *val) -{ - s32 ret; - - ret = sbs_read_string_data(client, sbs_data[reg_offset].addr, val); - - if (ret < 0) - return ret; - - return 0; -} - -static void sbs_unit_adjustment(struct i2c_client *client, - enum power_supply_property psp, union power_supply_propval *val) -{ -#define BASE_UNIT_CONVERSION 1000 -#define BATTERY_MODE_CAP_MULT_WATT (10 * BASE_UNIT_CONVERSION) -#define TIME_UNIT_CONVERSION 60 -#define TEMP_KELVIN_TO_CELSIUS 2731 - switch (psp) { - case POWER_SUPPLY_PROP_ENERGY_NOW: - case POWER_SUPPLY_PROP_ENERGY_FULL: - case POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN: - /* sbs provides energy in units of 10mWh. - * Convert to µWh - */ - val->intval *= BATTERY_MODE_CAP_MULT_WATT; - break; - - case POWER_SUPPLY_PROP_VOLTAGE_NOW: - case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: - case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: - case POWER_SUPPLY_PROP_CURRENT_NOW: - case POWER_SUPPLY_PROP_CHARGE_NOW: - case POWER_SUPPLY_PROP_CHARGE_FULL: - case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: - val->intval *= BASE_UNIT_CONVERSION; - break; - - case POWER_SUPPLY_PROP_TEMP: - /* sbs provides battery temperature in 0.1K - * so convert it to 0.1°C - */ - val->intval -= TEMP_KELVIN_TO_CELSIUS; - break; - - case POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG: - case POWER_SUPPLY_PROP_TIME_TO_FULL_AVG: - /* sbs provides time to empty and time to full in minutes. - * Convert to seconds - */ - val->intval *= TIME_UNIT_CONVERSION; - break; - - default: - dev_dbg(&client->dev, - "%s: no need for unit conversion %d\n", __func__, psp); - } -} - -static enum sbs_battery_mode sbs_set_battery_mode(struct i2c_client *client, - enum sbs_battery_mode mode) -{ - int ret, original_val; - - original_val = sbs_read_word_data(client, BATTERY_MODE_OFFSET); - if (original_val < 0) - return original_val; - - if ((original_val & BATTERY_MODE_MASK) == mode) - return mode; - - if (mode == BATTERY_MODE_AMPS) - ret = original_val & ~BATTERY_MODE_MASK; - else - ret = original_val | BATTERY_MODE_MASK; - - ret = sbs_write_word_data(client, BATTERY_MODE_OFFSET, ret); - if (ret < 0) - return ret; - - return original_val & BATTERY_MODE_MASK; -} - -static int sbs_get_battery_capacity(struct i2c_client *client, - int reg_offset, enum power_supply_property psp, - union power_supply_propval *val) -{ - s32 ret; - enum sbs_battery_mode mode = BATTERY_MODE_WATTS; - - if (power_supply_is_amp_property(psp)) - mode = BATTERY_MODE_AMPS; - - mode = sbs_set_battery_mode(client, mode); - if (mode < 0) - return mode; - - ret = sbs_read_word_data(client, sbs_data[reg_offset].addr); - if (ret < 0) - return ret; - - if (psp == POWER_SUPPLY_PROP_CAPACITY) { - /* sbs spec says that this can be >100 % - * even if max value is 100 % */ - val->intval = min(ret, 100); - } else - val->intval = ret; - - ret = sbs_set_battery_mode(client, mode); - if (ret < 0) - return ret; - - return 0; -} - -static char sbs_serial[5]; -static int sbs_get_battery_serial_number(struct i2c_client *client, - union power_supply_propval *val) -{ - int ret; - - ret = sbs_read_word_data(client, sbs_data[REG_SERIAL_NUMBER].addr); - if (ret < 0) - return ret; - - ret = sprintf(sbs_serial, "%04x", ret); - val->strval = sbs_serial; - - return 0; -} - -static int sbs_get_property_index(struct i2c_client *client, - enum power_supply_property psp) -{ - int count; - for (count = 0; count < ARRAY_SIZE(sbs_data); count++) - if (psp == sbs_data[count].psp) - return count; - - dev_warn(&client->dev, - "%s: Invalid Property - %d\n", __func__, psp); - - return -EINVAL; -} - -static int sbs_get_property(struct power_supply *psy, - enum power_supply_property psp, - union power_supply_propval *val) -{ - int ret = 0; - struct sbs_info *chip = power_supply_get_drvdata(psy); - struct i2c_client *client = chip->client; - - switch (psp) { - case POWER_SUPPLY_PROP_PRESENT: - case POWER_SUPPLY_PROP_HEALTH: - ret = sbs_get_battery_presence_and_health(client, psp, val); - if (psp == POWER_SUPPLY_PROP_PRESENT) - return 0; - break; - - case POWER_SUPPLY_PROP_TECHNOLOGY: - val->intval = POWER_SUPPLY_TECHNOLOGY_LION; - goto done; /* don't trigger power_supply_changed()! */ - - case POWER_SUPPLY_PROP_ENERGY_NOW: - case POWER_SUPPLY_PROP_ENERGY_FULL: - case POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN: - case POWER_SUPPLY_PROP_CHARGE_NOW: - case POWER_SUPPLY_PROP_CHARGE_FULL: - case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: - case POWER_SUPPLY_PROP_CAPACITY: - ret = sbs_get_property_index(client, psp); - if (ret < 0) - break; - - ret = sbs_get_battery_capacity(client, ret, psp, val); - break; - - case POWER_SUPPLY_PROP_SERIAL_NUMBER: - ret = sbs_get_battery_serial_number(client, val); - break; - - case POWER_SUPPLY_PROP_STATUS: - case POWER_SUPPLY_PROP_CYCLE_COUNT: - case POWER_SUPPLY_PROP_VOLTAGE_NOW: - case POWER_SUPPLY_PROP_CURRENT_NOW: - case POWER_SUPPLY_PROP_TEMP: - case POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG: - case POWER_SUPPLY_PROP_TIME_TO_FULL_AVG: - case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: - case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: - ret = sbs_get_property_index(client, psp); - if (ret < 0) - break; - - ret = sbs_get_battery_property(client, ret, psp, val); - break; - - case POWER_SUPPLY_PROP_MODEL_NAME: - ret = sbs_get_property_index(client, psp); - if (ret < 0) - break; - - ret = sbs_get_battery_string_property(client, ret, psp, - model_name); - val->strval = model_name; - break; - - case POWER_SUPPLY_PROP_MANUFACTURER: - ret = sbs_get_property_index(client, psp); - if (ret < 0) - break; - - ret = sbs_get_battery_string_property(client, ret, psp, - manufacturer); - val->strval = manufacturer; - break; - - default: - dev_err(&client->dev, - "%s: INVALID property\n", __func__); - return -EINVAL; - } - - if (!chip->enable_detection) - goto done; - - if (!chip->gpio_detect && - chip->is_present != (ret >= 0)) { - chip->is_present = (ret >= 0); - power_supply_changed(chip->power_supply); - } - -done: - if (!ret) { - /* Convert units to match requirements for power supply class */ - sbs_unit_adjustment(client, psp, val); - } - - dev_dbg(&client->dev, - "%s: property = %d, value = %x\n", __func__, psp, val->intval); - - if (ret && chip->is_present) - return ret; - - /* battery not present, so return NODATA for properties */ - if (ret) - return -ENODATA; - - return 0; -} - -static irqreturn_t sbs_irq(int irq, void *devid) -{ - struct power_supply *battery = devid; - - power_supply_changed(battery); - - return IRQ_HANDLED; -} - -static void sbs_external_power_changed(struct power_supply *psy) -{ - struct sbs_info *chip = power_supply_get_drvdata(psy); - - if (chip->ignore_changes > 0) { - chip->ignore_changes--; - return; - } - - /* cancel outstanding work */ - cancel_delayed_work_sync(&chip->work); - - schedule_delayed_work(&chip->work, HZ); - chip->poll_time = chip->pdata->poll_retry_count; -} - -static void sbs_delayed_work(struct work_struct *work) -{ - struct sbs_info *chip; - s32 ret; - - chip = container_of(work, struct sbs_info, work.work); - - ret = sbs_read_word_data(chip->client, sbs_data[REG_STATUS].addr); - /* if the read failed, give up on this work */ - if (ret < 0) { - chip->poll_time = 0; - return; - } - - if (ret & BATTERY_FULL_CHARGED) - ret = POWER_SUPPLY_STATUS_FULL; - else if (ret & BATTERY_DISCHARGING) - ret = POWER_SUPPLY_STATUS_DISCHARGING; - else - ret = POWER_SUPPLY_STATUS_CHARGING; - - if (chip->last_state != ret) { - chip->poll_time = 0; - power_supply_changed(chip->power_supply); - return; - } - if (chip->poll_time > 0) { - schedule_delayed_work(&chip->work, HZ); - chip->poll_time--; - return; - } -} - -#if defined(CONFIG_OF) - -#include -#include - -static const struct of_device_id sbs_dt_ids[] = { - { .compatible = "sbs,sbs-battery" }, - { .compatible = "ti,bq20z75" }, - { } -}; -MODULE_DEVICE_TABLE(of, sbs_dt_ids); - -static struct sbs_platform_data *sbs_of_populate_pdata( - struct i2c_client *client) -{ - struct device_node *of_node = client->dev.of_node; - struct sbs_platform_data *pdata = client->dev.platform_data; - enum of_gpio_flags gpio_flags; - int rc; - u32 prop; - - /* verify this driver matches this device */ - if (!of_node) - return NULL; - - /* if platform data is set, honor it */ - if (pdata) - return pdata; - - /* first make sure at least one property is set, otherwise - * it won't change behavior from running without pdata. - */ - if (!of_get_property(of_node, "sbs,i2c-retry-count", NULL) && - !of_get_property(of_node, "sbs,poll-retry-count", NULL) && - !of_get_property(of_node, "sbs,battery-detect-gpios", NULL)) - goto of_out; - - pdata = devm_kzalloc(&client->dev, sizeof(struct sbs_platform_data), - GFP_KERNEL); - if (!pdata) - goto of_out; - - rc = of_property_read_u32(of_node, "sbs,i2c-retry-count", &prop); - if (!rc) - pdata->i2c_retry_count = prop; - - rc = of_property_read_u32(of_node, "sbs,poll-retry-count", &prop); - if (!rc) - pdata->poll_retry_count = prop; - - if (!of_get_property(of_node, "sbs,battery-detect-gpios", NULL)) { - pdata->battery_detect = -1; - goto of_out; - } - - pdata->battery_detect = of_get_named_gpio_flags(of_node, - "sbs,battery-detect-gpios", 0, &gpio_flags); - - if (gpio_flags & OF_GPIO_ACTIVE_LOW) - pdata->battery_detect_present = 0; - else - pdata->battery_detect_present = 1; - -of_out: - return pdata; -} -#else -static struct sbs_platform_data *sbs_of_populate_pdata( - struct i2c_client *client) -{ - return client->dev.platform_data; -} -#endif - -static const struct power_supply_desc sbs_default_desc = { - .type = POWER_SUPPLY_TYPE_BATTERY, - .properties = sbs_properties, - .num_properties = ARRAY_SIZE(sbs_properties), - .get_property = sbs_get_property, - .external_power_changed = sbs_external_power_changed, -}; - -static int sbs_probe(struct i2c_client *client, - const struct i2c_device_id *id) -{ - struct sbs_info *chip; - struct power_supply_desc *sbs_desc; - struct sbs_platform_data *pdata = client->dev.platform_data; - struct power_supply_config psy_cfg = {}; - int rc; - int irq; - - sbs_desc = devm_kmemdup(&client->dev, &sbs_default_desc, - sizeof(*sbs_desc), GFP_KERNEL); - if (!sbs_desc) - return -ENOMEM; - - sbs_desc->name = devm_kasprintf(&client->dev, GFP_KERNEL, "sbs-%s", - dev_name(&client->dev)); - if (!sbs_desc->name) - return -ENOMEM; - - chip = kzalloc(sizeof(struct sbs_info), GFP_KERNEL); - if (!chip) - return -ENOMEM; - - chip->client = client; - chip->enable_detection = false; - chip->gpio_detect = false; - psy_cfg.of_node = client->dev.of_node; - psy_cfg.drv_data = chip; - /* ignore first notification of external change, it is generated - * from the power_supply_register call back - */ - chip->ignore_changes = 1; - chip->last_state = POWER_SUPPLY_STATUS_UNKNOWN; - - pdata = sbs_of_populate_pdata(client); - - if (pdata) { - chip->gpio_detect = gpio_is_valid(pdata->battery_detect); - chip->pdata = pdata; - } - - i2c_set_clientdata(client, chip); - - if (!chip->gpio_detect) - goto skip_gpio; - - rc = gpio_request(pdata->battery_detect, dev_name(&client->dev)); - if (rc) { - dev_warn(&client->dev, "Failed to request gpio: %d\n", rc); - chip->gpio_detect = false; - goto skip_gpio; - } - - rc = gpio_direction_input(pdata->battery_detect); - if (rc) { - dev_warn(&client->dev, "Failed to get gpio as input: %d\n", rc); - gpio_free(pdata->battery_detect); - chip->gpio_detect = false; - goto skip_gpio; - } - - irq = gpio_to_irq(pdata->battery_detect); - if (irq <= 0) { - dev_warn(&client->dev, "Failed to get gpio as irq: %d\n", irq); - gpio_free(pdata->battery_detect); - chip->gpio_detect = false; - goto skip_gpio; - } - - rc = request_irq(irq, sbs_irq, - IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, - dev_name(&client->dev), chip->power_supply); - if (rc) { - dev_warn(&client->dev, "Failed to request irq: %d\n", rc); - gpio_free(pdata->battery_detect); - chip->gpio_detect = false; - goto skip_gpio; - } - - chip->irq = irq; - -skip_gpio: - /* - * Before we register, we might need to make sure we can actually talk - * to the battery. - */ - if (!force_load) { - rc = sbs_read_word_data(client, sbs_data[REG_STATUS].addr); - - if (rc < 0) { - dev_err(&client->dev, "%s: Failed to get device status\n", - __func__); - goto exit_psupply; - } - } - - chip->power_supply = power_supply_register(&client->dev, sbs_desc, - &psy_cfg); - if (IS_ERR(chip->power_supply)) { - dev_err(&client->dev, - "%s: Failed to register power supply\n", __func__); - rc = PTR_ERR(chip->power_supply); - goto exit_psupply; - } - - dev_info(&client->dev, - "%s: battery gas gauge device registered\n", client->name); - - INIT_DELAYED_WORK(&chip->work, sbs_delayed_work); - - chip->enable_detection = true; - - return 0; - -exit_psupply: - if (chip->irq) - free_irq(chip->irq, chip->power_supply); - if (chip->gpio_detect) - gpio_free(pdata->battery_detect); - - kfree(chip); - - return rc; -} - -static int sbs_remove(struct i2c_client *client) -{ - struct sbs_info *chip = i2c_get_clientdata(client); - - if (chip->irq) - free_irq(chip->irq, chip->power_supply); - if (chip->gpio_detect) - gpio_free(chip->pdata->battery_detect); - - power_supply_unregister(chip->power_supply); - - cancel_delayed_work_sync(&chip->work); - - kfree(chip); - chip = NULL; - - return 0; -} - -#if defined CONFIG_PM_SLEEP - -static int sbs_suspend(struct device *dev) -{ - struct i2c_client *client = to_i2c_client(dev); - struct sbs_info *chip = i2c_get_clientdata(client); - s32 ret; - - if (chip->poll_time > 0) - cancel_delayed_work_sync(&chip->work); - - /* write to manufacturer access with sleep command */ - ret = sbs_write_word_data(client, sbs_data[REG_MANUFACTURER_DATA].addr, - MANUFACTURER_ACCESS_SLEEP); - if (chip->is_present && ret < 0) - return ret; - - return 0; -} - -static SIMPLE_DEV_PM_OPS(sbs_pm_ops, sbs_suspend, NULL); -#define SBS_PM_OPS (&sbs_pm_ops) - -#else -#define SBS_PM_OPS NULL -#endif - -static const struct i2c_device_id sbs_id[] = { - { "bq20z75", 0 }, - { "sbs-battery", 1 }, - {} -}; -MODULE_DEVICE_TABLE(i2c, sbs_id); - -static struct i2c_driver sbs_battery_driver = { - .probe = sbs_probe, - .remove = sbs_remove, - .id_table = sbs_id, - .driver = { - .name = "sbs-battery", - .of_match_table = of_match_ptr(sbs_dt_ids), - .pm = SBS_PM_OPS, - }, -}; -module_i2c_driver(sbs_battery_driver); - -MODULE_DESCRIPTION("SBS battery monitor driver"); -MODULE_LICENSE("GPL"); - -module_param(force_load, bool, S_IRUSR | S_IRGRP | S_IROTH); -MODULE_PARM_DESC(force_load, - "Attempt to load the driver even if no battery is connected"); diff --git a/drivers/power/smb347-charger.c b/drivers/power/smb347-charger.c deleted file mode 100644 index 072c5189bd6d..000000000000 --- a/drivers/power/smb347-charger.c +++ /dev/null @@ -1,1334 +0,0 @@ -/* - * Summit Microelectronics SMB347 Battery Charger Driver - * - * Copyright (C) 2011, Intel Corporation - * - * Authors: Bruce E. Robertson - * Mika Westerberg - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/* - * Configuration registers. These are mirrored to volatile RAM and can be - * written once %CMD_A_ALLOW_WRITE is set in %CMD_A register. They will be - * reloaded from non-volatile registers after POR. - */ -#define CFG_CHARGE_CURRENT 0x00 -#define CFG_CHARGE_CURRENT_FCC_MASK 0xe0 -#define CFG_CHARGE_CURRENT_FCC_SHIFT 5 -#define CFG_CHARGE_CURRENT_PCC_MASK 0x18 -#define CFG_CHARGE_CURRENT_PCC_SHIFT 3 -#define CFG_CHARGE_CURRENT_TC_MASK 0x07 -#define CFG_CURRENT_LIMIT 0x01 -#define CFG_CURRENT_LIMIT_DC_MASK 0xf0 -#define CFG_CURRENT_LIMIT_DC_SHIFT 4 -#define CFG_CURRENT_LIMIT_USB_MASK 0x0f -#define CFG_FLOAT_VOLTAGE 0x03 -#define CFG_FLOAT_VOLTAGE_FLOAT_MASK 0x3f -#define CFG_FLOAT_VOLTAGE_THRESHOLD_MASK 0xc0 -#define CFG_FLOAT_VOLTAGE_THRESHOLD_SHIFT 6 -#define CFG_STAT 0x05 -#define CFG_STAT_DISABLED BIT(5) -#define CFG_STAT_ACTIVE_HIGH BIT(7) -#define CFG_PIN 0x06 -#define CFG_PIN_EN_CTRL_MASK 0x60 -#define CFG_PIN_EN_CTRL_ACTIVE_HIGH 0x40 -#define CFG_PIN_EN_CTRL_ACTIVE_LOW 0x60 -#define CFG_PIN_EN_APSD_IRQ BIT(1) -#define CFG_PIN_EN_CHARGER_ERROR BIT(2) -#define CFG_THERM 0x07 -#define CFG_THERM_SOFT_HOT_COMPENSATION_MASK 0x03 -#define CFG_THERM_SOFT_HOT_COMPENSATION_SHIFT 0 -#define CFG_THERM_SOFT_COLD_COMPENSATION_MASK 0x0c -#define CFG_THERM_SOFT_COLD_COMPENSATION_SHIFT 2 -#define CFG_THERM_MONITOR_DISABLED BIT(4) -#define CFG_SYSOK 0x08 -#define CFG_SYSOK_SUSPEND_HARD_LIMIT_DISABLED BIT(2) -#define CFG_OTHER 0x09 -#define CFG_OTHER_RID_MASK 0xc0 -#define CFG_OTHER_RID_ENABLED_AUTO_OTG 0xc0 -#define CFG_OTG 0x0a -#define CFG_OTG_TEMP_THRESHOLD_MASK 0x30 -#define CFG_OTG_TEMP_THRESHOLD_SHIFT 4 -#define CFG_OTG_CC_COMPENSATION_MASK 0xc0 -#define CFG_OTG_CC_COMPENSATION_SHIFT 6 -#define CFG_TEMP_LIMIT 0x0b -#define CFG_TEMP_LIMIT_SOFT_HOT_MASK 0x03 -#define CFG_TEMP_LIMIT_SOFT_HOT_SHIFT 0 -#define CFG_TEMP_LIMIT_SOFT_COLD_MASK 0x0c -#define CFG_TEMP_LIMIT_SOFT_COLD_SHIFT 2 -#define CFG_TEMP_LIMIT_HARD_HOT_MASK 0x30 -#define CFG_TEMP_LIMIT_HARD_HOT_SHIFT 4 -#define CFG_TEMP_LIMIT_HARD_COLD_MASK 0xc0 -#define CFG_TEMP_LIMIT_HARD_COLD_SHIFT 6 -#define CFG_FAULT_IRQ 0x0c -#define CFG_FAULT_IRQ_DCIN_UV BIT(2) -#define CFG_STATUS_IRQ 0x0d -#define CFG_STATUS_IRQ_TERMINATION_OR_TAPER BIT(4) -#define CFG_STATUS_IRQ_CHARGE_TIMEOUT BIT(7) -#define CFG_ADDRESS 0x0e - -/* Command registers */ -#define CMD_A 0x30 -#define CMD_A_CHG_ENABLED BIT(1) -#define CMD_A_SUSPEND_ENABLED BIT(2) -#define CMD_A_ALLOW_WRITE BIT(7) -#define CMD_B 0x31 -#define CMD_C 0x33 - -/* Interrupt Status registers */ -#define IRQSTAT_A 0x35 -#define IRQSTAT_C 0x37 -#define IRQSTAT_C_TERMINATION_STAT BIT(0) -#define IRQSTAT_C_TERMINATION_IRQ BIT(1) -#define IRQSTAT_C_TAPER_IRQ BIT(3) -#define IRQSTAT_D 0x38 -#define IRQSTAT_D_CHARGE_TIMEOUT_STAT BIT(2) -#define IRQSTAT_D_CHARGE_TIMEOUT_IRQ BIT(3) -#define IRQSTAT_E 0x39 -#define IRQSTAT_E_USBIN_UV_STAT BIT(0) -#define IRQSTAT_E_USBIN_UV_IRQ BIT(1) -#define IRQSTAT_E_DCIN_UV_STAT BIT(4) -#define IRQSTAT_E_DCIN_UV_IRQ BIT(5) -#define IRQSTAT_F 0x3a - -/* Status registers */ -#define STAT_A 0x3b -#define STAT_A_FLOAT_VOLTAGE_MASK 0x3f -#define STAT_B 0x3c -#define STAT_C 0x3d -#define STAT_C_CHG_ENABLED BIT(0) -#define STAT_C_HOLDOFF_STAT BIT(3) -#define STAT_C_CHG_MASK 0x06 -#define STAT_C_CHG_SHIFT 1 -#define STAT_C_CHG_TERM BIT(5) -#define STAT_C_CHARGER_ERROR BIT(6) -#define STAT_E 0x3f - -#define SMB347_MAX_REGISTER 0x3f - -/** - * struct smb347_charger - smb347 charger instance - * @lock: protects concurrent access to online variables - * @dev: pointer to device - * @regmap: pointer to driver regmap - * @mains: power_supply instance for AC/DC power - * @usb: power_supply instance for USB power - * @battery: power_supply instance for battery - * @mains_online: is AC/DC input connected - * @usb_online: is USB input connected - * @charging_enabled: is charging enabled - * @pdata: pointer to platform data - */ -struct smb347_charger { - struct mutex lock; - struct device *dev; - struct regmap *regmap; - struct power_supply *mains; - struct power_supply *usb; - struct power_supply *battery; - bool mains_online; - bool usb_online; - bool charging_enabled; - const struct smb347_charger_platform_data *pdata; -}; - -/* Fast charge current in uA */ -static const unsigned int fcc_tbl[] = { - 700000, - 900000, - 1200000, - 1500000, - 1800000, - 2000000, - 2200000, - 2500000, -}; - -/* Pre-charge current in uA */ -static const unsigned int pcc_tbl[] = { - 100000, - 150000, - 200000, - 250000, -}; - -/* Termination current in uA */ -static const unsigned int tc_tbl[] = { - 37500, - 50000, - 100000, - 150000, - 200000, - 250000, - 500000, - 600000, -}; - -/* Input current limit in uA */ -static const unsigned int icl_tbl[] = { - 300000, - 500000, - 700000, - 900000, - 1200000, - 1500000, - 1800000, - 2000000, - 2200000, - 2500000, -}; - -/* Charge current compensation in uA */ -static const unsigned int ccc_tbl[] = { - 250000, - 700000, - 900000, - 1200000, -}; - -/* Convert register value to current using lookup table */ -static int hw_to_current(const unsigned int *tbl, size_t size, unsigned int val) -{ - if (val >= size) - return -EINVAL; - return tbl[val]; -} - -/* Convert current to register value using lookup table */ -static int current_to_hw(const unsigned int *tbl, size_t size, unsigned int val) -{ - size_t i; - - for (i = 0; i < size; i++) - if (val < tbl[i]) - break; - return i > 0 ? i - 1 : -EINVAL; -} - -/** - * smb347_update_ps_status - refreshes the power source status - * @smb: pointer to smb347 charger instance - * - * Function checks whether any power source is connected to the charger and - * updates internal state accordingly. If there is a change to previous state - * function returns %1, otherwise %0 and negative errno in case of errror. - */ -static int smb347_update_ps_status(struct smb347_charger *smb) -{ - bool usb = false; - bool dc = false; - unsigned int val; - int ret; - - ret = regmap_read(smb->regmap, IRQSTAT_E, &val); - if (ret < 0) - return ret; - - /* - * Dc and usb are set depending on whether they are enabled in - * platform data _and_ whether corresponding undervoltage is set. - */ - if (smb->pdata->use_mains) - dc = !(val & IRQSTAT_E_DCIN_UV_STAT); - if (smb->pdata->use_usb) - usb = !(val & IRQSTAT_E_USBIN_UV_STAT); - - mutex_lock(&smb->lock); - ret = smb->mains_online != dc || smb->usb_online != usb; - smb->mains_online = dc; - smb->usb_online = usb; - mutex_unlock(&smb->lock); - - return ret; -} - -/* - * smb347_is_ps_online - returns whether input power source is connected - * @smb: pointer to smb347 charger instance - * - * Returns %true if input power source is connected. Note that this is - * dependent on what platform has configured for usable power sources. For - * example if USB is disabled, this will return %false even if the USB cable - * is connected. - */ -static bool smb347_is_ps_online(struct smb347_charger *smb) -{ - bool ret; - - mutex_lock(&smb->lock); - ret = smb->usb_online || smb->mains_online; - mutex_unlock(&smb->lock); - - return ret; -} - -/** - * smb347_charging_status - returns status of charging - * @smb: pointer to smb347 charger instance - * - * Function returns charging status. %0 means no charging is in progress, - * %1 means pre-charging, %2 fast-charging and %3 taper-charging. - */ -static int smb347_charging_status(struct smb347_charger *smb) -{ - unsigned int val; - int ret; - - if (!smb347_is_ps_online(smb)) - return 0; - - ret = regmap_read(smb->regmap, STAT_C, &val); - if (ret < 0) - return 0; - - return (val & STAT_C_CHG_MASK) >> STAT_C_CHG_SHIFT; -} - -static int smb347_charging_set(struct smb347_charger *smb, bool enable) -{ - int ret = 0; - - if (smb->pdata->enable_control != SMB347_CHG_ENABLE_SW) { - dev_dbg(smb->dev, "charging enable/disable in SW disabled\n"); - return 0; - } - - mutex_lock(&smb->lock); - if (smb->charging_enabled != enable) { - ret = regmap_update_bits(smb->regmap, CMD_A, CMD_A_CHG_ENABLED, - enable ? CMD_A_CHG_ENABLED : 0); - if (!ret) - smb->charging_enabled = enable; - } - mutex_unlock(&smb->lock); - return ret; -} - -static inline int smb347_charging_enable(struct smb347_charger *smb) -{ - return smb347_charging_set(smb, true); -} - -static inline int smb347_charging_disable(struct smb347_charger *smb) -{ - return smb347_charging_set(smb, false); -} - -static int smb347_start_stop_charging(struct smb347_charger *smb) -{ - int ret; - - /* - * Depending on whether valid power source is connected or not, we - * disable or enable the charging. We do it manually because it - * depends on how the platform has configured the valid inputs. - */ - if (smb347_is_ps_online(smb)) { - ret = smb347_charging_enable(smb); - if (ret < 0) - dev_err(smb->dev, "failed to enable charging\n"); - } else { - ret = smb347_charging_disable(smb); - if (ret < 0) - dev_err(smb->dev, "failed to disable charging\n"); - } - - return ret; -} - -static int smb347_set_charge_current(struct smb347_charger *smb) -{ - int ret; - - if (smb->pdata->max_charge_current) { - ret = current_to_hw(fcc_tbl, ARRAY_SIZE(fcc_tbl), - smb->pdata->max_charge_current); - if (ret < 0) - return ret; - - ret = regmap_update_bits(smb->regmap, CFG_CHARGE_CURRENT, - CFG_CHARGE_CURRENT_FCC_MASK, - ret << CFG_CHARGE_CURRENT_FCC_SHIFT); - if (ret < 0) - return ret; - } - - if (smb->pdata->pre_charge_current) { - ret = current_to_hw(pcc_tbl, ARRAY_SIZE(pcc_tbl), - smb->pdata->pre_charge_current); - if (ret < 0) - return ret; - - ret = regmap_update_bits(smb->regmap, CFG_CHARGE_CURRENT, - CFG_CHARGE_CURRENT_PCC_MASK, - ret << CFG_CHARGE_CURRENT_PCC_SHIFT); - if (ret < 0) - return ret; - } - - if (smb->pdata->termination_current) { - ret = current_to_hw(tc_tbl, ARRAY_SIZE(tc_tbl), - smb->pdata->termination_current); - if (ret < 0) - return ret; - - ret = regmap_update_bits(smb->regmap, CFG_CHARGE_CURRENT, - CFG_CHARGE_CURRENT_TC_MASK, ret); - if (ret < 0) - return ret; - } - - return 0; -} - -static int smb347_set_current_limits(struct smb347_charger *smb) -{ - int ret; - - if (smb->pdata->mains_current_limit) { - ret = current_to_hw(icl_tbl, ARRAY_SIZE(icl_tbl), - smb->pdata->mains_current_limit); - if (ret < 0) - return ret; - - ret = regmap_update_bits(smb->regmap, CFG_CURRENT_LIMIT, - CFG_CURRENT_LIMIT_DC_MASK, - ret << CFG_CURRENT_LIMIT_DC_SHIFT); - if (ret < 0) - return ret; - } - - if (smb->pdata->usb_hc_current_limit) { - ret = current_to_hw(icl_tbl, ARRAY_SIZE(icl_tbl), - smb->pdata->usb_hc_current_limit); - if (ret < 0) - return ret; - - ret = regmap_update_bits(smb->regmap, CFG_CURRENT_LIMIT, - CFG_CURRENT_LIMIT_USB_MASK, ret); - if (ret < 0) - return ret; - } - - return 0; -} - -static int smb347_set_voltage_limits(struct smb347_charger *smb) -{ - int ret; - - if (smb->pdata->pre_to_fast_voltage) { - ret = smb->pdata->pre_to_fast_voltage; - - /* uV */ - ret = clamp_val(ret, 2400000, 3000000) - 2400000; - ret /= 200000; - - ret = regmap_update_bits(smb->regmap, CFG_FLOAT_VOLTAGE, - CFG_FLOAT_VOLTAGE_THRESHOLD_MASK, - ret << CFG_FLOAT_VOLTAGE_THRESHOLD_SHIFT); - if (ret < 0) - return ret; - } - - if (smb->pdata->max_charge_voltage) { - ret = smb->pdata->max_charge_voltage; - - /* uV */ - ret = clamp_val(ret, 3500000, 4500000) - 3500000; - ret /= 20000; - - ret = regmap_update_bits(smb->regmap, CFG_FLOAT_VOLTAGE, - CFG_FLOAT_VOLTAGE_FLOAT_MASK, ret); - if (ret < 0) - return ret; - } - - return 0; -} - -static int smb347_set_temp_limits(struct smb347_charger *smb) -{ - bool enable_therm_monitor = false; - int ret = 0; - int val; - - if (smb->pdata->chip_temp_threshold) { - val = smb->pdata->chip_temp_threshold; - - /* degree C */ - val = clamp_val(val, 100, 130) - 100; - val /= 10; - - ret = regmap_update_bits(smb->regmap, CFG_OTG, - CFG_OTG_TEMP_THRESHOLD_MASK, - val << CFG_OTG_TEMP_THRESHOLD_SHIFT); - if (ret < 0) - return ret; - } - - if (smb->pdata->soft_cold_temp_limit != SMB347_TEMP_USE_DEFAULT) { - val = smb->pdata->soft_cold_temp_limit; - - val = clamp_val(val, 0, 15); - val /= 5; - /* this goes from higher to lower so invert the value */ - val = ~val & 0x3; - - ret = regmap_update_bits(smb->regmap, CFG_TEMP_LIMIT, - CFG_TEMP_LIMIT_SOFT_COLD_MASK, - val << CFG_TEMP_LIMIT_SOFT_COLD_SHIFT); - if (ret < 0) - return ret; - - enable_therm_monitor = true; - } - - if (smb->pdata->soft_hot_temp_limit != SMB347_TEMP_USE_DEFAULT) { - val = smb->pdata->soft_hot_temp_limit; - - val = clamp_val(val, 40, 55) - 40; - val /= 5; - - ret = regmap_update_bits(smb->regmap, CFG_TEMP_LIMIT, - CFG_TEMP_LIMIT_SOFT_HOT_MASK, - val << CFG_TEMP_LIMIT_SOFT_HOT_SHIFT); - if (ret < 0) - return ret; - - enable_therm_monitor = true; - } - - if (smb->pdata->hard_cold_temp_limit != SMB347_TEMP_USE_DEFAULT) { - val = smb->pdata->hard_cold_temp_limit; - - val = clamp_val(val, -5, 10) + 5; - val /= 5; - /* this goes from higher to lower so invert the value */ - val = ~val & 0x3; - - ret = regmap_update_bits(smb->regmap, CFG_TEMP_LIMIT, - CFG_TEMP_LIMIT_HARD_COLD_MASK, - val << CFG_TEMP_LIMIT_HARD_COLD_SHIFT); - if (ret < 0) - return ret; - - enable_therm_monitor = true; - } - - if (smb->pdata->hard_hot_temp_limit != SMB347_TEMP_USE_DEFAULT) { - val = smb->pdata->hard_hot_temp_limit; - - val = clamp_val(val, 50, 65) - 50; - val /= 5; - - ret = regmap_update_bits(smb->regmap, CFG_TEMP_LIMIT, - CFG_TEMP_LIMIT_HARD_HOT_MASK, - val << CFG_TEMP_LIMIT_HARD_HOT_SHIFT); - if (ret < 0) - return ret; - - enable_therm_monitor = true; - } - - /* - * If any of the temperature limits are set, we also enable the - * thermistor monitoring. - * - * When soft limits are hit, the device will start to compensate - * current and/or voltage depending on the configuration. - * - * When hard limit is hit, the device will suspend charging - * depending on the configuration. - */ - if (enable_therm_monitor) { - ret = regmap_update_bits(smb->regmap, CFG_THERM, - CFG_THERM_MONITOR_DISABLED, 0); - if (ret < 0) - return ret; - } - - if (smb->pdata->suspend_on_hard_temp_limit) { - ret = regmap_update_bits(smb->regmap, CFG_SYSOK, - CFG_SYSOK_SUSPEND_HARD_LIMIT_DISABLED, 0); - if (ret < 0) - return ret; - } - - if (smb->pdata->soft_temp_limit_compensation != - SMB347_SOFT_TEMP_COMPENSATE_DEFAULT) { - val = smb->pdata->soft_temp_limit_compensation & 0x3; - - ret = regmap_update_bits(smb->regmap, CFG_THERM, - CFG_THERM_SOFT_HOT_COMPENSATION_MASK, - val << CFG_THERM_SOFT_HOT_COMPENSATION_SHIFT); - if (ret < 0) - return ret; - - ret = regmap_update_bits(smb->regmap, CFG_THERM, - CFG_THERM_SOFT_COLD_COMPENSATION_MASK, - val << CFG_THERM_SOFT_COLD_COMPENSATION_SHIFT); - if (ret < 0) - return ret; - } - - if (smb->pdata->charge_current_compensation) { - val = current_to_hw(ccc_tbl, ARRAY_SIZE(ccc_tbl), - smb->pdata->charge_current_compensation); - if (val < 0) - return val; - - ret = regmap_update_bits(smb->regmap, CFG_OTG, - CFG_OTG_CC_COMPENSATION_MASK, - (val & 0x3) << CFG_OTG_CC_COMPENSATION_SHIFT); - if (ret < 0) - return ret; - } - - return ret; -} - -/* - * smb347_set_writable - enables/disables writing to non-volatile registers - * @smb: pointer to smb347 charger instance - * - * You can enable/disable writing to the non-volatile configuration - * registers by calling this function. - * - * Returns %0 on success and negative errno in case of failure. - */ -static int smb347_set_writable(struct smb347_charger *smb, bool writable) -{ - return regmap_update_bits(smb->regmap, CMD_A, CMD_A_ALLOW_WRITE, - writable ? CMD_A_ALLOW_WRITE : 0); -} - -static int smb347_hw_init(struct smb347_charger *smb) -{ - unsigned int val; - int ret; - - ret = smb347_set_writable(smb, true); - if (ret < 0) - return ret; - - /* - * Program the platform specific configuration values to the device - * first. - */ - ret = smb347_set_charge_current(smb); - if (ret < 0) - goto fail; - - ret = smb347_set_current_limits(smb); - if (ret < 0) - goto fail; - - ret = smb347_set_voltage_limits(smb); - if (ret < 0) - goto fail; - - ret = smb347_set_temp_limits(smb); - if (ret < 0) - goto fail; - - /* If USB charging is disabled we put the USB in suspend mode */ - if (!smb->pdata->use_usb) { - ret = regmap_update_bits(smb->regmap, CMD_A, - CMD_A_SUSPEND_ENABLED, - CMD_A_SUSPEND_ENABLED); - if (ret < 0) - goto fail; - } - - /* - * If configured by platform data, we enable hardware Auto-OTG - * support for driving VBUS. Otherwise we disable it. - */ - ret = regmap_update_bits(smb->regmap, CFG_OTHER, CFG_OTHER_RID_MASK, - smb->pdata->use_usb_otg ? CFG_OTHER_RID_ENABLED_AUTO_OTG : 0); - if (ret < 0) - goto fail; - - /* - * Make the charging functionality controllable by a write to the - * command register unless pin control is specified in the platform - * data. - */ - switch (smb->pdata->enable_control) { - case SMB347_CHG_ENABLE_PIN_ACTIVE_LOW: - val = CFG_PIN_EN_CTRL_ACTIVE_LOW; - break; - case SMB347_CHG_ENABLE_PIN_ACTIVE_HIGH: - val = CFG_PIN_EN_CTRL_ACTIVE_HIGH; - break; - default: - val = 0; - break; - } - - ret = regmap_update_bits(smb->regmap, CFG_PIN, CFG_PIN_EN_CTRL_MASK, - val); - if (ret < 0) - goto fail; - - /* Disable Automatic Power Source Detection (APSD) interrupt. */ - ret = regmap_update_bits(smb->regmap, CFG_PIN, CFG_PIN_EN_APSD_IRQ, 0); - if (ret < 0) - goto fail; - - ret = smb347_update_ps_status(smb); - if (ret < 0) - goto fail; - - ret = smb347_start_stop_charging(smb); - -fail: - smb347_set_writable(smb, false); - return ret; -} - -static irqreturn_t smb347_interrupt(int irq, void *data) -{ - struct smb347_charger *smb = data; - unsigned int stat_c, irqstat_c, irqstat_d, irqstat_e; - bool handled = false; - int ret; - - ret = regmap_read(smb->regmap, STAT_C, &stat_c); - if (ret < 0) { - dev_warn(smb->dev, "reading STAT_C failed\n"); - return IRQ_NONE; - } - - ret = regmap_read(smb->regmap, IRQSTAT_C, &irqstat_c); - if (ret < 0) { - dev_warn(smb->dev, "reading IRQSTAT_C failed\n"); - return IRQ_NONE; - } - - ret = regmap_read(smb->regmap, IRQSTAT_D, &irqstat_d); - if (ret < 0) { - dev_warn(smb->dev, "reading IRQSTAT_D failed\n"); - return IRQ_NONE; - } - - ret = regmap_read(smb->regmap, IRQSTAT_E, &irqstat_e); - if (ret < 0) { - dev_warn(smb->dev, "reading IRQSTAT_E failed\n"); - return IRQ_NONE; - } - - /* - * If we get charger error we report the error back to user. - * If the error is recovered charging will resume again. - */ - if (stat_c & STAT_C_CHARGER_ERROR) { - dev_err(smb->dev, "charging stopped due to charger error\n"); - power_supply_changed(smb->battery); - handled = true; - } - - /* - * If we reached the termination current the battery is charged and - * we can update the status now. Charging is automatically - * disabled by the hardware. - */ - if (irqstat_c & (IRQSTAT_C_TERMINATION_IRQ | IRQSTAT_C_TAPER_IRQ)) { - if (irqstat_c & IRQSTAT_C_TERMINATION_STAT) - power_supply_changed(smb->battery); - dev_dbg(smb->dev, "going to HW maintenance mode\n"); - handled = true; - } - - /* - * If we got a charger timeout INT that means the charge - * full is not detected with in charge timeout value. - */ - if (irqstat_d & IRQSTAT_D_CHARGE_TIMEOUT_IRQ) { - dev_dbg(smb->dev, "total Charge Timeout INT received\n"); - - if (irqstat_d & IRQSTAT_D_CHARGE_TIMEOUT_STAT) - dev_warn(smb->dev, "charging stopped due to timeout\n"); - power_supply_changed(smb->battery); - handled = true; - } - - /* - * If we got an under voltage interrupt it means that AC/USB input - * was connected or disconnected. - */ - if (irqstat_e & (IRQSTAT_E_USBIN_UV_IRQ | IRQSTAT_E_DCIN_UV_IRQ)) { - if (smb347_update_ps_status(smb) > 0) { - smb347_start_stop_charging(smb); - if (smb->pdata->use_mains) - power_supply_changed(smb->mains); - if (smb->pdata->use_usb) - power_supply_changed(smb->usb); - } - handled = true; - } - - return handled ? IRQ_HANDLED : IRQ_NONE; -} - -static int smb347_irq_set(struct smb347_charger *smb, bool enable) -{ - int ret; - - ret = smb347_set_writable(smb, true); - if (ret < 0) - return ret; - - /* - * Enable/disable interrupts for: - * - under voltage - * - termination current reached - * - charger timeout - * - charger error - */ - ret = regmap_update_bits(smb->regmap, CFG_FAULT_IRQ, 0xff, - enable ? CFG_FAULT_IRQ_DCIN_UV : 0); - if (ret < 0) - goto fail; - - ret = regmap_update_bits(smb->regmap, CFG_STATUS_IRQ, 0xff, - enable ? (CFG_STATUS_IRQ_TERMINATION_OR_TAPER | - CFG_STATUS_IRQ_CHARGE_TIMEOUT) : 0); - if (ret < 0) - goto fail; - - ret = regmap_update_bits(smb->regmap, CFG_PIN, CFG_PIN_EN_CHARGER_ERROR, - enable ? CFG_PIN_EN_CHARGER_ERROR : 0); -fail: - smb347_set_writable(smb, false); - return ret; -} - -static inline int smb347_irq_enable(struct smb347_charger *smb) -{ - return smb347_irq_set(smb, true); -} - -static inline int smb347_irq_disable(struct smb347_charger *smb) -{ - return smb347_irq_set(smb, false); -} - -static int smb347_irq_init(struct smb347_charger *smb, - struct i2c_client *client) -{ - const struct smb347_charger_platform_data *pdata = smb->pdata; - int ret, irq = gpio_to_irq(pdata->irq_gpio); - - ret = gpio_request_one(pdata->irq_gpio, GPIOF_IN, client->name); - if (ret < 0) - goto fail; - - ret = request_threaded_irq(irq, NULL, smb347_interrupt, - IRQF_TRIGGER_FALLING | IRQF_ONESHOT, - client->name, smb); - if (ret < 0) - goto fail_gpio; - - ret = smb347_set_writable(smb, true); - if (ret < 0) - goto fail_irq; - - /* - * Configure the STAT output to be suitable for interrupts: disable - * all other output (except interrupts) and make it active low. - */ - ret = regmap_update_bits(smb->regmap, CFG_STAT, - CFG_STAT_ACTIVE_HIGH | CFG_STAT_DISABLED, - CFG_STAT_DISABLED); - if (ret < 0) - goto fail_readonly; - - smb347_set_writable(smb, false); - client->irq = irq; - return 0; - -fail_readonly: - smb347_set_writable(smb, false); -fail_irq: - free_irq(irq, smb); -fail_gpio: - gpio_free(pdata->irq_gpio); -fail: - client->irq = 0; - return ret; -} - -/* - * Returns the constant charge current programmed - * into the charger in uA. - */ -static int get_const_charge_current(struct smb347_charger *smb) -{ - int ret, intval; - unsigned int v; - - if (!smb347_is_ps_online(smb)) - return -ENODATA; - - ret = regmap_read(smb->regmap, STAT_B, &v); - if (ret < 0) - return ret; - - /* - * The current value is composition of FCC and PCC values - * and we can detect which table to use from bit 5. - */ - if (v & 0x20) { - intval = hw_to_current(fcc_tbl, ARRAY_SIZE(fcc_tbl), v & 7); - } else { - v >>= 3; - intval = hw_to_current(pcc_tbl, ARRAY_SIZE(pcc_tbl), v & 7); - } - - return intval; -} - -/* - * Returns the constant charge voltage programmed - * into the charger in uV. - */ -static int get_const_charge_voltage(struct smb347_charger *smb) -{ - int ret, intval; - unsigned int v; - - if (!smb347_is_ps_online(smb)) - return -ENODATA; - - ret = regmap_read(smb->regmap, STAT_A, &v); - if (ret < 0) - return ret; - - v &= STAT_A_FLOAT_VOLTAGE_MASK; - if (v > 0x3d) - v = 0x3d; - - intval = 3500000 + v * 20000; - - return intval; -} - -static int smb347_mains_get_property(struct power_supply *psy, - enum power_supply_property prop, - union power_supply_propval *val) -{ - struct smb347_charger *smb = power_supply_get_drvdata(psy); - int ret; - - switch (prop) { - case POWER_SUPPLY_PROP_ONLINE: - val->intval = smb->mains_online; - break; - - case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: - ret = get_const_charge_voltage(smb); - if (ret < 0) - return ret; - else - val->intval = ret; - break; - - case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: - ret = get_const_charge_current(smb); - if (ret < 0) - return ret; - else - val->intval = ret; - break; - - default: - return -EINVAL; - } - - return 0; -} - -static enum power_supply_property smb347_mains_properties[] = { - POWER_SUPPLY_PROP_ONLINE, - POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT, - POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE, -}; - -static int smb347_usb_get_property(struct power_supply *psy, - enum power_supply_property prop, - union power_supply_propval *val) -{ - struct smb347_charger *smb = power_supply_get_drvdata(psy); - int ret; - - switch (prop) { - case POWER_SUPPLY_PROP_ONLINE: - val->intval = smb->usb_online; - break; - - case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: - ret = get_const_charge_voltage(smb); - if (ret < 0) - return ret; - else - val->intval = ret; - break; - - case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: - ret = get_const_charge_current(smb); - if (ret < 0) - return ret; - else - val->intval = ret; - break; - - default: - return -EINVAL; - } - - return 0; -} - -static enum power_supply_property smb347_usb_properties[] = { - POWER_SUPPLY_PROP_ONLINE, - POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT, - POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE, -}; - -static int smb347_get_charging_status(struct smb347_charger *smb) -{ - int ret, status; - unsigned int val; - - if (!smb347_is_ps_online(smb)) - return POWER_SUPPLY_STATUS_DISCHARGING; - - ret = regmap_read(smb->regmap, STAT_C, &val); - if (ret < 0) - return ret; - - if ((val & STAT_C_CHARGER_ERROR) || - (val & STAT_C_HOLDOFF_STAT)) { - /* - * set to NOT CHARGING upon charger error - * or charging has stopped. - */ - status = POWER_SUPPLY_STATUS_NOT_CHARGING; - } else { - if ((val & STAT_C_CHG_MASK) >> STAT_C_CHG_SHIFT) { - /* - * set to charging if battery is in pre-charge, - * fast charge or taper charging mode. - */ - status = POWER_SUPPLY_STATUS_CHARGING; - } else if (val & STAT_C_CHG_TERM) { - /* - * set the status to FULL if battery is not in pre - * charge, fast charge or taper charging mode AND - * charging is terminated at least once. - */ - status = POWER_SUPPLY_STATUS_FULL; - } else { - /* - * in this case no charger error or termination - * occured but charging is not in progress!!! - */ - status = POWER_SUPPLY_STATUS_NOT_CHARGING; - } - } - - return status; -} - -static int smb347_battery_get_property(struct power_supply *psy, - enum power_supply_property prop, - union power_supply_propval *val) -{ - struct smb347_charger *smb = power_supply_get_drvdata(psy); - const struct smb347_charger_platform_data *pdata = smb->pdata; - int ret; - - ret = smb347_update_ps_status(smb); - if (ret < 0) - return ret; - - switch (prop) { - case POWER_SUPPLY_PROP_STATUS: - ret = smb347_get_charging_status(smb); - if (ret < 0) - return ret; - val->intval = ret; - break; - - case POWER_SUPPLY_PROP_CHARGE_TYPE: - if (!smb347_is_ps_online(smb)) - return -ENODATA; - - /* - * We handle trickle and pre-charging the same, and taper - * and none the same. - */ - switch (smb347_charging_status(smb)) { - case 1: - val->intval = POWER_SUPPLY_CHARGE_TYPE_TRICKLE; - break; - case 2: - val->intval = POWER_SUPPLY_CHARGE_TYPE_FAST; - break; - default: - val->intval = POWER_SUPPLY_CHARGE_TYPE_NONE; - break; - } - break; - - case POWER_SUPPLY_PROP_TECHNOLOGY: - val->intval = pdata->battery_info.technology; - break; - - case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: - val->intval = pdata->battery_info.voltage_min_design; - break; - - case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: - val->intval = pdata->battery_info.voltage_max_design; - break; - - case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: - val->intval = pdata->battery_info.charge_full_design; - break; - - case POWER_SUPPLY_PROP_MODEL_NAME: - val->strval = pdata->battery_info.name; - break; - - default: - return -EINVAL; - } - - return 0; -} - -static enum power_supply_property smb347_battery_properties[] = { - POWER_SUPPLY_PROP_STATUS, - POWER_SUPPLY_PROP_CHARGE_TYPE, - POWER_SUPPLY_PROP_TECHNOLOGY, - POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, - POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, - POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, - POWER_SUPPLY_PROP_MODEL_NAME, -}; - -static bool smb347_volatile_reg(struct device *dev, unsigned int reg) -{ - switch (reg) { - case IRQSTAT_A: - case IRQSTAT_C: - case IRQSTAT_E: - case IRQSTAT_F: - case STAT_A: - case STAT_B: - case STAT_C: - case STAT_E: - return true; - } - - return false; -} - -static bool smb347_readable_reg(struct device *dev, unsigned int reg) -{ - switch (reg) { - case CFG_CHARGE_CURRENT: - case CFG_CURRENT_LIMIT: - case CFG_FLOAT_VOLTAGE: - case CFG_STAT: - case CFG_PIN: - case CFG_THERM: - case CFG_SYSOK: - case CFG_OTHER: - case CFG_OTG: - case CFG_TEMP_LIMIT: - case CFG_FAULT_IRQ: - case CFG_STATUS_IRQ: - case CFG_ADDRESS: - case CMD_A: - case CMD_B: - case CMD_C: - return true; - } - - return smb347_volatile_reg(dev, reg); -} - -static const struct regmap_config smb347_regmap = { - .reg_bits = 8, - .val_bits = 8, - .max_register = SMB347_MAX_REGISTER, - .volatile_reg = smb347_volatile_reg, - .readable_reg = smb347_readable_reg, -}; - -static const struct power_supply_desc smb347_mains_desc = { - .name = "smb347-mains", - .type = POWER_SUPPLY_TYPE_MAINS, - .get_property = smb347_mains_get_property, - .properties = smb347_mains_properties, - .num_properties = ARRAY_SIZE(smb347_mains_properties), -}; - -static const struct power_supply_desc smb347_usb_desc = { - .name = "smb347-usb", - .type = POWER_SUPPLY_TYPE_USB, - .get_property = smb347_usb_get_property, - .properties = smb347_usb_properties, - .num_properties = ARRAY_SIZE(smb347_usb_properties), -}; - -static const struct power_supply_desc smb347_battery_desc = { - .name = "smb347-battery", - .type = POWER_SUPPLY_TYPE_BATTERY, - .get_property = smb347_battery_get_property, - .properties = smb347_battery_properties, - .num_properties = ARRAY_SIZE(smb347_battery_properties), -}; - -static int smb347_probe(struct i2c_client *client, - const struct i2c_device_id *id) -{ - static char *battery[] = { "smb347-battery" }; - const struct smb347_charger_platform_data *pdata; - struct power_supply_config mains_usb_cfg = {}, battery_cfg = {}; - struct device *dev = &client->dev; - struct smb347_charger *smb; - int ret; - - pdata = dev->platform_data; - if (!pdata) - return -EINVAL; - - if (!pdata->use_mains && !pdata->use_usb) - return -EINVAL; - - smb = devm_kzalloc(dev, sizeof(*smb), GFP_KERNEL); - if (!smb) - return -ENOMEM; - - i2c_set_clientdata(client, smb); - - mutex_init(&smb->lock); - smb->dev = &client->dev; - smb->pdata = pdata; - - smb->regmap = devm_regmap_init_i2c(client, &smb347_regmap); - if (IS_ERR(smb->regmap)) - return PTR_ERR(smb->regmap); - - ret = smb347_hw_init(smb); - if (ret < 0) - return ret; - - mains_usb_cfg.supplied_to = battery; - mains_usb_cfg.num_supplicants = ARRAY_SIZE(battery); - mains_usb_cfg.drv_data = smb; - if (smb->pdata->use_mains) { - smb->mains = power_supply_register(dev, &smb347_mains_desc, - &mains_usb_cfg); - if (IS_ERR(smb->mains)) - return PTR_ERR(smb->mains); - } - - if (smb->pdata->use_usb) { - smb->usb = power_supply_register(dev, &smb347_usb_desc, - &mains_usb_cfg); - if (IS_ERR(smb->usb)) { - if (smb->pdata->use_mains) - power_supply_unregister(smb->mains); - return PTR_ERR(smb->usb); - } - } - - battery_cfg.drv_data = smb; - smb->battery = power_supply_register(dev, &smb347_battery_desc, - &battery_cfg); - if (IS_ERR(smb->battery)) { - if (smb->pdata->use_usb) - power_supply_unregister(smb->usb); - if (smb->pdata->use_mains) - power_supply_unregister(smb->mains); - return PTR_ERR(smb->battery); - } - - /* - * Interrupt pin is optional. If it is connected, we setup the - * interrupt support here. - */ - if (pdata->irq_gpio >= 0) { - ret = smb347_irq_init(smb, client); - if (ret < 0) { - dev_warn(dev, "failed to initialize IRQ: %d\n", ret); - dev_warn(dev, "disabling IRQ support\n"); - } else { - smb347_irq_enable(smb); - } - } - - return 0; -} - -static int smb347_remove(struct i2c_client *client) -{ - struct smb347_charger *smb = i2c_get_clientdata(client); - - if (client->irq) { - smb347_irq_disable(smb); - free_irq(client->irq, smb); - gpio_free(smb->pdata->irq_gpio); - } - - power_supply_unregister(smb->battery); - if (smb->pdata->use_usb) - power_supply_unregister(smb->usb); - if (smb->pdata->use_mains) - power_supply_unregister(smb->mains); - return 0; -} - -static const struct i2c_device_id smb347_id[] = { - { "smb347", 0 }, - { } -}; -MODULE_DEVICE_TABLE(i2c, smb347_id); - -static struct i2c_driver smb347_driver = { - .driver = { - .name = "smb347", - }, - .probe = smb347_probe, - .remove = smb347_remove, - .id_table = smb347_id, -}; - -module_i2c_driver(smb347_driver); - -MODULE_AUTHOR("Bruce E. Robertson "); -MODULE_AUTHOR("Mika Westerberg "); -MODULE_DESCRIPTION("SMB347 battery charger driver"); -MODULE_LICENSE("GPL"); diff --git a/drivers/power/supply/88pm860x_battery.c b/drivers/power/supply/88pm860x_battery.c new file mode 100644 index 000000000000..63c57dc82ac1 --- /dev/null +++ b/drivers/power/supply/88pm860x_battery.c @@ -0,0 +1,1021 @@ +/* + * Battery driver for Marvell 88PM860x PMIC + * + * Copyright (c) 2012 Marvell International Ltd. + * Author: Jett Zhou + * Haojian Zhuang + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* bit definitions of Status Query Interface 2 */ +#define STATUS2_CHG (1 << 2) +#define STATUS2_BAT (1 << 3) +#define STATUS2_VBUS (1 << 4) + +/* bit definitions of Measurement Enable 1 Register */ +#define MEAS1_TINT (1 << 3) +#define MEAS1_GP1 (1 << 5) + +/* bit definitions of Measurement Enable 3 Register */ +#define MEAS3_IBAT (1 << 0) +#define MEAS3_BAT_DET (1 << 1) +#define MEAS3_CC (1 << 2) + +/* bit definitions of Measurement Off Time Register */ +#define MEAS_OFF_SLEEP_EN (1 << 1) + +/* bit definitions of GPADC Bias Current 2 Register */ +#define GPBIAS2_GPADC1_SET (2 << 4) +/* GPADC1 Bias Current value in uA unit */ +#define GPBIAS2_GPADC1_UA ((GPBIAS2_GPADC1_SET >> 4) * 5 + 1) + +/* bit definitions of GPADC Misc 1 Register */ +#define GPMISC1_GPADC_EN (1 << 0) + +/* bit definitions of Charger Control 6 Register */ +#define CC6_BAT_DET_GPADC1 1 + +/* bit definitions of Coulomb Counter Reading Register */ +#define CCNT_AVG_SEL (4 << 3) + +/* bit definitions of RTC miscellaneous Register1 */ +#define RTC_SOC_5LSB (0x1F << 3) + +/* bit definitions of RTC Register1 */ +#define RTC_SOC_3MSB (0x7) + +/* bit definitions of Power up Log register */ +#define BAT_WU_LOG (1<<6) + +/* coulomb counter index */ +#define CCNT_POS1 0 +#define CCNT_POS2 1 +#define CCNT_NEG1 2 +#define CCNT_NEG2 3 +#define CCNT_SPOS 4 +#define CCNT_SNEG 5 + +/* OCV -- Open Circuit Voltage */ +#define OCV_MODE_ACTIVE 0 +#define OCV_MODE_SLEEP 1 + +/* Vbat range of CC for measuring Rbat */ +#define LOW_BAT_THRESHOLD 3600 +#define VBATT_RESISTOR_MIN 3800 +#define VBATT_RESISTOR_MAX 4100 + +/* TBAT for batt, TINT for chip itself */ +#define PM860X_TEMP_TINT (0) +#define PM860X_TEMP_TBAT (1) + +/* + * Battery temperature based on NTC resistor, defined + * corresponding resistor value -- Ohm / C degeree. + */ +#define TBAT_NEG_25D 127773 /* -25 */ +#define TBAT_NEG_10D 54564 /* -10 */ +#define TBAT_0D 32330 /* 0 */ +#define TBAT_10D 19785 /* 10 */ +#define TBAT_20D 12468 /* 20 */ +#define TBAT_30D 8072 /* 30 */ +#define TBAT_40D 5356 /* 40 */ + +struct pm860x_battery_info { + struct pm860x_chip *chip; + struct i2c_client *i2c; + struct device *dev; + + struct power_supply *battery; + struct mutex lock; + int status; + int irq_cc; + int irq_batt; + int max_capacity; + int resistor; /* Battery Internal Resistor */ + int last_capacity; + int start_soc; + unsigned present:1; + unsigned temp_type:1; /* TINT or TBAT */ +}; + +struct ccnt { + unsigned long long int pos; + unsigned long long int neg; + unsigned int spos; + unsigned int sneg; + + int total_chg; /* mAh(3.6C) */ + int total_dischg; /* mAh(3.6C) */ +}; + +/* + * State of Charge. + * The first number is mAh(=3.6C), and the second number is percent point. + */ +static int array_soc[][2] = { + {4170, 100}, {4154, 99}, {4136, 98}, {4122, 97}, {4107, 96}, + {4102, 95}, {4088, 94}, {4081, 93}, {4070, 92}, {4060, 91}, + {4053, 90}, {4044, 89}, {4035, 88}, {4028, 87}, {4019, 86}, + {4013, 85}, {4006, 84}, {3995, 83}, {3987, 82}, {3982, 81}, + {3976, 80}, {3968, 79}, {3962, 78}, {3954, 77}, {3946, 76}, + {3941, 75}, {3934, 74}, {3929, 73}, {3922, 72}, {3916, 71}, + {3910, 70}, {3904, 69}, {3898, 68}, {3892, 67}, {3887, 66}, + {3880, 65}, {3874, 64}, {3868, 63}, {3862, 62}, {3854, 61}, + {3849, 60}, {3843, 59}, {3840, 58}, {3833, 57}, {3829, 56}, + {3824, 55}, {3818, 54}, {3815, 53}, {3810, 52}, {3808, 51}, + {3804, 50}, {3801, 49}, {3798, 48}, {3796, 47}, {3792, 46}, + {3789, 45}, {3785, 44}, {3784, 43}, {3782, 42}, {3780, 41}, + {3777, 40}, {3776, 39}, {3774, 38}, {3772, 37}, {3771, 36}, + {3769, 35}, {3768, 34}, {3764, 33}, {3763, 32}, {3760, 31}, + {3760, 30}, {3754, 29}, {3750, 28}, {3749, 27}, {3744, 26}, + {3740, 25}, {3734, 24}, {3732, 23}, {3728, 22}, {3726, 21}, + {3720, 20}, {3716, 19}, {3709, 18}, {3703, 17}, {3698, 16}, + {3692, 15}, {3683, 14}, {3675, 13}, {3670, 12}, {3665, 11}, + {3661, 10}, {3649, 9}, {3637, 8}, {3622, 7}, {3609, 6}, + {3580, 5}, {3558, 4}, {3540, 3}, {3510, 2}, {3429, 1}, +}; + +static struct ccnt ccnt_data; + +/* + * register 1 bit[7:0] -- bit[11:4] of measured value of voltage + * register 0 bit[3:0] -- bit[3:0] of measured value of voltage + */ +static int measure_12bit_voltage(struct pm860x_battery_info *info, + int offset, int *data) +{ + unsigned char buf[2]; + int ret; + + ret = pm860x_bulk_read(info->i2c, offset, 2, buf); + if (ret < 0) + return ret; + + *data = ((buf[0] & 0xff) << 4) | (buf[1] & 0x0f); + /* V_MEAS(mV) = data * 1.8 * 1000 / (2^12) */ + *data = ((*data & 0xfff) * 9 * 25) >> 9; + return 0; +} + +static int measure_vbatt(struct pm860x_battery_info *info, int state, + int *data) +{ + unsigned char buf[5]; + int ret; + + switch (state) { + case OCV_MODE_ACTIVE: + ret = measure_12bit_voltage(info, PM8607_VBAT_MEAS1, data); + if (ret) + return ret; + /* V_BATT_MEAS(mV) = value * 3 * 1.8 * 1000 / (2^12) */ + *data *= 3; + break; + case OCV_MODE_SLEEP: + /* + * voltage value of VBATT in sleep mode is saved in different + * registers. + * bit[11:10] -- bit[7:6] of LDO9(0x18) + * bit[9:8] -- bit[7:6] of LDO8(0x17) + * bit[7:6] -- bit[7:6] of LDO7(0x16) + * bit[5:4] -- bit[7:6] of LDO6(0x15) + * bit[3:0] -- bit[7:4] of LDO5(0x14) + */ + ret = pm860x_bulk_read(info->i2c, PM8607_LDO5, 5, buf); + if (ret < 0) + return ret; + ret = ((buf[4] >> 6) << 10) | ((buf[3] >> 6) << 8) + | ((buf[2] >> 6) << 6) | ((buf[1] >> 6) << 4) + | (buf[0] >> 4); + /* V_BATT_MEAS(mV) = data * 3 * 1.8 * 1000 / (2^12) */ + *data = ((*data & 0xff) * 27 * 25) >> 9; + break; + default: + return -EINVAL; + } + return 0; +} + +/* + * Return value is signed data. + * Negative value means discharging, and positive value means charging. + */ +static int measure_current(struct pm860x_battery_info *info, int *data) +{ + unsigned char buf[2]; + short s; + int ret; + + ret = pm860x_bulk_read(info->i2c, PM8607_IBAT_MEAS1, 2, buf); + if (ret < 0) + return ret; + + s = ((buf[0] & 0xff) << 8) | (buf[1] & 0xff); + /* current(mA) = value * 0.125 */ + *data = s >> 3; + return 0; +} + +static int set_charger_current(struct pm860x_battery_info *info, int data, + int *old) +{ + int ret; + + if (data < 50 || data > 1600 || !old) + return -EINVAL; + + data = ((data - 50) / 50) & 0x1f; + *old = pm860x_reg_read(info->i2c, PM8607_CHG_CTRL2); + *old = (*old & 0x1f) * 50 + 50; + ret = pm860x_set_bits(info->i2c, PM8607_CHG_CTRL2, 0x1f, data); + if (ret < 0) + return ret; + return 0; +} + +static int read_ccnt(struct pm860x_battery_info *info, int offset, + int *ccnt) +{ + unsigned char buf[2]; + int ret; + + ret = pm860x_set_bits(info->i2c, PM8607_CCNT, 7, offset & 7); + if (ret < 0) + goto out; + ret = pm860x_bulk_read(info->i2c, PM8607_CCNT_MEAS1, 2, buf); + if (ret < 0) + goto out; + *ccnt = ((buf[0] & 0xff) << 8) | (buf[1] & 0xff); + return 0; +out: + return ret; +} + +static int calc_ccnt(struct pm860x_battery_info *info, struct ccnt *ccnt) +{ + unsigned int sum; + int ret; + int data; + + ret = read_ccnt(info, CCNT_POS1, &data); + if (ret) + goto out; + sum = data & 0xffff; + ret = read_ccnt(info, CCNT_POS2, &data); + if (ret) + goto out; + sum |= (data & 0xffff) << 16; + ccnt->pos += sum; + + ret = read_ccnt(info, CCNT_NEG1, &data); + if (ret) + goto out; + sum = data & 0xffff; + ret = read_ccnt(info, CCNT_NEG2, &data); + if (ret) + goto out; + sum |= (data & 0xffff) << 16; + sum = ~sum + 1; /* since it's negative */ + ccnt->neg += sum; + + ret = read_ccnt(info, CCNT_SPOS, &data); + if (ret) + goto out; + ccnt->spos += data; + ret = read_ccnt(info, CCNT_SNEG, &data); + if (ret) + goto out; + + /* + * charge(mAh) = count * 1.6984 * 1e(-8) + * = count * 16984 * 1.024 * 1.024 * 1.024 / (2 ^ 40) + * = count * 18236 / (2 ^ 40) + */ + ccnt->total_chg = (int) ((ccnt->pos * 18236) >> 40); + ccnt->total_dischg = (int) ((ccnt->neg * 18236) >> 40); + return 0; +out: + return ret; +} + +static int clear_ccnt(struct pm860x_battery_info *info, struct ccnt *ccnt) +{ + int data; + + memset(ccnt, 0, sizeof(*ccnt)); + /* read to clear ccnt */ + read_ccnt(info, CCNT_POS1, &data); + read_ccnt(info, CCNT_POS2, &data); + read_ccnt(info, CCNT_NEG1, &data); + read_ccnt(info, CCNT_NEG2, &data); + read_ccnt(info, CCNT_SPOS, &data); + read_ccnt(info, CCNT_SNEG, &data); + return 0; +} + +/* Calculate Open Circuit Voltage */ +static int calc_ocv(struct pm860x_battery_info *info, int *ocv) +{ + int ret; + int i; + int data; + int vbatt_avg; + int vbatt_sum; + int ibatt_avg; + int ibatt_sum; + + if (!ocv) + return -EINVAL; + + for (i = 0, ibatt_sum = 0, vbatt_sum = 0; i < 10; i++) { + ret = measure_vbatt(info, OCV_MODE_ACTIVE, &data); + if (ret) + goto out; + vbatt_sum += data; + ret = measure_current(info, &data); + if (ret) + goto out; + ibatt_sum += data; + } + vbatt_avg = vbatt_sum / 10; + ibatt_avg = ibatt_sum / 10; + + mutex_lock(&info->lock); + if (info->present) + *ocv = vbatt_avg - ibatt_avg * info->resistor / 1000; + else + *ocv = vbatt_avg; + mutex_unlock(&info->lock); + dev_dbg(info->dev, "VBAT average:%d, OCV:%d\n", vbatt_avg, *ocv); + return 0; +out: + return ret; +} + +/* Calculate State of Charge (percent points) */ +static int calc_soc(struct pm860x_battery_info *info, int state, int *soc) +{ + int i; + int ocv; + int count; + int ret = -EINVAL; + + if (!soc) + return -EINVAL; + + switch (state) { + case OCV_MODE_ACTIVE: + ret = calc_ocv(info, &ocv); + break; + case OCV_MODE_SLEEP: + ret = measure_vbatt(info, OCV_MODE_SLEEP, &ocv); + break; + } + if (ret) + return ret; + + count = ARRAY_SIZE(array_soc); + if (ocv < array_soc[count - 1][0]) { + *soc = 0; + return 0; + } + + for (i = 0; i < count; i++) { + if (ocv >= array_soc[i][0]) { + *soc = array_soc[i][1]; + break; + } + } + return 0; +} + +static irqreturn_t pm860x_coulomb_handler(int irq, void *data) +{ + struct pm860x_battery_info *info = data; + + calc_ccnt(info, &ccnt_data); + return IRQ_HANDLED; +} + +static irqreturn_t pm860x_batt_handler(int irq, void *data) +{ + struct pm860x_battery_info *info = data; + int ret; + + mutex_lock(&info->lock); + ret = pm860x_reg_read(info->i2c, PM8607_STATUS_2); + if (ret & STATUS2_BAT) { + info->present = 1; + info->temp_type = PM860X_TEMP_TBAT; + } else { + info->present = 0; + info->temp_type = PM860X_TEMP_TINT; + } + mutex_unlock(&info->lock); + /* clear ccnt since battery is attached or dettached */ + clear_ccnt(info, &ccnt_data); + return IRQ_HANDLED; +} + +static void pm860x_init_battery(struct pm860x_battery_info *info) +{ + unsigned char buf[2]; + int ret; + int data; + int bat_remove; + int soc; + + /* measure enable on GPADC1 */ + data = MEAS1_GP1; + if (info->temp_type == PM860X_TEMP_TINT) + data |= MEAS1_TINT; + ret = pm860x_set_bits(info->i2c, PM8607_MEAS_EN1, data, data); + if (ret) + goto out; + + /* measure enable on IBAT, BAT_DET, CC. IBAT is depend on CC. */ + data = MEAS3_IBAT | MEAS3_BAT_DET | MEAS3_CC; + ret = pm860x_set_bits(info->i2c, PM8607_MEAS_EN3, data, data); + if (ret) + goto out; + + /* measure disable CC in sleep time */ + ret = pm860x_reg_write(info->i2c, PM8607_MEAS_OFF_TIME1, 0x82); + if (ret) + goto out; + ret = pm860x_reg_write(info->i2c, PM8607_MEAS_OFF_TIME2, 0x6c); + if (ret) + goto out; + + /* enable GPADC */ + ret = pm860x_set_bits(info->i2c, PM8607_GPADC_MISC1, + GPMISC1_GPADC_EN, GPMISC1_GPADC_EN); + if (ret < 0) + goto out; + + /* detect battery via GPADC1 */ + ret = pm860x_set_bits(info->i2c, PM8607_CHG_CTRL6, + CC6_BAT_DET_GPADC1, CC6_BAT_DET_GPADC1); + if (ret < 0) + goto out; + + ret = pm860x_set_bits(info->i2c, PM8607_CCNT, 7 << 3, + CCNT_AVG_SEL); + if (ret < 0) + goto out; + + /* set GPADC1 bias */ + ret = pm860x_set_bits(info->i2c, PM8607_GP_BIAS2, 0xF << 4, + GPBIAS2_GPADC1_SET); + if (ret < 0) + goto out; + + /* check whether battery present) */ + mutex_lock(&info->lock); + ret = pm860x_reg_read(info->i2c, PM8607_STATUS_2); + if (ret < 0) { + mutex_unlock(&info->lock); + goto out; + } + if (ret & STATUS2_BAT) { + info->present = 1; + info->temp_type = PM860X_TEMP_TBAT; + } else { + info->present = 0; + info->temp_type = PM860X_TEMP_TINT; + } + mutex_unlock(&info->lock); + + calc_soc(info, OCV_MODE_ACTIVE, &soc); + + data = pm860x_reg_read(info->i2c, PM8607_POWER_UP_LOG); + bat_remove = data & BAT_WU_LOG; + + dev_dbg(info->dev, "battery wake up? %s\n", + bat_remove != 0 ? "yes" : "no"); + + /* restore SOC from RTC domain register */ + if (bat_remove == 0) { + buf[0] = pm860x_reg_read(info->i2c, PM8607_RTC_MISC2); + buf[1] = pm860x_reg_read(info->i2c, PM8607_RTC1); + data = ((buf[1] & 0x3) << 5) | ((buf[0] >> 3) & 0x1F); + if (data > soc + 15) + info->start_soc = soc; + else if (data < soc - 15) + info->start_soc = soc; + else + info->start_soc = data; + dev_dbg(info->dev, "soc_rtc %d, soc_ocv :%d\n", data, soc); + } else { + pm860x_set_bits(info->i2c, PM8607_POWER_UP_LOG, + BAT_WU_LOG, BAT_WU_LOG); + info->start_soc = soc; + } + info->last_capacity = info->start_soc; + dev_dbg(info->dev, "init soc : %d\n", info->last_capacity); +out: + return; +} + +static void set_temp_threshold(struct pm860x_battery_info *info, + int min, int max) +{ + int data; + + /* (tmp << 8) / 1800 */ + if (min <= 0) + data = 0; + else + data = (min << 8) / 1800; + pm860x_reg_write(info->i2c, PM8607_GPADC1_HIGHTH, data); + dev_dbg(info->dev, "TEMP_HIGHTH : min: %d, 0x%x\n", min, data); + + if (max <= 0) + data = 0xff; + else + data = (max << 8) / 1800; + pm860x_reg_write(info->i2c, PM8607_GPADC1_LOWTH, data); + dev_dbg(info->dev, "TEMP_LOWTH:max : %d, 0x%x\n", max, data); +} + +static int measure_temp(struct pm860x_battery_info *info, int *data) +{ + int ret; + int temp; + int min; + int max; + + if (info->temp_type == PM860X_TEMP_TINT) { + ret = measure_12bit_voltage(info, PM8607_TINT_MEAS1, data); + if (ret) + return ret; + *data = (*data - 884) * 1000 / 3611; + } else { + ret = measure_12bit_voltage(info, PM8607_GPADC1_MEAS1, data); + if (ret) + return ret; + /* meausered Vtbat(mV) / Ibias_current(11uA)*/ + *data = (*data * 1000) / GPBIAS2_GPADC1_UA; + + if (*data > TBAT_NEG_25D) { + temp = -30; /* over cold , suppose -30 roughly */ + max = TBAT_NEG_10D * GPBIAS2_GPADC1_UA / 1000; + set_temp_threshold(info, 0, max); + } else if (*data > TBAT_NEG_10D) { + temp = -15; /* -15 degree, code */ + max = TBAT_NEG_10D * GPBIAS2_GPADC1_UA / 1000; + set_temp_threshold(info, 0, max); + } else if (*data > TBAT_0D) { + temp = -5; /* -5 degree */ + min = TBAT_NEG_10D * GPBIAS2_GPADC1_UA / 1000; + max = TBAT_40D * GPBIAS2_GPADC1_UA / 1000; + set_temp_threshold(info, min, max); + } else if (*data > TBAT_10D) { + temp = 5; /* in range of (0, 10) */ + min = TBAT_NEG_10D * GPBIAS2_GPADC1_UA / 1000; + max = TBAT_40D * GPBIAS2_GPADC1_UA / 1000; + set_temp_threshold(info, min, max); + } else if (*data > TBAT_20D) { + temp = 15; /* in range of (10, 20) */ + min = TBAT_NEG_10D * GPBIAS2_GPADC1_UA / 1000; + max = TBAT_40D * GPBIAS2_GPADC1_UA / 1000; + set_temp_threshold(info, min, max); + } else if (*data > TBAT_30D) { + temp = 25; /* in range of (20, 30) */ + min = TBAT_NEG_10D * GPBIAS2_GPADC1_UA / 1000; + max = TBAT_40D * GPBIAS2_GPADC1_UA / 1000; + set_temp_threshold(info, min, max); + } else if (*data > TBAT_40D) { + temp = 35; /* in range of (30, 40) */ + min = TBAT_NEG_10D * GPBIAS2_GPADC1_UA / 1000; + max = TBAT_40D * GPBIAS2_GPADC1_UA / 1000; + set_temp_threshold(info, min, max); + } else { + min = TBAT_40D * GPBIAS2_GPADC1_UA / 1000; + set_temp_threshold(info, min, 0); + temp = 45; /* over heat ,suppose 45 roughly */ + } + + dev_dbg(info->dev, "temp_C:%d C,temp_mv:%d mv\n", temp, *data); + *data = temp; + } + return 0; +} + +static int calc_resistor(struct pm860x_battery_info *info) +{ + int vbatt_sum1; + int vbatt_sum2; + int chg_current; + int ibatt_sum1; + int ibatt_sum2; + int data; + int ret; + int i; + + ret = measure_current(info, &data); + /* make sure that charging is launched by data > 0 */ + if (ret || data < 0) + goto out; + + ret = measure_vbatt(info, OCV_MODE_ACTIVE, &data); + if (ret) + goto out; + /* calculate resistor only in CC charge mode */ + if (data < VBATT_RESISTOR_MIN || data > VBATT_RESISTOR_MAX) + goto out; + + /* current is saved */ + if (set_charger_current(info, 500, &chg_current)) + goto out; + + /* + * set charge current as 500mA, wait about 500ms till charging + * process is launched and stable with the newer charging current. + */ + msleep(500); + + for (i = 0, vbatt_sum1 = 0, ibatt_sum1 = 0; i < 10; i++) { + ret = measure_vbatt(info, OCV_MODE_ACTIVE, &data); + if (ret) + goto out_meas; + vbatt_sum1 += data; + ret = measure_current(info, &data); + if (ret) + goto out_meas; + + if (data < 0) + ibatt_sum1 = ibatt_sum1 - data; /* discharging */ + else + ibatt_sum1 = ibatt_sum1 + data; /* charging */ + } + + if (set_charger_current(info, 100, &ret)) + goto out_meas; + /* + * set charge current as 100mA, wait about 500ms till charging + * process is launched and stable with the newer charging current. + */ + msleep(500); + + for (i = 0, vbatt_sum2 = 0, ibatt_sum2 = 0; i < 10; i++) { + ret = measure_vbatt(info, OCV_MODE_ACTIVE, &data); + if (ret) + goto out_meas; + vbatt_sum2 += data; + ret = measure_current(info, &data); + if (ret) + goto out_meas; + + if (data < 0) + ibatt_sum2 = ibatt_sum2 - data; /* discharging */ + else + ibatt_sum2 = ibatt_sum2 + data; /* charging */ + } + + /* restore current setting */ + if (set_charger_current(info, chg_current, &ret)) + goto out_meas; + + if ((vbatt_sum1 > vbatt_sum2) && (ibatt_sum1 > ibatt_sum2) && + (ibatt_sum2 > 0)) { + /* calculate resistor in discharging case */ + data = 1000 * (vbatt_sum1 - vbatt_sum2) + / (ibatt_sum1 - ibatt_sum2); + if ((data - info->resistor > 0) && + (data - info->resistor < info->resistor)) + info->resistor = data; + if ((info->resistor - data > 0) && + (info->resistor - data < data)) + info->resistor = data; + } + return 0; + +out_meas: + set_charger_current(info, chg_current, &ret); +out: + return -EINVAL; +} + +static int calc_capacity(struct pm860x_battery_info *info, int *cap) +{ + int ret; + int data; + int ibat; + int cap_ocv = 0; + int cap_cc = 0; + + ret = calc_ccnt(info, &ccnt_data); + if (ret) + goto out; +soc: + data = info->max_capacity * info->start_soc / 100; + if (ccnt_data.total_dischg - ccnt_data.total_chg <= data) { + cap_cc = + data + ccnt_data.total_chg - ccnt_data.total_dischg; + } else { + clear_ccnt(info, &ccnt_data); + calc_soc(info, OCV_MODE_ACTIVE, &info->start_soc); + dev_dbg(info->dev, "restart soc = %d !\n", + info->start_soc); + goto soc; + } + + cap_cc = cap_cc * 100 / info->max_capacity; + if (cap_cc < 0) + cap_cc = 0; + else if (cap_cc > 100) + cap_cc = 100; + + dev_dbg(info->dev, "%s, last cap : %d", __func__, + info->last_capacity); + + ret = measure_current(info, &ibat); + if (ret) + goto out; + /* Calculate the capacity when discharging(ibat < 0) */ + if (ibat < 0) { + ret = calc_soc(info, OCV_MODE_ACTIVE, &cap_ocv); + if (ret) + cap_ocv = info->last_capacity; + ret = measure_vbatt(info, OCV_MODE_ACTIVE, &data); + if (ret) + goto out; + if (data <= LOW_BAT_THRESHOLD) { + /* choose the lower capacity value to report + * between vbat and CC when vbat < 3.6v; + * than 3.6v; + */ + *cap = min(cap_ocv, cap_cc); + } else { + /* when detect vbat > 3.6v, but cap_cc < 15,and + * cap_ocv is 10% larger than cap_cc, we can think + * CC have some accumulation error, switch to OCV + * to estimate capacity; + * */ + if (cap_cc < 15 && cap_ocv - cap_cc > 10) + *cap = cap_ocv; + else + *cap = cap_cc; + } + /* when discharging, make sure current capacity + * is lower than last*/ + if (*cap > info->last_capacity) + *cap = info->last_capacity; + } else { + *cap = cap_cc; + } + info->last_capacity = *cap; + + dev_dbg(info->dev, "%s, cap_ocv:%d cap_cc:%d, cap:%d\n", + (ibat < 0) ? "discharging" : "charging", + cap_ocv, cap_cc, *cap); + /* + * store the current capacity to RTC domain register, + * after next power up , it will be restored. + */ + pm860x_set_bits(info->i2c, PM8607_RTC_MISC2, RTC_SOC_5LSB, + (*cap & 0x1F) << 3); + pm860x_set_bits(info->i2c, PM8607_RTC1, RTC_SOC_3MSB, + ((*cap >> 5) & 0x3)); + return 0; +out: + return ret; +} + +static void pm860x_external_power_changed(struct power_supply *psy) +{ + struct pm860x_battery_info *info = dev_get_drvdata(psy->dev.parent); + + calc_resistor(info); +} + +static int pm860x_batt_get_prop(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct pm860x_battery_info *info = dev_get_drvdata(psy->dev.parent); + int data; + int ret; + + switch (psp) { + case POWER_SUPPLY_PROP_PRESENT: + val->intval = info->present; + break; + case POWER_SUPPLY_PROP_CAPACITY: + ret = calc_capacity(info, &data); + if (ret) + return ret; + if (data < 0) + data = 0; + else if (data > 100) + data = 100; + /* return 100 if battery is not attached */ + if (!info->present) + data = 100; + val->intval = data; + break; + case POWER_SUPPLY_PROP_TECHNOLOGY: + val->intval = POWER_SUPPLY_TECHNOLOGY_LION; + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + /* return real vbatt Voltage */ + ret = measure_vbatt(info, OCV_MODE_ACTIVE, &data); + if (ret) + return ret; + val->intval = data * 1000; + break; + case POWER_SUPPLY_PROP_VOLTAGE_AVG: + /* return Open Circuit Voltage (not measured voltage) */ + ret = calc_ocv(info, &data); + if (ret) + return ret; + val->intval = data * 1000; + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + ret = measure_current(info, &data); + if (ret) + return ret; + val->intval = data; + break; + case POWER_SUPPLY_PROP_TEMP: + if (info->present) { + ret = measure_temp(info, &data); + if (ret) + return ret; + data *= 10; + } else { + /* Fake Temp 25C Without Battery */ + data = 250; + } + val->intval = data; + break; + default: + return -ENODEV; + } + return 0; +} + +static int pm860x_batt_set_prop(struct power_supply *psy, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct pm860x_battery_info *info = dev_get_drvdata(psy->dev.parent); + + switch (psp) { + case POWER_SUPPLY_PROP_CHARGE_FULL: + clear_ccnt(info, &ccnt_data); + info->start_soc = 100; + dev_dbg(info->dev, "chg done, update soc = %d\n", + info->start_soc); + break; + default: + return -EPERM; + } + + return 0; +} + + +static enum power_supply_property pm860x_batt_props[] = { + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_VOLTAGE_AVG, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_TEMP, +}; + +static const struct power_supply_desc pm860x_battery_desc = { + .name = "battery-monitor", + .type = POWER_SUPPLY_TYPE_BATTERY, + .properties = pm860x_batt_props, + .num_properties = ARRAY_SIZE(pm860x_batt_props), + .get_property = pm860x_batt_get_prop, + .set_property = pm860x_batt_set_prop, + .external_power_changed = pm860x_external_power_changed, +}; + +static int pm860x_battery_probe(struct platform_device *pdev) +{ + struct pm860x_chip *chip = dev_get_drvdata(pdev->dev.parent); + struct pm860x_battery_info *info; + struct pm860x_power_pdata *pdata; + int ret; + + info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + info->irq_cc = platform_get_irq(pdev, 0); + if (info->irq_cc <= 0) { + dev_err(&pdev->dev, "No IRQ resource!\n"); + return -EINVAL; + } + + info->irq_batt = platform_get_irq(pdev, 1); + if (info->irq_batt <= 0) { + dev_err(&pdev->dev, "No IRQ resource!\n"); + return -EINVAL; + } + + info->chip = chip; + info->i2c = + (chip->id == CHIP_PM8607) ? chip->client : chip->companion; + info->dev = &pdev->dev; + info->status = POWER_SUPPLY_STATUS_UNKNOWN; + pdata = pdev->dev.platform_data; + + mutex_init(&info->lock); + platform_set_drvdata(pdev, info); + + pm860x_init_battery(info); + + if (pdata && pdata->max_capacity) + info->max_capacity = pdata->max_capacity; + else + info->max_capacity = 1500; /* set default capacity */ + if (pdata && pdata->resistor) + info->resistor = pdata->resistor; + else + info->resistor = 300; /* set default internal resistor */ + + 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 = 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); + return ret; + } + + 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); + return ret; + } + + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int pm860x_battery_suspend(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct pm860x_chip *chip = dev_get_drvdata(pdev->dev.parent); + + if (device_may_wakeup(dev)) + chip->wakeup_flag |= 1 << PM8607_IRQ_CC; + return 0; +} + +static int pm860x_battery_resume(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct pm860x_chip *chip = dev_get_drvdata(pdev->dev.parent); + + if (device_may_wakeup(dev)) + chip->wakeup_flag &= ~(1 << PM8607_IRQ_CC); + return 0; +} +#endif + +static SIMPLE_DEV_PM_OPS(pm860x_battery_pm_ops, + pm860x_battery_suspend, pm860x_battery_resume); + +static struct platform_driver pm860x_battery_driver = { + .driver = { + .name = "88pm860x-battery", + .pm = &pm860x_battery_pm_ops, + }, + .probe = pm860x_battery_probe, +}; +module_platform_driver(pm860x_battery_driver); + +MODULE_DESCRIPTION("Marvell 88PM860x Battery driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/power/supply/88pm860x_charger.c b/drivers/power/supply/88pm860x_charger.c new file mode 100644 index 000000000000..2b82e44d9027 --- /dev/null +++ b/drivers/power/supply/88pm860x_charger.c @@ -0,0 +1,760 @@ +/* + * Battery driver for Marvell 88PM860x PMIC + * + * Copyright (c) 2012 Marvell International Ltd. + * Author: Jett Zhou + * Haojian Zhuang + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* bit definitions of Status Query Interface 2 */ +#define STATUS2_CHG (1 << 2) + +/* bit definitions of Reset Out Register */ +#define RESET_SW_PD (1 << 7) + +/* bit definitions of PreReg 1 */ +#define PREREG1_90MA (0x0) +#define PREREG1_180MA (0x1) +#define PREREG1_450MA (0x4) +#define PREREG1_540MA (0x5) +#define PREREG1_1350MA (0xE) +#define PREREG1_VSYS_4_5V (3 << 4) + +/* bit definitions of Charger Control 1 Register */ +#define CC1_MODE_OFF (0) +#define CC1_MODE_PRECHARGE (1) +#define CC1_MODE_FASTCHARGE (2) +#define CC1_MODE_PULSECHARGE (3) +#define CC1_ITERM_20MA (0 << 2) +#define CC1_ITERM_60MA (2 << 2) +#define CC1_VFCHG_4_2V (9 << 4) + +/* bit definitions of Charger Control 2 Register */ +#define CC2_ICHG_100MA (0x1) +#define CC2_ICHG_500MA (0x9) +#define CC2_ICHG_1000MA (0x13) + +/* bit definitions of Charger Control 3 Register */ +#define CC3_180MIN_TIMEOUT (0x6 << 4) +#define CC3_270MIN_TIMEOUT (0x7 << 4) +#define CC3_360MIN_TIMEOUT (0xA << 4) +#define CC3_DISABLE_TIMEOUT (0xF << 4) + +/* bit definitions of Charger Control 4 Register */ +#define CC4_IPRE_40MA (7) +#define CC4_VPCHG_3_2V (3 << 4) +#define CC4_IFCHG_MON_EN (1 << 6) +#define CC4_BTEMP_MON_EN (1 << 7) + +/* bit definitions of Charger Control 6 Register */ +#define CC6_BAT_OV_EN (1 << 2) +#define CC6_BAT_UV_EN (1 << 3) +#define CC6_UV_VBAT_SET (0x3 << 6) /* 2.8v */ + +/* bit definitions of Charger Control 7 Register */ +#define CC7_BAT_REM_EN (1 << 3) +#define CC7_IFSM_EN (1 << 7) + +/* bit definitions of Measurement Enable 1 Register */ +#define MEAS1_VBAT (1 << 0) + +/* bit definitions of Measurement Enable 3 Register */ +#define MEAS3_IBAT_EN (1 << 0) +#define MEAS3_CC_EN (1 << 2) + +#define FSM_INIT 0 +#define FSM_DISCHARGE 1 +#define FSM_PRECHARGE 2 +#define FSM_FASTCHARGE 3 + +#define PRECHARGE_THRESHOLD 3100 +#define POWEROFF_THRESHOLD 3400 +#define CHARGE_THRESHOLD 4000 +#define DISCHARGE_THRESHOLD 4180 + +/* over-temperature on PM8606 setting */ +#define OVER_TEMP_FLAG (1 << 6) +#define OVTEMP_AUTORECOVER (1 << 3) + +/* over-voltage protect on vchg setting mv */ +#define VCHG_NORMAL_LOW 4200 +#define VCHG_NORMAL_CHECK 5800 +#define VCHG_NORMAL_HIGH 6000 +#define VCHG_OVP_LOW 5500 + +struct pm860x_charger_info { + struct pm860x_chip *chip; + struct i2c_client *i2c; + struct i2c_client *i2c_8606; + struct device *dev; + + struct power_supply *usb; + struct mutex lock; + int irq_nums; + int irq[7]; + unsigned state:3; /* fsm state */ + unsigned online:1; /* usb charger */ + unsigned present:1; /* battery present */ + unsigned allowed:1; +}; + +static char *pm860x_supplied_to[] = { + "battery-monitor", +}; + +static int measure_vchg(struct pm860x_charger_info *info, int *data) +{ + unsigned char buf[2]; + int ret = 0; + + ret = pm860x_bulk_read(info->i2c, PM8607_VCHG_MEAS1, 2, buf); + if (ret < 0) + return ret; + + *data = ((buf[0] & 0xff) << 4) | (buf[1] & 0x0f); + /* V_BATT_MEAS(mV) = value * 5 * 1.8 * 1000 / (2^12) */ + *data = ((*data & 0xfff) * 9 * 125) >> 9; + + dev_dbg(info->dev, "%s, vchg: %d mv\n", __func__, *data); + + return ret; +} + +static void set_vchg_threshold(struct pm860x_charger_info *info, + int min, int max) +{ + int data; + + /* (tmp << 8) * / 5 / 1800 */ + if (min <= 0) + data = 0; + else + data = (min << 5) / 1125; + pm860x_reg_write(info->i2c, PM8607_VCHG_LOWTH, data); + dev_dbg(info->dev, "VCHG_LOWTH:%dmv, 0x%x\n", min, data); + + if (max <= 0) + data = 0xff; + else + data = (max << 5) / 1125; + pm860x_reg_write(info->i2c, PM8607_VCHG_HIGHTH, data); + dev_dbg(info->dev, "VCHG_HIGHTH:%dmv, 0x%x\n", max, data); + +} + +static void set_vbatt_threshold(struct pm860x_charger_info *info, + int min, int max) +{ + int data; + + /* (tmp << 8) * 3 / 1800 */ + if (min <= 0) + data = 0; + else + data = (min << 5) / 675; + pm860x_reg_write(info->i2c, PM8607_VBAT_LOWTH, data); + dev_dbg(info->dev, "VBAT Min:%dmv, LOWTH:0x%x\n", min, data); + + if (max <= 0) + data = 0xff; + else + data = (max << 5) / 675; + pm860x_reg_write(info->i2c, PM8607_VBAT_HIGHTH, data); + dev_dbg(info->dev, "VBAT Max:%dmv, HIGHTH:0x%x\n", max, data); + + return; +} + +static int start_precharge(struct pm860x_charger_info *info) +{ + int ret; + + dev_dbg(info->dev, "Start Pre-charging!\n"); + set_vbatt_threshold(info, 0, 0); + + ret = pm860x_reg_write(info->i2c_8606, PM8606_PREREGULATORA, + PREREG1_1350MA | PREREG1_VSYS_4_5V); + if (ret < 0) + goto out; + /* stop charging */ + ret = pm860x_set_bits(info->i2c, PM8607_CHG_CTRL1, 3, + CC1_MODE_OFF); + if (ret < 0) + goto out; + /* set 270 minutes timeout */ + ret = pm860x_set_bits(info->i2c, PM8607_CHG_CTRL3, (0xf << 4), + CC3_270MIN_TIMEOUT); + if (ret < 0) + goto out; + /* set precharge current, termination voltage, IBAT & TBAT monitor */ + ret = pm860x_reg_write(info->i2c, PM8607_CHG_CTRL4, + CC4_IPRE_40MA | CC4_VPCHG_3_2V | + CC4_IFCHG_MON_EN | CC4_BTEMP_MON_EN); + if (ret < 0) + goto out; + ret = pm860x_set_bits(info->i2c, PM8607_CHG_CTRL7, + CC7_BAT_REM_EN | CC7_IFSM_EN, + CC7_BAT_REM_EN | CC7_IFSM_EN); + if (ret < 0) + goto out; + /* trigger precharge */ + ret = pm860x_set_bits(info->i2c, PM8607_CHG_CTRL1, 3, + CC1_MODE_PRECHARGE); +out: + return ret; +} + +static int start_fastcharge(struct pm860x_charger_info *info) +{ + int ret; + + dev_dbg(info->dev, "Start Fast-charging!\n"); + + /* set fastcharge termination current & voltage, disable charging */ + ret = pm860x_reg_write(info->i2c, PM8607_CHG_CTRL1, + CC1_MODE_OFF | CC1_ITERM_60MA | + CC1_VFCHG_4_2V); + if (ret < 0) + goto out; + ret = pm860x_reg_write(info->i2c_8606, PM8606_PREREGULATORA, + PREREG1_540MA | PREREG1_VSYS_4_5V); + if (ret < 0) + goto out; + ret = pm860x_set_bits(info->i2c, PM8607_CHG_CTRL2, 0x1f, + CC2_ICHG_500MA); + if (ret < 0) + goto out; + /* set 270 minutes timeout */ + ret = pm860x_set_bits(info->i2c, PM8607_CHG_CTRL3, (0xf << 4), + CC3_270MIN_TIMEOUT); + if (ret < 0) + goto out; + /* set IBAT & TBAT monitor */ + ret = pm860x_set_bits(info->i2c, PM8607_CHG_CTRL4, + CC4_IFCHG_MON_EN | CC4_BTEMP_MON_EN, + CC4_IFCHG_MON_EN | CC4_BTEMP_MON_EN); + if (ret < 0) + goto out; + ret = pm860x_set_bits(info->i2c, PM8607_CHG_CTRL6, + CC6_BAT_OV_EN | CC6_BAT_UV_EN | + CC6_UV_VBAT_SET, + CC6_BAT_OV_EN | CC6_BAT_UV_EN | + CC6_UV_VBAT_SET); + if (ret < 0) + goto out; + ret = pm860x_set_bits(info->i2c, PM8607_CHG_CTRL7, + CC7_BAT_REM_EN | CC7_IFSM_EN, + CC7_BAT_REM_EN | CC7_IFSM_EN); + if (ret < 0) + goto out; + /* launch fast-charge */ + ret = pm860x_set_bits(info->i2c, PM8607_CHG_CTRL1, 3, + CC1_MODE_FASTCHARGE); + /* vchg threshold setting */ + set_vchg_threshold(info, VCHG_NORMAL_LOW, VCHG_NORMAL_HIGH); +out: + return ret; +} + +static void stop_charge(struct pm860x_charger_info *info, int vbatt) +{ + dev_dbg(info->dev, "Stop charging!\n"); + pm860x_set_bits(info->i2c, PM8607_CHG_CTRL1, 3, CC1_MODE_OFF); + if (vbatt > CHARGE_THRESHOLD && info->online) + set_vbatt_threshold(info, CHARGE_THRESHOLD, 0); +} + +static void power_off_notification(struct pm860x_charger_info *info) +{ + dev_dbg(info->dev, "Power-off notification!\n"); +} + +static int set_charging_fsm(struct pm860x_charger_info *info) +{ + struct power_supply *psy; + union power_supply_propval data; + unsigned char fsm_state[][16] = { "init", "discharge", "precharge", + "fastcharge", + }; + int ret; + int vbatt; + + psy = power_supply_get_by_name(pm860x_supplied_to[0]); + if (!psy) + return -EINVAL; + ret = power_supply_get_property(psy, POWER_SUPPLY_PROP_VOLTAGE_NOW, + &data); + if (ret) { + power_supply_put(psy); + return ret; + } + vbatt = data.intval / 1000; + + ret = power_supply_get_property(psy, POWER_SUPPLY_PROP_PRESENT, &data); + if (ret) { + power_supply_put(psy); + return ret; + } + power_supply_put(psy); + + mutex_lock(&info->lock); + info->present = data.intval; + + dev_dbg(info->dev, "Entering FSM:%s, Charger:%s, Battery:%s, " + "Allowed:%d\n", + &fsm_state[info->state][0], + (info->online) ? "online" : "N/A", + (info->present) ? "present" : "N/A", info->allowed); + dev_dbg(info->dev, "set_charging_fsm:vbatt:%d(mV)\n", vbatt); + + switch (info->state) { + case FSM_INIT: + if (info->online && info->present && info->allowed) { + if (vbatt < PRECHARGE_THRESHOLD) { + info->state = FSM_PRECHARGE; + start_precharge(info); + } else if (vbatt > DISCHARGE_THRESHOLD) { + info->state = FSM_DISCHARGE; + stop_charge(info, vbatt); + } else if (vbatt < DISCHARGE_THRESHOLD) { + info->state = FSM_FASTCHARGE; + start_fastcharge(info); + } + } else { + if (vbatt < POWEROFF_THRESHOLD) { + power_off_notification(info); + } else { + info->state = FSM_DISCHARGE; + stop_charge(info, vbatt); + } + } + break; + case FSM_PRECHARGE: + if (info->online && info->present && info->allowed) { + if (vbatt > PRECHARGE_THRESHOLD) { + info->state = FSM_FASTCHARGE; + start_fastcharge(info); + } + } else { + info->state = FSM_DISCHARGE; + stop_charge(info, vbatt); + } + break; + case FSM_FASTCHARGE: + if (info->online && info->present && info->allowed) { + if (vbatt < PRECHARGE_THRESHOLD) { + info->state = FSM_PRECHARGE; + start_precharge(info); + } + } else { + info->state = FSM_DISCHARGE; + stop_charge(info, vbatt); + } + break; + case FSM_DISCHARGE: + if (info->online && info->present && info->allowed) { + if (vbatt < PRECHARGE_THRESHOLD) { + info->state = FSM_PRECHARGE; + start_precharge(info); + } else if (vbatt < DISCHARGE_THRESHOLD) { + info->state = FSM_FASTCHARGE; + start_fastcharge(info); + } + } else { + if (vbatt < POWEROFF_THRESHOLD) + power_off_notification(info); + else if (vbatt > CHARGE_THRESHOLD && info->online) + set_vbatt_threshold(info, CHARGE_THRESHOLD, 0); + } + break; + default: + dev_warn(info->dev, "FSM meets wrong state:%d\n", + info->state); + break; + } + dev_dbg(info->dev, + "Out FSM:%s, Charger:%s, Battery:%s, Allowed:%d\n", + &fsm_state[info->state][0], + (info->online) ? "online" : "N/A", + (info->present) ? "present" : "N/A", info->allowed); + mutex_unlock(&info->lock); + + return 0; +} + +static irqreturn_t pm860x_charger_handler(int irq, void *data) +{ + struct pm860x_charger_info *info = data; + int ret; + + mutex_lock(&info->lock); + ret = pm860x_reg_read(info->i2c, PM8607_STATUS_2); + if (ret < 0) { + mutex_unlock(&info->lock); + goto out; + } + if (ret & STATUS2_CHG) { + info->online = 1; + info->allowed = 1; + } else { + info->online = 0; + info->allowed = 0; + } + mutex_unlock(&info->lock); + dev_dbg(info->dev, "%s, Charger:%s, Allowed:%d\n", __func__, + (info->online) ? "online" : "N/A", info->allowed); + + set_charging_fsm(info); + + power_supply_changed(info->usb); +out: + return IRQ_HANDLED; +} + +static irqreturn_t pm860x_temp_handler(int irq, void *data) +{ + struct power_supply *psy; + struct pm860x_charger_info *info = data; + union power_supply_propval temp; + int value; + int ret; + + psy = power_supply_get_by_name(pm860x_supplied_to[0]); + if (!psy) + return IRQ_HANDLED; + ret = power_supply_get_property(psy, POWER_SUPPLY_PROP_TEMP, &temp); + if (ret) + goto out; + value = temp.intval / 10; + + mutex_lock(&info->lock); + /* Temperature < -10 C or >40 C, Will not allow charge */ + if (value < -10 || value > 40) + info->allowed = 0; + else + info->allowed = 1; + dev_dbg(info->dev, "%s, Allowed: %d\n", __func__, info->allowed); + mutex_unlock(&info->lock); + + set_charging_fsm(info); +out: + power_supply_put(psy); + return IRQ_HANDLED; +} + +static irqreturn_t pm860x_exception_handler(int irq, void *data) +{ + struct pm860x_charger_info *info = data; + + mutex_lock(&info->lock); + info->allowed = 0; + mutex_unlock(&info->lock); + dev_dbg(info->dev, "%s, irq: %d\n", __func__, irq); + + set_charging_fsm(info); + return IRQ_HANDLED; +} + +static irqreturn_t pm860x_done_handler(int irq, void *data) +{ + struct pm860x_charger_info *info = data; + struct power_supply *psy; + union power_supply_propval val; + int ret; + int vbatt; + + mutex_lock(&info->lock); + /* pre-charge done, will transimit to fast-charge stage */ + if (info->state == FSM_PRECHARGE) { + info->allowed = 1; + goto out; + } + /* + * Fast charge done, delay to read + * the correct status of CHG_DET. + */ + mdelay(5); + info->allowed = 0; + psy = power_supply_get_by_name(pm860x_supplied_to[0]); + if (!psy) + goto out; + ret = power_supply_get_property(psy, POWER_SUPPLY_PROP_VOLTAGE_NOW, + &val); + if (ret) + goto out_psy_put; + vbatt = val.intval / 1000; + /* + * CHG_DONE interrupt is faster than CHG_DET interrupt when + * plug in/out usb, So we can not rely on info->online, we + * need check pm8607 status register to check usb is online + * or not, then we can decide it is real charge done + * automatically or it is triggered by usb plug out; + */ + ret = pm860x_reg_read(info->i2c, PM8607_STATUS_2); + if (ret < 0) + goto out_psy_put; + if (vbatt > CHARGE_THRESHOLD && ret & STATUS2_CHG) + power_supply_set_property(psy, POWER_SUPPLY_PROP_CHARGE_FULL, + &val); + +out_psy_put: + power_supply_put(psy); +out: + mutex_unlock(&info->lock); + dev_dbg(info->dev, "%s, Allowed: %d\n", __func__, info->allowed); + set_charging_fsm(info); + + return IRQ_HANDLED; +} + +static irqreturn_t pm860x_vbattery_handler(int irq, void *data) +{ + struct pm860x_charger_info *info = data; + + mutex_lock(&info->lock); + + set_vbatt_threshold(info, 0, 0); + + if (info->present && info->online) + info->allowed = 1; + else + info->allowed = 0; + mutex_unlock(&info->lock); + dev_dbg(info->dev, "%s, Allowed: %d\n", __func__, info->allowed); + + set_charging_fsm(info); + + return IRQ_HANDLED; +} + +static irqreturn_t pm860x_vchg_handler(int irq, void *data) +{ + struct pm860x_charger_info *info = data; + int vchg = 0; + + if (info->present) + goto out; + + measure_vchg(info, &vchg); + + mutex_lock(&info->lock); + if (!info->online) { + int status; + /* check if over-temp on pm8606 or not */ + status = pm860x_reg_read(info->i2c_8606, PM8606_FLAGS); + if (status & OVER_TEMP_FLAG) { + /* clear over temp flag and set auto recover */ + pm860x_set_bits(info->i2c_8606, PM8606_FLAGS, + OVER_TEMP_FLAG, OVER_TEMP_FLAG); + pm860x_set_bits(info->i2c_8606, + PM8606_VSYS, + OVTEMP_AUTORECOVER, + OVTEMP_AUTORECOVER); + dev_dbg(info->dev, + "%s, pm8606 over-temp occurred\n", __func__); + } + } + + if (vchg > VCHG_NORMAL_CHECK) { + set_vchg_threshold(info, VCHG_OVP_LOW, 0); + info->allowed = 0; + dev_dbg(info->dev, + "%s,pm8607 over-vchg occurred,vchg = %dmv\n", + __func__, vchg); + } else if (vchg < VCHG_OVP_LOW) { + set_vchg_threshold(info, VCHG_NORMAL_LOW, + VCHG_NORMAL_HIGH); + info->allowed = 1; + dev_dbg(info->dev, + "%s,pm8607 over-vchg recover,vchg = %dmv\n", + __func__, vchg); + } + mutex_unlock(&info->lock); + + dev_dbg(info->dev, "%s, Allowed: %d\n", __func__, info->allowed); + set_charging_fsm(info); +out: + return IRQ_HANDLED; +} + +static int pm860x_usb_get_prop(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct pm860x_charger_info *info = power_supply_get_drvdata(psy); + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + if (info->state == FSM_FASTCHARGE || + info->state == FSM_PRECHARGE) + val->intval = POWER_SUPPLY_STATUS_CHARGING; + else + val->intval = POWER_SUPPLY_STATUS_DISCHARGING; + break; + case POWER_SUPPLY_PROP_ONLINE: + val->intval = info->online; + break; + default: + return -ENODEV; + } + return 0; +} + +static enum power_supply_property pm860x_usb_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_ONLINE, +}; + +static int pm860x_init_charger(struct pm860x_charger_info *info) +{ + int ret; + + ret = pm860x_reg_read(info->i2c, PM8607_STATUS_2); + if (ret < 0) + return ret; + + mutex_lock(&info->lock); + info->state = FSM_INIT; + if (ret & STATUS2_CHG) { + info->online = 1; + info->allowed = 1; + } else { + info->online = 0; + info->allowed = 0; + } + mutex_unlock(&info->lock); + + set_charging_fsm(info); + return 0; +} + +static struct pm860x_irq_desc { + const char *name; + irqreturn_t (*handler)(int irq, void *data); +} pm860x_irq_descs[] = { + { "usb supply detect", pm860x_charger_handler }, + { "charge done", pm860x_done_handler }, + { "charge timeout", pm860x_exception_handler }, + { "charge fault", pm860x_exception_handler }, + { "temperature", pm860x_temp_handler }, + { "vbatt", pm860x_vbattery_handler }, + { "vchg", pm860x_vchg_handler }, +}; + +static const struct power_supply_desc pm860x_charger_desc = { + .name = "usb", + .type = POWER_SUPPLY_TYPE_USB, + .properties = pm860x_usb_props, + .num_properties = ARRAY_SIZE(pm860x_usb_props), + .get_property = pm860x_usb_get_prop, +}; + +static int pm860x_charger_probe(struct platform_device *pdev) +{ + struct pm860x_chip *chip = dev_get_drvdata(pdev->dev.parent); + struct power_supply_config psy_cfg = {}; + struct pm860x_charger_info *info; + int ret; + int count; + int i; + int j; + + info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + count = pdev->num_resources; + for (i = 0, j = 0; i < count; i++) { + info->irq[j] = platform_get_irq(pdev, i); + if (info->irq[j] < 0) + continue; + j++; + } + info->irq_nums = j; + + info->chip = chip; + info->i2c = + (chip->id == CHIP_PM8607) ? chip->client : chip->companion; + info->i2c_8606 = + (chip->id == CHIP_PM8607) ? chip->companion : chip->client; + if (!info->i2c_8606) { + dev_err(&pdev->dev, "Missed I2C address of 88PM8606!\n"); + ret = -EINVAL; + goto out; + } + info->dev = &pdev->dev; + + /* set init value for the case we are not using battery */ + set_vchg_threshold(info, VCHG_NORMAL_LOW, VCHG_OVP_LOW); + + mutex_init(&info->lock); + platform_set_drvdata(pdev, info); + + psy_cfg.drv_data = info; + psy_cfg.supplied_to = pm860x_supplied_to; + psy_cfg.num_supplicants = ARRAY_SIZE(pm860x_supplied_to); + info->usb = power_supply_register(&pdev->dev, &pm860x_charger_desc, + &psy_cfg); + if (IS_ERR(info->usb)) { + ret = PTR_ERR(info->usb); + goto out; + } + + pm860x_init_charger(info); + + for (i = 0; i < ARRAY_SIZE(info->irq); i++) { + ret = request_threaded_irq(info->irq[i], NULL, + pm860x_irq_descs[i].handler, + IRQF_ONESHOT, pm860x_irq_descs[i].name, info); + if (ret < 0) { + dev_err(chip->dev, "Failed to request IRQ: #%d: %d\n", + info->irq[i], ret); + goto out_irq; + } + } + return 0; + +out_irq: + power_supply_unregister(info->usb); + while (--i >= 0) + free_irq(info->irq[i], info); +out: + return ret; +} + +static int pm860x_charger_remove(struct platform_device *pdev) +{ + struct pm860x_charger_info *info = platform_get_drvdata(pdev); + int i; + + power_supply_unregister(info->usb); + for (i = 0; i < info->irq_nums; i++) + free_irq(info->irq[i], info); + return 0; +} + +static struct platform_driver pm860x_charger_driver = { + .driver = { + .name = "88pm860x-charger", + }, + .probe = pm860x_charger_probe, + .remove = pm860x_charger_remove, +}; +module_platform_driver(pm860x_charger_driver); + +MODULE_DESCRIPTION("Marvell 88PM860x Charger driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig new file mode 100644 index 000000000000..76806a0be820 --- /dev/null +++ b/drivers/power/supply/Kconfig @@ -0,0 +1,514 @@ +menuconfig POWER_SUPPLY + bool "Power supply class support" + help + Say Y here to enable power supply class support. This allows + power supply (batteries, AC, USB) monitoring by userspace + via sysfs and uevent (if available) and/or APM kernel interface + (if selected below). + +if POWER_SUPPLY + +config POWER_SUPPLY_DEBUG + bool "Power supply debug" + help + Say Y here to enable debugging messages for power supply class + and drivers. + +config PDA_POWER + tristate "Generic PDA/phone power driver" + depends on !S390 + help + Say Y here to enable generic power driver for PDAs and phones with + one or two external power supplies (AC/USB) connected to main and + backup batteries, and optional builtin charger. + +config APM_POWER + tristate "APM emulation for class batteries" + depends on APM_EMULATION + help + Say Y here to enable support APM status emulation using + battery class devices. + +config GENERIC_ADC_BATTERY + tristate "Generic battery support using IIO" + depends on IIO + help + Say Y here to enable support for the generic battery driver + which uses IIO framework to read adc. + +config MAX8925_POWER + tristate "MAX8925 battery charger support" + depends on MFD_MAX8925 + help + Say Y here to enable support for the battery charger in the Maxim + MAX8925 PMIC. + +config WM831X_BACKUP + tristate "WM831X backup battery charger support" + depends on MFD_WM831X + help + Say Y here to enable support for the backup battery charger + in the Wolfson Microelectronics WM831x PMICs. + +config WM831X_POWER + tristate "WM831X PMU support" + depends on MFD_WM831X + help + Say Y here to enable support for the power management unit + provided by Wolfson Microelectronics WM831x PMICs. + +config WM8350_POWER + tristate "WM8350 PMU support" + depends on MFD_WM8350 + help + Say Y here to enable support for the power management unit + provided by the Wolfson Microelectronics WM8350 PMIC. + +config TEST_POWER + tristate "Test power driver" + help + This driver is used for testing. It's safe to say M here. + +config BATTERY_88PM860X + tristate "Marvell 88PM860x battery driver" + depends on MFD_88PM860X + help + Say Y here to enable battery monitor for Marvell 88PM860x chip. + +config BATTERY_ACT8945A + tristate "Active-semi ACT8945A charger driver" + depends on MFD_ACT8945A || COMPILE_TEST + help + Say Y here to enable support for power supply provided by + Active-semi ActivePath ACT8945A charger. + +config BATTERY_DS2760 + tristate "DS2760 battery driver (HP iPAQ & others)" + depends on W1 && W1_SLAVE_DS2760 + help + Say Y here to enable support for batteries with ds2760 chip. + +config BATTERY_DS2780 + tristate "DS2780 battery driver" + depends on HAS_IOMEM + select W1 + select W1_SLAVE_DS2780 + help + Say Y here to enable support for batteries with ds2780 chip. + +config BATTERY_DS2781 + tristate "DS2781 battery driver" + depends on HAS_IOMEM + select W1 + select W1_SLAVE_DS2781 + help + If you enable this you will have the DS2781 battery driver support. + + The battery monitor chip is used in many batteries/devices + as the one who is responsible for charging/discharging/monitoring + Li+ batteries. + + If you are unsure, say N. + +config BATTERY_DS2782 + tristate "DS2782/DS2786 standalone gas-gauge" + depends on I2C + help + Say Y here to enable support for the DS2782/DS2786 standalone battery + gas-gauge. + +config BATTERY_PMU + tristate "Apple PMU battery" + depends on PPC32 && ADB_PMU + help + Say Y here to expose battery information on Apple machines + through the generic battery class. + +config BATTERY_OLPC + tristate "One Laptop Per Child battery" + depends on X86_32 && OLPC + help + Say Y to enable support for the battery on the OLPC laptop. + +config BATTERY_TOSA + tristate "Sharp SL-6000 (tosa) battery" + depends on MACH_TOSA && MFD_TC6393XB && TOUCHSCREEN_WM97XX + help + Say Y to enable support for the battery on the Sharp Zaurus + SL-6000 (tosa) models. + +config BATTERY_COLLIE + tristate "Sharp SL-5500 (collie) battery" + depends on SA1100_COLLIE && MCP_UCB1200 + help + Say Y to enable support for the battery on the Sharp Zaurus + SL-5500 (collie) models. + +config BATTERY_IPAQ_MICRO + tristate "iPAQ Atmel Micro ASIC battery driver" + depends on MFD_IPAQ_MICRO + help + Choose this option if you want to monitor battery status on + Compaq/HP iPAQ h3100 and h3600. + +config BATTERY_WM97XX + bool "WM97xx generic battery driver" + depends on TOUCHSCREEN_WM97XX=y + help + Say Y to enable support for battery measured by WM97xx aux port. + +config BATTERY_SBS + tristate "SBS Compliant gas gauge" + depends on I2C + help + Say Y to include support for SBS battery driver for SBS-compliant + gas gauges. + +config BATTERY_BQ27XXX + tristate "BQ27xxx battery driver" + help + Say Y here to enable support for batteries with BQ27xxx chips. + +config BATTERY_BQ27XXX_I2C + tristate "BQ27xxx I2C support" + depends on BATTERY_BQ27XXX + depends on I2C + default y + help + Say Y here to enable support for batteries with BQ27xxx chips + connected over an I2C bus. + +config BATTERY_DA9030 + tristate "DA9030 battery driver" + depends on PMIC_DA903X + help + Say Y here to enable support for batteries charger integrated into + DA9030 PMIC. + +config BATTERY_DA9052 + tristate "Dialog DA9052 Battery" + depends on PMIC_DA9052 + help + Say Y here to enable support for batteries charger integrated into + DA9052 PMIC. + +config CHARGER_DA9150 + tristate "Dialog Semiconductor DA9150 Charger support" + depends on MFD_DA9150 + depends on DA9150_GPADC + depends on IIO + help + Say Y here to enable support for charger unit of the DA9150 + Integrated Charger & Fuel-Gauge IC. + + This driver can also be built as a module. If so, the module will be + called da9150-charger. + +config BATTERY_DA9150 + tristate "Dialog Semiconductor DA9150 Fuel Gauge support" + depends on MFD_DA9150 + help + Say Y here to enable support for the Fuel-Gauge unit of the DA9150 + Integrated Charger & Fuel-Gauge IC + + This driver can also be built as a module. If so, the module will be + called da9150-fg. + +config AXP288_CHARGER + tristate "X-Powers AXP288 Charger" + depends on MFD_AXP20X && EXTCON_AXP288 + help + Say yes here to have support X-Power AXP288 power management IC (PMIC) + integrated charger. + +config AXP288_FUEL_GAUGE + tristate "X-Powers AXP288 Fuel Gauge" + depends on MFD_AXP20X && IIO + help + Say yes here to have support for X-Power power management IC (PMIC) + Fuel Gauge. The device provides battery statistics and status + monitoring as well as alerts for battery over/under voltage and + over/under temperature. + +config BATTERY_MAX17040 + tristate "Maxim MAX17040 Fuel Gauge" + depends on I2C + help + MAX17040 is fuel-gauge systems for lithium-ion (Li+) batteries + in handheld and portable equipment. The MAX17040 is configured + to operate with a single lithium cell + +config BATTERY_MAX17042 + tristate "Maxim MAX17042/17047/17050/8997/8966 Fuel Gauge" + depends on I2C + select REGMAP_I2C + help + MAX17042 is fuel-gauge systems for lithium-ion (Li+) batteries + in handheld and portable equipment. The MAX17042 is configured + to operate with a single lithium cell. MAX8997 and MAX8966 are + multi-function devices that include fuel gauages that are compatible + with MAX17042. This driver also supports max17047/50 chips which are + improved version of max17042. + +config BATTERY_Z2 + tristate "Z2 battery driver" + depends on I2C && MACH_ZIPIT2 + help + Say Y to include support for the battery on the Zipit Z2. + +config BATTERY_S3C_ADC + tristate "Battery driver for Samsung ADC based monitoring" + depends on S3C_ADC + help + Say Y here to enable support for iPAQ h1930/h1940/rx1950 battery + +config BATTERY_TWL4030_MADC + tristate "TWL4030 MADC battery driver" + depends on TWL4030_MADC + help + Say Y here to enable this dumb driver for batteries managed + through the TWL4030 MADC. + +config CHARGER_88PM860X + tristate "Marvell 88PM860x Charger driver" + depends on MFD_88PM860X && BATTERY_88PM860X + help + Say Y here to enable charger for Marvell 88PM860x chip. + +config CHARGER_PCF50633 + tristate "NXP PCF50633 MBC" + depends on MFD_PCF50633 + help + Say Y to include support for NXP PCF50633 Main Battery Charger. + +config BATTERY_JZ4740 + tristate "Ingenic JZ4740 battery" + depends on MACH_JZ4740 + depends on MFD_JZ4740_ADC + help + Say Y to enable support for the battery on Ingenic JZ4740 based + boards. + + 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 + help + Say Y here to enable support for battery information on Nokia + RX-51, also known as N900 tablet. + +config CHARGER_ISP1704 + tristate "ISP1704 USB Charger Detection" + depends on USB_PHY + depends on USB_GADGET || !USB_GADGET # if USB_GADGET=m, this can't be 'y' + help + Say Y to enable support for USB Charger Detection with + ISP1707/ISP1704 USB transceivers. + +config CHARGER_MAX8903 + tristate "MAX8903 Battery DC-DC Charger for USB and Adapter Power" + help + Say Y to enable support for the MAX8903 DC-DC charger and sysfs. + The driver supports controlling charger-enable and current-limit + pins based on the status of charger connections with interrupt + handlers. + +config CHARGER_TWL4030 + tristate "OMAP TWL4030 BCI charger driver" + depends on IIO && TWL4030_CORE + help + Say Y here to enable support for TWL4030 Battery Charge Interface. + +config CHARGER_LP8727 + tristate "TI/National Semiconductor LP8727 charger driver" + depends on I2C + help + Say Y here to enable support for LP8727 Charger Driver. + +config CHARGER_LP8788 + tristate "TI LP8788 charger driver" + depends on MFD_LP8788 + depends on LP8788_ADC + depends on IIO + help + Say Y to enable support for the LP8788 linear charger. + +config CHARGER_GPIO + tristate "GPIO charger" + depends on GPIOLIB || COMPILE_TEST + help + Say Y to include support for chargers which report their online status + through a GPIO pin. + + This driver can be build as a module. If so, the module will be + called gpio-charger. + +config CHARGER_MANAGER + bool "Battery charger manager for multiple chargers" + depends on REGULATOR + select EXTCON + help + Say Y to enable charger-manager support, which allows multiple + chargers attached to a battery and multiple batteries attached to a + system. The charger-manager also can monitor charging status in + runtime and in suspend-to-RAM by waking up the system periodically + with help of suspend_again support. + +config CHARGER_MAX14577 + tristate "Maxim MAX14577/77836 battery charger driver" + depends on MFD_MAX14577 + help + Say Y to enable support for the battery charger control sysfs and + platform data of MAX14577/77836 MUICs. + +config CHARGER_MAX77693 + tristate "Maxim MAX77693 battery charger driver" + depends on MFD_MAX77693 + help + Say Y to enable support for the Maxim MAX77693 battery charger. + +config CHARGER_MAX8997 + tristate "Maxim MAX8997/MAX8966 PMIC battery charger driver" + depends on MFD_MAX8997 && REGULATOR_MAX8997 + help + Say Y to enable support for the battery charger control sysfs and + platform data of MAX8997/LP3974 PMICs. + +config CHARGER_MAX8998 + tristate "Maxim MAX8998/LP3974 PMIC battery charger driver" + depends on MFD_MAX8998 && REGULATOR_MAX8998 + help + 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 + depends on EXTCON + 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 + help + Say Y to enable support for the TI BQ2415x battery charger + PMICs. + + You'll need this driver to charge batteries on e.g. Nokia + RX-51/N900. + +config CHARGER_BQ24190 + tristate "TI BQ24190 battery charger driver" + depends on I2C + depends on GPIOLIB || COMPILE_TEST + help + Say Y to enable support for the TI BQ24190 battery charger. + +config CHARGER_BQ24257 + 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 BQ24250, BQ24251, and BQ24257 battery + chargers. + +config CHARGER_BQ24735 + tristate "TI BQ24735 battery charger support" + depends on I2C + depends on GPIOLIB || COMPILE_TEST + help + Say Y to enable support for the TI BQ24735 battery charger. + +config CHARGER_BQ25890 + tristate "TI BQ25890 battery charger driver" + depends on I2C + depends on GPIOLIB || COMPILE_TEST + select REGMAP_I2C + help + Say Y to enable support for the TI BQ25890 battery charger. + +config CHARGER_SMB347 + tristate "Summit Microelectronics SMB347 Battery Charger" + depends on I2C + select REGMAP_I2C + help + Say Y to include support for Summit Microelectronics SMB347 + Battery Charger. + +config CHARGER_TPS65090 + tristate "TPS65090 battery charger driver" + depends on MFD_TPS65090 + help + 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 + help + Say Y here to include support for LTC2941 and LTC2943 Battery + Gauge IC. The driver reports the charge count continuously, and + measures the voltage and temperature every 10 seconds. + +config AB8500_BM + bool "AB8500 Battery Management Driver" + depends on AB8500_CORE && AB8500_GPADC + help + Say Y to include support for AB8500 battery management. + +config BATTERY_GOLDFISH + tristate "Goldfish battery driver" + depends on GOLDFISH || COMPILE_TEST + depends on HAS_IOMEM + help + Say Y to enable support for the battery and AC power in the + Goldfish emulator. + +config BATTERY_RT5033 + tristate "RT5033 fuel gauge support" + depends on MFD_RT5033 + help + This adds support for battery fuel gauge in Richtek RT5033 PMIC. + The fuelgauge calculates and determines the battery state of charge + according to battery open circuit voltage. + +config CHARGER_RT9455 + tristate "Richtek RT9455 battery charger driver" + depends on I2C + depends on GPIOLIB || COMPILE_TEST + select REGMAP_I2C + 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. + +endif # POWER_SUPPLY diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile new file mode 100644 index 000000000000..36c599d9a495 --- /dev/null +++ b/drivers/power/supply/Makefile @@ -0,0 +1,74 @@ +subdir-ccflags-$(CONFIG_POWER_SUPPLY_DEBUG) := -DDEBUG + +power_supply-y := power_supply_core.o +power_supply-$(CONFIG_SYSFS) += power_supply_sysfs.o +power_supply-$(CONFIG_LEDS_TRIGGERS) += power_supply_leds.o + +obj-$(CONFIG_POWER_SUPPLY) += power_supply.o +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 +obj-$(CONFIG_WM8350_POWER) += wm8350_power.o +obj-$(CONFIG_TEST_POWER) += test_power.o + +obj-$(CONFIG_BATTERY_88PM860X) += 88pm860x_battery.o +obj-$(CONFIG_BATTERY_ACT8945A) += act8945a_charger.o +obj-$(CONFIG_BATTERY_DS2760) += ds2760_battery.o +obj-$(CONFIG_BATTERY_DS2780) += ds2780_battery.o +obj-$(CONFIG_BATTERY_DS2781) += ds2781_battery.o +obj-$(CONFIG_BATTERY_DS2782) += ds2782_battery.o +obj-$(CONFIG_BATTERY_GAUGE_LTC2941) += ltc2941-battery-gauge.o +obj-$(CONFIG_BATTERY_GOLDFISH) += goldfish_battery.o +obj-$(CONFIG_BATTERY_PMU) += pmu_battery.o +obj-$(CONFIG_BATTERY_OLPC) += olpc_battery.o +obj-$(CONFIG_BATTERY_TOSA) += tosa_battery.o +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_BQ27XXX) += bq27xxx_battery.o +obj-$(CONFIG_BATTERY_BQ27XXX_I2C) += bq27xxx_battery_i2c.o +obj-$(CONFIG_BATTERY_DA9030) += da9030_battery.o +obj-$(CONFIG_BATTERY_DA9052) += da9052-battery.o +obj-$(CONFIG_CHARGER_DA9150) += da9150-charger.o +obj-$(CONFIG_BATTERY_DA9150) += da9150-fg.o +obj-$(CONFIG_BATTERY_MAX17040) += max17040_battery.o +obj-$(CONFIG_BATTERY_MAX17042) += max17042_battery.o +obj-$(CONFIG_BATTERY_Z2) += z2_battery.o +obj-$(CONFIG_BATTERY_RT5033) += rt5033_battery.o +obj-$(CONFIG_CHARGER_RT9455) += rt9455_charger.o +obj-$(CONFIG_BATTERY_S3C_ADC) += s3c_adc_battery.o +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 +obj-$(CONFIG_CHARGER_MAX8903) += max8903_charger.o +obj-$(CONFIG_CHARGER_TWL4030) += twl4030_charger.o +obj-$(CONFIG_CHARGER_LP8727) += lp8727_charger.o +obj-$(CONFIG_CHARGER_LP8788) += lp8788-charger.o +obj-$(CONFIG_CHARGER_GPIO) += gpio-charger.o +obj-$(CONFIG_CHARGER_MANAGER) += charger-manager.o +obj-$(CONFIG_CHARGER_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 +obj-$(CONFIG_CHARGER_BQ24735) += bq24735-charger.o +obj-$(CONFIG_CHARGER_BQ25890) += bq25890_charger.o +obj-$(CONFIG_CHARGER_SMB347) += smb347-charger.o +obj-$(CONFIG_CHARGER_TPS65090) += tps65090-charger.o +obj-$(CONFIG_CHARGER_TPS65217) += tps65217_charger.o +obj-$(CONFIG_AXP288_FUEL_GAUGE) += axp288_fuel_gauge.o +obj-$(CONFIG_AXP288_CHARGER) += axp288_charger.o diff --git a/drivers/power/supply/ab8500_bmdata.c b/drivers/power/supply/ab8500_bmdata.c new file mode 100644 index 000000000000..d29864533093 --- /dev/null +++ b/drivers/power/supply/ab8500_bmdata.c @@ -0,0 +1,605 @@ +#include +#include +#include +#include +#include +#include + +/* + * These are the defined batteries that uses a NTC and ID resistor placed + * inside of the battery pack. + * Note that the res_to_temp table must be strictly sorted by falling resistance + * values to work. + */ +const struct abx500_res_to_temp ab8500_temp_tbl_a_thermistor[] = { + {-5, 53407}, + { 0, 48594}, + { 5, 43804}, + {10, 39188}, + {15, 34870}, + {20, 30933}, + {25, 27422}, + {30, 24347}, + {35, 21694}, + {40, 19431}, + {45, 17517}, + {50, 15908}, + {55, 14561}, + {60, 13437}, + {65, 12500}, +}; +EXPORT_SYMBOL(ab8500_temp_tbl_a_thermistor); + +const int ab8500_temp_tbl_a_size = ARRAY_SIZE(ab8500_temp_tbl_a_thermistor); +EXPORT_SYMBOL(ab8500_temp_tbl_a_size); + +const struct abx500_res_to_temp ab8500_temp_tbl_b_thermistor[] = { + {-5, 200000}, + { 0, 159024}, + { 5, 151921}, + {10, 144300}, + {15, 136424}, + {20, 128565}, + {25, 120978}, + {30, 113875}, + {35, 107397}, + {40, 101629}, + {45, 96592}, + {50, 92253}, + {55, 88569}, + {60, 85461}, + {65, 82869}, +}; +EXPORT_SYMBOL(ab8500_temp_tbl_b_thermistor); + +const int ab8500_temp_tbl_b_size = ARRAY_SIZE(ab8500_temp_tbl_b_thermistor); +EXPORT_SYMBOL(ab8500_temp_tbl_b_size); + +static const struct abx500_v_to_cap cap_tbl_a_thermistor[] = { + {4171, 100}, + {4114, 95}, + {4009, 83}, + {3947, 74}, + {3907, 67}, + {3863, 59}, + {3830, 56}, + {3813, 53}, + {3791, 46}, + {3771, 33}, + {3754, 25}, + {3735, 20}, + {3717, 17}, + {3681, 13}, + {3664, 8}, + {3651, 6}, + {3635, 5}, + {3560, 3}, + {3408, 1}, + {3247, 0}, +}; + +static const struct abx500_v_to_cap cap_tbl_b_thermistor[] = { + {4161, 100}, + {4124, 98}, + {4044, 90}, + {4003, 85}, + {3966, 80}, + {3933, 75}, + {3888, 67}, + {3849, 60}, + {3813, 55}, + {3787, 47}, + {3772, 30}, + {3751, 25}, + {3718, 20}, + {3681, 16}, + {3660, 14}, + {3589, 10}, + {3546, 7}, + {3495, 4}, + {3404, 2}, + {3250, 0}, +}; + +static const struct abx500_v_to_cap cap_tbl[] = { + {4186, 100}, + {4163, 99}, + {4114, 95}, + {4068, 90}, + {3990, 80}, + {3926, 70}, + {3898, 65}, + {3866, 60}, + {3833, 55}, + {3812, 50}, + {3787, 40}, + {3768, 30}, + {3747, 25}, + {3730, 20}, + {3705, 15}, + {3699, 14}, + {3684, 12}, + {3672, 9}, + {3657, 7}, + {3638, 6}, + {3556, 4}, + {3424, 2}, + {3317, 1}, + {3094, 0}, +}; + +/* + * Note that the res_to_temp table must be strictly sorted by falling + * resistance values to work. + */ +static const struct abx500_res_to_temp temp_tbl[] = { + {-5, 214834}, + { 0, 162943}, + { 5, 124820}, + {10, 96520}, + {15, 75306}, + {20, 59254}, + {25, 47000}, + {30, 37566}, + {35, 30245}, + {40, 24520}, + {45, 20010}, + {50, 16432}, + {55, 13576}, + {60, 11280}, + {65, 9425}, +}; + +/* + * Note that the batres_vs_temp table must be strictly sorted by falling + * temperature values to work. + */ +static const struct batres_vs_temp temp_to_batres_tbl_thermistor[] = { + { 40, 120}, + { 30, 135}, + { 20, 165}, + { 10, 230}, + { 00, 325}, + {-10, 445}, + {-20, 595}, +}; + +/* + * Note that the batres_vs_temp table must be strictly sorted by falling + * temperature values to work. + */ +static const struct batres_vs_temp temp_to_batres_tbl_ext_thermistor[] = { + { 60, 300}, + { 30, 300}, + { 20, 300}, + { 10, 300}, + { 00, 300}, + {-10, 300}, + {-20, 300}, +}; + +/* battery resistance table for LI ION 9100 battery */ +static const struct batres_vs_temp temp_to_batres_tbl_9100[] = { + { 60, 180}, + { 30, 180}, + { 20, 180}, + { 10, 180}, + { 00, 180}, + {-10, 180}, + {-20, 180}, +}; + +static struct abx500_battery_type bat_type_thermistor[] = { + [BATTERY_UNKNOWN] = { + /* First element always represent the UNKNOWN battery */ + .name = POWER_SUPPLY_TECHNOLOGY_UNKNOWN, + .resis_high = 0, + .resis_low = 0, + .battery_resistance = 300, + .charge_full_design = 612, + .nominal_voltage = 3700, + .termination_vol = 4050, + .termination_curr = 200, + .recharge_cap = 95, + .normal_cur_lvl = 400, + .normal_vol_lvl = 4100, + .maint_a_cur_lvl = 400, + .maint_a_vol_lvl = 4050, + .maint_a_chg_timer_h = 60, + .maint_b_cur_lvl = 400, + .maint_b_vol_lvl = 4000, + .maint_b_chg_timer_h = 200, + .low_high_cur_lvl = 300, + .low_high_vol_lvl = 4000, + .n_temp_tbl_elements = ARRAY_SIZE(temp_tbl), + .r_to_t_tbl = temp_tbl, + .n_v_cap_tbl_elements = ARRAY_SIZE(cap_tbl), + .v_to_cap_tbl = cap_tbl, + .n_batres_tbl_elements = ARRAY_SIZE(temp_to_batres_tbl_thermistor), + .batres_tbl = temp_to_batres_tbl_thermistor, + }, + { + .name = POWER_SUPPLY_TECHNOLOGY_LIPO, + .resis_high = 53407, + .resis_low = 12500, + .battery_resistance = 300, + .charge_full_design = 900, + .nominal_voltage = 3600, + .termination_vol = 4150, + .termination_curr = 80, + .recharge_cap = 95, + .normal_cur_lvl = 700, + .normal_vol_lvl = 4200, + .maint_a_cur_lvl = 600, + .maint_a_vol_lvl = 4150, + .maint_a_chg_timer_h = 60, + .maint_b_cur_lvl = 600, + .maint_b_vol_lvl = 4100, + .maint_b_chg_timer_h = 200, + .low_high_cur_lvl = 300, + .low_high_vol_lvl = 4000, + .n_temp_tbl_elements = ARRAY_SIZE(ab8500_temp_tbl_a_thermistor), + .r_to_t_tbl = ab8500_temp_tbl_a_thermistor, + .n_v_cap_tbl_elements = ARRAY_SIZE(cap_tbl_a_thermistor), + .v_to_cap_tbl = cap_tbl_a_thermistor, + .n_batres_tbl_elements = ARRAY_SIZE(temp_to_batres_tbl_thermistor), + .batres_tbl = temp_to_batres_tbl_thermistor, + + }, + { + .name = POWER_SUPPLY_TECHNOLOGY_LIPO, + .resis_high = 200000, + .resis_low = 82869, + .battery_resistance = 300, + .charge_full_design = 900, + .nominal_voltage = 3600, + .termination_vol = 4150, + .termination_curr = 80, + .recharge_cap = 95, + .normal_cur_lvl = 700, + .normal_vol_lvl = 4200, + .maint_a_cur_lvl = 600, + .maint_a_vol_lvl = 4150, + .maint_a_chg_timer_h = 60, + .maint_b_cur_lvl = 600, + .maint_b_vol_lvl = 4100, + .maint_b_chg_timer_h = 200, + .low_high_cur_lvl = 300, + .low_high_vol_lvl = 4000, + .n_temp_tbl_elements = ARRAY_SIZE(ab8500_temp_tbl_b_thermistor), + .r_to_t_tbl = ab8500_temp_tbl_b_thermistor, + .n_v_cap_tbl_elements = ARRAY_SIZE(cap_tbl_b_thermistor), + .v_to_cap_tbl = cap_tbl_b_thermistor, + .n_batres_tbl_elements = ARRAY_SIZE(temp_to_batres_tbl_thermistor), + .batres_tbl = temp_to_batres_tbl_thermistor, + }, +}; + +static struct abx500_battery_type bat_type_ext_thermistor[] = { + [BATTERY_UNKNOWN] = { + /* First element always represent the UNKNOWN battery */ + .name = POWER_SUPPLY_TECHNOLOGY_UNKNOWN, + .resis_high = 0, + .resis_low = 0, + .battery_resistance = 300, + .charge_full_design = 612, + .nominal_voltage = 3700, + .termination_vol = 4050, + .termination_curr = 200, + .recharge_cap = 95, + .normal_cur_lvl = 400, + .normal_vol_lvl = 4100, + .maint_a_cur_lvl = 400, + .maint_a_vol_lvl = 4050, + .maint_a_chg_timer_h = 60, + .maint_b_cur_lvl = 400, + .maint_b_vol_lvl = 4000, + .maint_b_chg_timer_h = 200, + .low_high_cur_lvl = 300, + .low_high_vol_lvl = 4000, + .n_temp_tbl_elements = ARRAY_SIZE(temp_tbl), + .r_to_t_tbl = temp_tbl, + .n_v_cap_tbl_elements = ARRAY_SIZE(cap_tbl), + .v_to_cap_tbl = cap_tbl, + .n_batres_tbl_elements = ARRAY_SIZE(temp_to_batres_tbl_thermistor), + .batres_tbl = temp_to_batres_tbl_thermistor, + }, +/* + * These are the batteries that doesn't have an internal NTC resistor to measure + * its temperature. The temperature in this case is measure with a NTC placed + * near the battery but on the PCB. + */ + { + .name = POWER_SUPPLY_TECHNOLOGY_LIPO, + .resis_high = 76000, + .resis_low = 53000, + .battery_resistance = 300, + .charge_full_design = 900, + .nominal_voltage = 3700, + .termination_vol = 4150, + .termination_curr = 100, + .recharge_cap = 95, + .normal_cur_lvl = 700, + .normal_vol_lvl = 4200, + .maint_a_cur_lvl = 600, + .maint_a_vol_lvl = 4150, + .maint_a_chg_timer_h = 60, + .maint_b_cur_lvl = 600, + .maint_b_vol_lvl = 4100, + .maint_b_chg_timer_h = 200, + .low_high_cur_lvl = 300, + .low_high_vol_lvl = 4000, + .n_temp_tbl_elements = ARRAY_SIZE(temp_tbl), + .r_to_t_tbl = temp_tbl, + .n_v_cap_tbl_elements = ARRAY_SIZE(cap_tbl), + .v_to_cap_tbl = cap_tbl, + .n_batres_tbl_elements = ARRAY_SIZE(temp_to_batres_tbl_thermistor), + .batres_tbl = temp_to_batres_tbl_thermistor, + }, + { + .name = POWER_SUPPLY_TECHNOLOGY_LION, + .resis_high = 30000, + .resis_low = 10000, + .battery_resistance = 300, + .charge_full_design = 950, + .nominal_voltage = 3700, + .termination_vol = 4150, + .termination_curr = 100, + .recharge_cap = 95, + .normal_cur_lvl = 700, + .normal_vol_lvl = 4200, + .maint_a_cur_lvl = 600, + .maint_a_vol_lvl = 4150, + .maint_a_chg_timer_h = 60, + .maint_b_cur_lvl = 600, + .maint_b_vol_lvl = 4100, + .maint_b_chg_timer_h = 200, + .low_high_cur_lvl = 300, + .low_high_vol_lvl = 4000, + .n_temp_tbl_elements = ARRAY_SIZE(temp_tbl), + .r_to_t_tbl = temp_tbl, + .n_v_cap_tbl_elements = ARRAY_SIZE(cap_tbl), + .v_to_cap_tbl = cap_tbl, + .n_batres_tbl_elements = ARRAY_SIZE(temp_to_batres_tbl_thermistor), + .batres_tbl = temp_to_batres_tbl_thermistor, + }, + { + .name = POWER_SUPPLY_TECHNOLOGY_LION, + .resis_high = 95000, + .resis_low = 76001, + .battery_resistance = 300, + .charge_full_design = 950, + .nominal_voltage = 3700, + .termination_vol = 4150, + .termination_curr = 100, + .recharge_cap = 95, + .normal_cur_lvl = 700, + .normal_vol_lvl = 4200, + .maint_a_cur_lvl = 600, + .maint_a_vol_lvl = 4150, + .maint_a_chg_timer_h = 60, + .maint_b_cur_lvl = 600, + .maint_b_vol_lvl = 4100, + .maint_b_chg_timer_h = 200, + .low_high_cur_lvl = 300, + .low_high_vol_lvl = 4000, + .n_temp_tbl_elements = ARRAY_SIZE(temp_tbl), + .r_to_t_tbl = temp_tbl, + .n_v_cap_tbl_elements = ARRAY_SIZE(cap_tbl), + .v_to_cap_tbl = cap_tbl, + .n_batres_tbl_elements = ARRAY_SIZE(temp_to_batres_tbl_thermistor), + .batres_tbl = temp_to_batres_tbl_thermistor, + }, +}; + +static const struct abx500_bm_capacity_levels cap_levels = { + .critical = 2, + .low = 10, + .normal = 70, + .high = 95, + .full = 100, +}; + +static const struct abx500_fg_parameters fg = { + .recovery_sleep_timer = 10, + .recovery_total_time = 100, + .init_timer = 1, + .init_discard_time = 5, + .init_total_time = 40, + .high_curr_time = 60, + .accu_charging = 30, + .accu_high_curr = 30, + .high_curr_threshold = 50, + .lowbat_threshold = 3100, + .battok_falling_th_sel0 = 2860, + .battok_raising_th_sel1 = 2860, + .maint_thres = 95, + .user_cap_limit = 15, + .pcut_enable = 1, + .pcut_max_time = 127, + .pcut_flag_time = 112, + .pcut_max_restart = 15, + .pcut_debounce_time = 2, +}; + +static const struct abx500_maxim_parameters ab8500_maxi_params = { + .ena_maxi = true, + .chg_curr = 910, + .wait_cycles = 10, + .charger_curr_step = 100, +}; + +static const struct abx500_maxim_parameters abx540_maxi_params = { + .ena_maxi = true, + .chg_curr = 3000, + .wait_cycles = 10, + .charger_curr_step = 200, +}; + +static const struct abx500_bm_charger_parameters chg = { + .usb_volt_max = 5500, + .usb_curr_max = 1500, + .ac_volt_max = 7500, + .ac_curr_max = 1500, +}; + +/* + * This array maps the raw hex value to charger output current used by the + * AB8500 values + */ +static int ab8500_charge_output_curr_map[] = { + 100, 200, 300, 400, 500, 600, 700, 800, + 900, 1000, 1100, 1200, 1300, 1400, 1500, 1500, +}; + +static int ab8540_charge_output_curr_map[] = { + 0, 0, 0, 75, 100, 125, 150, 175, + 200, 225, 250, 275, 300, 325, 350, 375, + 400, 425, 450, 475, 500, 525, 550, 575, + 600, 625, 650, 675, 700, 725, 750, 775, + 800, 825, 850, 875, 900, 925, 950, 975, + 1000, 1025, 1050, 1075, 1100, 1125, 1150, 1175, + 1200, 1225, 1250, 1275, 1300, 1325, 1350, 1375, + 1400, 1425, 1450, 1500, 1600, 1700, 1900, 2000, +}; + +/* + * This array maps the raw hex value to charger input current used by the + * AB8500 values + */ +static int ab8500_charge_input_curr_map[] = { + 50, 98, 193, 290, 380, 450, 500, 600, + 700, 800, 900, 1000, 1100, 1300, 1400, 1500, +}; + +static int ab8540_charge_input_curr_map[] = { + 25, 50, 75, 100, 125, 150, 175, 200, + 225, 250, 275, 300, 325, 350, 375, 400, + 425, 450, 475, 500, 525, 550, 575, 600, + 625, 650, 675, 700, 725, 750, 775, 800, + 825, 850, 875, 900, 925, 950, 975, 1000, + 1025, 1050, 1075, 1100, 1125, 1150, 1175, 1200, + 1225, 1250, 1275, 1300, 1325, 1350, 1375, 1400, + 1425, 1450, 1475, 1500, 1500, 1500, 1500, 1500, +}; + +struct abx500_bm_data ab8500_bm_data = { + .temp_under = 3, + .temp_low = 8, + .temp_high = 43, + .temp_over = 48, + .main_safety_tmr_h = 4, + .temp_interval_chg = 20, + .temp_interval_nochg = 120, + .usb_safety_tmr_h = 4, + .bkup_bat_v = BUP_VCH_SEL_2P6V, + .bkup_bat_i = BUP_ICH_SEL_150UA, + .no_maintenance = false, + .capacity_scaling = false, + .adc_therm = ABx500_ADC_THERM_BATCTRL, + .chg_unknown_bat = false, + .enable_overshoot = false, + .fg_res = 100, + .cap_levels = &cap_levels, + .bat_type = bat_type_thermistor, + .n_btypes = ARRAY_SIZE(bat_type_thermistor), + .batt_id = 0, + .interval_charging = 5, + .interval_not_charging = 120, + .temp_hysteresis = 3, + .gnd_lift_resistance = 34, + .chg_output_curr = ab8500_charge_output_curr_map, + .n_chg_out_curr = ARRAY_SIZE(ab8500_charge_output_curr_map), + .maxi = &ab8500_maxi_params, + .chg_params = &chg, + .fg_params = &fg, + .chg_input_curr = ab8500_charge_input_curr_map, + .n_chg_in_curr = ARRAY_SIZE(ab8500_charge_input_curr_map), +}; + +struct abx500_bm_data ab8540_bm_data = { + .temp_under = 3, + .temp_low = 8, + .temp_high = 43, + .temp_over = 48, + .main_safety_tmr_h = 4, + .temp_interval_chg = 20, + .temp_interval_nochg = 120, + .usb_safety_tmr_h = 4, + .bkup_bat_v = BUP_VCH_SEL_2P6V, + .bkup_bat_i = BUP_ICH_SEL_150UA, + .no_maintenance = false, + .capacity_scaling = false, + .adc_therm = ABx500_ADC_THERM_BATCTRL, + .chg_unknown_bat = false, + .enable_overshoot = false, + .fg_res = 100, + .cap_levels = &cap_levels, + .bat_type = bat_type_thermistor, + .n_btypes = ARRAY_SIZE(bat_type_thermistor), + .batt_id = 0, + .interval_charging = 5, + .interval_not_charging = 120, + .temp_hysteresis = 3, + .gnd_lift_resistance = 0, + .maxi = &abx540_maxi_params, + .chg_params = &chg, + .fg_params = &fg, + .chg_output_curr = ab8540_charge_output_curr_map, + .n_chg_out_curr = ARRAY_SIZE(ab8540_charge_output_curr_map), + .chg_input_curr = ab8540_charge_input_curr_map, + .n_chg_in_curr = ARRAY_SIZE(ab8540_charge_input_curr_map), +}; + +int ab8500_bm_of_probe(struct device *dev, + struct device_node *np, + struct abx500_bm_data *bm) +{ + const struct batres_vs_temp *tmp_batres_tbl; + struct device_node *battery_node; + const char *btech; + int i; + + /* get phandle to 'battery-info' node */ + battery_node = of_parse_phandle(np, "battery", 0); + if (!battery_node) { + dev_err(dev, "battery node or reference missing\n"); + return -EINVAL; + } + + btech = of_get_property(battery_node, "stericsson,battery-type", NULL); + if (!btech) { + dev_warn(dev, "missing property battery-name/type\n"); + return -EINVAL; + } + + if (strncmp(btech, "LION", 4) == 0) { + bm->no_maintenance = true; + bm->chg_unknown_bat = true; + bm->bat_type[BATTERY_UNKNOWN].charge_full_design = 2600; + bm->bat_type[BATTERY_UNKNOWN].termination_vol = 4150; + bm->bat_type[BATTERY_UNKNOWN].recharge_cap = 95; + bm->bat_type[BATTERY_UNKNOWN].normal_cur_lvl = 520; + bm->bat_type[BATTERY_UNKNOWN].normal_vol_lvl = 4200; + } + + if (of_property_read_bool(battery_node, "thermistor-on-batctrl")) { + if (strncmp(btech, "LION", 4) == 0) + tmp_batres_tbl = temp_to_batres_tbl_9100; + else + tmp_batres_tbl = temp_to_batres_tbl_thermistor; + } else { + bm->n_btypes = 4; + bm->bat_type = bat_type_ext_thermistor; + bm->adc_therm = ABx500_ADC_THERM_BATTEMP; + tmp_batres_tbl = temp_to_batres_tbl_ext_thermistor; + } + + /* select the battery resolution table */ + for (i = 0; i < bm->n_btypes; ++i) + bm->bat_type[i].batres_tbl = tmp_batres_tbl; + + of_node_put(battery_node); + + return 0; +} diff --git a/drivers/power/supply/ab8500_btemp.c b/drivers/power/supply/ab8500_btemp.c new file mode 100644 index 000000000000..bf2e5dd301e7 --- /dev/null +++ b/drivers/power/supply/ab8500_btemp.c @@ -0,0 +1,1212 @@ +/* + * Copyright (C) ST-Ericsson SA 2012 + * + * Battery temperature driver for AB8500 + * + * License Terms: GNU General Public License v2 + * Author: + * Johan Palsson + * Karl Komierowski + * Arun R Murthy + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define VTVOUT_V 1800 + +#define BTEMP_THERMAL_LOW_LIMIT -10 +#define BTEMP_THERMAL_MED_LIMIT 0 +#define BTEMP_THERMAL_HIGH_LIMIT_52 52 +#define BTEMP_THERMAL_HIGH_LIMIT_57 57 +#define BTEMP_THERMAL_HIGH_LIMIT_62 62 + +#define BTEMP_BATCTRL_CURR_SRC_7UA 7 +#define BTEMP_BATCTRL_CURR_SRC_20UA 20 + +#define BTEMP_BATCTRL_CURR_SRC_16UA 16 +#define BTEMP_BATCTRL_CURR_SRC_18UA 18 + +#define BTEMP_BATCTRL_CURR_SRC_60UA 60 +#define BTEMP_BATCTRL_CURR_SRC_120UA 120 + +/** + * struct ab8500_btemp_interrupts - ab8500 interrupts + * @name: name of the interrupt + * @isr function pointer to the isr + */ +struct ab8500_btemp_interrupts { + char *name; + irqreturn_t (*isr)(int irq, void *data); +}; + +struct ab8500_btemp_events { + bool batt_rem; + bool btemp_high; + bool btemp_medhigh; + bool btemp_lowmed; + bool btemp_low; + bool ac_conn; + bool usb_conn; +}; + +struct ab8500_btemp_ranges { + int btemp_high_limit; + int btemp_med_limit; + int btemp_low_limit; +}; + +/** + * struct ab8500_btemp - ab8500 BTEMP device information + * @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 + * @parent: Pointer to the struct ab8500 + * @gpadc: Pointer to the struct gpadc + * @fg: Pointer to the struct fg + * @bm: Platform specific battery management information + * @btemp_psy: Structure for BTEMP specific battery properties + * @events: Structure for information about events triggered + * @btemp_ranges: Battery temperature range structure + * @btemp_wq: Work queue for measuring the temperature periodically + * @btemp_periodic_work: Work for measuring the temperature periodically + * @initialized: True if battery id read. + */ +struct ab8500_btemp { + struct device *dev; + struct list_head node; + int curr_source; + int bat_temp; + int prev_bat_temp; + struct ab8500 *parent; + struct ab8500_gpadc *gpadc; + struct ab8500_fg *fg; + struct abx500_bm_data *bm; + struct power_supply *btemp_psy; + struct ab8500_btemp_events events; + struct ab8500_btemp_ranges btemp_ranges; + struct workqueue_struct *btemp_wq; + struct delayed_work btemp_periodic_work; + bool initialized; +}; + +/* BTEMP power supply properties */ +static enum power_supply_property ab8500_btemp_props[] = { + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_TEMP, +}; + +static LIST_HEAD(ab8500_btemp_list); + +/** + * ab8500_btemp_get() - returns a reference to the primary AB8500 BTEMP + * (i.e. the first BTEMP in the instance 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; +} +EXPORT_SYMBOL(ab8500_btemp_get); + +/** + * ab8500_btemp_batctrl_volt_to_res() - convert batctrl voltage to resistance + * @di: pointer to the ab8500_btemp structure + * @v_batctrl: measured batctrl voltage + * @inst_curr: measured instant current + * + * This function returns the battery resistance that is + * derived from the BATCTRL voltage. + * Returns value in Ohms. + */ +static int ab8500_btemp_batctrl_volt_to_res(struct ab8500_btemp *di, + int v_batctrl, int inst_curr) +{ + int rbs; + + if (is_ab8500_1p1_or_earlier(di->parent)) { + /* + * For ABB cut1.0 and 1.1 BAT_CTRL is internally + * connected to 1.8V through a 450k resistor + */ + return (450000 * (v_batctrl)) / (1800 - v_batctrl); + } + + if (di->bm->adc_therm == ABx500_ADC_THERM_BATCTRL) { + /* + * If the battery has internal NTC, we use the current + * source to calculate the resistance. + */ + rbs = (v_batctrl * 1000 + - di->bm->gnd_lift_resistance * inst_curr) + / di->curr_source; + } else { + /* + * BAT_CTRL is internally + * connected to 1.8V through a 80k resistor + */ + rbs = (80000 * (v_batctrl)) / (1800 - v_batctrl); + } + + return rbs; +} + +/** + * ab8500_btemp_read_batctrl_voltage() - measure batctrl voltage + * @di: pointer to the ab8500_btemp structure + * + * This function returns the voltage on BATCTRL. Returns value in mV. + */ +static int ab8500_btemp_read_batctrl_voltage(struct ab8500_btemp *di) +{ + int vbtemp; + static int prev; + + vbtemp = ab8500_gpadc_convert(di->gpadc, BAT_CTRL); + if (vbtemp < 0) { + dev_err(di->dev, + "%s gpadc conversion failed, using previous value", + __func__); + return prev; + } + prev = vbtemp; + return vbtemp; +} + +/** + * ab8500_btemp_curr_source_enable() - enable/disable batctrl current source + * @di: pointer to the ab8500_btemp structure + * @enable: enable or disable the current source + * + * Enable or disable the current sources for the BatCtrl AD channel + */ +static int ab8500_btemp_curr_source_enable(struct ab8500_btemp *di, + bool enable) +{ + int curr; + int ret = 0; + + /* + * BATCTRL current sources are included on AB8500 cut2.0 + * and future versions + */ + if (is_ab8500_1p1_or_earlier(di->parent)) + return 0; + + /* Only do this for batteries with internal NTC */ + if (di->bm->adc_therm == ABx500_ADC_THERM_BATCTRL && enable) { + + if (is_ab8540(di->parent)) { + if (di->curr_source == BTEMP_BATCTRL_CURR_SRC_60UA) + curr = BAT_CTRL_60U_ENA; + else + curr = BAT_CTRL_120U_ENA; + } else if (is_ab9540(di->parent) || is_ab8505(di->parent)) { + if (di->curr_source == BTEMP_BATCTRL_CURR_SRC_16UA) + curr = BAT_CTRL_16U_ENA; + else + curr = BAT_CTRL_18U_ENA; + } else { + if (di->curr_source == BTEMP_BATCTRL_CURR_SRC_7UA) + curr = BAT_CTRL_7U_ENA; + else + curr = BAT_CTRL_20U_ENA; + } + + dev_dbg(di->dev, "Set BATCTRL %duA\n", di->curr_source); + + ret = abx500_mask_and_set_register_interruptible(di->dev, + AB8500_CHARGER, AB8500_BAT_CTRL_CURRENT_SOURCE, + FORCE_BAT_CTRL_CMP_HIGH, FORCE_BAT_CTRL_CMP_HIGH); + if (ret) { + dev_err(di->dev, "%s failed setting cmp_force\n", + __func__); + return ret; + } + + /* + * We have to wait one 32kHz cycle before enabling + * the current source, since ForceBatCtrlCmpHigh needs + * to be written in a separate cycle + */ + udelay(32); + + ret = abx500_set_register_interruptible(di->dev, + AB8500_CHARGER, AB8500_BAT_CTRL_CURRENT_SOURCE, + FORCE_BAT_CTRL_CMP_HIGH | curr); + if (ret) { + dev_err(di->dev, "%s failed enabling current source\n", + __func__); + goto disable_curr_source; + } + } else if (di->bm->adc_therm == ABx500_ADC_THERM_BATCTRL && !enable) { + dev_dbg(di->dev, "Disable BATCTRL curr source\n"); + + if (is_ab8540(di->parent)) { + /* Write 0 to the curr bits */ + ret = abx500_mask_and_set_register_interruptible( + di->dev, + AB8500_CHARGER, AB8500_BAT_CTRL_CURRENT_SOURCE, + BAT_CTRL_60U_ENA | BAT_CTRL_120U_ENA, + ~(BAT_CTRL_60U_ENA | BAT_CTRL_120U_ENA)); + } else if (is_ab9540(di->parent) || is_ab8505(di->parent)) { + /* Write 0 to the curr bits */ + ret = abx500_mask_and_set_register_interruptible( + di->dev, + AB8500_CHARGER, AB8500_BAT_CTRL_CURRENT_SOURCE, + BAT_CTRL_16U_ENA | BAT_CTRL_18U_ENA, + ~(BAT_CTRL_16U_ENA | BAT_CTRL_18U_ENA)); + } else { + /* Write 0 to the curr bits */ + ret = abx500_mask_and_set_register_interruptible( + di->dev, + AB8500_CHARGER, AB8500_BAT_CTRL_CURRENT_SOURCE, + BAT_CTRL_7U_ENA | BAT_CTRL_20U_ENA, + ~(BAT_CTRL_7U_ENA | BAT_CTRL_20U_ENA)); + } + + if (ret) { + dev_err(di->dev, "%s failed disabling current source\n", + __func__); + goto disable_curr_source; + } + + /* Enable Pull-Up and comparator */ + ret = abx500_mask_and_set_register_interruptible(di->dev, + AB8500_CHARGER, AB8500_BAT_CTRL_CURRENT_SOURCE, + BAT_CTRL_PULL_UP_ENA | BAT_CTRL_CMP_ENA, + BAT_CTRL_PULL_UP_ENA | BAT_CTRL_CMP_ENA); + if (ret) { + dev_err(di->dev, "%s failed enabling PU and comp\n", + __func__); + goto enable_pu_comp; + } + + /* + * We have to wait one 32kHz cycle before disabling + * ForceBatCtrlCmpHigh since this needs to be written + * in a separate cycle + */ + udelay(32); + + /* Disable 'force comparator' */ + ret = abx500_mask_and_set_register_interruptible(di->dev, + AB8500_CHARGER, AB8500_BAT_CTRL_CURRENT_SOURCE, + FORCE_BAT_CTRL_CMP_HIGH, ~FORCE_BAT_CTRL_CMP_HIGH); + if (ret) { + dev_err(di->dev, "%s failed disabling force comp\n", + __func__); + goto disable_force_comp; + } + } + return ret; + + /* + * We have to try unsetting FORCE_BAT_CTRL_CMP_HIGH one more time + * if we got an error above + */ +disable_curr_source: + if (is_ab8540(di->parent)) { + /* Write 0 to the curr bits */ + ret = abx500_mask_and_set_register_interruptible(di->dev, + AB8500_CHARGER, AB8500_BAT_CTRL_CURRENT_SOURCE, + BAT_CTRL_60U_ENA | BAT_CTRL_120U_ENA, + ~(BAT_CTRL_60U_ENA | BAT_CTRL_120U_ENA)); + } else if (is_ab9540(di->parent) || is_ab8505(di->parent)) { + /* Write 0 to the curr bits */ + ret = abx500_mask_and_set_register_interruptible(di->dev, + AB8500_CHARGER, AB8500_BAT_CTRL_CURRENT_SOURCE, + BAT_CTRL_16U_ENA | BAT_CTRL_18U_ENA, + ~(BAT_CTRL_16U_ENA | BAT_CTRL_18U_ENA)); + } else { + /* Write 0 to the curr bits */ + ret = abx500_mask_and_set_register_interruptible(di->dev, + AB8500_CHARGER, AB8500_BAT_CTRL_CURRENT_SOURCE, + BAT_CTRL_7U_ENA | BAT_CTRL_20U_ENA, + ~(BAT_CTRL_7U_ENA | BAT_CTRL_20U_ENA)); + } + + if (ret) { + dev_err(di->dev, "%s failed disabling current source\n", + __func__); + return ret; + } +enable_pu_comp: + /* Enable Pull-Up and comparator */ + ret = abx500_mask_and_set_register_interruptible(di->dev, + AB8500_CHARGER, AB8500_BAT_CTRL_CURRENT_SOURCE, + BAT_CTRL_PULL_UP_ENA | BAT_CTRL_CMP_ENA, + BAT_CTRL_PULL_UP_ENA | BAT_CTRL_CMP_ENA); + if (ret) { + dev_err(di->dev, "%s failed enabling PU and comp\n", + __func__); + return ret; + } + +disable_force_comp: + /* + * We have to wait one 32kHz cycle before disabling + * ForceBatCtrlCmpHigh since this needs to be written + * in a separate cycle + */ + udelay(32); + + /* Disable 'force comparator' */ + ret = abx500_mask_and_set_register_interruptible(di->dev, + AB8500_CHARGER, AB8500_BAT_CTRL_CURRENT_SOURCE, + FORCE_BAT_CTRL_CMP_HIGH, ~FORCE_BAT_CTRL_CMP_HIGH); + if (ret) { + dev_err(di->dev, "%s failed disabling force comp\n", + __func__); + return ret; + } + + return ret; +} + +/** + * ab8500_btemp_get_batctrl_res() - get battery resistance + * @di: pointer to the ab8500_btemp structure + * + * This function returns the battery pack identification resistance. + * Returns value in Ohms. + */ +static int ab8500_btemp_get_batctrl_res(struct ab8500_btemp *di) +{ + int ret; + int batctrl = 0; + int res; + int inst_curr; + int i; + + /* + * BATCTRL current sources are included on AB8500 cut2.0 + * and future versions + */ + ret = ab8500_btemp_curr_source_enable(di, true); + if (ret) { + dev_err(di->dev, "%s curr source enabled failed\n", __func__); + return ret; + } + + if (!di->fg) + di->fg = ab8500_fg_get(); + if (!di->fg) { + dev_err(di->dev, "No fg found\n"); + return -EINVAL; + } + + ret = ab8500_fg_inst_curr_start(di->fg); + + if (ret) { + dev_err(di->dev, "Failed to start current measurement\n"); + return ret; + } + + do { + msleep(20); + } while (!ab8500_fg_inst_curr_started(di->fg)); + + i = 0; + + do { + batctrl += ab8500_btemp_read_batctrl_voltage(di); + i++; + msleep(20); + } while (!ab8500_fg_inst_curr_done(di->fg)); + batctrl /= i; + + ret = ab8500_fg_inst_curr_finalize(di->fg, &inst_curr); + if (ret) { + dev_err(di->dev, "Failed to finalize current measurement\n"); + return ret; + } + + res = ab8500_btemp_batctrl_volt_to_res(di, batctrl, inst_curr); + + ret = ab8500_btemp_curr_source_enable(di, false); + if (ret) { + dev_err(di->dev, "%s curr source disable failed\n", __func__); + return ret; + } + + dev_dbg(di->dev, "%s batctrl: %d res: %d inst_curr: %d samples: %d\n", + __func__, batctrl, res, inst_curr, i); + + return res; +} + +/** + * ab8500_btemp_res_to_temp() - resistance to temperature + * @di: pointer to the ab8500_btemp structure + * @tbl: pointer to the resiatance to temperature table + * @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 + * 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; + /* + * Calculate the formula for the straight line + * Simple interpolation if we are within + * the resistance table limits, extrapolate + * if resistance is outside the limits. + */ + if (res > tbl[0].resist) + i = 0; + else if (res <= tbl[tbl_size - 1].resist) + i = tbl_size - 2; + else { + i = 0; + while (!(res <= tbl[i].resist && + res > tbl[i + 1].resist)) + i++; + } + + temp = tbl[i].temp + ((tbl[i + 1].temp - tbl[i].temp) * + (res - tbl[i].resist)) / (tbl[i + 1].resist - tbl[i].resist); + return temp; +} + +/** + * ab8500_btemp_measure_temp() - measure battery temperature + * @di: pointer to the ab8500_btemp structure + * + * Returns battery temperature (on success) else the previous temperature + */ +static int ab8500_btemp_measure_temp(struct ab8500_btemp *di) +{ + int temp; + static int prev; + int rbat, rntc, vntc; + u8 id; + + id = di->bm->batt_id; + + if (di->bm->adc_therm == ABx500_ADC_THERM_BATCTRL && + id != BATTERY_UNKNOWN) { + + rbat = ab8500_btemp_get_batctrl_res(di); + if (rbat < 0) { + dev_err(di->dev, "%s get batctrl res failed\n", + __func__); + /* + * Return out-of-range temperature so that + * charging is stopped + */ + return BTEMP_THERMAL_LOW_LIMIT; + } + + temp = ab8500_btemp_res_to_temp(di, + di->bm->bat_type[id].r_to_t_tbl, + di->bm->bat_type[id].n_temp_tbl_elements, rbat); + } else { + vntc = ab8500_gpadc_convert(di->gpadc, BTEMP_BALL); + if (vntc < 0) { + dev_err(di->dev, + "%s gpadc conversion failed," + " using previous value\n", __func__); + return prev; + } + /* + * The PCB NTC is sourced from VTVOUT via a 230kOhm + * resistor. + */ + rntc = 230000 * vntc / (VTVOUT_V - vntc); + + temp = ab8500_btemp_res_to_temp(di, + di->bm->bat_type[id].r_to_t_tbl, + di->bm->bat_type[id].n_temp_tbl_elements, rntc); + prev = temp; + } + dev_dbg(di->dev, "Battery temperature is %d\n", temp); + return temp; +} + +/** + * ab8500_btemp_id() - Identify the connected battery + * @di: pointer to the ab8500_btemp structure + * + * This function will try to identify the battery by reading the ID + * resistor. Some brands use a combined ID resistor with a NTC resistor to + * both be able to identify and to read the temperature of it. + */ +static int ab8500_btemp_id(struct ab8500_btemp *di) +{ + int res; + u8 i; + if (is_ab8540(di->parent)) + di->curr_source = BTEMP_BATCTRL_CURR_SRC_60UA; + else if (is_ab9540(di->parent) || is_ab8505(di->parent)) + di->curr_source = BTEMP_BATCTRL_CURR_SRC_16UA; + else + di->curr_source = BTEMP_BATCTRL_CURR_SRC_7UA; + + di->bm->batt_id = BATTERY_UNKNOWN; + + res = ab8500_btemp_get_batctrl_res(di); + if (res < 0) { + dev_err(di->dev, "%s get batctrl res failed\n", __func__); + return -ENXIO; + } + + /* BATTERY_UNKNOWN is defined on position 0, skip it! */ + for (i = BATTERY_UNKNOWN + 1; i < di->bm->n_btypes; i++) { + if ((res <= di->bm->bat_type[i].resis_high) && + (res >= di->bm->bat_type[i].resis_low)) { + dev_dbg(di->dev, "Battery detected on %s" + " low %d < res %d < high: %d" + " index: %d\n", + di->bm->adc_therm == ABx500_ADC_THERM_BATCTRL ? + "BATCTRL" : "BATTEMP", + di->bm->bat_type[i].resis_low, res, + di->bm->bat_type[i].resis_high, i); + + di->bm->batt_id = i; + break; + } + } + + if (di->bm->batt_id == BATTERY_UNKNOWN) { + dev_warn(di->dev, "Battery identified as unknown" + ", resistance %d Ohm\n", res); + return -ENXIO; + } + + /* + * We only have to change current source if the + * detected type is Type 1. + */ + if (di->bm->adc_therm == ABx500_ADC_THERM_BATCTRL && + di->bm->batt_id == 1) { + if (is_ab8540(di->parent)) { + dev_dbg(di->dev, + "Set BATCTRL current source to 60uA\n"); + di->curr_source = BTEMP_BATCTRL_CURR_SRC_60UA; + } else if (is_ab9540(di->parent) || is_ab8505(di->parent)) { + dev_dbg(di->dev, + "Set BATCTRL current source to 16uA\n"); + di->curr_source = BTEMP_BATCTRL_CURR_SRC_16UA; + } else { + dev_dbg(di->dev, "Set BATCTRL current source to 20uA\n"); + di->curr_source = BTEMP_BATCTRL_CURR_SRC_20UA; + } + } + + return di->bm->batt_id; +} + +/** + * ab8500_btemp_periodic_work() - Measuring the temperature periodically + * @work: pointer to the work_struct structure + * + * Work function for measuring the temperature periodically + */ +static void ab8500_btemp_periodic_work(struct work_struct *work) +{ + int interval; + int bat_temp; + struct ab8500_btemp *di = container_of(work, + struct ab8500_btemp, btemp_periodic_work.work); + + if (!di->initialized) { + /* Identify the battery */ + if (ab8500_btemp_id(di) < 0) + dev_warn(di->dev, "failed to identify the battery\n"); + } + + bat_temp = ab8500_btemp_measure_temp(di); + /* + * Filter battery temperature. + * Allow direct updates on temperature only if two samples result in + * same temperature. Else only allow 1 degree change from previous + * reported value in the direction of the new measurement. + */ + if ((bat_temp == di->prev_bat_temp) || !di->initialized) { + if ((di->bat_temp != di->prev_bat_temp) || !di->initialized) { + di->initialized = true; + di->bat_temp = bat_temp; + power_supply_changed(di->btemp_psy); + } + } else if (bat_temp < di->prev_bat_temp) { + di->bat_temp--; + power_supply_changed(di->btemp_psy); + } else if (bat_temp > di->prev_bat_temp) { + di->bat_temp++; + power_supply_changed(di->btemp_psy); + } + di->prev_bat_temp = bat_temp; + + if (di->events.ac_conn || di->events.usb_conn) + interval = di->bm->temp_interval_chg; + else + interval = di->bm->temp_interval_nochg; + + /* Schedule a new measurement */ + queue_delayed_work(di->btemp_wq, + &di->btemp_periodic_work, + round_jiffies(interval * HZ)); +} + +/** + * ab8500_btemp_batctrlindb_handler() - battery removal detected + * @irq: interrupt number + * @_di: void pointer that has to address of ab8500_btemp + * + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab8500_btemp_batctrlindb_handler(int irq, void *_di) +{ + struct ab8500_btemp *di = _di; + dev_err(di->dev, "Battery removal detected!\n"); + + di->events.batt_rem = true; + power_supply_changed(di->btemp_psy); + + return IRQ_HANDLED; +} + +/** + * ab8500_btemp_templow_handler() - battery temp lower than 10 degrees + * @irq: interrupt number + * @_di: void pointer that has to address of ab8500_btemp + * + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab8500_btemp_templow_handler(int irq, void *_di) +{ + struct ab8500_btemp *di = _di; + + if (is_ab8500_3p3_or_earlier(di->parent)) { + dev_dbg(di->dev, "Ignore false btemp low irq" + " for ABB cut 1.0, 1.1, 2.0 and 3.3\n"); + } else { + dev_crit(di->dev, "Battery temperature lower than -10deg c\n"); + + di->events.btemp_low = true; + di->events.btemp_high = false; + di->events.btemp_medhigh = false; + di->events.btemp_lowmed = false; + power_supply_changed(di->btemp_psy); + } + + return IRQ_HANDLED; +} + +/** + * ab8500_btemp_temphigh_handler() - battery temp higher than max temp + * @irq: interrupt number + * @_di: void pointer that has to address of ab8500_btemp + * + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab8500_btemp_temphigh_handler(int irq, void *_di) +{ + struct ab8500_btemp *di = _di; + + dev_crit(di->dev, "Battery temperature is higher than MAX temp\n"); + + di->events.btemp_high = true; + di->events.btemp_medhigh = false; + di->events.btemp_lowmed = false; + di->events.btemp_low = false; + power_supply_changed(di->btemp_psy); + + return IRQ_HANDLED; +} + +/** + * ab8500_btemp_lowmed_handler() - battery temp between low and medium + * @irq: interrupt number + * @_di: void pointer that has to address of ab8500_btemp + * + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab8500_btemp_lowmed_handler(int irq, void *_di) +{ + struct ab8500_btemp *di = _di; + + dev_dbg(di->dev, "Battery temperature is between low and medium\n"); + + di->events.btemp_lowmed = true; + di->events.btemp_medhigh = false; + di->events.btemp_high = false; + di->events.btemp_low = false; + power_supply_changed(di->btemp_psy); + + return IRQ_HANDLED; +} + +/** + * ab8500_btemp_medhigh_handler() - battery temp between medium and high + * @irq: interrupt number + * @_di: void pointer that has to address of ab8500_btemp + * + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab8500_btemp_medhigh_handler(int irq, void *_di) +{ + struct ab8500_btemp *di = _di; + + dev_dbg(di->dev, "Battery temperature is between medium and high\n"); + + di->events.btemp_medhigh = true; + di->events.btemp_lowmed = false; + di->events.btemp_high = false; + di->events.btemp_low = false; + power_supply_changed(di->btemp_psy); + + return IRQ_HANDLED; +} + +/** + * ab8500_btemp_periodic() - Periodic temperature measurements + * @di: pointer to the ab8500_btemp structure + * @enable: enable or disable periodic temperature measurements + * + * Starts of stops periodic temperature measurements. Periodic measurements + * should only be done when a charger is connected. + */ +static void ab8500_btemp_periodic(struct ab8500_btemp *di, + bool enable) +{ + dev_dbg(di->dev, "Enable periodic temperature measurements: %d\n", + enable); + /* + * Make sure a new measurement is done directly by cancelling + * any pending work + */ + cancel_delayed_work_sync(&di->btemp_periodic_work); + + if (enable) + queue_delayed_work(di->btemp_wq, &di->btemp_periodic_work, 0); +} + +/** + * ab8500_btemp_get_temp() - get battery temperature + * @di: pointer to the ab8500_btemp structure + * + * Returns battery temperature + */ +int ab8500_btemp_get_temp(struct ab8500_btemp *di) +{ + int temp = 0; + + /* + * The BTEMP events are not reliabe on AB8500 cut3.3 + * and prior versions + */ + if (is_ab8500_3p3_or_earlier(di->parent)) { + temp = di->bat_temp * 10; + } else { + if (di->events.btemp_low) { + if (temp > di->btemp_ranges.btemp_low_limit) + temp = di->btemp_ranges.btemp_low_limit * 10; + else + temp = di->bat_temp * 10; + } else if (di->events.btemp_high) { + if (temp < di->btemp_ranges.btemp_high_limit) + temp = di->btemp_ranges.btemp_high_limit * 10; + else + temp = di->bat_temp * 10; + } else if (di->events.btemp_lowmed) { + if (temp > di->btemp_ranges.btemp_med_limit) + temp = di->btemp_ranges.btemp_med_limit * 10; + else + temp = di->bat_temp * 10; + } else if (di->events.btemp_medhigh) { + if (temp < di->btemp_ranges.btemp_med_limit) + temp = di->btemp_ranges.btemp_med_limit * 10; + else + temp = di->bat_temp * 10; + } else + temp = di->bat_temp * 10; + } + return temp; +} +EXPORT_SYMBOL(ab8500_btemp_get_temp); + +/** + * ab8500_btemp_get_batctrl_temp() - get the temperature + * @btemp: pointer to the btemp structure + * + * Returns the batctrl temperature in millidegrees + */ +int ab8500_btemp_get_batctrl_temp(struct ab8500_btemp *btemp) +{ + return btemp->bat_temp * 1000; +} +EXPORT_SYMBOL(ab8500_btemp_get_batctrl_temp); + +/** + * ab8500_btemp_get_property() - get the btemp properties + * @psy: pointer to the power_supply structure + * @psp: pointer to the power_supply_property structure + * @val: pointer to the power_supply_propval union + * + * This function gets called when an application tries to get the btemp + * properties by reading the sysfs files. + * online: presence of the battery + * present: presence of the battery + * technology: battery technology + * temp: battery temperature + * Returns error code in case of failure else 0(on success) + */ +static int ab8500_btemp_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct ab8500_btemp *di = power_supply_get_drvdata(psy); + + switch (psp) { + case POWER_SUPPLY_PROP_PRESENT: + case POWER_SUPPLY_PROP_ONLINE: + if (di->events.batt_rem) + val->intval = 0; + else + val->intval = 1; + break; + case POWER_SUPPLY_PROP_TECHNOLOGY: + val->intval = di->bm->bat_type[di->bm->batt_id].name; + break; + case POWER_SUPPLY_PROP_TEMP: + val->intval = ab8500_btemp_get_temp(di); + break; + default: + return -EINVAL; + } + return 0; +} + +static int ab8500_btemp_get_ext_psy_data(struct device *dev, void *data) +{ + struct power_supply *psy; + struct power_supply *ext = dev_get_drvdata(dev); + const char **supplicants = (const char **)ext->supplied_to; + struct ab8500_btemp *di; + union power_supply_propval ret; + int j; + + psy = (struct power_supply *)data; + di = power_supply_get_drvdata(psy); + + /* + * For all psy where the name of your driver + * appears in any supplied_to + */ + j = match_string(supplicants, ext->num_supplicants, psy->desc->name); + if (j < 0) + return 0; + + /* Go through all properties for the psy */ + for (j = 0; j < ext->desc->num_properties; j++) { + enum power_supply_property prop; + prop = ext->desc->properties[j]; + + if (power_supply_get_property(ext, prop, &ret)) + continue; + + switch (prop) { + case POWER_SUPPLY_PROP_PRESENT: + switch (ext->desc->type) { + case POWER_SUPPLY_TYPE_MAINS: + /* AC disconnected */ + if (!ret.intval && di->events.ac_conn) { + di->events.ac_conn = false; + } + /* AC connected */ + else if (ret.intval && !di->events.ac_conn) { + di->events.ac_conn = true; + if (!di->events.usb_conn) + ab8500_btemp_periodic(di, true); + } + break; + case POWER_SUPPLY_TYPE_USB: + /* USB disconnected */ + if (!ret.intval && di->events.usb_conn) { + di->events.usb_conn = false; + } + /* USB connected */ + else if (ret.intval && !di->events.usb_conn) { + di->events.usb_conn = true; + if (!di->events.ac_conn) + ab8500_btemp_periodic(di, true); + } + break; + default: + break; + } + break; + default: + break; + } + } + return 0; +} + +/** + * ab8500_btemp_external_power_changed() - callback for power supply changes + * @psy: pointer to the structure power_supply + * + * This function is pointing to the function pointer external_power_changed + * of the structure power_supply. + * This function gets executed when there is a change in the external power + * supply to the btemp. + */ +static void ab8500_btemp_external_power_changed(struct power_supply *psy) +{ + struct ab8500_btemp *di = power_supply_get_drvdata(psy); + + class_for_each_device(power_supply_class, NULL, + di->btemp_psy, ab8500_btemp_get_ext_psy_data); +} + +/* ab8500 btemp driver interrupts and their respective isr */ +static struct ab8500_btemp_interrupts ab8500_btemp_irq[] = { + {"BAT_CTRL_INDB", ab8500_btemp_batctrlindb_handler}, + {"BTEMP_LOW", ab8500_btemp_templow_handler}, + {"BTEMP_HIGH", ab8500_btemp_temphigh_handler}, + {"BTEMP_LOW_MEDIUM", ab8500_btemp_lowmed_handler}, + {"BTEMP_MEDIUM_HIGH", ab8500_btemp_medhigh_handler}, +}; + +#if defined(CONFIG_PM) +static int ab8500_btemp_resume(struct platform_device *pdev) +{ + struct ab8500_btemp *di = platform_get_drvdata(pdev); + + ab8500_btemp_periodic(di, true); + + return 0; +} + +static int ab8500_btemp_suspend(struct platform_device *pdev, + pm_message_t state) +{ + struct ab8500_btemp *di = platform_get_drvdata(pdev); + + ab8500_btemp_periodic(di, false); + + return 0; +} +#else +#define ab8500_btemp_suspend NULL +#define ab8500_btemp_resume NULL +#endif + +static int ab8500_btemp_remove(struct platform_device *pdev) +{ + struct ab8500_btemp *di = platform_get_drvdata(pdev); + int i, irq; + + /* Disable interrupts */ + for (i = 0; i < ARRAY_SIZE(ab8500_btemp_irq); i++) { + irq = platform_get_irq_byname(pdev, ab8500_btemp_irq[i].name); + free_irq(irq, di); + } + + /* Delete the work queue */ + destroy_workqueue(di->btemp_wq); + + flush_scheduled_work(); + power_supply_unregister(di->btemp_psy); + + return 0; +} + +static char *supply_interface[] = { + "ab8500_chargalg", + "ab8500_fg", +}; + +static const struct power_supply_desc ab8500_btemp_desc = { + .name = "ab8500_btemp", + .type = POWER_SUPPLY_TYPE_BATTERY, + .properties = ab8500_btemp_props, + .num_properties = ARRAY_SIZE(ab8500_btemp_props), + .get_property = ab8500_btemp_get_property, + .external_power_changed = ab8500_btemp_external_power_changed, +}; + +static int ab8500_btemp_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct abx500_bm_data *plat = pdev->dev.platform_data; + struct power_supply_config psy_cfg = {}; + struct ab8500_btemp *di; + int irq, i, ret = 0; + u8 val; + + di = devm_kzalloc(&pdev->dev, sizeof(*di), GFP_KERNEL); + if (!di) { + dev_err(&pdev->dev, "%s no mem for ab8500_btemp\n", __func__); + return -ENOMEM; + } + + if (!plat) { + dev_err(&pdev->dev, "no battery management data supplied\n"); + return -EINVAL; + } + di->bm = plat; + + if (np) { + ret = ab8500_bm_of_probe(&pdev->dev, np, di->bm); + if (ret) { + dev_err(&pdev->dev, "failed to get battery information\n"); + return ret; + } + } + + /* get parent data */ + di->dev = &pdev->dev; + di->parent = dev_get_drvdata(pdev->dev.parent); + di->gpadc = ab8500_gpadc_get("ab8500-gpadc.0"); + + di->initialized = false; + + psy_cfg.supplied_to = supply_interface; + psy_cfg.num_supplicants = ARRAY_SIZE(supply_interface); + psy_cfg.drv_data = di; + + /* Create a work queue for the btemp */ + di->btemp_wq = + create_singlethread_workqueue("ab8500_btemp_wq"); + if (di->btemp_wq == NULL) { + dev_err(di->dev, "failed to create work queue\n"); + return -ENOMEM; + } + + /* Init work for measuring temperature periodically */ + INIT_DEFERRABLE_WORK(&di->btemp_periodic_work, + ab8500_btemp_periodic_work); + + /* Set BTEMP thermal limits. Low and Med are fixed */ + di->btemp_ranges.btemp_low_limit = BTEMP_THERMAL_LOW_LIMIT; + di->btemp_ranges.btemp_med_limit = BTEMP_THERMAL_MED_LIMIT; + + ret = abx500_get_register_interruptible(di->dev, AB8500_CHARGER, + AB8500_BTEMP_HIGH_TH, &val); + if (ret < 0) { + dev_err(di->dev, "%s ab8500 read failed\n", __func__); + goto free_btemp_wq; + } + switch (val) { + case BTEMP_HIGH_TH_57_0: + case BTEMP_HIGH_TH_57_1: + di->btemp_ranges.btemp_high_limit = + BTEMP_THERMAL_HIGH_LIMIT_57; + break; + case BTEMP_HIGH_TH_52: + di->btemp_ranges.btemp_high_limit = + BTEMP_THERMAL_HIGH_LIMIT_52; + break; + case BTEMP_HIGH_TH_62: + di->btemp_ranges.btemp_high_limit = + BTEMP_THERMAL_HIGH_LIMIT_62; + break; + } + + /* Register BTEMP power supply class */ + di->btemp_psy = power_supply_register(di->dev, &ab8500_btemp_desc, + &psy_cfg); + if (IS_ERR(di->btemp_psy)) { + dev_err(di->dev, "failed to register BTEMP psy\n"); + ret = PTR_ERR(di->btemp_psy); + goto free_btemp_wq; + } + + /* Register interrupts */ + for (i = 0; i < ARRAY_SIZE(ab8500_btemp_irq); i++) { + irq = platform_get_irq_byname(pdev, ab8500_btemp_irq[i].name); + ret = request_threaded_irq(irq, NULL, ab8500_btemp_irq[i].isr, + IRQF_SHARED | IRQF_NO_SUSPEND, + ab8500_btemp_irq[i].name, di); + + if (ret) { + dev_err(di->dev, "failed to request %s IRQ %d: %d\n" + , ab8500_btemp_irq[i].name, irq, ret); + goto free_irq; + } + dev_dbg(di->dev, "Requested %s IRQ %d: %d\n", + ab8500_btemp_irq[i].name, irq, ret); + } + + platform_set_drvdata(pdev, di); + + /* Kick off periodic temperature measurements */ + ab8500_btemp_periodic(di, true); + list_add_tail(&di->node, &ab8500_btemp_list); + + return ret; + +free_irq: + power_supply_unregister(di->btemp_psy); + + /* We also have to free all successfully registered irqs */ + for (i = i - 1; i >= 0; i--) { + irq = platform_get_irq_byname(pdev, ab8500_btemp_irq[i].name); + free_irq(irq, di); + } +free_btemp_wq: + destroy_workqueue(di->btemp_wq); + return ret; +} + +static const struct of_device_id ab8500_btemp_match[] = { + { .compatible = "stericsson,ab8500-btemp", }, + { }, +}; + +static struct platform_driver ab8500_btemp_driver = { + .probe = ab8500_btemp_probe, + .remove = ab8500_btemp_remove, + .suspend = ab8500_btemp_suspend, + .resume = ab8500_btemp_resume, + .driver = { + .name = "ab8500-btemp", + .of_match_table = ab8500_btemp_match, + }, +}; + +static int __init ab8500_btemp_init(void) +{ + return platform_driver_register(&ab8500_btemp_driver); +} + +static void __exit ab8500_btemp_exit(void) +{ + platform_driver_unregister(&ab8500_btemp_driver); +} + +device_initcall(ab8500_btemp_init); +module_exit(ab8500_btemp_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Johan Palsson, Karl Komierowski, Arun R Murthy"); +MODULE_ALIAS("platform:ab8500-btemp"); +MODULE_DESCRIPTION("AB8500 battery temperature driver"); diff --git a/drivers/power/supply/ab8500_charger.c b/drivers/power/supply/ab8500_charger.c new file mode 100644 index 000000000000..30de5d42b26a --- /dev/null +++ b/drivers/power/supply/ab8500_charger.c @@ -0,0 +1,3765 @@ +/* + * Copyright (C) ST-Ericsson SA 2012 + * + * Charger driver for AB8500 + * + * License Terms: GNU General Public License v2 + * Author: + * Johan Palsson + * Karl Komierowski + * Arun R Murthy + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Charger constants */ +#define NO_PW_CONN 0 +#define AC_PW_CONN 1 +#define USB_PW_CONN 2 + +#define MAIN_WDOG_ENA 0x01 +#define MAIN_WDOG_KICK 0x02 +#define MAIN_WDOG_DIS 0x00 +#define CHARG_WD_KICK 0x01 +#define MAIN_CH_ENA 0x01 +#define MAIN_CH_NO_OVERSHOOT_ENA_N 0x02 +#define USB_CH_ENA 0x01 +#define USB_CHG_NO_OVERSHOOT_ENA_N 0x02 +#define MAIN_CH_DET 0x01 +#define MAIN_CH_CV_ON 0x04 +#define USB_CH_CV_ON 0x08 +#define VBUS_DET_DBNC100 0x02 +#define VBUS_DET_DBNC1 0x01 +#define OTP_ENABLE_WD 0x01 +#define DROP_COUNT_RESET 0x01 +#define USB_CH_DET 0x01 + +#define MAIN_CH_INPUT_CURR_SHIFT 4 +#define VBUS_IN_CURR_LIM_SHIFT 4 +#define AB8540_VBUS_IN_CURR_LIM_SHIFT 2 +#define AUTO_VBUS_IN_CURR_LIM_SHIFT 4 +#define AB8540_AUTO_VBUS_IN_CURR_MASK 0x3F +#define VBUS_IN_CURR_LIM_RETRY_SET_TIME 30 /* seconds */ + +#define LED_INDICATOR_PWM_ENA 0x01 +#define LED_INDICATOR_PWM_DIS 0x00 +#define LED_IND_CUR_5MA 0x04 +#define LED_INDICATOR_PWM_DUTY_252_256 0xBF + +/* HW failure constants */ +#define MAIN_CH_TH_PROT 0x02 +#define VBUS_CH_NOK 0x08 +#define USB_CH_TH_PROT 0x02 +#define VBUS_OVV_TH 0x01 +#define MAIN_CH_NOK 0x01 +#define VBUS_DET 0x80 + +#define MAIN_CH_STATUS2_MAINCHGDROP 0x80 +#define MAIN_CH_STATUS2_MAINCHARGERDETDBNC 0x40 +#define USB_CH_VBUSDROP 0x40 +#define USB_CH_VBUSDETDBNC 0x01 + +/* UsbLineStatus register bit masks */ +#define AB8500_USB_LINK_STATUS 0x78 +#define AB8505_USB_LINK_STATUS 0xF8 +#define AB8500_STD_HOST_SUSP 0x18 +#define USB_LINK_STATUS_SHIFT 3 + +/* Watchdog timeout constant */ +#define WD_TIMER 0x30 /* 4min */ +#define WD_KICK_INTERVAL (60 * HZ) + +/* Lowest charger voltage is 3.39V -> 0x4E */ +#define LOW_VOLT_REG 0x4E + +/* Step up/down delay in us */ +#define STEP_UDELAY 1000 + +#define CHARGER_STATUS_POLL 10 /* in ms */ + +#define CHG_WD_INTERVAL (60 * HZ) + +#define AB8500_SW_CONTROL_FALLBACK 0x03 +/* Wait for enumeration before charing in us */ +#define WAIT_ACA_RID_ENUMERATION (5 * 1000) +/*External charger control*/ +#define AB8500_SYS_CHARGER_CONTROL_REG 0x52 +#define EXTERNAL_CHARGER_DISABLE_REG_VAL 0x03 +#define EXTERNAL_CHARGER_ENABLE_REG_VAL 0x07 + +/* UsbLineStatus register - usb types */ +enum ab8500_charger_link_status { + USB_STAT_NOT_CONFIGURED, + USB_STAT_STD_HOST_NC, + USB_STAT_STD_HOST_C_NS, + USB_STAT_STD_HOST_C_S, + USB_STAT_HOST_CHG_NM, + USB_STAT_HOST_CHG_HS, + USB_STAT_HOST_CHG_HS_CHIRP, + USB_STAT_DEDICATED_CHG, + USB_STAT_ACA_RID_A, + USB_STAT_ACA_RID_B, + USB_STAT_ACA_RID_C_NM, + USB_STAT_ACA_RID_C_HS, + USB_STAT_ACA_RID_C_HS_CHIRP, + USB_STAT_HM_IDGND, + USB_STAT_RESERVED, + USB_STAT_NOT_VALID_LINK, + USB_STAT_PHY_EN, + USB_STAT_SUP_NO_IDGND_VBUS, + USB_STAT_SUP_IDGND_VBUS, + USB_STAT_CHARGER_LINE_1, + USB_STAT_CARKIT_1, + USB_STAT_CARKIT_2, + USB_STAT_ACA_DOCK_CHARGER, +}; + +enum ab8500_usb_state { + AB8500_BM_USB_STATE_RESET_HS, /* HighSpeed Reset */ + AB8500_BM_USB_STATE_RESET_FS, /* FullSpeed/LowSpeed Reset */ + AB8500_BM_USB_STATE_CONFIGURED, + AB8500_BM_USB_STATE_SUSPEND, + AB8500_BM_USB_STATE_RESUME, + AB8500_BM_USB_STATE_MAX, +}; + +/* VBUS input current limits supported in AB8500 in mA */ +#define USB_CH_IP_CUR_LVL_0P05 50 +#define USB_CH_IP_CUR_LVL_0P09 98 +#define USB_CH_IP_CUR_LVL_0P19 193 +#define USB_CH_IP_CUR_LVL_0P29 290 +#define USB_CH_IP_CUR_LVL_0P38 380 +#define USB_CH_IP_CUR_LVL_0P45 450 +#define USB_CH_IP_CUR_LVL_0P5 500 +#define USB_CH_IP_CUR_LVL_0P6 600 +#define USB_CH_IP_CUR_LVL_0P7 700 +#define USB_CH_IP_CUR_LVL_0P8 800 +#define USB_CH_IP_CUR_LVL_0P9 900 +#define USB_CH_IP_CUR_LVL_1P0 1000 +#define USB_CH_IP_CUR_LVL_1P1 1100 +#define USB_CH_IP_CUR_LVL_1P3 1300 +#define USB_CH_IP_CUR_LVL_1P4 1400 +#define USB_CH_IP_CUR_LVL_1P5 1500 + +#define VBAT_TRESH_IP_CUR_RED 3800 + +#define to_ab8500_charger_usb_device_info(x) container_of((x), \ + struct ab8500_charger, usb_chg) +#define to_ab8500_charger_ac_device_info(x) container_of((x), \ + struct ab8500_charger, ac_chg) + +/** + * struct ab8500_charger_interrupts - ab8500 interupts + * @name: name of the interrupt + * @isr function pointer to the isr + */ +struct ab8500_charger_interrupts { + char *name; + irqreturn_t (*isr)(int irq, void *data); +}; + +struct ab8500_charger_info { + int charger_connected; + int charger_online; + int charger_voltage; + int cv_active; + bool wd_expired; + int charger_current; +}; + +struct ab8500_charger_event_flags { + bool mainextchnotok; + bool main_thermal_prot; + bool usb_thermal_prot; + bool vbus_ovv; + bool usbchargernotok; + bool chgwdexp; + bool vbus_collapse; + bool vbus_drop_end; +}; + +struct ab8500_charger_usb_state { + int usb_current; + int usb_current_tmp; + enum ab8500_usb_state state; + enum ab8500_usb_state state_tmp; + spinlock_t usb_lock; +}; + +struct ab8500_charger_max_usb_in_curr { + int usb_type_max; + int set_max; + int calculated_max; +}; + +/** + * struct ab8500_charger - ab8500 Charger device information + * @dev: Pointer to the structure device + * @vbus_detected: VBUS detected + * @vbus_detected_start: + * VBUS detected during startup + * @ac_conn: This will be true when the AC charger has been plugged + * @vddadc_en_ac: Indicate if VDD ADC supply is enabled because AC + * charger is enabled + * @vddadc_en_usb: Indicate if VDD ADC supply is enabled because USB + * charger is enabled + * @vbat Battery voltage + * @old_vbat Previously measured battery voltage + * @usb_device_is_unrecognised USB device is unrecognised by the hardware + * @autopower Indicate if we should have automatic pwron after pwrloss + * @autopower_cfg platform specific power config support for "pwron after pwrloss" + * @invalid_charger_detect_state State when forcing AB to use invalid charger + * @is_aca_rid: Incicate if accessory is ACA type + * @current_stepping_sessions: + * Counter for current stepping sessions + * @parent: Pointer to the struct ab8500 + * @gpadc: Pointer to the struct gpadc + * @bm: Platform specific battery management information + * @flags: Structure for information about events triggered + * @usb_state: Structure for usb stack information + * @max_usb_in_curr: Max USB charger input current + * @ac_chg: AC charger power supply + * @usb_chg: USB charger power supply + * @ac: Structure that holds the AC charger properties + * @usb: Structure that holds the USB charger properties + * @regu: Pointer to the struct regulator + * @charger_wq: Work queue for the IRQs and checking HW state + * @usb_ipt_crnt_lock: Lock to protect VBUS input current setting from mutuals + * @pm_lock: Lock to prevent system to suspend + * @check_vbat_work Work for checking vbat threshold to adjust vbus current + * @check_hw_failure_work: Work for checking HW state + * @check_usbchgnotok_work: Work for checking USB charger not ok status + * @kick_wd_work: Work for kicking the charger watchdog in case + * of ABB rev 1.* due to the watchog logic bug + * @ac_charger_attached_work: Work for checking if AC charger is still + * connected + * @usb_charger_attached_work: Work for checking if USB charger is still + * connected + * @ac_work: Work for checking AC charger connection + * @detect_usb_type_work: Work for detecting the USB type connected + * @usb_link_status_work: Work for checking the new USB link status + * @usb_state_changed_work: Work for checking USB state + * @attach_work: Work for detecting USB type + * @vbus_drop_end_work: Work for detecting VBUS drop end + * @check_main_thermal_prot_work: + * Work for checking Main thermal status + * @check_usb_thermal_prot_work: + * Work for checking USB thermal status + * @charger_attached_mutex: For controlling the wakelock + */ +struct ab8500_charger { + struct device *dev; + bool vbus_detected; + bool vbus_detected_start; + bool ac_conn; + bool vddadc_en_ac; + bool vddadc_en_usb; + int vbat; + int old_vbat; + bool usb_device_is_unrecognised; + bool autopower; + bool autopower_cfg; + int invalid_charger_detect_state; + int is_aca_rid; + atomic_t current_stepping_sessions; + struct ab8500 *parent; + struct ab8500_gpadc *gpadc; + struct abx500_bm_data *bm; + struct ab8500_charger_event_flags flags; + struct ab8500_charger_usb_state usb_state; + struct ab8500_charger_max_usb_in_curr max_usb_in_curr; + struct ux500_charger ac_chg; + struct ux500_charger usb_chg; + struct ab8500_charger_info ac; + struct ab8500_charger_info usb; + struct regulator *regu; + struct workqueue_struct *charger_wq; + struct mutex usb_ipt_crnt_lock; + struct delayed_work check_vbat_work; + struct delayed_work check_hw_failure_work; + struct delayed_work check_usbchgnotok_work; + struct delayed_work kick_wd_work; + struct delayed_work usb_state_changed_work; + struct delayed_work attach_work; + struct delayed_work ac_charger_attached_work; + struct delayed_work usb_charger_attached_work; + struct delayed_work vbus_drop_end_work; + struct work_struct ac_work; + struct work_struct detect_usb_type_work; + struct work_struct usb_link_status_work; + struct work_struct check_main_thermal_prot_work; + struct work_struct check_usb_thermal_prot_work; + struct usb_phy *usb_phy; + struct notifier_block nb; + struct mutex charger_attached_mutex; +}; + +/* AC properties */ +static enum power_supply_property ab8500_charger_ac_props[] = { + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_VOLTAGE_AVG, + POWER_SUPPLY_PROP_CURRENT_NOW, +}; + +/* USB properties */ +static enum power_supply_property ab8500_charger_usb_props[] = { + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_CURRENT_AVG, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_VOLTAGE_AVG, + POWER_SUPPLY_PROP_CURRENT_NOW, +}; + +/* + * Function for enabling and disabling sw fallback mode + * should always be disabled when no charger is connected. + */ +static void ab8500_enable_disable_sw_fallback(struct ab8500_charger *di, + bool fallback) +{ + u8 val; + u8 reg; + u8 bank; + u8 bit; + int ret; + + dev_dbg(di->dev, "SW Fallback: %d\n", fallback); + + if (is_ab8500(di->parent)) { + bank = 0x15; + reg = 0x0; + bit = 3; + } else { + bank = AB8500_SYS_CTRL1_BLOCK; + reg = AB8500_SW_CONTROL_FALLBACK; + bit = 0; + } + + /* read the register containing fallback bit */ + ret = abx500_get_register_interruptible(di->dev, bank, reg, &val); + if (ret < 0) { + dev_err(di->dev, "%d read failed\n", __LINE__); + return; + } + + if (is_ab8500(di->parent)) { + /* enable the OPT emulation registers */ + ret = abx500_set_register_interruptible(di->dev, 0x11, 0x00, 0x2); + if (ret) { + dev_err(di->dev, "%d write failed\n", __LINE__); + goto disable_otp; + } + } + + if (fallback) + val |= (1 << bit); + else + val &= ~(1 << bit); + + /* write back the changed fallback bit value to register */ + ret = abx500_set_register_interruptible(di->dev, bank, reg, val); + if (ret) { + dev_err(di->dev, "%d write failed\n", __LINE__); + } + +disable_otp: + if (is_ab8500(di->parent)) { + /* disable the set OTP registers again */ + ret = abx500_set_register_interruptible(di->dev, 0x11, 0x00, 0x0); + if (ret) { + dev_err(di->dev, "%d write failed\n", __LINE__); + } + } +} + +/** + * ab8500_power_supply_changed - a wrapper with local extentions for + * power_supply_changed + * @di: pointer to the ab8500_charger structure + * @psy: pointer to power_supply_that have changed. + * + */ +static void ab8500_power_supply_changed(struct ab8500_charger *di, + struct power_supply *psy) +{ + if (di->autopower_cfg) { + if (!di->usb.charger_connected && + !di->ac.charger_connected && + di->autopower) { + di->autopower = false; + ab8500_enable_disable_sw_fallback(di, false); + } else if (!di->autopower && + (di->ac.charger_connected || + di->usb.charger_connected)) { + di->autopower = true; + ab8500_enable_disable_sw_fallback(di, true); + } + } + power_supply_changed(psy); +} + +static void ab8500_charger_set_usb_connected(struct ab8500_charger *di, + bool connected) +{ + if (connected != di->usb.charger_connected) { + dev_dbg(di->dev, "USB connected:%i\n", connected); + di->usb.charger_connected = connected; + + if (!connected) + di->flags.vbus_drop_end = false; + + sysfs_notify(&di->usb_chg.psy->dev.kobj, NULL, "present"); + + if (connected) { + mutex_lock(&di->charger_attached_mutex); + mutex_unlock(&di->charger_attached_mutex); + + if (is_ab8500(di->parent)) + queue_delayed_work(di->charger_wq, + &di->usb_charger_attached_work, + HZ); + } else { + cancel_delayed_work_sync(&di->usb_charger_attached_work); + mutex_lock(&di->charger_attached_mutex); + mutex_unlock(&di->charger_attached_mutex); + } + } +} + +/** + * ab8500_charger_get_ac_voltage() - get ac charger voltage + * @di: pointer to the ab8500_charger structure + * + * Returns ac charger voltage (on success) + */ +static int ab8500_charger_get_ac_voltage(struct ab8500_charger *di) +{ + int vch; + + /* Only measure voltage if the charger is connected */ + if (di->ac.charger_connected) { + vch = ab8500_gpadc_convert(di->gpadc, MAIN_CHARGER_V); + if (vch < 0) + dev_err(di->dev, "%s gpadc conv failed,\n", __func__); + } else { + vch = 0; + } + return vch; +} + +/** + * ab8500_charger_ac_cv() - check if the main charger is in CV mode + * @di: pointer to the ab8500_charger structure + * + * Returns ac charger CV mode (on success) else error code + */ +static int ab8500_charger_ac_cv(struct ab8500_charger *di) +{ + u8 val; + int ret = 0; + + /* Only check CV mode if the charger is online */ + if (di->ac.charger_online) { + ret = abx500_get_register_interruptible(di->dev, AB8500_CHARGER, + AB8500_CH_STATUS1_REG, &val); + if (ret < 0) { + dev_err(di->dev, "%s ab8500 read failed\n", __func__); + return 0; + } + + if (val & MAIN_CH_CV_ON) + ret = 1; + else + ret = 0; + } + + return ret; +} + +/** + * ab8500_charger_get_vbus_voltage() - get vbus voltage + * @di: pointer to the ab8500_charger structure + * + * This function returns the vbus voltage. + * Returns vbus voltage (on success) + */ +static int ab8500_charger_get_vbus_voltage(struct ab8500_charger *di) +{ + int vch; + + /* Only measure voltage if the charger is connected */ + if (di->usb.charger_connected) { + vch = ab8500_gpadc_convert(di->gpadc, VBUS_V); + if (vch < 0) + dev_err(di->dev, "%s gpadc conv failed\n", __func__); + } else { + vch = 0; + } + return vch; +} + +/** + * ab8500_charger_get_usb_current() - get usb charger current + * @di: pointer to the ab8500_charger structure + * + * This function returns the usb charger current. + * Returns usb current (on success) and error code on failure + */ +static int ab8500_charger_get_usb_current(struct ab8500_charger *di) +{ + int ich; + + /* Only measure current if the charger is online */ + if (di->usb.charger_online) { + ich = ab8500_gpadc_convert(di->gpadc, USB_CHARGER_C); + if (ich < 0) + dev_err(di->dev, "%s gpadc conv failed\n", __func__); + } else { + ich = 0; + } + return ich; +} + +/** + * ab8500_charger_get_ac_current() - get ac charger current + * @di: pointer to the ab8500_charger structure + * + * This function returns the ac charger current. + * Returns ac current (on success) and error code on failure. + */ +static int ab8500_charger_get_ac_current(struct ab8500_charger *di) +{ + int ich; + + /* Only measure current if the charger is online */ + if (di->ac.charger_online) { + ich = ab8500_gpadc_convert(di->gpadc, MAIN_CHARGER_C); + if (ich < 0) + dev_err(di->dev, "%s gpadc conv failed\n", __func__); + } else { + ich = 0; + } + return ich; +} + +/** + * ab8500_charger_usb_cv() - check if the usb charger is in CV mode + * @di: pointer to the ab8500_charger structure + * + * Returns ac charger CV mode (on success) else error code + */ +static int ab8500_charger_usb_cv(struct ab8500_charger *di) +{ + int ret; + u8 val; + + /* Only check CV mode if the charger is online */ + if (di->usb.charger_online) { + ret = abx500_get_register_interruptible(di->dev, AB8500_CHARGER, + AB8500_CH_USBCH_STAT1_REG, &val); + if (ret < 0) { + dev_err(di->dev, "%s ab8500 read failed\n", __func__); + return 0; + } + + if (val & USB_CH_CV_ON) + ret = 1; + else + ret = 0; + } else { + ret = 0; + } + + return ret; +} + +/** + * ab8500_charger_detect_chargers() - Detect the connected chargers + * @di: pointer to the ab8500_charger structure + * @probe: if probe, don't delay and wait for HW + * + * Returns the type of charger connected. + * For USB it will not mean we can actually charge from it + * but that there is a USB cable connected that we have to + * identify. This is used during startup when we don't get + * interrupts of the charger detection + * + * Returns an integer value, that means, + * NO_PW_CONN no power supply is connected + * AC_PW_CONN if the AC power supply is connected + * USB_PW_CONN if the USB power supply is connected + * AC_PW_CONN + USB_PW_CONN if USB and AC power supplies are both connected + */ +static int ab8500_charger_detect_chargers(struct ab8500_charger *di, bool probe) +{ + int result = NO_PW_CONN; + int ret; + u8 val; + + /* Check for AC charger */ + ret = abx500_get_register_interruptible(di->dev, AB8500_CHARGER, + AB8500_CH_STATUS1_REG, &val); + if (ret < 0) { + dev_err(di->dev, "%s ab8500 read failed\n", __func__); + return ret; + } + + if (val & MAIN_CH_DET) + result = AC_PW_CONN; + + /* Check for USB charger */ + + if (!probe) { + /* + * AB8500 says VBUS_DET_DBNC1 & VBUS_DET_DBNC100 + * when disconnecting ACA even though no + * charger was connected. Try waiting a little + * longer than the 100 ms of VBUS_DET_DBNC100... + */ + msleep(110); + } + ret = abx500_get_register_interruptible(di->dev, AB8500_CHARGER, + AB8500_CH_USBCH_STAT1_REG, &val); + if (ret < 0) { + dev_err(di->dev, "%s ab8500 read failed\n", __func__); + return ret; + } + dev_dbg(di->dev, + "%s AB8500_CH_USBCH_STAT1_REG %x\n", __func__, + val); + if ((val & VBUS_DET_DBNC1) && (val & VBUS_DET_DBNC100)) + result |= USB_PW_CONN; + + return result; +} + +/** + * ab8500_charger_max_usb_curr() - get the max curr for the USB type + * @di: pointer to the ab8500_charger structure + * @link_status: the identified USB type + * + * Get the maximum current that is allowed to be drawn from the host + * based on the USB type. + * Returns error code in case of failure else 0 on success + */ +static int ab8500_charger_max_usb_curr(struct ab8500_charger *di, + enum ab8500_charger_link_status link_status) +{ + int ret = 0; + + di->usb_device_is_unrecognised = false; + + /* + * Platform only supports USB 2.0. + * This means that charging current from USB source + * is maximum 500 mA. Every occurence of USB_STAT_*_HOST_* + * should set USB_CH_IP_CUR_LVL_0P5. + */ + + switch (link_status) { + case USB_STAT_STD_HOST_NC: + case USB_STAT_STD_HOST_C_NS: + case USB_STAT_STD_HOST_C_S: + dev_dbg(di->dev, "USB Type - Standard host is " + "detected through USB driver\n"); + di->max_usb_in_curr.usb_type_max = USB_CH_IP_CUR_LVL_0P5; + di->is_aca_rid = 0; + break; + case USB_STAT_HOST_CHG_HS_CHIRP: + di->max_usb_in_curr.usb_type_max = USB_CH_IP_CUR_LVL_0P5; + di->is_aca_rid = 0; + break; + case USB_STAT_HOST_CHG_HS: + di->max_usb_in_curr.usb_type_max = USB_CH_IP_CUR_LVL_0P5; + di->is_aca_rid = 0; + break; + case USB_STAT_ACA_RID_C_HS: + di->max_usb_in_curr.usb_type_max = USB_CH_IP_CUR_LVL_0P9; + di->is_aca_rid = 0; + break; + case USB_STAT_ACA_RID_A: + /* + * Dedicated charger level minus maximum current accessory + * can consume (900mA). Closest level is 500mA + */ + dev_dbg(di->dev, "USB_STAT_ACA_RID_A detected\n"); + di->max_usb_in_curr.usb_type_max = USB_CH_IP_CUR_LVL_0P5; + di->is_aca_rid = 1; + break; + case USB_STAT_ACA_RID_B: + /* + * Dedicated charger level minus 120mA (20mA for ACA and + * 100mA for potential accessory). Closest level is 1300mA + */ + di->max_usb_in_curr.usb_type_max = USB_CH_IP_CUR_LVL_1P3; + dev_dbg(di->dev, "USB Type - 0x%02x MaxCurr: %d", link_status, + di->max_usb_in_curr.usb_type_max); + di->is_aca_rid = 1; + break; + case USB_STAT_HOST_CHG_NM: + di->max_usb_in_curr.usb_type_max = USB_CH_IP_CUR_LVL_0P5; + di->is_aca_rid = 0; + break; + case USB_STAT_DEDICATED_CHG: + di->max_usb_in_curr.usb_type_max = USB_CH_IP_CUR_LVL_1P5; + di->is_aca_rid = 0; + break; + case USB_STAT_ACA_RID_C_HS_CHIRP: + case USB_STAT_ACA_RID_C_NM: + di->max_usb_in_curr.usb_type_max = USB_CH_IP_CUR_LVL_1P5; + di->is_aca_rid = 1; + break; + case USB_STAT_NOT_CONFIGURED: + if (di->vbus_detected) { + di->usb_device_is_unrecognised = true; + dev_dbg(di->dev, "USB Type - Legacy charger.\n"); + di->max_usb_in_curr.usb_type_max = + USB_CH_IP_CUR_LVL_1P5; + break; + } + case USB_STAT_HM_IDGND: + dev_err(di->dev, "USB Type - Charging not allowed\n"); + di->max_usb_in_curr.usb_type_max = USB_CH_IP_CUR_LVL_0P05; + ret = -ENXIO; + break; + case USB_STAT_RESERVED: + if (is_ab8500(di->parent)) { + di->flags.vbus_collapse = true; + dev_err(di->dev, "USB Type - USB_STAT_RESERVED " + "VBUS has collapsed\n"); + ret = -ENXIO; + break; + } else { + dev_dbg(di->dev, "USB Type - Charging not allowed\n"); + di->max_usb_in_curr.usb_type_max = + USB_CH_IP_CUR_LVL_0P05; + dev_dbg(di->dev, "USB Type - 0x%02x MaxCurr: %d", + link_status, + di->max_usb_in_curr.usb_type_max); + ret = -ENXIO; + break; + } + case USB_STAT_CARKIT_1: + case USB_STAT_CARKIT_2: + case USB_STAT_ACA_DOCK_CHARGER: + case USB_STAT_CHARGER_LINE_1: + di->max_usb_in_curr.usb_type_max = USB_CH_IP_CUR_LVL_0P5; + dev_dbg(di->dev, "USB Type - 0x%02x MaxCurr: %d", link_status, + di->max_usb_in_curr.usb_type_max); + break; + case USB_STAT_NOT_VALID_LINK: + dev_err(di->dev, "USB Type invalid - try charging anyway\n"); + di->max_usb_in_curr.usb_type_max = USB_CH_IP_CUR_LVL_0P5; + break; + + default: + dev_err(di->dev, "USB Type - Unknown\n"); + di->max_usb_in_curr.usb_type_max = USB_CH_IP_CUR_LVL_0P05; + ret = -ENXIO; + break; + }; + + di->max_usb_in_curr.set_max = di->max_usb_in_curr.usb_type_max; + dev_dbg(di->dev, "USB Type - 0x%02x MaxCurr: %d", + link_status, di->max_usb_in_curr.set_max); + + return ret; +} + +/** + * ab8500_charger_read_usb_type() - read the type of usb connected + * @di: pointer to the ab8500_charger structure + * + * Detect the type of the plugged USB + * Returns error code in case of failure else 0 on success + */ +static int ab8500_charger_read_usb_type(struct ab8500_charger *di) +{ + int ret; + u8 val; + + ret = abx500_get_register_interruptible(di->dev, + AB8500_INTERRUPT, AB8500_IT_SOURCE21_REG, &val); + if (ret < 0) { + dev_err(di->dev, "%s ab8500 read failed\n", __func__); + return ret; + } + if (is_ab8500(di->parent)) + ret = abx500_get_register_interruptible(di->dev, AB8500_USB, + AB8500_USB_LINE_STAT_REG, &val); + else + ret = abx500_get_register_interruptible(di->dev, + AB8500_USB, AB8500_USB_LINK1_STAT_REG, &val); + if (ret < 0) { + dev_err(di->dev, "%s ab8500 read failed\n", __func__); + return ret; + } + + /* get the USB type */ + if (is_ab8500(di->parent)) + val = (val & AB8500_USB_LINK_STATUS) >> USB_LINK_STATUS_SHIFT; + else + val = (val & AB8505_USB_LINK_STATUS) >> USB_LINK_STATUS_SHIFT; + ret = ab8500_charger_max_usb_curr(di, + (enum ab8500_charger_link_status) val); + + return ret; +} + +/** + * ab8500_charger_detect_usb_type() - get the type of usb connected + * @di: pointer to the ab8500_charger structure + * + * Detect the type of the plugged USB + * Returns error code in case of failure else 0 on success + */ +static int ab8500_charger_detect_usb_type(struct ab8500_charger *di) +{ + int i, ret; + u8 val; + + /* + * On getting the VBUS rising edge detect interrupt there + * is a 250ms delay after which the register UsbLineStatus + * is filled with valid data. + */ + for (i = 0; i < 10; i++) { + msleep(250); + ret = abx500_get_register_interruptible(di->dev, + AB8500_INTERRUPT, AB8500_IT_SOURCE21_REG, + &val); + dev_dbg(di->dev, "%s AB8500_IT_SOURCE21_REG %x\n", + __func__, val); + if (ret < 0) { + dev_err(di->dev, "%s ab8500 read failed\n", __func__); + return ret; + } + + if (is_ab8500(di->parent)) + ret = abx500_get_register_interruptible(di->dev, + AB8500_USB, AB8500_USB_LINE_STAT_REG, &val); + else + ret = abx500_get_register_interruptible(di->dev, + AB8500_USB, AB8500_USB_LINK1_STAT_REG, &val); + if (ret < 0) { + dev_err(di->dev, "%s ab8500 read failed\n", __func__); + return ret; + } + dev_dbg(di->dev, "%s AB8500_USB_LINE_STAT_REG %x\n", __func__, + val); + /* + * Until the IT source register is read the UsbLineStatus + * register is not updated, hence doing the same + * Revisit this: + */ + + /* get the USB type */ + if (is_ab8500(di->parent)) + val = (val & AB8500_USB_LINK_STATUS) >> + USB_LINK_STATUS_SHIFT; + else + val = (val & AB8505_USB_LINK_STATUS) >> + USB_LINK_STATUS_SHIFT; + if (val) + break; + } + ret = ab8500_charger_max_usb_curr(di, + (enum ab8500_charger_link_status) val); + + return ret; +} + +/* + * This array maps the raw hex value to charger voltage used by the AB8500 + * Values taken from the UM0836 + */ +static int ab8500_charger_voltage_map[] = { + 3500 , + 3525 , + 3550 , + 3575 , + 3600 , + 3625 , + 3650 , + 3675 , + 3700 , + 3725 , + 3750 , + 3775 , + 3800 , + 3825 , + 3850 , + 3875 , + 3900 , + 3925 , + 3950 , + 3975 , + 4000 , + 4025 , + 4050 , + 4060 , + 4070 , + 4080 , + 4090 , + 4100 , + 4110 , + 4120 , + 4130 , + 4140 , + 4150 , + 4160 , + 4170 , + 4180 , + 4190 , + 4200 , + 4210 , + 4220 , + 4230 , + 4240 , + 4250 , + 4260 , + 4270 , + 4280 , + 4290 , + 4300 , + 4310 , + 4320 , + 4330 , + 4340 , + 4350 , + 4360 , + 4370 , + 4380 , + 4390 , + 4400 , + 4410 , + 4420 , + 4430 , + 4440 , + 4450 , + 4460 , + 4470 , + 4480 , + 4490 , + 4500 , + 4510 , + 4520 , + 4530 , + 4540 , + 4550 , + 4560 , + 4570 , + 4580 , + 4590 , + 4600 , +}; + +static int ab8500_voltage_to_regval(int voltage) +{ + int i; + + /* Special case for voltage below 3.5V */ + if (voltage < ab8500_charger_voltage_map[0]) + return LOW_VOLT_REG; + + for (i = 1; i < ARRAY_SIZE(ab8500_charger_voltage_map); i++) { + if (voltage < ab8500_charger_voltage_map[i]) + return i - 1; + } + + /* If not last element, return error */ + i = ARRAY_SIZE(ab8500_charger_voltage_map) - 1; + if (voltage == ab8500_charger_voltage_map[i]) + return i; + else + return -1; +} + +static int ab8500_current_to_regval(struct ab8500_charger *di, int curr) +{ + int i; + + if (curr < di->bm->chg_output_curr[0]) + return 0; + + for (i = 0; i < di->bm->n_chg_out_curr; i++) { + if (curr < di->bm->chg_output_curr[i]) + return i - 1; + } + + /* If not last element, return error */ + i = di->bm->n_chg_out_curr - 1; + if (curr == di->bm->chg_output_curr[i]) + return i; + else + return -1; +} + +static int ab8500_vbus_in_curr_to_regval(struct ab8500_charger *di, int curr) +{ + int i; + + if (curr < di->bm->chg_input_curr[0]) + return 0; + + for (i = 0; i < di->bm->n_chg_in_curr; i++) { + if (curr < di->bm->chg_input_curr[i]) + return i - 1; + } + + /* If not last element, return error */ + i = di->bm->n_chg_in_curr - 1; + if (curr == di->bm->chg_input_curr[i]) + return i; + else + return -1; +} + +/** + * ab8500_charger_get_usb_cur() - get usb current + * @di: pointer to the ab8500_charger structre + * + * The usb stack provides the maximum current that can be drawn from + * the standard usb host. This will be in mA. + * This function converts current in mA to a value that can be written + * to the register. Returns -1 if charging is not allowed + */ +static int ab8500_charger_get_usb_cur(struct ab8500_charger *di) +{ + int ret = 0; + switch (di->usb_state.usb_current) { + case 100: + di->max_usb_in_curr.usb_type_max = USB_CH_IP_CUR_LVL_0P09; + break; + case 200: + di->max_usb_in_curr.usb_type_max = USB_CH_IP_CUR_LVL_0P19; + break; + case 300: + di->max_usb_in_curr.usb_type_max = USB_CH_IP_CUR_LVL_0P29; + break; + case 400: + di->max_usb_in_curr.usb_type_max = USB_CH_IP_CUR_LVL_0P38; + break; + case 500: + di->max_usb_in_curr.usb_type_max = USB_CH_IP_CUR_LVL_0P5; + break; + default: + di->max_usb_in_curr.usb_type_max = USB_CH_IP_CUR_LVL_0P05; + ret = -EPERM; + break; + }; + di->max_usb_in_curr.set_max = di->max_usb_in_curr.usb_type_max; + return ret; +} + +/** + * ab8500_charger_check_continue_stepping() - Check to allow stepping + * @di: pointer to the ab8500_charger structure + * @reg: select what charger register to check + * + * Check if current stepping should be allowed to continue. + * Checks if charger source has not collapsed. If it has, further stepping + * is not allowed. + */ +static bool ab8500_charger_check_continue_stepping(struct ab8500_charger *di, + int reg) +{ + if (reg == AB8500_USBCH_IPT_CRNTLVL_REG) + return !di->flags.vbus_drop_end; + else + return true; +} + +/** + * ab8500_charger_set_current() - set charger current + * @di: pointer to the ab8500_charger structure + * @ich: charger current, in mA + * @reg: select what charger register to set + * + * Set charger current. + * There is no state machine in the AB to step up/down the charger + * current to avoid dips and spikes on MAIN, VBUS and VBAT when + * charging is started. Instead we need to implement + * this charger current step-up/down here. + * Returns error code in case of failure else 0(on success) + */ +static int ab8500_charger_set_current(struct ab8500_charger *di, + int ich, int reg) +{ + int ret = 0; + int curr_index, prev_curr_index, shift_value, i; + u8 reg_value; + u32 step_udelay; + bool no_stepping = false; + + atomic_inc(&di->current_stepping_sessions); + + ret = abx500_get_register_interruptible(di->dev, AB8500_CHARGER, + reg, ®_value); + if (ret < 0) { + dev_err(di->dev, "%s read failed\n", __func__); + goto exit_set_current; + } + + switch (reg) { + case AB8500_MCH_IPT_CURLVL_REG: + shift_value = MAIN_CH_INPUT_CURR_SHIFT; + prev_curr_index = (reg_value >> shift_value); + curr_index = ab8500_current_to_regval(di, ich); + step_udelay = STEP_UDELAY; + if (!di->ac.charger_connected) + no_stepping = true; + break; + case AB8500_USBCH_IPT_CRNTLVL_REG: + if (is_ab8540(di->parent)) + shift_value = AB8540_VBUS_IN_CURR_LIM_SHIFT; + else + shift_value = VBUS_IN_CURR_LIM_SHIFT; + prev_curr_index = (reg_value >> shift_value); + curr_index = ab8500_vbus_in_curr_to_regval(di, ich); + step_udelay = STEP_UDELAY * 100; + + if (!di->usb.charger_connected) + no_stepping = true; + break; + case AB8500_CH_OPT_CRNTLVL_REG: + shift_value = 0; + prev_curr_index = (reg_value >> shift_value); + curr_index = ab8500_current_to_regval(di, ich); + step_udelay = STEP_UDELAY; + if (curr_index && (curr_index - prev_curr_index) > 1) + step_udelay *= 100; + + if (!di->usb.charger_connected && !di->ac.charger_connected) + no_stepping = true; + + break; + default: + dev_err(di->dev, "%s current register not valid\n", __func__); + ret = -ENXIO; + goto exit_set_current; + } + + if (curr_index < 0) { + dev_err(di->dev, "requested current limit out-of-range\n"); + ret = -ENXIO; + goto exit_set_current; + } + + /* only update current if it's been changed */ + if (prev_curr_index == curr_index) { + dev_dbg(di->dev, "%s current not changed for reg: 0x%02x\n", + __func__, reg); + ret = 0; + goto exit_set_current; + } + + dev_dbg(di->dev, "%s set charger current: %d mA for reg: 0x%02x\n", + __func__, ich, reg); + + if (no_stepping) { + ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER, + reg, (u8)curr_index << shift_value); + if (ret) + dev_err(di->dev, "%s write failed\n", __func__); + } else if (prev_curr_index > curr_index) { + for (i = prev_curr_index - 1; i >= curr_index; i--) { + dev_dbg(di->dev, "curr change_1 to: %x for 0x%02x\n", + (u8) i << shift_value, reg); + ret = abx500_set_register_interruptible(di->dev, + AB8500_CHARGER, reg, (u8)i << shift_value); + if (ret) { + dev_err(di->dev, "%s write failed\n", __func__); + goto exit_set_current; + } + if (i != curr_index) + usleep_range(step_udelay, step_udelay * 2); + } + } else { + bool allow = true; + for (i = prev_curr_index + 1; i <= curr_index && allow; i++) { + dev_dbg(di->dev, "curr change_2 to: %x for 0x%02x\n", + (u8)i << shift_value, reg); + ret = abx500_set_register_interruptible(di->dev, + AB8500_CHARGER, reg, (u8)i << shift_value); + if (ret) { + dev_err(di->dev, "%s write failed\n", __func__); + goto exit_set_current; + } + if (i != curr_index) + usleep_range(step_udelay, step_udelay * 2); + + allow = ab8500_charger_check_continue_stepping(di, reg); + } + } + +exit_set_current: + atomic_dec(&di->current_stepping_sessions); + + return ret; +} + +/** + * ab8500_charger_set_vbus_in_curr() - set VBUS input current limit + * @di: pointer to the ab8500_charger structure + * @ich_in: charger input current limit + * + * Sets the current that can be drawn from the USB host + * Returns error code in case of failure else 0(on success) + */ +static int ab8500_charger_set_vbus_in_curr(struct ab8500_charger *di, + int ich_in) +{ + int min_value; + int ret; + + /* We should always use to lowest current limit */ + min_value = min(di->bm->chg_params->usb_curr_max, ich_in); + if (di->max_usb_in_curr.set_max > 0) + min_value = min(di->max_usb_in_curr.set_max, min_value); + + if (di->usb_state.usb_current >= 0) + min_value = min(di->usb_state.usb_current, min_value); + + switch (min_value) { + case 100: + if (di->vbat < VBAT_TRESH_IP_CUR_RED) + min_value = USB_CH_IP_CUR_LVL_0P05; + break; + case 500: + if (di->vbat < VBAT_TRESH_IP_CUR_RED) + min_value = USB_CH_IP_CUR_LVL_0P45; + break; + default: + break; + } + + dev_info(di->dev, "VBUS input current limit set to %d mA\n", min_value); + + mutex_lock(&di->usb_ipt_crnt_lock); + ret = ab8500_charger_set_current(di, min_value, + AB8500_USBCH_IPT_CRNTLVL_REG); + mutex_unlock(&di->usb_ipt_crnt_lock); + + return ret; +} + +/** + * ab8500_charger_set_main_in_curr() - set main charger input current + * @di: pointer to the ab8500_charger structure + * @ich_in: input charger current, in mA + * + * Set main charger input current. + * Returns error code in case of failure else 0(on success) + */ +static int ab8500_charger_set_main_in_curr(struct ab8500_charger *di, + int ich_in) +{ + return ab8500_charger_set_current(di, ich_in, + AB8500_MCH_IPT_CURLVL_REG); +} + +/** + * ab8500_charger_set_output_curr() - set charger output current + * @di: pointer to the ab8500_charger structure + * @ich_out: output charger current, in mA + * + * Set charger output current. + * Returns error code in case of failure else 0(on success) + */ +static int ab8500_charger_set_output_curr(struct ab8500_charger *di, + int ich_out) +{ + return ab8500_charger_set_current(di, ich_out, + AB8500_CH_OPT_CRNTLVL_REG); +} + +/** + * ab8500_charger_led_en() - turn on/off chargign led + * @di: pointer to the ab8500_charger structure + * @on: flag to turn on/off the chargign led + * + * Power ON/OFF charging LED indication + * Returns error code in case of failure else 0(on success) + */ +static int ab8500_charger_led_en(struct ab8500_charger *di, int on) +{ + int ret; + + if (on) { + /* Power ON charging LED indicator, set LED current to 5mA */ + ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER, + AB8500_LED_INDICATOR_PWM_CTRL, + (LED_IND_CUR_5MA | LED_INDICATOR_PWM_ENA)); + if (ret) { + dev_err(di->dev, "Power ON LED failed\n"); + return ret; + } + /* LED indicator PWM duty cycle 252/256 */ + ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER, + AB8500_LED_INDICATOR_PWM_DUTY, + LED_INDICATOR_PWM_DUTY_252_256); + if (ret) { + dev_err(di->dev, "Set LED PWM duty cycle failed\n"); + return ret; + } + } else { + /* Power off charging LED indicator */ + ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER, + AB8500_LED_INDICATOR_PWM_CTRL, + LED_INDICATOR_PWM_DIS); + if (ret) { + dev_err(di->dev, "Power-off LED failed\n"); + return ret; + } + } + + return ret; +} + +/** + * ab8500_charger_ac_en() - enable or disable ac charging + * @di: pointer to the ab8500_charger structure + * @enable: enable/disable flag + * @vset: charging voltage + * @iset: charging current + * + * Enable/Disable AC/Mains charging and turns on/off the charging led + * respectively. + **/ +static int ab8500_charger_ac_en(struct ux500_charger *charger, + int enable, int vset, int iset) +{ + int ret; + int volt_index; + int curr_index; + int input_curr_index; + u8 overshoot = 0; + + struct ab8500_charger *di = to_ab8500_charger_ac_device_info(charger); + + if (enable) { + /* Check if AC is connected */ + if (!di->ac.charger_connected) { + dev_err(di->dev, "AC charger not connected\n"); + return -ENXIO; + } + + /* Enable AC charging */ + dev_dbg(di->dev, "Enable AC: %dmV %dmA\n", vset, iset); + + /* + * Due to a bug in AB8500, BTEMP_HIGH/LOW interrupts + * will be triggered everytime we enable the VDD ADC supply. + * This will turn off charging for a short while. + * It can be avoided by having the supply on when + * there is a charger enabled. Normally the VDD ADC supply + * is enabled everytime a GPADC conversion is triggered. We will + * force it to be enabled from this driver to have + * the GPADC module independant of the AB8500 chargers + */ + if (!di->vddadc_en_ac) { + ret = regulator_enable(di->regu); + if (ret) + dev_warn(di->dev, + "Failed to enable regulator\n"); + else + di->vddadc_en_ac = true; + } + + /* Check if the requested voltage or current is valid */ + volt_index = ab8500_voltage_to_regval(vset); + curr_index = ab8500_current_to_regval(di, iset); + input_curr_index = ab8500_current_to_regval(di, + di->bm->chg_params->ac_curr_max); + if (volt_index < 0 || curr_index < 0 || input_curr_index < 0) { + dev_err(di->dev, + "Charger voltage or current too high, " + "charging not started\n"); + return -ENXIO; + } + + /* ChVoltLevel: maximum battery charging voltage */ + ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER, + AB8500_CH_VOLT_LVL_REG, (u8) volt_index); + if (ret) { + dev_err(di->dev, "%s write failed\n", __func__); + return ret; + } + /* MainChInputCurr: current that can be drawn from the charger*/ + ret = ab8500_charger_set_main_in_curr(di, + di->bm->chg_params->ac_curr_max); + if (ret) { + dev_err(di->dev, "%s Failed to set MainChInputCurr\n", + __func__); + return ret; + } + /* ChOutputCurentLevel: protected output current */ + ret = ab8500_charger_set_output_curr(di, iset); + if (ret) { + dev_err(di->dev, "%s " + "Failed to set ChOutputCurentLevel\n", + __func__); + return ret; + } + + /* Check if VBAT overshoot control should be enabled */ + if (!di->bm->enable_overshoot) + overshoot = MAIN_CH_NO_OVERSHOOT_ENA_N; + + /* Enable Main Charger */ + ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER, + AB8500_MCH_CTRL1, MAIN_CH_ENA | overshoot); + if (ret) { + dev_err(di->dev, "%s write failed\n", __func__); + return ret; + } + + /* Power on charging LED indication */ + ret = ab8500_charger_led_en(di, true); + if (ret < 0) + dev_err(di->dev, "failed to enable LED\n"); + + di->ac.charger_online = 1; + } else { + /* Disable AC charging */ + if (is_ab8500_1p1_or_earlier(di->parent)) { + /* + * For ABB revision 1.0 and 1.1 there is a bug in the + * watchdog logic. That means we have to continously + * kick the charger watchdog even when no charger is + * connected. This is only valid once the AC charger + * has been enabled. This is a bug that is not handled + * by the algorithm and the watchdog have to be kicked + * by the charger driver when the AC charger + * is disabled + */ + if (di->ac_conn) { + queue_delayed_work(di->charger_wq, + &di->kick_wd_work, + round_jiffies(WD_KICK_INTERVAL)); + } + + /* + * We can't turn off charging completely + * due to a bug in AB8500 cut1. + * If we do, charging will not start again. + * That is why we set the lowest voltage + * and current possible + */ + ret = abx500_set_register_interruptible(di->dev, + AB8500_CHARGER, + AB8500_CH_VOLT_LVL_REG, CH_VOL_LVL_3P5); + if (ret) { + dev_err(di->dev, + "%s write failed\n", __func__); + return ret; + } + + ret = ab8500_charger_set_output_curr(di, 0); + if (ret) { + dev_err(di->dev, "%s " + "Failed to set ChOutputCurentLevel\n", + __func__); + return ret; + } + } else { + ret = abx500_set_register_interruptible(di->dev, + AB8500_CHARGER, + AB8500_MCH_CTRL1, 0); + if (ret) { + dev_err(di->dev, + "%s write failed\n", __func__); + return ret; + } + } + + ret = ab8500_charger_led_en(di, false); + if (ret < 0) + dev_err(di->dev, "failed to disable LED\n"); + + di->ac.charger_online = 0; + di->ac.wd_expired = false; + + /* Disable regulator if enabled */ + if (di->vddadc_en_ac) { + regulator_disable(di->regu); + di->vddadc_en_ac = false; + } + + dev_dbg(di->dev, "%s Disabled AC charging\n", __func__); + } + ab8500_power_supply_changed(di, di->ac_chg.psy); + + return ret; +} + +/** + * ab8500_charger_usb_en() - enable usb charging + * @di: pointer to the ab8500_charger structure + * @enable: enable/disable flag + * @vset: charging voltage + * @ich_out: charger output current + * + * Enable/Disable USB charging and turns on/off the charging led respectively. + * Returns error code in case of failure else 0(on success) + */ +static int ab8500_charger_usb_en(struct ux500_charger *charger, + int enable, int vset, int ich_out) +{ + int ret; + int volt_index; + int curr_index; + u8 overshoot = 0; + + struct ab8500_charger *di = to_ab8500_charger_usb_device_info(charger); + + if (enable) { + /* Check if USB is connected */ + if (!di->usb.charger_connected) { + dev_err(di->dev, "USB charger not connected\n"); + return -ENXIO; + } + + /* + * Due to a bug in AB8500, BTEMP_HIGH/LOW interrupts + * will be triggered everytime we enable the VDD ADC supply. + * This will turn off charging for a short while. + * It can be avoided by having the supply on when + * there is a charger enabled. Normally the VDD ADC supply + * is enabled everytime a GPADC conversion is triggered. We will + * force it to be enabled from this driver to have + * the GPADC module independant of the AB8500 chargers + */ + if (!di->vddadc_en_usb) { + ret = regulator_enable(di->regu); + if (ret) + dev_warn(di->dev, + "Failed to enable regulator\n"); + else + di->vddadc_en_usb = true; + } + + /* Enable USB charging */ + dev_dbg(di->dev, "Enable USB: %dmV %dmA\n", vset, ich_out); + + /* Check if the requested voltage or current is valid */ + volt_index = ab8500_voltage_to_regval(vset); + curr_index = ab8500_current_to_regval(di, ich_out); + if (volt_index < 0 || curr_index < 0) { + dev_err(di->dev, + "Charger voltage or current too high, " + "charging not started\n"); + return -ENXIO; + } + + /* ChVoltLevel: max voltage upto which battery can be charged */ + ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER, + AB8500_CH_VOLT_LVL_REG, (u8) volt_index); + if (ret) { + dev_err(di->dev, "%s write failed\n", __func__); + return ret; + } + /* Check if VBAT overshoot control should be enabled */ + if (!di->bm->enable_overshoot) + overshoot = USB_CHG_NO_OVERSHOOT_ENA_N; + + /* Enable USB Charger */ + dev_dbg(di->dev, + "Enabling USB with write to AB8500_USBCH_CTRL1_REG\n"); + ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER, + AB8500_USBCH_CTRL1_REG, USB_CH_ENA | overshoot); + if (ret) { + dev_err(di->dev, "%s write failed\n", __func__); + return ret; + } + + /* If success power on charging LED indication */ + ret = ab8500_charger_led_en(di, true); + if (ret < 0) + dev_err(di->dev, "failed to enable LED\n"); + + di->usb.charger_online = 1; + + /* USBChInputCurr: current that can be drawn from the usb */ + ret = ab8500_charger_set_vbus_in_curr(di, + di->max_usb_in_curr.usb_type_max); + if (ret) { + dev_err(di->dev, "setting USBChInputCurr failed\n"); + return ret; + } + + /* ChOutputCurentLevel: protected output current */ + ret = ab8500_charger_set_output_curr(di, ich_out); + if (ret) { + dev_err(di->dev, "%s " + "Failed to set ChOutputCurentLevel\n", + __func__); + return ret; + } + + queue_delayed_work(di->charger_wq, &di->check_vbat_work, HZ); + + } else { + /* Disable USB charging */ + dev_dbg(di->dev, "%s Disabled USB charging\n", __func__); + ret = abx500_set_register_interruptible(di->dev, + AB8500_CHARGER, + AB8500_USBCH_CTRL1_REG, 0); + if (ret) { + dev_err(di->dev, + "%s write failed\n", __func__); + return ret; + } + + ret = ab8500_charger_led_en(di, false); + if (ret < 0) + dev_err(di->dev, "failed to disable LED\n"); + /* USBChInputCurr: current that can be drawn from the usb */ + ret = ab8500_charger_set_vbus_in_curr(di, 0); + if (ret) { + dev_err(di->dev, "setting USBChInputCurr failed\n"); + return ret; + } + + /* ChOutputCurentLevel: protected output current */ + ret = ab8500_charger_set_output_curr(di, 0); + if (ret) { + dev_err(di->dev, "%s " + "Failed to reset ChOutputCurentLevel\n", + __func__); + return ret; + } + di->usb.charger_online = 0; + di->usb.wd_expired = false; + + /* Disable regulator if enabled */ + if (di->vddadc_en_usb) { + regulator_disable(di->regu); + di->vddadc_en_usb = false; + } + + dev_dbg(di->dev, "%s Disabled USB charging\n", __func__); + + /* Cancel any pending Vbat check work */ + cancel_delayed_work(&di->check_vbat_work); + + } + ab8500_power_supply_changed(di, di->usb_chg.psy); + + return ret; +} + +static int ab8500_external_charger_prepare(struct notifier_block *charger_nb, + unsigned long event, void *data) +{ + int ret; + struct device *dev = data; + /*Toggle External charger control pin*/ + ret = abx500_set_register_interruptible(dev, AB8500_SYS_CTRL1_BLOCK, + AB8500_SYS_CHARGER_CONTROL_REG, + EXTERNAL_CHARGER_DISABLE_REG_VAL); + if (ret < 0) { + dev_err(dev, "write reg failed %d\n", ret); + goto out; + } + ret = abx500_set_register_interruptible(dev, AB8500_SYS_CTRL1_BLOCK, + AB8500_SYS_CHARGER_CONTROL_REG, + EXTERNAL_CHARGER_ENABLE_REG_VAL); + if (ret < 0) + dev_err(dev, "Write reg failed %d\n", ret); + +out: + return ret; +} + +/** + * ab8500_charger_usb_check_enable() - enable usb charging + * @charger: pointer to the ux500_charger structure + * @vset: charging voltage + * @iset: charger output current + * + * Check if the VBUS charger has been disconnected and reconnected without + * AB8500 rising an interrupt. Returns 0 on success. + */ +static int ab8500_charger_usb_check_enable(struct ux500_charger *charger, + int vset, int iset) +{ + u8 usbch_ctrl1 = 0; + int ret = 0; + + struct ab8500_charger *di = to_ab8500_charger_usb_device_info(charger); + + if (!di->usb.charger_connected) + return ret; + + ret = abx500_get_register_interruptible(di->dev, AB8500_CHARGER, + AB8500_USBCH_CTRL1_REG, &usbch_ctrl1); + if (ret < 0) { + dev_err(di->dev, "ab8500 read failed %d\n", __LINE__); + return ret; + } + dev_dbg(di->dev, "USB charger ctrl: 0x%02x\n", usbch_ctrl1); + + if (!(usbch_ctrl1 & USB_CH_ENA)) { + dev_info(di->dev, "Charging has been disabled abnormally and will be re-enabled\n"); + + ret = abx500_mask_and_set_register_interruptible(di->dev, + AB8500_CHARGER, AB8500_CHARGER_CTRL, + DROP_COUNT_RESET, DROP_COUNT_RESET); + if (ret < 0) { + dev_err(di->dev, "ab8500 write failed %d\n", __LINE__); + return ret; + } + + ret = ab8500_charger_usb_en(&di->usb_chg, true, vset, iset); + if (ret < 0) { + dev_err(di->dev, "Failed to enable VBUS charger %d\n", + __LINE__); + return ret; + } + } + return ret; +} + +/** + * ab8500_charger_ac_check_enable() - enable usb charging + * @charger: pointer to the ux500_charger structure + * @vset: charging voltage + * @iset: charger output current + * + * Check if the AC charger has been disconnected and reconnected without + * AB8500 rising an interrupt. Returns 0 on success. + */ +static int ab8500_charger_ac_check_enable(struct ux500_charger *charger, + int vset, int iset) +{ + u8 mainch_ctrl1 = 0; + int ret = 0; + + struct ab8500_charger *di = to_ab8500_charger_ac_device_info(charger); + + if (!di->ac.charger_connected) + return ret; + + ret = abx500_get_register_interruptible(di->dev, AB8500_CHARGER, + AB8500_MCH_CTRL1, &mainch_ctrl1); + if (ret < 0) { + dev_err(di->dev, "ab8500 read failed %d\n", __LINE__); + return ret; + } + dev_dbg(di->dev, "AC charger ctrl: 0x%02x\n", mainch_ctrl1); + + if (!(mainch_ctrl1 & MAIN_CH_ENA)) { + dev_info(di->dev, "Charging has been disabled abnormally and will be re-enabled\n"); + + ret = abx500_mask_and_set_register_interruptible(di->dev, + AB8500_CHARGER, AB8500_CHARGER_CTRL, + DROP_COUNT_RESET, DROP_COUNT_RESET); + + if (ret < 0) { + dev_err(di->dev, "ab8500 write failed %d\n", __LINE__); + return ret; + } + + ret = ab8500_charger_ac_en(&di->usb_chg, true, vset, iset); + if (ret < 0) { + dev_err(di->dev, "failed to enable AC charger %d\n", + __LINE__); + return ret; + } + } + return ret; +} + +/** + * ab8500_charger_watchdog_kick() - kick charger watchdog + * @di: pointer to the ab8500_charger structure + * + * Kick charger watchdog + * Returns error code in case of failure else 0(on success) + */ +static int ab8500_charger_watchdog_kick(struct ux500_charger *charger) +{ + int ret; + struct ab8500_charger *di; + + if (charger->psy->desc->type == POWER_SUPPLY_TYPE_MAINS) + di = to_ab8500_charger_ac_device_info(charger); + else if (charger->psy->desc->type == POWER_SUPPLY_TYPE_USB) + di = to_ab8500_charger_usb_device_info(charger); + else + return -ENXIO; + + ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER, + AB8500_CHARG_WD_CTRL, CHARG_WD_KICK); + if (ret) + dev_err(di->dev, "Failed to kick WD!\n"); + + return ret; +} + +/** + * ab8500_charger_update_charger_current() - update charger current + * @di: pointer to the ab8500_charger structure + * + * Update the charger output current for the specified charger + * Returns error code in case of failure else 0(on success) + */ +static int ab8500_charger_update_charger_current(struct ux500_charger *charger, + int ich_out) +{ + int ret; + struct ab8500_charger *di; + + if (charger->psy->desc->type == POWER_SUPPLY_TYPE_MAINS) + di = to_ab8500_charger_ac_device_info(charger); + else if (charger->psy->desc->type == POWER_SUPPLY_TYPE_USB) + di = to_ab8500_charger_usb_device_info(charger); + else + return -ENXIO; + + ret = ab8500_charger_set_output_curr(di, ich_out); + if (ret) { + dev_err(di->dev, "%s " + "Failed to set ChOutputCurentLevel\n", + __func__); + return ret; + } + + /* Reset the main and usb drop input current measurement counter */ + ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER, + AB8500_CHARGER_CTRL, DROP_COUNT_RESET); + if (ret) { + dev_err(di->dev, "%s write failed\n", __func__); + return ret; + } + + return ret; +} + +/** + * ab8540_charger_power_path_enable() - enable usb power path mode + * @charger: pointer to the ux500_charger structure + * @enable: enable/disable flag + * + * Enable or disable the power path for usb mode + * Returns error code in case of failure else 0(on success) + */ +static int ab8540_charger_power_path_enable(struct ux500_charger *charger, + bool enable) +{ + int ret; + struct ab8500_charger *di; + + if (charger->psy->desc->type == POWER_SUPPLY_TYPE_USB) + di = to_ab8500_charger_usb_device_info(charger); + else + return -ENXIO; + + ret = abx500_mask_and_set_register_interruptible(di->dev, + AB8500_CHARGER, AB8540_USB_PP_MODE_REG, + BUS_POWER_PATH_MODE_ENA, enable); + if (ret) { + dev_err(di->dev, "%s write failed\n", __func__); + return ret; + } + + return ret; +} + + +/** + * ab8540_charger_usb_pre_chg_enable() - enable usb pre change + * @charger: pointer to the ux500_charger structure + * @enable: enable/disable flag + * + * Enable or disable the pre-chage for usb mode + * Returns error code in case of failure else 0(on success) + */ +static int ab8540_charger_usb_pre_chg_enable(struct ux500_charger *charger, + bool enable) +{ + int ret; + struct ab8500_charger *di; + + if (charger->psy->desc->type == POWER_SUPPLY_TYPE_USB) + di = to_ab8500_charger_usb_device_info(charger); + else + return -ENXIO; + + ret = abx500_mask_and_set_register_interruptible(di->dev, + AB8500_CHARGER, AB8540_USB_PP_CHR_REG, + BUS_POWER_PATH_PRECHG_ENA, enable); + if (ret) { + dev_err(di->dev, "%s write failed\n", __func__); + return ret; + } + + return ret; +} + +static int ab8500_charger_get_ext_psy_data(struct device *dev, void *data) +{ + struct power_supply *psy; + struct power_supply *ext = dev_get_drvdata(dev); + const char **supplicants = (const char **)ext->supplied_to; + struct ab8500_charger *di; + union power_supply_propval ret; + int j; + struct ux500_charger *usb_chg; + + usb_chg = (struct ux500_charger *)data; + psy = usb_chg->psy; + + di = to_ab8500_charger_usb_device_info(usb_chg); + + /* For all psy where the driver name appears in any supplied_to */ + j = match_string(supplicants, ext->num_supplicants, psy->desc->name); + if (j < 0) + return 0; + + /* Go through all properties for the psy */ + for (j = 0; j < ext->desc->num_properties; j++) { + enum power_supply_property prop; + prop = ext->desc->properties[j]; + + if (power_supply_get_property(ext, prop, &ret)) + continue; + + switch (prop) { + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + switch (ext->desc->type) { + case POWER_SUPPLY_TYPE_BATTERY: + di->vbat = ret.intval / 1000; + break; + default: + break; + } + break; + default: + break; + } + } + return 0; +} + +/** + * ab8500_charger_check_vbat_work() - keep vbus current within spec + * @work pointer to the work_struct structure + * + * Due to a asic bug it is necessary to lower the input current to the vbus + * charger when charging with at some specific levels. This issue is only valid + * for below a certain battery voltage. This function makes sure that the + * the allowed current limit isn't exceeded. + */ +static void ab8500_charger_check_vbat_work(struct work_struct *work) +{ + int t = 10; + struct ab8500_charger *di = container_of(work, + struct ab8500_charger, check_vbat_work.work); + + class_for_each_device(power_supply_class, NULL, + di->usb_chg.psy, ab8500_charger_get_ext_psy_data); + + /* First run old_vbat is 0. */ + if (di->old_vbat == 0) + di->old_vbat = di->vbat; + + if (!((di->old_vbat <= VBAT_TRESH_IP_CUR_RED && + di->vbat <= VBAT_TRESH_IP_CUR_RED) || + (di->old_vbat > VBAT_TRESH_IP_CUR_RED && + di->vbat > VBAT_TRESH_IP_CUR_RED))) { + + dev_dbg(di->dev, "Vbat did cross threshold, curr: %d, new: %d," + " old: %d\n", di->max_usb_in_curr.usb_type_max, + di->vbat, di->old_vbat); + ab8500_charger_set_vbus_in_curr(di, + di->max_usb_in_curr.usb_type_max); + power_supply_changed(di->usb_chg.psy); + } + + di->old_vbat = di->vbat; + + /* + * No need to check the battery voltage every second when not close to + * the threshold. + */ + if (di->vbat < (VBAT_TRESH_IP_CUR_RED + 100) && + (di->vbat > (VBAT_TRESH_IP_CUR_RED - 100))) + t = 1; + + queue_delayed_work(di->charger_wq, &di->check_vbat_work, t * HZ); +} + +/** + * ab8500_charger_check_hw_failure_work() - check main charger failure + * @work: pointer to the work_struct structure + * + * Work queue function for checking the main charger status + */ +static void ab8500_charger_check_hw_failure_work(struct work_struct *work) +{ + int ret; + u8 reg_value; + + struct ab8500_charger *di = container_of(work, + struct ab8500_charger, check_hw_failure_work.work); + + /* Check if the status bits for HW failure is still active */ + if (di->flags.mainextchnotok) { + ret = abx500_get_register_interruptible(di->dev, + AB8500_CHARGER, AB8500_CH_STATUS2_REG, ®_value); + if (ret < 0) { + dev_err(di->dev, "%s ab8500 read failed\n", __func__); + return; + } + if (!(reg_value & MAIN_CH_NOK)) { + di->flags.mainextchnotok = false; + ab8500_power_supply_changed(di, di->ac_chg.psy); + } + } + if (di->flags.vbus_ovv) { + ret = abx500_get_register_interruptible(di->dev, + AB8500_CHARGER, AB8500_CH_USBCH_STAT2_REG, + ®_value); + if (ret < 0) { + dev_err(di->dev, "%s ab8500 read failed\n", __func__); + return; + } + if (!(reg_value & VBUS_OVV_TH)) { + di->flags.vbus_ovv = false; + ab8500_power_supply_changed(di, di->usb_chg.psy); + } + } + /* If we still have a failure, schedule a new check */ + if (di->flags.mainextchnotok || di->flags.vbus_ovv) { + queue_delayed_work(di->charger_wq, + &di->check_hw_failure_work, round_jiffies(HZ)); + } +} + +/** + * ab8500_charger_kick_watchdog_work() - kick the watchdog + * @work: pointer to the work_struct structure + * + * Work queue function for kicking the charger watchdog. + * + * For ABB revision 1.0 and 1.1 there is a bug in the watchdog + * logic. That means we have to continously kick the charger + * watchdog even when no charger is connected. This is only + * valid once the AC charger has been enabled. This is + * a bug that is not handled by the algorithm and the + * watchdog have to be kicked by the charger driver + * when the AC charger is disabled + */ +static void ab8500_charger_kick_watchdog_work(struct work_struct *work) +{ + int ret; + + struct ab8500_charger *di = container_of(work, + struct ab8500_charger, kick_wd_work.work); + + ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER, + AB8500_CHARG_WD_CTRL, CHARG_WD_KICK); + if (ret) + dev_err(di->dev, "Failed to kick WD!\n"); + + /* Schedule a new watchdog kick */ + queue_delayed_work(di->charger_wq, + &di->kick_wd_work, round_jiffies(WD_KICK_INTERVAL)); +} + +/** + * ab8500_charger_ac_work() - work to get and set main charger status + * @work: pointer to the work_struct structure + * + * Work queue function for checking the main charger status + */ +static void ab8500_charger_ac_work(struct work_struct *work) +{ + int ret; + + struct ab8500_charger *di = container_of(work, + struct ab8500_charger, ac_work); + + /* + * Since we can't be sure that the events are received + * synchronously, we have the check if the main charger is + * connected by reading the status register + */ + ret = ab8500_charger_detect_chargers(di, false); + if (ret < 0) + return; + + if (ret & AC_PW_CONN) { + di->ac.charger_connected = 1; + di->ac_conn = true; + } else { + di->ac.charger_connected = 0; + } + + ab8500_power_supply_changed(di, di->ac_chg.psy); + sysfs_notify(&di->ac_chg.psy->dev.kobj, NULL, "present"); +} + +static void ab8500_charger_usb_attached_work(struct work_struct *work) +{ + struct ab8500_charger *di = container_of(work, + struct ab8500_charger, + usb_charger_attached_work.work); + int usbch = (USB_CH_VBUSDROP | USB_CH_VBUSDETDBNC); + int ret, i; + u8 statval; + + for (i = 0; i < 10; i++) { + ret = abx500_get_register_interruptible(di->dev, + AB8500_CHARGER, + AB8500_CH_USBCH_STAT1_REG, + &statval); + if (ret < 0) { + dev_err(di->dev, "ab8500 read failed %d\n", __LINE__); + goto reschedule; + } + if ((statval & usbch) != usbch) + goto reschedule; + + msleep(CHARGER_STATUS_POLL); + } + + ab8500_charger_usb_en(&di->usb_chg, 0, 0, 0); + + mutex_lock(&di->charger_attached_mutex); + mutex_unlock(&di->charger_attached_mutex); + + return; + +reschedule: + queue_delayed_work(di->charger_wq, + &di->usb_charger_attached_work, + HZ); +} + +static void ab8500_charger_ac_attached_work(struct work_struct *work) +{ + + struct ab8500_charger *di = container_of(work, + struct ab8500_charger, + ac_charger_attached_work.work); + int mainch = (MAIN_CH_STATUS2_MAINCHGDROP | + MAIN_CH_STATUS2_MAINCHARGERDETDBNC); + int ret, i; + u8 statval; + + for (i = 0; i < 10; i++) { + ret = abx500_get_register_interruptible(di->dev, + AB8500_CHARGER, + AB8500_CH_STATUS2_REG, + &statval); + if (ret < 0) { + dev_err(di->dev, "ab8500 read failed %d\n", __LINE__); + goto reschedule; + } + + if ((statval & mainch) != mainch) + goto reschedule; + + msleep(CHARGER_STATUS_POLL); + } + + ab8500_charger_ac_en(&di->ac_chg, 0, 0, 0); + queue_work(di->charger_wq, &di->ac_work); + + mutex_lock(&di->charger_attached_mutex); + mutex_unlock(&di->charger_attached_mutex); + + return; + +reschedule: + queue_delayed_work(di->charger_wq, + &di->ac_charger_attached_work, + HZ); +} + +/** + * ab8500_charger_detect_usb_type_work() - work to detect USB type + * @work: Pointer to the work_struct structure + * + * Detect the type of USB plugged + */ +static void ab8500_charger_detect_usb_type_work(struct work_struct *work) +{ + int ret; + + struct ab8500_charger *di = container_of(work, + struct ab8500_charger, detect_usb_type_work); + + /* + * Since we can't be sure that the events are received + * synchronously, we have the check if is + * connected by reading the status register + */ + ret = ab8500_charger_detect_chargers(di, false); + if (ret < 0) + return; + + if (!(ret & USB_PW_CONN)) { + dev_dbg(di->dev, "%s di->vbus_detected = false\n", __func__); + di->vbus_detected = false; + ab8500_charger_set_usb_connected(di, false); + ab8500_power_supply_changed(di, di->usb_chg.psy); + } else { + dev_dbg(di->dev, "%s di->vbus_detected = true\n", __func__); + di->vbus_detected = true; + + if (is_ab8500_1p1_or_earlier(di->parent)) { + ret = ab8500_charger_detect_usb_type(di); + if (!ret) { + ab8500_charger_set_usb_connected(di, true); + ab8500_power_supply_changed(di, + di->usb_chg.psy); + } + } else { + /* + * For ABB cut2.0 and onwards we have an IRQ, + * USB_LINK_STATUS that will be triggered when the USB + * link status changes. The exception is USB connected + * during startup. Then we don't get a + * USB_LINK_STATUS IRQ + */ + if (di->vbus_detected_start) { + di->vbus_detected_start = false; + ret = ab8500_charger_detect_usb_type(di); + if (!ret) { + ab8500_charger_set_usb_connected(di, + true); + ab8500_power_supply_changed(di, + di->usb_chg.psy); + } + } + } + } +} + +/** + * ab8500_charger_usb_link_attach_work() - work to detect USB type + * @work: pointer to the work_struct structure + * + * Detect the type of USB plugged + */ +static void ab8500_charger_usb_link_attach_work(struct work_struct *work) +{ + struct ab8500_charger *di = + container_of(work, struct ab8500_charger, attach_work.work); + int ret; + + /* Update maximum input current if USB enumeration is not detected */ + if (!di->usb.charger_online) { + ret = ab8500_charger_set_vbus_in_curr(di, + di->max_usb_in_curr.usb_type_max); + if (ret) + return; + } + + ab8500_charger_set_usb_connected(di, true); + ab8500_power_supply_changed(di, di->usb_chg.psy); +} + +/** + * ab8500_charger_usb_link_status_work() - work to detect USB type + * @work: pointer to the work_struct structure + * + * Detect the type of USB plugged + */ +static void ab8500_charger_usb_link_status_work(struct work_struct *work) +{ + int detected_chargers; + int ret; + u8 val; + u8 link_status; + + struct ab8500_charger *di = container_of(work, + struct ab8500_charger, usb_link_status_work); + + /* + * Since we can't be sure that the events are received + * synchronously, we have the check if is + * connected by reading the status register + */ + detected_chargers = ab8500_charger_detect_chargers(di, false); + if (detected_chargers < 0) + return; + + /* + * Some chargers that breaks the USB spec is + * identified as invalid by AB8500 and it refuse + * to start the charging process. but by jumping + * thru a few hoops it can be forced to start. + */ + if (is_ab8500(di->parent)) + ret = abx500_get_register_interruptible(di->dev, AB8500_USB, + AB8500_USB_LINE_STAT_REG, &val); + else + ret = abx500_get_register_interruptible(di->dev, AB8500_USB, + AB8500_USB_LINK1_STAT_REG, &val); + + if (ret >= 0) + dev_dbg(di->dev, "UsbLineStatus register = 0x%02x\n", val); + else + dev_dbg(di->dev, "Error reading USB link status\n"); + + if (is_ab8500(di->parent)) + link_status = AB8500_USB_LINK_STATUS; + else + link_status = AB8505_USB_LINK_STATUS; + + if (detected_chargers & USB_PW_CONN) { + if (((val & link_status) >> USB_LINK_STATUS_SHIFT) == + USB_STAT_NOT_VALID_LINK && + di->invalid_charger_detect_state == 0) { + dev_dbg(di->dev, + "Invalid charger detected, state= 0\n"); + /*Enable charger*/ + abx500_mask_and_set_register_interruptible(di->dev, + AB8500_CHARGER, AB8500_USBCH_CTRL1_REG, + USB_CH_ENA, USB_CH_ENA); + /*Enable charger detection*/ + abx500_mask_and_set_register_interruptible(di->dev, + AB8500_USB, AB8500_USB_LINE_CTRL2_REG, + USB_CH_DET, USB_CH_DET); + di->invalid_charger_detect_state = 1; + /*exit and wait for new link status interrupt.*/ + return; + + } + if (di->invalid_charger_detect_state == 1) { + dev_dbg(di->dev, + "Invalid charger detected, state= 1\n"); + /*Stop charger detection*/ + abx500_mask_and_set_register_interruptible(di->dev, + AB8500_USB, AB8500_USB_LINE_CTRL2_REG, + USB_CH_DET, 0x00); + /*Check link status*/ + if (is_ab8500(di->parent)) + ret = abx500_get_register_interruptible(di->dev, + AB8500_USB, AB8500_USB_LINE_STAT_REG, + &val); + else + ret = abx500_get_register_interruptible(di->dev, + AB8500_USB, AB8500_USB_LINK1_STAT_REG, + &val); + + dev_dbg(di->dev, "USB link status= 0x%02x\n", + (val & link_status) >> USB_LINK_STATUS_SHIFT); + di->invalid_charger_detect_state = 2; + } + } else { + di->invalid_charger_detect_state = 0; + } + + if (!(detected_chargers & USB_PW_CONN)) { + di->vbus_detected = false; + ab8500_charger_set_usb_connected(di, false); + ab8500_power_supply_changed(di, di->usb_chg.psy); + return; + } + + dev_dbg(di->dev,"%s di->vbus_detected = true\n",__func__); + di->vbus_detected = true; + ret = ab8500_charger_read_usb_type(di); + if (ret) { + if (ret == -ENXIO) { + /* No valid charger type detected */ + ab8500_charger_set_usb_connected(di, false); + ab8500_power_supply_changed(di, di->usb_chg.psy); + } + return; + } + + if (di->usb_device_is_unrecognised) { + dev_dbg(di->dev, + "Potential Legacy Charger device. " + "Delay work for %d msec for USB enum " + "to finish", + WAIT_ACA_RID_ENUMERATION); + queue_delayed_work(di->charger_wq, + &di->attach_work, + msecs_to_jiffies(WAIT_ACA_RID_ENUMERATION)); + } else if (di->is_aca_rid == 1) { + /* Only wait once */ + di->is_aca_rid++; + dev_dbg(di->dev, + "%s Wait %d msec for USB enum to finish", + __func__, WAIT_ACA_RID_ENUMERATION); + queue_delayed_work(di->charger_wq, + &di->attach_work, + msecs_to_jiffies(WAIT_ACA_RID_ENUMERATION)); + } else { + queue_delayed_work(di->charger_wq, + &di->attach_work, + 0); + } +} + +static void ab8500_charger_usb_state_changed_work(struct work_struct *work) +{ + int ret; + unsigned long flags; + + struct ab8500_charger *di = container_of(work, + struct ab8500_charger, usb_state_changed_work.work); + + if (!di->vbus_detected) { + dev_dbg(di->dev, + "%s !di->vbus_detected\n", + __func__); + return; + } + + spin_lock_irqsave(&di->usb_state.usb_lock, flags); + di->usb_state.state = di->usb_state.state_tmp; + di->usb_state.usb_current = di->usb_state.usb_current_tmp; + spin_unlock_irqrestore(&di->usb_state.usb_lock, flags); + + dev_dbg(di->dev, "%s USB state: 0x%02x mA: %d\n", + __func__, di->usb_state.state, di->usb_state.usb_current); + + switch (di->usb_state.state) { + case AB8500_BM_USB_STATE_RESET_HS: + case AB8500_BM_USB_STATE_RESET_FS: + case AB8500_BM_USB_STATE_SUSPEND: + case AB8500_BM_USB_STATE_MAX: + ab8500_charger_set_usb_connected(di, false); + ab8500_power_supply_changed(di, di->usb_chg.psy); + break; + + case AB8500_BM_USB_STATE_RESUME: + /* + * when suspend->resume there should be delay + * of 1sec for enabling charging + */ + msleep(1000); + /* Intentional fall through */ + case AB8500_BM_USB_STATE_CONFIGURED: + /* + * USB is configured, enable charging with the charging + * input current obtained from USB driver + */ + if (!ab8500_charger_get_usb_cur(di)) { + /* Update maximum input current */ + ret = ab8500_charger_set_vbus_in_curr(di, + di->max_usb_in_curr.usb_type_max); + if (ret) + return; + + ab8500_charger_set_usb_connected(di, true); + ab8500_power_supply_changed(di, di->usb_chg.psy); + } + break; + + default: + break; + }; +} + +/** + * ab8500_charger_check_usbchargernotok_work() - check USB chg not ok status + * @work: pointer to the work_struct structure + * + * Work queue function for checking the USB charger Not OK status + */ +static void ab8500_charger_check_usbchargernotok_work(struct work_struct *work) +{ + int ret; + u8 reg_value; + bool prev_status; + + struct ab8500_charger *di = container_of(work, + struct ab8500_charger, check_usbchgnotok_work.work); + + /* Check if the status bit for usbchargernotok is still active */ + ret = abx500_get_register_interruptible(di->dev, + AB8500_CHARGER, AB8500_CH_USBCH_STAT2_REG, ®_value); + if (ret < 0) { + dev_err(di->dev, "%s ab8500 read failed\n", __func__); + return; + } + prev_status = di->flags.usbchargernotok; + + if (reg_value & VBUS_CH_NOK) { + di->flags.usbchargernotok = true; + /* Check again in 1sec */ + queue_delayed_work(di->charger_wq, + &di->check_usbchgnotok_work, HZ); + } else { + di->flags.usbchargernotok = false; + di->flags.vbus_collapse = false; + } + + if (prev_status != di->flags.usbchargernotok) + ab8500_power_supply_changed(di, di->usb_chg.psy); +} + +/** + * ab8500_charger_check_main_thermal_prot_work() - check main thermal status + * @work: pointer to the work_struct structure + * + * Work queue function for checking the Main thermal prot status + */ +static void ab8500_charger_check_main_thermal_prot_work( + struct work_struct *work) +{ + int ret; + u8 reg_value; + + struct ab8500_charger *di = container_of(work, + struct ab8500_charger, check_main_thermal_prot_work); + + /* Check if the status bit for main_thermal_prot is still active */ + ret = abx500_get_register_interruptible(di->dev, + AB8500_CHARGER, AB8500_CH_STATUS2_REG, ®_value); + if (ret < 0) { + dev_err(di->dev, "%s ab8500 read failed\n", __func__); + return; + } + if (reg_value & MAIN_CH_TH_PROT) + di->flags.main_thermal_prot = true; + else + di->flags.main_thermal_prot = false; + + ab8500_power_supply_changed(di, di->ac_chg.psy); +} + +/** + * ab8500_charger_check_usb_thermal_prot_work() - check usb thermal status + * @work: pointer to the work_struct structure + * + * Work queue function for checking the USB thermal prot status + */ +static void ab8500_charger_check_usb_thermal_prot_work( + struct work_struct *work) +{ + int ret; + u8 reg_value; + + struct ab8500_charger *di = container_of(work, + struct ab8500_charger, check_usb_thermal_prot_work); + + /* Check if the status bit for usb_thermal_prot is still active */ + ret = abx500_get_register_interruptible(di->dev, + AB8500_CHARGER, AB8500_CH_USBCH_STAT2_REG, ®_value); + if (ret < 0) { + dev_err(di->dev, "%s ab8500 read failed\n", __func__); + return; + } + if (reg_value & USB_CH_TH_PROT) + di->flags.usb_thermal_prot = true; + else + di->flags.usb_thermal_prot = false; + + ab8500_power_supply_changed(di, di->usb_chg.psy); +} + +/** + * ab8500_charger_mainchunplugdet_handler() - main charger unplugged + * @irq: interrupt number + * @_di: pointer to the ab8500_charger structure + * + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab8500_charger_mainchunplugdet_handler(int irq, void *_di) +{ + struct ab8500_charger *di = _di; + + dev_dbg(di->dev, "Main charger unplugged\n"); + queue_work(di->charger_wq, &di->ac_work); + + cancel_delayed_work_sync(&di->ac_charger_attached_work); + mutex_lock(&di->charger_attached_mutex); + mutex_unlock(&di->charger_attached_mutex); + + return IRQ_HANDLED; +} + +/** + * ab8500_charger_mainchplugdet_handler() - main charger plugged + * @irq: interrupt number + * @_di: pointer to the ab8500_charger structure + * + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab8500_charger_mainchplugdet_handler(int irq, void *_di) +{ + struct ab8500_charger *di = _di; + + dev_dbg(di->dev, "Main charger plugged\n"); + queue_work(di->charger_wq, &di->ac_work); + + mutex_lock(&di->charger_attached_mutex); + mutex_unlock(&di->charger_attached_mutex); + + if (is_ab8500(di->parent)) + queue_delayed_work(di->charger_wq, + &di->ac_charger_attached_work, + HZ); + return IRQ_HANDLED; +} + +/** + * ab8500_charger_mainextchnotok_handler() - main charger not ok + * @irq: interrupt number + * @_di: pointer to the ab8500_charger structure + * + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab8500_charger_mainextchnotok_handler(int irq, void *_di) +{ + struct ab8500_charger *di = _di; + + dev_dbg(di->dev, "Main charger not ok\n"); + di->flags.mainextchnotok = true; + ab8500_power_supply_changed(di, di->ac_chg.psy); + + /* Schedule a new HW failure check */ + queue_delayed_work(di->charger_wq, &di->check_hw_failure_work, 0); + + return IRQ_HANDLED; +} + +/** + * ab8500_charger_mainchthprotr_handler() - Die temp is above main charger + * thermal protection threshold + * @irq: interrupt number + * @_di: pointer to the ab8500_charger structure + * + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab8500_charger_mainchthprotr_handler(int irq, void *_di) +{ + struct ab8500_charger *di = _di; + + dev_dbg(di->dev, + "Die temp above Main charger thermal protection threshold\n"); + queue_work(di->charger_wq, &di->check_main_thermal_prot_work); + + return IRQ_HANDLED; +} + +/** + * ab8500_charger_mainchthprotf_handler() - Die temp is below main charger + * thermal protection threshold + * @irq: interrupt number + * @_di: pointer to the ab8500_charger structure + * + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab8500_charger_mainchthprotf_handler(int irq, void *_di) +{ + struct ab8500_charger *di = _di; + + dev_dbg(di->dev, + "Die temp ok for Main charger thermal protection threshold\n"); + queue_work(di->charger_wq, &di->check_main_thermal_prot_work); + + return IRQ_HANDLED; +} + +static void ab8500_charger_vbus_drop_end_work(struct work_struct *work) +{ + struct ab8500_charger *di = container_of(work, + struct ab8500_charger, vbus_drop_end_work.work); + int ret, curr; + u8 reg_value; + + di->flags.vbus_drop_end = false; + + /* Reset the drop counter */ + abx500_set_register_interruptible(di->dev, + AB8500_CHARGER, AB8500_CHARGER_CTRL, 0x01); + + if (is_ab8540(di->parent)) + ret = abx500_get_register_interruptible(di->dev, AB8500_CHARGER, + AB8540_CH_USBCH_STAT3_REG, ®_value); + else + ret = abx500_get_register_interruptible(di->dev, AB8500_CHARGER, + AB8500_CH_USBCH_STAT2_REG, ®_value); + if (ret < 0) { + dev_err(di->dev, "%s read failed\n", __func__); + return; + } + + if (is_ab8540(di->parent)) + curr = di->bm->chg_input_curr[ + reg_value & AB8540_AUTO_VBUS_IN_CURR_MASK]; + else + curr = di->bm->chg_input_curr[ + reg_value >> AUTO_VBUS_IN_CURR_LIM_SHIFT]; + + if (di->max_usb_in_curr.calculated_max != curr) { + /* USB source is collapsing */ + di->max_usb_in_curr.calculated_max = curr; + dev_dbg(di->dev, + "VBUS input current limiting to %d mA\n", + di->max_usb_in_curr.calculated_max); + } else { + /* + * USB source can not give more than this amount. + * Taking more will collapse the source. + */ + di->max_usb_in_curr.set_max = + di->max_usb_in_curr.calculated_max; + dev_dbg(di->dev, + "VBUS input current limited to %d mA\n", + di->max_usb_in_curr.set_max); + } + + if (di->usb.charger_connected) + ab8500_charger_set_vbus_in_curr(di, + di->max_usb_in_curr.usb_type_max); +} + +/** + * ab8500_charger_vbusdetf_handler() - VBUS falling detected + * @irq: interrupt number + * @_di: pointer to the ab8500_charger structure + * + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab8500_charger_vbusdetf_handler(int irq, void *_di) +{ + struct ab8500_charger *di = _di; + + di->vbus_detected = false; + dev_dbg(di->dev, "VBUS falling detected\n"); + queue_work(di->charger_wq, &di->detect_usb_type_work); + + return IRQ_HANDLED; +} + +/** + * ab8500_charger_vbusdetr_handler() - VBUS rising detected + * @irq: interrupt number + * @_di: pointer to the ab8500_charger structure + * + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab8500_charger_vbusdetr_handler(int irq, void *_di) +{ + struct ab8500_charger *di = _di; + + di->vbus_detected = true; + dev_dbg(di->dev, "VBUS rising detected\n"); + + queue_work(di->charger_wq, &di->detect_usb_type_work); + + return IRQ_HANDLED; +} + +/** + * ab8500_charger_usblinkstatus_handler() - USB link status has changed + * @irq: interrupt number + * @_di: pointer to the ab8500_charger structure + * + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab8500_charger_usblinkstatus_handler(int irq, void *_di) +{ + struct ab8500_charger *di = _di; + + dev_dbg(di->dev, "USB link status changed\n"); + + queue_work(di->charger_wq, &di->usb_link_status_work); + + return IRQ_HANDLED; +} + +/** + * ab8500_charger_usbchthprotr_handler() - Die temp is above usb charger + * thermal protection threshold + * @irq: interrupt number + * @_di: pointer to the ab8500_charger structure + * + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab8500_charger_usbchthprotr_handler(int irq, void *_di) +{ + struct ab8500_charger *di = _di; + + dev_dbg(di->dev, + "Die temp above USB charger thermal protection threshold\n"); + queue_work(di->charger_wq, &di->check_usb_thermal_prot_work); + + return IRQ_HANDLED; +} + +/** + * ab8500_charger_usbchthprotf_handler() - Die temp is below usb charger + * thermal protection threshold + * @irq: interrupt number + * @_di: pointer to the ab8500_charger structure + * + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab8500_charger_usbchthprotf_handler(int irq, void *_di) +{ + struct ab8500_charger *di = _di; + + dev_dbg(di->dev, + "Die temp ok for USB charger thermal protection threshold\n"); + queue_work(di->charger_wq, &di->check_usb_thermal_prot_work); + + return IRQ_HANDLED; +} + +/** + * ab8500_charger_usbchargernotokr_handler() - USB charger not ok detected + * @irq: interrupt number + * @_di: pointer to the ab8500_charger structure + * + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab8500_charger_usbchargernotokr_handler(int irq, void *_di) +{ + struct ab8500_charger *di = _di; + + dev_dbg(di->dev, "Not allowed USB charger detected\n"); + queue_delayed_work(di->charger_wq, &di->check_usbchgnotok_work, 0); + + return IRQ_HANDLED; +} + +/** + * ab8500_charger_chwdexp_handler() - Charger watchdog expired + * @irq: interrupt number + * @_di: pointer to the ab8500_charger structure + * + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab8500_charger_chwdexp_handler(int irq, void *_di) +{ + struct ab8500_charger *di = _di; + + dev_dbg(di->dev, "Charger watchdog expired\n"); + + /* + * The charger that was online when the watchdog expired + * needs to be restarted for charging to start again + */ + if (di->ac.charger_online) { + di->ac.wd_expired = true; + ab8500_power_supply_changed(di, di->ac_chg.psy); + } + if (di->usb.charger_online) { + di->usb.wd_expired = true; + ab8500_power_supply_changed(di, di->usb_chg.psy); + } + + return IRQ_HANDLED; +} + +/** + * ab8500_charger_vbuschdropend_handler() - VBUS drop removed + * @irq: interrupt number + * @_di: pointer to the ab8500_charger structure + * + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab8500_charger_vbuschdropend_handler(int irq, void *_di) +{ + struct ab8500_charger *di = _di; + + dev_dbg(di->dev, "VBUS charger drop ended\n"); + di->flags.vbus_drop_end = true; + + /* + * VBUS might have dropped due to bad connection. + * Schedule a new input limit set to the value SW requests. + */ + queue_delayed_work(di->charger_wq, &di->vbus_drop_end_work, + round_jiffies(VBUS_IN_CURR_LIM_RETRY_SET_TIME * HZ)); + + return IRQ_HANDLED; +} + +/** + * ab8500_charger_vbusovv_handler() - VBUS overvoltage detected + * @irq: interrupt number + * @_di: pointer to the ab8500_charger structure + * + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab8500_charger_vbusovv_handler(int irq, void *_di) +{ + struct ab8500_charger *di = _di; + + dev_dbg(di->dev, "VBUS overvoltage detected\n"); + di->flags.vbus_ovv = true; + ab8500_power_supply_changed(di, di->usb_chg.psy); + + /* Schedule a new HW failure check */ + queue_delayed_work(di->charger_wq, &di->check_hw_failure_work, 0); + + return IRQ_HANDLED; +} + +/** + * ab8500_charger_ac_get_property() - get the ac/mains properties + * @psy: pointer to the power_supply structure + * @psp: pointer to the power_supply_property structure + * @val: pointer to the power_supply_propval union + * + * This function gets called when an application tries to get the ac/mains + * properties by reading the sysfs files. + * AC/Mains properties are online, present and voltage. + * online: ac/mains charging is in progress or not + * present: presence of the ac/mains + * voltage: AC/Mains voltage + * Returns error code in case of failure else 0(on success) + */ +static int ab8500_charger_ac_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct ab8500_charger *di; + int ret; + + di = to_ab8500_charger_ac_device_info(psy_to_ux500_charger(psy)); + + switch (psp) { + case POWER_SUPPLY_PROP_HEALTH: + if (di->flags.mainextchnotok) + val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; + else if (di->ac.wd_expired || di->usb.wd_expired) + val->intval = POWER_SUPPLY_HEALTH_DEAD; + else if (di->flags.main_thermal_prot) + val->intval = POWER_SUPPLY_HEALTH_OVERHEAT; + else + val->intval = POWER_SUPPLY_HEALTH_GOOD; + break; + case POWER_SUPPLY_PROP_ONLINE: + val->intval = di->ac.charger_online; + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = di->ac.charger_connected; + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + ret = ab8500_charger_get_ac_voltage(di); + if (ret >= 0) + di->ac.charger_voltage = ret; + /* On error, use previous value */ + val->intval = di->ac.charger_voltage * 1000; + break; + case POWER_SUPPLY_PROP_VOLTAGE_AVG: + /* + * This property is used to indicate when CV mode is entered + * for the AC charger + */ + di->ac.cv_active = ab8500_charger_ac_cv(di); + val->intval = di->ac.cv_active; + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + ret = ab8500_charger_get_ac_current(di); + if (ret >= 0) + di->ac.charger_current = ret; + val->intval = di->ac.charger_current * 1000; + break; + default: + return -EINVAL; + } + return 0; +} + +/** + * ab8500_charger_usb_get_property() - get the usb properties + * @psy: pointer to the power_supply structure + * @psp: pointer to the power_supply_property structure + * @val: pointer to the power_supply_propval union + * + * This function gets called when an application tries to get the usb + * properties by reading the sysfs files. + * USB properties are online, present and voltage. + * online: usb charging is in progress or not + * present: presence of the usb + * voltage: vbus voltage + * Returns error code in case of failure else 0(on success) + */ +static int ab8500_charger_usb_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct ab8500_charger *di; + int ret; + + di = to_ab8500_charger_usb_device_info(psy_to_ux500_charger(psy)); + + switch (psp) { + case POWER_SUPPLY_PROP_HEALTH: + if (di->flags.usbchargernotok) + val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; + else if (di->ac.wd_expired || di->usb.wd_expired) + val->intval = POWER_SUPPLY_HEALTH_DEAD; + else if (di->flags.usb_thermal_prot) + val->intval = POWER_SUPPLY_HEALTH_OVERHEAT; + else if (di->flags.vbus_ovv) + val->intval = POWER_SUPPLY_HEALTH_OVERVOLTAGE; + else + val->intval = POWER_SUPPLY_HEALTH_GOOD; + break; + case POWER_SUPPLY_PROP_ONLINE: + val->intval = di->usb.charger_online; + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = di->usb.charger_connected; + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + ret = ab8500_charger_get_vbus_voltage(di); + if (ret >= 0) + di->usb.charger_voltage = ret; + val->intval = di->usb.charger_voltage * 1000; + break; + case POWER_SUPPLY_PROP_VOLTAGE_AVG: + /* + * This property is used to indicate when CV mode is entered + * for the USB charger + */ + di->usb.cv_active = ab8500_charger_usb_cv(di); + val->intval = di->usb.cv_active; + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + ret = ab8500_charger_get_usb_current(di); + if (ret >= 0) + di->usb.charger_current = ret; + val->intval = di->usb.charger_current * 1000; + break; + case POWER_SUPPLY_PROP_CURRENT_AVG: + /* + * This property is used to indicate when VBUS has collapsed + * due to too high output current from the USB charger + */ + if (di->flags.vbus_collapse) + val->intval = 1; + else + val->intval = 0; + break; + default: + return -EINVAL; + } + return 0; +} + +/** + * ab8500_charger_init_hw_registers() - Set up charger related registers + * @di: pointer to the ab8500_charger structure + * + * Set up charger OVV, watchdog and maximum voltage registers as well as + * charging of the backup battery + */ +static int ab8500_charger_init_hw_registers(struct ab8500_charger *di) +{ + int ret = 0; + u8 bup_vch_range = 0, vbup33_vrtcn = 0; + + /* Setup maximum charger current and voltage for ABB cut2.0 */ + if (!is_ab8500_1p1_or_earlier(di->parent)) { + ret = abx500_set_register_interruptible(di->dev, + AB8500_CHARGER, + AB8500_CH_VOLT_LVL_MAX_REG, CH_VOL_LVL_4P6); + if (ret) { + dev_err(di->dev, + "failed to set CH_VOLT_LVL_MAX_REG\n"); + goto out; + } + + if (is_ab8540(di->parent)) + ret = abx500_set_register_interruptible(di->dev, + AB8500_CHARGER, AB8500_CH_OPT_CRNTLVL_MAX_REG, + CH_OP_CUR_LVL_2P); + else + ret = abx500_set_register_interruptible(di->dev, + AB8500_CHARGER, AB8500_CH_OPT_CRNTLVL_MAX_REG, + CH_OP_CUR_LVL_1P6); + if (ret) { + dev_err(di->dev, + "failed to set CH_OPT_CRNTLVL_MAX_REG\n"); + goto out; + } + } + + if (is_ab9540_2p0(di->parent) || is_ab9540_3p0(di->parent) + || is_ab8505_2p0(di->parent) || is_ab8540(di->parent)) + ret = abx500_mask_and_set_register_interruptible(di->dev, + AB8500_CHARGER, + AB8500_USBCH_CTRL2_REG, + VBUS_AUTO_IN_CURR_LIM_ENA, + VBUS_AUTO_IN_CURR_LIM_ENA); + else + /* + * VBUS OVV set to 6.3V and enable automatic current limitation + */ + ret = abx500_set_register_interruptible(di->dev, + AB8500_CHARGER, + AB8500_USBCH_CTRL2_REG, + VBUS_OVV_SELECT_6P3V | VBUS_AUTO_IN_CURR_LIM_ENA); + if (ret) { + dev_err(di->dev, + "failed to set automatic current limitation\n"); + goto out; + } + + /* Enable main watchdog in OTP */ + ret = abx500_set_register_interruptible(di->dev, + AB8500_OTP_EMUL, AB8500_OTP_CONF_15, OTP_ENABLE_WD); + if (ret) { + dev_err(di->dev, "failed to enable main WD in OTP\n"); + goto out; + } + + /* Enable main watchdog */ + ret = abx500_set_register_interruptible(di->dev, + AB8500_SYS_CTRL2_BLOCK, + AB8500_MAIN_WDOG_CTRL_REG, MAIN_WDOG_ENA); + if (ret) { + dev_err(di->dev, "faile to enable main watchdog\n"); + goto out; + } + + /* + * Due to internal synchronisation, Enable and Kick watchdog bits + * cannot be enabled in a single write. + * A minimum delay of 2*32 kHz period (62.5µs) must be inserted + * between writing Enable then Kick bits. + */ + udelay(63); + + /* Kick main watchdog */ + ret = abx500_set_register_interruptible(di->dev, + AB8500_SYS_CTRL2_BLOCK, + AB8500_MAIN_WDOG_CTRL_REG, + (MAIN_WDOG_ENA | MAIN_WDOG_KICK)); + if (ret) { + dev_err(di->dev, "failed to kick main watchdog\n"); + goto out; + } + + /* Disable main watchdog */ + ret = abx500_set_register_interruptible(di->dev, + AB8500_SYS_CTRL2_BLOCK, + AB8500_MAIN_WDOG_CTRL_REG, MAIN_WDOG_DIS); + if (ret) { + dev_err(di->dev, "failed to disable main watchdog\n"); + goto out; + } + + /* Set watchdog timeout */ + ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER, + AB8500_CH_WD_TIMER_REG, WD_TIMER); + if (ret) { + dev_err(di->dev, "failed to set charger watchdog timeout\n"); + goto out; + } + + ret = ab8500_charger_led_en(di, false); + if (ret < 0) { + dev_err(di->dev, "failed to disable LED\n"); + goto out; + } + + /* Backup battery voltage and current */ + if (di->bm->bkup_bat_v > BUP_VCH_SEL_3P1V) + bup_vch_range = BUP_VCH_RANGE; + if (di->bm->bkup_bat_v == BUP_VCH_SEL_3P3V) + vbup33_vrtcn = VBUP33_VRTCN; + + ret = abx500_set_register_interruptible(di->dev, + AB8500_RTC, + AB8500_RTC_BACKUP_CHG_REG, + (di->bm->bkup_bat_v & 0x3) | di->bm->bkup_bat_i); + if (ret) { + dev_err(di->dev, "failed to setup backup battery charging\n"); + goto out; + } + if (is_ab8540(di->parent)) { + ret = abx500_set_register_interruptible(di->dev, + AB8500_RTC, + AB8500_RTC_CTRL1_REG, + bup_vch_range | vbup33_vrtcn); + if (ret) { + dev_err(di->dev, + "failed to setup backup battery charging\n"); + goto out; + } + } + + /* Enable backup battery charging */ + abx500_mask_and_set_register_interruptible(di->dev, + AB8500_RTC, AB8500_RTC_CTRL_REG, + RTC_BUP_CH_ENA, RTC_BUP_CH_ENA); + if (ret < 0) + dev_err(di->dev, "%s mask and set failed\n", __func__); + + if (is_ab8540(di->parent)) { + ret = abx500_mask_and_set_register_interruptible(di->dev, + AB8500_CHARGER, AB8540_USB_PP_MODE_REG, + BUS_VSYS_VOL_SELECT_MASK, BUS_VSYS_VOL_SELECT_3P6V); + if (ret) { + dev_err(di->dev, + "failed to setup usb power path vsys voltage\n"); + goto out; + } + ret = abx500_mask_and_set_register_interruptible(di->dev, + AB8500_CHARGER, AB8540_USB_PP_CHR_REG, + BUS_PP_PRECHG_CURRENT_MASK, 0); + if (ret) { + dev_err(di->dev, + "failed to setup usb power path prechage current\n"); + goto out; + } + } + +out: + return ret; +} + +/* + * ab8500 charger driver interrupts and their respective isr + */ +static struct ab8500_charger_interrupts ab8500_charger_irq[] = { + {"MAIN_CH_UNPLUG_DET", ab8500_charger_mainchunplugdet_handler}, + {"MAIN_CHARGE_PLUG_DET", ab8500_charger_mainchplugdet_handler}, + {"MAIN_EXT_CH_NOT_OK", ab8500_charger_mainextchnotok_handler}, + {"MAIN_CH_TH_PROT_R", ab8500_charger_mainchthprotr_handler}, + {"MAIN_CH_TH_PROT_F", ab8500_charger_mainchthprotf_handler}, + {"VBUS_DET_F", ab8500_charger_vbusdetf_handler}, + {"VBUS_DET_R", ab8500_charger_vbusdetr_handler}, + {"USB_LINK_STATUS", ab8500_charger_usblinkstatus_handler}, + {"USB_CH_TH_PROT_R", ab8500_charger_usbchthprotr_handler}, + {"USB_CH_TH_PROT_F", ab8500_charger_usbchthprotf_handler}, + {"USB_CHARGER_NOT_OKR", ab8500_charger_usbchargernotokr_handler}, + {"VBUS_OVV", ab8500_charger_vbusovv_handler}, + {"CH_WD_EXP", ab8500_charger_chwdexp_handler}, + {"VBUS_CH_DROP_END", ab8500_charger_vbuschdropend_handler}, +}; + +static int ab8500_charger_usb_notifier_call(struct notifier_block *nb, + unsigned long event, void *power) +{ + struct ab8500_charger *di = + container_of(nb, struct ab8500_charger, nb); + enum ab8500_usb_state bm_usb_state; + unsigned mA = *((unsigned *)power); + + if (!di) + return NOTIFY_DONE; + + if (event != USB_EVENT_VBUS) { + dev_dbg(di->dev, "not a standard host, returning\n"); + return NOTIFY_DONE; + } + + /* TODO: State is fabricate here. See if charger really needs USB + * state or if mA is enough + */ + if ((di->usb_state.usb_current == 2) && (mA > 2)) + bm_usb_state = AB8500_BM_USB_STATE_RESUME; + else if (mA == 0) + bm_usb_state = AB8500_BM_USB_STATE_RESET_HS; + else if (mA == 2) + bm_usb_state = AB8500_BM_USB_STATE_SUSPEND; + else if (mA >= 8) /* 8, 100, 500 */ + bm_usb_state = AB8500_BM_USB_STATE_CONFIGURED; + else /* Should never occur */ + bm_usb_state = AB8500_BM_USB_STATE_RESET_FS; + + dev_dbg(di->dev, "%s usb_state: 0x%02x mA: %d\n", + __func__, bm_usb_state, mA); + + spin_lock(&di->usb_state.usb_lock); + di->usb_state.state_tmp = bm_usb_state; + di->usb_state.usb_current_tmp = mA; + spin_unlock(&di->usb_state.usb_lock); + + /* + * wait for some time until you get updates from the usb stack + * and negotiations are completed + */ + queue_delayed_work(di->charger_wq, &di->usb_state_changed_work, HZ/2); + + return NOTIFY_OK; +} + +#if defined(CONFIG_PM) +static int ab8500_charger_resume(struct platform_device *pdev) +{ + int ret; + struct ab8500_charger *di = platform_get_drvdata(pdev); + + /* + * For ABB revision 1.0 and 1.1 there is a bug in the watchdog + * logic. That means we have to continously kick the charger + * watchdog even when no charger is connected. This is only + * valid once the AC charger has been enabled. This is + * a bug that is not handled by the algorithm and the + * watchdog have to be kicked by the charger driver + * when the AC charger is disabled + */ + if (di->ac_conn && is_ab8500_1p1_or_earlier(di->parent)) { + ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER, + AB8500_CHARG_WD_CTRL, CHARG_WD_KICK); + if (ret) + dev_err(di->dev, "Failed to kick WD!\n"); + + /* If not already pending start a new timer */ + queue_delayed_work(di->charger_wq, &di->kick_wd_work, + round_jiffies(WD_KICK_INTERVAL)); + } + + /* If we still have a HW failure, schedule a new check */ + if (di->flags.mainextchnotok || di->flags.vbus_ovv) { + queue_delayed_work(di->charger_wq, + &di->check_hw_failure_work, 0); + } + + if (di->flags.vbus_drop_end) + queue_delayed_work(di->charger_wq, &di->vbus_drop_end_work, 0); + + return 0; +} + +static int ab8500_charger_suspend(struct platform_device *pdev, + pm_message_t state) +{ + struct ab8500_charger *di = platform_get_drvdata(pdev); + + /* Cancel any pending jobs */ + cancel_delayed_work(&di->check_hw_failure_work); + cancel_delayed_work(&di->vbus_drop_end_work); + + flush_delayed_work(&di->attach_work); + flush_delayed_work(&di->usb_charger_attached_work); + flush_delayed_work(&di->ac_charger_attached_work); + flush_delayed_work(&di->check_usbchgnotok_work); + flush_delayed_work(&di->check_vbat_work); + flush_delayed_work(&di->kick_wd_work); + + flush_work(&di->usb_link_status_work); + flush_work(&di->ac_work); + flush_work(&di->detect_usb_type_work); + + if (atomic_read(&di->current_stepping_sessions)) + return -EAGAIN; + + return 0; +} +#else +#define ab8500_charger_suspend NULL +#define ab8500_charger_resume NULL +#endif + +static struct notifier_block charger_nb = { + .notifier_call = ab8500_external_charger_prepare, +}; + +static int ab8500_charger_remove(struct platform_device *pdev) +{ + struct ab8500_charger *di = platform_get_drvdata(pdev); + int i, irq, ret; + + /* Disable AC charging */ + ab8500_charger_ac_en(&di->ac_chg, false, 0, 0); + + /* Disable USB charging */ + ab8500_charger_usb_en(&di->usb_chg, false, 0, 0); + + /* Disable interrupts */ + for (i = 0; i < ARRAY_SIZE(ab8500_charger_irq); i++) { + irq = platform_get_irq_byname(pdev, ab8500_charger_irq[i].name); + free_irq(irq, di); + } + + /* Backup battery voltage and current disable */ + ret = abx500_mask_and_set_register_interruptible(di->dev, + AB8500_RTC, AB8500_RTC_CTRL_REG, RTC_BUP_CH_ENA, 0); + if (ret < 0) + dev_err(di->dev, "%s mask and set failed\n", __func__); + + usb_unregister_notifier(di->usb_phy, &di->nb); + usb_put_phy(di->usb_phy); + + /* Delete the work queue */ + destroy_workqueue(di->charger_wq); + + /* Unregister external charger enable notifier */ + if (!di->ac_chg.enabled) + blocking_notifier_chain_unregister( + &charger_notifier_list, &charger_nb); + + flush_scheduled_work(); + if (di->usb_chg.enabled) + power_supply_unregister(di->usb_chg.psy); + + if (di->ac_chg.enabled && !di->ac_chg.external) + power_supply_unregister(di->ac_chg.psy); + + return 0; +} + +static char *supply_interface[] = { + "ab8500_chargalg", + "ab8500_fg", + "ab8500_btemp", +}; + +static const struct power_supply_desc ab8500_ac_chg_desc = { + .name = "ab8500_ac", + .type = POWER_SUPPLY_TYPE_MAINS, + .properties = ab8500_charger_ac_props, + .num_properties = ARRAY_SIZE(ab8500_charger_ac_props), + .get_property = ab8500_charger_ac_get_property, +}; + +static const struct power_supply_desc ab8500_usb_chg_desc = { + .name = "ab8500_usb", + .type = POWER_SUPPLY_TYPE_USB, + .properties = ab8500_charger_usb_props, + .num_properties = ARRAY_SIZE(ab8500_charger_usb_props), + .get_property = ab8500_charger_usb_get_property, +}; + +static int ab8500_charger_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct abx500_bm_data *plat = pdev->dev.platform_data; + struct power_supply_config ac_psy_cfg = {}, usb_psy_cfg = {}; + struct ab8500_charger *di; + int irq, i, charger_status, ret = 0, ch_stat; + + di = devm_kzalloc(&pdev->dev, sizeof(*di), GFP_KERNEL); + if (!di) { + dev_err(&pdev->dev, "%s no mem for ab8500_charger\n", __func__); + return -ENOMEM; + } + + if (!plat) { + dev_err(&pdev->dev, "no battery management data supplied\n"); + return -EINVAL; + } + di->bm = plat; + + if (np) { + ret = ab8500_bm_of_probe(&pdev->dev, np, di->bm); + if (ret) { + dev_err(&pdev->dev, "failed to get battery information\n"); + return ret; + } + di->autopower_cfg = of_property_read_bool(np, "autopower_cfg"); + } else + di->autopower_cfg = false; + + /* get parent data */ + di->dev = &pdev->dev; + di->parent = dev_get_drvdata(pdev->dev.parent); + di->gpadc = ab8500_gpadc_get("ab8500-gpadc.0"); + + /* initialize lock */ + spin_lock_init(&di->usb_state.usb_lock); + mutex_init(&di->usb_ipt_crnt_lock); + + di->autopower = false; + di->invalid_charger_detect_state = 0; + + /* AC and USB supply config */ + ac_psy_cfg.supplied_to = supply_interface; + ac_psy_cfg.num_supplicants = ARRAY_SIZE(supply_interface); + ac_psy_cfg.drv_data = &di->ac_chg; + usb_psy_cfg.supplied_to = supply_interface; + usb_psy_cfg.num_supplicants = ARRAY_SIZE(supply_interface); + usb_psy_cfg.drv_data = &di->usb_chg; + + /* AC supply */ + /* ux500_charger sub-class */ + di->ac_chg.ops.enable = &ab8500_charger_ac_en; + di->ac_chg.ops.check_enable = &ab8500_charger_ac_check_enable; + di->ac_chg.ops.kick_wd = &ab8500_charger_watchdog_kick; + di->ac_chg.ops.update_curr = &ab8500_charger_update_charger_current; + di->ac_chg.max_out_volt = ab8500_charger_voltage_map[ + ARRAY_SIZE(ab8500_charger_voltage_map) - 1]; + di->ac_chg.max_out_curr = + di->bm->chg_output_curr[di->bm->n_chg_out_curr - 1]; + di->ac_chg.wdt_refresh = CHG_WD_INTERVAL; + di->ac_chg.enabled = di->bm->ac_enabled; + di->ac_chg.external = false; + + /*notifier for external charger enabling*/ + if (!di->ac_chg.enabled) + blocking_notifier_chain_register( + &charger_notifier_list, &charger_nb); + + /* USB supply */ + /* ux500_charger sub-class */ + di->usb_chg.ops.enable = &ab8500_charger_usb_en; + di->usb_chg.ops.check_enable = &ab8500_charger_usb_check_enable; + di->usb_chg.ops.kick_wd = &ab8500_charger_watchdog_kick; + di->usb_chg.ops.update_curr = &ab8500_charger_update_charger_current; + di->usb_chg.ops.pp_enable = &ab8540_charger_power_path_enable; + di->usb_chg.ops.pre_chg_enable = &ab8540_charger_usb_pre_chg_enable; + di->usb_chg.max_out_volt = ab8500_charger_voltage_map[ + ARRAY_SIZE(ab8500_charger_voltage_map) - 1]; + di->usb_chg.max_out_curr = + di->bm->chg_output_curr[di->bm->n_chg_out_curr - 1]; + di->usb_chg.wdt_refresh = CHG_WD_INTERVAL; + di->usb_chg.enabled = di->bm->usb_enabled; + di->usb_chg.external = false; + di->usb_chg.power_path = di->bm->usb_power_path; + di->usb_state.usb_current = -1; + + /* Create a work queue for the charger */ + di->charger_wq = + create_singlethread_workqueue("ab8500_charger_wq"); + if (di->charger_wq == NULL) { + dev_err(di->dev, "failed to create work queue\n"); + return -ENOMEM; + } + + mutex_init(&di->charger_attached_mutex); + + /* Init work for HW failure check */ + INIT_DEFERRABLE_WORK(&di->check_hw_failure_work, + ab8500_charger_check_hw_failure_work); + INIT_DEFERRABLE_WORK(&di->check_usbchgnotok_work, + ab8500_charger_check_usbchargernotok_work); + + INIT_DELAYED_WORK(&di->ac_charger_attached_work, + ab8500_charger_ac_attached_work); + INIT_DELAYED_WORK(&di->usb_charger_attached_work, + ab8500_charger_usb_attached_work); + + /* + * For ABB revision 1.0 and 1.1 there is a bug in the watchdog + * logic. That means we have to continously kick the charger + * watchdog even when no charger is connected. This is only + * valid once the AC charger has been enabled. This is + * a bug that is not handled by the algorithm and the + * watchdog have to be kicked by the charger driver + * when the AC charger is disabled + */ + INIT_DEFERRABLE_WORK(&di->kick_wd_work, + ab8500_charger_kick_watchdog_work); + + INIT_DEFERRABLE_WORK(&di->check_vbat_work, + ab8500_charger_check_vbat_work); + + INIT_DELAYED_WORK(&di->attach_work, + ab8500_charger_usb_link_attach_work); + + INIT_DELAYED_WORK(&di->usb_state_changed_work, + ab8500_charger_usb_state_changed_work); + + INIT_DELAYED_WORK(&di->vbus_drop_end_work, + ab8500_charger_vbus_drop_end_work); + + /* Init work for charger detection */ + INIT_WORK(&di->usb_link_status_work, + ab8500_charger_usb_link_status_work); + INIT_WORK(&di->ac_work, ab8500_charger_ac_work); + INIT_WORK(&di->detect_usb_type_work, + ab8500_charger_detect_usb_type_work); + + /* Init work for checking HW status */ + INIT_WORK(&di->check_main_thermal_prot_work, + ab8500_charger_check_main_thermal_prot_work); + INIT_WORK(&di->check_usb_thermal_prot_work, + ab8500_charger_check_usb_thermal_prot_work); + + /* + * VDD ADC supply needs to be enabled from this driver when there + * is a charger connected to avoid erroneous BTEMP_HIGH/LOW + * interrupts during charging + */ + di->regu = devm_regulator_get(di->dev, "vddadc"); + if (IS_ERR(di->regu)) { + ret = PTR_ERR(di->regu); + dev_err(di->dev, "failed to get vddadc regulator\n"); + goto free_charger_wq; + } + + + /* Initialize OVV, and other registers */ + ret = ab8500_charger_init_hw_registers(di); + if (ret) { + dev_err(di->dev, "failed to initialize ABB registers\n"); + goto free_charger_wq; + } + + /* Register AC charger class */ + if (di->ac_chg.enabled) { + di->ac_chg.psy = power_supply_register(di->dev, + &ab8500_ac_chg_desc, + &ac_psy_cfg); + if (IS_ERR(di->ac_chg.psy)) { + dev_err(di->dev, "failed to register AC charger\n"); + ret = PTR_ERR(di->ac_chg.psy); + goto free_charger_wq; + } + } + + /* Register USB charger class */ + if (di->usb_chg.enabled) { + di->usb_chg.psy = power_supply_register(di->dev, + &ab8500_usb_chg_desc, + &usb_psy_cfg); + if (IS_ERR(di->usb_chg.psy)) { + dev_err(di->dev, "failed to register USB charger\n"); + ret = PTR_ERR(di->usb_chg.psy); + goto free_ac; + } + } + + di->usb_phy = usb_get_phy(USB_PHY_TYPE_USB2); + if (IS_ERR_OR_NULL(di->usb_phy)) { + dev_err(di->dev, "failed to get usb transceiver\n"); + ret = -EINVAL; + goto free_usb; + } + di->nb.notifier_call = ab8500_charger_usb_notifier_call; + ret = usb_register_notifier(di->usb_phy, &di->nb); + if (ret) { + dev_err(di->dev, "failed to register usb notifier\n"); + goto put_usb_phy; + } + + /* Identify the connected charger types during startup */ + charger_status = ab8500_charger_detect_chargers(di, true); + if (charger_status & AC_PW_CONN) { + di->ac.charger_connected = 1; + di->ac_conn = true; + ab8500_power_supply_changed(di, di->ac_chg.psy); + sysfs_notify(&di->ac_chg.psy->dev.kobj, NULL, "present"); + } + + if (charger_status & USB_PW_CONN) { + di->vbus_detected = true; + di->vbus_detected_start = true; + queue_work(di->charger_wq, + &di->detect_usb_type_work); + } + + /* Register interrupts */ + for (i = 0; i < ARRAY_SIZE(ab8500_charger_irq); i++) { + irq = platform_get_irq_byname(pdev, ab8500_charger_irq[i].name); + ret = request_threaded_irq(irq, NULL, ab8500_charger_irq[i].isr, + IRQF_SHARED | IRQF_NO_SUSPEND, + ab8500_charger_irq[i].name, di); + + if (ret != 0) { + dev_err(di->dev, "failed to request %s IRQ %d: %d\n" + , ab8500_charger_irq[i].name, irq, ret); + goto free_irq; + } + dev_dbg(di->dev, "Requested %s IRQ %d: %d\n", + ab8500_charger_irq[i].name, irq, ret); + } + + platform_set_drvdata(pdev, di); + + mutex_lock(&di->charger_attached_mutex); + + ch_stat = ab8500_charger_detect_chargers(di, false); + + if ((ch_stat & AC_PW_CONN) == AC_PW_CONN) { + if (is_ab8500(di->parent)) + queue_delayed_work(di->charger_wq, + &di->ac_charger_attached_work, + HZ); + } + if ((ch_stat & USB_PW_CONN) == USB_PW_CONN) { + if (is_ab8500(di->parent)) + queue_delayed_work(di->charger_wq, + &di->usb_charger_attached_work, + HZ); + } + + mutex_unlock(&di->charger_attached_mutex); + + return ret; + +free_irq: + usb_unregister_notifier(di->usb_phy, &di->nb); + + /* We also have to free all successfully registered irqs */ + for (i = i - 1; i >= 0; i--) { + irq = platform_get_irq_byname(pdev, ab8500_charger_irq[i].name); + free_irq(irq, di); + } +put_usb_phy: + usb_put_phy(di->usb_phy); +free_usb: + if (di->usb_chg.enabled) + power_supply_unregister(di->usb_chg.psy); +free_ac: + if (di->ac_chg.enabled) + power_supply_unregister(di->ac_chg.psy); +free_charger_wq: + destroy_workqueue(di->charger_wq); + return ret; +} + +static const struct of_device_id ab8500_charger_match[] = { + { .compatible = "stericsson,ab8500-charger", }, + { }, +}; + +static struct platform_driver ab8500_charger_driver = { + .probe = ab8500_charger_probe, + .remove = ab8500_charger_remove, + .suspend = ab8500_charger_suspend, + .resume = ab8500_charger_resume, + .driver = { + .name = "ab8500-charger", + .of_match_table = ab8500_charger_match, + }, +}; + +static int __init ab8500_charger_init(void) +{ + return platform_driver_register(&ab8500_charger_driver); +} + +static void __exit ab8500_charger_exit(void) +{ + platform_driver_unregister(&ab8500_charger_driver); +} + +subsys_initcall_sync(ab8500_charger_init); +module_exit(ab8500_charger_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Johan Palsson, Karl Komierowski, Arun R Murthy"); +MODULE_ALIAS("platform:ab8500-charger"); +MODULE_DESCRIPTION("AB8500 charger management driver"); diff --git a/drivers/power/supply/ab8500_fg.c b/drivers/power/supply/ab8500_fg.c new file mode 100644 index 000000000000..5a36cf88578a --- /dev/null +++ b/drivers/power/supply/ab8500_fg.c @@ -0,0 +1,3272 @@ +/* + * Copyright (C) ST-Ericsson AB 2012 + * + * Main and Back-up battery management driver. + * + * Note: Backup battery management is required in case of Li-Ion battery and not + * for capacitive battery. HREF boards have capacitive battery and hence backup + * battery management is not used and the supported code is available in this + * driver. + * + * License Terms: GNU General Public License v2 + * Author: + * Johan Palsson + * Karl Komierowski + * Arun R Murthy + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MILLI_TO_MICRO 1000 +#define FG_LSB_IN_MA 1627 +#define QLSB_NANO_AMP_HOURS_X10 1071 +#define INS_CURR_TIMEOUT (3 * HZ) + +#define SEC_TO_SAMPLE(S) (S * 4) + +#define NBR_AVG_SAMPLES 20 + +#define LOW_BAT_CHECK_INTERVAL (HZ / 16) /* 62.5 ms */ + +#define VALID_CAPACITY_SEC (45 * 60) /* 45 minutes */ +#define BATT_OK_MIN 2360 /* mV */ +#define BATT_OK_INCREMENT 50 /* mV */ +#define BATT_OK_MAX_NR_INCREMENTS 0xE + +/* FG constants */ +#define BATT_OVV 0x01 + +#define interpolate(x, x1, y1, x2, y2) \ + ((y1) + ((((y2) - (y1)) * ((x) - (x1))) / ((x2) - (x1)))); + +/** + * struct ab8500_fg_interrupts - ab8500 fg interupts + * @name: name of the interrupt + * @isr function pointer to the isr + */ +struct ab8500_fg_interrupts { + char *name; + irqreturn_t (*isr)(int irq, void *data); +}; + +enum ab8500_fg_discharge_state { + AB8500_FG_DISCHARGE_INIT, + AB8500_FG_DISCHARGE_INITMEASURING, + AB8500_FG_DISCHARGE_INIT_RECOVERY, + AB8500_FG_DISCHARGE_RECOVERY, + AB8500_FG_DISCHARGE_READOUT_INIT, + AB8500_FG_DISCHARGE_READOUT, + AB8500_FG_DISCHARGE_WAKEUP, +}; + +static char *discharge_state[] = { + "DISCHARGE_INIT", + "DISCHARGE_INITMEASURING", + "DISCHARGE_INIT_RECOVERY", + "DISCHARGE_RECOVERY", + "DISCHARGE_READOUT_INIT", + "DISCHARGE_READOUT", + "DISCHARGE_WAKEUP", +}; + +enum ab8500_fg_charge_state { + AB8500_FG_CHARGE_INIT, + AB8500_FG_CHARGE_READOUT, +}; + +static char *charge_state[] = { + "CHARGE_INIT", + "CHARGE_READOUT", +}; + +enum ab8500_fg_calibration_state { + AB8500_FG_CALIB_INIT, + AB8500_FG_CALIB_WAIT, + AB8500_FG_CALIB_END, +}; + +struct ab8500_fg_avg_cap { + int avg; + int samples[NBR_AVG_SAMPLES]; + time64_t time_stamps[NBR_AVG_SAMPLES]; + int pos; + int nbr_samples; + int sum; +}; + +struct ab8500_fg_cap_scaling { + bool enable; + int cap_to_scale[2]; + int disable_cap_level; + int scaled_cap; +}; + +struct ab8500_fg_battery_capacity { + int max_mah_design; + int max_mah; + int mah; + int permille; + int level; + int prev_mah; + int prev_percent; + int prev_level; + int user_mah; + struct ab8500_fg_cap_scaling cap_scale; +}; + +struct ab8500_fg_flags { + bool fg_enabled; + bool conv_done; + bool charging; + bool fully_charged; + bool force_full; + bool low_bat_delay; + bool low_bat; + bool bat_ovv; + bool batt_unknown; + bool calibrate; + bool user_cap; + bool batt_id_received; +}; + +struct inst_curr_result_list { + struct list_head list; + int *result; +}; + +/** + * struct ab8500_fg - ab8500 FG device information + * @dev: Pointer to the structure device + * @node: a list of AB8500 FGs, hence prepared for reentrance + * @irq holds the CCEOC interrupt number + * @vbat: Battery voltage in mV + * @vbat_nom: Nominal battery voltage in mV + * @inst_curr: Instantenous battery current in mA + * @avg_curr: Average battery current in mA + * @bat_temp battery temperature + * @fg_samples: Number of samples used in the FG accumulation + * @accu_charge: Accumulated charge from the last conversion + * @recovery_cnt: Counter for recovery mode + * @high_curr_cnt: Counter for high current mode + * @init_cnt: Counter for init mode + * @low_bat_cnt Counter for number of consecutive low battery measures + * @nbr_cceoc_irq_cnt Counter for number of CCEOC irqs received since enabled + * @recovery_needed: Indicate if recovery is needed + * @high_curr_mode: Indicate if we're in high current mode + * @init_capacity: Indicate if initial capacity measuring should be done + * @turn_off_fg: True if fg was off before current measurement + * @calib_state State during offset calibration + * @discharge_state: Current discharge state + * @charge_state: Current charge state + * @ab8500_fg_started Completion struct used for the instant current start + * @ab8500_fg_complete Completion struct used for the instant current reading + * @flags: Structure for information about events triggered + * @bat_cap: Structure for battery capacity specific parameters + * @avg_cap: Average capacity filter + * @parent: Pointer to the struct ab8500 + * @gpadc: Pointer to the struct gpadc + * @bm: Platform specific battery management information + * @fg_psy: Structure that holds the FG specific battery properties + * @fg_wq: Work queue for running the FG algorithm + * @fg_periodic_work: Work to run the FG algorithm periodically + * @fg_low_bat_work: Work to check low bat condition + * @fg_reinit_work Work used to reset and reinitialise the FG algorithm + * @fg_work: Work to run the FG algorithm instantly + * @fg_acc_cur_work: Work to read the FG accumulator + * @fg_check_hw_failure_work: Work for checking HW state + * @cc_lock: Mutex for locking the CC + * @fg_kobject: Structure of type kobject + */ +struct ab8500_fg { + struct device *dev; + struct list_head node; + int irq; + int vbat; + int vbat_nom; + int inst_curr; + int avg_curr; + int bat_temp; + int fg_samples; + int accu_charge; + int recovery_cnt; + int high_curr_cnt; + int init_cnt; + int low_bat_cnt; + int nbr_cceoc_irq_cnt; + bool recovery_needed; + bool high_curr_mode; + bool init_capacity; + bool turn_off_fg; + enum ab8500_fg_calibration_state calib_state; + enum ab8500_fg_discharge_state discharge_state; + enum ab8500_fg_charge_state charge_state; + struct completion ab8500_fg_started; + struct completion ab8500_fg_complete; + struct ab8500_fg_flags flags; + struct ab8500_fg_battery_capacity bat_cap; + struct ab8500_fg_avg_cap avg_cap; + struct ab8500 *parent; + struct ab8500_gpadc *gpadc; + struct abx500_bm_data *bm; + struct power_supply *fg_psy; + struct workqueue_struct *fg_wq; + struct delayed_work fg_periodic_work; + struct delayed_work fg_low_bat_work; + struct delayed_work fg_reinit_work; + struct work_struct fg_work; + struct work_struct fg_acc_cur_work; + struct delayed_work fg_check_hw_failure_work; + struct mutex cc_lock; + struct kobject fg_kobject; +}; +static LIST_HEAD(ab8500_fg_list); + +/** + * ab8500_fg_get() - returns a reference to the primary AB8500 fuel gauge + * (i.e. the first fuel gauge in the instance list) + */ +struct ab8500_fg *ab8500_fg_get(void) +{ + struct ab8500_fg *fg; + + if (list_empty(&ab8500_fg_list)) + return NULL; + + fg = list_first_entry(&ab8500_fg_list, struct ab8500_fg, node); + return fg; +} + +/* Main battery properties */ +static enum power_supply_property ab8500_fg_props[] = { + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_CURRENT_AVG, + POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN, + POWER_SUPPLY_PROP_ENERGY_FULL, + POWER_SUPPLY_PROP_ENERGY_NOW, + POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, + POWER_SUPPLY_PROP_CHARGE_FULL, + POWER_SUPPLY_PROP_CHARGE_NOW, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_CAPACITY_LEVEL, +}; + +/* + * This array maps the raw hex value to lowbat voltage used by the AB8500 + * Values taken from the UM0836 + */ +static int ab8500_fg_lowbat_voltage_map[] = { + 2300 , + 2325 , + 2350 , + 2375 , + 2400 , + 2425 , + 2450 , + 2475 , + 2500 , + 2525 , + 2550 , + 2575 , + 2600 , + 2625 , + 2650 , + 2675 , + 2700 , + 2725 , + 2750 , + 2775 , + 2800 , + 2825 , + 2850 , + 2875 , + 2900 , + 2925 , + 2950 , + 2975 , + 3000 , + 3025 , + 3050 , + 3075 , + 3100 , + 3125 , + 3150 , + 3175 , + 3200 , + 3225 , + 3250 , + 3275 , + 3300 , + 3325 , + 3350 , + 3375 , + 3400 , + 3425 , + 3450 , + 3475 , + 3500 , + 3525 , + 3550 , + 3575 , + 3600 , + 3625 , + 3650 , + 3675 , + 3700 , + 3725 , + 3750 , + 3775 , + 3800 , + 3825 , + 3850 , + 3850 , +}; + +static u8 ab8500_volt_to_regval(int voltage) +{ + int i; + + if (voltage < ab8500_fg_lowbat_voltage_map[0]) + return 0; + + for (i = 0; i < ARRAY_SIZE(ab8500_fg_lowbat_voltage_map); i++) { + if (voltage < ab8500_fg_lowbat_voltage_map[i]) + return (u8) i - 1; + } + + /* If not captured above, return index of last element */ + return (u8) ARRAY_SIZE(ab8500_fg_lowbat_voltage_map) - 1; +} + +/** + * ab8500_fg_is_low_curr() - Low or high current mode + * @di: pointer to the ab8500_fg structure + * @curr: the current to base or our decision on + * + * Low current mode if the current consumption is below a certain threshold + */ +static int ab8500_fg_is_low_curr(struct ab8500_fg *di, int curr) +{ + /* + * We want to know if we're in low current mode + */ + if (curr > -di->bm->fg_params->high_curr_threshold) + return true; + else + return false; +} + +/** + * ab8500_fg_add_cap_sample() - Add capacity to average filter + * @di: pointer to the ab8500_fg structure + * @sample: the capacity in mAh to add to the filter + * + * A capacity is added to the filter and a new mean capacity is calculated and + * returned + */ +static int ab8500_fg_add_cap_sample(struct ab8500_fg *di, int sample) +{ + struct timespec64 ts64; + struct ab8500_fg_avg_cap *avg = &di->avg_cap; + + getnstimeofday64(&ts64); + + do { + avg->sum += sample - avg->samples[avg->pos]; + avg->samples[avg->pos] = sample; + avg->time_stamps[avg->pos] = ts64.tv_sec; + avg->pos++; + + if (avg->pos == NBR_AVG_SAMPLES) + avg->pos = 0; + + if (avg->nbr_samples < NBR_AVG_SAMPLES) + avg->nbr_samples++; + + /* + * Check the time stamp for each sample. If too old, + * replace with latest sample + */ + } while (ts64.tv_sec - VALID_CAPACITY_SEC > avg->time_stamps[avg->pos]); + + avg->avg = avg->sum / avg->nbr_samples; + + return avg->avg; +} + +/** + * ab8500_fg_clear_cap_samples() - Clear average filter + * @di: pointer to the ab8500_fg structure + * + * The capacity filter is is reset to zero. + */ +static void ab8500_fg_clear_cap_samples(struct ab8500_fg *di) +{ + int i; + struct ab8500_fg_avg_cap *avg = &di->avg_cap; + + avg->pos = 0; + avg->nbr_samples = 0; + avg->sum = 0; + avg->avg = 0; + + for (i = 0; i < NBR_AVG_SAMPLES; i++) { + avg->samples[i] = 0; + avg->time_stamps[i] = 0; + } +} + +/** + * ab8500_fg_fill_cap_sample() - Fill average filter + * @di: pointer to the ab8500_fg structure + * @sample: the capacity in mAh to fill the filter with + * + * The capacity filter is filled with a capacity in mAh + */ +static void ab8500_fg_fill_cap_sample(struct ab8500_fg *di, int sample) +{ + int i; + struct timespec64 ts64; + struct ab8500_fg_avg_cap *avg = &di->avg_cap; + + getnstimeofday64(&ts64); + + for (i = 0; i < NBR_AVG_SAMPLES; i++) { + avg->samples[i] = sample; + avg->time_stamps[i] = ts64.tv_sec; + } + + avg->pos = 0; + avg->nbr_samples = NBR_AVG_SAMPLES; + avg->sum = sample * NBR_AVG_SAMPLES; + avg->avg = sample; +} + +/** + * ab8500_fg_coulomb_counter() - enable coulomb counter + * @di: pointer to the ab8500_fg structure + * @enable: enable/disable + * + * Enable/Disable coulomb counter. + * On failure returns negative value. + */ +static int ab8500_fg_coulomb_counter(struct ab8500_fg *di, bool enable) +{ + int ret = 0; + mutex_lock(&di->cc_lock); + if (enable) { + /* To be able to reprogram the number of samples, we have to + * first stop the CC and then enable it again */ + ret = abx500_set_register_interruptible(di->dev, AB8500_RTC, + AB8500_RTC_CC_CONF_REG, 0x00); + if (ret) + goto cc_err; + + /* Program the samples */ + ret = abx500_set_register_interruptible(di->dev, + AB8500_GAS_GAUGE, AB8500_GASG_CC_NCOV_ACCU, + di->fg_samples); + if (ret) + goto cc_err; + + /* Start the CC */ + ret = abx500_set_register_interruptible(di->dev, AB8500_RTC, + AB8500_RTC_CC_CONF_REG, + (CC_DEEP_SLEEP_ENA | CC_PWR_UP_ENA)); + if (ret) + goto cc_err; + + di->flags.fg_enabled = true; + } else { + /* Clear any pending read requests */ + ret = abx500_mask_and_set_register_interruptible(di->dev, + AB8500_GAS_GAUGE, AB8500_GASG_CC_CTRL_REG, + (RESET_ACCU | READ_REQ), 0); + if (ret) + goto cc_err; + + ret = abx500_set_register_interruptible(di->dev, + AB8500_GAS_GAUGE, AB8500_GASG_CC_NCOV_ACCU_CTRL, 0); + if (ret) + goto cc_err; + + /* Stop the CC */ + ret = abx500_set_register_interruptible(di->dev, AB8500_RTC, + AB8500_RTC_CC_CONF_REG, 0); + if (ret) + goto cc_err; + + di->flags.fg_enabled = false; + + } + dev_dbg(di->dev, " CC enabled: %d Samples: %d\n", + enable, di->fg_samples); + + mutex_unlock(&di->cc_lock); + + return ret; +cc_err: + dev_err(di->dev, "%s Enabling coulomb counter failed\n", __func__); + mutex_unlock(&di->cc_lock); + return ret; +} + +/** + * ab8500_fg_inst_curr_start() - start battery instantaneous current + * @di: pointer to the ab8500_fg structure + * + * Returns 0 or error code + * Note: This is part "one" and has to be called before + * ab8500_fg_inst_curr_finalize() + */ +int ab8500_fg_inst_curr_start(struct ab8500_fg *di) +{ + u8 reg_val; + int ret; + + mutex_lock(&di->cc_lock); + + di->nbr_cceoc_irq_cnt = 0; + ret = abx500_get_register_interruptible(di->dev, AB8500_RTC, + AB8500_RTC_CC_CONF_REG, ®_val); + if (ret < 0) + goto fail; + + if (!(reg_val & CC_PWR_UP_ENA)) { + dev_dbg(di->dev, "%s Enable FG\n", __func__); + di->turn_off_fg = true; + + /* Program the samples */ + ret = abx500_set_register_interruptible(di->dev, + AB8500_GAS_GAUGE, AB8500_GASG_CC_NCOV_ACCU, + SEC_TO_SAMPLE(10)); + if (ret) + goto fail; + + /* Start the CC */ + ret = abx500_set_register_interruptible(di->dev, AB8500_RTC, + AB8500_RTC_CC_CONF_REG, + (CC_DEEP_SLEEP_ENA | CC_PWR_UP_ENA)); + if (ret) + goto fail; + } else { + di->turn_off_fg = false; + } + + /* Return and WFI */ + reinit_completion(&di->ab8500_fg_started); + reinit_completion(&di->ab8500_fg_complete); + enable_irq(di->irq); + + /* Note: cc_lock is still locked */ + return 0; +fail: + mutex_unlock(&di->cc_lock); + return ret; +} + +/** + * ab8500_fg_inst_curr_started() - check if fg conversion has started + * @di: pointer to the ab8500_fg structure + * + * Returns 1 if conversion started, 0 if still waiting + */ +int ab8500_fg_inst_curr_started(struct ab8500_fg *di) +{ + return completion_done(&di->ab8500_fg_started); +} + +/** + * ab8500_fg_inst_curr_done() - check if fg conversion is done + * @di: pointer to the ab8500_fg structure + * + * Returns 1 if conversion done, 0 if still waiting + */ +int ab8500_fg_inst_curr_done(struct ab8500_fg *di) +{ + return completion_done(&di->ab8500_fg_complete); +} + +/** + * ab8500_fg_inst_curr_finalize() - battery instantaneous current + * @di: pointer to the ab8500_fg structure + * @res: battery instantenous current(on success) + * + * Returns 0 or an error code + * Note: This is part "two" and has to be called at earliest 250 ms + * after ab8500_fg_inst_curr_start() + */ +int ab8500_fg_inst_curr_finalize(struct ab8500_fg *di, int *res) +{ + u8 low, high; + int val; + int ret; + unsigned long timeout; + + if (!completion_done(&di->ab8500_fg_complete)) { + timeout = wait_for_completion_timeout( + &di->ab8500_fg_complete, + INS_CURR_TIMEOUT); + dev_dbg(di->dev, "Finalize time: %d ms\n", + jiffies_to_msecs(INS_CURR_TIMEOUT - timeout)); + if (!timeout) { + ret = -ETIME; + disable_irq(di->irq); + di->nbr_cceoc_irq_cnt = 0; + dev_err(di->dev, "completion timed out [%d]\n", + __LINE__); + goto fail; + } + } + + disable_irq(di->irq); + di->nbr_cceoc_irq_cnt = 0; + + ret = abx500_mask_and_set_register_interruptible(di->dev, + AB8500_GAS_GAUGE, AB8500_GASG_CC_CTRL_REG, + READ_REQ, READ_REQ); + + /* 100uS between read request and read is needed */ + usleep_range(100, 100); + + /* Read CC Sample conversion value Low and high */ + ret = abx500_get_register_interruptible(di->dev, AB8500_GAS_GAUGE, + AB8500_GASG_CC_SMPL_CNVL_REG, &low); + if (ret < 0) + goto fail; + + ret = abx500_get_register_interruptible(di->dev, AB8500_GAS_GAUGE, + AB8500_GASG_CC_SMPL_CNVH_REG, &high); + if (ret < 0) + goto fail; + + /* + * negative value for Discharging + * convert 2's compliment into decimal + */ + if (high & 0x10) + val = (low | (high << 8) | 0xFFFFE000); + else + val = (low | (high << 8)); + + /* + * Convert to unit value in mA + * Full scale input voltage is + * 63.160mV => LSB = 63.160mV/(4096*res) = 1.542mA + * Given a 250ms conversion cycle time the LSB corresponds + * to 107.1 nAh. Convert to current by dividing by the conversion + * time in hours (250ms = 1 / (3600 * 4)h) + * 107.1nAh assumes 10mOhm, but fg_res is in 0.1mOhm + */ + val = (val * QLSB_NANO_AMP_HOURS_X10 * 36 * 4) / + (1000 * di->bm->fg_res); + + if (di->turn_off_fg) { + dev_dbg(di->dev, "%s Disable FG\n", __func__); + + /* Clear any pending read requests */ + ret = abx500_set_register_interruptible(di->dev, + AB8500_GAS_GAUGE, AB8500_GASG_CC_CTRL_REG, 0); + if (ret) + goto fail; + + /* Stop the CC */ + ret = abx500_set_register_interruptible(di->dev, AB8500_RTC, + AB8500_RTC_CC_CONF_REG, 0); + if (ret) + goto fail; + } + mutex_unlock(&di->cc_lock); + (*res) = val; + + return 0; +fail: + mutex_unlock(&di->cc_lock); + return ret; +} + +/** + * ab8500_fg_inst_curr_blocking() - battery instantaneous current + * @di: pointer to the ab8500_fg structure + * @res: battery instantenous current(on success) + * + * Returns 0 else error code + */ +int ab8500_fg_inst_curr_blocking(struct ab8500_fg *di) +{ + int ret; + unsigned long timeout; + int res = 0; + + ret = ab8500_fg_inst_curr_start(di); + if (ret) { + dev_err(di->dev, "Failed to initialize fg_inst\n"); + return 0; + } + + /* Wait for CC to actually start */ + if (!completion_done(&di->ab8500_fg_started)) { + timeout = wait_for_completion_timeout( + &di->ab8500_fg_started, + INS_CURR_TIMEOUT); + dev_dbg(di->dev, "Start time: %d ms\n", + jiffies_to_msecs(INS_CURR_TIMEOUT - timeout)); + if (!timeout) { + ret = -ETIME; + dev_err(di->dev, "completion timed out [%d]\n", + __LINE__); + goto fail; + } + } + + ret = ab8500_fg_inst_curr_finalize(di, &res); + if (ret) { + dev_err(di->dev, "Failed to finalize fg_inst\n"); + return 0; + } + + dev_dbg(di->dev, "%s instant current: %d", __func__, res); + return res; +fail: + disable_irq(di->irq); + mutex_unlock(&di->cc_lock); + return ret; +} + +/** + * ab8500_fg_acc_cur_work() - average battery current + * @work: pointer to the work_struct structure + * + * Updated the average battery current obtained from the + * coulomb counter. + */ +static void ab8500_fg_acc_cur_work(struct work_struct *work) +{ + int val; + int ret; + u8 low, med, high; + + struct ab8500_fg *di = container_of(work, + struct ab8500_fg, fg_acc_cur_work); + + mutex_lock(&di->cc_lock); + ret = abx500_set_register_interruptible(di->dev, AB8500_GAS_GAUGE, + AB8500_GASG_CC_NCOV_ACCU_CTRL, RD_NCONV_ACCU_REQ); + if (ret) + goto exit; + + ret = abx500_get_register_interruptible(di->dev, AB8500_GAS_GAUGE, + AB8500_GASG_CC_NCOV_ACCU_LOW, &low); + if (ret < 0) + goto exit; + + ret = abx500_get_register_interruptible(di->dev, AB8500_GAS_GAUGE, + AB8500_GASG_CC_NCOV_ACCU_MED, &med); + if (ret < 0) + goto exit; + + ret = abx500_get_register_interruptible(di->dev, AB8500_GAS_GAUGE, + AB8500_GASG_CC_NCOV_ACCU_HIGH, &high); + if (ret < 0) + goto exit; + + /* Check for sign bit in case of negative value, 2's compliment */ + if (high & 0x10) + val = (low | (med << 8) | (high << 16) | 0xFFE00000); + else + val = (low | (med << 8) | (high << 16)); + + /* + * Convert to uAh + * Given a 250ms conversion cycle time the LSB corresponds + * to 112.9 nAh. + * 112.9nAh assumes 10mOhm, but fg_res is in 0.1mOhm + */ + di->accu_charge = (val * QLSB_NANO_AMP_HOURS_X10) / + (100 * di->bm->fg_res); + + /* + * Convert to unit value in mA + * by dividing by the conversion + * time in hours (= samples / (3600 * 4)h) + * and multiply with 1000 + */ + di->avg_curr = (val * QLSB_NANO_AMP_HOURS_X10 * 36) / + (1000 * di->bm->fg_res * (di->fg_samples / 4)); + + di->flags.conv_done = true; + + mutex_unlock(&di->cc_lock); + + queue_work(di->fg_wq, &di->fg_work); + + dev_dbg(di->dev, "fg_res: %d, fg_samples: %d, gasg: %d, accu_charge: %d \n", + di->bm->fg_res, di->fg_samples, val, di->accu_charge); + return; +exit: + dev_err(di->dev, + "Failed to read or write gas gauge registers\n"); + mutex_unlock(&di->cc_lock); + queue_work(di->fg_wq, &di->fg_work); +} + +/** + * ab8500_fg_bat_voltage() - get battery voltage + * @di: pointer to the ab8500_fg structure + * + * Returns battery voltage(on success) else error code + */ +static int ab8500_fg_bat_voltage(struct ab8500_fg *di) +{ + int vbat; + static int prev; + + vbat = ab8500_gpadc_convert(di->gpadc, MAIN_BAT_V); + if (vbat < 0) { + dev_err(di->dev, + "%s gpadc conversion failed, using previous value\n", + __func__); + return prev; + } + + prev = vbat; + return vbat; +} + +/** + * ab8500_fg_volt_to_capacity() - Voltage based capacity + * @di: pointer to the ab8500_fg structure + * @voltage: The voltage to convert to a capacity + * + * Returns battery capacity in per mille based on voltage + */ +static int ab8500_fg_volt_to_capacity(struct ab8500_fg *di, int voltage) +{ + int i, tbl_size; + const struct abx500_v_to_cap *tbl; + int cap = 0; + + tbl = di->bm->bat_type[di->bm->batt_id].v_to_cap_tbl, + tbl_size = di->bm->bat_type[di->bm->batt_id].n_v_cap_tbl_elements; + + for (i = 0; i < tbl_size; ++i) { + if (voltage > tbl[i].voltage) + break; + } + + if ((i > 0) && (i < tbl_size)) { + cap = interpolate(voltage, + tbl[i].voltage, + tbl[i].capacity * 10, + tbl[i-1].voltage, + tbl[i-1].capacity * 10); + } else if (i == 0) { + cap = 1000; + } else { + cap = 0; + } + + dev_dbg(di->dev, "%s Vbat: %d, Cap: %d per mille", + __func__, voltage, cap); + + return cap; +} + +/** + * ab8500_fg_uncomp_volt_to_capacity() - Uncompensated voltage based capacity + * @di: pointer to the ab8500_fg structure + * + * Returns battery capacity based on battery voltage that is not compensated + * for the voltage drop due to the load + */ +static int ab8500_fg_uncomp_volt_to_capacity(struct ab8500_fg *di) +{ + di->vbat = ab8500_fg_bat_voltage(di); + return ab8500_fg_volt_to_capacity(di, di->vbat); +} + +/** + * ab8500_fg_battery_resistance() - Returns the battery inner resistance + * @di: pointer to the ab8500_fg structure + * + * Returns battery inner resistance added with the fuel gauge resistor value + * to get the total resistance in the whole link from gnd to bat+ node. + */ +static int ab8500_fg_battery_resistance(struct ab8500_fg *di) +{ + int i, tbl_size; + const struct batres_vs_temp *tbl; + int resist = 0; + + tbl = di->bm->bat_type[di->bm->batt_id].batres_tbl; + tbl_size = di->bm->bat_type[di->bm->batt_id].n_batres_tbl_elements; + + for (i = 0; i < tbl_size; ++i) { + if (di->bat_temp / 10 > tbl[i].temp) + break; + } + + if ((i > 0) && (i < tbl_size)) { + resist = interpolate(di->bat_temp / 10, + tbl[i].temp, + tbl[i].resist, + tbl[i-1].temp, + tbl[i-1].resist); + } else if (i == 0) { + resist = tbl[0].resist; + } else { + resist = tbl[tbl_size - 1].resist; + } + + dev_dbg(di->dev, "%s Temp: %d battery internal resistance: %d" + " fg resistance %d, total: %d (mOhm)\n", + __func__, di->bat_temp, resist, di->bm->fg_res / 10, + (di->bm->fg_res / 10) + resist); + + /* fg_res variable is in 0.1mOhm */ + resist += di->bm->fg_res / 10; + + return resist; +} + +/** + * ab8500_fg_load_comp_volt_to_capacity() - Load compensated voltage based capacity + * @di: pointer to the ab8500_fg structure + * + * Returns battery capacity based on battery voltage that is load compensated + * for the voltage drop + */ +static int ab8500_fg_load_comp_volt_to_capacity(struct ab8500_fg *di) +{ + int vbat_comp, res; + int i = 0; + int vbat = 0; + + ab8500_fg_inst_curr_start(di); + + do { + vbat += ab8500_fg_bat_voltage(di); + i++; + usleep_range(5000, 6000); + } while (!ab8500_fg_inst_curr_done(di)); + + ab8500_fg_inst_curr_finalize(di, &di->inst_curr); + + di->vbat = vbat / i; + res = ab8500_fg_battery_resistance(di); + + /* Use Ohms law to get the load compensated voltage */ + vbat_comp = di->vbat - (di->inst_curr * res) / 1000; + + dev_dbg(di->dev, "%s Measured Vbat: %dmV,Compensated Vbat %dmV, " + "R: %dmOhm, Current: %dmA Vbat Samples: %d\n", + __func__, di->vbat, vbat_comp, res, di->inst_curr, i); + + return ab8500_fg_volt_to_capacity(di, vbat_comp); +} + +/** + * ab8500_fg_convert_mah_to_permille() - Capacity in mAh to permille + * @di: pointer to the ab8500_fg structure + * @cap_mah: capacity in mAh + * + * Converts capacity in mAh to capacity in permille + */ +static int ab8500_fg_convert_mah_to_permille(struct ab8500_fg *di, int cap_mah) +{ + return (cap_mah * 1000) / di->bat_cap.max_mah_design; +} + +/** + * ab8500_fg_convert_permille_to_mah() - Capacity in permille to mAh + * @di: pointer to the ab8500_fg structure + * @cap_pm: capacity in permille + * + * Converts capacity in permille to capacity in mAh + */ +static int ab8500_fg_convert_permille_to_mah(struct ab8500_fg *di, int cap_pm) +{ + return cap_pm * di->bat_cap.max_mah_design / 1000; +} + +/** + * ab8500_fg_convert_mah_to_uwh() - Capacity in mAh to uWh + * @di: pointer to the ab8500_fg structure + * @cap_mah: capacity in mAh + * + * Converts capacity in mAh to capacity in uWh + */ +static int ab8500_fg_convert_mah_to_uwh(struct ab8500_fg *di, int cap_mah) +{ + u64 div_res; + u32 div_rem; + + div_res = ((u64) cap_mah) * ((u64) di->vbat_nom); + div_rem = do_div(div_res, 1000); + + /* Make sure to round upwards if necessary */ + if (div_rem >= 1000 / 2) + div_res++; + + return (int) div_res; +} + +/** + * ab8500_fg_calc_cap_charging() - Calculate remaining capacity while charging + * @di: pointer to the ab8500_fg structure + * + * Return the capacity in mAh based on previous calculated capcity and the FG + * accumulator register value. The filter is filled with this capacity + */ +static int ab8500_fg_calc_cap_charging(struct ab8500_fg *di) +{ + dev_dbg(di->dev, "%s cap_mah %d accu_charge %d\n", + __func__, + di->bat_cap.mah, + di->accu_charge); + + /* Capacity should not be less than 0 */ + if (di->bat_cap.mah + di->accu_charge > 0) + di->bat_cap.mah += di->accu_charge; + else + di->bat_cap.mah = 0; + /* + * We force capacity to 100% once when the algorithm + * reports that it's full. + */ + if (di->bat_cap.mah >= di->bat_cap.max_mah_design || + di->flags.force_full) { + di->bat_cap.mah = di->bat_cap.max_mah_design; + } + + ab8500_fg_fill_cap_sample(di, di->bat_cap.mah); + di->bat_cap.permille = + ab8500_fg_convert_mah_to_permille(di, di->bat_cap.mah); + + /* We need to update battery voltage and inst current when charging */ + di->vbat = ab8500_fg_bat_voltage(di); + di->inst_curr = ab8500_fg_inst_curr_blocking(di); + + return di->bat_cap.mah; +} + +/** + * ab8500_fg_calc_cap_discharge_voltage() - Capacity in discharge with voltage + * @di: pointer to the ab8500_fg structure + * @comp: if voltage should be load compensated before capacity calc + * + * Return the capacity in mAh based on the battery voltage. The voltage can + * either be load compensated or not. This value is added to the filter and a + * new mean value is calculated and returned. + */ +static int ab8500_fg_calc_cap_discharge_voltage(struct ab8500_fg *di, bool comp) +{ + int permille, mah; + + if (comp) + permille = ab8500_fg_load_comp_volt_to_capacity(di); + else + permille = ab8500_fg_uncomp_volt_to_capacity(di); + + mah = ab8500_fg_convert_permille_to_mah(di, permille); + + di->bat_cap.mah = ab8500_fg_add_cap_sample(di, mah); + di->bat_cap.permille = + ab8500_fg_convert_mah_to_permille(di, di->bat_cap.mah); + + return di->bat_cap.mah; +} + +/** + * ab8500_fg_calc_cap_discharge_fg() - Capacity in discharge with FG + * @di: pointer to the ab8500_fg structure + * + * Return the capacity in mAh based on previous calculated capcity and the FG + * accumulator register value. This value is added to the filter and a + * new mean value is calculated and returned. + */ +static int ab8500_fg_calc_cap_discharge_fg(struct ab8500_fg *di) +{ + int permille_volt, permille; + + dev_dbg(di->dev, "%s cap_mah %d accu_charge %d\n", + __func__, + di->bat_cap.mah, + di->accu_charge); + + /* Capacity should not be less than 0 */ + if (di->bat_cap.mah + di->accu_charge > 0) + di->bat_cap.mah += di->accu_charge; + else + di->bat_cap.mah = 0; + + if (di->bat_cap.mah >= di->bat_cap.max_mah_design) + di->bat_cap.mah = di->bat_cap.max_mah_design; + + /* + * Check against voltage based capacity. It can not be lower + * than what the uncompensated voltage says + */ + permille = ab8500_fg_convert_mah_to_permille(di, di->bat_cap.mah); + permille_volt = ab8500_fg_uncomp_volt_to_capacity(di); + + if (permille < permille_volt) { + di->bat_cap.permille = permille_volt; + di->bat_cap.mah = ab8500_fg_convert_permille_to_mah(di, + di->bat_cap.permille); + + dev_dbg(di->dev, "%s voltage based: perm %d perm_volt %d\n", + __func__, + permille, + permille_volt); + + ab8500_fg_fill_cap_sample(di, di->bat_cap.mah); + } else { + ab8500_fg_fill_cap_sample(di, di->bat_cap.mah); + di->bat_cap.permille = + ab8500_fg_convert_mah_to_permille(di, di->bat_cap.mah); + } + + return di->bat_cap.mah; +} + +/** + * ab8500_fg_capacity_level() - Get the battery capacity level + * @di: pointer to the ab8500_fg structure + * + * Get the battery capacity level based on the capacity in percent + */ +static int ab8500_fg_capacity_level(struct ab8500_fg *di) +{ + int ret, percent; + + percent = DIV_ROUND_CLOSEST(di->bat_cap.permille, 10); + + if (percent <= di->bm->cap_levels->critical || + di->flags.low_bat) + ret = POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL; + else if (percent <= di->bm->cap_levels->low) + ret = POWER_SUPPLY_CAPACITY_LEVEL_LOW; + else if (percent <= di->bm->cap_levels->normal) + ret = POWER_SUPPLY_CAPACITY_LEVEL_NORMAL; + else if (percent <= di->bm->cap_levels->high) + ret = POWER_SUPPLY_CAPACITY_LEVEL_HIGH; + else + ret = POWER_SUPPLY_CAPACITY_LEVEL_FULL; + + return ret; +} + +/** + * ab8500_fg_calculate_scaled_capacity() - Capacity scaling + * @di: pointer to the ab8500_fg structure + * + * Calculates the capacity to be shown to upper layers. Scales the capacity + * to have 100% as a reference from the actual capacity upon removal of charger + * when charging is in maintenance mode. + */ +static int ab8500_fg_calculate_scaled_capacity(struct ab8500_fg *di) +{ + struct ab8500_fg_cap_scaling *cs = &di->bat_cap.cap_scale; + int capacity = di->bat_cap.prev_percent; + + if (!cs->enable) + return capacity; + + /* + * As long as we are in fully charge mode scale the capacity + * to show 100%. + */ + if (di->flags.fully_charged) { + cs->cap_to_scale[0] = 100; + cs->cap_to_scale[1] = + max(capacity, di->bm->fg_params->maint_thres); + dev_dbg(di->dev, "Scale cap with %d/%d\n", + cs->cap_to_scale[0], cs->cap_to_scale[1]); + } + + /* Calculates the scaled capacity. */ + if ((cs->cap_to_scale[0] != cs->cap_to_scale[1]) + && (cs->cap_to_scale[1] > 0)) + capacity = min(100, + DIV_ROUND_CLOSEST(di->bat_cap.prev_percent * + cs->cap_to_scale[0], + cs->cap_to_scale[1])); + + if (di->flags.charging) { + if (capacity < cs->disable_cap_level) { + cs->disable_cap_level = capacity; + dev_dbg(di->dev, "Cap to stop scale lowered %d%%\n", + cs->disable_cap_level); + } else if (!di->flags.fully_charged) { + if (di->bat_cap.prev_percent >= + cs->disable_cap_level) { + dev_dbg(di->dev, "Disabling scaled capacity\n"); + cs->enable = false; + capacity = di->bat_cap.prev_percent; + } else { + dev_dbg(di->dev, + "Waiting in cap to level %d%%\n", + cs->disable_cap_level); + capacity = cs->disable_cap_level; + } + } + } + + return capacity; +} + +/** + * ab8500_fg_update_cap_scalers() - Capacity scaling + * @di: pointer to the ab8500_fg structure + * + * To be called when state change from charge<->discharge to update + * the capacity scalers. + */ +static void ab8500_fg_update_cap_scalers(struct ab8500_fg *di) +{ + struct ab8500_fg_cap_scaling *cs = &di->bat_cap.cap_scale; + + if (!cs->enable) + return; + if (di->flags.charging) { + di->bat_cap.cap_scale.disable_cap_level = + di->bat_cap.cap_scale.scaled_cap; + dev_dbg(di->dev, "Cap to stop scale at charge %d%%\n", + di->bat_cap.cap_scale.disable_cap_level); + } else { + if (cs->scaled_cap != 100) { + cs->cap_to_scale[0] = cs->scaled_cap; + cs->cap_to_scale[1] = di->bat_cap.prev_percent; + } else { + cs->cap_to_scale[0] = 100; + cs->cap_to_scale[1] = + max(di->bat_cap.prev_percent, + di->bm->fg_params->maint_thres); + } + + dev_dbg(di->dev, "Cap to scale at discharge %d/%d\n", + cs->cap_to_scale[0], cs->cap_to_scale[1]); + } +} + +/** + * ab8500_fg_check_capacity_limits() - Check if capacity has changed + * @di: pointer to the ab8500_fg structure + * @init: capacity is allowed to go up in init mode + * + * Check if capacity or capacity limit has changed and notify the system + * about it using the power_supply framework + */ +static void ab8500_fg_check_capacity_limits(struct ab8500_fg *di, bool init) +{ + bool changed = false; + int percent = DIV_ROUND_CLOSEST(di->bat_cap.permille, 10); + + di->bat_cap.level = ab8500_fg_capacity_level(di); + + if (di->bat_cap.level != di->bat_cap.prev_level) { + /* + * We do not allow reported capacity level to go up + * unless we're charging or if we're in init + */ + if (!(!di->flags.charging && di->bat_cap.level > + di->bat_cap.prev_level) || init) { + dev_dbg(di->dev, "level changed from %d to %d\n", + di->bat_cap.prev_level, + di->bat_cap.level); + di->bat_cap.prev_level = di->bat_cap.level; + changed = true; + } else { + dev_dbg(di->dev, "level not allowed to go up " + "since no charger is connected: %d to %d\n", + di->bat_cap.prev_level, + di->bat_cap.level); + } + } + + /* + * If we have received the LOW_BAT IRQ, set capacity to 0 to initiate + * shutdown + */ + if (di->flags.low_bat) { + dev_dbg(di->dev, "Battery low, set capacity to 0\n"); + di->bat_cap.prev_percent = 0; + di->bat_cap.permille = 0; + percent = 0; + di->bat_cap.prev_mah = 0; + di->bat_cap.mah = 0; + changed = true; + } else if (di->flags.fully_charged) { + /* + * We report 100% if algorithm reported fully charged + * and show 100% during maintenance charging (scaling). + */ + if (di->flags.force_full) { + di->bat_cap.prev_percent = percent; + di->bat_cap.prev_mah = di->bat_cap.mah; + + changed = true; + + if (!di->bat_cap.cap_scale.enable && + di->bm->capacity_scaling) { + di->bat_cap.cap_scale.enable = true; + di->bat_cap.cap_scale.cap_to_scale[0] = 100; + di->bat_cap.cap_scale.cap_to_scale[1] = + di->bat_cap.prev_percent; + di->bat_cap.cap_scale.disable_cap_level = 100; + } + } else if (di->bat_cap.prev_percent != percent) { + dev_dbg(di->dev, + "battery reported full " + "but capacity dropping: %d\n", + percent); + di->bat_cap.prev_percent = percent; + di->bat_cap.prev_mah = di->bat_cap.mah; + + changed = true; + } + } else if (di->bat_cap.prev_percent != percent) { + if (percent == 0) { + /* + * We will not report 0% unless we've got + * the LOW_BAT IRQ, no matter what the FG + * algorithm says. + */ + di->bat_cap.prev_percent = 1; + percent = 1; + + changed = true; + } else if (!(!di->flags.charging && + percent > di->bat_cap.prev_percent) || init) { + /* + * We do not allow reported capacity to go up + * unless we're charging or if we're in init + */ + dev_dbg(di->dev, + "capacity changed from %d to %d (%d)\n", + di->bat_cap.prev_percent, + percent, + di->bat_cap.permille); + di->bat_cap.prev_percent = percent; + di->bat_cap.prev_mah = di->bat_cap.mah; + + changed = true; + } else { + dev_dbg(di->dev, "capacity not allowed to go up since " + "no charger is connected: %d to %d (%d)\n", + di->bat_cap.prev_percent, + percent, + di->bat_cap.permille); + } + } + + if (changed) { + if (di->bm->capacity_scaling) { + di->bat_cap.cap_scale.scaled_cap = + ab8500_fg_calculate_scaled_capacity(di); + + dev_info(di->dev, "capacity=%d (%d)\n", + di->bat_cap.prev_percent, + di->bat_cap.cap_scale.scaled_cap); + } + power_supply_changed(di->fg_psy); + if (di->flags.fully_charged && di->flags.force_full) { + dev_dbg(di->dev, "Battery full, notifying.\n"); + di->flags.force_full = false; + sysfs_notify(&di->fg_kobject, NULL, "charge_full"); + } + sysfs_notify(&di->fg_kobject, NULL, "charge_now"); + } +} + +static void ab8500_fg_charge_state_to(struct ab8500_fg *di, + enum ab8500_fg_charge_state new_state) +{ + dev_dbg(di->dev, "Charge state from %d [%s] to %d [%s]\n", + di->charge_state, + charge_state[di->charge_state], + new_state, + charge_state[new_state]); + + di->charge_state = new_state; +} + +static void ab8500_fg_discharge_state_to(struct ab8500_fg *di, + enum ab8500_fg_discharge_state new_state) +{ + dev_dbg(di->dev, "Disharge state from %d [%s] to %d [%s]\n", + di->discharge_state, + discharge_state[di->discharge_state], + new_state, + discharge_state[new_state]); + + di->discharge_state = new_state; +} + +/** + * ab8500_fg_algorithm_charging() - FG algorithm for when charging + * @di: pointer to the ab8500_fg structure + * + * Battery capacity calculation state machine for when we're charging + */ +static void ab8500_fg_algorithm_charging(struct ab8500_fg *di) +{ + /* + * If we change to discharge mode + * we should start with recovery + */ + if (di->discharge_state != AB8500_FG_DISCHARGE_INIT_RECOVERY) + ab8500_fg_discharge_state_to(di, + AB8500_FG_DISCHARGE_INIT_RECOVERY); + + switch (di->charge_state) { + case AB8500_FG_CHARGE_INIT: + di->fg_samples = SEC_TO_SAMPLE( + di->bm->fg_params->accu_charging); + + ab8500_fg_coulomb_counter(di, true); + ab8500_fg_charge_state_to(di, AB8500_FG_CHARGE_READOUT); + + break; + + case AB8500_FG_CHARGE_READOUT: + /* + * Read the FG and calculate the new capacity + */ + mutex_lock(&di->cc_lock); + if (!di->flags.conv_done && !di->flags.force_full) { + /* Wasn't the CC IRQ that got us here */ + mutex_unlock(&di->cc_lock); + dev_dbg(di->dev, "%s CC conv not done\n", + __func__); + + break; + } + di->flags.conv_done = false; + mutex_unlock(&di->cc_lock); + + ab8500_fg_calc_cap_charging(di); + + break; + + default: + break; + } + + /* Check capacity limits */ + ab8500_fg_check_capacity_limits(di, false); +} + +static void force_capacity(struct ab8500_fg *di) +{ + int cap; + + ab8500_fg_clear_cap_samples(di); + cap = di->bat_cap.user_mah; + if (cap > di->bat_cap.max_mah_design) { + dev_dbg(di->dev, "Remaining cap %d can't be bigger than total" + " %d\n", cap, di->bat_cap.max_mah_design); + cap = di->bat_cap.max_mah_design; + } + ab8500_fg_fill_cap_sample(di, di->bat_cap.user_mah); + di->bat_cap.permille = ab8500_fg_convert_mah_to_permille(di, cap); + di->bat_cap.mah = cap; + ab8500_fg_check_capacity_limits(di, true); +} + +static bool check_sysfs_capacity(struct ab8500_fg *di) +{ + int cap, lower, upper; + int cap_permille; + + cap = di->bat_cap.user_mah; + + cap_permille = ab8500_fg_convert_mah_to_permille(di, + di->bat_cap.user_mah); + + lower = di->bat_cap.permille - di->bm->fg_params->user_cap_limit * 10; + upper = di->bat_cap.permille + di->bm->fg_params->user_cap_limit * 10; + + if (lower < 0) + lower = 0; + /* 1000 is permille, -> 100 percent */ + if (upper > 1000) + upper = 1000; + + dev_dbg(di->dev, "Capacity limits:" + " (Lower: %d User: %d Upper: %d) [user: %d, was: %d]\n", + lower, cap_permille, upper, cap, di->bat_cap.mah); + + /* If within limits, use the saved capacity and exit estimation...*/ + if (cap_permille > lower && cap_permille < upper) { + dev_dbg(di->dev, "OK! Using users cap %d uAh now\n", cap); + force_capacity(di); + return true; + } + dev_dbg(di->dev, "Capacity from user out of limits, ignoring"); + return false; +} + +/** + * ab8500_fg_algorithm_discharging() - FG algorithm for when discharging + * @di: pointer to the ab8500_fg structure + * + * Battery capacity calculation state machine for when we're discharging + */ +static void ab8500_fg_algorithm_discharging(struct ab8500_fg *di) +{ + int sleep_time; + + /* If we change to charge mode we should start with init */ + if (di->charge_state != AB8500_FG_CHARGE_INIT) + ab8500_fg_charge_state_to(di, AB8500_FG_CHARGE_INIT); + + switch (di->discharge_state) { + case AB8500_FG_DISCHARGE_INIT: + /* We use the FG IRQ to work on */ + di->init_cnt = 0; + di->fg_samples = SEC_TO_SAMPLE(di->bm->fg_params->init_timer); + ab8500_fg_coulomb_counter(di, true); + ab8500_fg_discharge_state_to(di, + AB8500_FG_DISCHARGE_INITMEASURING); + + /* Intentional fallthrough */ + case AB8500_FG_DISCHARGE_INITMEASURING: + /* + * Discard a number of samples during startup. + * After that, use compensated voltage for a few + * samples to get an initial capacity. + * Then go to READOUT + */ + sleep_time = di->bm->fg_params->init_timer; + + /* Discard the first [x] seconds */ + if (di->init_cnt > di->bm->fg_params->init_discard_time) { + ab8500_fg_calc_cap_discharge_voltage(di, true); + + ab8500_fg_check_capacity_limits(di, true); + } + + di->init_cnt += sleep_time; + if (di->init_cnt > di->bm->fg_params->init_total_time) + ab8500_fg_discharge_state_to(di, + AB8500_FG_DISCHARGE_READOUT_INIT); + + break; + + case AB8500_FG_DISCHARGE_INIT_RECOVERY: + di->recovery_cnt = 0; + di->recovery_needed = true; + ab8500_fg_discharge_state_to(di, + AB8500_FG_DISCHARGE_RECOVERY); + + /* Intentional fallthrough */ + + case AB8500_FG_DISCHARGE_RECOVERY: + sleep_time = di->bm->fg_params->recovery_sleep_timer; + + /* + * We should check the power consumption + * If low, go to READOUT (after x min) or + * RECOVERY_SLEEP if time left. + * If high, go to READOUT + */ + di->inst_curr = ab8500_fg_inst_curr_blocking(di); + + if (ab8500_fg_is_low_curr(di, di->inst_curr)) { + if (di->recovery_cnt > + di->bm->fg_params->recovery_total_time) { + di->fg_samples = SEC_TO_SAMPLE( + di->bm->fg_params->accu_high_curr); + ab8500_fg_coulomb_counter(di, true); + ab8500_fg_discharge_state_to(di, + AB8500_FG_DISCHARGE_READOUT); + di->recovery_needed = false; + } else { + queue_delayed_work(di->fg_wq, + &di->fg_periodic_work, + sleep_time * HZ); + } + di->recovery_cnt += sleep_time; + } else { + di->fg_samples = SEC_TO_SAMPLE( + di->bm->fg_params->accu_high_curr); + ab8500_fg_coulomb_counter(di, true); + ab8500_fg_discharge_state_to(di, + AB8500_FG_DISCHARGE_READOUT); + } + break; + + case AB8500_FG_DISCHARGE_READOUT_INIT: + di->fg_samples = SEC_TO_SAMPLE( + di->bm->fg_params->accu_high_curr); + ab8500_fg_coulomb_counter(di, true); + ab8500_fg_discharge_state_to(di, + AB8500_FG_DISCHARGE_READOUT); + break; + + case AB8500_FG_DISCHARGE_READOUT: + di->inst_curr = ab8500_fg_inst_curr_blocking(di); + + if (ab8500_fg_is_low_curr(di, di->inst_curr)) { + /* Detect mode change */ + if (di->high_curr_mode) { + di->high_curr_mode = false; + di->high_curr_cnt = 0; + } + + if (di->recovery_needed) { + ab8500_fg_discharge_state_to(di, + AB8500_FG_DISCHARGE_INIT_RECOVERY); + + queue_delayed_work(di->fg_wq, + &di->fg_periodic_work, 0); + + break; + } + + ab8500_fg_calc_cap_discharge_voltage(di, true); + } else { + mutex_lock(&di->cc_lock); + if (!di->flags.conv_done) { + /* Wasn't the CC IRQ that got us here */ + mutex_unlock(&di->cc_lock); + dev_dbg(di->dev, "%s CC conv not done\n", + __func__); + + break; + } + di->flags.conv_done = false; + mutex_unlock(&di->cc_lock); + + /* Detect mode change */ + if (!di->high_curr_mode) { + di->high_curr_mode = true; + di->high_curr_cnt = 0; + } + + di->high_curr_cnt += + di->bm->fg_params->accu_high_curr; + if (di->high_curr_cnt > + di->bm->fg_params->high_curr_time) + di->recovery_needed = true; + + ab8500_fg_calc_cap_discharge_fg(di); + } + + ab8500_fg_check_capacity_limits(di, false); + + break; + + case AB8500_FG_DISCHARGE_WAKEUP: + ab8500_fg_calc_cap_discharge_voltage(di, true); + + di->fg_samples = SEC_TO_SAMPLE( + di->bm->fg_params->accu_high_curr); + ab8500_fg_coulomb_counter(di, true); + ab8500_fg_discharge_state_to(di, + AB8500_FG_DISCHARGE_READOUT); + + ab8500_fg_check_capacity_limits(di, false); + + break; + + default: + break; + } +} + +/** + * ab8500_fg_algorithm_calibrate() - Internal columb counter offset calibration + * @di: pointer to the ab8500_fg structure + * + */ +static void ab8500_fg_algorithm_calibrate(struct ab8500_fg *di) +{ + int ret; + + switch (di->calib_state) { + case AB8500_FG_CALIB_INIT: + dev_dbg(di->dev, "Calibration ongoing...\n"); + + ret = abx500_mask_and_set_register_interruptible(di->dev, + AB8500_GAS_GAUGE, AB8500_GASG_CC_CTRL_REG, + CC_INT_CAL_N_AVG_MASK, CC_INT_CAL_SAMPLES_8); + if (ret < 0) + goto err; + + ret = abx500_mask_and_set_register_interruptible(di->dev, + AB8500_GAS_GAUGE, AB8500_GASG_CC_CTRL_REG, + CC_INTAVGOFFSET_ENA, CC_INTAVGOFFSET_ENA); + if (ret < 0) + goto err; + di->calib_state = AB8500_FG_CALIB_WAIT; + break; + case AB8500_FG_CALIB_END: + ret = abx500_mask_and_set_register_interruptible(di->dev, + AB8500_GAS_GAUGE, AB8500_GASG_CC_CTRL_REG, + CC_MUXOFFSET, CC_MUXOFFSET); + if (ret < 0) + goto err; + di->flags.calibrate = false; + dev_dbg(di->dev, "Calibration done...\n"); + queue_delayed_work(di->fg_wq, &di->fg_periodic_work, 0); + break; + case AB8500_FG_CALIB_WAIT: + dev_dbg(di->dev, "Calibration WFI\n"); + default: + break; + } + return; +err: + /* Something went wrong, don't calibrate then */ + dev_err(di->dev, "failed to calibrate the CC\n"); + di->flags.calibrate = false; + di->calib_state = AB8500_FG_CALIB_INIT; + queue_delayed_work(di->fg_wq, &di->fg_periodic_work, 0); +} + +/** + * ab8500_fg_algorithm() - Entry point for the FG algorithm + * @di: pointer to the ab8500_fg structure + * + * Entry point for the battery capacity calculation state machine + */ +static void ab8500_fg_algorithm(struct ab8500_fg *di) +{ + if (di->flags.calibrate) + ab8500_fg_algorithm_calibrate(di); + else { + if (di->flags.charging) + ab8500_fg_algorithm_charging(di); + else + ab8500_fg_algorithm_discharging(di); + } + + dev_dbg(di->dev, "[FG_DATA] %d %d %d %d %d %d %d %d %d %d " + "%d %d %d %d %d %d %d\n", + di->bat_cap.max_mah_design, + di->bat_cap.max_mah, + di->bat_cap.mah, + di->bat_cap.permille, + di->bat_cap.level, + di->bat_cap.prev_mah, + di->bat_cap.prev_percent, + di->bat_cap.prev_level, + di->vbat, + di->inst_curr, + di->avg_curr, + di->accu_charge, + di->flags.charging, + di->charge_state, + di->discharge_state, + di->high_curr_mode, + di->recovery_needed); +} + +/** + * ab8500_fg_periodic_work() - Run the FG state machine periodically + * @work: pointer to the work_struct structure + * + * Work queue function for periodic work + */ +static void ab8500_fg_periodic_work(struct work_struct *work) +{ + struct ab8500_fg *di = container_of(work, struct ab8500_fg, + fg_periodic_work.work); + + if (di->init_capacity) { + /* Get an initial capacity calculation */ + ab8500_fg_calc_cap_discharge_voltage(di, true); + ab8500_fg_check_capacity_limits(di, true); + di->init_capacity = false; + + queue_delayed_work(di->fg_wq, &di->fg_periodic_work, 0); + } else if (di->flags.user_cap) { + if (check_sysfs_capacity(di)) { + ab8500_fg_check_capacity_limits(di, true); + if (di->flags.charging) + ab8500_fg_charge_state_to(di, + AB8500_FG_CHARGE_INIT); + else + ab8500_fg_discharge_state_to(di, + AB8500_FG_DISCHARGE_READOUT_INIT); + } + di->flags.user_cap = false; + queue_delayed_work(di->fg_wq, &di->fg_periodic_work, 0); + } else + ab8500_fg_algorithm(di); + +} + +/** + * ab8500_fg_check_hw_failure_work() - Check OVV_BAT condition + * @work: pointer to the work_struct structure + * + * Work queue function for checking the OVV_BAT condition + */ +static void ab8500_fg_check_hw_failure_work(struct work_struct *work) +{ + int ret; + u8 reg_value; + + struct ab8500_fg *di = container_of(work, struct ab8500_fg, + fg_check_hw_failure_work.work); + + /* + * If we have had a battery over-voltage situation, + * check ovv-bit to see if it should be reset. + */ + ret = abx500_get_register_interruptible(di->dev, + AB8500_CHARGER, AB8500_CH_STAT_REG, + ®_value); + if (ret < 0) { + dev_err(di->dev, "%s ab8500 read failed\n", __func__); + return; + } + if ((reg_value & BATT_OVV) == BATT_OVV) { + if (!di->flags.bat_ovv) { + dev_dbg(di->dev, "Battery OVV\n"); + di->flags.bat_ovv = true; + power_supply_changed(di->fg_psy); + } + /* Not yet recovered from ovv, reschedule this test */ + queue_delayed_work(di->fg_wq, &di->fg_check_hw_failure_work, + HZ); + } else { + dev_dbg(di->dev, "Battery recovered from OVV\n"); + di->flags.bat_ovv = false; + power_supply_changed(di->fg_psy); + } +} + +/** + * ab8500_fg_low_bat_work() - Check LOW_BAT condition + * @work: pointer to the work_struct structure + * + * Work queue function for checking the LOW_BAT condition + */ +static void ab8500_fg_low_bat_work(struct work_struct *work) +{ + int vbat; + + struct ab8500_fg *di = container_of(work, struct ab8500_fg, + fg_low_bat_work.work); + + vbat = ab8500_fg_bat_voltage(di); + + /* Check if LOW_BAT still fulfilled */ + if (vbat < di->bm->fg_params->lowbat_threshold) { + /* Is it time to shut down? */ + if (di->low_bat_cnt < 1) { + di->flags.low_bat = true; + dev_warn(di->dev, "Shut down pending...\n"); + } else { + /* + * Else we need to re-schedule this check to be able to detect + * if the voltage increases again during charging or + * due to decreasing load. + */ + di->low_bat_cnt--; + dev_warn(di->dev, "Battery voltage still LOW\n"); + queue_delayed_work(di->fg_wq, &di->fg_low_bat_work, + round_jiffies(LOW_BAT_CHECK_INTERVAL)); + } + } else { + di->flags.low_bat_delay = false; + di->low_bat_cnt = 10; + dev_warn(di->dev, "Battery voltage OK again\n"); + } + + /* This is needed to dispatch LOW_BAT */ + ab8500_fg_check_capacity_limits(di, false); +} + +/** + * ab8500_fg_battok_calc - calculate the bit pattern corresponding + * to the target voltage. + * @di: pointer to the ab8500_fg structure + * @target target voltage + * + * Returns bit pattern closest to the target voltage + * valid return values are 0-14. (0-BATT_OK_MAX_NR_INCREMENTS) + */ + +static int ab8500_fg_battok_calc(struct ab8500_fg *di, int target) +{ + if (target > BATT_OK_MIN + + (BATT_OK_INCREMENT * BATT_OK_MAX_NR_INCREMENTS)) + return BATT_OK_MAX_NR_INCREMENTS; + if (target < BATT_OK_MIN) + return 0; + return (target - BATT_OK_MIN) / BATT_OK_INCREMENT; +} + +/** + * ab8500_fg_battok_init_hw_register - init battok levels + * @di: pointer to the ab8500_fg structure + * + */ + +static int ab8500_fg_battok_init_hw_register(struct ab8500_fg *di) +{ + int selected; + int sel0; + int sel1; + int cbp_sel0; + int cbp_sel1; + int ret; + int new_val; + + sel0 = di->bm->fg_params->battok_falling_th_sel0; + sel1 = di->bm->fg_params->battok_raising_th_sel1; + + cbp_sel0 = ab8500_fg_battok_calc(di, sel0); + cbp_sel1 = ab8500_fg_battok_calc(di, sel1); + + selected = BATT_OK_MIN + cbp_sel0 * BATT_OK_INCREMENT; + + if (selected != sel0) + dev_warn(di->dev, "Invalid voltage step:%d, using %d %d\n", + sel0, selected, cbp_sel0); + + selected = BATT_OK_MIN + cbp_sel1 * BATT_OK_INCREMENT; + + if (selected != sel1) + dev_warn(di->dev, "Invalid voltage step:%d, using %d %d\n", + sel1, selected, cbp_sel1); + + new_val = cbp_sel0 | (cbp_sel1 << 4); + + dev_dbg(di->dev, "using: %x %d %d\n", new_val, cbp_sel0, cbp_sel1); + ret = abx500_set_register_interruptible(di->dev, AB8500_SYS_CTRL2_BLOCK, + AB8500_BATT_OK_REG, new_val); + return ret; +} + +/** + * ab8500_fg_instant_work() - Run the FG state machine instantly + * @work: pointer to the work_struct structure + * + * Work queue function for instant work + */ +static void ab8500_fg_instant_work(struct work_struct *work) +{ + struct ab8500_fg *di = container_of(work, struct ab8500_fg, fg_work); + + ab8500_fg_algorithm(di); +} + +/** + * ab8500_fg_cc_data_end_handler() - end of data conversion isr. + * @irq: interrupt number + * @_di: pointer to the ab8500_fg structure + * + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab8500_fg_cc_data_end_handler(int irq, void *_di) +{ + struct ab8500_fg *di = _di; + if (!di->nbr_cceoc_irq_cnt) { + di->nbr_cceoc_irq_cnt++; + complete(&di->ab8500_fg_started); + } else { + di->nbr_cceoc_irq_cnt = 0; + complete(&di->ab8500_fg_complete); + } + return IRQ_HANDLED; +} + +/** + * ab8500_fg_cc_int_calib_handler () - end of calibration isr. + * @irq: interrupt number + * @_di: pointer to the ab8500_fg structure + * + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab8500_fg_cc_int_calib_handler(int irq, void *_di) +{ + struct ab8500_fg *di = _di; + di->calib_state = AB8500_FG_CALIB_END; + queue_delayed_work(di->fg_wq, &di->fg_periodic_work, 0); + return IRQ_HANDLED; +} + +/** + * ab8500_fg_cc_convend_handler() - isr to get battery avg current. + * @irq: interrupt number + * @_di: pointer to the ab8500_fg structure + * + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab8500_fg_cc_convend_handler(int irq, void *_di) +{ + struct ab8500_fg *di = _di; + + queue_work(di->fg_wq, &di->fg_acc_cur_work); + + return IRQ_HANDLED; +} + +/** + * ab8500_fg_batt_ovv_handler() - Battery OVV occured + * @irq: interrupt number + * @_di: pointer to the ab8500_fg structure + * + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab8500_fg_batt_ovv_handler(int irq, void *_di) +{ + struct ab8500_fg *di = _di; + + dev_dbg(di->dev, "Battery OVV\n"); + + /* Schedule a new HW failure check */ + queue_delayed_work(di->fg_wq, &di->fg_check_hw_failure_work, 0); + + return IRQ_HANDLED; +} + +/** + * ab8500_fg_lowbatf_handler() - Battery voltage is below LOW threshold + * @irq: interrupt number + * @_di: pointer to the ab8500_fg structure + * + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab8500_fg_lowbatf_handler(int irq, void *_di) +{ + struct ab8500_fg *di = _di; + + /* Initiate handling in ab8500_fg_low_bat_work() if not already initiated. */ + if (!di->flags.low_bat_delay) { + dev_warn(di->dev, "Battery voltage is below LOW threshold\n"); + di->flags.low_bat_delay = true; + /* + * Start a timer to check LOW_BAT again after some time + * This is done to avoid shutdown on single voltage dips + */ + queue_delayed_work(di->fg_wq, &di->fg_low_bat_work, + round_jiffies(LOW_BAT_CHECK_INTERVAL)); + } + return IRQ_HANDLED; +} + +/** + * ab8500_fg_get_property() - get the fg properties + * @psy: pointer to the power_supply structure + * @psp: pointer to the power_supply_property structure + * @val: pointer to the power_supply_propval union + * + * This function gets called when an application tries to get the + * fg properties by reading the sysfs files. + * voltage_now: battery voltage + * current_now: battery instant current + * current_avg: battery average current + * charge_full_design: capacity where battery is considered full + * charge_now: battery capacity in nAh + * capacity: capacity in percent + * capacity_level: capacity level + * + * Returns error code in case of failure else 0 on success + */ +static int ab8500_fg_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct ab8500_fg *di = power_supply_get_drvdata(psy); + + /* + * If battery is identified as unknown and charging of unknown + * batteries is disabled, we always report 100% capacity and + * capacity level UNKNOWN, since we can't calculate + * remaining capacity + */ + + switch (psp) { + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + if (di->flags.bat_ovv) + val->intval = BATT_OVV_VALUE * 1000; + else + val->intval = di->vbat * 1000; + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + val->intval = di->inst_curr * 1000; + break; + case POWER_SUPPLY_PROP_CURRENT_AVG: + val->intval = di->avg_curr * 1000; + break; + case POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN: + val->intval = ab8500_fg_convert_mah_to_uwh(di, + di->bat_cap.max_mah_design); + break; + case POWER_SUPPLY_PROP_ENERGY_FULL: + val->intval = ab8500_fg_convert_mah_to_uwh(di, + di->bat_cap.max_mah); + break; + case POWER_SUPPLY_PROP_ENERGY_NOW: + if (di->flags.batt_unknown && !di->bm->chg_unknown_bat && + di->flags.batt_id_received) + val->intval = ab8500_fg_convert_mah_to_uwh(di, + di->bat_cap.max_mah); + else + val->intval = ab8500_fg_convert_mah_to_uwh(di, + di->bat_cap.prev_mah); + break; + case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: + val->intval = di->bat_cap.max_mah_design; + break; + case POWER_SUPPLY_PROP_CHARGE_FULL: + val->intval = di->bat_cap.max_mah; + break; + case POWER_SUPPLY_PROP_CHARGE_NOW: + if (di->flags.batt_unknown && !di->bm->chg_unknown_bat && + di->flags.batt_id_received) + val->intval = di->bat_cap.max_mah; + else + val->intval = di->bat_cap.prev_mah; + break; + case POWER_SUPPLY_PROP_CAPACITY: + if (di->flags.batt_unknown && !di->bm->chg_unknown_bat && + di->flags.batt_id_received) + val->intval = 100; + else + val->intval = di->bat_cap.prev_percent; + break; + case POWER_SUPPLY_PROP_CAPACITY_LEVEL: + if (di->flags.batt_unknown && !di->bm->chg_unknown_bat && + di->flags.batt_id_received) + val->intval = POWER_SUPPLY_CAPACITY_LEVEL_UNKNOWN; + else + val->intval = di->bat_cap.prev_level; + break; + default: + return -EINVAL; + } + return 0; +} + +static int ab8500_fg_get_ext_psy_data(struct device *dev, void *data) +{ + struct power_supply *psy; + struct power_supply *ext = dev_get_drvdata(dev); + const char **supplicants = (const char **)ext->supplied_to; + struct ab8500_fg *di; + union power_supply_propval ret; + int j; + + psy = (struct power_supply *)data; + di = power_supply_get_drvdata(psy); + + /* + * For all psy where the name of your driver + * appears in any supplied_to + */ + j = match_string(supplicants, ext->num_supplicants, psy->desc->name); + if (j < 0) + return 0; + + /* Go through all properties for the psy */ + for (j = 0; j < ext->desc->num_properties; j++) { + enum power_supply_property prop; + prop = ext->desc->properties[j]; + + if (power_supply_get_property(ext, prop, &ret)) + continue; + + switch (prop) { + case POWER_SUPPLY_PROP_STATUS: + switch (ext->desc->type) { + case POWER_SUPPLY_TYPE_BATTERY: + switch (ret.intval) { + case POWER_SUPPLY_STATUS_UNKNOWN: + case POWER_SUPPLY_STATUS_DISCHARGING: + case POWER_SUPPLY_STATUS_NOT_CHARGING: + if (!di->flags.charging) + break; + di->flags.charging = false; + di->flags.fully_charged = false; + if (di->bm->capacity_scaling) + ab8500_fg_update_cap_scalers(di); + queue_work(di->fg_wq, &di->fg_work); + break; + case POWER_SUPPLY_STATUS_FULL: + if (di->flags.fully_charged) + break; + di->flags.fully_charged = true; + di->flags.force_full = true; + /* Save current capacity as maximum */ + di->bat_cap.max_mah = di->bat_cap.mah; + queue_work(di->fg_wq, &di->fg_work); + break; + case POWER_SUPPLY_STATUS_CHARGING: + if (di->flags.charging && + !di->flags.fully_charged) + break; + di->flags.charging = true; + di->flags.fully_charged = false; + if (di->bm->capacity_scaling) + ab8500_fg_update_cap_scalers(di); + queue_work(di->fg_wq, &di->fg_work); + break; + }; + default: + break; + }; + break; + case POWER_SUPPLY_PROP_TECHNOLOGY: + switch (ext->desc->type) { + case POWER_SUPPLY_TYPE_BATTERY: + if (!di->flags.batt_id_received && + di->bm->batt_id != BATTERY_UNKNOWN) { + const struct abx500_battery_type *b; + + b = &(di->bm->bat_type[di->bm->batt_id]); + + di->flags.batt_id_received = true; + + di->bat_cap.max_mah_design = + MILLI_TO_MICRO * + b->charge_full_design; + + di->bat_cap.max_mah = + di->bat_cap.max_mah_design; + + di->vbat_nom = b->nominal_voltage; + } + + if (ret.intval) + di->flags.batt_unknown = false; + else + di->flags.batt_unknown = true; + break; + default: + break; + } + break; + case POWER_SUPPLY_PROP_TEMP: + switch (ext->desc->type) { + case POWER_SUPPLY_TYPE_BATTERY: + if (di->flags.batt_id_received) + di->bat_temp = ret.intval; + break; + default: + break; + } + break; + default: + break; + } + } + return 0; +} + +/** + * ab8500_fg_init_hw_registers() - Set up FG related registers + * @di: pointer to the ab8500_fg structure + * + * Set up battery OVV, low battery voltage registers + */ +static int ab8500_fg_init_hw_registers(struct ab8500_fg *di) +{ + int ret; + + /* Set VBAT OVV threshold */ + ret = abx500_mask_and_set_register_interruptible(di->dev, + AB8500_CHARGER, + AB8500_BATT_OVV, + BATT_OVV_TH_4P75, + BATT_OVV_TH_4P75); + if (ret) { + dev_err(di->dev, "failed to set BATT_OVV\n"); + goto out; + } + + /* Enable VBAT OVV detection */ + ret = abx500_mask_and_set_register_interruptible(di->dev, + AB8500_CHARGER, + AB8500_BATT_OVV, + BATT_OVV_ENA, + BATT_OVV_ENA); + if (ret) { + dev_err(di->dev, "failed to enable BATT_OVV\n"); + goto out; + } + + /* Low Battery Voltage */ + ret = abx500_set_register_interruptible(di->dev, + AB8500_SYS_CTRL2_BLOCK, + AB8500_LOW_BAT_REG, + ab8500_volt_to_regval( + di->bm->fg_params->lowbat_threshold) << 1 | + LOW_BAT_ENABLE); + if (ret) { + dev_err(di->dev, "%s write failed\n", __func__); + goto out; + } + + /* Battery OK threshold */ + ret = ab8500_fg_battok_init_hw_register(di); + if (ret) { + dev_err(di->dev, "BattOk init write failed.\n"); + goto out; + } + + if (((is_ab8505(di->parent) || is_ab9540(di->parent)) && + abx500_get_chip_id(di->dev) >= AB8500_CUT2P0) + || is_ab8540(di->parent)) { + ret = abx500_set_register_interruptible(di->dev, AB8500_RTC, + AB8505_RTC_PCUT_MAX_TIME_REG, di->bm->fg_params->pcut_max_time); + + if (ret) { + dev_err(di->dev, "%s write failed AB8505_RTC_PCUT_MAX_TIME_REG\n", __func__); + goto out; + }; + + ret = abx500_set_register_interruptible(di->dev, AB8500_RTC, + AB8505_RTC_PCUT_FLAG_TIME_REG, di->bm->fg_params->pcut_flag_time); + + if (ret) { + dev_err(di->dev, "%s write failed AB8505_RTC_PCUT_FLAG_TIME_REG\n", __func__); + goto out; + }; + + ret = abx500_set_register_interruptible(di->dev, AB8500_RTC, + AB8505_RTC_PCUT_RESTART_REG, di->bm->fg_params->pcut_max_restart); + + if (ret) { + dev_err(di->dev, "%s write failed AB8505_RTC_PCUT_RESTART_REG\n", __func__); + goto out; + }; + + ret = abx500_set_register_interruptible(di->dev, AB8500_RTC, + AB8505_RTC_PCUT_DEBOUNCE_REG, di->bm->fg_params->pcut_debounce_time); + + if (ret) { + dev_err(di->dev, "%s write failed AB8505_RTC_PCUT_DEBOUNCE_REG\n", __func__); + goto out; + }; + + ret = abx500_set_register_interruptible(di->dev, AB8500_RTC, + AB8505_RTC_PCUT_CTL_STATUS_REG, di->bm->fg_params->pcut_enable); + + if (ret) { + dev_err(di->dev, "%s write failed AB8505_RTC_PCUT_CTL_STATUS_REG\n", __func__); + goto out; + }; + } +out: + return ret; +} + +/** + * ab8500_fg_external_power_changed() - callback for power supply changes + * @psy: pointer to the structure power_supply + * + * This function is the entry point of the pointer external_power_changed + * of the structure power_supply. + * This function gets executed when there is a change in any external power + * supply that this driver needs to be notified of. + */ +static void ab8500_fg_external_power_changed(struct power_supply *psy) +{ + struct ab8500_fg *di = power_supply_get_drvdata(psy); + + class_for_each_device(power_supply_class, NULL, + di->fg_psy, ab8500_fg_get_ext_psy_data); +} + +/** + * abab8500_fg_reinit_work() - work to reset the FG algorithm + * @work: pointer to the work_struct structure + * + * Used to reset the current battery capacity to be able to + * retrigger a new voltage base capacity calculation. For + * test and verification purpose. + */ +static void ab8500_fg_reinit_work(struct work_struct *work) +{ + struct ab8500_fg *di = container_of(work, struct ab8500_fg, + fg_reinit_work.work); + + if (di->flags.calibrate == false) { + dev_dbg(di->dev, "Resetting FG state machine to init.\n"); + ab8500_fg_clear_cap_samples(di); + ab8500_fg_calc_cap_discharge_voltage(di, true); + ab8500_fg_charge_state_to(di, AB8500_FG_CHARGE_INIT); + ab8500_fg_discharge_state_to(di, AB8500_FG_DISCHARGE_INIT); + queue_delayed_work(di->fg_wq, &di->fg_periodic_work, 0); + + } else { + dev_err(di->dev, "Residual offset calibration ongoing " + "retrying..\n"); + /* Wait one second until next try*/ + queue_delayed_work(di->fg_wq, &di->fg_reinit_work, + round_jiffies(1)); + } +} + +/* Exposure to the sysfs interface */ + +struct ab8500_fg_sysfs_entry { + struct attribute attr; + ssize_t (*show)(struct ab8500_fg *, char *); + ssize_t (*store)(struct ab8500_fg *, const char *, size_t); +}; + +static ssize_t charge_full_show(struct ab8500_fg *di, char *buf) +{ + return sprintf(buf, "%d\n", di->bat_cap.max_mah); +} + +static ssize_t charge_full_store(struct ab8500_fg *di, const char *buf, + size_t count) +{ + unsigned long charge_full; + ssize_t ret; + + ret = kstrtoul(buf, 10, &charge_full); + + dev_dbg(di->dev, "Ret %zd charge_full %lu", ret, charge_full); + + if (!ret) { + di->bat_cap.max_mah = (int) charge_full; + ret = count; + } + return ret; +} + +static ssize_t charge_now_show(struct ab8500_fg *di, char *buf) +{ + return sprintf(buf, "%d\n", di->bat_cap.prev_mah); +} + +static ssize_t charge_now_store(struct ab8500_fg *di, const char *buf, + size_t count) +{ + unsigned long charge_now; + ssize_t ret; + + ret = kstrtoul(buf, 10, &charge_now); + + dev_dbg(di->dev, "Ret %zd charge_now %lu was %d", + ret, charge_now, di->bat_cap.prev_mah); + + if (!ret) { + di->bat_cap.user_mah = (int) charge_now; + di->flags.user_cap = true; + ret = count; + queue_delayed_work(di->fg_wq, &di->fg_periodic_work, 0); + } + return ret; +} + +static struct ab8500_fg_sysfs_entry charge_full_attr = + __ATTR(charge_full, 0644, charge_full_show, charge_full_store); + +static struct ab8500_fg_sysfs_entry charge_now_attr = + __ATTR(charge_now, 0644, charge_now_show, charge_now_store); + +static ssize_t +ab8500_fg_show(struct kobject *kobj, struct attribute *attr, char *buf) +{ + struct ab8500_fg_sysfs_entry *entry; + struct ab8500_fg *di; + + entry = container_of(attr, struct ab8500_fg_sysfs_entry, attr); + di = container_of(kobj, struct ab8500_fg, fg_kobject); + + if (!entry->show) + return -EIO; + + return entry->show(di, buf); +} +static ssize_t +ab8500_fg_store(struct kobject *kobj, struct attribute *attr, const char *buf, + size_t count) +{ + struct ab8500_fg_sysfs_entry *entry; + struct ab8500_fg *di; + + entry = container_of(attr, struct ab8500_fg_sysfs_entry, attr); + di = container_of(kobj, struct ab8500_fg, fg_kobject); + + if (!entry->store) + return -EIO; + + return entry->store(di, buf, count); +} + +static const struct sysfs_ops ab8500_fg_sysfs_ops = { + .show = ab8500_fg_show, + .store = ab8500_fg_store, +}; + +static struct attribute *ab8500_fg_attrs[] = { + &charge_full_attr.attr, + &charge_now_attr.attr, + NULL, +}; + +static struct kobj_type ab8500_fg_ktype = { + .sysfs_ops = &ab8500_fg_sysfs_ops, + .default_attrs = ab8500_fg_attrs, +}; + +/** + * ab8500_chargalg_sysfs_exit() - de-init of sysfs entry + * @di: pointer to the struct ab8500_chargalg + * + * This function removes the entry in sysfs. + */ +static void ab8500_fg_sysfs_exit(struct ab8500_fg *di) +{ + kobject_del(&di->fg_kobject); +} + +/** + * ab8500_chargalg_sysfs_init() - init of sysfs entry + * @di: pointer to the struct ab8500_chargalg + * + * This function adds an entry in sysfs. + * Returns error code in case of failure else 0(on success) + */ +static int ab8500_fg_sysfs_init(struct ab8500_fg *di) +{ + int ret = 0; + + ret = kobject_init_and_add(&di->fg_kobject, + &ab8500_fg_ktype, + NULL, "battery"); + if (ret < 0) + dev_err(di->dev, "failed to create sysfs entry\n"); + + return ret; +} + +static ssize_t ab8505_powercut_flagtime_read(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int ret; + u8 reg_value; + struct power_supply *psy = dev_get_drvdata(dev); + struct ab8500_fg *di = power_supply_get_drvdata(psy); + + ret = abx500_get_register_interruptible(di->dev, AB8500_RTC, + AB8505_RTC_PCUT_FLAG_TIME_REG, ®_value); + + if (ret < 0) { + dev_err(dev, "Failed to read AB8505_RTC_PCUT_FLAG_TIME_REG\n"); + goto fail; + } + + return scnprintf(buf, PAGE_SIZE, "%d\n", (reg_value & 0x7F)); + +fail: + return ret; +} + +static ssize_t ab8505_powercut_flagtime_write(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + int ret; + long unsigned reg_value; + struct power_supply *psy = dev_get_drvdata(dev); + struct ab8500_fg *di = power_supply_get_drvdata(psy); + + reg_value = simple_strtoul(buf, NULL, 10); + + if (reg_value > 0x7F) { + dev_err(dev, "Incorrect parameter, echo 0 (1.98s) - 127 (15.625ms) for flagtime\n"); + goto fail; + } + + ret = abx500_set_register_interruptible(di->dev, AB8500_RTC, + AB8505_RTC_PCUT_FLAG_TIME_REG, (u8)reg_value); + + if (ret < 0) + dev_err(dev, "Failed to set AB8505_RTC_PCUT_FLAG_TIME_REG\n"); + +fail: + return count; +} + +static ssize_t ab8505_powercut_maxtime_read(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int ret; + u8 reg_value; + struct power_supply *psy = dev_get_drvdata(dev); + struct ab8500_fg *di = power_supply_get_drvdata(psy); + + ret = abx500_get_register_interruptible(di->dev, AB8500_RTC, + AB8505_RTC_PCUT_MAX_TIME_REG, ®_value); + + if (ret < 0) { + dev_err(dev, "Failed to read AB8505_RTC_PCUT_MAX_TIME_REG\n"); + goto fail; + } + + return scnprintf(buf, PAGE_SIZE, "%d\n", (reg_value & 0x7F)); + +fail: + return ret; + +} + +static ssize_t ab8505_powercut_maxtime_write(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + int ret; + int reg_value; + struct power_supply *psy = dev_get_drvdata(dev); + struct ab8500_fg *di = power_supply_get_drvdata(psy); + + reg_value = simple_strtoul(buf, NULL, 10); + if (reg_value > 0x7F) { + dev_err(dev, "Incorrect parameter, echo 0 (0.0s) - 127 (1.98s) for maxtime\n"); + goto fail; + } + + ret = abx500_set_register_interruptible(di->dev, AB8500_RTC, + AB8505_RTC_PCUT_MAX_TIME_REG, (u8)reg_value); + + if (ret < 0) + dev_err(dev, "Failed to set AB8505_RTC_PCUT_MAX_TIME_REG\n"); + +fail: + return count; +} + +static ssize_t ab8505_powercut_restart_read(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int ret; + u8 reg_value; + struct power_supply *psy = dev_get_drvdata(dev); + struct ab8500_fg *di = power_supply_get_drvdata(psy); + + ret = abx500_get_register_interruptible(di->dev, AB8500_RTC, + AB8505_RTC_PCUT_RESTART_REG, ®_value); + + if (ret < 0) { + dev_err(dev, "Failed to read AB8505_RTC_PCUT_RESTART_REG\n"); + goto fail; + } + + return scnprintf(buf, PAGE_SIZE, "%d\n", (reg_value & 0xF)); + +fail: + return ret; +} + +static ssize_t ab8505_powercut_restart_write(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + int ret; + int reg_value; + struct power_supply *psy = dev_get_drvdata(dev); + struct ab8500_fg *di = power_supply_get_drvdata(psy); + + reg_value = simple_strtoul(buf, NULL, 10); + if (reg_value > 0xF) { + dev_err(dev, "Incorrect parameter, echo 0 - 15 for number of restart\n"); + goto fail; + } + + ret = abx500_set_register_interruptible(di->dev, AB8500_RTC, + AB8505_RTC_PCUT_RESTART_REG, (u8)reg_value); + + if (ret < 0) + dev_err(dev, "Failed to set AB8505_RTC_PCUT_RESTART_REG\n"); + +fail: + return count; + +} + +static ssize_t ab8505_powercut_timer_read(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int ret; + u8 reg_value; + struct power_supply *psy = dev_get_drvdata(dev); + struct ab8500_fg *di = power_supply_get_drvdata(psy); + + ret = abx500_get_register_interruptible(di->dev, AB8500_RTC, + AB8505_RTC_PCUT_TIME_REG, ®_value); + + if (ret < 0) { + dev_err(dev, "Failed to read AB8505_RTC_PCUT_TIME_REG\n"); + goto fail; + } + + return scnprintf(buf, PAGE_SIZE, "%d\n", (reg_value & 0x7F)); + +fail: + return ret; +} + +static ssize_t ab8505_powercut_restart_counter_read(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int ret; + u8 reg_value; + struct power_supply *psy = dev_get_drvdata(dev); + struct ab8500_fg *di = power_supply_get_drvdata(psy); + + ret = abx500_get_register_interruptible(di->dev, AB8500_RTC, + AB8505_RTC_PCUT_RESTART_REG, ®_value); + + if (ret < 0) { + dev_err(dev, "Failed to read AB8505_RTC_PCUT_RESTART_REG\n"); + goto fail; + } + + return scnprintf(buf, PAGE_SIZE, "%d\n", (reg_value & 0xF0) >> 4); + +fail: + return ret; +} + +static ssize_t ab8505_powercut_read(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int ret; + u8 reg_value; + struct power_supply *psy = dev_get_drvdata(dev); + struct ab8500_fg *di = power_supply_get_drvdata(psy); + + ret = abx500_get_register_interruptible(di->dev, AB8500_RTC, + AB8505_RTC_PCUT_CTL_STATUS_REG, ®_value); + + if (ret < 0) + goto fail; + + return scnprintf(buf, PAGE_SIZE, "%d\n", (reg_value & 0x1)); + +fail: + return ret; +} + +static ssize_t ab8505_powercut_write(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + int ret; + int reg_value; + struct power_supply *psy = dev_get_drvdata(dev); + struct ab8500_fg *di = power_supply_get_drvdata(psy); + + reg_value = simple_strtoul(buf, NULL, 10); + if (reg_value > 0x1) { + dev_err(dev, "Incorrect parameter, echo 0/1 to disable/enable Pcut feature\n"); + goto fail; + } + + ret = abx500_set_register_interruptible(di->dev, AB8500_RTC, + AB8505_RTC_PCUT_CTL_STATUS_REG, (u8)reg_value); + + if (ret < 0) + dev_err(dev, "Failed to set AB8505_RTC_PCUT_CTL_STATUS_REG\n"); + +fail: + return count; +} + +static ssize_t ab8505_powercut_flag_read(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + + int ret; + u8 reg_value; + struct power_supply *psy = dev_get_drvdata(dev); + struct ab8500_fg *di = power_supply_get_drvdata(psy); + + ret = abx500_get_register_interruptible(di->dev, AB8500_RTC, + AB8505_RTC_PCUT_CTL_STATUS_REG, ®_value); + + if (ret < 0) { + dev_err(dev, "Failed to read AB8505_RTC_PCUT_CTL_STATUS_REG\n"); + goto fail; + } + + return scnprintf(buf, PAGE_SIZE, "%d\n", ((reg_value & 0x10) >> 4)); + +fail: + return ret; +} + +static ssize_t ab8505_powercut_debounce_read(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int ret; + u8 reg_value; + struct power_supply *psy = dev_get_drvdata(dev); + struct ab8500_fg *di = power_supply_get_drvdata(psy); + + ret = abx500_get_register_interruptible(di->dev, AB8500_RTC, + AB8505_RTC_PCUT_DEBOUNCE_REG, ®_value); + + if (ret < 0) { + dev_err(dev, "Failed to read AB8505_RTC_PCUT_DEBOUNCE_REG\n"); + goto fail; + } + + return scnprintf(buf, PAGE_SIZE, "%d\n", (reg_value & 0x7)); + +fail: + return ret; +} + +static ssize_t ab8505_powercut_debounce_write(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + int ret; + int reg_value; + struct power_supply *psy = dev_get_drvdata(dev); + struct ab8500_fg *di = power_supply_get_drvdata(psy); + + reg_value = simple_strtoul(buf, NULL, 10); + if (reg_value > 0x7) { + dev_err(dev, "Incorrect parameter, echo 0 to 7 for debounce setting\n"); + goto fail; + } + + ret = abx500_set_register_interruptible(di->dev, AB8500_RTC, + AB8505_RTC_PCUT_DEBOUNCE_REG, (u8)reg_value); + + if (ret < 0) + dev_err(dev, "Failed to set AB8505_RTC_PCUT_DEBOUNCE_REG\n"); + +fail: + return count; +} + +static ssize_t ab8505_powercut_enable_status_read(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int ret; + u8 reg_value; + struct power_supply *psy = dev_get_drvdata(dev); + struct ab8500_fg *di = power_supply_get_drvdata(psy); + + ret = abx500_get_register_interruptible(di->dev, AB8500_RTC, + AB8505_RTC_PCUT_CTL_STATUS_REG, ®_value); + + if (ret < 0) { + dev_err(dev, "Failed to read AB8505_RTC_PCUT_CTL_STATUS_REG\n"); + goto fail; + } + + return scnprintf(buf, PAGE_SIZE, "%d\n", ((reg_value & 0x20) >> 5)); + +fail: + return ret; +} + +static struct device_attribute ab8505_fg_sysfs_psy_attrs[] = { + __ATTR(powercut_flagtime, (S_IRUGO | S_IWUSR | S_IWGRP), + ab8505_powercut_flagtime_read, ab8505_powercut_flagtime_write), + __ATTR(powercut_maxtime, (S_IRUGO | S_IWUSR | S_IWGRP), + ab8505_powercut_maxtime_read, ab8505_powercut_maxtime_write), + __ATTR(powercut_restart_max, (S_IRUGO | S_IWUSR | S_IWGRP), + ab8505_powercut_restart_read, ab8505_powercut_restart_write), + __ATTR(powercut_timer, S_IRUGO, ab8505_powercut_timer_read, NULL), + __ATTR(powercut_restart_counter, S_IRUGO, + ab8505_powercut_restart_counter_read, NULL), + __ATTR(powercut_enable, (S_IRUGO | S_IWUSR | S_IWGRP), + ab8505_powercut_read, ab8505_powercut_write), + __ATTR(powercut_flag, S_IRUGO, ab8505_powercut_flag_read, NULL), + __ATTR(powercut_debounce_time, (S_IRUGO | S_IWUSR | S_IWGRP), + ab8505_powercut_debounce_read, ab8505_powercut_debounce_write), + __ATTR(powercut_enable_status, S_IRUGO, + ab8505_powercut_enable_status_read, NULL), +}; + +static int ab8500_fg_sysfs_psy_create_attrs(struct ab8500_fg *di) +{ + unsigned int i; + + if (((is_ab8505(di->parent) || is_ab9540(di->parent)) && + abx500_get_chip_id(di->dev) >= AB8500_CUT2P0) + || is_ab8540(di->parent)) { + for (i = 0; i < ARRAY_SIZE(ab8505_fg_sysfs_psy_attrs); i++) + if (device_create_file(&di->fg_psy->dev, + &ab8505_fg_sysfs_psy_attrs[i])) + goto sysfs_psy_create_attrs_failed_ab8505; + } + return 0; +sysfs_psy_create_attrs_failed_ab8505: + dev_err(&di->fg_psy->dev, "Failed creating sysfs psy attrs for ab8505.\n"); + while (i--) + device_remove_file(&di->fg_psy->dev, + &ab8505_fg_sysfs_psy_attrs[i]); + + return -EIO; +} + +static void ab8500_fg_sysfs_psy_remove_attrs(struct ab8500_fg *di) +{ + unsigned int i; + + if (((is_ab8505(di->parent) || is_ab9540(di->parent)) && + abx500_get_chip_id(di->dev) >= AB8500_CUT2P0) + || is_ab8540(di->parent)) { + for (i = 0; i < ARRAY_SIZE(ab8505_fg_sysfs_psy_attrs); i++) + (void)device_remove_file(&di->fg_psy->dev, + &ab8505_fg_sysfs_psy_attrs[i]); + } +} + +/* Exposure to the sysfs interface <> */ + +#if defined(CONFIG_PM) +static int ab8500_fg_resume(struct platform_device *pdev) +{ + struct ab8500_fg *di = platform_get_drvdata(pdev); + + /* + * Change state if we're not charging. If we're charging we will wake + * up on the FG IRQ + */ + if (!di->flags.charging) { + ab8500_fg_discharge_state_to(di, AB8500_FG_DISCHARGE_WAKEUP); + queue_work(di->fg_wq, &di->fg_work); + } + + return 0; +} + +static int ab8500_fg_suspend(struct platform_device *pdev, + pm_message_t state) +{ + struct ab8500_fg *di = platform_get_drvdata(pdev); + + flush_delayed_work(&di->fg_periodic_work); + flush_work(&di->fg_work); + flush_work(&di->fg_acc_cur_work); + flush_delayed_work(&di->fg_reinit_work); + flush_delayed_work(&di->fg_low_bat_work); + flush_delayed_work(&di->fg_check_hw_failure_work); + + /* + * If the FG is enabled we will disable it before going to suspend + * only if we're not charging + */ + if (di->flags.fg_enabled && !di->flags.charging) + ab8500_fg_coulomb_counter(di, false); + + return 0; +} +#else +#define ab8500_fg_suspend NULL +#define ab8500_fg_resume NULL +#endif + +static int ab8500_fg_remove(struct platform_device *pdev) +{ + int ret = 0; + struct ab8500_fg *di = platform_get_drvdata(pdev); + + list_del(&di->node); + + /* Disable coulomb counter */ + ret = ab8500_fg_coulomb_counter(di, false); + if (ret) + dev_err(di->dev, "failed to disable coulomb counter\n"); + + destroy_workqueue(di->fg_wq); + ab8500_fg_sysfs_exit(di); + + flush_scheduled_work(); + ab8500_fg_sysfs_psy_remove_attrs(di); + power_supply_unregister(di->fg_psy); + return ret; +} + +/* ab8500 fg driver interrupts and their respective isr */ +static struct ab8500_fg_interrupts ab8500_fg_irq_th[] = { + {"NCONV_ACCU", ab8500_fg_cc_convend_handler}, + {"BATT_OVV", ab8500_fg_batt_ovv_handler}, + {"LOW_BAT_F", ab8500_fg_lowbatf_handler}, + {"CC_INT_CALIB", ab8500_fg_cc_int_calib_handler}, +}; + +static struct ab8500_fg_interrupts ab8500_fg_irq_bh[] = { + {"CCEOC", ab8500_fg_cc_data_end_handler}, +}; + +static char *supply_interface[] = { + "ab8500_chargalg", + "ab8500_usb", +}; + +static const struct power_supply_desc ab8500_fg_desc = { + .name = "ab8500_fg", + .type = POWER_SUPPLY_TYPE_BATTERY, + .properties = ab8500_fg_props, + .num_properties = ARRAY_SIZE(ab8500_fg_props), + .get_property = ab8500_fg_get_property, + .external_power_changed = ab8500_fg_external_power_changed, +}; + +static int ab8500_fg_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct abx500_bm_data *plat = pdev->dev.platform_data; + struct power_supply_config psy_cfg = {}; + struct ab8500_fg *di; + int i, irq; + int ret = 0; + + di = devm_kzalloc(&pdev->dev, sizeof(*di), GFP_KERNEL); + if (!di) { + dev_err(&pdev->dev, "%s no mem for ab8500_fg\n", __func__); + return -ENOMEM; + } + + if (!plat) { + dev_err(&pdev->dev, "no battery management data supplied\n"); + return -EINVAL; + } + di->bm = plat; + + if (np) { + ret = ab8500_bm_of_probe(&pdev->dev, np, di->bm); + if (ret) { + dev_err(&pdev->dev, "failed to get battery information\n"); + return ret; + } + } + + mutex_init(&di->cc_lock); + + /* get parent data */ + di->dev = &pdev->dev; + di->parent = dev_get_drvdata(pdev->dev.parent); + di->gpadc = ab8500_gpadc_get("ab8500-gpadc.0"); + + psy_cfg.supplied_to = supply_interface; + psy_cfg.num_supplicants = ARRAY_SIZE(supply_interface); + psy_cfg.drv_data = di; + + di->bat_cap.max_mah_design = MILLI_TO_MICRO * + di->bm->bat_type[di->bm->batt_id].charge_full_design; + + di->bat_cap.max_mah = di->bat_cap.max_mah_design; + + di->vbat_nom = di->bm->bat_type[di->bm->batt_id].nominal_voltage; + + di->init_capacity = true; + + ab8500_fg_charge_state_to(di, AB8500_FG_CHARGE_INIT); + ab8500_fg_discharge_state_to(di, AB8500_FG_DISCHARGE_INIT); + + /* Create a work queue for running the FG algorithm */ + di->fg_wq = create_singlethread_workqueue("ab8500_fg_wq"); + if (di->fg_wq == NULL) { + dev_err(di->dev, "failed to create work queue\n"); + return -ENOMEM; + } + + /* Init work for running the fg algorithm instantly */ + INIT_WORK(&di->fg_work, ab8500_fg_instant_work); + + /* Init work for getting the battery accumulated current */ + INIT_WORK(&di->fg_acc_cur_work, ab8500_fg_acc_cur_work); + + /* Init work for reinitialising the fg algorithm */ + INIT_DEFERRABLE_WORK(&di->fg_reinit_work, + ab8500_fg_reinit_work); + + /* Work delayed Queue to run the state machine */ + INIT_DEFERRABLE_WORK(&di->fg_periodic_work, + ab8500_fg_periodic_work); + + /* Work to check low battery condition */ + INIT_DEFERRABLE_WORK(&di->fg_low_bat_work, + ab8500_fg_low_bat_work); + + /* Init work for HW failure check */ + INIT_DEFERRABLE_WORK(&di->fg_check_hw_failure_work, + ab8500_fg_check_hw_failure_work); + + /* Reset battery low voltage flag */ + di->flags.low_bat = false; + + /* Initialize low battery counter */ + di->low_bat_cnt = 10; + + /* Initialize OVV, and other registers */ + ret = ab8500_fg_init_hw_registers(di); + if (ret) { + dev_err(di->dev, "failed to initialize registers\n"); + goto free_inst_curr_wq; + } + + /* Consider battery unknown until we're informed otherwise */ + di->flags.batt_unknown = true; + di->flags.batt_id_received = false; + + /* Register FG power supply class */ + di->fg_psy = power_supply_register(di->dev, &ab8500_fg_desc, &psy_cfg); + if (IS_ERR(di->fg_psy)) { + dev_err(di->dev, "failed to register FG psy\n"); + ret = PTR_ERR(di->fg_psy); + goto free_inst_curr_wq; + } + + di->fg_samples = SEC_TO_SAMPLE(di->bm->fg_params->init_timer); + ab8500_fg_coulomb_counter(di, true); + + /* + * Initialize completion used to notify completion and start + * of inst current + */ + init_completion(&di->ab8500_fg_started); + init_completion(&di->ab8500_fg_complete); + + /* Register primary interrupt handlers */ + for (i = 0; i < ARRAY_SIZE(ab8500_fg_irq_th); i++) { + irq = platform_get_irq_byname(pdev, ab8500_fg_irq_th[i].name); + ret = request_irq(irq, ab8500_fg_irq_th[i].isr, + IRQF_SHARED | IRQF_NO_SUSPEND, + ab8500_fg_irq_th[i].name, di); + + if (ret != 0) { + dev_err(di->dev, "failed to request %s IRQ %d: %d\n", + ab8500_fg_irq_th[i].name, irq, ret); + goto free_irq; + } + dev_dbg(di->dev, "Requested %s IRQ %d: %d\n", + ab8500_fg_irq_th[i].name, irq, ret); + } + + /* Register threaded interrupt handler */ + irq = platform_get_irq_byname(pdev, ab8500_fg_irq_bh[0].name); + ret = request_threaded_irq(irq, NULL, ab8500_fg_irq_bh[0].isr, + IRQF_SHARED | IRQF_NO_SUSPEND | IRQF_ONESHOT, + ab8500_fg_irq_bh[0].name, di); + + if (ret != 0) { + dev_err(di->dev, "failed to request %s IRQ %d: %d\n", + ab8500_fg_irq_bh[0].name, irq, ret); + goto free_irq; + } + dev_dbg(di->dev, "Requested %s IRQ %d: %d\n", + ab8500_fg_irq_bh[0].name, irq, ret); + + di->irq = platform_get_irq_byname(pdev, "CCEOC"); + disable_irq(di->irq); + di->nbr_cceoc_irq_cnt = 0; + + platform_set_drvdata(pdev, di); + + ret = ab8500_fg_sysfs_init(di); + if (ret) { + dev_err(di->dev, "failed to create sysfs entry\n"); + goto free_irq; + } + + ret = ab8500_fg_sysfs_psy_create_attrs(di); + if (ret) { + dev_err(di->dev, "failed to create FG psy\n"); + ab8500_fg_sysfs_exit(di); + goto free_irq; + } + + /* Calibrate the fg first time */ + di->flags.calibrate = true; + di->calib_state = AB8500_FG_CALIB_INIT; + + /* Use room temp as default value until we get an update from driver. */ + di->bat_temp = 210; + + /* Run the FG algorithm */ + queue_delayed_work(di->fg_wq, &di->fg_periodic_work, 0); + + list_add_tail(&di->node, &ab8500_fg_list); + + return ret; + +free_irq: + power_supply_unregister(di->fg_psy); + + /* We also have to free all registered irqs */ + for (i = 0; i < ARRAY_SIZE(ab8500_fg_irq_th); i++) { + irq = platform_get_irq_byname(pdev, ab8500_fg_irq_th[i].name); + free_irq(irq, di); + } + irq = platform_get_irq_byname(pdev, ab8500_fg_irq_bh[0].name); + free_irq(irq, di); +free_inst_curr_wq: + destroy_workqueue(di->fg_wq); + return ret; +} + +static const struct of_device_id ab8500_fg_match[] = { + { .compatible = "stericsson,ab8500-fg", }, + { }, +}; + +static struct platform_driver ab8500_fg_driver = { + .probe = ab8500_fg_probe, + .remove = ab8500_fg_remove, + .suspend = ab8500_fg_suspend, + .resume = ab8500_fg_resume, + .driver = { + .name = "ab8500-fg", + .of_match_table = ab8500_fg_match, + }, +}; + +static int __init ab8500_fg_init(void) +{ + return platform_driver_register(&ab8500_fg_driver); +} + +static void __exit ab8500_fg_exit(void) +{ + platform_driver_unregister(&ab8500_fg_driver); +} + +subsys_initcall_sync(ab8500_fg_init); +module_exit(ab8500_fg_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Johan Palsson, Karl Komierowski"); +MODULE_ALIAS("platform:ab8500-fg"); +MODULE_DESCRIPTION("AB8500 Fuel Gauge driver"); diff --git a/drivers/power/supply/abx500_chargalg.c b/drivers/power/supply/abx500_chargalg.c new file mode 100644 index 000000000000..d9104b1ab7cf --- /dev/null +++ b/drivers/power/supply/abx500_chargalg.c @@ -0,0 +1,2166 @@ +/* + * Copyright (C) ST-Ericsson SA 2012 + * Copyright (c) 2012 Sony Mobile Communications AB + * + * Charging algorithm driver for abx500 variants + * + * License Terms: GNU General Public License v2 + * Authors: + * Johan Palsson + * Karl Komierowski + * Arun R Murthy + * Author: Imre Sunyi + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Watchdog kick interval */ +#define CHG_WD_INTERVAL (6 * HZ) + +/* End-of-charge criteria counter */ +#define EOC_COND_CNT 10 + +/* One hour expressed in seconds */ +#define ONE_HOUR_IN_SECONDS 3600 + +/* Five minutes expressed in seconds */ +#define FIVE_MINUTES_IN_SECONDS 300 + +/* Plus margin for the low battery threshold */ +#define BAT_PLUS_MARGIN (100) + +#define CHARGALG_CURR_STEP_LOW 0 +#define CHARGALG_CURR_STEP_HIGH 100 + +enum abx500_chargers { + NO_CHG, + AC_CHG, + USB_CHG, +}; + +struct abx500_chargalg_charger_info { + enum abx500_chargers conn_chg; + enum abx500_chargers prev_conn_chg; + enum abx500_chargers online_chg; + enum abx500_chargers prev_online_chg; + enum abx500_chargers charger_type; + bool usb_chg_ok; + bool ac_chg_ok; + int usb_volt; + int usb_curr; + int ac_volt; + int ac_curr; + int usb_vset; + int usb_iset; + int ac_vset; + int ac_iset; +}; + +struct abx500_chargalg_suspension_status { + bool suspended_change; + bool ac_suspended; + bool usb_suspended; +}; + +struct abx500_chargalg_current_step_status { + bool curr_step_change; + int curr_step; +}; + +struct abx500_chargalg_battery_data { + int temp; + int volt; + int avg_curr; + int inst_curr; + int percent; +}; + +enum abx500_chargalg_states { + STATE_HANDHELD_INIT, + STATE_HANDHELD, + STATE_CHG_NOT_OK_INIT, + STATE_CHG_NOT_OK, + STATE_HW_TEMP_PROTECT_INIT, + STATE_HW_TEMP_PROTECT, + STATE_NORMAL_INIT, + STATE_USB_PP_PRE_CHARGE, + STATE_NORMAL, + STATE_WAIT_FOR_RECHARGE_INIT, + STATE_WAIT_FOR_RECHARGE, + STATE_MAINTENANCE_A_INIT, + STATE_MAINTENANCE_A, + STATE_MAINTENANCE_B_INIT, + STATE_MAINTENANCE_B, + STATE_TEMP_UNDEROVER_INIT, + STATE_TEMP_UNDEROVER, + STATE_TEMP_LOWHIGH_INIT, + STATE_TEMP_LOWHIGH, + STATE_SUSPENDED_INIT, + STATE_SUSPENDED, + STATE_OVV_PROTECT_INIT, + STATE_OVV_PROTECT, + STATE_SAFETY_TIMER_EXPIRED_INIT, + STATE_SAFETY_TIMER_EXPIRED, + STATE_BATT_REMOVED_INIT, + STATE_BATT_REMOVED, + STATE_WD_EXPIRED_INIT, + STATE_WD_EXPIRED, +}; + +static const char *states[] = { + "HANDHELD_INIT", + "HANDHELD", + "CHG_NOT_OK_INIT", + "CHG_NOT_OK", + "HW_TEMP_PROTECT_INIT", + "HW_TEMP_PROTECT", + "NORMAL_INIT", + "USB_PP_PRE_CHARGE", + "NORMAL", + "WAIT_FOR_RECHARGE_INIT", + "WAIT_FOR_RECHARGE", + "MAINTENANCE_A_INIT", + "MAINTENANCE_A", + "MAINTENANCE_B_INIT", + "MAINTENANCE_B", + "TEMP_UNDEROVER_INIT", + "TEMP_UNDEROVER", + "TEMP_LOWHIGH_INIT", + "TEMP_LOWHIGH", + "SUSPENDED_INIT", + "SUSPENDED", + "OVV_PROTECT_INIT", + "OVV_PROTECT", + "SAFETY_TIMER_EXPIRED_INIT", + "SAFETY_TIMER_EXPIRED", + "BATT_REMOVED_INIT", + "BATT_REMOVED", + "WD_EXPIRED_INIT", + "WD_EXPIRED", +}; + +struct abx500_chargalg_events { + bool batt_unknown; + bool mainextchnotok; + bool batt_ovv; + bool batt_rem; + bool btemp_underover; + bool btemp_lowhigh; + bool main_thermal_prot; + bool usb_thermal_prot; + bool main_ovv; + bool vbus_ovv; + bool usbchargernotok; + bool safety_timer_expired; + bool maintenance_timer_expired; + bool ac_wd_expired; + bool usb_wd_expired; + bool ac_cv_active; + bool usb_cv_active; + bool vbus_collapsed; +}; + +/** + * struct abx500_charge_curr_maximization - Charger maximization parameters + * @original_iset: the non optimized/maximised charger current + * @current_iset: the charging current used at this moment + * @test_delta_i: the delta between the current we want to charge and the + current that is really going into the battery + * @condition_cnt: number of iterations needed before a new charger current + is set + * @max_current: maximum charger current + * @wait_cnt: to avoid too fast current step down in case of charger + * voltage collapse, we insert this delay between step + * down + * @level: tells in how many steps the charging current has been + increased + */ +struct abx500_charge_curr_maximization { + int original_iset; + int current_iset; + int test_delta_i; + int condition_cnt; + int max_current; + int wait_cnt; + u8 level; +}; + +enum maxim_ret { + MAXIM_RET_NOACTION, + MAXIM_RET_CHANGE, + MAXIM_RET_IBAT_TOO_HIGH, +}; + +/** + * struct abx500_chargalg - abx500 Charging algorithm device information + * @dev: pointer to the structure device + * @charge_status: battery operating status + * @eoc_cnt: counter used to determine end-of_charge + * @maintenance_chg: indicate if maintenance charge is active + * @t_hyst_norm temperature hysteresis when the temperature has been + * over or under normal limits + * @t_hyst_lowhigh temperature hysteresis when the temperature has been + * over or under the high or low limits + * @charge_state: current state of the charging algorithm + * @ccm charging current maximization parameters + * @chg_info: information about connected charger types + * @batt_data: data of the battery + * @susp_status: current charger suspension status + * @bm: Platform specific battery management information + * @curr_status: Current step status for over-current protection + * @parent: pointer to the struct abx500 + * @chargalg_psy: structure that holds the battery properties exposed by + * the charging algorithm + * @events: structure for information about events triggered + * @chargalg_wq: work queue for running the charging algorithm + * @chargalg_periodic_work: work to run the charging algorithm periodically + * @chargalg_wd_work: work to kick the charger watchdog periodically + * @chargalg_work: work to run the charging algorithm instantly + * @safety_timer: charging safety timer + * @maintenance_timer: maintenance charging timer + * @chargalg_kobject: structure of type kobject + */ +struct abx500_chargalg { + struct device *dev; + int charge_status; + int eoc_cnt; + bool maintenance_chg; + int t_hyst_norm; + int t_hyst_lowhigh; + enum abx500_chargalg_states charge_state; + struct abx500_charge_curr_maximization ccm; + struct abx500_chargalg_charger_info chg_info; + struct abx500_chargalg_battery_data batt_data; + struct abx500_chargalg_suspension_status susp_status; + struct ab8500 *parent; + struct abx500_chargalg_current_step_status curr_status; + struct abx500_bm_data *bm; + struct power_supply *chargalg_psy; + struct ux500_charger *ac_chg; + struct ux500_charger *usb_chg; + struct abx500_chargalg_events events; + struct workqueue_struct *chargalg_wq; + struct delayed_work chargalg_periodic_work; + struct delayed_work chargalg_wd_work; + struct work_struct chargalg_work; + struct hrtimer safety_timer; + struct hrtimer maintenance_timer; + struct kobject chargalg_kobject; +}; + +/*External charger prepare notifier*/ +BLOCKING_NOTIFIER_HEAD(charger_notifier_list); + +/* Main battery properties */ +static enum power_supply_property abx500_chargalg_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_HEALTH, +}; + +struct abx500_chargalg_sysfs_entry { + struct attribute attr; + ssize_t (*show)(struct abx500_chargalg *, char *); + ssize_t (*store)(struct abx500_chargalg *, const char *, size_t); +}; + +/** + * abx500_chargalg_safety_timer_expired() - Expiration of the safety timer + * @timer: pointer to the hrtimer structure + * + * This function gets called when the safety timer for the charger + * expires + */ +static enum hrtimer_restart +abx500_chargalg_safety_timer_expired(struct hrtimer *timer) +{ + struct abx500_chargalg *di = container_of(timer, struct abx500_chargalg, + safety_timer); + dev_err(di->dev, "Safety timer expired\n"); + di->events.safety_timer_expired = true; + + /* Trigger execution of the algorithm instantly */ + queue_work(di->chargalg_wq, &di->chargalg_work); + + return HRTIMER_NORESTART; +} + +/** + * abx500_chargalg_maintenance_timer_expired() - Expiration of + * the maintenance timer + * @timer: pointer to the timer structure + * + * This function gets called when the maintenence timer + * expires + */ +static enum hrtimer_restart +abx500_chargalg_maintenance_timer_expired(struct hrtimer *timer) +{ + + struct abx500_chargalg *di = container_of(timer, struct abx500_chargalg, + maintenance_timer); + + dev_dbg(di->dev, "Maintenance timer expired\n"); + di->events.maintenance_timer_expired = true; + + /* Trigger execution of the algorithm instantly */ + queue_work(di->chargalg_wq, &di->chargalg_work); + + return HRTIMER_NORESTART; +} + +/** + * abx500_chargalg_state_to() - Change charge state + * @di: pointer to the abx500_chargalg structure + * + * This function gets called when a charge state change should occur + */ +static void abx500_chargalg_state_to(struct abx500_chargalg *di, + enum abx500_chargalg_states state) +{ + dev_dbg(di->dev, + "State changed: %s (From state: [%d] %s =to=> [%d] %s )\n", + di->charge_state == state ? "NO" : "YES", + di->charge_state, + states[di->charge_state], + state, + states[state]); + + di->charge_state = state; +} + +static int abx500_chargalg_check_charger_enable(struct abx500_chargalg *di) +{ + switch (di->charge_state) { + case STATE_NORMAL: + case STATE_MAINTENANCE_A: + case STATE_MAINTENANCE_B: + break; + default: + return 0; + } + + if (di->chg_info.charger_type & USB_CHG) { + return di->usb_chg->ops.check_enable(di->usb_chg, + di->bm->bat_type[di->bm->batt_id].normal_vol_lvl, + di->bm->bat_type[di->bm->batt_id].normal_cur_lvl); + } else if ((di->chg_info.charger_type & AC_CHG) && + !(di->ac_chg->external)) { + return di->ac_chg->ops.check_enable(di->ac_chg, + di->bm->bat_type[di->bm->batt_id].normal_vol_lvl, + di->bm->bat_type[di->bm->batt_id].normal_cur_lvl); + } + return 0; +} + +/** + * abx500_chargalg_check_charger_connection() - Check charger connection change + * @di: pointer to the abx500_chargalg structure + * + * This function will check if there is a change in the charger connection + * and change charge state accordingly. AC has precedence over USB. + */ +static int abx500_chargalg_check_charger_connection(struct abx500_chargalg *di) +{ + if (di->chg_info.conn_chg != di->chg_info.prev_conn_chg || + di->susp_status.suspended_change) { + /* + * Charger state changed or suspension + * has changed since last update + */ + if ((di->chg_info.conn_chg & AC_CHG) && + !di->susp_status.ac_suspended) { + dev_dbg(di->dev, "Charging source is AC\n"); + if (di->chg_info.charger_type != AC_CHG) { + di->chg_info.charger_type = AC_CHG; + abx500_chargalg_state_to(di, STATE_NORMAL_INIT); + } + } else if ((di->chg_info.conn_chg & USB_CHG) && + !di->susp_status.usb_suspended) { + dev_dbg(di->dev, "Charging source is USB\n"); + di->chg_info.charger_type = USB_CHG; + abx500_chargalg_state_to(di, STATE_NORMAL_INIT); + } else if (di->chg_info.conn_chg && + (di->susp_status.ac_suspended || + di->susp_status.usb_suspended)) { + dev_dbg(di->dev, "Charging is suspended\n"); + di->chg_info.charger_type = NO_CHG; + abx500_chargalg_state_to(di, STATE_SUSPENDED_INIT); + } else { + dev_dbg(di->dev, "Charging source is OFF\n"); + di->chg_info.charger_type = NO_CHG; + abx500_chargalg_state_to(di, STATE_HANDHELD_INIT); + } + di->chg_info.prev_conn_chg = di->chg_info.conn_chg; + di->susp_status.suspended_change = false; + } + return di->chg_info.conn_chg; +} + +/** + * abx500_chargalg_check_current_step_status() - Check charging current + * step status. + * @di: pointer to the abx500_chargalg structure + * + * This function will check if there is a change in the charging current step + * and change charge state accordingly. + */ +static void abx500_chargalg_check_current_step_status + (struct abx500_chargalg *di) +{ + if (di->curr_status.curr_step_change) + abx500_chargalg_state_to(di, STATE_NORMAL_INIT); + di->curr_status.curr_step_change = false; +} + +/** + * abx500_chargalg_start_safety_timer() - Start charging safety timer + * @di: pointer to the abx500_chargalg structure + * + * The safety timer is used to avoid overcharging of old or bad batteries. + * There are different timers for AC and USB + */ +static void abx500_chargalg_start_safety_timer(struct abx500_chargalg *di) +{ + /* Charger-dependent expiration time in hours*/ + int timer_expiration = 0; + + switch (di->chg_info.charger_type) { + case AC_CHG: + timer_expiration = di->bm->main_safety_tmr_h; + break; + + case USB_CHG: + timer_expiration = di->bm->usb_safety_tmr_h; + break; + + default: + dev_err(di->dev, "Unknown charger to charge from\n"); + break; + } + + di->events.safety_timer_expired = false; + hrtimer_set_expires_range(&di->safety_timer, + ktime_set(timer_expiration * ONE_HOUR_IN_SECONDS, 0), + ktime_set(FIVE_MINUTES_IN_SECONDS, 0)); + hrtimer_start_expires(&di->safety_timer, HRTIMER_MODE_REL); +} + +/** + * abx500_chargalg_stop_safety_timer() - Stop charging safety timer + * @di: pointer to the abx500_chargalg structure + * + * The safety timer is stopped whenever the NORMAL state is exited + */ +static void abx500_chargalg_stop_safety_timer(struct abx500_chargalg *di) +{ + if (hrtimer_try_to_cancel(&di->safety_timer) >= 0) + di->events.safety_timer_expired = false; +} + +/** + * abx500_chargalg_start_maintenance_timer() - Start charging maintenance timer + * @di: pointer to the abx500_chargalg structure + * @duration: duration of ther maintenance timer in hours + * + * The maintenance timer is used to maintain the charge in the battery once + * the battery is considered full. These timers are chosen to match the + * discharge curve of the battery + */ +static void abx500_chargalg_start_maintenance_timer(struct abx500_chargalg *di, + int duration) +{ + hrtimer_set_expires_range(&di->maintenance_timer, + ktime_set(duration * ONE_HOUR_IN_SECONDS, 0), + ktime_set(FIVE_MINUTES_IN_SECONDS, 0)); + di->events.maintenance_timer_expired = false; + hrtimer_start_expires(&di->maintenance_timer, HRTIMER_MODE_REL); +} + +/** + * abx500_chargalg_stop_maintenance_timer() - Stop maintenance timer + * @di: pointer to the abx500_chargalg structure + * + * The maintenance timer is stopped whenever maintenance ends or when another + * state is entered + */ +static void abx500_chargalg_stop_maintenance_timer(struct abx500_chargalg *di) +{ + if (hrtimer_try_to_cancel(&di->maintenance_timer) >= 0) + di->events.maintenance_timer_expired = false; +} + +/** + * abx500_chargalg_kick_watchdog() - Kick charger watchdog + * @di: pointer to the abx500_chargalg structure + * + * The charger watchdog have to be kicked periodically whenever the charger is + * on, else the ABB will reset the system + */ +static int abx500_chargalg_kick_watchdog(struct abx500_chargalg *di) +{ + /* Check if charger exists and kick watchdog if charging */ + if (di->ac_chg && di->ac_chg->ops.kick_wd && + di->chg_info.online_chg & AC_CHG) { + /* + * If AB charger watchdog expired, pm2xxx charging + * gets disabled. To be safe, kick both AB charger watchdog + * and pm2xxx watchdog. + */ + if (di->ac_chg->external && + di->usb_chg && di->usb_chg->ops.kick_wd) + di->usb_chg->ops.kick_wd(di->usb_chg); + + return di->ac_chg->ops.kick_wd(di->ac_chg); + } + else if (di->usb_chg && di->usb_chg->ops.kick_wd && + di->chg_info.online_chg & USB_CHG) + return di->usb_chg->ops.kick_wd(di->usb_chg); + + return -ENXIO; +} + +/** + * abx500_chargalg_ac_en() - Turn on/off the AC charger + * @di: pointer to the abx500_chargalg structure + * @enable: charger on/off + * @vset: requested charger output voltage + * @iset: requested charger output current + * + * The AC charger will be turned on/off with the requested charge voltage and + * current + */ +static int abx500_chargalg_ac_en(struct abx500_chargalg *di, int enable, + int vset, int iset) +{ + static int abx500_chargalg_ex_ac_enable_toggle; + + if (!di->ac_chg || !di->ac_chg->ops.enable) + return -ENXIO; + + /* Select maximum of what both the charger and the battery supports */ + if (di->ac_chg->max_out_volt) + vset = min(vset, di->ac_chg->max_out_volt); + if (di->ac_chg->max_out_curr) + iset = min(iset, di->ac_chg->max_out_curr); + + di->chg_info.ac_iset = iset; + di->chg_info.ac_vset = vset; + + /* Enable external charger */ + if (enable && di->ac_chg->external && + !abx500_chargalg_ex_ac_enable_toggle) { + blocking_notifier_call_chain(&charger_notifier_list, + 0, di->dev); + abx500_chargalg_ex_ac_enable_toggle++; + } + + return di->ac_chg->ops.enable(di->ac_chg, enable, vset, iset); +} + +/** + * abx500_chargalg_usb_en() - Turn on/off the USB charger + * @di: pointer to the abx500_chargalg structure + * @enable: charger on/off + * @vset: requested charger output voltage + * @iset: requested charger output current + * + * The USB charger will be turned on/off with the requested charge voltage and + * current + */ +static int abx500_chargalg_usb_en(struct abx500_chargalg *di, int enable, + int vset, int iset) +{ + if (!di->usb_chg || !di->usb_chg->ops.enable) + return -ENXIO; + + /* Select maximum of what both the charger and the battery supports */ + if (di->usb_chg->max_out_volt) + vset = min(vset, di->usb_chg->max_out_volt); + if (di->usb_chg->max_out_curr) + iset = min(iset, di->usb_chg->max_out_curr); + + di->chg_info.usb_iset = iset; + di->chg_info.usb_vset = vset; + + return di->usb_chg->ops.enable(di->usb_chg, enable, vset, iset); +} + + /** + * ab8540_chargalg_usb_pp_en() - Enable/ disable USB power path + * @di: pointer to the abx500_chargalg structure + * @enable: power path enable/disable + * + * The USB power path will be enable/ disable + */ +static int ab8540_chargalg_usb_pp_en(struct abx500_chargalg *di, bool enable) +{ + if (!di->usb_chg || !di->usb_chg->ops.pp_enable) + return -ENXIO; + + return di->usb_chg->ops.pp_enable(di->usb_chg, enable); +} + +/** + * ab8540_chargalg_usb_pre_chg_en() - Enable/ disable USB pre-charge + * @di: pointer to the abx500_chargalg structure + * @enable: USB pre-charge enable/disable + * + * The USB USB pre-charge will be enable/ disable + */ +static int ab8540_chargalg_usb_pre_chg_en(struct abx500_chargalg *di, + bool enable) +{ + if (!di->usb_chg || !di->usb_chg->ops.pre_chg_enable) + return -ENXIO; + + return di->usb_chg->ops.pre_chg_enable(di->usb_chg, enable); +} + +/** + * abx500_chargalg_update_chg_curr() - Update charger current + * @di: pointer to the abx500_chargalg structure + * @iset: requested charger output current + * + * The charger output current will be updated for the charger + * that is currently in use + */ +static int abx500_chargalg_update_chg_curr(struct abx500_chargalg *di, + int iset) +{ + /* Check if charger exists and update current if charging */ + if (di->ac_chg && di->ac_chg->ops.update_curr && + di->chg_info.charger_type & AC_CHG) { + /* + * Select maximum of what both the charger + * and the battery supports + */ + if (di->ac_chg->max_out_curr) + iset = min(iset, di->ac_chg->max_out_curr); + + di->chg_info.ac_iset = iset; + + return di->ac_chg->ops.update_curr(di->ac_chg, iset); + } else if (di->usb_chg && di->usb_chg->ops.update_curr && + di->chg_info.charger_type & USB_CHG) { + /* + * Select maximum of what both the charger + * and the battery supports + */ + if (di->usb_chg->max_out_curr) + iset = min(iset, di->usb_chg->max_out_curr); + + di->chg_info.usb_iset = iset; + + return di->usb_chg->ops.update_curr(di->usb_chg, iset); + } + + return -ENXIO; +} + +/** + * abx500_chargalg_stop_charging() - Stop charging + * @di: pointer to the abx500_chargalg structure + * + * This function is called from any state where charging should be stopped. + * All charging is disabled and all status parameters and timers are changed + * accordingly + */ +static void abx500_chargalg_stop_charging(struct abx500_chargalg *di) +{ + abx500_chargalg_ac_en(di, false, 0, 0); + abx500_chargalg_usb_en(di, false, 0, 0); + abx500_chargalg_stop_safety_timer(di); + abx500_chargalg_stop_maintenance_timer(di); + di->charge_status = POWER_SUPPLY_STATUS_NOT_CHARGING; + di->maintenance_chg = false; + cancel_delayed_work(&di->chargalg_wd_work); + power_supply_changed(di->chargalg_psy); +} + +/** + * abx500_chargalg_hold_charging() - Pauses charging + * @di: pointer to the abx500_chargalg structure + * + * This function is called in the case where maintenance charging has been + * disabled and instead a battery voltage mode is entered to check when the + * battery voltage has reached a certain recharge voltage + */ +static void abx500_chargalg_hold_charging(struct abx500_chargalg *di) +{ + abx500_chargalg_ac_en(di, false, 0, 0); + abx500_chargalg_usb_en(di, false, 0, 0); + abx500_chargalg_stop_safety_timer(di); + abx500_chargalg_stop_maintenance_timer(di); + di->charge_status = POWER_SUPPLY_STATUS_CHARGING; + di->maintenance_chg = false; + cancel_delayed_work(&di->chargalg_wd_work); + power_supply_changed(di->chargalg_psy); +} + +/** + * abx500_chargalg_start_charging() - Start the charger + * @di: pointer to the abx500_chargalg structure + * @vset: requested charger output voltage + * @iset: requested charger output current + * + * A charger will be enabled depending on the requested charger type that was + * detected previously. + */ +static void abx500_chargalg_start_charging(struct abx500_chargalg *di, + int vset, int iset) +{ + switch (di->chg_info.charger_type) { + case AC_CHG: + dev_dbg(di->dev, + "AC parameters: Vset %d, Ich %d\n", vset, iset); + abx500_chargalg_usb_en(di, false, 0, 0); + abx500_chargalg_ac_en(di, true, vset, iset); + break; + + case USB_CHG: + dev_dbg(di->dev, + "USB parameters: Vset %d, Ich %d\n", vset, iset); + abx500_chargalg_ac_en(di, false, 0, 0); + abx500_chargalg_usb_en(di, true, vset, iset); + break; + + default: + dev_err(di->dev, "Unknown charger to charge from\n"); + break; + } +} + +/** + * abx500_chargalg_check_temp() - Check battery temperature ranges + * @di: pointer to the abx500_chargalg structure + * + * The battery temperature is checked against the predefined limits and the + * charge state is changed accordingly + */ +static void abx500_chargalg_check_temp(struct abx500_chargalg *di) +{ + if (di->batt_data.temp > (di->bm->temp_low + di->t_hyst_norm) && + di->batt_data.temp < (di->bm->temp_high - di->t_hyst_norm)) { + /* Temp OK! */ + di->events.btemp_underover = false; + di->events.btemp_lowhigh = false; + di->t_hyst_norm = 0; + di->t_hyst_lowhigh = 0; + } else { + if (((di->batt_data.temp >= di->bm->temp_high) && + (di->batt_data.temp < + (di->bm->temp_over - di->t_hyst_lowhigh))) || + ((di->batt_data.temp > + (di->bm->temp_under + di->t_hyst_lowhigh)) && + (di->batt_data.temp <= di->bm->temp_low))) { + /* TEMP minor!!!!! */ + di->events.btemp_underover = false; + di->events.btemp_lowhigh = true; + di->t_hyst_norm = di->bm->temp_hysteresis; + di->t_hyst_lowhigh = 0; + } else if (di->batt_data.temp <= di->bm->temp_under || + di->batt_data.temp >= di->bm->temp_over) { + /* TEMP major!!!!! */ + di->events.btemp_underover = true; + di->events.btemp_lowhigh = false; + di->t_hyst_norm = 0; + di->t_hyst_lowhigh = di->bm->temp_hysteresis; + } else { + /* Within hysteresis */ + dev_dbg(di->dev, "Within hysteresis limit temp: %d " + "hyst_lowhigh %d, hyst normal %d\n", + di->batt_data.temp, di->t_hyst_lowhigh, + di->t_hyst_norm); + } + } +} + +/** + * abx500_chargalg_check_charger_voltage() - Check charger voltage + * @di: pointer to the abx500_chargalg structure + * + * Charger voltage is checked against maximum limit + */ +static void abx500_chargalg_check_charger_voltage(struct abx500_chargalg *di) +{ + if (di->chg_info.usb_volt > di->bm->chg_params->usb_volt_max) + di->chg_info.usb_chg_ok = false; + else + di->chg_info.usb_chg_ok = true; + + if (di->chg_info.ac_volt > di->bm->chg_params->ac_volt_max) + di->chg_info.ac_chg_ok = false; + else + di->chg_info.ac_chg_ok = true; + +} + +/** + * abx500_chargalg_end_of_charge() - Check if end-of-charge criteria is fulfilled + * @di: pointer to the abx500_chargalg structure + * + * End-of-charge criteria is fulfilled when the battery voltage is above a + * certain limit and the battery current is below a certain limit for a + * predefined number of consecutive seconds. If true, the battery is full + */ +static void abx500_chargalg_end_of_charge(struct abx500_chargalg *di) +{ + if (di->charge_status == POWER_SUPPLY_STATUS_CHARGING && + di->charge_state == STATE_NORMAL && + !di->maintenance_chg && (di->batt_data.volt >= + di->bm->bat_type[di->bm->batt_id].termination_vol || + di->events.usb_cv_active || di->events.ac_cv_active) && + di->batt_data.avg_curr < + di->bm->bat_type[di->bm->batt_id].termination_curr && + di->batt_data.avg_curr > 0) { + if (++di->eoc_cnt >= EOC_COND_CNT) { + di->eoc_cnt = 0; + if ((di->chg_info.charger_type & USB_CHG) && + (di->usb_chg->power_path)) + ab8540_chargalg_usb_pp_en(di, true); + di->charge_status = POWER_SUPPLY_STATUS_FULL; + di->maintenance_chg = true; + dev_dbg(di->dev, "EOC reached!\n"); + power_supply_changed(di->chargalg_psy); + } else { + dev_dbg(di->dev, + " EOC limit reached for the %d" + " time, out of %d before EOC\n", + di->eoc_cnt, + EOC_COND_CNT); + } + } else { + di->eoc_cnt = 0; + } +} + +static void init_maxim_chg_curr(struct abx500_chargalg *di) +{ + di->ccm.original_iset = + di->bm->bat_type[di->bm->batt_id].normal_cur_lvl; + di->ccm.current_iset = + di->bm->bat_type[di->bm->batt_id].normal_cur_lvl; + di->ccm.test_delta_i = di->bm->maxi->charger_curr_step; + di->ccm.max_current = di->bm->maxi->chg_curr; + di->ccm.condition_cnt = di->bm->maxi->wait_cycles; + di->ccm.level = 0; +} + +/** + * abx500_chargalg_chg_curr_maxim - increases the charger current to + * compensate for the system load + * @di pointer to the abx500_chargalg structure + * + * This maximization function is used to raise the charger current to get the + * battery current as close to the optimal value as possible. The battery + * current during charging is affected by the system load + */ +static enum maxim_ret abx500_chargalg_chg_curr_maxim(struct abx500_chargalg *di) +{ + int delta_i; + + if (!di->bm->maxi->ena_maxi) + return MAXIM_RET_NOACTION; + + delta_i = di->ccm.original_iset - di->batt_data.inst_curr; + + if (di->events.vbus_collapsed) { + dev_dbg(di->dev, "Charger voltage has collapsed %d\n", + di->ccm.wait_cnt); + if (di->ccm.wait_cnt == 0) { + dev_dbg(di->dev, "lowering current\n"); + di->ccm.wait_cnt++; + di->ccm.condition_cnt = di->bm->maxi->wait_cycles; + di->ccm.max_current = + di->ccm.current_iset - di->ccm.test_delta_i; + di->ccm.current_iset = di->ccm.max_current; + di->ccm.level--; + return MAXIM_RET_CHANGE; + } else { + dev_dbg(di->dev, "waiting\n"); + /* Let's go in here twice before lowering curr again */ + di->ccm.wait_cnt = (di->ccm.wait_cnt + 1) % 3; + return MAXIM_RET_NOACTION; + } + } + + di->ccm.wait_cnt = 0; + + if ((di->batt_data.inst_curr > di->ccm.original_iset)) { + dev_dbg(di->dev, " Maximization Ibat (%dmA) too high" + " (limit %dmA) (current iset: %dmA)!\n", + di->batt_data.inst_curr, di->ccm.original_iset, + di->ccm.current_iset); + + if (di->ccm.current_iset == di->ccm.original_iset) + return MAXIM_RET_NOACTION; + + di->ccm.condition_cnt = di->bm->maxi->wait_cycles; + di->ccm.current_iset = di->ccm.original_iset; + di->ccm.level = 0; + + return MAXIM_RET_IBAT_TOO_HIGH; + } + + if (delta_i > di->ccm.test_delta_i && + (di->ccm.current_iset + di->ccm.test_delta_i) < + di->ccm.max_current) { + if (di->ccm.condition_cnt-- == 0) { + /* Increse the iset with cco.test_delta_i */ + di->ccm.condition_cnt = di->bm->maxi->wait_cycles; + di->ccm.current_iset += di->ccm.test_delta_i; + di->ccm.level++; + dev_dbg(di->dev, " Maximization needed, increase" + " with %d mA to %dmA (Optimal ibat: %d)" + " Level %d\n", + di->ccm.test_delta_i, + di->ccm.current_iset, + di->ccm.original_iset, + di->ccm.level); + return MAXIM_RET_CHANGE; + } else { + return MAXIM_RET_NOACTION; + } + } else { + di->ccm.condition_cnt = di->bm->maxi->wait_cycles; + return MAXIM_RET_NOACTION; + } +} + +static void handle_maxim_chg_curr(struct abx500_chargalg *di) +{ + enum maxim_ret ret; + int result; + + ret = abx500_chargalg_chg_curr_maxim(di); + switch (ret) { + case MAXIM_RET_CHANGE: + result = abx500_chargalg_update_chg_curr(di, + di->ccm.current_iset); + if (result) + dev_err(di->dev, "failed to set chg curr\n"); + break; + case MAXIM_RET_IBAT_TOO_HIGH: + result = abx500_chargalg_update_chg_curr(di, + di->bm->bat_type[di->bm->batt_id].normal_cur_lvl); + if (result) + dev_err(di->dev, "failed to set chg curr\n"); + break; + + case MAXIM_RET_NOACTION: + default: + /* Do nothing..*/ + break; + } +} + +static int abx500_chargalg_get_ext_psy_data(struct device *dev, void *data) +{ + struct power_supply *psy; + struct power_supply *ext = dev_get_drvdata(dev); + const char **supplicants = (const char **)ext->supplied_to; + struct abx500_chargalg *di; + union power_supply_propval ret; + int j; + bool capacity_updated = false; + + psy = (struct power_supply *)data; + di = power_supply_get_drvdata(psy); + /* For all psy where the driver name appears in any supplied_to */ + j = match_string(supplicants, ext->num_supplicants, psy->desc->name); + if (j < 0) + return 0; + + /* + * If external is not registering 'POWER_SUPPLY_PROP_CAPACITY' to its + * property because of handling that sysfs entry on its own, this is + * the place to get the battery capacity. + */ + if (!power_supply_get_property(ext, POWER_SUPPLY_PROP_CAPACITY, &ret)) { + di->batt_data.percent = ret.intval; + capacity_updated = true; + } + + /* Go through all properties for the psy */ + for (j = 0; j < ext->desc->num_properties; j++) { + enum power_supply_property prop; + prop = ext->desc->properties[j]; + + /* + * Initialize chargers if not already done. + * The ab8500_charger*/ + if (!di->ac_chg && + ext->desc->type == POWER_SUPPLY_TYPE_MAINS) + di->ac_chg = psy_to_ux500_charger(ext); + else if (!di->usb_chg && + ext->desc->type == POWER_SUPPLY_TYPE_USB) + di->usb_chg = psy_to_ux500_charger(ext); + + if (power_supply_get_property(ext, prop, &ret)) + continue; + switch (prop) { + case POWER_SUPPLY_PROP_PRESENT: + switch (ext->desc->type) { + case POWER_SUPPLY_TYPE_BATTERY: + /* Battery present */ + if (ret.intval) + di->events.batt_rem = false; + /* Battery removed */ + else + di->events.batt_rem = true; + break; + case POWER_SUPPLY_TYPE_MAINS: + /* AC disconnected */ + if (!ret.intval && + (di->chg_info.conn_chg & AC_CHG)) { + di->chg_info.prev_conn_chg = + di->chg_info.conn_chg; + di->chg_info.conn_chg &= ~AC_CHG; + } + /* AC connected */ + else if (ret.intval && + !(di->chg_info.conn_chg & AC_CHG)) { + di->chg_info.prev_conn_chg = + di->chg_info.conn_chg; + di->chg_info.conn_chg |= AC_CHG; + } + break; + case POWER_SUPPLY_TYPE_USB: + /* USB disconnected */ + if (!ret.intval && + (di->chg_info.conn_chg & USB_CHG)) { + di->chg_info.prev_conn_chg = + di->chg_info.conn_chg; + di->chg_info.conn_chg &= ~USB_CHG; + } + /* USB connected */ + else if (ret.intval && + !(di->chg_info.conn_chg & USB_CHG)) { + di->chg_info.prev_conn_chg = + di->chg_info.conn_chg; + di->chg_info.conn_chg |= USB_CHG; + } + break; + default: + break; + } + break; + + case POWER_SUPPLY_PROP_ONLINE: + switch (ext->desc->type) { + case POWER_SUPPLY_TYPE_BATTERY: + break; + case POWER_SUPPLY_TYPE_MAINS: + /* AC offline */ + if (!ret.intval && + (di->chg_info.online_chg & AC_CHG)) { + di->chg_info.prev_online_chg = + di->chg_info.online_chg; + di->chg_info.online_chg &= ~AC_CHG; + } + /* AC online */ + else if (ret.intval && + !(di->chg_info.online_chg & AC_CHG)) { + di->chg_info.prev_online_chg = + di->chg_info.online_chg; + di->chg_info.online_chg |= AC_CHG; + queue_delayed_work(di->chargalg_wq, + &di->chargalg_wd_work, 0); + } + break; + case POWER_SUPPLY_TYPE_USB: + /* USB offline */ + if (!ret.intval && + (di->chg_info.online_chg & USB_CHG)) { + di->chg_info.prev_online_chg = + di->chg_info.online_chg; + di->chg_info.online_chg &= ~USB_CHG; + } + /* USB online */ + else if (ret.intval && + !(di->chg_info.online_chg & USB_CHG)) { + di->chg_info.prev_online_chg = + di->chg_info.online_chg; + di->chg_info.online_chg |= USB_CHG; + queue_delayed_work(di->chargalg_wq, + &di->chargalg_wd_work, 0); + } + break; + default: + break; + } + break; + + case POWER_SUPPLY_PROP_HEALTH: + switch (ext->desc->type) { + case POWER_SUPPLY_TYPE_BATTERY: + break; + case POWER_SUPPLY_TYPE_MAINS: + switch (ret.intval) { + case POWER_SUPPLY_HEALTH_UNSPEC_FAILURE: + di->events.mainextchnotok = true; + di->events.main_thermal_prot = false; + di->events.main_ovv = false; + di->events.ac_wd_expired = false; + break; + case POWER_SUPPLY_HEALTH_DEAD: + di->events.ac_wd_expired = true; + di->events.mainextchnotok = false; + di->events.main_ovv = false; + di->events.main_thermal_prot = false; + break; + case POWER_SUPPLY_HEALTH_COLD: + case POWER_SUPPLY_HEALTH_OVERHEAT: + di->events.main_thermal_prot = true; + di->events.mainextchnotok = false; + di->events.main_ovv = false; + di->events.ac_wd_expired = false; + break; + case POWER_SUPPLY_HEALTH_OVERVOLTAGE: + di->events.main_ovv = true; + di->events.mainextchnotok = false; + di->events.main_thermal_prot = false; + di->events.ac_wd_expired = false; + break; + case POWER_SUPPLY_HEALTH_GOOD: + di->events.main_thermal_prot = false; + di->events.mainextchnotok = false; + di->events.main_ovv = false; + di->events.ac_wd_expired = false; + break; + default: + break; + } + break; + + case POWER_SUPPLY_TYPE_USB: + switch (ret.intval) { + case POWER_SUPPLY_HEALTH_UNSPEC_FAILURE: + di->events.usbchargernotok = true; + di->events.usb_thermal_prot = false; + di->events.vbus_ovv = false; + di->events.usb_wd_expired = false; + break; + case POWER_SUPPLY_HEALTH_DEAD: + di->events.usb_wd_expired = true; + di->events.usbchargernotok = false; + di->events.usb_thermal_prot = false; + di->events.vbus_ovv = false; + break; + case POWER_SUPPLY_HEALTH_COLD: + case POWER_SUPPLY_HEALTH_OVERHEAT: + di->events.usb_thermal_prot = true; + di->events.usbchargernotok = false; + di->events.vbus_ovv = false; + di->events.usb_wd_expired = false; + break; + case POWER_SUPPLY_HEALTH_OVERVOLTAGE: + di->events.vbus_ovv = true; + di->events.usbchargernotok = false; + di->events.usb_thermal_prot = false; + di->events.usb_wd_expired = false; + break; + case POWER_SUPPLY_HEALTH_GOOD: + di->events.usbchargernotok = false; + di->events.usb_thermal_prot = false; + di->events.vbus_ovv = false; + di->events.usb_wd_expired = false; + break; + default: + break; + } + default: + break; + } + break; + + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + switch (ext->desc->type) { + case POWER_SUPPLY_TYPE_BATTERY: + di->batt_data.volt = ret.intval / 1000; + break; + case POWER_SUPPLY_TYPE_MAINS: + di->chg_info.ac_volt = ret.intval / 1000; + break; + case POWER_SUPPLY_TYPE_USB: + di->chg_info.usb_volt = ret.intval / 1000; + break; + default: + break; + } + break; + + case POWER_SUPPLY_PROP_VOLTAGE_AVG: + switch (ext->desc->type) { + case POWER_SUPPLY_TYPE_MAINS: + /* AVG is used to indicate when we are + * in CV mode */ + if (ret.intval) + di->events.ac_cv_active = true; + else + di->events.ac_cv_active = false; + + break; + case POWER_SUPPLY_TYPE_USB: + /* AVG is used to indicate when we are + * in CV mode */ + if (ret.intval) + di->events.usb_cv_active = true; + else + di->events.usb_cv_active = false; + + break; + default: + break; + } + break; + + case POWER_SUPPLY_PROP_TECHNOLOGY: + switch (ext->desc->type) { + case POWER_SUPPLY_TYPE_BATTERY: + if (ret.intval) + di->events.batt_unknown = false; + else + di->events.batt_unknown = true; + + break; + default: + break; + } + break; + + case POWER_SUPPLY_PROP_TEMP: + di->batt_data.temp = ret.intval / 10; + break; + + case POWER_SUPPLY_PROP_CURRENT_NOW: + switch (ext->desc->type) { + case POWER_SUPPLY_TYPE_MAINS: + di->chg_info.ac_curr = + ret.intval / 1000; + break; + case POWER_SUPPLY_TYPE_USB: + di->chg_info.usb_curr = + ret.intval / 1000; + break; + case POWER_SUPPLY_TYPE_BATTERY: + di->batt_data.inst_curr = ret.intval / 1000; + break; + default: + break; + } + break; + + case POWER_SUPPLY_PROP_CURRENT_AVG: + switch (ext->desc->type) { + case POWER_SUPPLY_TYPE_BATTERY: + di->batt_data.avg_curr = ret.intval / 1000; + break; + case POWER_SUPPLY_TYPE_USB: + if (ret.intval) + di->events.vbus_collapsed = true; + else + di->events.vbus_collapsed = false; + break; + default: + break; + } + break; + case POWER_SUPPLY_PROP_CAPACITY: + if (!capacity_updated) + di->batt_data.percent = ret.intval; + break; + default: + break; + } + } + return 0; +} + +/** + * abx500_chargalg_external_power_changed() - callback for power supply changes + * @psy: pointer to the structure power_supply + * + * This function is the entry point of the pointer external_power_changed + * of the structure power_supply. + * This function gets executed when there is a change in any external power + * supply that this driver needs to be notified of. + */ +static void abx500_chargalg_external_power_changed(struct power_supply *psy) +{ + struct abx500_chargalg *di = power_supply_get_drvdata(psy); + + /* + * Trigger execution of the algorithm instantly and read + * all power_supply properties there instead + */ + queue_work(di->chargalg_wq, &di->chargalg_work); +} + +/** + * abx500_chargalg_algorithm() - Main function for the algorithm + * @di: pointer to the abx500_chargalg structure + * + * This is the main control function for the charging algorithm. + * It is called periodically or when something happens that will + * trigger a state change + */ +static void abx500_chargalg_algorithm(struct abx500_chargalg *di) +{ + int charger_status; + int ret; + int curr_step_lvl; + + /* Collect data from all power_supply class devices */ + class_for_each_device(power_supply_class, NULL, + di->chargalg_psy, abx500_chargalg_get_ext_psy_data); + + abx500_chargalg_end_of_charge(di); + abx500_chargalg_check_temp(di); + abx500_chargalg_check_charger_voltage(di); + + charger_status = abx500_chargalg_check_charger_connection(di); + abx500_chargalg_check_current_step_status(di); + + if (is_ab8500(di->parent)) { + ret = abx500_chargalg_check_charger_enable(di); + if (ret < 0) + dev_err(di->dev, "Checking charger is enabled error" + ": Returned Value %d\n", ret); + } + + /* + * First check if we have a charger connected. + * Also we don't allow charging of unknown batteries if configured + * this way + */ + if (!charger_status || + (di->events.batt_unknown && !di->bm->chg_unknown_bat)) { + if (di->charge_state != STATE_HANDHELD) { + di->events.safety_timer_expired = false; + abx500_chargalg_state_to(di, STATE_HANDHELD_INIT); + } + } + + /* If suspended, we should not continue checking the flags */ + else if (di->charge_state == STATE_SUSPENDED_INIT || + di->charge_state == STATE_SUSPENDED) { + /* We don't do anything here, just don,t continue */ + } + + /* Safety timer expiration */ + else if (di->events.safety_timer_expired) { + if (di->charge_state != STATE_SAFETY_TIMER_EXPIRED) + abx500_chargalg_state_to(di, + STATE_SAFETY_TIMER_EXPIRED_INIT); + } + /* + * Check if any interrupts has occured + * that will prevent us from charging + */ + + /* Battery removed */ + else if (di->events.batt_rem) { + if (di->charge_state != STATE_BATT_REMOVED) + abx500_chargalg_state_to(di, STATE_BATT_REMOVED_INIT); + } + /* Main or USB charger not ok. */ + else if (di->events.mainextchnotok || di->events.usbchargernotok) { + /* + * If vbus_collapsed is set, we have to lower the charger + * current, which is done in the normal state below + */ + if (di->charge_state != STATE_CHG_NOT_OK && + !di->events.vbus_collapsed) + abx500_chargalg_state_to(di, STATE_CHG_NOT_OK_INIT); + } + /* VBUS, Main or VBAT OVV. */ + else if (di->events.vbus_ovv || + di->events.main_ovv || + di->events.batt_ovv || + !di->chg_info.usb_chg_ok || + !di->chg_info.ac_chg_ok) { + if (di->charge_state != STATE_OVV_PROTECT) + abx500_chargalg_state_to(di, STATE_OVV_PROTECT_INIT); + } + /* USB Thermal, stop charging */ + else if (di->events.main_thermal_prot || + di->events.usb_thermal_prot) { + if (di->charge_state != STATE_HW_TEMP_PROTECT) + abx500_chargalg_state_to(di, + STATE_HW_TEMP_PROTECT_INIT); + } + /* Battery temp over/under */ + else if (di->events.btemp_underover) { + if (di->charge_state != STATE_TEMP_UNDEROVER) + abx500_chargalg_state_to(di, + STATE_TEMP_UNDEROVER_INIT); + } + /* Watchdog expired */ + else if (di->events.ac_wd_expired || + di->events.usb_wd_expired) { + if (di->charge_state != STATE_WD_EXPIRED) + abx500_chargalg_state_to(di, STATE_WD_EXPIRED_INIT); + } + /* Battery temp high/low */ + else if (di->events.btemp_lowhigh) { + if (di->charge_state != STATE_TEMP_LOWHIGH) + abx500_chargalg_state_to(di, STATE_TEMP_LOWHIGH_INIT); + } + + dev_dbg(di->dev, + "[CHARGALG] Vb %d Ib_avg %d Ib_inst %d Tb %d Cap %d Maint %d " + "State %s Active_chg %d Chg_status %d AC %d USB %d " + "AC_online %d USB_online %d AC_CV %d USB_CV %d AC_I %d " + "USB_I %d AC_Vset %d AC_Iset %d USB_Vset %d USB_Iset %d\n", + di->batt_data.volt, + di->batt_data.avg_curr, + di->batt_data.inst_curr, + di->batt_data.temp, + di->batt_data.percent, + di->maintenance_chg, + states[di->charge_state], + di->chg_info.charger_type, + di->charge_status, + di->chg_info.conn_chg & AC_CHG, + di->chg_info.conn_chg & USB_CHG, + di->chg_info.online_chg & AC_CHG, + di->chg_info.online_chg & USB_CHG, + di->events.ac_cv_active, + di->events.usb_cv_active, + di->chg_info.ac_curr, + di->chg_info.usb_curr, + di->chg_info.ac_vset, + di->chg_info.ac_iset, + di->chg_info.usb_vset, + di->chg_info.usb_iset); + + switch (di->charge_state) { + case STATE_HANDHELD_INIT: + abx500_chargalg_stop_charging(di); + di->charge_status = POWER_SUPPLY_STATUS_DISCHARGING; + abx500_chargalg_state_to(di, STATE_HANDHELD); + /* Intentional fallthrough */ + + case STATE_HANDHELD: + break; + + case STATE_SUSPENDED_INIT: + if (di->susp_status.ac_suspended) + abx500_chargalg_ac_en(di, false, 0, 0); + if (di->susp_status.usb_suspended) + abx500_chargalg_usb_en(di, false, 0, 0); + abx500_chargalg_stop_safety_timer(di); + abx500_chargalg_stop_maintenance_timer(di); + di->charge_status = POWER_SUPPLY_STATUS_NOT_CHARGING; + di->maintenance_chg = false; + abx500_chargalg_state_to(di, STATE_SUSPENDED); + power_supply_changed(di->chargalg_psy); + /* Intentional fallthrough */ + + case STATE_SUSPENDED: + /* CHARGING is suspended */ + break; + + case STATE_BATT_REMOVED_INIT: + abx500_chargalg_stop_charging(di); + abx500_chargalg_state_to(di, STATE_BATT_REMOVED); + /* Intentional fallthrough */ + + case STATE_BATT_REMOVED: + if (!di->events.batt_rem) + abx500_chargalg_state_to(di, STATE_NORMAL_INIT); + break; + + case STATE_HW_TEMP_PROTECT_INIT: + abx500_chargalg_stop_charging(di); + abx500_chargalg_state_to(di, STATE_HW_TEMP_PROTECT); + /* Intentional fallthrough */ + + case STATE_HW_TEMP_PROTECT: + if (!di->events.main_thermal_prot && + !di->events.usb_thermal_prot) + abx500_chargalg_state_to(di, STATE_NORMAL_INIT); + break; + + case STATE_OVV_PROTECT_INIT: + abx500_chargalg_stop_charging(di); + abx500_chargalg_state_to(di, STATE_OVV_PROTECT); + /* Intentional fallthrough */ + + case STATE_OVV_PROTECT: + if (!di->events.vbus_ovv && + !di->events.main_ovv && + !di->events.batt_ovv && + di->chg_info.usb_chg_ok && + di->chg_info.ac_chg_ok) + abx500_chargalg_state_to(di, STATE_NORMAL_INIT); + break; + + case STATE_CHG_NOT_OK_INIT: + abx500_chargalg_stop_charging(di); + abx500_chargalg_state_to(di, STATE_CHG_NOT_OK); + /* Intentional fallthrough */ + + case STATE_CHG_NOT_OK: + if (!di->events.mainextchnotok && + !di->events.usbchargernotok) + abx500_chargalg_state_to(di, STATE_NORMAL_INIT); + break; + + case STATE_SAFETY_TIMER_EXPIRED_INIT: + abx500_chargalg_stop_charging(di); + abx500_chargalg_state_to(di, STATE_SAFETY_TIMER_EXPIRED); + /* Intentional fallthrough */ + + case STATE_SAFETY_TIMER_EXPIRED: + /* We exit this state when charger is removed */ + break; + + case STATE_NORMAL_INIT: + if ((di->chg_info.charger_type & USB_CHG) && + di->usb_chg->power_path) { + if (di->batt_data.volt > + (di->bm->fg_params->lowbat_threshold + + BAT_PLUS_MARGIN)) { + ab8540_chargalg_usb_pre_chg_en(di, false); + ab8540_chargalg_usb_pp_en(di, false); + } else { + ab8540_chargalg_usb_pp_en(di, true); + ab8540_chargalg_usb_pre_chg_en(di, true); + abx500_chargalg_state_to(di, + STATE_USB_PP_PRE_CHARGE); + break; + } + } + + if (di->curr_status.curr_step == CHARGALG_CURR_STEP_LOW) + abx500_chargalg_stop_charging(di); + else { + curr_step_lvl = di->bm->bat_type[ + di->bm->batt_id].normal_cur_lvl + * di->curr_status.curr_step + / CHARGALG_CURR_STEP_HIGH; + abx500_chargalg_start_charging(di, + di->bm->bat_type[di->bm->batt_id] + .normal_vol_lvl, curr_step_lvl); + } + + abx500_chargalg_state_to(di, STATE_NORMAL); + abx500_chargalg_start_safety_timer(di); + abx500_chargalg_stop_maintenance_timer(di); + init_maxim_chg_curr(di); + di->charge_status = POWER_SUPPLY_STATUS_CHARGING; + di->eoc_cnt = 0; + di->maintenance_chg = false; + power_supply_changed(di->chargalg_psy); + + break; + + case STATE_USB_PP_PRE_CHARGE: + if (di->batt_data.volt > + (di->bm->fg_params->lowbat_threshold + + BAT_PLUS_MARGIN)) + abx500_chargalg_state_to(di, STATE_NORMAL_INIT); + break; + + case STATE_NORMAL: + handle_maxim_chg_curr(di); + if (di->charge_status == POWER_SUPPLY_STATUS_FULL && + di->maintenance_chg) { + if (di->bm->no_maintenance) + abx500_chargalg_state_to(di, + STATE_WAIT_FOR_RECHARGE_INIT); + else + abx500_chargalg_state_to(di, + STATE_MAINTENANCE_A_INIT); + } + break; + + /* This state will be used when the maintenance state is disabled */ + case STATE_WAIT_FOR_RECHARGE_INIT: + abx500_chargalg_hold_charging(di); + abx500_chargalg_state_to(di, STATE_WAIT_FOR_RECHARGE); + /* Intentional fallthrough */ + + case STATE_WAIT_FOR_RECHARGE: + if (di->batt_data.percent <= + di->bm->bat_type[di->bm->batt_id]. + recharge_cap) + abx500_chargalg_state_to(di, STATE_NORMAL_INIT); + break; + + case STATE_MAINTENANCE_A_INIT: + abx500_chargalg_stop_safety_timer(di); + abx500_chargalg_start_maintenance_timer(di, + di->bm->bat_type[ + di->bm->batt_id].maint_a_chg_timer_h); + abx500_chargalg_start_charging(di, + di->bm->bat_type[ + di->bm->batt_id].maint_a_vol_lvl, + di->bm->bat_type[ + di->bm->batt_id].maint_a_cur_lvl); + abx500_chargalg_state_to(di, STATE_MAINTENANCE_A); + power_supply_changed(di->chargalg_psy); + /* Intentional fallthrough*/ + + case STATE_MAINTENANCE_A: + if (di->events.maintenance_timer_expired) { + abx500_chargalg_stop_maintenance_timer(di); + abx500_chargalg_state_to(di, STATE_MAINTENANCE_B_INIT); + } + break; + + case STATE_MAINTENANCE_B_INIT: + abx500_chargalg_start_maintenance_timer(di, + di->bm->bat_type[ + di->bm->batt_id].maint_b_chg_timer_h); + abx500_chargalg_start_charging(di, + di->bm->bat_type[ + di->bm->batt_id].maint_b_vol_lvl, + di->bm->bat_type[ + di->bm->batt_id].maint_b_cur_lvl); + abx500_chargalg_state_to(di, STATE_MAINTENANCE_B); + power_supply_changed(di->chargalg_psy); + /* Intentional fallthrough*/ + + case STATE_MAINTENANCE_B: + if (di->events.maintenance_timer_expired) { + abx500_chargalg_stop_maintenance_timer(di); + abx500_chargalg_state_to(di, STATE_NORMAL_INIT); + } + break; + + case STATE_TEMP_LOWHIGH_INIT: + abx500_chargalg_start_charging(di, + di->bm->bat_type[ + di->bm->batt_id].low_high_vol_lvl, + di->bm->bat_type[ + di->bm->batt_id].low_high_cur_lvl); + abx500_chargalg_stop_maintenance_timer(di); + di->charge_status = POWER_SUPPLY_STATUS_CHARGING; + abx500_chargalg_state_to(di, STATE_TEMP_LOWHIGH); + power_supply_changed(di->chargalg_psy); + /* Intentional fallthrough */ + + case STATE_TEMP_LOWHIGH: + if (!di->events.btemp_lowhigh) + abx500_chargalg_state_to(di, STATE_NORMAL_INIT); + break; + + case STATE_WD_EXPIRED_INIT: + abx500_chargalg_stop_charging(di); + abx500_chargalg_state_to(di, STATE_WD_EXPIRED); + /* Intentional fallthrough */ + + case STATE_WD_EXPIRED: + if (!di->events.ac_wd_expired && + !di->events.usb_wd_expired) + abx500_chargalg_state_to(di, STATE_NORMAL_INIT); + break; + + case STATE_TEMP_UNDEROVER_INIT: + abx500_chargalg_stop_charging(di); + abx500_chargalg_state_to(di, STATE_TEMP_UNDEROVER); + /* Intentional fallthrough */ + + case STATE_TEMP_UNDEROVER: + if (!di->events.btemp_underover) + abx500_chargalg_state_to(di, STATE_NORMAL_INIT); + break; + } + + /* Start charging directly if the new state is a charge state */ + if (di->charge_state == STATE_NORMAL_INIT || + di->charge_state == STATE_MAINTENANCE_A_INIT || + di->charge_state == STATE_MAINTENANCE_B_INIT) + queue_work(di->chargalg_wq, &di->chargalg_work); +} + +/** + * abx500_chargalg_periodic_work() - Periodic work for the algorithm + * @work: pointer to the work_struct structure + * + * Work queue function for the charging algorithm + */ +static void abx500_chargalg_periodic_work(struct work_struct *work) +{ + struct abx500_chargalg *di = container_of(work, + struct abx500_chargalg, chargalg_periodic_work.work); + + abx500_chargalg_algorithm(di); + + /* + * If a charger is connected then the battery has to be monitored + * frequently, else the work can be delayed. + */ + if (di->chg_info.conn_chg) + queue_delayed_work(di->chargalg_wq, + &di->chargalg_periodic_work, + di->bm->interval_charging * HZ); + else + queue_delayed_work(di->chargalg_wq, + &di->chargalg_periodic_work, + di->bm->interval_not_charging * HZ); +} + +/** + * abx500_chargalg_wd_work() - periodic work to kick the charger watchdog + * @work: pointer to the work_struct structure + * + * Work queue function for kicking the charger watchdog + */ +static void abx500_chargalg_wd_work(struct work_struct *work) +{ + int ret; + struct abx500_chargalg *di = container_of(work, + struct abx500_chargalg, chargalg_wd_work.work); + + dev_dbg(di->dev, "abx500_chargalg_wd_work\n"); + + ret = abx500_chargalg_kick_watchdog(di); + if (ret < 0) + dev_err(di->dev, "failed to kick watchdog\n"); + + queue_delayed_work(di->chargalg_wq, + &di->chargalg_wd_work, CHG_WD_INTERVAL); +} + +/** + * abx500_chargalg_work() - Work to run the charging algorithm instantly + * @work: pointer to the work_struct structure + * + * Work queue function for calling the charging algorithm + */ +static void abx500_chargalg_work(struct work_struct *work) +{ + struct abx500_chargalg *di = container_of(work, + struct abx500_chargalg, chargalg_work); + + abx500_chargalg_algorithm(di); +} + +/** + * abx500_chargalg_get_property() - get the chargalg properties + * @psy: pointer to the power_supply structure + * @psp: pointer to the power_supply_property structure + * @val: pointer to the power_supply_propval union + * + * This function gets called when an application tries to get the + * chargalg properties by reading the sysfs files. + * status: charging/discharging/full/unknown + * health: health of the battery + * Returns error code in case of failure else 0 on success + */ +static int abx500_chargalg_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct abx500_chargalg *di = power_supply_get_drvdata(psy); + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + val->intval = di->charge_status; + break; + case POWER_SUPPLY_PROP_HEALTH: + if (di->events.batt_ovv) { + val->intval = POWER_SUPPLY_HEALTH_OVERVOLTAGE; + } else if (di->events.btemp_underover) { + if (di->batt_data.temp <= di->bm->temp_under) + val->intval = POWER_SUPPLY_HEALTH_COLD; + else + val->intval = POWER_SUPPLY_HEALTH_OVERHEAT; + } else if (di->charge_state == STATE_SAFETY_TIMER_EXPIRED || + di->charge_state == STATE_SAFETY_TIMER_EXPIRED_INIT) { + val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; + } else { + val->intval = POWER_SUPPLY_HEALTH_GOOD; + } + break; + default: + return -EINVAL; + } + return 0; +} + +/* Exposure to the sysfs interface */ + +static ssize_t abx500_chargalg_curr_step_show(struct abx500_chargalg *di, + char *buf) +{ + return sprintf(buf, "%d\n", di->curr_status.curr_step); +} + +static ssize_t abx500_chargalg_curr_step_store(struct abx500_chargalg *di, + const char *buf, size_t length) +{ + long int param; + int ret; + + ret = kstrtol(buf, 10, ¶m); + if (ret < 0) + return ret; + + di->curr_status.curr_step = param; + if (di->curr_status.curr_step >= CHARGALG_CURR_STEP_LOW && + di->curr_status.curr_step <= CHARGALG_CURR_STEP_HIGH) { + di->curr_status.curr_step_change = true; + queue_work(di->chargalg_wq, &di->chargalg_work); + } else + dev_info(di->dev, "Wrong current step\n" + "Enter 0. Disable AC/USB Charging\n" + "1--100. Set AC/USB charging current step\n" + "100. Enable AC/USB Charging\n"); + + return strlen(buf); +} + + +static ssize_t abx500_chargalg_en_show(struct abx500_chargalg *di, + char *buf) +{ + return sprintf(buf, "%d\n", + di->susp_status.ac_suspended && + di->susp_status.usb_suspended); +} + +static ssize_t abx500_chargalg_en_store(struct abx500_chargalg *di, + const char *buf, size_t length) +{ + long int param; + int ac_usb; + int ret; + + ret = kstrtol(buf, 10, ¶m); + if (ret < 0) + return ret; + + ac_usb = param; + switch (ac_usb) { + case 0: + /* Disable charging */ + di->susp_status.ac_suspended = true; + di->susp_status.usb_suspended = true; + di->susp_status.suspended_change = true; + /* Trigger a state change */ + queue_work(di->chargalg_wq, + &di->chargalg_work); + break; + case 1: + /* Enable AC Charging */ + di->susp_status.ac_suspended = false; + di->susp_status.suspended_change = true; + /* Trigger a state change */ + queue_work(di->chargalg_wq, + &di->chargalg_work); + break; + case 2: + /* Enable USB charging */ + di->susp_status.usb_suspended = false; + di->susp_status.suspended_change = true; + /* Trigger a state change */ + queue_work(di->chargalg_wq, + &di->chargalg_work); + break; + default: + dev_info(di->dev, "Wrong input\n" + "Enter 0. Disable AC/USB Charging\n" + "1. Enable AC charging\n" + "2. Enable USB Charging\n"); + }; + return strlen(buf); +} + +static struct abx500_chargalg_sysfs_entry abx500_chargalg_en_charger = + __ATTR(chargalg, 0644, abx500_chargalg_en_show, + abx500_chargalg_en_store); + +static struct abx500_chargalg_sysfs_entry abx500_chargalg_curr_step = + __ATTR(chargalg_curr_step, 0644, abx500_chargalg_curr_step_show, + abx500_chargalg_curr_step_store); + +static ssize_t abx500_chargalg_sysfs_show(struct kobject *kobj, + struct attribute *attr, char *buf) +{ + struct abx500_chargalg_sysfs_entry *entry = container_of(attr, + struct abx500_chargalg_sysfs_entry, attr); + + struct abx500_chargalg *di = container_of(kobj, + struct abx500_chargalg, chargalg_kobject); + + if (!entry->show) + return -EIO; + + return entry->show(di, buf); +} + +static ssize_t abx500_chargalg_sysfs_charger(struct kobject *kobj, + struct attribute *attr, const char *buf, size_t length) +{ + struct abx500_chargalg_sysfs_entry *entry = container_of(attr, + struct abx500_chargalg_sysfs_entry, attr); + + struct abx500_chargalg *di = container_of(kobj, + struct abx500_chargalg, chargalg_kobject); + + if (!entry->store) + return -EIO; + + return entry->store(di, buf, length); +} + +static struct attribute *abx500_chargalg_chg[] = { + &abx500_chargalg_en_charger.attr, + &abx500_chargalg_curr_step.attr, + NULL, +}; + +static const struct sysfs_ops abx500_chargalg_sysfs_ops = { + .show = abx500_chargalg_sysfs_show, + .store = abx500_chargalg_sysfs_charger, +}; + +static struct kobj_type abx500_chargalg_ktype = { + .sysfs_ops = &abx500_chargalg_sysfs_ops, + .default_attrs = abx500_chargalg_chg, +}; + +/** + * abx500_chargalg_sysfs_exit() - de-init of sysfs entry + * @di: pointer to the struct abx500_chargalg + * + * This function removes the entry in sysfs. + */ +static void abx500_chargalg_sysfs_exit(struct abx500_chargalg *di) +{ + kobject_del(&di->chargalg_kobject); +} + +/** + * abx500_chargalg_sysfs_init() - init of sysfs entry + * @di: pointer to the struct abx500_chargalg + * + * This function adds an entry in sysfs. + * Returns error code in case of failure else 0(on success) + */ +static int abx500_chargalg_sysfs_init(struct abx500_chargalg *di) +{ + int ret = 0; + + ret = kobject_init_and_add(&di->chargalg_kobject, + &abx500_chargalg_ktype, + NULL, "abx500_chargalg"); + if (ret < 0) + dev_err(di->dev, "failed to create sysfs entry\n"); + + return ret; +} +/* Exposure to the sysfs interface <> */ + +#if defined(CONFIG_PM) +static int abx500_chargalg_resume(struct platform_device *pdev) +{ + struct abx500_chargalg *di = platform_get_drvdata(pdev); + + /* Kick charger watchdog if charging (any charger online) */ + if (di->chg_info.online_chg) + queue_delayed_work(di->chargalg_wq, &di->chargalg_wd_work, 0); + + /* + * Run the charging algorithm directly to be sure we don't + * do it too seldom + */ + queue_delayed_work(di->chargalg_wq, &di->chargalg_periodic_work, 0); + + return 0; +} + +static int abx500_chargalg_suspend(struct platform_device *pdev, + pm_message_t state) +{ + struct abx500_chargalg *di = platform_get_drvdata(pdev); + + if (di->chg_info.online_chg) + cancel_delayed_work_sync(&di->chargalg_wd_work); + + cancel_delayed_work_sync(&di->chargalg_periodic_work); + + return 0; +} +#else +#define abx500_chargalg_suspend NULL +#define abx500_chargalg_resume NULL +#endif + +static int abx500_chargalg_remove(struct platform_device *pdev) +{ + struct abx500_chargalg *di = platform_get_drvdata(pdev); + + /* sysfs interface to enable/disbale charging from user space */ + abx500_chargalg_sysfs_exit(di); + + hrtimer_cancel(&di->safety_timer); + hrtimer_cancel(&di->maintenance_timer); + + cancel_delayed_work_sync(&di->chargalg_periodic_work); + cancel_delayed_work_sync(&di->chargalg_wd_work); + cancel_work_sync(&di->chargalg_work); + + /* Delete the work queue */ + destroy_workqueue(di->chargalg_wq); + + power_supply_unregister(di->chargalg_psy); + + return 0; +} + +static char *supply_interface[] = { + "ab8500_fg", +}; + +static const struct power_supply_desc abx500_chargalg_desc = { + .name = "abx500_chargalg", + .type = POWER_SUPPLY_TYPE_BATTERY, + .properties = abx500_chargalg_props, + .num_properties = ARRAY_SIZE(abx500_chargalg_props), + .get_property = abx500_chargalg_get_property, + .external_power_changed = abx500_chargalg_external_power_changed, +}; + +static int abx500_chargalg_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct abx500_bm_data *plat = pdev->dev.platform_data; + struct power_supply_config psy_cfg = {}; + struct abx500_chargalg *di; + int ret = 0; + + di = devm_kzalloc(&pdev->dev, sizeof(*di), GFP_KERNEL); + if (!di) { + dev_err(&pdev->dev, "%s no mem for ab8500_chargalg\n", __func__); + return -ENOMEM; + } + + if (!plat) { + dev_err(&pdev->dev, "no battery management data supplied\n"); + return -EINVAL; + } + di->bm = plat; + + if (np) { + ret = ab8500_bm_of_probe(&pdev->dev, np, di->bm); + if (ret) { + dev_err(&pdev->dev, "failed to get battery information\n"); + return ret; + } + } + + /* get device struct and parent */ + di->dev = &pdev->dev; + di->parent = dev_get_drvdata(pdev->dev.parent); + + psy_cfg.supplied_to = supply_interface; + psy_cfg.num_supplicants = ARRAY_SIZE(supply_interface); + psy_cfg.drv_data = di; + + /* Initilialize safety timer */ + hrtimer_init(&di->safety_timer, CLOCK_REALTIME, HRTIMER_MODE_ABS); + di->safety_timer.function = abx500_chargalg_safety_timer_expired; + + /* Initilialize maintenance timer */ + hrtimer_init(&di->maintenance_timer, CLOCK_REALTIME, HRTIMER_MODE_ABS); + di->maintenance_timer.function = + abx500_chargalg_maintenance_timer_expired; + + /* Create a work queue for the chargalg */ + di->chargalg_wq = + create_singlethread_workqueue("abx500_chargalg_wq"); + if (di->chargalg_wq == NULL) { + dev_err(di->dev, "failed to create work queue\n"); + return -ENOMEM; + } + + /* Init work for chargalg */ + INIT_DEFERRABLE_WORK(&di->chargalg_periodic_work, + abx500_chargalg_periodic_work); + INIT_DEFERRABLE_WORK(&di->chargalg_wd_work, + abx500_chargalg_wd_work); + + /* Init work for chargalg */ + INIT_WORK(&di->chargalg_work, abx500_chargalg_work); + + /* To detect charger at startup */ + di->chg_info.prev_conn_chg = -1; + + /* Register chargalg power supply class */ + di->chargalg_psy = power_supply_register(di->dev, &abx500_chargalg_desc, + &psy_cfg); + if (IS_ERR(di->chargalg_psy)) { + dev_err(di->dev, "failed to register chargalg psy\n"); + ret = PTR_ERR(di->chargalg_psy); + goto free_chargalg_wq; + } + + platform_set_drvdata(pdev, di); + + /* sysfs interface to enable/disable charging from user space */ + ret = abx500_chargalg_sysfs_init(di); + if (ret) { + dev_err(di->dev, "failed to create sysfs entry\n"); + goto free_psy; + } + di->curr_status.curr_step = CHARGALG_CURR_STEP_HIGH; + + /* Run the charging algorithm */ + queue_delayed_work(di->chargalg_wq, &di->chargalg_periodic_work, 0); + + dev_info(di->dev, "probe success\n"); + return ret; + +free_psy: + power_supply_unregister(di->chargalg_psy); +free_chargalg_wq: + destroy_workqueue(di->chargalg_wq); + return ret; +} + +static const struct of_device_id ab8500_chargalg_match[] = { + { .compatible = "stericsson,ab8500-chargalg", }, + { }, +}; + +static struct platform_driver abx500_chargalg_driver = { + .probe = abx500_chargalg_probe, + .remove = abx500_chargalg_remove, + .suspend = abx500_chargalg_suspend, + .resume = abx500_chargalg_resume, + .driver = { + .name = "ab8500-chargalg", + .of_match_table = ab8500_chargalg_match, + }, +}; + +module_platform_driver(abx500_chargalg_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Johan Palsson, Karl Komierowski"); +MODULE_ALIAS("platform:abx500-chargalg"); +MODULE_DESCRIPTION("abx500 battery charging algorithm"); diff --git a/drivers/power/supply/act8945a_charger.c b/drivers/power/supply/act8945a_charger.c new file mode 100644 index 000000000000..b5c00e45741e --- /dev/null +++ b/drivers/power/supply/act8945a_charger.c @@ -0,0 +1,359 @@ +/* + * Power supply driver for the Active-semi ACT8945A PMIC + * + * Copyright (C) 2015 Atmel Corporation + * + * Author: Wenyou Yang + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ +#include +#include +#include +#include +#include +#include + +static const char *act8945a_charger_model = "ACT8945A"; +static const char *act8945a_charger_manufacturer = "Active-semi"; + +/** + * ACT8945A Charger Register Map + */ + +/* 0x70: Reserved */ +#define ACT8945A_APCH_CFG 0x71 +#define ACT8945A_APCH_STATUS 0x78 +#define ACT8945A_APCH_CTRL 0x79 +#define ACT8945A_APCH_STATE 0x7A + +/* ACT8945A_APCH_CFG */ +#define APCH_CFG_OVPSET (0x3 << 0) +#define APCH_CFG_OVPSET_6V6 (0x0 << 0) +#define APCH_CFG_OVPSET_7V (0x1 << 0) +#define APCH_CFG_OVPSET_7V5 (0x2 << 0) +#define APCH_CFG_OVPSET_8V (0x3 << 0) +#define APCH_CFG_PRETIMO (0x3 << 2) +#define APCH_CFG_PRETIMO_40_MIN (0x0 << 2) +#define APCH_CFG_PRETIMO_60_MIN (0x1 << 2) +#define APCH_CFG_PRETIMO_80_MIN (0x2 << 2) +#define APCH_CFG_PRETIMO_DISABLED (0x3 << 2) +#define APCH_CFG_TOTTIMO (0x3 << 4) +#define APCH_CFG_TOTTIMO_3_HOUR (0x0 << 4) +#define APCH_CFG_TOTTIMO_4_HOUR (0x1 << 4) +#define APCH_CFG_TOTTIMO_5_HOUR (0x2 << 4) +#define APCH_CFG_TOTTIMO_DISABLED (0x3 << 4) +#define APCH_CFG_SUSCHG (0x1 << 7) + +#define APCH_STATUS_CHGDAT BIT(0) +#define APCH_STATUS_INDAT BIT(1) +#define APCH_STATUS_TEMPDAT BIT(2) +#define APCH_STATUS_TIMRDAT BIT(3) +#define APCH_STATUS_CHGSTAT BIT(4) +#define APCH_STATUS_INSTAT BIT(5) +#define APCH_STATUS_TEMPSTAT BIT(6) +#define APCH_STATUS_TIMRSTAT BIT(7) + +#define APCH_CTRL_CHGEOCOUT BIT(0) +#define APCH_CTRL_INDIS BIT(1) +#define APCH_CTRL_TEMPOUT BIT(2) +#define APCH_CTRL_TIMRPRE BIT(3) +#define APCH_CTRL_CHGEOCIN BIT(4) +#define APCH_CTRL_INCON BIT(5) +#define APCH_CTRL_TEMPIN BIT(6) +#define APCH_CTRL_TIMRTOT BIT(7) + +#define APCH_STATE_ACINSTAT (0x1 << 1) +#define APCH_STATE_CSTATE (0x3 << 4) +#define APCH_STATE_CSTATE_SHIFT 4 +#define APCH_STATE_CSTATE_DISABLED 0x00 +#define APCH_STATE_CSTATE_EOC 0x01 +#define APCH_STATE_CSTATE_FAST 0x02 +#define APCH_STATE_CSTATE_PRE 0x03 + +struct act8945a_charger { + struct regmap *regmap; + bool battery_temperature; +}; + +static int act8945a_get_charger_state(struct regmap *regmap, int *val) +{ + int ret; + unsigned int status, state; + + ret = regmap_read(regmap, ACT8945A_APCH_STATUS, &status); + if (ret < 0) + return ret; + + ret = regmap_read(regmap, ACT8945A_APCH_STATE, &state); + if (ret < 0) + return ret; + + state &= APCH_STATE_CSTATE; + state >>= APCH_STATE_CSTATE_SHIFT; + + if (state == APCH_STATE_CSTATE_EOC) { + if (status & APCH_STATUS_CHGDAT) + *val = POWER_SUPPLY_STATUS_FULL; + else + *val = POWER_SUPPLY_STATUS_NOT_CHARGING; + } else if ((state == APCH_STATE_CSTATE_FAST) || + (state == APCH_STATE_CSTATE_PRE)) { + *val = POWER_SUPPLY_STATUS_CHARGING; + } else { + *val = POWER_SUPPLY_STATUS_NOT_CHARGING; + } + + return 0; +} + +static int act8945a_get_charge_type(struct regmap *regmap, int *val) +{ + int ret; + unsigned int state; + + ret = regmap_read(regmap, ACT8945A_APCH_STATE, &state); + if (ret < 0) + return ret; + + state &= APCH_STATE_CSTATE; + state >>= APCH_STATE_CSTATE_SHIFT; + + switch (state) { + case APCH_STATE_CSTATE_PRE: + *val = POWER_SUPPLY_CHARGE_TYPE_TRICKLE; + break; + case APCH_STATE_CSTATE_FAST: + *val = POWER_SUPPLY_CHARGE_TYPE_FAST; + break; + case APCH_STATE_CSTATE_EOC: + case APCH_STATE_CSTATE_DISABLED: + default: + *val = POWER_SUPPLY_CHARGE_TYPE_NONE; + } + + return 0; +} + +static int act8945a_get_battery_health(struct act8945a_charger *charger, + struct regmap *regmap, int *val) +{ + int ret; + unsigned int status; + + ret = regmap_read(regmap, ACT8945A_APCH_STATUS, &status); + if (ret < 0) + return ret; + + if (charger->battery_temperature && !(status & APCH_STATUS_TEMPDAT)) + *val = POWER_SUPPLY_HEALTH_OVERHEAT; + else if (!(status & APCH_STATUS_INDAT)) + *val = POWER_SUPPLY_HEALTH_OVERVOLTAGE; + else if (status & APCH_STATUS_TIMRDAT) + *val = POWER_SUPPLY_HEALTH_SAFETY_TIMER_EXPIRE; + else + *val = POWER_SUPPLY_HEALTH_GOOD; + + return 0; +} + +static enum power_supply_property act8945a_charger_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_CHARGE_TYPE, + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_MODEL_NAME, + POWER_SUPPLY_PROP_MANUFACTURER +}; + +static int act8945a_charger_get_property(struct power_supply *psy, + enum power_supply_property prop, + union power_supply_propval *val) +{ + struct act8945a_charger *charger = power_supply_get_drvdata(psy); + struct regmap *regmap = charger->regmap; + int ret = 0; + + switch (prop) { + case POWER_SUPPLY_PROP_STATUS: + ret = act8945a_get_charger_state(regmap, &val->intval); + break; + case POWER_SUPPLY_PROP_CHARGE_TYPE: + ret = act8945a_get_charge_type(regmap, &val->intval); + break; + case POWER_SUPPLY_PROP_TECHNOLOGY: + val->intval = POWER_SUPPLY_TECHNOLOGY_LION; + break; + case POWER_SUPPLY_PROP_HEALTH: + ret = act8945a_get_battery_health(charger, + regmap, &val->intval); + break; + case POWER_SUPPLY_PROP_MODEL_NAME: + val->strval = act8945a_charger_model; + break; + case POWER_SUPPLY_PROP_MANUFACTURER: + val->strval = act8945a_charger_manufacturer; + break; + default: + return -EINVAL; + } + + return ret; +} + +static const struct power_supply_desc act8945a_charger_desc = { + .name = "act8945a-charger", + .type = POWER_SUPPLY_TYPE_BATTERY, + .get_property = act8945a_charger_get_property, + .properties = act8945a_charger_props, + .num_properties = ARRAY_SIZE(act8945a_charger_props), +}; + +#define DEFAULT_TOTAL_TIME_OUT 3 +#define DEFAULT_PRE_TIME_OUT 40 +#define DEFAULT_INPUT_OVP_THRESHOLD 6600 + +static int act8945a_charger_config(struct device *dev, + struct act8945a_charger *charger) +{ + struct device_node *np = dev->of_node; + enum of_gpio_flags flags; + struct regmap *regmap = charger->regmap; + + u32 total_time_out; + u32 pre_time_out; + u32 input_voltage_threshold; + int chglev_pin; + + unsigned int value = 0; + + if (!np) { + dev_err(dev, "no charger of node\n"); + return -EINVAL; + } + + charger->battery_temperature = of_property_read_bool(np, + "active-semi,check-battery-temperature"); + + chglev_pin = of_get_named_gpio_flags(np, + "active-semi,chglev-gpios", 0, &flags); + + if (gpio_is_valid(chglev_pin)) { + gpio_set_value(chglev_pin, + ((flags == OF_GPIO_ACTIVE_LOW) ? 0 : 1)); + } + + if (of_property_read_u32(np, + "active-semi,input-voltage-threshold-microvolt", + &input_voltage_threshold)) + input_voltage_threshold = DEFAULT_INPUT_OVP_THRESHOLD; + + if (of_property_read_u32(np, + "active-semi,precondition-timeout", + &pre_time_out)) + pre_time_out = DEFAULT_PRE_TIME_OUT; + + if (of_property_read_u32(np, "active-semi,total-timeout", + &total_time_out)) + total_time_out = DEFAULT_TOTAL_TIME_OUT; + + switch (input_voltage_threshold) { + case 8000: + value |= APCH_CFG_OVPSET_8V; + break; + case 7500: + value |= APCH_CFG_OVPSET_7V5; + break; + case 7000: + value |= APCH_CFG_OVPSET_7V; + break; + case 6600: + default: + value |= APCH_CFG_OVPSET_6V6; + break; + } + + switch (pre_time_out) { + case 60: + value |= APCH_CFG_PRETIMO_60_MIN; + break; + case 80: + value |= APCH_CFG_PRETIMO_80_MIN; + break; + case 0: + value |= APCH_CFG_PRETIMO_DISABLED; + break; + case 40: + default: + value |= APCH_CFG_PRETIMO_40_MIN; + break; + } + + switch (total_time_out) { + case 4: + value |= APCH_CFG_TOTTIMO_4_HOUR; + break; + case 5: + value |= APCH_CFG_TOTTIMO_5_HOUR; + break; + case 0: + value |= APCH_CFG_TOTTIMO_DISABLED; + break; + case 3: + default: + value |= APCH_CFG_TOTTIMO_3_HOUR; + break; + } + + return regmap_write(regmap, ACT8945A_APCH_CFG, value); +} + +static int act8945a_charger_probe(struct platform_device *pdev) +{ + struct act8945a_charger *charger; + struct power_supply *psy; + struct power_supply_config psy_cfg = {}; + int ret; + + charger = devm_kzalloc(&pdev->dev, sizeof(*charger), GFP_KERNEL); + if (!charger) + return -ENOMEM; + + charger->regmap = dev_get_regmap(pdev->dev.parent, NULL); + if (!charger->regmap) { + dev_err(&pdev->dev, "Parent did not provide regmap\n"); + return -EINVAL; + } + + ret = act8945a_charger_config(pdev->dev.parent, charger); + if (ret) + return ret; + + psy_cfg.of_node = pdev->dev.parent->of_node; + psy_cfg.drv_data = charger; + + psy = devm_power_supply_register(&pdev->dev, + &act8945a_charger_desc, + &psy_cfg); + if (IS_ERR(psy)) { + dev_err(&pdev->dev, "failed to register power supply\n"); + return PTR_ERR(psy); + } + + return 0; +} + +static struct platform_driver act8945a_charger_driver = { + .driver = { + .name = "act8945a-charger", + }, + .probe = act8945a_charger_probe, +}; +module_platform_driver(act8945a_charger_driver); + +MODULE_DESCRIPTION("Active-semi ACT8945A ActivePath charger driver"); +MODULE_AUTHOR("Wenyou Yang "); +MODULE_LICENSE("GPL"); diff --git a/drivers/power/supply/apm_power.c b/drivers/power/supply/apm_power.c new file mode 100644 index 000000000000..9d1a7fbcaed4 --- /dev/null +++ b/drivers/power/supply/apm_power.c @@ -0,0 +1,376 @@ +/* + * Copyright © 2007 Anton Vorontsov + * Copyright © 2007 Eugeny Boger + * + * Author: Eugeny Boger + * + * Use consistent with the GNU GPL is permitted, + * provided that this copyright notice is + * preserved in its entirety in all copies and derived works. + */ + +#include +#include +#include +#include + + +#define PSY_PROP(psy, prop, val) (power_supply_get_property(psy, \ + POWER_SUPPLY_PROP_##prop, val)) + +#define _MPSY_PROP(prop, val) (power_supply_get_property(main_battery, \ + prop, val)) + +#define MPSY_PROP(prop, val) _MPSY_PROP(POWER_SUPPLY_PROP_##prop, val) + +static DEFINE_MUTEX(apm_mutex); +static struct power_supply *main_battery; + +enum apm_source { + SOURCE_ENERGY, + SOURCE_CHARGE, + SOURCE_VOLTAGE, +}; + +struct find_bat_param { + struct power_supply *main; + struct power_supply *bat; + struct power_supply *max_charge_bat; + struct power_supply *max_energy_bat; + union power_supply_propval full; + int max_charge; + int max_energy; +}; + +static int __find_main_battery(struct device *dev, void *data) +{ + struct find_bat_param *bp = (struct find_bat_param *)data; + + bp->bat = dev_get_drvdata(dev); + + if (bp->bat->desc->use_for_apm) { + /* nice, we explicitly asked to report this battery. */ + bp->main = bp->bat; + return 1; + } + + if (!PSY_PROP(bp->bat, CHARGE_FULL_DESIGN, &bp->full) || + !PSY_PROP(bp->bat, CHARGE_FULL, &bp->full)) { + if (bp->full.intval > bp->max_charge) { + bp->max_charge_bat = bp->bat; + bp->max_charge = bp->full.intval; + } + } else if (!PSY_PROP(bp->bat, ENERGY_FULL_DESIGN, &bp->full) || + !PSY_PROP(bp->bat, ENERGY_FULL, &bp->full)) { + if (bp->full.intval > bp->max_energy) { + bp->max_energy_bat = bp->bat; + bp->max_energy = bp->full.intval; + } + } + return 0; +} + +static void find_main_battery(void) +{ + struct find_bat_param bp; + int error; + + memset(&bp, 0, sizeof(struct find_bat_param)); + main_battery = NULL; + bp.main = main_battery; + + error = class_for_each_device(power_supply_class, NULL, &bp, + __find_main_battery); + if (error) { + main_battery = bp.main; + return; + } + + if ((bp.max_energy_bat && bp.max_charge_bat) && + (bp.max_energy_bat != bp.max_charge_bat)) { + /* try guess battery with more capacity */ + if (!PSY_PROP(bp.max_charge_bat, VOLTAGE_MAX_DESIGN, + &bp.full)) { + if (bp.max_energy > bp.max_charge * bp.full.intval) + main_battery = bp.max_energy_bat; + else + main_battery = bp.max_charge_bat; + } else if (!PSY_PROP(bp.max_energy_bat, VOLTAGE_MAX_DESIGN, + &bp.full)) { + if (bp.max_charge > bp.max_energy / bp.full.intval) + main_battery = bp.max_charge_bat; + else + main_battery = bp.max_energy_bat; + } else { + /* give up, choice any */ + main_battery = bp.max_energy_bat; + } + } else if (bp.max_charge_bat) { + main_battery = bp.max_charge_bat; + } else if (bp.max_energy_bat) { + main_battery = bp.max_energy_bat; + } else { + /* give up, try the last if any */ + main_battery = bp.bat; + } +} + +static int do_calculate_time(int status, enum apm_source source) +{ + union power_supply_propval full; + union power_supply_propval empty; + union power_supply_propval cur; + union power_supply_propval I; + enum power_supply_property full_prop; + enum power_supply_property full_design_prop; + enum power_supply_property empty_prop; + enum power_supply_property empty_design_prop; + enum power_supply_property cur_avg_prop; + enum power_supply_property cur_now_prop; + + if (MPSY_PROP(CURRENT_AVG, &I)) { + /* if battery can't report average value, use momentary */ + if (MPSY_PROP(CURRENT_NOW, &I)) + return -1; + } + + if (!I.intval) + return 0; + + switch (source) { + case SOURCE_CHARGE: + full_prop = POWER_SUPPLY_PROP_CHARGE_FULL; + full_design_prop = POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN; + empty_prop = POWER_SUPPLY_PROP_CHARGE_EMPTY; + empty_design_prop = POWER_SUPPLY_PROP_CHARGE_EMPTY; + cur_avg_prop = POWER_SUPPLY_PROP_CHARGE_AVG; + cur_now_prop = POWER_SUPPLY_PROP_CHARGE_NOW; + break; + case SOURCE_ENERGY: + full_prop = POWER_SUPPLY_PROP_ENERGY_FULL; + full_design_prop = POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN; + empty_prop = POWER_SUPPLY_PROP_ENERGY_EMPTY; + empty_design_prop = POWER_SUPPLY_PROP_CHARGE_EMPTY; + cur_avg_prop = POWER_SUPPLY_PROP_ENERGY_AVG; + cur_now_prop = POWER_SUPPLY_PROP_ENERGY_NOW; + break; + case SOURCE_VOLTAGE: + full_prop = POWER_SUPPLY_PROP_VOLTAGE_MAX; + full_design_prop = POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN; + empty_prop = POWER_SUPPLY_PROP_VOLTAGE_MIN; + empty_design_prop = POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN; + cur_avg_prop = POWER_SUPPLY_PROP_VOLTAGE_AVG; + cur_now_prop = POWER_SUPPLY_PROP_VOLTAGE_NOW; + break; + default: + printk(KERN_ERR "Unsupported source: %d\n", source); + return -1; + } + + if (_MPSY_PROP(full_prop, &full)) { + /* if battery can't report this property, use design value */ + if (_MPSY_PROP(full_design_prop, &full)) + return -1; + } + + if (_MPSY_PROP(empty_prop, &empty)) { + /* if battery can't report this property, use design value */ + if (_MPSY_PROP(empty_design_prop, &empty)) + empty.intval = 0; + } + + if (_MPSY_PROP(cur_avg_prop, &cur)) { + /* if battery can't report average value, use momentary */ + if (_MPSY_PROP(cur_now_prop, &cur)) + return -1; + } + + if (status == POWER_SUPPLY_STATUS_CHARGING) + return ((cur.intval - full.intval) * 60L) / I.intval; + else + return -((cur.intval - empty.intval) * 60L) / I.intval; +} + +static int calculate_time(int status) +{ + int time; + + time = do_calculate_time(status, SOURCE_ENERGY); + if (time != -1) + return time; + + time = do_calculate_time(status, SOURCE_CHARGE); + if (time != -1) + return time; + + time = do_calculate_time(status, SOURCE_VOLTAGE); + if (time != -1) + return time; + + return -1; +} + +static int calculate_capacity(enum apm_source source) +{ + enum power_supply_property full_prop, empty_prop; + enum power_supply_property full_design_prop, empty_design_prop; + enum power_supply_property now_prop, avg_prop; + union power_supply_propval empty, full, cur; + int ret; + + switch (source) { + case SOURCE_CHARGE: + full_prop = POWER_SUPPLY_PROP_CHARGE_FULL; + empty_prop = POWER_SUPPLY_PROP_CHARGE_EMPTY; + full_design_prop = POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN; + empty_design_prop = POWER_SUPPLY_PROP_CHARGE_EMPTY_DESIGN; + now_prop = POWER_SUPPLY_PROP_CHARGE_NOW; + avg_prop = POWER_SUPPLY_PROP_CHARGE_AVG; + break; + case SOURCE_ENERGY: + full_prop = POWER_SUPPLY_PROP_ENERGY_FULL; + empty_prop = POWER_SUPPLY_PROP_ENERGY_EMPTY; + full_design_prop = POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN; + empty_design_prop = POWER_SUPPLY_PROP_ENERGY_EMPTY_DESIGN; + now_prop = POWER_SUPPLY_PROP_ENERGY_NOW; + avg_prop = POWER_SUPPLY_PROP_ENERGY_AVG; + break; + case SOURCE_VOLTAGE: + full_prop = POWER_SUPPLY_PROP_VOLTAGE_MAX; + empty_prop = POWER_SUPPLY_PROP_VOLTAGE_MIN; + full_design_prop = POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN; + empty_design_prop = POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN; + now_prop = POWER_SUPPLY_PROP_VOLTAGE_NOW; + avg_prop = POWER_SUPPLY_PROP_VOLTAGE_AVG; + break; + default: + printk(KERN_ERR "Unsupported source: %d\n", source); + return -1; + } + + if (_MPSY_PROP(full_prop, &full)) { + /* if battery can't report this property, use design value */ + if (_MPSY_PROP(full_design_prop, &full)) + return -1; + } + + if (_MPSY_PROP(avg_prop, &cur)) { + /* if battery can't report average value, use momentary */ + if (_MPSY_PROP(now_prop, &cur)) + return -1; + } + + if (_MPSY_PROP(empty_prop, &empty)) { + /* if battery can't report this property, use design value */ + if (_MPSY_PROP(empty_design_prop, &empty)) + empty.intval = 0; + } + + if (full.intval - empty.intval) + ret = ((cur.intval - empty.intval) * 100L) / + (full.intval - empty.intval); + else + return -1; + + if (ret > 100) + return 100; + else if (ret < 0) + return 0; + + return ret; +} + +static void apm_battery_apm_get_power_status(struct apm_power_info *info) +{ + union power_supply_propval status; + union power_supply_propval capacity, time_to_full, time_to_empty; + + mutex_lock(&apm_mutex); + find_main_battery(); + if (!main_battery) { + mutex_unlock(&apm_mutex); + return; + } + + /* status */ + + if (MPSY_PROP(STATUS, &status)) + status.intval = POWER_SUPPLY_STATUS_UNKNOWN; + + /* ac line status */ + + if ((status.intval == POWER_SUPPLY_STATUS_CHARGING) || + (status.intval == POWER_SUPPLY_STATUS_NOT_CHARGING) || + (status.intval == POWER_SUPPLY_STATUS_FULL)) + info->ac_line_status = APM_AC_ONLINE; + else + info->ac_line_status = APM_AC_OFFLINE; + + /* battery life (i.e. capacity, in percents) */ + + if (MPSY_PROP(CAPACITY, &capacity) == 0) { + info->battery_life = capacity.intval; + } else { + /* try calculate using energy */ + info->battery_life = calculate_capacity(SOURCE_ENERGY); + /* if failed try calculate using charge instead */ + if (info->battery_life == -1) + info->battery_life = calculate_capacity(SOURCE_CHARGE); + if (info->battery_life == -1) + info->battery_life = calculate_capacity(SOURCE_VOLTAGE); + } + + /* charging status */ + + if (status.intval == POWER_SUPPLY_STATUS_CHARGING) { + info->battery_status = APM_BATTERY_STATUS_CHARGING; + } else { + if (info->battery_life > 50) + info->battery_status = APM_BATTERY_STATUS_HIGH; + else if (info->battery_life > 5) + info->battery_status = APM_BATTERY_STATUS_LOW; + else + info->battery_status = APM_BATTERY_STATUS_CRITICAL; + } + info->battery_flag = info->battery_status; + + /* time */ + + info->units = APM_UNITS_MINS; + + if (status.intval == POWER_SUPPLY_STATUS_CHARGING) { + if (!MPSY_PROP(TIME_TO_FULL_AVG, &time_to_full) || + !MPSY_PROP(TIME_TO_FULL_NOW, &time_to_full)) + info->time = time_to_full.intval / 60; + else + info->time = calculate_time(status.intval); + } else { + if (!MPSY_PROP(TIME_TO_EMPTY_AVG, &time_to_empty) || + !MPSY_PROP(TIME_TO_EMPTY_NOW, &time_to_empty)) + info->time = time_to_empty.intval / 60; + else + info->time = calculate_time(status.intval); + } + + mutex_unlock(&apm_mutex); +} + +static int __init apm_battery_init(void) +{ + printk(KERN_INFO "APM Battery Driver\n"); + + apm_get_power_status = apm_battery_apm_get_power_status; + return 0; +} + +static void __exit apm_battery_exit(void) +{ + apm_get_power_status = NULL; +} + +module_init(apm_battery_init); +module_exit(apm_battery_exit); + +MODULE_AUTHOR("Eugeny Boger "); +MODULE_DESCRIPTION("APM emulation driver for battery monitoring class"); +MODULE_LICENSE("GPL"); diff --git a/drivers/power/supply/axp20x_usb_power.c b/drivers/power/supply/axp20x_usb_power.c new file mode 100644 index 000000000000..6af6feb7058d --- /dev/null +++ b/drivers/power/supply/axp20x_usb_power.c @@ -0,0 +1,294 @@ +/* + * AXP20x PMIC USB power supply status driver + * + * Copyright (C) 2015 Hans de Goede + * Copyright (C) 2014 Bruno Prémont + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 device_node *np; + 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: + if (of_device_is_compatible(power->np, + "x-powers,axp202-usb-power-supply")) { + val->intval = 100000; + } else { + val->intval = -1; /* No 100mA limit */ + } + 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; + } + + val->intval = POWER_SUPPLY_HEALTH_GOOD; + + if (of_device_is_compatible(power->np, + "x-powers,axp202-usb-power-supply")) { + 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; + 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 enum power_supply_property axp22x_usb_power_properties[] = { + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_VOLTAGE_MIN, + POWER_SUPPLY_PROP_CURRENT_MAX, +}; + +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 const struct power_supply_desc axp22x_usb_power_desc = { + .name = "axp20x-usb", + .type = POWER_SUPPLY_TYPE_USB, + .properties = axp22x_usb_power_properties, + .num_properties = ARRAY_SIZE(axp22x_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 axp20x_irq_names[] = { "VBUS_PLUGIN", + "VBUS_REMOVAL", "VBUS_VALID", "VBUS_NOT_VALID", NULL }; + static const char * const axp22x_irq_names[] = { + "VBUS_PLUGIN", "VBUS_REMOVAL", NULL }; + static const char * const *irq_names; + const struct power_supply_desc *usb_power_desc; + 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->np = pdev->dev.of_node; + power->regmap = axp20x->regmap; + + if (of_device_is_compatible(power->np, + "x-powers,axp202-usb-power-supply")) { + /* 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; + + 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")) { + usb_power_desc = &axp22x_usb_power_desc; + irq_names = axp22x_irq_names; + } else { + dev_err(&pdev->dev, "Unsupported AXP variant: %ld\n", + axp20x->variant); + return -EINVAL; + } + + psy_cfg.of_node = pdev->dev.of_node; + psy_cfg.drv_data = power; + + power->supply = devm_power_supply_register(&pdev->dev, 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; 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_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" }, + { .compatible = "x-powers,axp221-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 "); +MODULE_DESCRIPTION("AXP20x PMIC USB power supply status driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/power/supply/axp288_charger.c b/drivers/power/supply/axp288_charger.c new file mode 100644 index 000000000000..4030eeb7cf65 --- /dev/null +++ b/drivers/power/supply/axp288_charger.c @@ -0,0 +1,970 @@ +/* + * axp288_charger.c - X-power AXP288 PMIC Charger driver + * + * Copyright (C) 2014 Intel Corporation + * Author: Ramakrishna Pallala + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define PS_STAT_VBUS_TRIGGER (1 << 0) +#define PS_STAT_BAT_CHRG_DIR (1 << 2) +#define PS_STAT_VBAT_ABOVE_VHOLD (1 << 3) +#define PS_STAT_VBUS_VALID (1 << 4) +#define PS_STAT_VBUS_PRESENT (1 << 5) + +#define CHRG_STAT_BAT_SAFE_MODE (1 << 3) +#define CHRG_STAT_BAT_VALID (1 << 4) +#define CHRG_STAT_BAT_PRESENT (1 << 5) +#define CHRG_STAT_CHARGING (1 << 6) +#define CHRG_STAT_PMIC_OTP (1 << 7) + +#define VBUS_ISPOUT_CUR_LIM_MASK 0x03 +#define VBUS_ISPOUT_CUR_LIM_BIT_POS 0 +#define VBUS_ISPOUT_CUR_LIM_900MA 0x0 /* 900mA */ +#define VBUS_ISPOUT_CUR_LIM_1500MA 0x1 /* 1500mA */ +#define VBUS_ISPOUT_CUR_LIM_2000MA 0x2 /* 2000mA */ +#define VBUS_ISPOUT_CUR_NO_LIM 0x3 /* 2500mA */ +#define VBUS_ISPOUT_VHOLD_SET_MASK 0x31 +#define VBUS_ISPOUT_VHOLD_SET_BIT_POS 0x3 +#define VBUS_ISPOUT_VHOLD_SET_OFFSET 4000 /* 4000mV */ +#define VBUS_ISPOUT_VHOLD_SET_LSB_RES 100 /* 100mV */ +#define VBUS_ISPOUT_VHOLD_SET_4300MV 0x3 /* 4300mV */ +#define VBUS_ISPOUT_VBUS_PATH_DIS (1 << 7) + +#define CHRG_CCCV_CC_MASK 0xf /* 4 bits */ +#define CHRG_CCCV_CC_BIT_POS 0 +#define CHRG_CCCV_CC_OFFSET 200 /* 200mA */ +#define CHRG_CCCV_CC_LSB_RES 200 /* 200mA */ +#define CHRG_CCCV_ITERM_20P (1 << 4) /* 20% of CC */ +#define CHRG_CCCV_CV_MASK 0x60 /* 2 bits */ +#define CHRG_CCCV_CV_BIT_POS 5 +#define CHRG_CCCV_CV_4100MV 0x0 /* 4.10V */ +#define CHRG_CCCV_CV_4150MV 0x1 /* 4.15V */ +#define CHRG_CCCV_CV_4200MV 0x2 /* 4.20V */ +#define CHRG_CCCV_CV_4350MV 0x3 /* 4.35V */ +#define CHRG_CCCV_CHG_EN (1 << 7) + +#define CNTL2_CC_TIMEOUT_MASK 0x3 /* 2 bits */ +#define CNTL2_CC_TIMEOUT_OFFSET 6 /* 6 Hrs */ +#define CNTL2_CC_TIMEOUT_LSB_RES 2 /* 2 Hrs */ +#define CNTL2_CC_TIMEOUT_12HRS 0x3 /* 12 Hrs */ +#define CNTL2_CHGLED_TYPEB (1 << 4) +#define CNTL2_CHG_OUT_TURNON (1 << 5) +#define CNTL2_PC_TIMEOUT_MASK 0xC0 +#define CNTL2_PC_TIMEOUT_OFFSET 40 /* 40 mins */ +#define CNTL2_PC_TIMEOUT_LSB_RES 10 /* 10 mins */ +#define CNTL2_PC_TIMEOUT_70MINS 0x3 + +#define CHRG_ILIM_TEMP_LOOP_EN (1 << 3) +#define CHRG_VBUS_ILIM_MASK 0xf0 +#define CHRG_VBUS_ILIM_BIT_POS 4 +#define CHRG_VBUS_ILIM_100MA 0x0 /* 100mA */ +#define CHRG_VBUS_ILIM_500MA 0x1 /* 500mA */ +#define CHRG_VBUS_ILIM_900MA 0x2 /* 900mA */ +#define CHRG_VBUS_ILIM_1500MA 0x3 /* 1500mA */ +#define CHRG_VBUS_ILIM_2000MA 0x4 /* 2000mA */ +#define CHRG_VBUS_ILIM_2500MA 0x5 /* 2500mA */ +#define CHRG_VBUS_ILIM_3000MA 0x6 /* 3000mA */ + +#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 */ +#define CV_4150MV 4150 /* 4150mV */ +#define CV_4200MV 4200 /* 4200mV */ +#define CV_4350MV 4350 /* 4350mV */ + +#define CC_200MA 200 /* 200mA */ +#define CC_600MA 600 /* 600mA */ +#define CC_800MA 800 /* 800mA */ +#define CC_1000MA 1000 /* 1000mA */ +#define CC_1600MA 1600 /* 1600mA */ +#define CC_2000MA 2000 /* 2000mA */ + +#define ILIM_100MA 100 /* 100mA */ +#define ILIM_500MA 500 /* 500mA */ +#define ILIM_900MA 900 /* 900mA */ +#define ILIM_1500MA 1500 /* 1500mA */ +#define ILIM_2000MA 2000 /* 2000mA */ +#define ILIM_2500MA 2500 /* 2500mA */ +#define ILIM_3000MA 3000 /* 3000mA */ + +#define AXP288_EXTCON_DEV_NAME "axp288_extcon" + +enum { + VBUS_OV_IRQ = 0, + CHARGE_DONE_IRQ, + CHARGE_CHARGING_IRQ, + BAT_SAFE_QUIT_IRQ, + BAT_SAFE_ENTER_IRQ, + QCBTU_IRQ, + CBTU_IRQ, + QCBTO_IRQ, + CBTO_IRQ, + CHRG_INTR_END, +}; + +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]; + struct power_supply *psy_usb; + struct mutex lock; + + /* OTG/Host mode */ + struct { + struct work_struct work; + struct extcon_dev *cable; + struct notifier_block id_nb; + bool id_short; + } otg; + + /* SDP/CDP/DCP USB charging cable notifications */ + struct { + struct extcon_dev *edev; + bool connected; + enum power_supply_type chg_type; + struct notifier_block nb; + 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; +}; + +static inline int axp288_charger_set_cc(struct axp288_chrg_info *info, int cc) +{ + u8 reg_val; + int ret; + + if (cc < CHRG_CCCV_CC_OFFSET) + cc = CHRG_CCCV_CC_OFFSET; + else if (cc > info->max_cc) + cc = info->max_cc; + + reg_val = (cc - CHRG_CCCV_CC_OFFSET) / CHRG_CCCV_CC_LSB_RES; + cc = (reg_val * CHRG_CCCV_CC_LSB_RES) + CHRG_CCCV_CC_OFFSET; + reg_val = reg_val << CHRG_CCCV_CC_BIT_POS; + + ret = regmap_update_bits(info->regmap, + AXP20X_CHRG_CTRL1, + CHRG_CCCV_CC_MASK, reg_val); + if (ret >= 0) + info->cc = cc; + + return ret; +} + +static inline int axp288_charger_set_cv(struct axp288_chrg_info *info, int cv) +{ + u8 reg_val; + int ret; + + if (cv <= CV_4100MV) { + reg_val = CHRG_CCCV_CV_4100MV; + cv = CV_4100MV; + } else if (cv <= CV_4150MV) { + reg_val = CHRG_CCCV_CV_4150MV; + cv = CV_4150MV; + } else if (cv <= CV_4200MV) { + reg_val = CHRG_CCCV_CV_4200MV; + cv = CV_4200MV; + } else { + reg_val = CHRG_CCCV_CV_4350MV; + cv = CV_4350MV; + } + + reg_val = reg_val << CHRG_CCCV_CV_BIT_POS; + + ret = regmap_update_bits(info->regmap, + AXP20X_CHRG_CTRL1, + CHRG_CCCV_CV_MASK, reg_val); + + if (ret >= 0) + info->cv = cv; + + return ret; +} + +static inline int axp288_charger_set_vbus_inlmt(struct axp288_chrg_info *info, + int inlmt) +{ + int ret; + unsigned int val; + u8 reg_val; + + /* Read in limit register */ + ret = regmap_read(info->regmap, AXP20X_CHRG_BAK_CTRL, &val); + if (ret < 0) + goto set_inlmt_fail; + + if (inlmt <= ILIM_100MA) { + reg_val = CHRG_VBUS_ILIM_100MA; + inlmt = ILIM_100MA; + } else if (inlmt <= ILIM_500MA) { + reg_val = CHRG_VBUS_ILIM_500MA; + inlmt = ILIM_500MA; + } else if (inlmt <= ILIM_900MA) { + reg_val = CHRG_VBUS_ILIM_900MA; + inlmt = ILIM_900MA; + } else if (inlmt <= ILIM_1500MA) { + reg_val = CHRG_VBUS_ILIM_1500MA; + inlmt = ILIM_1500MA; + } else if (inlmt <= ILIM_2000MA) { + reg_val = CHRG_VBUS_ILIM_2000MA; + inlmt = ILIM_2000MA; + } else if (inlmt <= ILIM_2500MA) { + reg_val = CHRG_VBUS_ILIM_2500MA; + inlmt = ILIM_2500MA; + } else { + reg_val = CHRG_VBUS_ILIM_3000MA; + inlmt = ILIM_3000MA; + } + + reg_val = (val & ~CHRG_VBUS_ILIM_MASK) + | (reg_val << CHRG_VBUS_ILIM_BIT_POS); + ret = regmap_write(info->regmap, AXP20X_CHRG_BAK_CTRL, reg_val); + if (ret >= 0) + info->inlmt = inlmt; + else + dev_err(&info->pdev->dev, "charger BAK control %d\n", ret); + + +set_inlmt_fail: + return ret; +} + +static int axp288_charger_vbus_path_select(struct axp288_chrg_info *info, + bool enable) +{ + int ret; + + if (enable) + ret = regmap_update_bits(info->regmap, AXP20X_VBUS_IPSOUT_MGMT, + VBUS_ISPOUT_VBUS_PATH_DIS, 0); + else + ret = regmap_update_bits(info->regmap, AXP20X_VBUS_IPSOUT_MGMT, + VBUS_ISPOUT_VBUS_PATH_DIS, VBUS_ISPOUT_VBUS_PATH_DIS); + + if (ret < 0) + dev_err(&info->pdev->dev, "axp288 vbus path select %d\n", ret); + + + return ret; +} + +static int axp288_charger_enable_charger(struct axp288_chrg_info *info, + bool enable) +{ + int ret; + + if (enable) + ret = regmap_update_bits(info->regmap, AXP20X_CHRG_CTRL1, + CHRG_CCCV_CHG_EN, CHRG_CCCV_CHG_EN); + else + ret = regmap_update_bits(info->regmap, AXP20X_CHRG_CTRL1, + CHRG_CCCV_CHG_EN, 0); + if (ret < 0) + dev_err(&info->pdev->dev, "axp288 enable charger %d\n", ret); + else + info->is_charger_enabled = enable; + + return ret; +} + +static int axp288_charger_is_present(struct axp288_chrg_info *info) +{ + int ret, present = 0; + unsigned int val; + + ret = regmap_read(info->regmap, AXP20X_PWR_INPUT_STATUS, &val); + if (ret < 0) + return ret; + + if (val & PS_STAT_VBUS_PRESENT) + present = 1; + return present; +} + +static int axp288_charger_is_online(struct axp288_chrg_info *info) +{ + int ret, online = 0; + unsigned int val; + + ret = regmap_read(info->regmap, AXP20X_PWR_INPUT_STATUS, &val); + if (ret < 0) + return ret; + + if (val & PS_STAT_VBUS_VALID) + online = 1; + return online; +} + +static int axp288_get_charger_health(struct axp288_chrg_info *info) +{ + int ret, pwr_stat, chrg_stat; + int health = POWER_SUPPLY_HEALTH_UNKNOWN; + unsigned int val; + + ret = regmap_read(info->regmap, AXP20X_PWR_INPUT_STATUS, &val); + if ((ret < 0) || !(val & PS_STAT_VBUS_PRESENT)) + goto health_read_fail; + else + pwr_stat = val; + + ret = regmap_read(info->regmap, AXP20X_PWR_OP_MODE, &val); + if (ret < 0) + goto health_read_fail; + else + chrg_stat = val; + + if (!(pwr_stat & PS_STAT_VBUS_VALID)) + health = POWER_SUPPLY_HEALTH_DEAD; + else if (chrg_stat & CHRG_STAT_PMIC_OTP) + health = POWER_SUPPLY_HEALTH_OVERHEAT; + else if (chrg_stat & CHRG_STAT_BAT_SAFE_MODE) + health = POWER_SUPPLY_HEALTH_SAFETY_TIMER_EXPIRE; + else + health = POWER_SUPPLY_HEALTH_GOOD; + +health_read_fail: + return health; +} + +static int axp288_charger_usb_set_property(struct power_supply *psy, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct axp288_chrg_info *info = power_supply_get_drvdata(psy); + int ret = 0; + int scaled_val; + + mutex_lock(&info->lock); + + switch (psp) { + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: + scaled_val = min(val->intval, info->max_cc); + scaled_val = DIV_ROUND_CLOSEST(scaled_val, 1000); + ret = axp288_charger_set_cc(info, scaled_val); + if (ret < 0) + dev_warn(&info->pdev->dev, "set charge current failed\n"); + break; + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: + scaled_val = min(val->intval, info->max_cv); + scaled_val = DIV_ROUND_CLOSEST(scaled_val, 1000); + ret = axp288_charger_set_cv(info, scaled_val); + if (ret < 0) + dev_warn(&info->pdev->dev, "set charge voltage failed\n"); + break; + default: + ret = -EINVAL; + } + + mutex_unlock(&info->lock); + return ret; +} + +static int axp288_charger_usb_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct axp288_chrg_info *info = power_supply_get_drvdata(psy); + int ret = 0; + + mutex_lock(&info->lock); + + switch (psp) { + case POWER_SUPPLY_PROP_PRESENT: + /* Check for OTG case first */ + if (info->otg.id_short) { + val->intval = 0; + break; + } + ret = axp288_charger_is_present(info); + if (ret < 0) + goto psy_get_prop_fail; + info->present = ret; + val->intval = info->present; + break; + case POWER_SUPPLY_PROP_ONLINE: + /* Check for OTG case first */ + if (info->otg.id_short) { + val->intval = 0; + break; + } + ret = axp288_charger_is_online(info); + if (ret < 0) + goto psy_get_prop_fail; + info->online = ret; + val->intval = info->online; + break; + case POWER_SUPPLY_PROP_HEALTH: + val->intval = axp288_get_charger_health(info); + break; + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: + val->intval = info->cc * 1000; + break; + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX: + val->intval = info->max_cc * 1000; + break; + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: + val->intval = info->cv * 1000; + break; + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX: + val->intval = info->max_cv * 1000; + break; + case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT: + val->intval = info->inlmt * 1000; + break; + default: + ret = -EINVAL; + goto psy_get_prop_fail; + } + +psy_get_prop_fail: + mutex_unlock(&info->lock); + return ret; +} + +static int axp288_charger_property_is_writeable(struct power_supply *psy, + enum power_supply_property psp) +{ + int ret; + + switch (psp) { + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: + ret = 1; + break; + default: + ret = 0; + } + + return ret; +} + +static enum power_supply_property axp288_usb_props[] = { + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_TYPE, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX, + POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT, +}; + +static const struct power_supply_desc axp288_charger_desc = { + .name = "axp288_charger", + .type = POWER_SUPPLY_TYPE_USB, + .properties = axp288_usb_props, + .num_properties = ARRAY_SIZE(axp288_usb_props), + .get_property = axp288_charger_usb_get_property, + .set_property = axp288_charger_usb_set_property, + .property_is_writeable = axp288_charger_property_is_writeable, +}; + +static irqreturn_t axp288_charger_irq_thread_handler(int irq, void *dev) +{ + struct axp288_chrg_info *info = dev; + int i; + + for (i = 0; i < CHRG_INTR_END; i++) { + if (info->irq[i] == irq) + break; + } + + if (i >= CHRG_INTR_END) { + dev_warn(&info->pdev->dev, "spurious interrupt!!\n"); + return IRQ_NONE; + } + + switch (i) { + case VBUS_OV_IRQ: + dev_dbg(&info->pdev->dev, "VBUS Over Voltage INTR\n"); + break; + case CHARGE_DONE_IRQ: + dev_dbg(&info->pdev->dev, "Charging Done INTR\n"); + break; + case CHARGE_CHARGING_IRQ: + dev_dbg(&info->pdev->dev, "Start Charging IRQ\n"); + break; + case BAT_SAFE_QUIT_IRQ: + dev_dbg(&info->pdev->dev, + "Quit Safe Mode(restart timer) Charging IRQ\n"); + break; + case BAT_SAFE_ENTER_IRQ: + dev_dbg(&info->pdev->dev, + "Enter Safe Mode(timer expire) Charging IRQ\n"); + break; + case QCBTU_IRQ: + dev_dbg(&info->pdev->dev, + "Quit Battery Under Temperature(CHRG) INTR\n"); + break; + case CBTU_IRQ: + dev_dbg(&info->pdev->dev, + "Hit Battery Under Temperature(CHRG) INTR\n"); + break; + case QCBTO_IRQ: + dev_dbg(&info->pdev->dev, + "Quit Battery Over Temperature(CHRG) INTR\n"); + break; + case CBTO_IRQ: + dev_dbg(&info->pdev->dev, + "Hit Battery Over Temperature(CHRG) INTR\n"); + break; + default: + dev_warn(&info->pdev->dev, "Spurious Interrupt!!!\n"); + goto out; + } + + power_supply_changed(info->psy_usb); +out: + return IRQ_HANDLED; +} + +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; + + /* Determine cable/charger type */ + if (extcon_get_cable_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) { + 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) { + dev_dbg(&info->pdev->dev, "USB DCP charger is connected"); + info->cable.connected = true; + info->cable.chg_type = POWER_SUPPLY_TYPE_USB_DCP; + } else { + if (old_connected) + dev_dbg(&info->pdev->dev, "USB charger disconnected"); + info->cable.connected = false; + info->cable.chg_type = POWER_SUPPLY_TYPE_USB; + } + + /* Cable status changed */ + if (old_connected != info->cable.connected) + changed = true; + + if (!changed) + 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); + + } else if (!info->is_charger_enabled && info->cable.connected) { + switch (info->cable.chg_type) { + case POWER_SUPPLY_TYPE_USB: + current_limit = ILIM_500MA; + break; + case POWER_SUPPLY_TYPE_USB_CDP: + current_limit = ILIM_1500MA; + break; + case POWER_SUPPLY_TYPE_USB_DCP: + current_limit = ILIM_2000MA; + break; + default: + /* Unknown */ + current_limit = 0; + break; + } + + /* Set vbus current limit first, then enable charger */ + ret = axp288_charger_set_vbus_inlmt(info, current_limit); + if (ret < 0) { + 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); + } + } + + 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, + unsigned long event, void *param) +{ + struct axp288_chrg_info *info = + container_of(nb, struct axp288_chrg_info, cable.nb); + + schedule_work(&info->cable.work); + + return NOTIFY_OK; +} + +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; + + /* Disable VBUS path before enabling the 5V boost */ + ret = axp288_charger_vbus_path_select(info, !info->otg.id_short); + if (ret < 0) + dev_warn(&info->pdev->dev, "vbus path disable failed\n"); +} + +static int axp288_charger_handle_otg_evt(struct notifier_block *nb, + unsigned long event, void *param) +{ + 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) +{ + 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", + AXP20X_V_LTF_CHRG, 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", + AXP20X_V_HTF_CHRG, 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", + 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); + + /* 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", + AXP20X_CHRG_CTRL1, 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", + AXP20X_CC_CTRL, ret); + + /* Init charging current and voltage */ + info->max_cc = info->pdata->max_cc; + info->max_cv = info->pdata->max_cv; + + /* 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 { + /* Determine charge voltage */ + cv = (val & CHRG_CCCV_CV_MASK) >> CHRG_CCCV_CV_BIT_POS; + switch (cv) { + case CHRG_CCCV_CV_4100MV: + info->cv = CV_4100MV; + break; + case CHRG_CCCV_CV_4150MV: + info->cv = CV_4150MV; + break; + case CHRG_CCCV_CV_4200MV: + info->cv = CV_4200MV; + break; + case CHRG_CCCV_CV_4350MV: + info->cv = CV_4350MV; + break; + default: + info->cv = INT_MAX; + break; + } + + /* Determine charge current limit */ + cc = (ret & CHRG_CCCV_CC_MASK) >> CHRG_CCCV_CC_BIT_POS; + 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); + + ret = axp288_charger_set_cv(info, cv); + if (ret < 0) + dev_warn(&info->pdev->dev, + "error(%d) in setting CV\n", ret); + } +} + +static int axp288_charger_probe(struct platform_device *pdev) +{ + int ret, i, pirq; + struct axp288_chrg_info *info; + 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; + + 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.edev = extcon_get_extcon_dev(AXP288_EXTCON_DEV_NAME); + if (info->cable.edev == NULL) { + dev_dbg(&pdev->dev, "%s is not ready, probe deferred\n", + AXP288_EXTCON_DEV_NAME); + 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; + } + + platform_set_drvdata(pdev, info); + mutex_init(&info->lock); + + /* Register with power supply class */ + charger_cfg.drv_data = info; + info->psy_usb = power_supply_register(&pdev->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; + } + + /* 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); + + /* Register charger interrupts */ + for (i = 0; i < CHRG_INTR_END; i++) { + pirq = platform_get_irq(info->pdev, i); + info->irq[i] = regmap_irq_get_virq(info->regmap_irqc, pirq); + 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; + } + ret = devm_request_threaded_irq(&info->pdev->dev, info->irq[i], + NULL, axp288_charger_irq_thread_handler, + IRQF_ONESHOT, info->pdev->name, info); + if (ret) { + dev_err(&pdev->dev, "failed to request interrupt=%d\n", + info->irq[i]); + goto intr_reg_failed; + } + } + + 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 struct platform_driver axp288_charger_driver = { + .probe = axp288_charger_probe, + .remove = axp288_charger_remove, + .driver = { + .name = "axp288_charger", + }, +}; + +module_platform_driver(axp288_charger_driver); + +MODULE_AUTHOR("Ramakrishna Pallala "); +MODULE_DESCRIPTION("X-power AXP288 Charger Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/power/supply/axp288_fuel_gauge.c b/drivers/power/supply/axp288_fuel_gauge.c new file mode 100644 index 000000000000..50c0110d6b58 --- /dev/null +++ b/drivers/power/supply/axp288_fuel_gauge.c @@ -0,0 +1,1155 @@ +/* + * axp288_fuel_gauge.c - Xpower AXP288 PMIC Fuel Gauge Driver + * + * Copyright (C) 2014 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. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define CHRG_STAT_BAT_SAFE_MODE (1 << 3) +#define CHRG_STAT_BAT_VALID (1 << 4) +#define CHRG_STAT_BAT_PRESENT (1 << 5) +#define CHRG_STAT_CHARGING (1 << 6) +#define CHRG_STAT_PMIC_OTP (1 << 7) + +#define CHRG_CCCV_CC_MASK 0xf /* 4 bits */ +#define CHRG_CCCV_CC_BIT_POS 0 +#define CHRG_CCCV_CC_OFFSET 200 /* 200mA */ +#define CHRG_CCCV_CC_LSB_RES 200 /* 200mA */ +#define CHRG_CCCV_ITERM_20P (1 << 4) /* 20% of CC */ +#define CHRG_CCCV_CV_MASK 0x60 /* 2 bits */ +#define CHRG_CCCV_CV_BIT_POS 5 +#define CHRG_CCCV_CV_4100MV 0x0 /* 4.10V */ +#define CHRG_CCCV_CV_4150MV 0x1 /* 4.15V */ +#define CHRG_CCCV_CV_4200MV 0x2 /* 4.20V */ +#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) +#define FG_CNTL_CAP_ADJ_EN (1 << 5) +#define FG_CNTL_CC_EN (1 << 6) +#define FG_CNTL_GAUGE_EN (1 << 7) + +#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) +#define FG_OCV_CAP_VAL_MASK 0x7F +#define FG_CC_CAP_VALID (1 << 7) +#define FG_CC_CAP_VAL_MASK 0x7F + +#define FG_LOW_CAP_THR1_MASK 0xf0 /* 5% tp 20% */ +#define FG_LOW_CAP_THR1_VAL 0xa0 /* 15 perc */ +#define FG_LOW_CAP_THR2_MASK 0x0f /* 0% to 15% */ +#define FG_LOW_CAP_WARN_THR 14 /* 14 perc */ +#define FG_LOW_CAP_CRIT_THR 4 /* 4 perc */ +#define FG_LOW_CAP_SHDN_THR 0 /* 0 perc */ + +#define STATUS_MON_DELAY_JIFFIES (HZ * 60) /*60 sec */ +#define NR_RETRY_CNT 3 +#define DEV_NAME "axp288_fuel_gauge" + +/* 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) +#define PROP_VOLT(a) ((a) * 1000) +#define PROP_CURR(a) ((a) * 1000) + +#define AXP288_FG_INTR_NUM 6 +enum { + QWBTU_IRQ = 0, + WBTU_IRQ, + QWBTO_IRQ, + WBTO_IRQ, + WL2_IRQ, + WL1_IRQ, +}; + +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; + struct delayed_work status_monitor; + struct dentry *debug_file; +}; + +static enum power_supply_property fuel_gauge_props[] = { + POWER_SUPPLY_PROP_STATUS, + 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) +{ + int ret, i; + unsigned int val; + + for (i = 0; i < NR_RETRY_CNT; i++) { + ret = regmap_read(info->regmap, reg, &val); + if (ret == -EBUSY) + continue; + else + break; + } + + if (ret < 0) + dev_err(&info->pdev->dev, "axp288 reg read err:%d\n", ret); + + return val; +} + +static int fuel_gauge_reg_writeb(struct axp288_fg_info *info, int reg, u8 val) +{ + int ret; + + ret = regmap_write(info->regmap, reg, (unsigned int)val); + + if (ret < 0) + dev_err(&info->pdev->dev, "axp288 reg write err:%d\n", ret); + + return ret; +} + +static int pmic_read_adc_val(const char *name, int *raw_val, + struct axp288_fg_info *info) +{ + int ret, val = 0; + struct iio_channel *indio_chan; + + indio_chan = iio_channel_get(NULL, name); + if (IS_ERR_OR_NULL(indio_chan)) { + ret = PTR_ERR(indio_chan); + goto exit; + } + ret = iio_read_channel_raw(indio_chan, &val); + if (ret < 0) { + dev_err(&info->pdev->dev, + "IIO channel read error: %x, %x\n", ret, val); + goto err_exit; + } + + dev_dbg(&info->pdev->dev, "adc raw val=%x\n", val); + *raw_val = val; + +err_exit: + iio_channel_release(indio_chan); +exit: + return ret; +} + +#ifdef CONFIG_DEBUG_FS +static int fuel_gauge_debug_show(struct seq_file *s, void *data) +{ + struct axp288_fg_info *info = s->private; + int raw_val, ret; + + seq_printf(s, " PWR_STATUS[%02x] : %02x\n", + AXP20X_PWR_INPUT_STATUS, + fuel_gauge_reg_readb(info, AXP20X_PWR_INPUT_STATUS)); + seq_printf(s, "PWR_OP_MODE[%02x] : %02x\n", + AXP20X_PWR_OP_MODE, + fuel_gauge_reg_readb(info, AXP20X_PWR_OP_MODE)); + seq_printf(s, " CHRG_CTRL1[%02x] : %02x\n", + AXP20X_CHRG_CTRL1, + fuel_gauge_reg_readb(info, AXP20X_CHRG_CTRL1)); + seq_printf(s, " VLTF[%02x] : %02x\n", + AXP20X_V_LTF_DISCHRG, + fuel_gauge_reg_readb(info, AXP20X_V_LTF_DISCHRG)); + seq_printf(s, " VHTF[%02x] : %02x\n", + AXP20X_V_HTF_DISCHRG, + fuel_gauge_reg_readb(info, AXP20X_V_HTF_DISCHRG)); + seq_printf(s, " CC_CTRL[%02x] : %02x\n", + AXP20X_CC_CTRL, + fuel_gauge_reg_readb(info, AXP20X_CC_CTRL)); + seq_printf(s, "BATTERY CAP[%02x] : %02x\n", + AXP20X_FG_RES, + fuel_gauge_reg_readb(info, AXP20X_FG_RES)); + seq_printf(s, " FG_RDC1[%02x] : %02x\n", + AXP288_FG_RDC1_REG, + fuel_gauge_reg_readb(info, AXP288_FG_RDC1_REG)); + 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", + 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", + 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", + 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)); + seq_printf(s, " FG_OCV_CAP[%02x] : %02x\n", + AXP288_FG_OCV_CAP_REG, + fuel_gauge_reg_readb(info, AXP288_FG_OCV_CAP_REG)); + seq_printf(s, " FG_CC_CAP[%02x] : %02x\n", + AXP288_FG_CC_CAP_REG, + fuel_gauge_reg_readb(info, AXP288_FG_CC_CAP_REG)); + seq_printf(s, " FG_LOW_CAP[%02x] : %02x\n", + AXP288_FG_LOW_CAP_REG, + fuel_gauge_reg_readb(info, AXP288_FG_LOW_CAP_REG)); + seq_printf(s, "TUNING_CTL0[%02x] : %02x\n", + AXP288_FG_TUNE0, + fuel_gauge_reg_readb(info, AXP288_FG_TUNE0)); + seq_printf(s, "TUNING_CTL1[%02x] : %02x\n", + AXP288_FG_TUNE1, + fuel_gauge_reg_readb(info, AXP288_FG_TUNE1)); + seq_printf(s, "TUNING_CTL2[%02x] : %02x\n", + AXP288_FG_TUNE2, + fuel_gauge_reg_readb(info, AXP288_FG_TUNE2)); + seq_printf(s, "TUNING_CTL3[%02x] : %02x\n", + AXP288_FG_TUNE3, + fuel_gauge_reg_readb(info, AXP288_FG_TUNE3)); + seq_printf(s, "TUNING_CTL4[%02x] : %02x\n", + AXP288_FG_TUNE4, + fuel_gauge_reg_readb(info, AXP288_FG_TUNE4)); + seq_printf(s, "TUNING_CTL5[%02x] : %02x\n", + AXP288_FG_TUNE5, + fuel_gauge_reg_readb(info, AXP288_FG_TUNE5)); + + ret = pmic_read_adc_val("axp288-batt-temp", &raw_val, info); + if (ret >= 0) + seq_printf(s, "axp288-batttemp : %d\n", raw_val); + ret = pmic_read_adc_val("axp288-pmic-temp", &raw_val, info); + if (ret >= 0) + seq_printf(s, "axp288-pmictemp : %d\n", raw_val); + ret = pmic_read_adc_val("axp288-system-temp", &raw_val, info); + if (ret >= 0) + seq_printf(s, "axp288-systtemp : %d\n", raw_val); + ret = pmic_read_adc_val("axp288-chrg-curr", &raw_val, info); + if (ret >= 0) + seq_printf(s, "axp288-chrgcurr : %d\n", raw_val); + ret = pmic_read_adc_val("axp288-chrg-d-curr", &raw_val, info); + if (ret >= 0) + seq_printf(s, "axp288-dchrgcur : %d\n", raw_val); + ret = pmic_read_adc_val("axp288-batt-volt", &raw_val, info); + if (ret >= 0) + seq_printf(s, "axp288-battvolt : %d\n", raw_val); + + return 0; +} + +static int debug_open(struct inode *inode, struct file *file) +{ + return single_open(file, fuel_gauge_debug_show, inode->i_private); +} + +static const struct file_operations fg_debug_fops = { + .open = debug_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static void fuel_gauge_create_debugfs(struct axp288_fg_info *info) +{ + info->debug_file = debugfs_create_file("fuelgauge", 0666, NULL, + info, &fg_debug_fops); +} + +static void fuel_gauge_remove_debugfs(struct axp288_fg_info *info) +{ + debugfs_remove(info->debug_file); +} +#else +static inline void fuel_gauge_create_debugfs(struct axp288_fg_info *info) +{ +} +static inline void fuel_gauge_remove_debugfs(struct axp288_fg_info *info) +{ +} +#endif + +static void fuel_gauge_get_status(struct axp288_fg_info *info) +{ + int pwr_stat, ret; + int charge, discharge; + + pwr_stat = fuel_gauge_reg_readb(info, AXP20X_PWR_INPUT_STATUS); + if (pwr_stat < 0) { + dev_err(&info->pdev->dev, + "PWR STAT read failed:%d\n", pwr_stat); + return; + } + ret = pmic_read_adc_val("axp288-chrg-curr", &charge, info); + if (ret < 0) { + dev_err(&info->pdev->dev, + "ADC charge current read failed:%d\n", ret); + return; + } + ret = pmic_read_adc_val("axp288-chrg-d-curr", &discharge, info); + if (ret < 0) { + dev_err(&info->pdev->dev, + "ADC discharge current read failed:%d\n", ret); + return; + } + + if (charge > 0) + info->status = POWER_SUPPLY_STATUS_CHARGING; + else if (discharge > 0) + info->status = POWER_SUPPLY_STATUS_DISCHARGING; + else { + if (pwr_stat & CHRG_STAT_BAT_PRESENT) + info->status = POWER_SUPPLY_STATUS_FULL; + else + info->status = POWER_SUPPLY_STATUS_NOT_CHARGING; + } +} + +static int fuel_gauge_get_vbatt(struct axp288_fg_info *info, int *vbatt) +{ + int ret = 0, raw_val; + + ret = pmic_read_adc_val("axp288-batt-volt", &raw_val, info); + if (ret < 0) + goto vbatt_read_fail; + + *vbatt = VOLTAGE_FROM_ADC(raw_val); +vbatt_read_fail: + return ret; +} + +static int fuel_gauge_get_current(struct axp288_fg_info *info, int *cur) +{ + int ret, value = 0; + int charge, discharge; + + ret = pmic_read_adc_val("axp288-chrg-curr", &charge, info); + if (ret < 0) + goto current_read_fail; + ret = pmic_read_adc_val("axp288-chrg-d-curr", &discharge, info); + if (ret < 0) + goto current_read_fail; + + if (charge > 0) + value = charge; + else if (discharge > 0) + value = -1 * discharge; + + *cur = value; +current_read_fail: + 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; + + ret = fuel_gauge_reg_readb(info, AXP288_FG_OCVL_REG); + if (ret < 0) + goto vocv_read_fail; + value |= (ret & 0xf); + + *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; + + ret = fuel_gauge_get_vocv(info, &vocv); + if (ret < 0) + goto health_read_fail; + + if (vocv > info->pdata->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; + +health_read_fail: + 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) +{ + struct axp288_fg_info *info = power_supply_get_drvdata(ps); + int ret = 0, value; + + mutex_lock(&info->lock); + switch (prop) { + case POWER_SUPPLY_PROP_STATUS: + fuel_gauge_get_status(info); + val->intval = info->status; + break; + case POWER_SUPPLY_PROP_HEALTH: + val->intval = fuel_gauge_battery_health(info); + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + ret = fuel_gauge_get_vbatt(info, &value); + if (ret < 0) + goto fuel_gauge_read_err; + val->intval = PROP_VOLT(value); + break; + case POWER_SUPPLY_PROP_VOLTAGE_OCV: + ret = fuel_gauge_get_vocv(info, &value); + if (ret < 0) + goto fuel_gauge_read_err; + val->intval = PROP_VOLT(value); + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + ret = fuel_gauge_get_current(info, &value); + if (ret < 0) + goto fuel_gauge_read_err; + val->intval = PROP_CURR(value); + break; + case POWER_SUPPLY_PROP_PRESENT: + ret = fuel_gauge_reg_readb(info, AXP20X_PWR_OP_MODE); + if (ret < 0) + goto fuel_gauge_read_err; + + if (ret & CHRG_STAT_BAT_PRESENT) + val->intval = 1; + else + val->intval = 0; + break; + case POWER_SUPPLY_PROP_CAPACITY: + ret = fuel_gauge_reg_readb(info, AXP20X_FG_RES); + if (ret < 0) + goto fuel_gauge_read_err; + + if (!(ret & FG_REP_CAP_VALID)) + dev_err(&info->pdev->dev, + "capacity measurement not valid\n"); + val->intval = (ret & FG_REP_CAP_VAL_MASK); + break; + case POWER_SUPPLY_PROP_CAPACITY_ALERT_MIN: + ret = fuel_gauge_reg_readb(info, AXP288_FG_LOW_CAP_REG); + if (ret < 0) + 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); + 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; + break; + case POWER_SUPPLY_PROP_CHARGE_FULL: + ret = fuel_gauge_reg_readb(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); + 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; + break; + default: + mutex_unlock(&info->lock); + return -EINVAL; + } + + mutex_unlock(&info->lock); + return 0; + +fuel_gauge_read_err: + mutex_unlock(&info->lock); + return ret; +} + +static int fuel_gauge_set_property(struct power_supply *ps, + enum power_supply_property prop, + const union power_supply_propval *val) +{ + struct axp288_fg_info *info = power_supply_get_drvdata(ps); + int ret = 0; + + 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; + break; + } + ret = fuel_gauge_reg_readb(info, AXP288_FG_LOW_CAP_REG); + if (ret < 0) + break; + ret &= 0xf0; + ret |= (val->intval & 0xf); + ret = fuel_gauge_reg_writeb(info, AXP288_FG_LOW_CAP_REG, ret); + break; + default: + ret = -EINVAL; + break; + } + + mutex_unlock(&info->lock); + return ret; +} + +static int fuel_gauge_property_is_writeable(struct power_supply *psy, + enum power_supply_property psp) +{ + 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; + default: + ret = 0; + } + + return ret; +} + +static void fuel_gauge_status_monitor(struct work_struct *work) +{ + struct axp288_fg_info *info = container_of(work, + struct axp288_fg_info, status_monitor.work); + + fuel_gauge_get_status(info); + power_supply_changed(info->bat); + schedule_delayed_work(&info->status_monitor, STATUS_MON_DELAY_JIFFIES); +} + +static irqreturn_t fuel_gauge_thread_handler(int irq, void *dev) +{ + struct axp288_fg_info *info = dev; + int i; + + for (i = 0; i < AXP288_FG_INTR_NUM; i++) { + if (info->irq[i] == irq) + break; + } + + if (i >= AXP288_FG_INTR_NUM) { + dev_warn(&info->pdev->dev, "spurious interrupt!!\n"); + return IRQ_NONE; + } + + switch (i) { + case QWBTU_IRQ: + dev_info(&info->pdev->dev, + "Quit Battery under temperature in work mode IRQ (QWBTU)\n"); + break; + case WBTU_IRQ: + dev_info(&info->pdev->dev, + "Battery under temperature in work mode IRQ (WBTU)\n"); + break; + case QWBTO_IRQ: + dev_info(&info->pdev->dev, + "Quit Battery over temperature in work mode IRQ (QWBTO)\n"); + break; + case WBTO_IRQ: + dev_info(&info->pdev->dev, + "Battery over temperature in work mode IRQ (WBTO)\n"); + break; + case WL2_IRQ: + dev_info(&info->pdev->dev, "Low Batt Warning(2) INTR\n"); + break; + case WL1_IRQ: + dev_info(&info->pdev->dev, "Low Batt Warning(1) INTR\n"); + break; + default: + dev_warn(&info->pdev->dev, "Spurious Interrupt!!!\n"); + } + + power_supply_changed(info->bat); + return IRQ_HANDLED; +} + +static void fuel_gauge_external_power_changed(struct power_supply *psy) +{ + struct axp288_fg_info *info = power_supply_get_drvdata(psy); + + power_supply_changed(info->bat); +} + +static const struct power_supply_desc fuel_gauge_desc = { + .name = DEV_NAME, + .type = POWER_SUPPLY_TYPE_BATTERY, + .properties = fuel_gauge_props, + .num_properties = ARRAY_SIZE(fuel_gauge_props), + .get_property = fuel_gauge_get_property, + .set_property = fuel_gauge_set_property, + .property_is_writeable = fuel_gauge_property_is_writeable, + .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; + + for (i = 0; i < AXP288_FG_INTR_NUM; i++) { + pirq = platform_get_irq(info->pdev, i); + info->irq[i] = regmap_irq_get_virq(info->regmap_irqc, pirq); + if (info->irq[i] < 0) { + dev_warn(&info->pdev->dev, + "regmap_irq get virq failed for IRQ %d: %d\n", + pirq, info->irq[i]); + info->irq[i] = -1; + goto intr_failed; + } + ret = request_threaded_irq(info->irq[i], + NULL, fuel_gauge_thread_handler, + IRQF_ONESHOT, DEV_NAME, info); + if (ret) { + dev_warn(&info->pdev->dev, + "request irq failed for IRQ %d: %d\n", + pirq, info->irq[i]); + info->irq[i] = -1; + goto intr_failed; + } else { + dev_info(&info->pdev->dev, "HW IRQ %d -> VIRQ %d\n", + pirq, info->irq[i]); + } + } + return; + +intr_failed: + for (; i > 0; i--) { + free_irq(info->irq[i - 1], info); + info->irq[i - 1] = -1; + } +} + +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; + struct axp288_fg_info *info; + struct axp20x_dev *axp20x = dev_get_drvdata(pdev->dev.parent); + struct power_supply_config psy_cfg = {}; + + info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + info->pdev = 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); + + psy_cfg.drv_data = info; + info->bat = power_supply_register(&pdev->dev, &fuel_gauge_desc, &psy_cfg); + if (IS_ERR(info->bat)) { + ret = PTR_ERR(info->bat); + dev_err(&pdev->dev, "failed to register battery: %d\n", ret); + return ret; + } + + 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; +} + +static const struct platform_device_id axp288_fg_id_table[] = { + { .name = DEV_NAME }, + {}, +}; + +static int axp288_fuel_gauge_remove(struct platform_device *pdev) +{ + struct axp288_fg_info *info = platform_get_drvdata(pdev); + int i; + + cancel_delayed_work_sync(&info->status_monitor); + power_supply_unregister(info->bat); + fuel_gauge_remove_debugfs(info); + + for (i = 0; i < AXP288_FG_INTR_NUM; i++) + if (info->irq[i] >= 0) + free_irq(info->irq[i], info); + + return 0; +} + +static struct platform_driver axp288_fuel_gauge_driver = { + .probe = axp288_fuel_gauge_probe, + .remove = axp288_fuel_gauge_remove, + .id_table = axp288_fg_id_table, + .driver = { + .name = DEV_NAME, + }, +}; + +module_platform_driver(axp288_fuel_gauge_driver); + +MODULE_AUTHOR("Ramakrishna Pallala "); +MODULE_AUTHOR("Todd Brandt "); +MODULE_DESCRIPTION("Xpower AXP288 Fuel Gauge Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/power/supply/bq2415x_charger.c b/drivers/power/supply/bq2415x_charger.c new file mode 100644 index 000000000000..73e2f0b79dd4 --- /dev/null +++ b/drivers/power/supply/bq2415x_charger.c @@ -0,0 +1,1815 @@ +/* + * bq2415x charger driver + * + * Copyright (C) 2011-2013 Pali Rohár + * + * 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 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. + * + * Datasheets: + * http://www.ti.com/product/bq24150 + * http://www.ti.com/product/bq24150a + * http://www.ti.com/product/bq24152 + * http://www.ti.com/product/bq24153 + * http://www.ti.com/product/bq24153a + * http://www.ti.com/product/bq24155 + * http://www.ti.com/product/bq24157s + * http://www.ti.com/product/bq24158 + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +/* timeout for resetting chip timer */ +#define BQ2415X_TIMER_TIMEOUT 10 + +#define BQ2415X_REG_STATUS 0x00 +#define BQ2415X_REG_CONTROL 0x01 +#define BQ2415X_REG_VOLTAGE 0x02 +#define BQ2415X_REG_VENDER 0x03 +#define BQ2415X_REG_CURRENT 0x04 + +/* reset state for all registers */ +#define BQ2415X_RESET_STATUS BIT(6) +#define BQ2415X_RESET_CONTROL (BIT(4)|BIT(5)) +#define BQ2415X_RESET_VOLTAGE (BIT(1)|BIT(3)) +#define BQ2415X_RESET_CURRENT (BIT(0)|BIT(3)|BIT(7)) + +/* status register */ +#define BQ2415X_BIT_TMR_RST 7 +#define BQ2415X_BIT_OTG 7 +#define BQ2415X_BIT_EN_STAT 6 +#define BQ2415X_MASK_STAT (BIT(4)|BIT(5)) +#define BQ2415X_SHIFT_STAT 4 +#define BQ2415X_BIT_BOOST 3 +#define BQ2415X_MASK_FAULT (BIT(0)|BIT(1)|BIT(2)) +#define BQ2415X_SHIFT_FAULT 0 + +/* control register */ +#define BQ2415X_MASK_LIMIT (BIT(6)|BIT(7)) +#define BQ2415X_SHIFT_LIMIT 6 +#define BQ2415X_MASK_VLOWV (BIT(4)|BIT(5)) +#define BQ2415X_SHIFT_VLOWV 4 +#define BQ2415X_BIT_TE 3 +#define BQ2415X_BIT_CE 2 +#define BQ2415X_BIT_HZ_MODE 1 +#define BQ2415X_BIT_OPA_MODE 0 + +/* voltage register */ +#define BQ2415X_MASK_VO (BIT(2)|BIT(3)|BIT(4)|BIT(5)|BIT(6)|BIT(7)) +#define BQ2415X_SHIFT_VO 2 +#define BQ2415X_BIT_OTG_PL 1 +#define BQ2415X_BIT_OTG_EN 0 + +/* vender register */ +#define BQ2415X_MASK_VENDER (BIT(5)|BIT(6)|BIT(7)) +#define BQ2415X_SHIFT_VENDER 5 +#define BQ2415X_MASK_PN (BIT(3)|BIT(4)) +#define BQ2415X_SHIFT_PN 3 +#define BQ2415X_MASK_REVISION (BIT(0)|BIT(1)|BIT(2)) +#define BQ2415X_SHIFT_REVISION 0 + +/* current register */ +#define BQ2415X_MASK_RESET BIT(7) +#define BQ2415X_MASK_VI_CHRG (BIT(4)|BIT(5)|BIT(6)) +#define BQ2415X_SHIFT_VI_CHRG 4 +/* N/A BIT(3) */ +#define BQ2415X_MASK_VI_TERM (BIT(0)|BIT(1)|BIT(2)) +#define BQ2415X_SHIFT_VI_TERM 0 + + +enum bq2415x_command { + BQ2415X_TIMER_RESET, + BQ2415X_OTG_STATUS, + BQ2415X_STAT_PIN_STATUS, + BQ2415X_STAT_PIN_ENABLE, + BQ2415X_STAT_PIN_DISABLE, + BQ2415X_CHARGE_STATUS, + BQ2415X_BOOST_STATUS, + BQ2415X_FAULT_STATUS, + + BQ2415X_CHARGE_TERMINATION_STATUS, + BQ2415X_CHARGE_TERMINATION_ENABLE, + BQ2415X_CHARGE_TERMINATION_DISABLE, + BQ2415X_CHARGER_STATUS, + BQ2415X_CHARGER_ENABLE, + BQ2415X_CHARGER_DISABLE, + BQ2415X_HIGH_IMPEDANCE_STATUS, + BQ2415X_HIGH_IMPEDANCE_ENABLE, + BQ2415X_HIGH_IMPEDANCE_DISABLE, + BQ2415X_BOOST_MODE_STATUS, + BQ2415X_BOOST_MODE_ENABLE, + BQ2415X_BOOST_MODE_DISABLE, + + BQ2415X_OTG_LEVEL, + BQ2415X_OTG_ACTIVATE_HIGH, + BQ2415X_OTG_ACTIVATE_LOW, + BQ2415X_OTG_PIN_STATUS, + BQ2415X_OTG_PIN_ENABLE, + BQ2415X_OTG_PIN_DISABLE, + + BQ2415X_VENDER_CODE, + BQ2415X_PART_NUMBER, + BQ2415X_REVISION, +}; + +enum bq2415x_chip { + BQUNKNOWN, + BQ24150, + BQ24150A, + BQ24151, + BQ24151A, + BQ24152, + BQ24153, + BQ24153A, + BQ24155, + BQ24156, + BQ24156A, + BQ24157S, + BQ24158, +}; + +static char *bq2415x_chip_name[] = { + "unknown", + "bq24150", + "bq24150a", + "bq24151", + "bq24151a", + "bq24152", + "bq24153", + "bq24153a", + "bq24155", + "bq24156", + "bq24156a", + "bq24157s", + "bq24158", +}; + +struct bq2415x_device { + struct device *dev; + struct bq2415x_platform_data init_data; + struct power_supply *charger; + struct power_supply_desc charger_desc; + struct delayed_work work; + struct device_node *notify_node; + struct notifier_block nb; + enum bq2415x_mode reported_mode;/* mode reported by hook function */ + enum bq2415x_mode mode; /* currently configured mode */ + enum bq2415x_chip chip; + const char *timer_error; + char *model; + char *name; + int autotimer; /* 1 - if driver automatically reset timer, 0 - not */ + int automode; /* 1 - enabled, 0 - disabled; -1 - not supported */ + int id; +}; + +/* each registered chip must have unique id */ +static DEFINE_IDR(bq2415x_id); + +static DEFINE_MUTEX(bq2415x_id_mutex); +static DEFINE_MUTEX(bq2415x_timer_mutex); +static DEFINE_MUTEX(bq2415x_i2c_mutex); + +/**** i2c read functions ****/ + +/* read value from register */ +static int bq2415x_i2c_read(struct bq2415x_device *bq, u8 reg) +{ + struct i2c_client *client = to_i2c_client(bq->dev); + struct i2c_msg msg[2]; + u8 val; + int ret; + + if (!client->adapter) + return -ENODEV; + + msg[0].addr = client->addr; + msg[0].flags = 0; + msg[0].buf = ® + msg[0].len = sizeof(reg); + msg[1].addr = client->addr; + msg[1].flags = I2C_M_RD; + msg[1].buf = &val; + msg[1].len = sizeof(val); + + mutex_lock(&bq2415x_i2c_mutex); + ret = i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg)); + mutex_unlock(&bq2415x_i2c_mutex); + + if (ret < 0) + return ret; + + return val; +} + +/* read value from register, apply mask and right shift it */ +static int bq2415x_i2c_read_mask(struct bq2415x_device *bq, u8 reg, + u8 mask, u8 shift) +{ + int ret; + + if (shift > 8) + return -EINVAL; + + ret = bq2415x_i2c_read(bq, reg); + if (ret < 0) + return ret; + return (ret & mask) >> shift; +} + +/* read value from register and return one specified bit */ +static int bq2415x_i2c_read_bit(struct bq2415x_device *bq, u8 reg, u8 bit) +{ + if (bit > 8) + return -EINVAL; + return bq2415x_i2c_read_mask(bq, reg, BIT(bit), bit); +} + +/**** i2c write functions ****/ + +/* write value to register */ +static int bq2415x_i2c_write(struct bq2415x_device *bq, u8 reg, u8 val) +{ + struct i2c_client *client = to_i2c_client(bq->dev); + struct i2c_msg msg[1]; + u8 data[2]; + int ret; + + data[0] = reg; + data[1] = val; + + msg[0].addr = client->addr; + msg[0].flags = 0; + msg[0].buf = data; + msg[0].len = ARRAY_SIZE(data); + + mutex_lock(&bq2415x_i2c_mutex); + ret = i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg)); + mutex_unlock(&bq2415x_i2c_mutex); + + /* i2c_transfer returns number of messages transferred */ + if (ret < 0) + return ret; + else if (ret != 1) + return -EIO; + + return 0; +} + +/* read value from register, change it with mask left shifted and write back */ +static int bq2415x_i2c_write_mask(struct bq2415x_device *bq, u8 reg, u8 val, + u8 mask, u8 shift) +{ + int ret; + + if (shift > 8) + return -EINVAL; + + ret = bq2415x_i2c_read(bq, reg); + if (ret < 0) + return ret; + + ret &= ~mask; + ret |= val << shift; + + return bq2415x_i2c_write(bq, reg, ret); +} + +/* change only one bit in register */ +static int bq2415x_i2c_write_bit(struct bq2415x_device *bq, u8 reg, + bool val, u8 bit) +{ + if (bit > 8) + return -EINVAL; + return bq2415x_i2c_write_mask(bq, reg, val, BIT(bit), bit); +} + +/**** global functions ****/ + +/* exec command function */ +static int bq2415x_exec_command(struct bq2415x_device *bq, + enum bq2415x_command command) +{ + int ret; + + switch (command) { + case BQ2415X_TIMER_RESET: + return bq2415x_i2c_write_bit(bq, BQ2415X_REG_STATUS, + 1, BQ2415X_BIT_TMR_RST); + case BQ2415X_OTG_STATUS: + return bq2415x_i2c_read_bit(bq, BQ2415X_REG_STATUS, + BQ2415X_BIT_OTG); + case BQ2415X_STAT_PIN_STATUS: + return bq2415x_i2c_read_bit(bq, BQ2415X_REG_STATUS, + BQ2415X_BIT_EN_STAT); + case BQ2415X_STAT_PIN_ENABLE: + return bq2415x_i2c_write_bit(bq, BQ2415X_REG_STATUS, 1, + BQ2415X_BIT_EN_STAT); + case BQ2415X_STAT_PIN_DISABLE: + return bq2415x_i2c_write_bit(bq, BQ2415X_REG_STATUS, 0, + BQ2415X_BIT_EN_STAT); + case BQ2415X_CHARGE_STATUS: + return bq2415x_i2c_read_mask(bq, BQ2415X_REG_STATUS, + BQ2415X_MASK_STAT, BQ2415X_SHIFT_STAT); + case BQ2415X_BOOST_STATUS: + return bq2415x_i2c_read_bit(bq, BQ2415X_REG_STATUS, + BQ2415X_BIT_BOOST); + case BQ2415X_FAULT_STATUS: + return bq2415x_i2c_read_mask(bq, BQ2415X_REG_STATUS, + BQ2415X_MASK_FAULT, BQ2415X_SHIFT_FAULT); + + case BQ2415X_CHARGE_TERMINATION_STATUS: + return bq2415x_i2c_read_bit(bq, BQ2415X_REG_CONTROL, + BQ2415X_BIT_TE); + case BQ2415X_CHARGE_TERMINATION_ENABLE: + return bq2415x_i2c_write_bit(bq, BQ2415X_REG_CONTROL, + 1, BQ2415X_BIT_TE); + case BQ2415X_CHARGE_TERMINATION_DISABLE: + return bq2415x_i2c_write_bit(bq, BQ2415X_REG_CONTROL, + 0, BQ2415X_BIT_TE); + case BQ2415X_CHARGER_STATUS: + ret = bq2415x_i2c_read_bit(bq, BQ2415X_REG_CONTROL, + BQ2415X_BIT_CE); + if (ret < 0) + return ret; + return ret > 0 ? 0 : 1; + case BQ2415X_CHARGER_ENABLE: + return bq2415x_i2c_write_bit(bq, BQ2415X_REG_CONTROL, + 0, BQ2415X_BIT_CE); + case BQ2415X_CHARGER_DISABLE: + return bq2415x_i2c_write_bit(bq, BQ2415X_REG_CONTROL, + 1, BQ2415X_BIT_CE); + case BQ2415X_HIGH_IMPEDANCE_STATUS: + return bq2415x_i2c_read_bit(bq, BQ2415X_REG_CONTROL, + BQ2415X_BIT_HZ_MODE); + case BQ2415X_HIGH_IMPEDANCE_ENABLE: + return bq2415x_i2c_write_bit(bq, BQ2415X_REG_CONTROL, + 1, BQ2415X_BIT_HZ_MODE); + case BQ2415X_HIGH_IMPEDANCE_DISABLE: + return bq2415x_i2c_write_bit(bq, BQ2415X_REG_CONTROL, + 0, BQ2415X_BIT_HZ_MODE); + case BQ2415X_BOOST_MODE_STATUS: + return bq2415x_i2c_read_bit(bq, BQ2415X_REG_CONTROL, + BQ2415X_BIT_OPA_MODE); + case BQ2415X_BOOST_MODE_ENABLE: + return bq2415x_i2c_write_bit(bq, BQ2415X_REG_CONTROL, + 1, BQ2415X_BIT_OPA_MODE); + case BQ2415X_BOOST_MODE_DISABLE: + return bq2415x_i2c_write_bit(bq, BQ2415X_REG_CONTROL, + 0, BQ2415X_BIT_OPA_MODE); + + case BQ2415X_OTG_LEVEL: + return bq2415x_i2c_read_bit(bq, BQ2415X_REG_VOLTAGE, + BQ2415X_BIT_OTG_PL); + case BQ2415X_OTG_ACTIVATE_HIGH: + return bq2415x_i2c_write_bit(bq, BQ2415X_REG_VOLTAGE, + 1, BQ2415X_BIT_OTG_PL); + case BQ2415X_OTG_ACTIVATE_LOW: + return bq2415x_i2c_write_bit(bq, BQ2415X_REG_VOLTAGE, + 0, BQ2415X_BIT_OTG_PL); + case BQ2415X_OTG_PIN_STATUS: + return bq2415x_i2c_read_bit(bq, BQ2415X_REG_VOLTAGE, + BQ2415X_BIT_OTG_EN); + case BQ2415X_OTG_PIN_ENABLE: + return bq2415x_i2c_write_bit(bq, BQ2415X_REG_VOLTAGE, + 1, BQ2415X_BIT_OTG_EN); + case BQ2415X_OTG_PIN_DISABLE: + return bq2415x_i2c_write_bit(bq, BQ2415X_REG_VOLTAGE, + 0, BQ2415X_BIT_OTG_EN); + + case BQ2415X_VENDER_CODE: + return bq2415x_i2c_read_mask(bq, BQ2415X_REG_VENDER, + BQ2415X_MASK_VENDER, BQ2415X_SHIFT_VENDER); + case BQ2415X_PART_NUMBER: + return bq2415x_i2c_read_mask(bq, BQ2415X_REG_VENDER, + BQ2415X_MASK_PN, BQ2415X_SHIFT_PN); + case BQ2415X_REVISION: + return bq2415x_i2c_read_mask(bq, BQ2415X_REG_VENDER, + BQ2415X_MASK_REVISION, BQ2415X_SHIFT_REVISION); + } + return -EINVAL; +} + +/* detect chip type */ +static enum bq2415x_chip bq2415x_detect_chip(struct bq2415x_device *bq) +{ + struct i2c_client *client = to_i2c_client(bq->dev); + int ret = bq2415x_exec_command(bq, BQ2415X_PART_NUMBER); + + if (ret < 0) + return ret; + + switch (client->addr) { + case 0x6b: + switch (ret) { + case 0: + if (bq->chip == BQ24151A) + return bq->chip; + return BQ24151; + case 1: + if (bq->chip == BQ24150A || + bq->chip == BQ24152 || + bq->chip == BQ24155) + return bq->chip; + return BQ24150; + case 2: + if (bq->chip == BQ24153A) + return bq->chip; + return BQ24153; + default: + return BQUNKNOWN; + } + break; + + case 0x6a: + switch (ret) { + case 0: + if (bq->chip == BQ24156A) + return bq->chip; + return BQ24156; + case 2: + if (bq->chip == BQ24157S) + return bq->chip; + return BQ24158; + default: + return BQUNKNOWN; + } + break; + } + + return BQUNKNOWN; +} + +/* detect chip revision */ +static int bq2415x_detect_revision(struct bq2415x_device *bq) +{ + int ret = bq2415x_exec_command(bq, BQ2415X_REVISION); + int chip = bq2415x_detect_chip(bq); + + if (ret < 0 || chip < 0) + return -1; + + switch (chip) { + case BQ24150: + case BQ24150A: + case BQ24151: + case BQ24151A: + case BQ24152: + if (ret >= 0 && ret <= 3) + return ret; + return -1; + case BQ24153: + case BQ24153A: + case BQ24156: + case BQ24156A: + case BQ24157S: + case BQ24158: + if (ret == 3) + return 0; + else if (ret == 1) + return 1; + return -1; + case BQ24155: + if (ret == 3) + return 3; + return -1; + case BQUNKNOWN: + return -1; + } + + return -1; +} + +/* return chip vender code */ +static int bq2415x_get_vender_code(struct bq2415x_device *bq) +{ + int ret; + + ret = bq2415x_exec_command(bq, BQ2415X_VENDER_CODE); + if (ret < 0) + return 0; + + /* convert to binary */ + return (ret & 0x1) + + ((ret >> 1) & 0x1) * 10 + + ((ret >> 2) & 0x1) * 100; +} + +/* reset all chip registers to default state */ +static void bq2415x_reset_chip(struct bq2415x_device *bq) +{ + bq2415x_i2c_write(bq, BQ2415X_REG_CURRENT, BQ2415X_RESET_CURRENT); + bq2415x_i2c_write(bq, BQ2415X_REG_VOLTAGE, BQ2415X_RESET_VOLTAGE); + bq2415x_i2c_write(bq, BQ2415X_REG_CONTROL, BQ2415X_RESET_CONTROL); + bq2415x_i2c_write(bq, BQ2415X_REG_STATUS, BQ2415X_RESET_STATUS); + bq->timer_error = NULL; +} + +/**** properties functions ****/ + +/* set current limit in mA */ +static int bq2415x_set_current_limit(struct bq2415x_device *bq, int mA) +{ + int val; + + if (mA <= 100) + val = 0; + else if (mA <= 500) + val = 1; + else if (mA <= 800) + val = 2; + else + val = 3; + + return bq2415x_i2c_write_mask(bq, BQ2415X_REG_CONTROL, val, + BQ2415X_MASK_LIMIT, BQ2415X_SHIFT_LIMIT); +} + +/* get current limit in mA */ +static int bq2415x_get_current_limit(struct bq2415x_device *bq) +{ + int ret; + + ret = bq2415x_i2c_read_mask(bq, BQ2415X_REG_CONTROL, + BQ2415X_MASK_LIMIT, BQ2415X_SHIFT_LIMIT); + if (ret < 0) + return ret; + else if (ret == 0) + return 100; + else if (ret == 1) + return 500; + else if (ret == 2) + return 800; + else if (ret == 3) + return 1800; + return -EINVAL; +} + +/* set weak battery voltage in mV */ +static int bq2415x_set_weak_battery_voltage(struct bq2415x_device *bq, int mV) +{ + int val; + + /* round to 100mV */ + if (mV <= 3400 + 50) + val = 0; + else if (mV <= 3500 + 50) + val = 1; + else if (mV <= 3600 + 50) + val = 2; + else + val = 3; + + return bq2415x_i2c_write_mask(bq, BQ2415X_REG_CONTROL, val, + BQ2415X_MASK_VLOWV, BQ2415X_SHIFT_VLOWV); +} + +/* get weak battery voltage in mV */ +static int bq2415x_get_weak_battery_voltage(struct bq2415x_device *bq) +{ + int ret; + + ret = bq2415x_i2c_read_mask(bq, BQ2415X_REG_CONTROL, + BQ2415X_MASK_VLOWV, BQ2415X_SHIFT_VLOWV); + if (ret < 0) + return ret; + return 100 * (34 + ret); +} + +/* set battery regulation voltage in mV */ +static int bq2415x_set_battery_regulation_voltage(struct bq2415x_device *bq, + int mV) +{ + int val = (mV/10 - 350) / 2; + + /* + * According to datasheet, maximum battery regulation voltage is + * 4440mV which is b101111 = 47. + */ + if (val < 0) + val = 0; + else if (val > 47) + return -EINVAL; + + return bq2415x_i2c_write_mask(bq, BQ2415X_REG_VOLTAGE, val, + BQ2415X_MASK_VO, BQ2415X_SHIFT_VO); +} + +/* get battery regulation voltage in mV */ +static int bq2415x_get_battery_regulation_voltage(struct bq2415x_device *bq) +{ + int ret = bq2415x_i2c_read_mask(bq, BQ2415X_REG_VOLTAGE, + BQ2415X_MASK_VO, BQ2415X_SHIFT_VO); + + if (ret < 0) + return ret; + return 10 * (350 + 2*ret); +} + +/* set charge current in mA (platform data must provide resistor sense) */ +static int bq2415x_set_charge_current(struct bq2415x_device *bq, int mA) +{ + int val; + + if (bq->init_data.resistor_sense <= 0) + return -EINVAL; + + val = (mA * bq->init_data.resistor_sense - 37400) / 6800; + if (val < 0) + val = 0; + else if (val > 7) + val = 7; + + return bq2415x_i2c_write_mask(bq, BQ2415X_REG_CURRENT, val, + BQ2415X_MASK_VI_CHRG | BQ2415X_MASK_RESET, + BQ2415X_SHIFT_VI_CHRG); +} + +/* get charge current in mA (platform data must provide resistor sense) */ +static int bq2415x_get_charge_current(struct bq2415x_device *bq) +{ + int ret; + + if (bq->init_data.resistor_sense <= 0) + return -EINVAL; + + ret = bq2415x_i2c_read_mask(bq, BQ2415X_REG_CURRENT, + BQ2415X_MASK_VI_CHRG, BQ2415X_SHIFT_VI_CHRG); + if (ret < 0) + return ret; + return (37400 + 6800*ret) / bq->init_data.resistor_sense; +} + +/* set termination current in mA (platform data must provide resistor sense) */ +static int bq2415x_set_termination_current(struct bq2415x_device *bq, int mA) +{ + int val; + + if (bq->init_data.resistor_sense <= 0) + return -EINVAL; + + val = (mA * bq->init_data.resistor_sense - 3400) / 3400; + if (val < 0) + val = 0; + else if (val > 7) + val = 7; + + return bq2415x_i2c_write_mask(bq, BQ2415X_REG_CURRENT, val, + BQ2415X_MASK_VI_TERM | BQ2415X_MASK_RESET, + BQ2415X_SHIFT_VI_TERM); +} + +/* get termination current in mA (platform data must provide resistor sense) */ +static int bq2415x_get_termination_current(struct bq2415x_device *bq) +{ + int ret; + + if (bq->init_data.resistor_sense <= 0) + return -EINVAL; + + ret = bq2415x_i2c_read_mask(bq, BQ2415X_REG_CURRENT, + BQ2415X_MASK_VI_TERM, BQ2415X_SHIFT_VI_TERM); + if (ret < 0) + return ret; + return (3400 + 3400*ret) / bq->init_data.resistor_sense; +} + +/* set default value of property */ +#define bq2415x_set_default_value(bq, prop) \ + do { \ + int ret = 0; \ + if (bq->init_data.prop != -1) \ + ret = bq2415x_set_##prop(bq, bq->init_data.prop); \ + if (ret < 0) \ + return ret; \ + } while (0) + +/* set default values of all properties */ +static int bq2415x_set_defaults(struct bq2415x_device *bq) +{ + bq2415x_exec_command(bq, BQ2415X_BOOST_MODE_DISABLE); + bq2415x_exec_command(bq, BQ2415X_CHARGER_DISABLE); + bq2415x_exec_command(bq, BQ2415X_CHARGE_TERMINATION_DISABLE); + + bq2415x_set_default_value(bq, current_limit); + bq2415x_set_default_value(bq, weak_battery_voltage); + bq2415x_set_default_value(bq, battery_regulation_voltage); + + if (bq->init_data.resistor_sense > 0) { + bq2415x_set_default_value(bq, charge_current); + bq2415x_set_default_value(bq, termination_current); + bq2415x_exec_command(bq, BQ2415X_CHARGE_TERMINATION_ENABLE); + } + + bq2415x_exec_command(bq, BQ2415X_CHARGER_ENABLE); + return 0; +} + +/**** charger mode functions ****/ + +/* set charger mode */ +static int bq2415x_set_mode(struct bq2415x_device *bq, enum bq2415x_mode mode) +{ + int ret = 0; + int charger = 0; + int boost = 0; + + if (mode == BQ2415X_MODE_BOOST) + boost = 1; + else if (mode != BQ2415X_MODE_OFF) + charger = 1; + + if (!charger) + ret = bq2415x_exec_command(bq, BQ2415X_CHARGER_DISABLE); + + if (!boost) + ret = bq2415x_exec_command(bq, BQ2415X_BOOST_MODE_DISABLE); + + if (ret < 0) + return ret; + + switch (mode) { + case BQ2415X_MODE_OFF: + dev_dbg(bq->dev, "changing mode to: Offline\n"); + ret = bq2415x_set_current_limit(bq, 100); + break; + case BQ2415X_MODE_NONE: + dev_dbg(bq->dev, "changing mode to: N/A\n"); + ret = bq2415x_set_current_limit(bq, 100); + break; + case BQ2415X_MODE_HOST_CHARGER: + dev_dbg(bq->dev, "changing mode to: Host/HUB charger\n"); + ret = bq2415x_set_current_limit(bq, 500); + break; + case BQ2415X_MODE_DEDICATED_CHARGER: + dev_dbg(bq->dev, "changing mode to: Dedicated charger\n"); + ret = bq2415x_set_current_limit(bq, 1800); + break; + case BQ2415X_MODE_BOOST: /* Boost mode */ + dev_dbg(bq->dev, "changing mode to: Boost\n"); + ret = bq2415x_set_current_limit(bq, 100); + break; + } + + if (ret < 0) + return ret; + + if (charger) + ret = bq2415x_exec_command(bq, BQ2415X_CHARGER_ENABLE); + else if (boost) + ret = bq2415x_exec_command(bq, BQ2415X_BOOST_MODE_ENABLE); + + if (ret < 0) + return ret; + + bq2415x_set_default_value(bq, weak_battery_voltage); + bq2415x_set_default_value(bq, battery_regulation_voltage); + + bq->mode = mode; + sysfs_notify(&bq->charger->dev.kobj, NULL, "mode"); + + return 0; + +} + +static bool bq2415x_update_reported_mode(struct bq2415x_device *bq, int mA) +{ + enum bq2415x_mode mode; + + if (mA == 0) + mode = BQ2415X_MODE_OFF; + else if (mA < 500) + mode = BQ2415X_MODE_NONE; + else if (mA < 1800) + mode = BQ2415X_MODE_HOST_CHARGER; + else + mode = BQ2415X_MODE_DEDICATED_CHARGER; + + if (bq->reported_mode == mode) + return false; + + bq->reported_mode = mode; + return true; +} + +static int bq2415x_notifier_call(struct notifier_block *nb, + unsigned long val, void *v) +{ + struct bq2415x_device *bq = + container_of(nb, struct bq2415x_device, nb); + struct power_supply *psy = v; + union power_supply_propval prop; + int ret; + + if (val != PSY_EVENT_PROP_CHANGED) + return NOTIFY_OK; + + /* Ignore event if it was not send by notify_node/notify_device */ + if (bq->notify_node) { + if (!psy->dev.parent || + psy->dev.parent->of_node != bq->notify_node) + return NOTIFY_OK; + } else if (bq->init_data.notify_device) { + if (strcmp(psy->desc->name, bq->init_data.notify_device) != 0) + return NOTIFY_OK; + } + + dev_dbg(bq->dev, "notifier call was called\n"); + + ret = power_supply_get_property(psy, POWER_SUPPLY_PROP_CURRENT_MAX, + &prop); + if (ret != 0) + return NOTIFY_OK; + + if (!bq2415x_update_reported_mode(bq, prop.intval)) + return NOTIFY_OK; + + /* if automode is not enabled do not tell about reported_mode */ + if (bq->automode < 1) + return NOTIFY_OK; + + schedule_delayed_work(&bq->work, 0); + + return NOTIFY_OK; +} + +/**** timer functions ****/ + +/* enable/disable auto resetting chip timer */ +static void bq2415x_set_autotimer(struct bq2415x_device *bq, int state) +{ + mutex_lock(&bq2415x_timer_mutex); + + if (bq->autotimer == state) { + mutex_unlock(&bq2415x_timer_mutex); + return; + } + + bq->autotimer = state; + + if (state) { + schedule_delayed_work(&bq->work, BQ2415X_TIMER_TIMEOUT * HZ); + bq2415x_exec_command(bq, BQ2415X_TIMER_RESET); + bq->timer_error = NULL; + } else { + cancel_delayed_work_sync(&bq->work); + } + + mutex_unlock(&bq2415x_timer_mutex); +} + +/* called by bq2415x_timer_work on timer error */ +static void bq2415x_timer_error(struct bq2415x_device *bq, const char *msg) +{ + bq->timer_error = msg; + sysfs_notify(&bq->charger->dev.kobj, NULL, "timer"); + dev_err(bq->dev, "%s\n", msg); + if (bq->automode > 0) + bq->automode = 0; + bq2415x_set_mode(bq, BQ2415X_MODE_OFF); + bq2415x_set_autotimer(bq, 0); +} + +/* delayed work function for auto resetting chip timer */ +static void bq2415x_timer_work(struct work_struct *work) +{ + struct bq2415x_device *bq = container_of(work, struct bq2415x_device, + work.work); + int ret; + int error; + int boost; + + if (bq->automode > 0 && (bq->reported_mode != bq->mode)) { + sysfs_notify(&bq->charger->dev.kobj, NULL, "reported_mode"); + bq2415x_set_mode(bq, bq->reported_mode); + } + + if (!bq->autotimer) + return; + + ret = bq2415x_exec_command(bq, BQ2415X_TIMER_RESET); + if (ret < 0) { + bq2415x_timer_error(bq, "Resetting timer failed"); + return; + } + + boost = bq2415x_exec_command(bq, BQ2415X_BOOST_MODE_STATUS); + if (boost < 0) { + bq2415x_timer_error(bq, "Unknown error"); + return; + } + + error = bq2415x_exec_command(bq, BQ2415X_FAULT_STATUS); + if (error < 0) { + bq2415x_timer_error(bq, "Unknown error"); + return; + } + + if (boost) { + switch (error) { + /* Non fatal errors, chip is OK */ + case 0: /* No error */ + break; + case 6: /* Timer expired */ + dev_err(bq->dev, "Timer expired\n"); + break; + case 3: /* Battery voltage too low */ + dev_err(bq->dev, "Battery voltage to low\n"); + break; + + /* Fatal errors, disable and reset chip */ + case 1: /* Overvoltage protection (chip fried) */ + bq2415x_timer_error(bq, + "Overvoltage protection (chip fried)"); + return; + case 2: /* Overload */ + bq2415x_timer_error(bq, "Overload"); + return; + case 4: /* Battery overvoltage protection */ + bq2415x_timer_error(bq, + "Battery overvoltage protection"); + return; + case 5: /* Thermal shutdown (too hot) */ + bq2415x_timer_error(bq, + "Thermal shutdown (too hot)"); + return; + case 7: /* N/A */ + bq2415x_timer_error(bq, "Unknown error"); + return; + } + } else { + switch (error) { + /* Non fatal errors, chip is OK */ + case 0: /* No error */ + break; + case 2: /* Sleep mode */ + dev_err(bq->dev, "Sleep mode\n"); + break; + case 3: /* Poor input source */ + dev_err(bq->dev, "Poor input source\n"); + break; + case 6: /* Timer expired */ + dev_err(bq->dev, "Timer expired\n"); + break; + case 7: /* No battery */ + dev_err(bq->dev, "No battery\n"); + break; + + /* Fatal errors, disable and reset chip */ + case 1: /* Overvoltage protection (chip fried) */ + bq2415x_timer_error(bq, + "Overvoltage protection (chip fried)"); + return; + case 4: /* Battery overvoltage protection */ + bq2415x_timer_error(bq, + "Battery overvoltage protection"); + return; + case 5: /* Thermal shutdown (too hot) */ + bq2415x_timer_error(bq, + "Thermal shutdown (too hot)"); + return; + } + } + + schedule_delayed_work(&bq->work, BQ2415X_TIMER_TIMEOUT * HZ); +} + +/**** power supply interface code ****/ + +static enum power_supply_property bq2415x_power_supply_props[] = { + /* TODO: maybe add more power supply properties */ + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_MODEL_NAME, +}; + +static int bq2415x_power_supply_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct bq2415x_device *bq = power_supply_get_drvdata(psy); + int ret; + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + ret = bq2415x_exec_command(bq, BQ2415X_CHARGE_STATUS); + if (ret < 0) + return ret; + else if (ret == 0) /* Ready */ + val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; + else if (ret == 1) /* Charge in progress */ + val->intval = POWER_SUPPLY_STATUS_CHARGING; + else if (ret == 2) /* Charge done */ + val->intval = POWER_SUPPLY_STATUS_FULL; + else + val->intval = POWER_SUPPLY_STATUS_UNKNOWN; + break; + case POWER_SUPPLY_PROP_MODEL_NAME: + val->strval = bq->model; + break; + default: + return -EINVAL; + } + return 0; +} + +static int bq2415x_power_supply_init(struct bq2415x_device *bq) +{ + int ret; + int chip; + char revstr[8]; + struct power_supply_config psy_cfg = { .drv_data = bq, }; + + bq->charger_desc.name = bq->name; + bq->charger_desc.type = POWER_SUPPLY_TYPE_USB; + bq->charger_desc.properties = bq2415x_power_supply_props; + bq->charger_desc.num_properties = + ARRAY_SIZE(bq2415x_power_supply_props); + bq->charger_desc.get_property = bq2415x_power_supply_get_property; + + ret = bq2415x_detect_chip(bq); + if (ret < 0) + chip = BQUNKNOWN; + else + chip = ret; + + ret = bq2415x_detect_revision(bq); + if (ret < 0) + strcpy(revstr, "unknown"); + else + sprintf(revstr, "1.%d", ret); + + bq->model = kasprintf(GFP_KERNEL, + "chip %s, revision %s, vender code %.3d", + bq2415x_chip_name[chip], revstr, + bq2415x_get_vender_code(bq)); + if (!bq->model) { + dev_err(bq->dev, "failed to allocate model name\n"); + return -ENOMEM; + } + + bq->charger = power_supply_register(bq->dev, &bq->charger_desc, + &psy_cfg); + if (IS_ERR(bq->charger)) { + kfree(bq->model); + return PTR_ERR(bq->charger); + } + + return 0; +} + +static void bq2415x_power_supply_exit(struct bq2415x_device *bq) +{ + bq->autotimer = 0; + if (bq->automode > 0) + bq->automode = 0; + cancel_delayed_work_sync(&bq->work); + power_supply_unregister(bq->charger); + kfree(bq->model); +} + +/**** additional sysfs entries for power supply interface ****/ + +/* show *_status entries */ +static ssize_t bq2415x_sysfs_show_status(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct power_supply *psy = dev_get_drvdata(dev); + struct bq2415x_device *bq = power_supply_get_drvdata(psy); + enum bq2415x_command command; + int ret; + + if (strcmp(attr->attr.name, "otg_status") == 0) + command = BQ2415X_OTG_STATUS; + else if (strcmp(attr->attr.name, "charge_status") == 0) + command = BQ2415X_CHARGE_STATUS; + else if (strcmp(attr->attr.name, "boost_status") == 0) + command = BQ2415X_BOOST_STATUS; + else if (strcmp(attr->attr.name, "fault_status") == 0) + command = BQ2415X_FAULT_STATUS; + else + return -EINVAL; + + ret = bq2415x_exec_command(bq, command); + if (ret < 0) + return ret; + return sprintf(buf, "%d\n", ret); +} + +/* + * set timer entry: + * auto - enable auto mode + * off - disable auto mode + * (other values) - reset chip timer + */ +static ssize_t bq2415x_sysfs_set_timer(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t count) +{ + struct power_supply *psy = dev_get_drvdata(dev); + struct bq2415x_device *bq = power_supply_get_drvdata(psy); + int ret = 0; + + if (strncmp(buf, "auto", 4) == 0) + bq2415x_set_autotimer(bq, 1); + else if (strncmp(buf, "off", 3) == 0) + bq2415x_set_autotimer(bq, 0); + else + ret = bq2415x_exec_command(bq, BQ2415X_TIMER_RESET); + + if (ret < 0) + return ret; + return count; +} + +/* show timer entry (auto or off) */ +static ssize_t bq2415x_sysfs_show_timer(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct power_supply *psy = dev_get_drvdata(dev); + struct bq2415x_device *bq = power_supply_get_drvdata(psy); + + if (bq->timer_error) + return sprintf(buf, "%s\n", bq->timer_error); + + if (bq->autotimer) + return sprintf(buf, "auto\n"); + return sprintf(buf, "off\n"); +} + +/* + * set mode entry: + * auto - if automode is supported, enable it and set mode to reported + * none - disable charger and boost mode + * host - charging mode for host/hub chargers (current limit 500mA) + * dedicated - charging mode for dedicated chargers (unlimited current limit) + * boost - disable charger and enable boost mode + */ +static ssize_t bq2415x_sysfs_set_mode(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t count) +{ + struct power_supply *psy = dev_get_drvdata(dev); + struct bq2415x_device *bq = power_supply_get_drvdata(psy); + enum bq2415x_mode mode; + int ret = 0; + + if (strncmp(buf, "auto", 4) == 0) { + if (bq->automode < 0) + return -EINVAL; + bq->automode = 1; + mode = bq->reported_mode; + } else if (strncmp(buf, "off", 3) == 0) { + if (bq->automode > 0) + bq->automode = 0; + mode = BQ2415X_MODE_OFF; + } else if (strncmp(buf, "none", 4) == 0) { + if (bq->automode > 0) + bq->automode = 0; + mode = BQ2415X_MODE_NONE; + } else if (strncmp(buf, "host", 4) == 0) { + if (bq->automode > 0) + bq->automode = 0; + mode = BQ2415X_MODE_HOST_CHARGER; + } else if (strncmp(buf, "dedicated", 9) == 0) { + if (bq->automode > 0) + bq->automode = 0; + mode = BQ2415X_MODE_DEDICATED_CHARGER; + } else if (strncmp(buf, "boost", 5) == 0) { + if (bq->automode > 0) + bq->automode = 0; + mode = BQ2415X_MODE_BOOST; + } else if (strncmp(buf, "reset", 5) == 0) { + bq2415x_reset_chip(bq); + bq2415x_set_defaults(bq); + if (bq->automode <= 0) + return count; + bq->automode = 1; + mode = bq->reported_mode; + } else { + return -EINVAL; + } + + ret = bq2415x_set_mode(bq, mode); + if (ret < 0) + return ret; + return count; +} + +/* show mode entry (auto, none, host, dedicated or boost) */ +static ssize_t bq2415x_sysfs_show_mode(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct power_supply *psy = dev_get_drvdata(dev); + struct bq2415x_device *bq = power_supply_get_drvdata(psy); + ssize_t ret = 0; + + if (bq->automode > 0) + ret += sprintf(buf+ret, "auto ("); + + switch (bq->mode) { + case BQ2415X_MODE_OFF: + ret += sprintf(buf+ret, "off"); + break; + case BQ2415X_MODE_NONE: + ret += sprintf(buf+ret, "none"); + break; + case BQ2415X_MODE_HOST_CHARGER: + ret += sprintf(buf+ret, "host"); + break; + case BQ2415X_MODE_DEDICATED_CHARGER: + ret += sprintf(buf+ret, "dedicated"); + break; + case BQ2415X_MODE_BOOST: + ret += sprintf(buf+ret, "boost"); + break; + } + + if (bq->automode > 0) + ret += sprintf(buf+ret, ")"); + + ret += sprintf(buf+ret, "\n"); + return ret; +} + +/* show reported_mode entry (none, host, dedicated or boost) */ +static ssize_t bq2415x_sysfs_show_reported_mode(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct power_supply *psy = dev_get_drvdata(dev); + struct bq2415x_device *bq = power_supply_get_drvdata(psy); + + if (bq->automode < 0) + return -EINVAL; + + switch (bq->reported_mode) { + case BQ2415X_MODE_OFF: + return sprintf(buf, "off\n"); + case BQ2415X_MODE_NONE: + return sprintf(buf, "none\n"); + case BQ2415X_MODE_HOST_CHARGER: + return sprintf(buf, "host\n"); + case BQ2415X_MODE_DEDICATED_CHARGER: + return sprintf(buf, "dedicated\n"); + case BQ2415X_MODE_BOOST: + return sprintf(buf, "boost\n"); + } + + return -EINVAL; +} + +/* directly set raw value to chip register, format: 'register value' */ +static ssize_t bq2415x_sysfs_set_registers(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t count) +{ + struct power_supply *psy = dev_get_drvdata(dev); + struct bq2415x_device *bq = power_supply_get_drvdata(psy); + ssize_t ret = 0; + unsigned int reg; + unsigned int val; + + if (sscanf(buf, "%x %x", ®, &val) != 2) + return -EINVAL; + + if (reg > 4 || val > 255) + return -EINVAL; + + ret = bq2415x_i2c_write(bq, reg, val); + if (ret < 0) + return ret; + return count; +} + +/* print value of chip register, format: 'register=value' */ +static ssize_t bq2415x_sysfs_print_reg(struct bq2415x_device *bq, + u8 reg, + char *buf) +{ + int ret = bq2415x_i2c_read(bq, reg); + + if (ret < 0) + return sprintf(buf, "%#.2x=error %d\n", reg, ret); + return sprintf(buf, "%#.2x=%#.2x\n", reg, ret); +} + +/* show all raw values of chip register, format per line: 'register=value' */ +static ssize_t bq2415x_sysfs_show_registers(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct power_supply *psy = dev_get_drvdata(dev); + struct bq2415x_device *bq = power_supply_get_drvdata(psy); + ssize_t ret = 0; + + ret += bq2415x_sysfs_print_reg(bq, BQ2415X_REG_STATUS, buf+ret); + ret += bq2415x_sysfs_print_reg(bq, BQ2415X_REG_CONTROL, buf+ret); + ret += bq2415x_sysfs_print_reg(bq, BQ2415X_REG_VOLTAGE, buf+ret); + ret += bq2415x_sysfs_print_reg(bq, BQ2415X_REG_VENDER, buf+ret); + ret += bq2415x_sysfs_print_reg(bq, BQ2415X_REG_CURRENT, buf+ret); + return ret; +} + +/* set current and voltage limit entries (in mA or mV) */ +static ssize_t bq2415x_sysfs_set_limit(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t count) +{ + struct power_supply *psy = dev_get_drvdata(dev); + struct bq2415x_device *bq = power_supply_get_drvdata(psy); + long val; + int ret; + + if (kstrtol(buf, 10, &val) < 0) + return -EINVAL; + + if (strcmp(attr->attr.name, "current_limit") == 0) + ret = bq2415x_set_current_limit(bq, val); + else if (strcmp(attr->attr.name, "weak_battery_voltage") == 0) + ret = bq2415x_set_weak_battery_voltage(bq, val); + else if (strcmp(attr->attr.name, "battery_regulation_voltage") == 0) + ret = bq2415x_set_battery_regulation_voltage(bq, val); + else if (strcmp(attr->attr.name, "charge_current") == 0) + ret = bq2415x_set_charge_current(bq, val); + else if (strcmp(attr->attr.name, "termination_current") == 0) + ret = bq2415x_set_termination_current(bq, val); + else + return -EINVAL; + + if (ret < 0) + return ret; + return count; +} + +/* show current and voltage limit entries (in mA or mV) */ +static ssize_t bq2415x_sysfs_show_limit(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct power_supply *psy = dev_get_drvdata(dev); + struct bq2415x_device *bq = power_supply_get_drvdata(psy); + int ret; + + if (strcmp(attr->attr.name, "current_limit") == 0) + ret = bq2415x_get_current_limit(bq); + else if (strcmp(attr->attr.name, "weak_battery_voltage") == 0) + ret = bq2415x_get_weak_battery_voltage(bq); + else if (strcmp(attr->attr.name, "battery_regulation_voltage") == 0) + ret = bq2415x_get_battery_regulation_voltage(bq); + else if (strcmp(attr->attr.name, "charge_current") == 0) + ret = bq2415x_get_charge_current(bq); + else if (strcmp(attr->attr.name, "termination_current") == 0) + ret = bq2415x_get_termination_current(bq); + else + return -EINVAL; + + if (ret < 0) + return ret; + return sprintf(buf, "%d\n", ret); +} + +/* set *_enable entries */ +static ssize_t bq2415x_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 bq2415x_device *bq = power_supply_get_drvdata(psy); + enum bq2415x_command command; + long val; + int ret; + + if (kstrtol(buf, 10, &val) < 0) + return -EINVAL; + + if (strcmp(attr->attr.name, "charge_termination_enable") == 0) + command = val ? BQ2415X_CHARGE_TERMINATION_ENABLE : + BQ2415X_CHARGE_TERMINATION_DISABLE; + else if (strcmp(attr->attr.name, "high_impedance_enable") == 0) + command = val ? BQ2415X_HIGH_IMPEDANCE_ENABLE : + BQ2415X_HIGH_IMPEDANCE_DISABLE; + else if (strcmp(attr->attr.name, "otg_pin_enable") == 0) + command = val ? BQ2415X_OTG_PIN_ENABLE : + BQ2415X_OTG_PIN_DISABLE; + else if (strcmp(attr->attr.name, "stat_pin_enable") == 0) + command = val ? BQ2415X_STAT_PIN_ENABLE : + BQ2415X_STAT_PIN_DISABLE; + else + return -EINVAL; + + ret = bq2415x_exec_command(bq, command); + if (ret < 0) + return ret; + return count; +} + +/* show *_enable entries */ +static ssize_t bq2415x_sysfs_show_enable(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct power_supply *psy = dev_get_drvdata(dev); + struct bq2415x_device *bq = power_supply_get_drvdata(psy); + enum bq2415x_command command; + int ret; + + if (strcmp(attr->attr.name, "charge_termination_enable") == 0) + command = BQ2415X_CHARGE_TERMINATION_STATUS; + else if (strcmp(attr->attr.name, "high_impedance_enable") == 0) + command = BQ2415X_HIGH_IMPEDANCE_STATUS; + else if (strcmp(attr->attr.name, "otg_pin_enable") == 0) + command = BQ2415X_OTG_PIN_STATUS; + else if (strcmp(attr->attr.name, "stat_pin_enable") == 0) + command = BQ2415X_STAT_PIN_STATUS; + else + return -EINVAL; + + ret = bq2415x_exec_command(bq, command); + if (ret < 0) + return ret; + return sprintf(buf, "%d\n", ret); +} + +static DEVICE_ATTR(current_limit, S_IWUSR | S_IRUGO, + bq2415x_sysfs_show_limit, bq2415x_sysfs_set_limit); +static DEVICE_ATTR(weak_battery_voltage, S_IWUSR | S_IRUGO, + bq2415x_sysfs_show_limit, bq2415x_sysfs_set_limit); +static DEVICE_ATTR(battery_regulation_voltage, S_IWUSR | S_IRUGO, + bq2415x_sysfs_show_limit, bq2415x_sysfs_set_limit); +static DEVICE_ATTR(charge_current, S_IWUSR | S_IRUGO, + bq2415x_sysfs_show_limit, bq2415x_sysfs_set_limit); +static DEVICE_ATTR(termination_current, S_IWUSR | S_IRUGO, + bq2415x_sysfs_show_limit, bq2415x_sysfs_set_limit); + +static DEVICE_ATTR(charge_termination_enable, S_IWUSR | S_IRUGO, + bq2415x_sysfs_show_enable, bq2415x_sysfs_set_enable); +static DEVICE_ATTR(high_impedance_enable, S_IWUSR | S_IRUGO, + bq2415x_sysfs_show_enable, bq2415x_sysfs_set_enable); +static DEVICE_ATTR(otg_pin_enable, S_IWUSR | S_IRUGO, + bq2415x_sysfs_show_enable, bq2415x_sysfs_set_enable); +static DEVICE_ATTR(stat_pin_enable, S_IWUSR | S_IRUGO, + bq2415x_sysfs_show_enable, bq2415x_sysfs_set_enable); + +static DEVICE_ATTR(reported_mode, S_IRUGO, + bq2415x_sysfs_show_reported_mode, NULL); +static DEVICE_ATTR(mode, S_IWUSR | S_IRUGO, + bq2415x_sysfs_show_mode, bq2415x_sysfs_set_mode); +static DEVICE_ATTR(timer, S_IWUSR | S_IRUGO, + bq2415x_sysfs_show_timer, bq2415x_sysfs_set_timer); + +static DEVICE_ATTR(registers, S_IWUSR | S_IRUGO, + bq2415x_sysfs_show_registers, bq2415x_sysfs_set_registers); + +static DEVICE_ATTR(otg_status, S_IRUGO, bq2415x_sysfs_show_status, NULL); +static DEVICE_ATTR(charge_status, S_IRUGO, bq2415x_sysfs_show_status, NULL); +static DEVICE_ATTR(boost_status, S_IRUGO, bq2415x_sysfs_show_status, NULL); +static DEVICE_ATTR(fault_status, S_IRUGO, bq2415x_sysfs_show_status, NULL); + +static struct attribute *bq2415x_sysfs_attributes[] = { + /* + * TODO: some (appropriate) of these attrs should be switched to + * use power supply class props. + */ + &dev_attr_current_limit.attr, + &dev_attr_weak_battery_voltage.attr, + &dev_attr_battery_regulation_voltage.attr, + &dev_attr_charge_current.attr, + &dev_attr_termination_current.attr, + + &dev_attr_charge_termination_enable.attr, + &dev_attr_high_impedance_enable.attr, + &dev_attr_otg_pin_enable.attr, + &dev_attr_stat_pin_enable.attr, + + &dev_attr_reported_mode.attr, + &dev_attr_mode.attr, + &dev_attr_timer.attr, + + &dev_attr_registers.attr, + + &dev_attr_otg_status.attr, + &dev_attr_charge_status.attr, + &dev_attr_boost_status.attr, + &dev_attr_fault_status.attr, + NULL, +}; + +static const struct attribute_group bq2415x_sysfs_attr_group = { + .attrs = bq2415x_sysfs_attributes, +}; + +static int bq2415x_sysfs_init(struct bq2415x_device *bq) +{ + return sysfs_create_group(&bq->charger->dev.kobj, + &bq2415x_sysfs_attr_group); +} + +static void bq2415x_sysfs_exit(struct bq2415x_device *bq) +{ + sysfs_remove_group(&bq->charger->dev.kobj, &bq2415x_sysfs_attr_group); +} + +/* main bq2415x probe function */ +static int bq2415x_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int ret; + int num; + char *name = NULL; + struct bq2415x_device *bq; + struct device_node *np = client->dev.of_node; + struct bq2415x_platform_data *pdata = client->dev.platform_data; + const struct acpi_device_id *acpi_id = NULL; + struct power_supply *notify_psy = NULL; + union power_supply_propval prop; + + if (!np && !pdata && !ACPI_HANDLE(&client->dev)) { + dev_err(&client->dev, "Neither devicetree, nor platform data, nor ACPI support\n"); + return -ENODEV; + } + + /* Get new ID for the new device */ + mutex_lock(&bq2415x_id_mutex); + num = idr_alloc(&bq2415x_id, client, 0, 0, GFP_KERNEL); + mutex_unlock(&bq2415x_id_mutex); + if (num < 0) + return num; + + if (id) { + name = kasprintf(GFP_KERNEL, "%s-%d", id->name, num); + } else if (ACPI_HANDLE(&client->dev)) { + acpi_id = + acpi_match_device(client->dev.driver->acpi_match_table, + &client->dev); + name = kasprintf(GFP_KERNEL, "%s-%d", acpi_id->id, num); + } + if (!name) { + dev_err(&client->dev, "failed to allocate device name\n"); + ret = -ENOMEM; + goto error_1; + } + + bq = devm_kzalloc(&client->dev, sizeof(*bq), GFP_KERNEL); + if (!bq) { + ret = -ENOMEM; + goto error_2; + } + + i2c_set_clientdata(client, bq); + + bq->id = num; + bq->dev = &client->dev; + if (id) + bq->chip = id->driver_data; + else if (ACPI_HANDLE(bq->dev)) + bq->chip = acpi_id->driver_data; + bq->name = name; + bq->mode = BQ2415X_MODE_OFF; + bq->reported_mode = BQ2415X_MODE_OFF; + bq->autotimer = 0; + bq->automode = 0; + + if (np || ACPI_HANDLE(bq->dev)) { + ret = device_property_read_u32(bq->dev, + "ti,current-limit", + &bq->init_data.current_limit); + if (ret) + goto error_2; + ret = device_property_read_u32(bq->dev, + "ti,weak-battery-voltage", + &bq->init_data.weak_battery_voltage); + if (ret) + goto error_2; + ret = device_property_read_u32(bq->dev, + "ti,battery-regulation-voltage", + &bq->init_data.battery_regulation_voltage); + if (ret) + goto error_2; + ret = device_property_read_u32(bq->dev, + "ti,charge-current", + &bq->init_data.charge_current); + if (ret) + goto error_2; + ret = device_property_read_u32(bq->dev, + "ti,termination-current", + &bq->init_data.termination_current); + if (ret) + goto error_2; + ret = device_property_read_u32(bq->dev, + "ti,resistor-sense", + &bq->init_data.resistor_sense); + if (ret) + goto error_2; + if (np) + bq->notify_node = of_parse_phandle(np, + "ti,usb-charger-detection", 0); + } else { + memcpy(&bq->init_data, pdata, sizeof(bq->init_data)); + } + + bq2415x_reset_chip(bq); + + ret = bq2415x_power_supply_init(bq); + if (ret) { + dev_err(bq->dev, "failed to register power supply: %d\n", ret); + goto error_2; + } + + ret = bq2415x_sysfs_init(bq); + if (ret) { + dev_err(bq->dev, "failed to create sysfs entries: %d\n", ret); + goto error_3; + } + + ret = bq2415x_set_defaults(bq); + if (ret) { + dev_err(bq->dev, "failed to set default values: %d\n", ret); + goto error_4; + } + + if (bq->notify_node || bq->init_data.notify_device) { + bq->nb.notifier_call = bq2415x_notifier_call; + ret = power_supply_reg_notifier(&bq->nb); + if (ret) { + dev_err(bq->dev, "failed to reg notifier: %d\n", ret); + goto error_4; + } + + bq->automode = 1; + dev_info(bq->dev, "automode supported, waiting for events\n"); + } else { + bq->automode = -1; + dev_info(bq->dev, "automode not supported\n"); + } + + /* Query for initial reported_mode and set it */ + if (bq->nb.notifier_call) { + if (np) { + notify_psy = power_supply_get_by_phandle(np, + "ti,usb-charger-detection"); + if (IS_ERR(notify_psy)) + notify_psy = NULL; + } else if (bq->init_data.notify_device) { + notify_psy = power_supply_get_by_name( + bq->init_data.notify_device); + } + } + if (notify_psy) { + ret = power_supply_get_property(notify_psy, + POWER_SUPPLY_PROP_CURRENT_MAX, &prop); + power_supply_put(notify_psy); + + if (ret == 0) { + bq2415x_update_reported_mode(bq, prop.intval); + bq2415x_set_mode(bq, bq->reported_mode); + } + } + + INIT_DELAYED_WORK(&bq->work, bq2415x_timer_work); + bq2415x_set_autotimer(bq, 1); + + dev_info(bq->dev, "driver registered\n"); + return 0; + +error_4: + bq2415x_sysfs_exit(bq); +error_3: + bq2415x_power_supply_exit(bq); +error_2: + if (bq) + of_node_put(bq->notify_node); + kfree(name); +error_1: + mutex_lock(&bq2415x_id_mutex); + idr_remove(&bq2415x_id, num); + mutex_unlock(&bq2415x_id_mutex); + + return ret; +} + +/* main bq2415x remove function */ + +static int bq2415x_remove(struct i2c_client *client) +{ + struct bq2415x_device *bq = i2c_get_clientdata(client); + + if (bq->nb.notifier_call) + power_supply_unreg_notifier(&bq->nb); + + of_node_put(bq->notify_node); + bq2415x_sysfs_exit(bq); + bq2415x_power_supply_exit(bq); + + bq2415x_reset_chip(bq); + + mutex_lock(&bq2415x_id_mutex); + idr_remove(&bq2415x_id, bq->id); + mutex_unlock(&bq2415x_id_mutex); + + dev_info(bq->dev, "driver unregistered\n"); + + kfree(bq->name); + + return 0; +} + +static const struct i2c_device_id bq2415x_i2c_id_table[] = { + { "bq2415x", BQUNKNOWN }, + { "bq24150", BQ24150 }, + { "bq24150a", BQ24150A }, + { "bq24151", BQ24151 }, + { "bq24151a", BQ24151A }, + { "bq24152", BQ24152 }, + { "bq24153", BQ24153 }, + { "bq24153a", BQ24153A }, + { "bq24155", BQ24155 }, + { "bq24156", BQ24156 }, + { "bq24156a", BQ24156A }, + { "bq24157s", BQ24157S }, + { "bq24158", BQ24158 }, + {}, +}; +MODULE_DEVICE_TABLE(i2c, bq2415x_i2c_id_table); + +#ifdef CONFIG_ACPI +static const struct acpi_device_id bq2415x_i2c_acpi_match[] = { + { "BQ2415X", BQUNKNOWN }, + { "BQ241500", BQ24150 }, + { "BQA24150", BQ24150A }, + { "BQ241510", BQ24151 }, + { "BQA24151", BQ24151A }, + { "BQ241520", BQ24152 }, + { "BQ241530", BQ24153 }, + { "BQA24153", BQ24153A }, + { "BQ241550", BQ24155 }, + { "BQ241560", BQ24156 }, + { "BQA24156", BQ24156A }, + { "BQS24157", BQ24157S }, + { "BQ241580", BQ24158 }, + {}, +}; +MODULE_DEVICE_TABLE(acpi, bq2415x_i2c_acpi_match); +#endif + +#ifdef CONFIG_OF +static const struct of_device_id bq2415x_of_match_table[] = { + { .compatible = "ti,bq24150" }, + { .compatible = "ti,bq24150a" }, + { .compatible = "ti,bq24151" }, + { .compatible = "ti,bq24151a" }, + { .compatible = "ti,bq24152" }, + { .compatible = "ti,bq24153" }, + { .compatible = "ti,bq24153a" }, + { .compatible = "ti,bq24155" }, + { .compatible = "ti,bq24156" }, + { .compatible = "ti,bq24156a" }, + { .compatible = "ti,bq24157s" }, + { .compatible = "ti,bq24158" }, + {}, +}; +MODULE_DEVICE_TABLE(of, bq2415x_of_match_table); +#endif + +static struct i2c_driver bq2415x_driver = { + .driver = { + .name = "bq2415x-charger", + .of_match_table = of_match_ptr(bq2415x_of_match_table), + .acpi_match_table = ACPI_PTR(bq2415x_i2c_acpi_match), + }, + .probe = bq2415x_probe, + .remove = bq2415x_remove, + .id_table = bq2415x_i2c_id_table, +}; +module_i2c_driver(bq2415x_driver); + +MODULE_AUTHOR("Pali Rohár "); +MODULE_DESCRIPTION("bq2415x charger driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/power/supply/bq24190_charger.c b/drivers/power/supply/bq24190_charger.c new file mode 100644 index 000000000000..f5746b9f4e83 --- /dev/null +++ b/drivers/power/supply/bq24190_charger.c @@ -0,0 +1,1546 @@ +/* + * Driver for the TI bq24190 battery charger. + * + * Author: Mark A. Greer + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + + +#define BQ24190_MANUFACTURER "Texas Instruments" + +#define BQ24190_REG_ISC 0x00 /* Input Source Control */ +#define BQ24190_REG_ISC_EN_HIZ_MASK BIT(7) +#define BQ24190_REG_ISC_EN_HIZ_SHIFT 7 +#define BQ24190_REG_ISC_VINDPM_MASK (BIT(6) | BIT(5) | BIT(4) | \ + BIT(3)) +#define BQ24190_REG_ISC_VINDPM_SHIFT 3 +#define BQ24190_REG_ISC_IINLIM_MASK (BIT(2) | BIT(1) | BIT(0)) +#define BQ24190_REG_ISC_IINLIM_SHIFT 0 + +#define BQ24190_REG_POC 0x01 /* Power-On Configuration */ +#define BQ24190_REG_POC_RESET_MASK BIT(7) +#define BQ24190_REG_POC_RESET_SHIFT 7 +#define BQ24190_REG_POC_WDT_RESET_MASK BIT(6) +#define BQ24190_REG_POC_WDT_RESET_SHIFT 6 +#define BQ24190_REG_POC_CHG_CONFIG_MASK (BIT(5) | BIT(4)) +#define BQ24190_REG_POC_CHG_CONFIG_SHIFT 4 +#define BQ24190_REG_POC_SYS_MIN_MASK (BIT(3) | BIT(2) | BIT(1)) +#define BQ24190_REG_POC_SYS_MIN_SHIFT 1 +#define BQ24190_REG_POC_BOOST_LIM_MASK BIT(0) +#define BQ24190_REG_POC_BOOST_LIM_SHIFT 0 + +#define BQ24190_REG_CCC 0x02 /* Charge Current Control */ +#define BQ24190_REG_CCC_ICHG_MASK (BIT(7) | BIT(6) | BIT(5) | \ + BIT(4) | BIT(3) | BIT(2)) +#define BQ24190_REG_CCC_ICHG_SHIFT 2 +#define BQ24190_REG_CCC_FORCE_20PCT_MASK BIT(0) +#define BQ24190_REG_CCC_FORCE_20PCT_SHIFT 0 + +#define BQ24190_REG_PCTCC 0x03 /* Pre-charge/Termination Current Cntl */ +#define BQ24190_REG_PCTCC_IPRECHG_MASK (BIT(7) | BIT(6) | BIT(5) | \ + BIT(4)) +#define BQ24190_REG_PCTCC_IPRECHG_SHIFT 4 +#define BQ24190_REG_PCTCC_ITERM_MASK (BIT(3) | BIT(2) | BIT(1) | \ + BIT(0)) +#define BQ24190_REG_PCTCC_ITERM_SHIFT 0 + +#define BQ24190_REG_CVC 0x04 /* Charge Voltage Control */ +#define BQ24190_REG_CVC_VREG_MASK (BIT(7) | BIT(6) | BIT(5) | \ + BIT(4) | BIT(3) | BIT(2)) +#define BQ24190_REG_CVC_VREG_SHIFT 2 +#define BQ24190_REG_CVC_BATLOWV_MASK BIT(1) +#define BQ24190_REG_CVC_BATLOWV_SHIFT 1 +#define BQ24190_REG_CVC_VRECHG_MASK BIT(0) +#define BQ24190_REG_CVC_VRECHG_SHIFT 0 + +#define BQ24190_REG_CTTC 0x05 /* Charge Term/Timer Control */ +#define BQ24190_REG_CTTC_EN_TERM_MASK BIT(7) +#define BQ24190_REG_CTTC_EN_TERM_SHIFT 7 +#define BQ24190_REG_CTTC_TERM_STAT_MASK BIT(6) +#define BQ24190_REG_CTTC_TERM_STAT_SHIFT 6 +#define BQ24190_REG_CTTC_WATCHDOG_MASK (BIT(5) | BIT(4)) +#define BQ24190_REG_CTTC_WATCHDOG_SHIFT 4 +#define BQ24190_REG_CTTC_EN_TIMER_MASK BIT(3) +#define BQ24190_REG_CTTC_EN_TIMER_SHIFT 3 +#define BQ24190_REG_CTTC_CHG_TIMER_MASK (BIT(2) | BIT(1)) +#define BQ24190_REG_CTTC_CHG_TIMER_SHIFT 1 +#define BQ24190_REG_CTTC_JEITA_ISET_MASK BIT(0) +#define BQ24190_REG_CTTC_JEITA_ISET_SHIFT 0 + +#define BQ24190_REG_ICTRC 0x06 /* IR Comp/Thermal Regulation Control */ +#define BQ24190_REG_ICTRC_BAT_COMP_MASK (BIT(7) | BIT(6) | BIT(5)) +#define BQ24190_REG_ICTRC_BAT_COMP_SHIFT 5 +#define BQ24190_REG_ICTRC_VCLAMP_MASK (BIT(4) | BIT(3) | BIT(2)) +#define BQ24190_REG_ICTRC_VCLAMP_SHIFT 2 +#define BQ24190_REG_ICTRC_TREG_MASK (BIT(1) | BIT(0)) +#define BQ24190_REG_ICTRC_TREG_SHIFT 0 + +#define BQ24190_REG_MOC 0x07 /* Misc. Operation Control */ +#define BQ24190_REG_MOC_DPDM_EN_MASK BIT(7) +#define BQ24190_REG_MOC_DPDM_EN_SHIFT 7 +#define BQ24190_REG_MOC_TMR2X_EN_MASK BIT(6) +#define BQ24190_REG_MOC_TMR2X_EN_SHIFT 6 +#define BQ24190_REG_MOC_BATFET_DISABLE_MASK BIT(5) +#define BQ24190_REG_MOC_BATFET_DISABLE_SHIFT 5 +#define BQ24190_REG_MOC_JEITA_VSET_MASK BIT(4) +#define BQ24190_REG_MOC_JEITA_VSET_SHIFT 4 +#define BQ24190_REG_MOC_INT_MASK_MASK (BIT(1) | BIT(0)) +#define BQ24190_REG_MOC_INT_MASK_SHIFT 0 + +#define BQ24190_REG_SS 0x08 /* System Status */ +#define BQ24190_REG_SS_VBUS_STAT_MASK (BIT(7) | BIT(6)) +#define BQ24190_REG_SS_VBUS_STAT_SHIFT 6 +#define BQ24190_REG_SS_CHRG_STAT_MASK (BIT(5) | BIT(4)) +#define BQ24190_REG_SS_CHRG_STAT_SHIFT 4 +#define BQ24190_REG_SS_DPM_STAT_MASK BIT(3) +#define BQ24190_REG_SS_DPM_STAT_SHIFT 3 +#define BQ24190_REG_SS_PG_STAT_MASK BIT(2) +#define BQ24190_REG_SS_PG_STAT_SHIFT 2 +#define BQ24190_REG_SS_THERM_STAT_MASK BIT(1) +#define BQ24190_REG_SS_THERM_STAT_SHIFT 1 +#define BQ24190_REG_SS_VSYS_STAT_MASK BIT(0) +#define BQ24190_REG_SS_VSYS_STAT_SHIFT 0 + +#define BQ24190_REG_F 0x09 /* Fault */ +#define BQ24190_REG_F_WATCHDOG_FAULT_MASK BIT(7) +#define BQ24190_REG_F_WATCHDOG_FAULT_SHIFT 7 +#define BQ24190_REG_F_BOOST_FAULT_MASK BIT(6) +#define BQ24190_REG_F_BOOST_FAULT_SHIFT 6 +#define BQ24190_REG_F_CHRG_FAULT_MASK (BIT(5) | BIT(4)) +#define BQ24190_REG_F_CHRG_FAULT_SHIFT 4 +#define BQ24190_REG_F_BAT_FAULT_MASK BIT(3) +#define BQ24190_REG_F_BAT_FAULT_SHIFT 3 +#define BQ24190_REG_F_NTC_FAULT_MASK (BIT(2) | BIT(1) | BIT(0)) +#define BQ24190_REG_F_NTC_FAULT_SHIFT 0 + +#define BQ24190_REG_VPRS 0x0A /* Vendor/Part/Revision Status */ +#define BQ24190_REG_VPRS_PN_MASK (BIT(5) | BIT(4) | BIT(3)) +#define BQ24190_REG_VPRS_PN_SHIFT 3 +#define BQ24190_REG_VPRS_PN_24190 0x4 +#define BQ24190_REG_VPRS_PN_24192 0x5 /* Also 24193 */ +#define BQ24190_REG_VPRS_PN_24192I 0x3 +#define BQ24190_REG_VPRS_TS_PROFILE_MASK BIT(2) +#define BQ24190_REG_VPRS_TS_PROFILE_SHIFT 2 +#define BQ24190_REG_VPRS_DEV_REG_MASK (BIT(1) | BIT(0)) +#define BQ24190_REG_VPRS_DEV_REG_SHIFT 0 + +/* + * The FAULT register is latched by the bq24190 (except for NTC_FAULT) + * 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. + */ +struct bq24190_dev_info { + struct i2c_client *client; + struct device *dev; + struct power_supply *charger; + struct power_supply *battery; + char model_name[I2C_NAME_SIZE]; + kernel_ulong_t model; + 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; +}; + +/* + * The tables below provide a 2-way mapping for the value that goes in + * the register field and the real-world value that it represents. + * The index of the array is the value that goes in the register; the + * number at that index in the array is the real-world value that it + * represents. + */ +/* REG02[7:2] (ICHG) in uAh */ +static const int bq24190_ccc_ichg_values[] = { + 512000, 576000, 640000, 704000, 768000, 832000, 896000, 960000, + 1024000, 1088000, 1152000, 1216000, 1280000, 1344000, 1408000, 1472000, + 1536000, 1600000, 1664000, 1728000, 1792000, 1856000, 1920000, 1984000, + 2048000, 2112000, 2176000, 2240000, 2304000, 2368000, 2432000, 2496000, + 2560000, 2624000, 2688000, 2752000, 2816000, 2880000, 2944000, 3008000, + 3072000, 3136000, 3200000, 3264000, 3328000, 3392000, 3456000, 3520000, + 3584000, 3648000, 3712000, 3776000, 3840000, 3904000, 3968000, 4032000, + 4096000, 4160000, 4224000, 4288000, 4352000, 4416000, 4480000, 4544000 +}; + +/* REG04[7:2] (VREG) in uV */ +static const int bq24190_cvc_vreg_values[] = { + 3504000, 3520000, 3536000, 3552000, 3568000, 3584000, 3600000, 3616000, + 3632000, 3648000, 3664000, 3680000, 3696000, 3712000, 3728000, 3744000, + 3760000, 3776000, 3792000, 3808000, 3824000, 3840000, 3856000, 3872000, + 3888000, 3904000, 3920000, 3936000, 3952000, 3968000, 3984000, 4000000, + 4016000, 4032000, 4048000, 4064000, 4080000, 4096000, 4112000, 4128000, + 4144000, 4160000, 4176000, 4192000, 4208000, 4224000, 4240000, 4256000, + 4272000, 4288000, 4304000, 4320000, 4336000, 4352000, 4368000, 4384000, + 4400000 +}; + +/* REG06[1:0] (TREG) in tenths of degrees Celcius */ +static const int bq24190_ictrc_treg_values[] = { + 600, 800, 1000, 1200 +}; + +/* + * Return the index in 'tbl' of greatest value that is less than or equal to + * 'val'. The index range returned is 0 to 'tbl_size' - 1. Assumes that + * the values in 'tbl' are sorted from smallest to largest and 'tbl_size' + * is less than 2^8. + */ +static u8 bq24190_find_idx(const int tbl[], int tbl_size, int v) +{ + int i; + + for (i = 1; i < tbl_size; i++) + if (v < tbl[i]) + break; + + return i - 1; +} + +/* Basic driver I/O routines */ + +static int bq24190_read(struct bq24190_dev_info *bdi, u8 reg, u8 *data) +{ + int ret; + + ret = i2c_smbus_read_byte_data(bdi->client, reg); + if (ret < 0) + return ret; + + *data = ret; + return 0; +} + +static int bq24190_write(struct bq24190_dev_info *bdi, u8 reg, u8 data) +{ + return i2c_smbus_write_byte_data(bdi->client, reg, data); +} + +static int bq24190_read_mask(struct bq24190_dev_info *bdi, u8 reg, + u8 mask, u8 shift, u8 *data) +{ + u8 v; + int ret; + + ret = bq24190_read(bdi, reg, &v); + if (ret < 0) + return ret; + + v &= mask; + v >>= shift; + *data = v; + + return 0; +} + +static int bq24190_write_mask(struct bq24190_dev_info *bdi, u8 reg, + u8 mask, u8 shift, u8 data) +{ + u8 v; + int ret; + + ret = bq24190_read(bdi, reg, &v); + if (ret < 0) + return ret; + + v &= ~mask; + v |= ((data << shift) & mask); + + return bq24190_write(bdi, reg, v); +} + +static int bq24190_get_field_val(struct bq24190_dev_info *bdi, + u8 reg, u8 mask, u8 shift, + const int tbl[], int tbl_size, + int *val) +{ + u8 v; + int ret; + + ret = bq24190_read_mask(bdi, reg, mask, shift, &v); + if (ret < 0) + return ret; + + v = (v >= tbl_size) ? (tbl_size - 1) : v; + *val = tbl[v]; + + return 0; +} + +static int bq24190_set_field_val(struct bq24190_dev_info *bdi, + u8 reg, u8 mask, u8 shift, + const int tbl[], int tbl_size, + int val) +{ + u8 idx; + + idx = bq24190_find_idx(tbl, tbl_size, val); + + return bq24190_write_mask(bdi, reg, mask, shift, idx); +} + +#ifdef CONFIG_SYSFS +/* + * There are a numerous options that are configurable on the bq24190 + * that go well beyond what the power_supply properties provide access to. + * Provide sysfs access to them so they can be examined and possibly modified + * on the fly. They will be provided for the charger power_supply object only + * and will be prefixed by 'f_' to make them easier to recognize. + */ + +#define BQ24190_SYSFS_FIELD(_name, r, f, m, store) \ +{ \ + .attr = __ATTR(f_##_name, m, bq24190_sysfs_show, store), \ + .reg = BQ24190_REG_##r, \ + .mask = BQ24190_REG_##r##_##f##_MASK, \ + .shift = BQ24190_REG_##r##_##f##_SHIFT, \ +} + +#define BQ24190_SYSFS_FIELD_RW(_name, r, f) \ + BQ24190_SYSFS_FIELD(_name, r, f, S_IWUSR | S_IRUGO, \ + bq24190_sysfs_store) + +#define BQ24190_SYSFS_FIELD_RO(_name, r, f) \ + BQ24190_SYSFS_FIELD(_name, r, f, S_IRUGO, NULL) + +static ssize_t bq24190_sysfs_show(struct device *dev, + struct device_attribute *attr, char *buf); +static ssize_t bq24190_sysfs_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count); + +struct bq24190_sysfs_field_info { + struct device_attribute attr; + u8 reg; + u8 mask; + u8 shift; +}; + +/* On i386 ptrace-abi.h defines SS that breaks the macro calls below. */ +#undef SS + +static struct bq24190_sysfs_field_info bq24190_sysfs_field_tbl[] = { + /* sysfs name reg field in reg */ + BQ24190_SYSFS_FIELD_RW(en_hiz, ISC, EN_HIZ), + BQ24190_SYSFS_FIELD_RW(vindpm, ISC, VINDPM), + BQ24190_SYSFS_FIELD_RW(iinlim, ISC, IINLIM), + BQ24190_SYSFS_FIELD_RW(chg_config, POC, CHG_CONFIG), + BQ24190_SYSFS_FIELD_RW(sys_min, POC, SYS_MIN), + BQ24190_SYSFS_FIELD_RW(boost_lim, POC, BOOST_LIM), + BQ24190_SYSFS_FIELD_RW(ichg, CCC, ICHG), + BQ24190_SYSFS_FIELD_RW(force_20_pct, CCC, FORCE_20PCT), + BQ24190_SYSFS_FIELD_RW(iprechg, PCTCC, IPRECHG), + BQ24190_SYSFS_FIELD_RW(iterm, PCTCC, ITERM), + BQ24190_SYSFS_FIELD_RW(vreg, CVC, VREG), + BQ24190_SYSFS_FIELD_RW(batlowv, CVC, BATLOWV), + BQ24190_SYSFS_FIELD_RW(vrechg, CVC, VRECHG), + BQ24190_SYSFS_FIELD_RW(en_term, CTTC, EN_TERM), + BQ24190_SYSFS_FIELD_RW(term_stat, CTTC, TERM_STAT), + BQ24190_SYSFS_FIELD_RO(watchdog, CTTC, WATCHDOG), + BQ24190_SYSFS_FIELD_RW(en_timer, CTTC, EN_TIMER), + BQ24190_SYSFS_FIELD_RW(chg_timer, CTTC, CHG_TIMER), + BQ24190_SYSFS_FIELD_RW(jeta_iset, CTTC, JEITA_ISET), + BQ24190_SYSFS_FIELD_RW(bat_comp, ICTRC, BAT_COMP), + BQ24190_SYSFS_FIELD_RW(vclamp, ICTRC, VCLAMP), + BQ24190_SYSFS_FIELD_RW(treg, ICTRC, TREG), + BQ24190_SYSFS_FIELD_RW(dpdm_en, MOC, DPDM_EN), + BQ24190_SYSFS_FIELD_RW(tmr2x_en, MOC, TMR2X_EN), + BQ24190_SYSFS_FIELD_RW(batfet_disable, MOC, BATFET_DISABLE), + BQ24190_SYSFS_FIELD_RW(jeita_vset, MOC, JEITA_VSET), + BQ24190_SYSFS_FIELD_RO(int_mask, MOC, INT_MASK), + BQ24190_SYSFS_FIELD_RO(vbus_stat, SS, VBUS_STAT), + BQ24190_SYSFS_FIELD_RO(chrg_stat, SS, CHRG_STAT), + BQ24190_SYSFS_FIELD_RO(dpm_stat, SS, DPM_STAT), + BQ24190_SYSFS_FIELD_RO(pg_stat, SS, PG_STAT), + BQ24190_SYSFS_FIELD_RO(therm_stat, SS, THERM_STAT), + BQ24190_SYSFS_FIELD_RO(vsys_stat, SS, VSYS_STAT), + BQ24190_SYSFS_FIELD_RO(watchdog_fault, F, WATCHDOG_FAULT), + BQ24190_SYSFS_FIELD_RO(boost_fault, F, BOOST_FAULT), + BQ24190_SYSFS_FIELD_RO(chrg_fault, F, CHRG_FAULT), + BQ24190_SYSFS_FIELD_RO(bat_fault, F, BAT_FAULT), + BQ24190_SYSFS_FIELD_RO(ntc_fault, F, NTC_FAULT), + BQ24190_SYSFS_FIELD_RO(pn, VPRS, PN), + BQ24190_SYSFS_FIELD_RO(ts_profile, VPRS, TS_PROFILE), + BQ24190_SYSFS_FIELD_RO(dev_reg, VPRS, DEV_REG), +}; + +static struct attribute * + bq24190_sysfs_attrs[ARRAY_SIZE(bq24190_sysfs_field_tbl) + 1]; + +static const struct attribute_group bq24190_sysfs_attr_group = { + .attrs = bq24190_sysfs_attrs, +}; + +static void bq24190_sysfs_init_attrs(void) +{ + int i, limit = ARRAY_SIZE(bq24190_sysfs_field_tbl); + + for (i = 0; i < limit; i++) + bq24190_sysfs_attrs[i] = &bq24190_sysfs_field_tbl[i].attr.attr; + + bq24190_sysfs_attrs[limit] = NULL; /* Has additional entry for this */ +} + +static struct bq24190_sysfs_field_info *bq24190_sysfs_field_lookup( + const char *name) +{ + int i, limit = ARRAY_SIZE(bq24190_sysfs_field_tbl); + + for (i = 0; i < limit; i++) + if (!strcmp(name, bq24190_sysfs_field_tbl[i].attr.attr.name)) + break; + + if (i >= limit) + return NULL; + + return &bq24190_sysfs_field_tbl[i]; +} + +static ssize_t bq24190_sysfs_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct power_supply *psy = dev_get_drvdata(dev); + struct bq24190_dev_info *bdi = power_supply_get_drvdata(psy); + struct bq24190_sysfs_field_info *info; + int ret; + u8 v; + + info = bq24190_sysfs_field_lookup(attr->attr.name); + if (!info) + return -EINVAL; + + ret = bq24190_read_mask(bdi, info->reg, info->mask, info->shift, &v); + if (ret) + return ret; + + return scnprintf(buf, PAGE_SIZE, "%hhx\n", v); +} + +static ssize_t bq24190_sysfs_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct power_supply *psy = dev_get_drvdata(dev); + struct bq24190_dev_info *bdi = power_supply_get_drvdata(psy); + struct bq24190_sysfs_field_info *info; + int ret; + u8 v; + + info = bq24190_sysfs_field_lookup(attr->attr.name); + if (!info) + return -EINVAL; + + ret = kstrtou8(buf, 0, &v); + if (ret < 0) + return ret; + + ret = bq24190_write_mask(bdi, info->reg, info->mask, info->shift, v); + if (ret) + return ret; + + return count; +} + +static int bq24190_sysfs_create_group(struct bq24190_dev_info *bdi) +{ + bq24190_sysfs_init_attrs(); + + return sysfs_create_group(&bdi->charger->dev.kobj, + &bq24190_sysfs_attr_group); +} + +static void bq24190_sysfs_remove_group(struct bq24190_dev_info *bdi) +{ + sysfs_remove_group(&bdi->charger->dev.kobj, &bq24190_sysfs_attr_group); +} +#else +static int bq24190_sysfs_create_group(struct bq24190_dev_info *bdi) +{ + return 0; +} + +static inline void bq24190_sysfs_remove_group(struct bq24190_dev_info *bdi) {} +#endif + +/* + * According to the "Host Mode and default Mode" section of the + * manual, a write to any register causes the bq24190 to switch + * from default mode to host mode. It will switch back to default + * mode after a WDT timeout unless the WDT is turned off as well. + * So, by simply turning off the WDT, we accomplish both with the + * same write. + */ +static int bq24190_set_mode_host(struct bq24190_dev_info *bdi) +{ + int ret; + u8 v; + + ret = bq24190_read(bdi, BQ24190_REG_CTTC, &v); + if (ret < 0) + return ret; + + bdi->watchdog = ((v & BQ24190_REG_CTTC_WATCHDOG_MASK) >> + BQ24190_REG_CTTC_WATCHDOG_SHIFT); + v &= ~BQ24190_REG_CTTC_WATCHDOG_MASK; + + return bq24190_write(bdi, BQ24190_REG_CTTC, v); +} + +static int bq24190_register_reset(struct bq24190_dev_info *bdi) +{ + int ret, limit = 100; + u8 v; + + /* Reset the registers */ + ret = bq24190_write_mask(bdi, BQ24190_REG_POC, + BQ24190_REG_POC_RESET_MASK, + BQ24190_REG_POC_RESET_SHIFT, + 0x1); + if (ret < 0) + return ret; + + /* Reset bit will be cleared by hardware so poll until it is */ + do { + ret = bq24190_read_mask(bdi, BQ24190_REG_POC, + BQ24190_REG_POC_RESET_MASK, + BQ24190_REG_POC_RESET_SHIFT, + &v); + if (ret < 0) + return ret; + + if (!v) + break; + + udelay(10); + } while (--limit); + + if (!limit) + return -EIO; + + return 0; +} + +/* Charger power supply property routines */ + +static int bq24190_charger_get_charge_type(struct bq24190_dev_info *bdi, + union power_supply_propval *val) +{ + u8 v; + int type, ret; + + ret = bq24190_read_mask(bdi, BQ24190_REG_POC, + BQ24190_REG_POC_CHG_CONFIG_MASK, + BQ24190_REG_POC_CHG_CONFIG_SHIFT, + &v); + if (ret < 0) + return ret; + + /* If POC[CHG_CONFIG] (REG01[5:4]) == 0, charge is disabled */ + if (!v) { + type = POWER_SUPPLY_CHARGE_TYPE_NONE; + } else { + ret = bq24190_read_mask(bdi, BQ24190_REG_CCC, + BQ24190_REG_CCC_FORCE_20PCT_MASK, + BQ24190_REG_CCC_FORCE_20PCT_SHIFT, + &v); + if (ret < 0) + return ret; + + type = (v) ? POWER_SUPPLY_CHARGE_TYPE_TRICKLE : + POWER_SUPPLY_CHARGE_TYPE_FAST; + } + + val->intval = type; + + return 0; +} + +static int bq24190_charger_set_charge_type(struct bq24190_dev_info *bdi, + const union power_supply_propval *val) +{ + u8 chg_config, force_20pct, en_term; + int ret; + + /* + * According to the "Termination when REG02[0] = 1" section of + * the bq24190 manual, the trickle charge could be less than the + * termination current so it recommends turning off the termination + * function. + * + * Note: AFAICT from the datasheet, the user will have to manually + * turn off the charging when in 20% mode. If its not turned off, + * there could be battery damage. So, use this mode at your own risk. + */ + switch (val->intval) { + case POWER_SUPPLY_CHARGE_TYPE_NONE: + chg_config = 0x0; + break; + case POWER_SUPPLY_CHARGE_TYPE_TRICKLE: + chg_config = 0x1; + force_20pct = 0x1; + en_term = 0x0; + break; + case POWER_SUPPLY_CHARGE_TYPE_FAST: + chg_config = 0x1; + force_20pct = 0x0; + en_term = 0x1; + break; + default: + return -EINVAL; + } + + if (chg_config) { /* Enabling the charger */ + ret = bq24190_write_mask(bdi, BQ24190_REG_CCC, + BQ24190_REG_CCC_FORCE_20PCT_MASK, + BQ24190_REG_CCC_FORCE_20PCT_SHIFT, + force_20pct); + if (ret < 0) + return ret; + + ret = bq24190_write_mask(bdi, BQ24190_REG_CTTC, + BQ24190_REG_CTTC_EN_TERM_MASK, + BQ24190_REG_CTTC_EN_TERM_SHIFT, + en_term); + if (ret < 0) + return ret; + } + + return bq24190_write_mask(bdi, BQ24190_REG_POC, + BQ24190_REG_POC_CHG_CONFIG_MASK, + BQ24190_REG_POC_CHG_CONFIG_SHIFT, chg_config); +} + +static int bq24190_charger_get_health(struct bq24190_dev_info *bdi, + union power_supply_propval *val) +{ + u8 v; + int health, ret; + + 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 + * no way to tell which. Return 'OVERVOLTAGE' since there + * isn't an 'OVERCURRENT' value defined that we can return + * even if it was over-current. + */ + health = POWER_SUPPLY_HEALTH_OVERVOLTAGE; + } else { + v &= BQ24190_REG_F_CHRG_FAULT_MASK; + v >>= BQ24190_REG_F_CHRG_FAULT_SHIFT; + + switch (v) { + case 0x0: /* Normal */ + health = POWER_SUPPLY_HEALTH_GOOD; + break; + case 0x1: /* Input Fault (VBUS OVP or VBATintval = health; + + return 0; +} + +static int bq24190_charger_get_online(struct bq24190_dev_info *bdi, + union power_supply_propval *val) +{ + u8 v; + int ret; + + ret = bq24190_read_mask(bdi, BQ24190_REG_SS, + BQ24190_REG_SS_PG_STAT_MASK, + BQ24190_REG_SS_PG_STAT_SHIFT, &v); + if (ret < 0) + return ret; + + val->intval = v; + return 0; +} + +static int bq24190_charger_get_current(struct bq24190_dev_info *bdi, + union power_supply_propval *val) +{ + u8 v; + int curr, ret; + + ret = bq24190_get_field_val(bdi, BQ24190_REG_CCC, + BQ24190_REG_CCC_ICHG_MASK, BQ24190_REG_CCC_ICHG_SHIFT, + bq24190_ccc_ichg_values, + ARRAY_SIZE(bq24190_ccc_ichg_values), &curr); + if (ret < 0) + return ret; + + ret = bq24190_read_mask(bdi, BQ24190_REG_CCC, + BQ24190_REG_CCC_FORCE_20PCT_MASK, + BQ24190_REG_CCC_FORCE_20PCT_SHIFT, &v); + if (ret < 0) + return ret; + + /* If FORCE_20PCT is enabled, then current is 20% of ICHG value */ + if (v) + curr /= 5; + + val->intval = curr; + return 0; +} + +static int bq24190_charger_get_current_max(struct bq24190_dev_info *bdi, + union power_supply_propval *val) +{ + int idx = ARRAY_SIZE(bq24190_ccc_ichg_values) - 1; + + val->intval = bq24190_ccc_ichg_values[idx]; + return 0; +} + +static int bq24190_charger_set_current(struct bq24190_dev_info *bdi, + const union power_supply_propval *val) +{ + u8 v; + int ret, curr = val->intval; + + ret = bq24190_read_mask(bdi, BQ24190_REG_CCC, + BQ24190_REG_CCC_FORCE_20PCT_MASK, + BQ24190_REG_CCC_FORCE_20PCT_SHIFT, &v); + if (ret < 0) + return ret; + + /* If FORCE_20PCT is enabled, have to multiply value passed in by 5 */ + if (v) + curr *= 5; + + return bq24190_set_field_val(bdi, BQ24190_REG_CCC, + BQ24190_REG_CCC_ICHG_MASK, BQ24190_REG_CCC_ICHG_SHIFT, + bq24190_ccc_ichg_values, + ARRAY_SIZE(bq24190_ccc_ichg_values), curr); +} + +static int bq24190_charger_get_voltage(struct bq24190_dev_info *bdi, + union power_supply_propval *val) +{ + int voltage, ret; + + ret = bq24190_get_field_val(bdi, BQ24190_REG_CVC, + BQ24190_REG_CVC_VREG_MASK, BQ24190_REG_CVC_VREG_SHIFT, + bq24190_cvc_vreg_values, + ARRAY_SIZE(bq24190_cvc_vreg_values), &voltage); + if (ret < 0) + return ret; + + val->intval = voltage; + return 0; +} + +static int bq24190_charger_get_voltage_max(struct bq24190_dev_info *bdi, + union power_supply_propval *val) +{ + int idx = ARRAY_SIZE(bq24190_cvc_vreg_values) - 1; + + val->intval = bq24190_cvc_vreg_values[idx]; + return 0; +} + +static int bq24190_charger_set_voltage(struct bq24190_dev_info *bdi, + const union power_supply_propval *val) +{ + return bq24190_set_field_val(bdi, BQ24190_REG_CVC, + BQ24190_REG_CVC_VREG_MASK, BQ24190_REG_CVC_VREG_SHIFT, + bq24190_cvc_vreg_values, + ARRAY_SIZE(bq24190_cvc_vreg_values), val->intval); +} + +static int bq24190_charger_get_property(struct power_supply *psy, + enum power_supply_property psp, union power_supply_propval *val) +{ + struct bq24190_dev_info *bdi = power_supply_get_drvdata(psy); + int ret; + + dev_dbg(bdi->dev, "prop: %d\n", psp); + + pm_runtime_get_sync(bdi->dev); + + switch (psp) { + case POWER_SUPPLY_PROP_CHARGE_TYPE: + ret = bq24190_charger_get_charge_type(bdi, val); + break; + case POWER_SUPPLY_PROP_HEALTH: + ret = bq24190_charger_get_health(bdi, val); + break; + case POWER_SUPPLY_PROP_ONLINE: + ret = bq24190_charger_get_online(bdi, val); + break; + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: + ret = bq24190_charger_get_current(bdi, val); + break; + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX: + ret = bq24190_charger_get_current_max(bdi, val); + break; + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: + ret = bq24190_charger_get_voltage(bdi, val); + break; + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX: + ret = bq24190_charger_get_voltage_max(bdi, val); + break; + case POWER_SUPPLY_PROP_SCOPE: + val->intval = POWER_SUPPLY_SCOPE_SYSTEM; + ret = 0; + break; + case POWER_SUPPLY_PROP_MODEL_NAME: + val->strval = bdi->model_name; + ret = 0; + break; + case POWER_SUPPLY_PROP_MANUFACTURER: + val->strval = BQ24190_MANUFACTURER; + ret = 0; + break; + default: + ret = -ENODATA; + } + + pm_runtime_put_sync(bdi->dev); + return ret; +} + +static int bq24190_charger_set_property(struct power_supply *psy, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct bq24190_dev_info *bdi = power_supply_get_drvdata(psy); + int ret; + + dev_dbg(bdi->dev, "prop: %d\n", psp); + + pm_runtime_get_sync(bdi->dev); + + switch (psp) { + case POWER_SUPPLY_PROP_CHARGE_TYPE: + ret = bq24190_charger_set_charge_type(bdi, val); + break; + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: + ret = bq24190_charger_set_current(bdi, val); + break; + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: + ret = bq24190_charger_set_voltage(bdi, val); + break; + default: + ret = -EINVAL; + } + + pm_runtime_put_sync(bdi->dev); + return ret; +} + +static int bq24190_charger_property_is_writeable(struct power_supply *psy, + enum power_supply_property psp) +{ + int ret; + + switch (psp) { + case POWER_SUPPLY_PROP_CHARGE_TYPE: + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: + ret = 1; + break; + default: + ret = 0; + } + + return ret; +} + +static enum power_supply_property bq24190_charger_properties[] = { + POWER_SUPPLY_PROP_CHARGE_TYPE, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX, + POWER_SUPPLY_PROP_SCOPE, + POWER_SUPPLY_PROP_MODEL_NAME, + POWER_SUPPLY_PROP_MANUFACTURER, +}; + +static char *bq24190_charger_supplied_to[] = { + "main-battery", +}; + +static const struct power_supply_desc bq24190_charger_desc = { + .name = "bq24190-charger", + .type = POWER_SUPPLY_TYPE_USB, + .properties = bq24190_charger_properties, + .num_properties = ARRAY_SIZE(bq24190_charger_properties), + .get_property = bq24190_charger_get_property, + .set_property = bq24190_charger_set_property, + .property_is_writeable = bq24190_charger_property_is_writeable, +}; + +/* Battery power supply property routines */ + +static int bq24190_battery_get_status(struct bq24190_dev_info *bdi, + union power_supply_propval *val) +{ + u8 ss_reg, chrg_fault; + 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; + + ret = bq24190_read(bdi, BQ24190_REG_SS, &ss_reg); + if (ret < 0) + return ret; + + /* + * The battery must be discharging when any of these are true: + * - there is no good power source; + * - there is a charge fault. + * Could also be discharging when in "supplement mode" but + * there is no way to tell when its in that mode. + */ + if (!(ss_reg & BQ24190_REG_SS_PG_STAT_MASK) || chrg_fault) { + status = POWER_SUPPLY_STATUS_DISCHARGING; + } else { + ss_reg &= BQ24190_REG_SS_CHRG_STAT_MASK; + ss_reg >>= BQ24190_REG_SS_CHRG_STAT_SHIFT; + + switch (ss_reg) { + case 0x0: /* Not Charging */ + status = POWER_SUPPLY_STATUS_NOT_CHARGING; + break; + case 0x1: /* Pre-charge */ + case 0x2: /* Fast Charging */ + status = POWER_SUPPLY_STATUS_CHARGING; + break; + case 0x3: /* Charge Termination Done */ + status = POWER_SUPPLY_STATUS_FULL; + break; + default: + ret = -EIO; + } + } + + if (!ret) + val->intval = status; + + return ret; +} + +static int bq24190_battery_get_health(struct bq24190_dev_info *bdi, + union power_supply_propval *val) +{ + u8 v; + int health, ret; + + 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; + } else { + v &= BQ24190_REG_F_NTC_FAULT_MASK; + v >>= BQ24190_REG_F_NTC_FAULT_SHIFT; + + switch (v) { + case 0x0: /* Normal */ + health = POWER_SUPPLY_HEALTH_GOOD; + break; + case 0x1: /* TS1 Cold */ + case 0x3: /* TS2 Cold */ + case 0x5: /* Both Cold */ + health = POWER_SUPPLY_HEALTH_COLD; + break; + case 0x2: /* TS1 Hot */ + case 0x4: /* TS2 Hot */ + case 0x6: /* Both Hot */ + health = POWER_SUPPLY_HEALTH_OVERHEAT; + break; + default: + health = POWER_SUPPLY_HEALTH_UNKNOWN; + } + } + + val->intval = health; + return 0; +} + +static int bq24190_battery_get_online(struct bq24190_dev_info *bdi, + union power_supply_propval *val) +{ + u8 batfet_disable; + int ret; + + ret = bq24190_read_mask(bdi, BQ24190_REG_MOC, + BQ24190_REG_MOC_BATFET_DISABLE_MASK, + BQ24190_REG_MOC_BATFET_DISABLE_SHIFT, &batfet_disable); + if (ret < 0) + return ret; + + val->intval = !batfet_disable; + return 0; +} + +static int bq24190_battery_set_online(struct bq24190_dev_info *bdi, + const union power_supply_propval *val) +{ + return bq24190_write_mask(bdi, BQ24190_REG_MOC, + BQ24190_REG_MOC_BATFET_DISABLE_MASK, + BQ24190_REG_MOC_BATFET_DISABLE_SHIFT, !val->intval); +} + +static int bq24190_battery_get_temp_alert_max(struct bq24190_dev_info *bdi, + union power_supply_propval *val) +{ + int temp, ret; + + ret = bq24190_get_field_val(bdi, BQ24190_REG_ICTRC, + BQ24190_REG_ICTRC_TREG_MASK, + BQ24190_REG_ICTRC_TREG_SHIFT, + bq24190_ictrc_treg_values, + ARRAY_SIZE(bq24190_ictrc_treg_values), &temp); + if (ret < 0) + return ret; + + val->intval = temp; + return 0; +} + +static int bq24190_battery_set_temp_alert_max(struct bq24190_dev_info *bdi, + const union power_supply_propval *val) +{ + return bq24190_set_field_val(bdi, BQ24190_REG_ICTRC, + BQ24190_REG_ICTRC_TREG_MASK, + BQ24190_REG_ICTRC_TREG_SHIFT, + bq24190_ictrc_treg_values, + ARRAY_SIZE(bq24190_ictrc_treg_values), val->intval); +} + +static int bq24190_battery_get_property(struct power_supply *psy, + enum power_supply_property psp, union power_supply_propval *val) +{ + struct bq24190_dev_info *bdi = power_supply_get_drvdata(psy); + int ret; + + dev_dbg(bdi->dev, "prop: %d\n", psp); + + pm_runtime_get_sync(bdi->dev); + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + ret = bq24190_battery_get_status(bdi, val); + break; + case POWER_SUPPLY_PROP_HEALTH: + ret = bq24190_battery_get_health(bdi, val); + break; + case POWER_SUPPLY_PROP_ONLINE: + ret = bq24190_battery_get_online(bdi, val); + break; + case POWER_SUPPLY_PROP_TECHNOLOGY: + /* Could be Li-on or Li-polymer but no way to tell which */ + val->intval = POWER_SUPPLY_TECHNOLOGY_UNKNOWN; + ret = 0; + break; + case POWER_SUPPLY_PROP_TEMP_ALERT_MAX: + ret = bq24190_battery_get_temp_alert_max(bdi, val); + break; + case POWER_SUPPLY_PROP_SCOPE: + val->intval = POWER_SUPPLY_SCOPE_SYSTEM; + ret = 0; + break; + default: + ret = -ENODATA; + } + + pm_runtime_put_sync(bdi->dev); + return ret; +} + +static int bq24190_battery_set_property(struct power_supply *psy, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct bq24190_dev_info *bdi = power_supply_get_drvdata(psy); + int ret; + + dev_dbg(bdi->dev, "prop: %d\n", psp); + + pm_runtime_put_sync(bdi->dev); + + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + ret = bq24190_battery_set_online(bdi, val); + break; + case POWER_SUPPLY_PROP_TEMP_ALERT_MAX: + ret = bq24190_battery_set_temp_alert_max(bdi, val); + break; + default: + ret = -EINVAL; + } + + pm_runtime_put_sync(bdi->dev); + return ret; +} + +static int bq24190_battery_property_is_writeable(struct power_supply *psy, + enum power_supply_property psp) +{ + int ret; + + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + case POWER_SUPPLY_PROP_TEMP_ALERT_MAX: + ret = 1; + break; + default: + ret = 0; + } + + return ret; +} + +static enum power_supply_property bq24190_battery_properties[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_TEMP_ALERT_MAX, + POWER_SUPPLY_PROP_SCOPE, +}; + +static const struct power_supply_desc bq24190_battery_desc = { + .name = "bq24190-battery", + .type = POWER_SUPPLY_TYPE_BATTERY, + .properties = bq24190_battery_properties, + .num_properties = ARRAY_SIZE(bq24190_battery_properties), + .get_property = bq24190_battery_get_property, + .set_property = bq24190_battery_set_property, + .property_is_writeable = bq24190_battery_property_is_writeable, +}; + +static irqreturn_t bq24190_irq_handler_thread(int irq, void *data) +{ + struct bq24190_dev_info *bdi = data; + bool alert_userspace = false; + u8 ss_reg = 0, f_reg = 0; + int ret; + + pm_runtime_get_sync(bdi->dev); + + ret = bq24190_read(bdi, BQ24190_REG_SS, &ss_reg); + if (ret < 0) { + dev_err(bdi->dev, "Can't read SS reg: %d\n", ret); + goto out; + } + + if (ss_reg != bdi->ss_reg) { + /* + * The device is in host mode so when PG_STAT goes from 1->0 + * (i.e., power removed) HIZ needs to be disabled. + */ + if ((bdi->ss_reg & BQ24190_REG_SS_PG_STAT_MASK) && + !(ss_reg & BQ24190_REG_SS_PG_STAT_MASK)) { + ret = bq24190_write_mask(bdi, BQ24190_REG_ISC, + BQ24190_REG_ISC_EN_HIZ_MASK, + BQ24190_REG_ISC_EN_HIZ_SHIFT, + 0); + if (ret < 0) + dev_err(bdi->dev, "Can't access ISC reg: %d\n", + ret); + } + + 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) { + power_supply_changed(bdi->charger); + power_supply_changed(bdi->battery); + } else { + bdi->first_time = false; + } + } + +out: + pm_runtime_put_sync(bdi->dev); + + dev_dbg(bdi->dev, "ss_reg: 0x%02x, f_reg: 0x%02x\n", ss_reg, f_reg); + + return IRQ_HANDLED; +} + +static int bq24190_hw_init(struct bq24190_dev_info *bdi) +{ + u8 v; + int ret; + + pm_runtime_get_sync(bdi->dev); + + /* First check that the device really is what its supposed to be */ + ret = bq24190_read_mask(bdi, BQ24190_REG_VPRS, + BQ24190_REG_VPRS_PN_MASK, + BQ24190_REG_VPRS_PN_SHIFT, + &v); + if (ret < 0) + goto out; + + if (v != bdi->model) { + ret = -ENODEV; + goto out; + } + + ret = bq24190_register_reset(bdi); + if (ret < 0) + goto out; + + ret = bq24190_set_mode_host(bdi); +out: + pm_runtime_put_sync(bdi->dev); + return ret; +} + +#ifdef CONFIG_OF +static int bq24190_setup_dt(struct bq24190_dev_info *bdi) +{ + bdi->irq = irq_of_parse_and_map(bdi->dev->of_node, 0); + if (bdi->irq <= 0) + return -1; + + return 0; +} +#else +static int bq24190_setup_dt(struct bq24190_dev_info *bdi) +{ + return -1; +} +#endif + +static int bq24190_setup_pdata(struct bq24190_dev_info *bdi, + struct bq24190_platform_data *pdata) +{ + int ret; + + if (!gpio_is_valid(pdata->gpio_int)) + return -1; + + ret = gpio_request(pdata->gpio_int, dev_name(bdi->dev)); + if (ret < 0) + return -1; + + ret = gpio_direction_input(pdata->gpio_int); + if (ret < 0) + goto out; + + bdi->irq = gpio_to_irq(pdata->gpio_int); + if (!bdi->irq) + goto out; + + bdi->gpio_int = pdata->gpio_int; + return 0; + +out: + gpio_free(pdata->gpio_int); + return -1; +} + +static int bq24190_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 bq24190_platform_data *pdata = client->dev.platform_data; + struct power_supply_config charger_cfg = {}, battery_cfg = {}; + struct bq24190_dev_info *bdi; + int ret; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) { + dev_err(dev, "No support for SMBUS_BYTE_DATA\n"); + return -ENODEV; + } + + bdi = devm_kzalloc(dev, sizeof(*bdi), GFP_KERNEL); + if (!bdi) { + dev_err(dev, "Can't alloc bdi struct\n"); + return -ENOMEM; + } + + bdi->client = client; + bdi->dev = dev; + bdi->model = id->driver_data; + strncpy(bdi->model_name, id->name, I2C_NAME_SIZE); + mutex_init(&bdi->f_reg_lock); + bdi->first_time = true; + bdi->charger_health_valid = false; + bdi->battery_health_valid = false; + bdi->battery_status_valid = false; + + i2c_set_clientdata(client, bdi); + + if (dev->of_node) + ret = bq24190_setup_dt(bdi); + else + ret = bq24190_setup_pdata(bdi, pdata); + + if (ret) { + dev_err(dev, "Can't get irq info\n"); + 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; + } + + charger_cfg.drv_data = bdi; + charger_cfg.supplied_to = bq24190_charger_supplied_to; + charger_cfg.num_supplicants = ARRAY_SIZE(bq24190_charger_supplied_to), + bdi->charger = power_supply_register(dev, &bq24190_charger_desc, + &charger_cfg); + if (IS_ERR(bdi->charger)) { + dev_err(dev, "Can't register charger\n"); + ret = PTR_ERR(bdi->charger); + goto out2; + } + + battery_cfg.drv_data = bdi; + bdi->battery = power_supply_register(dev, &bq24190_battery_desc, + &battery_cfg); + if (IS_ERR(bdi->battery)) { + dev_err(dev, "Can't register battery\n"); + ret = PTR_ERR(bdi->battery); + goto out3; + } + + ret = bq24190_sysfs_create_group(bdi); + if (ret) { + dev_err(dev, "Can't create sysfs entries\n"); + goto out4; + } + + return 0; + +out4: + power_supply_unregister(bdi->battery); +out3: + power_supply_unregister(bdi->charger); +out2: + pm_runtime_disable(dev); +out1: + if (bdi->gpio_int) + gpio_free(bdi->gpio_int); + + return ret; +} + +static int bq24190_remove(struct i2c_client *client) +{ + struct bq24190_dev_info *bdi = i2c_get_clientdata(client); + + pm_runtime_get_sync(bdi->dev); + bq24190_register_reset(bdi); + pm_runtime_put_sync(bdi->dev); + + bq24190_sysfs_remove_group(bdi); + power_supply_unregister(bdi->battery); + power_supply_unregister(bdi->charger); + pm_runtime_disable(bdi->dev); + + if (bdi->gpio_int) + gpio_free(bdi->gpio_int); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int bq24190_pm_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct bq24190_dev_info *bdi = i2c_get_clientdata(client); + + pm_runtime_get_sync(bdi->dev); + bq24190_register_reset(bdi); + pm_runtime_put_sync(bdi->dev); + + return 0; +} + +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; + + pm_runtime_get_sync(bdi->dev); + bq24190_register_reset(bdi); + pm_runtime_put_sync(bdi->dev); + + /* Things may have changed while suspended so alert upper layer */ + power_supply_changed(bdi->charger); + power_supply_changed(bdi->battery); + + return 0; +} +#endif + +static SIMPLE_DEV_PM_OPS(bq24190_pm_ops, bq24190_pm_suspend, bq24190_pm_resume); + +/* + * Only support the bq24190 right now. The bq24192, bq24192i, and bq24193 + * are similar but not identical so the driver needs to be extended to + * support them. + */ +static const struct i2c_device_id bq24190_i2c_ids[] = { + { "bq24190", BQ24190_REG_VPRS_PN_24190 }, + { }, +}; +MODULE_DEVICE_TABLE(i2c, bq24190_i2c_ids); + +#ifdef CONFIG_OF +static const struct of_device_id bq24190_of_match[] = { + { .compatible = "ti,bq24190", }, + { }, +}; +MODULE_DEVICE_TABLE(of, bq24190_of_match); +#else +static const struct of_device_id bq24190_of_match[] = { + { }, +}; +#endif + +static struct i2c_driver bq24190_driver = { + .probe = bq24190_probe, + .remove = bq24190_remove, + .id_table = bq24190_i2c_ids, + .driver = { + .name = "bq24190-charger", + .pm = &bq24190_pm_ops, + .of_match_table = of_match_ptr(bq24190_of_match), + }, +}; +module_i2c_driver(bq24190_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Mark A. Greer "); +MODULE_DESCRIPTION("TI BQ24190 Charger Driver"); diff --git a/drivers/power/supply/bq24257_charger.c b/drivers/power/supply/bq24257_charger.c new file mode 100644 index 000000000000..1fea2c7ef97f --- /dev/null +++ b/drivers/power/supply/bq24257_charger.c @@ -0,0 +1,1196 @@ +/* + * TI BQ24257 charger driver + * + * Copyright (C) 2015 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; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + * + * Datasheets: + * http://www.ti.com/product/bq24250 + * http://www.ti.com/product/bq24251 + * http://www.ti.com/product/bq24257 + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define BQ24257_REG_1 0x00 +#define BQ24257_REG_2 0x01 +#define BQ24257_REG_3 0x02 +#define BQ24257_REG_4 0x03 +#define BQ24257_REG_5 0x04 +#define BQ24257_REG_6 0x05 +#define BQ24257_REG_7 0x06 + +#define BQ24257_MANUFACTURER "Texas Instruments" +#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_EN, F_TS_STAT, /* REG 6 */ + F_VOVP, F_CLR_VDP, F_FORCE_BATDET, F_FORCE_PTM, /* REG 7 */ + + F_MAX_FIELDS +}; + +/* initial field values, converted from uV/uA */ +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 { + u8 status; + u8 fault; + bool power_good; +}; + +struct bq24257_device { + struct i2c_client *client; + struct device *dev; + struct power_supply *charger; + + enum bq2425x_chip chip; + + struct regmap *rmap; + struct regmap_field *rmap_fields[F_MAX_FIELDS]; + + struct gpio_desc *pg; + + struct delayed_work iilimit_setup_work; + + struct bq24257_init_data init_data; + 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) +{ + switch (reg) { + case BQ24257_REG_2: + case BQ24257_REG_4: + return false; + + default: + return true; + } +} + +static const struct regmap_config bq24257_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = BQ24257_REG_7, + .cache_type = REGCACHE_RBTREE, + + .volatile_reg = bq24257_is_volatile_reg, +}; + +static const struct reg_field bq24257_reg_fields[] = { + /* REG 1 */ + [F_WD_FAULT] = REG_FIELD(BQ24257_REG_1, 7, 7), + [F_WD_EN] = REG_FIELD(BQ24257_REG_1, 6, 6), + [F_STAT] = REG_FIELD(BQ24257_REG_1, 4, 5), + [F_FAULT] = REG_FIELD(BQ24257_REG_1, 0, 3), + /* REG 2 */ + [F_RESET] = REG_FIELD(BQ24257_REG_2, 7, 7), + [F_IILIMIT] = REG_FIELD(BQ24257_REG_2, 4, 6), + [F_EN_STAT] = REG_FIELD(BQ24257_REG_2, 3, 3), + [F_EN_TERM] = REG_FIELD(BQ24257_REG_2, 2, 2), + [F_CE] = REG_FIELD(BQ24257_REG_2, 1, 1), + [F_HZ_MODE] = REG_FIELD(BQ24257_REG_2, 0, 0), + /* REG 3 */ + [F_VBAT] = REG_FIELD(BQ24257_REG_3, 2, 7), + [F_USB_DET] = REG_FIELD(BQ24257_REG_3, 0, 1), + /* REG 4 */ + [F_ICHG] = REG_FIELD(BQ24257_REG_4, 3, 7), + [F_ITERM] = REG_FIELD(BQ24257_REG_4, 0, 2), + /* REG 5 */ + [F_LOOP_STATUS] = REG_FIELD(BQ24257_REG_5, 6, 7), + [F_LOW_CHG] = REG_FIELD(BQ24257_REG_5, 5, 5), + [F_DPDM_EN] = REG_FIELD(BQ24257_REG_5, 4, 4), + [F_CE_STATUS] = REG_FIELD(BQ24257_REG_5, 3, 3), + [F_VINDPM] = REG_FIELD(BQ24257_REG_5, 0, 2), + /* REG 6 */ + [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), + [F_CLR_VDP] = REG_FIELD(BQ24257_REG_7, 4, 4), + [F_FORCE_BATDET] = REG_FIELD(BQ24257_REG_7, 3, 3), + [F_FORCE_PTM] = REG_FIELD(BQ24257_REG_7, 2, 2) +}; + +static const u32 bq24257_vbat_map[] = { + 3500000, 3520000, 3540000, 3560000, 3580000, 3600000, 3620000, 3640000, + 3660000, 3680000, 3700000, 3720000, 3740000, 3760000, 3780000, 3800000, + 3820000, 3840000, 3860000, 3880000, 3900000, 3920000, 3940000, 3960000, + 3980000, 4000000, 4020000, 4040000, 4060000, 4080000, 4100000, 4120000, + 4140000, 4160000, 4180000, 4200000, 4220000, 4240000, 4260000, 4280000, + 4300000, 4320000, 4340000, 4360000, 4380000, 4400000, 4420000, 4440000 +}; + +#define BQ24257_VBAT_MAP_SIZE ARRAY_SIZE(bq24257_vbat_map) + +static const u32 bq24257_ichg_map[] = { + 500000, 550000, 600000, 650000, 700000, 750000, 800000, 850000, 900000, + 950000, 1000000, 1050000, 1100000, 1150000, 1200000, 1250000, 1300000, + 1350000, 1400000, 1450000, 1500000, 1550000, 1600000, 1650000, 1700000, + 1750000, 1800000, 1850000, 1900000, 1950000, 2000000 +}; + +#define BQ24257_ICHG_MAP_SIZE ARRAY_SIZE(bq24257_ichg_map) + +static const u32 bq24257_iterm_map[] = { + 50000, 75000, 100000, 125000, 150000, 175000, 200000, 225000 +}; + +#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) +{ + int ret; + int val; + + ret = regmap_field_read(bq->rmap_fields[field_id], &val); + if (ret < 0) + return ret; + + return val; +} + +static int bq24257_field_write(struct bq24257_device *bq, + enum bq24257_fields field_id, u8 val) +{ + return regmap_field_write(bq->rmap_fields[field_id], val); +} + +static u8 bq24257_find_idx(u32 value, const u32 *map, u8 map_size) +{ + u8 idx; + + for (idx = 1; idx < map_size; idx++) + if (value < map[idx]) + break; + + return idx - 1; +} + +enum bq24257_status { + STATUS_READY, + STATUS_CHARGE_IN_PROGRESS, + STATUS_CHARGE_DONE, + STATUS_FAULT, +}; + +enum bq24257_fault { + FAULT_NORMAL, + FAULT_INPUT_OVP, + FAULT_INPUT_UVLO, + FAULT_SLEEP, + FAULT_BAT_TS, + FAULT_BAT_OVP, + FAULT_TS, + FAULT_TIMER, + FAULT_NO_BAT, + FAULT_ISET, + 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) +{ + struct bq24257_device *bq = power_supply_get_drvdata(psy); + struct bq24257_state state; + + mutex_lock(&bq->lock); + state = bq->state; + mutex_unlock(&bq->lock); + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + if (!state.power_good) + val->intval = POWER_SUPPLY_STATUS_DISCHARGING; + else if (state.status == STATUS_READY) + val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; + else if (state.status == STATUS_CHARGE_IN_PROGRESS) + val->intval = POWER_SUPPLY_STATUS_CHARGING; + else if (state.status == STATUS_CHARGE_DONE) + val->intval = POWER_SUPPLY_STATUS_FULL; + else + val->intval = POWER_SUPPLY_STATUS_UNKNOWN; + break; + + case POWER_SUPPLY_PROP_MANUFACTURER: + 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; + + case POWER_SUPPLY_PROP_HEALTH: + switch (state.fault) { + case FAULT_NORMAL: + val->intval = POWER_SUPPLY_HEALTH_GOOD; + break; + + case FAULT_INPUT_OVP: + case FAULT_BAT_OVP: + val->intval = POWER_SUPPLY_HEALTH_OVERVOLTAGE; + break; + + case FAULT_TS: + case FAULT_BAT_TS: + val->intval = POWER_SUPPLY_HEALTH_OVERHEAT; + break; + + case FAULT_TIMER: + val->intval = POWER_SUPPLY_HEALTH_SAFETY_TIMER_EXPIRE; + break; + + default: + val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; + break; + } + + break; + + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: + val->intval = bq24257_ichg_map[bq->init_data.ichg]; + break; + + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX: + val->intval = bq24257_ichg_map[BQ24257_ICHG_MAP_SIZE - 1]; + break; + + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: + val->intval = bq24257_vbat_map[bq->init_data.vbat]; + break; + + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX: + val->intval = bq24257_vbat_map[BQ24257_VBAT_MAP_SIZE - 1]; + break; + + case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT: + 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; + } + + 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) +{ + int ret; + + ret = bq24257_field_read(bq, F_STAT); + if (ret < 0) + return ret; + + state->status = ret; + + ret = bq24257_field_read(bq, F_FAULT); + if (ret < 0) + return ret; + + state->fault = ret; + + 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; +} + +static bool bq24257_state_changed(struct bq24257_device *bq, + struct bq24257_state *new_state) +{ + int ret; + + mutex_lock(&bq->lock); + ret = (bq->state.status != new_state->status || + bq->state.fault != new_state->fault || + bq->state.power_good != new_state->power_good); + mutex_unlock(&bq->lock); + + return ret; +} + +enum bq24257_loop_status { + LOOP_STATUS_NONE, + LOOP_STATUS_IN_DPM, + LOOP_STATUS_IN_CURRENT_LIMIT, + LOOP_STATUS_THERMAL, +}; + +enum bq24257_in_ilimit { + IILIMIT_100, + IILIMIT_150, + IILIMIT_500, + IILIMIT_900, + IILIMIT_1500, + IILIMIT_2000, + IILIMIT_EXT, + 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 */ + PORT_TYPE_SDP, /* Standard Downstream Port */ + PORT_TYPE_NON_STANDARD, +}; + +enum bq24257_safety_timer { + SAFETY_TIMER_45, + SAFETY_TIMER_360, + SAFETY_TIMER_540, + SAFETY_TIMER_NONE, +}; + +static int bq24257_iilimit_autoset(struct bq24257_device *bq) +{ + int loop_status; + int iilimit; + int port_type; + int ret; + const u8 new_iilimit[] = { + [PORT_TYPE_DCP] = IILIMIT_2000, + [PORT_TYPE_CDP] = IILIMIT_2000, + [PORT_TYPE_SDP] = IILIMIT_500, + [PORT_TYPE_NON_STANDARD] = IILIMIT_500 + }; + + ret = bq24257_field_read(bq, F_LOOP_STATUS); + if (ret < 0) + goto error; + + loop_status = ret; + + ret = bq24257_field_read(bq, F_IILIMIT); + if (ret < 0) + goto error; + + iilimit = ret; + + /* + * All USB ports should be able to handle 500mA. If not, DPM will lower + * the charging current to accommodate the power source. No need to set + * a lower IILIMIT value. + */ + if (loop_status == LOOP_STATUS_IN_DPM && iilimit == IILIMIT_500) + return 0; + + ret = bq24257_field_read(bq, F_USB_DET); + if (ret < 0) + goto error; + + port_type = ret; + + ret = bq24257_field_write(bq, F_IILIMIT, new_iilimit[port_type]); + if (ret < 0) + goto error; + + ret = bq24257_field_write(bq, F_TMR, SAFETY_TIMER_360); + if (ret < 0) + goto error; + + ret = bq24257_field_write(bq, F_CLR_VDP, 1); + if (ret < 0) + goto error; + + dev_dbg(bq->dev, "port/loop = %d/%d -> iilimit = %d\n", + port_type, loop_status, new_iilimit[port_type]); + + return 0; + +error: + dev_err(bq->dev, "%s: Error communicating with the chip.\n", __func__); + return ret; +} + +static void bq24257_iilimit_setup_work(struct work_struct *work) +{ + struct bq24257_device *bq = container_of(work, struct bq24257_device, + iilimit_setup_work.work); + + bq24257_iilimit_autoset(bq); +} + +static void bq24257_handle_state_change(struct bq24257_device *bq, + struct bq24257_state *new_state) +{ + int ret; + struct bq24257_state old_state; + + mutex_lock(&bq->lock); + old_state = bq->state; + mutex_unlock(&bq->lock); + + /* + * 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); + 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"); + + 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; + +error: + dev_err(bq->dev, "%s: Error communicating with the chip.\n", __func__); +} + +static irqreturn_t bq24257_irq_handler_thread(int irq, void *private) +{ + int ret; + struct bq24257_device *bq = private; + struct bq24257_state state; + + ret = bq24257_get_chip_state(bq, &state); + if (ret < 0) + return IRQ_HANDLED; + + if (!bq24257_state_changed(bq, &state)) + return IRQ_HANDLED; + + dev_dbg(bq->dev, "irq(state changed): status/fault/pg = %d/%d/%d\n", + state.status, state.fault, state.power_good); + + bq24257_handle_state_change(bq, &state); + + mutex_lock(&bq->lock); + bq->state = state; + mutex_unlock(&bq->lock); + + power_supply_changed(bq->charger); + + return IRQ_HANDLED; +} + +static int bq24257_hw_init(struct bq24257_device *bq) +{ + int ret; + int i; + struct bq24257_state state; + + const struct { + int field; + u32 value; + } init_data[] = { + {F_ICHG, bq->init_data.ichg}, + {F_VBAT, bq->init_data.vbat}, + {F_ITERM, bq->init_data.iterm}, + {F_VOVP, bq->init_data.vovp}, + {F_VINDPM, bq->init_data.vindpm}, + }; + + /* + * Disable the watchdog timer to prevent the IC from going back to + * default settings after 50 seconds of I2C inactivity. + */ + ret = bq24257_field_write(bq, F_WD_EN, 0); + if (ret < 0) + return ret; + + /* configure the charge currents and voltages */ + for (i = 0; i < ARRAY_SIZE(init_data); i++) { + ret = bq24257_field_write(bq, init_data[i].field, + init_data[i].value); + if (ret < 0) + return ret; + } + + ret = bq24257_get_chip_state(bq, &state); + if (ret < 0) + return ret; + + mutex_lock(&bq->lock); + bq->state = state; + mutex_unlock(&bq->lock); + + 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) + ret = bq24257_iilimit_autoset(bq); + + return ret; +} + +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, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX, + 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[] = { + "main-battery", +}; + +static const struct power_supply_desc bq24257_power_supply_desc = { + .name = "bq24257-charger", + .type = POWER_SUPPLY_TYPE_USB, + .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) +{ + struct power_supply_config psy_cfg = { .drv_data = bq, }; + + psy_cfg.supplied_to = bq24257_charger_supplied_to; + psy_cfg.num_supplicants = ARRAY_SIZE(bq24257_charger_supplied_to); + + bq->charger = devm_power_supply_register(bq->dev, + &bq24257_power_supply_desc, + &psy_cfg); + + return PTR_ERR_OR_ZERO(bq->charger); +} + +static void bq24257_pg_gpio_probe(struct bq24257_device *bq) +{ + bq->pg = devm_gpiod_get_optional(bq->dev, BQ24257_PG_GPIO, GPIOD_IN); + + 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; + } + + 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) +{ + int ret; + u32 property; + + /* Required properties */ + ret = device_property_read_u32(bq->dev, "ti,charge-current", &property); + if (ret < 0) + return ret; + + bq->init_data.ichg = bq24257_find_idx(property, bq24257_ichg_map, + BQ24257_ICHG_MAP_SIZE); + + ret = device_property_read_u32(bq->dev, "ti,battery-regulation-voltage", + &property); + if (ret < 0) + return ret; + + bq->init_data.vbat = bq24257_find_idx(property, bq24257_vbat_map, + BQ24257_VBAT_MAP_SIZE); + + ret = device_property_read_u32(bq->dev, "ti,termination-current", + &property); + if (ret < 0) + return ret; + + 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; +} + +static int bq24257_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; + const struct acpi_device_id *acpi_id; + struct bq24257_device *bq; + int ret; + int i; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) { + dev_err(dev, "No support for SMBUS_BYTE_DATA\n"); + return -ENODEV; + } + + bq = devm_kzalloc(dev, sizeof(*bq), GFP_KERNEL); + if (!bq) + return -ENOMEM; + + 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); + if (IS_ERR(bq->rmap)) { + dev_err(dev, "failed to allocate register map\n"); + return PTR_ERR(bq->rmap); + } + + for (i = 0; i < ARRAY_SIZE(bq24257_reg_fields); i++) { + const struct reg_field *reg_fields = bq24257_reg_fields; + + bq->rmap_fields[i] = devm_regmap_field_alloc(dev, bq->rmap, + reg_fields[i]); + if (IS_ERR(bq->rmap_fields[i])) { + dev_err(dev, "cannot allocate regmap field\n"); + return PTR_ERR(bq->rmap_fields[i]); + } + } + + i2c_set_clientdata(client, bq); + + if (!dev->platform_data) { + ret = bq24257_fw_probe(bq); + if (ret < 0) { + dev_err(dev, "Cannot read device properties.\n"); + return ret; + } + } else { + return -ENODEV; + } + + /* + * 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); + if (ret < 0) + return ret; + + /* + * Put the RESET bit back to 0, in cache. For some reason the HW always + * returns 1 on this bit, so this is the only way to avoid resetting the + * chip every time we update another field in this register. + */ + ret = bq24257_field_write(bq, F_RESET, 0); + if (ret < 0) + return ret; + + ret = bq24257_hw_init(bq); + if (ret < 0) { + dev_err(dev, "Cannot initialize the chip.\n"); + return ret; + } + + ret = devm_request_threaded_irq(dev, client->irq, NULL, + bq24257_irq_handler_thread, + IRQF_TRIGGER_FALLING | + IRQF_TRIGGER_RISING | IRQF_ONESHOT, + 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) { + dev_err(dev, "Failed to register power supply\n"); + 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); + + if (bq->iilimit_autoset_enable) + cancel_delayed_work_sync(&bq->iilimit_setup_work); + + sysfs_remove_group(&bq->charger->dev.kobj, &bq24257_attr_group); + + bq24257_field_write(bq, F_RESET, 1); /* reset to defaults */ + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int bq24257_suspend(struct device *dev) +{ + struct bq24257_device *bq = dev_get_drvdata(dev); + int ret = 0; + + 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); + if (ret < 0) + dev_err(bq->dev, "Cannot reset chip to standalone mode.\n"); + + return ret; +} + +static int bq24257_resume(struct device *dev) +{ + int ret; + struct bq24257_device *bq = dev_get_drvdata(dev); + + ret = regcache_drop_region(bq->rmap, BQ24257_REG_1, BQ24257_REG_7); + if (ret < 0) + return ret; + + ret = bq24257_field_write(bq, F_RESET, 0); + if (ret < 0) + return ret; + + ret = bq24257_hw_init(bq); + if (ret < 0) { + dev_err(bq->dev, "Cannot init chip after resume.\n"); + return ret; + } + + /* signal userspace, maybe state changed while suspended */ + power_supply_changed(bq->charger); + + return 0; +} +#endif + +static const struct dev_pm_ops bq24257_pm = { + SET_SYSTEM_SLEEP_PM_OPS(bq24257_suspend, bq24257_resume) +}; + +static const struct i2c_device_id bq24257_i2c_ids[] = { + { "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[] = { + { "BQ242500", BQ24250 }, + { "BQ242510", BQ24251 }, + { "BQ242570", BQ24257 }, + {}, +}; +MODULE_DEVICE_TABLE(acpi, bq24257_acpi_match); + +static struct i2c_driver bq24257_driver = { + .driver = { + .name = "bq24257-charger", + .of_match_table = of_match_ptr(bq24257_of_match), + .acpi_match_table = ACPI_PTR(bq24257_acpi_match), + .pm = &bq24257_pm, + }, + .probe = bq24257_probe, + .remove = bq24257_remove, + .id_table = bq24257_i2c_ids, +}; +module_i2c_driver(bq24257_driver); + +MODULE_AUTHOR("Laurentiu Palcu "); +MODULE_DESCRIPTION("bq24257 charger driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/power/supply/bq24735-charger.c b/drivers/power/supply/bq24735-charger.c new file mode 100644 index 000000000000..fa454c19ce17 --- /dev/null +++ b/drivers/power/supply/bq24735-charger.c @@ -0,0 +1,500 @@ +/* + * Battery charger driver for TI BQ24735 + * + * Copyright (c) 2013, NVIDIA CORPORATION. All rights reserved. + * + * 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; + * + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define BQ24735_CHG_OPT 0x12 +#define BQ24735_CHG_OPT_CHARGE_DISABLE (1 << 0) +#define BQ24735_CHG_OPT_AC_PRESENT (1 << 4) +#define BQ24735_CHARGE_CURRENT 0x14 +#define BQ24735_CHARGE_CURRENT_MASK 0x1fc0 +#define BQ24735_CHARGE_VOLTAGE 0x15 +#define BQ24735_CHARGE_VOLTAGE_MASK 0x7ff0 +#define BQ24735_INPUT_CURRENT 0x3f +#define BQ24735_INPUT_CURRENT_MASK 0x1f80 +#define BQ24735_MANUFACTURER_ID 0xfe +#define BQ24735_DEVICE_ID 0xff + +struct bq24735 { + struct power_supply *charger; + struct power_supply_desc charger_desc; + struct i2c_client *client; + struct bq24735_platform *pdata; + struct mutex lock; + bool charging; +}; + +static inline struct bq24735 *to_bq24735(struct power_supply *psy) +{ + return power_supply_get_drvdata(psy); +} + +static enum power_supply_property bq24735_charger_properties[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_ONLINE, +}; + +static int bq24735_charger_property_is_writeable(struct power_supply *psy, + enum power_supply_property psp) +{ + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + return 1; + default: + break; + } + + return 0; +} + +static inline int bq24735_write_word(struct i2c_client *client, u8 reg, + u16 value) +{ + return i2c_smbus_write_word_data(client, reg, le16_to_cpu(value)); +} + +static inline int bq24735_read_word(struct i2c_client *client, u8 reg) +{ + s32 ret = i2c_smbus_read_word_data(client, reg); + + return ret < 0 ? ret : le16_to_cpu(ret); +} + +static int bq24735_update_word(struct i2c_client *client, u8 reg, + u16 mask, u16 value) +{ + unsigned int tmp; + int ret; + + ret = bq24735_read_word(client, reg); + if (ret < 0) + return ret; + + tmp = ret & ~mask; + tmp |= value & mask; + + 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; + int ret; + u16 value; + + if (pdata->ext_control) + return 0; + + if (pdata->charge_current) { + value = pdata->charge_current & BQ24735_CHARGE_CURRENT_MASK; + + ret = bq24735_write_word(charger->client, + BQ24735_CHARGE_CURRENT, value); + if (ret < 0) { + dev_err(&charger->client->dev, + "Failed to write charger current : %d\n", + ret); + return ret; + } + } + + if (pdata->charge_voltage) { + value = pdata->charge_voltage & BQ24735_CHARGE_VOLTAGE_MASK; + + ret = bq24735_write_word(charger->client, + BQ24735_CHARGE_VOLTAGE, value); + if (ret < 0) { + dev_err(&charger->client->dev, + "Failed to write charger voltage : %d\n", + ret); + return ret; + } + } + + if (pdata->input_current) { + value = pdata->input_current & BQ24735_INPUT_CURRENT_MASK; + + ret = bq24735_write_word(charger->client, + BQ24735_INPUT_CURRENT, value); + if (ret < 0) { + dev_err(&charger->client->dev, + "Failed to write input current : %d\n", + ret); + return ret; + } + } + + return 0; +} + +static bool bq24735_charger_is_present(struct bq24735 *charger) +{ + struct bq24735_platform *pdata = charger->pdata; + int ret; + + if (pdata->status_gpio_valid) { + ret = gpio_get_value_cansleep(pdata->status_gpio); + return ret ^= pdata->status_gpio_active_low == 0; + } else { + int ac = 0; + + ac = bq24735_read_word(charger->client, BQ24735_CHG_OPT); + if (ac < 0) { + dev_err(&charger->client->dev, + "Failed to read charger options : %d\n", + ac); + return false; + } + return (ac & BQ24735_CHG_OPT_AC_PRESENT) ? true : false; + } + + return false; +} + +static int bq24735_charger_is_charging(struct bq24735 *charger) +{ + int ret = bq24735_read_word(charger->client, BQ24735_CHG_OPT); + + if (ret < 0) + return ret; + + return !(ret & BQ24735_CHG_OPT_CHARGE_DISABLE); +} + +static irqreturn_t bq24735_charger_isr(int irq, void *devid) +{ + struct power_supply *psy = devid; + struct bq24735 *charger = to_bq24735(psy); + + mutex_lock(&charger->lock); + + if (charger->charging && bq24735_charger_is_present(charger)) + bq24735_enable_charging(charger); + else + bq24735_disable_charging(charger); + + mutex_unlock(&charger->lock); + + power_supply_changed(psy); + + return IRQ_HANDLED; +} + +static int bq24735_charger_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct bq24735 *charger = to_bq24735(psy); + + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + val->intval = bq24735_charger_is_present(charger) ? 1 : 0; + break; + case POWER_SUPPLY_PROP_STATUS: + switch (bq24735_charger_is_charging(charger)) { + case 1: + val->intval = POWER_SUPPLY_STATUS_CHARGING; + break; + case 0: + val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; + break; + default: + val->intval = POWER_SUPPLY_STATUS_UNKNOWN; + break; + } + break; + default: + return -EINVAL; + } + + return 0; +} + +static int bq24735_charger_set_property(struct power_supply *psy, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct bq24735 *charger = to_bq24735(psy); + int ret; + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + switch (val->intval) { + case POWER_SUPPLY_STATUS_CHARGING: + mutex_lock(&charger->lock); + charger->charging = true; + ret = bq24735_enable_charging(charger); + mutex_unlock(&charger->lock); + if (ret) + return ret; + bq24735_config_charger(charger); + break; + case POWER_SUPPLY_STATUS_DISCHARGING: + case POWER_SUPPLY_STATUS_NOT_CHARGING: + mutex_lock(&charger->lock); + charger->charging = false; + ret = bq24735_disable_charging(charger); + mutex_unlock(&charger->lock); + if (ret) + return ret; + break; + default: + return -EINVAL; + } + power_supply_changed(psy); + break; + default: + return -EPERM; + } + + return 0; +} + +static struct bq24735_platform *bq24735_parse_dt_data(struct i2c_client *client) +{ + struct bq24735_platform *pdata; + struct device_node *np = client->dev.of_node; + u32 val; + int ret; + enum of_gpio_flags flags; + + pdata = devm_kzalloc(&client->dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) { + dev_err(&client->dev, + "Memory alloc for bq24735 pdata failed\n"); + return NULL; + } + + pdata->status_gpio = of_get_named_gpio_flags(np, "ti,ac-detect-gpios", + 0, &flags); + + if (flags & OF_GPIO_ACTIVE_LOW) + pdata->status_gpio_active_low = 1; + + ret = of_property_read_u32(np, "ti,charge-current", &val); + if (!ret) + pdata->charge_current = val; + + ret = of_property_read_u32(np, "ti,charge-voltage", &val); + if (!ret) + pdata->charge_voltage = val; + + ret = of_property_read_u32(np, "ti,input-current", &val); + if (!ret) + pdata->input_current = val; + + pdata->ext_control = of_property_read_bool(np, "ti,external-control"); + + return pdata; +} + +static int bq24735_charger_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int ret; + struct bq24735 *charger; + struct power_supply_desc *supply_desc; + struct power_supply_config psy_cfg = {}; + char *name; + + charger = devm_kzalloc(&client->dev, sizeof(*charger), GFP_KERNEL); + if (!charger) + return -ENOMEM; + + mutex_init(&charger->lock); + charger->charging = true; + charger->pdata = client->dev.platform_data; + + if (IS_ENABLED(CONFIG_OF) && !charger->pdata && client->dev.of_node) + charger->pdata = bq24735_parse_dt_data(client); + + if (!charger->pdata) { + dev_err(&client->dev, "no platform data provided\n"); + return -EINVAL; + } + + name = (char *)charger->pdata->name; + if (!name) { + name = devm_kasprintf(&client->dev, GFP_KERNEL, + "bq24735@%s", + dev_name(&client->dev)); + if (!name) { + dev_err(&client->dev, "Failed to alloc device name\n"); + return -ENOMEM; + } + } + + charger->client = client; + + supply_desc = &charger->charger_desc; + + supply_desc->name = name; + supply_desc->type = POWER_SUPPLY_TYPE_MAINS; + supply_desc->properties = bq24735_charger_properties; + supply_desc->num_properties = ARRAY_SIZE(bq24735_charger_properties); + supply_desc->get_property = bq24735_charger_get_property; + supply_desc->set_property = bq24735_charger_set_property; + supply_desc->property_is_writeable = + bq24735_charger_property_is_writeable; + + psy_cfg.supplied_to = charger->pdata->supplied_to; + psy_cfg.num_supplicants = charger->pdata->num_supplicants; + psy_cfg.of_node = client->dev.of_node; + psy_cfg.drv_data = charger; + + i2c_set_clientdata(client, charger); + + if (gpio_is_valid(charger->pdata->status_gpio)) { + ret = devm_gpio_request(&client->dev, + charger->pdata->status_gpio, + name); + if (ret) { + dev_err(&client->dev, + "Failed GPIO request for GPIO %d: %d\n", + charger->pdata->status_gpio, ret); + } + + charger->pdata->status_gpio_valid = !ret; + } + + if (!charger->pdata->status_gpio_valid + || 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", + ret); + return ret; + } else if (ret != 0x0040) { + dev_err(&client->dev, + "manufacturer id mismatch. 0x0040 != 0x%04x\n", ret); + return -ENODEV; + } + + ret = bq24735_read_word(client, BQ24735_DEVICE_ID); + if (ret < 0) { + dev_err(&client->dev, "Failed to read device id : %d\n", ret); + return ret; + } else if (ret != 0x000B) { + dev_err(&client->dev, + "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"); + return ret; + } + } + + charger->charger = devm_power_supply_register(&client->dev, supply_desc, + &psy_cfg); + if (IS_ERR(charger->charger)) { + ret = PTR_ERR(charger->charger); + dev_err(&client->dev, "Failed to register power supply: %d\n", + ret); + return ret; + } + + if (client->irq) { + ret = devm_request_threaded_irq(&client->dev, client->irq, + NULL, bq24735_charger_isr, + IRQF_TRIGGER_RISING | + IRQF_TRIGGER_FALLING | + IRQF_ONESHOT, + supply_desc->name, + charger->charger); + if (ret) { + dev_err(&client->dev, + "Unable to register IRQ %d err %d\n", + client->irq, ret); + return ret; + } + } + + return 0; +} + +static const struct i2c_device_id bq24735_charger_id[] = { + { "bq24735-charger", 0 }, + {} +}; +MODULE_DEVICE_TABLE(i2c, bq24735_charger_id); + +static const struct of_device_id bq24735_match_ids[] = { + { .compatible = "ti,bq24735", }, + { /* end */ } +}; +MODULE_DEVICE_TABLE(of, bq24735_match_ids); + +static struct i2c_driver bq24735_charger_driver = { + .driver = { + .name = "bq24735-charger", + .of_match_table = bq24735_match_ids, + }, + .probe = bq24735_charger_probe, + .id_table = bq24735_charger_id, +}; + +module_i2c_driver(bq24735_charger_driver); + +MODULE_DESCRIPTION("bq24735 battery charging driver"); +MODULE_AUTHOR("Darbha Sriharsha "); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/power/supply/bq25890_charger.c b/drivers/power/supply/bq25890_charger.c new file mode 100644 index 000000000000..f993a55cde20 --- /dev/null +++ b/drivers/power/supply/bq25890_charger.c @@ -0,0 +1,994 @@ +/* + * TI BQ25890 charger driver + * + * Copyright (C) 2015 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; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define BQ25890_MANUFACTURER "Texas Instruments" +#define BQ25890_IRQ_PIN "bq25890_irq" + +#define BQ25890_ID 3 + +enum bq25890_fields { + F_EN_HIZ, F_EN_ILIM, F_IILIM, /* Reg00 */ + F_BHOT, F_BCOLD, F_VINDPM_OFS, /* Reg01 */ + F_CONV_START, F_CONV_RATE, F_BOOSTF, F_ICO_EN, + F_HVDCP_EN, F_MAXC_EN, F_FORCE_DPM, F_AUTO_DPDM_EN, /* Reg02 */ + F_BAT_LOAD_EN, F_WD_RST, F_OTG_CFG, F_CHG_CFG, F_SYSVMIN, /* Reg03 */ + F_PUMPX_EN, F_ICHG, /* Reg04 */ + F_IPRECHG, F_ITERM, /* Reg05 */ + F_VREG, F_BATLOWV, F_VRECHG, /* Reg06 */ + F_TERM_EN, F_STAT_DIS, F_WD, F_TMR_EN, F_CHG_TMR, + F_JEITA_ISET, /* Reg07 */ + F_BATCMP, F_VCLAMP, F_TREG, /* Reg08 */ + F_FORCE_ICO, F_TMR2X_EN, F_BATFET_DIS, F_JEITA_VSET, + F_BATFET_DLY, F_BATFET_RST_EN, F_PUMPX_UP, F_PUMPX_DN, /* Reg09 */ + F_BOOSTV, F_BOOSTI, /* Reg0A */ + F_VBUS_STAT, F_CHG_STAT, F_PG_STAT, F_SDP_STAT, F_VSYS_STAT, /* Reg0B */ + F_WD_FAULT, F_BOOST_FAULT, F_CHG_FAULT, F_BAT_FAULT, + F_NTC_FAULT, /* Reg0C */ + F_FORCE_VINDPM, F_VINDPM, /* Reg0D */ + F_THERM_STAT, F_BATV, /* Reg0E */ + F_SYSV, /* Reg0F */ + F_TSPCT, /* Reg10 */ + F_VBUS_GD, F_VBUSV, /* Reg11 */ + F_ICHGR, /* Reg12 */ + F_VDPM_STAT, F_IDPM_STAT, F_IDPM_LIM, /* Reg13 */ + F_REG_RST, F_ICO_OPTIMIZED, F_PN, F_TS_PROFILE, F_DEV_REV, /* Reg14 */ + + F_MAX_FIELDS +}; + +/* initial field values, converted to register values */ +struct bq25890_init_data { + u8 ichg; /* charge current */ + u8 vreg; /* regulation voltage */ + u8 iterm; /* termination current */ + u8 iprechg; /* precharge current */ + u8 sysvmin; /* minimum system voltage limit */ + u8 boostv; /* boost regulation voltage */ + u8 boosti; /* boost current limit */ + u8 boostf; /* boost frequency */ + u8 ilim_en; /* enable ILIM pin */ + u8 treg; /* thermal regulation threshold */ +}; + +struct bq25890_state { + u8 online; + u8 chrg_status; + u8 chrg_fault; + u8 vsys_status; + u8 boost_fault; + u8 bat_fault; +}; + +struct bq25890_device { + struct i2c_client *client; + struct device *dev; + struct power_supply *charger; + + struct usb_phy *usb_phy; + struct notifier_block usb_nb; + struct work_struct usb_work; + unsigned long usb_event; + + struct regmap *rmap; + struct regmap_field *rmap_fields[F_MAX_FIELDS]; + + int chip_id; + struct bq25890_init_data init_data; + struct bq25890_state state; + + struct mutex lock; /* protect state data */ +}; + +static const struct regmap_range bq25890_readonly_reg_ranges[] = { + regmap_reg_range(0x0b, 0x0c), + regmap_reg_range(0x0e, 0x13), +}; + +static const struct regmap_access_table bq25890_writeable_regs = { + .no_ranges = bq25890_readonly_reg_ranges, + .n_no_ranges = ARRAY_SIZE(bq25890_readonly_reg_ranges), +}; + +static const struct regmap_range bq25890_volatile_reg_ranges[] = { + regmap_reg_range(0x00, 0x00), + regmap_reg_range(0x09, 0x09), + regmap_reg_range(0x0b, 0x0c), + regmap_reg_range(0x0e, 0x14), +}; + +static const struct regmap_access_table bq25890_volatile_regs = { + .yes_ranges = bq25890_volatile_reg_ranges, + .n_yes_ranges = ARRAY_SIZE(bq25890_volatile_reg_ranges), +}; + +static const struct regmap_config bq25890_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = 0x14, + .cache_type = REGCACHE_RBTREE, + + .wr_table = &bq25890_writeable_regs, + .volatile_table = &bq25890_volatile_regs, +}; + +static const struct reg_field bq25890_reg_fields[] = { + /* REG00 */ + [F_EN_HIZ] = REG_FIELD(0x00, 7, 7), + [F_EN_ILIM] = REG_FIELD(0x00, 6, 6), + [F_IILIM] = REG_FIELD(0x00, 0, 5), + /* REG01 */ + [F_BHOT] = REG_FIELD(0x01, 6, 7), + [F_BCOLD] = REG_FIELD(0x01, 5, 5), + [F_VINDPM_OFS] = REG_FIELD(0x01, 0, 4), + /* REG02 */ + [F_CONV_START] = REG_FIELD(0x02, 7, 7), + [F_CONV_RATE] = REG_FIELD(0x02, 6, 6), + [F_BOOSTF] = REG_FIELD(0x02, 5, 5), + [F_ICO_EN] = REG_FIELD(0x02, 4, 4), + [F_HVDCP_EN] = REG_FIELD(0x02, 3, 3), + [F_MAXC_EN] = REG_FIELD(0x02, 2, 2), + [F_FORCE_DPM] = REG_FIELD(0x02, 1, 1), + [F_AUTO_DPDM_EN] = REG_FIELD(0x02, 0, 0), + /* REG03 */ + [F_BAT_LOAD_EN] = REG_FIELD(0x03, 7, 7), + [F_WD_RST] = REG_FIELD(0x03, 6, 6), + [F_OTG_CFG] = REG_FIELD(0x03, 5, 5), + [F_CHG_CFG] = REG_FIELD(0x03, 4, 4), + [F_SYSVMIN] = REG_FIELD(0x03, 1, 3), + /* REG04 */ + [F_PUMPX_EN] = REG_FIELD(0x04, 7, 7), + [F_ICHG] = REG_FIELD(0x04, 0, 6), + /* REG05 */ + [F_IPRECHG] = REG_FIELD(0x05, 4, 7), + [F_ITERM] = REG_FIELD(0x05, 0, 3), + /* REG06 */ + [F_VREG] = REG_FIELD(0x06, 2, 7), + [F_BATLOWV] = REG_FIELD(0x06, 1, 1), + [F_VRECHG] = REG_FIELD(0x06, 0, 0), + /* REG07 */ + [F_TERM_EN] = REG_FIELD(0x07, 7, 7), + [F_STAT_DIS] = REG_FIELD(0x07, 6, 6), + [F_WD] = REG_FIELD(0x07, 4, 5), + [F_TMR_EN] = REG_FIELD(0x07, 3, 3), + [F_CHG_TMR] = REG_FIELD(0x07, 1, 2), + [F_JEITA_ISET] = REG_FIELD(0x07, 0, 0), + /* REG08 */ + [F_BATCMP] = REG_FIELD(0x08, 6, 7), + [F_VCLAMP] = REG_FIELD(0x08, 2, 4), + [F_TREG] = REG_FIELD(0x08, 0, 1), + /* REG09 */ + [F_FORCE_ICO] = REG_FIELD(0x09, 7, 7), + [F_TMR2X_EN] = REG_FIELD(0x09, 6, 6), + [F_BATFET_DIS] = REG_FIELD(0x09, 5, 5), + [F_JEITA_VSET] = REG_FIELD(0x09, 4, 4), + [F_BATFET_DLY] = REG_FIELD(0x09, 3, 3), + [F_BATFET_RST_EN] = REG_FIELD(0x09, 2, 2), + [F_PUMPX_UP] = REG_FIELD(0x09, 1, 1), + [F_PUMPX_DN] = REG_FIELD(0x09, 0, 0), + /* REG0A */ + [F_BOOSTV] = REG_FIELD(0x0A, 4, 7), + [F_BOOSTI] = REG_FIELD(0x0A, 0, 2), + /* REG0B */ + [F_VBUS_STAT] = REG_FIELD(0x0B, 5, 7), + [F_CHG_STAT] = REG_FIELD(0x0B, 3, 4), + [F_PG_STAT] = REG_FIELD(0x0B, 2, 2), + [F_SDP_STAT] = REG_FIELD(0x0B, 1, 1), + [F_VSYS_STAT] = REG_FIELD(0x0B, 0, 0), + /* REG0C */ + [F_WD_FAULT] = REG_FIELD(0x0C, 7, 7), + [F_BOOST_FAULT] = REG_FIELD(0x0C, 6, 6), + [F_CHG_FAULT] = REG_FIELD(0x0C, 4, 5), + [F_BAT_FAULT] = REG_FIELD(0x0C, 3, 3), + [F_NTC_FAULT] = REG_FIELD(0x0C, 0, 2), + /* REG0D */ + [F_FORCE_VINDPM] = REG_FIELD(0x0D, 7, 7), + [F_VINDPM] = REG_FIELD(0x0D, 0, 6), + /* REG0E */ + [F_THERM_STAT] = REG_FIELD(0x0E, 7, 7), + [F_BATV] = REG_FIELD(0x0E, 0, 6), + /* REG0F */ + [F_SYSV] = REG_FIELD(0x0F, 0, 6), + /* REG10 */ + [F_TSPCT] = REG_FIELD(0x10, 0, 6), + /* REG11 */ + [F_VBUS_GD] = REG_FIELD(0x11, 7, 7), + [F_VBUSV] = REG_FIELD(0x11, 0, 6), + /* REG12 */ + [F_ICHGR] = REG_FIELD(0x12, 0, 6), + /* REG13 */ + [F_VDPM_STAT] = REG_FIELD(0x13, 7, 7), + [F_IDPM_STAT] = REG_FIELD(0x13, 6, 6), + [F_IDPM_LIM] = REG_FIELD(0x13, 0, 5), + /* REG14 */ + [F_REG_RST] = REG_FIELD(0x14, 7, 7), + [F_ICO_OPTIMIZED] = REG_FIELD(0x14, 6, 6), + [F_PN] = REG_FIELD(0x14, 3, 5), + [F_TS_PROFILE] = REG_FIELD(0x14, 2, 2), + [F_DEV_REV] = REG_FIELD(0x14, 0, 1) +}; + +/* + * Most of the val -> idx conversions can be computed, given the minimum, + * maximum and the step between values. For the rest of conversions, we use + * lookup tables. + */ +enum bq25890_table_ids { + /* range tables */ + TBL_ICHG, + TBL_ITERM, + TBL_IPRECHG, + TBL_VREG, + TBL_BATCMP, + TBL_VCLAMP, + TBL_BOOSTV, + TBL_SYSVMIN, + + /* lookup tables */ + TBL_TREG, + TBL_BOOSTI, +}; + +/* Thermal Regulation Threshold lookup table, in degrees Celsius */ +static const u32 bq25890_treg_tbl[] = { 60, 80, 100, 120 }; + +#define BQ25890_TREG_TBL_SIZE ARRAY_SIZE(bq25890_treg_tbl) + +/* Boost mode current limit lookup table, in uA */ +static const u32 bq25890_boosti_tbl[] = { + 500000, 700000, 1100000, 1300000, 1600000, 1800000, 2100000, 2400000 +}; + +#define BQ25890_BOOSTI_TBL_SIZE ARRAY_SIZE(bq25890_boosti_tbl) + +struct bq25890_range { + u32 min; + u32 max; + u32 step; +}; + +struct bq25890_lookup { + const u32 *tbl; + u32 size; +}; + +static const union { + struct bq25890_range rt; + struct bq25890_lookup lt; +} bq25890_tables[] = { + /* range tables */ + [TBL_ICHG] = { .rt = {0, 5056000, 64000} }, /* uA */ + [TBL_ITERM] = { .rt = {64000, 1024000, 64000} }, /* uA */ + [TBL_VREG] = { .rt = {3840000, 4608000, 16000} }, /* uV */ + [TBL_BATCMP] = { .rt = {0, 140, 20} }, /* mOhm */ + [TBL_VCLAMP] = { .rt = {0, 224000, 32000} }, /* uV */ + [TBL_BOOSTV] = { .rt = {4550000, 5510000, 64000} }, /* uV */ + [TBL_SYSVMIN] = { .rt = {3000000, 3700000, 100000} }, /* uV */ + + /* lookup tables */ + [TBL_TREG] = { .lt = {bq25890_treg_tbl, BQ25890_TREG_TBL_SIZE} }, + [TBL_BOOSTI] = { .lt = {bq25890_boosti_tbl, BQ25890_BOOSTI_TBL_SIZE} } +}; + +static int bq25890_field_read(struct bq25890_device *bq, + enum bq25890_fields field_id) +{ + int ret; + int val; + + ret = regmap_field_read(bq->rmap_fields[field_id], &val); + if (ret < 0) + return ret; + + return val; +} + +static int bq25890_field_write(struct bq25890_device *bq, + enum bq25890_fields field_id, u8 val) +{ + return regmap_field_write(bq->rmap_fields[field_id], val); +} + +static u8 bq25890_find_idx(u32 value, enum bq25890_table_ids id) +{ + u8 idx; + + if (id >= TBL_TREG) { + const u32 *tbl = bq25890_tables[id].lt.tbl; + u32 tbl_size = bq25890_tables[id].lt.size; + + for (idx = 1; idx < tbl_size && tbl[idx] <= value; idx++) + ; + } else { + const struct bq25890_range *rtbl = &bq25890_tables[id].rt; + u8 rtbl_size; + + rtbl_size = (rtbl->max - rtbl->min) / rtbl->step + 1; + + for (idx = 1; + idx < rtbl_size && (idx * rtbl->step + rtbl->min <= value); + idx++) + ; + } + + return idx - 1; +} + +static u32 bq25890_find_val(u8 idx, enum bq25890_table_ids id) +{ + const struct bq25890_range *rtbl; + + /* lookup table? */ + if (id >= TBL_TREG) + return bq25890_tables[id].lt.tbl[idx]; + + /* range table */ + rtbl = &bq25890_tables[id].rt; + + return (rtbl->min + idx * rtbl->step); +} + +enum bq25890_status { + STATUS_NOT_CHARGING, + STATUS_PRE_CHARGING, + STATUS_FAST_CHARGING, + STATUS_TERMINATION_DONE, +}; + +enum bq25890_chrg_fault { + CHRG_FAULT_NORMAL, + CHRG_FAULT_INPUT, + CHRG_FAULT_THERMAL_SHUTDOWN, + CHRG_FAULT_TIMER_EXPIRED, +}; + +static int bq25890_power_supply_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + int ret; + struct bq25890_device *bq = power_supply_get_drvdata(psy); + struct bq25890_state state; + + mutex_lock(&bq->lock); + state = bq->state; + mutex_unlock(&bq->lock); + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + if (!state.online) + val->intval = POWER_SUPPLY_STATUS_DISCHARGING; + else if (state.chrg_status == STATUS_NOT_CHARGING) + val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; + else if (state.chrg_status == STATUS_PRE_CHARGING || + state.chrg_status == STATUS_FAST_CHARGING) + val->intval = POWER_SUPPLY_STATUS_CHARGING; + else if (state.chrg_status == STATUS_TERMINATION_DONE) + val->intval = POWER_SUPPLY_STATUS_FULL; + else + val->intval = POWER_SUPPLY_STATUS_UNKNOWN; + + break; + + case POWER_SUPPLY_PROP_MANUFACTURER: + val->strval = BQ25890_MANUFACTURER; + break; + + case POWER_SUPPLY_PROP_ONLINE: + val->intval = state.online; + break; + + case POWER_SUPPLY_PROP_HEALTH: + if (!state.chrg_fault && !state.bat_fault && !state.boost_fault) + val->intval = POWER_SUPPLY_HEALTH_GOOD; + else if (state.bat_fault) + val->intval = POWER_SUPPLY_HEALTH_OVERVOLTAGE; + else if (state.chrg_fault == CHRG_FAULT_TIMER_EXPIRED) + val->intval = POWER_SUPPLY_HEALTH_SAFETY_TIMER_EXPIRE; + else if (state.chrg_fault == CHRG_FAULT_THERMAL_SHUTDOWN) + val->intval = POWER_SUPPLY_HEALTH_OVERHEAT; + else + val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; + break; + + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: + ret = bq25890_field_read(bq, F_ICHGR); /* read measured value */ + if (ret < 0) + return ret; + + /* converted_val = ADC_val * 50mA (table 10.3.19) */ + val->intval = ret * 50000; + break; + + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX: + val->intval = bq25890_tables[TBL_ICHG].rt.max; + break; + + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: + if (!state.online) { + val->intval = 0; + break; + } + + ret = bq25890_field_read(bq, F_BATV); /* read measured value */ + if (ret < 0) + return ret; + + /* converted_val = 2.304V + ADC_val * 20mV (table 10.3.15) */ + val->intval = 2304000 + ret * 20000; + break; + + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX: + val->intval = bq25890_tables[TBL_VREG].rt.max; + break; + + case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT: + val->intval = bq25890_find_val(bq->init_data.iterm, TBL_ITERM); + break; + + default: + return -EINVAL; + } + + return 0; +} + +static int bq25890_get_chip_state(struct bq25890_device *bq, + struct bq25890_state *state) +{ + int i, ret; + + struct { + enum bq25890_fields id; + u8 *data; + } state_fields[] = { + {F_CHG_STAT, &state->chrg_status}, + {F_PG_STAT, &state->online}, + {F_VSYS_STAT, &state->vsys_status}, + {F_BOOST_FAULT, &state->boost_fault}, + {F_BAT_FAULT, &state->bat_fault}, + {F_CHG_FAULT, &state->chrg_fault} + }; + + for (i = 0; i < ARRAY_SIZE(state_fields); i++) { + ret = bq25890_field_read(bq, state_fields[i].id); + if (ret < 0) + return ret; + + *state_fields[i].data = ret; + } + + dev_dbg(bq->dev, "S:CHG/PG/VSYS=%d/%d/%d, F:CHG/BOOST/BAT=%d/%d/%d\n", + state->chrg_status, state->online, state->vsys_status, + state->chrg_fault, state->boost_fault, state->bat_fault); + + return 0; +} + +static bool bq25890_state_changed(struct bq25890_device *bq, + struct bq25890_state *new_state) +{ + struct bq25890_state old_state; + + mutex_lock(&bq->lock); + old_state = bq->state; + mutex_unlock(&bq->lock); + + return (old_state.chrg_status != new_state->chrg_status || + old_state.chrg_fault != new_state->chrg_fault || + old_state.online != new_state->online || + old_state.bat_fault != new_state->bat_fault || + old_state.boost_fault != new_state->boost_fault || + old_state.vsys_status != new_state->vsys_status); +} + +static void bq25890_handle_state_change(struct bq25890_device *bq, + struct bq25890_state *new_state) +{ + int ret; + struct bq25890_state old_state; + + mutex_lock(&bq->lock); + old_state = bq->state; + mutex_unlock(&bq->lock); + + if (!new_state->online) { /* power removed */ + /* disable ADC */ + ret = bq25890_field_write(bq, F_CONV_START, 0); + if (ret < 0) + goto error; + } else if (!old_state.online) { /* power inserted */ + /* enable ADC, to have control of charge current/voltage */ + ret = bq25890_field_write(bq, F_CONV_START, 1); + if (ret < 0) + goto error; + } + + return; + +error: + dev_err(bq->dev, "Error communicating with the chip.\n"); +} + +static irqreturn_t bq25890_irq_handler_thread(int irq, void *private) +{ + struct bq25890_device *bq = private; + int ret; + struct bq25890_state state; + + ret = bq25890_get_chip_state(bq, &state); + if (ret < 0) + goto handled; + + if (!bq25890_state_changed(bq, &state)) + goto handled; + + bq25890_handle_state_change(bq, &state); + + mutex_lock(&bq->lock); + bq->state = state; + mutex_unlock(&bq->lock); + + power_supply_changed(bq->charger); + +handled: + return IRQ_HANDLED; +} + +static int bq25890_chip_reset(struct bq25890_device *bq) +{ + int ret; + int rst_check_counter = 10; + + ret = bq25890_field_write(bq, F_REG_RST, 1); + if (ret < 0) + return ret; + + do { + ret = bq25890_field_read(bq, F_REG_RST); + if (ret < 0) + return ret; + + usleep_range(5, 10); + } while (ret == 1 && --rst_check_counter); + + if (!rst_check_counter) + return -ETIMEDOUT; + + return 0; +} + +static int bq25890_hw_init(struct bq25890_device *bq) +{ + int ret; + int i; + struct bq25890_state state; + + const struct { + enum bq25890_fields id; + u32 value; + } init_data[] = { + {F_ICHG, bq->init_data.ichg}, + {F_VREG, bq->init_data.vreg}, + {F_ITERM, bq->init_data.iterm}, + {F_IPRECHG, bq->init_data.iprechg}, + {F_SYSVMIN, bq->init_data.sysvmin}, + {F_BOOSTV, bq->init_data.boostv}, + {F_BOOSTI, bq->init_data.boosti}, + {F_BOOSTF, bq->init_data.boostf}, + {F_EN_ILIM, bq->init_data.ilim_en}, + {F_TREG, bq->init_data.treg} + }; + + ret = bq25890_chip_reset(bq); + if (ret < 0) + return ret; + + /* disable watchdog */ + ret = bq25890_field_write(bq, F_WD, 0); + if (ret < 0) + return ret; + + /* initialize currents/voltages and other parameters */ + for (i = 0; i < ARRAY_SIZE(init_data); i++) { + ret = bq25890_field_write(bq, init_data[i].id, + init_data[i].value); + if (ret < 0) + return ret; + } + + /* Configure ADC for continuous conversions. This does not enable it. */ + ret = bq25890_field_write(bq, F_CONV_RATE, 1); + if (ret < 0) + return ret; + + ret = bq25890_get_chip_state(bq, &state); + if (ret < 0) + return ret; + + mutex_lock(&bq->lock); + bq->state = state; + mutex_unlock(&bq->lock); + + return 0; +} + +static enum power_supply_property bq25890_power_supply_props[] = { + POWER_SUPPLY_PROP_MANUFACTURER, + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX, + POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT, +}; + +static char *bq25890_charger_supplied_to[] = { + "main-battery", +}; + +static const struct power_supply_desc bq25890_power_supply_desc = { + .name = "bq25890-charger", + .type = POWER_SUPPLY_TYPE_USB, + .properties = bq25890_power_supply_props, + .num_properties = ARRAY_SIZE(bq25890_power_supply_props), + .get_property = bq25890_power_supply_get_property, +}; + +static int bq25890_power_supply_init(struct bq25890_device *bq) +{ + struct power_supply_config psy_cfg = { .drv_data = bq, }; + + psy_cfg.supplied_to = bq25890_charger_supplied_to; + psy_cfg.num_supplicants = ARRAY_SIZE(bq25890_charger_supplied_to); + + bq->charger = power_supply_register(bq->dev, &bq25890_power_supply_desc, + &psy_cfg); + + return PTR_ERR_OR_ZERO(bq->charger); +} + +static void bq25890_usb_work(struct work_struct *data) +{ + int ret; + struct bq25890_device *bq = + container_of(data, struct bq25890_device, usb_work); + + switch (bq->usb_event) { + case USB_EVENT_ID: + /* Enable boost mode */ + ret = bq25890_field_write(bq, F_OTG_CFG, 1); + if (ret < 0) + goto error; + break; + + case USB_EVENT_NONE: + /* Disable boost mode */ + ret = bq25890_field_write(bq, F_OTG_CFG, 0); + if (ret < 0) + goto error; + + power_supply_changed(bq->charger); + break; + } + + return; + +error: + dev_err(bq->dev, "Error switching to boost/charger mode.\n"); +} + +static int bq25890_usb_notifier(struct notifier_block *nb, unsigned long val, + void *priv) +{ + struct bq25890_device *bq = + container_of(nb, struct bq25890_device, usb_nb); + + bq->usb_event = val; + queue_work(system_power_efficient_wq, &bq->usb_work); + + return NOTIFY_OK; +} + +static int bq25890_irq_probe(struct bq25890_device *bq) +{ + struct gpio_desc *irq; + + irq = devm_gpiod_get_index(bq->dev, BQ25890_IRQ_PIN, 0, GPIOD_IN); + if (IS_ERR(irq)) { + dev_err(bq->dev, "Could not probe irq pin.\n"); + return PTR_ERR(irq); + } + + return gpiod_to_irq(irq); +} + +static int bq25890_fw_read_u32_props(struct bq25890_device *bq) +{ + int ret; + u32 property; + int i; + struct bq25890_init_data *init = &bq->init_data; + struct { + char *name; + bool optional; + enum bq25890_table_ids tbl_id; + u8 *conv_data; /* holds converted value from given property */ + } props[] = { + /* required properties */ + {"ti,charge-current", false, TBL_ICHG, &init->ichg}, + {"ti,battery-regulation-voltage", false, TBL_VREG, &init->vreg}, + {"ti,termination-current", false, TBL_ITERM, &init->iterm}, + {"ti,precharge-current", false, TBL_ITERM, &init->iprechg}, + {"ti,minimum-sys-voltage", false, TBL_SYSVMIN, &init->sysvmin}, + {"ti,boost-voltage", false, TBL_BOOSTV, &init->boostv}, + {"ti,boost-max-current", false, TBL_BOOSTI, &init->boosti}, + + /* optional properties */ + {"ti,thermal-regulation-threshold", true, TBL_TREG, &init->treg} + }; + + /* initialize data for optional properties */ + init->treg = 3; /* 120 degrees Celsius */ + + for (i = 0; i < ARRAY_SIZE(props); i++) { + ret = device_property_read_u32(bq->dev, props[i].name, + &property); + if (ret < 0) { + if (props[i].optional) + continue; + + return ret; + } + + *props[i].conv_data = bq25890_find_idx(property, + props[i].tbl_id); + } + + return 0; +} + +static int bq25890_fw_probe(struct bq25890_device *bq) +{ + int ret; + struct bq25890_init_data *init = &bq->init_data; + + ret = bq25890_fw_read_u32_props(bq); + if (ret < 0) + return ret; + + init->ilim_en = device_property_read_bool(bq->dev, "ti,use-ilim-pin"); + init->boostf = device_property_read_bool(bq->dev, "ti,boost-low-freq"); + + return 0; +} + +static int bq25890_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 bq25890_device *bq; + int ret; + int i; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) { + dev_err(dev, "No support for SMBUS_BYTE_DATA\n"); + return -ENODEV; + } + + bq = devm_kzalloc(dev, sizeof(*bq), GFP_KERNEL); + if (!bq) + return -ENOMEM; + + bq->client = client; + bq->dev = dev; + + mutex_init(&bq->lock); + + bq->rmap = devm_regmap_init_i2c(client, &bq25890_regmap_config); + if (IS_ERR(bq->rmap)) { + dev_err(dev, "failed to allocate register map\n"); + return PTR_ERR(bq->rmap); + } + + for (i = 0; i < ARRAY_SIZE(bq25890_reg_fields); i++) { + const struct reg_field *reg_fields = bq25890_reg_fields; + + bq->rmap_fields[i] = devm_regmap_field_alloc(dev, bq->rmap, + reg_fields[i]); + if (IS_ERR(bq->rmap_fields[i])) { + dev_err(dev, "cannot allocate regmap field\n"); + return PTR_ERR(bq->rmap_fields[i]); + } + } + + i2c_set_clientdata(client, bq); + + bq->chip_id = bq25890_field_read(bq, F_PN); + if (bq->chip_id < 0) { + dev_err(dev, "Cannot read chip ID.\n"); + return bq->chip_id; + } + + if (bq->chip_id != BQ25890_ID) { + dev_err(dev, "Chip with ID=%d, not supported!\n", bq->chip_id); + return -ENODEV; + } + + if (!dev->platform_data) { + ret = bq25890_fw_probe(bq); + if (ret < 0) { + dev_err(dev, "Cannot read device properties.\n"); + return ret; + } + } else { + return -ENODEV; + } + + ret = bq25890_hw_init(bq); + if (ret < 0) { + dev_err(dev, "Cannot initialize the chip.\n"); + return ret; + } + + if (client->irq <= 0) + client->irq = bq25890_irq_probe(bq); + + if (client->irq < 0) { + dev_err(dev, "No irq resource found.\n"); + return client->irq; + } + + /* OTG reporting */ + bq->usb_phy = devm_usb_get_phy(dev, USB_PHY_TYPE_USB2); + if (!IS_ERR_OR_NULL(bq->usb_phy)) { + INIT_WORK(&bq->usb_work, bq25890_usb_work); + bq->usb_nb.notifier_call = bq25890_usb_notifier; + usb_register_notifier(bq->usb_phy, &bq->usb_nb); + } + + ret = devm_request_threaded_irq(dev, client->irq, NULL, + bq25890_irq_handler_thread, + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + BQ25890_IRQ_PIN, bq); + if (ret) + goto irq_fail; + + ret = bq25890_power_supply_init(bq); + if (ret < 0) { + dev_err(dev, "Failed to register power supply\n"); + goto irq_fail; + } + + return 0; + +irq_fail: + if (!IS_ERR_OR_NULL(bq->usb_phy)) + usb_unregister_notifier(bq->usb_phy, &bq->usb_nb); + + return ret; +} + +static int bq25890_remove(struct i2c_client *client) +{ + struct bq25890_device *bq = i2c_get_clientdata(client); + + power_supply_unregister(bq->charger); + + if (!IS_ERR_OR_NULL(bq->usb_phy)) + usb_unregister_notifier(bq->usb_phy, &bq->usb_nb); + + /* reset all registers to default values */ + bq25890_chip_reset(bq); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int bq25890_suspend(struct device *dev) +{ + struct bq25890_device *bq = dev_get_drvdata(dev); + + /* + * If charger is removed, while in suspend, make sure ADC is diabled + * since it consumes slightly more power. + */ + return bq25890_field_write(bq, F_CONV_START, 0); +} + +static int bq25890_resume(struct device *dev) +{ + int ret; + struct bq25890_state state; + struct bq25890_device *bq = dev_get_drvdata(dev); + + ret = bq25890_get_chip_state(bq, &state); + if (ret < 0) + return ret; + + mutex_lock(&bq->lock); + bq->state = state; + mutex_unlock(&bq->lock); + + /* Re-enable ADC only if charger is plugged in. */ + if (state.online) { + ret = bq25890_field_write(bq, F_CONV_START, 1); + if (ret < 0) + return ret; + } + + /* signal userspace, maybe state changed while suspended */ + power_supply_changed(bq->charger); + + return 0; +} +#endif + +static const struct dev_pm_ops bq25890_pm = { + SET_SYSTEM_SLEEP_PM_OPS(bq25890_suspend, bq25890_resume) +}; + +static const struct i2c_device_id bq25890_i2c_ids[] = { + { "bq25890", 0 }, + {}, +}; +MODULE_DEVICE_TABLE(i2c, bq25890_i2c_ids); + +static const struct of_device_id bq25890_of_match[] = { + { .compatible = "ti,bq25890", }, + { }, +}; +MODULE_DEVICE_TABLE(of, bq25890_of_match); + +static const struct acpi_device_id bq25890_acpi_match[] = { + {"BQ258900", 0}, + {}, +}; +MODULE_DEVICE_TABLE(acpi, bq25890_acpi_match); + +static struct i2c_driver bq25890_driver = { + .driver = { + .name = "bq25890-charger", + .of_match_table = of_match_ptr(bq25890_of_match), + .acpi_match_table = ACPI_PTR(bq25890_acpi_match), + .pm = &bq25890_pm, + }, + .probe = bq25890_probe, + .remove = bq25890_remove, + .id_table = bq25890_i2c_ids, +}; +module_i2c_driver(bq25890_driver); + +MODULE_AUTHOR("Laurentiu Palcu "); +MODULE_DESCRIPTION("bq25890 charger driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/power/supply/bq27xxx_battery.c b/drivers/power/supply/bq27xxx_battery.c new file mode 100644 index 000000000000..323d05a12f9b --- /dev/null +++ b/drivers/power/supply/bq27xxx_battery.c @@ -0,0 +1,1102 @@ +/* + * BQ27xxx battery driver + * + * Copyright (C) 2008 Rodolfo Giometti + * Copyright (C) 2008 Eurotech S.p.A. + * Copyright (C) 2010-2011 Lars-Peter Clausen + * Copyright (C) 2011 Pali Rohár + * + * Based on a previous work by Copyright (C) 2008 Texas Instruments, Inc. + * + * This package is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED + * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Datasheets: + * http://www.ti.com/product/bq27000 + * http://www.ti.com/product/bq27200 + * http://www.ti.com/product/bq27010 + * http://www.ti.com/product/bq27210 + * http://www.ti.com/product/bq27500 + * http://www.ti.com/product/bq27510-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 + * http://www.ti.com/product/bq27542-g1 + * http://www.ti.com/product/bq27546-g1 + * http://www.ti.com/product/bq27742-g1 + * http://www.ti.com/product/bq27545-g1 + * http://www.ti.com/product/bq27421-g1 + * http://www.ti.com/product/bq27425-g1 + * http://www.ti.com/product/bq27411-g1 + * http://www.ti.com/product/bq27621-g1 + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define DRIVER_VERSION "1.2.0" + +#define BQ27XXX_MANUFACTURER "Texas Instruments" + +/* BQ27XXX Flags */ +#define BQ27XXX_FLAG_DSC BIT(0) +#define BQ27XXX_FLAG_SOCF BIT(1) /* State-of-Charge threshold final */ +#define BQ27XXX_FLAG_SOC1 BIT(2) /* State-of-Charge threshold 1 */ +#define BQ27XXX_FLAG_FC BIT(9) +#define BQ27XXX_FLAG_OTD BIT(14) +#define BQ27XXX_FLAG_OTC BIT(15) +#define BQ27XXX_FLAG_UT BIT(14) +#define BQ27XXX_FLAG_OT BIT(15) + +/* BQ27000 has different layout for Flags register */ +#define BQ27000_FLAG_EDVF BIT(0) /* Final End-of-Discharge-Voltage flag */ +#define BQ27000_FLAG_EDV1 BIT(1) /* First End-of-Discharge-Voltage flag */ +#define BQ27000_FLAG_CI BIT(4) /* Capacity Inaccurate flag */ +#define BQ27000_FLAG_FC BIT(5) +#define BQ27000_FLAG_CHGS BIT(7) /* Charge state flag */ + +#define BQ27XXX_RS (20) /* Resistor sense mOhm */ +#define BQ27XXX_POWER_CONSTANT (29200) /* 29.2 µV^2 * 1000 */ +#define BQ27XXX_CURRENT_CONSTANT (3570) /* 3.57 µV * 1000 */ + +#define INVALID_REG_ADDR 0xff + +/* + * bq27xxx_reg_index - Register names + * + * These are indexes into a device's register mapping array. + */ + +enum bq27xxx_reg_index { + BQ27XXX_REG_CTRL = 0, /* Control */ + BQ27XXX_REG_TEMP, /* Temperature */ + BQ27XXX_REG_INT_TEMP, /* Internal Temperature */ + BQ27XXX_REG_VOLT, /* Voltage */ + BQ27XXX_REG_AI, /* Average Current */ + BQ27XXX_REG_FLAGS, /* Flags */ + BQ27XXX_REG_TTE, /* Time-to-Empty */ + BQ27XXX_REG_TTF, /* Time-to-Full */ + BQ27XXX_REG_TTES, /* Time-to-Empty Standby */ + BQ27XXX_REG_TTECP, /* Time-to-Empty at Constant Power */ + BQ27XXX_REG_NAC, /* Nominal Available Capacity */ + BQ27XXX_REG_FCC, /* Full Charge Capacity */ + BQ27XXX_REG_CYCT, /* Cycle Count */ + BQ27XXX_REG_AE, /* Available Energy */ + BQ27XXX_REG_SOC, /* State-of-Charge */ + BQ27XXX_REG_DCAP, /* Design Capacity */ + BQ27XXX_REG_AP, /* Average Power */ + BQ27XXX_REG_MAX, /* sentinel */ +}; + +/* Register mappings */ +static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = { + [BQ27000] = { + [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] = 0x0b, + [BQ27XXX_REG_DCAP] = 0x76, + [BQ27XXX_REG_AP] = 0x24, + }, + [BQ27010] = { + [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] = INVALID_REG_ADDR, + [BQ27XXX_REG_SOC] = 0x0b, + [BQ27XXX_REG_DCAP] = 0x76, + [BQ27XXX_REG_AP] = INVALID_REG_ADDR, + }, + [BQ27500] = { + [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] = 0x2a, + [BQ27XXX_REG_AE] = INVALID_REG_ADDR, + [BQ27XXX_REG_SOC] = 0x2c, + [BQ27XXX_REG_DCAP] = 0x3c, + [BQ27XXX_REG_AP] = INVALID_REG_ADDR, + }, + [BQ27530] = { + [BQ27XXX_REG_CTRL] = 0x00, + [BQ27XXX_REG_TEMP] = 0x06, + [BQ27XXX_REG_INT_TEMP] = 0x32, + [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] = INVALID_REG_ADDR, + [BQ27XXX_REG_TTECP] = INVALID_REG_ADDR, + [BQ27XXX_REG_NAC] = 0x0c, + [BQ27XXX_REG_FCC] = 0x12, + [BQ27XXX_REG_CYCT] = 0x2a, + [BQ27XXX_REG_AE] = INVALID_REG_ADDR, + [BQ27XXX_REG_SOC] = 0x2c, + [BQ27XXX_REG_DCAP] = INVALID_REG_ADDR, + [BQ27XXX_REG_AP] = 0x24, + }, + [BQ27541] = { + [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] = INVALID_REG_ADDR, + [BQ27XXX_REG_TTECP] = INVALID_REG_ADDR, + [BQ27XXX_REG_NAC] = 0x0c, + [BQ27XXX_REG_FCC] = 0x12, + [BQ27XXX_REG_CYCT] = 0x2a, + [BQ27XXX_REG_AE] = INVALID_REG_ADDR, + [BQ27XXX_REG_SOC] = 0x2c, + [BQ27XXX_REG_DCAP] = 0x3c, + [BQ27XXX_REG_AP] = 0x24, + }, + [BQ27545] = { + [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] = INVALID_REG_ADDR, + [BQ27XXX_REG_TTECP] = INVALID_REG_ADDR, + [BQ27XXX_REG_NAC] = 0x0c, + [BQ27XXX_REG_FCC] = 0x12, + [BQ27XXX_REG_CYCT] = 0x2a, + [BQ27XXX_REG_AE] = INVALID_REG_ADDR, + [BQ27XXX_REG_SOC] = 0x2c, + [BQ27XXX_REG_DCAP] = INVALID_REG_ADDR, + [BQ27XXX_REG_AP] = 0x24, + }, + [BQ27421] = { + [BQ27XXX_REG_CTRL] = 0x00, + [BQ27XXX_REG_TEMP] = 0x02, + [BQ27XXX_REG_INT_TEMP] = 0x1e, + [BQ27XXX_REG_VOLT] = 0x04, + [BQ27XXX_REG_AI] = 0x10, + [BQ27XXX_REG_FLAGS] = 0x06, + [BQ27XXX_REG_TTE] = INVALID_REG_ADDR, + [BQ27XXX_REG_TTF] = INVALID_REG_ADDR, + [BQ27XXX_REG_TTES] = INVALID_REG_ADDR, + [BQ27XXX_REG_TTECP] = INVALID_REG_ADDR, + [BQ27XXX_REG_NAC] = 0x08, + [BQ27XXX_REG_FCC] = 0x0e, + [BQ27XXX_REG_CYCT] = INVALID_REG_ADDR, + [BQ27XXX_REG_AE] = INVALID_REG_ADDR, + [BQ27XXX_REG_SOC] = 0x1c, + [BQ27XXX_REG_DCAP] = 0x3c, + [BQ27XXX_REG_AP] = 0x18, + }, +}; + +static enum power_supply_property bq27000_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_EMPTY_AVG, + 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 bq27010_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_EMPTY_AVG, + 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_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_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 bq27530_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_POWER_AVG, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_CYCLE_COUNT, + POWER_SUPPLY_PROP_MANUFACTURER, +}; + +static enum power_supply_property bq27541_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_POWER_AVG, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_MANUFACTURER, +}; + +static enum power_supply_property bq27545_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_HEALTH, + POWER_SUPPLY_PROP_CYCLE_COUNT, + POWER_SUPPLY_PROP_POWER_AVG, + POWER_SUPPLY_PROP_MANUFACTURER, +}; + +static enum power_supply_property bq27421_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_TECHNOLOGY, + POWER_SUPPLY_PROP_CHARGE_FULL, + POWER_SUPPLY_PROP_CHARGE_NOW, + POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, + POWER_SUPPLY_PROP_MANUFACTURER, +}; + +#define BQ27XXX_PROP(_id, _prop) \ + [_id] = { \ + .props = _prop, \ + .size = ARRAY_SIZE(_prop), \ + } + +static struct { + enum power_supply_property *props; + size_t size; +} bq27xxx_battery_props[] = { + BQ27XXX_PROP(BQ27000, bq27000_battery_props), + BQ27XXX_PROP(BQ27010, bq27010_battery_props), + BQ27XXX_PROP(BQ27500, bq27500_battery_props), + BQ27XXX_PROP(BQ27530, bq27530_battery_props), + BQ27XXX_PROP(BQ27541, bq27541_battery_props), + BQ27XXX_PROP(BQ27545, bq27545_battery_props), + BQ27XXX_PROP(BQ27421, bq27421_battery_props), +}; + +static unsigned int poll_interval = 360; +module_param(poll_interval, uint, 0644); +MODULE_PARM_DESC(poll_interval, + "battery poll interval in seconds - 0 disables polling"); + +/* + * Common code for BQ27xxx devices + */ + +static inline int bq27xxx_read(struct bq27xxx_device_info *di, int reg_index, + bool single) +{ + /* Reports EINVAL for invalid/missing registers */ + if (!di || di->regs[reg_index] == INVALID_REG_ADDR) + return -EINVAL; + + return di->bus.read(di, di->regs[reg_index], single); +} + +/* + * Return the battery State-of-Charge + * Or < 0 if something fails. + */ +static int bq27xxx_battery_read_soc(struct bq27xxx_device_info *di) +{ + int soc; + + if (di->chip == BQ27000 || di->chip == BQ27010) + soc = bq27xxx_read(di, BQ27XXX_REG_SOC, true); + else + soc = bq27xxx_read(di, BQ27XXX_REG_SOC, false); + + if (soc < 0) + dev_dbg(di->dev, "error reading State-of-Charge\n"); + + return soc; +} + +/* + * Return a battery charge value in µAh + * Or < 0 if something fails. + */ +static int bq27xxx_battery_read_charge(struct bq27xxx_device_info *di, u8 reg) +{ + int charge; + + charge = bq27xxx_read(di, reg, false); + if (charge < 0) { + dev_dbg(di->dev, "error reading charge register %02x: %d\n", + reg, charge); + return charge; + } + + if (di->chip == BQ27000 || di->chip == BQ27010) + charge *= BQ27XXX_CURRENT_CONSTANT / BQ27XXX_RS; + else + charge *= 1000; + + return charge; +} + +/* + * Return the battery Nominal available capacity in µAh + * Or < 0 if something fails. + */ +static inline int bq27xxx_battery_read_nac(struct bq27xxx_device_info *di) +{ + int flags; + + if (di->chip == BQ27000 || di->chip == BQ27010) { + flags = bq27xxx_read(di, BQ27XXX_REG_FLAGS, true); + if (flags >= 0 && (flags & BQ27000_FLAG_CI)) + return -ENODATA; + } + + return bq27xxx_battery_read_charge(di, BQ27XXX_REG_NAC); +} + +/* + * Return the battery Full Charge Capacity in µAh + * Or < 0 if something fails. + */ +static inline int bq27xxx_battery_read_fcc(struct bq27xxx_device_info *di) +{ + return bq27xxx_battery_read_charge(di, BQ27XXX_REG_FCC); +} + +/* + * Return the Design Capacity in µAh + * Or < 0 if something fails. + */ +static int bq27xxx_battery_read_dcap(struct bq27xxx_device_info *di) +{ + int dcap; + + if (di->chip == BQ27000 || di->chip == BQ27010) + dcap = bq27xxx_read(di, BQ27XXX_REG_DCAP, true); + else + dcap = bq27xxx_read(di, BQ27XXX_REG_DCAP, false); + + if (dcap < 0) { + dev_dbg(di->dev, "error reading initial last measured discharge\n"); + return dcap; + } + + if (di->chip == BQ27000 || di->chip == BQ27010) + dcap = (dcap << 8) * BQ27XXX_CURRENT_CONSTANT / BQ27XXX_RS; + else + dcap *= 1000; + + return dcap; +} + +/* + * Return the battery Available energy in µWh + * Or < 0 if something fails. + */ +static int bq27xxx_battery_read_energy(struct bq27xxx_device_info *di) +{ + int ae; + + ae = bq27xxx_read(di, BQ27XXX_REG_AE, false); + if (ae < 0) { + dev_dbg(di->dev, "error reading available energy\n"); + return ae; + } + + if (di->chip == BQ27000 || di->chip == BQ27010) + ae *= BQ27XXX_POWER_CONSTANT / BQ27XXX_RS; + else + ae *= 1000; + + return ae; +} + +/* + * Return the battery temperature in tenths of degree Kelvin + * Or < 0 if something fails. + */ +static int bq27xxx_battery_read_temperature(struct bq27xxx_device_info *di) +{ + int temp; + + temp = bq27xxx_read(di, BQ27XXX_REG_TEMP, false); + if (temp < 0) { + dev_err(di->dev, "error reading temperature\n"); + return temp; + } + + if (di->chip == BQ27000 || di->chip == BQ27010) + temp = 5 * temp / 2; + + return temp; +} + +/* + * Return the battery Cycle count total + * Or < 0 if something fails. + */ +static int bq27xxx_battery_read_cyct(struct bq27xxx_device_info *di) +{ + int cyct; + + cyct = bq27xxx_read(di, BQ27XXX_REG_CYCT, false); + if (cyct < 0) + dev_err(di->dev, "error reading cycle count total\n"); + + return cyct; +} + +/* + * Read a time register. + * Return < 0 if something fails. + */ +static int bq27xxx_battery_read_time(struct bq27xxx_device_info *di, u8 reg) +{ + int tval; + + tval = bq27xxx_read(di, reg, false); + if (tval < 0) { + dev_dbg(di->dev, "error reading time register %02x: %d\n", + reg, tval); + return tval; + } + + if (tval == 65535) + return -ENODATA; + + return tval * 60; +} + +/* + * Read an average power register. + * Return < 0 if something fails. + */ +static int bq27xxx_battery_read_pwr_avg(struct bq27xxx_device_info *di) +{ + int tval; + + tval = bq27xxx_read(di, BQ27XXX_REG_AP, false); + if (tval < 0) { + dev_err(di->dev, "error reading average power register %02x: %d\n", + BQ27XXX_REG_AP, tval); + return tval; + } + + if (di->chip == BQ27000 || di->chip == BQ27010) + return (tval * BQ27XXX_POWER_CONSTANT) / BQ27XXX_RS; + else + return tval; +} + +/* + * Returns true if a battery over temperature condition is detected + */ +static bool bq27xxx_battery_overtemp(struct bq27xxx_device_info *di, u16 flags) +{ + if (di->chip == BQ27500 || di->chip == BQ27541 || di->chip == BQ27545) + return flags & (BQ27XXX_FLAG_OTC | BQ27XXX_FLAG_OTD); + if (di->chip == BQ27530 || di->chip == BQ27421) + return flags & BQ27XXX_FLAG_OT; + + return false; +} + +/* + * Returns true if a battery under temperature condition is detected + */ +static bool bq27xxx_battery_undertemp(struct bq27xxx_device_info *di, u16 flags) +{ + if (di->chip == BQ27530 || di->chip == BQ27421) + return flags & BQ27XXX_FLAG_UT; + + return false; +} + +/* + * Returns true if a low state of charge condition is detected + */ +static bool bq27xxx_battery_dead(struct bq27xxx_device_info *di, u16 flags) +{ + if (di->chip == BQ27000 || di->chip == BQ27010) + return flags & (BQ27000_FLAG_EDV1 | BQ27000_FLAG_EDVF); + else + return flags & (BQ27XXX_FLAG_SOC1 | BQ27XXX_FLAG_SOCF); +} + +/* + * Read flag register. + * Return < 0 if something fails. + */ +static int bq27xxx_battery_read_health(struct bq27xxx_device_info *di) +{ + int flags; + + flags = bq27xxx_read(di, BQ27XXX_REG_FLAGS, false); + if (flags < 0) { + dev_err(di->dev, "error reading flag register:%d\n", flags); + return flags; + } + + /* Unlikely but important to return first */ + if (unlikely(bq27xxx_battery_overtemp(di, flags))) + return POWER_SUPPLY_HEALTH_OVERHEAT; + if (unlikely(bq27xxx_battery_undertemp(di, flags))) + return POWER_SUPPLY_HEALTH_COLD; + if (unlikely(bq27xxx_battery_dead(di, flags))) + return POWER_SUPPLY_HEALTH_DEAD; + + return POWER_SUPPLY_HEALTH_GOOD; +} + +void bq27xxx_battery_update(struct bq27xxx_device_info *di) +{ + struct bq27xxx_reg_cache cache = {0, }; + bool has_ci_flag = di->chip == BQ27000 || di->chip == BQ27010; + bool has_singe_flag = di->chip == BQ27000 || di->chip == BQ27010; + + cache.flags = bq27xxx_read(di, BQ27XXX_REG_FLAGS, has_singe_flag); + if ((cache.flags & 0xff) == 0xff) + cache.flags = -1; /* read error */ + if (cache.flags >= 0) { + cache.temperature = bq27xxx_battery_read_temperature(di); + if (has_ci_flag && (cache.flags & BQ27000_FLAG_CI)) { + dev_info_once(di->dev, "battery is not calibrated! ignoring capacity values\n"); + cache.capacity = -ENODATA; + cache.energy = -ENODATA; + cache.time_to_empty = -ENODATA; + cache.time_to_empty_avg = -ENODATA; + cache.time_to_full = -ENODATA; + cache.charge_full = -ENODATA; + cache.health = -ENODATA; + } else { + if (di->regs[BQ27XXX_REG_TTE] != INVALID_REG_ADDR) + cache.time_to_empty = bq27xxx_battery_read_time(di, BQ27XXX_REG_TTE); + if (di->regs[BQ27XXX_REG_TTECP] != INVALID_REG_ADDR) + cache.time_to_empty_avg = bq27xxx_battery_read_time(di, BQ27XXX_REG_TTECP); + if (di->regs[BQ27XXX_REG_TTF] != INVALID_REG_ADDR) + cache.time_to_full = bq27xxx_battery_read_time(di, BQ27XXX_REG_TTF); + cache.charge_full = bq27xxx_battery_read_fcc(di); + cache.capacity = bq27xxx_battery_read_soc(di); + if (di->regs[BQ27XXX_REG_AE] != INVALID_REG_ADDR) + cache.energy = bq27xxx_battery_read_energy(di); + cache.health = bq27xxx_battery_read_health(di); + } + if (di->regs[BQ27XXX_REG_CYCT] != INVALID_REG_ADDR) + cache.cycle_count = bq27xxx_battery_read_cyct(di); + if (di->regs[BQ27XXX_REG_AP] != INVALID_REG_ADDR) + cache.power_avg = bq27xxx_battery_read_pwr_avg(di); + + /* We only have to read charge design full once */ + if (di->charge_design_full <= 0) + di->charge_design_full = bq27xxx_battery_read_dcap(di); + } + + if (di->cache.capacity != cache.capacity) + power_supply_changed(di->bat); + + if (memcmp(&di->cache, &cache, sizeof(cache)) != 0) + di->cache = cache; + + di->last_update = jiffies; +} +EXPORT_SYMBOL_GPL(bq27xxx_battery_update); + +static void bq27xxx_battery_poll(struct work_struct *work) +{ + struct bq27xxx_device_info *di = + container_of(work, struct bq27xxx_device_info, + work.work); + + bq27xxx_battery_update(di); + + if (poll_interval > 0) + schedule_delayed_work(&di->work, poll_interval * HZ); +} + +/* + * Return the battery average current in µA + * Note that current can be negative signed as well + * Or 0 if something fails. + */ +static int bq27xxx_battery_current(struct bq27xxx_device_info *di, + union power_supply_propval *val) +{ + int curr; + int flags; + + curr = bq27xxx_read(di, BQ27XXX_REG_AI, false); + if (curr < 0) { + dev_err(di->dev, "error reading current\n"); + return curr; + } + + if (di->chip == BQ27000 || di->chip == BQ27010) { + flags = bq27xxx_read(di, BQ27XXX_REG_FLAGS, false); + if (flags & BQ27000_FLAG_CHGS) { + dev_dbg(di->dev, "negative current!\n"); + curr = -curr; + } + + val->intval = curr * BQ27XXX_CURRENT_CONSTANT / BQ27XXX_RS; + } else { + /* Other gauges return signed value */ + val->intval = (int)((s16)curr) * 1000; + } + + return 0; +} + +static int bq27xxx_battery_status(struct bq27xxx_device_info *di, + union power_supply_propval *val) +{ + int status; + + if (di->chip == BQ27000 || di->chip == BQ27010) { + if (di->cache.flags & BQ27000_FLAG_FC) + status = POWER_SUPPLY_STATUS_FULL; + else if (di->cache.flags & BQ27000_FLAG_CHGS) + status = POWER_SUPPLY_STATUS_CHARGING; + else if (power_supply_am_i_supplied(di->bat)) + status = POWER_SUPPLY_STATUS_NOT_CHARGING; + else + status = POWER_SUPPLY_STATUS_DISCHARGING; + } else { + if (di->cache.flags & BQ27XXX_FLAG_FC) + status = POWER_SUPPLY_STATUS_FULL; + else if (di->cache.flags & BQ27XXX_FLAG_DSC) + status = POWER_SUPPLY_STATUS_DISCHARGING; + else + status = POWER_SUPPLY_STATUS_CHARGING; + } + + val->intval = status; + + return 0; +} + +static int bq27xxx_battery_capacity_level(struct bq27xxx_device_info *di, + union power_supply_propval *val) +{ + int level; + + if (di->chip == BQ27000 || di->chip == BQ27010) { + if (di->cache.flags & BQ27000_FLAG_FC) + level = POWER_SUPPLY_CAPACITY_LEVEL_FULL; + else if (di->cache.flags & BQ27000_FLAG_EDV1) + level = POWER_SUPPLY_CAPACITY_LEVEL_LOW; + else if (di->cache.flags & BQ27000_FLAG_EDVF) + level = POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL; + else + level = POWER_SUPPLY_CAPACITY_LEVEL_NORMAL; + } else { + if (di->cache.flags & BQ27XXX_FLAG_FC) + level = POWER_SUPPLY_CAPACITY_LEVEL_FULL; + else if (di->cache.flags & BQ27XXX_FLAG_SOC1) + level = POWER_SUPPLY_CAPACITY_LEVEL_LOW; + else if (di->cache.flags & BQ27XXX_FLAG_SOCF) + level = POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL; + else + level = POWER_SUPPLY_CAPACITY_LEVEL_NORMAL; + } + + val->intval = level; + + return 0; +} + +/* + * Return the battery Voltage in millivolts + * Or < 0 if something fails. + */ +static int bq27xxx_battery_voltage(struct bq27xxx_device_info *di, + union power_supply_propval *val) +{ + int volt; + + volt = bq27xxx_read(di, BQ27XXX_REG_VOLT, false); + if (volt < 0) { + dev_err(di->dev, "error reading voltage\n"); + return volt; + } + + val->intval = volt * 1000; + + return 0; +} + +static int bq27xxx_simple_value(int value, + union power_supply_propval *val) +{ + if (value < 0) + return value; + + val->intval = value; + + return 0; +} + +static int bq27xxx_battery_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + int ret = 0; + struct bq27xxx_device_info *di = power_supply_get_drvdata(psy); + + mutex_lock(&di->lock); + if (time_is_before_jiffies(di->last_update + 5 * HZ)) { + cancel_delayed_work_sync(&di->work); + bq27xxx_battery_poll(&di->work.work); + } + mutex_unlock(&di->lock); + + if (psp != POWER_SUPPLY_PROP_PRESENT && di->cache.flags < 0) + return -ENODEV; + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + ret = bq27xxx_battery_status(di, val); + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + ret = bq27xxx_battery_voltage(di, val); + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = di->cache.flags < 0 ? 0 : 1; + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + ret = bq27xxx_battery_current(di, val); + break; + case POWER_SUPPLY_PROP_CAPACITY: + ret = bq27xxx_simple_value(di->cache.capacity, val); + break; + case POWER_SUPPLY_PROP_CAPACITY_LEVEL: + ret = bq27xxx_battery_capacity_level(di, val); + break; + case POWER_SUPPLY_PROP_TEMP: + ret = bq27xxx_simple_value(di->cache.temperature, val); + if (ret == 0) + val->intval -= 2731; /* convert decidegree k to c */ + break; + case POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW: + ret = bq27xxx_simple_value(di->cache.time_to_empty, val); + break; + case POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG: + ret = bq27xxx_simple_value(di->cache.time_to_empty_avg, val); + break; + case POWER_SUPPLY_PROP_TIME_TO_FULL_NOW: + ret = bq27xxx_simple_value(di->cache.time_to_full, val); + break; + case POWER_SUPPLY_PROP_TECHNOLOGY: + val->intval = POWER_SUPPLY_TECHNOLOGY_LION; + break; + case POWER_SUPPLY_PROP_CHARGE_NOW: + ret = bq27xxx_simple_value(bq27xxx_battery_read_nac(di), val); + break; + case POWER_SUPPLY_PROP_CHARGE_FULL: + ret = bq27xxx_simple_value(di->cache.charge_full, val); + break; + case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: + ret = bq27xxx_simple_value(di->charge_design_full, val); + break; + case POWER_SUPPLY_PROP_CYCLE_COUNT: + ret = bq27xxx_simple_value(di->cache.cycle_count, val); + break; + case POWER_SUPPLY_PROP_ENERGY_NOW: + ret = bq27xxx_simple_value(di->cache.energy, val); + break; + case POWER_SUPPLY_PROP_POWER_AVG: + ret = bq27xxx_simple_value(di->cache.power_avg, val); + break; + case POWER_SUPPLY_PROP_HEALTH: + ret = bq27xxx_simple_value(di->cache.health, val); + break; + case POWER_SUPPLY_PROP_MANUFACTURER: + val->strval = BQ27XXX_MANUFACTURER; + break; + default: + return -EINVAL; + } + + return ret; +} + +static void bq27xxx_external_power_changed(struct power_supply *psy) +{ + struct bq27xxx_device_info *di = power_supply_get_drvdata(psy); + + cancel_delayed_work_sync(&di->work); + schedule_delayed_work(&di->work, 0); +} + +int bq27xxx_battery_setup(struct bq27xxx_device_info *di) +{ + struct power_supply_desc *psy_desc; + struct power_supply_config psy_cfg = { .drv_data = di, }; + + INIT_DELAYED_WORK(&di->work, bq27xxx_battery_poll); + mutex_init(&di->lock); + di->regs = bq27xxx_regs[di->chip]; + + psy_desc = devm_kzalloc(di->dev, sizeof(*psy_desc), GFP_KERNEL); + if (!psy_desc) + return -ENOMEM; + + psy_desc->name = di->name; + psy_desc->type = POWER_SUPPLY_TYPE_BATTERY; + psy_desc->properties = bq27xxx_battery_props[di->chip].props; + psy_desc->num_properties = bq27xxx_battery_props[di->chip].size; + psy_desc->get_property = bq27xxx_battery_get_property; + psy_desc->external_power_changed = bq27xxx_external_power_changed; + + di->bat = power_supply_register_no_ws(di->dev, psy_desc, &psy_cfg); + if (IS_ERR(di->bat)) { + dev_err(di->dev, "failed to register battery\n"); + return PTR_ERR(di->bat); + } + + dev_info(di->dev, "support ver. %s enabled\n", DRIVER_VERSION); + + bq27xxx_battery_update(di); + + return 0; +} +EXPORT_SYMBOL_GPL(bq27xxx_battery_setup); + +void bq27xxx_battery_teardown(struct bq27xxx_device_info *di) +{ + /* + * power_supply_unregister call bq27xxx_battery_get_property which + * call bq27xxx_battery_poll. + * Make sure that bq27xxx_battery_poll will not call + * schedule_delayed_work again after unregister (which cause OOPS). + */ + poll_interval = 0; + + cancel_delayed_work_sync(&di->work); + + power_supply_unregister(di->bat); + + mutex_destroy(&di->lock); +} +EXPORT_SYMBOL_GPL(bq27xxx_battery_teardown); + +static int bq27xxx_battery_platform_read(struct bq27xxx_device_info *di, u8 reg, + bool single) +{ + struct device *dev = di->dev; + struct bq27xxx_platform_data *pdata = dev->platform_data; + unsigned int timeout = 3; + int upper, lower; + int temp; + + if (!single) { + /* Make sure the value has not changed in between reading the + * lower and the upper part */ + upper = pdata->read(dev, reg + 1); + do { + temp = upper; + if (upper < 0) + return upper; + + lower = pdata->read(dev, reg); + if (lower < 0) + return lower; + + upper = pdata->read(dev, reg + 1); + } while (temp != upper && --timeout); + + if (timeout == 0) + return -EIO; + + return (upper << 8) | lower; + } + + return pdata->read(dev, reg); +} + +static int bq27xxx_battery_platform_probe(struct platform_device *pdev) +{ + struct bq27xxx_device_info *di; + struct bq27xxx_platform_data *pdata = pdev->dev.platform_data; + + if (!pdata) { + dev_err(&pdev->dev, "no platform_data supplied\n"); + return -EINVAL; + } + + if (!pdata->read) { + dev_err(&pdev->dev, "no hdq read callback supplied\n"); + return -EINVAL; + } + + if (!pdata->chip) { + dev_err(&pdev->dev, "no device supplied\n"); + return -EINVAL; + } + + di = devm_kzalloc(&pdev->dev, sizeof(*di), GFP_KERNEL); + if (!di) + return -ENOMEM; + + platform_set_drvdata(pdev, di); + + di->dev = &pdev->dev; + di->chip = pdata->chip; + di->name = pdata->name ?: dev_name(&pdev->dev); + di->bus.read = bq27xxx_battery_platform_read; + + return bq27xxx_battery_setup(di); +} + +static int bq27xxx_battery_platform_remove(struct platform_device *pdev) +{ + struct bq27xxx_device_info *di = platform_get_drvdata(pdev); + + bq27xxx_battery_teardown(di); + + return 0; +} + +static const struct platform_device_id bq27xxx_battery_platform_id_table[] = { + { "bq27000-battery", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(platform, bq27xxx_battery_platform_id_table); + +#ifdef CONFIG_OF +static const struct of_device_id bq27xxx_battery_platform_of_match_table[] = { + { .compatible = "ti,bq27000" }, + {}, +}; +MODULE_DEVICE_TABLE(of, bq27xxx_battery_platform_of_match_table); +#endif + +static struct platform_driver bq27xxx_battery_platform_driver = { + .probe = bq27xxx_battery_platform_probe, + .remove = bq27xxx_battery_platform_remove, + .driver = { + .name = "bq27000-battery", + .of_match_table = of_match_ptr(bq27xxx_battery_platform_of_match_table), + }, + .id_table = bq27xxx_battery_platform_id_table, +}; +module_platform_driver(bq27xxx_battery_platform_driver); + +MODULE_ALIAS("platform:bq27000-battery"); + +MODULE_AUTHOR("Rodolfo Giometti "); +MODULE_DESCRIPTION("BQ27xxx battery monitor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/power/supply/bq27xxx_battery_i2c.c b/drivers/power/supply/bq27xxx_battery_i2c.c new file mode 100644 index 000000000000..85d4ea2a9c20 --- /dev/null +++ b/drivers/power/supply/bq27xxx_battery_i2c.c @@ -0,0 +1,205 @@ +/* + * BQ27xxx battery monitor I2C driver + * + * Copyright (C) 2015 Texas Instruments Incorporated - http://www.ti.com/ + * Andrew F. Davis + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed "as is" WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include + +#include + +static DEFINE_IDR(battery_id); +static DEFINE_MUTEX(battery_mutex); + +static irqreturn_t bq27xxx_battery_irq_handler_thread(int irq, void *data) +{ + struct bq27xxx_device_info *di = data; + + bq27xxx_battery_update(di); + + return IRQ_HANDLED; +} + +static int bq27xxx_battery_i2c_read(struct bq27xxx_device_info *di, u8 reg, + bool single) +{ + struct i2c_client *client = to_i2c_client(di->dev); + struct i2c_msg msg[2]; + unsigned char data[2]; + int ret; + + if (!client->adapter) + return -ENODEV; + + msg[0].addr = client->addr; + msg[0].flags = 0; + msg[0].buf = ® + msg[0].len = sizeof(reg); + msg[1].addr = client->addr; + msg[1].flags = I2C_M_RD; + msg[1].buf = data; + if (single) + msg[1].len = 1; + else + msg[1].len = 2; + + ret = i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg)); + if (ret < 0) + return ret; + + if (!single) + ret = get_unaligned_le16(data); + else + ret = data[0]; + + return ret; +} + +static int bq27xxx_battery_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct bq27xxx_device_info *di; + int ret; + char *name; + int num; + + /* Get new ID for the new battery device */ + mutex_lock(&battery_mutex); + num = idr_alloc(&battery_id, client, 0, 0, GFP_KERNEL); + mutex_unlock(&battery_mutex); + if (num < 0) + return num; + + name = devm_kasprintf(&client->dev, GFP_KERNEL, "%s-%d", id->name, num); + if (!name) + goto err_mem; + + di = devm_kzalloc(&client->dev, sizeof(*di), GFP_KERNEL); + if (!di) + goto err_mem; + + di->id = num; + di->dev = &client->dev; + di->chip = id->driver_data; + di->name = name; + di->bus.read = bq27xxx_battery_i2c_read; + + ret = bq27xxx_battery_setup(di); + if (ret) + goto err_failed; + + /* Schedule a polling after about 1 min */ + schedule_delayed_work(&di->work, 60 * HZ); + + i2c_set_clientdata(client, di); + + if (client->irq) { + ret = devm_request_threaded_irq(&client->dev, client->irq, + NULL, bq27xxx_battery_irq_handler_thread, + IRQF_ONESHOT, + di->name, di); + if (ret) { + dev_err(&client->dev, + "Unable to register IRQ %d error %d\n", + client->irq, ret); + return ret; + } + } + + return 0; + +err_mem: + ret = -ENOMEM; + +err_failed: + mutex_lock(&battery_mutex); + idr_remove(&battery_id, num); + mutex_unlock(&battery_mutex); + + return ret; +} + +static int bq27xxx_battery_i2c_remove(struct i2c_client *client) +{ + struct bq27xxx_device_info *di = i2c_get_clientdata(client); + + bq27xxx_battery_teardown(di); + + mutex_lock(&battery_mutex); + idr_remove(&battery_id, di->id); + mutex_unlock(&battery_mutex); + + return 0; +} + +static const struct i2c_device_id bq27xxx_i2c_id_table[] = { + { "bq27200", BQ27000 }, + { "bq27210", BQ27010 }, + { "bq27500", BQ27500 }, + { "bq27510", BQ27500 }, + { "bq27520", BQ27500 }, + { "bq27530", BQ27530 }, + { "bq27531", BQ27530 }, + { "bq27541", BQ27541 }, + { "bq27542", BQ27541 }, + { "bq27546", BQ27541 }, + { "bq27742", BQ27541 }, + { "bq27545", BQ27545 }, + { "bq27421", BQ27421 }, + { "bq27425", BQ27421 }, + { "bq27441", BQ27421 }, + { "bq27621", BQ27421 }, + {}, +}; +MODULE_DEVICE_TABLE(i2c, bq27xxx_i2c_id_table); + +#ifdef CONFIG_OF +static const struct of_device_id bq27xxx_battery_i2c_of_match_table[] = { + { .compatible = "ti,bq27200" }, + { .compatible = "ti,bq27210" }, + { .compatible = "ti,bq27500" }, + { .compatible = "ti,bq27510" }, + { .compatible = "ti,bq27520" }, + { .compatible = "ti,bq27530" }, + { .compatible = "ti,bq27531" }, + { .compatible = "ti,bq27541" }, + { .compatible = "ti,bq27542" }, + { .compatible = "ti,bq27546" }, + { .compatible = "ti,bq27742" }, + { .compatible = "ti,bq27545" }, + { .compatible = "ti,bq27421" }, + { .compatible = "ti,bq27425" }, + { .compatible = "ti,bq27441" }, + { .compatible = "ti,bq27621" }, + {}, +}; +MODULE_DEVICE_TABLE(of, bq27xxx_battery_i2c_of_match_table); +#endif + +static struct i2c_driver bq27xxx_battery_i2c_driver = { + .driver = { + .name = "bq27xxx-battery", + .of_match_table = of_match_ptr(bq27xxx_battery_i2c_of_match_table), + }, + .probe = bq27xxx_battery_i2c_probe, + .remove = bq27xxx_battery_i2c_remove, + .id_table = bq27xxx_i2c_id_table, +}; +module_i2c_driver(bq27xxx_battery_i2c_driver); + +MODULE_AUTHOR("Andrew F. Davis "); +MODULE_DESCRIPTION("BQ27xxx battery monitor i2c driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/power/supply/charger-manager.c b/drivers/power/supply/charger-manager.c new file mode 100644 index 000000000000..e664ca7c0afd --- /dev/null +++ b/drivers/power/supply/charger-manager.c @@ -0,0 +1,2074 @@ +/* + * Copyright (C) 2011 Samsung Electronics Co., Ltd. + * MyungJoo Ham + * + * This driver enables to monitor battery health and control charger + * during suspend-to-mem. + * Charger manager depends on other devices. register this later than + * the depending devices. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. +**/ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * Default termperature threshold for charging. + * Every temperature units are in tenth of centigrade. + */ +#define CM_DEFAULT_RECHARGE_TEMP_DIFF 50 +#define CM_DEFAULT_CHARGE_TEMP_MAX 500 + +static const char * const default_event_names[] = { + [CM_EVENT_UNKNOWN] = "Unknown", + [CM_EVENT_BATT_FULL] = "Battery Full", + [CM_EVENT_BATT_IN] = "Battery Inserted", + [CM_EVENT_BATT_OUT] = "Battery Pulled Out", + [CM_EVENT_BATT_OVERHEAT] = "Battery Overheat", + [CM_EVENT_BATT_COLD] = "Battery Cold", + [CM_EVENT_EXT_PWR_IN_OUT] = "External Power Attach/Detach", + [CM_EVENT_CHG_START_STOP] = "Charging Start/Stop", + [CM_EVENT_OTHERS] = "Other battery events" +}; + +/* + * Regard CM_JIFFIES_SMALL jiffies is small enough to ignore for + * delayed works so that we can run delayed works with CM_JIFFIES_SMALL + * without any delays. + */ +#define CM_JIFFIES_SMALL (2) + +/* If y is valid (> 0) and smaller than x, do x = y */ +#define CM_MIN_VALID(x, y) x = (((y > 0) && ((x) > (y))) ? (y) : (x)) + +/* + * Regard CM_RTC_SMALL (sec) is small enough to ignore error in invoking + * rtc alarm. It should be 2 or larger + */ +#define CM_RTC_SMALL (2) + +#define UEVENT_BUF_SIZE 32 + +static LIST_HEAD(cm_list); +static DEFINE_MUTEX(cm_list_mtx); + +/* About in-suspend (suspend-again) monitoring */ +static struct alarm *cm_timer; + +static bool cm_suspended; +static bool cm_timer_set; +static unsigned long cm_suspend_duration_ms; + +/* About normal (not suspended) monitoring */ +static unsigned long polling_jiffy = ULONG_MAX; /* ULONG_MAX: no polling */ +static unsigned long next_polling; /* Next appointed polling time */ +static struct workqueue_struct *cm_wq; /* init at driver add */ +static struct delayed_work cm_monitor_work; /* init at driver add */ + +/** + * is_batt_present - See if the battery presents in place. + * @cm: the Charger Manager representing the battery. + */ +static bool is_batt_present(struct charger_manager *cm) +{ + union power_supply_propval val; + struct power_supply *psy; + bool present = false; + int i, ret; + + switch (cm->desc->battery_present) { + case CM_BATTERY_PRESENT: + present = true; + break; + case CM_NO_BATTERY: + break; + case CM_FUEL_GAUGE: + psy = power_supply_get_by_name(cm->desc->psy_fuel_gauge); + if (!psy) + break; + + ret = power_supply_get_property(psy, POWER_SUPPLY_PROP_PRESENT, + &val); + if (ret == 0 && val.intval) + present = true; + power_supply_put(psy); + break; + case CM_CHARGER_STAT: + for (i = 0; cm->desc->psy_charger_stat[i]; i++) { + psy = power_supply_get_by_name( + cm->desc->psy_charger_stat[i]); + if (!psy) { + dev_err(cm->dev, "Cannot find power supply \"%s\"\n", + cm->desc->psy_charger_stat[i]); + continue; + } + + ret = power_supply_get_property(psy, + POWER_SUPPLY_PROP_PRESENT, &val); + power_supply_put(psy); + if (ret == 0 && val.intval) { + present = true; + break; + } + } + break; + } + + return present; +} + +/** + * is_ext_pwr_online - See if an external power source is attached to charge + * @cm: the Charger Manager representing the battery. + * + * Returns true if at least one of the chargers of the battery has an external + * power source attached to charge the battery regardless of whether it is + * actually charging or not. + */ +static bool is_ext_pwr_online(struct charger_manager *cm) +{ + union power_supply_propval val; + struct power_supply *psy; + bool online = false; + int i, ret; + + /* If at least one of them has one, it's yes. */ + for (i = 0; cm->desc->psy_charger_stat[i]; i++) { + psy = power_supply_get_by_name(cm->desc->psy_charger_stat[i]); + if (!psy) { + dev_err(cm->dev, "Cannot find power supply \"%s\"\n", + cm->desc->psy_charger_stat[i]); + continue; + } + + ret = power_supply_get_property(psy, POWER_SUPPLY_PROP_ONLINE, + &val); + power_supply_put(psy); + if (ret == 0 && val.intval) { + online = true; + break; + } + } + + return online; +} + +/** + * get_batt_uV - Get the voltage level of the battery + * @cm: the Charger Manager representing the battery. + * @uV: the voltage level returned. + * + * Returns 0 if there is no error. + * Returns a negative value on error. + */ +static int get_batt_uV(struct charger_manager *cm, int *uV) +{ + union power_supply_propval val; + struct power_supply *fuel_gauge; + int ret; + + fuel_gauge = power_supply_get_by_name(cm->desc->psy_fuel_gauge); + if (!fuel_gauge) + return -ENODEV; + + ret = power_supply_get_property(fuel_gauge, + POWER_SUPPLY_PROP_VOLTAGE_NOW, &val); + power_supply_put(fuel_gauge); + if (ret) + return ret; + + *uV = val.intval; + return 0; +} + +/** + * is_charging - Returns true if the battery is being charged. + * @cm: the Charger Manager representing the battery. + */ +static bool is_charging(struct charger_manager *cm) +{ + int i, ret; + bool charging = false; + struct power_supply *psy; + union power_supply_propval val; + + /* If there is no battery, it cannot be charged */ + if (!is_batt_present(cm)) + return false; + + /* If at least one of the charger is charging, return yes */ + for (i = 0; cm->desc->psy_charger_stat[i]; i++) { + /* 1. The charger sholuld not be DISABLED */ + if (cm->emergency_stop) + continue; + if (!cm->charger_enabled) + continue; + + psy = power_supply_get_by_name(cm->desc->psy_charger_stat[i]); + if (!psy) { + dev_err(cm->dev, "Cannot find power supply \"%s\"\n", + cm->desc->psy_charger_stat[i]); + continue; + } + + /* 2. The charger should be online (ext-power) */ + ret = power_supply_get_property(psy, POWER_SUPPLY_PROP_ONLINE, + &val); + if (ret) { + dev_warn(cm->dev, "Cannot read ONLINE value from %s\n", + cm->desc->psy_charger_stat[i]); + power_supply_put(psy); + continue; + } + if (val.intval == 0) { + power_supply_put(psy); + continue; + } + + /* + * 3. The charger should not be FULL, DISCHARGING, + * or NOT_CHARGING. + */ + ret = power_supply_get_property(psy, POWER_SUPPLY_PROP_STATUS, + &val); + power_supply_put(psy); + if (ret) { + dev_warn(cm->dev, "Cannot read STATUS value from %s\n", + cm->desc->psy_charger_stat[i]); + continue; + } + if (val.intval == POWER_SUPPLY_STATUS_FULL || + val.intval == POWER_SUPPLY_STATUS_DISCHARGING || + val.intval == POWER_SUPPLY_STATUS_NOT_CHARGING) + continue; + + /* Then, this is charging. */ + charging = true; + break; + } + + return charging; +} + +/** + * is_full_charged - Returns true if the battery is fully charged. + * @cm: the Charger Manager representing the battery. + */ +static bool is_full_charged(struct charger_manager *cm) +{ + struct charger_desc *desc = cm->desc; + union power_supply_propval val; + struct power_supply *fuel_gauge; + bool is_full = false; + int ret = 0; + int uV; + + /* If there is no battery, it cannot be charged */ + if (!is_batt_present(cm)) + return false; + + fuel_gauge = power_supply_get_by_name(cm->desc->psy_fuel_gauge); + if (!fuel_gauge) + return false; + + if (desc->fullbatt_full_capacity > 0) { + val.intval = 0; + + /* Not full if capacity of fuel gauge isn't full */ + ret = power_supply_get_property(fuel_gauge, + POWER_SUPPLY_PROP_CHARGE_FULL, &val); + if (!ret && val.intval > desc->fullbatt_full_capacity) { + is_full = true; + goto out; + } + } + + /* Full, if it's over the fullbatt voltage */ + if (desc->fullbatt_uV > 0) { + ret = get_batt_uV(cm, &uV); + if (!ret && uV >= desc->fullbatt_uV) { + is_full = true; + goto out; + } + } + + /* Full, if the capacity is more than fullbatt_soc */ + if (desc->fullbatt_soc > 0) { + val.intval = 0; + + ret = power_supply_get_property(fuel_gauge, + POWER_SUPPLY_PROP_CAPACITY, &val); + if (!ret && val.intval >= desc->fullbatt_soc) { + is_full = true; + goto out; + } + } + +out: + power_supply_put(fuel_gauge); + return is_full; +} + +/** + * is_polling_required - Return true if need to continue polling for this CM. + * @cm: the Charger Manager representing the battery. + */ +static bool is_polling_required(struct charger_manager *cm) +{ + switch (cm->desc->polling_mode) { + case CM_POLL_DISABLE: + return false; + case CM_POLL_ALWAYS: + return true; + case CM_POLL_EXTERNAL_POWER_ONLY: + return is_ext_pwr_online(cm); + case CM_POLL_CHARGING_ONLY: + return is_charging(cm); + default: + dev_warn(cm->dev, "Incorrect polling_mode (%d)\n", + cm->desc->polling_mode); + } + + return false; +} + +/** + * try_charger_enable - Enable/Disable chargers altogether + * @cm: the Charger Manager representing the battery. + * @enable: true: enable / false: disable + * + * Note that Charger Manager keeps the charger enabled regardless whether + * the charger is charging or not (because battery is full or no external + * power source exists) except when CM needs to disable chargers forcibly + * bacause of emergency causes; when the battery is overheated or too cold. + */ +static int try_charger_enable(struct charger_manager *cm, bool enable) +{ + int err = 0, i; + struct charger_desc *desc = cm->desc; + + /* Ignore if it's redundent command */ + if (enable == cm->charger_enabled) + return 0; + + if (enable) { + if (cm->emergency_stop) + return -EAGAIN; + + /* + * Save start time of charging to limit + * maximum possible charging time. + */ + cm->charging_start_time = ktime_to_ms(ktime_get()); + cm->charging_end_time = 0; + + for (i = 0 ; i < desc->num_charger_regulators ; i++) { + if (desc->charger_regulators[i].externally_control) + continue; + + err = regulator_enable(desc->charger_regulators[i].consumer); + if (err < 0) { + dev_warn(cm->dev, "Cannot enable %s regulator\n", + desc->charger_regulators[i].regulator_name); + } + } + } else { + /* + * Save end time of charging to maintain fully charged state + * of battery after full-batt. + */ + cm->charging_start_time = 0; + cm->charging_end_time = ktime_to_ms(ktime_get()); + + for (i = 0 ; i < desc->num_charger_regulators ; i++) { + if (desc->charger_regulators[i].externally_control) + continue; + + err = regulator_disable(desc->charger_regulators[i].consumer); + if (err < 0) { + dev_warn(cm->dev, "Cannot disable %s regulator\n", + desc->charger_regulators[i].regulator_name); + } + } + + /* + * Abnormal battery state - Stop charging forcibly, + * even if charger was enabled at the other places + */ + for (i = 0; i < desc->num_charger_regulators; i++) { + if (regulator_is_enabled( + desc->charger_regulators[i].consumer)) { + regulator_force_disable( + desc->charger_regulators[i].consumer); + dev_warn(cm->dev, "Disable regulator(%s) forcibly\n", + desc->charger_regulators[i].regulator_name); + } + } + } + + if (!err) + cm->charger_enabled = enable; + + return err; +} + +/** + * try_charger_restart - Restart charging. + * @cm: the Charger Manager representing the battery. + * + * Restart charging by turning off and on the charger. + */ +static int try_charger_restart(struct charger_manager *cm) +{ + int err; + + if (cm->emergency_stop) + return -EAGAIN; + + err = try_charger_enable(cm, false); + if (err) + return err; + + return try_charger_enable(cm, true); +} + +/** + * uevent_notify - Let users know something has changed. + * @cm: the Charger Manager representing the battery. + * @event: the event string. + * + * If @event is null, it implies that uevent_notify is called + * by resume function. When called in the resume function, cm_suspended + * should be already reset to false in order to let uevent_notify + * notify the recent event during the suspend to users. While + * suspended, uevent_notify does not notify users, but tracks + * events so that uevent_notify can notify users later after resumed. + */ +static void uevent_notify(struct charger_manager *cm, const char *event) +{ + static char env_str[UEVENT_BUF_SIZE + 1] = ""; + static char env_str_save[UEVENT_BUF_SIZE + 1] = ""; + + if (cm_suspended) { + /* Nothing in suspended-event buffer */ + if (env_str_save[0] == 0) { + if (!strncmp(env_str, event, UEVENT_BUF_SIZE)) + return; /* status not changed */ + strncpy(env_str_save, event, UEVENT_BUF_SIZE); + return; + } + + if (!strncmp(env_str_save, event, UEVENT_BUF_SIZE)) + return; /* Duplicated. */ + strncpy(env_str_save, event, UEVENT_BUF_SIZE); + return; + } + + if (event == NULL) { + /* No messages pending */ + if (!env_str_save[0]) + return; + + strncpy(env_str, env_str_save, UEVENT_BUF_SIZE); + kobject_uevent(&cm->dev->kobj, KOBJ_CHANGE); + env_str_save[0] = 0; + + return; + } + + /* status not changed */ + if (!strncmp(env_str, event, UEVENT_BUF_SIZE)) + return; + + /* save the status and notify the update */ + strncpy(env_str, event, UEVENT_BUF_SIZE); + kobject_uevent(&cm->dev->kobj, KOBJ_CHANGE); + + dev_info(cm->dev, "%s\n", event); +} + +/** + * fullbatt_vchk - Check voltage drop some times after "FULL" event. + * @work: the work_struct appointing the function + * + * If a user has designated "fullbatt_vchkdrop_ms/uV" values with + * charger_desc, Charger Manager checks voltage drop after the battery + * "FULL" event. It checks whether the voltage has dropped more than + * fullbatt_vchkdrop_uV by calling this function after fullbatt_vchkrop_ms. + */ +static void fullbatt_vchk(struct work_struct *work) +{ + struct delayed_work *dwork = to_delayed_work(work); + struct charger_manager *cm = container_of(dwork, + struct charger_manager, fullbatt_vchk_work); + struct charger_desc *desc = cm->desc; + int batt_uV, err, diff; + + /* remove the appointment for fullbatt_vchk */ + cm->fullbatt_vchk_jiffies_at = 0; + + if (!desc->fullbatt_vchkdrop_uV || !desc->fullbatt_vchkdrop_ms) + return; + + err = get_batt_uV(cm, &batt_uV); + if (err) { + dev_err(cm->dev, "%s: get_batt_uV error(%d)\n", __func__, err); + return; + } + + diff = desc->fullbatt_uV - batt_uV; + if (diff < 0) + return; + + dev_info(cm->dev, "VBATT dropped %duV after full-batt\n", diff); + + if (diff > desc->fullbatt_vchkdrop_uV) { + try_charger_restart(cm); + uevent_notify(cm, "Recharging"); + } +} + +/** + * check_charging_duration - Monitor charging/discharging duration + * @cm: the Charger Manager representing the battery. + * + * If whole charging duration exceed 'charging_max_duration_ms', + * cm stop charging to prevent overcharge/overheat. If discharging + * duration exceed 'discharging _max_duration_ms', charger cable is + * attached, after full-batt, cm start charging to maintain fully + * charged state for battery. + */ +static int check_charging_duration(struct charger_manager *cm) +{ + struct charger_desc *desc = cm->desc; + u64 curr = ktime_to_ms(ktime_get()); + u64 duration; + int ret = false; + + if (!desc->charging_max_duration_ms && + !desc->discharging_max_duration_ms) + return ret; + + if (cm->charger_enabled) { + duration = curr - cm->charging_start_time; + + if (duration > desc->charging_max_duration_ms) { + dev_info(cm->dev, "Charging duration exceed %ums\n", + desc->charging_max_duration_ms); + uevent_notify(cm, "Discharging"); + try_charger_enable(cm, false); + ret = true; + } + } else if (is_ext_pwr_online(cm) && !cm->charger_enabled) { + duration = curr - cm->charging_end_time; + + if (duration > desc->charging_max_duration_ms && + is_ext_pwr_online(cm)) { + dev_info(cm->dev, "Discharging duration exceed %ums\n", + desc->discharging_max_duration_ms); + uevent_notify(cm, "Recharging"); + try_charger_enable(cm, true); + ret = true; + } + } + + return ret; +} + +static int cm_get_battery_temperature_by_psy(struct charger_manager *cm, + int *temp) +{ + struct power_supply *fuel_gauge; + int ret; + + fuel_gauge = power_supply_get_by_name(cm->desc->psy_fuel_gauge); + if (!fuel_gauge) + return -ENODEV; + + ret = power_supply_get_property(fuel_gauge, + POWER_SUPPLY_PROP_TEMP, + (union power_supply_propval *)temp); + power_supply_put(fuel_gauge); + + return ret; +} + +static int cm_get_battery_temperature(struct charger_manager *cm, + int *temp) +{ + int ret; + + if (!cm->desc->measure_battery_temp) + return -ENODEV; + +#ifdef CONFIG_THERMAL + if (cm->tzd_batt) { + ret = thermal_zone_get_temp(cm->tzd_batt, temp); + if (!ret) + /* Calibrate temperature unit */ + *temp /= 100; + } else +#endif + { + /* if-else continued from CONFIG_THERMAL */ + ret = cm_get_battery_temperature_by_psy(cm, temp); + } + + return ret; +} + +static int cm_check_thermal_status(struct charger_manager *cm) +{ + struct charger_desc *desc = cm->desc; + int temp, upper_limit, lower_limit; + int ret = 0; + + ret = cm_get_battery_temperature(cm, &temp); + if (ret) { + /* FIXME: + * No information of battery temperature might + * occur hazadous result. We have to handle it + * depending on battery type. + */ + dev_err(cm->dev, "Failed to get battery temperature\n"); + return 0; + } + + upper_limit = desc->temp_max; + lower_limit = desc->temp_min; + + if (cm->emergency_stop) { + upper_limit -= desc->temp_diff; + lower_limit += desc->temp_diff; + } + + if (temp > upper_limit) + ret = CM_EVENT_BATT_OVERHEAT; + else if (temp < lower_limit) + ret = CM_EVENT_BATT_COLD; + + return ret; +} + +/** + * _cm_monitor - Monitor the temperature and return true for exceptions. + * @cm: the Charger Manager representing the battery. + * + * Returns true if there is an event to notify for the battery. + * (True if the status of "emergency_stop" changes) + */ +static bool _cm_monitor(struct charger_manager *cm) +{ + int temp_alrt; + + temp_alrt = cm_check_thermal_status(cm); + + /* It has been stopped already */ + if (temp_alrt && cm->emergency_stop) + return false; + + /* + * Check temperature whether overheat or cold. + * If temperature is out of range normal state, stop charging. + */ + if (temp_alrt) { + cm->emergency_stop = temp_alrt; + if (!try_charger_enable(cm, false)) + uevent_notify(cm, default_event_names[temp_alrt]); + + /* + * Check whole charging duration and discharing duration + * after full-batt. + */ + } else if (!cm->emergency_stop && check_charging_duration(cm)) { + dev_dbg(cm->dev, + "Charging/Discharging duration is out of range\n"); + /* + * Check dropped voltage of battery. If battery voltage is more + * dropped than fullbatt_vchkdrop_uV after fully charged state, + * charger-manager have to recharge battery. + */ + } else if (!cm->emergency_stop && is_ext_pwr_online(cm) && + !cm->charger_enabled) { + fullbatt_vchk(&cm->fullbatt_vchk_work.work); + + /* + * Check whether fully charged state to protect overcharge + * if charger-manager is charging for battery. + */ + } else if (!cm->emergency_stop && is_full_charged(cm) && + cm->charger_enabled) { + dev_info(cm->dev, "EVENT_HANDLE: Battery Fully Charged\n"); + uevent_notify(cm, default_event_names[CM_EVENT_BATT_FULL]); + + try_charger_enable(cm, false); + + fullbatt_vchk(&cm->fullbatt_vchk_work.work); + } else { + cm->emergency_stop = 0; + if (is_ext_pwr_online(cm)) { + if (!try_charger_enable(cm, true)) + uevent_notify(cm, "CHARGING"); + } + } + + return true; +} + +/** + * cm_monitor - Monitor every battery. + * + * Returns true if there is an event to notify from any of the batteries. + * (True if the status of "emergency_stop" changes) + */ +static bool cm_monitor(void) +{ + bool stop = false; + struct charger_manager *cm; + + mutex_lock(&cm_list_mtx); + + list_for_each_entry(cm, &cm_list, entry) { + if (_cm_monitor(cm)) + stop = true; + } + + mutex_unlock(&cm_list_mtx); + + return stop; +} + +/** + * _setup_polling - Setup the next instance of polling. + * @work: work_struct of the function _setup_polling. + */ +static void _setup_polling(struct work_struct *work) +{ + unsigned long min = ULONG_MAX; + struct charger_manager *cm; + bool keep_polling = false; + unsigned long _next_polling; + + mutex_lock(&cm_list_mtx); + + list_for_each_entry(cm, &cm_list, entry) { + if (is_polling_required(cm) && cm->desc->polling_interval_ms) { + keep_polling = true; + + if (min > cm->desc->polling_interval_ms) + min = cm->desc->polling_interval_ms; + } + } + + polling_jiffy = msecs_to_jiffies(min); + if (polling_jiffy <= CM_JIFFIES_SMALL) + polling_jiffy = CM_JIFFIES_SMALL + 1; + + if (!keep_polling) + polling_jiffy = ULONG_MAX; + if (polling_jiffy == ULONG_MAX) + goto out; + + WARN(cm_wq == NULL, "charger-manager: workqueue not initialized" + ". try it later. %s\n", __func__); + + /* + * Use mod_delayed_work() iff the next polling interval should + * occur before the currently scheduled one. If @cm_monitor_work + * isn't active, the end result is the same, so no need to worry + * about stale @next_polling. + */ + _next_polling = jiffies + polling_jiffy; + + if (time_before(_next_polling, next_polling)) { + mod_delayed_work(cm_wq, &cm_monitor_work, polling_jiffy); + next_polling = _next_polling; + } else { + if (queue_delayed_work(cm_wq, &cm_monitor_work, polling_jiffy)) + next_polling = _next_polling; + } +out: + mutex_unlock(&cm_list_mtx); +} +static DECLARE_WORK(setup_polling, _setup_polling); + +/** + * cm_monitor_poller - The Monitor / Poller. + * @work: work_struct of the function cm_monitor_poller + * + * During non-suspended state, cm_monitor_poller is used to poll and monitor + * the batteries. + */ +static void cm_monitor_poller(struct work_struct *work) +{ + cm_monitor(); + schedule_work(&setup_polling); +} + +/** + * fullbatt_handler - Event handler for CM_EVENT_BATT_FULL + * @cm: the Charger Manager representing the battery. + */ +static void fullbatt_handler(struct charger_manager *cm) +{ + struct charger_desc *desc = cm->desc; + + if (!desc->fullbatt_vchkdrop_uV || !desc->fullbatt_vchkdrop_ms) + goto out; + + if (cm_suspended) + device_set_wakeup_capable(cm->dev, true); + + mod_delayed_work(cm_wq, &cm->fullbatt_vchk_work, + msecs_to_jiffies(desc->fullbatt_vchkdrop_ms)); + cm->fullbatt_vchk_jiffies_at = jiffies + msecs_to_jiffies( + desc->fullbatt_vchkdrop_ms); + + if (cm->fullbatt_vchk_jiffies_at == 0) + cm->fullbatt_vchk_jiffies_at = 1; + +out: + dev_info(cm->dev, "EVENT_HANDLE: Battery Fully Charged\n"); + uevent_notify(cm, default_event_names[CM_EVENT_BATT_FULL]); +} + +/** + * battout_handler - Event handler for CM_EVENT_BATT_OUT + * @cm: the Charger Manager representing the battery. + */ +static void battout_handler(struct charger_manager *cm) +{ + if (cm_suspended) + device_set_wakeup_capable(cm->dev, true); + + if (!is_batt_present(cm)) { + dev_emerg(cm->dev, "Battery Pulled Out!\n"); + uevent_notify(cm, default_event_names[CM_EVENT_BATT_OUT]); + } else { + uevent_notify(cm, "Battery Reinserted?"); + } +} + +/** + * misc_event_handler - Handler for other evnets + * @cm: the Charger Manager representing the battery. + * @type: the Charger Manager representing the battery. + */ +static void misc_event_handler(struct charger_manager *cm, + enum cm_event_types type) +{ + if (cm_suspended) + device_set_wakeup_capable(cm->dev, true); + + if (is_polling_required(cm) && cm->desc->polling_interval_ms) + schedule_work(&setup_polling); + uevent_notify(cm, default_event_names[type]); +} + +static int charger_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct charger_manager *cm = power_supply_get_drvdata(psy); + struct charger_desc *desc = cm->desc; + struct power_supply *fuel_gauge = NULL; + int ret = 0; + int uV; + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + if (is_charging(cm)) + val->intval = POWER_SUPPLY_STATUS_CHARGING; + else if (is_ext_pwr_online(cm)) + val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; + else + val->intval = POWER_SUPPLY_STATUS_DISCHARGING; + break; + case POWER_SUPPLY_PROP_HEALTH: + if (cm->emergency_stop > 0) + val->intval = POWER_SUPPLY_HEALTH_OVERHEAT; + else if (cm->emergency_stop < 0) + val->intval = POWER_SUPPLY_HEALTH_COLD; + else + val->intval = POWER_SUPPLY_HEALTH_GOOD; + break; + case POWER_SUPPLY_PROP_PRESENT: + if (is_batt_present(cm)) + val->intval = 1; + else + val->intval = 0; + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + ret = get_batt_uV(cm, &val->intval); + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + fuel_gauge = power_supply_get_by_name(cm->desc->psy_fuel_gauge); + if (!fuel_gauge) { + ret = -ENODEV; + break; + } + ret = power_supply_get_property(fuel_gauge, + POWER_SUPPLY_PROP_CURRENT_NOW, val); + break; + case POWER_SUPPLY_PROP_TEMP: + case POWER_SUPPLY_PROP_TEMP_AMBIENT: + return cm_get_battery_temperature(cm, &val->intval); + case POWER_SUPPLY_PROP_CAPACITY: + if (!is_batt_present(cm)) { + /* There is no battery. Assume 100% */ + val->intval = 100; + break; + } + + fuel_gauge = power_supply_get_by_name(cm->desc->psy_fuel_gauge); + if (!fuel_gauge) { + ret = -ENODEV; + break; + } + + ret = power_supply_get_property(fuel_gauge, + POWER_SUPPLY_PROP_CAPACITY, val); + if (ret) + break; + + if (val->intval > 100) { + val->intval = 100; + break; + } + if (val->intval < 0) + val->intval = 0; + + /* Do not adjust SOC when charging: voltage is overrated */ + if (is_charging(cm)) + break; + + /* + * If the capacity value is inconsistent, calibrate it base on + * the battery voltage values and the thresholds given as desc + */ + ret = get_batt_uV(cm, &uV); + if (ret) { + /* Voltage information not available. No calibration */ + ret = 0; + break; + } + + if (desc->fullbatt_uV > 0 && uV >= desc->fullbatt_uV && + !is_charging(cm)) { + val->intval = 100; + break; + } + + break; + case POWER_SUPPLY_PROP_ONLINE: + if (is_ext_pwr_online(cm)) + val->intval = 1; + else + val->intval = 0; + break; + case POWER_SUPPLY_PROP_CHARGE_FULL: + if (is_full_charged(cm)) + val->intval = 1; + else + val->intval = 0; + ret = 0; + break; + case POWER_SUPPLY_PROP_CHARGE_NOW: + if (is_charging(cm)) { + fuel_gauge = power_supply_get_by_name( + cm->desc->psy_fuel_gauge); + if (!fuel_gauge) { + ret = -ENODEV; + break; + } + + ret = power_supply_get_property(fuel_gauge, + POWER_SUPPLY_PROP_CHARGE_NOW, + val); + if (ret) { + val->intval = 1; + ret = 0; + } else { + /* If CHARGE_NOW is supplied, use it */ + val->intval = (val->intval > 0) ? + val->intval : 1; + } + } else { + val->intval = 0; + } + break; + default: + return -EINVAL; + } + if (fuel_gauge) + power_supply_put(fuel_gauge); + return ret; +} + +#define NUM_CHARGER_PSY_OPTIONAL (4) +static enum power_supply_property default_charger_props[] = { + /* Guaranteed to provide */ + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_CHARGE_FULL, + /* + * Optional properties are: + * POWER_SUPPLY_PROP_CHARGE_NOW, + * POWER_SUPPLY_PROP_CURRENT_NOW, + * POWER_SUPPLY_PROP_TEMP, and + * POWER_SUPPLY_PROP_TEMP_AMBIENT, + */ +}; + +static const struct power_supply_desc psy_default = { + .name = "battery", + .type = POWER_SUPPLY_TYPE_BATTERY, + .properties = default_charger_props, + .num_properties = ARRAY_SIZE(default_charger_props), + .get_property = charger_get_property, + .no_thermal = true, +}; + +/** + * cm_setup_timer - For in-suspend monitoring setup wakeup alarm + * for suspend_again. + * + * Returns true if the alarm is set for Charger Manager to use. + * Returns false if + * cm_setup_timer fails to set an alarm, + * cm_setup_timer does not need to set an alarm for Charger Manager, + * or an alarm previously configured is to be used. + */ +static bool cm_setup_timer(void) +{ + struct charger_manager *cm; + unsigned int wakeup_ms = UINT_MAX; + int timer_req = 0; + + if (time_after(next_polling, jiffies)) + CM_MIN_VALID(wakeup_ms, + jiffies_to_msecs(next_polling - jiffies)); + + mutex_lock(&cm_list_mtx); + list_for_each_entry(cm, &cm_list, entry) { + unsigned int fbchk_ms = 0; + + /* fullbatt_vchk is required. setup timer for that */ + if (cm->fullbatt_vchk_jiffies_at) { + fbchk_ms = jiffies_to_msecs(cm->fullbatt_vchk_jiffies_at + - jiffies); + if (time_is_before_eq_jiffies( + cm->fullbatt_vchk_jiffies_at) || + msecs_to_jiffies(fbchk_ms) < CM_JIFFIES_SMALL) { + fullbatt_vchk(&cm->fullbatt_vchk_work.work); + fbchk_ms = 0; + } + } + CM_MIN_VALID(wakeup_ms, fbchk_ms); + + /* Skip if polling is not required for this CM */ + if (!is_polling_required(cm) && !cm->emergency_stop) + continue; + timer_req++; + if (cm->desc->polling_interval_ms == 0) + continue; + CM_MIN_VALID(wakeup_ms, cm->desc->polling_interval_ms); + } + mutex_unlock(&cm_list_mtx); + + if (timer_req && cm_timer) { + ktime_t now, add; + + /* + * Set alarm with the polling interval (wakeup_ms) + * The alarm time should be NOW + CM_RTC_SMALL or later. + */ + if (wakeup_ms == UINT_MAX || + wakeup_ms < CM_RTC_SMALL * MSEC_PER_SEC) + wakeup_ms = 2 * CM_RTC_SMALL * MSEC_PER_SEC; + + pr_info("Charger Manager wakeup timer: %u ms\n", wakeup_ms); + + now = ktime_get_boottime(); + add = ktime_set(wakeup_ms / MSEC_PER_SEC, + (wakeup_ms % MSEC_PER_SEC) * NSEC_PER_MSEC); + alarm_start(cm_timer, ktime_add(now, add)); + + cm_suspend_duration_ms = wakeup_ms; + + return true; + } + return false; +} + +/** + * charger_extcon_work - enable/diable charger according to the state + * of charger cable + * + * @work: work_struct of the function charger_extcon_work. + */ +static void charger_extcon_work(struct work_struct *work) +{ + struct charger_cable *cable = + container_of(work, struct charger_cable, wq); + int ret; + + if (cable->attached && cable->min_uA != 0 && cable->max_uA != 0) { + ret = regulator_set_current_limit(cable->charger->consumer, + cable->min_uA, cable->max_uA); + if (ret < 0) { + pr_err("Cannot set current limit of %s (%s)\n", + cable->charger->regulator_name, cable->name); + return; + } + + pr_info("Set current limit of %s : %duA ~ %duA\n", + cable->charger->regulator_name, + cable->min_uA, cable->max_uA); + } + + try_charger_enable(cable->cm, cable->attached); +} + +/** + * charger_extcon_notifier - receive the state of charger cable + * when registered cable is attached or detached. + * + * @self: the notifier block of the charger_extcon_notifier. + * @event: the cable state. + * @ptr: the data pointer of notifier block. + */ +static int charger_extcon_notifier(struct notifier_block *self, + unsigned long event, void *ptr) +{ + struct charger_cable *cable = + container_of(self, struct charger_cable, nb); + + /* + * The newly state of charger cable. + * If cable is attached, cable->attached is true. + */ + cable->attached = event; + + /* + * Setup monitoring to check battery state + * when charger cable is attached. + */ + if (cable->attached && is_polling_required(cable->cm)) { + cancel_work_sync(&setup_polling); + schedule_work(&setup_polling); + } + + /* + * Setup work for controlling charger(regulator) + * according to charger cable. + */ + schedule_work(&cable->wq); + + return NOTIFY_DONE; +} + +/** + * charger_extcon_init - register external connector to use it + * as the charger cable + * + * @cm: the Charger Manager representing the battery. + * @cable: the Charger cable representing the external connector. + */ +static int charger_extcon_init(struct charger_manager *cm, + struct charger_cable *cable) +{ + int ret = 0; + + /* + * Charger manager use Extcon framework to identify + * the charger cable among various external connector + * cable (e.g., TA, USB, MHL, Dock). + */ + INIT_WORK(&cable->wq, charger_extcon_work); + cable->nb.notifier_call = charger_extcon_notifier; + ret = extcon_register_interest(&cable->extcon_dev, + cable->extcon_name, cable->name, &cable->nb); + if (ret < 0) { + pr_info("Cannot register extcon_dev for %s(cable: %s)\n", + cable->extcon_name, cable->name); + ret = -EINVAL; + } + + return ret; +} + +/** + * charger_manager_register_extcon - Register extcon device to recevie state + * of charger cable. + * @cm: the Charger Manager representing the battery. + * + * This function support EXTCON(External Connector) subsystem to detect the + * state of charger cables for enabling or disabling charger(regulator) and + * select the charger cable for charging among a number of external cable + * according to policy of H/W board. + */ +static int charger_manager_register_extcon(struct charger_manager *cm) +{ + struct charger_desc *desc = cm->desc; + struct charger_regulator *charger; + int ret = 0; + int i; + int j; + + for (i = 0; i < desc->num_charger_regulators; i++) { + charger = &desc->charger_regulators[i]; + + charger->consumer = regulator_get(cm->dev, + charger->regulator_name); + if (IS_ERR(charger->consumer)) { + dev_err(cm->dev, "Cannot find charger(%s)\n", + charger->regulator_name); + return PTR_ERR(charger->consumer); + } + charger->cm = cm; + + for (j = 0; j < charger->num_cables; j++) { + struct charger_cable *cable = &charger->cables[j]; + + ret = charger_extcon_init(cm, cable); + if (ret < 0) { + dev_err(cm->dev, "Cannot initialize charger(%s)\n", + charger->regulator_name); + goto err; + } + cable->charger = charger; + cable->cm = cm; + } + } + +err: + return ret; +} + +/* help function of sysfs node to control charger(regulator) */ +static ssize_t charger_name_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct charger_regulator *charger + = container_of(attr, struct charger_regulator, attr_name); + + return sprintf(buf, "%s\n", charger->regulator_name); +} + +static ssize_t charger_state_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct charger_regulator *charger + = container_of(attr, struct charger_regulator, attr_state); + int state = 0; + + if (!charger->externally_control) + state = regulator_is_enabled(charger->consumer); + + return sprintf(buf, "%s\n", state ? "enabled" : "disabled"); +} + +static ssize_t charger_externally_control_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct charger_regulator *charger = container_of(attr, + struct charger_regulator, attr_externally_control); + + return sprintf(buf, "%d\n", charger->externally_control); +} + +static ssize_t charger_externally_control_store(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + struct charger_regulator *charger + = container_of(attr, struct charger_regulator, + attr_externally_control); + struct charger_manager *cm = charger->cm; + struct charger_desc *desc = cm->desc; + int i; + int ret; + int externally_control; + int chargers_externally_control = 1; + + ret = sscanf(buf, "%d", &externally_control); + if (ret == 0) { + ret = -EINVAL; + return ret; + } + + if (!externally_control) { + charger->externally_control = 0; + return count; + } + + for (i = 0; i < desc->num_charger_regulators; i++) { + if (&desc->charger_regulators[i] != charger && + !desc->charger_regulators[i].externally_control) { + /* + * At least, one charger is controlled by + * charger-manager + */ + chargers_externally_control = 0; + break; + } + } + + if (!chargers_externally_control) { + if (cm->charger_enabled) { + try_charger_enable(charger->cm, false); + charger->externally_control = externally_control; + try_charger_enable(charger->cm, true); + } else { + charger->externally_control = externally_control; + } + } else { + dev_warn(cm->dev, + "'%s' regulator should be controlled in charger-manager because charger-manager must need at least one charger for charging\n", + charger->regulator_name); + } + + return count; +} + +/** + * charger_manager_register_sysfs - Register sysfs entry for each charger + * @cm: the Charger Manager representing the battery. + * + * This function add sysfs entry for charger(regulator) to control charger from + * user-space. If some development board use one more chargers for charging + * but only need one charger on specific case which is dependent on user + * scenario or hardware restrictions, the user enter 1 or 0(zero) to '/sys/ + * class/power_supply/battery/charger.[index]/externally_control'. For example, + * if user enter 1 to 'sys/class/power_supply/battery/charger.[index]/ + * externally_control, this charger isn't controlled from charger-manager and + * always stay off state of regulator. + */ +static int charger_manager_register_sysfs(struct charger_manager *cm) +{ + struct charger_desc *desc = cm->desc; + struct charger_regulator *charger; + int chargers_externally_control = 1; + char buf[11]; + char *str; + int ret = 0; + int i; + + /* Create sysfs entry to control charger(regulator) */ + for (i = 0; i < desc->num_charger_regulators; i++) { + charger = &desc->charger_regulators[i]; + + snprintf(buf, 10, "charger.%d", i); + str = devm_kzalloc(cm->dev, + sizeof(char) * (strlen(buf) + 1), GFP_KERNEL); + if (!str) { + ret = -ENOMEM; + goto err; + } + strcpy(str, buf); + + charger->attrs[0] = &charger->attr_name.attr; + charger->attrs[1] = &charger->attr_state.attr; + charger->attrs[2] = &charger->attr_externally_control.attr; + charger->attrs[3] = NULL; + charger->attr_g.name = str; + charger->attr_g.attrs = charger->attrs; + + sysfs_attr_init(&charger->attr_name.attr); + charger->attr_name.attr.name = "name"; + charger->attr_name.attr.mode = 0444; + charger->attr_name.show = charger_name_show; + + sysfs_attr_init(&charger->attr_state.attr); + charger->attr_state.attr.name = "state"; + charger->attr_state.attr.mode = 0444; + charger->attr_state.show = charger_state_show; + + sysfs_attr_init(&charger->attr_externally_control.attr); + charger->attr_externally_control.attr.name + = "externally_control"; + charger->attr_externally_control.attr.mode = 0644; + charger->attr_externally_control.show + = charger_externally_control_show; + charger->attr_externally_control.store + = charger_externally_control_store; + + if (!desc->charger_regulators[i].externally_control || + !chargers_externally_control) + chargers_externally_control = 0; + + dev_info(cm->dev, "'%s' regulator's externally_control is %d\n", + charger->regulator_name, charger->externally_control); + + ret = sysfs_create_group(&cm->charger_psy->dev.kobj, + &charger->attr_g); + if (ret < 0) { + dev_err(cm->dev, "Cannot create sysfs entry of %s regulator\n", + charger->regulator_name); + ret = -EINVAL; + goto err; + } + } + + if (chargers_externally_control) { + dev_err(cm->dev, "Cannot register regulator because charger-manager must need at least one charger for charging battery\n"); + ret = -EINVAL; + goto err; + } + +err: + return ret; +} + +static int cm_init_thermal_data(struct charger_manager *cm, + struct power_supply *fuel_gauge) +{ + struct charger_desc *desc = cm->desc; + union power_supply_propval val; + int ret; + + /* Verify whether fuel gauge provides battery temperature */ + ret = power_supply_get_property(fuel_gauge, + POWER_SUPPLY_PROP_TEMP, &val); + + if (!ret) { + cm->charger_psy_desc.properties[cm->charger_psy_desc.num_properties] = + POWER_SUPPLY_PROP_TEMP; + cm->charger_psy_desc.num_properties++; + cm->desc->measure_battery_temp = true; + } +#ifdef CONFIG_THERMAL + if (ret && desc->thermal_zone) { + cm->tzd_batt = + thermal_zone_get_zone_by_name(desc->thermal_zone); + if (IS_ERR(cm->tzd_batt)) + return PTR_ERR(cm->tzd_batt); + + /* Use external thermometer */ + cm->charger_psy_desc.properties[cm->charger_psy_desc.num_properties] = + POWER_SUPPLY_PROP_TEMP_AMBIENT; + cm->charger_psy_desc.num_properties++; + cm->desc->measure_battery_temp = true; + ret = 0; + } +#endif + if (cm->desc->measure_battery_temp) { + /* NOTICE : Default allowable minimum charge temperature is 0 */ + if (!desc->temp_max) + desc->temp_max = CM_DEFAULT_CHARGE_TEMP_MAX; + if (!desc->temp_diff) + desc->temp_diff = CM_DEFAULT_RECHARGE_TEMP_DIFF; + } + + return ret; +} + +static const struct of_device_id charger_manager_match[] = { + { + .compatible = "charger-manager", + }, + {}, +}; + +static struct charger_desc *of_cm_parse_desc(struct device *dev) +{ + struct charger_desc *desc; + struct device_node *np = dev->of_node; + u32 poll_mode = CM_POLL_DISABLE; + u32 battery_stat = CM_NO_BATTERY; + int num_chgs = 0; + + desc = devm_kzalloc(dev, sizeof(*desc), GFP_KERNEL); + if (!desc) + return ERR_PTR(-ENOMEM); + + of_property_read_string(np, "cm-name", &desc->psy_name); + + of_property_read_u32(np, "cm-poll-mode", &poll_mode); + desc->polling_mode = poll_mode; + + of_property_read_u32(np, "cm-poll-interval", + &desc->polling_interval_ms); + + of_property_read_u32(np, "cm-fullbatt-vchkdrop-ms", + &desc->fullbatt_vchkdrop_ms); + of_property_read_u32(np, "cm-fullbatt-vchkdrop-volt", + &desc->fullbatt_vchkdrop_uV); + of_property_read_u32(np, "cm-fullbatt-voltage", &desc->fullbatt_uV); + of_property_read_u32(np, "cm-fullbatt-soc", &desc->fullbatt_soc); + of_property_read_u32(np, "cm-fullbatt-capacity", + &desc->fullbatt_full_capacity); + + of_property_read_u32(np, "cm-battery-stat", &battery_stat); + desc->battery_present = battery_stat; + + /* chargers */ + of_property_read_u32(np, "cm-num-chargers", &num_chgs); + if (num_chgs) { + /* Allocate empty bin at the tail of array */ + desc->psy_charger_stat = devm_kzalloc(dev, sizeof(char *) + * (num_chgs + 1), GFP_KERNEL); + if (desc->psy_charger_stat) { + int i; + for (i = 0; i < num_chgs; i++) + of_property_read_string_index(np, "cm-chargers", + i, &desc->psy_charger_stat[i]); + } else { + return ERR_PTR(-ENOMEM); + } + } + + of_property_read_string(np, "cm-fuel-gauge", &desc->psy_fuel_gauge); + + of_property_read_string(np, "cm-thermal-zone", &desc->thermal_zone); + + of_property_read_u32(np, "cm-battery-cold", &desc->temp_min); + if (of_get_property(np, "cm-battery-cold-in-minus", NULL)) + desc->temp_min *= -1; + of_property_read_u32(np, "cm-battery-hot", &desc->temp_max); + of_property_read_u32(np, "cm-battery-temp-diff", &desc->temp_diff); + + of_property_read_u32(np, "cm-charging-max", + &desc->charging_max_duration_ms); + of_property_read_u32(np, "cm-discharging-max", + &desc->discharging_max_duration_ms); + + /* battery charger regualtors */ + desc->num_charger_regulators = of_get_child_count(np); + if (desc->num_charger_regulators) { + struct charger_regulator *chg_regs; + struct device_node *child; + + chg_regs = devm_kzalloc(dev, sizeof(*chg_regs) + * desc->num_charger_regulators, + GFP_KERNEL); + if (!chg_regs) + return ERR_PTR(-ENOMEM); + + desc->charger_regulators = chg_regs; + + for_each_child_of_node(np, child) { + struct charger_cable *cables; + struct device_node *_child; + + of_property_read_string(child, "cm-regulator-name", + &chg_regs->regulator_name); + + /* charger cables */ + chg_regs->num_cables = of_get_child_count(child); + if (chg_regs->num_cables) { + cables = devm_kzalloc(dev, sizeof(*cables) + * chg_regs->num_cables, + GFP_KERNEL); + if (!cables) { + of_node_put(child); + return ERR_PTR(-ENOMEM); + } + + chg_regs->cables = cables; + + for_each_child_of_node(child, _child) { + of_property_read_string(_child, + "cm-cable-name", &cables->name); + of_property_read_string(_child, + "cm-cable-extcon", + &cables->extcon_name); + of_property_read_u32(_child, + "cm-cable-min", + &cables->min_uA); + of_property_read_u32(_child, + "cm-cable-max", + &cables->max_uA); + cables++; + } + } + chg_regs++; + } + } + return desc; +} + +static inline struct charger_desc *cm_get_drv_data(struct platform_device *pdev) +{ + if (pdev->dev.of_node) + return of_cm_parse_desc(&pdev->dev); + return dev_get_platdata(&pdev->dev); +} + +static enum alarmtimer_restart cm_timer_func(struct alarm *alarm, ktime_t now) +{ + cm_timer_set = false; + return ALARMTIMER_NORESTART; +} + +static int charger_manager_probe(struct platform_device *pdev) +{ + struct charger_desc *desc = cm_get_drv_data(pdev); + struct charger_manager *cm; + int ret = 0, i = 0; + int j = 0; + union power_supply_propval val; + struct power_supply *fuel_gauge; + struct power_supply_config psy_cfg = {}; + + if (IS_ERR(desc)) { + dev_err(&pdev->dev, "No platform data (desc) found\n"); + return -ENODEV; + } + + cm = devm_kzalloc(&pdev->dev, + sizeof(struct charger_manager), GFP_KERNEL); + if (!cm) + return -ENOMEM; + + /* Basic Values. Unspecified are Null or 0 */ + cm->dev = &pdev->dev; + cm->desc = desc; + psy_cfg.drv_data = cm; + + /* Initialize alarm timer */ + if (alarmtimer_get_rtcdev()) { + cm_timer = devm_kzalloc(cm->dev, sizeof(*cm_timer), GFP_KERNEL); + alarm_init(cm_timer, ALARM_BOOTTIME, cm_timer_func); + } + + /* + * The following two do not need to be errors. + * Users may intentionally ignore those two features. + */ + if (desc->fullbatt_uV == 0) { + dev_info(&pdev->dev, "Ignoring full-battery voltage threshold as it is not supplied\n"); + } + if (!desc->fullbatt_vchkdrop_ms || !desc->fullbatt_vchkdrop_uV) { + dev_info(&pdev->dev, "Disabling full-battery voltage drop checking mechanism as it is not supplied\n"); + desc->fullbatt_vchkdrop_ms = 0; + desc->fullbatt_vchkdrop_uV = 0; + } + if (desc->fullbatt_soc == 0) { + dev_info(&pdev->dev, "Ignoring full-battery soc(state of charge) threshold as it is not supplied\n"); + } + if (desc->fullbatt_full_capacity == 0) { + dev_info(&pdev->dev, "Ignoring full-battery full capacity threshold as it is not supplied\n"); + } + + if (!desc->charger_regulators || desc->num_charger_regulators < 1) { + dev_err(&pdev->dev, "charger_regulators undefined\n"); + return -EINVAL; + } + + if (!desc->psy_charger_stat || !desc->psy_charger_stat[0]) { + dev_err(&pdev->dev, "No power supply defined\n"); + return -EINVAL; + } + + if (!desc->psy_fuel_gauge) { + dev_err(&pdev->dev, "No fuel gauge power supply defined\n"); + return -EINVAL; + } + + /* Counting index only */ + while (desc->psy_charger_stat[i]) + i++; + + /* Check if charger's supplies are present at probe */ + for (i = 0; desc->psy_charger_stat[i]; i++) { + struct power_supply *psy; + + psy = power_supply_get_by_name(desc->psy_charger_stat[i]); + if (!psy) { + dev_err(&pdev->dev, "Cannot find power supply \"%s\"\n", + desc->psy_charger_stat[i]); + return -ENODEV; + } + power_supply_put(psy); + } + + if (desc->polling_interval_ms == 0 || + msecs_to_jiffies(desc->polling_interval_ms) <= CM_JIFFIES_SMALL) { + dev_err(&pdev->dev, "polling_interval_ms is too small\n"); + return -EINVAL; + } + + if (!desc->charging_max_duration_ms || + !desc->discharging_max_duration_ms) { + dev_info(&pdev->dev, "Cannot limit charging duration checking mechanism to prevent overcharge/overheat and control discharging duration\n"); + desc->charging_max_duration_ms = 0; + desc->discharging_max_duration_ms = 0; + } + + platform_set_drvdata(pdev, cm); + + memcpy(&cm->charger_psy_desc, &psy_default, sizeof(psy_default)); + + if (!desc->psy_name) + strncpy(cm->psy_name_buf, psy_default.name, PSY_NAME_MAX); + else + strncpy(cm->psy_name_buf, desc->psy_name, PSY_NAME_MAX); + cm->charger_psy_desc.name = cm->psy_name_buf; + + /* Allocate for psy properties because they may vary */ + cm->charger_psy_desc.properties = devm_kzalloc(&pdev->dev, + sizeof(enum power_supply_property) + * (ARRAY_SIZE(default_charger_props) + + NUM_CHARGER_PSY_OPTIONAL), GFP_KERNEL); + if (!cm->charger_psy_desc.properties) + return -ENOMEM; + + memcpy(cm->charger_psy_desc.properties, default_charger_props, + sizeof(enum power_supply_property) * + ARRAY_SIZE(default_charger_props)); + cm->charger_psy_desc.num_properties = psy_default.num_properties; + + /* Find which optional psy-properties are available */ + fuel_gauge = power_supply_get_by_name(desc->psy_fuel_gauge); + if (!fuel_gauge) { + dev_err(&pdev->dev, "Cannot find power supply \"%s\"\n", + desc->psy_fuel_gauge); + return -ENODEV; + } + if (!power_supply_get_property(fuel_gauge, + POWER_SUPPLY_PROP_CHARGE_NOW, &val)) { + cm->charger_psy_desc.properties[cm->charger_psy_desc.num_properties] = + POWER_SUPPLY_PROP_CHARGE_NOW; + cm->charger_psy_desc.num_properties++; + } + if (!power_supply_get_property(fuel_gauge, + POWER_SUPPLY_PROP_CURRENT_NOW, + &val)) { + cm->charger_psy_desc.properties[cm->charger_psy_desc.num_properties] = + POWER_SUPPLY_PROP_CURRENT_NOW; + cm->charger_psy_desc.num_properties++; + } + + ret = cm_init_thermal_data(cm, fuel_gauge); + if (ret) { + dev_err(&pdev->dev, "Failed to initialize thermal data\n"); + cm->desc->measure_battery_temp = false; + } + power_supply_put(fuel_gauge); + + INIT_DELAYED_WORK(&cm->fullbatt_vchk_work, fullbatt_vchk); + + cm->charger_psy = power_supply_register(&pdev->dev, + &cm->charger_psy_desc, + &psy_cfg); + if (IS_ERR(cm->charger_psy)) { + dev_err(&pdev->dev, "Cannot register charger-manager with name \"%s\"\n", + cm->charger_psy_desc.name); + return PTR_ERR(cm->charger_psy); + } + + /* Register extcon device for charger cable */ + ret = charger_manager_register_extcon(cm); + if (ret < 0) { + dev_err(&pdev->dev, "Cannot initialize extcon device\n"); + goto err_reg_extcon; + } + + /* Register sysfs entry for charger(regulator) */ + ret = charger_manager_register_sysfs(cm); + if (ret < 0) { + dev_err(&pdev->dev, + "Cannot initialize sysfs entry of regulator\n"); + goto err_reg_sysfs; + } + + /* Add to the list */ + mutex_lock(&cm_list_mtx); + list_add(&cm->entry, &cm_list); + mutex_unlock(&cm_list_mtx); + + /* + * Charger-manager is capable of waking up the systme from sleep + * when event is happend through cm_notify_event() + */ + device_init_wakeup(&pdev->dev, true); + device_set_wakeup_capable(&pdev->dev, false); + + /* + * Charger-manager have to check the charging state right after + * tialization of charger-manager and then update current charging + * state. + */ + cm_monitor(); + + schedule_work(&setup_polling); + + return 0; + +err_reg_sysfs: + for (i = 0; i < desc->num_charger_regulators; i++) { + struct charger_regulator *charger; + + charger = &desc->charger_regulators[i]; + sysfs_remove_group(&cm->charger_psy->dev.kobj, + &charger->attr_g); + } +err_reg_extcon: + for (i = 0; i < desc->num_charger_regulators; i++) { + struct charger_regulator *charger; + + charger = &desc->charger_regulators[i]; + for (j = 0; j < charger->num_cables; j++) { + struct charger_cable *cable = &charger->cables[j]; + /* Remove notifier block if only edev exists */ + if (cable->extcon_dev.edev) + extcon_unregister_interest(&cable->extcon_dev); + } + + regulator_put(desc->charger_regulators[i].consumer); + } + + power_supply_unregister(cm->charger_psy); + + return ret; +} + +static int charger_manager_remove(struct platform_device *pdev) +{ + struct charger_manager *cm = platform_get_drvdata(pdev); + struct charger_desc *desc = cm->desc; + int i = 0; + int j = 0; + + /* Remove from the list */ + mutex_lock(&cm_list_mtx); + list_del(&cm->entry); + mutex_unlock(&cm_list_mtx); + + cancel_work_sync(&setup_polling); + cancel_delayed_work_sync(&cm_monitor_work); + + for (i = 0 ; i < desc->num_charger_regulators ; i++) { + struct charger_regulator *charger + = &desc->charger_regulators[i]; + for (j = 0 ; j < charger->num_cables ; j++) { + struct charger_cable *cable = &charger->cables[j]; + extcon_unregister_interest(&cable->extcon_dev); + } + } + + for (i = 0 ; i < desc->num_charger_regulators ; i++) + regulator_put(desc->charger_regulators[i].consumer); + + power_supply_unregister(cm->charger_psy); + + try_charger_enable(cm, false); + + return 0; +} + +static const struct platform_device_id charger_manager_id[] = { + { "charger-manager", 0 }, + { }, +}; +MODULE_DEVICE_TABLE(platform, charger_manager_id); + +static int cm_suspend_noirq(struct device *dev) +{ + int ret = 0; + + if (device_may_wakeup(dev)) { + device_set_wakeup_capable(dev, false); + ret = -EAGAIN; + } + + return ret; +} + +static bool cm_need_to_awake(void) +{ + struct charger_manager *cm; + + if (cm_timer) + return false; + + mutex_lock(&cm_list_mtx); + list_for_each_entry(cm, &cm_list, entry) { + if (is_charging(cm)) { + mutex_unlock(&cm_list_mtx); + return true; + } + } + mutex_unlock(&cm_list_mtx); + + return false; +} + +static int cm_suspend_prepare(struct device *dev) +{ + struct charger_manager *cm = dev_get_drvdata(dev); + + if (cm_need_to_awake()) + return -EBUSY; + + if (!cm_suspended) + cm_suspended = true; + + cm_timer_set = cm_setup_timer(); + + if (cm_timer_set) { + cancel_work_sync(&setup_polling); + cancel_delayed_work_sync(&cm_monitor_work); + cancel_delayed_work(&cm->fullbatt_vchk_work); + } + + return 0; +} + +static void cm_suspend_complete(struct device *dev) +{ + struct charger_manager *cm = dev_get_drvdata(dev); + + if (cm_suspended) + cm_suspended = false; + + if (cm_timer_set) { + ktime_t remain; + + alarm_cancel(cm_timer); + cm_timer_set = false; + remain = alarm_expires_remaining(cm_timer); + cm_suspend_duration_ms -= ktime_to_ms(remain); + schedule_work(&setup_polling); + } + + _cm_monitor(cm); + + /* Re-enqueue delayed work (fullbatt_vchk_work) */ + if (cm->fullbatt_vchk_jiffies_at) { + unsigned long delay = 0; + unsigned long now = jiffies + CM_JIFFIES_SMALL; + + if (time_after_eq(now, cm->fullbatt_vchk_jiffies_at)) { + delay = (unsigned long)((long)now + - (long)(cm->fullbatt_vchk_jiffies_at)); + delay = jiffies_to_msecs(delay); + } else { + delay = 0; + } + + /* + * Account for cm_suspend_duration_ms with assuming that + * timer stops in suspend. + */ + if (delay > cm_suspend_duration_ms) + delay -= cm_suspend_duration_ms; + else + delay = 0; + + queue_delayed_work(cm_wq, &cm->fullbatt_vchk_work, + msecs_to_jiffies(delay)); + } + device_set_wakeup_capable(cm->dev, false); +} + +static const struct dev_pm_ops charger_manager_pm = { + .prepare = cm_suspend_prepare, + .suspend_noirq = cm_suspend_noirq, + .complete = cm_suspend_complete, +}; + +static struct platform_driver charger_manager_driver = { + .driver = { + .name = "charger-manager", + .pm = &charger_manager_pm, + .of_match_table = charger_manager_match, + }, + .probe = charger_manager_probe, + .remove = charger_manager_remove, + .id_table = charger_manager_id, +}; + +static int __init charger_manager_init(void) +{ + cm_wq = create_freezable_workqueue("charger_manager"); + INIT_DELAYED_WORK(&cm_monitor_work, cm_monitor_poller); + + return platform_driver_register(&charger_manager_driver); +} +late_initcall(charger_manager_init); + +static void __exit charger_manager_cleanup(void) +{ + destroy_workqueue(cm_wq); + cm_wq = NULL; + + platform_driver_unregister(&charger_manager_driver); +} +module_exit(charger_manager_cleanup); + +/** + * cm_notify_event - charger driver notify Charger Manager of charger event + * @psy: pointer to instance of charger's power_supply + * @type: type of charger event + * @msg: optional message passed to uevent_notify fuction + */ +void cm_notify_event(struct power_supply *psy, enum cm_event_types type, + char *msg) +{ + struct charger_manager *cm; + bool found_power_supply = false; + + if (psy == NULL) + return; + + mutex_lock(&cm_list_mtx); + list_for_each_entry(cm, &cm_list, entry) { + if (match_string(cm->desc->psy_charger_stat, -1, + psy->desc->name) >= 0) { + found_power_supply = true; + break; + } + } + mutex_unlock(&cm_list_mtx); + + if (!found_power_supply) + return; + + switch (type) { + case CM_EVENT_BATT_FULL: + fullbatt_handler(cm); + break; + case CM_EVENT_BATT_OUT: + battout_handler(cm); + break; + case CM_EVENT_BATT_IN: + case CM_EVENT_EXT_PWR_IN_OUT ... CM_EVENT_CHG_START_STOP: + misc_event_handler(cm, type); + break; + case CM_EVENT_UNKNOWN: + case CM_EVENT_OTHERS: + uevent_notify(cm, msg ? msg : default_event_names[type]); + break; + default: + dev_err(cm->dev, "%s: type not specified\n", __func__); + break; + } +} +EXPORT_SYMBOL_GPL(cm_notify_event); + +MODULE_AUTHOR("MyungJoo Ham "); +MODULE_DESCRIPTION("Charger Manager"); +MODULE_LICENSE("GPL"); diff --git a/drivers/power/supply/collie_battery.c b/drivers/power/supply/collie_battery.c new file mode 100644 index 000000000000..3a0bc608d4b5 --- /dev/null +++ b/drivers/power/supply/collie_battery.c @@ -0,0 +1,422 @@ +/* + * Battery and Power Management code for the Sharp SL-5x00 + * + * Copyright (C) 2009 Thomas Kunze + * + * based on tosa_battery.c + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +static DEFINE_MUTEX(bat_lock); /* protects gpio pins */ +static struct work_struct bat_work; +static struct ucb1x00 *ucb; + +struct collie_bat { + int status; + struct power_supply *psy; + int full_chrg; + + struct mutex work_lock; /* protects data */ + + bool (*is_present)(struct collie_bat *bat); + int gpio_full; + int gpio_charge_on; + + int technology; + + int gpio_bat; + int adc_bat; + int adc_bat_divider; + int bat_max; + int bat_min; + + int gpio_temp; + int adc_temp; + int adc_temp_divider; +}; + +static struct collie_bat collie_bat_main; + +static unsigned long collie_read_bat(struct collie_bat *bat) +{ + unsigned long value = 0; + + if (bat->gpio_bat < 0 || bat->adc_bat < 0) + return 0; + mutex_lock(&bat_lock); + gpio_set_value(bat->gpio_bat, 1); + msleep(5); + ucb1x00_adc_enable(ucb); + value = ucb1x00_adc_read(ucb, bat->adc_bat, UCB_SYNC); + ucb1x00_adc_disable(ucb); + gpio_set_value(bat->gpio_bat, 0); + mutex_unlock(&bat_lock); + value = value * 1000000 / bat->adc_bat_divider; + + return value; +} + +static unsigned long collie_read_temp(struct collie_bat *bat) +{ + unsigned long value = 0; + if (bat->gpio_temp < 0 || bat->adc_temp < 0) + return 0; + + mutex_lock(&bat_lock); + gpio_set_value(bat->gpio_temp, 1); + msleep(5); + ucb1x00_adc_enable(ucb); + value = ucb1x00_adc_read(ucb, bat->adc_temp, UCB_SYNC); + ucb1x00_adc_disable(ucb); + gpio_set_value(bat->gpio_temp, 0); + mutex_unlock(&bat_lock); + + value = value * 10000 / bat->adc_temp_divider; + + return value; +} + +static int collie_bat_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + int ret = 0; + struct collie_bat *bat = power_supply_get_drvdata(psy); + + if (bat->is_present && !bat->is_present(bat) + && psp != POWER_SUPPLY_PROP_PRESENT) { + return -ENODEV; + } + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + val->intval = bat->status; + break; + case POWER_SUPPLY_PROP_TECHNOLOGY: + val->intval = bat->technology; + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + val->intval = collie_read_bat(bat); + break; + case POWER_SUPPLY_PROP_VOLTAGE_MAX: + if (bat->full_chrg == -1) + val->intval = bat->bat_max; + else + val->intval = bat->full_chrg; + break; + case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: + val->intval = bat->bat_max; + break; + case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: + val->intval = bat->bat_min; + break; + case POWER_SUPPLY_PROP_TEMP: + val->intval = collie_read_temp(bat); + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = bat->is_present ? bat->is_present(bat) : 1; + break; + default: + ret = -EINVAL; + break; + } + return ret; +} + +static void collie_bat_external_power_changed(struct power_supply *psy) +{ + schedule_work(&bat_work); +} + +static irqreturn_t collie_bat_gpio_isr(int irq, void *data) +{ + pr_info("collie_bat_gpio irq\n"); + schedule_work(&bat_work); + return IRQ_HANDLED; +} + +static void collie_bat_update(struct collie_bat *bat) +{ + int old; + struct power_supply *psy = bat->psy; + + mutex_lock(&bat->work_lock); + + old = bat->status; + + if (bat->is_present && !bat->is_present(bat)) { + printk(KERN_NOTICE "%s not present\n", psy->desc->name); + bat->status = POWER_SUPPLY_STATUS_UNKNOWN; + bat->full_chrg = -1; + } else if (power_supply_am_i_supplied(psy)) { + if (bat->status == POWER_SUPPLY_STATUS_DISCHARGING) { + gpio_set_value(bat->gpio_charge_on, 1); + mdelay(15); + } + + if (gpio_get_value(bat->gpio_full)) { + if (old == POWER_SUPPLY_STATUS_CHARGING || + bat->full_chrg == -1) + bat->full_chrg = collie_read_bat(bat); + + gpio_set_value(bat->gpio_charge_on, 0); + bat->status = POWER_SUPPLY_STATUS_FULL; + } else { + gpio_set_value(bat->gpio_charge_on, 1); + bat->status = POWER_SUPPLY_STATUS_CHARGING; + } + } else { + gpio_set_value(bat->gpio_charge_on, 0); + bat->status = POWER_SUPPLY_STATUS_DISCHARGING; + } + + if (old != bat->status) + power_supply_changed(psy); + + mutex_unlock(&bat->work_lock); +} + +static void collie_bat_work(struct work_struct *work) +{ + collie_bat_update(&collie_bat_main); +} + + +static enum power_supply_property collie_bat_main_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, + POWER_SUPPLY_PROP_VOLTAGE_MAX, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_TEMP, +}; + +static enum power_supply_property collie_bat_bu_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, + POWER_SUPPLY_PROP_VOLTAGE_MAX, + POWER_SUPPLY_PROP_PRESENT, +}; + +static const struct power_supply_desc collie_bat_main_desc = { + .name = "main-battery", + .type = POWER_SUPPLY_TYPE_BATTERY, + .properties = collie_bat_main_props, + .num_properties = ARRAY_SIZE(collie_bat_main_props), + .get_property = collie_bat_get_property, + .external_power_changed = collie_bat_external_power_changed, + .use_for_apm = 1, +}; + +static struct collie_bat collie_bat_main = { + .status = POWER_SUPPLY_STATUS_DISCHARGING, + .full_chrg = -1, + .psy = NULL, + + .gpio_full = COLLIE_GPIO_CO, + .gpio_charge_on = COLLIE_GPIO_CHARGE_ON, + + .technology = POWER_SUPPLY_TECHNOLOGY_LIPO, + + .gpio_bat = COLLIE_GPIO_MBAT_ON, + .adc_bat = UCB_ADC_INP_AD1, + .adc_bat_divider = 155, + .bat_max = 4310000, + .bat_min = 1551 * 1000000 / 414, + + .gpio_temp = COLLIE_GPIO_TMP_ON, + .adc_temp = UCB_ADC_INP_AD0, + .adc_temp_divider = 10000, +}; + +static const struct power_supply_desc collie_bat_bu_desc = { + .name = "backup-battery", + .type = POWER_SUPPLY_TYPE_BATTERY, + .properties = collie_bat_bu_props, + .num_properties = ARRAY_SIZE(collie_bat_bu_props), + .get_property = collie_bat_get_property, + .external_power_changed = collie_bat_external_power_changed, +}; + +static struct collie_bat collie_bat_bu = { + .status = POWER_SUPPLY_STATUS_UNKNOWN, + .full_chrg = -1, + .psy = NULL, + + .gpio_full = -1, + .gpio_charge_on = -1, + + .technology = POWER_SUPPLY_TECHNOLOGY_LiMn, + + .gpio_bat = COLLIE_GPIO_BBAT_ON, + .adc_bat = UCB_ADC_INP_AD1, + .adc_bat_divider = 155, + .bat_max = 3000000, + .bat_min = 1900000, + + .gpio_temp = -1, + .adc_temp = -1, + .adc_temp_divider = -1, +}; + +static struct gpio collie_batt_gpios[] = { + { COLLIE_GPIO_CO, GPIOF_IN, "main battery full" }, + { COLLIE_GPIO_MAIN_BAT_LOW, GPIOF_IN, "main battery low" }, + { COLLIE_GPIO_CHARGE_ON, GPIOF_OUT_INIT_LOW, "main charge on" }, + { COLLIE_GPIO_MBAT_ON, GPIOF_OUT_INIT_LOW, "main battery" }, + { COLLIE_GPIO_TMP_ON, GPIOF_OUT_INIT_LOW, "main battery temp" }, + { COLLIE_GPIO_BBAT_ON, GPIOF_OUT_INIT_LOW, "backup battery" }, +}; + +#ifdef CONFIG_PM +static int wakeup_enabled; + +static int collie_bat_suspend(struct ucb1x00_dev *dev) +{ + /* flush all pending status updates */ + flush_work(&bat_work); + + if (device_may_wakeup(&dev->ucb->dev) && + collie_bat_main.status == POWER_SUPPLY_STATUS_CHARGING) + wakeup_enabled = !enable_irq_wake(gpio_to_irq(COLLIE_GPIO_CO)); + else + wakeup_enabled = 0; + + return 0; +} + +static int collie_bat_resume(struct ucb1x00_dev *dev) +{ + if (wakeup_enabled) + disable_irq_wake(gpio_to_irq(COLLIE_GPIO_CO)); + + /* things may have changed while we were away */ + schedule_work(&bat_work); + return 0; +} +#else +#define collie_bat_suspend NULL +#define collie_bat_resume NULL +#endif + +static int collie_bat_probe(struct ucb1x00_dev *dev) +{ + int ret; + struct power_supply_config psy_main_cfg = {}, psy_bu_cfg = {}; + + if (!machine_is_collie()) + return -ENODEV; + + ucb = dev->ucb; + + ret = gpio_request_array(collie_batt_gpios, + ARRAY_SIZE(collie_batt_gpios)); + if (ret) + return ret; + + mutex_init(&collie_bat_main.work_lock); + + INIT_WORK(&bat_work, collie_bat_work); + + psy_main_cfg.drv_data = &collie_bat_main; + collie_bat_main.psy = power_supply_register(&dev->ucb->dev, + &collie_bat_main_desc, + &psy_main_cfg); + if (IS_ERR(collie_bat_main.psy)) { + ret = PTR_ERR(collie_bat_main.psy); + goto err_psy_reg_main; + } + + psy_bu_cfg.drv_data = &collie_bat_bu; + collie_bat_bu.psy = power_supply_register(&dev->ucb->dev, + &collie_bat_bu_desc, + &psy_bu_cfg); + if (IS_ERR(collie_bat_bu.psy)) { + ret = PTR_ERR(collie_bat_bu.psy); + goto err_psy_reg_bu; + } + + ret = request_irq(gpio_to_irq(COLLIE_GPIO_CO), + collie_bat_gpio_isr, + IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, + "main full", &collie_bat_main); + if (ret) + goto err_irq; + + device_init_wakeup(&ucb->dev, 1); + schedule_work(&bat_work); + + return 0; + +err_irq: + power_supply_unregister(collie_bat_bu.psy); +err_psy_reg_bu: + power_supply_unregister(collie_bat_main.psy); +err_psy_reg_main: + + /* see comment in collie_bat_remove */ + cancel_work_sync(&bat_work); + gpio_free_array(collie_batt_gpios, ARRAY_SIZE(collie_batt_gpios)); + return ret; +} + +static void collie_bat_remove(struct ucb1x00_dev *dev) +{ + free_irq(gpio_to_irq(COLLIE_GPIO_CO), &collie_bat_main); + + power_supply_unregister(collie_bat_bu.psy); + power_supply_unregister(collie_bat_main.psy); + + /* + * Now cancel the bat_work. We won't get any more schedules, + * since all sources (isr and external_power_changed) are + * unregistered now. + */ + cancel_work_sync(&bat_work); + gpio_free_array(collie_batt_gpios, ARRAY_SIZE(collie_batt_gpios)); +} + +static struct ucb1x00_driver collie_bat_driver = { + .add = collie_bat_probe, + .remove = collie_bat_remove, + .suspend = collie_bat_suspend, + .resume = collie_bat_resume, +}; + +static int __init collie_bat_init(void) +{ + return ucb1x00_register_driver(&collie_bat_driver); +} + +static void __exit collie_bat_exit(void) +{ + ucb1x00_unregister_driver(&collie_bat_driver); +} + +module_init(collie_bat_init); +module_exit(collie_bat_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Thomas Kunze"); +MODULE_DESCRIPTION("Collie battery driver"); diff --git a/drivers/power/supply/da9030_battery.c b/drivers/power/supply/da9030_battery.c new file mode 100644 index 000000000000..5ca0f4d90792 --- /dev/null +++ b/drivers/power/supply/da9030_battery.c @@ -0,0 +1,596 @@ +/* + * Battery charger driver for Dialog Semiconductor DA9030 + * + * Copyright (C) 2008 Compulab, Ltd. + * Mike Rapoport + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#define DA9030_FAULT_LOG 0x0a +#define DA9030_FAULT_LOG_OVER_TEMP (1 << 7) +#define DA9030_FAULT_LOG_VBAT_OVER (1 << 4) + +#define DA9030_CHARGE_CONTROL 0x28 +#define DA9030_CHRG_CHARGER_ENABLE (1 << 7) + +#define DA9030_ADC_MAN_CONTROL 0x30 +#define DA9030_ADC_TBATREF_ENABLE (1 << 5) +#define DA9030_ADC_LDO_INT_ENABLE (1 << 4) + +#define DA9030_ADC_AUTO_CONTROL 0x31 +#define DA9030_ADC_TBAT_ENABLE (1 << 5) +#define DA9030_ADC_VBAT_IN_TXON (1 << 4) +#define DA9030_ADC_VCH_ENABLE (1 << 3) +#define DA9030_ADC_ICH_ENABLE (1 << 2) +#define DA9030_ADC_VBAT_ENABLE (1 << 1) +#define DA9030_ADC_AUTO_SLEEP_ENABLE (1 << 0) + +#define DA9030_VBATMON 0x32 +#define DA9030_VBATMONTXON 0x33 +#define DA9030_TBATHIGHP 0x34 +#define DA9030_TBATHIGHN 0x35 +#define DA9030_TBATLOW 0x36 + +#define DA9030_VBAT_RES 0x41 +#define DA9030_VBATMIN_RES 0x42 +#define DA9030_VBATMINTXON_RES 0x43 +#define DA9030_ICHMAX_RES 0x44 +#define DA9030_ICHMIN_RES 0x45 +#define DA9030_ICHAVERAGE_RES 0x46 +#define DA9030_VCHMAX_RES 0x47 +#define DA9030_VCHMIN_RES 0x48 +#define DA9030_TBAT_RES 0x49 + +struct da9030_adc_res { + uint8_t vbat_res; + uint8_t vbatmin_res; + uint8_t vbatmintxon; + uint8_t ichmax_res; + uint8_t ichmin_res; + uint8_t ichaverage_res; + uint8_t vchmax_res; + uint8_t vchmin_res; + uint8_t tbat_res; + uint8_t adc_in4_res; + uint8_t adc_in5_res; +}; + +struct da9030_battery_thresholds { + int tbat_low; + int tbat_high; + int tbat_restart; + + int vbat_low; + int vbat_crit; + int vbat_charge_start; + int vbat_charge_stop; + int vbat_charge_restart; + + int vcharge_min; + int vcharge_max; +}; + +struct da9030_charger { + struct power_supply *psy; + struct power_supply_desc psy_desc; + + struct device *master; + + struct da9030_adc_res adc; + struct delayed_work work; + unsigned int interval; + + struct power_supply_info *battery_info; + + struct da9030_battery_thresholds thresholds; + + unsigned int charge_milliamp; + unsigned int charge_millivolt; + + /* charger status */ + bool chdet; + uint8_t fault; + int mA; + int mV; + bool is_on; + + struct notifier_block nb; + + /* platform callbacks for battery low and critical events */ + void (*battery_low)(void); + void (*battery_critical)(void); + + struct dentry *debug_file; +}; + +static inline int da9030_reg_to_mV(int reg) +{ + return ((reg * 2650) >> 8) + 2650; +} + +static inline int da9030_millivolt_to_reg(int mV) +{ + return ((mV - 2650) << 8) / 2650; +} + +static inline int da9030_reg_to_mA(int reg) +{ + return ((reg * 24000) >> 8) / 15; +} + +#ifdef CONFIG_DEBUG_FS +static int bat_debug_show(struct seq_file *s, void *data) +{ + struct da9030_charger *charger = s->private; + + seq_printf(s, "charger is %s\n", charger->is_on ? "on" : "off"); + if (charger->chdet) { + seq_printf(s, "iset = %dmA, vset = %dmV\n", + charger->mA, charger->mV); + } + + seq_printf(s, "vbat_res = %d (%dmV)\n", + charger->adc.vbat_res, + da9030_reg_to_mV(charger->adc.vbat_res)); + seq_printf(s, "vbatmin_res = %d (%dmV)\n", + charger->adc.vbatmin_res, + da9030_reg_to_mV(charger->adc.vbatmin_res)); + seq_printf(s, "vbatmintxon = %d (%dmV)\n", + charger->adc.vbatmintxon, + da9030_reg_to_mV(charger->adc.vbatmintxon)); + seq_printf(s, "ichmax_res = %d (%dmA)\n", + charger->adc.ichmax_res, + da9030_reg_to_mV(charger->adc.ichmax_res)); + seq_printf(s, "ichmin_res = %d (%dmA)\n", + charger->adc.ichmin_res, + da9030_reg_to_mA(charger->adc.ichmin_res)); + seq_printf(s, "ichaverage_res = %d (%dmA)\n", + charger->adc.ichaverage_res, + da9030_reg_to_mA(charger->adc.ichaverage_res)); + seq_printf(s, "vchmax_res = %d (%dmV)\n", + charger->adc.vchmax_res, + da9030_reg_to_mA(charger->adc.vchmax_res)); + seq_printf(s, "vchmin_res = %d (%dmV)\n", + charger->adc.vchmin_res, + da9030_reg_to_mV(charger->adc.vchmin_res)); + + return 0; +} + +static int debug_open(struct inode *inode, struct file *file) +{ + return single_open(file, bat_debug_show, inode->i_private); +} + +static const struct file_operations bat_debug_fops = { + .open = debug_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static struct dentry *da9030_bat_create_debugfs(struct da9030_charger *charger) +{ + charger->debug_file = debugfs_create_file("charger", 0666, NULL, + charger, &bat_debug_fops); + return charger->debug_file; +} + +static void da9030_bat_remove_debugfs(struct da9030_charger *charger) +{ + debugfs_remove(charger->debug_file); +} +#else +static inline struct dentry *da9030_bat_create_debugfs(struct da9030_charger *charger) +{ + return NULL; +} +static inline void da9030_bat_remove_debugfs(struct da9030_charger *charger) +{ +} +#endif + +static inline void da9030_read_adc(struct da9030_charger *charger, + struct da9030_adc_res *adc) +{ + da903x_reads(charger->master, DA9030_VBAT_RES, + sizeof(*adc), (uint8_t *)adc); +} + +static void da9030_charger_update_state(struct da9030_charger *charger) +{ + uint8_t val; + + da903x_read(charger->master, DA9030_CHARGE_CONTROL, &val); + charger->is_on = (val & DA9030_CHRG_CHARGER_ENABLE) ? 1 : 0; + charger->mA = ((val >> 3) & 0xf) * 100; + charger->mV = (val & 0x7) * 50 + 4000; + + da9030_read_adc(charger, &charger->adc); + da903x_read(charger->master, DA9030_FAULT_LOG, &charger->fault); + charger->chdet = da903x_query_status(charger->master, + DA9030_STATUS_CHDET); +} + +static void da9030_set_charge(struct da9030_charger *charger, int on) +{ + uint8_t val; + + if (on) { + val = DA9030_CHRG_CHARGER_ENABLE; + val |= (charger->charge_milliamp / 100) << 3; + val |= (charger->charge_millivolt - 4000) / 50; + charger->is_on = 1; + } else { + val = 0; + charger->is_on = 0; + } + + da903x_write(charger->master, DA9030_CHARGE_CONTROL, val); + + power_supply_changed(charger->psy); +} + +static void da9030_charger_check_state(struct da9030_charger *charger) +{ + da9030_charger_update_state(charger); + + /* we wake or boot with external power on */ + if (!charger->is_on) { + if ((charger->chdet) && + (charger->adc.vbat_res < + charger->thresholds.vbat_charge_start)) { + da9030_set_charge(charger, 1); + } + } else { + /* Charger has been pulled out */ + if (!charger->chdet) { + da9030_set_charge(charger, 0); + return; + } + + if (charger->adc.vbat_res >= + charger->thresholds.vbat_charge_stop) { + da9030_set_charge(charger, 0); + da903x_write(charger->master, DA9030_VBATMON, + charger->thresholds.vbat_charge_restart); + } else if (charger->adc.vbat_res > + charger->thresholds.vbat_low) { + /* we are charging and passed LOW_THRESH, + so upate DA9030 VBAT threshold + */ + da903x_write(charger->master, DA9030_VBATMON, + charger->thresholds.vbat_low); + } + if (charger->adc.vchmax_res > charger->thresholds.vcharge_max || + charger->adc.vchmin_res < charger->thresholds.vcharge_min || + /* Tempreture readings are negative */ + charger->adc.tbat_res < charger->thresholds.tbat_high || + charger->adc.tbat_res > charger->thresholds.tbat_low) { + /* disable charger */ + da9030_set_charge(charger, 0); + } + } +} + +static void da9030_charging_monitor(struct work_struct *work) +{ + struct da9030_charger *charger; + + charger = container_of(work, struct da9030_charger, work.work); + + da9030_charger_check_state(charger); + + /* reschedule for the next time */ + schedule_delayed_work(&charger->work, charger->interval); +} + +static enum power_supply_property da9030_battery_props[] = { + POWER_SUPPLY_PROP_MODEL_NAME, + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, + POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CURRENT_AVG, +}; + +static void da9030_battery_check_status(struct da9030_charger *charger, + union power_supply_propval *val) +{ + if (charger->chdet) { + if (charger->is_on) + val->intval = POWER_SUPPLY_STATUS_CHARGING; + else + val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; + } else { + val->intval = POWER_SUPPLY_STATUS_DISCHARGING; + } +} + +static void da9030_battery_check_health(struct da9030_charger *charger, + union power_supply_propval *val) +{ + if (charger->fault & DA9030_FAULT_LOG_OVER_TEMP) + val->intval = POWER_SUPPLY_HEALTH_OVERHEAT; + else if (charger->fault & DA9030_FAULT_LOG_VBAT_OVER) + val->intval = POWER_SUPPLY_HEALTH_OVERVOLTAGE; + else + val->intval = POWER_SUPPLY_HEALTH_GOOD; +} + +static int da9030_battery_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct da9030_charger *charger = power_supply_get_drvdata(psy); + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + da9030_battery_check_status(charger, val); + break; + case POWER_SUPPLY_PROP_HEALTH: + da9030_battery_check_health(charger, val); + break; + case POWER_SUPPLY_PROP_TECHNOLOGY: + val->intval = charger->battery_info->technology; + break; + case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: + val->intval = charger->battery_info->voltage_max_design; + break; + case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: + val->intval = charger->battery_info->voltage_min_design; + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + val->intval = da9030_reg_to_mV(charger->adc.vbat_res) * 1000; + break; + case POWER_SUPPLY_PROP_CURRENT_AVG: + val->intval = + da9030_reg_to_mA(charger->adc.ichaverage_res) * 1000; + break; + case POWER_SUPPLY_PROP_MODEL_NAME: + val->strval = charger->battery_info->name; + break; + default: + break; + } + + return 0; +} + +static void da9030_battery_vbat_event(struct da9030_charger *charger) +{ + da9030_read_adc(charger, &charger->adc); + + if (charger->is_on) + return; + + if (charger->adc.vbat_res < charger->thresholds.vbat_low) { + /* set VBAT threshold for critical */ + da903x_write(charger->master, DA9030_VBATMON, + charger->thresholds.vbat_crit); + if (charger->battery_low) + charger->battery_low(); + } else if (charger->adc.vbat_res < + charger->thresholds.vbat_crit) { + /* notify the system of battery critical */ + if (charger->battery_critical) + charger->battery_critical(); + } +} + +static int da9030_battery_event(struct notifier_block *nb, unsigned long event, + void *data) +{ + struct da9030_charger *charger = + container_of(nb, struct da9030_charger, nb); + + switch (event) { + case DA9030_EVENT_CHDET: + cancel_delayed_work_sync(&charger->work); + schedule_work(&charger->work.work); + break; + case DA9030_EVENT_VBATMON: + da9030_battery_vbat_event(charger); + break; + case DA9030_EVENT_CHIOVER: + case DA9030_EVENT_TBAT: + da9030_set_charge(charger, 0); + break; + } + + return 0; +} + +static void da9030_battery_convert_thresholds(struct da9030_charger *charger, + struct da9030_battery_info *pdata) +{ + charger->thresholds.tbat_low = pdata->tbat_low; + charger->thresholds.tbat_high = pdata->tbat_high; + charger->thresholds.tbat_restart = pdata->tbat_restart; + + charger->thresholds.vbat_low = + da9030_millivolt_to_reg(pdata->vbat_low); + charger->thresholds.vbat_crit = + da9030_millivolt_to_reg(pdata->vbat_crit); + charger->thresholds.vbat_charge_start = + da9030_millivolt_to_reg(pdata->vbat_charge_start); + charger->thresholds.vbat_charge_stop = + da9030_millivolt_to_reg(pdata->vbat_charge_stop); + charger->thresholds.vbat_charge_restart = + da9030_millivolt_to_reg(pdata->vbat_charge_restart); + + charger->thresholds.vcharge_min = + da9030_millivolt_to_reg(pdata->vcharge_min); + charger->thresholds.vcharge_max = + da9030_millivolt_to_reg(pdata->vcharge_max); +} + +static void da9030_battery_setup_psy(struct da9030_charger *charger) +{ + struct power_supply_desc *psy_desc = &charger->psy_desc; + struct power_supply_info *info = charger->battery_info; + + psy_desc->name = info->name; + psy_desc->use_for_apm = info->use_for_apm; + psy_desc->type = POWER_SUPPLY_TYPE_BATTERY; + psy_desc->get_property = da9030_battery_get_property; + + psy_desc->properties = da9030_battery_props; + psy_desc->num_properties = ARRAY_SIZE(da9030_battery_props); +}; + +static int da9030_battery_charger_init(struct da9030_charger *charger) +{ + char v[5]; + int ret; + + v[0] = v[1] = charger->thresholds.vbat_low; + v[2] = charger->thresholds.tbat_high; + v[3] = charger->thresholds.tbat_restart; + v[4] = charger->thresholds.tbat_low; + + ret = da903x_writes(charger->master, DA9030_VBATMON, 5, v); + if (ret) + return ret; + + /* + * Enable reference voltage supply for ADC from the LDO_INTERNAL + * regulator. Must be set before ADC measurements can be made. + */ + ret = da903x_write(charger->master, DA9030_ADC_MAN_CONTROL, + DA9030_ADC_LDO_INT_ENABLE | + DA9030_ADC_TBATREF_ENABLE); + if (ret) + return ret; + + /* enable auto ADC measuremnts */ + return da903x_write(charger->master, DA9030_ADC_AUTO_CONTROL, + DA9030_ADC_TBAT_ENABLE | DA9030_ADC_VBAT_IN_TXON | + DA9030_ADC_VCH_ENABLE | DA9030_ADC_ICH_ENABLE | + DA9030_ADC_VBAT_ENABLE | + DA9030_ADC_AUTO_SLEEP_ENABLE); +} + +static int da9030_battery_probe(struct platform_device *pdev) +{ + struct da9030_charger *charger; + struct power_supply_config psy_cfg = {}; + struct da9030_battery_info *pdata = pdev->dev.platform_data; + int ret; + + if (pdata == NULL) + return -EINVAL; + + if (pdata->charge_milliamp >= 1500 || + pdata->charge_millivolt < 4000 || + pdata->charge_millivolt > 4350) + return -EINVAL; + + charger = devm_kzalloc(&pdev->dev, sizeof(*charger), GFP_KERNEL); + if (charger == NULL) + return -ENOMEM; + + charger->master = pdev->dev.parent; + + /* 10 seconds between monitor runs unless platform defines other + interval */ + charger->interval = msecs_to_jiffies( + (pdata->batmon_interval ? : 10) * 1000); + + charger->charge_milliamp = pdata->charge_milliamp; + charger->charge_millivolt = pdata->charge_millivolt; + charger->battery_info = pdata->battery_info; + charger->battery_low = pdata->battery_low; + charger->battery_critical = pdata->battery_critical; + + da9030_battery_convert_thresholds(charger, pdata); + + ret = da9030_battery_charger_init(charger); + if (ret) + goto err_charger_init; + + INIT_DELAYED_WORK(&charger->work, da9030_charging_monitor); + schedule_delayed_work(&charger->work, charger->interval); + + charger->nb.notifier_call = da9030_battery_event; + ret = da903x_register_notifier(charger->master, &charger->nb, + DA9030_EVENT_CHDET | + DA9030_EVENT_VBATMON | + DA9030_EVENT_CHIOVER | + DA9030_EVENT_TBAT); + if (ret) + goto err_notifier; + + da9030_battery_setup_psy(charger); + psy_cfg.drv_data = charger; + charger->psy = power_supply_register(&pdev->dev, &charger->psy_desc, + &psy_cfg); + if (IS_ERR(charger->psy)) { + ret = PTR_ERR(charger->psy); + goto err_ps_register; + } + + charger->debug_file = da9030_bat_create_debugfs(charger); + platform_set_drvdata(pdev, charger); + return 0; + +err_ps_register: + da903x_unregister_notifier(charger->master, &charger->nb, + DA9030_EVENT_CHDET | DA9030_EVENT_VBATMON | + DA9030_EVENT_CHIOVER | DA9030_EVENT_TBAT); +err_notifier: + cancel_delayed_work(&charger->work); + +err_charger_init: + return ret; +} + +static int da9030_battery_remove(struct platform_device *dev) +{ + struct da9030_charger *charger = platform_get_drvdata(dev); + + da9030_bat_remove_debugfs(charger); + + da903x_unregister_notifier(charger->master, &charger->nb, + DA9030_EVENT_CHDET | DA9030_EVENT_VBATMON | + DA9030_EVENT_CHIOVER | DA9030_EVENT_TBAT); + cancel_delayed_work_sync(&charger->work); + da9030_set_charge(charger, 0); + power_supply_unregister(charger->psy); + + return 0; +} + +static struct platform_driver da903x_battery_driver = { + .driver = { + .name = "da903x-battery", + }, + .probe = da9030_battery_probe, + .remove = da9030_battery_remove, +}; + +module_platform_driver(da903x_battery_driver); + +MODULE_DESCRIPTION("DA9030 battery charger driver"); +MODULE_AUTHOR("Mike Rapoport, CompuLab"); +MODULE_LICENSE("GPL"); diff --git a/drivers/power/supply/da9052-battery.c b/drivers/power/supply/da9052-battery.c new file mode 100644 index 000000000000..830ec46fe7d0 --- /dev/null +++ b/drivers/power/supply/da9052-battery.c @@ -0,0 +1,669 @@ +/* + * Batttery Driver for Dialog DA9052 PMICs + * + * Copyright(c) 2011 Dialog Semiconductor Ltd. + * + * Author: David Dajun Chen + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +/* STATIC CONFIGURATION */ +#define DA9052_BAT_CUTOFF_VOLT 2800 +#define DA9052_BAT_TSH 62000 +#define DA9052_BAT_LOW_CAP 4 +#define DA9052_AVG_SZ 4 +#define DA9052_VC_TBL_SZ 68 +#define DA9052_VC_TBL_REF_SZ 3 + +#define DA9052_ISET_USB_MASK 0x0F +#define DA9052_CHG_USB_ILIM_MASK 0x40 +#define DA9052_CHG_LIM_COLS 16 + +#define DA9052_MEAN(x, y) ((x + y) / 2) + +enum charger_type_enum { + DA9052_NOCHARGER = 1, + DA9052_CHARGER, +}; + +static const u16 da9052_chg_current_lim[2][DA9052_CHG_LIM_COLS] = { + {70, 80, 90, 100, 110, 120, 400, 450, + 500, 550, 600, 650, 700, 900, 1100, 1300}, + {80, 90, 100, 110, 120, 400, 450, 500, + 550, 600, 800, 1000, 1200, 1400, 1600, 1800}, +}; + +static const u16 vc_tbl_ref[3] = {10, 25, 40}; +/* Lookup table for voltage vs capacity */ +static u32 const vc_tbl[3][68][2] = { + /* For temperature 10 degree Celsius */ + { + {4082, 100}, {4036, 98}, + {4020, 96}, {4008, 95}, + {3997, 93}, {3983, 91}, + {3964, 90}, {3943, 88}, + {3926, 87}, {3912, 85}, + {3900, 84}, {3890, 82}, + {3881, 80}, {3873, 79}, + {3865, 77}, {3857, 76}, + {3848, 74}, {3839, 73}, + {3829, 71}, {3820, 70}, + {3811, 68}, {3802, 67}, + {3794, 65}, {3785, 64}, + {3778, 62}, {3770, 61}, + {3763, 59}, {3756, 58}, + {3750, 56}, {3744, 55}, + {3738, 53}, {3732, 52}, + {3727, 50}, {3722, 49}, + {3717, 47}, {3712, 46}, + {3708, 44}, {3703, 43}, + {3700, 41}, {3696, 40}, + {3693, 38}, {3691, 37}, + {3688, 35}, {3686, 34}, + {3683, 32}, {3681, 31}, + {3678, 29}, {3675, 28}, + {3672, 26}, {3669, 25}, + {3665, 23}, {3661, 22}, + {3656, 21}, {3651, 19}, + {3645, 18}, {3639, 16}, + {3631, 15}, {3622, 13}, + {3611, 12}, {3600, 10}, + {3587, 9}, {3572, 7}, + {3548, 6}, {3503, 5}, + {3420, 3}, {3268, 2}, + {2992, 1}, {2746, 0} + }, + /* For temperature 25 degree Celsius */ + { + {4102, 100}, {4065, 98}, + {4048, 96}, {4034, 95}, + {4021, 93}, {4011, 92}, + {4001, 90}, {3986, 88}, + {3968, 87}, {3952, 85}, + {3938, 84}, {3926, 82}, + {3916, 81}, {3908, 79}, + {3900, 77}, {3892, 76}, + {3883, 74}, {3874, 73}, + {3864, 71}, {3855, 70}, + {3846, 68}, {3836, 67}, + {3827, 65}, {3819, 64}, + {3810, 62}, {3801, 61}, + {3793, 59}, {3786, 58}, + {3778, 56}, {3772, 55}, + {3765, 53}, {3759, 52}, + {3754, 50}, {3748, 49}, + {3743, 47}, {3738, 46}, + {3733, 44}, {3728, 43}, + {3724, 41}, {3720, 40}, + {3716, 38}, {3712, 37}, + {3709, 35}, {3706, 34}, + {3703, 33}, {3701, 31}, + {3698, 30}, {3696, 28}, + {3693, 27}, {3690, 25}, + {3687, 24}, {3683, 22}, + {3680, 21}, {3675, 19}, + {3671, 18}, {3666, 17}, + {3660, 15}, {3654, 14}, + {3647, 12}, {3639, 11}, + {3630, 9}, {3621, 8}, + {3613, 6}, {3606, 5}, + {3597, 4}, {3582, 2}, + {3546, 1}, {2747, 0} + }, + /* For temperature 40 degree Celsius */ + { + {4114, 100}, {4081, 98}, + {4065, 96}, {4050, 95}, + {4036, 93}, {4024, 92}, + {4013, 90}, {4002, 88}, + {3990, 87}, {3976, 85}, + {3962, 84}, {3950, 82}, + {3939, 81}, {3930, 79}, + {3921, 77}, {3912, 76}, + {3902, 74}, {3893, 73}, + {3883, 71}, {3874, 70}, + {3865, 68}, {3856, 67}, + {3847, 65}, {3838, 64}, + {3829, 62}, {3820, 61}, + {3812, 59}, {3803, 58}, + {3795, 56}, {3787, 55}, + {3780, 53}, {3773, 52}, + {3767, 50}, {3761, 49}, + {3756, 47}, {3751, 46}, + {3746, 44}, {3741, 43}, + {3736, 41}, {3732, 40}, + {3728, 38}, {3724, 37}, + {3720, 35}, {3716, 34}, + {3713, 33}, {3710, 31}, + {3707, 30}, {3704, 28}, + {3701, 27}, {3698, 25}, + {3695, 24}, {3691, 22}, + {3686, 21}, {3681, 19}, + {3676, 18}, {3671, 17}, + {3666, 15}, {3661, 14}, + {3655, 12}, {3648, 11}, + {3640, 9}, {3632, 8}, + {3622, 6}, {3616, 5}, + {3611, 4}, {3604, 2}, + {3594, 1}, {2747, 0} + } +}; + +struct da9052_battery { + struct da9052 *da9052; + struct power_supply *psy; + struct notifier_block nb; + int charger_type; + int status; + int health; +}; + +static inline int volt_reg_to_mV(int value) +{ + return ((value * 1000) / 512) + 2500; +} + +static inline int ichg_reg_to_mA(int value) +{ + return (value * 3900) / 1000; +} + +static int da9052_read_chgend_current(struct da9052_battery *bat, + int *current_mA) +{ + int ret; + + if (bat->status == POWER_SUPPLY_STATUS_DISCHARGING) + return -EINVAL; + + ret = da9052_reg_read(bat->da9052, DA9052_ICHG_END_REG); + if (ret < 0) + return ret; + + *current_mA = ichg_reg_to_mA(ret & DA9052_ICHGEND_ICHGEND); + + return 0; +} + +static int da9052_read_chg_current(struct da9052_battery *bat, int *current_mA) +{ + int ret; + + if (bat->status == POWER_SUPPLY_STATUS_DISCHARGING) + return -EINVAL; + + ret = da9052_reg_read(bat->da9052, DA9052_ICHG_AV_REG); + if (ret < 0) + return ret; + + *current_mA = ichg_reg_to_mA(ret & DA9052_ICHGAV_ICHGAV); + + return 0; +} + +static int da9052_bat_check_status(struct da9052_battery *bat, int *status) +{ + u8 v[2] = {0, 0}; + u8 bat_status; + u8 chg_end; + int ret; + int chg_current; + int chg_end_current; + bool dcinsel; + bool dcindet; + bool vbussel; + bool vbusdet; + bool dc; + bool vbus; + + ret = da9052_group_read(bat->da9052, DA9052_STATUS_A_REG, 2, v); + if (ret < 0) + return ret; + + bat_status = v[0]; + chg_end = v[1]; + + dcinsel = bat_status & DA9052_STATUSA_DCINSEL; + dcindet = bat_status & DA9052_STATUSA_DCINDET; + vbussel = bat_status & DA9052_STATUSA_VBUSSEL; + vbusdet = bat_status & DA9052_STATUSA_VBUSDET; + dc = dcinsel && dcindet; + vbus = vbussel && vbusdet; + + /* Preference to WALL(DCIN) charger unit */ + if (dc || vbus) { + bat->charger_type = DA9052_CHARGER; + + /* If charging end flag is set and Charging current is greater + * than charging end limit then battery is charging + */ + if ((chg_end & DA9052_STATUSB_CHGEND) != 0) { + ret = da9052_read_chg_current(bat, &chg_current); + if (ret < 0) + return ret; + ret = da9052_read_chgend_current(bat, &chg_end_current); + if (ret < 0) + return ret; + + if (chg_current >= chg_end_current) + bat->status = POWER_SUPPLY_STATUS_CHARGING; + else + bat->status = POWER_SUPPLY_STATUS_NOT_CHARGING; + } else { + /* If Charging end flag is cleared then battery is + * charging + */ + bat->status = POWER_SUPPLY_STATUS_CHARGING; + } + } else if (dcindet || vbusdet) { + bat->charger_type = DA9052_CHARGER; + bat->status = POWER_SUPPLY_STATUS_NOT_CHARGING; + } else { + bat->charger_type = DA9052_NOCHARGER; + bat->status = POWER_SUPPLY_STATUS_DISCHARGING; + } + + if (status != NULL) + *status = bat->status; + return 0; +} + +static int da9052_bat_read_volt(struct da9052_battery *bat, int *volt_mV) +{ + int volt; + + volt = da9052_adc_manual_read(bat->da9052, DA9052_ADC_MAN_MUXSEL_VBAT); + if (volt < 0) + return volt; + + *volt_mV = volt_reg_to_mV(volt); + + return 0; +} + +static int da9052_bat_check_presence(struct da9052_battery *bat, int *illegal) +{ + int bat_temp; + + bat_temp = da9052_adc_read_temp(bat->da9052); + if (bat_temp < 0) + return bat_temp; + + if (bat_temp > DA9052_BAT_TSH) + *illegal = 1; + else + *illegal = 0; + + return 0; +} + +static int da9052_bat_interpolate(int vbat_lower, int vbat_upper, + int level_lower, int level_upper, + int bat_voltage) +{ + int tmp; + + tmp = ((level_upper - level_lower) * 1000) / (vbat_upper - vbat_lower); + tmp = level_lower + (((bat_voltage - vbat_lower) * tmp) / 1000); + + return tmp; +} + +static unsigned char da9052_determine_vc_tbl_index(unsigned char adc_temp) +{ + int i; + + if (adc_temp <= vc_tbl_ref[0]) + return 0; + + if (adc_temp > vc_tbl_ref[DA9052_VC_TBL_REF_SZ - 1]) + return DA9052_VC_TBL_REF_SZ - 1; + + for (i = 0; i < DA9052_VC_TBL_REF_SZ - 1; i++) { + if ((adc_temp > vc_tbl_ref[i]) && + (adc_temp <= DA9052_MEAN(vc_tbl_ref[i], vc_tbl_ref[i + 1]))) + return i; + if ((adc_temp > DA9052_MEAN(vc_tbl_ref[i], vc_tbl_ref[i + 1])) + && (adc_temp <= vc_tbl_ref[i])) + return i + 1; + } + /* + * For some reason authors of the driver didn't presume that we can + * end up here. It might be OK, but might be not, no one knows for + * sure. Go check your battery, is it on fire? + */ + WARN_ON(1); + return 0; +} + +static int da9052_bat_read_capacity(struct da9052_battery *bat, int *capacity) +{ + int adc_temp; + int bat_voltage; + int vbat_lower; + int vbat_upper; + int level_upper; + int level_lower; + int ret; + int flag; + int i = 0; + int j; + + ret = da9052_bat_read_volt(bat, &bat_voltage); + if (ret < 0) + return ret; + + adc_temp = da9052_adc_read_temp(bat->da9052); + if (adc_temp < 0) + return adc_temp; + + i = da9052_determine_vc_tbl_index(adc_temp); + + if (bat_voltage >= vc_tbl[i][0][0]) { + *capacity = 100; + return 0; + } + if (bat_voltage <= vc_tbl[i][DA9052_VC_TBL_SZ - 1][0]) { + *capacity = 0; + return 0; + } + flag = 0; + + for (j = 0; j < (DA9052_VC_TBL_SZ-1); j++) { + if ((bat_voltage <= vc_tbl[i][j][0]) && + (bat_voltage >= vc_tbl[i][j + 1][0])) { + vbat_upper = vc_tbl[i][j][0]; + vbat_lower = vc_tbl[i][j + 1][0]; + level_upper = vc_tbl[i][j][1]; + level_lower = vc_tbl[i][j + 1][1]; + flag = 1; + break; + } + } + if (!flag) + return -EIO; + + *capacity = da9052_bat_interpolate(vbat_lower, vbat_upper, level_lower, + level_upper, bat_voltage); + + return 0; +} + +static int da9052_bat_check_health(struct da9052_battery *bat, int *health) +{ + int ret; + int bat_illegal; + int capacity; + + ret = da9052_bat_check_presence(bat, &bat_illegal); + if (ret < 0) + return ret; + + if (bat_illegal) { + bat->health = POWER_SUPPLY_HEALTH_UNKNOWN; + return 0; + } + + if (bat->health != POWER_SUPPLY_HEALTH_OVERHEAT) { + ret = da9052_bat_read_capacity(bat, &capacity); + if (ret < 0) + return ret; + if (capacity < DA9052_BAT_LOW_CAP) + bat->health = POWER_SUPPLY_HEALTH_DEAD; + else + bat->health = POWER_SUPPLY_HEALTH_GOOD; + } + + *health = bat->health; + + return 0; +} + +static irqreturn_t da9052_bat_irq(int irq, void *data) +{ + struct da9052_battery *bat = data; + int virq; + + virq = regmap_irq_get_virq(bat->da9052->irq_data, irq); + irq -= virq; + + if (irq == DA9052_IRQ_CHGEND) + bat->status = POWER_SUPPLY_STATUS_FULL; + else + da9052_bat_check_status(bat, NULL); + + if (irq == DA9052_IRQ_CHGEND || irq == DA9052_IRQ_DCIN || + irq == DA9052_IRQ_VBUS || irq == DA9052_IRQ_TBAT) { + power_supply_changed(bat->psy); + } + + return IRQ_HANDLED; +} + +static int da9052_USB_current_notifier(struct notifier_block *nb, + unsigned long events, void *data) +{ + u8 row; + u8 col; + int *current_mA = data; + int ret; + struct da9052_battery *bat = container_of(nb, struct da9052_battery, + nb); + + if (bat->status == POWER_SUPPLY_STATUS_DISCHARGING) + return -EPERM; + + ret = da9052_reg_read(bat->da9052, DA9052_CHGBUCK_REG); + if (ret & DA9052_CHG_USB_ILIM_MASK) + return -EPERM; + + if (bat->da9052->chip_id == DA9052) + row = 0; + else + row = 1; + + if (*current_mA < da9052_chg_current_lim[row][0] || + *current_mA > da9052_chg_current_lim[row][DA9052_CHG_LIM_COLS - 1]) + return -EINVAL; + + for (col = 0; col <= DA9052_CHG_LIM_COLS - 1 ; col++) { + if (*current_mA <= da9052_chg_current_lim[row][col]) + break; + } + + return da9052_reg_update(bat->da9052, DA9052_ISET_REG, + DA9052_ISET_USB_MASK, col); +} + +static int da9052_bat_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + int ret; + int illegal; + struct da9052_battery *bat = power_supply_get_drvdata(psy); + + ret = da9052_bat_check_presence(bat, &illegal); + if (ret < 0) + return ret; + + if (illegal && psp != POWER_SUPPLY_PROP_PRESENT) + return -ENODEV; + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + ret = da9052_bat_check_status(bat, &val->intval); + break; + case POWER_SUPPLY_PROP_ONLINE: + val->intval = + (bat->charger_type == DA9052_NOCHARGER) ? 0 : 1; + break; + case POWER_SUPPLY_PROP_PRESENT: + ret = da9052_bat_check_presence(bat, &val->intval); + break; + case POWER_SUPPLY_PROP_HEALTH: + ret = da9052_bat_check_health(bat, &val->intval); + break; + case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: + val->intval = DA9052_BAT_CUTOFF_VOLT * 1000; + break; + case POWER_SUPPLY_PROP_VOLTAGE_AVG: + ret = da9052_bat_read_volt(bat, &val->intval); + break; + case POWER_SUPPLY_PROP_CURRENT_AVG: + ret = da9052_read_chg_current(bat, &val->intval); + break; + case POWER_SUPPLY_PROP_CAPACITY: + ret = da9052_bat_read_capacity(bat, &val->intval); + break; + case POWER_SUPPLY_PROP_TEMP: + val->intval = da9052_adc_read_temp(bat->da9052); + ret = val->intval; + break; + case POWER_SUPPLY_PROP_TECHNOLOGY: + val->intval = POWER_SUPPLY_TECHNOLOGY_LION; + break; + default: + return -EINVAL; + } + return ret; +} + +static enum power_supply_property da9052_bat_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, + POWER_SUPPLY_PROP_VOLTAGE_AVG, + POWER_SUPPLY_PROP_CURRENT_AVG, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_TEMP, + POWER_SUPPLY_PROP_TECHNOLOGY, +}; + +static struct power_supply_desc psy_desc = { + .name = "da9052-bat", + .type = POWER_SUPPLY_TYPE_BATTERY, + .properties = da9052_bat_props, + .num_properties = ARRAY_SIZE(da9052_bat_props), + .get_property = da9052_bat_get_property, +}; + +static char *da9052_bat_irqs[] = { + "BATT TEMP", + "DCIN DET", + "DCIN REM", + "VBUS DET", + "VBUS REM", + "CHG END", +}; + +static int da9052_bat_irq_bits[] = { + DA9052_IRQ_TBAT, + DA9052_IRQ_DCIN, + DA9052_IRQ_DCINREM, + DA9052_IRQ_VBUS, + DA9052_IRQ_VBUSREM, + DA9052_IRQ_CHGEND, +}; + +static s32 da9052_bat_probe(struct platform_device *pdev) +{ + struct da9052_pdata *pdata; + struct da9052_battery *bat; + struct power_supply_config psy_cfg = {}; + int ret; + int i; + + bat = devm_kzalloc(&pdev->dev, sizeof(struct da9052_battery), + GFP_KERNEL); + if (!bat) + return -ENOMEM; + + psy_cfg.drv_data = bat; + + bat->da9052 = dev_get_drvdata(pdev->dev.parent); + bat->charger_type = DA9052_NOCHARGER; + bat->status = POWER_SUPPLY_STATUS_UNKNOWN; + bat->health = POWER_SUPPLY_HEALTH_UNKNOWN; + bat->nb.notifier_call = da9052_USB_current_notifier; + + pdata = bat->da9052->dev->platform_data; + if (pdata != NULL && pdata->use_for_apm) + psy_desc.use_for_apm = pdata->use_for_apm; + else + psy_desc.use_for_apm = 1; + + for (i = 0; i < ARRAY_SIZE(da9052_bat_irqs); i++) { + ret = da9052_request_irq(bat->da9052, + da9052_bat_irq_bits[i], da9052_bat_irqs[i], + da9052_bat_irq, bat); + + if (ret != 0) { + dev_err(bat->da9052->dev, + "DA9052 failed to request %s IRQ: %d\n", + da9052_bat_irqs[i], ret); + goto err; + } + } + + bat->psy = power_supply_register(&pdev->dev, &psy_desc, &psy_cfg); + if (IS_ERR(bat->psy)) { + ret = PTR_ERR(bat->psy); + goto err; + } + + platform_set_drvdata(pdev, bat); + return 0; + +err: + while (--i >= 0) + da9052_free_irq(bat->da9052, da9052_bat_irq_bits[i], bat); + + return ret; +} +static int da9052_bat_remove(struct platform_device *pdev) +{ + int i; + struct da9052_battery *bat = platform_get_drvdata(pdev); + + for (i = 0; i < ARRAY_SIZE(da9052_bat_irqs); i++) + da9052_free_irq(bat->da9052, da9052_bat_irq_bits[i], bat); + + power_supply_unregister(bat->psy); + + return 0; +} + +static struct platform_driver da9052_bat_driver = { + .probe = da9052_bat_probe, + .remove = da9052_bat_remove, + .driver = { + .name = "da9052-bat", + }, +}; +module_platform_driver(da9052_bat_driver); + +MODULE_DESCRIPTION("DA9052 BAT Device Driver"); +MODULE_AUTHOR("David Dajun Chen "); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:da9052-bat"); diff --git a/drivers/power/supply/da9150-charger.c b/drivers/power/supply/da9150-charger.c new file mode 100644 index 000000000000..60099815296e --- /dev/null +++ b/drivers/power/supply/da9150-charger.c @@ -0,0 +1,694 @@ +/* + * DA9150 Charger Driver + * + * Copyright (c) 2014 Dialog Semiconductor + * + * Author: Adam Thomson + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Private data */ +struct da9150_charger { + struct da9150 *da9150; + struct device *dev; + + struct power_supply *usb; + struct power_supply *battery; + struct power_supply *supply_online; + + struct usb_phy *usb_phy; + struct notifier_block otg_nb; + struct work_struct otg_work; + unsigned long usb_event; + + struct iio_channel *ibus_chan; + struct iio_channel *vbus_chan; + struct iio_channel *tjunc_chan; + struct iio_channel *vbat_chan; +}; + +static inline int da9150_charger_supply_online(struct da9150_charger *charger, + struct power_supply *psy, + union power_supply_propval *val) +{ + val->intval = (psy == charger->supply_online) ? 1 : 0; + + return 0; +} + +/* Charger Properties */ +static int da9150_charger_vbus_voltage_now(struct da9150_charger *charger, + union power_supply_propval *val) +{ + int v_val, ret; + + /* Read processed value - mV units */ + ret = iio_read_channel_processed(charger->vbus_chan, &v_val); + if (ret < 0) + return ret; + + /* Convert voltage to expected uV units */ + val->intval = v_val * 1000; + + return 0; +} + +static int da9150_charger_ibus_current_avg(struct da9150_charger *charger, + union power_supply_propval *val) +{ + int i_val, ret; + + /* Read processed value - mA units */ + ret = iio_read_channel_processed(charger->ibus_chan, &i_val); + if (ret < 0) + return ret; + + /* Convert current to expected uA units */ + val->intval = i_val * 1000; + + return 0; +} + +static int da9150_charger_tjunc_temp(struct da9150_charger *charger, + union power_supply_propval *val) +{ + int t_val, ret; + + /* Read processed value - 0.001 degrees C units */ + ret = iio_read_channel_processed(charger->tjunc_chan, &t_val); + if (ret < 0) + return ret; + + /* Convert temp to expect 0.1 degrees C units */ + val->intval = t_val / 100; + + return 0; +} + +static enum power_supply_property da9150_charger_props[] = { + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CURRENT_AVG, + POWER_SUPPLY_PROP_TEMP, +}; + +static int da9150_charger_get_prop(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct da9150_charger *charger = dev_get_drvdata(psy->dev.parent); + int ret; + + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + ret = da9150_charger_supply_online(charger, psy, val); + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + ret = da9150_charger_vbus_voltage_now(charger, val); + break; + case POWER_SUPPLY_PROP_CURRENT_AVG: + ret = da9150_charger_ibus_current_avg(charger, val); + break; + case POWER_SUPPLY_PROP_TEMP: + ret = da9150_charger_tjunc_temp(charger, val); + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +/* Battery Properties */ +static int da9150_charger_battery_status(struct da9150_charger *charger, + union power_supply_propval *val) +{ + u8 reg; + + /* Check to see if battery is discharging */ + reg = da9150_reg_read(charger->da9150, DA9150_STATUS_H); + + if (((reg & DA9150_VBUS_STAT_MASK) == DA9150_VBUS_STAT_OFF) || + ((reg & DA9150_VBUS_STAT_MASK) == DA9150_VBUS_STAT_WAIT)) { + val->intval = POWER_SUPPLY_STATUS_DISCHARGING; + + return 0; + } + + reg = da9150_reg_read(charger->da9150, DA9150_STATUS_J); + + /* Now check for other states */ + switch (reg & DA9150_CHG_STAT_MASK) { + case DA9150_CHG_STAT_ACT: + case DA9150_CHG_STAT_PRE: + case DA9150_CHG_STAT_CC: + case DA9150_CHG_STAT_CV: + val->intval = POWER_SUPPLY_STATUS_CHARGING; + break; + case DA9150_CHG_STAT_OFF: + case DA9150_CHG_STAT_SUSP: + case DA9150_CHG_STAT_TEMP: + case DA9150_CHG_STAT_TIME: + case DA9150_CHG_STAT_BAT: + val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; + break; + case DA9150_CHG_STAT_FULL: + val->intval = POWER_SUPPLY_STATUS_FULL; + break; + default: + val->intval = POWER_SUPPLY_STATUS_UNKNOWN; + break; + } + + return 0; +} + +static int da9150_charger_battery_health(struct da9150_charger *charger, + union power_supply_propval *val) +{ + u8 reg; + + reg = da9150_reg_read(charger->da9150, DA9150_STATUS_J); + + /* Check if temperature limit reached */ + switch (reg & DA9150_CHG_TEMP_MASK) { + case DA9150_CHG_TEMP_UNDER: + val->intval = POWER_SUPPLY_HEALTH_COLD; + return 0; + case DA9150_CHG_TEMP_OVER: + val->intval = POWER_SUPPLY_HEALTH_OVERHEAT; + return 0; + default: + break; + } + + /* Check for other health states */ + switch (reg & DA9150_CHG_STAT_MASK) { + case DA9150_CHG_STAT_ACT: + case DA9150_CHG_STAT_PRE: + val->intval = POWER_SUPPLY_HEALTH_DEAD; + break; + case DA9150_CHG_STAT_TIME: + val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; + break; + default: + val->intval = POWER_SUPPLY_HEALTH_GOOD; + break; + } + + return 0; +} + +static int da9150_charger_battery_present(struct da9150_charger *charger, + union power_supply_propval *val) +{ + u8 reg; + + /* Check if battery present or removed */ + reg = da9150_reg_read(charger->da9150, DA9150_STATUS_J); + if ((reg & DA9150_CHG_STAT_MASK) == DA9150_CHG_STAT_BAT) + val->intval = 0; + else + val->intval = 1; + + return 0; +} + +static int da9150_charger_battery_charge_type(struct da9150_charger *charger, + union power_supply_propval *val) +{ + u8 reg; + + reg = da9150_reg_read(charger->da9150, DA9150_STATUS_J); + + switch (reg & DA9150_CHG_STAT_MASK) { + case DA9150_CHG_STAT_CC: + val->intval = POWER_SUPPLY_CHARGE_TYPE_FAST; + break; + case DA9150_CHG_STAT_ACT: + case DA9150_CHG_STAT_PRE: + case DA9150_CHG_STAT_CV: + val->intval = POWER_SUPPLY_CHARGE_TYPE_TRICKLE; + break; + default: + val->intval = POWER_SUPPLY_CHARGE_TYPE_NONE; + break; + } + + return 0; +} + +static int da9150_charger_battery_voltage_min(struct da9150_charger *charger, + union power_supply_propval *val) +{ + u8 reg; + + reg = da9150_reg_read(charger->da9150, DA9150_PPR_CHGCTRL_C); + + /* Value starts at 2500 mV, 50 mV increments, presented in uV */ + val->intval = ((reg & DA9150_CHG_VFAULT_MASK) * 50000) + 2500000; + + return 0; +} + +static int da9150_charger_battery_voltage_now(struct da9150_charger *charger, + union power_supply_propval *val) +{ + int v_val, ret; + + /* Read processed value - mV units */ + ret = iio_read_channel_processed(charger->vbat_chan, &v_val); + if (ret < 0) + return ret; + + val->intval = v_val * 1000; + + return 0; +} + +static int da9150_charger_battery_current_max(struct da9150_charger *charger, + union power_supply_propval *val) +{ + int reg; + + reg = da9150_reg_read(charger->da9150, DA9150_PPR_CHGCTRL_D); + + /* 25mA increments */ + val->intval = reg * 25000; + + return 0; +} + +static int da9150_charger_battery_voltage_max(struct da9150_charger *charger, + union power_supply_propval *val) +{ + u8 reg; + + reg = da9150_reg_read(charger->da9150, DA9150_PPR_CHGCTRL_B); + + /* Value starts at 3650 mV, 25 mV increments, presented in uV */ + val->intval = ((reg & DA9150_CHG_VBAT_MASK) * 25000) + 3650000; + return 0; +} + +static enum power_supply_property da9150_charger_bat_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_CHARGE_TYPE, + POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX, +}; + +static int da9150_charger_battery_get_prop(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct da9150_charger *charger = dev_get_drvdata(psy->dev.parent); + int ret; + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + ret = da9150_charger_battery_status(charger, val); + break; + case POWER_SUPPLY_PROP_ONLINE: + ret = da9150_charger_supply_online(charger, psy, val); + break; + case POWER_SUPPLY_PROP_HEALTH: + ret = da9150_charger_battery_health(charger, val); + break; + case POWER_SUPPLY_PROP_PRESENT: + ret = da9150_charger_battery_present(charger, val); + break; + case POWER_SUPPLY_PROP_CHARGE_TYPE: + ret = da9150_charger_battery_charge_type(charger, val); + break; + case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: + ret = da9150_charger_battery_voltage_min(charger, val); + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + ret = da9150_charger_battery_voltage_now(charger, val); + break; + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX: + ret = da9150_charger_battery_current_max(charger, val); + break; + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX: + ret = da9150_charger_battery_voltage_max(charger, val); + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static irqreturn_t da9150_charger_chg_irq(int irq, void *data) +{ + struct da9150_charger *charger = data; + + power_supply_changed(charger->battery); + + return IRQ_HANDLED; +} + +static irqreturn_t da9150_charger_tjunc_irq(int irq, void *data) +{ + struct da9150_charger *charger = data; + + /* Nothing we can really do except report this. */ + dev_crit(charger->dev, "TJunc over temperature!!!\n"); + power_supply_changed(charger->usb); + + return IRQ_HANDLED; +} + +static irqreturn_t da9150_charger_vfault_irq(int irq, void *data) +{ + struct da9150_charger *charger = data; + + /* Nothing we can really do except report this. */ + dev_crit(charger->dev, "VSYS under voltage!!!\n"); + power_supply_changed(charger->usb); + power_supply_changed(charger->battery); + + return IRQ_HANDLED; +} + +static irqreturn_t da9150_charger_vbus_irq(int irq, void *data) +{ + struct da9150_charger *charger = data; + u8 reg; + + reg = da9150_reg_read(charger->da9150, DA9150_STATUS_H); + + /* Charger plugged in or battery only */ + switch (reg & DA9150_VBUS_STAT_MASK) { + case DA9150_VBUS_STAT_OFF: + case DA9150_VBUS_STAT_WAIT: + charger->supply_online = charger->battery; + break; + case DA9150_VBUS_STAT_CHG: + charger->supply_online = charger->usb; + break; + default: + dev_warn(charger->dev, "Unknown VBUS state - reg = 0x%x\n", + reg); + charger->supply_online = NULL; + break; + } + + power_supply_changed(charger->usb); + power_supply_changed(charger->battery); + + return IRQ_HANDLED; +} + +static void da9150_charger_otg_work(struct work_struct *data) +{ + struct da9150_charger *charger = + container_of(data, struct da9150_charger, otg_work); + + switch (charger->usb_event) { + case USB_EVENT_ID: + /* Enable OTG Boost */ + da9150_set_bits(charger->da9150, DA9150_PPR_BKCTRL_A, + DA9150_VBUS_MODE_MASK, DA9150_VBUS_MODE_OTG); + break; + case USB_EVENT_NONE: + /* Revert to charge mode */ + power_supply_changed(charger->usb); + power_supply_changed(charger->battery); + da9150_set_bits(charger->da9150, DA9150_PPR_BKCTRL_A, + DA9150_VBUS_MODE_MASK, DA9150_VBUS_MODE_CHG); + break; + } +} + +static int da9150_charger_otg_ncb(struct notifier_block *nb, unsigned long val, + void *priv) +{ + struct da9150_charger *charger = + container_of(nb, struct da9150_charger, otg_nb); + + dev_dbg(charger->dev, "DA9150 OTG notify %lu\n", val); + + charger->usb_event = val; + schedule_work(&charger->otg_work); + + return NOTIFY_OK; +} + +static int da9150_charger_register_irq(struct platform_device *pdev, + irq_handler_t handler, + const char *irq_name) +{ + struct device *dev = &pdev->dev; + struct da9150_charger *charger = platform_get_drvdata(pdev); + int irq, ret; + + irq = platform_get_irq_byname(pdev, irq_name); + if (irq < 0) { + dev_err(dev, "Failed to get IRQ CHG_STATUS: %d\n", irq); + return irq; + } + + ret = request_threaded_irq(irq, NULL, handler, IRQF_ONESHOT, irq_name, + charger); + if (ret) + dev_err(dev, "Failed to request IRQ %d: %d\n", irq, ret); + + return ret; +} + +static void da9150_charger_unregister_irq(struct platform_device *pdev, + const char *irq_name) +{ + struct device *dev = &pdev->dev; + struct da9150_charger *charger = platform_get_drvdata(pdev); + int irq; + + irq = platform_get_irq_byname(pdev, irq_name); + if (irq < 0) { + dev_err(dev, "Failed to get IRQ CHG_STATUS: %d\n", irq); + return; + } + + free_irq(irq, charger); +} + +static const struct power_supply_desc usb_desc = { + .name = "da9150-usb", + .type = POWER_SUPPLY_TYPE_USB, + .properties = da9150_charger_props, + .num_properties = ARRAY_SIZE(da9150_charger_props), + .get_property = da9150_charger_get_prop, +}; + +static const struct power_supply_desc battery_desc = { + .name = "da9150-battery", + .type = POWER_SUPPLY_TYPE_BATTERY, + .properties = da9150_charger_bat_props, + .num_properties = ARRAY_SIZE(da9150_charger_bat_props), + .get_property = da9150_charger_battery_get_prop, +}; + +static int da9150_charger_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct da9150 *da9150 = dev_get_drvdata(dev->parent); + struct da9150_charger *charger; + u8 reg; + int ret; + + charger = devm_kzalloc(dev, sizeof(struct da9150_charger), GFP_KERNEL); + if (!charger) + return -ENOMEM; + + platform_set_drvdata(pdev, charger); + charger->da9150 = da9150; + charger->dev = dev; + + /* Acquire ADC channels */ + charger->ibus_chan = iio_channel_get(dev, "CHAN_IBUS"); + if (IS_ERR(charger->ibus_chan)) { + ret = PTR_ERR(charger->ibus_chan); + goto ibus_chan_fail; + } + + charger->vbus_chan = iio_channel_get(dev, "CHAN_VBUS"); + if (IS_ERR(charger->vbus_chan)) { + ret = PTR_ERR(charger->vbus_chan); + goto vbus_chan_fail; + } + + charger->tjunc_chan = iio_channel_get(dev, "CHAN_TJUNC"); + if (IS_ERR(charger->tjunc_chan)) { + ret = PTR_ERR(charger->tjunc_chan); + goto tjunc_chan_fail; + } + + charger->vbat_chan = iio_channel_get(dev, "CHAN_VBAT"); + if (IS_ERR(charger->vbat_chan)) { + ret = PTR_ERR(charger->vbat_chan); + goto vbat_chan_fail; + } + + /* Register power supplies */ + charger->usb = power_supply_register(dev, &usb_desc, NULL); + if (IS_ERR(charger->usb)) { + ret = PTR_ERR(charger->usb); + goto usb_fail; + } + + charger->battery = power_supply_register(dev, &battery_desc, NULL); + if (IS_ERR(charger->battery)) { + ret = PTR_ERR(charger->battery); + goto battery_fail; + } + + /* Get initial online supply */ + reg = da9150_reg_read(da9150, DA9150_STATUS_H); + + switch (reg & DA9150_VBUS_STAT_MASK) { + case DA9150_VBUS_STAT_OFF: + case DA9150_VBUS_STAT_WAIT: + charger->supply_online = charger->battery; + break; + case DA9150_VBUS_STAT_CHG: + charger->supply_online = charger->usb; + break; + default: + dev_warn(dev, "Unknown VBUS state - reg = 0x%x\n", reg); + charger->supply_online = NULL; + break; + } + + /* Setup OTG reporting & configuration */ + charger->usb_phy = devm_usb_get_phy(dev, USB_PHY_TYPE_USB2); + if (!IS_ERR_OR_NULL(charger->usb_phy)) { + INIT_WORK(&charger->otg_work, da9150_charger_otg_work); + charger->otg_nb.notifier_call = da9150_charger_otg_ncb; + usb_register_notifier(charger->usb_phy, &charger->otg_nb); + } + + /* Register IRQs */ + ret = da9150_charger_register_irq(pdev, da9150_charger_chg_irq, + "CHG_STATUS"); + if (ret < 0) + goto chg_irq_fail; + + ret = da9150_charger_register_irq(pdev, da9150_charger_tjunc_irq, + "CHG_TJUNC"); + if (ret < 0) + goto tjunc_irq_fail; + + ret = da9150_charger_register_irq(pdev, da9150_charger_vfault_irq, + "CHG_VFAULT"); + if (ret < 0) + goto vfault_irq_fail; + + ret = da9150_charger_register_irq(pdev, da9150_charger_vbus_irq, + "CHG_VBUS"); + if (ret < 0) + goto vbus_irq_fail; + + return 0; + + +vbus_irq_fail: + da9150_charger_unregister_irq(pdev, "CHG_VFAULT"); +vfault_irq_fail: + da9150_charger_unregister_irq(pdev, "CHG_TJUNC"); +tjunc_irq_fail: + da9150_charger_unregister_irq(pdev, "CHG_STATUS"); +chg_irq_fail: + if (!IS_ERR_OR_NULL(charger->usb_phy)) + usb_unregister_notifier(charger->usb_phy, &charger->otg_nb); +battery_fail: + power_supply_unregister(charger->usb); + +usb_fail: + iio_channel_release(charger->vbat_chan); + +vbat_chan_fail: + iio_channel_release(charger->tjunc_chan); + +tjunc_chan_fail: + iio_channel_release(charger->vbus_chan); + +vbus_chan_fail: + iio_channel_release(charger->ibus_chan); + +ibus_chan_fail: + return ret; +} + +static int da9150_charger_remove(struct platform_device *pdev) +{ + struct da9150_charger *charger = platform_get_drvdata(pdev); + int irq; + + /* Make sure IRQs are released before unregistering power supplies */ + irq = platform_get_irq_byname(pdev, "CHG_VBUS"); + free_irq(irq, charger); + + irq = platform_get_irq_byname(pdev, "CHG_VFAULT"); + free_irq(irq, charger); + + irq = platform_get_irq_byname(pdev, "CHG_TJUNC"); + free_irq(irq, charger); + + irq = platform_get_irq_byname(pdev, "CHG_STATUS"); + free_irq(irq, charger); + + if (!IS_ERR_OR_NULL(charger->usb_phy)) + usb_unregister_notifier(charger->usb_phy, &charger->otg_nb); + + power_supply_unregister(charger->battery); + power_supply_unregister(charger->usb); + + /* Release ADC channels */ + iio_channel_release(charger->ibus_chan); + iio_channel_release(charger->vbus_chan); + iio_channel_release(charger->tjunc_chan); + iio_channel_release(charger->vbat_chan); + + return 0; +} + +static struct platform_driver da9150_charger_driver = { + .driver = { + .name = "da9150-charger", + }, + .probe = da9150_charger_probe, + .remove = da9150_charger_remove, +}; + +module_platform_driver(da9150_charger_driver); + +MODULE_DESCRIPTION("Charger Driver for DA9150"); +MODULE_AUTHOR("Adam Thomson "); +MODULE_LICENSE("GPL"); diff --git a/drivers/power/supply/da9150-fg.c b/drivers/power/supply/da9150-fg.c new file mode 100644 index 000000000000..8b8ce978656a --- /dev/null +++ b/drivers/power/supply/da9150-fg.c @@ -0,0 +1,579 @@ +/* + * DA9150 Fuel-Gauge Driver + * + * Copyright (c) 2015 Dialog Semiconductor + * + * Author: Adam Thomson + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Core2Wire */ +#define DA9150_QIF_READ (0x0 << 7) +#define DA9150_QIF_WRITE (0x1 << 7) +#define DA9150_QIF_CODE_MASK 0x7F + +#define DA9150_QIF_BYTE_SIZE 8 +#define DA9150_QIF_BYTE_MASK 0xFF +#define DA9150_QIF_SHORT_SIZE 2 +#define DA9150_QIF_LONG_SIZE 4 + +/* QIF Codes */ +#define DA9150_QIF_UAVG 6 +#define DA9150_QIF_UAVG_SIZE DA9150_QIF_LONG_SIZE +#define DA9150_QIF_IAVG 8 +#define DA9150_QIF_IAVG_SIZE DA9150_QIF_LONG_SIZE +#define DA9150_QIF_NTCAVG 12 +#define DA9150_QIF_NTCAVG_SIZE DA9150_QIF_LONG_SIZE +#define DA9150_QIF_SHUNT_VAL 36 +#define DA9150_QIF_SHUNT_VAL_SIZE DA9150_QIF_SHORT_SIZE +#define DA9150_QIF_SD_GAIN 38 +#define DA9150_QIF_SD_GAIN_SIZE DA9150_QIF_LONG_SIZE +#define DA9150_QIF_FCC_MAH 40 +#define DA9150_QIF_FCC_MAH_SIZE DA9150_QIF_SHORT_SIZE +#define DA9150_QIF_SOC_PCT 43 +#define DA9150_QIF_SOC_PCT_SIZE DA9150_QIF_SHORT_SIZE +#define DA9150_QIF_CHARGE_LIMIT 44 +#define DA9150_QIF_CHARGE_LIMIT_SIZE DA9150_QIF_SHORT_SIZE +#define DA9150_QIF_DISCHARGE_LIMIT 45 +#define DA9150_QIF_DISCHARGE_LIMIT_SIZE DA9150_QIF_SHORT_SIZE +#define DA9150_QIF_FW_MAIN_VER 118 +#define DA9150_QIF_FW_MAIN_VER_SIZE DA9150_QIF_SHORT_SIZE +#define DA9150_QIF_E_FG_STATUS 126 +#define DA9150_QIF_E_FG_STATUS_SIZE DA9150_QIF_SHORT_SIZE +#define DA9150_QIF_SYNC 127 +#define DA9150_QIF_SYNC_SIZE DA9150_QIF_SHORT_SIZE +#define DA9150_QIF_MAX_CODES 128 + +/* QIF Sync Timeout */ +#define DA9150_QIF_SYNC_TIMEOUT 1000 +#define DA9150_QIF_SYNC_RETRIES 10 + +/* QIF E_FG_STATUS */ +#define DA9150_FG_IRQ_LOW_SOC_MASK (1 << 0) +#define DA9150_FG_IRQ_HIGH_SOC_MASK (1 << 1) +#define DA9150_FG_IRQ_SOC_MASK \ + (DA9150_FG_IRQ_LOW_SOC_MASK | DA9150_FG_IRQ_HIGH_SOC_MASK) + +/* Private data */ +struct da9150_fg { + struct da9150 *da9150; + struct device *dev; + + struct mutex io_lock; + + struct power_supply *battery; + struct delayed_work work; + u32 interval; + + int warn_soc; + int crit_soc; + int soc; +}; + +/* Battery Properties */ +static u32 da9150_fg_read_attr(struct da9150_fg *fg, u8 code, u8 size) + +{ + u8 buf[size]; + u8 read_addr; + u32 res = 0; + int i; + + /* Set QIF code (READ mode) */ + read_addr = (code & DA9150_QIF_CODE_MASK) | DA9150_QIF_READ; + + da9150_read_qif(fg->da9150, read_addr, size, buf); + for (i = 0; i < size; ++i) + res |= (buf[i] << (i * DA9150_QIF_BYTE_SIZE)); + + return res; +} + +static void da9150_fg_write_attr(struct da9150_fg *fg, u8 code, u8 size, + u32 val) + +{ + u8 buf[size]; + u8 write_addr; + int i; + + /* Set QIF code (WRITE mode) */ + write_addr = (code & DA9150_QIF_CODE_MASK) | DA9150_QIF_WRITE; + + for (i = 0; i < size; ++i) { + buf[i] = (val >> (i * DA9150_QIF_BYTE_SIZE)) & + DA9150_QIF_BYTE_MASK; + } + da9150_write_qif(fg->da9150, write_addr, size, buf); +} + +/* Trigger QIF Sync to update QIF readable data */ +static void da9150_fg_read_sync_start(struct da9150_fg *fg) +{ + int i = 0; + u32 res = 0; + + mutex_lock(&fg->io_lock); + + /* Check if QIF sync already requested, and write to sync if not */ + res = da9150_fg_read_attr(fg, DA9150_QIF_SYNC, + DA9150_QIF_SYNC_SIZE); + if (res > 0) + da9150_fg_write_attr(fg, DA9150_QIF_SYNC, + DA9150_QIF_SYNC_SIZE, 0); + + /* Wait for sync to complete */ + res = 0; + while ((res == 0) && (i++ < DA9150_QIF_SYNC_RETRIES)) { + usleep_range(DA9150_QIF_SYNC_TIMEOUT, + DA9150_QIF_SYNC_TIMEOUT * 2); + res = da9150_fg_read_attr(fg, DA9150_QIF_SYNC, + DA9150_QIF_SYNC_SIZE); + } + + /* Check if sync completed */ + if (res == 0) + dev_err(fg->dev, "Failed to perform QIF read sync!\n"); +} + +/* + * Should always be called after QIF sync read has been performed, and all + * attributes required have been accessed. + */ +static inline void da9150_fg_read_sync_end(struct da9150_fg *fg) +{ + mutex_unlock(&fg->io_lock); +} + +/* Sync read of single QIF attribute */ +static u32 da9150_fg_read_attr_sync(struct da9150_fg *fg, u8 code, u8 size) +{ + u32 val; + + da9150_fg_read_sync_start(fg); + val = da9150_fg_read_attr(fg, code, size); + da9150_fg_read_sync_end(fg); + + return val; +} + +/* Wait for QIF Sync, write QIF data and wait for ack */ +static void da9150_fg_write_attr_sync(struct da9150_fg *fg, u8 code, u8 size, + u32 val) +{ + int i = 0; + u32 res = 0, sync_val; + + mutex_lock(&fg->io_lock); + + /* Check if QIF sync already requested */ + res = da9150_fg_read_attr(fg, DA9150_QIF_SYNC, + DA9150_QIF_SYNC_SIZE); + + /* Wait for an existing sync to complete */ + while ((res == 0) && (i++ < DA9150_QIF_SYNC_RETRIES)) { + usleep_range(DA9150_QIF_SYNC_TIMEOUT, + DA9150_QIF_SYNC_TIMEOUT * 2); + res = da9150_fg_read_attr(fg, DA9150_QIF_SYNC, + DA9150_QIF_SYNC_SIZE); + } + + if (res == 0) { + dev_err(fg->dev, "Timeout waiting for existing QIF sync!\n"); + mutex_unlock(&fg->io_lock); + return; + } + + /* Write value for QIF code */ + da9150_fg_write_attr(fg, code, size, val); + + /* Wait for write acknowledgment */ + i = 0; + sync_val = res; + while ((res == sync_val) && (i++ < DA9150_QIF_SYNC_RETRIES)) { + usleep_range(DA9150_QIF_SYNC_TIMEOUT, + DA9150_QIF_SYNC_TIMEOUT * 2); + res = da9150_fg_read_attr(fg, DA9150_QIF_SYNC, + DA9150_QIF_SYNC_SIZE); + } + + mutex_unlock(&fg->io_lock); + + /* Check write was actually successful */ + if (res != (sync_val + 1)) + dev_err(fg->dev, "Error performing QIF sync write for code %d\n", + code); +} + +/* Power Supply attributes */ +static int da9150_fg_capacity(struct da9150_fg *fg, + union power_supply_propval *val) +{ + val->intval = da9150_fg_read_attr_sync(fg, DA9150_QIF_SOC_PCT, + DA9150_QIF_SOC_PCT_SIZE); + + if (val->intval > 100) + val->intval = 100; + + return 0; +} + +static int da9150_fg_current_avg(struct da9150_fg *fg, + union power_supply_propval *val) +{ + u32 iavg, sd_gain, shunt_val; + u64 div, res; + + da9150_fg_read_sync_start(fg); + iavg = da9150_fg_read_attr(fg, DA9150_QIF_IAVG, + DA9150_QIF_IAVG_SIZE); + shunt_val = da9150_fg_read_attr(fg, DA9150_QIF_SHUNT_VAL, + DA9150_QIF_SHUNT_VAL_SIZE); + sd_gain = da9150_fg_read_attr(fg, DA9150_QIF_SD_GAIN, + DA9150_QIF_SD_GAIN_SIZE); + da9150_fg_read_sync_end(fg); + + div = (u64) (sd_gain * shunt_val * 65536ULL); + do_div(div, 1000000); + res = (u64) (iavg * 1000000ULL); + do_div(res, div); + + val->intval = (int) res; + + return 0; +} + +static int da9150_fg_voltage_avg(struct da9150_fg *fg, + union power_supply_propval *val) +{ + u64 res; + + val->intval = da9150_fg_read_attr_sync(fg, DA9150_QIF_UAVG, + DA9150_QIF_UAVG_SIZE); + + res = (u64) (val->intval * 186ULL); + do_div(res, 10000); + val->intval = (int) res; + + return 0; +} + +static int da9150_fg_charge_full(struct da9150_fg *fg, + union power_supply_propval *val) +{ + val->intval = da9150_fg_read_attr_sync(fg, DA9150_QIF_FCC_MAH, + DA9150_QIF_FCC_MAH_SIZE); + + val->intval = val->intval * 1000; + + return 0; +} + +/* + * Temperature reading from device is only valid if battery/system provides + * valid NTC to associated pin of DA9150 chip. + */ +static int da9150_fg_temp(struct da9150_fg *fg, + union power_supply_propval *val) +{ + val->intval = da9150_fg_read_attr_sync(fg, DA9150_QIF_NTCAVG, + DA9150_QIF_NTCAVG_SIZE); + + val->intval = (val->intval * 10) / 1048576; + + return 0; +} + +static enum power_supply_property da9150_fg_props[] = { + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_CURRENT_AVG, + POWER_SUPPLY_PROP_VOLTAGE_AVG, + POWER_SUPPLY_PROP_CHARGE_FULL, + POWER_SUPPLY_PROP_TEMP, +}; + +static int da9150_fg_get_prop(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct da9150_fg *fg = dev_get_drvdata(psy->dev.parent); + int ret; + + switch (psp) { + case POWER_SUPPLY_PROP_CAPACITY: + ret = da9150_fg_capacity(fg, val); + break; + case POWER_SUPPLY_PROP_CURRENT_AVG: + ret = da9150_fg_current_avg(fg, val); + break; + case POWER_SUPPLY_PROP_VOLTAGE_AVG: + ret = da9150_fg_voltage_avg(fg, val); + break; + case POWER_SUPPLY_PROP_CHARGE_FULL: + ret = da9150_fg_charge_full(fg, val); + break; + case POWER_SUPPLY_PROP_TEMP: + ret = da9150_fg_temp(fg, val); + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +/* Repeated SOC check */ +static bool da9150_fg_soc_changed(struct da9150_fg *fg) +{ + union power_supply_propval val; + + da9150_fg_capacity(fg, &val); + if (val.intval != fg->soc) { + fg->soc = val.intval; + return true; + } + + return false; +} + +static void da9150_fg_work(struct work_struct *work) +{ + struct da9150_fg *fg = container_of(work, struct da9150_fg, work.work); + + /* Report if SOC has changed */ + if (da9150_fg_soc_changed(fg)) + power_supply_changed(fg->battery); + + schedule_delayed_work(&fg->work, msecs_to_jiffies(fg->interval)); +} + +/* SOC level event configuration */ +static void da9150_fg_soc_event_config(struct da9150_fg *fg) +{ + int soc; + + soc = da9150_fg_read_attr_sync(fg, DA9150_QIF_SOC_PCT, + DA9150_QIF_SOC_PCT_SIZE); + + if (soc > fg->warn_soc) { + /* If SOC > warn level, set discharge warn level event */ + da9150_fg_write_attr_sync(fg, DA9150_QIF_DISCHARGE_LIMIT, + DA9150_QIF_DISCHARGE_LIMIT_SIZE, + fg->warn_soc + 1); + } else if ((soc <= fg->warn_soc) && (soc > fg->crit_soc)) { + /* + * If SOC <= warn level, set discharge crit level event, + * and set charge warn level event. + */ + da9150_fg_write_attr_sync(fg, DA9150_QIF_DISCHARGE_LIMIT, + DA9150_QIF_DISCHARGE_LIMIT_SIZE, + fg->crit_soc + 1); + + da9150_fg_write_attr_sync(fg, DA9150_QIF_CHARGE_LIMIT, + DA9150_QIF_CHARGE_LIMIT_SIZE, + fg->warn_soc); + } else if (soc <= fg->crit_soc) { + /* If SOC <= crit level, set charge crit level event */ + da9150_fg_write_attr_sync(fg, DA9150_QIF_CHARGE_LIMIT, + DA9150_QIF_CHARGE_LIMIT_SIZE, + fg->crit_soc); + } +} + +static irqreturn_t da9150_fg_irq(int irq, void *data) +{ + struct da9150_fg *fg = data; + u32 e_fg_status; + + /* Read FG IRQ status info */ + e_fg_status = da9150_fg_read_attr(fg, DA9150_QIF_E_FG_STATUS, + DA9150_QIF_E_FG_STATUS_SIZE); + + /* Handle warning/critical threhold events */ + if (e_fg_status & DA9150_FG_IRQ_SOC_MASK) + da9150_fg_soc_event_config(fg); + + /* Clear any FG IRQs */ + da9150_fg_write_attr(fg, DA9150_QIF_E_FG_STATUS, + DA9150_QIF_E_FG_STATUS_SIZE, e_fg_status); + + return IRQ_HANDLED; +} + +static struct da9150_fg_pdata *da9150_fg_dt_pdata(struct device *dev) +{ + struct device_node *fg_node = dev->of_node; + struct da9150_fg_pdata *pdata; + + pdata = devm_kzalloc(dev, sizeof(struct da9150_fg_pdata), GFP_KERNEL); + if (!pdata) + return NULL; + + of_property_read_u32(fg_node, "dlg,update-interval", + &pdata->update_interval); + of_property_read_u8(fg_node, "dlg,warn-soc-level", + &pdata->warn_soc_lvl); + of_property_read_u8(fg_node, "dlg,crit-soc-level", + &pdata->crit_soc_lvl); + + return pdata; +} + +static const struct power_supply_desc fg_desc = { + .name = "da9150-fg", + .type = POWER_SUPPLY_TYPE_BATTERY, + .properties = da9150_fg_props, + .num_properties = ARRAY_SIZE(da9150_fg_props), + .get_property = da9150_fg_get_prop, +}; + +static int da9150_fg_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct da9150 *da9150 = dev_get_drvdata(dev->parent); + struct da9150_fg_pdata *fg_pdata = dev_get_platdata(dev); + struct da9150_fg *fg; + int ver, irq, ret = 0; + + fg = devm_kzalloc(dev, sizeof(*fg), GFP_KERNEL); + if (fg == NULL) + return -ENOMEM; + + platform_set_drvdata(pdev, fg); + fg->da9150 = da9150; + fg->dev = dev; + + mutex_init(&fg->io_lock); + + /* Enable QIF */ + da9150_set_bits(da9150, DA9150_CORE2WIRE_CTRL_A, DA9150_FG_QIF_EN_MASK, + DA9150_FG_QIF_EN_MASK); + + fg->battery = devm_power_supply_register(dev, &fg_desc, NULL); + if (IS_ERR(fg->battery)) { + ret = PTR_ERR(fg->battery); + return ret; + } + + ver = da9150_fg_read_attr(fg, DA9150_QIF_FW_MAIN_VER, + DA9150_QIF_FW_MAIN_VER_SIZE); + dev_info(dev, "Version: 0x%x\n", ver); + + /* Handle DT data if provided */ + if (dev->of_node) { + fg_pdata = da9150_fg_dt_pdata(dev); + dev->platform_data = fg_pdata; + } + + /* Handle any pdata provided */ + if (fg_pdata) { + fg->interval = fg_pdata->update_interval; + + if (fg_pdata->warn_soc_lvl > 100) + dev_warn(dev, "Invalid SOC warning level provided, Ignoring"); + else + fg->warn_soc = fg_pdata->warn_soc_lvl; + + if ((fg_pdata->crit_soc_lvl > 100) || + (fg_pdata->crit_soc_lvl >= fg_pdata->warn_soc_lvl)) + dev_warn(dev, "Invalid SOC critical level provided, Ignoring"); + else + fg->crit_soc = fg_pdata->crit_soc_lvl; + + + } + + /* Configure initial SOC level events */ + da9150_fg_soc_event_config(fg); + + /* + * If an interval period has been provided then setup repeating + * work for reporting data updates. + */ + if (fg->interval) { + INIT_DELAYED_WORK(&fg->work, da9150_fg_work); + schedule_delayed_work(&fg->work, + msecs_to_jiffies(fg->interval)); + } + + /* Register IRQ */ + irq = platform_get_irq_byname(pdev, "FG"); + if (irq < 0) { + dev_err(dev, "Failed to get IRQ FG: %d\n", irq); + ret = irq; + goto irq_fail; + } + + ret = devm_request_threaded_irq(dev, irq, NULL, da9150_fg_irq, + IRQF_ONESHOT, "FG", fg); + if (ret) { + dev_err(dev, "Failed to request IRQ %d: %d\n", irq, ret); + goto irq_fail; + } + + return 0; + +irq_fail: + if (fg->interval) + cancel_delayed_work(&fg->work); + + return ret; +} + +static int da9150_fg_remove(struct platform_device *pdev) +{ + struct da9150_fg *fg = platform_get_drvdata(pdev); + + if (fg->interval) + cancel_delayed_work(&fg->work); + + return 0; +} + +static int da9150_fg_resume(struct platform_device *pdev) +{ + struct da9150_fg *fg = platform_get_drvdata(pdev); + + /* + * Trigger SOC check to happen now so as to indicate any value change + * since last check before suspend. + */ + if (fg->interval) + flush_delayed_work(&fg->work); + + return 0; +} + +static struct platform_driver da9150_fg_driver = { + .driver = { + .name = "da9150-fuel-gauge", + }, + .probe = da9150_fg_probe, + .remove = da9150_fg_remove, + .resume = da9150_fg_resume, +}; + +module_platform_driver(da9150_fg_driver); + +MODULE_DESCRIPTION("Fuel-Gauge Driver for DA9150"); +MODULE_AUTHOR("Adam Thomson "); +MODULE_LICENSE("GPL"); diff --git a/drivers/power/supply/ds2760_battery.c b/drivers/power/supply/ds2760_battery.c new file mode 100644 index 000000000000..369ab00bf453 --- /dev/null +++ b/drivers/power/supply/ds2760_battery.c @@ -0,0 +1,647 @@ +/* + * Driver for batteries with DS2760 chips inside. + * + * Copyright © 2007 Anton Vorontsov + * 2004-2007 Matt Reimer + * 2004 Szabolcs Gyurko + * + * Use consistent with the GNU GPL is permitted, + * provided that this copyright notice is + * preserved in its entirety in all copies and derived works. + * + * Author: Anton Vorontsov + * February 2007 + * + * Matt Reimer + * April 2004, 2005, 2007 + * + * Szabolcs Gyurko + * September 2004 + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../../w1/w1.h" +#include "../../w1/slaves/w1_ds2760.h" + +struct ds2760_device_info { + struct device *dev; + + /* DS2760 data, valid after calling ds2760_battery_read_status() */ + unsigned long update_time; /* jiffies when data read */ + char raw[DS2760_DATA_SIZE]; /* raw DS2760 data */ + int voltage_raw; /* units of 4.88 mV */ + int voltage_uV; /* units of µV */ + int current_raw; /* units of 0.625 mA */ + int current_uA; /* units of µA */ + int accum_current_raw; /* units of 0.25 mAh */ + int accum_current_uAh; /* units of µAh */ + int temp_raw; /* units of 0.125 °C */ + int temp_C; /* units of 0.1 °C */ + int rated_capacity; /* units of µAh */ + int rem_capacity; /* percentage */ + int full_active_uAh; /* units of µAh */ + int empty_uAh; /* units of µAh */ + int life_sec; /* units of seconds */ + int charge_status; /* POWER_SUPPLY_STATUS_* */ + + int full_counter; + struct power_supply *bat; + struct power_supply_desc bat_desc; + struct device *w1_dev; + struct workqueue_struct *monitor_wqueue; + struct delayed_work monitor_work; + struct delayed_work set_charged_work; +}; + +static unsigned int cache_time = 1000; +module_param(cache_time, uint, 0644); +MODULE_PARM_DESC(cache_time, "cache time in milliseconds"); + +static bool pmod_enabled; +module_param(pmod_enabled, bool, 0644); +MODULE_PARM_DESC(pmod_enabled, "PMOD enable bit"); + +static unsigned int rated_capacity; +module_param(rated_capacity, uint, 0644); +MODULE_PARM_DESC(rated_capacity, "rated battery capacity, 10*mAh or index"); + +static unsigned int current_accum; +module_param(current_accum, uint, 0644); +MODULE_PARM_DESC(current_accum, "current accumulator value"); + +/* Some batteries have their rated capacity stored a N * 10 mAh, while + * others use an index into this table. */ +static int rated_capacities[] = { + 0, + 920, /* Samsung */ + 920, /* BYD */ + 920, /* Lishen */ + 920, /* NEC */ + 1440, /* Samsung */ + 1440, /* BYD */ +#ifdef CONFIG_MACH_H4700 + 1800, /* HP iPAQ hx4700 3.7V 1800mAh (359113-001) */ +#else + 1440, /* Lishen */ +#endif + 1440, /* NEC */ + 2880, /* Samsung */ + 2880, /* BYD */ + 2880, /* Lishen */ + 2880, /* NEC */ +#ifdef CONFIG_MACH_H4700 + 0, + 3600, /* HP iPAQ hx4700 3.7V 3600mAh (359114-001) */ +#endif +}; + +/* array is level at temps 0°C, 10°C, 20°C, 30°C, 40°C + * temp is in Celsius */ +static int battery_interpolate(int array[], int temp) +{ + int index, dt; + + if (temp <= 0) + return array[0]; + if (temp >= 40) + return array[4]; + + index = temp / 10; + dt = temp % 10; + + return array[index] + (((array[index + 1] - array[index]) * dt) / 10); +} + +static int ds2760_battery_read_status(struct ds2760_device_info *di) +{ + int ret, i, start, count, scale[5]; + + if (di->update_time && time_before(jiffies, di->update_time + + msecs_to_jiffies(cache_time))) + return 0; + + /* The first time we read the entire contents of SRAM/EEPROM, + * but after that we just read the interesting bits that change. */ + if (di->update_time == 0) { + start = 0; + count = DS2760_DATA_SIZE; + } else { + start = DS2760_VOLTAGE_MSB; + count = DS2760_TEMP_LSB - start + 1; + } + + ret = w1_ds2760_read(di->w1_dev, di->raw + start, start, count); + if (ret != count) { + dev_warn(di->dev, "call to w1_ds2760_read failed (0x%p)\n", + di->w1_dev); + return 1; + } + + di->update_time = jiffies; + + /* DS2760 reports voltage in units of 4.88mV, but the battery class + * reports in units of uV, so convert by multiplying by 4880. */ + di->voltage_raw = (di->raw[DS2760_VOLTAGE_MSB] << 3) | + (di->raw[DS2760_VOLTAGE_LSB] >> 5); + di->voltage_uV = di->voltage_raw * 4880; + + /* DS2760 reports current in signed units of 0.625mA, but the battery + * class reports in units of µA, so convert by multiplying by 625. */ + di->current_raw = + (((signed char)di->raw[DS2760_CURRENT_MSB]) << 5) | + (di->raw[DS2760_CURRENT_LSB] >> 3); + di->current_uA = di->current_raw * 625; + + /* DS2760 reports accumulated current in signed units of 0.25mAh. */ + di->accum_current_raw = + (((signed char)di->raw[DS2760_CURRENT_ACCUM_MSB]) << 8) | + di->raw[DS2760_CURRENT_ACCUM_LSB]; + di->accum_current_uAh = di->accum_current_raw * 250; + + /* DS2760 reports temperature in signed units of 0.125°C, but the + * battery class reports in units of 1/10 °C, so we convert by + * multiplying by .125 * 10 = 1.25. */ + di->temp_raw = (((signed char)di->raw[DS2760_TEMP_MSB]) << 3) | + (di->raw[DS2760_TEMP_LSB] >> 5); + di->temp_C = di->temp_raw + (di->temp_raw / 4); + + /* At least some battery monitors (e.g. HP iPAQ) store the battery's + * maximum rated capacity. */ + if (di->raw[DS2760_RATED_CAPACITY] < ARRAY_SIZE(rated_capacities)) + di->rated_capacity = rated_capacities[ + (unsigned int)di->raw[DS2760_RATED_CAPACITY]]; + else + di->rated_capacity = di->raw[DS2760_RATED_CAPACITY] * 10; + + di->rated_capacity *= 1000; /* convert to µAh */ + + /* Calculate the full level at the present temperature. */ + di->full_active_uAh = di->raw[DS2760_ACTIVE_FULL] << 8 | + di->raw[DS2760_ACTIVE_FULL + 1]; + + /* If the full_active_uAh value is not given, fall back to the rated + * capacity. This is likely to happen when chips are not part of the + * battery pack and is therefore not bootstrapped. */ + if (di->full_active_uAh == 0) + di->full_active_uAh = di->rated_capacity / 1000L; + + scale[0] = di->full_active_uAh; + for (i = 1; i < 5; i++) + scale[i] = scale[i - 1] + di->raw[DS2760_ACTIVE_FULL + 1 + i]; + + di->full_active_uAh = battery_interpolate(scale, di->temp_C / 10); + di->full_active_uAh *= 1000; /* convert to µAh */ + + /* Calculate the empty level at the present temperature. */ + scale[4] = di->raw[DS2760_ACTIVE_EMPTY + 4]; + for (i = 3; i >= 0; i--) + scale[i] = scale[i + 1] + di->raw[DS2760_ACTIVE_EMPTY + i]; + + di->empty_uAh = battery_interpolate(scale, di->temp_C / 10); + di->empty_uAh *= 1000; /* convert to µAh */ + + if (di->full_active_uAh == di->empty_uAh) + di->rem_capacity = 0; + else + /* From Maxim Application Note 131: remaining capacity = + * ((ICA - Empty Value) / (Full Value - Empty Value)) x 100% */ + di->rem_capacity = ((di->accum_current_uAh - di->empty_uAh) * 100L) / + (di->full_active_uAh - di->empty_uAh); + + if (di->rem_capacity < 0) + di->rem_capacity = 0; + if (di->rem_capacity > 100) + di->rem_capacity = 100; + + if (di->current_uA < -100L) + di->life_sec = -((di->accum_current_uAh - di->empty_uAh) * 36L) + / (di->current_uA / 100L); + else + di->life_sec = 0; + + return 0; +} + +static void ds2760_battery_set_current_accum(struct ds2760_device_info *di, + unsigned int acr_val) +{ + unsigned char acr[2]; + + /* acr is in units of 0.25 mAh */ + acr_val *= 4L; + acr_val /= 1000; + + acr[0] = acr_val >> 8; + acr[1] = acr_val & 0xff; + + if (w1_ds2760_write(di->w1_dev, acr, DS2760_CURRENT_ACCUM_MSB, 2) < 2) + dev_warn(di->dev, "ACR write failed\n"); +} + +static void ds2760_battery_update_status(struct ds2760_device_info *di) +{ + int old_charge_status = di->charge_status; + + ds2760_battery_read_status(di); + + if (di->charge_status == POWER_SUPPLY_STATUS_UNKNOWN) + di->full_counter = 0; + + if (power_supply_am_i_supplied(di->bat)) { + if (di->current_uA > 10000) { + di->charge_status = POWER_SUPPLY_STATUS_CHARGING; + di->full_counter = 0; + } else if (di->current_uA < -5000) { + if (di->charge_status != POWER_SUPPLY_STATUS_NOT_CHARGING) + dev_notice(di->dev, "not enough power to " + "charge\n"); + di->charge_status = POWER_SUPPLY_STATUS_NOT_CHARGING; + di->full_counter = 0; + } else if (di->current_uA < 10000 && + di->charge_status != POWER_SUPPLY_STATUS_FULL) { + + /* Don't consider the battery to be full unless + * we've seen the current < 10 mA at least two + * consecutive times. */ + + di->full_counter++; + + if (di->full_counter < 2) { + di->charge_status = POWER_SUPPLY_STATUS_CHARGING; + } else { + di->charge_status = POWER_SUPPLY_STATUS_FULL; + ds2760_battery_set_current_accum(di, + di->full_active_uAh); + } + } + } else { + di->charge_status = POWER_SUPPLY_STATUS_DISCHARGING; + di->full_counter = 0; + } + + if (di->charge_status != old_charge_status) + power_supply_changed(di->bat); +} + +static void ds2760_battery_write_status(struct ds2760_device_info *di, + char status) +{ + if (status == di->raw[DS2760_STATUS_REG]) + return; + + w1_ds2760_write(di->w1_dev, &status, DS2760_STATUS_WRITE_REG, 1); + w1_ds2760_store_eeprom(di->w1_dev, DS2760_EEPROM_BLOCK1); + w1_ds2760_recall_eeprom(di->w1_dev, DS2760_EEPROM_BLOCK1); +} + +static void ds2760_battery_write_rated_capacity(struct ds2760_device_info *di, + unsigned char rated_capacity) +{ + if (rated_capacity == di->raw[DS2760_RATED_CAPACITY]) + return; + + w1_ds2760_write(di->w1_dev, &rated_capacity, DS2760_RATED_CAPACITY, 1); + w1_ds2760_store_eeprom(di->w1_dev, DS2760_EEPROM_BLOCK1); + w1_ds2760_recall_eeprom(di->w1_dev, DS2760_EEPROM_BLOCK1); +} + +static void ds2760_battery_write_active_full(struct ds2760_device_info *di, + int active_full) +{ + unsigned char tmp[2] = { + active_full >> 8, + active_full & 0xff + }; + + if (tmp[0] == di->raw[DS2760_ACTIVE_FULL] && + tmp[1] == di->raw[DS2760_ACTIVE_FULL + 1]) + return; + + w1_ds2760_write(di->w1_dev, tmp, DS2760_ACTIVE_FULL, sizeof(tmp)); + w1_ds2760_store_eeprom(di->w1_dev, DS2760_EEPROM_BLOCK0); + w1_ds2760_recall_eeprom(di->w1_dev, DS2760_EEPROM_BLOCK0); + + /* Write to the di->raw[] buffer directly - the DS2760_ACTIVE_FULL + * values won't be read back by ds2760_battery_read_status() */ + di->raw[DS2760_ACTIVE_FULL] = tmp[0]; + di->raw[DS2760_ACTIVE_FULL + 1] = tmp[1]; +} + +static void ds2760_battery_work(struct work_struct *work) +{ + struct ds2760_device_info *di = container_of(work, + struct ds2760_device_info, monitor_work.work); + const int interval = HZ * 60; + + dev_dbg(di->dev, "%s\n", __func__); + + ds2760_battery_update_status(di); + queue_delayed_work(di->monitor_wqueue, &di->monitor_work, interval); +} + +static void ds2760_battery_external_power_changed(struct power_supply *psy) +{ + struct ds2760_device_info *di = power_supply_get_drvdata(psy); + + dev_dbg(di->dev, "%s\n", __func__); + + mod_delayed_work(di->monitor_wqueue, &di->monitor_work, HZ/10); +} + + +static void ds2760_battery_set_charged_work(struct work_struct *work) +{ + char bias; + struct ds2760_device_info *di = container_of(work, + struct ds2760_device_info, set_charged_work.work); + + dev_dbg(di->dev, "%s\n", __func__); + + ds2760_battery_read_status(di); + + /* When we get notified by external circuitry that the battery is + * considered fully charged now, we know that there is no current + * flow any more. However, the ds2760's internal current meter is + * too inaccurate to rely on - spec say something ~15% failure. + * Hence, we use the current offset bias register to compensate + * that error. + */ + + if (!power_supply_am_i_supplied(di->bat)) + return; + + bias = (signed char) di->current_raw + + (signed char) di->raw[DS2760_CURRENT_OFFSET_BIAS]; + + dev_dbg(di->dev, "%s: bias = %d\n", __func__, bias); + + w1_ds2760_write(di->w1_dev, &bias, DS2760_CURRENT_OFFSET_BIAS, 1); + w1_ds2760_store_eeprom(di->w1_dev, DS2760_EEPROM_BLOCK1); + w1_ds2760_recall_eeprom(di->w1_dev, DS2760_EEPROM_BLOCK1); + + /* Write to the di->raw[] buffer directly - the CURRENT_OFFSET_BIAS + * value won't be read back by ds2760_battery_read_status() */ + di->raw[DS2760_CURRENT_OFFSET_BIAS] = bias; +} + +static void ds2760_battery_set_charged(struct power_supply *psy) +{ + struct ds2760_device_info *di = power_supply_get_drvdata(psy); + + /* postpone the actual work by 20 secs. This is for debouncing GPIO + * signals and to let the current value settle. See AN4188. */ + mod_delayed_work(di->monitor_wqueue, &di->set_charged_work, HZ * 20); +} + +static int ds2760_battery_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct ds2760_device_info *di = power_supply_get_drvdata(psy); + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + val->intval = di->charge_status; + return 0; + default: + break; + } + + ds2760_battery_read_status(di); + + switch (psp) { + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + val->intval = di->voltage_uV; + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + val->intval = di->current_uA; + break; + case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: + val->intval = di->rated_capacity; + break; + case POWER_SUPPLY_PROP_CHARGE_FULL: + val->intval = di->full_active_uAh; + break; + case POWER_SUPPLY_PROP_CHARGE_EMPTY: + val->intval = di->empty_uAh; + break; + case POWER_SUPPLY_PROP_CHARGE_NOW: + val->intval = di->accum_current_uAh; + break; + case POWER_SUPPLY_PROP_TEMP: + val->intval = di->temp_C; + break; + case POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW: + val->intval = di->life_sec; + break; + case POWER_SUPPLY_PROP_CAPACITY: + val->intval = di->rem_capacity; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int ds2760_battery_set_property(struct power_supply *psy, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct ds2760_device_info *di = power_supply_get_drvdata(psy); + + switch (psp) { + case POWER_SUPPLY_PROP_CHARGE_FULL: + /* the interface counts in uAh, convert the value */ + ds2760_battery_write_active_full(di, val->intval / 1000L); + break; + + case POWER_SUPPLY_PROP_CHARGE_NOW: + /* ds2760_battery_set_current_accum() does the conversion */ + ds2760_battery_set_current_accum(di, val->intval); + break; + + default: + return -EPERM; + } + + return 0; +} + +static int ds2760_battery_property_is_writeable(struct power_supply *psy, + enum power_supply_property psp) +{ + switch (psp) { + case POWER_SUPPLY_PROP_CHARGE_FULL: + case POWER_SUPPLY_PROP_CHARGE_NOW: + return 1; + + default: + break; + } + + return 0; +} + +static enum power_supply_property ds2760_battery_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, + POWER_SUPPLY_PROP_CHARGE_FULL, + POWER_SUPPLY_PROP_CHARGE_EMPTY, + POWER_SUPPLY_PROP_CHARGE_NOW, + POWER_SUPPLY_PROP_TEMP, + POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW, + POWER_SUPPLY_PROP_CAPACITY, +}; + +static int ds2760_battery_probe(struct platform_device *pdev) +{ + struct power_supply_config psy_cfg = {}; + char status; + int retval = 0; + struct ds2760_device_info *di; + + di = devm_kzalloc(&pdev->dev, sizeof(*di), GFP_KERNEL); + if (!di) { + retval = -ENOMEM; + goto di_alloc_failed; + } + + platform_set_drvdata(pdev, di); + + di->dev = &pdev->dev; + di->w1_dev = pdev->dev.parent; + di->bat_desc.name = dev_name(&pdev->dev); + di->bat_desc.type = POWER_SUPPLY_TYPE_BATTERY; + di->bat_desc.properties = ds2760_battery_props; + di->bat_desc.num_properties = ARRAY_SIZE(ds2760_battery_props); + di->bat_desc.get_property = ds2760_battery_get_property; + di->bat_desc.set_property = ds2760_battery_set_property; + di->bat_desc.property_is_writeable = + ds2760_battery_property_is_writeable; + di->bat_desc.set_charged = ds2760_battery_set_charged; + di->bat_desc.external_power_changed = + ds2760_battery_external_power_changed; + + psy_cfg.drv_data = di; + + di->charge_status = POWER_SUPPLY_STATUS_UNKNOWN; + + /* enable sleep mode feature */ + ds2760_battery_read_status(di); + status = di->raw[DS2760_STATUS_REG]; + if (pmod_enabled) + status |= DS2760_STATUS_PMOD; + else + status &= ~DS2760_STATUS_PMOD; + + ds2760_battery_write_status(di, status); + + /* set rated capacity from module param */ + if (rated_capacity) + ds2760_battery_write_rated_capacity(di, rated_capacity); + + /* set current accumulator if given as parameter. + * this should only be done for bootstrapping the value */ + if (current_accum) + ds2760_battery_set_current_accum(di, current_accum); + + di->bat = power_supply_register(&pdev->dev, &di->bat_desc, &psy_cfg); + if (IS_ERR(di->bat)) { + dev_err(di->dev, "failed to register battery\n"); + retval = PTR_ERR(di->bat); + goto batt_failed; + } + + INIT_DELAYED_WORK(&di->monitor_work, ds2760_battery_work); + INIT_DELAYED_WORK(&di->set_charged_work, + ds2760_battery_set_charged_work); + di->monitor_wqueue = create_singlethread_workqueue(dev_name(&pdev->dev)); + if (!di->monitor_wqueue) { + retval = -ESRCH; + goto workqueue_failed; + } + queue_delayed_work(di->monitor_wqueue, &di->monitor_work, HZ * 1); + + goto success; + +workqueue_failed: + power_supply_unregister(di->bat); +batt_failed: +di_alloc_failed: +success: + return retval; +} + +static int ds2760_battery_remove(struct platform_device *pdev) +{ + struct ds2760_device_info *di = platform_get_drvdata(pdev); + + cancel_delayed_work_sync(&di->monitor_work); + cancel_delayed_work_sync(&di->set_charged_work); + destroy_workqueue(di->monitor_wqueue); + power_supply_unregister(di->bat); + + return 0; +} + +#ifdef CONFIG_PM + +static int ds2760_battery_suspend(struct platform_device *pdev, + pm_message_t state) +{ + struct ds2760_device_info *di = platform_get_drvdata(pdev); + + di->charge_status = POWER_SUPPLY_STATUS_UNKNOWN; + + return 0; +} + +static int ds2760_battery_resume(struct platform_device *pdev) +{ + struct ds2760_device_info *di = platform_get_drvdata(pdev); + + di->charge_status = POWER_SUPPLY_STATUS_UNKNOWN; + power_supply_changed(di->bat); + + mod_delayed_work(di->monitor_wqueue, &di->monitor_work, HZ); + + return 0; +} + +#else + +#define ds2760_battery_suspend NULL +#define ds2760_battery_resume NULL + +#endif /* CONFIG_PM */ + +MODULE_ALIAS("platform:ds2760-battery"); + +static struct platform_driver ds2760_battery_driver = { + .driver = { + .name = "ds2760-battery", + }, + .probe = ds2760_battery_probe, + .remove = ds2760_battery_remove, + .suspend = ds2760_battery_suspend, + .resume = ds2760_battery_resume, +}; + +module_platform_driver(ds2760_battery_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Szabolcs Gyurko , " + "Matt Reimer , " + "Anton Vorontsov "); +MODULE_DESCRIPTION("ds2760 battery driver"); diff --git a/drivers/power/supply/ds2780_battery.c b/drivers/power/supply/ds2780_battery.c new file mode 100644 index 000000000000..1b3b6fa89c28 --- /dev/null +++ b/drivers/power/supply/ds2780_battery.c @@ -0,0 +1,838 @@ +/* + * 1-wire client/driver for the Maxim/Dallas DS2780 Stand-Alone Fuel Gauge IC + * + * Copyright (C) 2010 Indesign, LLC + * + * Author: Clifton Barnes + * + * Based on ds2760_battery and ds2782_battery drivers + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "../../w1/w1.h" +#include "../../w1/slaves/w1_ds2780.h" + +/* Current unit measurement in uA for a 1 milli-ohm sense resistor */ +#define DS2780_CURRENT_UNITS 1563 +/* Charge unit measurement in uAh for a 1 milli-ohm sense resistor */ +#define DS2780_CHARGE_UNITS 6250 +/* Number of bytes in user EEPROM space */ +#define DS2780_USER_EEPROM_SIZE (DS2780_EEPROM_BLOCK0_END - \ + DS2780_EEPROM_BLOCK0_START + 1) +/* Number of bytes in parameter EEPROM space */ +#define DS2780_PARAM_EEPROM_SIZE (DS2780_EEPROM_BLOCK1_END - \ + DS2780_EEPROM_BLOCK1_START + 1) + +struct ds2780_device_info { + struct device *dev; + struct power_supply *bat; + struct power_supply_desc bat_desc; + struct device *w1_dev; +}; + +enum current_types { + CURRENT_NOW, + CURRENT_AVG, +}; + +static const char model[] = "DS2780"; +static const char manufacturer[] = "Maxim/Dallas"; + +static inline struct ds2780_device_info * +to_ds2780_device_info(struct power_supply *psy) +{ + return power_supply_get_drvdata(psy); +} + +static inline struct power_supply *to_power_supply(struct device *dev) +{ + return dev_get_drvdata(dev); +} + +static inline int ds2780_battery_io(struct ds2780_device_info *dev_info, + char *buf, int addr, size_t count, int io) +{ + return w1_ds2780_io(dev_info->w1_dev, buf, addr, count, io); +} + +static inline int ds2780_read8(struct ds2780_device_info *dev_info, u8 *val, + int addr) +{ + return ds2780_battery_io(dev_info, val, addr, sizeof(u8), 0); +} + +static int ds2780_read16(struct ds2780_device_info *dev_info, s16 *val, + int addr) +{ + int ret; + u8 raw[2]; + + ret = ds2780_battery_io(dev_info, raw, addr, sizeof(raw), 0); + if (ret < 0) + return ret; + + *val = (raw[0] << 8) | raw[1]; + + return 0; +} + +static inline int ds2780_read_block(struct ds2780_device_info *dev_info, + u8 *val, int addr, size_t count) +{ + return ds2780_battery_io(dev_info, val, addr, count, 0); +} + +static inline int ds2780_write(struct ds2780_device_info *dev_info, u8 *val, + int addr, size_t count) +{ + return ds2780_battery_io(dev_info, val, addr, count, 1); +} + +static inline int ds2780_store_eeprom(struct device *dev, int addr) +{ + return w1_ds2780_eeprom_cmd(dev, addr, W1_DS2780_COPY_DATA); +} + +static inline int ds2780_recall_eeprom(struct device *dev, int addr) +{ + return w1_ds2780_eeprom_cmd(dev, addr, W1_DS2780_RECALL_DATA); +} + +static int ds2780_save_eeprom(struct ds2780_device_info *dev_info, int reg) +{ + int ret; + + ret = ds2780_store_eeprom(dev_info->w1_dev, reg); + if (ret < 0) + return ret; + + ret = ds2780_recall_eeprom(dev_info->w1_dev, reg); + if (ret < 0) + return ret; + + return 0; +} + +/* Set sense resistor value in mhos */ +static int ds2780_set_sense_register(struct ds2780_device_info *dev_info, + u8 conductance) +{ + int ret; + + ret = ds2780_write(dev_info, &conductance, + DS2780_RSNSP_REG, sizeof(u8)); + if (ret < 0) + return ret; + + return ds2780_save_eeprom(dev_info, DS2780_RSNSP_REG); +} + +/* Get RSGAIN value from 0 to 1.999 in steps of 0.001 */ +static int ds2780_get_rsgain_register(struct ds2780_device_info *dev_info, + u16 *rsgain) +{ + return ds2780_read16(dev_info, rsgain, DS2780_RSGAIN_MSB_REG); +} + +/* Set RSGAIN value from 0 to 1.999 in steps of 0.001 */ +static int ds2780_set_rsgain_register(struct ds2780_device_info *dev_info, + u16 rsgain) +{ + int ret; + u8 raw[] = {rsgain >> 8, rsgain & 0xFF}; + + ret = ds2780_write(dev_info, raw, + DS2780_RSGAIN_MSB_REG, sizeof(raw)); + if (ret < 0) + return ret; + + return ds2780_save_eeprom(dev_info, DS2780_RSGAIN_MSB_REG); +} + +static int ds2780_get_voltage(struct ds2780_device_info *dev_info, + int *voltage_uV) +{ + int ret; + s16 voltage_raw; + + /* + * The voltage value is located in 10 bits across the voltage MSB + * and LSB registers in two's compliment form + * Sign bit of the voltage value is in bit 7 of the voltage MSB register + * Bits 9 - 3 of the voltage value are in bits 6 - 0 of the + * voltage MSB register + * Bits 2 - 0 of the voltage value are in bits 7 - 5 of the + * voltage LSB register + */ + ret = ds2780_read16(dev_info, &voltage_raw, + DS2780_VOLT_MSB_REG); + if (ret < 0) + return ret; + + /* + * DS2780 reports voltage in units of 4.88mV, but the battery class + * reports in units of uV, so convert by multiplying by 4880. + */ + *voltage_uV = (voltage_raw / 32) * 4880; + return 0; +} + +static int ds2780_get_temperature(struct ds2780_device_info *dev_info, + int *temperature) +{ + int ret; + s16 temperature_raw; + + /* + * The temperature value is located in 10 bits across the temperature + * MSB and LSB registers in two's compliment form + * Sign bit of the temperature value is in bit 7 of the temperature + * MSB register + * Bits 9 - 3 of the temperature value are in bits 6 - 0 of the + * temperature MSB register + * Bits 2 - 0 of the temperature value are in bits 7 - 5 of the + * temperature LSB register + */ + ret = ds2780_read16(dev_info, &temperature_raw, + DS2780_TEMP_MSB_REG); + if (ret < 0) + return ret; + + /* + * Temperature is measured in units of 0.125 degrees celcius, the + * power_supply class measures temperature in tenths of degrees + * celsius. The temperature value is stored as a 10 bit number, plus + * sign in the upper bits of a 16 bit register. + */ + *temperature = ((temperature_raw / 32) * 125) / 100; + return 0; +} + +static int ds2780_get_current(struct ds2780_device_info *dev_info, + enum current_types type, int *current_uA) +{ + int ret, sense_res; + s16 current_raw; + u8 sense_res_raw, reg_msb; + + /* + * The units of measurement for current are dependent on the value of + * the sense resistor. + */ + ret = ds2780_read8(dev_info, &sense_res_raw, DS2780_RSNSP_REG); + if (ret < 0) + return ret; + + if (sense_res_raw == 0) { + dev_err(dev_info->dev, "sense resistor value is 0\n"); + return -EINVAL; + } + sense_res = 1000 / sense_res_raw; + + if (type == CURRENT_NOW) + reg_msb = DS2780_CURRENT_MSB_REG; + else if (type == CURRENT_AVG) + reg_msb = DS2780_IAVG_MSB_REG; + else + return -EINVAL; + + /* + * The current value is located in 16 bits across the current MSB + * and LSB registers in two's compliment form + * Sign bit of the current value is in bit 7 of the current MSB register + * Bits 14 - 8 of the current value are in bits 6 - 0 of the current + * MSB register + * Bits 7 - 0 of the current value are in bits 7 - 0 of the current + * LSB register + */ + ret = ds2780_read16(dev_info, ¤t_raw, reg_msb); + if (ret < 0) + return ret; + + *current_uA = current_raw * (DS2780_CURRENT_UNITS / sense_res); + return 0; +} + +static int ds2780_get_accumulated_current(struct ds2780_device_info *dev_info, + int *accumulated_current) +{ + int ret, sense_res; + s16 current_raw; + u8 sense_res_raw; + + /* + * The units of measurement for accumulated current are dependent on + * the value of the sense resistor. + */ + ret = ds2780_read8(dev_info, &sense_res_raw, DS2780_RSNSP_REG); + if (ret < 0) + return ret; + + if (sense_res_raw == 0) { + dev_err(dev_info->dev, "sense resistor value is 0\n"); + return -ENXIO; + } + sense_res = 1000 / sense_res_raw; + + /* + * The ACR value is located in 16 bits across the ACR MSB and + * LSB registers + * Bits 15 - 8 of the ACR value are in bits 7 - 0 of the ACR + * MSB register + * Bits 7 - 0 of the ACR value are in bits 7 - 0 of the ACR + * LSB register + */ + ret = ds2780_read16(dev_info, ¤t_raw, DS2780_ACR_MSB_REG); + if (ret < 0) + return ret; + + *accumulated_current = current_raw * (DS2780_CHARGE_UNITS / sense_res); + return 0; +} + +static int ds2780_get_capacity(struct ds2780_device_info *dev_info, + int *capacity) +{ + int ret; + u8 raw; + + ret = ds2780_read8(dev_info, &raw, DS2780_RARC_REG); + if (ret < 0) + return ret; + + *capacity = raw; + return raw; +} + +static int ds2780_get_status(struct ds2780_device_info *dev_info, int *status) +{ + int ret, current_uA, capacity; + + ret = ds2780_get_current(dev_info, CURRENT_NOW, ¤t_uA); + if (ret < 0) + return ret; + + ret = ds2780_get_capacity(dev_info, &capacity); + if (ret < 0) + return ret; + + if (capacity == 100) + *status = POWER_SUPPLY_STATUS_FULL; + else if (current_uA == 0) + *status = POWER_SUPPLY_STATUS_NOT_CHARGING; + else if (current_uA < 0) + *status = POWER_SUPPLY_STATUS_DISCHARGING; + else + *status = POWER_SUPPLY_STATUS_CHARGING; + + return 0; +} + +static int ds2780_get_charge_now(struct ds2780_device_info *dev_info, + int *charge_now) +{ + int ret; + u16 charge_raw; + + /* + * The RAAC value is located in 16 bits across the RAAC MSB and + * LSB registers + * Bits 15 - 8 of the RAAC value are in bits 7 - 0 of the RAAC + * MSB register + * Bits 7 - 0 of the RAAC value are in bits 7 - 0 of the RAAC + * LSB register + */ + ret = ds2780_read16(dev_info, &charge_raw, DS2780_RAAC_MSB_REG); + if (ret < 0) + return ret; + + *charge_now = charge_raw * 1600; + return 0; +} + +static int ds2780_get_control_register(struct ds2780_device_info *dev_info, + u8 *control_reg) +{ + return ds2780_read8(dev_info, control_reg, DS2780_CONTROL_REG); +} + +static int ds2780_set_control_register(struct ds2780_device_info *dev_info, + u8 control_reg) +{ + int ret; + + ret = ds2780_write(dev_info, &control_reg, + DS2780_CONTROL_REG, sizeof(u8)); + if (ret < 0) + return ret; + + return ds2780_save_eeprom(dev_info, DS2780_CONTROL_REG); +} + +static int ds2780_battery_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + int ret = 0; + struct ds2780_device_info *dev_info = to_ds2780_device_info(psy); + + switch (psp) { + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + ret = ds2780_get_voltage(dev_info, &val->intval); + break; + + case POWER_SUPPLY_PROP_TEMP: + ret = ds2780_get_temperature(dev_info, &val->intval); + break; + + case POWER_SUPPLY_PROP_MODEL_NAME: + val->strval = model; + break; + + case POWER_SUPPLY_PROP_MANUFACTURER: + val->strval = manufacturer; + break; + + case POWER_SUPPLY_PROP_CURRENT_NOW: + ret = ds2780_get_current(dev_info, CURRENT_NOW, &val->intval); + break; + + case POWER_SUPPLY_PROP_CURRENT_AVG: + ret = ds2780_get_current(dev_info, CURRENT_AVG, &val->intval); + break; + + case POWER_SUPPLY_PROP_STATUS: + ret = ds2780_get_status(dev_info, &val->intval); + break; + + case POWER_SUPPLY_PROP_CAPACITY: + ret = ds2780_get_capacity(dev_info, &val->intval); + break; + + case POWER_SUPPLY_PROP_CHARGE_COUNTER: + ret = ds2780_get_accumulated_current(dev_info, &val->intval); + break; + + case POWER_SUPPLY_PROP_CHARGE_NOW: + ret = ds2780_get_charge_now(dev_info, &val->intval); + break; + + default: + ret = -EINVAL; + } + + return ret; +} + +static enum power_supply_property ds2780_battery_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_TEMP, + POWER_SUPPLY_PROP_MODEL_NAME, + POWER_SUPPLY_PROP_MANUFACTURER, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_CURRENT_AVG, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_CHARGE_COUNTER, + POWER_SUPPLY_PROP_CHARGE_NOW, +}; + +static ssize_t ds2780_get_pmod_enabled(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int ret; + u8 control_reg; + struct power_supply *psy = to_power_supply(dev); + struct ds2780_device_info *dev_info = to_ds2780_device_info(psy); + + /* Get power mode */ + ret = ds2780_get_control_register(dev_info, &control_reg); + if (ret < 0) + return ret; + + return sprintf(buf, "%d\n", + !!(control_reg & DS2780_CONTROL_REG_PMOD)); +} + +static ssize_t ds2780_set_pmod_enabled(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t count) +{ + int ret; + u8 control_reg, new_setting; + struct power_supply *psy = to_power_supply(dev); + struct ds2780_device_info *dev_info = to_ds2780_device_info(psy); + + /* Set power mode */ + ret = ds2780_get_control_register(dev_info, &control_reg); + if (ret < 0) + return ret; + + ret = kstrtou8(buf, 0, &new_setting); + if (ret < 0) + return ret; + + if ((new_setting != 0) && (new_setting != 1)) { + dev_err(dev_info->dev, "Invalid pmod setting (0 or 1)\n"); + return -EINVAL; + } + + if (new_setting) + control_reg |= DS2780_CONTROL_REG_PMOD; + else + control_reg &= ~DS2780_CONTROL_REG_PMOD; + + ret = ds2780_set_control_register(dev_info, control_reg); + if (ret < 0) + return ret; + + return count; +} + +static ssize_t ds2780_get_sense_resistor_value(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int ret; + u8 sense_resistor; + struct power_supply *psy = to_power_supply(dev); + struct ds2780_device_info *dev_info = to_ds2780_device_info(psy); + + ret = ds2780_read8(dev_info, &sense_resistor, DS2780_RSNSP_REG); + if (ret < 0) + return ret; + + ret = sprintf(buf, "%d\n", sense_resistor); + return ret; +} + +static ssize_t ds2780_set_sense_resistor_value(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t count) +{ + int ret; + u8 new_setting; + struct power_supply *psy = to_power_supply(dev); + struct ds2780_device_info *dev_info = to_ds2780_device_info(psy); + + ret = kstrtou8(buf, 0, &new_setting); + if (ret < 0) + return ret; + + ret = ds2780_set_sense_register(dev_info, new_setting); + if (ret < 0) + return ret; + + return count; +} + +static ssize_t ds2780_get_rsgain_setting(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int ret; + u16 rsgain; + struct power_supply *psy = to_power_supply(dev); + struct ds2780_device_info *dev_info = to_ds2780_device_info(psy); + + ret = ds2780_get_rsgain_register(dev_info, &rsgain); + if (ret < 0) + return ret; + + return sprintf(buf, "%d\n", rsgain); +} + +static ssize_t ds2780_set_rsgain_setting(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t count) +{ + int ret; + u16 new_setting; + struct power_supply *psy = to_power_supply(dev); + struct ds2780_device_info *dev_info = to_ds2780_device_info(psy); + + ret = kstrtou16(buf, 0, &new_setting); + if (ret < 0) + return ret; + + /* Gain can only be from 0 to 1.999 in steps of .001 */ + if (new_setting > 1999) { + dev_err(dev_info->dev, "Invalid rsgain setting (0 - 1999)\n"); + return -EINVAL; + } + + ret = ds2780_set_rsgain_register(dev_info, new_setting); + if (ret < 0) + return ret; + + return count; +} + +static ssize_t ds2780_get_pio_pin(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int ret; + u8 sfr; + struct power_supply *psy = to_power_supply(dev); + struct ds2780_device_info *dev_info = to_ds2780_device_info(psy); + + ret = ds2780_read8(dev_info, &sfr, DS2780_SFR_REG); + if (ret < 0) + return ret; + + ret = sprintf(buf, "%d\n", sfr & DS2780_SFR_REG_PIOSC); + return ret; +} + +static ssize_t ds2780_set_pio_pin(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t count) +{ + int ret; + u8 new_setting; + struct power_supply *psy = to_power_supply(dev); + struct ds2780_device_info *dev_info = to_ds2780_device_info(psy); + + ret = kstrtou8(buf, 0, &new_setting); + if (ret < 0) + return ret; + + if ((new_setting != 0) && (new_setting != 1)) { + dev_err(dev_info->dev, "Invalid pio_pin setting (0 or 1)\n"); + return -EINVAL; + } + + ret = ds2780_write(dev_info, &new_setting, + DS2780_SFR_REG, sizeof(u8)); + if (ret < 0) + return ret; + + return count; +} + +static ssize_t ds2780_read_param_eeprom_bin(struct file *filp, + struct kobject *kobj, + struct bin_attribute *bin_attr, + char *buf, loff_t off, size_t count) +{ + struct device *dev = container_of(kobj, struct device, kobj); + struct power_supply *psy = to_power_supply(dev); + struct ds2780_device_info *dev_info = to_ds2780_device_info(psy); + + return ds2780_read_block(dev_info, buf, + DS2780_EEPROM_BLOCK1_START + off, count); +} + +static ssize_t ds2780_write_param_eeprom_bin(struct file *filp, + struct kobject *kobj, + struct bin_attribute *bin_attr, + char *buf, loff_t off, size_t count) +{ + struct device *dev = container_of(kobj, struct device, kobj); + struct power_supply *psy = to_power_supply(dev); + struct ds2780_device_info *dev_info = to_ds2780_device_info(psy); + int ret; + + ret = ds2780_write(dev_info, buf, + DS2780_EEPROM_BLOCK1_START + off, count); + if (ret < 0) + return ret; + + ret = ds2780_save_eeprom(dev_info, DS2780_EEPROM_BLOCK1_START); + if (ret < 0) + return ret; + + return count; +} + +static struct bin_attribute ds2780_param_eeprom_bin_attr = { + .attr = { + .name = "param_eeprom", + .mode = S_IRUGO | S_IWUSR, + }, + .size = DS2780_PARAM_EEPROM_SIZE, + .read = ds2780_read_param_eeprom_bin, + .write = ds2780_write_param_eeprom_bin, +}; + +static ssize_t ds2780_read_user_eeprom_bin(struct file *filp, + struct kobject *kobj, + struct bin_attribute *bin_attr, + char *buf, loff_t off, size_t count) +{ + struct device *dev = container_of(kobj, struct device, kobj); + struct power_supply *psy = to_power_supply(dev); + struct ds2780_device_info *dev_info = to_ds2780_device_info(psy); + + return ds2780_read_block(dev_info, buf, + DS2780_EEPROM_BLOCK0_START + off, count); +} + +static ssize_t ds2780_write_user_eeprom_bin(struct file *filp, + struct kobject *kobj, + struct bin_attribute *bin_attr, + char *buf, loff_t off, size_t count) +{ + struct device *dev = container_of(kobj, struct device, kobj); + struct power_supply *psy = to_power_supply(dev); + struct ds2780_device_info *dev_info = to_ds2780_device_info(psy); + int ret; + + ret = ds2780_write(dev_info, buf, + DS2780_EEPROM_BLOCK0_START + off, count); + if (ret < 0) + return ret; + + ret = ds2780_save_eeprom(dev_info, DS2780_EEPROM_BLOCK0_START); + if (ret < 0) + return ret; + + return count; +} + +static struct bin_attribute ds2780_user_eeprom_bin_attr = { + .attr = { + .name = "user_eeprom", + .mode = S_IRUGO | S_IWUSR, + }, + .size = DS2780_USER_EEPROM_SIZE, + .read = ds2780_read_user_eeprom_bin, + .write = ds2780_write_user_eeprom_bin, +}; + +static DEVICE_ATTR(pmod_enabled, S_IRUGO | S_IWUSR, ds2780_get_pmod_enabled, + ds2780_set_pmod_enabled); +static DEVICE_ATTR(sense_resistor_value, S_IRUGO | S_IWUSR, + ds2780_get_sense_resistor_value, ds2780_set_sense_resistor_value); +static DEVICE_ATTR(rsgain_setting, S_IRUGO | S_IWUSR, ds2780_get_rsgain_setting, + ds2780_set_rsgain_setting); +static DEVICE_ATTR(pio_pin, S_IRUGO | S_IWUSR, ds2780_get_pio_pin, + ds2780_set_pio_pin); + + +static struct attribute *ds2780_attributes[] = { + &dev_attr_pmod_enabled.attr, + &dev_attr_sense_resistor_value.attr, + &dev_attr_rsgain_setting.attr, + &dev_attr_pio_pin.attr, + NULL +}; + +static const struct attribute_group ds2780_attr_group = { + .attrs = ds2780_attributes, +}; + +static int ds2780_battery_probe(struct platform_device *pdev) +{ + struct power_supply_config psy_cfg = {}; + int ret = 0; + struct ds2780_device_info *dev_info; + + dev_info = devm_kzalloc(&pdev->dev, sizeof(*dev_info), GFP_KERNEL); + if (!dev_info) { + ret = -ENOMEM; + goto fail; + } + + platform_set_drvdata(pdev, dev_info); + + dev_info->dev = &pdev->dev; + dev_info->w1_dev = pdev->dev.parent; + dev_info->bat_desc.name = dev_name(&pdev->dev); + dev_info->bat_desc.type = POWER_SUPPLY_TYPE_BATTERY; + dev_info->bat_desc.properties = ds2780_battery_props; + dev_info->bat_desc.num_properties = ARRAY_SIZE(ds2780_battery_props); + dev_info->bat_desc.get_property = ds2780_battery_get_property; + + psy_cfg.drv_data = dev_info; + + dev_info->bat = power_supply_register(&pdev->dev, &dev_info->bat_desc, + &psy_cfg); + if (IS_ERR(dev_info->bat)) { + dev_err(dev_info->dev, "failed to register battery\n"); + ret = PTR_ERR(dev_info->bat); + goto fail; + } + + ret = sysfs_create_group(&dev_info->bat->dev.kobj, &ds2780_attr_group); + if (ret) { + dev_err(dev_info->dev, "failed to create sysfs group\n"); + goto fail_unregister; + } + + ret = sysfs_create_bin_file(&dev_info->bat->dev.kobj, + &ds2780_param_eeprom_bin_attr); + if (ret) { + dev_err(dev_info->dev, + "failed to create param eeprom bin file"); + goto fail_remove_group; + } + + ret = sysfs_create_bin_file(&dev_info->bat->dev.kobj, + &ds2780_user_eeprom_bin_attr); + if (ret) { + dev_err(dev_info->dev, + "failed to create user eeprom bin file"); + goto fail_remove_bin_file; + } + + return 0; + +fail_remove_bin_file: + sysfs_remove_bin_file(&dev_info->bat->dev.kobj, + &ds2780_param_eeprom_bin_attr); +fail_remove_group: + sysfs_remove_group(&dev_info->bat->dev.kobj, &ds2780_attr_group); +fail_unregister: + power_supply_unregister(dev_info->bat); +fail: + return ret; +} + +static int ds2780_battery_remove(struct platform_device *pdev) +{ + struct ds2780_device_info *dev_info = platform_get_drvdata(pdev); + + /* + * Remove attributes before unregistering power supply + * because 'bat' will be freed on power_supply_unregister() call. + */ + sysfs_remove_group(&dev_info->bat->dev.kobj, &ds2780_attr_group); + + power_supply_unregister(dev_info->bat); + + return 0; +} + +static struct platform_driver ds2780_battery_driver = { + .driver = { + .name = "ds2780-battery", + }, + .probe = ds2780_battery_probe, + .remove = ds2780_battery_remove, +}; + +module_platform_driver(ds2780_battery_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Clifton Barnes "); +MODULE_DESCRIPTION("Maxim/Dallas DS2780 Stand-Alone Fuel Gauage IC driver"); +MODULE_ALIAS("platform:ds2780-battery"); diff --git a/drivers/power/supply/ds2781_battery.c b/drivers/power/supply/ds2781_battery.c new file mode 100644 index 000000000000..cc0149131f89 --- /dev/null +++ b/drivers/power/supply/ds2781_battery.c @@ -0,0 +1,839 @@ +/* + * 1-wire client/driver for the Maxim/Dallas DS2781 Stand-Alone Fuel Gauge IC + * + * Author: Renata Sayakhova + * + * Based on ds2780_battery drivers + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "../../w1/w1.h" +#include "../../w1/slaves/w1_ds2781.h" + +/* Current unit measurement in uA for a 1 milli-ohm sense resistor */ +#define DS2781_CURRENT_UNITS 1563 +/* Charge unit measurement in uAh for a 1 milli-ohm sense resistor */ +#define DS2781_CHARGE_UNITS 6250 +/* Number of bytes in user EEPROM space */ +#define DS2781_USER_EEPROM_SIZE (DS2781_EEPROM_BLOCK0_END - \ + DS2781_EEPROM_BLOCK0_START + 1) +/* Number of bytes in parameter EEPROM space */ +#define DS2781_PARAM_EEPROM_SIZE (DS2781_EEPROM_BLOCK1_END - \ + DS2781_EEPROM_BLOCK1_START + 1) + +struct ds2781_device_info { + struct device *dev; + struct power_supply *bat; + struct power_supply_desc bat_desc; + struct device *w1_dev; +}; + +enum current_types { + CURRENT_NOW, + CURRENT_AVG, +}; + +static const char model[] = "DS2781"; +static const char manufacturer[] = "Maxim/Dallas"; + +static inline struct ds2781_device_info * +to_ds2781_device_info(struct power_supply *psy) +{ + return power_supply_get_drvdata(psy); +} + +static inline struct power_supply *to_power_supply(struct device *dev) +{ + return dev_get_drvdata(dev); +} + +static inline int ds2781_battery_io(struct ds2781_device_info *dev_info, + char *buf, int addr, size_t count, int io) +{ + return w1_ds2781_io(dev_info->w1_dev, buf, addr, count, io); +} + +static int w1_ds2781_read(struct ds2781_device_info *dev_info, char *buf, + int addr, size_t count) +{ + return ds2781_battery_io(dev_info, buf, addr, count, 0); +} + +static inline int ds2781_read8(struct ds2781_device_info *dev_info, u8 *val, + int addr) +{ + return ds2781_battery_io(dev_info, val, addr, sizeof(u8), 0); +} + +static int ds2781_read16(struct ds2781_device_info *dev_info, s16 *val, + int addr) +{ + int ret; + u8 raw[2]; + + ret = ds2781_battery_io(dev_info, raw, addr, sizeof(raw), 0); + if (ret < 0) + return ret; + + *val = (raw[0] << 8) | raw[1]; + + return 0; +} + +static inline int ds2781_read_block(struct ds2781_device_info *dev_info, + u8 *val, int addr, size_t count) +{ + return ds2781_battery_io(dev_info, val, addr, count, 0); +} + +static inline int ds2781_write(struct ds2781_device_info *dev_info, u8 *val, + int addr, size_t count) +{ + return ds2781_battery_io(dev_info, val, addr, count, 1); +} + +static inline int ds2781_store_eeprom(struct device *dev, int addr) +{ + return w1_ds2781_eeprom_cmd(dev, addr, W1_DS2781_COPY_DATA); +} + +static inline int ds2781_recall_eeprom(struct device *dev, int addr) +{ + return w1_ds2781_eeprom_cmd(dev, addr, W1_DS2781_RECALL_DATA); +} + +static int ds2781_save_eeprom(struct ds2781_device_info *dev_info, int reg) +{ + int ret; + + ret = ds2781_store_eeprom(dev_info->w1_dev, reg); + if (ret < 0) + return ret; + + ret = ds2781_recall_eeprom(dev_info->w1_dev, reg); + if (ret < 0) + return ret; + + return 0; +} + +/* Set sense resistor value in mhos */ +static int ds2781_set_sense_register(struct ds2781_device_info *dev_info, + u8 conductance) +{ + int ret; + + ret = ds2781_write(dev_info, &conductance, + DS2781_RSNSP, sizeof(u8)); + if (ret < 0) + return ret; + + return ds2781_save_eeprom(dev_info, DS2781_RSNSP); +} + +/* Get RSGAIN value from 0 to 1.999 in steps of 0.001 */ +static int ds2781_get_rsgain_register(struct ds2781_device_info *dev_info, + u16 *rsgain) +{ + return ds2781_read16(dev_info, rsgain, DS2781_RSGAIN_MSB); +} + +/* Set RSGAIN value from 0 to 1.999 in steps of 0.001 */ +static int ds2781_set_rsgain_register(struct ds2781_device_info *dev_info, + u16 rsgain) +{ + int ret; + u8 raw[] = {rsgain >> 8, rsgain & 0xFF}; + + ret = ds2781_write(dev_info, raw, + DS2781_RSGAIN_MSB, sizeof(raw)); + if (ret < 0) + return ret; + + return ds2781_save_eeprom(dev_info, DS2781_RSGAIN_MSB); +} + +static int ds2781_get_voltage(struct ds2781_device_info *dev_info, + int *voltage_uV) +{ + int ret; + char val[2]; + int voltage_raw; + + ret = w1_ds2781_read(dev_info, val, DS2781_VOLT_MSB, 2 * sizeof(u8)); + if (ret < 0) + return ret; + /* + * The voltage value is located in 10 bits across the voltage MSB + * and LSB registers in two's compliment form + * Sign bit of the voltage value is in bit 7 of the voltage MSB register + * Bits 9 - 3 of the voltage value are in bits 6 - 0 of the + * voltage MSB register + * Bits 2 - 0 of the voltage value are in bits 7 - 5 of the + * voltage LSB register + */ + voltage_raw = (val[0] << 3) | + (val[1] >> 5); + + /* DS2781 reports voltage in units of 9.76mV, but the battery class + * reports in units of uV, so convert by multiplying by 9760. */ + *voltage_uV = voltage_raw * 9760; + + return 0; +} + +static int ds2781_get_temperature(struct ds2781_device_info *dev_info, + int *temp) +{ + int ret; + char val[2]; + int temp_raw; + + ret = w1_ds2781_read(dev_info, val, DS2781_TEMP_MSB, 2 * sizeof(u8)); + if (ret < 0) + return ret; + /* + * The temperature value is located in 10 bits across the temperature + * MSB and LSB registers in two's compliment form + * Sign bit of the temperature value is in bit 7 of the temperature + * MSB register + * Bits 9 - 3 of the temperature value are in bits 6 - 0 of the + * temperature MSB register + * Bits 2 - 0 of the temperature value are in bits 7 - 5 of the + * temperature LSB register + */ + temp_raw = ((val[0]) << 3) | + (val[1] >> 5); + *temp = temp_raw + (temp_raw / 4); + + return 0; +} + +static int ds2781_get_current(struct ds2781_device_info *dev_info, + enum current_types type, int *current_uA) +{ + int ret, sense_res; + s16 current_raw; + u8 sense_res_raw, reg_msb; + + /* + * The units of measurement for current are dependent on the value of + * the sense resistor. + */ + ret = ds2781_read8(dev_info, &sense_res_raw, DS2781_RSNSP); + if (ret < 0) + return ret; + + if (sense_res_raw == 0) { + dev_err(dev_info->dev, "sense resistor value is 0\n"); + return -EINVAL; + } + sense_res = 1000 / sense_res_raw; + + if (type == CURRENT_NOW) + reg_msb = DS2781_CURRENT_MSB; + else if (type == CURRENT_AVG) + reg_msb = DS2781_IAVG_MSB; + else + return -EINVAL; + + /* + * The current value is located in 16 bits across the current MSB + * and LSB registers in two's compliment form + * Sign bit of the current value is in bit 7 of the current MSB register + * Bits 14 - 8 of the current value are in bits 6 - 0 of the current + * MSB register + * Bits 7 - 0 of the current value are in bits 7 - 0 of the current + * LSB register + */ + ret = ds2781_read16(dev_info, ¤t_raw, reg_msb); + if (ret < 0) + return ret; + + *current_uA = current_raw * (DS2781_CURRENT_UNITS / sense_res); + return 0; +} + +static int ds2781_get_accumulated_current(struct ds2781_device_info *dev_info, + int *accumulated_current) +{ + int ret, sense_res; + s16 current_raw; + u8 sense_res_raw; + + /* + * The units of measurement for accumulated current are dependent on + * the value of the sense resistor. + */ + ret = ds2781_read8(dev_info, &sense_res_raw, DS2781_RSNSP); + if (ret < 0) + return ret; + + if (sense_res_raw == 0) { + dev_err(dev_info->dev, "sense resistor value is 0\n"); + return -EINVAL; + } + sense_res = 1000 / sense_res_raw; + + /* + * The ACR value is located in 16 bits across the ACR MSB and + * LSB registers + * Bits 15 - 8 of the ACR value are in bits 7 - 0 of the ACR + * MSB register + * Bits 7 - 0 of the ACR value are in bits 7 - 0 of the ACR + * LSB register + */ + ret = ds2781_read16(dev_info, ¤t_raw, DS2781_ACR_MSB); + if (ret < 0) + return ret; + + *accumulated_current = current_raw * (DS2781_CHARGE_UNITS / sense_res); + return 0; +} + +static int ds2781_get_capacity(struct ds2781_device_info *dev_info, + int *capacity) +{ + int ret; + u8 raw; + + ret = ds2781_read8(dev_info, &raw, DS2781_RARC); + if (ret < 0) + return ret; + + *capacity = raw; + return 0; +} + +static int ds2781_get_status(struct ds2781_device_info *dev_info, int *status) +{ + int ret, current_uA, capacity; + + ret = ds2781_get_current(dev_info, CURRENT_NOW, ¤t_uA); + if (ret < 0) + return ret; + + ret = ds2781_get_capacity(dev_info, &capacity); + if (ret < 0) + return ret; + + if (power_supply_am_i_supplied(dev_info->bat)) { + if (capacity == 100) + *status = POWER_SUPPLY_STATUS_FULL; + else if (current_uA > 50000) + *status = POWER_SUPPLY_STATUS_CHARGING; + else + *status = POWER_SUPPLY_STATUS_NOT_CHARGING; + } else { + *status = POWER_SUPPLY_STATUS_DISCHARGING; + } + return 0; +} + +static int ds2781_get_charge_now(struct ds2781_device_info *dev_info, + int *charge_now) +{ + int ret; + u16 charge_raw; + + /* + * The RAAC value is located in 16 bits across the RAAC MSB and + * LSB registers + * Bits 15 - 8 of the RAAC value are in bits 7 - 0 of the RAAC + * MSB register + * Bits 7 - 0 of the RAAC value are in bits 7 - 0 of the RAAC + * LSB register + */ + ret = ds2781_read16(dev_info, &charge_raw, DS2781_RAAC_MSB); + if (ret < 0) + return ret; + + *charge_now = charge_raw * 1600; + return 0; +} + +static int ds2781_get_control_register(struct ds2781_device_info *dev_info, + u8 *control_reg) +{ + return ds2781_read8(dev_info, control_reg, DS2781_CONTROL); +} + +static int ds2781_set_control_register(struct ds2781_device_info *dev_info, + u8 control_reg) +{ + int ret; + + ret = ds2781_write(dev_info, &control_reg, + DS2781_CONTROL, sizeof(u8)); + if (ret < 0) + return ret; + + return ds2781_save_eeprom(dev_info, DS2781_CONTROL); +} + +static int ds2781_battery_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + int ret = 0; + struct ds2781_device_info *dev_info = to_ds2781_device_info(psy); + + switch (psp) { + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + ret = ds2781_get_voltage(dev_info, &val->intval); + break; + + case POWER_SUPPLY_PROP_TEMP: + ret = ds2781_get_temperature(dev_info, &val->intval); + break; + + case POWER_SUPPLY_PROP_MODEL_NAME: + val->strval = model; + break; + + case POWER_SUPPLY_PROP_MANUFACTURER: + val->strval = manufacturer; + break; + + case POWER_SUPPLY_PROP_CURRENT_NOW: + ret = ds2781_get_current(dev_info, CURRENT_NOW, &val->intval); + break; + + case POWER_SUPPLY_PROP_CURRENT_AVG: + ret = ds2781_get_current(dev_info, CURRENT_AVG, &val->intval); + break; + + case POWER_SUPPLY_PROP_STATUS: + ret = ds2781_get_status(dev_info, &val->intval); + break; + + case POWER_SUPPLY_PROP_CAPACITY: + ret = ds2781_get_capacity(dev_info, &val->intval); + break; + + case POWER_SUPPLY_PROP_CHARGE_COUNTER: + ret = ds2781_get_accumulated_current(dev_info, &val->intval); + break; + + case POWER_SUPPLY_PROP_CHARGE_NOW: + ret = ds2781_get_charge_now(dev_info, &val->intval); + break; + + default: + ret = -EINVAL; + } + + return ret; +} + +static enum power_supply_property ds2781_battery_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_TEMP, + POWER_SUPPLY_PROP_MODEL_NAME, + POWER_SUPPLY_PROP_MANUFACTURER, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_CURRENT_AVG, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_CHARGE_COUNTER, + POWER_SUPPLY_PROP_CHARGE_NOW, +}; + +static ssize_t ds2781_get_pmod_enabled(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int ret; + u8 control_reg; + struct power_supply *psy = to_power_supply(dev); + struct ds2781_device_info *dev_info = to_ds2781_device_info(psy); + + /* Get power mode */ + ret = ds2781_get_control_register(dev_info, &control_reg); + if (ret < 0) + return ret; + + return sprintf(buf, "%d\n", + !!(control_reg & DS2781_CONTROL_PMOD)); +} + +static ssize_t ds2781_set_pmod_enabled(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t count) +{ + int ret; + u8 control_reg, new_setting; + struct power_supply *psy = to_power_supply(dev); + struct ds2781_device_info *dev_info = to_ds2781_device_info(psy); + + /* Set power mode */ + ret = ds2781_get_control_register(dev_info, &control_reg); + if (ret < 0) + return ret; + + ret = kstrtou8(buf, 0, &new_setting); + if (ret < 0) + return ret; + + if ((new_setting != 0) && (new_setting != 1)) { + dev_err(dev_info->dev, "Invalid pmod setting (0 or 1)\n"); + return -EINVAL; + } + + if (new_setting) + control_reg |= DS2781_CONTROL_PMOD; + else + control_reg &= ~DS2781_CONTROL_PMOD; + + ret = ds2781_set_control_register(dev_info, control_reg); + if (ret < 0) + return ret; + + return count; +} + +static ssize_t ds2781_get_sense_resistor_value(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int ret; + u8 sense_resistor; + struct power_supply *psy = to_power_supply(dev); + struct ds2781_device_info *dev_info = to_ds2781_device_info(psy); + + ret = ds2781_read8(dev_info, &sense_resistor, DS2781_RSNSP); + if (ret < 0) + return ret; + + ret = sprintf(buf, "%d\n", sense_resistor); + return ret; +} + +static ssize_t ds2781_set_sense_resistor_value(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t count) +{ + int ret; + u8 new_setting; + struct power_supply *psy = to_power_supply(dev); + struct ds2781_device_info *dev_info = to_ds2781_device_info(psy); + + ret = kstrtou8(buf, 0, &new_setting); + if (ret < 0) + return ret; + + ret = ds2781_set_sense_register(dev_info, new_setting); + if (ret < 0) + return ret; + + return count; +} + +static ssize_t ds2781_get_rsgain_setting(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int ret; + u16 rsgain; + struct power_supply *psy = to_power_supply(dev); + struct ds2781_device_info *dev_info = to_ds2781_device_info(psy); + + ret = ds2781_get_rsgain_register(dev_info, &rsgain); + if (ret < 0) + return ret; + + return sprintf(buf, "%d\n", rsgain); +} + +static ssize_t ds2781_set_rsgain_setting(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t count) +{ + int ret; + u16 new_setting; + struct power_supply *psy = to_power_supply(dev); + struct ds2781_device_info *dev_info = to_ds2781_device_info(psy); + + ret = kstrtou16(buf, 0, &new_setting); + if (ret < 0) + return ret; + + /* Gain can only be from 0 to 1.999 in steps of .001 */ + if (new_setting > 1999) { + dev_err(dev_info->dev, "Invalid rsgain setting (0 - 1999)\n"); + return -EINVAL; + } + + ret = ds2781_set_rsgain_register(dev_info, new_setting); + if (ret < 0) + return ret; + + return count; +} + +static ssize_t ds2781_get_pio_pin(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int ret; + u8 sfr; + struct power_supply *psy = to_power_supply(dev); + struct ds2781_device_info *dev_info = to_ds2781_device_info(psy); + + ret = ds2781_read8(dev_info, &sfr, DS2781_SFR); + if (ret < 0) + return ret; + + ret = sprintf(buf, "%d\n", sfr & DS2781_SFR_PIOSC); + return ret; +} + +static ssize_t ds2781_set_pio_pin(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t count) +{ + int ret; + u8 new_setting; + struct power_supply *psy = to_power_supply(dev); + struct ds2781_device_info *dev_info = to_ds2781_device_info(psy); + + ret = kstrtou8(buf, 0, &new_setting); + if (ret < 0) + return ret; + + if ((new_setting != 0) && (new_setting != 1)) { + dev_err(dev_info->dev, "Invalid pio_pin setting (0 or 1)\n"); + return -EINVAL; + } + + ret = ds2781_write(dev_info, &new_setting, + DS2781_SFR, sizeof(u8)); + if (ret < 0) + return ret; + + return count; +} + +static ssize_t ds2781_read_param_eeprom_bin(struct file *filp, + struct kobject *kobj, + struct bin_attribute *bin_attr, + char *buf, loff_t off, size_t count) +{ + struct device *dev = container_of(kobj, struct device, kobj); + struct power_supply *psy = to_power_supply(dev); + struct ds2781_device_info *dev_info = to_ds2781_device_info(psy); + + return ds2781_read_block(dev_info, buf, + DS2781_EEPROM_BLOCK1_START + off, count); +} + +static ssize_t ds2781_write_param_eeprom_bin(struct file *filp, + struct kobject *kobj, + struct bin_attribute *bin_attr, + char *buf, loff_t off, size_t count) +{ + struct device *dev = container_of(kobj, struct device, kobj); + struct power_supply *psy = to_power_supply(dev); + struct ds2781_device_info *dev_info = to_ds2781_device_info(psy); + int ret; + + ret = ds2781_write(dev_info, buf, + DS2781_EEPROM_BLOCK1_START + off, count); + if (ret < 0) + return ret; + + ret = ds2781_save_eeprom(dev_info, DS2781_EEPROM_BLOCK1_START); + if (ret < 0) + return ret; + + return count; +} + +static struct bin_attribute ds2781_param_eeprom_bin_attr = { + .attr = { + .name = "param_eeprom", + .mode = S_IRUGO | S_IWUSR, + }, + .size = DS2781_PARAM_EEPROM_SIZE, + .read = ds2781_read_param_eeprom_bin, + .write = ds2781_write_param_eeprom_bin, +}; + +static ssize_t ds2781_read_user_eeprom_bin(struct file *filp, + struct kobject *kobj, + struct bin_attribute *bin_attr, + char *buf, loff_t off, size_t count) +{ + struct device *dev = container_of(kobj, struct device, kobj); + struct power_supply *psy = to_power_supply(dev); + struct ds2781_device_info *dev_info = to_ds2781_device_info(psy); + + return ds2781_read_block(dev_info, buf, + DS2781_EEPROM_BLOCK0_START + off, count); + +} + +static ssize_t ds2781_write_user_eeprom_bin(struct file *filp, + struct kobject *kobj, + struct bin_attribute *bin_attr, + char *buf, loff_t off, size_t count) +{ + struct device *dev = container_of(kobj, struct device, kobj); + struct power_supply *psy = to_power_supply(dev); + struct ds2781_device_info *dev_info = to_ds2781_device_info(psy); + int ret; + + ret = ds2781_write(dev_info, buf, + DS2781_EEPROM_BLOCK0_START + off, count); + if (ret < 0) + return ret; + + ret = ds2781_save_eeprom(dev_info, DS2781_EEPROM_BLOCK0_START); + if (ret < 0) + return ret; + + return count; +} + +static struct bin_attribute ds2781_user_eeprom_bin_attr = { + .attr = { + .name = "user_eeprom", + .mode = S_IRUGO | S_IWUSR, + }, + .size = DS2781_USER_EEPROM_SIZE, + .read = ds2781_read_user_eeprom_bin, + .write = ds2781_write_user_eeprom_bin, +}; + +static DEVICE_ATTR(pmod_enabled, S_IRUGO | S_IWUSR, ds2781_get_pmod_enabled, + ds2781_set_pmod_enabled); +static DEVICE_ATTR(sense_resistor_value, S_IRUGO | S_IWUSR, + ds2781_get_sense_resistor_value, ds2781_set_sense_resistor_value); +static DEVICE_ATTR(rsgain_setting, S_IRUGO | S_IWUSR, ds2781_get_rsgain_setting, + ds2781_set_rsgain_setting); +static DEVICE_ATTR(pio_pin, S_IRUGO | S_IWUSR, ds2781_get_pio_pin, + ds2781_set_pio_pin); + + +static struct attribute *ds2781_attributes[] = { + &dev_attr_pmod_enabled.attr, + &dev_attr_sense_resistor_value.attr, + &dev_attr_rsgain_setting.attr, + &dev_attr_pio_pin.attr, + NULL +}; + +static const struct attribute_group ds2781_attr_group = { + .attrs = ds2781_attributes, +}; + +static int ds2781_battery_probe(struct platform_device *pdev) +{ + struct power_supply_config psy_cfg = {}; + int ret = 0; + struct ds2781_device_info *dev_info; + + dev_info = devm_kzalloc(&pdev->dev, sizeof(*dev_info), GFP_KERNEL); + if (!dev_info) + return -ENOMEM; + + platform_set_drvdata(pdev, dev_info); + + dev_info->dev = &pdev->dev; + dev_info->w1_dev = pdev->dev.parent; + dev_info->bat_desc.name = dev_name(&pdev->dev); + dev_info->bat_desc.type = POWER_SUPPLY_TYPE_BATTERY; + dev_info->bat_desc.properties = ds2781_battery_props; + dev_info->bat_desc.num_properties = ARRAY_SIZE(ds2781_battery_props); + dev_info->bat_desc.get_property = ds2781_battery_get_property; + + psy_cfg.drv_data = dev_info; + + dev_info->bat = power_supply_register(&pdev->dev, &dev_info->bat_desc, + &psy_cfg); + if (IS_ERR(dev_info->bat)) { + dev_err(dev_info->dev, "failed to register battery\n"); + ret = PTR_ERR(dev_info->bat); + goto fail; + } + + ret = sysfs_create_group(&dev_info->bat->dev.kobj, &ds2781_attr_group); + if (ret) { + dev_err(dev_info->dev, "failed to create sysfs group\n"); + goto fail_unregister; + } + + ret = sysfs_create_bin_file(&dev_info->bat->dev.kobj, + &ds2781_param_eeprom_bin_attr); + if (ret) { + dev_err(dev_info->dev, + "failed to create param eeprom bin file"); + goto fail_remove_group; + } + + ret = sysfs_create_bin_file(&dev_info->bat->dev.kobj, + &ds2781_user_eeprom_bin_attr); + if (ret) { + dev_err(dev_info->dev, + "failed to create user eeprom bin file"); + goto fail_remove_bin_file; + } + + return 0; + +fail_remove_bin_file: + sysfs_remove_bin_file(&dev_info->bat->dev.kobj, + &ds2781_param_eeprom_bin_attr); +fail_remove_group: + sysfs_remove_group(&dev_info->bat->dev.kobj, &ds2781_attr_group); +fail_unregister: + power_supply_unregister(dev_info->bat); +fail: + return ret; +} + +static int ds2781_battery_remove(struct platform_device *pdev) +{ + struct ds2781_device_info *dev_info = platform_get_drvdata(pdev); + + /* + * Remove attributes before unregistering power supply + * because 'bat' will be freed on power_supply_unregister() call. + */ + sysfs_remove_group(&dev_info->bat->dev.kobj, &ds2781_attr_group); + + power_supply_unregister(dev_info->bat); + + return 0; +} + +static struct platform_driver ds2781_battery_driver = { + .driver = { + .name = "ds2781-battery", + }, + .probe = ds2781_battery_probe, + .remove = ds2781_battery_remove, +}; +module_platform_driver(ds2781_battery_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Renata Sayakhova "); +MODULE_DESCRIPTION("Maxim/Dallas DS2781 Stand-Alone Fuel Gauage IC driver"); +MODULE_ALIAS("platform:ds2781-battery"); + diff --git a/drivers/power/supply/ds2782_battery.c b/drivers/power/supply/ds2782_battery.c new file mode 100644 index 000000000000..a1b7e0592245 --- /dev/null +++ b/drivers/power/supply/ds2782_battery.c @@ -0,0 +1,475 @@ +/* + * I2C client/driver for the Maxim/Dallas DS2782 Stand-Alone Fuel Gauge IC + * + * Copyright (C) 2009 Bluewater Systems Ltd + * + * Author: Ryan Mallon + * + * DS2786 added by Yulia Vilensky + * + * UEvent sending added by Evgeny Romanov + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DS2782_REG_RARC 0x06 /* Remaining active relative capacity */ + +#define DS278x_REG_VOLT_MSB 0x0c +#define DS278x_REG_TEMP_MSB 0x0a +#define DS278x_REG_CURRENT_MSB 0x0e + +/* EEPROM Block */ +#define DS2782_REG_RSNSP 0x69 /* Sense resistor value */ + +/* Current unit measurement in uA for a 1 milli-ohm sense resistor */ +#define DS2782_CURRENT_UNITS 1563 + +#define DS2786_REG_RARC 0x02 /* Remaining active relative capacity */ + +#define DS2786_CURRENT_UNITS 25 + +#define DS278x_DELAY 1000 + +struct ds278x_info; + +struct ds278x_battery_ops { + int (*get_battery_current)(struct ds278x_info *info, int *current_uA); + int (*get_battery_voltage)(struct ds278x_info *info, int *voltage_uV); + int (*get_battery_capacity)(struct ds278x_info *info, int *capacity); +}; + +#define to_ds278x_info(x) power_supply_get_drvdata(x) + +struct ds278x_info { + struct i2c_client *client; + struct power_supply *battery; + struct power_supply_desc battery_desc; + const struct ds278x_battery_ops *ops; + struct delayed_work bat_work; + int id; + int rsns; + int capacity; + int status; /* State Of Charge */ +}; + +static DEFINE_IDR(battery_id); +static DEFINE_MUTEX(battery_lock); + +static inline int ds278x_read_reg(struct ds278x_info *info, int reg, u8 *val) +{ + int ret; + + ret = i2c_smbus_read_byte_data(info->client, reg); + if (ret < 0) { + dev_err(&info->client->dev, "register read failed\n"); + return ret; + } + + *val = ret; + return 0; +} + +static inline int ds278x_read_reg16(struct ds278x_info *info, int reg_msb, + s16 *val) +{ + int ret; + + ret = i2c_smbus_read_word_data(info->client, reg_msb); + if (ret < 0) { + dev_err(&info->client->dev, "register read failed\n"); + return ret; + } + + *val = swab16(ret); + return 0; +} + +static int ds278x_get_temp(struct ds278x_info *info, int *temp) +{ + s16 raw; + int err; + + /* + * Temperature is measured in units of 0.125 degrees celcius, the + * power_supply class measures temperature in tenths of degrees + * celsius. The temperature value is stored as a 10 bit number, plus + * sign in the upper bits of a 16 bit register. + */ + err = ds278x_read_reg16(info, DS278x_REG_TEMP_MSB, &raw); + if (err) + return err; + *temp = ((raw / 32) * 125) / 100; + return 0; +} + +static int ds2782_get_current(struct ds278x_info *info, int *current_uA) +{ + int sense_res; + int err; + u8 sense_res_raw; + s16 raw; + + /* + * The units of measurement for current are dependent on the value of + * the sense resistor. + */ + err = ds278x_read_reg(info, DS2782_REG_RSNSP, &sense_res_raw); + if (err) + return err; + if (sense_res_raw == 0) { + dev_err(&info->client->dev, "sense resistor value is 0\n"); + return -ENXIO; + } + sense_res = 1000 / sense_res_raw; + + dev_dbg(&info->client->dev, "sense resistor = %d milli-ohms\n", + sense_res); + err = ds278x_read_reg16(info, DS278x_REG_CURRENT_MSB, &raw); + if (err) + return err; + *current_uA = raw * (DS2782_CURRENT_UNITS / sense_res); + return 0; +} + +static int ds2782_get_voltage(struct ds278x_info *info, int *voltage_uV) +{ + s16 raw; + int err; + + /* + * Voltage is measured in units of 4.88mV. The voltage is stored as + * a 10-bit number plus sign, in the upper bits of a 16-bit register + */ + err = ds278x_read_reg16(info, DS278x_REG_VOLT_MSB, &raw); + if (err) + return err; + *voltage_uV = (raw / 32) * 4800; + return 0; +} + +static int ds2782_get_capacity(struct ds278x_info *info, int *capacity) +{ + int err; + u8 raw; + + err = ds278x_read_reg(info, DS2782_REG_RARC, &raw); + if (err) + return err; + *capacity = raw; + return 0; +} + +static int ds2786_get_current(struct ds278x_info *info, int *current_uA) +{ + int err; + s16 raw; + + err = ds278x_read_reg16(info, DS278x_REG_CURRENT_MSB, &raw); + if (err) + return err; + *current_uA = (raw / 16) * (DS2786_CURRENT_UNITS / info->rsns); + return 0; +} + +static int ds2786_get_voltage(struct ds278x_info *info, int *voltage_uV) +{ + s16 raw; + int err; + + /* + * Voltage is measured in units of 1.22mV. The voltage is stored as + * a 12-bit number plus sign, in the upper bits of a 16-bit register + */ + err = ds278x_read_reg16(info, DS278x_REG_VOLT_MSB, &raw); + if (err) + return err; + *voltage_uV = (raw / 8) * 1220; + return 0; +} + +static int ds2786_get_capacity(struct ds278x_info *info, int *capacity) +{ + int err; + u8 raw; + + err = ds278x_read_reg(info, DS2786_REG_RARC, &raw); + if (err) + return err; + /* Relative capacity is displayed with resolution 0.5 % */ + *capacity = raw/2 ; + return 0; +} + +static int ds278x_get_status(struct ds278x_info *info, int *status) +{ + int err; + int current_uA; + int capacity; + + err = info->ops->get_battery_current(info, ¤t_uA); + if (err) + return err; + + err = info->ops->get_battery_capacity(info, &capacity); + if (err) + return err; + + info->capacity = capacity; + + if (capacity == 100) + *status = POWER_SUPPLY_STATUS_FULL; + else if (current_uA == 0) + *status = POWER_SUPPLY_STATUS_NOT_CHARGING; + else if (current_uA < 0) + *status = POWER_SUPPLY_STATUS_DISCHARGING; + else + *status = POWER_SUPPLY_STATUS_CHARGING; + + return 0; +} + +static int ds278x_battery_get_property(struct power_supply *psy, + enum power_supply_property prop, + union power_supply_propval *val) +{ + struct ds278x_info *info = to_ds278x_info(psy); + int ret; + + switch (prop) { + case POWER_SUPPLY_PROP_STATUS: + ret = ds278x_get_status(info, &val->intval); + break; + + case POWER_SUPPLY_PROP_CAPACITY: + ret = info->ops->get_battery_capacity(info, &val->intval); + break; + + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + ret = info->ops->get_battery_voltage(info, &val->intval); + break; + + case POWER_SUPPLY_PROP_CURRENT_NOW: + ret = info->ops->get_battery_current(info, &val->intval); + break; + + case POWER_SUPPLY_PROP_TEMP: + ret = ds278x_get_temp(info, &val->intval); + break; + + default: + ret = -EINVAL; + } + + return ret; +} + +static void ds278x_bat_update(struct ds278x_info *info) +{ + int old_status = info->status; + int old_capacity = info->capacity; + + ds278x_get_status(info, &info->status); + + if ((old_status != info->status) || (old_capacity != info->capacity)) + power_supply_changed(info->battery); +} + +static void ds278x_bat_work(struct work_struct *work) +{ + struct ds278x_info *info; + + info = container_of(work, struct ds278x_info, bat_work.work); + ds278x_bat_update(info); + + schedule_delayed_work(&info->bat_work, DS278x_DELAY); +} + +static enum power_supply_property ds278x_battery_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_TEMP, +}; + +static void ds278x_power_supply_init(struct power_supply_desc *battery) +{ + battery->type = POWER_SUPPLY_TYPE_BATTERY; + battery->properties = ds278x_battery_props; + battery->num_properties = ARRAY_SIZE(ds278x_battery_props); + battery->get_property = ds278x_battery_get_property; + battery->external_power_changed = NULL; +} + +static int ds278x_battery_remove(struct i2c_client *client) +{ + struct ds278x_info *info = i2c_get_clientdata(client); + + power_supply_unregister(info->battery); + kfree(info->battery_desc.name); + + mutex_lock(&battery_lock); + idr_remove(&battery_id, info->id); + mutex_unlock(&battery_lock); + + cancel_delayed_work(&info->bat_work); + + kfree(info); + return 0; +} + +#ifdef CONFIG_PM_SLEEP + +static int ds278x_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct ds278x_info *info = i2c_get_clientdata(client); + + cancel_delayed_work(&info->bat_work); + return 0; +} + +static int ds278x_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct ds278x_info *info = i2c_get_clientdata(client); + + schedule_delayed_work(&info->bat_work, DS278x_DELAY); + return 0; +} +#endif /* CONFIG_PM_SLEEP */ + +static SIMPLE_DEV_PM_OPS(ds278x_battery_pm_ops, ds278x_suspend, ds278x_resume); + +enum ds278x_num_id { + DS2782 = 0, + DS2786, +}; + +static const struct ds278x_battery_ops ds278x_ops[] = { + [DS2782] = { + .get_battery_current = ds2782_get_current, + .get_battery_voltage = ds2782_get_voltage, + .get_battery_capacity = ds2782_get_capacity, + }, + [DS2786] = { + .get_battery_current = ds2786_get_current, + .get_battery_voltage = ds2786_get_voltage, + .get_battery_capacity = ds2786_get_capacity, + } +}; + +static int ds278x_battery_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct ds278x_platform_data *pdata = client->dev.platform_data; + struct power_supply_config psy_cfg = {}; + struct ds278x_info *info; + int ret; + int num; + + /* + * ds2786 should have the sense resistor value set + * in the platform data + */ + if (id->driver_data == DS2786 && !pdata) { + dev_err(&client->dev, "missing platform data for ds2786\n"); + return -EINVAL; + } + + /* Get an ID for this battery */ + mutex_lock(&battery_lock); + ret = idr_alloc(&battery_id, client, 0, 0, GFP_KERNEL); + mutex_unlock(&battery_lock); + if (ret < 0) + goto fail_id; + num = ret; + + info = kzalloc(sizeof(*info), GFP_KERNEL); + if (!info) { + ret = -ENOMEM; + goto fail_info; + } + + info->battery_desc.name = kasprintf(GFP_KERNEL, "%s-%d", + client->name, num); + if (!info->battery_desc.name) { + ret = -ENOMEM; + goto fail_name; + } + + if (id->driver_data == DS2786) + info->rsns = pdata->rsns; + + i2c_set_clientdata(client, info); + info->client = client; + info->id = num; + info->ops = &ds278x_ops[id->driver_data]; + ds278x_power_supply_init(&info->battery_desc); + psy_cfg.drv_data = info; + + info->capacity = 100; + info->status = POWER_SUPPLY_STATUS_FULL; + + INIT_DELAYED_WORK(&info->bat_work, ds278x_bat_work); + + info->battery = power_supply_register(&client->dev, + &info->battery_desc, &psy_cfg); + if (IS_ERR(info->battery)) { + dev_err(&client->dev, "failed to register battery\n"); + ret = PTR_ERR(info->battery); + goto fail_register; + } else { + schedule_delayed_work(&info->bat_work, DS278x_DELAY); + } + + return 0; + +fail_register: + kfree(info->battery_desc.name); +fail_name: + kfree(info); +fail_info: + mutex_lock(&battery_lock); + idr_remove(&battery_id, num); + mutex_unlock(&battery_lock); +fail_id: + return ret; +} + +static const struct i2c_device_id ds278x_id[] = { + {"ds2782", DS2782}, + {"ds2786", DS2786}, + {}, +}; +MODULE_DEVICE_TABLE(i2c, ds278x_id); + +static struct i2c_driver ds278x_battery_driver = { + .driver = { + .name = "ds2782-battery", + .pm = &ds278x_battery_pm_ops, + }, + .probe = ds278x_battery_probe, + .remove = ds278x_battery_remove, + .id_table = ds278x_id, +}; +module_i2c_driver(ds278x_battery_driver); + +MODULE_AUTHOR("Ryan Mallon"); +MODULE_DESCRIPTION("Maxim/Dallas DS2782 Stand-Alone Fuel Gauage IC driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/power/supply/generic-adc-battery.c b/drivers/power/supply/generic-adc-battery.c new file mode 100644 index 000000000000..edb36bf781b0 --- /dev/null +++ b/drivers/power/supply/generic-adc-battery.c @@ -0,0 +1,432 @@ +/* + * Generic battery driver code using IIO + * Copyright (C) 2012, Anish Kumar + * based on jz4740-battery.c + * based on s3c_adc_battery.c + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file COPYING in the main directory of this archive for + * more details. + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define JITTER_DEFAULT 10 /* hope 10ms is enough */ + +enum gab_chan_type { + GAB_VOLTAGE = 0, + GAB_CURRENT, + GAB_POWER, + GAB_MAX_CHAN_TYPE +}; + +/* + * gab_chan_name suggests the standard channel names for commonly used + * channel types. + */ +static const char *const gab_chan_name[] = { + [GAB_VOLTAGE] = "voltage", + [GAB_CURRENT] = "current", + [GAB_POWER] = "power", +}; + +struct gab { + struct power_supply *psy; + struct power_supply_desc psy_desc; + struct iio_channel *channel[GAB_MAX_CHAN_TYPE]; + struct gab_platform_data *pdata; + struct delayed_work bat_work; + int level; + int status; + bool cable_plugged; +}; + +static struct gab *to_generic_bat(struct power_supply *psy) +{ + return power_supply_get_drvdata(psy); +} + +static void gab_ext_power_changed(struct power_supply *psy) +{ + struct gab *adc_bat = to_generic_bat(psy); + + schedule_delayed_work(&adc_bat->bat_work, msecs_to_jiffies(0)); +} + +static const enum power_supply_property gab_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, + POWER_SUPPLY_PROP_CHARGE_EMPTY_DESIGN, + POWER_SUPPLY_PROP_CHARGE_NOW, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, + POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, + POWER_SUPPLY_PROP_MODEL_NAME, +}; + +/* + * This properties are set based on the received platform data and this + * should correspond one-to-one with enum chan_type. + */ +static const enum power_supply_property gab_dyn_props[] = { + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_POWER_NOW, +}; + +static bool gab_charge_finished(struct gab *adc_bat) +{ + struct gab_platform_data *pdata = adc_bat->pdata; + bool ret = gpio_get_value(pdata->gpio_charge_finished); + bool inv = pdata->gpio_inverted; + + if (!gpio_is_valid(pdata->gpio_charge_finished)) + return false; + return ret ^ inv; +} + +static int gab_get_status(struct gab *adc_bat) +{ + struct gab_platform_data *pdata = adc_bat->pdata; + struct power_supply_info *bat_info; + + bat_info = &pdata->battery_info; + if (adc_bat->level == bat_info->charge_full_design) + return POWER_SUPPLY_STATUS_FULL; + return adc_bat->status; +} + +static enum gab_chan_type gab_prop_to_chan(enum power_supply_property psp) +{ + switch (psp) { + case POWER_SUPPLY_PROP_POWER_NOW: + return GAB_POWER; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + return GAB_VOLTAGE; + case POWER_SUPPLY_PROP_CURRENT_NOW: + return GAB_CURRENT; + default: + WARN_ON(1); + break; + } + return GAB_POWER; +} + +static int read_channel(struct gab *adc_bat, enum power_supply_property psp, + int *result) +{ + int ret; + int chan_index; + + chan_index = gab_prop_to_chan(psp); + ret = iio_read_channel_processed(adc_bat->channel[chan_index], + result); + if (ret < 0) + pr_err("read channel error\n"); + return ret; +} + +static int gab_get_property(struct power_supply *psy, + enum power_supply_property psp, union power_supply_propval *val) +{ + struct gab *adc_bat; + struct gab_platform_data *pdata; + struct power_supply_info *bat_info; + int result = 0; + int ret = 0; + + adc_bat = to_generic_bat(psy); + if (!adc_bat) { + dev_err(&psy->dev, "no battery infos ?!\n"); + return -EINVAL; + } + pdata = adc_bat->pdata; + bat_info = &pdata->battery_info; + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + val->intval = gab_get_status(adc_bat); + break; + case POWER_SUPPLY_PROP_CHARGE_EMPTY_DESIGN: + val->intval = 0; + break; + case POWER_SUPPLY_PROP_CHARGE_NOW: + val->intval = pdata->cal_charge(result); + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + case POWER_SUPPLY_PROP_CURRENT_NOW: + case POWER_SUPPLY_PROP_POWER_NOW: + ret = read_channel(adc_bat, psp, &result); + if (ret < 0) + goto err; + val->intval = result; + break; + case POWER_SUPPLY_PROP_TECHNOLOGY: + val->intval = bat_info->technology; + break; + case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: + val->intval = bat_info->voltage_min_design; + break; + case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: + val->intval = bat_info->voltage_max_design; + break; + case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: + val->intval = bat_info->charge_full_design; + break; + case POWER_SUPPLY_PROP_MODEL_NAME: + val->strval = bat_info->name; + break; + default: + return -EINVAL; + } +err: + return ret; +} + +static void gab_work(struct work_struct *work) +{ + struct gab *adc_bat; + struct gab_platform_data *pdata; + struct delayed_work *delayed_work; + bool is_plugged; + int status; + + delayed_work = to_delayed_work(work); + adc_bat = container_of(delayed_work, struct gab, bat_work); + pdata = adc_bat->pdata; + status = adc_bat->status; + + is_plugged = power_supply_am_i_supplied(adc_bat->psy); + adc_bat->cable_plugged = is_plugged; + + if (!is_plugged) + adc_bat->status = POWER_SUPPLY_STATUS_DISCHARGING; + else if (gab_charge_finished(adc_bat)) + adc_bat->status = POWER_SUPPLY_STATUS_NOT_CHARGING; + else + adc_bat->status = POWER_SUPPLY_STATUS_CHARGING; + + if (status != adc_bat->status) + power_supply_changed(adc_bat->psy); +} + +static irqreturn_t gab_charged(int irq, void *dev_id) +{ + struct gab *adc_bat = dev_id; + struct gab_platform_data *pdata = adc_bat->pdata; + int delay; + + delay = pdata->jitter_delay ? pdata->jitter_delay : JITTER_DEFAULT; + schedule_delayed_work(&adc_bat->bat_work, + msecs_to_jiffies(delay)); + return IRQ_HANDLED; +} + +static int gab_probe(struct platform_device *pdev) +{ + struct gab *adc_bat; + struct power_supply_desc *psy_desc; + struct power_supply_config psy_cfg = {}; + struct gab_platform_data *pdata = pdev->dev.platform_data; + enum power_supply_property *properties; + int ret = 0; + int chan; + int index = 0; + + adc_bat = devm_kzalloc(&pdev->dev, sizeof(*adc_bat), GFP_KERNEL); + if (!adc_bat) { + dev_err(&pdev->dev, "failed to allocate memory\n"); + return -ENOMEM; + } + + psy_cfg.drv_data = adc_bat; + psy_desc = &adc_bat->psy_desc; + psy_desc->name = pdata->battery_info.name; + + /* bootup default values for the battery */ + adc_bat->cable_plugged = false; + adc_bat->status = POWER_SUPPLY_STATUS_DISCHARGING; + psy_desc->type = POWER_SUPPLY_TYPE_BATTERY; + psy_desc->get_property = gab_get_property; + psy_desc->external_power_changed = gab_ext_power_changed; + adc_bat->pdata = pdata; + + /* + * copying the static properties and allocating extra memory for holding + * the extra configurable properties received from platform data. + */ + psy_desc->properties = kcalloc(ARRAY_SIZE(gab_props) + + ARRAY_SIZE(gab_chan_name), + sizeof(*psy_desc->properties), + GFP_KERNEL); + if (!psy_desc->properties) { + ret = -ENOMEM; + goto first_mem_fail; + } + + memcpy(psy_desc->properties, gab_props, sizeof(gab_props)); + properties = (enum power_supply_property *) + ((char *)psy_desc->properties + sizeof(gab_props)); + + /* + * getting channel from iio and copying the battery properties + * based on the channel supported by consumer device. + */ + for (chan = 0; chan < ARRAY_SIZE(gab_chan_name); chan++) { + adc_bat->channel[chan] = iio_channel_get(&pdev->dev, + gab_chan_name[chan]); + if (IS_ERR(adc_bat->channel[chan])) { + ret = PTR_ERR(adc_bat->channel[chan]); + adc_bat->channel[chan] = NULL; + } else { + /* copying properties for supported channels only */ + memcpy(properties + sizeof(*(psy_desc->properties)) * index, + &gab_dyn_props[chan], + sizeof(gab_dyn_props[chan])); + index++; + } + } + + /* none of the channels are supported so let's bail out */ + if (index == 0) { + ret = -ENODEV; + goto second_mem_fail; + } + + /* + * Total number of properties is equal to static properties + * plus the dynamic properties.Some properties may not be set + * as come channels may be not be supported by the device.So + * we need to take care of that. + */ + psy_desc->num_properties = ARRAY_SIZE(gab_props) + index; + + adc_bat->psy = power_supply_register(&pdev->dev, psy_desc, &psy_cfg); + if (IS_ERR(adc_bat->psy)) { + ret = PTR_ERR(adc_bat->psy); + goto err_reg_fail; + } + + INIT_DELAYED_WORK(&adc_bat->bat_work, gab_work); + + if (gpio_is_valid(pdata->gpio_charge_finished)) { + int irq; + ret = gpio_request(pdata->gpio_charge_finished, "charged"); + if (ret) + goto gpio_req_fail; + + irq = gpio_to_irq(pdata->gpio_charge_finished); + ret = request_any_context_irq(irq, gab_charged, + IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, + "battery charged", adc_bat); + if (ret < 0) + goto err_gpio; + } + + platform_set_drvdata(pdev, adc_bat); + + /* Schedule timer to check current status */ + schedule_delayed_work(&adc_bat->bat_work, + msecs_to_jiffies(0)); + return 0; + +err_gpio: + gpio_free(pdata->gpio_charge_finished); +gpio_req_fail: + power_supply_unregister(adc_bat->psy); +err_reg_fail: + for (chan = 0; chan < ARRAY_SIZE(gab_chan_name); chan++) { + if (adc_bat->channel[chan]) + iio_channel_release(adc_bat->channel[chan]); + } +second_mem_fail: + kfree(psy_desc->properties); +first_mem_fail: + return ret; +} + +static int gab_remove(struct platform_device *pdev) +{ + int chan; + struct gab *adc_bat = platform_get_drvdata(pdev); + struct gab_platform_data *pdata = adc_bat->pdata; + + power_supply_unregister(adc_bat->psy); + + if (gpio_is_valid(pdata->gpio_charge_finished)) { + free_irq(gpio_to_irq(pdata->gpio_charge_finished), adc_bat); + gpio_free(pdata->gpio_charge_finished); + } + + for (chan = 0; chan < ARRAY_SIZE(gab_chan_name); chan++) { + if (adc_bat->channel[chan]) + iio_channel_release(adc_bat->channel[chan]); + } + + kfree(adc_bat->psy_desc.properties); + cancel_delayed_work(&adc_bat->bat_work); + return 0; +} + +#ifdef CONFIG_PM +static int gab_suspend(struct device *dev) +{ + struct gab *adc_bat = dev_get_drvdata(dev); + + cancel_delayed_work_sync(&adc_bat->bat_work); + adc_bat->status = POWER_SUPPLY_STATUS_UNKNOWN; + return 0; +} + +static int gab_resume(struct device *dev) +{ + struct gab *adc_bat = dev_get_drvdata(dev); + struct gab_platform_data *pdata = adc_bat->pdata; + int delay; + + delay = pdata->jitter_delay ? pdata->jitter_delay : JITTER_DEFAULT; + + /* Schedule timer to check current status */ + schedule_delayed_work(&adc_bat->bat_work, + msecs_to_jiffies(delay)); + return 0; +} + +static const struct dev_pm_ops gab_pm_ops = { + .suspend = gab_suspend, + .resume = gab_resume, +}; + +#define GAB_PM_OPS (&gab_pm_ops) +#else +#define GAB_PM_OPS (NULL) +#endif + +static struct platform_driver gab_driver = { + .driver = { + .name = "generic-adc-battery", + .pm = GAB_PM_OPS + }, + .probe = gab_probe, + .remove = gab_remove, +}; +module_platform_driver(gab_driver); + +MODULE_AUTHOR("anish kumar "); +MODULE_DESCRIPTION("generic battery driver using IIO"); +MODULE_LICENSE("GPL"); diff --git a/drivers/power/supply/goldfish_battery.c b/drivers/power/supply/goldfish_battery.c new file mode 100644 index 000000000000..f5c525e4482a --- /dev/null +++ b/drivers/power/supply/goldfish_battery.c @@ -0,0 +1,256 @@ +/* + * Power supply driver for the goldfish emulator + * + * Copyright (C) 2008 Google, Inc. + * Copyright (C) 2012 Intel, Inc. + * Copyright (C) 2013 Intel, Inc. + * Author: Mike Lockwood + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct goldfish_battery_data { + void __iomem *reg_base; + int irq; + spinlock_t lock; + + struct power_supply *battery; + struct power_supply *ac; +}; + +#define GOLDFISH_BATTERY_READ(data, addr) \ + (readl(data->reg_base + addr)) +#define GOLDFISH_BATTERY_WRITE(data, addr, x) \ + (writel(x, data->reg_base + addr)) + +/* + * Temporary variable used between goldfish_battery_probe() and + * goldfish_battery_open(). + */ +static struct goldfish_battery_data *battery_data; + +enum { + /* status register */ + BATTERY_INT_STATUS = 0x00, + /* set this to enable IRQ */ + BATTERY_INT_ENABLE = 0x04, + + BATTERY_AC_ONLINE = 0x08, + BATTERY_STATUS = 0x0C, + BATTERY_HEALTH = 0x10, + BATTERY_PRESENT = 0x14, + BATTERY_CAPACITY = 0x18, + + BATTERY_STATUS_CHANGED = 1U << 0, + AC_STATUS_CHANGED = 1U << 1, + BATTERY_INT_MASK = BATTERY_STATUS_CHANGED | AC_STATUS_CHANGED, +}; + + +static int goldfish_ac_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct goldfish_battery_data *data = power_supply_get_drvdata(psy); + int ret = 0; + + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + val->intval = GOLDFISH_BATTERY_READ(data, BATTERY_AC_ONLINE); + break; + default: + ret = -EINVAL; + break; + } + return ret; +} + +static int goldfish_battery_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct goldfish_battery_data *data = power_supply_get_drvdata(psy); + int ret = 0; + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + val->intval = GOLDFISH_BATTERY_READ(data, BATTERY_STATUS); + break; + case POWER_SUPPLY_PROP_HEALTH: + val->intval = GOLDFISH_BATTERY_READ(data, BATTERY_HEALTH); + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = GOLDFISH_BATTERY_READ(data, BATTERY_PRESENT); + break; + case POWER_SUPPLY_PROP_TECHNOLOGY: + val->intval = POWER_SUPPLY_TECHNOLOGY_LION; + break; + case POWER_SUPPLY_PROP_CAPACITY: + val->intval = GOLDFISH_BATTERY_READ(data, BATTERY_CAPACITY); + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static enum power_supply_property goldfish_battery_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_CAPACITY, +}; + +static enum power_supply_property goldfish_ac_props[] = { + POWER_SUPPLY_PROP_ONLINE, +}; + +static irqreturn_t goldfish_battery_interrupt(int irq, void *dev_id) +{ + unsigned long irq_flags; + struct goldfish_battery_data *data = dev_id; + uint32_t status; + + spin_lock_irqsave(&data->lock, irq_flags); + + /* read status flags, which will clear the interrupt */ + status = GOLDFISH_BATTERY_READ(data, BATTERY_INT_STATUS); + status &= BATTERY_INT_MASK; + + if (status & BATTERY_STATUS_CHANGED) + power_supply_changed(data->battery); + if (status & AC_STATUS_CHANGED) + power_supply_changed(data->ac); + + spin_unlock_irqrestore(&data->lock, irq_flags); + return status ? IRQ_HANDLED : IRQ_NONE; +} + +static const struct power_supply_desc battery_desc = { + .properties = goldfish_battery_props, + .num_properties = ARRAY_SIZE(goldfish_battery_props), + .get_property = goldfish_battery_get_property, + .name = "battery", + .type = POWER_SUPPLY_TYPE_BATTERY, +}; + +static const struct power_supply_desc ac_desc = { + .properties = goldfish_ac_props, + .num_properties = ARRAY_SIZE(goldfish_ac_props), + .get_property = goldfish_ac_get_property, + .name = "ac", + .type = POWER_SUPPLY_TYPE_MAINS, +}; + +static int goldfish_battery_probe(struct platform_device *pdev) +{ + int ret; + struct resource *r; + struct goldfish_battery_data *data; + struct power_supply_config psy_cfg = {}; + + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); + if (data == NULL) + return -ENOMEM; + + spin_lock_init(&data->lock); + + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (r == NULL) { + dev_err(&pdev->dev, "platform_get_resource failed\n"); + return -ENODEV; + } + + data->reg_base = devm_ioremap(&pdev->dev, r->start, resource_size(r)); + if (data->reg_base == NULL) { + dev_err(&pdev->dev, "unable to remap MMIO\n"); + return -ENOMEM; + } + + data->irq = platform_get_irq(pdev, 0); + if (data->irq < 0) { + dev_err(&pdev->dev, "platform_get_irq failed\n"); + return -ENODEV; + } + + ret = devm_request_irq(&pdev->dev, data->irq, goldfish_battery_interrupt, + IRQF_SHARED, pdev->name, data); + if (ret) + return ret; + + psy_cfg.drv_data = data; + + data->ac = power_supply_register(&pdev->dev, &ac_desc, &psy_cfg); + if (IS_ERR(data->ac)) + return PTR_ERR(data->ac); + + data->battery = power_supply_register(&pdev->dev, &battery_desc, + &psy_cfg); + if (IS_ERR(data->battery)) { + power_supply_unregister(data->ac); + return PTR_ERR(data->battery); + } + + platform_set_drvdata(pdev, data); + battery_data = data; + + GOLDFISH_BATTERY_WRITE(data, BATTERY_INT_ENABLE, BATTERY_INT_MASK); + return 0; +} + +static int goldfish_battery_remove(struct platform_device *pdev) +{ + struct goldfish_battery_data *data = platform_get_drvdata(pdev); + + power_supply_unregister(data->battery); + power_supply_unregister(data->ac); + battery_data = NULL; + return 0; +} + +static const struct of_device_id goldfish_battery_of_match[] = { + { .compatible = "google,goldfish-battery", }, + {}, +}; +MODULE_DEVICE_TABLE(of, goldfish_battery_of_match); + +static const struct acpi_device_id goldfish_battery_acpi_match[] = { + { "GFSH0001", 0 }, + { }, +}; +MODULE_DEVICE_TABLE(acpi, goldfish_battery_acpi_match); + +static struct platform_driver goldfish_battery_device = { + .probe = goldfish_battery_probe, + .remove = goldfish_battery_remove, + .driver = { + .name = "goldfish-battery", + .of_match_table = goldfish_battery_of_match, + .acpi_match_table = ACPI_PTR(goldfish_battery_acpi_match), + } +}; +module_platform_driver(goldfish_battery_device); + +MODULE_AUTHOR("Mike Lockwood lockwood@android.com"); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Battery driver for the Goldfish emulator"); diff --git a/drivers/power/supply/gpio-charger.c b/drivers/power/supply/gpio-charger.c new file mode 100644 index 000000000000..c5869b1941ac --- /dev/null +++ b/drivers/power/supply/gpio-charger.c @@ -0,0 +1,280 @@ +/* + * Copyright (C) 2010, Lars-Peter Clausen + * Driver for chargers which report their online status through a GPIO pin + * + * 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. + * + * 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., + * 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +struct gpio_charger { + const struct gpio_charger_platform_data *pdata; + unsigned int irq; + bool wakeup_enabled; + + struct power_supply *charger; + struct power_supply_desc charger_desc; +}; + +static irqreturn_t gpio_charger_irq(int irq, void *devid) +{ + struct power_supply *charger = devid; + + power_supply_changed(charger); + + return IRQ_HANDLED; +} + +static inline struct gpio_charger *psy_to_gpio_charger(struct power_supply *psy) +{ + return power_supply_get_drvdata(psy); +} + +static int gpio_charger_get_property(struct power_supply *psy, + enum power_supply_property psp, union power_supply_propval *val) +{ + struct gpio_charger *gpio_charger = psy_to_gpio_charger(psy); + const struct gpio_charger_platform_data *pdata = gpio_charger->pdata; + + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + val->intval = !!gpio_get_value_cansleep(pdata->gpio); + val->intval ^= pdata->gpio_active_low; + break; + default: + return -EINVAL; + } + + return 0; +} + +static enum power_supply_property gpio_charger_properties[] = { + POWER_SUPPLY_PROP_ONLINE, +}; + +static +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) + return ERR_PTR(-ENOENT); + + pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) + 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) { + if (!strncmp("unknown", chargetype, 7)) + pdata->type = POWER_SUPPLY_TYPE_UNKNOWN; + else if (!strncmp("battery", chargetype, 7)) + pdata->type = POWER_SUPPLY_TYPE_BATTERY; + else if (!strncmp("ups", chargetype, 3)) + pdata->type = POWER_SUPPLY_TYPE_UPS; + else if (!strncmp("mains", chargetype, 5)) + pdata->type = POWER_SUPPLY_TYPE_MAINS; + else if (!strncmp("usb-sdp", chargetype, 7)) + pdata->type = POWER_SUPPLY_TYPE_USB; + else if (!strncmp("usb-dcp", chargetype, 7)) + pdata->type = POWER_SUPPLY_TYPE_USB_DCP; + else if (!strncmp("usb-cdp", chargetype, 7)) + pdata->type = POWER_SUPPLY_TYPE_USB_CDP; + else if (!strncmp("usb-aca", chargetype, 7)) + pdata->type = POWER_SUPPLY_TYPE_USB_ACA; + else + dev_warn(dev, "unknown charger type %s\n", chargetype); + } + + return pdata; +} + +static int gpio_charger_probe(struct platform_device *pdev) +{ + const struct gpio_charger_platform_data *pdata = pdev->dev.platform_data; + struct power_supply_config psy_cfg = {}; + struct gpio_charger *gpio_charger; + struct power_supply_desc *charger_desc; + int ret; + int irq; + + if (!pdata) { + pdata = gpio_charger_parse_dt(&pdev->dev); + if (IS_ERR(pdata)) { + ret = PTR_ERR(pdata); + if (ret != -EPROBE_DEFER) + dev_err(&pdev->dev, "No platform data\n"); + return ret; + } + } + + 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) { + dev_err(&pdev->dev, "Failed to alloc driver structure\n"); + return -ENOMEM; + } + + charger_desc = &gpio_charger->charger_desc; + + charger_desc->name = pdata->name ? pdata->name : "gpio-charger"; + charger_desc->type = pdata->type; + charger_desc->properties = gpio_charger_properties; + charger_desc->num_properties = ARRAY_SIZE(gpio_charger_properties); + charger_desc->get_property = gpio_charger_get_property; + + psy_cfg.supplied_to = pdata->supplied_to; + psy_cfg.num_supplicants = pdata->num_supplicants; + 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, + charger_desc, &psy_cfg); + if (IS_ERR(gpio_charger->charger)) { + ret = PTR_ERR(gpio_charger->charger); + dev_err(&pdev->dev, "Failed to register power supply: %d\n", + ret); + goto err_gpio_free; + } + + irq = gpio_to_irq(pdata->gpio); + if (irq > 0) { + ret = request_any_context_irq(irq, gpio_charger_irq, + IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, + dev_name(&pdev->dev), gpio_charger->charger); + if (ret < 0) + dev_warn(&pdev->dev, "Failed to request irq: %d\n", ret); + else + gpio_charger->irq = irq; + } + + platform_set_drvdata(pdev, gpio_charger); + + device_init_wakeup(&pdev->dev, 1); + + return 0; + +err_gpio_free: + gpio_free(pdata->gpio); +err_free: + return ret; +} + +static int gpio_charger_remove(struct platform_device *pdev) +{ + struct gpio_charger *gpio_charger = platform_get_drvdata(pdev); + + if (gpio_charger->irq) + free_irq(gpio_charger->irq, gpio_charger->charger); + + power_supply_unregister(gpio_charger->charger); + + gpio_free(gpio_charger->pdata->gpio); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int gpio_charger_suspend(struct device *dev) +{ + struct gpio_charger *gpio_charger = dev_get_drvdata(dev); + + if (device_may_wakeup(dev)) + gpio_charger->wakeup_enabled = + !enable_irq_wake(gpio_charger->irq); + + return 0; +} + +static int gpio_charger_resume(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct gpio_charger *gpio_charger = platform_get_drvdata(pdev); + + if (device_may_wakeup(dev) && gpio_charger->wakeup_enabled) + disable_irq_wake(gpio_charger->irq); + power_supply_changed(gpio_charger->charger); + + return 0; +} +#endif + +static SIMPLE_DEV_PM_OPS(gpio_charger_pm_ops, + gpio_charger_suspend, gpio_charger_resume); + +static const struct of_device_id gpio_charger_match[] = { + { .compatible = "gpio-charger" }, + { } +}; +MODULE_DEVICE_TABLE(of, gpio_charger_match); + +static struct platform_driver gpio_charger_driver = { + .probe = gpio_charger_probe, + .remove = gpio_charger_remove, + .driver = { + .name = "gpio-charger", + .pm = &gpio_charger_pm_ops, + .of_match_table = gpio_charger_match, + }, +}; + +module_platform_driver(gpio_charger_driver); + +MODULE_AUTHOR("Lars-Peter Clausen "); +MODULE_DESCRIPTION("Driver for chargers which report their online status through a GPIO"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:gpio-charger"); diff --git a/drivers/power/supply/intel_mid_battery.c b/drivers/power/supply/intel_mid_battery.c new file mode 100644 index 000000000000..9fa4acc107ca --- /dev/null +++ b/drivers/power/supply/intel_mid_battery.c @@ -0,0 +1,796 @@ +/* + * 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 + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#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 = + create_singlethread_workqueue(dev_name(dev)); + 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 "); +MODULE_DESCRIPTION("Intel Moorestown PMIC Battery Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/power/supply/ipaq_micro_battery.c b/drivers/power/supply/ipaq_micro_battery.c new file mode 100644 index 000000000000..35b01c7d775b --- /dev/null +++ b/drivers/power/supply/ipaq_micro_battery.c @@ -0,0 +1,316 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * h3xxx atmel micro companion support, battery subdevice + * based on previous kernel 2.4 version + * Author : Alessandro Gardich + * Author : Linus Walleij + * + */ + +#include +#include +#include +#include +#include +#include + +#define BATT_PERIOD 100000 /* 100 seconds in milliseconds */ + +#define MICRO_BATT_CHEM_ALKALINE 0x01 +#define MICRO_BATT_CHEM_NICD 0x02 +#define MICRO_BATT_CHEM_NIMH 0x03 +#define MICRO_BATT_CHEM_LION 0x04 +#define MICRO_BATT_CHEM_LIPOLY 0x05 +#define MICRO_BATT_CHEM_NOT_INSTALLED 0x06 +#define MICRO_BATT_CHEM_UNKNOWN 0xff + +#define MICRO_BATT_STATUS_HIGH 0x01 +#define MICRO_BATT_STATUS_LOW 0x02 +#define MICRO_BATT_STATUS_CRITICAL 0x04 +#define MICRO_BATT_STATUS_CHARGING 0x08 +#define MICRO_BATT_STATUS_CHARGEMAIN 0x10 +#define MICRO_BATT_STATUS_DEAD 0x20 /* Battery will not charge */ +#define MICRO_BATT_STATUS_NOTINSTALLED 0x20 /* For expansion pack batteries */ +#define MICRO_BATT_STATUS_FULL 0x40 /* Battery fully charged */ +#define MICRO_BATT_STATUS_NOBATTERY 0x80 +#define MICRO_BATT_STATUS_UNKNOWN 0xff + +struct micro_battery { + struct ipaq_micro *micro; + struct workqueue_struct *wq; + struct delayed_work update; + u8 ac; + u8 chemistry; + unsigned int voltage; + u16 temperature; + u8 flag; +}; + +static void micro_battery_work(struct work_struct *work) +{ + struct micro_battery *mb = container_of(work, + struct micro_battery, update.work); + struct ipaq_micro_msg msg_battery = { + .id = MSG_BATTERY, + }; + struct ipaq_micro_msg msg_sensor = { + .id = MSG_THERMAL_SENSOR, + }; + + /* First send battery message */ + ipaq_micro_tx_msg_sync(mb->micro, &msg_battery); + if (msg_battery.rx_len < 4) + pr_info("ERROR"); + + /* + * Returned message format: + * byte 0: 0x00 = Not plugged in + * 0x01 = AC adapter plugged in + * byte 1: chemistry + * byte 2: voltage LSB + * byte 3: voltage MSB + * byte 4: flags + * byte 5-9: same for battery 2 + */ + mb->ac = msg_battery.rx_data[0]; + mb->chemistry = msg_battery.rx_data[1]; + mb->voltage = ((((unsigned short)msg_battery.rx_data[3] << 8) + + msg_battery.rx_data[2]) * 5000L) * 1000 / 1024; + mb->flag = msg_battery.rx_data[4]; + + if (msg_battery.rx_len == 9) + pr_debug("second battery ignored\n"); + + /* Then read the sensor */ + ipaq_micro_tx_msg_sync(mb->micro, &msg_sensor); + mb->temperature = msg_sensor.rx_data[1] << 8 | msg_sensor.rx_data[0]; + + queue_delayed_work(mb->wq, &mb->update, msecs_to_jiffies(BATT_PERIOD)); +} + +static int get_capacity(struct power_supply *b) +{ + struct micro_battery *mb = dev_get_drvdata(b->dev.parent); + + switch (mb->flag & 0x07) { + case MICRO_BATT_STATUS_HIGH: + return 100; + break; + case MICRO_BATT_STATUS_LOW: + return 50; + break; + case MICRO_BATT_STATUS_CRITICAL: + return 5; + break; + default: + break; + } + return 0; +} + +static int get_status(struct power_supply *b) +{ + struct micro_battery *mb = dev_get_drvdata(b->dev.parent); + + if (mb->flag == MICRO_BATT_STATUS_UNKNOWN) + return POWER_SUPPLY_STATUS_UNKNOWN; + + if (mb->flag & MICRO_BATT_STATUS_FULL) + return POWER_SUPPLY_STATUS_FULL; + + if ((mb->flag & MICRO_BATT_STATUS_CHARGING) || + (mb->flag & MICRO_BATT_STATUS_CHARGEMAIN)) + return POWER_SUPPLY_STATUS_CHARGING; + + return POWER_SUPPLY_STATUS_DISCHARGING; +} + +static int micro_batt_get_property(struct power_supply *b, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct micro_battery *mb = dev_get_drvdata(b->dev.parent); + + switch (psp) { + case POWER_SUPPLY_PROP_TECHNOLOGY: + switch (mb->chemistry) { + case MICRO_BATT_CHEM_NICD: + val->intval = POWER_SUPPLY_TECHNOLOGY_NiCd; + break; + case MICRO_BATT_CHEM_NIMH: + val->intval = POWER_SUPPLY_TECHNOLOGY_NiMH; + break; + case MICRO_BATT_CHEM_LION: + val->intval = POWER_SUPPLY_TECHNOLOGY_LION; + break; + case MICRO_BATT_CHEM_LIPOLY: + val->intval = POWER_SUPPLY_TECHNOLOGY_LIPO; + break; + default: + val->intval = POWER_SUPPLY_TECHNOLOGY_UNKNOWN; + break; + }; + break; + case POWER_SUPPLY_PROP_STATUS: + val->intval = get_status(b); + break; + case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: + val->intval = 4700000; + break; + case POWER_SUPPLY_PROP_CAPACITY: + val->intval = get_capacity(b); + break; + case POWER_SUPPLY_PROP_TEMP: + val->intval = mb->temperature; + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + val->intval = mb->voltage; + break; + default: + return -EINVAL; + }; + + return 0; +} + +static int micro_ac_get_property(struct power_supply *b, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct micro_battery *mb = dev_get_drvdata(b->dev.parent); + + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + val->intval = mb->ac; + break; + default: + return -EINVAL; + }; + + return 0; +} + +static enum power_supply_property micro_batt_power_props[] = { + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_TEMP, + POWER_SUPPLY_PROP_VOLTAGE_NOW, +}; + +static const struct power_supply_desc micro_batt_power_desc = { + .name = "main-battery", + .type = POWER_SUPPLY_TYPE_BATTERY, + .properties = micro_batt_power_props, + .num_properties = ARRAY_SIZE(micro_batt_power_props), + .get_property = micro_batt_get_property, + .use_for_apm = 1, +}; + +static enum power_supply_property micro_ac_power_props[] = { + POWER_SUPPLY_PROP_ONLINE, +}; + +static const struct power_supply_desc micro_ac_power_desc = { + .name = "ac", + .type = POWER_SUPPLY_TYPE_MAINS, + .properties = micro_ac_power_props, + .num_properties = ARRAY_SIZE(micro_ac_power_props), + .get_property = micro_ac_get_property, +}; + +static struct power_supply *micro_batt_power, *micro_ac_power; + +static int micro_batt_probe(struct platform_device *pdev) +{ + struct micro_battery *mb; + int ret; + + mb = devm_kzalloc(&pdev->dev, sizeof(*mb), GFP_KERNEL); + if (!mb) + return -ENOMEM; + + mb->micro = dev_get_drvdata(pdev->dev.parent); + mb->wq = create_singlethread_workqueue("ipaq-battery-wq"); + if (!mb->wq) + return -ENOMEM; + + INIT_DELAYED_WORK(&mb->update, micro_battery_work); + platform_set_drvdata(pdev, mb); + queue_delayed_work(mb->wq, &mb->update, 1); + + micro_batt_power = power_supply_register(&pdev->dev, + µ_batt_power_desc, NULL); + if (IS_ERR(micro_batt_power)) { + ret = PTR_ERR(micro_batt_power); + goto batt_err; + } + + micro_ac_power = power_supply_register(&pdev->dev, + µ_ac_power_desc, NULL); + if (IS_ERR(micro_ac_power)) { + ret = PTR_ERR(micro_ac_power); + goto ac_err; + } + + dev_info(&pdev->dev, "iPAQ micro battery driver\n"); + return 0; + +ac_err: + power_supply_unregister(micro_batt_power); +batt_err: + cancel_delayed_work_sync(&mb->update); + destroy_workqueue(mb->wq); + return ret; +} + +static int micro_batt_remove(struct platform_device *pdev) + +{ + struct micro_battery *mb = platform_get_drvdata(pdev); + + power_supply_unregister(micro_ac_power); + power_supply_unregister(micro_batt_power); + cancel_delayed_work_sync(&mb->update); + destroy_workqueue(mb->wq); + + return 0; +} + +static int __maybe_unused micro_batt_suspend(struct device *dev) +{ + struct micro_battery *mb = dev_get_drvdata(dev); + + cancel_delayed_work_sync(&mb->update); + return 0; +} + +static int __maybe_unused micro_batt_resume(struct device *dev) +{ + struct micro_battery *mb = dev_get_drvdata(dev); + + queue_delayed_work(mb->wq, &mb->update, msecs_to_jiffies(BATT_PERIOD)); + return 0; +} + +static const struct dev_pm_ops micro_batt_dev_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(micro_batt_suspend, micro_batt_resume) +}; + +static struct platform_driver micro_batt_device_driver = { + .driver = { + .name = "ipaq-micro-battery", + .pm = µ_batt_dev_pm_ops, + }, + .probe = micro_batt_probe, + .remove = micro_batt_remove, +}; +module_platform_driver(micro_batt_device_driver); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("driver for iPAQ Atmel micro battery"); +MODULE_ALIAS("platform:battery-ipaq-micro"); diff --git a/drivers/power/supply/isp1704_charger.c b/drivers/power/supply/isp1704_charger.c new file mode 100644 index 000000000000..4cd6899b961e --- /dev/null +++ b/drivers/power/supply/isp1704_charger.c @@ -0,0 +1,559 @@ +/* + * ISP1704 USB Charger Detection driver + * + * Copyright (C) 2010 Nokia Corporation + * Copyright (C) 2012 - 2013 Pali Rohár + * + * 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 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 + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +/* Vendor specific Power Control register */ +#define ISP1704_PWR_CTRL 0x3d +#define ISP1704_PWR_CTRL_SWCTRL (1 << 0) +#define ISP1704_PWR_CTRL_DET_COMP (1 << 1) +#define ISP1704_PWR_CTRL_BVALID_RISE (1 << 2) +#define ISP1704_PWR_CTRL_BVALID_FALL (1 << 3) +#define ISP1704_PWR_CTRL_DP_WKPU_EN (1 << 4) +#define ISP1704_PWR_CTRL_VDAT_DET (1 << 5) +#define ISP1704_PWR_CTRL_DPVSRC_EN (1 << 6) +#define ISP1704_PWR_CTRL_HWDETECT (1 << 7) + +#define NXP_VENDOR_ID 0x04cc + +static u16 isp170x_id[] = { + 0x1704, + 0x1707, +}; + +struct isp1704_charger { + struct device *dev; + struct power_supply *psy; + struct power_supply_desc psy_desc; + struct usb_phy *phy; + struct notifier_block nb; + struct work_struct work; + + /* properties */ + char model[8]; + unsigned present:1; + unsigned online:1; + unsigned current_max; +}; + +static inline int isp1704_read(struct isp1704_charger *isp, u32 reg) +{ + return usb_phy_io_read(isp->phy, reg); +} + +static inline int isp1704_write(struct isp1704_charger *isp, u32 reg, u32 val) +{ + return usb_phy_io_write(isp->phy, val, reg); +} + +/* + * Disable/enable the power from the isp1704 if a function for it + * has been provided with platform data. + */ +static void isp1704_charger_set_power(struct isp1704_charger *isp, bool on) +{ + struct isp1704_charger_data *board = isp->dev->platform_data; + + if (board && board->set_power) + board->set_power(on); + else if (board) + gpio_set_value(board->enable_gpio, on); +} + +/* + * Determine is the charging port DCP (dedicated charger) or CDP (Host/HUB + * chargers). + * + * REVISIT: The method is defined in Battery Charging Specification and is + * applicable to any ULPI transceiver. Nothing isp170x specific here. + */ +static inline int isp1704_charger_type(struct isp1704_charger *isp) +{ + u8 reg; + u8 func_ctrl; + u8 otg_ctrl; + int type = POWER_SUPPLY_TYPE_USB_DCP; + + func_ctrl = isp1704_read(isp, ULPI_FUNC_CTRL); + otg_ctrl = isp1704_read(isp, ULPI_OTG_CTRL); + + /* disable pulldowns */ + reg = ULPI_OTG_CTRL_DM_PULLDOWN | ULPI_OTG_CTRL_DP_PULLDOWN; + isp1704_write(isp, ULPI_CLR(ULPI_OTG_CTRL), reg); + + /* full speed */ + isp1704_write(isp, ULPI_CLR(ULPI_FUNC_CTRL), + ULPI_FUNC_CTRL_XCVRSEL_MASK); + isp1704_write(isp, ULPI_SET(ULPI_FUNC_CTRL), + ULPI_FUNC_CTRL_FULL_SPEED); + + /* Enable strong pull-up on DP (1.5K) and reset */ + reg = ULPI_FUNC_CTRL_TERMSELECT | ULPI_FUNC_CTRL_RESET; + isp1704_write(isp, ULPI_SET(ULPI_FUNC_CTRL), reg); + usleep_range(1000, 2000); + + reg = isp1704_read(isp, ULPI_DEBUG); + if ((reg & 3) != 3) + type = POWER_SUPPLY_TYPE_USB_CDP; + + /* recover original state */ + isp1704_write(isp, ULPI_FUNC_CTRL, func_ctrl); + isp1704_write(isp, ULPI_OTG_CTRL, otg_ctrl); + + return type; +} + +/* + * ISP1704 detects PS/2 adapters as charger. To make sure the detected charger + * is actually a dedicated charger, the following steps need to be taken. + */ +static inline int isp1704_charger_verify(struct isp1704_charger *isp) +{ + int ret = 0; + u8 r; + + /* Reset the transceiver */ + r = isp1704_read(isp, ULPI_FUNC_CTRL); + r |= ULPI_FUNC_CTRL_RESET; + isp1704_write(isp, ULPI_FUNC_CTRL, r); + usleep_range(1000, 2000); + + /* Set normal mode */ + r &= ~(ULPI_FUNC_CTRL_RESET | ULPI_FUNC_CTRL_OPMODE_MASK); + isp1704_write(isp, ULPI_FUNC_CTRL, r); + + /* Clear the DP and DM pull-down bits */ + r = ULPI_OTG_CTRL_DP_PULLDOWN | ULPI_OTG_CTRL_DM_PULLDOWN; + isp1704_write(isp, ULPI_CLR(ULPI_OTG_CTRL), r); + + /* Enable strong pull-up on DP (1.5K) and reset */ + r = ULPI_FUNC_CTRL_TERMSELECT | ULPI_FUNC_CTRL_RESET; + isp1704_write(isp, ULPI_SET(ULPI_FUNC_CTRL), r); + usleep_range(1000, 2000); + + /* Read the line state */ + if (!isp1704_read(isp, ULPI_DEBUG)) { + /* Disable strong pull-up on DP (1.5K) */ + isp1704_write(isp, ULPI_CLR(ULPI_FUNC_CTRL), + ULPI_FUNC_CTRL_TERMSELECT); + return 1; + } + + /* Is it a charger or PS/2 connection */ + + /* Enable weak pull-up resistor on DP */ + isp1704_write(isp, ULPI_SET(ISP1704_PWR_CTRL), + ISP1704_PWR_CTRL_DP_WKPU_EN); + + /* Disable strong pull-up on DP (1.5K) */ + isp1704_write(isp, ULPI_CLR(ULPI_FUNC_CTRL), + ULPI_FUNC_CTRL_TERMSELECT); + + /* Enable weak pull-down resistor on DM */ + isp1704_write(isp, ULPI_SET(ULPI_OTG_CTRL), + ULPI_OTG_CTRL_DM_PULLDOWN); + + /* It's a charger if the line states are clear */ + if (!(isp1704_read(isp, ULPI_DEBUG))) + ret = 1; + + /* Disable weak pull-up resistor on DP */ + isp1704_write(isp, ULPI_CLR(ISP1704_PWR_CTRL), + ISP1704_PWR_CTRL_DP_WKPU_EN); + + return ret; +} + +static inline int isp1704_charger_detect(struct isp1704_charger *isp) +{ + unsigned long timeout; + u8 pwr_ctrl; + int ret = 0; + + pwr_ctrl = isp1704_read(isp, ISP1704_PWR_CTRL); + + /* set SW control bit in PWR_CTRL register */ + isp1704_write(isp, ISP1704_PWR_CTRL, + ISP1704_PWR_CTRL_SWCTRL); + + /* enable manual charger detection */ + isp1704_write(isp, ULPI_SET(ISP1704_PWR_CTRL), + ISP1704_PWR_CTRL_SWCTRL + | ISP1704_PWR_CTRL_DPVSRC_EN); + usleep_range(1000, 2000); + + timeout = jiffies + msecs_to_jiffies(300); + do { + /* Check if there is a charger */ + if (isp1704_read(isp, ISP1704_PWR_CTRL) + & ISP1704_PWR_CTRL_VDAT_DET) { + ret = isp1704_charger_verify(isp); + break; + } + } while (!time_after(jiffies, timeout) && isp->online); + + /* recover original state */ + isp1704_write(isp, ISP1704_PWR_CTRL, pwr_ctrl); + + return ret; +} + +static inline int isp1704_charger_detect_dcp(struct isp1704_charger *isp) +{ + if (isp1704_charger_detect(isp) && + isp1704_charger_type(isp) == POWER_SUPPLY_TYPE_USB_DCP) + return true; + else + return false; +} + +static void isp1704_charger_work(struct work_struct *data) +{ + struct isp1704_charger *isp = + container_of(data, struct isp1704_charger, work); + static DEFINE_MUTEX(lock); + + mutex_lock(&lock); + + switch (isp->phy->last_event) { + case USB_EVENT_VBUS: + /* do not call wall charger detection more times */ + if (!isp->present) { + isp->online = true; + isp->present = 1; + isp1704_charger_set_power(isp, 1); + + /* detect wall charger */ + if (isp1704_charger_detect_dcp(isp)) { + isp->psy_desc.type = POWER_SUPPLY_TYPE_USB_DCP; + isp->current_max = 1800; + } else { + isp->psy_desc.type = POWER_SUPPLY_TYPE_USB; + isp->current_max = 500; + } + + /* enable data pullups */ + if (isp->phy->otg->gadget) + usb_gadget_connect(isp->phy->otg->gadget); + } + + if (isp->psy_desc.type != POWER_SUPPLY_TYPE_USB_DCP) { + /* + * Only 500mA here or high speed chirp + * handshaking may break + */ + if (isp->current_max > 500) + isp->current_max = 500; + + if (isp->current_max > 100) + isp->psy_desc.type = POWER_SUPPLY_TYPE_USB_CDP; + } + break; + case USB_EVENT_NONE: + isp->online = false; + isp->present = 0; + isp->current_max = 0; + isp->psy_desc.type = POWER_SUPPLY_TYPE_USB; + + /* + * Disable data pullups. We need to prevent the controller from + * enumerating. + * + * FIXME: This is here to allow charger detection with Host/HUB + * chargers. The pullups may be enabled elsewhere, so this can + * not be the final solution. + */ + if (isp->phy->otg->gadget) + usb_gadget_disconnect(isp->phy->otg->gadget); + + isp1704_charger_set_power(isp, 0); + break; + default: + goto out; + } + + power_supply_changed(isp->psy); +out: + mutex_unlock(&lock); +} + +static int isp1704_notifier_call(struct notifier_block *nb, + unsigned long val, void *v) +{ + struct isp1704_charger *isp = + container_of(nb, struct isp1704_charger, nb); + + schedule_work(&isp->work); + + return NOTIFY_OK; +} + +static int isp1704_charger_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct isp1704_charger *isp = power_supply_get_drvdata(psy); + + switch (psp) { + case POWER_SUPPLY_PROP_PRESENT: + val->intval = isp->present; + break; + case POWER_SUPPLY_PROP_ONLINE: + val->intval = isp->online; + break; + case POWER_SUPPLY_PROP_CURRENT_MAX: + val->intval = isp->current_max; + break; + case POWER_SUPPLY_PROP_MODEL_NAME: + val->strval = isp->model; + break; + case POWER_SUPPLY_PROP_MANUFACTURER: + val->strval = "NXP"; + break; + default: + return -EINVAL; + } + return 0; +} + +static enum power_supply_property power_props[] = { + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_CURRENT_MAX, + POWER_SUPPLY_PROP_MODEL_NAME, + POWER_SUPPLY_PROP_MANUFACTURER, +}; + +static inline int isp1704_test_ulpi(struct isp1704_charger *isp) +{ + int vendor; + int product; + int i; + int ret = -ENODEV; + + /* Test ULPI interface */ + ret = isp1704_write(isp, ULPI_SCRATCH, 0xaa); + if (ret < 0) + return ret; + + ret = isp1704_read(isp, ULPI_SCRATCH); + if (ret < 0) + return ret; + + if (ret != 0xaa) + return -ENODEV; + + /* Verify the product and vendor id matches */ + vendor = isp1704_read(isp, ULPI_VENDOR_ID_LOW); + vendor |= isp1704_read(isp, ULPI_VENDOR_ID_HIGH) << 8; + if (vendor != NXP_VENDOR_ID) + return -ENODEV; + + product = isp1704_read(isp, ULPI_PRODUCT_ID_LOW); + product |= isp1704_read(isp, ULPI_PRODUCT_ID_HIGH) << 8; + + for (i = 0; i < ARRAY_SIZE(isp170x_id); i++) { + if (product == isp170x_id[i]) { + sprintf(isp->model, "isp%x", product); + return product; + } + } + + dev_err(isp->dev, "product id %x not matching known ids", product); + + return -ENODEV; +} + +static int isp1704_charger_probe(struct platform_device *pdev) +{ + struct isp1704_charger *isp; + int ret = -ENODEV; + struct power_supply_config psy_cfg = {}; + + struct isp1704_charger_data *pdata = dev_get_platdata(&pdev->dev); + struct device_node *np = pdev->dev.of_node; + + if (np) { + int gpio = of_get_named_gpio(np, "nxp,enable-gpio", 0); + + if (gpio < 0) { + dev_err(&pdev->dev, "missing DT GPIO nxp,enable-gpio\n"); + return gpio; + } + + pdata = devm_kzalloc(&pdev->dev, + sizeof(struct isp1704_charger_data), GFP_KERNEL); + pdata->enable_gpio = gpio; + + dev_info(&pdev->dev, "init gpio %d\n", pdata->enable_gpio); + + ret = devm_gpio_request_one(&pdev->dev, pdata->enable_gpio, + GPIOF_OUT_INIT_HIGH, "isp1704_reset"); + if (ret) { + dev_err(&pdev->dev, "gpio request failed\n"); + goto fail0; + } + } + + if (!pdata) { + dev_err(&pdev->dev, "missing platform data!\n"); + return -ENODEV; + } + + + isp = devm_kzalloc(&pdev->dev, sizeof(*isp), GFP_KERNEL); + if (!isp) + return -ENOMEM; + + if (np) + isp->phy = devm_usb_get_phy_by_phandle(&pdev->dev, "usb-phy", 0); + else + isp->phy = devm_usb_get_phy(&pdev->dev, USB_PHY_TYPE_USB2); + + if (IS_ERR(isp->phy)) { + ret = PTR_ERR(isp->phy); + dev_err(&pdev->dev, "usb_get_phy failed\n"); + goto fail0; + } + + isp->dev = &pdev->dev; + platform_set_drvdata(pdev, isp); + + isp1704_charger_set_power(isp, 1); + + ret = isp1704_test_ulpi(isp); + if (ret < 0) { + dev_err(&pdev->dev, "isp1704_test_ulpi failed\n"); + goto fail1; + } + + isp->psy_desc.name = "isp1704"; + isp->psy_desc.type = POWER_SUPPLY_TYPE_USB; + isp->psy_desc.properties = power_props; + isp->psy_desc.num_properties = ARRAY_SIZE(power_props); + isp->psy_desc.get_property = isp1704_charger_get_property; + + psy_cfg.drv_data = isp; + + isp->psy = power_supply_register(isp->dev, &isp->psy_desc, &psy_cfg); + if (IS_ERR(isp->psy)) { + ret = PTR_ERR(isp->psy); + dev_err(&pdev->dev, "power_supply_register failed\n"); + goto fail1; + } + + /* + * REVISIT: using work in order to allow the usb notifications to be + * made atomically in the future. + */ + INIT_WORK(&isp->work, isp1704_charger_work); + + isp->nb.notifier_call = isp1704_notifier_call; + + ret = usb_register_notifier(isp->phy, &isp->nb); + if (ret) { + dev_err(&pdev->dev, "usb_register_notifier failed\n"); + goto fail2; + } + + dev_info(isp->dev, "registered with product id %s\n", isp->model); + + /* + * Taking over the D+ pullup. + * + * FIXME: The device will be disconnected if it was already + * enumerated. The charger driver should be always loaded before any + * gadget is loaded. + */ + if (isp->phy->otg->gadget) + usb_gadget_disconnect(isp->phy->otg->gadget); + + if (isp->phy->last_event == USB_EVENT_NONE) + isp1704_charger_set_power(isp, 0); + + /* Detect charger if VBUS is valid (the cable was already plugged). */ + if (isp->phy->last_event == USB_EVENT_VBUS && + !isp->phy->otg->default_a) + schedule_work(&isp->work); + + return 0; +fail2: + power_supply_unregister(isp->psy); +fail1: + isp1704_charger_set_power(isp, 0); +fail0: + dev_err(&pdev->dev, "failed to register isp1704 with error %d\n", ret); + + return ret; +} + +static int isp1704_charger_remove(struct platform_device *pdev) +{ + struct isp1704_charger *isp = platform_get_drvdata(pdev); + + usb_unregister_notifier(isp->phy, &isp->nb); + power_supply_unregister(isp->psy); + isp1704_charger_set_power(isp, 0); + + return 0; +} + +#ifdef CONFIG_OF +static const struct of_device_id omap_isp1704_of_match[] = { + { .compatible = "nxp,isp1704", }, + { .compatible = "nxp,isp1707", }, + {}, +}; +MODULE_DEVICE_TABLE(of, omap_isp1704_of_match); +#endif + +static struct platform_driver isp1704_charger_driver = { + .driver = { + .name = "isp1704_charger", + .of_match_table = of_match_ptr(omap_isp1704_of_match), + }, + .probe = isp1704_charger_probe, + .remove = isp1704_charger_remove, +}; + +module_platform_driver(isp1704_charger_driver); + +MODULE_ALIAS("platform:isp1704_charger"); +MODULE_AUTHOR("Nokia Corporation"); +MODULE_DESCRIPTION("ISP170x USB Charger driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/power/supply/jz4740-battery.c b/drivers/power/supply/jz4740-battery.c new file mode 100644 index 000000000000..88f04f4d1a70 --- /dev/null +++ b/drivers/power/supply/jz4740-battery.c @@ -0,0 +1,425 @@ +/* + * Battery measurement code for Ingenic JZ SOC. + * + * Copyright (C) 2009 Jiejing Zhang + * Copyright (C) 2010, Lars-Peter Clausen + * + * based on tosa_battery.c + * + * Copyright (C) 2008 Marek Vasut +* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include + +struct jz_battery { + struct jz_battery_platform_data *pdata; + struct platform_device *pdev; + + void __iomem *base; + + int irq; + int charge_irq; + + const struct mfd_cell *cell; + + int status; + long voltage; + + struct completion read_completion; + + struct power_supply *battery; + struct power_supply_desc battery_desc; + struct delayed_work work; + + struct mutex lock; +}; + +static inline struct jz_battery *psy_to_jz_battery(struct power_supply *psy) +{ + return power_supply_get_drvdata(psy); +} + +static irqreturn_t jz_battery_irq_handler(int irq, void *devid) +{ + struct jz_battery *battery = devid; + + complete(&battery->read_completion); + return IRQ_HANDLED; +} + +static long jz_battery_read_voltage(struct jz_battery *battery) +{ + long t; + unsigned long val; + long voltage; + + mutex_lock(&battery->lock); + + reinit_completion(&battery->read_completion); + + enable_irq(battery->irq); + battery->cell->enable(battery->pdev); + + t = wait_for_completion_interruptible_timeout(&battery->read_completion, + HZ); + + if (t > 0) { + val = readw(battery->base) & 0xfff; + + if (battery->pdata->info.voltage_max_design <= 2500000) + val = (val * 78125UL) >> 7UL; + else + val = ((val * 924375UL) >> 9UL) + 33000; + voltage = (long)val; + } else { + voltage = t ? t : -ETIMEDOUT; + } + + battery->cell->disable(battery->pdev); + disable_irq(battery->irq); + + mutex_unlock(&battery->lock); + + return voltage; +} + +static int jz_battery_get_capacity(struct power_supply *psy) +{ + struct jz_battery *jz_battery = psy_to_jz_battery(psy); + struct power_supply_info *info = &jz_battery->pdata->info; + long voltage; + int ret; + int voltage_span; + + voltage = jz_battery_read_voltage(jz_battery); + + if (voltage < 0) + return voltage; + + voltage_span = info->voltage_max_design - info->voltage_min_design; + ret = ((voltage - info->voltage_min_design) * 100) / voltage_span; + + if (ret > 100) + ret = 100; + else if (ret < 0) + ret = 0; + + return ret; +} + +static int jz_battery_get_property(struct power_supply *psy, + enum power_supply_property psp, union power_supply_propval *val) +{ + struct jz_battery *jz_battery = psy_to_jz_battery(psy); + struct power_supply_info *info = &jz_battery->pdata->info; + long voltage; + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + val->intval = jz_battery->status; + break; + case POWER_SUPPLY_PROP_TECHNOLOGY: + val->intval = jz_battery->pdata->info.technology; + break; + case POWER_SUPPLY_PROP_HEALTH: + voltage = jz_battery_read_voltage(jz_battery); + if (voltage < info->voltage_min_design) + val->intval = POWER_SUPPLY_HEALTH_DEAD; + else + val->intval = POWER_SUPPLY_HEALTH_GOOD; + break; + case POWER_SUPPLY_PROP_CAPACITY: + val->intval = jz_battery_get_capacity(psy); + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + val->intval = jz_battery_read_voltage(jz_battery); + if (val->intval < 0) + return val->intval; + break; + case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: + val->intval = info->voltage_max_design; + break; + case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: + val->intval = info->voltage_min_design; + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = 1; + break; + default: + return -EINVAL; + } + return 0; +} + +static void jz_battery_external_power_changed(struct power_supply *psy) +{ + struct jz_battery *jz_battery = psy_to_jz_battery(psy); + + mod_delayed_work(system_wq, &jz_battery->work, 0); +} + +static irqreturn_t jz_battery_charge_irq(int irq, void *data) +{ + struct jz_battery *jz_battery = data; + + mod_delayed_work(system_wq, &jz_battery->work, 0); + + return IRQ_HANDLED; +} + +static void jz_battery_update(struct jz_battery *jz_battery) +{ + int status; + long voltage; + bool has_changed = false; + int is_charging; + + if (gpio_is_valid(jz_battery->pdata->gpio_charge)) { + is_charging = gpio_get_value(jz_battery->pdata->gpio_charge); + is_charging ^= jz_battery->pdata->gpio_charge_active_low; + if (is_charging) + status = POWER_SUPPLY_STATUS_CHARGING; + else + status = POWER_SUPPLY_STATUS_NOT_CHARGING; + + if (status != jz_battery->status) { + jz_battery->status = status; + has_changed = true; + } + } + + voltage = jz_battery_read_voltage(jz_battery); + if (voltage >= 0 && abs(voltage - jz_battery->voltage) > 50000) { + jz_battery->voltage = voltage; + has_changed = true; + } + + if (has_changed) + power_supply_changed(jz_battery->battery); +} + +static enum power_supply_property jz_battery_properties[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, + POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, + POWER_SUPPLY_PROP_PRESENT, +}; + +static void jz_battery_work(struct work_struct *work) +{ + /* Too small interval will increase system workload */ + const int interval = HZ * 30; + struct jz_battery *jz_battery = container_of(work, struct jz_battery, + work.work); + + jz_battery_update(jz_battery); + schedule_delayed_work(&jz_battery->work, interval); +} + +static int jz_battery_probe(struct platform_device *pdev) +{ + int ret = 0; + struct jz_battery_platform_data *pdata = pdev->dev.parent->platform_data; + struct power_supply_config psy_cfg = {}; + struct jz_battery *jz_battery; + struct power_supply_desc *battery_desc; + struct resource *mem; + + if (!pdata) { + dev_err(&pdev->dev, "No platform_data supplied\n"); + return -ENXIO; + } + + jz_battery = devm_kzalloc(&pdev->dev, sizeof(*jz_battery), GFP_KERNEL); + if (!jz_battery) { + dev_err(&pdev->dev, "Failed to allocate driver structure\n"); + return -ENOMEM; + } + + jz_battery->cell = mfd_get_cell(pdev); + + jz_battery->irq = platform_get_irq(pdev, 0); + if (jz_battery->irq < 0) { + dev_err(&pdev->dev, "Failed to get platform irq: %d\n", ret); + return jz_battery->irq; + } + + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + + jz_battery->base = devm_ioremap_resource(&pdev->dev, mem); + if (IS_ERR(jz_battery->base)) + return PTR_ERR(jz_battery->base); + + battery_desc = &jz_battery->battery_desc; + battery_desc->name = pdata->info.name; + battery_desc->type = POWER_SUPPLY_TYPE_BATTERY; + battery_desc->properties = jz_battery_properties; + battery_desc->num_properties = ARRAY_SIZE(jz_battery_properties); + battery_desc->get_property = jz_battery_get_property; + battery_desc->external_power_changed = + jz_battery_external_power_changed; + battery_desc->use_for_apm = 1; + + psy_cfg.drv_data = jz_battery; + + jz_battery->pdata = pdata; + jz_battery->pdev = pdev; + + init_completion(&jz_battery->read_completion); + mutex_init(&jz_battery->lock); + + INIT_DELAYED_WORK(&jz_battery->work, jz_battery_work); + + ret = request_irq(jz_battery->irq, jz_battery_irq_handler, 0, pdev->name, + jz_battery); + if (ret) { + dev_err(&pdev->dev, "Failed to request irq %d\n", ret); + return ret; + } + disable_irq(jz_battery->irq); + + if (gpio_is_valid(pdata->gpio_charge)) { + ret = gpio_request(pdata->gpio_charge, dev_name(&pdev->dev)); + if (ret) { + dev_err(&pdev->dev, "charger state gpio request failed.\n"); + goto err_free_irq; + } + ret = gpio_direction_input(pdata->gpio_charge); + if (ret) { + dev_err(&pdev->dev, "charger state gpio set direction failed.\n"); + goto err_free_gpio; + } + + jz_battery->charge_irq = gpio_to_irq(pdata->gpio_charge); + + if (jz_battery->charge_irq >= 0) { + ret = request_irq(jz_battery->charge_irq, + jz_battery_charge_irq, + IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, + dev_name(&pdev->dev), jz_battery); + if (ret) { + dev_err(&pdev->dev, "Failed to request charge irq: %d\n", ret); + goto err_free_gpio; + } + } + } else { + jz_battery->charge_irq = -1; + } + + if (jz_battery->pdata->info.voltage_max_design <= 2500000) + jz4740_adc_set_config(pdev->dev.parent, JZ_ADC_CONFIG_BAT_MB, + JZ_ADC_CONFIG_BAT_MB); + else + jz4740_adc_set_config(pdev->dev.parent, JZ_ADC_CONFIG_BAT_MB, 0); + + jz_battery->battery = power_supply_register(&pdev->dev, battery_desc, + &psy_cfg); + if (IS_ERR(jz_battery->battery)) { + dev_err(&pdev->dev, "power supply battery register failed.\n"); + ret = PTR_ERR(jz_battery->battery); + goto err_free_charge_irq; + } + + platform_set_drvdata(pdev, jz_battery); + schedule_delayed_work(&jz_battery->work, 0); + + return 0; + +err_free_charge_irq: + if (jz_battery->charge_irq >= 0) + free_irq(jz_battery->charge_irq, jz_battery); +err_free_gpio: + if (gpio_is_valid(pdata->gpio_charge)) + gpio_free(jz_battery->pdata->gpio_charge); +err_free_irq: + free_irq(jz_battery->irq, jz_battery); + return ret; +} + +static int jz_battery_remove(struct platform_device *pdev) +{ + struct jz_battery *jz_battery = platform_get_drvdata(pdev); + + cancel_delayed_work_sync(&jz_battery->work); + + if (gpio_is_valid(jz_battery->pdata->gpio_charge)) { + if (jz_battery->charge_irq >= 0) + free_irq(jz_battery->charge_irq, jz_battery); + gpio_free(jz_battery->pdata->gpio_charge); + } + + power_supply_unregister(jz_battery->battery); + + free_irq(jz_battery->irq, jz_battery); + + return 0; +} + +#ifdef CONFIG_PM +static int jz_battery_suspend(struct device *dev) +{ + struct jz_battery *jz_battery = dev_get_drvdata(dev); + + cancel_delayed_work_sync(&jz_battery->work); + jz_battery->status = POWER_SUPPLY_STATUS_UNKNOWN; + + return 0; +} + +static int jz_battery_resume(struct device *dev) +{ + struct jz_battery *jz_battery = dev_get_drvdata(dev); + + schedule_delayed_work(&jz_battery->work, 0); + + return 0; +} + +static const struct dev_pm_ops jz_battery_pm_ops = { + .suspend = jz_battery_suspend, + .resume = jz_battery_resume, +}; + +#define JZ_BATTERY_PM_OPS (&jz_battery_pm_ops) +#else +#define JZ_BATTERY_PM_OPS NULL +#endif + +static struct platform_driver jz_battery_driver = { + .probe = jz_battery_probe, + .remove = jz_battery_remove, + .driver = { + .name = "jz4740-battery", + .pm = JZ_BATTERY_PM_OPS, + }, +}; + +module_platform_driver(jz_battery_driver); + +MODULE_ALIAS("platform:jz4740-battery"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Lars-Peter Clausen "); +MODULE_DESCRIPTION("JZ4740 SoC battery driver"); diff --git a/drivers/power/supply/lp8727_charger.c b/drivers/power/supply/lp8727_charger.c new file mode 100644 index 000000000000..042fb3dacb46 --- /dev/null +++ b/drivers/power/supply/lp8727_charger.c @@ -0,0 +1,631 @@ +/* + * Driver for LP8727 Micro/Mini USB IC with integrated charger + * + * Copyright (C) 2011 Texas Instruments + * Copyright (C) 2011 National Semiconductor + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#define LP8788_NUM_INTREGS 2 +#define DEFAULT_DEBOUNCE_MSEC 270 + +/* Registers */ +#define LP8727_CTRL1 0x1 +#define LP8727_CTRL2 0x2 +#define LP8727_SWCTRL 0x3 +#define LP8727_INT1 0x4 +#define LP8727_INT2 0x5 +#define LP8727_STATUS1 0x6 +#define LP8727_STATUS2 0x7 +#define LP8727_CHGCTRL2 0x9 + +/* CTRL1 register */ +#define LP8727_CP_EN BIT(0) +#define LP8727_ADC_EN BIT(1) +#define LP8727_ID200_EN BIT(4) + +/* CTRL2 register */ +#define LP8727_CHGDET_EN BIT(1) +#define LP8727_INT_EN BIT(6) + +/* SWCTRL register */ +#define LP8727_SW_DM1_DM (0x0 << 0) +#define LP8727_SW_DM1_HiZ (0x7 << 0) +#define LP8727_SW_DP2_DP (0x0 << 3) +#define LP8727_SW_DP2_HiZ (0x7 << 3) + +/* INT1 register */ +#define LP8727_IDNO (0xF << 0) +#define LP8727_VBUS BIT(4) + +/* STATUS1 register */ +#define LP8727_CHGSTAT (3 << 4) +#define LP8727_CHPORT BIT(6) +#define LP8727_DCPORT BIT(7) +#define LP8727_STAT_EOC 0x30 + +/* STATUS2 register */ +#define LP8727_TEMP_STAT (3 << 5) +#define LP8727_TEMP_SHIFT 5 + +/* CHGCTRL2 register */ +#define LP8727_ICHG_SHIFT 4 + +enum lp8727_dev_id { + LP8727_ID_NONE, + LP8727_ID_TA, + LP8727_ID_DEDICATED_CHG, + LP8727_ID_USB_CHG, + LP8727_ID_USB_DS, + LP8727_ID_MAX, +}; + +enum lp8727_die_temp { + LP8788_TEMP_75C, + LP8788_TEMP_95C, + LP8788_TEMP_115C, + LP8788_TEMP_135C, +}; + +struct lp8727_psy { + struct power_supply *ac; + struct power_supply *usb; + struct power_supply *batt; +}; + +struct lp8727_chg { + struct device *dev; + struct i2c_client *client; + struct mutex xfer_lock; + struct lp8727_psy *psy; + struct lp8727_platform_data *pdata; + + /* Charger Data */ + enum lp8727_dev_id devid; + struct lp8727_chg_param *chg_param; + + /* Interrupt Handling */ + int irq; + struct delayed_work work; + unsigned long debounce_jiffies; +}; + +static int lp8727_read_bytes(struct lp8727_chg *pchg, u8 reg, u8 *data, u8 len) +{ + s32 ret; + + mutex_lock(&pchg->xfer_lock); + ret = i2c_smbus_read_i2c_block_data(pchg->client, reg, len, data); + mutex_unlock(&pchg->xfer_lock); + + return (ret != len) ? -EIO : 0; +} + +static inline int lp8727_read_byte(struct lp8727_chg *pchg, u8 reg, u8 *data) +{ + return lp8727_read_bytes(pchg, reg, data, 1); +} + +static int lp8727_write_byte(struct lp8727_chg *pchg, u8 reg, u8 data) +{ + int ret; + + mutex_lock(&pchg->xfer_lock); + ret = i2c_smbus_write_byte_data(pchg->client, reg, data); + mutex_unlock(&pchg->xfer_lock); + + return ret; +} + +static bool lp8727_is_charger_attached(const char *name, int id) +{ + if (!strcmp(name, "ac")) + return id == LP8727_ID_TA || id == LP8727_ID_DEDICATED_CHG; + else if (!strcmp(name, "usb")) + return id == LP8727_ID_USB_CHG; + + return id >= LP8727_ID_TA && id <= LP8727_ID_USB_CHG; +} + +static int lp8727_init_device(struct lp8727_chg *pchg) +{ + u8 val; + int ret; + u8 intstat[LP8788_NUM_INTREGS]; + + /* clear interrupts */ + ret = lp8727_read_bytes(pchg, LP8727_INT1, intstat, LP8788_NUM_INTREGS); + if (ret) + return ret; + + val = LP8727_ID200_EN | LP8727_ADC_EN | LP8727_CP_EN; + ret = lp8727_write_byte(pchg, LP8727_CTRL1, val); + if (ret) + return ret; + + val = LP8727_INT_EN | LP8727_CHGDET_EN; + return lp8727_write_byte(pchg, LP8727_CTRL2, val); +} + +static int lp8727_is_dedicated_charger(struct lp8727_chg *pchg) +{ + u8 val; + + lp8727_read_byte(pchg, LP8727_STATUS1, &val); + return val & LP8727_DCPORT; +} + +static int lp8727_is_usb_charger(struct lp8727_chg *pchg) +{ + u8 val; + + lp8727_read_byte(pchg, LP8727_STATUS1, &val); + return val & LP8727_CHPORT; +} + +static inline void lp8727_ctrl_switch(struct lp8727_chg *pchg, u8 sw) +{ + lp8727_write_byte(pchg, LP8727_SWCTRL, sw); +} + +static void lp8727_id_detection(struct lp8727_chg *pchg, u8 id, int vbusin) +{ + struct lp8727_platform_data *pdata = pchg->pdata; + u8 devid = LP8727_ID_NONE; + u8 swctrl = LP8727_SW_DM1_HiZ | LP8727_SW_DP2_HiZ; + + switch (id) { + case 0x5: + devid = LP8727_ID_TA; + pchg->chg_param = pdata ? pdata->ac : NULL; + break; + case 0xB: + if (lp8727_is_dedicated_charger(pchg)) { + pchg->chg_param = pdata ? pdata->ac : NULL; + devid = LP8727_ID_DEDICATED_CHG; + } else if (lp8727_is_usb_charger(pchg)) { + pchg->chg_param = pdata ? pdata->usb : NULL; + devid = LP8727_ID_USB_CHG; + swctrl = LP8727_SW_DM1_DM | LP8727_SW_DP2_DP; + } else if (vbusin) { + devid = LP8727_ID_USB_DS; + swctrl = LP8727_SW_DM1_DM | LP8727_SW_DP2_DP; + } + break; + default: + devid = LP8727_ID_NONE; + pchg->chg_param = NULL; + break; + } + + pchg->devid = devid; + lp8727_ctrl_switch(pchg, swctrl); +} + +static void lp8727_enable_chgdet(struct lp8727_chg *pchg) +{ + u8 val; + + lp8727_read_byte(pchg, LP8727_CTRL2, &val); + val |= LP8727_CHGDET_EN; + lp8727_write_byte(pchg, LP8727_CTRL2, val); +} + +static void lp8727_delayed_func(struct work_struct *_work) +{ + struct lp8727_chg *pchg = container_of(_work, struct lp8727_chg, + work.work); + u8 intstat[LP8788_NUM_INTREGS]; + u8 idno; + u8 vbus; + + if (lp8727_read_bytes(pchg, LP8727_INT1, intstat, LP8788_NUM_INTREGS)) { + dev_err(pchg->dev, "can not read INT registers\n"); + return; + } + + idno = intstat[0] & LP8727_IDNO; + vbus = intstat[0] & LP8727_VBUS; + + lp8727_id_detection(pchg, idno, vbus); + lp8727_enable_chgdet(pchg); + + power_supply_changed(pchg->psy->ac); + power_supply_changed(pchg->psy->usb); + power_supply_changed(pchg->psy->batt); +} + +static irqreturn_t lp8727_isr_func(int irq, void *ptr) +{ + struct lp8727_chg *pchg = ptr; + + schedule_delayed_work(&pchg->work, pchg->debounce_jiffies); + return IRQ_HANDLED; +} + +static int lp8727_setup_irq(struct lp8727_chg *pchg) +{ + int ret; + int irq = pchg->client->irq; + unsigned delay_msec = pchg->pdata ? pchg->pdata->debounce_msec : + DEFAULT_DEBOUNCE_MSEC; + + INIT_DELAYED_WORK(&pchg->work, lp8727_delayed_func); + + if (irq <= 0) { + dev_warn(pchg->dev, "invalid irq number: %d\n", irq); + return 0; + } + + ret = request_threaded_irq(irq, NULL, lp8727_isr_func, + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + "lp8727_irq", pchg); + + if (ret) + return ret; + + pchg->irq = irq; + pchg->debounce_jiffies = msecs_to_jiffies(delay_msec); + + return 0; +} + +static void lp8727_release_irq(struct lp8727_chg *pchg) +{ + cancel_delayed_work_sync(&pchg->work); + + if (pchg->irq) + free_irq(pchg->irq, pchg); +} + +static enum power_supply_property lp8727_charger_prop[] = { + POWER_SUPPLY_PROP_ONLINE, +}; + +static enum power_supply_property lp8727_battery_prop[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_TEMP, +}; + +static char *battery_supplied_to[] = { + "main_batt", +}; + +static int lp8727_charger_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct lp8727_chg *pchg = dev_get_drvdata(psy->dev.parent); + + if (psp != POWER_SUPPLY_PROP_ONLINE) + return -EINVAL; + + val->intval = lp8727_is_charger_attached(psy->desc->name, pchg->devid); + + return 0; +} + +static bool lp8727_is_high_temperature(enum lp8727_die_temp temp) +{ + switch (temp) { + case LP8788_TEMP_95C: + case LP8788_TEMP_115C: + case LP8788_TEMP_135C: + return true; + default: + return false; + } +} + +static int lp8727_battery_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct lp8727_chg *pchg = dev_get_drvdata(psy->dev.parent); + struct lp8727_platform_data *pdata = pchg->pdata; + enum lp8727_die_temp temp; + u8 read; + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + if (!lp8727_is_charger_attached(psy->desc->name, pchg->devid)) { + val->intval = POWER_SUPPLY_STATUS_DISCHARGING; + return 0; + } + + lp8727_read_byte(pchg, LP8727_STATUS1, &read); + + val->intval = (read & LP8727_CHGSTAT) == LP8727_STAT_EOC ? + POWER_SUPPLY_STATUS_FULL : + POWER_SUPPLY_STATUS_CHARGING; + break; + case POWER_SUPPLY_PROP_HEALTH: + lp8727_read_byte(pchg, LP8727_STATUS2, &read); + temp = (read & LP8727_TEMP_STAT) >> LP8727_TEMP_SHIFT; + + val->intval = lp8727_is_high_temperature(temp) ? + POWER_SUPPLY_HEALTH_OVERHEAT : + POWER_SUPPLY_HEALTH_GOOD; + break; + case POWER_SUPPLY_PROP_PRESENT: + if (!pdata) + return -EINVAL; + + if (pdata->get_batt_present) + val->intval = pdata->get_batt_present(); + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + if (!pdata) + return -EINVAL; + + if (pdata->get_batt_level) + val->intval = pdata->get_batt_level(); + break; + case POWER_SUPPLY_PROP_CAPACITY: + if (!pdata) + return -EINVAL; + + if (pdata->get_batt_capacity) + val->intval = pdata->get_batt_capacity(); + break; + case POWER_SUPPLY_PROP_TEMP: + if (!pdata) + return -EINVAL; + + if (pdata->get_batt_temp) + val->intval = pdata->get_batt_temp(); + break; + default: + break; + } + + return 0; +} + +static void lp8727_charger_changed(struct power_supply *psy) +{ + struct lp8727_chg *pchg = dev_get_drvdata(psy->dev.parent); + u8 eoc_level; + u8 ichg; + u8 val; + + /* skip if no charger exists */ + if (!lp8727_is_charger_attached(psy->desc->name, pchg->devid)) + return; + + /* update charging parameters */ + if (pchg->chg_param) { + eoc_level = pchg->chg_param->eoc_level; + ichg = pchg->chg_param->ichg; + val = (ichg << LP8727_ICHG_SHIFT) | eoc_level; + lp8727_write_byte(pchg, LP8727_CHGCTRL2, val); + } +} + +static const struct power_supply_desc lp8727_ac_desc = { + .name = "ac", + .type = POWER_SUPPLY_TYPE_MAINS, + .properties = lp8727_charger_prop, + .num_properties = ARRAY_SIZE(lp8727_charger_prop), + .get_property = lp8727_charger_get_property, +}; + +static const struct power_supply_desc lp8727_usb_desc = { + .name = "usb", + .type = POWER_SUPPLY_TYPE_USB, + .properties = lp8727_charger_prop, + .num_properties = ARRAY_SIZE(lp8727_charger_prop), + .get_property = lp8727_charger_get_property, +}; + +static const struct power_supply_desc lp8727_batt_desc = { + .name = "main_batt", + .type = POWER_SUPPLY_TYPE_BATTERY, + .properties = lp8727_battery_prop, + .num_properties = ARRAY_SIZE(lp8727_battery_prop), + .get_property = lp8727_battery_get_property, + .external_power_changed = lp8727_charger_changed, +}; + +static int lp8727_register_psy(struct lp8727_chg *pchg) +{ + struct power_supply_config psy_cfg = {}; /* Only for ac and usb */ + struct lp8727_psy *psy; + + psy = devm_kzalloc(pchg->dev, sizeof(*psy), GFP_KERNEL); + if (!psy) + return -ENOMEM; + + pchg->psy = psy; + + psy_cfg.supplied_to = battery_supplied_to; + psy_cfg.num_supplicants = ARRAY_SIZE(battery_supplied_to); + + psy->ac = power_supply_register(pchg->dev, &lp8727_ac_desc, &psy_cfg); + if (IS_ERR(psy->ac)) + goto err_psy_ac; + + psy->usb = power_supply_register(pchg->dev, &lp8727_usb_desc, + &psy_cfg); + if (IS_ERR(psy->usb)) + goto err_psy_usb; + + psy->batt = power_supply_register(pchg->dev, &lp8727_batt_desc, NULL); + if (IS_ERR(psy->batt)) + goto err_psy_batt; + + return 0; + +err_psy_batt: + power_supply_unregister(psy->usb); +err_psy_usb: + power_supply_unregister(psy->ac); +err_psy_ac: + return -EPERM; +} + +static void lp8727_unregister_psy(struct lp8727_chg *pchg) +{ + struct lp8727_psy *psy = pchg->psy; + + if (!psy) + return; + + power_supply_unregister(psy->ac); + power_supply_unregister(psy->usb); + power_supply_unregister(psy->batt); +} + +#ifdef CONFIG_OF +static struct lp8727_chg_param +*lp8727_parse_charge_pdata(struct device *dev, struct device_node *np) +{ + struct lp8727_chg_param *param; + + param = devm_kzalloc(dev, sizeof(*param), GFP_KERNEL); + if (!param) + goto out; + + of_property_read_u8(np, "eoc-level", (u8 *)¶m->eoc_level); + of_property_read_u8(np, "charging-current", (u8 *)¶m->ichg); +out: + return param; +} + +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; + + pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) + 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); + + if (!strcmp(type, "ac")) + pdata->ac = lp8727_parse_charge_pdata(dev, child); + + if (!strcmp(type, "usb")) + pdata->usb = lp8727_parse_charge_pdata(dev, child); + } + + return pdata; +} +#else +static struct lp8727_platform_data *lp8727_parse_dt(struct device *dev) +{ + 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) { + 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); + if (!pchg) + return -ENOMEM; + + pchg->client = cl; + pchg->dev = &cl->dev; + pchg->pdata = pdata; + i2c_set_clientdata(cl, pchg); + + mutex_init(&pchg->xfer_lock); + + ret = lp8727_init_device(pchg); + if (ret) { + dev_err(pchg->dev, "i2c communication err: %d", ret); + return ret; + } + + ret = lp8727_register_psy(pchg); + if (ret) { + dev_err(pchg->dev, "power supplies register err: %d", ret); + return ret; + } + + ret = lp8727_setup_irq(pchg); + if (ret) { + dev_err(pchg->dev, "irq handler err: %d", ret); + lp8727_unregister_psy(pchg); + return ret; + } + + return 0; +} + +static int lp8727_remove(struct i2c_client *cl) +{ + struct lp8727_chg *pchg = i2c_get_clientdata(cl); + + lp8727_release_irq(pchg); + lp8727_unregister_psy(pchg); + return 0; +} + +static const struct of_device_id lp8727_dt_ids[] = { + { .compatible = "ti,lp8727", }, + { } +}; +MODULE_DEVICE_TABLE(of, lp8727_dt_ids); + +static const struct i2c_device_id lp8727_ids[] = { + {"lp8727", 0}, + { } +}; +MODULE_DEVICE_TABLE(i2c, lp8727_ids); + +static struct i2c_driver lp8727_driver = { + .driver = { + .name = "lp8727", + .of_match_table = of_match_ptr(lp8727_dt_ids), + }, + .probe = lp8727_probe, + .remove = lp8727_remove, + .id_table = lp8727_ids, +}; +module_i2c_driver(lp8727_driver); + +MODULE_DESCRIPTION("TI/National Semiconductor LP8727 charger driver"); +MODULE_AUTHOR("Milo Kim , Daniel Jeong "); +MODULE_LICENSE("GPL"); diff --git a/drivers/power/supply/lp8788-charger.c b/drivers/power/supply/lp8788-charger.c new file mode 100644 index 000000000000..7321b727d484 --- /dev/null +++ b/drivers/power/supply/lp8788-charger.c @@ -0,0 +1,764 @@ +/* + * TI LP8788 MFD - battery charger driver + * + * Copyright 2012 Texas Instruments + * + * Author: Milo(Woogyom) Kim + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* register address */ +#define LP8788_CHG_STATUS 0x07 +#define LP8788_CHG_IDCIN 0x13 +#define LP8788_CHG_IBATT 0x14 +#define LP8788_CHG_VTERM 0x15 +#define LP8788_CHG_EOC 0x16 + +/* mask/shift bits */ +#define LP8788_CHG_INPUT_STATE_M 0x03 /* Addr 07h */ +#define LP8788_CHG_STATE_M 0x3C +#define LP8788_CHG_STATE_S 2 +#define LP8788_NO_BATT_M BIT(6) +#define LP8788_BAD_BATT_M BIT(7) +#define LP8788_CHG_IBATT_M 0x1F /* Addr 14h */ +#define LP8788_CHG_VTERM_M 0x0F /* Addr 15h */ +#define LP8788_CHG_EOC_LEVEL_M 0x30 /* Addr 16h */ +#define LP8788_CHG_EOC_LEVEL_S 4 +#define LP8788_CHG_EOC_TIME_M 0x0E +#define LP8788_CHG_EOC_TIME_S 1 +#define LP8788_CHG_EOC_MODE_M BIT(0) + +#define LP8788_CHARGER_NAME "charger" +#define LP8788_BATTERY_NAME "main_batt" + +#define LP8788_CHG_START 0x11 +#define LP8788_CHG_END 0x1C + +#define LP8788_ISEL_MAX 23 +#define LP8788_ISEL_STEP 50 +#define LP8788_VTERM_MIN 4100 +#define LP8788_VTERM_STEP 25 +#define LP8788_MAX_BATT_CAPACITY 100 +#define LP8788_MAX_CHG_IRQS 11 + +enum lp8788_charging_state { + LP8788_OFF, + LP8788_WARM_UP, + LP8788_LOW_INPUT = 0x3, + LP8788_PRECHARGE, + LP8788_CC, + LP8788_CV, + LP8788_MAINTENANCE, + LP8788_BATTERY_FAULT, + LP8788_SYSTEM_SUPPORT = 0xC, + LP8788_HIGH_CURRENT = 0xF, + LP8788_MAX_CHG_STATE, +}; + +enum lp8788_charger_adc_sel { + LP8788_VBATT, + LP8788_BATT_TEMP, + LP8788_NUM_CHG_ADC, +}; + +enum lp8788_charger_input_state { + LP8788_SYSTEM_SUPPLY = 1, + LP8788_FULL_FUNCTION, +}; + +/* + * struct lp8788_chg_irq + * @which : lp8788 interrupt id + * @virq : Linux IRQ number from irq_domain + */ +struct lp8788_chg_irq { + enum lp8788_int_id which; + int virq; +}; + +/* + * struct lp8788_charger + * @lp : used for accessing the registers of mfd lp8788 device + * @charger : power supply driver for the battery charger + * @battery : power supply driver for the battery + * @charger_work : work queue for charger input interrupts + * @chan : iio channels for getting adc values + * eg) battery voltage, capacity and temperature + * @irqs : charger dedicated interrupts + * @num_irqs : total numbers of charger interrupts + * @pdata : charger platform specific data + */ +struct lp8788_charger { + struct lp8788 *lp; + struct power_supply *charger; + struct power_supply *battery; + struct work_struct charger_work; + struct iio_channel *chan[LP8788_NUM_CHG_ADC]; + struct lp8788_chg_irq irqs[LP8788_MAX_CHG_IRQS]; + int num_irqs; + struct lp8788_charger_platform_data *pdata; +}; + +static char *battery_supplied_to[] = { + LP8788_BATTERY_NAME, +}; + +static enum power_supply_property lp8788_charger_prop[] = { + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_CURRENT_MAX, +}; + +static enum power_supply_property lp8788_battery_prop[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX, + POWER_SUPPLY_PROP_TEMP, +}; + +static bool lp8788_is_charger_detected(struct lp8788_charger *pchg) +{ + u8 data; + + lp8788_read_byte(pchg->lp, LP8788_CHG_STATUS, &data); + data &= LP8788_CHG_INPUT_STATE_M; + + return data == LP8788_SYSTEM_SUPPLY || data == LP8788_FULL_FUNCTION; +} + +static int lp8788_charger_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct lp8788_charger *pchg = dev_get_drvdata(psy->dev.parent); + u8 read; + + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + val->intval = lp8788_is_charger_detected(pchg); + break; + case POWER_SUPPLY_PROP_CURRENT_MAX: + lp8788_read_byte(pchg->lp, LP8788_CHG_IDCIN, &read); + val->intval = LP8788_ISEL_STEP * + (min_t(int, read, LP8788_ISEL_MAX) + 1); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int lp8788_get_battery_status(struct lp8788_charger *pchg, + union power_supply_propval *val) +{ + enum lp8788_charging_state state; + u8 data; + int ret; + + ret = lp8788_read_byte(pchg->lp, LP8788_CHG_STATUS, &data); + if (ret) + return ret; + + state = (data & LP8788_CHG_STATE_M) >> LP8788_CHG_STATE_S; + switch (state) { + case LP8788_OFF: + val->intval = POWER_SUPPLY_STATUS_DISCHARGING; + break; + case LP8788_PRECHARGE: + case LP8788_CC: + case LP8788_CV: + case LP8788_HIGH_CURRENT: + val->intval = POWER_SUPPLY_STATUS_CHARGING; + break; + case LP8788_MAINTENANCE: + val->intval = POWER_SUPPLY_STATUS_FULL; + break; + default: + val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; + break; + } + + return 0; +} + +static int lp8788_get_battery_health(struct lp8788_charger *pchg, + union power_supply_propval *val) +{ + u8 data; + int ret; + + ret = lp8788_read_byte(pchg->lp, LP8788_CHG_STATUS, &data); + if (ret) + return ret; + + if (data & LP8788_NO_BATT_M) + val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; + else if (data & LP8788_BAD_BATT_M) + val->intval = POWER_SUPPLY_HEALTH_DEAD; + else + val->intval = POWER_SUPPLY_HEALTH_GOOD; + + return 0; +} + +static int lp8788_get_battery_present(struct lp8788_charger *pchg, + union power_supply_propval *val) +{ + u8 data; + int ret; + + ret = lp8788_read_byte(pchg->lp, LP8788_CHG_STATUS, &data); + if (ret) + return ret; + + val->intval = !(data & LP8788_NO_BATT_M); + return 0; +} + +static int lp8788_get_vbatt_adc(struct lp8788_charger *pchg, int *result) +{ + struct iio_channel *channel = pchg->chan[LP8788_VBATT]; + + if (!channel) + return -EINVAL; + + return iio_read_channel_processed(channel, result); +} + +static int lp8788_get_battery_voltage(struct lp8788_charger *pchg, + union power_supply_propval *val) +{ + return lp8788_get_vbatt_adc(pchg, &val->intval); +} + +static int lp8788_get_battery_capacity(struct lp8788_charger *pchg, + union power_supply_propval *val) +{ + struct lp8788 *lp = pchg->lp; + struct lp8788_charger_platform_data *pdata = pchg->pdata; + unsigned int max_vbatt; + int vbatt; + enum lp8788_charging_state state; + u8 data; + int ret; + + if (!pdata) + return -EINVAL; + + max_vbatt = pdata->max_vbatt_mv; + if (max_vbatt == 0) + return -EINVAL; + + ret = lp8788_read_byte(lp, LP8788_CHG_STATUS, &data); + if (ret) + return ret; + + state = (data & LP8788_CHG_STATE_M) >> LP8788_CHG_STATE_S; + + if (state == LP8788_MAINTENANCE) { + val->intval = LP8788_MAX_BATT_CAPACITY; + } else { + ret = lp8788_get_vbatt_adc(pchg, &vbatt); + if (ret) + return ret; + + val->intval = (vbatt * LP8788_MAX_BATT_CAPACITY) / max_vbatt; + val->intval = min(val->intval, LP8788_MAX_BATT_CAPACITY); + } + + return 0; +} + +static int lp8788_get_battery_temperature(struct lp8788_charger *pchg, + union power_supply_propval *val) +{ + struct iio_channel *channel = pchg->chan[LP8788_BATT_TEMP]; + int result; + int ret; + + if (!channel) + return -EINVAL; + + ret = iio_read_channel_processed(channel, &result); + if (ret < 0) + return -EINVAL; + + /* unit: 0.1 'C */ + val->intval = result * 10; + + return 0; +} + +static int lp8788_get_battery_charging_current(struct lp8788_charger *pchg, + union power_supply_propval *val) +{ + u8 read; + + lp8788_read_byte(pchg->lp, LP8788_CHG_IBATT, &read); + read &= LP8788_CHG_IBATT_M; + val->intval = LP8788_ISEL_STEP * + (min_t(int, read, LP8788_ISEL_MAX) + 1); + + return 0; +} + +static int lp8788_get_charging_termination_voltage(struct lp8788_charger *pchg, + union power_supply_propval *val) +{ + u8 read; + + lp8788_read_byte(pchg->lp, LP8788_CHG_VTERM, &read); + read &= LP8788_CHG_VTERM_M; + val->intval = LP8788_VTERM_MIN + LP8788_VTERM_STEP * read; + + return 0; +} + +static int lp8788_battery_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct lp8788_charger *pchg = dev_get_drvdata(psy->dev.parent); + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + return lp8788_get_battery_status(pchg, val); + case POWER_SUPPLY_PROP_HEALTH: + return lp8788_get_battery_health(pchg, val); + case POWER_SUPPLY_PROP_PRESENT: + return lp8788_get_battery_present(pchg, val); + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + return lp8788_get_battery_voltage(pchg, val); + case POWER_SUPPLY_PROP_CAPACITY: + return lp8788_get_battery_capacity(pchg, val); + case POWER_SUPPLY_PROP_TEMP: + return lp8788_get_battery_temperature(pchg, val); + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: + return lp8788_get_battery_charging_current(pchg, val); + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX: + return lp8788_get_charging_termination_voltage(pchg, val); + default: + return -EINVAL; + } +} + +static inline bool lp8788_is_valid_charger_register(u8 addr) +{ + return addr >= LP8788_CHG_START && addr <= LP8788_CHG_END; +} + +static int lp8788_update_charger_params(struct platform_device *pdev, + struct lp8788_charger *pchg) +{ + struct lp8788 *lp = pchg->lp; + struct lp8788_charger_platform_data *pdata = pchg->pdata; + struct lp8788_chg_param *param; + int i; + int ret; + + if (!pdata || !pdata->chg_params) { + dev_info(&pdev->dev, "skip updating charger parameters\n"); + return 0; + } + + /* settting charging parameters */ + for (i = 0; i < pdata->num_chg_params; i++) { + param = pdata->chg_params + i; + + if (!param) + continue; + + if (lp8788_is_valid_charger_register(param->addr)) { + ret = lp8788_write_byte(lp, param->addr, param->val); + if (ret) + return ret; + } + } + + return 0; +} + +static const struct power_supply_desc lp8788_psy_charger_desc = { + .name = LP8788_CHARGER_NAME, + .type = POWER_SUPPLY_TYPE_MAINS, + .properties = lp8788_charger_prop, + .num_properties = ARRAY_SIZE(lp8788_charger_prop), + .get_property = lp8788_charger_get_property, +}; + +static const struct power_supply_desc lp8788_psy_battery_desc = { + .name = LP8788_BATTERY_NAME, + .type = POWER_SUPPLY_TYPE_BATTERY, + .properties = lp8788_battery_prop, + .num_properties = ARRAY_SIZE(lp8788_battery_prop), + .get_property = lp8788_battery_get_property, +}; + +static int lp8788_psy_register(struct platform_device *pdev, + struct lp8788_charger *pchg) +{ + struct power_supply_config charger_cfg = {}; + + charger_cfg.supplied_to = battery_supplied_to; + charger_cfg.num_supplicants = ARRAY_SIZE(battery_supplied_to); + + pchg->charger = power_supply_register(&pdev->dev, + &lp8788_psy_charger_desc, + &charger_cfg); + if (IS_ERR(pchg->charger)) + return -EPERM; + + pchg->battery = power_supply_register(&pdev->dev, + &lp8788_psy_battery_desc, NULL); + if (IS_ERR(pchg->battery)) { + power_supply_unregister(pchg->charger); + return -EPERM; + } + + return 0; +} + +static void lp8788_psy_unregister(struct lp8788_charger *pchg) +{ + power_supply_unregister(pchg->battery); + power_supply_unregister(pchg->charger); +} + +static void lp8788_charger_event(struct work_struct *work) +{ + struct lp8788_charger *pchg = + container_of(work, struct lp8788_charger, charger_work); + struct lp8788_charger_platform_data *pdata = pchg->pdata; + enum lp8788_charger_event event = lp8788_is_charger_detected(pchg); + + pdata->charger_event(pchg->lp, event); +} + +static bool lp8788_find_irq_id(struct lp8788_charger *pchg, int virq, int *id) +{ + bool found = false; + int i; + + for (i = 0; i < pchg->num_irqs; i++) { + if (pchg->irqs[i].virq == virq) { + *id = pchg->irqs[i].which; + found = true; + break; + } + } + + return found; +} + +static irqreturn_t lp8788_charger_irq_thread(int virq, void *ptr) +{ + struct lp8788_charger *pchg = ptr; + struct lp8788_charger_platform_data *pdata = pchg->pdata; + int id = -1; + + if (!lp8788_find_irq_id(pchg, virq, &id)) + return IRQ_NONE; + + switch (id) { + case LP8788_INT_CHG_INPUT_STATE: + case LP8788_INT_CHG_STATE: + case LP8788_INT_EOC: + case LP8788_INT_BATT_LOW: + case LP8788_INT_NO_BATT: + power_supply_changed(pchg->charger); + power_supply_changed(pchg->battery); + break; + default: + break; + } + + /* report charger dectection event if used */ + if (!pdata) + goto irq_handled; + + if (pdata->charger_event && id == LP8788_INT_CHG_INPUT_STATE) + schedule_work(&pchg->charger_work); + +irq_handled: + return IRQ_HANDLED; +} + +static int lp8788_set_irqs(struct platform_device *pdev, + struct lp8788_charger *pchg, const char *name) +{ + struct resource *r; + struct irq_domain *irqdm = pchg->lp->irqdm; + int irq_start; + int irq_end; + int virq; + int nr_irq; + int i; + int ret; + + /* no error even if no irq resource */ + r = platform_get_resource_byname(pdev, IORESOURCE_IRQ, name); + if (!r) + return 0; + + irq_start = r->start; + irq_end = r->end; + + for (i = irq_start; i <= irq_end; i++) { + nr_irq = pchg->num_irqs; + + virq = irq_create_mapping(irqdm, i); + pchg->irqs[nr_irq].virq = virq; + pchg->irqs[nr_irq].which = i; + pchg->num_irqs++; + + ret = request_threaded_irq(virq, NULL, + lp8788_charger_irq_thread, + 0, name, pchg); + if (ret) + break; + } + + if (i <= irq_end) + goto err_free_irq; + + return 0; + +err_free_irq: + for (i = 0; i < pchg->num_irqs; i++) + free_irq(pchg->irqs[i].virq, pchg); + return ret; +} + +static int lp8788_irq_register(struct platform_device *pdev, + struct lp8788_charger *pchg) +{ + const char *name[] = { + LP8788_CHG_IRQ, LP8788_PRSW_IRQ, LP8788_BATT_IRQ + }; + int i; + int ret; + + INIT_WORK(&pchg->charger_work, lp8788_charger_event); + pchg->num_irqs = 0; + + for (i = 0; i < ARRAY_SIZE(name); i++) { + ret = lp8788_set_irqs(pdev, pchg, name[i]); + if (ret) { + dev_warn(&pdev->dev, "irq setup failed: %s\n", name[i]); + return ret; + } + } + + if (pchg->num_irqs > LP8788_MAX_CHG_IRQS) { + dev_err(&pdev->dev, "invalid total number of irqs: %d\n", + pchg->num_irqs); + return -EINVAL; + } + + + return 0; +} + +static void lp8788_irq_unregister(struct platform_device *pdev, + struct lp8788_charger *pchg) +{ + int i; + int irq; + + for (i = 0; i < pchg->num_irqs; i++) { + irq = pchg->irqs[i].virq; + if (!irq) + continue; + + free_irq(irq, pchg); + } +} + +static void lp8788_setup_adc_channel(struct device *dev, + struct lp8788_charger *pchg) +{ + struct lp8788_charger_platform_data *pdata = pchg->pdata; + struct iio_channel *chan; + + if (!pdata) + return; + + /* ADC channel for battery voltage */ + chan = iio_channel_get(dev, pdata->adc_vbatt); + pchg->chan[LP8788_VBATT] = IS_ERR(chan) ? NULL : chan; + + /* ADC channel for battery temperature */ + chan = iio_channel_get(dev, pdata->adc_batt_temp); + pchg->chan[LP8788_BATT_TEMP] = IS_ERR(chan) ? NULL : chan; +} + +static void lp8788_release_adc_channel(struct lp8788_charger *pchg) +{ + int i; + + for (i = 0; i < LP8788_NUM_CHG_ADC; i++) { + if (!pchg->chan[i]) + continue; + + iio_channel_release(pchg->chan[i]); + pchg->chan[i] = NULL; + } +} + +static ssize_t lp8788_show_charger_status(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct lp8788_charger *pchg = dev_get_drvdata(dev); + enum lp8788_charging_state state; + char *desc[LP8788_MAX_CHG_STATE] = { + [LP8788_OFF] = "CHARGER OFF", + [LP8788_WARM_UP] = "WARM UP", + [LP8788_LOW_INPUT] = "LOW INPUT STATE", + [LP8788_PRECHARGE] = "CHARGING - PRECHARGE", + [LP8788_CC] = "CHARGING - CC", + [LP8788_CV] = "CHARGING - CV", + [LP8788_MAINTENANCE] = "NO CHARGING - MAINTENANCE", + [LP8788_BATTERY_FAULT] = "BATTERY FAULT", + [LP8788_SYSTEM_SUPPORT] = "SYSTEM SUPPORT", + [LP8788_HIGH_CURRENT] = "HIGH CURRENT", + }; + u8 data; + + lp8788_read_byte(pchg->lp, LP8788_CHG_STATUS, &data); + state = (data & LP8788_CHG_STATE_M) >> LP8788_CHG_STATE_S; + + return scnprintf(buf, PAGE_SIZE, "%s\n", desc[state]); +} + +static ssize_t lp8788_show_eoc_time(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct lp8788_charger *pchg = dev_get_drvdata(dev); + char *stime[] = { "400ms", "5min", "10min", "15min", + "20min", "25min", "30min" "No timeout" }; + u8 val; + + lp8788_read_byte(pchg->lp, LP8788_CHG_EOC, &val); + val = (val & LP8788_CHG_EOC_TIME_M) >> LP8788_CHG_EOC_TIME_S; + + return scnprintf(buf, PAGE_SIZE, "End Of Charge Time: %s\n", + stime[val]); +} + +static ssize_t lp8788_show_eoc_level(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct lp8788_charger *pchg = dev_get_drvdata(dev); + char *abs_level[] = { "25mA", "49mA", "75mA", "98mA" }; + char *relative_level[] = { "5%", "10%", "15%", "20%" }; + char *level; + u8 val; + u8 mode; + + lp8788_read_byte(pchg->lp, LP8788_CHG_EOC, &val); + + mode = val & LP8788_CHG_EOC_MODE_M; + val = (val & LP8788_CHG_EOC_LEVEL_M) >> LP8788_CHG_EOC_LEVEL_S; + level = mode ? abs_level[val] : relative_level[val]; + + return scnprintf(buf, PAGE_SIZE, "End Of Charge Level: %s\n", level); +} + +static DEVICE_ATTR(charger_status, S_IRUSR, lp8788_show_charger_status, NULL); +static DEVICE_ATTR(eoc_time, S_IRUSR, lp8788_show_eoc_time, NULL); +static DEVICE_ATTR(eoc_level, S_IRUSR, lp8788_show_eoc_level, NULL); + +static struct attribute *lp8788_charger_attr[] = { + &dev_attr_charger_status.attr, + &dev_attr_eoc_time.attr, + &dev_attr_eoc_level.attr, + NULL, +}; + +static const struct attribute_group lp8788_attr_group = { + .attrs = lp8788_charger_attr, +}; + +static int lp8788_charger_probe(struct platform_device *pdev) +{ + struct lp8788 *lp = dev_get_drvdata(pdev->dev.parent); + struct lp8788_charger *pchg; + struct device *dev = &pdev->dev; + int ret; + + pchg = devm_kzalloc(dev, sizeof(struct lp8788_charger), GFP_KERNEL); + if (!pchg) + return -ENOMEM; + + pchg->lp = lp; + pchg->pdata = lp->pdata ? lp->pdata->chg_pdata : NULL; + platform_set_drvdata(pdev, pchg); + + ret = lp8788_update_charger_params(pdev, pchg); + if (ret) + return ret; + + lp8788_setup_adc_channel(&pdev->dev, pchg); + + ret = lp8788_psy_register(pdev, pchg); + if (ret) + return ret; + + ret = sysfs_create_group(&pdev->dev.kobj, &lp8788_attr_group); + if (ret) { + lp8788_psy_unregister(pchg); + return ret; + } + + ret = lp8788_irq_register(pdev, pchg); + if (ret) + dev_warn(dev, "failed to register charger irq: %d\n", ret); + + return 0; +} + +static int lp8788_charger_remove(struct platform_device *pdev) +{ + struct lp8788_charger *pchg = platform_get_drvdata(pdev); + + flush_work(&pchg->charger_work); + lp8788_irq_unregister(pdev, pchg); + sysfs_remove_group(&pdev->dev.kobj, &lp8788_attr_group); + lp8788_psy_unregister(pchg); + lp8788_release_adc_channel(pchg); + + return 0; +} + +static struct platform_driver lp8788_charger_driver = { + .probe = lp8788_charger_probe, + .remove = lp8788_charger_remove, + .driver = { + .name = LP8788_DEV_CHARGER, + }, +}; +module_platform_driver(lp8788_charger_driver); + +MODULE_DESCRIPTION("TI LP8788 Charger Driver"); +MODULE_AUTHOR("Milo Kim"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:lp8788-charger"); diff --git a/drivers/power/supply/ltc2941-battery-gauge.c b/drivers/power/supply/ltc2941-battery-gauge.c new file mode 100644 index 000000000000..4adf2ba021ce --- /dev/null +++ b/drivers/power/supply/ltc2941-battery-gauge.c @@ -0,0 +1,514 @@ +/* + * I2C client/driver for the Linear Technology LTC2941 and LTC2943 + * Battery Gas Gauge IC + * + * Copyright (C) 2014 Topic Embedded Systems + * + * Author: Auryn Verwegen + * Author: Mike Looijmans + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define I16_MSB(x) ((x >> 8) & 0xFF) +#define I16_LSB(x) (x & 0xFF) + +#define LTC294X_WORK_DELAY 10 /* Update delay in seconds */ + +#define LTC294X_MAX_VALUE 0xFFFF +#define LTC294X_MID_SUPPLY 0x7FFF + +#define LTC2941_MAX_PRESCALER_EXP 7 +#define LTC2943_MAX_PRESCALER_EXP 6 + +enum ltc294x_reg { + LTC294X_REG_STATUS = 0x00, + LTC294X_REG_CONTROL = 0x01, + LTC294X_REG_ACC_CHARGE_MSB = 0x02, + LTC294X_REG_ACC_CHARGE_LSB = 0x03, + LTC294X_REG_THRESH_HIGH_MSB = 0x04, + LTC294X_REG_THRESH_HIGH_LSB = 0x05, + LTC294X_REG_THRESH_LOW_MSB = 0x06, + LTC294X_REG_THRESH_LOW_LSB = 0x07, + LTC294X_REG_VOLTAGE_MSB = 0x08, + LTC294X_REG_VOLTAGE_LSB = 0x09, + LTC294X_REG_CURRENT_MSB = 0x0E, + LTC294X_REG_CURRENT_LSB = 0x0F, + LTC294X_REG_TEMPERATURE_MSB = 0x14, + LTC294X_REG_TEMPERATURE_LSB = 0x15, +}; + +#define LTC2943_REG_CONTROL_MODE_MASK (BIT(7) | BIT(6)) +#define LTC2943_REG_CONTROL_MODE_SCAN BIT(7) +#define LTC294X_REG_CONTROL_PRESCALER_MASK (BIT(5) | BIT(4) | BIT(3)) +#define LTC294X_REG_CONTROL_SHUTDOWN_MASK (BIT(0)) +#define LTC294X_REG_CONTROL_PRESCALER_SET(x) \ + ((x << 3) & LTC294X_REG_CONTROL_PRESCALER_MASK) +#define LTC294X_REG_CONTROL_ALCC_CONFIG_DISABLED 0 + +#define LTC2941_NUM_REGS 0x08 +#define LTC2943_NUM_REGS 0x18 + +struct ltc294x_info { + struct i2c_client *client; /* I2C Client pointer */ + struct power_supply *supply; /* Supply pointer */ + struct power_supply_desc supply_desc; /* Supply description */ + struct delayed_work work; /* Work scheduler */ + int num_regs; /* Number of registers (chip type) */ + int charge; /* Last charge register content */ + int r_sense; /* mOhm */ + int Qlsb; /* nAh */ +}; + +static inline int convert_bin_to_uAh( + const struct ltc294x_info *info, int Q) +{ + return ((Q * (info->Qlsb / 10))) / 100; +} + +static inline int convert_uAh_to_bin( + const struct ltc294x_info *info, int uAh) +{ + int Q; + + Q = (uAh * 100) / (info->Qlsb/10); + return (Q < LTC294X_MAX_VALUE) ? Q : LTC294X_MAX_VALUE; +} + +static int ltc294x_read_regs(struct i2c_client *client, + enum ltc294x_reg reg, u8 *buf, int num_regs) +{ + int ret; + struct i2c_msg msgs[2] = { }; + u8 reg_start = reg; + + msgs[0].addr = client->addr; + msgs[0].len = 1; + msgs[0].buf = ®_start; + + msgs[1].addr = client->addr; + msgs[1].len = num_regs; + msgs[1].buf = buf; + msgs[1].flags = I2C_M_RD; + + ret = i2c_transfer(client->adapter, &msgs[0], 2); + if (ret < 0) { + dev_err(&client->dev, "ltc2941 read_reg failed!\n"); + return ret; + } + + dev_dbg(&client->dev, "%s (%#x, %d) -> %#x\n", + __func__, reg, num_regs, *buf); + + return 0; +} + +static int ltc294x_write_regs(struct i2c_client *client, + enum ltc294x_reg reg, const u8 *buf, int num_regs) +{ + int ret; + u8 reg_start = reg; + + ret = i2c_smbus_write_i2c_block_data(client, reg_start, num_regs, buf); + if (ret < 0) { + dev_err(&client->dev, "ltc2941 write_reg failed!\n"); + return ret; + } + + dev_dbg(&client->dev, "%s (%#x, %d) -> %#x\n", + __func__, reg, num_regs, *buf); + + return 0; +} + +static int ltc294x_reset(const struct ltc294x_info *info, int prescaler_exp) +{ + int ret; + u8 value; + u8 control; + + /* Read status and control registers */ + ret = ltc294x_read_regs(info->client, LTC294X_REG_CONTROL, &value, 1); + if (ret < 0) { + dev_err(&info->client->dev, + "Could not read registers from device\n"); + goto error_exit; + } + + control = LTC294X_REG_CONTROL_PRESCALER_SET(prescaler_exp) | + LTC294X_REG_CONTROL_ALCC_CONFIG_DISABLED; + /* Put the 2943 into "monitor" mode, so it measures every 10 sec */ + if (info->num_regs == LTC2943_NUM_REGS) + control |= LTC2943_REG_CONTROL_MODE_SCAN; + + if (value != control) { + ret = ltc294x_write_regs(info->client, + LTC294X_REG_CONTROL, &control, 1); + if (ret < 0) { + dev_err(&info->client->dev, + "Could not write register\n"); + goto error_exit; + } + } + + return 0; + +error_exit: + return ret; +} + +static int ltc294x_read_charge_register(const struct ltc294x_info *info) +{ + int ret; + u8 datar[2]; + + ret = ltc294x_read_regs(info->client, + LTC294X_REG_ACC_CHARGE_MSB, &datar[0], 2); + if (ret < 0) + return ret; + return (datar[0] << 8) + datar[1]; +} + +static int ltc294x_get_charge_now(const struct ltc294x_info *info, int *val) +{ + int value = ltc294x_read_charge_register(info); + + if (value < 0) + return value; + /* When r_sense < 0, this counts up when the battery discharges */ + if (info->Qlsb < 0) + value -= 0xFFFF; + *val = convert_bin_to_uAh(info, value); + return 0; +} + +static int ltc294x_set_charge_now(const struct ltc294x_info *info, int val) +{ + int ret; + u8 dataw[2]; + u8 ctrl_reg; + s32 value; + + value = convert_uAh_to_bin(info, val); + /* Direction depends on how sense+/- were connected */ + if (info->Qlsb < 0) + value += 0xFFFF; + if ((value < 0) || (value > 0xFFFF)) /* input validation */ + return -EINVAL; + + /* Read control register */ + ret = ltc294x_read_regs(info->client, + LTC294X_REG_CONTROL, &ctrl_reg, 1); + if (ret < 0) + return ret; + /* Disable analog section */ + ctrl_reg |= LTC294X_REG_CONTROL_SHUTDOWN_MASK; + ret = ltc294x_write_regs(info->client, + LTC294X_REG_CONTROL, &ctrl_reg, 1); + if (ret < 0) + return ret; + /* Set new charge value */ + dataw[0] = I16_MSB(value); + dataw[1] = I16_LSB(value); + ret = ltc294x_write_regs(info->client, + LTC294X_REG_ACC_CHARGE_MSB, &dataw[0], 2); + if (ret < 0) + goto error_exit; + /* Enable analog section */ +error_exit: + ctrl_reg &= ~LTC294X_REG_CONTROL_SHUTDOWN_MASK; + ret = ltc294x_write_regs(info->client, + LTC294X_REG_CONTROL, &ctrl_reg, 1); + + return ret < 0 ? ret : 0; +} + +static int ltc294x_get_charge_counter( + const struct ltc294x_info *info, int *val) +{ + int value = ltc294x_read_charge_register(info); + + if (value < 0) + return value; + value -= LTC294X_MID_SUPPLY; + *val = convert_bin_to_uAh(info, value); + return 0; +} + +static int ltc294x_get_voltage(const struct ltc294x_info *info, int *val) +{ + int ret; + u8 datar[2]; + u32 value; + + ret = ltc294x_read_regs(info->client, + LTC294X_REG_VOLTAGE_MSB, &datar[0], 2); + value = (datar[0] << 8) | datar[1]; + *val = ((value * 23600) / 0xFFFF) * 1000; /* in uV */ + return ret; +} + +static int ltc294x_get_current(const struct ltc294x_info *info, int *val) +{ + int ret; + u8 datar[2]; + s32 value; + + ret = ltc294x_read_regs(info->client, + LTC294X_REG_CURRENT_MSB, &datar[0], 2); + value = (datar[0] << 8) | datar[1]; + value -= 0x7FFF; + /* Value is in range -32k..+32k, r_sense is usually 10..50 mOhm, + * the formula below keeps everything in s32 range while preserving + * enough digits */ + *val = 1000 * ((60000 * value) / (info->r_sense * 0x7FFF)); /* in uA */ + return ret; +} + +static int ltc294x_get_temperature(const struct ltc294x_info *info, int *val) +{ + int ret; + u8 datar[2]; + u32 value; + + ret = ltc294x_read_regs(info->client, + LTC294X_REG_TEMPERATURE_MSB, &datar[0], 2); + value = (datar[0] << 8) | datar[1]; + /* Full-scale is 510 Kelvin, convert to centidegrees */ + *val = (((51000 * value) / 0xFFFF) - 27215); + return ret; +} + +static int ltc294x_get_property(struct power_supply *psy, + enum power_supply_property prop, + union power_supply_propval *val) +{ + struct ltc294x_info *info = power_supply_get_drvdata(psy); + + switch (prop) { + case POWER_SUPPLY_PROP_CHARGE_NOW: + return ltc294x_get_charge_now(info, &val->intval); + case POWER_SUPPLY_PROP_CHARGE_COUNTER: + return ltc294x_get_charge_counter(info, &val->intval); + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + return ltc294x_get_voltage(info, &val->intval); + case POWER_SUPPLY_PROP_CURRENT_NOW: + return ltc294x_get_current(info, &val->intval); + case POWER_SUPPLY_PROP_TEMP: + return ltc294x_get_temperature(info, &val->intval); + default: + return -EINVAL; + } +} + +static int ltc294x_set_property(struct power_supply *psy, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct ltc294x_info *info = power_supply_get_drvdata(psy); + + switch (psp) { + case POWER_SUPPLY_PROP_CHARGE_NOW: + return ltc294x_set_charge_now(info, val->intval); + default: + return -EPERM; + } +} + +static int ltc294x_property_is_writeable( + struct power_supply *psy, enum power_supply_property psp) +{ + switch (psp) { + case POWER_SUPPLY_PROP_CHARGE_NOW: + return 1; + default: + return 0; + } +} + +static void ltc294x_update(struct ltc294x_info *info) +{ + int charge = ltc294x_read_charge_register(info); + + if (charge != info->charge) { + info->charge = charge; + power_supply_changed(info->supply); + } +} + +static void ltc294x_work(struct work_struct *work) +{ + struct ltc294x_info *info; + + info = container_of(work, struct ltc294x_info, work.work); + ltc294x_update(info); + schedule_delayed_work(&info->work, LTC294X_WORK_DELAY * HZ); +} + +static enum power_supply_property ltc294x_properties[] = { + POWER_SUPPLY_PROP_CHARGE_COUNTER, + POWER_SUPPLY_PROP_CHARGE_NOW, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_TEMP, +}; + +static int ltc294x_i2c_remove(struct i2c_client *client) +{ + struct ltc294x_info *info = i2c_get_clientdata(client); + + cancel_delayed_work(&info->work); + power_supply_unregister(info->supply); + return 0; +} + +static int ltc294x_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct power_supply_config psy_cfg = {}; + struct ltc294x_info *info; + int ret; + u32 prescaler_exp; + s32 r_sense; + struct device_node *np; + + info = devm_kzalloc(&client->dev, sizeof(*info), GFP_KERNEL); + if (info == NULL) + return -ENOMEM; + + i2c_set_clientdata(client, info); + + np = of_node_get(client->dev.of_node); + + info->num_regs = id->driver_data; + info->supply_desc.name = np->name; + + /* r_sense can be negative, when sense+ is connected to the battery + * instead of the sense-. This results in reversed measurements. */ + ret = of_property_read_u32(np, "lltc,resistor-sense", &r_sense); + if (ret < 0) { + dev_err(&client->dev, + "Could not find lltc,resistor-sense in devicetree\n"); + return ret; + } + info->r_sense = r_sense; + + ret = of_property_read_u32(np, "lltc,prescaler-exponent", + &prescaler_exp); + if (ret < 0) { + dev_warn(&client->dev, + "lltc,prescaler-exponent not in devicetree\n"); + prescaler_exp = LTC2941_MAX_PRESCALER_EXP; + } + + if (info->num_regs == LTC2943_NUM_REGS) { + if (prescaler_exp > LTC2943_MAX_PRESCALER_EXP) + prescaler_exp = LTC2943_MAX_PRESCALER_EXP; + info->Qlsb = ((340 * 50000) / r_sense) / + (4096 / (1 << (2*prescaler_exp))); + } else { + if (prescaler_exp > LTC2941_MAX_PRESCALER_EXP) + prescaler_exp = LTC2941_MAX_PRESCALER_EXP; + info->Qlsb = ((85 * 50000) / r_sense) / + (128 / (1 << prescaler_exp)); + } + + info->client = client; + info->supply_desc.type = POWER_SUPPLY_TYPE_BATTERY; + info->supply_desc.properties = ltc294x_properties; + if (info->num_regs >= LTC294X_REG_TEMPERATURE_LSB) + info->supply_desc.num_properties = + ARRAY_SIZE(ltc294x_properties); + else if (info->num_regs >= LTC294X_REG_CURRENT_LSB) + info->supply_desc.num_properties = + ARRAY_SIZE(ltc294x_properties) - 1; + else if (info->num_regs >= LTC294X_REG_VOLTAGE_LSB) + info->supply_desc.num_properties = + ARRAY_SIZE(ltc294x_properties) - 2; + else + info->supply_desc.num_properties = + ARRAY_SIZE(ltc294x_properties) - 3; + info->supply_desc.get_property = ltc294x_get_property; + info->supply_desc.set_property = ltc294x_set_property; + info->supply_desc.property_is_writeable = ltc294x_property_is_writeable; + info->supply_desc.external_power_changed = NULL; + + psy_cfg.drv_data = info; + + INIT_DELAYED_WORK(&info->work, ltc294x_work); + + ret = ltc294x_reset(info, prescaler_exp); + if (ret < 0) { + dev_err(&client->dev, "Communication with chip failed\n"); + return ret; + } + + info->supply = power_supply_register(&client->dev, &info->supply_desc, + &psy_cfg); + if (IS_ERR(info->supply)) { + dev_err(&client->dev, "failed to register ltc2941\n"); + return PTR_ERR(info->supply); + } else { + schedule_delayed_work(&info->work, LTC294X_WORK_DELAY * HZ); + } + + return 0; +} + +#ifdef CONFIG_PM_SLEEP + +static int ltc294x_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct ltc294x_info *info = i2c_get_clientdata(client); + + cancel_delayed_work(&info->work); + return 0; +} + +static int ltc294x_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct ltc294x_info *info = i2c_get_clientdata(client); + + schedule_delayed_work(&info->work, LTC294X_WORK_DELAY * HZ); + return 0; +} + +static SIMPLE_DEV_PM_OPS(ltc294x_pm_ops, ltc294x_suspend, ltc294x_resume); +#define LTC294X_PM_OPS (<c294x_pm_ops) + +#else +#define LTC294X_PM_OPS NULL +#endif /* CONFIG_PM_SLEEP */ + + +static const struct i2c_device_id ltc294x_i2c_id[] = { + {"ltc2941", LTC2941_NUM_REGS}, + {"ltc2943", LTC2943_NUM_REGS}, + { }, +}; +MODULE_DEVICE_TABLE(i2c, ltc294x_i2c_id); + +static struct i2c_driver ltc294x_driver = { + .driver = { + .name = "LTC2941", + .pm = LTC294X_PM_OPS, + }, + .probe = ltc294x_i2c_probe, + .remove = ltc294x_i2c_remove, + .id_table = ltc294x_i2c_id, +}; +module_i2c_driver(ltc294x_driver); + +MODULE_AUTHOR("Auryn Verwegen, Topic Embedded Systems"); +MODULE_AUTHOR("Mike Looijmans, Topic Embedded Products"); +MODULE_DESCRIPTION("LTC2941/LTC2943 Battery Gas Gauge IC driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/power/supply/max14577_charger.c b/drivers/power/supply/max14577_charger.c new file mode 100644 index 000000000000..a36bcaf62dd4 --- /dev/null +++ b/drivers/power/supply/max14577_charger.c @@ -0,0 +1,648 @@ +/* + * max14577_charger.c - Battery charger driver for the Maxim 14577/77836 + * + * Copyright (C) 2013,2014 Samsung Electronics + * Krzysztof Kozlowski + * + * 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 program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include + +struct max14577_charger { + struct device *dev; + struct max14577 *max14577; + struct power_supply *charger; + + struct max14577_charger_platform_data *pdata; +}; + +/* + * Helper function for mapping values of STATUS2/CHGTYP register on max14577 + * and max77836 chipsets to enum maxim_muic_charger_type. + */ +static enum max14577_muic_charger_type maxim_get_charger_type( + enum maxim_device_type dev_type, u8 val) { + switch (val) { + case MAX14577_CHARGER_TYPE_NONE: + case MAX14577_CHARGER_TYPE_USB: + case MAX14577_CHARGER_TYPE_DOWNSTREAM_PORT: + case MAX14577_CHARGER_TYPE_DEDICATED_CHG: + case MAX14577_CHARGER_TYPE_SPECIAL_500MA: + case MAX14577_CHARGER_TYPE_SPECIAL_1A: + return val; + case MAX14577_CHARGER_TYPE_DEAD_BATTERY: + case MAX14577_CHARGER_TYPE_RESERVED: + if (dev_type == MAXIM_DEVICE_TYPE_MAX77836) + val |= 0x8; + return val; + default: + WARN_ONCE(1, "max14577: Unsupported chgtyp register value 0x%02x", val); + return val; + } +} + +static int max14577_get_charger_state(struct max14577_charger *chg, int *val) +{ + struct regmap *rmap = chg->max14577->regmap; + int ret; + u8 reg_data; + + /* + * Charging occurs only if: + * - CHGCTRL2/MBCHOSTEN == 1 + * - STATUS2/CGMBC == 1 + * + * TODO: + * - handle FULL after Top-off timer (EOC register may be off + * and the charger won't be charging although MBCHOSTEN is on) + * - handle properly dead-battery charging (respect timer) + * - handle timers (fast-charge and prequal) /MBCCHGERR/ + */ + ret = max14577_read_reg(rmap, MAX14577_CHG_REG_CHG_CTRL2, ®_data); + if (ret < 0) + goto out; + + if ((reg_data & CHGCTRL2_MBCHOSTEN_MASK) == 0) { + *val = POWER_SUPPLY_STATUS_DISCHARGING; + goto out; + } + + ret = max14577_read_reg(rmap, MAX14577_CHG_REG_STATUS3, ®_data); + if (ret < 0) + goto out; + + if (reg_data & STATUS3_CGMBC_MASK) { + /* Charger or USB-cable is connected */ + if (reg_data & STATUS3_EOC_MASK) + *val = POWER_SUPPLY_STATUS_FULL; + else + *val = POWER_SUPPLY_STATUS_CHARGING; + goto out; + } + + *val = POWER_SUPPLY_STATUS_DISCHARGING; + +out: + return ret; +} + +/* + * Supported charge types: + * - POWER_SUPPLY_CHARGE_TYPE_NONE + * - POWER_SUPPLY_CHARGE_TYPE_FAST + */ +static int max14577_get_charge_type(struct max14577_charger *chg, int *val) +{ + int ret, charging; + + /* + * TODO: CHARGE_TYPE_TRICKLE (VCHGR_RC or EOC)? + * As spec says: + * [after reaching EOC interrupt] + * "When the battery is fully charged, the 30-minute (typ) + * top-off timer starts. The device continues to trickle + * charge the battery until the top-off timer runs out." + */ + ret = max14577_get_charger_state(chg, &charging); + if (ret < 0) + return ret; + + if (charging == POWER_SUPPLY_STATUS_CHARGING) + *val = POWER_SUPPLY_CHARGE_TYPE_FAST; + else + *val = POWER_SUPPLY_CHARGE_TYPE_NONE; + + return 0; +} + +static int max14577_get_online(struct max14577_charger *chg, int *val) +{ + struct regmap *rmap = chg->max14577->regmap; + u8 reg_data; + int ret; + enum max14577_muic_charger_type chg_type; + + ret = max14577_read_reg(rmap, MAX14577_MUIC_REG_STATUS2, ®_data); + if (ret < 0) + return ret; + + reg_data = ((reg_data & STATUS2_CHGTYP_MASK) >> STATUS2_CHGTYP_SHIFT); + chg_type = maxim_get_charger_type(chg->max14577->dev_type, reg_data); + switch (chg_type) { + case MAX14577_CHARGER_TYPE_USB: + case MAX14577_CHARGER_TYPE_DEDICATED_CHG: + case MAX14577_CHARGER_TYPE_SPECIAL_500MA: + case MAX14577_CHARGER_TYPE_SPECIAL_1A: + case MAX14577_CHARGER_TYPE_DEAD_BATTERY: + case MAX77836_CHARGER_TYPE_SPECIAL_BIAS: + *val = 1; + break; + case MAX14577_CHARGER_TYPE_NONE: + case MAX14577_CHARGER_TYPE_DOWNSTREAM_PORT: + case MAX14577_CHARGER_TYPE_RESERVED: + case MAX77836_CHARGER_TYPE_RESERVED: + default: + *val = 0; + } + + return 0; +} + +/* + * Supported health statuses: + * - POWER_SUPPLY_HEALTH_DEAD + * - POWER_SUPPLY_HEALTH_OVERVOLTAGE + * - POWER_SUPPLY_HEALTH_GOOD + */ +static int max14577_get_battery_health(struct max14577_charger *chg, int *val) +{ + struct regmap *rmap = chg->max14577->regmap; + int ret; + u8 reg_data; + enum max14577_muic_charger_type chg_type; + + ret = max14577_read_reg(rmap, MAX14577_MUIC_REG_STATUS2, ®_data); + if (ret < 0) + goto out; + + reg_data = ((reg_data & STATUS2_CHGTYP_MASK) >> STATUS2_CHGTYP_SHIFT); + chg_type = maxim_get_charger_type(chg->max14577->dev_type, reg_data); + if (chg_type == MAX14577_CHARGER_TYPE_DEAD_BATTERY) { + *val = POWER_SUPPLY_HEALTH_DEAD; + goto out; + } + + ret = max14577_read_reg(rmap, MAX14577_CHG_REG_STATUS3, ®_data); + if (ret < 0) + goto out; + + if (reg_data & STATUS3_OVP_MASK) { + *val = POWER_SUPPLY_HEALTH_OVERVOLTAGE; + goto out; + } + + /* Not dead, not overvoltage */ + *val = POWER_SUPPLY_HEALTH_GOOD; + +out: + return ret; +} + +/* + * Always returns 1. + * The max14577 chip doesn't report any status of battery presence. + * Lets assume that it will always be used with some battery. + */ +static int max14577_get_present(struct max14577_charger *chg, int *val) +{ + *val = 1; + + return 0; +} + +static int max14577_set_fast_charge_timer(struct max14577_charger *chg, + unsigned long hours) +{ + u8 reg_data; + + switch (hours) { + case 5 ... 7: + reg_data = hours - 3; + break; + case 0: + /* Disable */ + reg_data = 0x7; + break; + default: + dev_err(chg->dev, "Wrong value for Fast-Charge Timer: %lu\n", + hours); + return -EINVAL; + } + reg_data <<= CHGCTRL1_TCHW_SHIFT; + + return max14577_update_reg(chg->max14577->regmap, + MAX14577_REG_CHGCTRL1, CHGCTRL1_TCHW_MASK, reg_data); +} + +static int max14577_init_constant_voltage(struct max14577_charger *chg, + unsigned int uvolt) +{ + u8 reg_data; + + if (uvolt < MAXIM_CHARGER_CONSTANT_VOLTAGE_MIN || + uvolt > MAXIM_CHARGER_CONSTANT_VOLTAGE_MAX) + return -EINVAL; + + if (uvolt == 4200000) + reg_data = 0x0; + else if (uvolt == MAXIM_CHARGER_CONSTANT_VOLTAGE_MAX) + reg_data = 0x1f; + else if (uvolt <= 4280000) { + unsigned int val = uvolt; + + val -= MAXIM_CHARGER_CONSTANT_VOLTAGE_MIN; + val /= MAXIM_CHARGER_CONSTANT_VOLTAGE_STEP; + if (uvolt <= 4180000) + reg_data = 0x1 + val; + else + reg_data = val; /* Fix for gap between 4.18V and 4.22V */ + } else + return -EINVAL; + + reg_data <<= CHGCTRL3_MBCCVWRC_SHIFT; + + return max14577_write_reg(chg->max14577->regmap, + MAX14577_CHG_REG_CHG_CTRL3, reg_data); +} + +static int max14577_init_eoc(struct max14577_charger *chg, + unsigned int uamp) +{ + unsigned int current_bits = 0xf; + u8 reg_data; + + switch (chg->max14577->dev_type) { + case MAXIM_DEVICE_TYPE_MAX77836: + if (uamp < 5000) + return -EINVAL; /* Requested current is too low */ + + if (uamp >= 7500 && uamp < 10000) + current_bits = 0x0; + else if (uamp <= 50000) { + /* <5000, 7499> and <10000, 50000> */ + current_bits = uamp / 5000; + } else { + uamp = min(uamp, 100000U) - 50000U; + current_bits = 0xa + uamp / 10000; + } + break; + + case MAXIM_DEVICE_TYPE_MAX14577: + default: + if (uamp < MAX14577_CHARGER_EOC_CURRENT_LIMIT_MIN) + return -EINVAL; /* Requested current is too low */ + + uamp = min(uamp, MAX14577_CHARGER_EOC_CURRENT_LIMIT_MAX); + uamp -= MAX14577_CHARGER_EOC_CURRENT_LIMIT_MIN; + current_bits = uamp / MAX14577_CHARGER_EOC_CURRENT_LIMIT_STEP; + break; + } + + reg_data = current_bits << CHGCTRL5_EOCS_SHIFT; + + return max14577_update_reg(chg->max14577->regmap, + MAX14577_CHG_REG_CHG_CTRL5, CHGCTRL5_EOCS_MASK, + reg_data); +} + +static int max14577_init_fast_charge(struct max14577_charger *chg, + unsigned int uamp) +{ + u8 reg_data; + int ret; + const struct maxim_charger_current *limits = + &maxim_charger_currents[chg->max14577->dev_type]; + + ret = maxim_charger_calc_reg_current(limits, uamp, uamp, ®_data); + if (ret) { + dev_err(chg->dev, "Wrong value for fast charge: %u\n", uamp); + return ret; + } + + return max14577_update_reg(chg->max14577->regmap, + MAX14577_CHG_REG_CHG_CTRL4, + CHGCTRL4_MBCICHWRCL_MASK | CHGCTRL4_MBCICHWRCH_MASK, + reg_data); +} + +/* + * Sets charger registers to proper and safe default values. + * Some of these values are equal to defaults in MAX14577E + * data sheet but there are minor differences. + */ +static int max14577_charger_reg_init(struct max14577_charger *chg) +{ + struct regmap *rmap = chg->max14577->regmap; + u8 reg_data; + int ret; + + /* + * Charger-Type Manual Detection, default off (set CHGTYPMAN to 0) + * Charger-Detection Enable, default on (set CHGDETEN to 1) + * Combined mask of CHGDETEN and CHGTYPMAN will zero the CHGTYPMAN bit + */ + reg_data = 0x1 << CDETCTRL1_CHGDETEN_SHIFT; + max14577_update_reg(rmap, MAX14577_REG_CDETCTRL1, + CDETCTRL1_CHGDETEN_MASK | CDETCTRL1_CHGTYPMAN_MASK, + reg_data); + + /* + * Wall-Adapter Rapid Charge, default on + * Battery-Charger, default on + */ + reg_data = 0x1 << CHGCTRL2_VCHGR_RC_SHIFT; + reg_data |= 0x1 << CHGCTRL2_MBCHOSTEN_SHIFT; + max14577_write_reg(rmap, MAX14577_REG_CHGCTRL2, reg_data); + + /* Auto Charging Stop, default off */ + reg_data = 0x0 << CHGCTRL6_AUTOSTOP_SHIFT; + max14577_write_reg(rmap, MAX14577_REG_CHGCTRL6, reg_data); + + ret = max14577_init_constant_voltage(chg, chg->pdata->constant_uvolt); + if (ret) + return ret; + + ret = max14577_init_eoc(chg, chg->pdata->eoc_uamp); + if (ret) + return ret; + + ret = max14577_init_fast_charge(chg, chg->pdata->fast_charge_uamp); + if (ret) + return ret; + + ret = max14577_set_fast_charge_timer(chg, + MAXIM_CHARGER_FAST_CHARGE_TIMER_DEFAULT); + if (ret) + return ret; + + /* Initialize Overvoltage-Protection Threshold */ + switch (chg->pdata->ovp_uvolt) { + case 7500000: + reg_data = 0x0; + break; + case 6000000: + case 6500000: + case 7000000: + reg_data = 0x1 + (chg->pdata->ovp_uvolt - 6000000) / 500000; + break; + default: + dev_err(chg->dev, "Wrong value for OVP: %u\n", + chg->pdata->ovp_uvolt); + return -EINVAL; + } + reg_data <<= CHGCTRL7_OTPCGHCVS_SHIFT; + max14577_write_reg(rmap, MAX14577_REG_CHGCTRL7, reg_data); + + return 0; +} + +/* Support property from charger */ +static enum power_supply_property max14577_charger_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_CHARGE_TYPE, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_MODEL_NAME, + POWER_SUPPLY_PROP_MANUFACTURER, +}; + +static const char * const model_names[] = { + [MAXIM_DEVICE_TYPE_UNKNOWN] = "MAX14577-like", + [MAXIM_DEVICE_TYPE_MAX14577] = "MAX14577", + [MAXIM_DEVICE_TYPE_MAX77836] = "MAX77836", +}; +static const char *manufacturer = "Maxim Integrated"; + +static int max14577_charger_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct max14577_charger *chg = power_supply_get_drvdata(psy); + int ret = 0; + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + ret = max14577_get_charger_state(chg, &val->intval); + break; + case POWER_SUPPLY_PROP_CHARGE_TYPE: + ret = max14577_get_charge_type(chg, &val->intval); + break; + case POWER_SUPPLY_PROP_HEALTH: + ret = max14577_get_battery_health(chg, &val->intval); + break; + case POWER_SUPPLY_PROP_PRESENT: + ret = max14577_get_present(chg, &val->intval); + break; + case POWER_SUPPLY_PROP_ONLINE: + ret = max14577_get_online(chg, &val->intval); + break; + case POWER_SUPPLY_PROP_MODEL_NAME: + BUILD_BUG_ON(ARRAY_SIZE(model_names) != MAXIM_DEVICE_TYPE_NUM); + val->strval = model_names[chg->max14577->dev_type]; + break; + case POWER_SUPPLY_PROP_MANUFACTURER: + val->strval = manufacturer; + break; + default: + return -EINVAL; + } + + return ret; +} + +static const struct power_supply_desc max14577_charger_desc = { + .name = "max14577-charger", + .type = POWER_SUPPLY_TYPE_BATTERY, + .properties = max14577_charger_props, + .num_properties = ARRAY_SIZE(max14577_charger_props), + .get_property = max14577_charger_get_property, +}; + +#ifdef CONFIG_OF +static struct max14577_charger_platform_data *max14577_charger_dt_init( + struct platform_device *pdev) +{ + struct max14577_charger_platform_data *pdata; + struct device_node *np = pdev->dev.of_node; + int ret; + + if (!np) { + dev_err(&pdev->dev, "No charger OF node\n"); + return ERR_PTR(-EINVAL); + } + + pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) + return ERR_PTR(-ENOMEM); + + ret = of_property_read_u32(np, "maxim,constant-uvolt", + &pdata->constant_uvolt); + if (ret) { + dev_err(&pdev->dev, "Cannot parse maxim,constant-uvolt field from DT\n"); + return ERR_PTR(ret); + } + + ret = of_property_read_u32(np, "maxim,fast-charge-uamp", + &pdata->fast_charge_uamp); + if (ret) { + dev_err(&pdev->dev, "Cannot parse maxim,fast-charge-uamp field from DT\n"); + return ERR_PTR(ret); + } + + ret = of_property_read_u32(np, "maxim,eoc-uamp", &pdata->eoc_uamp); + if (ret) { + dev_err(&pdev->dev, "Cannot parse maxim,eoc-uamp field from DT\n"); + return ERR_PTR(ret); + } + + ret = of_property_read_u32(np, "maxim,ovp-uvolt", &pdata->ovp_uvolt); + if (ret) { + dev_err(&pdev->dev, "Cannot parse maxim,ovp-uvolt field from DT\n"); + return ERR_PTR(ret); + } + + return pdata; +} +#else /* CONFIG_OF */ +static struct max14577_charger_platform_data *max14577_charger_dt_init( + struct platform_device *pdev) +{ + return NULL; +} +#endif /* CONFIG_OF */ + +static ssize_t show_fast_charge_timer(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct max14577_charger *chg = dev_get_drvdata(dev); + u8 reg_data; + int ret; + unsigned int val; + + ret = max14577_read_reg(chg->max14577->regmap, MAX14577_REG_CHGCTRL1, + ®_data); + if (ret) + return ret; + + reg_data &= CHGCTRL1_TCHW_MASK; + reg_data >>= CHGCTRL1_TCHW_SHIFT; + switch (reg_data) { + case 0x2 ... 0x4: + val = reg_data + 3; + break; + case 0x7: + val = 0; + break; + default: + val = 5; + break; + } + + return scnprintf(buf, PAGE_SIZE, "%u\n", val); +} + +static ssize_t store_fast_charge_timer(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct max14577_charger *chg = dev_get_drvdata(dev); + unsigned long val; + int ret; + + ret = kstrtoul(buf, 10, &val); + if (ret) + return ret; + + ret = max14577_set_fast_charge_timer(chg, val); + if (ret) + return ret; + + return count; +} + +static DEVICE_ATTR(fast_charge_timer, S_IRUGO | S_IWUSR, + show_fast_charge_timer, store_fast_charge_timer); + +static int max14577_charger_probe(struct platform_device *pdev) +{ + struct max14577_charger *chg; + struct power_supply_config psy_cfg = {}; + struct max14577 *max14577 = dev_get_drvdata(pdev->dev.parent); + int ret; + + chg = devm_kzalloc(&pdev->dev, sizeof(*chg), GFP_KERNEL); + if (!chg) + return -ENOMEM; + + platform_set_drvdata(pdev, chg); + chg->dev = &pdev->dev; + chg->max14577 = max14577; + + chg->pdata = max14577_charger_dt_init(pdev); + if (IS_ERR_OR_NULL(chg->pdata)) + return PTR_ERR(chg->pdata); + + ret = max14577_charger_reg_init(chg); + if (ret) + return ret; + + ret = device_create_file(&pdev->dev, &dev_attr_fast_charge_timer); + if (ret) { + dev_err(&pdev->dev, "failed: create sysfs entry\n"); + return ret; + } + + psy_cfg.drv_data = chg; + chg->charger = power_supply_register(&pdev->dev, &max14577_charger_desc, + &psy_cfg); + if (IS_ERR(chg->charger)) { + dev_err(&pdev->dev, "failed: power supply register\n"); + ret = PTR_ERR(chg->charger); + goto err; + } + + /* Check for valid values for charger */ + BUILD_BUG_ON(MAX14577_CHARGER_EOC_CURRENT_LIMIT_MIN + + MAX14577_CHARGER_EOC_CURRENT_LIMIT_STEP * 0xf != + MAX14577_CHARGER_EOC_CURRENT_LIMIT_MAX); + return 0; + +err: + device_remove_file(&pdev->dev, &dev_attr_fast_charge_timer); + + return ret; +} + +static int max14577_charger_remove(struct platform_device *pdev) +{ + struct max14577_charger *chg = platform_get_drvdata(pdev); + + device_remove_file(&pdev->dev, &dev_attr_fast_charge_timer); + power_supply_unregister(chg->charger); + + return 0; +} + +static const struct platform_device_id max14577_charger_id[] = { + { "max14577-charger", MAXIM_DEVICE_TYPE_MAX14577, }, + { "max77836-charger", MAXIM_DEVICE_TYPE_MAX77836, }, + { } +}; +MODULE_DEVICE_TABLE(platform, max14577_charger_id); + +static struct platform_driver max14577_charger_driver = { + .driver = { + .name = "max14577-charger", + }, + .probe = max14577_charger_probe, + .remove = max14577_charger_remove, + .id_table = max14577_charger_id, +}; +module_platform_driver(max14577_charger_driver); + +MODULE_AUTHOR("Krzysztof Kozlowski "); +MODULE_DESCRIPTION("Maxim 14577/77836 charger driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/power/supply/max17040_battery.c b/drivers/power/supply/max17040_battery.c new file mode 100644 index 000000000000..8689c80202b5 --- /dev/null +++ b/drivers/power/supply/max17040_battery.c @@ -0,0 +1,305 @@ +/* + * max17040_battery.c + * fuel-gauge systems for lithium-ion (Li+) batteries + * + * Copyright (C) 2009 Samsung Electronics + * Minkyu Kang + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MAX17040_VCELL_MSB 0x02 +#define MAX17040_VCELL_LSB 0x03 +#define MAX17040_SOC_MSB 0x04 +#define MAX17040_SOC_LSB 0x05 +#define MAX17040_MODE_MSB 0x06 +#define MAX17040_MODE_LSB 0x07 +#define MAX17040_VER_MSB 0x08 +#define MAX17040_VER_LSB 0x09 +#define MAX17040_RCOMP_MSB 0x0C +#define MAX17040_RCOMP_LSB 0x0D +#define MAX17040_CMD_MSB 0xFE +#define MAX17040_CMD_LSB 0xFF + +#define MAX17040_DELAY 1000 +#define MAX17040_BATTERY_FULL 95 + +struct max17040_chip { + struct i2c_client *client; + struct delayed_work work; + struct power_supply *battery; + struct max17040_platform_data *pdata; + + /* State Of Connect */ + int online; + /* battery voltage */ + int vcell; + /* battery capacity */ + int soc; + /* State Of Charge */ + int status; +}; + +static int max17040_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct max17040_chip *chip = power_supply_get_drvdata(psy); + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + val->intval = chip->status; + break; + case POWER_SUPPLY_PROP_ONLINE: + val->intval = chip->online; + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + val->intval = chip->vcell; + break; + case POWER_SUPPLY_PROP_CAPACITY: + val->intval = chip->soc; + break; + default: + return -EINVAL; + } + return 0; +} + +static int max17040_write_reg(struct i2c_client *client, int reg, u8 value) +{ + int ret; + + ret = i2c_smbus_write_byte_data(client, reg, value); + + if (ret < 0) + dev_err(&client->dev, "%s: err %d\n", __func__, ret); + + return ret; +} + +static int max17040_read_reg(struct i2c_client *client, int reg) +{ + int ret; + + ret = i2c_smbus_read_byte_data(client, reg); + + if (ret < 0) + dev_err(&client->dev, "%s: err %d\n", __func__, ret); + + return ret; +} + +static void max17040_reset(struct i2c_client *client) +{ + max17040_write_reg(client, MAX17040_CMD_MSB, 0x54); + max17040_write_reg(client, MAX17040_CMD_LSB, 0x00); +} + +static void max17040_get_vcell(struct i2c_client *client) +{ + struct max17040_chip *chip = i2c_get_clientdata(client); + u8 msb; + u8 lsb; + + msb = max17040_read_reg(client, MAX17040_VCELL_MSB); + lsb = max17040_read_reg(client, MAX17040_VCELL_LSB); + + chip->vcell = (msb << 4) + (lsb >> 4); +} + +static void max17040_get_soc(struct i2c_client *client) +{ + struct max17040_chip *chip = i2c_get_clientdata(client); + u8 msb; + u8 lsb; + + msb = max17040_read_reg(client, MAX17040_SOC_MSB); + lsb = max17040_read_reg(client, MAX17040_SOC_LSB); + + chip->soc = msb; +} + +static void max17040_get_version(struct i2c_client *client) +{ + u8 msb; + u8 lsb; + + msb = max17040_read_reg(client, MAX17040_VER_MSB); + lsb = max17040_read_reg(client, MAX17040_VER_LSB); + + dev_info(&client->dev, "MAX17040 Fuel-Gauge Ver %d%d\n", msb, lsb); +} + +static void max17040_get_online(struct i2c_client *client) +{ + struct max17040_chip *chip = i2c_get_clientdata(client); + + if (chip->pdata && chip->pdata->battery_online) + chip->online = chip->pdata->battery_online(); + else + chip->online = 1; +} + +static void max17040_get_status(struct i2c_client *client) +{ + struct max17040_chip *chip = i2c_get_clientdata(client); + + if (!chip->pdata || !chip->pdata->charger_online + || !chip->pdata->charger_enable) { + chip->status = POWER_SUPPLY_STATUS_UNKNOWN; + return; + } + + if (chip->pdata->charger_online()) { + if (chip->pdata->charger_enable()) + chip->status = POWER_SUPPLY_STATUS_CHARGING; + else + chip->status = POWER_SUPPLY_STATUS_NOT_CHARGING; + } else { + chip->status = POWER_SUPPLY_STATUS_DISCHARGING; + } + + if (chip->soc > MAX17040_BATTERY_FULL) + chip->status = POWER_SUPPLY_STATUS_FULL; +} + +static void max17040_work(struct work_struct *work) +{ + struct max17040_chip *chip; + + chip = container_of(work, struct max17040_chip, work.work); + + max17040_get_vcell(chip->client); + max17040_get_soc(chip->client); + max17040_get_online(chip->client); + max17040_get_status(chip->client); + + queue_delayed_work(system_power_efficient_wq, &chip->work, + MAX17040_DELAY); +} + +static enum power_supply_property max17040_battery_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CAPACITY, +}; + +static const struct power_supply_desc max17040_battery_desc = { + .name = "battery", + .type = POWER_SUPPLY_TYPE_BATTERY, + .get_property = max17040_get_property, + .properties = max17040_battery_props, + .num_properties = ARRAY_SIZE(max17040_battery_props), +}; + +static int max17040_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent); + struct power_supply_config psy_cfg = {}; + struct max17040_chip *chip; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE)) + return -EIO; + + chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL); + if (!chip) + return -ENOMEM; + + chip->client = client; + chip->pdata = client->dev.platform_data; + + i2c_set_clientdata(client, chip); + psy_cfg.drv_data = chip; + + chip->battery = power_supply_register(&client->dev, + &max17040_battery_desc, &psy_cfg); + if (IS_ERR(chip->battery)) { + dev_err(&client->dev, "failed: power supply register\n"); + return PTR_ERR(chip->battery); + } + + max17040_reset(client); + max17040_get_version(client); + + INIT_DEFERRABLE_WORK(&chip->work, max17040_work); + queue_delayed_work(system_power_efficient_wq, &chip->work, + MAX17040_DELAY); + + return 0; +} + +static int max17040_remove(struct i2c_client *client) +{ + struct max17040_chip *chip = i2c_get_clientdata(client); + + power_supply_unregister(chip->battery); + cancel_delayed_work(&chip->work); + return 0; +} + +#ifdef CONFIG_PM_SLEEP + +static int max17040_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct max17040_chip *chip = i2c_get_clientdata(client); + + cancel_delayed_work(&chip->work); + return 0; +} + +static int max17040_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct max17040_chip *chip = i2c_get_clientdata(client); + + queue_delayed_work(system_power_efficient_wq, &chip->work, + MAX17040_DELAY); + return 0; +} + +static SIMPLE_DEV_PM_OPS(max17040_pm_ops, max17040_suspend, max17040_resume); +#define MAX17040_PM_OPS (&max17040_pm_ops) + +#else + +#define MAX17040_PM_OPS NULL + +#endif /* CONFIG_PM_SLEEP */ + +static const struct i2c_device_id max17040_id[] = { + { "max17040" }, + { "max77836-battery" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, max17040_id); + +static struct i2c_driver max17040_i2c_driver = { + .driver = { + .name = "max17040", + .pm = MAX17040_PM_OPS, + }, + .probe = max17040_probe, + .remove = max17040_remove, + .id_table = max17040_id, +}; +module_i2c_driver(max17040_i2c_driver); + +MODULE_AUTHOR("Minkyu Kang "); +MODULE_DESCRIPTION("MAX17040 Fuel Gauge"); +MODULE_LICENSE("GPL"); diff --git a/drivers/power/supply/max17042_battery.c b/drivers/power/supply/max17042_battery.c new file mode 100644 index 000000000000..9c65f134d447 --- /dev/null +++ b/drivers/power/supply/max17042_battery.c @@ -0,0 +1,1016 @@ +/* + * Fuel gauge driver for Maxim 17042 / 8966 / 8997 + * Note that Maxim 8966 and 8997 are mfd and this is its subdevice. + * + * Copyright (C) 2011 Samsung Electronics + * MyungJoo Ham + * + * 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 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 + * + * This driver is based on max17040_battery.c + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Status register bits */ +#define STATUS_POR_BIT (1 << 1) +#define STATUS_BST_BIT (1 << 3) +#define STATUS_VMN_BIT (1 << 8) +#define STATUS_TMN_BIT (1 << 9) +#define STATUS_SMN_BIT (1 << 10) +#define STATUS_BI_BIT (1 << 11) +#define STATUS_VMX_BIT (1 << 12) +#define STATUS_TMX_BIT (1 << 13) +#define STATUS_SMX_BIT (1 << 14) +#define STATUS_BR_BIT (1 << 15) + +/* Interrupt mask bits */ +#define CONFIG_ALRT_BIT_ENBL (1 << 2) +#define STATUS_INTR_SOCMIN_BIT (1 << 10) +#define STATUS_INTR_SOCMAX_BIT (1 << 14) + +#define VFSOC0_LOCK 0x0000 +#define VFSOC0_UNLOCK 0x0080 +#define MODEL_UNLOCK1 0X0059 +#define MODEL_UNLOCK2 0X00C4 +#define MODEL_LOCK1 0X0000 +#define MODEL_LOCK2 0X0000 + +#define dQ_ACC_DIV 0x4 +#define dP_ACC_100 0x1900 +#define dP_ACC_200 0x3200 + +#define MAX17042_VMAX_TOLERANCE 50 /* 50 mV */ + +struct max17042_chip { + struct i2c_client *client; + struct regmap *regmap; + struct power_supply *battery; + enum max170xx_chip_type chip_type; + struct max17042_platform_data *pdata; + struct work_struct work; + int init_complete; +}; + +static enum power_supply_property max17042_battery_props[] = { + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_CYCLE_COUNT, + POWER_SUPPLY_PROP_VOLTAGE_MAX, + POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_VOLTAGE_AVG, + POWER_SUPPLY_PROP_VOLTAGE_OCV, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_CHARGE_FULL, + POWER_SUPPLY_PROP_CHARGE_COUNTER, + POWER_SUPPLY_PROP_TEMP, + POWER_SUPPLY_PROP_TEMP_ALERT_MIN, + POWER_SUPPLY_PROP_TEMP_ALERT_MAX, + POWER_SUPPLY_PROP_TEMP_MIN, + POWER_SUPPLY_PROP_TEMP_MAX, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_CURRENT_AVG, +}; + +static int max17042_get_temperature(struct max17042_chip *chip, int *temp) +{ + int ret; + u32 data; + struct regmap *map = chip->regmap; + + ret = regmap_read(map, MAX17042_TEMP, &data); + if (ret < 0) + return ret; + + *temp = data; + /* The value is signed. */ + if (*temp & 0x8000) { + *temp = (0x7fff & ~*temp) + 1; + *temp *= -1; + } + + /* The value is converted into deci-centigrade scale */ + /* Units of LSB = 1 / 256 degree Celsius */ + *temp = *temp * 10 / 256; + return 0; +} + +static int max17042_get_battery_health(struct max17042_chip *chip, int *health) +{ + int temp, vavg, vbatt, ret; + u32 val; + + ret = regmap_read(chip->regmap, MAX17042_AvgVCELL, &val); + if (ret < 0) + goto health_error; + + /* bits [0-3] unused */ + vavg = val * 625 / 8; + /* Convert to millivolts */ + vavg /= 1000; + + ret = regmap_read(chip->regmap, MAX17042_VCELL, &val); + if (ret < 0) + goto health_error; + + /* bits [0-3] unused */ + vbatt = val * 625 / 8; + /* Convert to millivolts */ + vbatt /= 1000; + + if (vavg < chip->pdata->vmin) { + *health = POWER_SUPPLY_HEALTH_DEAD; + goto out; + } + + if (vbatt > chip->pdata->vmax + MAX17042_VMAX_TOLERANCE) { + *health = POWER_SUPPLY_HEALTH_OVERVOLTAGE; + goto out; + } + + ret = max17042_get_temperature(chip, &temp); + if (ret < 0) + goto health_error; + + if (temp <= chip->pdata->temp_min) { + *health = POWER_SUPPLY_HEALTH_COLD; + goto out; + } + + if (temp >= chip->pdata->temp_max) { + *health = POWER_SUPPLY_HEALTH_OVERHEAT; + goto out; + } + + *health = POWER_SUPPLY_HEALTH_GOOD; + +out: + return 0; + +health_error: + return ret; +} + +static int max17042_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct max17042_chip *chip = power_supply_get_drvdata(psy); + struct regmap *map = chip->regmap; + int ret; + u32 data; + + if (!chip->init_complete) + return -EAGAIN; + + switch (psp) { + case POWER_SUPPLY_PROP_PRESENT: + ret = regmap_read(map, MAX17042_STATUS, &data); + if (ret < 0) + return ret; + + if (data & MAX17042_STATUS_BattAbsent) + val->intval = 0; + else + val->intval = 1; + break; + case POWER_SUPPLY_PROP_CYCLE_COUNT: + ret = regmap_read(map, MAX17042_Cycles, &data); + if (ret < 0) + return ret; + + val->intval = data; + break; + case POWER_SUPPLY_PROP_VOLTAGE_MAX: + ret = regmap_read(map, MAX17042_MinMaxVolt, &data); + if (ret < 0) + return ret; + + val->intval = data >> 8; + val->intval *= 20000; /* Units of LSB = 20mV */ + break; + case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: + if (chip->chip_type == MAXIM_DEVICE_TYPE_MAX17042) + ret = regmap_read(map, MAX17042_V_empty, &data); + else + ret = regmap_read(map, MAX17047_V_empty, &data); + if (ret < 0) + return ret; + + val->intval = data >> 7; + val->intval *= 10000; /* Units of LSB = 10mV */ + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + ret = regmap_read(map, MAX17042_VCELL, &data); + if (ret < 0) + return ret; + + val->intval = data * 625 / 8; + break; + case POWER_SUPPLY_PROP_VOLTAGE_AVG: + ret = regmap_read(map, MAX17042_AvgVCELL, &data); + if (ret < 0) + return ret; + + val->intval = data * 625 / 8; + break; + case POWER_SUPPLY_PROP_VOLTAGE_OCV: + ret = regmap_read(map, MAX17042_OCVInternal, &data); + if (ret < 0) + return ret; + + val->intval = data * 625 / 8; + break; + case POWER_SUPPLY_PROP_CAPACITY: + ret = regmap_read(map, MAX17042_RepSOC, &data); + if (ret < 0) + return ret; + + val->intval = data >> 8; + break; + case POWER_SUPPLY_PROP_CHARGE_FULL: + ret = regmap_read(map, MAX17042_FullCAP, &data); + if (ret < 0) + return ret; + + val->intval = data * 1000 / 2; + break; + case POWER_SUPPLY_PROP_CHARGE_COUNTER: + ret = regmap_read(map, MAX17042_QH, &data); + if (ret < 0) + return ret; + + val->intval = data * 1000 / 2; + break; + case POWER_SUPPLY_PROP_TEMP: + ret = max17042_get_temperature(chip, &val->intval); + if (ret < 0) + return ret; + break; + case POWER_SUPPLY_PROP_TEMP_ALERT_MIN: + ret = regmap_read(map, MAX17042_TALRT_Th, &data); + if (ret < 0) + return ret; + /* LSB is Alert Minimum. In deci-centigrade */ + val->intval = (data & 0xff) * 10; + break; + case POWER_SUPPLY_PROP_TEMP_ALERT_MAX: + ret = regmap_read(map, MAX17042_TALRT_Th, &data); + if (ret < 0) + return ret; + /* MSB is Alert Maximum. In deci-centigrade */ + val->intval = (data >> 8) * 10; + break; + case POWER_SUPPLY_PROP_TEMP_MIN: + val->intval = chip->pdata->temp_min; + break; + case POWER_SUPPLY_PROP_TEMP_MAX: + val->intval = chip->pdata->temp_max; + break; + case POWER_SUPPLY_PROP_HEALTH: + ret = max17042_get_battery_health(chip, &val->intval); + if (ret < 0) + return ret; + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + if (chip->pdata->enable_current_sense) { + ret = regmap_read(map, MAX17042_Current, &data); + if (ret < 0) + return ret; + + val->intval = data; + if (val->intval & 0x8000) { + /* Negative */ + val->intval = ~val->intval & 0x7fff; + val->intval++; + val->intval *= -1; + } + val->intval *= 1562500 / chip->pdata->r_sns; + } else { + return -EINVAL; + } + break; + case POWER_SUPPLY_PROP_CURRENT_AVG: + if (chip->pdata->enable_current_sense) { + ret = regmap_read(map, MAX17042_AvgCurrent, &data); + if (ret < 0) + return ret; + + val->intval = data; + if (val->intval & 0x8000) { + /* Negative */ + val->intval = ~val->intval & 0x7fff; + val->intval++; + val->intval *= -1; + } + val->intval *= 1562500 / chip->pdata->r_sns; + } else { + return -EINVAL; + } + break; + default: + return -EINVAL; + } + return 0; +} + +static int max17042_set_property(struct power_supply *psy, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct max17042_chip *chip = power_supply_get_drvdata(psy); + struct regmap *map = chip->regmap; + int ret = 0; + u32 data; + int8_t temp; + + switch (psp) { + case POWER_SUPPLY_PROP_TEMP_ALERT_MIN: + ret = regmap_read(map, MAX17042_TALRT_Th, &data); + if (ret < 0) + return ret; + + /* Input in deci-centigrade, convert to centigrade */ + temp = val->intval / 10; + /* force min < max */ + if (temp >= (int8_t)(data >> 8)) + temp = (int8_t)(data >> 8) - 1; + /* Write both MAX and MIN ALERT */ + data = (data & 0xff00) + temp; + ret = regmap_write(map, MAX17042_TALRT_Th, data); + break; + case POWER_SUPPLY_PROP_TEMP_ALERT_MAX: + ret = regmap_read(map, MAX17042_TALRT_Th, &data); + if (ret < 0) + return ret; + + /* Input in Deci-Centigrade, convert to centigrade */ + temp = val->intval / 10; + /* force max > min */ + if (temp <= (int8_t)(data & 0xff)) + temp = (int8_t)(data & 0xff) + 1; + /* Write both MAX and MIN ALERT */ + data = (data & 0xff) + (temp << 8); + ret = regmap_write(map, MAX17042_TALRT_Th, data); + break; + default: + ret = -EINVAL; + } + + return ret; +} + +static int max17042_property_is_writeable(struct power_supply *psy, + enum power_supply_property psp) +{ + int ret; + + switch (psp) { + case POWER_SUPPLY_PROP_TEMP_ALERT_MIN: + case POWER_SUPPLY_PROP_TEMP_ALERT_MAX: + ret = 1; + break; + default: + ret = 0; + } + + return ret; +} + +static int max17042_write_verify_reg(struct regmap *map, u8 reg, u32 value) +{ + int retries = 8; + int ret; + u32 read_value; + + do { + ret = regmap_write(map, reg, value); + regmap_read(map, reg, &read_value); + if (read_value != value) { + ret = -EIO; + retries--; + } + } while (retries && read_value != value); + + if (ret < 0) + pr_err("%s: err %d\n", __func__, ret); + + return ret; +} + +static inline void max17042_override_por(struct regmap *map, + u8 reg, u16 value) +{ + if (value) + regmap_write(map, reg, value); +} + +static inline void max10742_unlock_model(struct max17042_chip *chip) +{ + struct regmap *map = chip->regmap; + + regmap_write(map, MAX17042_MLOCKReg1, MODEL_UNLOCK1); + regmap_write(map, MAX17042_MLOCKReg2, MODEL_UNLOCK2); +} + +static inline void max10742_lock_model(struct max17042_chip *chip) +{ + struct regmap *map = chip->regmap; + + regmap_write(map, MAX17042_MLOCKReg1, MODEL_LOCK1); + regmap_write(map, MAX17042_MLOCKReg2, MODEL_LOCK2); +} + +static inline void max17042_write_model_data(struct max17042_chip *chip, + u8 addr, int size) +{ + struct regmap *map = chip->regmap; + int i; + + for (i = 0; i < size; i++) + regmap_write(map, addr + i, + chip->pdata->config_data->cell_char_tbl[i]); +} + +static inline void max17042_read_model_data(struct max17042_chip *chip, + u8 addr, u32 *data, int size) +{ + struct regmap *map = chip->regmap; + int i; + + for (i = 0; i < size; i++) + regmap_read(map, addr + i, &data[i]); +} + +static inline int max17042_model_data_compare(struct max17042_chip *chip, + u16 *data1, u16 *data2, int size) +{ + int i; + + if (memcmp(data1, data2, size)) { + dev_err(&chip->client->dev, "%s compare failed\n", __func__); + for (i = 0; i < size; i++) + dev_info(&chip->client->dev, "0x%x, 0x%x", + data1[i], data2[i]); + dev_info(&chip->client->dev, "\n"); + return -EINVAL; + } + return 0; +} + +static int max17042_init_model(struct max17042_chip *chip) +{ + int ret; + int table_size = ARRAY_SIZE(chip->pdata->config_data->cell_char_tbl); + u32 *temp_data; + + temp_data = kcalloc(table_size, sizeof(*temp_data), GFP_KERNEL); + if (!temp_data) + return -ENOMEM; + + max10742_unlock_model(chip); + max17042_write_model_data(chip, MAX17042_MODELChrTbl, + table_size); + max17042_read_model_data(chip, MAX17042_MODELChrTbl, temp_data, + table_size); + + ret = max17042_model_data_compare( + chip, + chip->pdata->config_data->cell_char_tbl, + (u16 *)temp_data, + table_size); + + max10742_lock_model(chip); + kfree(temp_data); + + return ret; +} + +static int max17042_verify_model_lock(struct max17042_chip *chip) +{ + int i; + int table_size = ARRAY_SIZE(chip->pdata->config_data->cell_char_tbl); + u32 *temp_data; + int ret = 0; + + temp_data = kcalloc(table_size, sizeof(*temp_data), GFP_KERNEL); + if (!temp_data) + return -ENOMEM; + + max17042_read_model_data(chip, MAX17042_MODELChrTbl, temp_data, + table_size); + for (i = 0; i < table_size; i++) + if (temp_data[i]) + ret = -EINVAL; + + kfree(temp_data); + return ret; +} + +static void max17042_write_config_regs(struct max17042_chip *chip) +{ + struct max17042_config_data *config = chip->pdata->config_data; + struct regmap *map = chip->regmap; + + regmap_write(map, MAX17042_CONFIG, config->config); + regmap_write(map, MAX17042_LearnCFG, config->learn_cfg); + regmap_write(map, MAX17042_FilterCFG, + config->filter_cfg); + regmap_write(map, MAX17042_RelaxCFG, config->relax_cfg); + if (chip->chip_type == MAXIM_DEVICE_TYPE_MAX17047 || + chip->chip_type == MAXIM_DEVICE_TYPE_MAX17050) + regmap_write(map, MAX17047_FullSOCThr, + config->full_soc_thresh); +} + +static void max17042_write_custom_regs(struct max17042_chip *chip) +{ + struct max17042_config_data *config = chip->pdata->config_data; + struct regmap *map = chip->regmap; + + max17042_write_verify_reg(map, MAX17042_RCOMP0, config->rcomp0); + max17042_write_verify_reg(map, MAX17042_TempCo, config->tcompc0); + max17042_write_verify_reg(map, MAX17042_ICHGTerm, config->ichgt_term); + if (chip->chip_type == MAXIM_DEVICE_TYPE_MAX17042) { + regmap_write(map, MAX17042_EmptyTempCo, config->empty_tempco); + max17042_write_verify_reg(map, MAX17042_K_empty0, + config->kempty0); + } else { + max17042_write_verify_reg(map, MAX17047_QRTbl00, + config->qrtbl00); + max17042_write_verify_reg(map, MAX17047_QRTbl10, + config->qrtbl10); + max17042_write_verify_reg(map, MAX17047_QRTbl20, + config->qrtbl20); + max17042_write_verify_reg(map, MAX17047_QRTbl30, + config->qrtbl30); + } +} + +static void max17042_update_capacity_regs(struct max17042_chip *chip) +{ + struct max17042_config_data *config = chip->pdata->config_data; + struct regmap *map = chip->regmap; + + max17042_write_verify_reg(map, MAX17042_FullCAP, + config->fullcap); + regmap_write(map, MAX17042_DesignCap, config->design_cap); + max17042_write_verify_reg(map, MAX17042_FullCAPNom, + config->fullcapnom); +} + +static void max17042_reset_vfsoc0_reg(struct max17042_chip *chip) +{ + unsigned int vfSoc; + struct regmap *map = chip->regmap; + + regmap_read(map, MAX17042_VFSOC, &vfSoc); + regmap_write(map, MAX17042_VFSOC0Enable, VFSOC0_UNLOCK); + max17042_write_verify_reg(map, MAX17042_VFSOC0, vfSoc); + regmap_write(map, MAX17042_VFSOC0Enable, VFSOC0_LOCK); +} + +static void max17042_load_new_capacity_params(struct max17042_chip *chip) +{ + u32 full_cap0, rep_cap, dq_acc, vfSoc; + u32 rem_cap; + + struct max17042_config_data *config = chip->pdata->config_data; + struct regmap *map = chip->regmap; + + regmap_read(map, MAX17042_FullCAP0, &full_cap0); + regmap_read(map, MAX17042_VFSOC, &vfSoc); + + /* fg_vfSoc needs to shifted by 8 bits to get the + * perc in 1% accuracy, to get the right rem_cap multiply + * full_cap0, fg_vfSoc and devide by 100 + */ + rem_cap = ((vfSoc >> 8) * full_cap0) / 100; + max17042_write_verify_reg(map, MAX17042_RemCap, rem_cap); + + rep_cap = rem_cap; + max17042_write_verify_reg(map, MAX17042_RepCap, rep_cap); + + /* Write dQ_acc to 200% of Capacity and dP_acc to 200% */ + dq_acc = config->fullcap / dQ_ACC_DIV; + max17042_write_verify_reg(map, MAX17042_dQacc, dq_acc); + max17042_write_verify_reg(map, MAX17042_dPacc, dP_ACC_200); + + max17042_write_verify_reg(map, MAX17042_FullCAP, + config->fullcap); + regmap_write(map, MAX17042_DesignCap, + config->design_cap); + max17042_write_verify_reg(map, MAX17042_FullCAPNom, + config->fullcapnom); + /* Update SOC register with new SOC */ + regmap_write(map, MAX17042_RepSOC, vfSoc); +} + +/* + * Block write all the override values coming from platform data. + * This function MUST be called before the POR initialization proceedure + * specified by maxim. + */ +static inline void max17042_override_por_values(struct max17042_chip *chip) +{ + struct regmap *map = chip->regmap; + struct max17042_config_data *config = chip->pdata->config_data; + + max17042_override_por(map, MAX17042_TGAIN, config->tgain); + max17042_override_por(map, MAx17042_TOFF, config->toff); + max17042_override_por(map, MAX17042_CGAIN, config->cgain); + max17042_override_por(map, MAX17042_COFF, config->coff); + + max17042_override_por(map, MAX17042_VALRT_Th, config->valrt_thresh); + max17042_override_por(map, MAX17042_TALRT_Th, config->talrt_thresh); + max17042_override_por(map, MAX17042_SALRT_Th, + config->soc_alrt_thresh); + max17042_override_por(map, MAX17042_CONFIG, config->config); + max17042_override_por(map, MAX17042_SHDNTIMER, config->shdntimer); + + max17042_override_por(map, MAX17042_DesignCap, config->design_cap); + max17042_override_por(map, MAX17042_ICHGTerm, config->ichgt_term); + + max17042_override_por(map, MAX17042_AtRate, config->at_rate); + max17042_override_por(map, MAX17042_LearnCFG, config->learn_cfg); + max17042_override_por(map, MAX17042_FilterCFG, config->filter_cfg); + max17042_override_por(map, MAX17042_RelaxCFG, config->relax_cfg); + max17042_override_por(map, MAX17042_MiscCFG, config->misc_cfg); + max17042_override_por(map, MAX17042_MaskSOC, config->masksoc); + + max17042_override_por(map, MAX17042_FullCAP, config->fullcap); + max17042_override_por(map, MAX17042_FullCAPNom, config->fullcapnom); + if (chip->chip_type == MAXIM_DEVICE_TYPE_MAX17042) + max17042_override_por(map, MAX17042_SOC_empty, + config->socempty); + max17042_override_por(map, MAX17042_LAvg_empty, config->lavg_empty); + max17042_override_por(map, MAX17042_dQacc, config->dqacc); + max17042_override_por(map, MAX17042_dPacc, config->dpacc); + + if (chip->chip_type == MAXIM_DEVICE_TYPE_MAX17042) + max17042_override_por(map, MAX17042_V_empty, config->vempty); + else + max17042_override_por(map, MAX17047_V_empty, config->vempty); + max17042_override_por(map, MAX17042_TempNom, config->temp_nom); + max17042_override_por(map, MAX17042_TempLim, config->temp_lim); + max17042_override_por(map, MAX17042_FCTC, config->fctc); + max17042_override_por(map, MAX17042_RCOMP0, config->rcomp0); + max17042_override_por(map, MAX17042_TempCo, config->tcompc0); + if (chip->chip_type) { + max17042_override_por(map, MAX17042_EmptyTempCo, + config->empty_tempco); + max17042_override_por(map, MAX17042_K_empty0, + config->kempty0); + } +} + +static int max17042_init_chip(struct max17042_chip *chip) +{ + struct regmap *map = chip->regmap; + int ret; + + max17042_override_por_values(chip); + /* After Power up, the MAX17042 requires 500mS in order + * to perform signal debouncing and initial SOC reporting + */ + msleep(500); + + /* Initialize configaration */ + max17042_write_config_regs(chip); + + /* write cell characterization data */ + ret = max17042_init_model(chip); + if (ret) { + dev_err(&chip->client->dev, "%s init failed\n", + __func__); + return -EIO; + } + + ret = max17042_verify_model_lock(chip); + if (ret) { + dev_err(&chip->client->dev, "%s lock verify failed\n", + __func__); + return -EIO; + } + /* write custom parameters */ + max17042_write_custom_regs(chip); + + /* update capacity params */ + max17042_update_capacity_regs(chip); + + /* delay must be atleast 350mS to allow VFSOC + * to be calculated from the new configuration + */ + msleep(350); + + /* reset vfsoc0 reg */ + max17042_reset_vfsoc0_reg(chip); + + /* load new capacity params */ + max17042_load_new_capacity_params(chip); + + /* Init complete, Clear the POR bit */ + regmap_update_bits(map, MAX17042_STATUS, STATUS_POR_BIT, 0x0); + return 0; +} + +static void max17042_set_soc_threshold(struct max17042_chip *chip, u16 off) +{ + struct regmap *map = chip->regmap; + u32 soc, soc_tr; + + /* program interrupt thesholds such that we should + * get interrupt for every 'off' perc change in the soc + */ + regmap_read(map, MAX17042_RepSOC, &soc); + soc >>= 8; + soc_tr = (soc + off) << 8; + soc_tr |= (soc - off); + regmap_write(map, MAX17042_SALRT_Th, soc_tr); +} + +static irqreturn_t max17042_thread_handler(int id, void *dev) +{ + struct max17042_chip *chip = dev; + u32 val; + + regmap_read(chip->regmap, MAX17042_STATUS, &val); + if ((val & STATUS_INTR_SOCMIN_BIT) || + (val & STATUS_INTR_SOCMAX_BIT)) { + dev_info(&chip->client->dev, "SOC threshold INTR\n"); + max17042_set_soc_threshold(chip, 1); + } + + power_supply_changed(chip->battery); + return IRQ_HANDLED; +} + +static void max17042_init_worker(struct work_struct *work) +{ + struct max17042_chip *chip = container_of(work, + struct max17042_chip, work); + int ret; + + /* Initialize registers according to values from the platform data */ + if (chip->pdata->enable_por_init && chip->pdata->config_data) { + ret = max17042_init_chip(chip); + if (ret) + return; + } + + chip->init_complete = 1; +} + +#ifdef CONFIG_OF +static struct max17042_platform_data * +max17042_get_pdata(struct device *dev) +{ + struct device_node *np = dev->of_node; + u32 prop; + struct max17042_platform_data *pdata; + + if (!np) + return dev->platform_data; + + pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) + return NULL; + + /* + * Require current sense resistor value to be specified for + * current-sense functionality to be enabled at all. + */ + if (of_property_read_u32(np, "maxim,rsns-microohm", &prop) == 0) { + pdata->r_sns = prop; + pdata->enable_current_sense = true; + } + + if (of_property_read_s32(np, "maxim,cold-temp", &pdata->temp_min)) + pdata->temp_min = INT_MIN; + if (of_property_read_s32(np, "maxim,over-heat-temp", &pdata->temp_max)) + pdata->temp_max = INT_MAX; + if (of_property_read_s32(np, "maxim,dead-volt", &pdata->vmin)) + pdata->vmin = INT_MIN; + if (of_property_read_s32(np, "maxim,over-volt", &pdata->vmax)) + pdata->vmax = INT_MAX; + + return pdata; +} +#else +static struct max17042_platform_data * +max17042_get_pdata(struct device *dev) +{ + return dev->platform_data; +} +#endif + +static const struct regmap_config max17042_regmap_config = { + .reg_bits = 8, + .val_bits = 16, + .val_format_endian = REGMAP_ENDIAN_NATIVE, +}; + +static const struct power_supply_desc max17042_psy_desc = { + .name = "max170xx_battery", + .type = POWER_SUPPLY_TYPE_BATTERY, + .get_property = max17042_get_property, + .set_property = max17042_set_property, + .property_is_writeable = max17042_property_is_writeable, + .properties = max17042_battery_props, + .num_properties = ARRAY_SIZE(max17042_battery_props), +}; + +static const struct power_supply_desc max17042_no_current_sense_psy_desc = { + .name = "max170xx_battery", + .type = POWER_SUPPLY_TYPE_BATTERY, + .get_property = max17042_get_property, + .set_property = max17042_set_property, + .property_is_writeable = max17042_property_is_writeable, + .properties = max17042_battery_props, + .num_properties = ARRAY_SIZE(max17042_battery_props) - 2, +}; + +static int max17042_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent); + const struct power_supply_desc *max17042_desc = &max17042_psy_desc; + struct power_supply_config psy_cfg = {}; + struct max17042_chip *chip; + int ret; + int i; + u32 val; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_WORD_DATA)) + return -EIO; + + chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL); + if (!chip) + return -ENOMEM; + + chip->client = client; + chip->regmap = devm_regmap_init_i2c(client, &max17042_regmap_config); + if (IS_ERR(chip->regmap)) { + dev_err(&client->dev, "Failed to initialize regmap\n"); + return -EINVAL; + } + + chip->pdata = max17042_get_pdata(&client->dev); + if (!chip->pdata) { + dev_err(&client->dev, "no platform data provided\n"); + return -EINVAL; + } + + i2c_set_clientdata(client, chip); + chip->chip_type = id->driver_data; + psy_cfg.drv_data = chip; + + /* When current is not measured, + * CURRENT_NOW and CURRENT_AVG properties should be invisible. */ + if (!chip->pdata->enable_current_sense) + max17042_desc = &max17042_no_current_sense_psy_desc; + + if (chip->pdata->r_sns == 0) + chip->pdata->r_sns = MAX17042_DEFAULT_SNS_RESISTOR; + + if (chip->pdata->init_data) + for (i = 0; i < chip->pdata->num_init_data; i++) + regmap_write(chip->regmap, + chip->pdata->init_data[i].addr, + chip->pdata->init_data[i].data); + + if (!chip->pdata->enable_current_sense) { + regmap_write(chip->regmap, MAX17042_CGAIN, 0x0000); + regmap_write(chip->regmap, MAX17042_MiscCFG, 0x0003); + regmap_write(chip->regmap, MAX17042_LearnCFG, 0x0007); + } + + 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 = 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, + CONFIG_ALRT_BIT_ENBL); + max17042_set_soc_threshold(chip, 1); + } else { + client->irq = 0; + dev_err(&client->dev, "%s(): cannot get IRQ\n", + __func__); + } + } + + regmap_read(chip->regmap, MAX17042_STATUS, &val); + if (val & STATUS_POR_BIT) { + INIT_WORK(&chip->work, max17042_init_worker); + schedule_work(&chip->work); + } else { + chip->init_complete = 1; + } + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int max17042_suspend(struct device *dev) +{ + struct max17042_chip *chip = dev_get_drvdata(dev); + + /* + * disable the irq and enable irq_wake + * capability to the interrupt line. + */ + if (chip->client->irq) { + disable_irq(chip->client->irq); + enable_irq_wake(chip->client->irq); + } + + return 0; +} + +static int max17042_resume(struct device *dev) +{ + struct max17042_chip *chip = dev_get_drvdata(dev); + + if (chip->client->irq) { + disable_irq_wake(chip->client->irq); + enable_irq(chip->client->irq); + /* re-program the SOC thresholds to 1% change */ + max17042_set_soc_threshold(chip, 1); + } + + return 0; +} +#endif + +static SIMPLE_DEV_PM_OPS(max17042_pm_ops, max17042_suspend, + max17042_resume); + +#ifdef CONFIG_OF +static const struct of_device_id max17042_dt_match[] = { + { .compatible = "maxim,max17042" }, + { .compatible = "maxim,max17047" }, + { .compatible = "maxim,max17050" }, + { }, +}; +MODULE_DEVICE_TABLE(of, max17042_dt_match); +#endif + +static const struct i2c_device_id max17042_id[] = { + { "max17042", MAXIM_DEVICE_TYPE_MAX17042 }, + { "max17047", MAXIM_DEVICE_TYPE_MAX17047 }, + { "max17050", MAXIM_DEVICE_TYPE_MAX17050 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, max17042_id); + +static struct i2c_driver max17042_i2c_driver = { + .driver = { + .name = "max17042", + .of_match_table = of_match_ptr(max17042_dt_match), + .pm = &max17042_pm_ops, + }, + .probe = max17042_probe, + .id_table = max17042_id, +}; +module_i2c_driver(max17042_i2c_driver); + +MODULE_AUTHOR("MyungJoo Ham "); +MODULE_DESCRIPTION("MAX17042 Fuel Gauge"); +MODULE_LICENSE("GPL"); diff --git a/drivers/power/supply/max77693_charger.c b/drivers/power/supply/max77693_charger.c new file mode 100644 index 000000000000..060cab5ae3aa --- /dev/null +++ b/drivers/power/supply/max77693_charger.c @@ -0,0 +1,771 @@ +/* + * max77693_charger.c - Battery charger driver for the Maxim 77693 + * + * Copyright (C) 2014 Samsung Electronics + * Krzysztof Kozlowski + * + * 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 program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include + +#define MAX77693_CHARGER_NAME "max77693-charger" +static const char *max77693_charger_model = "MAX77693"; +static const char *max77693_charger_manufacturer = "Maxim Integrated"; + +struct max77693_charger { + struct device *dev; + struct max77693_dev *max77693; + struct power_supply *charger; + + u32 constant_volt; + u32 min_system_volt; + u32 thermal_regulation_temp; + u32 batttery_overcurrent; + u32 charge_input_threshold_volt; +}; + +static int max77693_get_charger_state(struct regmap *regmap, int *val) +{ + int ret; + unsigned int data; + + ret = regmap_read(regmap, MAX77693_CHG_REG_CHG_DETAILS_01, &data); + if (ret < 0) + return ret; + + data &= CHG_DETAILS_01_CHG_MASK; + data >>= CHG_DETAILS_01_CHG_SHIFT; + + switch (data) { + case MAX77693_CHARGING_PREQUALIFICATION: + case MAX77693_CHARGING_FAST_CONST_CURRENT: + case MAX77693_CHARGING_FAST_CONST_VOLTAGE: + case MAX77693_CHARGING_TOP_OFF: + /* In high temp the charging current is reduced, but still charging */ + case MAX77693_CHARGING_HIGH_TEMP: + *val = POWER_SUPPLY_STATUS_CHARGING; + break; + case MAX77693_CHARGING_DONE: + *val = POWER_SUPPLY_STATUS_FULL; + break; + case MAX77693_CHARGING_TIMER_EXPIRED: + case MAX77693_CHARGING_THERMISTOR_SUSPEND: + *val = POWER_SUPPLY_STATUS_NOT_CHARGING; + break; + case MAX77693_CHARGING_OFF: + case MAX77693_CHARGING_OVER_TEMP: + case MAX77693_CHARGING_WATCHDOG_EXPIRED: + *val = POWER_SUPPLY_STATUS_DISCHARGING; + break; + case MAX77693_CHARGING_RESERVED: + default: + *val = POWER_SUPPLY_STATUS_UNKNOWN; + } + + return 0; +} + +static int max77693_get_charge_type(struct regmap *regmap, int *val) +{ + int ret; + unsigned int data; + + ret = regmap_read(regmap, MAX77693_CHG_REG_CHG_DETAILS_01, &data); + if (ret < 0) + return ret; + + data &= CHG_DETAILS_01_CHG_MASK; + data >>= CHG_DETAILS_01_CHG_SHIFT; + + switch (data) { + case MAX77693_CHARGING_PREQUALIFICATION: + /* + * Top-off: trickle or fast? In top-off the current varies between + * 100 and 250 mA. It is higher than prequalification current. + */ + case MAX77693_CHARGING_TOP_OFF: + *val = POWER_SUPPLY_CHARGE_TYPE_TRICKLE; + break; + case MAX77693_CHARGING_FAST_CONST_CURRENT: + case MAX77693_CHARGING_FAST_CONST_VOLTAGE: + /* In high temp the charging current is reduced, but still charging */ + case MAX77693_CHARGING_HIGH_TEMP: + *val = POWER_SUPPLY_CHARGE_TYPE_FAST; + break; + case MAX77693_CHARGING_DONE: + case MAX77693_CHARGING_TIMER_EXPIRED: + case MAX77693_CHARGING_THERMISTOR_SUSPEND: + case MAX77693_CHARGING_OFF: + case MAX77693_CHARGING_OVER_TEMP: + case MAX77693_CHARGING_WATCHDOG_EXPIRED: + *val = POWER_SUPPLY_CHARGE_TYPE_NONE; + break; + case MAX77693_CHARGING_RESERVED: + default: + *val = POWER_SUPPLY_CHARGE_TYPE_UNKNOWN; + } + + return 0; +} + +/* + * Supported health statuses: + * - POWER_SUPPLY_HEALTH_DEAD + * - POWER_SUPPLY_HEALTH_GOOD + * - POWER_SUPPLY_HEALTH_OVERVOLTAGE + * - POWER_SUPPLY_HEALTH_SAFETY_TIMER_EXPIRE + * - POWER_SUPPLY_HEALTH_UNKNOWN + * - POWER_SUPPLY_HEALTH_UNSPEC_FAILURE + */ +static int max77693_get_battery_health(struct regmap *regmap, int *val) +{ + int ret; + unsigned int data; + + ret = regmap_read(regmap, MAX77693_CHG_REG_CHG_DETAILS_01, &data); + if (ret < 0) + return ret; + + data &= CHG_DETAILS_01_BAT_MASK; + data >>= CHG_DETAILS_01_BAT_SHIFT; + + switch (data) { + case MAX77693_BATTERY_NOBAT: + *val = POWER_SUPPLY_HEALTH_DEAD; + break; + case MAX77693_BATTERY_PREQUALIFICATION: + case MAX77693_BATTERY_GOOD: + case MAX77693_BATTERY_LOWVOLTAGE: + *val = POWER_SUPPLY_HEALTH_GOOD; + break; + case MAX77693_BATTERY_TIMER_EXPIRED: + /* + * Took longer to charge than expected, charging suspended. + * Damaged battery? + */ + *val = POWER_SUPPLY_HEALTH_SAFETY_TIMER_EXPIRE; + break; + case MAX77693_BATTERY_OVERVOLTAGE: + *val = POWER_SUPPLY_HEALTH_OVERVOLTAGE; + break; + case MAX77693_BATTERY_OVERCURRENT: + *val = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; + break; + case MAX77693_BATTERY_RESERVED: + default: + *val = POWER_SUPPLY_HEALTH_UNKNOWN; + break; + } + + return 0; +} + +static int max77693_get_present(struct regmap *regmap, int *val) +{ + unsigned int data; + int ret; + + /* + * Read CHG_INT_OK register. High DETBAT bit here should be + * equal to value 0x0 in CHG_DETAILS_01/BAT field. + */ + ret = regmap_read(regmap, MAX77693_CHG_REG_CHG_INT_OK, &data); + if (ret < 0) + return ret; + + *val = (data & CHG_INT_OK_DETBAT_MASK) ? 0 : 1; + + return 0; +} + +static int max77693_get_online(struct regmap *regmap, int *val) +{ + unsigned int data; + int ret; + + ret = regmap_read(regmap, MAX77693_CHG_REG_CHG_INT_OK, &data); + if (ret < 0) + return ret; + + *val = (data & CHG_INT_OK_CHGIN_MASK) ? 1 : 0; + + return 0; +} + +static enum power_supply_property max77693_charger_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_CHARGE_TYPE, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_MODEL_NAME, + POWER_SUPPLY_PROP_MANUFACTURER, +}; + +static int max77693_charger_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct max77693_charger *chg = power_supply_get_drvdata(psy); + struct regmap *regmap = chg->max77693->regmap; + int ret = 0; + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + ret = max77693_get_charger_state(regmap, &val->intval); + break; + case POWER_SUPPLY_PROP_CHARGE_TYPE: + ret = max77693_get_charge_type(regmap, &val->intval); + break; + case POWER_SUPPLY_PROP_HEALTH: + ret = max77693_get_battery_health(regmap, &val->intval); + break; + case POWER_SUPPLY_PROP_PRESENT: + ret = max77693_get_present(regmap, &val->intval); + break; + case POWER_SUPPLY_PROP_ONLINE: + ret = max77693_get_online(regmap, &val->intval); + break; + case POWER_SUPPLY_PROP_MODEL_NAME: + val->strval = max77693_charger_model; + break; + case POWER_SUPPLY_PROP_MANUFACTURER: + val->strval = max77693_charger_manufacturer; + break; + default: + return -EINVAL; + } + + return ret; +} + +static const struct power_supply_desc max77693_charger_desc = { + .name = MAX77693_CHARGER_NAME, + .type = POWER_SUPPLY_TYPE_BATTERY, + .properties = max77693_charger_props, + .num_properties = ARRAY_SIZE(max77693_charger_props), + .get_property = max77693_charger_get_property, +}; + +static ssize_t device_attr_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count, + int (*fn)(struct max77693_charger *, unsigned long)) +{ + struct max77693_charger *chg = dev_get_drvdata(dev); + unsigned long val; + int ret; + + ret = kstrtoul(buf, 10, &val); + if (ret) + return ret; + + ret = fn(chg, val); + if (ret) + return ret; + + return count; +} + +static ssize_t fast_charge_timer_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct max77693_charger *chg = dev_get_drvdata(dev); + unsigned int data, val; + int ret; + + ret = regmap_read(chg->max77693->regmap, MAX77693_CHG_REG_CHG_CNFG_01, + &data); + if (ret < 0) + return ret; + + data &= CHG_CNFG_01_FCHGTIME_MASK; + data >>= CHG_CNFG_01_FCHGTIME_SHIFT; + switch (data) { + case 0x1 ... 0x7: + /* Starting from 4 hours, step by 2 hours */ + val = 4 + (data - 1) * 2; + break; + case 0x0: + default: + val = 0; + break; + } + + return scnprintf(buf, PAGE_SIZE, "%u\n", val); +} + +static int max77693_set_fast_charge_timer(struct max77693_charger *chg, + unsigned long hours) +{ + unsigned int data; + + /* + * 0x00 - disable + * 0x01 - 4h + * 0x02 - 6h + * ... + * 0x07 - 16h + * Round down odd values. + */ + switch (hours) { + case 4 ... 16: + data = (hours - 4) / 2 + 1; + break; + case 0: + /* Disable */ + data = 0; + break; + default: + return -EINVAL; + } + data <<= CHG_CNFG_01_FCHGTIME_SHIFT; + + return regmap_update_bits(chg->max77693->regmap, + MAX77693_CHG_REG_CHG_CNFG_01, + CHG_CNFG_01_FCHGTIME_MASK, data); +} + +static ssize_t fast_charge_timer_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + return device_attr_store(dev, attr, buf, count, + max77693_set_fast_charge_timer); +} + +static ssize_t top_off_threshold_current_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct max77693_charger *chg = dev_get_drvdata(dev); + unsigned int data, val; + int ret; + + ret = regmap_read(chg->max77693->regmap, MAX77693_CHG_REG_CHG_CNFG_03, + &data); + if (ret < 0) + return ret; + + data &= CHG_CNFG_03_TOITH_MASK; + data >>= CHG_CNFG_03_TOITH_SHIFT; + + if (data <= 0x04) + val = 100000 + data * 25000; + else + val = data * 50000; + + return scnprintf(buf, PAGE_SIZE, "%u\n", val); +} + +static int max77693_set_top_off_threshold_current(struct max77693_charger *chg, + unsigned long uamp) +{ + unsigned int data; + + if (uamp < 100000 || uamp > 350000) + return -EINVAL; + + if (uamp <= 200000) + data = (uamp - 100000) / 25000; + else + /* (200000, 350000> */ + data = uamp / 50000; + + data <<= CHG_CNFG_03_TOITH_SHIFT; + + return regmap_update_bits(chg->max77693->regmap, + MAX77693_CHG_REG_CHG_CNFG_03, + CHG_CNFG_03_TOITH_MASK, data); +} + +static ssize_t top_off_threshold_current_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + return device_attr_store(dev, attr, buf, count, + max77693_set_top_off_threshold_current); +} + +static ssize_t top_off_timer_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct max77693_charger *chg = dev_get_drvdata(dev); + unsigned int data, val; + int ret; + + ret = regmap_read(chg->max77693->regmap, MAX77693_CHG_REG_CHG_CNFG_03, + &data); + if (ret < 0) + return ret; + + data &= CHG_CNFG_03_TOTIME_MASK; + data >>= CHG_CNFG_03_TOTIME_SHIFT; + + val = data * 10; + + return scnprintf(buf, PAGE_SIZE, "%u\n", val); +} + +static int max77693_set_top_off_timer(struct max77693_charger *chg, + unsigned long minutes) +{ + unsigned int data; + + if (minutes > 70) + return -EINVAL; + + data = minutes / 10; + data <<= CHG_CNFG_03_TOTIME_SHIFT; + + return regmap_update_bits(chg->max77693->regmap, + MAX77693_CHG_REG_CHG_CNFG_03, + CHG_CNFG_03_TOTIME_MASK, data); +} + +static ssize_t top_off_timer_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + return device_attr_store(dev, attr, buf, count, + max77693_set_top_off_timer); +} + +static DEVICE_ATTR_RW(fast_charge_timer); +static DEVICE_ATTR_RW(top_off_threshold_current); +static DEVICE_ATTR_RW(top_off_timer); + +static int max77693_set_constant_volt(struct max77693_charger *chg, + unsigned int uvolt) +{ + unsigned int data; + + /* + * 0x00 - 3.650 V + * 0x01 - 3.675 V + * ... + * 0x1b - 4.325 V + * 0x1c - 4.340 V + * 0x1d - 4.350 V + * 0x1e - 4.375 V + * 0x1f - 4.400 V + */ + if (uvolt >= 3650000 && uvolt < 4340000) + data = (uvolt - 3650000) / 25000; + else if (uvolt >= 4340000 && uvolt < 4350000) + data = 0x1c; + else if (uvolt >= 4350000 && uvolt <= 4400000) + data = 0x1d + (uvolt - 4350000) / 25000; + else { + dev_err(chg->dev, "Wrong value for charging constant voltage\n"); + return -EINVAL; + } + + data <<= CHG_CNFG_04_CHGCVPRM_SHIFT; + + dev_dbg(chg->dev, "Charging constant voltage: %u (0x%x)\n", uvolt, + data); + + return regmap_update_bits(chg->max77693->regmap, + MAX77693_CHG_REG_CHG_CNFG_04, + CHG_CNFG_04_CHGCVPRM_MASK, data); +} + +static int max77693_set_min_system_volt(struct max77693_charger *chg, + unsigned int uvolt) +{ + unsigned int data; + + if (uvolt < 3000000 || uvolt > 3700000) { + dev_err(chg->dev, "Wrong value for minimum system regulation voltage\n"); + return -EINVAL; + } + + data = (uvolt - 3000000) / 100000; + + data <<= CHG_CNFG_04_MINVSYS_SHIFT; + + dev_dbg(chg->dev, "Minimum system regulation voltage: %u (0x%x)\n", + uvolt, data); + + return regmap_update_bits(chg->max77693->regmap, + MAX77693_CHG_REG_CHG_CNFG_04, + CHG_CNFG_04_MINVSYS_MASK, data); +} + +static int max77693_set_thermal_regulation_temp(struct max77693_charger *chg, + unsigned int cels) +{ + unsigned int data; + + switch (cels) { + case 70: + case 85: + case 100: + case 115: + data = (cels - 70) / 15; + break; + default: + dev_err(chg->dev, "Wrong value for thermal regulation loop temperature\n"); + return -EINVAL; + } + + data <<= CHG_CNFG_07_REGTEMP_SHIFT; + + dev_dbg(chg->dev, "Thermal regulation loop temperature: %u (0x%x)\n", + cels, data); + + return regmap_update_bits(chg->max77693->regmap, + MAX77693_CHG_REG_CHG_CNFG_07, + CHG_CNFG_07_REGTEMP_MASK, data); +} + +static int max77693_set_batttery_overcurrent(struct max77693_charger *chg, + unsigned int uamp) +{ + unsigned int data; + + if (uamp && (uamp < 2000000 || uamp > 3500000)) { + dev_err(chg->dev, "Wrong value for battery overcurrent\n"); + return -EINVAL; + } + + if (uamp) + data = ((uamp - 2000000) / 250000) + 1; + else + data = 0; /* disable */ + + data <<= CHG_CNFG_12_B2SOVRC_SHIFT; + + dev_dbg(chg->dev, "Battery overcurrent: %u (0x%x)\n", uamp, data); + + return regmap_update_bits(chg->max77693->regmap, + MAX77693_CHG_REG_CHG_CNFG_12, + CHG_CNFG_12_B2SOVRC_MASK, data); +} + +static int max77693_set_charge_input_threshold_volt(struct max77693_charger *chg, + unsigned int uvolt) +{ + unsigned int data; + + switch (uvolt) { + case 4300000: + data = 0x0; + break; + case 4700000: + case 4800000: + case 4900000: + data = (uvolt - 4700000) / 100000; + default: + dev_err(chg->dev, "Wrong value for charge input voltage regulation threshold\n"); + return -EINVAL; + } + + data <<= CHG_CNFG_12_VCHGINREG_SHIFT; + + dev_dbg(chg->dev, "Charge input voltage regulation threshold: %u (0x%x)\n", + uvolt, data); + + return regmap_update_bits(chg->max77693->regmap, + MAX77693_CHG_REG_CHG_CNFG_12, + CHG_CNFG_12_VCHGINREG_MASK, data); +} + +/* + * Sets charger registers to proper and safe default values. + */ +static int max77693_reg_init(struct max77693_charger *chg) +{ + int ret; + unsigned int data; + + /* Unlock charger register protection */ + data = (0x3 << CHG_CNFG_06_CHGPROT_SHIFT); + ret = regmap_update_bits(chg->max77693->regmap, + MAX77693_CHG_REG_CHG_CNFG_06, + CHG_CNFG_06_CHGPROT_MASK, data); + if (ret) { + dev_err(chg->dev, "Error unlocking registers: %d\n", ret); + return ret; + } + + ret = max77693_set_fast_charge_timer(chg, DEFAULT_FAST_CHARGE_TIMER); + if (ret) + return ret; + + ret = max77693_set_top_off_threshold_current(chg, + DEFAULT_TOP_OFF_THRESHOLD_CURRENT); + if (ret) + return ret; + + ret = max77693_set_top_off_timer(chg, DEFAULT_TOP_OFF_TIMER); + if (ret) + return ret; + + ret = max77693_set_constant_volt(chg, chg->constant_volt); + if (ret) + return ret; + + ret = max77693_set_min_system_volt(chg, chg->min_system_volt); + if (ret) + return ret; + + ret = max77693_set_thermal_regulation_temp(chg, + chg->thermal_regulation_temp); + if (ret) + return ret; + + ret = max77693_set_batttery_overcurrent(chg, chg->batttery_overcurrent); + if (ret) + return ret; + + return max77693_set_charge_input_threshold_volt(chg, + chg->charge_input_threshold_volt); +} + +#ifdef CONFIG_OF +static int max77693_dt_init(struct device *dev, struct max77693_charger *chg) +{ + struct device_node *np = dev->of_node; + + if (!np) { + dev_err(dev, "no charger OF node\n"); + return -EINVAL; + } + + if (of_property_read_u32(np, "maxim,constant-microvolt", + &chg->constant_volt)) + chg->constant_volt = DEFAULT_CONSTANT_VOLT; + + if (of_property_read_u32(np, "maxim,min-system-microvolt", + &chg->min_system_volt)) + chg->min_system_volt = DEFAULT_MIN_SYSTEM_VOLT; + + if (of_property_read_u32(np, "maxim,thermal-regulation-celsius", + &chg->thermal_regulation_temp)) + chg->thermal_regulation_temp = DEFAULT_THERMAL_REGULATION_TEMP; + + if (of_property_read_u32(np, "maxim,battery-overcurrent-microamp", + &chg->batttery_overcurrent)) + chg->batttery_overcurrent = DEFAULT_BATTERY_OVERCURRENT; + + if (of_property_read_u32(np, "maxim,charge-input-threshold-microvolt", + &chg->charge_input_threshold_volt)) + chg->charge_input_threshold_volt = + DEFAULT_CHARGER_INPUT_THRESHOLD_VOLT; + + return 0; +} +#else /* CONFIG_OF */ +static int max77693_dt_init(struct device *dev, struct max77693_charger *chg) +{ + return 0; +} +#endif /* CONFIG_OF */ + +static int max77693_charger_probe(struct platform_device *pdev) +{ + struct max77693_charger *chg; + struct power_supply_config psy_cfg = {}; + struct max77693_dev *max77693 = dev_get_drvdata(pdev->dev.parent); + int ret; + + chg = devm_kzalloc(&pdev->dev, sizeof(*chg), GFP_KERNEL); + if (!chg) + return -ENOMEM; + + platform_set_drvdata(pdev, chg); + chg->dev = &pdev->dev; + chg->max77693 = max77693; + + ret = max77693_dt_init(&pdev->dev, chg); + if (ret) + return ret; + + ret = max77693_reg_init(chg); + if (ret) + return ret; + + psy_cfg.drv_data = chg; + + ret = device_create_file(&pdev->dev, &dev_attr_fast_charge_timer); + if (ret) { + dev_err(&pdev->dev, "failed: create fast charge timer sysfs entry\n"); + goto err; + } + + ret = device_create_file(&pdev->dev, + &dev_attr_top_off_threshold_current); + if (ret) { + dev_err(&pdev->dev, "failed: create top off current sysfs entry\n"); + goto err; + } + + ret = device_create_file(&pdev->dev, &dev_attr_top_off_timer); + if (ret) { + dev_err(&pdev->dev, "failed: create top off timer sysfs entry\n"); + goto err; + } + + chg->charger = power_supply_register(&pdev->dev, + &max77693_charger_desc, + &psy_cfg); + if (IS_ERR(chg->charger)) { + dev_err(&pdev->dev, "failed: power supply register\n"); + ret = PTR_ERR(chg->charger); + goto err; + } + + return 0; + +err: + device_remove_file(&pdev->dev, &dev_attr_top_off_timer); + device_remove_file(&pdev->dev, &dev_attr_top_off_threshold_current); + device_remove_file(&pdev->dev, &dev_attr_fast_charge_timer); + + return ret; +} + +static int max77693_charger_remove(struct platform_device *pdev) +{ + struct max77693_charger *chg = platform_get_drvdata(pdev); + + device_remove_file(&pdev->dev, &dev_attr_top_off_timer); + device_remove_file(&pdev->dev, &dev_attr_top_off_threshold_current); + device_remove_file(&pdev->dev, &dev_attr_fast_charge_timer); + + power_supply_unregister(chg->charger); + + return 0; +} + +static const struct platform_device_id max77693_charger_id[] = { + { "max77693-charger", 0, }, + { } +}; +MODULE_DEVICE_TABLE(platform, max77693_charger_id); + +static struct platform_driver max77693_charger_driver = { + .driver = { + .name = "max77693-charger", + }, + .probe = max77693_charger_probe, + .remove = max77693_charger_remove, + .id_table = max77693_charger_id, +}; +module_platform_driver(max77693_charger_driver); + +MODULE_AUTHOR("Krzysztof Kozlowski "); +MODULE_DESCRIPTION("Maxim 77693 charger driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/power/supply/max8903_charger.c b/drivers/power/supply/max8903_charger.c new file mode 100644 index 000000000000..fdc73d686153 --- /dev/null +++ b/drivers/power/supply/max8903_charger.c @@ -0,0 +1,459 @@ +/* + * max8903_charger.c - Maxim 8903 USB/Adapter Charger Driver + * + * Copyright (C) 2011 Samsung Electronics + * MyungJoo Ham + * + * 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 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 + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct max8903_data { + struct max8903_pdata *pdata; + struct device *dev; + struct power_supply *psy; + struct power_supply_desc psy_desc; + bool fault; + bool usb_in; + bool ta_in; +}; + +static enum power_supply_property max8903_charger_props[] = { + POWER_SUPPLY_PROP_STATUS, /* Charger status output */ + POWER_SUPPLY_PROP_ONLINE, /* External power source */ + POWER_SUPPLY_PROP_HEALTH, /* Fault or OK */ +}; + +static int max8903_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct max8903_data *data = power_supply_get_drvdata(psy); + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + val->intval = POWER_SUPPLY_STATUS_UNKNOWN; + if (gpio_is_valid(data->pdata->chg)) { + if (gpio_get_value(data->pdata->chg) == 0) + val->intval = POWER_SUPPLY_STATUS_CHARGING; + else if (data->usb_in || data->ta_in) + val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; + else + val->intval = POWER_SUPPLY_STATUS_DISCHARGING; + } + break; + case POWER_SUPPLY_PROP_ONLINE: + val->intval = 0; + if (data->usb_in || data->ta_in) + val->intval = 1; + break; + case POWER_SUPPLY_PROP_HEALTH: + val->intval = POWER_SUPPLY_HEALTH_GOOD; + if (data->fault) + val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; + break; + default: + return -EINVAL; + } + + return 0; +} + +static irqreturn_t max8903_dcin(int irq, void *_data) +{ + struct max8903_data *data = _data; + struct max8903_pdata *pdata = data->pdata; + bool ta_in; + enum power_supply_type old_type; + + ta_in = gpio_get_value(pdata->dok) ? false : true; + + if (ta_in == data->ta_in) + return IRQ_HANDLED; + + data->ta_in = ta_in; + + /* Set Current-Limit-Mode 1:DC 0:USB */ + if (gpio_is_valid(pdata->dcm)) + gpio_set_value(pdata->dcm, ta_in ? 1 : 0); + + /* Charger Enable / Disable (cen is negated) */ + if (gpio_is_valid(pdata->cen)) + gpio_set_value(pdata->cen, ta_in ? 0 : + (data->usb_in ? 0 : 1)); + + dev_dbg(data->dev, "TA(DC-IN) Charger %s.\n", ta_in ? + "Connected" : "Disconnected"); + + old_type = data->psy_desc.type; + + if (data->ta_in) + data->psy_desc.type = POWER_SUPPLY_TYPE_MAINS; + else if (data->usb_in) + data->psy_desc.type = POWER_SUPPLY_TYPE_USB; + else + data->psy_desc.type = POWER_SUPPLY_TYPE_BATTERY; + + if (old_type != data->psy_desc.type) + power_supply_changed(data->psy); + + return IRQ_HANDLED; +} + +static irqreturn_t max8903_usbin(int irq, void *_data) +{ + struct max8903_data *data = _data; + struct max8903_pdata *pdata = data->pdata; + bool usb_in; + enum power_supply_type old_type; + + usb_in = gpio_get_value(pdata->uok) ? false : true; + + if (usb_in == data->usb_in) + return IRQ_HANDLED; + + data->usb_in = usb_in; + + /* Do not touch Current-Limit-Mode */ + + /* Charger Enable / Disable (cen is negated) */ + if (gpio_is_valid(pdata->cen)) + gpio_set_value(pdata->cen, usb_in ? 0 : + (data->ta_in ? 0 : 1)); + + dev_dbg(data->dev, "USB Charger %s.\n", usb_in ? + "Connected" : "Disconnected"); + + old_type = data->psy_desc.type; + + if (data->ta_in) + data->psy_desc.type = POWER_SUPPLY_TYPE_MAINS; + else if (data->usb_in) + data->psy_desc.type = POWER_SUPPLY_TYPE_USB; + else + data->psy_desc.type = POWER_SUPPLY_TYPE_BATTERY; + + if (old_type != data->psy_desc.type) + power_supply_changed(data->psy); + + return IRQ_HANDLED; +} + +static irqreturn_t max8903_fault(int irq, void *_data) +{ + struct max8903_data *data = _data; + struct max8903_pdata *pdata = data->pdata; + bool fault; + + fault = gpio_get_value(pdata->flt) ? false : true; + + if (fault == data->fault) + return IRQ_HANDLED; + + data->fault = fault; + + if (fault) + dev_err(data->dev, "Charger suffers a fault and stops.\n"); + else + dev_err(data->dev, "Charger recovered from a fault.\n"); + + return IRQ_HANDLED; +} + +static struct max8903_pdata *max8903_parse_dt_data(struct device *dev) +{ + struct device_node *np = dev->of_node; + struct max8903_pdata *pdata = NULL; + + if (!np) + return NULL; + + pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) + return NULL; + + pdata->dc_valid = false; + pdata->usb_valid = false; + + pdata->cen = of_get_named_gpio(np, "cen-gpios", 0); + if (!gpio_is_valid(pdata->cen)) + pdata->cen = -EINVAL; + + pdata->chg = of_get_named_gpio(np, "chg-gpios", 0); + if (!gpio_is_valid(pdata->chg)) + pdata->chg = -EINVAL; + + pdata->flt = of_get_named_gpio(np, "flt-gpios", 0); + if (!gpio_is_valid(pdata->flt)) + pdata->flt = -EINVAL; + + pdata->usus = of_get_named_gpio(np, "usus-gpios", 0); + if (!gpio_is_valid(pdata->usus)) + pdata->usus = -EINVAL; + + pdata->dcm = of_get_named_gpio(np, "dcm-gpios", 0); + if (!gpio_is_valid(pdata->dcm)) + pdata->dcm = -EINVAL; + + pdata->dok = of_get_named_gpio(np, "dok-gpios", 0); + if (!gpio_is_valid(pdata->dok)) + pdata->dok = -EINVAL; + else + pdata->dc_valid = true; + + pdata->uok = of_get_named_gpio(np, "uok-gpios", 0); + if (!gpio_is_valid(pdata->uok)) + pdata->uok = -EINVAL; + else + pdata->usb_valid = true; + + return pdata; +} + +static int max8903_setup_gpios(struct platform_device *pdev) +{ + struct max8903_data *data = platform_get_drvdata(pdev); + struct device *dev = &pdev->dev; + struct max8903_pdata *pdata = pdev->dev.platform_data; + int ret = 0; + int gpio; + int ta_in = 0; + int usb_in = 0; + + if (pdata->dc_valid) { + if (gpio_is_valid(pdata->dok)) { + ret = devm_gpio_request(dev, pdata->dok, + data->psy_desc.name); + if (ret) { + dev_err(dev, + "Failed GPIO request for dok: %d err %d\n", + pdata->dok, ret); + return ret; + } + + gpio = pdata->dok; /* PULL_UPed Interrupt */ + ta_in = gpio_get_value(gpio) ? 0 : 1; + } else { + dev_err(dev, "When DC is wired, DOK should be wired as well.\n"); + return -EINVAL; + } + } + + if (gpio_is_valid(pdata->dcm)) { + ret = devm_gpio_request(dev, pdata->dcm, data->psy_desc.name); + if (ret) { + dev_err(dev, + "Failed GPIO request for dcm: %d err %d\n", + pdata->dcm, ret); + return ret; + } + + gpio = pdata->dcm; /* Output */ + gpio_set_value(gpio, ta_in); + } + + if (pdata->usb_valid) { + if (gpio_is_valid(pdata->uok)) { + ret = devm_gpio_request(dev, pdata->uok, + data->psy_desc.name); + if (ret) { + dev_err(dev, + "Failed GPIO request for uok: %d err %d\n", + pdata->uok, ret); + return ret; + } + + gpio = pdata->uok; + usb_in = gpio_get_value(gpio) ? 0 : 1; + } else { + dev_err(dev, "When USB is wired, UOK should be wired." + "as well.\n"); + return -EINVAL; + } + } + + if (gpio_is_valid(pdata->cen)) { + ret = devm_gpio_request(dev, pdata->cen, data->psy_desc.name); + if (ret) { + dev_err(dev, + "Failed GPIO request for cen: %d err %d\n", + pdata->cen, ret); + return ret; + } + + gpio_set_value(pdata->cen, (ta_in || usb_in) ? 0 : 1); + } + + if (gpio_is_valid(pdata->chg)) { + ret = devm_gpio_request(dev, pdata->chg, data->psy_desc.name); + if (ret) { + dev_err(dev, + "Failed GPIO request for chg: %d err %d\n", + pdata->chg, ret); + return ret; + } + } + + if (gpio_is_valid(pdata->flt)) { + ret = devm_gpio_request(dev, pdata->flt, data->psy_desc.name); + if (ret) { + dev_err(dev, + "Failed GPIO request for flt: %d err %d\n", + pdata->flt, ret); + return ret; + } + } + + if (gpio_is_valid(pdata->usus)) { + ret = devm_gpio_request(dev, pdata->usus, data->psy_desc.name); + if (ret) { + dev_err(dev, + "Failed GPIO request for usus: %d err %d\n", + pdata->usus, ret); + return ret; + } + } + + data->fault = false; + data->ta_in = ta_in; + data->usb_in = usb_in; + + return 0; +} + +static int max8903_probe(struct platform_device *pdev) +{ + struct max8903_data *data; + struct device *dev = &pdev->dev; + struct max8903_pdata *pdata = pdev->dev.platform_data; + struct power_supply_config psy_cfg = {}; + int ret = 0; + + data = devm_kzalloc(dev, sizeof(struct max8903_data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + if (IS_ENABLED(CONFIG_OF) && !pdata && dev->of_node) + pdata = max8903_parse_dt_data(dev); + + if (!pdata) { + dev_err(dev, "No platform data.\n"); + return -EINVAL; + } + + pdev->dev.platform_data = pdata; + data->pdata = pdata; + data->dev = dev; + platform_set_drvdata(pdev, data); + + if (pdata->dc_valid == false && pdata->usb_valid == false) { + dev_err(dev, "No valid power sources.\n"); + return -EINVAL; + } + + ret = max8903_setup_gpios(pdev); + if (ret) + return ret; + + data->psy_desc.name = "max8903_charger"; + data->psy_desc.type = (data->ta_in) ? POWER_SUPPLY_TYPE_MAINS : + ((data->usb_in) ? POWER_SUPPLY_TYPE_USB : + POWER_SUPPLY_TYPE_BATTERY); + data->psy_desc.get_property = max8903_get_property; + data->psy_desc.properties = max8903_charger_props; + data->psy_desc.num_properties = ARRAY_SIZE(max8903_charger_props); + + psy_cfg.of_node = dev->of_node; + psy_cfg.drv_data = data; + + 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"); + return PTR_ERR(data->psy); + } + + if (pdata->dc_valid) { + ret = devm_request_threaded_irq(dev, gpio_to_irq(pdata->dok), + NULL, max8903_dcin, + IRQF_TRIGGER_FALLING | + IRQF_TRIGGER_RISING | IRQF_ONESHOT, + "MAX8903 DC IN", data); + if (ret) { + dev_err(dev, "Cannot request irq %d for DC (%d)\n", + gpio_to_irq(pdata->dok), ret); + return ret; + } + } + + if (pdata->usb_valid) { + ret = devm_request_threaded_irq(dev, gpio_to_irq(pdata->uok), + NULL, max8903_usbin, + IRQF_TRIGGER_FALLING | + IRQF_TRIGGER_RISING | IRQF_ONESHOT, + "MAX8903 USB IN", data); + if (ret) { + dev_err(dev, "Cannot request irq %d for USB (%d)\n", + gpio_to_irq(pdata->uok), ret); + return ret; + } + } + + if (gpio_is_valid(pdata->flt)) { + ret = devm_request_threaded_irq(dev, gpio_to_irq(pdata->flt), + NULL, max8903_fault, + IRQF_TRIGGER_FALLING | + IRQF_TRIGGER_RISING | IRQF_ONESHOT, + "MAX8903 Fault", data); + if (ret) { + dev_err(dev, "Cannot request irq %d for Fault (%d)\n", + gpio_to_irq(pdata->flt), ret); + return ret; + } + } + + return 0; +} + +static const struct of_device_id max8903_match_ids[] = { + { .compatible = "maxim,max8903", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, max8903_match_ids); + +static struct platform_driver max8903_driver = { + .probe = max8903_probe, + .driver = { + .name = "max8903-charger", + .of_match_table = max8903_match_ids + }, +}; + +module_platform_driver(max8903_driver); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("MAX8903 Charger Driver"); +MODULE_AUTHOR("MyungJoo Ham "); +MODULE_ALIAS("platform:max8903-charger"); diff --git a/drivers/power/supply/max8925_power.c b/drivers/power/supply/max8925_power.c new file mode 100644 index 000000000000..3b94620ce5c1 --- /dev/null +++ b/drivers/power/supply/max8925_power.c @@ -0,0 +1,596 @@ +/* + * Battery driver for Maxim MAX8925 + * + * Copyright (c) 2009-2010 Marvell International Ltd. + * Haojian Zhuang + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* registers in GPM */ +#define MAX8925_OUT5VEN 0x54 +#define MAX8925_OUT3VEN 0x58 +#define MAX8925_CHG_CNTL1 0x7c + +/* bits definition */ +#define MAX8925_CHG_STAT_VSYSLOW (1 << 0) +#define MAX8925_CHG_STAT_MODE_MASK (3 << 2) +#define MAX8925_CHG_STAT_EN_MASK (1 << 4) +#define MAX8925_CHG_MBDET (1 << 1) +#define MAX8925_CHG_AC_RANGE_MASK (3 << 6) + +/* registers in ADC */ +#define MAX8925_ADC_RES_CNFG1 0x06 +#define MAX8925_ADC_AVG_CNFG1 0x07 +#define MAX8925_ADC_ACQ_CNFG1 0x08 +#define MAX8925_ADC_ACQ_CNFG2 0x09 +/* 2 bytes registers in below. MSB is 1st, LSB is 2nd. */ +#define MAX8925_ADC_AUX2 0x62 +#define MAX8925_ADC_VCHG 0x64 +#define MAX8925_ADC_VBBATT 0x66 +#define MAX8925_ADC_VMBATT 0x68 +#define MAX8925_ADC_ISNS 0x6a +#define MAX8925_ADC_THM 0x6c +#define MAX8925_ADC_TDIE 0x6e +#define MAX8925_CMD_AUX2 0xc8 +#define MAX8925_CMD_VCHG 0xd0 +#define MAX8925_CMD_VBBATT 0xd8 +#define MAX8925_CMD_VMBATT 0xe0 +#define MAX8925_CMD_ISNS 0xe8 +#define MAX8925_CMD_THM 0xf0 +#define MAX8925_CMD_TDIE 0xf8 + +enum { + MEASURE_AUX2, + MEASURE_VCHG, + MEASURE_VBBATT, + MEASURE_VMBATT, + MEASURE_ISNS, + MEASURE_THM, + MEASURE_TDIE, + MEASURE_MAX, +}; + +struct max8925_power_info { + struct max8925_chip *chip; + struct i2c_client *gpm; + struct i2c_client *adc; + + struct power_supply *ac; + struct power_supply *usb; + struct power_supply *battery; + int irq_base; + unsigned ac_online:1; + unsigned usb_online:1; + unsigned bat_online:1; + unsigned chg_mode:2; + unsigned batt_detect:1; /* detecing MB by ID pin */ + unsigned topoff_threshold:2; + unsigned fast_charge:3; + unsigned no_temp_support:1; + unsigned no_insert_detect:1; + + int (*set_charger) (int); +}; + +static int __set_charger(struct max8925_power_info *info, int enable) +{ + struct max8925_chip *chip = info->chip; + if (enable) { + /* enable charger in platform */ + if (info->set_charger) + info->set_charger(1); + /* enable charger */ + max8925_set_bits(info->gpm, MAX8925_CHG_CNTL1, 1 << 7, 0); + } else { + /* disable charge */ + max8925_set_bits(info->gpm, MAX8925_CHG_CNTL1, 1 << 7, 1 << 7); + if (info->set_charger) + info->set_charger(0); + } + dev_dbg(chip->dev, "%s\n", (enable) ? "Enable charger" + : "Disable charger"); + return 0; +} + +static irqreturn_t max8925_charger_handler(int irq, void *data) +{ + struct max8925_power_info *info = (struct max8925_power_info *)data; + struct max8925_chip *chip = info->chip; + + switch (irq - chip->irq_base) { + case MAX8925_IRQ_VCHG_DC_R: + info->ac_online = 1; + __set_charger(info, 1); + dev_dbg(chip->dev, "Adapter inserted\n"); + break; + case MAX8925_IRQ_VCHG_DC_F: + info->ac_online = 0; + __set_charger(info, 0); + dev_dbg(chip->dev, "Adapter removed\n"); + break; + case MAX8925_IRQ_VCHG_THM_OK_F: + /* Battery is not ready yet */ + dev_dbg(chip->dev, "Battery temperature is out of range\n"); + case MAX8925_IRQ_VCHG_DC_OVP: + dev_dbg(chip->dev, "Error detection\n"); + __set_charger(info, 0); + break; + case MAX8925_IRQ_VCHG_THM_OK_R: + /* Battery is ready now */ + dev_dbg(chip->dev, "Battery temperature is in range\n"); + break; + case MAX8925_IRQ_VCHG_SYSLOW_R: + /* VSYS is low */ + dev_info(chip->dev, "Sys power is too low\n"); + break; + case MAX8925_IRQ_VCHG_SYSLOW_F: + dev_dbg(chip->dev, "Sys power is above low threshold\n"); + break; + case MAX8925_IRQ_VCHG_DONE: + __set_charger(info, 0); + dev_dbg(chip->dev, "Charging is done\n"); + break; + case MAX8925_IRQ_VCHG_TOPOFF: + dev_dbg(chip->dev, "Charging in top-off mode\n"); + break; + case MAX8925_IRQ_VCHG_TMR_FAULT: + __set_charger(info, 0); + dev_dbg(chip->dev, "Safe timer is expired\n"); + break; + case MAX8925_IRQ_VCHG_RST: + __set_charger(info, 0); + dev_dbg(chip->dev, "Charger is reset\n"); + break; + } + return IRQ_HANDLED; +} + +static int start_measure(struct max8925_power_info *info, int type) +{ + unsigned char buf[2] = {0, 0}; + int meas_cmd; + int meas_reg = 0, ret; + + switch (type) { + case MEASURE_VCHG: + meas_cmd = MAX8925_CMD_VCHG; + meas_reg = MAX8925_ADC_VCHG; + break; + case MEASURE_VBBATT: + meas_cmd = MAX8925_CMD_VBBATT; + meas_reg = MAX8925_ADC_VBBATT; + break; + case MEASURE_VMBATT: + meas_cmd = MAX8925_CMD_VMBATT; + meas_reg = MAX8925_ADC_VMBATT; + break; + case MEASURE_ISNS: + meas_cmd = MAX8925_CMD_ISNS; + meas_reg = MAX8925_ADC_ISNS; + break; + default: + return -EINVAL; + } + + max8925_reg_write(info->adc, meas_cmd, 0); + max8925_bulk_read(info->adc, meas_reg, 2, buf); + ret = ((buf[0]<<8) | buf[1]) >> 4; + + return ret; +} + +static int max8925_ac_get_prop(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct max8925_power_info *info = dev_get_drvdata(psy->dev.parent); + int ret = 0; + + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + val->intval = info->ac_online; + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + if (info->ac_online) { + ret = start_measure(info, MEASURE_VCHG); + if (ret >= 0) { + val->intval = ret * 2000; /* unit is uV */ + goto out; + } + } + ret = -ENODATA; + break; + default: + ret = -ENODEV; + break; + } +out: + return ret; +} + +static enum power_supply_property max8925_ac_props[] = { + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_VOLTAGE_NOW, +}; + +static int max8925_usb_get_prop(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct max8925_power_info *info = dev_get_drvdata(psy->dev.parent); + int ret = 0; + + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + val->intval = info->usb_online; + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + if (info->usb_online) { + ret = start_measure(info, MEASURE_VCHG); + if (ret >= 0) { + val->intval = ret * 2000; /* unit is uV */ + goto out; + } + } + ret = -ENODATA; + break; + default: + ret = -ENODEV; + break; + } +out: + return ret; +} + +static enum power_supply_property max8925_usb_props[] = { + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_VOLTAGE_NOW, +}; + +static int max8925_bat_get_prop(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct max8925_power_info *info = dev_get_drvdata(psy->dev.parent); + int ret = 0; + + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + val->intval = info->bat_online; + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + if (info->bat_online) { + ret = start_measure(info, MEASURE_VMBATT); + if (ret >= 0) { + val->intval = ret * 2000; /* unit is uV */ + ret = 0; + break; + } + } + ret = -ENODATA; + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + if (info->bat_online) { + ret = start_measure(info, MEASURE_ISNS); + if (ret >= 0) { + /* assume r_sns is 0.02 */ + ret = ((ret * 6250) - 3125) /* uA */; + val->intval = 0; + if (ret > 0) + val->intval = ret; /* unit is mA */ + ret = 0; + break; + } + } + ret = -ENODATA; + break; + case POWER_SUPPLY_PROP_CHARGE_TYPE: + if (!info->bat_online) { + ret = -ENODATA; + break; + } + ret = max8925_reg_read(info->gpm, MAX8925_CHG_STATUS); + ret = (ret & MAX8925_CHG_STAT_MODE_MASK) >> 2; + switch (ret) { + case 1: + val->intval = POWER_SUPPLY_CHARGE_TYPE_FAST; + break; + case 0: + case 2: + val->intval = POWER_SUPPLY_CHARGE_TYPE_TRICKLE; + break; + case 3: + val->intval = POWER_SUPPLY_CHARGE_TYPE_NONE; + break; + } + ret = 0; + break; + case POWER_SUPPLY_PROP_STATUS: + if (!info->bat_online) { + ret = -ENODATA; + break; + } + ret = max8925_reg_read(info->gpm, MAX8925_CHG_STATUS); + if (info->usb_online || info->ac_online) { + val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; + if (ret & MAX8925_CHG_STAT_EN_MASK) + val->intval = POWER_SUPPLY_STATUS_CHARGING; + } else + val->intval = POWER_SUPPLY_STATUS_DISCHARGING; + ret = 0; + break; + default: + ret = -ENODEV; + break; + } + return ret; +} + +static enum power_supply_property max8925_battery_props[] = { + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_CHARGE_TYPE, + POWER_SUPPLY_PROP_STATUS, +}; + +static const struct power_supply_desc ac_desc = { + .name = "max8925-ac", + .type = POWER_SUPPLY_TYPE_MAINS, + .properties = max8925_ac_props, + .num_properties = ARRAY_SIZE(max8925_ac_props), + .get_property = max8925_ac_get_prop, +}; + +static const struct power_supply_desc usb_desc = { + .name = "max8925-usb", + .type = POWER_SUPPLY_TYPE_USB, + .properties = max8925_usb_props, + .num_properties = ARRAY_SIZE(max8925_usb_props), + .get_property = max8925_usb_get_prop, +}; + +static const struct power_supply_desc battery_desc = { + .name = "max8925-battery", + .type = POWER_SUPPLY_TYPE_BATTERY, + .properties = max8925_battery_props, + .num_properties = ARRAY_SIZE(max8925_battery_props), + .get_property = max8925_bat_get_prop, +}; + +#define REQUEST_IRQ(_irq, _name) \ +do { \ + ret = request_threaded_irq(chip->irq_base + _irq, NULL, \ + max8925_charger_handler, \ + IRQF_ONESHOT, _name, info); \ + if (ret) \ + dev_err(chip->dev, "Failed to request IRQ #%d: %d\n", \ + _irq, ret); \ +} while (0) + +static int max8925_init_charger(struct max8925_chip *chip, + struct max8925_power_info *info) +{ + int ret; + + REQUEST_IRQ(MAX8925_IRQ_VCHG_DC_OVP, "ac-ovp"); + if (!info->no_insert_detect) { + REQUEST_IRQ(MAX8925_IRQ_VCHG_DC_F, "ac-remove"); + REQUEST_IRQ(MAX8925_IRQ_VCHG_DC_R, "ac-insert"); + } + if (!info->no_temp_support) { + REQUEST_IRQ(MAX8925_IRQ_VCHG_THM_OK_R, "batt-temp-in-range"); + REQUEST_IRQ(MAX8925_IRQ_VCHG_THM_OK_F, "batt-temp-out-range"); + } + REQUEST_IRQ(MAX8925_IRQ_VCHG_SYSLOW_F, "vsys-high"); + REQUEST_IRQ(MAX8925_IRQ_VCHG_SYSLOW_R, "vsys-low"); + REQUEST_IRQ(MAX8925_IRQ_VCHG_RST, "charger-reset"); + REQUEST_IRQ(MAX8925_IRQ_VCHG_DONE, "charger-done"); + REQUEST_IRQ(MAX8925_IRQ_VCHG_TOPOFF, "charger-topoff"); + REQUEST_IRQ(MAX8925_IRQ_VCHG_TMR_FAULT, "charger-timer-expire"); + + info->usb_online = 0; + info->bat_online = 0; + + /* check for power - can miss interrupt at boot time */ + if (start_measure(info, MEASURE_VCHG) * 2000 > 500000) + info->ac_online = 1; + else + info->ac_online = 0; + + ret = max8925_reg_read(info->gpm, MAX8925_CHG_STATUS); + if (ret >= 0) { + /* + * If battery detection is enabled, ID pin of battery is + * connected to MBDET pin of MAX8925. It could be used to + * detect battery presence. + * Otherwise, we have to assume that battery is always on. + */ + if (info->batt_detect) + info->bat_online = (ret & MAX8925_CHG_MBDET) ? 0 : 1; + else + info->bat_online = 1; + if (ret & MAX8925_CHG_AC_RANGE_MASK) + info->ac_online = 1; + else + info->ac_online = 0; + } + /* disable charge */ + max8925_set_bits(info->gpm, MAX8925_CHG_CNTL1, 1 << 7, 1 << 7); + /* set charging current in charge topoff mode */ + max8925_set_bits(info->gpm, MAX8925_CHG_CNTL1, 3 << 5, + info->topoff_threshold << 5); + /* set charing current in fast charge mode */ + max8925_set_bits(info->gpm, MAX8925_CHG_CNTL1, 7, info->fast_charge); + + return 0; +} + +static int max8925_deinit_charger(struct max8925_power_info *info) +{ + struct max8925_chip *chip = info->chip; + int irq; + + irq = chip->irq_base + MAX8925_IRQ_VCHG_DC_OVP; + for (; irq <= chip->irq_base + MAX8925_IRQ_VCHG_TMR_FAULT; irq++) + free_irq(irq, info); + + return 0; +} + +#ifdef CONFIG_OF +static struct max8925_power_pdata * +max8925_power_dt_init(struct platform_device *pdev) +{ + struct device_node *nproot = pdev->dev.parent->of_node; + struct device_node *np; + int batt_detect; + int topoff_threshold; + int fast_charge; + int no_temp_support; + int no_insert_detect; + struct max8925_power_pdata *pdata; + + if (!nproot) + return pdev->dev.platform_data; + + np = of_get_child_by_name(nproot, "charger"); + if (!np) { + dev_err(&pdev->dev, "failed to find charger node\n"); + return NULL; + } + + pdata = devm_kzalloc(&pdev->dev, + sizeof(struct max8925_power_pdata), + GFP_KERNEL); + if (!pdata) + goto ret; + + of_property_read_u32(np, "topoff-threshold", &topoff_threshold); + of_property_read_u32(np, "batt-detect", &batt_detect); + of_property_read_u32(np, "fast-charge", &fast_charge); + of_property_read_u32(np, "no-insert-detect", &no_insert_detect); + of_property_read_u32(np, "no-temp-support", &no_temp_support); + + pdata->batt_detect = batt_detect; + pdata->fast_charge = fast_charge; + pdata->topoff_threshold = topoff_threshold; + pdata->no_insert_detect = no_insert_detect; + pdata->no_temp_support = no_temp_support; + +ret: + of_node_put(np); + return pdata; +} +#else +static struct max8925_power_pdata * +max8925_power_dt_init(struct platform_device *pdev) +{ + return pdev->dev.platform_data; +} +#endif + +static int max8925_power_probe(struct platform_device *pdev) +{ + struct max8925_chip *chip = dev_get_drvdata(pdev->dev.parent); + struct power_supply_config psy_cfg = {}; /* Only for ac and usb */ + struct max8925_power_pdata *pdata = NULL; + struct max8925_power_info *info; + int ret; + + pdata = max8925_power_dt_init(pdev); + if (!pdata) { + dev_err(&pdev->dev, "platform data isn't assigned to " + "power supply\n"); + return -EINVAL; + } + + info = devm_kzalloc(&pdev->dev, sizeof(struct max8925_power_info), + GFP_KERNEL); + if (!info) + return -ENOMEM; + info->chip = chip; + info->gpm = chip->i2c; + info->adc = chip->adc; + platform_set_drvdata(pdev, info); + + psy_cfg.supplied_to = pdata->supplied_to; + psy_cfg.num_supplicants = pdata->num_supplicants; + + info->ac = power_supply_register(&pdev->dev, &ac_desc, &psy_cfg); + if (IS_ERR(info->ac)) { + ret = PTR_ERR(info->ac); + goto out; + } + info->ac->dev.parent = &pdev->dev; + + info->usb = power_supply_register(&pdev->dev, &usb_desc, &psy_cfg); + if (IS_ERR(info->usb)) { + ret = PTR_ERR(info->usb); + goto out_unregister_ac; + } + info->usb->dev.parent = &pdev->dev; + + info->battery = power_supply_register(&pdev->dev, &battery_desc, NULL); + if (IS_ERR(info->battery)) { + ret = PTR_ERR(info->battery); + goto out_unregister_usb; + } + info->battery->dev.parent = &pdev->dev; + + info->batt_detect = pdata->batt_detect; + info->topoff_threshold = pdata->topoff_threshold; + info->fast_charge = pdata->fast_charge; + info->set_charger = pdata->set_charger; + info->no_temp_support = pdata->no_temp_support; + info->no_insert_detect = pdata->no_insert_detect; + + max8925_init_charger(chip, info); + return 0; +out_unregister_usb: + power_supply_unregister(info->usb); +out_unregister_ac: + power_supply_unregister(info->ac); +out: + return ret; +} + +static int max8925_power_remove(struct platform_device *pdev) +{ + struct max8925_power_info *info = platform_get_drvdata(pdev); + + if (info) { + power_supply_unregister(info->ac); + power_supply_unregister(info->usb); + power_supply_unregister(info->battery); + max8925_deinit_charger(info); + } + return 0; +} + +static struct platform_driver max8925_power_driver = { + .probe = max8925_power_probe, + .remove = max8925_power_remove, + .driver = { + .name = "max8925-power", + }, +}; + +module_platform_driver(max8925_power_driver); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Power supply driver for MAX8925"); +MODULE_ALIAS("platform:max8925-power"); diff --git a/drivers/power/supply/max8997_charger.c b/drivers/power/supply/max8997_charger.c new file mode 100644 index 000000000000..0b2eab571528 --- /dev/null +++ b/drivers/power/supply/max8997_charger.c @@ -0,0 +1,211 @@ +/* + * max8997_charger.c - Power supply consumer driver for the Maxim 8997/8966 + * + * Copyright (C) 2011 Samsung Electronics + * MyungJoo Ham + * + * 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 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 + */ + +#include +#include +#include +#include +#include +#include +#include + +struct charger_data { + struct device *dev; + struct max8997_dev *iodev; + struct power_supply *battery; +}; + +static enum power_supply_property max8997_battery_props[] = { + POWER_SUPPLY_PROP_STATUS, /* "FULL" or "NOT FULL" only. */ + POWER_SUPPLY_PROP_PRESENT, /* the presence of battery */ + POWER_SUPPLY_PROP_ONLINE, /* charger is active or not */ +}; + +/* Note that the charger control is done by a current regulator "CHARGER" */ +static int max8997_battery_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct charger_data *charger = power_supply_get_drvdata(psy); + struct i2c_client *i2c = charger->iodev->i2c; + int ret; + u8 reg; + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + val->intval = 0; + ret = max8997_read_reg(i2c, MAX8997_REG_STATUS4, ®); + if (ret) + return ret; + if ((reg & (1 << 0)) == 0x1) + val->intval = POWER_SUPPLY_STATUS_FULL; + + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = 0; + ret = max8997_read_reg(i2c, MAX8997_REG_STATUS4, ®); + if (ret) + return ret; + if ((reg & (1 << 2)) == 0x0) + val->intval = 1; + + break; + case POWER_SUPPLY_PROP_ONLINE: + val->intval = 0; + ret = max8997_read_reg(i2c, MAX8997_REG_STATUS4, ®); + if (ret) + return ret; + /* DCINOK */ + if (reg & (1 << 1)) + val->intval = 1; + + break; + default: + return -EINVAL; + } + + return 0; +} + +static const struct power_supply_desc max8997_battery_desc = { + .name = "max8997_pmic", + .type = POWER_SUPPLY_TYPE_BATTERY, + .get_property = max8997_battery_get_property, + .properties = max8997_battery_props, + .num_properties = ARRAY_SIZE(max8997_battery_props), +}; + +static int max8997_battery_probe(struct platform_device *pdev) +{ + int ret = 0; + struct charger_data *charger; + struct max8997_dev *iodev = dev_get_drvdata(pdev->dev.parent); + struct max8997_platform_data *pdata = dev_get_platdata(iodev->dev); + struct power_supply_config psy_cfg = {}; + + if (!pdata) + return -EINVAL; + + if (pdata->eoc_mA) { + int val = (pdata->eoc_mA - 50) / 10; + if (val < 0) + val = 0; + if (val > 0xf) + val = 0xf; + + ret = max8997_update_reg(iodev->i2c, + MAX8997_REG_MBCCTRL5, val, 0xf); + if (ret < 0) { + dev_err(&pdev->dev, "Cannot use i2c bus.\n"); + return ret; + } + } + + switch (pdata->timeout) { + case 5: + ret = max8997_update_reg(iodev->i2c, MAX8997_REG_MBCCTRL1, + 0x2 << 4, 0x7 << 4); + break; + case 6: + ret = max8997_update_reg(iodev->i2c, MAX8997_REG_MBCCTRL1, + 0x3 << 4, 0x7 << 4); + break; + case 7: + ret = max8997_update_reg(iodev->i2c, MAX8997_REG_MBCCTRL1, + 0x4 << 4, 0x7 << 4); + break; + case 0: + ret = max8997_update_reg(iodev->i2c, MAX8997_REG_MBCCTRL1, + 0x7 << 4, 0x7 << 4); + break; + default: + dev_err(&pdev->dev, "incorrect timeout value (%d)\n", + pdata->timeout); + return -EINVAL; + } + if (ret < 0) { + dev_err(&pdev->dev, "Cannot use i2c bus.\n"); + return ret; + } + + charger = devm_kzalloc(&pdev->dev, sizeof(struct charger_data), + GFP_KERNEL); + if (charger == NULL) { + dev_err(&pdev->dev, "Cannot allocate memory.\n"); + return -ENOMEM; + } + + platform_set_drvdata(pdev, charger); + + + charger->dev = &pdev->dev; + charger->iodev = iodev; + + psy_cfg.drv_data = charger; + + charger->battery = power_supply_register(&pdev->dev, + &max8997_battery_desc, + &psy_cfg); + if (IS_ERR(charger->battery)) { + dev_err(&pdev->dev, "failed: power supply register\n"); + return PTR_ERR(charger->battery); + } + + 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 }, + { } +}; + +static struct platform_driver max8997_battery_driver = { + .driver = { + .name = "max8997-battery", + }, + .probe = max8997_battery_probe, + .remove = max8997_battery_remove, + .id_table = max8997_battery_id, +}; + +static int __init max8997_battery_init(void) +{ + return platform_driver_register(&max8997_battery_driver); +} +subsys_initcall(max8997_battery_init); + +static void __exit max8997_battery_cleanup(void) +{ + platform_driver_unregister(&max8997_battery_driver); +} +module_exit(max8997_battery_cleanup); + +MODULE_DESCRIPTION("MAXIM 8997/8966 battery control driver"); +MODULE_AUTHOR("MyungJoo Ham "); +MODULE_LICENSE("GPL"); diff --git a/drivers/power/supply/max8998_charger.c b/drivers/power/supply/max8998_charger.c new file mode 100644 index 000000000000..b64cf0f14142 --- /dev/null +++ b/drivers/power/supply/max8998_charger.c @@ -0,0 +1,202 @@ +/* + * max8998_charger.c - Power supply consumer driver for the Maxim 8998/LP3974 + * + * Copyright (C) 2009-2010 Samsung Electronics + * MyungJoo Ham + * + * 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 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 + */ + +#include +#include +#include +#include +#include +#include +#include + +struct max8998_battery_data { + struct device *dev; + struct max8998_dev *iodev; + struct power_supply *battery; +}; + +static enum power_supply_property max8998_battery_props[] = { + POWER_SUPPLY_PROP_PRESENT, /* the presence of battery */ + POWER_SUPPLY_PROP_ONLINE, /* charger is active or not */ +}; + +/* Note that the charger control is done by a current regulator "CHARGER" */ +static int max8998_battery_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct max8998_battery_data *max8998 = power_supply_get_drvdata(psy); + struct i2c_client *i2c = max8998->iodev->i2c; + int ret; + u8 reg; + + switch (psp) { + case POWER_SUPPLY_PROP_PRESENT: + ret = max8998_read_reg(i2c, MAX8998_REG_STATUS2, ®); + if (ret) + return ret; + if (reg & (1 << 4)) + val->intval = 0; + else + val->intval = 1; + break; + case POWER_SUPPLY_PROP_ONLINE: + ret = max8998_read_reg(i2c, MAX8998_REG_STATUS2, ®); + if (ret) + return ret; + if (reg & (1 << 3)) + val->intval = 0; + else + val->intval = 1; + break; + default: + return -EINVAL; + } + + return 0; +} + +static const struct power_supply_desc max8998_battery_desc = { + .name = "max8998_pmic", + .type = POWER_SUPPLY_TYPE_BATTERY, + .get_property = max8998_battery_get_property, + .properties = max8998_battery_props, + .num_properties = ARRAY_SIZE(max8998_battery_props), +}; + +static int max8998_battery_probe(struct platform_device *pdev) +{ + struct max8998_dev *iodev = dev_get_drvdata(pdev->dev.parent); + struct max8998_platform_data *pdata = dev_get_platdata(iodev->dev); + struct power_supply_config psy_cfg = {}; + struct max8998_battery_data *max8998; + struct i2c_client *i2c; + int ret = 0; + + if (!pdata) { + dev_err(pdev->dev.parent, "No platform init data supplied\n"); + return -ENODEV; + } + + max8998 = devm_kzalloc(&pdev->dev, sizeof(struct max8998_battery_data), + GFP_KERNEL); + if (!max8998) + return -ENOMEM; + + max8998->dev = &pdev->dev; + max8998->iodev = iodev; + platform_set_drvdata(pdev, max8998); + i2c = max8998->iodev->i2c; + + /* Setup "End of Charge" */ + /* If EOC value equals 0, + * remain value set from bootloader or default value */ + if (pdata->eoc >= 10 && pdata->eoc <= 45) { + max8998_update_reg(i2c, MAX8998_REG_CHGR1, + (pdata->eoc / 5 - 2) << 5, 0x7 << 5); + } else if (pdata->eoc == 0) { + dev_dbg(max8998->dev, + "EOC value not set: leave it unchanged.\n"); + } else { + dev_err(max8998->dev, "Invalid EOC value\n"); + return -EINVAL; + } + + /* Setup Charge Restart Level */ + switch (pdata->restart) { + case 100: + max8998_update_reg(i2c, MAX8998_REG_CHGR1, 0x1 << 3, 0x3 << 3); + break; + case 150: + max8998_update_reg(i2c, MAX8998_REG_CHGR1, 0x0 << 3, 0x3 << 3); + break; + case 200: + max8998_update_reg(i2c, MAX8998_REG_CHGR1, 0x2 << 3, 0x3 << 3); + break; + case -1: + max8998_update_reg(i2c, MAX8998_REG_CHGR1, 0x3 << 3, 0x3 << 3); + break; + case 0: + dev_dbg(max8998->dev, + "Restart Level not set: leave it unchanged.\n"); + break; + default: + dev_err(max8998->dev, "Invalid Restart Level\n"); + return -EINVAL; + } + + /* Setup Charge Full Timeout */ + switch (pdata->timeout) { + case 5: + max8998_update_reg(i2c, MAX8998_REG_CHGR2, 0x0 << 4, 0x3 << 4); + break; + case 6: + max8998_update_reg(i2c, MAX8998_REG_CHGR2, 0x1 << 4, 0x3 << 4); + break; + case 7: + max8998_update_reg(i2c, MAX8998_REG_CHGR2, 0x2 << 4, 0x3 << 4); + break; + case -1: + max8998_update_reg(i2c, MAX8998_REG_CHGR2, 0x3 << 4, 0x3 << 4); + break; + case 0: + dev_dbg(max8998->dev, + "Full Timeout not set: leave it unchanged.\n"); + break; + default: + dev_err(max8998->dev, "Invalid Full Timeout value\n"); + return -EINVAL; + } + + psy_cfg.drv_data = max8998; + + 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); + return ret; + } + + return 0; +} + +static const struct platform_device_id max8998_battery_id[] = { + { "max8998-battery", TYPE_MAX8998 }, + { } +}; + +static struct platform_driver max8998_battery_driver = { + .driver = { + .name = "max8998-battery", + }, + .probe = max8998_battery_probe, + .id_table = max8998_battery_id, +}; + +module_platform_driver(max8998_battery_driver); + +MODULE_DESCRIPTION("MAXIM 8998 battery control driver"); +MODULE_AUTHOR("MyungJoo Ham "); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:max8998-battery"); diff --git a/drivers/power/supply/olpc_battery.c b/drivers/power/supply/olpc_battery.c new file mode 100644 index 000000000000..9e29b1321648 --- /dev/null +++ b/drivers/power/supply/olpc_battery.c @@ -0,0 +1,692 @@ +/* + * Battery driver for One Laptop Per Child board. + * + * Copyright © 2006-2010 David Woodhouse + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#define EC_BAT_VOLTAGE 0x10 /* uint16_t, *9.76/32, mV */ +#define EC_BAT_CURRENT 0x11 /* int16_t, *15.625/120, mA */ +#define EC_BAT_ACR 0x12 /* int16_t, *6250/15, µAh */ +#define EC_BAT_TEMP 0x13 /* uint16_t, *100/256, °C */ +#define EC_AMB_TEMP 0x14 /* uint16_t, *100/256, °C */ +#define EC_BAT_STATUS 0x15 /* uint8_t, bitmask */ +#define EC_BAT_SOC 0x16 /* uint8_t, percentage */ +#define EC_BAT_SERIAL 0x17 /* uint8_t[6] */ +#define EC_BAT_EEPROM 0x18 /* uint8_t adr as input, uint8_t output */ +#define EC_BAT_ERRCODE 0x1f /* uint8_t, bitmask */ + +#define BAT_STAT_PRESENT 0x01 +#define BAT_STAT_FULL 0x02 +#define BAT_STAT_LOW 0x04 +#define BAT_STAT_DESTROY 0x08 +#define BAT_STAT_AC 0x10 +#define BAT_STAT_CHARGING 0x20 +#define BAT_STAT_DISCHARGING 0x40 +#define BAT_STAT_TRICKLE 0x80 + +#define BAT_ERR_INFOFAIL 0x02 +#define BAT_ERR_OVERVOLTAGE 0x04 +#define BAT_ERR_OVERTEMP 0x05 +#define BAT_ERR_GAUGESTOP 0x06 +#define BAT_ERR_OUT_OF_CONTROL 0x07 +#define BAT_ERR_ID_FAIL 0x09 +#define BAT_ERR_ACR_FAIL 0x10 + +#define BAT_ADDR_MFR_TYPE 0x5F + +/********************************************************************* + * Power + *********************************************************************/ + +static int olpc_ac_get_prop(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + int ret = 0; + uint8_t status; + + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + ret = olpc_ec_cmd(EC_BAT_STATUS, NULL, 0, &status, 1); + if (ret) + return ret; + + val->intval = !!(status & BAT_STAT_AC); + break; + default: + ret = -EINVAL; + break; + } + return ret; +} + +static enum power_supply_property olpc_ac_props[] = { + POWER_SUPPLY_PROP_ONLINE, +}; + +static const struct power_supply_desc olpc_ac_desc = { + .name = "olpc-ac", + .type = POWER_SUPPLY_TYPE_MAINS, + .properties = olpc_ac_props, + .num_properties = ARRAY_SIZE(olpc_ac_props), + .get_property = olpc_ac_get_prop, +}; + +static struct power_supply *olpc_ac; + +static char bat_serial[17]; /* Ick */ + +static int olpc_bat_get_status(union power_supply_propval *val, uint8_t ec_byte) +{ + if (olpc_platform_info.ecver > 0x44) { + if (ec_byte & (BAT_STAT_CHARGING | BAT_STAT_TRICKLE)) + val->intval = POWER_SUPPLY_STATUS_CHARGING; + else if (ec_byte & BAT_STAT_DISCHARGING) + val->intval = POWER_SUPPLY_STATUS_DISCHARGING; + else if (ec_byte & BAT_STAT_FULL) + val->intval = POWER_SUPPLY_STATUS_FULL; + else /* er,... */ + val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; + } else { + /* Older EC didn't report charge/discharge bits */ + if (!(ec_byte & BAT_STAT_AC)) /* No AC means discharging */ + val->intval = POWER_SUPPLY_STATUS_DISCHARGING; + else if (ec_byte & BAT_STAT_FULL) + val->intval = POWER_SUPPLY_STATUS_FULL; + else /* Not _necessarily_ true but EC doesn't tell all yet */ + val->intval = POWER_SUPPLY_STATUS_CHARGING; + } + + return 0; +} + +static int olpc_bat_get_health(union power_supply_propval *val) +{ + uint8_t ec_byte; + int ret; + + ret = olpc_ec_cmd(EC_BAT_ERRCODE, NULL, 0, &ec_byte, 1); + if (ret) + return ret; + + switch (ec_byte) { + case 0: + val->intval = POWER_SUPPLY_HEALTH_GOOD; + break; + + case BAT_ERR_OVERTEMP: + val->intval = POWER_SUPPLY_HEALTH_OVERHEAT; + break; + + case BAT_ERR_OVERVOLTAGE: + val->intval = POWER_SUPPLY_HEALTH_OVERVOLTAGE; + break; + + case BAT_ERR_INFOFAIL: + case BAT_ERR_OUT_OF_CONTROL: + case BAT_ERR_ID_FAIL: + case BAT_ERR_ACR_FAIL: + val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; + break; + + default: + /* Eep. We don't know this failure code */ + ret = -EIO; + } + + return ret; +} + +static int olpc_bat_get_mfr(union power_supply_propval *val) +{ + uint8_t ec_byte; + int ret; + + ec_byte = BAT_ADDR_MFR_TYPE; + ret = olpc_ec_cmd(EC_BAT_EEPROM, &ec_byte, 1, &ec_byte, 1); + if (ret) + return ret; + + switch (ec_byte >> 4) { + case 1: + val->strval = "Gold Peak"; + break; + case 2: + val->strval = "BYD"; + break; + default: + val->strval = "Unknown"; + break; + } + + return ret; +} + +static int olpc_bat_get_tech(union power_supply_propval *val) +{ + uint8_t ec_byte; + int ret; + + ec_byte = BAT_ADDR_MFR_TYPE; + ret = olpc_ec_cmd(EC_BAT_EEPROM, &ec_byte, 1, &ec_byte, 1); + if (ret) + return ret; + + switch (ec_byte & 0xf) { + case 1: + val->intval = POWER_SUPPLY_TECHNOLOGY_NiMH; + break; + case 2: + val->intval = POWER_SUPPLY_TECHNOLOGY_LiFe; + break; + default: + val->intval = POWER_SUPPLY_TECHNOLOGY_UNKNOWN; + break; + } + + return ret; +} + +static int olpc_bat_get_charge_full_design(union power_supply_propval *val) +{ + uint8_t ec_byte; + union power_supply_propval tech; + int ret, mfr; + + ret = olpc_bat_get_tech(&tech); + if (ret) + return ret; + + ec_byte = BAT_ADDR_MFR_TYPE; + ret = olpc_ec_cmd(EC_BAT_EEPROM, &ec_byte, 1, &ec_byte, 1); + if (ret) + return ret; + + mfr = ec_byte >> 4; + + switch (tech.intval) { + case POWER_SUPPLY_TECHNOLOGY_NiMH: + switch (mfr) { + case 1: /* Gold Peak */ + val->intval = 3000000*.8; + break; + default: + return -EIO; + } + break; + + case POWER_SUPPLY_TECHNOLOGY_LiFe: + switch (mfr) { + case 1: /* Gold Peak, fall through */ + case 2: /* BYD */ + val->intval = 2800000; + break; + default: + return -EIO; + } + break; + + default: + return -EIO; + } + + return ret; +} + +static int olpc_bat_get_charge_now(union power_supply_propval *val) +{ + uint8_t soc; + union power_supply_propval full; + int ret; + + ret = olpc_ec_cmd(EC_BAT_SOC, NULL, 0, &soc, 1); + if (ret) + return ret; + + ret = olpc_bat_get_charge_full_design(&full); + if (ret) + return ret; + + val->intval = soc * (full.intval / 100); + return 0; +} + +static int olpc_bat_get_voltage_max_design(union power_supply_propval *val) +{ + uint8_t ec_byte; + union power_supply_propval tech; + int mfr; + int ret; + + ret = olpc_bat_get_tech(&tech); + if (ret) + return ret; + + ec_byte = BAT_ADDR_MFR_TYPE; + ret = olpc_ec_cmd(EC_BAT_EEPROM, &ec_byte, 1, &ec_byte, 1); + if (ret) + return ret; + + mfr = ec_byte >> 4; + + switch (tech.intval) { + case POWER_SUPPLY_TECHNOLOGY_NiMH: + switch (mfr) { + case 1: /* Gold Peak */ + val->intval = 6000000; + break; + default: + return -EIO; + } + break; + + case POWER_SUPPLY_TECHNOLOGY_LiFe: + switch (mfr) { + case 1: /* Gold Peak */ + val->intval = 6400000; + break; + case 2: /* BYD */ + val->intval = 6500000; + break; + default: + return -EIO; + } + break; + + default: + return -EIO; + } + + return ret; +} + +/********************************************************************* + * Battery properties + *********************************************************************/ +static int olpc_bat_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + int ret = 0; + __be16 ec_word; + uint8_t ec_byte; + __be64 ser_buf; + + ret = olpc_ec_cmd(EC_BAT_STATUS, NULL, 0, &ec_byte, 1); + if (ret) + return ret; + + /* Theoretically there's a race here -- the battery could be + removed immediately after we check whether it's present, and + then we query for some other property of the now-absent battery. + It doesn't matter though -- the EC will return the last-known + information, and it's as if we just ran that _little_ bit faster + and managed to read it out before the battery went away. */ + if (!(ec_byte & (BAT_STAT_PRESENT | BAT_STAT_TRICKLE)) && + psp != POWER_SUPPLY_PROP_PRESENT) + return -ENODEV; + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + ret = olpc_bat_get_status(val, ec_byte); + if (ret) + return ret; + break; + case POWER_SUPPLY_PROP_CHARGE_TYPE: + if (ec_byte & BAT_STAT_TRICKLE) + val->intval = POWER_SUPPLY_CHARGE_TYPE_TRICKLE; + else if (ec_byte & BAT_STAT_CHARGING) + val->intval = POWER_SUPPLY_CHARGE_TYPE_FAST; + else + val->intval = POWER_SUPPLY_CHARGE_TYPE_NONE; + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = !!(ec_byte & (BAT_STAT_PRESENT | + BAT_STAT_TRICKLE)); + break; + + case POWER_SUPPLY_PROP_HEALTH: + if (ec_byte & BAT_STAT_DESTROY) + val->intval = POWER_SUPPLY_HEALTH_DEAD; + else { + ret = olpc_bat_get_health(val); + if (ret) + return ret; + } + break; + + case POWER_SUPPLY_PROP_MANUFACTURER: + ret = olpc_bat_get_mfr(val); + if (ret) + return ret; + break; + case POWER_SUPPLY_PROP_TECHNOLOGY: + ret = olpc_bat_get_tech(val); + if (ret) + return ret; + break; + case POWER_SUPPLY_PROP_VOLTAGE_AVG: + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + ret = olpc_ec_cmd(EC_BAT_VOLTAGE, NULL, 0, (void *)&ec_word, 2); + if (ret) + return ret; + + val->intval = (s16)be16_to_cpu(ec_word) * 9760L / 32; + break; + case POWER_SUPPLY_PROP_CURRENT_AVG: + case POWER_SUPPLY_PROP_CURRENT_NOW: + ret = olpc_ec_cmd(EC_BAT_CURRENT, NULL, 0, (void *)&ec_word, 2); + if (ret) + return ret; + + val->intval = (s16)be16_to_cpu(ec_word) * 15625L / 120; + break; + case POWER_SUPPLY_PROP_CAPACITY: + ret = olpc_ec_cmd(EC_BAT_SOC, NULL, 0, &ec_byte, 1); + if (ret) + return ret; + val->intval = ec_byte; + break; + case POWER_SUPPLY_PROP_CAPACITY_LEVEL: + if (ec_byte & BAT_STAT_FULL) + val->intval = POWER_SUPPLY_CAPACITY_LEVEL_FULL; + else if (ec_byte & BAT_STAT_LOW) + val->intval = POWER_SUPPLY_CAPACITY_LEVEL_LOW; + else + val->intval = POWER_SUPPLY_CAPACITY_LEVEL_NORMAL; + break; + case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: + ret = olpc_bat_get_charge_full_design(val); + if (ret) + return ret; + break; + case POWER_SUPPLY_PROP_CHARGE_NOW: + ret = olpc_bat_get_charge_now(val); + if (ret) + return ret; + break; + case POWER_SUPPLY_PROP_TEMP: + ret = olpc_ec_cmd(EC_BAT_TEMP, NULL, 0, (void *)&ec_word, 2); + if (ret) + return ret; + + val->intval = (s16)be16_to_cpu(ec_word) * 100 / 256; + break; + case POWER_SUPPLY_PROP_TEMP_AMBIENT: + ret = olpc_ec_cmd(EC_AMB_TEMP, NULL, 0, (void *)&ec_word, 2); + if (ret) + return ret; + + val->intval = (int)be16_to_cpu(ec_word) * 100 / 256; + break; + case POWER_SUPPLY_PROP_CHARGE_COUNTER: + ret = olpc_ec_cmd(EC_BAT_ACR, NULL, 0, (void *)&ec_word, 2); + if (ret) + return ret; + + val->intval = (s16)be16_to_cpu(ec_word) * 6250 / 15; + break; + case POWER_SUPPLY_PROP_SERIAL_NUMBER: + ret = olpc_ec_cmd(EC_BAT_SERIAL, NULL, 0, (void *)&ser_buf, 8); + if (ret) + return ret; + + sprintf(bat_serial, "%016llx", (long long)be64_to_cpu(ser_buf)); + val->strval = bat_serial; + break; + case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: + ret = olpc_bat_get_voltage_max_design(val); + if (ret) + return ret; + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static enum power_supply_property olpc_xo1_bat_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_CHARGE_TYPE, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_VOLTAGE_AVG, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CURRENT_AVG, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_CAPACITY_LEVEL, + POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, + POWER_SUPPLY_PROP_CHARGE_NOW, + POWER_SUPPLY_PROP_TEMP, + POWER_SUPPLY_PROP_TEMP_AMBIENT, + POWER_SUPPLY_PROP_MANUFACTURER, + POWER_SUPPLY_PROP_SERIAL_NUMBER, + POWER_SUPPLY_PROP_CHARGE_COUNTER, + POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, +}; + +/* XO-1.5 does not have ambient temperature property */ +static enum power_supply_property olpc_xo15_bat_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_CHARGE_TYPE, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_VOLTAGE_AVG, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CURRENT_AVG, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_CAPACITY_LEVEL, + POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, + POWER_SUPPLY_PROP_CHARGE_NOW, + POWER_SUPPLY_PROP_TEMP, + POWER_SUPPLY_PROP_MANUFACTURER, + POWER_SUPPLY_PROP_SERIAL_NUMBER, + POWER_SUPPLY_PROP_CHARGE_COUNTER, + POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, +}; + +/* EEPROM reading goes completely around the power_supply API, sadly */ + +#define EEPROM_START 0x20 +#define EEPROM_END 0x80 +#define EEPROM_SIZE (EEPROM_END - EEPROM_START) + +static ssize_t olpc_bat_eeprom_read(struct file *filp, struct kobject *kobj, + struct bin_attribute *attr, char *buf, loff_t off, size_t count) +{ + uint8_t ec_byte; + int ret; + int i; + + for (i = 0; i < count; i++) { + ec_byte = EEPROM_START + off + i; + ret = olpc_ec_cmd(EC_BAT_EEPROM, &ec_byte, 1, &buf[i], 1); + if (ret) { + pr_err("olpc-battery: " + "EC_BAT_EEPROM cmd @ 0x%x failed - %d!\n", + ec_byte, ret); + return -EIO; + } + } + + return count; +} + +static struct bin_attribute olpc_bat_eeprom = { + .attr = { + .name = "eeprom", + .mode = S_IRUGO, + }, + .size = EEPROM_SIZE, + .read = olpc_bat_eeprom_read, +}; + +/* Allow userspace to see the specific error value pulled from the EC */ + +static ssize_t olpc_bat_error_read(struct device *dev, + struct device_attribute *attr, char *buf) +{ + uint8_t ec_byte; + ssize_t ret; + + ret = olpc_ec_cmd(EC_BAT_ERRCODE, NULL, 0, &ec_byte, 1); + if (ret < 0) + return ret; + + return sprintf(buf, "%d\n", ec_byte); +} + +static struct device_attribute olpc_bat_error = { + .attr = { + .name = "error", + .mode = S_IRUGO, + }, + .show = olpc_bat_error_read, +}; + +/********************************************************************* + * Initialisation + *********************************************************************/ + +static struct power_supply_desc olpc_bat_desc = { + .name = "olpc-battery", + .get_property = olpc_bat_get_property, + .use_for_apm = 1, +}; + +static struct power_supply *olpc_bat; + +static int olpc_battery_suspend(struct platform_device *pdev, + pm_message_t state) +{ + if (device_may_wakeup(&olpc_ac->dev)) + olpc_ec_wakeup_set(EC_SCI_SRC_ACPWR); + else + olpc_ec_wakeup_clear(EC_SCI_SRC_ACPWR); + + if (device_may_wakeup(&olpc_bat->dev)) + olpc_ec_wakeup_set(EC_SCI_SRC_BATTERY | EC_SCI_SRC_BATSOC + | EC_SCI_SRC_BATERR); + else + olpc_ec_wakeup_clear(EC_SCI_SRC_BATTERY | EC_SCI_SRC_BATSOC + | EC_SCI_SRC_BATERR); + + return 0; +} + +static int olpc_battery_probe(struct platform_device *pdev) +{ + int ret; + uint8_t status; + + /* + * We've seen a number of EC protocol changes; this driver requires + * the latest EC protocol, supported by 0x44 and above. + */ + if (olpc_platform_info.ecver < 0x44) { + printk(KERN_NOTICE "OLPC EC version 0x%02x too old for " + "battery driver.\n", olpc_platform_info.ecver); + return -ENXIO; + } + + ret = olpc_ec_cmd(EC_BAT_STATUS, NULL, 0, &status, 1); + if (ret) + return ret; + + /* Ignore the status. It doesn't actually matter */ + + olpc_ac = power_supply_register(&pdev->dev, &olpc_ac_desc, NULL); + if (IS_ERR(olpc_ac)) + return PTR_ERR(olpc_ac); + + if (olpc_board_at_least(olpc_board_pre(0xd0))) { /* XO-1.5 */ + olpc_bat_desc.properties = olpc_xo15_bat_props; + olpc_bat_desc.num_properties = ARRAY_SIZE(olpc_xo15_bat_props); + } else { /* XO-1 */ + olpc_bat_desc.properties = olpc_xo1_bat_props; + olpc_bat_desc.num_properties = ARRAY_SIZE(olpc_xo1_bat_props); + } + + olpc_bat = power_supply_register(&pdev->dev, &olpc_bat_desc, NULL); + if (IS_ERR(olpc_bat)) { + ret = PTR_ERR(olpc_bat); + goto battery_failed; + } + + ret = device_create_bin_file(&olpc_bat->dev, &olpc_bat_eeprom); + if (ret) + goto eeprom_failed; + + ret = device_create_file(&olpc_bat->dev, &olpc_bat_error); + if (ret) + goto error_failed; + + if (olpc_ec_wakeup_available()) { + device_set_wakeup_capable(&olpc_ac->dev, true); + device_set_wakeup_capable(&olpc_bat->dev, true); + } + + return 0; + +error_failed: + device_remove_bin_file(&olpc_bat->dev, &olpc_bat_eeprom); +eeprom_failed: + power_supply_unregister(olpc_bat); +battery_failed: + power_supply_unregister(olpc_ac); + return ret; +} + +static int olpc_battery_remove(struct platform_device *pdev) +{ + device_remove_file(&olpc_bat->dev, &olpc_bat_error); + device_remove_bin_file(&olpc_bat->dev, &olpc_bat_eeprom); + power_supply_unregister(olpc_bat); + power_supply_unregister(olpc_ac); + return 0; +} + +static const struct of_device_id olpc_battery_ids[] = { + { .compatible = "olpc,xo1-battery" }, + {} +}; +MODULE_DEVICE_TABLE(of, olpc_battery_ids); + +static struct platform_driver olpc_battery_driver = { + .driver = { + .name = "olpc-battery", + .of_match_table = olpc_battery_ids, + }, + .probe = olpc_battery_probe, + .remove = olpc_battery_remove, + .suspend = olpc_battery_suspend, +}; + +module_platform_driver(olpc_battery_driver); + +MODULE_AUTHOR("David Woodhouse "); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Battery driver for One Laptop Per Child 'XO' machine"); diff --git a/drivers/power/supply/pcf50633-charger.c b/drivers/power/supply/pcf50633-charger.c new file mode 100644 index 000000000000..d05597b4e40f --- /dev/null +++ b/drivers/power/supply/pcf50633-charger.c @@ -0,0 +1,488 @@ +/* NXP PCF50633 Main Battery Charger Driver + * + * (C) 2006-2008 by Openmoko, Inc. + * Author: Balaji Rao + * All rights reserved. + * + * Broken down from monstrous PCF50633 driver mainly by + * Harald Welte, Andy Green and Werner Almesberger + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +struct pcf50633_mbc { + struct pcf50633 *pcf; + + int adapter_online; + int usb_online; + + struct power_supply *usb; + struct power_supply *adapter; + struct power_supply *ac; +}; + +int pcf50633_mbc_usb_curlim_set(struct pcf50633 *pcf, int ma) +{ + struct pcf50633_mbc *mbc = platform_get_drvdata(pcf->mbc_pdev); + int ret = 0; + u8 bits; + int charging_start = 1; + u8 mbcs2, chgmod; + unsigned int mbcc5; + + if (ma >= 1000) { + bits = PCF50633_MBCC7_USB_1000mA; + ma = 1000; + } else if (ma >= 500) { + bits = PCF50633_MBCC7_USB_500mA; + ma = 500; + } else if (ma >= 100) { + bits = PCF50633_MBCC7_USB_100mA; + ma = 100; + } else { + bits = PCF50633_MBCC7_USB_SUSPEND; + charging_start = 0; + ma = 0; + } + + ret = pcf50633_reg_set_bit_mask(pcf, PCF50633_REG_MBCC7, + PCF50633_MBCC7_USB_MASK, bits); + if (ret) + dev_err(pcf->dev, "error setting usb curlim to %d mA\n", ma); + else + dev_info(pcf->dev, "usb curlim to %d mA\n", ma); + + /* + * We limit the charging current to be the USB current limit. + * The reason is that on pcf50633, when it enters PMU Standby mode, + * which it does when the device goes "off", the USB current limit + * reverts to the variant default. In at least one common case, that + * default is 500mA. By setting the charging current to be the same + * as the USB limit we set here before PMU standby, we enforce it only + * using the correct amount of current even when the USB current limit + * gets reset to the wrong thing + */ + + if (mbc->pcf->pdata->charger_reference_current_ma) { + mbcc5 = (ma << 8) / mbc->pcf->pdata->charger_reference_current_ma; + if (mbcc5 > 255) + mbcc5 = 255; + pcf50633_reg_write(mbc->pcf, PCF50633_REG_MBCC5, mbcc5); + } + + mbcs2 = pcf50633_reg_read(mbc->pcf, PCF50633_REG_MBCS2); + chgmod = (mbcs2 & PCF50633_MBCS2_MBC_MASK); + + /* If chgmod == BATFULL, setting chgena has no effect. + * Datasheet says we need to set resume instead but when autoresume is + * used resume doesn't work. Clear and set chgena instead. + */ + if (chgmod != PCF50633_MBCS2_MBC_BAT_FULL) + pcf50633_reg_set_bit_mask(pcf, PCF50633_REG_MBCC1, + PCF50633_MBCC1_CHGENA, PCF50633_MBCC1_CHGENA); + else { + pcf50633_reg_clear_bits(pcf, PCF50633_REG_MBCC1, + PCF50633_MBCC1_CHGENA); + pcf50633_reg_set_bit_mask(pcf, PCF50633_REG_MBCC1, + PCF50633_MBCC1_CHGENA, PCF50633_MBCC1_CHGENA); + } + + power_supply_changed(mbc->usb); + + return ret; +} +EXPORT_SYMBOL_GPL(pcf50633_mbc_usb_curlim_set); + +int pcf50633_mbc_get_status(struct pcf50633 *pcf) +{ + struct pcf50633_mbc *mbc = platform_get_drvdata(pcf->mbc_pdev); + int status = 0; + u8 chgmod; + + if (!mbc) + return 0; + + chgmod = pcf50633_reg_read(mbc->pcf, PCF50633_REG_MBCS2) + & PCF50633_MBCS2_MBC_MASK; + + if (mbc->usb_online) + status |= PCF50633_MBC_USB_ONLINE; + if (chgmod == PCF50633_MBCS2_MBC_USB_PRE || + chgmod == PCF50633_MBCS2_MBC_USB_PRE_WAIT || + chgmod == PCF50633_MBCS2_MBC_USB_FAST || + chgmod == PCF50633_MBCS2_MBC_USB_FAST_WAIT) + status |= PCF50633_MBC_USB_ACTIVE; + if (mbc->adapter_online) + status |= PCF50633_MBC_ADAPTER_ONLINE; + if (chgmod == PCF50633_MBCS2_MBC_ADP_PRE || + chgmod == PCF50633_MBCS2_MBC_ADP_PRE_WAIT || + chgmod == PCF50633_MBCS2_MBC_ADP_FAST || + chgmod == PCF50633_MBCS2_MBC_ADP_FAST_WAIT) + status |= PCF50633_MBC_ADAPTER_ACTIVE; + + return status; +} +EXPORT_SYMBOL_GPL(pcf50633_mbc_get_status); + +int pcf50633_mbc_get_usb_online_status(struct pcf50633 *pcf) +{ + struct pcf50633_mbc *mbc = platform_get_drvdata(pcf->mbc_pdev); + + if (!mbc) + return 0; + + return mbc->usb_online; +} +EXPORT_SYMBOL_GPL(pcf50633_mbc_get_usb_online_status); + +static ssize_t +show_chgmode(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct pcf50633_mbc *mbc = dev_get_drvdata(dev); + + u8 mbcs2 = pcf50633_reg_read(mbc->pcf, PCF50633_REG_MBCS2); + u8 chgmod = (mbcs2 & PCF50633_MBCS2_MBC_MASK); + + return sprintf(buf, "%d\n", chgmod); +} +static DEVICE_ATTR(chgmode, S_IRUGO, show_chgmode, NULL); + +static ssize_t +show_usblim(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct pcf50633_mbc *mbc = dev_get_drvdata(dev); + u8 usblim = pcf50633_reg_read(mbc->pcf, PCF50633_REG_MBCC7) & + PCF50633_MBCC7_USB_MASK; + unsigned int ma; + + if (usblim == PCF50633_MBCC7_USB_1000mA) + ma = 1000; + else if (usblim == PCF50633_MBCC7_USB_500mA) + ma = 500; + else if (usblim == PCF50633_MBCC7_USB_100mA) + ma = 100; + else + ma = 0; + + return sprintf(buf, "%u\n", ma); +} + +static ssize_t set_usblim(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct pcf50633_mbc *mbc = dev_get_drvdata(dev); + unsigned long ma; + int ret; + + ret = kstrtoul(buf, 10, &ma); + if (ret) + return ret; + + pcf50633_mbc_usb_curlim_set(mbc->pcf, ma); + + return count; +} + +static DEVICE_ATTR(usb_curlim, S_IRUGO | S_IWUSR, show_usblim, set_usblim); + +static ssize_t +show_chglim(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct pcf50633_mbc *mbc = dev_get_drvdata(dev); + u8 mbcc5 = pcf50633_reg_read(mbc->pcf, PCF50633_REG_MBCC5); + unsigned int ma; + + if (!mbc->pcf->pdata->charger_reference_current_ma) + return -ENODEV; + + ma = (mbc->pcf->pdata->charger_reference_current_ma * mbcc5) >> 8; + + return sprintf(buf, "%u\n", ma); +} + +static ssize_t set_chglim(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct pcf50633_mbc *mbc = dev_get_drvdata(dev); + unsigned long ma; + unsigned int mbcc5; + int ret; + + if (!mbc->pcf->pdata->charger_reference_current_ma) + return -ENODEV; + + ret = kstrtoul(buf, 10, &ma); + if (ret) + return ret; + + mbcc5 = (ma << 8) / mbc->pcf->pdata->charger_reference_current_ma; + if (mbcc5 > 255) + mbcc5 = 255; + pcf50633_reg_write(mbc->pcf, PCF50633_REG_MBCC5, mbcc5); + + return count; +} + +/* + * This attribute allows to change MBC charging limit on the fly + * independently of usb current limit. It also gets set automatically every + * time usb current limit is changed. + */ +static DEVICE_ATTR(chg_curlim, S_IRUGO | S_IWUSR, show_chglim, set_chglim); + +static struct attribute *pcf50633_mbc_sysfs_entries[] = { + &dev_attr_chgmode.attr, + &dev_attr_usb_curlim.attr, + &dev_attr_chg_curlim.attr, + NULL, +}; + +static struct attribute_group mbc_attr_group = { + .name = NULL, /* put in device directory */ + .attrs = pcf50633_mbc_sysfs_entries, +}; + +static void +pcf50633_mbc_irq_handler(int irq, void *data) +{ + struct pcf50633_mbc *mbc = data; + + /* USB */ + if (irq == PCF50633_IRQ_USBINS) { + mbc->usb_online = 1; + } else if (irq == PCF50633_IRQ_USBREM) { + mbc->usb_online = 0; + pcf50633_mbc_usb_curlim_set(mbc->pcf, 0); + } + + /* Adapter */ + if (irq == PCF50633_IRQ_ADPINS) + mbc->adapter_online = 1; + else if (irq == PCF50633_IRQ_ADPREM) + mbc->adapter_online = 0; + + power_supply_changed(mbc->ac); + power_supply_changed(mbc->usb); + power_supply_changed(mbc->adapter); + + if (mbc->pcf->pdata->mbc_event_callback) + mbc->pcf->pdata->mbc_event_callback(mbc->pcf, irq); +} + +static int adapter_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct pcf50633_mbc *mbc = power_supply_get_drvdata(psy); + int ret = 0; + + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + val->intval = mbc->adapter_online; + break; + default: + ret = -EINVAL; + break; + } + return ret; +} + +static int usb_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct pcf50633_mbc *mbc = power_supply_get_drvdata(psy); + int ret = 0; + u8 usblim = pcf50633_reg_read(mbc->pcf, PCF50633_REG_MBCC7) & + PCF50633_MBCC7_USB_MASK; + + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + val->intval = mbc->usb_online && + (usblim <= PCF50633_MBCC7_USB_500mA); + break; + default: + ret = -EINVAL; + break; + } + return ret; +} + +static int ac_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct pcf50633_mbc *mbc = power_supply_get_drvdata(psy); + int ret = 0; + u8 usblim = pcf50633_reg_read(mbc->pcf, PCF50633_REG_MBCC7) & + PCF50633_MBCC7_USB_MASK; + + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + val->intval = mbc->usb_online && + (usblim == PCF50633_MBCC7_USB_1000mA); + break; + default: + ret = -EINVAL; + break; + } + return ret; +} + +static enum power_supply_property power_props[] = { + POWER_SUPPLY_PROP_ONLINE, +}; + +static const u8 mbc_irq_handlers[] = { + PCF50633_IRQ_ADPINS, + PCF50633_IRQ_ADPREM, + PCF50633_IRQ_USBINS, + PCF50633_IRQ_USBREM, + PCF50633_IRQ_BATFULL, + PCF50633_IRQ_CHGHALT, + PCF50633_IRQ_THLIMON, + PCF50633_IRQ_THLIMOFF, + PCF50633_IRQ_USBLIMON, + PCF50633_IRQ_USBLIMOFF, + PCF50633_IRQ_LOWSYS, + PCF50633_IRQ_LOWBAT, +}; + +static const struct power_supply_desc pcf50633_mbc_adapter_desc = { + .name = "adapter", + .type = POWER_SUPPLY_TYPE_MAINS, + .properties = power_props, + .num_properties = ARRAY_SIZE(power_props), + .get_property = &adapter_get_property, +}; + +static const struct power_supply_desc pcf50633_mbc_usb_desc = { + .name = "usb", + .type = POWER_SUPPLY_TYPE_USB, + .properties = power_props, + .num_properties = ARRAY_SIZE(power_props), + .get_property = usb_get_property, +}; + +static const struct power_supply_desc pcf50633_mbc_ac_desc = { + .name = "ac", + .type = POWER_SUPPLY_TYPE_MAINS, + .properties = power_props, + .num_properties = ARRAY_SIZE(power_props), + .get_property = ac_get_property, +}; + +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; + + mbc = devm_kzalloc(&pdev->dev, sizeof(*mbc), GFP_KERNEL); + if (!mbc) + return -ENOMEM; + + platform_set_drvdata(pdev, mbc); + mbc->pcf = dev_to_pcf50633(pdev->dev.parent); + + /* Set up IRQ handlers */ + for (i = 0; i < ARRAY_SIZE(mbc_irq_handlers); i++) + pcf50633_register_irq(mbc->pcf, mbc_irq_handlers[i], + pcf50633_mbc_irq_handler, mbc); + + psy_cfg.supplied_to = mbc->pcf->pdata->batteries; + psy_cfg.num_supplicants = mbc->pcf->pdata->num_batteries; + psy_cfg.drv_data = mbc; + + /* Create power supplies */ + mbc->adapter = power_supply_register(&pdev->dev, + &pcf50633_mbc_adapter_desc, + &psy_cfg); + if (IS_ERR(mbc->adapter)) { + dev_err(mbc->pcf->dev, "failed to register adapter\n"); + ret = PTR_ERR(mbc->adapter); + return ret; + } + + mbc->usb = power_supply_register(&pdev->dev, &pcf50633_mbc_usb_desc, + &psy_cfg); + 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; + } + + mbc->ac = power_supply_register(&pdev->dev, &pcf50633_mbc_ac_desc, + &psy_cfg); + if (IS_ERR(mbc->ac)) { + 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; + } + + ret = sysfs_create_group(&pdev->dev.kobj, &mbc_attr_group); + if (ret) + dev_err(mbc->pcf->dev, "failed to create sysfs entries\n"); + + mbcs1 = pcf50633_reg_read(mbc->pcf, PCF50633_REG_MBCS1); + if (mbcs1 & PCF50633_MBCS1_USBPRES) + pcf50633_mbc_irq_handler(PCF50633_IRQ_USBINS, mbc); + if (mbcs1 & PCF50633_MBCS1_ADAPTPRES) + pcf50633_mbc_irq_handler(PCF50633_IRQ_ADPINS, mbc); + + return 0; +} + +static int pcf50633_mbc_remove(struct platform_device *pdev) +{ + struct pcf50633_mbc *mbc = platform_get_drvdata(pdev); + int i; + + /* Remove IRQ handlers */ + for (i = 0; i < ARRAY_SIZE(mbc_irq_handlers); i++) + pcf50633_free_irq(mbc->pcf, mbc_irq_handlers[i]); + + sysfs_remove_group(&pdev->dev.kobj, &mbc_attr_group); + power_supply_unregister(mbc->usb); + power_supply_unregister(mbc->adapter); + power_supply_unregister(mbc->ac); + + return 0; +} + +static struct platform_driver pcf50633_mbc_driver = { + .driver = { + .name = "pcf50633-mbc", + }, + .probe = pcf50633_mbc_probe, + .remove = pcf50633_mbc_remove, +}; + +module_platform_driver(pcf50633_mbc_driver); + +MODULE_AUTHOR("Balaji Rao "); +MODULE_DESCRIPTION("PCF50633 mbc driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:pcf50633-mbc"); diff --git a/drivers/power/supply/pda_power.c b/drivers/power/supply/pda_power.c new file mode 100644 index 000000000000..dfe1ee89f7c7 --- /dev/null +++ b/drivers/power/supply/pda_power.c @@ -0,0 +1,514 @@ +/* + * Common power driver for PDAs and phones with one or two external + * power supplies (AC/USB) connected to main and backup batteries, + * and optional builtin charger. + * + * Copyright © 2007 Anton Vorontsov + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static inline unsigned int get_irq_flags(struct resource *res) +{ + return IRQF_SHARED | (res->flags & IRQF_TRIGGER_MASK); +} + +static struct device *dev; +static struct pda_power_pdata *pdata; +static struct resource *ac_irq, *usb_irq; +static struct timer_list charger_timer; +static struct timer_list supply_timer; +static struct timer_list polling_timer; +static int polling; +static struct power_supply *pda_psy_ac, *pda_psy_usb; + +#if IS_ENABLED(CONFIG_USB_PHY) +static struct usb_phy *transceiver; +static struct notifier_block otg_nb; +#endif + +static struct regulator *ac_draw; + +enum { + PDA_PSY_OFFLINE = 0, + PDA_PSY_ONLINE = 1, + PDA_PSY_TO_CHANGE, +}; +static int new_ac_status = -1; +static int new_usb_status = -1; +static int ac_status = -1; +static int usb_status = -1; + +static int pda_power_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + if (psy->desc->type == POWER_SUPPLY_TYPE_MAINS) + val->intval = pdata->is_ac_online ? + pdata->is_ac_online() : 0; + else + val->intval = pdata->is_usb_online ? + pdata->is_usb_online() : 0; + break; + default: + return -EINVAL; + } + return 0; +} + +static enum power_supply_property pda_power_props[] = { + POWER_SUPPLY_PROP_ONLINE, +}; + +static char *pda_power_supplied_to[] = { + "main-battery", + "backup-battery", +}; + +static const struct power_supply_desc pda_psy_ac_desc = { + .name = "ac", + .type = POWER_SUPPLY_TYPE_MAINS, + .properties = pda_power_props, + .num_properties = ARRAY_SIZE(pda_power_props), + .get_property = pda_power_get_property, +}; + +static const struct power_supply_desc pda_psy_usb_desc = { + .name = "usb", + .type = POWER_SUPPLY_TYPE_USB, + .properties = pda_power_props, + .num_properties = ARRAY_SIZE(pda_power_props), + .get_property = pda_power_get_property, +}; + +static void update_status(void) +{ + if (pdata->is_ac_online) + new_ac_status = !!pdata->is_ac_online(); + + if (pdata->is_usb_online) + new_usb_status = !!pdata->is_usb_online(); +} + +static void update_charger(void) +{ + static int regulator_enabled; + int max_uA = pdata->ac_max_uA; + + if (pdata->set_charge) { + if (new_ac_status > 0) { + dev_dbg(dev, "charger on (AC)\n"); + pdata->set_charge(PDA_POWER_CHARGE_AC); + } else if (new_usb_status > 0) { + dev_dbg(dev, "charger on (USB)\n"); + pdata->set_charge(PDA_POWER_CHARGE_USB); + } else { + dev_dbg(dev, "charger off\n"); + pdata->set_charge(0); + } + } else if (ac_draw) { + if (new_ac_status > 0) { + regulator_set_current_limit(ac_draw, max_uA, max_uA); + if (!regulator_enabled) { + dev_dbg(dev, "charger on (AC)\n"); + WARN_ON(regulator_enable(ac_draw)); + regulator_enabled = 1; + } + } else { + if (regulator_enabled) { + dev_dbg(dev, "charger off\n"); + WARN_ON(regulator_disable(ac_draw)); + regulator_enabled = 0; + } + } + } +} + +static void supply_timer_func(unsigned long unused) +{ + if (ac_status == PDA_PSY_TO_CHANGE) { + ac_status = new_ac_status; + power_supply_changed(pda_psy_ac); + } + + if (usb_status == PDA_PSY_TO_CHANGE) { + usb_status = new_usb_status; + power_supply_changed(pda_psy_usb); + } +} + +static void psy_changed(void) +{ + update_charger(); + + /* + * Okay, charger set. Now wait a bit before notifying supplicants, + * charge power should stabilize. + */ + mod_timer(&supply_timer, + jiffies + msecs_to_jiffies(pdata->wait_for_charger)); +} + +static void charger_timer_func(unsigned long unused) +{ + update_status(); + psy_changed(); +} + +static irqreturn_t power_changed_isr(int irq, void *power_supply) +{ + if (power_supply == pda_psy_ac) + ac_status = PDA_PSY_TO_CHANGE; + else if (power_supply == pda_psy_usb) + usb_status = PDA_PSY_TO_CHANGE; + else + return IRQ_NONE; + + /* + * Wait a bit before reading ac/usb line status and setting charger, + * because ac/usb status readings may lag from irq. + */ + mod_timer(&charger_timer, + jiffies + msecs_to_jiffies(pdata->wait_for_status)); + + return IRQ_HANDLED; +} + +static void polling_timer_func(unsigned long unused) +{ + int changed = 0; + + dev_dbg(dev, "polling...\n"); + + update_status(); + + if (!ac_irq && new_ac_status != ac_status) { + ac_status = PDA_PSY_TO_CHANGE; + changed = 1; + } + + if (!usb_irq && new_usb_status != usb_status) { + usb_status = PDA_PSY_TO_CHANGE; + changed = 1; + } + + if (changed) + psy_changed(); + + mod_timer(&polling_timer, + jiffies + msecs_to_jiffies(pdata->polling_interval)); +} + +#if IS_ENABLED(CONFIG_USB_PHY) +static int otg_is_usb_online(void) +{ + return (transceiver->last_event == USB_EVENT_VBUS || + transceiver->last_event == USB_EVENT_ENUMERATED); +} + +static int otg_is_ac_online(void) +{ + return (transceiver->last_event == USB_EVENT_CHARGER); +} + +static int otg_handle_notification(struct notifier_block *nb, + unsigned long event, void *unused) +{ + switch (event) { + case USB_EVENT_CHARGER: + ac_status = PDA_PSY_TO_CHANGE; + break; + case USB_EVENT_VBUS: + case USB_EVENT_ENUMERATED: + usb_status = PDA_PSY_TO_CHANGE; + break; + case USB_EVENT_NONE: + ac_status = PDA_PSY_TO_CHANGE; + usb_status = PDA_PSY_TO_CHANGE; + break; + default: + return NOTIFY_OK; + } + + /* + * Wait a bit before reading ac/usb line status and setting charger, + * because ac/usb status readings may lag from irq. + */ + mod_timer(&charger_timer, + jiffies + msecs_to_jiffies(pdata->wait_for_status)); + + return NOTIFY_OK; +} +#endif + +static int pda_power_probe(struct platform_device *pdev) +{ + struct power_supply_config psy_cfg = {}; + int ret = 0; + + dev = &pdev->dev; + + if (pdev->id != -1) { + dev_err(dev, "it's meaningless to register several " + "pda_powers; use id = -1\n"); + ret = -EINVAL; + goto wrongid; + } + + pdata = pdev->dev.platform_data; + + if (pdata->init) { + ret = pdata->init(dev); + if (ret < 0) + goto init_failed; + } + + ac_draw = regulator_get(dev, "ac_draw"); + if (IS_ERR(ac_draw)) { + dev_dbg(dev, "couldn't get ac_draw regulator\n"); + ac_draw = NULL; + } + + update_status(); + update_charger(); + + if (!pdata->wait_for_status) + pdata->wait_for_status = 500; + + if (!pdata->wait_for_charger) + pdata->wait_for_charger = 500; + + if (!pdata->polling_interval) + pdata->polling_interval = 2000; + + if (!pdata->ac_max_uA) + pdata->ac_max_uA = 500000; + + setup_timer(&charger_timer, charger_timer_func, 0); + setup_timer(&supply_timer, supply_timer_func, 0); + + ac_irq = platform_get_resource_byname(pdev, IORESOURCE_IRQ, "ac"); + usb_irq = platform_get_resource_byname(pdev, IORESOURCE_IRQ, "usb"); + + if (pdata->supplied_to) { + psy_cfg.supplied_to = pdata->supplied_to; + psy_cfg.num_supplicants = pdata->num_supplicants; + } else { + psy_cfg.supplied_to = pda_power_supplied_to; + psy_cfg.num_supplicants = ARRAY_SIZE(pda_power_supplied_to); + } + +#if IS_ENABLED(CONFIG_USB_PHY) + transceiver = usb_get_phy(USB_PHY_TYPE_USB2); + if (!IS_ERR_OR_NULL(transceiver)) { + if (!pdata->is_usb_online) + pdata->is_usb_online = otg_is_usb_online; + if (!pdata->is_ac_online) + pdata->is_ac_online = otg_is_ac_online; + } +#endif + + if (pdata->is_ac_online) { + pda_psy_ac = power_supply_register(&pdev->dev, + &pda_psy_ac_desc, &psy_cfg); + if (IS_ERR(pda_psy_ac)) { + dev_err(dev, "failed to register %s power supply\n", + pda_psy_ac_desc.name); + ret = PTR_ERR(pda_psy_ac); + goto ac_supply_failed; + } + + if (ac_irq) { + ret = request_irq(ac_irq->start, power_changed_isr, + get_irq_flags(ac_irq), ac_irq->name, + pda_psy_ac); + if (ret) { + dev_err(dev, "request ac irq failed\n"); + goto ac_irq_failed; + } + } else { + polling = 1; + } + } + + if (pdata->is_usb_online) { + pda_psy_usb = power_supply_register(&pdev->dev, + &pda_psy_usb_desc, + &psy_cfg); + if (IS_ERR(pda_psy_usb)) { + dev_err(dev, "failed to register %s power supply\n", + pda_psy_usb_desc.name); + ret = PTR_ERR(pda_psy_usb); + goto usb_supply_failed; + } + + if (usb_irq) { + ret = request_irq(usb_irq->start, power_changed_isr, + get_irq_flags(usb_irq), + usb_irq->name, pda_psy_usb); + if (ret) { + dev_err(dev, "request usb irq failed\n"); + goto usb_irq_failed; + } + } else { + polling = 1; + } + } + +#if IS_ENABLED(CONFIG_USB_PHY) + if (!IS_ERR_OR_NULL(transceiver) && pdata->use_otg_notifier) { + otg_nb.notifier_call = otg_handle_notification; + ret = usb_register_notifier(transceiver, &otg_nb); + if (ret) { + dev_err(dev, "failure to register otg notifier\n"); + goto otg_reg_notifier_failed; + } + polling = 0; + } +#endif + + if (polling) { + dev_dbg(dev, "will poll for status\n"); + setup_timer(&polling_timer, polling_timer_func, 0); + mod_timer(&polling_timer, + jiffies + msecs_to_jiffies(pdata->polling_interval)); + } + + if (ac_irq || usb_irq) + device_init_wakeup(&pdev->dev, 1); + + return 0; + +#if IS_ENABLED(CONFIG_USB_PHY) +otg_reg_notifier_failed: + if (pdata->is_usb_online && usb_irq) + free_irq(usb_irq->start, pda_psy_usb); +#endif +usb_irq_failed: + if (pdata->is_usb_online) + power_supply_unregister(pda_psy_usb); +usb_supply_failed: + if (pdata->is_ac_online && ac_irq) + free_irq(ac_irq->start, pda_psy_ac); +#if IS_ENABLED(CONFIG_USB_PHY) + if (!IS_ERR_OR_NULL(transceiver)) + usb_put_phy(transceiver); +#endif +ac_irq_failed: + if (pdata->is_ac_online) + power_supply_unregister(pda_psy_ac); +ac_supply_failed: + if (ac_draw) { + regulator_put(ac_draw); + ac_draw = NULL; + } + if (pdata->exit) + pdata->exit(dev); +init_failed: +wrongid: + return ret; +} + +static int pda_power_remove(struct platform_device *pdev) +{ + if (pdata->is_usb_online && usb_irq) + free_irq(usb_irq->start, pda_psy_usb); + if (pdata->is_ac_online && ac_irq) + free_irq(ac_irq->start, pda_psy_ac); + + if (polling) + del_timer_sync(&polling_timer); + del_timer_sync(&charger_timer); + del_timer_sync(&supply_timer); + + if (pdata->is_usb_online) + power_supply_unregister(pda_psy_usb); + if (pdata->is_ac_online) + power_supply_unregister(pda_psy_ac); +#if IS_ENABLED(CONFIG_USB_PHY) + if (!IS_ERR_OR_NULL(transceiver)) + usb_put_phy(transceiver); +#endif + if (ac_draw) { + regulator_put(ac_draw); + ac_draw = NULL; + } + if (pdata->exit) + pdata->exit(dev); + + return 0; +} + +#ifdef CONFIG_PM +static int ac_wakeup_enabled; +static int usb_wakeup_enabled; + +static int pda_power_suspend(struct platform_device *pdev, pm_message_t state) +{ + if (pdata->suspend) { + int ret = pdata->suspend(state); + + if (ret) + return ret; + } + + if (device_may_wakeup(&pdev->dev)) { + if (ac_irq) + ac_wakeup_enabled = !enable_irq_wake(ac_irq->start); + if (usb_irq) + usb_wakeup_enabled = !enable_irq_wake(usb_irq->start); + } + + return 0; +} + +static int pda_power_resume(struct platform_device *pdev) +{ + if (device_may_wakeup(&pdev->dev)) { + if (usb_irq && usb_wakeup_enabled) + disable_irq_wake(usb_irq->start); + if (ac_irq && ac_wakeup_enabled) + disable_irq_wake(ac_irq->start); + } + + if (pdata->resume) + return pdata->resume(); + + return 0; +} +#else +#define pda_power_suspend NULL +#define pda_power_resume NULL +#endif /* CONFIG_PM */ + +static struct platform_driver pda_power_pdrv = { + .driver = { + .name = "pda-power", + }, + .probe = pda_power_probe, + .remove = pda_power_remove, + .suspend = pda_power_suspend, + .resume = pda_power_resume, +}; + +module_platform_driver(pda_power_pdrv); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Anton Vorontsov "); +MODULE_ALIAS("platform:pda-power"); diff --git a/drivers/power/supply/pm2301_charger.c b/drivers/power/supply/pm2301_charger.c new file mode 100644 index 000000000000..fb62ed3fc38c --- /dev/null +++ b/drivers/power/supply/pm2301_charger.c @@ -0,0 +1,1257 @@ +/* + * Copyright 2012 ST Ericsson. + * + * Power supply driver for ST Ericsson pm2xxx_charger charger + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "pm2301_charger.h" + +#define to_pm2xxx_charger_ac_device_info(x) container_of((x), \ + struct pm2xxx_charger, ac_chg) +#define SLEEP_MIN 50 +#define SLEEP_MAX 100 +#define PM2XXX_AUTOSUSPEND_DELAY 500 + +static int pm2xxx_interrupt_registers[] = { + PM2XXX_REG_INT1, + PM2XXX_REG_INT2, + PM2XXX_REG_INT3, + PM2XXX_REG_INT4, + PM2XXX_REG_INT5, + PM2XXX_REG_INT6, +}; + +static enum power_supply_property pm2xxx_charger_ac_props[] = { + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_VOLTAGE_AVG, +}; + +static int pm2xxx_charger_voltage_map[] = { + 3500, + 3525, + 3550, + 3575, + 3600, + 3625, + 3650, + 3675, + 3700, + 3725, + 3750, + 3775, + 3800, + 3825, + 3850, + 3875, + 3900, + 3925, + 3950, + 3975, + 4000, + 4025, + 4050, + 4075, + 4100, + 4125, + 4150, + 4175, + 4200, + 4225, + 4250, + 4275, + 4300, +}; + +static int pm2xxx_charger_current_map[] = { + 200, + 200, + 400, + 600, + 800, + 1000, + 1200, + 1400, + 1600, + 1800, + 2000, + 2200, + 2400, + 2600, + 2800, + 3000, +}; + +static const struct i2c_device_id pm2xxx_ident[] = { + { "pm2301", 0 }, + { } +}; + +static void set_lpn_pin(struct pm2xxx_charger *pm2) +{ + if (!pm2->ac.charger_connected && gpio_is_valid(pm2->lpn_pin)) { + gpio_set_value(pm2->lpn_pin, 1); + usleep_range(SLEEP_MIN, SLEEP_MAX); + } +} + +static void clear_lpn_pin(struct pm2xxx_charger *pm2) +{ + if (!pm2->ac.charger_connected && gpio_is_valid(pm2->lpn_pin)) + gpio_set_value(pm2->lpn_pin, 0); +} + +static int pm2xxx_reg_read(struct pm2xxx_charger *pm2, int reg, u8 *val) +{ + int ret; + + /* wake up the device */ + pm_runtime_get_sync(pm2->dev); + + ret = i2c_smbus_read_i2c_block_data(pm2->config.pm2xxx_i2c, reg, + 1, val); + if (ret < 0) + dev_err(pm2->dev, "Error reading register at 0x%x\n", reg); + else + ret = 0; + + pm_runtime_put_sync(pm2->dev); + + return ret; +} + +static int pm2xxx_reg_write(struct pm2xxx_charger *pm2, int reg, u8 val) +{ + int ret; + + /* wake up the device */ + pm_runtime_get_sync(pm2->dev); + + ret = i2c_smbus_write_i2c_block_data(pm2->config.pm2xxx_i2c, reg, + 1, &val); + if (ret < 0) + dev_err(pm2->dev, "Error writing register at 0x%x\n", reg); + else + ret = 0; + + pm_runtime_put_sync(pm2->dev); + + return ret; +} + +static int pm2xxx_charging_enable_mngt(struct pm2xxx_charger *pm2) +{ + int ret; + + /* Enable charging */ + ret = pm2xxx_reg_write(pm2, PM2XXX_BATT_CTRL_REG2, + (PM2XXX_CH_AUTO_RESUME_EN | PM2XXX_CHARGER_ENA)); + + return ret; +} + +static int pm2xxx_charging_disable_mngt(struct pm2xxx_charger *pm2) +{ + int ret; + + /* Disable SW EOC ctrl */ + ret = pm2xxx_reg_write(pm2, PM2XXX_SW_CTRL_REG, PM2XXX_SWCTRL_HW); + if (ret < 0) { + dev_err(pm2->dev, "%s pm2xxx write failed\n", __func__); + return ret; + } + + /* Disable charging */ + ret = pm2xxx_reg_write(pm2, PM2XXX_BATT_CTRL_REG2, + (PM2XXX_CH_AUTO_RESUME_DIS | PM2XXX_CHARGER_DIS)); + if (ret < 0) { + dev_err(pm2->dev, "%s pm2xxx write failed\n", __func__); + return ret; + } + + return 0; +} + +static int pm2xxx_charger_batt_therm_mngt(struct pm2xxx_charger *pm2, int val) +{ + queue_work(pm2->charger_wq, &pm2->check_main_thermal_prot_work); + + return 0; +} + + +static int pm2xxx_charger_die_therm_mngt(struct pm2xxx_charger *pm2, int val) +{ + queue_work(pm2->charger_wq, &pm2->check_main_thermal_prot_work); + + return 0; +} + +static int pm2xxx_charger_ovv_mngt(struct pm2xxx_charger *pm2, int val) +{ + dev_err(pm2->dev, "Overvoltage detected\n"); + pm2->flags.ovv = true; + power_supply_changed(pm2->ac_chg.psy); + + /* Schedule a new HW failure check */ + queue_delayed_work(pm2->charger_wq, &pm2->check_hw_failure_work, 0); + + return 0; +} + +static int pm2xxx_charger_wd_exp_mngt(struct pm2xxx_charger *pm2, int val) +{ + dev_dbg(pm2->dev , "20 minutes watchdog expired\n"); + + pm2->ac.wd_expired = true; + power_supply_changed(pm2->ac_chg.psy); + + return 0; +} + +static int pm2xxx_charger_vbat_lsig_mngt(struct pm2xxx_charger *pm2, int val) +{ + int ret; + + switch (val) { + case PM2XXX_INT1_ITVBATLOWR: + dev_dbg(pm2->dev, "VBAT grows above VBAT_LOW level\n"); + /* Enable SW EOC ctrl */ + ret = pm2xxx_reg_write(pm2, PM2XXX_SW_CTRL_REG, + PM2XXX_SWCTRL_SW); + if (ret < 0) { + dev_err(pm2->dev, "%s pm2xxx write failed\n", __func__); + return ret; + } + break; + + case PM2XXX_INT1_ITVBATLOWF: + dev_dbg(pm2->dev, "VBAT drops below VBAT_LOW level\n"); + /* Disable SW EOC ctrl */ + ret = pm2xxx_reg_write(pm2, PM2XXX_SW_CTRL_REG, + PM2XXX_SWCTRL_HW); + if (ret < 0) { + dev_err(pm2->dev, "%s pm2xxx write failed\n", __func__); + return ret; + } + break; + + default: + dev_err(pm2->dev, "Unknown VBAT level\n"); + } + + return 0; +} + +static int pm2xxx_charger_bat_disc_mngt(struct pm2xxx_charger *pm2, int val) +{ + dev_dbg(pm2->dev, "battery disconnected\n"); + + return 0; +} + +static int pm2xxx_charger_detection(struct pm2xxx_charger *pm2, u8 *val) +{ + int ret; + + ret = pm2xxx_reg_read(pm2, PM2XXX_SRCE_REG_INT2, val); + + if (ret < 0) { + dev_err(pm2->dev, "Charger detection failed\n"); + goto out; + } + + *val &= (PM2XXX_INT2_S_ITVPWR1PLUG | PM2XXX_INT2_S_ITVPWR2PLUG); + +out: + return ret; +} + +static int pm2xxx_charger_itv_pwr_plug_mngt(struct pm2xxx_charger *pm2, int val) +{ + + int ret; + u8 read_val; + + /* + * Since we can't be sure that the events are received + * synchronously, we have the check if the main charger is + * connected by reading the interrupt source register. + */ + ret = pm2xxx_charger_detection(pm2, &read_val); + + if ((ret == 0) && read_val) { + pm2->ac.charger_connected = 1; + pm2->ac_conn = true; + queue_work(pm2->charger_wq, &pm2->ac_work); + } + + + return ret; +} + +static int pm2xxx_charger_itv_pwr_unplug_mngt(struct pm2xxx_charger *pm2, + int val) +{ + pm2->ac.charger_connected = 0; + queue_work(pm2->charger_wq, &pm2->ac_work); + + return 0; +} + +static int pm2_int_reg0(void *pm2_data, int val) +{ + struct pm2xxx_charger *pm2 = pm2_data; + int ret = 0; + + if (val & PM2XXX_INT1_ITVBATLOWR) { + ret = pm2xxx_charger_vbat_lsig_mngt(pm2, + PM2XXX_INT1_ITVBATLOWR); + if (ret < 0) + goto out; + } + + if (val & PM2XXX_INT1_ITVBATLOWF) { + ret = pm2xxx_charger_vbat_lsig_mngt(pm2, + PM2XXX_INT1_ITVBATLOWF); + if (ret < 0) + goto out; + } + + if (val & PM2XXX_INT1_ITVBATDISCONNECT) { + ret = pm2xxx_charger_bat_disc_mngt(pm2, + PM2XXX_INT1_ITVBATDISCONNECT); + if (ret < 0) + goto out; + } +out: + return ret; +} + +static int pm2_int_reg1(void *pm2_data, int val) +{ + struct pm2xxx_charger *pm2 = pm2_data; + int ret = 0; + + if (val & (PM2XXX_INT2_ITVPWR1PLUG | PM2XXX_INT2_ITVPWR2PLUG)) { + dev_dbg(pm2->dev , "Main charger plugged\n"); + ret = pm2xxx_charger_itv_pwr_plug_mngt(pm2, val & + (PM2XXX_INT2_ITVPWR1PLUG | PM2XXX_INT2_ITVPWR2PLUG)); + } + + if (val & + (PM2XXX_INT2_ITVPWR1UNPLUG | PM2XXX_INT2_ITVPWR2UNPLUG)) { + dev_dbg(pm2->dev , "Main charger unplugged\n"); + ret = pm2xxx_charger_itv_pwr_unplug_mngt(pm2, val & + (PM2XXX_INT2_ITVPWR1UNPLUG | + PM2XXX_INT2_ITVPWR2UNPLUG)); + } + + return ret; +} + +static int pm2_int_reg2(void *pm2_data, int val) +{ + struct pm2xxx_charger *pm2 = pm2_data; + int ret = 0; + + if (val & PM2XXX_INT3_ITAUTOTIMEOUTWD) + ret = pm2xxx_charger_wd_exp_mngt(pm2, val); + + if (val & (PM2XXX_INT3_ITCHPRECHARGEWD | + PM2XXX_INT3_ITCHCCWD | PM2XXX_INT3_ITCHCVWD)) { + dev_dbg(pm2->dev, + "Watchdog occurred for precharge, CC and CV charge\n"); + } + + return ret; +} + +static int pm2_int_reg3(void *pm2_data, int val) +{ + struct pm2xxx_charger *pm2 = pm2_data; + int ret = 0; + + if (val & (PM2XXX_INT4_ITCHARGINGON)) { + dev_dbg(pm2->dev , + "chargind operation has started\n"); + } + + if (val & (PM2XXX_INT4_ITVRESUME)) { + dev_dbg(pm2->dev, + "battery discharged down to VResume threshold\n"); + } + + if (val & (PM2XXX_INT4_ITBATTFULL)) { + dev_dbg(pm2->dev , "battery fully detected\n"); + } + + if (val & (PM2XXX_INT4_ITCVPHASE)) { + dev_dbg(pm2->dev, "CV phase enter with 0.5C charging\n"); + } + + if (val & (PM2XXX_INT4_ITVPWR2OVV | PM2XXX_INT4_ITVPWR1OVV)) { + pm2->failure_case = VPWR_OVV; + ret = pm2xxx_charger_ovv_mngt(pm2, val & + (PM2XXX_INT4_ITVPWR2OVV | PM2XXX_INT4_ITVPWR1OVV)); + dev_dbg(pm2->dev, "VPWR/VSYSTEM overvoltage detected\n"); + } + + if (val & (PM2XXX_INT4_S_ITBATTEMPCOLD | + PM2XXX_INT4_S_ITBATTEMPHOT)) { + ret = pm2xxx_charger_batt_therm_mngt(pm2, val & + (PM2XXX_INT4_S_ITBATTEMPCOLD | + PM2XXX_INT4_S_ITBATTEMPHOT)); + dev_dbg(pm2->dev, "BTEMP is too Low/High\n"); + } + + return ret; +} + +static int pm2_int_reg4(void *pm2_data, int val) +{ + struct pm2xxx_charger *pm2 = pm2_data; + int ret = 0; + + if (val & PM2XXX_INT5_ITVSYSTEMOVV) { + pm2->failure_case = VSYSTEM_OVV; + ret = pm2xxx_charger_ovv_mngt(pm2, val & + PM2XXX_INT5_ITVSYSTEMOVV); + dev_dbg(pm2->dev, "VSYSTEM overvoltage detected\n"); + } + + if (val & (PM2XXX_INT5_ITTHERMALWARNINGFALL | + PM2XXX_INT5_ITTHERMALWARNINGRISE | + PM2XXX_INT5_ITTHERMALSHUTDOWNFALL | + PM2XXX_INT5_ITTHERMALSHUTDOWNRISE)) { + dev_dbg(pm2->dev, "BTEMP die temperature is too Low/High\n"); + ret = pm2xxx_charger_die_therm_mngt(pm2, val & + (PM2XXX_INT5_ITTHERMALWARNINGFALL | + PM2XXX_INT5_ITTHERMALWARNINGRISE | + PM2XXX_INT5_ITTHERMALSHUTDOWNFALL | + PM2XXX_INT5_ITTHERMALSHUTDOWNRISE)); + } + + return ret; +} + +static int pm2_int_reg5(void *pm2_data, int val) +{ + struct pm2xxx_charger *pm2 = pm2_data; + int ret = 0; + + if (val & (PM2XXX_INT6_ITVPWR2DROP | PM2XXX_INT6_ITVPWR1DROP)) { + dev_dbg(pm2->dev, "VMPWR drop to VBAT level\n"); + } + + if (val & (PM2XXX_INT6_ITVPWR2VALIDRISE | + PM2XXX_INT6_ITVPWR1VALIDRISE | + PM2XXX_INT6_ITVPWR2VALIDFALL | + PM2XXX_INT6_ITVPWR1VALIDFALL)) { + dev_dbg(pm2->dev, "Falling/Rising edge on WPWR1/2\n"); + } + + return ret; +} + +static irqreturn_t pm2xxx_irq_int(int irq, void *data) +{ + struct pm2xxx_charger *pm2 = data; + struct pm2xxx_interrupts *interrupt = pm2->pm2_int; + int i; + + /* wake up the device */ + pm_runtime_get_sync(pm2->dev); + + do { + for (i = 0; i < PM2XXX_NUM_INT_REG; i++) { + pm2xxx_reg_read(pm2, + pm2xxx_interrupt_registers[i], + &(interrupt->reg[i])); + + if (interrupt->reg[i] > 0) + interrupt->handler[i](pm2, interrupt->reg[i]); + } + } while (gpio_get_value(pm2->pdata->gpio_irq_number) == 0); + + pm_runtime_mark_last_busy(pm2->dev); + pm_runtime_put_autosuspend(pm2->dev); + + return IRQ_HANDLED; +} + +static int pm2xxx_charger_get_ac_cv(struct pm2xxx_charger *pm2) +{ + int ret = 0; + u8 val; + + if (pm2->ac.charger_connected && pm2->ac.charger_online) { + + ret = pm2xxx_reg_read(pm2, PM2XXX_SRCE_REG_INT4, &val); + if (ret < 0) { + dev_err(pm2->dev, "%s pm2xxx read failed\n", __func__); + goto out; + } + + if (val & PM2XXX_INT4_S_ITCVPHASE) + ret = PM2XXX_CONST_VOLT; + else + ret = PM2XXX_CONST_CURR; + } +out: + return ret; +} + +static int pm2xxx_current_to_regval(int curr) +{ + int i; + + if (curr < pm2xxx_charger_current_map[0]) + return 0; + + for (i = 1; i < ARRAY_SIZE(pm2xxx_charger_current_map); i++) { + if (curr < pm2xxx_charger_current_map[i]) + return (i - 1); + } + + i = ARRAY_SIZE(pm2xxx_charger_current_map) - 1; + if (curr == pm2xxx_charger_current_map[i]) + return i; + else + return -EINVAL; +} + +static int pm2xxx_voltage_to_regval(int curr) +{ + int i; + + if (curr < pm2xxx_charger_voltage_map[0]) + return 0; + + for (i = 1; i < ARRAY_SIZE(pm2xxx_charger_voltage_map); i++) { + if (curr < pm2xxx_charger_voltage_map[i]) + return i - 1; + } + + i = ARRAY_SIZE(pm2xxx_charger_voltage_map) - 1; + if (curr == pm2xxx_charger_voltage_map[i]) + return i; + else + return -EINVAL; +} + +static int pm2xxx_charger_update_charger_current(struct ux500_charger *charger, + int ich_out) +{ + int ret; + int curr_index; + struct pm2xxx_charger *pm2; + u8 val; + + if (charger->psy->desc->type == POWER_SUPPLY_TYPE_MAINS) + pm2 = to_pm2xxx_charger_ac_device_info(charger); + else + return -ENXIO; + + curr_index = pm2xxx_current_to_regval(ich_out); + if (curr_index < 0) { + dev_err(pm2->dev, + "Charger current too high, charging not started\n"); + return -ENXIO; + } + + ret = pm2xxx_reg_read(pm2, PM2XXX_BATT_CTRL_REG6, &val); + if (ret >= 0) { + val &= ~PM2XXX_DIR_CH_CC_CURRENT_MASK; + val |= curr_index; + ret = pm2xxx_reg_write(pm2, PM2XXX_BATT_CTRL_REG6, val); + if (ret < 0) { + dev_err(pm2->dev, + "%s write failed\n", __func__); + } + } + else + dev_err(pm2->dev, "%s read failed\n", __func__); + + return ret; +} + +static int pm2xxx_charger_ac_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct pm2xxx_charger *pm2; + + pm2 = to_pm2xxx_charger_ac_device_info(psy_to_ux500_charger(psy)); + + switch (psp) { + case POWER_SUPPLY_PROP_HEALTH: + if (pm2->flags.mainextchnotok) + val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; + else if (pm2->ac.wd_expired) + val->intval = POWER_SUPPLY_HEALTH_DEAD; + else if (pm2->flags.main_thermal_prot) + val->intval = POWER_SUPPLY_HEALTH_OVERHEAT; + else if (pm2->flags.ovv) + val->intval = POWER_SUPPLY_HEALTH_OVERVOLTAGE; + else + val->intval = POWER_SUPPLY_HEALTH_GOOD; + break; + case POWER_SUPPLY_PROP_ONLINE: + val->intval = pm2->ac.charger_online; + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = pm2->ac.charger_connected; + break; + case POWER_SUPPLY_PROP_VOLTAGE_AVG: + pm2->ac.cv_active = pm2xxx_charger_get_ac_cv(pm2); + val->intval = pm2->ac.cv_active; + break; + default: + return -EINVAL; + } + return 0; +} + +static int pm2xxx_charging_init(struct pm2xxx_charger *pm2) +{ + int ret = 0; + + /* enable CC and CV watchdog */ + ret = pm2xxx_reg_write(pm2, PM2XXX_BATT_CTRL_REG3, + (PM2XXX_CH_WD_CV_PHASE_60MIN | PM2XXX_CH_WD_CC_PHASE_60MIN)); + if( ret < 0) + return ret; + + /* enable precharge watchdog */ + ret = pm2xxx_reg_write(pm2, PM2XXX_BATT_CTRL_REG4, + PM2XXX_CH_WD_PRECH_PHASE_60MIN); + + /* Disable auto timeout */ + ret = pm2xxx_reg_write(pm2, PM2XXX_BATT_CTRL_REG5, + PM2XXX_CH_WD_AUTO_TIMEOUT_20MIN); + + /* + * EOC current level = 100mA + * Precharge current level = 100mA + * CC current level = 1000mA + */ + ret = pm2xxx_reg_write(pm2, PM2XXX_BATT_CTRL_REG6, + (PM2XXX_DIR_CH_CC_CURRENT_1000MA | + PM2XXX_CH_PRECH_CURRENT_100MA | + PM2XXX_CH_EOC_CURRENT_100MA)); + + /* + * recharge threshold = 3.8V + * Precharge to CC threshold = 2.9V + */ + ret = pm2xxx_reg_write(pm2, PM2XXX_BATT_CTRL_REG7, + (PM2XXX_CH_PRECH_VOL_2_9 | PM2XXX_CH_VRESUME_VOL_3_8)); + + /* float voltage charger level = 4.2V */ + ret = pm2xxx_reg_write(pm2, PM2XXX_BATT_CTRL_REG8, + PM2XXX_CH_VOLT_4_2); + + /* Voltage drop between VBAT and VSYS in HW charging = 300mV */ + ret = pm2xxx_reg_write(pm2, PM2XXX_BATT_CTRL_REG9, + (PM2XXX_CH_150MV_DROP_300MV | PM2XXX_CHARCHING_INFO_DIS | + PM2XXX_CH_CC_REDUCED_CURRENT_IDENT | + PM2XXX_CH_CC_MODEDROP_DIS)); + + /* Input charger level of over voltage = 10V */ + ret = pm2xxx_reg_write(pm2, PM2XXX_INP_VOLT_VPWR2, + PM2XXX_VPWR2_OVV_10); + ret = pm2xxx_reg_write(pm2, PM2XXX_INP_VOLT_VPWR1, + PM2XXX_VPWR1_OVV_10); + + /* Input charger drop */ + ret = pm2xxx_reg_write(pm2, PM2XXX_INP_DROP_VPWR2, + (PM2XXX_VPWR2_HW_OPT_DIS | PM2XXX_VPWR2_VALID_DIS | + PM2XXX_VPWR2_DROP_DIS)); + ret = pm2xxx_reg_write(pm2, PM2XXX_INP_DROP_VPWR1, + (PM2XXX_VPWR1_HW_OPT_DIS | PM2XXX_VPWR1_VALID_DIS | + PM2XXX_VPWR1_DROP_DIS)); + + /* Disable battery low monitoring */ + ret = pm2xxx_reg_write(pm2, PM2XXX_BATT_LOW_LEV_COMP_REG, + PM2XXX_VBAT_LOW_MONITORING_ENA); + + return ret; +} + +static int pm2xxx_charger_ac_en(struct ux500_charger *charger, + int enable, int vset, int iset) +{ + int ret; + int volt_index; + int curr_index; + u8 val; + + struct pm2xxx_charger *pm2 = to_pm2xxx_charger_ac_device_info(charger); + + if (enable) { + if (!pm2->ac.charger_connected) { + dev_dbg(pm2->dev, "AC charger not connected\n"); + return -ENXIO; + } + + dev_dbg(pm2->dev, "Enable AC: %dmV %dmA\n", vset, iset); + if (!pm2->vddadc_en_ac) { + ret = regulator_enable(pm2->regu); + if (ret) + dev_warn(pm2->dev, + "Failed to enable vddadc regulator\n"); + else + pm2->vddadc_en_ac = true; + } + + ret = pm2xxx_charging_init(pm2); + if (ret < 0) { + dev_err(pm2->dev, "%s charging init failed\n", + __func__); + goto error_occured; + } + + volt_index = pm2xxx_voltage_to_regval(vset); + curr_index = pm2xxx_current_to_regval(iset); + + if (volt_index < 0 || curr_index < 0) { + dev_err(pm2->dev, + "Charger voltage or current too high, " + "charging not started\n"); + return -ENXIO; + } + + ret = pm2xxx_reg_read(pm2, PM2XXX_BATT_CTRL_REG8, &val); + if (ret < 0) { + dev_err(pm2->dev, "%s pm2xxx read failed\n", __func__); + goto error_occured; + } + val &= ~PM2XXX_CH_VOLT_MASK; + val |= volt_index; + ret = pm2xxx_reg_write(pm2, PM2XXX_BATT_CTRL_REG8, val); + if (ret < 0) { + dev_err(pm2->dev, "%s pm2xxx write failed\n", __func__); + goto error_occured; + } + + ret = pm2xxx_reg_read(pm2, PM2XXX_BATT_CTRL_REG6, &val); + if (ret < 0) { + dev_err(pm2->dev, "%s pm2xxx read failed\n", __func__); + goto error_occured; + } + val &= ~PM2XXX_DIR_CH_CC_CURRENT_MASK; + val |= curr_index; + ret = pm2xxx_reg_write(pm2, PM2XXX_BATT_CTRL_REG6, val); + if (ret < 0) { + dev_err(pm2->dev, "%s pm2xxx write failed\n", __func__); + goto error_occured; + } + + if (!pm2->bat->enable_overshoot) { + ret = pm2xxx_reg_read(pm2, PM2XXX_LED_CTRL_REG, &val); + if (ret < 0) { + dev_err(pm2->dev, "%s pm2xxx read failed\n", + __func__); + goto error_occured; + } + val |= PM2XXX_ANTI_OVERSHOOT_EN; + ret = pm2xxx_reg_write(pm2, PM2XXX_LED_CTRL_REG, val); + if (ret < 0) { + dev_err(pm2->dev, "%s pm2xxx write failed\n", + __func__); + goto error_occured; + } + } + + ret = pm2xxx_charging_enable_mngt(pm2); + if (ret < 0) { + dev_err(pm2->dev, "Failed to enable" + "pm2xxx ac charger\n"); + goto error_occured; + } + + pm2->ac.charger_online = 1; + } else { + pm2->ac.charger_online = 0; + pm2->ac.wd_expired = false; + + /* Disable regulator if enabled */ + if (pm2->vddadc_en_ac) { + regulator_disable(pm2->regu); + pm2->vddadc_en_ac = false; + } + + ret = pm2xxx_charging_disable_mngt(pm2); + if (ret < 0) { + dev_err(pm2->dev, "failed to disable" + "pm2xxx ac charger\n"); + goto error_occured; + } + + dev_dbg(pm2->dev, "PM2301: " "Disabled AC charging\n"); + } + power_supply_changed(pm2->ac_chg.psy); + +error_occured: + return ret; +} + +static int pm2xxx_charger_watchdog_kick(struct ux500_charger *charger) +{ + int ret; + struct pm2xxx_charger *pm2; + + if (charger->psy->desc->type == POWER_SUPPLY_TYPE_MAINS) + pm2 = to_pm2xxx_charger_ac_device_info(charger); + else + return -ENXIO; + + ret = pm2xxx_reg_write(pm2, PM2XXX_BATT_WD_KICK, WD_TIMER); + if (ret) + dev_err(pm2->dev, "Failed to kick WD!\n"); + + return ret; +} + +static void pm2xxx_charger_ac_work(struct work_struct *work) +{ + struct pm2xxx_charger *pm2 = container_of(work, + struct pm2xxx_charger, ac_work); + + + power_supply_changed(pm2->ac_chg.psy); + sysfs_notify(&pm2->ac_chg.psy->dev.kobj, NULL, "present"); +}; + +static void pm2xxx_charger_check_hw_failure_work(struct work_struct *work) +{ + u8 reg_value; + + struct pm2xxx_charger *pm2 = container_of(work, + struct pm2xxx_charger, check_hw_failure_work.work); + + if (pm2->flags.ovv) { + pm2xxx_reg_read(pm2, PM2XXX_SRCE_REG_INT4, ®_value); + + if (!(reg_value & (PM2XXX_INT4_S_ITVPWR1OVV | + PM2XXX_INT4_S_ITVPWR2OVV))) { + pm2->flags.ovv = false; + power_supply_changed(pm2->ac_chg.psy); + } + } + + /* If we still have a failure, schedule a new check */ + if (pm2->flags.ovv) { + queue_delayed_work(pm2->charger_wq, + &pm2->check_hw_failure_work, round_jiffies(HZ)); + } +} + +static void pm2xxx_charger_check_main_thermal_prot_work( + struct work_struct *work) +{ + int ret; + u8 val; + + struct pm2xxx_charger *pm2 = container_of(work, struct pm2xxx_charger, + check_main_thermal_prot_work); + + /* Check if die temp warning is still active */ + ret = pm2xxx_reg_read(pm2, PM2XXX_SRCE_REG_INT5, &val); + if (ret < 0) { + dev_err(pm2->dev, "%s pm2xxx read failed\n", __func__); + return; + } + if (val & (PM2XXX_INT5_S_ITTHERMALWARNINGRISE + | PM2XXX_INT5_S_ITTHERMALSHUTDOWNRISE)) + pm2->flags.main_thermal_prot = true; + else if (val & (PM2XXX_INT5_S_ITTHERMALWARNINGFALL + | PM2XXX_INT5_S_ITTHERMALSHUTDOWNFALL)) + pm2->flags.main_thermal_prot = false; + + power_supply_changed(pm2->ac_chg.psy); +} + +static struct pm2xxx_interrupts pm2xxx_int = { + .handler[0] = pm2_int_reg0, + .handler[1] = pm2_int_reg1, + .handler[2] = pm2_int_reg2, + .handler[3] = pm2_int_reg3, + .handler[4] = pm2_int_reg4, + .handler[5] = pm2_int_reg5, +}; + +static struct pm2xxx_irq pm2xxx_charger_irq[] = { + {"PM2XXX_IRQ_INT", pm2xxx_irq_int}, +}; + +static int __maybe_unused pm2xxx_wall_charger_resume(struct device *dev) +{ + struct i2c_client *i2c_client = to_i2c_client(dev); + struct pm2xxx_charger *pm2; + + pm2 = (struct pm2xxx_charger *)i2c_get_clientdata(i2c_client); + set_lpn_pin(pm2); + + /* If we still have a HW failure, schedule a new check */ + if (pm2->flags.ovv) + queue_delayed_work(pm2->charger_wq, + &pm2->check_hw_failure_work, 0); + + return 0; +} + +static int __maybe_unused pm2xxx_wall_charger_suspend(struct device *dev) +{ + struct i2c_client *i2c_client = to_i2c_client(dev); + struct pm2xxx_charger *pm2; + + pm2 = (struct pm2xxx_charger *)i2c_get_clientdata(i2c_client); + clear_lpn_pin(pm2); + + /* Cancel any pending HW failure check */ + if (delayed_work_pending(&pm2->check_hw_failure_work)) + cancel_delayed_work(&pm2->check_hw_failure_work); + + flush_work(&pm2->ac_work); + flush_work(&pm2->check_main_thermal_prot_work); + + return 0; +} + +static int __maybe_unused pm2xxx_runtime_suspend(struct device *dev) +{ + struct i2c_client *pm2xxx_i2c_client = to_i2c_client(dev); + struct pm2xxx_charger *pm2; + + pm2 = (struct pm2xxx_charger *)i2c_get_clientdata(pm2xxx_i2c_client); + clear_lpn_pin(pm2); + + return 0; +} + +static int __maybe_unused pm2xxx_runtime_resume(struct device *dev) +{ + struct i2c_client *pm2xxx_i2c_client = to_i2c_client(dev); + struct pm2xxx_charger *pm2; + + pm2 = (struct pm2xxx_charger *)i2c_get_clientdata(pm2xxx_i2c_client); + + if (gpio_is_valid(pm2->lpn_pin) && gpio_get_value(pm2->lpn_pin) == 0) + set_lpn_pin(pm2); + + return 0; +} + +static const struct dev_pm_ops pm2xxx_pm_ops __maybe_unused = { + SET_SYSTEM_SLEEP_PM_OPS(pm2xxx_wall_charger_suspend, + pm2xxx_wall_charger_resume) + SET_RUNTIME_PM_OPS(pm2xxx_runtime_suspend, pm2xxx_runtime_resume, NULL) +}; + +static int pm2xxx_wall_charger_probe(struct i2c_client *i2c_client, + const struct i2c_device_id *id) +{ + struct pm2xxx_platform_data *pl_data = i2c_client->dev.platform_data; + struct power_supply_config psy_cfg = {}; + struct pm2xxx_charger *pm2; + int ret = 0; + u8 val; + int i; + + if (!pl_data) { + dev_err(&i2c_client->dev, "No platform data supplied\n"); + return -EINVAL; + } + + pm2 = kzalloc(sizeof(struct pm2xxx_charger), GFP_KERNEL); + if (!pm2) { + dev_err(&i2c_client->dev, "pm2xxx_charger allocation failed\n"); + return -ENOMEM; + } + + /* get parent data */ + pm2->dev = &i2c_client->dev; + + pm2->pm2_int = &pm2xxx_int; + + /* get charger spcific platform data */ + if (!pl_data->wall_charger) { + dev_err(pm2->dev, "no charger platform data supplied\n"); + ret = -EINVAL; + goto free_device_info; + } + + pm2->pdata = pl_data->wall_charger; + + /* get battery specific platform data */ + if (!pl_data->battery) { + dev_err(pm2->dev, "no battery platform data supplied\n"); + ret = -EINVAL; + goto free_device_info; + } + + pm2->bat = pl_data->battery; + + if (!i2c_check_functionality(i2c_client->adapter, + I2C_FUNC_SMBUS_BYTE_DATA | + I2C_FUNC_SMBUS_READ_WORD_DATA)) { + ret = -ENODEV; + dev_info(pm2->dev, "pm2301 i2c_check_functionality failed\n"); + goto free_device_info; + } + + pm2->config.pm2xxx_i2c = i2c_client; + pm2->config.pm2xxx_id = (struct i2c_device_id *) id; + i2c_set_clientdata(i2c_client, pm2); + + /* AC supply */ + /* power_supply base class */ + pm2->ac_chg_desc.name = pm2->pdata->label; + pm2->ac_chg_desc.type = POWER_SUPPLY_TYPE_MAINS; + pm2->ac_chg_desc.properties = pm2xxx_charger_ac_props; + pm2->ac_chg_desc.num_properties = ARRAY_SIZE(pm2xxx_charger_ac_props); + pm2->ac_chg_desc.get_property = pm2xxx_charger_ac_get_property; + + psy_cfg.supplied_to = pm2->pdata->supplied_to; + psy_cfg.num_supplicants = pm2->pdata->num_supplicants; + /* pm2xxx_charger sub-class */ + pm2->ac_chg.ops.enable = &pm2xxx_charger_ac_en; + pm2->ac_chg.ops.kick_wd = &pm2xxx_charger_watchdog_kick; + pm2->ac_chg.ops.update_curr = &pm2xxx_charger_update_charger_current; + pm2->ac_chg.max_out_volt = pm2xxx_charger_voltage_map[ + ARRAY_SIZE(pm2xxx_charger_voltage_map) - 1]; + pm2->ac_chg.max_out_curr = pm2xxx_charger_current_map[ + ARRAY_SIZE(pm2xxx_charger_current_map) - 1]; + pm2->ac_chg.wdt_refresh = WD_KICK_INTERVAL; + pm2->ac_chg.enabled = true; + pm2->ac_chg.external = true; + + /* Create a work queue for the charger */ + pm2->charger_wq = create_singlethread_workqueue("pm2xxx_charger_wq"); + if (pm2->charger_wq == NULL) { + ret = -ENOMEM; + dev_err(pm2->dev, "failed to create work queue\n"); + goto free_device_info; + } + + /* Init work for charger detection */ + INIT_WORK(&pm2->ac_work, pm2xxx_charger_ac_work); + + /* Init work for checking HW status */ + INIT_WORK(&pm2->check_main_thermal_prot_work, + pm2xxx_charger_check_main_thermal_prot_work); + + /* Init work for HW failure check */ + INIT_DEFERRABLE_WORK(&pm2->check_hw_failure_work, + pm2xxx_charger_check_hw_failure_work); + + /* + * VDD ADC supply needs to be enabled from this driver when there + * is a charger connected to avoid erroneous BTEMP_HIGH/LOW + * interrupts during charging + */ + pm2->regu = regulator_get(pm2->dev, "vddadc"); + if (IS_ERR(pm2->regu)) { + ret = PTR_ERR(pm2->regu); + dev_err(pm2->dev, "failed to get vddadc regulator\n"); + goto free_charger_wq; + } + + /* Register AC charger class */ + pm2->ac_chg.psy = power_supply_register(pm2->dev, &pm2->ac_chg_desc, + &psy_cfg); + if (IS_ERR(pm2->ac_chg.psy)) { + dev_err(pm2->dev, "failed to register AC charger\n"); + ret = PTR_ERR(pm2->ac_chg.psy); + goto free_regulator; + } + + /* Register interrupts */ + ret = request_threaded_irq(gpio_to_irq(pm2->pdata->gpio_irq_number), + NULL, + pm2xxx_charger_irq[0].isr, + pm2->pdata->irq_type, + pm2xxx_charger_irq[0].name, pm2); + + if (ret != 0) { + dev_err(pm2->dev, "failed to request %s IRQ %d: %d\n", + pm2xxx_charger_irq[0].name, + gpio_to_irq(pm2->pdata->gpio_irq_number), ret); + goto unregister_pm2xxx_charger; + } + + ret = pm_runtime_set_active(pm2->dev); + if (ret) + dev_err(pm2->dev, "set active Error\n"); + + pm_runtime_enable(pm2->dev); + pm_runtime_set_autosuspend_delay(pm2->dev, PM2XXX_AUTOSUSPEND_DELAY); + pm_runtime_use_autosuspend(pm2->dev); + pm_runtime_resume(pm2->dev); + + /* pm interrupt can wake up system */ + ret = enable_irq_wake(gpio_to_irq(pm2->pdata->gpio_irq_number)); + if (ret) { + dev_err(pm2->dev, "failed to set irq wake\n"); + goto unregister_pm2xxx_interrupt; + } + + mutex_init(&pm2->lock); + + if (gpio_is_valid(pm2->pdata->lpn_gpio)) { + /* get lpn GPIO from platform data */ + pm2->lpn_pin = pm2->pdata->lpn_gpio; + + /* + * Charger detection mechanism requires pulling up the LPN pin + * while i2c communication if Charger is not connected + * LPN pin of PM2301 is GPIO60 of AB9540 + */ + ret = gpio_request(pm2->lpn_pin, "pm2301_lpm_gpio"); + + if (ret < 0) { + dev_err(pm2->dev, "pm2301_lpm_gpio request failed\n"); + goto disable_pm2_irq_wake; + } + ret = gpio_direction_output(pm2->lpn_pin, 0); + if (ret < 0) { + dev_err(pm2->dev, "pm2301_lpm_gpio direction failed\n"); + goto free_gpio; + } + set_lpn_pin(pm2); + } + + /* read interrupt registers */ + for (i = 0; i < PM2XXX_NUM_INT_REG; i++) + pm2xxx_reg_read(pm2, + pm2xxx_interrupt_registers[i], + &val); + + ret = pm2xxx_charger_detection(pm2, &val); + + if ((ret == 0) && val) { + pm2->ac.charger_connected = 1; + ab8500_override_turn_on_stat(~AB8500_POW_KEY_1_ON, + AB8500_MAIN_CH_DET); + pm2->ac_conn = true; + power_supply_changed(pm2->ac_chg.psy); + sysfs_notify(&pm2->ac_chg.psy->dev.kobj, NULL, "present"); + } + + return 0; + +free_gpio: + if (gpio_is_valid(pm2->lpn_pin)) + gpio_free(pm2->lpn_pin); +disable_pm2_irq_wake: + disable_irq_wake(gpio_to_irq(pm2->pdata->gpio_irq_number)); +unregister_pm2xxx_interrupt: + /* disable interrupt */ + free_irq(gpio_to_irq(pm2->pdata->gpio_irq_number), pm2); +unregister_pm2xxx_charger: + /* unregister power supply */ + power_supply_unregister(pm2->ac_chg.psy); +free_regulator: + /* disable the regulator */ + regulator_put(pm2->regu); +free_charger_wq: + destroy_workqueue(pm2->charger_wq); +free_device_info: + kfree(pm2); + + return ret; +} + +static int pm2xxx_wall_charger_remove(struct i2c_client *i2c_client) +{ + struct pm2xxx_charger *pm2 = i2c_get_clientdata(i2c_client); + + /* Disable pm_runtime */ + pm_runtime_disable(pm2->dev); + /* Disable AC charging */ + pm2xxx_charger_ac_en(&pm2->ac_chg, false, 0, 0); + + /* Disable wake by pm interrupt */ + disable_irq_wake(gpio_to_irq(pm2->pdata->gpio_irq_number)); + + /* Disable interrupts */ + free_irq(gpio_to_irq(pm2->pdata->gpio_irq_number), pm2); + + /* Delete the work queue */ + destroy_workqueue(pm2->charger_wq); + + flush_scheduled_work(); + + /* disable the regulator */ + regulator_put(pm2->regu); + + power_supply_unregister(pm2->ac_chg.psy); + + if (gpio_is_valid(pm2->lpn_pin)) + gpio_free(pm2->lpn_pin); + + kfree(pm2); + + return 0; +} + +static const struct i2c_device_id pm2xxx_id[] = { + { "pm2301", 0 }, + { } +}; + +MODULE_DEVICE_TABLE(i2c, pm2xxx_id); + +static struct i2c_driver pm2xxx_charger_driver = { + .probe = pm2xxx_wall_charger_probe, + .remove = pm2xxx_wall_charger_remove, + .driver = { + .name = "pm2xxx-wall_charger", + .pm = IS_ENABLED(CONFIG_PM) ? &pm2xxx_pm_ops : NULL, + }, + .id_table = pm2xxx_id, +}; + +static int __init pm2xxx_charger_init(void) +{ + return i2c_add_driver(&pm2xxx_charger_driver); +} + +static void __exit pm2xxx_charger_exit(void) +{ + i2c_del_driver(&pm2xxx_charger_driver); +} + +device_initcall_sync(pm2xxx_charger_init); +module_exit(pm2xxx_charger_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Rajkumar kasirajan, Olivier Launay"); +MODULE_DESCRIPTION("PM2xxx charger management driver"); diff --git a/drivers/power/supply/pm2301_charger.h b/drivers/power/supply/pm2301_charger.h new file mode 100644 index 000000000000..24181cf9717b --- /dev/null +++ b/drivers/power/supply/pm2301_charger.h @@ -0,0 +1,493 @@ +/* + * Copyright (C) ST-Ericsson SA 2012 + * + * PM2301 power supply interface + * + * License terms: GNU General Public License (GPL), version 2 + */ + +#ifndef PM2301_CHARGER_H +#define PM2301_CHARGER_H + +/* Watchdog timeout constant */ +#define WD_TIMER 0x30 /* 4min */ +#define WD_KICK_INTERVAL (30 * HZ) + +#define PM2XXX_NUM_INT_REG 0x6 + +/* Constant voltage/current */ +#define PM2XXX_CONST_CURR 0x0 +#define PM2XXX_CONST_VOLT 0x1 + +/* Lowest charger voltage is 3.39V -> 0x4E */ +#define LOW_VOLT_REG 0x4E + +#define PM2XXX_BATT_CTRL_REG1 0x00 +#define PM2XXX_BATT_CTRL_REG2 0x01 +#define PM2XXX_BATT_CTRL_REG3 0x02 +#define PM2XXX_BATT_CTRL_REG4 0x03 +#define PM2XXX_BATT_CTRL_REG5 0x04 +#define PM2XXX_BATT_CTRL_REG6 0x05 +#define PM2XXX_BATT_CTRL_REG7 0x06 +#define PM2XXX_BATT_CTRL_REG8 0x07 +#define PM2XXX_NTC_CTRL_REG1 0x08 +#define PM2XXX_NTC_CTRL_REG2 0x09 +#define PM2XXX_BATT_CTRL_REG9 0x0A +#define PM2XXX_BATT_STAT_REG1 0x0B +#define PM2XXX_INP_VOLT_VPWR2 0x11 +#define PM2XXX_INP_DROP_VPWR2 0x13 +#define PM2XXX_INP_VOLT_VPWR1 0x15 +#define PM2XXX_INP_DROP_VPWR1 0x17 +#define PM2XXX_INP_MODE_VPWR 0x18 +#define PM2XXX_BATT_WD_KICK 0x70 +#define PM2XXX_DEV_VER_STAT 0x0C +#define PM2XXX_THERM_WARN_CTRL_REG 0x20 +#define PM2XXX_BATT_DISC_REG 0x21 +#define PM2XXX_BATT_LOW_LEV_COMP_REG 0x22 +#define PM2XXX_BATT_LOW_LEV_VAL_REG 0x23 +#define PM2XXX_I2C_PAD_CTRL_REG 0x24 +#define PM2XXX_SW_CTRL_REG 0x26 +#define PM2XXX_LED_CTRL_REG 0x28 + +#define PM2XXX_REG_INT1 0x40 +#define PM2XXX_MASK_REG_INT1 0x50 +#define PM2XXX_SRCE_REG_INT1 0x60 +#define PM2XXX_REG_INT2 0x41 +#define PM2XXX_MASK_REG_INT2 0x51 +#define PM2XXX_SRCE_REG_INT2 0x61 +#define PM2XXX_REG_INT3 0x42 +#define PM2XXX_MASK_REG_INT3 0x52 +#define PM2XXX_SRCE_REG_INT3 0x62 +#define PM2XXX_REG_INT4 0x43 +#define PM2XXX_MASK_REG_INT4 0x53 +#define PM2XXX_SRCE_REG_INT4 0x63 +#define PM2XXX_REG_INT5 0x44 +#define PM2XXX_MASK_REG_INT5 0x54 +#define PM2XXX_SRCE_REG_INT5 0x64 +#define PM2XXX_REG_INT6 0x45 +#define PM2XXX_MASK_REG_INT6 0x55 +#define PM2XXX_SRCE_REG_INT6 0x65 + +#define VPWR_OVV 0x0 +#define VSYSTEM_OVV 0x1 + +/* control Reg 1 */ +#define PM2XXX_CH_RESUME_EN 0x1 +#define PM2XXX_CH_RESUME_DIS 0x0 + +/* control Reg 2 */ +#define PM2XXX_CH_AUTO_RESUME_EN 0X2 +#define PM2XXX_CH_AUTO_RESUME_DIS 0X0 +#define PM2XXX_CHARGER_ENA 0x4 +#define PM2XXX_CHARGER_DIS 0x0 + +/* control Reg 3 */ +#define PM2XXX_CH_WD_CC_PHASE_OFF 0x0 +#define PM2XXX_CH_WD_CC_PHASE_5MIN 0x1 +#define PM2XXX_CH_WD_CC_PHASE_10MIN 0x2 +#define PM2XXX_CH_WD_CC_PHASE_30MIN 0x3 +#define PM2XXX_CH_WD_CC_PHASE_60MIN 0x4 +#define PM2XXX_CH_WD_CC_PHASE_120MIN 0x5 +#define PM2XXX_CH_WD_CC_PHASE_240MIN 0x6 +#define PM2XXX_CH_WD_CC_PHASE_360MIN 0x7 + +#define PM2XXX_CH_WD_CV_PHASE_OFF (0x0<<3) +#define PM2XXX_CH_WD_CV_PHASE_5MIN (0x1<<3) +#define PM2XXX_CH_WD_CV_PHASE_10MIN (0x2<<3) +#define PM2XXX_CH_WD_CV_PHASE_30MIN (0x3<<3) +#define PM2XXX_CH_WD_CV_PHASE_60MIN (0x4<<3) +#define PM2XXX_CH_WD_CV_PHASE_120MIN (0x5<<3) +#define PM2XXX_CH_WD_CV_PHASE_240MIN (0x6<<3) +#define PM2XXX_CH_WD_CV_PHASE_360MIN (0x7<<3) + +/* control Reg 4 */ +#define PM2XXX_CH_WD_PRECH_PHASE_OFF 0x0 +#define PM2XXX_CH_WD_PRECH_PHASE_1MIN 0x1 +#define PM2XXX_CH_WD_PRECH_PHASE_5MIN 0x2 +#define PM2XXX_CH_WD_PRECH_PHASE_10MIN 0x3 +#define PM2XXX_CH_WD_PRECH_PHASE_30MIN 0x4 +#define PM2XXX_CH_WD_PRECH_PHASE_60MIN 0x5 +#define PM2XXX_CH_WD_PRECH_PHASE_120MIN 0x6 +#define PM2XXX_CH_WD_PRECH_PHASE_240MIN 0x7 + +/* control Reg 5 */ +#define PM2XXX_CH_WD_AUTO_TIMEOUT_NONE 0x0 +#define PM2XXX_CH_WD_AUTO_TIMEOUT_20MIN 0x1 + +/* control Reg 6 */ +#define PM2XXX_DIR_CH_CC_CURRENT_MASK 0x0F +#define PM2XXX_DIR_CH_CC_CURRENT_200MA 0x0 +#define PM2XXX_DIR_CH_CC_CURRENT_400MA 0x2 +#define PM2XXX_DIR_CH_CC_CURRENT_600MA 0x3 +#define PM2XXX_DIR_CH_CC_CURRENT_800MA 0x4 +#define PM2XXX_DIR_CH_CC_CURRENT_1000MA 0x5 +#define PM2XXX_DIR_CH_CC_CURRENT_1200MA 0x6 +#define PM2XXX_DIR_CH_CC_CURRENT_1400MA 0x7 +#define PM2XXX_DIR_CH_CC_CURRENT_1600MA 0x8 +#define PM2XXX_DIR_CH_CC_CURRENT_1800MA 0x9 +#define PM2XXX_DIR_CH_CC_CURRENT_2000MA 0xA +#define PM2XXX_DIR_CH_CC_CURRENT_2200MA 0xB +#define PM2XXX_DIR_CH_CC_CURRENT_2400MA 0xC +#define PM2XXX_DIR_CH_CC_CURRENT_2600MA 0xD +#define PM2XXX_DIR_CH_CC_CURRENT_2800MA 0xE +#define PM2XXX_DIR_CH_CC_CURRENT_3000MA 0xF + +#define PM2XXX_CH_PRECH_CURRENT_MASK 0x30 +#define PM2XXX_CH_PRECH_CURRENT_25MA (0x0<<4) +#define PM2XXX_CH_PRECH_CURRENT_50MA (0x1<<4) +#define PM2XXX_CH_PRECH_CURRENT_75MA (0x2<<4) +#define PM2XXX_CH_PRECH_CURRENT_100MA (0x3<<4) + +#define PM2XXX_CH_EOC_CURRENT_MASK 0xC0 +#define PM2XXX_CH_EOC_CURRENT_100MA (0x0<<6) +#define PM2XXX_CH_EOC_CURRENT_150MA (0x1<<6) +#define PM2XXX_CH_EOC_CURRENT_300MA (0x2<<6) +#define PM2XXX_CH_EOC_CURRENT_400MA (0x3<<6) + +/* control Reg 7 */ +#define PM2XXX_CH_PRECH_VOL_2_5 0x0 +#define PM2XXX_CH_PRECH_VOL_2_7 0x1 +#define PM2XXX_CH_PRECH_VOL_2_9 0x2 +#define PM2XXX_CH_PRECH_VOL_3_1 0x3 + +#define PM2XXX_CH_VRESUME_VOL_3_2 (0x0<<2) +#define PM2XXX_CH_VRESUME_VOL_3_4 (0x1<<2) +#define PM2XXX_CH_VRESUME_VOL_3_6 (0x2<<2) +#define PM2XXX_CH_VRESUME_VOL_3_8 (0x3<<2) + +/* control Reg 8 */ +#define PM2XXX_CH_VOLT_MASK 0x3F +#define PM2XXX_CH_VOLT_3_5 0x0 +#define PM2XXX_CH_VOLT_3_5225 0x1 +#define PM2XXX_CH_VOLT_3_6 0x4 +#define PM2XXX_CH_VOLT_3_7 0x8 +#define PM2XXX_CH_VOLT_4_0 0x14 +#define PM2XXX_CH_VOLT_4_175 0x1B +#define PM2XXX_CH_VOLT_4_2 0x1C +#define PM2XXX_CH_VOLT_4_275 0x1F +#define PM2XXX_CH_VOLT_4_3 0x20 + +/*NTC control register 1*/ +#define PM2XXX_BTEMP_HIGH_TH_45 0x0 +#define PM2XXX_BTEMP_HIGH_TH_50 0x1 +#define PM2XXX_BTEMP_HIGH_TH_55 0x2 +#define PM2XXX_BTEMP_HIGH_TH_60 0x3 +#define PM2XXX_BTEMP_HIGH_TH_65 0x4 + +#define PM2XXX_BTEMP_LOW_TH_N5 (0x0<<3) +#define PM2XXX_BTEMP_LOW_TH_0 (0x1<<3) +#define PM2XXX_BTEMP_LOW_TH_5 (0x2<<3) +#define PM2XXX_BTEMP_LOW_TH_10 (0x3<<3) + +/*NTC control register 2*/ +#define PM2XXX_NTC_BETA_COEFF_3477 0x0 +#define PM2XXX_NTC_BETA_COEFF_3964 0x1 + +#define PM2XXX_NTC_RES_10K (0x0<<2) +#define PM2XXX_NTC_RES_47K (0x1<<2) +#define PM2XXX_NTC_RES_100K (0x2<<2) +#define PM2XXX_NTC_RES_NO_NTC (0x3<<2) + +/* control Reg 9 */ +#define PM2XXX_CH_CC_MODEDROP_EN 1 +#define PM2XXX_CH_CC_MODEDROP_DIS 0 + +#define PM2XXX_CH_CC_REDUCED_CURRENT_100MA (0x0<<1) +#define PM2XXX_CH_CC_REDUCED_CURRENT_200MA (0x1<<1) +#define PM2XXX_CH_CC_REDUCED_CURRENT_400MA (0x2<<1) +#define PM2XXX_CH_CC_REDUCED_CURRENT_IDENT (0x3<<1) + +#define PM2XXX_CHARCHING_INFO_DIS (0<<3) +#define PM2XXX_CHARCHING_INFO_EN (1<<3) + +#define PM2XXX_CH_150MV_DROP_300MV (0<<4) +#define PM2XXX_CH_150MV_DROP_150MV (1<<4) + + +/* charger status register */ +#define PM2XXX_CHG_STATUS_OFF 0x0 +#define PM2XXX_CHG_STATUS_ON 0x1 +#define PM2XXX_CHG_STATUS_FULL 0x2 +#define PM2XXX_CHG_STATUS_ERR 0x3 +#define PM2XXX_CHG_STATUS_WAIT 0x4 +#define PM2XXX_CHG_STATUS_NOBAT 0x5 + +/* Input charger voltage VPWR2 */ +#define PM2XXX_VPWR2_OVV_6_0 0x0 +#define PM2XXX_VPWR2_OVV_6_3 0x1 +#define PM2XXX_VPWR2_OVV_10 0x2 +#define PM2XXX_VPWR2_OVV_NONE 0x3 + +/* Input charger drop VPWR2 */ +#define PM2XXX_VPWR2_HW_OPT_EN (0x1<<4) +#define PM2XXX_VPWR2_HW_OPT_DIS (0x0<<4) + +#define PM2XXX_VPWR2_VALID_EN (0x1<<3) +#define PM2XXX_VPWR2_VALID_DIS (0x0<<3) + +#define PM2XXX_VPWR2_DROP_EN (0x1<<2) +#define PM2XXX_VPWR2_DROP_DIS (0x0<<2) + +/* Input charger voltage VPWR1 */ +#define PM2XXX_VPWR1_OVV_6_0 0x0 +#define PM2XXX_VPWR1_OVV_6_3 0x1 +#define PM2XXX_VPWR1_OVV_10 0x2 +#define PM2XXX_VPWR1_OVV_NONE 0x3 + +/* Input charger drop VPWR1 */ +#define PM2XXX_VPWR1_HW_OPT_EN (0x1<<4) +#define PM2XXX_VPWR1_HW_OPT_DIS (0x0<<4) + +#define PM2XXX_VPWR1_VALID_EN (0x1<<3) +#define PM2XXX_VPWR1_VALID_DIS (0x0<<3) + +#define PM2XXX_VPWR1_DROP_EN (0x1<<2) +#define PM2XXX_VPWR1_DROP_DIS (0x0<<2) + +/* Battery low level comparator control register */ +#define PM2XXX_VBAT_LOW_MONITORING_DIS 0x0 +#define PM2XXX_VBAT_LOW_MONITORING_ENA 0x1 + +/* Battery low level value control register */ +#define PM2XXX_VBAT_LOW_LEVEL_2_3 0x0 +#define PM2XXX_VBAT_LOW_LEVEL_2_4 0x1 +#define PM2XXX_VBAT_LOW_LEVEL_2_5 0x2 +#define PM2XXX_VBAT_LOW_LEVEL_2_6 0x3 +#define PM2XXX_VBAT_LOW_LEVEL_2_7 0x4 +#define PM2XXX_VBAT_LOW_LEVEL_2_8 0x5 +#define PM2XXX_VBAT_LOW_LEVEL_2_9 0x6 +#define PM2XXX_VBAT_LOW_LEVEL_3_0 0x7 +#define PM2XXX_VBAT_LOW_LEVEL_3_1 0x8 +#define PM2XXX_VBAT_LOW_LEVEL_3_2 0x9 +#define PM2XXX_VBAT_LOW_LEVEL_3_3 0xA +#define PM2XXX_VBAT_LOW_LEVEL_3_4 0xB +#define PM2XXX_VBAT_LOW_LEVEL_3_5 0xC +#define PM2XXX_VBAT_LOW_LEVEL_3_6 0xD +#define PM2XXX_VBAT_LOW_LEVEL_3_7 0xE +#define PM2XXX_VBAT_LOW_LEVEL_3_8 0xF +#define PM2XXX_VBAT_LOW_LEVEL_3_9 0x10 +#define PM2XXX_VBAT_LOW_LEVEL_4_0 0x11 +#define PM2XXX_VBAT_LOW_LEVEL_4_1 0x12 +#define PM2XXX_VBAT_LOW_LEVEL_4_2 0x13 + +/* SW CTRL */ +#define PM2XXX_SWCTRL_HW 0x0 +#define PM2XXX_SWCTRL_SW 0x1 + + +/* LED Driver Control */ +#define PM2XXX_LED_CURRENT_MASK 0x0C +#define PM2XXX_LED_CURRENT_2_5MA (0X0<<2) +#define PM2XXX_LED_CURRENT_1MA (0X1<<2) +#define PM2XXX_LED_CURRENT_5MA (0X2<<2) +#define PM2XXX_LED_CURRENT_10MA (0X3<<2) + +#define PM2XXX_LED_SELECT_MASK 0x02 +#define PM2XXX_LED_SELECT_EN (0X0<<1) +#define PM2XXX_LED_SELECT_DIS (0X1<<1) + +#define PM2XXX_ANTI_OVERSHOOT_MASK 0x01 +#define PM2XXX_ANTI_OVERSHOOT_DIS 0X0 +#define PM2XXX_ANTI_OVERSHOOT_EN 0X1 + +enum pm2xxx_reg_int1 { + PM2XXX_INT1_ITVBATDISCONNECT = 0x02, + PM2XXX_INT1_ITVBATLOWR = 0x04, + PM2XXX_INT1_ITVBATLOWF = 0x08, +}; + +enum pm2xxx_mask_reg_int1 { + PM2XXX_INT1_M_ITVBATDISCONNECT = 0x02, + PM2XXX_INT1_M_ITVBATLOWR = 0x04, + PM2XXX_INT1_M_ITVBATLOWF = 0x08, +}; + +enum pm2xxx_source_reg_int1 { + PM2XXX_INT1_S_ITVBATDISCONNECT = 0x02, + PM2XXX_INT1_S_ITVBATLOWR = 0x04, + PM2XXX_INT1_S_ITVBATLOWF = 0x08, +}; + +enum pm2xxx_reg_int2 { + PM2XXX_INT2_ITVPWR2PLUG = 0x01, + PM2XXX_INT2_ITVPWR2UNPLUG = 0x02, + PM2XXX_INT2_ITVPWR1PLUG = 0x04, + PM2XXX_INT2_ITVPWR1UNPLUG = 0x08, +}; + +enum pm2xxx_mask_reg_int2 { + PM2XXX_INT2_M_ITVPWR2PLUG = 0x01, + PM2XXX_INT2_M_ITVPWR2UNPLUG = 0x02, + PM2XXX_INT2_M_ITVPWR1PLUG = 0x04, + PM2XXX_INT2_M_ITVPWR1UNPLUG = 0x08, +}; + +enum pm2xxx_source_reg_int2 { + PM2XXX_INT2_S_ITVPWR2PLUG = 0x03, + PM2XXX_INT2_S_ITVPWR1PLUG = 0x0c, +}; + +enum pm2xxx_reg_int3 { + PM2XXX_INT3_ITCHPRECHARGEWD = 0x01, + PM2XXX_INT3_ITCHCCWD = 0x02, + PM2XXX_INT3_ITCHCVWD = 0x04, + PM2XXX_INT3_ITAUTOTIMEOUTWD = 0x08, +}; + +enum pm2xxx_mask_reg_int3 { + PM2XXX_INT3_M_ITCHPRECHARGEWD = 0x01, + PM2XXX_INT3_M_ITCHCCWD = 0x02, + PM2XXX_INT3_M_ITCHCVWD = 0x04, + PM2XXX_INT3_M_ITAUTOTIMEOUTWD = 0x08, +}; + +enum pm2xxx_source_reg_int3 { + PM2XXX_INT3_S_ITCHPRECHARGEWD = 0x01, + PM2XXX_INT3_S_ITCHCCWD = 0x02, + PM2XXX_INT3_S_ITCHCVWD = 0x04, + PM2XXX_INT3_S_ITAUTOTIMEOUTWD = 0x08, +}; + +enum pm2xxx_reg_int4 { + PM2XXX_INT4_ITBATTEMPCOLD = 0x01, + PM2XXX_INT4_ITBATTEMPHOT = 0x02, + PM2XXX_INT4_ITVPWR2OVV = 0x04, + PM2XXX_INT4_ITVPWR1OVV = 0x08, + PM2XXX_INT4_ITCHARGINGON = 0x10, + PM2XXX_INT4_ITVRESUME = 0x20, + PM2XXX_INT4_ITBATTFULL = 0x40, + PM2XXX_INT4_ITCVPHASE = 0x80, +}; + +enum pm2xxx_mask_reg_int4 { + PM2XXX_INT4_M_ITBATTEMPCOLD = 0x01, + PM2XXX_INT4_M_ITBATTEMPHOT = 0x02, + PM2XXX_INT4_M_ITVPWR2OVV = 0x04, + PM2XXX_INT4_M_ITVPWR1OVV = 0x08, + PM2XXX_INT4_M_ITCHARGINGON = 0x10, + PM2XXX_INT4_M_ITVRESUME = 0x20, + PM2XXX_INT4_M_ITBATTFULL = 0x40, + PM2XXX_INT4_M_ITCVPHASE = 0x80, +}; + +enum pm2xxx_source_reg_int4 { + PM2XXX_INT4_S_ITBATTEMPCOLD = 0x01, + PM2XXX_INT4_S_ITBATTEMPHOT = 0x02, + PM2XXX_INT4_S_ITVPWR2OVV = 0x04, + PM2XXX_INT4_S_ITVPWR1OVV = 0x08, + PM2XXX_INT4_S_ITCHARGINGON = 0x10, + PM2XXX_INT4_S_ITVRESUME = 0x20, + PM2XXX_INT4_S_ITBATTFULL = 0x40, + PM2XXX_INT4_S_ITCVPHASE = 0x80, +}; + +enum pm2xxx_reg_int5 { + PM2XXX_INT5_ITTHERMALSHUTDOWNRISE = 0x01, + PM2XXX_INT5_ITTHERMALSHUTDOWNFALL = 0x02, + PM2XXX_INT5_ITTHERMALWARNINGRISE = 0x04, + PM2XXX_INT5_ITTHERMALWARNINGFALL = 0x08, + PM2XXX_INT5_ITVSYSTEMOVV = 0x10, +}; + +enum pm2xxx_mask_reg_int5 { + PM2XXX_INT5_M_ITTHERMALSHUTDOWNRISE = 0x01, + PM2XXX_INT5_M_ITTHERMALSHUTDOWNFALL = 0x02, + PM2XXX_INT5_M_ITTHERMALWARNINGRISE = 0x04, + PM2XXX_INT5_M_ITTHERMALWARNINGFALL = 0x08, + PM2XXX_INT5_M_ITVSYSTEMOVV = 0x10, +}; + +enum pm2xxx_source_reg_int5 { + PM2XXX_INT5_S_ITTHERMALSHUTDOWNRISE = 0x01, + PM2XXX_INT5_S_ITTHERMALSHUTDOWNFALL = 0x02, + PM2XXX_INT5_S_ITTHERMALWARNINGRISE = 0x04, + PM2XXX_INT5_S_ITTHERMALWARNINGFALL = 0x08, + PM2XXX_INT5_S_ITVSYSTEMOVV = 0x10, +}; + +enum pm2xxx_reg_int6 { + PM2XXX_INT6_ITVPWR2DROP = 0x01, + PM2XXX_INT6_ITVPWR1DROP = 0x02, + PM2XXX_INT6_ITVPWR2VALIDRISE = 0x04, + PM2XXX_INT6_ITVPWR2VALIDFALL = 0x08, + PM2XXX_INT6_ITVPWR1VALIDRISE = 0x10, + PM2XXX_INT6_ITVPWR1VALIDFALL = 0x20, +}; + +enum pm2xxx_mask_reg_int6 { + PM2XXX_INT6_M_ITVPWR2DROP = 0x01, + PM2XXX_INT6_M_ITVPWR1DROP = 0x02, + PM2XXX_INT6_M_ITVPWR2VALIDRISE = 0x04, + PM2XXX_INT6_M_ITVPWR2VALIDFALL = 0x08, + PM2XXX_INT6_M_ITVPWR1VALIDRISE = 0x10, + PM2XXX_INT6_M_ITVPWR1VALIDFALL = 0x20, +}; + +enum pm2xxx_source_reg_int6 { + PM2XXX_INT6_S_ITVPWR2DROP = 0x01, + PM2XXX_INT6_S_ITVPWR1DROP = 0x02, + PM2XXX_INT6_S_ITVPWR2VALIDRISE = 0x04, + PM2XXX_INT6_S_ITVPWR2VALIDFALL = 0x08, + PM2XXX_INT6_S_ITVPWR1VALIDRISE = 0x10, + PM2XXX_INT6_S_ITVPWR1VALIDFALL = 0x20, +}; + +struct pm2xxx_charger_info { + int charger_connected; + int charger_online; + int cv_active; + bool wd_expired; +}; + +struct pm2xxx_charger_event_flags { + bool mainextchnotok; + bool main_thermal_prot; + bool ovv; + bool chgwdexp; +}; + +struct pm2xxx_interrupts { + u8 reg[PM2XXX_NUM_INT_REG]; + int (*handler[PM2XXX_NUM_INT_REG])(void *, int); +}; + +struct pm2xxx_config { + struct i2c_client *pm2xxx_i2c; + struct i2c_device_id *pm2xxx_id; +}; + +struct pm2xxx_irq { + char *name; + irqreturn_t (*isr)(int irq, void *data); +}; + +struct pm2xxx_charger { + struct device *dev; + u8 chip_id; + bool vddadc_en_ac; + struct pm2xxx_config config; + bool ac_conn; + unsigned int gpio_irq; + int vbat; + int old_vbat; + int failure_case; + int failure_input_ovv; + unsigned int lpn_pin; + struct pm2xxx_interrupts *pm2_int; + struct regulator *regu; + struct pm2xxx_bm_data *bat; + struct mutex lock; + struct ab8500 *parent; + struct pm2xxx_charger_info ac; + struct pm2xxx_charger_platform_data *pdata; + struct workqueue_struct *charger_wq; + struct delayed_work check_vbat_work; + struct work_struct ac_work; + struct work_struct check_main_thermal_prot_work; + struct delayed_work check_hw_failure_work; + struct ux500_charger ac_chg; + struct power_supply_desc ac_chg_desc; + struct pm2xxx_charger_event_flags flags; +}; + +#endif /* PM2301_CHARGER_H */ diff --git a/drivers/power/supply/pmu_battery.c b/drivers/power/supply/pmu_battery.c new file mode 100644 index 000000000000..9c8d5253812c --- /dev/null +++ b/drivers/power/supply/pmu_battery.c @@ -0,0 +1,226 @@ +/* + * Battery class driver for Apple PMU + * + * Copyright © 2006 David Woodhouse + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include + +static struct pmu_battery_dev { + struct power_supply *bat; + struct power_supply_desc bat_desc; + struct pmu_battery_info *pbi; + char name[16]; + int propval; +} *pbats[PMU_MAX_BATTERIES]; + +#define to_pmu_battery_dev(x) power_supply_get_drvdata(x) + +/********************************************************************* + * Power + *********************************************************************/ + +static int pmu_get_ac_prop(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + val->intval = (!!(pmu_power_flags & PMU_PWR_AC_PRESENT)) || + (pmu_battery_count == 0); + break; + default: + return -EINVAL; + } + + return 0; +} + +static enum power_supply_property pmu_ac_props[] = { + POWER_SUPPLY_PROP_ONLINE, +}; + +static const struct power_supply_desc pmu_ac_desc = { + .name = "pmu-ac", + .type = POWER_SUPPLY_TYPE_MAINS, + .properties = pmu_ac_props, + .num_properties = ARRAY_SIZE(pmu_ac_props), + .get_property = pmu_get_ac_prop, +}; + +static struct power_supply *pmu_ac; + +/********************************************************************* + * Battery properties + *********************************************************************/ + +static char *pmu_batt_types[] = { + "Smart", "Comet", "Hooper", "Unknown" +}; + +static char *pmu_bat_get_model_name(struct pmu_battery_info *pbi) +{ + switch (pbi->flags & PMU_BATT_TYPE_MASK) { + case PMU_BATT_TYPE_SMART: + return pmu_batt_types[0]; + case PMU_BATT_TYPE_COMET: + return pmu_batt_types[1]; + case PMU_BATT_TYPE_HOOPER: + return pmu_batt_types[2]; + default: break; + } + return pmu_batt_types[3]; +} + +static int pmu_bat_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct pmu_battery_dev *pbat = to_pmu_battery_dev(psy); + struct pmu_battery_info *pbi = pbat->pbi; + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + if (pbi->flags & PMU_BATT_CHARGING) + val->intval = POWER_SUPPLY_STATUS_CHARGING; + else if (pmu_power_flags & PMU_PWR_AC_PRESENT) + val->intval = POWER_SUPPLY_STATUS_FULL; + else + val->intval = POWER_SUPPLY_STATUS_DISCHARGING; + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = !!(pbi->flags & PMU_BATT_PRESENT); + break; + case POWER_SUPPLY_PROP_MODEL_NAME: + val->strval = pmu_bat_get_model_name(pbi); + break; + case POWER_SUPPLY_PROP_ENERGY_AVG: + val->intval = pbi->charge * 1000; /* mWh -> µWh */ + break; + case POWER_SUPPLY_PROP_ENERGY_FULL: + val->intval = pbi->max_charge * 1000; /* mWh -> µWh */ + break; + case POWER_SUPPLY_PROP_CURRENT_AVG: + val->intval = pbi->amperage * 1000; /* mA -> µA */ + break; + case POWER_SUPPLY_PROP_VOLTAGE_AVG: + val->intval = pbi->voltage * 1000; /* mV -> µV */ + break; + case POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG: + val->intval = pbi->time_remaining; + break; + default: + return -EINVAL; + } + + return 0; +} + +static enum power_supply_property pmu_bat_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_MODEL_NAME, + POWER_SUPPLY_PROP_ENERGY_AVG, + POWER_SUPPLY_PROP_ENERGY_FULL, + POWER_SUPPLY_PROP_CURRENT_AVG, + POWER_SUPPLY_PROP_VOLTAGE_AVG, + POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG, +}; + +/********************************************************************* + * Initialisation + *********************************************************************/ + +static struct platform_device *bat_pdev; + +static int __init pmu_bat_init(void) +{ + int ret = 0; + int i; + + bat_pdev = platform_device_register_simple("pmu-battery", + 0, NULL, 0); + if (IS_ERR(bat_pdev)) { + ret = PTR_ERR(bat_pdev); + goto pdev_register_failed; + } + + pmu_ac = power_supply_register(&bat_pdev->dev, &pmu_ac_desc, NULL); + if (IS_ERR(pmu_ac)) { + ret = PTR_ERR(pmu_ac); + goto ac_register_failed; + } + + for (i = 0; i < pmu_battery_count; i++) { + struct power_supply_config psy_cfg = {}; + struct pmu_battery_dev *pbat = kzalloc(sizeof(*pbat), + GFP_KERNEL); + if (!pbat) + break; + + sprintf(pbat->name, "PMU_battery_%d", i); + pbat->bat_desc.name = pbat->name; + pbat->bat_desc.properties = pmu_bat_props; + pbat->bat_desc.num_properties = ARRAY_SIZE(pmu_bat_props); + pbat->bat_desc.get_property = pmu_bat_get_property; + pbat->pbi = &pmu_batteries[i]; + psy_cfg.drv_data = pbat; + + pbat->bat = power_supply_register(&bat_pdev->dev, + &pbat->bat_desc, + &psy_cfg); + if (IS_ERR(pbat->bat)) { + ret = PTR_ERR(pbat->bat); + kfree(pbat); + goto battery_register_failed; + } + pbats[i] = pbat; + } + + goto success; + +battery_register_failed: + while (i--) { + if (!pbats[i]) + continue; + power_supply_unregister(pbats[i]->bat); + kfree(pbats[i]); + } + power_supply_unregister(pmu_ac); +ac_register_failed: + platform_device_unregister(bat_pdev); +pdev_register_failed: +success: + return ret; +} + +static void __exit pmu_bat_exit(void) +{ + int i; + + for (i = 0; i < PMU_MAX_BATTERIES; i++) { + if (!pbats[i]) + continue; + power_supply_unregister(pbats[i]->bat); + kfree(pbats[i]); + } + power_supply_unregister(pmu_ac); + platform_device_unregister(bat_pdev); +} + +module_init(pmu_bat_init); +module_exit(pmu_bat_exit); + +MODULE_AUTHOR("David Woodhouse "); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("PMU battery driver"); diff --git a/drivers/power/supply/power_supply.h b/drivers/power/supply/power_supply.h new file mode 100644 index 000000000000..cc439fd89d8d --- /dev/null +++ b/drivers/power/supply/power_supply.h @@ -0,0 +1,42 @@ +/* + * Functions private to power supply class + * + * Copyright © 2007 Anton Vorontsov + * Copyright © 2004 Szabolcs Gyurko + * Copyright © 2003 Ian Molton + * + * Modified: 2004, Oct Szabolcs Gyurko + * + * You may use this code as per GPL version 2 + */ + +struct device; +struct device_type; +struct power_supply; + +#ifdef CONFIG_SYSFS + +extern void power_supply_init_attrs(struct device_type *dev_type); +extern int power_supply_uevent(struct device *dev, struct kobj_uevent_env *env); + +#else + +static inline void power_supply_init_attrs(struct device_type *dev_type) {} +#define power_supply_uevent NULL + +#endif /* CONFIG_SYSFS */ + +#ifdef CONFIG_LEDS_TRIGGERS + +extern void power_supply_update_leds(struct power_supply *psy); +extern int power_supply_create_triggers(struct power_supply *psy); +extern void power_supply_remove_triggers(struct power_supply *psy); + +#else + +static inline void power_supply_update_leds(struct power_supply *psy) {} +static inline int power_supply_create_triggers(struct power_supply *psy) +{ return 0; } +static inline void power_supply_remove_triggers(struct power_supply *psy) {} + +#endif /* CONFIG_LEDS_TRIGGERS */ diff --git a/drivers/power/supply/power_supply_core.c b/drivers/power/supply/power_supply_core.c new file mode 100644 index 000000000000..a74d8ca383a1 --- /dev/null +++ b/drivers/power/supply/power_supply_core.c @@ -0,0 +1,989 @@ +/* + * Universal power supply monitor class + * + * Copyright © 2007 Anton Vorontsov + * Copyright © 2004 Szabolcs Gyurko + * Copyright © 2003 Ian Molton + * + * Modified: 2004, Oct Szabolcs Gyurko + * + * You may use this code as per GPL version 2 + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "power_supply.h" + +/* exported for the APM Power driver, APM emulation */ +struct class *power_supply_class; +EXPORT_SYMBOL_GPL(power_supply_class); + +ATOMIC_NOTIFIER_HEAD(power_supply_notifier); +EXPORT_SYMBOL_GPL(power_supply_notifier); + +static struct device_type power_supply_dev_type; + +#define POWER_SUPPLY_DEFERRED_REGISTER_TIME msecs_to_jiffies(10) + +static bool __power_supply_is_supplied_by(struct power_supply *supplier, + struct power_supply *supply) +{ + int i; + + if (!supply->supplied_from && !supplier->supplied_to) + return false; + + /* Support both supplied_to and supplied_from modes */ + if (supply->supplied_from) { + if (!supplier->desc->name) + return false; + for (i = 0; i < supply->num_supplies; i++) + if (!strcmp(supplier->desc->name, supply->supplied_from[i])) + return true; + } else { + if (!supply->desc->name) + return false; + for (i = 0; i < supplier->num_supplicants; i++) + if (!strcmp(supplier->supplied_to[i], supply->desc->name)) + return true; + } + + return false; +} + +static int __power_supply_changed_work(struct device *dev, void *data) +{ + struct power_supply *psy = data; + struct power_supply *pst = dev_get_drvdata(dev); + + if (__power_supply_is_supplied_by(psy, pst)) { + if (pst->desc->external_power_changed) + pst->desc->external_power_changed(pst); + } + + return 0; +} + +static void power_supply_changed_work(struct work_struct *work) +{ + unsigned long flags; + struct power_supply *psy = container_of(work, struct power_supply, + changed_work); + + dev_dbg(&psy->dev, "%s\n", __func__); + + spin_lock_irqsave(&psy->changed_lock, flags); + /* + * Check 'changed' here to avoid issues due to race between + * power_supply_changed() and this routine. In worst case + * power_supply_changed() can be called again just before we take above + * lock. During the first call of this routine we will mark 'changed' as + * false and it will stay false for the next call as well. + */ + if (likely(psy->changed)) { + psy->changed = false; + spin_unlock_irqrestore(&psy->changed_lock, flags); + class_for_each_device(power_supply_class, NULL, psy, + __power_supply_changed_work); + power_supply_update_leds(psy); + atomic_notifier_call_chain(&power_supply_notifier, + PSY_EVENT_PROP_CHANGED, psy); + kobject_uevent(&psy->dev.kobj, KOBJ_CHANGE); + spin_lock_irqsave(&psy->changed_lock, flags); + } + + /* + * Hold the wakeup_source until all events are processed. + * power_supply_changed() might have called again and have set 'changed' + * to true. + */ + if (likely(!psy->changed)) + pm_relax(&psy->dev); + spin_unlock_irqrestore(&psy->changed_lock, flags); +} + +void power_supply_changed(struct power_supply *psy) +{ + unsigned long flags; + + dev_dbg(&psy->dev, "%s\n", __func__); + + spin_lock_irqsave(&psy->changed_lock, flags); + psy->changed = true; + pm_stay_awake(&psy->dev); + spin_unlock_irqrestore(&psy->changed_lock, flags); + schedule_work(&psy->changed_work); +} +EXPORT_SYMBOL_GPL(power_supply_changed); + +/* + * Notify that power supply was registered after parent finished the probing. + * + * Often power supply is registered from driver's probe function. However + * calling power_supply_changed() directly from power_supply_register() + * would lead to execution of get_property() function provided by the driver + * too early - before the probe ends. + * + * Avoid that by waiting on parent's mutex. + */ +static void power_supply_deferred_register_work(struct work_struct *work) +{ + struct power_supply *psy = container_of(work, struct power_supply, + deferred_register_work.work); + + if (psy->dev.parent) + mutex_lock(&psy->dev.parent->mutex); + + power_supply_changed(psy); + + if (psy->dev.parent) + mutex_unlock(&psy->dev.parent->mutex); +} + +#ifdef CONFIG_OF +#include + +static int __power_supply_populate_supplied_from(struct device *dev, + void *data) +{ + struct power_supply *psy = data; + struct power_supply *epsy = dev_get_drvdata(dev); + struct device_node *np; + int i = 0; + + do { + np = of_parse_phandle(psy->of_node, "power-supplies", i++); + if (!np) + break; + + if (np == epsy->of_node) { + dev_info(&psy->dev, "%s: Found supply : %s\n", + psy->desc->name, epsy->desc->name); + psy->supplied_from[i-1] = (char *)epsy->desc->name; + psy->num_supplies++; + of_node_put(np); + break; + } + of_node_put(np); + } while (np); + + return 0; +} + +static int power_supply_populate_supplied_from(struct power_supply *psy) +{ + int error; + + error = class_for_each_device(power_supply_class, NULL, psy, + __power_supply_populate_supplied_from); + + dev_dbg(&psy->dev, "%s %d\n", __func__, error); + + return error; +} + +static int __power_supply_find_supply_from_node(struct device *dev, + void *data) +{ + struct device_node *np = data; + struct power_supply *epsy = dev_get_drvdata(dev); + + /* returning non-zero breaks out of class_for_each_device loop */ + if (epsy->of_node == np) + return 1; + + return 0; +} + +static int power_supply_find_supply_from_node(struct device_node *supply_node) +{ + int error; + + /* + * class_for_each_device() either returns its own errors or values + * returned by __power_supply_find_supply_from_node(). + * + * __power_supply_find_supply_from_node() will return 0 (no match) + * or 1 (match). + * + * We return 0 if class_for_each_device() returned 1, -EPROBE_DEFER if + * it returned 0, or error as returned by it. + */ + error = class_for_each_device(power_supply_class, NULL, supply_node, + __power_supply_find_supply_from_node); + + return error ? (error == 1 ? 0 : error) : -EPROBE_DEFER; +} + +static int power_supply_check_supplies(struct power_supply *psy) +{ + struct device_node *np; + int cnt = 0; + + /* If there is already a list honor it */ + if (psy->supplied_from && psy->num_supplies > 0) + return 0; + + /* No device node found, nothing to do */ + if (!psy->of_node) + return 0; + + do { + int ret; + + np = of_parse_phandle(psy->of_node, "power-supplies", cnt++); + if (!np) + break; + + ret = power_supply_find_supply_from_node(np); + of_node_put(np); + + if (ret) { + dev_dbg(&psy->dev, "Failed to find supply!\n"); + return ret; + } + } while (np); + + /* Missing valid "power-supplies" entries */ + if (cnt == 1) + return 0; + + /* All supplies found, allocate char ** array for filling */ + psy->supplied_from = devm_kzalloc(&psy->dev, sizeof(psy->supplied_from), + GFP_KERNEL); + if (!psy->supplied_from) { + dev_err(&psy->dev, "Couldn't allocate memory for supply list\n"); + return -ENOMEM; + } + + *psy->supplied_from = devm_kzalloc(&psy->dev, + sizeof(char *) * (cnt - 1), + GFP_KERNEL); + if (!*psy->supplied_from) { + dev_err(&psy->dev, "Couldn't allocate memory for supply list\n"); + return -ENOMEM; + } + + return power_supply_populate_supplied_from(psy); +} +#else +static inline int power_supply_check_supplies(struct power_supply *psy) +{ + return 0; +} +#endif + +static int __power_supply_am_i_supplied(struct device *dev, void *data) +{ + union power_supply_propval ret = {0,}; + struct power_supply *psy = data; + struct power_supply *epsy = dev_get_drvdata(dev); + + if (__power_supply_is_supplied_by(epsy, psy)) + if (!epsy->desc->get_property(epsy, POWER_SUPPLY_PROP_ONLINE, + &ret)) + return ret.intval; + + return 0; +} + +int power_supply_am_i_supplied(struct power_supply *psy) +{ + int error; + + error = class_for_each_device(power_supply_class, NULL, psy, + __power_supply_am_i_supplied); + + dev_dbg(&psy->dev, "%s %d\n", __func__, error); + + return error; +} +EXPORT_SYMBOL_GPL(power_supply_am_i_supplied); + +static int __power_supply_is_system_supplied(struct device *dev, void *data) +{ + union power_supply_propval ret = {0,}; + struct power_supply *psy = dev_get_drvdata(dev); + unsigned int *count = data; + + (*count)++; + if (psy->desc->type != POWER_SUPPLY_TYPE_BATTERY) + if (!psy->desc->get_property(psy, POWER_SUPPLY_PROP_ONLINE, + &ret)) + return ret.intval; + + return 0; +} + +int power_supply_is_system_supplied(void) +{ + int error; + unsigned int count = 0; + + error = class_for_each_device(power_supply_class, NULL, &count, + __power_supply_is_system_supplied); + + /* + * If no power class device was found at all, most probably we are + * running on a desktop system, so assume we are on mains power. + */ + if (count == 0) + return 1; + + return error; +} +EXPORT_SYMBOL_GPL(power_supply_is_system_supplied); + +int power_supply_set_battery_charged(struct power_supply *psy) +{ + if (atomic_read(&psy->use_cnt) >= 0 && + psy->desc->type == POWER_SUPPLY_TYPE_BATTERY && + psy->desc->set_charged) { + psy->desc->set_charged(psy); + return 0; + } + + return -EINVAL; +} +EXPORT_SYMBOL_GPL(power_supply_set_battery_charged); + +static int power_supply_match_device_by_name(struct device *dev, const void *data) +{ + const char *name = data; + struct power_supply *psy = dev_get_drvdata(dev); + + return strcmp(psy->desc->name, name) == 0; +} + +/** + * power_supply_get_by_name() - Search for a power supply and returns its ref + * @name: Power supply name to fetch + * + * If power supply was found, it increases reference count for the + * internal power supply's device. The user should power_supply_put() + * after usage. + * + * Return: On success returns a reference to a power supply with + * matching name equals to @name, a NULL otherwise. + */ +struct power_supply *power_supply_get_by_name(const char *name) +{ + struct power_supply *psy = NULL; + struct device *dev = class_find_device(power_supply_class, NULL, name, + power_supply_match_device_by_name); + + if (dev) { + psy = dev_get_drvdata(dev); + atomic_inc(&psy->use_cnt); + } + + return psy; +} +EXPORT_SYMBOL_GPL(power_supply_get_by_name); + +/** + * power_supply_put() - Drop reference obtained with power_supply_get_by_name + * @psy: Reference to put + * + * The reference to power supply should be put before unregistering + * the power supply. + */ +void power_supply_put(struct power_supply *psy) +{ + might_sleep(); + + atomic_dec(&psy->use_cnt); + put_device(&psy->dev); +} +EXPORT_SYMBOL_GPL(power_supply_put); + +#ifdef CONFIG_OF +static int power_supply_match_device_node(struct device *dev, const void *data) +{ + return dev->parent && dev->parent->of_node == data; +} + +/** + * power_supply_get_by_phandle() - Search for a power supply and returns its ref + * @np: Pointer to device node holding phandle property + * @phandle_name: Name of property holding a power supply name + * + * If power supply was found, it increases reference count for the + * internal power supply's device. The user should power_supply_put() + * after usage. + * + * Return: On success returns a reference to a power supply with + * matching name equals to value under @property, NULL or ERR_PTR otherwise. + */ +struct power_supply *power_supply_get_by_phandle(struct device_node *np, + const char *property) +{ + struct device_node *power_supply_np; + struct power_supply *psy = NULL; + struct device *dev; + + power_supply_np = of_parse_phandle(np, property, 0); + if (!power_supply_np) + return ERR_PTR(-ENODEV); + + dev = class_find_device(power_supply_class, NULL, power_supply_np, + power_supply_match_device_node); + + of_node_put(power_supply_np); + + if (dev) { + psy = dev_get_drvdata(dev); + atomic_inc(&psy->use_cnt); + } + + return psy; +} +EXPORT_SYMBOL_GPL(power_supply_get_by_phandle); + +static void devm_power_supply_put(struct device *dev, void *res) +{ + struct power_supply **psy = res; + + power_supply_put(*psy); +} + +/** + * devm_power_supply_get_by_phandle() - Resource managed version of + * power_supply_get_by_phandle() + * @dev: Pointer to device holding phandle property + * @phandle_name: Name of property holding a power supply phandle + * + * Return: On success returns a reference to a power supply with + * matching name equals to value under @property, NULL or ERR_PTR otherwise. + */ +struct power_supply *devm_power_supply_get_by_phandle(struct device *dev, + const char *property) +{ + struct power_supply **ptr, *psy; + + if (!dev->of_node) + return ERR_PTR(-ENODEV); + + ptr = devres_alloc(devm_power_supply_put, sizeof(*ptr), GFP_KERNEL); + if (!ptr) + return ERR_PTR(-ENOMEM); + + psy = power_supply_get_by_phandle(dev->of_node, property); + if (IS_ERR_OR_NULL(psy)) { + devres_free(ptr); + } else { + *ptr = psy; + devres_add(dev, ptr); + } + return psy; +} +EXPORT_SYMBOL_GPL(devm_power_supply_get_by_phandle); +#endif /* CONFIG_OF */ + +int power_supply_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + if (atomic_read(&psy->use_cnt) <= 0) { + if (!psy->initialized) + return -EAGAIN; + return -ENODEV; + } + + return psy->desc->get_property(psy, psp, val); +} +EXPORT_SYMBOL_GPL(power_supply_get_property); + +int power_supply_set_property(struct power_supply *psy, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + if (atomic_read(&psy->use_cnt) <= 0 || !psy->desc->set_property) + return -ENODEV; + + return psy->desc->set_property(psy, psp, val); +} +EXPORT_SYMBOL_GPL(power_supply_set_property); + +int power_supply_property_is_writeable(struct power_supply *psy, + enum power_supply_property psp) +{ + if (atomic_read(&psy->use_cnt) <= 0 || + !psy->desc->property_is_writeable) + return -ENODEV; + + return psy->desc->property_is_writeable(psy, psp); +} +EXPORT_SYMBOL_GPL(power_supply_property_is_writeable); + +void power_supply_external_power_changed(struct power_supply *psy) +{ + if (atomic_read(&psy->use_cnt) <= 0 || + !psy->desc->external_power_changed) + return; + + psy->desc->external_power_changed(psy); +} +EXPORT_SYMBOL_GPL(power_supply_external_power_changed); + +int power_supply_powers(struct power_supply *psy, struct device *dev) +{ + return sysfs_create_link(&psy->dev.kobj, &dev->kobj, "powers"); +} +EXPORT_SYMBOL_GPL(power_supply_powers); + +static void power_supply_dev_release(struct device *dev) +{ + struct power_supply *psy = container_of(dev, struct power_supply, dev); + pr_debug("device: '%s': %s\n", dev_name(dev), __func__); + kfree(psy); +} + +int power_supply_reg_notifier(struct notifier_block *nb) +{ + return atomic_notifier_chain_register(&power_supply_notifier, nb); +} +EXPORT_SYMBOL_GPL(power_supply_reg_notifier); + +void power_supply_unreg_notifier(struct notifier_block *nb) +{ + atomic_notifier_chain_unregister(&power_supply_notifier, nb); +} +EXPORT_SYMBOL_GPL(power_supply_unreg_notifier); + +#ifdef CONFIG_THERMAL +static int power_supply_read_temp(struct thermal_zone_device *tzd, + int *temp) +{ + struct power_supply *psy; + union power_supply_propval val; + int ret; + + WARN_ON(tzd == NULL); + psy = tzd->devdata; + ret = power_supply_get_property(psy, POWER_SUPPLY_PROP_TEMP, &val); + if (ret) + return ret; + + /* Convert tenths of degree Celsius to milli degree Celsius. */ + *temp = val.intval * 100; + + return ret; +} + +static struct thermal_zone_device_ops psy_tzd_ops = { + .get_temp = power_supply_read_temp, +}; + +static int psy_register_thermal(struct power_supply *psy) +{ + int i; + + if (psy->desc->no_thermal) + return 0; + + /* Register battery zone device psy reports temperature */ + for (i = 0; i < psy->desc->num_properties; i++) { + if (psy->desc->properties[i] == POWER_SUPPLY_PROP_TEMP) { + psy->tzd = thermal_zone_device_register(psy->desc->name, + 0, 0, psy, &psy_tzd_ops, NULL, 0, 0); + return PTR_ERR_OR_ZERO(psy->tzd); + } + } + return 0; +} + +static void psy_unregister_thermal(struct power_supply *psy) +{ + if (IS_ERR_OR_NULL(psy->tzd)) + return; + thermal_zone_device_unregister(psy->tzd); +} + +/* thermal cooling device callbacks */ +static int ps_get_max_charge_cntl_limit(struct thermal_cooling_device *tcd, + unsigned long *state) +{ + struct power_supply *psy; + union power_supply_propval val; + int ret; + + psy = tcd->devdata; + ret = power_supply_get_property(psy, + POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT_MAX, &val); + if (ret) + return ret; + + *state = val.intval; + + return ret; +} + +static int ps_get_cur_chrage_cntl_limit(struct thermal_cooling_device *tcd, + unsigned long *state) +{ + struct power_supply *psy; + union power_supply_propval val; + int ret; + + psy = tcd->devdata; + ret = power_supply_get_property(psy, + POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT, &val); + if (ret) + return ret; + + *state = val.intval; + + return ret; +} + +static int ps_set_cur_charge_cntl_limit(struct thermal_cooling_device *tcd, + unsigned long state) +{ + struct power_supply *psy; + union power_supply_propval val; + int ret; + + psy = tcd->devdata; + val.intval = state; + ret = psy->desc->set_property(psy, + POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT, &val); + + return ret; +} + +static struct thermal_cooling_device_ops psy_tcd_ops = { + .get_max_state = ps_get_max_charge_cntl_limit, + .get_cur_state = ps_get_cur_chrage_cntl_limit, + .set_cur_state = ps_set_cur_charge_cntl_limit, +}; + +static int psy_register_cooler(struct power_supply *psy) +{ + int i; + + /* Register for cooling device if psy can control charging */ + for (i = 0; i < psy->desc->num_properties; i++) { + if (psy->desc->properties[i] == + POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT) { + psy->tcd = thermal_cooling_device_register( + (char *)psy->desc->name, + psy, &psy_tcd_ops); + return PTR_ERR_OR_ZERO(psy->tcd); + } + } + return 0; +} + +static void psy_unregister_cooler(struct power_supply *psy) +{ + if (IS_ERR_OR_NULL(psy->tcd)) + return; + thermal_cooling_device_unregister(psy->tcd); +} +#else +static int psy_register_thermal(struct power_supply *psy) +{ + return 0; +} + +static void psy_unregister_thermal(struct power_supply *psy) +{ +} + +static int psy_register_cooler(struct power_supply *psy) +{ + return 0; +} + +static void psy_unregister_cooler(struct power_supply *psy) +{ +} +#endif + +static struct power_supply *__must_check +__power_supply_register(struct device *parent, + const struct power_supply_desc *desc, + const struct power_supply_config *cfg, + bool ws) +{ + struct device *dev; + struct power_supply *psy; + int rc; + + if (!parent) + pr_warn("%s: Expected proper parent device for '%s'\n", + __func__, desc->name); + + psy = kzalloc(sizeof(*psy), GFP_KERNEL); + if (!psy) + return ERR_PTR(-ENOMEM); + + dev = &psy->dev; + + device_initialize(dev); + + dev->class = power_supply_class; + dev->type = &power_supply_dev_type; + dev->parent = parent; + dev->release = power_supply_dev_release; + dev_set_drvdata(dev, psy); + psy->desc = desc; + if (cfg) { + psy->drv_data = cfg->drv_data; + psy->of_node = cfg->of_node; + psy->supplied_to = cfg->supplied_to; + psy->num_supplicants = cfg->num_supplicants; + } + + rc = dev_set_name(dev, "%s", desc->name); + if (rc) + goto dev_set_name_failed; + + INIT_WORK(&psy->changed_work, power_supply_changed_work); + INIT_DELAYED_WORK(&psy->deferred_register_work, + power_supply_deferred_register_work); + + rc = power_supply_check_supplies(psy); + if (rc) { + dev_info(dev, "Not all required supplies found, defer probe\n"); + goto check_supplies_failed; + } + + spin_lock_init(&psy->changed_lock); + rc = device_init_wakeup(dev, ws); + if (rc) + goto wakeup_init_failed; + + rc = device_add(dev); + if (rc) + goto device_add_failed; + + rc = psy_register_thermal(psy); + if (rc) + goto register_thermal_failed; + + rc = psy_register_cooler(psy); + if (rc) + goto register_cooler_failed; + + rc = power_supply_create_triggers(psy); + if (rc) + goto create_triggers_failed; + + /* + * Update use_cnt after any uevents (most notably from device_add()). + * We are here still during driver's probe but + * the power_supply_uevent() calls back driver's get_property + * method so: + * 1. Driver did not assigned the returned struct power_supply, + * 2. Driver could not finish initialization (anything in its probe + * after calling power_supply_register()). + */ + atomic_inc(&psy->use_cnt); + psy->initialized = true; + + queue_delayed_work(system_power_efficient_wq, + &psy->deferred_register_work, + POWER_SUPPLY_DEFERRED_REGISTER_TIME); + + return psy; + +create_triggers_failed: + psy_unregister_cooler(psy); +register_cooler_failed: + psy_unregister_thermal(psy); +register_thermal_failed: + device_del(dev); +device_add_failed: +wakeup_init_failed: +check_supplies_failed: +dev_set_name_failed: + put_device(dev); + return ERR_PTR(rc); +} + +/** + * power_supply_register() - Register new power supply + * @parent: Device to be a parent of power supply's device, usually + * the device which probe function calls this + * @desc: Description of power supply, must be valid through whole + * lifetime of this power supply + * @cfg: Run-time specific configuration accessed during registering, + * may be NULL + * + * Return: A pointer to newly allocated power_supply on success + * or ERR_PTR otherwise. + * Use power_supply_unregister() on returned power_supply pointer to release + * resources. + */ +struct power_supply *__must_check power_supply_register(struct device *parent, + const struct power_supply_desc *desc, + const struct power_supply_config *cfg) +{ + return __power_supply_register(parent, desc, cfg, true); +} +EXPORT_SYMBOL_GPL(power_supply_register); + +/** + * power_supply_register_no_ws() - Register new non-waking-source power supply + * @parent: Device to be a parent of power supply's device, usually + * the device which probe function calls this + * @desc: Description of power supply, must be valid through whole + * lifetime of this power supply + * @cfg: Run-time specific configuration accessed during registering, + * may be NULL + * + * Return: A pointer to newly allocated power_supply on success + * or ERR_PTR otherwise. + * Use power_supply_unregister() on returned power_supply pointer to release + * resources. + */ +struct power_supply *__must_check +power_supply_register_no_ws(struct device *parent, + const struct power_supply_desc *desc, + const struct power_supply_config *cfg) +{ + return __power_supply_register(parent, desc, cfg, false); +} +EXPORT_SYMBOL_GPL(power_supply_register_no_ws); + +static void devm_power_supply_release(struct device *dev, void *res) +{ + struct power_supply **psy = res; + + power_supply_unregister(*psy); +} + +/** + * devm_power_supply_register() - Register managed power supply + * @parent: Device to be a parent of power supply's device, usually + * the device which probe function calls this + * @desc: Description of power supply, must be valid through whole + * lifetime of this power supply + * @cfg: Run-time specific configuration accessed during registering, + * may be NULL + * + * Return: A pointer to newly allocated power_supply on success + * or ERR_PTR otherwise. + * The returned power_supply pointer will be automatically unregistered + * on driver detach. + */ +struct power_supply *__must_check +devm_power_supply_register(struct device *parent, + const struct power_supply_desc *desc, + const struct power_supply_config *cfg) +{ + struct power_supply **ptr, *psy; + + ptr = devres_alloc(devm_power_supply_release, sizeof(*ptr), GFP_KERNEL); + + if (!ptr) + return ERR_PTR(-ENOMEM); + psy = __power_supply_register(parent, desc, cfg, true); + if (IS_ERR(psy)) { + devres_free(ptr); + } else { + *ptr = psy; + devres_add(parent, ptr); + } + return psy; +} +EXPORT_SYMBOL_GPL(devm_power_supply_register); + +/** + * devm_power_supply_register_no_ws() - Register managed non-waking-source power supply + * @parent: Device to be a parent of power supply's device, usually + * the device which probe function calls this + * @desc: Description of power supply, must be valid through whole + * lifetime of this power supply + * @cfg: Run-time specific configuration accessed during registering, + * may be NULL + * + * Return: A pointer to newly allocated power_supply on success + * or ERR_PTR otherwise. + * The returned power_supply pointer will be automatically unregistered + * on driver detach. + */ +struct power_supply *__must_check +devm_power_supply_register_no_ws(struct device *parent, + const struct power_supply_desc *desc, + const struct power_supply_config *cfg) +{ + struct power_supply **ptr, *psy; + + ptr = devres_alloc(devm_power_supply_release, sizeof(*ptr), GFP_KERNEL); + + if (!ptr) + return ERR_PTR(-ENOMEM); + psy = __power_supply_register(parent, desc, cfg, false); + if (IS_ERR(psy)) { + devres_free(ptr); + } else { + *ptr = psy; + devres_add(parent, ptr); + } + return psy; +} +EXPORT_SYMBOL_GPL(devm_power_supply_register_no_ws); + +/** + * power_supply_unregister() - Remove this power supply from system + * @psy: Pointer to power supply to unregister + * + * Remove this power supply from the system. The resources of power supply + * will be freed here or on last power_supply_put() call. + */ +void power_supply_unregister(struct power_supply *psy) +{ + WARN_ON(atomic_dec_return(&psy->use_cnt)); + cancel_work_sync(&psy->changed_work); + cancel_delayed_work_sync(&psy->deferred_register_work); + sysfs_remove_link(&psy->dev.kobj, "powers"); + power_supply_remove_triggers(psy); + psy_unregister_cooler(psy); + psy_unregister_thermal(psy); + device_init_wakeup(&psy->dev, false); + device_unregister(&psy->dev); +} +EXPORT_SYMBOL_GPL(power_supply_unregister); + +void *power_supply_get_drvdata(struct power_supply *psy) +{ + return psy->drv_data; +} +EXPORT_SYMBOL_GPL(power_supply_get_drvdata); + +static int __init power_supply_class_init(void) +{ + power_supply_class = class_create(THIS_MODULE, "power_supply"); + + if (IS_ERR(power_supply_class)) + return PTR_ERR(power_supply_class); + + power_supply_class->dev_uevent = power_supply_uevent; + power_supply_init_attrs(&power_supply_dev_type); + + return 0; +} + +static void __exit power_supply_class_exit(void) +{ + class_destroy(power_supply_class); +} + +subsys_initcall(power_supply_class_init); +module_exit(power_supply_class_exit); + +MODULE_DESCRIPTION("Universal power supply monitor class"); +MODULE_AUTHOR("Ian Molton , " + "Szabolcs Gyurko, " + "Anton Vorontsov "); +MODULE_LICENSE("GPL"); diff --git a/drivers/power/supply/power_supply_leds.c b/drivers/power/supply/power_supply_leds.c new file mode 100644 index 000000000000..2277ad9c2f68 --- /dev/null +++ b/drivers/power/supply/power_supply_leds.c @@ -0,0 +1,170 @@ +/* + * LEDs triggers for power supply class + * + * Copyright © 2007 Anton Vorontsov + * Copyright © 2004 Szabolcs Gyurko + * Copyright © 2003 Ian Molton + * + * Modified: 2004, Oct Szabolcs Gyurko + * + * You may use this code as per GPL version 2 + */ + +#include +#include +#include +#include + +#include "power_supply.h" + +/* Battery specific LEDs triggers. */ + +static void power_supply_update_bat_leds(struct power_supply *psy) +{ + union power_supply_propval status; + unsigned long delay_on = 0; + unsigned long delay_off = 0; + + if (power_supply_get_property(psy, POWER_SUPPLY_PROP_STATUS, &status)) + return; + + dev_dbg(&psy->dev, "%s %d\n", __func__, status.intval); + + switch (status.intval) { + case POWER_SUPPLY_STATUS_FULL: + led_trigger_event(psy->charging_full_trig, LED_FULL); + led_trigger_event(psy->charging_trig, LED_OFF); + led_trigger_event(psy->full_trig, LED_FULL); + led_trigger_event(psy->charging_blink_full_solid_trig, + LED_FULL); + break; + case POWER_SUPPLY_STATUS_CHARGING: + led_trigger_event(psy->charging_full_trig, LED_FULL); + led_trigger_event(psy->charging_trig, LED_FULL); + led_trigger_event(psy->full_trig, LED_OFF); + led_trigger_blink(psy->charging_blink_full_solid_trig, + &delay_on, &delay_off); + break; + default: + led_trigger_event(psy->charging_full_trig, LED_OFF); + led_trigger_event(psy->charging_trig, LED_OFF); + led_trigger_event(psy->full_trig, LED_OFF); + led_trigger_event(psy->charging_blink_full_solid_trig, + LED_OFF); + break; + } +} + +static int power_supply_create_bat_triggers(struct power_supply *psy) +{ + psy->charging_full_trig_name = kasprintf(GFP_KERNEL, + "%s-charging-or-full", psy->desc->name); + if (!psy->charging_full_trig_name) + goto charging_full_failed; + + psy->charging_trig_name = kasprintf(GFP_KERNEL, + "%s-charging", psy->desc->name); + if (!psy->charging_trig_name) + goto charging_failed; + + psy->full_trig_name = kasprintf(GFP_KERNEL, "%s-full", psy->desc->name); + if (!psy->full_trig_name) + goto full_failed; + + psy->charging_blink_full_solid_trig_name = kasprintf(GFP_KERNEL, + "%s-charging-blink-full-solid", psy->desc->name); + if (!psy->charging_blink_full_solid_trig_name) + goto charging_blink_full_solid_failed; + + led_trigger_register_simple(psy->charging_full_trig_name, + &psy->charging_full_trig); + led_trigger_register_simple(psy->charging_trig_name, + &psy->charging_trig); + led_trigger_register_simple(psy->full_trig_name, + &psy->full_trig); + led_trigger_register_simple(psy->charging_blink_full_solid_trig_name, + &psy->charging_blink_full_solid_trig); + + return 0; + +charging_blink_full_solid_failed: + kfree(psy->full_trig_name); +full_failed: + kfree(psy->charging_trig_name); +charging_failed: + kfree(psy->charging_full_trig_name); +charging_full_failed: + return -ENOMEM; +} + +static void power_supply_remove_bat_triggers(struct power_supply *psy) +{ + led_trigger_unregister_simple(psy->charging_full_trig); + led_trigger_unregister_simple(psy->charging_trig); + led_trigger_unregister_simple(psy->full_trig); + led_trigger_unregister_simple(psy->charging_blink_full_solid_trig); + kfree(psy->charging_blink_full_solid_trig_name); + kfree(psy->full_trig_name); + kfree(psy->charging_trig_name); + kfree(psy->charging_full_trig_name); +} + +/* Generated power specific LEDs triggers. */ + +static void power_supply_update_gen_leds(struct power_supply *psy) +{ + union power_supply_propval online; + + if (power_supply_get_property(psy, POWER_SUPPLY_PROP_ONLINE, &online)) + return; + + dev_dbg(&psy->dev, "%s %d\n", __func__, online.intval); + + if (online.intval) + led_trigger_event(psy->online_trig, LED_FULL); + else + led_trigger_event(psy->online_trig, LED_OFF); +} + +static int power_supply_create_gen_triggers(struct power_supply *psy) +{ + psy->online_trig_name = kasprintf(GFP_KERNEL, "%s-online", + psy->desc->name); + if (!psy->online_trig_name) + return -ENOMEM; + + led_trigger_register_simple(psy->online_trig_name, &psy->online_trig); + + return 0; +} + +static void power_supply_remove_gen_triggers(struct power_supply *psy) +{ + led_trigger_unregister_simple(psy->online_trig); + kfree(psy->online_trig_name); +} + +/* Choice what triggers to create&update. */ + +void power_supply_update_leds(struct power_supply *psy) +{ + if (psy->desc->type == POWER_SUPPLY_TYPE_BATTERY) + power_supply_update_bat_leds(psy); + else + power_supply_update_gen_leds(psy); +} + +int power_supply_create_triggers(struct power_supply *psy) +{ + if (psy->desc->type == POWER_SUPPLY_TYPE_BATTERY) + return power_supply_create_bat_triggers(psy); + return power_supply_create_gen_triggers(psy); +} + +void power_supply_remove_triggers(struct power_supply *psy) +{ + if (psy->desc->type == POWER_SUPPLY_TYPE_BATTERY) + power_supply_remove_bat_triggers(psy); + else + power_supply_remove_gen_triggers(psy); +} diff --git a/drivers/power/supply/power_supply_sysfs.c b/drivers/power/supply/power_supply_sysfs.c new file mode 100644 index 000000000000..bcde8d13476a --- /dev/null +++ b/drivers/power/supply/power_supply_sysfs.c @@ -0,0 +1,337 @@ +/* + * Sysfs interface for the universal power supply monitor class + * + * Copyright © 2007 David Woodhouse + * Copyright © 2007 Anton Vorontsov + * Copyright © 2004 Szabolcs Gyurko + * Copyright © 2003 Ian Molton + * + * Modified: 2004, Oct Szabolcs Gyurko + * + * You may use this code as per GPL version 2 + */ + +#include +#include +#include +#include +#include + +#include "power_supply.h" + +/* + * This is because the name "current" breaks the device attr macro. + * The "current" word resolves to "(get_current())" so instead of + * "current" "(get_current())" appears in the sysfs. + * + * The source of this definition is the device.h which calls __ATTR + * macro in sysfs.h which calls the __stringify macro. + * + * Only modification that the name is not tried to be resolved + * (as a macro let's say). + */ + +#define POWER_SUPPLY_ATTR(_name) \ +{ \ + .attr = { .name = #_name }, \ + .show = power_supply_show_property, \ + .store = power_supply_store_property, \ +} + +static struct device_attribute power_supply_attrs[]; + +static ssize_t power_supply_show_property(struct device *dev, + struct device_attribute *attr, + char *buf) { + static char *type_text[] = { + "Unknown", "Battery", "UPS", "Mains", "USB", + "USB_DCP", "USB_CDP", "USB_ACA", "USB_C", + "USB_PD", "USB_PD_DRP" + }; + static char *status_text[] = { + "Unknown", "Charging", "Discharging", "Not charging", "Full" + }; + static char *charge_type[] = { + "Unknown", "N/A", "Trickle", "Fast" + }; + static char *health_text[] = { + "Unknown", "Good", "Overheat", "Dead", "Over voltage", + "Unspecified failure", "Cold", "Watchdog timer expire", + "Safety timer expire" + }; + static char *technology_text[] = { + "Unknown", "NiMH", "Li-ion", "Li-poly", "LiFe", "NiCd", + "LiMn" + }; + static char *capacity_level_text[] = { + "Unknown", "Critical", "Low", "Normal", "High", "Full" + }; + static char *scope_text[] = { + "Unknown", "System", "Device" + }; + ssize_t ret = 0; + struct power_supply *psy = dev_get_drvdata(dev); + const ptrdiff_t off = attr - power_supply_attrs; + union power_supply_propval value; + + if (off == POWER_SUPPLY_PROP_TYPE) { + value.intval = psy->desc->type; + } else { + ret = power_supply_get_property(psy, off, &value); + + if (ret < 0) { + if (ret == -ENODATA) + dev_dbg(dev, "driver has no data for `%s' property\n", + attr->attr.name); + else if (ret != -ENODEV && ret != -EAGAIN) + dev_err(dev, "driver failed to report `%s' property: %zd\n", + attr->attr.name, ret); + return ret; + } + } + + if (off == POWER_SUPPLY_PROP_STATUS) + return sprintf(buf, "%s\n", status_text[value.intval]); + else if (off == POWER_SUPPLY_PROP_CHARGE_TYPE) + return sprintf(buf, "%s\n", charge_type[value.intval]); + else if (off == POWER_SUPPLY_PROP_HEALTH) + return sprintf(buf, "%s\n", health_text[value.intval]); + else if (off == POWER_SUPPLY_PROP_TECHNOLOGY) + return sprintf(buf, "%s\n", technology_text[value.intval]); + else if (off == POWER_SUPPLY_PROP_CAPACITY_LEVEL) + return sprintf(buf, "%s\n", capacity_level_text[value.intval]); + else if (off == POWER_SUPPLY_PROP_TYPE) + return sprintf(buf, "%s\n", type_text[value.intval]); + else if (off == POWER_SUPPLY_PROP_SCOPE) + return sprintf(buf, "%s\n", scope_text[value.intval]); + else if (off >= POWER_SUPPLY_PROP_MODEL_NAME) + return sprintf(buf, "%s\n", value.strval); + + return sprintf(buf, "%d\n", value.intval); +} + +static ssize_t power_supply_store_property(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) { + ssize_t ret; + struct power_supply *psy = dev_get_drvdata(dev); + const ptrdiff_t off = attr - power_supply_attrs; + union power_supply_propval value; + long long_val; + + /* TODO: support other types than int */ + ret = kstrtol(buf, 10, &long_val); + if (ret < 0) + return ret; + + value.intval = long_val; + + ret = power_supply_set_property(psy, off, &value); + if (ret < 0) + return ret; + + return count; +} + +/* Must be in the same order as POWER_SUPPLY_PROP_* */ +static struct device_attribute power_supply_attrs[] = { + /* Properties of type `int' */ + POWER_SUPPLY_ATTR(status), + POWER_SUPPLY_ATTR(charge_type), + POWER_SUPPLY_ATTR(health), + POWER_SUPPLY_ATTR(present), + POWER_SUPPLY_ATTR(online), + POWER_SUPPLY_ATTR(authentic), + POWER_SUPPLY_ATTR(technology), + POWER_SUPPLY_ATTR(cycle_count), + POWER_SUPPLY_ATTR(voltage_max), + POWER_SUPPLY_ATTR(voltage_min), + POWER_SUPPLY_ATTR(voltage_max_design), + POWER_SUPPLY_ATTR(voltage_min_design), + POWER_SUPPLY_ATTR(voltage_now), + POWER_SUPPLY_ATTR(voltage_avg), + POWER_SUPPLY_ATTR(voltage_ocv), + POWER_SUPPLY_ATTR(voltage_boot), + POWER_SUPPLY_ATTR(current_max), + POWER_SUPPLY_ATTR(current_now), + POWER_SUPPLY_ATTR(current_avg), + POWER_SUPPLY_ATTR(current_boot), + POWER_SUPPLY_ATTR(power_now), + POWER_SUPPLY_ATTR(power_avg), + POWER_SUPPLY_ATTR(charge_full_design), + POWER_SUPPLY_ATTR(charge_empty_design), + POWER_SUPPLY_ATTR(charge_full), + POWER_SUPPLY_ATTR(charge_empty), + POWER_SUPPLY_ATTR(charge_now), + POWER_SUPPLY_ATTR(charge_avg), + POWER_SUPPLY_ATTR(charge_counter), + POWER_SUPPLY_ATTR(constant_charge_current), + POWER_SUPPLY_ATTR(constant_charge_current_max), + POWER_SUPPLY_ATTR(constant_charge_voltage), + POWER_SUPPLY_ATTR(constant_charge_voltage_max), + POWER_SUPPLY_ATTR(charge_control_limit), + POWER_SUPPLY_ATTR(charge_control_limit_max), + POWER_SUPPLY_ATTR(input_current_limit), + POWER_SUPPLY_ATTR(energy_full_design), + POWER_SUPPLY_ATTR(energy_empty_design), + POWER_SUPPLY_ATTR(energy_full), + POWER_SUPPLY_ATTR(energy_empty), + POWER_SUPPLY_ATTR(energy_now), + POWER_SUPPLY_ATTR(energy_avg), + POWER_SUPPLY_ATTR(capacity), + POWER_SUPPLY_ATTR(capacity_alert_min), + POWER_SUPPLY_ATTR(capacity_alert_max), + POWER_SUPPLY_ATTR(capacity_level), + POWER_SUPPLY_ATTR(temp), + POWER_SUPPLY_ATTR(temp_max), + POWER_SUPPLY_ATTR(temp_min), + POWER_SUPPLY_ATTR(temp_alert_min), + POWER_SUPPLY_ATTR(temp_alert_max), + POWER_SUPPLY_ATTR(temp_ambient), + POWER_SUPPLY_ATTR(temp_ambient_alert_min), + POWER_SUPPLY_ATTR(temp_ambient_alert_max), + POWER_SUPPLY_ATTR(time_to_empty_now), + POWER_SUPPLY_ATTR(time_to_empty_avg), + POWER_SUPPLY_ATTR(time_to_full_now), + POWER_SUPPLY_ATTR(time_to_full_avg), + POWER_SUPPLY_ATTR(type), + POWER_SUPPLY_ATTR(scope), + POWER_SUPPLY_ATTR(charge_term_current), + POWER_SUPPLY_ATTR(calibrate), + /* Properties of type `const char *' */ + POWER_SUPPLY_ATTR(model_name), + POWER_SUPPLY_ATTR(manufacturer), + POWER_SUPPLY_ATTR(serial_number), +}; + +static struct attribute * +__power_supply_attrs[ARRAY_SIZE(power_supply_attrs) + 1]; + +static umode_t power_supply_attr_is_visible(struct kobject *kobj, + struct attribute *attr, + int attrno) +{ + struct device *dev = container_of(kobj, struct device, kobj); + struct power_supply *psy = dev_get_drvdata(dev); + umode_t mode = S_IRUSR | S_IRGRP | S_IROTH; + int i; + + if (attrno == POWER_SUPPLY_PROP_TYPE) + return mode; + + for (i = 0; i < psy->desc->num_properties; i++) { + int property = psy->desc->properties[i]; + + if (property == attrno) { + if (psy->desc->property_is_writeable && + psy->desc->property_is_writeable(psy, property) > 0) + mode |= S_IWUSR; + + return mode; + } + } + + return 0; +} + +static struct attribute_group power_supply_attr_group = { + .attrs = __power_supply_attrs, + .is_visible = power_supply_attr_is_visible, +}; + +static const struct attribute_group *power_supply_attr_groups[] = { + &power_supply_attr_group, + NULL, +}; + +void power_supply_init_attrs(struct device_type *dev_type) +{ + int i; + + dev_type->groups = power_supply_attr_groups; + + for (i = 0; i < ARRAY_SIZE(power_supply_attrs); i++) + __power_supply_attrs[i] = &power_supply_attrs[i].attr; +} + +static char *kstruprdup(const char *str, gfp_t gfp) +{ + char *ret, *ustr; + + ustr = ret = kmalloc(strlen(str) + 1, gfp); + + if (!ret) + return NULL; + + while (*str) + *ustr++ = toupper(*str++); + + *ustr = 0; + + return ret; +} + +int power_supply_uevent(struct device *dev, struct kobj_uevent_env *env) +{ + struct power_supply *psy = dev_get_drvdata(dev); + int ret = 0, j; + char *prop_buf; + char *attrname; + + dev_dbg(dev, "uevent\n"); + + if (!psy || !psy->desc) { + dev_dbg(dev, "No power supply yet\n"); + return ret; + } + + dev_dbg(dev, "POWER_SUPPLY_NAME=%s\n", psy->desc->name); + + ret = add_uevent_var(env, "POWER_SUPPLY_NAME=%s", psy->desc->name); + if (ret) + return ret; + + prop_buf = (char *)get_zeroed_page(GFP_KERNEL); + if (!prop_buf) + return -ENOMEM; + + for (j = 0; j < psy->desc->num_properties; j++) { + struct device_attribute *attr; + char *line; + + attr = &power_supply_attrs[psy->desc->properties[j]]; + + ret = power_supply_show_property(dev, attr, prop_buf); + if (ret == -ENODEV || ret == -ENODATA) { + /* When a battery is absent, we expect -ENODEV. Don't abort; + send the uevent with at least the the PRESENT=0 property */ + ret = 0; + continue; + } + + if (ret < 0) + goto out; + + line = strchr(prop_buf, '\n'); + if (line) + *line = 0; + + attrname = kstruprdup(attr->attr.name, GFP_KERNEL); + if (!attrname) { + ret = -ENOMEM; + goto out; + } + + dev_dbg(dev, "prop %s=%s\n", attrname, prop_buf); + + ret = add_uevent_var(env, "POWER_SUPPLY_%s=%s", attrname, prop_buf); + kfree(attrname); + if (ret) + goto out; + } + +out: + free_page((unsigned long)prop_buf); + + return ret; +} diff --git a/drivers/power/supply/qcom_smbb.c b/drivers/power/supply/qcom_smbb.c new file mode 100644 index 000000000000..b5896ba2a602 --- /dev/null +++ b/drivers/power/supply/qcom_smbb.c @@ -0,0 +1,972 @@ +/* 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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; + struct extcon_dev *edev; + + 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 const unsigned int smbb_usb_extcon_cable[] = { + EXTCON_USB, + EXTCON_NONE, +}; + +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); + extcon_set_cable_state_(chg->edev, EXTCON_USB, + chg->status & 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); + } + + chg->edev = devm_extcon_dev_allocate(&pdev->dev, smbb_usb_extcon_cable); + if (IS_ERR(chg->edev)) { + dev_err(&pdev->dev, "failed to allocate extcon device\n"); + return -ENOMEM; + } + + rc = devm_extcon_dev_register(&pdev->dev, chg->edev); + if (rc < 0) { + dev_err(&pdev->dev, "failed to register extcon device\n"); + return rc; + } + + 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"); diff --git a/drivers/power/supply/rt5033_battery.c b/drivers/power/supply/rt5033_battery.c new file mode 100644 index 000000000000..bcdd83048492 --- /dev/null +++ b/drivers/power/supply/rt5033_battery.c @@ -0,0 +1,182 @@ +/* + * Fuel gauge driver for Richtek RT5033 + * + * Copyright (C) 2014 Samsung Electronics, Co., Ltd. + * Author: Beomho Seo + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published bythe Free Software Foundation. + */ + +#include +#include +#include +#include +#include + +static int rt5033_battery_get_capacity(struct i2c_client *client) +{ + struct rt5033_battery *battery = i2c_get_clientdata(client); + u32 msb; + + regmap_read(battery->regmap, RT5033_FUEL_REG_SOC_H, &msb); + + return msb; +} + +static int rt5033_battery_get_present(struct i2c_client *client) +{ + struct rt5033_battery *battery = i2c_get_clientdata(client); + u32 val; + + regmap_read(battery->regmap, RT5033_FUEL_REG_CONFIG_L, &val); + + return (val & RT5033_FUEL_BAT_PRESENT) ? true : false; +} + +static int rt5033_battery_get_watt_prop(struct i2c_client *client, + enum power_supply_property psp) +{ + struct rt5033_battery *battery = i2c_get_clientdata(client); + unsigned int regh, regl; + int ret; + u32 msb, lsb; + + switch (psp) { + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + regh = RT5033_FUEL_REG_VBAT_H; + regl = RT5033_FUEL_REG_VBAT_L; + break; + case POWER_SUPPLY_PROP_VOLTAGE_AVG: + regh = RT5033_FUEL_REG_AVG_VOLT_H; + regl = RT5033_FUEL_REG_AVG_VOLT_L; + break; + case POWER_SUPPLY_PROP_VOLTAGE_OCV: + regh = RT5033_FUEL_REG_OCV_H; + regl = RT5033_FUEL_REG_OCV_L; + break; + default: + return -EINVAL; + } + + regmap_read(battery->regmap, regh, &msb); + regmap_read(battery->regmap, regl, &lsb); + + ret = ((msb << 4) + (lsb >> 4)) * 1250 / 1000; + + return ret; +} + +static int rt5033_battery_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct rt5033_battery *battery = power_supply_get_drvdata(psy); + + switch (psp) { + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + case POWER_SUPPLY_PROP_VOLTAGE_AVG: + case POWER_SUPPLY_PROP_VOLTAGE_OCV: + val->intval = rt5033_battery_get_watt_prop(battery->client, + psp); + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = rt5033_battery_get_present(battery->client); + break; + case POWER_SUPPLY_PROP_CAPACITY: + val->intval = rt5033_battery_get_capacity(battery->client); + break; + default: + return -EINVAL; + } + return 0; +} + +static enum power_supply_property rt5033_battery_props[] = { + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_VOLTAGE_AVG, + POWER_SUPPLY_PROP_VOLTAGE_OCV, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_CAPACITY, +}; + +static const struct regmap_config rt5033_battery_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = RT5033_FUEL_REG_END, +}; + +static const struct power_supply_desc rt5033_battery_desc = { + .name = "rt5033-battery", + .type = POWER_SUPPLY_TYPE_BATTERY, + .get_property = rt5033_battery_get_property, + .properties = rt5033_battery_props, + .num_properties = ARRAY_SIZE(rt5033_battery_props), +}; + +static int rt5033_battery_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent); + struct power_supply_config psy_cfg = {}; + struct rt5033_battery *battery; + u32 ret; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE)) + return -EIO; + + battery = devm_kzalloc(&client->dev, sizeof(*battery), GFP_KERNEL); + if (!battery) + return -EINVAL; + + battery->client = client; + battery->regmap = devm_regmap_init_i2c(client, + &rt5033_battery_regmap_config); + if (IS_ERR(battery->regmap)) { + dev_err(&client->dev, "Failed to initialize regmap\n"); + return -EINVAL; + } + + i2c_set_clientdata(client, battery); + psy_cfg.drv_data = battery; + + battery->psy = power_supply_register(&client->dev, + &rt5033_battery_desc, &psy_cfg); + if (IS_ERR(battery->psy)) { + dev_err(&client->dev, "Failed to register power supply\n"); + ret = PTR_ERR(battery->psy); + return ret; + } + + return 0; +} + +static int rt5033_battery_remove(struct i2c_client *client) +{ + struct rt5033_battery *battery = i2c_get_clientdata(client); + + power_supply_unregister(battery->psy); + + return 0; +} + +static const struct i2c_device_id rt5033_battery_id[] = { + { "rt5033-battery", }, + { } +}; +MODULE_DEVICE_TABLE(i2c, rt5033_battery_id); + +static struct i2c_driver rt5033_battery_driver = { + .driver = { + .name = "rt5033-battery", + }, + .probe = rt5033_battery_probe, + .remove = rt5033_battery_remove, + .id_table = rt5033_battery_id, +}; +module_i2c_driver(rt5033_battery_driver); + +MODULE_DESCRIPTION("Richtek RT5033 fuel gauge driver"); +MODULE_AUTHOR("Beomho Seo "); +MODULE_LICENSE("GPL"); diff --git a/drivers/power/supply/rt9455_charger.c b/drivers/power/supply/rt9455_charger.c new file mode 100644 index 000000000000..cfdbde9daf94 --- /dev/null +++ b/drivers/power/supply/rt9455_charger.c @@ -0,0 +1,1763 @@ +/* + * Driver for Richtek RT9455WSC battery charger. + * + * Copyright (C) 2015 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; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define RT9455_MANUFACTURER "Richtek" +#define RT9455_MODEL_NAME "RT9455" +#define RT9455_DRIVER_NAME "rt9455-charger" + +#define RT9455_IRQ_NAME "interrupt" + +#define RT9455_PWR_RDY_DELAY 1 /* 1 second */ +#define RT9455_MAX_CHARGING_TIME 21600 /* 6 hrs */ +#define RT9455_BATT_PRESENCE_DELAY 60 /* 60 seconds */ + +#define RT9455_CHARGE_MODE 0x00 +#define RT9455_BOOST_MODE 0x01 + +#define RT9455_FAULT 0x03 + +#define RT9455_IAICR_100MA 0x00 +#define RT9455_IAICR_500MA 0x01 +#define RT9455_IAICR_NO_LIMIT 0x03 + +#define RT9455_CHARGE_DISABLE 0x00 +#define RT9455_CHARGE_ENABLE 0x01 + +#define RT9455_PWR_FAULT 0x00 +#define RT9455_PWR_GOOD 0x01 + +#define RT9455_REG_CTRL1 0x00 /* CTRL1 reg address */ +#define RT9455_REG_CTRL2 0x01 /* CTRL2 reg address */ +#define RT9455_REG_CTRL3 0x02 /* CTRL3 reg address */ +#define RT9455_REG_DEV_ID 0x03 /* DEV_ID reg address */ +#define RT9455_REG_CTRL4 0x04 /* CTRL4 reg address */ +#define RT9455_REG_CTRL5 0x05 /* CTRL5 reg address */ +#define RT9455_REG_CTRL6 0x06 /* CTRL6 reg address */ +#define RT9455_REG_CTRL7 0x07 /* CTRL7 reg address */ +#define RT9455_REG_IRQ1 0x08 /* IRQ1 reg address */ +#define RT9455_REG_IRQ2 0x09 /* IRQ2 reg address */ +#define RT9455_REG_IRQ3 0x0A /* IRQ3 reg address */ +#define RT9455_REG_MASK1 0x0B /* MASK1 reg address */ +#define RT9455_REG_MASK2 0x0C /* MASK2 reg address */ +#define RT9455_REG_MASK3 0x0D /* MASK3 reg address */ + +enum rt9455_fields { + F_STAT, F_BOOST, F_PWR_RDY, F_OTG_PIN_POLARITY, /* CTRL1 reg fields */ + + F_IAICR, F_TE_SHDN_EN, F_HIGHER_OCP, F_TE, F_IAICR_INT, F_HIZ, + F_OPA_MODE, /* CTRL2 reg fields */ + + F_VOREG, F_OTG_PL, F_OTG_EN, /* CTRL3 reg fields */ + + F_VENDOR_ID, F_CHIP_REV, /* DEV_ID reg fields */ + + F_RST, /* CTRL4 reg fields */ + + F_TMR_EN, F_MIVR, F_IPREC, F_IEOC_PERCENTAGE, /* CTRL5 reg fields*/ + + F_IAICR_SEL, F_ICHRG, F_VPREC, /* CTRL6 reg fields */ + + F_BATD_EN, F_CHG_EN, F_VMREG, /* CTRL7 reg fields */ + + F_TSDI, F_VINOVPI, F_BATAB, /* IRQ1 reg fields */ + + F_CHRVPI, F_CHBATOVI, F_CHTERMI, F_CHRCHGI, F_CH32MI, F_CHTREGI, + F_CHMIVRI, /* IRQ2 reg fields */ + + F_BSTBUSOVI, F_BSTOLI, F_BSTLOWVI, F_BST32SI, /* IRQ3 reg fields */ + + F_TSDM, F_VINOVPIM, F_BATABM, /* MASK1 reg fields */ + + F_CHRVPIM, F_CHBATOVIM, F_CHTERMIM, F_CHRCHGIM, F_CH32MIM, F_CHTREGIM, + F_CHMIVRIM, /* MASK2 reg fields */ + + F_BSTVINOVIM, F_BSTOLIM, F_BSTLOWVIM, F_BST32SIM, /* MASK3 reg fields */ + + F_MAX_FIELDS +}; + +static const struct reg_field rt9455_reg_fields[] = { + [F_STAT] = REG_FIELD(RT9455_REG_CTRL1, 4, 5), + [F_BOOST] = REG_FIELD(RT9455_REG_CTRL1, 3, 3), + [F_PWR_RDY] = REG_FIELD(RT9455_REG_CTRL1, 2, 2), + [F_OTG_PIN_POLARITY] = REG_FIELD(RT9455_REG_CTRL1, 1, 1), + + [F_IAICR] = REG_FIELD(RT9455_REG_CTRL2, 6, 7), + [F_TE_SHDN_EN] = REG_FIELD(RT9455_REG_CTRL2, 5, 5), + [F_HIGHER_OCP] = REG_FIELD(RT9455_REG_CTRL2, 4, 4), + [F_TE] = REG_FIELD(RT9455_REG_CTRL2, 3, 3), + [F_IAICR_INT] = REG_FIELD(RT9455_REG_CTRL2, 2, 2), + [F_HIZ] = REG_FIELD(RT9455_REG_CTRL2, 1, 1), + [F_OPA_MODE] = REG_FIELD(RT9455_REG_CTRL2, 0, 0), + + [F_VOREG] = REG_FIELD(RT9455_REG_CTRL3, 2, 7), + [F_OTG_PL] = REG_FIELD(RT9455_REG_CTRL3, 1, 1), + [F_OTG_EN] = REG_FIELD(RT9455_REG_CTRL3, 0, 0), + + [F_VENDOR_ID] = REG_FIELD(RT9455_REG_DEV_ID, 4, 7), + [F_CHIP_REV] = REG_FIELD(RT9455_REG_DEV_ID, 0, 3), + + [F_RST] = REG_FIELD(RT9455_REG_CTRL4, 7, 7), + + [F_TMR_EN] = REG_FIELD(RT9455_REG_CTRL5, 7, 7), + [F_MIVR] = REG_FIELD(RT9455_REG_CTRL5, 4, 5), + [F_IPREC] = REG_FIELD(RT9455_REG_CTRL5, 2, 3), + [F_IEOC_PERCENTAGE] = REG_FIELD(RT9455_REG_CTRL5, 0, 1), + + [F_IAICR_SEL] = REG_FIELD(RT9455_REG_CTRL6, 7, 7), + [F_ICHRG] = REG_FIELD(RT9455_REG_CTRL6, 4, 6), + [F_VPREC] = REG_FIELD(RT9455_REG_CTRL6, 0, 2), + + [F_BATD_EN] = REG_FIELD(RT9455_REG_CTRL7, 6, 6), + [F_CHG_EN] = REG_FIELD(RT9455_REG_CTRL7, 4, 4), + [F_VMREG] = REG_FIELD(RT9455_REG_CTRL7, 0, 3), + + [F_TSDI] = REG_FIELD(RT9455_REG_IRQ1, 7, 7), + [F_VINOVPI] = REG_FIELD(RT9455_REG_IRQ1, 6, 6), + [F_BATAB] = REG_FIELD(RT9455_REG_IRQ1, 0, 0), + + [F_CHRVPI] = REG_FIELD(RT9455_REG_IRQ2, 7, 7), + [F_CHBATOVI] = REG_FIELD(RT9455_REG_IRQ2, 5, 5), + [F_CHTERMI] = REG_FIELD(RT9455_REG_IRQ2, 4, 4), + [F_CHRCHGI] = REG_FIELD(RT9455_REG_IRQ2, 3, 3), + [F_CH32MI] = REG_FIELD(RT9455_REG_IRQ2, 2, 2), + [F_CHTREGI] = REG_FIELD(RT9455_REG_IRQ2, 1, 1), + [F_CHMIVRI] = REG_FIELD(RT9455_REG_IRQ2, 0, 0), + + [F_BSTBUSOVI] = REG_FIELD(RT9455_REG_IRQ3, 7, 7), + [F_BSTOLI] = REG_FIELD(RT9455_REG_IRQ3, 6, 6), + [F_BSTLOWVI] = REG_FIELD(RT9455_REG_IRQ3, 5, 5), + [F_BST32SI] = REG_FIELD(RT9455_REG_IRQ3, 3, 3), + + [F_TSDM] = REG_FIELD(RT9455_REG_MASK1, 7, 7), + [F_VINOVPIM] = REG_FIELD(RT9455_REG_MASK1, 6, 6), + [F_BATABM] = REG_FIELD(RT9455_REG_MASK1, 0, 0), + + [F_CHRVPIM] = REG_FIELD(RT9455_REG_MASK2, 7, 7), + [F_CHBATOVIM] = REG_FIELD(RT9455_REG_MASK2, 5, 5), + [F_CHTERMIM] = REG_FIELD(RT9455_REG_MASK2, 4, 4), + [F_CHRCHGIM] = REG_FIELD(RT9455_REG_MASK2, 3, 3), + [F_CH32MIM] = REG_FIELD(RT9455_REG_MASK2, 2, 2), + [F_CHTREGIM] = REG_FIELD(RT9455_REG_MASK2, 1, 1), + [F_CHMIVRIM] = REG_FIELD(RT9455_REG_MASK2, 0, 0), + + [F_BSTVINOVIM] = REG_FIELD(RT9455_REG_MASK3, 7, 7), + [F_BSTOLIM] = REG_FIELD(RT9455_REG_MASK3, 6, 6), + [F_BSTLOWVIM] = REG_FIELD(RT9455_REG_MASK3, 5, 5), + [F_BST32SIM] = REG_FIELD(RT9455_REG_MASK3, 3, 3), +}; + +#define GET_MASK(fid) (BIT(rt9455_reg_fields[fid].msb + 1) - \ + BIT(rt9455_reg_fields[fid].lsb)) + +/* + * Each array initialised below shows the possible real-world values for a + * group of bits belonging to RT9455 registers. The arrays are sorted in + * ascending order. The index of each real-world value represents the value + * that is encoded in the group of bits belonging to RT9455 registers. + */ +/* REG06[6:4] (ICHRG) in uAh */ +static const int rt9455_ichrg_values[] = { + 500000, 650000, 800000, 950000, 1100000, 1250000, 1400000, 1550000 +}; + +/* + * When the charger is in charge mode, REG02[7:2] represent battery regulation + * voltage. + */ +/* REG02[7:2] (VOREG) in uV */ +static const int rt9455_voreg_values[] = { + 3500000, 3520000, 3540000, 3560000, 3580000, 3600000, 3620000, 3640000, + 3660000, 3680000, 3700000, 3720000, 3740000, 3760000, 3780000, 3800000, + 3820000, 3840000, 3860000, 3880000, 3900000, 3920000, 3940000, 3960000, + 3980000, 4000000, 4020000, 4040000, 4060000, 4080000, 4100000, 4120000, + 4140000, 4160000, 4180000, 4200000, 4220000, 4240000, 4260000, 4280000, + 4300000, 4330000, 4350000, 4370000, 4390000, 4410000, 4430000, 4450000, + 4450000, 4450000, 4450000, 4450000, 4450000, 4450000, 4450000, 4450000, + 4450000, 4450000, 4450000, 4450000, 4450000, 4450000, 4450000, 4450000 +}; + +/* + * When the charger is in boost mode, REG02[7:2] represent boost output + * voltage. + */ +/* REG02[7:2] (Boost output voltage) in uV */ +static const int rt9455_boost_voltage_values[] = { + 4425000, 4450000, 4475000, 4500000, 4525000, 4550000, 4575000, 4600000, + 4625000, 4650000, 4675000, 4700000, 4725000, 4750000, 4775000, 4800000, + 4825000, 4850000, 4875000, 4900000, 4925000, 4950000, 4975000, 5000000, + 5025000, 5050000, 5075000, 5100000, 5125000, 5150000, 5175000, 5200000, + 5225000, 5250000, 5275000, 5300000, 5325000, 5350000, 5375000, 5400000, + 5425000, 5450000, 5475000, 5500000, 5525000, 5550000, 5575000, 5600000, + 5600000, 5600000, 5600000, 5600000, 5600000, 5600000, 5600000, 5600000, + 5600000, 5600000, 5600000, 5600000, 5600000, 5600000, 5600000, 5600000, +}; + +/* REG07[3:0] (VMREG) in uV */ +static const int rt9455_vmreg_values[] = { + 4200000, 4220000, 4240000, 4260000, 4280000, 4300000, 4320000, 4340000, + 4360000, 4380000, 4400000, 4430000, 4450000, 4450000, 4450000, 4450000 +}; + +/* REG05[5:4] (IEOC_PERCENTAGE) */ +static const int rt9455_ieoc_percentage_values[] = { + 10, 30, 20, 30 +}; + +/* REG05[1:0] (MIVR) in uV */ +static const int rt9455_mivr_values[] = { + 4000000, 4250000, 4500000, 5000000 +}; + +/* REG05[1:0] (IAICR) in uA */ +static const int rt9455_iaicr_values[] = { + 100000, 500000, 1000000, 2000000 +}; + +struct rt9455_info { + struct i2c_client *client; + struct regmap *regmap; + struct regmap_field *regmap_fields[F_MAX_FIELDS]; + struct power_supply *charger; +#if IS_ENABLED(CONFIG_USB_PHY) + struct usb_phy *usb_phy; + struct notifier_block nb; +#endif + struct delayed_work pwr_rdy_work; + struct delayed_work max_charging_time_work; + struct delayed_work batt_presence_work; + u32 voreg; + u32 boost_voltage; +}; + +/* + * Iterate through each element of the 'tbl' array until an element whose value + * is greater than v is found. Return the index of the respective element, + * or the index of the last element in the array, if no such element is found. + */ +static unsigned int rt9455_find_idx(const int tbl[], int tbl_size, int v) +{ + int i; + + /* + * No need to iterate until the last index in the table because + * if no element greater than v is found in the table, + * or if only the last element is greater than v, + * function returns the index of the last element. + */ + for (i = 0; i < tbl_size - 1; i++) + if (v <= tbl[i]) + return i; + + return (tbl_size - 1); +} + +static int rt9455_get_field_val(struct rt9455_info *info, + enum rt9455_fields field, + const int tbl[], int tbl_size, int *val) +{ + unsigned int v; + int ret; + + ret = regmap_field_read(info->regmap_fields[field], &v); + if (ret) + return ret; + + v = (v >= tbl_size) ? (tbl_size - 1) : v; + *val = tbl[v]; + + return 0; +} + +static int rt9455_set_field_val(struct rt9455_info *info, + enum rt9455_fields field, + const int tbl[], int tbl_size, int val) +{ + unsigned int idx = rt9455_find_idx(tbl, tbl_size, val); + + return regmap_field_write(info->regmap_fields[field], idx); +} + +static int rt9455_register_reset(struct rt9455_info *info) +{ + struct device *dev = &info->client->dev; + unsigned int v; + int ret, limit = 100; + + ret = regmap_field_write(info->regmap_fields[F_RST], 0x01); + if (ret) { + dev_err(dev, "Failed to set RST bit\n"); + return ret; + } + + /* + * To make sure that reset operation has finished, loop until RST bit + * is set to 0. + */ + do { + ret = regmap_field_read(info->regmap_fields[F_RST], &v); + if (ret) { + dev_err(dev, "Failed to read RST bit\n"); + return ret; + } + + if (!v) + break; + + usleep_range(10, 100); + } while (--limit); + + if (!limit) + return -EIO; + + return 0; +} + +/* Charger power supply property routines */ +static enum power_supply_property rt9455_charger_properties[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX, + POWER_SUPPLY_PROP_SCOPE, + POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT, + POWER_SUPPLY_PROP_MODEL_NAME, + POWER_SUPPLY_PROP_MANUFACTURER, +}; + +static char *rt9455_charger_supplied_to[] = { + "main-battery", +}; + +static int rt9455_charger_get_status(struct rt9455_info *info, + union power_supply_propval *val) +{ + unsigned int v, pwr_rdy; + int ret; + + ret = regmap_field_read(info->regmap_fields[F_PWR_RDY], + &pwr_rdy); + if (ret) { + dev_err(&info->client->dev, "Failed to read PWR_RDY bit\n"); + return ret; + } + + /* + * If PWR_RDY bit is unset, the battery is discharging. Otherwise, + * STAT bits value must be checked. + */ + if (!pwr_rdy) { + val->intval = POWER_SUPPLY_STATUS_DISCHARGING; + return 0; + } + + ret = regmap_field_read(info->regmap_fields[F_STAT], &v); + if (ret) { + dev_err(&info->client->dev, "Failed to read STAT bits\n"); + return ret; + } + + switch (v) { + case 0: + /* + * If PWR_RDY bit is set, but STAT bits value is 0, the charger + * may be in one of the following cases: + * 1. CHG_EN bit is 0. + * 2. CHG_EN bit is 1 but the battery is not connected. + * In any of these cases, POWER_SUPPLY_STATUS_NOT_CHARGING is + * returned. + */ + val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; + return 0; + case 1: + val->intval = POWER_SUPPLY_STATUS_CHARGING; + return 0; + case 2: + val->intval = POWER_SUPPLY_STATUS_FULL; + return 0; + default: + val->intval = POWER_SUPPLY_STATUS_UNKNOWN; + return 0; + } +} + +static int rt9455_charger_get_health(struct rt9455_info *info, + union power_supply_propval *val) +{ + struct device *dev = &info->client->dev; + unsigned int v; + int ret; + + val->intval = POWER_SUPPLY_HEALTH_GOOD; + + ret = regmap_read(info->regmap, RT9455_REG_IRQ1, &v); + if (ret) { + dev_err(dev, "Failed to read IRQ1 register\n"); + return ret; + } + + if (v & GET_MASK(F_TSDI)) { + val->intval = POWER_SUPPLY_HEALTH_OVERHEAT; + return 0; + } + if (v & GET_MASK(F_VINOVPI)) { + val->intval = POWER_SUPPLY_HEALTH_OVERVOLTAGE; + return 0; + } + if (v & GET_MASK(F_BATAB)) { + val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; + return 0; + } + + ret = regmap_read(info->regmap, RT9455_REG_IRQ2, &v); + if (ret) { + dev_err(dev, "Failed to read IRQ2 register\n"); + return ret; + } + + if (v & GET_MASK(F_CHBATOVI)) { + val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; + return 0; + } + if (v & GET_MASK(F_CH32MI)) { + val->intval = POWER_SUPPLY_HEALTH_SAFETY_TIMER_EXPIRE; + return 0; + } + + ret = regmap_read(info->regmap, RT9455_REG_IRQ3, &v); + if (ret) { + dev_err(dev, "Failed to read IRQ3 register\n"); + return ret; + } + + if (v & GET_MASK(F_BSTBUSOVI)) { + val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; + return 0; + } + if (v & GET_MASK(F_BSTOLI)) { + val->intval = POWER_SUPPLY_HEALTH_OVERVOLTAGE; + return 0; + } + if (v & GET_MASK(F_BSTLOWVI)) { + val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; + return 0; + } + if (v & GET_MASK(F_BST32SI)) { + val->intval = POWER_SUPPLY_HEALTH_SAFETY_TIMER_EXPIRE; + return 0; + } + + ret = regmap_field_read(info->regmap_fields[F_STAT], &v); + if (ret) { + dev_err(dev, "Failed to read STAT bits\n"); + return ret; + } + + if (v == RT9455_FAULT) { + val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; + return 0; + } + + return 0; +} + +static int rt9455_charger_get_battery_presence(struct rt9455_info *info, + union power_supply_propval *val) +{ + unsigned int v; + int ret; + + ret = regmap_field_read(info->regmap_fields[F_BATAB], &v); + if (ret) { + dev_err(&info->client->dev, "Failed to read BATAB bit\n"); + return ret; + } + + /* + * Since BATAB is 1 when battery is NOT present and 0 otherwise, + * !BATAB is returned. + */ + val->intval = !v; + + return 0; +} + +static int rt9455_charger_get_online(struct rt9455_info *info, + union power_supply_propval *val) +{ + unsigned int v; + int ret; + + ret = regmap_field_read(info->regmap_fields[F_PWR_RDY], &v); + if (ret) { + dev_err(&info->client->dev, "Failed to read PWR_RDY bit\n"); + return ret; + } + + val->intval = (int)v; + + return 0; +} + +static int rt9455_charger_get_current(struct rt9455_info *info, + union power_supply_propval *val) +{ + int curr; + int ret; + + ret = rt9455_get_field_val(info, F_ICHRG, + rt9455_ichrg_values, + ARRAY_SIZE(rt9455_ichrg_values), + &curr); + if (ret) { + dev_err(&info->client->dev, "Failed to read ICHRG value\n"); + return ret; + } + + val->intval = curr; + + return 0; +} + +static int rt9455_charger_get_current_max(struct rt9455_info *info, + union power_supply_propval *val) +{ + int idx = ARRAY_SIZE(rt9455_ichrg_values) - 1; + + val->intval = rt9455_ichrg_values[idx]; + + return 0; +} + +static int rt9455_charger_get_voltage(struct rt9455_info *info, + union power_supply_propval *val) +{ + int voltage; + int ret; + + ret = rt9455_get_field_val(info, F_VOREG, + rt9455_voreg_values, + ARRAY_SIZE(rt9455_voreg_values), + &voltage); + if (ret) { + dev_err(&info->client->dev, "Failed to read VOREG value\n"); + return ret; + } + + val->intval = voltage; + + return 0; +} + +static int rt9455_charger_get_voltage_max(struct rt9455_info *info, + union power_supply_propval *val) +{ + int idx = ARRAY_SIZE(rt9455_vmreg_values) - 1; + + val->intval = rt9455_vmreg_values[idx]; + + return 0; +} + +static int rt9455_charger_get_term_current(struct rt9455_info *info, + union power_supply_propval *val) +{ + struct device *dev = &info->client->dev; + int ichrg, ieoc_percentage, ret; + + ret = rt9455_get_field_val(info, F_ICHRG, + rt9455_ichrg_values, + ARRAY_SIZE(rt9455_ichrg_values), + &ichrg); + if (ret) { + dev_err(dev, "Failed to read ICHRG value\n"); + return ret; + } + + ret = rt9455_get_field_val(info, F_IEOC_PERCENTAGE, + rt9455_ieoc_percentage_values, + ARRAY_SIZE(rt9455_ieoc_percentage_values), + &ieoc_percentage); + if (ret) { + dev_err(dev, "Failed to read IEOC value\n"); + return ret; + } + + val->intval = ichrg * ieoc_percentage / 100; + + return 0; +} + +static int rt9455_charger_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct rt9455_info *info = power_supply_get_drvdata(psy); + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + return rt9455_charger_get_status(info, val); + case POWER_SUPPLY_PROP_HEALTH: + return rt9455_charger_get_health(info, val); + case POWER_SUPPLY_PROP_PRESENT: + return rt9455_charger_get_battery_presence(info, val); + case POWER_SUPPLY_PROP_ONLINE: + return rt9455_charger_get_online(info, val); + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: + return rt9455_charger_get_current(info, val); + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX: + return rt9455_charger_get_current_max(info, val); + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: + return rt9455_charger_get_voltage(info, val); + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX: + return rt9455_charger_get_voltage_max(info, val); + case POWER_SUPPLY_PROP_SCOPE: + val->intval = POWER_SUPPLY_SCOPE_SYSTEM; + return 0; + case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT: + return rt9455_charger_get_term_current(info, val); + case POWER_SUPPLY_PROP_MODEL_NAME: + val->strval = RT9455_MODEL_NAME; + return 0; + case POWER_SUPPLY_PROP_MANUFACTURER: + val->strval = RT9455_MANUFACTURER; + return 0; + default: + return -ENODATA; + } +} + +static int rt9455_hw_init(struct rt9455_info *info, u32 ichrg, + u32 ieoc_percentage, + u32 mivr, u32 iaicr) +{ + struct device *dev = &info->client->dev; + int idx, ret; + + ret = rt9455_register_reset(info); + if (ret) { + dev_err(dev, "Power On Reset failed\n"); + return ret; + } + + /* Set TE bit in order to enable end of charge detection */ + ret = regmap_field_write(info->regmap_fields[F_TE], 1); + if (ret) { + dev_err(dev, "Failed to set TE bit\n"); + return ret; + } + + /* Set TE_SHDN_EN bit in order to enable end of charge detection */ + ret = regmap_field_write(info->regmap_fields[F_TE_SHDN_EN], 1); + if (ret) { + dev_err(dev, "Failed to set TE_SHDN_EN bit\n"); + return ret; + } + + /* + * Set BATD_EN bit in order to enable battery detection + * when charging is done + */ + ret = regmap_field_write(info->regmap_fields[F_BATD_EN], 1); + if (ret) { + dev_err(dev, "Failed to set BATD_EN bit\n"); + return ret; + } + + /* + * Disable Safety Timer. In charge mode, this timer terminates charging + * if no read or write via I2C is done within 32 minutes. This timer + * avoids overcharging the baterry when the OS is not loaded and the + * charger is connected to a power source. + * In boost mode, this timer triggers BST32SI interrupt if no read or + * write via I2C is done within 32 seconds. + * When the OS is loaded and the charger driver is inserted, it is used + * delayed_work, named max_charging_time_work, to avoid overcharging + * the battery. + */ + ret = regmap_field_write(info->regmap_fields[F_TMR_EN], 0x00); + if (ret) { + dev_err(dev, "Failed to disable Safety Timer\n"); + return ret; + } + + /* Set ICHRG to value retrieved from device-specific data */ + ret = rt9455_set_field_val(info, F_ICHRG, + rt9455_ichrg_values, + ARRAY_SIZE(rt9455_ichrg_values), ichrg); + if (ret) { + dev_err(dev, "Failed to set ICHRG value\n"); + return ret; + } + + /* Set IEOC Percentage to value retrieved from device-specific data */ + ret = rt9455_set_field_val(info, F_IEOC_PERCENTAGE, + rt9455_ieoc_percentage_values, + ARRAY_SIZE(rt9455_ieoc_percentage_values), + ieoc_percentage); + if (ret) { + dev_err(dev, "Failed to set IEOC Percentage value\n"); + return ret; + } + + /* Set VOREG to value retrieved from device-specific data */ + ret = rt9455_set_field_val(info, F_VOREG, + rt9455_voreg_values, + ARRAY_SIZE(rt9455_voreg_values), + info->voreg); + if (ret) { + dev_err(dev, "Failed to set VOREG value\n"); + return ret; + } + + /* Set VMREG value to maximum (4.45V). */ + idx = ARRAY_SIZE(rt9455_vmreg_values) - 1; + ret = rt9455_set_field_val(info, F_VMREG, + rt9455_vmreg_values, + ARRAY_SIZE(rt9455_vmreg_values), + rt9455_vmreg_values[idx]); + if (ret) { + dev_err(dev, "Failed to set VMREG value\n"); + return ret; + } + + /* + * Set MIVR to value retrieved from device-specific data. + * If no value is specified, default value for MIVR is 4.5V. + */ + if (mivr == -1) + mivr = 4500000; + + ret = rt9455_set_field_val(info, F_MIVR, + rt9455_mivr_values, + ARRAY_SIZE(rt9455_mivr_values), mivr); + if (ret) { + dev_err(dev, "Failed to set MIVR value\n"); + return ret; + } + + /* + * Set IAICR to value retrieved from device-specific data. + * If no value is specified, default value for IAICR is 500 mA. + */ + if (iaicr == -1) + iaicr = 500000; + + ret = rt9455_set_field_val(info, F_IAICR, + rt9455_iaicr_values, + ARRAY_SIZE(rt9455_iaicr_values), iaicr); + if (ret) { + dev_err(dev, "Failed to set IAICR value\n"); + return ret; + } + + /* + * Set IAICR_INT bit so that IAICR value is determined by IAICR bits + * and not by OTG pin. + */ + ret = regmap_field_write(info->regmap_fields[F_IAICR_INT], 0x01); + if (ret) { + dev_err(dev, "Failed to set IAICR_INT bit\n"); + return ret; + } + + /* + * Disable CHMIVRI interrupt. Because the driver sets MIVR value, + * CHMIVRI is triggered, but there is no action to be taken by the + * driver when CHMIVRI is triggered. + */ + ret = regmap_field_write(info->regmap_fields[F_CHMIVRIM], 0x01); + if (ret) { + dev_err(dev, "Failed to mask CHMIVRI interrupt\n"); + return ret; + } + + return 0; +} + +#if IS_ENABLED(CONFIG_USB_PHY) +/* + * Before setting the charger into boost mode, boost output voltage is + * set. This is needed because boost output voltage may differ from battery + * regulation voltage. F_VOREG bits represent either battery regulation voltage + * or boost output voltage, depending on the mode the charger is. Both battery + * regulation voltage and boost output voltage are read from DT/ACPI during + * probe. + */ +static int rt9455_set_boost_voltage_before_boost_mode(struct rt9455_info *info) +{ + struct device *dev = &info->client->dev; + int ret; + + ret = rt9455_set_field_val(info, F_VOREG, + rt9455_boost_voltage_values, + ARRAY_SIZE(rt9455_boost_voltage_values), + info->boost_voltage); + if (ret) { + dev_err(dev, "Failed to set boost output voltage value\n"); + return ret; + } + + return 0; +} +#endif + +/* + * Before setting the charger into charge mode, battery regulation voltage is + * set. This is needed because boost output voltage may differ from battery + * regulation voltage. F_VOREG bits represent either battery regulation voltage + * or boost output voltage, depending on the mode the charger is. Both battery + * regulation voltage and boost output voltage are read from DT/ACPI during + * probe. + */ +static int rt9455_set_voreg_before_charge_mode(struct rt9455_info *info) +{ + struct device *dev = &info->client->dev; + int ret; + + ret = rt9455_set_field_val(info, F_VOREG, + rt9455_voreg_values, + ARRAY_SIZE(rt9455_voreg_values), + info->voreg); + if (ret) { + dev_err(dev, "Failed to set VOREG value\n"); + return ret; + } + + return 0; +} + +static int rt9455_irq_handler_check_irq1_register(struct rt9455_info *info, + bool *_is_battery_absent, + bool *_alert_userspace) +{ + unsigned int irq1, mask1, mask2; + struct device *dev = &info->client->dev; + bool is_battery_absent = false; + bool alert_userspace = false; + int ret; + + ret = regmap_read(info->regmap, RT9455_REG_IRQ1, &irq1); + if (ret) { + dev_err(dev, "Failed to read IRQ1 register\n"); + return ret; + } + + ret = regmap_read(info->regmap, RT9455_REG_MASK1, &mask1); + if (ret) { + dev_err(dev, "Failed to read MASK1 register\n"); + return ret; + } + + if (irq1 & GET_MASK(F_TSDI)) { + dev_err(dev, "Thermal shutdown fault occurred\n"); + alert_userspace = true; + } + + if (irq1 & GET_MASK(F_VINOVPI)) { + dev_err(dev, "Overvoltage input occurred\n"); + alert_userspace = true; + } + + if (irq1 & GET_MASK(F_BATAB)) { + dev_err(dev, "Battery absence occurred\n"); + is_battery_absent = true; + alert_userspace = true; + + if ((mask1 & GET_MASK(F_BATABM)) == 0) { + ret = regmap_field_write(info->regmap_fields[F_BATABM], + 0x01); + if (ret) { + dev_err(dev, "Failed to mask BATAB interrupt\n"); + return ret; + } + } + + ret = regmap_read(info->regmap, RT9455_REG_MASK2, &mask2); + if (ret) { + dev_err(dev, "Failed to read MASK2 register\n"); + return ret; + } + + if (mask2 & GET_MASK(F_CHTERMIM)) { + ret = regmap_field_write( + info->regmap_fields[F_CHTERMIM], 0x00); + if (ret) { + dev_err(dev, "Failed to unmask CHTERMI interrupt\n"); + return ret; + } + } + + if (mask2 & GET_MASK(F_CHRCHGIM)) { + ret = regmap_field_write( + info->regmap_fields[F_CHRCHGIM], 0x00); + if (ret) { + dev_err(dev, "Failed to unmask CHRCHGI interrupt\n"); + return ret; + } + } + + /* + * When the battery is absent, max_charging_time_work is + * cancelled, since no charging is done. + */ + cancel_delayed_work_sync(&info->max_charging_time_work); + /* + * Since no interrupt is triggered when the battery is + * reconnected, max_charging_time_work is not rescheduled. + * Therefore, batt_presence_work is scheduled to check whether + * the battery is still absent or not. + */ + queue_delayed_work(system_power_efficient_wq, + &info->batt_presence_work, + RT9455_BATT_PRESENCE_DELAY * HZ); + } + + *_is_battery_absent = is_battery_absent; + + if (alert_userspace) + *_alert_userspace = alert_userspace; + + return 0; +} + +static int rt9455_irq_handler_check_irq2_register(struct rt9455_info *info, + bool is_battery_absent, + bool *_alert_userspace) +{ + unsigned int irq2, mask2; + struct device *dev = &info->client->dev; + bool alert_userspace = false; + int ret; + + ret = regmap_read(info->regmap, RT9455_REG_IRQ2, &irq2); + if (ret) { + dev_err(dev, "Failed to read IRQ2 register\n"); + return ret; + } + + ret = regmap_read(info->regmap, RT9455_REG_MASK2, &mask2); + if (ret) { + dev_err(dev, "Failed to read MASK2 register\n"); + return ret; + } + + if (irq2 & GET_MASK(F_CHRVPI)) { + dev_dbg(dev, "Charger fault occurred\n"); + /* + * CHRVPI bit is set in 2 cases: + * 1. when the power source is connected to the charger. + * 2. when the power source is disconnected from the charger. + * To identify the case, PWR_RDY bit is checked. Because + * PWR_RDY bit is set / cleared after CHRVPI interrupt is + * triggered, it is used delayed_work to later read PWR_RDY bit. + * Also, do not set to true alert_userspace, because there is no + * need to notify userspace when CHRVPI interrupt has occurred. + * Userspace will be notified after PWR_RDY bit is read. + */ + queue_delayed_work(system_power_efficient_wq, + &info->pwr_rdy_work, + RT9455_PWR_RDY_DELAY * HZ); + } + if (irq2 & GET_MASK(F_CHBATOVI)) { + dev_err(dev, "Battery OVP occurred\n"); + alert_userspace = true; + } + if (irq2 & GET_MASK(F_CHTERMI)) { + dev_dbg(dev, "Charge terminated\n"); + if (!is_battery_absent) { + if ((mask2 & GET_MASK(F_CHTERMIM)) == 0) { + ret = regmap_field_write( + info->regmap_fields[F_CHTERMIM], 0x01); + if (ret) { + dev_err(dev, "Failed to mask CHTERMI interrupt\n"); + return ret; + } + /* + * Update MASK2 value, since CHTERMIM bit is + * set. + */ + mask2 = mask2 | GET_MASK(F_CHTERMIM); + } + cancel_delayed_work_sync(&info->max_charging_time_work); + alert_userspace = true; + } + } + if (irq2 & GET_MASK(F_CHRCHGI)) { + dev_dbg(dev, "Recharge request\n"); + ret = regmap_field_write(info->regmap_fields[F_CHG_EN], + RT9455_CHARGE_ENABLE); + if (ret) { + dev_err(dev, "Failed to enable charging\n"); + return ret; + } + if (mask2 & GET_MASK(F_CHTERMIM)) { + ret = regmap_field_write( + info->regmap_fields[F_CHTERMIM], 0x00); + if (ret) { + dev_err(dev, "Failed to unmask CHTERMI interrupt\n"); + return ret; + } + /* Update MASK2 value, since CHTERMIM bit is cleared. */ + mask2 = mask2 & ~GET_MASK(F_CHTERMIM); + } + if (!is_battery_absent) { + /* + * No need to check whether the charger is connected to + * power source when CHRCHGI is received, since CHRCHGI + * is not triggered if the charger is not connected to + * the power source. + */ + queue_delayed_work(system_power_efficient_wq, + &info->max_charging_time_work, + RT9455_MAX_CHARGING_TIME * HZ); + alert_userspace = true; + } + } + if (irq2 & GET_MASK(F_CH32MI)) { + dev_err(dev, "Charger fault. 32 mins timeout occurred\n"); + alert_userspace = true; + } + if (irq2 & GET_MASK(F_CHTREGI)) { + dev_warn(dev, + "Charger warning. Thermal regulation loop active\n"); + alert_userspace = true; + } + if (irq2 & GET_MASK(F_CHMIVRI)) { + dev_dbg(dev, + "Charger warning. Input voltage MIVR loop active\n"); + } + + if (alert_userspace) + *_alert_userspace = alert_userspace; + + return 0; +} + +static int rt9455_irq_handler_check_irq3_register(struct rt9455_info *info, + bool *_alert_userspace) +{ + unsigned int irq3, mask3; + struct device *dev = &info->client->dev; + bool alert_userspace = false; + int ret; + + ret = regmap_read(info->regmap, RT9455_REG_IRQ3, &irq3); + if (ret) { + dev_err(dev, "Failed to read IRQ3 register\n"); + return ret; + } + + ret = regmap_read(info->regmap, RT9455_REG_MASK3, &mask3); + if (ret) { + dev_err(dev, "Failed to read MASK3 register\n"); + return ret; + } + + if (irq3 & GET_MASK(F_BSTBUSOVI)) { + dev_err(dev, "Boost fault. Overvoltage input occurred\n"); + alert_userspace = true; + } + if (irq3 & GET_MASK(F_BSTOLI)) { + dev_err(dev, "Boost fault. Overload\n"); + alert_userspace = true; + } + if (irq3 & GET_MASK(F_BSTLOWVI)) { + dev_err(dev, "Boost fault. Battery voltage too low\n"); + alert_userspace = true; + } + if (irq3 & GET_MASK(F_BST32SI)) { + dev_err(dev, "Boost fault. 32 seconds timeout occurred.\n"); + alert_userspace = true; + } + + if (alert_userspace) { + dev_info(dev, "Boost fault occurred, therefore the charger goes into charge mode\n"); + ret = rt9455_set_voreg_before_charge_mode(info); + if (ret) { + dev_err(dev, "Failed to set VOREG before entering charge mode\n"); + return ret; + } + ret = regmap_field_write(info->regmap_fields[F_OPA_MODE], + RT9455_CHARGE_MODE); + if (ret) { + dev_err(dev, "Failed to set charger in charge mode\n"); + return ret; + } + *_alert_userspace = alert_userspace; + } + + return 0; +} + +static irqreturn_t rt9455_irq_handler_thread(int irq, void *data) +{ + struct rt9455_info *info = data; + struct device *dev; + bool alert_userspace = false; + bool is_battery_absent = false; + unsigned int status; + int ret; + + if (!info) + return IRQ_NONE; + + dev = &info->client->dev; + + if (irq != info->client->irq) { + dev_err(dev, "Interrupt is not for RT9455 charger\n"); + return IRQ_NONE; + } + + ret = regmap_field_read(info->regmap_fields[F_STAT], &status); + if (ret) { + dev_err(dev, "Failed to read STAT bits\n"); + return IRQ_HANDLED; + } + dev_dbg(dev, "Charger status is %d\n", status); + + /* + * Each function that processes an IRQ register receives as output + * parameter alert_userspace pointer. alert_userspace is set to true + * in such a function only if an interrupt has occurred in the + * respective interrupt register. This way, it is avoided the following + * case: interrupt occurs only in IRQ1 register, + * rt9455_irq_handler_check_irq1_register() function sets to true + * alert_userspace, but rt9455_irq_handler_check_irq2_register() + * and rt9455_irq_handler_check_irq3_register() functions set to false + * alert_userspace and power_supply_changed() is never called. + */ + ret = rt9455_irq_handler_check_irq1_register(info, &is_battery_absent, + &alert_userspace); + if (ret) { + dev_err(dev, "Failed to handle IRQ1 register\n"); + return IRQ_HANDLED; + } + + ret = rt9455_irq_handler_check_irq2_register(info, is_battery_absent, + &alert_userspace); + if (ret) { + dev_err(dev, "Failed to handle IRQ2 register\n"); + return IRQ_HANDLED; + } + + ret = rt9455_irq_handler_check_irq3_register(info, &alert_userspace); + if (ret) { + dev_err(dev, "Failed to handle IRQ3 register\n"); + return IRQ_HANDLED; + } + + if (alert_userspace) { + /* + * Sometimes, an interrupt occurs while rt9455_probe() function + * is executing and power_supply_register() is not yet called. + * Do not call power_supply_changed() in this case. + */ + if (info->charger) + power_supply_changed(info->charger); + } + + return IRQ_HANDLED; +} + +static int rt9455_discover_charger(struct rt9455_info *info, u32 *ichrg, + u32 *ieoc_percentage, + u32 *mivr, u32 *iaicr) +{ + struct device *dev = &info->client->dev; + int ret; + + if (!dev->of_node && !ACPI_HANDLE(dev)) { + dev_err(dev, "No support for either device tree or ACPI\n"); + return -EINVAL; + } + /* + * ICHRG, IEOC_PERCENTAGE, VOREG and boost output voltage are mandatory + * parameters. + */ + ret = device_property_read_u32(dev, "richtek,output-charge-current", + ichrg); + if (ret) { + dev_err(dev, "Error: missing \"output-charge-current\" property\n"); + return ret; + } + + ret = device_property_read_u32(dev, "richtek,end-of-charge-percentage", + ieoc_percentage); + if (ret) { + dev_err(dev, "Error: missing \"end-of-charge-percentage\" property\n"); + return ret; + } + + ret = device_property_read_u32(dev, + "richtek,battery-regulation-voltage", + &info->voreg); + if (ret) { + dev_err(dev, "Error: missing \"battery-regulation-voltage\" property\n"); + return ret; + } + + ret = device_property_read_u32(dev, "richtek,boost-output-voltage", + &info->boost_voltage); + if (ret) { + dev_err(dev, "Error: missing \"boost-output-voltage\" property\n"); + return ret; + } + + /* + * MIVR and IAICR are optional parameters. Do not return error if one of + * them is not present in ACPI table or device tree specification. + */ + device_property_read_u32(dev, "richtek,min-input-voltage-regulation", + mivr); + device_property_read_u32(dev, "richtek,avg-input-current-regulation", + iaicr); + + return 0; +} + +#if IS_ENABLED(CONFIG_USB_PHY) +static int rt9455_usb_event_none(struct rt9455_info *info, + u8 opa_mode, u8 iaicr) +{ + struct device *dev = &info->client->dev; + int ret; + + if (opa_mode == RT9455_BOOST_MODE) { + ret = rt9455_set_voreg_before_charge_mode(info); + if (ret) { + dev_err(dev, "Failed to set VOREG before entering charge mode\n"); + return ret; + } + /* + * If the charger is in boost mode, and it has received + * USB_EVENT_NONE, this means the consumer device powered by the + * charger is not connected anymore. + * In this case, the charger goes into charge mode. + */ + dev_dbg(dev, "USB_EVENT_NONE received, therefore the charger goes into charge mode\n"); + ret = regmap_field_write(info->regmap_fields[F_OPA_MODE], + RT9455_CHARGE_MODE); + if (ret) { + dev_err(dev, "Failed to set charger in charge mode\n"); + return NOTIFY_DONE; + } + } + + dev_dbg(dev, "USB_EVENT_NONE received, therefore IAICR is set to its minimum value\n"); + if (iaicr != RT9455_IAICR_100MA) { + ret = regmap_field_write(info->regmap_fields[F_IAICR], + RT9455_IAICR_100MA); + if (ret) { + dev_err(dev, "Failed to set IAICR value\n"); + return NOTIFY_DONE; + } + } + + return NOTIFY_OK; +} + +static int rt9455_usb_event_vbus(struct rt9455_info *info, + u8 opa_mode, u8 iaicr) +{ + struct device *dev = &info->client->dev; + int ret; + + if (opa_mode == RT9455_BOOST_MODE) { + ret = rt9455_set_voreg_before_charge_mode(info); + if (ret) { + dev_err(dev, "Failed to set VOREG before entering charge mode\n"); + return ret; + } + /* + * If the charger is in boost mode, and it has received + * USB_EVENT_VBUS, this means the consumer device powered by the + * charger is not connected anymore. + * In this case, the charger goes into charge mode. + */ + dev_dbg(dev, "USB_EVENT_VBUS received, therefore the charger goes into charge mode\n"); + ret = regmap_field_write(info->regmap_fields[F_OPA_MODE], + RT9455_CHARGE_MODE); + if (ret) { + dev_err(dev, "Failed to set charger in charge mode\n"); + return NOTIFY_DONE; + } + } + + dev_dbg(dev, "USB_EVENT_VBUS received, therefore IAICR is set to 500 mA\n"); + if (iaicr != RT9455_IAICR_500MA) { + ret = regmap_field_write(info->regmap_fields[F_IAICR], + RT9455_IAICR_500MA); + if (ret) { + dev_err(dev, "Failed to set IAICR value\n"); + return NOTIFY_DONE; + } + } + + return NOTIFY_OK; +} + +static int rt9455_usb_event_id(struct rt9455_info *info, + u8 opa_mode, u8 iaicr) +{ + struct device *dev = &info->client->dev; + int ret; + + if (opa_mode == RT9455_CHARGE_MODE) { + ret = rt9455_set_boost_voltage_before_boost_mode(info); + if (ret) { + dev_err(dev, "Failed to set boost output voltage before entering boost mode\n"); + return ret; + } + /* + * If the charger is in charge mode, and it has received + * USB_EVENT_ID, this means a consumer device is connected and + * it should be powered by the charger. + * In this case, the charger goes into boost mode. + */ + dev_dbg(dev, "USB_EVENT_ID received, therefore the charger goes into boost mode\n"); + ret = regmap_field_write(info->regmap_fields[F_OPA_MODE], + RT9455_BOOST_MODE); + if (ret) { + dev_err(dev, "Failed to set charger in boost mode\n"); + return NOTIFY_DONE; + } + } + + dev_dbg(dev, "USB_EVENT_ID received, therefore IAICR is set to its minimum value\n"); + if (iaicr != RT9455_IAICR_100MA) { + ret = regmap_field_write(info->regmap_fields[F_IAICR], + RT9455_IAICR_100MA); + if (ret) { + dev_err(dev, "Failed to set IAICR value\n"); + return NOTIFY_DONE; + } + } + + return NOTIFY_OK; +} + +static int rt9455_usb_event_charger(struct rt9455_info *info, + u8 opa_mode, u8 iaicr) +{ + struct device *dev = &info->client->dev; + int ret; + + if (opa_mode == RT9455_BOOST_MODE) { + ret = rt9455_set_voreg_before_charge_mode(info); + if (ret) { + dev_err(dev, "Failed to set VOREG before entering charge mode\n"); + return ret; + } + /* + * If the charger is in boost mode, and it has received + * USB_EVENT_CHARGER, this means the consumer device powered by + * the charger is not connected anymore. + * In this case, the charger goes into charge mode. + */ + dev_dbg(dev, "USB_EVENT_CHARGER received, therefore the charger goes into charge mode\n"); + ret = regmap_field_write(info->regmap_fields[F_OPA_MODE], + RT9455_CHARGE_MODE); + if (ret) { + dev_err(dev, "Failed to set charger in charge mode\n"); + return NOTIFY_DONE; + } + } + + dev_dbg(dev, "USB_EVENT_CHARGER received, therefore IAICR is set to no current limit\n"); + if (iaicr != RT9455_IAICR_NO_LIMIT) { + ret = regmap_field_write(info->regmap_fields[F_IAICR], + RT9455_IAICR_NO_LIMIT); + if (ret) { + dev_err(dev, "Failed to set IAICR value\n"); + return NOTIFY_DONE; + } + } + + return NOTIFY_OK; +} + +static int rt9455_usb_event(struct notifier_block *nb, + unsigned long event, void *power) +{ + struct rt9455_info *info = container_of(nb, struct rt9455_info, nb); + struct device *dev = &info->client->dev; + unsigned int opa_mode, iaicr; + int ret; + + /* + * Determine whether the charger is in charge mode + * or in boost mode. + */ + ret = regmap_field_read(info->regmap_fields[F_OPA_MODE], + &opa_mode); + if (ret) { + dev_err(dev, "Failed to read OPA_MODE value\n"); + return NOTIFY_DONE; + } + + ret = regmap_field_read(info->regmap_fields[F_IAICR], + &iaicr); + if (ret) { + dev_err(dev, "Failed to read IAICR value\n"); + return NOTIFY_DONE; + } + + dev_dbg(dev, "Received USB event %lu\n", event); + switch (event) { + case USB_EVENT_NONE: + return rt9455_usb_event_none(info, opa_mode, iaicr); + case USB_EVENT_VBUS: + return rt9455_usb_event_vbus(info, opa_mode, iaicr); + case USB_EVENT_ID: + return rt9455_usb_event_id(info, opa_mode, iaicr); + case USB_EVENT_CHARGER: + return rt9455_usb_event_charger(info, opa_mode, iaicr); + default: + dev_err(dev, "Unknown USB event\n"); + } + return NOTIFY_DONE; +} +#endif + +static void rt9455_pwr_rdy_work_callback(struct work_struct *work) +{ + struct rt9455_info *info = container_of(work, struct rt9455_info, + pwr_rdy_work.work); + struct device *dev = &info->client->dev; + unsigned int pwr_rdy; + int ret; + + ret = regmap_field_read(info->regmap_fields[F_PWR_RDY], &pwr_rdy); + if (ret) { + dev_err(dev, "Failed to read PWR_RDY bit\n"); + return; + } + switch (pwr_rdy) { + case RT9455_PWR_FAULT: + dev_dbg(dev, "Charger disconnected from power source\n"); + cancel_delayed_work_sync(&info->max_charging_time_work); + break; + case RT9455_PWR_GOOD: + dev_dbg(dev, "Charger connected to power source\n"); + ret = regmap_field_write(info->regmap_fields[F_CHG_EN], + RT9455_CHARGE_ENABLE); + if (ret) { + dev_err(dev, "Failed to enable charging\n"); + return; + } + queue_delayed_work(system_power_efficient_wq, + &info->max_charging_time_work, + RT9455_MAX_CHARGING_TIME * HZ); + break; + } + /* + * Notify userspace that the charger has been either connected to or + * disconnected from the power source. + */ + power_supply_changed(info->charger); +} + +static void rt9455_max_charging_time_work_callback(struct work_struct *work) +{ + struct rt9455_info *info = container_of(work, struct rt9455_info, + max_charging_time_work.work); + struct device *dev = &info->client->dev; + int ret; + + dev_err(dev, "Battery has been charging for at least 6 hours and is not yet fully charged. Battery is dead, therefore charging is disabled.\n"); + ret = regmap_field_write(info->regmap_fields[F_CHG_EN], + RT9455_CHARGE_DISABLE); + if (ret) + dev_err(dev, "Failed to disable charging\n"); +} + +static void rt9455_batt_presence_work_callback(struct work_struct *work) +{ + struct rt9455_info *info = container_of(work, struct rt9455_info, + batt_presence_work.work); + struct device *dev = &info->client->dev; + unsigned int irq1, mask1; + int ret; + + ret = regmap_read(info->regmap, RT9455_REG_IRQ1, &irq1); + if (ret) { + dev_err(dev, "Failed to read IRQ1 register\n"); + return; + } + + /* + * If the battery is still absent, batt_presence_work is rescheduled. + * Otherwise, max_charging_time is scheduled. + */ + if (irq1 & GET_MASK(F_BATAB)) { + queue_delayed_work(system_power_efficient_wq, + &info->batt_presence_work, + RT9455_BATT_PRESENCE_DELAY * HZ); + } else { + queue_delayed_work(system_power_efficient_wq, + &info->max_charging_time_work, + RT9455_MAX_CHARGING_TIME * HZ); + + ret = regmap_read(info->regmap, RT9455_REG_MASK1, &mask1); + if (ret) { + dev_err(dev, "Failed to read MASK1 register\n"); + return; + } + + if (mask1 & GET_MASK(F_BATABM)) { + ret = regmap_field_write(info->regmap_fields[F_BATABM], + 0x00); + if (ret) + dev_err(dev, "Failed to unmask BATAB interrupt\n"); + } + /* + * Notify userspace that the battery is now connected to the + * charger. + */ + power_supply_changed(info->charger); + } +} + +static const struct power_supply_desc rt9455_charger_desc = { + .name = RT9455_DRIVER_NAME, + .type = POWER_SUPPLY_TYPE_USB, + .properties = rt9455_charger_properties, + .num_properties = ARRAY_SIZE(rt9455_charger_properties), + .get_property = rt9455_charger_get_property, +}; + +static bool rt9455_is_writeable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case RT9455_REG_DEV_ID: + case RT9455_REG_IRQ1: + case RT9455_REG_IRQ2: + case RT9455_REG_IRQ3: + return false; + default: + return true; + } +} + +static bool rt9455_is_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case RT9455_REG_DEV_ID: + case RT9455_REG_CTRL5: + case RT9455_REG_CTRL6: + return false; + default: + return true; + } +} + +static const struct regmap_config rt9455_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .writeable_reg = rt9455_is_writeable_reg, + .volatile_reg = rt9455_is_volatile_reg, + .max_register = RT9455_REG_MASK3, + .cache_type = REGCACHE_RBTREE, +}; + +static int rt9455_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 rt9455_info *info; + struct power_supply_config rt9455_charger_config = {}; + /* + * Mandatory device-specific data values. Also, VOREG and boost output + * voltage are mandatory values, but they are stored in rt9455_info + * structure. + */ + u32 ichrg, ieoc_percentage; + /* Optional device-specific data values. */ + u32 mivr = -1, iaicr = -1; + int i, ret; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) { + dev_err(dev, "No support for SMBUS_BYTE_DATA\n"); + return -ENODEV; + } + info = devm_kzalloc(dev, sizeof(*info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + info->client = client; + i2c_set_clientdata(client, info); + + info->regmap = devm_regmap_init_i2c(client, + &rt9455_regmap_config); + if (IS_ERR(info->regmap)) { + dev_err(dev, "Failed to initialize register map\n"); + return -EINVAL; + } + + for (i = 0; i < F_MAX_FIELDS; i++) { + info->regmap_fields[i] = + devm_regmap_field_alloc(dev, info->regmap, + rt9455_reg_fields[i]); + if (IS_ERR(info->regmap_fields[i])) { + dev_err(dev, + "Failed to allocate regmap field = %d\n", i); + return PTR_ERR(info->regmap_fields[i]); + } + } + + ret = rt9455_discover_charger(info, &ichrg, &ieoc_percentage, + &mivr, &iaicr); + if (ret) { + dev_err(dev, "Failed to discover charger\n"); + return ret; + } + +#if IS_ENABLED(CONFIG_USB_PHY) + info->usb_phy = devm_usb_get_phy(dev, USB_PHY_TYPE_USB2); + if (IS_ERR(info->usb_phy)) { + dev_err(dev, "Failed to get USB transceiver\n"); + } else { + info->nb.notifier_call = rt9455_usb_event; + ret = usb_register_notifier(info->usb_phy, &info->nb); + if (ret) { + dev_err(dev, "Failed to register USB notifier\n"); + /* + * If usb_register_notifier() fails, set notifier_call + * to NULL, to avoid calling usb_unregister_notifier(). + */ + info->nb.notifier_call = NULL; + } + } +#endif + + INIT_DEFERRABLE_WORK(&info->pwr_rdy_work, rt9455_pwr_rdy_work_callback); + INIT_DEFERRABLE_WORK(&info->max_charging_time_work, + rt9455_max_charging_time_work_callback); + INIT_DEFERRABLE_WORK(&info->batt_presence_work, + rt9455_batt_presence_work_callback); + + rt9455_charger_config.of_node = dev->of_node; + rt9455_charger_config.drv_data = info; + rt9455_charger_config.supplied_to = rt9455_charger_supplied_to; + rt9455_charger_config.num_supplicants = + ARRAY_SIZE(rt9455_charger_supplied_to); + ret = devm_request_threaded_irq(dev, client->irq, NULL, + rt9455_irq_handler_thread, + IRQF_TRIGGER_LOW | IRQF_ONESHOT, + RT9455_DRIVER_NAME, info); + if (ret) { + dev_err(dev, "Failed to register IRQ handler\n"); + goto put_usb_notifier; + } + + ret = rt9455_hw_init(info, ichrg, ieoc_percentage, mivr, iaicr); + if (ret) { + dev_err(dev, "Failed to set charger to its default values\n"); + goto put_usb_notifier; + } + + info->charger = devm_power_supply_register(dev, &rt9455_charger_desc, + &rt9455_charger_config); + if (IS_ERR(info->charger)) { + dev_err(dev, "Failed to register charger\n"); + ret = PTR_ERR(info->charger); + goto put_usb_notifier; + } + + return 0; + +put_usb_notifier: +#if IS_ENABLED(CONFIG_USB_PHY) + if (info->nb.notifier_call) { + usb_unregister_notifier(info->usb_phy, &info->nb); + info->nb.notifier_call = NULL; + } +#endif + return ret; +} + +static int rt9455_remove(struct i2c_client *client) +{ + int ret; + struct rt9455_info *info = i2c_get_clientdata(client); + + ret = rt9455_register_reset(info); + if (ret) + dev_err(&info->client->dev, "Failed to set charger to its default values\n"); + +#if IS_ENABLED(CONFIG_USB_PHY) + if (info->nb.notifier_call) + usb_unregister_notifier(info->usb_phy, &info->nb); +#endif + + cancel_delayed_work_sync(&info->pwr_rdy_work); + cancel_delayed_work_sync(&info->max_charging_time_work); + cancel_delayed_work_sync(&info->batt_presence_work); + + return ret; +} + +static const struct i2c_device_id rt9455_i2c_id_table[] = { + { RT9455_DRIVER_NAME, 0 }, + { }, +}; +MODULE_DEVICE_TABLE(i2c, rt9455_i2c_id_table); + +static const struct of_device_id rt9455_of_match[] = { + { .compatible = "richtek,rt9455", }, + { }, +}; +MODULE_DEVICE_TABLE(of, rt9455_of_match); + +static const struct acpi_device_id rt9455_i2c_acpi_match[] = { + { "RT945500", 0 }, + { } +}; +MODULE_DEVICE_TABLE(acpi, rt9455_i2c_acpi_match); + +static struct i2c_driver rt9455_driver = { + .probe = rt9455_probe, + .remove = rt9455_remove, + .id_table = rt9455_i2c_id_table, + .driver = { + .name = RT9455_DRIVER_NAME, + .of_match_table = of_match_ptr(rt9455_of_match), + .acpi_match_table = ACPI_PTR(rt9455_i2c_acpi_match), + }, +}; +module_i2c_driver(rt9455_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Anda-Maria Nicolae "); +MODULE_DESCRIPTION("Richtek RT9455 Charger Driver"); diff --git a/drivers/power/supply/rx51_battery.c b/drivers/power/supply/rx51_battery.c new file mode 100644 index 000000000000..af9383d23d12 --- /dev/null +++ b/drivers/power/supply/rx51_battery.c @@ -0,0 +1,297 @@ +/* + * Nokia RX-51 battery driver + * + * Copyright (C) 2012 Pali Rohár + * + * 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 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +struct rx51_device_info { + struct device *dev; + struct power_supply *bat; + struct power_supply_desc bat_desc; + struct iio_channel *channel_temp; + struct iio_channel *channel_bsi; + struct iio_channel *channel_vbat; +}; + +/* + * Read ADCIN channel value, code copied from maemo kernel + */ +static int rx51_battery_read_adc(struct iio_channel *channel) +{ + int val, err; + err = iio_read_channel_average_raw(channel, &val); + if (err < 0) + return err; + return val; +} + +/* + * Read ADCIN channel 12 (voltage) and convert RAW value to micro voltage + * This conversion formula was extracted from maemo program bsi-read + */ +static int rx51_battery_read_voltage(struct rx51_device_info *di) +{ + int voltage = rx51_battery_read_adc(di->channel_vbat); + + if (voltage < 0) { + dev_err(di->dev, "Could not read ADC: %d\n", voltage); + return voltage; + } + + return 1000 * (10000 * voltage / 1705); +} + +/* + * Temperature look-up tables + * TEMP = (1/(t1 + 1/298) - 273.15) + * Where t1 = (1/B) * ln((RAW_ADC_U * 2.5)/(R * I * 255)) + * Formula is based on experimental data, RX-51 CAL data, maemo program bme + * and formula from da9052 driver with values R = 100, B = 3380, I = 0.00671 + */ + +/* + * Table1 (temperature for first 25 RAW values) + * Usage: TEMP = rx51_temp_table1[RAW] + * RAW is between 1 and 24 + * TEMP is between 201 C and 55 C + */ +static u8 rx51_temp_table1[] = { + 255, 201, 159, 138, 124, 114, 106, 99, 94, 89, 85, 82, 78, 75, + 73, 70, 68, 66, 64, 62, 61, 59, 57, 56, 55 +}; + +/* + * Table2 (lowest RAW value for temperature) + * Usage: RAW = rx51_temp_table2[TEMP-rx51_temp_table2_first] + * TEMP is between 53 C and -32 C + * RAW is between 25 and 993 + */ +#define rx51_temp_table2_first 53 +static u16 rx51_temp_table2[] = { + 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 39, + 40, 41, 43, 44, 46, 48, 49, 51, 53, 55, 57, 59, 61, 64, + 66, 69, 71, 74, 77, 80, 83, 86, 90, 94, 97, 101, 106, 110, + 115, 119, 125, 130, 136, 141, 148, 154, 161, 168, 176, 184, 202, 211, + 221, 231, 242, 254, 266, 279, 293, 308, 323, 340, 357, 375, 395, 415, + 437, 460, 485, 511, 539, 568, 600, 633, 669, 706, 747, 790, 836, 885, + 937, 993, 1024 +}; + +/* + * Read ADCIN channel 0 (battery temp) and convert value to tenths of Celsius + * Use Temperature look-up tables for conversation + */ +static int rx51_battery_read_temperature(struct rx51_device_info *di) +{ + int min = 0; + int max = ARRAY_SIZE(rx51_temp_table2) - 1; + int raw = rx51_battery_read_adc(di->channel_temp); + + if (raw < 0) + dev_err(di->dev, "Could not read ADC: %d\n", raw); + + /* Zero and negative values are undefined */ + if (raw <= 0) + return INT_MAX; + + /* ADC channels are 10 bit, higher value are undefined */ + if (raw >= (1 << 10)) + return INT_MIN; + + /* First check for temperature in first direct table */ + if (raw < ARRAY_SIZE(rx51_temp_table1)) + return rx51_temp_table1[raw] * 10; + + /* Binary search RAW value in second inverse table */ + while (max - min > 1) { + int mid = (max + min) / 2; + if (rx51_temp_table2[mid] <= raw) + min = mid; + else if (rx51_temp_table2[mid] > raw) + max = mid; + if (rx51_temp_table2[mid] == raw) + break; + } + + return (rx51_temp_table2_first - min) * 10; +} + +/* + * Read ADCIN channel 4 (BSI) and convert RAW value to micro Ah + * This conversion formula was extracted from maemo program bsi-read + */ +static int rx51_battery_read_capacity(struct rx51_device_info *di) +{ + int capacity = rx51_battery_read_adc(di->channel_bsi); + + if (capacity < 0) { + dev_err(di->dev, "Could not read ADC: %d\n", capacity); + return capacity; + } + + return 1280 * (1200 * capacity)/(1024 - capacity); +} + +/* + * Return power_supply property + */ +static int rx51_battery_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct rx51_device_info *di = power_supply_get_drvdata(psy); + + switch (psp) { + case POWER_SUPPLY_PROP_TECHNOLOGY: + val->intval = POWER_SUPPLY_TECHNOLOGY_LION; + break; + case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: + val->intval = 4200000; + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = rx51_battery_read_voltage(di) ? 1 : 0; + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + val->intval = rx51_battery_read_voltage(di); + break; + case POWER_SUPPLY_PROP_TEMP: + val->intval = rx51_battery_read_temperature(di); + break; + case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: + val->intval = rx51_battery_read_capacity(di); + break; + default: + return -EINVAL; + } + + if (val->intval == INT_MAX || val->intval == INT_MIN) + return -EINVAL; + + return 0; +} + +static enum power_supply_property rx51_battery_props[] = { + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_TEMP, + POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, +}; + +static int rx51_battery_probe(struct platform_device *pdev) +{ + struct power_supply_config psy_cfg = {}; + struct rx51_device_info *di; + int ret; + + di = devm_kzalloc(&pdev->dev, sizeof(*di), GFP_KERNEL); + if (!di) + return -ENOMEM; + + platform_set_drvdata(pdev, di); + + di->dev = &pdev->dev; + di->bat_desc.name = "rx51-battery"; + di->bat_desc.type = POWER_SUPPLY_TYPE_BATTERY; + di->bat_desc.properties = rx51_battery_props; + di->bat_desc.num_properties = ARRAY_SIZE(rx51_battery_props); + di->bat_desc.get_property = rx51_battery_get_property; + + psy_cfg.drv_data = di; + + di->channel_temp = iio_channel_get(di->dev, "temp"); + if (IS_ERR(di->channel_temp)) { + ret = PTR_ERR(di->channel_temp); + goto error; + } + + di->channel_bsi = iio_channel_get(di->dev, "bsi"); + if (IS_ERR(di->channel_bsi)) { + ret = PTR_ERR(di->channel_bsi); + goto error_channel_temp; + } + + di->channel_vbat = iio_channel_get(di->dev, "vbat"); + if (IS_ERR(di->channel_vbat)) { + ret = PTR_ERR(di->channel_vbat); + goto error_channel_bsi; + } + + di->bat = power_supply_register(di->dev, &di->bat_desc, &psy_cfg); + if (IS_ERR(di->bat)) { + ret = PTR_ERR(di->bat); + goto error_channel_vbat; + } + + return 0; + +error_channel_vbat: + iio_channel_release(di->channel_vbat); +error_channel_bsi: + iio_channel_release(di->channel_bsi); +error_channel_temp: + iio_channel_release(di->channel_temp); +error: + + return ret; +} + +static int rx51_battery_remove(struct platform_device *pdev) +{ + struct rx51_device_info *di = platform_get_drvdata(pdev); + + power_supply_unregister(di->bat); + + iio_channel_release(di->channel_vbat); + iio_channel_release(di->channel_bsi); + iio_channel_release(di->channel_temp); + + return 0; +} + +#ifdef CONFIG_OF +static const struct of_device_id n900_battery_of_match[] = { + {.compatible = "nokia,n900-battery", }, + { }, +}; +MODULE_DEVICE_TABLE(of, n900_battery_of_match); +#endif + +static struct platform_driver rx51_battery_driver = { + .probe = rx51_battery_probe, + .remove = rx51_battery_remove, + .driver = { + .name = "rx51-battery", + .of_match_table = of_match_ptr(n900_battery_of_match), + }, +}; +module_platform_driver(rx51_battery_driver); + +MODULE_ALIAS("platform:rx51-battery"); +MODULE_AUTHOR("Pali Rohár "); +MODULE_DESCRIPTION("Nokia RX-51 battery driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/power/supply/s3c_adc_battery.c b/drivers/power/supply/s3c_adc_battery.c new file mode 100644 index 000000000000..0ffe5cd3abf6 --- /dev/null +++ b/drivers/power/supply/s3c_adc_battery.c @@ -0,0 +1,459 @@ +/* + * iPAQ h1930/h1940/rx1950 battery controller driver + * Copyright (c) Vasily Khoruzhick + * Based on h1940_battery.c by Arnaud Patard + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file COPYING in the main directory of this archive for + * more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define BAT_POLL_INTERVAL 10000 /* ms */ +#define JITTER_DELAY 500 /* ms */ + +struct s3c_adc_bat { + struct power_supply *psy; + struct s3c_adc_client *client; + struct s3c_adc_bat_pdata *pdata; + int volt_value; + int cur_value; + unsigned int timestamp; + int level; + int status; + int cable_plugged:1; +}; + +static struct delayed_work bat_work; + +static void s3c_adc_bat_ext_power_changed(struct power_supply *psy) +{ + schedule_delayed_work(&bat_work, + msecs_to_jiffies(JITTER_DELAY)); +} + +static int gather_samples(struct s3c_adc_client *client, int num, int channel) +{ + int value, i; + + /* default to 1 if nothing is set */ + if (num < 1) + num = 1; + + value = 0; + for (i = 0; i < num; i++) + value += s3c_adc_read(client, channel); + value /= num; + + return value; +} + +static enum power_supply_property s3c_adc_backup_bat_props[] = { + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_VOLTAGE_MIN, + POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, +}; + +static int s3c_adc_backup_bat_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct s3c_adc_bat *bat = power_supply_get_drvdata(psy); + + if (!bat) { + dev_err(&psy->dev, "%s: no battery infos ?!\n", __func__); + return -EINVAL; + } + + if (bat->volt_value < 0 || + jiffies_to_msecs(jiffies - bat->timestamp) > + BAT_POLL_INTERVAL) { + bat->volt_value = gather_samples(bat->client, + bat->pdata->backup_volt_samples, + bat->pdata->backup_volt_channel); + bat->volt_value *= bat->pdata->backup_volt_mult; + bat->timestamp = jiffies; + } + + switch (psp) { + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + val->intval = bat->volt_value; + return 0; + case POWER_SUPPLY_PROP_VOLTAGE_MIN: + val->intval = bat->pdata->backup_volt_min; + return 0; + case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: + val->intval = bat->pdata->backup_volt_max; + return 0; + default: + return -EINVAL; + } +} + +static const struct power_supply_desc backup_bat_desc = { + .name = "backup-battery", + .type = POWER_SUPPLY_TYPE_BATTERY, + .properties = s3c_adc_backup_bat_props, + .num_properties = ARRAY_SIZE(s3c_adc_backup_bat_props), + .get_property = s3c_adc_backup_bat_get_property, + .use_for_apm = 1, +}; + +static struct s3c_adc_bat backup_bat; + +static enum power_supply_property s3c_adc_main_bat_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, + POWER_SUPPLY_PROP_CHARGE_EMPTY_DESIGN, + POWER_SUPPLY_PROP_CHARGE_NOW, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CURRENT_NOW, +}; + +static int calc_full_volt(int volt_val, int cur_val, int impedance) +{ + return volt_val + cur_val * impedance / 1000; +} + +static int charge_finished(struct s3c_adc_bat *bat) +{ + return bat->pdata->gpio_inverted ? + !gpio_get_value(bat->pdata->gpio_charge_finished) : + gpio_get_value(bat->pdata->gpio_charge_finished); +} + +static int s3c_adc_bat_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct s3c_adc_bat *bat = power_supply_get_drvdata(psy); + + int new_level; + int full_volt; + const struct s3c_adc_bat_thresh *lut; + unsigned int lut_size; + + if (!bat) { + dev_err(&psy->dev, "no battery infos ?!\n"); + return -EINVAL; + } + + lut = bat->pdata->lut_noac; + lut_size = bat->pdata->lut_noac_cnt; + + if (bat->volt_value < 0 || bat->cur_value < 0 || + jiffies_to_msecs(jiffies - bat->timestamp) > + BAT_POLL_INTERVAL) { + bat->volt_value = gather_samples(bat->client, + bat->pdata->volt_samples, + bat->pdata->volt_channel) * bat->pdata->volt_mult; + bat->cur_value = gather_samples(bat->client, + bat->pdata->current_samples, + bat->pdata->current_channel) * bat->pdata->current_mult; + bat->timestamp = jiffies; + } + + if (bat->cable_plugged && + ((bat->pdata->gpio_charge_finished < 0) || + !charge_finished(bat))) { + lut = bat->pdata->lut_acin; + lut_size = bat->pdata->lut_acin_cnt; + } + + new_level = 100000; + full_volt = calc_full_volt((bat->volt_value / 1000), + (bat->cur_value / 1000), bat->pdata->internal_impedance); + + if (full_volt < calc_full_volt(lut->volt, lut->cur, + bat->pdata->internal_impedance)) { + lut_size--; + while (lut_size--) { + int lut_volt1; + int lut_volt2; + + lut_volt1 = calc_full_volt(lut[0].volt, lut[0].cur, + bat->pdata->internal_impedance); + lut_volt2 = calc_full_volt(lut[1].volt, lut[1].cur, + bat->pdata->internal_impedance); + if (full_volt < lut_volt1 && full_volt >= lut_volt2) { + new_level = (lut[1].level + + (lut[0].level - lut[1].level) * + (full_volt - lut_volt2) / + (lut_volt1 - lut_volt2)) * 1000; + break; + } + new_level = lut[1].level * 1000; + lut++; + } + } + + bat->level = new_level; + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + if (bat->pdata->gpio_charge_finished < 0) + val->intval = bat->level == 100000 ? + POWER_SUPPLY_STATUS_FULL : bat->status; + else + val->intval = bat->status; + return 0; + case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: + val->intval = 100000; + return 0; + case POWER_SUPPLY_PROP_CHARGE_EMPTY_DESIGN: + val->intval = 0; + return 0; + case POWER_SUPPLY_PROP_CHARGE_NOW: + val->intval = bat->level; + return 0; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + val->intval = bat->volt_value; + return 0; + case POWER_SUPPLY_PROP_CURRENT_NOW: + val->intval = bat->cur_value; + return 0; + default: + return -EINVAL; + } +} + +static const struct power_supply_desc main_bat_desc = { + .name = "main-battery", + .type = POWER_SUPPLY_TYPE_BATTERY, + .properties = s3c_adc_main_bat_props, + .num_properties = ARRAY_SIZE(s3c_adc_main_bat_props), + .get_property = s3c_adc_bat_get_property, + .external_power_changed = s3c_adc_bat_ext_power_changed, + .use_for_apm = 1, +}; + +static struct s3c_adc_bat main_bat; + +static void s3c_adc_bat_work(struct work_struct *work) +{ + struct s3c_adc_bat *bat = &main_bat; + int is_charged; + int is_plugged; + static int was_plugged; + + is_plugged = power_supply_am_i_supplied(bat->psy); + bat->cable_plugged = is_plugged; + if (is_plugged != was_plugged) { + was_plugged = is_plugged; + if (is_plugged) { + if (bat->pdata->enable_charger) + bat->pdata->enable_charger(); + bat->status = POWER_SUPPLY_STATUS_CHARGING; + } else { + if (bat->pdata->disable_charger) + bat->pdata->disable_charger(); + bat->status = POWER_SUPPLY_STATUS_DISCHARGING; + } + } else { + if ((bat->pdata->gpio_charge_finished >= 0) && is_plugged) { + is_charged = charge_finished(&main_bat); + if (is_charged) { + if (bat->pdata->disable_charger) + bat->pdata->disable_charger(); + bat->status = POWER_SUPPLY_STATUS_FULL; + } else { + if (bat->pdata->enable_charger) + bat->pdata->enable_charger(); + bat->status = POWER_SUPPLY_STATUS_CHARGING; + } + } + } + + power_supply_changed(bat->psy); +} + +static irqreturn_t s3c_adc_bat_charged(int irq, void *dev_id) +{ + schedule_delayed_work(&bat_work, + msecs_to_jiffies(JITTER_DELAY)); + return IRQ_HANDLED; +} + +static int s3c_adc_bat_probe(struct platform_device *pdev) +{ + struct s3c_adc_client *client; + struct s3c_adc_bat_pdata *pdata = pdev->dev.platform_data; + int ret; + + client = s3c_adc_register(pdev, NULL, NULL, 0); + if (IS_ERR(client)) { + dev_err(&pdev->dev, "cannot register adc\n"); + return PTR_ERR(client); + } + + platform_set_drvdata(pdev, client); + + main_bat.client = client; + main_bat.pdata = pdata; + main_bat.volt_value = -1; + main_bat.cur_value = -1; + main_bat.cable_plugged = 0; + main_bat.status = POWER_SUPPLY_STATUS_DISCHARGING; + + main_bat.psy = power_supply_register(&pdev->dev, &main_bat_desc, NULL); + if (IS_ERR(main_bat.psy)) { + ret = PTR_ERR(main_bat.psy); + goto err_reg_main; + } + if (pdata->backup_volt_mult) { + const struct power_supply_config psy_cfg + = { .drv_data = &backup_bat, }; + + backup_bat.client = client; + backup_bat.pdata = pdev->dev.platform_data; + backup_bat.volt_value = -1; + backup_bat.psy = power_supply_register(&pdev->dev, + &backup_bat_desc, + &psy_cfg); + if (IS_ERR(backup_bat.psy)) { + ret = PTR_ERR(backup_bat.psy); + goto err_reg_backup; + } + } + + INIT_DELAYED_WORK(&bat_work, s3c_adc_bat_work); + + if (pdata->gpio_charge_finished >= 0) { + ret = gpio_request(pdata->gpio_charge_finished, "charged"); + if (ret) + goto err_gpio; + + ret = request_irq(gpio_to_irq(pdata->gpio_charge_finished), + s3c_adc_bat_charged, + IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, + "battery charged", NULL); + if (ret) + goto err_irq; + } + + if (pdata->init) { + ret = pdata->init(); + if (ret) + goto err_platform; + } + + dev_info(&pdev->dev, "successfully loaded\n"); + device_init_wakeup(&pdev->dev, 1); + + /* Schedule timer to check current status */ + schedule_delayed_work(&bat_work, + msecs_to_jiffies(JITTER_DELAY)); + + return 0; + +err_platform: + if (pdata->gpio_charge_finished >= 0) + free_irq(gpio_to_irq(pdata->gpio_charge_finished), NULL); +err_irq: + if (pdata->gpio_charge_finished >= 0) + gpio_free(pdata->gpio_charge_finished); +err_gpio: + if (pdata->backup_volt_mult) + power_supply_unregister(backup_bat.psy); +err_reg_backup: + power_supply_unregister(main_bat.psy); +err_reg_main: + return ret; +} + +static int s3c_adc_bat_remove(struct platform_device *pdev) +{ + struct s3c_adc_client *client = platform_get_drvdata(pdev); + struct s3c_adc_bat_pdata *pdata = pdev->dev.platform_data; + + power_supply_unregister(main_bat.psy); + if (pdata->backup_volt_mult) + power_supply_unregister(backup_bat.psy); + + s3c_adc_release(client); + + if (pdata->gpio_charge_finished >= 0) { + free_irq(gpio_to_irq(pdata->gpio_charge_finished), NULL); + gpio_free(pdata->gpio_charge_finished); + } + + cancel_delayed_work(&bat_work); + + if (pdata->exit) + pdata->exit(); + + return 0; +} + +#ifdef CONFIG_PM +static int s3c_adc_bat_suspend(struct platform_device *pdev, + pm_message_t state) +{ + struct s3c_adc_bat_pdata *pdata = pdev->dev.platform_data; + + if (pdata->gpio_charge_finished >= 0) { + if (device_may_wakeup(&pdev->dev)) + enable_irq_wake( + gpio_to_irq(pdata->gpio_charge_finished)); + else { + disable_irq(gpio_to_irq(pdata->gpio_charge_finished)); + main_bat.pdata->disable_charger(); + } + } + + return 0; +} + +static int s3c_adc_bat_resume(struct platform_device *pdev) +{ + struct s3c_adc_bat_pdata *pdata = pdev->dev.platform_data; + + if (pdata->gpio_charge_finished >= 0) { + if (device_may_wakeup(&pdev->dev)) + disable_irq_wake( + gpio_to_irq(pdata->gpio_charge_finished)); + else + enable_irq(gpio_to_irq(pdata->gpio_charge_finished)); + } + + /* Schedule timer to check current status */ + schedule_delayed_work(&bat_work, + msecs_to_jiffies(JITTER_DELAY)); + + return 0; +} +#else +#define s3c_adc_bat_suspend NULL +#define s3c_adc_bat_resume NULL +#endif + +static struct platform_driver s3c_adc_bat_driver = { + .driver = { + .name = "s3c-adc-battery", + }, + .probe = s3c_adc_bat_probe, + .remove = s3c_adc_bat_remove, + .suspend = s3c_adc_bat_suspend, + .resume = s3c_adc_bat_resume, +}; + +module_platform_driver(s3c_adc_bat_driver); + +MODULE_AUTHOR("Vasily Khoruzhick "); +MODULE_DESCRIPTION("iPAQ H1930/H1940/RX1950 battery controller driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/power/supply/sbs-battery.c b/drivers/power/supply/sbs-battery.c new file mode 100644 index 000000000000..768b9fcb58ea --- /dev/null +++ b/drivers/power/supply/sbs-battery.c @@ -0,0 +1,998 @@ +/* + * Gas Gauge driver for SBS Compliant Batteries + * + * Copyright (c) 2010, NVIDIA 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; either version 2 of the License, or + * (at your option) any later version. + * + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +enum { + REG_MANUFACTURER_DATA, + REG_TEMPERATURE, + REG_VOLTAGE, + REG_CURRENT, + REG_CAPACITY, + REG_TIME_TO_EMPTY, + REG_TIME_TO_FULL, + REG_STATUS, + REG_CYCLE_COUNT, + REG_SERIAL_NUMBER, + REG_REMAINING_CAPACITY, + REG_REMAINING_CAPACITY_CHARGE, + REG_FULL_CHARGE_CAPACITY, + REG_FULL_CHARGE_CAPACITY_CHARGE, + REG_DESIGN_CAPACITY, + REG_DESIGN_CAPACITY_CHARGE, + REG_DESIGN_VOLTAGE_MIN, + REG_DESIGN_VOLTAGE_MAX, + REG_MANUFACTURER, + REG_MODEL_NAME, +}; + +/* Battery Mode defines */ +#define BATTERY_MODE_OFFSET 0x03 +#define BATTERY_MODE_MASK 0x8000 +enum sbs_battery_mode { + BATTERY_MODE_AMPS, + BATTERY_MODE_WATTS +}; + +/* manufacturer access defines */ +#define MANUFACTURER_ACCESS_STATUS 0x0006 +#define MANUFACTURER_ACCESS_SLEEP 0x0011 + +/* battery status value bits */ +#define BATTERY_DISCHARGING 0x40 +#define BATTERY_FULL_CHARGED 0x20 +#define BATTERY_FULL_DISCHARGED 0x10 + +/* min_value and max_value are only valid for numerical data */ +#define SBS_DATA(_psp, _addr, _min_value, _max_value) { \ + .psp = _psp, \ + .addr = _addr, \ + .min_value = _min_value, \ + .max_value = _max_value, \ +} + +static const struct chip_data { + enum power_supply_property psp; + u8 addr; + int min_value; + int max_value; +} sbs_data[] = { + [REG_MANUFACTURER_DATA] = + SBS_DATA(POWER_SUPPLY_PROP_PRESENT, 0x00, 0, 65535), + [REG_TEMPERATURE] = + SBS_DATA(POWER_SUPPLY_PROP_TEMP, 0x08, 0, 65535), + [REG_VOLTAGE] = + SBS_DATA(POWER_SUPPLY_PROP_VOLTAGE_NOW, 0x09, 0, 20000), + [REG_CURRENT] = + SBS_DATA(POWER_SUPPLY_PROP_CURRENT_NOW, 0x0A, -32768, 32767), + [REG_CAPACITY] = + SBS_DATA(POWER_SUPPLY_PROP_CAPACITY, 0x0D, 0, 100), + [REG_REMAINING_CAPACITY] = + SBS_DATA(POWER_SUPPLY_PROP_ENERGY_NOW, 0x0F, 0, 65535), + [REG_REMAINING_CAPACITY_CHARGE] = + SBS_DATA(POWER_SUPPLY_PROP_CHARGE_NOW, 0x0F, 0, 65535), + [REG_FULL_CHARGE_CAPACITY] = + SBS_DATA(POWER_SUPPLY_PROP_ENERGY_FULL, 0x10, 0, 65535), + [REG_FULL_CHARGE_CAPACITY_CHARGE] = + SBS_DATA(POWER_SUPPLY_PROP_CHARGE_FULL, 0x10, 0, 65535), + [REG_TIME_TO_EMPTY] = + SBS_DATA(POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG, 0x12, 0, 65535), + [REG_TIME_TO_FULL] = + SBS_DATA(POWER_SUPPLY_PROP_TIME_TO_FULL_AVG, 0x13, 0, 65535), + [REG_STATUS] = + SBS_DATA(POWER_SUPPLY_PROP_STATUS, 0x16, 0, 65535), + [REG_CYCLE_COUNT] = + SBS_DATA(POWER_SUPPLY_PROP_CYCLE_COUNT, 0x17, 0, 65535), + [REG_DESIGN_CAPACITY] = + SBS_DATA(POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN, 0x18, 0, 65535), + [REG_DESIGN_CAPACITY_CHARGE] = + SBS_DATA(POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, 0x18, 0, 65535), + [REG_DESIGN_VOLTAGE_MIN] = + SBS_DATA(POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, 0x19, 0, 65535), + [REG_DESIGN_VOLTAGE_MAX] = + SBS_DATA(POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, 0x19, 0, 65535), + [REG_SERIAL_NUMBER] = + SBS_DATA(POWER_SUPPLY_PROP_SERIAL_NUMBER, 0x1C, 0, 65535), + /* Properties of type `const char *' */ + [REG_MANUFACTURER] = + SBS_DATA(POWER_SUPPLY_PROP_MANUFACTURER, 0x20, 0, 65535), + [REG_MODEL_NAME] = + SBS_DATA(POWER_SUPPLY_PROP_MODEL_NAME, 0x21, 0, 65535) +}; + +static enum power_supply_property sbs_properties[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_CYCLE_COUNT, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_TEMP, + POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG, + POWER_SUPPLY_PROP_TIME_TO_FULL_AVG, + POWER_SUPPLY_PROP_SERIAL_NUMBER, + POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, + POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, + POWER_SUPPLY_PROP_ENERGY_NOW, + POWER_SUPPLY_PROP_ENERGY_FULL, + POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN, + POWER_SUPPLY_PROP_CHARGE_NOW, + POWER_SUPPLY_PROP_CHARGE_FULL, + POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, + /* Properties of type `const char *' */ + POWER_SUPPLY_PROP_MANUFACTURER, + POWER_SUPPLY_PROP_MODEL_NAME +}; + +struct sbs_info { + struct i2c_client *client; + struct power_supply *power_supply; + struct sbs_platform_data *pdata; + bool is_present; + bool gpio_detect; + bool enable_detection; + int irq; + int last_state; + int poll_time; + struct delayed_work work; + int ignore_changes; +}; + +static char model_name[I2C_SMBUS_BLOCK_MAX + 1]; +static char manufacturer[I2C_SMBUS_BLOCK_MAX + 1]; +static bool force_load; + +static int sbs_read_word_data(struct i2c_client *client, u8 address) +{ + struct sbs_info *chip = i2c_get_clientdata(client); + s32 ret = 0; + int retries = 1; + + if (chip->pdata) + retries = max(chip->pdata->i2c_retry_count + 1, 1); + + while (retries > 0) { + ret = i2c_smbus_read_word_data(client, address); + if (ret >= 0) + break; + retries--; + } + + if (ret < 0) { + dev_dbg(&client->dev, + "%s: i2c read at address 0x%x failed\n", + __func__, address); + return ret; + } + + return le16_to_cpu(ret); +} + +static int sbs_read_string_data(struct i2c_client *client, u8 address, + char *values) +{ + struct sbs_info *chip = i2c_get_clientdata(client); + s32 ret = 0, block_length = 0; + int retries_length = 1, retries_block = 1; + u8 block_buffer[I2C_SMBUS_BLOCK_MAX + 1]; + + if (chip->pdata) { + retries_length = max(chip->pdata->i2c_retry_count + 1, 1); + retries_block = max(chip->pdata->i2c_retry_count + 1, 1); + } + + /* Adapter needs to support these two functions */ + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_BYTE_DATA | + I2C_FUNC_SMBUS_I2C_BLOCK)){ + return -ENODEV; + } + + /* Get the length of block data */ + while (retries_length > 0) { + ret = i2c_smbus_read_byte_data(client, address); + if (ret >= 0) + break; + retries_length--; + } + + if (ret < 0) { + dev_dbg(&client->dev, + "%s: i2c read at address 0x%x failed\n", + __func__, address); + return ret; + } + + /* block_length does not include NULL terminator */ + block_length = ret; + if (block_length > I2C_SMBUS_BLOCK_MAX) { + dev_err(&client->dev, + "%s: Returned block_length is longer than 0x%x\n", + __func__, I2C_SMBUS_BLOCK_MAX); + return -EINVAL; + } + + /* Get the block data */ + while (retries_block > 0) { + ret = i2c_smbus_read_i2c_block_data( + client, address, + block_length + 1, block_buffer); + if (ret >= 0) + break; + retries_block--; + } + + if (ret < 0) { + dev_dbg(&client->dev, + "%s: i2c read at address 0x%x failed\n", + __func__, address); + return ret; + } + + /* block_buffer[0] == block_length */ + memcpy(values, block_buffer + 1, block_length); + values[block_length] = '\0'; + + return le16_to_cpu(ret); +} + +static int sbs_write_word_data(struct i2c_client *client, u8 address, + u16 value) +{ + struct sbs_info *chip = i2c_get_clientdata(client); + s32 ret = 0; + int retries = 1; + + if (chip->pdata) + retries = max(chip->pdata->i2c_retry_count + 1, 1); + + while (retries > 0) { + ret = i2c_smbus_write_word_data(client, address, + le16_to_cpu(value)); + if (ret >= 0) + break; + retries--; + } + + if (ret < 0) { + dev_dbg(&client->dev, + "%s: i2c write to address 0x%x failed\n", + __func__, address); + return ret; + } + + return 0; +} + +static int sbs_get_battery_presence_and_health( + struct i2c_client *client, enum power_supply_property psp, + union power_supply_propval *val) +{ + s32 ret; + struct sbs_info *chip = i2c_get_clientdata(client); + + if (psp == POWER_SUPPLY_PROP_PRESENT && + chip->gpio_detect) { + ret = gpio_get_value(chip->pdata->battery_detect); + if (ret == chip->pdata->battery_detect_present) + val->intval = 1; + else + val->intval = 0; + chip->is_present = val->intval; + return ret; + } + + /* Write to ManufacturerAccess with + * ManufacturerAccess command and then + * read the status */ + ret = sbs_write_word_data(client, sbs_data[REG_MANUFACTURER_DATA].addr, + MANUFACTURER_ACCESS_STATUS); + if (ret < 0) { + if (psp == POWER_SUPPLY_PROP_PRESENT) + val->intval = 0; /* battery removed */ + return ret; + } + + ret = sbs_read_word_data(client, sbs_data[REG_MANUFACTURER_DATA].addr); + if (ret < 0) + return ret; + + if (ret < sbs_data[REG_MANUFACTURER_DATA].min_value || + ret > sbs_data[REG_MANUFACTURER_DATA].max_value) { + val->intval = 0; + return 0; + } + + /* Mask the upper nibble of 2nd byte and + * lower byte of response then + * shift the result by 8 to get status*/ + ret &= 0x0F00; + ret >>= 8; + if (psp == POWER_SUPPLY_PROP_PRESENT) { + if (ret == 0x0F) + /* battery removed */ + val->intval = 0; + else + val->intval = 1; + } else if (psp == POWER_SUPPLY_PROP_HEALTH) { + if (ret == 0x09) + val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; + else if (ret == 0x0B) + val->intval = POWER_SUPPLY_HEALTH_OVERHEAT; + else if (ret == 0x0C) + val->intval = POWER_SUPPLY_HEALTH_DEAD; + else + val->intval = POWER_SUPPLY_HEALTH_GOOD; + } + + return 0; +} + +static int sbs_get_battery_property(struct i2c_client *client, + int reg_offset, enum power_supply_property psp, + union power_supply_propval *val) +{ + struct sbs_info *chip = i2c_get_clientdata(client); + s32 ret; + + ret = sbs_read_word_data(client, sbs_data[reg_offset].addr); + if (ret < 0) + return ret; + + /* returned values are 16 bit */ + if (sbs_data[reg_offset].min_value < 0) + ret = (s16)ret; + + if (ret >= sbs_data[reg_offset].min_value && + ret <= sbs_data[reg_offset].max_value) { + val->intval = ret; + if (psp != POWER_SUPPLY_PROP_STATUS) + return 0; + + if (ret & BATTERY_FULL_CHARGED) + val->intval = POWER_SUPPLY_STATUS_FULL; + else if (ret & BATTERY_DISCHARGING) + val->intval = POWER_SUPPLY_STATUS_DISCHARGING; + else + val->intval = POWER_SUPPLY_STATUS_CHARGING; + + if (chip->poll_time == 0) + chip->last_state = val->intval; + else if (chip->last_state != val->intval) { + cancel_delayed_work_sync(&chip->work); + power_supply_changed(chip->power_supply); + chip->poll_time = 0; + } + } else { + if (psp == POWER_SUPPLY_PROP_STATUS) + val->intval = POWER_SUPPLY_STATUS_UNKNOWN; + else + val->intval = 0; + } + + return 0; +} + +static int sbs_get_battery_string_property(struct i2c_client *client, + int reg_offset, enum power_supply_property psp, char *val) +{ + s32 ret; + + ret = sbs_read_string_data(client, sbs_data[reg_offset].addr, val); + + if (ret < 0) + return ret; + + return 0; +} + +static void sbs_unit_adjustment(struct i2c_client *client, + enum power_supply_property psp, union power_supply_propval *val) +{ +#define BASE_UNIT_CONVERSION 1000 +#define BATTERY_MODE_CAP_MULT_WATT (10 * BASE_UNIT_CONVERSION) +#define TIME_UNIT_CONVERSION 60 +#define TEMP_KELVIN_TO_CELSIUS 2731 + switch (psp) { + case POWER_SUPPLY_PROP_ENERGY_NOW: + case POWER_SUPPLY_PROP_ENERGY_FULL: + case POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN: + /* sbs provides energy in units of 10mWh. + * Convert to µWh + */ + val->intval *= BATTERY_MODE_CAP_MULT_WATT; + break; + + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: + case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: + case POWER_SUPPLY_PROP_CURRENT_NOW: + case POWER_SUPPLY_PROP_CHARGE_NOW: + case POWER_SUPPLY_PROP_CHARGE_FULL: + case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: + val->intval *= BASE_UNIT_CONVERSION; + break; + + case POWER_SUPPLY_PROP_TEMP: + /* sbs provides battery temperature in 0.1K + * so convert it to 0.1°C + */ + val->intval -= TEMP_KELVIN_TO_CELSIUS; + break; + + case POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG: + case POWER_SUPPLY_PROP_TIME_TO_FULL_AVG: + /* sbs provides time to empty and time to full in minutes. + * Convert to seconds + */ + val->intval *= TIME_UNIT_CONVERSION; + break; + + default: + dev_dbg(&client->dev, + "%s: no need for unit conversion %d\n", __func__, psp); + } +} + +static enum sbs_battery_mode sbs_set_battery_mode(struct i2c_client *client, + enum sbs_battery_mode mode) +{ + int ret, original_val; + + original_val = sbs_read_word_data(client, BATTERY_MODE_OFFSET); + if (original_val < 0) + return original_val; + + if ((original_val & BATTERY_MODE_MASK) == mode) + return mode; + + if (mode == BATTERY_MODE_AMPS) + ret = original_val & ~BATTERY_MODE_MASK; + else + ret = original_val | BATTERY_MODE_MASK; + + ret = sbs_write_word_data(client, BATTERY_MODE_OFFSET, ret); + if (ret < 0) + return ret; + + return original_val & BATTERY_MODE_MASK; +} + +static int sbs_get_battery_capacity(struct i2c_client *client, + int reg_offset, enum power_supply_property psp, + union power_supply_propval *val) +{ + s32 ret; + enum sbs_battery_mode mode = BATTERY_MODE_WATTS; + + if (power_supply_is_amp_property(psp)) + mode = BATTERY_MODE_AMPS; + + mode = sbs_set_battery_mode(client, mode); + if (mode < 0) + return mode; + + ret = sbs_read_word_data(client, sbs_data[reg_offset].addr); + if (ret < 0) + return ret; + + if (psp == POWER_SUPPLY_PROP_CAPACITY) { + /* sbs spec says that this can be >100 % + * even if max value is 100 % */ + val->intval = min(ret, 100); + } else + val->intval = ret; + + ret = sbs_set_battery_mode(client, mode); + if (ret < 0) + return ret; + + return 0; +} + +static char sbs_serial[5]; +static int sbs_get_battery_serial_number(struct i2c_client *client, + union power_supply_propval *val) +{ + int ret; + + ret = sbs_read_word_data(client, sbs_data[REG_SERIAL_NUMBER].addr); + if (ret < 0) + return ret; + + ret = sprintf(sbs_serial, "%04x", ret); + val->strval = sbs_serial; + + return 0; +} + +static int sbs_get_property_index(struct i2c_client *client, + enum power_supply_property psp) +{ + int count; + for (count = 0; count < ARRAY_SIZE(sbs_data); count++) + if (psp == sbs_data[count].psp) + return count; + + dev_warn(&client->dev, + "%s: Invalid Property - %d\n", __func__, psp); + + return -EINVAL; +} + +static int sbs_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + int ret = 0; + struct sbs_info *chip = power_supply_get_drvdata(psy); + struct i2c_client *client = chip->client; + + switch (psp) { + case POWER_SUPPLY_PROP_PRESENT: + case POWER_SUPPLY_PROP_HEALTH: + ret = sbs_get_battery_presence_and_health(client, psp, val); + if (psp == POWER_SUPPLY_PROP_PRESENT) + return 0; + break; + + case POWER_SUPPLY_PROP_TECHNOLOGY: + val->intval = POWER_SUPPLY_TECHNOLOGY_LION; + goto done; /* don't trigger power_supply_changed()! */ + + case POWER_SUPPLY_PROP_ENERGY_NOW: + case POWER_SUPPLY_PROP_ENERGY_FULL: + case POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN: + case POWER_SUPPLY_PROP_CHARGE_NOW: + case POWER_SUPPLY_PROP_CHARGE_FULL: + case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: + case POWER_SUPPLY_PROP_CAPACITY: + ret = sbs_get_property_index(client, psp); + if (ret < 0) + break; + + ret = sbs_get_battery_capacity(client, ret, psp, val); + break; + + case POWER_SUPPLY_PROP_SERIAL_NUMBER: + ret = sbs_get_battery_serial_number(client, val); + break; + + case POWER_SUPPLY_PROP_STATUS: + case POWER_SUPPLY_PROP_CYCLE_COUNT: + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + case POWER_SUPPLY_PROP_CURRENT_NOW: + case POWER_SUPPLY_PROP_TEMP: + case POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG: + case POWER_SUPPLY_PROP_TIME_TO_FULL_AVG: + case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: + case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: + ret = sbs_get_property_index(client, psp); + if (ret < 0) + break; + + ret = sbs_get_battery_property(client, ret, psp, val); + break; + + case POWER_SUPPLY_PROP_MODEL_NAME: + ret = sbs_get_property_index(client, psp); + if (ret < 0) + break; + + ret = sbs_get_battery_string_property(client, ret, psp, + model_name); + val->strval = model_name; + break; + + case POWER_SUPPLY_PROP_MANUFACTURER: + ret = sbs_get_property_index(client, psp); + if (ret < 0) + break; + + ret = sbs_get_battery_string_property(client, ret, psp, + manufacturer); + val->strval = manufacturer; + break; + + default: + dev_err(&client->dev, + "%s: INVALID property\n", __func__); + return -EINVAL; + } + + if (!chip->enable_detection) + goto done; + + if (!chip->gpio_detect && + chip->is_present != (ret >= 0)) { + chip->is_present = (ret >= 0); + power_supply_changed(chip->power_supply); + } + +done: + if (!ret) { + /* Convert units to match requirements for power supply class */ + sbs_unit_adjustment(client, psp, val); + } + + dev_dbg(&client->dev, + "%s: property = %d, value = %x\n", __func__, psp, val->intval); + + if (ret && chip->is_present) + return ret; + + /* battery not present, so return NODATA for properties */ + if (ret) + return -ENODATA; + + return 0; +} + +static irqreturn_t sbs_irq(int irq, void *devid) +{ + struct power_supply *battery = devid; + + power_supply_changed(battery); + + return IRQ_HANDLED; +} + +static void sbs_external_power_changed(struct power_supply *psy) +{ + struct sbs_info *chip = power_supply_get_drvdata(psy); + + if (chip->ignore_changes > 0) { + chip->ignore_changes--; + return; + } + + /* cancel outstanding work */ + cancel_delayed_work_sync(&chip->work); + + schedule_delayed_work(&chip->work, HZ); + chip->poll_time = chip->pdata->poll_retry_count; +} + +static void sbs_delayed_work(struct work_struct *work) +{ + struct sbs_info *chip; + s32 ret; + + chip = container_of(work, struct sbs_info, work.work); + + ret = sbs_read_word_data(chip->client, sbs_data[REG_STATUS].addr); + /* if the read failed, give up on this work */ + if (ret < 0) { + chip->poll_time = 0; + return; + } + + if (ret & BATTERY_FULL_CHARGED) + ret = POWER_SUPPLY_STATUS_FULL; + else if (ret & BATTERY_DISCHARGING) + ret = POWER_SUPPLY_STATUS_DISCHARGING; + else + ret = POWER_SUPPLY_STATUS_CHARGING; + + if (chip->last_state != ret) { + chip->poll_time = 0; + power_supply_changed(chip->power_supply); + return; + } + if (chip->poll_time > 0) { + schedule_delayed_work(&chip->work, HZ); + chip->poll_time--; + return; + } +} + +#if defined(CONFIG_OF) + +#include +#include + +static const struct of_device_id sbs_dt_ids[] = { + { .compatible = "sbs,sbs-battery" }, + { .compatible = "ti,bq20z75" }, + { } +}; +MODULE_DEVICE_TABLE(of, sbs_dt_ids); + +static struct sbs_platform_data *sbs_of_populate_pdata( + struct i2c_client *client) +{ + struct device_node *of_node = client->dev.of_node; + struct sbs_platform_data *pdata = client->dev.platform_data; + enum of_gpio_flags gpio_flags; + int rc; + u32 prop; + + /* verify this driver matches this device */ + if (!of_node) + return NULL; + + /* if platform data is set, honor it */ + if (pdata) + return pdata; + + /* first make sure at least one property is set, otherwise + * it won't change behavior from running without pdata. + */ + if (!of_get_property(of_node, "sbs,i2c-retry-count", NULL) && + !of_get_property(of_node, "sbs,poll-retry-count", NULL) && + !of_get_property(of_node, "sbs,battery-detect-gpios", NULL)) + goto of_out; + + pdata = devm_kzalloc(&client->dev, sizeof(struct sbs_platform_data), + GFP_KERNEL); + if (!pdata) + goto of_out; + + rc = of_property_read_u32(of_node, "sbs,i2c-retry-count", &prop); + if (!rc) + pdata->i2c_retry_count = prop; + + rc = of_property_read_u32(of_node, "sbs,poll-retry-count", &prop); + if (!rc) + pdata->poll_retry_count = prop; + + if (!of_get_property(of_node, "sbs,battery-detect-gpios", NULL)) { + pdata->battery_detect = -1; + goto of_out; + } + + pdata->battery_detect = of_get_named_gpio_flags(of_node, + "sbs,battery-detect-gpios", 0, &gpio_flags); + + if (gpio_flags & OF_GPIO_ACTIVE_LOW) + pdata->battery_detect_present = 0; + else + pdata->battery_detect_present = 1; + +of_out: + return pdata; +} +#else +static struct sbs_platform_data *sbs_of_populate_pdata( + struct i2c_client *client) +{ + return client->dev.platform_data; +} +#endif + +static const struct power_supply_desc sbs_default_desc = { + .type = POWER_SUPPLY_TYPE_BATTERY, + .properties = sbs_properties, + .num_properties = ARRAY_SIZE(sbs_properties), + .get_property = sbs_get_property, + .external_power_changed = sbs_external_power_changed, +}; + +static int sbs_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct sbs_info *chip; + struct power_supply_desc *sbs_desc; + struct sbs_platform_data *pdata = client->dev.platform_data; + struct power_supply_config psy_cfg = {}; + int rc; + int irq; + + sbs_desc = devm_kmemdup(&client->dev, &sbs_default_desc, + sizeof(*sbs_desc), GFP_KERNEL); + if (!sbs_desc) + return -ENOMEM; + + sbs_desc->name = devm_kasprintf(&client->dev, GFP_KERNEL, "sbs-%s", + dev_name(&client->dev)); + if (!sbs_desc->name) + return -ENOMEM; + + chip = kzalloc(sizeof(struct sbs_info), GFP_KERNEL); + if (!chip) + return -ENOMEM; + + chip->client = client; + chip->enable_detection = false; + chip->gpio_detect = false; + psy_cfg.of_node = client->dev.of_node; + psy_cfg.drv_data = chip; + /* ignore first notification of external change, it is generated + * from the power_supply_register call back + */ + chip->ignore_changes = 1; + chip->last_state = POWER_SUPPLY_STATUS_UNKNOWN; + + pdata = sbs_of_populate_pdata(client); + + if (pdata) { + chip->gpio_detect = gpio_is_valid(pdata->battery_detect); + chip->pdata = pdata; + } + + i2c_set_clientdata(client, chip); + + if (!chip->gpio_detect) + goto skip_gpio; + + rc = gpio_request(pdata->battery_detect, dev_name(&client->dev)); + if (rc) { + dev_warn(&client->dev, "Failed to request gpio: %d\n", rc); + chip->gpio_detect = false; + goto skip_gpio; + } + + rc = gpio_direction_input(pdata->battery_detect); + if (rc) { + dev_warn(&client->dev, "Failed to get gpio as input: %d\n", rc); + gpio_free(pdata->battery_detect); + chip->gpio_detect = false; + goto skip_gpio; + } + + irq = gpio_to_irq(pdata->battery_detect); + if (irq <= 0) { + dev_warn(&client->dev, "Failed to get gpio as irq: %d\n", irq); + gpio_free(pdata->battery_detect); + chip->gpio_detect = false; + goto skip_gpio; + } + + rc = request_irq(irq, sbs_irq, + IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, + dev_name(&client->dev), chip->power_supply); + if (rc) { + dev_warn(&client->dev, "Failed to request irq: %d\n", rc); + gpio_free(pdata->battery_detect); + chip->gpio_detect = false; + goto skip_gpio; + } + + chip->irq = irq; + +skip_gpio: + /* + * Before we register, we might need to make sure we can actually talk + * to the battery. + */ + if (!force_load) { + rc = sbs_read_word_data(client, sbs_data[REG_STATUS].addr); + + if (rc < 0) { + dev_err(&client->dev, "%s: Failed to get device status\n", + __func__); + goto exit_psupply; + } + } + + chip->power_supply = power_supply_register(&client->dev, sbs_desc, + &psy_cfg); + if (IS_ERR(chip->power_supply)) { + dev_err(&client->dev, + "%s: Failed to register power supply\n", __func__); + rc = PTR_ERR(chip->power_supply); + goto exit_psupply; + } + + dev_info(&client->dev, + "%s: battery gas gauge device registered\n", client->name); + + INIT_DELAYED_WORK(&chip->work, sbs_delayed_work); + + chip->enable_detection = true; + + return 0; + +exit_psupply: + if (chip->irq) + free_irq(chip->irq, chip->power_supply); + if (chip->gpio_detect) + gpio_free(pdata->battery_detect); + + kfree(chip); + + return rc; +} + +static int sbs_remove(struct i2c_client *client) +{ + struct sbs_info *chip = i2c_get_clientdata(client); + + if (chip->irq) + free_irq(chip->irq, chip->power_supply); + if (chip->gpio_detect) + gpio_free(chip->pdata->battery_detect); + + power_supply_unregister(chip->power_supply); + + cancel_delayed_work_sync(&chip->work); + + kfree(chip); + chip = NULL; + + return 0; +} + +#if defined CONFIG_PM_SLEEP + +static int sbs_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct sbs_info *chip = i2c_get_clientdata(client); + s32 ret; + + if (chip->poll_time > 0) + cancel_delayed_work_sync(&chip->work); + + /* write to manufacturer access with sleep command */ + ret = sbs_write_word_data(client, sbs_data[REG_MANUFACTURER_DATA].addr, + MANUFACTURER_ACCESS_SLEEP); + if (chip->is_present && ret < 0) + return ret; + + return 0; +} + +static SIMPLE_DEV_PM_OPS(sbs_pm_ops, sbs_suspend, NULL); +#define SBS_PM_OPS (&sbs_pm_ops) + +#else +#define SBS_PM_OPS NULL +#endif + +static const struct i2c_device_id sbs_id[] = { + { "bq20z75", 0 }, + { "sbs-battery", 1 }, + {} +}; +MODULE_DEVICE_TABLE(i2c, sbs_id); + +static struct i2c_driver sbs_battery_driver = { + .probe = sbs_probe, + .remove = sbs_remove, + .id_table = sbs_id, + .driver = { + .name = "sbs-battery", + .of_match_table = of_match_ptr(sbs_dt_ids), + .pm = SBS_PM_OPS, + }, +}; +module_i2c_driver(sbs_battery_driver); + +MODULE_DESCRIPTION("SBS battery monitor driver"); +MODULE_LICENSE("GPL"); + +module_param(force_load, bool, S_IRUSR | S_IRGRP | S_IROTH); +MODULE_PARM_DESC(force_load, + "Attempt to load the driver even if no battery is connected"); diff --git a/drivers/power/supply/smb347-charger.c b/drivers/power/supply/smb347-charger.c new file mode 100644 index 000000000000..072c5189bd6d --- /dev/null +++ b/drivers/power/supply/smb347-charger.c @@ -0,0 +1,1334 @@ +/* + * Summit Microelectronics SMB347 Battery Charger Driver + * + * Copyright (C) 2011, Intel Corporation + * + * Authors: Bruce E. Robertson + * Mika Westerberg + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * Configuration registers. These are mirrored to volatile RAM and can be + * written once %CMD_A_ALLOW_WRITE is set in %CMD_A register. They will be + * reloaded from non-volatile registers after POR. + */ +#define CFG_CHARGE_CURRENT 0x00 +#define CFG_CHARGE_CURRENT_FCC_MASK 0xe0 +#define CFG_CHARGE_CURRENT_FCC_SHIFT 5 +#define CFG_CHARGE_CURRENT_PCC_MASK 0x18 +#define CFG_CHARGE_CURRENT_PCC_SHIFT 3 +#define CFG_CHARGE_CURRENT_TC_MASK 0x07 +#define CFG_CURRENT_LIMIT 0x01 +#define CFG_CURRENT_LIMIT_DC_MASK 0xf0 +#define CFG_CURRENT_LIMIT_DC_SHIFT 4 +#define CFG_CURRENT_LIMIT_USB_MASK 0x0f +#define CFG_FLOAT_VOLTAGE 0x03 +#define CFG_FLOAT_VOLTAGE_FLOAT_MASK 0x3f +#define CFG_FLOAT_VOLTAGE_THRESHOLD_MASK 0xc0 +#define CFG_FLOAT_VOLTAGE_THRESHOLD_SHIFT 6 +#define CFG_STAT 0x05 +#define CFG_STAT_DISABLED BIT(5) +#define CFG_STAT_ACTIVE_HIGH BIT(7) +#define CFG_PIN 0x06 +#define CFG_PIN_EN_CTRL_MASK 0x60 +#define CFG_PIN_EN_CTRL_ACTIVE_HIGH 0x40 +#define CFG_PIN_EN_CTRL_ACTIVE_LOW 0x60 +#define CFG_PIN_EN_APSD_IRQ BIT(1) +#define CFG_PIN_EN_CHARGER_ERROR BIT(2) +#define CFG_THERM 0x07 +#define CFG_THERM_SOFT_HOT_COMPENSATION_MASK 0x03 +#define CFG_THERM_SOFT_HOT_COMPENSATION_SHIFT 0 +#define CFG_THERM_SOFT_COLD_COMPENSATION_MASK 0x0c +#define CFG_THERM_SOFT_COLD_COMPENSATION_SHIFT 2 +#define CFG_THERM_MONITOR_DISABLED BIT(4) +#define CFG_SYSOK 0x08 +#define CFG_SYSOK_SUSPEND_HARD_LIMIT_DISABLED BIT(2) +#define CFG_OTHER 0x09 +#define CFG_OTHER_RID_MASK 0xc0 +#define CFG_OTHER_RID_ENABLED_AUTO_OTG 0xc0 +#define CFG_OTG 0x0a +#define CFG_OTG_TEMP_THRESHOLD_MASK 0x30 +#define CFG_OTG_TEMP_THRESHOLD_SHIFT 4 +#define CFG_OTG_CC_COMPENSATION_MASK 0xc0 +#define CFG_OTG_CC_COMPENSATION_SHIFT 6 +#define CFG_TEMP_LIMIT 0x0b +#define CFG_TEMP_LIMIT_SOFT_HOT_MASK 0x03 +#define CFG_TEMP_LIMIT_SOFT_HOT_SHIFT 0 +#define CFG_TEMP_LIMIT_SOFT_COLD_MASK 0x0c +#define CFG_TEMP_LIMIT_SOFT_COLD_SHIFT 2 +#define CFG_TEMP_LIMIT_HARD_HOT_MASK 0x30 +#define CFG_TEMP_LIMIT_HARD_HOT_SHIFT 4 +#define CFG_TEMP_LIMIT_HARD_COLD_MASK 0xc0 +#define CFG_TEMP_LIMIT_HARD_COLD_SHIFT 6 +#define CFG_FAULT_IRQ 0x0c +#define CFG_FAULT_IRQ_DCIN_UV BIT(2) +#define CFG_STATUS_IRQ 0x0d +#define CFG_STATUS_IRQ_TERMINATION_OR_TAPER BIT(4) +#define CFG_STATUS_IRQ_CHARGE_TIMEOUT BIT(7) +#define CFG_ADDRESS 0x0e + +/* Command registers */ +#define CMD_A 0x30 +#define CMD_A_CHG_ENABLED BIT(1) +#define CMD_A_SUSPEND_ENABLED BIT(2) +#define CMD_A_ALLOW_WRITE BIT(7) +#define CMD_B 0x31 +#define CMD_C 0x33 + +/* Interrupt Status registers */ +#define IRQSTAT_A 0x35 +#define IRQSTAT_C 0x37 +#define IRQSTAT_C_TERMINATION_STAT BIT(0) +#define IRQSTAT_C_TERMINATION_IRQ BIT(1) +#define IRQSTAT_C_TAPER_IRQ BIT(3) +#define IRQSTAT_D 0x38 +#define IRQSTAT_D_CHARGE_TIMEOUT_STAT BIT(2) +#define IRQSTAT_D_CHARGE_TIMEOUT_IRQ BIT(3) +#define IRQSTAT_E 0x39 +#define IRQSTAT_E_USBIN_UV_STAT BIT(0) +#define IRQSTAT_E_USBIN_UV_IRQ BIT(1) +#define IRQSTAT_E_DCIN_UV_STAT BIT(4) +#define IRQSTAT_E_DCIN_UV_IRQ BIT(5) +#define IRQSTAT_F 0x3a + +/* Status registers */ +#define STAT_A 0x3b +#define STAT_A_FLOAT_VOLTAGE_MASK 0x3f +#define STAT_B 0x3c +#define STAT_C 0x3d +#define STAT_C_CHG_ENABLED BIT(0) +#define STAT_C_HOLDOFF_STAT BIT(3) +#define STAT_C_CHG_MASK 0x06 +#define STAT_C_CHG_SHIFT 1 +#define STAT_C_CHG_TERM BIT(5) +#define STAT_C_CHARGER_ERROR BIT(6) +#define STAT_E 0x3f + +#define SMB347_MAX_REGISTER 0x3f + +/** + * struct smb347_charger - smb347 charger instance + * @lock: protects concurrent access to online variables + * @dev: pointer to device + * @regmap: pointer to driver regmap + * @mains: power_supply instance for AC/DC power + * @usb: power_supply instance for USB power + * @battery: power_supply instance for battery + * @mains_online: is AC/DC input connected + * @usb_online: is USB input connected + * @charging_enabled: is charging enabled + * @pdata: pointer to platform data + */ +struct smb347_charger { + struct mutex lock; + struct device *dev; + struct regmap *regmap; + struct power_supply *mains; + struct power_supply *usb; + struct power_supply *battery; + bool mains_online; + bool usb_online; + bool charging_enabled; + const struct smb347_charger_platform_data *pdata; +}; + +/* Fast charge current in uA */ +static const unsigned int fcc_tbl[] = { + 700000, + 900000, + 1200000, + 1500000, + 1800000, + 2000000, + 2200000, + 2500000, +}; + +/* Pre-charge current in uA */ +static const unsigned int pcc_tbl[] = { + 100000, + 150000, + 200000, + 250000, +}; + +/* Termination current in uA */ +static const unsigned int tc_tbl[] = { + 37500, + 50000, + 100000, + 150000, + 200000, + 250000, + 500000, + 600000, +}; + +/* Input current limit in uA */ +static const unsigned int icl_tbl[] = { + 300000, + 500000, + 700000, + 900000, + 1200000, + 1500000, + 1800000, + 2000000, + 2200000, + 2500000, +}; + +/* Charge current compensation in uA */ +static const unsigned int ccc_tbl[] = { + 250000, + 700000, + 900000, + 1200000, +}; + +/* Convert register value to current using lookup table */ +static int hw_to_current(const unsigned int *tbl, size_t size, unsigned int val) +{ + if (val >= size) + return -EINVAL; + return tbl[val]; +} + +/* Convert current to register value using lookup table */ +static int current_to_hw(const unsigned int *tbl, size_t size, unsigned int val) +{ + size_t i; + + for (i = 0; i < size; i++) + if (val < tbl[i]) + break; + return i > 0 ? i - 1 : -EINVAL; +} + +/** + * smb347_update_ps_status - refreshes the power source status + * @smb: pointer to smb347 charger instance + * + * Function checks whether any power source is connected to the charger and + * updates internal state accordingly. If there is a change to previous state + * function returns %1, otherwise %0 and negative errno in case of errror. + */ +static int smb347_update_ps_status(struct smb347_charger *smb) +{ + bool usb = false; + bool dc = false; + unsigned int val; + int ret; + + ret = regmap_read(smb->regmap, IRQSTAT_E, &val); + if (ret < 0) + return ret; + + /* + * Dc and usb are set depending on whether they are enabled in + * platform data _and_ whether corresponding undervoltage is set. + */ + if (smb->pdata->use_mains) + dc = !(val & IRQSTAT_E_DCIN_UV_STAT); + if (smb->pdata->use_usb) + usb = !(val & IRQSTAT_E_USBIN_UV_STAT); + + mutex_lock(&smb->lock); + ret = smb->mains_online != dc || smb->usb_online != usb; + smb->mains_online = dc; + smb->usb_online = usb; + mutex_unlock(&smb->lock); + + return ret; +} + +/* + * smb347_is_ps_online - returns whether input power source is connected + * @smb: pointer to smb347 charger instance + * + * Returns %true if input power source is connected. Note that this is + * dependent on what platform has configured for usable power sources. For + * example if USB is disabled, this will return %false even if the USB cable + * is connected. + */ +static bool smb347_is_ps_online(struct smb347_charger *smb) +{ + bool ret; + + mutex_lock(&smb->lock); + ret = smb->usb_online || smb->mains_online; + mutex_unlock(&smb->lock); + + return ret; +} + +/** + * smb347_charging_status - returns status of charging + * @smb: pointer to smb347 charger instance + * + * Function returns charging status. %0 means no charging is in progress, + * %1 means pre-charging, %2 fast-charging and %3 taper-charging. + */ +static int smb347_charging_status(struct smb347_charger *smb) +{ + unsigned int val; + int ret; + + if (!smb347_is_ps_online(smb)) + return 0; + + ret = regmap_read(smb->regmap, STAT_C, &val); + if (ret < 0) + return 0; + + return (val & STAT_C_CHG_MASK) >> STAT_C_CHG_SHIFT; +} + +static int smb347_charging_set(struct smb347_charger *smb, bool enable) +{ + int ret = 0; + + if (smb->pdata->enable_control != SMB347_CHG_ENABLE_SW) { + dev_dbg(smb->dev, "charging enable/disable in SW disabled\n"); + return 0; + } + + mutex_lock(&smb->lock); + if (smb->charging_enabled != enable) { + ret = regmap_update_bits(smb->regmap, CMD_A, CMD_A_CHG_ENABLED, + enable ? CMD_A_CHG_ENABLED : 0); + if (!ret) + smb->charging_enabled = enable; + } + mutex_unlock(&smb->lock); + return ret; +} + +static inline int smb347_charging_enable(struct smb347_charger *smb) +{ + return smb347_charging_set(smb, true); +} + +static inline int smb347_charging_disable(struct smb347_charger *smb) +{ + return smb347_charging_set(smb, false); +} + +static int smb347_start_stop_charging(struct smb347_charger *smb) +{ + int ret; + + /* + * Depending on whether valid power source is connected or not, we + * disable or enable the charging. We do it manually because it + * depends on how the platform has configured the valid inputs. + */ + if (smb347_is_ps_online(smb)) { + ret = smb347_charging_enable(smb); + if (ret < 0) + dev_err(smb->dev, "failed to enable charging\n"); + } else { + ret = smb347_charging_disable(smb); + if (ret < 0) + dev_err(smb->dev, "failed to disable charging\n"); + } + + return ret; +} + +static int smb347_set_charge_current(struct smb347_charger *smb) +{ + int ret; + + if (smb->pdata->max_charge_current) { + ret = current_to_hw(fcc_tbl, ARRAY_SIZE(fcc_tbl), + smb->pdata->max_charge_current); + if (ret < 0) + return ret; + + ret = regmap_update_bits(smb->regmap, CFG_CHARGE_CURRENT, + CFG_CHARGE_CURRENT_FCC_MASK, + ret << CFG_CHARGE_CURRENT_FCC_SHIFT); + if (ret < 0) + return ret; + } + + if (smb->pdata->pre_charge_current) { + ret = current_to_hw(pcc_tbl, ARRAY_SIZE(pcc_tbl), + smb->pdata->pre_charge_current); + if (ret < 0) + return ret; + + ret = regmap_update_bits(smb->regmap, CFG_CHARGE_CURRENT, + CFG_CHARGE_CURRENT_PCC_MASK, + ret << CFG_CHARGE_CURRENT_PCC_SHIFT); + if (ret < 0) + return ret; + } + + if (smb->pdata->termination_current) { + ret = current_to_hw(tc_tbl, ARRAY_SIZE(tc_tbl), + smb->pdata->termination_current); + if (ret < 0) + return ret; + + ret = regmap_update_bits(smb->regmap, CFG_CHARGE_CURRENT, + CFG_CHARGE_CURRENT_TC_MASK, ret); + if (ret < 0) + return ret; + } + + return 0; +} + +static int smb347_set_current_limits(struct smb347_charger *smb) +{ + int ret; + + if (smb->pdata->mains_current_limit) { + ret = current_to_hw(icl_tbl, ARRAY_SIZE(icl_tbl), + smb->pdata->mains_current_limit); + if (ret < 0) + return ret; + + ret = regmap_update_bits(smb->regmap, CFG_CURRENT_LIMIT, + CFG_CURRENT_LIMIT_DC_MASK, + ret << CFG_CURRENT_LIMIT_DC_SHIFT); + if (ret < 0) + return ret; + } + + if (smb->pdata->usb_hc_current_limit) { + ret = current_to_hw(icl_tbl, ARRAY_SIZE(icl_tbl), + smb->pdata->usb_hc_current_limit); + if (ret < 0) + return ret; + + ret = regmap_update_bits(smb->regmap, CFG_CURRENT_LIMIT, + CFG_CURRENT_LIMIT_USB_MASK, ret); + if (ret < 0) + return ret; + } + + return 0; +} + +static int smb347_set_voltage_limits(struct smb347_charger *smb) +{ + int ret; + + if (smb->pdata->pre_to_fast_voltage) { + ret = smb->pdata->pre_to_fast_voltage; + + /* uV */ + ret = clamp_val(ret, 2400000, 3000000) - 2400000; + ret /= 200000; + + ret = regmap_update_bits(smb->regmap, CFG_FLOAT_VOLTAGE, + CFG_FLOAT_VOLTAGE_THRESHOLD_MASK, + ret << CFG_FLOAT_VOLTAGE_THRESHOLD_SHIFT); + if (ret < 0) + return ret; + } + + if (smb->pdata->max_charge_voltage) { + ret = smb->pdata->max_charge_voltage; + + /* uV */ + ret = clamp_val(ret, 3500000, 4500000) - 3500000; + ret /= 20000; + + ret = regmap_update_bits(smb->regmap, CFG_FLOAT_VOLTAGE, + CFG_FLOAT_VOLTAGE_FLOAT_MASK, ret); + if (ret < 0) + return ret; + } + + return 0; +} + +static int smb347_set_temp_limits(struct smb347_charger *smb) +{ + bool enable_therm_monitor = false; + int ret = 0; + int val; + + if (smb->pdata->chip_temp_threshold) { + val = smb->pdata->chip_temp_threshold; + + /* degree C */ + val = clamp_val(val, 100, 130) - 100; + val /= 10; + + ret = regmap_update_bits(smb->regmap, CFG_OTG, + CFG_OTG_TEMP_THRESHOLD_MASK, + val << CFG_OTG_TEMP_THRESHOLD_SHIFT); + if (ret < 0) + return ret; + } + + if (smb->pdata->soft_cold_temp_limit != SMB347_TEMP_USE_DEFAULT) { + val = smb->pdata->soft_cold_temp_limit; + + val = clamp_val(val, 0, 15); + val /= 5; + /* this goes from higher to lower so invert the value */ + val = ~val & 0x3; + + ret = regmap_update_bits(smb->regmap, CFG_TEMP_LIMIT, + CFG_TEMP_LIMIT_SOFT_COLD_MASK, + val << CFG_TEMP_LIMIT_SOFT_COLD_SHIFT); + if (ret < 0) + return ret; + + enable_therm_monitor = true; + } + + if (smb->pdata->soft_hot_temp_limit != SMB347_TEMP_USE_DEFAULT) { + val = smb->pdata->soft_hot_temp_limit; + + val = clamp_val(val, 40, 55) - 40; + val /= 5; + + ret = regmap_update_bits(smb->regmap, CFG_TEMP_LIMIT, + CFG_TEMP_LIMIT_SOFT_HOT_MASK, + val << CFG_TEMP_LIMIT_SOFT_HOT_SHIFT); + if (ret < 0) + return ret; + + enable_therm_monitor = true; + } + + if (smb->pdata->hard_cold_temp_limit != SMB347_TEMP_USE_DEFAULT) { + val = smb->pdata->hard_cold_temp_limit; + + val = clamp_val(val, -5, 10) + 5; + val /= 5; + /* this goes from higher to lower so invert the value */ + val = ~val & 0x3; + + ret = regmap_update_bits(smb->regmap, CFG_TEMP_LIMIT, + CFG_TEMP_LIMIT_HARD_COLD_MASK, + val << CFG_TEMP_LIMIT_HARD_COLD_SHIFT); + if (ret < 0) + return ret; + + enable_therm_monitor = true; + } + + if (smb->pdata->hard_hot_temp_limit != SMB347_TEMP_USE_DEFAULT) { + val = smb->pdata->hard_hot_temp_limit; + + val = clamp_val(val, 50, 65) - 50; + val /= 5; + + ret = regmap_update_bits(smb->regmap, CFG_TEMP_LIMIT, + CFG_TEMP_LIMIT_HARD_HOT_MASK, + val << CFG_TEMP_LIMIT_HARD_HOT_SHIFT); + if (ret < 0) + return ret; + + enable_therm_monitor = true; + } + + /* + * If any of the temperature limits are set, we also enable the + * thermistor monitoring. + * + * When soft limits are hit, the device will start to compensate + * current and/or voltage depending on the configuration. + * + * When hard limit is hit, the device will suspend charging + * depending on the configuration. + */ + if (enable_therm_monitor) { + ret = regmap_update_bits(smb->regmap, CFG_THERM, + CFG_THERM_MONITOR_DISABLED, 0); + if (ret < 0) + return ret; + } + + if (smb->pdata->suspend_on_hard_temp_limit) { + ret = regmap_update_bits(smb->regmap, CFG_SYSOK, + CFG_SYSOK_SUSPEND_HARD_LIMIT_DISABLED, 0); + if (ret < 0) + return ret; + } + + if (smb->pdata->soft_temp_limit_compensation != + SMB347_SOFT_TEMP_COMPENSATE_DEFAULT) { + val = smb->pdata->soft_temp_limit_compensation & 0x3; + + ret = regmap_update_bits(smb->regmap, CFG_THERM, + CFG_THERM_SOFT_HOT_COMPENSATION_MASK, + val << CFG_THERM_SOFT_HOT_COMPENSATION_SHIFT); + if (ret < 0) + return ret; + + ret = regmap_update_bits(smb->regmap, CFG_THERM, + CFG_THERM_SOFT_COLD_COMPENSATION_MASK, + val << CFG_THERM_SOFT_COLD_COMPENSATION_SHIFT); + if (ret < 0) + return ret; + } + + if (smb->pdata->charge_current_compensation) { + val = current_to_hw(ccc_tbl, ARRAY_SIZE(ccc_tbl), + smb->pdata->charge_current_compensation); + if (val < 0) + return val; + + ret = regmap_update_bits(smb->regmap, CFG_OTG, + CFG_OTG_CC_COMPENSATION_MASK, + (val & 0x3) << CFG_OTG_CC_COMPENSATION_SHIFT); + if (ret < 0) + return ret; + } + + return ret; +} + +/* + * smb347_set_writable - enables/disables writing to non-volatile registers + * @smb: pointer to smb347 charger instance + * + * You can enable/disable writing to the non-volatile configuration + * registers by calling this function. + * + * Returns %0 on success and negative errno in case of failure. + */ +static int smb347_set_writable(struct smb347_charger *smb, bool writable) +{ + return regmap_update_bits(smb->regmap, CMD_A, CMD_A_ALLOW_WRITE, + writable ? CMD_A_ALLOW_WRITE : 0); +} + +static int smb347_hw_init(struct smb347_charger *smb) +{ + unsigned int val; + int ret; + + ret = smb347_set_writable(smb, true); + if (ret < 0) + return ret; + + /* + * Program the platform specific configuration values to the device + * first. + */ + ret = smb347_set_charge_current(smb); + if (ret < 0) + goto fail; + + ret = smb347_set_current_limits(smb); + if (ret < 0) + goto fail; + + ret = smb347_set_voltage_limits(smb); + if (ret < 0) + goto fail; + + ret = smb347_set_temp_limits(smb); + if (ret < 0) + goto fail; + + /* If USB charging is disabled we put the USB in suspend mode */ + if (!smb->pdata->use_usb) { + ret = regmap_update_bits(smb->regmap, CMD_A, + CMD_A_SUSPEND_ENABLED, + CMD_A_SUSPEND_ENABLED); + if (ret < 0) + goto fail; + } + + /* + * If configured by platform data, we enable hardware Auto-OTG + * support for driving VBUS. Otherwise we disable it. + */ + ret = regmap_update_bits(smb->regmap, CFG_OTHER, CFG_OTHER_RID_MASK, + smb->pdata->use_usb_otg ? CFG_OTHER_RID_ENABLED_AUTO_OTG : 0); + if (ret < 0) + goto fail; + + /* + * Make the charging functionality controllable by a write to the + * command register unless pin control is specified in the platform + * data. + */ + switch (smb->pdata->enable_control) { + case SMB347_CHG_ENABLE_PIN_ACTIVE_LOW: + val = CFG_PIN_EN_CTRL_ACTIVE_LOW; + break; + case SMB347_CHG_ENABLE_PIN_ACTIVE_HIGH: + val = CFG_PIN_EN_CTRL_ACTIVE_HIGH; + break; + default: + val = 0; + break; + } + + ret = regmap_update_bits(smb->regmap, CFG_PIN, CFG_PIN_EN_CTRL_MASK, + val); + if (ret < 0) + goto fail; + + /* Disable Automatic Power Source Detection (APSD) interrupt. */ + ret = regmap_update_bits(smb->regmap, CFG_PIN, CFG_PIN_EN_APSD_IRQ, 0); + if (ret < 0) + goto fail; + + ret = smb347_update_ps_status(smb); + if (ret < 0) + goto fail; + + ret = smb347_start_stop_charging(smb); + +fail: + smb347_set_writable(smb, false); + return ret; +} + +static irqreturn_t smb347_interrupt(int irq, void *data) +{ + struct smb347_charger *smb = data; + unsigned int stat_c, irqstat_c, irqstat_d, irqstat_e; + bool handled = false; + int ret; + + ret = regmap_read(smb->regmap, STAT_C, &stat_c); + if (ret < 0) { + dev_warn(smb->dev, "reading STAT_C failed\n"); + return IRQ_NONE; + } + + ret = regmap_read(smb->regmap, IRQSTAT_C, &irqstat_c); + if (ret < 0) { + dev_warn(smb->dev, "reading IRQSTAT_C failed\n"); + return IRQ_NONE; + } + + ret = regmap_read(smb->regmap, IRQSTAT_D, &irqstat_d); + if (ret < 0) { + dev_warn(smb->dev, "reading IRQSTAT_D failed\n"); + return IRQ_NONE; + } + + ret = regmap_read(smb->regmap, IRQSTAT_E, &irqstat_e); + if (ret < 0) { + dev_warn(smb->dev, "reading IRQSTAT_E failed\n"); + return IRQ_NONE; + } + + /* + * If we get charger error we report the error back to user. + * If the error is recovered charging will resume again. + */ + if (stat_c & STAT_C_CHARGER_ERROR) { + dev_err(smb->dev, "charging stopped due to charger error\n"); + power_supply_changed(smb->battery); + handled = true; + } + + /* + * If we reached the termination current the battery is charged and + * we can update the status now. Charging is automatically + * disabled by the hardware. + */ + if (irqstat_c & (IRQSTAT_C_TERMINATION_IRQ | IRQSTAT_C_TAPER_IRQ)) { + if (irqstat_c & IRQSTAT_C_TERMINATION_STAT) + power_supply_changed(smb->battery); + dev_dbg(smb->dev, "going to HW maintenance mode\n"); + handled = true; + } + + /* + * If we got a charger timeout INT that means the charge + * full is not detected with in charge timeout value. + */ + if (irqstat_d & IRQSTAT_D_CHARGE_TIMEOUT_IRQ) { + dev_dbg(smb->dev, "total Charge Timeout INT received\n"); + + if (irqstat_d & IRQSTAT_D_CHARGE_TIMEOUT_STAT) + dev_warn(smb->dev, "charging stopped due to timeout\n"); + power_supply_changed(smb->battery); + handled = true; + } + + /* + * If we got an under voltage interrupt it means that AC/USB input + * was connected or disconnected. + */ + if (irqstat_e & (IRQSTAT_E_USBIN_UV_IRQ | IRQSTAT_E_DCIN_UV_IRQ)) { + if (smb347_update_ps_status(smb) > 0) { + smb347_start_stop_charging(smb); + if (smb->pdata->use_mains) + power_supply_changed(smb->mains); + if (smb->pdata->use_usb) + power_supply_changed(smb->usb); + } + handled = true; + } + + return handled ? IRQ_HANDLED : IRQ_NONE; +} + +static int smb347_irq_set(struct smb347_charger *smb, bool enable) +{ + int ret; + + ret = smb347_set_writable(smb, true); + if (ret < 0) + return ret; + + /* + * Enable/disable interrupts for: + * - under voltage + * - termination current reached + * - charger timeout + * - charger error + */ + ret = regmap_update_bits(smb->regmap, CFG_FAULT_IRQ, 0xff, + enable ? CFG_FAULT_IRQ_DCIN_UV : 0); + if (ret < 0) + goto fail; + + ret = regmap_update_bits(smb->regmap, CFG_STATUS_IRQ, 0xff, + enable ? (CFG_STATUS_IRQ_TERMINATION_OR_TAPER | + CFG_STATUS_IRQ_CHARGE_TIMEOUT) : 0); + if (ret < 0) + goto fail; + + ret = regmap_update_bits(smb->regmap, CFG_PIN, CFG_PIN_EN_CHARGER_ERROR, + enable ? CFG_PIN_EN_CHARGER_ERROR : 0); +fail: + smb347_set_writable(smb, false); + return ret; +} + +static inline int smb347_irq_enable(struct smb347_charger *smb) +{ + return smb347_irq_set(smb, true); +} + +static inline int smb347_irq_disable(struct smb347_charger *smb) +{ + return smb347_irq_set(smb, false); +} + +static int smb347_irq_init(struct smb347_charger *smb, + struct i2c_client *client) +{ + const struct smb347_charger_platform_data *pdata = smb->pdata; + int ret, irq = gpio_to_irq(pdata->irq_gpio); + + ret = gpio_request_one(pdata->irq_gpio, GPIOF_IN, client->name); + if (ret < 0) + goto fail; + + ret = request_threaded_irq(irq, NULL, smb347_interrupt, + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + client->name, smb); + if (ret < 0) + goto fail_gpio; + + ret = smb347_set_writable(smb, true); + if (ret < 0) + goto fail_irq; + + /* + * Configure the STAT output to be suitable for interrupts: disable + * all other output (except interrupts) and make it active low. + */ + ret = regmap_update_bits(smb->regmap, CFG_STAT, + CFG_STAT_ACTIVE_HIGH | CFG_STAT_DISABLED, + CFG_STAT_DISABLED); + if (ret < 0) + goto fail_readonly; + + smb347_set_writable(smb, false); + client->irq = irq; + return 0; + +fail_readonly: + smb347_set_writable(smb, false); +fail_irq: + free_irq(irq, smb); +fail_gpio: + gpio_free(pdata->irq_gpio); +fail: + client->irq = 0; + return ret; +} + +/* + * Returns the constant charge current programmed + * into the charger in uA. + */ +static int get_const_charge_current(struct smb347_charger *smb) +{ + int ret, intval; + unsigned int v; + + if (!smb347_is_ps_online(smb)) + return -ENODATA; + + ret = regmap_read(smb->regmap, STAT_B, &v); + if (ret < 0) + return ret; + + /* + * The current value is composition of FCC and PCC values + * and we can detect which table to use from bit 5. + */ + if (v & 0x20) { + intval = hw_to_current(fcc_tbl, ARRAY_SIZE(fcc_tbl), v & 7); + } else { + v >>= 3; + intval = hw_to_current(pcc_tbl, ARRAY_SIZE(pcc_tbl), v & 7); + } + + return intval; +} + +/* + * Returns the constant charge voltage programmed + * into the charger in uV. + */ +static int get_const_charge_voltage(struct smb347_charger *smb) +{ + int ret, intval; + unsigned int v; + + if (!smb347_is_ps_online(smb)) + return -ENODATA; + + ret = regmap_read(smb->regmap, STAT_A, &v); + if (ret < 0) + return ret; + + v &= STAT_A_FLOAT_VOLTAGE_MASK; + if (v > 0x3d) + v = 0x3d; + + intval = 3500000 + v * 20000; + + return intval; +} + +static int smb347_mains_get_property(struct power_supply *psy, + enum power_supply_property prop, + union power_supply_propval *val) +{ + struct smb347_charger *smb = power_supply_get_drvdata(psy); + int ret; + + switch (prop) { + case POWER_SUPPLY_PROP_ONLINE: + val->intval = smb->mains_online; + break; + + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: + ret = get_const_charge_voltage(smb); + if (ret < 0) + return ret; + else + val->intval = ret; + break; + + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: + ret = get_const_charge_current(smb); + if (ret < 0) + return ret; + else + val->intval = ret; + break; + + default: + return -EINVAL; + } + + return 0; +} + +static enum power_supply_property smb347_mains_properties[] = { + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE, +}; + +static int smb347_usb_get_property(struct power_supply *psy, + enum power_supply_property prop, + union power_supply_propval *val) +{ + struct smb347_charger *smb = power_supply_get_drvdata(psy); + int ret; + + switch (prop) { + case POWER_SUPPLY_PROP_ONLINE: + val->intval = smb->usb_online; + break; + + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: + ret = get_const_charge_voltage(smb); + if (ret < 0) + return ret; + else + val->intval = ret; + break; + + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: + ret = get_const_charge_current(smb); + if (ret < 0) + return ret; + else + val->intval = ret; + break; + + default: + return -EINVAL; + } + + return 0; +} + +static enum power_supply_property smb347_usb_properties[] = { + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE, +}; + +static int smb347_get_charging_status(struct smb347_charger *smb) +{ + int ret, status; + unsigned int val; + + if (!smb347_is_ps_online(smb)) + return POWER_SUPPLY_STATUS_DISCHARGING; + + ret = regmap_read(smb->regmap, STAT_C, &val); + if (ret < 0) + return ret; + + if ((val & STAT_C_CHARGER_ERROR) || + (val & STAT_C_HOLDOFF_STAT)) { + /* + * set to NOT CHARGING upon charger error + * or charging has stopped. + */ + status = POWER_SUPPLY_STATUS_NOT_CHARGING; + } else { + if ((val & STAT_C_CHG_MASK) >> STAT_C_CHG_SHIFT) { + /* + * set to charging if battery is in pre-charge, + * fast charge or taper charging mode. + */ + status = POWER_SUPPLY_STATUS_CHARGING; + } else if (val & STAT_C_CHG_TERM) { + /* + * set the status to FULL if battery is not in pre + * charge, fast charge or taper charging mode AND + * charging is terminated at least once. + */ + status = POWER_SUPPLY_STATUS_FULL; + } else { + /* + * in this case no charger error or termination + * occured but charging is not in progress!!! + */ + status = POWER_SUPPLY_STATUS_NOT_CHARGING; + } + } + + return status; +} + +static int smb347_battery_get_property(struct power_supply *psy, + enum power_supply_property prop, + union power_supply_propval *val) +{ + struct smb347_charger *smb = power_supply_get_drvdata(psy); + const struct smb347_charger_platform_data *pdata = smb->pdata; + int ret; + + ret = smb347_update_ps_status(smb); + if (ret < 0) + return ret; + + switch (prop) { + case POWER_SUPPLY_PROP_STATUS: + ret = smb347_get_charging_status(smb); + if (ret < 0) + return ret; + val->intval = ret; + break; + + case POWER_SUPPLY_PROP_CHARGE_TYPE: + if (!smb347_is_ps_online(smb)) + return -ENODATA; + + /* + * We handle trickle and pre-charging the same, and taper + * and none the same. + */ + switch (smb347_charging_status(smb)) { + case 1: + val->intval = POWER_SUPPLY_CHARGE_TYPE_TRICKLE; + break; + case 2: + val->intval = POWER_SUPPLY_CHARGE_TYPE_FAST; + break; + default: + val->intval = POWER_SUPPLY_CHARGE_TYPE_NONE; + break; + } + break; + + case POWER_SUPPLY_PROP_TECHNOLOGY: + val->intval = pdata->battery_info.technology; + break; + + case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: + val->intval = pdata->battery_info.voltage_min_design; + break; + + case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: + val->intval = pdata->battery_info.voltage_max_design; + break; + + case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: + val->intval = pdata->battery_info.charge_full_design; + break; + + case POWER_SUPPLY_PROP_MODEL_NAME: + val->strval = pdata->battery_info.name; + break; + + default: + return -EINVAL; + } + + return 0; +} + +static enum power_supply_property smb347_battery_properties[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_CHARGE_TYPE, + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, + POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, + POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, + POWER_SUPPLY_PROP_MODEL_NAME, +}; + +static bool smb347_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case IRQSTAT_A: + case IRQSTAT_C: + case IRQSTAT_E: + case IRQSTAT_F: + case STAT_A: + case STAT_B: + case STAT_C: + case STAT_E: + return true; + } + + return false; +} + +static bool smb347_readable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case CFG_CHARGE_CURRENT: + case CFG_CURRENT_LIMIT: + case CFG_FLOAT_VOLTAGE: + case CFG_STAT: + case CFG_PIN: + case CFG_THERM: + case CFG_SYSOK: + case CFG_OTHER: + case CFG_OTG: + case CFG_TEMP_LIMIT: + case CFG_FAULT_IRQ: + case CFG_STATUS_IRQ: + case CFG_ADDRESS: + case CMD_A: + case CMD_B: + case CMD_C: + return true; + } + + return smb347_volatile_reg(dev, reg); +} + +static const struct regmap_config smb347_regmap = { + .reg_bits = 8, + .val_bits = 8, + .max_register = SMB347_MAX_REGISTER, + .volatile_reg = smb347_volatile_reg, + .readable_reg = smb347_readable_reg, +}; + +static const struct power_supply_desc smb347_mains_desc = { + .name = "smb347-mains", + .type = POWER_SUPPLY_TYPE_MAINS, + .get_property = smb347_mains_get_property, + .properties = smb347_mains_properties, + .num_properties = ARRAY_SIZE(smb347_mains_properties), +}; + +static const struct power_supply_desc smb347_usb_desc = { + .name = "smb347-usb", + .type = POWER_SUPPLY_TYPE_USB, + .get_property = smb347_usb_get_property, + .properties = smb347_usb_properties, + .num_properties = ARRAY_SIZE(smb347_usb_properties), +}; + +static const struct power_supply_desc smb347_battery_desc = { + .name = "smb347-battery", + .type = POWER_SUPPLY_TYPE_BATTERY, + .get_property = smb347_battery_get_property, + .properties = smb347_battery_properties, + .num_properties = ARRAY_SIZE(smb347_battery_properties), +}; + +static int smb347_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + static char *battery[] = { "smb347-battery" }; + const struct smb347_charger_platform_data *pdata; + struct power_supply_config mains_usb_cfg = {}, battery_cfg = {}; + struct device *dev = &client->dev; + struct smb347_charger *smb; + int ret; + + pdata = dev->platform_data; + if (!pdata) + return -EINVAL; + + if (!pdata->use_mains && !pdata->use_usb) + return -EINVAL; + + smb = devm_kzalloc(dev, sizeof(*smb), GFP_KERNEL); + if (!smb) + return -ENOMEM; + + i2c_set_clientdata(client, smb); + + mutex_init(&smb->lock); + smb->dev = &client->dev; + smb->pdata = pdata; + + smb->regmap = devm_regmap_init_i2c(client, &smb347_regmap); + if (IS_ERR(smb->regmap)) + return PTR_ERR(smb->regmap); + + ret = smb347_hw_init(smb); + if (ret < 0) + return ret; + + mains_usb_cfg.supplied_to = battery; + mains_usb_cfg.num_supplicants = ARRAY_SIZE(battery); + mains_usb_cfg.drv_data = smb; + if (smb->pdata->use_mains) { + smb->mains = power_supply_register(dev, &smb347_mains_desc, + &mains_usb_cfg); + if (IS_ERR(smb->mains)) + return PTR_ERR(smb->mains); + } + + if (smb->pdata->use_usb) { + smb->usb = power_supply_register(dev, &smb347_usb_desc, + &mains_usb_cfg); + if (IS_ERR(smb->usb)) { + if (smb->pdata->use_mains) + power_supply_unregister(smb->mains); + return PTR_ERR(smb->usb); + } + } + + battery_cfg.drv_data = smb; + smb->battery = power_supply_register(dev, &smb347_battery_desc, + &battery_cfg); + if (IS_ERR(smb->battery)) { + if (smb->pdata->use_usb) + power_supply_unregister(smb->usb); + if (smb->pdata->use_mains) + power_supply_unregister(smb->mains); + return PTR_ERR(smb->battery); + } + + /* + * Interrupt pin is optional. If it is connected, we setup the + * interrupt support here. + */ + if (pdata->irq_gpio >= 0) { + ret = smb347_irq_init(smb, client); + if (ret < 0) { + dev_warn(dev, "failed to initialize IRQ: %d\n", ret); + dev_warn(dev, "disabling IRQ support\n"); + } else { + smb347_irq_enable(smb); + } + } + + return 0; +} + +static int smb347_remove(struct i2c_client *client) +{ + struct smb347_charger *smb = i2c_get_clientdata(client); + + if (client->irq) { + smb347_irq_disable(smb); + free_irq(client->irq, smb); + gpio_free(smb->pdata->irq_gpio); + } + + power_supply_unregister(smb->battery); + if (smb->pdata->use_usb) + power_supply_unregister(smb->usb); + if (smb->pdata->use_mains) + power_supply_unregister(smb->mains); + return 0; +} + +static const struct i2c_device_id smb347_id[] = { + { "smb347", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, smb347_id); + +static struct i2c_driver smb347_driver = { + .driver = { + .name = "smb347", + }, + .probe = smb347_probe, + .remove = smb347_remove, + .id_table = smb347_id, +}; + +module_i2c_driver(smb347_driver); + +MODULE_AUTHOR("Bruce E. Robertson "); +MODULE_AUTHOR("Mika Westerberg "); +MODULE_DESCRIPTION("SMB347 battery charger driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/power/supply/test_power.c b/drivers/power/supply/test_power.c new file mode 100644 index 000000000000..57246cdbd042 --- /dev/null +++ b/drivers/power/supply/test_power.c @@ -0,0 +1,533 @@ +/* + * Power supply driver for testing. + * + * Copyright 2010 Anton Vorontsov + * + * Dynamic module parameter code from the Virtual Battery Driver + * Copyright (C) 2008 Pylone, Inc. + * By: Masashi YOKOTA + * Originally found here: + * http://downloads.pylone.jp/src/virtual_battery/virtual_battery-0.0.1.tar.bz2 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include + +enum test_power_id { + TEST_AC, + TEST_BATTERY, + TEST_USB, + TEST_POWER_NUM, +}; + +static int ac_online = 1; +static int usb_online = 1; +static int battery_status = POWER_SUPPLY_STATUS_DISCHARGING; +static int battery_health = POWER_SUPPLY_HEALTH_GOOD; +static int battery_present = 1; /* true */ +static int battery_technology = POWER_SUPPLY_TECHNOLOGY_LION; +static int battery_capacity = 50; +static int battery_voltage = 3300; + +static bool module_initialized; + +static int test_power_get_ac_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + val->intval = ac_online; + break; + default: + return -EINVAL; + } + return 0; +} + +static int test_power_get_usb_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + val->intval = usb_online; + break; + default: + return -EINVAL; + } + return 0; +} + +static int test_power_get_battery_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + switch (psp) { + case POWER_SUPPLY_PROP_MODEL_NAME: + val->strval = "Test battery"; + break; + case POWER_SUPPLY_PROP_MANUFACTURER: + val->strval = "Linux"; + break; + case POWER_SUPPLY_PROP_SERIAL_NUMBER: + val->strval = UTS_RELEASE; + break; + case POWER_SUPPLY_PROP_STATUS: + val->intval = battery_status; + break; + case POWER_SUPPLY_PROP_CHARGE_TYPE: + val->intval = POWER_SUPPLY_CHARGE_TYPE_FAST; + break; + case POWER_SUPPLY_PROP_HEALTH: + val->intval = battery_health; + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = battery_present; + break; + case POWER_SUPPLY_PROP_TECHNOLOGY: + val->intval = battery_technology; + break; + case POWER_SUPPLY_PROP_CAPACITY_LEVEL: + val->intval = POWER_SUPPLY_CAPACITY_LEVEL_NORMAL; + break; + case POWER_SUPPLY_PROP_CAPACITY: + case POWER_SUPPLY_PROP_CHARGE_NOW: + val->intval = battery_capacity; + break; + case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: + case POWER_SUPPLY_PROP_CHARGE_FULL: + val->intval = 100; + break; + case POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG: + case POWER_SUPPLY_PROP_TIME_TO_FULL_NOW: + val->intval = 3600; + break; + case POWER_SUPPLY_PROP_TEMP: + val->intval = 26; + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + val->intval = battery_voltage; + break; + default: + pr_info("%s: some properties deliberately report errors.\n", + __func__); + return -EINVAL; + } + return 0; +} + +static enum power_supply_property test_power_ac_props[] = { + POWER_SUPPLY_PROP_ONLINE, +}; + +static enum power_supply_property test_power_battery_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_CHARGE_TYPE, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, + POWER_SUPPLY_PROP_CHARGE_FULL, + POWER_SUPPLY_PROP_CHARGE_NOW, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_CAPACITY_LEVEL, + POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG, + POWER_SUPPLY_PROP_TIME_TO_FULL_NOW, + POWER_SUPPLY_PROP_MODEL_NAME, + POWER_SUPPLY_PROP_MANUFACTURER, + POWER_SUPPLY_PROP_SERIAL_NUMBER, + POWER_SUPPLY_PROP_TEMP, + POWER_SUPPLY_PROP_VOLTAGE_NOW, +}; + +static char *test_power_ac_supplied_to[] = { + "test_battery", +}; + +static struct power_supply *test_power_supplies[TEST_POWER_NUM]; + +static const struct power_supply_desc test_power_desc[] = { + [TEST_AC] = { + .name = "test_ac", + .type = POWER_SUPPLY_TYPE_MAINS, + .properties = test_power_ac_props, + .num_properties = ARRAY_SIZE(test_power_ac_props), + .get_property = test_power_get_ac_property, + }, + [TEST_BATTERY] = { + .name = "test_battery", + .type = POWER_SUPPLY_TYPE_BATTERY, + .properties = test_power_battery_props, + .num_properties = ARRAY_SIZE(test_power_battery_props), + .get_property = test_power_get_battery_property, + }, + [TEST_USB] = { + .name = "test_usb", + .type = POWER_SUPPLY_TYPE_USB, + .properties = test_power_ac_props, + .num_properties = ARRAY_SIZE(test_power_ac_props), + .get_property = test_power_get_usb_property, + }, +}; + +static const struct power_supply_config test_power_configs[] = { + { + /* test_ac */ + .supplied_to = test_power_ac_supplied_to, + .num_supplicants = ARRAY_SIZE(test_power_ac_supplied_to), + }, { + /* test_battery */ + }, { + /* test_usb */ + .supplied_to = test_power_ac_supplied_to, + .num_supplicants = ARRAY_SIZE(test_power_ac_supplied_to), + }, +}; + +static int __init test_power_init(void) +{ + int i; + int ret; + + BUILD_BUG_ON(TEST_POWER_NUM != ARRAY_SIZE(test_power_supplies)); + BUILD_BUG_ON(TEST_POWER_NUM != ARRAY_SIZE(test_power_configs)); + + for (i = 0; i < ARRAY_SIZE(test_power_supplies); i++) { + test_power_supplies[i] = power_supply_register(NULL, + &test_power_desc[i], + &test_power_configs[i]); + if (IS_ERR(test_power_supplies[i])) { + pr_err("%s: failed to register %s\n", __func__, + test_power_desc[i].name); + ret = PTR_ERR(test_power_supplies[i]); + goto failed; + } + } + + module_initialized = true; + return 0; +failed: + while (--i >= 0) + power_supply_unregister(test_power_supplies[i]); + return ret; +} +module_init(test_power_init); + +static void __exit test_power_exit(void) +{ + int i; + + /* Let's see how we handle changes... */ + ac_online = 0; + usb_online = 0; + battery_status = POWER_SUPPLY_STATUS_DISCHARGING; + for (i = 0; i < ARRAY_SIZE(test_power_supplies); i++) + power_supply_changed(test_power_supplies[i]); + pr_info("%s: 'changed' event sent, sleeping for 10 seconds...\n", + __func__); + ssleep(10); + + for (i = 0; i < ARRAY_SIZE(test_power_supplies); i++) + power_supply_unregister(test_power_supplies[i]); + + module_initialized = false; +} +module_exit(test_power_exit); + + + +#define MAX_KEYLENGTH 256 +struct battery_property_map { + int value; + char const *key; +}; + +static struct battery_property_map map_ac_online[] = { + { 0, "off" }, + { 1, "on" }, + { -1, NULL }, +}; + +static struct battery_property_map map_status[] = { + { POWER_SUPPLY_STATUS_CHARGING, "charging" }, + { POWER_SUPPLY_STATUS_DISCHARGING, "discharging" }, + { POWER_SUPPLY_STATUS_NOT_CHARGING, "not-charging" }, + { POWER_SUPPLY_STATUS_FULL, "full" }, + { -1, NULL }, +}; + +static struct battery_property_map map_health[] = { + { POWER_SUPPLY_HEALTH_GOOD, "good" }, + { POWER_SUPPLY_HEALTH_OVERHEAT, "overheat" }, + { POWER_SUPPLY_HEALTH_DEAD, "dead" }, + { POWER_SUPPLY_HEALTH_OVERVOLTAGE, "overvoltage" }, + { POWER_SUPPLY_HEALTH_UNSPEC_FAILURE, "failure" }, + { -1, NULL }, +}; + +static struct battery_property_map map_present[] = { + { 0, "false" }, + { 1, "true" }, + { -1, NULL }, +}; + +static struct battery_property_map map_technology[] = { + { POWER_SUPPLY_TECHNOLOGY_NiMH, "NiMH" }, + { POWER_SUPPLY_TECHNOLOGY_LION, "LION" }, + { POWER_SUPPLY_TECHNOLOGY_LIPO, "LIPO" }, + { POWER_SUPPLY_TECHNOLOGY_LiFe, "LiFe" }, + { POWER_SUPPLY_TECHNOLOGY_NiCd, "NiCd" }, + { POWER_SUPPLY_TECHNOLOGY_LiMn, "LiMn" }, + { -1, NULL }, +}; + + +static int map_get_value(struct battery_property_map *map, const char *key, + int def_val) +{ + char buf[MAX_KEYLENGTH]; + int cr; + + strncpy(buf, key, MAX_KEYLENGTH); + buf[MAX_KEYLENGTH-1] = '\0'; + + cr = strnlen(buf, MAX_KEYLENGTH) - 1; + if (cr < 0) + return def_val; + if (buf[cr] == '\n') + buf[cr] = '\0'; + + while (map->key) { + if (strncasecmp(map->key, buf, MAX_KEYLENGTH) == 0) + return map->value; + map++; + } + + return def_val; +} + + +static const char *map_get_key(struct battery_property_map *map, int value, + const char *def_key) +{ + while (map->key) { + if (map->value == value) + return map->key; + map++; + } + + return def_key; +} + +static inline void signal_power_supply_changed(struct power_supply *psy) +{ + if (module_initialized) + power_supply_changed(psy); +} + +static int param_set_ac_online(const char *key, const struct kernel_param *kp) +{ + ac_online = map_get_value(map_ac_online, key, ac_online); + signal_power_supply_changed(test_power_supplies[TEST_AC]); + return 0; +} + +static int param_get_ac_online(char *buffer, const struct kernel_param *kp) +{ + strcpy(buffer, map_get_key(map_ac_online, ac_online, "unknown")); + return strlen(buffer); +} + +static int param_set_usb_online(const char *key, const struct kernel_param *kp) +{ + usb_online = map_get_value(map_ac_online, key, usb_online); + signal_power_supply_changed(test_power_supplies[TEST_USB]); + return 0; +} + +static int param_get_usb_online(char *buffer, const struct kernel_param *kp) +{ + strcpy(buffer, map_get_key(map_ac_online, usb_online, "unknown")); + return strlen(buffer); +} + +static int param_set_battery_status(const char *key, + const struct kernel_param *kp) +{ + battery_status = map_get_value(map_status, key, battery_status); + signal_power_supply_changed(test_power_supplies[TEST_BATTERY]); + return 0; +} + +static int param_get_battery_status(char *buffer, const struct kernel_param *kp) +{ + strcpy(buffer, map_get_key(map_status, battery_status, "unknown")); + return strlen(buffer); +} + +static int param_set_battery_health(const char *key, + const struct kernel_param *kp) +{ + battery_health = map_get_value(map_health, key, battery_health); + signal_power_supply_changed(test_power_supplies[TEST_BATTERY]); + return 0; +} + +static int param_get_battery_health(char *buffer, const struct kernel_param *kp) +{ + strcpy(buffer, map_get_key(map_health, battery_health, "unknown")); + return strlen(buffer); +} + +static int param_set_battery_present(const char *key, + const struct kernel_param *kp) +{ + battery_present = map_get_value(map_present, key, battery_present); + signal_power_supply_changed(test_power_supplies[TEST_AC]); + return 0; +} + +static int param_get_battery_present(char *buffer, + const struct kernel_param *kp) +{ + strcpy(buffer, map_get_key(map_present, battery_present, "unknown")); + return strlen(buffer); +} + +static int param_set_battery_technology(const char *key, + const struct kernel_param *kp) +{ + battery_technology = map_get_value(map_technology, key, + battery_technology); + signal_power_supply_changed(test_power_supplies[TEST_BATTERY]); + return 0; +} + +static int param_get_battery_technology(char *buffer, + const struct kernel_param *kp) +{ + strcpy(buffer, + map_get_key(map_technology, battery_technology, "unknown")); + return strlen(buffer); +} + +static int param_set_battery_capacity(const char *key, + const struct kernel_param *kp) +{ + int tmp; + + if (1 != sscanf(key, "%d", &tmp)) + return -EINVAL; + + battery_capacity = tmp; + signal_power_supply_changed(test_power_supplies[TEST_BATTERY]); + return 0; +} + +#define param_get_battery_capacity param_get_int + +static int param_set_battery_voltage(const char *key, + const struct kernel_param *kp) +{ + int tmp; + + if (1 != sscanf(key, "%d", &tmp)) + return -EINVAL; + + battery_voltage = tmp; + signal_power_supply_changed(test_power_supplies[TEST_BATTERY]); + return 0; +} + +#define param_get_battery_voltage param_get_int + +static const struct kernel_param_ops param_ops_ac_online = { + .set = param_set_ac_online, + .get = param_get_ac_online, +}; + +static const struct kernel_param_ops param_ops_usb_online = { + .set = param_set_usb_online, + .get = param_get_usb_online, +}; + +static const struct kernel_param_ops param_ops_battery_status = { + .set = param_set_battery_status, + .get = param_get_battery_status, +}; + +static const struct kernel_param_ops param_ops_battery_present = { + .set = param_set_battery_present, + .get = param_get_battery_present, +}; + +static const struct kernel_param_ops param_ops_battery_technology = { + .set = param_set_battery_technology, + .get = param_get_battery_technology, +}; + +static const struct kernel_param_ops param_ops_battery_health = { + .set = param_set_battery_health, + .get = param_get_battery_health, +}; + +static const struct kernel_param_ops param_ops_battery_capacity = { + .set = param_set_battery_capacity, + .get = param_get_battery_capacity, +}; + +static const struct kernel_param_ops param_ops_battery_voltage = { + .set = param_set_battery_voltage, + .get = param_get_battery_voltage, +}; + +#define param_check_ac_online(name, p) __param_check(name, p, void); +#define param_check_usb_online(name, p) __param_check(name, p, void); +#define param_check_battery_status(name, p) __param_check(name, p, void); +#define param_check_battery_present(name, p) __param_check(name, p, void); +#define param_check_battery_technology(name, p) __param_check(name, p, void); +#define param_check_battery_health(name, p) __param_check(name, p, void); +#define param_check_battery_capacity(name, p) __param_check(name, p, void); +#define param_check_battery_voltage(name, p) __param_check(name, p, void); + + +module_param(ac_online, ac_online, 0644); +MODULE_PARM_DESC(ac_online, "AC charging state "); + +module_param(usb_online, usb_online, 0644); +MODULE_PARM_DESC(usb_online, "USB charging state "); + +module_param(battery_status, battery_status, 0644); +MODULE_PARM_DESC(battery_status, + "battery status "); + +module_param(battery_present, battery_present, 0644); +MODULE_PARM_DESC(battery_present, + "battery presence state "); + +module_param(battery_technology, battery_technology, 0644); +MODULE_PARM_DESC(battery_technology, + "battery technology "); + +module_param(battery_health, battery_health, 0644); +MODULE_PARM_DESC(battery_health, + "battery health state "); + +module_param(battery_capacity, battery_capacity, 0644); +MODULE_PARM_DESC(battery_capacity, "battery capacity (percentage)"); + +module_param(battery_voltage, battery_voltage, 0644); +MODULE_PARM_DESC(battery_voltage, "battery voltage (millivolts)"); + +MODULE_DESCRIPTION("Power supply driver for testing"); +MODULE_AUTHOR("Anton Vorontsov "); +MODULE_LICENSE("GPL"); diff --git a/drivers/power/supply/tosa_battery.c b/drivers/power/supply/tosa_battery.c new file mode 100644 index 000000000000..6e88c1b37945 --- /dev/null +++ b/drivers/power/supply/tosa_battery.c @@ -0,0 +1,470 @@ +/* + * Battery and Power Management code for the Sharp SL-6000x + * + * Copyright (c) 2005 Dirk Opfer + * Copyright (c) 2008 Dmitry Baryshkov + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +static DEFINE_MUTEX(bat_lock); /* protects gpio pins */ +static struct work_struct bat_work; + +struct tosa_bat { + int status; + struct power_supply *psy; + int full_chrg; + + struct mutex work_lock; /* protects data */ + + bool (*is_present)(struct tosa_bat *bat); + int gpio_full; + int gpio_charge_off; + + int technology; + + int gpio_bat; + int adc_bat; + int adc_bat_divider; + int bat_max; + int bat_min; + + int gpio_temp; + int adc_temp; + int adc_temp_divider; +}; + +static struct tosa_bat tosa_bat_main; +static struct tosa_bat tosa_bat_jacket; + +static unsigned long tosa_read_bat(struct tosa_bat *bat) +{ + unsigned long value = 0; + + if (bat->gpio_bat < 0 || bat->adc_bat < 0) + return 0; + + mutex_lock(&bat_lock); + gpio_set_value(bat->gpio_bat, 1); + msleep(5); + value = wm97xx_read_aux_adc(dev_get_drvdata(bat->psy->dev.parent), + bat->adc_bat); + gpio_set_value(bat->gpio_bat, 0); + mutex_unlock(&bat_lock); + + value = value * 1000000 / bat->adc_bat_divider; + + return value; +} + +static unsigned long tosa_read_temp(struct tosa_bat *bat) +{ + unsigned long value = 0; + + if (bat->gpio_temp < 0 || bat->adc_temp < 0) + return 0; + + mutex_lock(&bat_lock); + gpio_set_value(bat->gpio_temp, 1); + msleep(5); + value = wm97xx_read_aux_adc(dev_get_drvdata(bat->psy->dev.parent), + bat->adc_temp); + gpio_set_value(bat->gpio_temp, 0); + mutex_unlock(&bat_lock); + + value = value * 10000 / bat->adc_temp_divider; + + return value; +} + +static int tosa_bat_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + int ret = 0; + struct tosa_bat *bat = power_supply_get_drvdata(psy); + + if (bat->is_present && !bat->is_present(bat) + && psp != POWER_SUPPLY_PROP_PRESENT) { + return -ENODEV; + } + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + val->intval = bat->status; + break; + case POWER_SUPPLY_PROP_TECHNOLOGY: + val->intval = bat->technology; + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + val->intval = tosa_read_bat(bat); + break; + case POWER_SUPPLY_PROP_VOLTAGE_MAX: + if (bat->full_chrg == -1) + val->intval = bat->bat_max; + else + val->intval = bat->full_chrg; + break; + case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: + val->intval = bat->bat_max; + break; + case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: + val->intval = bat->bat_min; + break; + case POWER_SUPPLY_PROP_TEMP: + val->intval = tosa_read_temp(bat); + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = bat->is_present ? bat->is_present(bat) : 1; + break; + default: + ret = -EINVAL; + break; + } + return ret; +} + +static bool tosa_jacket_bat_is_present(struct tosa_bat *bat) +{ + return gpio_get_value(TOSA_GPIO_JACKET_DETECT) == 0; +} + +static void tosa_bat_external_power_changed(struct power_supply *psy) +{ + schedule_work(&bat_work); +} + +static irqreturn_t tosa_bat_gpio_isr(int irq, void *data) +{ + pr_info("tosa_bat_gpio irq\n"); + schedule_work(&bat_work); + return IRQ_HANDLED; +} + +static void tosa_bat_update(struct tosa_bat *bat) +{ + int old; + struct power_supply *psy = bat->psy; + + mutex_lock(&bat->work_lock); + + old = bat->status; + + if (bat->is_present && !bat->is_present(bat)) { + printk(KERN_NOTICE "%s not present\n", psy->desc->name); + bat->status = POWER_SUPPLY_STATUS_UNKNOWN; + bat->full_chrg = -1; + } else if (power_supply_am_i_supplied(psy)) { + if (bat->status == POWER_SUPPLY_STATUS_DISCHARGING) { + gpio_set_value(bat->gpio_charge_off, 0); + mdelay(15); + } + + if (gpio_get_value(bat->gpio_full)) { + if (old == POWER_SUPPLY_STATUS_CHARGING || + bat->full_chrg == -1) + bat->full_chrg = tosa_read_bat(bat); + + gpio_set_value(bat->gpio_charge_off, 1); + bat->status = POWER_SUPPLY_STATUS_FULL; + } else { + gpio_set_value(bat->gpio_charge_off, 0); + bat->status = POWER_SUPPLY_STATUS_CHARGING; + } + } else { + gpio_set_value(bat->gpio_charge_off, 1); + bat->status = POWER_SUPPLY_STATUS_DISCHARGING; + } + + if (old != bat->status) + power_supply_changed(psy); + + mutex_unlock(&bat->work_lock); +} + +static void tosa_bat_work(struct work_struct *work) +{ + tosa_bat_update(&tosa_bat_main); + tosa_bat_update(&tosa_bat_jacket); +} + + +static enum power_supply_property tosa_bat_main_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_VOLTAGE_MAX, + POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, + POWER_SUPPLY_PROP_TEMP, + POWER_SUPPLY_PROP_PRESENT, +}; + +static enum power_supply_property tosa_bat_bu_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, + POWER_SUPPLY_PROP_PRESENT, +}; + +static const struct power_supply_desc tosa_bat_main_desc = { + .name = "main-battery", + .type = POWER_SUPPLY_TYPE_BATTERY, + .properties = tosa_bat_main_props, + .num_properties = ARRAY_SIZE(tosa_bat_main_props), + .get_property = tosa_bat_get_property, + .external_power_changed = tosa_bat_external_power_changed, + .use_for_apm = 1, +}; + +static const struct power_supply_desc tosa_bat_jacket_desc = { + .name = "jacket-battery", + .type = POWER_SUPPLY_TYPE_BATTERY, + .properties = tosa_bat_main_props, + .num_properties = ARRAY_SIZE(tosa_bat_main_props), + .get_property = tosa_bat_get_property, + .external_power_changed = tosa_bat_external_power_changed, +}; + +static const struct power_supply_desc tosa_bat_bu_desc = { + .name = "backup-battery", + .type = POWER_SUPPLY_TYPE_BATTERY, + .properties = tosa_bat_bu_props, + .num_properties = ARRAY_SIZE(tosa_bat_bu_props), + .get_property = tosa_bat_get_property, + .external_power_changed = tosa_bat_external_power_changed, +}; + +static struct tosa_bat tosa_bat_main = { + .status = POWER_SUPPLY_STATUS_DISCHARGING, + .full_chrg = -1, + .psy = NULL, + + .gpio_full = TOSA_GPIO_BAT0_CRG, + .gpio_charge_off = TOSA_GPIO_CHARGE_OFF, + + .technology = POWER_SUPPLY_TECHNOLOGY_LIPO, + + .gpio_bat = TOSA_GPIO_BAT0_V_ON, + .adc_bat = WM97XX_AUX_ID3, + .adc_bat_divider = 414, + .bat_max = 4310000, + .bat_min = 1551 * 1000000 / 414, + + .gpio_temp = TOSA_GPIO_BAT1_TH_ON, + .adc_temp = WM97XX_AUX_ID2, + .adc_temp_divider = 10000, +}; + +static struct tosa_bat tosa_bat_jacket = { + .status = POWER_SUPPLY_STATUS_DISCHARGING, + .full_chrg = -1, + .psy = NULL, + + .is_present = tosa_jacket_bat_is_present, + .gpio_full = TOSA_GPIO_BAT1_CRG, + .gpio_charge_off = TOSA_GPIO_CHARGE_OFF_JC, + + .technology = POWER_SUPPLY_TECHNOLOGY_LIPO, + + .gpio_bat = TOSA_GPIO_BAT1_V_ON, + .adc_bat = WM97XX_AUX_ID3, + .adc_bat_divider = 414, + .bat_max = 4310000, + .bat_min = 1551 * 1000000 / 414, + + .gpio_temp = TOSA_GPIO_BAT0_TH_ON, + .adc_temp = WM97XX_AUX_ID2, + .adc_temp_divider = 10000, +}; + +static struct tosa_bat tosa_bat_bu = { + .status = POWER_SUPPLY_STATUS_UNKNOWN, + .full_chrg = -1, + .psy = NULL, + + .gpio_full = -1, + .gpio_charge_off = -1, + + .technology = POWER_SUPPLY_TECHNOLOGY_LiMn, + + .gpio_bat = TOSA_GPIO_BU_CHRG_ON, + .adc_bat = WM97XX_AUX_ID4, + .adc_bat_divider = 1266, + + .gpio_temp = -1, + .adc_temp = -1, + .adc_temp_divider = -1, +}; + +static struct gpio tosa_bat_gpios[] = { + { TOSA_GPIO_CHARGE_OFF, GPIOF_OUT_INIT_HIGH, "main charge off" }, + { TOSA_GPIO_CHARGE_OFF_JC, GPIOF_OUT_INIT_HIGH, "jacket charge off" }, + { TOSA_GPIO_BAT_SW_ON, GPIOF_OUT_INIT_LOW, "battery switch" }, + { TOSA_GPIO_BAT0_V_ON, GPIOF_OUT_INIT_LOW, "main battery" }, + { TOSA_GPIO_BAT1_V_ON, GPIOF_OUT_INIT_LOW, "jacket battery" }, + { TOSA_GPIO_BAT1_TH_ON, GPIOF_OUT_INIT_LOW, "main battery temp" }, + { TOSA_GPIO_BAT0_TH_ON, GPIOF_OUT_INIT_LOW, "jacket battery temp" }, + { TOSA_GPIO_BU_CHRG_ON, GPIOF_OUT_INIT_LOW, "backup battery" }, + { TOSA_GPIO_BAT0_CRG, GPIOF_IN, "main battery full" }, + { TOSA_GPIO_BAT1_CRG, GPIOF_IN, "jacket battery full" }, + { TOSA_GPIO_BAT0_LOW, GPIOF_IN, "main battery low" }, + { TOSA_GPIO_BAT1_LOW, GPIOF_IN, "jacket battery low" }, + { TOSA_GPIO_JACKET_DETECT, GPIOF_IN, "jacket detect" }, +}; + +#ifdef CONFIG_PM +static int tosa_bat_suspend(struct platform_device *dev, pm_message_t state) +{ + /* flush all pending status updates */ + flush_work(&bat_work); + return 0; +} + +static int tosa_bat_resume(struct platform_device *dev) +{ + /* things may have changed while we were away */ + schedule_work(&bat_work); + return 0; +} +#else +#define tosa_bat_suspend NULL +#define tosa_bat_resume NULL +#endif + +static int tosa_bat_probe(struct platform_device *dev) +{ + int ret; + struct power_supply_config main_psy_cfg = {}, + jacket_psy_cfg = {}, + bu_psy_cfg = {}; + + if (!machine_is_tosa()) + return -ENODEV; + + ret = gpio_request_array(tosa_bat_gpios, ARRAY_SIZE(tosa_bat_gpios)); + if (ret) + return ret; + + mutex_init(&tosa_bat_main.work_lock); + mutex_init(&tosa_bat_jacket.work_lock); + + INIT_WORK(&bat_work, tosa_bat_work); + + main_psy_cfg.drv_data = &tosa_bat_main; + tosa_bat_main.psy = power_supply_register(&dev->dev, + &tosa_bat_main_desc, + &main_psy_cfg); + if (IS_ERR(tosa_bat_main.psy)) { + ret = PTR_ERR(tosa_bat_main.psy); + goto err_psy_reg_main; + } + + jacket_psy_cfg.drv_data = &tosa_bat_jacket; + tosa_bat_jacket.psy = power_supply_register(&dev->dev, + &tosa_bat_jacket_desc, + &jacket_psy_cfg); + if (IS_ERR(tosa_bat_jacket.psy)) { + ret = PTR_ERR(tosa_bat_jacket.psy); + goto err_psy_reg_jacket; + } + + bu_psy_cfg.drv_data = &tosa_bat_bu; + tosa_bat_bu.psy = power_supply_register(&dev->dev, &tosa_bat_bu_desc, + &bu_psy_cfg); + if (IS_ERR(tosa_bat_bu.psy)) { + ret = PTR_ERR(tosa_bat_bu.psy); + goto err_psy_reg_bu; + } + + ret = request_irq(gpio_to_irq(TOSA_GPIO_BAT0_CRG), + tosa_bat_gpio_isr, + IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, + "main full", &tosa_bat_main); + if (ret) + goto err_req_main; + + ret = request_irq(gpio_to_irq(TOSA_GPIO_BAT1_CRG), + tosa_bat_gpio_isr, + IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, + "jacket full", &tosa_bat_jacket); + if (ret) + goto err_req_jacket; + + ret = request_irq(gpio_to_irq(TOSA_GPIO_JACKET_DETECT), + tosa_bat_gpio_isr, + IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, + "jacket detect", &tosa_bat_jacket); + if (!ret) { + schedule_work(&bat_work); + return 0; + } + + free_irq(gpio_to_irq(TOSA_GPIO_BAT1_CRG), &tosa_bat_jacket); +err_req_jacket: + free_irq(gpio_to_irq(TOSA_GPIO_BAT0_CRG), &tosa_bat_main); +err_req_main: + power_supply_unregister(tosa_bat_bu.psy); +err_psy_reg_bu: + power_supply_unregister(tosa_bat_jacket.psy); +err_psy_reg_jacket: + power_supply_unregister(tosa_bat_main.psy); +err_psy_reg_main: + + /* see comment in tosa_bat_remove */ + cancel_work_sync(&bat_work); + + gpio_free_array(tosa_bat_gpios, ARRAY_SIZE(tosa_bat_gpios)); + return ret; +} + +static int tosa_bat_remove(struct platform_device *dev) +{ + free_irq(gpio_to_irq(TOSA_GPIO_JACKET_DETECT), &tosa_bat_jacket); + free_irq(gpio_to_irq(TOSA_GPIO_BAT1_CRG), &tosa_bat_jacket); + free_irq(gpio_to_irq(TOSA_GPIO_BAT0_CRG), &tosa_bat_main); + + power_supply_unregister(tosa_bat_bu.psy); + power_supply_unregister(tosa_bat_jacket.psy); + power_supply_unregister(tosa_bat_main.psy); + + /* + * Now cancel the bat_work. We won't get any more schedules, + * since all sources (isr and external_power_changed) are + * unregistered now. + */ + cancel_work_sync(&bat_work); + gpio_free_array(tosa_bat_gpios, ARRAY_SIZE(tosa_bat_gpios)); + return 0; +} + +static struct platform_driver tosa_bat_driver = { + .driver.name = "wm97xx-battery", + .driver.owner = THIS_MODULE, + .probe = tosa_bat_probe, + .remove = tosa_bat_remove, + .suspend = tosa_bat_suspend, + .resume = tosa_bat_resume, +}; + +module_platform_driver(tosa_bat_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Dmitry Baryshkov"); +MODULE_DESCRIPTION("Tosa battery driver"); +MODULE_ALIAS("platform:wm97xx-battery"); diff --git a/drivers/power/supply/tps65090-charger.c b/drivers/power/supply/tps65090-charger.c new file mode 100644 index 000000000000..1b4b5e09538e --- /dev/null +++ b/drivers/power/supply/tps65090-charger.c @@ -0,0 +1,370 @@ +/* + * Battery charger driver for TI's tps65090 + * + * Copyright (c) 2013, NVIDIA CORPORATION. All rights reserved. + + * 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 . + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define TPS65090_CHARGER_ENABLE BIT(0) +#define TPS65090_VACG BIT(1) +#define TPS65090_NOITERM BIT(5) + +#define POLL_INTERVAL (HZ * 2) /* Used when no irq */ + +struct tps65090_charger { + struct device *dev; + int ac_online; + int prev_ac_online; + int irq; + struct task_struct *poll_task; + bool passive_mode; + struct power_supply *ac; + struct tps65090_platform_data *pdata; +}; + +static enum power_supply_property tps65090_ac_props[] = { + POWER_SUPPLY_PROP_ONLINE, +}; + +static int tps65090_low_chrg_current(struct tps65090_charger *charger) +{ + int ret; + + if (charger->passive_mode) + return 0; + + ret = tps65090_write(charger->dev->parent, TPS65090_REG_CG_CTRL5, + TPS65090_NOITERM); + if (ret < 0) { + dev_err(charger->dev, "%s(): error reading in register 0x%x\n", + __func__, TPS65090_REG_CG_CTRL5); + return ret; + } + return 0; +} + +static int tps65090_enable_charging(struct tps65090_charger *charger) +{ + int ret; + uint8_t ctrl0 = 0; + + if (charger->passive_mode) + return 0; + + ret = tps65090_read(charger->dev->parent, TPS65090_REG_CG_CTRL0, + &ctrl0); + if (ret < 0) { + dev_err(charger->dev, "%s(): error reading in register 0x%x\n", + __func__, TPS65090_REG_CG_CTRL0); + return ret; + } + + ret = tps65090_write(charger->dev->parent, TPS65090_REG_CG_CTRL0, + (ctrl0 | TPS65090_CHARGER_ENABLE)); + if (ret < 0) { + dev_err(charger->dev, "%s(): error writing in register 0x%x\n", + __func__, TPS65090_REG_CG_CTRL0); + return ret; + } + return 0; +} + +static int tps65090_config_charger(struct tps65090_charger *charger) +{ + uint8_t intrmask = 0; + int ret; + + if (charger->passive_mode) + return 0; + + if (charger->pdata->enable_low_current_chrg) { + ret = tps65090_low_chrg_current(charger); + if (ret < 0) { + dev_err(charger->dev, + "error configuring low charge current\n"); + return ret; + } + } + + /* Enable the VACG interrupt for AC power detect */ + ret = tps65090_read(charger->dev->parent, TPS65090_REG_INTR_MASK, + &intrmask); + if (ret < 0) { + dev_err(charger->dev, "%s(): error reading in register 0x%x\n", + __func__, TPS65090_REG_INTR_MASK); + return ret; + } + + ret = tps65090_write(charger->dev->parent, TPS65090_REG_INTR_MASK, + (intrmask | TPS65090_VACG)); + if (ret < 0) { + dev_err(charger->dev, "%s(): error writing in register 0x%x\n", + __func__, TPS65090_REG_CG_CTRL0); + return ret; + } + + return 0; +} + +static int tps65090_ac_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct tps65090_charger *charger = power_supply_get_drvdata(psy); + + if (psp == POWER_SUPPLY_PROP_ONLINE) { + val->intval = charger->ac_online; + charger->prev_ac_online = charger->ac_online; + return 0; + } + return -EINVAL; +} + +static irqreturn_t tps65090_charger_isr(int irq, void *dev_id) +{ + struct tps65090_charger *charger = dev_id; + int ret; + uint8_t status1 = 0; + uint8_t intrsts = 0; + + ret = tps65090_read(charger->dev->parent, TPS65090_REG_CG_STATUS1, + &status1); + if (ret < 0) { + dev_err(charger->dev, "%s(): Error in reading reg 0x%x\n", + __func__, TPS65090_REG_CG_STATUS1); + return IRQ_HANDLED; + } + msleep(75); + ret = tps65090_read(charger->dev->parent, TPS65090_REG_INTR_STS, + &intrsts); + if (ret < 0) { + dev_err(charger->dev, "%s(): Error in reading reg 0x%x\n", + __func__, TPS65090_REG_INTR_STS); + return IRQ_HANDLED; + } + + if (intrsts & TPS65090_VACG) { + ret = tps65090_enable_charging(charger); + if (ret < 0) + return IRQ_HANDLED; + charger->ac_online = 1; + } else { + charger->ac_online = 0; + } + + /* Clear interrupts. */ + if (!charger->passive_mode) { + ret = tps65090_write(charger->dev->parent, + TPS65090_REG_INTR_STS, 0x00); + if (ret < 0) { + dev_err(charger->dev, + "%s(): Error in writing reg 0x%x\n", + __func__, TPS65090_REG_INTR_STS); + } + } + + if (charger->prev_ac_online != charger->ac_online) + power_supply_changed(charger->ac); + + return IRQ_HANDLED; +} + +static struct tps65090_platform_data * + tps65090_parse_dt_charger_data(struct platform_device *pdev) +{ + struct tps65090_platform_data *pdata; + struct device_node *np = pdev->dev.of_node; + unsigned int prop; + + pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) { + dev_err(&pdev->dev, "Memory alloc for tps65090_pdata failed\n"); + return NULL; + } + + prop = of_property_read_bool(np, "ti,enable-low-current-chrg"); + pdata->enable_low_current_chrg = prop; + + pdata->irq_base = -1; + + return pdata; + +} + +static int tps65090_charger_poll_task(void *data) +{ + set_freezable(); + + while (!kthread_should_stop()) { + schedule_timeout_interruptible(POLL_INTERVAL); + try_to_freeze(); + tps65090_charger_isr(-1, data); + } + return 0; +} + +static const struct power_supply_desc tps65090_charger_desc = { + .name = "tps65090-ac", + .type = POWER_SUPPLY_TYPE_MAINS, + .get_property = tps65090_ac_get_property, + .properties = tps65090_ac_props, + .num_properties = ARRAY_SIZE(tps65090_ac_props), +}; + +static int tps65090_charger_probe(struct platform_device *pdev) +{ + struct tps65090_charger *cdata; + struct tps65090_platform_data *pdata; + struct power_supply_config psy_cfg = {}; + uint8_t status1 = 0; + int ret; + int irq; + + pdata = dev_get_platdata(pdev->dev.parent); + + if (IS_ENABLED(CONFIG_OF) && !pdata && pdev->dev.of_node) + pdata = tps65090_parse_dt_charger_data(pdev); + + if (!pdata) { + dev_err(&pdev->dev, "%s():no platform data available\n", + __func__); + return -ENODEV; + } + + cdata = devm_kzalloc(&pdev->dev, sizeof(*cdata), GFP_KERNEL); + if (!cdata) { + dev_err(&pdev->dev, "failed to allocate memory status\n"); + return -ENOMEM; + } + + platform_set_drvdata(pdev, cdata); + + cdata->dev = &pdev->dev; + cdata->pdata = pdata; + + psy_cfg.supplied_to = pdata->supplied_to; + psy_cfg.num_supplicants = pdata->num_supplicants; + psy_cfg.of_node = pdev->dev.of_node; + psy_cfg.drv_data = cdata; + + cdata->ac = power_supply_register(&pdev->dev, &tps65090_charger_desc, + &psy_cfg); + if (IS_ERR(cdata->ac)) { + dev_err(&pdev->dev, "failed: power supply register\n"); + return PTR_ERR(cdata->ac); + } + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + irq = -ENXIO; + cdata->irq = irq; + + ret = tps65090_config_charger(cdata); + if (ret < 0) { + dev_err(&pdev->dev, "charger config failed, err %d\n", ret); + goto fail_unregister_supply; + } + + /* Check for charger presence */ + ret = tps65090_read(cdata->dev->parent, TPS65090_REG_CG_STATUS1, + &status1); + if (ret < 0) { + dev_err(cdata->dev, "%s(): Error in reading reg 0x%x", __func__, + TPS65090_REG_CG_STATUS1); + goto fail_unregister_supply; + } + + if (status1 != 0) { + ret = tps65090_enable_charging(cdata); + if (ret < 0) { + dev_err(cdata->dev, "error enabling charger\n"); + goto fail_unregister_supply; + } + cdata->ac_online = 1; + power_supply_changed(cdata->ac); + } + + if (irq != -ENXIO) { + ret = devm_request_threaded_irq(&pdev->dev, irq, NULL, + tps65090_charger_isr, 0, "tps65090-charger", cdata); + if (ret) { + dev_err(cdata->dev, + "Unable to register irq %d err %d\n", irq, + ret); + goto fail_unregister_supply; + } + } else { + cdata->poll_task = kthread_run(tps65090_charger_poll_task, + cdata, "ktps65090charger"); + cdata->passive_mode = true; + if (IS_ERR(cdata->poll_task)) { + ret = PTR_ERR(cdata->poll_task); + dev_err(cdata->dev, + "Unable to run kthread err %d\n", ret); + goto fail_unregister_supply; + } + } + + return 0; + +fail_unregister_supply: + power_supply_unregister(cdata->ac); + + return ret; +} + +static int tps65090_charger_remove(struct platform_device *pdev) +{ + struct tps65090_charger *cdata = platform_get_drvdata(pdev); + + if (cdata->irq == -ENXIO) + kthread_stop(cdata->poll_task); + power_supply_unregister(cdata->ac); + + return 0; +} + +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 = { + .name = "tps65090-charger", + .of_match_table = of_tps65090_charger_match, + }, + .probe = tps65090_charger_probe, + .remove = tps65090_charger_remove, +}; +module_platform_driver(tps65090_charger_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Syed Rafiuddin "); +MODULE_DESCRIPTION("tps65090 battery charger driver"); diff --git a/drivers/power/supply/tps65217_charger.c b/drivers/power/supply/tps65217_charger.c new file mode 100644 index 000000000000..73dfae41def8 --- /dev/null +++ b/drivers/power/supply/tps65217_charger.c @@ -0,0 +1,268 @@ +/* + * 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 . + */ + +/* + * Battery charger driver for TI's tps65217 + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#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; + struct power_supply_config cfg = {}; + 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; + + cfg.of_node = pdev->dev.of_node; + cfg.drv_data = charger; + + charger->ac = devm_power_supply_register(&pdev->dev, + &tps65217_charger_desc, + &cfg); + 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 "); +MODULE_DESCRIPTION("TPS65217 battery charger driver"); diff --git a/drivers/power/supply/twl4030_charger.c b/drivers/power/supply/twl4030_charger.c new file mode 100644 index 000000000000..bcd4dc304f27 --- /dev/null +++ b/drivers/power/supply/twl4030_charger.c @@ -0,0 +1,1162 @@ +/* + * TWL4030/TPS65950 BCI (Battery Charger Interface) driver + * + * Copyright (C) 2010 Gražvydas Ignotas + * + * based on twl4030_bci_battery.c by TI + * Copyright (C) 2008 Texas Instruments, Inc. + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define TWL4030_BCIMDEN 0x00 +#define TWL4030_BCIMDKEY 0x01 +#define TWL4030_BCIMSTATEC 0x02 +#define TWL4030_BCIICHG 0x08 +#define TWL4030_BCIVAC 0x0a +#define TWL4030_BCIVBUS 0x0c +#define TWL4030_BCIMFSTS3 0x0F +#define TWL4030_BCIMFSTS4 0x10 +#define TWL4030_BCICTL1 0x23 +#define TWL4030_BB_CFG 0x12 +#define TWL4030_BCIIREF1 0x27 +#define TWL4030_BCIIREF2 0x28 +#define TWL4030_BCIMFKEY 0x11 +#define TWL4030_BCIMFEN3 0x14 +#define TWL4030_BCIMFTH8 0x1d +#define TWL4030_BCIMFTH9 0x1e +#define TWL4030_BCIWDKEY 0x21 + +#define TWL4030_BCIMFSTS1 0x01 + +#define TWL4030_BCIAUTOWEN BIT(5) +#define TWL4030_CONFIG_DONE BIT(4) +#define TWL4030_CVENAC BIT(2) +#define TWL4030_BCIAUTOUSB BIT(1) +#define TWL4030_BCIAUTOAC BIT(0) +#define TWL4030_CGAIN BIT(5) +#define TWL4030_USBFASTMCHG BIT(2) +#define TWL4030_STS_VBUS BIT(7) +#define TWL4030_STS_USB_ID BIT(2) +#define TWL4030_BBCHEN BIT(4) +#define TWL4030_BBSEL_MASK 0x0c +#define TWL4030_BBSEL_2V5 0x00 +#define TWL4030_BBSEL_3V0 0x04 +#define TWL4030_BBSEL_3V1 0x08 +#define TWL4030_BBSEL_3V2 0x0c +#define TWL4030_BBISEL_MASK 0x03 +#define TWL4030_BBISEL_25uA 0x00 +#define TWL4030_BBISEL_150uA 0x01 +#define TWL4030_BBISEL_500uA 0x02 +#define TWL4030_BBISEL_1000uA 0x03 + +#define TWL4030_BATSTSPCHG BIT(2) +#define TWL4030_BATSTSMCHG BIT(6) + +/* BCI interrupts */ +#define TWL4030_WOVF BIT(0) /* Watchdog overflow */ +#define TWL4030_TMOVF BIT(1) /* Timer overflow */ +#define TWL4030_ICHGHIGH BIT(2) /* Battery charge current high */ +#define TWL4030_ICHGLOW BIT(3) /* Battery cc. low / FSM state change */ +#define TWL4030_ICHGEOC BIT(4) /* Battery current end-of-charge */ +#define TWL4030_TBATOR2 BIT(5) /* Battery temperature out of range 2 */ +#define TWL4030_TBATOR1 BIT(6) /* Battery temperature out of range 1 */ +#define TWL4030_BATSTS BIT(7) /* Battery status */ + +#define TWL4030_VBATLVL BIT(0) /* VBAT level */ +#define TWL4030_VBATOV BIT(1) /* VBAT overvoltage */ +#define TWL4030_VBUSOV BIT(2) /* VBUS overvoltage */ +#define TWL4030_ACCHGOV BIT(3) /* Ac charger overvoltage */ + +#define TWL4030_MSTATEC_USB BIT(4) +#define TWL4030_MSTATEC_AC BIT(5) +#define TWL4030_MSTATEC_MASK 0x0f +#define TWL4030_MSTATEC_QUICK1 0x02 +#define TWL4030_MSTATEC_QUICK7 0x07 +#define TWL4030_MSTATEC_COMPLETE1 0x0b +#define TWL4030_MSTATEC_COMPLETE4 0x0e + +/* + * If AC (Accessory Charger) voltage exceeds 4.5V (MADC 11) + * then AC is available. + */ +static inline int ac_available(struct iio_channel *channel_vac) +{ + int val, err; + + if (!channel_vac) + return 0; + + err = iio_read_channel_processed(channel_vac, &val); + if (err < 0) + return 0; + return val > 4500; +} + +static bool allow_usb; +module_param(allow_usb, bool, 0644); +MODULE_PARM_DESC(allow_usb, "Allow USB charge drawing default current"); + +struct twl4030_bci { + struct device *dev; + struct power_supply *ac; + struct power_supply *usb; + struct usb_phy *transceiver; + struct notifier_block usb_nb; + struct work_struct work; + int irq_chg; + int irq_bci; + int usb_enabled; + + /* + * ichg_* and *_cur values in uA. If any are 'large', we set + * CGAIN to '1' which doubles the range for half the + * precision. + */ + 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 +#define CHARGE_AUTO 1 +#define CHARGE_LINEAR 2 + + /* When setting the USB current we slowly increase the + * requested current until target is reached or the voltage + * drops below 4.75V. In the latter case we step back one + * step. + */ + unsigned int usb_cur_target; + struct delayed_work current_worker; +#define USB_CUR_STEP 20000 /* 20mA at a time */ +#define USB_MIN_VOLT 4750000 /* 4.75V */ +#define USB_CUR_DELAY msecs_to_jiffies(100) +#define USB_MAX_CURRENT 1700000 /* TWL4030 caps at 1.7A */ + + unsigned long event; +}; + +/* strings for 'usb_mode' values */ +static char *modes[] = { "off", "auto", "continuous" }; + +/* + * clear and set bits on an given register on a given module + */ +static int twl4030_clear_set(u8 mod_no, u8 clear, u8 set, u8 reg) +{ + u8 val = 0; + int ret; + + ret = twl_i2c_read_u8(mod_no, &val, reg); + if (ret) + return ret; + + val &= ~clear; + val |= set; + + return twl_i2c_write_u8(mod_no, val, reg); +} + +static int twl4030_bci_read(u8 reg, u8 *val) +{ + return twl_i2c_read_u8(TWL_MODULE_MAIN_CHARGE, val, reg); +} + +static int twl4030_clear_set_boot_bci(u8 clear, u8 set) +{ + return twl4030_clear_set(TWL_MODULE_PM_MASTER, clear, + TWL4030_CONFIG_DONE | TWL4030_BCIAUTOWEN | set, + TWL4030_PM_MASTER_BOOT_BCI); +} + +static int twl4030bci_read_adc_val(u8 reg) +{ + int ret, temp; + u8 val; + + /* read MSB */ + ret = twl4030_bci_read(reg + 1, &val); + if (ret) + return ret; + + temp = (int)(val & 0x03) << 8; + + /* read LSB */ + ret = twl4030_bci_read(reg, &val); + if (ret) + return ret; + + return temp | val; +} + +/* + * Check if Battery Pack was present + */ +static int twl4030_is_battery_present(struct twl4030_bci *bci) +{ + int ret; + u8 val = 0; + + /* Battery presence in Main charge? */ + ret = twl_i2c_read_u8(TWL_MODULE_MAIN_CHARGE, &val, TWL4030_BCIMFSTS3); + if (ret) + return ret; + if (val & TWL4030_BATSTSMCHG) + return 0; + + /* + * OK, It could be that bootloader did not enable main charger, + * pre-charge is h/w auto. So, Battery presence in Pre-charge? + */ + ret = twl_i2c_read_u8(TWL4030_MODULE_PRECHARGE, &val, + TWL4030_BCIMFSTS1); + if (ret) + return ret; + if (val & TWL4030_BATSTSPCHG) + return 0; + + return -ENODEV; +} + +/* + * TI provided formulas: + * CGAIN == 0: ICHG = (BCIICHG * 1.7) / (2^10 - 1) - 0.85 + * CGAIN == 1: ICHG = (BCIICHG * 3.4) / (2^10 - 1) - 1.7 + * Here we use integer approximation of: + * CGAIN == 0: val * 1.6618 - 0.85 * 1000 + * CGAIN == 1: (val * 1.6618 - 0.85 * 1000) * 2 + */ +/* + * convert twl register value for currents into uA + */ +static int regval2ua(int regval, bool cgain) +{ + if (cgain) + return (regval * 16618 - 8500 * 1000) / 5; + else + return (regval * 16618 - 8500 * 1000) / 10; +} + +/* + * convert uA currents into twl register value + */ +static int ua2regval(int ua, bool cgain) +{ + int ret; + if (cgain) + ua /= 2; + ret = (ua * 10 + 8500 * 1000) / 16618; + /* rounding problems */ + if (ret < 512) + ret = 512; + return ret; +} + +static int twl4030_charger_update_current(struct twl4030_bci *bci) +{ + int status; + int cur; + unsigned reg, cur_reg; + u8 bcictl1, oldreg, fullreg; + bool cgain = false; + u8 boot_bci; + + /* + * If AC (Accessory Charger) voltage exceeds 4.5V (MADC 11) + * and AC is enabled, set current for 'ac' + */ + if (ac_available(bci->channel_vac)) { + cur = bci->ac_cur; + bci->ac_is_active = true; + } else { + cur = bci->usb_cur; + bci->ac_is_active = false; + if (cur > bci->usb_cur_target) { + cur = bci->usb_cur_target; + bci->usb_cur = cur; + } + if (cur < bci->usb_cur_target) + schedule_delayed_work(&bci->current_worker, USB_CUR_DELAY); + } + + /* First, check thresholds and see if cgain is needed */ + if (bci->ichg_eoc >= 200000) + cgain = true; + if (bci->ichg_lo >= 400000) + cgain = true; + if (bci->ichg_hi >= 820000) + cgain = true; + if (cur > 852000) + cgain = true; + + status = twl4030_bci_read(TWL4030_BCICTL1, &bcictl1); + if (status < 0) + return status; + if (twl_i2c_read_u8(TWL_MODULE_PM_MASTER, &boot_bci, + TWL4030_PM_MASTER_BOOT_BCI) < 0) + boot_bci = 0; + boot_bci &= 7; + + if ((!!cgain) != !!(bcictl1 & TWL4030_CGAIN)) + /* Need to turn for charging while we change the + * CGAIN bit. Leave it off while everything is + * updated. + */ + twl4030_clear_set_boot_bci(boot_bci, 0); + + /* + * For ichg_eoc, the hardware only supports reg values matching + * 100XXXX000, and requires the XXXX be stored in the high nibble + * of TWL4030_BCIMFTH8. + */ + reg = ua2regval(bci->ichg_eoc, cgain); + if (reg > 0x278) + reg = 0x278; + if (reg < 0x200) + reg = 0x200; + reg = (reg >> 3) & 0xf; + fullreg = reg << 4; + + /* + * For ichg_lo, reg value must match 10XXXX0000. + * XXXX is stored in low nibble of TWL4030_BCIMFTH8. + */ + reg = ua2regval(bci->ichg_lo, cgain); + if (reg > 0x2F0) + reg = 0x2F0; + if (reg < 0x200) + reg = 0x200; + reg = (reg >> 4) & 0xf; + fullreg |= reg; + + /* ichg_eoc and ichg_lo live in same register */ + status = twl4030_bci_read(TWL4030_BCIMFTH8, &oldreg); + if (status < 0) + return status; + if (oldreg != fullreg) { + status = twl_i2c_write_u8(TWL_MODULE_MAIN_CHARGE, 0xF4, + TWL4030_BCIMFKEY); + if (status < 0) + return status; + twl_i2c_write_u8(TWL_MODULE_MAIN_CHARGE, + fullreg, TWL4030_BCIMFTH8); + } + + /* ichg_hi threshold must be 1XXXX01100 (I think) */ + reg = ua2regval(bci->ichg_hi, cgain); + if (reg > 0x3E0) + reg = 0x3E0; + if (reg < 0x200) + reg = 0x200; + fullreg = (reg >> 5) & 0xF; + fullreg <<= 4; + status = twl4030_bci_read(TWL4030_BCIMFTH9, &oldreg); + if (status < 0) + return status; + if ((oldreg & 0xF0) != fullreg) { + fullreg |= (oldreg & 0x0F); + status = twl_i2c_write_u8(TWL_MODULE_MAIN_CHARGE, 0xE7, + TWL4030_BCIMFKEY); + if (status < 0) + return status; + twl_i2c_write_u8(TWL_MODULE_MAIN_CHARGE, + fullreg, TWL4030_BCIMFTH9); + } + + /* + * And finally, set the current. This is stored in + * two registers. + */ + reg = ua2regval(cur, cgain); + /* we have only 10 bits */ + if (reg > 0x3ff) + reg = 0x3ff; + status = twl4030_bci_read(TWL4030_BCIIREF1, &oldreg); + if (status < 0) + return status; + cur_reg = oldreg; + status = twl4030_bci_read(TWL4030_BCIIREF2, &oldreg); + if (status < 0) + return status; + cur_reg |= oldreg << 8; + if (reg != oldreg) { + /* disable write protection for one write access for + * BCIIREF */ + status = twl_i2c_write_u8(TWL_MODULE_MAIN_CHARGE, 0xE7, + TWL4030_BCIMFKEY); + if (status < 0) + return status; + status = twl_i2c_write_u8(TWL_MODULE_MAIN_CHARGE, + (reg & 0x100) ? 3 : 2, + TWL4030_BCIIREF2); + if (status < 0) + return status; + /* disable write protection for one write access for + * BCIIREF */ + status = twl_i2c_write_u8(TWL_MODULE_MAIN_CHARGE, 0xE7, + TWL4030_BCIMFKEY); + if (status < 0) + return status; + status = twl_i2c_write_u8(TWL_MODULE_MAIN_CHARGE, + reg & 0xff, + TWL4030_BCIIREF1); + } + if ((!!cgain) != !!(bcictl1 & TWL4030_CGAIN)) { + /* Flip CGAIN and re-enable charging */ + bcictl1 ^= TWL4030_CGAIN; + twl_i2c_write_u8(TWL_MODULE_MAIN_CHARGE, + bcictl1, TWL4030_BCICTL1); + twl4030_clear_set_boot_bci(0, boot_bci); + } + return 0; +} + +static int twl4030_charger_get_current(void); + +static void twl4030_current_worker(struct work_struct *data) +{ + int v, curr; + int res; + struct twl4030_bci *bci = container_of(data, struct twl4030_bci, + current_worker.work); + + res = twl4030bci_read_adc_val(TWL4030_BCIVBUS); + if (res < 0) + v = 0; + else + /* BCIVBUS uses ADCIN8, 7/1023 V/step */ + v = res * 6843; + curr = twl4030_charger_get_current(); + + dev_dbg(bci->dev, "v=%d cur=%d limit=%d target=%d\n", v, curr, + bci->usb_cur, bci->usb_cur_target); + + if (v < USB_MIN_VOLT) { + /* Back up and stop adjusting. */ + bci->usb_cur -= USB_CUR_STEP; + bci->usb_cur_target = bci->usb_cur; + } else if (bci->usb_cur >= bci->usb_cur_target || + bci->usb_cur + USB_CUR_STEP > USB_MAX_CURRENT) { + /* Reached target and voltage is OK - stop */ + return; + } else { + bci->usb_cur += USB_CUR_STEP; + schedule_delayed_work(&bci->current_worker, USB_CUR_DELAY); + } + twl4030_charger_update_current(bci); +} + +/* + * Enable/Disable USB Charge functionality. + */ +static int twl4030_charger_enable_usb(struct twl4030_bci *bci, bool enable) +{ + int ret; + + if (bci->usb_mode == CHARGE_OFF) + enable = false; + if (enable && !IS_ERR_OR_NULL(bci->transceiver)) { + + twl4030_charger_update_current(bci); + + /* Need to keep phy powered */ + if (!bci->usb_enabled) { + pm_runtime_get_sync(bci->transceiver->dev); + bci->usb_enabled = 1; + } + + if (bci->usb_mode == CHARGE_AUTO) + /* forcing the field BCIAUTOUSB (BOOT_BCI[1]) to 1 */ + ret = twl4030_clear_set_boot_bci(0, TWL4030_BCIAUTOUSB); + + /* forcing USBFASTMCHG(BCIMFSTS4[2]) to 1 */ + ret = twl4030_clear_set(TWL_MODULE_MAIN_CHARGE, 0, + TWL4030_USBFASTMCHG, TWL4030_BCIMFSTS4); + if (bci->usb_mode == CHARGE_LINEAR) { + twl4030_clear_set_boot_bci(TWL4030_BCIAUTOAC|TWL4030_CVENAC, 0); + /* Watch dog key: WOVF acknowledge */ + ret = twl_i2c_write_u8(TWL_MODULE_MAIN_CHARGE, 0x33, + TWL4030_BCIWDKEY); + /* 0x24 + EKEY6: off mode */ + ret = twl_i2c_write_u8(TWL_MODULE_MAIN_CHARGE, 0x2a, + TWL4030_BCIMDKEY); + /* EKEY2: Linear charge: USB path */ + ret = twl_i2c_write_u8(TWL_MODULE_MAIN_CHARGE, 0x26, + TWL4030_BCIMDKEY); + /* WDKEY5: stop watchdog count */ + ret = twl_i2c_write_u8(TWL_MODULE_MAIN_CHARGE, 0xf3, + TWL4030_BCIWDKEY); + /* enable MFEN3 access */ + ret = twl_i2c_write_u8(TWL_MODULE_MAIN_CHARGE, 0x9c, + TWL4030_BCIMFKEY); + /* ICHGEOCEN - end-of-charge monitor (current < 80mA) + * (charging continues) + * ICHGLOWEN - current level monitor (charge continues) + * don't monitor over-current or heat save + */ + ret = twl_i2c_write_u8(TWL_MODULE_MAIN_CHARGE, 0xf0, + TWL4030_BCIMFEN3); + } + } else { + ret = twl4030_clear_set_boot_bci(TWL4030_BCIAUTOUSB, 0); + ret |= twl_i2c_write_u8(TWL_MODULE_MAIN_CHARGE, 0x2a, + TWL4030_BCIMDKEY); + if (bci->usb_enabled) { + pm_runtime_mark_last_busy(bci->transceiver->dev); + pm_runtime_put_autosuspend(bci->transceiver->dev); + bci->usb_enabled = 0; + } + bci->usb_cur = 0; + } + + return ret; +} + +/* + * Enable/Disable AC Charge funtionality. + */ +static int twl4030_charger_enable_ac(struct twl4030_bci *bci, bool enable) +{ + int ret; + + if (bci->ac_mode == CHARGE_OFF) + enable = false; + + if (enable) + ret = twl4030_clear_set_boot_bci(0, TWL4030_BCIAUTOAC); + else + ret = twl4030_clear_set_boot_bci(TWL4030_BCIAUTOAC, 0); + + return ret; +} + +/* + * Enable/Disable charging of Backup Battery. + */ +static int twl4030_charger_enable_backup(int uvolt, int uamp) +{ + int ret; + u8 flags; + + if (uvolt < 2500000 || + uamp < 25) { + /* disable charging of backup battery */ + ret = twl4030_clear_set(TWL_MODULE_PM_RECEIVER, + TWL4030_BBCHEN, 0, TWL4030_BB_CFG); + return ret; + } + + flags = TWL4030_BBCHEN; + if (uvolt >= 3200000) + flags |= TWL4030_BBSEL_3V2; + else if (uvolt >= 3100000) + flags |= TWL4030_BBSEL_3V1; + else if (uvolt >= 3000000) + flags |= TWL4030_BBSEL_3V0; + else + flags |= TWL4030_BBSEL_2V5; + + if (uamp >= 1000) + flags |= TWL4030_BBISEL_1000uA; + else if (uamp >= 500) + flags |= TWL4030_BBISEL_500uA; + else if (uamp >= 150) + flags |= TWL4030_BBISEL_150uA; + else + flags |= TWL4030_BBISEL_25uA; + + ret = twl4030_clear_set(TWL_MODULE_PM_RECEIVER, + TWL4030_BBSEL_MASK | TWL4030_BBISEL_MASK, + flags, + TWL4030_BB_CFG); + + return ret; +} + +/* + * TWL4030 CHG_PRES (AC charger presence) events + */ +static irqreturn_t twl4030_charger_interrupt(int irq, void *arg) +{ + struct twl4030_bci *bci = arg; + + dev_dbg(bci->dev, "CHG_PRES irq\n"); + /* reset current on each 'plug' event */ + bci->ac_cur = 500000; + twl4030_charger_update_current(bci); + power_supply_changed(bci->ac); + power_supply_changed(bci->usb); + + return IRQ_HANDLED; +} + +/* + * TWL4030 BCI monitoring events + */ +static irqreturn_t twl4030_bci_interrupt(int irq, void *arg) +{ + struct twl4030_bci *bci = arg; + u8 irqs1, irqs2; + int ret; + + ret = twl_i2c_read_u8(TWL4030_MODULE_INTERRUPTS, &irqs1, + TWL4030_INTERRUPTS_BCIISR1A); + if (ret < 0) + return IRQ_HANDLED; + + ret = twl_i2c_read_u8(TWL4030_MODULE_INTERRUPTS, &irqs2, + TWL4030_INTERRUPTS_BCIISR2A); + if (ret < 0) + return IRQ_HANDLED; + + dev_dbg(bci->dev, "BCI irq %02x %02x\n", irqs2, irqs1); + + if (irqs1 & (TWL4030_ICHGLOW | TWL4030_ICHGEOC)) { + /* charger state change, inform the core */ + power_supply_changed(bci->ac); + power_supply_changed(bci->usb); + } + twl4030_charger_update_current(bci); + + /* various monitoring events, for now we just log them here */ + if (irqs1 & (TWL4030_TBATOR2 | TWL4030_TBATOR1)) + dev_warn(bci->dev, "battery temperature out of range\n"); + + if (irqs1 & TWL4030_BATSTS) + dev_crit(bci->dev, "battery disconnected\n"); + + if (irqs2 & TWL4030_VBATOV) + dev_crit(bci->dev, "VBAT overvoltage\n"); + + if (irqs2 & TWL4030_VBUSOV) + dev_crit(bci->dev, "VBUS overvoltage\n"); + + if (irqs2 & TWL4030_ACCHGOV) + dev_crit(bci->dev, "Ac charger overvoltage\n"); + + return IRQ_HANDLED; +} + +/* + * Provide "max_current" attribute in sysfs. + */ +static ssize_t +twl4030_bci_max_current_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t n) +{ + struct twl4030_bci *bci = dev_get_drvdata(dev->parent); + int cur = 0; + int status = 0; + status = kstrtoint(buf, 10, &cur); + if (status) + return status; + if (cur < 0) + return -EINVAL; + if (dev == &bci->ac->dev) + bci->ac_cur = cur; + else + bci->usb_cur_target = cur; + + twl4030_charger_update_current(bci); + return n; +} + +/* + * sysfs max_current show + */ +static ssize_t twl4030_bci_max_current_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int status = 0; + int cur = -1; + u8 bcictl1; + struct twl4030_bci *bci = dev_get_drvdata(dev->parent); + + if (dev == &bci->ac->dev) { + if (!bci->ac_is_active) + cur = bci->ac_cur; + } else { + if (bci->ac_is_active) + cur = bci->usb_cur_target; + } + if (cur < 0) { + cur = twl4030bci_read_adc_val(TWL4030_BCIIREF1); + if (cur < 0) + return cur; + status = twl4030_bci_read(TWL4030_BCICTL1, &bcictl1); + if (status < 0) + return status; + cur = regval2ua(cur, bcictl1 & TWL4030_CGAIN); + } + return scnprintf(buf, PAGE_SIZE, "%u\n", cur); +} + +static DEVICE_ATTR(max_current, 0644, twl4030_bci_max_current_show, + twl4030_bci_max_current_store); + +static void twl4030_bci_usb_work(struct work_struct *data) +{ + struct twl4030_bci *bci = container_of(data, struct twl4030_bci, work); + + switch (bci->event) { + case USB_EVENT_VBUS: + case USB_EVENT_CHARGER: + twl4030_charger_enable_usb(bci, true); + break; + case USB_EVENT_NONE: + twl4030_charger_enable_usb(bci, false); + break; + } +} + +static int twl4030_bci_usb_ncb(struct notifier_block *nb, unsigned long val, + void *priv) +{ + struct twl4030_bci *bci = container_of(nb, struct twl4030_bci, usb_nb); + + dev_dbg(bci->dev, "OTG notify %lu\n", val); + + /* reset current on each 'plug' event */ + if (allow_usb) + bci->usb_cur_target = 500000; + else + bci->usb_cur_target = 100000; + + bci->event = val; + schedule_work(&bci->work); + + return NOTIFY_OK; +} + +/* + * sysfs charger enabled store + */ +static ssize_t +twl4030_bci_mode_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t n) +{ + struct twl4030_bci *bci = dev_get_drvdata(dev->parent); + int mode; + int status; + + if (sysfs_streq(buf, modes[0])) + mode = 0; + else if (sysfs_streq(buf, modes[1])) + mode = 1; + else if (sysfs_streq(buf, modes[2])) + mode = 2; + else + return -EINVAL; + if (dev == &bci->ac->dev) { + if (mode == 2) + return -EINVAL; + twl4030_charger_enable_ac(bci, false); + bci->ac_mode = mode; + status = twl4030_charger_enable_ac(bci, true); + } else { + twl4030_charger_enable_usb(bci, false); + bci->usb_mode = mode; + status = twl4030_charger_enable_usb(bci, true); + } + return (status == 0) ? n : status; +} + +/* + * sysfs charger enabled show + */ +static ssize_t +twl4030_bci_mode_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct twl4030_bci *bci = dev_get_drvdata(dev->parent); + int len = 0; + int i; + int mode = bci->usb_mode; + + if (dev == &bci->ac->dev) + mode = bci->ac_mode; + + for (i = 0; i < ARRAY_SIZE(modes); i++) + if (mode == i) + len += snprintf(buf+len, PAGE_SIZE-len, + "[%s] ", modes[i]); + else + len += snprintf(buf+len, PAGE_SIZE-len, + "%s ", modes[i]); + buf[len-1] = '\n'; + return len; +} +static DEVICE_ATTR(mode, 0644, twl4030_bci_mode_show, + twl4030_bci_mode_store); + +static int twl4030_charger_get_current(void) +{ + int curr; + int ret; + u8 bcictl1; + + curr = twl4030bci_read_adc_val(TWL4030_BCIICHG); + if (curr < 0) + return curr; + + ret = twl4030_bci_read(TWL4030_BCICTL1, &bcictl1); + if (ret) + return ret; + + return regval2ua(curr, bcictl1 & TWL4030_CGAIN); +} + +/* + * Returns the main charge FSM state + * Or < 0 on failure. + */ +static int twl4030bci_state(struct twl4030_bci *bci) +{ + int ret; + u8 state; + + ret = twl4030_bci_read(TWL4030_BCIMSTATEC, &state); + if (ret) { + pr_err("twl4030_bci: error reading BCIMSTATEC\n"); + return ret; + } + + dev_dbg(bci->dev, "state: %02x\n", state); + + return state; +} + +static int twl4030_bci_state_to_status(int state) +{ + state &= TWL4030_MSTATEC_MASK; + if (TWL4030_MSTATEC_QUICK1 <= state && state <= TWL4030_MSTATEC_QUICK7) + return POWER_SUPPLY_STATUS_CHARGING; + else if (TWL4030_MSTATEC_COMPLETE1 <= state && + state <= TWL4030_MSTATEC_COMPLETE4) + return POWER_SUPPLY_STATUS_FULL; + else + return POWER_SUPPLY_STATUS_NOT_CHARGING; +} + +static int twl4030_bci_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct twl4030_bci *bci = dev_get_drvdata(psy->dev.parent); + int is_charging; + int state; + int ret; + + state = twl4030bci_state(bci); + if (state < 0) + return state; + + if (psy->desc->type == POWER_SUPPLY_TYPE_USB) + is_charging = state & TWL4030_MSTATEC_USB; + else + is_charging = state & TWL4030_MSTATEC_AC; + if (!is_charging) { + u8 s; + twl4030_bci_read(TWL4030_BCIMDEN, &s); + if (psy->desc->type == POWER_SUPPLY_TYPE_USB) + is_charging = s & 1; + else + is_charging = s & 2; + if (is_charging) + /* A little white lie */ + state = TWL4030_MSTATEC_QUICK1; + } + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + if (is_charging) + val->intval = twl4030_bci_state_to_status(state); + else + val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + /* charging must be active for meaningful result */ + if (!is_charging) + return -ENODATA; + if (psy->desc->type == POWER_SUPPLY_TYPE_USB) { + ret = twl4030bci_read_adc_val(TWL4030_BCIVBUS); + if (ret < 0) + return ret; + /* BCIVBUS uses ADCIN8, 7/1023 V/step */ + val->intval = ret * 6843; + } else { + ret = twl4030bci_read_adc_val(TWL4030_BCIVAC); + if (ret < 0) + return ret; + /* BCIVAC uses ADCIN11, 10/1023 V/step */ + val->intval = ret * 9775; + } + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + if (!is_charging) + return -ENODATA; + /* current measurement is shared between AC and USB */ + ret = twl4030_charger_get_current(); + if (ret < 0) + return ret; + val->intval = ret; + break; + case POWER_SUPPLY_PROP_ONLINE: + val->intval = is_charging && + twl4030_bci_state_to_status(state) != + POWER_SUPPLY_STATUS_NOT_CHARGING; + break; + default: + return -EINVAL; + } + + return 0; +} + +static enum power_supply_property twl4030_charger_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CURRENT_NOW, +}; + +#ifdef CONFIG_OF +static const struct twl4030_bci_platform_data * +twl4030_bci_parse_dt(struct device *dev) +{ + struct device_node *np = dev->of_node; + struct twl4030_bci_platform_data *pdata; + u32 num; + + if (!np) + return NULL; + pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) + return pdata; + + if (of_property_read_u32(np, "ti,bb-uvolt", &num) == 0) + pdata->bb_uvolt = num; + if (of_property_read_u32(np, "ti,bb-uamp", &num) == 0) + pdata->bb_uamp = num; + return pdata; +} +#else +static inline const struct twl4030_bci_platform_data * +twl4030_bci_parse_dt(struct device *dev) +{ + return NULL; +} +#endif + +static const struct power_supply_desc twl4030_bci_ac_desc = { + .name = "twl4030_ac", + .type = POWER_SUPPLY_TYPE_MAINS, + .properties = twl4030_charger_props, + .num_properties = ARRAY_SIZE(twl4030_charger_props), + .get_property = twl4030_bci_get_property, +}; + +static const struct power_supply_desc twl4030_bci_usb_desc = { + .name = "twl4030_usb", + .type = POWER_SUPPLY_TYPE_USB, + .properties = twl4030_charger_props, + .num_properties = ARRAY_SIZE(twl4030_charger_props), + .get_property = twl4030_bci_get_property, +}; + +static int twl4030_bci_probe(struct platform_device *pdev) +{ + struct twl4030_bci *bci; + const struct twl4030_bci_platform_data *pdata = pdev->dev.platform_data; + int ret; + u32 reg; + + bci = devm_kzalloc(&pdev->dev, sizeof(*bci), GFP_KERNEL); + if (bci == NULL) + return -ENOMEM; + + if (!pdata) + pdata = twl4030_bci_parse_dt(&pdev->dev); + + bci->ichg_eoc = 80100; /* Stop charging when current drops to here */ + bci->ichg_lo = 241000; /* Low threshold */ + bci->ichg_hi = 500000; /* High threshold */ + bci->ac_cur = 500000; /* 500mA */ + if (allow_usb) + bci->usb_cur_target = 500000; /* 500mA */ + else + bci->usb_cur_target = 100000; /* 100mA */ + bci->usb_mode = CHARGE_AUTO; + bci->ac_mode = CHARGE_AUTO; + + bci->dev = &pdev->dev; + bci->irq_chg = platform_get_irq(pdev, 0); + bci->irq_bci = platform_get_irq(pdev, 1); + + /* Only proceed further *IF* battery is physically present */ + ret = twl4030_is_battery_present(bci); + if (ret) { + dev_crit(&pdev->dev, "Battery was not detected:%d\n", ret); + return ret; + } + + platform_set_drvdata(pdev, bci); + + bci->ac = devm_power_supply_register(&pdev->dev, &twl4030_bci_ac_desc, + NULL); + if (IS_ERR(bci->ac)) { + ret = PTR_ERR(bci->ac); + dev_err(&pdev->dev, "failed to register ac: %d\n", ret); + return ret; + } + + bci->usb = devm_power_supply_register(&pdev->dev, &twl4030_bci_usb_desc, + NULL); + if (IS_ERR(bci->usb)) { + ret = PTR_ERR(bci->usb); + dev_err(&pdev->dev, "failed to register usb: %d\n", ret); + return ret; + } + + ret = devm_request_threaded_irq(&pdev->dev, bci->irq_chg, NULL, + twl4030_charger_interrupt, IRQF_ONESHOT, pdev->name, + bci); + if (ret < 0) { + dev_err(&pdev->dev, "could not request irq %d, status %d\n", + bci->irq_chg, ret); + return ret; + } + + ret = devm_request_threaded_irq(&pdev->dev, bci->irq_bci, NULL, + twl4030_bci_interrupt, IRQF_ONESHOT, pdev->name, bci); + if (ret < 0) { + dev_err(&pdev->dev, "could not request irq %d, status %d\n", + bci->irq_bci, ret); + 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); + + bci->usb_nb.notifier_call = twl4030_bci_usb_ncb; + if (bci->dev->of_node) { + struct device_node *phynode; + + phynode = of_find_compatible_node(bci->dev->of_node->parent, + NULL, "ti,twl4030-usb"); + if (phynode) + bci->transceiver = devm_usb_get_phy_by_node( + bci->dev, phynode, &bci->usb_nb); + } + + /* Enable interrupts now. */ + reg = ~(u32)(TWL4030_ICHGLOW | TWL4030_ICHGEOC | TWL4030_TBATOR2 | + TWL4030_TBATOR1 | TWL4030_BATSTS); + ret = twl_i2c_write_u8(TWL4030_MODULE_INTERRUPTS, reg, + TWL4030_INTERRUPTS_BCIIMR1A); + if (ret < 0) { + dev_err(&pdev->dev, "failed to unmask interrupts: %d\n", ret); + goto fail; + } + + reg = ~(u32)(TWL4030_VBATOV | TWL4030_VBUSOV | TWL4030_ACCHGOV); + ret = twl_i2c_write_u8(TWL4030_MODULE_INTERRUPTS, reg, + TWL4030_INTERRUPTS_BCIIMR2A); + if (ret < 0) + dev_warn(&pdev->dev, "failed to unmask interrupts: %d\n", ret); + + twl4030_charger_update_current(bci); + if (device_create_file(&bci->usb->dev, &dev_attr_max_current)) + dev_warn(&pdev->dev, "could not create sysfs file\n"); + if (device_create_file(&bci->usb->dev, &dev_attr_mode)) + dev_warn(&pdev->dev, "could not create sysfs file\n"); + if (device_create_file(&bci->ac->dev, &dev_attr_mode)) + dev_warn(&pdev->dev, "could not create sysfs file\n"); + if (device_create_file(&bci->ac->dev, &dev_attr_max_current)) + dev_warn(&pdev->dev, "could not create sysfs file\n"); + + twl4030_charger_enable_ac(bci, true); + if (!IS_ERR_OR_NULL(bci->transceiver)) + twl4030_bci_usb_ncb(&bci->usb_nb, + bci->transceiver->last_event, + NULL); + else + twl4030_charger_enable_usb(bci, false); + if (pdata) + twl4030_charger_enable_backup(pdata->bb_uvolt, + pdata->bb_uamp); + else + 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) +{ + struct twl4030_bci *bci = platform_get_drvdata(pdev); + + twl4030_charger_enable_ac(bci, false); + twl4030_charger_enable_usb(bci, false); + twl4030_charger_enable_backup(0, 0); + + iio_channel_release(bci->channel_vac); + + device_remove_file(&bci->usb->dev, &dev_attr_max_current); + device_remove_file(&bci->usb->dev, &dev_attr_mode); + device_remove_file(&bci->ac->dev, &dev_attr_max_current); + device_remove_file(&bci->ac->dev, &dev_attr_mode); + /* mask interrupts */ + twl_i2c_write_u8(TWL4030_MODULE_INTERRUPTS, 0xff, + TWL4030_INTERRUPTS_BCIIMR1A); + twl_i2c_write_u8(TWL4030_MODULE_INTERRUPTS, 0xff, + TWL4030_INTERRUPTS_BCIIMR2A); + + return 0; +} + +static const struct of_device_id twl_bci_of_match[] = { + {.compatible = "ti,twl4030-bci", }, + { } +}; +MODULE_DEVICE_TABLE(of, twl_bci_of_match); + +static struct platform_driver twl4030_bci_driver = { + .probe = twl4030_bci_probe, + .driver = { + .name = "twl4030_bci", + .of_match_table = of_match_ptr(twl_bci_of_match), + }, + .remove = __exit_p(twl4030_bci_remove), +}; +module_platform_driver(twl4030_bci_driver); + +MODULE_AUTHOR("Gražvydas Ignotas"); +MODULE_DESCRIPTION("TWL4030 Battery Charger Interface driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:twl4030_bci"); diff --git a/drivers/power/supply/twl4030_madc_battery.c b/drivers/power/supply/twl4030_madc_battery.c new file mode 100644 index 000000000000..f5817e422d64 --- /dev/null +++ b/drivers/power/supply/twl4030_madc_battery.c @@ -0,0 +1,278 @@ +/* + * Dumb driver for LiIon batteries using TWL4030 madc. + * + * Copyright 2013 Golden Delicious Computers + * Lukas Märdian + * + * Based on dumb driver for gta01 battery + * Copyright 2009 Openmoko, Inc + * Balaji Rao + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct twl4030_madc_battery { + struct power_supply *psy; + struct twl4030_madc_bat_platform_data *pdata; + struct iio_channel *channel_temp; + struct iio_channel *channel_ichg; + struct iio_channel *channel_vbat; +}; + +static enum power_supply_property twl4030_madc_bat_props[] = { + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_CHARGE_FULL, + POWER_SUPPLY_PROP_CHARGE_NOW, + POWER_SUPPLY_PROP_TEMP, + POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW, +}; + +static int madc_read(struct iio_channel *channel) +{ + int val, err; + err = iio_read_channel_processed(channel, &val); + if (err < 0) + return err; + + return val; +} + +static int twl4030_madc_bat_get_charging_status(struct twl4030_madc_battery *bt) +{ + return (madc_read(bt->channel_ichg) > 0) ? 1 : 0; +} + +static int twl4030_madc_bat_get_voltage(struct twl4030_madc_battery *bt) +{ + return madc_read(bt->channel_vbat); +} + +static int twl4030_madc_bat_get_current(struct twl4030_madc_battery *bt) +{ + return madc_read(bt->channel_ichg) * 1000; +} + +static int twl4030_madc_bat_get_temp(struct twl4030_madc_battery *bt) +{ + return madc_read(bt->channel_temp) * 10; +} + +static int twl4030_madc_bat_voltscale(struct twl4030_madc_battery *bat, + int volt) +{ + struct twl4030_madc_bat_calibration *calibration; + int i, res = 0; + + /* choose charging curve */ + if (twl4030_madc_bat_get_charging_status(bat)) + calibration = bat->pdata->charging; + else + calibration = bat->pdata->discharging; + + if (volt > calibration[0].voltage) { + res = calibration[0].level; + } else { + for (i = 0; calibration[i+1].voltage >= 0; i++) { + if (volt <= calibration[i].voltage && + volt >= calibration[i+1].voltage) { + /* interval found - interpolate within range */ + res = calibration[i].level - + ((calibration[i].voltage - volt) * + (calibration[i].level - + calibration[i+1].level)) / + (calibration[i].voltage - + calibration[i+1].voltage); + break; + } + } + } + return res; +} + +static int twl4030_madc_bat_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct twl4030_madc_battery *bat = power_supply_get_drvdata(psy); + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + if (twl4030_madc_bat_voltscale(bat, + twl4030_madc_bat_get_voltage(bat)) > 95) + val->intval = POWER_SUPPLY_STATUS_FULL; + else { + if (twl4030_madc_bat_get_charging_status(bat)) + val->intval = POWER_SUPPLY_STATUS_CHARGING; + else + val->intval = POWER_SUPPLY_STATUS_DISCHARGING; + } + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + val->intval = twl4030_madc_bat_get_voltage(bat) * 1000; + break; + case POWER_SUPPLY_PROP_TECHNOLOGY: + val->intval = POWER_SUPPLY_TECHNOLOGY_LION; + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + val->intval = twl4030_madc_bat_get_current(bat); + break; + case POWER_SUPPLY_PROP_PRESENT: + /* assume battery is always present */ + val->intval = 1; + break; + case POWER_SUPPLY_PROP_CHARGE_NOW: { + int percent = twl4030_madc_bat_voltscale(bat, + twl4030_madc_bat_get_voltage(bat)); + val->intval = (percent * bat->pdata->capacity) / 100; + break; + } + case POWER_SUPPLY_PROP_CAPACITY: + val->intval = twl4030_madc_bat_voltscale(bat, + twl4030_madc_bat_get_voltage(bat)); + break; + case POWER_SUPPLY_PROP_CHARGE_FULL: + val->intval = bat->pdata->capacity; + break; + case POWER_SUPPLY_PROP_TEMP: + val->intval = twl4030_madc_bat_get_temp(bat); + break; + case POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW: { + int percent = twl4030_madc_bat_voltscale(bat, + twl4030_madc_bat_get_voltage(bat)); + /* in mAh */ + int chg = (percent * (bat->pdata->capacity/1000))/100; + + /* assume discharge with 400 mA (ca. 1.5W) */ + val->intval = (3600l * chg) / 400; + break; + } + default: + return -EINVAL; + } + + return 0; +} + +static void twl4030_madc_bat_ext_changed(struct power_supply *psy) +{ + power_supply_changed(psy); +} + +static const struct power_supply_desc twl4030_madc_bat_desc = { + .name = "twl4030_battery", + .type = POWER_SUPPLY_TYPE_BATTERY, + .properties = twl4030_madc_bat_props, + .num_properties = ARRAY_SIZE(twl4030_madc_bat_props), + .get_property = twl4030_madc_bat_get_property, + .external_power_changed = twl4030_madc_bat_ext_changed, + +}; + +static int twl4030_cmp(const void *a, const void *b) +{ + return ((struct twl4030_madc_bat_calibration *)b)->voltage - + ((struct twl4030_madc_bat_calibration *)a)->voltage; +} + +static int twl4030_madc_battery_probe(struct platform_device *pdev) +{ + struct twl4030_madc_battery *twl4030_madc_bat; + struct twl4030_madc_bat_platform_data *pdata = pdev->dev.platform_data; + struct power_supply_config psy_cfg = {}; + int ret = 0; + + twl4030_madc_bat = devm_kzalloc(&pdev->dev, sizeof(*twl4030_madc_bat), + GFP_KERNEL); + if (!twl4030_madc_bat) + return -ENOMEM; + + twl4030_madc_bat->channel_temp = iio_channel_get(&pdev->dev, "temp"); + if (IS_ERR(twl4030_madc_bat->channel_temp)) { + ret = PTR_ERR(twl4030_madc_bat->channel_temp); + goto err; + } + + twl4030_madc_bat->channel_ichg = iio_channel_get(&pdev->dev, "ichg"); + if (IS_ERR(twl4030_madc_bat->channel_ichg)) { + ret = PTR_ERR(twl4030_madc_bat->channel_ichg); + goto err_temp; + } + + twl4030_madc_bat->channel_vbat = iio_channel_get(&pdev->dev, "vbat"); + if (IS_ERR(twl4030_madc_bat->channel_vbat)) { + ret = PTR_ERR(twl4030_madc_bat->channel_vbat); + goto err_ichg; + } + + /* sort charging and discharging calibration data */ + sort(pdata->charging, pdata->charging_size, + sizeof(struct twl4030_madc_bat_calibration), + twl4030_cmp, NULL); + sort(pdata->discharging, pdata->discharging_size, + sizeof(struct twl4030_madc_bat_calibration), + twl4030_cmp, NULL); + + twl4030_madc_bat->pdata = pdata; + platform_set_drvdata(pdev, twl4030_madc_bat); + psy_cfg.drv_data = twl4030_madc_bat; + twl4030_madc_bat->psy = power_supply_register(&pdev->dev, + &twl4030_madc_bat_desc, + &psy_cfg); + if (IS_ERR(twl4030_madc_bat->psy)) { + ret = PTR_ERR(twl4030_madc_bat->psy); + goto err_vbat; + } + + return 0; + +err_vbat: + iio_channel_release(twl4030_madc_bat->channel_vbat); +err_ichg: + iio_channel_release(twl4030_madc_bat->channel_ichg); +err_temp: + iio_channel_release(twl4030_madc_bat->channel_temp); +err: + return ret; +} + +static int twl4030_madc_battery_remove(struct platform_device *pdev) +{ + struct twl4030_madc_battery *bat = platform_get_drvdata(pdev); + + power_supply_unregister(bat->psy); + + iio_channel_release(bat->channel_vbat); + iio_channel_release(bat->channel_ichg); + iio_channel_release(bat->channel_temp); + + return 0; +} + +static struct platform_driver twl4030_madc_battery_driver = { + .driver = { + .name = "twl4030_madc_battery", + }, + .probe = twl4030_madc_battery_probe, + .remove = twl4030_madc_battery_remove, +}; +module_platform_driver(twl4030_madc_battery_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Lukas Märdian "); +MODULE_DESCRIPTION("twl4030_madc battery driver"); +MODULE_ALIAS("platform:twl4030_madc_battery"); diff --git a/drivers/power/supply/wm831x_backup.c b/drivers/power/supply/wm831x_backup.c new file mode 100644 index 000000000000..2e33109ca8c7 --- /dev/null +++ b/drivers/power/supply/wm831x_backup.c @@ -0,0 +1,225 @@ +/* + * Backup battery driver for Wolfson Microelectronics wm831x PMICs + * + * Copyright 2009 Wolfson Microelectronics PLC. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +struct wm831x_backup { + struct wm831x *wm831x; + struct power_supply *backup; + struct power_supply_desc backup_desc; + char name[20]; +}; + +static int wm831x_backup_read_voltage(struct wm831x *wm831x, + enum wm831x_auxadc src, + union power_supply_propval *val) +{ + int ret; + + ret = wm831x_auxadc_read_uv(wm831x, src); + if (ret >= 0) + val->intval = ret; + + return ret; +} + +/********************************************************************* + * Backup supply properties + *********************************************************************/ + +static void wm831x_config_backup(struct wm831x *wm831x) +{ + struct wm831x_pdata *wm831x_pdata = wm831x->dev->platform_data; + struct wm831x_backup_pdata *pdata; + int ret, reg; + + if (!wm831x_pdata || !wm831x_pdata->backup) { + dev_warn(wm831x->dev, + "No backup battery charger configuration\n"); + return; + } + + pdata = wm831x_pdata->backup; + + reg = 0; + + if (pdata->charger_enable) + reg |= WM831X_BKUP_CHG_ENA | WM831X_BKUP_BATT_DET_ENA; + if (pdata->no_constant_voltage) + reg |= WM831X_BKUP_CHG_MODE; + + switch (pdata->vlim) { + case 2500: + break; + case 3100: + reg |= WM831X_BKUP_CHG_VLIM; + break; + default: + dev_err(wm831x->dev, "Invalid backup voltage limit %dmV\n", + pdata->vlim); + } + + switch (pdata->ilim) { + case 100: + break; + case 200: + reg |= 1; + break; + case 300: + reg |= 2; + break; + case 400: + reg |= 3; + break; + default: + dev_err(wm831x->dev, "Invalid backup current limit %duA\n", + pdata->ilim); + } + + ret = wm831x_reg_unlock(wm831x); + if (ret != 0) { + dev_err(wm831x->dev, "Failed to unlock registers: %d\n", ret); + return; + } + + ret = wm831x_set_bits(wm831x, WM831X_BACKUP_CHARGER_CONTROL, + WM831X_BKUP_CHG_ENA_MASK | + WM831X_BKUP_CHG_MODE_MASK | + WM831X_BKUP_BATT_DET_ENA_MASK | + WM831X_BKUP_CHG_VLIM_MASK | + WM831X_BKUP_CHG_ILIM_MASK, + reg); + if (ret != 0) + dev_err(wm831x->dev, + "Failed to set backup charger config: %d\n", ret); + + wm831x_reg_lock(wm831x); +} + +static int wm831x_backup_get_prop(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct wm831x_backup *devdata = dev_get_drvdata(psy->dev.parent); + struct wm831x *wm831x = devdata->wm831x; + int ret = 0; + + ret = wm831x_reg_read(wm831x, WM831X_BACKUP_CHARGER_CONTROL); + if (ret < 0) + return ret; + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + if (ret & WM831X_BKUP_CHG_STS) + val->intval = POWER_SUPPLY_STATUS_CHARGING; + else + val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; + break; + + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + ret = wm831x_backup_read_voltage(wm831x, WM831X_AUX_BKUP_BATT, + val); + break; + + case POWER_SUPPLY_PROP_PRESENT: + if (ret & WM831X_BKUP_CHG_STS) + val->intval = 1; + else + val->intval = 0; + break; + + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static enum power_supply_property wm831x_backup_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_PRESENT, +}; + +/********************************************************************* + * Initialisation + *********************************************************************/ + +static int wm831x_backup_probe(struct platform_device *pdev) +{ + struct wm831x *wm831x = dev_get_drvdata(pdev->dev.parent); + struct wm831x_pdata *wm831x_pdata = wm831x->dev->platform_data; + struct wm831x_backup *devdata; + + devdata = devm_kzalloc(&pdev->dev, sizeof(struct wm831x_backup), + GFP_KERNEL); + if (devdata == NULL) + return -ENOMEM; + + devdata->wm831x = wm831x; + platform_set_drvdata(pdev, devdata); + + /* We ignore configuration failures since we can still read + * back the status without enabling the charger (which may + * already be enabled anyway). + */ + wm831x_config_backup(wm831x); + + if (wm831x_pdata && wm831x_pdata->wm831x_num) + snprintf(devdata->name, sizeof(devdata->name), + "wm831x-backup.%d", wm831x_pdata->wm831x_num); + else + snprintf(devdata->name, sizeof(devdata->name), + "wm831x-backup"); + + devdata->backup_desc.name = devdata->name; + devdata->backup_desc.type = POWER_SUPPLY_TYPE_BATTERY; + devdata->backup_desc.properties = wm831x_backup_props; + devdata->backup_desc.num_properties = ARRAY_SIZE(wm831x_backup_props); + devdata->backup_desc.get_property = wm831x_backup_get_prop; + devdata->backup = power_supply_register(&pdev->dev, + &devdata->backup_desc, NULL); + + return PTR_ERR_OR_ZERO(devdata->backup); +} + +static int wm831x_backup_remove(struct platform_device *pdev) +{ + struct wm831x_backup *devdata = platform_get_drvdata(pdev); + + power_supply_unregister(devdata->backup); + + return 0; +} + +static struct platform_driver wm831x_backup_driver = { + .probe = wm831x_backup_probe, + .remove = wm831x_backup_remove, + .driver = { + .name = "wm831x-backup", + }, +}; + +module_platform_driver(wm831x_backup_driver); + +MODULE_DESCRIPTION("Backup battery charger driver for WM831x PMICs"); +MODULE_AUTHOR("Mark Brown "); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:wm831x-backup"); diff --git a/drivers/power/supply/wm831x_power.c b/drivers/power/supply/wm831x_power.c new file mode 100644 index 000000000000..7082301da945 --- /dev/null +++ b/drivers/power/supply/wm831x_power.c @@ -0,0 +1,673 @@ +/* + * PMU driver for Wolfson Microelectronics wm831x PMICs + * + * Copyright 2009 Wolfson Microelectronics PLC. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +struct wm831x_power { + struct wm831x *wm831x; + struct power_supply *wall; + struct power_supply *usb; + struct power_supply *battery; + struct power_supply_desc wall_desc; + struct power_supply_desc usb_desc; + struct power_supply_desc battery_desc; + char wall_name[20]; + char usb_name[20]; + char battery_name[20]; + bool have_battery; +}; + +static int wm831x_power_check_online(struct wm831x *wm831x, int supply, + union power_supply_propval *val) +{ + int ret; + + ret = wm831x_reg_read(wm831x, WM831X_SYSTEM_STATUS); + if (ret < 0) + return ret; + + if (ret & supply) + val->intval = 1; + else + val->intval = 0; + + return 0; +} + +static int wm831x_power_read_voltage(struct wm831x *wm831x, + enum wm831x_auxadc src, + union power_supply_propval *val) +{ + int ret; + + ret = wm831x_auxadc_read_uv(wm831x, src); + if (ret >= 0) + val->intval = ret; + + return ret; +} + +/********************************************************************* + * WALL Power + *********************************************************************/ +static int wm831x_wall_get_prop(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct wm831x_power *wm831x_power = dev_get_drvdata(psy->dev.parent); + struct wm831x *wm831x = wm831x_power->wm831x; + int ret = 0; + + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + ret = wm831x_power_check_online(wm831x, WM831X_PWR_WALL, val); + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + ret = wm831x_power_read_voltage(wm831x, WM831X_AUX_WALL, val); + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static enum power_supply_property wm831x_wall_props[] = { + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_VOLTAGE_NOW, +}; + +/********************************************************************* + * USB Power + *********************************************************************/ +static int wm831x_usb_get_prop(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct wm831x_power *wm831x_power = dev_get_drvdata(psy->dev.parent); + struct wm831x *wm831x = wm831x_power->wm831x; + int ret = 0; + + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + ret = wm831x_power_check_online(wm831x, WM831X_PWR_USB, val); + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + ret = wm831x_power_read_voltage(wm831x, WM831X_AUX_USB, val); + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static enum power_supply_property wm831x_usb_props[] = { + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_VOLTAGE_NOW, +}; + +/********************************************************************* + * Battery properties + *********************************************************************/ + +struct chg_map { + int val; + int reg_val; +}; + +static struct chg_map trickle_ilims[] = { + { 50, 0 << WM831X_CHG_TRKL_ILIM_SHIFT }, + { 100, 1 << WM831X_CHG_TRKL_ILIM_SHIFT }, + { 150, 2 << WM831X_CHG_TRKL_ILIM_SHIFT }, + { 200, 3 << WM831X_CHG_TRKL_ILIM_SHIFT }, +}; + +static struct chg_map vsels[] = { + { 4050, 0 << WM831X_CHG_VSEL_SHIFT }, + { 4100, 1 << WM831X_CHG_VSEL_SHIFT }, + { 4150, 2 << WM831X_CHG_VSEL_SHIFT }, + { 4200, 3 << WM831X_CHG_VSEL_SHIFT }, +}; + +static struct chg_map fast_ilims[] = { + { 0, 0 << WM831X_CHG_FAST_ILIM_SHIFT }, + { 50, 1 << WM831X_CHG_FAST_ILIM_SHIFT }, + { 100, 2 << WM831X_CHG_FAST_ILIM_SHIFT }, + { 150, 3 << WM831X_CHG_FAST_ILIM_SHIFT }, + { 200, 4 << WM831X_CHG_FAST_ILIM_SHIFT }, + { 250, 5 << WM831X_CHG_FAST_ILIM_SHIFT }, + { 300, 6 << WM831X_CHG_FAST_ILIM_SHIFT }, + { 350, 7 << WM831X_CHG_FAST_ILIM_SHIFT }, + { 400, 8 << WM831X_CHG_FAST_ILIM_SHIFT }, + { 450, 9 << WM831X_CHG_FAST_ILIM_SHIFT }, + { 500, 10 << WM831X_CHG_FAST_ILIM_SHIFT }, + { 600, 11 << WM831X_CHG_FAST_ILIM_SHIFT }, + { 700, 12 << WM831X_CHG_FAST_ILIM_SHIFT }, + { 800, 13 << WM831X_CHG_FAST_ILIM_SHIFT }, + { 900, 14 << WM831X_CHG_FAST_ILIM_SHIFT }, + { 1000, 15 << WM831X_CHG_FAST_ILIM_SHIFT }, +}; + +static struct chg_map eoc_iterms[] = { + { 20, 0 << WM831X_CHG_ITERM_SHIFT }, + { 30, 1 << WM831X_CHG_ITERM_SHIFT }, + { 40, 2 << WM831X_CHG_ITERM_SHIFT }, + { 50, 3 << WM831X_CHG_ITERM_SHIFT }, + { 60, 4 << WM831X_CHG_ITERM_SHIFT }, + { 70, 5 << WM831X_CHG_ITERM_SHIFT }, + { 80, 6 << WM831X_CHG_ITERM_SHIFT }, + { 90, 7 << WM831X_CHG_ITERM_SHIFT }, +}; + +static struct chg_map chg_times[] = { + { 60, 0 << WM831X_CHG_TIME_SHIFT }, + { 90, 1 << WM831X_CHG_TIME_SHIFT }, + { 120, 2 << WM831X_CHG_TIME_SHIFT }, + { 150, 3 << WM831X_CHG_TIME_SHIFT }, + { 180, 4 << WM831X_CHG_TIME_SHIFT }, + { 210, 5 << WM831X_CHG_TIME_SHIFT }, + { 240, 6 << WM831X_CHG_TIME_SHIFT }, + { 270, 7 << WM831X_CHG_TIME_SHIFT }, + { 300, 8 << WM831X_CHG_TIME_SHIFT }, + { 330, 9 << WM831X_CHG_TIME_SHIFT }, + { 360, 10 << WM831X_CHG_TIME_SHIFT }, + { 390, 11 << WM831X_CHG_TIME_SHIFT }, + { 420, 12 << WM831X_CHG_TIME_SHIFT }, + { 450, 13 << WM831X_CHG_TIME_SHIFT }, + { 480, 14 << WM831X_CHG_TIME_SHIFT }, + { 510, 15 << WM831X_CHG_TIME_SHIFT }, +}; + +static void wm831x_battey_apply_config(struct wm831x *wm831x, + struct chg_map *map, int count, int val, + int *reg, const char *name, + const char *units) +{ + int i; + + for (i = 0; i < count; i++) + if (val == map[i].val) + break; + if (i == count) { + dev_err(wm831x->dev, "Invalid %s %d%s\n", + name, val, units); + } else { + *reg |= map[i].reg_val; + dev_dbg(wm831x->dev, "Set %s of %d%s\n", name, val, units); + } +} + +static void wm831x_config_battery(struct wm831x *wm831x) +{ + struct wm831x_pdata *wm831x_pdata = wm831x->dev->platform_data; + struct wm831x_battery_pdata *pdata; + int ret, reg1, reg2; + + if (!wm831x_pdata || !wm831x_pdata->battery) { + dev_warn(wm831x->dev, + "No battery charger configuration\n"); + return; + } + + pdata = wm831x_pdata->battery; + + reg1 = 0; + reg2 = 0; + + if (!pdata->enable) { + dev_info(wm831x->dev, "Battery charger disabled\n"); + return; + } + + reg1 |= WM831X_CHG_ENA; + if (pdata->off_mask) + reg2 |= WM831X_CHG_OFF_MSK; + if (pdata->fast_enable) + reg1 |= WM831X_CHG_FAST; + + wm831x_battey_apply_config(wm831x, trickle_ilims, + ARRAY_SIZE(trickle_ilims), + pdata->trickle_ilim, ®2, + "trickle charge current limit", "mA"); + + wm831x_battey_apply_config(wm831x, vsels, ARRAY_SIZE(vsels), + pdata->vsel, ®2, + "target voltage", "mV"); + + wm831x_battey_apply_config(wm831x, fast_ilims, ARRAY_SIZE(fast_ilims), + pdata->fast_ilim, ®2, + "fast charge current limit", "mA"); + + wm831x_battey_apply_config(wm831x, eoc_iterms, ARRAY_SIZE(eoc_iterms), + pdata->eoc_iterm, ®1, + "end of charge current threshold", "mA"); + + wm831x_battey_apply_config(wm831x, chg_times, ARRAY_SIZE(chg_times), + pdata->timeout, ®2, + "charger timeout", "min"); + + ret = wm831x_reg_unlock(wm831x); + if (ret != 0) { + dev_err(wm831x->dev, "Failed to unlock registers: %d\n", ret); + return; + } + + ret = wm831x_set_bits(wm831x, WM831X_CHARGER_CONTROL_1, + WM831X_CHG_ENA_MASK | + WM831X_CHG_FAST_MASK | + WM831X_CHG_ITERM_MASK, + reg1); + if (ret != 0) + dev_err(wm831x->dev, "Failed to set charger control 1: %d\n", + ret); + + ret = wm831x_set_bits(wm831x, WM831X_CHARGER_CONTROL_2, + WM831X_CHG_OFF_MSK | + WM831X_CHG_TIME_MASK | + WM831X_CHG_FAST_ILIM_MASK | + WM831X_CHG_TRKL_ILIM_MASK | + WM831X_CHG_VSEL_MASK, + reg2); + if (ret != 0) + dev_err(wm831x->dev, "Failed to set charger control 2: %d\n", + ret); + + wm831x_reg_lock(wm831x); +} + +static int wm831x_bat_check_status(struct wm831x *wm831x, int *status) +{ + int ret; + + ret = wm831x_reg_read(wm831x, WM831X_SYSTEM_STATUS); + if (ret < 0) + return ret; + + if (ret & WM831X_PWR_SRC_BATT) { + *status = POWER_SUPPLY_STATUS_DISCHARGING; + return 0; + } + + ret = wm831x_reg_read(wm831x, WM831X_CHARGER_STATUS); + if (ret < 0) + return ret; + + switch (ret & WM831X_CHG_STATE_MASK) { + case WM831X_CHG_STATE_OFF: + *status = POWER_SUPPLY_STATUS_NOT_CHARGING; + break; + case WM831X_CHG_STATE_TRICKLE: + case WM831X_CHG_STATE_FAST: + *status = POWER_SUPPLY_STATUS_CHARGING; + break; + + default: + *status = POWER_SUPPLY_STATUS_UNKNOWN; + break; + } + + return 0; +} + +static int wm831x_bat_check_type(struct wm831x *wm831x, int *type) +{ + int ret; + + ret = wm831x_reg_read(wm831x, WM831X_CHARGER_STATUS); + if (ret < 0) + return ret; + + switch (ret & WM831X_CHG_STATE_MASK) { + case WM831X_CHG_STATE_TRICKLE: + case WM831X_CHG_STATE_TRICKLE_OT: + *type = POWER_SUPPLY_CHARGE_TYPE_TRICKLE; + break; + case WM831X_CHG_STATE_FAST: + case WM831X_CHG_STATE_FAST_OT: + *type = POWER_SUPPLY_CHARGE_TYPE_FAST; + break; + default: + *type = POWER_SUPPLY_CHARGE_TYPE_NONE; + break; + } + + return 0; +} + +static int wm831x_bat_check_health(struct wm831x *wm831x, int *health) +{ + int ret; + + ret = wm831x_reg_read(wm831x, WM831X_CHARGER_STATUS); + if (ret < 0) + return ret; + + if (ret & WM831X_BATT_HOT_STS) { + *health = POWER_SUPPLY_HEALTH_OVERHEAT; + return 0; + } + + if (ret & WM831X_BATT_COLD_STS) { + *health = POWER_SUPPLY_HEALTH_COLD; + return 0; + } + + if (ret & WM831X_BATT_OV_STS) { + *health = POWER_SUPPLY_HEALTH_OVERVOLTAGE; + return 0; + } + + switch (ret & WM831X_CHG_STATE_MASK) { + case WM831X_CHG_STATE_TRICKLE_OT: + case WM831X_CHG_STATE_FAST_OT: + *health = POWER_SUPPLY_HEALTH_OVERHEAT; + break; + case WM831X_CHG_STATE_DEFECTIVE: + *health = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; + break; + default: + *health = POWER_SUPPLY_HEALTH_GOOD; + break; + } + + return 0; +} + +static int wm831x_bat_get_prop(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct wm831x_power *wm831x_power = dev_get_drvdata(psy->dev.parent); + struct wm831x *wm831x = wm831x_power->wm831x; + int ret = 0; + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + ret = wm831x_bat_check_status(wm831x, &val->intval); + break; + case POWER_SUPPLY_PROP_ONLINE: + ret = wm831x_power_check_online(wm831x, WM831X_PWR_SRC_BATT, + val); + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + ret = wm831x_power_read_voltage(wm831x, WM831X_AUX_BATT, val); + break; + case POWER_SUPPLY_PROP_HEALTH: + ret = wm831x_bat_check_health(wm831x, &val->intval); + break; + case POWER_SUPPLY_PROP_CHARGE_TYPE: + ret = wm831x_bat_check_type(wm831x, &val->intval); + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static enum power_supply_property wm831x_bat_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_CHARGE_TYPE, +}; + +static const char *wm831x_bat_irqs[] = { + "BATT HOT", + "BATT COLD", + "BATT FAIL", + "OV", + "END", + "TO", + "MODE", + "START", +}; + +static irqreturn_t wm831x_bat_irq(int irq, void *data) +{ + struct wm831x_power *wm831x_power = data; + struct wm831x *wm831x = wm831x_power->wm831x; + + dev_dbg(wm831x->dev, "Battery status changed: %d\n", irq); + + /* The battery charger is autonomous so we don't need to do + * anything except kick user space */ + if (wm831x_power->have_battery) + power_supply_changed(wm831x_power->battery); + + return IRQ_HANDLED; +} + + +/********************************************************************* + * Initialisation + *********************************************************************/ + +static irqreturn_t wm831x_syslo_irq(int irq, void *data) +{ + struct wm831x_power *wm831x_power = data; + struct wm831x *wm831x = wm831x_power->wm831x; + + /* Not much we can actually *do* but tell people for + * posterity, we're probably about to run out of power. */ + dev_crit(wm831x->dev, "SYSVDD under voltage\n"); + + return IRQ_HANDLED; +} + +static irqreturn_t wm831x_pwr_src_irq(int irq, void *data) +{ + struct wm831x_power *wm831x_power = data; + struct wm831x *wm831x = wm831x_power->wm831x; + + dev_dbg(wm831x->dev, "Power source changed\n"); + + /* Just notify for everything - little harm in overnotifying. */ + if (wm831x_power->have_battery) + power_supply_changed(wm831x_power->battery); + power_supply_changed(wm831x_power->usb); + power_supply_changed(wm831x_power->wall); + + return IRQ_HANDLED; +} + +static int wm831x_power_probe(struct platform_device *pdev) +{ + struct wm831x *wm831x = dev_get_drvdata(pdev->dev.parent); + struct wm831x_pdata *wm831x_pdata = wm831x->dev->platform_data; + struct wm831x_power *power; + int ret, irq, i; + + power = devm_kzalloc(&pdev->dev, sizeof(struct wm831x_power), + GFP_KERNEL); + if (power == NULL) + return -ENOMEM; + + power->wm831x = wm831x; + platform_set_drvdata(pdev, power); + + if (wm831x_pdata && wm831x_pdata->wm831x_num) { + snprintf(power->wall_name, sizeof(power->wall_name), + "wm831x-wall.%d", wm831x_pdata->wm831x_num); + snprintf(power->battery_name, sizeof(power->wall_name), + "wm831x-battery.%d", wm831x_pdata->wm831x_num); + snprintf(power->usb_name, sizeof(power->wall_name), + "wm831x-usb.%d", wm831x_pdata->wm831x_num); + } else { + snprintf(power->wall_name, sizeof(power->wall_name), + "wm831x-wall"); + snprintf(power->battery_name, sizeof(power->wall_name), + "wm831x-battery"); + snprintf(power->usb_name, sizeof(power->wall_name), + "wm831x-usb"); + } + + /* We ignore configuration failures since we can still read back + * the status without enabling the charger. + */ + wm831x_config_battery(wm831x); + + power->wall_desc.name = power->wall_name; + power->wall_desc.type = POWER_SUPPLY_TYPE_MAINS; + power->wall_desc.properties = wm831x_wall_props; + power->wall_desc.num_properties = ARRAY_SIZE(wm831x_wall_props); + power->wall_desc.get_property = wm831x_wall_get_prop; + power->wall = power_supply_register(&pdev->dev, &power->wall_desc, + NULL); + if (IS_ERR(power->wall)) { + ret = PTR_ERR(power->wall); + goto err; + } + + power->usb_desc.name = power->usb_name, + power->usb_desc.type = POWER_SUPPLY_TYPE_USB; + power->usb_desc.properties = wm831x_usb_props; + power->usb_desc.num_properties = ARRAY_SIZE(wm831x_usb_props); + power->usb_desc.get_property = wm831x_usb_get_prop; + power->usb = power_supply_register(&pdev->dev, &power->usb_desc, NULL); + if (IS_ERR(power->usb)) { + ret = PTR_ERR(power->usb); + goto err_wall; + } + + ret = wm831x_reg_read(wm831x, WM831X_CHARGER_CONTROL_1); + if (ret < 0) + goto err_wall; + power->have_battery = ret & WM831X_CHG_ENA; + + if (power->have_battery) { + power->battery_desc.name = power->battery_name; + power->battery_desc.properties = wm831x_bat_props; + power->battery_desc.num_properties = ARRAY_SIZE(wm831x_bat_props); + power->battery_desc.get_property = wm831x_bat_get_prop; + power->battery_desc.use_for_apm = 1; + power->battery = power_supply_register(&pdev->dev, + &power->battery_desc, + NULL); + if (IS_ERR(power->battery)) { + ret = PTR_ERR(power->battery); + goto err_usb; + } + } + + irq = wm831x_irq(wm831x, platform_get_irq_byname(pdev, "SYSLO")); + ret = request_threaded_irq(irq, NULL, wm831x_syslo_irq, + IRQF_TRIGGER_RISING | IRQF_ONESHOT, "System power low", + power); + if (ret != 0) { + dev_err(&pdev->dev, "Failed to request SYSLO IRQ %d: %d\n", + irq, ret); + goto err_battery; + } + + irq = wm831x_irq(wm831x, platform_get_irq_byname(pdev, "PWR SRC")); + ret = request_threaded_irq(irq, NULL, wm831x_pwr_src_irq, + IRQF_TRIGGER_RISING | IRQF_ONESHOT, "Power source", + power); + if (ret != 0) { + dev_err(&pdev->dev, "Failed to request PWR SRC IRQ %d: %d\n", + irq, ret); + goto err_syslo; + } + + for (i = 0; i < ARRAY_SIZE(wm831x_bat_irqs); i++) { + irq = wm831x_irq(wm831x, + platform_get_irq_byname(pdev, + wm831x_bat_irqs[i])); + ret = request_threaded_irq(irq, NULL, wm831x_bat_irq, + IRQF_TRIGGER_RISING | IRQF_ONESHOT, + wm831x_bat_irqs[i], + power); + if (ret != 0) { + dev_err(&pdev->dev, + "Failed to request %s IRQ %d: %d\n", + wm831x_bat_irqs[i], irq, ret); + goto err_bat_irq; + } + } + + return ret; + +err_bat_irq: + --i; + for (; i >= 0; i--) { + irq = platform_get_irq_byname(pdev, wm831x_bat_irqs[i]); + free_irq(irq, power); + } + irq = wm831x_irq(wm831x, platform_get_irq_byname(pdev, "PWR SRC")); + free_irq(irq, power); +err_syslo: + irq = wm831x_irq(wm831x, platform_get_irq_byname(pdev, "SYSLO")); + free_irq(irq, power); +err_battery: + if (power->have_battery) + power_supply_unregister(power->battery); +err_usb: + power_supply_unregister(power->usb); +err_wall: + power_supply_unregister(power->wall); +err: + return ret; +} + +static int wm831x_power_remove(struct platform_device *pdev) +{ + struct wm831x_power *wm831x_power = platform_get_drvdata(pdev); + struct wm831x *wm831x = wm831x_power->wm831x; + int irq, i; + + for (i = 0; i < ARRAY_SIZE(wm831x_bat_irqs); i++) { + irq = wm831x_irq(wm831x, + platform_get_irq_byname(pdev, + wm831x_bat_irqs[i])); + free_irq(irq, wm831x_power); + } + + irq = wm831x_irq(wm831x, platform_get_irq_byname(pdev, "PWR SRC")); + free_irq(irq, wm831x_power); + + irq = wm831x_irq(wm831x, platform_get_irq_byname(pdev, "SYSLO")); + free_irq(irq, wm831x_power); + + if (wm831x_power->have_battery) + power_supply_unregister(wm831x_power->battery); + power_supply_unregister(wm831x_power->wall); + power_supply_unregister(wm831x_power->usb); + return 0; +} + +static struct platform_driver wm831x_power_driver = { + .probe = wm831x_power_probe, + .remove = wm831x_power_remove, + .driver = { + .name = "wm831x-power", + }, +}; + +module_platform_driver(wm831x_power_driver); + +MODULE_DESCRIPTION("Power supply driver for WM831x PMICs"); +MODULE_AUTHOR("Mark Brown "); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:wm831x-power"); diff --git a/drivers/power/supply/wm8350_power.c b/drivers/power/supply/wm8350_power.c new file mode 100644 index 000000000000..5c5880664e09 --- /dev/null +++ b/drivers/power/supply/wm8350_power.c @@ -0,0 +1,540 @@ +/* + * Battery driver for wm8350 PMIC + * + * Copyright 2007, 2008 Wolfson Microelectronics PLC. + * + * Based on OLPC Battery Driver + * + * Copyright 2006 David Woodhouse + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include + +static int wm8350_read_battery_uvolts(struct wm8350 *wm8350) +{ + return wm8350_read_auxadc(wm8350, WM8350_AUXADC_BATT, 0, 0) + * WM8350_AUX_COEFF; +} + +static int wm8350_read_line_uvolts(struct wm8350 *wm8350) +{ + return wm8350_read_auxadc(wm8350, WM8350_AUXADC_LINE, 0, 0) + * WM8350_AUX_COEFF; +} + +static int wm8350_read_usb_uvolts(struct wm8350 *wm8350) +{ + return wm8350_read_auxadc(wm8350, WM8350_AUXADC_USB, 0, 0) + * WM8350_AUX_COEFF; +} + +#define WM8350_BATT_SUPPLY 1 +#define WM8350_USB_SUPPLY 2 +#define WM8350_LINE_SUPPLY 4 + +static inline int wm8350_charge_time_min(struct wm8350 *wm8350, int min) +{ + if (!wm8350->power.rev_g_coeff) + return (((min - 30) / 15) & 0xf) << 8; + else + return (((min - 30) / 30) & 0xf) << 8; +} + +static int wm8350_get_supplies(struct wm8350 *wm8350) +{ + u16 sm, ov, co, chrg; + int supplies = 0; + + sm = wm8350_reg_read(wm8350, WM8350_STATE_MACHINE_STATUS); + ov = wm8350_reg_read(wm8350, WM8350_MISC_OVERRIDES); + co = wm8350_reg_read(wm8350, WM8350_COMPARATOR_OVERRIDES); + chrg = wm8350_reg_read(wm8350, WM8350_BATTERY_CHARGER_CONTROL_2); + + /* USB_SM */ + sm = (sm & WM8350_USB_SM_MASK) >> WM8350_USB_SM_SHIFT; + + /* CHG_ISEL */ + chrg &= WM8350_CHG_ISEL_MASK; + + /* If the USB state machine is active then we're using that with or + * without battery, otherwise check for wall supply */ + if (((sm == WM8350_USB_SM_100_SLV) || + (sm == WM8350_USB_SM_500_SLV) || + (sm == WM8350_USB_SM_STDBY_SLV)) + && !(ov & WM8350_USB_LIMIT_OVRDE)) + supplies = WM8350_USB_SUPPLY; + else if (((sm == WM8350_USB_SM_100_SLV) || + (sm == WM8350_USB_SM_500_SLV) || + (sm == WM8350_USB_SM_STDBY_SLV)) + && (ov & WM8350_USB_LIMIT_OVRDE) && (chrg == 0)) + supplies = WM8350_USB_SUPPLY | WM8350_BATT_SUPPLY; + else if (co & WM8350_WALL_FB_OVRDE) + supplies = WM8350_LINE_SUPPLY; + else + supplies = WM8350_BATT_SUPPLY; + + return supplies; +} + +static int wm8350_charger_config(struct wm8350 *wm8350, + struct wm8350_charger_policy *policy) +{ + u16 reg, eoc_mA, fast_limit_mA; + + if (!policy) { + dev_warn(wm8350->dev, + "No charger policy, charger not configured.\n"); + return -EINVAL; + } + + /* make sure USB fast charge current is not > 500mA */ + if (policy->fast_limit_USB_mA > 500) { + dev_err(wm8350->dev, "USB fast charge > 500mA\n"); + return -EINVAL; + } + + eoc_mA = WM8350_CHG_EOC_mA(policy->eoc_mA); + + wm8350_reg_unlock(wm8350); + + reg = wm8350_reg_read(wm8350, WM8350_BATTERY_CHARGER_CONTROL_1) + & WM8350_CHG_ENA_R168; + wm8350_reg_write(wm8350, WM8350_BATTERY_CHARGER_CONTROL_1, + reg | eoc_mA | policy->trickle_start_mV | + WM8350_CHG_TRICKLE_TEMP_CHOKE | + WM8350_CHG_TRICKLE_USB_CHOKE | + WM8350_CHG_FAST_USB_THROTTLE); + + if (wm8350_get_supplies(wm8350) & WM8350_USB_SUPPLY) { + fast_limit_mA = + WM8350_CHG_FAST_LIMIT_mA(policy->fast_limit_USB_mA); + wm8350_reg_write(wm8350, WM8350_BATTERY_CHARGER_CONTROL_2, + policy->charge_mV | policy->trickle_charge_USB_mA | + fast_limit_mA | wm8350_charge_time_min(wm8350, + policy->charge_timeout)); + + } else { + fast_limit_mA = + WM8350_CHG_FAST_LIMIT_mA(policy->fast_limit_mA); + wm8350_reg_write(wm8350, WM8350_BATTERY_CHARGER_CONTROL_2, + policy->charge_mV | policy->trickle_charge_mA | + fast_limit_mA | wm8350_charge_time_min(wm8350, + policy->charge_timeout)); + } + + wm8350_reg_lock(wm8350); + return 0; +} + +static int wm8350_batt_status(struct wm8350 *wm8350) +{ + u16 state; + + state = wm8350_reg_read(wm8350, WM8350_BATTERY_CHARGER_CONTROL_2); + state &= WM8350_CHG_STS_MASK; + + switch (state) { + case WM8350_CHG_STS_OFF: + return POWER_SUPPLY_STATUS_DISCHARGING; + + case WM8350_CHG_STS_TRICKLE: + case WM8350_CHG_STS_FAST: + return POWER_SUPPLY_STATUS_CHARGING; + + default: + return POWER_SUPPLY_STATUS_UNKNOWN; + } +} + +static ssize_t charger_state_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct wm8350 *wm8350 = dev_get_drvdata(dev); + char *charge; + int state; + + state = wm8350_reg_read(wm8350, WM8350_BATTERY_CHARGER_CONTROL_2) & + WM8350_CHG_STS_MASK; + switch (state) { + case WM8350_CHG_STS_OFF: + charge = "Charger Off"; + break; + case WM8350_CHG_STS_TRICKLE: + charge = "Trickle Charging"; + break; + case WM8350_CHG_STS_FAST: + charge = "Fast Charging"; + break; + default: + return 0; + } + + return sprintf(buf, "%s\n", charge); +} + +static DEVICE_ATTR(charger_state, 0444, charger_state_show, NULL); + +static irqreturn_t wm8350_charger_handler(int irq, void *data) +{ + struct wm8350 *wm8350 = data; + struct wm8350_power *power = &wm8350->power; + struct wm8350_charger_policy *policy = power->policy; + + switch (irq - wm8350->irq_base) { + case WM8350_IRQ_CHG_BAT_FAIL: + dev_err(wm8350->dev, "battery failed\n"); + break; + case WM8350_IRQ_CHG_TO: + dev_err(wm8350->dev, "charger timeout\n"); + power_supply_changed(power->battery); + break; + + case WM8350_IRQ_CHG_BAT_HOT: + case WM8350_IRQ_CHG_BAT_COLD: + case WM8350_IRQ_CHG_START: + case WM8350_IRQ_CHG_END: + power_supply_changed(power->battery); + break; + + case WM8350_IRQ_CHG_FAST_RDY: + dev_dbg(wm8350->dev, "fast charger ready\n"); + wm8350_charger_config(wm8350, policy); + wm8350_reg_unlock(wm8350); + wm8350_set_bits(wm8350, WM8350_BATTERY_CHARGER_CONTROL_1, + WM8350_CHG_FAST); + wm8350_reg_lock(wm8350); + break; + + case WM8350_IRQ_CHG_VBATT_LT_3P9: + dev_warn(wm8350->dev, "battery < 3.9V\n"); + break; + case WM8350_IRQ_CHG_VBATT_LT_3P1: + dev_warn(wm8350->dev, "battery < 3.1V\n"); + break; + case WM8350_IRQ_CHG_VBATT_LT_2P85: + dev_warn(wm8350->dev, "battery < 2.85V\n"); + break; + + /* Supply change. We will overnotify but it should do + * no harm. */ + case WM8350_IRQ_EXT_USB_FB: + case WM8350_IRQ_EXT_WALL_FB: + wm8350_charger_config(wm8350, policy); + case WM8350_IRQ_EXT_BAT_FB: /* Fall through */ + power_supply_changed(power->battery); + power_supply_changed(power->usb); + power_supply_changed(power->ac); + break; + + default: + dev_err(wm8350->dev, "Unknown interrupt %d\n", irq); + } + + return IRQ_HANDLED; +} + +/********************************************************************* + * AC Power + *********************************************************************/ +static int wm8350_ac_get_prop(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct wm8350 *wm8350 = dev_get_drvdata(psy->dev.parent); + int ret = 0; + + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + val->intval = !!(wm8350_get_supplies(wm8350) & + WM8350_LINE_SUPPLY); + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + val->intval = wm8350_read_line_uvolts(wm8350); + break; + default: + ret = -EINVAL; + break; + } + return ret; +} + +static enum power_supply_property wm8350_ac_props[] = { + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_VOLTAGE_NOW, +}; + +/********************************************************************* + * USB Power + *********************************************************************/ +static int wm8350_usb_get_prop(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct wm8350 *wm8350 = dev_get_drvdata(psy->dev.parent); + int ret = 0; + + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + val->intval = !!(wm8350_get_supplies(wm8350) & + WM8350_USB_SUPPLY); + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + val->intval = wm8350_read_usb_uvolts(wm8350); + break; + default: + ret = -EINVAL; + break; + } + return ret; +} + +static enum power_supply_property wm8350_usb_props[] = { + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_VOLTAGE_NOW, +}; + +/********************************************************************* + * Battery properties + *********************************************************************/ + +static int wm8350_bat_check_health(struct wm8350 *wm8350) +{ + u16 reg; + + if (wm8350_read_battery_uvolts(wm8350) < 2850000) + return POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; + + reg = wm8350_reg_read(wm8350, WM8350_CHARGER_OVERRIDES); + if (reg & WM8350_CHG_BATT_HOT_OVRDE) + return POWER_SUPPLY_HEALTH_OVERHEAT; + + if (reg & WM8350_CHG_BATT_COLD_OVRDE) + return POWER_SUPPLY_HEALTH_COLD; + + return POWER_SUPPLY_HEALTH_GOOD; +} + +static int wm8350_bat_get_charge_type(struct wm8350 *wm8350) +{ + int state; + + state = wm8350_reg_read(wm8350, WM8350_BATTERY_CHARGER_CONTROL_2) & + WM8350_CHG_STS_MASK; + switch (state) { + case WM8350_CHG_STS_OFF: + return POWER_SUPPLY_CHARGE_TYPE_NONE; + case WM8350_CHG_STS_TRICKLE: + return POWER_SUPPLY_CHARGE_TYPE_TRICKLE; + case WM8350_CHG_STS_FAST: + return POWER_SUPPLY_CHARGE_TYPE_FAST; + default: + return POWER_SUPPLY_CHARGE_TYPE_UNKNOWN; + } +} + +static int wm8350_bat_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct wm8350 *wm8350 = dev_get_drvdata(psy->dev.parent); + int ret = 0; + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + val->intval = wm8350_batt_status(wm8350); + break; + case POWER_SUPPLY_PROP_ONLINE: + val->intval = !!(wm8350_get_supplies(wm8350) & + WM8350_BATT_SUPPLY); + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + val->intval = wm8350_read_battery_uvolts(wm8350); + break; + case POWER_SUPPLY_PROP_HEALTH: + val->intval = wm8350_bat_check_health(wm8350); + break; + case POWER_SUPPLY_PROP_CHARGE_TYPE: + val->intval = wm8350_bat_get_charge_type(wm8350); + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static enum power_supply_property wm8350_bat_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_CHARGE_TYPE, +}; + +static const struct power_supply_desc wm8350_ac_desc = { + .name = "wm8350-ac", + .type = POWER_SUPPLY_TYPE_MAINS, + .properties = wm8350_ac_props, + .num_properties = ARRAY_SIZE(wm8350_ac_props), + .get_property = wm8350_ac_get_prop, +}; + +static const struct power_supply_desc wm8350_battery_desc = { + .name = "wm8350-battery", + .properties = wm8350_bat_props, + .num_properties = ARRAY_SIZE(wm8350_bat_props), + .get_property = wm8350_bat_get_property, + .use_for_apm = 1, +}; + +static const struct power_supply_desc wm8350_usb_desc = { + .name = "wm8350-usb", + .type = POWER_SUPPLY_TYPE_USB, + .properties = wm8350_usb_props, + .num_properties = ARRAY_SIZE(wm8350_usb_props), + .get_property = wm8350_usb_get_prop, +}; + +/********************************************************************* + * Initialisation + *********************************************************************/ + +static void wm8350_init_charger(struct wm8350 *wm8350) +{ + /* register our interest in charger events */ + wm8350_register_irq(wm8350, WM8350_IRQ_CHG_BAT_HOT, + wm8350_charger_handler, 0, "Battery hot", wm8350); + wm8350_register_irq(wm8350, WM8350_IRQ_CHG_BAT_COLD, + wm8350_charger_handler, 0, "Battery cold", wm8350); + wm8350_register_irq(wm8350, WM8350_IRQ_CHG_BAT_FAIL, + wm8350_charger_handler, 0, "Battery fail", wm8350); + wm8350_register_irq(wm8350, WM8350_IRQ_CHG_TO, + wm8350_charger_handler, 0, + "Charger timeout", wm8350); + wm8350_register_irq(wm8350, WM8350_IRQ_CHG_END, + wm8350_charger_handler, 0, + "Charge end", wm8350); + wm8350_register_irq(wm8350, WM8350_IRQ_CHG_START, + wm8350_charger_handler, 0, + "Charge start", wm8350); + wm8350_register_irq(wm8350, WM8350_IRQ_CHG_FAST_RDY, + wm8350_charger_handler, 0, + "Fast charge ready", wm8350); + wm8350_register_irq(wm8350, WM8350_IRQ_CHG_VBATT_LT_3P9, + wm8350_charger_handler, 0, + "Battery <3.9V", wm8350); + wm8350_register_irq(wm8350, WM8350_IRQ_CHG_VBATT_LT_3P1, + wm8350_charger_handler, 0, + "Battery <3.1V", wm8350); + wm8350_register_irq(wm8350, WM8350_IRQ_CHG_VBATT_LT_2P85, + wm8350_charger_handler, 0, + "Battery <2.85V", wm8350); + + /* and supply change events */ + wm8350_register_irq(wm8350, WM8350_IRQ_EXT_USB_FB, + wm8350_charger_handler, 0, "USB", wm8350); + wm8350_register_irq(wm8350, WM8350_IRQ_EXT_WALL_FB, + wm8350_charger_handler, 0, "Wall", wm8350); + wm8350_register_irq(wm8350, WM8350_IRQ_EXT_BAT_FB, + wm8350_charger_handler, 0, "Battery", wm8350); +} + +static void free_charger_irq(struct wm8350 *wm8350) +{ + wm8350_free_irq(wm8350, WM8350_IRQ_CHG_BAT_HOT, wm8350); + wm8350_free_irq(wm8350, WM8350_IRQ_CHG_BAT_COLD, wm8350); + wm8350_free_irq(wm8350, WM8350_IRQ_CHG_BAT_FAIL, wm8350); + wm8350_free_irq(wm8350, WM8350_IRQ_CHG_TO, wm8350); + wm8350_free_irq(wm8350, WM8350_IRQ_CHG_END, wm8350); + wm8350_free_irq(wm8350, WM8350_IRQ_CHG_START, wm8350); + wm8350_free_irq(wm8350, WM8350_IRQ_CHG_VBATT_LT_3P9, wm8350); + wm8350_free_irq(wm8350, WM8350_IRQ_CHG_VBATT_LT_3P1, wm8350); + wm8350_free_irq(wm8350, WM8350_IRQ_CHG_VBATT_LT_2P85, wm8350); + wm8350_free_irq(wm8350, WM8350_IRQ_EXT_USB_FB, wm8350); + wm8350_free_irq(wm8350, WM8350_IRQ_EXT_WALL_FB, wm8350); + wm8350_free_irq(wm8350, WM8350_IRQ_EXT_BAT_FB, wm8350); +} + +static int wm8350_power_probe(struct platform_device *pdev) +{ + struct wm8350 *wm8350 = platform_get_drvdata(pdev); + struct wm8350_power *power = &wm8350->power; + struct wm8350_charger_policy *policy = power->policy; + int ret; + + power->ac = power_supply_register(&pdev->dev, &wm8350_ac_desc, NULL); + if (IS_ERR(power->ac)) + return PTR_ERR(power->ac); + + power->battery = power_supply_register(&pdev->dev, &wm8350_battery_desc, + NULL); + if (IS_ERR(power->battery)) { + ret = PTR_ERR(power->battery); + goto battery_failed; + } + + power->usb = power_supply_register(&pdev->dev, &wm8350_usb_desc, NULL); + if (IS_ERR(power->usb)) { + ret = PTR_ERR(power->usb); + goto usb_failed; + } + + ret = device_create_file(&pdev->dev, &dev_attr_charger_state); + if (ret < 0) + dev_warn(wm8350->dev, "failed to add charge sysfs: %d\n", ret); + ret = 0; + + wm8350_init_charger(wm8350); + if (wm8350_charger_config(wm8350, policy) == 0) { + wm8350_reg_unlock(wm8350); + wm8350_set_bits(wm8350, WM8350_POWER_MGMT_5, WM8350_CHG_ENA); + wm8350_reg_lock(wm8350); + } + + return ret; + +usb_failed: + power_supply_unregister(power->battery); +battery_failed: + power_supply_unregister(power->ac); + + return ret; +} + +static int wm8350_power_remove(struct platform_device *pdev) +{ + struct wm8350 *wm8350 = platform_get_drvdata(pdev); + struct wm8350_power *power = &wm8350->power; + + free_charger_irq(wm8350); + device_remove_file(&pdev->dev, &dev_attr_charger_state); + power_supply_unregister(power->battery); + power_supply_unregister(power->ac); + power_supply_unregister(power->usb); + return 0; +} + +static struct platform_driver wm8350_power_driver = { + .probe = wm8350_power_probe, + .remove = wm8350_power_remove, + .driver = { + .name = "wm8350-power", + }, +}; + +module_platform_driver(wm8350_power_driver); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Power supply driver for WM8350"); +MODULE_ALIAS("platform:wm8350-power"); diff --git a/drivers/power/supply/wm97xx_battery.c b/drivers/power/supply/wm97xx_battery.c new file mode 100644 index 000000000000..6285626d142a --- /dev/null +++ b/drivers/power/supply/wm97xx_battery.c @@ -0,0 +1,297 @@ +/* + * Battery measurement code for WM97xx + * + * based on tosa_battery.c + * + * Copyright (C) 2008 Marek Vasut + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static struct work_struct bat_work; +static DEFINE_MUTEX(work_lock); +static int bat_status = POWER_SUPPLY_STATUS_UNKNOWN; +static enum power_supply_property *prop; + +static unsigned long wm97xx_read_bat(struct power_supply *bat_ps) +{ + struct wm97xx_pdata *wmdata = bat_ps->dev.parent->platform_data; + struct wm97xx_batt_pdata *pdata = wmdata->batt_pdata; + + return wm97xx_read_aux_adc(dev_get_drvdata(bat_ps->dev.parent), + pdata->batt_aux) * pdata->batt_mult / + pdata->batt_div; +} + +static unsigned long wm97xx_read_temp(struct power_supply *bat_ps) +{ + struct wm97xx_pdata *wmdata = bat_ps->dev.parent->platform_data; + struct wm97xx_batt_pdata *pdata = wmdata->batt_pdata; + + return wm97xx_read_aux_adc(dev_get_drvdata(bat_ps->dev.parent), + pdata->temp_aux) * pdata->temp_mult / + pdata->temp_div; +} + +static int wm97xx_bat_get_property(struct power_supply *bat_ps, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct wm97xx_pdata *wmdata = bat_ps->dev.parent->platform_data; + struct wm97xx_batt_pdata *pdata = wmdata->batt_pdata; + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + val->intval = bat_status; + break; + case POWER_SUPPLY_PROP_TECHNOLOGY: + val->intval = pdata->batt_tech; + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + if (pdata->batt_aux >= 0) + val->intval = wm97xx_read_bat(bat_ps); + else + return -EINVAL; + break; + case POWER_SUPPLY_PROP_TEMP: + if (pdata->temp_aux >= 0) + val->intval = wm97xx_read_temp(bat_ps); + else + return -EINVAL; + break; + case POWER_SUPPLY_PROP_VOLTAGE_MAX: + if (pdata->max_voltage >= 0) + val->intval = pdata->max_voltage; + else + return -EINVAL; + break; + case POWER_SUPPLY_PROP_VOLTAGE_MIN: + if (pdata->min_voltage >= 0) + val->intval = pdata->min_voltage; + else + return -EINVAL; + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = 1; + break; + default: + return -EINVAL; + } + return 0; +} + +static void wm97xx_bat_external_power_changed(struct power_supply *bat_ps) +{ + schedule_work(&bat_work); +} + +static void wm97xx_bat_update(struct power_supply *bat_ps) +{ + int old_status = bat_status; + struct wm97xx_pdata *wmdata = bat_ps->dev.parent->platform_data; + struct wm97xx_batt_pdata *pdata = wmdata->batt_pdata; + + mutex_lock(&work_lock); + + bat_status = (pdata->charge_gpio >= 0) ? + (gpio_get_value(pdata->charge_gpio) ? + POWER_SUPPLY_STATUS_DISCHARGING : + POWER_SUPPLY_STATUS_CHARGING) : + POWER_SUPPLY_STATUS_UNKNOWN; + + if (old_status != bat_status) { + pr_debug("%s: %i -> %i\n", bat_ps->desc->name, old_status, + bat_status); + power_supply_changed(bat_ps); + } + + mutex_unlock(&work_lock); +} + +static struct power_supply *bat_psy; +static struct power_supply_desc bat_psy_desc = { + .type = POWER_SUPPLY_TYPE_BATTERY, + .get_property = wm97xx_bat_get_property, + .external_power_changed = wm97xx_bat_external_power_changed, + .use_for_apm = 1, +}; + +static void wm97xx_bat_work(struct work_struct *work) +{ + wm97xx_bat_update(bat_psy); +} + +static irqreturn_t wm97xx_chrg_irq(int irq, void *data) +{ + schedule_work(&bat_work); + return IRQ_HANDLED; +} + +#ifdef CONFIG_PM +static int wm97xx_bat_suspend(struct device *dev) +{ + flush_work(&bat_work); + return 0; +} + +static int wm97xx_bat_resume(struct device *dev) +{ + schedule_work(&bat_work); + return 0; +} + +static const struct dev_pm_ops wm97xx_bat_pm_ops = { + .suspend = wm97xx_bat_suspend, + .resume = wm97xx_bat_resume, +}; +#endif + +static int wm97xx_bat_probe(struct platform_device *dev) +{ + int ret = 0; + int props = 1; /* POWER_SUPPLY_PROP_PRESENT */ + int i = 0; + struct wm97xx_pdata *wmdata = dev->dev.platform_data; + struct wm97xx_batt_pdata *pdata; + + if (!wmdata) { + dev_err(&dev->dev, "No platform data supplied\n"); + return -EINVAL; + } + + pdata = wmdata->batt_pdata; + + 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) + goto err; + ret = gpio_direction_input(pdata->charge_gpio); + if (ret) + goto err2; + ret = request_irq(gpio_to_irq(pdata->charge_gpio), + wm97xx_chrg_irq, 0, + "AC Detect", dev); + if (ret) + goto err2; + props++; /* POWER_SUPPLY_PROP_STATUS */ + } + + if (pdata->batt_tech >= 0) + props++; /* POWER_SUPPLY_PROP_TECHNOLOGY */ + if (pdata->temp_aux >= 0) + props++; /* POWER_SUPPLY_PROP_TEMP */ + if (pdata->batt_aux >= 0) + props++; /* POWER_SUPPLY_PROP_VOLTAGE_NOW */ + if (pdata->max_voltage >= 0) + props++; /* POWER_SUPPLY_PROP_VOLTAGE_MAX */ + if (pdata->min_voltage >= 0) + props++; /* POWER_SUPPLY_PROP_VOLTAGE_MIN */ + + prop = kzalloc(props * sizeof(*prop), GFP_KERNEL); + if (!prop) { + ret = -ENOMEM; + goto err3; + } + + prop[i++] = POWER_SUPPLY_PROP_PRESENT; + if (pdata->charge_gpio >= 0) + prop[i++] = POWER_SUPPLY_PROP_STATUS; + if (pdata->batt_tech >= 0) + prop[i++] = POWER_SUPPLY_PROP_TECHNOLOGY; + if (pdata->temp_aux >= 0) + prop[i++] = POWER_SUPPLY_PROP_TEMP; + if (pdata->batt_aux >= 0) + prop[i++] = POWER_SUPPLY_PROP_VOLTAGE_NOW; + if (pdata->max_voltage >= 0) + prop[i++] = POWER_SUPPLY_PROP_VOLTAGE_MAX; + if (pdata->min_voltage >= 0) + prop[i++] = POWER_SUPPLY_PROP_VOLTAGE_MIN; + + INIT_WORK(&bat_work, wm97xx_bat_work); + + if (!pdata->batt_name) { + dev_info(&dev->dev, "Please consider setting proper battery " + "name in platform definition file, falling " + "back to name \"wm97xx-batt\"\n"); + bat_psy_desc.name = "wm97xx-batt"; + } else + bat_psy_desc.name = pdata->batt_name; + + bat_psy_desc.properties = prop; + bat_psy_desc.num_properties = props; + + bat_psy = power_supply_register(&dev->dev, &bat_psy_desc, NULL); + if (!IS_ERR(bat_psy)) { + schedule_work(&bat_work); + } else { + ret = PTR_ERR(bat_psy); + goto err4; + } + + return 0; +err4: + kfree(prop); +err3: + if (gpio_is_valid(pdata->charge_gpio)) + free_irq(gpio_to_irq(pdata->charge_gpio), dev); +err2: + if (gpio_is_valid(pdata->charge_gpio)) + gpio_free(pdata->charge_gpio); +err: + return ret; +} + +static int wm97xx_bat_remove(struct platform_device *dev) +{ + struct wm97xx_pdata *wmdata = dev->dev.platform_data; + struct wm97xx_batt_pdata *pdata = wmdata->batt_pdata; + + if (pdata && gpio_is_valid(pdata->charge_gpio)) { + free_irq(gpio_to_irq(pdata->charge_gpio), dev); + gpio_free(pdata->charge_gpio); + } + cancel_work_sync(&bat_work); + power_supply_unregister(bat_psy); + kfree(prop); + return 0; +} + +static struct platform_driver wm97xx_bat_driver = { + .driver = { + .name = "wm97xx-battery", +#ifdef CONFIG_PM + .pm = &wm97xx_bat_pm_ops, +#endif + }, + .probe = wm97xx_bat_probe, + .remove = wm97xx_bat_remove, +}; + +module_platform_driver(wm97xx_bat_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Marek Vasut "); +MODULE_DESCRIPTION("WM97xx battery driver"); diff --git a/drivers/power/supply/z2_battery.c b/drivers/power/supply/z2_battery.c new file mode 100644 index 000000000000..b201e3facf73 --- /dev/null +++ b/drivers/power/supply/z2_battery.c @@ -0,0 +1,331 @@ +/* + * Battery measurement code for Zipit Z2 + * + * Copyright (C) 2009 Peter Edwards + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#define Z2_DEFAULT_NAME "Z2" + +struct z2_charger { + struct z2_battery_info *info; + int bat_status; + struct i2c_client *client; + struct power_supply *batt_ps; + struct power_supply_desc batt_ps_desc; + struct mutex work_lock; + struct work_struct bat_work; +}; + +static unsigned long z2_read_bat(struct z2_charger *charger) +{ + int data; + data = i2c_smbus_read_byte_data(charger->client, + charger->info->batt_I2C_reg); + if (data < 0) + return 0; + + return data * charger->info->batt_mult / charger->info->batt_div; +} + +static int z2_batt_get_property(struct power_supply *batt_ps, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct z2_charger *charger = power_supply_get_drvdata(batt_ps); + struct z2_battery_info *info = charger->info; + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + val->intval = charger->bat_status; + break; + case POWER_SUPPLY_PROP_TECHNOLOGY: + val->intval = info->batt_tech; + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + if (info->batt_I2C_reg >= 0) + val->intval = z2_read_bat(charger); + else + return -EINVAL; + break; + case POWER_SUPPLY_PROP_VOLTAGE_MAX: + if (info->max_voltage >= 0) + val->intval = info->max_voltage; + else + return -EINVAL; + break; + case POWER_SUPPLY_PROP_VOLTAGE_MIN: + if (info->min_voltage >= 0) + val->intval = info->min_voltage; + else + return -EINVAL; + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = 1; + break; + default: + return -EINVAL; + } + + return 0; +} + +static void z2_batt_ext_power_changed(struct power_supply *batt_ps) +{ + struct z2_charger *charger = power_supply_get_drvdata(batt_ps); + + schedule_work(&charger->bat_work); +} + +static void z2_batt_update(struct z2_charger *charger) +{ + int old_status = charger->bat_status; + struct z2_battery_info *info; + + info = charger->info; + + mutex_lock(&charger->work_lock); + + charger->bat_status = (info->charge_gpio >= 0) ? + (gpio_get_value(info->charge_gpio) ? + POWER_SUPPLY_STATUS_CHARGING : + POWER_SUPPLY_STATUS_DISCHARGING) : + POWER_SUPPLY_STATUS_UNKNOWN; + + if (old_status != charger->bat_status) { + pr_debug("%s: %i -> %i\n", charger->batt_ps->desc->name, + old_status, + charger->bat_status); + power_supply_changed(charger->batt_ps); + } + + mutex_unlock(&charger->work_lock); +} + +static void z2_batt_work(struct work_struct *work) +{ + struct z2_charger *charger; + charger = container_of(work, struct z2_charger, bat_work); + z2_batt_update(charger); +} + +static irqreturn_t z2_charge_switch_irq(int irq, void *devid) +{ + struct z2_charger *charger = devid; + schedule_work(&charger->bat_work); + return IRQ_HANDLED; +} + +static int z2_batt_ps_init(struct z2_charger *charger, int props) +{ + int i = 0; + enum power_supply_property *prop; + struct z2_battery_info *info = charger->info; + + if (info->charge_gpio >= 0) + props++; /* POWER_SUPPLY_PROP_STATUS */ + if (info->batt_tech >= 0) + props++; /* POWER_SUPPLY_PROP_TECHNOLOGY */ + if (info->batt_I2C_reg >= 0) + props++; /* POWER_SUPPLY_PROP_VOLTAGE_NOW */ + if (info->max_voltage >= 0) + props++; /* POWER_SUPPLY_PROP_VOLTAGE_MAX */ + if (info->min_voltage >= 0) + props++; /* POWER_SUPPLY_PROP_VOLTAGE_MIN */ + + prop = kzalloc(props * sizeof(*prop), GFP_KERNEL); + if (!prop) + return -ENOMEM; + + prop[i++] = POWER_SUPPLY_PROP_PRESENT; + if (info->charge_gpio >= 0) + prop[i++] = POWER_SUPPLY_PROP_STATUS; + if (info->batt_tech >= 0) + prop[i++] = POWER_SUPPLY_PROP_TECHNOLOGY; + if (info->batt_I2C_reg >= 0) + prop[i++] = POWER_SUPPLY_PROP_VOLTAGE_NOW; + if (info->max_voltage >= 0) + prop[i++] = POWER_SUPPLY_PROP_VOLTAGE_MAX; + if (info->min_voltage >= 0) + prop[i++] = POWER_SUPPLY_PROP_VOLTAGE_MIN; + + if (!info->batt_name) { + dev_info(&charger->client->dev, + "Please consider setting proper battery " + "name in platform definition file, falling " + "back to name \" Z2_DEFAULT_NAME \"\n"); + charger->batt_ps_desc.name = Z2_DEFAULT_NAME; + } else + charger->batt_ps_desc.name = info->batt_name; + + charger->batt_ps_desc.properties = prop; + charger->batt_ps_desc.num_properties = props; + charger->batt_ps_desc.type = POWER_SUPPLY_TYPE_BATTERY; + charger->batt_ps_desc.get_property = z2_batt_get_property; + charger->batt_ps_desc.external_power_changed = + z2_batt_ext_power_changed; + charger->batt_ps_desc.use_for_apm = 1; + + return 0; +} + +static int z2_batt_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int ret = 0; + int props = 1; /* POWER_SUPPLY_PROP_PRESENT */ + struct z2_charger *charger; + struct z2_battery_info *info = client->dev.platform_data; + struct power_supply_config psy_cfg = {}; + + if (info == NULL) { + dev_err(&client->dev, + "Please set platform device platform_data" + " to a valid z2_battery_info pointer!\n"); + return -EINVAL; + } + + charger = kzalloc(sizeof(*charger), GFP_KERNEL); + if (charger == NULL) + return -ENOMEM; + + charger->bat_status = POWER_SUPPLY_STATUS_UNKNOWN; + charger->info = info; + charger->client = client; + i2c_set_clientdata(client, charger); + psy_cfg.drv_data = charger; + + mutex_init(&charger->work_lock); + + if (info->charge_gpio >= 0 && gpio_is_valid(info->charge_gpio)) { + ret = gpio_request(info->charge_gpio, "BATT CHRG"); + if (ret) + goto err; + + ret = gpio_direction_input(info->charge_gpio); + if (ret) + goto err2; + + irq_set_irq_type(gpio_to_irq(info->charge_gpio), + IRQ_TYPE_EDGE_BOTH); + ret = request_irq(gpio_to_irq(info->charge_gpio), + z2_charge_switch_irq, 0, + "AC Detect", charger); + if (ret) + goto err3; + } + + ret = z2_batt_ps_init(charger, props); + if (ret) + goto err3; + + INIT_WORK(&charger->bat_work, z2_batt_work); + + charger->batt_ps = power_supply_register(&client->dev, + &charger->batt_ps_desc, + &psy_cfg); + if (IS_ERR(charger->batt_ps)) { + ret = PTR_ERR(charger->batt_ps); + goto err4; + } + + schedule_work(&charger->bat_work); + + return 0; + +err4: + kfree(charger->batt_ps_desc.properties); +err3: + if (info->charge_gpio >= 0 && gpio_is_valid(info->charge_gpio)) + free_irq(gpio_to_irq(info->charge_gpio), charger); +err2: + if (info->charge_gpio >= 0 && gpio_is_valid(info->charge_gpio)) + gpio_free(info->charge_gpio); +err: + kfree(charger); + return ret; +} + +static int z2_batt_remove(struct i2c_client *client) +{ + struct z2_charger *charger = i2c_get_clientdata(client); + struct z2_battery_info *info = charger->info; + + cancel_work_sync(&charger->bat_work); + power_supply_unregister(charger->batt_ps); + + kfree(charger->batt_ps_desc.properties); + if (info->charge_gpio >= 0 && gpio_is_valid(info->charge_gpio)) { + free_irq(gpio_to_irq(info->charge_gpio), charger); + gpio_free(info->charge_gpio); + } + + kfree(charger); + + return 0; +} + +#ifdef CONFIG_PM +static int z2_batt_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct z2_charger *charger = i2c_get_clientdata(client); + + flush_work(&charger->bat_work); + return 0; +} + +static int z2_batt_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct z2_charger *charger = i2c_get_clientdata(client); + + schedule_work(&charger->bat_work); + return 0; +} + +static const struct dev_pm_ops z2_battery_pm_ops = { + .suspend = z2_batt_suspend, + .resume = z2_batt_resume, +}; + +#define Z2_BATTERY_PM_OPS (&z2_battery_pm_ops) + +#else +#define Z2_BATTERY_PM_OPS (NULL) +#endif + +static const struct i2c_device_id z2_batt_id[] = { + { "aer915", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, z2_batt_id); + +static struct i2c_driver z2_batt_driver = { + .driver = { + .name = "z2-battery", + .owner = THIS_MODULE, + .pm = Z2_BATTERY_PM_OPS + }, + .probe = z2_batt_probe, + .remove = z2_batt_remove, + .id_table = z2_batt_id, +}; +module_i2c_driver(z2_batt_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Peter Edwards "); +MODULE_DESCRIPTION("Zipit Z2 battery driver"); diff --git a/drivers/power/test_power.c b/drivers/power/test_power.c deleted file mode 100644 index 57246cdbd042..000000000000 --- a/drivers/power/test_power.c +++ /dev/null @@ -1,533 +0,0 @@ -/* - * Power supply driver for testing. - * - * Copyright 2010 Anton Vorontsov - * - * Dynamic module parameter code from the Virtual Battery Driver - * Copyright (C) 2008 Pylone, Inc. - * By: Masashi YOKOTA - * Originally found here: - * http://downloads.pylone.jp/src/virtual_battery/virtual_battery-0.0.1.tar.bz2 - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - */ - -#include -#include -#include -#include -#include -#include - -enum test_power_id { - TEST_AC, - TEST_BATTERY, - TEST_USB, - TEST_POWER_NUM, -}; - -static int ac_online = 1; -static int usb_online = 1; -static int battery_status = POWER_SUPPLY_STATUS_DISCHARGING; -static int battery_health = POWER_SUPPLY_HEALTH_GOOD; -static int battery_present = 1; /* true */ -static int battery_technology = POWER_SUPPLY_TECHNOLOGY_LION; -static int battery_capacity = 50; -static int battery_voltage = 3300; - -static bool module_initialized; - -static int test_power_get_ac_property(struct power_supply *psy, - enum power_supply_property psp, - union power_supply_propval *val) -{ - switch (psp) { - case POWER_SUPPLY_PROP_ONLINE: - val->intval = ac_online; - break; - default: - return -EINVAL; - } - return 0; -} - -static int test_power_get_usb_property(struct power_supply *psy, - enum power_supply_property psp, - union power_supply_propval *val) -{ - switch (psp) { - case POWER_SUPPLY_PROP_ONLINE: - val->intval = usb_online; - break; - default: - return -EINVAL; - } - return 0; -} - -static int test_power_get_battery_property(struct power_supply *psy, - enum power_supply_property psp, - union power_supply_propval *val) -{ - switch (psp) { - case POWER_SUPPLY_PROP_MODEL_NAME: - val->strval = "Test battery"; - break; - case POWER_SUPPLY_PROP_MANUFACTURER: - val->strval = "Linux"; - break; - case POWER_SUPPLY_PROP_SERIAL_NUMBER: - val->strval = UTS_RELEASE; - break; - case POWER_SUPPLY_PROP_STATUS: - val->intval = battery_status; - break; - case POWER_SUPPLY_PROP_CHARGE_TYPE: - val->intval = POWER_SUPPLY_CHARGE_TYPE_FAST; - break; - case POWER_SUPPLY_PROP_HEALTH: - val->intval = battery_health; - break; - case POWER_SUPPLY_PROP_PRESENT: - val->intval = battery_present; - break; - case POWER_SUPPLY_PROP_TECHNOLOGY: - val->intval = battery_technology; - break; - case POWER_SUPPLY_PROP_CAPACITY_LEVEL: - val->intval = POWER_SUPPLY_CAPACITY_LEVEL_NORMAL; - break; - case POWER_SUPPLY_PROP_CAPACITY: - case POWER_SUPPLY_PROP_CHARGE_NOW: - val->intval = battery_capacity; - break; - case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: - case POWER_SUPPLY_PROP_CHARGE_FULL: - val->intval = 100; - break; - case POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG: - case POWER_SUPPLY_PROP_TIME_TO_FULL_NOW: - val->intval = 3600; - break; - case POWER_SUPPLY_PROP_TEMP: - val->intval = 26; - break; - case POWER_SUPPLY_PROP_VOLTAGE_NOW: - val->intval = battery_voltage; - break; - default: - pr_info("%s: some properties deliberately report errors.\n", - __func__); - return -EINVAL; - } - return 0; -} - -static enum power_supply_property test_power_ac_props[] = { - POWER_SUPPLY_PROP_ONLINE, -}; - -static enum power_supply_property test_power_battery_props[] = { - POWER_SUPPLY_PROP_STATUS, - POWER_SUPPLY_PROP_CHARGE_TYPE, - POWER_SUPPLY_PROP_HEALTH, - POWER_SUPPLY_PROP_PRESENT, - POWER_SUPPLY_PROP_TECHNOLOGY, - POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, - POWER_SUPPLY_PROP_CHARGE_FULL, - POWER_SUPPLY_PROP_CHARGE_NOW, - POWER_SUPPLY_PROP_CAPACITY, - POWER_SUPPLY_PROP_CAPACITY_LEVEL, - POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG, - POWER_SUPPLY_PROP_TIME_TO_FULL_NOW, - POWER_SUPPLY_PROP_MODEL_NAME, - POWER_SUPPLY_PROP_MANUFACTURER, - POWER_SUPPLY_PROP_SERIAL_NUMBER, - POWER_SUPPLY_PROP_TEMP, - POWER_SUPPLY_PROP_VOLTAGE_NOW, -}; - -static char *test_power_ac_supplied_to[] = { - "test_battery", -}; - -static struct power_supply *test_power_supplies[TEST_POWER_NUM]; - -static const struct power_supply_desc test_power_desc[] = { - [TEST_AC] = { - .name = "test_ac", - .type = POWER_SUPPLY_TYPE_MAINS, - .properties = test_power_ac_props, - .num_properties = ARRAY_SIZE(test_power_ac_props), - .get_property = test_power_get_ac_property, - }, - [TEST_BATTERY] = { - .name = "test_battery", - .type = POWER_SUPPLY_TYPE_BATTERY, - .properties = test_power_battery_props, - .num_properties = ARRAY_SIZE(test_power_battery_props), - .get_property = test_power_get_battery_property, - }, - [TEST_USB] = { - .name = "test_usb", - .type = POWER_SUPPLY_TYPE_USB, - .properties = test_power_ac_props, - .num_properties = ARRAY_SIZE(test_power_ac_props), - .get_property = test_power_get_usb_property, - }, -}; - -static const struct power_supply_config test_power_configs[] = { - { - /* test_ac */ - .supplied_to = test_power_ac_supplied_to, - .num_supplicants = ARRAY_SIZE(test_power_ac_supplied_to), - }, { - /* test_battery */ - }, { - /* test_usb */ - .supplied_to = test_power_ac_supplied_to, - .num_supplicants = ARRAY_SIZE(test_power_ac_supplied_to), - }, -}; - -static int __init test_power_init(void) -{ - int i; - int ret; - - BUILD_BUG_ON(TEST_POWER_NUM != ARRAY_SIZE(test_power_supplies)); - BUILD_BUG_ON(TEST_POWER_NUM != ARRAY_SIZE(test_power_configs)); - - for (i = 0; i < ARRAY_SIZE(test_power_supplies); i++) { - test_power_supplies[i] = power_supply_register(NULL, - &test_power_desc[i], - &test_power_configs[i]); - if (IS_ERR(test_power_supplies[i])) { - pr_err("%s: failed to register %s\n", __func__, - test_power_desc[i].name); - ret = PTR_ERR(test_power_supplies[i]); - goto failed; - } - } - - module_initialized = true; - return 0; -failed: - while (--i >= 0) - power_supply_unregister(test_power_supplies[i]); - return ret; -} -module_init(test_power_init); - -static void __exit test_power_exit(void) -{ - int i; - - /* Let's see how we handle changes... */ - ac_online = 0; - usb_online = 0; - battery_status = POWER_SUPPLY_STATUS_DISCHARGING; - for (i = 0; i < ARRAY_SIZE(test_power_supplies); i++) - power_supply_changed(test_power_supplies[i]); - pr_info("%s: 'changed' event sent, sleeping for 10 seconds...\n", - __func__); - ssleep(10); - - for (i = 0; i < ARRAY_SIZE(test_power_supplies); i++) - power_supply_unregister(test_power_supplies[i]); - - module_initialized = false; -} -module_exit(test_power_exit); - - - -#define MAX_KEYLENGTH 256 -struct battery_property_map { - int value; - char const *key; -}; - -static struct battery_property_map map_ac_online[] = { - { 0, "off" }, - { 1, "on" }, - { -1, NULL }, -}; - -static struct battery_property_map map_status[] = { - { POWER_SUPPLY_STATUS_CHARGING, "charging" }, - { POWER_SUPPLY_STATUS_DISCHARGING, "discharging" }, - { POWER_SUPPLY_STATUS_NOT_CHARGING, "not-charging" }, - { POWER_SUPPLY_STATUS_FULL, "full" }, - { -1, NULL }, -}; - -static struct battery_property_map map_health[] = { - { POWER_SUPPLY_HEALTH_GOOD, "good" }, - { POWER_SUPPLY_HEALTH_OVERHEAT, "overheat" }, - { POWER_SUPPLY_HEALTH_DEAD, "dead" }, - { POWER_SUPPLY_HEALTH_OVERVOLTAGE, "overvoltage" }, - { POWER_SUPPLY_HEALTH_UNSPEC_FAILURE, "failure" }, - { -1, NULL }, -}; - -static struct battery_property_map map_present[] = { - { 0, "false" }, - { 1, "true" }, - { -1, NULL }, -}; - -static struct battery_property_map map_technology[] = { - { POWER_SUPPLY_TECHNOLOGY_NiMH, "NiMH" }, - { POWER_SUPPLY_TECHNOLOGY_LION, "LION" }, - { POWER_SUPPLY_TECHNOLOGY_LIPO, "LIPO" }, - { POWER_SUPPLY_TECHNOLOGY_LiFe, "LiFe" }, - { POWER_SUPPLY_TECHNOLOGY_NiCd, "NiCd" }, - { POWER_SUPPLY_TECHNOLOGY_LiMn, "LiMn" }, - { -1, NULL }, -}; - - -static int map_get_value(struct battery_property_map *map, const char *key, - int def_val) -{ - char buf[MAX_KEYLENGTH]; - int cr; - - strncpy(buf, key, MAX_KEYLENGTH); - buf[MAX_KEYLENGTH-1] = '\0'; - - cr = strnlen(buf, MAX_KEYLENGTH) - 1; - if (cr < 0) - return def_val; - if (buf[cr] == '\n') - buf[cr] = '\0'; - - while (map->key) { - if (strncasecmp(map->key, buf, MAX_KEYLENGTH) == 0) - return map->value; - map++; - } - - return def_val; -} - - -static const char *map_get_key(struct battery_property_map *map, int value, - const char *def_key) -{ - while (map->key) { - if (map->value == value) - return map->key; - map++; - } - - return def_key; -} - -static inline void signal_power_supply_changed(struct power_supply *psy) -{ - if (module_initialized) - power_supply_changed(psy); -} - -static int param_set_ac_online(const char *key, const struct kernel_param *kp) -{ - ac_online = map_get_value(map_ac_online, key, ac_online); - signal_power_supply_changed(test_power_supplies[TEST_AC]); - return 0; -} - -static int param_get_ac_online(char *buffer, const struct kernel_param *kp) -{ - strcpy(buffer, map_get_key(map_ac_online, ac_online, "unknown")); - return strlen(buffer); -} - -static int param_set_usb_online(const char *key, const struct kernel_param *kp) -{ - usb_online = map_get_value(map_ac_online, key, usb_online); - signal_power_supply_changed(test_power_supplies[TEST_USB]); - return 0; -} - -static int param_get_usb_online(char *buffer, const struct kernel_param *kp) -{ - strcpy(buffer, map_get_key(map_ac_online, usb_online, "unknown")); - return strlen(buffer); -} - -static int param_set_battery_status(const char *key, - const struct kernel_param *kp) -{ - battery_status = map_get_value(map_status, key, battery_status); - signal_power_supply_changed(test_power_supplies[TEST_BATTERY]); - return 0; -} - -static int param_get_battery_status(char *buffer, const struct kernel_param *kp) -{ - strcpy(buffer, map_get_key(map_status, battery_status, "unknown")); - return strlen(buffer); -} - -static int param_set_battery_health(const char *key, - const struct kernel_param *kp) -{ - battery_health = map_get_value(map_health, key, battery_health); - signal_power_supply_changed(test_power_supplies[TEST_BATTERY]); - return 0; -} - -static int param_get_battery_health(char *buffer, const struct kernel_param *kp) -{ - strcpy(buffer, map_get_key(map_health, battery_health, "unknown")); - return strlen(buffer); -} - -static int param_set_battery_present(const char *key, - const struct kernel_param *kp) -{ - battery_present = map_get_value(map_present, key, battery_present); - signal_power_supply_changed(test_power_supplies[TEST_AC]); - return 0; -} - -static int param_get_battery_present(char *buffer, - const struct kernel_param *kp) -{ - strcpy(buffer, map_get_key(map_present, battery_present, "unknown")); - return strlen(buffer); -} - -static int param_set_battery_technology(const char *key, - const struct kernel_param *kp) -{ - battery_technology = map_get_value(map_technology, key, - battery_technology); - signal_power_supply_changed(test_power_supplies[TEST_BATTERY]); - return 0; -} - -static int param_get_battery_technology(char *buffer, - const struct kernel_param *kp) -{ - strcpy(buffer, - map_get_key(map_technology, battery_technology, "unknown")); - return strlen(buffer); -} - -static int param_set_battery_capacity(const char *key, - const struct kernel_param *kp) -{ - int tmp; - - if (1 != sscanf(key, "%d", &tmp)) - return -EINVAL; - - battery_capacity = tmp; - signal_power_supply_changed(test_power_supplies[TEST_BATTERY]); - return 0; -} - -#define param_get_battery_capacity param_get_int - -static int param_set_battery_voltage(const char *key, - const struct kernel_param *kp) -{ - int tmp; - - if (1 != sscanf(key, "%d", &tmp)) - return -EINVAL; - - battery_voltage = tmp; - signal_power_supply_changed(test_power_supplies[TEST_BATTERY]); - return 0; -} - -#define param_get_battery_voltage param_get_int - -static const struct kernel_param_ops param_ops_ac_online = { - .set = param_set_ac_online, - .get = param_get_ac_online, -}; - -static const struct kernel_param_ops param_ops_usb_online = { - .set = param_set_usb_online, - .get = param_get_usb_online, -}; - -static const struct kernel_param_ops param_ops_battery_status = { - .set = param_set_battery_status, - .get = param_get_battery_status, -}; - -static const struct kernel_param_ops param_ops_battery_present = { - .set = param_set_battery_present, - .get = param_get_battery_present, -}; - -static const struct kernel_param_ops param_ops_battery_technology = { - .set = param_set_battery_technology, - .get = param_get_battery_technology, -}; - -static const struct kernel_param_ops param_ops_battery_health = { - .set = param_set_battery_health, - .get = param_get_battery_health, -}; - -static const struct kernel_param_ops param_ops_battery_capacity = { - .set = param_set_battery_capacity, - .get = param_get_battery_capacity, -}; - -static const struct kernel_param_ops param_ops_battery_voltage = { - .set = param_set_battery_voltage, - .get = param_get_battery_voltage, -}; - -#define param_check_ac_online(name, p) __param_check(name, p, void); -#define param_check_usb_online(name, p) __param_check(name, p, void); -#define param_check_battery_status(name, p) __param_check(name, p, void); -#define param_check_battery_present(name, p) __param_check(name, p, void); -#define param_check_battery_technology(name, p) __param_check(name, p, void); -#define param_check_battery_health(name, p) __param_check(name, p, void); -#define param_check_battery_capacity(name, p) __param_check(name, p, void); -#define param_check_battery_voltage(name, p) __param_check(name, p, void); - - -module_param(ac_online, ac_online, 0644); -MODULE_PARM_DESC(ac_online, "AC charging state "); - -module_param(usb_online, usb_online, 0644); -MODULE_PARM_DESC(usb_online, "USB charging state "); - -module_param(battery_status, battery_status, 0644); -MODULE_PARM_DESC(battery_status, - "battery status "); - -module_param(battery_present, battery_present, 0644); -MODULE_PARM_DESC(battery_present, - "battery presence state "); - -module_param(battery_technology, battery_technology, 0644); -MODULE_PARM_DESC(battery_technology, - "battery technology "); - -module_param(battery_health, battery_health, 0644); -MODULE_PARM_DESC(battery_health, - "battery health state "); - -module_param(battery_capacity, battery_capacity, 0644); -MODULE_PARM_DESC(battery_capacity, "battery capacity (percentage)"); - -module_param(battery_voltage, battery_voltage, 0644); -MODULE_PARM_DESC(battery_voltage, "battery voltage (millivolts)"); - -MODULE_DESCRIPTION("Power supply driver for testing"); -MODULE_AUTHOR("Anton Vorontsov "); -MODULE_LICENSE("GPL"); diff --git a/drivers/power/tosa_battery.c b/drivers/power/tosa_battery.c deleted file mode 100644 index 6e88c1b37945..000000000000 --- a/drivers/power/tosa_battery.c +++ /dev/null @@ -1,470 +0,0 @@ -/* - * Battery and Power Management code for the Sharp SL-6000x - * - * Copyright (c) 2005 Dirk Opfer - * Copyright (c) 2008 Dmitry Baryshkov - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - * - */ -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -static DEFINE_MUTEX(bat_lock); /* protects gpio pins */ -static struct work_struct bat_work; - -struct tosa_bat { - int status; - struct power_supply *psy; - int full_chrg; - - struct mutex work_lock; /* protects data */ - - bool (*is_present)(struct tosa_bat *bat); - int gpio_full; - int gpio_charge_off; - - int technology; - - int gpio_bat; - int adc_bat; - int adc_bat_divider; - int bat_max; - int bat_min; - - int gpio_temp; - int adc_temp; - int adc_temp_divider; -}; - -static struct tosa_bat tosa_bat_main; -static struct tosa_bat tosa_bat_jacket; - -static unsigned long tosa_read_bat(struct tosa_bat *bat) -{ - unsigned long value = 0; - - if (bat->gpio_bat < 0 || bat->adc_bat < 0) - return 0; - - mutex_lock(&bat_lock); - gpio_set_value(bat->gpio_bat, 1); - msleep(5); - value = wm97xx_read_aux_adc(dev_get_drvdata(bat->psy->dev.parent), - bat->adc_bat); - gpio_set_value(bat->gpio_bat, 0); - mutex_unlock(&bat_lock); - - value = value * 1000000 / bat->adc_bat_divider; - - return value; -} - -static unsigned long tosa_read_temp(struct tosa_bat *bat) -{ - unsigned long value = 0; - - if (bat->gpio_temp < 0 || bat->adc_temp < 0) - return 0; - - mutex_lock(&bat_lock); - gpio_set_value(bat->gpio_temp, 1); - msleep(5); - value = wm97xx_read_aux_adc(dev_get_drvdata(bat->psy->dev.parent), - bat->adc_temp); - gpio_set_value(bat->gpio_temp, 0); - mutex_unlock(&bat_lock); - - value = value * 10000 / bat->adc_temp_divider; - - return value; -} - -static int tosa_bat_get_property(struct power_supply *psy, - enum power_supply_property psp, - union power_supply_propval *val) -{ - int ret = 0; - struct tosa_bat *bat = power_supply_get_drvdata(psy); - - if (bat->is_present && !bat->is_present(bat) - && psp != POWER_SUPPLY_PROP_PRESENT) { - return -ENODEV; - } - - switch (psp) { - case POWER_SUPPLY_PROP_STATUS: - val->intval = bat->status; - break; - case POWER_SUPPLY_PROP_TECHNOLOGY: - val->intval = bat->technology; - break; - case POWER_SUPPLY_PROP_VOLTAGE_NOW: - val->intval = tosa_read_bat(bat); - break; - case POWER_SUPPLY_PROP_VOLTAGE_MAX: - if (bat->full_chrg == -1) - val->intval = bat->bat_max; - else - val->intval = bat->full_chrg; - break; - case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: - val->intval = bat->bat_max; - break; - case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: - val->intval = bat->bat_min; - break; - case POWER_SUPPLY_PROP_TEMP: - val->intval = tosa_read_temp(bat); - break; - case POWER_SUPPLY_PROP_PRESENT: - val->intval = bat->is_present ? bat->is_present(bat) : 1; - break; - default: - ret = -EINVAL; - break; - } - return ret; -} - -static bool tosa_jacket_bat_is_present(struct tosa_bat *bat) -{ - return gpio_get_value(TOSA_GPIO_JACKET_DETECT) == 0; -} - -static void tosa_bat_external_power_changed(struct power_supply *psy) -{ - schedule_work(&bat_work); -} - -static irqreturn_t tosa_bat_gpio_isr(int irq, void *data) -{ - pr_info("tosa_bat_gpio irq\n"); - schedule_work(&bat_work); - return IRQ_HANDLED; -} - -static void tosa_bat_update(struct tosa_bat *bat) -{ - int old; - struct power_supply *psy = bat->psy; - - mutex_lock(&bat->work_lock); - - old = bat->status; - - if (bat->is_present && !bat->is_present(bat)) { - printk(KERN_NOTICE "%s not present\n", psy->desc->name); - bat->status = POWER_SUPPLY_STATUS_UNKNOWN; - bat->full_chrg = -1; - } else if (power_supply_am_i_supplied(psy)) { - if (bat->status == POWER_SUPPLY_STATUS_DISCHARGING) { - gpio_set_value(bat->gpio_charge_off, 0); - mdelay(15); - } - - if (gpio_get_value(bat->gpio_full)) { - if (old == POWER_SUPPLY_STATUS_CHARGING || - bat->full_chrg == -1) - bat->full_chrg = tosa_read_bat(bat); - - gpio_set_value(bat->gpio_charge_off, 1); - bat->status = POWER_SUPPLY_STATUS_FULL; - } else { - gpio_set_value(bat->gpio_charge_off, 0); - bat->status = POWER_SUPPLY_STATUS_CHARGING; - } - } else { - gpio_set_value(bat->gpio_charge_off, 1); - bat->status = POWER_SUPPLY_STATUS_DISCHARGING; - } - - if (old != bat->status) - power_supply_changed(psy); - - mutex_unlock(&bat->work_lock); -} - -static void tosa_bat_work(struct work_struct *work) -{ - tosa_bat_update(&tosa_bat_main); - tosa_bat_update(&tosa_bat_jacket); -} - - -static enum power_supply_property tosa_bat_main_props[] = { - POWER_SUPPLY_PROP_STATUS, - POWER_SUPPLY_PROP_TECHNOLOGY, - POWER_SUPPLY_PROP_VOLTAGE_NOW, - POWER_SUPPLY_PROP_VOLTAGE_MAX, - POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, - POWER_SUPPLY_PROP_TEMP, - POWER_SUPPLY_PROP_PRESENT, -}; - -static enum power_supply_property tosa_bat_bu_props[] = { - POWER_SUPPLY_PROP_STATUS, - POWER_SUPPLY_PROP_TECHNOLOGY, - POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, - POWER_SUPPLY_PROP_VOLTAGE_NOW, - POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, - POWER_SUPPLY_PROP_PRESENT, -}; - -static const struct power_supply_desc tosa_bat_main_desc = { - .name = "main-battery", - .type = POWER_SUPPLY_TYPE_BATTERY, - .properties = tosa_bat_main_props, - .num_properties = ARRAY_SIZE(tosa_bat_main_props), - .get_property = tosa_bat_get_property, - .external_power_changed = tosa_bat_external_power_changed, - .use_for_apm = 1, -}; - -static const struct power_supply_desc tosa_bat_jacket_desc = { - .name = "jacket-battery", - .type = POWER_SUPPLY_TYPE_BATTERY, - .properties = tosa_bat_main_props, - .num_properties = ARRAY_SIZE(tosa_bat_main_props), - .get_property = tosa_bat_get_property, - .external_power_changed = tosa_bat_external_power_changed, -}; - -static const struct power_supply_desc tosa_bat_bu_desc = { - .name = "backup-battery", - .type = POWER_SUPPLY_TYPE_BATTERY, - .properties = tosa_bat_bu_props, - .num_properties = ARRAY_SIZE(tosa_bat_bu_props), - .get_property = tosa_bat_get_property, - .external_power_changed = tosa_bat_external_power_changed, -}; - -static struct tosa_bat tosa_bat_main = { - .status = POWER_SUPPLY_STATUS_DISCHARGING, - .full_chrg = -1, - .psy = NULL, - - .gpio_full = TOSA_GPIO_BAT0_CRG, - .gpio_charge_off = TOSA_GPIO_CHARGE_OFF, - - .technology = POWER_SUPPLY_TECHNOLOGY_LIPO, - - .gpio_bat = TOSA_GPIO_BAT0_V_ON, - .adc_bat = WM97XX_AUX_ID3, - .adc_bat_divider = 414, - .bat_max = 4310000, - .bat_min = 1551 * 1000000 / 414, - - .gpio_temp = TOSA_GPIO_BAT1_TH_ON, - .adc_temp = WM97XX_AUX_ID2, - .adc_temp_divider = 10000, -}; - -static struct tosa_bat tosa_bat_jacket = { - .status = POWER_SUPPLY_STATUS_DISCHARGING, - .full_chrg = -1, - .psy = NULL, - - .is_present = tosa_jacket_bat_is_present, - .gpio_full = TOSA_GPIO_BAT1_CRG, - .gpio_charge_off = TOSA_GPIO_CHARGE_OFF_JC, - - .technology = POWER_SUPPLY_TECHNOLOGY_LIPO, - - .gpio_bat = TOSA_GPIO_BAT1_V_ON, - .adc_bat = WM97XX_AUX_ID3, - .adc_bat_divider = 414, - .bat_max = 4310000, - .bat_min = 1551 * 1000000 / 414, - - .gpio_temp = TOSA_GPIO_BAT0_TH_ON, - .adc_temp = WM97XX_AUX_ID2, - .adc_temp_divider = 10000, -}; - -static struct tosa_bat tosa_bat_bu = { - .status = POWER_SUPPLY_STATUS_UNKNOWN, - .full_chrg = -1, - .psy = NULL, - - .gpio_full = -1, - .gpio_charge_off = -1, - - .technology = POWER_SUPPLY_TECHNOLOGY_LiMn, - - .gpio_bat = TOSA_GPIO_BU_CHRG_ON, - .adc_bat = WM97XX_AUX_ID4, - .adc_bat_divider = 1266, - - .gpio_temp = -1, - .adc_temp = -1, - .adc_temp_divider = -1, -}; - -static struct gpio tosa_bat_gpios[] = { - { TOSA_GPIO_CHARGE_OFF, GPIOF_OUT_INIT_HIGH, "main charge off" }, - { TOSA_GPIO_CHARGE_OFF_JC, GPIOF_OUT_INIT_HIGH, "jacket charge off" }, - { TOSA_GPIO_BAT_SW_ON, GPIOF_OUT_INIT_LOW, "battery switch" }, - { TOSA_GPIO_BAT0_V_ON, GPIOF_OUT_INIT_LOW, "main battery" }, - { TOSA_GPIO_BAT1_V_ON, GPIOF_OUT_INIT_LOW, "jacket battery" }, - { TOSA_GPIO_BAT1_TH_ON, GPIOF_OUT_INIT_LOW, "main battery temp" }, - { TOSA_GPIO_BAT0_TH_ON, GPIOF_OUT_INIT_LOW, "jacket battery temp" }, - { TOSA_GPIO_BU_CHRG_ON, GPIOF_OUT_INIT_LOW, "backup battery" }, - { TOSA_GPIO_BAT0_CRG, GPIOF_IN, "main battery full" }, - { TOSA_GPIO_BAT1_CRG, GPIOF_IN, "jacket battery full" }, - { TOSA_GPIO_BAT0_LOW, GPIOF_IN, "main battery low" }, - { TOSA_GPIO_BAT1_LOW, GPIOF_IN, "jacket battery low" }, - { TOSA_GPIO_JACKET_DETECT, GPIOF_IN, "jacket detect" }, -}; - -#ifdef CONFIG_PM -static int tosa_bat_suspend(struct platform_device *dev, pm_message_t state) -{ - /* flush all pending status updates */ - flush_work(&bat_work); - return 0; -} - -static int tosa_bat_resume(struct platform_device *dev) -{ - /* things may have changed while we were away */ - schedule_work(&bat_work); - return 0; -} -#else -#define tosa_bat_suspend NULL -#define tosa_bat_resume NULL -#endif - -static int tosa_bat_probe(struct platform_device *dev) -{ - int ret; - struct power_supply_config main_psy_cfg = {}, - jacket_psy_cfg = {}, - bu_psy_cfg = {}; - - if (!machine_is_tosa()) - return -ENODEV; - - ret = gpio_request_array(tosa_bat_gpios, ARRAY_SIZE(tosa_bat_gpios)); - if (ret) - return ret; - - mutex_init(&tosa_bat_main.work_lock); - mutex_init(&tosa_bat_jacket.work_lock); - - INIT_WORK(&bat_work, tosa_bat_work); - - main_psy_cfg.drv_data = &tosa_bat_main; - tosa_bat_main.psy = power_supply_register(&dev->dev, - &tosa_bat_main_desc, - &main_psy_cfg); - if (IS_ERR(tosa_bat_main.psy)) { - ret = PTR_ERR(tosa_bat_main.psy); - goto err_psy_reg_main; - } - - jacket_psy_cfg.drv_data = &tosa_bat_jacket; - tosa_bat_jacket.psy = power_supply_register(&dev->dev, - &tosa_bat_jacket_desc, - &jacket_psy_cfg); - if (IS_ERR(tosa_bat_jacket.psy)) { - ret = PTR_ERR(tosa_bat_jacket.psy); - goto err_psy_reg_jacket; - } - - bu_psy_cfg.drv_data = &tosa_bat_bu; - tosa_bat_bu.psy = power_supply_register(&dev->dev, &tosa_bat_bu_desc, - &bu_psy_cfg); - if (IS_ERR(tosa_bat_bu.psy)) { - ret = PTR_ERR(tosa_bat_bu.psy); - goto err_psy_reg_bu; - } - - ret = request_irq(gpio_to_irq(TOSA_GPIO_BAT0_CRG), - tosa_bat_gpio_isr, - IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, - "main full", &tosa_bat_main); - if (ret) - goto err_req_main; - - ret = request_irq(gpio_to_irq(TOSA_GPIO_BAT1_CRG), - tosa_bat_gpio_isr, - IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, - "jacket full", &tosa_bat_jacket); - if (ret) - goto err_req_jacket; - - ret = request_irq(gpio_to_irq(TOSA_GPIO_JACKET_DETECT), - tosa_bat_gpio_isr, - IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, - "jacket detect", &tosa_bat_jacket); - if (!ret) { - schedule_work(&bat_work); - return 0; - } - - free_irq(gpio_to_irq(TOSA_GPIO_BAT1_CRG), &tosa_bat_jacket); -err_req_jacket: - free_irq(gpio_to_irq(TOSA_GPIO_BAT0_CRG), &tosa_bat_main); -err_req_main: - power_supply_unregister(tosa_bat_bu.psy); -err_psy_reg_bu: - power_supply_unregister(tosa_bat_jacket.psy); -err_psy_reg_jacket: - power_supply_unregister(tosa_bat_main.psy); -err_psy_reg_main: - - /* see comment in tosa_bat_remove */ - cancel_work_sync(&bat_work); - - gpio_free_array(tosa_bat_gpios, ARRAY_SIZE(tosa_bat_gpios)); - return ret; -} - -static int tosa_bat_remove(struct platform_device *dev) -{ - free_irq(gpio_to_irq(TOSA_GPIO_JACKET_DETECT), &tosa_bat_jacket); - free_irq(gpio_to_irq(TOSA_GPIO_BAT1_CRG), &tosa_bat_jacket); - free_irq(gpio_to_irq(TOSA_GPIO_BAT0_CRG), &tosa_bat_main); - - power_supply_unregister(tosa_bat_bu.psy); - power_supply_unregister(tosa_bat_jacket.psy); - power_supply_unregister(tosa_bat_main.psy); - - /* - * Now cancel the bat_work. We won't get any more schedules, - * since all sources (isr and external_power_changed) are - * unregistered now. - */ - cancel_work_sync(&bat_work); - gpio_free_array(tosa_bat_gpios, ARRAY_SIZE(tosa_bat_gpios)); - return 0; -} - -static struct platform_driver tosa_bat_driver = { - .driver.name = "wm97xx-battery", - .driver.owner = THIS_MODULE, - .probe = tosa_bat_probe, - .remove = tosa_bat_remove, - .suspend = tosa_bat_suspend, - .resume = tosa_bat_resume, -}; - -module_platform_driver(tosa_bat_driver); - -MODULE_LICENSE("GPL"); -MODULE_AUTHOR("Dmitry Baryshkov"); -MODULE_DESCRIPTION("Tosa battery driver"); -MODULE_ALIAS("platform:wm97xx-battery"); diff --git a/drivers/power/tps65090-charger.c b/drivers/power/tps65090-charger.c deleted file mode 100644 index 1b4b5e09538e..000000000000 --- a/drivers/power/tps65090-charger.c +++ /dev/null @@ -1,370 +0,0 @@ -/* - * Battery charger driver for TI's tps65090 - * - * Copyright (c) 2013, NVIDIA CORPORATION. All rights reserved. - - * 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 . - */ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#define TPS65090_CHARGER_ENABLE BIT(0) -#define TPS65090_VACG BIT(1) -#define TPS65090_NOITERM BIT(5) - -#define POLL_INTERVAL (HZ * 2) /* Used when no irq */ - -struct tps65090_charger { - struct device *dev; - int ac_online; - int prev_ac_online; - int irq; - struct task_struct *poll_task; - bool passive_mode; - struct power_supply *ac; - struct tps65090_platform_data *pdata; -}; - -static enum power_supply_property tps65090_ac_props[] = { - POWER_SUPPLY_PROP_ONLINE, -}; - -static int tps65090_low_chrg_current(struct tps65090_charger *charger) -{ - int ret; - - if (charger->passive_mode) - return 0; - - ret = tps65090_write(charger->dev->parent, TPS65090_REG_CG_CTRL5, - TPS65090_NOITERM); - if (ret < 0) { - dev_err(charger->dev, "%s(): error reading in register 0x%x\n", - __func__, TPS65090_REG_CG_CTRL5); - return ret; - } - return 0; -} - -static int tps65090_enable_charging(struct tps65090_charger *charger) -{ - int ret; - uint8_t ctrl0 = 0; - - if (charger->passive_mode) - return 0; - - ret = tps65090_read(charger->dev->parent, TPS65090_REG_CG_CTRL0, - &ctrl0); - if (ret < 0) { - dev_err(charger->dev, "%s(): error reading in register 0x%x\n", - __func__, TPS65090_REG_CG_CTRL0); - return ret; - } - - ret = tps65090_write(charger->dev->parent, TPS65090_REG_CG_CTRL0, - (ctrl0 | TPS65090_CHARGER_ENABLE)); - if (ret < 0) { - dev_err(charger->dev, "%s(): error writing in register 0x%x\n", - __func__, TPS65090_REG_CG_CTRL0); - return ret; - } - return 0; -} - -static int tps65090_config_charger(struct tps65090_charger *charger) -{ - uint8_t intrmask = 0; - int ret; - - if (charger->passive_mode) - return 0; - - if (charger->pdata->enable_low_current_chrg) { - ret = tps65090_low_chrg_current(charger); - if (ret < 0) { - dev_err(charger->dev, - "error configuring low charge current\n"); - return ret; - } - } - - /* Enable the VACG interrupt for AC power detect */ - ret = tps65090_read(charger->dev->parent, TPS65090_REG_INTR_MASK, - &intrmask); - if (ret < 0) { - dev_err(charger->dev, "%s(): error reading in register 0x%x\n", - __func__, TPS65090_REG_INTR_MASK); - return ret; - } - - ret = tps65090_write(charger->dev->parent, TPS65090_REG_INTR_MASK, - (intrmask | TPS65090_VACG)); - if (ret < 0) { - dev_err(charger->dev, "%s(): error writing in register 0x%x\n", - __func__, TPS65090_REG_CG_CTRL0); - return ret; - } - - return 0; -} - -static int tps65090_ac_get_property(struct power_supply *psy, - enum power_supply_property psp, - union power_supply_propval *val) -{ - struct tps65090_charger *charger = power_supply_get_drvdata(psy); - - if (psp == POWER_SUPPLY_PROP_ONLINE) { - val->intval = charger->ac_online; - charger->prev_ac_online = charger->ac_online; - return 0; - } - return -EINVAL; -} - -static irqreturn_t tps65090_charger_isr(int irq, void *dev_id) -{ - struct tps65090_charger *charger = dev_id; - int ret; - uint8_t status1 = 0; - uint8_t intrsts = 0; - - ret = tps65090_read(charger->dev->parent, TPS65090_REG_CG_STATUS1, - &status1); - if (ret < 0) { - dev_err(charger->dev, "%s(): Error in reading reg 0x%x\n", - __func__, TPS65090_REG_CG_STATUS1); - return IRQ_HANDLED; - } - msleep(75); - ret = tps65090_read(charger->dev->parent, TPS65090_REG_INTR_STS, - &intrsts); - if (ret < 0) { - dev_err(charger->dev, "%s(): Error in reading reg 0x%x\n", - __func__, TPS65090_REG_INTR_STS); - return IRQ_HANDLED; - } - - if (intrsts & TPS65090_VACG) { - ret = tps65090_enable_charging(charger); - if (ret < 0) - return IRQ_HANDLED; - charger->ac_online = 1; - } else { - charger->ac_online = 0; - } - - /* Clear interrupts. */ - if (!charger->passive_mode) { - ret = tps65090_write(charger->dev->parent, - TPS65090_REG_INTR_STS, 0x00); - if (ret < 0) { - dev_err(charger->dev, - "%s(): Error in writing reg 0x%x\n", - __func__, TPS65090_REG_INTR_STS); - } - } - - if (charger->prev_ac_online != charger->ac_online) - power_supply_changed(charger->ac); - - return IRQ_HANDLED; -} - -static struct tps65090_platform_data * - tps65090_parse_dt_charger_data(struct platform_device *pdev) -{ - struct tps65090_platform_data *pdata; - struct device_node *np = pdev->dev.of_node; - unsigned int prop; - - pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL); - if (!pdata) { - dev_err(&pdev->dev, "Memory alloc for tps65090_pdata failed\n"); - return NULL; - } - - prop = of_property_read_bool(np, "ti,enable-low-current-chrg"); - pdata->enable_low_current_chrg = prop; - - pdata->irq_base = -1; - - return pdata; - -} - -static int tps65090_charger_poll_task(void *data) -{ - set_freezable(); - - while (!kthread_should_stop()) { - schedule_timeout_interruptible(POLL_INTERVAL); - try_to_freeze(); - tps65090_charger_isr(-1, data); - } - return 0; -} - -static const struct power_supply_desc tps65090_charger_desc = { - .name = "tps65090-ac", - .type = POWER_SUPPLY_TYPE_MAINS, - .get_property = tps65090_ac_get_property, - .properties = tps65090_ac_props, - .num_properties = ARRAY_SIZE(tps65090_ac_props), -}; - -static int tps65090_charger_probe(struct platform_device *pdev) -{ - struct tps65090_charger *cdata; - struct tps65090_platform_data *pdata; - struct power_supply_config psy_cfg = {}; - uint8_t status1 = 0; - int ret; - int irq; - - pdata = dev_get_platdata(pdev->dev.parent); - - if (IS_ENABLED(CONFIG_OF) && !pdata && pdev->dev.of_node) - pdata = tps65090_parse_dt_charger_data(pdev); - - if (!pdata) { - dev_err(&pdev->dev, "%s():no platform data available\n", - __func__); - return -ENODEV; - } - - cdata = devm_kzalloc(&pdev->dev, sizeof(*cdata), GFP_KERNEL); - if (!cdata) { - dev_err(&pdev->dev, "failed to allocate memory status\n"); - return -ENOMEM; - } - - platform_set_drvdata(pdev, cdata); - - cdata->dev = &pdev->dev; - cdata->pdata = pdata; - - psy_cfg.supplied_to = pdata->supplied_to; - psy_cfg.num_supplicants = pdata->num_supplicants; - psy_cfg.of_node = pdev->dev.of_node; - psy_cfg.drv_data = cdata; - - cdata->ac = power_supply_register(&pdev->dev, &tps65090_charger_desc, - &psy_cfg); - if (IS_ERR(cdata->ac)) { - dev_err(&pdev->dev, "failed: power supply register\n"); - return PTR_ERR(cdata->ac); - } - - irq = platform_get_irq(pdev, 0); - if (irq < 0) - irq = -ENXIO; - cdata->irq = irq; - - ret = tps65090_config_charger(cdata); - if (ret < 0) { - dev_err(&pdev->dev, "charger config failed, err %d\n", ret); - goto fail_unregister_supply; - } - - /* Check for charger presence */ - ret = tps65090_read(cdata->dev->parent, TPS65090_REG_CG_STATUS1, - &status1); - if (ret < 0) { - dev_err(cdata->dev, "%s(): Error in reading reg 0x%x", __func__, - TPS65090_REG_CG_STATUS1); - goto fail_unregister_supply; - } - - if (status1 != 0) { - ret = tps65090_enable_charging(cdata); - if (ret < 0) { - dev_err(cdata->dev, "error enabling charger\n"); - goto fail_unregister_supply; - } - cdata->ac_online = 1; - power_supply_changed(cdata->ac); - } - - if (irq != -ENXIO) { - ret = devm_request_threaded_irq(&pdev->dev, irq, NULL, - tps65090_charger_isr, 0, "tps65090-charger", cdata); - if (ret) { - dev_err(cdata->dev, - "Unable to register irq %d err %d\n", irq, - ret); - goto fail_unregister_supply; - } - } else { - cdata->poll_task = kthread_run(tps65090_charger_poll_task, - cdata, "ktps65090charger"); - cdata->passive_mode = true; - if (IS_ERR(cdata->poll_task)) { - ret = PTR_ERR(cdata->poll_task); - dev_err(cdata->dev, - "Unable to run kthread err %d\n", ret); - goto fail_unregister_supply; - } - } - - return 0; - -fail_unregister_supply: - power_supply_unregister(cdata->ac); - - return ret; -} - -static int tps65090_charger_remove(struct platform_device *pdev) -{ - struct tps65090_charger *cdata = platform_get_drvdata(pdev); - - if (cdata->irq == -ENXIO) - kthread_stop(cdata->poll_task); - power_supply_unregister(cdata->ac); - - return 0; -} - -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 = { - .name = "tps65090-charger", - .of_match_table = of_tps65090_charger_match, - }, - .probe = tps65090_charger_probe, - .remove = tps65090_charger_remove, -}; -module_platform_driver(tps65090_charger_driver); - -MODULE_LICENSE("GPL v2"); -MODULE_AUTHOR("Syed Rafiuddin "); -MODULE_DESCRIPTION("tps65090 battery charger driver"); diff --git a/drivers/power/tps65217_charger.c b/drivers/power/tps65217_charger.c deleted file mode 100644 index 73dfae41def8..000000000000 --- a/drivers/power/tps65217_charger.c +++ /dev/null @@ -1,268 +0,0 @@ -/* - * 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 . - */ - -/* - * Battery charger driver for TI's tps65217 - */ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -#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; - struct power_supply_config cfg = {}; - 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; - - cfg.of_node = pdev->dev.of_node; - cfg.drv_data = charger; - - charger->ac = devm_power_supply_register(&pdev->dev, - &tps65217_charger_desc, - &cfg); - 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 "); -MODULE_DESCRIPTION("TPS65217 battery charger driver"); diff --git a/drivers/power/twl4030_charger.c b/drivers/power/twl4030_charger.c deleted file mode 100644 index bcd4dc304f27..000000000000 --- a/drivers/power/twl4030_charger.c +++ /dev/null @@ -1,1162 +0,0 @@ -/* - * TWL4030/TPS65950 BCI (Battery Charger Interface) driver - * - * Copyright (C) 2010 Gražvydas Ignotas - * - * based on twl4030_bci_battery.c by TI - * Copyright (C) 2008 Texas Instruments, Inc. - * - * 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define TWL4030_BCIMDEN 0x00 -#define TWL4030_BCIMDKEY 0x01 -#define TWL4030_BCIMSTATEC 0x02 -#define TWL4030_BCIICHG 0x08 -#define TWL4030_BCIVAC 0x0a -#define TWL4030_BCIVBUS 0x0c -#define TWL4030_BCIMFSTS3 0x0F -#define TWL4030_BCIMFSTS4 0x10 -#define TWL4030_BCICTL1 0x23 -#define TWL4030_BB_CFG 0x12 -#define TWL4030_BCIIREF1 0x27 -#define TWL4030_BCIIREF2 0x28 -#define TWL4030_BCIMFKEY 0x11 -#define TWL4030_BCIMFEN3 0x14 -#define TWL4030_BCIMFTH8 0x1d -#define TWL4030_BCIMFTH9 0x1e -#define TWL4030_BCIWDKEY 0x21 - -#define TWL4030_BCIMFSTS1 0x01 - -#define TWL4030_BCIAUTOWEN BIT(5) -#define TWL4030_CONFIG_DONE BIT(4) -#define TWL4030_CVENAC BIT(2) -#define TWL4030_BCIAUTOUSB BIT(1) -#define TWL4030_BCIAUTOAC BIT(0) -#define TWL4030_CGAIN BIT(5) -#define TWL4030_USBFASTMCHG BIT(2) -#define TWL4030_STS_VBUS BIT(7) -#define TWL4030_STS_USB_ID BIT(2) -#define TWL4030_BBCHEN BIT(4) -#define TWL4030_BBSEL_MASK 0x0c -#define TWL4030_BBSEL_2V5 0x00 -#define TWL4030_BBSEL_3V0 0x04 -#define TWL4030_BBSEL_3V1 0x08 -#define TWL4030_BBSEL_3V2 0x0c -#define TWL4030_BBISEL_MASK 0x03 -#define TWL4030_BBISEL_25uA 0x00 -#define TWL4030_BBISEL_150uA 0x01 -#define TWL4030_BBISEL_500uA 0x02 -#define TWL4030_BBISEL_1000uA 0x03 - -#define TWL4030_BATSTSPCHG BIT(2) -#define TWL4030_BATSTSMCHG BIT(6) - -/* BCI interrupts */ -#define TWL4030_WOVF BIT(0) /* Watchdog overflow */ -#define TWL4030_TMOVF BIT(1) /* Timer overflow */ -#define TWL4030_ICHGHIGH BIT(2) /* Battery charge current high */ -#define TWL4030_ICHGLOW BIT(3) /* Battery cc. low / FSM state change */ -#define TWL4030_ICHGEOC BIT(4) /* Battery current end-of-charge */ -#define TWL4030_TBATOR2 BIT(5) /* Battery temperature out of range 2 */ -#define TWL4030_TBATOR1 BIT(6) /* Battery temperature out of range 1 */ -#define TWL4030_BATSTS BIT(7) /* Battery status */ - -#define TWL4030_VBATLVL BIT(0) /* VBAT level */ -#define TWL4030_VBATOV BIT(1) /* VBAT overvoltage */ -#define TWL4030_VBUSOV BIT(2) /* VBUS overvoltage */ -#define TWL4030_ACCHGOV BIT(3) /* Ac charger overvoltage */ - -#define TWL4030_MSTATEC_USB BIT(4) -#define TWL4030_MSTATEC_AC BIT(5) -#define TWL4030_MSTATEC_MASK 0x0f -#define TWL4030_MSTATEC_QUICK1 0x02 -#define TWL4030_MSTATEC_QUICK7 0x07 -#define TWL4030_MSTATEC_COMPLETE1 0x0b -#define TWL4030_MSTATEC_COMPLETE4 0x0e - -/* - * If AC (Accessory Charger) voltage exceeds 4.5V (MADC 11) - * then AC is available. - */ -static inline int ac_available(struct iio_channel *channel_vac) -{ - int val, err; - - if (!channel_vac) - return 0; - - err = iio_read_channel_processed(channel_vac, &val); - if (err < 0) - return 0; - return val > 4500; -} - -static bool allow_usb; -module_param(allow_usb, bool, 0644); -MODULE_PARM_DESC(allow_usb, "Allow USB charge drawing default current"); - -struct twl4030_bci { - struct device *dev; - struct power_supply *ac; - struct power_supply *usb; - struct usb_phy *transceiver; - struct notifier_block usb_nb; - struct work_struct work; - int irq_chg; - int irq_bci; - int usb_enabled; - - /* - * ichg_* and *_cur values in uA. If any are 'large', we set - * CGAIN to '1' which doubles the range for half the - * precision. - */ - 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 -#define CHARGE_AUTO 1 -#define CHARGE_LINEAR 2 - - /* When setting the USB current we slowly increase the - * requested current until target is reached or the voltage - * drops below 4.75V. In the latter case we step back one - * step. - */ - unsigned int usb_cur_target; - struct delayed_work current_worker; -#define USB_CUR_STEP 20000 /* 20mA at a time */ -#define USB_MIN_VOLT 4750000 /* 4.75V */ -#define USB_CUR_DELAY msecs_to_jiffies(100) -#define USB_MAX_CURRENT 1700000 /* TWL4030 caps at 1.7A */ - - unsigned long event; -}; - -/* strings for 'usb_mode' values */ -static char *modes[] = { "off", "auto", "continuous" }; - -/* - * clear and set bits on an given register on a given module - */ -static int twl4030_clear_set(u8 mod_no, u8 clear, u8 set, u8 reg) -{ - u8 val = 0; - int ret; - - ret = twl_i2c_read_u8(mod_no, &val, reg); - if (ret) - return ret; - - val &= ~clear; - val |= set; - - return twl_i2c_write_u8(mod_no, val, reg); -} - -static int twl4030_bci_read(u8 reg, u8 *val) -{ - return twl_i2c_read_u8(TWL_MODULE_MAIN_CHARGE, val, reg); -} - -static int twl4030_clear_set_boot_bci(u8 clear, u8 set) -{ - return twl4030_clear_set(TWL_MODULE_PM_MASTER, clear, - TWL4030_CONFIG_DONE | TWL4030_BCIAUTOWEN | set, - TWL4030_PM_MASTER_BOOT_BCI); -} - -static int twl4030bci_read_adc_val(u8 reg) -{ - int ret, temp; - u8 val; - - /* read MSB */ - ret = twl4030_bci_read(reg + 1, &val); - if (ret) - return ret; - - temp = (int)(val & 0x03) << 8; - - /* read LSB */ - ret = twl4030_bci_read(reg, &val); - if (ret) - return ret; - - return temp | val; -} - -/* - * Check if Battery Pack was present - */ -static int twl4030_is_battery_present(struct twl4030_bci *bci) -{ - int ret; - u8 val = 0; - - /* Battery presence in Main charge? */ - ret = twl_i2c_read_u8(TWL_MODULE_MAIN_CHARGE, &val, TWL4030_BCIMFSTS3); - if (ret) - return ret; - if (val & TWL4030_BATSTSMCHG) - return 0; - - /* - * OK, It could be that bootloader did not enable main charger, - * pre-charge is h/w auto. So, Battery presence in Pre-charge? - */ - ret = twl_i2c_read_u8(TWL4030_MODULE_PRECHARGE, &val, - TWL4030_BCIMFSTS1); - if (ret) - return ret; - if (val & TWL4030_BATSTSPCHG) - return 0; - - return -ENODEV; -} - -/* - * TI provided formulas: - * CGAIN == 0: ICHG = (BCIICHG * 1.7) / (2^10 - 1) - 0.85 - * CGAIN == 1: ICHG = (BCIICHG * 3.4) / (2^10 - 1) - 1.7 - * Here we use integer approximation of: - * CGAIN == 0: val * 1.6618 - 0.85 * 1000 - * CGAIN == 1: (val * 1.6618 - 0.85 * 1000) * 2 - */ -/* - * convert twl register value for currents into uA - */ -static int regval2ua(int regval, bool cgain) -{ - if (cgain) - return (regval * 16618 - 8500 * 1000) / 5; - else - return (regval * 16618 - 8500 * 1000) / 10; -} - -/* - * convert uA currents into twl register value - */ -static int ua2regval(int ua, bool cgain) -{ - int ret; - if (cgain) - ua /= 2; - ret = (ua * 10 + 8500 * 1000) / 16618; - /* rounding problems */ - if (ret < 512) - ret = 512; - return ret; -} - -static int twl4030_charger_update_current(struct twl4030_bci *bci) -{ - int status; - int cur; - unsigned reg, cur_reg; - u8 bcictl1, oldreg, fullreg; - bool cgain = false; - u8 boot_bci; - - /* - * If AC (Accessory Charger) voltage exceeds 4.5V (MADC 11) - * and AC is enabled, set current for 'ac' - */ - if (ac_available(bci->channel_vac)) { - cur = bci->ac_cur; - bci->ac_is_active = true; - } else { - cur = bci->usb_cur; - bci->ac_is_active = false; - if (cur > bci->usb_cur_target) { - cur = bci->usb_cur_target; - bci->usb_cur = cur; - } - if (cur < bci->usb_cur_target) - schedule_delayed_work(&bci->current_worker, USB_CUR_DELAY); - } - - /* First, check thresholds and see if cgain is needed */ - if (bci->ichg_eoc >= 200000) - cgain = true; - if (bci->ichg_lo >= 400000) - cgain = true; - if (bci->ichg_hi >= 820000) - cgain = true; - if (cur > 852000) - cgain = true; - - status = twl4030_bci_read(TWL4030_BCICTL1, &bcictl1); - if (status < 0) - return status; - if (twl_i2c_read_u8(TWL_MODULE_PM_MASTER, &boot_bci, - TWL4030_PM_MASTER_BOOT_BCI) < 0) - boot_bci = 0; - boot_bci &= 7; - - if ((!!cgain) != !!(bcictl1 & TWL4030_CGAIN)) - /* Need to turn for charging while we change the - * CGAIN bit. Leave it off while everything is - * updated. - */ - twl4030_clear_set_boot_bci(boot_bci, 0); - - /* - * For ichg_eoc, the hardware only supports reg values matching - * 100XXXX000, and requires the XXXX be stored in the high nibble - * of TWL4030_BCIMFTH8. - */ - reg = ua2regval(bci->ichg_eoc, cgain); - if (reg > 0x278) - reg = 0x278; - if (reg < 0x200) - reg = 0x200; - reg = (reg >> 3) & 0xf; - fullreg = reg << 4; - - /* - * For ichg_lo, reg value must match 10XXXX0000. - * XXXX is stored in low nibble of TWL4030_BCIMFTH8. - */ - reg = ua2regval(bci->ichg_lo, cgain); - if (reg > 0x2F0) - reg = 0x2F0; - if (reg < 0x200) - reg = 0x200; - reg = (reg >> 4) & 0xf; - fullreg |= reg; - - /* ichg_eoc and ichg_lo live in same register */ - status = twl4030_bci_read(TWL4030_BCIMFTH8, &oldreg); - if (status < 0) - return status; - if (oldreg != fullreg) { - status = twl_i2c_write_u8(TWL_MODULE_MAIN_CHARGE, 0xF4, - TWL4030_BCIMFKEY); - if (status < 0) - return status; - twl_i2c_write_u8(TWL_MODULE_MAIN_CHARGE, - fullreg, TWL4030_BCIMFTH8); - } - - /* ichg_hi threshold must be 1XXXX01100 (I think) */ - reg = ua2regval(bci->ichg_hi, cgain); - if (reg > 0x3E0) - reg = 0x3E0; - if (reg < 0x200) - reg = 0x200; - fullreg = (reg >> 5) & 0xF; - fullreg <<= 4; - status = twl4030_bci_read(TWL4030_BCIMFTH9, &oldreg); - if (status < 0) - return status; - if ((oldreg & 0xF0) != fullreg) { - fullreg |= (oldreg & 0x0F); - status = twl_i2c_write_u8(TWL_MODULE_MAIN_CHARGE, 0xE7, - TWL4030_BCIMFKEY); - if (status < 0) - return status; - twl_i2c_write_u8(TWL_MODULE_MAIN_CHARGE, - fullreg, TWL4030_BCIMFTH9); - } - - /* - * And finally, set the current. This is stored in - * two registers. - */ - reg = ua2regval(cur, cgain); - /* we have only 10 bits */ - if (reg > 0x3ff) - reg = 0x3ff; - status = twl4030_bci_read(TWL4030_BCIIREF1, &oldreg); - if (status < 0) - return status; - cur_reg = oldreg; - status = twl4030_bci_read(TWL4030_BCIIREF2, &oldreg); - if (status < 0) - return status; - cur_reg |= oldreg << 8; - if (reg != oldreg) { - /* disable write protection for one write access for - * BCIIREF */ - status = twl_i2c_write_u8(TWL_MODULE_MAIN_CHARGE, 0xE7, - TWL4030_BCIMFKEY); - if (status < 0) - return status; - status = twl_i2c_write_u8(TWL_MODULE_MAIN_CHARGE, - (reg & 0x100) ? 3 : 2, - TWL4030_BCIIREF2); - if (status < 0) - return status; - /* disable write protection for one write access for - * BCIIREF */ - status = twl_i2c_write_u8(TWL_MODULE_MAIN_CHARGE, 0xE7, - TWL4030_BCIMFKEY); - if (status < 0) - return status; - status = twl_i2c_write_u8(TWL_MODULE_MAIN_CHARGE, - reg & 0xff, - TWL4030_BCIIREF1); - } - if ((!!cgain) != !!(bcictl1 & TWL4030_CGAIN)) { - /* Flip CGAIN and re-enable charging */ - bcictl1 ^= TWL4030_CGAIN; - twl_i2c_write_u8(TWL_MODULE_MAIN_CHARGE, - bcictl1, TWL4030_BCICTL1); - twl4030_clear_set_boot_bci(0, boot_bci); - } - return 0; -} - -static int twl4030_charger_get_current(void); - -static void twl4030_current_worker(struct work_struct *data) -{ - int v, curr; - int res; - struct twl4030_bci *bci = container_of(data, struct twl4030_bci, - current_worker.work); - - res = twl4030bci_read_adc_val(TWL4030_BCIVBUS); - if (res < 0) - v = 0; - else - /* BCIVBUS uses ADCIN8, 7/1023 V/step */ - v = res * 6843; - curr = twl4030_charger_get_current(); - - dev_dbg(bci->dev, "v=%d cur=%d limit=%d target=%d\n", v, curr, - bci->usb_cur, bci->usb_cur_target); - - if (v < USB_MIN_VOLT) { - /* Back up and stop adjusting. */ - bci->usb_cur -= USB_CUR_STEP; - bci->usb_cur_target = bci->usb_cur; - } else if (bci->usb_cur >= bci->usb_cur_target || - bci->usb_cur + USB_CUR_STEP > USB_MAX_CURRENT) { - /* Reached target and voltage is OK - stop */ - return; - } else { - bci->usb_cur += USB_CUR_STEP; - schedule_delayed_work(&bci->current_worker, USB_CUR_DELAY); - } - twl4030_charger_update_current(bci); -} - -/* - * Enable/Disable USB Charge functionality. - */ -static int twl4030_charger_enable_usb(struct twl4030_bci *bci, bool enable) -{ - int ret; - - if (bci->usb_mode == CHARGE_OFF) - enable = false; - if (enable && !IS_ERR_OR_NULL(bci->transceiver)) { - - twl4030_charger_update_current(bci); - - /* Need to keep phy powered */ - if (!bci->usb_enabled) { - pm_runtime_get_sync(bci->transceiver->dev); - bci->usb_enabled = 1; - } - - if (bci->usb_mode == CHARGE_AUTO) - /* forcing the field BCIAUTOUSB (BOOT_BCI[1]) to 1 */ - ret = twl4030_clear_set_boot_bci(0, TWL4030_BCIAUTOUSB); - - /* forcing USBFASTMCHG(BCIMFSTS4[2]) to 1 */ - ret = twl4030_clear_set(TWL_MODULE_MAIN_CHARGE, 0, - TWL4030_USBFASTMCHG, TWL4030_BCIMFSTS4); - if (bci->usb_mode == CHARGE_LINEAR) { - twl4030_clear_set_boot_bci(TWL4030_BCIAUTOAC|TWL4030_CVENAC, 0); - /* Watch dog key: WOVF acknowledge */ - ret = twl_i2c_write_u8(TWL_MODULE_MAIN_CHARGE, 0x33, - TWL4030_BCIWDKEY); - /* 0x24 + EKEY6: off mode */ - ret = twl_i2c_write_u8(TWL_MODULE_MAIN_CHARGE, 0x2a, - TWL4030_BCIMDKEY); - /* EKEY2: Linear charge: USB path */ - ret = twl_i2c_write_u8(TWL_MODULE_MAIN_CHARGE, 0x26, - TWL4030_BCIMDKEY); - /* WDKEY5: stop watchdog count */ - ret = twl_i2c_write_u8(TWL_MODULE_MAIN_CHARGE, 0xf3, - TWL4030_BCIWDKEY); - /* enable MFEN3 access */ - ret = twl_i2c_write_u8(TWL_MODULE_MAIN_CHARGE, 0x9c, - TWL4030_BCIMFKEY); - /* ICHGEOCEN - end-of-charge monitor (current < 80mA) - * (charging continues) - * ICHGLOWEN - current level monitor (charge continues) - * don't monitor over-current or heat save - */ - ret = twl_i2c_write_u8(TWL_MODULE_MAIN_CHARGE, 0xf0, - TWL4030_BCIMFEN3); - } - } else { - ret = twl4030_clear_set_boot_bci(TWL4030_BCIAUTOUSB, 0); - ret |= twl_i2c_write_u8(TWL_MODULE_MAIN_CHARGE, 0x2a, - TWL4030_BCIMDKEY); - if (bci->usb_enabled) { - pm_runtime_mark_last_busy(bci->transceiver->dev); - pm_runtime_put_autosuspend(bci->transceiver->dev); - bci->usb_enabled = 0; - } - bci->usb_cur = 0; - } - - return ret; -} - -/* - * Enable/Disable AC Charge funtionality. - */ -static int twl4030_charger_enable_ac(struct twl4030_bci *bci, bool enable) -{ - int ret; - - if (bci->ac_mode == CHARGE_OFF) - enable = false; - - if (enable) - ret = twl4030_clear_set_boot_bci(0, TWL4030_BCIAUTOAC); - else - ret = twl4030_clear_set_boot_bci(TWL4030_BCIAUTOAC, 0); - - return ret; -} - -/* - * Enable/Disable charging of Backup Battery. - */ -static int twl4030_charger_enable_backup(int uvolt, int uamp) -{ - int ret; - u8 flags; - - if (uvolt < 2500000 || - uamp < 25) { - /* disable charging of backup battery */ - ret = twl4030_clear_set(TWL_MODULE_PM_RECEIVER, - TWL4030_BBCHEN, 0, TWL4030_BB_CFG); - return ret; - } - - flags = TWL4030_BBCHEN; - if (uvolt >= 3200000) - flags |= TWL4030_BBSEL_3V2; - else if (uvolt >= 3100000) - flags |= TWL4030_BBSEL_3V1; - else if (uvolt >= 3000000) - flags |= TWL4030_BBSEL_3V0; - else - flags |= TWL4030_BBSEL_2V5; - - if (uamp >= 1000) - flags |= TWL4030_BBISEL_1000uA; - else if (uamp >= 500) - flags |= TWL4030_BBISEL_500uA; - else if (uamp >= 150) - flags |= TWL4030_BBISEL_150uA; - else - flags |= TWL4030_BBISEL_25uA; - - ret = twl4030_clear_set(TWL_MODULE_PM_RECEIVER, - TWL4030_BBSEL_MASK | TWL4030_BBISEL_MASK, - flags, - TWL4030_BB_CFG); - - return ret; -} - -/* - * TWL4030 CHG_PRES (AC charger presence) events - */ -static irqreturn_t twl4030_charger_interrupt(int irq, void *arg) -{ - struct twl4030_bci *bci = arg; - - dev_dbg(bci->dev, "CHG_PRES irq\n"); - /* reset current on each 'plug' event */ - bci->ac_cur = 500000; - twl4030_charger_update_current(bci); - power_supply_changed(bci->ac); - power_supply_changed(bci->usb); - - return IRQ_HANDLED; -} - -/* - * TWL4030 BCI monitoring events - */ -static irqreturn_t twl4030_bci_interrupt(int irq, void *arg) -{ - struct twl4030_bci *bci = arg; - u8 irqs1, irqs2; - int ret; - - ret = twl_i2c_read_u8(TWL4030_MODULE_INTERRUPTS, &irqs1, - TWL4030_INTERRUPTS_BCIISR1A); - if (ret < 0) - return IRQ_HANDLED; - - ret = twl_i2c_read_u8(TWL4030_MODULE_INTERRUPTS, &irqs2, - TWL4030_INTERRUPTS_BCIISR2A); - if (ret < 0) - return IRQ_HANDLED; - - dev_dbg(bci->dev, "BCI irq %02x %02x\n", irqs2, irqs1); - - if (irqs1 & (TWL4030_ICHGLOW | TWL4030_ICHGEOC)) { - /* charger state change, inform the core */ - power_supply_changed(bci->ac); - power_supply_changed(bci->usb); - } - twl4030_charger_update_current(bci); - - /* various monitoring events, for now we just log them here */ - if (irqs1 & (TWL4030_TBATOR2 | TWL4030_TBATOR1)) - dev_warn(bci->dev, "battery temperature out of range\n"); - - if (irqs1 & TWL4030_BATSTS) - dev_crit(bci->dev, "battery disconnected\n"); - - if (irqs2 & TWL4030_VBATOV) - dev_crit(bci->dev, "VBAT overvoltage\n"); - - if (irqs2 & TWL4030_VBUSOV) - dev_crit(bci->dev, "VBUS overvoltage\n"); - - if (irqs2 & TWL4030_ACCHGOV) - dev_crit(bci->dev, "Ac charger overvoltage\n"); - - return IRQ_HANDLED; -} - -/* - * Provide "max_current" attribute in sysfs. - */ -static ssize_t -twl4030_bci_max_current_store(struct device *dev, struct device_attribute *attr, - const char *buf, size_t n) -{ - struct twl4030_bci *bci = dev_get_drvdata(dev->parent); - int cur = 0; - int status = 0; - status = kstrtoint(buf, 10, &cur); - if (status) - return status; - if (cur < 0) - return -EINVAL; - if (dev == &bci->ac->dev) - bci->ac_cur = cur; - else - bci->usb_cur_target = cur; - - twl4030_charger_update_current(bci); - return n; -} - -/* - * sysfs max_current show - */ -static ssize_t twl4030_bci_max_current_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - int status = 0; - int cur = -1; - u8 bcictl1; - struct twl4030_bci *bci = dev_get_drvdata(dev->parent); - - if (dev == &bci->ac->dev) { - if (!bci->ac_is_active) - cur = bci->ac_cur; - } else { - if (bci->ac_is_active) - cur = bci->usb_cur_target; - } - if (cur < 0) { - cur = twl4030bci_read_adc_val(TWL4030_BCIIREF1); - if (cur < 0) - return cur; - status = twl4030_bci_read(TWL4030_BCICTL1, &bcictl1); - if (status < 0) - return status; - cur = regval2ua(cur, bcictl1 & TWL4030_CGAIN); - } - return scnprintf(buf, PAGE_SIZE, "%u\n", cur); -} - -static DEVICE_ATTR(max_current, 0644, twl4030_bci_max_current_show, - twl4030_bci_max_current_store); - -static void twl4030_bci_usb_work(struct work_struct *data) -{ - struct twl4030_bci *bci = container_of(data, struct twl4030_bci, work); - - switch (bci->event) { - case USB_EVENT_VBUS: - case USB_EVENT_CHARGER: - twl4030_charger_enable_usb(bci, true); - break; - case USB_EVENT_NONE: - twl4030_charger_enable_usb(bci, false); - break; - } -} - -static int twl4030_bci_usb_ncb(struct notifier_block *nb, unsigned long val, - void *priv) -{ - struct twl4030_bci *bci = container_of(nb, struct twl4030_bci, usb_nb); - - dev_dbg(bci->dev, "OTG notify %lu\n", val); - - /* reset current on each 'plug' event */ - if (allow_usb) - bci->usb_cur_target = 500000; - else - bci->usb_cur_target = 100000; - - bci->event = val; - schedule_work(&bci->work); - - return NOTIFY_OK; -} - -/* - * sysfs charger enabled store - */ -static ssize_t -twl4030_bci_mode_store(struct device *dev, struct device_attribute *attr, - const char *buf, size_t n) -{ - struct twl4030_bci *bci = dev_get_drvdata(dev->parent); - int mode; - int status; - - if (sysfs_streq(buf, modes[0])) - mode = 0; - else if (sysfs_streq(buf, modes[1])) - mode = 1; - else if (sysfs_streq(buf, modes[2])) - mode = 2; - else - return -EINVAL; - if (dev == &bci->ac->dev) { - if (mode == 2) - return -EINVAL; - twl4030_charger_enable_ac(bci, false); - bci->ac_mode = mode; - status = twl4030_charger_enable_ac(bci, true); - } else { - twl4030_charger_enable_usb(bci, false); - bci->usb_mode = mode; - status = twl4030_charger_enable_usb(bci, true); - } - return (status == 0) ? n : status; -} - -/* - * sysfs charger enabled show - */ -static ssize_t -twl4030_bci_mode_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - struct twl4030_bci *bci = dev_get_drvdata(dev->parent); - int len = 0; - int i; - int mode = bci->usb_mode; - - if (dev == &bci->ac->dev) - mode = bci->ac_mode; - - for (i = 0; i < ARRAY_SIZE(modes); i++) - if (mode == i) - len += snprintf(buf+len, PAGE_SIZE-len, - "[%s] ", modes[i]); - else - len += snprintf(buf+len, PAGE_SIZE-len, - "%s ", modes[i]); - buf[len-1] = '\n'; - return len; -} -static DEVICE_ATTR(mode, 0644, twl4030_bci_mode_show, - twl4030_bci_mode_store); - -static int twl4030_charger_get_current(void) -{ - int curr; - int ret; - u8 bcictl1; - - curr = twl4030bci_read_adc_val(TWL4030_BCIICHG); - if (curr < 0) - return curr; - - ret = twl4030_bci_read(TWL4030_BCICTL1, &bcictl1); - if (ret) - return ret; - - return regval2ua(curr, bcictl1 & TWL4030_CGAIN); -} - -/* - * Returns the main charge FSM state - * Or < 0 on failure. - */ -static int twl4030bci_state(struct twl4030_bci *bci) -{ - int ret; - u8 state; - - ret = twl4030_bci_read(TWL4030_BCIMSTATEC, &state); - if (ret) { - pr_err("twl4030_bci: error reading BCIMSTATEC\n"); - return ret; - } - - dev_dbg(bci->dev, "state: %02x\n", state); - - return state; -} - -static int twl4030_bci_state_to_status(int state) -{ - state &= TWL4030_MSTATEC_MASK; - if (TWL4030_MSTATEC_QUICK1 <= state && state <= TWL4030_MSTATEC_QUICK7) - return POWER_SUPPLY_STATUS_CHARGING; - else if (TWL4030_MSTATEC_COMPLETE1 <= state && - state <= TWL4030_MSTATEC_COMPLETE4) - return POWER_SUPPLY_STATUS_FULL; - else - return POWER_SUPPLY_STATUS_NOT_CHARGING; -} - -static int twl4030_bci_get_property(struct power_supply *psy, - enum power_supply_property psp, - union power_supply_propval *val) -{ - struct twl4030_bci *bci = dev_get_drvdata(psy->dev.parent); - int is_charging; - int state; - int ret; - - state = twl4030bci_state(bci); - if (state < 0) - return state; - - if (psy->desc->type == POWER_SUPPLY_TYPE_USB) - is_charging = state & TWL4030_MSTATEC_USB; - else - is_charging = state & TWL4030_MSTATEC_AC; - if (!is_charging) { - u8 s; - twl4030_bci_read(TWL4030_BCIMDEN, &s); - if (psy->desc->type == POWER_SUPPLY_TYPE_USB) - is_charging = s & 1; - else - is_charging = s & 2; - if (is_charging) - /* A little white lie */ - state = TWL4030_MSTATEC_QUICK1; - } - - switch (psp) { - case POWER_SUPPLY_PROP_STATUS: - if (is_charging) - val->intval = twl4030_bci_state_to_status(state); - else - val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; - break; - case POWER_SUPPLY_PROP_VOLTAGE_NOW: - /* charging must be active for meaningful result */ - if (!is_charging) - return -ENODATA; - if (psy->desc->type == POWER_SUPPLY_TYPE_USB) { - ret = twl4030bci_read_adc_val(TWL4030_BCIVBUS); - if (ret < 0) - return ret; - /* BCIVBUS uses ADCIN8, 7/1023 V/step */ - val->intval = ret * 6843; - } else { - ret = twl4030bci_read_adc_val(TWL4030_BCIVAC); - if (ret < 0) - return ret; - /* BCIVAC uses ADCIN11, 10/1023 V/step */ - val->intval = ret * 9775; - } - break; - case POWER_SUPPLY_PROP_CURRENT_NOW: - if (!is_charging) - return -ENODATA; - /* current measurement is shared between AC and USB */ - ret = twl4030_charger_get_current(); - if (ret < 0) - return ret; - val->intval = ret; - break; - case POWER_SUPPLY_PROP_ONLINE: - val->intval = is_charging && - twl4030_bci_state_to_status(state) != - POWER_SUPPLY_STATUS_NOT_CHARGING; - break; - default: - return -EINVAL; - } - - return 0; -} - -static enum power_supply_property twl4030_charger_props[] = { - POWER_SUPPLY_PROP_STATUS, - POWER_SUPPLY_PROP_ONLINE, - POWER_SUPPLY_PROP_VOLTAGE_NOW, - POWER_SUPPLY_PROP_CURRENT_NOW, -}; - -#ifdef CONFIG_OF -static const struct twl4030_bci_platform_data * -twl4030_bci_parse_dt(struct device *dev) -{ - struct device_node *np = dev->of_node; - struct twl4030_bci_platform_data *pdata; - u32 num; - - if (!np) - return NULL; - pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); - if (!pdata) - return pdata; - - if (of_property_read_u32(np, "ti,bb-uvolt", &num) == 0) - pdata->bb_uvolt = num; - if (of_property_read_u32(np, "ti,bb-uamp", &num) == 0) - pdata->bb_uamp = num; - return pdata; -} -#else -static inline const struct twl4030_bci_platform_data * -twl4030_bci_parse_dt(struct device *dev) -{ - return NULL; -} -#endif - -static const struct power_supply_desc twl4030_bci_ac_desc = { - .name = "twl4030_ac", - .type = POWER_SUPPLY_TYPE_MAINS, - .properties = twl4030_charger_props, - .num_properties = ARRAY_SIZE(twl4030_charger_props), - .get_property = twl4030_bci_get_property, -}; - -static const struct power_supply_desc twl4030_bci_usb_desc = { - .name = "twl4030_usb", - .type = POWER_SUPPLY_TYPE_USB, - .properties = twl4030_charger_props, - .num_properties = ARRAY_SIZE(twl4030_charger_props), - .get_property = twl4030_bci_get_property, -}; - -static int twl4030_bci_probe(struct platform_device *pdev) -{ - struct twl4030_bci *bci; - const struct twl4030_bci_platform_data *pdata = pdev->dev.platform_data; - int ret; - u32 reg; - - bci = devm_kzalloc(&pdev->dev, sizeof(*bci), GFP_KERNEL); - if (bci == NULL) - return -ENOMEM; - - if (!pdata) - pdata = twl4030_bci_parse_dt(&pdev->dev); - - bci->ichg_eoc = 80100; /* Stop charging when current drops to here */ - bci->ichg_lo = 241000; /* Low threshold */ - bci->ichg_hi = 500000; /* High threshold */ - bci->ac_cur = 500000; /* 500mA */ - if (allow_usb) - bci->usb_cur_target = 500000; /* 500mA */ - else - bci->usb_cur_target = 100000; /* 100mA */ - bci->usb_mode = CHARGE_AUTO; - bci->ac_mode = CHARGE_AUTO; - - bci->dev = &pdev->dev; - bci->irq_chg = platform_get_irq(pdev, 0); - bci->irq_bci = platform_get_irq(pdev, 1); - - /* Only proceed further *IF* battery is physically present */ - ret = twl4030_is_battery_present(bci); - if (ret) { - dev_crit(&pdev->dev, "Battery was not detected:%d\n", ret); - return ret; - } - - platform_set_drvdata(pdev, bci); - - bci->ac = devm_power_supply_register(&pdev->dev, &twl4030_bci_ac_desc, - NULL); - if (IS_ERR(bci->ac)) { - ret = PTR_ERR(bci->ac); - dev_err(&pdev->dev, "failed to register ac: %d\n", ret); - return ret; - } - - bci->usb = devm_power_supply_register(&pdev->dev, &twl4030_bci_usb_desc, - NULL); - if (IS_ERR(bci->usb)) { - ret = PTR_ERR(bci->usb); - dev_err(&pdev->dev, "failed to register usb: %d\n", ret); - return ret; - } - - ret = devm_request_threaded_irq(&pdev->dev, bci->irq_chg, NULL, - twl4030_charger_interrupt, IRQF_ONESHOT, pdev->name, - bci); - if (ret < 0) { - dev_err(&pdev->dev, "could not request irq %d, status %d\n", - bci->irq_chg, ret); - return ret; - } - - ret = devm_request_threaded_irq(&pdev->dev, bci->irq_bci, NULL, - twl4030_bci_interrupt, IRQF_ONESHOT, pdev->name, bci); - if (ret < 0) { - dev_err(&pdev->dev, "could not request irq %d, status %d\n", - bci->irq_bci, ret); - 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); - - bci->usb_nb.notifier_call = twl4030_bci_usb_ncb; - if (bci->dev->of_node) { - struct device_node *phynode; - - phynode = of_find_compatible_node(bci->dev->of_node->parent, - NULL, "ti,twl4030-usb"); - if (phynode) - bci->transceiver = devm_usb_get_phy_by_node( - bci->dev, phynode, &bci->usb_nb); - } - - /* Enable interrupts now. */ - reg = ~(u32)(TWL4030_ICHGLOW | TWL4030_ICHGEOC | TWL4030_TBATOR2 | - TWL4030_TBATOR1 | TWL4030_BATSTS); - ret = twl_i2c_write_u8(TWL4030_MODULE_INTERRUPTS, reg, - TWL4030_INTERRUPTS_BCIIMR1A); - if (ret < 0) { - dev_err(&pdev->dev, "failed to unmask interrupts: %d\n", ret); - goto fail; - } - - reg = ~(u32)(TWL4030_VBATOV | TWL4030_VBUSOV | TWL4030_ACCHGOV); - ret = twl_i2c_write_u8(TWL4030_MODULE_INTERRUPTS, reg, - TWL4030_INTERRUPTS_BCIIMR2A); - if (ret < 0) - dev_warn(&pdev->dev, "failed to unmask interrupts: %d\n", ret); - - twl4030_charger_update_current(bci); - if (device_create_file(&bci->usb->dev, &dev_attr_max_current)) - dev_warn(&pdev->dev, "could not create sysfs file\n"); - if (device_create_file(&bci->usb->dev, &dev_attr_mode)) - dev_warn(&pdev->dev, "could not create sysfs file\n"); - if (device_create_file(&bci->ac->dev, &dev_attr_mode)) - dev_warn(&pdev->dev, "could not create sysfs file\n"); - if (device_create_file(&bci->ac->dev, &dev_attr_max_current)) - dev_warn(&pdev->dev, "could not create sysfs file\n"); - - twl4030_charger_enable_ac(bci, true); - if (!IS_ERR_OR_NULL(bci->transceiver)) - twl4030_bci_usb_ncb(&bci->usb_nb, - bci->transceiver->last_event, - NULL); - else - twl4030_charger_enable_usb(bci, false); - if (pdata) - twl4030_charger_enable_backup(pdata->bb_uvolt, - pdata->bb_uamp); - else - 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) -{ - struct twl4030_bci *bci = platform_get_drvdata(pdev); - - twl4030_charger_enable_ac(bci, false); - twl4030_charger_enable_usb(bci, false); - twl4030_charger_enable_backup(0, 0); - - iio_channel_release(bci->channel_vac); - - device_remove_file(&bci->usb->dev, &dev_attr_max_current); - device_remove_file(&bci->usb->dev, &dev_attr_mode); - device_remove_file(&bci->ac->dev, &dev_attr_max_current); - device_remove_file(&bci->ac->dev, &dev_attr_mode); - /* mask interrupts */ - twl_i2c_write_u8(TWL4030_MODULE_INTERRUPTS, 0xff, - TWL4030_INTERRUPTS_BCIIMR1A); - twl_i2c_write_u8(TWL4030_MODULE_INTERRUPTS, 0xff, - TWL4030_INTERRUPTS_BCIIMR2A); - - return 0; -} - -static const struct of_device_id twl_bci_of_match[] = { - {.compatible = "ti,twl4030-bci", }, - { } -}; -MODULE_DEVICE_TABLE(of, twl_bci_of_match); - -static struct platform_driver twl4030_bci_driver = { - .probe = twl4030_bci_probe, - .driver = { - .name = "twl4030_bci", - .of_match_table = of_match_ptr(twl_bci_of_match), - }, - .remove = __exit_p(twl4030_bci_remove), -}; -module_platform_driver(twl4030_bci_driver); - -MODULE_AUTHOR("Gražvydas Ignotas"); -MODULE_DESCRIPTION("TWL4030 Battery Charger Interface driver"); -MODULE_LICENSE("GPL"); -MODULE_ALIAS("platform:twl4030_bci"); diff --git a/drivers/power/twl4030_madc_battery.c b/drivers/power/twl4030_madc_battery.c deleted file mode 100644 index f5817e422d64..000000000000 --- a/drivers/power/twl4030_madc_battery.c +++ /dev/null @@ -1,278 +0,0 @@ -/* - * Dumb driver for LiIon batteries using TWL4030 madc. - * - * Copyright 2013 Golden Delicious Computers - * Lukas Märdian - * - * Based on dumb driver for gta01 battery - * Copyright 2009 Openmoko, Inc - * Balaji Rao - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -struct twl4030_madc_battery { - struct power_supply *psy; - struct twl4030_madc_bat_platform_data *pdata; - struct iio_channel *channel_temp; - struct iio_channel *channel_ichg; - struct iio_channel *channel_vbat; -}; - -static enum power_supply_property twl4030_madc_bat_props[] = { - POWER_SUPPLY_PROP_PRESENT, - POWER_SUPPLY_PROP_STATUS, - POWER_SUPPLY_PROP_TECHNOLOGY, - POWER_SUPPLY_PROP_VOLTAGE_NOW, - POWER_SUPPLY_PROP_CURRENT_NOW, - POWER_SUPPLY_PROP_CAPACITY, - POWER_SUPPLY_PROP_CHARGE_FULL, - POWER_SUPPLY_PROP_CHARGE_NOW, - POWER_SUPPLY_PROP_TEMP, - POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW, -}; - -static int madc_read(struct iio_channel *channel) -{ - int val, err; - err = iio_read_channel_processed(channel, &val); - if (err < 0) - return err; - - return val; -} - -static int twl4030_madc_bat_get_charging_status(struct twl4030_madc_battery *bt) -{ - return (madc_read(bt->channel_ichg) > 0) ? 1 : 0; -} - -static int twl4030_madc_bat_get_voltage(struct twl4030_madc_battery *bt) -{ - return madc_read(bt->channel_vbat); -} - -static int twl4030_madc_bat_get_current(struct twl4030_madc_battery *bt) -{ - return madc_read(bt->channel_ichg) * 1000; -} - -static int twl4030_madc_bat_get_temp(struct twl4030_madc_battery *bt) -{ - return madc_read(bt->channel_temp) * 10; -} - -static int twl4030_madc_bat_voltscale(struct twl4030_madc_battery *bat, - int volt) -{ - struct twl4030_madc_bat_calibration *calibration; - int i, res = 0; - - /* choose charging curve */ - if (twl4030_madc_bat_get_charging_status(bat)) - calibration = bat->pdata->charging; - else - calibration = bat->pdata->discharging; - - if (volt > calibration[0].voltage) { - res = calibration[0].level; - } else { - for (i = 0; calibration[i+1].voltage >= 0; i++) { - if (volt <= calibration[i].voltage && - volt >= calibration[i+1].voltage) { - /* interval found - interpolate within range */ - res = calibration[i].level - - ((calibration[i].voltage - volt) * - (calibration[i].level - - calibration[i+1].level)) / - (calibration[i].voltage - - calibration[i+1].voltage); - break; - } - } - } - return res; -} - -static int twl4030_madc_bat_get_property(struct power_supply *psy, - enum power_supply_property psp, - union power_supply_propval *val) -{ - struct twl4030_madc_battery *bat = power_supply_get_drvdata(psy); - - switch (psp) { - case POWER_SUPPLY_PROP_STATUS: - if (twl4030_madc_bat_voltscale(bat, - twl4030_madc_bat_get_voltage(bat)) > 95) - val->intval = POWER_SUPPLY_STATUS_FULL; - else { - if (twl4030_madc_bat_get_charging_status(bat)) - val->intval = POWER_SUPPLY_STATUS_CHARGING; - else - val->intval = POWER_SUPPLY_STATUS_DISCHARGING; - } - break; - case POWER_SUPPLY_PROP_VOLTAGE_NOW: - val->intval = twl4030_madc_bat_get_voltage(bat) * 1000; - break; - case POWER_SUPPLY_PROP_TECHNOLOGY: - val->intval = POWER_SUPPLY_TECHNOLOGY_LION; - break; - case POWER_SUPPLY_PROP_CURRENT_NOW: - val->intval = twl4030_madc_bat_get_current(bat); - break; - case POWER_SUPPLY_PROP_PRESENT: - /* assume battery is always present */ - val->intval = 1; - break; - case POWER_SUPPLY_PROP_CHARGE_NOW: { - int percent = twl4030_madc_bat_voltscale(bat, - twl4030_madc_bat_get_voltage(bat)); - val->intval = (percent * bat->pdata->capacity) / 100; - break; - } - case POWER_SUPPLY_PROP_CAPACITY: - val->intval = twl4030_madc_bat_voltscale(bat, - twl4030_madc_bat_get_voltage(bat)); - break; - case POWER_SUPPLY_PROP_CHARGE_FULL: - val->intval = bat->pdata->capacity; - break; - case POWER_SUPPLY_PROP_TEMP: - val->intval = twl4030_madc_bat_get_temp(bat); - break; - case POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW: { - int percent = twl4030_madc_bat_voltscale(bat, - twl4030_madc_bat_get_voltage(bat)); - /* in mAh */ - int chg = (percent * (bat->pdata->capacity/1000))/100; - - /* assume discharge with 400 mA (ca. 1.5W) */ - val->intval = (3600l * chg) / 400; - break; - } - default: - return -EINVAL; - } - - return 0; -} - -static void twl4030_madc_bat_ext_changed(struct power_supply *psy) -{ - power_supply_changed(psy); -} - -static const struct power_supply_desc twl4030_madc_bat_desc = { - .name = "twl4030_battery", - .type = POWER_SUPPLY_TYPE_BATTERY, - .properties = twl4030_madc_bat_props, - .num_properties = ARRAY_SIZE(twl4030_madc_bat_props), - .get_property = twl4030_madc_bat_get_property, - .external_power_changed = twl4030_madc_bat_ext_changed, - -}; - -static int twl4030_cmp(const void *a, const void *b) -{ - return ((struct twl4030_madc_bat_calibration *)b)->voltage - - ((struct twl4030_madc_bat_calibration *)a)->voltage; -} - -static int twl4030_madc_battery_probe(struct platform_device *pdev) -{ - struct twl4030_madc_battery *twl4030_madc_bat; - struct twl4030_madc_bat_platform_data *pdata = pdev->dev.platform_data; - struct power_supply_config psy_cfg = {}; - int ret = 0; - - twl4030_madc_bat = devm_kzalloc(&pdev->dev, sizeof(*twl4030_madc_bat), - GFP_KERNEL); - if (!twl4030_madc_bat) - return -ENOMEM; - - twl4030_madc_bat->channel_temp = iio_channel_get(&pdev->dev, "temp"); - if (IS_ERR(twl4030_madc_bat->channel_temp)) { - ret = PTR_ERR(twl4030_madc_bat->channel_temp); - goto err; - } - - twl4030_madc_bat->channel_ichg = iio_channel_get(&pdev->dev, "ichg"); - if (IS_ERR(twl4030_madc_bat->channel_ichg)) { - ret = PTR_ERR(twl4030_madc_bat->channel_ichg); - goto err_temp; - } - - twl4030_madc_bat->channel_vbat = iio_channel_get(&pdev->dev, "vbat"); - if (IS_ERR(twl4030_madc_bat->channel_vbat)) { - ret = PTR_ERR(twl4030_madc_bat->channel_vbat); - goto err_ichg; - } - - /* sort charging and discharging calibration data */ - sort(pdata->charging, pdata->charging_size, - sizeof(struct twl4030_madc_bat_calibration), - twl4030_cmp, NULL); - sort(pdata->discharging, pdata->discharging_size, - sizeof(struct twl4030_madc_bat_calibration), - twl4030_cmp, NULL); - - twl4030_madc_bat->pdata = pdata; - platform_set_drvdata(pdev, twl4030_madc_bat); - psy_cfg.drv_data = twl4030_madc_bat; - twl4030_madc_bat->psy = power_supply_register(&pdev->dev, - &twl4030_madc_bat_desc, - &psy_cfg); - if (IS_ERR(twl4030_madc_bat->psy)) { - ret = PTR_ERR(twl4030_madc_bat->psy); - goto err_vbat; - } - - return 0; - -err_vbat: - iio_channel_release(twl4030_madc_bat->channel_vbat); -err_ichg: - iio_channel_release(twl4030_madc_bat->channel_ichg); -err_temp: - iio_channel_release(twl4030_madc_bat->channel_temp); -err: - return ret; -} - -static int twl4030_madc_battery_remove(struct platform_device *pdev) -{ - struct twl4030_madc_battery *bat = platform_get_drvdata(pdev); - - power_supply_unregister(bat->psy); - - iio_channel_release(bat->channel_vbat); - iio_channel_release(bat->channel_ichg); - iio_channel_release(bat->channel_temp); - - return 0; -} - -static struct platform_driver twl4030_madc_battery_driver = { - .driver = { - .name = "twl4030_madc_battery", - }, - .probe = twl4030_madc_battery_probe, - .remove = twl4030_madc_battery_remove, -}; -module_platform_driver(twl4030_madc_battery_driver); - -MODULE_LICENSE("GPL"); -MODULE_AUTHOR("Lukas Märdian "); -MODULE_DESCRIPTION("twl4030_madc battery driver"); -MODULE_ALIAS("platform:twl4030_madc_battery"); diff --git a/drivers/power/wm831x_backup.c b/drivers/power/wm831x_backup.c deleted file mode 100644 index 2e33109ca8c7..000000000000 --- a/drivers/power/wm831x_backup.c +++ /dev/null @@ -1,225 +0,0 @@ -/* - * Backup battery driver for Wolfson Microelectronics wm831x PMICs - * - * Copyright 2009 Wolfson Microelectronics PLC. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - */ - -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -struct wm831x_backup { - struct wm831x *wm831x; - struct power_supply *backup; - struct power_supply_desc backup_desc; - char name[20]; -}; - -static int wm831x_backup_read_voltage(struct wm831x *wm831x, - enum wm831x_auxadc src, - union power_supply_propval *val) -{ - int ret; - - ret = wm831x_auxadc_read_uv(wm831x, src); - if (ret >= 0) - val->intval = ret; - - return ret; -} - -/********************************************************************* - * Backup supply properties - *********************************************************************/ - -static void wm831x_config_backup(struct wm831x *wm831x) -{ - struct wm831x_pdata *wm831x_pdata = wm831x->dev->platform_data; - struct wm831x_backup_pdata *pdata; - int ret, reg; - - if (!wm831x_pdata || !wm831x_pdata->backup) { - dev_warn(wm831x->dev, - "No backup battery charger configuration\n"); - return; - } - - pdata = wm831x_pdata->backup; - - reg = 0; - - if (pdata->charger_enable) - reg |= WM831X_BKUP_CHG_ENA | WM831X_BKUP_BATT_DET_ENA; - if (pdata->no_constant_voltage) - reg |= WM831X_BKUP_CHG_MODE; - - switch (pdata->vlim) { - case 2500: - break; - case 3100: - reg |= WM831X_BKUP_CHG_VLIM; - break; - default: - dev_err(wm831x->dev, "Invalid backup voltage limit %dmV\n", - pdata->vlim); - } - - switch (pdata->ilim) { - case 100: - break; - case 200: - reg |= 1; - break; - case 300: - reg |= 2; - break; - case 400: - reg |= 3; - break; - default: - dev_err(wm831x->dev, "Invalid backup current limit %duA\n", - pdata->ilim); - } - - ret = wm831x_reg_unlock(wm831x); - if (ret != 0) { - dev_err(wm831x->dev, "Failed to unlock registers: %d\n", ret); - return; - } - - ret = wm831x_set_bits(wm831x, WM831X_BACKUP_CHARGER_CONTROL, - WM831X_BKUP_CHG_ENA_MASK | - WM831X_BKUP_CHG_MODE_MASK | - WM831X_BKUP_BATT_DET_ENA_MASK | - WM831X_BKUP_CHG_VLIM_MASK | - WM831X_BKUP_CHG_ILIM_MASK, - reg); - if (ret != 0) - dev_err(wm831x->dev, - "Failed to set backup charger config: %d\n", ret); - - wm831x_reg_lock(wm831x); -} - -static int wm831x_backup_get_prop(struct power_supply *psy, - enum power_supply_property psp, - union power_supply_propval *val) -{ - struct wm831x_backup *devdata = dev_get_drvdata(psy->dev.parent); - struct wm831x *wm831x = devdata->wm831x; - int ret = 0; - - ret = wm831x_reg_read(wm831x, WM831X_BACKUP_CHARGER_CONTROL); - if (ret < 0) - return ret; - - switch (psp) { - case POWER_SUPPLY_PROP_STATUS: - if (ret & WM831X_BKUP_CHG_STS) - val->intval = POWER_SUPPLY_STATUS_CHARGING; - else - val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; - break; - - case POWER_SUPPLY_PROP_VOLTAGE_NOW: - ret = wm831x_backup_read_voltage(wm831x, WM831X_AUX_BKUP_BATT, - val); - break; - - case POWER_SUPPLY_PROP_PRESENT: - if (ret & WM831X_BKUP_CHG_STS) - val->intval = 1; - else - val->intval = 0; - break; - - default: - ret = -EINVAL; - break; - } - - return ret; -} - -static enum power_supply_property wm831x_backup_props[] = { - POWER_SUPPLY_PROP_STATUS, - POWER_SUPPLY_PROP_VOLTAGE_NOW, - POWER_SUPPLY_PROP_PRESENT, -}; - -/********************************************************************* - * Initialisation - *********************************************************************/ - -static int wm831x_backup_probe(struct platform_device *pdev) -{ - struct wm831x *wm831x = dev_get_drvdata(pdev->dev.parent); - struct wm831x_pdata *wm831x_pdata = wm831x->dev->platform_data; - struct wm831x_backup *devdata; - - devdata = devm_kzalloc(&pdev->dev, sizeof(struct wm831x_backup), - GFP_KERNEL); - if (devdata == NULL) - return -ENOMEM; - - devdata->wm831x = wm831x; - platform_set_drvdata(pdev, devdata); - - /* We ignore configuration failures since we can still read - * back the status without enabling the charger (which may - * already be enabled anyway). - */ - wm831x_config_backup(wm831x); - - if (wm831x_pdata && wm831x_pdata->wm831x_num) - snprintf(devdata->name, sizeof(devdata->name), - "wm831x-backup.%d", wm831x_pdata->wm831x_num); - else - snprintf(devdata->name, sizeof(devdata->name), - "wm831x-backup"); - - devdata->backup_desc.name = devdata->name; - devdata->backup_desc.type = POWER_SUPPLY_TYPE_BATTERY; - devdata->backup_desc.properties = wm831x_backup_props; - devdata->backup_desc.num_properties = ARRAY_SIZE(wm831x_backup_props); - devdata->backup_desc.get_property = wm831x_backup_get_prop; - devdata->backup = power_supply_register(&pdev->dev, - &devdata->backup_desc, NULL); - - return PTR_ERR_OR_ZERO(devdata->backup); -} - -static int wm831x_backup_remove(struct platform_device *pdev) -{ - struct wm831x_backup *devdata = platform_get_drvdata(pdev); - - power_supply_unregister(devdata->backup); - - return 0; -} - -static struct platform_driver wm831x_backup_driver = { - .probe = wm831x_backup_probe, - .remove = wm831x_backup_remove, - .driver = { - .name = "wm831x-backup", - }, -}; - -module_platform_driver(wm831x_backup_driver); - -MODULE_DESCRIPTION("Backup battery charger driver for WM831x PMICs"); -MODULE_AUTHOR("Mark Brown "); -MODULE_LICENSE("GPL"); -MODULE_ALIAS("platform:wm831x-backup"); diff --git a/drivers/power/wm831x_power.c b/drivers/power/wm831x_power.c deleted file mode 100644 index 7082301da945..000000000000 --- a/drivers/power/wm831x_power.c +++ /dev/null @@ -1,673 +0,0 @@ -/* - * PMU driver for Wolfson Microelectronics wm831x PMICs - * - * Copyright 2009 Wolfson Microelectronics PLC. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - */ - -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -struct wm831x_power { - struct wm831x *wm831x; - struct power_supply *wall; - struct power_supply *usb; - struct power_supply *battery; - struct power_supply_desc wall_desc; - struct power_supply_desc usb_desc; - struct power_supply_desc battery_desc; - char wall_name[20]; - char usb_name[20]; - char battery_name[20]; - bool have_battery; -}; - -static int wm831x_power_check_online(struct wm831x *wm831x, int supply, - union power_supply_propval *val) -{ - int ret; - - ret = wm831x_reg_read(wm831x, WM831X_SYSTEM_STATUS); - if (ret < 0) - return ret; - - if (ret & supply) - val->intval = 1; - else - val->intval = 0; - - return 0; -} - -static int wm831x_power_read_voltage(struct wm831x *wm831x, - enum wm831x_auxadc src, - union power_supply_propval *val) -{ - int ret; - - ret = wm831x_auxadc_read_uv(wm831x, src); - if (ret >= 0) - val->intval = ret; - - return ret; -} - -/********************************************************************* - * WALL Power - *********************************************************************/ -static int wm831x_wall_get_prop(struct power_supply *psy, - enum power_supply_property psp, - union power_supply_propval *val) -{ - struct wm831x_power *wm831x_power = dev_get_drvdata(psy->dev.parent); - struct wm831x *wm831x = wm831x_power->wm831x; - int ret = 0; - - switch (psp) { - case POWER_SUPPLY_PROP_ONLINE: - ret = wm831x_power_check_online(wm831x, WM831X_PWR_WALL, val); - break; - case POWER_SUPPLY_PROP_VOLTAGE_NOW: - ret = wm831x_power_read_voltage(wm831x, WM831X_AUX_WALL, val); - break; - default: - ret = -EINVAL; - break; - } - - return ret; -} - -static enum power_supply_property wm831x_wall_props[] = { - POWER_SUPPLY_PROP_ONLINE, - POWER_SUPPLY_PROP_VOLTAGE_NOW, -}; - -/********************************************************************* - * USB Power - *********************************************************************/ -static int wm831x_usb_get_prop(struct power_supply *psy, - enum power_supply_property psp, - union power_supply_propval *val) -{ - struct wm831x_power *wm831x_power = dev_get_drvdata(psy->dev.parent); - struct wm831x *wm831x = wm831x_power->wm831x; - int ret = 0; - - switch (psp) { - case POWER_SUPPLY_PROP_ONLINE: - ret = wm831x_power_check_online(wm831x, WM831X_PWR_USB, val); - break; - case POWER_SUPPLY_PROP_VOLTAGE_NOW: - ret = wm831x_power_read_voltage(wm831x, WM831X_AUX_USB, val); - break; - default: - ret = -EINVAL; - break; - } - - return ret; -} - -static enum power_supply_property wm831x_usb_props[] = { - POWER_SUPPLY_PROP_ONLINE, - POWER_SUPPLY_PROP_VOLTAGE_NOW, -}; - -/********************************************************************* - * Battery properties - *********************************************************************/ - -struct chg_map { - int val; - int reg_val; -}; - -static struct chg_map trickle_ilims[] = { - { 50, 0 << WM831X_CHG_TRKL_ILIM_SHIFT }, - { 100, 1 << WM831X_CHG_TRKL_ILIM_SHIFT }, - { 150, 2 << WM831X_CHG_TRKL_ILIM_SHIFT }, - { 200, 3 << WM831X_CHG_TRKL_ILIM_SHIFT }, -}; - -static struct chg_map vsels[] = { - { 4050, 0 << WM831X_CHG_VSEL_SHIFT }, - { 4100, 1 << WM831X_CHG_VSEL_SHIFT }, - { 4150, 2 << WM831X_CHG_VSEL_SHIFT }, - { 4200, 3 << WM831X_CHG_VSEL_SHIFT }, -}; - -static struct chg_map fast_ilims[] = { - { 0, 0 << WM831X_CHG_FAST_ILIM_SHIFT }, - { 50, 1 << WM831X_CHG_FAST_ILIM_SHIFT }, - { 100, 2 << WM831X_CHG_FAST_ILIM_SHIFT }, - { 150, 3 << WM831X_CHG_FAST_ILIM_SHIFT }, - { 200, 4 << WM831X_CHG_FAST_ILIM_SHIFT }, - { 250, 5 << WM831X_CHG_FAST_ILIM_SHIFT }, - { 300, 6 << WM831X_CHG_FAST_ILIM_SHIFT }, - { 350, 7 << WM831X_CHG_FAST_ILIM_SHIFT }, - { 400, 8 << WM831X_CHG_FAST_ILIM_SHIFT }, - { 450, 9 << WM831X_CHG_FAST_ILIM_SHIFT }, - { 500, 10 << WM831X_CHG_FAST_ILIM_SHIFT }, - { 600, 11 << WM831X_CHG_FAST_ILIM_SHIFT }, - { 700, 12 << WM831X_CHG_FAST_ILIM_SHIFT }, - { 800, 13 << WM831X_CHG_FAST_ILIM_SHIFT }, - { 900, 14 << WM831X_CHG_FAST_ILIM_SHIFT }, - { 1000, 15 << WM831X_CHG_FAST_ILIM_SHIFT }, -}; - -static struct chg_map eoc_iterms[] = { - { 20, 0 << WM831X_CHG_ITERM_SHIFT }, - { 30, 1 << WM831X_CHG_ITERM_SHIFT }, - { 40, 2 << WM831X_CHG_ITERM_SHIFT }, - { 50, 3 << WM831X_CHG_ITERM_SHIFT }, - { 60, 4 << WM831X_CHG_ITERM_SHIFT }, - { 70, 5 << WM831X_CHG_ITERM_SHIFT }, - { 80, 6 << WM831X_CHG_ITERM_SHIFT }, - { 90, 7 << WM831X_CHG_ITERM_SHIFT }, -}; - -static struct chg_map chg_times[] = { - { 60, 0 << WM831X_CHG_TIME_SHIFT }, - { 90, 1 << WM831X_CHG_TIME_SHIFT }, - { 120, 2 << WM831X_CHG_TIME_SHIFT }, - { 150, 3 << WM831X_CHG_TIME_SHIFT }, - { 180, 4 << WM831X_CHG_TIME_SHIFT }, - { 210, 5 << WM831X_CHG_TIME_SHIFT }, - { 240, 6 << WM831X_CHG_TIME_SHIFT }, - { 270, 7 << WM831X_CHG_TIME_SHIFT }, - { 300, 8 << WM831X_CHG_TIME_SHIFT }, - { 330, 9 << WM831X_CHG_TIME_SHIFT }, - { 360, 10 << WM831X_CHG_TIME_SHIFT }, - { 390, 11 << WM831X_CHG_TIME_SHIFT }, - { 420, 12 << WM831X_CHG_TIME_SHIFT }, - { 450, 13 << WM831X_CHG_TIME_SHIFT }, - { 480, 14 << WM831X_CHG_TIME_SHIFT }, - { 510, 15 << WM831X_CHG_TIME_SHIFT }, -}; - -static void wm831x_battey_apply_config(struct wm831x *wm831x, - struct chg_map *map, int count, int val, - int *reg, const char *name, - const char *units) -{ - int i; - - for (i = 0; i < count; i++) - if (val == map[i].val) - break; - if (i == count) { - dev_err(wm831x->dev, "Invalid %s %d%s\n", - name, val, units); - } else { - *reg |= map[i].reg_val; - dev_dbg(wm831x->dev, "Set %s of %d%s\n", name, val, units); - } -} - -static void wm831x_config_battery(struct wm831x *wm831x) -{ - struct wm831x_pdata *wm831x_pdata = wm831x->dev->platform_data; - struct wm831x_battery_pdata *pdata; - int ret, reg1, reg2; - - if (!wm831x_pdata || !wm831x_pdata->battery) { - dev_warn(wm831x->dev, - "No battery charger configuration\n"); - return; - } - - pdata = wm831x_pdata->battery; - - reg1 = 0; - reg2 = 0; - - if (!pdata->enable) { - dev_info(wm831x->dev, "Battery charger disabled\n"); - return; - } - - reg1 |= WM831X_CHG_ENA; - if (pdata->off_mask) - reg2 |= WM831X_CHG_OFF_MSK; - if (pdata->fast_enable) - reg1 |= WM831X_CHG_FAST; - - wm831x_battey_apply_config(wm831x, trickle_ilims, - ARRAY_SIZE(trickle_ilims), - pdata->trickle_ilim, ®2, - "trickle charge current limit", "mA"); - - wm831x_battey_apply_config(wm831x, vsels, ARRAY_SIZE(vsels), - pdata->vsel, ®2, - "target voltage", "mV"); - - wm831x_battey_apply_config(wm831x, fast_ilims, ARRAY_SIZE(fast_ilims), - pdata->fast_ilim, ®2, - "fast charge current limit", "mA"); - - wm831x_battey_apply_config(wm831x, eoc_iterms, ARRAY_SIZE(eoc_iterms), - pdata->eoc_iterm, ®1, - "end of charge current threshold", "mA"); - - wm831x_battey_apply_config(wm831x, chg_times, ARRAY_SIZE(chg_times), - pdata->timeout, ®2, - "charger timeout", "min"); - - ret = wm831x_reg_unlock(wm831x); - if (ret != 0) { - dev_err(wm831x->dev, "Failed to unlock registers: %d\n", ret); - return; - } - - ret = wm831x_set_bits(wm831x, WM831X_CHARGER_CONTROL_1, - WM831X_CHG_ENA_MASK | - WM831X_CHG_FAST_MASK | - WM831X_CHG_ITERM_MASK, - reg1); - if (ret != 0) - dev_err(wm831x->dev, "Failed to set charger control 1: %d\n", - ret); - - ret = wm831x_set_bits(wm831x, WM831X_CHARGER_CONTROL_2, - WM831X_CHG_OFF_MSK | - WM831X_CHG_TIME_MASK | - WM831X_CHG_FAST_ILIM_MASK | - WM831X_CHG_TRKL_ILIM_MASK | - WM831X_CHG_VSEL_MASK, - reg2); - if (ret != 0) - dev_err(wm831x->dev, "Failed to set charger control 2: %d\n", - ret); - - wm831x_reg_lock(wm831x); -} - -static int wm831x_bat_check_status(struct wm831x *wm831x, int *status) -{ - int ret; - - ret = wm831x_reg_read(wm831x, WM831X_SYSTEM_STATUS); - if (ret < 0) - return ret; - - if (ret & WM831X_PWR_SRC_BATT) { - *status = POWER_SUPPLY_STATUS_DISCHARGING; - return 0; - } - - ret = wm831x_reg_read(wm831x, WM831X_CHARGER_STATUS); - if (ret < 0) - return ret; - - switch (ret & WM831X_CHG_STATE_MASK) { - case WM831X_CHG_STATE_OFF: - *status = POWER_SUPPLY_STATUS_NOT_CHARGING; - break; - case WM831X_CHG_STATE_TRICKLE: - case WM831X_CHG_STATE_FAST: - *status = POWER_SUPPLY_STATUS_CHARGING; - break; - - default: - *status = POWER_SUPPLY_STATUS_UNKNOWN; - break; - } - - return 0; -} - -static int wm831x_bat_check_type(struct wm831x *wm831x, int *type) -{ - int ret; - - ret = wm831x_reg_read(wm831x, WM831X_CHARGER_STATUS); - if (ret < 0) - return ret; - - switch (ret & WM831X_CHG_STATE_MASK) { - case WM831X_CHG_STATE_TRICKLE: - case WM831X_CHG_STATE_TRICKLE_OT: - *type = POWER_SUPPLY_CHARGE_TYPE_TRICKLE; - break; - case WM831X_CHG_STATE_FAST: - case WM831X_CHG_STATE_FAST_OT: - *type = POWER_SUPPLY_CHARGE_TYPE_FAST; - break; - default: - *type = POWER_SUPPLY_CHARGE_TYPE_NONE; - break; - } - - return 0; -} - -static int wm831x_bat_check_health(struct wm831x *wm831x, int *health) -{ - int ret; - - ret = wm831x_reg_read(wm831x, WM831X_CHARGER_STATUS); - if (ret < 0) - return ret; - - if (ret & WM831X_BATT_HOT_STS) { - *health = POWER_SUPPLY_HEALTH_OVERHEAT; - return 0; - } - - if (ret & WM831X_BATT_COLD_STS) { - *health = POWER_SUPPLY_HEALTH_COLD; - return 0; - } - - if (ret & WM831X_BATT_OV_STS) { - *health = POWER_SUPPLY_HEALTH_OVERVOLTAGE; - return 0; - } - - switch (ret & WM831X_CHG_STATE_MASK) { - case WM831X_CHG_STATE_TRICKLE_OT: - case WM831X_CHG_STATE_FAST_OT: - *health = POWER_SUPPLY_HEALTH_OVERHEAT; - break; - case WM831X_CHG_STATE_DEFECTIVE: - *health = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; - break; - default: - *health = POWER_SUPPLY_HEALTH_GOOD; - break; - } - - return 0; -} - -static int wm831x_bat_get_prop(struct power_supply *psy, - enum power_supply_property psp, - union power_supply_propval *val) -{ - struct wm831x_power *wm831x_power = dev_get_drvdata(psy->dev.parent); - struct wm831x *wm831x = wm831x_power->wm831x; - int ret = 0; - - switch (psp) { - case POWER_SUPPLY_PROP_STATUS: - ret = wm831x_bat_check_status(wm831x, &val->intval); - break; - case POWER_SUPPLY_PROP_ONLINE: - ret = wm831x_power_check_online(wm831x, WM831X_PWR_SRC_BATT, - val); - break; - case POWER_SUPPLY_PROP_VOLTAGE_NOW: - ret = wm831x_power_read_voltage(wm831x, WM831X_AUX_BATT, val); - break; - case POWER_SUPPLY_PROP_HEALTH: - ret = wm831x_bat_check_health(wm831x, &val->intval); - break; - case POWER_SUPPLY_PROP_CHARGE_TYPE: - ret = wm831x_bat_check_type(wm831x, &val->intval); - break; - default: - ret = -EINVAL; - break; - } - - return ret; -} - -static enum power_supply_property wm831x_bat_props[] = { - POWER_SUPPLY_PROP_STATUS, - POWER_SUPPLY_PROP_ONLINE, - POWER_SUPPLY_PROP_VOLTAGE_NOW, - POWER_SUPPLY_PROP_HEALTH, - POWER_SUPPLY_PROP_CHARGE_TYPE, -}; - -static const char *wm831x_bat_irqs[] = { - "BATT HOT", - "BATT COLD", - "BATT FAIL", - "OV", - "END", - "TO", - "MODE", - "START", -}; - -static irqreturn_t wm831x_bat_irq(int irq, void *data) -{ - struct wm831x_power *wm831x_power = data; - struct wm831x *wm831x = wm831x_power->wm831x; - - dev_dbg(wm831x->dev, "Battery status changed: %d\n", irq); - - /* The battery charger is autonomous so we don't need to do - * anything except kick user space */ - if (wm831x_power->have_battery) - power_supply_changed(wm831x_power->battery); - - return IRQ_HANDLED; -} - - -/********************************************************************* - * Initialisation - *********************************************************************/ - -static irqreturn_t wm831x_syslo_irq(int irq, void *data) -{ - struct wm831x_power *wm831x_power = data; - struct wm831x *wm831x = wm831x_power->wm831x; - - /* Not much we can actually *do* but tell people for - * posterity, we're probably about to run out of power. */ - dev_crit(wm831x->dev, "SYSVDD under voltage\n"); - - return IRQ_HANDLED; -} - -static irqreturn_t wm831x_pwr_src_irq(int irq, void *data) -{ - struct wm831x_power *wm831x_power = data; - struct wm831x *wm831x = wm831x_power->wm831x; - - dev_dbg(wm831x->dev, "Power source changed\n"); - - /* Just notify for everything - little harm in overnotifying. */ - if (wm831x_power->have_battery) - power_supply_changed(wm831x_power->battery); - power_supply_changed(wm831x_power->usb); - power_supply_changed(wm831x_power->wall); - - return IRQ_HANDLED; -} - -static int wm831x_power_probe(struct platform_device *pdev) -{ - struct wm831x *wm831x = dev_get_drvdata(pdev->dev.parent); - struct wm831x_pdata *wm831x_pdata = wm831x->dev->platform_data; - struct wm831x_power *power; - int ret, irq, i; - - power = devm_kzalloc(&pdev->dev, sizeof(struct wm831x_power), - GFP_KERNEL); - if (power == NULL) - return -ENOMEM; - - power->wm831x = wm831x; - platform_set_drvdata(pdev, power); - - if (wm831x_pdata && wm831x_pdata->wm831x_num) { - snprintf(power->wall_name, sizeof(power->wall_name), - "wm831x-wall.%d", wm831x_pdata->wm831x_num); - snprintf(power->battery_name, sizeof(power->wall_name), - "wm831x-battery.%d", wm831x_pdata->wm831x_num); - snprintf(power->usb_name, sizeof(power->wall_name), - "wm831x-usb.%d", wm831x_pdata->wm831x_num); - } else { - snprintf(power->wall_name, sizeof(power->wall_name), - "wm831x-wall"); - snprintf(power->battery_name, sizeof(power->wall_name), - "wm831x-battery"); - snprintf(power->usb_name, sizeof(power->wall_name), - "wm831x-usb"); - } - - /* We ignore configuration failures since we can still read back - * the status without enabling the charger. - */ - wm831x_config_battery(wm831x); - - power->wall_desc.name = power->wall_name; - power->wall_desc.type = POWER_SUPPLY_TYPE_MAINS; - power->wall_desc.properties = wm831x_wall_props; - power->wall_desc.num_properties = ARRAY_SIZE(wm831x_wall_props); - power->wall_desc.get_property = wm831x_wall_get_prop; - power->wall = power_supply_register(&pdev->dev, &power->wall_desc, - NULL); - if (IS_ERR(power->wall)) { - ret = PTR_ERR(power->wall); - goto err; - } - - power->usb_desc.name = power->usb_name, - power->usb_desc.type = POWER_SUPPLY_TYPE_USB; - power->usb_desc.properties = wm831x_usb_props; - power->usb_desc.num_properties = ARRAY_SIZE(wm831x_usb_props); - power->usb_desc.get_property = wm831x_usb_get_prop; - power->usb = power_supply_register(&pdev->dev, &power->usb_desc, NULL); - if (IS_ERR(power->usb)) { - ret = PTR_ERR(power->usb); - goto err_wall; - } - - ret = wm831x_reg_read(wm831x, WM831X_CHARGER_CONTROL_1); - if (ret < 0) - goto err_wall; - power->have_battery = ret & WM831X_CHG_ENA; - - if (power->have_battery) { - power->battery_desc.name = power->battery_name; - power->battery_desc.properties = wm831x_bat_props; - power->battery_desc.num_properties = ARRAY_SIZE(wm831x_bat_props); - power->battery_desc.get_property = wm831x_bat_get_prop; - power->battery_desc.use_for_apm = 1; - power->battery = power_supply_register(&pdev->dev, - &power->battery_desc, - NULL); - if (IS_ERR(power->battery)) { - ret = PTR_ERR(power->battery); - goto err_usb; - } - } - - irq = wm831x_irq(wm831x, platform_get_irq_byname(pdev, "SYSLO")); - ret = request_threaded_irq(irq, NULL, wm831x_syslo_irq, - IRQF_TRIGGER_RISING | IRQF_ONESHOT, "System power low", - power); - if (ret != 0) { - dev_err(&pdev->dev, "Failed to request SYSLO IRQ %d: %d\n", - irq, ret); - goto err_battery; - } - - irq = wm831x_irq(wm831x, platform_get_irq_byname(pdev, "PWR SRC")); - ret = request_threaded_irq(irq, NULL, wm831x_pwr_src_irq, - IRQF_TRIGGER_RISING | IRQF_ONESHOT, "Power source", - power); - if (ret != 0) { - dev_err(&pdev->dev, "Failed to request PWR SRC IRQ %d: %d\n", - irq, ret); - goto err_syslo; - } - - for (i = 0; i < ARRAY_SIZE(wm831x_bat_irqs); i++) { - irq = wm831x_irq(wm831x, - platform_get_irq_byname(pdev, - wm831x_bat_irqs[i])); - ret = request_threaded_irq(irq, NULL, wm831x_bat_irq, - IRQF_TRIGGER_RISING | IRQF_ONESHOT, - wm831x_bat_irqs[i], - power); - if (ret != 0) { - dev_err(&pdev->dev, - "Failed to request %s IRQ %d: %d\n", - wm831x_bat_irqs[i], irq, ret); - goto err_bat_irq; - } - } - - return ret; - -err_bat_irq: - --i; - for (; i >= 0; i--) { - irq = platform_get_irq_byname(pdev, wm831x_bat_irqs[i]); - free_irq(irq, power); - } - irq = wm831x_irq(wm831x, platform_get_irq_byname(pdev, "PWR SRC")); - free_irq(irq, power); -err_syslo: - irq = wm831x_irq(wm831x, platform_get_irq_byname(pdev, "SYSLO")); - free_irq(irq, power); -err_battery: - if (power->have_battery) - power_supply_unregister(power->battery); -err_usb: - power_supply_unregister(power->usb); -err_wall: - power_supply_unregister(power->wall); -err: - return ret; -} - -static int wm831x_power_remove(struct platform_device *pdev) -{ - struct wm831x_power *wm831x_power = platform_get_drvdata(pdev); - struct wm831x *wm831x = wm831x_power->wm831x; - int irq, i; - - for (i = 0; i < ARRAY_SIZE(wm831x_bat_irqs); i++) { - irq = wm831x_irq(wm831x, - platform_get_irq_byname(pdev, - wm831x_bat_irqs[i])); - free_irq(irq, wm831x_power); - } - - irq = wm831x_irq(wm831x, platform_get_irq_byname(pdev, "PWR SRC")); - free_irq(irq, wm831x_power); - - irq = wm831x_irq(wm831x, platform_get_irq_byname(pdev, "SYSLO")); - free_irq(irq, wm831x_power); - - if (wm831x_power->have_battery) - power_supply_unregister(wm831x_power->battery); - power_supply_unregister(wm831x_power->wall); - power_supply_unregister(wm831x_power->usb); - return 0; -} - -static struct platform_driver wm831x_power_driver = { - .probe = wm831x_power_probe, - .remove = wm831x_power_remove, - .driver = { - .name = "wm831x-power", - }, -}; - -module_platform_driver(wm831x_power_driver); - -MODULE_DESCRIPTION("Power supply driver for WM831x PMICs"); -MODULE_AUTHOR("Mark Brown "); -MODULE_LICENSE("GPL"); -MODULE_ALIAS("platform:wm831x-power"); diff --git a/drivers/power/wm8350_power.c b/drivers/power/wm8350_power.c deleted file mode 100644 index 5c5880664e09..000000000000 --- a/drivers/power/wm8350_power.c +++ /dev/null @@ -1,540 +0,0 @@ -/* - * Battery driver for wm8350 PMIC - * - * Copyright 2007, 2008 Wolfson Microelectronics PLC. - * - * Based on OLPC Battery Driver - * - * Copyright 2006 David Woodhouse - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - */ - -#include -#include -#include -#include -#include -#include -#include - -static int wm8350_read_battery_uvolts(struct wm8350 *wm8350) -{ - return wm8350_read_auxadc(wm8350, WM8350_AUXADC_BATT, 0, 0) - * WM8350_AUX_COEFF; -} - -static int wm8350_read_line_uvolts(struct wm8350 *wm8350) -{ - return wm8350_read_auxadc(wm8350, WM8350_AUXADC_LINE, 0, 0) - * WM8350_AUX_COEFF; -} - -static int wm8350_read_usb_uvolts(struct wm8350 *wm8350) -{ - return wm8350_read_auxadc(wm8350, WM8350_AUXADC_USB, 0, 0) - * WM8350_AUX_COEFF; -} - -#define WM8350_BATT_SUPPLY 1 -#define WM8350_USB_SUPPLY 2 -#define WM8350_LINE_SUPPLY 4 - -static inline int wm8350_charge_time_min(struct wm8350 *wm8350, int min) -{ - if (!wm8350->power.rev_g_coeff) - return (((min - 30) / 15) & 0xf) << 8; - else - return (((min - 30) / 30) & 0xf) << 8; -} - -static int wm8350_get_supplies(struct wm8350 *wm8350) -{ - u16 sm, ov, co, chrg; - int supplies = 0; - - sm = wm8350_reg_read(wm8350, WM8350_STATE_MACHINE_STATUS); - ov = wm8350_reg_read(wm8350, WM8350_MISC_OVERRIDES); - co = wm8350_reg_read(wm8350, WM8350_COMPARATOR_OVERRIDES); - chrg = wm8350_reg_read(wm8350, WM8350_BATTERY_CHARGER_CONTROL_2); - - /* USB_SM */ - sm = (sm & WM8350_USB_SM_MASK) >> WM8350_USB_SM_SHIFT; - - /* CHG_ISEL */ - chrg &= WM8350_CHG_ISEL_MASK; - - /* If the USB state machine is active then we're using that with or - * without battery, otherwise check for wall supply */ - if (((sm == WM8350_USB_SM_100_SLV) || - (sm == WM8350_USB_SM_500_SLV) || - (sm == WM8350_USB_SM_STDBY_SLV)) - && !(ov & WM8350_USB_LIMIT_OVRDE)) - supplies = WM8350_USB_SUPPLY; - else if (((sm == WM8350_USB_SM_100_SLV) || - (sm == WM8350_USB_SM_500_SLV) || - (sm == WM8350_USB_SM_STDBY_SLV)) - && (ov & WM8350_USB_LIMIT_OVRDE) && (chrg == 0)) - supplies = WM8350_USB_SUPPLY | WM8350_BATT_SUPPLY; - else if (co & WM8350_WALL_FB_OVRDE) - supplies = WM8350_LINE_SUPPLY; - else - supplies = WM8350_BATT_SUPPLY; - - return supplies; -} - -static int wm8350_charger_config(struct wm8350 *wm8350, - struct wm8350_charger_policy *policy) -{ - u16 reg, eoc_mA, fast_limit_mA; - - if (!policy) { - dev_warn(wm8350->dev, - "No charger policy, charger not configured.\n"); - return -EINVAL; - } - - /* make sure USB fast charge current is not > 500mA */ - if (policy->fast_limit_USB_mA > 500) { - dev_err(wm8350->dev, "USB fast charge > 500mA\n"); - return -EINVAL; - } - - eoc_mA = WM8350_CHG_EOC_mA(policy->eoc_mA); - - wm8350_reg_unlock(wm8350); - - reg = wm8350_reg_read(wm8350, WM8350_BATTERY_CHARGER_CONTROL_1) - & WM8350_CHG_ENA_R168; - wm8350_reg_write(wm8350, WM8350_BATTERY_CHARGER_CONTROL_1, - reg | eoc_mA | policy->trickle_start_mV | - WM8350_CHG_TRICKLE_TEMP_CHOKE | - WM8350_CHG_TRICKLE_USB_CHOKE | - WM8350_CHG_FAST_USB_THROTTLE); - - if (wm8350_get_supplies(wm8350) & WM8350_USB_SUPPLY) { - fast_limit_mA = - WM8350_CHG_FAST_LIMIT_mA(policy->fast_limit_USB_mA); - wm8350_reg_write(wm8350, WM8350_BATTERY_CHARGER_CONTROL_2, - policy->charge_mV | policy->trickle_charge_USB_mA | - fast_limit_mA | wm8350_charge_time_min(wm8350, - policy->charge_timeout)); - - } else { - fast_limit_mA = - WM8350_CHG_FAST_LIMIT_mA(policy->fast_limit_mA); - wm8350_reg_write(wm8350, WM8350_BATTERY_CHARGER_CONTROL_2, - policy->charge_mV | policy->trickle_charge_mA | - fast_limit_mA | wm8350_charge_time_min(wm8350, - policy->charge_timeout)); - } - - wm8350_reg_lock(wm8350); - return 0; -} - -static int wm8350_batt_status(struct wm8350 *wm8350) -{ - u16 state; - - state = wm8350_reg_read(wm8350, WM8350_BATTERY_CHARGER_CONTROL_2); - state &= WM8350_CHG_STS_MASK; - - switch (state) { - case WM8350_CHG_STS_OFF: - return POWER_SUPPLY_STATUS_DISCHARGING; - - case WM8350_CHG_STS_TRICKLE: - case WM8350_CHG_STS_FAST: - return POWER_SUPPLY_STATUS_CHARGING; - - default: - return POWER_SUPPLY_STATUS_UNKNOWN; - } -} - -static ssize_t charger_state_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - struct wm8350 *wm8350 = dev_get_drvdata(dev); - char *charge; - int state; - - state = wm8350_reg_read(wm8350, WM8350_BATTERY_CHARGER_CONTROL_2) & - WM8350_CHG_STS_MASK; - switch (state) { - case WM8350_CHG_STS_OFF: - charge = "Charger Off"; - break; - case WM8350_CHG_STS_TRICKLE: - charge = "Trickle Charging"; - break; - case WM8350_CHG_STS_FAST: - charge = "Fast Charging"; - break; - default: - return 0; - } - - return sprintf(buf, "%s\n", charge); -} - -static DEVICE_ATTR(charger_state, 0444, charger_state_show, NULL); - -static irqreturn_t wm8350_charger_handler(int irq, void *data) -{ - struct wm8350 *wm8350 = data; - struct wm8350_power *power = &wm8350->power; - struct wm8350_charger_policy *policy = power->policy; - - switch (irq - wm8350->irq_base) { - case WM8350_IRQ_CHG_BAT_FAIL: - dev_err(wm8350->dev, "battery failed\n"); - break; - case WM8350_IRQ_CHG_TO: - dev_err(wm8350->dev, "charger timeout\n"); - power_supply_changed(power->battery); - break; - - case WM8350_IRQ_CHG_BAT_HOT: - case WM8350_IRQ_CHG_BAT_COLD: - case WM8350_IRQ_CHG_START: - case WM8350_IRQ_CHG_END: - power_supply_changed(power->battery); - break; - - case WM8350_IRQ_CHG_FAST_RDY: - dev_dbg(wm8350->dev, "fast charger ready\n"); - wm8350_charger_config(wm8350, policy); - wm8350_reg_unlock(wm8350); - wm8350_set_bits(wm8350, WM8350_BATTERY_CHARGER_CONTROL_1, - WM8350_CHG_FAST); - wm8350_reg_lock(wm8350); - break; - - case WM8350_IRQ_CHG_VBATT_LT_3P9: - dev_warn(wm8350->dev, "battery < 3.9V\n"); - break; - case WM8350_IRQ_CHG_VBATT_LT_3P1: - dev_warn(wm8350->dev, "battery < 3.1V\n"); - break; - case WM8350_IRQ_CHG_VBATT_LT_2P85: - dev_warn(wm8350->dev, "battery < 2.85V\n"); - break; - - /* Supply change. We will overnotify but it should do - * no harm. */ - case WM8350_IRQ_EXT_USB_FB: - case WM8350_IRQ_EXT_WALL_FB: - wm8350_charger_config(wm8350, policy); - case WM8350_IRQ_EXT_BAT_FB: /* Fall through */ - power_supply_changed(power->battery); - power_supply_changed(power->usb); - power_supply_changed(power->ac); - break; - - default: - dev_err(wm8350->dev, "Unknown interrupt %d\n", irq); - } - - return IRQ_HANDLED; -} - -/********************************************************************* - * AC Power - *********************************************************************/ -static int wm8350_ac_get_prop(struct power_supply *psy, - enum power_supply_property psp, - union power_supply_propval *val) -{ - struct wm8350 *wm8350 = dev_get_drvdata(psy->dev.parent); - int ret = 0; - - switch (psp) { - case POWER_SUPPLY_PROP_ONLINE: - val->intval = !!(wm8350_get_supplies(wm8350) & - WM8350_LINE_SUPPLY); - break; - case POWER_SUPPLY_PROP_VOLTAGE_NOW: - val->intval = wm8350_read_line_uvolts(wm8350); - break; - default: - ret = -EINVAL; - break; - } - return ret; -} - -static enum power_supply_property wm8350_ac_props[] = { - POWER_SUPPLY_PROP_ONLINE, - POWER_SUPPLY_PROP_VOLTAGE_NOW, -}; - -/********************************************************************* - * USB Power - *********************************************************************/ -static int wm8350_usb_get_prop(struct power_supply *psy, - enum power_supply_property psp, - union power_supply_propval *val) -{ - struct wm8350 *wm8350 = dev_get_drvdata(psy->dev.parent); - int ret = 0; - - switch (psp) { - case POWER_SUPPLY_PROP_ONLINE: - val->intval = !!(wm8350_get_supplies(wm8350) & - WM8350_USB_SUPPLY); - break; - case POWER_SUPPLY_PROP_VOLTAGE_NOW: - val->intval = wm8350_read_usb_uvolts(wm8350); - break; - default: - ret = -EINVAL; - break; - } - return ret; -} - -static enum power_supply_property wm8350_usb_props[] = { - POWER_SUPPLY_PROP_ONLINE, - POWER_SUPPLY_PROP_VOLTAGE_NOW, -}; - -/********************************************************************* - * Battery properties - *********************************************************************/ - -static int wm8350_bat_check_health(struct wm8350 *wm8350) -{ - u16 reg; - - if (wm8350_read_battery_uvolts(wm8350) < 2850000) - return POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; - - reg = wm8350_reg_read(wm8350, WM8350_CHARGER_OVERRIDES); - if (reg & WM8350_CHG_BATT_HOT_OVRDE) - return POWER_SUPPLY_HEALTH_OVERHEAT; - - if (reg & WM8350_CHG_BATT_COLD_OVRDE) - return POWER_SUPPLY_HEALTH_COLD; - - return POWER_SUPPLY_HEALTH_GOOD; -} - -static int wm8350_bat_get_charge_type(struct wm8350 *wm8350) -{ - int state; - - state = wm8350_reg_read(wm8350, WM8350_BATTERY_CHARGER_CONTROL_2) & - WM8350_CHG_STS_MASK; - switch (state) { - case WM8350_CHG_STS_OFF: - return POWER_SUPPLY_CHARGE_TYPE_NONE; - case WM8350_CHG_STS_TRICKLE: - return POWER_SUPPLY_CHARGE_TYPE_TRICKLE; - case WM8350_CHG_STS_FAST: - return POWER_SUPPLY_CHARGE_TYPE_FAST; - default: - return POWER_SUPPLY_CHARGE_TYPE_UNKNOWN; - } -} - -static int wm8350_bat_get_property(struct power_supply *psy, - enum power_supply_property psp, - union power_supply_propval *val) -{ - struct wm8350 *wm8350 = dev_get_drvdata(psy->dev.parent); - int ret = 0; - - switch (psp) { - case POWER_SUPPLY_PROP_STATUS: - val->intval = wm8350_batt_status(wm8350); - break; - case POWER_SUPPLY_PROP_ONLINE: - val->intval = !!(wm8350_get_supplies(wm8350) & - WM8350_BATT_SUPPLY); - break; - case POWER_SUPPLY_PROP_VOLTAGE_NOW: - val->intval = wm8350_read_battery_uvolts(wm8350); - break; - case POWER_SUPPLY_PROP_HEALTH: - val->intval = wm8350_bat_check_health(wm8350); - break; - case POWER_SUPPLY_PROP_CHARGE_TYPE: - val->intval = wm8350_bat_get_charge_type(wm8350); - break; - default: - ret = -EINVAL; - break; - } - - return ret; -} - -static enum power_supply_property wm8350_bat_props[] = { - POWER_SUPPLY_PROP_STATUS, - POWER_SUPPLY_PROP_ONLINE, - POWER_SUPPLY_PROP_VOLTAGE_NOW, - POWER_SUPPLY_PROP_HEALTH, - POWER_SUPPLY_PROP_CHARGE_TYPE, -}; - -static const struct power_supply_desc wm8350_ac_desc = { - .name = "wm8350-ac", - .type = POWER_SUPPLY_TYPE_MAINS, - .properties = wm8350_ac_props, - .num_properties = ARRAY_SIZE(wm8350_ac_props), - .get_property = wm8350_ac_get_prop, -}; - -static const struct power_supply_desc wm8350_battery_desc = { - .name = "wm8350-battery", - .properties = wm8350_bat_props, - .num_properties = ARRAY_SIZE(wm8350_bat_props), - .get_property = wm8350_bat_get_property, - .use_for_apm = 1, -}; - -static const struct power_supply_desc wm8350_usb_desc = { - .name = "wm8350-usb", - .type = POWER_SUPPLY_TYPE_USB, - .properties = wm8350_usb_props, - .num_properties = ARRAY_SIZE(wm8350_usb_props), - .get_property = wm8350_usb_get_prop, -}; - -/********************************************************************* - * Initialisation - *********************************************************************/ - -static void wm8350_init_charger(struct wm8350 *wm8350) -{ - /* register our interest in charger events */ - wm8350_register_irq(wm8350, WM8350_IRQ_CHG_BAT_HOT, - wm8350_charger_handler, 0, "Battery hot", wm8350); - wm8350_register_irq(wm8350, WM8350_IRQ_CHG_BAT_COLD, - wm8350_charger_handler, 0, "Battery cold", wm8350); - wm8350_register_irq(wm8350, WM8350_IRQ_CHG_BAT_FAIL, - wm8350_charger_handler, 0, "Battery fail", wm8350); - wm8350_register_irq(wm8350, WM8350_IRQ_CHG_TO, - wm8350_charger_handler, 0, - "Charger timeout", wm8350); - wm8350_register_irq(wm8350, WM8350_IRQ_CHG_END, - wm8350_charger_handler, 0, - "Charge end", wm8350); - wm8350_register_irq(wm8350, WM8350_IRQ_CHG_START, - wm8350_charger_handler, 0, - "Charge start", wm8350); - wm8350_register_irq(wm8350, WM8350_IRQ_CHG_FAST_RDY, - wm8350_charger_handler, 0, - "Fast charge ready", wm8350); - wm8350_register_irq(wm8350, WM8350_IRQ_CHG_VBATT_LT_3P9, - wm8350_charger_handler, 0, - "Battery <3.9V", wm8350); - wm8350_register_irq(wm8350, WM8350_IRQ_CHG_VBATT_LT_3P1, - wm8350_charger_handler, 0, - "Battery <3.1V", wm8350); - wm8350_register_irq(wm8350, WM8350_IRQ_CHG_VBATT_LT_2P85, - wm8350_charger_handler, 0, - "Battery <2.85V", wm8350); - - /* and supply change events */ - wm8350_register_irq(wm8350, WM8350_IRQ_EXT_USB_FB, - wm8350_charger_handler, 0, "USB", wm8350); - wm8350_register_irq(wm8350, WM8350_IRQ_EXT_WALL_FB, - wm8350_charger_handler, 0, "Wall", wm8350); - wm8350_register_irq(wm8350, WM8350_IRQ_EXT_BAT_FB, - wm8350_charger_handler, 0, "Battery", wm8350); -} - -static void free_charger_irq(struct wm8350 *wm8350) -{ - wm8350_free_irq(wm8350, WM8350_IRQ_CHG_BAT_HOT, wm8350); - wm8350_free_irq(wm8350, WM8350_IRQ_CHG_BAT_COLD, wm8350); - wm8350_free_irq(wm8350, WM8350_IRQ_CHG_BAT_FAIL, wm8350); - wm8350_free_irq(wm8350, WM8350_IRQ_CHG_TO, wm8350); - wm8350_free_irq(wm8350, WM8350_IRQ_CHG_END, wm8350); - wm8350_free_irq(wm8350, WM8350_IRQ_CHG_START, wm8350); - wm8350_free_irq(wm8350, WM8350_IRQ_CHG_VBATT_LT_3P9, wm8350); - wm8350_free_irq(wm8350, WM8350_IRQ_CHG_VBATT_LT_3P1, wm8350); - wm8350_free_irq(wm8350, WM8350_IRQ_CHG_VBATT_LT_2P85, wm8350); - wm8350_free_irq(wm8350, WM8350_IRQ_EXT_USB_FB, wm8350); - wm8350_free_irq(wm8350, WM8350_IRQ_EXT_WALL_FB, wm8350); - wm8350_free_irq(wm8350, WM8350_IRQ_EXT_BAT_FB, wm8350); -} - -static int wm8350_power_probe(struct platform_device *pdev) -{ - struct wm8350 *wm8350 = platform_get_drvdata(pdev); - struct wm8350_power *power = &wm8350->power; - struct wm8350_charger_policy *policy = power->policy; - int ret; - - power->ac = power_supply_register(&pdev->dev, &wm8350_ac_desc, NULL); - if (IS_ERR(power->ac)) - return PTR_ERR(power->ac); - - power->battery = power_supply_register(&pdev->dev, &wm8350_battery_desc, - NULL); - if (IS_ERR(power->battery)) { - ret = PTR_ERR(power->battery); - goto battery_failed; - } - - power->usb = power_supply_register(&pdev->dev, &wm8350_usb_desc, NULL); - if (IS_ERR(power->usb)) { - ret = PTR_ERR(power->usb); - goto usb_failed; - } - - ret = device_create_file(&pdev->dev, &dev_attr_charger_state); - if (ret < 0) - dev_warn(wm8350->dev, "failed to add charge sysfs: %d\n", ret); - ret = 0; - - wm8350_init_charger(wm8350); - if (wm8350_charger_config(wm8350, policy) == 0) { - wm8350_reg_unlock(wm8350); - wm8350_set_bits(wm8350, WM8350_POWER_MGMT_5, WM8350_CHG_ENA); - wm8350_reg_lock(wm8350); - } - - return ret; - -usb_failed: - power_supply_unregister(power->battery); -battery_failed: - power_supply_unregister(power->ac); - - return ret; -} - -static int wm8350_power_remove(struct platform_device *pdev) -{ - struct wm8350 *wm8350 = platform_get_drvdata(pdev); - struct wm8350_power *power = &wm8350->power; - - free_charger_irq(wm8350); - device_remove_file(&pdev->dev, &dev_attr_charger_state); - power_supply_unregister(power->battery); - power_supply_unregister(power->ac); - power_supply_unregister(power->usb); - return 0; -} - -static struct platform_driver wm8350_power_driver = { - .probe = wm8350_power_probe, - .remove = wm8350_power_remove, - .driver = { - .name = "wm8350-power", - }, -}; - -module_platform_driver(wm8350_power_driver); - -MODULE_LICENSE("GPL"); -MODULE_DESCRIPTION("Power supply driver for WM8350"); -MODULE_ALIAS("platform:wm8350-power"); diff --git a/drivers/power/wm97xx_battery.c b/drivers/power/wm97xx_battery.c deleted file mode 100644 index c2f09ed35050..000000000000 --- a/drivers/power/wm97xx_battery.c +++ /dev/null @@ -1,299 +0,0 @@ -/* - * linux/drivers/power/wm97xx_battery.c - * - * Battery measurement code for WM97xx - * - * based on tosa_battery.c - * - * Copyright (C) 2008 Marek Vasut - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - * - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -static struct work_struct bat_work; -static DEFINE_MUTEX(work_lock); -static int bat_status = POWER_SUPPLY_STATUS_UNKNOWN; -static enum power_supply_property *prop; - -static unsigned long wm97xx_read_bat(struct power_supply *bat_ps) -{ - struct wm97xx_pdata *wmdata = bat_ps->dev.parent->platform_data; - struct wm97xx_batt_pdata *pdata = wmdata->batt_pdata; - - return wm97xx_read_aux_adc(dev_get_drvdata(bat_ps->dev.parent), - pdata->batt_aux) * pdata->batt_mult / - pdata->batt_div; -} - -static unsigned long wm97xx_read_temp(struct power_supply *bat_ps) -{ - struct wm97xx_pdata *wmdata = bat_ps->dev.parent->platform_data; - struct wm97xx_batt_pdata *pdata = wmdata->batt_pdata; - - return wm97xx_read_aux_adc(dev_get_drvdata(bat_ps->dev.parent), - pdata->temp_aux) * pdata->temp_mult / - pdata->temp_div; -} - -static int wm97xx_bat_get_property(struct power_supply *bat_ps, - enum power_supply_property psp, - union power_supply_propval *val) -{ - struct wm97xx_pdata *wmdata = bat_ps->dev.parent->platform_data; - struct wm97xx_batt_pdata *pdata = wmdata->batt_pdata; - - switch (psp) { - case POWER_SUPPLY_PROP_STATUS: - val->intval = bat_status; - break; - case POWER_SUPPLY_PROP_TECHNOLOGY: - val->intval = pdata->batt_tech; - break; - case POWER_SUPPLY_PROP_VOLTAGE_NOW: - if (pdata->batt_aux >= 0) - val->intval = wm97xx_read_bat(bat_ps); - else - return -EINVAL; - break; - case POWER_SUPPLY_PROP_TEMP: - if (pdata->temp_aux >= 0) - val->intval = wm97xx_read_temp(bat_ps); - else - return -EINVAL; - break; - case POWER_SUPPLY_PROP_VOLTAGE_MAX: - if (pdata->max_voltage >= 0) - val->intval = pdata->max_voltage; - else - return -EINVAL; - break; - case POWER_SUPPLY_PROP_VOLTAGE_MIN: - if (pdata->min_voltage >= 0) - val->intval = pdata->min_voltage; - else - return -EINVAL; - break; - case POWER_SUPPLY_PROP_PRESENT: - val->intval = 1; - break; - default: - return -EINVAL; - } - return 0; -} - -static void wm97xx_bat_external_power_changed(struct power_supply *bat_ps) -{ - schedule_work(&bat_work); -} - -static void wm97xx_bat_update(struct power_supply *bat_ps) -{ - int old_status = bat_status; - struct wm97xx_pdata *wmdata = bat_ps->dev.parent->platform_data; - struct wm97xx_batt_pdata *pdata = wmdata->batt_pdata; - - mutex_lock(&work_lock); - - bat_status = (pdata->charge_gpio >= 0) ? - (gpio_get_value(pdata->charge_gpio) ? - POWER_SUPPLY_STATUS_DISCHARGING : - POWER_SUPPLY_STATUS_CHARGING) : - POWER_SUPPLY_STATUS_UNKNOWN; - - if (old_status != bat_status) { - pr_debug("%s: %i -> %i\n", bat_ps->desc->name, old_status, - bat_status); - power_supply_changed(bat_ps); - } - - mutex_unlock(&work_lock); -} - -static struct power_supply *bat_psy; -static struct power_supply_desc bat_psy_desc = { - .type = POWER_SUPPLY_TYPE_BATTERY, - .get_property = wm97xx_bat_get_property, - .external_power_changed = wm97xx_bat_external_power_changed, - .use_for_apm = 1, -}; - -static void wm97xx_bat_work(struct work_struct *work) -{ - wm97xx_bat_update(bat_psy); -} - -static irqreturn_t wm97xx_chrg_irq(int irq, void *data) -{ - schedule_work(&bat_work); - return IRQ_HANDLED; -} - -#ifdef CONFIG_PM -static int wm97xx_bat_suspend(struct device *dev) -{ - flush_work(&bat_work); - return 0; -} - -static int wm97xx_bat_resume(struct device *dev) -{ - schedule_work(&bat_work); - return 0; -} - -static const struct dev_pm_ops wm97xx_bat_pm_ops = { - .suspend = wm97xx_bat_suspend, - .resume = wm97xx_bat_resume, -}; -#endif - -static int wm97xx_bat_probe(struct platform_device *dev) -{ - int ret = 0; - int props = 1; /* POWER_SUPPLY_PROP_PRESENT */ - int i = 0; - struct wm97xx_pdata *wmdata = dev->dev.platform_data; - struct wm97xx_batt_pdata *pdata; - - if (!wmdata) { - dev_err(&dev->dev, "No platform data supplied\n"); - return -EINVAL; - } - - pdata = wmdata->batt_pdata; - - 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) - goto err; - ret = gpio_direction_input(pdata->charge_gpio); - if (ret) - goto err2; - ret = request_irq(gpio_to_irq(pdata->charge_gpio), - wm97xx_chrg_irq, 0, - "AC Detect", dev); - if (ret) - goto err2; - props++; /* POWER_SUPPLY_PROP_STATUS */ - } - - if (pdata->batt_tech >= 0) - props++; /* POWER_SUPPLY_PROP_TECHNOLOGY */ - if (pdata->temp_aux >= 0) - props++; /* POWER_SUPPLY_PROP_TEMP */ - if (pdata->batt_aux >= 0) - props++; /* POWER_SUPPLY_PROP_VOLTAGE_NOW */ - if (pdata->max_voltage >= 0) - props++; /* POWER_SUPPLY_PROP_VOLTAGE_MAX */ - if (pdata->min_voltage >= 0) - props++; /* POWER_SUPPLY_PROP_VOLTAGE_MIN */ - - prop = kzalloc(props * sizeof(*prop), GFP_KERNEL); - if (!prop) { - ret = -ENOMEM; - goto err3; - } - - prop[i++] = POWER_SUPPLY_PROP_PRESENT; - if (pdata->charge_gpio >= 0) - prop[i++] = POWER_SUPPLY_PROP_STATUS; - if (pdata->batt_tech >= 0) - prop[i++] = POWER_SUPPLY_PROP_TECHNOLOGY; - if (pdata->temp_aux >= 0) - prop[i++] = POWER_SUPPLY_PROP_TEMP; - if (pdata->batt_aux >= 0) - prop[i++] = POWER_SUPPLY_PROP_VOLTAGE_NOW; - if (pdata->max_voltage >= 0) - prop[i++] = POWER_SUPPLY_PROP_VOLTAGE_MAX; - if (pdata->min_voltage >= 0) - prop[i++] = POWER_SUPPLY_PROP_VOLTAGE_MIN; - - INIT_WORK(&bat_work, wm97xx_bat_work); - - if (!pdata->batt_name) { - dev_info(&dev->dev, "Please consider setting proper battery " - "name in platform definition file, falling " - "back to name \"wm97xx-batt\"\n"); - bat_psy_desc.name = "wm97xx-batt"; - } else - bat_psy_desc.name = pdata->batt_name; - - bat_psy_desc.properties = prop; - bat_psy_desc.num_properties = props; - - bat_psy = power_supply_register(&dev->dev, &bat_psy_desc, NULL); - if (!IS_ERR(bat_psy)) { - schedule_work(&bat_work); - } else { - ret = PTR_ERR(bat_psy); - goto err4; - } - - return 0; -err4: - kfree(prop); -err3: - if (gpio_is_valid(pdata->charge_gpio)) - free_irq(gpio_to_irq(pdata->charge_gpio), dev); -err2: - if (gpio_is_valid(pdata->charge_gpio)) - gpio_free(pdata->charge_gpio); -err: - return ret; -} - -static int wm97xx_bat_remove(struct platform_device *dev) -{ - struct wm97xx_pdata *wmdata = dev->dev.platform_data; - struct wm97xx_batt_pdata *pdata = wmdata->batt_pdata; - - if (pdata && gpio_is_valid(pdata->charge_gpio)) { - free_irq(gpio_to_irq(pdata->charge_gpio), dev); - gpio_free(pdata->charge_gpio); - } - cancel_work_sync(&bat_work); - power_supply_unregister(bat_psy); - kfree(prop); - return 0; -} - -static struct platform_driver wm97xx_bat_driver = { - .driver = { - .name = "wm97xx-battery", -#ifdef CONFIG_PM - .pm = &wm97xx_bat_pm_ops, -#endif - }, - .probe = wm97xx_bat_probe, - .remove = wm97xx_bat_remove, -}; - -module_platform_driver(wm97xx_bat_driver); - -MODULE_LICENSE("GPL"); -MODULE_AUTHOR("Marek Vasut "); -MODULE_DESCRIPTION("WM97xx battery driver"); diff --git a/drivers/power/z2_battery.c b/drivers/power/z2_battery.c deleted file mode 100644 index b201e3facf73..000000000000 --- a/drivers/power/z2_battery.c +++ /dev/null @@ -1,331 +0,0 @@ -/* - * Battery measurement code for Zipit Z2 - * - * Copyright (C) 2009 Peter Edwards - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - * - */ - -#include -#include -#include -#include -#include -#include -#include -#include - -#define Z2_DEFAULT_NAME "Z2" - -struct z2_charger { - struct z2_battery_info *info; - int bat_status; - struct i2c_client *client; - struct power_supply *batt_ps; - struct power_supply_desc batt_ps_desc; - struct mutex work_lock; - struct work_struct bat_work; -}; - -static unsigned long z2_read_bat(struct z2_charger *charger) -{ - int data; - data = i2c_smbus_read_byte_data(charger->client, - charger->info->batt_I2C_reg); - if (data < 0) - return 0; - - return data * charger->info->batt_mult / charger->info->batt_div; -} - -static int z2_batt_get_property(struct power_supply *batt_ps, - enum power_supply_property psp, - union power_supply_propval *val) -{ - struct z2_charger *charger = power_supply_get_drvdata(batt_ps); - struct z2_battery_info *info = charger->info; - - switch (psp) { - case POWER_SUPPLY_PROP_STATUS: - val->intval = charger->bat_status; - break; - case POWER_SUPPLY_PROP_TECHNOLOGY: - val->intval = info->batt_tech; - break; - case POWER_SUPPLY_PROP_VOLTAGE_NOW: - if (info->batt_I2C_reg >= 0) - val->intval = z2_read_bat(charger); - else - return -EINVAL; - break; - case POWER_SUPPLY_PROP_VOLTAGE_MAX: - if (info->max_voltage >= 0) - val->intval = info->max_voltage; - else - return -EINVAL; - break; - case POWER_SUPPLY_PROP_VOLTAGE_MIN: - if (info->min_voltage >= 0) - val->intval = info->min_voltage; - else - return -EINVAL; - break; - case POWER_SUPPLY_PROP_PRESENT: - val->intval = 1; - break; - default: - return -EINVAL; - } - - return 0; -} - -static void z2_batt_ext_power_changed(struct power_supply *batt_ps) -{ - struct z2_charger *charger = power_supply_get_drvdata(batt_ps); - - schedule_work(&charger->bat_work); -} - -static void z2_batt_update(struct z2_charger *charger) -{ - int old_status = charger->bat_status; - struct z2_battery_info *info; - - info = charger->info; - - mutex_lock(&charger->work_lock); - - charger->bat_status = (info->charge_gpio >= 0) ? - (gpio_get_value(info->charge_gpio) ? - POWER_SUPPLY_STATUS_CHARGING : - POWER_SUPPLY_STATUS_DISCHARGING) : - POWER_SUPPLY_STATUS_UNKNOWN; - - if (old_status != charger->bat_status) { - pr_debug("%s: %i -> %i\n", charger->batt_ps->desc->name, - old_status, - charger->bat_status); - power_supply_changed(charger->batt_ps); - } - - mutex_unlock(&charger->work_lock); -} - -static void z2_batt_work(struct work_struct *work) -{ - struct z2_charger *charger; - charger = container_of(work, struct z2_charger, bat_work); - z2_batt_update(charger); -} - -static irqreturn_t z2_charge_switch_irq(int irq, void *devid) -{ - struct z2_charger *charger = devid; - schedule_work(&charger->bat_work); - return IRQ_HANDLED; -} - -static int z2_batt_ps_init(struct z2_charger *charger, int props) -{ - int i = 0; - enum power_supply_property *prop; - struct z2_battery_info *info = charger->info; - - if (info->charge_gpio >= 0) - props++; /* POWER_SUPPLY_PROP_STATUS */ - if (info->batt_tech >= 0) - props++; /* POWER_SUPPLY_PROP_TECHNOLOGY */ - if (info->batt_I2C_reg >= 0) - props++; /* POWER_SUPPLY_PROP_VOLTAGE_NOW */ - if (info->max_voltage >= 0) - props++; /* POWER_SUPPLY_PROP_VOLTAGE_MAX */ - if (info->min_voltage >= 0) - props++; /* POWER_SUPPLY_PROP_VOLTAGE_MIN */ - - prop = kzalloc(props * sizeof(*prop), GFP_KERNEL); - if (!prop) - return -ENOMEM; - - prop[i++] = POWER_SUPPLY_PROP_PRESENT; - if (info->charge_gpio >= 0) - prop[i++] = POWER_SUPPLY_PROP_STATUS; - if (info->batt_tech >= 0) - prop[i++] = POWER_SUPPLY_PROP_TECHNOLOGY; - if (info->batt_I2C_reg >= 0) - prop[i++] = POWER_SUPPLY_PROP_VOLTAGE_NOW; - if (info->max_voltage >= 0) - prop[i++] = POWER_SUPPLY_PROP_VOLTAGE_MAX; - if (info->min_voltage >= 0) - prop[i++] = POWER_SUPPLY_PROP_VOLTAGE_MIN; - - if (!info->batt_name) { - dev_info(&charger->client->dev, - "Please consider setting proper battery " - "name in platform definition file, falling " - "back to name \" Z2_DEFAULT_NAME \"\n"); - charger->batt_ps_desc.name = Z2_DEFAULT_NAME; - } else - charger->batt_ps_desc.name = info->batt_name; - - charger->batt_ps_desc.properties = prop; - charger->batt_ps_desc.num_properties = props; - charger->batt_ps_desc.type = POWER_SUPPLY_TYPE_BATTERY; - charger->batt_ps_desc.get_property = z2_batt_get_property; - charger->batt_ps_desc.external_power_changed = - z2_batt_ext_power_changed; - charger->batt_ps_desc.use_for_apm = 1; - - return 0; -} - -static int z2_batt_probe(struct i2c_client *client, - const struct i2c_device_id *id) -{ - int ret = 0; - int props = 1; /* POWER_SUPPLY_PROP_PRESENT */ - struct z2_charger *charger; - struct z2_battery_info *info = client->dev.platform_data; - struct power_supply_config psy_cfg = {}; - - if (info == NULL) { - dev_err(&client->dev, - "Please set platform device platform_data" - " to a valid z2_battery_info pointer!\n"); - return -EINVAL; - } - - charger = kzalloc(sizeof(*charger), GFP_KERNEL); - if (charger == NULL) - return -ENOMEM; - - charger->bat_status = POWER_SUPPLY_STATUS_UNKNOWN; - charger->info = info; - charger->client = client; - i2c_set_clientdata(client, charger); - psy_cfg.drv_data = charger; - - mutex_init(&charger->work_lock); - - if (info->charge_gpio >= 0 && gpio_is_valid(info->charge_gpio)) { - ret = gpio_request(info->charge_gpio, "BATT CHRG"); - if (ret) - goto err; - - ret = gpio_direction_input(info->charge_gpio); - if (ret) - goto err2; - - irq_set_irq_type(gpio_to_irq(info->charge_gpio), - IRQ_TYPE_EDGE_BOTH); - ret = request_irq(gpio_to_irq(info->charge_gpio), - z2_charge_switch_irq, 0, - "AC Detect", charger); - if (ret) - goto err3; - } - - ret = z2_batt_ps_init(charger, props); - if (ret) - goto err3; - - INIT_WORK(&charger->bat_work, z2_batt_work); - - charger->batt_ps = power_supply_register(&client->dev, - &charger->batt_ps_desc, - &psy_cfg); - if (IS_ERR(charger->batt_ps)) { - ret = PTR_ERR(charger->batt_ps); - goto err4; - } - - schedule_work(&charger->bat_work); - - return 0; - -err4: - kfree(charger->batt_ps_desc.properties); -err3: - if (info->charge_gpio >= 0 && gpio_is_valid(info->charge_gpio)) - free_irq(gpio_to_irq(info->charge_gpio), charger); -err2: - if (info->charge_gpio >= 0 && gpio_is_valid(info->charge_gpio)) - gpio_free(info->charge_gpio); -err: - kfree(charger); - return ret; -} - -static int z2_batt_remove(struct i2c_client *client) -{ - struct z2_charger *charger = i2c_get_clientdata(client); - struct z2_battery_info *info = charger->info; - - cancel_work_sync(&charger->bat_work); - power_supply_unregister(charger->batt_ps); - - kfree(charger->batt_ps_desc.properties); - if (info->charge_gpio >= 0 && gpio_is_valid(info->charge_gpio)) { - free_irq(gpio_to_irq(info->charge_gpio), charger); - gpio_free(info->charge_gpio); - } - - kfree(charger); - - return 0; -} - -#ifdef CONFIG_PM -static int z2_batt_suspend(struct device *dev) -{ - struct i2c_client *client = to_i2c_client(dev); - struct z2_charger *charger = i2c_get_clientdata(client); - - flush_work(&charger->bat_work); - return 0; -} - -static int z2_batt_resume(struct device *dev) -{ - struct i2c_client *client = to_i2c_client(dev); - struct z2_charger *charger = i2c_get_clientdata(client); - - schedule_work(&charger->bat_work); - return 0; -} - -static const struct dev_pm_ops z2_battery_pm_ops = { - .suspend = z2_batt_suspend, - .resume = z2_batt_resume, -}; - -#define Z2_BATTERY_PM_OPS (&z2_battery_pm_ops) - -#else -#define Z2_BATTERY_PM_OPS (NULL) -#endif - -static const struct i2c_device_id z2_batt_id[] = { - { "aer915", 0 }, - { } -}; -MODULE_DEVICE_TABLE(i2c, z2_batt_id); - -static struct i2c_driver z2_batt_driver = { - .driver = { - .name = "z2-battery", - .owner = THIS_MODULE, - .pm = Z2_BATTERY_PM_OPS - }, - .probe = z2_batt_probe, - .remove = z2_batt_remove, - .id_table = z2_batt_id, -}; -module_i2c_driver(z2_batt_driver); - -MODULE_LICENSE("GPL"); -MODULE_AUTHOR("Peter Edwards "); -MODULE_DESCRIPTION("Zipit Z2 battery driver"); -- cgit v1.2.1