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 "pxa25x.h" |
34 | #include "gumstix.h" |
35 | #include <linux/platform_data/video-pxafb.h> |
36 | |
37 | #include "generic.h" |
38 | |
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(gpio: gpios[i], label: 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, value: 0); |
120 | gpio_direction_output(STDBY_GPIO_PIN, value: 0); |
121 | gpio_direction_output(RST_GPIO_PIN, value: 0); |
122 | |
123 | gpio_direction_input(RDY_GPIO_PIN); |
124 | gpio_direction_input(ERR_GPIO_PIN); |
125 | |
126 | gpio_direction_output(PCBPWR_GPIO_PIN, value: 0); |
127 | |
128 | return 0; |
129 | |
130 | err_req_gpio: |
131 | while (--i >= 0) |
132 | gpio_free(gpio: gpios[i]); |
133 | |
134 | return err; |
135 | } |
136 | |
137 | static void am200_cleanup(struct metronomefb_par *par) |
138 | { |
139 | int i; |
140 | |
141 | free_irq(PXA_GPIO_TO_IRQ(RDY_GPIO_PIN), par); |
142 | |
143 | for (i = 0; i < ARRAY_SIZE(gpios); i++) |
144 | gpio_free(gpio: 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(module: 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(module: 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 |
197 | * that we can use pxa_set_fb_info */ |
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 | |
252 | pxa_set_fb_info(NULL, &am200_fb_info); |
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 | |
295 | ret = request_irq(PXA_GPIO_TO_IRQ(RDY_GPIO_PIN), handler: am200_handle_irq, |
296 | IRQF_TRIGGER_FALLING, name: "AM200" , dev: info->par); |
297 | if (ret) |
298 | dev_err(&am200_device->dev, "request_irq failed: %d\n" , ret); |
299 | |
300 | return ret; |
301 | } |
302 | |
303 | static void am200_set_rst(struct metronomefb_par *par, int state) |
304 | { |
305 | gpio_set_value(RST_GPIO_PIN, value: state); |
306 | } |
307 | |
308 | static void am200_set_stdby(struct metronomefb_par *par, int state) |
309 | { |
310 | gpio_set_value(STDBY_GPIO_PIN, value: state); |
311 | } |
312 | |
313 | static int am200_wait_event(struct metronomefb_par *par) |
314 | { |
315 | return wait_event_timeout(par->waitq, gpio_get_value(RDY_GPIO_PIN), HZ); |
316 | } |
317 | |
318 | static int am200_wait_event_intr(struct metronomefb_par *par) |
319 | { |
320 | return wait_event_interruptible_timeout(par->waitq, |
321 | gpio_get_value(RDY_GPIO_PIN), HZ); |
322 | } |
323 | |
324 | static struct metronome_board am200_board = { |
325 | .owner = THIS_MODULE, |
326 | .setup_irq = am200_setup_irq, |
327 | .setup_io = am200_init_gpio_regs, |
328 | .setup_fb = am200_setup_fb, |
329 | .set_rst = am200_set_rst, |
330 | .set_stdby = am200_set_stdby, |
331 | .met_wait_event = am200_wait_event, |
332 | .met_wait_event_intr = am200_wait_event_intr, |
333 | .get_panel_type = am200_get_panel_type, |
334 | .cleanup = am200_cleanup, |
335 | }; |
336 | |
337 | static unsigned long am200_pin_config[] __initdata = { |
338 | GPIO51_GPIO, |
339 | GPIO49_GPIO, |
340 | GPIO48_GPIO, |
341 | GPIO32_GPIO, |
342 | GPIO17_GPIO, |
343 | GPIO16_GPIO, |
344 | }; |
345 | |
346 | int __init am200_init(void) |
347 | { |
348 | int ret; |
349 | |
350 | /* |
351 | * Before anything else, we request notification for any fb |
352 | * creation events. |
353 | * |
354 | * FIXME: This is terrible and needs to be nuked. The notifier is used |
355 | * to get at the fb base address from the boot splash fb driver, which |
356 | * is then passed to metronomefb. Instaed of metronomfb or this board |
357 | * support file here figuring this out on their own. |
358 | * |
359 | * See also the #ifdef in fbmem.c. |
360 | */ |
361 | fb_register_client(nb: &am200_fb_notif); |
362 | |
363 | pxa2xx_mfp_config(ARRAY_AND_SIZE(am200_pin_config)); |
364 | |
365 | /* request our platform independent driver */ |
366 | request_module("metronomefb" ); |
367 | |
368 | am200_device = platform_device_alloc(name: "metronomefb" , id: -1); |
369 | if (!am200_device) |
370 | return -ENOMEM; |
371 | |
372 | /* the am200_board that will be seen by metronomefb is a copy */ |
373 | platform_device_add_data(pdev: am200_device, data: &am200_board, |
374 | size: sizeof(am200_board)); |
375 | |
376 | /* this _add binds metronomefb to am200. metronomefb refcounts am200 */ |
377 | ret = platform_device_add(pdev: am200_device); |
378 | |
379 | if (ret) { |
380 | platform_device_put(pdev: am200_device); |
381 | fb_unregister_client(nb: &am200_fb_notif); |
382 | return ret; |
383 | } |
384 | |
385 | am200_presetup_fb(); |
386 | |
387 | return 0; |
388 | } |
389 | |
390 | module_param(panel_type, uint, 0); |
391 | MODULE_PARM_DESC(panel_type, "Select the panel type: 6, 8, 97" ); |
392 | |
393 | MODULE_DESCRIPTION("board driver for am200 metronome epd kit" ); |
394 | MODULE_AUTHOR("Jaya Kumar" ); |
395 | MODULE_LICENSE("GPL" ); |
396 | |