Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
torvalds
GitHub Repository: torvalds/linux
Path: blob/master/drivers/auxdisplay/line-display.c
26278 views
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 <[email protected]>
7
*
8
* Copyright (C) 2021 Glider bv
9
*/
10
11
#ifndef CONFIG_PANEL_BOOT_MESSAGE
12
#include <generated/utsrelease.h>
13
#endif
14
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>
26
27
#include <linux/map_to_7segment.h>
28
#include <linux/map_to_14segment.h>
29
30
#include "line-display.h"
31
32
#define DEFAULT_SCROLL_RATE (HZ / 2)
33
34
/**
35
* linedisp_scroll() - scroll the display by a character
36
* @t: really a pointer to the private data structure
37
*
38
* Scroll the current message along the display by one character, rearming the
39
* timer if required.
40
*/
41
static void linedisp_scroll(struct timer_list *t)
42
{
43
struct linedisp *linedisp = timer_container_of(linedisp, t, timer);
44
unsigned int i, ch = linedisp->scroll_pos;
45
unsigned int num_chars = linedisp->num_chars;
46
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];
52
53
/* wrap around to the start of the string */
54
ch = 0;
55
}
56
57
/* update the display */
58
linedisp->ops->update(linedisp);
59
60
/* move on to the next character */
61
linedisp->scroll_pos++;
62
linedisp->scroll_pos %= linedisp->message_len;
63
64
/* rearm the timer */
65
if (linedisp->message_len > num_chars && linedisp->scroll_rate)
66
mod_timer(&linedisp->timer, jiffies + linedisp->scroll_rate);
67
}
68
69
/**
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
74
*
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.
78
*
79
* Return: 0 on success, -ENOMEM on memory allocation failure
80
*/
81
static int linedisp_display(struct linedisp *linedisp, const char *msg,
82
ssize_t count)
83
{
84
char *new_msg;
85
86
/* stop the scroll timer */
87
timer_delete_sync(&linedisp->timer);
88
89
if (count == -1)
90
count = strlen(msg);
91
92
/* if the string ends with a newline, trim it */
93
if (msg[count - 1] == '\n')
94
count--;
95
96
if (!count) {
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);
103
return 0;
104
}
105
106
new_msg = kmemdup_nul(msg, count, GFP_KERNEL);
107
if (!new_msg)
108
return -ENOMEM;
109
110
kfree(linedisp->message);
111
112
linedisp->message = new_msg;
113
linedisp->message_len = count;
114
linedisp->scroll_pos = 0;
115
116
/* update the display */
117
linedisp_scroll(&linedisp->timer);
118
119
return 0;
120
}
121
122
/**
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
127
*
128
* Read the current message being displayed or scrolled across the display into
129
* @buf, for reads from sysfs.
130
*
131
* Return: the number of characters written to @buf
132
*/
133
static ssize_t message_show(struct device *dev, struct device_attribute *attr,
134
char *buf)
135
{
136
struct linedisp *linedisp = container_of(dev, struct linedisp, dev);
137
138
return sysfs_emit(buf, "%s\n", linedisp->message);
139
}
140
141
/**
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
147
*
148
* Write a new message to display or scroll across the display from sysfs.
149
*
150
* Return: the size of the message on success, else -ERRNO
151
*/
152
static ssize_t message_store(struct device *dev, struct device_attribute *attr,
153
const char *buf, size_t count)
154
{
155
struct linedisp *linedisp = container_of(dev, struct linedisp, dev);
156
int err;
157
158
err = linedisp_display(linedisp, buf, count);
159
return err ?: count;
160
}
161
162
static DEVICE_ATTR_RW(message);
163
164
static ssize_t scroll_step_ms_show(struct device *dev,
165
struct device_attribute *attr, char *buf)
166
{
167
struct linedisp *linedisp = container_of(dev, struct linedisp, dev);
168
169
return sysfs_emit(buf, "%u\n", jiffies_to_msecs(linedisp->scroll_rate));
170
}
171
172
static ssize_t scroll_step_ms_store(struct device *dev,
173
struct device_attribute *attr,
174
const char *buf, size_t count)
175
{
176
struct linedisp *linedisp = container_of(dev, struct linedisp, dev);
177
unsigned int ms;
178
int err;
179
180
err = kstrtouint(buf, 10, &ms);
181
if (err)
182
return err;
183
184
linedisp->scroll_rate = msecs_to_jiffies(ms);
185
if (linedisp->message && linedisp->message_len > linedisp->num_chars) {
186
timer_delete_sync(&linedisp->timer);
187
if (linedisp->scroll_rate)
188
linedisp_scroll(&linedisp->timer);
189
}
190
191
return count;
192
}
193
194
static DEVICE_ATTR_RW(scroll_step_ms);
195
196
static ssize_t map_seg_show(struct device *dev, struct device_attribute *attr, char *buf)
197
{
198
struct linedisp *linedisp = container_of(dev, struct linedisp, dev);
199
struct linedisp_map *map = linedisp->map;
200
201
memcpy(buf, &map->map, map->size);
202
return map->size;
203
}
204
205
static ssize_t map_seg_store(struct device *dev, struct device_attribute *attr,
206
const char *buf, size_t count)
207
{
208
struct linedisp *linedisp = container_of(dev, struct linedisp, dev);
209
struct linedisp_map *map = linedisp->map;
210
211
if (count != map->size)
212
return -EINVAL;
213
214
memcpy(&map->map, buf, count);
215
return count;
216
}
217
218
static const SEG7_DEFAULT_MAP(initial_map_seg7);
219
static DEVICE_ATTR(map_seg7, 0644, map_seg_show, map_seg_store);
220
221
static const SEG14_DEFAULT_MAP(initial_map_seg14);
222
static DEVICE_ATTR(map_seg14, 0644, map_seg_show, map_seg_store);
223
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,
229
NULL
230
};
231
232
static umode_t linedisp_attr_is_visible(struct kobject *kobj, struct attribute *attr, int n)
233
{
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;
238
239
if (attr == &dev_attr_map_seg7.attr) {
240
if (!map)
241
return 0;
242
if (map->type != LINEDISP_MAP_SEG7)
243
return 0;
244
}
245
246
if (attr == &dev_attr_map_seg14.attr) {
247
if (!map)
248
return 0;
249
if (map->type != LINEDISP_MAP_SEG14)
250
return 0;
251
}
252
253
return mode;
254
};
255
256
static const struct attribute_group linedisp_group = {
257
.is_visible = linedisp_attr_is_visible,
258
.attrs = linedisp_attrs,
259
};
260
__ATTRIBUTE_GROUPS(linedisp);
261
262
static DEFINE_IDA(linedisp_id);
263
264
static void linedisp_release(struct device *dev)
265
{
266
struct linedisp *linedisp = container_of(dev, struct linedisp, dev);
267
268
kfree(linedisp->map);
269
kfree(linedisp->message);
270
kfree(linedisp->buf);
271
ida_free(&linedisp_id, linedisp->id);
272
}
273
274
static const struct device_type linedisp_type = {
275
.groups = linedisp_groups,
276
.release = linedisp_release,
277
};
278
279
static int linedisp_init_map(struct linedisp *linedisp)
280
{
281
struct linedisp_map *map;
282
int err;
283
284
if (!linedisp->ops->get_map_type)
285
return 0;
286
287
err = linedisp->ops->get_map_type(linedisp);
288
if (err < 0)
289
return err;
290
291
map = kmalloc(sizeof(*map), GFP_KERNEL);
292
if (!map)
293
return -ENOMEM;
294
295
map->type = err;
296
297
/* assign initial mapping */
298
switch (map->type) {
299
case LINEDISP_MAP_SEG7:
300
map->map.seg7 = initial_map_seg7;
301
map->size = sizeof(map->map.seg7);
302
break;
303
case LINEDISP_MAP_SEG14:
304
map->map.seg14 = initial_map_seg14;
305
map->size = sizeof(map->map.seg14);
306
break;
307
default:
308
kfree(map);
309
return -EINVAL;
310
}
311
312
linedisp->map = map;
313
314
return 0;
315
}
316
317
#ifdef CONFIG_PANEL_BOOT_MESSAGE
318
#define LINEDISP_INIT_TEXT CONFIG_PANEL_BOOT_MESSAGE
319
#else
320
#define LINEDISP_INIT_TEXT "Linux " UTS_RELEASE " "
321
#endif
322
323
/**
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
329
*
330
* Return: zero on success, else a negative error code.
331
*/
332
int linedisp_register(struct linedisp *linedisp, struct device *parent,
333
unsigned int num_chars, const struct linedisp_ops *ops)
334
{
335
int err;
336
337
memset(linedisp, 0, sizeof(*linedisp));
338
linedisp->dev.parent = parent;
339
linedisp->dev.type = &linedisp_type;
340
linedisp->ops = ops;
341
linedisp->num_chars = num_chars;
342
linedisp->scroll_rate = DEFAULT_SCROLL_RATE;
343
344
err = ida_alloc(&linedisp_id, GFP_KERNEL);
345
if (err < 0)
346
return err;
347
linedisp->id = err;
348
349
device_initialize(&linedisp->dev);
350
dev_set_name(&linedisp->dev, "linedisp.%u", linedisp->id);
351
352
err = -ENOMEM;
353
linedisp->buf = kzalloc(linedisp->num_chars, GFP_KERNEL);
354
if (!linedisp->buf)
355
goto out_put_device;
356
357
/* initialise a character mapping, if required */
358
err = linedisp_init_map(linedisp);
359
if (err)
360
goto out_put_device;
361
362
/* initialise a timer for scrolling the message */
363
timer_setup(&linedisp->timer, linedisp_scroll, 0);
364
365
err = device_add(&linedisp->dev);
366
if (err)
367
goto out_del_timer;
368
369
/* display a default message */
370
err = linedisp_display(linedisp, LINEDISP_INIT_TEXT, -1);
371
if (err)
372
goto out_del_dev;
373
374
return 0;
375
376
out_del_dev:
377
device_del(&linedisp->dev);
378
out_del_timer:
379
timer_delete_sync(&linedisp->timer);
380
out_put_device:
381
put_device(&linedisp->dev);
382
return err;
383
}
384
EXPORT_SYMBOL_NS_GPL(linedisp_register, "LINEDISP");
385
386
/**
387
* linedisp_unregister - unregister a character line display
388
* @linedisp: pointer to character line display structure registered previously
389
* with linedisp_register()
390
*/
391
void linedisp_unregister(struct linedisp *linedisp)
392
{
393
device_del(&linedisp->dev);
394
timer_delete_sync(&linedisp->timer);
395
put_device(&linedisp->dev);
396
}
397
EXPORT_SYMBOL_NS_GPL(linedisp_unregister, "LINEDISP");
398
399
MODULE_DESCRIPTION("Character line display core support");
400
MODULE_LICENSE("GPL");
401
402