summaryrefslogtreecommitdiffstats
path: root/drivers/auxdisplay
diff options
context:
space:
mode:
authorAndy Shevchenko <andriy.shevchenko@linux.intel.com>2017-04-04 13:25:22 -0400
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>2017-04-08 11:48:20 -0400
commit00846a4425d373761c70cbe82cceb98d3d4da179 (patch)
tree38d8ecd3954282bb3caf0d1510eee861658ca311 /drivers/auxdisplay
parent51c1e9b554c996284d5d3bd44da829f41a49a21b (diff)
auxdisplay: Move arm-charlcd.c to drivers/auxdisplay folder
It looks like arm-charlcd.c belongs to auxdisplay subsystem. Move it to drivers/auxdisplay folder. No functional changes intended. Reviewed-by: Linus Walleij <linus.walleij@linaro.org> Signed-off-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Diffstat (limited to 'drivers/auxdisplay')
-rw-r--r--drivers/auxdisplay/Kconfig10
-rw-r--r--drivers/auxdisplay/Makefile1
-rw-r--r--drivers/auxdisplay/arm-charlcd.c368
3 files changed, 379 insertions, 0 deletions
diff --git a/drivers/auxdisplay/Kconfig b/drivers/auxdisplay/Kconfig
index ad83a0598c67..9ae6681c90ad 100644
--- a/drivers/auxdisplay/Kconfig
+++ b/drivers/auxdisplay/Kconfig
@@ -157,6 +157,16 @@ config HT16K33
157 157
158endif # AUXDISPLAY 158endif # AUXDISPLAY
159 159
160config ARM_CHARLCD
161 bool "ARM Ltd. Character LCD Driver"
162 depends on PLAT_VERSATILE
163 help
164 This is a driver for the character LCD found on the ARM Ltd.
165 Versatile and RealView Platform Baseboards. It doesn't do
166 very much more than display the text "ARM Linux" on the first
167 line and the Linux version on the second line, but that's
168 still useful.
169
160config PANEL 170config PANEL
161 tristate "Parallel port LCD/Keypad Panel support" 171 tristate "Parallel port LCD/Keypad Panel support"
162 depends on PARPORT 172 depends on PARPORT
diff --git a/drivers/auxdisplay/Makefile b/drivers/auxdisplay/Makefile
index 58c25e5ece84..2b8af3dc5e42 100644
--- a/drivers/auxdisplay/Makefile
+++ b/drivers/auxdisplay/Makefile
@@ -3,6 +3,7 @@
3# 3#
4 4
5obj-$(CONFIG_CHARLCD) += charlcd.o 5obj-$(CONFIG_CHARLCD) += charlcd.o
6obj-$(CONFIG_ARM_CHARLCD) += arm-charlcd.o
6obj-$(CONFIG_KS0108) += ks0108.o 7obj-$(CONFIG_KS0108) += ks0108.o
7obj-$(CONFIG_CFAG12864B) += cfag12864b.o cfag12864bfb.o 8obj-$(CONFIG_CFAG12864B) += cfag12864b.o cfag12864bfb.o
8obj-$(CONFIG_IMG_ASCII_LCD) += img-ascii-lcd.o 9obj-$(CONFIG_IMG_ASCII_LCD) += img-ascii-lcd.o
diff --git a/drivers/auxdisplay/arm-charlcd.c b/drivers/auxdisplay/arm-charlcd.c
new file mode 100644
index 000000000000..b3176ee92b90
--- /dev/null
+++ b/drivers/auxdisplay/arm-charlcd.c
@@ -0,0 +1,368 @@
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/interrupt.h>
12#include <linux/platform_device.h>
13#include <linux/of.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 */
64struct 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
74static 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
90static 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
113static 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
158static 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
173static 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
184static 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
195static 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
206static 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
231static 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
257static 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
265static 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_memregion;
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, 0,
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
321out_no_irq:
322 iounmap(lcd->virtbase);
323out_no_memregion:
324 release_mem_region(lcd->phybase, SZ_4K);
325out_no_resource:
326 kfree(lcd);
327 return ret;
328}
329
330static int charlcd_suspend(struct device *dev)
331{
332 struct platform_device *pdev = to_platform_device(dev);
333 struct charlcd *lcd = platform_get_drvdata(pdev);
334
335 /* Power the display off */
336 charlcd_4bit_command(lcd, HD_DISPCTRL);
337 return 0;
338}
339
340static int charlcd_resume(struct device *dev)
341{
342 struct platform_device *pdev = to_platform_device(dev);
343 struct charlcd *lcd = platform_get_drvdata(pdev);
344
345 /* Turn the display back on */
346 charlcd_4bit_command(lcd, HD_DISPCTRL | HD_DISPCTRL_ON);
347 return 0;
348}
349
350static const struct dev_pm_ops charlcd_pm_ops = {
351 .suspend = charlcd_suspend,
352 .resume = charlcd_resume,
353};
354
355static const struct of_device_id charlcd_match[] = {
356 { .compatible = "arm,versatile-lcd", },
357 {}
358};
359
360static struct platform_driver charlcd_driver = {
361 .driver = {
362 .name = DRIVERNAME,
363 .pm = &charlcd_pm_ops,
364 .suppress_bind_attrs = true,
365 .of_match_table = of_match_ptr(charlcd_match),
366 },
367};
368builtin_platform_driver_probe(charlcd_driver, charlcd_probe);