diff options
Diffstat (limited to 'drivers/auxdisplay/line-display.c')
-rw-r--r-- | drivers/auxdisplay/line-display.c | 166 |
1 files changed, 148 insertions, 18 deletions
diff --git a/drivers/auxdisplay/line-display.c b/drivers/auxdisplay/line-display.c index 03e7f104aa..e2b546210f 100644 --- a/drivers/auxdisplay/line-display.c +++ b/drivers/auxdisplay/line-display.c @@ -10,13 +10,21 @@ #include <generated/utsrelease.h> +#include <linux/container_of.h> #include <linux/device.h> +#include <linux/export.h> +#include <linux/idr.h> +#include <linux/jiffies.h> +#include <linux/kstrtox.h> #include <linux/module.h> #include <linux/slab.h> #include <linux/string.h> #include <linux/sysfs.h> #include <linux/timer.h> +#include <linux/map_to_7segment.h> +#include <linux/map_to_14segment.h> + #include "line-display.h" #define DEFAULT_SCROLL_RATE (HZ / 2) @@ -45,7 +53,7 @@ static void linedisp_scroll(struct timer_list *t) } /* update the display */ - linedisp->update(linedisp); + linedisp->ops->update(linedisp); /* move on to the next character */ linedisp->scroll_pos++; @@ -89,7 +97,7 @@ static int linedisp_display(struct linedisp *linedisp, const char *msg, linedisp->message = NULL; linedisp->message_len = 0; memset(linedisp->buf, ' ', linedisp->num_chars); - linedisp->update(linedisp); + linedisp->ops->update(linedisp); return 0; } @@ -165,9 +173,11 @@ static ssize_t scroll_step_ms_store(struct device *dev, { struct linedisp *linedisp = container_of(dev, struct linedisp, dev); unsigned int ms; + int err; - if (kstrtouint(buf, 10, &ms) != 0) - return -EINVAL; + err = kstrtouint(buf, 10, &ms); + if (err) + return err; linedisp->scroll_rate = msecs_to_jiffies(ms); if (linedisp->message && linedisp->message_len > linedisp->num_chars) { @@ -181,45 +191,165 @@ static ssize_t scroll_step_ms_store(struct device *dev, static DEVICE_ATTR_RW(scroll_step_ms); +static ssize_t map_seg_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct linedisp *linedisp = container_of(dev, struct linedisp, dev); + struct linedisp_map *map = linedisp->map; + + memcpy(buf, &map->map, map->size); + return map->size; +} + +static ssize_t map_seg_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct linedisp *linedisp = container_of(dev, struct linedisp, dev); + struct linedisp_map *map = linedisp->map; + + if (count != map->size) + return -EINVAL; + + memcpy(&map->map, buf, count); + return count; +} + +static const SEG7_DEFAULT_MAP(initial_map_seg7); +static DEVICE_ATTR(map_seg7, 0644, map_seg_show, map_seg_store); + +static const SEG14_DEFAULT_MAP(initial_map_seg14); +static DEVICE_ATTR(map_seg14, 0644, map_seg_show, map_seg_store); + static struct attribute *linedisp_attrs[] = { &dev_attr_message.attr, &dev_attr_scroll_step_ms.attr, - NULL, + &dev_attr_map_seg7.attr, + &dev_attr_map_seg14.attr, + NULL +}; + +static umode_t linedisp_attr_is_visible(struct kobject *kobj, struct attribute *attr, int n) +{ + struct device *dev = kobj_to_dev(kobj); + struct linedisp *linedisp = container_of(dev, struct linedisp, dev); + struct linedisp_map *map = linedisp->map; + umode_t mode = attr->mode; + + if (attr == &dev_attr_map_seg7.attr) { + if (!map) + return 0; + if (map->type != LINEDISP_MAP_SEG7) + return 0; + } + + if (attr == &dev_attr_map_seg14.attr) { + if (!map) + return 0; + if (map->type != LINEDISP_MAP_SEG14) + return 0; + } + + return mode; }; -ATTRIBUTE_GROUPS(linedisp); + +static const struct attribute_group linedisp_group = { + .is_visible = linedisp_attr_is_visible, + .attrs = linedisp_attrs, +}; +__ATTRIBUTE_GROUPS(linedisp); + +static DEFINE_IDA(linedisp_id); + +static void linedisp_release(struct device *dev) +{ + struct linedisp *linedisp = container_of(dev, struct linedisp, dev); + + kfree(linedisp->map); + kfree(linedisp->message); + kfree(linedisp->buf); + ida_free(&linedisp_id, linedisp->id); +} static const struct device_type linedisp_type = { .groups = linedisp_groups, + .release = linedisp_release, }; +static int linedisp_init_map(struct linedisp *linedisp) +{ + struct linedisp_map *map; + int err; + + if (!linedisp->ops->get_map_type) + return 0; + + err = linedisp->ops->get_map_type(linedisp); + if (err < 0) + return err; + + map = kmalloc(sizeof(*map), GFP_KERNEL); + if (!map) + return -ENOMEM; + + map->type = err; + + /* assign initial mapping */ + switch (map->type) { + case LINEDISP_MAP_SEG7: + map->map.seg7 = initial_map_seg7; + map->size = sizeof(map->map.seg7); + break; + case LINEDISP_MAP_SEG14: + map->map.seg14 = initial_map_seg14; + map->size = sizeof(map->map.seg14); + break; + default: + kfree(map); + return -EINVAL; + } + + linedisp->map = map; + + return 0; +} + /** * linedisp_register - register a character line display * @linedisp: pointer to character line display structure * @parent: parent device * @num_chars: the number of characters that can be displayed - * @buf: pointer to a buffer that can hold @num_chars characters - * @update: Function called to update the display. This must not sleep! + * @ops: character line display operations * * Return: zero on success, else a negative error code. */ int linedisp_register(struct linedisp *linedisp, struct device *parent, - unsigned int num_chars, char *buf, - void (*update)(struct linedisp *linedisp)) + unsigned int num_chars, const struct linedisp_ops *ops) { - static atomic_t linedisp_id = ATOMIC_INIT(-1); int err; memset(linedisp, 0, sizeof(*linedisp)); linedisp->dev.parent = parent; linedisp->dev.type = &linedisp_type; - linedisp->update = update; - linedisp->buf = buf; + linedisp->ops = ops; linedisp->num_chars = num_chars; linedisp->scroll_rate = DEFAULT_SCROLL_RATE; + err = ida_alloc(&linedisp_id, GFP_KERNEL); + if (err < 0) + return err; + linedisp->id = err; + device_initialize(&linedisp->dev); - dev_set_name(&linedisp->dev, "linedisp.%lu", - (unsigned long)atomic_inc_return(&linedisp_id)); + dev_set_name(&linedisp->dev, "linedisp.%u", linedisp->id); + + err = -ENOMEM; + linedisp->buf = kzalloc(linedisp->num_chars, GFP_KERNEL); + if (!linedisp->buf) + goto out_put_device; + + /* initialise a character mapping, if required */ + err = linedisp_init_map(linedisp); + if (err) + goto out_put_device; /* initialise a timer for scrolling the message */ timer_setup(&linedisp->timer, linedisp_scroll, 0); @@ -239,10 +369,11 @@ out_del_dev: device_del(&linedisp->dev); out_del_timer: del_timer_sync(&linedisp->timer); +out_put_device: put_device(&linedisp->dev); return err; } -EXPORT_SYMBOL_GPL(linedisp_register); +EXPORT_SYMBOL_NS_GPL(linedisp_register, LINEDISP); /** * linedisp_unregister - unregister a character line display @@ -253,9 +384,8 @@ void linedisp_unregister(struct linedisp *linedisp) { device_del(&linedisp->dev); del_timer_sync(&linedisp->timer); - kfree(linedisp->message); put_device(&linedisp->dev); } -EXPORT_SYMBOL_GPL(linedisp_unregister); +EXPORT_SYMBOL_NS_GPL(linedisp_unregister, LINEDISP); MODULE_LICENSE("GPL"); |