[xburst] Add support for the n516
[openwrt/svn-archive/archive.git] / target / linux / xburst / files-2.6.32 / arch / mips / jz4740 / board-n516-display.c
1 /*
2 * board-n516-display.c -- Platform device for N516 display
3 *
4 * Copyright (C) 2009, Yauhen Kharuzhy <jekhor@gmail.com>
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
11 #include <linux/module.h>
12 #include <linux/kernel.h>
13 #include <linux/errno.h>
14 #include <linux/string.h>
15 #include <linux/delay.h>
16 #include <linux/interrupt.h>
17 #include <linux/fb.h>
18 #include <linux/init.h>
19 #include <linux/platform_device.h>
20 #include <linux/irq.h>
21 #include <linux/gpio.h>
22 #include <linux/jz4740_fb.h>
23
24 #include <asm/mach-jz4740/platform.h>
25 #include <asm/mach-jz4740/board-n516.h>
26
27 #include <video/metronomefb.h>
28 #include <linux/console.h>
29
30 extern struct platform_device jz_lcd_device;
31
32 static struct fb_videomode n516_fb_modes[] = {
33 [0] = {
34 .name = "Metronome 800x600",
35 .refresh = 50,
36 .xres = 400,
37 .yres = 624,
38 .hsync_len = 31,
39 .vsync_len = 23,
40 .right_margin = 31,
41 .left_margin = 5,
42 .upper_margin = 1,
43 .lower_margin = 2,
44 .sync = FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT,
45 },
46 };
47
48 static struct jz4740_fb_platform_data n516_fb_pdata = {
49 .num_modes = ARRAY_SIZE(n516_fb_modes),
50 .modes = n516_fb_modes,
51 .bpp = 16,
52 .lcd_type = JZ_LCD_TYPE_GENERIC_16_BIT,
53 };
54
55 struct n516_board_info {
56 uint8_t *metromem;
57 size_t wfm_size;
58 struct fb_info *host_fbinfo; /* the host LCD controller's fbi */
59 unsigned int fw;
60 unsigned int fh;
61 };
62
63 static struct platform_device *n516_device;
64 static struct n516_board_info n516_board_info;
65
66 static int metronome_gpios[] = {
67 GPIO_DISPLAY_STBY,
68 GPIO_DISPLAY_RST_L,
69 GPIO_DISPLAY_RDY,
70 GPIO_DISPLAY_ERR,
71 /* GPIO_DISPLAY_OFF,*/
72 };
73
74 static const char *metronome_gpio_names[] = {
75 "Metronome STDBY",
76 "Metronome RST",
77 "Metronome RDY",
78 "Metronome ERR",
79 /* "Metronone OFF",*/
80 };
81
82 static int n516_enable_hostfb(bool enable)
83 {
84 int ret;
85 int blank = enable ? FB_BLANK_UNBLANK : FB_BLANK_POWERDOWN;
86
87 acquire_console_sem();
88 ret = fb_blank(n516_board_info.host_fbinfo, blank);
89 release_console_sem();
90
91 return ret;
92 }
93
94 static int n516_init_metronome_gpios(struct metronomefb_par *par)
95 {
96 int i;
97 int ret;
98
99 for (i = 0; i < ARRAY_SIZE(metronome_gpios); ++i) {
100 ret = gpio_request(metronome_gpios[i], metronome_gpio_names[i]);
101 if (ret)
102 goto err;
103 }
104
105 gpio_direction_output(GPIO_DISPLAY_OFF, 0);
106 gpio_direction_output(GPIO_DISPLAY_RST_L, 0);
107 gpio_direction_output(GPIO_DISPLAY_STBY, 0);
108 gpio_direction_input(GPIO_DISPLAY_RDY);
109 gpio_direction_input(GPIO_DISPLAY_ERR);
110
111 return 0;
112 err:
113 for (--i; i >= 0; --i)
114 gpio_free(metronome_gpios[i]);
115
116 return ret;
117 }
118
119 static int n516_share_video_mem(struct fb_info *info)
120 {
121 int ret;
122
123 dev_dbg(&n516_device->dev, "ENTER %s\n", __func__);
124 dev_dbg(&n516_device->dev, "%s, info->var.xres = %u, info->var.yres = %u\n", __func__, info->var.xres, info->var.yres);
125 /* rough check if this is our desired fb and not something else */
126 if ((info->var.xres != n516_fb_pdata.modes[0].xres)
127 || (info->var.yres != n516_fb_pdata.modes[0].yres))
128 return 0;
129
130 /* we've now been notified that we have our new fb */
131 n516_board_info.metromem = info->screen_base;
132 n516_board_info.host_fbinfo = info;
133
134 n516_enable_hostfb(false);
135 /* try to refcount host drv since we are the consumer after this */
136 if (!try_module_get(info->fbops->owner))
137 return -ENODEV;
138
139 /* this _add binds metronomefb to n516. metronomefb refcounts n516 */
140 ret = platform_device_add(n516_device);
141
142 if (ret) {
143 platform_device_put(n516_device);
144 return ret;
145 }
146
147 /* request our platform independent driver */
148 request_module("metronomefb");
149
150 return 0;
151 }
152
153 static int n516_unshare_video_mem(struct fb_info *info)
154 {
155 dev_dbg(&n516_device->dev, "ENTER %s\n", __func__);
156
157 if (info != n516_board_info.host_fbinfo)
158 return 0;
159
160 module_put(n516_board_info.host_fbinfo->fbops->owner);
161 return 0;
162 }
163
164 static int n516_fb_notifier_callback(struct notifier_block *self,
165 unsigned long event, void *data)
166 {
167 struct fb_event *evdata = data;
168 struct fb_info *info = evdata->info;
169
170 dev_dbg(&n516_device->dev, "ENTER %s\n", __func__);
171
172 if (event == FB_EVENT_FB_REGISTERED)
173 return n516_share_video_mem(info);
174 else if (event == FB_EVENT_FB_UNREGISTERED)
175 return n516_unshare_video_mem(info);
176
177 return 0;
178 }
179
180 static struct notifier_block n516_fb_notif = {
181 .notifier_call = n516_fb_notifier_callback,
182 };
183
184 /* this gets called as part of our init. these steps must be done now so
185 * that we can use set_pxa_fb_info */
186 static void __init n516_presetup_fb(void)
187 {
188 int padding_size;
189 int totalsize;
190
191 /* the frame buffer is divided as follows:
192 command | CRC | padding
193 16kb waveform data | CRC | padding
194 image data | CRC
195 */
196
197 n516_board_info.fw = 800;
198 n516_board_info.fh = 624;
199
200 /* waveform must be 16k + 2 for checksum */
201 n516_board_info.wfm_size = roundup(16*1024 + 2, n516_board_info.fw);
202
203 padding_size = PAGE_SIZE + (4 * n516_board_info.fw);
204
205 /* total is 1 cmd , 1 wfm, padding and image */
206 totalsize = n516_board_info.fw + n516_board_info.wfm_size;
207 totalsize += padding_size + (n516_board_info.fw*n516_board_info.fh);
208
209 /* save this off because we're manipulating fw after this and
210 * we'll need it when we're ready to setup the framebuffer */
211
212 /* the reason we do this adjustment is because we want to acquire
213 * more framebuffer memory without imposing custom awareness on the
214 * underlying driver */
215 n516_fb_pdata.modes[0].yres = DIV_ROUND_UP(totalsize, n516_board_info.fw);
216
217 jz4740_framebuffer_device.dev.platform_data = &n516_fb_pdata;
218 platform_device_register(&jz4740_framebuffer_device);
219 }
220
221 /* this gets called by metronomefb as part of its init, in our case, we
222 * have already completed initial framebuffer init in presetup_fb so we
223 * can just setup the fb access pointers */
224 static int n516_setup_fb(struct metronomefb_par *par)
225 {
226 /* metromem was set up by the notifier in share_video_mem so now
227 * we can use its value to calculate the other entries */
228 par->metromem_cmd = (struct metromem_cmd *) n516_board_info.metromem;
229 par->metromem_wfm = n516_board_info.metromem + n516_board_info.fw;
230 par->metromem_img = par->metromem_wfm + n516_board_info.wfm_size;
231 par->metromem_img_csum = (u16 *) (par->metromem_img + (n516_board_info.fw * n516_board_info.fh));
232 par->metromem_dma = n516_board_info.host_fbinfo->fix.smem_start;
233
234 return 0;
235 }
236
237 static int n516_get_panel_type(void)
238 {
239 return 5;
240 }
241
242 static irqreturn_t n516_handle_irq(int irq, void *dev_id)
243 {
244 struct metronomefb_par *par = dev_id;
245
246 dev_dbg(&par->pdev->dev, "Metronome IRQ! RDY=%d\n", gpio_get_value(GPIO_DISPLAY_RDY));
247 wake_up_all(&par->waitq);
248
249 return IRQ_HANDLED;
250 }
251
252 static void n516_power_ctl(struct metronomefb_par *par, int cmd)
253 {
254 switch (cmd) {
255 case METRONOME_POWER_OFF:
256 gpio_set_value(GPIO_DISPLAY_OFF, 1);
257 n516_enable_hostfb(false);
258 break;
259 case METRONOME_POWER_ON:
260 gpio_set_value(GPIO_DISPLAY_OFF, 0);
261 n516_enable_hostfb(true);
262 break;
263 }
264 }
265
266 static int n516_get_rdy(struct metronomefb_par *par)
267 {
268 return gpio_get_value(GPIO_DISPLAY_RDY);
269 }
270
271 static int n516_get_err(struct metronomefb_par *par)
272 {
273 return gpio_get_value(GPIO_DISPLAY_ERR);
274 }
275
276 static int n516_setup_irq(struct fb_info *info)
277 {
278 int ret;
279
280 dev_dbg(&n516_device->dev, "ENTER %s\n", __func__);
281
282 ret = request_irq(gpio_to_irq(GPIO_DISPLAY_RDY), n516_handle_irq,
283 IRQF_TRIGGER_RISING,
284 "n516", info->par);
285 if (ret)
286 dev_err(&n516_device->dev, "request_irq failed: %d\n", ret);
287
288 return ret;
289 }
290
291 static void n516_set_rst(struct metronomefb_par *par, int state)
292 {
293 dev_dbg(&n516_device->dev, "ENTER %s, RDY=%d\n", __func__, gpio_get_value(GPIO_DISPLAY_RDY));
294 if (state)
295 gpio_set_value(GPIO_DISPLAY_RST_L, 1);
296 else
297 gpio_set_value(GPIO_DISPLAY_RST_L, 0);
298 }
299
300 static void n516_set_stdby(struct metronomefb_par *par, int state)
301 {
302 dev_dbg(&n516_device->dev, "ENTER %s, RDY=%d\n", __func__, gpio_get_value(GPIO_DISPLAY_RDY));
303 if (state)
304 gpio_set_value(GPIO_DISPLAY_STBY, 1);
305 else
306 gpio_set_value(GPIO_DISPLAY_STBY, 0);
307 }
308
309 static int n516_wait_event(struct metronomefb_par *par)
310 {
311 unsigned long timeout = jiffies + HZ / 20;
312
313 dev_dbg(&n516_device->dev, "ENTER1 %s, RDY=%d\n",
314 __func__, gpio_get_value(GPIO_DISPLAY_RDY));
315 while (n516_get_rdy(par) && time_before(jiffies, timeout))
316 schedule();
317
318 dev_dbg(&n516_device->dev, "ENTER2 %s, RDY=%d\n",
319 __func__, gpio_get_value(GPIO_DISPLAY_RDY));
320 return wait_event_timeout(par->waitq,
321 n516_get_rdy(par), HZ * 2) ? 0 : -EIO;
322 }
323
324 static int n516_wait_event_intr(struct metronomefb_par *par)
325 {
326 unsigned long timeout = jiffies + HZ/20;
327
328 dev_dbg(&n516_device->dev, "ENTER1 %s, RDY=%d\n",
329 __func__, gpio_get_value(GPIO_DISPLAY_RDY));
330 while (n516_get_rdy(par) && time_before(jiffies, timeout))
331 schedule();
332
333 dev_dbg(&n516_device->dev, "ENTER2 %s, RDY=%d\n",
334 __func__, gpio_get_value(GPIO_DISPLAY_RDY));
335 return wait_event_interruptible_timeout(par->waitq,
336 n516_get_rdy(par), HZ * 2) ? 0 : -EIO;
337 }
338
339 static void n516_cleanup(struct metronomefb_par *par)
340 {
341 int i;
342
343 free_irq(gpio_to_irq(GPIO_DISPLAY_RDY), par);
344 for (i = 0; i < ARRAY_SIZE(metronome_gpios); ++i)
345 gpio_free(metronome_gpios[i]);
346 }
347
348 static struct metronome_board n516_board __initdata = {
349 .owner = THIS_MODULE,
350 .power_ctl = n516_power_ctl,
351 .setup_irq = n516_setup_irq,
352 .setup_io = n516_init_metronome_gpios,
353 .setup_fb = n516_setup_fb,
354 .set_rst = n516_set_rst,
355 .get_err = n516_get_err,
356 .get_rdy = n516_get_rdy,
357 .set_stdby = n516_set_stdby,
358 .met_wait_event = n516_wait_event,
359 .met_wait_event_intr = n516_wait_event_intr,
360 .get_panel_type = n516_get_panel_type,
361 .cleanup = n516_cleanup,
362 };
363
364 static int __init n516_init(void)
365 {
366 int ret;
367
368 /* Keep the metronome off, until its driver is loaded */
369 ret = gpio_request(GPIO_DISPLAY_OFF, "Display off");
370 if (ret)
371 return ret;
372
373 gpio_direction_output(GPIO_DISPLAY_OFF, 1);
374
375 /* before anything else, we request notification for any fb
376 * creation events */
377 fb_register_client(&n516_fb_notif);
378
379 n516_device = platform_device_alloc("metronomefb", -1);
380 if (!n516_device)
381 return -ENOMEM;
382
383 /* the n516_board that will be seen by metronomefb is a copy */
384 platform_device_add_data(n516_device, &n516_board,
385 sizeof(n516_board));
386
387 n516_presetup_fb();
388
389 return 0;
390 }
391 module_init(n516_init);
392
393 MODULE_DESCRIPTION("board driver for n516 display");
394 MODULE_AUTHOR("Yauhen Kharuzhy");
395 MODULE_LICENSE("GPL");