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