linux/drivers/auxdisplay/img-ascii-lcd.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-or-later
   2/*
   3 * Copyright (C) 2016 Imagination Technologies
   4 * Author: Paul Burton <paul.burton@mips.com>
   5 */
   6
   7#include <generated/utsrelease.h>
   8#include <linux/kernel.h>
   9#include <linux/io.h>
  10#include <linux/mfd/syscon.h>
  11#include <linux/module.h>
  12#include <linux/of_address.h>
  13#include <linux/of_platform.h>
  14#include <linux/platform_device.h>
  15#include <linux/regmap.h>
  16#include <linux/slab.h>
  17#include <linux/sysfs.h>
  18
  19struct img_ascii_lcd_ctx;
  20
  21/**
  22 * struct img_ascii_lcd_config - Configuration information about an LCD model
  23 * @num_chars: the number of characters the LCD can display
  24 * @external_regmap: true if registers are in a system controller, else false
  25 * @update: function called to update the LCD
  26 */
  27struct img_ascii_lcd_config {
  28        unsigned int num_chars;
  29        bool external_regmap;
  30        void (*update)(struct img_ascii_lcd_ctx *ctx);
  31};
  32
  33/**
  34 * struct img_ascii_lcd_ctx - Private data structure
  35 * @pdev: the ASCII LCD platform device
  36 * @base: the base address of the LCD registers
  37 * @regmap: the regmap through which LCD registers are accessed
  38 * @offset: the offset within regmap to the start of the LCD registers
  39 * @cfg: pointer to the LCD model configuration
  40 * @message: the full message to display or scroll on the LCD
  41 * @message_len: the length of the @message string
  42 * @scroll_pos: index of the first character of @message currently displayed
  43 * @scroll_rate: scroll interval in jiffies
  44 * @timer: timer used to implement scrolling
  45 * @curr: the string currently displayed on the LCD
  46 */
  47struct img_ascii_lcd_ctx {
  48        struct platform_device *pdev;
  49        union {
  50                void __iomem *base;
  51                struct regmap *regmap;
  52        };
  53        u32 offset;
  54        const struct img_ascii_lcd_config *cfg;
  55        char *message;
  56        unsigned int message_len;
  57        unsigned int scroll_pos;
  58        unsigned int scroll_rate;
  59        struct timer_list timer;
  60        char curr[] __aligned(8);
  61};
  62
  63/*
  64 * MIPS Boston development board
  65 */
  66
  67static void boston_update(struct img_ascii_lcd_ctx *ctx)
  68{
  69        ulong val;
  70
  71#if BITS_PER_LONG == 64
  72        val = *((u64 *)&ctx->curr[0]);
  73        __raw_writeq(val, ctx->base);
  74#elif BITS_PER_LONG == 32
  75        val = *((u32 *)&ctx->curr[0]);
  76        __raw_writel(val, ctx->base);
  77        val = *((u32 *)&ctx->curr[4]);
  78        __raw_writel(val, ctx->base + 4);
  79#else
  80# error Not 32 or 64 bit
  81#endif
  82}
  83
  84static struct img_ascii_lcd_config boston_config = {
  85        .num_chars = 8,
  86        .update = boston_update,
  87};
  88
  89/*
  90 * MIPS Malta development board
  91 */
  92
  93static void malta_update(struct img_ascii_lcd_ctx *ctx)
  94{
  95        unsigned int i;
  96        int err = 0;
  97
  98        for (i = 0; i < ctx->cfg->num_chars; i++) {
  99                err = regmap_write(ctx->regmap,
 100                                   ctx->offset + (i * 8), ctx->curr[i]);
 101                if (err)
 102                        break;
 103        }
 104
 105        if (unlikely(err))
 106                pr_err_ratelimited("Failed to update LCD display: %d\n", err);
 107}
 108
 109static struct img_ascii_lcd_config malta_config = {
 110        .num_chars = 8,
 111        .external_regmap = true,
 112        .update = malta_update,
 113};
 114
 115/*
 116 * MIPS SEAD3 development board
 117 */
 118
 119enum {
 120        SEAD3_REG_LCD_CTRL              = 0x00,
 121#define SEAD3_REG_LCD_CTRL_SETDRAM      BIT(7)
 122        SEAD3_REG_LCD_DATA              = 0x08,
 123        SEAD3_REG_CPLD_STATUS           = 0x10,
 124#define SEAD3_REG_CPLD_STATUS_BUSY      BIT(0)
 125        SEAD3_REG_CPLD_DATA             = 0x18,
 126#define SEAD3_REG_CPLD_DATA_BUSY        BIT(7)
 127};
 128
 129static int sead3_wait_sm_idle(struct img_ascii_lcd_ctx *ctx)
 130{
 131        unsigned int status;
 132        int err;
 133
 134        do {
 135                err = regmap_read(ctx->regmap,
 136                                  ctx->offset + SEAD3_REG_CPLD_STATUS,
 137                                  &status);
 138                if (err)
 139                        return err;
 140        } while (status & SEAD3_REG_CPLD_STATUS_BUSY);
 141
 142        return 0;
 143
 144}
 145
 146static int sead3_wait_lcd_idle(struct img_ascii_lcd_ctx *ctx)
 147{
 148        unsigned int cpld_data;
 149        int err;
 150
 151        err = sead3_wait_sm_idle(ctx);
 152        if (err)
 153                return err;
 154
 155        do {
 156                err = regmap_read(ctx->regmap,
 157                                  ctx->offset + SEAD3_REG_LCD_CTRL,
 158                                  &cpld_data);
 159                if (err)
 160                        return err;
 161
 162                err = sead3_wait_sm_idle(ctx);
 163                if (err)
 164                        return err;
 165
 166                err = regmap_read(ctx->regmap,
 167                                  ctx->offset + SEAD3_REG_CPLD_DATA,
 168                                  &cpld_data);
 169                if (err)
 170                        return err;
 171        } while (cpld_data & SEAD3_REG_CPLD_DATA_BUSY);
 172
 173        return 0;
 174}
 175
 176static void sead3_update(struct img_ascii_lcd_ctx *ctx)
 177{
 178        unsigned int i;
 179        int err = 0;
 180
 181        for (i = 0; i < ctx->cfg->num_chars; i++) {
 182                err = sead3_wait_lcd_idle(ctx);
 183                if (err)
 184                        break;
 185
 186                err = regmap_write(ctx->regmap,
 187                                   ctx->offset + SEAD3_REG_LCD_CTRL,
 188                                   SEAD3_REG_LCD_CTRL_SETDRAM | i);
 189                if (err)
 190                        break;
 191
 192                err = sead3_wait_lcd_idle(ctx);
 193                if (err)
 194                        break;
 195
 196                err = regmap_write(ctx->regmap,
 197                                   ctx->offset + SEAD3_REG_LCD_DATA,
 198                                   ctx->curr[i]);
 199                if (err)
 200                        break;
 201        }
 202
 203        if (unlikely(err))
 204                pr_err_ratelimited("Failed to update LCD display: %d\n", err);
 205}
 206
 207static struct img_ascii_lcd_config sead3_config = {
 208        .num_chars = 16,
 209        .external_regmap = true,
 210        .update = sead3_update,
 211};
 212
 213static const struct of_device_id img_ascii_lcd_matches[] = {
 214        { .compatible = "img,boston-lcd", .data = &boston_config },
 215        { .compatible = "mti,malta-lcd", .data = &malta_config },
 216        { .compatible = "mti,sead3-lcd", .data = &sead3_config },
 217        { /* sentinel */ }
 218};
 219MODULE_DEVICE_TABLE(of, img_ascii_lcd_matches);
 220
 221/**
 222 * img_ascii_lcd_scroll() - scroll the display by a character
 223 * @t: really a pointer to the private data structure
 224 *
 225 * Scroll the current message along the LCD by one character, rearming the
 226 * timer if required.
 227 */
 228static void img_ascii_lcd_scroll(struct timer_list *t)
 229{
 230        struct img_ascii_lcd_ctx *ctx = from_timer(ctx, t, timer);
 231        unsigned int i, ch = ctx->scroll_pos;
 232        unsigned int num_chars = ctx->cfg->num_chars;
 233
 234        /* update the current message string */
 235        for (i = 0; i < num_chars;) {
 236                /* copy as many characters from the string as possible */
 237                for (; i < num_chars && ch < ctx->message_len; i++, ch++)
 238                        ctx->curr[i] = ctx->message[ch];
 239
 240                /* wrap around to the start of the string */
 241                ch = 0;
 242        }
 243
 244        /* update the LCD */
 245        ctx->cfg->update(ctx);
 246
 247        /* move on to the next character */
 248        ctx->scroll_pos++;
 249        ctx->scroll_pos %= ctx->message_len;
 250
 251        /* rearm the timer */
 252        if (ctx->message_len > ctx->cfg->num_chars)
 253                mod_timer(&ctx->timer, jiffies + ctx->scroll_rate);
 254}
 255
 256/**
 257 * img_ascii_lcd_display() - set the message to be displayed
 258 * @ctx: pointer to the private data structure
 259 * @msg: the message to display
 260 * @count: length of msg, or -1
 261 *
 262 * Display a new message @msg on the LCD. @msg can be longer than the number of
 263 * characters the LCD can display, in which case it will begin scrolling across
 264 * the LCD display.
 265 *
 266 * Return: 0 on success, -ENOMEM on memory allocation failure
 267 */
 268static int img_ascii_lcd_display(struct img_ascii_lcd_ctx *ctx,
 269                             const char *msg, ssize_t count)
 270{
 271        char *new_msg;
 272
 273        /* stop the scroll timer */
 274        del_timer_sync(&ctx->timer);
 275
 276        if (count == -1)
 277                count = strlen(msg);
 278
 279        /* if the string ends with a newline, trim it */
 280        if (msg[count - 1] == '\n')
 281                count--;
 282
 283        new_msg = devm_kmalloc(&ctx->pdev->dev, count + 1, GFP_KERNEL);
 284        if (!new_msg)
 285                return -ENOMEM;
 286
 287        memcpy(new_msg, msg, count);
 288        new_msg[count] = 0;
 289
 290        if (ctx->message)
 291                devm_kfree(&ctx->pdev->dev, ctx->message);
 292
 293        ctx->message = new_msg;
 294        ctx->message_len = count;
 295        ctx->scroll_pos = 0;
 296
 297        /* update the LCD */
 298        img_ascii_lcd_scroll(&ctx->timer);
 299
 300        return 0;
 301}
 302
 303/**
 304 * message_show() - read message via sysfs
 305 * @dev: the LCD device
 306 * @attr: the LCD message attribute
 307 * @buf: the buffer to read the message into
 308 *
 309 * Read the current message being displayed or scrolled across the LCD display
 310 * into @buf, for reads from sysfs.
 311 *
 312 * Return: the number of characters written to @buf
 313 */
 314static ssize_t message_show(struct device *dev, struct device_attribute *attr,
 315                            char *buf)
 316{
 317        struct img_ascii_lcd_ctx *ctx = dev_get_drvdata(dev);
 318
 319        return sprintf(buf, "%s\n", ctx->message);
 320}
 321
 322/**
 323 * message_store() - write a new message via sysfs
 324 * @dev: the LCD device
 325 * @attr: the LCD message attribute
 326 * @buf: the buffer containing the new message
 327 * @count: the size of the message in @buf
 328 *
 329 * Write a new message to display or scroll across the LCD display from sysfs.
 330 *
 331 * Return: the size of the message on success, else -ERRNO
 332 */
 333static ssize_t message_store(struct device *dev, struct device_attribute *attr,
 334                             const char *buf, size_t count)
 335{
 336        struct img_ascii_lcd_ctx *ctx = dev_get_drvdata(dev);
 337        int err;
 338
 339        err = img_ascii_lcd_display(ctx, buf, count);
 340        return err ?: count;
 341}
 342
 343static DEVICE_ATTR_RW(message);
 344
 345/**
 346 * img_ascii_lcd_probe() - probe an LCD display device
 347 * @pdev: the LCD platform device
 348 *
 349 * Probe an LCD display device, ensuring that we have the required resources in
 350 * order to access the LCD & setting up private data as well as sysfs files.
 351 *
 352 * Return: 0 on success, else -ERRNO
 353 */
 354static int img_ascii_lcd_probe(struct platform_device *pdev)
 355{
 356        const struct of_device_id *match;
 357        const struct img_ascii_lcd_config *cfg;
 358        struct img_ascii_lcd_ctx *ctx;
 359        int err;
 360
 361        match = of_match_device(img_ascii_lcd_matches, &pdev->dev);
 362        if (!match)
 363                return -ENODEV;
 364
 365        cfg = match->data;
 366        ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx) + cfg->num_chars,
 367                           GFP_KERNEL);
 368        if (!ctx)
 369                return -ENOMEM;
 370
 371        if (cfg->external_regmap) {
 372                ctx->regmap = syscon_node_to_regmap(pdev->dev.parent->of_node);
 373                if (IS_ERR(ctx->regmap))
 374                        return PTR_ERR(ctx->regmap);
 375
 376                if (of_property_read_u32(pdev->dev.of_node, "offset",
 377                                         &ctx->offset))
 378                        return -EINVAL;
 379        } else {
 380                ctx->base = devm_platform_ioremap_resource(pdev, 0);
 381                if (IS_ERR(ctx->base))
 382                        return PTR_ERR(ctx->base);
 383        }
 384
 385        ctx->pdev = pdev;
 386        ctx->cfg = cfg;
 387        ctx->message = NULL;
 388        ctx->scroll_pos = 0;
 389        ctx->scroll_rate = HZ / 2;
 390
 391        /* initialise a timer for scrolling the message */
 392        timer_setup(&ctx->timer, img_ascii_lcd_scroll, 0);
 393
 394        platform_set_drvdata(pdev, ctx);
 395
 396        /* display a default message */
 397        err = img_ascii_lcd_display(ctx, "Linux " UTS_RELEASE "       ", -1);
 398        if (err)
 399                goto out_del_timer;
 400
 401        err = device_create_file(&pdev->dev, &dev_attr_message);
 402        if (err)
 403                goto out_del_timer;
 404
 405        return 0;
 406out_del_timer:
 407        del_timer_sync(&ctx->timer);
 408        return err;
 409}
 410
 411/**
 412 * img_ascii_lcd_remove() - remove an LCD display device
 413 * @pdev: the LCD platform device
 414 *
 415 * Remove an LCD display device, freeing private resources & ensuring that the
 416 * driver stops using the LCD display registers.
 417 *
 418 * Return: 0
 419 */
 420static int img_ascii_lcd_remove(struct platform_device *pdev)
 421{
 422        struct img_ascii_lcd_ctx *ctx = platform_get_drvdata(pdev);
 423
 424        device_remove_file(&pdev->dev, &dev_attr_message);
 425        del_timer_sync(&ctx->timer);
 426        return 0;
 427}
 428
 429static struct platform_driver img_ascii_lcd_driver = {
 430        .driver = {
 431                .name           = "img-ascii-lcd",
 432                .of_match_table = img_ascii_lcd_matches,
 433        },
 434        .probe  = img_ascii_lcd_probe,
 435        .remove = img_ascii_lcd_remove,
 436};
 437module_platform_driver(img_ascii_lcd_driver);
 438
 439MODULE_DESCRIPTION("Imagination Technologies ASCII LCD Display");
 440MODULE_AUTHOR("Paul Burton <paul.burton@mips.com>");
 441MODULE_LICENSE("GPL");
 442