diff options
Diffstat (limited to 'arch/arm/mach-pxa/am200epd.c')
-rw-r--r-- | arch/arm/mach-pxa/am200epd.c | 374 |
1 files changed, 374 insertions, 0 deletions
diff --git a/arch/arm/mach-pxa/am200epd.c b/arch/arm/mach-pxa/am200epd.c new file mode 100644 index 000000000000..b965085a37b9 --- /dev/null +++ b/arch/arm/mach-pxa/am200epd.c | |||
@@ -0,0 +1,374 @@ | |||
1 | /* | ||
2 | * 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/irq.h> | ||
31 | #include <linux/gpio.h> | ||
32 | |||
33 | #include <mach/pxafb.h> | ||
34 | |||
35 | #include <video/metronomefb.h> | ||
36 | |||
37 | static unsigned int panel_type = 6; | ||
38 | static struct platform_device *am200_device; | ||
39 | static struct metronome_board am200_board; | ||
40 | |||
41 | static struct pxafb_mode_info am200_fb_mode_9inch7 = { | ||
42 | .pixclock = 40000, | ||
43 | .xres = 1200, | ||
44 | .yres = 842, | ||
45 | .bpp = 16, | ||
46 | .hsync_len = 2, | ||
47 | .left_margin = 2, | ||
48 | .right_margin = 2, | ||
49 | .vsync_len = 1, | ||
50 | .upper_margin = 2, | ||
51 | .lower_margin = 25, | ||
52 | .sync = FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, | ||
53 | }; | ||
54 | |||
55 | static struct pxafb_mode_info am200_fb_mode_8inch = { | ||
56 | .pixclock = 40000, | ||
57 | .xres = 1088, | ||
58 | .yres = 791, | ||
59 | .bpp = 16, | ||
60 | .hsync_len = 28, | ||
61 | .left_margin = 8, | ||
62 | .right_margin = 30, | ||
63 | .vsync_len = 8, | ||
64 | .upper_margin = 10, | ||
65 | .lower_margin = 8, | ||
66 | .sync = FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, | ||
67 | }; | ||
68 | |||
69 | static struct pxafb_mode_info am200_fb_mode_6inch = { | ||
70 | .pixclock = 40189, | ||
71 | .xres = 832, | ||
72 | .yres = 622, | ||
73 | .bpp = 16, | ||
74 | .hsync_len = 28, | ||
75 | .left_margin = 34, | ||
76 | .right_margin = 34, | ||
77 | .vsync_len = 25, | ||
78 | .upper_margin = 0, | ||
79 | .lower_margin = 2, | ||
80 | .sync = FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, | ||
81 | }; | ||
82 | |||
83 | static struct pxafb_mach_info am200_fb_info = { | ||
84 | .modes = &am200_fb_mode_6inch, | ||
85 | .num_modes = 1, | ||
86 | .lcd_conn = LCD_TYPE_COLOR_TFT | LCD_PCLK_EDGE_FALL | | ||
87 | LCD_AC_BIAS_FREQ(24), | ||
88 | }; | ||
89 | |||
90 | /* register offsets for gpio control */ | ||
91 | #define LED_GPIO_PIN 51 | ||
92 | #define STDBY_GPIO_PIN 48 | ||
93 | #define RST_GPIO_PIN 49 | ||
94 | #define RDY_GPIO_PIN 32 | ||
95 | #define ERR_GPIO_PIN 17 | ||
96 | #define PCBPWR_GPIO_PIN 16 | ||
97 | static int gpios[] = { LED_GPIO_PIN , STDBY_GPIO_PIN , RST_GPIO_PIN, | ||
98 | RDY_GPIO_PIN, ERR_GPIO_PIN, PCBPWR_GPIO_PIN }; | ||
99 | static char *gpio_names[] = { "LED" , "STDBY" , "RST", "RDY", "ERR", "PCBPWR" }; | ||
100 | |||
101 | static int am200_init_gpio_regs(struct metronomefb_par *par) | ||
102 | { | ||
103 | int i; | ||
104 | int err; | ||
105 | |||
106 | for (i = 0; i < ARRAY_SIZE(gpios); i++) { | ||
107 | err = gpio_request(gpios[i], gpio_names[i]); | ||
108 | if (err) { | ||
109 | dev_err(&am200_device->dev, "failed requesting " | ||
110 | "gpio %s, err=%d\n", gpio_names[i], err); | ||
111 | goto err_req_gpio; | ||
112 | } | ||
113 | } | ||
114 | |||
115 | gpio_direction_output(LED_GPIO_PIN, 0); | ||
116 | gpio_direction_output(STDBY_GPIO_PIN, 0); | ||
117 | gpio_direction_output(RST_GPIO_PIN, 0); | ||
118 | |||
119 | gpio_direction_input(RDY_GPIO_PIN); | ||
120 | gpio_direction_input(ERR_GPIO_PIN); | ||
121 | |||
122 | gpio_direction_output(PCBPWR_GPIO_PIN, 0); | ||
123 | |||
124 | return 0; | ||
125 | |||
126 | err_req_gpio: | ||
127 | while (i > 0) | ||
128 | gpio_free(gpios[i--]); | ||
129 | |||
130 | return err; | ||
131 | } | ||
132 | |||
133 | static void am200_cleanup(struct metronomefb_par *par) | ||
134 | { | ||
135 | int i; | ||
136 | |||
137 | free_irq(IRQ_GPIO(RDY_GPIO_PIN), par); | ||
138 | |||
139 | for (i = 0; i < ARRAY_SIZE(gpios); i++) | ||
140 | gpio_free(gpios[i]); | ||
141 | } | ||
142 | |||
143 | static int am200_share_video_mem(struct fb_info *info) | ||
144 | { | ||
145 | /* rough check if this is our desired fb and not something else */ | ||
146 | if ((info->var.xres != am200_fb_info.modes->xres) | ||
147 | || (info->var.yres != am200_fb_info.modes->yres)) | ||
148 | return 0; | ||
149 | |||
150 | /* we've now been notified that we have our new fb */ | ||
151 | am200_board.metromem = info->screen_base; | ||
152 | am200_board.host_fbinfo = info; | ||
153 | |||
154 | /* try to refcount host drv since we are the consumer after this */ | ||
155 | if (!try_module_get(info->fbops->owner)) | ||
156 | return -ENODEV; | ||
157 | |||
158 | return 0; | ||
159 | } | ||
160 | |||
161 | static int am200_unshare_video_mem(struct fb_info *info) | ||
162 | { | ||
163 | dev_dbg(&am200_device->dev, "ENTER %s\n", __func__); | ||
164 | |||
165 | if (info != am200_board.host_fbinfo) | ||
166 | return 0; | ||
167 | |||
168 | module_put(am200_board.host_fbinfo->fbops->owner); | ||
169 | return 0; | ||
170 | } | ||
171 | |||
172 | static int am200_fb_notifier_callback(struct notifier_block *self, | ||
173 | unsigned long event, void *data) | ||
174 | { | ||
175 | struct fb_event *evdata = data; | ||
176 | struct fb_info *info = evdata->info; | ||
177 | |||
178 | dev_dbg(&am200_device->dev, "ENTER %s\n", __func__); | ||
179 | |||
180 | if (event == FB_EVENT_FB_REGISTERED) | ||
181 | return am200_share_video_mem(info); | ||
182 | else if (event == FB_EVENT_FB_UNREGISTERED) | ||
183 | return am200_unshare_video_mem(info); | ||
184 | |||
185 | return 0; | ||
186 | } | ||
187 | |||
188 | static struct notifier_block am200_fb_notif = { | ||
189 | .notifier_call = am200_fb_notifier_callback, | ||
190 | }; | ||
191 | |||
192 | /* this gets called as part of our init. these steps must be done now so | ||
193 | * that we can use set_pxa_fb_info */ | ||
194 | static void __init am200_presetup_fb(void) | ||
195 | { | ||
196 | int fw; | ||
197 | int fh; | ||
198 | int padding_size; | ||
199 | int totalsize; | ||
200 | |||
201 | switch (panel_type) { | ||
202 | case 6: | ||
203 | am200_fb_info.modes = &am200_fb_mode_6inch; | ||
204 | break; | ||
205 | case 8: | ||
206 | am200_fb_info.modes = &am200_fb_mode_8inch; | ||
207 | break; | ||
208 | case 97: | ||
209 | am200_fb_info.modes = &am200_fb_mode_9inch7; | ||
210 | break; | ||
211 | default: | ||
212 | dev_err(&am200_device->dev, "invalid panel_type selection," | ||
213 | " setting to 6\n"); | ||
214 | am200_fb_info.modes = &am200_fb_mode_6inch; | ||
215 | break; | ||
216 | } | ||
217 | |||
218 | /* the frame buffer is divided as follows: | ||
219 | command | CRC | padding | ||
220 | 16kb waveform data | CRC | padding | ||
221 | image data | CRC | ||
222 | */ | ||
223 | |||
224 | fw = am200_fb_info.modes->xres; | ||
225 | fh = am200_fb_info.modes->yres; | ||
226 | |||
227 | /* waveform must be 16k + 2 for checksum */ | ||
228 | am200_board.wfm_size = roundup(16*1024 + 2, fw); | ||
229 | |||
230 | padding_size = PAGE_SIZE + (4 * fw); | ||
231 | |||
232 | /* total is 1 cmd , 1 wfm, padding and image */ | ||
233 | totalsize = fw + am200_board.wfm_size + padding_size + (fw*fh); | ||
234 | |||
235 | /* save this off because we're manipulating fw after this and | ||
236 | * we'll need it when we're ready to setup the framebuffer */ | ||
237 | am200_board.fw = fw; | ||
238 | am200_board.fh = fh; | ||
239 | |||
240 | /* the reason we do this adjustment is because we want to acquire | ||
241 | * more framebuffer memory without imposing custom awareness on the | ||
242 | * underlying pxafb driver */ | ||
243 | am200_fb_info.modes->yres = DIV_ROUND_UP(totalsize, fw); | ||
244 | |||
245 | /* we divide since we told the LCD controller we're 16bpp */ | ||
246 | am200_fb_info.modes->xres /= 2; | ||
247 | |||
248 | set_pxa_fb_info(&am200_fb_info); | ||
249 | |||
250 | } | ||
251 | |||
252 | /* this gets called by metronomefb as part of its init, in our case, we | ||
253 | * have already completed initial framebuffer init in presetup_fb so we | ||
254 | * can just setup the fb access pointers */ | ||
255 | static int am200_setup_fb(struct metronomefb_par *par) | ||
256 | { | ||
257 | int fw; | ||
258 | int fh; | ||
259 | |||
260 | fw = am200_board.fw; | ||
261 | fh = am200_board.fh; | ||
262 | |||
263 | /* metromem was set up by the notifier in share_video_mem so now | ||
264 | * we can use its value to calculate the other entries */ | ||
265 | par->metromem_cmd = (struct metromem_cmd *) am200_board.metromem; | ||
266 | par->metromem_wfm = am200_board.metromem + fw; | ||
267 | par->metromem_img = par->metromem_wfm + am200_board.wfm_size; | ||
268 | par->metromem_img_csum = (u16 *) (par->metromem_img + (fw * fh)); | ||
269 | par->metromem_dma = am200_board.host_fbinfo->fix.smem_start; | ||
270 | |||
271 | return 0; | ||
272 | } | ||
273 | |||
274 | static int am200_get_panel_type(void) | ||
275 | { | ||
276 | return panel_type; | ||
277 | } | ||
278 | |||
279 | static irqreturn_t am200_handle_irq(int irq, void *dev_id) | ||
280 | { | ||
281 | struct metronomefb_par *par = dev_id; | ||
282 | |||
283 | wake_up_interruptible(&par->waitq); | ||
284 | return IRQ_HANDLED; | ||
285 | } | ||
286 | |||
287 | static int am200_setup_irq(struct fb_info *info) | ||
288 | { | ||
289 | int ret; | ||
290 | |||
291 | ret = request_irq(IRQ_GPIO(RDY_GPIO_PIN), am200_handle_irq, | ||
292 | IRQF_DISABLED|IRQF_TRIGGER_FALLING, | ||
293 | "AM200", info->par); | ||
294 | if (ret) | ||
295 | dev_err(&am200_device->dev, "request_irq failed: %d\n", ret); | ||
296 | |||
297 | return ret; | ||
298 | } | ||
299 | |||
300 | static void am200_set_rst(struct metronomefb_par *par, int state) | ||
301 | { | ||
302 | gpio_set_value(RST_GPIO_PIN, state); | ||
303 | } | ||
304 | |||
305 | static void am200_set_stdby(struct metronomefb_par *par, int state) | ||
306 | { | ||
307 | gpio_set_value(STDBY_GPIO_PIN, state); | ||
308 | } | ||
309 | |||
310 | static int am200_wait_event(struct metronomefb_par *par) | ||
311 | { | ||
312 | return wait_event_timeout(par->waitq, gpio_get_value(RDY_GPIO_PIN), HZ); | ||
313 | } | ||
314 | |||
315 | static int am200_wait_event_intr(struct metronomefb_par *par) | ||
316 | { | ||
317 | return wait_event_interruptible_timeout(par->waitq, | ||
318 | gpio_get_value(RDY_GPIO_PIN), HZ); | ||
319 | } | ||
320 | |||
321 | static struct metronome_board am200_board = { | ||
322 | .owner = THIS_MODULE, | ||
323 | .setup_irq = am200_setup_irq, | ||
324 | .setup_io = am200_init_gpio_regs, | ||
325 | .setup_fb = am200_setup_fb, | ||
326 | .set_rst = am200_set_rst, | ||
327 | .set_stdby = am200_set_stdby, | ||
328 | .met_wait_event = am200_wait_event, | ||
329 | .met_wait_event_intr = am200_wait_event_intr, | ||
330 | .get_panel_type = am200_get_panel_type, | ||
331 | .cleanup = am200_cleanup, | ||
332 | }; | ||
333 | |||
334 | static int __init am200_init(void) | ||
335 | { | ||
336 | int ret; | ||
337 | |||
338 | /* before anything else, we request notification for any fb | ||
339 | * creation events */ | ||
340 | fb_register_client(&am200_fb_notif); | ||
341 | |||
342 | /* request our platform independent driver */ | ||
343 | request_module("metronomefb"); | ||
344 | |||
345 | am200_device = platform_device_alloc("metronomefb", -1); | ||
346 | if (!am200_device) | ||
347 | return -ENOMEM; | ||
348 | |||
349 | /* the am200_board that will be seen by metronomefb is a copy */ | ||
350 | platform_device_add_data(am200_device, &am200_board, | ||
351 | sizeof(am200_board)); | ||
352 | |||
353 | /* this _add binds metronomefb to am200. metronomefb refcounts am200 */ | ||
354 | ret = platform_device_add(am200_device); | ||
355 | |||
356 | if (ret) { | ||
357 | platform_device_put(am200_device); | ||
358 | fb_unregister_client(&am200_fb_notif); | ||
359 | return ret; | ||
360 | } | ||
361 | |||
362 | am200_presetup_fb(); | ||
363 | |||
364 | return 0; | ||
365 | } | ||
366 | |||
367 | module_param(panel_type, uint, 0); | ||
368 | MODULE_PARM_DESC(panel_type, "Select the panel type: 6, 8, 97"); | ||
369 | |||
370 | module_init(am200_init); | ||
371 | |||
372 | MODULE_DESCRIPTION("board driver for am200 metronome epd kit"); | ||
373 | MODULE_AUTHOR("Jaya Kumar"); | ||
374 | MODULE_LICENSE("GPL"); | ||