1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * Character line display core support |
4 | * |
5 | * Copyright (C) 2016 Imagination Technologies |
6 | * Author: Paul Burton <paul.burton@mips.com> |
7 | * |
8 | * Copyright (C) 2021 Glider bv |
9 | */ |
10 | |
11 | #include <generated/utsrelease.h> |
12 | |
13 | #include <linux/container_of.h> |
14 | #include <linux/device.h> |
15 | #include <linux/export.h> |
16 | #include <linux/idr.h> |
17 | #include <linux/jiffies.h> |
18 | #include <linux/kstrtox.h> |
19 | #include <linux/module.h> |
20 | #include <linux/slab.h> |
21 | #include <linux/string.h> |
22 | #include <linux/sysfs.h> |
23 | #include <linux/timer.h> |
24 | |
25 | #include <linux/map_to_7segment.h> |
26 | #include <linux/map_to_14segment.h> |
27 | |
28 | #include "line-display.h" |
29 | |
30 | #define DEFAULT_SCROLL_RATE (HZ / 2) |
31 | |
32 | /** |
33 | * linedisp_scroll() - scroll the display by a character |
34 | * @t: really a pointer to the private data structure |
35 | * |
36 | * Scroll the current message along the display by one character, rearming the |
37 | * timer if required. |
38 | */ |
39 | static void linedisp_scroll(struct timer_list *t) |
40 | { |
41 | struct linedisp *linedisp = from_timer(linedisp, t, timer); |
42 | unsigned int i, ch = linedisp->scroll_pos; |
43 | unsigned int num_chars = linedisp->num_chars; |
44 | |
45 | /* update the current message string */ |
46 | for (i = 0; i < num_chars;) { |
47 | /* copy as many characters from the string as possible */ |
48 | for (; i < num_chars && ch < linedisp->message_len; i++, ch++) |
49 | linedisp->buf[i] = linedisp->message[ch]; |
50 | |
51 | /* wrap around to the start of the string */ |
52 | ch = 0; |
53 | } |
54 | |
55 | /* update the display */ |
56 | linedisp->ops->update(linedisp); |
57 | |
58 | /* move on to the next character */ |
59 | linedisp->scroll_pos++; |
60 | linedisp->scroll_pos %= linedisp->message_len; |
61 | |
62 | /* rearm the timer */ |
63 | if (linedisp->message_len > num_chars && linedisp->scroll_rate) |
64 | mod_timer(timer: &linedisp->timer, expires: jiffies + linedisp->scroll_rate); |
65 | } |
66 | |
67 | /** |
68 | * linedisp_display() - set the message to be displayed |
69 | * @linedisp: pointer to the private data structure |
70 | * @msg: the message to display |
71 | * @count: length of msg, or -1 |
72 | * |
73 | * Display a new message @msg on the display. @msg can be longer than the |
74 | * number of characters the display can display, in which case it will begin |
75 | * scrolling across the display. |
76 | * |
77 | * Return: 0 on success, -ENOMEM on memory allocation failure |
78 | */ |
79 | static int linedisp_display(struct linedisp *linedisp, const char *msg, |
80 | ssize_t count) |
81 | { |
82 | char *new_msg; |
83 | |
84 | /* stop the scroll timer */ |
85 | del_timer_sync(timer: &linedisp->timer); |
86 | |
87 | if (count == -1) |
88 | count = strlen(msg); |
89 | |
90 | /* if the string ends with a newline, trim it */ |
91 | if (msg[count - 1] == '\n') |
92 | count--; |
93 | |
94 | if (!count) { |
95 | /* Clear the display */ |
96 | kfree(objp: linedisp->message); |
97 | linedisp->message = NULL; |
98 | linedisp->message_len = 0; |
99 | memset(linedisp->buf, ' ', linedisp->num_chars); |
100 | linedisp->ops->update(linedisp); |
101 | return 0; |
102 | } |
103 | |
104 | new_msg = kmemdup_nul(s: msg, len: count, GFP_KERNEL); |
105 | if (!new_msg) |
106 | return -ENOMEM; |
107 | |
108 | kfree(objp: linedisp->message); |
109 | |
110 | linedisp->message = new_msg; |
111 | linedisp->message_len = count; |
112 | linedisp->scroll_pos = 0; |
113 | |
114 | /* update the display */ |
115 | linedisp_scroll(t: &linedisp->timer); |
116 | |
117 | return 0; |
118 | } |
119 | |
120 | /** |
121 | * message_show() - read message via sysfs |
122 | * @dev: the display device |
123 | * @attr: the display message attribute |
124 | * @buf: the buffer to read the message into |
125 | * |
126 | * Read the current message being displayed or scrolled across the display into |
127 | * @buf, for reads from sysfs. |
128 | * |
129 | * Return: the number of characters written to @buf |
130 | */ |
131 | static ssize_t message_show(struct device *dev, struct device_attribute *attr, |
132 | char *buf) |
133 | { |
134 | struct linedisp *linedisp = container_of(dev, struct linedisp, dev); |
135 | |
136 | return sysfs_emit(buf, fmt: "%s\n" , linedisp->message); |
137 | } |
138 | |
139 | /** |
140 | * message_store() - write a new message via sysfs |
141 | * @dev: the display device |
142 | * @attr: the display message attribute |
143 | * @buf: the buffer containing the new message |
144 | * @count: the size of the message in @buf |
145 | * |
146 | * Write a new message to display or scroll across the display from sysfs. |
147 | * |
148 | * Return: the size of the message on success, else -ERRNO |
149 | */ |
150 | static ssize_t message_store(struct device *dev, struct device_attribute *attr, |
151 | const char *buf, size_t count) |
152 | { |
153 | struct linedisp *linedisp = container_of(dev, struct linedisp, dev); |
154 | int err; |
155 | |
156 | err = linedisp_display(linedisp, msg: buf, count); |
157 | return err ?: count; |
158 | } |
159 | |
160 | static DEVICE_ATTR_RW(message); |
161 | |
162 | static ssize_t scroll_step_ms_show(struct device *dev, |
163 | struct device_attribute *attr, char *buf) |
164 | { |
165 | struct linedisp *linedisp = container_of(dev, struct linedisp, dev); |
166 | |
167 | return sysfs_emit(buf, fmt: "%u\n" , jiffies_to_msecs(j: linedisp->scroll_rate)); |
168 | } |
169 | |
170 | static ssize_t scroll_step_ms_store(struct device *dev, |
171 | struct device_attribute *attr, |
172 | const char *buf, size_t count) |
173 | { |
174 | struct linedisp *linedisp = container_of(dev, struct linedisp, dev); |
175 | unsigned int ms; |
176 | int err; |
177 | |
178 | err = kstrtouint(s: buf, base: 10, res: &ms); |
179 | if (err) |
180 | return err; |
181 | |
182 | linedisp->scroll_rate = msecs_to_jiffies(m: ms); |
183 | if (linedisp->message && linedisp->message_len > linedisp->num_chars) { |
184 | del_timer_sync(timer: &linedisp->timer); |
185 | if (linedisp->scroll_rate) |
186 | linedisp_scroll(t: &linedisp->timer); |
187 | } |
188 | |
189 | return count; |
190 | } |
191 | |
192 | static DEVICE_ATTR_RW(scroll_step_ms); |
193 | |
194 | static ssize_t map_seg_show(struct device *dev, struct device_attribute *attr, char *buf) |
195 | { |
196 | struct linedisp *linedisp = container_of(dev, struct linedisp, dev); |
197 | struct linedisp_map *map = linedisp->map; |
198 | |
199 | memcpy(buf, &map->map, map->size); |
200 | return map->size; |
201 | } |
202 | |
203 | static ssize_t map_seg_store(struct device *dev, struct device_attribute *attr, |
204 | const char *buf, size_t count) |
205 | { |
206 | struct linedisp *linedisp = container_of(dev, struct linedisp, dev); |
207 | struct linedisp_map *map = linedisp->map; |
208 | |
209 | if (count != map->size) |
210 | return -EINVAL; |
211 | |
212 | memcpy(&map->map, buf, count); |
213 | return count; |
214 | } |
215 | |
216 | static const SEG7_DEFAULT_MAP(initial_map_seg7); |
217 | static DEVICE_ATTR(map_seg7, 0644, map_seg_show, map_seg_store); |
218 | |
219 | static const SEG14_DEFAULT_MAP(initial_map_seg14); |
220 | static DEVICE_ATTR(map_seg14, 0644, map_seg_show, map_seg_store); |
221 | |
222 | static struct attribute *linedisp_attrs[] = { |
223 | &dev_attr_message.attr, |
224 | &dev_attr_scroll_step_ms.attr, |
225 | &dev_attr_map_seg7.attr, |
226 | &dev_attr_map_seg14.attr, |
227 | NULL |
228 | }; |
229 | |
230 | static umode_t linedisp_attr_is_visible(struct kobject *kobj, struct attribute *attr, int n) |
231 | { |
232 | struct device *dev = kobj_to_dev(kobj); |
233 | struct linedisp *linedisp = container_of(dev, struct linedisp, dev); |
234 | struct linedisp_map *map = linedisp->map; |
235 | umode_t mode = attr->mode; |
236 | |
237 | if (attr == &dev_attr_map_seg7.attr) { |
238 | if (!map) |
239 | return 0; |
240 | if (map->type != LINEDISP_MAP_SEG7) |
241 | return 0; |
242 | } |
243 | |
244 | if (attr == &dev_attr_map_seg14.attr) { |
245 | if (!map) |
246 | return 0; |
247 | if (map->type != LINEDISP_MAP_SEG14) |
248 | return 0; |
249 | } |
250 | |
251 | return mode; |
252 | }; |
253 | |
254 | static const struct attribute_group linedisp_group = { |
255 | .is_visible = linedisp_attr_is_visible, |
256 | .attrs = linedisp_attrs, |
257 | }; |
258 | __ATTRIBUTE_GROUPS(linedisp); |
259 | |
260 | static DEFINE_IDA(linedisp_id); |
261 | |
262 | static void linedisp_release(struct device *dev) |
263 | { |
264 | struct linedisp *linedisp = container_of(dev, struct linedisp, dev); |
265 | |
266 | kfree(objp: linedisp->map); |
267 | kfree(objp: linedisp->message); |
268 | kfree(objp: linedisp->buf); |
269 | ida_free(&linedisp_id, id: linedisp->id); |
270 | } |
271 | |
272 | static const struct device_type linedisp_type = { |
273 | .groups = linedisp_groups, |
274 | .release = linedisp_release, |
275 | }; |
276 | |
277 | static int linedisp_init_map(struct linedisp *linedisp) |
278 | { |
279 | struct linedisp_map *map; |
280 | int err; |
281 | |
282 | if (!linedisp->ops->get_map_type) |
283 | return 0; |
284 | |
285 | err = linedisp->ops->get_map_type(linedisp); |
286 | if (err < 0) |
287 | return err; |
288 | |
289 | map = kmalloc(size: sizeof(*map), GFP_KERNEL); |
290 | if (!map) |
291 | return -ENOMEM; |
292 | |
293 | map->type = err; |
294 | |
295 | /* assign initial mapping */ |
296 | switch (map->type) { |
297 | case LINEDISP_MAP_SEG7: |
298 | map->map.seg7 = initial_map_seg7; |
299 | map->size = sizeof(map->map.seg7); |
300 | break; |
301 | case LINEDISP_MAP_SEG14: |
302 | map->map.seg14 = initial_map_seg14; |
303 | map->size = sizeof(map->map.seg14); |
304 | break; |
305 | default: |
306 | kfree(objp: map); |
307 | return -EINVAL; |
308 | } |
309 | |
310 | linedisp->map = map; |
311 | |
312 | return 0; |
313 | } |
314 | |
315 | /** |
316 | * linedisp_register - register a character line display |
317 | * @linedisp: pointer to character line display structure |
318 | * @parent: parent device |
319 | * @num_chars: the number of characters that can be displayed |
320 | * @ops: character line display operations |
321 | * |
322 | * Return: zero on success, else a negative error code. |
323 | */ |
324 | int linedisp_register(struct linedisp *linedisp, struct device *parent, |
325 | unsigned int num_chars, const struct linedisp_ops *ops) |
326 | { |
327 | int err; |
328 | |
329 | memset(linedisp, 0, sizeof(*linedisp)); |
330 | linedisp->dev.parent = parent; |
331 | linedisp->dev.type = &linedisp_type; |
332 | linedisp->ops = ops; |
333 | linedisp->num_chars = num_chars; |
334 | linedisp->scroll_rate = DEFAULT_SCROLL_RATE; |
335 | |
336 | err = ida_alloc(ida: &linedisp_id, GFP_KERNEL); |
337 | if (err < 0) |
338 | return err; |
339 | linedisp->id = err; |
340 | |
341 | device_initialize(dev: &linedisp->dev); |
342 | dev_set_name(dev: &linedisp->dev, name: "linedisp.%u" , linedisp->id); |
343 | |
344 | err = -ENOMEM; |
345 | linedisp->buf = kzalloc(size: linedisp->num_chars, GFP_KERNEL); |
346 | if (!linedisp->buf) |
347 | goto out_put_device; |
348 | |
349 | /* initialise a character mapping, if required */ |
350 | err = linedisp_init_map(linedisp); |
351 | if (err) |
352 | goto out_put_device; |
353 | |
354 | /* initialise a timer for scrolling the message */ |
355 | timer_setup(&linedisp->timer, linedisp_scroll, 0); |
356 | |
357 | err = device_add(dev: &linedisp->dev); |
358 | if (err) |
359 | goto out_del_timer; |
360 | |
361 | /* display a default message */ |
362 | err = linedisp_display(linedisp, msg: "Linux " UTS_RELEASE " " , count: -1); |
363 | if (err) |
364 | goto out_del_dev; |
365 | |
366 | return 0; |
367 | |
368 | out_del_dev: |
369 | device_del(dev: &linedisp->dev); |
370 | out_del_timer: |
371 | del_timer_sync(timer: &linedisp->timer); |
372 | out_put_device: |
373 | put_device(dev: &linedisp->dev); |
374 | return err; |
375 | } |
376 | EXPORT_SYMBOL_NS_GPL(linedisp_register, LINEDISP); |
377 | |
378 | /** |
379 | * linedisp_unregister - unregister a character line display |
380 | * @linedisp: pointer to character line display structure registered previously |
381 | * with linedisp_register() |
382 | */ |
383 | void linedisp_unregister(struct linedisp *linedisp) |
384 | { |
385 | device_del(dev: &linedisp->dev); |
386 | del_timer_sync(timer: &linedisp->timer); |
387 | put_device(dev: &linedisp->dev); |
388 | } |
389 | EXPORT_SYMBOL_NS_GPL(linedisp_unregister, LINEDISP); |
390 | |
391 | MODULE_LICENSE("GPL" ); |
392 | |