diff options
Diffstat (limited to 'drivers/power')
-rw-r--r-- | drivers/power/reset/Kconfig | 11 | ||||
-rw-r--r-- | drivers/power/reset/Makefile | 1 | ||||
-rw-r--r-- | drivers/power/reset/qcom-pon.c | 91 | ||||
-rw-r--r-- | drivers/power/reset/zx-reboot.c | 1 | ||||
-rw-r--r-- | drivers/power/supply/Kconfig | 21 | ||||
-rw-r--r-- | drivers/power/supply/Makefile | 2 | ||||
-rw-r--r-- | drivers/power/supply/ab8500_fg.c | 14 | ||||
-rw-r--r-- | drivers/power/supply/adp5061.c | 745 | ||||
-rw-r--r-- | drivers/power/supply/cros_usbpd-charger.c | 544 | ||||
-rw-r--r-- | drivers/power/supply/max1721x_battery.c | 2 | ||||
-rw-r--r-- | drivers/power/supply/sbs-battery.c | 67 | ||||
-rw-r--r-- | drivers/power/supply/tps65217_charger.c | 22 |
12 files changed, 1482 insertions, 39 deletions
diff --git a/drivers/power/reset/Kconfig b/drivers/power/reset/Kconfig index df58fc878b3e..6533aa560aa1 100644 --- a/drivers/power/reset/Kconfig +++ b/drivers/power/reset/Kconfig @@ -104,6 +104,17 @@ config POWER_RESET_MSM help Power off and restart support for Qualcomm boards. +config POWER_RESET_QCOM_PON + tristate "Qualcomm power-on driver" + depends on ARCH_QCOM + depends on MFD_SPMI_PMIC + select REBOOT_MODE + help + Power On support for Qualcomm boards. + If you have a Qualcomm platform and need support for + power-on and reboot reason, Say Y. + If unsure, Say N. + config POWER_RESET_OCELOT_RESET bool "Microsemi Ocelot reset driver" depends on MSCC_OCELOT || COMPILE_TEST diff --git a/drivers/power/reset/Makefile b/drivers/power/reset/Makefile index 7778c7485cf1..0aebee954ac1 100644 --- a/drivers/power/reset/Makefile +++ b/drivers/power/reset/Makefile @@ -11,6 +11,7 @@ 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_MSM) += msm-poweroff.o +obj-$(CONFIG_POWER_RESET_QCOM_PON) += qcom-pon.o obj-$(CONFIG_POWER_RESET_OCELOT_RESET) += ocelot-reset.o obj-$(CONFIG_POWER_RESET_PIIX4_POWEROFF) += piix4-poweroff.o obj-$(CONFIG_POWER_RESET_LTC2952) += ltc2952-poweroff.o diff --git a/drivers/power/reset/qcom-pon.c b/drivers/power/reset/qcom-pon.c new file mode 100644 index 000000000000..0c4caaa7e88f --- /dev/null +++ b/drivers/power/reset/qcom-pon.c @@ -0,0 +1,91 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2017-18 Linaro Limited + +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_platform.h> +#include <linux/platform_device.h> +#include <linux/reboot.h> +#include <linux/reboot-mode.h> +#include <linux/regmap.h> + +#define PON_SOFT_RB_SPARE 0x8f + +struct pm8916_pon { + struct device *dev; + struct regmap *regmap; + u32 baseaddr; + struct reboot_mode_driver reboot_mode; +}; + +static int pm8916_reboot_mode_write(struct reboot_mode_driver *reboot, + unsigned int magic) +{ + struct pm8916_pon *pon = container_of + (reboot, struct pm8916_pon, reboot_mode); + int ret; + + ret = regmap_update_bits(pon->regmap, + pon->baseaddr + PON_SOFT_RB_SPARE, + 0xfc, magic << 2); + if (ret < 0) + dev_err(pon->dev, "update reboot mode bits failed\n"); + + return ret; +} + +static int pm8916_pon_probe(struct platform_device *pdev) +{ + struct pm8916_pon *pon; + int error; + + pon = devm_kzalloc(&pdev->dev, sizeof(*pon), GFP_KERNEL); + if (!pon) + return -ENOMEM; + + pon->dev = &pdev->dev; + + pon->regmap = dev_get_regmap(pdev->dev.parent, NULL); + if (!pon->regmap) { + dev_err(&pdev->dev, "failed to locate regmap\n"); + return -ENODEV; + } + + error = of_property_read_u32(pdev->dev.of_node, "reg", + &pon->baseaddr); + if (error) + return error; + + pon->reboot_mode.dev = &pdev->dev; + pon->reboot_mode.write = pm8916_reboot_mode_write; + error = devm_reboot_mode_register(&pdev->dev, &pon->reboot_mode); + if (error) { + dev_err(&pdev->dev, "can't register reboot mode\n"); + return error; + } + + platform_set_drvdata(pdev, pon); + + return devm_of_platform_populate(&pdev->dev); +} + +static const struct of_device_id pm8916_pon_id_table[] = { + { .compatible = "qcom,pm8916-pon" }, + { } +}; +MODULE_DEVICE_TABLE(of, pm8916_pon_id_table); + +static struct platform_driver pm8916_pon_driver = { + .probe = pm8916_pon_probe, + .driver = { + .name = "pm8916-pon", + .of_match_table = of_match_ptr(pm8916_pon_id_table), + }, +}; +module_platform_driver(pm8916_pon_driver); + +MODULE_DESCRIPTION("pm8916 Power On driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/power/reset/zx-reboot.c b/drivers/power/reset/zx-reboot.c index c03e96e6a041..186901c96c01 100644 --- a/drivers/power/reset/zx-reboot.c +++ b/drivers/power/reset/zx-reboot.c @@ -51,6 +51,7 @@ static int zx_reboot_probe(struct platform_device *pdev) np = of_find_compatible_node(NULL, NULL, "zte,zx296702-pcu"); pcu_base = of_iomap(np, 0); + of_node_put(np); if (!pcu_base) { iounmap(base); WARN(1, "failed to map pcu_base address"); diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig index 518a88c4adfa..ff6dab0bf0dd 100644 --- a/drivers/power/supply/Kconfig +++ b/drivers/power/supply/Kconfig @@ -75,6 +75,17 @@ config BATTERY_88PM860X help Say Y here to enable battery monitor for Marvell 88PM860x chip. +config CHARGER_ADP5061 + tristate "ADP5061 battery charger driver" + depends on I2C + select REGMAP_I2C + help + Say Y here to enable support for the ADP5061 standalone battery + charger. + + This driver can be built as a module. If so, the module will be + called adp5061. + config BATTERY_ACT8945A tristate "Active-semi ACT8945A charger driver" depends on MFD_ACT8945A || COMPILE_TEST @@ -624,4 +635,14 @@ config CHARGER_RT9455 help Say Y to enable support for Richtek RT9455 battery charger. +config CHARGER_CROS_USBPD + tristate "ChromeOS EC based USBPD charger" + depends on MFD_CROS_EC + default n + help + Say Y here to enable ChromeOS EC based USBPD charger + driver. This driver gets various bits of information about + what is connected to USB PD ports from the EC and converts + that into power_supply properties. + endif # POWER_SUPPLY diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile index e83aa843bcc6..a26b402c45d9 100644 --- a/drivers/power/supply/Makefile +++ b/drivers/power/supply/Makefile @@ -18,6 +18,7 @@ obj-$(CONFIG_WM8350_POWER) += wm8350_power.o obj-$(CONFIG_TEST_POWER) += test_power.o obj-$(CONFIG_BATTERY_88PM860X) += 88pm860x_battery.o +obj-$(CONFIG_CHARGER_ADP5061) += adp5061.o obj-$(CONFIG_BATTERY_ACT8945A) += act8945a_charger.o obj-$(CONFIG_BATTERY_AXP20X) += axp20x_battery.o obj-$(CONFIG_CHARGER_AXP20X) += axp20x_ac_power.o @@ -83,3 +84,4 @@ obj-$(CONFIG_CHARGER_TPS65090) += tps65090-charger.o obj-$(CONFIG_CHARGER_TPS65217) += tps65217_charger.o obj-$(CONFIG_AXP288_FUEL_GAUGE) += axp288_fuel_gauge.o obj-$(CONFIG_AXP288_CHARGER) += axp288_charger.o +obj-$(CONFIG_CHARGER_CROS_USBPD) += cros_usbpd-charger.o diff --git a/drivers/power/supply/ab8500_fg.c b/drivers/power/supply/ab8500_fg.c index d9c6c7bedd85..02356f9b5f22 100644 --- a/drivers/power/supply/ab8500_fg.c +++ b/drivers/power/supply/ab8500_fg.c @@ -379,15 +379,13 @@ static int ab8500_fg_is_low_curr(struct ab8500_fg *di, int curr) */ static int ab8500_fg_add_cap_sample(struct ab8500_fg *di, int sample) { - struct timespec64 ts64; + time64_t now = ktime_get_boottime_seconds(); struct ab8500_fg_avg_cap *avg = &di->avg_cap; - getnstimeofday64(&ts64); - do { avg->sum += sample - avg->samples[avg->pos]; avg->samples[avg->pos] = sample; - avg->time_stamps[avg->pos] = ts64.tv_sec; + avg->time_stamps[avg->pos] = now; avg->pos++; if (avg->pos == NBR_AVG_SAMPLES) @@ -400,7 +398,7 @@ static int ab8500_fg_add_cap_sample(struct ab8500_fg *di, int sample) * Check the time stamp for each sample. If too old, * replace with latest sample */ - } while (ts64.tv_sec - VALID_CAPACITY_SEC > avg->time_stamps[avg->pos]); + } while (now - VALID_CAPACITY_SEC > avg->time_stamps[avg->pos]); avg->avg = avg->sum / avg->nbr_samples; @@ -439,14 +437,14 @@ static void ab8500_fg_clear_cap_samples(struct ab8500_fg *di) static void ab8500_fg_fill_cap_sample(struct ab8500_fg *di, int sample) { int i; - struct timespec64 ts64; + time64_t now; struct ab8500_fg_avg_cap *avg = &di->avg_cap; - getnstimeofday64(&ts64); + now = ktime_get_boottime_seconds(); for (i = 0; i < NBR_AVG_SAMPLES; i++) { avg->samples[i] = sample; - avg->time_stamps[i] = ts64.tv_sec; + avg->time_stamps[i] = now; } avg->pos = 0; diff --git a/drivers/power/supply/adp5061.c b/drivers/power/supply/adp5061.c new file mode 100644 index 000000000000..939fd3d8fb1a --- /dev/null +++ b/drivers/power/supply/adp5061.c @@ -0,0 +1,745 @@ +/* + * ADP5061 I2C Programmable Linear Battery Charger + * + * Copyright 2018 Analog Devices Inc. + * + * Licensed under the GPL-2. + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/i2c.h> +#include <linux/delay.h> +#include <linux/pm.h> +#include <linux/mod_devicetable.h> +#include <linux/power_supply.h> +#include <linux/platform_device.h> +#include <linux/of.h> +#include <linux/regmap.h> + +/* ADP5061 registers definition */ +#define ADP5061_ID 0x00 +#define ADP5061_REV 0x01 +#define ADP5061_VINX_SET 0x02 +#define ADP5061_TERM_SET 0x03 +#define ADP5061_CHG_CURR 0x04 +#define ADP5061_VOLTAGE_TH 0x05 +#define ADP5061_TIMER_SET 0x06 +#define ADP5061_FUNC_SET_1 0x07 +#define ADP5061_FUNC_SET_2 0x08 +#define ADP5061_INT_EN 0x09 +#define ADP5061_INT_ACT 0x0A +#define ADP5061_CHG_STATUS_1 0x0B +#define ADP5061_CHG_STATUS_2 0x0C +#define ADP5061_FAULT 0x0D +#define ADP5061_BATTERY_SHORT 0x10 +#define ADP5061_IEND 0x11 + +/* ADP5061_VINX_SET */ +#define ADP5061_VINX_SET_ILIM_MSK GENMASK(3, 0) +#define ADP5061_VINX_SET_ILIM_MODE(x) (((x) & 0x0F) << 0) + +/* ADP5061_TERM_SET */ +#define ADP5061_TERM_SET_VTRM_MSK GENMASK(7, 2) +#define ADP5061_TERM_SET_VTRM_MODE(x) (((x) & 0x3F) << 2) +#define ADP5061_TERM_SET_CHG_VLIM_MSK GENMASK(1, 0) +#define ADP5061_TERM_SET_CHG_VLIM_MODE(x) (((x) & 0x03) << 0) + +/* ADP5061_CHG_CURR */ +#define ADP5061_CHG_CURR_ICHG_MSK GENMASK(6, 2) +#define ADP5061_CHG_CURR_ICHG_MODE(x) (((x) & 0x1F) << 2) +#define ADP5061_CHG_CURR_ITRK_DEAD_MSK GENMASK(1, 0) +#define ADP5061_CHG_CURR_ITRK_DEAD_MODE(x) (((x) & 0x03) << 0) + +/* ADP5061_VOLTAGE_TH */ +#define ADP5061_VOLTAGE_TH_DIS_RCH_MSK BIT(7) +#define ADP5061_VOLTAGE_TH_DIS_RCH_MODE(x) (((x) & 0x01) << 7) +#define ADP5061_VOLTAGE_TH_VRCH_MSK GENMASK(6, 5) +#define ADP5061_VOLTAGE_TH_VRCH_MODE(x) (((x) & 0x03) << 5) +#define ADP5061_VOLTAGE_TH_VTRK_DEAD_MSK GENMASK(4, 3) +#define ADP5061_VOLTAGE_TH_VTRK_DEAD_MODE(x) (((x) & 0x03) << 3) +#define ADP5061_VOLTAGE_TH_VWEAK_MSK GENMASK(2, 0) +#define ADP5061_VOLTAGE_TH_VWEAK_MODE(x) (((x) & 0x07) << 0) + +/* ADP5061_CHG_STATUS_1 */ +#define ADP5061_CHG_STATUS_1_VIN_OV(x) (((x) >> 7) & 0x1) +#define ADP5061_CHG_STATUS_1_VIN_OK(x) (((x) >> 6) & 0x1) +#define ADP5061_CHG_STATUS_1_VIN_ILIM(x) (((x) >> 5) & 0x1) +#define ADP5061_CHG_STATUS_1_THERM_LIM(x) (((x) >> 4) & 0x1) +#define ADP5061_CHG_STATUS_1_CHDONE(x) (((x) >> 3) & 0x1) +#define ADP5061_CHG_STATUS_1_CHG_STATUS(x) (((x) >> 0) & 0x7) + +/* ADP5061_CHG_STATUS_2 */ +#define ADP5061_CHG_STATUS_2_THR_STATUS(x) (((x) >> 5) & 0x7) +#define ADP5061_CHG_STATUS_2_RCH_LIM_INFO(x) (((x) >> 3) & 0x1) +#define ADP5061_CHG_STATUS_2_BAT_STATUS(x) (((x) >> 0) & 0x7) + +/* ADP5061_IEND */ +#define ADP5061_IEND_IEND_MSK GENMASK(7, 5) +#define ADP5061_IEND_IEND_MODE(x) (((x) & 0x07) << 5) + +#define ADP5061_NO_BATTERY 0x01 +#define ADP5061_ICHG_MAX 1300 // mA + +enum adp5061_chg_status { + ADP5061_CHG_OFF, + ADP5061_CHG_TRICKLE, + ADP5061_CHG_FAST_CC, + ADP5061_CHG_FAST_CV, + ADP5061_CHG_COMPLETE, + ADP5061_CHG_LDO_MODE, + ADP5061_CHG_TIMER_EXP, + ADP5061_CHG_BAT_DET, +}; + +static const int adp5061_chg_type[4] = { + [ADP5061_CHG_OFF] = POWER_SUPPLY_CHARGE_TYPE_NONE, + [ADP5061_CHG_TRICKLE] = POWER_SUPPLY_CHARGE_TYPE_TRICKLE, + [ADP5061_CHG_FAST_CC] = POWER_SUPPLY_CHARGE_TYPE_FAST, + [ADP5061_CHG_FAST_CV] = POWER_SUPPLY_CHARGE_TYPE_FAST, +}; + +static const int adp5061_vweak_th[8] = { + 2700, 2800, 2900, 3000, 3100, 3200, 3300, 3400, +}; + +static const int adp5061_prechg_current[4] = { + 5, 10, 20, 80, +}; + +static const int adp5061_vmin[4] = { + 2000, 2500, 2600, 2900, +}; + +static const int adp5061_const_chg_vmax[4] = { + 3200, 3400, 3700, 3800, +}; + +static const int adp5061_const_ichg[24] = { + 50, 100, 150, 200, 250, 300, 350, 400, 450, 500, 550, 600, 650, + 700, 750, 800, 850, 900, 950, 1000, 1050, 1100, 1200, 1300, +}; + +static const int adp5061_vmax[36] = { + 3800, 3820, 3840, 3860, 3880, 3900, 3920, 3940, 3960, 3980, + 4000, 4020, 4040, 4060, 4080, 4100, 4120, 4140, 4160, 4180, + 4200, 4220, 4240, 4260, 4280, 4300, 4320, 4340, 4360, 4380, + 4400, 4420, 4440, 4460, 4480, 4500, +}; + +static const int adp5061_in_current_lim[16] = { + 100, 150, 200, 250, 300, 400, 500, 600, 700, + 800, 900, 1000, 1200, 1500, 1800, 2100, +}; + +static const int adp5061_iend[8] = { + 12500, 32500, 52500, 72500, 92500, 117500, 142500, 170000, +}; + +struct adp5061_state { + struct i2c_client *client; + struct regmap *regmap; + struct power_supply *psy; +}; + +static int adp5061_get_array_index(const int *array, u8 size, int val) +{ + int i; + + for (i = 1; i < size; i++) { + if (val < array[i]) + break; + } + + return i-1; +} + +static int adp5061_get_status(struct adp5061_state *st, + u8 *status1, u8 *status2) +{ + u8 buf[2]; + int ret; + + /* CHG_STATUS1 and CHG_STATUS2 are adjacent regs */ + ret = regmap_bulk_read(st->regmap, ADP5061_CHG_STATUS_1, + &buf[0], 2); + if (ret < 0) + return ret; + + *status1 = buf[0]; + *status2 = buf[1]; + + return ret; +} + +static int adp5061_get_input_current_limit(struct adp5061_state *st, + union power_supply_propval *val) +{ + unsigned int regval; + int mode, ret; + + ret = regmap_read(st->regmap, ADP5061_VINX_SET, ®val); + if (ret < 0) + return ret; + + mode = ADP5061_VINX_SET_ILIM_MODE(regval); + val->intval = adp5061_in_current_lim[mode] * 1000; + + return ret; +} + +static int adp5061_set_input_current_limit(struct adp5061_state *st, int val) +{ + int index; + + /* Convert from uA to mA */ + val /= 1000; + index = adp5061_get_array_index(adp5061_in_current_lim, + ARRAY_SIZE(adp5061_in_current_lim), + val); + if (index < 0) + return index; + + return regmap_update_bits(st->regmap, ADP5061_VINX_SET, + ADP5061_VINX_SET_ILIM_MSK, + ADP5061_VINX_SET_ILIM_MODE(index)); +} + +static int adp5061_set_min_voltage(struct adp5061_state *st, int val) +{ + int index; + + /* Convert from uV to mV */ + val /= 1000; + index = adp5061_get_array_index(adp5061_vmin, + ARRAY_SIZE(adp5061_vmin), + val); + if (index < 0) + return index; + + return regmap_update_bits(st->regmap, ADP5061_VOLTAGE_TH, + ADP5061_VOLTAGE_TH_VTRK_DEAD_MSK, + ADP5061_VOLTAGE_TH_VTRK_DEAD_MODE(index)); +} + +static int adp5061_get_min_voltage(struct adp5061_state *st, + union power_supply_propval *val) +{ + unsigned int regval; + int ret; + + ret = regmap_read(st->regmap, ADP5061_VOLTAGE_TH, ®val); + if (ret < 0) + return ret; + + regval = ((regval & ADP5061_VOLTAGE_TH_VTRK_DEAD_MSK) >> 3); + val->intval = adp5061_vmin[regval] * 1000; + + return ret; +} + +static int adp5061_get_chg_volt_lim(struct adp5061_state *st, + union power_supply_propval *val) +{ + unsigned int regval; + int mode, ret; + + ret = regmap_read(st->regmap, ADP5061_TERM_SET, ®val); + if (ret < 0) + return ret; + + mode = ADP5061_TERM_SET_CHG_VLIM_MODE(regval); + val->intval = adp5061_const_chg_vmax[mode] * 1000; + + return ret; +} + +static int adp5061_get_max_voltage(struct adp5061_state *st, + union power_supply_propval *val) +{ + unsigned int regval; + int ret; + + ret = regmap_read(st->regmap, ADP5061_TERM_SET, ®val); + if (ret < 0) + return ret; + + regval = ((regval & ADP5061_TERM_SET_VTRM_MSK) >> 2) - 0x0F; + if (regval >= ARRAY_SIZE(adp5061_vmax)) + regval = ARRAY_SIZE(adp5061_vmax) - 1; + + val->intval = adp5061_vmax[regval] * 1000; + + return ret; +} + +static int adp5061_set_max_voltage(struct adp5061_state *st, int val) +{ + int vmax_index; + + /* Convert from uV to mV */ + val /= 1000; + if (val > 4500) + val = 4500; + + vmax_index = adp5061_get_array_index(adp5061_vmax, + ARRAY_SIZE(adp5061_vmax), val); + if (vmax_index < 0) + return vmax_index; + + vmax_index += 0x0F; + + return regmap_update_bits(st->regmap, ADP5061_TERM_SET, + ADP5061_TERM_SET_VTRM_MSK, + ADP5061_TERM_SET_VTRM_MODE(vmax_index)); +} + +static int adp5061_set_const_chg_vmax(struct adp5061_state *st, int val) +{ + int index; + + /* Convert from uV to mV */ + val /= 1000; + index = adp5061_get_array_index(adp5061_const_chg_vmax, + ARRAY_SIZE(adp5061_const_chg_vmax), + val); + if (index < 0) + return index; + + return regmap_update_bits(st->regmap, ADP5061_TERM_SET, + ADP5061_TERM_SET_CHG_VLIM_MSK, + ADP5061_TERM_SET_CHG_VLIM_MODE(index)); +} + +static int adp5061_set_const_chg_current(struct adp5061_state *st, int val) +{ + + int index; + + /* Convert from uA to mA */ + val /= 1000; + if (val > ADP5061_ICHG_MAX) + val = ADP5061_ICHG_MAX; + + index = adp5061_get_array_index(adp5061_const_ichg, + ARRAY_SIZE(adp5061_const_ichg), + val); + if (index < 0) + return index; + + return regmap_update_bits(st->regmap, ADP5061_CHG_CURR, + ADP5061_CHG_CURR_ICHG_MSK, + ADP5061_CHG_CURR_ICHG_MODE(index)); +} + +static int adp5061_get_const_chg_current(struct adp5061_state *st, + union power_supply_propval *val) +{ + unsigned int regval; + int ret; + + ret = regmap_read(st->regmap, ADP5061_CHG_CURR, ®val); + if (ret < 0) + return ret; + + regval = ((regval & ADP5061_CHG_CURR_ICHG_MSK) >> 2); + if (regval >= ARRAY_SIZE(adp5061_const_ichg)) + regval = ARRAY_SIZE(adp5061_const_ichg) - 1; + + val->intval = adp5061_const_ichg[regval] * 1000; + + return ret; +} + +static int adp5061_get_prechg_current(struct adp5061_state *st, + union power_supply_propval *val) +{ + unsigned int regval; + int ret; + + ret = regmap_read(st->regmap, ADP5061_CHG_CURR, ®val); + if (ret < 0) + return ret; + + regval &= ADP5061_CHG_CURR_ITRK_DEAD_MSK; + val->intval = adp5061_prechg_current[regval] * 1000; + + return ret; +} + +static int adp5061_set_prechg_current(struct adp5061_state *st, int val) +{ + int index; + + /* Convert from uA to mA */ + val /= 1000; + index = adp5061_get_array_index(adp5061_prechg_current, + ARRAY_SIZE(adp5061_prechg_current), + val); + if (index < 0) + return index; + + return regmap_update_bits(st->regmap, ADP5061_CHG_CURR, + ADP5061_CHG_CURR_ITRK_DEAD_MSK, + ADP5061_CHG_CURR_ITRK_DEAD_MODE(index)); +} + +static int adp5061_get_vweak_th(struct adp5061_state *st, + union power_supply_propval *val) +{ + unsigned int regval; + int ret; + + ret = regmap_read(st->regmap, ADP5061_VOLTAGE_TH, ®val); + if (ret < 0) + return ret; + + regval &= ADP5061_VOLTAGE_TH_VWEAK_MSK; + val->intval = adp5061_vweak_th[regval] * 1000; + + return ret; +} + +static int adp5061_set_vweak_th(struct adp5061_state *st, int val) +{ + int index; + + /* Convert from uV to mV */ + val /= 1000; + index = adp5061_get_array_index(adp5061_vweak_th, + ARRAY_SIZE(adp5061_vweak_th), + val); + if (index < 0) + return index; + + return regmap_update_bits(st->regmap, ADP5061_VOLTAGE_TH, + ADP5061_VOLTAGE_TH_VWEAK_MSK, + ADP5061_VOLTAGE_TH_VWEAK_MODE(index)); +} + +static int adp5061_get_chg_type(struct adp5061_state *st, + union power_supply_propval *val) +{ + u8 status1, status2; + int chg_type, ret; + + ret = adp5061_get_status(st, &status1, &status2); + if (ret < 0) + return ret; + + chg_type = adp5061_chg_type[ADP5061_CHG_STATUS_1_CHG_STATUS(status1)]; + if (chg_type > ADP5061_CHG_FAST_CV) + val->intval = POWER_SUPPLY_STATUS_UNKNOWN; + else + val->intval = chg_type; + + return ret; +} + +static int adp5061_get_charger_status(struct adp5061_state *st, + union power_supply_propval *val) +{ + u8 status1, status2; + int ret; + + ret = adp5061_get_status(st, &status1, &status2); + if (ret < 0) + return ret; + + switch (ADP5061_CHG_STATUS_1_CHG_STATUS(status1)) { + case ADP5061_CHG_OFF: + val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; + break; + case ADP5061_CHG_TRICKLE: + case ADP5061_CHG_FAST_CC: + case ADP5061_CHG_FAST_CV: + val->intval = POWER_SUPPLY_STATUS_CHARGING; + break; + case ADP5061_CHG_COMPLETE: + val->intval = POWER_SUPPLY_STATUS_FULL; + break; + case ADP5061_CHG_TIMER_EXP: + /* The battery must be discharging if there is a charge fault */ + val->intval = POWER_SUPPLY_STATUS_DISCHARGING; + break; + default: + val->intval = POWER_SUPPLY_STATUS_UNKNOWN; + } + + return ret; +} + +static int adp5061_get_battery_status(struct adp5061_state *st, + union power_supply_propval *val) +{ + u8 status1, status2; + int ret; + + ret = adp5061_get_status(st, &status1, &status2); + if (ret < 0) + return ret; + + switch (ADP5061_CHG_STATUS_2_BAT_STATUS(status2)) { + case 0x0: /* Battery monitor off */ + case 0x1: /* No battery */ + val->intval = POWER_SUPPLY_CAPACITY_LEVEL_UNKNOWN; + break; + case 0x2: /* VBAT < VTRK */ + val->intval = POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL; + break; + case 0x3: /* VTRK < VBAT_SNS < VWEAK */ + val->intval = POWER_SUPPLY_CAPACITY_LEVEL_LOW; + break; + case 0x4: /* VBAT_SNS > VWEAK */ + val->intval = POWER_SUPPLY_CAPACITY_LEVEL_NORMAL; + break; + } + + return ret; +} + +static int adp5061_get_termination_current(struct adp5061_state *st, + union power_supply_propval *val) +{ + unsigned int regval; + int ret; + + ret = regmap_read(st->regmap, ADP5061_IEND, ®val); + if (ret < 0) + return ret; + + regval = (regval & ADP5061_IEND_IEND_MSK) >> 5; + val->intval = adp5061_iend[regval]; + + return ret; +} + +static int adp5061_set_termination_current(struct adp5061_state *st, int val) +{ + int index; + + index = adp5061_get_array_index(adp5061_iend, + ARRAY_SIZE(adp5061_iend), + val); + if (index < 0) + return index; + + return regmap_update_bits(st->regmap, ADP5061_IEND, + ADP5061_IEND_IEND_MSK, + ADP5061_IEND_IEND_MODE(index)); +} + +static int adp5061_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct adp5061_state *st = power_supply_get_drvdata(psy); + u8 status1, status2; + int mode, ret; + + switch (psp) { + case POWER_SUPPLY_PROP_PRESENT: + ret = adp5061_get_status(st, &status1, &status2); + if (ret < 0) + return ret; + + mode = ADP5061_CHG_STATUS_2_BAT_STATUS(status2); + if (mode == ADP5061_NO_BATTERY) + val->intval = 0; + else + val->intval = 1; + break; + case POWER_SUPPLY_PROP_CHARGE_TYPE: + return adp5061_get_chg_type(st, val); + case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: + /* This property is used to indicate the input current + * limit into VINx (ILIM) + */ + return adp5061_get_input_current_limit(st, val); + case POWER_SUPPLY_PROP_VOLTAGE_MAX: + /* This property is used to indicate the termination + * voltage (VTRM) + */ + return adp5061_get_max_voltage(st, val); + case POWER_SUPPLY_PROP_VOLTAGE_MIN: + /* + * This property is used to indicate the trickle to fast + * charge threshold (VTRK_DEAD) + */ + return adp5061_get_min_voltage(st, val); + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX: + /* This property is used to indicate the charging + * voltage limit (CHG_VLIM) + */ + return adp5061_get_chg_volt_lim(st, val); + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: + /* + * This property is used to indicate the value of the constant + * current charge (ICHG) + */ + return adp5061_get_const_chg_current(st, val); + case POWER_SUPPLY_PROP_PRECHARGE_CURRENT: + /* + * This property is used to indicate the value of the trickle + * and weak charge currents (ITRK_DEAD) + */ + return adp5061_get_prechg_current(st, val); + case POWER_SUPPLY_PROP_VOLTAGE_AVG: + /* + * This property is used to set the VWEAK threshold + * bellow this value, weak charge mode is entered + * above this value, fast chargerge mode is entered + */ + return adp5061_get_vweak_th(st, val); + case POWER_SUPPLY_PROP_STATUS: + /* + * Indicate the charger status in relation to power + * supply status property + */ + return adp5061_get_charger_status(st, val); + case POWER_SUPPLY_PROP_CAPACITY_LEVEL: + /* + * Indicate the battery status in relation to power + * supply capacity level property + */ + return adp5061_get_battery_status(st, val); + case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT: + /* Indicate the values of the termination current */ + return adp5061_get_termination_current(st, val); + default: + return -EINVAL; + } + + return 0; +} + +static int adp5061_set_property(struct power_supply *psy, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct adp5061_state *st = power_supply_get_drvdata(psy); + + switch (psp) { + case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: + return adp5061_set_input_current_limit(st, val->intval); + case POWER_SUPPLY_PROP_VOLTAGE_MAX: + return adp5061_set_max_voltage(st, val->intval); + case POWER_SUPPLY_PROP_VOLTAGE_MIN: + return adp5061_set_min_voltage(st, val->intval); + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX: + return adp5061_set_const_chg_vmax(st, val->intval); + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: + return adp5061_set_const_chg_current(st, val->intval); + case POWER_SUPPLY_PROP_PRECHARGE_CURRENT: + return adp5061_set_prechg_current(st, val->intval); + case POWER_SUPPLY_PROP_VOLTAGE_AVG: + return adp5061_set_vweak_th(st, val->intval); + case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT: + return adp5061_set_termination_current(st, val->intval); + default: + return -EINVAL; + } + + return 0; +} + +static int adp5061_prop_writeable(struct power_supply *psy, + enum power_supply_property psp) +{ + switch (psp) { + case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: + case POWER_SUPPLY_PROP_VOLTAGE_MAX: + case POWER_SUPPLY_PROP_VOLTAGE_MIN: + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX: + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: + case POWER_SUPPLY_PROP_PRECHARGE_CURRENT: + case POWER_SUPPLY_PROP_VOLTAGE_AVG: + case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT: + return 1; + default: + return 0; + } +} + +static enum power_supply_property adp5061_props[] = { + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_CHARGE_TYPE, + POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT, + POWER_SUPPLY_PROP_VOLTAGE_MAX, + POWER_SUPPLY_PROP_VOLTAGE_MIN, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT, + POWER_SUPPLY_PROP_PRECHARGE_CURRENT, + POWER_SUPPLY_PROP_VOLTAGE_AVG, + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_CAPACITY_LEVEL, + POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT, +}; + +static const struct regmap_config adp5061_regmap_config = { + .reg_bits = 8, + .val_bits = 8, +}; + +static const struct power_supply_desc adp5061_desc = { + .name = "adp5061", + .type = POWER_SUPPLY_TYPE_USB, + .get_property = adp5061_get_property, + .set_property = adp5061_set_property, + .property_is_writeable = adp5061_prop_writeable, + .properties = adp5061_props, + .num_properties = ARRAY_SIZE(adp5061_props), +}; + +static int adp5061_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct power_supply_config psy_cfg = {}; + struct adp5061_state *st; + + st = devm_kzalloc(&client->dev, sizeof(*st), GFP_KERNEL); + if (!st) + return -ENOMEM; + + st->client = client; + st->regmap = devm_regmap_init_i2c(client, + &adp5061_regmap_config); + if (IS_ERR(st->regmap)) { + dev_err(&client->dev, "Failed to initialize register map\n"); + return -EINVAL; + } + + i2c_set_clientdata(client, st); + psy_cfg.drv_data = st; + + st->psy = devm_power_supply_register(&client->dev, + &adp5061_desc, + &psy_cfg); + + if (IS_ERR(st->psy)) { + dev_err(&client->dev, "Failed to register power supply\n"); + return PTR_ERR(st->psy); + } + + return 0; +} + +static const struct i2c_device_id adp5061_id[] = { + { "adp5061", 0}, + { } +}; +MODULE_DEVICE_TABLE(i2c, adp5061_id); + +static struct i2c_driver adp5061_driver = { + .driver = { + .name = KBUILD_MODNAME, + }, + .probe = adp5061_probe, + .id_table = adp5061_id, +}; +module_i2c_driver(adp5061_driver); + +MODULE_DESCRIPTION("Analog Devices adp5061 battery charger driver"); +MODULE_AUTHOR("Stefan Popa <stefan.popa@analog.com>"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/power/supply/cros_usbpd-charger.c b/drivers/power/supply/cros_usbpd-charger.c new file mode 100644 index 000000000000..3a0c96fd1bc1 --- /dev/null +++ b/drivers/power/supply/cros_usbpd-charger.c @@ -0,0 +1,544 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Power supply driver for ChromeOS EC based USB PD Charger. + * + * Copyright (c) 2014 - 2018 Google, Inc + */ + +#include <linux/module.h> +#include <linux/mfd/cros_ec.h> +#include <linux/mfd/cros_ec_commands.h> +#include <linux/platform_device.h> +#include <linux/power_supply.h> +#include <linux/slab.h> + +#define CHARGER_DIR_NAME "CROS_USBPD_CHARGER%d" +#define CHARGER_DIR_NAME_LENGTH sizeof(CHARGER_DIR_NAME) +#define CHARGER_CACHE_UPDATE_DELAY msecs_to_jiffies(500) +#define CHARGER_MANUFACTURER_MODEL_LENGTH 32 + +#define DRV_NAME "cros-usbpd-charger" + +struct port_data { + int port_number; + char name[CHARGER_DIR_NAME_LENGTH]; + char manufacturer[CHARGER_MANUFACTURER_MODEL_LENGTH]; + char model_name[CHARGER_MANUFACTURER_MODEL_LENGTH]; + struct power_supply *psy; + struct power_supply_desc psy_desc; + int psy_usb_type; + int psy_online; + int psy_status; + int psy_current_max; + int psy_voltage_max_design; + int psy_voltage_now; + int psy_power_max; + struct charger_data *charger; + unsigned long last_update; +}; + +struct charger_data { + struct device *dev; + struct cros_ec_dev *ec_dev; + struct cros_ec_device *ec_device; + int num_charger_ports; + int num_registered_psy; + struct port_data *ports[EC_USB_PD_MAX_PORTS]; + struct notifier_block notifier; +}; + +static enum power_supply_property cros_usbpd_charger_props[] = { + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_CURRENT_MAX, + POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_MODEL_NAME, + POWER_SUPPLY_PROP_MANUFACTURER, + POWER_SUPPLY_PROP_USB_TYPE +}; + +static enum power_supply_usb_type cros_usbpd_charger_usb_types[] = { + POWER_SUPPLY_USB_TYPE_UNKNOWN, + POWER_SUPPLY_USB_TYPE_SDP, + POWER_SUPPLY_USB_TYPE_DCP, + POWER_SUPPLY_USB_TYPE_CDP, + POWER_SUPPLY_USB_TYPE_C, + POWER_SUPPLY_USB_TYPE_PD, + POWER_SUPPLY_USB_TYPE_PD_DRP, + POWER_SUPPLY_USB_TYPE_APPLE_BRICK_ID +}; + +static int cros_usbpd_charger_ec_command(struct charger_data *charger, + unsigned int version, + unsigned int command, + void *outdata, + unsigned int outsize, + void *indata, + unsigned int insize) +{ + struct cros_ec_dev *ec_dev = charger->ec_dev; + struct cros_ec_command *msg; + int ret; + + msg = kzalloc(sizeof(*msg) + max(outsize, insize), GFP_KERNEL); + if (!msg) + return -ENOMEM; + + msg->version = version; + msg->command = ec_dev->cmd_offset + command; + msg->outsize = outsize; + msg->insize = insize; + + if (outsize) + memcpy(msg->data, outdata, outsize); + + ret = cros_ec_cmd_xfer_status(charger->ec_device, msg); + if (ret >= 0 && insize) + memcpy(indata, msg->data, insize); + + kfree(msg); + return ret; +} + +static int cros_usbpd_charger_get_num_ports(struct charger_data *charger) +{ + struct ec_response_usb_pd_ports resp; + int ret; + + ret = cros_usbpd_charger_ec_command(charger, 0, EC_CMD_USB_PD_PORTS, + NULL, 0, &resp, sizeof(resp)); + if (ret < 0) { + dev_err(charger->dev, + "Unable to get the number or ports (err:0x%x)\n", ret); + return ret; + } + + return resp.num_ports; +} + +static int cros_usbpd_charger_get_discovery_info(struct port_data *port) +{ + struct charger_data *charger = port->charger; + struct ec_params_usb_pd_discovery_entry resp; + struct ec_params_usb_pd_info_request req; + int ret; + + req.port = port->port_number; + + ret = cros_usbpd_charger_ec_command(charger, 0, + EC_CMD_USB_PD_DISCOVERY, + &req, sizeof(req), + &resp, sizeof(resp)); + if (ret < 0) { + dev_err(charger->dev, + "Unable to query discovery info (err:0x%x)\n", ret); + return ret; + } + + dev_dbg(charger->dev, "Port %d: VID = 0x%x, PID=0x%x, PTYPE=0x%x\n", + port->port_number, resp.vid, resp.pid, resp.ptype); + + snprintf(port->manufacturer, sizeof(port->manufacturer), "%x", + resp.vid); + snprintf(port->model_name, sizeof(port->model_name), "%x", resp.pid); + + return 0; +} + +static int cros_usbpd_charger_get_power_info(struct port_data *port) +{ + struct charger_data *charger = port->charger; + struct ec_response_usb_pd_power_info resp; + struct ec_params_usb_pd_power_info req; + int last_psy_status, last_psy_usb_type; + struct device *dev = charger->dev; + int ret; + + req.port = port->port_number; + ret = cros_usbpd_charger_ec_command(charger, 0, + EC_CMD_USB_PD_POWER_INFO, + &req, sizeof(req), + &resp, sizeof(resp)); + if (ret < 0) { + dev_err(dev, "Unable to query PD power info (err:0x%x)\n", ret); + return ret; + } + + last_psy_status = port->psy_status; + last_psy_usb_type = port->psy_usb_type; + + switch (resp.role) { + case USB_PD_PORT_POWER_DISCONNECTED: + port->psy_status = POWER_SUPPLY_STATUS_NOT_CHARGING; + port->psy_online = 0; + break; + case USB_PD_PORT_POWER_SOURCE: + port->psy_status = POWER_SUPPLY_STATUS_NOT_CHARGING; + port->psy_online = 0; + break; + case USB_PD_PORT_POWER_SINK: + port->psy_status = POWER_SUPPLY_STATUS_CHARGING; + port->psy_online = 1; + break; + case USB_PD_PORT_POWER_SINK_NOT_CHARGING: + port->psy_status = POWER_SUPPLY_STATUS_NOT_CHARGING; + port->psy_online = 1; + break; + default: + dev_err(dev, "Unknown role %d\n", resp.role); + break; + } + + port->psy_voltage_max_design = resp.meas.voltage_max; + port->psy_voltage_now = resp.meas.voltage_now; + port->psy_current_max = resp.meas.current_max; + port->psy_power_max = resp.max_power; + + switch (resp.type) { + case USB_CHG_TYPE_BC12_SDP: + case USB_CHG_TYPE_VBUS: + port->psy_usb_type = POWER_SUPPLY_USB_TYPE_SDP; + break; + case USB_CHG_TYPE_NONE: + /* + * For dual-role devices when we are a source, the firmware + * reports the type as NONE. Report such chargers as type + * USB_PD_DRP. + */ + if (resp.role == USB_PD_PORT_POWER_SOURCE && resp.dualrole) + port->psy_usb_type = POWER_SUPPLY_USB_TYPE_PD_DRP; + else + port->psy_usb_type = POWER_SUPPLY_USB_TYPE_SDP; + break; + case USB_CHG_TYPE_OTHER: + case USB_CHG_TYPE_PROPRIETARY: + port->psy_usb_type = POWER_SUPPLY_USB_TYPE_APPLE_BRICK_ID; + break; + case USB_CHG_TYPE_C: + port->psy_usb_type = POWER_SUPPLY_USB_TYPE_C; + break; + case USB_CHG_TYPE_BC12_DCP: + port->psy_usb_type = POWER_SUPPLY_USB_TYPE_DCP; + break; + case USB_CHG_TYPE_BC12_CDP: + port->psy_usb_type = POWER_SUPPLY_USB_TYPE_CDP; + break; + case USB_CHG_TYPE_PD: + if (resp.dualrole) + port->psy_usb_type = POWER_SUPPLY_USB_TYPE_PD_DRP; + else + port->psy_usb_type = POWER_SUPPLY_USB_TYPE_PD; + break; + case USB_CHG_TYPE_UNKNOWN: + /* + * While the EC is trying to determine the type of charger that + * has been plugged in, it will report the charger type as + * unknown. Additionally since the power capabilities are + * unknown, report the max current and voltage as zero. + */ + port->psy_usb_type = POWER_SUPPLY_USB_TYPE_UNKNOWN; + port->psy_voltage_max_design = 0; + port->psy_current_max = 0; + break; + default: + dev_err(dev, "Port %d: default case!\n", port->port_number); + port->psy_usb_type = POWER_SUPPLY_USB_TYPE_SDP; + } + + port->psy_desc.type = POWER_SUPPLY_TYPE_USB; + + dev_dbg(dev, + "Port %d: type=%d vmax=%d vnow=%d cmax=%d clim=%d pmax=%d\n", + port->port_number, resp.type, resp.meas.voltage_max, + resp.meas.voltage_now, resp.meas.current_max, + resp.meas.current_lim, resp.max_power); + + /* + * If power supply type or status changed, explicitly call + * power_supply_changed. This results in udev event getting generated + * and allows user mode apps to react quicker instead of waiting for + * their next poll of power supply status. + */ + if (last_psy_usb_type != port->psy_usb_type || + last_psy_status != port->psy_status) + power_supply_changed(port->psy); + + return 0; +} + +static int cros_usbpd_charger_get_port_status(struct port_data *port, + bool ratelimit) +{ + int ret; + + if (ratelimit && + time_is_after_jiffies(port->last_update + + CHARGER_CACHE_UPDATE_DELAY)) + return 0; + + ret = cros_usbpd_charger_get_power_info(port); + if (ret < 0) + return ret; + + ret = cros_usbpd_charger_get_discovery_info(port); + port->last_update = jiffies; + + return ret; +} + +static void cros_usbpd_charger_power_changed(struct power_supply *psy) +{ + struct port_data *port = power_supply_get_drvdata(psy); + struct charger_data *charger = port->charger; + int i; + + for (i = 0; i < charger->num_registered_psy; i++) + cros_usbpd_charger_get_port_status(charger->ports[i], false); +} + +static int cros_usbpd_charger_get_prop(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct port_data *port = power_supply_get_drvdata(psy); + struct charger_data *charger = port->charger; + struct cros_ec_device *ec_device = charger->ec_device; + struct device *dev = charger->dev; + int ret; + + /* Only refresh ec_port_status for dynamic properties */ + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + /* + * If mkbp_event_supported, then we can be assured that + * the driver's state for the online property is consistent + * with the hardware. However, if we aren't event driven, + * the optimization before to skip an ec_port_status get + * and only returned cached values of the online property will + * cause a delay in detecting a cable attach until one of the + * other properties are read. + * + * Allow an ec_port_status refresh for online property check + * if we're not already online to check for plug events if + * not mkbp_event_supported. + */ + if (ec_device->mkbp_event_supported || port->psy_online) + break; + case POWER_SUPPLY_PROP_CURRENT_MAX: + case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + ret = cros_usbpd_charger_get_port_status(port, true); + if (ret < 0) { + dev_err(dev, "Failed to get port status (err:0x%x)\n", + ret); + return -EINVAL; + } + break; + default: + break; + } + + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + val->intval = port->psy_online; + break; + case POWER_SUPPLY_PROP_STATUS: + val->intval = port->psy_status; + break; + case POWER_SUPPLY_PROP_CURRENT_MAX: + val->intval = port->psy_current_max * 1000; + break; + case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: + val->intval = port->psy_voltage_max_design * 1000; + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + val->intval = port->psy_voltage_now * 1000; + break; + case POWER_SUPPLY_PROP_USB_TYPE: + val->intval = port->psy_usb_type; + break; + case POWER_SUPPLY_PROP_MODEL_NAME: + val->strval = port->model_name; + break; + case POWER_SUPPLY_PROP_MANUFACTURER: + val->strval = port->manufacturer; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int cros_usbpd_charger_ec_event(struct notifier_block *nb, + unsigned long queued_during_suspend, + void *_notify) +{ + struct cros_ec_device *ec_device; + struct charger_data *charger; + struct device *dev; + u32 host_event; + + charger = container_of(nb, struct charger_data, notifier); + ec_device = charger->ec_device; + dev = charger->dev; + + host_event = cros_ec_get_host_event(ec_device); + if (host_event & EC_HOST_EVENT_MASK(EC_HOST_EVENT_PD_MCU)) { + cros_usbpd_charger_power_changed(charger->ports[0]->psy); + return NOTIFY_OK; + } else { + return NOTIFY_DONE; + } +} + +static void cros_usbpd_charger_unregister_notifier(void *data) +{ + struct charger_data *charger = data; + struct cros_ec_device *ec_device = charger->ec_device; + + blocking_notifier_chain_unregister(&ec_device->event_notifier, + &charger->notifier); +} + +static int cros_usbpd_charger_probe(struct platform_device *pd) +{ + struct cros_ec_dev *ec_dev = dev_get_drvdata(pd->dev.parent); + struct cros_ec_device *ec_device = ec_dev->ec_dev; + struct power_supply_desc *psy_desc; + struct device *dev = &pd->dev; + struct charger_data *charger; + struct power_supply *psy; + struct port_data *port; + int ret = -EINVAL; + int i; + + charger = devm_kzalloc(dev, sizeof(struct charger_data), + GFP_KERNEL); + if (!charger) + return -ENOMEM; + + charger->dev = dev; + charger->ec_dev = ec_dev; + charger->ec_device = ec_device; + + platform_set_drvdata(pd, charger); + + charger->num_charger_ports = cros_usbpd_charger_get_num_ports(charger); + if (charger->num_charger_ports <= 0) { + /* + * This can happen on a system that doesn't support USB PD. + * Log a message, but no need to warn. + */ + dev_info(dev, "No charging ports found\n"); + ret = -ENODEV; + goto fail_nowarn; + } + + for (i = 0; i < charger->num_charger_ports; i++) { + struct power_supply_config psy_cfg = {}; + + port = devm_kzalloc(dev, sizeof(struct port_data), GFP_KERNEL); + if (!port) { + ret = -ENOMEM; + goto fail; + } + + port->charger = charger; + port->port_number = i; + sprintf(port->name, CHARGER_DIR_NAME, i); + + psy_desc = &port->psy_desc; + psy_desc->name = port->name; + psy_desc->type = POWER_SUPPLY_TYPE_USB; + psy_desc->get_property = cros_usbpd_charger_get_prop; + psy_desc->external_power_changed = + cros_usbpd_charger_power_changed; + psy_desc->properties = cros_usbpd_charger_props; + psy_desc->num_properties = + ARRAY_SIZE(cros_usbpd_charger_props); + psy_desc->usb_types = cros_usbpd_charger_usb_types; + psy_desc->num_usb_types = + ARRAY_SIZE(cros_usbpd_charger_usb_types); + psy_cfg.drv_data = port; + + psy = devm_power_supply_register_no_ws(dev, psy_desc, + &psy_cfg); + if (IS_ERR(psy)) { + dev_err(dev, "Failed to register power supply\n"); + continue; + } + port->psy = psy; + + charger->ports[charger->num_registered_psy++] = port; + } + + if (!charger->num_registered_psy) { + ret = -ENODEV; + dev_err(dev, "No power supplies registered\n"); + goto fail; + } + + if (ec_device->mkbp_event_supported) { + /* Get PD events from the EC */ + charger->notifier.notifier_call = cros_usbpd_charger_ec_event; + ret = blocking_notifier_chain_register( + &ec_device->event_notifier, + &charger->notifier); + if (ret < 0) { + dev_warn(dev, "failed to register notifier\n"); + } else { + ret = devm_add_action_or_reset(dev, + cros_usbpd_charger_unregister_notifier, + charger); + if (ret < 0) + goto fail; + } + } + + return 0; + +fail: + WARN(1, "%s: Failing probe (err:0x%x)\n", dev_name(dev), ret); + +fail_nowarn: + dev_info(dev, "Failing probe (err:0x%x)\n", ret); + return ret; +} + +#ifdef CONFIG_PM_SLEEP +static int cros_usbpd_charger_resume(struct device *dev) +{ + struct charger_data *charger = dev_get_drvdata(dev); + int i; + + if (!charger) + return 0; + + for (i = 0; i < charger->num_registered_psy; i++) { + power_supply_changed(charger->ports[i]->psy); + charger->ports[i]->last_update = + jiffies - CHARGER_CACHE_UPDATE_DELAY; + } + + return 0; +} +#endif + +static SIMPLE_DEV_PM_OPS(cros_usbpd_charger_pm_ops, NULL, + cros_usbpd_charger_resume); + +static struct platform_driver cros_usbpd_charger_driver = { + .driver = { + .name = DRV_NAME, + .pm = &cros_usbpd_charger_pm_ops, + }, + .probe = cros_usbpd_charger_probe +}; + +module_platform_driver(cros_usbpd_charger_driver); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("ChromeOS EC USBPD charger"); +MODULE_ALIAS("platform:" DRV_NAME); diff --git a/drivers/power/supply/max1721x_battery.c b/drivers/power/supply/max1721x_battery.c index 9ee601a03d9b..9ca895b0dabb 100644 --- a/drivers/power/supply/max1721x_battery.c +++ b/drivers/power/supply/max1721x_battery.c @@ -372,7 +372,7 @@ static int devm_w1_max1721x_add_device(struct w1_slave *sl) } if (!info->rsense) { - dev_warn(info->w1_dev, "RSenese not calibrated, set 10 mOhms!\n"); + dev_warn(info->w1_dev, "RSense not calibrated, set 10 mOhms!\n"); info->rsense = 1000; /* in regs in 10^-5 */ } dev_info(info->w1_dev, "RSense: %d mOhms.\n", info->rsense / 100); diff --git a/drivers/power/supply/sbs-battery.c b/drivers/power/supply/sbs-battery.c index 83d7b4115857..8ba6abf584de 100644 --- a/drivers/power/supply/sbs-battery.c +++ b/drivers/power/supply/sbs-battery.c @@ -23,6 +23,7 @@ #include <linux/kernel.h> #include <linux/module.h> #include <linux/of.h> +#include <linux/of_device.h> #include <linux/power/sbs-battery.h> #include <linux/power_supply.h> #include <linux/slab.h> @@ -156,6 +157,9 @@ static enum power_supply_property sbs_properties[] = { POWER_SUPPLY_PROP_MODEL_NAME }; +/* Supports special manufacturer commands from TI BQ20Z75 IC. */ +#define SBS_FLAGS_TI_BQ20Z75 BIT(0) + struct sbs_info { struct i2c_client *client; struct power_supply *power_supply; @@ -168,6 +172,7 @@ struct sbs_info { u32 poll_retry_count; struct delayed_work work; struct mutex mode_lock; + u32 flags; }; static char model_name[I2C_SMBUS_BLOCK_MAX + 1]; @@ -316,16 +321,40 @@ static int sbs_get_battery_presence_and_health( struct i2c_client *client, enum power_supply_property psp, union power_supply_propval *val) { + int ret; + + if (psp == POWER_SUPPLY_PROP_PRESENT) { + /* Dummy command; if it succeeds, battery is present. */ + ret = sbs_read_word_data(client, sbs_data[REG_STATUS].addr); + if (ret < 0) + val->intval = 0; /* battery disconnected */ + else + val->intval = 1; /* battery present */ + } else { /* POWER_SUPPLY_PROP_HEALTH */ + /* SBS spec doesn't have a general health command. */ + val->intval = POWER_SUPPLY_HEALTH_UNKNOWN; + } + + return 0; +} + +static int sbs_get_ti_battery_presence_and_health( + struct i2c_client *client, enum power_supply_property psp, + union power_supply_propval *val) +{ s32 ret; /* * Write to ManufacturerAccess with ManufacturerAccess command - * and then read the status. Do not check for error on the write - * since not all batteries implement write access to this command, - * while others mandate it. + * and then read the status. */ - sbs_write_word_data(client, sbs_data[REG_MANUFACTURER_DATA].addr, - MANUFACTURER_ACCESS_STATUS); + ret = sbs_write_word_data(client, sbs_data[REG_MANUFACTURER_DATA].addr, + MANUFACTURER_ACCESS_STATUS); + if (ret < 0) { + if (psp == POWER_SUPPLY_PROP_PRESENT) + val->intval = 0; /* battery removed */ + return ret; + } ret = sbs_read_word_data(client, sbs_data[REG_MANUFACTURER_DATA].addr); if (ret < 0) { @@ -600,7 +629,12 @@ static int sbs_get_property(struct power_supply *psy, switch (psp) { case POWER_SUPPLY_PROP_PRESENT: case POWER_SUPPLY_PROP_HEALTH: - ret = sbs_get_battery_presence_and_health(client, psp, val); + if (client->flags & SBS_FLAGS_TI_BQ20Z75) + ret = sbs_get_ti_battery_presence_and_health(client, + psp, val); + else + ret = sbs_get_battery_presence_and_health(client, psp, + val); if (psp == POWER_SUPPLY_PROP_PRESENT) return 0; break; @@ -806,6 +840,7 @@ static int sbs_probe(struct i2c_client *client, if (!chip) return -ENOMEM; + chip->flags = (u32)(uintptr_t)of_device_get_match_data(&client->dev); chip->client = client; chip->enable_detection = false; psy_cfg.of_node = client->dev.of_node; @@ -911,16 +946,19 @@ static int sbs_suspend(struct device *dev) { struct i2c_client *client = to_i2c_client(dev); struct sbs_info *chip = i2c_get_clientdata(client); + int ret; if (chip->poll_time > 0) cancel_delayed_work_sync(&chip->work); - /* - * Write to manufacturer access with sleep command. - * Support is manufacturer dependend, so ignore errors. - */ - sbs_write_word_data(client, sbs_data[REG_MANUFACTURER_DATA].addr, - MANUFACTURER_ACCESS_SLEEP); + if (chip->flags & SBS_FLAGS_TI_BQ20Z75) { + /* Write to manufacturer access with sleep command. */ + ret = sbs_write_word_data(client, + sbs_data[REG_MANUFACTURER_DATA].addr, + MANUFACTURER_ACCESS_SLEEP); + if (chip->is_present && ret < 0) + return ret; + } return 0; } @@ -941,7 +979,10 @@ MODULE_DEVICE_TABLE(i2c, sbs_id); static const struct of_device_id sbs_dt_ids[] = { { .compatible = "sbs,sbs-battery" }, - { .compatible = "ti,bq20z75" }, + { + .compatible = "ti,bq20z75", + .data = (void *)SBS_FLAGS_TI_BQ20Z75, + }, { } }; MODULE_DEVICE_TABLE(of, sbs_dt_ids); diff --git a/drivers/power/supply/tps65217_charger.c b/drivers/power/supply/tps65217_charger.c index 1f5234098aaf..814c2b81fdfe 100644 --- a/drivers/power/supply/tps65217_charger.c +++ b/drivers/power/supply/tps65217_charger.c @@ -1,20 +1,8 @@ -/* - * Battery charger driver for TI's tps65217 - * - * Copyright (c) 2015, Collabora Ltd. - - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU General Public License, - * version 2, as published by the Free Software Foundation. - - * This program is distributed in the hope it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ +// SPDX-License-Identifier: GPL-2.0 +// Battery charger driver for TI's tps65217 +// +// Copyright (C) 2015 Collabora Ltd. +// Author: Enric Balletbo i Serra <enric.balletbo@collabora.com> /* * Battery charger driver for TI's tps65217 |