video: tegra: set window size on mode change
[linux-2.6.git] / drivers / video / tegra / fb.c
1 /*
2  * drivers/video/tegra/fb.c
3  *
4  * Copyright (C) 2010 Google, Inc.
5  * Author: Erik Gilling <konkers@android.com>
6  *         Colin Cross <ccross@android.com>
7  *         Travis Geiselbrecht <travis@palm.com>
8  *
9  * This software is licensed under the terms of the GNU General Public
10  * License version 2, as published by the Free Software Foundation, and
11  * may be copied, distributed, and modified under those terms.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details.
17  *
18  */
19
20 #include <linux/fb.h>
21 #include <linux/module.h>
22 #include <linux/kernel.h>
23 #include <linux/errno.h>
24 #include <linux/string.h>
25 #include <linux/mm.h>
26 #include <linux/slab.h>
27 #include <linux/nvhost_bus.h>
28
29 #include <asm/atomic.h>
30
31 #include <mach/dc.h>
32 #include <mach/fb.h>
33
34 struct tegra_fb_info {
35         struct tegra_dc_win     *win;
36         struct nvhost_device    *ndev;
37         struct fb_info          *info;
38         bool                    valid;
39
40         struct resource         *fb_mem;
41
42         int                     xres;
43         int                     yres;
44
45         atomic_t                in_use;
46 };
47
48 /* palette array used by the fbcon */
49 static u32 pseudo_palette[16];
50
51 static int tegra_fb_open(struct fb_info *info, int user)
52 {
53         struct tegra_fb_info *tegra_fb = info->par;
54
55         if (atomic_xchg(&tegra_fb->in_use, 1))
56                 return -EBUSY;
57
58         return 0;
59 }
60
61 static int tegra_fb_release(struct fb_info *info, int user)
62 {
63         struct tegra_fb_info *tegra_fb = info->par;
64
65         WARN_ON(!atomic_xchg(&tegra_fb->in_use, 0));
66
67         return 0;
68 }
69
70 static int tegra_fb_check_var(struct fb_var_screeninfo *var,
71                               struct fb_info *info)
72 {
73         if ((var->yres * var->xres * var->bits_per_pixel / 8 * 2) >
74             info->screen_size)
75                 return -EINVAL;
76
77         /* double yres_virtual to allow double buffering through pan_display */
78         var->yres_virtual = var->yres * 2;
79
80         return 0;
81 }
82
83 static int tegra_fb_set_par(struct fb_info *info)
84 {
85         struct tegra_fb_info *tegra_fb = info->par;
86         struct fb_var_screeninfo *var = &info->var;
87
88         /* we only support RGB ordering for now */
89         switch (var->bits_per_pixel) {
90         case 32:
91         case 24:
92                 var->red.offset = 0;
93                 var->red.length = 8;
94                 var->green.offset = 8;
95                 var->green.length = 8;
96                 var->blue.offset = 16;
97                 var->blue.length = 8;
98                 tegra_fb->win->fmt = TEGRA_WIN_FMT_R8G8B8A8;
99                 break;
100         case 16:
101                 var->red.offset = 11;
102                 var->red.length = 5;
103                 var->green.offset = 5;
104                 var->green.length = 6;
105                 var->blue.offset = 0;
106                 var->blue.length = 5;
107                 tegra_fb->win->fmt = TEGRA_WIN_FMT_B5G6R5;
108                 break;
109
110         case 0:
111                 break;
112
113         default:
114                 return -EINVAL;
115         }
116         info->fix.line_length = var->xres * var->bits_per_pixel / 8;
117
118         if (var->pixclock) {
119                 struct tegra_dc_mode mode;
120
121                 info->mode = (struct fb_videomode *)
122                         fb_find_best_mode(var, &info->modelist);
123                 if (!info->mode) {
124                         dev_warn(&tegra_fb->ndev->dev, "can't match video mode\n");
125                         return -EINVAL;
126                 }
127
128                 mode.pclk = PICOS2KHZ(info->mode->pixclock) * 1000;
129                 mode.h_ref_to_sync = 1;
130                 mode.v_ref_to_sync = 1;
131                 mode.h_sync_width = info->mode->hsync_len;
132                 mode.v_sync_width = info->mode->vsync_len;
133                 mode.h_back_porch = info->mode->left_margin;
134                 mode.v_back_porch = info->mode->upper_margin;
135                 mode.h_active = info->mode->xres;
136                 mode.v_active = info->mode->yres;
137                 mode.h_front_porch = info->mode->right_margin;
138                 mode.v_front_porch = info->mode->lower_margin;
139
140                 tegra_dc_set_mode(tegra_fb->win->dc, &mode);
141
142                 tegra_fb->win->w = info->mode->xres;
143                 tegra_fb->win->h = info->mode->xres;
144                 tegra_fb->win->out_w = info->mode->xres;
145                 tegra_fb->win->out_h = info->mode->xres;
146         }
147         return 0;
148 }
149
150 static int tegra_fb_setcolreg(unsigned regno, unsigned red, unsigned green,
151         unsigned blue, unsigned transp, struct fb_info *info)
152 {
153         struct fb_var_screeninfo *var = &info->var;
154
155         if (info->fix.visual == FB_VISUAL_TRUECOLOR ||
156             info->fix.visual == FB_VISUAL_DIRECTCOLOR) {
157                 u32 v;
158
159                 if (regno >= 16)
160                         return -EINVAL;
161
162                 v = (red << var->red.offset) |
163                         (green << var->green.offset) |
164                         (blue << var->blue.offset);
165
166                 ((u32 *)info->pseudo_palette)[regno] = v;
167         }
168
169         return 0;
170 }
171
172 static int tegra_fb_blank(int blank, struct fb_info *info)
173 {
174         struct tegra_fb_info *tegra_fb = info->par;
175
176         switch (blank) {
177         case FB_BLANK_UNBLANK:
178                 dev_dbg(&tegra_fb->ndev->dev, "unblank\n");
179                 tegra_dc_enable(tegra_fb->win->dc);
180                 return 0;
181
182         case FB_BLANK_POWERDOWN:
183                 dev_dbg(&tegra_fb->ndev->dev, "blank\n");
184                 tegra_dc_disable(tegra_fb->win->dc);
185                 return 0;
186
187         default:
188                 return -ENOTTY;
189         }
190 }
191
192 static int tegra_fb_pan_display(struct fb_var_screeninfo *var,
193                                 struct fb_info *info)
194 {
195         struct tegra_fb_info *tegra_fb = info->par;
196         char __iomem *flush_start;
197         char __iomem *flush_end;
198         u32 addr;
199
200         flush_start = info->screen_base + (var->yoffset * info->fix.line_length);
201         flush_end = flush_start + (var->yres * info->fix.line_length);
202
203         info->var.xoffset = var->xoffset;
204         info->var.yoffset = var->yoffset;
205
206         addr = info->fix.smem_start + (var->yoffset * info->fix.line_length) +
207                 (var->xoffset * (var->bits_per_pixel/8));
208
209         tegra_fb->win->phys_addr = addr;
210         /* TODO: update virt_addr */
211
212         tegra_dc_update_windows(&tegra_fb->win, 1);
213         tegra_dc_sync_windows(&tegra_fb->win, 1);
214
215         return 0;
216 }
217
218 static void tegra_fb_fillrect(struct fb_info *info,
219                               const struct fb_fillrect *rect)
220 {
221         cfb_fillrect(info, rect);
222 }
223
224 static void tegra_fb_copyarea(struct fb_info *info,
225                               const struct fb_copyarea *region)
226 {
227         cfb_copyarea(info, region);
228 }
229
230 static void tegra_fb_imageblit(struct fb_info *info,
231                                const struct fb_image *image)
232 {
233         cfb_imageblit(info, image);
234 }
235
236 static struct fb_ops tegra_fb_ops = {
237         .owner = THIS_MODULE,
238         .fb_open = tegra_fb_open,
239         .fb_release = tegra_fb_release,
240         .fb_check_var = tegra_fb_check_var,
241         .fb_set_par = tegra_fb_set_par,
242         .fb_setcolreg = tegra_fb_setcolreg,
243         .fb_blank = tegra_fb_blank,
244         .fb_pan_display = tegra_fb_pan_display,
245         .fb_fillrect = tegra_fb_fillrect,
246         .fb_copyarea = tegra_fb_copyarea,
247         .fb_imageblit = tegra_fb_imageblit,
248 };
249
250 void tegra_fb_update_monspecs(struct tegra_fb_info *fb_info,
251                               struct fb_monspecs *specs,
252                               bool (*mode_filter)(struct fb_videomode *mode))
253 {
254         struct fb_event event;
255         int i;
256
257         mutex_lock(&fb_info->info->lock);
258         fb_destroy_modedb(fb_info->info->monspecs.modedb);
259
260         memcpy(&fb_info->info->monspecs, specs,
261                sizeof(fb_info->info->monspecs));
262
263         fb_destroy_modelist(&fb_info->info->modelist);
264
265         for (i = 0; i < specs->modedb_len; i++) {
266                 if (mode_filter) {
267                         if (mode_filter(&specs->modedb[i]))
268                                 fb_add_videomode(&specs->modedb[i],
269                                                  &fb_info->info->modelist);
270                 } else {
271                         fb_add_videomode(&specs->modedb[i],
272                                          &fb_info->info->modelist);
273                 }
274         }
275
276         fb_info->info->mode = (struct fb_videomode *)
277                 fb_find_best_display(specs, &fb_info->info->modelist);
278
279         event.info = fb_info->info;
280         fb_notifier_call_chain(FB_EVENT_NEW_MODELIST, &event);
281         mutex_unlock(&fb_info->info->lock);
282 }
283
284 struct tegra_fb_info *tegra_fb_register(struct nvhost_device *ndev,
285                                         struct tegra_dc *dc,
286                                         struct tegra_fb_data *fb_data,
287                                         struct resource *fb_mem)
288 {
289         struct tegra_dc_win *win;
290         struct fb_info *info;
291         struct tegra_fb_info *tegra_fb;
292         void __iomem *fb_base = NULL;
293         unsigned long fb_size = 0;      unsigned long fb_phys = 0;
294         int ret = 0;
295
296         win = tegra_dc_get_window(dc, fb_data->win);
297         if (!win) {
298                 dev_err(&ndev->dev, "dc does not have a window at index %d\n",
299                         fb_data->win);
300                 return ERR_PTR(-ENOENT);
301         }
302
303         info = framebuffer_alloc(sizeof(struct tegra_fb_info), &ndev->dev);
304         if (!info) {
305                 ret = -ENOMEM;
306                 goto err;
307         }
308
309         tegra_fb = info->par;
310         tegra_fb->win = win;
311         tegra_fb->ndev = ndev;
312         tegra_fb->fb_mem = fb_mem;
313         tegra_fb->xres = fb_data->xres;
314         tegra_fb->yres = fb_data->yres;
315         atomic_set(&tegra_fb->in_use, 0);
316
317         if (fb_mem) {
318                 fb_size = resource_size(fb_mem);
319                 fb_phys = fb_mem->start;
320                 fb_base = ioremap_nocache(fb_phys, fb_size);
321                 if (!fb_base) {
322                         dev_err(&ndev->dev, "fb can't be mapped\n");
323                         ret = -EBUSY;
324                         goto err_free;
325                 }
326                 tegra_fb->valid = true;
327         }
328
329         info->fbops = &tegra_fb_ops;
330         info->pseudo_palette = pseudo_palette;
331         info->screen_base = fb_base;
332         info->screen_size = fb_size;
333
334         strlcpy(info->fix.id, "tegra_fb", sizeof(info->fix.id));
335         info->fix.type          = FB_TYPE_PACKED_PIXELS;
336         info->fix.visual        = FB_VISUAL_TRUECOLOR;
337         info->fix.xpanstep      = 1;
338         info->fix.ypanstep      = 1;
339         info->fix.accel         = FB_ACCEL_NONE;
340         info->fix.smem_start    = fb_phys;
341         info->fix.smem_len      = fb_size;
342
343         info->var.xres                  = fb_data->xres;
344         info->var.yres                  = fb_data->yres;
345         info->var.xres_virtual          = fb_data->xres;
346         info->var.yres_virtual          = fb_data->yres * 2;
347         info->var.bits_per_pixel        = fb_data->bits_per_pixel;
348         info->var.activate              = FB_ACTIVATE_VBL;
349         /* TODO: fill in the following by querying the DC */
350         info->var.height                = -1;
351         info->var.width                 = -1;
352         info->var.pixclock              = 0;
353         info->var.left_margin           = 0;
354         info->var.right_margin          = 0;
355         info->var.upper_margin          = 0;
356         info->var.lower_margin          = 0;
357         info->var.hsync_len             = 0;
358         info->var.vsync_len             = 0;
359         info->var.vmode                 = FB_VMODE_NONINTERLACED;
360
361         win->x = 0;
362         win->y = 0;
363         win->w = fb_data->xres;
364         win->h = fb_data->yres;
365         /* TODO: set to output res dc */
366         win->out_w = fb_data->xres;
367         win->out_h = fb_data->yres;
368         win->phys_addr = fb_phys;
369         win->virt_addr = fb_base;
370         win->flags = TEGRA_WIN_FLAG_ENABLED | TEGRA_WIN_FLAG_COLOR_EXPAND;
371
372         if (fb_mem)
373                 tegra_fb_set_par(info);
374
375         if (register_framebuffer(info)) {
376                 dev_err(&ndev->dev, "failed to register framebuffer\n");
377                 ret = -ENODEV;
378                 goto err_iounmap_fb;
379         }
380
381         tegra_fb->info = info;
382
383         dev_info(&ndev->dev, "probed\n");
384
385         return tegra_fb;
386
387 err_iounmap_fb:
388         iounmap(fb_base);
389 err_free:
390         framebuffer_release(info);
391 err:
392         return ERR_PTR(ret);
393 }
394
395 void tegra_fb_unregister(struct tegra_fb_info *fb_info)
396 {
397         struct fb_info *info = fb_info->info;
398
399         unregister_framebuffer(info);
400         iounmap(info->screen_base);
401         framebuffer_release(info);
402 }