]>
Commit | Line | Data |
---|---|---|
0cad855f PB |
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 | ||
23 | struct 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 | */ | |
31 | struct 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 | */ | |
51 | struct 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 | ||
71 | static 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 | ||
88 | static 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 | ||
97 | static 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 | ||
113 | static 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 | ||
123 | enum { | |
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 | ||
133 | static 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 | ||
150 | static 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 | ||
180 | static 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 | ||
211 | static struct img_ascii_lcd_config sead3_config = { | |
212 | .num_chars = 16, | |
213 | .external_regmap = true, | |
214 | .update = sead3_update, | |
215 | }; | |
216 | ||
217 | static 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 | */ | |
230 | static 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 | */ | |
270 | static 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 | */ | |
316 | static 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 | */ | |
335 | static 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 | ||
345 | static 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 | */ | |
356 | static 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; | |
412 | out_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 | */ | |
426 | static 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 | ||
435 | static 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 | }; | |
443 | module_platform_driver(img_ascii_lcd_driver); |