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