diff options
author | Christian Mauderer <oss@c-mauderer.de> | 2019-05-13 15:33:07 -0400 |
---|---|---|
committer | Jacek Anaszewski <jacek.anaszewski@gmail.com> | 2019-05-24 16:19:43 -0400 |
commit | e9a804d7a428432d8ad0bc7984f459daf0cc4da3 (patch) | |
tree | 64d334a8c47bb387ae68720c5ecddbab9b14ec64 | |
parent | e7c787cb2697ee4c101109bf21b4fc397cea150b (diff) |
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>
-rw-r--r-- | drivers/leds/Kconfig | 10 | ||||
-rw-r--r-- | drivers/leds/Makefile | 1 | ||||
-rw-r--r-- | drivers/leds/leds-spi-byte.c | 161 |
3 files changed, 172 insertions, 0 deletions
diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig index 71be87bdb926..35fcddb7ac2a 100644 --- a/drivers/leds/Kconfig +++ b/drivers/leds/Kconfig | |||
@@ -783,6 +783,16 @@ config LEDS_NIC78BX | |||
783 | To compile this driver as a module, choose M here: the module | 783 | To compile this driver as a module, choose M here: the module |
784 | will be called leds-nic78bx. | 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 | comment "LED Triggers" | 796 | comment "LED Triggers" |
787 | source "drivers/leds/trigger/Kconfig" | 797 | source "drivers/leds/trigger/Kconfig" |
788 | 798 | ||
diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile index 1e9702ebffee..2e8bff590700 100644 --- a/drivers/leds/Makefile +++ b/drivers/leds/Makefile | |||
@@ -77,6 +77,7 @@ obj-$(CONFIG_LEDS_PM8058) += leds-pm8058.o | |||
77 | obj-$(CONFIG_LEDS_MLXCPLD) += leds-mlxcpld.o | 77 | obj-$(CONFIG_LEDS_MLXCPLD) += leds-mlxcpld.o |
78 | obj-$(CONFIG_LEDS_MLXREG) += leds-mlxreg.o | 78 | obj-$(CONFIG_LEDS_MLXREG) += leds-mlxreg.o |
79 | obj-$(CONFIG_LEDS_NIC78BX) += leds-nic78bx.o | 79 | obj-$(CONFIG_LEDS_NIC78BX) += leds-nic78bx.o |
80 | obj-$(CONFIG_LEDS_SPI_BYTE) += leds-spi-byte.o | ||
80 | obj-$(CONFIG_LEDS_MT6323) += leds-mt6323.o | 81 | obj-$(CONFIG_LEDS_MT6323) += leds-mt6323.o |
81 | obj-$(CONFIG_LEDS_LM3692X) += leds-lm3692x.o | 82 | obj-$(CONFIG_LEDS_LM3692X) += leds-lm3692x.o |
82 | obj-$(CONFIG_LEDS_SC27XX_BLTC) += leds-sc27xx-bltc.o | 83 | obj-$(CONFIG_LEDS_SC27XX_BLTC) += leds-sc27xx-bltc.o |
diff --git a/drivers/leds/leds-spi-byte.c b/drivers/leds/leds-spi-byte.c new file mode 100644 index 000000000000..b231b563b7bb --- /dev/null +++ b/drivers/leds/leds-spi-byte.c | |||
@@ -0,0 +1,161 @@ | |||
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"); | ||