Geert Uytterhoeven | 7e76aec | 2021-10-19 16:45:05 +0200 | [diff] [blame] | 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/device.h> |
| 14 | #include <linux/module.h> |
| 15 | #include <linux/slab.h> |
| 16 | #include <linux/string.h> |
| 17 | #include <linux/sysfs.h> |
| 18 | #include <linux/timer.h> |
| 19 | |
| 20 | #include "line-display.h" |
| 21 | |
Geert Uytterhoeven | d79141c | 2021-10-19 16:45:07 +0200 | [diff] [blame] | 22 | #define DEFAULT_SCROLL_RATE (HZ / 2) |
| 23 | |
Geert Uytterhoeven | 7e76aec | 2021-10-19 16:45:05 +0200 | [diff] [blame] | 24 | /** |
| 25 | * linedisp_scroll() - scroll the display by a character |
| 26 | * @t: really a pointer to the private data structure |
| 27 | * |
| 28 | * Scroll the current message along the display by one character, rearming the |
| 29 | * timer if required. |
| 30 | */ |
| 31 | static void linedisp_scroll(struct timer_list *t) |
| 32 | { |
| 33 | struct linedisp *linedisp = from_timer(linedisp, t, timer); |
| 34 | unsigned int i, ch = linedisp->scroll_pos; |
| 35 | unsigned int num_chars = linedisp->num_chars; |
| 36 | |
| 37 | /* update the current message string */ |
| 38 | for (i = 0; i < num_chars;) { |
| 39 | /* copy as many characters from the string as possible */ |
| 40 | for (; i < num_chars && ch < linedisp->message_len; i++, ch++) |
| 41 | linedisp->buf[i] = linedisp->message[ch]; |
| 42 | |
| 43 | /* wrap around to the start of the string */ |
| 44 | ch = 0; |
| 45 | } |
| 46 | |
| 47 | /* update the display */ |
| 48 | linedisp->update(linedisp); |
| 49 | |
| 50 | /* move on to the next character */ |
| 51 | linedisp->scroll_pos++; |
| 52 | linedisp->scroll_pos %= linedisp->message_len; |
| 53 | |
| 54 | /* rearm the timer */ |
Geert Uytterhoeven | d79141c | 2021-10-19 16:45:07 +0200 | [diff] [blame] | 55 | if (linedisp->message_len > num_chars && linedisp->scroll_rate) |
Geert Uytterhoeven | 7e76aec | 2021-10-19 16:45:05 +0200 | [diff] [blame] | 56 | mod_timer(&linedisp->timer, jiffies + linedisp->scroll_rate); |
| 57 | } |
| 58 | |
| 59 | /** |
| 60 | * linedisp_display() - set the message to be displayed |
| 61 | * @linedisp: pointer to the private data structure |
| 62 | * @msg: the message to display |
| 63 | * @count: length of msg, or -1 |
| 64 | * |
| 65 | * Display a new message @msg on the display. @msg can be longer than the |
| 66 | * number of characters the display can display, in which case it will begin |
| 67 | * scrolling across the display. |
| 68 | * |
| 69 | * Return: 0 on success, -ENOMEM on memory allocation failure |
| 70 | */ |
| 71 | static int linedisp_display(struct linedisp *linedisp, const char *msg, |
| 72 | ssize_t count) |
| 73 | { |
| 74 | char *new_msg; |
| 75 | |
| 76 | /* stop the scroll timer */ |
| 77 | del_timer_sync(&linedisp->timer); |
| 78 | |
| 79 | if (count == -1) |
| 80 | count = strlen(msg); |
| 81 | |
| 82 | /* if the string ends with a newline, trim it */ |
| 83 | if (msg[count - 1] == '\n') |
| 84 | count--; |
| 85 | |
| 86 | if (!count) { |
| 87 | /* Clear the display */ |
| 88 | kfree(linedisp->message); |
| 89 | linedisp->message = NULL; |
| 90 | linedisp->message_len = 0; |
| 91 | memset(linedisp->buf, ' ', linedisp->num_chars); |
| 92 | linedisp->update(linedisp); |
| 93 | return 0; |
| 94 | } |
| 95 | |
Geert Uytterhoeven | 364f2c39 | 2021-10-19 16:45:06 +0200 | [diff] [blame] | 96 | new_msg = kmemdup_nul(msg, count, GFP_KERNEL); |
Geert Uytterhoeven | 7e76aec | 2021-10-19 16:45:05 +0200 | [diff] [blame] | 97 | if (!new_msg) |
| 98 | return -ENOMEM; |
| 99 | |
Geert Uytterhoeven | 7e76aec | 2021-10-19 16:45:05 +0200 | [diff] [blame] | 100 | kfree(linedisp->message); |
| 101 | |
| 102 | linedisp->message = new_msg; |
| 103 | linedisp->message_len = count; |
| 104 | linedisp->scroll_pos = 0; |
| 105 | |
| 106 | /* update the display */ |
| 107 | linedisp_scroll(&linedisp->timer); |
| 108 | |
| 109 | return 0; |
| 110 | } |
| 111 | |
| 112 | /** |
| 113 | * message_show() - read message via sysfs |
| 114 | * @dev: the display device |
| 115 | * @attr: the display message attribute |
| 116 | * @buf: the buffer to read the message into |
| 117 | * |
| 118 | * Read the current message being displayed or scrolled across the display into |
| 119 | * @buf, for reads from sysfs. |
| 120 | * |
| 121 | * Return: the number of characters written to @buf |
| 122 | */ |
| 123 | static ssize_t message_show(struct device *dev, struct device_attribute *attr, |
| 124 | char *buf) |
| 125 | { |
| 126 | struct linedisp *linedisp = container_of(dev, struct linedisp, dev); |
| 127 | |
| 128 | return sysfs_emit(buf, "%s\n", linedisp->message); |
| 129 | } |
| 130 | |
| 131 | /** |
| 132 | * message_store() - write a new message via sysfs |
| 133 | * @dev: the display device |
| 134 | * @attr: the display message attribute |
| 135 | * @buf: the buffer containing the new message |
| 136 | * @count: the size of the message in @buf |
| 137 | * |
| 138 | * Write a new message to display or scroll across the display from sysfs. |
| 139 | * |
| 140 | * Return: the size of the message on success, else -ERRNO |
| 141 | */ |
| 142 | static ssize_t message_store(struct device *dev, struct device_attribute *attr, |
| 143 | const char *buf, size_t count) |
| 144 | { |
| 145 | struct linedisp *linedisp = container_of(dev, struct linedisp, dev); |
| 146 | int err; |
| 147 | |
| 148 | err = linedisp_display(linedisp, buf, count); |
| 149 | return err ?: count; |
| 150 | } |
| 151 | |
| 152 | static DEVICE_ATTR_RW(message); |
| 153 | |
Geert Uytterhoeven | d79141c | 2021-10-19 16:45:07 +0200 | [diff] [blame] | 154 | static ssize_t scroll_step_ms_show(struct device *dev, |
| 155 | struct device_attribute *attr, char *buf) |
| 156 | { |
| 157 | struct linedisp *linedisp = container_of(dev, struct linedisp, dev); |
| 158 | |
| 159 | return sysfs_emit(buf, "%u\n", jiffies_to_msecs(linedisp->scroll_rate)); |
| 160 | } |
| 161 | |
| 162 | static ssize_t scroll_step_ms_store(struct device *dev, |
| 163 | struct device_attribute *attr, |
| 164 | const char *buf, size_t count) |
| 165 | { |
| 166 | struct linedisp *linedisp = container_of(dev, struct linedisp, dev); |
| 167 | unsigned int ms; |
| 168 | |
| 169 | if (kstrtouint(buf, 10, &ms) != 0) |
| 170 | return -EINVAL; |
| 171 | |
| 172 | linedisp->scroll_rate = msecs_to_jiffies(ms); |
| 173 | if (linedisp->message && linedisp->message_len > linedisp->num_chars) { |
| 174 | del_timer_sync(&linedisp->timer); |
| 175 | if (linedisp->scroll_rate) |
| 176 | linedisp_scroll(&linedisp->timer); |
| 177 | } |
| 178 | |
| 179 | return count; |
| 180 | } |
| 181 | |
| 182 | static DEVICE_ATTR_RW(scroll_step_ms); |
| 183 | |
Geert Uytterhoeven | 7e76aec | 2021-10-19 16:45:05 +0200 | [diff] [blame] | 184 | static struct attribute *linedisp_attrs[] = { |
| 185 | &dev_attr_message.attr, |
Geert Uytterhoeven | d79141c | 2021-10-19 16:45:07 +0200 | [diff] [blame] | 186 | &dev_attr_scroll_step_ms.attr, |
Geert Uytterhoeven | 7e76aec | 2021-10-19 16:45:05 +0200 | [diff] [blame] | 187 | NULL, |
| 188 | }; |
| 189 | ATTRIBUTE_GROUPS(linedisp); |
| 190 | |
| 191 | static const struct device_type linedisp_type = { |
| 192 | .groups = linedisp_groups, |
| 193 | }; |
| 194 | |
| 195 | /** |
| 196 | * linedisp_register - register a character line display |
| 197 | * @linedisp: pointer to character line display structure |
| 198 | * @parent: parent device |
| 199 | * @num_chars: the number of characters that can be displayed |
| 200 | * @buf: pointer to a buffer that can hold @num_chars characters |
| 201 | * @update: Function called to update the display. This must not sleep! |
| 202 | * |
| 203 | * Return: zero on success, else a negative error code. |
| 204 | */ |
| 205 | int linedisp_register(struct linedisp *linedisp, struct device *parent, |
| 206 | unsigned int num_chars, char *buf, |
| 207 | void (*update)(struct linedisp *linedisp)) |
| 208 | { |
| 209 | static atomic_t linedisp_id = ATOMIC_INIT(-1); |
| 210 | int err; |
| 211 | |
| 212 | memset(linedisp, 0, sizeof(*linedisp)); |
| 213 | linedisp->dev.parent = parent; |
| 214 | linedisp->dev.type = &linedisp_type; |
| 215 | linedisp->update = update; |
| 216 | linedisp->buf = buf; |
| 217 | linedisp->num_chars = num_chars; |
Geert Uytterhoeven | d79141c | 2021-10-19 16:45:07 +0200 | [diff] [blame] | 218 | linedisp->scroll_rate = DEFAULT_SCROLL_RATE; |
Geert Uytterhoeven | 7e76aec | 2021-10-19 16:45:05 +0200 | [diff] [blame] | 219 | |
| 220 | device_initialize(&linedisp->dev); |
| 221 | dev_set_name(&linedisp->dev, "linedisp.%lu", |
| 222 | (unsigned long)atomic_inc_return(&linedisp_id)); |
| 223 | |
| 224 | /* initialise a timer for scrolling the message */ |
| 225 | timer_setup(&linedisp->timer, linedisp_scroll, 0); |
| 226 | |
| 227 | err = device_add(&linedisp->dev); |
| 228 | if (err) |
| 229 | goto out_del_timer; |
| 230 | |
| 231 | /* display a default message */ |
| 232 | err = linedisp_display(linedisp, "Linux " UTS_RELEASE " ", -1); |
| 233 | if (err) |
| 234 | goto out_del_dev; |
| 235 | |
| 236 | return 0; |
| 237 | |
| 238 | out_del_dev: |
| 239 | device_del(&linedisp->dev); |
| 240 | out_del_timer: |
| 241 | del_timer_sync(&linedisp->timer); |
| 242 | put_device(&linedisp->dev); |
| 243 | return err; |
| 244 | } |
| 245 | EXPORT_SYMBOL_GPL(linedisp_register); |
| 246 | |
| 247 | /** |
| 248 | * linedisp_unregister - unregister a character line display |
| 249 | * @linedisp: pointer to character line display structure registered previously |
| 250 | * with linedisp_register() |
| 251 | */ |
| 252 | void linedisp_unregister(struct linedisp *linedisp) |
| 253 | { |
| 254 | device_del(&linedisp->dev); |
| 255 | del_timer_sync(&linedisp->timer); |
| 256 | kfree(linedisp->message); |
| 257 | put_device(&linedisp->dev); |
| 258 | } |
| 259 | EXPORT_SYMBOL_GPL(linedisp_unregister); |
| 260 | |
| 261 | MODULE_LICENSE("GPL"); |