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