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/mod_devicetable.h> |
8 | #include <linux/module.h> |
9 | #include <linux/spi/spi.h> |
10 | #include <linux/workqueue.h> |
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 | |
42 | #define CR_DEV_NAME "cr0014114" |
43 | |
44 | struct cr0014114_led { |
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[] __counted_by(count); |
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(spi: priv->spi, buf: &cmd, len: 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(spi: priv->spi, buf: &cmd, len: 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(j: priv->delay - now); |
120 | usleep_range(min: udelay, max: 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(buf: priv->buf, len: priv->count + 2); |
136 | ret = spi_write(spi: priv->spi, buf: priv->buf, len: 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(lock: &priv->lock); |
155 | |
156 | if (ret) |
157 | dev_warn(priv->dev, "sync of LEDs failed %d\n" , ret); |
158 | |
159 | schedule_delayed_work(dwork: &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 | |
170 | dev_dbg(led->priv->dev, "Set brightness to %d\n" , brightness); |
171 | |
172 | mutex_lock(&led->priv->lock); |
173 | led->brightness = (u8)brightness; |
174 | ret = cr0014114_sync(priv: led->priv); |
175 | mutex_unlock(lock: &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; |
185 | struct led_init_data init_data = {}; |
186 | int ret; |
187 | |
188 | device_for_each_child_node(priv->dev, child) { |
189 | led = &priv->leds[i]; |
190 | |
191 | led->priv = priv; |
192 | led->ldev.max_brightness = CR_MAX_BRIGHTNESS; |
193 | led->ldev.brightness_set_blocking = cr0014114_set_sync; |
194 | |
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(parent: priv->dev, led_cdev: &led->ldev, |
200 | init_data: &init_data); |
201 | if (ret) { |
202 | dev_err(priv->dev, |
203 | "failed to register LED device, err %d" , ret); |
204 | fwnode_handle_put(fwnode: child); |
205 | return ret; |
206 | } |
207 | |
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(dev: &spi->dev); |
221 | if (!count) { |
222 | dev_err(&spi->dev, "LEDs are not defined in device tree!" ); |
223 | return -ENODEV; |
224 | } |
225 | |
226 | priv = devm_kzalloc(dev: &spi->dev, struct_size(priv, leds, count), |
227 | GFP_KERNEL); |
228 | if (!priv) |
229 | return -ENOMEM; |
230 | |
231 | priv->buf = devm_kzalloc(dev: &spi->dev, size: 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(dwork: &priv->work, CR_RECOUNT_DELAY); |
263 | |
264 | spi_set_drvdata(spi, data: priv); |
265 | |
266 | return 0; |
267 | } |
268 | |
269 | static void cr0014114_remove(struct spi_device *spi) |
270 | { |
271 | struct cr0014114 *priv = spi_get_drvdata(spi); |
272 | |
273 | cancel_delayed_work_sync(dwork: &priv->work); |
274 | mutex_destroy(lock: &priv->lock); |
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" ); |
299 | |