From a5afc7605b2ae7fb95be9b94e85179bc4c8aaee4 Mon Sep 17 00:00:00 2001 From: Sebastian Reichel Date: Tue, 13 Apr 2021 01:03:18 +0200 Subject: [PATCH 01/42] dt-bindings: power: supply: charger-manager: Convert to DT schema format Convert the binding to DT schema format. I slightly modified the binding by allowing regulator-[0-9] and cable-[0-9] instead of regulator@[0-9] and cable@[0-9], so that DT compiler does not complain about missing 'reg' property. The driver actually ignores the nodename and can handle both styles. Signed-off-by: Sebastian Reichel Reviewed-by: Rob Herring Signed-off-by: Sebastian Reichel --- .../bindings/power/supply/charger-manager.txt | 91 -------- .../power/supply/charger-manager.yaml | 215 ++++++++++++++++++ 2 files changed, 215 insertions(+), 91 deletions(-) delete mode 100644 Documentation/devicetree/bindings/power/supply/charger-manager.txt create mode 100644 Documentation/devicetree/bindings/power/supply/charger-manager.yaml diff --git a/Documentation/devicetree/bindings/power/supply/charger-manager.txt b/Documentation/devicetree/bindings/power/supply/charger-manager.txt deleted file mode 100644 index b5ae9061b7a0..000000000000 --- a/Documentation/devicetree/bindings/power/supply/charger-manager.txt +++ /dev/null @@ -1,91 +0,0 @@ -charger-manager bindings -~~~~~~~~~~~~~~~~~~~~~~~~ - -Required properties : - - compatible : "charger-manager" - - <>-supply : for regulator consumer, named according to cm-regulator-name - - cm-chargers : name of chargers - - cm-fuel-gauge : name of battery fuel gauge - - subnode : - - cm-regulator-name : name of charger regulator - - subnode : - - cm-cable-name : name of charger cable - one of USB, USB-HOST, - SDP, DCP, CDP, ACA, FAST-CHARGER, SLOW-CHARGER, WPT, - PD, DOCK, JIG, or MECHANICAL - - cm-cable-extcon : name of extcon dev -(optional) - cm-cable-min : minimum current of cable -(optional) - cm-cable-max : maximum current of cable - -Optional properties : - - cm-name : charger manager's name (default : "battery") - - cm-poll-mode : polling mode - 0 for disabled, 1 for always, 2 for when - external power is connected, or 3 for when charging. If not present, - then polling is disabled - - cm-poll-interval : polling interval (in ms) - - cm-battery-stat : battery status - 0 for battery always present, 1 for no - battery, 2 to check presence via fuel gauge, or 3 to check presence - via charger - - cm-fullbatt-vchkdrop-volt : voltage drop (in uV) before restarting charging - - cm-fullbatt-voltage : voltage (in uV) of full battery - - cm-fullbatt-soc : state of charge to consider as full battery - - cm-fullbatt-capacity : capcity (in uAh) to consider as full battery - - cm-thermal-zone : name of external thermometer's thermal zone - - cm-battery-* : threshold battery temperature for charging - -cold : critical cold temperature of battery for charging - -cold-in-minus : flag that cold temperature is in minus degrees - -hot : critical hot temperature of battery for charging - -temp-diff : temperature difference to allow recharging - - cm-dis/charging-max = limits of charging duration - -Deprecated properties: - - cm-num-chargers - - cm-fullbatt-vchkdrop-ms - -Example : - charger-manager@0 { - compatible = "charger-manager"; - chg-reg-supply = <&charger_regulator>; - - cm-name = "battery"; - /* Always polling ON : 30s */ - cm-poll-mode = <1>; - cm-poll-interval = <30000>; - - cm-fullbatt-vchkdrop-volt = <150000>; - cm-fullbatt-soc = <100>; - - cm-battery-stat = <3>; - - cm-chargers = "charger0", "charger1", "charger2"; - - cm-fuel-gauge = "fuelgauge0"; - - cm-thermal-zone = "thermal_zone.1" - /* in deci centigrade */ - cm-battery-cold = <50>; - cm-battery-cold-in-minus; - cm-battery-hot = <800>; - cm-battery-temp-diff = <100>; - - /* Allow charging for 5hr */ - cm-charging-max = <18000000>; - /* Allow discharging for 2hr */ - cm-discharging-max = <7200000>; - - regulator@0 { - cm-regulator-name = "chg-reg"; - cable@0 { - cm-cable-name = "USB"; - cm-cable-extcon = "extcon-dev.0"; - cm-cable-min = <475000>; - cm-cable-max = <500000>; - }; - cable@1 { - cm-cable-name = "SDP"; - cm-cable-extcon = "extcon-dev.0"; - cm-cable-min = <650000>; - cm-cable-max = <675000>; - }; - }; - - }; diff --git a/Documentation/devicetree/bindings/power/supply/charger-manager.yaml b/Documentation/devicetree/bindings/power/supply/charger-manager.yaml new file mode 100644 index 000000000000..c863cfa67865 --- /dev/null +++ b/Documentation/devicetree/bindings/power/supply/charger-manager.yaml @@ -0,0 +1,215 @@ +# SPDX-License-Identifier: GPL-2.0 +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/power/supply/charger-manager.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Charger Manager + +maintainers: + - Sebastian Reichel + +description: | + Binding for the legacy charger manager driver. + Please do not use for new products. + +properties: + compatible: + const: charger-manager + + cm-chargers: + description: name of chargers + $ref: /schemas/types.yaml#/definitions/string-array + + cm-num-chargers: + $ref: /schemas/types.yaml#/definitions/uint32 + deprecated: true + + cm-fuel-gauge: + description: name of battery fuel gauge + $ref: /schemas/types.yaml#/definitions/string + + cm-name: + description: name of the charger manager + default: battery + $ref: /schemas/types.yaml#/definitions/string + + cm-poll-mode: + description: polling mode + default: 0 + enum: + - 0 # disabled + - 1 # always + - 2 # when external power is connected + - 3 # when charging + + cm-poll-interval: + description: polling interval (in ms) + $ref: /schemas/types.yaml#/definitions/uint32 + + cm-battery-stat: + description: battery status + enum: + - 0 # battery always present + - 1 # no battery + - 2 # check presence via fuel gauge + - 3 # check presence via charger + + cm-fullbatt-vchkdrop-volt: + description: voltage drop before restarting charging in uV + $ref: /schemas/types.yaml#/definitions/uint32 + + cm-fullbatt-vchkdrop-ms: + deprecated: true + + cm-fullbatt-voltage: + description: voltage of full battery in uV + $ref: /schemas/types.yaml#/definitions/uint32 + + cm-fullbatt-soc: + description: state of charge to consider as full battery in % + $ref: /schemas/types.yaml#/definitions/uint32 + + cm-fullbatt-capacity: + description: capcity to consider as full battery in uAh + $ref: /schemas/types.yaml#/definitions/uint32 + + cm-thermal-zone: + description: name of external thermometer's thermal zone + $ref: /schemas/types.yaml#/definitions/string + + cm-discharging-max: + description: limits of discharging duration in ms + $ref: /schemas/types.yaml#/definitions/uint32 + + cm-charging-max: + description: limits of charging duration in ms + $ref: /schemas/types.yaml#/definitions/uint32 + + cm-battery-cold: + description: critical cold temperature of battery for charging in deci-degree celsius + $ref: /schemas/types.yaml#/definitions/uint32 + + cm-battery-cold-in-minus: + description: if set cm-battery-cold temperature is in minus degrees + type: boolean + + cm-battery-hot: + description: critical hot temperature of battery for charging in deci-degree celsius + $ref: /schemas/types.yaml#/definitions/uint32 + + cm-battery-temp-diff: + description: temperature difference to allow recharging in deci-degree celsius + $ref: /schemas/types.yaml#/definitions/uint32 + +patternProperties: + "-supply$": + description: regulator consumer, named according to cm-regulator-name + $ref: /schemas/types.yaml#/definitions/phandle + + "^regulator[@-][0-9]$": + type: object + properties: + cm-regulator-name: + description: name of charger regulator + $ref: /schemas/types.yaml#/definitions/string + + required: + - cm-regulator-name + + additionalProperties: false + + patternProperties: + "^cable[@-][0-9]$": + type: object + properties: + cm-cable-name: + description: name of charger cable + enum: + - USB + - USB-HOST + - SDP + - DCP + - CDP + - ACA + - FAST-CHARGER + - SLOW-CHARGER + - WPT + - PD + - DOCK + - JIG + - MECHANICAL + + cm-cable-extcon: + description: name of extcon dev + $ref: /schemas/types.yaml#/definitions/string + + cm-cable-min: + description: minimum current of cable in uA + $ref: /schemas/types.yaml#/definitions/uint32 + + cm-cable-max: + description: maximum current of cable in uA + $ref: /schemas/types.yaml#/definitions/uint32 + + required: + - cm-cable-name + - cm-cable-extcon + + additionalProperties: false + +required: + - compatible + - cm-chargers + - cm-fuel-gauge + +additionalProperties: false + +examples: + - | + charger-manager { + compatible = "charger-manager"; + chg-reg-supply = <&charger_regulator>; + + cm-name = "battery"; + /* Always polling ON : 30s */ + cm-poll-mode = <1>; + cm-poll-interval = <30000>; + + cm-fullbatt-vchkdrop-volt = <150000>; + cm-fullbatt-soc = <100>; + + cm-battery-stat = <3>; + + cm-chargers = "charger0", "charger1", "charger2"; + + cm-fuel-gauge = "fuelgauge0"; + + cm-thermal-zone = "thermal_zone.1"; + /* in deci centigrade */ + cm-battery-cold = <50>; + cm-battery-cold-in-minus; + cm-battery-hot = <800>; + cm-battery-temp-diff = <100>; + + /* Allow charging for 5hr */ + cm-charging-max = <18000000>; + /* Allow discharging for 2hr */ + cm-discharging-max = <7200000>; + + regulator-0 { + cm-regulator-name = "chg-reg"; + cable-0 { + cm-cable-name = "USB"; + cm-cable-extcon = "extcon-dev.0"; + cm-cable-min = <475000>; + cm-cable-max = <500000>; + }; + cable-1 { + cm-cable-name = "SDP"; + cm-cable-extcon = "extcon-dev.0"; + cm-cable-min = <650000>; + cm-cable-max = <675000>; + }; + }; + }; From 5e9ae95736841391459e3d5e2609a2197343b039 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Tue, 4 May 2021 08:14:37 -0400 Subject: [PATCH 02/42] MAINTAINERS: power: supply: use Krzysztof Kozlowski's Canonical address Switch to Canonical address in S3C power supply driver, just like in other entries. Cc: Krzysztof Kozlowski Signed-off-by: Krzysztof Kozlowski Signed-off-by: Sebastian Reichel --- MAINTAINERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MAINTAINERS b/MAINTAINERS index bd7aff0c120f..3e90df381516 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -16018,7 +16018,7 @@ W: http://www.ibm.com/developerworks/linux/linux390/ F: drivers/s390/scsi/zfcp_* S3C ADC BATTERY DRIVER -M: Krzysztof Kozlowski +M: Krzysztof Kozlowski L: linux-samsung-soc@vger.kernel.org S: Odd Fixes F: drivers/power/supply/s3c_adc_battery.c From 603fcfb9d4ec1cad8d66d3bb37f3613afa8a661a Mon Sep 17 00:00:00 2001 From: Zou Wei Date: Tue, 11 May 2021 11:37:45 +0800 Subject: [PATCH 03/42] power: supply: sc27xx: Add missing MODULE_DEVICE_TABLE This patch adds missing MODULE_DEVICE_TABLE definition which generates correct modalias for automatic loading of this driver when it is built as an external module. Reported-by: Hulk Robot Signed-off-by: Zou Wei Signed-off-by: Sebastian Reichel --- drivers/power/supply/sc27xx_fuel_gauge.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/power/supply/sc27xx_fuel_gauge.c b/drivers/power/supply/sc27xx_fuel_gauge.c index 9c627618c224..1ae8374e1ceb 100644 --- a/drivers/power/supply/sc27xx_fuel_gauge.c +++ b/drivers/power/supply/sc27xx_fuel_gauge.c @@ -1342,6 +1342,7 @@ static const struct of_device_id sc27xx_fgu_of_match[] = { { .compatible = "sprd,sc2731-fgu", }, { } }; +MODULE_DEVICE_TABLE(of, sc27xx_fgu_of_match); static struct platform_driver sc27xx_fgu_driver = { .probe = sc27xx_fgu_probe, From 2aac79d14d76879c8e307820b31876e315b1b242 Mon Sep 17 00:00:00 2001 From: Zou Wei Date: Tue, 11 May 2021 14:17:12 +0800 Subject: [PATCH 04/42] power: supply: sc2731_charger: Add missing MODULE_DEVICE_TABLE This patch adds missing MODULE_DEVICE_TABLE definition which generates correct modalias for automatic loading of this driver when it is built as an external module. Reported-by: Hulk Robot Signed-off-by: Zou Wei Signed-off-by: Sebastian Reichel --- drivers/power/supply/sc2731_charger.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/power/supply/sc2731_charger.c b/drivers/power/supply/sc2731_charger.c index 335cb857ef30..288b79836c13 100644 --- a/drivers/power/supply/sc2731_charger.c +++ b/drivers/power/supply/sc2731_charger.c @@ -524,6 +524,7 @@ static const struct of_device_id sc2731_charger_of_match[] = { { .compatible = "sprd,sc2731-charger", }, { } }; +MODULE_DEVICE_TABLE(of, sc2731_charger_of_match); static struct platform_driver sc2731_charger_driver = { .driver = { From e633f33d2669cb54db2846f9cde08662d254dbd3 Mon Sep 17 00:00:00 2001 From: Maximilian Luz Date: Tue, 4 May 2021 20:48:13 +0200 Subject: [PATCH 05/42] power: supply: surface_battery: Fix battery event handling The battery subsystem of the Surface Aggregator Module EC requires us to register the battery notifier with instance ID 0. However, battery events are actually sent with the instance ID corresponding to the device, which is nonzero. Thus, the strict-matching approach doesn't work here and will discard events that the driver is expected to handle. To fix this we have to fall back on notifier matching by target-category only and have to manually check the instance ID in the notifier callback. Fixes: 167f77f7d0b3 ("power: supply: Add battery driver for Surface Aggregator Module") Signed-off-by: Maximilian Luz Signed-off-by: Sebastian Reichel --- drivers/power/supply/surface_battery.c | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/drivers/power/supply/surface_battery.c b/drivers/power/supply/surface_battery.c index 7efa431a62b2..5ec2e6bb2465 100644 --- a/drivers/power/supply/surface_battery.c +++ b/drivers/power/supply/surface_battery.c @@ -345,6 +345,16 @@ static u32 spwr_notify_bat(struct ssam_event_notifier *nf, const struct ssam_eve struct spwr_battery_device *bat = container_of(nf, struct spwr_battery_device, notif); int status; + /* + * We cannot use strict matching when registering the notifier as the + * EC expects us to register it against instance ID 0. Strict matching + * would thus drop events, as those may have non-zero instance IDs in + * this subsystem. So we need to check the instance ID of the event + * here manually. + */ + if (event->instance_id != bat->sdev->uid.instance) + return 0; + dev_dbg(&bat->sdev->dev, "power event (cid = %#04x, iid = %#04x, tid = %#04x)\n", event->command_id, event->instance_id, event->target_id); @@ -720,8 +730,8 @@ static void spwr_battery_init(struct spwr_battery_device *bat, struct ssam_devic bat->notif.base.fn = spwr_notify_bat; bat->notif.event.reg = registry; bat->notif.event.id.target_category = sdev->uid.category; - bat->notif.event.id.instance = 0; - bat->notif.event.mask = SSAM_EVENT_MASK_STRICT; + bat->notif.event.id.instance = 0; /* need to register with instance 0 */ + bat->notif.event.mask = SSAM_EVENT_MASK_TARGET; bat->notif.event.flags = SSAM_EVENT_SEQUENCED; bat->psy_desc.name = bat->name; From 94233f11f8b6be9dd78f66cca162de66b7b66ae6 Mon Sep 17 00:00:00 2001 From: Jian Xin Date: Thu, 3 Jun 2021 17:47:18 +0800 Subject: [PATCH 06/42] power: supply: ab8500: Fix typo fix misspelled 'interrupts' Signed-off-by: Jian Xin Signed-off-by: Sebastian Reichel --- drivers/power/supply/ab8500_fg.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/power/supply/ab8500_fg.c b/drivers/power/supply/ab8500_fg.c index 0c7c01a0d979..43b65c0cbade 100644 --- a/drivers/power/supply/ab8500_fg.c +++ b/drivers/power/supply/ab8500_fg.c @@ -59,7 +59,7 @@ ((y1) + ((((y2) - (y1)) * ((x) - (x1))) / ((x2) - (x1)))); /** - * struct ab8500_fg_interrupts - ab8500 fg interupts + * struct ab8500_fg_interrupts - ab8500 fg interrupts * @name: name of the interrupt * @isr function pointer to the isr */ From 1c1f13a006ed0d71bb5664c8b7e3e77a28da3beb Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Sun, 23 May 2021 00:50:39 +0200 Subject: [PATCH 07/42] power: supply: ab8500: Move to componentized binding The driver has problems with the different components of the charging code racing with each other to probe(). This results in all four subdrivers populating battery information to ascertain that it is populated for their own needs for example. Fix this by using component probing and thus expressing to the kernel that these are dependent components. The probes can happen in any order and will only acquire resources such as state container, regulators and interrupts and initialize the data structures, but no execution happens until the .bind() callback is called. The charging driver is the main component and binds first, then bind in order the three subcomponents: ab8500-fg, ab8500-btemp and ab8500-chargalg. Do some housekeeping while we are moving the code around. Like use devm_* for IRQs so as to cut down on some boilerplate. Signed-off-by: Linus Walleij Signed-off-by: Sebastian Reichel --- drivers/power/supply/ab8500-bm.h | 4 + drivers/power/supply/ab8500_btemp.c | 118 ++++---- drivers/power/supply/ab8500_charger.c | 355 ++++++++++++++----------- drivers/power/supply/ab8500_fg.c | 136 +++++----- drivers/power/supply/abx500_chargalg.c | 122 +++++---- 5 files changed, 390 insertions(+), 345 deletions(-) diff --git a/drivers/power/supply/ab8500-bm.h b/drivers/power/supply/ab8500-bm.h index 41c69a4f2a1f..012595a9d269 100644 --- a/drivers/power/supply/ab8500-bm.h +++ b/drivers/power/supply/ab8500-bm.h @@ -730,4 +730,8 @@ int ab8500_bm_of_probe(struct device *dev, struct device_node *np, struct abx500_bm_data *bm); +extern struct platform_driver ab8500_fg_driver; +extern struct platform_driver ab8500_btemp_driver; +extern struct platform_driver abx500_chargalg_driver; + #endif /* _AB8500_CHARGER_H_ */ diff --git a/drivers/power/supply/ab8500_btemp.c b/drivers/power/supply/ab8500_btemp.c index fdfcd59fc43e..3598b5a748e7 100644 --- a/drivers/power/supply/ab8500_btemp.c +++ b/drivers/power/supply/ab8500_btemp.c @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -932,26 +933,6 @@ static int __maybe_unused ab8500_btemp_suspend(struct device *dev) return 0; } -static int ab8500_btemp_remove(struct platform_device *pdev) -{ - struct ab8500_btemp *di = platform_get_drvdata(pdev); - int i, irq; - - /* Disable interrupts */ - for (i = 0; i < ARRAY_SIZE(ab8500_btemp_irq); i++) { - irq = platform_get_irq_byname(pdev, ab8500_btemp_irq[i].name); - free_irq(irq, di); - } - - /* Delete the work queue */ - destroy_workqueue(di->btemp_wq); - - flush_scheduled_work(); - power_supply_unregister(di->btemp_psy); - - return 0; -} - static char *supply_interface[] = { "ab8500_chargalg", "ab8500_fg", @@ -966,6 +947,40 @@ static const struct power_supply_desc ab8500_btemp_desc = { .external_power_changed = ab8500_btemp_external_power_changed, }; +static int ab8500_btemp_bind(struct device *dev, struct device *master, + void *data) +{ + struct ab8500_btemp *di = dev_get_drvdata(dev); + + /* Create a work queue for the btemp */ + di->btemp_wq = + alloc_workqueue("ab8500_btemp_wq", WQ_MEM_RECLAIM, 0); + if (di->btemp_wq == NULL) { + dev_err(dev, "failed to create work queue\n"); + return -ENOMEM; + } + + /* Kick off periodic temperature measurements */ + ab8500_btemp_periodic(di, true); + + return 0; +} + +static void ab8500_btemp_unbind(struct device *dev, struct device *master, + void *data) +{ + struct ab8500_btemp *di = dev_get_drvdata(dev); + + /* Delete the work queue */ + destroy_workqueue(di->btemp_wq); + flush_scheduled_work(); +} + +static const struct component_ops ab8500_btemp_component_ops = { + .bind = ab8500_btemp_bind, + .unbind = ab8500_btemp_unbind, +}; + static int ab8500_btemp_probe(struct platform_device *pdev) { struct device_node *np = pdev->dev.of_node; @@ -1011,14 +1026,6 @@ static int ab8500_btemp_probe(struct platform_device *pdev) psy_cfg.num_supplicants = ARRAY_SIZE(supply_interface); psy_cfg.drv_data = di; - /* Create a work queue for the btemp */ - di->btemp_wq = - alloc_workqueue("ab8500_btemp_wq", WQ_MEM_RECLAIM, 0); - if (di->btemp_wq == NULL) { - dev_err(dev, "failed to create work queue\n"); - return -ENOMEM; - } - /* Init work for measuring temperature periodically */ INIT_DEFERRABLE_WORK(&di->btemp_periodic_work, ab8500_btemp_periodic_work); @@ -1031,7 +1038,7 @@ static int ab8500_btemp_probe(struct platform_device *pdev) AB8500_BTEMP_HIGH_TH, &val); if (ret < 0) { dev_err(dev, "%s ab8500 read failed\n", __func__); - goto free_btemp_wq; + return ret; } switch (val) { case BTEMP_HIGH_TH_57_0: @@ -1050,30 +1057,28 @@ static int ab8500_btemp_probe(struct platform_device *pdev) } /* Register BTEMP power supply class */ - di->btemp_psy = power_supply_register(dev, &ab8500_btemp_desc, - &psy_cfg); + di->btemp_psy = devm_power_supply_register(dev, &ab8500_btemp_desc, + &psy_cfg); if (IS_ERR(di->btemp_psy)) { dev_err(dev, "failed to register BTEMP psy\n"); - ret = PTR_ERR(di->btemp_psy); - goto free_btemp_wq; + return PTR_ERR(di->btemp_psy); } /* Register interrupts */ for (i = 0; i < ARRAY_SIZE(ab8500_btemp_irq); i++) { irq = platform_get_irq_byname(pdev, ab8500_btemp_irq[i].name); - if (irq < 0) { - ret = irq; - goto free_irq; - } + if (irq < 0) + return irq; - ret = request_threaded_irq(irq, NULL, ab8500_btemp_irq[i].isr, + ret = devm_request_threaded_irq(dev, irq, NULL, + ab8500_btemp_irq[i].isr, IRQF_SHARED | IRQF_NO_SUSPEND | IRQF_ONESHOT, ab8500_btemp_irq[i].name, di); if (ret) { dev_err(dev, "failed to request %s IRQ %d: %d\n" , ab8500_btemp_irq[i].name, irq, ret); - goto free_irq; + return ret; } dev_dbg(dev, "Requested %s IRQ %d: %d\n", ab8500_btemp_irq[i].name, irq, ret); @@ -1081,23 +1086,16 @@ static int ab8500_btemp_probe(struct platform_device *pdev) platform_set_drvdata(pdev, di); - /* Kick off periodic temperature measurements */ - ab8500_btemp_periodic(di, true); list_add_tail(&di->node, &ab8500_btemp_list); - return ret; + return component_add(dev, &ab8500_btemp_component_ops); +} -free_irq: - /* We also have to free all successfully registered irqs */ - for (i = i - 1; i >= 0; i--) { - irq = platform_get_irq_byname(pdev, ab8500_btemp_irq[i].name); - free_irq(irq, di); - } +static int ab8500_btemp_remove(struct platform_device *pdev) +{ + component_del(&pdev->dev, &ab8500_btemp_component_ops); - power_supply_unregister(di->btemp_psy); -free_btemp_wq: - destroy_workqueue(di->btemp_wq); - return ret; + return 0; } static SIMPLE_DEV_PM_OPS(ab8500_btemp_pm_ops, ab8500_btemp_suspend, ab8500_btemp_resume); @@ -1107,7 +1105,7 @@ static const struct of_device_id ab8500_btemp_match[] = { { }, }; -static struct platform_driver ab8500_btemp_driver = { +struct platform_driver ab8500_btemp_driver = { .probe = ab8500_btemp_probe, .remove = ab8500_btemp_remove, .driver = { @@ -1116,20 +1114,6 @@ static struct platform_driver ab8500_btemp_driver = { .pm = &ab8500_btemp_pm_ops, }, }; - -static int __init ab8500_btemp_init(void) -{ - return platform_driver_register(&ab8500_btemp_driver); -} - -static void __exit ab8500_btemp_exit(void) -{ - platform_driver_unregister(&ab8500_btemp_driver); -} - -device_initcall(ab8500_btemp_init); -module_exit(ab8500_btemp_exit); - MODULE_LICENSE("GPL v2"); MODULE_AUTHOR("Johan Palsson, Karl Komierowski, Arun R Murthy"); MODULE_ALIAS("platform:ab8500-btemp"); diff --git a/drivers/power/supply/ab8500_charger.c b/drivers/power/supply/ab8500_charger.c index a9be10eb2c22..af32cfae9f19 100644 --- a/drivers/power/supply/ab8500_charger.c +++ b/drivers/power/supply/ab8500_charger.c @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -3276,50 +3277,6 @@ static struct notifier_block charger_nb = { .notifier_call = ab8500_external_charger_prepare, }; -static int ab8500_charger_remove(struct platform_device *pdev) -{ - struct ab8500_charger *di = platform_get_drvdata(pdev); - int i, irq, ret; - - /* Disable AC charging */ - ab8500_charger_ac_en(&di->ac_chg, false, 0, 0); - - /* Disable USB charging */ - ab8500_charger_usb_en(&di->usb_chg, false, 0, 0); - - /* Disable interrupts */ - for (i = 0; i < ARRAY_SIZE(ab8500_charger_irq); i++) { - irq = platform_get_irq_byname(pdev, ab8500_charger_irq[i].name); - free_irq(irq, di); - } - - /* Backup battery voltage and current disable */ - ret = abx500_mask_and_set_register_interruptible(di->dev, - AB8500_RTC, AB8500_RTC_CTRL_REG, RTC_BUP_CH_ENA, 0); - if (ret < 0) - dev_err(di->dev, "%s mask and set failed\n", __func__); - - usb_unregister_notifier(di->usb_phy, &di->nb); - usb_put_phy(di->usb_phy); - - /* Delete the work queue */ - destroy_workqueue(di->charger_wq); - - /* Unregister external charger enable notifier */ - if (!di->ac_chg.enabled) - blocking_notifier_chain_unregister( - &charger_notifier_list, &charger_nb); - - flush_scheduled_work(); - if (di->usb_chg.enabled) - power_supply_unregister(di->usb_chg.psy); - - if (di->ac_chg.enabled && !di->ac_chg.external) - power_supply_unregister(di->ac_chg.psy); - - return 0; -} - static char *supply_interface[] = { "ab8500_chargalg", "ab8500_fg", @@ -3342,13 +3299,100 @@ static const struct power_supply_desc ab8500_usb_chg_desc = { .get_property = ab8500_charger_usb_get_property, }; +static int ab8500_charger_bind(struct device *dev) +{ + struct ab8500_charger *di = dev_get_drvdata(dev); + int ch_stat; + int ret; + + /* Create a work queue for the charger */ + di->charger_wq = alloc_ordered_workqueue("ab8500_charger_wq", + WQ_MEM_RECLAIM); + if (di->charger_wq == NULL) { + dev_err(dev, "failed to create work queue\n"); + return -ENOMEM; + } + + ch_stat = ab8500_charger_detect_chargers(di, false); + + if (ch_stat & AC_PW_CONN) { + if (is_ab8500(di->parent)) + queue_delayed_work(di->charger_wq, + &di->ac_charger_attached_work, + HZ); + } + if (ch_stat & USB_PW_CONN) { + if (is_ab8500(di->parent)) + queue_delayed_work(di->charger_wq, + &di->usb_charger_attached_work, + HZ); + di->vbus_detected = true; + di->vbus_detected_start = true; + queue_work(di->charger_wq, + &di->detect_usb_type_work); + } + + ret = component_bind_all(dev, di); + if (ret) { + dev_err(dev, "can't bind component devices\n"); + return ret; + } + + return 0; +} + +static void ab8500_charger_unbind(struct device *dev) +{ + struct ab8500_charger *di = dev_get_drvdata(dev); + int ret; + + /* Disable AC charging */ + ab8500_charger_ac_en(&di->ac_chg, false, 0, 0); + + /* Disable USB charging */ + ab8500_charger_usb_en(&di->usb_chg, false, 0, 0); + + /* Backup battery voltage and current disable */ + ret = abx500_mask_and_set_register_interruptible(di->dev, + AB8500_RTC, AB8500_RTC_CTRL_REG, RTC_BUP_CH_ENA, 0); + if (ret < 0) + dev_err(di->dev, "%s mask and set failed\n", __func__); + + /* Delete the work queue */ + destroy_workqueue(di->charger_wq); + + flush_scheduled_work(); + + /* Unbind fg, btemp, algorithm */ + component_unbind_all(dev, di); +} + +static const struct component_master_ops ab8500_charger_comp_ops = { + .bind = ab8500_charger_bind, + .unbind = ab8500_charger_unbind, +}; + +static struct platform_driver *const ab8500_charger_component_drivers[] = { + &ab8500_fg_driver, + &ab8500_btemp_driver, + &abx500_chargalg_driver, +}; + +static int ab8500_charger_compare_dev(struct device *dev, void *data) +{ + return dev == data; +} + static int ab8500_charger_probe(struct platform_device *pdev) { - struct device_node *np = pdev->dev.of_node; + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct component_match *match = NULL; struct power_supply_config ac_psy_cfg = {}, usb_psy_cfg = {}; struct ab8500_charger *di; - int irq, i, charger_status, ret = 0, ch_stat; - struct device *dev = &pdev->dev; + int charger_status; + int i, irq; + int ret; di = devm_kzalloc(dev, sizeof(*di), GFP_KERNEL); if (!di) @@ -3393,6 +3437,38 @@ static int ab8500_charger_probe(struct platform_device *pdev) return ret; } + /* + * VDD ADC supply needs to be enabled from this driver when there + * is a charger connected to avoid erroneous BTEMP_HIGH/LOW + * interrupts during charging + */ + di->regu = devm_regulator_get(dev, "vddadc"); + if (IS_ERR(di->regu)) { + ret = PTR_ERR(di->regu); + dev_err(dev, "failed to get vddadc regulator\n"); + return ret; + } + + /* Request interrupts */ + for (i = 0; i < ARRAY_SIZE(ab8500_charger_irq); i++) { + irq = platform_get_irq_byname(pdev, ab8500_charger_irq[i].name); + if (irq < 0) + return irq; + + ret = devm_request_threaded_irq(dev, + irq, NULL, ab8500_charger_irq[i].isr, + IRQF_SHARED | IRQF_NO_SUSPEND | IRQF_ONESHOT, + ab8500_charger_irq[i].name, di); + + if (ret != 0) { + dev_err(dev, "failed to request %s IRQ %d: %d\n" + , ab8500_charger_irq[i].name, irq, ret); + return ret; + } + dev_dbg(dev, "Requested %s IRQ %d: %d\n", + ab8500_charger_irq[i].name, irq, ret); + } + /* initialize lock */ spin_lock_init(&di->usb_state.usb_lock); mutex_init(&di->usb_ipt_crnt_lock); @@ -3422,11 +3498,6 @@ static int ab8500_charger_probe(struct platform_device *pdev) di->ac_chg.enabled = di->bm->ac_enabled; di->ac_chg.external = false; - /*notifier for external charger enabling*/ - if (!di->ac_chg.enabled) - blocking_notifier_chain_register( - &charger_notifier_list, &charger_nb); - /* USB supply */ /* ux500_charger sub-class */ di->usb_chg.ops.enable = &ab8500_charger_usb_en; @@ -3442,14 +3513,6 @@ static int ab8500_charger_probe(struct platform_device *pdev) di->usb_chg.external = false; di->usb_state.usb_current = -1; - /* Create a work queue for the charger */ - di->charger_wq = alloc_ordered_workqueue("ab8500_charger_wq", - WQ_MEM_RECLAIM); - if (di->charger_wq == NULL) { - dev_err(dev, "failed to create work queue\n"); - return -ENOMEM; - } - mutex_init(&di->charger_attached_mutex); /* Init work for HW failure check */ @@ -3500,63 +3563,36 @@ static int ab8500_charger_probe(struct platform_device *pdev) INIT_WORK(&di->check_usb_thermal_prot_work, ab8500_charger_check_usb_thermal_prot_work); - /* - * VDD ADC supply needs to be enabled from this driver when there - * is a charger connected to avoid erroneous BTEMP_HIGH/LOW - * interrupts during charging - */ - di->regu = devm_regulator_get(dev, "vddadc"); - if (IS_ERR(di->regu)) { - ret = PTR_ERR(di->regu); - dev_err(dev, "failed to get vddadc regulator\n"); - goto free_charger_wq; - } - /* Initialize OVV, and other registers */ ret = ab8500_charger_init_hw_registers(di); if (ret) { dev_err(dev, "failed to initialize ABB registers\n"); - goto free_charger_wq; + return ret; } /* Register AC charger class */ if (di->ac_chg.enabled) { - di->ac_chg.psy = power_supply_register(dev, + di->ac_chg.psy = devm_power_supply_register(dev, &ab8500_ac_chg_desc, &ac_psy_cfg); if (IS_ERR(di->ac_chg.psy)) { dev_err(dev, "failed to register AC charger\n"); - ret = PTR_ERR(di->ac_chg.psy); - goto free_charger_wq; + return PTR_ERR(di->ac_chg.psy); } } /* Register USB charger class */ if (di->usb_chg.enabled) { - di->usb_chg.psy = power_supply_register(dev, + di->usb_chg.psy = devm_power_supply_register(dev, &ab8500_usb_chg_desc, &usb_psy_cfg); if (IS_ERR(di->usb_chg.psy)) { dev_err(dev, "failed to register USB charger\n"); - ret = PTR_ERR(di->usb_chg.psy); - goto free_ac; + return PTR_ERR(di->usb_chg.psy); } } - di->usb_phy = usb_get_phy(USB_PHY_TYPE_USB2); - if (IS_ERR_OR_NULL(di->usb_phy)) { - dev_err(dev, "failed to get usb transceiver\n"); - ret = -EINVAL; - goto free_usb; - } - di->nb.notifier_call = ab8500_charger_usb_notifier_call; - ret = usb_register_notifier(di->usb_phy, &di->nb); - if (ret) { - dev_err(dev, "failed to register usb notifier\n"); - goto put_usb_phy; - } - /* Identify the connected charger types during startup */ charger_status = ab8500_charger_detect_chargers(di, true); if (charger_status & AC_PW_CONN) { @@ -3566,78 +3602,86 @@ static int ab8500_charger_probe(struct platform_device *pdev) sysfs_notify(&di->ac_chg.psy->dev.kobj, NULL, "present"); } - if (charger_status & USB_PW_CONN) { - di->vbus_detected = true; - di->vbus_detected_start = true; - queue_work(di->charger_wq, - &di->detect_usb_type_work); - } - - /* Register interrupts */ - for (i = 0; i < ARRAY_SIZE(ab8500_charger_irq); i++) { - irq = platform_get_irq_byname(pdev, ab8500_charger_irq[i].name); - if (irq < 0) { - ret = irq; - goto free_irq; - } - - ret = request_threaded_irq(irq, NULL, ab8500_charger_irq[i].isr, - IRQF_SHARED | IRQF_NO_SUSPEND | IRQF_ONESHOT, - ab8500_charger_irq[i].name, di); - - if (ret != 0) { - dev_err(dev, "failed to request %s IRQ %d: %d\n" - , ab8500_charger_irq[i].name, irq, ret); - goto free_irq; - } - dev_dbg(dev, "Requested %s IRQ %d: %d\n", - ab8500_charger_irq[i].name, irq, ret); - } - platform_set_drvdata(pdev, di); - mutex_lock(&di->charger_attached_mutex); + /* Create something that will match the subdrivers when we bind */ + for (i = 0; i < ARRAY_SIZE(ab8500_charger_component_drivers); i++) { + struct device_driver *drv = &ab8500_charger_component_drivers[i]->driver; + struct device *p = NULL, *d; - ch_stat = ab8500_charger_detect_chargers(di, false); - - if ((ch_stat & AC_PW_CONN) == AC_PW_CONN) { - if (is_ab8500(di->parent)) - queue_delayed_work(di->charger_wq, - &di->ac_charger_attached_work, - HZ); + while ((d = platform_find_device_by_driver(p, drv))) { + put_device(p); + component_match_add(dev, &match, + ab8500_charger_compare_dev, d); + p = d; + } + put_device(p); } - if ((ch_stat & USB_PW_CONN) == USB_PW_CONN) { - if (is_ab8500(di->parent)) - queue_delayed_work(di->charger_wq, - &di->usb_charger_attached_work, - HZ); + if (!match) { + dev_err(dev, "no matching components\n"); + return -ENODEV; + } + if (IS_ERR(match)) { + dev_err(dev, "could not create component match\n"); + return PTR_ERR(match); } - mutex_unlock(&di->charger_attached_mutex); + /* Notifier for external charger enabling */ + if (!di->ac_chg.enabled) + blocking_notifier_chain_register( + &charger_notifier_list, &charger_nb); - return ret; -free_irq: + di->usb_phy = usb_get_phy(USB_PHY_TYPE_USB2); + if (IS_ERR_OR_NULL(di->usb_phy)) { + dev_err(dev, "failed to get usb transceiver\n"); + ret = -EINVAL; + goto out_charger_notifier; + } + di->nb.notifier_call = ab8500_charger_usb_notifier_call; + ret = usb_register_notifier(di->usb_phy, &di->nb); + if (ret) { + dev_err(dev, "failed to register usb notifier\n"); + goto put_usb_phy; + } + + + ret = component_master_add_with_match(&pdev->dev, + &ab8500_charger_comp_ops, + match); + if (ret) { + dev_err(dev, "failed to add component master\n"); + goto free_notifier; + } + + return 0; + +free_notifier: usb_unregister_notifier(di->usb_phy, &di->nb); - - /* We also have to free all successfully registered irqs */ - for (i = i - 1; i >= 0; i--) { - irq = platform_get_irq_byname(pdev, ab8500_charger_irq[i].name); - free_irq(irq, di); - } put_usb_phy: usb_put_phy(di->usb_phy); -free_usb: - if (di->usb_chg.enabled) - power_supply_unregister(di->usb_chg.psy); -free_ac: - if (di->ac_chg.enabled) - power_supply_unregister(di->ac_chg.psy); -free_charger_wq: - destroy_workqueue(di->charger_wq); +out_charger_notifier: + if (!di->ac_chg.enabled) + blocking_notifier_chain_unregister( + &charger_notifier_list, &charger_nb); return ret; } +static int ab8500_charger_remove(struct platform_device *pdev) +{ + struct ab8500_charger *di = platform_get_drvdata(pdev); + + component_master_del(&pdev->dev, &ab8500_charger_comp_ops); + + usb_unregister_notifier(di->usb_phy, &di->nb); + usb_put_phy(di->usb_phy); + if (!di->ac_chg.enabled) + blocking_notifier_chain_unregister( + &charger_notifier_list, &charger_nb); + + return 0; +} + static SIMPLE_DEV_PM_OPS(ab8500_charger_pm_ops, ab8500_charger_suspend, ab8500_charger_resume); static const struct of_device_id ab8500_charger_match[] = { @@ -3657,15 +3701,24 @@ static struct platform_driver ab8500_charger_driver = { static int __init ab8500_charger_init(void) { + int ret; + + ret = platform_register_drivers(ab8500_charger_component_drivers, + ARRAY_SIZE(ab8500_charger_component_drivers)); + if (ret) + return ret; + return platform_driver_register(&ab8500_charger_driver); } static void __exit ab8500_charger_exit(void) { + platform_unregister_drivers(ab8500_charger_component_drivers, + ARRAY_SIZE(ab8500_charger_component_drivers)); platform_driver_unregister(&ab8500_charger_driver); } -subsys_initcall_sync(ab8500_charger_init); +module_init(ab8500_charger_init); module_exit(ab8500_charger_exit); MODULE_LICENSE("GPL v2"); diff --git a/drivers/power/supply/ab8500_fg.c b/drivers/power/supply/ab8500_fg.c index 43b65c0cbade..c0ba0dfb3fd6 100644 --- a/drivers/power/supply/ab8500_fg.c +++ b/drivers/power/supply/ab8500_fg.c @@ -17,6 +17,7 @@ #include #include +#include #include #include #include @@ -2980,27 +2981,6 @@ static int __maybe_unused ab8500_fg_suspend(struct device *dev) return 0; } -static int ab8500_fg_remove(struct platform_device *pdev) -{ - int ret = 0; - struct ab8500_fg *di = platform_get_drvdata(pdev); - - list_del(&di->node); - - /* Disable coulomb counter */ - ret = ab8500_fg_coulomb_counter(di, false); - if (ret) - dev_err(di->dev, "failed to disable coulomb counter\n"); - - destroy_workqueue(di->fg_wq); - ab8500_fg_sysfs_exit(di); - - flush_scheduled_work(); - ab8500_fg_sysfs_psy_remove_attrs(di); - power_supply_unregister(di->fg_psy); - return ret; -} - /* ab8500 fg driver interrupts and their respective isr */ static struct ab8500_fg_interrupts ab8500_fg_irq[] = { {"NCONV_ACCU", ab8500_fg_cc_convend_handler}, @@ -3024,11 +3004,50 @@ static const struct power_supply_desc ab8500_fg_desc = { .external_power_changed = ab8500_fg_external_power_changed, }; +static int ab8500_fg_bind(struct device *dev, struct device *master, + void *data) +{ + struct ab8500_fg *di = dev_get_drvdata(dev); + + /* Create a work queue for running the FG algorithm */ + di->fg_wq = alloc_ordered_workqueue("ab8500_fg_wq", WQ_MEM_RECLAIM); + if (di->fg_wq == NULL) { + dev_err(dev, "failed to create work queue\n"); + return -ENOMEM; + } + + /* Start the coulomb counter */ + ab8500_fg_coulomb_counter(di, true); + /* Run the FG algorithm */ + queue_delayed_work(di->fg_wq, &di->fg_periodic_work, 0); + + return 0; +} + +static void ab8500_fg_unbind(struct device *dev, struct device *master, + void *data) +{ + struct ab8500_fg *di = dev_get_drvdata(dev); + int ret; + + /* Disable coulomb counter */ + ret = ab8500_fg_coulomb_counter(di, false); + if (ret) + dev_err(dev, "failed to disable coulomb counter\n"); + + destroy_workqueue(di->fg_wq); + flush_scheduled_work(); +} + +static const struct component_ops ab8500_fg_component_ops = { + .bind = ab8500_fg_bind, + .unbind = ab8500_fg_unbind, +}; + static int ab8500_fg_probe(struct platform_device *pdev) { - struct device_node *np = pdev->dev.of_node; - struct power_supply_config psy_cfg = {}; struct device *dev = &pdev->dev; + struct power_supply_config psy_cfg = {}; struct ab8500_fg *di; int i, irq; int ret = 0; @@ -3074,13 +3093,6 @@ static int ab8500_fg_probe(struct platform_device *pdev) ab8500_fg_charge_state_to(di, AB8500_FG_CHARGE_INIT); ab8500_fg_discharge_state_to(di, AB8500_FG_DISCHARGE_INIT); - /* Create a work queue for running the FG algorithm */ - di->fg_wq = alloc_ordered_workqueue("ab8500_fg_wq", WQ_MEM_RECLAIM); - if (di->fg_wq == NULL) { - dev_err(dev, "failed to create work queue\n"); - return -ENOMEM; - } - /* Init work for running the fg algorithm instantly */ INIT_WORK(&di->fg_work, ab8500_fg_instant_work); @@ -3113,7 +3125,7 @@ static int ab8500_fg_probe(struct platform_device *pdev) ret = ab8500_fg_init_hw_registers(di); if (ret) { dev_err(dev, "failed to initialize registers\n"); - goto free_inst_curr_wq; + return ret; } /* Consider battery unknown until we're informed otherwise */ @@ -3121,15 +3133,13 @@ static int ab8500_fg_probe(struct platform_device *pdev) di->flags.batt_id_received = false; /* Register FG power supply class */ - di->fg_psy = power_supply_register(dev, &ab8500_fg_desc, &psy_cfg); + di->fg_psy = devm_power_supply_register(dev, &ab8500_fg_desc, &psy_cfg); if (IS_ERR(di->fg_psy)) { dev_err(dev, "failed to register FG psy\n"); - ret = PTR_ERR(di->fg_psy); - goto free_inst_curr_wq; + return PTR_ERR(di->fg_psy); } di->fg_samples = SEC_TO_SAMPLE(di->bm->fg_params->init_timer); - ab8500_fg_coulomb_counter(di, true); /* * Initialize completion used to notify completion and start @@ -3141,19 +3151,18 @@ static int ab8500_fg_probe(struct platform_device *pdev) /* Register primary interrupt handlers */ for (i = 0; i < ARRAY_SIZE(ab8500_fg_irq); i++) { irq = platform_get_irq_byname(pdev, ab8500_fg_irq[i].name); - if (irq < 0) { - ret = irq; - goto free_irq; - } + if (irq < 0) + return irq; - ret = request_threaded_irq(irq, NULL, ab8500_fg_irq[i].isr, + ret = devm_request_threaded_irq(dev, irq, NULL, + ab8500_fg_irq[i].isr, IRQF_SHARED | IRQF_NO_SUSPEND | IRQF_ONESHOT, ab8500_fg_irq[i].name, di); if (ret != 0) { dev_err(dev, "failed to request %s IRQ %d: %d\n", ab8500_fg_irq[i].name, irq, ret); - goto free_irq; + return ret; } dev_dbg(dev, "Requested %s IRQ %d: %d\n", ab8500_fg_irq[i].name, irq, ret); @@ -3168,14 +3177,14 @@ static int ab8500_fg_probe(struct platform_device *pdev) ret = ab8500_fg_sysfs_init(di); if (ret) { dev_err(dev, "failed to create sysfs entry\n"); - goto free_irq; + return ret; } ret = ab8500_fg_sysfs_psy_create_attrs(di); if (ret) { dev_err(dev, "failed to create FG psy\n"); ab8500_fg_sysfs_exit(di); - goto free_irq; + return ret; } /* Calibrate the fg first time */ @@ -3185,24 +3194,21 @@ static int ab8500_fg_probe(struct platform_device *pdev) /* Use room temp as default value until we get an update from driver. */ di->bat_temp = 210; - /* Run the FG algorithm */ - queue_delayed_work(di->fg_wq, &di->fg_periodic_work, 0); - list_add_tail(&di->node, &ab8500_fg_list); - return ret; + return component_add(dev, &ab8500_fg_component_ops); +} -free_irq: - /* We also have to free all registered irqs */ - while (--i >= 0) { - /* Last assignment of i from primary interrupt handlers */ - irq = platform_get_irq_byname(pdev, ab8500_fg_irq[i].name); - free_irq(irq, di); - } +static int ab8500_fg_remove(struct platform_device *pdev) +{ + int ret = 0; + struct ab8500_fg *di = platform_get_drvdata(pdev); + + component_del(&pdev->dev, &ab8500_fg_component_ops); + list_del(&di->node); + ab8500_fg_sysfs_exit(di); + ab8500_fg_sysfs_psy_remove_attrs(di); - power_supply_unregister(di->fg_psy); -free_inst_curr_wq: - destroy_workqueue(di->fg_wq); return ret; } @@ -3213,7 +3219,7 @@ static const struct of_device_id ab8500_fg_match[] = { { }, }; -static struct platform_driver ab8500_fg_driver = { +struct platform_driver ab8500_fg_driver = { .probe = ab8500_fg_probe, .remove = ab8500_fg_remove, .driver = { @@ -3222,20 +3228,6 @@ static struct platform_driver ab8500_fg_driver = { .pm = &ab8500_fg_pm_ops, }, }; - -static int __init ab8500_fg_init(void) -{ - return platform_driver_register(&ab8500_fg_driver); -} - -static void __exit ab8500_fg_exit(void) -{ - platform_driver_unregister(&ab8500_fg_driver); -} - -subsys_initcall_sync(ab8500_fg_init); -module_exit(ab8500_fg_exit); - MODULE_LICENSE("GPL v2"); MODULE_AUTHOR("Johan Palsson, Karl Komierowski"); MODULE_ALIAS("platform:ab8500-fg"); diff --git a/drivers/power/supply/abx500_chargalg.c b/drivers/power/supply/abx500_chargalg.c index f5b792243727..599684ce0e4b 100644 --- a/drivers/power/supply/abx500_chargalg.c +++ b/drivers/power/supply/abx500_chargalg.c @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -1943,28 +1944,6 @@ static int __maybe_unused abx500_chargalg_suspend(struct device *dev) return 0; } -static int abx500_chargalg_remove(struct platform_device *pdev) -{ - struct abx500_chargalg *di = platform_get_drvdata(pdev); - - /* sysfs interface to enable/disbale charging from user space */ - abx500_chargalg_sysfs_exit(di); - - hrtimer_cancel(&di->safety_timer); - hrtimer_cancel(&di->maintenance_timer); - - cancel_delayed_work_sync(&di->chargalg_periodic_work); - cancel_delayed_work_sync(&di->chargalg_wd_work); - cancel_work_sync(&di->chargalg_work); - - /* Delete the work queue */ - destroy_workqueue(di->chargalg_wq); - - power_supply_unregister(di->chargalg_psy); - - return 0; -} - static char *supply_interface[] = { "ab8500_fg", }; @@ -1978,29 +1957,70 @@ static const struct power_supply_desc abx500_chargalg_desc = { .external_power_changed = abx500_chargalg_external_power_changed, }; +static int abx500_chargalg_bind(struct device *dev, struct device *master, + void *data) +{ + struct abx500_chargalg *di = dev_get_drvdata(dev); + + /* Create a work queue for the chargalg */ + di->chargalg_wq = alloc_ordered_workqueue("abx500_chargalg_wq", + WQ_MEM_RECLAIM); + if (di->chargalg_wq == NULL) { + dev_err(di->dev, "failed to create work queue\n"); + return -ENOMEM; + } + + /* Run the charging algorithm */ + queue_delayed_work(di->chargalg_wq, &di->chargalg_periodic_work, 0); + + return 0; +} + +static void abx500_chargalg_unbind(struct device *dev, struct device *master, + void *data) +{ + struct abx500_chargalg *di = dev_get_drvdata(dev); + + /* Stop all timers and work */ + hrtimer_cancel(&di->safety_timer); + hrtimer_cancel(&di->maintenance_timer); + + cancel_delayed_work_sync(&di->chargalg_periodic_work); + cancel_delayed_work_sync(&di->chargalg_wd_work); + cancel_work_sync(&di->chargalg_work); + + /* Delete the work queue */ + destroy_workqueue(di->chargalg_wq); + flush_scheduled_work(); +} + +static const struct component_ops abx500_chargalg_component_ops = { + .bind = abx500_chargalg_bind, + .unbind = abx500_chargalg_unbind, +}; + static int abx500_chargalg_probe(struct platform_device *pdev) { - struct device_node *np = pdev->dev.of_node; + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; struct power_supply_config psy_cfg = {}; struct abx500_chargalg *di; int ret = 0; - di = devm_kzalloc(&pdev->dev, sizeof(*di), GFP_KERNEL); - if (!di) { - dev_err(&pdev->dev, "%s no mem for ab8500_chargalg\n", __func__); + di = devm_kzalloc(dev, sizeof(*di), GFP_KERNEL); + if (!di) return -ENOMEM; - } di->bm = &ab8500_bm_data; - ret = ab8500_bm_of_probe(&pdev->dev, np, di->bm); + ret = ab8500_bm_of_probe(dev, np, di->bm); if (ret) { - dev_err(&pdev->dev, "failed to get battery information\n"); + dev_err(dev, "failed to get battery information\n"); return ret; } /* get device struct and parent */ - di->dev = &pdev->dev; + di->dev = dev; di->parent = dev_get_drvdata(pdev->dev.parent); psy_cfg.supplied_to = supply_interface; @@ -2016,14 +2036,6 @@ static int abx500_chargalg_probe(struct platform_device *pdev) di->maintenance_timer.function = abx500_chargalg_maintenance_timer_expired; - /* Create a work queue for the chargalg */ - di->chargalg_wq = alloc_ordered_workqueue("abx500_chargalg_wq", - WQ_MEM_RECLAIM); - if (di->chargalg_wq == NULL) { - dev_err(di->dev, "failed to create work queue\n"); - return -ENOMEM; - } - /* Init work for chargalg */ INIT_DEFERRABLE_WORK(&di->chargalg_periodic_work, abx500_chargalg_periodic_work); @@ -2037,12 +2049,12 @@ static int abx500_chargalg_probe(struct platform_device *pdev) di->chg_info.prev_conn_chg = -1; /* Register chargalg power supply class */ - di->chargalg_psy = power_supply_register(di->dev, &abx500_chargalg_desc, + di->chargalg_psy = devm_power_supply_register(di->dev, + &abx500_chargalg_desc, &psy_cfg); if (IS_ERR(di->chargalg_psy)) { dev_err(di->dev, "failed to register chargalg psy\n"); - ret = PTR_ERR(di->chargalg_psy); - goto free_chargalg_wq; + return PTR_ERR(di->chargalg_psy); } platform_set_drvdata(pdev, di); @@ -2051,21 +2063,24 @@ static int abx500_chargalg_probe(struct platform_device *pdev) ret = abx500_chargalg_sysfs_init(di); if (ret) { dev_err(di->dev, "failed to create sysfs entry\n"); - goto free_psy; + return ret; } di->curr_status.curr_step = CHARGALG_CURR_STEP_HIGH; - /* Run the charging algorithm */ - queue_delayed_work(di->chargalg_wq, &di->chargalg_periodic_work, 0); - dev_info(di->dev, "probe success\n"); - return ret; + return component_add(dev, &abx500_chargalg_component_ops); +} -free_psy: - power_supply_unregister(di->chargalg_psy); -free_chargalg_wq: - destroy_workqueue(di->chargalg_wq); - return ret; +static int abx500_chargalg_remove(struct platform_device *pdev) +{ + struct abx500_chargalg *di = platform_get_drvdata(pdev); + + component_del(&pdev->dev, &abx500_chargalg_component_ops); + + /* sysfs interface to enable/disable charging from user space */ + abx500_chargalg_sysfs_exit(di); + + return 0; } static SIMPLE_DEV_PM_OPS(abx500_chargalg_pm_ops, abx500_chargalg_suspend, abx500_chargalg_resume); @@ -2075,7 +2090,7 @@ static const struct of_device_id ab8500_chargalg_match[] = { { }, }; -static struct platform_driver abx500_chargalg_driver = { +struct platform_driver abx500_chargalg_driver = { .probe = abx500_chargalg_probe, .remove = abx500_chargalg_remove, .driver = { @@ -2084,9 +2099,6 @@ static struct platform_driver abx500_chargalg_driver = { .pm = &abx500_chargalg_pm_ops, }, }; - -module_platform_driver(abx500_chargalg_driver); - MODULE_LICENSE("GPL v2"); MODULE_AUTHOR("Johan Palsson, Karl Komierowski"); MODULE_ALIAS("platform:abx500-chargalg"); From 7e2bb83c617f8fccc04db7d03f105a06b9d491a9 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Sun, 23 May 2021 00:50:40 +0200 Subject: [PATCH 08/42] power: supply: ab8500: Call battery population once The code was calling ab8500_bm_of_probe() in four different spots effectively overwriting the same configuration three times. This was done because probe order was uncertain. Since we now used componentized probe, call it only once while probing the main charging component. Signed-off-by: Linus Walleij Signed-off-by: Sebastian Reichel --- drivers/power/supply/ab8500_btemp.c | 7 ------- drivers/power/supply/ab8500_fg.c | 6 ------ drivers/power/supply/abx500_chargalg.c | 7 ------- 3 files changed, 20 deletions(-) diff --git a/drivers/power/supply/ab8500_btemp.c b/drivers/power/supply/ab8500_btemp.c index 3598b5a748e7..5b664d2f6b82 100644 --- a/drivers/power/supply/ab8500_btemp.c +++ b/drivers/power/supply/ab8500_btemp.c @@ -983,7 +983,6 @@ static const struct component_ops ab8500_btemp_component_ops = { static int ab8500_btemp_probe(struct platform_device *pdev) { - struct device_node *np = pdev->dev.of_node; struct power_supply_config psy_cfg = {}; struct device *dev = &pdev->dev; struct ab8500_btemp *di; @@ -996,12 +995,6 @@ static int ab8500_btemp_probe(struct platform_device *pdev) di->bm = &ab8500_bm_data; - ret = ab8500_bm_of_probe(dev, np, di->bm); - if (ret) { - dev_err(dev, "failed to get battery information\n"); - return ret; - } - /* get parent data */ di->dev = dev; di->parent = dev_get_drvdata(pdev->dev.parent); diff --git a/drivers/power/supply/ab8500_fg.c b/drivers/power/supply/ab8500_fg.c index c0ba0dfb3fd6..4da89920657c 100644 --- a/drivers/power/supply/ab8500_fg.c +++ b/drivers/power/supply/ab8500_fg.c @@ -3058,12 +3058,6 @@ static int ab8500_fg_probe(struct platform_device *pdev) di->bm = &ab8500_bm_data; - ret = ab8500_bm_of_probe(dev, np, di->bm); - if (ret) { - dev_err(dev, "failed to get battery information\n"); - return ret; - } - mutex_init(&di->cc_lock); /* get parent data */ diff --git a/drivers/power/supply/abx500_chargalg.c b/drivers/power/supply/abx500_chargalg.c index 599684ce0e4b..a17849bfacbf 100644 --- a/drivers/power/supply/abx500_chargalg.c +++ b/drivers/power/supply/abx500_chargalg.c @@ -2002,7 +2002,6 @@ static const struct component_ops abx500_chargalg_component_ops = { static int abx500_chargalg_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; - struct device_node *np = dev->of_node; struct power_supply_config psy_cfg = {}; struct abx500_chargalg *di; int ret = 0; @@ -2013,12 +2012,6 @@ static int abx500_chargalg_probe(struct platform_device *pdev) di->bm = &ab8500_bm_data; - ret = ab8500_bm_of_probe(dev, np, di->bm); - if (ret) { - dev_err(dev, "failed to get battery information\n"); - return ret; - } - /* get device struct and parent */ di->dev = dev; di->parent = dev_get_drvdata(pdev->dev.parent); From 5bcb5087c9dd3dca1ff0ebd8002c5313c9332b56 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Sun, 23 May 2021 00:50:41 +0200 Subject: [PATCH 09/42] power: supply: ab8500: Avoid NULL pointers Sometimes the code will crash because we haven't enabled AC or USB charging and thus not created the corresponding psy device. Fix it by checking that it is there before notifying. Signed-off-by: Linus Walleij Signed-off-by: Sebastian Reichel --- drivers/power/supply/ab8500_charger.c | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/drivers/power/supply/ab8500_charger.c b/drivers/power/supply/ab8500_charger.c index af32cfae9f19..57fbfe3d8c03 100644 --- a/drivers/power/supply/ab8500_charger.c +++ b/drivers/power/supply/ab8500_charger.c @@ -415,6 +415,14 @@ disable_otp: static void ab8500_power_supply_changed(struct ab8500_charger *di, struct power_supply *psy) { + /* + * This happens if we get notifications or interrupts and + * the platform has been configured not to support one or + * other type of charging. + */ + if (!psy) + return; + if (di->autopower_cfg) { if (!di->usb.charger_connected && !di->ac.charger_connected && @@ -441,7 +449,15 @@ static void ab8500_charger_set_usb_connected(struct ab8500_charger *di, if (!connected) di->flags.vbus_drop_end = false; - sysfs_notify(&di->usb_chg.psy->dev.kobj, NULL, "present"); + /* + * Sometimes the platform is configured not to support + * USB charging and no psy has been created, but we still + * will get these notifications. + */ + if (di->usb_chg.psy) { + sysfs_notify(&di->usb_chg.psy->dev.kobj, NULL, + "present"); + } if (connected) { mutex_lock(&di->charger_attached_mutex); From f9184a228d7a60ad56b810d549a7debb355f1be6 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Sun, 23 May 2021 00:50:42 +0200 Subject: [PATCH 10/42] power: supply: ab8500: Enable USB and AC The vendor code tree supplies platform data to enable he USB charging for AB8500 and AB8500 and disable the AC charging on the AB8505. This was missed when the driver was submitted to the mainline kernel. Fix this by doing what the vendor kernel does: always register the USB charger, do not register the AC charger on the AB8505. Signed-off-by: Linus Walleij Signed-off-by: Sebastian Reichel --- drivers/power/supply/ab8500-bm.h | 2 -- drivers/power/supply/ab8500_charger.c | 24 ++++++++++++++---------- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/drivers/power/supply/ab8500-bm.h b/drivers/power/supply/ab8500-bm.h index 012595a9d269..871bdc1f5cbd 100644 --- a/drivers/power/supply/ab8500-bm.h +++ b/drivers/power/supply/ab8500-bm.h @@ -507,8 +507,6 @@ struct abx500_bm_data { int bkup_bat_v; int bkup_bat_i; bool autopower_cfg; - bool ac_enabled; - bool usb_enabled; bool no_maintenance; bool capacity_scaling; bool chg_unknown_bat; diff --git a/drivers/power/supply/ab8500_charger.c b/drivers/power/supply/ab8500_charger.c index 57fbfe3d8c03..e6f23ae05f95 100644 --- a/drivers/power/supply/ab8500_charger.c +++ b/drivers/power/supply/ab8500_charger.c @@ -3511,7 +3511,14 @@ static int ab8500_charger_probe(struct platform_device *pdev) di->ac_chg.max_out_curr = di->bm->chg_output_curr[di->bm->n_chg_out_curr - 1]; di->ac_chg.wdt_refresh = CHG_WD_INTERVAL; - di->ac_chg.enabled = di->bm->ac_enabled; + /* + * The AB8505 only supports USB charging. If we are not the + * AB8505, register an AC charger. + * + * TODO: if this should be opt-in, add DT properties for this. + */ + if (!is_ab8505(di->parent)) + di->ac_chg.enabled = true; di->ac_chg.external = false; /* USB supply */ @@ -3525,7 +3532,6 @@ static int ab8500_charger_probe(struct platform_device *pdev) di->usb_chg.max_out_curr = di->bm->chg_output_curr[di->bm->n_chg_out_curr - 1]; di->usb_chg.wdt_refresh = CHG_WD_INTERVAL; - di->usb_chg.enabled = di->bm->usb_enabled; di->usb_chg.external = false; di->usb_state.usb_current = -1; @@ -3599,14 +3605,12 @@ static int ab8500_charger_probe(struct platform_device *pdev) } /* Register USB charger class */ - if (di->usb_chg.enabled) { - di->usb_chg.psy = devm_power_supply_register(dev, - &ab8500_usb_chg_desc, - &usb_psy_cfg); - if (IS_ERR(di->usb_chg.psy)) { - dev_err(dev, "failed to register USB charger\n"); - return PTR_ERR(di->usb_chg.psy); - } + di->usb_chg.psy = devm_power_supply_register(dev, + &ab8500_usb_chg_desc, + &usb_psy_cfg); + if (IS_ERR(di->usb_chg.psy)) { + dev_err(dev, "failed to register USB charger\n"); + return PTR_ERR(di->usb_chg.psy); } /* Identify the connected charger types during startup */ From 9c2b682610a25d36d07afcea939823da230b508b Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Sun, 23 May 2021 00:50:43 +0200 Subject: [PATCH 11/42] power: supply: ab8500: Drop unused member This setting is read directly from the device tree in the ab8500_charger.c code. Signed-off-by: Linus Walleij Signed-off-by: Sebastian Reichel --- drivers/power/supply/ab8500-bm.h | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/power/supply/ab8500-bm.h b/drivers/power/supply/ab8500-bm.h index 871bdc1f5cbd..0c940571e5b0 100644 --- a/drivers/power/supply/ab8500-bm.h +++ b/drivers/power/supply/ab8500-bm.h @@ -506,7 +506,6 @@ struct abx500_bm_data { int usb_safety_tmr_h; int bkup_bat_v; int bkup_bat_i; - bool autopower_cfg; bool no_maintenance; bool capacity_scaling; bool chg_unknown_bat; From 066ebe8ca1e4734471772df734233af5c53d21ae Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Tue, 1 Jun 2021 15:35:59 +0200 Subject: [PATCH 12/42] power: ab8500: remove unused header The ab8500.h header in linux/power is not referenced/included, so can be safely removed. Signed-off-by: Krzysztof Kozlowski Signed-off-by: Sebastian Reichel --- include/linux/power/ab8500.h | 16 ---------------- 1 file changed, 16 deletions(-) delete mode 100644 include/linux/power/ab8500.h diff --git a/include/linux/power/ab8500.h b/include/linux/power/ab8500.h deleted file mode 100644 index 51976b52f373..000000000000 --- a/include/linux/power/ab8500.h +++ /dev/null @@ -1,16 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-only */ -/* - * Copyright (C) ST-Ericsson 2013 - * Author: Hongbo Zhang - */ - -#ifndef PWR_AB8500_H -#define PWR_AB8500_H - -extern const struct abx500_res_to_temp ab8500_temp_tbl_a_thermistor[]; -extern const int ab8500_temp_tbl_a_size; - -extern const struct abx500_res_to_temp ab8500_temp_tbl_b_thermistor[]; -extern const int ab8500_temp_tbl_b_size; - -#endif /* PWR_AB8500_H */ From 73ddad05b44e885f2791d31ff42d583b17d41f44 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Tue, 1 Jun 2021 15:36:00 +0200 Subject: [PATCH 13/42] MAINTAINERS: power: supply: cover also header files Only one header (smartreflex.h) in include/linux/power/ is not related to battery or charger drivers. All others should be covered by power supply maintainers entry so patches to them will reach respective people and lists. Signed-off-by: Krzysztof Kozlowski Signed-off-by: Sebastian Reichel --- MAINTAINERS | 1 + 1 file changed, 1 insertion(+) diff --git a/MAINTAINERS b/MAINTAINERS index 3e90df381516..c6c6f7315265 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -14659,6 +14659,7 @@ T: git git://git.kernel.org/pub/scm/linux/kernel/git/sre/linux-power-supply.git F: Documentation/ABI/testing/sysfs-class-power F: Documentation/devicetree/bindings/power/supply/ F: drivers/power/supply/ +F: include/linux/power/ F: include/linux/power_supply.h POWERNV OPERATOR PANEL LCD DISPLAY DRIVER From e2bfc28afabc94ee91f1bee214bb33b41b7811b7 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Tue, 11 May 2021 13:37:11 -0700 Subject: [PATCH 14/42] power: supply: ab8500: Drop unnecessary NULL check after container_of The result of container_of() operations is never NULL unless the element is the first element of the embedded structure, which is not the case here. The NULL check is therefore unnecessary and misleading. Remove it. This change was made automatically with the following Coccinelle script. @@ type t; identifier v; statement s; @@ <+... ( t v = container_of(...); | v = container_of(...); ) ... when != v - if (\( !v \| v == NULL \) ) s ...+> Signed-off-by: Guenter Roeck Signed-off-by: Sebastian Reichel --- drivers/power/supply/ab8500_charger.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/drivers/power/supply/ab8500_charger.c b/drivers/power/supply/ab8500_charger.c index e6f23ae05f95..4a6bc4330c08 100644 --- a/drivers/power/supply/ab8500_charger.c +++ b/drivers/power/supply/ab8500_charger.c @@ -3188,9 +3188,6 @@ static int ab8500_charger_usb_notifier_call(struct notifier_block *nb, enum ab8500_usb_state bm_usb_state; unsigned mA = *((unsigned *)power); - if (!di) - return NOTIFY_DONE; - if (event != USB_EVENT_VBUS) { dev_dbg(di->dev, "not a standard host, returning\n"); return NOTIFY_DONE; From aa8c8bf64b6e11f846087301f033b0e5977b1342 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Sat, 29 May 2021 01:59:52 +0200 Subject: [PATCH 15/42] power: supply: pm2301_charger: Delete driver The PM2301 was only used in tandem with AB9540, part of U9540, a platform that was cancelled and never deployed in products. Delete it. Signed-off-by: Linus Walleij Signed-off-by: Sebastian Reichel --- drivers/power/supply/Makefile | 2 +- drivers/power/supply/pm2301_charger.c | 1249 ------------------------- include/linux/pm2301_charger.h | 48 - 3 files changed, 1 insertion(+), 1298 deletions(-) delete mode 100644 drivers/power/supply/pm2301_charger.c delete mode 100644 include/linux/pm2301_charger.h diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile index a7309a3d1a47..16ebfaf6d55b 100644 --- a/drivers/power/supply/Makefile +++ b/drivers/power/supply/Makefile @@ -60,7 +60,7 @@ obj-$(CONFIG_BATTERY_TWL4030_MADC) += twl4030_madc_battery.o obj-$(CONFIG_CHARGER_88PM860X) += 88pm860x_charger.o obj-$(CONFIG_CHARGER_PCF50633) += pcf50633-charger.o obj-$(CONFIG_BATTERY_RX51) += rx51_battery.o -obj-$(CONFIG_AB8500_BM) += ab8500_bmdata.o ab8500_charger.o ab8500_fg.o ab8500_btemp.o abx500_chargalg.o pm2301_charger.o +obj-$(CONFIG_AB8500_BM) += ab8500_bmdata.o ab8500_charger.o ab8500_fg.o ab8500_btemp.o abx500_chargalg.o obj-$(CONFIG_CHARGER_CPCAP) += cpcap-charger.o obj-$(CONFIG_CHARGER_ISP1704) += isp1704_charger.o obj-$(CONFIG_CHARGER_MAX8903) += max8903_charger.o diff --git a/drivers/power/supply/pm2301_charger.c b/drivers/power/supply/pm2301_charger.c deleted file mode 100644 index f86bbbeaff6c..000000000000 --- a/drivers/power/supply/pm2301_charger.c +++ /dev/null @@ -1,1249 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * Copyright 2012 ST Ericsson. - * - * Power supply driver for ST Ericsson pm2xxx_charger charger - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "ab8500-bm.h" -#include "ab8500-chargalg.h" -#include "pm2301_charger.h" - -#define to_pm2xxx_charger_ac_device_info(x) container_of((x), \ - struct pm2xxx_charger, ac_chg) -#define SLEEP_MIN 50 -#define SLEEP_MAX 100 -#define PM2XXX_AUTOSUSPEND_DELAY 500 - -static int pm2xxx_interrupt_registers[] = { - PM2XXX_REG_INT1, - PM2XXX_REG_INT2, - PM2XXX_REG_INT3, - PM2XXX_REG_INT4, - PM2XXX_REG_INT5, - PM2XXX_REG_INT6, -}; - -static enum power_supply_property pm2xxx_charger_ac_props[] = { - POWER_SUPPLY_PROP_HEALTH, - POWER_SUPPLY_PROP_PRESENT, - POWER_SUPPLY_PROP_ONLINE, - POWER_SUPPLY_PROP_VOLTAGE_AVG, -}; - -static int pm2xxx_charger_voltage_map[] = { - 3500, - 3525, - 3550, - 3575, - 3600, - 3625, - 3650, - 3675, - 3700, - 3725, - 3750, - 3775, - 3800, - 3825, - 3850, - 3875, - 3900, - 3925, - 3950, - 3975, - 4000, - 4025, - 4050, - 4075, - 4100, - 4125, - 4150, - 4175, - 4200, - 4225, - 4250, - 4275, - 4300, -}; - -static int pm2xxx_charger_current_map[] = { - 200, - 200, - 400, - 600, - 800, - 1000, - 1200, - 1400, - 1600, - 1800, - 2000, - 2200, - 2400, - 2600, - 2800, - 3000, -}; - -static void set_lpn_pin(struct pm2xxx_charger *pm2) -{ - if (!pm2->ac.charger_connected && gpio_is_valid(pm2->lpn_pin)) { - gpio_set_value(pm2->lpn_pin, 1); - usleep_range(SLEEP_MIN, SLEEP_MAX); - } -} - -static void clear_lpn_pin(struct pm2xxx_charger *pm2) -{ - if (!pm2->ac.charger_connected && gpio_is_valid(pm2->lpn_pin)) - gpio_set_value(pm2->lpn_pin, 0); -} - -static int pm2xxx_reg_read(struct pm2xxx_charger *pm2, int reg, u8 *val) -{ - int ret; - - /* wake up the device */ - pm_runtime_get_sync(pm2->dev); - - ret = i2c_smbus_read_i2c_block_data(pm2->config.pm2xxx_i2c, reg, - 1, val); - if (ret < 0) - dev_err(pm2->dev, "Error reading register at 0x%x\n", reg); - else - ret = 0; - - pm_runtime_put_sync(pm2->dev); - - return ret; -} - -static int pm2xxx_reg_write(struct pm2xxx_charger *pm2, int reg, u8 val) -{ - int ret; - - /* wake up the device */ - pm_runtime_get_sync(pm2->dev); - - ret = i2c_smbus_write_i2c_block_data(pm2->config.pm2xxx_i2c, reg, - 1, &val); - if (ret < 0) - dev_err(pm2->dev, "Error writing register at 0x%x\n", reg); - else - ret = 0; - - pm_runtime_put_sync(pm2->dev); - - return ret; -} - -static int pm2xxx_charging_enable_mngt(struct pm2xxx_charger *pm2) -{ - int ret; - - /* Enable charging */ - ret = pm2xxx_reg_write(pm2, PM2XXX_BATT_CTRL_REG2, - (PM2XXX_CH_AUTO_RESUME_EN | PM2XXX_CHARGER_ENA)); - - return ret; -} - -static int pm2xxx_charging_disable_mngt(struct pm2xxx_charger *pm2) -{ - int ret; - - /* Disable SW EOC ctrl */ - ret = pm2xxx_reg_write(pm2, PM2XXX_SW_CTRL_REG, PM2XXX_SWCTRL_HW); - if (ret < 0) { - dev_err(pm2->dev, "%s pm2xxx write failed\n", __func__); - return ret; - } - - /* Disable charging */ - ret = pm2xxx_reg_write(pm2, PM2XXX_BATT_CTRL_REG2, - (PM2XXX_CH_AUTO_RESUME_DIS | PM2XXX_CHARGER_DIS)); - if (ret < 0) { - dev_err(pm2->dev, "%s pm2xxx write failed\n", __func__); - return ret; - } - - return 0; -} - -static int pm2xxx_charger_batt_therm_mngt(struct pm2xxx_charger *pm2, int val) -{ - queue_work(pm2->charger_wq, &pm2->check_main_thermal_prot_work); - - return 0; -} - - -static int pm2xxx_charger_die_therm_mngt(struct pm2xxx_charger *pm2, int val) -{ - queue_work(pm2->charger_wq, &pm2->check_main_thermal_prot_work); - - return 0; -} - -static int pm2xxx_charger_ovv_mngt(struct pm2xxx_charger *pm2, int val) -{ - dev_err(pm2->dev, "Overvoltage detected\n"); - pm2->flags.ovv = true; - power_supply_changed(pm2->ac_chg.psy); - - /* Schedule a new HW failure check */ - queue_delayed_work(pm2->charger_wq, &pm2->check_hw_failure_work, 0); - - return 0; -} - -static int pm2xxx_charger_wd_exp_mngt(struct pm2xxx_charger *pm2, int val) -{ - dev_dbg(pm2->dev , "20 minutes watchdog expired\n"); - - pm2->ac.wd_expired = true; - power_supply_changed(pm2->ac_chg.psy); - - return 0; -} - -static int pm2xxx_charger_vbat_lsig_mngt(struct pm2xxx_charger *pm2, int val) -{ - int ret; - - switch (val) { - case PM2XXX_INT1_ITVBATLOWR: - dev_dbg(pm2->dev, "VBAT grows above VBAT_LOW level\n"); - /* Enable SW EOC ctrl */ - ret = pm2xxx_reg_write(pm2, PM2XXX_SW_CTRL_REG, - PM2XXX_SWCTRL_SW); - if (ret < 0) { - dev_err(pm2->dev, "%s pm2xxx write failed\n", __func__); - return ret; - } - break; - - case PM2XXX_INT1_ITVBATLOWF: - dev_dbg(pm2->dev, "VBAT drops below VBAT_LOW level\n"); - /* Disable SW EOC ctrl */ - ret = pm2xxx_reg_write(pm2, PM2XXX_SW_CTRL_REG, - PM2XXX_SWCTRL_HW); - if (ret < 0) { - dev_err(pm2->dev, "%s pm2xxx write failed\n", __func__); - return ret; - } - break; - - default: - dev_err(pm2->dev, "Unknown VBAT level\n"); - } - - return 0; -} - -static int pm2xxx_charger_bat_disc_mngt(struct pm2xxx_charger *pm2, int val) -{ - dev_dbg(pm2->dev, "battery disconnected\n"); - - return 0; -} - -static int pm2xxx_charger_detection(struct pm2xxx_charger *pm2, u8 *val) -{ - int ret; - - ret = pm2xxx_reg_read(pm2, PM2XXX_SRCE_REG_INT2, val); - - if (ret < 0) { - dev_err(pm2->dev, "Charger detection failed\n"); - goto out; - } - - *val &= (PM2XXX_INT2_S_ITVPWR1PLUG | PM2XXX_INT2_S_ITVPWR2PLUG); - -out: - return ret; -} - -static int pm2xxx_charger_itv_pwr_plug_mngt(struct pm2xxx_charger *pm2, int val) -{ - - int ret; - u8 read_val; - - /* - * Since we can't be sure that the events are received - * synchronously, we have the check if the main charger is - * connected by reading the interrupt source register. - */ - ret = pm2xxx_charger_detection(pm2, &read_val); - - if ((ret == 0) && read_val) { - pm2->ac.charger_connected = 1; - pm2->ac_conn = true; - queue_work(pm2->charger_wq, &pm2->ac_work); - } - - - return ret; -} - -static int pm2xxx_charger_itv_pwr_unplug_mngt(struct pm2xxx_charger *pm2, - int val) -{ - pm2->ac.charger_connected = 0; - queue_work(pm2->charger_wq, &pm2->ac_work); - - return 0; -} - -static int pm2_int_reg0(void *pm2_data, int val) -{ - struct pm2xxx_charger *pm2 = pm2_data; - int ret = 0; - - if (val & PM2XXX_INT1_ITVBATLOWR) { - ret = pm2xxx_charger_vbat_lsig_mngt(pm2, - PM2XXX_INT1_ITVBATLOWR); - if (ret < 0) - goto out; - } - - if (val & PM2XXX_INT1_ITVBATLOWF) { - ret = pm2xxx_charger_vbat_lsig_mngt(pm2, - PM2XXX_INT1_ITVBATLOWF); - if (ret < 0) - goto out; - } - - if (val & PM2XXX_INT1_ITVBATDISCONNECT) { - ret = pm2xxx_charger_bat_disc_mngt(pm2, - PM2XXX_INT1_ITVBATDISCONNECT); - if (ret < 0) - goto out; - } -out: - return ret; -} - -static int pm2_int_reg1(void *pm2_data, int val) -{ - struct pm2xxx_charger *pm2 = pm2_data; - int ret = 0; - - if (val & (PM2XXX_INT2_ITVPWR1PLUG | PM2XXX_INT2_ITVPWR2PLUG)) { - dev_dbg(pm2->dev , "Main charger plugged\n"); - ret = pm2xxx_charger_itv_pwr_plug_mngt(pm2, val & - (PM2XXX_INT2_ITVPWR1PLUG | PM2XXX_INT2_ITVPWR2PLUG)); - } - - if (val & - (PM2XXX_INT2_ITVPWR1UNPLUG | PM2XXX_INT2_ITVPWR2UNPLUG)) { - dev_dbg(pm2->dev , "Main charger unplugged\n"); - ret = pm2xxx_charger_itv_pwr_unplug_mngt(pm2, val & - (PM2XXX_INT2_ITVPWR1UNPLUG | - PM2XXX_INT2_ITVPWR2UNPLUG)); - } - - return ret; -} - -static int pm2_int_reg2(void *pm2_data, int val) -{ - struct pm2xxx_charger *pm2 = pm2_data; - int ret = 0; - - if (val & PM2XXX_INT3_ITAUTOTIMEOUTWD) - ret = pm2xxx_charger_wd_exp_mngt(pm2, val); - - if (val & (PM2XXX_INT3_ITCHPRECHARGEWD | - PM2XXX_INT3_ITCHCCWD | PM2XXX_INT3_ITCHCVWD)) { - dev_dbg(pm2->dev, - "Watchdog occurred for precharge, CC and CV charge\n"); - } - - return ret; -} - -static int pm2_int_reg3(void *pm2_data, int val) -{ - struct pm2xxx_charger *pm2 = pm2_data; - int ret = 0; - - if (val & (PM2XXX_INT4_ITCHARGINGON)) { - dev_dbg(pm2->dev , - "charging operation has started\n"); - } - - if (val & (PM2XXX_INT4_ITVRESUME)) { - dev_dbg(pm2->dev, - "battery discharged down to VResume threshold\n"); - } - - if (val & (PM2XXX_INT4_ITBATTFULL)) { - dev_dbg(pm2->dev , "battery fully detected\n"); - } - - if (val & (PM2XXX_INT4_ITCVPHASE)) { - dev_dbg(pm2->dev, "CV phase enter with 0.5C charging\n"); - } - - if (val & (PM2XXX_INT4_ITVPWR2OVV | PM2XXX_INT4_ITVPWR1OVV)) { - pm2->failure_case = VPWR_OVV; - ret = pm2xxx_charger_ovv_mngt(pm2, val & - (PM2XXX_INT4_ITVPWR2OVV | PM2XXX_INT4_ITVPWR1OVV)); - dev_dbg(pm2->dev, "VPWR/VSYSTEM overvoltage detected\n"); - } - - if (val & (PM2XXX_INT4_S_ITBATTEMPCOLD | - PM2XXX_INT4_S_ITBATTEMPHOT)) { - ret = pm2xxx_charger_batt_therm_mngt(pm2, val & - (PM2XXX_INT4_S_ITBATTEMPCOLD | - PM2XXX_INT4_S_ITBATTEMPHOT)); - dev_dbg(pm2->dev, "BTEMP is too Low/High\n"); - } - - return ret; -} - -static int pm2_int_reg4(void *pm2_data, int val) -{ - struct pm2xxx_charger *pm2 = pm2_data; - int ret = 0; - - if (val & PM2XXX_INT5_ITVSYSTEMOVV) { - pm2->failure_case = VSYSTEM_OVV; - ret = pm2xxx_charger_ovv_mngt(pm2, val & - PM2XXX_INT5_ITVSYSTEMOVV); - dev_dbg(pm2->dev, "VSYSTEM overvoltage detected\n"); - } - - if (val & (PM2XXX_INT5_ITTHERMALWARNINGFALL | - PM2XXX_INT5_ITTHERMALWARNINGRISE | - PM2XXX_INT5_ITTHERMALSHUTDOWNFALL | - PM2XXX_INT5_ITTHERMALSHUTDOWNRISE)) { - dev_dbg(pm2->dev, "BTEMP die temperature is too Low/High\n"); - ret = pm2xxx_charger_die_therm_mngt(pm2, val & - (PM2XXX_INT5_ITTHERMALWARNINGFALL | - PM2XXX_INT5_ITTHERMALWARNINGRISE | - PM2XXX_INT5_ITTHERMALSHUTDOWNFALL | - PM2XXX_INT5_ITTHERMALSHUTDOWNRISE)); - } - - return ret; -} - -static int pm2_int_reg5(void *pm2_data, int val) -{ - struct pm2xxx_charger *pm2 = pm2_data; - - if (val & (PM2XXX_INT6_ITVPWR2DROP | PM2XXX_INT6_ITVPWR1DROP)) { - dev_dbg(pm2->dev, "VMPWR drop to VBAT level\n"); - } - - if (val & (PM2XXX_INT6_ITVPWR2VALIDRISE | - PM2XXX_INT6_ITVPWR1VALIDRISE | - PM2XXX_INT6_ITVPWR2VALIDFALL | - PM2XXX_INT6_ITVPWR1VALIDFALL)) { - dev_dbg(pm2->dev, "Falling/Rising edge on WPWR1/2\n"); - } - - return 0; -} - -static irqreturn_t pm2xxx_irq_int(int irq, void *data) -{ - struct pm2xxx_charger *pm2 = data; - struct pm2xxx_interrupts *interrupt = pm2->pm2_int; - int i; - - /* wake up the device */ - pm_runtime_get_sync(pm2->dev); - - do { - for (i = 0; i < PM2XXX_NUM_INT_REG; i++) { - pm2xxx_reg_read(pm2, - pm2xxx_interrupt_registers[i], - &(interrupt->reg[i])); - - if (interrupt->reg[i] > 0) - interrupt->handler[i](pm2, interrupt->reg[i]); - } - } while (gpio_get_value(pm2->pdata->gpio_irq_number) == 0); - - pm_runtime_mark_last_busy(pm2->dev); - pm_runtime_put_autosuspend(pm2->dev); - - return IRQ_HANDLED; -} - -static int pm2xxx_charger_get_ac_cv(struct pm2xxx_charger *pm2) -{ - int ret = 0; - u8 val; - - if (pm2->ac.charger_connected && pm2->ac.charger_online) { - - ret = pm2xxx_reg_read(pm2, PM2XXX_SRCE_REG_INT4, &val); - if (ret < 0) { - dev_err(pm2->dev, "%s pm2xxx read failed\n", __func__); - goto out; - } - - if (val & PM2XXX_INT4_S_ITCVPHASE) - ret = PM2XXX_CONST_VOLT; - else - ret = PM2XXX_CONST_CURR; - } -out: - return ret; -} - -static int pm2xxx_current_to_regval(int curr) -{ - int i; - - if (curr < pm2xxx_charger_current_map[0]) - return 0; - - for (i = 1; i < ARRAY_SIZE(pm2xxx_charger_current_map); i++) { - if (curr < pm2xxx_charger_current_map[i]) - return (i - 1); - } - - i = ARRAY_SIZE(pm2xxx_charger_current_map) - 1; - if (curr == pm2xxx_charger_current_map[i]) - return i; - else - return -EINVAL; -} - -static int pm2xxx_voltage_to_regval(int curr) -{ - int i; - - if (curr < pm2xxx_charger_voltage_map[0]) - return 0; - - for (i = 1; i < ARRAY_SIZE(pm2xxx_charger_voltage_map); i++) { - if (curr < pm2xxx_charger_voltage_map[i]) - return i - 1; - } - - i = ARRAY_SIZE(pm2xxx_charger_voltage_map) - 1; - if (curr == pm2xxx_charger_voltage_map[i]) - return i; - else - return -EINVAL; -} - -static int pm2xxx_charger_update_charger_current(struct ux500_charger *charger, - int ich_out) -{ - int ret; - int curr_index; - struct pm2xxx_charger *pm2; - u8 val; - - if (charger->psy->desc->type == POWER_SUPPLY_TYPE_MAINS) - pm2 = to_pm2xxx_charger_ac_device_info(charger); - else - return -ENXIO; - - curr_index = pm2xxx_current_to_regval(ich_out); - if (curr_index < 0) { - dev_err(pm2->dev, - "Charger current too high, charging not started\n"); - return -ENXIO; - } - - ret = pm2xxx_reg_read(pm2, PM2XXX_BATT_CTRL_REG6, &val); - if (ret >= 0) { - val &= ~PM2XXX_DIR_CH_CC_CURRENT_MASK; - val |= curr_index; - ret = pm2xxx_reg_write(pm2, PM2XXX_BATT_CTRL_REG6, val); - if (ret < 0) { - dev_err(pm2->dev, - "%s write failed\n", __func__); - } - } - else - dev_err(pm2->dev, "%s read failed\n", __func__); - - return ret; -} - -static int pm2xxx_charger_ac_get_property(struct power_supply *psy, - enum power_supply_property psp, - union power_supply_propval *val) -{ - struct pm2xxx_charger *pm2; - - pm2 = to_pm2xxx_charger_ac_device_info(psy_to_ux500_charger(psy)); - - switch (psp) { - case POWER_SUPPLY_PROP_HEALTH: - if (pm2->flags.mainextchnotok) - val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; - else if (pm2->ac.wd_expired) - val->intval = POWER_SUPPLY_HEALTH_DEAD; - else if (pm2->flags.main_thermal_prot) - val->intval = POWER_SUPPLY_HEALTH_OVERHEAT; - else if (pm2->flags.ovv) - val->intval = POWER_SUPPLY_HEALTH_OVERVOLTAGE; - else - val->intval = POWER_SUPPLY_HEALTH_GOOD; - break; - case POWER_SUPPLY_PROP_ONLINE: - val->intval = pm2->ac.charger_online; - break; - case POWER_SUPPLY_PROP_PRESENT: - val->intval = pm2->ac.charger_connected; - break; - case POWER_SUPPLY_PROP_VOLTAGE_AVG: - pm2->ac.cv_active = pm2xxx_charger_get_ac_cv(pm2); - val->intval = pm2->ac.cv_active; - break; - default: - return -EINVAL; - } - return 0; -} - -static int pm2xxx_charging_init(struct pm2xxx_charger *pm2) -{ - int ret = 0; - - /* enable CC and CV watchdog */ - ret = pm2xxx_reg_write(pm2, PM2XXX_BATT_CTRL_REG3, - (PM2XXX_CH_WD_CV_PHASE_60MIN | PM2XXX_CH_WD_CC_PHASE_60MIN)); - if( ret < 0) - return ret; - - /* enable precharge watchdog */ - ret = pm2xxx_reg_write(pm2, PM2XXX_BATT_CTRL_REG4, - PM2XXX_CH_WD_PRECH_PHASE_60MIN); - - /* Disable auto timeout */ - ret = pm2xxx_reg_write(pm2, PM2XXX_BATT_CTRL_REG5, - PM2XXX_CH_WD_AUTO_TIMEOUT_20MIN); - - /* - * EOC current level = 100mA - * Precharge current level = 100mA - * CC current level = 1000mA - */ - ret = pm2xxx_reg_write(pm2, PM2XXX_BATT_CTRL_REG6, - (PM2XXX_DIR_CH_CC_CURRENT_1000MA | - PM2XXX_CH_PRECH_CURRENT_100MA | - PM2XXX_CH_EOC_CURRENT_100MA)); - - /* - * recharge threshold = 3.8V - * Precharge to CC threshold = 2.9V - */ - ret = pm2xxx_reg_write(pm2, PM2XXX_BATT_CTRL_REG7, - (PM2XXX_CH_PRECH_VOL_2_9 | PM2XXX_CH_VRESUME_VOL_3_8)); - - /* float voltage charger level = 4.2V */ - ret = pm2xxx_reg_write(pm2, PM2XXX_BATT_CTRL_REG8, - PM2XXX_CH_VOLT_4_2); - - /* Voltage drop between VBAT and VSYS in HW charging = 300mV */ - ret = pm2xxx_reg_write(pm2, PM2XXX_BATT_CTRL_REG9, - (PM2XXX_CH_150MV_DROP_300MV | PM2XXX_CHARCHING_INFO_DIS | - PM2XXX_CH_CC_REDUCED_CURRENT_IDENT | - PM2XXX_CH_CC_MODEDROP_DIS)); - - /* Input charger level of over voltage = 10V */ - ret = pm2xxx_reg_write(pm2, PM2XXX_INP_VOLT_VPWR2, - PM2XXX_VPWR2_OVV_10); - ret = pm2xxx_reg_write(pm2, PM2XXX_INP_VOLT_VPWR1, - PM2XXX_VPWR1_OVV_10); - - /* Input charger drop */ - ret = pm2xxx_reg_write(pm2, PM2XXX_INP_DROP_VPWR2, - (PM2XXX_VPWR2_HW_OPT_DIS | PM2XXX_VPWR2_VALID_DIS | - PM2XXX_VPWR2_DROP_DIS)); - ret = pm2xxx_reg_write(pm2, PM2XXX_INP_DROP_VPWR1, - (PM2XXX_VPWR1_HW_OPT_DIS | PM2XXX_VPWR1_VALID_DIS | - PM2XXX_VPWR1_DROP_DIS)); - - /* Disable battery low monitoring */ - ret = pm2xxx_reg_write(pm2, PM2XXX_BATT_LOW_LEV_COMP_REG, - PM2XXX_VBAT_LOW_MONITORING_ENA); - - return ret; -} - -static int pm2xxx_charger_ac_en(struct ux500_charger *charger, - int enable, int vset, int iset) -{ - int ret; - int volt_index; - int curr_index; - u8 val; - - struct pm2xxx_charger *pm2 = to_pm2xxx_charger_ac_device_info(charger); - - if (enable) { - if (!pm2->ac.charger_connected) { - dev_dbg(pm2->dev, "AC charger not connected\n"); - return -ENXIO; - } - - dev_dbg(pm2->dev, "Enable AC: %dmV %dmA\n", vset, iset); - if (!pm2->vddadc_en_ac) { - ret = regulator_enable(pm2->regu); - if (ret) - dev_warn(pm2->dev, - "Failed to enable vddadc regulator\n"); - else - pm2->vddadc_en_ac = true; - } - - ret = pm2xxx_charging_init(pm2); - if (ret < 0) { - dev_err(pm2->dev, "%s charging init failed\n", - __func__); - goto error_occured; - } - - volt_index = pm2xxx_voltage_to_regval(vset); - curr_index = pm2xxx_current_to_regval(iset); - - if (volt_index < 0 || curr_index < 0) { - dev_err(pm2->dev, - "Charger voltage or current too high, " - "charging not started\n"); - return -ENXIO; - } - - ret = pm2xxx_reg_read(pm2, PM2XXX_BATT_CTRL_REG8, &val); - if (ret < 0) { - dev_err(pm2->dev, "%s pm2xxx read failed\n", __func__); - goto error_occured; - } - val &= ~PM2XXX_CH_VOLT_MASK; - val |= volt_index; - ret = pm2xxx_reg_write(pm2, PM2XXX_BATT_CTRL_REG8, val); - if (ret < 0) { - dev_err(pm2->dev, "%s pm2xxx write failed\n", __func__); - goto error_occured; - } - - ret = pm2xxx_reg_read(pm2, PM2XXX_BATT_CTRL_REG6, &val); - if (ret < 0) { - dev_err(pm2->dev, "%s pm2xxx read failed\n", __func__); - goto error_occured; - } - val &= ~PM2XXX_DIR_CH_CC_CURRENT_MASK; - val |= curr_index; - ret = pm2xxx_reg_write(pm2, PM2XXX_BATT_CTRL_REG6, val); - if (ret < 0) { - dev_err(pm2->dev, "%s pm2xxx write failed\n", __func__); - goto error_occured; - } - - if (!pm2->bat->enable_overshoot) { - ret = pm2xxx_reg_read(pm2, PM2XXX_LED_CTRL_REG, &val); - if (ret < 0) { - dev_err(pm2->dev, "%s pm2xxx read failed\n", - __func__); - goto error_occured; - } - val |= PM2XXX_ANTI_OVERSHOOT_EN; - ret = pm2xxx_reg_write(pm2, PM2XXX_LED_CTRL_REG, val); - if (ret < 0) { - dev_err(pm2->dev, "%s pm2xxx write failed\n", - __func__); - goto error_occured; - } - } - - ret = pm2xxx_charging_enable_mngt(pm2); - if (ret < 0) { - dev_err(pm2->dev, "Failed to enable" - "pm2xxx ac charger\n"); - goto error_occured; - } - - pm2->ac.charger_online = 1; - } else { - pm2->ac.charger_online = 0; - pm2->ac.wd_expired = false; - - /* Disable regulator if enabled */ - if (pm2->vddadc_en_ac) { - regulator_disable(pm2->regu); - pm2->vddadc_en_ac = false; - } - - ret = pm2xxx_charging_disable_mngt(pm2); - if (ret < 0) { - dev_err(pm2->dev, "failed to disable" - "pm2xxx ac charger\n"); - goto error_occured; - } - - dev_dbg(pm2->dev, "PM2301: " "Disabled AC charging\n"); - } - power_supply_changed(pm2->ac_chg.psy); - -error_occured: - return ret; -} - -static int pm2xxx_charger_watchdog_kick(struct ux500_charger *charger) -{ - int ret; - struct pm2xxx_charger *pm2; - - if (charger->psy->desc->type == POWER_SUPPLY_TYPE_MAINS) - pm2 = to_pm2xxx_charger_ac_device_info(charger); - else - return -ENXIO; - - ret = pm2xxx_reg_write(pm2, PM2XXX_BATT_WD_KICK, WD_TIMER); - if (ret) - dev_err(pm2->dev, "Failed to kick WD!\n"); - - return ret; -} - -static void pm2xxx_charger_ac_work(struct work_struct *work) -{ - struct pm2xxx_charger *pm2 = container_of(work, - struct pm2xxx_charger, ac_work); - - - power_supply_changed(pm2->ac_chg.psy); - sysfs_notify(&pm2->ac_chg.psy->dev.kobj, NULL, "present"); -}; - -static void pm2xxx_charger_check_hw_failure_work(struct work_struct *work) -{ - u8 reg_value; - - struct pm2xxx_charger *pm2 = container_of(work, - struct pm2xxx_charger, check_hw_failure_work.work); - - if (pm2->flags.ovv) { - pm2xxx_reg_read(pm2, PM2XXX_SRCE_REG_INT4, ®_value); - - if (!(reg_value & (PM2XXX_INT4_S_ITVPWR1OVV | - PM2XXX_INT4_S_ITVPWR2OVV))) { - pm2->flags.ovv = false; - power_supply_changed(pm2->ac_chg.psy); - } - } - - /* If we still have a failure, schedule a new check */ - if (pm2->flags.ovv) { - queue_delayed_work(pm2->charger_wq, - &pm2->check_hw_failure_work, round_jiffies(HZ)); - } -} - -static void pm2xxx_charger_check_main_thermal_prot_work( - struct work_struct *work) -{ - int ret; - u8 val; - - struct pm2xxx_charger *pm2 = container_of(work, struct pm2xxx_charger, - check_main_thermal_prot_work); - - /* Check if die temp warning is still active */ - ret = pm2xxx_reg_read(pm2, PM2XXX_SRCE_REG_INT5, &val); - if (ret < 0) { - dev_err(pm2->dev, "%s pm2xxx read failed\n", __func__); - return; - } - if (val & (PM2XXX_INT5_S_ITTHERMALWARNINGRISE - | PM2XXX_INT5_S_ITTHERMALSHUTDOWNRISE)) - pm2->flags.main_thermal_prot = true; - else if (val & (PM2XXX_INT5_S_ITTHERMALWARNINGFALL - | PM2XXX_INT5_S_ITTHERMALSHUTDOWNFALL)) - pm2->flags.main_thermal_prot = false; - - power_supply_changed(pm2->ac_chg.psy); -} - -static struct pm2xxx_interrupts pm2xxx_int = { - .handler[0] = pm2_int_reg0, - .handler[1] = pm2_int_reg1, - .handler[2] = pm2_int_reg2, - .handler[3] = pm2_int_reg3, - .handler[4] = pm2_int_reg4, - .handler[5] = pm2_int_reg5, -}; - -static struct pm2xxx_irq pm2xxx_charger_irq[] = { - {"PM2XXX_IRQ_INT", pm2xxx_irq_int}, -}; - -static int __maybe_unused pm2xxx_wall_charger_resume(struct device *dev) -{ - struct i2c_client *i2c_client = to_i2c_client(dev); - struct pm2xxx_charger *pm2; - - pm2 = (struct pm2xxx_charger *)i2c_get_clientdata(i2c_client); - set_lpn_pin(pm2); - - /* If we still have a HW failure, schedule a new check */ - if (pm2->flags.ovv) - queue_delayed_work(pm2->charger_wq, - &pm2->check_hw_failure_work, 0); - - return 0; -} - -static int __maybe_unused pm2xxx_wall_charger_suspend(struct device *dev) -{ - struct i2c_client *i2c_client = to_i2c_client(dev); - struct pm2xxx_charger *pm2; - - pm2 = (struct pm2xxx_charger *)i2c_get_clientdata(i2c_client); - clear_lpn_pin(pm2); - - /* Cancel any pending HW failure check */ - if (delayed_work_pending(&pm2->check_hw_failure_work)) - cancel_delayed_work(&pm2->check_hw_failure_work); - - flush_work(&pm2->ac_work); - flush_work(&pm2->check_main_thermal_prot_work); - - return 0; -} - -static int __maybe_unused pm2xxx_runtime_suspend(struct device *dev) -{ - struct i2c_client *pm2xxx_i2c_client = to_i2c_client(dev); - struct pm2xxx_charger *pm2; - - pm2 = (struct pm2xxx_charger *)i2c_get_clientdata(pm2xxx_i2c_client); - clear_lpn_pin(pm2); - - return 0; -} - -static int __maybe_unused pm2xxx_runtime_resume(struct device *dev) -{ - struct i2c_client *pm2xxx_i2c_client = to_i2c_client(dev); - struct pm2xxx_charger *pm2; - - pm2 = (struct pm2xxx_charger *)i2c_get_clientdata(pm2xxx_i2c_client); - - if (gpio_is_valid(pm2->lpn_pin) && gpio_get_value(pm2->lpn_pin) == 0) - set_lpn_pin(pm2); - - return 0; -} - -static const struct dev_pm_ops pm2xxx_pm_ops __maybe_unused = { - SET_SYSTEM_SLEEP_PM_OPS(pm2xxx_wall_charger_suspend, - pm2xxx_wall_charger_resume) - SET_RUNTIME_PM_OPS(pm2xxx_runtime_suspend, pm2xxx_runtime_resume, NULL) -}; - -static int pm2xxx_wall_charger_probe(struct i2c_client *i2c_client, - const struct i2c_device_id *id) -{ - struct pm2xxx_platform_data *pl_data = i2c_client->dev.platform_data; - struct power_supply_config psy_cfg = {}; - struct pm2xxx_charger *pm2; - int ret = 0; - u8 val; - int i; - - if (!pl_data) { - dev_err(&i2c_client->dev, "No platform data supplied\n"); - return -EINVAL; - } - - pm2 = kzalloc(sizeof(struct pm2xxx_charger), GFP_KERNEL); - if (!pm2) { - dev_err(&i2c_client->dev, "pm2xxx_charger allocation failed\n"); - return -ENOMEM; - } - - /* get parent data */ - pm2->dev = &i2c_client->dev; - - pm2->pm2_int = &pm2xxx_int; - - /* get charger spcific platform data */ - if (!pl_data->wall_charger) { - dev_err(pm2->dev, "no charger platform data supplied\n"); - ret = -EINVAL; - goto free_device_info; - } - - pm2->pdata = pl_data->wall_charger; - - /* get battery specific platform data */ - if (!pl_data->battery) { - dev_err(pm2->dev, "no battery platform data supplied\n"); - ret = -EINVAL; - goto free_device_info; - } - - pm2->bat = pl_data->battery; - - if (!i2c_check_functionality(i2c_client->adapter, - I2C_FUNC_SMBUS_BYTE_DATA | - I2C_FUNC_SMBUS_READ_WORD_DATA)) { - ret = -ENODEV; - dev_info(pm2->dev, "pm2301 i2c_check_functionality failed\n"); - goto free_device_info; - } - - pm2->config.pm2xxx_i2c = i2c_client; - pm2->config.pm2xxx_id = (struct i2c_device_id *) id; - i2c_set_clientdata(i2c_client, pm2); - - /* AC supply */ - /* power_supply base class */ - pm2->ac_chg_desc.name = pm2->pdata->label; - pm2->ac_chg_desc.type = POWER_SUPPLY_TYPE_MAINS; - pm2->ac_chg_desc.properties = pm2xxx_charger_ac_props; - pm2->ac_chg_desc.num_properties = ARRAY_SIZE(pm2xxx_charger_ac_props); - pm2->ac_chg_desc.get_property = pm2xxx_charger_ac_get_property; - - psy_cfg.supplied_to = pm2->pdata->supplied_to; - psy_cfg.num_supplicants = pm2->pdata->num_supplicants; - /* pm2xxx_charger sub-class */ - pm2->ac_chg.ops.enable = &pm2xxx_charger_ac_en; - pm2->ac_chg.ops.kick_wd = &pm2xxx_charger_watchdog_kick; - pm2->ac_chg.ops.update_curr = &pm2xxx_charger_update_charger_current; - pm2->ac_chg.max_out_volt = pm2xxx_charger_voltage_map[ - ARRAY_SIZE(pm2xxx_charger_voltage_map) - 1]; - pm2->ac_chg.max_out_curr = pm2xxx_charger_current_map[ - ARRAY_SIZE(pm2xxx_charger_current_map) - 1]; - pm2->ac_chg.wdt_refresh = WD_KICK_INTERVAL; - pm2->ac_chg.enabled = true; - pm2->ac_chg.external = true; - - /* Create a work queue for the charger */ - pm2->charger_wq = alloc_ordered_workqueue("pm2xxx_charger_wq", - WQ_MEM_RECLAIM); - if (pm2->charger_wq == NULL) { - ret = -ENOMEM; - dev_err(pm2->dev, "failed to create work queue\n"); - goto free_device_info; - } - - /* Init work for charger detection */ - INIT_WORK(&pm2->ac_work, pm2xxx_charger_ac_work); - - /* Init work for checking HW status */ - INIT_WORK(&pm2->check_main_thermal_prot_work, - pm2xxx_charger_check_main_thermal_prot_work); - - /* Init work for HW failure check */ - INIT_DEFERRABLE_WORK(&pm2->check_hw_failure_work, - pm2xxx_charger_check_hw_failure_work); - - /* - * VDD ADC supply needs to be enabled from this driver when there - * is a charger connected to avoid erroneous BTEMP_HIGH/LOW - * interrupts during charging - */ - pm2->regu = regulator_get(pm2->dev, "vddadc"); - if (IS_ERR(pm2->regu)) { - ret = PTR_ERR(pm2->regu); - dev_err(pm2->dev, "failed to get vddadc regulator\n"); - goto free_charger_wq; - } - - /* Register AC charger class */ - pm2->ac_chg.psy = power_supply_register(pm2->dev, &pm2->ac_chg_desc, - &psy_cfg); - if (IS_ERR(pm2->ac_chg.psy)) { - dev_err(pm2->dev, "failed to register AC charger\n"); - ret = PTR_ERR(pm2->ac_chg.psy); - goto free_regulator; - } - - /* Register interrupts */ - ret = request_threaded_irq(gpio_to_irq(pm2->pdata->gpio_irq_number), - NULL, - pm2xxx_charger_irq[0].isr, - pm2->pdata->irq_type | IRQF_ONESHOT, - pm2xxx_charger_irq[0].name, pm2); - - if (ret != 0) { - dev_err(pm2->dev, "failed to request %s IRQ %d: %d\n", - pm2xxx_charger_irq[0].name, - gpio_to_irq(pm2->pdata->gpio_irq_number), ret); - goto unregister_pm2xxx_charger; - } - - ret = pm_runtime_set_active(pm2->dev); - if (ret) - dev_err(pm2->dev, "set active Error\n"); - - pm_runtime_enable(pm2->dev); - pm_runtime_set_autosuspend_delay(pm2->dev, PM2XXX_AUTOSUSPEND_DELAY); - pm_runtime_use_autosuspend(pm2->dev); - pm_runtime_resume(pm2->dev); - - /* pm interrupt can wake up system */ - ret = enable_irq_wake(gpio_to_irq(pm2->pdata->gpio_irq_number)); - if (ret) { - dev_err(pm2->dev, "failed to set irq wake\n"); - goto unregister_pm2xxx_interrupt; - } - - mutex_init(&pm2->lock); - - if (gpio_is_valid(pm2->pdata->lpn_gpio)) { - /* get lpn GPIO from platform data */ - pm2->lpn_pin = pm2->pdata->lpn_gpio; - - /* - * Charger detection mechanism requires pulling up the LPN pin - * while i2c communication if Charger is not connected - * LPN pin of PM2301 is GPIO60 of AB9540 - */ - ret = gpio_request(pm2->lpn_pin, "pm2301_lpm_gpio"); - - if (ret < 0) { - dev_err(pm2->dev, "pm2301_lpm_gpio request failed\n"); - goto disable_pm2_irq_wake; - } - ret = gpio_direction_output(pm2->lpn_pin, 0); - if (ret < 0) { - dev_err(pm2->dev, "pm2301_lpm_gpio direction failed\n"); - goto free_gpio; - } - set_lpn_pin(pm2); - } - - /* read interrupt registers */ - for (i = 0; i < PM2XXX_NUM_INT_REG; i++) - pm2xxx_reg_read(pm2, - pm2xxx_interrupt_registers[i], - &val); - - ret = pm2xxx_charger_detection(pm2, &val); - - if ((ret == 0) && val) { - pm2->ac.charger_connected = 1; - ab8500_override_turn_on_stat(~AB8500_POW_KEY_1_ON, - AB8500_MAIN_CH_DET); - pm2->ac_conn = true; - power_supply_changed(pm2->ac_chg.psy); - sysfs_notify(&pm2->ac_chg.psy->dev.kobj, NULL, "present"); - } - - return 0; - -free_gpio: - if (gpio_is_valid(pm2->lpn_pin)) - gpio_free(pm2->lpn_pin); -disable_pm2_irq_wake: - disable_irq_wake(gpio_to_irq(pm2->pdata->gpio_irq_number)); -unregister_pm2xxx_interrupt: - /* disable interrupt */ - free_irq(gpio_to_irq(pm2->pdata->gpio_irq_number), pm2); -unregister_pm2xxx_charger: - /* unregister power supply */ - power_supply_unregister(pm2->ac_chg.psy); -free_regulator: - /* disable the regulator */ - regulator_put(pm2->regu); -free_charger_wq: - destroy_workqueue(pm2->charger_wq); -free_device_info: - kfree(pm2); - - return ret; -} - -static int pm2xxx_wall_charger_remove(struct i2c_client *i2c_client) -{ - struct pm2xxx_charger *pm2 = i2c_get_clientdata(i2c_client); - - /* Disable pm_runtime */ - pm_runtime_disable(pm2->dev); - /* Disable AC charging */ - pm2xxx_charger_ac_en(&pm2->ac_chg, false, 0, 0); - - /* Disable wake by pm interrupt */ - disable_irq_wake(gpio_to_irq(pm2->pdata->gpio_irq_number)); - - /* Disable interrupts */ - free_irq(gpio_to_irq(pm2->pdata->gpio_irq_number), pm2); - - /* Delete the work queue */ - destroy_workqueue(pm2->charger_wq); - - flush_scheduled_work(); - - /* disable the regulator */ - regulator_put(pm2->regu); - - power_supply_unregister(pm2->ac_chg.psy); - - if (gpio_is_valid(pm2->lpn_pin)) - gpio_free(pm2->lpn_pin); - - kfree(pm2); - - return 0; -} - -static const struct i2c_device_id pm2xxx_id[] = { - { "pm2301", 0 }, - { } -}; - -MODULE_DEVICE_TABLE(i2c, pm2xxx_id); - -static struct i2c_driver pm2xxx_charger_driver = { - .probe = pm2xxx_wall_charger_probe, - .remove = pm2xxx_wall_charger_remove, - .driver = { - .name = "pm2xxx-wall_charger", - .pm = IS_ENABLED(CONFIG_PM) ? &pm2xxx_pm_ops : NULL, - }, - .id_table = pm2xxx_id, -}; - -static int __init pm2xxx_charger_init(void) -{ - return i2c_add_driver(&pm2xxx_charger_driver); -} - -static void __exit pm2xxx_charger_exit(void) -{ - i2c_del_driver(&pm2xxx_charger_driver); -} - -device_initcall_sync(pm2xxx_charger_init); -module_exit(pm2xxx_charger_exit); - -MODULE_LICENSE("GPL v2"); -MODULE_AUTHOR("Rajkumar kasirajan, Olivier Launay"); -MODULE_DESCRIPTION("PM2xxx charger management driver"); diff --git a/include/linux/pm2301_charger.h b/include/linux/pm2301_charger.h deleted file mode 100644 index b8fac96f05aa..000000000000 --- a/include/linux/pm2301_charger.h +++ /dev/null @@ -1,48 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-only */ -/* - * PM2301 charger driver. - * - * Copyright (C) 2012 ST Ericsson Corporation - * - * Contact: Olivier LAUNAY (olivier.launay@stericsson.com - */ - -#ifndef __LINUX_PM2301_H -#define __LINUX_PM2301_H - -/** - * struct pm2xxx_bm_charger_parameters - Charger specific parameters - * @ac_volt_max: maximum allowed AC charger voltage in mV - * @ac_curr_max: maximum allowed AC charger current in mA - */ -struct pm2xxx_bm_charger_parameters { - int ac_volt_max; - int ac_curr_max; -}; - -/** - * struct pm2xxx_bm_data - pm2xxx battery management data - * @enable_overshoot flag to enable VBAT overshoot control - * @chg_params charger parameters - */ -struct pm2xxx_bm_data { - bool enable_overshoot; - const struct pm2xxx_bm_charger_parameters *chg_params; -}; - -struct pm2xxx_charger_platform_data { - char **supplied_to; - size_t num_supplicants; - int i2c_bus; - const char *label; - int gpio_irq_number; - unsigned int lpn_gpio; - int irq_type; -}; - -struct pm2xxx_platform_data { - struct pm2xxx_charger_platform_data *wall_charger; - struct pm2xxx_bm_data *battery; -}; - -#endif /* __LINUX_PM2301_H */ From 3ee236296a4093a6c9967647ed6f640a81ae9f25 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Sat, 29 May 2021 02:19:02 +0200 Subject: [PATCH 16/42] power: supply: smb347-charger: Drop unused include This driver is including the legacy GPIO header but not using any symbols from it. Delete the include. Signed-off-by: Linus Walleij Reviewed-by: Dmitry Osipenko Acked-by: David Heidelberg Signed-off-by: Sebastian Reichel --- drivers/power/supply/smb347-charger.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/power/supply/smb347-charger.c b/drivers/power/supply/smb347-charger.c index 3376f42d46c3..df240420f2de 100644 --- a/drivers/power/supply/smb347-charger.c +++ b/drivers/power/supply/smb347-charger.c @@ -10,7 +10,6 @@ #include #include -#include #include #include #include From 7d76367774d716d28bf003defded61a37b4c83ed Mon Sep 17 00:00:00 2001 From: Andreas Kemnade Date: Fri, 28 May 2021 23:30:05 +0200 Subject: [PATCH 17/42] power: supply: rn5t618: Add charger type detection The RC5T619 can detect SDP, CDP and DCP chargers, so let's add support for it. Signed-off-by: Andreas Kemnade Signed-off-by: Sebastian Reichel --- drivers/power/supply/rn5t618_power.c | 47 ++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/drivers/power/supply/rn5t618_power.c b/drivers/power/supply/rn5t618_power.c index dee520f0fdf5..f8f00f67ce4c 100644 --- a/drivers/power/supply/rn5t618_power.c +++ b/drivers/power/supply/rn5t618_power.c @@ -37,6 +37,11 @@ #define CHG_STATE_NO_BAT2 13 #define CHG_STATE_CHG_READY_VUSB 14 +#define GCHGDET_TYPE_MASK 0x30 +#define GCHGDET_TYPE_SDP 0x00 +#define GCHGDET_TYPE_CDP 0x10 +#define GCHGDET_TYPE_DCP 0x20 + #define FG_ENABLE 1 struct rn5t618_power_info { @@ -48,8 +53,16 @@ struct rn5t618_power_info { int irq; }; +static enum power_supply_usb_type rn5t618_usb_types[] = { + POWER_SUPPLY_USB_TYPE_SDP, + POWER_SUPPLY_USB_TYPE_DCP, + POWER_SUPPLY_USB_TYPE_CDP, + POWER_SUPPLY_USB_TYPE_UNKNOWN +}; + static enum power_supply_property rn5t618_usb_props[] = { POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_USB_TYPE, POWER_SUPPLY_PROP_ONLINE, }; @@ -372,6 +385,33 @@ static int rn5t618_adp_get_property(struct power_supply *psy, return 0; } +static int rc5t619_usb_get_type(struct rn5t618_power_info *info, + union power_supply_propval *val) +{ + unsigned int regval; + int ret; + + ret = regmap_read(info->rn5t618->regmap, RN5T618_GCHGDET, ®val); + if (ret < 0) + return ret; + + switch (regval & GCHGDET_TYPE_MASK) { + case GCHGDET_TYPE_SDP: + val->intval = POWER_SUPPLY_USB_TYPE_SDP; + break; + case GCHGDET_TYPE_CDP: + val->intval = POWER_SUPPLY_USB_TYPE_CDP; + break; + case GCHGDET_TYPE_DCP: + val->intval = POWER_SUPPLY_USB_TYPE_DCP; + break; + default: + val->intval = POWER_SUPPLY_USB_TYPE_UNKNOWN; + } + + return 0; +} + static int rn5t618_usb_get_property(struct power_supply *psy, enum power_supply_property psp, union power_supply_propval *val) @@ -401,6 +441,11 @@ static int rn5t618_usb_get_property(struct power_supply *psy, val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; break; + case POWER_SUPPLY_PROP_USB_TYPE: + if (!online || (info->rn5t618->variant != RC5T619)) + return -ENODATA; + + return rc5t619_usb_get_type(info, val); default: return -EINVAL; } @@ -427,6 +472,8 @@ static const struct power_supply_desc rn5t618_adp_desc = { static const struct power_supply_desc rn5t618_usb_desc = { .name = "rn5t618-usb", .type = POWER_SUPPLY_TYPE_USB, + .usb_types = rn5t618_usb_types, + .num_usb_types = ARRAY_SIZE(rn5t618_usb_types), .properties = rn5t618_usb_props, .num_properties = ARRAY_SIZE(rn5t618_usb_props), .get_property = rn5t618_usb_get_property, From 20a3c8b57b2bc2b372bba55ce9d9a90d0030892e Mon Sep 17 00:00:00 2001 From: Andreas Kemnade Date: Fri, 28 May 2021 23:30:06 +0200 Subject: [PATCH 18/42] power: supply: rn5t618: Add input current limit Adds properties for setting the maximum current to USB, ADP and Battery supplies. USB and ADP limits are reset to OTP values upon replugging. Signed-off-by: Andreas Kemnade Signed-off-by: Sebastian Reichel --- drivers/power/supply/rn5t618_power.c | 188 +++++++++++++++++++++++++++ 1 file changed, 188 insertions(+) diff --git a/drivers/power/supply/rn5t618_power.c b/drivers/power/supply/rn5t618_power.c index f8f00f67ce4c..819061918b2a 100644 --- a/drivers/power/supply/rn5t618_power.c +++ b/drivers/power/supply/rn5t618_power.c @@ -44,6 +44,20 @@ #define FG_ENABLE 1 +/* + * Formula seems accurate for battery current, but for USB current around 70mA + * per step was seen on Kobo Clara HD but all sources show the same formula + * also fur USB current. To avoid accidentially unwanted high currents we stick + * to that formula + */ +#define TO_CUR_REG(x) ((x) / 100000 - 1) +#define FROM_CUR_REG(x) ((((x) & 0x1f) + 1) * 100000) +#define CHG_MIN_CUR 100000 +#define CHG_MAX_CUR 1800000 +#define ADP_MAX_CUR 2500000 +#define USB_MAX_CUR 1400000 + + struct rn5t618_power_info { struct rn5t618 *rn5t618; struct platform_device *pdev; @@ -61,12 +75,16 @@ static enum power_supply_usb_type rn5t618_usb_types[] = { }; static enum power_supply_property rn5t618_usb_props[] = { + /* input current limit is not very accurate */ + POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT, POWER_SUPPLY_PROP_STATUS, POWER_SUPPLY_PROP_USB_TYPE, POWER_SUPPLY_PROP_ONLINE, }; static enum power_supply_property rn5t618_adp_props[] = { + /* input current limit is not very accurate */ + POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT, POWER_SUPPLY_PROP_STATUS, POWER_SUPPLY_PROP_ONLINE, }; @@ -82,6 +100,7 @@ static enum power_supply_property rn5t618_battery_props[] = { POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW, POWER_SUPPLY_PROP_TIME_TO_FULL_NOW, POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT, POWER_SUPPLY_PROP_CHARGE_FULL, POWER_SUPPLY_PROP_CHARGE_NOW, }; @@ -271,6 +290,36 @@ static int rn5t618_battery_ttf(struct rn5t618_power_info *info, return 0; } +static int rn5t618_battery_set_current_limit(struct rn5t618_power_info *info, + const union power_supply_propval *val) +{ + if (val->intval < CHG_MIN_CUR) + return -EINVAL; + + if (val->intval >= CHG_MAX_CUR) + return -EINVAL; + + return regmap_update_bits(info->rn5t618->regmap, + RN5T618_CHGISET, + 0x1F, TO_CUR_REG(val->intval)); +} + +static int rn5t618_battery_get_current_limit(struct rn5t618_power_info *info, + union power_supply_propval *val) +{ + unsigned int regval; + int ret; + + ret = regmap_read(info->rn5t618->regmap, RN5T618_CHGISET, + ®val); + if (ret < 0) + return ret; + + val->intval = FROM_CUR_REG(regval); + + return 0; +} + static int rn5t618_battery_charge_full(struct rn5t618_power_info *info, union power_supply_propval *val) { @@ -336,6 +385,9 @@ static int rn5t618_battery_get_property(struct power_supply *psy, case POWER_SUPPLY_PROP_TECHNOLOGY: val->intval = POWER_SUPPLY_TECHNOLOGY_LION; break; + case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT: + ret = rn5t618_battery_get_current_limit(info, val); + break; case POWER_SUPPLY_PROP_CHARGE_FULL: ret = rn5t618_battery_charge_full(info, val); break; @@ -349,12 +401,38 @@ static int rn5t618_battery_get_property(struct power_supply *psy, return ret; } +static int rn5t618_battery_set_property(struct power_supply *psy, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct rn5t618_power_info *info = power_supply_get_drvdata(psy); + + switch (psp) { + case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT: + return rn5t618_battery_set_current_limit(info, val); + default: + return -EINVAL; + } +} + +static int rn5t618_battery_property_is_writeable(struct power_supply *psy, + enum power_supply_property psp) +{ + switch (psp) { + case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT: + return true; + default: + return false; + } +} + static int rn5t618_adp_get_property(struct power_supply *psy, enum power_supply_property psp, union power_supply_propval *val) { struct rn5t618_power_info *info = power_supply_get_drvdata(psy); unsigned int chgstate; + unsigned int regval; bool online; int ret; @@ -377,6 +455,14 @@ static int rn5t618_adp_get_property(struct power_supply *psy, if (val->intval != POWER_SUPPLY_STATUS_CHARGING) val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; + break; + case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: + ret = regmap_read(info->rn5t618->regmap, + RN5T618_REGISET1, ®val); + if (ret < 0) + return ret; + + val->intval = FROM_CUR_REG(regval); break; default: return -EINVAL; @@ -385,6 +471,45 @@ static int rn5t618_adp_get_property(struct power_supply *psy, return 0; } +static int rn5t618_adp_set_property(struct power_supply *psy, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct rn5t618_power_info *info = power_supply_get_drvdata(psy); + int ret; + + switch (psp) { + case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: + if (val->intval > ADP_MAX_CUR) + return -EINVAL; + + if (val->intval < CHG_MIN_CUR) + return -EINVAL; + + ret = regmap_write(info->rn5t618->regmap, RN5T618_REGISET1, + TO_CUR_REG(val->intval)); + if (ret < 0) + return ret; + + break; + default: + return -EINVAL; + } + + return 0; +} + +static int rn5t618_adp_property_is_writeable(struct power_supply *psy, + enum power_supply_property psp) +{ + switch (psp) { + case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: + return true; + default: + return false; + } +} + static int rc5t619_usb_get_type(struct rn5t618_power_info *info, union power_supply_propval *val) { @@ -418,6 +543,7 @@ static int rn5t618_usb_get_property(struct power_supply *psy, { struct rn5t618_power_info *info = power_supply_get_drvdata(psy); unsigned int chgstate; + unsigned int regval; bool online; int ret; @@ -446,6 +572,23 @@ static int rn5t618_usb_get_property(struct power_supply *psy, return -ENODATA; return rc5t619_usb_get_type(info, val); + case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: + ret = regmap_read(info->rn5t618->regmap, RN5T618_CHGCTL1, + ®val); + if (ret < 0) + return ret; + + val->intval = 0; + if (regval & 2) { + ret = regmap_read(info->rn5t618->regmap, + RN5T618_REGISET2, + ®val); + if (ret < 0) + return ret; + + val->intval = FROM_CUR_REG(regval); + } + break; default: return -EINVAL; } @@ -453,12 +596,53 @@ static int rn5t618_usb_get_property(struct power_supply *psy, return 0; } +static int rn5t618_usb_set_property(struct power_supply *psy, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct rn5t618_power_info *info = power_supply_get_drvdata(psy); + int ret; + + switch (psp) { + case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: + if (val->intval > USB_MAX_CUR) + return -EINVAL; + + if (val->intval < CHG_MIN_CUR) + return -EINVAL; + + ret = regmap_write(info->rn5t618->regmap, RN5T618_REGISET2, + 0xE0 | TO_CUR_REG(val->intval)); + if (ret < 0) + return ret; + + break; + default: + return -EINVAL; + } + + return 0; +} + +static int rn5t618_usb_property_is_writeable(struct power_supply *psy, + enum power_supply_property psp) +{ + switch (psp) { + case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: + return true; + default: + return false; + } +} + static const struct power_supply_desc rn5t618_battery_desc = { .name = "rn5t618-battery", .type = POWER_SUPPLY_TYPE_BATTERY, .properties = rn5t618_battery_props, .num_properties = ARRAY_SIZE(rn5t618_battery_props), .get_property = rn5t618_battery_get_property, + .set_property = rn5t618_battery_set_property, + .property_is_writeable = rn5t618_battery_property_is_writeable, }; static const struct power_supply_desc rn5t618_adp_desc = { @@ -467,6 +651,8 @@ static const struct power_supply_desc rn5t618_adp_desc = { .properties = rn5t618_adp_props, .num_properties = ARRAY_SIZE(rn5t618_adp_props), .get_property = rn5t618_adp_get_property, + .set_property = rn5t618_adp_set_property, + .property_is_writeable = rn5t618_adp_property_is_writeable, }; static const struct power_supply_desc rn5t618_usb_desc = { @@ -477,6 +663,8 @@ static const struct power_supply_desc rn5t618_usb_desc = { .properties = rn5t618_usb_props, .num_properties = ARRAY_SIZE(rn5t618_usb_props), .get_property = rn5t618_usb_get_property, + .set_property = rn5t618_usb_set_property, + .property_is_writeable = rn5t618_usb_property_is_writeable, }; static irqreturn_t rn5t618_charger_irq(int irq, void *data) From b49a81d0fd021a1f0c8660fa40ad9984d9d1f5b7 Mon Sep 17 00:00:00 2001 From: Ikjoon Jang Date: Wed, 26 May 2021 19:16:04 +0800 Subject: [PATCH 19/42] power: supply: sbs-battery: cache constant string properties Currently sbs-battery supports three string properties - manufacturer, model_name, and chemistry. Buffers for those properties are currently defined as global variables. This patch moves those global variables into struct sbs_info and cache/reuse them as they are all constant values. Signed-off-by: Ikjoon Jang Tested-by: Hsin-Yi Wang Signed-off-by: Sebastian Reichel --- drivers/power/supply/sbs-battery.c | 153 ++++++++++++++++++----------- 1 file changed, 95 insertions(+), 58 deletions(-) diff --git a/drivers/power/supply/sbs-battery.c b/drivers/power/supply/sbs-battery.c index 8d7a10730e43..f84dbaab283a 100644 --- a/drivers/power/supply/sbs-battery.c +++ b/drivers/power/supply/sbs-battery.c @@ -189,6 +189,14 @@ static const enum power_supply_property sbs_properties[] = { /* Supports special manufacturer commands from TI BQ20Z65 and BQ20Z75 IC. */ #define SBS_FLAGS_TI_BQ20ZX5 BIT(0) +static const enum power_supply_property string_properties[] = { + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_MANUFACTURER, + POWER_SUPPLY_PROP_MODEL_NAME, +}; + +#define NR_STRING_BUFFERS ARRAY_SIZE(string_properties) + struct sbs_info { struct i2c_client *client; struct power_supply *power_supply; @@ -202,11 +210,32 @@ struct sbs_info { struct delayed_work work; struct mutex mode_lock; u32 flags; + int technology; + char strings[NR_STRING_BUFFERS][I2C_SMBUS_BLOCK_MAX + 1]; }; -static char model_name[I2C_SMBUS_BLOCK_MAX + 1]; -static char manufacturer[I2C_SMBUS_BLOCK_MAX + 1]; -static char chemistry[I2C_SMBUS_BLOCK_MAX + 1]; +static char *sbs_get_string_buf(struct sbs_info *chip, + enum power_supply_property psp) +{ + int i = 0; + + for (i = 0; i < NR_STRING_BUFFERS; i++) + if (string_properties[i] == psp) + return chip->strings[i]; + + return ERR_PTR(-EINVAL); +} + +static void sbs_invalidate_cached_props(struct sbs_info *chip) +{ + int i = 0; + + chip->technology = -1; + + for (i = 0; i < NR_STRING_BUFFERS; i++) + chip->strings[i][0] = 0; +} + static bool force_load; static int sbs_read_word_data(struct i2c_client *client, u8 address); @@ -244,6 +273,7 @@ static int sbs_update_presence(struct sbs_info *chip, bool is_present) chip->is_present = false; /* Disable PEC when no device is present */ client->flags &= ~I2C_CLIENT_PEC; + sbs_invalidate_cached_props(chip); return 0; } @@ -640,17 +670,45 @@ static int sbs_get_battery_property(struct i2c_client *client, return 0; } -static int sbs_get_battery_string_property(struct i2c_client *client, - int reg_offset, enum power_supply_property psp, char *val) +static int sbs_get_property_index(struct i2c_client *client, + enum power_supply_property psp) { - s32 ret; + int count; - ret = sbs_read_string_data(client, sbs_data[reg_offset].addr, val); + for (count = 0; count < ARRAY_SIZE(sbs_data); count++) + if (psp == sbs_data[count].psp) + return count; - if (ret < 0) - return ret; + dev_warn(&client->dev, + "%s: Invalid Property - %d\n", __func__, psp); - return 0; + return -EINVAL; +} + +static const char *sbs_get_constant_string(struct sbs_info *chip, + enum power_supply_property psp) +{ + int ret; + char *buf; + u8 addr; + + buf = sbs_get_string_buf(chip, psp); + if (IS_ERR(buf)) + return buf; + + if (!buf[0]) { + ret = sbs_get_property_index(chip->client, psp); + if (ret < 0) + return ERR_PTR(ret); + + addr = sbs_data[ret].addr; + + ret = sbs_read_string_data(chip->client, addr, buf); + if (ret < 0) + return ERR_PTR(ret); + } + + return buf; } static void sbs_unit_adjustment(struct i2c_client *client, @@ -773,48 +831,36 @@ static int sbs_get_battery_serial_number(struct i2c_client *client, return 0; } -static int sbs_get_property_index(struct i2c_client *client, - enum power_supply_property psp) -{ - int count; - for (count = 0; count < ARRAY_SIZE(sbs_data); count++) - if (psp == sbs_data[count].psp) - return count; - - dev_warn(&client->dev, - "%s: Invalid Property - %d\n", __func__, psp); - - return -EINVAL; -} - -static int sbs_get_chemistry(struct i2c_client *client, +static int sbs_get_chemistry(struct sbs_info *chip, union power_supply_propval *val) { - enum power_supply_property psp = POWER_SUPPLY_PROP_TECHNOLOGY; - int ret; + const char *chemistry; - ret = sbs_get_property_index(client, psp); - if (ret < 0) - return ret; + if (chip->technology != -1) { + val->intval = chip->technology; + return 0; + } - ret = sbs_get_battery_string_property(client, ret, psp, - chemistry); - if (ret < 0) - return ret; + chemistry = sbs_get_constant_string(chip, POWER_SUPPLY_PROP_TECHNOLOGY); + + if (IS_ERR(chemistry)) + return PTR_ERR(chemistry); if (!strncasecmp(chemistry, "LION", 4)) - val->intval = POWER_SUPPLY_TECHNOLOGY_LION; + chip->technology = POWER_SUPPLY_TECHNOLOGY_LION; else if (!strncasecmp(chemistry, "LiP", 3)) - val->intval = POWER_SUPPLY_TECHNOLOGY_LIPO; + chip->technology = POWER_SUPPLY_TECHNOLOGY_LIPO; else if (!strncasecmp(chemistry, "NiCd", 4)) - val->intval = POWER_SUPPLY_TECHNOLOGY_NiCd; + chip->technology = POWER_SUPPLY_TECHNOLOGY_NiCd; else if (!strncasecmp(chemistry, "NiMH", 4)) - val->intval = POWER_SUPPLY_TECHNOLOGY_NiMH; + chip->technology = POWER_SUPPLY_TECHNOLOGY_NiMH; else - val->intval = POWER_SUPPLY_TECHNOLOGY_UNKNOWN; + chip->technology = POWER_SUPPLY_TECHNOLOGY_UNKNOWN; - if (val->intval == POWER_SUPPLY_TECHNOLOGY_UNKNOWN) - dev_warn(&client->dev, "Unknown chemistry: %s\n", chemistry); + if (chip->technology == POWER_SUPPLY_TECHNOLOGY_UNKNOWN) + dev_warn(&chip->client->dev, "Unknown chemistry: %s\n", chemistry); + + val->intval = chip->technology; return 0; } @@ -858,6 +904,7 @@ static int sbs_get_property(struct power_supply *psy, int ret = 0; struct sbs_info *chip = power_supply_get_drvdata(psy); struct i2c_client *client = chip->client; + const char *str; if (chip->gpio_detect) { ret = gpiod_get_value_cansleep(chip->gpio_detect); @@ -883,7 +930,7 @@ static int sbs_get_property(struct power_supply *psy, break; case POWER_SUPPLY_PROP_TECHNOLOGY: - ret = sbs_get_chemistry(client, val); + ret = sbs_get_chemistry(chip, val); if (ret < 0) break; @@ -935,23 +982,12 @@ static int sbs_get_property(struct power_supply *psy, break; case POWER_SUPPLY_PROP_MODEL_NAME: - ret = sbs_get_property_index(client, psp); - if (ret < 0) - break; - - ret = sbs_get_battery_string_property(client, ret, psp, - model_name); - val->strval = model_name; - break; - case POWER_SUPPLY_PROP_MANUFACTURER: - ret = sbs_get_property_index(client, psp); - if (ret < 0) - break; - - ret = sbs_get_battery_string_property(client, ret, psp, - manufacturer); - val->strval = manufacturer; + str = sbs_get_constant_string(chip, psp); + if (IS_ERR(str)) + ret = PTR_ERR(str); + else + val->strval = str; break; case POWER_SUPPLY_PROP_MANUFACTURE_YEAR: @@ -1098,6 +1134,7 @@ static int sbs_probe(struct i2c_client *client) psy_cfg.of_node = client->dev.of_node; psy_cfg.drv_data = chip; chip->last_state = POWER_SUPPLY_STATUS_UNKNOWN; + sbs_invalidate_cached_props(chip); mutex_init(&chip->mode_lock); /* use pdata if available, fall back to DT properties, From 601423bc0c06467d019cf2a446962a5bf1b5e330 Mon Sep 17 00:00:00 2001 From: Maximilian Luz Date: Tue, 11 May 2021 11:24:21 +0200 Subject: [PATCH 20/42] power: supply: surface-charger: Fix type of integer variable The ac->state field is __le32, not u32. So change the variable we're temporarily storing it in to __le32 as well. Reported-by: kernel test robot Fixes: e61ffb344591 ("power: supply: Add AC driver for Surface Aggregator Module") Signed-off-by: Maximilian Luz Signed-off-by: Sebastian Reichel --- drivers/power/supply/surface_charger.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/power/supply/surface_charger.c b/drivers/power/supply/surface_charger.c index 81a5b79822c9..a060c36c7766 100644 --- a/drivers/power/supply/surface_charger.c +++ b/drivers/power/supply/surface_charger.c @@ -66,7 +66,7 @@ struct spwr_ac_device { static int spwr_ac_update_unlocked(struct spwr_ac_device *ac) { - u32 old = ac->state; + __le32 old = ac->state; int status; lockdep_assert_held(&ac->lock); From 7fbf6b731bca347700e460d94b130f9d734b33e9 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Wed, 26 May 2021 13:20:35 -0400 Subject: [PATCH 21/42] power: supply: max17042: Do not enforce (incorrect) interrupt trigger type Interrupt line can be configured on different hardware in different way, even inverted. Therefore driver should not enforce specific trigger type - edge falling - but instead rely on Devicetree to configure it. The Maxim 17047/77693 datasheets describe the interrupt line as active low with a requirement of acknowledge from the CPU therefore the edge falling is not correct. The interrupt line is shared between PMIC and RTC driver, so using level sensitive interrupt is here especially important to avoid races. With an edge configuration in case if first PMIC signals interrupt followed shortly after by the RTC, the interrupt might not be yet cleared/acked thus the second one would not be noticed. Signed-off-by: Krzysztof Kozlowski Signed-off-by: Sebastian Reichel --- drivers/power/supply/max17042_battery.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/power/supply/max17042_battery.c b/drivers/power/supply/max17042_battery.c index 1d7326cd8fc6..ce2041b30a06 100644 --- a/drivers/power/supply/max17042_battery.c +++ b/drivers/power/supply/max17042_battery.c @@ -1104,7 +1104,7 @@ static int max17042_probe(struct i2c_client *client, } if (client->irq) { - unsigned int flags = IRQF_TRIGGER_FALLING | IRQF_ONESHOT; + unsigned int flags = IRQF_ONESHOT; /* * On ACPI systems the IRQ may be handled by ACPI-event code, From 8bb2314fc22628333d89df83d695ff9a8d2a6eac Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Wed, 26 May 2021 13:20:36 -0400 Subject: [PATCH 22/42] power: supply: max17040: Do not enforce (incorrect) interrupt trigger type Interrupt line can be configured on different hardware in different way, even inverted. Therefore driver should not enforce specific trigger type - edge falling - but instead rely on Devicetree to configure it. The Maxim 14577/77836 datasheets describe the interrupt line as active low with a requirement of acknowledge from the CPU therefore the edge falling is not correct. Signed-off-by: Krzysztof Kozlowski Acked-by: Iskren Chernev Acked-by: Rob Herring Signed-off-by: Sebastian Reichel --- .../devicetree/bindings/power/supply/maxim,max17040.yaml | 2 +- drivers/power/supply/max17040_battery.c | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/Documentation/devicetree/bindings/power/supply/maxim,max17040.yaml b/Documentation/devicetree/bindings/power/supply/maxim,max17040.yaml index de91cf3f058c..f792d06db413 100644 --- a/Documentation/devicetree/bindings/power/supply/maxim,max17040.yaml +++ b/Documentation/devicetree/bindings/power/supply/maxim,max17040.yaml @@ -89,7 +89,7 @@ examples: reg = <0x36>; maxim,alert-low-soc-level = <10>; interrupt-parent = <&gpio7>; - interrupts = <2 IRQ_TYPE_EDGE_FALLING>; + interrupts = <2 IRQ_TYPE_LEVEL_LOW>; wakeup-source; }; }; diff --git a/drivers/power/supply/max17040_battery.c b/drivers/power/supply/max17040_battery.c index 1aab868adabf..e80dd9141ae7 100644 --- a/drivers/power/supply/max17040_battery.c +++ b/drivers/power/supply/max17040_battery.c @@ -361,12 +361,10 @@ static irqreturn_t max17040_thread_handler(int id, void *dev) static int max17040_enable_alert_irq(struct max17040_chip *chip) { struct i2c_client *client = chip->client; - unsigned int flags; int ret; - flags = IRQF_TRIGGER_FALLING | IRQF_ONESHOT; ret = devm_request_threaded_irq(&client->dev, client->irq, NULL, - max17040_thread_handler, flags, + max17040_thread_handler, IRQF_ONESHOT, chip->battery->desc->name, chip); return ret; From 4b53bdd53a90cd660918766f445c0ee16fe57cf1 Mon Sep 17 00:00:00 2001 From: Matti Vaittinen Date: Tue, 25 May 2021 13:15:47 +0300 Subject: [PATCH 23/42] power: supply: Drop BD70528 support The only known BD70528 use-cases are such that the PMIC is controlled from separate MCU which is not running Linux. I am not aware of any Linux driver users. Furthermore, it seems there is no demand for this IC. Let's ease the maintenance burden and drop the driver. We can always add it back if there is sudden need for it. Signed-off-by: Matti Vaittinen Signed-off-by: Sebastian Reichel --- drivers/power/supply/Kconfig | 9 - drivers/power/supply/Makefile | 1 - drivers/power/supply/bd70528-charger.c | 710 ------------------------- 3 files changed, 720 deletions(-) delete mode 100644 drivers/power/supply/bd70528-charger.c diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig index e696364126f1..b99d19f48769 100644 --- a/drivers/power/supply/Kconfig +++ b/drivers/power/supply/Kconfig @@ -760,15 +760,6 @@ config CHARGER_UCS1002 Say Y to enable support for Microchip UCS1002 Programmable USB Port Power Controller with Charger Emulation. -config CHARGER_BD70528 - tristate "ROHM bd70528 charger driver" - depends on MFD_ROHM_BD70528 - select LINEAR_RANGES - help - Say Y here to enable support for getting battery status - information and altering charger configurations from charger - block of the ROHM BD70528 Power Management IC. - config CHARGER_BD99954 tristate "ROHM bd99954 charger driver" depends on I2C diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile index 16ebfaf6d55b..33059a91f60c 100644 --- a/drivers/power/supply/Makefile +++ b/drivers/power/supply/Makefile @@ -96,7 +96,6 @@ 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 -obj-$(CONFIG_CHARGER_BD70528) += bd70528-charger.o obj-$(CONFIG_CHARGER_BD99954) += bd99954-charger.o obj-$(CONFIG_CHARGER_WILCO) += wilco-charger.o obj-$(CONFIG_RN5T618_POWER) += rn5t618_power.o diff --git a/drivers/power/supply/bd70528-charger.c b/drivers/power/supply/bd70528-charger.c deleted file mode 100644 index 7c1f0b99c71b..000000000000 --- a/drivers/power/supply/bd70528-charger.c +++ /dev/null @@ -1,710 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -// -// Copyright (C) 2018 ROHM Semiconductors -// -// power-supply driver for ROHM BD70528 PMIC - -/* - * BD70528 charger HW state machine. - * - * The thermal shutdown state is not drawn. From any other state but - * battery error and suspend it is possible to go to TSD/TMP states - * if temperature is out of bounds. - * - * CHG_RST = H - * or CHG_EN=L - * or (DCIN2_UVLO=L && DCIN1_UVLO=L) - * or (DCIN2_OVLO=H & DCIN1_UVKLO=L) - * - * +--------------+ +--------------+ - * | | | | - * | Any state +-------> | Suspend | - * | | | | - * +--------------+ +------+-------+ - * | - * CHG_EN = H && BAT_DET = H && | - * No errors (temp, bat_ov, UVLO, | - * OVLO...) | - * | - * BAT_OV or +---------v----------+ - * (DBAT && TTRI) | | - * +-----------------+ Trickle Charge | <---------------+ - * | | | | - * | +-------+------------+ | - * | | | - * | | ^ | - * | V_BAT > VTRI_TH | | VBAT < VTRI_TH - 50mV | - * | | | | - * | v | | - * | | | - * | BAT_OV or +----------+----+ | - * | (DBAT && TFST) | | | - * | +----------------+ Fast Charge | | - * | | | | | - * v v +----+----------+ | - * | | - *+----------------+ ILIM_DET=L | ^ ILIM_DET | - *| | & CV_DET=H | | or CV_DET=L | - *| Battery Error | & VBAT > | | or VBAT < VRECHG_TH | - *| | VRECHG_TH | | or IBAT > IFST/x | - *+----------------+ & IBAT < | | | - * IFST/x v | | - * ^ | | - * | +---------+-+ | - * | | | | - * +-------------------+ Top OFF | | - * BAT_OV = H or | | | - * (DBAT && TFST) +-----+-----+ | - * | | - * Stay top-off for 15s | | - * v | - * | - * +--------+ | - * | | | - * | Done +-------------------------+ - * | | - * +--------+ VBAT < VRECHG_TH - */ - -#include -#include -#include -#include -#include -#include -#include - -#define CHG_STAT_SUSPEND 0x0 -#define CHG_STAT_TRICKLE 0x1 -#define CHG_STAT_FAST 0x3 -#define CHG_STAT_TOPOFF 0xe -#define CHG_STAT_DONE 0xf -#define CHG_STAT_OTP_TRICKLE 0x10 -#define CHG_STAT_OTP_FAST 0x11 -#define CHG_STAT_OTP_DONE 0x12 -#define CHG_STAT_TSD_TRICKLE 0x20 -#define CHG_STAT_TSD_FAST 0x21 -#define CHG_STAT_TSD_TOPOFF 0x22 -#define CHG_STAT_BAT_ERR 0x7f - -static const char *bd70528_charger_model = "BD70528"; -static const char *bd70528_charger_manufacturer = "ROHM Semiconductors"; - -#define BD_ERR_IRQ_HND(_name_, _wrn_) \ -static irqreturn_t bd0528_##_name_##_interrupt(int irq, void *arg) \ -{ \ - struct power_supply *psy = (struct power_supply *)arg; \ - \ - power_supply_changed(psy); \ - dev_err(&psy->dev, (_wrn_)); \ - \ - return IRQ_HANDLED; \ -} - -#define BD_INFO_IRQ_HND(_name_, _wrn_) \ -static irqreturn_t bd0528_##_name_##_interrupt(int irq, void *arg) \ -{ \ - struct power_supply *psy = (struct power_supply *)arg; \ - \ - power_supply_changed(psy); \ - dev_dbg(&psy->dev, (_wrn_)); \ - \ - return IRQ_HANDLED; \ -} - -#define BD_IRQ_HND(_name_) bd0528_##_name_##_interrupt - -struct bd70528_psy { - struct regmap *regmap; - struct device *dev; - struct power_supply *psy; -}; - -BD_ERR_IRQ_HND(BAT_OV_DET, "Battery overvoltage detected\n"); -BD_ERR_IRQ_HND(DBAT_DET, "Dead battery detected\n"); -BD_ERR_IRQ_HND(COLD_DET, "Battery cold\n"); -BD_ERR_IRQ_HND(HOT_DET, "Battery hot\n"); -BD_ERR_IRQ_HND(CHG_TSD, "Charger thermal shutdown\n"); -BD_ERR_IRQ_HND(DCIN2_OV_DET, "DCIN2 overvoltage detected\n"); - -BD_INFO_IRQ_HND(BAT_OV_RES, "Battery voltage back to normal\n"); -BD_INFO_IRQ_HND(COLD_RES, "Battery temperature back to normal\n"); -BD_INFO_IRQ_HND(HOT_RES, "Battery temperature back to normal\n"); -BD_INFO_IRQ_HND(BAT_RMV, "Battery removed\n"); -BD_INFO_IRQ_HND(BAT_DET, "Battery detected\n"); -BD_INFO_IRQ_HND(DCIN2_OV_RES, "DCIN2 voltage back to normal\n"); -BD_INFO_IRQ_HND(DCIN2_RMV, "DCIN2 removed\n"); -BD_INFO_IRQ_HND(DCIN2_DET, "DCIN2 detected\n"); -BD_INFO_IRQ_HND(DCIN1_RMV, "DCIN1 removed\n"); -BD_INFO_IRQ_HND(DCIN1_DET, "DCIN1 detected\n"); - -struct irq_name_pair { - const char *n; - irqreturn_t (*h)(int irq, void *arg); -}; - -static int bd70528_get_irqs(struct platform_device *pdev, - struct bd70528_psy *bdpsy) -{ - int irq, i, ret; - unsigned int mask; - static const struct irq_name_pair bd70528_chg_irqs[] = { - { .n = "bd70528-bat-ov-res", .h = BD_IRQ_HND(BAT_OV_RES) }, - { .n = "bd70528-bat-ov-det", .h = BD_IRQ_HND(BAT_OV_DET) }, - { .n = "bd70528-bat-dead", .h = BD_IRQ_HND(DBAT_DET) }, - { .n = "bd70528-bat-warmed", .h = BD_IRQ_HND(COLD_RES) }, - { .n = "bd70528-bat-cold", .h = BD_IRQ_HND(COLD_DET) }, - { .n = "bd70528-bat-cooled", .h = BD_IRQ_HND(HOT_RES) }, - { .n = "bd70528-bat-hot", .h = BD_IRQ_HND(HOT_DET) }, - { .n = "bd70528-chg-tshd", .h = BD_IRQ_HND(CHG_TSD) }, - { .n = "bd70528-bat-removed", .h = BD_IRQ_HND(BAT_RMV) }, - { .n = "bd70528-bat-detected", .h = BD_IRQ_HND(BAT_DET) }, - { .n = "bd70528-dcin2-ov-res", .h = BD_IRQ_HND(DCIN2_OV_RES) }, - { .n = "bd70528-dcin2-ov-det", .h = BD_IRQ_HND(DCIN2_OV_DET) }, - { .n = "bd70528-dcin2-removed", .h = BD_IRQ_HND(DCIN2_RMV) }, - { .n = "bd70528-dcin2-detected", .h = BD_IRQ_HND(DCIN2_DET) }, - { .n = "bd70528-dcin1-removed", .h = BD_IRQ_HND(DCIN1_RMV) }, - { .n = "bd70528-dcin1-detected", .h = BD_IRQ_HND(DCIN1_DET) }, - }; - - for (i = 0; i < ARRAY_SIZE(bd70528_chg_irqs); i++) { - irq = platform_get_irq_byname(pdev, bd70528_chg_irqs[i].n); - if (irq < 0) { - dev_err(&pdev->dev, "Bad IRQ information for %s (%d)\n", - bd70528_chg_irqs[i].n, irq); - return irq; - } - ret = devm_request_threaded_irq(&pdev->dev, irq, NULL, - bd70528_chg_irqs[i].h, - IRQF_ONESHOT, - bd70528_chg_irqs[i].n, - bdpsy->psy); - - if (ret) - return ret; - } - /* - * BD70528 irq controller is not touching the main mask register. - * So enable the charger block interrupts at main level. We can just - * leave them enabled as irq-controller should disable irqs - * from sub-registers when IRQ is disabled or freed. - */ - mask = BD70528_REG_INT_BAT1_MASK | BD70528_REG_INT_BAT2_MASK; - ret = regmap_update_bits(bdpsy->regmap, - BD70528_REG_INT_MAIN_MASK, mask, 0); - if (ret) - dev_err(&pdev->dev, "Failed to enable charger IRQs\n"); - - return ret; -} - -static int bd70528_get_charger_status(struct bd70528_psy *bdpsy, int *val) -{ - int ret; - unsigned int v; - - ret = regmap_read(bdpsy->regmap, BD70528_REG_CHG_CURR_STAT, &v); - if (ret) { - dev_err(bdpsy->dev, "Charger state read failure %d\n", - ret); - return ret; - } - - switch (v & BD70528_MASK_CHG_STAT) { - case CHG_STAT_SUSPEND: - /* Maybe we should check the CHG_TTRI_EN? */ - case CHG_STAT_OTP_TRICKLE: - case CHG_STAT_OTP_FAST: - case CHG_STAT_OTP_DONE: - case CHG_STAT_TSD_TRICKLE: - case CHG_STAT_TSD_FAST: - case CHG_STAT_TSD_TOPOFF: - case CHG_STAT_BAT_ERR: - *val = POWER_SUPPLY_STATUS_NOT_CHARGING; - break; - case CHG_STAT_DONE: - *val = POWER_SUPPLY_STATUS_FULL; - break; - case CHG_STAT_TRICKLE: - case CHG_STAT_FAST: - case CHG_STAT_TOPOFF: - *val = POWER_SUPPLY_STATUS_CHARGING; - break; - default: - *val = POWER_SUPPLY_STATUS_UNKNOWN; - break; - } - - return 0; -} - -static int bd70528_get_charge_type(struct bd70528_psy *bdpsy, int *val) -{ - int ret; - unsigned int v; - - ret = regmap_read(bdpsy->regmap, BD70528_REG_CHG_CURR_STAT, &v); - if (ret) { - dev_err(bdpsy->dev, "Charger state read failure %d\n", - ret); - return ret; - } - - switch (v & BD70528_MASK_CHG_STAT) { - case CHG_STAT_TRICKLE: - *val = POWER_SUPPLY_CHARGE_TYPE_TRICKLE; - break; - case CHG_STAT_FAST: - case CHG_STAT_TOPOFF: - *val = POWER_SUPPLY_CHARGE_TYPE_FAST; - break; - case CHG_STAT_DONE: - case CHG_STAT_SUSPEND: - /* Maybe we should check the CHG_TTRI_EN? */ - case CHG_STAT_OTP_TRICKLE: - case CHG_STAT_OTP_FAST: - case CHG_STAT_OTP_DONE: - case CHG_STAT_TSD_TRICKLE: - case CHG_STAT_TSD_FAST: - case CHG_STAT_TSD_TOPOFF: - case CHG_STAT_BAT_ERR: - *val = POWER_SUPPLY_CHARGE_TYPE_NONE; - break; - default: - *val = POWER_SUPPLY_CHARGE_TYPE_UNKNOWN; - break; - } - - return 0; -} - -static int bd70528_get_battery_health(struct bd70528_psy *bdpsy, int *val) -{ - int ret; - unsigned int v; - - ret = regmap_read(bdpsy->regmap, BD70528_REG_CHG_BAT_STAT, &v); - if (ret) { - dev_err(bdpsy->dev, "Battery state read failure %d\n", - ret); - return ret; - } - /* No battery? */ - if (!(v & BD70528_MASK_CHG_BAT_DETECT)) - *val = POWER_SUPPLY_HEALTH_DEAD; - else if (v & BD70528_MASK_CHG_BAT_OVERVOLT) - *val = POWER_SUPPLY_HEALTH_OVERVOLTAGE; - else if (v & BD70528_MASK_CHG_BAT_TIMER) - *val = POWER_SUPPLY_HEALTH_SAFETY_TIMER_EXPIRE; - else - *val = POWER_SUPPLY_HEALTH_GOOD; - - return 0; -} - -static int bd70528_get_online(struct bd70528_psy *bdpsy, int *val) -{ - int ret; - unsigned int v; - - ret = regmap_read(bdpsy->regmap, BD70528_REG_CHG_IN_STAT, &v); - if (ret) { - dev_err(bdpsy->dev, "DC1 IN state read failure %d\n", - ret); - return ret; - } - - *val = (v & BD70528_MASK_CHG_DCIN1_UVLO) ? 1 : 0; - - return 0; -} - -static int bd70528_get_present(struct bd70528_psy *bdpsy, int *val) -{ - int ret; - unsigned int v; - - ret = regmap_read(bdpsy->regmap, BD70528_REG_CHG_BAT_STAT, &v); - if (ret) { - dev_err(bdpsy->dev, "Battery state read failure %d\n", - ret); - return ret; - } - - *val = (v & BD70528_MASK_CHG_BAT_DETECT) ? 1 : 0; - - return 0; -} - -static const struct linear_range current_limit_ranges[] = { - { - .min = 5, - .step = 1, - .min_sel = 0, - .max_sel = 0x22, - }, - { - .min = 40, - .step = 5, - .min_sel = 0x23, - .max_sel = 0x26, - }, - { - .min = 60, - .step = 20, - .min_sel = 0x27, - .max_sel = 0x2d, - }, - { - .min = 200, - .step = 50, - .min_sel = 0x2e, - .max_sel = 0x34, - }, - { - .min = 500, - .step = 0, - .min_sel = 0x35, - .max_sel = 0x3f, - }, -}; - -/* - * BD70528 would support setting and getting own charge current/ - * voltage for low temperatures. The driver currently only reads - * the charge current at room temperature. We do set both though. - */ -static const struct linear_range warm_charge_curr[] = { - { - .min = 10, - .step = 10, - .min_sel = 0, - .max_sel = 0x12, - }, - { - .min = 200, - .step = 25, - .min_sel = 0x13, - .max_sel = 0x1f, - }, -}; - -/* - * Cold charge current selectors are identical to warm charge current - * selectors. The difference is that only smaller currents are available - * at cold charge range. - */ -#define MAX_COLD_CHG_CURR_SEL 0x15 -#define MAX_WARM_CHG_CURR_SEL 0x1f -#define MIN_CHG_CURR_SEL 0x0 - -static int get_charge_current(struct bd70528_psy *bdpsy, int *ma) -{ - unsigned int sel; - int ret; - - ret = regmap_read(bdpsy->regmap, BD70528_REG_CHG_CHG_CURR_WARM, - &sel); - if (ret) { - dev_err(bdpsy->dev, - "Charge current reading failed (%d)\n", ret); - return ret; - } - - sel &= BD70528_MASK_CHG_CHG_CURR; - - ret = linear_range_get_value_array(&warm_charge_curr[0], - ARRAY_SIZE(warm_charge_curr), - sel, ma); - if (ret) { - dev_err(bdpsy->dev, - "Unknown charge current value 0x%x\n", - sel); - } - - return ret; -} - -static int get_current_limit(struct bd70528_psy *bdpsy, int *ma) -{ - unsigned int sel; - int ret; - - ret = regmap_read(bdpsy->regmap, BD70528_REG_CHG_DCIN_ILIM, - &sel); - - if (ret) { - dev_err(bdpsy->dev, - "Input current limit reading failed (%d)\n", ret); - return ret; - } - - sel &= BD70528_MASK_CHG_DCIN_ILIM; - - ret = linear_range_get_value_array(¤t_limit_ranges[0], - ARRAY_SIZE(current_limit_ranges), - sel, ma); - if (ret) { - /* Unspecified values mean 500 mA */ - *ma = 500; - } - return 0; -} - -static enum power_supply_property bd70528_charger_props[] = { - POWER_SUPPLY_PROP_STATUS, - POWER_SUPPLY_PROP_CHARGE_TYPE, - POWER_SUPPLY_PROP_HEALTH, - POWER_SUPPLY_PROP_PRESENT, - POWER_SUPPLY_PROP_ONLINE, - POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT, - POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT, - POWER_SUPPLY_PROP_MODEL_NAME, - POWER_SUPPLY_PROP_MANUFACTURER, -}; - -static int bd70528_charger_get_property(struct power_supply *psy, - enum power_supply_property psp, - union power_supply_propval *val) -{ - struct bd70528_psy *bdpsy = power_supply_get_drvdata(psy); - int ret = 0; - - switch (psp) { - case POWER_SUPPLY_PROP_STATUS: - return bd70528_get_charger_status(bdpsy, &val->intval); - case POWER_SUPPLY_PROP_CHARGE_TYPE: - return bd70528_get_charge_type(bdpsy, &val->intval); - case POWER_SUPPLY_PROP_HEALTH: - return bd70528_get_battery_health(bdpsy, &val->intval); - case POWER_SUPPLY_PROP_PRESENT: - return bd70528_get_present(bdpsy, &val->intval); - case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: - ret = get_current_limit(bdpsy, &val->intval); - val->intval *= 1000; - return ret; - case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: - ret = get_charge_current(bdpsy, &val->intval); - val->intval *= 1000; - return ret; - case POWER_SUPPLY_PROP_ONLINE: - return bd70528_get_online(bdpsy, &val->intval); - case POWER_SUPPLY_PROP_MODEL_NAME: - val->strval = bd70528_charger_model; - return 0; - case POWER_SUPPLY_PROP_MANUFACTURER: - val->strval = bd70528_charger_manufacturer; - return 0; - default: - break; - } - - return -EINVAL; -} - -static int bd70528_prop_is_writable(struct power_supply *psy, - enum power_supply_property psp) -{ - switch (psp) { - case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: - case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: - return 1; - default: - break; - } - return 0; -} - -static int set_charge_current(struct bd70528_psy *bdpsy, int ma) -{ - unsigned int reg; - int ret = 0, tmpret; - bool found; - - if (ma > 500) { - dev_warn(bdpsy->dev, - "Requested charge current %u exceed maximum (500mA)\n", - ma); - reg = MAX_WARM_CHG_CURR_SEL; - goto set; - } - if (ma < 10) { - dev_err(bdpsy->dev, - "Requested charge current %u smaller than min (10mA)\n", - ma); - reg = MIN_CHG_CURR_SEL; - ret = -EINVAL; - goto set; - } - -/* - * For BD70528 voltage/current limits we happily accept any value which - * belongs the range. We could check if value matching the selector is - * desired by computing the range min + (sel - sel_low) * range step - but - * I guess it is enough if we use voltage/current which is closest (below) - * the requested? - */ - - ret = linear_range_get_selector_low_array(warm_charge_curr, - ARRAY_SIZE(warm_charge_curr), - ma, ®, &found); - if (ret) { - dev_err(bdpsy->dev, - "Unsupported charge current %u mA\n", ma); - reg = MIN_CHG_CURR_SEL; - goto set; - } - if (!found) { - /* - * There was a gap in supported values and we hit it. - * Yet a smaller value was found so we use it. - */ - dev_warn(bdpsy->dev, - "Unsupported charge current %u mA\n", ma); - } -set: - - tmpret = regmap_update_bits(bdpsy->regmap, - BD70528_REG_CHG_CHG_CURR_WARM, - BD70528_MASK_CHG_CHG_CURR, reg); - if (tmpret) - dev_err(bdpsy->dev, - "Charge current write failure (%d)\n", tmpret); - - if (reg > MAX_COLD_CHG_CURR_SEL) - reg = MAX_COLD_CHG_CURR_SEL; - - if (!tmpret) - tmpret = regmap_update_bits(bdpsy->regmap, - BD70528_REG_CHG_CHG_CURR_COLD, - BD70528_MASK_CHG_CHG_CURR, reg); - - if (!ret) - ret = tmpret; - - return ret; -} - -#define MAX_CURR_LIMIT_SEL 0x34 -#define MIN_CURR_LIMIT_SEL 0x0 - -static int set_current_limit(struct bd70528_psy *bdpsy, int ma) -{ - unsigned int reg; - int ret = 0, tmpret; - bool found; - - if (ma > 500) { - dev_warn(bdpsy->dev, - "Requested current limit %u exceed maximum (500mA)\n", - ma); - reg = MAX_CURR_LIMIT_SEL; - goto set; - } - if (ma < 5) { - dev_err(bdpsy->dev, - "Requested current limit %u smaller than min (5mA)\n", - ma); - reg = MIN_CURR_LIMIT_SEL; - ret = -EINVAL; - goto set; - } - - ret = linear_range_get_selector_low_array(current_limit_ranges, - ARRAY_SIZE(current_limit_ranges), - ma, ®, &found); - if (ret) { - dev_err(bdpsy->dev, "Unsupported current limit %umA\n", ma); - reg = MIN_CURR_LIMIT_SEL; - goto set; - } - if (!found) { - /* - * There was a gap in supported values and we hit it. - * We found a smaller value from ranges and use it. - * Warn user though. - */ - dev_warn(bdpsy->dev, "Unsupported current limit %umA\n", ma); - } - -set: - tmpret = regmap_update_bits(bdpsy->regmap, - BD70528_REG_CHG_DCIN_ILIM, - BD70528_MASK_CHG_DCIN_ILIM, reg); - - if (!ret) - ret = tmpret; - - return ret; -} - -static int bd70528_charger_set_property(struct power_supply *psy, - enum power_supply_property psp, - const union power_supply_propval *val) -{ - struct bd70528_psy *bdpsy = power_supply_get_drvdata(psy); - - switch (psp) { - case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: - return set_current_limit(bdpsy, val->intval / 1000); - case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: - return set_charge_current(bdpsy, val->intval / 1000); - default: - break; - } - return -EINVAL; -} - -static const struct power_supply_desc bd70528_charger_desc = { - .name = "bd70528-charger", - .type = POWER_SUPPLY_TYPE_MAINS, - .properties = bd70528_charger_props, - .num_properties = ARRAY_SIZE(bd70528_charger_props), - .get_property = bd70528_charger_get_property, - .set_property = bd70528_charger_set_property, - .property_is_writeable = bd70528_prop_is_writable, -}; - -static int bd70528_power_probe(struct platform_device *pdev) -{ - struct bd70528_psy *bdpsy; - struct power_supply_config cfg = {}; - - bdpsy = devm_kzalloc(&pdev->dev, sizeof(*bdpsy), GFP_KERNEL); - if (!bdpsy) - return -ENOMEM; - - bdpsy->regmap = dev_get_regmap(pdev->dev.parent, NULL); - if (!bdpsy->regmap) { - dev_err(&pdev->dev, "No regmap found for chip\n"); - return -EINVAL; - } - bdpsy->dev = &pdev->dev; - - platform_set_drvdata(pdev, bdpsy); - cfg.drv_data = bdpsy; - cfg.of_node = pdev->dev.parent->of_node; - - bdpsy->psy = devm_power_supply_register(&pdev->dev, - &bd70528_charger_desc, &cfg); - if (IS_ERR(bdpsy->psy)) { - dev_err(&pdev->dev, "failed: power supply register\n"); - return PTR_ERR(bdpsy->psy); - } - - return bd70528_get_irqs(pdev, bdpsy); -} - -static struct platform_driver bd70528_power = { - .driver = { - .name = "bd70528-power" - }, - .probe = bd70528_power_probe, -}; - -module_platform_driver(bd70528_power); - -MODULE_AUTHOR("Matti Vaittinen "); -MODULE_DESCRIPTION("BD70528 power-supply driver"); -MODULE_LICENSE("GPL"); -MODULE_ALIAS("platform:bd70528-power"); From 4a8fd33517daa6020f10c31f609f9ec8a07775f8 Mon Sep 17 00:00:00 2001 From: Stephan Gerhold Date: Mon, 17 May 2021 12:51:11 +0200 Subject: [PATCH 24/42] dt-bindings: power: supply: Add DT schema for richtek,rt5033-battery The RT5033 PMIC provides a simple fuel gauge via I2C. Add a DT schema to describe how to set it up in the device tree. Note that although RT5033 is a MFD with lots of functionality (also charger, regulator, LEDs, ...) the fuel gauge has a separate I2C bus and is not part of the MFD. Cc: Beomho Seo Signed-off-by: Stephan Gerhold Reviewed-by: Rob Herring Signed-off-by: Sebastian Reichel --- .../power/supply/richtek,rt5033-battery.yaml | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 Documentation/devicetree/bindings/power/supply/richtek,rt5033-battery.yaml diff --git a/Documentation/devicetree/bindings/power/supply/richtek,rt5033-battery.yaml b/Documentation/devicetree/bindings/power/supply/richtek,rt5033-battery.yaml new file mode 100644 index 000000000000..ae647d3355a2 --- /dev/null +++ b/Documentation/devicetree/bindings/power/supply/richtek,rt5033-battery.yaml @@ -0,0 +1,54 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: "http://devicetree.org/schemas/power/supply/richtek,rt5033-battery.yaml#" +$schema: "http://devicetree.org/meta-schemas/core.yaml#" + +title: Richtek RT5033 PMIC Fuel Gauge + +maintainers: + - Stephan Gerhold + +allOf: + - $ref: power-supply.yaml# + +properties: + compatible: + const: richtek,rt5033-battery + + reg: + maxItems: 1 + + interrupts: + maxItems: 1 + +required: + - compatible + - reg + +additionalProperties: false + +examples: + - | + i2c { + #address-cells = <1>; + #size-cells = <0>; + + battery@35 { + compatible = "richtek,rt5033-battery"; + reg = <0x35>; + }; + }; + - | + #include + i2c { + #address-cells = <1>; + #size-cells = <0>; + + battery@35 { + compatible = "richtek,rt5033-battery"; + reg = <0x35>; + interrupt-parent = <&msmgpio>; + interrupts = <121 IRQ_TYPE_EDGE_FALLING>; + }; + }; From f3076cd8d1d5fa64b5e1fa5affc045c2fc123baa Mon Sep 17 00:00:00 2001 From: Stephan Gerhold Date: Mon, 17 May 2021 12:51:12 +0200 Subject: [PATCH 25/42] power: supply: rt5033_battery: Fix device tree enumeration The fuel gauge in the RT5033 PMIC has its own I2C bus and interrupt line. Therefore, it is not actually part of the RT5033 MFD and needs its own of_match_table to probe properly. Also, given that it's independent of the MFD, there is actually no need to make the Kconfig depend on MFD_RT5033. Although the driver uses the shared header, there is no compile or runtime dependency on the RT5033 MFD driver. Cc: Beomho Seo Cc: Chanwoo Choi Fixes: b847dd96e659 ("power: rt5033_battery: Add RT5033 Fuel gauge device driver") Signed-off-by: Stephan Gerhold Signed-off-by: Sebastian Reichel --- drivers/power/supply/Kconfig | 3 ++- drivers/power/supply/rt5033_battery.c | 7 +++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig index b99d19f48769..11f5368e810e 100644 --- a/drivers/power/supply/Kconfig +++ b/drivers/power/supply/Kconfig @@ -712,7 +712,8 @@ config BATTERY_GOLDFISH config BATTERY_RT5033 tristate "RT5033 fuel gauge support" - depends on MFD_RT5033 + depends on I2C + select REGMAP_I2C help This adds support for battery fuel gauge in Richtek RT5033 PMIC. The fuelgauge calculates and determines the battery state of charge diff --git a/drivers/power/supply/rt5033_battery.c b/drivers/power/supply/rt5033_battery.c index f330452341f0..9ad0afe83d1b 100644 --- a/drivers/power/supply/rt5033_battery.c +++ b/drivers/power/supply/rt5033_battery.c @@ -164,9 +164,16 @@ static const struct i2c_device_id rt5033_battery_id[] = { }; MODULE_DEVICE_TABLE(i2c, rt5033_battery_id); +static const struct of_device_id rt5033_battery_of_match[] = { + { .compatible = "richtek,rt5033-battery", }, + { } +}; +MODULE_DEVICE_TABLE(of, rt5033_battery_of_match); + static struct i2c_driver rt5033_battery_driver = { .driver = { .name = "rt5033-battery", + .of_match_table = rt5033_battery_of_match, }, .probe = rt5033_battery_probe, .remove = rt5033_battery_remove, From ed3443fb4df4e140a22f65144546c8a8e1e27f4e Mon Sep 17 00:00:00 2001 From: Bixuan Cui Date: Sat, 8 May 2021 11:14:59 +0800 Subject: [PATCH 26/42] power: reset: gpio-poweroff: add missing MODULE_DEVICE_TABLE This patch adds missing MODULE_DEVICE_TABLE definition which generates correct modalias for automatic loading of this driver when it is built as an external module. Reported-by: Hulk Robot Signed-off-by: Bixuan Cui Signed-off-by: Sebastian Reichel --- drivers/power/reset/gpio-poweroff.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/power/reset/gpio-poweroff.c b/drivers/power/reset/gpio-poweroff.c index c5067eb75370..1c5af2fef142 100644 --- a/drivers/power/reset/gpio-poweroff.c +++ b/drivers/power/reset/gpio-poweroff.c @@ -90,6 +90,7 @@ static const struct of_device_id of_gpio_poweroff_match[] = { { .compatible = "gpio-poweroff", }, {}, }; +MODULE_DEVICE_TABLE(of, of_gpio_poweroff_match); static struct platform_driver gpio_poweroff_driver = { .probe = gpio_poweroff_probe, From 8ef9f687abff3b8e44578b810a74fdcf753478f3 Mon Sep 17 00:00:00 2001 From: Zhen Lei Date: Tue, 11 May 2021 17:49:19 +0800 Subject: [PATCH 27/42] power: reset: at91-sama5d2_shdwc: Remove redundant error printing in at91_shdwc_probe() When devm_ioremap_resource() fails, a clear enough error message will be printed by its subfunction __devm_ioremap_resource(). The error information contains the device name, failure cause, and possibly resource information. Therefore, remove the error printing here to simplify code and reduce the binary size. Reported-by: Hulk Robot Signed-off-by: Zhen Lei Signed-off-by: Sebastian Reichel --- drivers/power/reset/at91-sama5d2_shdwc.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/power/reset/at91-sama5d2_shdwc.c b/drivers/power/reset/at91-sama5d2_shdwc.c index 125e592af445..d8ecffe72f16 100644 --- a/drivers/power/reset/at91-sama5d2_shdwc.c +++ b/drivers/power/reset/at91-sama5d2_shdwc.c @@ -351,10 +351,8 @@ static int __init at91_shdwc_probe(struct platform_device *pdev) res = platform_get_resource(pdev, IORESOURCE_MEM, 0); at91_shdwc->shdwc_base = devm_ioremap_resource(&pdev->dev, res); - if (IS_ERR(at91_shdwc->shdwc_base)) { - dev_err(&pdev->dev, "Could not map reset controller address\n"); + if (IS_ERR(at91_shdwc->shdwc_base)) return PTR_ERR(at91_shdwc->shdwc_base); - } match = of_match_node(at91_shdwc_of_match, pdev->dev.of_node); at91_shdwc->rcfg = match->data; From c603bec359aece14c5e74a4aa174822692fba7d6 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Fri, 7 May 2021 12:19:25 -0400 Subject: [PATCH 28/42] power: supply: max17040: remove non-working POWER_SUPPLY_PROP_STATUS The driver was reporting POWER_SUPPLY_PROP_STATUS via platform data functions. Without platform data, the max17040_get_status() functions returns early with POWER_SUPPLY_STATUS_UNKNOWN. Since there are no platforms using the driver with platform data (no board files with the driver), the status property was always unknown. Signed-off-by: Krzysztof Kozlowski Signed-off-by: Sebastian Reichel --- drivers/power/supply/max17040_battery.c | 32 +++---------------------- 1 file changed, 3 insertions(+), 29 deletions(-) diff --git a/drivers/power/supply/max17040_battery.c b/drivers/power/supply/max17040_battery.c index e80dd9141ae7..f569af676471 100644 --- a/drivers/power/supply/max17040_battery.c +++ b/drivers/power/supply/max17040_battery.c @@ -147,8 +147,6 @@ struct max17040_chip { /* battery capacity */ int soc; - /* State Of Charge */ - int status; /* Low alert threshold from 32% to 1% of the State of Charge */ u32 low_soc_alert; /* some devices return twice the capacity */ @@ -225,24 +223,6 @@ static int max17040_get_online(struct max17040_chip *chip) chip->pdata->battery_online() : 1; } -static int max17040_get_status(struct max17040_chip *chip) -{ - if (!chip->pdata || !chip->pdata->charger_online - || !chip->pdata->charger_enable) - return POWER_SUPPLY_STATUS_UNKNOWN; - - if (max17040_get_soc(chip) > MAX17040_BATTERY_FULL) - return POWER_SUPPLY_STATUS_FULL; - - if (chip->pdata->charger_online()) - if (chip->pdata->charger_enable()) - return POWER_SUPPLY_STATUS_CHARGING; - else - return POWER_SUPPLY_STATUS_NOT_CHARGING; - else - return POWER_SUPPLY_STATUS_DISCHARGING; -} - static int max17040_get_of_data(struct max17040_chip *chip) { struct device *dev = &chip->client->dev; @@ -283,7 +263,6 @@ static int max17040_get_of_data(struct max17040_chip *chip) static void max17040_check_changes(struct max17040_chip *chip) { chip->soc = max17040_get_soc(chip); - chip->status = max17040_get_status(chip); } static void max17040_queue_work(struct max17040_chip *chip) @@ -302,17 +281,16 @@ static void max17040_stop_work(void *data) static void max17040_work(struct work_struct *work) { struct max17040_chip *chip; - int last_soc, last_status; + int last_soc; chip = container_of(work, struct max17040_chip, work.work); - /* store SOC and status to check changes */ + /* store SOC to check changes */ last_soc = chip->soc; - last_status = chip->status; max17040_check_changes(chip); /* check changes and send uevent */ - if (last_soc != chip->soc || last_status != chip->status) + if (last_soc != chip->soc) power_supply_changed(chip->battery); max17040_queue_work(chip); @@ -413,9 +391,6 @@ static int max17040_get_property(struct power_supply *psy, struct max17040_chip *chip = power_supply_get_drvdata(psy); switch (psp) { - case POWER_SUPPLY_PROP_STATUS: - val->intval = max17040_get_status(chip); - break; case POWER_SUPPLY_PROP_ONLINE: val->intval = max17040_get_online(chip); break; @@ -442,7 +417,6 @@ static const struct regmap_config max17040_regmap = { }; static enum power_supply_property max17040_battery_props[] = { - POWER_SUPPLY_PROP_STATUS, POWER_SUPPLY_PROP_ONLINE, POWER_SUPPLY_PROP_VOLTAGE_NOW, POWER_SUPPLY_PROP_CAPACITY, From ce0ae8324e0953292a9a745ec074497ba9c1c7d3 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Fri, 7 May 2021 12:19:26 -0400 Subject: [PATCH 29/42] power: supply: max17040: simplify POWER_SUPPLY_PROP_ONLINE The driver was reporting POWER_SUPPLY_PROP_ONLINE via platform data functions or '1' if no platform data was provided. Since there are no platforms using the driver with platform data (no board files with the driver), the online property can be simplified to always return '1'. Signed-off-by: Krzysztof Kozlowski Signed-off-by: Sebastian Reichel --- drivers/power/supply/max17040_battery.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/power/supply/max17040_battery.c b/drivers/power/supply/max17040_battery.c index f569af676471..aecc0c840351 100644 --- a/drivers/power/supply/max17040_battery.c +++ b/drivers/power/supply/max17040_battery.c @@ -219,8 +219,7 @@ static int max17040_get_version(struct max17040_chip *chip) static int max17040_get_online(struct max17040_chip *chip) { - return chip->pdata && chip->pdata->battery_online ? - chip->pdata->battery_online() : 1; + return 1; } static int max17040_get_of_data(struct max17040_chip *chip) From cd70c85c5752f060b09b0cf5b7694717471ce998 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Fri, 7 May 2021 12:19:27 -0400 Subject: [PATCH 30/42] power: supply: max17040: drop unused platform data support There are no platforms using the driver with platform data (no board files with the driver), so the dead code can be dropped. Signed-off-by: Krzysztof Kozlowski Signed-off-by: Sebastian Reichel --- drivers/power/supply/max17040_battery.c | 3 --- include/linux/max17040_battery.h | 16 ---------------- 2 files changed, 19 deletions(-) delete mode 100644 include/linux/max17040_battery.h diff --git a/drivers/power/supply/max17040_battery.c b/drivers/power/supply/max17040_battery.c index aecc0c840351..3cea92e28dc3 100644 --- a/drivers/power/supply/max17040_battery.c +++ b/drivers/power/supply/max17040_battery.c @@ -16,7 +16,6 @@ #include #include #include -#include #include #include @@ -142,7 +141,6 @@ struct max17040_chip { struct regmap *regmap; struct delayed_work work; struct power_supply *battery; - struct max17040_platform_data *pdata; struct chip_data data; /* battery capacity */ @@ -451,7 +449,6 @@ static int max17040_probe(struct i2c_client *client, chip->client = client; chip->regmap = devm_regmap_init_i2c(client, &max17040_regmap); - chip->pdata = client->dev.platform_data; chip_id = (enum chip_id) id->driver_data; if (client->dev.of_node) { ret = max17040_get_of_data(chip); diff --git a/include/linux/max17040_battery.h b/include/linux/max17040_battery.h deleted file mode 100644 index 593602fc9317..000000000000 --- a/include/linux/max17040_battery.h +++ /dev/null @@ -1,16 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-only */ -/* - * Copyright (C) 2009 Samsung Electronics - * Minkyu Kang - */ - -#ifndef __MAX17040_BATTERY_H_ -#define __MAX17040_BATTERY_H_ - -struct max17040_platform_data { - int (*battery_online)(void); - int (*charger_online)(void); - int (*charger_enable)(void); -}; - -#endif From 6a0fcc87c9e35191d37a8819fdab9d30e523515b Mon Sep 17 00:00:00 2001 From: Hermann Lauer Date: Wed, 12 May 2021 12:58:56 +0200 Subject: [PATCH 31/42] power: supply: axp20x_battery: allow disabling battery charging Allow disabling and re-enabling battery charging of an axp209 PMIC through a writable status property. With the current driver code charging is always on. This works on the axp209 of Banana {Pi M1+,Pro} and should work on all AXP chips. Signed-off-by: Hermann.Lauer@uni-heidelberg.de Signed-off-by: Sebastian Reichel --- drivers/power/supply/axp20x_battery.c | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/drivers/power/supply/axp20x_battery.c b/drivers/power/supply/axp20x_battery.c index e84b6e4da14a..18a9db0df4b1 100644 --- a/drivers/power/supply/axp20x_battery.c +++ b/drivers/power/supply/axp20x_battery.c @@ -40,6 +40,7 @@ #define AXP209_FG_PERCENT GENMASK(6, 0) #define AXP22X_FG_VALID BIT(7) +#define AXP20X_CHRG_CTRL1_ENABLE BIT(7) #define AXP20X_CHRG_CTRL1_TGT_VOLT GENMASK(6, 5) #define AXP20X_CHRG_CTRL1_TGT_4_1V (0 << 5) #define AXP20X_CHRG_CTRL1_TGT_4_15V (1 << 5) @@ -468,7 +469,18 @@ static int axp20x_battery_set_prop(struct power_supply *psy, case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX: return axp20x_set_max_constant_charge_current(axp20x_batt, val->intval); + case POWER_SUPPLY_PROP_STATUS: + switch (val->intval) { + case POWER_SUPPLY_STATUS_CHARGING: + return regmap_update_bits(axp20x_batt->regmap, AXP20X_CHRG_CTRL1, + AXP20X_CHRG_CTRL1_ENABLE, AXP20X_CHRG_CTRL1_ENABLE); + case POWER_SUPPLY_STATUS_DISCHARGING: + case POWER_SUPPLY_STATUS_NOT_CHARGING: + return regmap_update_bits(axp20x_batt->regmap, AXP20X_CHRG_CTRL1, + AXP20X_CHRG_CTRL1_ENABLE, 0); + } + fallthrough; default: return -EINVAL; } @@ -491,7 +503,8 @@ static enum power_supply_property axp20x_battery_props[] = { static int axp20x_battery_prop_writeable(struct power_supply *psy, enum power_supply_property psp) { - return psp == POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN || + return psp == POWER_SUPPLY_PROP_STATUS || + psp == POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN || psp == POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN || psp == POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT || psp == POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX; From aecd127b45eac8ce0ff667a4a855ff66905fb88c Mon Sep 17 00:00:00 2001 From: Carl Philipp Klemm Date: Fri, 23 Apr 2021 14:58:31 +0200 Subject: [PATCH 32/42] power: supply: cpcap-battery: invalidate config when incompatible measurements are read This invalidates empty->counter_uah and charge_full when charge_now indicates that they are grossly wrong and adds some tolerance to POWER_SUPPLY_PROP_CHARGE_FULL to allow for inaccuracies in the charge counter and manufacturing tolerances in the battery. Signed-off-by: Carl Philipp Klemm Signed-off-by: Sebastian Reichel --- drivers/power/supply/cpcap-battery.c | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/drivers/power/supply/cpcap-battery.c b/drivers/power/supply/cpcap-battery.c index a3fc0084cda0..8d62d4241da3 100644 --- a/drivers/power/supply/cpcap-battery.c +++ b/drivers/power/supply/cpcap-battery.c @@ -667,10 +667,23 @@ static int cpcap_battery_get_property(struct power_supply *psy, if (!empty->voltage) return -ENODATA; val->intval = empty->counter_uah - latest->counter_uah; - if (val->intval < 0) + if (val->intval < 0) { + /* Assume invalid config if CHARGE_NOW is -20% */ + if (ddata->charge_full && abs(val->intval) > ddata->charge_full/5) { + empty->voltage = 0; + ddata->charge_full = 0; + return -ENODATA; + } val->intval = 0; - else if (ddata->charge_full && ddata->charge_full < val->intval) + } else if (ddata->charge_full && ddata->charge_full < val->intval) { + /* Assume invalid config if CHARGE_NOW exceeds CHARGE_FULL by 20% */ + if (val->intval > (6*ddata->charge_full)/5) { + empty->voltage = 0; + ddata->charge_full = 0; + return -ENODATA; + } val->intval = ddata->charge_full; + } break; case POWER_SUPPLY_PROP_CHARGE_FULL: if (!ddata->charge_full) @@ -747,7 +760,7 @@ static int cpcap_battery_set_property(struct power_supply *psy, case POWER_SUPPLY_PROP_CHARGE_FULL: if (val->intval < 0) return -EINVAL; - if (val->intval > ddata->config.info.charge_full_design) + if (val->intval > (6*ddata->config.info.charge_full_design)/5) return -EINVAL; ddata->charge_full = val->intval; From eab4e6d953c1059a30ac0f15826abc7dd2374d3c Mon Sep 17 00:00:00 2001 From: Carl Philipp Klemm Date: Fri, 23 Apr 2021 15:00:57 +0200 Subject: [PATCH 33/42] power: supply: cpcap-charger: get the battery inserted infomation from cpcap-battery This avoids reimplementing the detection logic twice and removes the possibility of activating charging with 500mA even if a battery is not detected. Signed-off-by: Carl Philipp Klemm Signed-off-by: Sebastian Reichel --- drivers/power/supply/cpcap-charger.c | 39 ++++++++++++++-------------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/drivers/power/supply/cpcap-charger.c b/drivers/power/supply/cpcap-charger.c index df01abc49ce8..60e0ce105a29 100644 --- a/drivers/power/supply/cpcap-charger.c +++ b/drivers/power/supply/cpcap-charger.c @@ -173,23 +173,6 @@ static enum power_supply_property cpcap_charger_props[] = { POWER_SUPPLY_PROP_CURRENT_NOW, }; -/* No battery always shows temperature of -40000 */ -static bool cpcap_charger_battery_found(struct cpcap_charger_ddata *ddata) -{ - struct iio_channel *channel; - int error, temperature; - - channel = ddata->channels[CPCAP_CHARGER_IIO_BATTDET]; - error = iio_read_channel_processed(channel, &temperature); - if (error < 0) { - dev_warn(ddata->dev, "%s failed: %i\n", __func__, error); - - return false; - } - - return temperature > -20000 && temperature < 60000; -} - static int cpcap_charger_get_charge_voltage(struct cpcap_charger_ddata *ddata) { struct iio_channel *channel; @@ -700,11 +683,29 @@ static void cpcap_usb_detect(struct work_struct *work) if (!ddata->feeding_vbus && cpcap_charger_vbus_valid(ddata) && s.chrgcurr1) { - int max_current = 532000; + int max_current; int vchrg, ichrg; + union power_supply_propval val; + struct power_supply *battery; - if (cpcap_charger_battery_found(ddata)) + battery = power_supply_get_by_name("battery"); + if (IS_ERR_OR_NULL(battery)) { + dev_err(ddata->dev, "battery power_supply not available %li\n", + PTR_ERR(battery)); + return; + } + + error = power_supply_get_property(battery, POWER_SUPPLY_PROP_PRESENT, &val); + power_supply_put(battery); + if (error) + goto out_err; + + if (val.intval) { max_current = 1596000; + } else { + dev_info(ddata->dev, "battery not inserted, charging disabled\n"); + max_current = 0; + } if (max_current > ddata->limit_current) max_current = ddata->limit_current; From 4465b3a621e761d82d1a92e3fda88c5d33c804b8 Mon Sep 17 00:00:00 2001 From: Zou Wei Date: Sat, 5 Jun 2021 09:21:23 +0800 Subject: [PATCH 34/42] power: reset: regulator-poweroff: add missing MODULE_DEVICE_TABLE This patch adds missing MODULE_DEVICE_TABLE definition which generates correct modalias for automatic loading of this driver when it is built as an external module. Reported-by: Hulk Robot Signed-off-by: Zou Wei Signed-off-by: Sebastian Reichel --- drivers/power/reset/regulator-poweroff.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/power/reset/regulator-poweroff.c b/drivers/power/reset/regulator-poweroff.c index f697088e0ad1..20701203935f 100644 --- a/drivers/power/reset/regulator-poweroff.c +++ b/drivers/power/reset/regulator-poweroff.c @@ -64,6 +64,7 @@ static const struct of_device_id of_regulator_poweroff_match[] = { { .compatible = "regulator-poweroff", }, {}, }; +MODULE_DEVICE_TABLE(of, of_regulator_poweroff_match); static struct platform_driver regulator_poweroff_driver = { .probe = regulator_poweroff_probe, From 073b5d5b1f9cc94a3eea25279fbafee3f4f5f097 Mon Sep 17 00:00:00 2001 From: Zou Wei Date: Sat, 5 Jun 2021 09:21:54 +0800 Subject: [PATCH 35/42] power: supply: charger-manager: add missing MODULE_DEVICE_TABLE This patch adds missing MODULE_DEVICE_TABLE definition which generates correct modalias for automatic loading of this driver when it is built as an external module. Reported-by: Hulk Robot Signed-off-by: Zou Wei Signed-off-by: Sebastian Reichel --- drivers/power/supply/charger-manager.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/power/supply/charger-manager.c b/drivers/power/supply/charger-manager.c index 45da870aecca..d67edb760c94 100644 --- a/drivers/power/supply/charger-manager.c +++ b/drivers/power/supply/charger-manager.c @@ -1279,6 +1279,7 @@ static const struct of_device_id charger_manager_match[] = { }, {}, }; +MODULE_DEVICE_TABLE(of, charger_manager_match); static struct charger_desc *of_cm_parse_desc(struct device *dev) { From dfe52db13ab8d24857a9840ec7ca75eef800c26c Mon Sep 17 00:00:00 2001 From: Zou Wei Date: Sat, 5 Jun 2021 09:21:41 +0800 Subject: [PATCH 36/42] power: supply: ab8500: add missing MODULE_DEVICE_TABLE This patch adds missing MODULE_DEVICE_TABLE definition which generates correct modalias for automatic loading of this driver when it is built as an external module. Reported-by: Hulk Robot Signed-off-by: Zou Wei Signed-off-by: Sebastian Reichel --- drivers/power/supply/ab8500_btemp.c | 1 + drivers/power/supply/ab8500_charger.c | 1 + drivers/power/supply/ab8500_fg.c | 1 + 3 files changed, 3 insertions(+) diff --git a/drivers/power/supply/ab8500_btemp.c b/drivers/power/supply/ab8500_btemp.c index 5b664d2f6b82..dbdcff32f353 100644 --- a/drivers/power/supply/ab8500_btemp.c +++ b/drivers/power/supply/ab8500_btemp.c @@ -1097,6 +1097,7 @@ static const struct of_device_id ab8500_btemp_match[] = { { .compatible = "stericsson,ab8500-btemp", }, { }, }; +MODULE_DEVICE_TABLE(of, ab8500_btemp_match); struct platform_driver ab8500_btemp_driver = { .probe = ab8500_btemp_probe, diff --git a/drivers/power/supply/ab8500_charger.c b/drivers/power/supply/ab8500_charger.c index 4a6bc4330c08..fa49e12e5a60 100644 --- a/drivers/power/supply/ab8500_charger.c +++ b/drivers/power/supply/ab8500_charger.c @@ -3705,6 +3705,7 @@ static const struct of_device_id ab8500_charger_match[] = { { .compatible = "stericsson,ab8500-charger", }, { }, }; +MODULE_DEVICE_TABLE(of, ab8500_charger_match); static struct platform_driver ab8500_charger_driver = { .probe = ab8500_charger_probe, diff --git a/drivers/power/supply/ab8500_fg.c b/drivers/power/supply/ab8500_fg.c index 4da89920657c..3d45ed0157c6 100644 --- a/drivers/power/supply/ab8500_fg.c +++ b/drivers/power/supply/ab8500_fg.c @@ -3212,6 +3212,7 @@ static const struct of_device_id ab8500_fg_match[] = { { .compatible = "stericsson,ab8500-fg", }, { }, }; +MODULE_DEVICE_TABLE(of, ab8500_fg_match); struct platform_driver ab8500_fg_driver = { .probe = ab8500_fg_probe, From 30e677a2ba2920b1234d1b38307dfa1dbb993e20 Mon Sep 17 00:00:00 2001 From: Yu Jiahua Date: Tue, 15 Jun 2021 19:46:22 -0800 Subject: [PATCH 37/42] drivers: power: add missing MODULE_DEVICE_TABLE in keystone-reset.c This patch adds missing MODULE_DEVICE_TABLE definition which generates correct modalias for automatic loading of this driver when it is built as an external module. Signed-off-by: Yu Jiahua Signed-off-by: Sebastian Reichel --- drivers/power/reset/keystone-reset.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/power/reset/keystone-reset.c b/drivers/power/reset/keystone-reset.c index 211eeef0c81a..c720112db704 100644 --- a/drivers/power/reset/keystone-reset.c +++ b/drivers/power/reset/keystone-reset.c @@ -71,6 +71,7 @@ static const struct of_device_id rsctrl_of_match[] = { {.compatible = "ti,keystone-reset", }, {}, }; +MODULE_DEVICE_TABLE(of, rsctrl_of_match); static int rsctrl_probe(struct platform_device *pdev) { From df6f3f7eb70d310c3cee2d8e08ed32067fb6fcf4 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Thu, 17 Jun 2021 21:23:10 +0300 Subject: [PATCH 38/42] power: supply: bq24190_charger: drop of_match_ptr() from device ID table MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The driver can match only via the DT table so the table should be always used and the of_match_ptr() does not have any sense (this also allows ACPI matching via PRP0001, even though it might be not relevant here). This fixes compile warning (!CONFIG_OF): drivers/power/supply/bq24190_charger.c:1972:34: warning: ‘bq24190_of_match’ defined but not used [-Wunused-const-variable=] Signed-off-by: Andy Shevchenko Signed-off-by: Sebastian Reichel --- drivers/power/supply/bq24190_charger.c | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/drivers/power/supply/bq24190_charger.c b/drivers/power/supply/bq24190_charger.c index 852e86bfe2fb..35ff0c8fe96f 100644 --- a/drivers/power/supply/bq24190_charger.c +++ b/drivers/power/supply/bq24190_charger.c @@ -5,11 +5,10 @@ * Author: Mark A. Greer */ +#include #include #include #include -#include -#include #include #include #include @@ -1959,7 +1958,6 @@ static const struct i2c_device_id bq24190_i2c_ids[] = { }; MODULE_DEVICE_TABLE(i2c, bq24190_i2c_ids); -#ifdef CONFIG_OF static const struct of_device_id bq24190_of_match[] = { { .compatible = "ti,bq24190", }, { .compatible = "ti,bq24192", }, @@ -1968,11 +1966,6 @@ static const struct of_device_id bq24190_of_match[] = { { }, }; MODULE_DEVICE_TABLE(of, bq24190_of_match); -#else -static const struct of_device_id bq24190_of_match[] = { - { }, -}; -#endif static struct i2c_driver bq24190_driver = { .probe = bq24190_probe, @@ -1981,7 +1974,7 @@ static struct i2c_driver bq24190_driver = { .driver = { .name = "bq24190-charger", .pm = &bq24190_pm_ops, - .of_match_table = of_match_ptr(bq24190_of_match), + .of_match_table = bq24190_of_match, }, }; module_i2c_driver(bq24190_driver); From 0973e96b4bbd19aa50881a8e0e4653b824420de6 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Thu, 17 Jun 2021 21:27:01 +0200 Subject: [PATCH 39/42] power: supply: axp288_fuel_gauge: Rename fuel_gauge_blacklist to no_battery_list As documented in the updated "Naming" chapter of Documentation/process/coding-style.rst, usage of the work blacklist should be avoided where possible. Rename the list of devices which have no battery to the axp288_no_battery_list, which also more accurately describes the contents of the list. Signed-off-by: Hans de Goede Signed-off-by: Sebastian Reichel --- drivers/power/supply/axp288_fuel_gauge.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/power/supply/axp288_fuel_gauge.c b/drivers/power/supply/axp288_fuel_gauge.c index 39e16ecb7638..b74c4acba25d 100644 --- a/drivers/power/supply/axp288_fuel_gauge.c +++ b/drivers/power/supply/axp288_fuel_gauge.c @@ -676,7 +676,7 @@ intr_failed: * detection reports one despite it not being there. * Please keep this listed sorted alphabetically. */ -static const struct dmi_system_id axp288_fuel_gauge_blacklist[] = { +static const struct dmi_system_id axp288_no_battery_list[] = { { /* ACEPC T8 Cherry Trail Z8350 mini PC */ .matches = { @@ -764,7 +764,7 @@ static int axp288_fuel_gauge_probe(struct platform_device *pdev) }; unsigned int val; - if (dmi_check_system(axp288_fuel_gauge_blacklist)) + if (dmi_check_system(axp288_no_battery_list)) return -ENODEV; /* From 3a06b912a5ce494d7b7300b12719c562be7b566f Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Thu, 17 Jun 2021 21:27:02 +0200 Subject: [PATCH 40/42] power: supply: axp288_fuel_gauge: Make "T3 MRD" no_battery_list DMI entry more generic It turns out that the "T3 MRD" DMI_BOARD_NAME value is used in a lot of different Cherry Trail x5-z8300 / x5-z8350 based Mini-PC / HDMI-stick models from Ace PC / Meegopad / MinisForum / Wintel (and likely also other vendors). Most of the other DMI strings on these boxes unfortunately contain various generic values like "Default string" or "$(DEFAULT_STRING)", so we cannot match on them. These devices do have their chassis-type correctly set to a value of "3" (desktop) which is a pleasant surprise, so also match on that. This should avoid the quirk accidentally also getting applied to laptops / tablets (which do actually have a battery). Although in my quite large database of Bay and Cherry Trail based devices DMIdecode dumps I don't have any laptops / tables with a board-name of "T3 MRD", so this should not be an issue. BugLink: https://askubuntu.com/questions/1206714/how-can-a-mini-pc-be-stopped-from-being-detected-as-a-laptop-with-a-battery/ Signed-off-by: Hans de Goede Signed-off-by: Sebastian Reichel --- drivers/power/supply/axp288_fuel_gauge.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/drivers/power/supply/axp288_fuel_gauge.c b/drivers/power/supply/axp288_fuel_gauge.c index b74c4acba25d..a9c28a725818 100644 --- a/drivers/power/supply/axp288_fuel_gauge.c +++ b/drivers/power/supply/axp288_fuel_gauge.c @@ -723,15 +723,6 @@ static const struct dmi_system_id axp288_no_battery_list[] = { DMI_MATCH(DMI_PRODUCT_NAME, "MEEGOPAD T02"), }, }, - { - /* Meegopad T08 */ - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "Default string"), - DMI_MATCH(DMI_BOARD_VENDOR, "To be filled by OEM."), - DMI_MATCH(DMI_BOARD_NAME, "T3 MRD"), - DMI_MATCH(DMI_BOARD_VERSION, "V1.1"), - }, - }, { /* Mele PCG03 Mini PC */ .matches = { DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "Mini PC"), @@ -745,6 +736,15 @@ static const struct dmi_system_id axp288_no_battery_list[] = { DMI_MATCH(DMI_PRODUCT_NAME, "Z83-4"), } }, + { + /* Various Ace PC/Meegopad/MinisForum/Wintel Mini-PCs/HDMI-sticks */ + .matches = { + DMI_MATCH(DMI_BOARD_NAME, "T3 MRD"), + DMI_MATCH(DMI_CHASSIS_TYPE, "3"), + DMI_MATCH(DMI_BIOS_VENDOR, "American Megatrends Inc."), + DMI_MATCH(DMI_BIOS_VERSION, "5.11"), + }, + }, {} }; From f390e4bd79289b85bfe345d4d9d160dc4926dfc1 Mon Sep 17 00:00:00 2001 From: Colin Ian King Date: Fri, 18 Jun 2021 10:29:24 +0100 Subject: [PATCH 41/42] power: supply: axp288_fuel_gauge: remove redundant continue statement The continue statement at the end of a for-loop has no effect, invert the if expression and remove the continue. Signed-off-by: Colin Ian King Reviewed-by: Hans de Goede Signed-off-by: Sebastian Reichel --- drivers/power/supply/axp288_fuel_gauge.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/power/supply/axp288_fuel_gauge.c b/drivers/power/supply/axp288_fuel_gauge.c index a9c28a725818..2ba2d8d6b8e6 100644 --- a/drivers/power/supply/axp288_fuel_gauge.c +++ b/drivers/power/supply/axp288_fuel_gauge.c @@ -142,9 +142,7 @@ static int fuel_gauge_reg_readb(struct axp288_fg_info *info, int reg) for (i = 0; i < NR_RETRY_CNT; i++) { ret = regmap_read(info->regmap, reg, &val); - if (ret == -EBUSY) - continue; - else + if (ret != -EBUSY) break; } From f1c74a6c07e76fcb31a4bcc1f437c4361a2674ce Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Sun, 27 Jun 2021 01:47:49 +0200 Subject: [PATCH 42/42] power: supply: ab8500: Fix an old bug Trying to get the AB8500 charging driver working I ran into a bit of bitrot: we haven't used the driver for a while so errors in refactorings won't be noticed. This one is pretty self evident: use argument to the macro or we end up with a random pointer to something else. Cc: stable@vger.kernel.org Cc: Krzysztof Kozlowski Cc: Marcus Cooper Fixes: 297d716f6260 ("power_supply: Change ownership from driver to core") Signed-off-by: Linus Walleij Signed-off-by: Sebastian Reichel --- drivers/power/supply/ab8500-chargalg.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/power/supply/ab8500-chargalg.h b/drivers/power/supply/ab8500-chargalg.h index 94a6f9068bc5..07e6ff50084f 100644 --- a/drivers/power/supply/ab8500-chargalg.h +++ b/drivers/power/supply/ab8500-chargalg.h @@ -15,7 +15,7 @@ * - POWER_SUPPLY_TYPE_USB, * because only them store as drv_data pointer to struct ux500_charger. */ -#define psy_to_ux500_charger(x) power_supply_get_drvdata(psy) +#define psy_to_ux500_charger(x) power_supply_get_drvdata(x) /* Forward declaration */ struct ux500_charger;