Commit | Line | Data |
---|---|---|
03c33a4f JK |
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 | ||
a09e64fb | 36 | #include <mach/pxa-regs.h> |
03c33a4f JK |
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 | ||
6cab4860 | 224 | return set_irq_type(IRQ_GPIO(RDY_GPIO_PIN), IRQ_TYPE_EDGE_FALLING); |
03c33a4f JK |
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"); |