2022-06-27 08:42:46

by Jean-Jacques Hiblot

[permalink] [raw]
Subject: [PATCH v6 0/3] Add support for the TLC5925

This series adds the support for the TLC5925 LED controller.
This LED controller is driven through SPI. There is little internal logic
and it can be thought of as a deserializer + latches.
The TLC5925 itself drives up to 16 LEDs, but multiple TLC5925s can be
chained to drive more.

The first patch describes the dt bindings.
The second patch implements most of the driver and supports only
synchronous brightness setting (brightness_set_blocking).
The last patch implements the non-blocking version (brightness_set).

changes v5->v6:
* Make the 'ti,shif-register-length' property optional. The default
value is 16: the length of the shift register of a single TLC5925.
* fix minor issues in the binding description

changes v4->v5:
* add the headers that the code is a direct user of
* replace dev_err() with dev_err_probe() in ->probe()
* comestic changes (spaces, alignment and blank lines)

changes v3->v4:
* add missing MODULE_DEVICE_TABLE(of, ...)
* use dev_err_probe() to avoid spamming the log in case of deferred probe
* use bitmap ops instead of direct memory access + lock
* sort headers and a few other cosmetic changes.

changes v2->v3:
* fixed the yaml description of the bindings (now passes dt_binding_check)
* renamed shit-register-length into ti,shift-register-length and specify
its type

changes v1->v2:
* renamed property shift_register_length into shift-register-length
* add a SPI MODULE_DEVICE_TABLE structure
* fixed the yaml description of the bindings (now passes dt_binding_check)


Jean-Jacques Hiblot (3):
dt-bindings: leds: Add bindings for the TLC5925 controller
leds: Add driver for the TLC5925 LED controller
leds: tlc5925: Add support for non blocking operations

.../devicetree/bindings/leds/ti,tlc5925.yaml | 105 ++++++++++
drivers/leds/Kconfig | 6 +
drivers/leds/Makefile | 1 +
drivers/leds/leds-tlc5925.c | 182 ++++++++++++++++++
4 files changed, 294 insertions(+)
create mode 100644 Documentation/devicetree/bindings/leds/ti,tlc5925.yaml
create mode 100644 drivers/leds/leds-tlc5925.c

--
2.25.1


2022-06-27 08:54:34

by Jean-Jacques Hiblot

[permalink] [raw]
Subject: [PATCH v6 2/3] leds: Add driver for the TLC5925 LED controller

The TLC5925 is a 16-channels constant-current LED sink driver.
It is controlled via SPI but doesn't offer a register-based interface.
Instead it contains a shift register and latches that convert the
serial input into a parallel output.

Datasheet: https://www.ti.com/lit/ds/symlink/tlc5925.pdf
Signed-off-by: Jean-Jacques Hiblot <[email protected]>
Reviewed-by: Andy Shevchenko <[email protected]>
---
drivers/leds/Kconfig | 6 ++
drivers/leds/Makefile | 1 +
drivers/leds/leds-tlc5925.c | 148 ++++++++++++++++++++++++++++++++++++
3 files changed, 155 insertions(+)
create mode 100644 drivers/leds/leds-tlc5925.c

diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
index a49979f41eee..b17eb01210ba 100644
--- a/drivers/leds/Kconfig
+++ b/drivers/leds/Kconfig
@@ -658,6 +658,12 @@ config LEDS_TLC591XX
This option enables support for Texas Instruments TLC59108
and TLC59116 LED controllers.

+config LEDS_TLC5925
+ tristate "LED driver for TLC5925 controller"
+ depends on LEDS_CLASS && SPI
+ help
+ This option enables support for Texas Instruments TLC5925.
+
config LEDS_MAX77650
tristate "LED support for Maxim MAX77650 PMIC"
depends on LEDS_CLASS && MFD_MAX77650
diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile
index 4fd2f92cd198..9d15b88d482f 100644
--- a/drivers/leds/Makefile
+++ b/drivers/leds/Makefile
@@ -81,6 +81,7 @@ obj-$(CONFIG_LEDS_SYSCON) += leds-syscon.o
obj-$(CONFIG_LEDS_TCA6507) += leds-tca6507.o
obj-$(CONFIG_LEDS_TI_LMU_COMMON) += leds-ti-lmu-common.o
obj-$(CONFIG_LEDS_TLC591XX) += leds-tlc591xx.o
+obj-$(CONFIG_LEDS_TLC5925) += leds-tlc5925.o
obj-$(CONFIG_LEDS_TPS6105X) += leds-tps6105x.o
obj-$(CONFIG_LEDS_TURRIS_OMNIA) += leds-turris-omnia.o
obj-$(CONFIG_LEDS_WM831X_STATUS) += leds-wm831x-status.o
diff --git a/drivers/leds/leds-tlc5925.c b/drivers/leds/leds-tlc5925.c
new file mode 100644
index 000000000000..797836354c74
--- /dev/null
+++ b/drivers/leds/leds-tlc5925.c
@@ -0,0 +1,148 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * The driver supports controllers with a very simple SPI protocol:
+ * - the data is deserialized in a shift-register when CS is asserted
+ * - the data is latched when CS is de-asserted
+ * - the LED are either on or off (no control of the brightness)
+ *
+ * Supported devices:
+ * - "ti,tlc5925": Low-Power 16-Channel Constant-Current LED Sink Driver
+ * https://www.ti.com/lit/ds/symlink/tlc5925.pdf
+ */
+
+#include <linux/container_of.h>
+#include <linux/err.h>
+#include <linux/gpio/consumer.h>
+#include <linux/leds.h>
+#include <linux/module.h>
+#include <linux/property.h>
+#include <linux/spi/spi.h>
+#include <linux/types.h>
+
+#define TLC5925_SHIFT_REGISTER_LENGTH 16
+
+struct single_led_priv {
+ struct led_classdev cdev;
+ int idx;
+};
+
+struct tlc5925_leds_priv {
+ int max_num_leds;
+ unsigned long *state;
+ struct single_led_priv leds[];
+};
+
+static int tlc5925_brightness_set_blocking(struct led_classdev *cdev,
+ enum led_brightness brightness)
+{
+ struct spi_device *spi = to_spi_device(cdev->dev->parent);
+ struct tlc5925_leds_priv *priv = spi_get_drvdata(spi);
+ struct single_led_priv *led =
+ container_of(cdev, struct single_led_priv, cdev);
+ int index = led->idx;
+
+ assign_bit(index, priv->state, !!brightness);
+
+ return spi_write(spi, priv->state, BITS_TO_BYTES(priv->max_num_leds));
+}
+
+static int tlc5925_probe(struct spi_device *spi)
+{
+ struct device *dev = &spi->dev;
+ struct fwnode_handle *child;
+ struct tlc5925_leds_priv *priv;
+ int count;
+ int max_num_leds = TLC5925_SHIFT_REGISTER_LENGTH;
+ struct gpio_descs *gpios;
+
+ /* Assert all the OE/ lines */
+ gpios = devm_gpiod_get_array(dev, "output-enable-b", GPIOD_OUT_LOW);
+ if (IS_ERR(gpios))
+ return dev_err_probe(dev, PTR_ERR(gpios),
+ "Unable to get the 'output-enable-b' gpios\n");
+
+ count = device_get_child_node_count(dev);
+ if (!count)
+ return dev_err_probe(dev, -ENODEV, "no led defined.\n");
+
+ device_property_read_u32(dev, "ti,shift-register-length",
+ &max_num_leds);
+
+ if (max_num_leds % 8)
+ return dev_err_probe(dev, -EINVAL,
+ "'ti,shift-register-length' must be a multiple of 8\n");
+ if (max_num_leds == 0)
+ return dev_err_probe(dev, -EINVAL,
+ "'ti,shift-register-length' must be greater than 0\n");
+
+ priv = devm_kzalloc(dev, struct_size(priv, leds, count), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->state = devm_bitmap_zalloc(dev, max_num_leds, GFP_KERNEL);
+ if (!priv->state)
+ return -ENOMEM;
+
+ priv->max_num_leds = max_num_leds;
+
+ device_for_each_child_node(dev, child) {
+ struct led_init_data init_data = { .fwnode = child };
+ struct led_classdev *cdev;
+ u32 idx;
+ int ret;
+
+ ret = fwnode_property_read_u32(child, "reg", &idx);
+ if (ret || idx >= max_num_leds) {
+ dev_warn(dev, "%pfwP: invalid reg value. Ignoring.\n",
+ child);
+ fwnode_handle_put(child);
+ continue;
+ }
+
+ count--;
+ priv->leds[count].idx = idx;
+ cdev = &(priv->leds[count].cdev);
+ cdev->brightness = LED_OFF;
+ cdev->max_brightness = 1;
+ cdev->brightness_set_blocking = tlc5925_brightness_set_blocking;
+
+ ret = devm_led_classdev_register_ext(dev, cdev, &init_data);
+ if (ret) {
+ dev_warn(dev, "%pfwP: cannot create LED device.\n",
+ child);
+ fwnode_handle_put(child);
+ continue;
+ }
+ }
+
+ spi_set_drvdata(spi, priv);
+
+ return 0;
+}
+
+static const struct of_device_id tlc5925_dt_ids[] = {
+ { .compatible = "ti,tlc5925", },
+ {}
+};
+MODULE_DEVICE_TABLE(of, tlc5925_dt_ids);
+
+static const struct spi_device_id tlc5925_id[] = {
+ {"tlc5925", 0},
+ {}
+};
+MODULE_DEVICE_TABLE(spi, tlc5925_id);
+
+static struct spi_driver tlc5925_driver = {
+ .driver = {
+ .name = KBUILD_MODNAME,
+ .of_match_table = tlc5925_dt_ids,
+ },
+ .id_table = tlc5925_id,
+ .probe = tlc5925_probe,
+};
+module_spi_driver(tlc5925_driver);
+
+MODULE_AUTHOR("Jean-Jacques Hiblot <[email protected]>");
+MODULE_DESCRIPTION("TLC5925 LED driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("spi:tlc5925");
--
2.25.1

2022-06-27 09:16:58

by Jean-Jacques Hiblot

[permalink] [raw]
Subject: [PATCH v6 3/3] leds: tlc5925: Add support for non blocking operations

Settings multiple LEDs in a row can be a slow operation because of the
time required to acquire the bus and prepare the transfer.
And, in most cases, it is not required that the operation is synchronous.
Implementing the non-blocking brightness_set() for such cases.
A work queue is used to perform the actual SPI transfer.

The blocking method is still available in case someone needs to perform
this operation synchronously (ie by calling led_set_brightness_sync()).

Signed-off-by: Jean-Jacques Hiblot <[email protected]>
Reviewed-by: Andy Shevchenko <[email protected]>
---
drivers/leds/leds-tlc5925.c | 38 +++++++++++++++++++++++++++++++++++--
1 file changed, 36 insertions(+), 2 deletions(-)

diff --git a/drivers/leds/leds-tlc5925.c b/drivers/leds/leds-tlc5925.c
index 797836354c74..0423e3592bd7 100644
--- a/drivers/leds/leds-tlc5925.c
+++ b/drivers/leds/leds-tlc5925.c
@@ -18,6 +18,7 @@
#include <linux/property.h>
#include <linux/spi/spi.h>
#include <linux/types.h>
+#include <linux/workqueue.h>

#define TLC5925_SHIFT_REGISTER_LENGTH 16

@@ -29,10 +30,25 @@ struct single_led_priv {
struct tlc5925_leds_priv {
int max_num_leds;
unsigned long *state;
+ struct spi_device *spi;
+ struct work_struct xmit_work;
struct single_led_priv leds[];
};

-static int tlc5925_brightness_set_blocking(struct led_classdev *cdev,
+static int xmit(struct tlc5925_leds_priv *priv)
+{
+ return spi_write(priv->spi, priv->state, BITS_TO_BYTES(priv->max_num_leds));
+}
+
+static void xmit_work(struct work_struct *ws)
+{
+ struct tlc5925_leds_priv *priv =
+ container_of(ws, struct tlc5925_leds_priv, xmit_work);
+
+ xmit(priv);
+};
+
+static void tlc5925_brightness_set(struct led_classdev *cdev,
enum led_brightness brightness)
{
struct spi_device *spi = to_spi_device(cdev->dev->parent);
@@ -43,9 +59,23 @@ static int tlc5925_brightness_set_blocking(struct led_classdev *cdev,

assign_bit(index, priv->state, !!brightness);

- return spi_write(spi, priv->state, BITS_TO_BYTES(priv->max_num_leds));
+ schedule_work(&priv->xmit_work);
}

+static int tlc5925_brightness_set_blocking(struct led_classdev *cdev,
+ enum led_brightness brightness)
+{
+ struct spi_device *spi = to_spi_device(cdev->dev->parent);
+ struct tlc5925_leds_priv *priv = spi_get_drvdata(spi);
+ struct single_led_priv *led =
+ container_of(cdev, struct single_led_priv, cdev);
+ int index = led->idx;
+
+ assign_bit(index, priv->state, !!brightness);
+
+ cancel_work_sync(&priv->xmit_work);
+ return xmit(priv);
+}
static int tlc5925_probe(struct spi_device *spi)
{
struct device *dev = &spi->dev;
@@ -83,6 +113,9 @@ static int tlc5925_probe(struct spi_device *spi)
if (!priv->state)
return -ENOMEM;

+ priv->spi = spi;
+ INIT_WORK(&priv->xmit_work, xmit_work);
+
priv->max_num_leds = max_num_leds;

device_for_each_child_node(dev, child) {
@@ -104,6 +137,7 @@ static int tlc5925_probe(struct spi_device *spi)
cdev = &(priv->leds[count].cdev);
cdev->brightness = LED_OFF;
cdev->max_brightness = 1;
+ cdev->brightness_set = tlc5925_brightness_set;
cdev->brightness_set_blocking = tlc5925_brightness_set_blocking;

ret = devm_led_classdev_register_ext(dev, cdev, &init_data);
--
2.25.1