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

leds: spi-byte: add single byte SPI LED driver

This driver adds support for simple SPI based LED controller which use
only one byte for setting the brightness.

Signed-off-by: Christian Mauderer <oss@c-mauderer.de>
Acked-by: Pavel Machek <pavel@ucw.cz>
Signed-off-by: Jacek Anaszewski <jacek.anaszewski@gmail.com>

authored by

Christian Mauderer and committed by
Jacek Anaszewski
e9a804d7 e7c787cb

+172
+10
drivers/leds/Kconfig
··· 783 783 To compile this driver as a module, choose M here: the module 784 784 will be called leds-nic78bx. 785 785 786 + config LEDS_SPI_BYTE 787 + tristate "LED support for SPI LED controller with a single byte" 788 + depends on LEDS_CLASS 789 + depends on SPI 790 + depends on OF 791 + help 792 + This option enables support for LED controller which use a single byte 793 + for controlling the brightness. Currently the following controller is 794 + supported: Ubiquiti airCube ISP microcontroller based LED controller. 795 + 786 796 comment "LED Triggers" 787 797 source "drivers/leds/trigger/Kconfig" 788 798
+1
drivers/leds/Makefile
··· 77 77 obj-$(CONFIG_LEDS_MLXCPLD) += leds-mlxcpld.o 78 78 obj-$(CONFIG_LEDS_MLXREG) += leds-mlxreg.o 79 79 obj-$(CONFIG_LEDS_NIC78BX) += leds-nic78bx.o 80 + obj-$(CONFIG_LEDS_SPI_BYTE) += leds-spi-byte.o 80 81 obj-$(CONFIG_LEDS_MT6323) += leds-mt6323.o 81 82 obj-$(CONFIG_LEDS_LM3692X) += leds-lm3692x.o 82 83 obj-$(CONFIG_LEDS_SC27XX_BLTC) += leds-sc27xx-bltc.o
+161
drivers/leds/leds-spi-byte.c
··· 1 + // SPDX-License-Identifier: GPL-2.0 2 + // Copyright (c) 2019 Christian Mauderer <oss@c-mauderer.de> 3 + 4 + /* 5 + * The driver supports controllers with a very simple SPI protocol: 6 + * - one LED is controlled by a single byte on MOSI 7 + * - the value of the byte gives the brightness between two values (lowest to 8 + * highest) 9 + * - no return value is necessary (no MISO signal) 10 + * 11 + * The value for minimum and maximum brightness depends on the device 12 + * (compatible string). 13 + * 14 + * Supported devices: 15 + * - "ubnt,acb-spi-led": Microcontroller (SONiX 8F26E611LA) based device used 16 + * for example in Ubiquiti airCube ISP. Reverse engineered protocol for this 17 + * controller: 18 + * * Higher two bits set a mode. Lower six bits are a parameter. 19 + * * Mode: 00 -> set brightness between 0x00 (min) and 0x3F (max) 20 + * * Mode: 01 -> pulsing pattern (min -> max -> min) with an interval. From 21 + * some tests, the period is about (50ms + 102ms * parameter). There is a 22 + * slightly different pattern starting from 0x10 (longer gap between the 23 + * pulses) but the time still follows that calculation. 24 + * * Mode: 10 -> same as 01 but with only a ramp from min to max. Again a 25 + * slight jump in the pattern at 0x10. 26 + * * Mode: 11 -> blinking (off -> 25% -> off -> 25% -> ...) with a period of 27 + * (105ms * parameter) 28 + * NOTE: This driver currently only supports mode 00. 29 + */ 30 + 31 + #include <linux/leds.h> 32 + #include <linux/module.h> 33 + #include <linux/of_device.h> 34 + #include <linux/spi/spi.h> 35 + #include <linux/mutex.h> 36 + #include <uapi/linux/uleds.h> 37 + 38 + struct spi_byte_chipdef { 39 + /* SPI byte that will be send to switch the LED off */ 40 + u8 off_value; 41 + /* SPI byte that will be send to switch the LED to maximum brightness */ 42 + u8 max_value; 43 + }; 44 + 45 + struct spi_byte_led { 46 + struct led_classdev ldev; 47 + struct spi_device *spi; 48 + char name[LED_MAX_NAME_SIZE]; 49 + struct mutex mutex; 50 + const struct spi_byte_chipdef *cdef; 51 + }; 52 + 53 + static const struct spi_byte_chipdef ubnt_acb_spi_led_cdef = { 54 + .off_value = 0x0, 55 + .max_value = 0x3F, 56 + }; 57 + 58 + static const struct of_device_id spi_byte_dt_ids[] = { 59 + { .compatible = "ubnt,acb-spi-led", .data = &ubnt_acb_spi_led_cdef }, 60 + {}, 61 + }; 62 + 63 + MODULE_DEVICE_TABLE(of, spi_byte_dt_ids); 64 + 65 + static int spi_byte_brightness_set_blocking(struct led_classdev *dev, 66 + enum led_brightness brightness) 67 + { 68 + struct spi_byte_led *led = container_of(dev, struct spi_byte_led, ldev); 69 + u8 value; 70 + int ret; 71 + 72 + value = (u8) brightness + led->cdef->off_value; 73 + 74 + mutex_lock(&led->mutex); 75 + ret = spi_write(led->spi, &value, sizeof(value)); 76 + mutex_unlock(&led->mutex); 77 + 78 + return ret; 79 + } 80 + 81 + static int spi_byte_probe(struct spi_device *spi) 82 + { 83 + const struct of_device_id *of_dev_id; 84 + struct device_node *child; 85 + struct device *dev = &spi->dev; 86 + struct spi_byte_led *led; 87 + const char *name = "leds-spi-byte::"; 88 + const char *state; 89 + int ret; 90 + 91 + of_dev_id = of_match_device(spi_byte_dt_ids, dev); 92 + if (!of_dev_id) 93 + return -EINVAL; 94 + 95 + if (of_get_child_count(dev->of_node) != 1) { 96 + dev_err(dev, "Device must have exactly one LED sub-node."); 97 + return -EINVAL; 98 + } 99 + child = of_get_next_child(dev->of_node, NULL); 100 + 101 + led = devm_kzalloc(dev, sizeof(*led), GFP_KERNEL); 102 + if (!led) 103 + return -ENOMEM; 104 + 105 + of_property_read_string(child, "label", &name); 106 + strlcpy(led->name, name, sizeof(led->name)); 107 + led->spi = spi; 108 + mutex_init(&led->mutex); 109 + led->cdef = of_dev_id->data; 110 + led->ldev.name = led->name; 111 + led->ldev.brightness = LED_OFF; 112 + led->ldev.max_brightness = led->cdef->max_value - led->cdef->off_value; 113 + led->ldev.brightness_set_blocking = spi_byte_brightness_set_blocking; 114 + 115 + state = of_get_property(child, "default-state", NULL); 116 + if (state) { 117 + if (!strcmp(state, "on")) { 118 + led->ldev.brightness = led->ldev.max_brightness; 119 + } else if (strcmp(state, "off")) { 120 + /* all other cases except "off" */ 121 + dev_err(dev, "default-state can only be 'on' or 'off'"); 122 + return -EINVAL; 123 + } 124 + } 125 + spi_byte_brightness_set_blocking(&led->ldev, 126 + led->ldev.brightness); 127 + 128 + ret = devm_led_classdev_register(&spi->dev, &led->ldev); 129 + if (ret) { 130 + mutex_destroy(&led->mutex); 131 + return ret; 132 + } 133 + spi_set_drvdata(spi, led); 134 + 135 + return 0; 136 + } 137 + 138 + static int spi_byte_remove(struct spi_device *spi) 139 + { 140 + struct spi_byte_led *led = spi_get_drvdata(spi); 141 + 142 + mutex_destroy(&led->mutex); 143 + 144 + return 0; 145 + } 146 + 147 + static struct spi_driver spi_byte_driver = { 148 + .probe = spi_byte_probe, 149 + .remove = spi_byte_remove, 150 + .driver = { 151 + .name = KBUILD_MODNAME, 152 + .of_match_table = spi_byte_dt_ids, 153 + }, 154 + }; 155 + 156 + module_spi_driver(spi_byte_driver); 157 + 158 + MODULE_AUTHOR("Christian Mauderer <oss@c-mauderer.de>"); 159 + MODULE_DESCRIPTION("single byte SPI LED driver"); 160 + MODULE_LICENSE("GPL v2"); 161 + MODULE_ALIAS("spi:leds-spi-byte");