1 // SPDX-License-Identifier: GPL-2.0-or-later
3 * Character line display core support
5 * Copyright (C) 2016 Imagination Technologies
6 * Author: Paul Burton <paul.burton@mips.com>
8 * Copyright (C) 2021 Glider bv
11 #ifndef CONFIG_PANEL_BOOT_MESSAGE
12 #include <generated/utsrelease.h>
15 #include <linux/container_of.h>
16 #include <linux/device.h>
17 #include <linux/export.h>
18 #include <linux/idr.h>
19 #include <linux/jiffies.h>
20 #include <linux/kstrtox.h>
21 #include <linux/module.h>
22 #include <linux/slab.h>
23 #include <linux/string.h>
24 #include <linux/sysfs.h>
25 #include <linux/timer.h>
27 #include <linux/map_to_7segment.h>
28 #include <linux/map_to_14segment.h>
30 #include "line-display.h"
32 #define DEFAULT_SCROLL_RATE (HZ / 2)
35 * linedisp_scroll() - scroll the display by a character
36 * @t: really a pointer to the private data structure
38 * Scroll the current message along the display by one character, rearming the
41 static void linedisp_scroll(struct timer_list
*t
)
43 struct linedisp
*linedisp
= from_timer(linedisp
, t
, timer
);
44 unsigned int i
, ch
= linedisp
->scroll_pos
;
45 unsigned int num_chars
= linedisp
->num_chars
;
47 /* update the current message string */
48 for (i
= 0; i
< num_chars
;) {
49 /* copy as many characters from the string as possible */
50 for (; i
< num_chars
&& ch
< linedisp
->message_len
; i
++, ch
++)
51 linedisp
->buf
[i
] = linedisp
->message
[ch
];
53 /* wrap around to the start of the string */
57 /* update the display */
58 linedisp
->ops
->update(linedisp
);
60 /* move on to the next character */
61 linedisp
->scroll_pos
++;
62 linedisp
->scroll_pos
%= linedisp
->message_len
;
65 if (linedisp
->message_len
> num_chars
&& linedisp
->scroll_rate
)
66 mod_timer(&linedisp
->timer
, jiffies
+ linedisp
->scroll_rate
);
70 * linedisp_display() - set the message to be displayed
71 * @linedisp: pointer to the private data structure
72 * @msg: the message to display
73 * @count: length of msg, or -1
75 * Display a new message @msg on the display. @msg can be longer than the
76 * number of characters the display can display, in which case it will begin
77 * scrolling across the display.
79 * Return: 0 on success, -ENOMEM on memory allocation failure
81 static int linedisp_display(struct linedisp
*linedisp
, const char *msg
,
86 /* stop the scroll timer */
87 del_timer_sync(&linedisp
->timer
);
92 /* if the string ends with a newline, trim it */
93 if (msg
[count
- 1] == '\n')
97 /* Clear the display */
98 kfree(linedisp
->message
);
99 linedisp
->message
= NULL
;
100 linedisp
->message_len
= 0;
101 memset(linedisp
->buf
, ' ', linedisp
->num_chars
);
102 linedisp
->ops
->update(linedisp
);
106 new_msg
= kmemdup_nul(msg
, count
, GFP_KERNEL
);
110 kfree(linedisp
->message
);
112 linedisp
->message
= new_msg
;
113 linedisp
->message_len
= count
;
114 linedisp
->scroll_pos
= 0;
116 /* update the display */
117 linedisp_scroll(&linedisp
->timer
);
123 * message_show() - read message via sysfs
124 * @dev: the display device
125 * @attr: the display message attribute
126 * @buf: the buffer to read the message into
128 * Read the current message being displayed or scrolled across the display into
129 * @buf, for reads from sysfs.
131 * Return: the number of characters written to @buf
133 static ssize_t
message_show(struct device
*dev
, struct device_attribute
*attr
,
136 struct linedisp
*linedisp
= container_of(dev
, struct linedisp
, dev
);
138 return sysfs_emit(buf
, "%s\n", linedisp
->message
);
142 * message_store() - write a new message via sysfs
143 * @dev: the display device
144 * @attr: the display message attribute
145 * @buf: the buffer containing the new message
146 * @count: the size of the message in @buf
148 * Write a new message to display or scroll across the display from sysfs.
150 * Return: the size of the message on success, else -ERRNO
152 static ssize_t
message_store(struct device
*dev
, struct device_attribute
*attr
,
153 const char *buf
, size_t count
)
155 struct linedisp
*linedisp
= container_of(dev
, struct linedisp
, dev
);
158 err
= linedisp_display(linedisp
, buf
, count
);
162 static DEVICE_ATTR_RW(message
);
164 static ssize_t
scroll_step_ms_show(struct device
*dev
,
165 struct device_attribute
*attr
, char *buf
)
167 struct linedisp
*linedisp
= container_of(dev
, struct linedisp
, dev
);
169 return sysfs_emit(buf
, "%u\n", jiffies_to_msecs(linedisp
->scroll_rate
));
172 static ssize_t
scroll_step_ms_store(struct device
*dev
,
173 struct device_attribute
*attr
,
174 const char *buf
, size_t count
)
176 struct linedisp
*linedisp
= container_of(dev
, struct linedisp
, dev
);
180 err
= kstrtouint(buf
, 10, &ms
);
184 linedisp
->scroll_rate
= msecs_to_jiffies(ms
);
185 if (linedisp
->message
&& linedisp
->message_len
> linedisp
->num_chars
) {
186 del_timer_sync(&linedisp
->timer
);
187 if (linedisp
->scroll_rate
)
188 linedisp_scroll(&linedisp
->timer
);
194 static DEVICE_ATTR_RW(scroll_step_ms
);
196 static ssize_t
map_seg_show(struct device
*dev
, struct device_attribute
*attr
, char *buf
)
198 struct linedisp
*linedisp
= container_of(dev
, struct linedisp
, dev
);
199 struct linedisp_map
*map
= linedisp
->map
;
201 memcpy(buf
, &map
->map
, map
->size
);
205 static ssize_t
map_seg_store(struct device
*dev
, struct device_attribute
*attr
,
206 const char *buf
, size_t count
)
208 struct linedisp
*linedisp
= container_of(dev
, struct linedisp
, dev
);
209 struct linedisp_map
*map
= linedisp
->map
;
211 if (count
!= map
->size
)
214 memcpy(&map
->map
, buf
, count
);
218 static const SEG7_DEFAULT_MAP(initial_map_seg7
);
219 static DEVICE_ATTR(map_seg7
, 0644, map_seg_show
, map_seg_store
);
221 static const SEG14_DEFAULT_MAP(initial_map_seg14
);
222 static DEVICE_ATTR(map_seg14
, 0644, map_seg_show
, map_seg_store
);
224 static struct attribute
*linedisp_attrs
[] = {
225 &dev_attr_message
.attr
,
226 &dev_attr_scroll_step_ms
.attr
,
227 &dev_attr_map_seg7
.attr
,
228 &dev_attr_map_seg14
.attr
,
232 static umode_t
linedisp_attr_is_visible(struct kobject
*kobj
, struct attribute
*attr
, int n
)
234 struct device
*dev
= kobj_to_dev(kobj
);
235 struct linedisp
*linedisp
= container_of(dev
, struct linedisp
, dev
);
236 struct linedisp_map
*map
= linedisp
->map
;
237 umode_t mode
= attr
->mode
;
239 if (attr
== &dev_attr_map_seg7
.attr
) {
242 if (map
->type
!= LINEDISP_MAP_SEG7
)
246 if (attr
== &dev_attr_map_seg14
.attr
) {
249 if (map
->type
!= LINEDISP_MAP_SEG14
)
256 static const struct attribute_group linedisp_group
= {
257 .is_visible
= linedisp_attr_is_visible
,
258 .attrs
= linedisp_attrs
,
260 __ATTRIBUTE_GROUPS(linedisp
);
262 static DEFINE_IDA(linedisp_id
);
264 static void linedisp_release(struct device
*dev
)
266 struct linedisp
*linedisp
= container_of(dev
, struct linedisp
, dev
);
268 kfree(linedisp
->map
);
269 kfree(linedisp
->message
);
270 kfree(linedisp
->buf
);
271 ida_free(&linedisp_id
, linedisp
->id
);
274 static const struct device_type linedisp_type
= {
275 .groups
= linedisp_groups
,
276 .release
= linedisp_release
,
279 static int linedisp_init_map(struct linedisp
*linedisp
)
281 struct linedisp_map
*map
;
284 if (!linedisp
->ops
->get_map_type
)
287 err
= linedisp
->ops
->get_map_type(linedisp
);
291 map
= kmalloc(sizeof(*map
), GFP_KERNEL
);
297 /* assign initial mapping */
299 case LINEDISP_MAP_SEG7
:
300 map
->map
.seg7
= initial_map_seg7
;
301 map
->size
= sizeof(map
->map
.seg7
);
303 case LINEDISP_MAP_SEG14
:
304 map
->map
.seg14
= initial_map_seg14
;
305 map
->size
= sizeof(map
->map
.seg14
);
317 #ifdef CONFIG_PANEL_BOOT_MESSAGE
318 #define LINEDISP_INIT_TEXT CONFIG_PANEL_BOOT_MESSAGE
320 #define LINEDISP_INIT_TEXT "Linux " UTS_RELEASE " "
324 * linedisp_register - register a character line display
325 * @linedisp: pointer to character line display structure
326 * @parent: parent device
327 * @num_chars: the number of characters that can be displayed
328 * @ops: character line display operations
330 * Return: zero on success, else a negative error code.
332 int linedisp_register(struct linedisp
*linedisp
, struct device
*parent
,
333 unsigned int num_chars
, const struct linedisp_ops
*ops
)
337 memset(linedisp
, 0, sizeof(*linedisp
));
338 linedisp
->dev
.parent
= parent
;
339 linedisp
->dev
.type
= &linedisp_type
;
341 linedisp
->num_chars
= num_chars
;
342 linedisp
->scroll_rate
= DEFAULT_SCROLL_RATE
;
344 err
= ida_alloc(&linedisp_id
, GFP_KERNEL
);
349 device_initialize(&linedisp
->dev
);
350 dev_set_name(&linedisp
->dev
, "linedisp.%u", linedisp
->id
);
353 linedisp
->buf
= kzalloc(linedisp
->num_chars
, GFP_KERNEL
);
357 /* initialise a character mapping, if required */
358 err
= linedisp_init_map(linedisp
);
362 /* initialise a timer for scrolling the message */
363 timer_setup(&linedisp
->timer
, linedisp_scroll
, 0);
365 err
= device_add(&linedisp
->dev
);
369 /* display a default message */
370 err
= linedisp_display(linedisp
, LINEDISP_INIT_TEXT
, -1);
377 device_del(&linedisp
->dev
);
379 del_timer_sync(&linedisp
->timer
);
381 put_device(&linedisp
->dev
);
384 EXPORT_SYMBOL_NS_GPL(linedisp_register
, "LINEDISP");
387 * linedisp_unregister - unregister a character line display
388 * @linedisp: pointer to character line display structure registered previously
389 * with linedisp_register()
391 void linedisp_unregister(struct linedisp
*linedisp
)
393 device_del(&linedisp
->dev
);
394 del_timer_sync(&linedisp
->timer
);
395 put_device(&linedisp
->dev
);
397 EXPORT_SYMBOL_NS_GPL(linedisp_unregister
, "LINEDISP");
399 MODULE_DESCRIPTION("Character line display core support");
400 MODULE_LICENSE("GPL");