xref: /linux/drivers/auxdisplay/line-display.c (revision 71dfa617ea9f18e4585fe78364217cd32b1fc382)
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(&linedisp->timer, 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(&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(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(msg, count, GFP_KERNEL);
105 	if (!new_msg)
106 		return -ENOMEM;
107 
108 	kfree(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(&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, "%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, 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, "%u\n", jiffies_to_msecs(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(buf, 10, &ms);
179 	if (err)
180 		return err;
181 
182 	linedisp->scroll_rate = msecs_to_jiffies(ms);
183 	if (linedisp->message && linedisp->message_len > linedisp->num_chars) {
184 		del_timer_sync(&linedisp->timer);
185 		if (linedisp->scroll_rate)
186 			linedisp_scroll(&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(linedisp->map);
267 	kfree(linedisp->message);
268 	kfree(linedisp->buf);
269 	ida_free(&linedisp_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(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(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(&linedisp_id, GFP_KERNEL);
337 	if (err < 0)
338 		return err;
339 	linedisp->id = err;
340 
341 	device_initialize(&linedisp->dev);
342 	dev_set_name(&linedisp->dev, "linedisp.%u", linedisp->id);
343 
344 	err = -ENOMEM;
345 	linedisp->buf = kzalloc(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(&linedisp->dev);
358 	if (err)
359 		goto out_del_timer;
360 
361 	/* display a default message */
362 	err = linedisp_display(linedisp, "Linux " UTS_RELEASE "       ", -1);
363 	if (err)
364 		goto out_del_dev;
365 
366 	return 0;
367 
368 out_del_dev:
369 	device_del(&linedisp->dev);
370 out_del_timer:
371 	del_timer_sync(&linedisp->timer);
372 out_put_device:
373 	put_device(&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(&linedisp->dev);
386 	del_timer_sync(&linedisp->timer);
387 	put_device(&linedisp->dev);
388 }
389 EXPORT_SYMBOL_NS_GPL(linedisp_unregister, LINEDISP);
390 
391 MODULE_LICENSE("GPL");
392