diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2021-04-28 15:43:58 -0700 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2021-04-28 15:43:58 -0700 |
commit | 5a69e9bce9984806029926f405b4517878e703e2 (patch) | |
tree | 97d3a817b17a3f8a2e7d6099c6d96bcb67d1cd66 /drivers/power | |
parent | a8b5e037d8a00d396377a97f08f5fd2a410b96a1 (diff) | |
parent | d0a43c12ee9f57ddb284272187bd18726c2c2c98 (diff) |
Merge tag 'for-v5.13' of git://git.kernel.org/pub/scm/linux/kernel/git/sre/linux-power-supply
Pull power supply and reset updates from Sebastian Reichel:
"battery/charger driver changes:
- core:
- provide function stubs if CONFIG_POWER_SUPPLY=n
- reduce loglevel for probe defer info
- surface:
- new battery and charger drivers for Surface
- bq27xxx:
- add bq78z100 support
- fix current_now/power_avg for newer chips
- cw2015:
- add CHARGE_NOW support
- ab8500:
- drop pdata support
- convert most DT bindings to YAML
- lots of minor fixes and cleanups
reset drivers:
- ltc2952-poweroff:
- make trigger delay configurable from DT
- minor fixes and cleanups"
* tag 'for-v5.13' of git://git.kernel.org/pub/scm/linux/kernel/git/sre/linux-power-supply: (97 commits)
power: supply: cpcap-battery: fix invalid usage of list cursor
power: supply: bq256xx: add kerneldoc for structure members
power: supply: act8945a: correct kerneldoc
power: supply: max17040: remove unneeded double cast
power: supply: max17040: handle device_property_read_u8_array() failure
power: supply: max14577: remove unneeded variable initialization
power: supply: surface-charger: Make symbol 'surface_ac_pm_ops' static
power: supply: surface-battery: Make some symbols static
power: reset: restart-poweroff: Add missing MODULE_DEVICE_TABLE
power: reset: hisi-reboot: add missing MODULE_DEVICE_TABLE
power: supply: s3c_adc_battery: fix possible use-after-free in s3c_adc_bat_remove()
power: supply: generic-adc-battery: fix possible use-after-free in gab_remove()
power: supply: Add AC driver for Surface Aggregator Module
power: supply: Add battery driver for Surface Aggregator Module
power: supply: bq25980: Move props from battery node
power: supply: core: Use true and false for bool variable
power: supply: goldfish: Remove the GOLDFISH dependency
power: reset: ltc2952: make trigger delay configurable
power: supply: cpcap-charger: Simplify bool conversion
power: supply: cpcap-charger: Add usleep to cpcap charger to avoid usb plug bounce
...
Diffstat (limited to 'drivers/power')
46 files changed, 2291 insertions, 356 deletions
diff --git a/drivers/power/reset/Makefile b/drivers/power/reset/Makefile index 4f959b697606..cf3f4d02d8a5 100644 --- a/drivers/power/reset/Makefile +++ b/drivers/power/reset/Makefile @@ -11,7 +11,7 @@ obj-$(CONFIG_POWER_RESET_GEMINI_POWEROFF) += gemini-poweroff.o obj-$(CONFIG_POWER_RESET_GPIO) += gpio-poweroff.o obj-$(CONFIG_POWER_RESET_GPIO_RESTART) += gpio-restart.o obj-$(CONFIG_POWER_RESET_HISI) += hisi-reboot.o -obj-${CONFIG_POWER_RESET_LINKSTATION} += linkstation-poweroff.o +obj-$(CONFIG_POWER_RESET_LINKSTATION) += linkstation-poweroff.o obj-$(CONFIG_POWER_RESET_MSM) += msm-poweroff.o obj-$(CONFIG_POWER_RESET_MT6323) += mt6323-poweroff.o obj-$(CONFIG_POWER_RESET_OXNAS) += oxnas-restart.o diff --git a/drivers/power/reset/at91-reset.c b/drivers/power/reset/at91-reset.c index 3ff9d93a5226..026649409135 100644 --- a/drivers/power/reset/at91-reset.c +++ b/drivers/power/reset/at91-reset.c @@ -192,7 +192,7 @@ static int __init at91_reset_probe(struct platform_device *pdev) if (!reset) return -ENOMEM; - reset->rstc_base = of_iomap(pdev->dev.of_node, 0); + reset->rstc_base = devm_of_iomap(&pdev->dev, pdev->dev.of_node, 0, NULL); if (!reset->rstc_base) { dev_err(&pdev->dev, "Could not map reset controller address\n"); return -ENODEV; @@ -202,7 +202,7 @@ static int __init at91_reset_probe(struct platform_device *pdev) /* we need to shutdown the ddr controller, so get ramc base */ for_each_matching_node_and_match(np, at91_ramc_of_match, &match) { reset->ramc_lpr = (u32)match->data; - reset->ramc_base[idx] = of_iomap(np, 0); + reset->ramc_base[idx] = devm_of_iomap(&pdev->dev, np, 0, NULL); if (!reset->ramc_base[idx]) { dev_err(&pdev->dev, "Could not map ram controller address\n"); of_node_put(np); diff --git a/drivers/power/reset/hisi-reboot.c b/drivers/power/reset/hisi-reboot.c index 0ba5fdce186f..34409340528f 100644 --- a/drivers/power/reset/hisi-reboot.c +++ b/drivers/power/reset/hisi-reboot.c @@ -1,8 +1,8 @@ // SPDX-License-Identifier: GPL-2.0-only /* - * Hisilicon SoC reset code + * HiSilicon SoC reset code * - * Copyright (c) 2014 Hisilicon Ltd. + * Copyright (c) 2014 HiSilicon Ltd. * Copyright (c) 2014 Linaro Ltd. * * Author: Haojian Zhuang <haojian.zhuang@linaro.org> @@ -68,6 +68,7 @@ static const struct of_device_id hisi_reboot_of_match[] = { { .compatible = "hisilicon,sysctrl" }, {} }; +MODULE_DEVICE_TABLE(of, hisi_reboot_of_match); static struct platform_driver hisi_reboot_driver = { .probe = hisi_reboot_probe, diff --git a/drivers/power/reset/ltc2952-poweroff.c b/drivers/power/reset/ltc2952-poweroff.c index 318927938b05..d1495af30081 100644 --- a/drivers/power/reset/ltc2952-poweroff.c +++ b/drivers/power/reset/ltc2952-poweroff.c @@ -55,6 +55,7 @@ #include <linux/mod_devicetable.h> #include <linux/gpio/consumer.h> #include <linux/reboot.h> +#include <linux/property.h> struct ltc2952_poweroff { struct hrtimer timer_trigger; @@ -172,10 +173,17 @@ static void ltc2952_poweroff_default(struct ltc2952_poweroff *data) static int ltc2952_poweroff_init(struct platform_device *pdev) { int ret; + u32 trigger_delay_ms; struct ltc2952_poweroff *data = platform_get_drvdata(pdev); ltc2952_poweroff_default(data); + if (!device_property_read_u32(&pdev->dev, "trigger-delay-ms", + &trigger_delay_ms)) { + data->trigger_delay = ktime_set(trigger_delay_ms / MSEC_PER_SEC, + (trigger_delay_ms % MSEC_PER_SEC) * NSEC_PER_MSEC); + } + data->gpio_watchdog = devm_gpiod_get(&pdev->dev, "watchdog", GPIOD_OUT_LOW); if (IS_ERR(data->gpio_watchdog)) { diff --git a/drivers/power/reset/restart-poweroff.c b/drivers/power/reset/restart-poweroff.c index d233daa5835b..04d4228119b2 100644 --- a/drivers/power/reset/restart-poweroff.c +++ b/drivers/power/reset/restart-poweroff.c @@ -45,6 +45,7 @@ static const struct of_device_id of_restart_poweroff_match[] = { { .compatible = "restart-poweroff", }, {}, }; +MODULE_DEVICE_TABLE(of, of_restart_poweroff_match); static struct platform_driver restart_poweroff_driver = { .probe = restart_poweroff_probe, diff --git a/drivers/power/reset/vexpress-poweroff.c b/drivers/power/reset/vexpress-poweroff.c index 1fdbcbd95fc2..447ffdacddf9 100644 --- a/drivers/power/reset/vexpress-poweroff.c +++ b/drivers/power/reset/vexpress-poweroff.c @@ -133,7 +133,7 @@ static int vexpress_reset_probe(struct platform_device *pdev) case FUNC_REBOOT: ret = _vexpress_register_restart_handler(&pdev->dev); break; - }; + } return ret; } diff --git a/drivers/power/supply/88pm860x_battery.c b/drivers/power/supply/88pm860x_battery.c index 590da88a17a2..f3f3f8cd1a7f 100644 --- a/drivers/power/supply/88pm860x_battery.c +++ b/drivers/power/supply/88pm860x_battery.c @@ -109,8 +109,8 @@ struct pm860x_battery_info { }; struct ccnt { - unsigned long long int pos; - unsigned long long int neg; + unsigned long long pos; + unsigned long long neg; unsigned int spos; unsigned int sneg; diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig index 006b95eca673..e696364126f1 100644 --- a/drivers/power/supply/Kconfig +++ b/drivers/power/supply/Kconfig @@ -698,17 +698,17 @@ config BATTERY_GAUGE_LTC2941 config AB8500_BM bool "AB8500 Battery Management Driver" - depends on AB8500_CORE && AB8500_GPADC && (IIO = y) + depends on AB8500_CORE && AB8500_GPADC && (IIO = y) && OF 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. + Say Y to enable support for the Goldfish battery and AC power + driver. Originated in the Android Studio Emulator (goldfish) it is + going to be used in other emulators. config BATTERY_RT5033 tristate "RT5033 fuel gauge support" @@ -801,4 +801,36 @@ config BATTERY_ACER_A500 help Say Y to include support for Acer Iconia Tab A500 battery fuel gauge. +config BATTERY_SURFACE + tristate "Battery driver for 7th-generation Microsoft Surface devices" + depends on SURFACE_AGGREGATOR_REGISTRY + help + Driver for battery devices connected via/managed by the Surface System + Aggregator Module (SSAM). + + This driver provides battery-information and -status support for + Surface devices where said data is not exposed via the standard ACPI + devices. On those models (7th-generation), battery-information is + instead handled directly via SSAM client devices and this driver. + + Say M or Y here to include battery status support for 7th-generation + Microsoft Surface devices, i.e. Surface Pro 7, Surface Laptop 3, + Surface Book 3, and Surface Laptop Go. + +config CHARGER_SURFACE + tristate "AC driver for 7th-generation Microsoft Surface devices" + depends on SURFACE_AGGREGATOR_REGISTRY + help + Driver for AC devices connected via/managed by the Surface System + Aggregator Module (SSAM). + + This driver provides AC-information and -status support for Surface + devices where said data is not exposed via the standard ACPI devices. + On those models (7th-generation), AC-information is instead handled + directly via a SSAM client device and this driver. + + Say M or Y here to include AC status support for 7th-generation + Microsoft Surface devices, i.e. Surface Pro 7, Surface Laptop 3, + Surface Book 3, and Surface Laptop Go. + endif # POWER_SUPPLY diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile index 5e5fdbbef531..a7309a3d1a47 100644 --- a/drivers/power/supply/Makefile +++ b/drivers/power/supply/Makefile @@ -101,3 +101,5 @@ obj-$(CONFIG_CHARGER_BD99954) += bd99954-charger.o obj-$(CONFIG_CHARGER_WILCO) += wilco-charger.o obj-$(CONFIG_RN5T618_POWER) += rn5t618_power.o obj-$(CONFIG_BATTERY_ACER_A500) += acer_a500_battery.o +obj-$(CONFIG_BATTERY_SURFACE) += surface_battery.o +obj-$(CONFIG_CHARGER_SURFACE) += surface_charger.o diff --git a/drivers/power/supply/ab8500-bm.h b/drivers/power/supply/ab8500-bm.h new file mode 100644 index 000000000000..41c69a4f2a1f --- /dev/null +++ b/drivers/power/supply/ab8500-bm.h @@ -0,0 +1,733 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#ifndef _AB8500_CHARGER_H_ +#define _AB8500_CHARGER_H_ + +#include <linux/kernel.h> + +/* + * System control 2 register offsets. + * bank = 0x02 + */ +#define AB8500_MAIN_WDOG_CTRL_REG 0x01 +#define AB8500_LOW_BAT_REG 0x03 +#define AB8500_BATT_OK_REG 0x04 +/* + * USB/ULPI register offsets + * Bank : 0x5 + */ +#define AB8500_USB_LINE_STAT_REG 0x80 +#define AB8500_USB_LINE_CTRL2_REG 0x82 +#define AB8500_USB_LINK1_STAT_REG 0x94 + +/* + * Charger / status register offfsets + * Bank : 0x0B + */ +#define AB8500_CH_STATUS1_REG 0x00 +#define AB8500_CH_STATUS2_REG 0x01 +#define AB8500_CH_USBCH_STAT1_REG 0x02 +#define AB8500_CH_USBCH_STAT2_REG 0x03 +#define AB8540_CH_USBCH_STAT3_REG 0x04 +#define AB8500_CH_STAT_REG 0x05 + +/* + * Charger / control register offfsets + * Bank : 0x0B + */ +#define AB8500_CH_VOLT_LVL_REG 0x40 +#define AB8500_CH_VOLT_LVL_MAX_REG 0x41 /*Only in Cut2.0*/ +#define AB8500_CH_OPT_CRNTLVL_REG 0x42 +#define AB8500_CH_OPT_CRNTLVL_MAX_REG 0x43 /*Only in Cut2.0*/ +#define AB8500_CH_WD_TIMER_REG 0x50 +#define AB8500_CHARG_WD_CTRL 0x51 +#define AB8500_BTEMP_HIGH_TH 0x52 +#define AB8500_LED_INDICATOR_PWM_CTRL 0x53 +#define AB8500_LED_INDICATOR_PWM_DUTY 0x54 +#define AB8500_BATT_OVV 0x55 +#define AB8500_CHARGER_CTRL 0x56 +#define AB8500_BAT_CTRL_CURRENT_SOURCE 0x60 /*Only in Cut2.0*/ + +/* + * Charger / main control register offsets + * Bank : 0x0B + */ +#define AB8500_MCH_CTRL1 0x80 +#define AB8500_MCH_CTRL2 0x81 +#define AB8500_MCH_IPT_CURLVL_REG 0x82 +#define AB8500_CH_WD_REG 0x83 + +/* + * Charger / USB control register offsets + * Bank : 0x0B + */ +#define AB8500_USBCH_CTRL1_REG 0xC0 +#define AB8500_USBCH_CTRL2_REG 0xC1 +#define AB8500_USBCH_IPT_CRNTLVL_REG 0xC2 +#define AB8540_USB_PP_MODE_REG 0xC5 +#define AB8540_USB_PP_CHR_REG 0xC6 + +/* + * Gas Gauge register offsets + * Bank : 0x0C + */ +#define AB8500_GASG_CC_CTRL_REG 0x00 +#define AB8500_GASG_CC_ACCU1_REG 0x01 +#define AB8500_GASG_CC_ACCU2_REG 0x02 +#define AB8500_GASG_CC_ACCU3_REG 0x03 +#define AB8500_GASG_CC_ACCU4_REG 0x04 +#define AB8500_GASG_CC_SMPL_CNTRL_REG 0x05 +#define AB8500_GASG_CC_SMPL_CNTRH_REG 0x06 +#define AB8500_GASG_CC_SMPL_CNVL_REG 0x07 +#define AB8500_GASG_CC_SMPL_CNVH_REG 0x08 +#define AB8500_GASG_CC_CNTR_AVGOFF_REG 0x09 +#define AB8500_GASG_CC_OFFSET_REG 0x0A +#define AB8500_GASG_CC_NCOV_ACCU 0x10 +#define AB8500_GASG_CC_NCOV_ACCU_CTRL 0x11 +#define AB8500_GASG_CC_NCOV_ACCU_LOW 0x12 +#define AB8500_GASG_CC_NCOV_ACCU_MED 0x13 +#define AB8500_GASG_CC_NCOV_ACCU_HIGH 0x14 + +/* + * Interrupt register offsets + * Bank : 0x0E + */ +#define AB8500_IT_SOURCE2_REG 0x01 +#define AB8500_IT_SOURCE21_REG 0x14 + +/* + * RTC register offsets + * Bank: 0x0F + */ +#define AB8500_RTC_BACKUP_CHG_REG 0x0C +#define AB8500_RTC_CC_CONF_REG 0x01 +#define AB8500_RTC_CTRL_REG 0x0B +#define AB8500_RTC_CTRL1_REG 0x11 + +/* + * OTP register offsets + * Bank : 0x15 + */ +#define AB8500_OTP_CONF_15 0x0E + +/* GPADC constants from AB8500 spec, UM0836 */ +#define ADC_RESOLUTION 1024 +#define ADC_CH_MAIN_MIN 0 +#define ADC_CH_MAIN_MAX 20030 +#define ADC_CH_VBUS_MIN 0 +#define ADC_CH_VBUS_MAX 20030 +#define ADC_CH_VBAT_MIN 2300 +#define ADC_CH_VBAT_MAX 4800 +#define ADC_CH_BKBAT_MIN 0 +#define ADC_CH_BKBAT_MAX 3200 + +/* Main charge i/p current */ +#define MAIN_CH_IP_CUR_0P9A 0x80 +#define MAIN_CH_IP_CUR_1P0A 0x90 +#define MAIN_CH_IP_CUR_1P1A 0xA0 +#define MAIN_CH_IP_CUR_1P2A 0xB0 +#define MAIN_CH_IP_CUR_1P3A 0xC0 +#define MAIN_CH_IP_CUR_1P4A 0xD0 +#define MAIN_CH_IP_CUR_1P5A 0xE0 + +/* ChVoltLevel */ +#define CH_VOL_LVL_3P5 0x00 +#define CH_VOL_LVL_4P0 0x14 +#define CH_VOL_LVL_4P05 0x16 +#define CH_VOL_LVL_4P1 0x1B +#define CH_VOL_LVL_4P15 0x20 +#define CH_VOL_LVL_4P2 0x25 +#define CH_VOL_LVL_4P6 0x4D + +/* ChOutputCurrentLevel */ +#define CH_OP_CUR_LVL_0P1 0x00 +#define CH_OP_CUR_LVL_0P2 0x01 +#define CH_OP_CUR_LVL_0P3 0x02 +#define CH_OP_CUR_LVL_0P4 0x03 +#define CH_OP_CUR_LVL_0P5 0x04 +#define CH_OP_CUR_LVL_0P6 0x05 +#define CH_OP_CUR_LVL_0P7 0x06 +#define CH_OP_CUR_LVL_0P8 0x07 +#define CH_OP_CUR_LVL_0P9 0x08 +#define CH_OP_CUR_LVL_1P4 0x0D +#define CH_OP_CUR_LVL_1P5 0x0E +#define CH_OP_CUR_LVL_1P6 0x0F +#define CH_OP_CUR_LVL_2P 0x3F + +/* BTEMP High thermal limits */ +#define BTEMP_HIGH_TH_57_0 0x00 +#define BTEMP_HIGH_TH_52 0x01 +#define BTEMP_HIGH_TH_57_1 0x02 +#define BTEMP_HIGH_TH_62 0x03 + +/* current is mA */ +#define USB_0P1A 100 +#define USB_0P2A 200 +#define USB_0P3A 300 +#define USB_0P4A 400 +#define USB_0P5A 500 + +#define LOW_BAT_3P1V 0x20 +#define LOW_BAT_2P3V 0x00 +#define LOW_BAT_RESET 0x01 +#define LOW_BAT_ENABLE 0x01 + +/* Backup battery constants */ +#define BUP_ICH_SEL_50UA 0x00 +#define BUP_ICH_SEL_150UA 0x04 +#define BUP_ICH_SEL_300UA 0x08 +#define BUP_ICH_SEL_700UA 0x0C + +enum bup_vch_sel { + BUP_VCH_SEL_2P5V, + BUP_VCH_SEL_2P6V, + BUP_VCH_SEL_2P8V, + BUP_VCH_SEL_3P1V, + /* + * Note that the following 5 values 2.7v, 2.9v, 3.0v, 3.2v, 3.3v + * are only available on ab8540. You can't choose these 5 + * voltage on ab8500/ab8505/ab9540. + */ + BUP_VCH_SEL_2P7V, + BUP_VCH_SEL_2P9V, + BUP_VCH_SEL_3P0V, + BUP_VCH_SEL_3P2V, + BUP_VCH_SEL_3P3V, +}; + +#define BUP_VCH_RANGE 0x02 +#define VBUP33_VRTCN 0x01 + +/* Battery OVV constants */ +#define BATT_OVV_ENA 0x02 +#define BATT_OVV_TH_3P7 0x00 +#define BATT_OVV_TH_4P75 0x01 + +/* A value to indicate over voltage */ +#define BATT_OVV_VALUE 4750 + +/* VBUS OVV constants */ +#define VBUS_OVV_SELECT_MASK 0x78 +#define VBUS_OVV_SELECT_5P6V 0x00 +#define VBUS_OVV_SELECT_5P7V 0x08 +#define VBUS_OVV_SELECT_5P8V 0x10 +#define VBUS_OVV_SELECT_5P9V 0x18 +#define VBUS_OVV_SELECT_6P0V 0x20 +#define VBUS_OVV_SELECT_6P1V 0x28 +#define VBUS_OVV_SELECT_6P2V 0x30 +#define VBUS_OVV_SELECT_6P3V 0x38 + +#define VBUS_AUTO_IN_CURR_LIM_ENA 0x04 + +/* Fuel Gauge constants */ +#define RESET_ACCU 0x02 +#define READ_REQ 0x01 +#define CC_DEEP_SLEEP_ENA 0x02 +#define CC_PWR_UP_ENA 0x01 +#define CC_SAMPLES_40 0x28 +#define RD_NCONV_ACCU_REQ 0x01 +#define CC_CALIB 0x08 +#define CC_INTAVGOFFSET_ENA 0x10 +#define CC_MUXOFFSET 0x80 +#define CC_INT_CAL_N_AVG_MASK 0x60 +#define CC_INT_CAL_SAMPLES_16 0x40 +#define CC_INT_CAL_SAMPLES_8 0x20 +#define CC_INT_CAL_SAMPLES_4 0x00 + +/* RTC constants */ +#define RTC_BUP_CH_ENA 0x10 + +/* BatCtrl Current Source Constants */ +#define BAT_CTRL_7U_ENA 0x01 +#define BAT_CTRL_20U_ENA 0x02 +#define BAT_CTRL_18U_ENA 0x01 +#define BAT_CTRL_16U_ENA 0x02 +#define BAT_CTRL_CMP_ENA 0x04 +#define FORCE_BAT_CTRL_CMP_HIGH 0x08 +#define BAT_CTRL_PULL_UP_ENA 0x10 + +/* Battery type */ +#define BATTERY_UNKNOWN 00 + +/* Registers for pcut feature in ab8505 and ab9540 */ +#define AB8505_RTC_PCUT_CTL_STATUS_REG 0x12 +#define AB8505_RTC_PCUT_TIME_REG 0x13 +#define AB8505_RTC_PCUT_MAX_TIME_REG 0x14 +#define AB8505_RTC_PCUT_FLAG_TIME_REG 0x15 +#define AB8505_RTC_PCUT_RESTART_REG 0x16 +#define AB8505_RTC_PCUT_DEBOUNCE_REG 0x17 + +/* USB Power Path constants for ab8540 */ +#define BUS_VSYS_VOL_SELECT_MASK 0x06 +#define BUS_VSYS_VOL_SELECT_3P6V 0x00 +#define BUS_VSYS_VOL_SELECT_3P325V 0x02 +#define BUS_VSYS_VOL_SELECT_3P9V 0x04 +#define BUS_VSYS_VOL_SELECT_4P3V 0x06 +#define BUS_POWER_PATH_MODE_ENA 0x01 +#define BUS_PP_PRECHG_CURRENT_MASK 0x0E +#define BUS_POWER_PATH_PRECHG_ENA 0x01 + +/* + * ADC for the battery thermistor. + * When using the ABx500_ADC_THERM_BATCTRL the battery ID resistor is combined + * with a NTC resistor to both identify the battery and to measure its + * temperature. Different phone manufactures uses different techniques to both + * identify the battery and to read its temperature. + */ +enum abx500_adc_therm { + ABx500_ADC_THERM_BATCTRL, + ABx500_ADC_THERM_BATTEMP, +}; + +/** + * struct abx500_res_to_temp - defines one point in a temp to res curve. To + * be used in battery packs that combines the identification resistor with a + * NTC resistor. + * @temp: battery pack temperature in Celsius + * @resist: NTC resistor net total resistance + */ +struct abx500_res_to_temp { + int temp; + int resist; +}; + +/** + * struct abx500_v_to_cap - Table for translating voltage to capacity + * @voltage: Voltage in mV + * @capacity: Capacity in percent + */ +struct abx500_v_to_cap { + int voltage; + int capacity; +}; + +/* Forward declaration */ +struct abx500_fg; + +/** + * struct abx500_fg_parameters - Fuel gauge algorithm parameters, in seconds + * if not specified + * @recovery_sleep_timer: Time between measurements while recovering + * @recovery_total_time: Total recovery time + * @init_timer: Measurement interval during startup + * @init_discard_time: Time we discard voltage measurement at startup + * @init_total_time: Total init time during startup + * @high_curr_time: Time current has to be high to go to recovery + * @accu_charging: FG accumulation time while charging + * @accu_high_curr: FG accumulation time in high current mode + * @high_curr_threshold: High current threshold, in mA + * @lowbat_threshold: Low battery threshold, in mV + * @overbat_threshold: Over battery threshold, in mV + * @battok_falling_th_sel0 Threshold in mV for battOk signal sel0 + * Resolution in 50 mV step. + * @battok_raising_th_sel1 Threshold in mV for battOk signal sel1 + * Resolution in 50 mV step. + * @user_cap_limit Capacity reported from user must be within this + * limit to be considered as sane, in percentage + * points. + * @maint_thres This is the threshold where we stop reporting + * battery full while in maintenance, in per cent + * @pcut_enable: Enable power cut feature in ab8505 + * @pcut_max_time: Max time threshold + * @pcut_flag_time: Flagtime threshold + * @pcut_max_restart: Max number of restarts + * @pcut_debounce_time: Sets battery debounce time + */ +struct abx500_fg_parameters { + int recovery_sleep_timer; + int recovery_total_time; + int init_timer; + int init_discard_time; + int init_total_time; + int high_curr_time; + int accu_charging; + int accu_high_curr; + int high_curr_threshold; + int lowbat_threshold; + int overbat_threshold; + int battok_falling_th_sel0; + int battok_raising_th_sel1; + int user_cap_limit; + int maint_thres; + bool pcut_enable; + u8 pcut_max_time; + u8 pcut_flag_time; + u8 pcut_max_restart; + u8 pcut_debounce_time; +}; + +/** + * struct abx500_charger_maximization - struct used by the board config. + * @use_maxi: Enable maximization for this battery type + * @maxi_chg_curr: Maximum charger current allowed + * @maxi_wait_cycles: cycles to wait before setting charger current + * @charger_curr_step delta between two charger current settings (mA) + */ +struct abx500_maxim_parameters { + bool ena_maxi; + int chg_curr; + int wait_cycles; + int charger_curr_step; +}; + +/** + * struct abx500_battery_type - different batteries supported + * @name: battery technology + * @resis_high: battery upper resistance limit + * @resis_low: battery lower resistance limit + * @charge_full_design: Maximum battery capacity in mAh + * @nominal_voltage: Nominal voltage of the battery in mV + * @termination_vol: max voltage upto which battery can be charged + * @termination_curr battery charging termination current in mA + * @recharge_cap battery capacity limit that will trigger a new + * full charging cycle in the case where maintenan- + * -ce charging has been disabled + * @normal_cur_lvl: charger current in normal state in mA + * @normal_vol_lvl: charger voltage in normal state in mV + * @maint_a_cur_lvl: charger current in maintenance A state in mA + * @maint_a_vol_lvl: charger voltage in maintenance A state in mV + * @maint_a_chg_timer_h: charge time in maintenance A state + * @maint_b_cur_lvl: charger current in maintenance B state in mA + * @maint_b_vol_lvl: charger voltage in maintenance B state in mV + * @maint_b_chg_timer_h: charge time in maintenance B state + * @low_high_cur_lvl: charger current in temp low/high state in mA + * @low_high_vol_lvl: charger voltage in temp low/high state in mV' + * @battery_resistance: battery inner resistance in mOhm. + * @n_r_t_tbl_elements: number of elements in r_to_t_tbl + * @r_to_t_tbl: table containing resistance to temp points + * @n_v_cap_tbl_elements: number of elements in v_to_cap_tbl + * @v_to_cap_tbl: Voltage to capacity (in %) table + * @n_batres_tbl_elements number of elements in the batres_tbl + * @batres_tbl battery internal resistance vs temperature table + */ +struct abx500_battery_type { + int name; + int resis_high; + int resis_low; + int charge_full_design; + int nominal_voltage; + int termination_vol; + int termination_curr; + int recharge_cap; + int normal_cur_lvl; + int normal_vol_lvl; + int maint_a_cur_lvl; + int maint_a_vol_lvl; + int maint_a_chg_timer_h; + int maint_b_cur_lvl; + int maint_b_vol_lvl; + int maint_b_chg_timer_h; + int low_high_cur_lvl; + int low_high_vol_lvl; + int battery_resistance; + int n_temp_tbl_elements; + const struct abx500_res_to_temp *r_to_t_tbl; + int n_v_cap_tbl_elements; + const struct abx500_v_to_cap *v_to_cap_tbl; + int n_batres_tbl_elements; + const struct batres_vs_temp *batres_tbl; +}; + +/** + * struct abx500_bm_capacity_levels - abx500 capacity level data + * @critical: critical capacity level in percent + * @low: low capacity level in percent + * @normal: normal capacity level in percent + * @high: high capacity level in percent + * @full: full capacity level in percent + */ +struct abx500_bm_capacity_levels { + int critical; + int low; + int normal; + int high; + int full; +}; + +/** + * struct abx500_bm_charger_parameters - Charger specific parameters + * @usb_volt_max: maximum allowed USB charger voltage in mV + * @usb_curr_max: maximum allowed USB charger current in mA + * @ac_volt_max: maximum allowed AC charger voltage in mV + * @ac_curr_max: maximum allowed AC charger current in mA + */ +struct abx500_bm_charger_parameters { + int usb_volt_max; + int usb_curr_max; + int ac_volt_max; + int ac_curr_max; +}; + +/** + * struct abx500_bm_data - abx500 battery management data + * @temp_under under this temp, charging is stopped + * @temp_low between this temp and temp_under charging is reduced + * @temp_high between this temp and temp_over charging is reduced + * @temp_over over this temp, charging is stopped + * @temp_now present battery temperature + * @temp_interval_chg temperature measurement interval in s when charging + * @temp_interval_nochg temperature measurement interval in s when not charging + * @main_safety_tmr_h safety timer for main charger + * @usb_safety_tmr_h safety timer for usb charger + * @bkup_bat_v voltage which we charge the backup battery with + * @bkup_bat_i current which we charge the backup battery with + * @no_maintenance indicates that maintenance charging is disabled + * @capacity_scaling indicates whether capacity scaling is to be used + * @abx500_adc_therm placement of thermistor, batctrl or battemp adc + * @chg_unknown_bat flag to enable charging of unknown batteries + * @enable_overshoot flag to enable VBAT overshoot control + * @auto_trig flag to enable auto adc trigger + * @fg_res resistance of FG resistor in 0.1mOhm + * @n_btypes number of elements in array bat_type + * @batt_id index of the identified battery in array bat_type + * @interval_charging charge alg cycle period time when charging (sec) + * @interval_not_charging charge alg cycle period time when not charging (sec) + * @temp_hysteresis temperature hysteresis + * @gnd_lift_resistance Battery ground to phone ground resistance (mOhm) + * @n_chg_out_curr number of elements in array chg_output_curr + * @n_chg_in_curr number of elements in array chg_input_curr + * @chg_output_curr charger output current level map + * @chg_input_curr charger input current level map + * @maxi maximization parameters + * @cap_levels capacity in percent for the different capacity levels + * @bat_type table of supported battery types + * @chg_params charger parameters + * @fg_params fuel gauge parameters + */ +struct abx500_bm_data { + int temp_under; + int temp_low; + int temp_high; + int temp_over; + int temp_now; + int temp_interval_chg; + int temp_interval_nochg; + int main_safety_tmr_h; + int usb_safety_tmr_h; + int bkup_bat_v; + int bkup_bat_i; + bool autopower_cfg; + bool ac_enabled; + bool usb_enabled; + bool no_maintenance; + bool capacity_scaling; + bool chg_unknown_bat; + bool enable_overshoot; + bool auto_trig; + enum abx500_adc_therm adc_therm; + int fg_res; + int n_btypes; + int batt_id; + int interval_charging; + int interval_not_charging; + int temp_hysteresis; + int gnd_lift_resistance; + int n_chg_out_curr; + int n_chg_in_curr; + int *chg_output_curr; + int *chg_input_curr; + const struct abx500_maxim_parameters *maxi; + const struct abx500_bm_capacity_levels *cap_levels; + struct abx500_battery_type *bat_type; + const struct abx500_bm_charger_parameters *chg_params; + const struct abx500_fg_parameters *fg_params; +}; + +enum { + NTC_EXTERNAL = 0, + NTC_INTERNAL, +}; + +/** + * struct res_to_temp - defines one point in a temp to res curve. To + * be used in battery packs that combines the identification resistor with a + * NTC resistor. + * @temp: battery pack temperature in Celsius + * @resist: NTC resistor net total resistance + */ +struct res_to_temp { + int temp; + int resist; +}; + +/** + * struct batres_vs_temp - defines one point in a temp vs battery internal + * resistance curve. + * @temp: battery pack temperature in Celsius + * @resist: battery internal reistance in mOhm + */ +struct batres_vs_temp { + int temp; + int resist; +}; + +/* Forward declaration */ +struct ab8500_fg; + +/** + * struct ab8500_fg_parameters - Fuel gauge algorithm parameters, in seconds + * if not specified + * @recovery_sleep_timer: Time between measurements while recovering + * @recovery_total_time: Total recovery time + * @init_timer: Measurement interval during startup + * @init_discard_time: Time we discard voltage measurement at startup + * @init_total_time: Total init time during startup + * @high_curr_time: Time current has to be high to go to recovery + * @accu_charging: FG accumulation time while charging + * @accu_high_curr: FG accumulation time in high current mode + * @high_curr_threshold: High current threshold, in mA + * @lowbat_threshold: Low battery threshold, in mV + * @battok_falling_th_sel0 Threshold in mV for battOk signal sel0 + * Resolution in 50 mV step. + * @battok_raising_th_sel1 Threshold in mV for battOk signal sel1 + * Resolution in 50 mV step. + * @user_cap_limit Capacity reported from user must be within this + * limit to be considered as sane, in percentage + * points. + * @maint_thres This is the threshold where we stop reporting + * battery full while in maintenance, in per cent + * @pcut_enable: Enable power cut feature in ab8505 + * @pcut_max_time: Max time threshold + * @pcut_flag_time: Flagtime threshold + * @pcut_max_restart: Max number of restarts + * @pcut_debunce_time: Sets battery debounce time + */ +struct ab8500_fg_parameters { + int recovery_sleep_timer; + int recovery_total_time; + int init_timer; + int init_discard_time; + int init_total_time; + int high_curr_time; + int accu_charging; + int accu_high_curr; + int high_curr_threshold; + int lowbat_threshold; + int battok_falling_th_sel0; + int battok_raising_th_sel1; + int user_cap_limit; + int maint_thres; + bool pcut_enable; + u8 pcut_max_time; + u8 pcut_flag_time; + u8 pcut_max_restart; + u8 pcut_debunce_time; +}; + +/** + * struct ab8500_charger_maximization - struct used by the board config. + * @use_maxi: Enable maximization for this battery type + * @maxi_chg_curr: Maximum charger current allowed + * @maxi_wait_cycles: cycles to wait before setting charger current + * @charger_curr_step delta between two charger current settings (mA) + */ +struct ab8500_maxim_parameters { + bool ena_maxi; + int chg_curr; + int wait_cycles; + int charger_curr_step; +}; + +/** + * struct ab8500_bm_capacity_levels - ab8500 capacity level data + * @critical: critical capacity level in percent + * @low: low capacity level in percent + * @normal: normal capacity level in percent + * @high: high capacity level in percent + * @full: full capacity level in percent + */ +struct ab8500_bm_capacity_levels { + int critical; + int low; + int normal; + int high; + int full; +}; + +/** + * struct ab8500_bm_charger_parameters - Charger specific parameters + * @usb_volt_max: maximum allowed USB charger voltage in mV + * @usb_curr_max: maximum allowed USB charger current in mA + * @ac_volt_max: maximum allowed AC charger voltage in mV + * @ac_curr_max: maximum allowed AC charger current in mA + */ +struct ab8500_bm_charger_parameters { + int usb_volt_max; + int usb_curr_max; + int ac_volt_max; + int ac_curr_max; +}; + +/** + * struct ab8500_bm_data - ab8500 battery management data + * @temp_under under this temp, charging is stopped + * @temp_low between this temp and temp_under charging is reduced + * @temp_high between this temp and temp_over charging is reduced + * @temp_over over this temp, charging is stopped + * @temp_interval_chg temperature measurement interval in s when charging + * @temp_interval_nochg temperature measurement interval in s when not charging + * @main_safety_tmr_h safety timer for main charger + * @usb_safety_tmr_h safety timer for usb charger + * @bkup_bat_v voltage which we charge the backup battery with + * @bkup_bat_i current which we charge the backup battery with + * @no_maintenance indicates that maintenance charging is disabled + * @capacity_scaling indicates whether capacity scaling is to be used + * @adc_therm placement of thermistor, batctrl or battemp adc + * @chg_unknown_bat flag to enable charging of unknown batteries + * @enable_overshoot flag to enable VBAT overshoot control + * @fg_res resistance of FG resistor in 0.1mOhm + * @n_btypes number of elements in array bat_type + * @batt_id index of the identified battery in array bat_type + * @interval_charging charge alg cycle period time when charging (sec) + * @interval_not_charging charge alg cycle period time when not charging (sec) + * @temp_hysteresis temperature hysteresis + * @gnd_lift_resistance Battery ground to phone ground resistance (mOhm) + * @maxi: maximization parameters + * @cap_levels capacity in percent for the different capacity levels + * @bat_type table of supported battery types + * @chg_params charger parameters + * @fg_params fuel gauge parameters + */ +struct ab8500_bm_data { + int temp_under; + int temp_low; + int temp_high; + int temp_over; + int temp_interval_chg; + int temp_interval_nochg; + int main_safety_tmr_h; + int usb_safety_tmr_h; + int bkup_bat_v; + int bkup_bat_i; + bool no_maintenance; + bool capacity_scaling; + bool chg_unknown_bat; + bool enable_overshoot; + enum abx500_adc_therm adc_therm; + int fg_res; + int n_btypes; + int batt_id; + int interval_charging; + int interval_not_charging; + int temp_hysteresis; + int gnd_lift_resistance; + const struct ab8500_maxim_parameters *maxi; + const struct ab8500_bm_capacity_levels *cap_levels; + const struct ab8500_bm_charger_parameters *chg_params; + const struct ab8500_fg_parameters *fg_params; +}; + +extern struct abx500_bm_data ab8500_bm_data; + +void ab8500_charger_usb_state_changed(u8 bm_usb_state, u16 mA); +struct ab8500_fg *ab8500_fg_get(void); +int ab8500_fg_inst_curr_blocking(struct ab8500_fg *dev); +int ab8500_fg_inst_curr_start(struct ab8500_fg *di); +int ab8500_fg_inst_curr_finalize(struct ab8500_fg *di, int *res); +int ab8500_fg_inst_curr_started(struct ab8500_fg *di); +int ab8500_fg_inst_curr_done(struct ab8500_fg *di); +int ab8500_bm_of_probe(struct device *dev, + struct device_node *np, + struct abx500_bm_data *bm); + +#endif /* _AB8500_CHARGER_H_ */ diff --git a/drivers/power/supply/ab8500-chargalg.h b/drivers/power/supply/ab8500-chargalg.h new file mode 100644 index 000000000000..94a6f9068bc5 --- /dev/null +++ b/drivers/power/supply/ab8500-chargalg.h @@ -0,0 +1,51 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) ST-Ericsson SA 2012 + * Author: Johan Gardsmark <johan.gardsmark@stericsson.com> for ST-Ericsson. + */ + +#ifndef _AB8500_CHARGALG_H_ +#define _AB8500_CHARGALG_H_ + +#include <linux/power_supply.h> + +/* + * Valid only for supplies of type: + * - POWER_SUPPLY_TYPE_MAINS, + * - POWER_SUPPLY_TYPE_USB, + * because only them store as drv_data pointer to struct ux500_charger. + */ +#define psy_to_ux500_charger(x) power_supply_get_drvdata(psy) + +/* Forward declaration */ +struct ux500_charger; + +struct ux500_charger_ops { + int (*enable) (struct ux500_charger *, int, int, int); + int (*check_enable) (struct ux500_charger *, int, int); + int (*kick_wd) (struct ux500_charger *); + int (*update_curr) (struct ux500_charger *, int); +}; + +/** + * struct ux500_charger - power supply ux500 charger sub class + * @psy power supply base class + * @ops ux500 charger operations + * @max_out_volt maximum output charger voltage in mV + * @max_out_curr maximum output charger current in mA + * @enabled indicates if this charger is used or not + * @external external charger unit (pm2xxx) + */ +struct ux500_charger { + struct power_supply *psy; + struct ux500_charger_ops ops; + int max_out_volt; + int max_out_curr; + int wdt_refresh; + bool enabled; + bool external; +}; + +extern struct blocking_notifier_head charger_notifier_list; + +#endif /* _AB8500_CHARGALG_H_ */ diff --git a/drivers/power/supply/ab8500_bmdata.c b/drivers/power/supply/ab8500_bmdata.c index f6a66979cbb5..c2b8c0bb77e2 100644 --- a/drivers/power/supply/ab8500_bmdata.c +++ b/drivers/power/supply/ab8500_bmdata.c @@ -4,7 +4,8 @@ #include <linux/of.h> #include <linux/mfd/abx500.h> #include <linux/mfd/abx500/ab8500.h> -#include <linux/mfd/abx500/ab8500-bm.h> + +#include "ab8500-bm.h" /* * These are the defined batteries that uses a NTC and ID resistor placed diff --git a/drivers/power/supply/ab8500_btemp.c b/drivers/power/supply/ab8500_btemp.c index d20345386b1e..fdfcd59fc43e 100644 --- a/drivers/power/supply/ab8500_btemp.c +++ b/drivers/power/supply/ab8500_btemp.c @@ -25,9 +25,10 @@ #include <linux/mfd/core.h> #include <linux/mfd/abx500.h> #include <linux/mfd/abx500/ab8500.h> -#include <linux/mfd/abx500/ab8500-bm.h> #include <linux/iio/consumer.h> +#include "ab8500-bm.h" + #define VTVOUT_V 1800 #define BTEMP_THERMAL_LOW_LIMIT -10 @@ -120,16 +121,6 @@ static enum power_supply_property ab8500_btemp_props[] = { 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) -{ - return list_first_entry(&ab8500_btemp_list, struct ab8500_btemp, node); -} -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 @@ -754,7 +745,7 @@ static void ab8500_btemp_periodic(struct ab8500_btemp *di, * * Returns battery temperature */ -int ab8500_btemp_get_temp(struct ab8500_btemp *di) +static int ab8500_btemp_get_temp(struct ab8500_btemp *di) { int temp = 0; @@ -790,19 +781,6 @@ int ab8500_btemp_get_temp(struct ab8500_btemp *di) } 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 @@ -991,7 +969,6 @@ static const struct power_supply_desc ab8500_btemp_desc = { 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 device *dev = &pdev->dev; struct ab8500_btemp *di; @@ -1002,18 +979,12 @@ static int ab8500_btemp_probe(struct platform_device *pdev) if (!di) return -ENOMEM; - if (!plat) { - dev_err(dev, "no battery management data supplied\n"); - return -EINVAL; - } - di->bm = plat; + di->bm = &ab8500_bm_data; - if (np) { - ret = ab8500_bm_of_probe(dev, np, di->bm); - if (ret) { - dev_err(dev, "failed to get battery information\n"); - return ret; - } + ret = ab8500_bm_of_probe(dev, np, di->bm); + if (ret) { + dev_err(dev, "failed to get battery information\n"); + return ret; } /* get parent data */ diff --git a/drivers/power/supply/ab8500_charger.c b/drivers/power/supply/ab8500_charger.c index ac77c8882d17..a9be10eb2c22 100644 --- a/drivers/power/supply/ab8500_charger.c +++ b/drivers/power/supply/ab8500_charger.c @@ -28,12 +28,13 @@ #include <linux/mfd/core.h> #include <linux/mfd/abx500/ab8500.h> #include <linux/mfd/abx500.h> -#include <linux/mfd/abx500/ab8500-bm.h> -#include <linux/mfd/abx500/ux500_chargalg.h> #include <linux/usb/otg.h> #include <linux/mutex.h> #include <linux/iio/consumer.h> +#include "ab8500-bm.h" +#include "ab8500-chargalg.h" + /* Charger constants */ #define NO_PW_CONN 0 #define AC_PW_CONN 1 @@ -3344,7 +3345,6 @@ static const struct power_supply_desc ab8500_usb_chg_desc = { 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; @@ -3354,21 +3354,14 @@ static int ab8500_charger_probe(struct platform_device *pdev) if (!di) return -ENOMEM; - if (!plat) { - dev_err(dev, "no battery management data supplied\n"); - return -EINVAL; - } - di->bm = plat; + di->bm = &ab8500_bm_data; - if (np) { - ret = ab8500_bm_of_probe(dev, np, di->bm); - if (ret) { - dev_err(dev, "failed to get battery information\n"); - return ret; - } - di->autopower_cfg = of_property_read_bool(np, "autopower_cfg"); - } else - di->autopower_cfg = false; + ret = ab8500_bm_of_probe(dev, np, di->bm); + if (ret) { + dev_err(dev, "failed to get battery information\n"); + return ret; + } + di->autopower_cfg = of_property_read_bool(np, "autopower_cfg"); /* get parent data */ di->dev = dev; diff --git a/drivers/power/supply/ab8500_fg.c b/drivers/power/supply/ab8500_fg.c index 06ff42c71f24..0c7c01a0d979 100644 --- a/drivers/power/supply/ab8500_fg.c +++ b/drivers/power/supply/ab8500_fg.c @@ -31,10 +31,11 @@ #include <linux/mfd/core.h> #include <linux/mfd/abx500.h> #include <linux/mfd/abx500/ab8500.h> -#include <linux/mfd/abx500/ab8500-bm.h> #include <linux/iio/consumer.h> #include <linux/kernel.h> +#include "ab8500-bm.h" + #define MILLI_TO_MICRO 1000 #define FG_LSB_IN_MA 1627 #define QLSB_NANO_AMP_HOURS_X10 1071 @@ -3026,7 +3027,6 @@ static const struct power_supply_desc ab8500_fg_desc = { 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 device *dev = &pdev->dev; struct ab8500_fg *di; @@ -3037,18 +3037,12 @@ static int ab8500_fg_probe(struct platform_device *pdev) if (!di) return -ENOMEM; - if (!plat) { - dev_err(dev, "no battery management data supplied\n"); - return -EINVAL; - } - di->bm = plat; + di->bm = &ab8500_bm_data; - if (np) { - ret = ab8500_bm_of_probe(dev, np, di->bm); - if (ret) { - dev_err(dev, "failed to get battery information\n"); - return ret; - } + ret = ab8500_bm_of_probe(dev, np, di->bm); + if (ret) { + dev_err(dev, "failed to get battery information\n"); + return ret; } mutex_init(&di->cc_lock); diff --git a/drivers/power/supply/abx500_chargalg.c b/drivers/power/supply/abx500_chargalg.c index a9d84d845f24..f5b792243727 100644 --- a/drivers/power/supply/abx500_chargalg.c +++ b/drivers/power/supply/abx500_chargalg.c @@ -28,10 +28,11 @@ #include <linux/mfd/core.h> #include <linux/mfd/abx500.h> #include <linux/mfd/abx500/ab8500.h> -#include <linux/mfd/abx500/ux500_chargalg.h> -#include <linux/mfd/abx500/ab8500-bm.h> #include <linux/notifier.h> +#include "ab8500-bm.h" +#include "ab8500-chargalg.h" + /* Watchdog kick interval */ #define CHG_WD_INTERVAL (6 * HZ) @@ -1980,7 +1981,6 @@ static const struct power_supply_desc abx500_chargalg_desc = { 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; @@ -1991,18 +1991,12 @@ static int abx500_chargalg_probe(struct platform_device *pdev) return -ENOMEM; } - if (!plat) { - dev_err(&pdev->dev, "no battery management data supplied\n"); - return -EINVAL; - } - di->bm = plat; + di->bm = &ab8500_bm_data; - 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; - } + 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 */ diff --git a/drivers/power/supply/act8945a_charger.c b/drivers/power/supply/act8945a_charger.c index 5f3eb6941d05..e9b5f4283772 100644 --- a/drivers/power/supply/act8945a_charger.c +++ b/drivers/power/supply/act8945a_charger.c @@ -18,7 +18,7 @@ static const char *act8945a_charger_model = "ACT8945A"; static const char *act8945a_charger_manufacturer = "Active-semi"; -/** +/* * ACT8945A Charger Register Map */ diff --git a/drivers/power/supply/axp20x_usb_power.c b/drivers/power/supply/axp20x_usb_power.c index e954970b50e6..a1d110f7ddce 100644 --- a/drivers/power/supply/axp20x_usb_power.c +++ b/drivers/power/supply/axp20x_usb_power.c @@ -619,8 +619,10 @@ static int axp20x_usb_power_probe(struct platform_device *pdev) if (power->axp20x_id == AXP813_ID) { /* Enable USB Battery Charging specification detection */ - regmap_update_bits(axp20x->regmap, AXP288_BC_GLOBAL, + ret = regmap_update_bits(axp20x->regmap, AXP288_BC_GLOBAL, AXP813_BC_EN, AXP813_BC_EN); + if (ret) + return ret; } psy_cfg.of_node = pdev->dev.of_node; diff --git a/drivers/power/supply/bq256xx_charger.c b/drivers/power/supply/bq256xx_charger.c index 2ab5ba4af92b..f501ecd49202 100644 --- a/drivers/power/supply/bq256xx_charger.c +++ b/drivers/power/supply/bq256xx_charger.c @@ -202,6 +202,8 @@ enum bq256xx_id { * @client: i2c client structure * @regmap: register map structure * @dev: device structure + * @charger: power supply registered for the charger + * @battery: power supply registered for the battery * @lock: mutex lock structure * * @usb2_phy: usb_phy identifier diff --git a/drivers/power/supply/bq25980_charger.c b/drivers/power/supply/bq25980_charger.c index 530ff4025b31..0008c229fd9c 100644 --- a/drivers/power/supply/bq25980_charger.c +++ b/drivers/power/supply/bq25980_charger.c @@ -606,33 +606,6 @@ static int bq25980_get_state(struct bq25980_device *bq, return 0; } -static int bq25980_set_battery_property(struct power_supply *psy, - enum power_supply_property psp, - const union power_supply_propval *val) -{ - struct bq25980_device *bq = power_supply_get_drvdata(psy); - int ret = 0; - - switch (psp) { - case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: - ret = bq25980_set_const_charge_curr(bq, val->intval); - if (ret) - return ret; - break; - - case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: - ret = bq25980_set_const_charge_volt(bq, val->intval); - if (ret) - return ret; - break; - - default: - return -EINVAL; - } - - return ret; -} - static int bq25980_get_battery_property(struct power_supply *psy, enum power_supply_property psp, union power_supply_propval *val) @@ -701,6 +674,18 @@ static int bq25980_set_charger_property(struct power_supply *psy, return ret; break; + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: + ret = bq25980_set_const_charge_curr(bq, val->intval); + if (ret) + return ret; + break; + + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: + ret = bq25980_set_const_charge_volt(bq, val->intval); + if (ret) + return ret; + break; + default: return -EINVAL; } @@ -922,7 +907,6 @@ static struct power_supply_desc bq25980_battery_desc = { .name = "bq25980-battery", .type = POWER_SUPPLY_TYPE_BATTERY, .get_property = bq25980_get_battery_property, - .set_property = bq25980_set_battery_property, .properties = bq25980_battery_props, .num_properties = ARRAY_SIZE(bq25980_battery_props), .property_is_writeable = bq25980_property_is_writeable, diff --git a/drivers/power/supply/bq27xxx_battery.c b/drivers/power/supply/bq27xxx_battery.c index 4c4a7b1c64c5..7e5e24b585d8 100644 --- a/drivers/power/supply/bq27xxx_battery.c +++ b/drivers/power/supply/bq27xxx_battery.c @@ -39,6 +39,7 @@ * https://www.ti.com/product/bq27z561 * https://www.ti.com/product/bq28z610 * https://www.ti.com/product/bq34z100-g1 + * https://www.ti.com/product/bq78z100 */ #include <linux/device.h> @@ -515,6 +516,27 @@ static u8 [BQ27XXX_REG_DCAP] = 0x3c, [BQ27XXX_REG_AP] = 0x22, BQ27XXX_DM_REG_ROWS, + }, + bq78z100_regs[BQ27XXX_REG_MAX] = { + [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] = 0x18, + [BQ27XXX_REG_TTES] = 0x1c, + [BQ27XXX_REG_TTECP] = INVALID_REG_ADDR, + [BQ27XXX_REG_NAC] = INVALID_REG_ADDR, + [BQ27XXX_REG_RC] = 0x10, + [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] = 0x22, + BQ27XXX_DM_REG_ROWS, }; static enum power_supply_property bq27000_props[] = { @@ -813,6 +835,26 @@ static enum power_supply_property bq34z100_props[] = { POWER_SUPPLY_PROP_MANUFACTURER, }; +static enum power_supply_property bq78z100_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_CAPACITY_LEVEL, + POWER_SUPPLY_PROP_TEMP, + POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW, + POWER_SUPPLY_PROP_TIME_TO_FULL_NOW, + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_CHARGE_FULL, + POWER_SUPPLY_PROP_CHARGE_NOW, + POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, + POWER_SUPPLY_PROP_CYCLE_COUNT, + POWER_SUPPLY_PROP_POWER_AVG, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_MANUFACTURER, +}; + struct bq27xxx_dm_reg { u8 subclass_id; u8 offset; @@ -911,6 +953,7 @@ static struct bq27xxx_dm_reg bq27621_dm_regs[] = { #define bq27z561_dm_regs 0 #define bq28z610_dm_regs 0 #define bq34z100_dm_regs 0 +#define bq78z100_dm_regs 0 #define BQ27XXX_O_ZERO BIT(0) #define BQ27XXX_O_OTDC BIT(1) /* has OTC/OTD overtemperature flags */ @@ -969,6 +1012,7 @@ static struct { [BQ28Z610] = BQ27XXX_DATA(bq28z610, 0 , BQ27Z561_O_BITS), [BQ34Z100] = BQ27XXX_DATA(bq34z100, 0 , BQ27XXX_O_OTDC | BQ27XXX_O_SOC_SI | \ BQ27XXX_O_HAS_CI | BQ27XXX_O_MUL_CHEM), + [BQ78Z100] = BQ27XXX_DATA(bq78z100, 0 , BQ27Z561_O_BITS), }; static DEFINE_MUTEX(bq27xxx_list_lock); @@ -1662,27 +1706,6 @@ static int bq27xxx_battery_read_time(struct bq27xxx_device_info *di, u8 reg) } /* - * 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->opts & BQ27XXX_O_ZERO) - 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) @@ -1769,8 +1792,6 @@ void bq27xxx_battery_update(struct bq27xxx_device_info *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) @@ -1800,14 +1821,27 @@ static void bq27xxx_battery_poll(struct work_struct *work) schedule_delayed_work(&di->work, poll_interval * HZ); } +static bool bq27xxx_battery_is_full(struct bq27xxx_device_info *di, int flags) +{ + if (di->opts & BQ27XXX_O_ZERO) + return (flags & BQ27000_FLAG_FC); + else if (di->opts & BQ27Z561_O_BITS) + return (flags & BQ27Z561_FLAG_FC); + else + return (flags & BQ27XXX_FLAG_FC); +} + /* - * Return the battery average current in µA + * Return the battery average current in µA and the status * 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) +static int bq27xxx_battery_current_and_status( + struct bq27xxx_device_info *di, + union power_supply_propval *val_curr, + union power_supply_propval *val_status) { + bool single_flags = (di->opts & BQ27XXX_O_ZERO); int curr; int flags; @@ -1817,55 +1851,66 @@ static int bq27xxx_battery_current(struct bq27xxx_device_info *di, return curr; } + flags = bq27xxx_read(di, BQ27XXX_REG_FLAGS, single_flags); + if (flags < 0) { + dev_err(di->dev, "error reading flags\n"); + return flags; + } + if (di->opts & BQ27XXX_O_ZERO) { - flags = bq27xxx_read(di, BQ27XXX_REG_FLAGS, true); if (!(flags & BQ27000_FLAG_CHGS)) { dev_dbg(di->dev, "negative current!\n"); curr = -curr; } - val->intval = curr * BQ27XXX_CURRENT_CONSTANT / BQ27XXX_RS; + curr = curr * BQ27XXX_CURRENT_CONSTANT / BQ27XXX_RS; } else { /* Other gauges return signed value */ - val->intval = -(int)((s16)curr) * 1000; + curr = (int)((s16)curr) * 1000; + } + + if (val_curr) + val_curr->intval = curr; + + if (val_status) { + if (curr > 0) { + val_status->intval = POWER_SUPPLY_STATUS_CHARGING; + } else if (curr < 0) { + val_status->intval = POWER_SUPPLY_STATUS_DISCHARGING; + } else { + if (bq27xxx_battery_is_full(di, flags)) + val_status->intval = POWER_SUPPLY_STATUS_FULL; + else + val_status->intval = + POWER_SUPPLY_STATUS_NOT_CHARGING; + } } return 0; } -static int bq27xxx_battery_status(struct bq27xxx_device_info *di, - union power_supply_propval *val) +/* + * Get the average power in µW + * Return < 0 if something fails. + */ +static int bq27xxx_battery_pwr_avg(struct bq27xxx_device_info *di, + union power_supply_propval *val) { - int status; - - if (di->opts & BQ27XXX_O_ZERO) { - 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 - status = POWER_SUPPLY_STATUS_DISCHARGING; - } else if (di->opts & BQ27Z561_O_BITS) { - if (di->cache.flags & BQ27Z561_FLAG_FC) - status = POWER_SUPPLY_STATUS_FULL; - else if (di->cache.flags & BQ27Z561_FLAG_DIS_CH) - status = POWER_SUPPLY_STATUS_DISCHARGING; - else - status = POWER_SUPPLY_STATUS_CHARGING; - } 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; + int power; + + power = bq27xxx_read(di, BQ27XXX_REG_AP, false); + if (power < 0) { + dev_err(di->dev, + "error reading average power register %02x: %d\n", + BQ27XXX_REG_AP, power); + return power; } - if ((status == POWER_SUPPLY_STATUS_DISCHARGING) && - (power_supply_am_i_supplied(di->bat) > 0)) - status = POWER_SUPPLY_STATUS_NOT_CHARGING; - - val->intval = status; + if (di->opts & BQ27XXX_O_ZERO) + val->intval = (power * BQ27XXX_POWER_CONSTANT) / BQ27XXX_RS; + else + /* Other gauges return a signed value in units of 10mW */ + val->intval = (int)((s16)power) * 10000; return 0; } @@ -1957,7 +2002,7 @@ static int bq27xxx_battery_get_property(struct power_supply *psy, switch (psp) { case POWER_SUPPLY_PROP_STATUS: - ret = bq27xxx_battery_status(di, val); + ret = bq27xxx_battery_current_and_status(di, NULL, val); break; case POWER_SUPPLY_PROP_VOLTAGE_NOW: ret = bq27xxx_battery_voltage(di, val); @@ -1966,7 +2011,7 @@ static int bq27xxx_battery_get_property(struct power_supply *psy, val->intval = di->cache.flags < 0 ? 0 : 1; break; case POWER_SUPPLY_PROP_CURRENT_NOW: - ret = bq27xxx_battery_current(di, val); + ret = bq27xxx_battery_current_and_status(di, val, NULL); break; case POWER_SUPPLY_PROP_CAPACITY: ret = bq27xxx_simple_value(di->cache.capacity, val); @@ -2020,7 +2065,7 @@ static int bq27xxx_battery_get_property(struct power_supply *psy, ret = bq27xxx_simple_value(di->cache.energy, val); break; case POWER_SUPPLY_PROP_POWER_AVG: - ret = bq27xxx_simple_value(di->cache.power_avg, val); + ret = bq27xxx_battery_pwr_avg(di, val); break; case POWER_SUPPLY_PROP_HEALTH: ret = bq27xxx_simple_value(di->cache.health, val); diff --git a/drivers/power/supply/bq27xxx_battery_i2c.c b/drivers/power/supply/bq27xxx_battery_i2c.c index eb4f4284982f..46f078350fd3 100644 --- a/drivers/power/supply/bq27xxx_battery_i2c.c +++ b/drivers/power/supply/bq27xxx_battery_i2c.c @@ -248,6 +248,7 @@ static const struct i2c_device_id bq27xxx_i2c_id_table[] = { { "bq27z561", BQ27Z561 }, { "bq28z610", BQ28Z610 }, { "bq34z100", BQ34Z100 }, + { "bq78z100", BQ78Z100 }, {}, }; MODULE_DEVICE_TABLE(i2c, bq27xxx_i2c_id_table); @@ -284,6 +285,7 @@ static const struct of_device_id bq27xxx_battery_i2c_of_match_table[] = { { .compatible = "ti,bq27z561" }, { .compatible = "ti,bq28z610" }, { .compatible = "ti,bq34z100" }, + { .compatible = "ti,bq78z100" }, {}, }; MODULE_DEVICE_TABLE(of, bq27xxx_battery_i2c_of_match_table); diff --git a/drivers/power/supply/charger-manager.c b/drivers/power/supply/charger-manager.c index 4dea8ecd70bc..45da870aecca 100644 --- a/drivers/power/supply/charger-manager.c +++ b/drivers/power/supply/charger-manager.c @@ -1604,7 +1604,7 @@ static int charger_manager_probe(struct platform_device *pdev) mutex_unlock(&cm_list_mtx); /* - * Charger-manager is capable of waking up the systme from sleep + * Charger-manager is capable of waking up the system from sleep * when event is happened through cm_notify_event() */ device_init_wakeup(&pdev->dev, true); diff --git a/drivers/power/supply/cpcap-battery.c b/drivers/power/supply/cpcap-battery.c index 6d5bcdb9f45d..a3fc0084cda0 100644 --- a/drivers/power/supply/cpcap-battery.c +++ b/drivers/power/supply/cpcap-battery.c @@ -786,7 +786,7 @@ static irqreturn_t cpcap_battery_irq_thread(int irq, void *data) break; } - if (!d) + if (list_entry_is_head(d, &ddata->irq_list, node)) return IRQ_NONE; latest = cpcap_battery_latest(ddata); diff --git a/drivers/power/supply/cpcap-charger.c b/drivers/power/supply/cpcap-charger.c index 641dcad1133f..df01abc49ce8 100644 --- a/drivers/power/supply/cpcap-charger.c +++ b/drivers/power/supply/cpcap-charger.c @@ -318,7 +318,7 @@ static int cpcap_charger_current_to_regval(int microamp) return CPCAP_REG_CRM_ICHRG(0x0); if (miliamp < 177) return CPCAP_REG_CRM_ICHRG(0x1); - if (miliamp > 1596) + if (miliamp >= 1596) return CPCAP_REG_CRM_ICHRG(0xe); res = microamp / 88666; @@ -465,7 +465,7 @@ static bool cpcap_charger_vbus_valid(struct cpcap_charger_ddata *ddata) error = iio_read_channel_processed(channel, &value); if (error >= 0) - return value > 3900 ? true : false; + return value > 3900; dev_err(ddata->dev, "error reading VBUS: %i\n", error); @@ -668,6 +668,9 @@ static void cpcap_usb_detect(struct work_struct *work) return; } + /* Delay for 80ms to avoid vbus bouncing when usb cable is plugged in */ + usleep_range(80000, 120000); + /* Throttle chrgcurr2 interrupt for charger done and retry */ switch (ddata->status) { case POWER_SUPPLY_STATUS_CHARGING: diff --git a/drivers/power/supply/cw2015_battery.c b/drivers/power/supply/cw2015_battery.c index 0146f1bfc29b..d110597746b0 100644 --- a/drivers/power/supply/cw2015_battery.c +++ b/drivers/power/supply/cw2015_battery.c @@ -511,6 +511,11 @@ static int cw_battery_get_property(struct power_supply *psy, val->intval = 0; break; + case POWER_SUPPLY_PROP_CHARGE_NOW: + val->intval = cw_bat->battery.charge_full_design_uah; + val->intval = val->intval * cw_bat->soc / 100; + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: if (cw_battery_valid_time_to_empty(cw_bat) && cw_bat->battery.charge_full_design_uah > 0) { @@ -542,6 +547,7 @@ static enum power_supply_property cw_battery_properties[] = { POWER_SUPPLY_PROP_CHARGE_COUNTER, POWER_SUPPLY_PROP_CHARGE_FULL, POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, + POWER_SUPPLY_PROP_CHARGE_NOW, POWER_SUPPLY_PROP_CURRENT_NOW, }; diff --git a/drivers/power/supply/ds2781_battery.c b/drivers/power/supply/ds2781_battery.c index 3df3c820b38c..05b859bf2dc0 100644 --- a/drivers/power/supply/ds2781_battery.c +++ b/drivers/power/supply/ds2781_battery.c @@ -626,7 +626,7 @@ static ssize_t ds2781_read_param_eeprom_bin(struct file *filp, struct bin_attribute *bin_attr, char *buf, loff_t off, size_t count) { - struct device *dev = container_of(kobj, struct device, kobj); + struct device *dev = kobj_to_dev(kobj); struct power_supply *psy = to_power_supply(dev); struct ds2781_device_info *dev_info = to_ds2781_device_info(psy); @@ -639,7 +639,7 @@ static ssize_t ds2781_write_param_eeprom_bin(struct file *filp, struct bin_attribute *bin_attr, char *buf, loff_t off, size_t count) { - struct device *dev = container_of(kobj, struct device, kobj); + struct device *dev = kobj_to_dev(kobj); struct power_supply *psy = to_power_supply(dev); struct ds2781_device_info *dev_info = to_ds2781_device_info(psy); int ret; @@ -671,7 +671,7 @@ static ssize_t ds2781_read_user_eeprom_bin(struct file *filp, struct bin_attribute *bin_attr, char *buf, loff_t off, size_t count) { - struct device *dev = container_of(kobj, struct device, kobj); + struct device *dev = kobj_to_dev(kobj); struct power_supply *psy = to_power_supply(dev); struct ds2781_device_info *dev_info = to_ds2781_device_info(psy); @@ -685,7 +685,7 @@ static ssize_t ds2781_write_user_eeprom_bin(struct file *filp, struct bin_attribute *bin_attr, char *buf, loff_t off, size_t count) { - struct device *dev = container_of(kobj, struct device, kobj); + struct device *dev = kobj_to_dev(kobj); struct power_supply *psy = to_power_supply(dev); struct ds2781_device_info *dev_info = to_ds2781_device_info(psy); int ret; diff --git a/drivers/power/supply/generic-adc-battery.c b/drivers/power/supply/generic-adc-battery.c index 0032069fbc2b..66039c665dd1 100644 --- a/drivers/power/supply/generic-adc-battery.c +++ b/drivers/power/supply/generic-adc-battery.c @@ -373,7 +373,7 @@ static int gab_remove(struct platform_device *pdev) } kfree(adc_bat->psy_desc.properties); - cancel_delayed_work(&adc_bat->bat_work); + cancel_delayed_work_sync(&adc_bat->bat_work); return 0; } diff --git a/drivers/power/supply/lp8788-charger.c b/drivers/power/supply/lp8788-charger.c index e7931ffb7151..397e5a03b7d9 100644 --- a/drivers/power/supply/lp8788-charger.c +++ b/drivers/power/supply/lp8788-charger.c @@ -501,7 +501,7 @@ static int lp8788_set_irqs(struct platform_device *pdev, ret = request_threaded_irq(virq, NULL, lp8788_charger_irq_thread, - 0, name, pchg); + IRQF_ONESHOT, name, pchg); if (ret) break; } diff --git a/drivers/power/supply/max14577_charger.c b/drivers/power/supply/max14577_charger.c index dcedae18d7be..f244cd902eb9 100644 --- a/drivers/power/supply/max14577_charger.c +++ b/drivers/power/supply/max14577_charger.c @@ -261,7 +261,7 @@ static int max14577_init_constant_voltage(struct max14577_charger *chg, static int max14577_init_eoc(struct max14577_charger *chg, unsigned int uamp) { - unsigned int current_bits = 0xf; + unsigned int current_bits; u8 reg_data; switch (chg->max14577->dev_type) { diff --git a/drivers/power/supply/max17040_battery.c b/drivers/power/supply/max17040_battery.c index d956c67d5155..1aab868adabf 100644 --- a/drivers/power/supply/max17040_battery.c +++ b/drivers/power/supply/max17040_battery.c @@ -268,11 +268,10 @@ static int max17040_get_of_data(struct max17040_chip *chip) rcomp_len = device_property_count_u8(dev, "maxim,rcomp"); chip->rcomp = MAX17040_RCOMP_DEFAULT; if (rcomp_len == data->rcomp_bytes) { - device_property_read_u8_array(dev, "maxim,rcomp", - rcomp, rcomp_len); - chip->rcomp = rcomp_len == 2 ? - rcomp[0] << 8 | rcomp[1] : - rcomp[0] << 8; + if (!device_property_read_u8_array(dev, "maxim,rcomp", + rcomp, rcomp_len)) + chip->rcomp = rcomp_len == 2 ? rcomp[0] << 8 | rcomp[1] : + rcomp[0] << 8; } else if (rcomp_len > 0) { dev_err(dev, "maxim,rcomp has incorrect length\n"); return -EINVAL; @@ -487,8 +486,7 @@ static int max17040_probe(struct i2c_client *client, ret = max17040_get_of_data(chip); if (ret) return ret; - chip_id = (enum chip_id) (uintptr_t) - of_device_get_match_data(&client->dev); + chip_id = (uintptr_t)of_device_get_match_data(&client->dev); } chip->data = max17040_family[chip_id]; diff --git a/drivers/power/supply/max17042_battery.c b/drivers/power/supply/max17042_battery.c index 79d4b5988360..1d7326cd8fc6 100644 --- a/drivers/power/supply/max17042_battery.c +++ b/drivers/power/supply/max17042_battery.c @@ -131,7 +131,7 @@ static int max17042_get_status(struct max17042_chip *chip, int *status) * * When this cycle the battery gets charged to a higher (calculated) * capacity then the previous cycle then FullCAP will get updated - * contineously once end-of-charge detection kicks in, so allow the + * continuously once end-of-charge detection kicks in, so allow the * 2 to differ a bit. */ @@ -739,7 +739,7 @@ static void max17042_load_new_capacity_params(struct max17042_chip *chip) /* * Block write all the override values coming from platform data. - * This function MUST be called before the POR initialization proceedure + * This function MUST be called before the POR initialization procedure * specified by maxim. */ static inline void max17042_override_por_values(struct max17042_chip *chip) @@ -811,7 +811,7 @@ static int max17042_init_chip(struct max17042_chip *chip) */ msleep(500); - /* Initialize configaration */ + /* Initialize configuration */ max17042_write_config_regs(chip); /* write cell characterization data */ @@ -855,7 +855,7 @@ 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 + /* program interrupt thresholds such that we should * get interrupt for every 'off' perc change in the soc */ regmap_read(map, MAX17042_RepSOC, &soc); diff --git a/drivers/power/supply/max1721x_battery.c b/drivers/power/supply/max1721x_battery.c index 1b1a36f8e929..473e53cd2801 100644 --- a/drivers/power/supply/max1721x_battery.c +++ b/drivers/power/supply/max1721x_battery.c @@ -1,6 +1,6 @@ /* * 1-Wire implementation for Maxim Semiconductor - * MAX7211/MAX17215 stanalone fuel gauge chip + * MAX7211/MAX17215 standalone fuel gauge chip * * Copyright (C) 2017 Radioavionica Corporation * Author: Alex A. Mihaylov <minimumlaw@rambler.ru> @@ -28,7 +28,7 @@ /* Number of valid register addresses in W1 mode */ #define MAX1721X_MAX_REG_NR 0x1EF -/* Factory settings (nonvilatile registers) (W1 specific) */ +/* Factory settings (nonvolatile registers) (W1 specific) */ #define MAX1721X_REG_NRSENSE 0x1CF /* RSense in 10^-5 Ohm */ /* Strings */ #define MAX1721X_REG_MFG_STR 0x1CC @@ -105,7 +105,7 @@ static inline int max172xx_temperature_to_ps(unsigned int reg) /* * Calculating current registers resolution: * - * RSense stored in 10^-5 Ohm, so mesaurment voltage must be + * RSense stored in 10^-5 Ohm, so measurement voltage must be * in 10^-11 Volts for get current in uA. * 16 bit current reg fullscale +/-51.2mV is 102400 uV. * So: 102400 / 65535 * 10^5 = 156252 @@ -137,7 +137,7 @@ static int max1721x_battery_get_property(struct power_supply *psy, /* * POWER_SUPPLY_PROP_PRESENT will always readable via * sysfs interface. Value return 0 if battery not - * present or unaccesable via W1. + * present or unaccessible via W1. */ val->intval = regmap_read(info->regmap, MAX172XX_REG_STATUS, @@ -334,9 +334,9 @@ static int devm_w1_max1721x_add_device(struct w1_slave *sl) /* * power_supply class battery name translated from W1 slave device - * unical ID (look like 26-0123456789AB) to "max1721x-0123456789AB\0" - * so, 26 (device family) correcpondent to max1721x devices. - * Device name still unical for any numbers connected devices. + * unique ID (look like 26-0123456789AB) to "max1721x-0123456789AB\0" + * so, 26 (device family) correspond to max1721x devices. + * Device name still unique for any number of connected devices. */ snprintf(info->name, sizeof(info->name), "max1721x-%012X", (unsigned int)sl->reg_num.id); diff --git a/drivers/power/supply/max8997_charger.c b/drivers/power/supply/max8997_charger.c index 321bd6b8ee41..25207fe2aa68 100644 --- a/drivers/power/supply/max8997_charger.c +++ b/drivers/power/supply/max8997_charger.c @@ -168,6 +168,7 @@ 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 device_node *np = pdev->dev.of_node; struct i2c_client *i2c = iodev->i2c; struct max8997_platform_data *pdata = iodev->pdata; struct power_supply_config psy_cfg = {}; @@ -237,20 +238,23 @@ static int max8997_battery_probe(struct platform_device *pdev) return PTR_ERR(charger->battery); } + // grab regulator from parent device's node + pdev->dev.of_node = iodev->dev->of_node; charger->reg = devm_regulator_get_optional(&pdev->dev, "charger"); + pdev->dev.of_node = np; if (IS_ERR(charger->reg)) { if (PTR_ERR(charger->reg) == -EPROBE_DEFER) return -EPROBE_DEFER; dev_info(&pdev->dev, "couldn't get charger regulator\n"); } - charger->edev = extcon_get_edev_by_phandle(&pdev->dev, 0); - if (IS_ERR(charger->edev)) { - if (PTR_ERR(charger->edev) == -EPROBE_DEFER) + charger->edev = extcon_get_extcon_dev("max8997-muic"); + if (IS_ERR_OR_NULL(charger->edev)) { + if (!charger->edev) return -EPROBE_DEFER; dev_info(charger->dev, "couldn't get extcon device\n"); } - if (!IS_ERR(charger->reg) && !IS_ERR(charger->edev)) { + if (!IS_ERR(charger->reg) && !IS_ERR_OR_NULL(charger->edev)) { INIT_WORK(&charger->extcon_work, max8997_battery_extcon_evt_worker); ret = devm_add_action(&pdev->dev, max8997_battery_extcon_evt_stop_work, charger); if (ret) { @@ -263,7 +267,7 @@ static int max8997_battery_probe(struct platform_device *pdev) if (ret) { dev_err(&pdev->dev, "failed to register extcon notifier\n"); return ret; - }; + } } return 0; diff --git a/drivers/power/supply/pm2301_charger.c b/drivers/power/supply/pm2301_charger.c index ac06ecf7fc9c..f86bbbeaff6c 100644 --- a/drivers/power/supply/pm2301_charger.c +++ b/drivers/power/supply/pm2301_charger.c @@ -18,13 +18,13 @@ #include <linux/i2c.h> #include <linux/workqueue.h> #include <linux/mfd/abx500/ab8500.h> -#include <linux/mfd/abx500/ab8500-bm.h> -#include <linux/mfd/abx500/ux500_chargalg.h> #include <linux/pm2301_charger.h> #include <linux/gpio.h> #include <linux/pm_runtime.h> #include <linux/pm.h> +#include "ab8500-bm.h" +#include "ab8500-chargalg.h" #include "pm2301_charger.h" #define to_pm2xxx_charger_ac_device_info(x) container_of((x), \ @@ -1089,7 +1089,7 @@ static int pm2xxx_wall_charger_probe(struct i2c_client *i2c_client, ret = request_threaded_irq(gpio_to_irq(pm2->pdata->gpio_irq_number), NULL, pm2xxx_charger_irq[0].isr, - pm2->pdata->irq_type, + pm2->pdata->irq_type | IRQF_ONESHOT, pm2xxx_charger_irq[0].name, pm2); if (ret != 0) { diff --git a/drivers/power/supply/power_supply_core.c b/drivers/power/supply/power_supply_core.c index 38e3aa642131..d99e2f11c183 100644 --- a/drivers/power/supply/power_supply_core.c +++ b/drivers/power/supply/power_supply_core.c @@ -169,7 +169,7 @@ static int __power_supply_populate_supplied_from(struct device *dev, break; if (np == epsy->of_node) { - dev_info(&psy->dev, "%s: Found supply : %s\n", + dev_dbg(&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++; @@ -1143,7 +1143,7 @@ __power_supply_register(struct device *parent, rc = power_supply_check_supplies(psy); if (rc) { - dev_info(dev, "Not all required supplies found, defer probe\n"); + dev_dbg(dev, "Not all required supplies found, defer probe\n"); goto check_supplies_failed; } diff --git a/drivers/power/supply/s3c_adc_battery.c b/drivers/power/supply/s3c_adc_battery.c index a2addc24ee8b..68d31a3bee48 100644 --- a/drivers/power/supply/s3c_adc_battery.c +++ b/drivers/power/supply/s3c_adc_battery.c @@ -1,13 +1,8 @@ -/* - * 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. - * - */ +// SPDX-License-Identifier: GPL-2.0 +// +// iPAQ h1930/h1940/rx1950 battery controller driver +// Copyright (c) Vasily Khoruzhick +// Based on h1940_battery.c by Arnaud Patard #include <linux/interrupt.h> #include <linux/platform_device.h> @@ -395,7 +390,7 @@ static int s3c_adc_bat_remove(struct platform_device *pdev) if (main_bat.charge_finished) free_irq(gpiod_to_irq(main_bat.charge_finished), NULL); - cancel_delayed_work(&bat_work); + cancel_delayed_work_sync(&bat_work); if (pdata->exit) pdata->exit(); @@ -407,8 +402,6 @@ static int s3c_adc_bat_remove(struct platform_device *pdev) 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 (main_bat.charge_finished) { if (device_may_wakeup(&pdev->dev)) enable_irq_wake( @@ -424,8 +417,6 @@ static int s3c_adc_bat_suspend(struct platform_device *pdev, static int s3c_adc_bat_resume(struct platform_device *pdev) { - struct s3c_adc_bat_pdata *pdata = pdev->dev.platform_data; - if (main_bat.charge_finished) { if (device_may_wakeup(&pdev->dev)) disable_irq_wake( diff --git a/drivers/power/supply/sbs-battery.c b/drivers/power/supply/sbs-battery.c index 70ea404b2a36..8d7a10730e43 100644 --- a/drivers/power/supply/sbs-battery.c +++ b/drivers/power/supply/sbs-battery.c @@ -1124,11 +1124,9 @@ static int sbs_probe(struct i2c_client *client) chip->gpio_detect = devm_gpiod_get_optional(&client->dev, "sbs,battery-detect", GPIOD_IN); - if (IS_ERR(chip->gpio_detect)) { - dev_err(&client->dev, "Failed to get gpio: %ld\n", - PTR_ERR(chip->gpio_detect)); - return PTR_ERR(chip->gpio_detect); - } + if (IS_ERR(chip->gpio_detect)) + return dev_err_probe(&client->dev, PTR_ERR(chip->gpio_detect), + "Failed to get gpio\n"); i2c_set_clientdata(client, chip); @@ -1159,11 +1157,9 @@ skip_gpio: rc = sbs_get_battery_presence_and_health( client, POWER_SUPPLY_PROP_PRESENT, &val); - if (rc < 0 || !val.intval) { - dev_err(&client->dev, "Failed to get present status\n"); - rc = -ENODEV; - goto exit_psupply; - } + if (rc < 0 || !val.intval) + return dev_err_probe(&client->dev, -ENODEV, + "Failed to get present status\n"); } rc = devm_delayed_work_autocancel(&client->dev, &chip->work, @@ -1173,20 +1169,14 @@ skip_gpio: chip->power_supply = devm_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; - } + if (IS_ERR(chip->power_supply)) + return dev_err_probe(&client->dev, PTR_ERR(chip->power_supply), + "Failed to register power supply\n"); dev_info(&client->dev, "%s: battery gas gauge device registered\n", client->name); return 0; - -exit_psupply: - return rc; } #if defined CONFIG_PM_SLEEP diff --git a/drivers/power/supply/sbs-charger.c b/drivers/power/supply/sbs-charger.c index fbfb6a620961..6fa65d118ec1 100644 --- a/drivers/power/supply/sbs-charger.c +++ b/drivers/power/supply/sbs-charger.c @@ -16,9 +16,7 @@ #include <linux/i2c.h> #include <linux/slab.h> #include <linux/interrupt.h> -#include <linux/gpio.h> #include <linux/regmap.h> -#include <linux/of_gpio.h> #include <linux/bitops.h> #define SBS_CHARGER_REG_SPEC_INFO 0x11 @@ -189,18 +187,14 @@ static int sbs_probe(struct i2c_client *client, * to the battery. */ ret = regmap_read(chip->regmap, SBS_CHARGER_REG_STATUS, &val); - if (ret) { - dev_err(&client->dev, "Failed to get device status\n"); - return ret; - } + if (ret) + return dev_err_probe(&client->dev, ret, "Failed to get device status\n"); chip->last_state = val; - chip->power_supply = devm_power_supply_register(&client->dev, &sbs_desc, - &psy_cfg); - if (IS_ERR(chip->power_supply)) { - dev_err(&client->dev, "Failed to register power supply\n"); - return PTR_ERR(chip->power_supply); - } + chip->power_supply = devm_power_supply_register(&client->dev, &sbs_desc, &psy_cfg); + if (IS_ERR(chip->power_supply)) + return dev_err_probe(&client->dev, PTR_ERR(chip->power_supply), + "Failed to register power supply\n"); /* * The sbs-charger spec doesn't impose the use of an interrupt. So in @@ -212,10 +206,8 @@ static int sbs_probe(struct i2c_client *client, NULL, sbs_irq_thread, IRQF_TRIGGER_FALLING | IRQF_ONESHOT, dev_name(&client->dev), chip); - if (ret) { - dev_err(&client->dev, "Failed to request irq, %d\n", ret); - return ret; - } + if (ret) + return dev_err_probe(&client->dev, ret, "Failed to request irq\n"); } else { INIT_DELAYED_WORK(&chip->work, sbs_delayed_work); schedule_delayed_work(&chip->work, diff --git a/drivers/power/supply/sbs-manager.c b/drivers/power/supply/sbs-manager.c index 666243d9dd59..71ec8f74f835 100644 --- a/drivers/power/supply/sbs-manager.c +++ b/drivers/power/supply/sbs-manager.c @@ -13,7 +13,7 @@ * Karl-Heinz Schneider <karl-heinz@schneider-inet.de> */ -#include <linux/gpio.h> +#include <linux/gpio/driver.h> #include <linux/module.h> #include <linux/i2c.h> #include <linux/i2c-mux.h> @@ -294,10 +294,8 @@ static int sbsm_gpio_setup(struct sbsm_data *data) gc->owner = THIS_MODULE; ret = devm_gpiochip_add_data(dev, gc, data); - if (ret) { - dev_err(dev, "devm_gpiochip_add_data failed: %d\n", ret); - return ret; - } + if (ret) + return dev_err_probe(dev, ret, "devm_gpiochip_add_data failed\n"); return ret; } @@ -311,6 +309,12 @@ static const struct power_supply_desc sbsm_default_psy_desc = { .property_is_writeable = &sbsm_prop_is_writeable, }; +static void sbsm_del_mux_adapter(void *data) +{ + struct sbsm_data *sbsm = data; + i2c_mux_del_adapters(sbsm->muxc); +} + static int sbsm_probe(struct i2c_client *client, const struct i2c_device_id *id) { @@ -343,13 +347,14 @@ static int sbsm_probe(struct i2c_client *client, data->supported_bats = ret & SBSM_MASK_BAT_SUPPORTED; data->muxc = i2c_mux_alloc(adapter, dev, SBSM_MAX_BATS, 0, I2C_MUX_LOCKED, &sbsm_select, NULL); - if (!data->muxc) { - dev_err(dev, "failed to alloc i2c mux\n"); - ret = -ENOMEM; - goto err_mux_alloc; - } + if (!data->muxc) + return dev_err_probe(dev, -ENOMEM, "failed to alloc i2c mux\n"); data->muxc->priv = data; + ret = devm_add_action_or_reset(dev, sbsm_del_mux_adapter, data); + if (ret) + return ret; + /* register muxed i2c channels. One for each supported battery */ for (i = 0; i < SBSM_MAX_BATS; ++i) { if (data->supported_bats & BIT(i)) { @@ -358,54 +363,28 @@ static int sbsm_probe(struct i2c_client *client, break; } } - if (ret) { - dev_err(dev, "failed to register i2c mux channel %d\n", i + 1); - goto err_mux_register; - } + if (ret) + return dev_err_probe(dev, ret, "failed to register i2c mux channel %d\n", i + 1); - psy_desc = devm_kmemdup(dev, &sbsm_default_psy_desc, - sizeof(struct power_supply_desc), - GFP_KERNEL); - if (!psy_desc) { - ret = -ENOMEM; - goto err_psy; - } + psy_desc = devm_kmemdup(dev, &sbsm_default_psy_desc, sizeof(*psy_desc), GFP_KERNEL); + if (!psy_desc) + return -ENOMEM; + + psy_desc->name = devm_kasprintf(dev, GFP_KERNEL, "sbsm-%s", dev_name(&client->dev)); + if (!psy_desc->name) + return -ENOMEM; - psy_desc->name = devm_kasprintf(dev, GFP_KERNEL, "sbsm-%s", - dev_name(&client->dev)); - if (!psy_desc->name) { - ret = -ENOMEM; - goto err_psy; - } ret = sbsm_gpio_setup(data); if (ret < 0) - goto err_psy; + return ret; psy_cfg.drv_data = data; psy_cfg.of_node = dev->of_node; data->psy = devm_power_supply_register(dev, psy_desc, &psy_cfg); - if (IS_ERR(data->psy)) { - ret = PTR_ERR(data->psy); - dev_err(dev, "failed to register power supply %s\n", - psy_desc->name); - goto err_psy; - } - - return 0; - -err_psy: -err_mux_register: - i2c_mux_del_adapters(data->muxc); - -err_mux_alloc: - return ret; -} - -static int sbsm_remove(struct i2c_client *client) -{ - struct sbsm_data *data = i2c_get_clientdata(client); + if (IS_ERR(data->psy)) + return dev_err_probe(dev, PTR_ERR(data->psy), + "failed to register power supply %s\n", psy_desc->name); - i2c_mux_del_adapters(data->muxc); return 0; } @@ -431,7 +410,6 @@ static struct i2c_driver sbsm_driver = { .of_match_table = of_match_ptr(sbsm_dt_ids), }, .probe = sbsm_probe, - .remove = sbsm_remove, .alert = sbsm_alert, .id_table = sbsm_ids }; diff --git a/drivers/power/supply/smb347-charger.c b/drivers/power/supply/smb347-charger.c index 8cfbd8d6b478..3376f42d46c3 100644 --- a/drivers/power/supply/smb347-charger.c +++ b/drivers/power/supply/smb347-charger.c @@ -911,11 +911,14 @@ static int smb347_irq_init(struct smb347_charger *smb, { int ret; - ret = devm_request_threaded_irq(smb->dev, client->irq, NULL, - smb347_interrupt, IRQF_ONESHOT, - client->name, smb); - if (ret < 0) - return ret; + smb->irq_unsupported = true; + + /* + * Interrupt pin is optional. If it is connected, we setup the + * interrupt support here. + */ + if (!client->irq) + return 0; ret = smb347_set_writable(smb, true); if (ret < 0) @@ -931,7 +934,25 @@ static int smb347_irq_init(struct smb347_charger *smb, smb347_set_writable(smb, false); - return ret; + if (ret < 0) { + dev_warn(smb->dev, "failed to initialize IRQ: %d\n", ret); + dev_warn(smb->dev, "disabling IRQ support\n"); + return 0; + } + + ret = devm_request_threaded_irq(smb->dev, client->irq, NULL, + smb347_interrupt, IRQF_ONESHOT, + client->name, smb); + if (ret) + return ret; + + smb->irq_unsupported = false; + + ret = smb347_irq_enable(smb); + if (ret < 0) + return ret; + + return 0; } /* @@ -1120,9 +1141,13 @@ static int smb347_get_property(struct power_supply *psy, struct i2c_client *client = to_i2c_client(smb->dev); int ret; - disable_irq(client->irq); + if (!smb->irq_unsupported) + disable_irq(client->irq); + ret = smb347_get_property_locked(psy, prop, val); - enable_irq(client->irq); + + if (!smb->irq_unsupported) + enable_irq(client->irq); return ret; } @@ -1339,20 +1364,9 @@ static int smb347_probe(struct i2c_client *client, if (ret < 0) return ret; - /* - * Interrupt pin is optional. If it is connected, we setup the - * interrupt support here. - */ - if (client->irq) { - 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"); - smb->irq_unsupported = true; - } else { - smb347_irq_enable(smb); - } - } + ret = smb347_irq_init(smb, client); + if (ret) + return ret; return 0; } @@ -1387,11 +1401,10 @@ static struct i2c_driver smb347_driver = { .name = "smb347", .of_match_table = smb3xx_of_match, }, - .probe = smb347_probe, - .remove = smb347_remove, - .id_table = smb347_id, + .probe = smb347_probe, + .remove = smb347_remove, + .id_table = smb347_id, }; - module_i2c_driver(smb347_driver); MODULE_AUTHOR("Bruce E. Robertson <bruce.e.robertson@intel.com>"); diff --git a/drivers/power/supply/surface_battery.c b/drivers/power/supply/surface_battery.c new file mode 100644 index 000000000000..7efa431a62b2 --- /dev/null +++ b/drivers/power/supply/surface_battery.c @@ -0,0 +1,865 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Battery driver for 7th-generation Microsoft Surface devices via Surface + * System Aggregator Module (SSAM). + * + * Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com> + */ + +#include <asm/unaligned.h> +#include <linux/jiffies.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/power_supply.h> +#include <linux/sysfs.h> +#include <linux/types.h> +#include <linux/workqueue.h> + +#include <linux/surface_aggregator/device.h> + + +/* -- SAM interface. -------------------------------------------------------- */ + +enum sam_event_cid_bat { + SAM_EVENT_CID_BAT_BIX = 0x15, + SAM_EVENT_CID_BAT_BST = 0x16, + SAM_EVENT_CID_BAT_ADP = 0x17, + SAM_EVENT_CID_BAT_PROT = 0x18, + SAM_EVENT_CID_BAT_DPTF = 0x53, +}; + +enum sam_battery_sta { + SAM_BATTERY_STA_OK = 0x0f, + SAM_BATTERY_STA_PRESENT = 0x10, +}; + +enum sam_battery_state { + SAM_BATTERY_STATE_DISCHARGING = BIT(0), + SAM_BATTERY_STATE_CHARGING = BIT(1), + SAM_BATTERY_STATE_CRITICAL = BIT(2), +}; + +enum sam_battery_power_unit { + SAM_BATTERY_POWER_UNIT_mW = 0, + SAM_BATTERY_POWER_UNIT_mA = 1, +}; + +/* Equivalent to data returned in ACPI _BIX method, revision 0. */ +struct spwr_bix { + u8 revision; + __le32 power_unit; + __le32 design_cap; + __le32 last_full_charge_cap; + __le32 technology; + __le32 design_voltage; + __le32 design_cap_warn; + __le32 design_cap_low; + __le32 cycle_count; + __le32 measurement_accuracy; + __le32 max_sampling_time; + __le32 min_sampling_time; + __le32 max_avg_interval; + __le32 min_avg_interval; + __le32 bat_cap_granularity_1; + __le32 bat_cap_granularity_2; + __u8 model[21]; + __u8 serial[11]; + __u8 type[5]; + __u8 oem_info[21]; +} __packed; + +static_assert(sizeof(struct spwr_bix) == 119); + +/* Equivalent to data returned in ACPI _BST method. */ +struct spwr_bst { + __le32 state; + __le32 present_rate; + __le32 remaining_cap; + __le32 present_voltage; +} __packed; + +static_assert(sizeof(struct spwr_bst) == 16); + +#define SPWR_BIX_REVISION 0 +#define SPWR_BATTERY_VALUE_UNKNOWN 0xffffffff + +/* Get battery status (_STA) */ +SSAM_DEFINE_SYNC_REQUEST_CL_R(ssam_bat_get_sta, __le32, { + .target_category = SSAM_SSH_TC_BAT, + .command_id = 0x01, +}); + +/* Get battery static information (_BIX). */ +SSAM_DEFINE_SYNC_REQUEST_CL_R(ssam_bat_get_bix, struct spwr_bix, { + .target_category = SSAM_SSH_TC_BAT, + .command_id = 0x02, +}); + +/* Get battery dynamic information (_BST). */ +SSAM_DEFINE_SYNC_REQUEST_CL_R(ssam_bat_get_bst, struct spwr_bst, { + .target_category = SSAM_SSH_TC_BAT, + .command_id = 0x03, +}); + +/* Set battery trip point (_BTP). */ +SSAM_DEFINE_SYNC_REQUEST_CL_W(ssam_bat_set_btp, __le32, { + .target_category = SSAM_SSH_TC_BAT, + .command_id = 0x04, +}); + + +/* -- Device structures. ---------------------------------------------------- */ + +struct spwr_psy_properties { + const char *name; + struct ssam_event_registry registry; +}; + +struct spwr_battery_device { + struct ssam_device *sdev; + + char name[32]; + struct power_supply *psy; + struct power_supply_desc psy_desc; + + struct delayed_work update_work; + + struct ssam_event_notifier notif; + + struct mutex lock; /* Guards access to state data below. */ + unsigned long timestamp; + + __le32 sta; + struct spwr_bix bix; + struct spwr_bst bst; + u32 alarm; +}; + + +/* -- Module parameters. ---------------------------------------------------- */ + +static unsigned int cache_time = 1000; +module_param(cache_time, uint, 0644); +MODULE_PARM_DESC(cache_time, "battery state caching time in milliseconds [default: 1000]"); + + +/* -- State management. ----------------------------------------------------- */ + +/* + * Delay for battery update quirk. See spwr_external_power_changed() below + * for more details. + */ +#define SPWR_AC_BAT_UPDATE_DELAY msecs_to_jiffies(5000) + +static bool spwr_battery_present(struct spwr_battery_device *bat) +{ + lockdep_assert_held(&bat->lock); + + return le32_to_cpu(bat->sta) & SAM_BATTERY_STA_PRESENT; +} + +static int spwr_battery_load_sta(struct spwr_battery_device *bat) +{ + lockdep_assert_held(&bat->lock); + + return ssam_retry(ssam_bat_get_sta, bat->sdev, &bat->sta); +} + +static int spwr_battery_load_bix(struct spwr_battery_device *bat) +{ + int status; + + lockdep_assert_held(&bat->lock); + + if (!spwr_battery_present(bat)) + return 0; + + status = ssam_retry(ssam_bat_get_bix, bat->sdev, &bat->bix); + + /* Enforce NULL terminated strings in case anything goes wrong... */ + bat->bix.model[ARRAY_SIZE(bat->bix.model) - 1] = 0; + bat->bix.serial[ARRAY_SIZE(bat->bix.serial) - 1] = 0; + bat->bix.type[ARRAY_SIZE(bat->bix.type) - 1] = 0; + bat->bix.oem_info[ARRAY_SIZE(bat->bix.oem_info) - 1] = 0; + + return status; +} + +static int spwr_battery_load_bst(struct spwr_battery_device *bat) +{ + lockdep_assert_held(&bat->lock); + + if (!spwr_battery_present(bat)) + return 0; + + return ssam_retry(ssam_bat_get_bst, bat->sdev, &bat->bst); +} + +static int spwr_battery_set_alarm_unlocked(struct spwr_battery_device *bat, u32 value) +{ + __le32 value_le = cpu_to_le32(value); + + lockdep_assert_held(&bat->lock); + + bat->alarm = value; + return ssam_retry(ssam_bat_set_btp, bat->sdev, &value_le); +} + +static int spwr_battery_update_bst_unlocked(struct spwr_battery_device *bat, bool cached) +{ + unsigned long cache_deadline = bat->timestamp + msecs_to_jiffies(cache_time); + int status; + + lockdep_assert_held(&bat->lock); + + if (cached && bat->timestamp && time_is_after_jiffies(cache_deadline)) + return 0; + + status = spwr_battery_load_sta(bat); + if (status) + return status; + + status = spwr_battery_load_bst(bat); + if (status) + return status; + + bat->timestamp = jiffies; + return 0; +} + +static int spwr_battery_update_bst(struct spwr_battery_device *bat, bool cached) +{ + int status; + + mutex_lock(&bat->lock); + status = spwr_battery_update_bst_unlocked(bat, cached); + mutex_unlock(&bat->lock); + + return status; +} + +static int spwr_battery_update_bix_unlocked(struct spwr_battery_device *bat) +{ + int status; + + lockdep_assert_held(&bat->lock); + + status = spwr_battery_load_sta(bat); + if (status) + return status; + + status = spwr_battery_load_bix(bat); + if (status) + return status; + + status = spwr_battery_load_bst(bat); + if (status) + return status; + + if (bat->bix.revision != SPWR_BIX_REVISION) + dev_warn(&bat->sdev->dev, "unsupported battery revision: %u\n", bat->bix.revision); + + bat->timestamp = jiffies; + return 0; +} + +static u32 sprw_battery_get_full_cap_safe(struct spwr_battery_device *bat) +{ + u32 full_cap = get_unaligned_le32(&bat->bix.last_full_charge_cap); + + lockdep_assert_held(&bat->lock); + + if (full_cap == 0 || full_cap == SPWR_BATTERY_VALUE_UNKNOWN) + full_cap = get_unaligned_le32(&bat->bix.design_cap); + + return full_cap; +} + +static bool spwr_battery_is_full(struct spwr_battery_device *bat) +{ + u32 state = get_unaligned_le32(&bat->bst.state); + u32 full_cap = sprw_battery_get_full_cap_safe(bat); + u32 remaining_cap = get_unaligned_le32(&bat->bst.remaining_cap); + + lockdep_assert_held(&bat->lock); + + return full_cap != SPWR_BATTERY_VALUE_UNKNOWN && full_cap != 0 && + remaining_cap != SPWR_BATTERY_VALUE_UNKNOWN && + remaining_cap >= full_cap && + state == 0; +} + +static int spwr_battery_recheck_full(struct spwr_battery_device *bat) +{ + bool present; + u32 unit; + int status; + + mutex_lock(&bat->lock); + unit = get_unaligned_le32(&bat->bix.power_unit); + present = spwr_battery_present(bat); + + status = spwr_battery_update_bix_unlocked(bat); + if (status) + goto out; + + /* If battery has been attached, (re-)initialize alarm. */ + if (!present && spwr_battery_present(bat)) { + u32 cap_warn = get_unaligned_le32(&bat->bix.design_cap_warn); + + status = spwr_battery_set_alarm_unlocked(bat, cap_warn); + if (status) + goto out; + } + + /* + * Warn if the unit has changed. This is something we genuinely don't + * expect to happen, so make this a big warning. If it does, we'll + * need to add support for it. + */ + WARN_ON(unit != get_unaligned_le32(&bat->bix.power_unit)); + +out: + mutex_unlock(&bat->lock); + + if (!status) + power_supply_changed(bat->psy); + + return status; +} + +static int spwr_battery_recheck_status(struct spwr_battery_device *bat) +{ + int status; + + status = spwr_battery_update_bst(bat, false); + if (!status) + power_supply_changed(bat->psy); + + return status; +} + +static u32 spwr_notify_bat(struct ssam_event_notifier *nf, const struct ssam_event *event) +{ + struct spwr_battery_device *bat = container_of(nf, struct spwr_battery_device, notif); + int status; + + dev_dbg(&bat->sdev->dev, "power event (cid = %#04x, iid = %#04x, tid = %#04x)\n", + event->command_id, event->instance_id, event->target_id); + + switch (event->command_id) { + case SAM_EVENT_CID_BAT_BIX: + status = spwr_battery_recheck_full(bat); + break; + + case SAM_EVENT_CID_BAT_BST: + status = spwr_battery_recheck_status(bat); + break; + + case SAM_EVENT_CID_BAT_PROT: + /* + * TODO: Implement support for battery protection status change + * event. + */ + status = 0; + break; + + case SAM_EVENT_CID_BAT_DPTF: + /* + * TODO: Implement support for DPTF event. + */ + status = 0; + break; + + default: + return 0; + } + + return ssam_notifier_from_errno(status) | SSAM_NOTIF_HANDLED; +} + +static void spwr_battery_update_bst_workfn(struct work_struct *work) +{ + struct delayed_work *dwork = to_delayed_work(work); + struct spwr_battery_device *bat; + int status; + + bat = container_of(dwork, struct spwr_battery_device, update_work); + + status = spwr_battery_update_bst(bat, false); + if (status) { + dev_err(&bat->sdev->dev, "failed to update battery state: %d\n", status); + return; + } + + power_supply_changed(bat->psy); +} + +static void spwr_external_power_changed(struct power_supply *psy) +{ + struct spwr_battery_device *bat = power_supply_get_drvdata(psy); + + /* + * Handle battery update quirk: When the battery is fully charged (or + * charged up to the limit imposed by the UEFI battery limit) and the + * adapter is plugged in or removed, the EC does not send a separate + * event for the state (charging/discharging) change. Furthermore it + * may take some time until the state is updated on the battery. + * Schedule an update to solve this. + */ + + schedule_delayed_work(&bat->update_work, SPWR_AC_BAT_UPDATE_DELAY); +} + + +/* -- Properties. ----------------------------------------------------------- */ + +static const enum power_supply_property spwr_battery_props_chg[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_CYCLE_COUNT, + POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, + 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_NOW, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_CAPACITY_LEVEL, + POWER_SUPPLY_PROP_MODEL_NAME, + POWER_SUPPLY_PROP_MANUFACTURER, + POWER_SUPPLY_PROP_SERIAL_NUMBER, +}; + +static const enum power_supply_property spwr_battery_props_eng[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_CYCLE_COUNT, + POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_POWER_NOW, + POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN, + POWER_SUPPLY_PROP_ENERGY_FULL, + POWER_SUPPLY_PROP_ENERGY_NOW, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_CAPACITY_LEVEL, + POWER_SUPPLY_PROP_MODEL_NAME, + POWER_SUPPLY_PROP_MANUFACTURER, + POWER_SUPPLY_PROP_SERIAL_NUMBER, +}; + +static int spwr_battery_prop_status(struct spwr_battery_device *bat) +{ + u32 state = get_unaligned_le32(&bat->bst.state); + u32 present_rate = get_unaligned_le32(&bat->bst.present_rate); + + lockdep_assert_held(&bat->lock); + + if (state & SAM_BATTERY_STATE_DISCHARGING) + return POWER_SUPPLY_STATUS_DISCHARGING; + + if (state & SAM_BATTERY_STATE_CHARGING) + return POWER_SUPPLY_STATUS_CHARGING; + + if (spwr_battery_is_full(bat)) + return POWER_SUPPLY_STATUS_FULL; + + if (present_rate == 0) + return POWER_SUPPLY_STATUS_NOT_CHARGING; + + return POWER_SUPPLY_STATUS_UNKNOWN; +} + +static int spwr_battery_prop_technology(struct spwr_battery_device *bat) +{ + lockdep_assert_held(&bat->lock); + + if (!strcasecmp("NiCd", bat->bix.type)) + return POWER_SUPPLY_TECHNOLOGY_NiCd; + + if (!strcasecmp("NiMH", bat->bix.type)) + return POWER_SUPPLY_TECHNOLOGY_NiMH; + + if (!strcasecmp("LION", bat->bix.type)) + return POWER_SUPPLY_TECHNOLOGY_LION; + + if (!strncasecmp("LI-ION", bat->bix.type, 6)) + return POWER_SUPPLY_TECHNOLOGY_LION; + + if (!strcasecmp("LiP", bat->bix.type)) + return POWER_SUPPLY_TECHNOLOGY_LIPO; + + return POWER_SUPPLY_TECHNOLOGY_UNKNOWN; +} + +static int spwr_battery_prop_capacity(struct spwr_battery_device *bat) +{ + u32 full_cap = sprw_battery_get_full_cap_safe(bat); + u32 remaining_cap = get_unaligned_le32(&bat->bst.remaining_cap); + + lockdep_assert_held(&bat->lock); + + if (full_cap == 0 || full_cap == SPWR_BATTERY_VALUE_UNKNOWN) + return -ENODATA; + + if (remaining_cap == SPWR_BATTERY_VALUE_UNKNOWN) + return -ENODATA; + + return remaining_cap * 100 / full_cap; +} + +static int spwr_battery_prop_capacity_level(struct spwr_battery_device *bat) +{ + u32 state = get_unaligned_le32(&bat->bst.state); + u32 remaining_cap = get_unaligned_le32(&bat->bst.remaining_cap); + + lockdep_assert_held(&bat->lock); + + if (state & SAM_BATTERY_STATE_CRITICAL) + return POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL; + + if (spwr_battery_is_full(bat)) + return POWER_SUPPLY_CAPACITY_LEVEL_FULL; + + if (remaining_cap <= bat->alarm) + return POWER_SUPPLY_CAPACITY_LEVEL_LOW; + + return POWER_SUPPLY_CAPACITY_LEVEL_NORMAL; +} + +static int spwr_battery_get_property(struct power_supply *psy, enum power_supply_property psp, + union power_supply_propval *val) +{ + struct spwr_battery_device *bat = power_supply_get_drvdata(psy); + u32 value; + int status; + + mutex_lock(&bat->lock); + + status = spwr_battery_update_bst_unlocked(bat, true); + if (status) + goto out; + + /* Abort if battery is not present. */ + if (!spwr_battery_present(bat) && psp != POWER_SUPPLY_PROP_PRESENT) { + status = -ENODEV; + goto out; + } + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + val->intval = spwr_battery_prop_status(bat); + break; + + case POWER_SUPPLY_PROP_PRESENT: + val->intval = spwr_battery_present(bat); + break; + + case POWER_SUPPLY_PROP_TECHNOLOGY: + val->intval = spwr_battery_prop_technology(bat); + break; + + case POWER_SUPPLY_PROP_CYCLE_COUNT: + value = get_unaligned_le32(&bat->bix.cycle_count); + if (value != SPWR_BATTERY_VALUE_UNKNOWN) + val->intval = value; + else + status = -ENODATA; + break; + + case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: + value = get_unaligned_le32(&bat->bix.design_voltage); + if (value != SPWR_BATTERY_VALUE_UNKNOWN) + val->intval = value * 1000; + else + status = -ENODATA; + break; + + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + value = get_unaligned_le32(&bat->bst.present_voltage); + if (value != SPWR_BATTERY_VALUE_UNKNOWN) + val->intval = value * 1000; + else + status = -ENODATA; + break; + + case POWER_SUPPLY_PROP_CURRENT_NOW: + case POWER_SUPPLY_PROP_POWER_NOW: + value = get_unaligned_le32(&bat->bst.present_rate); + if (value != SPWR_BATTERY_VALUE_UNKNOWN) + val->intval = value * 1000; + else + status = -ENODATA; + break; + + case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: + case POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN: + value = get_unaligned_le32(&bat->bix.design_cap); + if (value != SPWR_BATTERY_VALUE_UNKNOWN) + val->intval = value * 1000; + else + status = -ENODATA; + break; + + case POWER_SUPPLY_PROP_CHARGE_FULL: + case POWER_SUPPLY_PROP_ENERGY_FULL: + value = get_unaligned_le32(&bat->bix.last_full_charge_cap); + if (value != SPWR_BATTERY_VALUE_UNKNOWN) + val->intval = value * 1000; + else + status = -ENODATA; + break; + + case POWER_SUPPLY_PROP_CHARGE_NOW: + case POWER_SUPPLY_PROP_ENERGY_NOW: + value = get_unaligned_le32(&bat->bst.remaining_cap); + if (value != SPWR_BATTERY_VALUE_UNKNOWN) + val->intval = value * 1000; + else + status = -ENODATA; + break; + + case POWER_SUPPLY_PROP_CAPACITY: + val->intval = spwr_battery_prop_capacity(bat); + break; + + case POWER_SUPPLY_PROP_CAPACITY_LEVEL: + val->intval = spwr_battery_prop_capacity_level(bat); + break; + + case POWER_SUPPLY_PROP_MODEL_NAME: + val->strval = bat->bix.model; + break; + + case POWER_SUPPLY_PROP_MANUFACTURER: + val->strval = bat->bix.oem_info; + break; + + case POWER_SUPPLY_PROP_SERIAL_NUMBER: + val->strval = bat->bix.serial; + break; + + default: + status = -EINVAL; + break; + } + +out: + mutex_unlock(&bat->lock); + return status; +} + + +/* -- Alarm attribute. ------------------------------------------------------ */ + +static ssize_t alarm_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct power_supply *psy = dev_get_drvdata(dev); + struct spwr_battery_device *bat = power_supply_get_drvdata(psy); + int status; + + mutex_lock(&bat->lock); + status = sysfs_emit(buf, "%d\n", bat->alarm * 1000); + mutex_unlock(&bat->lock); + + return status; +} + +static ssize_t alarm_store(struct device *dev, struct device_attribute *attr, const char *buf, + size_t count) +{ + struct power_supply *psy = dev_get_drvdata(dev); + struct spwr_battery_device *bat = power_supply_get_drvdata(psy); + unsigned long value; + int status; + + status = kstrtoul(buf, 0, &value); + if (status) + return status; + + mutex_lock(&bat->lock); + + if (!spwr_battery_present(bat)) { + mutex_unlock(&bat->lock); + return -ENODEV; + } + + status = spwr_battery_set_alarm_unlocked(bat, value / 1000); + if (status) { + mutex_unlock(&bat->lock); + return status; + } + + mutex_unlock(&bat->lock); + return count; +} + +static DEVICE_ATTR_RW(alarm); + +static struct attribute *spwr_battery_attrs[] = { + &dev_attr_alarm.attr, + NULL, +}; +ATTRIBUTE_GROUPS(spwr_battery); + + +/* -- Device setup. --------------------------------------------------------- */ + +static void spwr_battery_init(struct spwr_battery_device *bat, struct ssam_device *sdev, + struct ssam_event_registry registry, const char *name) +{ + mutex_init(&bat->lock); + strncpy(bat->name, name, ARRAY_SIZE(bat->name) - 1); + + bat->sdev = sdev; + + bat->notif.base.priority = 1; + bat->notif.base.fn = spwr_notify_bat; + bat->notif.event.reg = registry; + bat->notif.event.id.target_category = sdev->uid.category; + bat->notif.event.id.instance = 0; + bat->notif.event.mask = SSAM_EVENT_MASK_STRICT; + bat->notif.event.flags = SSAM_EVENT_SEQUENCED; + + bat->psy_desc.name = bat->name; + bat->psy_desc.type = POWER_SUPPLY_TYPE_BATTERY; + bat->psy_desc.get_property = spwr_battery_get_property; + + INIT_DELAYED_WORK(&bat->update_work, spwr_battery_update_bst_workfn); +} + +static int spwr_battery_register(struct spwr_battery_device *bat) +{ + struct power_supply_config psy_cfg = {}; + __le32 sta; + int status; + + /* Make sure the device is there and functioning properly. */ + status = ssam_retry(ssam_bat_get_sta, bat->sdev, &sta); + if (status) + return status; + + if ((le32_to_cpu(sta) & SAM_BATTERY_STA_OK) != SAM_BATTERY_STA_OK) + return -ENODEV; + + /* Satisfy lockdep although we are in an exclusive context here. */ + mutex_lock(&bat->lock); + + status = spwr_battery_update_bix_unlocked(bat); + if (status) { + mutex_unlock(&bat->lock); + return status; + } + + if (spwr_battery_present(bat)) { + u32 cap_warn = get_unaligned_le32(&bat->bix.design_cap_warn); + + status = spwr_battery_set_alarm_unlocked(bat, cap_warn); + if (status) { + mutex_unlock(&bat->lock); + return status; + } + } + + mutex_unlock(&bat->lock); + + bat->psy_desc.external_power_changed = spwr_external_power_changed; + + switch (get_unaligned_le32(&bat->bix.power_unit)) { + case SAM_BATTERY_POWER_UNIT_mW: + bat->psy_desc.properties = spwr_battery_props_eng; + bat->psy_desc.num_properties = ARRAY_SIZE(spwr_battery_props_eng); + break; + + case SAM_BATTERY_POWER_UNIT_mA: + bat->psy_desc.properties = spwr_battery_props_chg; + bat->psy_desc.num_properties = ARRAY_SIZE(spwr_battery_props_chg); + break; + + default: + dev_err(&bat->sdev->dev, "unsupported battery power unit: %u\n", + get_unaligned_le32(&bat->bix.power_unit)); + return -EINVAL; + } + + psy_cfg.drv_data = bat; + psy_cfg.attr_grp = spwr_battery_groups; + + bat->psy = devm_power_supply_register(&bat->sdev->dev, &bat->psy_desc, &psy_cfg); + if (IS_ERR(bat->psy)) + return PTR_ERR(bat->psy); + + return ssam_notifier_register(bat->sdev->ctrl, &bat->notif); +} + + +/* -- Driver setup. --------------------------------------------------------- */ + +static int __maybe_unused surface_battery_resume(struct device *dev) +{ + return spwr_battery_recheck_full(dev_get_drvdata(dev)); +} +static SIMPLE_DEV_PM_OPS(surface_battery_pm_ops, NULL, surface_battery_resume); + +static int surface_battery_probe(struct ssam_device *sdev) +{ + const struct spwr_psy_properties *p; + struct spwr_battery_device *bat; + + p = ssam_device_get_match_data(sdev); + if (!p) + return -ENODEV; + + bat = devm_kzalloc(&sdev->dev, sizeof(*bat), GFP_KERNEL); + if (!bat) + return -ENOMEM; + + spwr_battery_init(bat, sdev, p->registry, p->name); + ssam_device_set_drvdata(sdev, bat); + + return spwr_battery_register(bat); +} + +static void surface_battery_remove(struct ssam_device *sdev) +{ + struct spwr_battery_device *bat = ssam_device_get_drvdata(sdev); + + ssam_notifier_unregister(sdev->ctrl, &bat->notif); + cancel_delayed_work_sync(&bat->update_work); +} + +static const struct spwr_psy_properties spwr_psy_props_bat1 = { + .name = "BAT1", + .registry = SSAM_EVENT_REGISTRY_SAM, +}; + +static const struct spwr_psy_properties spwr_psy_props_bat2_sb3 = { + .name = "BAT2", + .registry = SSAM_EVENT_REGISTRY_KIP, +}; + +static const struct ssam_device_id surface_battery_match[] = { + { SSAM_SDEV(BAT, 0x01, 0x01, 0x00), (unsigned long)&spwr_psy_props_bat1 }, + { SSAM_SDEV(BAT, 0x02, 0x01, 0x00), (unsigned long)&spwr_psy_props_bat2_sb3 }, + { }, +}; +MODULE_DEVICE_TABLE(ssam, surface_battery_match); + +static struct ssam_device_driver surface_battery_driver = { + .probe = surface_battery_probe, + .remove = surface_battery_remove, + .match_table = surface_battery_match, + .driver = { + .name = "surface_battery", + .pm = &surface_battery_pm_ops, + .probe_type = PROBE_PREFER_ASYNCHRONOUS, + }, +}; +module_ssam_device_driver(surface_battery_driver); + +MODULE_AUTHOR("Maximilian Luz <luzmaximilian@gmail.com>"); +MODULE_DESCRIPTION("Battery driver for Surface System Aggregator Module"); +MODULE_LICENSE("GPL"); diff --git a/drivers/power/supply/surface_charger.c b/drivers/power/supply/surface_charger.c new file mode 100644 index 000000000000..81a5b79822c9 --- /dev/null +++ b/drivers/power/supply/surface_charger.c @@ -0,0 +1,282 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * AC driver for 7th-generation Microsoft Surface devices via Surface System + * Aggregator Module (SSAM). + * + * Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com> + */ + +#include <asm/unaligned.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/power_supply.h> +#include <linux/types.h> + +#include <linux/surface_aggregator/device.h> + + +/* -- SAM interface. -------------------------------------------------------- */ + +enum sam_event_cid_bat { + SAM_EVENT_CID_BAT_ADP = 0x17, +}; + +enum sam_battery_sta { + SAM_BATTERY_STA_OK = 0x0f, + SAM_BATTERY_STA_PRESENT = 0x10, +}; + +/* Get battery status (_STA). */ +SSAM_DEFINE_SYNC_REQUEST_CL_R(ssam_bat_get_sta, __le32, { + .target_category = SSAM_SSH_TC_BAT, + .command_id = 0x01, +}); + +/* Get platform power source for battery (_PSR / DPTF PSRC). */ +SSAM_DEFINE_SYNC_REQUEST_CL_R(ssam_bat_get_psrc, __le32, { + .target_category = SSAM_SSH_TC_BAT, + .command_id = 0x0d, +}); + + +/* -- Device structures. ---------------------------------------------------- */ + +struct spwr_psy_properties { + const char *name; + struct ssam_event_registry registry; +}; + +struct spwr_ac_device { + struct ssam_device *sdev; + + char name[32]; + struct power_supply *psy; + struct power_supply_desc psy_desc; + + struct ssam_event_notifier notif; + + struct mutex lock; /* Guards access to state below. */ + + __le32 state; +}; + + +/* -- State management. ----------------------------------------------------- */ + +static int spwr_ac_update_unlocked(struct spwr_ac_device *ac) +{ + u32 old = ac->state; + int status; + + lockdep_assert_held(&ac->lock); + + status = ssam_retry(ssam_bat_get_psrc, ac->sdev, &ac->state); + if (status < 0) + return status; + + return old != ac->state; +} + +static int spwr_ac_update(struct spwr_ac_device *ac) +{ + int status; + + mutex_lock(&ac->lock); + status = spwr_ac_update_unlocked(ac); + mutex_unlock(&ac->lock); + + return status; +} + +static int spwr_ac_recheck(struct spwr_ac_device *ac) +{ + int status; + + status = spwr_ac_update(ac); + if (status > 0) + power_supply_changed(ac->psy); + + return status >= 0 ? 0 : status; +} + +static u32 spwr_notify_ac(struct ssam_event_notifier *nf, const struct ssam_event *event) +{ + struct spwr_ac_device *ac; + int status; + + ac = container_of(nf, struct spwr_ac_device, notif); + + dev_dbg(&ac->sdev->dev, "power event (cid = %#04x, iid = %#04x, tid = %#04x)\n", + event->command_id, event->instance_id, event->target_id); + + /* + * Allow events of all targets/instances here. Global adapter status + * seems to be handled via target=1 and instance=1, but events are + * reported on all targets/instances in use. + * + * While it should be enough to just listen on 1/1, listen everywhere to + * make sure we don't miss anything. + */ + + switch (event->command_id) { + case SAM_EVENT_CID_BAT_ADP: + status = spwr_ac_recheck(ac); + return ssam_notifier_from_errno(status) | SSAM_NOTIF_HANDLED; + + default: + return 0; + } +} + + +/* -- Properties. ----------------------------------------------------------- */ + +static const enum power_supply_property spwr_ac_props[] = { + POWER_SUPPLY_PROP_ONLINE, +}; + +static int spwr_ac_get_property(struct power_supply *psy, enum power_supply_property psp, + union power_supply_propval *val) +{ + struct spwr_ac_device *ac = power_supply_get_drvdata(psy); + int status; + + mutex_lock(&ac->lock); + + status = spwr_ac_update_unlocked(ac); + if (status) + goto out; + + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + val->intval = !!le32_to_cpu(ac->state); + break; + + default: + status = -EINVAL; + goto out; + } + +out: + mutex_unlock(&ac->lock); + return status; +} + + +/* -- Device setup. --------------------------------------------------------- */ + +static char *battery_supplied_to[] = { + "BAT1", + "BAT2", +}; + +static void spwr_ac_init(struct spwr_ac_device *ac, struct ssam_device *sdev, + struct ssam_event_registry registry, const char *name) +{ + mutex_init(&ac->lock); + strncpy(ac->name, name, ARRAY_SIZE(ac->name) - 1); + + ac->sdev = sdev; + + ac->notif.base.priority = 1; + ac->notif.base.fn = spwr_notify_ac; + ac->notif.event.reg = registry; + ac->notif.event.id.target_category = sdev->uid.category; + ac->notif.event.id.instance = 0; + ac->notif.event.mask = SSAM_EVENT_MASK_NONE; + ac->notif.event.flags = SSAM_EVENT_SEQUENCED; + + ac->psy_desc.name = ac->name; + ac->psy_desc.type = POWER_SUPPLY_TYPE_MAINS; + ac->psy_desc.properties = spwr_ac_props; + ac->psy_desc.num_properties = ARRAY_SIZE(spwr_ac_props); + ac->psy_desc.get_property = spwr_ac_get_property; +} + +static int spwr_ac_register(struct spwr_ac_device *ac) +{ + struct power_supply_config psy_cfg = {}; + __le32 sta; + int status; + + /* Make sure the device is there and functioning properly. */ + status = ssam_retry(ssam_bat_get_sta, ac->sdev, &sta); + if (status) + return status; + + if ((le32_to_cpu(sta) & SAM_BATTERY_STA_OK) != SAM_BATTERY_STA_OK) + return -ENODEV; + + psy_cfg.drv_data = ac; + psy_cfg.supplied_to = battery_supplied_to; + psy_cfg.num_supplicants = ARRAY_SIZE(battery_supplied_to); + + ac->psy = devm_power_supply_register(&ac->sdev->dev, &ac->psy_desc, &psy_cfg); + if (IS_ERR(ac->psy)) + return PTR_ERR(ac->psy); + + return ssam_notifier_register(ac->sdev->ctrl, &ac->notif); +} + + +/* -- Driver setup. --------------------------------------------------------- */ + +static int __maybe_unused surface_ac_resume(struct device *dev) +{ + return spwr_ac_recheck(dev_get_drvdata(dev)); +} +static SIMPLE_DEV_PM_OPS(surface_ac_pm_ops, NULL, surface_ac_resume); + +static int surface_ac_probe(struct ssam_device *sdev) +{ + const struct spwr_psy_properties *p; + struct spwr_ac_device *ac; + + p = ssam_device_get_match_data(sdev); + if (!p) + return -ENODEV; + + ac = devm_kzalloc(&sdev->dev, sizeof(*ac), GFP_KERNEL); + if (!ac) + return -ENOMEM; + + spwr_ac_init(ac, sdev, p->registry, p->name); + ssam_device_set_drvdata(sdev, ac); + + return spwr_ac_register(ac); +} + +static void surface_ac_remove(struct ssam_device *sdev) +{ + struct spwr_ac_device *ac = ssam_device_get_drvdata(sdev); + + ssam_notifier_unregister(sdev->ctrl, &ac->notif); +} + +static const struct spwr_psy_properties spwr_psy_props_adp1 = { + .name = "ADP1", + .registry = SSAM_EVENT_REGISTRY_SAM, +}; + +static const struct ssam_device_id surface_ac_match[] = { + { SSAM_SDEV(BAT, 0x01, 0x01, 0x01), (unsigned long)&spwr_psy_props_adp1 }, + { }, +}; +MODULE_DEVICE_TABLE(ssam, surface_ac_match); + +static struct ssam_device_driver surface_ac_driver = { + .probe = surface_ac_probe, + .remove = surface_ac_remove, + .match_table = surface_ac_match, + .driver = { + .name = "surface_ac", + .pm = &surface_ac_pm_ops, + .probe_type = PROBE_PREFER_ASYNCHRONOUS, + }, +}; +module_ssam_device_driver(surface_ac_driver); + +MODULE_AUTHOR("Maximilian Luz <luzmaximilian@gmail.com>"); +MODULE_DESCRIPTION("AC driver for Surface System Aggregator Module"); +MODULE_LICENSE("GPL"); diff --git a/drivers/power/supply/tps65090-charger.c b/drivers/power/supply/tps65090-charger.c index 6b0098e5a88b..0990b2fa6cd8 100644 --- a/drivers/power/supply/tps65090-charger.c +++ b/drivers/power/supply/tps65090-charger.c @@ -301,7 +301,7 @@ static int tps65090_charger_probe(struct platform_device *pdev) if (irq != -ENXIO) { ret = devm_request_threaded_irq(&pdev->dev, irq, NULL, - tps65090_charger_isr, 0, "tps65090-charger", cdata); + tps65090_charger_isr, IRQF_ONESHOT, "tps65090-charger", cdata); if (ret) { dev_err(cdata->dev, "Unable to register irq %d err %d\n", irq, diff --git a/drivers/power/supply/tps65217_charger.c b/drivers/power/supply/tps65217_charger.c index 814c2b81fdfe..ba33d1617e0b 100644 --- a/drivers/power/supply/tps65217_charger.c +++ b/drivers/power/supply/tps65217_charger.c @@ -238,7 +238,7 @@ static int tps65217_charger_probe(struct platform_device *pdev) for (i = 0; i < NUM_CHARGER_IRQS; i++) { ret = devm_request_threaded_irq(&pdev->dev, irq[i], NULL, tps65217_charger_irq, - 0, "tps65217-charger", + IRQF_ONESHOT, "tps65217-charger", charger); if (ret) { dev_err(charger->dev, diff --git a/drivers/power/supply/z2_battery.c b/drivers/power/supply/z2_battery.c index b1508fe70e5e..7ed4e4bb26ec 100644 --- a/drivers/power/supply/z2_battery.c +++ b/drivers/power/supply/z2_battery.c @@ -90,9 +90,6 @@ static void z2_batt_ext_power_changed(struct power_supply *batt_ps) 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); |