Re: [PATCH 3/3] leds: Add Broadchip BCT3024 LED driver

From: Krzysztof Kozlowski
Date: Fri Jul 28 2023 - 03:42:05 EST


On 27/07/2023 18:05, Matus Gajdos wrote:
> The BCT3024 chip is an I2C LED driver with independent 24 output
> channels. Each channel supports 256 levels.
>
> Signed-off-by: Matus Gajdos <matuszpd@xxxxxxxxx>
> ---
> drivers/leds/Kconfig | 9 +
> drivers/leds/Makefile | 1 +
> drivers/leds/leds-bct3024.c | 564 ++++++++++++++++++++++++++++++++++++
> 3 files changed, 574 insertions(+)
> create mode 100644 drivers/leds/leds-bct3024.c

Thank you for your patch. There is something to discuss/improve.


>
> diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
> index 6046dfeca16f..75059db201e2 100644
> --- a/drivers/leds/Kconfig
> +++ b/drivers/leds/Kconfig
> @@ -135,6 +135,15 @@ config LEDS_BCM6358
> This option enables support for LEDs connected to the BCM6358
> LED HW controller accessed via

...

> +}
> +
> +static int bct3024_brightness_set(struct led_classdev *ldev,
> + enum led_brightness brightness)
> +{
> + struct bct3024_led *led = container_of(ldev, struct bct3024_led, ldev);
> + struct bct3024_data *priv = led->priv;
> + struct device *dev = priv->dev;
> + int ret;
> + unsigned int ctrl, bright;
> +
> + if (priv->state == BCT3024_STATE_INIT)
> + return -EIO;
> +
> + if (brightness == 0) {
> + ctrl = 0x00;
> + bright = 0;
> + } else if (brightness < 256) {
> + ctrl = 0x01;
> + bright = brightness;
> + }
> +
> + mutex_lock(&priv->lock);

What do you protect with lock? This must be documented in lock's
definition in your struct.

> +
> + if (bct3024_any_active(priv) && (priv->state == BCT3024_STATE_IDLE ||
> + priv->state == BCT3024_STATE_SHUTDOWN)) {
> + pm_runtime_get_sync(dev);
> + priv->state = BCT3024_STATE_ACTIVE;

I don't understand why you added state machine for handling state. You
are basically duplicating runtime PM...

> + }
> +
> + for (; led; led = led->next) {
> + ret = bct3024_write(priv, BCT3024_REG_CONTROL(led->idx), ctrl);
> + if (ret < 0)
> + goto exit;
> + ret = bct3024_write(priv, BCT3024_REG_BRIGHTNESS(led->idx), bright);
> + if (ret < 0)
> + goto exit;
> + }
> +
> + ret = bct3024_write(priv, BCT3024_REG_UPDATE, 0);
> + if (ret < 0)
> + goto exit;
> +
> + ret = bct3024_set_shutdown_reg(priv, false);
> + if (ret < 0)
> + goto exit;
> +
> + if (!ret && priv->state == BCT3024_STATE_ACTIVE) {
> + priv->state = BCT3024_STATE_IDLE;
> + pm_runtime_mark_last_busy(dev);
> + pm_runtime_put_autosuspend(dev);
> + /* Activate autosuspend */
> + pm_runtime_idle(dev);
> + }
> +
> + ret = 0;
> +
> +exit:
> + mutex_unlock(&priv->lock);
> +
> + return ret;
> +}
> +
> +static int bct3024_setup_led(struct bct3024_data *priv, u32 reg,
> + struct bct3024_led **prev, struct led_init_data *idata)
> +{
> + struct device *dev = priv->dev;
> + struct bct3024_led *led;
> + int ret;
> +
> + if (reg >= BCT3024_LED_COUNT) {
> + ret = -EINVAL;
> + dev_err_probe(dev, ret, "invalid reg value: %u\n", reg);
> + return ret;

That's not correct syntax.

return dev_err_probe
> + }
> +
> + led = &priv->leds[reg];
> +
> + if (led->active) {
> + ret = -EINVAL;
> + dev_err_probe(dev, ret, "reg redeclared: %u\n", reg);
> + return ret;

Ditto

> + }
> +
> + led->active = true;
> + led->priv = priv;
> + led->idx = reg;
> +
> + if (!*prev) {
> + led->ldev.max_brightness = 255;
> + led->ldev.brightness_set_blocking = bct3024_brightness_set;
> +
> + ret = devm_led_classdev_register_ext(dev, &led->ldev, idata);
> + if (ret < 0) {
> + dev_err_probe(dev, ret, "failed to register led %u\n", reg);
> + return ret;

Ditto

> + }
> + } else
> + (*prev)->next = led;
> +
> + *prev = led;
> +
> + return 0;
> +}
> +
> +static int bct3024_of_parse(struct i2c_client *client)
> +{
> + struct bct3024_data *priv = i2c_get_clientdata(client);
> + struct device *dev = priv->dev;
> + struct device_node *np;
> + int ret;
> +
> + ret = of_get_child_count(dev->of_node);
> + if (!ret || ret > ARRAY_SIZE(priv->leds)) {
> + dev_err_probe(dev, -EINVAL, "invalid nodes count: %d\n", ret);
> + return -EINVAL;

Ditto

> + }
> +
> + for_each_child_of_node(dev->of_node, np) {
> + u32 regs[BCT3024_LED_COUNT];
> + struct led_init_data idata = {
> + .fwnode = of_fwnode_handle(np),
> + .devicename = client->name,
> + };
> + struct bct3024_led *led;
> + int count, i;
> +
> + count = of_property_read_variable_u32_array(np, "reg", regs, 1,
> + BCT3024_LED_COUNT);
> + if (count < 0) {
> + ret = count;
> + dev_err_probe(dev, ret, "failed to read node reg\n");
> + goto fail;

Ditto

> + }
> +
> + for (i = 0, led = NULL; i < count; i++) {
> + ret = bct3024_setup_led(priv, regs[i], &led, &idata);
> + if (ret < 0)
> + goto fail;
> + }
> + }
> +
> + return 0;
> +
> +fail:
> + of_node_put(np);
> +
> + return ret;
> +}
> +
> +static int bct3024_i2c_probe(struct i2c_client *client)
> +{
> + struct bct3024_data *priv;
> + struct device *dev = &client->dev;
> + int ret;
> +
> + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
> + if (!priv)
> + return -ENOMEM;
> +
> + priv->dev = dev;
> +
> + priv->supply = devm_regulator_get_optional(dev, "vdd");
> + if (IS_ERR(priv->supply)) {
> + ret = PTR_ERR(priv->supply);
> + if (ret != -ENODEV) {
> + dev_err_probe(dev, ret, "failed to get supply\n");
> + return ret;

Ditto

> + }
> + priv->supply = NULL;
> + }
> +
> + priv->shutdown_gpiod = devm_gpiod_get_optional(dev, "shutdown", GPIOD_OUT_HIGH);
> + if (IS_ERR(priv->shutdown_gpiod)) {
> + ret = PTR_ERR(priv->shutdown_gpiod);
> + dev_err_probe(dev, ret, "failed to get shutdown gpio\n");
> + return ret;

Everywhere...

> + }
> +
> + priv->regmap = devm_regmap_init_i2c(client, &bct3024_regmap);
> + if (IS_ERR(priv->regmap)) {
> + ret = PTR_ERR(priv->regmap);
> + return ret;


It's return PTR_ERR....

> + }
> +
> + mutex_init(&priv->lock);
> + i2c_set_clientdata(client, priv);
> +
> + pm_runtime_set_autosuspend_delay(dev, 2000);
> + pm_runtime_use_autosuspend(dev);
> + pm_runtime_enable(dev);
> + if (!pm_runtime_enabled(dev)) {
> + ret = bct3024_shutdown(priv, false);

This should go to error handling path... Although why? There was no
power on code between devm_regmap_init_i2c() and here.

> + if (ret < 0)
> + return ret;
> + }
> +
> + ret = pm_runtime_resume_and_get(dev);
> + if (ret < 0)
> + goto fail;
> +
> + ret = bct3024_of_parse(client);
> + if (ret < 0)
> + goto fail;
> +
> + pm_runtime_mark_last_busy(dev);
> + pm_runtime_put_autosuspend(dev);
> +
> + return 0;
> +
> +fail:
> + dev_err_probe(dev, ret, "probe failed\n");

No, no no. Do you see any driver doing it?

> + if (!pm_runtime_status_suspended(dev))
> + bct3024_shutdown(priv, 2);

Which call this is reversing? Each step in probe should have its reverse
(when applicable of course). Don't add some new unrelated reverse calls
which do not have a matching enable. If you shutdown here, I expect
there was "bct3024_powerup". Where is it? Looks like you put unrelated
code into the shutdown making it all very difficult to understand.

Where do you reverse PM runtime calls here?

> + return ret;
> +}
> +
> +static void bct3024_i2c_remove(struct i2c_client *client)
> +{
> + struct bct3024_data *priv = i2c_get_clientdata(client);
> +
> + bct3024_shutdown(priv, true);
> + pm_runtime_disable(priv->dev);
> + mutex_destroy(&priv->lock);
> +}
> +
> +static void bct3024_i2c_shutdown(struct i2c_client *client)
> +{
> + struct bct3024_data *priv = i2c_get_clientdata(client);
> +
> + bct3024_shutdown(priv, true);
> +}
> +
> +#ifdef CONFIG_PM_SLEEP
> +static int bct3024_runtime_idle(struct device *dev)
> +{
> + struct bct3024_data *priv = dev_get_drvdata(dev);
> +
> + dev_dbg(dev, "%s: %d\n", __func__, priv->state);

No simple debug statements for entry/exit of functions. There is
extensive trace infrastructure for all this.

> +
> + return priv->state == BCT3024_STATE_ACTIVE ? -EBUSY : 0;
> +}
> +
> +static int bct3024_runtime_suspend(struct device *dev)
> +{
> + struct bct3024_data *priv = dev_get_drvdata(dev);
> +
> + dev_dbg(dev, "%s: %d\n", __func__, priv->state);

Ditto

> +
> + switch (priv->state) {
> + case BCT3024_STATE_INIT:
> + case BCT3024_STATE_SHUTDOWN:
> + return 0;
> + default:
> + break;
> + }
> +
> + return bct3024_shutdown(priv, true);
> +}
> +
> +static int bct3024_runtime_resume(struct device *dev)
> +{
> + struct bct3024_data *priv = dev_get_drvdata(dev);
> + int ret;
> +
> + dev_dbg(dev, "%s: %d\n", __func__, priv->state);

Ditto

> +
> + switch (priv->state) {
> + case BCT3024_STATE_INIT:
> + case BCT3024_STATE_SHUTDOWN:
> + break;
> + default:
> + return 0;
> + }
> +
> + ret = bct3024_shutdown(priv, false);
> + if (ret < 0)
> + bct3024_shutdown(priv, 2);
> +
> + return ret;
> +}
> +#endif
> +
> +static const struct dev_pm_ops bct3024_pm_ops = {
> + SET_RUNTIME_PM_OPS(bct3024_runtime_suspend, bct3024_runtime_resume,
> + bct3024_runtime_idle)
> + SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, pm_runtime_force_resume)
> +};
> +
> +static const struct of_device_id bct3024_of_match[] = {
> + { .compatible = "broadchip,bct3024" },
> + { }
> +};
> +MODULE_DEVICE_TABLE(of, bct3024_of_match);
> +
> +static struct i2c_driver bct3024_driver = {
> + .driver = {
> + .name = "bct3024",
> + .of_match_table = bct3024_of_match,
> + .pm = &bct3024_pm_ops,
> + },
> + .probe_new = bct3024_i2c_probe,
> + .shutdown = bct3024_i2c_shutdown,
> + .remove = bct3024_i2c_remove,
> +};
> +
> +module_i2c_driver(bct3024_driver);
> +
> +MODULE_AUTHOR("Matus Gajdos <matuszpd@xxxxxxxxx>");
> +MODULE_LICENSE("GPL");
> +MODULE_DESCRIPTION("Broadchip BCT3024 LED driver");

Best regards,
Krzysztof