Jaya Kumar | 03c33a4 | 2008-04-28 02:15:38 -0700 | [diff] [blame^] | 1 | /* |
| 2 | * linux/drivers/video/am200epd.c -- Platform device for AM200 EPD kit |
| 3 | * |
| 4 | * Copyright (C) 2008, Jaya Kumar |
| 5 | * |
| 6 | * This file is subject to the terms and conditions of the GNU General Public |
| 7 | * License. See the file COPYING in the main directory of this archive for |
| 8 | * more details. |
| 9 | * |
| 10 | * Layout is based on skeletonfb.c by James Simmons and Geert Uytterhoeven. |
| 11 | * |
| 12 | * This work was made possible by help and equipment support from E-Ink |
| 13 | * Corporation. http://support.eink.com/community |
| 14 | * |
| 15 | * This driver is written to be used with the Metronome display controller. |
| 16 | * on the AM200 EPD prototype kit/development kit with an E-Ink 800x600 |
| 17 | * Vizplex EPD on a Gumstix board using the Lyre interface board. |
| 18 | * |
| 19 | */ |
| 20 | |
| 21 | #include <linux/module.h> |
| 22 | #include <linux/kernel.h> |
| 23 | #include <linux/errno.h> |
| 24 | #include <linux/string.h> |
| 25 | #include <linux/delay.h> |
| 26 | #include <linux/interrupt.h> |
| 27 | #include <linux/fb.h> |
| 28 | #include <linux/init.h> |
| 29 | #include <linux/platform_device.h> |
| 30 | #include <linux/list.h> |
| 31 | #include <linux/uaccess.h> |
| 32 | #include <linux/irq.h> |
| 33 | |
| 34 | #include <video/metronomefb.h> |
| 35 | |
| 36 | #include <asm/arch/pxa-regs.h> |
| 37 | |
| 38 | /* register offsets for gpio control */ |
| 39 | #define LED_GPIO_PIN 51 |
| 40 | #define STDBY_GPIO_PIN 48 |
| 41 | #define RST_GPIO_PIN 49 |
| 42 | #define RDY_GPIO_PIN 32 |
| 43 | #define ERR_GPIO_PIN 17 |
| 44 | #define PCBPWR_GPIO_PIN 16 |
| 45 | |
| 46 | #define AF_SEL_GPIO_N 0x3 |
| 47 | #define GAFR0_U_OFFSET(pin) ((pin - 16) * 2) |
| 48 | #define GAFR1_L_OFFSET(pin) ((pin - 32) * 2) |
| 49 | #define GAFR1_U_OFFSET(pin) ((pin - 48) * 2) |
| 50 | #define GPDR1_OFFSET(pin) (pin - 32) |
| 51 | #define GPCR1_OFFSET(pin) (pin - 32) |
| 52 | #define GPSR1_OFFSET(pin) (pin - 32) |
| 53 | #define GPCR0_OFFSET(pin) (pin) |
| 54 | #define GPSR0_OFFSET(pin) (pin) |
| 55 | |
| 56 | static void am200_set_gpio_output(int pin, int val) |
| 57 | { |
| 58 | u8 index; |
| 59 | |
| 60 | index = pin >> 4; |
| 61 | |
| 62 | switch (index) { |
| 63 | case 1: |
| 64 | if (val) |
| 65 | GPSR0 |= (1 << GPSR0_OFFSET(pin)); |
| 66 | else |
| 67 | GPCR0 |= (1 << GPCR0_OFFSET(pin)); |
| 68 | break; |
| 69 | case 2: |
| 70 | break; |
| 71 | case 3: |
| 72 | if (val) |
| 73 | GPSR1 |= (1 << GPSR1_OFFSET(pin)); |
| 74 | else |
| 75 | GPCR1 |= (1 << GPCR1_OFFSET(pin)); |
| 76 | break; |
| 77 | default: |
| 78 | printk(KERN_ERR "unimplemented\n"); |
| 79 | } |
| 80 | } |
| 81 | |
| 82 | static void __devinit am200_init_gpio_pin(int pin, int dir) |
| 83 | { |
| 84 | u8 index; |
| 85 | /* dir 0 is output, 1 is input |
| 86 | - do 2 things here: |
| 87 | - set gpio alternate function to standard gpio |
| 88 | - set gpio direction to input or output */ |
| 89 | |
| 90 | index = pin >> 4; |
| 91 | switch (index) { |
| 92 | case 1: |
| 93 | GAFR0_U &= ~(AF_SEL_GPIO_N << GAFR0_U_OFFSET(pin)); |
| 94 | |
| 95 | if (dir) |
| 96 | GPDR0 &= ~(1 << pin); |
| 97 | else |
| 98 | GPDR0 |= (1 << pin); |
| 99 | break; |
| 100 | case 2: |
| 101 | GAFR1_L &= ~(AF_SEL_GPIO_N << GAFR1_L_OFFSET(pin)); |
| 102 | |
| 103 | if (dir) |
| 104 | GPDR1 &= ~(1 << GPDR1_OFFSET(pin)); |
| 105 | else |
| 106 | GPDR1 |= (1 << GPDR1_OFFSET(pin)); |
| 107 | break; |
| 108 | case 3: |
| 109 | GAFR1_U &= ~(AF_SEL_GPIO_N << GAFR1_U_OFFSET(pin)); |
| 110 | |
| 111 | if (dir) |
| 112 | GPDR1 &= ~(1 << GPDR1_OFFSET(pin)); |
| 113 | else |
| 114 | GPDR1 |= (1 << GPDR1_OFFSET(pin)); |
| 115 | break; |
| 116 | default: |
| 117 | printk(KERN_ERR "unimplemented\n"); |
| 118 | } |
| 119 | } |
| 120 | |
| 121 | static void am200_init_gpio_regs(struct metronomefb_par *par) |
| 122 | { |
| 123 | am200_init_gpio_pin(LED_GPIO_PIN, 0); |
| 124 | am200_set_gpio_output(LED_GPIO_PIN, 0); |
| 125 | |
| 126 | am200_init_gpio_pin(STDBY_GPIO_PIN, 0); |
| 127 | am200_set_gpio_output(STDBY_GPIO_PIN, 0); |
| 128 | |
| 129 | am200_init_gpio_pin(RST_GPIO_PIN, 0); |
| 130 | am200_set_gpio_output(RST_GPIO_PIN, 0); |
| 131 | |
| 132 | am200_init_gpio_pin(RDY_GPIO_PIN, 1); |
| 133 | |
| 134 | am200_init_gpio_pin(ERR_GPIO_PIN, 1); |
| 135 | |
| 136 | am200_init_gpio_pin(PCBPWR_GPIO_PIN, 0); |
| 137 | am200_set_gpio_output(PCBPWR_GPIO_PIN, 0); |
| 138 | } |
| 139 | |
| 140 | static void am200_disable_lcd_controller(struct metronomefb_par *par) |
| 141 | { |
| 142 | LCSR = 0xffffffff; /* Clear LCD Status Register */ |
| 143 | LCCR0 |= LCCR0_DIS; /* Disable LCD Controller */ |
| 144 | |
| 145 | /* we reset and just wait for things to settle */ |
| 146 | msleep(200); |
| 147 | } |
| 148 | |
| 149 | static void am200_enable_lcd_controller(struct metronomefb_par *par) |
| 150 | { |
| 151 | LCSR = 0xffffffff; |
| 152 | FDADR0 = par->metromem_desc_dma; |
| 153 | LCCR0 |= LCCR0_ENB; |
| 154 | } |
| 155 | |
| 156 | static void am200_init_lcdc_regs(struct metronomefb_par *par) |
| 157 | { |
| 158 | /* here we do: |
| 159 | - disable the lcd controller |
| 160 | - setup lcd control registers |
| 161 | - setup dma descriptor |
| 162 | - reenable lcd controller |
| 163 | */ |
| 164 | |
| 165 | /* disable the lcd controller */ |
| 166 | am200_disable_lcd_controller(par); |
| 167 | |
| 168 | /* setup lcd control registers */ |
| 169 | LCCR0 = LCCR0_LDM | LCCR0_SFM | LCCR0_IUM | LCCR0_EFM | LCCR0_PAS |
| 170 | | LCCR0_QDM | LCCR0_BM | LCCR0_OUM; |
| 171 | |
| 172 | LCCR1 = (par->info->var.xres/2 - 1) /* pixels per line */ |
| 173 | | (27 << 10) /* hsync pulse width - 1 */ |
| 174 | | (33 << 16) /* eol pixel count */ |
| 175 | | (33 << 24); /* bol pixel count */ |
| 176 | |
| 177 | LCCR2 = (par->info->var.yres - 1) /* lines per panel */ |
| 178 | | (24 << 10) /* vsync pulse width - 1 */ |
| 179 | | (2 << 16) /* eof pixel count */ |
| 180 | | (0 << 24); /* bof pixel count */ |
| 181 | |
| 182 | LCCR3 = 2 /* pixel clock divisor */ |
| 183 | | (24 << 8) /* AC Bias pin freq */ |
| 184 | | LCCR3_16BPP /* BPP */ |
| 185 | | LCCR3_PCP; /* PCP falling edge */ |
| 186 | |
| 187 | } |
| 188 | |
| 189 | static void am200_post_dma_setup(struct metronomefb_par *par) |
| 190 | { |
| 191 | par->metromem_desc->mFDADR0 = par->metromem_desc_dma; |
| 192 | par->metromem_desc->mFSADR0 = par->metromem_dma; |
| 193 | par->metromem_desc->mFIDR0 = 0; |
| 194 | par->metromem_desc->mLDCMD0 = par->info->var.xres |
| 195 | * par->info->var.yres; |
| 196 | am200_enable_lcd_controller(par); |
| 197 | } |
| 198 | |
| 199 | static void am200_free_irq(struct fb_info *info) |
| 200 | { |
| 201 | free_irq(IRQ_GPIO(RDY_GPIO_PIN), info); |
| 202 | } |
| 203 | |
| 204 | static irqreturn_t am200_handle_irq(int irq, void *dev_id) |
| 205 | { |
| 206 | struct fb_info *info = dev_id; |
| 207 | struct metronomefb_par *par = info->par; |
| 208 | |
| 209 | wake_up_interruptible(&par->waitq); |
| 210 | return IRQ_HANDLED; |
| 211 | } |
| 212 | |
| 213 | static int am200_setup_irq(struct fb_info *info) |
| 214 | { |
| 215 | int retval; |
| 216 | |
| 217 | retval = request_irq(IRQ_GPIO(RDY_GPIO_PIN), am200_handle_irq, |
| 218 | IRQF_DISABLED, "AM200", info); |
| 219 | if (retval) { |
| 220 | printk(KERN_ERR "am200epd: request_irq failed: %d\n", retval); |
| 221 | return retval; |
| 222 | } |
| 223 | |
| 224 | return set_irq_type(IRQ_GPIO(RDY_GPIO_PIN), IRQT_FALLING); |
| 225 | } |
| 226 | |
| 227 | static void am200_set_rst(struct metronomefb_par *par, int state) |
| 228 | { |
| 229 | am200_set_gpio_output(RST_GPIO_PIN, state); |
| 230 | } |
| 231 | |
| 232 | static void am200_set_stdby(struct metronomefb_par *par, int state) |
| 233 | { |
| 234 | am200_set_gpio_output(STDBY_GPIO_PIN, state); |
| 235 | } |
| 236 | |
| 237 | static int am200_wait_event(struct metronomefb_par *par) |
| 238 | { |
| 239 | return wait_event_timeout(par->waitq, (GPLR1 & 0x01), HZ); |
| 240 | } |
| 241 | |
| 242 | static int am200_wait_event_intr(struct metronomefb_par *par) |
| 243 | { |
| 244 | return wait_event_interruptible_timeout(par->waitq, (GPLR1 & 0x01), HZ); |
| 245 | } |
| 246 | |
| 247 | static struct metronome_board am200_board = { |
| 248 | .owner = THIS_MODULE, |
| 249 | .free_irq = am200_free_irq, |
| 250 | .setup_irq = am200_setup_irq, |
| 251 | .init_gpio_regs = am200_init_gpio_regs, |
| 252 | .init_lcdc_regs = am200_init_lcdc_regs, |
| 253 | .post_dma_setup = am200_post_dma_setup, |
| 254 | .set_rst = am200_set_rst, |
| 255 | .set_stdby = am200_set_stdby, |
| 256 | .met_wait_event = am200_wait_event, |
| 257 | .met_wait_event_intr = am200_wait_event_intr, |
| 258 | }; |
| 259 | |
| 260 | static struct platform_device *am200_device; |
| 261 | |
| 262 | static int __init am200_init(void) |
| 263 | { |
| 264 | int ret; |
| 265 | |
| 266 | /* request our platform independent driver */ |
| 267 | request_module("metronomefb"); |
| 268 | |
| 269 | am200_device = platform_device_alloc("metronomefb", -1); |
| 270 | if (!am200_device) |
| 271 | return -ENOMEM; |
| 272 | |
| 273 | platform_device_add_data(am200_device, &am200_board, |
| 274 | sizeof(am200_board)); |
| 275 | |
| 276 | /* this _add binds metronomefb to am200. metronomefb refcounts am200 */ |
| 277 | ret = platform_device_add(am200_device); |
| 278 | |
| 279 | if (ret) |
| 280 | platform_device_put(am200_device); |
| 281 | |
| 282 | return ret; |
| 283 | } |
| 284 | |
| 285 | static void __exit am200_exit(void) |
| 286 | { |
| 287 | platform_device_unregister(am200_device); |
| 288 | } |
| 289 | |
| 290 | module_init(am200_init); |
| 291 | module_exit(am200_exit); |
| 292 | |
| 293 | MODULE_DESCRIPTION("board driver for am200 metronome epd kit"); |
| 294 | MODULE_AUTHOR("Jaya Kumar"); |
| 295 | MODULE_LICENSE("GPL"); |