1253544Shselasky// SPDX-License-Identifier: GPL-2.0 2253544Shselasky// Copyright (c) 2019 Crane Merchandising Systems. All rights reserved. 3253544Shselasky// Copyright (C) 2019 Oleh Kravchenko <oleg@kaa.org.ua> 4253544Shselasky 5253544Shselasky#include <linux/delay.h> 6253544Shselasky#include <linux/leds.h> 7253544Shselasky#include <linux/mod_devicetable.h> 8253544Shselasky#include <linux/module.h> 9253544Shselasky#include <linux/property.h> 10253544Shselasky#include <linux/spi/spi.h> 11253544Shselasky 12253544Shselasky/* 13253544Shselasky * EL15203000 SPI protocol description: 14253544Shselasky * +-----+---------+ 15253544Shselasky * | LED | COMMAND | 16253544Shselasky * +-----+---------+ 17253544Shselasky * | 1 | 1 | 18253544Shselasky * +-----+---------+ 19253544Shselasky * (*) LEDs MCU board expects 20 msec delay per byte. 20253544Shselasky * 21253544Shselasky * LEDs: 22253544Shselasky * +----------+--------------+-------------------------------------------+ 23253544Shselasky * | ID | NAME | DESCRIPTION | 24253544Shselasky * +----------+--------------+-------------------------------------------+ 25253544Shselasky * | 'P' 0x50 | Pipe | Consists from 5 LEDs, controlled by board | 26253544Shselasky * +----------+--------------+-------------------------------------------+ 27253544Shselasky * | 'S' 0x53 | Screen frame | Light tube around the screen | 28253544Shselasky * +----------+--------------+-------------------------------------------+ 29253544Shselasky * | 'V' 0x56 | Vending area | Highlights a cup of coffee | 30253544Shselasky * +----------+--------------+-------------------------------------------+ 31253544Shselasky * 32253618Sobrien * COMMAND: 33253618Sobrien * +----------+-----------------+--------------+--------------+ 34253618Sobrien * | VALUES | PIPE | SCREEN FRAME | VENDING AREA | 35253544Shselasky * +----------+-----------------+--------------+--------------+ 36253544Shselasky * | '0' 0x30 | Off | 37253544Shselasky * +----------+-----------------------------------------------+ 38253544Shselasky * | '1' 0x31 | On | 39253544Shselasky * +----------+-----------------+--------------+--------------+ 40253544Shselasky * | '2' 0x32 | Cascade | Breathing | 41253544Shselasky * +----------+-----------------+--------------+ 42253544Shselasky * | '3' 0x33 | Inverse cascade | 43253544Shselasky * +----------+-----------------+ 44253544Shselasky * | '4' 0x34 | Bounce | 45253544Shselasky * +----------+-----------------+ 46253544Shselasky * | '5' 0x35 | Inverse bounce | 47253544Shselasky * +----------+-----------------+ 48253544Shselasky */ 49253544Shselasky 50253544Shselasky/* EL15203000 default settings */ 51253544Shselasky#define EL_FW_DELAY_USEC 20000ul 52253544Shselasky#define EL_PATTERN_DELAY_MSEC 800u 53253544Shselasky#define EL_PATTERN_LEN 10u 54253544Shselasky#define EL_PATTERN_HALF_LEN (EL_PATTERN_LEN / 2) 55253544Shselasky 56253544Shselaskyenum el15203000_command { 57253544Shselasky /* for all LEDs */ 58253544Shselasky EL_OFF = '0', 59253544Shselasky EL_ON = '1', 60253544Shselasky 61253544Shselasky /* for Screen LED */ 62253544Shselasky EL_SCREEN_BREATHING = '2', 63253544Shselasky 64253544Shselasky /* for Pipe LED */ 65253544Shselasky EL_PIPE_CASCADE = '2', 66253544Shselasky EL_PIPE_INV_CASCADE = '3', 67253544Shselasky EL_PIPE_BOUNCE = '4', 68253544Shselasky EL_PIPE_INV_BOUNCE = '5', 69253544Shselasky}; 70253544Shselasky 71253544Shselaskystruct el15203000_led { 72253544Shselasky struct led_classdev ldev; 73253544Shselasky struct el15203000 *priv; 74253544Shselasky u32 reg; 75253544Shselasky}; 76253544Shselasky 77253544Shselaskystruct el15203000 { 78253544Shselasky struct device *dev; 79253544Shselasky struct mutex lock; 80253544Shselasky struct spi_device *spi; 81253544Shselasky unsigned long delay; 82253544Shselasky size_t count; 83253544Shselasky struct el15203000_led leds[] __counted_by(count); 84253544Shselasky}; 85253544Shselasky 86253544Shselasky#define to_el15203000_led(d) container_of(d, struct el15203000_led, ldev) 87253544Shselasky 88253544Shselaskystatic int el15203000_cmd(struct el15203000_led *led, u8 brightness) 89253544Shselasky{ 90253544Shselasky int ret; 91253544Shselasky u8 cmd[2]; 92253544Shselasky size_t i; 93253544Shselasky 94253544Shselasky mutex_lock(&led->priv->lock); 95253544Shselasky 96253544Shselasky dev_dbg(led->priv->dev, "Set brightness of 0x%02x(%c) to 0x%02x(%c)", 97253544Shselasky led->reg, led->reg, brightness, brightness); 98276701Shselasky 99253544Shselasky /* to avoid SPI mistiming with firmware we should wait some time */ 100253544Shselasky if (time_after(led->priv->delay, jiffies)) { 101253544Shselasky dev_dbg(led->priv->dev, "Wait %luus to sync", 102253544Shselasky EL_FW_DELAY_USEC); 103253544Shselasky 104276701Shselasky usleep_range(EL_FW_DELAY_USEC, 105253544Shselasky EL_FW_DELAY_USEC + 1); 106253544Shselasky } 107253544Shselasky 108253544Shselasky cmd[0] = led->reg; 109276701Shselasky cmd[1] = brightness; 110253544Shselasky 111253544Shselasky for (i = 0; i < ARRAY_SIZE(cmd); i++) { 112253544Shselasky if (i) 113253544Shselasky usleep_range(EL_FW_DELAY_USEC, 114253544Shselasky EL_FW_DELAY_USEC + 1); 115253544Shselasky 116253544Shselasky ret = spi_write(led->priv->spi, &cmd[i], sizeof(cmd[i])); 117253544Shselasky if (ret) { 118253544Shselasky dev_err(led->priv->dev, 119253544Shselasky "spi_write() error %d", ret); 120253544Shselasky break; 121253544Shselasky } 122253544Shselasky } 123253544Shselasky 124253544Shselasky led->priv->delay = jiffies + usecs_to_jiffies(EL_FW_DELAY_USEC); 125253544Shselasky 126253544Shselasky mutex_unlock(&led->priv->lock); 127253544Shselasky 128253544Shselasky return ret; 129253544Shselasky} 130253544Shselasky 131253544Shselaskystatic int el15203000_set_blocking(struct led_classdev *ldev, 132253544Shselasky enum led_brightness brightness) 133253544Shselasky{ 134253544Shselasky struct el15203000_led *led = to_el15203000_led(ldev); 135253544Shselasky 136253544Shselasky return el15203000_cmd(led, brightness == LED_OFF ? EL_OFF : EL_ON); 137253544Shselasky} 138253544Shselasky 139253544Shselaskystatic int el15203000_pattern_set_S(struct led_classdev *ldev, 140253544Shselasky struct led_pattern *pattern, 141253544Shselasky u32 len, int repeat) 142253544Shselasky{ 143253544Shselasky struct el15203000_led *led = to_el15203000_led(ldev); 144253544Shselasky 145253544Shselasky if (repeat > 0 || len != 2 || 146253544Shselasky pattern[0].delta_t != 4000 || pattern[0].brightness != 0 || 147253544Shselasky pattern[1].delta_t != 4000 || pattern[1].brightness != 1) 148253544Shselasky return -EINVAL; 149253544Shselasky 150253544Shselasky dev_dbg(led->priv->dev, "Breathing mode for 0x%02x(%c)", 151253544Shselasky led->reg, led->reg); 152253544Shselasky 153253544Shselasky return el15203000_cmd(led, EL_SCREEN_BREATHING); 154253544Shselasky} 155253544Shselasky 156253544Shselaskystatic bool is_cascade(const struct led_pattern *pattern, u32 len, 157253544Shselasky bool inv, bool right) 158253544Shselasky{ 159253544Shselasky int val, t; 160253544Shselasky u32 i; 161253544Shselasky 162253544Shselasky if (len != EL_PATTERN_HALF_LEN) 163253544Shselasky return false; 164253544Shselasky 165253544Shselasky val = right ? BIT(4) : BIT(0); 166253544Shselasky 167253544Shselasky for (i = 0; i < len; i++) { 168253544Shselasky t = inv ? ~val & GENMASK(4, 0) : val; 169253544Shselasky 170253544Shselasky if (pattern[i].delta_t != EL_PATTERN_DELAY_MSEC || 171253544Shselasky pattern[i].brightness != t) 172253544Shselasky return false; 173253544Shselasky 174253544Shselasky val = right ? val >> 1 : val << 1; 175253544Shselasky } 176253544Shselasky 177253544Shselasky return true; 178253544Shselasky} 179253544Shselasky 180253544Shselaskystatic bool is_bounce(const struct led_pattern *pattern, u32 len, bool inv) 181253544Shselasky{ 182253544Shselasky if (len != EL_PATTERN_LEN) 183253544Shselasky return false; 184253544Shselasky 185253544Shselasky return is_cascade(pattern, EL_PATTERN_HALF_LEN, inv, false) && 186253544Shselasky is_cascade(pattern + EL_PATTERN_HALF_LEN, 187253544Shselasky EL_PATTERN_HALF_LEN, inv, true); 188253544Shselasky} 189253544Shselasky 190253544Shselaskystatic int el15203000_pattern_set_P(struct led_classdev *ldev, 191253544Shselasky struct led_pattern *pattern, 192253544Shselasky u32 len, int repeat) 193253544Shselasky{ 194253544Shselasky struct el15203000_led *led = to_el15203000_led(ldev); 195253544Shselasky u8 cmd; 196253544Shselasky 197253544Shselasky if (repeat > 0) 198253544Shselasky return -EINVAL; 199253544Shselasky 200253544Shselasky if (is_cascade(pattern, len, false, false)) { 201253544Shselasky dev_dbg(led->priv->dev, "Cascade mode for 0x%02x(%c)", 202253544Shselasky led->reg, led->reg); 203253544Shselasky 204253544Shselasky cmd = EL_PIPE_CASCADE; 205253544Shselasky } else if (is_cascade(pattern, len, true, false)) { 206253544Shselasky dev_dbg(led->priv->dev, "Inverse cascade mode for 0x%02x(%c)", 207253544Shselasky led->reg, led->reg); 208253544Shselasky 209253544Shselasky cmd = EL_PIPE_INV_CASCADE; 210253544Shselasky } else if (is_bounce(pattern, len, false)) { 211253544Shselasky dev_dbg(led->priv->dev, "Bounce mode for 0x%02x(%c)", 212253544Shselasky led->reg, led->reg); 213253544Shselasky 214253544Shselasky cmd = EL_PIPE_BOUNCE; 215253544Shselasky } else if (is_bounce(pattern, len, true)) { 216253544Shselasky dev_dbg(led->priv->dev, "Inverse bounce mode for 0x%02x(%c)", 217253544Shselasky led->reg, led->reg); 218253544Shselasky 219253544Shselasky cmd = EL_PIPE_INV_BOUNCE; 220253544Shselasky } else { 221253544Shselasky dev_err(led->priv->dev, "Invalid hw_pattern for 0x%02x(%c)!", 222253544Shselasky led->reg, led->reg); 223253544Shselasky 224253544Shselasky return -EINVAL; 225253544Shselasky } 226253544Shselasky 227253544Shselasky return el15203000_cmd(led, cmd); 228253544Shselasky} 229253544Shselasky 230253544Shselaskystatic int el15203000_pattern_clear(struct led_classdev *ldev) 231253544Shselasky{ 232253544Shselasky struct el15203000_led *led = to_el15203000_led(ldev); 233253544Shselasky 234253544Shselasky return el15203000_cmd(led, EL_OFF); 235253544Shselasky} 236253544Shselasky 237253544Shselaskystatic int el15203000_probe_dt(struct el15203000 *priv) 238253544Shselasky{ 239253544Shselasky struct el15203000_led *led = priv->leds; 240253544Shselasky struct fwnode_handle *child; 241253544Shselasky int ret; 242253544Shselasky 243253544Shselasky device_for_each_child_node(priv->dev, child) { 244253544Shselasky struct led_init_data init_data = {}; 245253544Shselasky 246253544Shselasky ret = fwnode_property_read_u32(child, "reg", &led->reg); 247253544Shselasky if (ret) { 248253544Shselasky dev_err(priv->dev, "LED without ID number"); 249253544Shselasky goto err_child_out; 250253544Shselasky } 251253544Shselasky 252253544Shselasky if (led->reg > U8_MAX) { 253253544Shselasky dev_err(priv->dev, "LED value %d is invalid", led->reg); 254253544Shselasky ret = -EINVAL; 255253544Shselasky goto err_child_out; 256253544Shselasky } 257253544Shselasky 258253544Shselasky led->priv = priv; 259253544Shselasky led->ldev.max_brightness = LED_ON; 260253544Shselasky led->ldev.brightness_set_blocking = el15203000_set_blocking; 261253544Shselasky 262253544Shselasky if (led->reg == 'S') { 263253544Shselasky led->ldev.pattern_set = el15203000_pattern_set_S; 264253544Shselasky led->ldev.pattern_clear = el15203000_pattern_clear; 265253544Shselasky } else if (led->reg == 'P') { 266253544Shselasky led->ldev.pattern_set = el15203000_pattern_set_P; 267253544Shselasky led->ldev.pattern_clear = el15203000_pattern_clear; 268253544Shselasky } 269253544Shselasky 270253544Shselasky init_data.fwnode = child; 271253544Shselasky ret = devm_led_classdev_register_ext(priv->dev, &led->ldev, 272253544Shselasky &init_data); 273253544Shselasky if (ret) { 274253544Shselasky dev_err(priv->dev, 275253544Shselasky "failed to register LED device %s, err %d", 276253544Shselasky led->ldev.name, ret); 277253544Shselasky goto err_child_out; 278253544Shselasky } 279253544Shselasky 280253544Shselasky led++; 281253544Shselasky } 282253544Shselasky 283253544Shselasky return 0; 284253544Shselasky 285253544Shselaskyerr_child_out: 286253544Shselasky fwnode_handle_put(child); 287253544Shselasky return ret; 288253544Shselasky} 289253544Shselasky 290253544Shselaskystatic int el15203000_probe(struct spi_device *spi) 291253544Shselasky{ 292253544Shselasky struct el15203000 *priv; 293253544Shselasky size_t count; 294253544Shselasky 295253544Shselasky count = device_get_child_node_count(&spi->dev); 296253544Shselasky if (!count) { 297253544Shselasky dev_err(&spi->dev, "LEDs are not defined in device tree!"); 298253544Shselasky return -ENODEV; 299253544Shselasky } 300253544Shselasky 301253544Shselasky priv = devm_kzalloc(&spi->dev, struct_size(priv, leds, count), 302253544Shselasky GFP_KERNEL); 303253544Shselasky if (!priv) 304253544Shselasky return -ENOMEM; 305253544Shselasky 306253544Shselasky mutex_init(&priv->lock); 307253544Shselasky priv->count = count; 308253544Shselasky priv->dev = &spi->dev; 309253544Shselasky priv->spi = spi; 310253544Shselasky priv->delay = jiffies - 311253544Shselasky usecs_to_jiffies(EL_FW_DELAY_USEC); 312253544Shselasky 313253544Shselasky spi_set_drvdata(spi, priv); 314253544Shselasky 315253544Shselasky return el15203000_probe_dt(priv); 316253544Shselasky} 317253544Shselasky 318253544Shselaskystatic void el15203000_remove(struct spi_device *spi) 319253544Shselasky{ 320253544Shselasky struct el15203000 *priv = spi_get_drvdata(spi); 321253544Shselasky 322253544Shselasky mutex_destroy(&priv->lock); 323253544Shselasky} 324253544Shselasky 325253544Shselaskystatic const struct of_device_id el15203000_dt_ids[] = { 326253544Shselasky { .compatible = "crane,el15203000", }, 327253544Shselasky {}, 328253544Shselasky}; 329253544Shselasky 330253544ShselaskyMODULE_DEVICE_TABLE(of, el15203000_dt_ids); 331253544Shselasky 332253544Shselaskystatic struct spi_driver el15203000_driver = { 333253544Shselasky .probe = el15203000_probe, 334253544Shselasky .remove = el15203000_remove, 335253544Shselasky .driver = { 336253544Shselasky .name = KBUILD_MODNAME, 337253544Shselasky .of_match_table = el15203000_dt_ids, 338253544Shselasky }, 339253544Shselasky}; 340253544Shselasky 341253544Shselaskymodule_spi_driver(el15203000_driver); 342253544Shselasky 343253544ShselaskyMODULE_AUTHOR("Oleh Kravchenko <oleg@kaa.org.ua>"); 344253544ShselaskyMODULE_DESCRIPTION("el15203000 LED driver"); 345253544ShselaskyMODULE_LICENSE("GPL v2"); 346253544ShselaskyMODULE_ALIAS("spi:el15203000"); 347253544Shselasky