Root/
1 | /* |
2 | * Driver for the on-board character LCD found on some ARM reference boards |
3 | * This is basically an Hitachi HD44780 LCD with a custom IP block to drive it |
4 | * http://en.wikipedia.org/wiki/HD44780_Character_LCD |
5 | * Currently it will just display the text "ARM Linux" and the linux version |
6 | * |
7 | * License terms: GNU General Public License (GPL) version 2 |
8 | * Author: Linus Walleij <triad@df.lth.se> |
9 | */ |
10 | #include <linux/init.h> |
11 | #include <linux/module.h> |
12 | #include <linux/interrupt.h> |
13 | #include <linux/platform_device.h> |
14 | #include <linux/completion.h> |
15 | #include <linux/delay.h> |
16 | #include <linux/io.h> |
17 | #include <linux/slab.h> |
18 | #include <linux/workqueue.h> |
19 | #include <generated/utsrelease.h> |
20 | |
21 | #define DRIVERNAME "arm-charlcd" |
22 | #define CHARLCD_TIMEOUT (msecs_to_jiffies(1000)) |
23 | |
24 | /* Offsets to registers */ |
25 | #define CHAR_COM 0x00U |
26 | #define CHAR_DAT 0x04U |
27 | #define CHAR_RD 0x08U |
28 | #define CHAR_RAW 0x0CU |
29 | #define CHAR_MASK 0x10U |
30 | #define CHAR_STAT 0x14U |
31 | |
32 | #define CHAR_RAW_CLEAR 0x00000000U |
33 | #define CHAR_RAW_VALID 0x00000100U |
34 | |
35 | /* Hitachi HD44780 display commands */ |
36 | #define HD_CLEAR 0x01U |
37 | #define HD_HOME 0x02U |
38 | #define HD_ENTRYMODE 0x04U |
39 | #define HD_ENTRYMODE_INCREMENT 0x02U |
40 | #define HD_ENTRYMODE_SHIFT 0x01U |
41 | #define HD_DISPCTRL 0x08U |
42 | #define HD_DISPCTRL_ON 0x04U |
43 | #define HD_DISPCTRL_CURSOR_ON 0x02U |
44 | #define HD_DISPCTRL_CURSOR_BLINK 0x01U |
45 | #define HD_CRSR_SHIFT 0x10U |
46 | #define HD_CRSR_SHIFT_DISPLAY 0x08U |
47 | #define HD_CRSR_SHIFT_DISPLAY_RIGHT 0x04U |
48 | #define HD_FUNCSET 0x20U |
49 | #define HD_FUNCSET_8BIT 0x10U |
50 | #define HD_FUNCSET_2_LINES 0x08U |
51 | #define HD_FUNCSET_FONT_5X10 0x04U |
52 | #define HD_SET_CGRAM 0x40U |
53 | #define HD_SET_DDRAM 0x80U |
54 | #define HD_BUSY_FLAG 0x80U |
55 | |
56 | /** |
57 | * @dev: a pointer back to containing device |
58 | * @phybase: the offset to the controller in physical memory |
59 | * @physize: the size of the physical page |
60 | * @virtbase: the offset to the controller in virtual memory |
61 | * @irq: reserved interrupt number |
62 | * @complete: completion structure for the last LCD command |
63 | */ |
64 | struct charlcd { |
65 | struct device *dev; |
66 | u32 phybase; |
67 | u32 physize; |
68 | void __iomem *virtbase; |
69 | int irq; |
70 | struct completion complete; |
71 | struct delayed_work init_work; |
72 | }; |
73 | |
74 | static irqreturn_t charlcd_interrupt(int irq, void *data) |
75 | { |
76 | struct charlcd *lcd = data; |
77 | u8 status; |
78 | |
79 | status = readl(lcd->virtbase + CHAR_STAT) & 0x01; |
80 | /* Clear IRQ */ |
81 | writel(CHAR_RAW_CLEAR, lcd->virtbase + CHAR_RAW); |
82 | if (status) |
83 | complete(&lcd->complete); |
84 | else |
85 | dev_info(lcd->dev, "Spurious IRQ (%02x)\n", status); |
86 | return IRQ_HANDLED; |
87 | } |
88 | |
89 | |
90 | static void charlcd_wait_complete_irq(struct charlcd *lcd) |
91 | { |
92 | int ret; |
93 | |
94 | ret = wait_for_completion_interruptible_timeout(&lcd->complete, |
95 | CHARLCD_TIMEOUT); |
96 | /* Disable IRQ after completion */ |
97 | writel(0x00, lcd->virtbase + CHAR_MASK); |
98 | |
99 | if (ret < 0) { |
100 | dev_err(lcd->dev, |
101 | "wait_for_completion_interruptible_timeout() " |
102 | "returned %d waiting for ready\n", ret); |
103 | return; |
104 | } |
105 | |
106 | if (ret == 0) { |
107 | dev_err(lcd->dev, "charlcd controller timed out " |
108 | "waiting for ready\n"); |
109 | return; |
110 | } |
111 | } |
112 | |
113 | static u8 charlcd_4bit_read_char(struct charlcd *lcd) |
114 | { |
115 | u8 data; |
116 | u32 val; |
117 | int i; |
118 | |
119 | /* If we can, use an IRQ to wait for the data, else poll */ |
120 | if (lcd->irq >= 0) |
121 | charlcd_wait_complete_irq(lcd); |
122 | else { |
123 | i = 0; |
124 | val = 0; |
125 | while (!(val & CHAR_RAW_VALID) && i < 10) { |
126 | udelay(100); |
127 | val = readl(lcd->virtbase + CHAR_RAW); |
128 | i++; |
129 | } |
130 | |
131 | writel(CHAR_RAW_CLEAR, lcd->virtbase + CHAR_RAW); |
132 | } |
133 | msleep(1); |
134 | |
135 | /* Read the 4 high bits of the data */ |
136 | data = readl(lcd->virtbase + CHAR_RD) & 0xf0; |
137 | |
138 | /* |
139 | * The second read for the low bits does not trigger an IRQ |
140 | * so in this case we have to poll for the 4 lower bits |
141 | */ |
142 | i = 0; |
143 | val = 0; |
144 | while (!(val & CHAR_RAW_VALID) && i < 10) { |
145 | udelay(100); |
146 | val = readl(lcd->virtbase + CHAR_RAW); |
147 | i++; |
148 | } |
149 | writel(CHAR_RAW_CLEAR, lcd->virtbase + CHAR_RAW); |
150 | msleep(1); |
151 | |
152 | /* Read the 4 low bits of the data */ |
153 | data |= (readl(lcd->virtbase + CHAR_RD) >> 4) & 0x0f; |
154 | |
155 | return data; |
156 | } |
157 | |
158 | static bool charlcd_4bit_read_bf(struct charlcd *lcd) |
159 | { |
160 | if (lcd->irq >= 0) { |
161 | /* |
162 | * If we'll use IRQs to wait for the busyflag, clear any |
163 | * pending flag and enable IRQ |
164 | */ |
165 | writel(CHAR_RAW_CLEAR, lcd->virtbase + CHAR_RAW); |
166 | init_completion(&lcd->complete); |
167 | writel(0x01, lcd->virtbase + CHAR_MASK); |
168 | } |
169 | readl(lcd->virtbase + CHAR_COM); |
170 | return charlcd_4bit_read_char(lcd) & HD_BUSY_FLAG ? true : false; |
171 | } |
172 | |
173 | static void charlcd_4bit_wait_busy(struct charlcd *lcd) |
174 | { |
175 | int retries = 50; |
176 | |
177 | udelay(100); |
178 | while (charlcd_4bit_read_bf(lcd) && retries) |
179 | retries--; |
180 | if (!retries) |
181 | dev_err(lcd->dev, "timeout waiting for busyflag\n"); |
182 | } |
183 | |
184 | static void charlcd_4bit_command(struct charlcd *lcd, u8 cmd) |
185 | { |
186 | u32 cmdlo = (cmd << 4) & 0xf0; |
187 | u32 cmdhi = (cmd & 0xf0); |
188 | |
189 | writel(cmdhi, lcd->virtbase + CHAR_COM); |
190 | udelay(10); |
191 | writel(cmdlo, lcd->virtbase + CHAR_COM); |
192 | charlcd_4bit_wait_busy(lcd); |
193 | } |
194 | |
195 | static void charlcd_4bit_char(struct charlcd *lcd, u8 ch) |
196 | { |
197 | u32 chlo = (ch << 4) & 0xf0; |
198 | u32 chhi = (ch & 0xf0); |
199 | |
200 | writel(chhi, lcd->virtbase + CHAR_DAT); |
201 | udelay(10); |
202 | writel(chlo, lcd->virtbase + CHAR_DAT); |
203 | charlcd_4bit_wait_busy(lcd); |
204 | } |
205 | |
206 | static void charlcd_4bit_print(struct charlcd *lcd, int line, const char *str) |
207 | { |
208 | u8 offset; |
209 | int i; |
210 | |
211 | /* |
212 | * We support line 0, 1 |
213 | * Line 1 runs from 0x00..0x27 |
214 | * Line 2 runs from 0x28..0x4f |
215 | */ |
216 | if (line == 0) |
217 | offset = 0; |
218 | else if (line == 1) |
219 | offset = 0x28; |
220 | else |
221 | return; |
222 | |
223 | /* Set offset */ |
224 | charlcd_4bit_command(lcd, HD_SET_DDRAM | offset); |
225 | |
226 | /* Send string */ |
227 | for (i = 0; i < strlen(str) && i < 0x28; i++) |
228 | charlcd_4bit_char(lcd, str[i]); |
229 | } |
230 | |
231 | static void charlcd_4bit_init(struct charlcd *lcd) |
232 | { |
233 | /* These commands cannot be checked with the busy flag */ |
234 | writel(HD_FUNCSET | HD_FUNCSET_8BIT, lcd->virtbase + CHAR_COM); |
235 | msleep(5); |
236 | writel(HD_FUNCSET | HD_FUNCSET_8BIT, lcd->virtbase + CHAR_COM); |
237 | udelay(100); |
238 | writel(HD_FUNCSET | HD_FUNCSET_8BIT, lcd->virtbase + CHAR_COM); |
239 | udelay(100); |
240 | /* Go to 4bit mode */ |
241 | writel(HD_FUNCSET, lcd->virtbase + CHAR_COM); |
242 | udelay(100); |
243 | /* |
244 | * 4bit mode, 2 lines, 5x8 font, after this the number of lines |
245 | * and the font cannot be changed until the next initialization sequence |
246 | */ |
247 | charlcd_4bit_command(lcd, HD_FUNCSET | HD_FUNCSET_2_LINES); |
248 | charlcd_4bit_command(lcd, HD_DISPCTRL | HD_DISPCTRL_ON); |
249 | charlcd_4bit_command(lcd, HD_ENTRYMODE | HD_ENTRYMODE_INCREMENT); |
250 | charlcd_4bit_command(lcd, HD_CLEAR); |
251 | charlcd_4bit_command(lcd, HD_HOME); |
252 | /* Put something useful in the display */ |
253 | charlcd_4bit_print(lcd, 0, "ARM Linux"); |
254 | charlcd_4bit_print(lcd, 1, UTS_RELEASE); |
255 | } |
256 | |
257 | static void charlcd_init_work(struct work_struct *work) |
258 | { |
259 | struct charlcd *lcd = |
260 | container_of(work, struct charlcd, init_work.work); |
261 | |
262 | charlcd_4bit_init(lcd); |
263 | } |
264 | |
265 | static int __init charlcd_probe(struct platform_device *pdev) |
266 | { |
267 | int ret; |
268 | struct charlcd *lcd; |
269 | struct resource *res; |
270 | |
271 | lcd = kzalloc(sizeof(struct charlcd), GFP_KERNEL); |
272 | if (!lcd) |
273 | return -ENOMEM; |
274 | |
275 | lcd->dev = &pdev->dev; |
276 | |
277 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
278 | if (!res) { |
279 | ret = -ENOENT; |
280 | goto out_no_resource; |
281 | } |
282 | lcd->phybase = res->start; |
283 | lcd->physize = resource_size(res); |
284 | |
285 | if (request_mem_region(lcd->phybase, lcd->physize, |
286 | DRIVERNAME) == NULL) { |
287 | ret = -EBUSY; |
288 | goto out_no_memregion; |
289 | } |
290 | |
291 | lcd->virtbase = ioremap(lcd->phybase, lcd->physize); |
292 | if (!lcd->virtbase) { |
293 | ret = -ENOMEM; |
294 | goto out_no_remap; |
295 | } |
296 | |
297 | lcd->irq = platform_get_irq(pdev, 0); |
298 | /* If no IRQ is supplied, we'll survive without it */ |
299 | if (lcd->irq >= 0) { |
300 | if (request_irq(lcd->irq, charlcd_interrupt, IRQF_DISABLED, |
301 | DRIVERNAME, lcd)) { |
302 | ret = -EIO; |
303 | goto out_no_irq; |
304 | } |
305 | } |
306 | |
307 | platform_set_drvdata(pdev, lcd); |
308 | |
309 | /* |
310 | * Initialize the display in a delayed work, because |
311 | * it is VERY slow and would slow down the boot of the system. |
312 | */ |
313 | INIT_DELAYED_WORK(&lcd->init_work, charlcd_init_work); |
314 | schedule_delayed_work(&lcd->init_work, 0); |
315 | |
316 | dev_info(&pdev->dev, "initialized ARM character LCD at %08x\n", |
317 | lcd->phybase); |
318 | |
319 | return 0; |
320 | |
321 | out_no_irq: |
322 | iounmap(lcd->virtbase); |
323 | out_no_remap: |
324 | platform_set_drvdata(pdev, NULL); |
325 | out_no_memregion: |
326 | release_mem_region(lcd->phybase, SZ_4K); |
327 | out_no_resource: |
328 | kfree(lcd); |
329 | return ret; |
330 | } |
331 | |
332 | static int __exit charlcd_remove(struct platform_device *pdev) |
333 | { |
334 | struct charlcd *lcd = platform_get_drvdata(pdev); |
335 | |
336 | if (lcd) { |
337 | free_irq(lcd->irq, lcd); |
338 | iounmap(lcd->virtbase); |
339 | release_mem_region(lcd->phybase, lcd->physize); |
340 | platform_set_drvdata(pdev, NULL); |
341 | kfree(lcd); |
342 | } |
343 | |
344 | return 0; |
345 | } |
346 | |
347 | static int charlcd_suspend(struct device *dev) |
348 | { |
349 | struct platform_device *pdev = to_platform_device(dev); |
350 | struct charlcd *lcd = platform_get_drvdata(pdev); |
351 | |
352 | /* Power the display off */ |
353 | charlcd_4bit_command(lcd, HD_DISPCTRL); |
354 | return 0; |
355 | } |
356 | |
357 | static int charlcd_resume(struct device *dev) |
358 | { |
359 | struct platform_device *pdev = to_platform_device(dev); |
360 | struct charlcd *lcd = platform_get_drvdata(pdev); |
361 | |
362 | /* Turn the display back on */ |
363 | charlcd_4bit_command(lcd, HD_DISPCTRL | HD_DISPCTRL_ON); |
364 | return 0; |
365 | } |
366 | |
367 | static const struct dev_pm_ops charlcd_pm_ops = { |
368 | .suspend = charlcd_suspend, |
369 | .resume = charlcd_resume, |
370 | }; |
371 | |
372 | static struct platform_driver charlcd_driver = { |
373 | .driver = { |
374 | .name = DRIVERNAME, |
375 | .owner = THIS_MODULE, |
376 | .pm = &charlcd_pm_ops, |
377 | }, |
378 | .remove = __exit_p(charlcd_remove), |
379 | }; |
380 | |
381 | static int __init charlcd_init(void) |
382 | { |
383 | return platform_driver_probe(&charlcd_driver, charlcd_probe); |
384 | } |
385 | |
386 | static void __exit charlcd_exit(void) |
387 | { |
388 | platform_driver_unregister(&charlcd_driver); |
389 | } |
390 | |
391 | module_init(charlcd_init); |
392 | module_exit(charlcd_exit); |
393 | |
394 | MODULE_AUTHOR("Linus Walleij <triad@df.lth.se>"); |
395 | MODULE_DESCRIPTION("ARM Character LCD Driver"); |
396 | MODULE_LICENSE("GPL v2"); |
397 |
Branches:
ben-wpan
ben-wpan-stefan
javiroman/ks7010
jz-2.6.34
jz-2.6.34-rc5
jz-2.6.34-rc6
jz-2.6.34-rc7
jz-2.6.35
jz-2.6.36
jz-2.6.37
jz-2.6.38
jz-2.6.39
jz-3.0
jz-3.1
jz-3.11
jz-3.12
jz-3.13
jz-3.15
jz-3.16
jz-3.18-dt
jz-3.2
jz-3.3
jz-3.4
jz-3.5
jz-3.6
jz-3.6-rc2-pwm
jz-3.9
jz-3.9-clk
jz-3.9-rc8
jz47xx
jz47xx-2.6.38
master
Tags:
od-2011-09-04
od-2011-09-18
v2.6.34-rc5
v2.6.34-rc6
v2.6.34-rc7
v3.9