blob: 2e82adf9ab6d7c9c22e548d74e303aa8e8e4ef40 [file] [log] [blame]
Ben Dooksec549a02009-03-31 15:25:39 -07001/* linux/drivers/video/s3c-fb.c
2 *
3 * Copyright 2008 Openmoko Inc.
Ben Dooks50a55032010-08-10 18:02:33 -07004 * Copyright 2008-2010 Simtec Electronics
Ben Dooksec549a02009-03-31 15:25:39 -07005 * Ben Dooks <ben@simtec.co.uk>
6 * http://armlinux.simtec.co.uk/
7 *
8 * Samsung SoC Framebuffer driver
9 *
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License version 2 as
Ben Dooksc4bb6ff2010-08-10 18:02:34 -070012 * published by the Free Software FoundatIon.
Ben Dooksec549a02009-03-31 15:25:39 -070013*/
14
15#include <linux/kernel.h>
16#include <linux/module.h>
17#include <linux/platform_device.h>
18#include <linux/dma-mapping.h>
Tejun Heo5a0e3ad2010-03-24 17:04:11 +090019#include <linux/slab.h>
Ben Dooksec549a02009-03-31 15:25:39 -070020#include <linux/init.h>
Ben Dooksec549a02009-03-31 15:25:39 -070021#include <linux/clk.h>
22#include <linux/fb.h>
23#include <linux/io.h>
Pawel Osciakefdc8462010-08-10 18:02:38 -070024#include <linux/uaccess.h>
25#include <linux/interrupt.h>
Ben Dooksec549a02009-03-31 15:25:39 -070026
27#include <mach/map.h>
Ben Dooksc4bb6ff2010-08-10 18:02:34 -070028#include <plat/regs-fb-v4.h>
Ben Dooksec549a02009-03-31 15:25:39 -070029#include <plat/fb.h>
30
31/* This driver will export a number of framebuffer interfaces depending
32 * on the configuration passed in via the platform data. Each fb instance
33 * maps to a hardware window. Currently there is no support for runtime
34 * setting of the alpha-blending functions that each window has, so only
35 * window 0 is actually useful.
36 *
37 * Window 0 is treated specially, it is used for the basis of the LCD
38 * output timings and as the control for the output power-down state.
39*/
40
Ben Dooks50a55032010-08-10 18:02:33 -070041/* note, the previous use of <mach/regs-fb.h> to get platform specific data
42 * has been replaced by using the platform device name to pick the correct
43 * configuration data for the system.
Ben Dooksec549a02009-03-31 15:25:39 -070044*/
45
46#ifdef CONFIG_FB_S3C_DEBUG_REGWRITE
47#undef writel
48#define writel(v, r) do { \
49 printk(KERN_DEBUG "%s: %08x => %p\n", __func__, (unsigned int)v, r); \
50 __raw_writel(v, r); } while(0)
51#endif /* FB_S3C_DEBUG_REGWRITE */
52
Pawel Osciakefdc8462010-08-10 18:02:38 -070053/* irq_flags bits */
54#define S3C_FB_VSYNC_IRQ_EN 0
55
56#define VSYNC_TIMEOUT_MSEC 50
57
Ben Dooksec549a02009-03-31 15:25:39 -070058struct s3c_fb;
59
Ben Dooks50a55032010-08-10 18:02:33 -070060#define VALID_BPP(x) (1 << ((x) - 1))
61
Ben Dooksc4bb6ff2010-08-10 18:02:34 -070062#define OSD_BASE(win, variant) ((variant).osd + ((win) * (variant).osd_stride))
63#define VIDOSD_A(win, variant) (OSD_BASE(win, variant) + 0x00)
64#define VIDOSD_B(win, variant) (OSD_BASE(win, variant) + 0x04)
65#define VIDOSD_C(win, variant) (OSD_BASE(win, variant) + 0x08)
66#define VIDOSD_D(win, variant) (OSD_BASE(win, variant) + 0x0C)
67
Ben Dooks50a55032010-08-10 18:02:33 -070068/**
69 * struct s3c_fb_variant - fb variant information
Ben Dooksc4bb6ff2010-08-10 18:02:34 -070070 * @is_2443: Set if S3C2443/S3C2416 style hardware.
Ben Dooks50a55032010-08-10 18:02:33 -070071 * @nr_windows: The number of windows.
Ben Dooksc4bb6ff2010-08-10 18:02:34 -070072 * @vidtcon: The base for the VIDTCONx registers
73 * @wincon: The base for the WINxCON registers.
74 * @winmap: The base for the WINxMAP registers.
75 * @keycon: The abse for the WxKEYCON registers.
76 * @buf_start: Offset of buffer start registers.
77 * @buf_size: Offset of buffer size registers.
78 * @buf_end: Offset of buffer end registers.
79 * @osd: The base for the OSD registers.
Ben Dooks50a55032010-08-10 18:02:33 -070080 * @palette: Address of palette memory, or 0 if none.
Pawel Osciak067b2262010-08-10 18:02:38 -070081 * @has_prtcon: Set if has PRTCON register.
Ben Dooks50a55032010-08-10 18:02:33 -070082 */
83struct s3c_fb_variant {
Ben Dooksc4bb6ff2010-08-10 18:02:34 -070084 unsigned int is_2443:1;
Ben Dooks50a55032010-08-10 18:02:33 -070085 unsigned short nr_windows;
Ben Dooksc4bb6ff2010-08-10 18:02:34 -070086 unsigned short vidtcon;
87 unsigned short wincon;
88 unsigned short winmap;
89 unsigned short keycon;
90 unsigned short buf_start;
91 unsigned short buf_end;
92 unsigned short buf_size;
93 unsigned short osd;
94 unsigned short osd_stride;
Ben Dooks50a55032010-08-10 18:02:33 -070095 unsigned short palette[S3C_FB_MAX_WIN];
Pawel Osciak067b2262010-08-10 18:02:38 -070096
97 unsigned int has_prtcon:1;
Ben Dooks50a55032010-08-10 18:02:33 -070098};
99
100/**
101 * struct s3c_fb_win_variant
102 * @has_osd_c: Set if has OSD C register.
103 * @has_osd_d: Set if has OSD D register.
104 * @palette_sz: Size of palette in entries.
105 * @palette_16bpp: Set if palette is 16bits wide.
106 * @valid_bpp: 1 bit per BPP setting to show valid bits-per-pixel.
107 *
108 * valid_bpp bit x is set if (x+1)BPP is supported.
109 */
110struct s3c_fb_win_variant {
111 unsigned int has_osd_c:1;
112 unsigned int has_osd_d:1;
113 unsigned int palette_16bpp:1;
114 unsigned short palette_sz;
115 u32 valid_bpp;
116};
117
118/**
119 * struct s3c_fb_driverdata - per-device type driver data for init time.
120 * @variant: The variant information for this driver.
121 * @win: The window information for each window.
122 */
123struct s3c_fb_driverdata {
124 struct s3c_fb_variant variant;
125 struct s3c_fb_win_variant *win[S3C_FB_MAX_WIN];
126};
127
Ben Dooksec549a02009-03-31 15:25:39 -0700128/**
Ben Dooksbc2da1b2010-08-10 18:02:34 -0700129 * struct s3c_fb_palette - palette information
130 * @r: Red bitfield.
131 * @g: Green bitfield.
132 * @b: Blue bitfield.
133 * @a: Alpha bitfield.
134 */
135struct s3c_fb_palette {
136 struct fb_bitfield r;
137 struct fb_bitfield g;
138 struct fb_bitfield b;
139 struct fb_bitfield a;
140};
141
142/**
Ben Dooksec549a02009-03-31 15:25:39 -0700143 * struct s3c_fb_win - per window private data for each framebuffer.
144 * @windata: The platform data supplied for the window configuration.
145 * @parent: The hardware that this window is part of.
146 * @fbinfo: Pointer pack to the framebuffer info for this window.
Ben Dooks50a55032010-08-10 18:02:33 -0700147 * @varint: The variant information for this window.
Ben Dooksec549a02009-03-31 15:25:39 -0700148 * @palette_buffer: Buffer/cache to hold palette entries.
149 * @pseudo_palette: For use in TRUECOLOUR modes for entries 0..15/
150 * @index: The window number of this window.
151 * @palette: The bitfields for changing r/g/b into a hardware palette entry.
152 */
153struct s3c_fb_win {
154 struct s3c_fb_pd_win *windata;
155 struct s3c_fb *parent;
156 struct fb_info *fbinfo;
157 struct s3c_fb_palette palette;
Ben Dooks50a55032010-08-10 18:02:33 -0700158 struct s3c_fb_win_variant variant;
Ben Dooksec549a02009-03-31 15:25:39 -0700159
160 u32 *palette_buffer;
161 u32 pseudo_palette[16];
162 unsigned int index;
163};
164
165/**
Pawel Osciakefdc8462010-08-10 18:02:38 -0700166 * struct s3c_fb_vsync - vsync information
167 * @wait: a queue for processes waiting for vsync
168 * @count: vsync interrupt count
169 */
170struct s3c_fb_vsync {
171 wait_queue_head_t wait;
172 unsigned int count;
173};
174
175/**
Ben Dooksec549a02009-03-31 15:25:39 -0700176 * struct s3c_fb - overall hardware state of the hardware
177 * @dev: The device that we bound to, for printing, etc.
178 * @regs_res: The resource we claimed for the IO registers.
179 * @bus_clk: The clk (hclk) feeding our interface and possibly pixclk.
180 * @regs: The mapped hardware registers.
Ben Dooks50a55032010-08-10 18:02:33 -0700181 * @variant: Variant information for this hardware.
Ben Dooksec549a02009-03-31 15:25:39 -0700182 * @enabled: A bitmask of enabled hardware windows.
183 * @pdata: The platform configuration data passed with the device.
184 * @windows: The hardware windows that have been claimed.
Pawel Osciakefdc8462010-08-10 18:02:38 -0700185 * @irq_no: IRQ line number
186 * @irq_flags: irq flags
187 * @vsync_info: VSYNC-related information (count, queues...)
Ben Dooksec549a02009-03-31 15:25:39 -0700188 */
189struct s3c_fb {
190 struct device *dev;
191 struct resource *regs_res;
192 struct clk *bus_clk;
193 void __iomem *regs;
Ben Dooks50a55032010-08-10 18:02:33 -0700194 struct s3c_fb_variant variant;
Ben Dooksec549a02009-03-31 15:25:39 -0700195
196 unsigned char enabled;
197
198 struct s3c_fb_platdata *pdata;
199 struct s3c_fb_win *windows[S3C_FB_MAX_WIN];
Pawel Osciakefdc8462010-08-10 18:02:38 -0700200
201 int irq_no;
202 unsigned long irq_flags;
203 struct s3c_fb_vsync vsync_info;
Ben Dooksec549a02009-03-31 15:25:39 -0700204};
205
206/**
Ben Dooks50a55032010-08-10 18:02:33 -0700207 * s3c_fb_validate_win_bpp - validate the bits-per-pixel for this mode.
208 * @win: The device window.
209 * @bpp: The bit depth.
Ben Dooksec549a02009-03-31 15:25:39 -0700210 */
Ben Dooks50a55032010-08-10 18:02:33 -0700211static bool s3c_fb_validate_win_bpp(struct s3c_fb_win *win, unsigned int bpp)
Ben Dooksec549a02009-03-31 15:25:39 -0700212{
Ben Dooks50a55032010-08-10 18:02:33 -0700213 return win->variant.valid_bpp & VALID_BPP(bpp);
Ben Dooksec549a02009-03-31 15:25:39 -0700214}
215
216/**
217 * s3c_fb_check_var() - framebuffer layer request to verify a given mode.
218 * @var: The screen information to verify.
219 * @info: The framebuffer device.
220 *
221 * Framebuffer layer call to verify the given information and allow us to
222 * update various information depending on the hardware capabilities.
223 */
224static int s3c_fb_check_var(struct fb_var_screeninfo *var,
225 struct fb_info *info)
226{
227 struct s3c_fb_win *win = info->par;
228 struct s3c_fb_pd_win *windata = win->windata;
229 struct s3c_fb *sfb = win->parent;
230
231 dev_dbg(sfb->dev, "checking parameters\n");
232
233 var->xres_virtual = max((unsigned int)windata->virtual_x, var->xres);
234 var->yres_virtual = max((unsigned int)windata->virtual_y, var->yres);
235
Ben Dooks50a55032010-08-10 18:02:33 -0700236 if (!s3c_fb_validate_win_bpp(win, var->bits_per_pixel)) {
Ben Dooksec549a02009-03-31 15:25:39 -0700237 dev_dbg(sfb->dev, "win %d: unsupported bpp %d\n",
238 win->index, var->bits_per_pixel);
239 return -EINVAL;
240 }
241
242 /* always ensure these are zero, for drop through cases below */
243 var->transp.offset = 0;
244 var->transp.length = 0;
245
246 switch (var->bits_per_pixel) {
247 case 1:
248 case 2:
249 case 4:
250 case 8:
Ben Dooks50a55032010-08-10 18:02:33 -0700251 if (sfb->variant.palette[win->index] != 0) {
Ben Dooksec549a02009-03-31 15:25:39 -0700252 /* non palletised, A:1,R:2,G:3,B:2 mode */
253 var->red.offset = 4;
254 var->green.offset = 2;
255 var->blue.offset = 0;
256 var->red.length = 5;
257 var->green.length = 3;
258 var->blue.length = 2;
259 var->transp.offset = 7;
260 var->transp.length = 1;
261 } else {
262 var->red.offset = 0;
263 var->red.length = var->bits_per_pixel;
264 var->green = var->red;
265 var->blue = var->red;
266 }
267 break;
268
269 case 19:
270 /* 666 with one bit alpha/transparency */
271 var->transp.offset = 18;
272 var->transp.length = 1;
273 case 18:
274 var->bits_per_pixel = 32;
275
276 /* 666 format */
277 var->red.offset = 12;
278 var->green.offset = 6;
279 var->blue.offset = 0;
280 var->red.length = 6;
281 var->green.length = 6;
282 var->blue.length = 6;
283 break;
284
285 case 16:
286 /* 16 bpp, 565 format */
287 var->red.offset = 11;
288 var->green.offset = 5;
289 var->blue.offset = 0;
290 var->red.length = 5;
291 var->green.length = 6;
292 var->blue.length = 5;
293 break;
294
295 case 28:
296 case 25:
297 var->transp.length = var->bits_per_pixel - 24;
298 var->transp.offset = 24;
299 /* drop through */
300 case 24:
301 /* our 24bpp is unpacked, so 32bpp */
302 var->bits_per_pixel = 32;
303 case 32:
304 var->red.offset = 16;
305 var->red.length = 8;
306 var->green.offset = 8;
307 var->green.length = 8;
308 var->blue.offset = 0;
309 var->blue.length = 8;
310 break;
311
312 default:
313 dev_err(sfb->dev, "invalid bpp\n");
314 }
315
316 dev_dbg(sfb->dev, "%s: verified parameters\n", __func__);
317 return 0;
318}
319
320/**
321 * s3c_fb_calc_pixclk() - calculate the divider to create the pixel clock.
322 * @sfb: The hardware state.
323 * @pixclock: The pixel clock wanted, in picoseconds.
324 *
325 * Given the specified pixel clock, work out the necessary divider to get
326 * close to the output frequency.
327 */
Mark Browneb29a5c2010-01-15 17:01:40 -0800328static int s3c_fb_calc_pixclk(struct s3c_fb *sfb, unsigned int pixclk)
Ben Dooksec549a02009-03-31 15:25:39 -0700329{
330 unsigned long clk = clk_get_rate(sfb->bus_clk);
Mark Browneb29a5c2010-01-15 17:01:40 -0800331 unsigned long long tmp;
Ben Dooksec549a02009-03-31 15:25:39 -0700332 unsigned int result;
333
Mark Browneb29a5c2010-01-15 17:01:40 -0800334 tmp = (unsigned long long)clk;
335 tmp *= pixclk;
336
337 do_div(tmp, 1000000000UL);
338 result = (unsigned int)tmp / 1000;
Ben Dooksec549a02009-03-31 15:25:39 -0700339
340 dev_dbg(sfb->dev, "pixclk=%u, clk=%lu, div=%d (%lu)\n",
341 pixclk, clk, result, clk / result);
342
343 return result;
344}
345
346/**
347 * s3c_fb_align_word() - align pixel count to word boundary
348 * @bpp: The number of bits per pixel
349 * @pix: The value to be aligned.
350 *
351 * Align the given pixel count so that it will start on an 32bit word
352 * boundary.
353 */
354static int s3c_fb_align_word(unsigned int bpp, unsigned int pix)
355{
356 int pix_per_word;
357
358 if (bpp > 16)
359 return pix;
360
361 pix_per_word = (8 * 32) / bpp;
362 return ALIGN(pix, pix_per_word);
363}
364
365/**
366 * s3c_fb_set_par() - framebuffer request to set new framebuffer state.
367 * @info: The framebuffer to change.
368 *
369 * Framebuffer layer request to set a new mode for the specified framebuffer
370 */
371static int s3c_fb_set_par(struct fb_info *info)
372{
373 struct fb_var_screeninfo *var = &info->var;
374 struct s3c_fb_win *win = info->par;
375 struct s3c_fb *sfb = win->parent;
376 void __iomem *regs = sfb->regs;
Ben Dooksc4bb6ff2010-08-10 18:02:34 -0700377 void __iomem *buf = regs;
Ben Dooksec549a02009-03-31 15:25:39 -0700378 int win_no = win->index;
InKi Dae600ce1a2009-07-05 12:08:21 -0700379 u32 osdc_data = 0;
Ben Dooksec549a02009-03-31 15:25:39 -0700380 u32 data;
381 u32 pagewidth;
382 int clkdiv;
383
384 dev_dbg(sfb->dev, "setting framebuffer parameters\n");
385
386 switch (var->bits_per_pixel) {
387 case 32:
388 case 24:
389 case 16:
390 case 12:
391 info->fix.visual = FB_VISUAL_TRUECOLOR;
392 break;
393 case 8:
Ben Dooks50a55032010-08-10 18:02:33 -0700394 if (win->variant.palette_sz >= 256)
Ben Dooksec549a02009-03-31 15:25:39 -0700395 info->fix.visual = FB_VISUAL_PSEUDOCOLOR;
396 else
397 info->fix.visual = FB_VISUAL_TRUECOLOR;
398 break;
399 case 1:
400 info->fix.visual = FB_VISUAL_MONO01;
401 break;
402 default:
403 info->fix.visual = FB_VISUAL_PSEUDOCOLOR;
404 break;
405 }
406
407 info->fix.line_length = (var->xres_virtual * var->bits_per_pixel) / 8;
408
Pawel Osciak067b2262010-08-10 18:02:38 -0700409 info->fix.xpanstep = info->var.xres_virtual > info->var.xres ? 1 : 0;
410 info->fix.ypanstep = info->var.yres_virtual > info->var.yres ? 1 : 0;
411
Ben Dooksec549a02009-03-31 15:25:39 -0700412 /* disable the window whilst we update it */
413 writel(0, regs + WINCON(win_no));
414
InKi Daead044902010-08-10 18:02:31 -0700415 /* use platform specified window as the basis for the lcd timings */
Ben Dooksec549a02009-03-31 15:25:39 -0700416
InKi Daead044902010-08-10 18:02:31 -0700417 if (win_no == sfb->pdata->default_win) {
Mark Browneb29a5c2010-01-15 17:01:40 -0800418 clkdiv = s3c_fb_calc_pixclk(sfb, var->pixclock);
Ben Dooksec549a02009-03-31 15:25:39 -0700419
420 data = sfb->pdata->vidcon0;
421 data &= ~(VIDCON0_CLKVAL_F_MASK | VIDCON0_CLKDIR);
422
423 if (clkdiv > 1)
424 data |= VIDCON0_CLKVAL_F(clkdiv-1) | VIDCON0_CLKDIR;
425 else
426 data &= ~VIDCON0_CLKDIR; /* 1:1 clock */
427
428 /* write the timing data to the panel */
429
Ben Dooksc4bb6ff2010-08-10 18:02:34 -0700430 if (sfb->variant.is_2443)
431 data |= (1 << 5);
432
Ben Dooksec549a02009-03-31 15:25:39 -0700433 data |= VIDCON0_ENVID | VIDCON0_ENVID_F;
434 writel(data, regs + VIDCON0);
435
436 data = VIDTCON0_VBPD(var->upper_margin - 1) |
437 VIDTCON0_VFPD(var->lower_margin - 1) |
438 VIDTCON0_VSPW(var->vsync_len - 1);
439
Ben Dooksc4bb6ff2010-08-10 18:02:34 -0700440 writel(data, regs + sfb->variant.vidtcon);
Ben Dooksec549a02009-03-31 15:25:39 -0700441
442 data = VIDTCON1_HBPD(var->left_margin - 1) |
443 VIDTCON1_HFPD(var->right_margin - 1) |
444 VIDTCON1_HSPW(var->hsync_len - 1);
445
Ben Dooksc4bb6ff2010-08-10 18:02:34 -0700446 /* VIDTCON1 */
447 writel(data, regs + sfb->variant.vidtcon + 4);
Ben Dooksec549a02009-03-31 15:25:39 -0700448
449 data = VIDTCON2_LINEVAL(var->yres - 1) |
450 VIDTCON2_HOZVAL(var->xres - 1);
Ben Dooksc4bb6ff2010-08-10 18:02:34 -0700451 writel(data, regs +sfb->variant.vidtcon + 8 );
Ben Dooksec549a02009-03-31 15:25:39 -0700452 }
453
454 /* write the buffer address */
455
Ben Dooksc4bb6ff2010-08-10 18:02:34 -0700456 /* start and end registers stride is 8 */
457 buf = regs + win_no * 8;
458
459 writel(info->fix.smem_start, buf + sfb->variant.buf_start);
Ben Dooksec549a02009-03-31 15:25:39 -0700460
461 data = info->fix.smem_start + info->fix.line_length * var->yres;
Ben Dooksc4bb6ff2010-08-10 18:02:34 -0700462 writel(data, buf + sfb->variant.buf_end);
Ben Dooksec549a02009-03-31 15:25:39 -0700463
464 pagewidth = (var->xres * var->bits_per_pixel) >> 3;
465 data = VIDW_BUF_SIZE_OFFSET(info->fix.line_length - pagewidth) |
466 VIDW_BUF_SIZE_PAGEWIDTH(pagewidth);
Ben Dooksc4bb6ff2010-08-10 18:02:34 -0700467 writel(data, regs + sfb->variant.buf_size + (win_no * 4));
Ben Dooksec549a02009-03-31 15:25:39 -0700468
469 /* write 'OSD' registers to control position of framebuffer */
470
471 data = VIDOSDxA_TOPLEFT_X(0) | VIDOSDxA_TOPLEFT_Y(0);
Ben Dooksc4bb6ff2010-08-10 18:02:34 -0700472 writel(data, regs + VIDOSD_A(win_no, sfb->variant));
Ben Dooksec549a02009-03-31 15:25:39 -0700473
474 data = VIDOSDxB_BOTRIGHT_X(s3c_fb_align_word(var->bits_per_pixel,
475 var->xres - 1)) |
476 VIDOSDxB_BOTRIGHT_Y(var->yres - 1);
477
Ben Dooksc4bb6ff2010-08-10 18:02:34 -0700478 writel(data, regs + VIDOSD_B(win_no, sfb->variant));
Ben Dooksec549a02009-03-31 15:25:39 -0700479
480 data = var->xres * var->yres;
InKi Dae39000d62009-06-16 15:34:27 -0700481
InKi Dae39000d62009-06-16 15:34:27 -0700482 osdc_data = VIDISD14C_ALPHA1_R(0xf) |
483 VIDISD14C_ALPHA1_G(0xf) |
484 VIDISD14C_ALPHA1_B(0xf);
485
Ben Dooks50a55032010-08-10 18:02:33 -0700486 if (win->variant.has_osd_d) {
Ben Dooksc4bb6ff2010-08-10 18:02:34 -0700487 writel(data, regs + VIDOSD_D(win_no, sfb->variant));
488 writel(osdc_data, regs + VIDOSD_C(win_no, sfb->variant));
Ben Dooksec549a02009-03-31 15:25:39 -0700489 } else
Ben Dooksc4bb6ff2010-08-10 18:02:34 -0700490 writel(data, regs + VIDOSD_C(win_no, sfb->variant));
Ben Dooksec549a02009-03-31 15:25:39 -0700491
492 data = WINCONx_ENWIN;
493
494 /* note, since we have to round up the bits-per-pixel, we end up
495 * relying on the bitfield information for r/g/b/a to work out
496 * exactly which mode of operation is intended. */
497
498 switch (var->bits_per_pixel) {
499 case 1:
500 data |= WINCON0_BPPMODE_1BPP;
501 data |= WINCONx_BITSWP;
502 data |= WINCONx_BURSTLEN_4WORD;
503 break;
504 case 2:
505 data |= WINCON0_BPPMODE_2BPP;
506 data |= WINCONx_BITSWP;
507 data |= WINCONx_BURSTLEN_8WORD;
508 break;
509 case 4:
510 data |= WINCON0_BPPMODE_4BPP;
511 data |= WINCONx_BITSWP;
512 data |= WINCONx_BURSTLEN_8WORD;
513 break;
514 case 8:
515 if (var->transp.length != 0)
516 data |= WINCON1_BPPMODE_8BPP_1232;
517 else
518 data |= WINCON0_BPPMODE_8BPP_PALETTE;
519 data |= WINCONx_BURSTLEN_8WORD;
520 data |= WINCONx_BYTSWP;
521 break;
522 case 16:
523 if (var->transp.length != 0)
524 data |= WINCON1_BPPMODE_16BPP_A1555;
525 else
526 data |= WINCON0_BPPMODE_16BPP_565;
527 data |= WINCONx_HAWSWP;
528 data |= WINCONx_BURSTLEN_16WORD;
529 break;
530 case 24:
531 case 32:
532 if (var->red.length == 6) {
533 if (var->transp.length != 0)
534 data |= WINCON1_BPPMODE_19BPP_A1666;
535 else
536 data |= WINCON1_BPPMODE_18BPP_666;
InKi Dae39000d62009-06-16 15:34:27 -0700537 } else if (var->transp.length == 1)
538 data |= WINCON1_BPPMODE_25BPP_A1888
539 | WINCON1_BLD_PIX;
540 else if (var->transp.length == 4)
541 data |= WINCON1_BPPMODE_28BPP_A4888
542 | WINCON1_BLD_PIX | WINCON1_ALPHA_SEL;
Ben Dooksec549a02009-03-31 15:25:39 -0700543 else
544 data |= WINCON0_BPPMODE_24BPP_888;
545
InKi Daedc8498c2010-08-10 18:02:32 -0700546 data |= WINCONx_WSWP;
Ben Dooksec549a02009-03-31 15:25:39 -0700547 data |= WINCONx_BURSTLEN_16WORD;
548 break;
549 }
550
Ben Dooksc4bb6ff2010-08-10 18:02:34 -0700551 /* Enable the colour keying for the window below this one */
InKi Dae39000d62009-06-16 15:34:27 -0700552 if (win_no > 0) {
553 u32 keycon0_data = 0, keycon1_data = 0;
Ben Dooksc4bb6ff2010-08-10 18:02:34 -0700554 void __iomem *keycon = regs + sfb->variant.keycon;
InKi Dae39000d62009-06-16 15:34:27 -0700555
556 keycon0_data = ~(WxKEYCON0_KEYBL_EN |
557 WxKEYCON0_KEYEN_F |
558 WxKEYCON0_DIRCON) | WxKEYCON0_COMPKEY(0);
559
560 keycon1_data = WxKEYCON1_COLVAL(0xffffff);
561
Ben Dooksc4bb6ff2010-08-10 18:02:34 -0700562 keycon += (win_no - 1) * 8;
563
564 writel(keycon0_data, keycon + WKEYCON0);
565 writel(keycon1_data, keycon + WKEYCON1);
InKi Dae39000d62009-06-16 15:34:27 -0700566 }
567
Ben Dooksc4bb6ff2010-08-10 18:02:34 -0700568 writel(data, regs + sfb->variant.wincon + (win_no * 4));
569 writel(0x0, regs + sfb->variant.winmap + (win_no * 4));
Ben Dooksec549a02009-03-31 15:25:39 -0700570
571 return 0;
572}
573
574/**
575 * s3c_fb_update_palette() - set or schedule a palette update.
576 * @sfb: The hardware information.
577 * @win: The window being updated.
578 * @reg: The palette index being changed.
579 * @value: The computed palette value.
580 *
581 * Change the value of a palette register, either by directly writing to
582 * the palette (this requires the palette RAM to be disconnected from the
583 * hardware whilst this is in progress) or schedule the update for later.
584 *
585 * At the moment, since we have no VSYNC interrupt support, we simply set
586 * the palette entry directly.
587 */
588static void s3c_fb_update_palette(struct s3c_fb *sfb,
589 struct s3c_fb_win *win,
590 unsigned int reg,
591 u32 value)
592{
593 void __iomem *palreg;
594 u32 palcon;
595
Ben Dooks50a55032010-08-10 18:02:33 -0700596 palreg = sfb->regs + sfb->variant.palette[win->index];
Ben Dooksec549a02009-03-31 15:25:39 -0700597
598 dev_dbg(sfb->dev, "%s: win %d, reg %d (%p): %08x\n",
599 __func__, win->index, reg, palreg, value);
600
601 win->palette_buffer[reg] = value;
602
603 palcon = readl(sfb->regs + WPALCON);
604 writel(palcon | WPALCON_PAL_UPDATE, sfb->regs + WPALCON);
605
Ben Dooks50a55032010-08-10 18:02:33 -0700606 if (win->variant.palette_16bpp)
607 writew(value, palreg + (reg * 2));
Ben Dooksec549a02009-03-31 15:25:39 -0700608 else
Ben Dooks50a55032010-08-10 18:02:33 -0700609 writel(value, palreg + (reg * 4));
Ben Dooksec549a02009-03-31 15:25:39 -0700610
611 writel(palcon, sfb->regs + WPALCON);
612}
613
614static inline unsigned int chan_to_field(unsigned int chan,
615 struct fb_bitfield *bf)
616{
617 chan &= 0xffff;
618 chan >>= 16 - bf->length;
619 return chan << bf->offset;
620}
621
622/**
623 * s3c_fb_setcolreg() - framebuffer layer request to change palette.
624 * @regno: The palette index to change.
625 * @red: The red field for the palette data.
626 * @green: The green field for the palette data.
627 * @blue: The blue field for the palette data.
628 * @trans: The transparency (alpha) field for the palette data.
629 * @info: The framebuffer being changed.
630 */
631static int s3c_fb_setcolreg(unsigned regno,
632 unsigned red, unsigned green, unsigned blue,
633 unsigned transp, struct fb_info *info)
634{
635 struct s3c_fb_win *win = info->par;
636 struct s3c_fb *sfb = win->parent;
637 unsigned int val;
638
639 dev_dbg(sfb->dev, "%s: win %d: %d => rgb=%d/%d/%d\n",
640 __func__, win->index, regno, red, green, blue);
641
642 switch (info->fix.visual) {
643 case FB_VISUAL_TRUECOLOR:
644 /* true-colour, use pseudo-palette */
645
646 if (regno < 16) {
647 u32 *pal = info->pseudo_palette;
648
649 val = chan_to_field(red, &info->var.red);
650 val |= chan_to_field(green, &info->var.green);
651 val |= chan_to_field(blue, &info->var.blue);
652
653 pal[regno] = val;
654 }
655 break;
656
657 case FB_VISUAL_PSEUDOCOLOR:
Ben Dooks50a55032010-08-10 18:02:33 -0700658 if (regno < win->variant.palette_sz) {
Ben Dooksec549a02009-03-31 15:25:39 -0700659 val = chan_to_field(red, &win->palette.r);
660 val |= chan_to_field(green, &win->palette.g);
661 val |= chan_to_field(blue, &win->palette.b);
662
663 s3c_fb_update_palette(sfb, win, regno, val);
664 }
665
666 break;
667
668 default:
669 return 1; /* unknown type */
670 }
671
672 return 0;
673}
674
675/**
676 * s3c_fb_enable() - Set the state of the main LCD output
677 * @sfb: The main framebuffer state.
678 * @enable: The state to set.
679 */
680static void s3c_fb_enable(struct s3c_fb *sfb, int enable)
681{
682 u32 vidcon0 = readl(sfb->regs + VIDCON0);
683
684 if (enable)
685 vidcon0 |= VIDCON0_ENVID | VIDCON0_ENVID_F;
686 else {
687 /* see the note in the framebuffer datasheet about
688 * why you cannot take both of these bits down at the
689 * same time. */
690
691 if (!(vidcon0 & VIDCON0_ENVID))
692 return;
693
694 vidcon0 |= VIDCON0_ENVID;
695 vidcon0 &= ~VIDCON0_ENVID_F;
696 }
697
698 writel(vidcon0, sfb->regs + VIDCON0);
699}
700
701/**
702 * s3c_fb_blank() - blank or unblank the given window
703 * @blank_mode: The blank state from FB_BLANK_*
704 * @info: The framebuffer to blank.
705 *
706 * Framebuffer layer request to change the power state.
707 */
708static int s3c_fb_blank(int blank_mode, struct fb_info *info)
709{
710 struct s3c_fb_win *win = info->par;
711 struct s3c_fb *sfb = win->parent;
712 unsigned int index = win->index;
713 u32 wincon;
714
715 dev_dbg(sfb->dev, "blank mode %d\n", blank_mode);
716
Ben Dooksc4bb6ff2010-08-10 18:02:34 -0700717 wincon = readl(sfb->regs + sfb->variant.wincon + (index * 4));
Ben Dooksec549a02009-03-31 15:25:39 -0700718
719 switch (blank_mode) {
720 case FB_BLANK_POWERDOWN:
721 wincon &= ~WINCONx_ENWIN;
722 sfb->enabled &= ~(1 << index);
723 /* fall through to FB_BLANK_NORMAL */
724
725 case FB_BLANK_NORMAL:
726 /* disable the DMA and display 0x0 (black) */
727 writel(WINxMAP_MAP | WINxMAP_MAP_COLOUR(0x0),
Ben Dooksc4bb6ff2010-08-10 18:02:34 -0700728 sfb->regs + sfb->variant.winmap + (index * 4));
Ben Dooksec549a02009-03-31 15:25:39 -0700729 break;
730
731 case FB_BLANK_UNBLANK:
Ben Dooksc4bb6ff2010-08-10 18:02:34 -0700732 writel(0x0, sfb->regs + sfb->variant.winmap + (index * 4));
Ben Dooksec549a02009-03-31 15:25:39 -0700733 wincon |= WINCONx_ENWIN;
734 sfb->enabled |= (1 << index);
735 break;
736
737 case FB_BLANK_VSYNC_SUSPEND:
738 case FB_BLANK_HSYNC_SUSPEND:
739 default:
740 return 1;
741 }
742
Ben Dooksc4bb6ff2010-08-10 18:02:34 -0700743 writel(wincon, sfb->regs + sfb->variant.wincon + (index * 4));
Ben Dooksec549a02009-03-31 15:25:39 -0700744
745 /* Check the enabled state to see if we need to be running the
746 * main LCD interface, as if there are no active windows then
747 * it is highly likely that we also do not need to output
748 * anything.
749 */
750
751 /* We could do something like the following code, but the current
752 * system of using framebuffer events means that we cannot make
753 * the distinction between just window 0 being inactive and all
754 * the windows being down.
755 *
756 * s3c_fb_enable(sfb, sfb->enabled ? 1 : 0);
757 */
758
759 /* we're stuck with this until we can do something about overriding
760 * the power control using the blanking event for a single fb.
761 */
InKi Daead044902010-08-10 18:02:31 -0700762 if (index == sfb->pdata->default_win)
Ben Dooksec549a02009-03-31 15:25:39 -0700763 s3c_fb_enable(sfb, blank_mode != FB_BLANK_POWERDOWN ? 1 : 0);
764
765 return 0;
766}
767
Pawel Osciak067b2262010-08-10 18:02:38 -0700768/**
769 * s3c_fb_pan_display() - Pan the display.
770 *
771 * Note that the offsets can be written to the device at any time, as their
772 * values are latched at each vsync automatically. This also means that only
773 * the last call to this function will have any effect on next vsync, but
774 * there is no need to sleep waiting for it to prevent tearing.
775 *
776 * @var: The screen information to verify.
777 * @info: The framebuffer device.
778 */
779static int s3c_fb_pan_display(struct fb_var_screeninfo *var,
780 struct fb_info *info)
781{
782 struct s3c_fb_win *win = info->par;
783 struct s3c_fb *sfb = win->parent;
784 void __iomem *buf = sfb->regs + win->index * 8;
785 unsigned int start_boff, end_boff;
786
787 /* Offset in bytes to the start of the displayed area */
788 start_boff = var->yoffset * info->fix.line_length;
789 /* X offset depends on the current bpp */
790 if (info->var.bits_per_pixel >= 8) {
791 start_boff += var->xoffset * (info->var.bits_per_pixel >> 3);
792 } else {
793 switch (info->var.bits_per_pixel) {
794 case 4:
795 start_boff += var->xoffset >> 1;
796 break;
797 case 2:
798 start_boff += var->xoffset >> 2;
799 break;
800 case 1:
801 start_boff += var->xoffset >> 3;
802 break;
803 default:
804 dev_err(sfb->dev, "invalid bpp\n");
805 return -EINVAL;
806 }
807 }
808 /* Offset in bytes to the end of the displayed area */
809 end_boff = start_boff + var->yres * info->fix.line_length;
810
811 /* Temporarily turn off per-vsync update from shadow registers until
812 * both start and end addresses are updated to prevent corruption */
813 if (sfb->variant.has_prtcon)
814 writel(PRTCON_PROTECT, sfb->regs + PRTCON);
815
816 writel(info->fix.smem_start + start_boff, buf + sfb->variant.buf_start);
817 writel(info->fix.smem_start + end_boff, buf + sfb->variant.buf_end);
818
819 if (sfb->variant.has_prtcon)
820 writel(0, sfb->regs + PRTCON);
821
822 return 0;
823}
824
Pawel Osciakefdc8462010-08-10 18:02:38 -0700825/**
826 * s3c_fb_enable_irq() - enable framebuffer interrupts
827 * @sfb: main hardware state
828 */
829static void s3c_fb_enable_irq(struct s3c_fb *sfb)
830{
831 void __iomem *regs = sfb->regs;
832 u32 irq_ctrl_reg;
833
834 if (!test_and_set_bit(S3C_FB_VSYNC_IRQ_EN, &sfb->irq_flags)) {
835 /* IRQ disabled, enable it */
836 irq_ctrl_reg = readl(regs + VIDINTCON0);
837
838 irq_ctrl_reg |= VIDINTCON0_INT_ENABLE;
839 irq_ctrl_reg |= VIDINTCON0_INT_FRAME;
840
841 irq_ctrl_reg &= ~VIDINTCON0_FRAMESEL0_MASK;
842 irq_ctrl_reg |= VIDINTCON0_FRAMESEL0_VSYNC;
843 irq_ctrl_reg &= ~VIDINTCON0_FRAMESEL1_MASK;
844 irq_ctrl_reg |= VIDINTCON0_FRAMESEL1_NONE;
845
846 writel(irq_ctrl_reg, regs + VIDINTCON0);
847 }
848}
849
850/**
851 * s3c_fb_disable_irq() - disable framebuffer interrupts
852 * @sfb: main hardware state
853 */
854static void s3c_fb_disable_irq(struct s3c_fb *sfb)
855{
856 void __iomem *regs = sfb->regs;
857 u32 irq_ctrl_reg;
858
859 if (test_and_clear_bit(S3C_FB_VSYNC_IRQ_EN, &sfb->irq_flags)) {
860 /* IRQ enabled, disable it */
861 irq_ctrl_reg = readl(regs + VIDINTCON0);
862
863 irq_ctrl_reg &= ~VIDINTCON0_INT_FRAME;
864 irq_ctrl_reg &= ~VIDINTCON0_INT_ENABLE;
865
866 writel(irq_ctrl_reg, regs + VIDINTCON0);
867 }
868}
869
870static irqreturn_t s3c_fb_irq(int irq, void *dev_id)
871{
872 struct s3c_fb *sfb = dev_id;
873 void __iomem *regs = sfb->regs;
874 u32 irq_sts_reg;
875
876 irq_sts_reg = readl(regs + VIDINTCON1);
877
878 if (irq_sts_reg & VIDINTCON1_INT_FRAME) {
879
880 /* VSYNC interrupt, accept it */
881 writel(VIDINTCON1_INT_FRAME, regs + VIDINTCON1);
882
883 sfb->vsync_info.count++;
884 wake_up_interruptible(&sfb->vsync_info.wait);
885 }
886
887 /* We only support waiting for VSYNC for now, so it's safe
888 * to always disable irqs here.
889 */
890 s3c_fb_disable_irq(sfb);
891
892 return IRQ_HANDLED;
893}
894
895/**
896 * s3c_fb_wait_for_vsync() - sleep until next VSYNC interrupt or timeout
897 * @sfb: main hardware state
898 * @crtc: head index.
899 */
900static int s3c_fb_wait_for_vsync(struct s3c_fb *sfb, u32 crtc)
901{
902 unsigned long count;
903 int ret;
904
905 if (crtc != 0)
906 return -ENODEV;
907
908 count = sfb->vsync_info.count;
909 s3c_fb_enable_irq(sfb);
910 ret = wait_event_interruptible_timeout(sfb->vsync_info.wait,
911 count != sfb->vsync_info.count,
912 msecs_to_jiffies(VSYNC_TIMEOUT_MSEC));
913 if (ret == 0)
914 return -ETIMEDOUT;
915
916 return 0;
917}
918
919static int s3c_fb_ioctl(struct fb_info *info, unsigned int cmd,
920 unsigned long arg)
921{
922 struct s3c_fb_win *win = info->par;
923 struct s3c_fb *sfb = win->parent;
924 int ret;
925 u32 crtc;
926
927 switch (cmd) {
928 case FBIO_WAITFORVSYNC:
929 if (get_user(crtc, (u32 __user *)arg)) {
930 ret = -EFAULT;
931 break;
932 }
933
934 ret = s3c_fb_wait_for_vsync(sfb, crtc);
935 break;
936 default:
937 ret = -ENOTTY;
938 }
939
940 return ret;
941}
942
Ben Dooksec549a02009-03-31 15:25:39 -0700943static struct fb_ops s3c_fb_ops = {
944 .owner = THIS_MODULE,
945 .fb_check_var = s3c_fb_check_var,
946 .fb_set_par = s3c_fb_set_par,
947 .fb_blank = s3c_fb_blank,
948 .fb_setcolreg = s3c_fb_setcolreg,
949 .fb_fillrect = cfb_fillrect,
950 .fb_copyarea = cfb_copyarea,
951 .fb_imageblit = cfb_imageblit,
Pawel Osciak067b2262010-08-10 18:02:38 -0700952 .fb_pan_display = s3c_fb_pan_display,
Pawel Osciakefdc8462010-08-10 18:02:38 -0700953 .fb_ioctl = s3c_fb_ioctl,
Ben Dooksec549a02009-03-31 15:25:39 -0700954};
955
956/**
957 * s3c_fb_alloc_memory() - allocate display memory for framebuffer window
958 * @sfb: The base resources for the hardware.
959 * @win: The window to initialise memory for.
960 *
961 * Allocate memory for the given framebuffer.
962 */
963static int __devinit s3c_fb_alloc_memory(struct s3c_fb *sfb,
964 struct s3c_fb_win *win)
965{
966 struct s3c_fb_pd_win *windata = win->windata;
967 unsigned int real_size, virt_size, size;
968 struct fb_info *fbi = win->fbinfo;
969 dma_addr_t map_dma;
970
971 dev_dbg(sfb->dev, "allocating memory for display\n");
972
973 real_size = windata->win_mode.xres * windata->win_mode.yres;
974 virt_size = windata->virtual_x * windata->virtual_y;
975
976 dev_dbg(sfb->dev, "real_size=%u (%u.%u), virt_size=%u (%u.%u)\n",
977 real_size, windata->win_mode.xres, windata->win_mode.yres,
978 virt_size, windata->virtual_x, windata->virtual_y);
979
980 size = (real_size > virt_size) ? real_size : virt_size;
981 size *= (windata->max_bpp > 16) ? 32 : windata->max_bpp;
982 size /= 8;
983
984 fbi->fix.smem_len = size;
985 size = PAGE_ALIGN(size);
986
987 dev_dbg(sfb->dev, "want %u bytes for window\n", size);
988
989 fbi->screen_base = dma_alloc_writecombine(sfb->dev, size,
990 &map_dma, GFP_KERNEL);
991 if (!fbi->screen_base)
992 return -ENOMEM;
993
994 dev_dbg(sfb->dev, "mapped %x to %p\n",
995 (unsigned int)map_dma, fbi->screen_base);
996
997 memset(fbi->screen_base, 0x0, size);
998 fbi->fix.smem_start = map_dma;
999
1000 return 0;
1001}
1002
1003/**
1004 * s3c_fb_free_memory() - free the display memory for the given window
1005 * @sfb: The base resources for the hardware.
1006 * @win: The window to free the display memory for.
1007 *
1008 * Free the display memory allocated by s3c_fb_alloc_memory().
1009 */
1010static void s3c_fb_free_memory(struct s3c_fb *sfb, struct s3c_fb_win *win)
1011{
1012 struct fb_info *fbi = win->fbinfo;
1013
Pawel Osciakcd7d7e02010-08-10 18:02:35 -07001014 if (fbi->screen_base)
1015 dma_free_writecombine(sfb->dev, PAGE_ALIGN(fbi->fix.smem_len),
Ben Dooksec549a02009-03-31 15:25:39 -07001016 fbi->screen_base, fbi->fix.smem_start);
1017}
1018
1019/**
1020 * s3c_fb_release_win() - release resources for a framebuffer window.
1021 * @win: The window to cleanup the resources for.
1022 *
1023 * Release the resources that where claimed for the hardware window,
1024 * such as the framebuffer instance and any memory claimed for it.
1025 */
1026static void s3c_fb_release_win(struct s3c_fb *sfb, struct s3c_fb_win *win)
1027{
Krzysztof Heltddc518d2009-06-16 15:34:33 -07001028 if (win->fbinfo) {
1029 unregister_framebuffer(win->fbinfo);
Pawel Osciakcd7d7e02010-08-10 18:02:35 -07001030 if (win->fbinfo->cmap.len)
1031 fb_dealloc_cmap(&win->fbinfo->cmap);
Krzysztof Heltddc518d2009-06-16 15:34:33 -07001032 s3c_fb_free_memory(sfb, win);
1033 framebuffer_release(win->fbinfo);
1034 }
Ben Dooksec549a02009-03-31 15:25:39 -07001035}
1036
1037/**
1038 * s3c_fb_probe_win() - register an hardware window
1039 * @sfb: The base resources for the hardware
Ben Dooks50a55032010-08-10 18:02:33 -07001040 * @variant: The variant information for this window.
Ben Dooksec549a02009-03-31 15:25:39 -07001041 * @res: Pointer to where to place the resultant window.
1042 *
1043 * Allocate and do the basic initialisation for one of the hardware's graphics
1044 * windows.
1045 */
1046static int __devinit s3c_fb_probe_win(struct s3c_fb *sfb, unsigned int win_no,
Ben Dooks50a55032010-08-10 18:02:33 -07001047 struct s3c_fb_win_variant *variant,
Ben Dooksec549a02009-03-31 15:25:39 -07001048 struct s3c_fb_win **res)
1049{
1050 struct fb_var_screeninfo *var;
1051 struct fb_videomode *initmode;
1052 struct s3c_fb_pd_win *windata;
1053 struct s3c_fb_win *win;
1054 struct fb_info *fbinfo;
1055 int palette_size;
1056 int ret;
1057
Ben Dooksc4bb6ff2010-08-10 18:02:34 -07001058 dev_dbg(sfb->dev, "probing window %d, variant %p\n", win_no, variant);
Ben Dooksec549a02009-03-31 15:25:39 -07001059
Pawel Osciakefdc8462010-08-10 18:02:38 -07001060 init_waitqueue_head(&sfb->vsync_info.wait);
1061
Ben Dooks50a55032010-08-10 18:02:33 -07001062 palette_size = variant->palette_sz * 4;
Ben Dooksec549a02009-03-31 15:25:39 -07001063
1064 fbinfo = framebuffer_alloc(sizeof(struct s3c_fb_win) +
1065 palette_size * sizeof(u32), sfb->dev);
1066 if (!fbinfo) {
1067 dev_err(sfb->dev, "failed to allocate framebuffer\n");
1068 return -ENOENT;
1069 }
1070
1071 windata = sfb->pdata->win[win_no];
1072 initmode = &windata->win_mode;
1073
1074 WARN_ON(windata->max_bpp == 0);
1075 WARN_ON(windata->win_mode.xres == 0);
1076 WARN_ON(windata->win_mode.yres == 0);
1077
1078 win = fbinfo->par;
Pawel Osciakcd7d7e02010-08-10 18:02:35 -07001079 *res = win;
Ben Dooksec549a02009-03-31 15:25:39 -07001080 var = &fbinfo->var;
Ben Dooks50a55032010-08-10 18:02:33 -07001081 win->variant = *variant;
Ben Dooksec549a02009-03-31 15:25:39 -07001082 win->fbinfo = fbinfo;
1083 win->parent = sfb;
1084 win->windata = windata;
1085 win->index = win_no;
1086 win->palette_buffer = (u32 *)(win + 1);
1087
1088 ret = s3c_fb_alloc_memory(sfb, win);
1089 if (ret) {
1090 dev_err(sfb->dev, "failed to allocate display memory\n");
Krzysztof Heltddc518d2009-06-16 15:34:33 -07001091 return ret;
Ben Dooksec549a02009-03-31 15:25:39 -07001092 }
1093
1094 /* setup the r/b/g positions for the window's palette */
Ben Dooksbc2da1b2010-08-10 18:02:34 -07001095 if (win->variant.palette_16bpp) {
1096 /* Set RGB 5:6:5 as default */
1097 win->palette.r.offset = 11;
1098 win->palette.r.length = 5;
1099 win->palette.g.offset = 5;
1100 win->palette.g.length = 6;
1101 win->palette.b.offset = 0;
1102 win->palette.b.length = 5;
1103
1104 } else {
1105 /* Set 8bpp or 8bpp and 1bit alpha */
1106 win->palette.r.offset = 16;
1107 win->palette.r.length = 8;
1108 win->palette.g.offset = 8;
1109 win->palette.g.length = 8;
1110 win->palette.b.offset = 0;
1111 win->palette.b.length = 8;
1112 }
Ben Dooksec549a02009-03-31 15:25:39 -07001113
1114 /* setup the initial video mode from the window */
1115 fb_videomode_to_var(&fbinfo->var, initmode);
1116
1117 fbinfo->fix.type = FB_TYPE_PACKED_PIXELS;
1118 fbinfo->fix.accel = FB_ACCEL_NONE;
1119 fbinfo->var.activate = FB_ACTIVATE_NOW;
1120 fbinfo->var.vmode = FB_VMODE_NONINTERLACED;
1121 fbinfo->var.bits_per_pixel = windata->default_bpp;
1122 fbinfo->fbops = &s3c_fb_ops;
1123 fbinfo->flags = FBINFO_FLAG_DEFAULT;
1124 fbinfo->pseudo_palette = &win->pseudo_palette;
1125
1126 /* prepare to actually start the framebuffer */
1127
1128 ret = s3c_fb_check_var(&fbinfo->var, fbinfo);
1129 if (ret < 0) {
1130 dev_err(sfb->dev, "check_var failed on initial video params\n");
Krzysztof Heltddc518d2009-06-16 15:34:33 -07001131 return ret;
Ben Dooksec549a02009-03-31 15:25:39 -07001132 }
1133
1134 /* create initial colour map */
1135
Ben Dooks50a55032010-08-10 18:02:33 -07001136 ret = fb_alloc_cmap(&fbinfo->cmap, win->variant.palette_sz, 1);
Ben Dooksec549a02009-03-31 15:25:39 -07001137 if (ret == 0)
1138 fb_set_cmap(&fbinfo->cmap, fbinfo);
1139 else
1140 dev_err(sfb->dev, "failed to allocate fb cmap\n");
1141
1142 s3c_fb_set_par(fbinfo);
1143
1144 dev_dbg(sfb->dev, "about to register framebuffer\n");
1145
1146 /* run the check_var and set_par on our configuration. */
1147
1148 ret = register_framebuffer(fbinfo);
1149 if (ret < 0) {
1150 dev_err(sfb->dev, "failed to register framebuffer\n");
Krzysztof Heltddc518d2009-06-16 15:34:33 -07001151 return ret;
Ben Dooksec549a02009-03-31 15:25:39 -07001152 }
1153
Ben Dooksec549a02009-03-31 15:25:39 -07001154 dev_info(sfb->dev, "window %d: fb %s\n", win_no, fbinfo->fix.id);
1155
1156 return 0;
Ben Dooksec549a02009-03-31 15:25:39 -07001157}
1158
1159/**
1160 * s3c_fb_clear_win() - clear hardware window registers.
1161 * @sfb: The base resources for the hardware.
1162 * @win: The window to process.
1163 *
1164 * Reset the specific window registers to a known state.
1165 */
1166static void s3c_fb_clear_win(struct s3c_fb *sfb, int win)
1167{
1168 void __iomem *regs = sfb->regs;
1169
Ben Dooksc4bb6ff2010-08-10 18:02:34 -07001170 writel(0, regs + sfb->variant.wincon + (win * 4));
1171 writel(0, regs + VIDOSD_A(win, sfb->variant));
1172 writel(0, regs + VIDOSD_B(win, sfb->variant));
1173 writel(0, regs + VIDOSD_C(win, sfb->variant));
Ben Dooksec549a02009-03-31 15:25:39 -07001174}
1175
1176static int __devinit s3c_fb_probe(struct platform_device *pdev)
1177{
Ben Dooks50a55032010-08-10 18:02:33 -07001178 struct s3c_fb_driverdata *fbdrv;
Ben Dooksec549a02009-03-31 15:25:39 -07001179 struct device *dev = &pdev->dev;
1180 struct s3c_fb_platdata *pd;
1181 struct s3c_fb *sfb;
1182 struct resource *res;
1183 int win;
1184 int ret = 0;
1185
Ben Dooks50a55032010-08-10 18:02:33 -07001186 fbdrv = (struct s3c_fb_driverdata *)platform_get_device_id(pdev)->driver_data;
1187
1188 if (fbdrv->variant.nr_windows > S3C_FB_MAX_WIN) {
1189 dev_err(dev, "too many windows, cannot attach\n");
1190 return -EINVAL;
1191 }
1192
Ben Dooksec549a02009-03-31 15:25:39 -07001193 pd = pdev->dev.platform_data;
1194 if (!pd) {
1195 dev_err(dev, "no platform data specified\n");
1196 return -EINVAL;
1197 }
1198
1199 sfb = kzalloc(sizeof(struct s3c_fb), GFP_KERNEL);
1200 if (!sfb) {
1201 dev_err(dev, "no memory for framebuffers\n");
1202 return -ENOMEM;
1203 }
1204
Ben Dooksc4bb6ff2010-08-10 18:02:34 -07001205 dev_dbg(dev, "allocate new framebuffer %p\n", sfb);
1206
Ben Dooksec549a02009-03-31 15:25:39 -07001207 sfb->dev = dev;
1208 sfb->pdata = pd;
Ben Dooks50a55032010-08-10 18:02:33 -07001209 sfb->variant = fbdrv->variant;
Ben Dooksec549a02009-03-31 15:25:39 -07001210
1211 sfb->bus_clk = clk_get(dev, "lcd");
1212 if (IS_ERR(sfb->bus_clk)) {
1213 dev_err(dev, "failed to get bus clock\n");
1214 goto err_sfb;
1215 }
1216
1217 clk_enable(sfb->bus_clk);
1218
1219 res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
1220 if (!res) {
1221 dev_err(dev, "failed to find registers\n");
1222 ret = -ENOENT;
1223 goto err_clk;
1224 }
1225
1226 sfb->regs_res = request_mem_region(res->start, resource_size(res),
1227 dev_name(dev));
1228 if (!sfb->regs_res) {
1229 dev_err(dev, "failed to claim register region\n");
1230 ret = -ENOENT;
1231 goto err_clk;
1232 }
1233
1234 sfb->regs = ioremap(res->start, resource_size(res));
1235 if (!sfb->regs) {
1236 dev_err(dev, "failed to map registers\n");
1237 ret = -ENXIO;
1238 goto err_req_region;
1239 }
1240
Pawel Osciakefdc8462010-08-10 18:02:38 -07001241 res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
1242 if (!res) {
1243 dev_err(dev, "failed to acquire irq resource\n");
1244 ret = -ENOENT;
1245 goto err_ioremap;
1246 }
1247 sfb->irq_no = res->start;
1248 ret = request_irq(sfb->irq_no, s3c_fb_irq,
1249 0, "s3c_fb", sfb);
1250 if (ret) {
1251 dev_err(dev, "irq request failed\n");
1252 goto err_ioremap;
1253 }
1254
Ben Dooksec549a02009-03-31 15:25:39 -07001255 dev_dbg(dev, "got resources (regs %p), probing windows\n", sfb->regs);
1256
1257 /* setup gpio and output polarity controls */
1258
1259 pd->setup_gpio();
1260
1261 writel(pd->vidcon1, sfb->regs + VIDCON1);
1262
1263 /* zero all windows before we do anything */
1264
Ben Dooks50a55032010-08-10 18:02:33 -07001265 for (win = 0; win < fbdrv->variant.nr_windows; win++)
Ben Dooksec549a02009-03-31 15:25:39 -07001266 s3c_fb_clear_win(sfb, win);
1267
Ben Dooks94947032010-08-10 18:02:32 -07001268 /* initialise colour key controls */
Ben Dooks50a55032010-08-10 18:02:33 -07001269 for (win = 0; win < (fbdrv->variant.nr_windows - 1); win++) {
Ben Dooksc4bb6ff2010-08-10 18:02:34 -07001270 void __iomem *regs = sfb->regs + sfb->variant.keycon;
1271
1272 regs += (win * 8);
1273 writel(0xffffff, regs + WKEYCON0);
1274 writel(0xffffff, regs + WKEYCON1);
Ben Dooks94947032010-08-10 18:02:32 -07001275 }
1276
Ben Dooksec549a02009-03-31 15:25:39 -07001277 /* we have the register setup, start allocating framebuffers */
1278
Ben Dooks50a55032010-08-10 18:02:33 -07001279 for (win = 0; win < fbdrv->variant.nr_windows; win++) {
Ben Dooksec549a02009-03-31 15:25:39 -07001280 if (!pd->win[win])
1281 continue;
1282
Ben Dooks50a55032010-08-10 18:02:33 -07001283 ret = s3c_fb_probe_win(sfb, win, fbdrv->win[win],
1284 &sfb->windows[win]);
Ben Dooksec549a02009-03-31 15:25:39 -07001285 if (ret < 0) {
1286 dev_err(dev, "failed to create window %d\n", win);
1287 for (; win >= 0; win--)
1288 s3c_fb_release_win(sfb, sfb->windows[win]);
Pawel Osciakefdc8462010-08-10 18:02:38 -07001289 goto err_irq;
Ben Dooksec549a02009-03-31 15:25:39 -07001290 }
1291 }
1292
1293 platform_set_drvdata(pdev, sfb);
1294
1295 return 0;
1296
Pawel Osciakefdc8462010-08-10 18:02:38 -07001297err_irq:
1298 free_irq(sfb->irq_no, sfb);
1299
Ben Dooksec549a02009-03-31 15:25:39 -07001300err_ioremap:
1301 iounmap(sfb->regs);
1302
1303err_req_region:
1304 release_resource(sfb->regs_res);
1305 kfree(sfb->regs_res);
1306
1307err_clk:
1308 clk_disable(sfb->bus_clk);
1309 clk_put(sfb->bus_clk);
1310
1311err_sfb:
1312 kfree(sfb);
1313 return ret;
1314}
1315
1316/**
1317 * s3c_fb_remove() - Cleanup on module finalisation
1318 * @pdev: The platform device we are bound to.
1319 *
1320 * Shutdown and then release all the resources that the driver allocated
1321 * on initialisation.
1322 */
1323static int __devexit s3c_fb_remove(struct platform_device *pdev)
1324{
1325 struct s3c_fb *sfb = platform_get_drvdata(pdev);
1326 int win;
1327
Pawel Osciakc42b1102009-07-29 15:02:10 -07001328 for (win = 0; win < S3C_FB_MAX_WIN; win++)
Marek Szyprowski17663e52009-05-28 14:34:35 -07001329 if (sfb->windows[win])
1330 s3c_fb_release_win(sfb, sfb->windows[win]);
Ben Dooksec549a02009-03-31 15:25:39 -07001331
Pawel Osciakefdc8462010-08-10 18:02:38 -07001332 free_irq(sfb->irq_no, sfb);
1333
Ben Dooksec549a02009-03-31 15:25:39 -07001334 iounmap(sfb->regs);
1335
1336 clk_disable(sfb->bus_clk);
1337 clk_put(sfb->bus_clk);
1338
1339 release_resource(sfb->regs_res);
1340 kfree(sfb->regs_res);
1341
1342 kfree(sfb);
1343
1344 return 0;
1345}
1346
1347#ifdef CONFIG_PM
1348static int s3c_fb_suspend(struct platform_device *pdev, pm_message_t state)
1349{
1350 struct s3c_fb *sfb = platform_get_drvdata(pdev);
1351 struct s3c_fb_win *win;
1352 int win_no;
1353
Pawel Osciakc42b1102009-07-29 15:02:10 -07001354 for (win_no = S3C_FB_MAX_WIN - 1; win_no >= 0; win_no--) {
Ben Dooksec549a02009-03-31 15:25:39 -07001355 win = sfb->windows[win_no];
1356 if (!win)
1357 continue;
1358
1359 /* use the blank function to push into power-down */
1360 s3c_fb_blank(FB_BLANK_POWERDOWN, win->fbinfo);
1361 }
1362
1363 clk_disable(sfb->bus_clk);
1364 return 0;
1365}
1366
1367static int s3c_fb_resume(struct platform_device *pdev)
1368{
1369 struct s3c_fb *sfb = platform_get_drvdata(pdev);
Marek Szyprowski17663e52009-05-28 14:34:35 -07001370 struct s3c_fb_platdata *pd = sfb->pdata;
Ben Dooksec549a02009-03-31 15:25:39 -07001371 struct s3c_fb_win *win;
1372 int win_no;
1373
1374 clk_enable(sfb->bus_clk);
1375
Marek Szyprowski17663e52009-05-28 14:34:35 -07001376 /* setup registers */
1377 writel(pd->vidcon1, sfb->regs + VIDCON1);
1378
1379 /* zero all windows before we do anything */
Ben Dooks50a55032010-08-10 18:02:33 -07001380 for (win_no = 0; win_no < sfb->variant.nr_windows; win_no++)
Marek Szyprowski17663e52009-05-28 14:34:35 -07001381 s3c_fb_clear_win(sfb, win_no);
1382
Ben Dooks50a55032010-08-10 18:02:33 -07001383 for (win_no = 0; win_no < sfb->variant.nr_windows - 1; win_no++) {
Ben Dooksc4bb6ff2010-08-10 18:02:34 -07001384 void __iomem *regs = sfb->regs + sfb->variant.keycon;
1385
1386 regs += (win_no * 8);
1387 writel(0xffffff, regs + WKEYCON0);
1388 writel(0xffffff, regs + WKEYCON1);
Ben Dooks94947032010-08-10 18:02:32 -07001389 }
1390
Marek Szyprowski17663e52009-05-28 14:34:35 -07001391 /* restore framebuffers */
Ben Dooksec549a02009-03-31 15:25:39 -07001392 for (win_no = 0; win_no < S3C_FB_MAX_WIN; win_no++) {
1393 win = sfb->windows[win_no];
1394 if (!win)
1395 continue;
1396
1397 dev_dbg(&pdev->dev, "resuming window %d\n", win_no);
1398 s3c_fb_set_par(win->fbinfo);
1399 }
1400
1401 return 0;
1402}
1403#else
1404#define s3c_fb_suspend NULL
1405#define s3c_fb_resume NULL
1406#endif
1407
Ben Dooks50a55032010-08-10 18:02:33 -07001408
1409#define VALID_BPP124 (VALID_BPP(1) | VALID_BPP(2) | VALID_BPP(4))
1410#define VALID_BPP1248 (VALID_BPP124 | VALID_BPP(8))
1411
1412static struct s3c_fb_win_variant s3c_fb_data_64xx_wins[] __devinitdata = {
1413 [0] = {
1414 .has_osd_c = 1,
1415 .palette_sz = 256,
1416 .valid_bpp = VALID_BPP1248 | VALID_BPP(16) | VALID_BPP(24),
1417 },
1418 [1] = {
1419 .has_osd_c = 1,
1420 .has_osd_d = 1,
1421 .palette_sz = 256,
1422 .valid_bpp = (VALID_BPP1248 | VALID_BPP(16) |
1423 VALID_BPP(18) | VALID_BPP(19) |
1424 VALID_BPP(24) | VALID_BPP(25)),
1425 },
1426 [2] = {
1427 .has_osd_c = 1,
1428 .has_osd_d = 1,
1429 .palette_sz = 16,
1430 .palette_16bpp = 1,
1431 .valid_bpp = (VALID_BPP1248 | VALID_BPP(16) |
1432 VALID_BPP(18) | VALID_BPP(19) |
1433 VALID_BPP(24) | VALID_BPP(25)),
1434 },
1435 [3] = {
1436 .has_osd_c = 1,
1437 .has_osd_d = 1,
1438 .palette_sz = 16,
1439 .palette_16bpp = 1,
1440 .valid_bpp = (VALID_BPP124 | VALID_BPP(16) |
1441 VALID_BPP(18) | VALID_BPP(19) |
1442 VALID_BPP(24) | VALID_BPP(25)),
1443 },
1444 [4] = {
1445 .has_osd_c = 1,
1446 .palette_sz = 4,
1447 .palette_16bpp = 1,
1448 .valid_bpp = (VALID_BPP(1) | VALID_BPP(2) |
1449 VALID_BPP(16) | VALID_BPP(18) |
1450 VALID_BPP(24) | VALID_BPP(25)),
1451 },
1452};
1453
1454static struct s3c_fb_driverdata s3c_fb_data_64xx __devinitdata = {
1455 .variant = {
1456 .nr_windows = 5,
Ben Dooksc4bb6ff2010-08-10 18:02:34 -07001457 .vidtcon = VIDTCON0,
1458 .wincon = WINCON(0),
1459 .winmap = WINxMAP(0),
1460 .keycon = WKEYCON,
1461 .osd = VIDOSD_BASE,
1462 .osd_stride = 16,
1463 .buf_start = VIDW_BUF_START(0),
1464 .buf_size = VIDW_BUF_SIZE(0),
1465 .buf_end = VIDW_BUF_END(0),
Ben Dooks50a55032010-08-10 18:02:33 -07001466
1467 .palette = {
1468 [0] = 0x400,
1469 [1] = 0x800,
1470 [2] = 0x300,
1471 [3] = 0x320,
1472 [4] = 0x340,
1473 },
Pawel Osciak067b2262010-08-10 18:02:38 -07001474
1475 .has_prtcon = 1,
Ben Dooks50a55032010-08-10 18:02:33 -07001476 },
1477 .win[0] = &s3c_fb_data_64xx_wins[0],
1478 .win[1] = &s3c_fb_data_64xx_wins[1],
1479 .win[2] = &s3c_fb_data_64xx_wins[2],
1480 .win[3] = &s3c_fb_data_64xx_wins[3],
1481 .win[4] = &s3c_fb_data_64xx_wins[4],
1482};
1483
Pawel Osciak4e591ac2010-08-10 18:02:36 -07001484static struct s3c_fb_driverdata s3c_fb_data_s5pc100 __devinitdata = {
1485 .variant = {
1486 .nr_windows = 5,
1487 .vidtcon = VIDTCON0,
1488 .wincon = WINCON(0),
1489 .winmap = WINxMAP(0),
1490 .keycon = WKEYCON,
1491 .osd = VIDOSD_BASE,
1492 .osd_stride = 16,
1493 .buf_start = VIDW_BUF_START(0),
1494 .buf_size = VIDW_BUF_SIZE(0),
1495 .buf_end = VIDW_BUF_END(0),
1496
1497 .palette = {
1498 [0] = 0x2400,
1499 [1] = 0x2800,
1500 [2] = 0x2c00,
1501 [3] = 0x3000,
1502 [4] = 0x3400,
1503 },
Pawel Osciak067b2262010-08-10 18:02:38 -07001504
1505 .has_prtcon = 1,
Pawel Osciak4e591ac2010-08-10 18:02:36 -07001506 },
1507 .win[0] = &s3c_fb_data_64xx_wins[0],
1508 .win[1] = &s3c_fb_data_64xx_wins[1],
1509 .win[2] = &s3c_fb_data_64xx_wins[2],
1510 .win[3] = &s3c_fb_data_64xx_wins[3],
1511 .win[4] = &s3c_fb_data_64xx_wins[4],
1512};
1513
1514static struct s3c_fb_driverdata s3c_fb_data_s5pv210 __devinitdata = {
Ben Dooks50a55032010-08-10 18:02:33 -07001515 .variant = {
1516 .nr_windows = 5,
Ben Dooksc4bb6ff2010-08-10 18:02:34 -07001517 .vidtcon = VIDTCON0,
1518 .wincon = WINCON(0),
1519 .winmap = WINxMAP(0),
1520 .keycon = WKEYCON,
1521 .osd = VIDOSD_BASE,
1522 .osd_stride = 16,
1523 .buf_start = VIDW_BUF_START(0),
1524 .buf_size = VIDW_BUF_SIZE(0),
1525 .buf_end = VIDW_BUF_END(0),
Ben Dooks50a55032010-08-10 18:02:33 -07001526
1527 .palette = {
1528 [0] = 0x2400,
1529 [1] = 0x2800,
1530 [2] = 0x2c00,
1531 [3] = 0x3000,
1532 [4] = 0x3400,
1533 },
1534 },
1535 .win[0] = &s3c_fb_data_64xx_wins[0],
1536 .win[1] = &s3c_fb_data_64xx_wins[1],
1537 .win[2] = &s3c_fb_data_64xx_wins[2],
1538 .win[3] = &s3c_fb_data_64xx_wins[3],
1539 .win[4] = &s3c_fb_data_64xx_wins[4],
1540};
1541
Ben Dooksc4bb6ff2010-08-10 18:02:34 -07001542/* S3C2443/S3C2416 style hardware */
1543static struct s3c_fb_driverdata s3c_fb_data_s3c2443 __devinitdata = {
1544 .variant = {
1545 .nr_windows = 2,
1546 .is_2443 = 1,
1547
1548 .vidtcon = 0x08,
1549 .wincon = 0x14,
1550 .winmap = 0xd0,
1551 .keycon = 0xb0,
1552 .osd = 0x28,
1553 .osd_stride = 12,
1554 .buf_start = 0x64,
1555 .buf_size = 0x94,
1556 .buf_end = 0x7c,
1557
1558 .palette = {
1559 [0] = 0x400,
1560 [1] = 0x800,
1561 },
1562 },
1563 .win[0] = &(struct s3c_fb_win_variant) {
1564 .palette_sz = 256,
1565 .valid_bpp = VALID_BPP1248 | VALID_BPP(16) | VALID_BPP(24),
1566 },
1567 .win[1] = &(struct s3c_fb_win_variant) {
1568 .has_osd_c = 1,
1569 .palette_sz = 256,
1570 .valid_bpp = (VALID_BPP1248 | VALID_BPP(16) |
1571 VALID_BPP(18) | VALID_BPP(19) |
1572 VALID_BPP(24) | VALID_BPP(25) |
1573 VALID_BPP(28)),
1574 },
1575};
1576
Ben Dooks50a55032010-08-10 18:02:33 -07001577static struct platform_device_id s3c_fb_driver_ids[] = {
1578 {
1579 .name = "s3c-fb",
1580 .driver_data = (unsigned long)&s3c_fb_data_64xx,
1581 }, {
Pawel Osciak4e591ac2010-08-10 18:02:36 -07001582 .name = "s5pc100-fb",
1583 .driver_data = (unsigned long)&s3c_fb_data_s5pc100,
1584 }, {
1585 .name = "s5pv210-fb",
1586 .driver_data = (unsigned long)&s3c_fb_data_s5pv210,
Ben Dooksc4bb6ff2010-08-10 18:02:34 -07001587 }, {
1588 .name = "s3c2443-fb",
1589 .driver_data = (unsigned long)&s3c_fb_data_s3c2443,
Ben Dooks50a55032010-08-10 18:02:33 -07001590 },
1591 {},
1592};
1593MODULE_DEVICE_TABLE(platform, s3c_fb_driver_ids);
1594
Ben Dooksec549a02009-03-31 15:25:39 -07001595static struct platform_driver s3c_fb_driver = {
1596 .probe = s3c_fb_probe,
Peter Korsgaard3163eaba2009-09-22 16:47:55 -07001597 .remove = __devexit_p(s3c_fb_remove),
Ben Dooksec549a02009-03-31 15:25:39 -07001598 .suspend = s3c_fb_suspend,
1599 .resume = s3c_fb_resume,
Ben Dooks50a55032010-08-10 18:02:33 -07001600 .id_table = s3c_fb_driver_ids,
Ben Dooksec549a02009-03-31 15:25:39 -07001601 .driver = {
1602 .name = "s3c-fb",
1603 .owner = THIS_MODULE,
1604 },
1605};
1606
1607static int __init s3c_fb_init(void)
1608{
1609 return platform_driver_register(&s3c_fb_driver);
1610}
1611
1612static void __exit s3c_fb_cleanup(void)
1613{
1614 platform_driver_unregister(&s3c_fb_driver);
1615}
1616
1617module_init(s3c_fb_init);
1618module_exit(s3c_fb_cleanup);
1619
1620MODULE_AUTHOR("Ben Dooks <ben@simtec.co.uk>");
1621MODULE_DESCRIPTION("Samsung S3C SoC Framebuffer driver");
1622MODULE_LICENSE("GPL");
1623MODULE_ALIAS("platform:s3c-fb");