On 18/09/2024 10:41, Andreas Kemnade wrote: > Add a driver for the charger in the TWL6030/32. For now it does not report > much in sysfs but parameters are set up for USB, charging is enabled with > the specified parameters. It stops charging when full and also restarts > charging. > This prevents ending up in a system setup where you run out of battery > although a charger is plugged in after precharge completed. > > Battery voltage behavior was checked via the GPADC. > Few stylistic comments below. > Signed-off-by: Andreas Kemnade <andreas@xxxxxxxxxxxx> > --- > drivers/power/supply/Kconfig | 10 + > drivers/power/supply/Makefile | 1 + > drivers/power/supply/twl6030_charger.c | 566 +++++++++++++++++++++++++ > 3 files changed, 577 insertions(+) > create mode 100644 drivers/power/supply/twl6030_charger.c > > diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig > index bcfa63fb9f1e2..9f2eef6787f7a 100644 > --- a/drivers/power/supply/Kconfig > +++ b/drivers/power/supply/Kconfig > @@ -493,6 +493,16 @@ config CHARGER_TWL4030 > help > Say Y here to enable support for TWL4030 Battery Charge Interface. > > +config CHARGER_TWL6030 > + tristate "OMAP TWL6030 BCI charger driver" > + depends on IIO && TWL4030_CORE || COMPILE_TEST, at least for TWL part (but please test first) > + help > + Say Y here to enable support for TWL6030/6032 Battery Charge > + Interface. > + > + This driver can be build as a module. If so, the module will be > + called twl6030_charger. > + > + > +static int twl6030_charger_probe(struct platform_device *pdev) > +{ > + struct twl6030_charger_info *charger; > + struct power_supply_config psy_cfg = {}; > + int ret; > + u8 val; > + > + charger = devm_kzalloc(&pdev->dev, sizeof(*charger), GFP_KERNEL); > + if (!charger) > + return -ENOMEM; > + > + charger->dev = &pdev->dev; > + charger->irq_chg = platform_get_irq(pdev, 0); > + > + platform_set_drvdata(pdev, charger); > + psy_cfg.drv_data = charger; > + > + charger->channel_vusb = devm_iio_channel_get(&pdev->dev, "vusb"); > + if (IS_ERR(charger->channel_vusb)) { > + ret = PTR_ERR(charger->channel_vusb); > + if (ret == -EPROBE_DEFER) > + return ret; /* iio not ready */ > + dev_warn(&pdev->dev, "could not request vusb iio channel (%d)", > + ret); > + charger->channel_vusb = NULL; > + } > + > + charger->usb = devm_power_supply_register(&pdev->dev, > + &twl6030_charger_usb_desc, > + &psy_cfg); > + if (IS_ERR(charger->usb)) { Checkpatch... > + return dev_err_probe(&pdev->dev, PTR_ERR(charger->usb), > + "Failed to register usb\n"); > + } > + > + ret = power_supply_get_battery_info(charger->usb, &charger->binfo); > + if (ret < 0) > + return dev_err_probe(&pdev->dev, ret, > + "Failed to get battery info\n"); > + > + dev_info(&pdev->dev, "battery with vmax %d imax: %d\n", > + charger->binfo->constant_charge_voltage_max_uv, > + charger->binfo->constant_charge_current_max_ua); > + > + if (charger->binfo->constant_charge_voltage_max_uv == -EINVAL) { > + ret = twl6030_charger_read(CHARGERUSB_CTRLLIMIT1, &val); > + if (ret < 0) > + return ret; > + > + charger->binfo->constant_charge_voltage_max_uv = > + VOREG_TO_UV(val); > + } > + > + if (charger->binfo->constant_charge_voltage_max_uv > 4760000 || > + charger->binfo->constant_charge_voltage_max_uv < 350000) > + return dev_err_probe(&pdev->dev, -EINVAL, > + "Invalid charge voltage\n"); > + > + if (charger->binfo->constant_charge_current_max_ua == -EINVAL) { > + ret = twl6030_charger_read(CHARGERUSB_CTRLLIMIT2, &val); > + if (ret < 0) > + return ret; > + > + charger->binfo->constant_charge_current_max_ua = VICHRG_TO_UA(val); > + } > + > + if (charger->binfo->constant_charge_current_max_ua < 100000 || > + charger->binfo->constant_charge_current_max_ua > 1500000) { > + return dev_err_probe(&pdev->dev, -EINVAL, > + "Invalid charge current\n"); > + } > + > + if ((charger->binfo->charge_term_current_ua != -EINVAL) && > + (charger->binfo->charge_term_current_ua > 400000 || > + charger->binfo->charge_term_current_ua < 50000)) { > + return dev_err_probe(&pdev->dev, -EINVAL, > + "Invalid charge termination current\n"); > + } > + > + ret = devm_delayed_work_autocancel(&pdev->dev, > + &charger->charger_monitor, > + twl6030_charger_wdg); > + if (ret < 0) > + return dev_err_probe(&pdev->dev, ret, > + "Failed to register delayed work\n"); > + > + ret = devm_request_threaded_irq(&pdev->dev, charger->irq_chg, NULL, > + twl6030_charger_interrupt, > + IRQF_ONESHOT, pdev->name, > + charger); > + if (ret < 0) { Drop {}, see checkpatch. > + return dev_err_probe(&pdev->dev, ret, > + "could not request irq %d\n", > + charger->irq_chg); > + } > + > + /* turing to charging to configure things */ > + twl6030_charger_write(CONTROLLER_CTRL1, 0); > + twl6030_charger_interrupt(0, charger); > + > + return 0; > +} > + > +static const struct of_device_id twl_charger_of_match[] __maybe_unused = { > + {.compatible = "ti,twl6030-charger", }, > + {.compatible = "ti,twl6032-charger", }, So they are compatible? Why two entries in such case? > + { } > +}; > +MODULE_DEVICE_TABLE(of, twl_charger_of_match); > + > +static struct platform_driver twl6030_charger_driver = { > + .probe = twl6030_charger_probe, > + .driver = { > + .name = "twl6030_charger", > + .of_match_table = of_match_ptr(twl_charger_of_match), I propose to drop of_match_ptr and maybe_unused, so this won't be restricted only to OF > + }, > +}; > +module_platform_driver(twl6030_charger_driver); > + > +MODULE_DESCRIPTION("TWL6030 Battery Charger Interface driver"); > +MODULE_LICENSE("GPL"); > +MODULE_ALIAS("platform:twl6030_charger"); You should not need MODULE_ALIAS() in normal cases. If you need it, usually it means your device ID table is wrong (e.g. misses either entries or MODULE_DEVICE_TABLE()). MODULE_ALIAS() is not a substitute for incomplete ID table. Best regards, Krzysztof