Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

leds: add LED driver for EL15203000 board

This patch adds a LED class driver for the LEDs found on
the Crane Merchandising System EL15203000 LEDs board
(aka RED LEDs board).

Signed-off-by: Oleh Kravchenko <oleg@kaa.org.ua>
Reviewed-by: Dan Murphy <dmurphy@ti.com>
Signed-off-by: Pavel Machek <pavel@ucw.cz>

authored by

Oleh Kravchenko and committed by
Pavel
fc19967b a788f6f2

+510
+139
Documentation/ABI/testing/sysfs-class-led-driver-el15203000
··· 1 + What: /sys/class/leds/<led>/hw_pattern 2 + Date: September 2019 3 + KernelVersion: 5.5 4 + Description: 5 + Specify a hardware pattern for the EL15203000 LED. 6 + The LEDs board supports only predefined patterns by firmware 7 + for specific LEDs. 8 + 9 + Breathing mode for Screen frame light tube: 10 + "0 4000 1 4000" 11 + 12 + ^ 13 + | 14 + Max-| --- 15 + | / \ 16 + | / \ 17 + | / \ / 18 + | / \ / 19 + Min-|- --- 20 + | 21 + 0------4------8--> time (sec) 22 + 23 + Cascade mode for Pipe LED: 24 + "1 800 2 800 4 800 8 800 16 800" 25 + 26 + ^ 27 + | 28 + 0 On -|----+ +----+ +--- 29 + | | | | | 30 + Off-| +-------------------+ +-------------------+ 31 + | 32 + 1 On -| +----+ +----+ 33 + | | | | | 34 + Off |----+ +-------------------+ +------------------ 35 + | 36 + 2 On -| +----+ +----+ 37 + | | | | | 38 + Off-|---------+ +-------------------+ +------------- 39 + | 40 + 3 On -| +----+ +----+ 41 + | | | | | 42 + Off-|--------------+ +-------------------+ +-------- 43 + | 44 + 4 On -| +----+ +----+ 45 + | | | | | 46 + Off-|-------------------+ +-------------------+ +--- 47 + | 48 + 0---0.8--1.6--2.4--3.2---4---4.8--5.6--6.4--7.2---8--> time (sec) 49 + 50 + Inverted cascade mode for Pipe LED: 51 + "30 800 29 800 27 800 23 800 15 800" 52 + 53 + ^ 54 + | 55 + 0 On -| +-------------------+ +-------------------+ 56 + | | | | | 57 + Off-|----+ +----+ +--- 58 + | 59 + 1 On -|----+ +-------------------+ +------------------ 60 + | | | | | 61 + Off | +----+ +----+ 62 + | 63 + 2 On -|---------+ +-------------------+ +------------- 64 + | | | | | 65 + Off-| +----+ +----+ 66 + | 67 + 3 On -|--------------+ +-------------------+ +-------- 68 + | | | | | 69 + Off-| +----+ +----+ 70 + | 71 + 4 On -|-------------------+ +-------------------+ +--- 72 + | | | | | 73 + Off-| +----+ +----+ 74 + | 75 + 0---0.8--1.6--2.4--3.2---4---4.8--5.6--6.4--7.2---8--> time (sec) 76 + 77 + Bounce mode for Pipe LED: 78 + "1 800 2 800 4 800 8 800 16 800 16 800 8 800 4 800 2 800 1 800" 79 + 80 + ^ 81 + | 82 + 0 On -|----+ +-------- 83 + | | | 84 + Off-| +---------------------------------------+ 85 + | 86 + 1 On -| +----+ +----+ 87 + | | | | | 88 + Off |----+ +-----------------------------+ +-------- 89 + | 90 + 2 On -| +----+ +----+ 91 + | | | | | 92 + Off-|---------+ +-------------------+ +------------- 93 + | 94 + 3 On -| +----+ +----+ 95 + | | | | | 96 + Off-|--------------+ +---------+ +------------------ 97 + | 98 + 4 On -| +---------+ 99 + | | | 100 + Off-|-------------------+ +----------------------- 101 + | 102 + 0---0.8--1.6--2.4--3.2---4---4.8--5.6--6.4--7.2---8--> time (sec) 103 + 104 + Inverted bounce mode for Pipe LED: 105 + "30 800 29 800 27 800 23 800 15 800 15 800 23 800 27 800 29 800 30 800" 106 + 107 + ^ 108 + | 109 + 0 On -| +---------------------------------------+ 110 + | | | 111 + Off-|----+ +-------- 112 + | 113 + 1 On -|----+ +-----------------------------+ +-------- 114 + | | | | | 115 + Off | +----+ +----+ 116 + | 117 + 2 On -|---------+ +-------------------+ +------------- 118 + | | | | | 119 + Off-| +----+ +----+ 120 + | 121 + 3 On -|--------------+ +---------+ +------------------ 122 + | | | | | 123 + Off-| +----+ +----+ 124 + | 125 + 4 On -|-------------------+ +----------------------- 126 + | | | 127 + Off-| +---------+ 128 + | 129 + 0---0.8--1.6--2.4--3.2---4---4.8--5.6--6.4--7.2---8--> time (sec) 130 + 131 + What: /sys/class/leds/<led>/repeat 132 + Date: September 2019 133 + KernelVersion: 5.5 134 + Description: 135 + EL15203000 supports only indefinitely patterns, 136 + so this file should always store -1. 137 + 138 + For more info, please see: 139 + Documentation/ABI/testing/sysfs-class-led-trigger-pattern
+13
drivers/leds/Kconfig
··· 132 132 To compile this driver as a module, choose M here: the module 133 133 will be called leds-cr0014114. 134 134 135 + config LEDS_EL15203000 136 + tristate "LED Support for Crane EL15203000" 137 + depends on LEDS_CLASS 138 + depends on SPI 139 + depends on OF 140 + help 141 + This option enables support for EL15203000 LED Board 142 + (aka RED LED board) which is widely used in coffee vending 143 + machines produced by Crane Merchandising Systems. 144 + 145 + To compile this driver as a module, choose M here: the module 146 + will be called leds-el15203000. 147 + 135 148 config LEDS_LM3530 136 149 tristate "LCD Backlight driver for LM3530" 137 150 depends on LEDS_CLASS
+1
drivers/leds/Makefile
··· 89 89 # LED SPI Drivers 90 90 obj-$(CONFIG_LEDS_CR0014114) += leds-cr0014114.o 91 91 obj-$(CONFIG_LEDS_DAC124S085) += leds-dac124s085.o 92 + obj-$(CONFIG_LEDS_EL15203000) += leds-el15203000.o 92 93 93 94 # LED Userspace Drivers 94 95 obj-$(CONFIG_LEDS_USER) += uleds.o
+357
drivers/leds/leds-el15203000.c
··· 1 + // SPDX-License-Identifier: GPL-2.0 2 + // Copyright (c) 2019 Crane Merchandising Systems. All rights reserved. 3 + // Copyright (C) 2019 Oleh Kravchenko <oleg@kaa.org.ua> 4 + 5 + #include <linux/delay.h> 6 + #include <linux/leds.h> 7 + #include <linux/module.h> 8 + #include <linux/of_device.h> 9 + #include <linux/spi/spi.h> 10 + 11 + /* 12 + * EL15203000 SPI protocol description: 13 + * +-----+---------+ 14 + * | LED | COMMAND | 15 + * +-----+---------+ 16 + * | 1 | 1 | 17 + * +-----+---------+ 18 + * (*) LEDs MCU board expects 20 msec delay per byte. 19 + * 20 + * LEDs: 21 + * +----------+--------------+-------------------------------------------+ 22 + * | ID | NAME | DESCRIPTION | 23 + * +----------+--------------+-------------------------------------------+ 24 + * | 'P' 0x50 | Pipe | Consists from 5 LEDs, controlled by board | 25 + * +----------+--------------+-------------------------------------------+ 26 + * | 'S' 0x53 | Screen frame | Light tube around the screen | 27 + * +----------+--------------+-------------------------------------------+ 28 + * | 'V' 0x56 | Vending area | Highlights a cup of coffee | 29 + * +----------+--------------+-------------------------------------------+ 30 + * 31 + * COMMAND: 32 + * +----------+-----------------+--------------+--------------+ 33 + * | VALUES | PIPE | SCREEN FRAME | VENDING AREA | 34 + * +----------+-----------------+--------------+--------------+ 35 + * | '0' 0x30 | Off | 36 + * +----------+-----------------------------------------------+ 37 + * | '1' 0x31 | On | 38 + * +----------+-----------------+--------------+--------------+ 39 + * | '2' 0x32 | Cascade | Breathing | 40 + * +----------+-----------------+--------------+ 41 + * | '3' 0x33 | Inverse cascade | 42 + * +----------+-----------------+ 43 + * | '4' 0x34 | Bounce | 44 + * +----------+-----------------+ 45 + * | '5' 0x35 | Inverse bounce | 46 + * +----------+-----------------+ 47 + */ 48 + 49 + /* EL15203000 default settings */ 50 + #define EL_FW_DELAY_USEC 20000ul 51 + #define EL_PATTERN_DELAY_MSEC 800u 52 + #define EL_PATTERN_LEN 10u 53 + #define EL_PATTERN_HALF_LEN (EL_PATTERN_LEN / 2) 54 + 55 + enum el15203000_command { 56 + /* for all LEDs */ 57 + EL_OFF = '0', 58 + EL_ON = '1', 59 + 60 + /* for Screen LED */ 61 + EL_SCREEN_BREATHING = '2', 62 + 63 + /* for Pipe LED */ 64 + EL_PIPE_CASCADE = '2', 65 + EL_PIPE_INV_CASCADE = '3', 66 + EL_PIPE_BOUNCE = '4', 67 + EL_PIPE_INV_BOUNCE = '5', 68 + }; 69 + 70 + struct el15203000_led { 71 + struct el15203000 *priv; 72 + struct led_classdev ldev; 73 + u32 reg; 74 + }; 75 + 76 + struct el15203000 { 77 + struct device *dev; 78 + struct mutex lock; 79 + struct spi_device *spi; 80 + unsigned long delay; 81 + size_t count; 82 + struct el15203000_led leds[]; 83 + }; 84 + 85 + static int el15203000_cmd(struct el15203000_led *led, u8 brightness) 86 + { 87 + int ret; 88 + u8 cmd[2]; 89 + size_t i; 90 + 91 + mutex_lock(&led->priv->lock); 92 + 93 + dev_dbg(led->priv->dev, "Set brightness of 0x%02x(%c) to 0x%02x(%c)", 94 + led->reg, led->reg, brightness, brightness); 95 + 96 + /* to avoid SPI mistiming with firmware we should wait some time */ 97 + if (time_after(led->priv->delay, jiffies)) { 98 + dev_dbg(led->priv->dev, "Wait %luus to sync", 99 + EL_FW_DELAY_USEC); 100 + 101 + usleep_range(EL_FW_DELAY_USEC, 102 + EL_FW_DELAY_USEC + 1); 103 + } 104 + 105 + cmd[0] = led->reg; 106 + cmd[1] = brightness; 107 + 108 + for (i = 0; i < ARRAY_SIZE(cmd); i++) { 109 + if (i) 110 + usleep_range(EL_FW_DELAY_USEC, 111 + EL_FW_DELAY_USEC + 1); 112 + 113 + ret = spi_write(led->priv->spi, &cmd[i], sizeof(cmd[i])); 114 + if (ret) { 115 + dev_err(led->priv->dev, 116 + "spi_write() error %d", ret); 117 + break; 118 + } 119 + } 120 + 121 + led->priv->delay = jiffies + usecs_to_jiffies(EL_FW_DELAY_USEC); 122 + 123 + mutex_unlock(&led->priv->lock); 124 + 125 + return ret; 126 + } 127 + 128 + static int el15203000_set_blocking(struct led_classdev *ldev, 129 + enum led_brightness brightness) 130 + { 131 + struct el15203000_led *led = container_of(ldev, 132 + struct el15203000_led, 133 + ldev); 134 + 135 + return el15203000_cmd(led, brightness == LED_OFF ? EL_OFF : EL_ON); 136 + } 137 + 138 + static int el15203000_pattern_set_S(struct led_classdev *ldev, 139 + struct led_pattern *pattern, 140 + u32 len, int repeat) 141 + { 142 + struct el15203000_led *led = container_of(ldev, 143 + struct el15203000_led, 144 + ldev); 145 + 146 + if (repeat > 0 || len != 2 || 147 + pattern[0].delta_t != 4000 || pattern[0].brightness != 0 || 148 + pattern[1].delta_t != 4000 || pattern[1].brightness != 1) 149 + return -EINVAL; 150 + 151 + dev_dbg(led->priv->dev, "Breathing mode for 0x%02x(%c)", 152 + led->reg, led->reg); 153 + 154 + return el15203000_cmd(led, EL_SCREEN_BREATHING); 155 + } 156 + 157 + static bool is_cascade(const struct led_pattern *pattern, u32 len, 158 + bool inv, bool right) 159 + { 160 + int val, t; 161 + u32 i; 162 + 163 + if (len != EL_PATTERN_HALF_LEN) 164 + return false; 165 + 166 + val = right ? BIT(4) : BIT(0); 167 + 168 + for (i = 0; i < len; i++) { 169 + t = inv ? ~val & GENMASK(4, 0) : val; 170 + 171 + if (pattern[i].delta_t != EL_PATTERN_DELAY_MSEC || 172 + pattern[i].brightness != t) 173 + return false; 174 + 175 + val = right ? val >> 1 : val << 1; 176 + } 177 + 178 + return true; 179 + } 180 + 181 + static bool is_bounce(const struct led_pattern *pattern, u32 len, bool inv) 182 + { 183 + if (len != EL_PATTERN_LEN) 184 + return false; 185 + 186 + return is_cascade(pattern, EL_PATTERN_HALF_LEN, inv, false) && 187 + is_cascade(pattern + EL_PATTERN_HALF_LEN, 188 + EL_PATTERN_HALF_LEN, inv, true); 189 + } 190 + 191 + static int el15203000_pattern_set_P(struct led_classdev *ldev, 192 + struct led_pattern *pattern, 193 + u32 len, int repeat) 194 + { 195 + u8 cmd; 196 + struct el15203000_led *led = container_of(ldev, 197 + struct el15203000_led, 198 + ldev); 199 + 200 + if (repeat > 0) 201 + return -EINVAL; 202 + 203 + if (is_cascade(pattern, len, false, false)) { 204 + dev_dbg(led->priv->dev, "Cascade mode for 0x%02x(%c)", 205 + led->reg, led->reg); 206 + 207 + cmd = EL_PIPE_CASCADE; 208 + } else if (is_cascade(pattern, len, true, false)) { 209 + dev_dbg(led->priv->dev, "Inverse cascade mode for 0x%02x(%c)", 210 + led->reg, led->reg); 211 + 212 + cmd = EL_PIPE_INV_CASCADE; 213 + } else if (is_bounce(pattern, len, false)) { 214 + dev_dbg(led->priv->dev, "Bounce mode for 0x%02x(%c)", 215 + led->reg, led->reg); 216 + 217 + cmd = EL_PIPE_BOUNCE; 218 + } else if (is_bounce(pattern, len, true)) { 219 + dev_dbg(led->priv->dev, "Inverse bounce mode for 0x%02x(%c)", 220 + led->reg, led->reg); 221 + 222 + cmd = EL_PIPE_INV_BOUNCE; 223 + } else { 224 + dev_err(led->priv->dev, "Invalid hw_pattern for 0x%02x(%c)!", 225 + led->reg, led->reg); 226 + 227 + return -EINVAL; 228 + } 229 + 230 + return el15203000_cmd(led, cmd); 231 + } 232 + 233 + static int el15203000_pattern_clear(struct led_classdev *ldev) 234 + { 235 + struct el15203000_led *led = container_of(ldev, 236 + struct el15203000_led, 237 + ldev); 238 + 239 + return el15203000_cmd(led, EL_OFF); 240 + } 241 + 242 + static int el15203000_probe_dt(struct el15203000 *priv) 243 + { 244 + struct el15203000_led *led = priv->leds; 245 + struct fwnode_handle *child; 246 + int ret; 247 + 248 + device_for_each_child_node(priv->dev, child) { 249 + struct led_init_data init_data = {}; 250 + 251 + ret = fwnode_property_read_u32(child, "reg", &led->reg); 252 + if (ret) { 253 + dev_err(priv->dev, "LED without ID number"); 254 + fwnode_handle_put(child); 255 + 256 + break; 257 + } 258 + 259 + if (led->reg > U8_MAX) { 260 + dev_err(priv->dev, "LED value %d is invalid", led->reg); 261 + fwnode_handle_put(child); 262 + 263 + return -EINVAL; 264 + } 265 + 266 + fwnode_property_read_string(child, "linux,default-trigger", 267 + &led->ldev.default_trigger); 268 + 269 + led->priv = priv; 270 + led->ldev.max_brightness = LED_ON; 271 + led->ldev.brightness_set_blocking = el15203000_set_blocking; 272 + 273 + if (led->reg == 'S') { 274 + led->ldev.pattern_set = el15203000_pattern_set_S; 275 + led->ldev.pattern_clear = el15203000_pattern_clear; 276 + } else if (led->reg == 'P') { 277 + led->ldev.pattern_set = el15203000_pattern_set_P; 278 + led->ldev.pattern_clear = el15203000_pattern_clear; 279 + } 280 + 281 + init_data.fwnode = child; 282 + ret = devm_led_classdev_register_ext(priv->dev, &led->ldev, 283 + &init_data); 284 + if (ret) { 285 + dev_err(priv->dev, 286 + "failed to register LED device %s, err %d", 287 + led->ldev.name, ret); 288 + fwnode_handle_put(child); 289 + 290 + break; 291 + } 292 + 293 + led++; 294 + } 295 + 296 + return ret; 297 + } 298 + 299 + static int el15203000_probe(struct spi_device *spi) 300 + { 301 + struct el15203000 *priv; 302 + size_t count; 303 + 304 + count = device_get_child_node_count(&spi->dev); 305 + if (!count) { 306 + dev_err(&spi->dev, "LEDs are not defined in device tree!"); 307 + return -ENODEV; 308 + } 309 + 310 + priv = devm_kzalloc(&spi->dev, struct_size(priv, leds, count), 311 + GFP_KERNEL); 312 + if (!priv) 313 + return -ENOMEM; 314 + 315 + mutex_init(&priv->lock); 316 + priv->count = count; 317 + priv->dev = &spi->dev; 318 + priv->spi = spi; 319 + priv->delay = jiffies - 320 + usecs_to_jiffies(EL_FW_DELAY_USEC); 321 + 322 + spi_set_drvdata(spi, priv); 323 + 324 + return el15203000_probe_dt(priv); 325 + } 326 + 327 + static int el15203000_remove(struct spi_device *spi) 328 + { 329 + struct el15203000 *priv = spi_get_drvdata(spi); 330 + 331 + mutex_destroy(&priv->lock); 332 + 333 + return 0; 334 + } 335 + 336 + static const struct of_device_id el15203000_dt_ids[] = { 337 + { .compatible = "crane,el15203000", }, 338 + {}, 339 + }; 340 + 341 + MODULE_DEVICE_TABLE(of, el15203000_dt_ids); 342 + 343 + static struct spi_driver el15203000_driver = { 344 + .probe = el15203000_probe, 345 + .remove = el15203000_remove, 346 + .driver = { 347 + .name = KBUILD_MODNAME, 348 + .of_match_table = el15203000_dt_ids, 349 + }, 350 + }; 351 + 352 + module_spi_driver(el15203000_driver); 353 + 354 + MODULE_AUTHOR("Oleh Kravchenko <oleg@kaa.org.ua>"); 355 + MODULE_DESCRIPTION("el15203000 LED driver"); 356 + MODULE_LICENSE("GPL v2"); 357 + MODULE_ALIAS("spi:el15203000");