bmips: leds-sercomm-msp430: improve driver
[openwrt/staging/dedeckeh.git] / target / linux / bmips / files / drivers / leds / leds-sercomm-msp430.c
1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3 * Driver for Sercomm MSP430G2513 LEDs.
4 *
5 * Copyright 2023 Álvaro Fernández Rojas <noltari@gmail.com>
6 */
7
8 #include <linux/delay.h>
9 #include <linux/leds.h>
10 #include <linux/module.h>
11 #include <linux/of_device.h>
12 #include <linux/spi/spi.h>
13 #include "leds.h"
14
15 /*
16 * MSP430G2513 SPI protocol description:
17 * +----+----+----+----+----+----+
18 * | b1 | b2 | b3 | b4 | b5 | b6 |
19 * +----+----+----+----+----+----+
20 * 6 bytes TX & RX per transaction.
21 *
22 * LEDs:
23 * MSP430G2513 can control up to 9 LEDs.
24 * b1: LED ID [1,9]
25 * b2: LED function
26 * b3-b6: LED function parameters
27 *
28 * LED functions:
29 * [0] Off
30 * [1] On
31 * [2] Flash
32 * - b4: delay (x 6ms)
33 * - b5: repeat (0 = infinite)
34 * [3] Pulse
35 * - b3: delay (x 6ms)
36 * - b4: blink while pulsing? (unknown)
37 * - b5: repeat (0 = infinite)
38 * [4] Pulse On
39 * - b3: delay (x 6ms)
40 * - b4: blink while pulsing? (unknown)
41 * - b5: repeat (0 = infinite)
42 * [5] Pulse Off
43 * - b3: delay (x 6ms)
44 * - b4: blink while pulsing? (unknown)
45 * - b5: repeat (0 = infinite)
46 * [6] Level
47 * - b3: brightness [0,4]
48 *
49 * MCU Commands (b1 = 0x55):
50 * [0x0a] FW upgrade data
51 * - b3: Data size (usually 0x40), which is appended to TX & RX.
52 * [0x31] Get MCU version? (unknown)
53 * [0x68] Get MCU work mode
54 * [0xa5] Start FW upgrade
55 * [0xf0] End FW upgrade
56 */
57
58 #define MSP430_CMD_BYTES 6
59 #define MSP430_CMD_MCU 0x55
60 #define MSP430_MCU_WM 0x68
61
62 #define MSP430_LED_MIN_ID 1
63 #define MSP430_LED_MAX_ID 9
64
65 #define MSP430_LED_OFF 0
66 #define MSP430_LED_ON 1
67 #define MSP430_LED_FLASH 2
68 #define MSP430_LED_PULSE 3
69 #define MSP430_LED_PULSE_ON 4
70 #define MSP430_LED_PULSE_OFF 5
71 #define MSP430_LED_LEVEL 6
72
73 #define MSP430_LED_BLINK_DEF 500
74 #define MSP430_LED_BLINK_MASK 0xff
75 #define MSP430_LED_BLINK_MS 6
76 #define MSP430_LED_BLINK_MAX (MSP430_LED_BLINK_MS * \
77 MSP430_LED_BLINK_MASK)
78
79 #define MSP430_LED_BRIGHTNESS_MAX 5
80 #define MSP430_LED_REPEAT_MAX 0xff
81
82 /**
83 * struct msp430_led - state container for Sercomm MSP430 based LEDs
84 * @cdev: LED class device for this LED
85 * @spi: spi resource
86 * @id: LED ID
87 */
88 struct msp430_led {
89 struct led_classdev cdev;
90 struct spi_device *spi;
91 u8 id;
92 };
93
94 static inline int msp430_cmd(struct spi_device *spi, u8 tx[MSP430_CMD_BYTES],
95 u8 rx[MSP430_CMD_BYTES])
96 {
97 struct device *dev = &spi->dev;
98 int rc;
99
100 memset(rx, 0, MSP430_CMD_BYTES);
101
102 rc = spi_write_then_read(spi, tx, MSP430_CMD_BYTES,
103 rx, MSP430_CMD_BYTES);
104 if (rc)
105 dev_err(dev, "spi error\n");
106
107 dev_dbg(dev, "msp430_cmd: [%02x %02x %02x %02x %02x %02x]"
108 " -> [%02x %02x %02x %02x %02x %02x]",
109 tx[0], tx[1], tx[2], tx[3], tx[4], tx[5],
110 rx[0], rx[1], rx[2], rx[3], rx[4], rx[5]);
111
112 return rc;
113 }
114
115 static unsigned long msp430_blink_delay(unsigned long delay)
116 {
117 unsigned long msp430_delay;
118
119 msp430_delay = delay + MSP430_LED_BLINK_MS / 2;
120 msp430_delay = msp430_delay / MSP430_LED_BLINK_MS;
121 if (msp430_delay == 0)
122 msp430_delay = 1;
123
124 return msp430_delay;
125 }
126
127 static int msp430_blink_set(struct led_classdev *led_cdev,
128 unsigned long *delay_on,
129 unsigned long *delay_off)
130 {
131 struct msp430_led *led =
132 container_of(led_cdev, struct msp430_led, cdev);
133 u8 tx[MSP430_CMD_BYTES] = {led->id, MSP430_LED_FLASH, 0, 0, 0, 0};
134 u8 rx[MSP430_CMD_BYTES];
135 unsigned long delay;
136
137 if (!*delay_on)
138 *delay_on = MSP430_LED_BLINK_DEF;
139 if (!*delay_off)
140 *delay_off = MSP430_LED_BLINK_DEF;
141
142 delay = msp430_blink_delay(*delay_on);
143 if (delay != msp430_blink_delay(*delay_off)) {
144 dev_dbg(led_cdev->dev,
145 "fallback to soft blinking (delay_on != delay_off)\n");
146 return -EINVAL;
147 }
148
149 if (delay > MSP430_LED_BLINK_MASK) {
150 dev_dbg(led_cdev->dev,
151 "fallback to soft blinking (delay > %ums)\n",
152 MSP430_LED_BLINK_MAX);
153 return -EINVAL;
154 }
155
156 tx[3] = delay;
157
158 return msp430_cmd(led->spi, tx, rx);
159 }
160
161 static int msp430_brightness_set(struct led_classdev *led_cdev,
162 enum led_brightness brightness)
163 {
164 struct msp430_led *led =
165 container_of(led_cdev, struct msp430_led, cdev);
166 u8 tx[MSP430_CMD_BYTES] = {led->id, 0, 0, 0, 0, 0};
167 u8 rx[MSP430_CMD_BYTES];
168 u8 val = (u8) brightness;
169
170 switch (val)
171 {
172 case LED_OFF:
173 tx[1] = MSP430_LED_OFF;
174 break;
175 case MSP430_LED_BRIGHTNESS_MAX:
176 tx[1] = MSP430_LED_ON;
177 break;
178 default:
179 tx[1] = MSP430_LED_LEVEL;
180 tx[2] = val - 1;
181 break;
182 }
183
184 return msp430_cmd(led->spi, tx, rx);
185 }
186
187 static int msp430_pattern_clear(struct led_classdev *ldev)
188 {
189 msp430_brightness_set(ldev, LED_OFF);
190
191 return 0;
192 }
193
194 static int msp430_pattern_set(struct led_classdev *led_cdev,
195 struct led_pattern *pattern,
196 u32 len, int repeat)
197 {
198 struct msp430_led *led =
199 container_of(led_cdev, struct msp430_led, cdev);
200 u8 tx[MSP430_CMD_BYTES] = {led->id, 0, 0, 0, 0, 0};
201 u8 rx[MSP430_CMD_BYTES];
202 unsigned long delay0;
203 unsigned long delay1;
204 int rc;
205
206 if (len != 2 ||
207 repeat > MSP430_LED_REPEAT_MAX ||
208 pattern[0].delta_t > MSP430_LED_BLINK_MAX ||
209 pattern[1].delta_t > MSP430_LED_BLINK_MAX)
210 return -EINVAL;
211
212 delay0 = msp430_blink_delay(pattern[0].delta_t);
213 delay1 = msp430_blink_delay(pattern[1].delta_t);
214
215 /* Infinite pattern */
216 if (repeat < 0)
217 repeat = 0;
218
219 /* Pulse: <off> <delay> <max> <delay> */
220 if (delay0 == delay1 &&
221 pattern[0].brightness == LED_OFF &&
222 pattern[1].brightness == MSP430_LED_BRIGHTNESS_MAX)
223 {
224 tx[1] = MSP430_LED_PULSE;
225 tx[2] = delay0;
226 tx[4] = (u8) repeat;
227 }
228
229 /* Pulse On: <off> <delay> <max> <0ms> */
230 if (pattern[0].delta_t != 0 &&
231 pattern[1].delta_t == 0 &&
232 pattern[0].brightness == LED_OFF &&
233 pattern[1].brightness == MSP430_LED_BRIGHTNESS_MAX) {
234 tx[1] = MSP430_LED_PULSE_ON;
235 tx[2] = delay0;
236 tx[4] = (u8) repeat;
237 }
238
239 /* Pulse Off: <max> <delay> <off> <0ms> */
240 if (pattern[0].delta_t != 0 &&
241 pattern[1].delta_t == 0 &&
242 pattern[0].brightness == MSP430_LED_BRIGHTNESS_MAX &&
243 pattern[1].brightness == LED_OFF) {
244 tx[1] = MSP430_LED_PULSE_OFF;
245 tx[2] = delay0;
246 tx[4] = (u8) repeat;
247 }
248
249 if (!tx[1])
250 return -EINVAL;
251
252 rc = msp430_cmd(led->spi, tx, rx);
253 if (rc)
254 return rc;
255
256 return 0;
257 }
258
259 static int msp430_led(struct spi_device *spi, struct device_node *nc, u8 id)
260 {
261 struct device *dev = &spi->dev;
262 struct led_init_data init_data = {};
263 struct msp430_led *led;
264 enum led_default_state state;
265 int rc;
266
267 led = devm_kzalloc(dev, sizeof(*led), GFP_KERNEL);
268 if (!led)
269 return -ENOMEM;
270
271 led->id = id;
272 led->spi = spi;
273
274 init_data.fwnode = of_fwnode_handle(nc);
275
276 state = led_init_default_state_get(init_data.fwnode);
277 switch (state) {
278 case LEDS_DEFSTATE_ON:
279 led->cdev.brightness = MSP430_LED_BRIGHTNESS_MAX;
280 break;
281 default:
282 led->cdev.brightness = LED_OFF;
283 break;
284 }
285
286 msp430_brightness_set(&led->cdev, led->cdev.brightness);
287
288 led->cdev.blink_set = msp430_blink_set;
289 led->cdev.brightness_set_blocking = msp430_brightness_set;
290 led->cdev.max_brightness = MSP430_LED_BRIGHTNESS_MAX;
291 led->cdev.pattern_clear = msp430_pattern_clear;
292 led->cdev.pattern_set = msp430_pattern_set;
293
294 rc = devm_led_classdev_register_ext(dev, &led->cdev, &init_data);
295 if (rc < 0)
296 return rc;
297
298 dev_dbg(dev, "registered LED %s\n", led->cdev.name);
299
300 return 0;
301 }
302
303 static inline int msp430_check_workmode(struct spi_device *spi)
304 {
305 struct device *dev = &spi->dev;
306 u8 tx[MSP430_CMD_BYTES] = {MSP430_CMD_MCU, MSP430_MCU_WM, 0, 0, 0, 0};
307 u8 rx[MSP430_CMD_BYTES];
308 int rc;
309
310 rc = msp430_cmd(spi, tx, rx);
311 if (rc)
312 return rc;
313
314 if ((rx[3] == 0xA5 && rx[4] == 'Z') ||
315 (rx[4] == 0xA5 && rx[5] == 'Z') ||
316 (rx[4] == '\b' && rx[5] == '\n')) {
317 dev_err(dev, "invalid workmode: "
318 "[%02x %02x %02x %02x %02x %02x]\n",
319 rx[0], rx[1], rx[2], rx[3], rx[4], rx[5]);
320 return -EINVAL;
321 }
322
323 return 0;
324 }
325
326 static int msp430_leds_probe(struct spi_device *spi)
327 {
328 struct device *dev = &spi->dev;
329 struct device_node *np = dev_of_node(dev);
330 struct device_node *child;
331 int rc;
332
333 rc = msp430_check_workmode(spi);
334 if (rc)
335 return rc;
336
337 for_each_available_child_of_node(np, child) {
338 u32 reg;
339
340 if (of_property_read_u32(child, "reg", &reg))
341 continue;
342
343 if (reg < MSP430_LED_MIN_ID || reg > MSP430_LED_MAX_ID) {
344 dev_err(dev, "invalid LED (%u) [%d, %d]\n", reg,
345 MSP430_LED_MIN_ID, MSP430_LED_MAX_ID);
346 continue;
347 }
348
349 rc = msp430_led(spi, child, reg);
350 if (rc < 0) {
351 of_node_put(child);
352 return rc;
353 }
354 }
355
356 return 0;
357 }
358
359 static const struct of_device_id msp430_leds_of_match[] = {
360 { .compatible = "sercomm,msp430-leds", },
361 { /* sentinel */ },
362 };
363 MODULE_DEVICE_TABLE(of, msp430_leds_of_match);
364
365 static const struct spi_device_id msp430_leds_id_table[] = {
366 { "msp430-leds", 0 },
367 { /* sentinel */ }
368 };
369 MODULE_DEVICE_TABLE(spi, msp430_leds_id_table);
370
371 static struct spi_driver msp430_leds_driver = {
372 .driver = {
373 .name = KBUILD_MODNAME,
374 .of_match_table = msp430_leds_of_match,
375 },
376 .id_table = msp430_leds_id_table,
377 .probe = msp430_leds_probe,
378 };
379 module_spi_driver(msp430_leds_driver);
380
381 MODULE_AUTHOR("Álvaro Fernández Rojas <noltari@gmail.com>");
382 MODULE_DESCRIPTION("LED driver for Sercomm MSP430 controllers");
383 MODULE_LICENSE("GPL v2");
384 MODULE_ALIAS("platform:leds-sercomm-msp430");