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