Orange Pi5 kernel

Deprecated Linux kernel 5.10.110 for OrangePi 5/5B/5+ boards

3 Commits   0 Branches   0 Tags
^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");