17e76aeceSGeert Uytterhoeven // SPDX-License-Identifier: GPL-2.0-or-later
27e76aeceSGeert Uytterhoeven /*
37e76aeceSGeert Uytterhoeven  * Character line display core support
47e76aeceSGeert Uytterhoeven  *
57e76aeceSGeert Uytterhoeven  * Copyright (C) 2016 Imagination Technologies
67e76aeceSGeert Uytterhoeven  * Author: Paul Burton <paul.burton@mips.com>
77e76aeceSGeert Uytterhoeven  *
87e76aeceSGeert Uytterhoeven  * Copyright (C) 2021 Glider bv
97e76aeceSGeert Uytterhoeven  */
107e76aeceSGeert Uytterhoeven 
117e76aeceSGeert Uytterhoeven #include <generated/utsrelease.h>
127e76aeceSGeert Uytterhoeven 
137e76aeceSGeert Uytterhoeven #include <linux/device.h>
147e76aeceSGeert Uytterhoeven #include <linux/module.h>
157e76aeceSGeert Uytterhoeven #include <linux/slab.h>
167e76aeceSGeert Uytterhoeven #include <linux/string.h>
177e76aeceSGeert Uytterhoeven #include <linux/sysfs.h>
187e76aeceSGeert Uytterhoeven #include <linux/timer.h>
197e76aeceSGeert Uytterhoeven 
207e76aeceSGeert Uytterhoeven #include "line-display.h"
217e76aeceSGeert Uytterhoeven 
22*d79141c3SGeert Uytterhoeven #define DEFAULT_SCROLL_RATE	(HZ / 2)
23*d79141c3SGeert Uytterhoeven 
247e76aeceSGeert Uytterhoeven /**
257e76aeceSGeert Uytterhoeven  * linedisp_scroll() - scroll the display by a character
267e76aeceSGeert Uytterhoeven  * @t: really a pointer to the private data structure
277e76aeceSGeert Uytterhoeven  *
287e76aeceSGeert Uytterhoeven  * Scroll the current message along the display by one character, rearming the
297e76aeceSGeert Uytterhoeven  * timer if required.
307e76aeceSGeert Uytterhoeven  */
linedisp_scroll(struct timer_list * t)317e76aeceSGeert Uytterhoeven static void linedisp_scroll(struct timer_list *t)
327e76aeceSGeert Uytterhoeven {
337e76aeceSGeert Uytterhoeven 	struct linedisp *linedisp = from_timer(linedisp, t, timer);
347e76aeceSGeert Uytterhoeven 	unsigned int i, ch = linedisp->scroll_pos;
357e76aeceSGeert Uytterhoeven 	unsigned int num_chars = linedisp->num_chars;
367e76aeceSGeert Uytterhoeven 
377e76aeceSGeert Uytterhoeven 	/* update the current message string */
387e76aeceSGeert Uytterhoeven 	for (i = 0; i < num_chars;) {
397e76aeceSGeert Uytterhoeven 		/* copy as many characters from the string as possible */
407e76aeceSGeert Uytterhoeven 		for (; i < num_chars && ch < linedisp->message_len; i++, ch++)
417e76aeceSGeert Uytterhoeven 			linedisp->buf[i] = linedisp->message[ch];
427e76aeceSGeert Uytterhoeven 
437e76aeceSGeert Uytterhoeven 		/* wrap around to the start of the string */
447e76aeceSGeert Uytterhoeven 		ch = 0;
457e76aeceSGeert Uytterhoeven 	}
467e76aeceSGeert Uytterhoeven 
477e76aeceSGeert Uytterhoeven 	/* update the display */
487e76aeceSGeert Uytterhoeven 	linedisp->update(linedisp);
497e76aeceSGeert Uytterhoeven 
507e76aeceSGeert Uytterhoeven 	/* move on to the next character */
517e76aeceSGeert Uytterhoeven 	linedisp->scroll_pos++;
527e76aeceSGeert Uytterhoeven 	linedisp->scroll_pos %= linedisp->message_len;
537e76aeceSGeert Uytterhoeven 
547e76aeceSGeert Uytterhoeven 	/* rearm the timer */
55*d79141c3SGeert Uytterhoeven 	if (linedisp->message_len > num_chars && linedisp->scroll_rate)
567e76aeceSGeert Uytterhoeven 		mod_timer(&linedisp->timer, jiffies + linedisp->scroll_rate);
577e76aeceSGeert Uytterhoeven }
587e76aeceSGeert Uytterhoeven 
597e76aeceSGeert Uytterhoeven /**
607e76aeceSGeert Uytterhoeven  * linedisp_display() - set the message to be displayed
617e76aeceSGeert Uytterhoeven  * @linedisp: pointer to the private data structure
627e76aeceSGeert Uytterhoeven  * @msg: the message to display
637e76aeceSGeert Uytterhoeven  * @count: length of msg, or -1
647e76aeceSGeert Uytterhoeven  *
657e76aeceSGeert Uytterhoeven  * Display a new message @msg on the display. @msg can be longer than the
667e76aeceSGeert Uytterhoeven  * number of characters the display can display, in which case it will begin
677e76aeceSGeert Uytterhoeven  * scrolling across the display.
687e76aeceSGeert Uytterhoeven  *
697e76aeceSGeert Uytterhoeven  * Return: 0 on success, -ENOMEM on memory allocation failure
707e76aeceSGeert Uytterhoeven  */
linedisp_display(struct linedisp * linedisp,const char * msg,ssize_t count)717e76aeceSGeert Uytterhoeven static int linedisp_display(struct linedisp *linedisp, const char *msg,
727e76aeceSGeert Uytterhoeven 			    ssize_t count)
737e76aeceSGeert Uytterhoeven {
747e76aeceSGeert Uytterhoeven 	char *new_msg;
757e76aeceSGeert Uytterhoeven 
767e76aeceSGeert Uytterhoeven 	/* stop the scroll timer */
777e76aeceSGeert Uytterhoeven 	del_timer_sync(&linedisp->timer);
787e76aeceSGeert Uytterhoeven 
797e76aeceSGeert Uytterhoeven 	if (count == -1)
807e76aeceSGeert Uytterhoeven 		count = strlen(msg);
817e76aeceSGeert Uytterhoeven 
827e76aeceSGeert Uytterhoeven 	/* if the string ends with a newline, trim it */
837e76aeceSGeert Uytterhoeven 	if (msg[count - 1] == '\n')
847e76aeceSGeert Uytterhoeven 		count--;
857e76aeceSGeert Uytterhoeven 
867e76aeceSGeert Uytterhoeven 	if (!count) {
877e76aeceSGeert Uytterhoeven 		/* Clear the display */
887e76aeceSGeert Uytterhoeven 		kfree(linedisp->message);
897e76aeceSGeert Uytterhoeven 		linedisp->message = NULL;
907e76aeceSGeert Uytterhoeven 		linedisp->message_len = 0;
917e76aeceSGeert Uytterhoeven 		memset(linedisp->buf, ' ', linedisp->num_chars);
927e76aeceSGeert Uytterhoeven 		linedisp->update(linedisp);
937e76aeceSGeert Uytterhoeven 		return 0;
947e76aeceSGeert Uytterhoeven 	}
957e76aeceSGeert Uytterhoeven 
96364f2c39SGeert Uytterhoeven 	new_msg = kmemdup_nul(msg, count, GFP_KERNEL);
977e76aeceSGeert Uytterhoeven 	if (!new_msg)
987e76aeceSGeert Uytterhoeven 		return -ENOMEM;
997e76aeceSGeert Uytterhoeven 
1007e76aeceSGeert Uytterhoeven 	kfree(linedisp->message);
1017e76aeceSGeert Uytterhoeven 
1027e76aeceSGeert Uytterhoeven 	linedisp->message = new_msg;
1037e76aeceSGeert Uytterhoeven 	linedisp->message_len = count;
1047e76aeceSGeert Uytterhoeven 	linedisp->scroll_pos = 0;
1057e76aeceSGeert Uytterhoeven 
1067e76aeceSGeert Uytterhoeven 	/* update the display */
1077e76aeceSGeert Uytterhoeven 	linedisp_scroll(&linedisp->timer);
1087e76aeceSGeert Uytterhoeven 
1097e76aeceSGeert Uytterhoeven 	return 0;
1107e76aeceSGeert Uytterhoeven }
1117e76aeceSGeert Uytterhoeven 
1127e76aeceSGeert Uytterhoeven /**
1137e76aeceSGeert Uytterhoeven  * message_show() - read message via sysfs
1147e76aeceSGeert Uytterhoeven  * @dev: the display device
1157e76aeceSGeert Uytterhoeven  * @attr: the display message attribute
1167e76aeceSGeert Uytterhoeven  * @buf: the buffer to read the message into
1177e76aeceSGeert Uytterhoeven  *
1187e76aeceSGeert Uytterhoeven  * Read the current message being displayed or scrolled across the display into
1197e76aeceSGeert Uytterhoeven  * @buf, for reads from sysfs.
1207e76aeceSGeert Uytterhoeven  *
1217e76aeceSGeert Uytterhoeven  * Return: the number of characters written to @buf
1227e76aeceSGeert Uytterhoeven  */
message_show(struct device * dev,struct device_attribute * attr,char * buf)1237e76aeceSGeert Uytterhoeven static ssize_t message_show(struct device *dev, struct device_attribute *attr,
1247e76aeceSGeert Uytterhoeven 			    char *buf)
1257e76aeceSGeert Uytterhoeven {
1267e76aeceSGeert Uytterhoeven 	struct linedisp *linedisp = container_of(dev, struct linedisp, dev);
1277e76aeceSGeert Uytterhoeven 
1287e76aeceSGeert Uytterhoeven 	return sysfs_emit(buf, "%s\n", linedisp->message);
1297e76aeceSGeert Uytterhoeven }
1307e76aeceSGeert Uytterhoeven 
1317e76aeceSGeert Uytterhoeven /**
1327e76aeceSGeert Uytterhoeven  * message_store() - write a new message via sysfs
1337e76aeceSGeert Uytterhoeven  * @dev: the display device
1347e76aeceSGeert Uytterhoeven  * @attr: the display message attribute
1357e76aeceSGeert Uytterhoeven  * @buf: the buffer containing the new message
1367e76aeceSGeert Uytterhoeven  * @count: the size of the message in @buf
1377e76aeceSGeert Uytterhoeven  *
1387e76aeceSGeert Uytterhoeven  * Write a new message to display or scroll across the display from sysfs.
1397e76aeceSGeert Uytterhoeven  *
1407e76aeceSGeert Uytterhoeven  * Return: the size of the message on success, else -ERRNO
1417e76aeceSGeert Uytterhoeven  */
message_store(struct device * dev,struct device_attribute * attr,const char * buf,size_t count)1427e76aeceSGeert Uytterhoeven static ssize_t message_store(struct device *dev, struct device_attribute *attr,
1437e76aeceSGeert Uytterhoeven 			     const char *buf, size_t count)
1447e76aeceSGeert Uytterhoeven {
1457e76aeceSGeert Uytterhoeven 	struct linedisp *linedisp = container_of(dev, struct linedisp, dev);
1467e76aeceSGeert Uytterhoeven 	int err;
1477e76aeceSGeert Uytterhoeven 
1487e76aeceSGeert Uytterhoeven 	err = linedisp_display(linedisp, buf, count);
1497e76aeceSGeert Uytterhoeven 	return err ?: count;
1507e76aeceSGeert Uytterhoeven }
1517e76aeceSGeert Uytterhoeven 
1527e76aeceSGeert Uytterhoeven static DEVICE_ATTR_RW(message);
1537e76aeceSGeert Uytterhoeven 
scroll_step_ms_show(struct device * dev,struct device_attribute * attr,char * buf)154*d79141c3SGeert Uytterhoeven static ssize_t scroll_step_ms_show(struct device *dev,
155*d79141c3SGeert Uytterhoeven 				   struct device_attribute *attr, char *buf)
156*d79141c3SGeert Uytterhoeven {
157*d79141c3SGeert Uytterhoeven 	struct linedisp *linedisp = container_of(dev, struct linedisp, dev);
158*d79141c3SGeert Uytterhoeven 
159*d79141c3SGeert Uytterhoeven 	return sysfs_emit(buf, "%u\n", jiffies_to_msecs(linedisp->scroll_rate));
160*d79141c3SGeert Uytterhoeven }
161*d79141c3SGeert Uytterhoeven 
scroll_step_ms_store(struct device * dev,struct device_attribute * attr,const char * buf,size_t count)162*d79141c3SGeert Uytterhoeven static ssize_t scroll_step_ms_store(struct device *dev,
163*d79141c3SGeert Uytterhoeven 				    struct device_attribute *attr,
164*d79141c3SGeert Uytterhoeven 				    const char *buf, size_t count)
165*d79141c3SGeert Uytterhoeven {
166*d79141c3SGeert Uytterhoeven 	struct linedisp *linedisp = container_of(dev, struct linedisp, dev);
167*d79141c3SGeert Uytterhoeven 	unsigned int ms;
168*d79141c3SGeert Uytterhoeven 
169*d79141c3SGeert Uytterhoeven 	if (kstrtouint(buf, 10, &ms) != 0)
170*d79141c3SGeert Uytterhoeven 		return -EINVAL;
171*d79141c3SGeert Uytterhoeven 
172*d79141c3SGeert Uytterhoeven 	linedisp->scroll_rate = msecs_to_jiffies(ms);
173*d79141c3SGeert Uytterhoeven 	if (linedisp->message && linedisp->message_len > linedisp->num_chars) {
174*d79141c3SGeert Uytterhoeven 		del_timer_sync(&linedisp->timer);
175*d79141c3SGeert Uytterhoeven 		if (linedisp->scroll_rate)
176*d79141c3SGeert Uytterhoeven 			linedisp_scroll(&linedisp->timer);
177*d79141c3SGeert Uytterhoeven 	}
178*d79141c3SGeert Uytterhoeven 
179*d79141c3SGeert Uytterhoeven 	return count;
180*d79141c3SGeert Uytterhoeven }
181*d79141c3SGeert Uytterhoeven 
182*d79141c3SGeert Uytterhoeven static DEVICE_ATTR_RW(scroll_step_ms);
183*d79141c3SGeert Uytterhoeven 
1847e76aeceSGeert Uytterhoeven static struct attribute *linedisp_attrs[] = {
1857e76aeceSGeert Uytterhoeven 	&dev_attr_message.attr,
186*d79141c3SGeert Uytterhoeven 	&dev_attr_scroll_step_ms.attr,
1877e76aeceSGeert Uytterhoeven 	NULL,
1887e76aeceSGeert Uytterhoeven };
1897e76aeceSGeert Uytterhoeven ATTRIBUTE_GROUPS(linedisp);
1907e76aeceSGeert Uytterhoeven 
1917e76aeceSGeert Uytterhoeven static const struct device_type linedisp_type = {
1927e76aeceSGeert Uytterhoeven 	.groups	= linedisp_groups,
1937e76aeceSGeert Uytterhoeven };
1947e76aeceSGeert Uytterhoeven 
1957e76aeceSGeert Uytterhoeven /**
1967e76aeceSGeert Uytterhoeven  * linedisp_register - register a character line display
1977e76aeceSGeert Uytterhoeven  * @linedisp: pointer to character line display structure
1987e76aeceSGeert Uytterhoeven  * @parent: parent device
1997e76aeceSGeert Uytterhoeven  * @num_chars: the number of characters that can be displayed
2007e76aeceSGeert Uytterhoeven  * @buf: pointer to a buffer that can hold @num_chars characters
2017e76aeceSGeert Uytterhoeven  * @update: Function called to update the display.  This must not sleep!
2027e76aeceSGeert Uytterhoeven  *
2037e76aeceSGeert Uytterhoeven  * Return: zero on success, else a negative error code.
2047e76aeceSGeert Uytterhoeven  */
linedisp_register(struct linedisp * linedisp,struct device * parent,unsigned int num_chars,char * buf,void (* update)(struct linedisp * linedisp))2057e76aeceSGeert Uytterhoeven int linedisp_register(struct linedisp *linedisp, struct device *parent,
2067e76aeceSGeert Uytterhoeven 		      unsigned int num_chars, char *buf,
2077e76aeceSGeert Uytterhoeven 		      void (*update)(struct linedisp *linedisp))
2087e76aeceSGeert Uytterhoeven {
2097e76aeceSGeert Uytterhoeven 	static atomic_t linedisp_id = ATOMIC_INIT(-1);
2107e76aeceSGeert Uytterhoeven 	int err;
2117e76aeceSGeert Uytterhoeven 
2127e76aeceSGeert Uytterhoeven 	memset(linedisp, 0, sizeof(*linedisp));
2137e76aeceSGeert Uytterhoeven 	linedisp->dev.parent = parent;
2147e76aeceSGeert Uytterhoeven 	linedisp->dev.type = &linedisp_type;
2157e76aeceSGeert Uytterhoeven 	linedisp->update = update;
2167e76aeceSGeert Uytterhoeven 	linedisp->buf = buf;
2177e76aeceSGeert Uytterhoeven 	linedisp->num_chars = num_chars;
218*d79141c3SGeert Uytterhoeven 	linedisp->scroll_rate = DEFAULT_SCROLL_RATE;
2197e76aeceSGeert Uytterhoeven 
2207e76aeceSGeert Uytterhoeven 	device_initialize(&linedisp->dev);
2217e76aeceSGeert Uytterhoeven 	dev_set_name(&linedisp->dev, "linedisp.%lu",
2227e76aeceSGeert Uytterhoeven 		     (unsigned long)atomic_inc_return(&linedisp_id));
2237e76aeceSGeert Uytterhoeven 
2247e76aeceSGeert Uytterhoeven 	/* initialise a timer for scrolling the message */
2257e76aeceSGeert Uytterhoeven 	timer_setup(&linedisp->timer, linedisp_scroll, 0);
2267e76aeceSGeert Uytterhoeven 
2277e76aeceSGeert Uytterhoeven 	err = device_add(&linedisp->dev);
2287e76aeceSGeert Uytterhoeven 	if (err)
2297e76aeceSGeert Uytterhoeven 		goto out_del_timer;
2307e76aeceSGeert Uytterhoeven 
2317e76aeceSGeert Uytterhoeven 	/* display a default message */
2327e76aeceSGeert Uytterhoeven 	err = linedisp_display(linedisp, "Linux " UTS_RELEASE "       ", -1);
2337e76aeceSGeert Uytterhoeven 	if (err)
2347e76aeceSGeert Uytterhoeven 		goto out_del_dev;
2357e76aeceSGeert Uytterhoeven 
2367e76aeceSGeert Uytterhoeven 	return 0;
2377e76aeceSGeert Uytterhoeven 
2387e76aeceSGeert Uytterhoeven out_del_dev:
2397e76aeceSGeert Uytterhoeven 	device_del(&linedisp->dev);
2407e76aeceSGeert Uytterhoeven out_del_timer:
2417e76aeceSGeert Uytterhoeven 	del_timer_sync(&linedisp->timer);
2427e76aeceSGeert Uytterhoeven 	put_device(&linedisp->dev);
2437e76aeceSGeert Uytterhoeven 	return err;
2447e76aeceSGeert Uytterhoeven }
2457e76aeceSGeert Uytterhoeven EXPORT_SYMBOL_GPL(linedisp_register);
2467e76aeceSGeert Uytterhoeven 
2477e76aeceSGeert Uytterhoeven /**
2487e76aeceSGeert Uytterhoeven  * linedisp_unregister - unregister a character line display
2497e76aeceSGeert Uytterhoeven  * @linedisp: pointer to character line display structure registered previously
2507e76aeceSGeert Uytterhoeven  *	      with linedisp_register()
2517e76aeceSGeert Uytterhoeven  */
linedisp_unregister(struct linedisp * linedisp)2527e76aeceSGeert Uytterhoeven void linedisp_unregister(struct linedisp *linedisp)
2537e76aeceSGeert Uytterhoeven {
2547e76aeceSGeert Uytterhoeven 	device_del(&linedisp->dev);
2557e76aeceSGeert Uytterhoeven 	del_timer_sync(&linedisp->timer);
2567e76aeceSGeert Uytterhoeven 	kfree(linedisp->message);
2577e76aeceSGeert Uytterhoeven 	put_device(&linedisp->dev);
2587e76aeceSGeert Uytterhoeven }
2597e76aeceSGeert Uytterhoeven EXPORT_SYMBOL_GPL(linedisp_unregister);
2607e76aeceSGeert Uytterhoeven 
2617e76aeceSGeert Uytterhoeven MODULE_LICENSE("GPL");
262