diff options
Diffstat (limited to 'drivers/auxdisplay/img-ascii-lcd.c')
| -rw-r--r-- | drivers/auxdisplay/img-ascii-lcd.c | 278 |
1 files changed, 66 insertions, 212 deletions
diff --git a/drivers/auxdisplay/img-ascii-lcd.c b/drivers/auxdisplay/img-ascii-lcd.c index 25306fa27251..32e1863ef4b2 100644 --- a/drivers/auxdisplay/img-ascii-lcd.c +++ b/drivers/auxdisplay/img-ascii-lcd.c @@ -1,24 +1,20 @@ +// SPDX-License-Identifier: GPL-2.0-or-later /* * Copyright (C) 2016 Imagination Technologies - * Author: Paul Burton <paul.burton@imgtec.com> - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the - * Free Software Foundation; either version 2 of the License, or (at your - * option) any later version. + * Author: Paul Burton <paul.burton@mips.com> */ -#include <generated/utsrelease.h> #include <linux/kernel.h> #include <linux/io.h> #include <linux/mfd/syscon.h> #include <linux/module.h> -#include <linux/of_address.h> -#include <linux/of_platform.h> +#include <linux/of.h> #include <linux/platform_device.h> +#include <linux/property.h> #include <linux/regmap.h> #include <linux/slab.h> -#include <linux/sysfs.h> + +#include "line-display.h" struct img_ascii_lcd_ctx; @@ -26,82 +22,74 @@ struct img_ascii_lcd_ctx; * struct img_ascii_lcd_config - Configuration information about an LCD model * @num_chars: the number of characters the LCD can display * @external_regmap: true if registers are in a system controller, else false - * @update: function called to update the LCD + * @ops: character line display operations */ struct img_ascii_lcd_config { unsigned int num_chars; bool external_regmap; - void (*update)(struct img_ascii_lcd_ctx *ctx); + const struct linedisp_ops ops; }; /** * struct img_ascii_lcd_ctx - Private data structure - * @pdev: the ASCII LCD platform device + * @linedisp: line display structure * @base: the base address of the LCD registers * @regmap: the regmap through which LCD registers are accessed * @offset: the offset within regmap to the start of the LCD registers - * @cfg: pointer to the LCD model configuration - * @message: the full message to display or scroll on the LCD - * @message_len: the length of the @message string - * @scroll_pos: index of the first character of @message currently displayed - * @scroll_rate: scroll interval in jiffies - * @timer: timer used to implement scrolling - * @curr: the string currently displayed on the LCD */ struct img_ascii_lcd_ctx { - struct platform_device *pdev; + struct linedisp linedisp; union { void __iomem *base; struct regmap *regmap; }; u32 offset; - const struct img_ascii_lcd_config *cfg; - char *message; - unsigned int message_len; - unsigned int scroll_pos; - unsigned int scroll_rate; - struct timer_list timer; - char curr[] __aligned(8); }; /* * MIPS Boston development board */ -static void boston_update(struct img_ascii_lcd_ctx *ctx) +static void boston_update(struct linedisp *linedisp) { + struct img_ascii_lcd_ctx *ctx = + container_of(linedisp, struct img_ascii_lcd_ctx, linedisp); ulong val; #if BITS_PER_LONG == 64 - val = *((u64 *)&ctx->curr[0]); + val = *((u64 *)&linedisp->buf[0]); __raw_writeq(val, ctx->base); #elif BITS_PER_LONG == 32 - val = *((u32 *)&ctx->curr[0]); + val = *((u32 *)&linedisp->buf[0]); __raw_writel(val, ctx->base); - val = *((u32 *)&ctx->curr[4]); + val = *((u32 *)&linedisp->buf[4]); __raw_writel(val, ctx->base + 4); #else # error Not 32 or 64 bit #endif } -static struct img_ascii_lcd_config boston_config = { +static const struct img_ascii_lcd_config boston_config = { .num_chars = 8, - .update = boston_update, + .ops = { + .update = boston_update, + }, }; /* * MIPS Malta development board */ -static void malta_update(struct img_ascii_lcd_ctx *ctx) +static void malta_update(struct linedisp *linedisp) { + struct img_ascii_lcd_ctx *ctx = + container_of(linedisp, struct img_ascii_lcd_ctx, linedisp); unsigned int i; - int err; + int err = 0; - for (i = 0; i < ctx->cfg->num_chars; i++) { + for (i = 0; i < linedisp->num_chars; i++) { err = regmap_write(ctx->regmap, - ctx->offset + (i * 8), ctx->curr[i]); + ctx->offset + (i * 8), linedisp->buf[i]); if (err) break; } @@ -110,10 +98,12 @@ static void malta_update(struct img_ascii_lcd_ctx *ctx) pr_err_ratelimited("Failed to update LCD display: %d\n", err); } -static struct img_ascii_lcd_config malta_config = { +static const struct img_ascii_lcd_config malta_config = { .num_chars = 8, .external_regmap = true, - .update = malta_update, + .ops = { + .update = malta_update, + }, }; /* @@ -177,12 +167,14 @@ static int sead3_wait_lcd_idle(struct img_ascii_lcd_ctx *ctx) return 0; } -static void sead3_update(struct img_ascii_lcd_ctx *ctx) +static void sead3_update(struct linedisp *linedisp) { + struct img_ascii_lcd_ctx *ctx = + container_of(linedisp, struct img_ascii_lcd_ctx, linedisp); unsigned int i; - int err; + int err = 0; - for (i = 0; i < ctx->cfg->num_chars; i++) { + for (i = 0; i < linedisp->num_chars; i++) { err = sead3_wait_lcd_idle(ctx); if (err) break; @@ -199,7 +191,7 @@ static void sead3_update(struct img_ascii_lcd_ctx *ctx) err = regmap_write(ctx->regmap, ctx->offset + SEAD3_REG_LCD_DATA, - ctx->curr[i]); + linedisp->buf[i]); if (err) break; } @@ -208,10 +200,12 @@ static void sead3_update(struct img_ascii_lcd_ctx *ctx) pr_err_ratelimited("Failed to update LCD display: %d\n", err); } -static struct img_ascii_lcd_config sead3_config = { +static const struct img_ascii_lcd_config sead3_config = { .num_chars = 16, .external_regmap = true, - .update = sead3_update, + .ops = { + .update = sead3_update, + }, }; static const struct of_device_id img_ascii_lcd_matches[] = { @@ -223,130 +217,6 @@ static const struct of_device_id img_ascii_lcd_matches[] = { MODULE_DEVICE_TABLE(of, img_ascii_lcd_matches); /** - * img_ascii_lcd_scroll() - scroll the display by a character - * @arg: really a pointer to the private data structure - * - * Scroll the current message along the LCD by one character, rearming the - * timer if required. - */ -static void img_ascii_lcd_scroll(unsigned long arg) -{ - struct img_ascii_lcd_ctx *ctx = (struct img_ascii_lcd_ctx *)arg; - unsigned int i, ch = ctx->scroll_pos; - unsigned int num_chars = ctx->cfg->num_chars; - - /* update the current message string */ - for (i = 0; i < num_chars;) { - /* copy as many characters from the string as possible */ - for (; i < num_chars && ch < ctx->message_len; i++, ch++) - ctx->curr[i] = ctx->message[ch]; - - /* wrap around to the start of the string */ - ch = 0; - } - - /* update the LCD */ - ctx->cfg->update(ctx); - - /* move on to the next character */ - ctx->scroll_pos++; - ctx->scroll_pos %= ctx->message_len; - - /* rearm the timer */ - if (ctx->message_len > ctx->cfg->num_chars) - mod_timer(&ctx->timer, jiffies + ctx->scroll_rate); -} - -/** - * img_ascii_lcd_display() - set the message to be displayed - * @ctx: pointer to the private data structure - * @msg: the message to display - * @count: length of msg, or -1 - * - * Display a new message @msg on the LCD. @msg can be longer than the number of - * characters the LCD can display, in which case it will begin scrolling across - * the LCD display. - * - * Return: 0 on success, -ENOMEM on memory allocation failure - */ -static int img_ascii_lcd_display(struct img_ascii_lcd_ctx *ctx, - const char *msg, ssize_t count) -{ - char *new_msg; - - /* stop the scroll timer */ - del_timer_sync(&ctx->timer); - - if (count == -1) - count = strlen(msg); - - /* if the string ends with a newline, trim it */ - if (msg[count - 1] == '\n') - count--; - - new_msg = devm_kmalloc(&ctx->pdev->dev, count + 1, GFP_KERNEL); - if (!new_msg) - return -ENOMEM; - - memcpy(new_msg, msg, count); - new_msg[count] = 0; - - if (ctx->message) - devm_kfree(&ctx->pdev->dev, ctx->message); - - ctx->message = new_msg; - ctx->message_len = count; - ctx->scroll_pos = 0; - - /* update the LCD */ - img_ascii_lcd_scroll((unsigned long)ctx); - - return 0; -} - -/** - * message_show() - read message via sysfs - * @dev: the LCD device - * @attr: the LCD message attribute - * @buf: the buffer to read the message into - * - * Read the current message being displayed or scrolled across the LCD display - * into @buf, for reads from sysfs. - * - * Return: the number of characters written to @buf - */ -static ssize_t message_show(struct device *dev, struct device_attribute *attr, - char *buf) -{ - struct img_ascii_lcd_ctx *ctx = dev_get_drvdata(dev); - - return sprintf(buf, "%s\n", ctx->message); -} - -/** - * message_store() - write a new message via sysfs - * @dev: the LCD device - * @attr: the LCD message attribute - * @buf: the buffer containing the new message - * @count: the size of the message in @buf - * - * Write a new message to display or scroll across the LCD display from sysfs. - * - * Return: the size of the message on success, else -ERRNO - */ -static ssize_t message_store(struct device *dev, struct device_attribute *attr, - const char *buf, size_t count) -{ - struct img_ascii_lcd_ctx *ctx = dev_get_drvdata(dev); - int err; - - err = img_ascii_lcd_display(ctx, buf, count); - return err ?: count; -} - -static DEVICE_ATTR_RW(message); - -/** * img_ascii_lcd_probe() - probe an LCD display device * @pdev: the LCD platform device * @@ -357,62 +227,44 @@ static DEVICE_ATTR_RW(message); */ static int img_ascii_lcd_probe(struct platform_device *pdev) { - const struct of_device_id *match; - const struct img_ascii_lcd_config *cfg; + struct device *dev = &pdev->dev; + const struct img_ascii_lcd_config *cfg = device_get_match_data(dev); struct img_ascii_lcd_ctx *ctx; - struct resource *res; int err; - match = of_match_device(img_ascii_lcd_matches, &pdev->dev); - if (!match) - return -ENODEV; - - cfg = match->data; - ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx) + cfg->num_chars, - GFP_KERNEL); + ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL); if (!ctx) return -ENOMEM; if (cfg->external_regmap) { - ctx->regmap = syscon_node_to_regmap(pdev->dev.parent->of_node); + ctx->regmap = syscon_node_to_regmap(dev->parent->of_node); if (IS_ERR(ctx->regmap)) return PTR_ERR(ctx->regmap); - if (of_property_read_u32(pdev->dev.of_node, "offset", - &ctx->offset)) + if (of_property_read_u32(dev->of_node, "offset", &ctx->offset)) return -EINVAL; } else { - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - ctx->base = devm_ioremap_resource(&pdev->dev, res); + ctx->base = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(ctx->base)) return PTR_ERR(ctx->base); } - ctx->pdev = pdev; - ctx->cfg = cfg; - ctx->message = NULL; - ctx->scroll_pos = 0; - ctx->scroll_rate = HZ / 2; - - /* initialise a timer for scrolling the message */ - init_timer(&ctx->timer); - ctx->timer.function = img_ascii_lcd_scroll; - ctx->timer.data = (unsigned long)ctx; - - platform_set_drvdata(pdev, ctx); - - /* display a default message */ - err = img_ascii_lcd_display(ctx, "Linux " UTS_RELEASE " ", -1); + err = linedisp_register(&ctx->linedisp, dev, cfg->num_chars, &cfg->ops); if (err) - goto out_del_timer; + return err; - err = device_create_file(&pdev->dev, &dev_attr_message); + /* for backwards compatibility */ + err = compat_only_sysfs_link_entry_to_kobj(&dev->kobj, + &ctx->linedisp.dev.kobj, + "message", NULL); if (err) - goto out_del_timer; + goto err_unregister; + platform_set_drvdata(pdev, ctx); return 0; -out_del_timer: - del_timer_sync(&ctx->timer); + +err_unregister: + linedisp_unregister(&ctx->linedisp); return err; } @@ -422,16 +274,13 @@ out_del_timer: * * Remove an LCD display device, freeing private resources & ensuring that the * driver stops using the LCD display registers. - * - * Return: 0 */ -static int img_ascii_lcd_remove(struct platform_device *pdev) +static void img_ascii_lcd_remove(struct platform_device *pdev) { struct img_ascii_lcd_ctx *ctx = platform_get_drvdata(pdev); - device_remove_file(&pdev->dev, &dev_attr_message); - del_timer_sync(&ctx->timer); - return 0; + sysfs_remove_link(&pdev->dev.kobj, "message"); + linedisp_unregister(&ctx->linedisp); } static struct platform_driver img_ascii_lcd_driver = { @@ -440,6 +289,11 @@ static struct platform_driver img_ascii_lcd_driver = { .of_match_table = img_ascii_lcd_matches, }, .probe = img_ascii_lcd_probe, - .remove = img_ascii_lcd_remove, + .remove = img_ascii_lcd_remove, }; module_platform_driver(img_ascii_lcd_driver); + +MODULE_DESCRIPTION("Imagination Technologies ASCII LCD Display"); +MODULE_AUTHOR("Paul Burton <paul.burton@mips.com>"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("LINEDISP"); |
