Commit 1c1f13a0 authored by Linus Walleij's avatar Linus Walleij Committed by Sebastian Reichel

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: default avatarLinus Walleij <linus.walleij@linaro.org>
Signed-off-by: default avatarSebastian Reichel <sebastian.reichel@collabora.com>
parent 94233f11
......@@ -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_ */
......@@ -13,6 +13,7 @@
#include <linux/init.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/component.h>
#include <linux/interrupt.h>
#include <linux/delay.h>
#include <linux/slab.h>
......@@ -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");
......
This diff is collapsed.
......@@ -17,6 +17,7 @@
#include <linux/init.h>
#include <linux/module.h>
#include <linux/component.h>
#include <linux/device.h>
#include <linux/interrupt.h>
#include <linux/platform_device.h>
......@@ -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");
......
......@@ -15,6 +15,7 @@
#include <linux/init.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/component.h>
#include <linux/hrtimer.h>
#include <linux/interrupt.h>
#include <linux/delay.h>
......@@ -1943,13 +1944,44 @@ static int __maybe_unused abx500_chargalg_suspend(struct device *dev)
return 0;
}
static int abx500_chargalg_remove(struct platform_device *pdev)
static char *supply_interface[] = {
"ab8500_fg",
};
static const struct power_supply_desc abx500_chargalg_desc = {
.name = "abx500_chargalg",
.type = POWER_SUPPLY_TYPE_BATTERY,
.properties = abx500_chargalg_props,
.num_properties = ARRAY_SIZE(abx500_chargalg_props),
.get_property = abx500_chargalg_get_property,
.external_power_changed = abx500_chargalg_external_power_changed,
};
static int abx500_chargalg_bind(struct device *dev, struct device *master,
void *data)
{
struct abx500_chargalg *di = platform_get_drvdata(pdev);
struct abx500_chargalg *di = dev_get_drvdata(dev);
/* sysfs interface to enable/disbale charging from user space */
abx500_chargalg_sysfs_exit(di);
/* 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);
......@@ -1959,48 +1991,36 @@ static int abx500_chargalg_remove(struct platform_device *pdev)
/* Delete the work queue */
destroy_workqueue(di->chargalg_wq);
power_supply_unregister(di->chargalg_psy);
return 0;
flush_scheduled_work();
}
static char *supply_interface[] = {
"ab8500_fg",
};
static const struct power_supply_desc abx500_chargalg_desc = {
.name = "abx500_chargalg",
.type = POWER_SUPPLY_TYPE_BATTERY,
.properties = abx500_chargalg_props,
.num_properties = ARRAY_SIZE(abx500_chargalg_props),
.get_property = abx500_chargalg_get_property,
.external_power_changed = abx500_chargalg_external_power_changed,
static 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");
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment