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