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