diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2019-05-15 18:50:40 -0700 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2019-05-15 18:50:40 -0700 |
commit | 8649efb2f8750dcabff018a27784bab4ecb9f88f (patch) | |
tree | 4351aa7377c2042be5963d0f8b5600a1eb221a59 /drivers/power | |
parent | 5fd09ba68297c967f5ba6bea9c3b444d34f80ee5 (diff) | |
parent | baf5964ecfe19a0109fe2e497e72840ce0f488e6 (diff) | |
download | linux-8649efb2f8750dcabff018a27784bab4ecb9f88f.tar.gz linux-8649efb2f8750dcabff018a27784bab4ecb9f88f.tar.bz2 linux-8649efb2f8750dcabff018a27784bab4ecb9f88f.zip |
Merge tag 'for-v5.2' of git://git.kernel.org/pub/scm/linux/kernel/git/sre/linux-power-supply
Pull power supply and reset updates from Sebastian Reichel:
"Core:
- Add over-current health state
- Add standard, adaptive and custom charge types
- Add new properties for start/end charge threshold
New Drivers / Hardware:
- UCS1002 Programmable USB Port Power Controller
- Ingenic JZ47xx Battery Fuel Gauge
- AXP20x USB Power: Add AXP813 support
- AT91 poweroff: Add SAM9X60 support
- OLPC battery: Add XO-1.5 and XO-1.75 support
Misc Changes:
- syscon-reboot: support mask property
- AXP288 fuel gauge: Blacklist ACEPC T8/T11. Looks like some vendor
thought it's a good idea to build a desktop system with a fuel
gauge, that slowly "discharges"...
- cpcap-battery: Fix calculation errors
- misc fixes"
* tag 'for-v5.2' of git://git.kernel.org/pub/scm/linux/kernel/git/sre/linux-power-supply: (54 commits)
power: supply: olpc_battery: force the le/be casts
power: supply: ucs1002: Fix build error without CONFIG_REGULATOR
power: supply: ucs1002: Fix wrong return value checking
power: supply: Add driver for Microchip UCS1002
dt-bindings: power: supply: Add bindings for Microchip UCS1002
power: supply: core: Add POWER_SUPPLY_HEALTH_OVERCURRENT constant
power: supply: core: fix clang -Wunsequenced
power: supply: core: Add missing documentation for CHARGE_CONTROL_* properties
power: supply: core: Add CHARGE_CONTROL_{START_THRESHOLD,END_THRESHOLD} properties
power: supply: core: Add Standard, Adaptive, and Custom charge types
power: supply: axp288_fuel_gauge: Add ACEPC T8 and T11 mini PCs to the blacklist
power: supply: bq27xxx_battery: Notify also about status changes
power: supply: olpc_battery: Have the framework register sysfs files for us
power: supply: olpc_battery: Add OLPC XO 1.75 support
power: supply: olpc_battery: Avoid using platform_info
power: supply: olpc_battery: Use devm_power_supply_register()
power: supply: olpc_battery: Move priv data to a struct
power: supply: olpc_battery: Use DT to get battery version
x86/platform/olpc: Use a correct version when making up a battery node
x86/platform/olpc: Trivial code move in DT fixup
...
Diffstat (limited to 'drivers/power')
20 files changed, 1367 insertions, 221 deletions
diff --git a/drivers/power/reset/at91-sama5d2_shdwc.c b/drivers/power/reset/at91-sama5d2_shdwc.c index 2b686c55b717..e341cc5c0ea6 100644 --- a/drivers/power/reset/at91-sama5d2_shdwc.c +++ b/drivers/power/reset/at91-sama5d2_shdwc.c @@ -57,15 +57,21 @@ #define SHDW_WK_PIN(reg, cfg) ((reg) & AT91_SHDW_WKUPIS((cfg)->wkup_pin_input)) #define SHDW_RTCWK(reg, cfg) (((reg) >> ((cfg)->sr_rtcwk_shift)) & 0x1) +#define SHDW_RTTWK(reg, cfg) (((reg) >> ((cfg)->sr_rttwk_shift)) & 0x1) #define SHDW_RTCWKEN(cfg) (1 << ((cfg)->mr_rtcwk_shift)) +#define SHDW_RTTWKEN(cfg) (1 << ((cfg)->mr_rttwk_shift)) #define DBC_PERIOD_US(x) DIV_ROUND_UP_ULL((1000000 * (x)), \ SLOW_CLOCK_FREQ) +#define SHDW_CFG_NOT_USED (32) + struct shdwc_config { u8 wkup_pin_input; u8 mr_rtcwk_shift; + u8 mr_rttwk_shift; u8 sr_rtcwk_shift; + u8 sr_rttwk_shift; }; struct shdwc { @@ -104,6 +110,8 @@ static void __init at91_wakeup_status(struct platform_device *pdev) reason = "WKUP pin"; else if (SHDW_RTCWK(reg, shdw->cfg)) reason = "RTC"; + else if (SHDW_RTTWK(reg, shdw->cfg)) + reason = "RTT"; pr_info("AT91: Wake-Up source: %s\n", reason); } @@ -221,6 +229,9 @@ static void at91_shdwc_dt_configure(struct platform_device *pdev) if (of_property_read_bool(np, "atmel,wakeup-rtc-timer")) mode |= SHDW_RTCWKEN(shdw->cfg); + if (of_property_read_bool(np, "atmel,wakeup-rtt-timer")) + mode |= SHDW_RTTWKEN(shdw->cfg); + dev_dbg(&pdev->dev, "%s: mode = %#x\n", __func__, mode); writel(mode, shdw->shdwc_base + AT91_SHDW_MR); @@ -231,13 +242,27 @@ static void at91_shdwc_dt_configure(struct platform_device *pdev) static const struct shdwc_config sama5d2_shdwc_config = { .wkup_pin_input = 0, .mr_rtcwk_shift = 17, + .mr_rttwk_shift = SHDW_CFG_NOT_USED, .sr_rtcwk_shift = 5, + .sr_rttwk_shift = SHDW_CFG_NOT_USED, +}; + +static const struct shdwc_config sam9x60_shdwc_config = { + .wkup_pin_input = 0, + .mr_rtcwk_shift = 17, + .mr_rttwk_shift = 16, + .sr_rtcwk_shift = 5, + .sr_rttwk_shift = 4, }; static const struct of_device_id at91_shdwc_of_match[] = { { .compatible = "atmel,sama5d2-shdwc", .data = &sama5d2_shdwc_config, + }, + { + .compatible = "microchip,sam9x60-shdwc", + .data = &sam9x60_shdwc_config, }, { /*sentinel*/ } diff --git a/drivers/power/reset/syscon-reboot.c b/drivers/power/reset/syscon-reboot.c index 7d0d269a0837..5a6bb638c331 100644 --- a/drivers/power/reset/syscon-reboot.c +++ b/drivers/power/reset/syscon-reboot.c @@ -27,6 +27,7 @@ struct syscon_reboot_context { struct regmap *map; u32 offset; + u32 value; u32 mask; struct notifier_block restart_handler; }; @@ -39,7 +40,7 @@ static int syscon_restart_handle(struct notifier_block *this, restart_handler); /* Issue the reboot */ - regmap_write(ctx->map, ctx->offset, ctx->mask); + regmap_update_bits(ctx->map, ctx->offset, ctx->mask, ctx->value); mdelay(1000); @@ -51,6 +52,7 @@ static int syscon_reboot_probe(struct platform_device *pdev) { struct syscon_reboot_context *ctx; struct device *dev = &pdev->dev; + int mask_err, value_err; int err; ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx), GFP_KERNEL); @@ -64,8 +66,21 @@ static int syscon_reboot_probe(struct platform_device *pdev) if (of_property_read_u32(pdev->dev.of_node, "offset", &ctx->offset)) return -EINVAL; - if (of_property_read_u32(pdev->dev.of_node, "mask", &ctx->mask)) + value_err = of_property_read_u32(pdev->dev.of_node, "value", &ctx->value); + mask_err = of_property_read_u32(pdev->dev.of_node, "mask", &ctx->mask); + if (value_err && mask_err) { + dev_err(dev, "unable to read 'value' and 'mask'"); return -EINVAL; + } + + if (value_err) { + /* support old binding */ + ctx->value = ctx->mask; + ctx->mask = 0xFFFFFFFF; + } else if (mask_err) { + /* support value without mask*/ + ctx->mask = 0xFFFFFFFF; + } ctx->restart_handler.notifier_call = syscon_restart_handle; ctx->restart_handler.priority = 192; diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig index 0230c96fa94d..26dacdab03cc 100644 --- a/drivers/power/supply/Kconfig +++ b/drivers/power/supply/Kconfig @@ -169,6 +169,17 @@ config BATTERY_COLLIE Say Y to enable support for the battery on the Sharp Zaurus SL-5500 (collie) models. +config BATTERY_INGENIC + tristate "Ingenic JZ47xx SoCs battery driver" + depends on MIPS || COMPILE_TEST + depends on INGENIC_ADC + help + Choose this option if you want to monitor battery status on + Ingenic JZ47xx SoC based devices. + + This driver can also be built as a module. If so, the module will be + called ingenic-battery. + config BATTERY_IPAQ_MICRO tristate "iPAQ Atmel Micro ASIC battery driver" depends on MFD_IPAQ_MICRO @@ -475,12 +486,12 @@ config CHARGER_MANAGER runtime and in suspend-to-RAM by waking up the system periodically with help of suspend_again support. -config CHARGER_LTC3651 - tristate "LTC3651 charger" +config CHARGER_LT3651 + tristate "Analog Devices LT3651 charger" depends on GPIOLIB help - Say Y to include support for the LTC3651 battery charger which reports - its status via GPIO lines. + Say Y to include support for the Analog Devices (Linear Technology) + LT3651 battery charger which reports its status via GPIO lines. config CHARGER_MAX14577 tristate "Maxim MAX14577/77836 battery charger driver" @@ -667,4 +678,14 @@ config FUEL_GAUGE_SC27XX Say Y here to enable support for fuel gauge with SC27XX PMIC chips. +config CHARGER_UCS1002 + tristate "Microchip UCS1002 USB Port Power Controller" + depends on I2C + depends on OF + depends on REGULATOR + select REGMAP_I2C + help + Say Y to enable support for Microchip UCS1002 Programmable + USB Port Power Controller with Charger Emulation. + endif # POWER_SUPPLY diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile index b73eb8c5c1a9..f208273f9686 100644 --- a/drivers/power/supply/Makefile +++ b/drivers/power/supply/Makefile @@ -34,6 +34,7 @@ obj-$(CONFIG_BATTERY_PMU) += pmu_battery.o obj-$(CONFIG_BATTERY_OLPC) += olpc_battery.o obj-$(CONFIG_BATTERY_TOSA) += tosa_battery.o obj-$(CONFIG_BATTERY_COLLIE) += collie_battery.o +obj-$(CONFIG_BATTERY_INGENIC) += ingenic-battery.o obj-$(CONFIG_BATTERY_IPAQ_MICRO) += ipaq_micro_battery.o obj-$(CONFIG_BATTERY_WM97XX) += wm97xx_battery.o obj-$(CONFIG_BATTERY_SBS) += sbs-battery.o @@ -67,7 +68,7 @@ obj-$(CONFIG_CHARGER_LP8727) += lp8727_charger.o obj-$(CONFIG_CHARGER_LP8788) += lp8788-charger.o obj-$(CONFIG_CHARGER_GPIO) += gpio-charger.o obj-$(CONFIG_CHARGER_MANAGER) += charger-manager.o -obj-$(CONFIG_CHARGER_LTC3651) += ltc3651-charger.o +obj-$(CONFIG_CHARGER_LT3651) += lt3651-charger.o obj-$(CONFIG_CHARGER_MAX14577) += max14577_charger.o obj-$(CONFIG_CHARGER_DETECTOR_MAX14656) += max14656_charger_detector.o obj-$(CONFIG_CHARGER_MAX77650) += max77650-charger.o @@ -88,3 +89,4 @@ obj-$(CONFIG_AXP288_CHARGER) += axp288_charger.o obj-$(CONFIG_CHARGER_CROS_USBPD) += cros_usbpd-charger.o obj-$(CONFIG_CHARGER_SC2731) += sc2731_charger.o obj-$(CONFIG_FUEL_GAUGE_SC27XX) += sc27xx_fuel_gauge.o +obj-$(CONFIG_CHARGER_UCS1002) += ucs1002_power.o diff --git a/drivers/power/supply/ab8500_bmdata.c b/drivers/power/supply/ab8500_bmdata.c index 7b2b69916f48..f6a66979cbb5 100644 --- a/drivers/power/supply/ab8500_bmdata.c +++ b/drivers/power/supply/ab8500_bmdata.c @@ -508,6 +508,7 @@ int ab8500_bm_of_probe(struct device *dev, btech = of_get_property(battery_node, "stericsson,battery-type", NULL); if (!btech) { dev_warn(dev, "missing property battery-name/type\n"); + of_node_put(battery_node); return -EINVAL; } diff --git a/drivers/power/supply/axp20x_usb_power.c b/drivers/power/supply/axp20x_usb_power.c index f52fe77edb6f..d2b1255ee1cc 100644 --- a/drivers/power/supply/axp20x_usb_power.c +++ b/drivers/power/supply/axp20x_usb_power.c @@ -24,6 +24,7 @@ #include <linux/regmap.h> #include <linux/slab.h> #include <linux/iio/consumer.h> +#include <linux/workqueue.h> #define DRVNAME "axp20x-usb-power-supply" @@ -36,16 +37,27 @@ #define AXP20X_VBUS_VHOLD_MASK GENMASK(5, 3) #define AXP20X_VBUS_VHOLD_OFFSET 3 #define AXP20X_VBUS_CLIMIT_MASK 3 -#define AXP20X_VBUC_CLIMIT_900mA 0 -#define AXP20X_VBUC_CLIMIT_500mA 1 -#define AXP20X_VBUC_CLIMIT_100mA 2 -#define AXP20X_VBUC_CLIMIT_NONE 3 +#define AXP20X_VBUS_CLIMIT_900mA 0 +#define AXP20X_VBUS_CLIMIT_500mA 1 +#define AXP20X_VBUS_CLIMIT_100mA 2 +#define AXP20X_VBUS_CLIMIT_NONE 3 + +#define AXP813_VBUS_CLIMIT_900mA 0 +#define AXP813_VBUS_CLIMIT_1500mA 1 +#define AXP813_VBUS_CLIMIT_2000mA 2 +#define AXP813_VBUS_CLIMIT_2500mA 3 #define AXP20X_ADC_EN1_VBUS_CURR BIT(2) #define AXP20X_ADC_EN1_VBUS_VOLT BIT(3) #define AXP20X_VBUS_MON_VBUS_VALID BIT(3) +/* + * Note do not raise the debounce time, we must report Vusb high within + * 100ms otherwise we get Vbus errors in musb. + */ +#define DEBOUNCE_TIME msecs_to_jiffies(50) + struct axp20x_usb_power { struct device_node *np; struct regmap *regmap; @@ -53,6 +65,8 @@ struct axp20x_usb_power { enum axp20x_variants axp20x_id; struct iio_channel *vbus_v; struct iio_channel *vbus_i; + struct delayed_work vbus_detect; + unsigned int old_status; }; static irqreturn_t axp20x_usb_power_irq(int irq, void *devid) @@ -64,6 +78,89 @@ static irqreturn_t axp20x_usb_power_irq(int irq, void *devid) return IRQ_HANDLED; } +static void axp20x_usb_power_poll_vbus(struct work_struct *work) +{ + struct axp20x_usb_power *power = + container_of(work, struct axp20x_usb_power, vbus_detect.work); + unsigned int val; + int ret; + + ret = regmap_read(power->regmap, AXP20X_PWR_INPUT_STATUS, &val); + if (ret) + goto out; + + val &= (AXP20X_PWR_STATUS_VBUS_PRESENT | AXP20X_PWR_STATUS_VBUS_USED); + if (val != power->old_status) + power_supply_changed(power->supply); + + power->old_status = val; + +out: + mod_delayed_work(system_wq, &power->vbus_detect, DEBOUNCE_TIME); +} + +static bool axp20x_usb_vbus_needs_polling(struct axp20x_usb_power *power) +{ + if (power->axp20x_id >= AXP221_ID) + return true; + + return false; +} + +static int axp20x_get_current_max(struct axp20x_usb_power *power, int *val) +{ + unsigned int v; + int ret = regmap_read(power->regmap, AXP20X_VBUS_IPSOUT_MGMT, &v); + + if (ret) + return ret; + + switch (v & AXP20X_VBUS_CLIMIT_MASK) { + case AXP20X_VBUS_CLIMIT_100mA: + if (power->axp20x_id == AXP221_ID) + *val = -1; /* No 100mA limit */ + else + *val = 100000; + break; + case AXP20X_VBUS_CLIMIT_500mA: + *val = 500000; + break; + case AXP20X_VBUS_CLIMIT_900mA: + *val = 900000; + break; + case AXP20X_VBUS_CLIMIT_NONE: + *val = -1; + break; + } + + return 0; +} + +static int axp813_get_current_max(struct axp20x_usb_power *power, int *val) +{ + unsigned int v; + int ret = regmap_read(power->regmap, AXP20X_VBUS_IPSOUT_MGMT, &v); + + if (ret) + return ret; + + switch (v & AXP20X_VBUS_CLIMIT_MASK) { + case AXP813_VBUS_CLIMIT_900mA: + *val = 900000; + break; + case AXP813_VBUS_CLIMIT_1500mA: + *val = 1500000; + break; + case AXP813_VBUS_CLIMIT_2000mA: + *val = 2000000; + break; + case AXP813_VBUS_CLIMIT_2500mA: + *val = 2500000; + break; + } + return 0; +} + static int axp20x_usb_power_get_property(struct power_supply *psy, enum power_supply_property psp, union power_supply_propval *val) { @@ -102,28 +199,9 @@ static int axp20x_usb_power_get_property(struct power_supply *psy, val->intval = ret * 1700; /* 1 step = 1.7 mV */ return 0; case POWER_SUPPLY_PROP_CURRENT_MAX: - ret = regmap_read(power->regmap, AXP20X_VBUS_IPSOUT_MGMT, &v); - if (ret) - return ret; - - switch (v & AXP20X_VBUS_CLIMIT_MASK) { - case AXP20X_VBUC_CLIMIT_100mA: - if (power->axp20x_id == AXP221_ID) - val->intval = -1; /* No 100mA limit */ - else - val->intval = 100000; - break; - case AXP20X_VBUC_CLIMIT_500mA: - val->intval = 500000; - break; - case AXP20X_VBUC_CLIMIT_900mA: - val->intval = 900000; - break; - case AXP20X_VBUC_CLIMIT_NONE: - val->intval = -1; - break; - } - return 0; + if (power->axp20x_id == AXP813_ID) + return axp813_get_current_max(power, &val->intval); + return axp20x_get_current_max(power, &val->intval); case POWER_SUPPLY_PROP_CURRENT_NOW: if (IS_ENABLED(CONFIG_AXP20X_ADC)) { ret = iio_read_channel_processed(power->vbus_i, @@ -214,6 +292,31 @@ static int axp20x_usb_power_set_voltage_min(struct axp20x_usb_power *power, return -EINVAL; } +static int axp813_usb_power_set_current_max(struct axp20x_usb_power *power, + int intval) +{ + int val; + + switch (intval) { + case 900000: + return regmap_update_bits(power->regmap, + AXP20X_VBUS_IPSOUT_MGMT, + AXP20X_VBUS_CLIMIT_MASK, + AXP813_VBUS_CLIMIT_900mA); + case 1500000: + case 2000000: + case 2500000: + val = (intval - 1000000) / 500000; + return regmap_update_bits(power->regmap, + AXP20X_VBUS_IPSOUT_MGMT, + AXP20X_VBUS_CLIMIT_MASK, val); + default: + return -EINVAL; + } + + return -EINVAL; +} + static int axp20x_usb_power_set_current_max(struct axp20x_usb_power *power, int intval) { @@ -248,6 +351,9 @@ static int axp20x_usb_power_set_property(struct power_supply *psy, return axp20x_usb_power_set_voltage_min(power, val->intval); case POWER_SUPPLY_PROP_CURRENT_MAX: + if (power->axp20x_id == AXP813_ID) + return axp813_usb_power_set_current_max(power, + val->intval); return axp20x_usb_power_set_current_max(power, val->intval); default: @@ -357,6 +463,7 @@ static int axp20x_usb_power_probe(struct platform_device *pdev) if (!power) return -ENOMEM; + platform_set_drvdata(pdev, power); power->axp20x_id = (enum axp20x_variants)of_device_get_match_data( &pdev->dev); @@ -382,7 +489,8 @@ static int axp20x_usb_power_probe(struct platform_device *pdev) usb_power_desc = &axp20x_usb_power_desc; irq_names = axp20x_irq_names; } else if (power->axp20x_id == AXP221_ID || - power->axp20x_id == AXP223_ID) { + power->axp20x_id == AXP223_ID || + power->axp20x_id == AXP813_ID) { usb_power_desc = &axp22x_usb_power_desc; irq_names = axp22x_irq_names; } else { @@ -415,6 +523,19 @@ static int axp20x_usb_power_probe(struct platform_device *pdev) irq_names[i], ret); } + INIT_DELAYED_WORK(&power->vbus_detect, axp20x_usb_power_poll_vbus); + if (axp20x_usb_vbus_needs_polling(power)) + queue_delayed_work(system_wq, &power->vbus_detect, 0); + + return 0; +} + +static int axp20x_usb_power_remove(struct platform_device *pdev) +{ + struct axp20x_usb_power *power = platform_get_drvdata(pdev); + + cancel_delayed_work_sync(&power->vbus_detect); + return 0; } @@ -428,12 +549,16 @@ static const struct of_device_id axp20x_usb_power_match[] = { }, { .compatible = "x-powers,axp223-usb-power-supply", .data = (void *)AXP223_ID, + }, { + .compatible = "x-powers,axp813-usb-power-supply", + .data = (void *)AXP813_ID, }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, axp20x_usb_power_match); static struct platform_driver axp20x_usb_power_driver = { .probe = axp20x_usb_power_probe, + .remove = axp20x_usb_power_remove, .driver = { .name = DRVNAME, .of_match_table = axp20x_usb_power_match, diff --git a/drivers/power/supply/axp288_charger.c b/drivers/power/supply/axp288_charger.c index f8c6da9277b3..00b961890a38 100644 --- a/drivers/power/supply/axp288_charger.c +++ b/drivers/power/supply/axp288_charger.c @@ -833,6 +833,10 @@ static int axp288_charger_probe(struct platform_device *pdev) /* Register charger interrupts */ for (i = 0; i < CHRG_INTR_END; i++) { pirq = platform_get_irq(info->pdev, i); + if (pirq < 0) { + dev_err(&pdev->dev, "Failed to get IRQ: %d\n", pirq); + return pirq; + } info->irq[i] = regmap_irq_get_virq(info->regmap_irqc, pirq); if (info->irq[i] < 0) { dev_warn(&info->pdev->dev, diff --git a/drivers/power/supply/axp288_fuel_gauge.c b/drivers/power/supply/axp288_fuel_gauge.c index 9ff2461820d8..368281bc0d2b 100644 --- a/drivers/power/supply/axp288_fuel_gauge.c +++ b/drivers/power/supply/axp288_fuel_gauge.c @@ -686,6 +686,26 @@ intr_failed: */ static const struct dmi_system_id axp288_fuel_gauge_blacklist[] = { { + /* ACEPC T8 Cherry Trail Z8350 mini PC */ + .matches = { + DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "To be filled by O.E.M."), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "Cherry Trail CR"), + DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "T8"), + /* also match on somewhat unique bios-version */ + DMI_EXACT_MATCH(DMI_BIOS_VERSION, "1.000"), + }, + }, + { + /* ACEPC T11 Cherry Trail Z8350 mini PC */ + .matches = { + DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "To be filled by O.E.M."), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "Cherry Trail CR"), + DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "T11"), + /* also match on somewhat unique bios-version */ + DMI_EXACT_MATCH(DMI_BIOS_VERSION, "1.000"), + }, + }, + { /* Intel Cherry Trail Compute Stick, Windows version */ .matches = { DMI_MATCH(DMI_SYS_VENDOR, "Intel Corporation"), diff --git a/drivers/power/supply/bq27xxx_battery.c b/drivers/power/supply/bq27xxx_battery.c index 29b3a4056865..195c18c2f426 100644 --- a/drivers/power/supply/bq27xxx_battery.c +++ b/drivers/power/supply/bq27xxx_battery.c @@ -1612,7 +1612,8 @@ void bq27xxx_battery_update(struct bq27xxx_device_info *di) di->charge_design_full = bq27xxx_battery_read_dcap(di); } - if (di->cache.capacity != cache.capacity) + if ((di->cache.capacity != cache.capacity) || + (di->cache.flags != cache.flags)) power_supply_changed(di->bat); if (memcmp(&di->cache, &cache, sizeof(cache)) != 0) diff --git a/drivers/power/supply/charger-manager.c b/drivers/power/supply/charger-manager.c index 2e8db5e6de0b..a6900aa0d2ed 100644 --- a/drivers/power/supply/charger-manager.c +++ b/drivers/power/supply/charger-manager.c @@ -1987,6 +1987,9 @@ static struct platform_driver charger_manager_driver = { static int __init charger_manager_init(void) { cm_wq = create_freezable_workqueue("charger_manager"); + if (unlikely(!cm_wq)) + return -ENOMEM; + INIT_DELAYED_WORK(&cm_monitor_work, cm_monitor_poller); return platform_driver_register(&charger_manager_driver); diff --git a/drivers/power/supply/cpcap-battery.c b/drivers/power/supply/cpcap-battery.c index 6887870ba32c..61d6447d1966 100644 --- a/drivers/power/supply/cpcap-battery.c +++ b/drivers/power/supply/cpcap-battery.c @@ -82,9 +82,9 @@ struct cpcap_battery_config { }; struct cpcap_coulomb_counter_data { - s32 sample; /* 24-bits */ + s32 sample; /* 24 or 32 bits */ s32 accumulator; - s16 offset; /* 10-bits */ + s16 offset; /* 9 bits */ }; enum cpcap_battery_state { @@ -213,7 +213,7 @@ static int cpcap_battery_get_current(struct cpcap_battery_ddata *ddata) * TI or ST coulomb counter in the PMIC. */ static int cpcap_battery_cc_raw_div(struct cpcap_battery_ddata *ddata, - u32 sample, s32 accumulator, + s32 sample, s32 accumulator, s16 offset, u32 divider) { s64 acc; @@ -224,9 +224,6 @@ static int cpcap_battery_cc_raw_div(struct cpcap_battery_ddata *ddata, if (!divider) return 0; - sample &= 0xffffff; /* 24-bits, unsigned */ - offset &= 0x7ff; /* 10-bits, signed */ - switch (ddata->vendor) { case CPCAP_VENDOR_ST: cc_lsb = 95374; /* μAms per LSB */ @@ -259,7 +256,7 @@ static int cpcap_battery_cc_raw_div(struct cpcap_battery_ddata *ddata, /* 3600000μAms = 1μAh */ static int cpcap_battery_cc_to_uah(struct cpcap_battery_ddata *ddata, - u32 sample, s32 accumulator, + s32 sample, s32 accumulator, s16 offset) { return cpcap_battery_cc_raw_div(ddata, sample, @@ -268,7 +265,7 @@ static int cpcap_battery_cc_to_uah(struct cpcap_battery_ddata *ddata, } static int cpcap_battery_cc_to_ua(struct cpcap_battery_ddata *ddata, - u32 sample, s32 accumulator, + s32 sample, s32 accumulator, s16 offset) { return cpcap_battery_cc_raw_div(ddata, sample, @@ -312,17 +309,19 @@ cpcap_battery_read_accumulated(struct cpcap_battery_ddata *ddata, /* Sample value CPCAP_REG_CCS1 & 2 */ ccd->sample = (buf[1] & 0x0fff) << 16; ccd->sample |= buf[0]; + if (ddata->vendor == CPCAP_VENDOR_TI) + ccd->sample = sign_extend32(24, ccd->sample); /* Accumulator value CPCAP_REG_CCA1 & 2 */ ccd->accumulator = ((s16)buf[3]) << 16; ccd->accumulator |= buf[2]; - /* Offset value CPCAP_REG_CCO */ - ccd->offset = buf[5]; - - /* Adjust offset based on mode value CPCAP_REG_CCM? */ - if (buf[4] >= 0x200) - ccd->offset |= 0xfc00; + /* + * Coulomb counter calibration offset is CPCAP_REG_CCM, + * REG_CCO seems unused + */ + ccd->offset = buf[4]; + ccd->offset = sign_extend32(ccd->offset, 9); return cpcap_battery_cc_to_uah(ddata, ccd->sample, @@ -477,11 +476,11 @@ static int cpcap_battery_get_property(struct power_supply *psy, val->intval = ddata->config.info.voltage_min_design; break; case POWER_SUPPLY_PROP_CURRENT_AVG: - if (cached) { + sample = latest->cc.sample - previous->cc.sample; + if (!sample) { val->intval = cpcap_battery_cc_get_avg_current(ddata); break; } - sample = latest->cc.sample - previous->cc.sample; accumulator = latest->cc.accumulator - previous->cc.accumulator; val->intval = cpcap_battery_cc_to_ua(ddata, sample, accumulator, @@ -498,13 +497,13 @@ static int cpcap_battery_get_property(struct power_supply *psy, val->intval = div64_s64(tmp, 100); break; case POWER_SUPPLY_PROP_POWER_AVG: - if (cached) { + sample = latest->cc.sample - previous->cc.sample; + if (!sample) { tmp = cpcap_battery_cc_get_avg_current(ddata); tmp *= (latest->voltage / 10000); val->intval = div64_s64(tmp, 100); break; } - sample = latest->cc.sample - previous->cc.sample; accumulator = latest->cc.accumulator - previous->cc.accumulator; tmp = cpcap_battery_cc_to_ua(ddata, sample, accumulator, latest->cc.offset); @@ -562,11 +561,11 @@ static irqreturn_t cpcap_battery_irq_thread(int irq, void *data) switch (d->action) { case CPCAP_BATTERY_IRQ_ACTION_BATTERY_LOW: - if (latest->counter_uah >= 0) + if (latest->current_ua >= 0) dev_warn(ddata->dev, "Battery low at 3.3V!\n"); break; case CPCAP_BATTERY_IRQ_ACTION_POWEROFF: - if (latest->counter_uah >= 0) { + if (latest->current_ua >= 0) { dev_emerg(ddata->dev, "Battery empty at 3.1V, powering off\n"); orderly_poweroff(true); @@ -670,8 +669,9 @@ static int cpcap_battery_init_iio(struct cpcap_battery_ddata *ddata) return 0; out_err: - dev_err(ddata->dev, "could not initialize VBUS or ID IIO: %i\n", - error); + if (error != -EPROBE_DEFER) + dev_err(ddata->dev, "could not initialize VBUS or ID IIO: %i\n", + error); return error; } diff --git a/drivers/power/supply/cpcap-charger.c b/drivers/power/supply/cpcap-charger.c index c3ed7b476676..b4781b5d1e10 100644 --- a/drivers/power/supply/cpcap-charger.c +++ b/drivers/power/supply/cpcap-charger.c @@ -574,8 +574,9 @@ static int cpcap_charger_init_iio(struct cpcap_charger_ddata *ddata) return 0; out_err: - dev_err(ddata->dev, "could not initialize VBUS or ID IIO: %i\n", - error); + if (error != -EPROBE_DEFER) + dev_err(ddata->dev, "could not initialize VBUS or ID IIO: %i\n", + error); return error; } diff --git a/drivers/power/supply/gpio-charger.c b/drivers/power/supply/gpio-charger.c index 7e4f11d5a230..f99e8f1eef23 100644 --- a/drivers/power/supply/gpio-charger.c +++ b/drivers/power/supply/gpio-charger.c @@ -29,11 +29,13 @@ struct gpio_charger { unsigned int irq; + unsigned int charge_status_irq; bool wakeup_enabled; struct power_supply *charger; struct power_supply_desc charger_desc; struct gpio_desc *gpiod; + struct gpio_desc *charge_status; }; static irqreturn_t gpio_charger_irq(int irq, void *devid) @@ -59,6 +61,12 @@ static int gpio_charger_get_property(struct power_supply *psy, case POWER_SUPPLY_PROP_ONLINE: val->intval = gpiod_get_value_cansleep(gpio_charger->gpiod); break; + case POWER_SUPPLY_PROP_STATUS: + if (gpiod_get_value_cansleep(gpio_charger->charge_status)) + val->intval = POWER_SUPPLY_STATUS_CHARGING; + else + val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; + break; default: return -EINVAL; } @@ -93,8 +101,29 @@ static enum power_supply_type gpio_charger_get_type(struct device *dev) return POWER_SUPPLY_TYPE_UNKNOWN; } +static int gpio_charger_get_irq(struct device *dev, void *dev_id, + struct gpio_desc *gpio) +{ + int ret, irq = gpiod_to_irq(gpio); + + if (irq > 0) { + ret = devm_request_any_context_irq(dev, irq, gpio_charger_irq, + IRQF_TRIGGER_RISING | + IRQF_TRIGGER_FALLING, + dev_name(dev), + dev_id); + if (ret < 0) { + dev_warn(dev, "Failed to request irq: %d\n", ret); + irq = 0; + } + } + + return irq; +} + static enum power_supply_property gpio_charger_properties[] = { POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_STATUS /* Must always be last in the array. */ }; static int gpio_charger_probe(struct platform_device *pdev) @@ -104,8 +133,10 @@ static int gpio_charger_probe(struct platform_device *pdev) struct power_supply_config psy_cfg = {}; struct gpio_charger *gpio_charger; struct power_supply_desc *charger_desc; + struct gpio_desc *charge_status; + int charge_status_irq; unsigned long flags; - int irq, ret; + int ret; if (!pdata && !dev->of_node) { dev_err(dev, "No platform data\n"); @@ -151,9 +182,17 @@ static int gpio_charger_probe(struct platform_device *pdev) return PTR_ERR(gpio_charger->gpiod); } + charge_status = devm_gpiod_get_optional(dev, "charge-status", GPIOD_IN); + gpio_charger->charge_status = charge_status; + if (IS_ERR(gpio_charger->charge_status)) + return PTR_ERR(gpio_charger->charge_status); + charger_desc = &gpio_charger->charger_desc; charger_desc->properties = gpio_charger_properties; charger_desc->num_properties = ARRAY_SIZE(gpio_charger_properties); + /* Remove POWER_SUPPLY_PROP_STATUS from the supported properties. */ + if (!gpio_charger->charge_status) + charger_desc->num_properties -= 1; charger_desc->get_property = gpio_charger_get_property; psy_cfg.of_node = dev->of_node; @@ -180,16 +219,12 @@ static int gpio_charger_probe(struct platform_device *pdev) return ret; } - irq = gpiod_to_irq(gpio_charger->gpiod); - if (irq > 0) { - ret = devm_request_any_context_irq(dev, irq, gpio_charger_irq, - IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, - dev_name(dev), gpio_charger->charger); - if (ret < 0) - dev_warn(dev, "Failed to request irq: %d\n", ret); - else - gpio_charger->irq = irq; - } + gpio_charger->irq = gpio_charger_get_irq(dev, gpio_charger->charger, + gpio_charger->gpiod); + + charge_status_irq = gpio_charger_get_irq(dev, gpio_charger->charger, + gpio_charger->charge_status); + gpio_charger->charge_status_irq = charge_status_irq; platform_set_drvdata(pdev, gpio_charger); diff --git a/drivers/power/supply/ingenic-battery.c b/drivers/power/supply/ingenic-battery.c new file mode 100644 index 000000000000..35816d4b3012 --- /dev/null +++ b/drivers/power/supply/ingenic-battery.c @@ -0,0 +1,184 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Battery driver for the Ingenic JZ47xx SoCs + * Copyright (c) 2019 Artur Rojek <contact@artur-rojek.eu> + * + * based on drivers/power/supply/jz4740-battery.c + */ + +#include <linux/iio/consumer.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/power_supply.h> +#include <linux/property.h> + +struct ingenic_battery { + struct device *dev; + struct iio_channel *channel; + struct power_supply_desc desc; + struct power_supply *battery; + struct power_supply_battery_info info; +}; + +static int ingenic_battery_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct ingenic_battery *bat = power_supply_get_drvdata(psy); + struct power_supply_battery_info *info = &bat->info; + int ret; + + switch (psp) { + case POWER_SUPPLY_PROP_HEALTH: + ret = iio_read_channel_processed(bat->channel, &val->intval); + val->intval *= 1000; + if (val->intval < info->voltage_min_design_uv) + val->intval = POWER_SUPPLY_HEALTH_DEAD; + else if (val->intval > info->voltage_max_design_uv) + val->intval = POWER_SUPPLY_HEALTH_OVERVOLTAGE; + else + val->intval = POWER_SUPPLY_HEALTH_GOOD; + return ret; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + ret = iio_read_channel_processed(bat->channel, &val->intval); + val->intval *= 1000; + return ret; + case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: + val->intval = info->voltage_min_design_uv; + return 0; + case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: + val->intval = info->voltage_max_design_uv; + return 0; + default: + return -EINVAL; + }; +} + +/* Set the most appropriate IIO channel voltage reference scale + * based on the battery's max voltage. + */ +static int ingenic_battery_set_scale(struct ingenic_battery *bat) +{ + const int *scale_raw; + int scale_len, scale_type, best_idx = -1, best_mV, max_raw, i, ret; + u64 max_mV; + + ret = iio_read_max_channel_raw(bat->channel, &max_raw); + if (ret) { + dev_err(bat->dev, "Unable to read max raw channel value\n"); + return ret; + } + + ret = iio_read_avail_channel_attribute(bat->channel, &scale_raw, + &scale_type, &scale_len, + IIO_CHAN_INFO_SCALE); + if (ret < 0) { + dev_err(bat->dev, "Unable to read channel avail scale\n"); + return ret; + } + if (ret != IIO_AVAIL_LIST || scale_type != IIO_VAL_FRACTIONAL_LOG2) + return -EINVAL; + + max_mV = bat->info.voltage_max_design_uv / 1000; + + for (i = 0; i < scale_len; i += 2) { + u64 scale_mV = (max_raw * scale_raw[i]) >> scale_raw[i + 1]; + + if (scale_mV < max_mV) + continue; + + if (best_idx >= 0 && scale_mV > best_mV) + continue; + + best_mV = scale_mV; + best_idx = i; + } + + if (best_idx < 0) { + dev_err(bat->dev, "Unable to find matching voltage scale\n"); + return -EINVAL; + } + + return iio_write_channel_attribute(bat->channel, + scale_raw[best_idx], + scale_raw[best_idx + 1], + IIO_CHAN_INFO_SCALE); +} + +static enum power_supply_property ingenic_battery_properties[] = { + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, + POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, +}; + +static int ingenic_battery_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct ingenic_battery *bat; + struct power_supply_config psy_cfg = {}; + struct power_supply_desc *desc; + int ret; + + bat = devm_kzalloc(dev, sizeof(*bat), GFP_KERNEL); + if (!bat) + return -ENOMEM; + + bat->dev = dev; + bat->channel = devm_iio_channel_get(dev, "battery"); + if (IS_ERR(bat->channel)) + return PTR_ERR(bat->channel); + + desc = &bat->desc; + desc->name = "jz-battery"; + desc->type = POWER_SUPPLY_TYPE_BATTERY; + desc->properties = ingenic_battery_properties; + desc->num_properties = ARRAY_SIZE(ingenic_battery_properties); + desc->get_property = ingenic_battery_get_property; + psy_cfg.drv_data = bat; + psy_cfg.of_node = dev->of_node; + + bat->battery = devm_power_supply_register(dev, desc, &psy_cfg); + if (IS_ERR(bat->battery)) { + dev_err(dev, "Unable to register battery\n"); + return PTR_ERR(bat->battery); + } + + ret = power_supply_get_battery_info(bat->battery, &bat->info); + if (ret) { + dev_err(dev, "Unable to get battery info: %d\n", ret); + return ret; + } + if (bat->info.voltage_min_design_uv < 0) { + dev_err(dev, "Unable to get voltage min design\n"); + return bat->info.voltage_min_design_uv; + } + if (bat->info.voltage_max_design_uv < 0) { + dev_err(dev, "Unable to get voltage max design\n"); + return bat->info.voltage_max_design_uv; + } + + return ingenic_battery_set_scale(bat); +} + +#ifdef CONFIG_OF +static const struct of_device_id ingenic_battery_of_match[] = { + { .compatible = "ingenic,jz4740-battery", }, + { }, +}; +MODULE_DEVICE_TABLE(of, ingenic_battery_of_match); +#endif + +static struct platform_driver ingenic_battery_driver = { + .driver = { + .name = "ingenic-battery", + .of_match_table = of_match_ptr(ingenic_battery_of_match), + }, + .probe = ingenic_battery_probe, +}; +module_platform_driver(ingenic_battery_driver); + +MODULE_DESCRIPTION("Battery driver for Ingenic JZ47xx SoCs"); +MODULE_AUTHOR("Artur Rojek <contact@artur-rojek.eu>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/power/supply/ltc3651-charger.c b/drivers/power/supply/lt3651-charger.c index eea63ff211c4..8de500ffad95 100644 --- a/drivers/power/supply/ltc3651-charger.c +++ b/drivers/power/supply/lt3651-charger.c @@ -1,11 +1,7 @@ +// SPDX-License-Identifier: GPL-2.0+ /* + * Driver for Analog Devices (Linear Technology) LT3651 charger IC. * Copyright (C) 2017, Topic Embedded Products - * Driver for LTC3651 charger IC. - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the - * Free Software Foundation; either version 2 of the License, or (at your - * option) any later version. */ #include <linux/device.h> @@ -19,7 +15,7 @@ #include <linux/slab.h> #include <linux/of.h> -struct ltc3651_charger { +struct lt3651_charger { struct power_supply *charger; struct power_supply_desc charger_desc; struct gpio_desc *acpr_gpio; @@ -27,7 +23,7 @@ struct ltc3651_charger { struct gpio_desc *chrg_gpio; }; -static irqreturn_t ltc3651_charger_irq(int irq, void *devid) +static irqreturn_t lt3651_charger_irq(int irq, void *devid) { struct power_supply *charger = devid; @@ -36,37 +32,37 @@ static irqreturn_t ltc3651_charger_irq(int irq, void *devid) return IRQ_HANDLED; } -static inline struct ltc3651_charger *psy_to_ltc3651_charger( +static inline struct lt3651_charger *psy_to_lt3651_charger( struct power_supply *psy) { return power_supply_get_drvdata(psy); } -static int ltc3651_charger_get_property(struct power_supply *psy, +static int lt3651_charger_get_property(struct power_supply *psy, enum power_supply_property psp, union power_supply_propval *val) { - struct ltc3651_charger *ltc3651_charger = psy_to_ltc3651_charger(psy); + struct lt3651_charger *lt3651_charger = psy_to_lt3651_charger(psy); switch (psp) { case POWER_SUPPLY_PROP_STATUS: - if (!ltc3651_charger->chrg_gpio) { + if (!lt3651_charger->chrg_gpio) { val->intval = POWER_SUPPLY_STATUS_UNKNOWN; break; } - if (gpiod_get_value(ltc3651_charger->chrg_gpio)) + if (gpiod_get_value(lt3651_charger->chrg_gpio)) val->intval = POWER_SUPPLY_STATUS_CHARGING; else val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; break; case POWER_SUPPLY_PROP_ONLINE: - val->intval = gpiod_get_value(ltc3651_charger->acpr_gpio); + val->intval = gpiod_get_value(lt3651_charger->acpr_gpio); break; case POWER_SUPPLY_PROP_HEALTH: - if (!ltc3651_charger->fault_gpio) { + if (!lt3651_charger->fault_gpio) { val->intval = POWER_SUPPLY_HEALTH_UNKNOWN; break; } - if (!gpiod_get_value(ltc3651_charger->fault_gpio)) { + if (!gpiod_get_value(lt3651_charger->fault_gpio)) { val->intval = POWER_SUPPLY_HEALTH_GOOD; break; } @@ -74,11 +70,11 @@ static int ltc3651_charger_get_property(struct power_supply *psy, * If the fault pin is active, the chrg pin explains the type * of failure. */ - if (!ltc3651_charger->chrg_gpio) { + if (!lt3651_charger->chrg_gpio) { val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; break; } - val->intval = gpiod_get_value(ltc3651_charger->chrg_gpio) ? + val->intval = gpiod_get_value(lt3651_charger->chrg_gpio) ? POWER_SUPPLY_HEALTH_OVERHEAT : POWER_SUPPLY_HEALTH_DEAD; break; @@ -89,59 +85,59 @@ static int ltc3651_charger_get_property(struct power_supply *psy, return 0; } -static enum power_supply_property ltc3651_charger_properties[] = { +static enum power_supply_property lt3651_charger_properties[] = { POWER_SUPPLY_PROP_STATUS, POWER_SUPPLY_PROP_ONLINE, POWER_SUPPLY_PROP_HEALTH, }; -static int ltc3651_charger_probe(struct platform_device *pdev) +static int lt3651_charger_probe(struct platform_device *pdev) { struct power_supply_config psy_cfg = {}; - struct ltc3651_charger *ltc3651_charger; + struct lt3651_charger *lt3651_charger; struct power_supply_desc *charger_desc; int ret; - ltc3651_charger = devm_kzalloc(&pdev->dev, sizeof(*ltc3651_charger), + lt3651_charger = devm_kzalloc(&pdev->dev, sizeof(*lt3651_charger), GFP_KERNEL); - if (!ltc3651_charger) + if (!lt3651_charger) return -ENOMEM; - ltc3651_charger->acpr_gpio = devm_gpiod_get(&pdev->dev, + lt3651_charger->acpr_gpio = devm_gpiod_get(&pdev->dev, "lltc,acpr", GPIOD_IN); - if (IS_ERR(ltc3651_charger->acpr_gpio)) { - ret = PTR_ERR(ltc3651_charger->acpr_gpio); + if (IS_ERR(lt3651_charger->acpr_gpio)) { + ret = PTR_ERR(lt3651_charger->acpr_gpio); dev_err(&pdev->dev, "Failed to acquire acpr GPIO: %d\n", ret); return ret; } - ltc3651_charger->fault_gpio = devm_gpiod_get_optional(&pdev->dev, + lt3651_charger->fault_gpio = devm_gpiod_get_optional(&pdev->dev, "lltc,fault", GPIOD_IN); - if (IS_ERR(ltc3651_charger->fault_gpio)) { - ret = PTR_ERR(ltc3651_charger->fault_gpio); + if (IS_ERR(lt3651_charger->fault_gpio)) { + ret = PTR_ERR(lt3651_charger->fault_gpio); dev_err(&pdev->dev, "Failed to acquire fault GPIO: %d\n", ret); return ret; } - ltc3651_charger->chrg_gpio = devm_gpiod_get_optional(&pdev->dev, + lt3651_charger->chrg_gpio = devm_gpiod_get_optional(&pdev->dev, "lltc,chrg", GPIOD_IN); - if (IS_ERR(ltc3651_charger->chrg_gpio)) { - ret = PTR_ERR(ltc3651_charger->chrg_gpio); + if (IS_ERR(lt3651_charger->chrg_gpio)) { + ret = PTR_ERR(lt3651_charger->chrg_gpio); dev_err(&pdev->dev, "Failed to acquire chrg GPIO: %d\n", ret); return ret; } - charger_desc = <c3651_charger->charger_desc; + charger_desc = <3651_charger->charger_desc; charger_desc->name = pdev->dev.of_node->name; charger_desc->type = POWER_SUPPLY_TYPE_MAINS; - charger_desc->properties = ltc3651_charger_properties; - charger_desc->num_properties = ARRAY_SIZE(ltc3651_charger_properties); - charger_desc->get_property = ltc3651_charger_get_property; + charger_desc->properties = lt3651_charger_properties; + charger_desc->num_properties = ARRAY_SIZE(lt3651_charger_properties); + charger_desc->get_property = lt3651_charger_get_property; psy_cfg.of_node = pdev->dev.of_node; - psy_cfg.drv_data = ltc3651_charger; + psy_cfg.drv_data = lt3651_charger; - ltc3651_charger->charger = devm_power_supply_register(&pdev->dev, + lt3651_charger->charger = devm_power_supply_register(&pdev->dev, charger_desc, &psy_cfg); - if (IS_ERR(ltc3651_charger->charger)) { - ret = PTR_ERR(ltc3651_charger->charger); + if (IS_ERR(lt3651_charger->charger)) { + ret = PTR_ERR(lt3651_charger->charger); dev_err(&pdev->dev, "Failed to register power supply: %d\n", ret); return ret; @@ -152,59 +148,60 @@ static int ltc3651_charger_probe(struct platform_device *pdev) * support IRQs on these pins, userspace will have to poll the sysfs * files manually. */ - if (ltc3651_charger->acpr_gpio) { - ret = gpiod_to_irq(ltc3651_charger->acpr_gpio); + if (lt3651_charger->acpr_gpio) { + ret = gpiod_to_irq(lt3651_charger->acpr_gpio); if (ret >= 0) ret = devm_request_any_context_irq(&pdev->dev, ret, - ltc3651_charger_irq, + lt3651_charger_irq, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, - dev_name(&pdev->dev), ltc3651_charger->charger); + dev_name(&pdev->dev), lt3651_charger->charger); if (ret < 0) dev_warn(&pdev->dev, "Failed to request acpr irq\n"); } - if (ltc3651_charger->fault_gpio) { - ret = gpiod_to_irq(ltc3651_charger->fault_gpio); + if (lt3651_charger->fault_gpio) { + ret = gpiod_to_irq(lt3651_charger->fault_gpio); if (ret >= 0) ret = devm_request_any_context_irq(&pdev->dev, ret, - ltc3651_charger_irq, + lt3651_charger_irq, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, - dev_name(&pdev->dev), ltc3651_charger->charger); + dev_name(&pdev->dev), lt3651_charger->charger); if (ret < 0) dev_warn(&pdev->dev, "Failed to request fault irq\n"); } - if (ltc3651_charger->chrg_gpio) { - ret = gpiod_to_irq(ltc3651_charger->chrg_gpio); + if (lt3651_charger->chrg_gpio) { + ret = gpiod_to_irq(lt3651_charger->chrg_gpio); if (ret >= 0) ret = devm_request_any_context_irq(&pdev->dev, ret, - ltc3651_charger_irq, + lt3651_charger_irq, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, - dev_name(&pdev->dev), ltc3651_charger->charger); + dev_name(&pdev->dev), lt3651_charger->charger); if (ret < 0) dev_warn(&pdev->dev, "Failed to request chrg irq\n"); } - platform_set_drvdata(pdev, ltc3651_charger); + platform_set_drvdata(pdev, lt3651_charger); return 0; } -static const struct of_device_id ltc3651_charger_match[] = { - { .compatible = "lltc,ltc3651-charger" }, +static const struct of_device_id lt3651_charger_match[] = { + { .compatible = "lltc,ltc3651-charger" }, /* DEPRECATED */ + { .compatible = "lltc,lt3651-charger" }, { } }; -MODULE_DEVICE_TABLE(of, ltc3651_charger_match); +MODULE_DEVICE_TABLE(of, lt3651_charger_match); -static struct platform_driver ltc3651_charger_driver = { - .probe = ltc3651_charger_probe, +static struct platform_driver lt3651_charger_driver = { + .probe = lt3651_charger_probe, .driver = { - .name = "ltc3651-charger", - .of_match_table = ltc3651_charger_match, + .name = "lt3651-charger", + .of_match_table = lt3651_charger_match, }, }; -module_platform_driver(ltc3651_charger_driver); +module_platform_driver(lt3651_charger_driver); MODULE_AUTHOR("Mike Looijmans <mike.looijmans@topic.nl>"); -MODULE_DESCRIPTION("Driver for LTC3651 charger"); +MODULE_DESCRIPTION("Driver for LT3651 charger"); MODULE_LICENSE("GPL"); -MODULE_ALIAS("platform:ltc3651-charger"); +MODULE_ALIAS("platform:lt3651-charger"); diff --git a/drivers/power/supply/max14656_charger_detector.c b/drivers/power/supply/max14656_charger_detector.c index b91b1d2999dc..9e6472834e37 100644 --- a/drivers/power/supply/max14656_charger_detector.c +++ b/drivers/power/supply/max14656_charger_detector.c @@ -240,6 +240,14 @@ static enum power_supply_property max14656_battery_props[] = { POWER_SUPPLY_PROP_MANUFACTURER, }; +static void stop_irq_work(void *data) +{ + struct max14656_chip *chip = data; + + cancel_delayed_work_sync(&chip->irq_work); +} + + static int max14656_probe(struct i2c_client *client, const struct i2c_device_id *id) { @@ -278,7 +286,19 @@ static int max14656_probe(struct i2c_client *client, if (ret) return -ENODEV; + chip->detect_psy = devm_power_supply_register(dev, + &chip->psy_desc, &psy_cfg); + if (IS_ERR(chip->detect_psy)) { + dev_err(dev, "power_supply_register failed\n"); + return -EINVAL; + } + INIT_DELAYED_WORK(&chip->irq_work, max14656_irq_worker); + ret = devm_add_action(dev, stop_irq_work, chip); + if (ret) { + dev_err(dev, "devm_add_action %d failed\n", ret); + return ret; + } ret = devm_request_irq(dev, chip->irq, max14656_irq, IRQF_TRIGGER_FALLING, @@ -289,13 +309,6 @@ static int max14656_probe(struct i2c_client *client, } enable_irq_wake(chip->irq); - chip->detect_psy = devm_power_supply_register(dev, - &chip->psy_desc, &psy_cfg); - if (IS_ERR(chip->detect_psy)) { - dev_err(dev, "power_supply_register failed\n"); - return -EINVAL; - } - schedule_delayed_work(&chip->irq_work, msecs_to_jiffies(2000)); return 0; diff --git a/drivers/power/supply/olpc_battery.c b/drivers/power/supply/olpc_battery.c index 5a97e42a3547..7720e4c2ac0b 100644 --- a/drivers/power/supply/olpc_battery.c +++ b/drivers/power/supply/olpc_battery.c @@ -14,6 +14,7 @@ #include <linux/types.h> #include <linux/err.h> #include <linux/device.h> +#include <linux/of.h> #include <linux/platform_device.h> #include <linux/power_supply.h> #include <linux/jiffies.h> @@ -52,6 +53,14 @@ #define BAT_ADDR_MFR_TYPE 0x5F +struct olpc_battery_data { + struct power_supply *olpc_ac; + struct power_supply *olpc_bat; + char bat_serial[17]; + bool new_proto; + bool little_endian; +}; + /********************************************************************* * Power *********************************************************************/ @@ -90,13 +99,10 @@ static const struct power_supply_desc olpc_ac_desc = { .get_property = olpc_ac_get_prop, }; -static struct power_supply *olpc_ac; - -static char bat_serial[17]; /* Ick */ - -static int olpc_bat_get_status(union power_supply_propval *val, uint8_t ec_byte) +static int olpc_bat_get_status(struct olpc_battery_data *data, + union power_supply_propval *val, uint8_t ec_byte) { - if (olpc_platform_info.ecver > 0x44) { + if (data->new_proto) { if (ec_byte & (BAT_STAT_CHARGING | BAT_STAT_TRICKLE)) val->intval = POWER_SUPPLY_STATUS_CHARGING; else if (ec_byte & BAT_STAT_DISCHARGING) @@ -318,6 +324,14 @@ static int olpc_bat_get_voltage_max_design(union power_supply_propval *val) return ret; } +static u16 ecword_to_cpu(struct olpc_battery_data *data, u16 ec_word) +{ + if (data->little_endian) + return le16_to_cpu((__force __le16)ec_word); + else + return be16_to_cpu((__force __be16)ec_word); +} + /********************************************************************* * Battery properties *********************************************************************/ @@ -325,8 +339,9 @@ static int olpc_bat_get_property(struct power_supply *psy, enum power_supply_property psp, union power_supply_propval *val) { + struct olpc_battery_data *data = power_supply_get_drvdata(psy); int ret = 0; - __be16 ec_word; + u16 ec_word; uint8_t ec_byte; __be64 ser_buf; @@ -346,7 +361,7 @@ static int olpc_bat_get_property(struct power_supply *psy, switch (psp) { case POWER_SUPPLY_PROP_STATUS: - ret = olpc_bat_get_status(val, ec_byte); + ret = olpc_bat_get_status(data, val, ec_byte); if (ret) return ret; break; @@ -389,7 +404,7 @@ static int olpc_bat_get_property(struct power_supply *psy, if (ret) return ret; - val->intval = (s16)be16_to_cpu(ec_word) * 9760L / 32; + val->intval = ecword_to_cpu(data, ec_word) * 9760L / 32; break; case POWER_SUPPLY_PROP_CURRENT_AVG: case POWER_SUPPLY_PROP_CURRENT_NOW: @@ -397,7 +412,7 @@ static int olpc_bat_get_property(struct power_supply *psy, if (ret) return ret; - val->intval = (s16)be16_to_cpu(ec_word) * 15625L / 120; + val->intval = ecword_to_cpu(data, ec_word) * 15625L / 120; break; case POWER_SUPPLY_PROP_CAPACITY: ret = olpc_ec_cmd(EC_BAT_SOC, NULL, 0, &ec_byte, 1); @@ -428,29 +443,29 @@ static int olpc_bat_get_property(struct power_supply *psy, if (ret) return ret; - val->intval = (s16)be16_to_cpu(ec_word) * 10 / 256; + val->intval = ecword_to_cpu(data, ec_word) * 10 / 256; break; case POWER_SUPPLY_PROP_TEMP_AMBIENT: ret = olpc_ec_cmd(EC_AMB_TEMP, NULL, 0, (void *)&ec_word, 2); if (ret) return ret; - val->intval = (int)be16_to_cpu(ec_word) * 10 / 256; + val->intval = (int)ecword_to_cpu(data, ec_word) * 10 / 256; break; case POWER_SUPPLY_PROP_CHARGE_COUNTER: ret = olpc_ec_cmd(EC_BAT_ACR, NULL, 0, (void *)&ec_word, 2); if (ret) return ret; - val->intval = (s16)be16_to_cpu(ec_word) * 6250 / 15; + val->intval = ecword_to_cpu(data, ec_word) * 6250 / 15; break; case POWER_SUPPLY_PROP_SERIAL_NUMBER: ret = olpc_ec_cmd(EC_BAT_SERIAL, NULL, 0, (void *)&ser_buf, 8); if (ret) return ret; - sprintf(bat_serial, "%016llx", (long long)be64_to_cpu(ser_buf)); - val->strval = bat_serial; + sprintf(data->bat_serial, "%016llx", (long long)be64_to_cpu(ser_buf)); + val->strval = data->bat_serial; break; case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: ret = olpc_bat_get_voltage_max_design(val); @@ -536,7 +551,7 @@ static ssize_t olpc_bat_eeprom_read(struct file *filp, struct kobject *kobj, return count; } -static const struct bin_attribute olpc_bat_eeprom = { +static struct bin_attribute olpc_bat_eeprom = { .attr = { .name = "eeprom", .mode = S_IRUGO, @@ -560,7 +575,7 @@ static ssize_t olpc_bat_error_read(struct device *dev, return sprintf(buf, "%d\n", ec_byte); } -static const struct device_attribute olpc_bat_error = { +static struct device_attribute olpc_bat_error = { .attr = { .name = "error", .mode = S_IRUGO, @@ -568,6 +583,27 @@ static const struct device_attribute olpc_bat_error = { .show = olpc_bat_error_read, }; +static struct attribute *olpc_bat_sysfs_attrs[] = { + &olpc_bat_error.attr, + NULL +}; + +static struct bin_attribute *olpc_bat_sysfs_bin_attrs[] = { + &olpc_bat_eeprom, + NULL +}; + +static const struct attribute_group olpc_bat_sysfs_group = { + .attrs = olpc_bat_sysfs_attrs, + .bin_attrs = olpc_bat_sysfs_bin_attrs, + +}; + +static const struct attribute_group *olpc_bat_sysfs_groups[] = { + &olpc_bat_sysfs_group, + NULL +}; + /********************************************************************* * Initialisation *********************************************************************/ @@ -578,17 +614,17 @@ static struct power_supply_desc olpc_bat_desc = { .use_for_apm = 1, }; -static struct power_supply *olpc_bat; - static int olpc_battery_suspend(struct platform_device *pdev, pm_message_t state) { - if (device_may_wakeup(&olpc_ac->dev)) + struct olpc_battery_data *data = platform_get_drvdata(pdev); + + if (device_may_wakeup(&data->olpc_ac->dev)) olpc_ec_wakeup_set(EC_SCI_SRC_ACPWR); else olpc_ec_wakeup_clear(EC_SCI_SRC_ACPWR); - if (device_may_wakeup(&olpc_bat->dev)) + if (device_may_wakeup(&data->olpc_bat->dev)) olpc_ec_wakeup_set(EC_SCI_SRC_BATTERY | EC_SCI_SRC_BATSOC | EC_SCI_SRC_BATERR); else @@ -600,16 +636,37 @@ static int olpc_battery_suspend(struct platform_device *pdev, static int olpc_battery_probe(struct platform_device *pdev) { - int ret; + struct power_supply_config bat_psy_cfg = {}; + struct power_supply_config ac_psy_cfg = {}; + struct olpc_battery_data *data; uint8_t status; + uint8_t ecver; + int ret; + + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + platform_set_drvdata(pdev, data); - /* - * We've seen a number of EC protocol changes; this driver requires - * the latest EC protocol, supported by 0x44 and above. - */ - if (olpc_platform_info.ecver < 0x44) { + /* See if the EC is already there and get the EC revision */ + ret = olpc_ec_cmd(EC_FIRMWARE_REV, NULL, 0, &ecver, 1); + if (ret) + return ret; + + if (of_find_compatible_node(NULL, NULL, "olpc,xo1.75-ec")) { + /* XO 1.75 */ + data->new_proto = true; + data->little_endian = true; + } else if (ecver > 0x44) { + /* XO 1 or 1.5 with a new EC firmware. */ + data->new_proto = true; + } else if (ecver < 0x44) { + /* + * We've seen a number of EC protocol changes; this driver + * requires the latest EC protocol, supported by 0x44 and above. + */ printk(KERN_NOTICE "OLPC EC version 0x%02x too old for " - "battery driver.\n", olpc_platform_info.ecver); + "battery driver.\n", ecver); return -ENXIO; } @@ -619,59 +676,44 @@ static int olpc_battery_probe(struct platform_device *pdev) /* Ignore the status. It doesn't actually matter */ - olpc_ac = power_supply_register(&pdev->dev, &olpc_ac_desc, NULL); - if (IS_ERR(olpc_ac)) - return PTR_ERR(olpc_ac); + ac_psy_cfg.of_node = pdev->dev.of_node; + ac_psy_cfg.drv_data = data; + + data->olpc_ac = devm_power_supply_register(&pdev->dev, &olpc_ac_desc, + &ac_psy_cfg); + if (IS_ERR(data->olpc_ac)) + return PTR_ERR(data->olpc_ac); - if (olpc_board_at_least(olpc_board_pre(0xd0))) { /* XO-1.5 */ + if (of_device_is_compatible(pdev->dev.of_node, "olpc,xo1.5-battery")) { + /* XO-1.5 */ olpc_bat_desc.properties = olpc_xo15_bat_props; olpc_bat_desc.num_properties = ARRAY_SIZE(olpc_xo15_bat_props); - } else { /* XO-1 */ + } else { + /* XO-1 */ olpc_bat_desc.properties = olpc_xo1_bat_props; olpc_bat_desc.num_properties = ARRAY_SIZE(olpc_xo1_bat_props); } - olpc_bat = power_supply_register(&pdev->dev, &olpc_bat_desc, NULL); - if (IS_ERR(olpc_bat)) { - ret = PTR_ERR(olpc_bat); - goto battery_failed; - } - - ret = device_create_bin_file(&olpc_bat->dev, &olpc_bat_eeprom); - if (ret) - goto eeprom_failed; + bat_psy_cfg.of_node = pdev->dev.of_node; + bat_psy_cfg.drv_data = data; + bat_psy_cfg.attr_grp = olpc_bat_sysfs_groups; - ret = device_create_file(&olpc_bat->dev, &olpc_bat_error); - if (ret) - goto error_failed; + data->olpc_bat = devm_power_supply_register(&pdev->dev, &olpc_bat_desc, + &bat_psy_cfg); + if (IS_ERR(data->olpc_bat)) + return PTR_ERR(data->olpc_bat); if (olpc_ec_wakeup_available()) { - device_set_wakeup_capable(&olpc_ac->dev, true); - device_set_wakeup_capable(&olpc_bat->dev, true); + device_set_wakeup_capable(&data->olpc_ac->dev, true); + device_set_wakeup_capable(&data->olpc_bat->dev, true); } return 0; - -error_failed: - device_remove_bin_file(&olpc_bat->dev, &olpc_bat_eeprom); -eeprom_failed: - power_supply_unregister(olpc_bat); -battery_failed: - power_supply_unregister(olpc_ac); - return ret; -} - -static int olpc_battery_remove(struct platform_device *pdev) -{ - device_remove_file(&olpc_bat->dev, &olpc_bat_error); - device_remove_bin_file(&olpc_bat->dev, &olpc_bat_eeprom); - power_supply_unregister(olpc_bat); - power_supply_unregister(olpc_ac); - return 0; } static const struct of_device_id olpc_battery_ids[] = { { .compatible = "olpc,xo1-battery" }, + { .compatible = "olpc,xo1.5-battery" }, {} }; MODULE_DEVICE_TABLE(of, olpc_battery_ids); @@ -682,7 +724,6 @@ static struct platform_driver olpc_battery_driver = { .of_match_table = olpc_battery_ids, }, .probe = olpc_battery_probe, - .remove = olpc_battery_remove, .suspend = olpc_battery_suspend, }; diff --git a/drivers/power/supply/power_supply_core.c b/drivers/power/supply/power_supply_core.c index c917a8b43b2b..f7033ecf6d0b 100644 --- a/drivers/power/supply/power_supply_core.c +++ b/drivers/power/supply/power_supply_core.c @@ -598,10 +598,12 @@ int power_supply_get_battery_info(struct power_supply *psy, err = of_property_read_string(battery_np, "compatible", &value); if (err) - return err; + goto out_put_node; - if (strcmp("simple-battery", value)) - return -ENODEV; + if (strcmp("simple-battery", value)) { + err = -ENODEV; + goto out_put_node; + } /* The property and field names below must correspond to elements * in enum power_supply_property. For reasoning, see @@ -620,19 +622,21 @@ int power_supply_get_battery_info(struct power_supply *psy, &info->precharge_current_ua); of_property_read_u32(battery_np, "charge-term-current-microamp", &info->charge_term_current_ua); - of_property_read_u32(battery_np, "constant_charge_current_max_microamp", + of_property_read_u32(battery_np, "constant-charge-current-max-microamp", &info->constant_charge_current_max_ua); - of_property_read_u32(battery_np, "constant_charge_voltage_max_microvolt", + of_property_read_u32(battery_np, "constant-charge-voltage-max-microvolt", &info->constant_charge_voltage_max_uv); of_property_read_u32(battery_np, "factory-internal-resistance-micro-ohms", &info->factory_internal_resistance_uohm); len = of_property_count_u32_elems(battery_np, "ocv-capacity-celsius"); if (len < 0 && len != -EINVAL) { - return len; + err = len; + goto out_put_node; } else if (len > POWER_SUPPLY_OCV_TEMP_MAX) { dev_err(&psy->dev, "Too many temperature values\n"); - return -EINVAL; + err = -EINVAL; + goto out_put_node; } else if (len > 0) { of_property_read_u32_array(battery_np, "ocv-capacity-celsius", info->ocv_temp, len); @@ -650,7 +654,8 @@ int power_supply_get_battery_info(struct power_supply *psy, dev_err(&psy->dev, "failed to get %s\n", propname); kfree(propname); power_supply_put_battery_info(psy, info); - return -EINVAL; + err = -EINVAL; + goto out_put_node; } kfree(propname); @@ -661,16 +666,21 @@ int power_supply_get_battery_info(struct power_supply *psy, devm_kcalloc(&psy->dev, tab_len, sizeof(*table), GFP_KERNEL); if (!info->ocv_table[index]) { power_supply_put_battery_info(psy, info); - return -ENOMEM; + err = -ENOMEM; + goto out_put_node; } for (i = 0; i < tab_len; i++) { - table[i].ocv = be32_to_cpu(*list++); - table[i].capacity = be32_to_cpu(*list++); + table[i].ocv = be32_to_cpu(*list); + list++; + table[i].capacity = be32_to_cpu(*list); + list++; } } - return 0; +out_put_node: + of_node_put(battery_np); + return err; } EXPORT_SYMBOL_GPL(power_supply_get_battery_info); @@ -899,7 +909,7 @@ static int ps_get_max_charge_cntl_limit(struct thermal_cooling_device *tcd, return ret; } -static int ps_get_cur_chrage_cntl_limit(struct thermal_cooling_device *tcd, +static int ps_get_cur_charge_cntl_limit(struct thermal_cooling_device *tcd, unsigned long *state) { struct power_supply *psy; @@ -934,7 +944,7 @@ static int ps_set_cur_charge_cntl_limit(struct thermal_cooling_device *tcd, static const struct thermal_cooling_device_ops psy_tcd_ops = { .get_max_state = ps_get_max_charge_cntl_limit, - .get_cur_state = ps_get_cur_chrage_cntl_limit, + .get_cur_state = ps_get_cur_charge_cntl_limit, .set_cur_state = ps_set_cur_charge_cntl_limit, }; diff --git a/drivers/power/supply/power_supply_sysfs.c b/drivers/power/supply/power_supply_sysfs.c index 5358a80d854f..a704a76d7529 100644 --- a/drivers/power/supply/power_supply_sysfs.c +++ b/drivers/power/supply/power_supply_sysfs.c @@ -56,13 +56,13 @@ static const char * const power_supply_status_text[] = { }; static const char * const power_supply_charge_type_text[] = { - "Unknown", "N/A", "Trickle", "Fast" + "Unknown", "N/A", "Trickle", "Fast", "Standard", "Adaptive", "Custom" }; static const char * const power_supply_health_text[] = { "Unknown", "Good", "Overheat", "Dead", "Over voltage", "Unspecified failure", "Cold", "Watchdog timer expire", - "Safety timer expire" + "Safety timer expire", "Over current" }; static const char * const power_supply_technology_text[] = { @@ -274,6 +274,8 @@ static struct device_attribute power_supply_attrs[] = { POWER_SUPPLY_ATTR(constant_charge_voltage_max), POWER_SUPPLY_ATTR(charge_control_limit), POWER_SUPPLY_ATTR(charge_control_limit_max), + POWER_SUPPLY_ATTR(charge_control_start_threshold), + POWER_SUPPLY_ATTR(charge_control_end_threshold), POWER_SUPPLY_ATTR(input_current_limit), POWER_SUPPLY_ATTR(energy_full_design), POWER_SUPPLY_ATTR(energy_empty_design), diff --git a/drivers/power/supply/ucs1002_power.c b/drivers/power/supply/ucs1002_power.c new file mode 100644 index 000000000000..1c89d030c045 --- /dev/null +++ b/drivers/power/supply/ucs1002_power.c @@ -0,0 +1,646 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Driver for UCS1002 Programmable USB Port Power Controller + * + * Copyright (C) 2019 Zodiac Inflight Innovations + */ +#include <linux/bits.h> +#include <linux/freezer.h> +#include <linux/gpio/consumer.h> +#include <linux/i2c.h> +#include <linux/interrupt.h> +#include <linux/kernel.h> +#include <linux/kthread.h> +#include <linux/device.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_irq.h> +#include <linux/power_supply.h> +#include <linux/regmap.h> +#include <linux/regulator/driver.h> +#include <linux/regulator/of_regulator.h> + +/* UCS1002 Registers */ +#define UCS1002_REG_CURRENT_MEASUREMENT 0x00 + +/* + * The Total Accumulated Charge registers store the total accumulated + * charge delivered from the VS source to a portable device. The total + * value is calculated using four registers, from 01h to 04h. The bit + * weighting of the registers is given in mA/hrs. + */ +#define UCS1002_REG_TOTAL_ACC_CHARGE 0x01 + +/* Other Status Register */ +#define UCS1002_REG_OTHER_STATUS 0x0f +# define F_ADET_PIN BIT(4) +# define F_CHG_ACT BIT(3) + +/* Interrupt Status */ +#define UCS1002_REG_INTERRUPT_STATUS 0x10 +# define F_DISCHARGE_ERR BIT(6) +# define F_RESET BIT(5) +# define F_MIN_KEEP_OUT BIT(4) +# define F_TSD BIT(3) +# define F_OVER_VOLT BIT(2) +# define F_BACK_VOLT BIT(1) +# define F_OVER_ILIM BIT(0) + +/* Pin Status Register */ +#define UCS1002_REG_PIN_STATUS 0x14 +# define UCS1002_PWR_STATE_MASK 0x03 +# define F_PWR_EN_PIN BIT(6) +# define F_M2_PIN BIT(5) +# define F_M1_PIN BIT(4) +# define F_EM_EN_PIN BIT(3) +# define F_SEL_PIN BIT(2) +# define F_ACTIVE_MODE_MASK GENMASK(5, 3) +# define F_ACTIVE_MODE_PASSTHROUGH F_M2_PIN +# define F_ACTIVE_MODE_DEDICATED F_EM_EN_PIN +# define F_ACTIVE_MODE_BC12_DCP (F_M2_PIN | F_EM_EN_PIN) +# define F_ACTIVE_MODE_BC12_SDP F_M1_PIN +# define F_ACTIVE_MODE_BC12_CDP (F_M1_PIN | F_M2_PIN | F_EM_EN_PIN) + +/* General Configuration Register */ +#define UCS1002_REG_GENERAL_CFG 0x15 +# define F_RATION_EN BIT(3) + +/* Emulation Configuration Register */ +#define UCS1002_REG_EMU_CFG 0x16 + +/* Switch Configuration Register */ +#define UCS1002_REG_SWITCH_CFG 0x17 +# define F_PIN_IGNORE BIT(7) +# define F_EM_EN_SET BIT(5) +# define F_M2_SET BIT(4) +# define F_M1_SET BIT(3) +# define F_S0_SET BIT(2) +# define F_PWR_EN_SET BIT(1) +# define F_LATCH_SET BIT(0) +# define V_SET_ACTIVE_MODE_MASK GENMASK(5, 3) +# define V_SET_ACTIVE_MODE_PASSTHROUGH F_M2_SET +# define V_SET_ACTIVE_MODE_DEDICATED F_EM_EN_SET +# define V_SET_ACTIVE_MODE_BC12_DCP (F_M2_SET | F_EM_EN_SET) +# define V_SET_ACTIVE_MODE_BC12_SDP F_M1_SET +# define V_SET_ACTIVE_MODE_BC12_CDP (F_M1_SET | F_M2_SET | F_EM_EN_SET) + +/* Current Limit Register */ +#define UCS1002_REG_ILIMIT 0x19 +# define UCS1002_ILIM_SW_MASK GENMASK(3, 0) + +/* Product ID */ +#define UCS1002_REG_PRODUCT_ID 0xfd +# define UCS1002_PRODUCT_ID 0x4e + +/* Manufacture name */ +#define UCS1002_MANUFACTURER "SMSC" + +struct ucs1002_info { + struct power_supply *charger; + struct i2c_client *client; + struct regmap *regmap; + struct regulator_desc *regulator_descriptor; + bool present; +}; + +static enum power_supply_property ucs1002_props[] = { + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_CHARGE_NOW, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_CURRENT_MAX, + POWER_SUPPLY_PROP_PRESENT, /* the presence of PED */ + POWER_SUPPLY_PROP_MANUFACTURER, + POWER_SUPPLY_PROP_USB_TYPE, + POWER_SUPPLY_PROP_HEALTH, +}; + +static int ucs1002_get_online(struct ucs1002_info *info, + union power_supply_propval *val) +{ + unsigned int reg; + int ret; + + ret = regmap_read(info->regmap, UCS1002_REG_OTHER_STATUS, ®); + if (ret) + return ret; + + val->intval = !!(reg & F_CHG_ACT); + + return 0; +} + +static int ucs1002_get_charge(struct ucs1002_info *info, + union power_supply_propval *val) +{ + /* + * To fit within 32 bits some values are rounded (uA/h) + * + * For Total Accumulated Charge Middle Low Byte register, addr + * 03h, byte 2 + * + * B0: 0.01084 mA/h rounded to 11 uA/h + * B1: 0.02169 mA/h rounded to 22 uA/h + * B2: 0.04340 mA/h rounded to 43 uA/h + * B3: 0.08676 mA/h rounded to 87 uA/h + * B4: 0.17350 mA/h rounded to 173 uÁ/h + * + * For Total Accumulated Charge Low Byte register, addr 04h, + * byte 3 + * + * B6: 0.00271 mA/h rounded to 3 uA/h + * B7: 0.005422 mA/h rounded to 5 uA/h + */ + static const int bit_weights_uAh[BITS_PER_TYPE(u32)] = { + /* + * Bit corresponding to low byte (offset 0x04) + * B0 B1 B2 B3 B4 B5 B6 B7 + */ + 0, 0, 0, 0, 0, 0, 3, 5, + /* + * Bit corresponding to middle low byte (offset 0x03) + * B0 B1 B2 B3 B4 B5 B6 B7 + */ + 11, 22, 43, 87, 173, 347, 694, 1388, + /* + * Bit corresponding to middle high byte (offset 0x02) + * B0 B1 B2 B3 B4 B5 B6 B7 + */ + 2776, 5552, 11105, 22210, 44420, 88840, 177700, 355400, + /* + * Bit corresponding to high byte (offset 0x01) + * B0 B1 B2 B3 B4 B5 B6 B7 + */ + 710700, 1421000, 2843000, 5685000, 11371000, 22742000, + 45484000, 90968000, + }; + unsigned long total_acc_charger; + unsigned int reg; + int i, ret; + + ret = regmap_bulk_read(info->regmap, UCS1002_REG_TOTAL_ACC_CHARGE, + ®, sizeof(u32)); + if (ret) + return ret; + + total_acc_charger = be32_to_cpu(reg); /* BE as per offsets above */ + val->intval = 0; + + for_each_set_bit(i, &total_acc_charger, ARRAY_SIZE(bit_weights_uAh)) + val->intval += bit_weights_uAh[i]; + + return 0; +} + +static int ucs1002_get_current(struct ucs1002_info *info, + union power_supply_propval *val) +{ + /* + * The Current Measurement register stores the measured + * current value delivered to the portable device. The range + * is from 9.76 mA to 2.5 A. + */ + static const int bit_weights_uA[BITS_PER_TYPE(u8)] = { + 9760, 19500, 39000, 78100, 156200, 312300, 624600, 1249300, + }; + unsigned long current_measurement; + unsigned int reg; + int i, ret; + + ret = regmap_read(info->regmap, UCS1002_REG_CURRENT_MEASUREMENT, ®); + if (ret) + return ret; + + current_measurement = reg; + val->intval = 0; + + for_each_set_bit(i, ¤t_measurement, ARRAY_SIZE(bit_weights_uA)) + val->intval += bit_weights_uA[i]; + + return 0; +} + +/* + * The Current Limit register stores the maximum current used by the + * port switch. The range is from 500mA to 2.5 A. + */ +static const u32 ucs1002_current_limit_uA[] = { + 500000, 900000, 1000000, 1200000, 1500000, 1800000, 2000000, 2500000, +}; + +static int ucs1002_get_max_current(struct ucs1002_info *info, + union power_supply_propval *val) +{ + unsigned int reg; + int ret; + + ret = regmap_read(info->regmap, UCS1002_REG_ILIMIT, ®); + if (ret) + return ret; + + val->intval = ucs1002_current_limit_uA[reg & UCS1002_ILIM_SW_MASK]; + + return 0; +} + +static int ucs1002_set_max_current(struct ucs1002_info *info, u32 val) +{ + unsigned int reg; + int ret, idx; + + for (idx = 0; idx < ARRAY_SIZE(ucs1002_current_limit_uA); idx++) { + if (val == ucs1002_current_limit_uA[idx]) + break; + } + + if (idx == ARRAY_SIZE(ucs1002_current_limit_uA)) + return -EINVAL; + + ret = regmap_write(info->regmap, UCS1002_REG_ILIMIT, idx); + if (ret) + return ret; + /* + * Any current limit setting exceeding the one set via ILIM + * pin will be rejected, so we read out freshly changed limit + * to make sure that it took effect. + */ + ret = regmap_read(info->regmap, UCS1002_REG_ILIMIT, ®); + if (ret) + return ret; + + if (reg != idx) + return -EINVAL; + + return 0; +} + +static enum power_supply_usb_type ucs1002_usb_types[] = { + POWER_SUPPLY_USB_TYPE_PD, + POWER_SUPPLY_USB_TYPE_SDP, + POWER_SUPPLY_USB_TYPE_DCP, + POWER_SUPPLY_USB_TYPE_CDP, + POWER_SUPPLY_USB_TYPE_UNKNOWN, +}; + +static int ucs1002_set_usb_type(struct ucs1002_info *info, int val) +{ + unsigned int mode; + + if (val < 0 || val >= ARRAY_SIZE(ucs1002_usb_types)) + return -EINVAL; + + switch (ucs1002_usb_types[val]) { + case POWER_SUPPLY_USB_TYPE_PD: + mode = V_SET_ACTIVE_MODE_DEDICATED; + break; + case POWER_SUPPLY_USB_TYPE_SDP: + mode = V_SET_ACTIVE_MODE_BC12_SDP; + break; + case POWER_SUPPLY_USB_TYPE_DCP: + mode = V_SET_ACTIVE_MODE_BC12_DCP; + break; + case POWER_SUPPLY_USB_TYPE_CDP: + mode = V_SET_ACTIVE_MODE_BC12_CDP; + break; + default: + return -EINVAL; + } + + return regmap_update_bits(info->regmap, UCS1002_REG_SWITCH_CFG, + V_SET_ACTIVE_MODE_MASK, mode); +} + +static int ucs1002_get_usb_type(struct ucs1002_info *info, + union power_supply_propval *val) +{ + enum power_supply_usb_type type; + unsigned int reg; + int ret; + + ret = regmap_read(info->regmap, UCS1002_REG_PIN_STATUS, ®); + if (ret) + return ret; + + switch (reg & F_ACTIVE_MODE_MASK) { + default: + type = POWER_SUPPLY_USB_TYPE_UNKNOWN; + break; + case F_ACTIVE_MODE_DEDICATED: + type = POWER_SUPPLY_USB_TYPE_PD; + break; + case F_ACTIVE_MODE_BC12_SDP: + type = POWER_SUPPLY_USB_TYPE_SDP; + break; + case F_ACTIVE_MODE_BC12_DCP: + type = POWER_SUPPLY_USB_TYPE_DCP; + break; + case F_ACTIVE_MODE_BC12_CDP: + type = POWER_SUPPLY_USB_TYPE_CDP; + break; + }; + + val->intval = type; + + return 0; +} + +static int ucs1002_get_health(struct ucs1002_info *info, + union power_supply_propval *val) +{ + unsigned int reg; + int ret, health; + + ret = regmap_read(info->regmap, UCS1002_REG_INTERRUPT_STATUS, ®); + if (ret) + return ret; + + if (reg & F_TSD) + health = POWER_SUPPLY_HEALTH_OVERHEAT; + else if (reg & (F_OVER_VOLT | F_BACK_VOLT)) + health = POWER_SUPPLY_HEALTH_OVERVOLTAGE; + else if (reg & F_OVER_ILIM) + health = POWER_SUPPLY_HEALTH_OVERCURRENT; + else if (reg & (F_DISCHARGE_ERR | F_MIN_KEEP_OUT)) + health = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; + else + health = POWER_SUPPLY_HEALTH_GOOD; + + val->intval = health; + + return 0; +} + +static int ucs1002_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct ucs1002_info *info = power_supply_get_drvdata(psy); + + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + return ucs1002_get_online(info, val); + case POWER_SUPPLY_PROP_CHARGE_NOW: + return ucs1002_get_charge(info, val); + case POWER_SUPPLY_PROP_CURRENT_NOW: + return ucs1002_get_current(info, val); + case POWER_SUPPLY_PROP_CURRENT_MAX: + return ucs1002_get_max_current(info, val); + case POWER_SUPPLY_PROP_USB_TYPE: + return ucs1002_get_usb_type(info, val); + case POWER_SUPPLY_PROP_HEALTH: + return ucs1002_get_health(info, val); + case POWER_SUPPLY_PROP_PRESENT: + val->intval = info->present; + return 0; + case POWER_SUPPLY_PROP_MANUFACTURER: + val->strval = UCS1002_MANUFACTURER; + return 0; + default: + return -EINVAL; + } +} + +static int ucs1002_set_property(struct power_supply *psy, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct ucs1002_info *info = power_supply_get_drvdata(psy); + + switch (psp) { + case POWER_SUPPLY_PROP_CURRENT_MAX: + return ucs1002_set_max_current(info, val->intval); + case POWER_SUPPLY_PROP_USB_TYPE: + return ucs1002_set_usb_type(info, val->intval); + default: + return -EINVAL; + } +} + +static int ucs1002_property_is_writeable(struct power_supply *psy, + enum power_supply_property psp) +{ + switch (psp) { + case POWER_SUPPLY_PROP_CURRENT_MAX: + case POWER_SUPPLY_PROP_USB_TYPE: + return true; + default: + return false; + } +} + +static const struct power_supply_desc ucs1002_charger_desc = { + .name = "ucs1002", + .type = POWER_SUPPLY_TYPE_USB, + .usb_types = ucs1002_usb_types, + .num_usb_types = ARRAY_SIZE(ucs1002_usb_types), + .get_property = ucs1002_get_property, + .set_property = ucs1002_set_property, + .property_is_writeable = ucs1002_property_is_writeable, + .properties = ucs1002_props, + .num_properties = ARRAY_SIZE(ucs1002_props), +}; + +static irqreturn_t ucs1002_charger_irq(int irq, void *data) +{ + int ret, regval; + bool present; + struct ucs1002_info *info = data; + + present = info->present; + + ret = regmap_read(info->regmap, UCS1002_REG_OTHER_STATUS, ®val); + if (ret) + return IRQ_HANDLED; + + /* update attached status */ + info->present = regval & F_ADET_PIN; + + /* notify the change */ + if (present != info->present) + power_supply_changed(info->charger); + + return IRQ_HANDLED; +} + +static irqreturn_t ucs1002_alert_irq(int irq, void *data) +{ + struct ucs1002_info *info = data; + + power_supply_changed(info->charger); + + return IRQ_HANDLED; +} + +static const struct regulator_ops ucs1002_regulator_ops = { + .is_enabled = regulator_is_enabled_regmap, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, +}; + +static const struct regulator_desc ucs1002_regulator_descriptor = { + .name = "ucs1002-vbus", + .ops = &ucs1002_regulator_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + .enable_reg = UCS1002_REG_SWITCH_CFG, + .enable_mask = F_PWR_EN_SET, + .enable_val = F_PWR_EN_SET, + .fixed_uV = 5000000, + .n_voltages = 1, +}; + +static int ucs1002_probe(struct i2c_client *client, + const struct i2c_device_id *dev_id) +{ + struct device *dev = &client->dev; + struct power_supply_config charger_config = {}; + const struct regmap_config regmap_config = { + .reg_bits = 8, + .val_bits = 8, + }; + struct regulator_config regulator_config = {}; + int irq_a_det, irq_alert, ret; + struct regulator_dev *rdev; + struct ucs1002_info *info; + unsigned int regval; + + info = devm_kzalloc(dev, sizeof(*info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + info->regmap = devm_regmap_init_i2c(client, ®map_config); + ret = PTR_ERR_OR_ZERO(info->regmap); + if (ret) { + dev_err(dev, "Regmap initialization failed: %d\n", ret); + return ret; + } + + info->client = client; + + irq_a_det = of_irq_get_byname(dev->of_node, "a_det"); + irq_alert = of_irq_get_byname(dev->of_node, "alert"); + + charger_config.of_node = dev->of_node; + charger_config.drv_data = info; + + ret = regmap_read(info->regmap, UCS1002_REG_PRODUCT_ID, ®val); + if (ret) { + dev_err(dev, "Failed to read product ID: %d\n", ret); + return ret; + } + + if (regval != UCS1002_PRODUCT_ID) { + dev_err(dev, + "Product ID does not match (0x%02x != 0x%02x)\n", + regval, UCS1002_PRODUCT_ID); + return -ENODEV; + } + + /* Enable charge rationing by default */ + ret = regmap_update_bits(info->regmap, UCS1002_REG_GENERAL_CFG, + F_RATION_EN, F_RATION_EN); + if (ret) { + dev_err(dev, "Failed to read general config: %d\n", ret); + return ret; + } + + /* + * Ignore the M1, M2, PWR_EN, and EM_EN pin states. Set active + * mode selection to BC1.2 CDP. + */ + ret = regmap_update_bits(info->regmap, UCS1002_REG_SWITCH_CFG, + V_SET_ACTIVE_MODE_MASK | F_PIN_IGNORE, + V_SET_ACTIVE_MODE_BC12_CDP | F_PIN_IGNORE); + if (ret) { + dev_err(dev, "Failed to configure default mode: %d\n", ret); + return ret; + } + /* + * Be safe and set initial current limit to 500mA + */ + ret = ucs1002_set_max_current(info, 500000); + if (ret) { + dev_err(dev, "Failed to set max current default: %d\n", ret); + return ret; + } + + info->charger = devm_power_supply_register(dev, &ucs1002_charger_desc, + &charger_config); + ret = PTR_ERR_OR_ZERO(info->charger); + if (ret) { + dev_err(dev, "Failed to register power supply: %d\n", ret); + return ret; + } + + ret = regmap_read(info->regmap, UCS1002_REG_PIN_STATUS, ®val); + if (ret) { + dev_err(dev, "Failed to read pin status: %d\n", ret); + return ret; + } + + info->regulator_descriptor = + devm_kmemdup(dev, &ucs1002_regulator_descriptor, + sizeof(ucs1002_regulator_descriptor), + GFP_KERNEL); + if (!info->regulator_descriptor) + return -ENOMEM; + + info->regulator_descriptor->enable_is_inverted = !(regval & F_SEL_PIN); + + regulator_config.dev = dev; + regulator_config.of_node = dev->of_node; + regulator_config.regmap = info->regmap; + + rdev = devm_regulator_register(dev, info->regulator_descriptor, + ®ulator_config); + ret = PTR_ERR_OR_ZERO(rdev); + if (ret) { + dev_err(dev, "Failed to register VBUS regulator: %d\n", ret); + return ret; + } + + if (irq_a_det > 0) { + ret = devm_request_threaded_irq(dev, irq_a_det, NULL, + ucs1002_charger_irq, + IRQF_ONESHOT, + "ucs1002-a_det", info); + if (ret) { + dev_err(dev, "Failed to request A_DET threaded irq: %d\n", + ret); + return ret; + } + } + + if (irq_alert > 0) { + ret = devm_request_threaded_irq(dev, irq_alert, NULL, + ucs1002_alert_irq, + IRQF_ONESHOT, + "ucs1002-alert", info); + if (ret) { + dev_err(dev, "Failed to request ALERT threaded irq: %d\n", + ret); + return ret; + } + } + + return 0; +} + +static const struct of_device_id ucs1002_of_match[] = { + { .compatible = "microchip,ucs1002", }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, ucs1002_of_match); + +static struct i2c_driver ucs1002_driver = { + .driver = { + .name = "ucs1002", + .of_match_table = ucs1002_of_match, + }, + .probe = ucs1002_probe, +}; +module_i2c_driver(ucs1002_driver); + +MODULE_DESCRIPTION("Microchip UCS1002 Programmable USB Port Power Controller"); +MODULE_AUTHOR("Enric Balletbo Serra <enric.balletbo@collabora.com>"); +MODULE_AUTHOR("Andrey Smirnov <andrew.smirnov@gmail.com>"); +MODULE_LICENSE("GPL"); |