162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Character line display core support 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright (C) 2016 Imagination Technologies 662306a36Sopenharmony_ci * Author: Paul Burton <paul.burton@mips.com> 762306a36Sopenharmony_ci * 862306a36Sopenharmony_ci * Copyright (C) 2021 Glider bv 962306a36Sopenharmony_ci */ 1062306a36Sopenharmony_ci 1162306a36Sopenharmony_ci#include <generated/utsrelease.h> 1262306a36Sopenharmony_ci 1362306a36Sopenharmony_ci#include <linux/device.h> 1462306a36Sopenharmony_ci#include <linux/module.h> 1562306a36Sopenharmony_ci#include <linux/slab.h> 1662306a36Sopenharmony_ci#include <linux/string.h> 1762306a36Sopenharmony_ci#include <linux/sysfs.h> 1862306a36Sopenharmony_ci#include <linux/timer.h> 1962306a36Sopenharmony_ci 2062306a36Sopenharmony_ci#include "line-display.h" 2162306a36Sopenharmony_ci 2262306a36Sopenharmony_ci#define DEFAULT_SCROLL_RATE (HZ / 2) 2362306a36Sopenharmony_ci 2462306a36Sopenharmony_ci/** 2562306a36Sopenharmony_ci * linedisp_scroll() - scroll the display by a character 2662306a36Sopenharmony_ci * @t: really a pointer to the private data structure 2762306a36Sopenharmony_ci * 2862306a36Sopenharmony_ci * Scroll the current message along the display by one character, rearming the 2962306a36Sopenharmony_ci * timer if required. 3062306a36Sopenharmony_ci */ 3162306a36Sopenharmony_cistatic void linedisp_scroll(struct timer_list *t) 3262306a36Sopenharmony_ci{ 3362306a36Sopenharmony_ci struct linedisp *linedisp = from_timer(linedisp, t, timer); 3462306a36Sopenharmony_ci unsigned int i, ch = linedisp->scroll_pos; 3562306a36Sopenharmony_ci unsigned int num_chars = linedisp->num_chars; 3662306a36Sopenharmony_ci 3762306a36Sopenharmony_ci /* update the current message string */ 3862306a36Sopenharmony_ci for (i = 0; i < num_chars;) { 3962306a36Sopenharmony_ci /* copy as many characters from the string as possible */ 4062306a36Sopenharmony_ci for (; i < num_chars && ch < linedisp->message_len; i++, ch++) 4162306a36Sopenharmony_ci linedisp->buf[i] = linedisp->message[ch]; 4262306a36Sopenharmony_ci 4362306a36Sopenharmony_ci /* wrap around to the start of the string */ 4462306a36Sopenharmony_ci ch = 0; 4562306a36Sopenharmony_ci } 4662306a36Sopenharmony_ci 4762306a36Sopenharmony_ci /* update the display */ 4862306a36Sopenharmony_ci linedisp->update(linedisp); 4962306a36Sopenharmony_ci 5062306a36Sopenharmony_ci /* move on to the next character */ 5162306a36Sopenharmony_ci linedisp->scroll_pos++; 5262306a36Sopenharmony_ci linedisp->scroll_pos %= linedisp->message_len; 5362306a36Sopenharmony_ci 5462306a36Sopenharmony_ci /* rearm the timer */ 5562306a36Sopenharmony_ci if (linedisp->message_len > num_chars && linedisp->scroll_rate) 5662306a36Sopenharmony_ci mod_timer(&linedisp->timer, jiffies + linedisp->scroll_rate); 5762306a36Sopenharmony_ci} 5862306a36Sopenharmony_ci 5962306a36Sopenharmony_ci/** 6062306a36Sopenharmony_ci * linedisp_display() - set the message to be displayed 6162306a36Sopenharmony_ci * @linedisp: pointer to the private data structure 6262306a36Sopenharmony_ci * @msg: the message to display 6362306a36Sopenharmony_ci * @count: length of msg, or -1 6462306a36Sopenharmony_ci * 6562306a36Sopenharmony_ci * Display a new message @msg on the display. @msg can be longer than the 6662306a36Sopenharmony_ci * number of characters the display can display, in which case it will begin 6762306a36Sopenharmony_ci * scrolling across the display. 6862306a36Sopenharmony_ci * 6962306a36Sopenharmony_ci * Return: 0 on success, -ENOMEM on memory allocation failure 7062306a36Sopenharmony_ci */ 7162306a36Sopenharmony_cistatic int linedisp_display(struct linedisp *linedisp, const char *msg, 7262306a36Sopenharmony_ci ssize_t count) 7362306a36Sopenharmony_ci{ 7462306a36Sopenharmony_ci char *new_msg; 7562306a36Sopenharmony_ci 7662306a36Sopenharmony_ci /* stop the scroll timer */ 7762306a36Sopenharmony_ci del_timer_sync(&linedisp->timer); 7862306a36Sopenharmony_ci 7962306a36Sopenharmony_ci if (count == -1) 8062306a36Sopenharmony_ci count = strlen(msg); 8162306a36Sopenharmony_ci 8262306a36Sopenharmony_ci /* if the string ends with a newline, trim it */ 8362306a36Sopenharmony_ci if (msg[count - 1] == '\n') 8462306a36Sopenharmony_ci count--; 8562306a36Sopenharmony_ci 8662306a36Sopenharmony_ci if (!count) { 8762306a36Sopenharmony_ci /* Clear the display */ 8862306a36Sopenharmony_ci kfree(linedisp->message); 8962306a36Sopenharmony_ci linedisp->message = NULL; 9062306a36Sopenharmony_ci linedisp->message_len = 0; 9162306a36Sopenharmony_ci memset(linedisp->buf, ' ', linedisp->num_chars); 9262306a36Sopenharmony_ci linedisp->update(linedisp); 9362306a36Sopenharmony_ci return 0; 9462306a36Sopenharmony_ci } 9562306a36Sopenharmony_ci 9662306a36Sopenharmony_ci new_msg = kmemdup_nul(msg, count, GFP_KERNEL); 9762306a36Sopenharmony_ci if (!new_msg) 9862306a36Sopenharmony_ci return -ENOMEM; 9962306a36Sopenharmony_ci 10062306a36Sopenharmony_ci kfree(linedisp->message); 10162306a36Sopenharmony_ci 10262306a36Sopenharmony_ci linedisp->message = new_msg; 10362306a36Sopenharmony_ci linedisp->message_len = count; 10462306a36Sopenharmony_ci linedisp->scroll_pos = 0; 10562306a36Sopenharmony_ci 10662306a36Sopenharmony_ci /* update the display */ 10762306a36Sopenharmony_ci linedisp_scroll(&linedisp->timer); 10862306a36Sopenharmony_ci 10962306a36Sopenharmony_ci return 0; 11062306a36Sopenharmony_ci} 11162306a36Sopenharmony_ci 11262306a36Sopenharmony_ci/** 11362306a36Sopenharmony_ci * message_show() - read message via sysfs 11462306a36Sopenharmony_ci * @dev: the display device 11562306a36Sopenharmony_ci * @attr: the display message attribute 11662306a36Sopenharmony_ci * @buf: the buffer to read the message into 11762306a36Sopenharmony_ci * 11862306a36Sopenharmony_ci * Read the current message being displayed or scrolled across the display into 11962306a36Sopenharmony_ci * @buf, for reads from sysfs. 12062306a36Sopenharmony_ci * 12162306a36Sopenharmony_ci * Return: the number of characters written to @buf 12262306a36Sopenharmony_ci */ 12362306a36Sopenharmony_cistatic ssize_t message_show(struct device *dev, struct device_attribute *attr, 12462306a36Sopenharmony_ci char *buf) 12562306a36Sopenharmony_ci{ 12662306a36Sopenharmony_ci struct linedisp *linedisp = container_of(dev, struct linedisp, dev); 12762306a36Sopenharmony_ci 12862306a36Sopenharmony_ci return sysfs_emit(buf, "%s\n", linedisp->message); 12962306a36Sopenharmony_ci} 13062306a36Sopenharmony_ci 13162306a36Sopenharmony_ci/** 13262306a36Sopenharmony_ci * message_store() - write a new message via sysfs 13362306a36Sopenharmony_ci * @dev: the display device 13462306a36Sopenharmony_ci * @attr: the display message attribute 13562306a36Sopenharmony_ci * @buf: the buffer containing the new message 13662306a36Sopenharmony_ci * @count: the size of the message in @buf 13762306a36Sopenharmony_ci * 13862306a36Sopenharmony_ci * Write a new message to display or scroll across the display from sysfs. 13962306a36Sopenharmony_ci * 14062306a36Sopenharmony_ci * Return: the size of the message on success, else -ERRNO 14162306a36Sopenharmony_ci */ 14262306a36Sopenharmony_cistatic ssize_t message_store(struct device *dev, struct device_attribute *attr, 14362306a36Sopenharmony_ci const char *buf, size_t count) 14462306a36Sopenharmony_ci{ 14562306a36Sopenharmony_ci struct linedisp *linedisp = container_of(dev, struct linedisp, dev); 14662306a36Sopenharmony_ci int err; 14762306a36Sopenharmony_ci 14862306a36Sopenharmony_ci err = linedisp_display(linedisp, buf, count); 14962306a36Sopenharmony_ci return err ?: count; 15062306a36Sopenharmony_ci} 15162306a36Sopenharmony_ci 15262306a36Sopenharmony_cistatic DEVICE_ATTR_RW(message); 15362306a36Sopenharmony_ci 15462306a36Sopenharmony_cistatic ssize_t scroll_step_ms_show(struct device *dev, 15562306a36Sopenharmony_ci struct device_attribute *attr, char *buf) 15662306a36Sopenharmony_ci{ 15762306a36Sopenharmony_ci struct linedisp *linedisp = container_of(dev, struct linedisp, dev); 15862306a36Sopenharmony_ci 15962306a36Sopenharmony_ci return sysfs_emit(buf, "%u\n", jiffies_to_msecs(linedisp->scroll_rate)); 16062306a36Sopenharmony_ci} 16162306a36Sopenharmony_ci 16262306a36Sopenharmony_cistatic ssize_t scroll_step_ms_store(struct device *dev, 16362306a36Sopenharmony_ci struct device_attribute *attr, 16462306a36Sopenharmony_ci const char *buf, size_t count) 16562306a36Sopenharmony_ci{ 16662306a36Sopenharmony_ci struct linedisp *linedisp = container_of(dev, struct linedisp, dev); 16762306a36Sopenharmony_ci unsigned int ms; 16862306a36Sopenharmony_ci 16962306a36Sopenharmony_ci if (kstrtouint(buf, 10, &ms) != 0) 17062306a36Sopenharmony_ci return -EINVAL; 17162306a36Sopenharmony_ci 17262306a36Sopenharmony_ci linedisp->scroll_rate = msecs_to_jiffies(ms); 17362306a36Sopenharmony_ci if (linedisp->message && linedisp->message_len > linedisp->num_chars) { 17462306a36Sopenharmony_ci del_timer_sync(&linedisp->timer); 17562306a36Sopenharmony_ci if (linedisp->scroll_rate) 17662306a36Sopenharmony_ci linedisp_scroll(&linedisp->timer); 17762306a36Sopenharmony_ci } 17862306a36Sopenharmony_ci 17962306a36Sopenharmony_ci return count; 18062306a36Sopenharmony_ci} 18162306a36Sopenharmony_ci 18262306a36Sopenharmony_cistatic DEVICE_ATTR_RW(scroll_step_ms); 18362306a36Sopenharmony_ci 18462306a36Sopenharmony_cistatic struct attribute *linedisp_attrs[] = { 18562306a36Sopenharmony_ci &dev_attr_message.attr, 18662306a36Sopenharmony_ci &dev_attr_scroll_step_ms.attr, 18762306a36Sopenharmony_ci NULL, 18862306a36Sopenharmony_ci}; 18962306a36Sopenharmony_ciATTRIBUTE_GROUPS(linedisp); 19062306a36Sopenharmony_ci 19162306a36Sopenharmony_cistatic const struct device_type linedisp_type = { 19262306a36Sopenharmony_ci .groups = linedisp_groups, 19362306a36Sopenharmony_ci}; 19462306a36Sopenharmony_ci 19562306a36Sopenharmony_ci/** 19662306a36Sopenharmony_ci * linedisp_register - register a character line display 19762306a36Sopenharmony_ci * @linedisp: pointer to character line display structure 19862306a36Sopenharmony_ci * @parent: parent device 19962306a36Sopenharmony_ci * @num_chars: the number of characters that can be displayed 20062306a36Sopenharmony_ci * @buf: pointer to a buffer that can hold @num_chars characters 20162306a36Sopenharmony_ci * @update: Function called to update the display. This must not sleep! 20262306a36Sopenharmony_ci * 20362306a36Sopenharmony_ci * Return: zero on success, else a negative error code. 20462306a36Sopenharmony_ci */ 20562306a36Sopenharmony_ciint linedisp_register(struct linedisp *linedisp, struct device *parent, 20662306a36Sopenharmony_ci unsigned int num_chars, char *buf, 20762306a36Sopenharmony_ci void (*update)(struct linedisp *linedisp)) 20862306a36Sopenharmony_ci{ 20962306a36Sopenharmony_ci static atomic_t linedisp_id = ATOMIC_INIT(-1); 21062306a36Sopenharmony_ci int err; 21162306a36Sopenharmony_ci 21262306a36Sopenharmony_ci memset(linedisp, 0, sizeof(*linedisp)); 21362306a36Sopenharmony_ci linedisp->dev.parent = parent; 21462306a36Sopenharmony_ci linedisp->dev.type = &linedisp_type; 21562306a36Sopenharmony_ci linedisp->update = update; 21662306a36Sopenharmony_ci linedisp->buf = buf; 21762306a36Sopenharmony_ci linedisp->num_chars = num_chars; 21862306a36Sopenharmony_ci linedisp->scroll_rate = DEFAULT_SCROLL_RATE; 21962306a36Sopenharmony_ci 22062306a36Sopenharmony_ci device_initialize(&linedisp->dev); 22162306a36Sopenharmony_ci dev_set_name(&linedisp->dev, "linedisp.%lu", 22262306a36Sopenharmony_ci (unsigned long)atomic_inc_return(&linedisp_id)); 22362306a36Sopenharmony_ci 22462306a36Sopenharmony_ci /* initialise a timer for scrolling the message */ 22562306a36Sopenharmony_ci timer_setup(&linedisp->timer, linedisp_scroll, 0); 22662306a36Sopenharmony_ci 22762306a36Sopenharmony_ci err = device_add(&linedisp->dev); 22862306a36Sopenharmony_ci if (err) 22962306a36Sopenharmony_ci goto out_del_timer; 23062306a36Sopenharmony_ci 23162306a36Sopenharmony_ci /* display a default message */ 23262306a36Sopenharmony_ci err = linedisp_display(linedisp, "Linux " UTS_RELEASE " ", -1); 23362306a36Sopenharmony_ci if (err) 23462306a36Sopenharmony_ci goto out_del_dev; 23562306a36Sopenharmony_ci 23662306a36Sopenharmony_ci return 0; 23762306a36Sopenharmony_ci 23862306a36Sopenharmony_ciout_del_dev: 23962306a36Sopenharmony_ci device_del(&linedisp->dev); 24062306a36Sopenharmony_ciout_del_timer: 24162306a36Sopenharmony_ci del_timer_sync(&linedisp->timer); 24262306a36Sopenharmony_ci put_device(&linedisp->dev); 24362306a36Sopenharmony_ci return err; 24462306a36Sopenharmony_ci} 24562306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(linedisp_register); 24662306a36Sopenharmony_ci 24762306a36Sopenharmony_ci/** 24862306a36Sopenharmony_ci * linedisp_unregister - unregister a character line display 24962306a36Sopenharmony_ci * @linedisp: pointer to character line display structure registered previously 25062306a36Sopenharmony_ci * with linedisp_register() 25162306a36Sopenharmony_ci */ 25262306a36Sopenharmony_civoid linedisp_unregister(struct linedisp *linedisp) 25362306a36Sopenharmony_ci{ 25462306a36Sopenharmony_ci device_del(&linedisp->dev); 25562306a36Sopenharmony_ci del_timer_sync(&linedisp->timer); 25662306a36Sopenharmony_ci kfree(linedisp->message); 25762306a36Sopenharmony_ci put_device(&linedisp->dev); 25862306a36Sopenharmony_ci} 25962306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(linedisp_unregister); 26062306a36Sopenharmony_ci 26162306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 262