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