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