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