Commit | Line | Data |
---|---|---|
9e50d5fb OK |
1 | // SPDX-License-Identifier: GPL-2.0 |
2 | // Copyright (c) 2018 Crane Merchandising Systems. All rights reserved. | |
3 | // Copyright (C) 2018 Oleh Kravchenko <oleg@kaa.org.ua> | |
4 | ||
5 | #include <linux/delay.h> | |
6 | #include <linux/leds.h> | |
7 | #include <linux/module.h> | |
8 | #include <linux/of_device.h> | |
9 | #include <linux/spi/spi.h> | |
10 | #include <linux/workqueue.h> | |
9e50d5fb OK |
11 | |
12 | /* | |
13 | * CR0014114 SPI protocol descrtiption: | |
14 | * +----+-----------------------------------+----+ | |
15 | * | CMD| BRIGHTNESS |CRC | | |
16 | * +----+-----------------------------------+----+ | |
17 | * | | LED0| LED1| LED2| LED3| LED4| LED5| | | |
18 | * | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | | |
19 | * | |R|G|B|R|G|B|R|G|B|R|G|B|R|G|B|R|G|B| | | |
20 | * | 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 1 | | |
21 | * | |1|1|1|1|1|1|1|1|1|1|1|1|1|1|1|1|1|1| | | |
22 | * | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | | |
23 | * | | 18 | | | |
24 | * +----+-----------------------------------+----+ | |
25 | * | 20 | | |
26 | * +---------------------------------------------+ | |
27 | * | |
28 | * PS: Boards can be connected to the chain: | |
29 | * SPI -> board0 -> board1 -> board2 .. | |
30 | */ | |
31 | ||
32 | /* CR0014114 SPI commands */ | |
33 | #define CR_SET_BRIGHTNESS 0x80 | |
34 | #define CR_INIT_REENUMERATE 0x81 | |
35 | #define CR_NEXT_REENUMERATE 0x82 | |
36 | ||
37 | /* CR0014114 default settings */ | |
38 | #define CR_MAX_BRIGHTNESS GENMASK(6, 0) | |
39 | #define CR_FW_DELAY_MSEC 10 | |
40 | #define CR_RECOUNT_DELAY (HZ * 3600) | |
41 | ||
889003c2 JA |
42 | #define CR_DEV_NAME "cr0014114" |
43 | ||
9e50d5fb | 44 | struct cr0014114_led { |
9e50d5fb OK |
45 | struct cr0014114 *priv; |
46 | struct led_classdev ldev; | |
47 | u8 brightness; | |
48 | }; | |
49 | ||
50 | struct cr0014114 { | |
51 | bool do_recount; | |
52 | size_t count; | |
53 | struct delayed_work work; | |
54 | struct device *dev; | |
55 | struct mutex lock; | |
56 | struct spi_device *spi; | |
57 | u8 *buf; | |
58 | unsigned long delay; | |
59 | struct cr0014114_led leds[]; | |
60 | }; | |
61 | ||
62 | static void cr0014114_calc_crc(u8 *buf, const size_t len) | |
63 | { | |
64 | size_t i; | |
65 | u8 crc; | |
66 | ||
67 | for (i = 1, crc = 1; i < len - 1; i++) | |
68 | crc += buf[i]; | |
69 | crc |= BIT(7); | |
70 | ||
71 | /* special case when CRC matches the SPI commands */ | |
72 | if (crc == CR_SET_BRIGHTNESS || | |
73 | crc == CR_INIT_REENUMERATE || | |
74 | crc == CR_NEXT_REENUMERATE) | |
75 | crc = 0xfe; | |
76 | ||
77 | buf[len - 1] = crc; | |
78 | } | |
79 | ||
80 | static int cr0014114_recount(struct cr0014114 *priv) | |
81 | { | |
82 | int ret; | |
83 | size_t i; | |
84 | u8 cmd; | |
85 | ||
86 | dev_dbg(priv->dev, "LEDs recount is started\n"); | |
87 | ||
88 | cmd = CR_INIT_REENUMERATE; | |
89 | ret = spi_write(priv->spi, &cmd, sizeof(cmd)); | |
90 | if (ret) | |
91 | goto err; | |
92 | ||
93 | cmd = CR_NEXT_REENUMERATE; | |
94 | for (i = 0; i < priv->count; i++) { | |
95 | msleep(CR_FW_DELAY_MSEC); | |
96 | ||
97 | ret = spi_write(priv->spi, &cmd, sizeof(cmd)); | |
98 | if (ret) | |
99 | goto err; | |
100 | } | |
101 | ||
102 | err: | |
103 | dev_dbg(priv->dev, "LEDs recount is finished\n"); | |
104 | ||
105 | if (ret) | |
106 | dev_err(priv->dev, "with error %d", ret); | |
107 | ||
108 | return ret; | |
109 | } | |
110 | ||
111 | static int cr0014114_sync(struct cr0014114 *priv) | |
112 | { | |
113 | int ret; | |
114 | size_t i; | |
115 | unsigned long udelay, now = jiffies; | |
116 | ||
117 | /* to avoid SPI mistiming with firmware we should wait some time */ | |
118 | if (time_after(priv->delay, now)) { | |
119 | udelay = jiffies_to_usecs(priv->delay - now); | |
120 | usleep_range(udelay, udelay + 1); | |
121 | } | |
122 | ||
123 | if (unlikely(priv->do_recount)) { | |
124 | ret = cr0014114_recount(priv); | |
125 | if (ret) | |
126 | goto err; | |
127 | ||
128 | priv->do_recount = false; | |
129 | msleep(CR_FW_DELAY_MSEC); | |
130 | } | |
131 | ||
132 | priv->buf[0] = CR_SET_BRIGHTNESS; | |
133 | for (i = 0; i < priv->count; i++) | |
134 | priv->buf[i + 1] = priv->leds[i].brightness; | |
135 | cr0014114_calc_crc(priv->buf, priv->count + 2); | |
136 | ret = spi_write(priv->spi, priv->buf, priv->count + 2); | |
137 | ||
138 | err: | |
139 | priv->delay = jiffies + msecs_to_jiffies(CR_FW_DELAY_MSEC); | |
140 | ||
141 | return ret; | |
142 | } | |
143 | ||
144 | static void cr0014114_recount_work(struct work_struct *work) | |
145 | { | |
146 | int ret; | |
147 | struct cr0014114 *priv = container_of(work, | |
148 | struct cr0014114, | |
149 | work.work); | |
150 | ||
151 | mutex_lock(&priv->lock); | |
152 | priv->do_recount = true; | |
153 | ret = cr0014114_sync(priv); | |
154 | mutex_unlock(&priv->lock); | |
155 | ||
156 | if (ret) | |
157 | dev_warn(priv->dev, "sync of LEDs failed %d\n", ret); | |
158 | ||
159 | schedule_delayed_work(&priv->work, CR_RECOUNT_DELAY); | |
160 | } | |
161 | ||
162 | static int cr0014114_set_sync(struct led_classdev *ldev, | |
163 | enum led_brightness brightness) | |
164 | { | |
165 | int ret; | |
166 | struct cr0014114_led *led = container_of(ldev, | |
167 | struct cr0014114_led, | |
168 | ldev); | |
169 | ||
889003c2 | 170 | dev_dbg(led->priv->dev, "Set brightness to %d\n", brightness); |
9e50d5fb OK |
171 | |
172 | mutex_lock(&led->priv->lock); | |
173 | led->brightness = (u8)brightness; | |
174 | ret = cr0014114_sync(led->priv); | |
175 | mutex_unlock(&led->priv->lock); | |
176 | ||
177 | return ret; | |
178 | } | |
179 | ||
180 | static int cr0014114_probe_dt(struct cr0014114 *priv) | |
181 | { | |
182 | size_t i = 0; | |
183 | struct cr0014114_led *led; | |
184 | struct fwnode_handle *child; | |
889003c2 | 185 | struct led_init_data init_data = {}; |
9e50d5fb | 186 | int ret; |
9e50d5fb OK |
187 | |
188 | device_for_each_child_node(priv->dev, child) { | |
9e50d5fb OK |
189 | led = &priv->leds[i]; |
190 | ||
9e50d5fb | 191 | led->priv = priv; |
9e50d5fb OK |
192 | led->ldev.max_brightness = CR_MAX_BRIGHTNESS; |
193 | led->ldev.brightness_set_blocking = cr0014114_set_sync; | |
194 | ||
889003c2 JA |
195 | init_data.fwnode = child; |
196 | init_data.devicename = CR_DEV_NAME; | |
197 | init_data.default_label = ":"; | |
198 | ||
199 | ret = devm_led_classdev_register_ext(priv->dev, &led->ldev, | |
200 | &init_data); | |
9e50d5fb OK |
201 | if (ret) { |
202 | dev_err(priv->dev, | |
889003c2 | 203 | "failed to register LED device, err %d", ret); |
9e50d5fb OK |
204 | fwnode_handle_put(child); |
205 | return ret; | |
206 | } | |
207 | ||
9e50d5fb OK |
208 | i++; |
209 | } | |
210 | ||
211 | return 0; | |
212 | } | |
213 | ||
214 | static int cr0014114_probe(struct spi_device *spi) | |
215 | { | |
216 | struct cr0014114 *priv; | |
217 | size_t count; | |
218 | int ret; | |
219 | ||
220 | count = device_get_child_node_count(&spi->dev); | |
221 | if (!count) { | |
222 | dev_err(&spi->dev, "LEDs are not defined in device tree!"); | |
223 | return -ENODEV; | |
224 | } | |
225 | ||
f3278e3f | 226 | priv = devm_kzalloc(&spi->dev, struct_size(priv, leds, count), |
9e50d5fb OK |
227 | GFP_KERNEL); |
228 | if (!priv) | |
229 | return -ENOMEM; | |
230 | ||
231 | priv->buf = devm_kzalloc(&spi->dev, count + 2, GFP_KERNEL); | |
232 | if (!priv->buf) | |
233 | return -ENOMEM; | |
234 | ||
235 | mutex_init(&priv->lock); | |
236 | INIT_DELAYED_WORK(&priv->work, cr0014114_recount_work); | |
237 | priv->count = count; | |
238 | priv->dev = &spi->dev; | |
239 | priv->spi = spi; | |
240 | priv->delay = jiffies - | |
241 | msecs_to_jiffies(CR_FW_DELAY_MSEC); | |
242 | ||
243 | priv->do_recount = true; | |
244 | ret = cr0014114_sync(priv); | |
245 | if (ret) { | |
246 | dev_err(priv->dev, "first recount failed %d\n", ret); | |
247 | return ret; | |
248 | } | |
249 | ||
250 | priv->do_recount = true; | |
251 | ret = cr0014114_sync(priv); | |
252 | if (ret) { | |
253 | dev_err(priv->dev, "second recount failed %d\n", ret); | |
254 | return ret; | |
255 | } | |
256 | ||
257 | ret = cr0014114_probe_dt(priv); | |
258 | if (ret) | |
259 | return ret; | |
260 | ||
261 | /* setup recount work to workaround buggy firmware */ | |
262 | schedule_delayed_work(&priv->work, CR_RECOUNT_DELAY); | |
263 | ||
264 | spi_set_drvdata(spi, priv); | |
265 | ||
266 | return 0; | |
267 | } | |
268 | ||
a0386bba | 269 | static void cr0014114_remove(struct spi_device *spi) |
9e50d5fb OK |
270 | { |
271 | struct cr0014114 *priv = spi_get_drvdata(spi); | |
272 | ||
273 | cancel_delayed_work_sync(&priv->work); | |
274 | mutex_destroy(&priv->lock); | |
9e50d5fb OK |
275 | } |
276 | ||
277 | static const struct of_device_id cr0014114_dt_ids[] = { | |
278 | { .compatible = "crane,cr0014114", }, | |
279 | {}, | |
280 | }; | |
281 | ||
282 | MODULE_DEVICE_TABLE(of, cr0014114_dt_ids); | |
283 | ||
284 | static struct spi_driver cr0014114_driver = { | |
285 | .probe = cr0014114_probe, | |
286 | .remove = cr0014114_remove, | |
287 | .driver = { | |
288 | .name = KBUILD_MODNAME, | |
289 | .of_match_table = cr0014114_dt_ids, | |
290 | }, | |
291 | }; | |
292 | ||
293 | module_spi_driver(cr0014114_driver); | |
294 | ||
295 | MODULE_AUTHOR("Oleh Kravchenko <oleg@kaa.org.ua>"); | |
296 | MODULE_DESCRIPTION("cr0014114 LED driver"); | |
297 | MODULE_LICENSE("GPL v2"); | |
298 | MODULE_ALIAS("spi:cr0014114"); |