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