Re: [PATCH] leds: add LED driver for Worldsemi WS2812B

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On Mon, Mar 14, 2022 at 7:23 PM Ivan Vozvakhov <i.vozvakhov@xxxxxxxxxxxx> wrote:
>
> This patch adds a LED class driver (powered by SPI)
> for the WS2812B LEDs that's is widely used in

that's --> that

> consumer electronic devices and DIY.

Any links to the datasheet?

You may add it as a Datasheet: tag.

...

> +maintainers:
> +  - Ivan Vozvakhov <i.vozvakhov@xxxxxxx>

By email I can't think of this being the same person as the author /
submitter of the patch. Can you elaborate why the addresses are
different?

...

> +description: |
> +  Bindings for the Worldsemi WS2812B LED's powered by SPI.

LEDs

> +  Used SPI-MOSI only.

I believe you meant slightly different, i.e. "The only SPI MOSI wire
is in use." But then the question here is, what about CS?

...

> +config LEDS_WS2812B
> +        tristate "LED Support for Worldsemi WS2812B"
> +        depends on LEDS_CLASS
> +        depends on SPI

> +        depends on OF

It should be quite good justification why this (OF) depency is here

> +        help
> +          This option enables support for WS2812B LED's

LEDs

> +          through SPI.

...

> +/*
> + * LEDs driver for Worldsemi WS2812B through SPI

> + * SPI-MOSI for data transfer

Isn't it obvious?

> + * Required DMA transfers

Why?

> + * Copyright (C) 2022 Ivan Vozvakhov <i.vozvakhov@xxxxxxx>
> + *
> + * Inspired by (C) Martin Sperl <kernel@xxxxxxxxxxxxxxxx>

Inspired by a person? Or by some work done by this person? I would
recommend finding somebody for a proof-reading the English text in the
comments and similar pieces of this contribution.

> + *

Redundant blank line.

> + */

...

> +#include <linux/of.h>

I believe this can be replaced by mod_devicetable.h and property.h,
and the latter one if it's really needed. Let's see below...

> +#include <linux/module.h>
> +#include <linux/mutex.h>
> +#include <linux/slab.h>
> +#include <linux/spinlock.h>
> +#include <linux/workqueue.h>
> +#include <linux/spi/spi.h>
> +#include <linux/uaccess.h>
> +#include <linux/miscdevice.h>

Perhaps ordered?

...

> +#define SPI_BUS_SPEED_HZ 2500000

Why? It should come at least from DT.

> +/*
> + * Basically, SPI pull-up MOSI line, but for correct state it should be pull-down

the correct
pulled-down

> + * (RES is detected by low signal).
> + * SPI-MOSI for some controllers could have z-state with pull-down for MOSI

a Z-state

> + * before first SPI-CLK edges.

the first

> + * To eliminate it, send RES sequence before first bit's.
> + */

...

> +/*
> + * Ioctl interface for set's several led's at one time.

IOCTL
LEDs

> + *
> + * [start_led, stop_led)
> + */
> +struct ws2812b_multi_set {
> +       int start_led;
> +       int stop_led;

> +       uint8_t *brightnesses;

AFAIUC this is not gonna work in IOCTLs.

> +};
> +
> +#define LEDS_WS2812B_IOCTL_MAGIC    'z'
> +#define LEDS_WS2812B_IOCTL_MULTI_SET    \
> +       _IOW(LEDS_WS2812B_IOCTL_MAGIC, 0x01, struct ws2812b_multi_set)
> +#define LEDS_WS2812B_IOCTL_GET_LEDS_NUMBER      \
> +       _IOR(LEDS_WS2812B_IOCTL_MAGIC, 0x02, int)

Where is the documentation part?
And why do you need a non-standard IOCTL?

> +/*
> + * Each led's state bits coded by 3 bits,

If I got right the meaning it should be something like:
"Each of the LED state is coded by 3 bits,"

> + * 8 led's one-color state (actual LED) would take 24 real-bits.

LEDs

> + * That 24 bits divided into high, medium, low groups.
> + * All possible states defined there (see brightess_encode func. for masks).
> + */
> +const char byte2encoding_h[] = {
> +       0x92, 0x93, 0x9a, 0x9b,
> +       0xd2, 0xd3, 0xda, 0xdb

Here and elsewhere in similar cases leave the comma at the end.

> +};

...

> +static void brightess_encode(
> +               struct ws2812b_encoding *enc,
> +               const uint8_t val)

You have issues with indentation. Same applies to many other places in
this patch.

> +{
> +       enc->h = byte2encoding_h[(val >> 5) & 0x07];
> +       enc->m = byte2encoding_m[(val >> 3) & 0x03];
> +       enc->l = byte2encoding_l[(val >> 0) & 0x07];

Use GENMASK().

> +}

...

> +       led_enc = (struct ws2812b_encoding *)((uint8_t *)led_enc + DELAY_BEFORE_FIRST_DATA);

Urgh! Why so many interesting castings? Can you avoid this?

...

> +       for (i = 0; i < priv->num_leds; i++, led_enc++, led++)

Why do you need the last two increments? Wouldn't array indices work?

...

> +               brightness = kmalloc(sizeof(uint8_t) * leds_to_change, GFP_KERNEL);
> +               if (!brightness)
> +                       return -ENOMEM;
> +
> +               if (copy_from_user(brightness, ms.brightnesses,
> +                                       sizeof(uint8_t) * leds_to_change)) {
> +                       ret = -EFAULT;
> +                       break;
> +               }

Seems like NIH memdup_user()

...

> +static int ws2812b_parse_child_dt(const struct device *dev,
> +               struct device_node *child,
> +               struct ws2812b_led *led)
> +{
> +       struct led_classdev *ldev = &led->ldev;
> +       const char *state;
> +
> +       if (of_property_read_string(child, "label", &ldev->name))
> +               ldev->name = child->name;
> +
> +       state = of_get_property(child, "default-state", NULL);
> +       if (state) {
> +               if (!strcmp(state, "on")) {
> +                       ldev->brightness = LED_FULL;
> +               } else if (strcmp(state, "off")) {
> +                       dev_err(dev, "default-state can only be 'on' or 'off'");
> +                       return -EINVAL;
> +               }
> +               ldev->brightness = LED_OFF;
> +       }

Isn't it done in the LED core?

> +       ldev->brightness_set = ws2812b_led_set_brightness;
> +
> +       INIT_WORK(&led->work, ws2812b_led_work);
> +
> +       return 0;
> +}
> +
> +static int ws2812b_parse_dt(struct device *dev,
> +               struct ws2812b_priv *priv)
> +{
> +       struct device_node *child;

> +       int ret = 0, i = 0;

'ret = 0' is a redundant assignment.

> +       for_each_child_of_node(dev->of_node, child) {

device_for_each_child_node() ?

> +               struct ws2812b_led *led = &priv->leds[i];
> +
> +               led->priv = priv;
> +               led->dev = dev;
> +               led->child = child;
> +               led->num = i;
> +
> +               spin_lock_init(&led->led_data_lock);
> +
> +               ret = ws2812b_parse_child_dt(dev, child, led);
> +
> +               if (ret)
> +                       goto err;
> +
> +               ret = devm_led_classdev_register(dev, &led->ldev);
> +               if (ret) {
> +                       dev_err(dev, "failed to register led for %s: %d\n", led->ldev.name, ret);
> +                       goto err;
> +               }
> +
> +               led->ldev.dev->of_node = child;
> +               i++;
> +       }
> +
> +       return 0;
> +err:
> +       of_node_put(child);
> +       return ret;
> +}

...

> +static const struct of_device_id ws2812b_driver_ids[] = {
> +       { .compatible = "worldsemi,ws2812b" },

> +       {},

No comma for terminator entry.

> +};

...

> +       count_leds = of_get_child_count(dev->of_node);
> +       if (!count_leds) {
> +               dev_err(dev, "should define at least one led\n");
> +               return -EINVAL;

return dev_err_probe(...);

> +       }

...

> +       priv->leds = devm_kzalloc(dev, sizeof(struct ws2812b_led) * count_leds, GFP_KERNEL);

devm_kcalloc()

...

> +       len = DELAY_BEFORE_FIRST_DATA + count_leds * sizeof(struct ws2812b_encoding) + RESET_BYTES;

Needs a use of a macro from overflow.h. I believe it's struct_size() or so.

...

> +       if (of_property_read_string(dev->of_node, "device-name", &priv->mdev.name))

Is it standard binding?

> +               priv->mdev.name = DEFAULT_DEVICE_NAME;

...

> +       ret = misc_register(&priv->mdev);
> +       if (ret) {
> +               dev_err(dev, "can't register %s device\n", priv->mdev.name);
> +               return ret;

return dev_err_probe(...);

> +       }

...

> +       ret = ws2812b_parse_dt(dev, priv);
> +       if (ret)

Resource leak, no?

> +               return ret;

In general mixing devm with non-devm is a bad idea.

...

> +static int ws2812b_remove(struct spi_device *spi)
> +{
> +       struct ws2812b_priv *priv = spi_get_drvdata(spi);
> +       int i;
> +
> +       for (i = 0; i < priv->num_leds; i++) {
> +               led_classdev_unregister(&priv->leds[i].ldev);
> +               cancel_work_sync(&priv->leds[i].work);
> +       }
> +       cancel_work_sync(&priv->work_update_all);

Ditto.

> +       return 0;
> +}

...

> +       .driver = {
> +               .name = KBUILD_MODNAME,

> +               .owner = THIS_MODULE,

This is done by a macro. Read its implementation.

> +               .of_match_table = ws2812b_driver_ids,
> +       },
> +};

> +

Redundant blank line.

> +module_spi_driver(ws2812b_driver);

...

> +MODULE_ALIAS("spi:ws2812b");

Why?

-- 
With Best Regards,
Andy Shevchenko



[Index of Archives]     [Linux ARM Kernel]     [Linux ARM]     [Linux Omap]     [Fedora ARM]     [IETF Annouce]     [Security]     [Bugtraq]     [Linux OMAP]     [Linux MIPS]     [ECOS]     [Asterisk Internet PBX]     [Linux API]

  Powered by Linux