From 59b3e31e73322ec195e45e0a1da712c752ee1b0c Mon Sep 17 00:00:00 2001 From: Daniel Golle Date: Tue, 28 Nov 2023 04:00:10 +0000 Subject: leds: trigger: netdev: Extend speeds up to 10G Add 2.5G, 5G and 10G as available speeds to the netdev LED trigger. Signed-off-by: Daniel Golle Reviewed-by: Andrew Lunn Link: https://lore.kernel.org/r/99e7d3304c6bba7f4863a4a80764a869855f2085.1701143925.git.daniel@makrotopia.org Signed-off-by: Lee Jones --- drivers/leds/trigger/ledtrig-netdev.c | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) (limited to 'drivers/leds') diff --git a/drivers/leds/trigger/ledtrig-netdev.c b/drivers/leds/trigger/ledtrig-netdev.c index e358e77e4b38..bd68da15c723 100644 --- a/drivers/leds/trigger/ledtrig-netdev.c +++ b/drivers/leds/trigger/ledtrig-netdev.c @@ -99,6 +99,18 @@ static void set_baseline_state(struct led_netdev_data *trigger_data) trigger_data->link_speed == SPEED_1000) blink_on = true; + if (test_bit(TRIGGER_NETDEV_LINK_2500, &trigger_data->mode) && + trigger_data->link_speed == SPEED_2500) + blink_on = true; + + if (test_bit(TRIGGER_NETDEV_LINK_5000, &trigger_data->mode) && + trigger_data->link_speed == SPEED_5000) + blink_on = true; + + if (test_bit(TRIGGER_NETDEV_LINK_10000, &trigger_data->mode) && + trigger_data->link_speed == SPEED_10000) + blink_on = true; + if (test_bit(TRIGGER_NETDEV_HALF_DUPLEX, &trigger_data->mode) && trigger_data->duplex == DUPLEX_HALF) blink_on = true; @@ -286,6 +298,9 @@ static ssize_t netdev_led_attr_show(struct device *dev, char *buf, case TRIGGER_NETDEV_LINK_10: case TRIGGER_NETDEV_LINK_100: case TRIGGER_NETDEV_LINK_1000: + case TRIGGER_NETDEV_LINK_2500: + case TRIGGER_NETDEV_LINK_5000: + case TRIGGER_NETDEV_LINK_10000: case TRIGGER_NETDEV_HALF_DUPLEX: case TRIGGER_NETDEV_FULL_DUPLEX: case TRIGGER_NETDEV_TX: @@ -316,6 +331,9 @@ static ssize_t netdev_led_attr_store(struct device *dev, const char *buf, case TRIGGER_NETDEV_LINK_10: case TRIGGER_NETDEV_LINK_100: case TRIGGER_NETDEV_LINK_1000: + case TRIGGER_NETDEV_LINK_2500: + case TRIGGER_NETDEV_LINK_5000: + case TRIGGER_NETDEV_LINK_10000: case TRIGGER_NETDEV_HALF_DUPLEX: case TRIGGER_NETDEV_FULL_DUPLEX: case TRIGGER_NETDEV_TX: @@ -334,7 +352,10 @@ static ssize_t netdev_led_attr_store(struct device *dev, const char *buf, if (test_bit(TRIGGER_NETDEV_LINK, &mode) && (test_bit(TRIGGER_NETDEV_LINK_10, &mode) || test_bit(TRIGGER_NETDEV_LINK_100, &mode) || - test_bit(TRIGGER_NETDEV_LINK_1000, &mode))) + test_bit(TRIGGER_NETDEV_LINK_1000, &mode) || + test_bit(TRIGGER_NETDEV_LINK_2500, &mode) || + test_bit(TRIGGER_NETDEV_LINK_5000, &mode) || + test_bit(TRIGGER_NETDEV_LINK_10000, &mode))) return -EINVAL; cancel_delayed_work_sync(&trigger_data->work); @@ -364,6 +385,9 @@ DEFINE_NETDEV_TRIGGER(link, TRIGGER_NETDEV_LINK); DEFINE_NETDEV_TRIGGER(link_10, TRIGGER_NETDEV_LINK_10); DEFINE_NETDEV_TRIGGER(link_100, TRIGGER_NETDEV_LINK_100); DEFINE_NETDEV_TRIGGER(link_1000, TRIGGER_NETDEV_LINK_1000); +DEFINE_NETDEV_TRIGGER(link_2500, TRIGGER_NETDEV_LINK_2500); +DEFINE_NETDEV_TRIGGER(link_5000, TRIGGER_NETDEV_LINK_5000); +DEFINE_NETDEV_TRIGGER(link_10000, TRIGGER_NETDEV_LINK_10000); DEFINE_NETDEV_TRIGGER(half_duplex, TRIGGER_NETDEV_HALF_DUPLEX); DEFINE_NETDEV_TRIGGER(full_duplex, TRIGGER_NETDEV_FULL_DUPLEX); DEFINE_NETDEV_TRIGGER(tx, TRIGGER_NETDEV_TX); @@ -422,6 +446,9 @@ static struct attribute *netdev_trig_attrs[] = { &dev_attr_link_10.attr, &dev_attr_link_100.attr, &dev_attr_link_1000.attr, + &dev_attr_link_2500.attr, + &dev_attr_link_5000.attr, + &dev_attr_link_10000.attr, &dev_attr_full_duplex.attr, &dev_attr_half_duplex.attr, &dev_attr_rx.attr, @@ -519,6 +546,9 @@ static void netdev_trig_work(struct work_struct *work) test_bit(TRIGGER_NETDEV_LINK_10, &trigger_data->mode) || test_bit(TRIGGER_NETDEV_LINK_100, &trigger_data->mode) || test_bit(TRIGGER_NETDEV_LINK_1000, &trigger_data->mode) || + test_bit(TRIGGER_NETDEV_LINK_2500, &trigger_data->mode) || + test_bit(TRIGGER_NETDEV_LINK_5000, &trigger_data->mode) || + test_bit(TRIGGER_NETDEV_LINK_10000, &trigger_data->mode) || test_bit(TRIGGER_NETDEV_HALF_DUPLEX, &trigger_data->mode) || test_bit(TRIGGER_NETDEV_FULL_DUPLEX, &trigger_data->mode); interval = jiffies_to_msecs( -- cgit v1.2.3 From a82cc9b8debfa3e71098a71bece2a696cc1d9624 Mon Sep 17 00:00:00 2001 From: Rob Herring Date: Wed, 25 Oct 2023 14:06:19 -0500 Subject: leds: syscon: Support 'reg' in addition to 'offset' for register address The register-bit-led binding now also supports 'reg' in addition to 'offset' for the register address. Add support to the driver to get the address from 'reg'. Signed-off-by: Rob Herring Reviewed-by: Linus Walleij Link: https://lore.kernel.org/r/20231025190619.881090-2-robh@kernel.org Signed-off-by: Lee Jones --- drivers/leds/leds-syscon.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'drivers/leds') diff --git a/drivers/leds/leds-syscon.c b/drivers/leds/leds-syscon.c index 360a376fa738..d633ad519d0c 100644 --- a/drivers/leds/leds-syscon.c +++ b/drivers/leds/leds-syscon.c @@ -81,7 +81,8 @@ static int syscon_led_probe(struct platform_device *pdev) sled->map = map; - if (of_property_read_u32(np, "offset", &sled->offset)) + if (of_property_read_u32(np, "reg", &sled->offset) && + of_property_read_u32(np, "offset", &sled->offset)) return -EINVAL; if (of_property_read_u32(np, "mask", &sled->mask)) return -EINVAL; -- cgit v1.2.3 From ec95a68dad00c81d53ab9aa9bcbdf0a86347e0d9 Mon Sep 17 00:00:00 2001 From: Samuel Holland Date: Sun, 29 Oct 2023 16:26:56 -0500 Subject: leds: sun50i-a100: New driver for the A100 LED controller Some Allwinner sunxi SoCs, starting with the A100, contain an LED controller designed to drive RGB LED pixels. Add a driver for it using the multicolor LED framework, and with LEDs defined in the device tree. Acked-by: Guo Ren Acked-by: Jernej Skrabec Acked-by: Palmer Dabbelt Signed-off-by: Samuel Holland Link: https://lore.kernel.org/r/20231029212738.7871-3-samuel@sholland.org Signed-off-by: Lee Jones --- drivers/leds/Kconfig | 9 + drivers/leds/Makefile | 1 + drivers/leds/leds-sun50i-a100.c | 580 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 590 insertions(+) create mode 100644 drivers/leds/leds-sun50i-a100.c (limited to 'drivers/leds') diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig index 6292fddcc55c..6496ef525579 100644 --- a/drivers/leds/Kconfig +++ b/drivers/leds/Kconfig @@ -298,6 +298,15 @@ config LEDS_COBALT_RAQ help This option enables support for the Cobalt Raq series LEDs. +config LEDS_SUN50I_A100 + tristate "LED support for Allwinner A100 RGB LED controller" + depends on LEDS_CLASS_MULTICOLOR && OF + depends on ARCH_SUNXI || COMPILE_TEST + help + This option enables support for the RGB LED controller found + in some Allwinner sunxi SoCs, including A100, R329, and D1. + It uses a one-wire interface to control up to 1024 LEDs. + config LEDS_SUNFIRE tristate "LED support for SunFire servers." depends on LEDS_CLASS diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile index d7348e8bc019..b82a538e3f8c 100644 --- a/drivers/leds/Makefile +++ b/drivers/leds/Makefile @@ -78,6 +78,7 @@ obj-$(CONFIG_LEDS_POWERNV) += leds-powernv.o obj-$(CONFIG_LEDS_PWM) += leds-pwm.o obj-$(CONFIG_LEDS_REGULATOR) += leds-regulator.o obj-$(CONFIG_LEDS_SC27XX_BLTC) += leds-sc27xx-bltc.o +obj-$(CONFIG_LEDS_SUN50I_A100) += leds-sun50i-a100.o obj-$(CONFIG_LEDS_SUNFIRE) += leds-sunfire.o obj-$(CONFIG_LEDS_SYSCON) += leds-syscon.o obj-$(CONFIG_LEDS_TCA6507) += leds-tca6507.o diff --git a/drivers/leds/leds-sun50i-a100.c b/drivers/leds/leds-sun50i-a100.c new file mode 100644 index 000000000000..e4a7e692a908 --- /dev/null +++ b/drivers/leds/leds-sun50i-a100.c @@ -0,0 +1,580 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2021-2023 Samuel Holland + * + * Partly based on drivers/leds/leds-turris-omnia.c, which is: + * Copyright (c) 2020 by Marek Behún + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define LEDC_CTRL_REG 0x0000 +#define LEDC_CTRL_REG_DATA_LENGTH GENMASK(28, 16) +#define LEDC_CTRL_REG_RGB_MODE GENMASK(8, 6) +#define LEDC_CTRL_REG_LEDC_EN BIT(0) +#define LEDC_T01_TIMING_CTRL_REG 0x0004 +#define LEDC_T01_TIMING_CTRL_REG_T1H GENMASK(26, 21) +#define LEDC_T01_TIMING_CTRL_REG_T1L GENMASK(20, 16) +#define LEDC_T01_TIMING_CTRL_REG_T0H GENMASK(10, 6) +#define LEDC_T01_TIMING_CTRL_REG_T0L GENMASK(5, 0) +#define LEDC_RESET_TIMING_CTRL_REG 0x000c +#define LEDC_RESET_TIMING_CTRL_REG_TR GENMASK(28, 16) +#define LEDC_RESET_TIMING_CTRL_REG_LED_NUM GENMASK(9, 0) +#define LEDC_DATA_REG 0x0014 +#define LEDC_DMA_CTRL_REG 0x0018 +#define LEDC_DMA_CTRL_REG_DMA_EN BIT(5) +#define LEDC_DMA_CTRL_REG_FIFO_TRIG_LEVEL GENMASK(4, 0) +#define LEDC_INT_CTRL_REG 0x001c +#define LEDC_INT_CTRL_REG_GLOBAL_INT_EN BIT(5) +#define LEDC_INT_CTRL_REG_FIFO_CPUREQ_INT_EN BIT(1) +#define LEDC_INT_CTRL_REG_TRANS_FINISH_INT_EN BIT(0) +#define LEDC_INT_STS_REG 0x0020 +#define LEDC_INT_STS_REG_FIFO_WLW GENMASK(15, 10) +#define LEDC_INT_STS_REG_FIFO_CPUREQ_INT BIT(1) +#define LEDC_INT_STS_REG_TRANS_FINISH_INT BIT(0) + +#define LEDC_FIFO_DEPTH 32U +#define LEDC_MAX_LEDS 1024 +#define LEDC_CHANNELS_PER_LED 3 /* RGB */ + +#define LEDS_TO_BYTES(n) ((n) * sizeof(u32)) + +struct sun50i_a100_ledc_led { + struct led_classdev_mc mc_cdev; + struct mc_subled subled_info[LEDC_CHANNELS_PER_LED]; + u32 addr; +}; + +#define to_ledc_led(mc) container_of(mc, struct sun50i_a100_ledc_led, mc_cdev) + +struct sun50i_a100_ledc_timing { + u32 t0h_ns; + u32 t0l_ns; + u32 t1h_ns; + u32 t1l_ns; + u32 treset_ns; +}; + +struct sun50i_a100_ledc { + struct device *dev; + void __iomem *base; + struct clk *bus_clk; + struct clk *mod_clk; + struct reset_control *reset; + + u32 *buffer; + struct dma_chan *dma_chan; + dma_addr_t dma_handle; + unsigned int pio_length; + unsigned int pio_offset; + + spinlock_t lock; + unsigned int next_length; + bool xfer_active; + + u32 format; + struct sun50i_a100_ledc_timing timing; + + u32 max_addr; + u32 num_leds; + struct sun50i_a100_ledc_led leds[] __counted_by(num_leds); +}; + +static int sun50i_a100_ledc_dma_xfer(struct sun50i_a100_ledc *priv, unsigned int length) +{ + struct dma_async_tx_descriptor *desc; + dma_cookie_t cookie; + + desc = dmaengine_prep_slave_single(priv->dma_chan, priv->dma_handle, + LEDS_TO_BYTES(length), DMA_MEM_TO_DEV, 0); + if (!desc) + return -ENOMEM; + + cookie = dmaengine_submit(desc); + if (dma_submit_error(cookie)) + return -EIO; + + dma_async_issue_pending(priv->dma_chan); + + return 0; +} + +static void sun50i_a100_ledc_pio_xfer(struct sun50i_a100_ledc *priv, unsigned int fifo_used) +{ + unsigned int burst, length, offset; + u32 control; + + length = priv->pio_length; + offset = priv->pio_offset; + burst = min(length, LEDC_FIFO_DEPTH - fifo_used); + + iowrite32_rep(priv->base + LEDC_DATA_REG, priv->buffer + offset, burst); + + if (burst < length) { + priv->pio_length = length - burst; + priv->pio_offset = offset + burst; + + if (!offset) { + control = readl(priv->base + LEDC_INT_CTRL_REG); + control |= LEDC_INT_CTRL_REG_FIFO_CPUREQ_INT_EN; + writel(control, priv->base + LEDC_INT_CTRL_REG); + } + } else { + /* Disable the request IRQ once all data is written. */ + control = readl(priv->base + LEDC_INT_CTRL_REG); + control &= ~LEDC_INT_CTRL_REG_FIFO_CPUREQ_INT_EN; + writel(control, priv->base + LEDC_INT_CTRL_REG); + } +} + +static void sun50i_a100_ledc_start_xfer(struct sun50i_a100_ledc *priv, unsigned int length) +{ + bool use_dma = false; + u32 control; + + if (priv->dma_chan && length > LEDC_FIFO_DEPTH) { + int ret; + + ret = sun50i_a100_ledc_dma_xfer(priv, length); + if (ret) + dev_warn(priv->dev, "Failed to set up DMA (%d), using PIO\n", ret); + else + use_dma = true; + } + + /* The DMA trigger level must be at least the burst length. */ + control = FIELD_PREP(LEDC_DMA_CTRL_REG_DMA_EN, use_dma) | + FIELD_PREP_CONST(LEDC_DMA_CTRL_REG_FIFO_TRIG_LEVEL, LEDC_FIFO_DEPTH / 2); + writel(control, priv->base + LEDC_DMA_CTRL_REG); + + control = readl(priv->base + LEDC_CTRL_REG); + control &= ~LEDC_CTRL_REG_DATA_LENGTH; + control |= FIELD_PREP(LEDC_CTRL_REG_DATA_LENGTH, length) | LEDC_CTRL_REG_LEDC_EN; + writel(control, priv->base + LEDC_CTRL_REG); + + if (!use_dma) { + /* The FIFO is empty when starting a new transfer. */ + unsigned int fifo_used = 0; + + priv->pio_length = length; + priv->pio_offset = 0; + + sun50i_a100_ledc_pio_xfer(priv, fifo_used); + } +} + +static irqreturn_t sun50i_a100_ledc_irq(int irq, void *data) +{ + struct sun50i_a100_ledc *priv = data; + u32 status; + + status = readl(priv->base + LEDC_INT_STS_REG); + + if (status & LEDC_INT_STS_REG_TRANS_FINISH_INT) { + unsigned int next_length; + + spin_lock(&priv->lock); + + /* If another transfer is queued, dequeue and start it. */ + next_length = priv->next_length; + if (next_length) + priv->next_length = 0; + else + priv->xfer_active = false; + + spin_unlock(&priv->lock); + + if (next_length) + sun50i_a100_ledc_start_xfer(priv, next_length); + } else if (status & LEDC_INT_STS_REG_FIFO_CPUREQ_INT) { + /* Continue the current transfer. */ + sun50i_a100_ledc_pio_xfer(priv, FIELD_GET(LEDC_INT_STS_REG_FIFO_WLW, status)); + } + + /* Clear the W1C status bits. */ + writel(status, priv->base + LEDC_INT_STS_REG); + + return IRQ_HANDLED; +} + +static void sun50i_a100_ledc_brightness_set(struct led_classdev *cdev, + enum led_brightness brightness) +{ + struct sun50i_a100_ledc *priv = dev_get_drvdata(cdev->dev->parent); + struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(cdev); + struct sun50i_a100_ledc_led *led = to_ledc_led(mc_cdev); + unsigned int next_length; + unsigned long flags; + bool xfer_active; + + led_mc_calc_color_components(mc_cdev, brightness); + + priv->buffer[led->addr] = led->subled_info[0].brightness << 16 | + led->subled_info[1].brightness << 8 | + led->subled_info[2].brightness; + + spin_lock_irqsave(&priv->lock, flags); + + /* Start, enqueue, or extend an enqueued transfer, as appropriate. */ + next_length = max(priv->next_length, led->addr + 1); + xfer_active = priv->xfer_active; + if (xfer_active) + priv->next_length = next_length; + else + priv->xfer_active = true; + + spin_unlock_irqrestore(&priv->lock, flags); + + if (!xfer_active) + sun50i_a100_ledc_start_xfer(priv, next_length); +} + +static const char *const sun50i_a100_ledc_formats[] = { + "rgb", "rbg", "grb", "gbr", "brg", "bgr", +}; + +static int sun50i_a100_ledc_parse_format(const struct device_node *np, + struct sun50i_a100_ledc *priv) +{ + const char *format = "grb"; + u32 i; + + of_property_read_string(np, "allwinner,pixel-format", &format); + + for (i = 0; i < ARRAY_SIZE(sun50i_a100_ledc_formats); i++) { + if (!strcmp(format, sun50i_a100_ledc_formats[i])) { + priv->format = i; + return 0; + } + } + + return dev_err_probe(priv->dev, -EINVAL, "Bad pixel format '%s'\n", format); +} + +static void sun50i_a100_ledc_set_format(struct sun50i_a100_ledc *priv) +{ + u32 control; + + control = readl(priv->base + LEDC_CTRL_REG); + control &= ~LEDC_CTRL_REG_RGB_MODE; + control |= FIELD_PREP(LEDC_CTRL_REG_RGB_MODE, priv->format); + writel(control, priv->base + LEDC_CTRL_REG); +} + +static const struct sun50i_a100_ledc_timing sun50i_a100_ledc_default_timing = { + .t0h_ns = 336, + .t0l_ns = 840, + .t1h_ns = 882, + .t1l_ns = 294, + .treset_ns = 300000, +}; + +static int sun50i_a100_ledc_parse_timing(const struct device_node *np, + struct sun50i_a100_ledc *priv) +{ + struct sun50i_a100_ledc_timing *timing = &priv->timing; + + *timing = sun50i_a100_ledc_default_timing; + + of_property_read_u32(np, "allwinner,t0h-ns", &timing->t0h_ns); + of_property_read_u32(np, "allwinner,t0l-ns", &timing->t0l_ns); + of_property_read_u32(np, "allwinner,t1h-ns", &timing->t1h_ns); + of_property_read_u32(np, "allwinner,t1l-ns", &timing->t1l_ns); + of_property_read_u32(np, "allwinner,treset-ns", &timing->treset_ns); + + return 0; +} + +static void sun50i_a100_ledc_set_timing(struct sun50i_a100_ledc *priv) +{ + const struct sun50i_a100_ledc_timing *timing = &priv->timing; + unsigned long mod_freq = clk_get_rate(priv->mod_clk); + u32 cycle_ns = NSEC_PER_SEC / mod_freq; + u32 control; + + control = FIELD_PREP(LEDC_T01_TIMING_CTRL_REG_T1H, timing->t1h_ns / cycle_ns) | + FIELD_PREP(LEDC_T01_TIMING_CTRL_REG_T1L, timing->t1l_ns / cycle_ns) | + FIELD_PREP(LEDC_T01_TIMING_CTRL_REG_T0H, timing->t0h_ns / cycle_ns) | + FIELD_PREP(LEDC_T01_TIMING_CTRL_REG_T0L, timing->t0l_ns / cycle_ns); + writel(control, priv->base + LEDC_T01_TIMING_CTRL_REG); + + control = FIELD_PREP(LEDC_RESET_TIMING_CTRL_REG_TR, timing->treset_ns / cycle_ns) | + FIELD_PREP(LEDC_RESET_TIMING_CTRL_REG_LED_NUM, priv->max_addr); + writel(control, priv->base + LEDC_RESET_TIMING_CTRL_REG); +} + +static int sun50i_a100_ledc_resume(struct device *dev) +{ + struct sun50i_a100_ledc *priv = dev_get_drvdata(dev); + int ret; + + ret = reset_control_deassert(priv->reset); + if (ret) + return ret; + + ret = clk_prepare_enable(priv->bus_clk); + if (ret) + goto err_assert_reset; + + ret = clk_prepare_enable(priv->mod_clk); + if (ret) + goto err_disable_bus_clk; + + sun50i_a100_ledc_set_format(priv); + sun50i_a100_ledc_set_timing(priv); + + writel(LEDC_INT_CTRL_REG_GLOBAL_INT_EN | LEDC_INT_CTRL_REG_TRANS_FINISH_INT_EN, + priv->base + LEDC_INT_CTRL_REG); + + return 0; + +err_disable_bus_clk: + clk_disable_unprepare(priv->bus_clk); +err_assert_reset: + reset_control_assert(priv->reset); + + return ret; +} + +static int sun50i_a100_ledc_suspend(struct device *dev) +{ + struct sun50i_a100_ledc *priv = dev_get_drvdata(dev); + + /* Wait for all transfers to complete. */ + for (;;) { + unsigned long flags; + bool xfer_active; + + spin_lock_irqsave(&priv->lock, flags); + xfer_active = priv->xfer_active; + spin_unlock_irqrestore(&priv->lock, flags); + if (!xfer_active) + break; + + msleep(1); + } + + clk_disable_unprepare(priv->mod_clk); + clk_disable_unprepare(priv->bus_clk); + reset_control_assert(priv->reset); + + return 0; +} + +static void sun50i_a100_ledc_dma_cleanup(void *data) +{ + struct sun50i_a100_ledc *priv = data; + + dma_release_channel(priv->dma_chan); +} + +static int sun50i_a100_ledc_probe(struct platform_device *pdev) +{ + const struct device_node *np = pdev->dev.of_node; + struct dma_slave_config dma_cfg = {}; + struct led_init_data init_data = {}; + struct sun50i_a100_ledc_led *led; + struct device *dev = &pdev->dev; + struct sun50i_a100_ledc *priv; + struct device_node *child; + struct resource *mem; + u32 max_addr = 0; + u32 num_leds = 0; + int irq, ret; + + /* + * The maximum LED address must be known in sun50i_a100_ledc_resume() before + * class device registration, so parse and validate the subnodes up front. + */ + for_each_available_child_of_node(np, child) { + u32 addr, color; + + ret = of_property_read_u32(child, "reg", &addr); + if (ret || addr >= LEDC_MAX_LEDS) { + of_node_put(child); + return dev_err_probe(dev, -EINVAL, "'reg' must be between 0 and %d\n", + LEDC_MAX_LEDS - 1); + } + + ret = of_property_read_u32(child, "color", &color); + if (ret || color != LED_COLOR_ID_RGB) { + of_node_put(child); + return dev_err_probe(dev, -EINVAL, "'color' must be LED_COLOR_ID_RGB\n"); + } + + max_addr = max(max_addr, addr); + num_leds++; + } + + if (!num_leds) + return -ENODEV; + + priv = devm_kzalloc(dev, struct_size(priv, leds, num_leds), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->dev = dev; + priv->max_addr = max_addr; + priv->num_leds = num_leds; + spin_lock_init(&priv->lock); + dev_set_drvdata(dev, priv); + + ret = sun50i_a100_ledc_parse_format(np, priv); + if (ret) + return ret; + + ret = sun50i_a100_ledc_parse_timing(np, priv); + if (ret) + return ret; + + priv->base = devm_platform_get_and_ioremap_resource(pdev, 0, &mem); + if (IS_ERR(priv->base)) + return PTR_ERR(priv->base); + + priv->bus_clk = devm_clk_get(dev, "bus"); + if (IS_ERR(priv->bus_clk)) + return PTR_ERR(priv->bus_clk); + + priv->mod_clk = devm_clk_get(dev, "mod"); + if (IS_ERR(priv->mod_clk)) + return PTR_ERR(priv->mod_clk); + + priv->reset = devm_reset_control_get_exclusive(dev, NULL); + if (IS_ERR(priv->reset)) + return PTR_ERR(priv->reset); + + priv->dma_chan = dma_request_chan(dev, "tx"); + if (IS_ERR(priv->dma_chan)) { + if (PTR_ERR(priv->dma_chan) != -ENODEV) + return PTR_ERR(priv->dma_chan); + + priv->dma_chan = NULL; + + priv->buffer = devm_kzalloc(dev, LEDS_TO_BYTES(LEDC_MAX_LEDS), GFP_KERNEL); + if (!priv->buffer) + return -ENOMEM; + } else { + ret = devm_add_action_or_reset(dev, sun50i_a100_ledc_dma_cleanup, priv); + if (ret) + return ret; + + dma_cfg.dst_addr = mem->start + LEDC_DATA_REG; + dma_cfg.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + dma_cfg.dst_maxburst = LEDC_FIFO_DEPTH / 2; + + ret = dmaengine_slave_config(priv->dma_chan, &dma_cfg); + if (ret) + return ret; + + priv->buffer = dmam_alloc_attrs(dmaengine_get_dma_device(priv->dma_chan), + LEDS_TO_BYTES(LEDC_MAX_LEDS), &priv->dma_handle, + GFP_KERNEL, DMA_ATTR_WRITE_COMBINE); + if (!priv->buffer) + return -ENOMEM; + } + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + ret = devm_request_irq(dev, irq, sun50i_a100_ledc_irq, 0, dev_name(dev), priv); + if (ret) + return ret; + + ret = sun50i_a100_ledc_resume(dev); + if (ret) + return ret; + + led = priv->leds; + for_each_available_child_of_node(np, child) { + struct led_classdev *cdev; + + /* The node was already validated above. */ + of_property_read_u32(child, "reg", &led->addr); + + led->subled_info[0].color_index = LED_COLOR_ID_RED; + led->subled_info[0].channel = 0; + led->subled_info[1].color_index = LED_COLOR_ID_GREEN; + led->subled_info[1].channel = 1; + led->subled_info[2].color_index = LED_COLOR_ID_BLUE; + led->subled_info[2].channel = 2; + + led->mc_cdev.num_colors = ARRAY_SIZE(led->subled_info); + led->mc_cdev.subled_info = led->subled_info; + + cdev = &led->mc_cdev.led_cdev; + cdev->max_brightness = U8_MAX; + cdev->brightness_set = sun50i_a100_ledc_brightness_set; + + init_data.fwnode = of_fwnode_handle(child); + + ret = led_classdev_multicolor_register_ext(dev, &led->mc_cdev, &init_data); + if (ret) { + dev_err_probe(dev, ret, "Failed to register multicolor LED %u", led->addr); + goto err_put_child; + } + + led++; + } + + dev_info(dev, "Registered %u LEDs\n", num_leds); + + return 0; + +err_put_child: + of_node_put(child); + while (led-- > priv->leds) + led_classdev_multicolor_unregister(&led->mc_cdev); + sun50i_a100_ledc_suspend(&pdev->dev); + + return ret; +} + +static void sun50i_a100_ledc_remove(struct platform_device *pdev) +{ + struct sun50i_a100_ledc *priv = platform_get_drvdata(pdev); + + for (u32 i = 0; i < priv->num_leds; i++) + led_classdev_multicolor_unregister(&priv->leds[i].mc_cdev); + sun50i_a100_ledc_suspend(&pdev->dev); +} + +static const struct of_device_id sun50i_a100_ledc_of_match[] = { + { .compatible = "allwinner,sun50i-a100-ledc" }, + {} +}; +MODULE_DEVICE_TABLE(of, sun50i_a100_ledc_of_match); + +static DEFINE_SIMPLE_DEV_PM_OPS(sun50i_a100_ledc_pm, + sun50i_a100_ledc_suspend, + sun50i_a100_ledc_resume); + +static struct platform_driver sun50i_a100_ledc_driver = { + .probe = sun50i_a100_ledc_probe, + .remove_new = sun50i_a100_ledc_remove, + .shutdown = sun50i_a100_ledc_remove, + .driver = { + .name = "sun50i-a100-ledc", + .of_match_table = sun50i_a100_ledc_of_match, + .pm = pm_ptr(&sun50i_a100_ledc_pm), + }, +}; +module_platform_driver(sun50i_a100_ledc_driver); + +MODULE_AUTHOR("Samuel Holland "); +MODULE_DESCRIPTION("Allwinner A100 LED controller driver"); +MODULE_LICENSE("GPL"); -- cgit v1.2.3 From 75469bb0537ad2ab0fc1fb6e534a79cfc03f3b3f Mon Sep 17 00:00:00 2001 From: Dang Huynh Date: Fri, 3 Nov 2023 18:42:03 +0700 Subject: leds: aw2013: Select missing dependency REGMAP_I2C The AW2013 driver uses devm_regmap_init_i2c, so REGMAP_I2C needs to be selected. Otherwise build process may fail with: ld: drivers/leds/leds-aw2013.o: in function `aw2013_probe': leds-aw2013.c:345: undefined reference to `__devm_regmap_init_i2c' Signed-off-by: Dang Huynh Acked-by: Nikita Travkin Fixes: 59ea3c9faf32 ("leds: add aw2013 driver") Link: https://lore.kernel.org/r/20231103114203.1108922-1-danct12@riseup.net Signed-off-by: Lee Jones --- drivers/leds/Kconfig | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers/leds') diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig index 6496ef525579..e7697a888159 100644 --- a/drivers/leds/Kconfig +++ b/drivers/leds/Kconfig @@ -110,6 +110,7 @@ config LEDS_AW200XX config LEDS_AW2013 tristate "LED support for Awinic AW2013" depends on LEDS_CLASS && I2C && OF + select REGMAP_I2C help This option enables support for the AW2013 3-channel LED driver. -- cgit v1.2.3 From 9bbd6b7209cf1e26390975b7617a29901c8990a1 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Fri, 3 Nov 2023 21:53:07 +0200 Subject: leds: trigger: gpio: Replace custom code for gpiod_get_optional() gpiod_get_optional() and currently used fwnode_gpiod_get_index() are both wrappers against the same engine internally. Since we have a pointer to struct device there is no reason to use fwnode type of GPIO call. So, replace the current fwnode call by respective gpiod ones. Signed-off-by: Andy Shevchenko Reviewed-by: Linus Walleij Link: https://lore.kernel.org/r/20231103195310.948327-1-andriy.shevchenko@linux.intel.com Signed-off-by: Lee Jones --- drivers/leds/trigger/ledtrig-gpio.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) (limited to 'drivers/leds') diff --git a/drivers/leds/trigger/ledtrig-gpio.c b/drivers/leds/trigger/ledtrig-gpio.c index 9b7fe5dd5208..d91ae7fde3cf 100644 --- a/drivers/leds/trigger/ledtrig-gpio.c +++ b/drivers/leds/trigger/ledtrig-gpio.c @@ -89,10 +89,7 @@ static int gpio_trig_activate(struct led_classdev *led) * The generic property "trigger-sources" is followed, * and we hope that this is a GPIO. */ - gpio_data->gpiod = fwnode_gpiod_get_index(dev->fwnode, - "trigger-sources", - 0, GPIOD_IN, - "led-trigger"); + gpio_data->gpiod = gpiod_get_optional(dev, "trigger-sources", GPIOD_IN); if (IS_ERR(gpio_data->gpiod)) { ret = PTR_ERR(gpio_data->gpiod); kfree(gpio_data); @@ -104,6 +101,8 @@ static int gpio_trig_activate(struct led_classdev *led) return -EINVAL; } + gpiod_set_consumer_name(gpio_data->gpiod, "led-trigger"); + gpio_data->led = led; led_set_trigger_data(led, gpio_data); -- cgit v1.2.3 From 7d6766f5377686b871ab52c564327718ece233ad Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Fri, 3 Nov 2023 21:53:08 +0200 Subject: leds: trigger: gpio: Convert to use kstrtox() sscanf() is a heavy one and moreover requires additional boundary checks. Convert driver to use kstrtou8() in gpio_trig_inverted_store(). Signed-off-by: Andy Shevchenko Reviewed-by: Linus Walleij Link: https://lore.kernel.org/r/20231103195310.948327-2-andriy.shevchenko@linux.intel.com Signed-off-by: Lee Jones --- drivers/leds/trigger/ledtrig-gpio.c | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) (limited to 'drivers/leds') diff --git a/drivers/leds/trigger/ledtrig-gpio.c b/drivers/leds/trigger/ledtrig-gpio.c index d91ae7fde3cf..8a30f9228186 100644 --- a/drivers/leds/trigger/ledtrig-gpio.c +++ b/drivers/leds/trigger/ledtrig-gpio.c @@ -53,14 +53,12 @@ static ssize_t gpio_trig_brightness_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t n) { struct gpio_trig_data *gpio_data = led_trigger_get_drvdata(dev); - unsigned desired_brightness; + u8 desired_brightness; int ret; - ret = sscanf(buf, "%u", &desired_brightness); - if (ret < 1 || desired_brightness > 255) { - dev_err(dev, "invalid value\n"); - return -EINVAL; - } + ret = kstrtou8(buf, 10, &desired_brightness); + if (ret) + return ret; gpio_data->desired_brightness = desired_brightness; -- cgit v1.2.3 From 7b9c5500f42e92992fe7b012974e0dddb673b1f5 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Fri, 3 Nov 2023 21:53:09 +0200 Subject: leds: trigger: gpio: Use sysfs_emit() to instead of s*printf() Follow the advice of the Documentation/filesystems/sysfs.rst and show() should only use sysfs_emit() or sysfs_emit_at() when formatting the value to be returned to user space. Signed-off-by: Andy Shevchenko Reviewed-by: Linus Walleij Link: https://lore.kernel.org/r/20231103195310.948327-3-andriy.shevchenko@linux.intel.com Signed-off-by: Lee Jones --- drivers/leds/trigger/ledtrig-gpio.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/leds') diff --git a/drivers/leds/trigger/ledtrig-gpio.c b/drivers/leds/trigger/ledtrig-gpio.c index 8a30f9228186..8824be19881f 100644 --- a/drivers/leds/trigger/ledtrig-gpio.c +++ b/drivers/leds/trigger/ledtrig-gpio.c @@ -46,7 +46,7 @@ static ssize_t gpio_trig_brightness_show(struct device *dev, { struct gpio_trig_data *gpio_data = led_trigger_get_drvdata(dev); - return sprintf(buf, "%u\n", gpio_data->desired_brightness); + return sysfs_emit(buf, "%u\n", gpio_data->desired_brightness); } static ssize_t gpio_trig_brightness_store(struct device *dev, -- cgit v1.2.3 From 804073f542077a8de17494fb3577a49513788a71 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Fri, 3 Nov 2023 21:53:10 +0200 Subject: leds: trigger: gpio: Convert to DEVICE_ATTR_RW() Instead of custom wrapper, use DEVICE_ATTR_RW() directly. Signed-off-by: Andy Shevchenko Reviewed-by: Linus Walleij Link: https://lore.kernel.org/r/20231103195310.948327-4-andriy.shevchenko@linux.intel.com Signed-off-by: Lee Jones --- drivers/leds/trigger/ledtrig-gpio.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) (limited to 'drivers/leds') diff --git a/drivers/leds/trigger/ledtrig-gpio.c b/drivers/leds/trigger/ledtrig-gpio.c index 8824be19881f..7f6a2352b0ac 100644 --- a/drivers/leds/trigger/ledtrig-gpio.c +++ b/drivers/leds/trigger/ledtrig-gpio.c @@ -41,7 +41,7 @@ static irqreturn_t gpio_trig_irq(int irq, void *_led) return IRQ_HANDLED; } -static ssize_t gpio_trig_brightness_show(struct device *dev, +static ssize_t desired_brightness_show(struct device *dev, struct device_attribute *attr, char *buf) { struct gpio_trig_data *gpio_data = led_trigger_get_drvdata(dev); @@ -49,7 +49,7 @@ static ssize_t gpio_trig_brightness_show(struct device *dev, return sysfs_emit(buf, "%u\n", gpio_data->desired_brightness); } -static ssize_t gpio_trig_brightness_store(struct device *dev, +static ssize_t desired_brightness_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t n) { struct gpio_trig_data *gpio_data = led_trigger_get_drvdata(dev); @@ -64,8 +64,7 @@ static ssize_t gpio_trig_brightness_store(struct device *dev, return n; } -static DEVICE_ATTR(desired_brightness, 0644, gpio_trig_brightness_show, - gpio_trig_brightness_store); +static DEVICE_ATTR_RW(desired_brightness); static struct attribute *gpio_trig_attrs[] = { &dev_attr_desired_brightness.attr, -- cgit v1.2.3 From 130199ec02b2cf16aed5ab0d2553da60df2c794a Mon Sep 17 00:00:00 2001 From: Andrew Davis Date: Thu, 16 Nov 2023 16:41:20 -0600 Subject: leds: tca6507: Use devm_gpiochip_add_data() to simplify remove path Use devm version of gpiochip add function to handle removal for us. Signed-off-by: Andrew Davis Link: https://lore.kernel.org/r/20231116224121.302150-1-afd@ti.com Signed-off-by: Lee Jones --- drivers/leds/leds-tca6507.c | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) (limited to 'drivers/leds') diff --git a/drivers/leds/leds-tca6507.c b/drivers/leds/leds-tca6507.c index e19074614095..9a6af6d8b5b5 100644 --- a/drivers/leds/leds-tca6507.c +++ b/drivers/leds/leds-tca6507.c @@ -638,19 +638,13 @@ static int tca6507_probe_gpios(struct device *dev, tca->gpio.direction_output = tca6507_gpio_direction_output; tca->gpio.set = tca6507_gpio_set_value; tca->gpio.parent = dev; - err = gpiochip_add_data(&tca->gpio, tca); + err = devm_gpiochip_add_data(dev, &tca->gpio, tca); if (err) { tca->gpio.ngpio = 0; return err; } return 0; } - -static void tca6507_remove_gpio(struct tca6507_chip *tca) -{ - if (tca->gpio.ngpio) - gpiochip_remove(&tca->gpio); -} #else /* CONFIG_GPIOLIB */ static int tca6507_probe_gpios(struct device *dev, struct tca6507_chip *tca, @@ -658,9 +652,6 @@ static int tca6507_probe_gpios(struct device *dev, { return 0; } -static void tca6507_remove_gpio(struct tca6507_chip *tca) -{ -} #endif /* CONFIG_GPIOLIB */ static struct tca6507_platform_data * @@ -793,7 +784,6 @@ static void tca6507_remove(struct i2c_client *client) if (tca_leds[i].led_cdev.name) led_classdev_unregister(&tca_leds[i].led_cdev); } - tca6507_remove_gpio(tca); cancel_work_sync(&tca->work); } -- cgit v1.2.3 From 1b5c2fa7081cca586237b97379f478460ec8d702 Mon Sep 17 00:00:00 2001 From: Andrew Davis Date: Thu, 16 Nov 2023 16:41:21 -0600 Subject: leds: tca6507: Use devm_led_classdev_register() to simplify remove path Use devm version of LED classdev register add function to handle removal. Signed-off-by: Andrew Davis Link: https://lore.kernel.org/r/20231116224121.302150-2-afd@ti.com Signed-off-by: Lee Jones --- drivers/leds/leds-tca6507.c | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) (limited to 'drivers/leds') diff --git a/drivers/leds/leds-tca6507.c b/drivers/leds/leds-tca6507.c index 9a6af6d8b5b5..4f22f4224946 100644 --- a/drivers/leds/leds-tca6507.c +++ b/drivers/leds/leds-tca6507.c @@ -753,37 +753,25 @@ static int tca6507_probe(struct i2c_client *client) l->led_cdev.brightness_set = tca6507_brightness_set; l->led_cdev.blink_set = tca6507_blink_set; l->bank = -1; - err = led_classdev_register(dev, &l->led_cdev); + err = devm_led_classdev_register(dev, &l->led_cdev); if (err < 0) - goto exit; + return err; } } err = tca6507_probe_gpios(dev, tca, pdata); if (err) - goto exit; + return err; /* set all registers to known state - zero */ tca->reg_set = 0x7f; schedule_work(&tca->work); return 0; -exit: - while (i--) { - if (tca->leds[i].led_cdev.name) - led_classdev_unregister(&tca->leds[i].led_cdev); - } - return err; } static void tca6507_remove(struct i2c_client *client) { - int i; struct tca6507_chip *tca = i2c_get_clientdata(client); - struct tca6507_led *tca_leds = tca->leds; - for (i = 0; i < NUM_LEDS; i++) { - if (tca_leds[i].led_cdev.name) - led_classdev_unregister(&tca_leds[i].led_cdev); - } cancel_work_sync(&tca->work); } -- cgit v1.2.3 From 736214b4b02adf8734206599e36e2081d47554a2 Mon Sep 17 00:00:00 2001 From: Patrick Rudolph Date: Thu, 23 Nov 2023 13:28:02 +0000 Subject: leds: max5970: Add support for max5970 The MAX5970 is hot swap controller and has 4 indication LED. Signed-off-by: Patrick Rudolph Signed-off-by: Naresh Solanki Link: https://lore.kernel.org/r/20231123132803.1107174-1-naresh.solanki@9elements.com Signed-off-by: Lee Jones --- drivers/leds/Kconfig | 11 +++++ drivers/leds/Makefile | 1 + drivers/leds/leds-max5970.c | 109 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 121 insertions(+) create mode 100644 drivers/leds/leds-max5970.c (limited to 'drivers/leds') diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig index e7697a888159..bfa11e7b157f 100644 --- a/drivers/leds/Kconfig +++ b/drivers/leds/Kconfig @@ -648,6 +648,17 @@ config LEDS_ADP5520 To compile this driver as a module, choose M here: the module will be called leds-adp5520. +config LEDS_MAX5970 + tristate "LED Support for Maxim 5970" + depends on LEDS_CLASS + depends on MFD_MAX5970 + help + This option enables support for the Maxim MAX5970 & MAX5978 smart + switch indication LEDs via the I2C bus. + + To compile this driver as a module, choose M here: the module will + be called leds-max5970. + config LEDS_MC13783 tristate "LED Support for MC13XXX PMIC" depends on LEDS_CLASS diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile index b82a538e3f8c..ce07dc295ff0 100644 --- a/drivers/leds/Makefile +++ b/drivers/leds/Makefile @@ -56,6 +56,7 @@ obj-$(CONFIG_LEDS_LP8501) += leds-lp8501.o obj-$(CONFIG_LEDS_LP8788) += leds-lp8788.o obj-$(CONFIG_LEDS_LP8860) += leds-lp8860.o obj-$(CONFIG_LEDS_LT3593) += leds-lt3593.o +obj-$(CONFIG_LEDS_MAX5970) += leds-max5970.o obj-$(CONFIG_LEDS_MAX77650) += leds-max77650.o obj-$(CONFIG_LEDS_MAX8997) += leds-max8997.o obj-$(CONFIG_LEDS_MC13783) += leds-mc13783.o diff --git a/drivers/leds/leds-max5970.c b/drivers/leds/leds-max5970.c new file mode 100644 index 000000000000..456a16a47450 --- /dev/null +++ b/drivers/leds/leds-max5970.c @@ -0,0 +1,109 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Device driver for leds in MAX5970 and MAX5978 IC + * + * Copyright (c) 2022 9elements GmbH + * + * Author: Patrick Rudolph + */ + +#include +#include +#include +#include +#include + +#define ldev_to_maxled(c) container_of(c, struct max5970_led, cdev) + +struct max5970_led { + struct device *dev; + struct regmap *regmap; + struct led_classdev cdev; + unsigned int index; +}; + +static int max5970_led_set_brightness(struct led_classdev *cdev, + enum led_brightness brightness) +{ + struct max5970_led *ddata = ldev_to_maxled(cdev); + int ret, val; + + /* Set/clear corresponding bit for given led index */ + val = !brightness ? BIT(ddata->index) : 0; + + ret = regmap_update_bits(ddata->regmap, MAX5970_REG_LED_FLASH, BIT(ddata->index), val); + if (ret < 0) + dev_err(cdev->dev, "failed to set brightness %d", ret); + + return ret; +} + +static int max5970_led_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *np = dev_of_node(dev->parent); + struct regmap *regmap; + struct device_node *led_node, *child; + struct max5970_led *ddata; + int ret = -ENODEV, num_leds = 0; + + regmap = dev_get_regmap(pdev->dev.parent, NULL); + if (!regmap) + return -ENODEV; + + led_node = of_get_child_by_name(np, "leds"); + if (!led_node) + return -ENODEV; + + for_each_available_child_of_node(led_node, child) { + u32 reg; + + if (of_property_read_u32(child, "reg", ®)) + continue; + + if (reg >= MAX5970_NUM_LEDS) { + dev_err(dev, "invalid LED (%u >= %d)\n", reg, MAX5970_NUM_LEDS); + continue; + } + + ddata = devm_kzalloc(dev, sizeof(*ddata), GFP_KERNEL); + if (!ddata) { + of_node_put(child); + return -ENOMEM; + } + + ddata->index = reg; + ddata->regmap = regmap; + ddata->dev = dev; + + if (of_property_read_string(child, "label", &ddata->cdev.name)) + ddata->cdev.name = child->name; + + ddata->cdev.max_brightness = 1; + ddata->cdev.brightness_set_blocking = max5970_led_set_brightness; + ddata->cdev.default_trigger = "none"; + + ret = devm_led_classdev_register(dev, &ddata->cdev); + if (ret < 0) { + of_node_put(child); + dev_err(dev, "Failed to initialize LED %u\n", reg); + return ret; + } + num_leds++; + } + + return ret; +} + +static struct platform_driver max5970_led_driver = { + .driver = { + .name = "max5970-led", + }, + .probe = max5970_led_probe, +}; + +module_platform_driver(max5970_led_driver); +MODULE_AUTHOR("Patrick Rudolph "); +MODULE_AUTHOR("Naresh Solanki "); +MODULE_DESCRIPTION("MAX5970_hot-swap controller LED driver"); +MODULE_LICENSE("GPL"); -- cgit v1.2.3 From 25054b232681c286fca9c678854f56494d1352cc Mon Sep 17 00:00:00 2001 From: Florian Eckert Date: Mon, 27 Nov 2023 09:16:21 +0100 Subject: leds: ledtrig-tty: Free allocated ttyname buffer on deactivate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The ttyname buffer for the ledtrig_tty_data struct is allocated in the sysfs ttyname_store() function. This buffer must be released on trigger deactivation. This was missing and is thus a memory leak. While we are at it, the TTY handler in the ledtrig_tty_data struct should also be returned in case of the trigger deactivation call. Cc: stable@vger.kernel.org Fixes: fd4a641ac88f ("leds: trigger: implement a tty trigger") Signed-off-by: Florian Eckert Reviewed-by: Uwe Kleine-König Reviewed-by: Greg Kroah-Hartman Link: https://lore.kernel.org/r/20231127081621.774866-1-fe@dev.tdt.de Signed-off-by: Lee Jones --- drivers/leds/trigger/ledtrig-tty.c | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'drivers/leds') diff --git a/drivers/leds/trigger/ledtrig-tty.c b/drivers/leds/trigger/ledtrig-tty.c index 8ae0d2d284af..3e69a7bde928 100644 --- a/drivers/leds/trigger/ledtrig-tty.c +++ b/drivers/leds/trigger/ledtrig-tty.c @@ -168,6 +168,10 @@ static void ledtrig_tty_deactivate(struct led_classdev *led_cdev) cancel_delayed_work_sync(&trigger_data->dwork); + kfree(trigger_data->ttyname); + tty_kref_put(trigger_data->tty); + trigger_data->tty = NULL; + kfree(trigger_data); } -- cgit v1.2.3 From 9e1815f8c77155aa0818d65b1903a5a39af0ab75 Mon Sep 17 00:00:00 2001 From: Uwe Kleine-König Date: Sun, 26 Nov 2023 10:52:32 +0100 Subject: leds: qcom-lpg: Use devm_pwmchip_add() simplifying driver removal MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit With pwmchip_remove() being automatically called after switching to devm_pwmchip_add() the remove function can be dropped completely. Yay! With lpg_remove() gone there is no user of the platform device's drvdata left, so platform_set_drvdata() can be dropped from .probe(), too. Signed-off-by: Uwe Kleine-König Link: https://lore.kernel.org/r/20231126095230.683204-2-u.kleine-koenig@pengutronix.de Signed-off-by: Lee Jones --- drivers/leds/rgb/leds-qcom-lpg.c | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) (limited to 'drivers/leds') diff --git a/drivers/leds/rgb/leds-qcom-lpg.c b/drivers/leds/rgb/leds-qcom-lpg.c index 68d82a682bf6..f5805fd0eb21 100644 --- a/drivers/leds/rgb/leds-qcom-lpg.c +++ b/drivers/leds/rgb/leds-qcom-lpg.c @@ -1095,7 +1095,7 @@ static int lpg_add_pwm(struct lpg *lpg) lpg->pwm.npwm = lpg->num_channels; lpg->pwm.ops = &lpg_pwm_ops; - ret = pwmchip_add(&lpg->pwm); + ret = devm_pwmchip_add(lpg->dev, &lpg->pwm); if (ret) dev_err(lpg->dev, "failed to add PWM chip: ret %d\n", ret); @@ -1324,8 +1324,6 @@ static int lpg_probe(struct platform_device *pdev) if (!lpg->data) return -EINVAL; - platform_set_drvdata(pdev, lpg); - lpg->dev = &pdev->dev; mutex_init(&lpg->lock); @@ -1363,13 +1361,6 @@ static int lpg_probe(struct platform_device *pdev) return lpg_add_pwm(lpg); } -static void lpg_remove(struct platform_device *pdev) -{ - struct lpg *lpg = platform_get_drvdata(pdev); - - pwmchip_remove(&lpg->pwm); -} - static const struct lpg_data pm8916_pwm_data = { .num_channels = 1, .channels = (const struct lpg_channel_data[]) { @@ -1529,7 +1520,6 @@ MODULE_DEVICE_TABLE(of, lpg_of_table); static struct platform_driver lpg_driver = { .probe = lpg_probe, - .remove_new = lpg_remove, .driver = { .name = "qcom-spmi-lpg", .of_match_table = lpg_of_table, -- cgit v1.2.3 From 793bf5510d5e30dff2ce1d6e446b385cd494d5af Mon Sep 17 00:00:00 2001 From: Uwe Kleine-König Date: Sun, 26 Nov 2023 10:52:33 +0100 Subject: leds: qcom-lpg: Consistenly use dev_err_probe() in .probe()'s error path MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit One error path already used dev_err_probe(). Adapt the other error paths that emit an error message to also use this function for consistency and slightly simplified code. Signed-off-by: Uwe Kleine-König Link: https://lore.kernel.org/r/20231126095230.683204-3-u.kleine-koenig@pengutronix.de Signed-off-by: Lee Jones --- drivers/leds/rgb/leds-qcom-lpg.c | 40 +++++++++++++++++----------------------- 1 file changed, 17 insertions(+), 23 deletions(-) (limited to 'drivers/leds') diff --git a/drivers/leds/rgb/leds-qcom-lpg.c b/drivers/leds/rgb/leds-qcom-lpg.c index f5805fd0eb21..54c90ee43ef8 100644 --- a/drivers/leds/rgb/leds-qcom-lpg.c +++ b/drivers/leds/rgb/leds-qcom-lpg.c @@ -552,9 +552,9 @@ static int lpg_parse_dtest(struct lpg *lpg) ret = count; goto err_malformed; } else if (count != lpg->data->num_channels * 2) { - dev_err(lpg->dev, "qcom,dtest needs to be %d items\n", - lpg->data->num_channels * 2); - return -EINVAL; + return dev_err_probe(lpg->dev, -EINVAL, + "qcom,dtest needs to be %d items\n", + lpg->data->num_channels * 2); } for (i = 0; i < lpg->data->num_channels; i++) { @@ -574,8 +574,7 @@ static int lpg_parse_dtest(struct lpg *lpg) return 0; err_malformed: - dev_err(lpg->dev, "malformed qcom,dtest\n"); - return ret; + return dev_err_probe(lpg->dev, ret, "malformed qcom,dtest\n"); } static void lpg_apply_dtest(struct lpg_channel *chan) @@ -1097,7 +1096,7 @@ static int lpg_add_pwm(struct lpg *lpg) ret = devm_pwmchip_add(lpg->dev, &lpg->pwm); if (ret) - dev_err(lpg->dev, "failed to add PWM chip: ret %d\n", ret); + dev_err_probe(lpg->dev, ret, "failed to add PWM chip\n"); return ret; } @@ -1111,19 +1110,16 @@ static int lpg_parse_channel(struct lpg *lpg, struct device_node *np, int ret; ret = of_property_read_u32(np, "reg", ®); - if (ret || !reg || reg > lpg->num_channels) { - dev_err(lpg->dev, "invalid \"reg\" of %pOFn\n", np); - return -EINVAL; - } + if (ret || !reg || reg > lpg->num_channels) + return dev_err_probe(lpg->dev, -EINVAL, "invalid \"reg\" of %pOFn\n", np); chan = &lpg->channels[reg - 1]; chan->in_use = true; ret = of_property_read_u32(np, "color", &color); - if (ret < 0 && ret != -EINVAL) { - dev_err(lpg->dev, "failed to parse \"color\" of %pOF\n", np); - return ret; - } + if (ret < 0 && ret != -EINVAL) + return dev_err_probe(lpg->dev, ret, + "failed to parse \"color\" of %pOF\n", np); chan->color = color; @@ -1146,10 +1142,9 @@ static int lpg_add_led(struct lpg *lpg, struct device_node *np) int i; ret = of_property_read_u32(np, "color", &color); - if (ret < 0 && ret != -EINVAL) { - dev_err(lpg->dev, "failed to parse \"color\" of %pOF\n", np); - return ret; - } + if (ret < 0 && ret != -EINVAL) + return dev_err_probe(lpg->dev, ret, + "failed to parse \"color\" of %pOF\n", np); if (color == LED_COLOR_ID_RGB) num_channels = of_get_available_child_count(np); @@ -1226,7 +1221,7 @@ static int lpg_add_led(struct lpg *lpg, struct device_node *np) else ret = devm_led_classdev_register_ext(lpg->dev, &led->cdev, &init_data); if (ret) - dev_err(lpg->dev, "unable to register %s\n", cdev->name); + dev_err_probe(lpg->dev, ret, "unable to register %s\n", cdev->name); return ret; } @@ -1272,10 +1267,9 @@ static int lpg_init_triled(struct lpg *lpg) if (lpg->triled_has_src_sel) { ret = of_property_read_u32(np, "qcom,power-source", &lpg->triled_src); - if (ret || lpg->triled_src == 2 || lpg->triled_src > 3) { - dev_err(lpg->dev, "invalid power source\n"); - return -EINVAL; - } + if (ret || lpg->triled_src == 2 || lpg->triled_src > 3) + return dev_err_probe(lpg->dev, -EINVAL, + "invalid power source\n"); } /* Disable automatic trickle charge LED */ -- cgit v1.2.3 From 76675f69bed5f1e8ae44ba72904ff7f97aa95c0a Mon Sep 17 00:00:00 2001 From: Florian Eckert Date: Mon, 27 Nov 2023 12:03:09 +0100 Subject: leds: ledtrig-tty: Replace mutex with completion With this commit, the mutex handling is replaced by the completion handling. When handling mutex, it must always be ensured that the held mutex is also released again. This is more error-prone should the number of code paths increase. This is a preparatory commit to make the trigger more configurable via additional sysfs parameters. With this change, the worker always runs and is no longer stopped if no ttyname is set. Signed-off-by: Florian Eckert Reviewed-by: Greg Kroah-Hartman Link: https://lore.kernel.org/r/20231127110311.3583957-3-fe@dev.tdt.de Signed-off-by: Lee Jones --- drivers/leds/trigger/ledtrig-tty.c | 59 +++++++++++++++++++------------------- 1 file changed, 30 insertions(+), 29 deletions(-) (limited to 'drivers/leds') diff --git a/drivers/leds/trigger/ledtrig-tty.c b/drivers/leds/trigger/ledtrig-tty.c index 3e69a7bde928..b16bd9cb9713 100644 --- a/drivers/leds/trigger/ledtrig-tty.c +++ b/drivers/leds/trigger/ledtrig-tty.c @@ -1,5 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 +#include #include #include #include @@ -12,15 +13,23 @@ struct ledtrig_tty_data { struct led_classdev *led_cdev; struct delayed_work dwork; - struct mutex mutex; + struct completion sysfs; const char *ttyname; struct tty_struct *tty; int rx, tx; }; -static void ledtrig_tty_restart(struct ledtrig_tty_data *trigger_data) +static int ledtrig_tty_wait_for_completion(struct device *dev) { - schedule_delayed_work(&trigger_data->dwork, 0); + struct ledtrig_tty_data *trigger_data = led_trigger_get_drvdata(dev); + int ret; + + ret = wait_for_completion_timeout(&trigger_data->sysfs, + msecs_to_jiffies(LEDTRIG_TTY_INTERVAL * 20)); + if (ret == 0) + return -ETIMEDOUT; + + return ret; } static ssize_t ttyname_show(struct device *dev, @@ -28,14 +37,16 @@ static ssize_t ttyname_show(struct device *dev, { struct ledtrig_tty_data *trigger_data = led_trigger_get_drvdata(dev); ssize_t len = 0; + int completion; - mutex_lock(&trigger_data->mutex); + reinit_completion(&trigger_data->sysfs); + completion = ledtrig_tty_wait_for_completion(dev); + if (completion < 0) + return completion; if (trigger_data->ttyname) len = sprintf(buf, "%s\n", trigger_data->ttyname); - mutex_unlock(&trigger_data->mutex); - return len; } @@ -46,7 +57,7 @@ static ssize_t ttyname_store(struct device *dev, struct ledtrig_tty_data *trigger_data = led_trigger_get_drvdata(dev); char *ttyname; ssize_t ret = size; - bool running; + int completion; if (size > 0 && buf[size - 1] == '\n') size -= 1; @@ -59,9 +70,10 @@ static ssize_t ttyname_store(struct device *dev, ttyname = NULL; } - mutex_lock(&trigger_data->mutex); - - running = trigger_data->ttyname != NULL; + reinit_completion(&trigger_data->sysfs); + completion = ledtrig_tty_wait_for_completion(dev); + if (completion < 0) + return completion; kfree(trigger_data->ttyname); tty_kref_put(trigger_data->tty); @@ -69,11 +81,6 @@ static ssize_t ttyname_store(struct device *dev, trigger_data->ttyname = ttyname; - mutex_unlock(&trigger_data->mutex); - - if (ttyname && !running) - ledtrig_tty_restart(trigger_data); - return ret; } static DEVICE_ATTR_RW(ttyname); @@ -85,13 +92,8 @@ static void ledtrig_tty_work(struct work_struct *work) struct serial_icounter_struct icount; int ret; - mutex_lock(&trigger_data->mutex); - - if (!trigger_data->ttyname) { - /* exit without rescheduling */ - mutex_unlock(&trigger_data->mutex); - return; - } + if (!trigger_data->ttyname) + goto out; /* try to get the tty corresponding to $ttyname */ if (!trigger_data->tty) { @@ -116,11 +118,8 @@ static void ledtrig_tty_work(struct work_struct *work) } ret = tty_get_icount(trigger_data->tty, &icount); - if (ret) { - dev_info(trigger_data->tty->dev, "Failed to get icount, stopped polling\n"); - mutex_unlock(&trigger_data->mutex); - return; - } + if (ret) + goto out; if (icount.rx != trigger_data->rx || icount.tx != trigger_data->tx) { @@ -134,7 +133,7 @@ static void ledtrig_tty_work(struct work_struct *work) } out: - mutex_unlock(&trigger_data->mutex); + complete_all(&trigger_data->sysfs); schedule_delayed_work(&trigger_data->dwork, msecs_to_jiffies(LEDTRIG_TTY_INTERVAL * 2)); } @@ -157,7 +156,9 @@ static int ledtrig_tty_activate(struct led_classdev *led_cdev) INIT_DELAYED_WORK(&trigger_data->dwork, ledtrig_tty_work); trigger_data->led_cdev = led_cdev; - mutex_init(&trigger_data->mutex); + init_completion(&trigger_data->sysfs); + + schedule_delayed_work(&trigger_data->dwork, 0); return 0; } -- cgit v1.2.3 From 5b755ca677dba117063c6fd8d7ce21b67376deba Mon Sep 17 00:00:00 2001 From: Florian Eckert Date: Mon, 27 Nov 2023 12:03:10 +0100 Subject: leds: ledtrig-tty: Make rx tx activitate configurable Until now, the LED blinks when data is sent via the tty (rx/tx). This is not configurable. This change adds the possibility to make the indication for the direction of the transmitted data independently controllable via the new rx and tx sysfs entries. - rx: Signal reception (rx) of data on the named tty device. If set to 0, the LED will not blink on reception. If set to 1 (default), the LED will blink on reception. - tx: Signal transmission (tx) of data on the named tty device. If set to 0, the LED will not blink on transmission. If set to 1 (default), the LED will blink on transmission. This new sysfs entry are on by default. Thus the trigger behaves as before this change. Signed-off-by: Florian Eckert Reviewed-by: Greg Kroah-Hartman Link: https://lore.kernel.org/r/20231127110311.3583957-4-fe@dev.tdt.de Signed-off-by: Lee Jones --- .../ABI/testing/sysfs-class-led-trigger-tty | 16 +++ drivers/leds/trigger/ledtrig-tty.c | 114 +++++++++++++++++++-- 2 files changed, 119 insertions(+), 11 deletions(-) (limited to 'drivers/leds') diff --git a/Documentation/ABI/testing/sysfs-class-led-trigger-tty b/Documentation/ABI/testing/sysfs-class-led-trigger-tty index 2bf6b24e781b..504dece151b8 100644 --- a/Documentation/ABI/testing/sysfs-class-led-trigger-tty +++ b/Documentation/ABI/testing/sysfs-class-led-trigger-tty @@ -4,3 +4,19 @@ KernelVersion: 5.10 Contact: linux-leds@vger.kernel.org Description: Specifies the tty device name of the triggering tty + +What: /sys/class/leds//rx +Date: February 2024 +KernelVersion: 6.8 +Description: + Signal reception (rx) of data on the named tty device. + If set to 0, the LED will not blink on reception. + If set to 1 (default), the LED will blink on reception. + +What: /sys/class/leds//tx +Date: February 2024 +KernelVersion: 6.8 +Description: + Signal transmission (tx) of data on the named tty device. + If set to 0, the LED will not blink on transmission. + If set to 1 (default), the LED will blink on transmission. diff --git a/drivers/leds/trigger/ledtrig-tty.c b/drivers/leds/trigger/ledtrig-tty.c index b16bd9cb9713..4f1cdd0e4e89 100644 --- a/drivers/leds/trigger/ledtrig-tty.c +++ b/drivers/leds/trigger/ledtrig-tty.c @@ -17,6 +17,19 @@ struct ledtrig_tty_data { const char *ttyname; struct tty_struct *tty; int rx, tx; + bool mode_rx; + bool mode_tx; +}; + +/* Indicates which state the LED should now display */ +enum led_trigger_tty_state { + TTY_LED_BLINK, + TTY_LED_DISABLE, +}; + +enum led_trigger_tty_modes { + TRIGGER_TTY_RX = 0, + TRIGGER_TTY_TX, }; static int ledtrig_tty_wait_for_completion(struct device *dev) @@ -85,11 +98,69 @@ static ssize_t ttyname_store(struct device *dev, } static DEVICE_ATTR_RW(ttyname); +static ssize_t ledtrig_tty_attr_show(struct device *dev, char *buf, + enum led_trigger_tty_modes attr) +{ + struct ledtrig_tty_data *trigger_data = led_trigger_get_drvdata(dev); + bool state; + + switch (attr) { + case TRIGGER_TTY_RX: + state = trigger_data->mode_rx; + break; + case TRIGGER_TTY_TX: + state = trigger_data->mode_tx; + break; + } + + return sysfs_emit(buf, "%u\n", state); +} + +static ssize_t ledtrig_tty_attr_store(struct device *dev, const char *buf, + size_t size, enum led_trigger_tty_modes attr) +{ + struct ledtrig_tty_data *trigger_data = led_trigger_get_drvdata(dev); + bool state; + int ret; + + ret = kstrtobool(buf, &state); + if (ret) + return ret; + + switch (attr) { + case TRIGGER_TTY_RX: + trigger_data->mode_rx = state; + break; + case TRIGGER_TTY_TX: + trigger_data->mode_tx = state; + break; + } + + return size; +} + +#define DEFINE_TTY_TRIGGER(trigger_name, trigger) \ + static ssize_t trigger_name##_show(struct device *dev, \ + struct device_attribute *attr, char *buf) \ + { \ + return ledtrig_tty_attr_show(dev, buf, trigger); \ + } \ + static ssize_t trigger_name##_store(struct device *dev, \ + struct device_attribute *attr, const char *buf, size_t size) \ + { \ + return ledtrig_tty_attr_store(dev, buf, size, trigger); \ + } \ + static DEVICE_ATTR_RW(trigger_name) + +DEFINE_TTY_TRIGGER(rx, TRIGGER_TTY_RX); +DEFINE_TTY_TRIGGER(tx, TRIGGER_TTY_TX); + static void ledtrig_tty_work(struct work_struct *work) { struct ledtrig_tty_data *trigger_data = container_of(work, struct ledtrig_tty_data, dwork.work); - struct serial_icounter_struct icount; + enum led_trigger_tty_state state = TTY_LED_DISABLE; + unsigned long interval = LEDTRIG_TTY_INTERVAL; int ret; if (!trigger_data->ttyname) @@ -117,22 +188,37 @@ static void ledtrig_tty_work(struct work_struct *work) trigger_data->tty = tty; } - ret = tty_get_icount(trigger_data->tty, &icount); - if (ret) - goto out; + if (trigger_data->mode_rx || trigger_data->mode_tx) { + struct serial_icounter_struct icount; - if (icount.rx != trigger_data->rx || - icount.tx != trigger_data->tx) { - unsigned long interval = LEDTRIG_TTY_INTERVAL; + ret = tty_get_icount(trigger_data->tty, &icount); + if (ret) + goto out; - led_blink_set_oneshot(trigger_data->led_cdev, &interval, - &interval, 0); + if (trigger_data->mode_tx && (icount.tx != trigger_data->tx)) { + trigger_data->tx = icount.tx; + state = TTY_LED_BLINK; + } - trigger_data->rx = icount.rx; - trigger_data->tx = icount.tx; + if (trigger_data->mode_rx && (icount.rx != trigger_data->rx)) { + trigger_data->rx = icount.rx; + state = TTY_LED_BLINK; + } } out: + switch (state) { + case TTY_LED_BLINK: + led_blink_set_oneshot(trigger_data->led_cdev, &interval, + &interval, 0); + break; + case TTY_LED_DISABLE: + fallthrough; + default: + led_set_brightness(trigger_data->led_cdev, LED_OFF); + break; + } + complete_all(&trigger_data->sysfs); schedule_delayed_work(&trigger_data->dwork, msecs_to_jiffies(LEDTRIG_TTY_INTERVAL * 2)); @@ -140,6 +226,8 @@ out: static struct attribute *ledtrig_tty_attrs[] = { &dev_attr_ttyname.attr, + &dev_attr_rx.attr, + &dev_attr_tx.attr, NULL }; ATTRIBUTE_GROUPS(ledtrig_tty); @@ -152,6 +240,10 @@ static int ledtrig_tty_activate(struct led_classdev *led_cdev) if (!trigger_data) return -ENOMEM; + /* Enable default rx/tx mode */ + trigger_data->mode_rx = true; + trigger_data->mode_tx = true; + led_set_trigger_data(led_cdev, trigger_data); INIT_DELAYED_WORK(&trigger_data->dwork, ledtrig_tty_work); -- cgit v1.2.3 From 6dec659896b4e683beaea223d306e71e174e84cd Mon Sep 17 00:00:00 2001 From: Florian Eckert Date: Mon, 27 Nov 2023 12:03:11 +0100 Subject: leds: ledtrig-tty: Add additional line state evaluation The serial tty interface also supports additional input signals, that can also be evaluated within this trigger. This change is adding the following additional input sources, which could be controlled via the '/sys/class//' sysfs interface. Explanation: DCE = Data Communication Equipment (Modem) DTE = Data Terminal Equipment (Computer) - cts: DCE is ready to accept data from the DTE (CTS = Clear To Send). If the line state is detected, the LED is switched on. If set to 0 (default), the LED will not evaluate CTS. If set to 1, the LED will evaluate CTS. - dsr: DCE is ready to receive and send data (DSR = Data Set Ready). If the line state is detected, the LED is switched on. If set to 0 (default), the LED will not evaluate DSR. If set to 1, the LED will evaluate DSR. - dcd: DTE is receiving a carrier from the DCE (DCD = Data Carrier Detect). If the line state is detected, the LED is switched on. If set to 0 (default), the LED will not evaluate DCD. If set to 1, the LED will evaluate DCD. - rng: DCE has detected an incoming ring signal on the telephone line (RNG = Ring Indicator). If the line state is detected, the LED is switched on. If set to 0 (default), the LED will not evaluate RNG. If set to 1, the LED will evaluate RNG. Also add an invert flag on LED blink, so that the LED blinks in the correct order. * If one off the new enabled input signals are evaluatet as 'enabled', and data are transmitted, then the LED should first blink 'off' and then 'on' (invert). * If all the new enabled input signals are evaluatet as 'disabled', and data are transmitted, then the LED should first blink 'on' and then 'off'. Signed-off-by: Florian Eckert Reviewed-by: Maarten Brock Reviewed-by: Greg Kroah-Hartman Link: https://lore.kernel.org/r/20231127110311.3583957-5-fe@dev.tdt.de Signed-off-by: Lee Jones --- .../ABI/testing/sysfs-class-led-trigger-tty | 40 +++++++++++ drivers/leds/trigger/ledtrig-tty.c | 78 +++++++++++++++++++++- 2 files changed, 117 insertions(+), 1 deletion(-) (limited to 'drivers/leds') diff --git a/Documentation/ABI/testing/sysfs-class-led-trigger-tty b/Documentation/ABI/testing/sysfs-class-led-trigger-tty index 504dece151b8..30cef9ac0f49 100644 --- a/Documentation/ABI/testing/sysfs-class-led-trigger-tty +++ b/Documentation/ABI/testing/sysfs-class-led-trigger-tty @@ -20,3 +20,43 @@ Description: Signal transmission (tx) of data on the named tty device. If set to 0, the LED will not blink on transmission. If set to 1 (default), the LED will blink on transmission. + +What: /sys/class/leds//cts +Date: February 2024 +KernelVersion: 6.8 +Description: + CTS = Clear To Send + DCE is ready to accept data from the DTE. + If the line state is detected, the LED is switched on. + If set to 0 (default), the LED will not evaluate CTS. + If set to 1, the LED will evaluate CTS. + +What: /sys/class/leds//dsr +Date: February 2024 +KernelVersion: 6.8 +Description: + DSR = Data Set Ready + DCE is ready to receive and send data. + If the line state is detected, the LED is switched on. + If set to 0 (default), the LED will not evaluate DSR. + If set to 1, the LED will evaluate DSR. + +What: /sys/class/leds//dcd +Date: February 2024 +KernelVersion: 6.8 +Description: + DCD = Data Carrier Detect + DTE is receiving a carrier from the DCE. + If the line state is detected, the LED is switched on. + If set to 0 (default), the LED will not evaluate CAR (DCD). + If set to 1, the LED will evaluate CAR (DCD). + +What: /sys/class/leds//rng +Date: February 2024 +KernelVersion: 6.8 +Description: + RNG = Ring Indicator + DCE has detected an incoming ring signal on the telephone + line. If the line state is detected, the LED is switched on. + If set to 0 (default), the LED will not evaluate RNG. + If set to 1, the LED will evaluate RNG. diff --git a/drivers/leds/trigger/ledtrig-tty.c b/drivers/leds/trigger/ledtrig-tty.c index 4f1cdd0e4e89..8cf1485e8165 100644 --- a/drivers/leds/trigger/ledtrig-tty.c +++ b/drivers/leds/trigger/ledtrig-tty.c @@ -19,17 +19,26 @@ struct ledtrig_tty_data { int rx, tx; bool mode_rx; bool mode_tx; + bool mode_cts; + bool mode_dsr; + bool mode_dcd; + bool mode_rng; }; /* Indicates which state the LED should now display */ enum led_trigger_tty_state { TTY_LED_BLINK, + TTY_LED_ENABLE, TTY_LED_DISABLE, }; enum led_trigger_tty_modes { TRIGGER_TTY_RX = 0, TRIGGER_TTY_TX, + TRIGGER_TTY_CTS, + TRIGGER_TTY_DSR, + TRIGGER_TTY_DCD, + TRIGGER_TTY_RNG, }; static int ledtrig_tty_wait_for_completion(struct device *dev) @@ -111,6 +120,18 @@ static ssize_t ledtrig_tty_attr_show(struct device *dev, char *buf, case TRIGGER_TTY_TX: state = trigger_data->mode_tx; break; + case TRIGGER_TTY_CTS: + state = trigger_data->mode_cts; + break; + case TRIGGER_TTY_DSR: + state = trigger_data->mode_dsr; + break; + case TRIGGER_TTY_DCD: + state = trigger_data->mode_dcd; + break; + case TRIGGER_TTY_RNG: + state = trigger_data->mode_rng; + break; } return sysfs_emit(buf, "%u\n", state); @@ -134,6 +155,18 @@ static ssize_t ledtrig_tty_attr_store(struct device *dev, const char *buf, case TRIGGER_TTY_TX: trigger_data->mode_tx = state; break; + case TRIGGER_TTY_CTS: + trigger_data->mode_cts = state; + break; + case TRIGGER_TTY_DSR: + trigger_data->mode_dsr = state; + break; + case TRIGGER_TTY_DCD: + trigger_data->mode_dcd = state; + break; + case TRIGGER_TTY_RNG: + trigger_data->mode_rng = state; + break; } return size; @@ -154,6 +187,10 @@ static ssize_t ledtrig_tty_attr_store(struct device *dev, const char *buf, DEFINE_TTY_TRIGGER(rx, TRIGGER_TTY_RX); DEFINE_TTY_TRIGGER(tx, TRIGGER_TTY_TX); +DEFINE_TTY_TRIGGER(cts, TRIGGER_TTY_CTS); +DEFINE_TTY_TRIGGER(dsr, TRIGGER_TTY_DSR); +DEFINE_TTY_TRIGGER(dcd, TRIGGER_TTY_DCD); +DEFINE_TTY_TRIGGER(rng, TRIGGER_TTY_RNG); static void ledtrig_tty_work(struct work_struct *work) { @@ -161,6 +198,8 @@ static void ledtrig_tty_work(struct work_struct *work) container_of(work, struct ledtrig_tty_data, dwork.work); enum led_trigger_tty_state state = TTY_LED_DISABLE; unsigned long interval = LEDTRIG_TTY_INTERVAL; + bool invert = false; + int status; int ret; if (!trigger_data->ttyname) @@ -188,6 +227,33 @@ static void ledtrig_tty_work(struct work_struct *work) trigger_data->tty = tty; } + status = tty_get_tiocm(trigger_data->tty); + if (status > 0) { + if (trigger_data->mode_cts) { + if (status & TIOCM_CTS) + state = TTY_LED_ENABLE; + } + + if (trigger_data->mode_dsr) { + if (status & TIOCM_DSR) + state = TTY_LED_ENABLE; + } + + if (trigger_data->mode_dcd) { + if (status & TIOCM_CAR) + state = TTY_LED_ENABLE; + } + + if (trigger_data->mode_rng) { + if (status & TIOCM_RNG) + state = TTY_LED_ENABLE; + } + } + + /* + * The evaluation of rx/tx must be done after the evaluation + * of TIOCM_*, because rx/tx has priority. + */ if (trigger_data->mode_rx || trigger_data->mode_tx) { struct serial_icounter_struct icount; @@ -197,11 +263,13 @@ static void ledtrig_tty_work(struct work_struct *work) if (trigger_data->mode_tx && (icount.tx != trigger_data->tx)) { trigger_data->tx = icount.tx; + invert = state == TTY_LED_ENABLE; state = TTY_LED_BLINK; } if (trigger_data->mode_rx && (icount.rx != trigger_data->rx)) { trigger_data->rx = icount.rx; + invert = state == TTY_LED_ENABLE; state = TTY_LED_BLINK; } } @@ -210,7 +278,11 @@ out: switch (state) { case TTY_LED_BLINK: led_blink_set_oneshot(trigger_data->led_cdev, &interval, - &interval, 0); + &interval, invert); + break; + case TTY_LED_ENABLE: + led_set_brightness(trigger_data->led_cdev, + trigger_data->led_cdev->blink_brightness); break; case TTY_LED_DISABLE: fallthrough; @@ -228,6 +300,10 @@ static struct attribute *ledtrig_tty_attrs[] = { &dev_attr_ttyname.attr, &dev_attr_rx.attr, &dev_attr_tx.attr, + &dev_attr_cts.attr, + &dev_attr_dsr.attr, + &dev_attr_dcd.attr, + &dev_attr_rng.attr, NULL }; ATTRIBUTE_GROUPS(ledtrig_tty); -- cgit v1.2.3 From adfd4621b78d0c02da91335da2b9ad847cb7b39e Mon Sep 17 00:00:00 2001 From: Martin Kurbanov Date: Sat, 25 Nov 2023 23:05:09 +0300 Subject: leds: aw200xx: Fix write to DIM parameter If write only DIM value to the page 4, LED brightness will not be updated, as both DIM and FADE need to be written to the page 4. Therefore, write DIM to the page 1. Fixes: 36a87f371b7a ("leds: Add AW20xx driver") Signed-off-by: Martin Kurbanov Signed-off-by: Dmitry Rokosov Reviewed-by: Andy Shevchenko Link: https://lore.kernel.org/r/20231125200519.1750-2-ddrokosov@salutedevices.com Signed-off-by: Lee Jones --- drivers/leds/leds-aw200xx.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'drivers/leds') diff --git a/drivers/leds/leds-aw200xx.c b/drivers/leds/leds-aw200xx.c index 14ca236ce29e..f3bed4f05b34 100644 --- a/drivers/leds/leds-aw200xx.c +++ b/drivers/leds/leds-aw200xx.c @@ -74,6 +74,10 @@ #define AW200XX_LED2REG(x, columns) \ ((x) + (((x) / (columns)) * (AW200XX_DSIZE_COLUMNS_MAX - (columns)))) +/* DIM current configuration register on page 1 */ +#define AW200XX_REG_DIM_PAGE1(x, columns) \ + AW200XX_REG(AW200XX_PAGE1, AW200XX_LED2REG(x, columns)) + /* * DIM current configuration register (page 4). * The even address for current DIM configuration. @@ -153,7 +157,8 @@ static ssize_t dim_store(struct device *dev, struct device_attribute *devattr, if (dim >= 0) { ret = regmap_write(chip->regmap, - AW200XX_REG_DIM(led->num, columns), dim); + AW200XX_REG_DIM_PAGE1(led->num, columns), + dim); if (ret) goto out_unlock; } -- cgit v1.2.3 From d882762f7950c3dfd56c786a35d1518397773947 Mon Sep 17 00:00:00 2001 From: Dmitry Rokosov Date: Sat, 25 Nov 2023 23:05:10 +0300 Subject: leds: aw200xx: Support HWEN hardware control HWEN is hardware control, which is used for enable/disable aw200xx chip. It's high active, internally pulled down to GND. After HWEN pin set high the chip begins to load the OTP information, which takes 200us to complete. About 200us wait time is needed for internal oscillator startup and display SRAM initialization. After display SRAM initialization, the registers in page 1 to page 5 can be configured via i2c interface. Signed-off-by: Dmitry Rokosov Link: https://lore.kernel.org/r/20231125200519.1750-3-ddrokosov@salutedevices.com Signed-off-by: Lee Jones --- drivers/leds/leds-aw200xx.c | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) (limited to 'drivers/leds') diff --git a/drivers/leds/leds-aw200xx.c b/drivers/leds/leds-aw200xx.c index f3bed4f05b34..1bef0691dc41 100644 --- a/drivers/leds/leds-aw200xx.c +++ b/drivers/leds/leds-aw200xx.c @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -116,6 +117,7 @@ struct aw200xx { struct mutex mutex; u32 num_leds; u32 display_rows; + struct gpio_desc *hwen; struct aw200xx_led leds[] __counted_by(num_leds); }; @@ -358,6 +360,25 @@ static int aw200xx_chip_check(const struct aw200xx *const chip) return 0; } +static void aw200xx_enable(const struct aw200xx *const chip) +{ + gpiod_set_value_cansleep(chip->hwen, 1); + + /* + * After HWEN pin set high the chip begins to load the OTP information, + * which takes 200us to complete. About 200us wait time is needed for + * internal oscillator startup and display SRAM initialization. After + * display SRAM initialization, the registers in page1 to page5 can be + * configured via i2c interface. + */ + fsleep(400); +} + +static void aw200xx_disable(const struct aw200xx *const chip) +{ + return gpiod_set_value_cansleep(chip->hwen, 0); +} + static int aw200xx_probe_fw(struct device *dev, struct aw200xx *chip) { struct fwnode_handle *child; @@ -517,6 +538,14 @@ static int aw200xx_probe(struct i2c_client *client) if (IS_ERR(chip->regmap)) return PTR_ERR(chip->regmap); + chip->hwen = devm_gpiod_get_optional(&client->dev, "enable", + GPIOD_OUT_HIGH); + if (IS_ERR(chip->hwen)) + return dev_err_probe(&client->dev, PTR_ERR(chip->hwen), + "Cannot get enable GPIO"); + + aw200xx_enable(chip); + ret = aw200xx_chip_check(chip); if (ret) return ret; @@ -537,6 +566,9 @@ static int aw200xx_probe(struct i2c_client *client) ret = aw200xx_chip_init(chip); out_unlock: + if (ret) + aw200xx_disable(chip); + mutex_unlock(&chip->mutex); return ret; } @@ -546,6 +578,7 @@ static void aw200xx_remove(struct i2c_client *client) struct aw200xx *chip = i2c_get_clientdata(client); aw200xx_chip_reset(chip); + aw200xx_disable(chip); mutex_destroy(&chip->mutex); } -- cgit v1.2.3 From 2b8db5729d10b23edd61e018c18fa7b865e2d76c Mon Sep 17 00:00:00 2001 From: George Stark Date: Sat, 25 Nov 2023 23:05:12 +0300 Subject: leds: aw200xx: Calculate dts property display_rows in the driver Get rid of device tree property "awinic,display-rows". The property value actually means number of current switches and depends on how LEDs are connected to the device. It should be calculated manually by max used LED number. In the same way it is computed automatically now. Max used LED is taken from LED definition subnodes. Signed-off-by: George Stark Signed-off-by: Dmitry Rokosov Reviewed-by: Andy Shevchenko Link: https://lore.kernel.org/r/20231125200519.1750-5-ddrokosov@salutedevices.com Signed-off-by: Lee Jones --- drivers/leds/leds-aw200xx.c | 37 +++++++++++++++++++++++++++---------- 1 file changed, 27 insertions(+), 10 deletions(-) (limited to 'drivers/leds') diff --git a/drivers/leds/leds-aw200xx.c b/drivers/leds/leds-aw200xx.c index 1bef0691dc41..1756b012a0b7 100644 --- a/drivers/leds/leds-aw200xx.c +++ b/drivers/leds/leds-aw200xx.c @@ -379,6 +379,31 @@ static void aw200xx_disable(const struct aw200xx *const chip) return gpiod_set_value_cansleep(chip->hwen, 0); } +static int aw200xx_probe_get_display_rows(struct device *dev, + struct aw200xx *chip) +{ + struct fwnode_handle *child; + u32 max_source = 0; + + device_for_each_child_node(dev, child) { + u32 source; + int ret; + + ret = fwnode_property_read_u32(child, "reg", &source); + if (ret || source >= chip->cdef->channels) + continue; + + max_source = max(max_source, source); + } + + if (max_source == 0) + return -EINVAL; + + chip->display_rows = max_source / chip->cdef->display_size_columns + 1; + + return 0; +} + static int aw200xx_probe_fw(struct device *dev, struct aw200xx *chip) { struct fwnode_handle *child; @@ -386,18 +411,10 @@ static int aw200xx_probe_fw(struct device *dev, struct aw200xx *chip) int ret; int i; - ret = device_property_read_u32(dev, "awinic,display-rows", - &chip->display_rows); + ret = aw200xx_probe_get_display_rows(dev, chip); if (ret) return dev_err_probe(dev, ret, - "Failed to read 'display-rows' property\n"); - - if (!chip->display_rows || - chip->display_rows > chip->cdef->display_size_rows_max) { - return dev_err_probe(dev, -EINVAL, - "Invalid leds display size %u\n", - chip->display_rows); - } + "No valid led definitions found\n"); current_max = aw200xx_imax_from_global(chip, AW200XX_IMAX_MAX_uA); current_min = aw200xx_imax_from_global(chip, AW200XX_IMAX_MIN_uA); -- cgit v1.2.3 From d883a5ab2f345c2adca781901731795ab94886fb Mon Sep 17 00:00:00 2001 From: George Stark Date: Sat, 25 Nov 2023 23:05:14 +0300 Subject: leds: aw200xx: Add delay after software reset According to the datasheets of AW200xx devices, the software reset takes at least 1ms. Therefore, it is required to add a delay after the reset before issuing commands to the device. Signed-off-by: George Stark Signed-off-by: Dmitry Rokosov Link: https://lore.kernel.org/r/20231125200519.1750-7-ddrokosov@salutedevices.com Signed-off-by: Lee Jones --- drivers/leds/leds-aw200xx.c | 3 +++ 1 file changed, 3 insertions(+) (limited to 'drivers/leds') diff --git a/drivers/leds/leds-aw200xx.c b/drivers/leds/leds-aw200xx.c index 1756b012a0b7..d4877722eb56 100644 --- a/drivers/leds/leds-aw200xx.c +++ b/drivers/leds/leds-aw200xx.c @@ -321,6 +321,9 @@ static int aw200xx_chip_reset(const struct aw200xx *const chip) if (ret) return ret; + /* According to the datasheet software reset takes at least 1ms */ + fsleep(1000); + regcache_mark_dirty(chip->regmap); return regmap_write(chip->regmap, AW200XX_REG_FCD, AW200XX_FCD_CLEAR); } -- cgit v1.2.3 From 96b43a108bd689159486e97cb5a8b0170fa46657 Mon Sep 17 00:00:00 2001 From: George Stark Date: Sat, 25 Nov 2023 23:05:15 +0300 Subject: leds: aw200xx: Enable disable_locking flag in regmap config In the driver regmap is always used under mutex so regmap's inner lock can be disabled. Signed-off-by: George Stark Signed-off-by: Dmitry Rokosov Reviewed-by: Andy Shevchenko Link: https://lore.kernel.org/r/20231125200519.1750-8-ddrokosov@salutedevices.com Signed-off-by: Lee Jones --- drivers/leds/leds-aw200xx.c | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers/leds') diff --git a/drivers/leds/leds-aw200xx.c b/drivers/leds/leds-aw200xx.c index d4877722eb56..220860258dec 100644 --- a/drivers/leds/leds-aw200xx.c +++ b/drivers/leds/leds-aw200xx.c @@ -526,6 +526,7 @@ static const struct regmap_config aw200xx_regmap_config = { .rd_table = &aw200xx_readable_table, .wr_table = &aw200xx_writeable_table, .cache_type = REGCACHE_MAPLE, + .disable_locking = true, }; static int aw200xx_probe(struct i2c_client *client) -- cgit v1.2.3 From 150bca53652d8dcef9714d0873a86e129391f1e6 Mon Sep 17 00:00:00 2001 From: George Stark Date: Sat, 25 Nov 2023 23:05:16 +0300 Subject: leds: aw200xx: Improve autodim calculation method It is highly recommended to leverage the DIV_ROUND_UP() function as a more refined and mathematically precise alternative to employing a coarse division method. Signed-off-by: George Stark Signed-off-by: Dmitry Rokosov Reviewed-by: Andy Shevchenko Link: https://lore.kernel.org/r/20231125200519.1750-9-ddrokosov@salutedevices.com Signed-off-by: Lee Jones --- drivers/leds/leds-aw200xx.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'drivers/leds') diff --git a/drivers/leds/leds-aw200xx.c b/drivers/leds/leds-aw200xx.c index 220860258dec..fe9b1565cef0 100644 --- a/drivers/leds/leds-aw200xx.c +++ b/drivers/leds/leds-aw200xx.c @@ -87,6 +87,8 @@ #define AW200XX_REG_DIM(x, columns) \ AW200XX_REG(AW200XX_PAGE4, AW200XX_LED2REG(x, columns) * 2) #define AW200XX_REG_DIM2FADE(x) ((x) + 1) +#define AW200XX_REG_FADE2DIM(fade) \ + DIV_ROUND_UP((fade) * AW200XX_DIM_MAX, AW200XX_FADE_MAX) /* * Duty ratio of display scan (see p.15 of datasheet for formula): @@ -195,9 +197,7 @@ static int aw200xx_brightness_set(struct led_classdev *cdev, dim = led->dim; if (dim < 0) - dim = max_t(int, - brightness / (AW200XX_FADE_MAX / AW200XX_DIM_MAX), - 1); + dim = AW200XX_REG_FADE2DIM(brightness); ret = regmap_write(chip->regmap, reg, dim); if (ret) @@ -462,6 +462,7 @@ static int aw200xx_probe_fw(struct device *dev, struct aw200xx *chip) led->num = source; led->chip = chip; led->cdev.brightness_set_blocking = aw200xx_brightness_set; + led->cdev.max_brightness = AW200XX_FADE_MAX; led->cdev.groups = dim_groups; init_data.fwnode = child; -- cgit v1.2.3 From 634fea792a31717669094f7866cf32b8eeb932c0 Mon Sep 17 00:00:00 2001 From: George Stark Date: Sat, 25 Nov 2023 23:05:17 +0300 Subject: leds: aw200xx: Add support for aw20108 device Add support for the Awinic aw20108 device, which belongs to the same LED drivers family. The new device supports 108 LEDs using a matrix of 12x9 outputs." Signed-off-by: George Stark Signed-off-by: Dmitry Rokosov Link: https://lore.kernel.org/r/20231125200519.1750-10-ddrokosov@salutedevices.com Signed-off-by: Lee Jones --- drivers/leds/Kconfig | 14 +++++++++----- drivers/leds/leds-aw200xx.c | 10 +++++++++- 2 files changed, 18 insertions(+), 6 deletions(-) (limited to 'drivers/leds') diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig index bfa11e7b157f..a2a4689e0502 100644 --- a/drivers/leds/Kconfig +++ b/drivers/leds/Kconfig @@ -95,14 +95,18 @@ config LEDS_ARIEL Say Y to if your machine is a Dell Wyse 3020 thin client. config LEDS_AW200XX - tristate "LED support for Awinic AW20036/AW20054/AW20072" + tristate "LED support for Awinic AW20036/AW20054/AW20072/AW20108" depends on LEDS_CLASS depends on I2C help - This option enables support for the AW20036/AW20054/AW20072 LED driver. - It is a 3x12/6x9/6x12 matrix LED driver programmed via - an I2C interface, up to 36/54/72 LEDs or 12/18/24 RGBs, - 3 pattern controllers for auto breathing or group dimming control. + This option enables support for the Awinic AW200XX LED controllers. + It is a matrix LED driver programmed via an I2C interface. Devices have + a set of individually controlled LEDs and support 3 pattern controllers + for auto breathing or group dimming control. Supported devices: + - AW20036 (3x12) 36 LEDs + - AW20054 (6x9) 54 LEDs + - AW20072 (6x12) 72 LEDs + - AW20108 (9x12) 108 LEDs To compile this driver as a module, choose M here: the module will be called leds-aw200xx. diff --git a/drivers/leds/leds-aw200xx.c b/drivers/leds/leds-aw200xx.c index fe9b1565cef0..f584a7f98fc5 100644 --- a/drivers/leds/leds-aw200xx.c +++ b/drivers/leds/leds-aw200xx.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 /* - * Awinic AW20036/AW20054/AW20072 LED driver + * Awinic AW20036/AW20054/AW20072/AW20108 LED driver * * Copyright (c) 2023, SberDevices. All Rights Reserved. * @@ -622,10 +622,17 @@ static const struct aw200xx_chipdef aw20072_cdef = { .display_size_columns = 12, }; +static const struct aw200xx_chipdef aw20108_cdef = { + .channels = 108, + .display_size_rows_max = 9, + .display_size_columns = 12, +}; + static const struct i2c_device_id aw200xx_id[] = { { "aw20036" }, { "aw20054" }, { "aw20072" }, + { "aw20108" }, {} }; MODULE_DEVICE_TABLE(i2c, aw200xx_id); @@ -634,6 +641,7 @@ static const struct of_device_id aw200xx_match_table[] = { { .compatible = "awinic,aw20036", .data = &aw20036_cdef, }, { .compatible = "awinic,aw20054", .data = &aw20054_cdef, }, { .compatible = "awinic,aw20072", .data = &aw20072_cdef, }, + { .compatible = "awinic,aw20108", .data = &aw20108_cdef, }, {} }; MODULE_DEVICE_TABLE(of, aw200xx_match_table); -- cgit v1.2.3 From e7431bd7899c955e126c742fe608512f7e2e111a Mon Sep 17 00:00:00 2001 From: Stefan Wahren Date: Sat, 2 Dec 2023 19:36:36 +0100 Subject: leds: gpio: Add kernel log if devm_fwnode_gpiod_get() fails In case leds-gpio fails to get at least one of possibly many GPIOs from the DT (e.g. the GPIO is already requested) neither gpiolib nor the driver does provide any helpful error log: leds-gpio: probe of leds failed with error -16 As the driver knows better how to handle errors with such mandatory GPIOs, let's implement an error log which points to the affected GPIO. Signed-off-by: Stefan Wahren Reviewed-by: Andy Shevchenko Reviewed-by: Linus Walleij Link: https://lore.kernel.org/r/20231202183636.7055-1-wahrenst@gmx.net Signed-off-by: Lee Jones --- drivers/leds/leds-gpio.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'drivers/leds') diff --git a/drivers/leds/leds-gpio.c b/drivers/leds/leds-gpio.c index 710c319ad312..83fcd7b6afff 100644 --- a/drivers/leds/leds-gpio.c +++ b/drivers/leds/leds-gpio.c @@ -172,6 +172,8 @@ static struct gpio_leds_priv *gpio_leds_create(struct device *dev) led.gpiod = devm_fwnode_gpiod_get(dev, child, NULL, GPIOD_ASIS, NULL); if (IS_ERR(led.gpiod)) { + dev_err_probe(dev, PTR_ERR(led.gpiod), "Failed to get GPIO '%pfw'\n", + child); fwnode_handle_put(child); return ERR_CAST(led.gpiod); } -- cgit v1.2.3 From 9e314ded2832908ef270468a5d8337c83f25f550 Mon Sep 17 00:00:00 2001 From: Uwe Kleine-König Date: Wed, 6 Dec 2023 12:43:19 +0100 Subject: leds: qcom-lpg: Introduce a wrapper for getting driver data from a pwm chip MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Uwe Kleine-König Link: https://lore.kernel.org/r/4785982785812615d15c7dd6d2755270bd8670b2.1701860672.git.u.kleine-koenig@pengutronix.de Signed-off-by: Lee Jones --- drivers/leds/rgb/leds-qcom-lpg.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) (limited to 'drivers/leds') diff --git a/drivers/leds/rgb/leds-qcom-lpg.c b/drivers/leds/rgb/leds-qcom-lpg.c index 54c90ee43ef8..156b73d1f4a2 100644 --- a/drivers/leds/rgb/leds-qcom-lpg.c +++ b/drivers/leds/rgb/leds-qcom-lpg.c @@ -976,9 +976,14 @@ static int lpg_pattern_mc_clear(struct led_classdev *cdev) return lpg_pattern_clear(led); } +static inline struct lpg *lpg_pwm_from_chip(struct pwm_chip *chip) +{ + return container_of(chip, struct lpg, pwm); +} + static int lpg_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm) { - struct lpg *lpg = container_of(chip, struct lpg, pwm); + struct lpg *lpg = lpg_pwm_from_chip(chip); struct lpg_channel *chan = &lpg->channels[pwm->hwpwm]; return chan->in_use ? -EBUSY : 0; @@ -994,7 +999,7 @@ static int lpg_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm) static int lpg_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, const struct pwm_state *state) { - struct lpg *lpg = container_of(chip, struct lpg, pwm); + struct lpg *lpg = lpg_pwm_from_chip(chip); struct lpg_channel *chan = &lpg->channels[pwm->hwpwm]; int ret = 0; @@ -1025,7 +1030,7 @@ out_unlock: static int lpg_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm, struct pwm_state *state) { - struct lpg *lpg = container_of(chip, struct lpg, pwm); + struct lpg *lpg = lpg_pwm_from_chip(chip); struct lpg_channel *chan = &lpg->channels[pwm->hwpwm]; unsigned int resolution; unsigned int pre_div; -- cgit v1.2.3 From c82a1662d4548c454de5343b88f69b9fc82266b3 Mon Sep 17 00:00:00 2001 From: Heiner Kallweit Date: Fri, 8 Dec 2023 23:56:41 +0100 Subject: leds: trigger: Remove unused function led_trigger_rename_static() This function was added with a8df7b1ab70b ("leds: add led_trigger_rename function") 11 yrs ago, but it has no users. So remove it. Signed-off-by: Heiner Kallweit Link: https://lore.kernel.org/r/d90f30be-f661-4db7-b0b5-d09d07a78a68@gmail.com Signed-off-by: Lee Jones --- drivers/leds/led-triggers.c | 13 ------------- include/linux/leds.h | 17 ----------------- 2 files changed, 30 deletions(-) (limited to 'drivers/leds') diff --git a/drivers/leds/led-triggers.c b/drivers/leds/led-triggers.c index 6a5e1f41f9a4..bd59a14a4a90 100644 --- a/drivers/leds/led-triggers.c +++ b/drivers/leds/led-triggers.c @@ -269,19 +269,6 @@ void led_trigger_set_default(struct led_classdev *led_cdev) } EXPORT_SYMBOL_GPL(led_trigger_set_default); -void led_trigger_rename_static(const char *name, struct led_trigger *trig) -{ - /* new name must be on a temporary string to prevent races */ - BUG_ON(name == trig->name); - - down_write(&triggers_list_lock); - /* this assumes that trig->name was originaly allocated to - * non constant storage */ - strcpy((char *)trig->name, name); - up_write(&triggers_list_lock); -} -EXPORT_SYMBOL_GPL(led_trigger_rename_static); - /* LED Trigger Interface */ int led_trigger_register(struct led_trigger *trig) diff --git a/include/linux/leds.h b/include/linux/leds.h index 1bdf7f5a0d7c..4754b02d3a2c 100644 --- a/include/linux/leds.h +++ b/include/linux/leds.h @@ -527,23 +527,6 @@ static inline void *led_get_trigger_data(struct led_classdev *led_cdev) return led_cdev->trigger_data; } -/** - * led_trigger_rename_static - rename a trigger - * @name: the new trigger name - * @trig: the LED trigger to rename - * - * Change a LED trigger name by copying the string passed in - * name into current trigger name, which MUST be large - * enough for the new string. - * - * Note that name must NOT point to the same string used - * during LED registration, as that could lead to races. - * - * This is meant to be used on triggers with statically - * allocated name. - */ -void led_trigger_rename_static(const char *name, struct led_trigger *trig); - #define module_led_trigger(__led_trigger) \ module_driver(__led_trigger, led_trigger_register, \ led_trigger_unregister) -- cgit v1.2.3 From 40cfa414e7f99ea0aa3b578e382eed93540c3641 Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Tue, 12 Dec 2023 22:45:22 +0100 Subject: leds: sun50i-a100: Avoid division-by-zero warning When CONFIG_COMMON_CLK is disabled, e.g. on an x86 randconfig compile test, clang reports a field overflow from propagating the result of a division by zero: drivers/leds/leds-sun50i-a100.c:309:12: error: call to '__compiletime_assert_265' declared with 'error' attribute: FIELD_PREP: value too large for the field control = FIELD_PREP(LEDC_T01_TIMING_CTRL_REG_T1H, timing->t1h_ns / cycle_ns) | Avoid the problem by adding an explicit check for the zero value here. Alternatively the assertion could be avoided with a Kconfig dependency on COMMON_CLK. Fixes: 090a25ad9798 ("leds: sun50i-a100: New driver for the A100 LED controller") Signed-off-by: Arnd Bergmann Reviewed-by: Guo Ren Link: https://lore.kernel.org/r/20231212214536.175327-1-arnd@kernel.org Signed-off-by: Lee Jones --- drivers/leds/leds-sun50i-a100.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'drivers/leds') diff --git a/drivers/leds/leds-sun50i-a100.c b/drivers/leds/leds-sun50i-a100.c index e4a7e692a908..171cefd1ea0d 100644 --- a/drivers/leds/leds-sun50i-a100.c +++ b/drivers/leds/leds-sun50i-a100.c @@ -303,9 +303,13 @@ static void sun50i_a100_ledc_set_timing(struct sun50i_a100_ledc *priv) { const struct sun50i_a100_ledc_timing *timing = &priv->timing; unsigned long mod_freq = clk_get_rate(priv->mod_clk); - u32 cycle_ns = NSEC_PER_SEC / mod_freq; + u32 cycle_ns; u32 control; + if (!mod_freq) + return; + + cycle_ns = NSEC_PER_SEC / mod_freq; control = FIELD_PREP(LEDC_T01_TIMING_CTRL_REG_T1H, timing->t1h_ns / cycle_ns) | FIELD_PREP(LEDC_T01_TIMING_CTRL_REG_T1L, timing->t1l_ns / cycle_ns) | FIELD_PREP(LEDC_T01_TIMING_CTRL_REG_T0H, timing->t0h_ns / cycle_ns) | -- cgit v1.2.3 From 5e72f1fe23839bd2093693a357a6fac8e5463483 Mon Sep 17 00:00:00 2001 From: Jean Delvare Date: Sat, 2 Dec 2023 21:43:53 +0100 Subject: leds: rgb: Drop obsolete dependency on COMPILE_TEST Since commit 0166dc11be91 ("of: make CONFIG_OF user selectable"), it is possible to test-build any driver which depends on OF on any architecture by explicitly selecting OF. Therefore depending on COMPILE_TEST as an alternative is no longer needed. Signed-off-by: Jean Delvare Link: https://lore.kernel.org/r/20231202214353.7c02f23c@endymion.delvare Signed-off-by: Lee Jones --- drivers/leds/rgb/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/leds') diff --git a/drivers/leds/rgb/Kconfig b/drivers/leds/rgb/Kconfig index a6a21f564673..e66bd21b9852 100644 --- a/drivers/leds/rgb/Kconfig +++ b/drivers/leds/rgb/Kconfig @@ -4,7 +4,7 @@ if LEDS_CLASS_MULTICOLOR config LEDS_GROUP_MULTICOLOR tristate "LEDs group multi-color support" - depends on OF || COMPILE_TEST + depends on OF help This option enables support for monochrome LEDs that are grouped into multicolor LEDs which is useful in the case where LEDs of -- cgit v1.2.3 From d3578b4982e6ebccbd898806ac86b2db4b2bcc5e Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Thu, 14 Dec 2023 20:40:08 +0200 Subject: leds: max5970: Remove unused variable leds-max5970.c:50:21: warning: variable 'num_leds' set but not used [-Wunused-but-set-variable] Remove unused variable. Fixes: 736214b4b02a ("leds: max5970: Add support for max5970") Signed-off-by: Andy Shevchenko Link: https://lore.kernel.org/r/20231214184050.1272848-2-andriy.shevchenko@linux.intel.com Signed-off-by: Lee Jones --- drivers/leds/leds-max5970.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'drivers/leds') diff --git a/drivers/leds/leds-max5970.c b/drivers/leds/leds-max5970.c index 456a16a47450..7959d079ae94 100644 --- a/drivers/leds/leds-max5970.c +++ b/drivers/leds/leds-max5970.c @@ -45,7 +45,7 @@ static int max5970_led_probe(struct platform_device *pdev) struct regmap *regmap; struct device_node *led_node, *child; struct max5970_led *ddata; - int ret = -ENODEV, num_leds = 0; + int ret = -ENODEV; regmap = dev_get_regmap(pdev->dev.parent, NULL); if (!regmap) @@ -89,7 +89,6 @@ static int max5970_led_probe(struct platform_device *pdev) dev_err(dev, "Failed to initialize LED %u\n", reg); return ret; } - num_leds++; } return ret; -- cgit v1.2.3 From 6d63d05e26f8d5e22308efc25793660101fd7602 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Thu, 14 Dec 2023 20:40:09 +0200 Subject: leds: max5970: Make use of device properties Convert the module to be property provider agnostic and allow it to be used on non-OF platforms. Add mod_devicetable.h include. Signed-off-by: Andy Shevchenko Link: https://lore.kernel.org/r/20231214184050.1272848-3-andriy.shevchenko@linux.intel.com Signed-off-by: Lee Jones --- drivers/leds/leds-max5970.c | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) (limited to 'drivers/leds') diff --git a/drivers/leds/leds-max5970.c b/drivers/leds/leds-max5970.c index 7959d079ae94..de57b385b4f6 100644 --- a/drivers/leds/leds-max5970.c +++ b/drivers/leds/leds-max5970.c @@ -9,8 +9,9 @@ #include #include -#include +#include #include +#include #include #define ldev_to_maxled(c) container_of(c, struct max5970_led, cdev) @@ -40,25 +41,24 @@ static int max5970_led_set_brightness(struct led_classdev *cdev, static int max5970_led_probe(struct platform_device *pdev) { + struct fwnode_handle *led_node, *child; struct device *dev = &pdev->dev; - struct device_node *np = dev_of_node(dev->parent); struct regmap *regmap; - struct device_node *led_node, *child; struct max5970_led *ddata; int ret = -ENODEV; - regmap = dev_get_regmap(pdev->dev.parent, NULL); + regmap = dev_get_regmap(dev->parent, NULL); if (!regmap) return -ENODEV; - led_node = of_get_child_by_name(np, "leds"); + led_node = device_get_named_child_node(dev->parent, "leds"); if (!led_node) return -ENODEV; - for_each_available_child_of_node(led_node, child) { + fwnode_for_each_available_child_node(led_node, child) { u32 reg; - if (of_property_read_u32(child, "reg", ®)) + if (fwnode_property_read_u32(child, "reg", ®)) continue; if (reg >= MAX5970_NUM_LEDS) { @@ -68,7 +68,7 @@ static int max5970_led_probe(struct platform_device *pdev) ddata = devm_kzalloc(dev, sizeof(*ddata), GFP_KERNEL); if (!ddata) { - of_node_put(child); + fwnode_handle_put(child); return -ENOMEM; } @@ -76,8 +76,8 @@ static int max5970_led_probe(struct platform_device *pdev) ddata->regmap = regmap; ddata->dev = dev; - if (of_property_read_string(child, "label", &ddata->cdev.name)) - ddata->cdev.name = child->name; + if (fwnode_property_read_string(child, "label", &ddata->cdev.name)) + ddata->cdev.name = fwnode_get_name(child); ddata->cdev.max_brightness = 1; ddata->cdev.brightness_set_blocking = max5970_led_set_brightness; @@ -85,7 +85,7 @@ static int max5970_led_probe(struct platform_device *pdev) ret = devm_led_classdev_register(dev, &ddata->cdev); if (ret < 0) { - of_node_put(child); + fwnode_handle_put(child); dev_err(dev, "Failed to initialize LED %u\n", reg); return ret; } @@ -100,8 +100,8 @@ static struct platform_driver max5970_led_driver = { }, .probe = max5970_led_probe, }; - module_platform_driver(max5970_led_driver); + MODULE_AUTHOR("Patrick Rudolph "); MODULE_AUTHOR("Naresh Solanki "); MODULE_DESCRIPTION("MAX5970_hot-swap controller LED driver"); -- cgit v1.2.3 From e7baa5b437a782308b86c1517ae252fd1353eb0b Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Thu, 14 Dec 2023 20:40:10 +0200 Subject: leds: max5970: Make use of dev_err_probe() Simplify the error handling in probe function by switching from dev_err() to dev_err_probe(). Signed-off-by: Andy Shevchenko Link: https://lore.kernel.org/r/20231214184050.1272848-4-andriy.shevchenko@linux.intel.com Signed-off-by: Lee Jones --- drivers/leds/leds-max5970.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'drivers/leds') diff --git a/drivers/leds/leds-max5970.c b/drivers/leds/leds-max5970.c index de57b385b4f6..60db3c28d7d9 100644 --- a/drivers/leds/leds-max5970.c +++ b/drivers/leds/leds-max5970.c @@ -62,7 +62,7 @@ static int max5970_led_probe(struct platform_device *pdev) continue; if (reg >= MAX5970_NUM_LEDS) { - dev_err(dev, "invalid LED (%u >= %d)\n", reg, MAX5970_NUM_LEDS); + dev_err_probe(dev, -EINVAL, "invalid LED (%u >= %d)\n", reg, MAX5970_NUM_LEDS); continue; } @@ -86,8 +86,7 @@ static int max5970_led_probe(struct platform_device *pdev) ret = devm_led_classdev_register(dev, &ddata->cdev); if (ret < 0) { fwnode_handle_put(child); - dev_err(dev, "Failed to initialize LED %u\n", reg); - return ret; + return dev_err_probe(dev, ret, "Failed to initialize LED %u\n", reg); } } -- cgit v1.2.3 From 808c7881876756dfcd8c4d0c3efc27c9262da822 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Thu, 14 Dec 2023 20:40:11 +0200 Subject: leds: max5970: Add missing headers Don't inherit headers "by chance" from others. Include the needed ones explicitly. Signed-off-by: Andy Shevchenko Link: https://lore.kernel.org/r/20231214184050.1272848-5-andriy.shevchenko@linux.intel.com Signed-off-by: Lee Jones --- drivers/leds/leds-max5970.c | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'drivers/leds') diff --git a/drivers/leds/leds-max5970.c b/drivers/leds/leds-max5970.c index 60db3c28d7d9..56a584311581 100644 --- a/drivers/leds/leds-max5970.c +++ b/drivers/leds/leds-max5970.c @@ -7,9 +7,13 @@ * Author: Patrick Rudolph */ +#include +#include +#include #include #include #include +#include #include #include #include -- cgit v1.2.3 From 06c5206ccdb459de95bb8d558602d2a722dd4bbc Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Thu, 14 Dec 2023 21:21:31 +0200 Subject: leds: sun50i-a100: Convert to be agnostic to property provider Convert the driver to be agnostic to the property provider. LEDS subsytem is not dependent on OF, so no need to make drivers be a such. Signed-off-by: Andy Shevchenko Reviewed-by: Jernej Skrabec Link: https://lore.kernel.org/r/20231214192131.1309912-1-andriy.shevchenko@linux.intel.com Signed-off-by: Lee Jones --- drivers/leds/Kconfig | 2 +- drivers/leds/leds-sun50i-a100.c | 46 ++++++++++++++++++++--------------------- 2 files changed, 24 insertions(+), 24 deletions(-) (limited to 'drivers/leds') diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig index a2a4689e0502..d721b254e1e4 100644 --- a/drivers/leds/Kconfig +++ b/drivers/leds/Kconfig @@ -305,7 +305,7 @@ config LEDS_COBALT_RAQ config LEDS_SUN50I_A100 tristate "LED support for Allwinner A100 RGB LED controller" - depends on LEDS_CLASS_MULTICOLOR && OF + depends on LEDS_CLASS_MULTICOLOR depends on ARCH_SUNXI || COMPILE_TEST help This option enables support for the RGB LED controller found diff --git a/drivers/leds/leds-sun50i-a100.c b/drivers/leds/leds-sun50i-a100.c index 171cefd1ea0d..62d21c3a3575 100644 --- a/drivers/leds/leds-sun50i-a100.c +++ b/drivers/leds/leds-sun50i-a100.c @@ -15,10 +15,11 @@ #include #include #include +#include #include -#include #include #include +#include #include #include @@ -247,13 +248,13 @@ static const char *const sun50i_a100_ledc_formats[] = { "rgb", "rbg", "grb", "gbr", "brg", "bgr", }; -static int sun50i_a100_ledc_parse_format(const struct device_node *np, +static int sun50i_a100_ledc_parse_format(struct device *dev, struct sun50i_a100_ledc *priv) { const char *format = "grb"; u32 i; - of_property_read_string(np, "allwinner,pixel-format", &format); + device_property_read_string(dev, "allwinner,pixel-format", &format); for (i = 0; i < ARRAY_SIZE(sun50i_a100_ledc_formats); i++) { if (!strcmp(format, sun50i_a100_ledc_formats[i])) { @@ -262,7 +263,7 @@ static int sun50i_a100_ledc_parse_format(const struct device_node *np, } } - return dev_err_probe(priv->dev, -EINVAL, "Bad pixel format '%s'\n", format); + return dev_err_probe(dev, -EINVAL, "Bad pixel format '%s'\n", format); } static void sun50i_a100_ledc_set_format(struct sun50i_a100_ledc *priv) @@ -283,18 +284,18 @@ static const struct sun50i_a100_ledc_timing sun50i_a100_ledc_default_timing = { .treset_ns = 300000, }; -static int sun50i_a100_ledc_parse_timing(const struct device_node *np, +static int sun50i_a100_ledc_parse_timing(struct device *dev, struct sun50i_a100_ledc *priv) { struct sun50i_a100_ledc_timing *timing = &priv->timing; *timing = sun50i_a100_ledc_default_timing; - of_property_read_u32(np, "allwinner,t0h-ns", &timing->t0h_ns); - of_property_read_u32(np, "allwinner,t0l-ns", &timing->t0l_ns); - of_property_read_u32(np, "allwinner,t1h-ns", &timing->t1h_ns); - of_property_read_u32(np, "allwinner,t1l-ns", &timing->t1l_ns); - of_property_read_u32(np, "allwinner,treset-ns", &timing->treset_ns); + device_property_read_u32(dev, "allwinner,t0h-ns", &timing->t0h_ns); + device_property_read_u32(dev, "allwinner,t0l-ns", &timing->t0l_ns); + device_property_read_u32(dev, "allwinner,t1h-ns", &timing->t1h_ns); + device_property_read_u32(dev, "allwinner,t1l-ns", &timing->t1l_ns); + device_property_read_u32(dev, "allwinner,treset-ns", &timing->treset_ns); return 0; } @@ -388,13 +389,12 @@ static void sun50i_a100_ledc_dma_cleanup(void *data) static int sun50i_a100_ledc_probe(struct platform_device *pdev) { - const struct device_node *np = pdev->dev.of_node; struct dma_slave_config dma_cfg = {}; struct led_init_data init_data = {}; struct sun50i_a100_ledc_led *led; struct device *dev = &pdev->dev; struct sun50i_a100_ledc *priv; - struct device_node *child; + struct fwnode_handle *child; struct resource *mem; u32 max_addr = 0; u32 num_leds = 0; @@ -404,19 +404,19 @@ static int sun50i_a100_ledc_probe(struct platform_device *pdev) * The maximum LED address must be known in sun50i_a100_ledc_resume() before * class device registration, so parse and validate the subnodes up front. */ - for_each_available_child_of_node(np, child) { + device_for_each_child_node(dev, child) { u32 addr, color; - ret = of_property_read_u32(child, "reg", &addr); + ret = fwnode_property_read_u32(child, "reg", &addr); if (ret || addr >= LEDC_MAX_LEDS) { - of_node_put(child); + fwnode_handle_put(child); return dev_err_probe(dev, -EINVAL, "'reg' must be between 0 and %d\n", LEDC_MAX_LEDS - 1); } - ret = of_property_read_u32(child, "color", &color); + ret = fwnode_property_read_u32(child, "color", &color); if (ret || color != LED_COLOR_ID_RGB) { - of_node_put(child); + fwnode_handle_put(child); return dev_err_probe(dev, -EINVAL, "'color' must be LED_COLOR_ID_RGB\n"); } @@ -437,11 +437,11 @@ static int sun50i_a100_ledc_probe(struct platform_device *pdev) spin_lock_init(&priv->lock); dev_set_drvdata(dev, priv); - ret = sun50i_a100_ledc_parse_format(np, priv); + ret = sun50i_a100_ledc_parse_format(dev, priv); if (ret) return ret; - ret = sun50i_a100_ledc_parse_timing(np, priv); + ret = sun50i_a100_ledc_parse_timing(dev, priv); if (ret) return ret; @@ -504,11 +504,11 @@ static int sun50i_a100_ledc_probe(struct platform_device *pdev) return ret; led = priv->leds; - for_each_available_child_of_node(np, child) { + device_for_each_child_node(dev, child) { struct led_classdev *cdev; /* The node was already validated above. */ - of_property_read_u32(child, "reg", &led->addr); + fwnode_property_read_u32(child, "reg", &led->addr); led->subled_info[0].color_index = LED_COLOR_ID_RED; led->subled_info[0].channel = 0; @@ -524,7 +524,7 @@ static int sun50i_a100_ledc_probe(struct platform_device *pdev) cdev->max_brightness = U8_MAX; cdev->brightness_set = sun50i_a100_ledc_brightness_set; - init_data.fwnode = of_fwnode_handle(child); + init_data.fwnode = child; ret = led_classdev_multicolor_register_ext(dev, &led->mc_cdev, &init_data); if (ret) { @@ -540,7 +540,7 @@ static int sun50i_a100_ledc_probe(struct platform_device *pdev) return 0; err_put_child: - of_node_put(child); + fwnode_handle_put(child); while (led-- > priv->leds) led_classdev_multicolor_unregister(&led->mc_cdev); sun50i_a100_ledc_suspend(&pdev->dev); -- cgit v1.2.3 From afacb21834bb02785ddb0c3ec197208803b74faa Mon Sep 17 00:00:00 2001 From: Heiner Kallweit Date: Sat, 16 Dec 2023 21:05:33 +0100 Subject: leds: trigger: panic: Don't register panic notifier if creating the trigger failed It doesn't make sense to register the panic notifier if creating the panic trigger failed. Signed-off-by: Heiner Kallweit Link: https://lore.kernel.org/r/8a61e229-5388-46c7-919a-4d18cc7362b2@gmail.com Signed-off-by: Lee Jones --- drivers/leds/trigger/ledtrig-panic.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'drivers/leds') diff --git a/drivers/leds/trigger/ledtrig-panic.c b/drivers/leds/trigger/ledtrig-panic.c index 64abf2e91608..5a6b21bfeb9a 100644 --- a/drivers/leds/trigger/ledtrig-panic.c +++ b/drivers/leds/trigger/ledtrig-panic.c @@ -64,10 +64,13 @@ static long led_panic_blink(int state) static int __init ledtrig_panic_init(void) { + led_trigger_register_simple("panic", &trigger); + if (!trigger) + return -ENOMEM; + atomic_notifier_chain_register(&panic_notifier_list, &led_trigger_panic_nb); - led_trigger_register_simple("panic", &trigger); panic_blink = led_panic_blink; return 0; } -- cgit v1.2.3 From 4289e434c46c8cbd32cf8b67fa7689b3d2ca4361 Mon Sep 17 00:00:00 2001 From: Heiner Kallweit Date: Sun, 17 Dec 2023 19:46:42 +0100 Subject: leds: trigger: netdev: Add core support for hw not supporting fallback to LED sw control If hw doesn't support sw control of the LED and we switch to a mode not supported by hw, currently we get lots of errors because neither brigthness_set() nor brithness_set_blocking() is set. Deal with this by not falling back to sw control, and return -EOPNOTSUPP to the user. Note that we still store the new mode. This is needed in case an intermediate unsupported mode is necessary to switch from one supported mode to another. Add a comment explaining how a driver for such hw is supposed to behave. Signed-off-by: Heiner Kallweit Reviewed-by: Andrew Lunn Link: https://lore.kernel.org/r/3fd5184c-3641-4b0b-b59a-f489ec69a6cd@gmail.com Signed-off-by: Lee Jones --- drivers/leds/trigger/ledtrig-netdev.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) (limited to 'drivers/leds') diff --git a/drivers/leds/trigger/ledtrig-netdev.c b/drivers/leds/trigger/ledtrig-netdev.c index bd68da15c723..836610292b37 100644 --- a/drivers/leds/trigger/ledtrig-netdev.c +++ b/drivers/leds/trigger/ledtrig-netdev.c @@ -38,6 +38,16 @@ * tx - LED blinks on transmitted data * rx - LED blinks on receive data * + * Note: If the user selects a mode that is not supported by hw, default + * behavior is to fall back to software control of the LED. However not every + * hw supports software control. LED callbacks brightness_set() and + * brightness_set_blocking() are NULL in this case. hw_control_is_supported() + * should use available means supported by hw to inform the user that selected + * mode isn't supported by hw. This could be switching off the LED or any + * hw blink mode. If software control fallback isn't possible, we return + * -EOPNOTSUPP to the user, but still store the selected mode. This is needed + * in case an intermediate unsupported mode is necessary to switch from one + * supported mode to another. */ struct led_netdev_data { @@ -318,6 +328,7 @@ static ssize_t netdev_led_attr_store(struct device *dev, const char *buf, size_t size, enum led_trigger_netdev_modes attr) { struct led_netdev_data *trigger_data = led_trigger_get_drvdata(dev); + struct led_classdev *led_cdev = trigger_data->led_cdev; unsigned long state, mode = trigger_data->mode; int ret; int bit; @@ -363,6 +374,10 @@ static ssize_t netdev_led_attr_store(struct device *dev, const char *buf, trigger_data->mode = mode; trigger_data->hw_control = can_hw_control(trigger_data); + if (!led_cdev->brightness_set && !led_cdev->brightness_set_blocking && + !trigger_data->hw_control) + return -EOPNOTSUPP; + set_baseline_state(trigger_data); return size; -- cgit v1.2.3