From patchwork Mon May 23 08:49:55 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jean-Jacques Hiblot X-Patchwork-Id: 575651 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 58385C433F5 for ; Mon, 23 May 2022 08:58:32 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S232209AbiEWI6b (ORCPT ); Mon, 23 May 2022 04:58:31 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:52344 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S232273AbiEWI6a (ORCPT ); Mon, 23 May 2022 04:58:30 -0400 X-Greylist: delayed 497 seconds by postgrey-1.37 at lindbergh.monkeyblade.net; Mon, 23 May 2022 01:58:27 PDT Received: from smtpout1.mo528.mail-out.ovh.net (smtpout1.mo528.mail-out.ovh.net [46.105.34.251]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id D0C073EAB5; Mon, 23 May 2022 01:58:27 -0700 (PDT) Received: from pro2.mail.ovh.net (unknown [10.109.143.120]) by mo528.mail-out.ovh.net (Postfix) with ESMTPS id 8F21E10347F4B; Mon, 23 May 2022 10:50:08 +0200 (CEST) Received: from localhost.localdomain (88.161.25.233) by DAG1EX2.emp2.local (172.16.2.2) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) id 15.1.2507.9; Mon, 23 May 2022 10:50:08 +0200 From: Jean-Jacques Hiblot To: Pavel Machek , Rob Herring , Krzysztof Kozlowski , Jean-Jacques Hiblot CC: , , Subject: [PATCH 1/3] dt-bindings: leds: Add bindings for the TLC5925 controller Date: Mon, 23 May 2022 10:49:55 +0200 Message-ID: <20220523084958.2723943-2-jjhiblot@traphandler.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20220523084958.2723943-1-jjhiblot@traphandler.com> References: <20220523084958.2723943-1-jjhiblot@traphandler.com> MIME-Version: 1.0 X-Originating-IP: [88.161.25.233] X-ClientProxiedBy: DAG8EX2.emp2.local (172.16.2.82) To DAG1EX2.emp2.local (172.16.2.2) X-Ovh-Tracer-Id: 5386305156936645083 X-VR-SPAMSTATE: OK X-VR-SPAMSCORE: -100 X-VR-SPAMCAUSE: gggruggvucftvghtrhhoucdtuddrgedvfedrjedtgddutdcutefuodetggdotefrodftvfcurfhrohhfihhlvgemucfqggfjpdevjffgvefmvefgnecuuegrihhlohhuthemucehtddtnecusecvtfgvtghiphhivghnthhsucdlqddutddtmdenucfjughrpefhvfevufffkffojghfggfgtghisehtkeertdertddtnecuhfhrohhmpeflvggrnhdqlfgrtghquhgvshcujfhisghlohhtuceojhhjhhhisghlohhtsehtrhgrphhhrghnughlvghrrdgtohhmqeenucggtffrrghtthgvrhhnpedttdffveeljeetleeijefhffevtdffleejheejiefgjeeludefvdevjedutdejhfenucffohhmrghinhepuggvvhhitggvthhrvggvrdhorhhgnecukfhppedtrddtrddtrddtpdekkedrudeiuddrvdehrddvfeefnecuvehluhhsthgvrhfuihiivgeptdenucfrrghrrghmpehmohguvgepshhmthhpohhuthdphhgvlhhopehprhhovddrmhgrihhlrdhovhhhrdhnvghtpdhinhgvtheptddrtddrtddrtddpmhgrihhlfhhrohhmpehjjhhhihgslhhothesthhrrghphhgrnhgulhgvrhdrtghomhdpnhgspghrtghpthhtohepuddprhgtphhtthhopehlihhnuhigqdhkvghrnhgvlhesvhhgvghrrdhkvghrnhgvlhdrohhrgh Precedence: bulk List-ID: X-Mailing-List: linux-leds@vger.kernel.org Add bindings documentation for the TLC5925 LED controller. Signed-off-by: Jean-Jacques Hiblot --- devicetree@vger.kernel.org .../bindings/leds/leds-tlc5925.yaml | 100 ++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 Documentation/devicetree/bindings/leds/leds-tlc5925.yaml diff --git a/Documentation/devicetree/bindings/leds/leds-tlc5925.yaml b/Documentation/devicetree/bindings/leds/leds-tlc5925.yaml new file mode 100644 index 000000000000..156db599d5a1 --- /dev/null +++ b/Documentation/devicetree/bindings/leds/leds-tlc5925.yaml @@ -0,0 +1,100 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/leds/leds-tlc5925.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: LEDs connected to TI TLC5925 controller + +maintainers: + - Jean-Jacques Hiblot + +description: | + The TLC5925 is a low-power 16-channel constant-current LED sink driver. + It is controlled through a SPI interface. + It is built around a shift register and latches which convert serial + input data into a parallel output. Several TLC5925 can be chained to + control more than 16 LEDs with a single chip-select. + The brightness level cannot be controlled, each LED is either on or off. + + Each LED is represented as a sub-node of the ti,tlc5925 device. + +properties: + compatible: + const: ti,tlc5925 + + shift_register_length: + maxItems: 1 + description: | + The length of the shift register. If several TLC5925 are chained, + shift_register_length should be set to 16 times the number of TLC5925. + The value must be a multiple of 8. + + "#address-cells": + const: 1 + + "#size-cells": + const: 0 + + output-enable-b-gpios: + description: | + GPIO pins to enable/disable the parallel output. They describe the GPIOs + connected to the OE/ pin of the TLC5925s. + +patternProperties: + "@[a-f0-9]+$": + type: object + + $ref: common.yaml# + + properties: + reg: + items: + description: | + LED pin number (must be lower than shift_register_length). + The furthest LED down the chain has the pin number 0. + + required: + - reg + +required: + - reg + - "#address-cells" + - "#size-cells" + - shift_register_length + +examples: + - | + &spi0 { + leds@2 { + compatible = "ti,tlc5925"; + reg = <0x02>; + spi-max-frequency = <30000000>; + shift_register_length = <32>; + output-enable-b-gpios = <&gpio0b 9 GPIO_ACTIVE_HIGH>, <&gpio0b 7 GPIO_ACTIVE_HIGH>; + #address-cells = <1>; + #size-cells = <0>; + + led-satus@0 { + reg = <0>; + function = LED_FUNCTION_STATUS; + color = ; + }; + + led-satus@4 { + reg = <4>; + function = LED_FUNCTION_STATUS; + color = ; + }; + + led-alive@24 { + reg = <24>; + label = "green:alive" + }; + + led-panic@31 { + reg = <31>; + label = "red:panic" + }; + }; + }; From patchwork Mon May 23 08:49:56 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jean-Jacques Hiblot X-Patchwork-Id: 575869 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 47E08C433EF for ; Mon, 23 May 2022 08:55:19 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S232047AbiEWIzR (ORCPT ); Mon, 23 May 2022 04:55:17 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:48292 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S232102AbiEWIzQ (ORCPT ); Mon, 23 May 2022 04:55:16 -0400 X-Greylist: delayed 303 seconds by postgrey-1.37 at lindbergh.monkeyblade.net; Mon, 23 May 2022 01:55:14 PDT Received: from smtpout1.mo3004.mail-out.ovh.net (smtpout1.mo3004.mail-out.ovh.net [79.137.123.219]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 5E7B53DDF6 for ; Mon, 23 May 2022 01:55:14 -0700 (PDT) Received: from pro2.mail.ovh.net (unknown [10.108.4.109]) by mo3004.mail-out.ovh.net (Postfix) with ESMTPS id 80163242FAF; Mon, 23 May 2022 08:50:09 +0000 (UTC) Received: from localhost.localdomain (88.161.25.233) by DAG1EX2.emp2.local (172.16.2.2) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) id 15.1.2507.9; Mon, 23 May 2022 10:50:09 +0200 From: Jean-Jacques Hiblot To: Pavel Machek CC: Jean-Jacques Hiblot , , Subject: [PATCH 2/3] leds: Add driver for the TLC5925 LED controller Date: Mon, 23 May 2022 10:49:56 +0200 Message-ID: <20220523084958.2723943-3-jjhiblot@traphandler.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20220523084958.2723943-1-jjhiblot@traphandler.com> References: <20220523084958.2723943-1-jjhiblot@traphandler.com> MIME-Version: 1.0 X-Originating-IP: [88.161.25.233] X-ClientProxiedBy: DAG8EX2.emp2.local (172.16.2.82) To DAG1EX2.emp2.local (172.16.2.2) X-Ovh-Tracer-Id: 5386586629945637361 X-VR-SPAMSTATE: OK X-VR-SPAMSCORE: -100 X-VR-SPAMCAUSE: gggruggvucftvghtrhhoucdtuddrgedvfedrjedtgddutdcutefuodetggdotefrodftvfcurfhrohhfihhlvgemucfqggfjpdevjffgvefmvefgnecuuegrihhlohhuthemucehtddtnecusecvtfgvtghiphhivghnthhsucdlqddutddtmdenucfjughrpefhvfevufffkffojghfggfgtghisehtkeertdertddtnecuhfhrohhmpeflvggrnhdqlfgrtghquhgvshcujfhisghlohhtuceojhhjhhhisghlohhtsehtrhgrphhhrghnughlvghrrdgtohhmqeenucggtffrrghtthgvrhhnpeejveevffefudeutddukeekueeifeeuhfetfefgfedulefhhffgvddvvdevieekveenucffohhmrghinhepthhirdgtohhmnecukfhppedtrddtrddtrddtpdekkedrudeiuddrvdehrddvfeefnecuvehluhhsthgvrhfuihiivgeptdenucfrrghrrghmpehmohguvgepshhmthhpohhuthdphhgvlhhopehprhhovddrmhgrihhlrdhovhhhrdhnvghtpdhinhgvtheptddrtddrtddrtddpmhgrihhlfhhrohhmpehjjhhhihgslhhothesthhrrghphhgrnhgulhgvrhdrtghomhdpnhgspghrtghpthhtohepuddprhgtphhtthhopehlihhnuhigqdhlvggushesvhhgvghrrdhkvghrnhgvlhdrohhrgh Precedence: bulk List-ID: X-Mailing-List: linux-leds@vger.kernel.org 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. Signed-off-by: Jean-Jacques Hiblot --- drivers/leds/Kconfig | 6 ++ drivers/leds/Makefile | 1 + drivers/leds/leds-tlc5925.c | 157 ++++++++++++++++++++++++++++++++++++ 3 files changed, 164 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..f33eff8b7333 --- /dev/null +++ b/drivers/leds/leds-tlc5925.c @@ -0,0 +1,157 @@ +// 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 +#include +#include +#include +#include +#include +#include + +struct single_led_priv { + int idx; + struct led_classdev cdev; +}; + +struct tlc5925_leds_priv { + int max_num_leds; + u8 *state; + spinlock_t lock; + 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; + + spin_lock(&priv->lock); + if (brightness) + priv->state[index / 8] |= (1 << (index % 8)); + else + priv->state[index / 8] &= ~(1 << (index % 8)); + spin_unlock(&priv->lock); + + return spi_write(spi, priv->state, priv->max_num_leds / 8); +} + + +static int tlc5925_probe(struct spi_device *spi) +{ + struct device *dev = &spi->dev; + struct fwnode_handle *child; + struct tlc5925_leds_priv *priv; + int ret; + int max_num_leds, count; + struct gpio_descs *gpios; + + count = device_get_child_node_count(dev); + if (!count) { + dev_err(dev, "no led defined.\n"); + return -ENODEV; + } + + ret = device_property_read_u32_array(dev, "shift_register_length", + &max_num_leds, 1); + if (ret) { + dev_err(dev, "'shift_register_length' property is required.\n"); + return -EINVAL; + } + + if (max_num_leds % 8) { + dev_err(dev, "'shift_register_length' must be a multiple of 8\n"); + return -EINVAL; + } + + if (max_num_leds == 0) { + dev_err(dev, "'shift_register_length' must be greater than 0\n"); + return -EINVAL; + } + + /* Assert all the OE/ lines */ + gpios = devm_gpiod_get_array(dev, "output-enable-b", GPIOD_OUT_LOW); + if (IS_ERR(gpios)) { + dev_err(dev, "Unable to get the 'output-enable-b' gpios\n"); + return PTR_ERR(gpios); + } + + priv = devm_kzalloc(dev, struct_size(priv, leds, count), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + spin_lock_init(&priv->lock); + + priv->state = devm_kzalloc(dev, DIV_ROUND_UP(max_num_leds, 8), 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; + + ret = fwnode_property_read_u32_array(child, "reg", &idx, 1); + if (ret || idx >= max_num_leds) { + dev_err(dev, "%s: invalid reg value. Ignoring.\n", + fwnode_get_name(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_err(dev, "%s: cannot create LED device.\n", + fwnode_get_name(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", }, + {}, +}; + +static struct spi_driver tlc5925_driver = { + .probe = tlc5925_probe, + .driver = { + .name = KBUILD_MODNAME, + .of_match_table = tlc5925_dt_ids, + }, +}; + +module_spi_driver(tlc5925_driver); + +MODULE_AUTHOR("Jean-Jacques Hiblot "); +MODULE_DESCRIPTION("TLC5925 LED driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("spi:tlc5925"); From patchwork Mon May 23 08:49:57 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jean-Jacques Hiblot X-Patchwork-Id: 575868 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 64948C433F5 for ; Mon, 23 May 2022 09:00:16 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S232434AbiEWJAO (ORCPT ); Mon, 23 May 2022 05:00:14 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:55136 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S232431AbiEWJAI (ORCPT ); Mon, 23 May 2022 05:00:08 -0400 Received: from smtpout1.mo3004.mail-out.ovh.net (smtpout1.mo3004.mail-out.ovh.net [79.137.123.219]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 88E4F43ADC for ; Mon, 23 May 2022 02:00:06 -0700 (PDT) Received: from pro2.mail.ovh.net (unknown [10.108.4.109]) by mo3004.mail-out.ovh.net (Postfix) with ESMTPS id 03E2A243537; Mon, 23 May 2022 08:50:10 +0000 (UTC) Received: from localhost.localdomain (88.161.25.233) by DAG1EX2.emp2.local (172.16.2.2) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) id 15.1.2507.9; Mon, 23 May 2022 10:50:09 +0200 From: Jean-Jacques Hiblot To: Pavel Machek CC: Jean-Jacques Hiblot , , Subject: [PATCH 3/3] leds: tlc5925: Add support for non blocking operations Date: Mon, 23 May 2022 10:49:57 +0200 Message-ID: <20220523084958.2723943-4-jjhiblot@traphandler.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20220523084958.2723943-1-jjhiblot@traphandler.com> References: <20220523084958.2723943-1-jjhiblot@traphandler.com> MIME-Version: 1.0 X-Originating-IP: [88.161.25.233] X-ClientProxiedBy: DAG8EX2.emp2.local (172.16.2.82) To DAG1EX2.emp2.local (172.16.2.2) X-Ovh-Tracer-Id: 5386868106753096155 X-VR-SPAMSTATE: OK X-VR-SPAMSCORE: -100 X-VR-SPAMCAUSE: gggruggvucftvghtrhhoucdtuddrgedvfedrjedtgddutdcutefuodetggdotefrodftvfcurfhrohhfihhlvgemucfqggfjpdevjffgvefmvefgnecuuegrihhlohhuthemucehtddtnecusecvtfgvtghiphhivghnthhsucdlqddutddtmdenucfjughrpefhvfevufffkffojghfggfgtghisehtkeertdertddtnecuhfhrohhmpeflvggrnhdqlfgrtghquhgvshcujfhisghlohhtuceojhhjhhhisghlohhtsehtrhgrphhhrghnughlvghrrdgtohhmqeenucggtffrrghtthgvrhhnpeduteevleevvefggfdvueffffejhfehheeuiedtgedtjeeghfehueduudegfeefueenucfkpheptddrtddrtddrtddpkeekrdduiedurddvhedrvdeffeenucevlhhushhtvghrufhiiigvpedtnecurfgrrhgrmhepmhhouggvpehsmhhtphhouhhtpdhhvghlohepphhrohdvrdhmrghilhdrohhvhhdrnhgvthdpihhnvghtpedtrddtrddtrddtpdhmrghilhhfrhhomhepjhhjhhhisghlohhtsehtrhgrphhhrghnughlvghrrdgtohhmpdhnsggprhgtphhtthhopedupdhrtghpthhtoheplhhinhhugidqkhgvrhhnvghlsehvghgvrhdrkhgvrhhnvghlrdhorhhg Precedence: bulk List-ID: X-Mailing-List: linux-leds@vger.kernel.org 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 --- drivers/leds/leds-tlc5925.c | 76 +++++++++++++++++++++++++++++++++---- 1 file changed, 68 insertions(+), 8 deletions(-) diff --git a/drivers/leds/leds-tlc5925.c b/drivers/leds/leds-tlc5925.c index f33eff8b7333..d7dd572eca69 100644 --- a/drivers/leds/leds-tlc5925.c +++ b/drivers/leds/leds-tlc5925.c @@ -18,6 +18,8 @@ #include #include +#define BITS_PER_ATOMIC (sizeof(atomic_t) * 8) + struct single_led_priv { int idx; struct led_classdev cdev; @@ -25,12 +27,35 @@ struct single_led_priv { struct tlc5925_leds_priv { int max_num_leds; - u8 *state; + int max_state; + atomic_t *state; + int *spi_buffer; spinlock_t lock; + 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) +{ + int i; + + spin_lock(&priv->lock); + for (i = 0; i < priv->max_state / (sizeof(atomic_t) * 8) ; i++) + priv->spi_buffer[i] = atomic_read(&priv->state[i]); + spin_unlock(&priv->lock); + + return spi_write(priv->spi, priv->spi_buffer, priv->max_num_leds / 8); +} + +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); @@ -40,16 +65,36 @@ static int tlc5925_brightness_set_blocking(struct led_classdev *cdev, cdev); int index = led->idx; - spin_lock(&priv->lock); if (brightness) - priv->state[index / 8] |= (1 << (index % 8)); + atomic_or(1 << (index % BITS_PER_ATOMIC), + &priv->state[index / BITS_PER_ATOMIC]); else - priv->state[index / 8] &= ~(1 << (index % 8)); - spin_unlock(&priv->lock); + atomic_and(~(1 << (index % BITS_PER_ATOMIC)), + &priv->state[index / BITS_PER_ATOMIC]); - return spi_write(spi, priv->state, priv->max_num_leds / 8); + 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; + + if (brightness) + atomic_or(1 << (index % BITS_PER_ATOMIC), + &priv->state[index / BITS_PER_ATOMIC]); + else + atomic_and(~(1 << (index % BITS_PER_ATOMIC)), + &priv->state[index / BITS_PER_ATOMIC]); + + cancel_work_sync(&priv->xmit_work); + return xmit(priv); +} static int tlc5925_probe(struct spi_device *spi) { @@ -96,10 +141,24 @@ static int tlc5925_probe(struct spi_device *spi) spin_lock_init(&priv->lock); - priv->state = devm_kzalloc(dev, DIV_ROUND_UP(max_num_leds, 8), GFP_KERNEL); + priv->spi = spi; + INIT_WORK(&priv->xmit_work, xmit_work); + + // Allocate the buffer used to hold the state of each LED + priv->max_state = round_up(max_num_leds, BITS_PER_ATOMIC); + priv->state = devm_kzalloc(dev, + priv->max_state / 8, + GFP_KERNEL); if (!priv->state) return -ENOMEM; + // Allocate a second buffer for the communication on the SPI bus + priv->spi_buffer = devm_kzalloc(dev, + priv->max_state / 8, + GFP_KERNEL); + if (!priv->spi_buffer) + return -ENOMEM; + priv->max_num_leds = max_num_leds; device_for_each_child_node(dev, child) { @@ -120,6 +179,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);