| Nicolas Ferre | 1434058 | 2007-05-10 22:23:26 -0700 | [diff] [blame] | 1 | /* | 
|  | 2 | *  Driver for AT91/AT32 LCD Controller | 
|  | 3 | * | 
|  | 4 | *  Copyright (C) 2007 Atmel Corporation | 
|  | 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/kernel.h> | 
|  | 12 | #include <linux/platform_device.h> | 
|  | 13 | #include <linux/dma-mapping.h> | 
|  | 14 | #include <linux/interrupt.h> | 
|  | 15 | #include <linux/clk.h> | 
|  | 16 | #include <linux/fb.h> | 
|  | 17 | #include <linux/init.h> | 
|  | 18 | #include <linux/delay.h> | 
| David Brownell | a9a84c3 | 2008-02-06 01:39:26 -0800 | [diff] [blame] | 19 | #include <linux/backlight.h> | 
| Nicolas Ferre | 1434058 | 2007-05-10 22:23:26 -0700 | [diff] [blame] | 20 |  | 
| Russell King | a09e64f | 2008-08-05 16:14:15 +0100 | [diff] [blame] | 21 | #include <mach/board.h> | 
|  | 22 | #include <mach/cpu.h> | 
|  | 23 | #include <mach/gpio.h> | 
| Nicolas Ferre | 1434058 | 2007-05-10 22:23:26 -0700 | [diff] [blame] | 24 |  | 
|  | 25 | #include <video/atmel_lcdc.h> | 
|  | 26 |  | 
|  | 27 | #define lcdc_readl(sinfo, reg)		__raw_readl((sinfo)->mmio+(reg)) | 
|  | 28 | #define lcdc_writel(sinfo, reg, val)	__raw_writel((val), (sinfo)->mmio+(reg)) | 
|  | 29 |  | 
|  | 30 | /* configurable parameters */ | 
|  | 31 | #define ATMEL_LCDC_CVAL_DEFAULT		0xc8 | 
|  | 32 | #define ATMEL_LCDC_DMA_BURST_LEN	8 | 
|  | 33 |  | 
| Nicolas Ferre | 5fb2d92 | 2008-04-28 02:15:21 -0700 | [diff] [blame] | 34 | #if defined(CONFIG_ARCH_AT91SAM9263) || defined(CONFIG_ARCH_AT91CAP9) || \ | 
|  | 35 | defined(CONFIG_ARCH_AT91SAM9RL) | 
| Nicolas Ferre | 1434058 | 2007-05-10 22:23:26 -0700 | [diff] [blame] | 36 | #define ATMEL_LCDC_FIFO_SIZE		2048 | 
|  | 37 | #else | 
|  | 38 | #define ATMEL_LCDC_FIFO_SIZE		512 | 
|  | 39 | #endif | 
|  | 40 |  | 
|  | 41 | #if defined(CONFIG_ARCH_AT91) | 
| Haavard Skinnemoen | e730d8b0a | 2008-08-12 15:08:56 -0700 | [diff] [blame] | 42 | #define	ATMEL_LCDFB_FBINFO_DEFAULT	(FBINFO_DEFAULT \ | 
|  | 43 | | FBINFO_PARTIAL_PAN_OK \ | 
|  | 44 | | FBINFO_HWACCEL_YPAN) | 
| Nicolas Ferre | 1434058 | 2007-05-10 22:23:26 -0700 | [diff] [blame] | 45 |  | 
|  | 46 | static inline void atmel_lcdfb_update_dma2d(struct atmel_lcdfb_info *sinfo, | 
|  | 47 | struct fb_var_screeninfo *var) | 
|  | 48 | { | 
|  | 49 |  | 
|  | 50 | } | 
|  | 51 | #elif defined(CONFIG_AVR32) | 
|  | 52 | #define	ATMEL_LCDFB_FBINFO_DEFAULT	(FBINFO_DEFAULT \ | 
|  | 53 | | FBINFO_PARTIAL_PAN_OK \ | 
|  | 54 | | FBINFO_HWACCEL_XPAN \ | 
|  | 55 | | FBINFO_HWACCEL_YPAN) | 
|  | 56 |  | 
|  | 57 | static void atmel_lcdfb_update_dma2d(struct atmel_lcdfb_info *sinfo, | 
|  | 58 | struct fb_var_screeninfo *var) | 
|  | 59 | { | 
|  | 60 | u32 dma2dcfg; | 
|  | 61 | u32 pixeloff; | 
|  | 62 |  | 
|  | 63 | pixeloff = (var->xoffset * var->bits_per_pixel) & 0x1f; | 
|  | 64 |  | 
|  | 65 | dma2dcfg = ((var->xres_virtual - var->xres) * var->bits_per_pixel) / 8; | 
|  | 66 | dma2dcfg |= pixeloff << ATMEL_LCDC_PIXELOFF_OFFSET; | 
|  | 67 | lcdc_writel(sinfo, ATMEL_LCDC_DMA2DCFG, dma2dcfg); | 
|  | 68 |  | 
|  | 69 | /* Update configuration */ | 
|  | 70 | lcdc_writel(sinfo, ATMEL_LCDC_DMACON, | 
|  | 71 | lcdc_readl(sinfo, ATMEL_LCDC_DMACON) | 
|  | 72 | | ATMEL_LCDC_DMAUPDT); | 
|  | 73 | } | 
|  | 74 | #endif | 
|  | 75 |  | 
| David Brownell | a9a84c3 | 2008-02-06 01:39:26 -0800 | [diff] [blame] | 76 | static const u32 contrast_ctr = ATMEL_LCDC_PS_DIV8 | 
|  | 77 | | ATMEL_LCDC_POL_POSITIVE | 
|  | 78 | | ATMEL_LCDC_ENA_PWMENABLE; | 
|  | 79 |  | 
|  | 80 | #ifdef CONFIG_BACKLIGHT_ATMEL_LCDC | 
|  | 81 |  | 
|  | 82 | /* some bl->props field just changed */ | 
|  | 83 | static int atmel_bl_update_status(struct backlight_device *bl) | 
|  | 84 | { | 
|  | 85 | struct atmel_lcdfb_info *sinfo = bl_get_data(bl); | 
|  | 86 | int			power = sinfo->bl_power; | 
|  | 87 | int			brightness = bl->props.brightness; | 
|  | 88 |  | 
|  | 89 | /* REVISIT there may be a meaningful difference between | 
|  | 90 | * fb_blank and power ... there seem to be some cases | 
|  | 91 | * this doesn't handle correctly. | 
|  | 92 | */ | 
|  | 93 | if (bl->props.fb_blank != sinfo->bl_power) | 
|  | 94 | power = bl->props.fb_blank; | 
|  | 95 | else if (bl->props.power != sinfo->bl_power) | 
|  | 96 | power = bl->props.power; | 
|  | 97 |  | 
|  | 98 | if (brightness < 0 && power == FB_BLANK_UNBLANK) | 
|  | 99 | brightness = lcdc_readl(sinfo, ATMEL_LCDC_CONTRAST_VAL); | 
|  | 100 | else if (power != FB_BLANK_UNBLANK) | 
|  | 101 | brightness = 0; | 
|  | 102 |  | 
|  | 103 | lcdc_writel(sinfo, ATMEL_LCDC_CONTRAST_VAL, brightness); | 
|  | 104 | lcdc_writel(sinfo, ATMEL_LCDC_CONTRAST_CTR, | 
|  | 105 | brightness ? contrast_ctr : 0); | 
|  | 106 |  | 
|  | 107 | bl->props.fb_blank = bl->props.power = sinfo->bl_power = power; | 
|  | 108 |  | 
|  | 109 | return 0; | 
|  | 110 | } | 
|  | 111 |  | 
|  | 112 | static int atmel_bl_get_brightness(struct backlight_device *bl) | 
|  | 113 | { | 
|  | 114 | struct atmel_lcdfb_info *sinfo = bl_get_data(bl); | 
|  | 115 |  | 
|  | 116 | return lcdc_readl(sinfo, ATMEL_LCDC_CONTRAST_VAL); | 
|  | 117 | } | 
|  | 118 |  | 
|  | 119 | static struct backlight_ops atmel_lcdc_bl_ops = { | 
|  | 120 | .update_status = atmel_bl_update_status, | 
|  | 121 | .get_brightness = atmel_bl_get_brightness, | 
|  | 122 | }; | 
|  | 123 |  | 
|  | 124 | static void init_backlight(struct atmel_lcdfb_info *sinfo) | 
|  | 125 | { | 
|  | 126 | struct backlight_device	*bl; | 
|  | 127 |  | 
|  | 128 | sinfo->bl_power = FB_BLANK_UNBLANK; | 
|  | 129 |  | 
|  | 130 | if (sinfo->backlight) | 
|  | 131 | return; | 
|  | 132 |  | 
|  | 133 | bl = backlight_device_register("backlight", &sinfo->pdev->dev, | 
|  | 134 | sinfo, &atmel_lcdc_bl_ops); | 
|  | 135 | if (IS_ERR(sinfo->backlight)) { | 
|  | 136 | dev_err(&sinfo->pdev->dev, "error %ld on backlight register\n", | 
|  | 137 | PTR_ERR(bl)); | 
|  | 138 | return; | 
|  | 139 | } | 
|  | 140 | sinfo->backlight = bl; | 
|  | 141 |  | 
|  | 142 | bl->props.power = FB_BLANK_UNBLANK; | 
|  | 143 | bl->props.fb_blank = FB_BLANK_UNBLANK; | 
|  | 144 | bl->props.max_brightness = 0xff; | 
|  | 145 | bl->props.brightness = atmel_bl_get_brightness(bl); | 
|  | 146 | } | 
|  | 147 |  | 
|  | 148 | static void exit_backlight(struct atmel_lcdfb_info *sinfo) | 
|  | 149 | { | 
|  | 150 | if (sinfo->backlight) | 
|  | 151 | backlight_device_unregister(sinfo->backlight); | 
|  | 152 | } | 
|  | 153 |  | 
|  | 154 | #else | 
|  | 155 |  | 
|  | 156 | static void init_backlight(struct atmel_lcdfb_info *sinfo) | 
|  | 157 | { | 
|  | 158 | dev_warn(&sinfo->pdev->dev, "backlight control is not available\n"); | 
|  | 159 | } | 
|  | 160 |  | 
|  | 161 | static void exit_backlight(struct atmel_lcdfb_info *sinfo) | 
|  | 162 | { | 
|  | 163 | } | 
|  | 164 |  | 
|  | 165 | #endif | 
|  | 166 |  | 
|  | 167 | static void init_contrast(struct atmel_lcdfb_info *sinfo) | 
|  | 168 | { | 
|  | 169 | /* have some default contrast/backlight settings */ | 
|  | 170 | lcdc_writel(sinfo, ATMEL_LCDC_CONTRAST_CTR, contrast_ctr); | 
|  | 171 | lcdc_writel(sinfo, ATMEL_LCDC_CONTRAST_VAL, ATMEL_LCDC_CVAL_DEFAULT); | 
|  | 172 |  | 
|  | 173 | if (sinfo->lcdcon_is_backlight) | 
|  | 174 | init_backlight(sinfo); | 
|  | 175 | } | 
|  | 176 |  | 
| Nicolas Ferre | 1434058 | 2007-05-10 22:23:26 -0700 | [diff] [blame] | 177 |  | 
|  | 178 | static struct fb_fix_screeninfo atmel_lcdfb_fix __initdata = { | 
|  | 179 | .type		= FB_TYPE_PACKED_PIXELS, | 
|  | 180 | .visual		= FB_VISUAL_TRUECOLOR, | 
|  | 181 | .xpanstep	= 0, | 
| Haavard Skinnemoen | e730d8b0a | 2008-08-12 15:08:56 -0700 | [diff] [blame] | 182 | .ypanstep	= 1, | 
| Nicolas Ferre | 1434058 | 2007-05-10 22:23:26 -0700 | [diff] [blame] | 183 | .ywrapstep	= 0, | 
|  | 184 | .accel		= FB_ACCEL_NONE, | 
|  | 185 | }; | 
|  | 186 |  | 
| Nicolas Ferre | 250a269 | 2007-07-21 04:37:59 -0700 | [diff] [blame] | 187 | static unsigned long compute_hozval(unsigned long xres, unsigned long lcdcon2) | 
|  | 188 | { | 
|  | 189 | unsigned long value; | 
|  | 190 |  | 
|  | 191 | if (!(cpu_is_at91sam9261() || cpu_is_at32ap7000())) | 
|  | 192 | return xres; | 
|  | 193 |  | 
|  | 194 | value = xres; | 
|  | 195 | if ((lcdcon2 & ATMEL_LCDC_DISTYPE) != ATMEL_LCDC_DISTYPE_TFT) { | 
|  | 196 | /* STN display */ | 
|  | 197 | if ((lcdcon2 & ATMEL_LCDC_DISTYPE) == ATMEL_LCDC_DISTYPE_STNCOLOR) { | 
|  | 198 | value *= 3; | 
|  | 199 | } | 
|  | 200 | if ( (lcdcon2 & ATMEL_LCDC_IFWIDTH) == ATMEL_LCDC_IFWIDTH_4 | 
|  | 201 | || ( (lcdcon2 & ATMEL_LCDC_IFWIDTH) == ATMEL_LCDC_IFWIDTH_8 | 
|  | 202 | && (lcdcon2 & ATMEL_LCDC_SCANMOD) == ATMEL_LCDC_SCANMOD_DUAL )) | 
|  | 203 | value = DIV_ROUND_UP(value, 4); | 
|  | 204 | else | 
|  | 205 | value = DIV_ROUND_UP(value, 8); | 
|  | 206 | } | 
|  | 207 |  | 
|  | 208 | return value; | 
|  | 209 | } | 
| Nicolas Ferre | 1434058 | 2007-05-10 22:23:26 -0700 | [diff] [blame] | 210 |  | 
| Haavard Skinnemoen | 3aa04f1 | 2008-09-13 02:33:23 -0700 | [diff] [blame] | 211 | static void atmel_lcdfb_stop_nowait(struct atmel_lcdfb_info *sinfo) | 
|  | 212 | { | 
|  | 213 | /* Turn off the LCD controller and the DMA controller */ | 
|  | 214 | lcdc_writel(sinfo, ATMEL_LCDC_PWRCON, | 
|  | 215 | sinfo->guard_time << ATMEL_LCDC_GUARDT_OFFSET); | 
|  | 216 |  | 
|  | 217 | /* Wait for the LCDC core to become idle */ | 
|  | 218 | while (lcdc_readl(sinfo, ATMEL_LCDC_PWRCON) & ATMEL_LCDC_BUSY) | 
|  | 219 | msleep(10); | 
|  | 220 |  | 
|  | 221 | lcdc_writel(sinfo, ATMEL_LCDC_DMACON, 0); | 
|  | 222 | } | 
|  | 223 |  | 
|  | 224 | static void atmel_lcdfb_stop(struct atmel_lcdfb_info *sinfo) | 
|  | 225 | { | 
|  | 226 | atmel_lcdfb_stop_nowait(sinfo); | 
|  | 227 |  | 
|  | 228 | /* Wait for DMA engine to become idle... */ | 
|  | 229 | while (lcdc_readl(sinfo, ATMEL_LCDC_DMACON) & ATMEL_LCDC_DMABUSY) | 
|  | 230 | msleep(10); | 
|  | 231 | } | 
|  | 232 |  | 
|  | 233 | static void atmel_lcdfb_start(struct atmel_lcdfb_info *sinfo) | 
|  | 234 | { | 
|  | 235 | lcdc_writel(sinfo, ATMEL_LCDC_DMACON, sinfo->default_dmacon); | 
|  | 236 | lcdc_writel(sinfo, ATMEL_LCDC_PWRCON, | 
|  | 237 | (sinfo->guard_time << ATMEL_LCDC_GUARDT_OFFSET) | 
|  | 238 | | ATMEL_LCDC_PWR); | 
|  | 239 | } | 
|  | 240 |  | 
| Nicolas Ferre | 1434058 | 2007-05-10 22:23:26 -0700 | [diff] [blame] | 241 | static void atmel_lcdfb_update_dma(struct fb_info *info, | 
|  | 242 | struct fb_var_screeninfo *var) | 
|  | 243 | { | 
|  | 244 | struct atmel_lcdfb_info *sinfo = info->par; | 
|  | 245 | struct fb_fix_screeninfo *fix = &info->fix; | 
|  | 246 | unsigned long dma_addr; | 
|  | 247 |  | 
|  | 248 | dma_addr = (fix->smem_start + var->yoffset * fix->line_length | 
|  | 249 | + var->xoffset * var->bits_per_pixel / 8); | 
|  | 250 |  | 
|  | 251 | dma_addr &= ~3UL; | 
|  | 252 |  | 
|  | 253 | /* Set framebuffer DMA base address and pixel offset */ | 
|  | 254 | lcdc_writel(sinfo, ATMEL_LCDC_DMABADDR1, dma_addr); | 
|  | 255 |  | 
|  | 256 | atmel_lcdfb_update_dma2d(sinfo, var); | 
|  | 257 | } | 
|  | 258 |  | 
|  | 259 | static inline void atmel_lcdfb_free_video_memory(struct atmel_lcdfb_info *sinfo) | 
|  | 260 | { | 
|  | 261 | struct fb_info *info = sinfo->info; | 
|  | 262 |  | 
|  | 263 | dma_free_writecombine(info->device, info->fix.smem_len, | 
|  | 264 | info->screen_base, info->fix.smem_start); | 
|  | 265 | } | 
|  | 266 |  | 
|  | 267 | /** | 
|  | 268 | *	atmel_lcdfb_alloc_video_memory - Allocate framebuffer memory | 
|  | 269 | *	@sinfo: the frame buffer to allocate memory for | 
|  | 270 | */ | 
|  | 271 | static int atmel_lcdfb_alloc_video_memory(struct atmel_lcdfb_info *sinfo) | 
|  | 272 | { | 
|  | 273 | struct fb_info *info = sinfo->info; | 
|  | 274 | struct fb_var_screeninfo *var = &info->var; | 
| Haavard Skinnemoen | ea757ac | 2008-08-12 15:08:57 -0700 | [diff] [blame] | 275 | unsigned int smem_len; | 
| Nicolas Ferre | 1434058 | 2007-05-10 22:23:26 -0700 | [diff] [blame] | 276 |  | 
| Haavard Skinnemoen | ea757ac | 2008-08-12 15:08:57 -0700 | [diff] [blame] | 277 | smem_len = (var->xres_virtual * var->yres_virtual | 
|  | 278 | * ((var->bits_per_pixel + 7) / 8)); | 
|  | 279 | info->fix.smem_len = max(smem_len, sinfo->smem_len); | 
| Nicolas Ferre | 1434058 | 2007-05-10 22:23:26 -0700 | [diff] [blame] | 280 |  | 
|  | 281 | info->screen_base = dma_alloc_writecombine(info->device, info->fix.smem_len, | 
|  | 282 | (dma_addr_t *)&info->fix.smem_start, GFP_KERNEL); | 
|  | 283 |  | 
|  | 284 | if (!info->screen_base) { | 
|  | 285 | return -ENOMEM; | 
|  | 286 | } | 
|  | 287 |  | 
| Haavard Skinnemoen | 01d3a5e | 2008-04-28 02:15:19 -0700 | [diff] [blame] | 288 | memset(info->screen_base, 0, info->fix.smem_len); | 
|  | 289 |  | 
| Nicolas Ferre | 1434058 | 2007-05-10 22:23:26 -0700 | [diff] [blame] | 290 | return 0; | 
|  | 291 | } | 
|  | 292 |  | 
| Nicolas Ferre | 968910b | 2008-07-23 21:31:34 -0700 | [diff] [blame] | 293 | static const struct fb_videomode *atmel_lcdfb_choose_mode(struct fb_var_screeninfo *var, | 
|  | 294 | struct fb_info *info) | 
|  | 295 | { | 
|  | 296 | struct fb_videomode varfbmode; | 
|  | 297 | const struct fb_videomode *fbmode = NULL; | 
|  | 298 |  | 
|  | 299 | fb_var_to_videomode(&varfbmode, var); | 
|  | 300 | fbmode = fb_find_nearest_mode(&varfbmode, &info->modelist); | 
|  | 301 | if (fbmode) | 
|  | 302 | fb_videomode_to_var(var, fbmode); | 
|  | 303 | return fbmode; | 
|  | 304 | } | 
|  | 305 |  | 
|  | 306 |  | 
| Nicolas Ferre | 1434058 | 2007-05-10 22:23:26 -0700 | [diff] [blame] | 307 | /** | 
|  | 308 | *      atmel_lcdfb_check_var - Validates a var passed in. | 
|  | 309 | *      @var: frame buffer variable screen structure | 
|  | 310 | *      @info: frame buffer structure that represents a single frame buffer | 
|  | 311 | * | 
|  | 312 | *	Checks to see if the hardware supports the state requested by | 
|  | 313 | *	var passed in. This function does not alter the hardware | 
|  | 314 | *	state!!!  This means the data stored in struct fb_info and | 
|  | 315 | *	struct atmel_lcdfb_info do not change. This includes the var | 
|  | 316 | *	inside of struct fb_info.  Do NOT change these. This function | 
|  | 317 | *	can be called on its own if we intent to only test a mode and | 
|  | 318 | *	not actually set it. The stuff in modedb.c is a example of | 
|  | 319 | *	this. If the var passed in is slightly off by what the | 
|  | 320 | *	hardware can support then we alter the var PASSED in to what | 
|  | 321 | *	we can do. If the hardware doesn't support mode change a | 
|  | 322 | *	-EINVAL will be returned by the upper layers. You don't need | 
|  | 323 | *	to implement this function then. If you hardware doesn't | 
|  | 324 | *	support changing the resolution then this function is not | 
|  | 325 | *	needed. In this case the driver would just provide a var that | 
|  | 326 | *	represents the static state the screen is in. | 
|  | 327 | * | 
|  | 328 | *	Returns negative errno on error, or zero on success. | 
|  | 329 | */ | 
|  | 330 | static int atmel_lcdfb_check_var(struct fb_var_screeninfo *var, | 
|  | 331 | struct fb_info *info) | 
|  | 332 | { | 
|  | 333 | struct device *dev = info->device; | 
|  | 334 | struct atmel_lcdfb_info *sinfo = info->par; | 
|  | 335 | unsigned long clk_value_khz; | 
|  | 336 |  | 
|  | 337 | clk_value_khz = clk_get_rate(sinfo->lcdc_clk) / 1000; | 
|  | 338 |  | 
|  | 339 | dev_dbg(dev, "%s:\n", __func__); | 
| Nicolas Ferre | 968910b | 2008-07-23 21:31:34 -0700 | [diff] [blame] | 340 |  | 
|  | 341 | if (!(var->pixclock && var->bits_per_pixel)) { | 
|  | 342 | /* choose a suitable mode if possible */ | 
|  | 343 | if (!atmel_lcdfb_choose_mode(var, info)) { | 
|  | 344 | dev_err(dev, "needed value not specified\n"); | 
|  | 345 | return -EINVAL; | 
|  | 346 | } | 
|  | 347 | } | 
|  | 348 |  | 
| Nicolas Ferre | 1434058 | 2007-05-10 22:23:26 -0700 | [diff] [blame] | 349 | dev_dbg(dev, "  resolution: %ux%u\n", var->xres, var->yres); | 
|  | 350 | dev_dbg(dev, "  pixclk:     %lu KHz\n", PICOS2KHZ(var->pixclock)); | 
|  | 351 | dev_dbg(dev, "  bpp:        %u\n", var->bits_per_pixel); | 
|  | 352 | dev_dbg(dev, "  clk:        %lu KHz\n", clk_value_khz); | 
|  | 353 |  | 
|  | 354 | if ((PICOS2KHZ(var->pixclock) * var->bits_per_pixel / 8) > clk_value_khz) { | 
|  | 355 | dev_err(dev, "%lu KHz pixel clock is too fast\n", PICOS2KHZ(var->pixclock)); | 
|  | 356 | return -EINVAL; | 
|  | 357 | } | 
|  | 358 |  | 
| Nicolas Ferre | 968910b | 2008-07-23 21:31:34 -0700 | [diff] [blame] | 359 | /* Do not allow to have real resoulution larger than virtual */ | 
|  | 360 | if (var->xres > var->xres_virtual) | 
|  | 361 | var->xres_virtual = var->xres; | 
|  | 362 |  | 
|  | 363 | if (var->yres > var->yres_virtual) | 
|  | 364 | var->yres_virtual = var->yres; | 
|  | 365 |  | 
| Nicolas Ferre | 1434058 | 2007-05-10 22:23:26 -0700 | [diff] [blame] | 366 | /* Force same alignment for each line */ | 
|  | 367 | var->xres = (var->xres + 3) & ~3UL; | 
|  | 368 | var->xres_virtual = (var->xres_virtual + 3) & ~3UL; | 
|  | 369 |  | 
|  | 370 | var->red.msb_right = var->green.msb_right = var->blue.msb_right = 0; | 
|  | 371 | var->transp.msb_right = 0; | 
|  | 372 | var->transp.offset = var->transp.length = 0; | 
|  | 373 | var->xoffset = var->yoffset = 0; | 
|  | 374 |  | 
| Haavard Skinnemoen | 162b3a0 | 2008-02-06 01:39:11 -0800 | [diff] [blame] | 375 | /* Saturate vertical and horizontal timings at maximum values */ | 
|  | 376 | var->vsync_len = min_t(u32, var->vsync_len, | 
|  | 377 | (ATMEL_LCDC_VPW >> ATMEL_LCDC_VPW_OFFSET) + 1); | 
|  | 378 | var->upper_margin = min_t(u32, var->upper_margin, | 
|  | 379 | ATMEL_LCDC_VBP >> ATMEL_LCDC_VBP_OFFSET); | 
|  | 380 | var->lower_margin = min_t(u32, var->lower_margin, | 
|  | 381 | ATMEL_LCDC_VFP); | 
|  | 382 | var->right_margin = min_t(u32, var->right_margin, | 
|  | 383 | (ATMEL_LCDC_HFP >> ATMEL_LCDC_HFP_OFFSET) + 1); | 
|  | 384 | var->hsync_len = min_t(u32, var->hsync_len, | 
|  | 385 | (ATMEL_LCDC_HPW >> ATMEL_LCDC_HPW_OFFSET) + 1); | 
|  | 386 | var->left_margin = min_t(u32, var->left_margin, | 
|  | 387 | ATMEL_LCDC_HBP + 1); | 
|  | 388 |  | 
|  | 389 | /* Some parameters can't be zero */ | 
|  | 390 | var->vsync_len = max_t(u32, var->vsync_len, 1); | 
|  | 391 | var->right_margin = max_t(u32, var->right_margin, 1); | 
|  | 392 | var->hsync_len = max_t(u32, var->hsync_len, 1); | 
|  | 393 | var->left_margin = max_t(u32, var->left_margin, 1); | 
|  | 394 |  | 
| Nicolas Ferre | 1434058 | 2007-05-10 22:23:26 -0700 | [diff] [blame] | 395 | switch (var->bits_per_pixel) { | 
| Nicolas Ferre | 250a269 | 2007-07-21 04:37:59 -0700 | [diff] [blame] | 396 | case 1: | 
| Nicolas Ferre | 1434058 | 2007-05-10 22:23:26 -0700 | [diff] [blame] | 397 | case 2: | 
|  | 398 | case 4: | 
|  | 399 | case 8: | 
|  | 400 | var->red.offset = var->green.offset = var->blue.offset = 0; | 
|  | 401 | var->red.length = var->green.length = var->blue.length | 
|  | 402 | = var->bits_per_pixel; | 
|  | 403 | break; | 
|  | 404 | case 15: | 
|  | 405 | case 16: | 
| Nicolas Ferre | fd08580 | 2008-04-28 02:15:21 -0700 | [diff] [blame] | 406 | if (sinfo->lcd_wiring_mode == ATMEL_LCDC_WIRING_RGB) { | 
|  | 407 | /* RGB:565 mode */ | 
|  | 408 | var->red.offset = 11; | 
|  | 409 | var->blue.offset = 0; | 
|  | 410 | var->green.length = 6; | 
| Guillaume GARDET | fbd03a1 | 2008-08-29 10:11:24 +0100 | [diff] [blame] | 411 | } else if (sinfo->lcd_wiring_mode == ATMEL_LCDC_WIRING_RGB555) { | 
|  | 412 | var->red.offset = 10; | 
|  | 413 | var->blue.offset = 0; | 
|  | 414 | var->green.length = 5; | 
| Nicolas Ferre | fd08580 | 2008-04-28 02:15:21 -0700 | [diff] [blame] | 415 | } else { | 
|  | 416 | /* BGR:555 mode */ | 
|  | 417 | var->red.offset = 0; | 
|  | 418 | var->blue.offset = 10; | 
|  | 419 | var->green.length = 5; | 
|  | 420 | } | 
| Nicolas Ferre | 1434058 | 2007-05-10 22:23:26 -0700 | [diff] [blame] | 421 | var->green.offset = 5; | 
| Nicolas Ferre | fd08580 | 2008-04-28 02:15:21 -0700 | [diff] [blame] | 422 | var->red.length = var->blue.length = 5; | 
| Nicolas Ferre | 1434058 | 2007-05-10 22:23:26 -0700 | [diff] [blame] | 423 | break; | 
| Nicolas Ferre | 1434058 | 2007-05-10 22:23:26 -0700 | [diff] [blame] | 424 | case 32: | 
| Haavard Skinnemoen | 4440e0e | 2007-07-21 04:38:02 -0700 | [diff] [blame] | 425 | var->transp.offset = 24; | 
|  | 426 | var->transp.length = 8; | 
|  | 427 | /* fall through */ | 
|  | 428 | case 24: | 
| Nicolas Ferre | fd08580 | 2008-04-28 02:15:21 -0700 | [diff] [blame] | 429 | if (sinfo->lcd_wiring_mode == ATMEL_LCDC_WIRING_RGB) { | 
|  | 430 | /* RGB:888 mode */ | 
|  | 431 | var->red.offset = 16; | 
|  | 432 | var->blue.offset = 0; | 
|  | 433 | } else { | 
|  | 434 | /* BGR:888 mode */ | 
|  | 435 | var->red.offset = 0; | 
|  | 436 | var->blue.offset = 16; | 
|  | 437 | } | 
| Nicolas Ferre | 1434058 | 2007-05-10 22:23:26 -0700 | [diff] [blame] | 438 | var->green.offset = 8; | 
| Nicolas Ferre | 1434058 | 2007-05-10 22:23:26 -0700 | [diff] [blame] | 439 | var->red.length = var->green.length = var->blue.length = 8; | 
|  | 440 | break; | 
|  | 441 | default: | 
|  | 442 | dev_err(dev, "color depth %d not supported\n", | 
|  | 443 | var->bits_per_pixel); | 
|  | 444 | return -EINVAL; | 
|  | 445 | } | 
|  | 446 |  | 
|  | 447 | return 0; | 
|  | 448 | } | 
|  | 449 |  | 
| Nicolas Ferre | d22579b | 2008-07-23 21:31:20 -0700 | [diff] [blame] | 450 | /* | 
|  | 451 | * LCD reset sequence | 
|  | 452 | */ | 
|  | 453 | static void atmel_lcdfb_reset(struct atmel_lcdfb_info *sinfo) | 
|  | 454 | { | 
|  | 455 | might_sleep(); | 
|  | 456 |  | 
| Haavard Skinnemoen | 3aa04f1 | 2008-09-13 02:33:23 -0700 | [diff] [blame] | 457 | atmel_lcdfb_stop(sinfo); | 
|  | 458 | atmel_lcdfb_start(sinfo); | 
| Nicolas Ferre | d22579b | 2008-07-23 21:31:20 -0700 | [diff] [blame] | 459 | } | 
|  | 460 |  | 
| Nicolas Ferre | 1434058 | 2007-05-10 22:23:26 -0700 | [diff] [blame] | 461 | /** | 
|  | 462 | *      atmel_lcdfb_set_par - Alters the hardware state. | 
|  | 463 | *      @info: frame buffer structure that represents a single frame buffer | 
|  | 464 | * | 
|  | 465 | *	Using the fb_var_screeninfo in fb_info we set the resolution | 
|  | 466 | *	of the this particular framebuffer. This function alters the | 
|  | 467 | *	par AND the fb_fix_screeninfo stored in fb_info. It doesn't | 
|  | 468 | *	not alter var in fb_info since we are using that data. This | 
|  | 469 | *	means we depend on the data in var inside fb_info to be | 
|  | 470 | *	supported by the hardware.  atmel_lcdfb_check_var is always called | 
|  | 471 | *	before atmel_lcdfb_set_par to ensure this.  Again if you can't | 
|  | 472 | *	change the resolution you don't need this function. | 
|  | 473 | * | 
|  | 474 | */ | 
|  | 475 | static int atmel_lcdfb_set_par(struct fb_info *info) | 
|  | 476 | { | 
|  | 477 | struct atmel_lcdfb_info *sinfo = info->par; | 
| Nicolas Ferre | 250a269 | 2007-07-21 04:37:59 -0700 | [diff] [blame] | 478 | unsigned long hozval_linesz; | 
| Nicolas Ferre | 1434058 | 2007-05-10 22:23:26 -0700 | [diff] [blame] | 479 | unsigned long value; | 
|  | 480 | unsigned long clk_value_khz; | 
| Nicolas Ferre | 250a269 | 2007-07-21 04:37:59 -0700 | [diff] [blame] | 481 | unsigned long bits_per_line; | 
| Nicolas Ferre | 1434058 | 2007-05-10 22:23:26 -0700 | [diff] [blame] | 482 |  | 
| Nicolas Ferre | d22579b | 2008-07-23 21:31:20 -0700 | [diff] [blame] | 483 | might_sleep(); | 
|  | 484 |  | 
| Nicolas Ferre | 1434058 | 2007-05-10 22:23:26 -0700 | [diff] [blame] | 485 | dev_dbg(info->device, "%s:\n", __func__); | 
|  | 486 | dev_dbg(info->device, "  * resolution: %ux%u (%ux%u virtual)\n", | 
|  | 487 | info->var.xres, info->var.yres, | 
|  | 488 | info->var.xres_virtual, info->var.yres_virtual); | 
|  | 489 |  | 
| Haavard Skinnemoen | 3aa04f1 | 2008-09-13 02:33:23 -0700 | [diff] [blame] | 490 | atmel_lcdfb_stop_nowait(sinfo); | 
| Nicolas Ferre | 1434058 | 2007-05-10 22:23:26 -0700 | [diff] [blame] | 491 |  | 
| Nicolas Ferre | 250a269 | 2007-07-21 04:37:59 -0700 | [diff] [blame] | 492 | if (info->var.bits_per_pixel == 1) | 
|  | 493 | info->fix.visual = FB_VISUAL_MONO01; | 
|  | 494 | else if (info->var.bits_per_pixel <= 8) | 
| Nicolas Ferre | 1434058 | 2007-05-10 22:23:26 -0700 | [diff] [blame] | 495 | info->fix.visual = FB_VISUAL_PSEUDOCOLOR; | 
|  | 496 | else | 
|  | 497 | info->fix.visual = FB_VISUAL_TRUECOLOR; | 
|  | 498 |  | 
| Nicolas Ferre | 250a269 | 2007-07-21 04:37:59 -0700 | [diff] [blame] | 499 | bits_per_line = info->var.xres_virtual * info->var.bits_per_pixel; | 
|  | 500 | info->fix.line_length = DIV_ROUND_UP(bits_per_line, 8); | 
| Nicolas Ferre | 1434058 | 2007-05-10 22:23:26 -0700 | [diff] [blame] | 501 |  | 
|  | 502 | /* Re-initialize the DMA engine... */ | 
|  | 503 | dev_dbg(info->device, "  * update DMA engine\n"); | 
|  | 504 | atmel_lcdfb_update_dma(info, &info->var); | 
|  | 505 |  | 
|  | 506 | /* ...set frame size and burst length = 8 words (?) */ | 
|  | 507 | value = (info->var.yres * info->var.xres * info->var.bits_per_pixel) / 32; | 
|  | 508 | value |= ((ATMEL_LCDC_DMA_BURST_LEN - 1) << ATMEL_LCDC_BLENGTH_OFFSET); | 
|  | 509 | lcdc_writel(sinfo, ATMEL_LCDC_DMAFRMCFG, value); | 
|  | 510 |  | 
|  | 511 | /* Now, the LCDC core... */ | 
|  | 512 |  | 
|  | 513 | /* Set pixel clock */ | 
|  | 514 | clk_value_khz = clk_get_rate(sinfo->lcdc_clk) / 1000; | 
|  | 515 |  | 
| Nicolas Ferre | 250a269 | 2007-07-21 04:37:59 -0700 | [diff] [blame] | 516 | value = DIV_ROUND_UP(clk_value_khz, PICOS2KHZ(info->var.pixclock)); | 
| Nicolas Ferre | 1434058 | 2007-05-10 22:23:26 -0700 | [diff] [blame] | 517 |  | 
| Nicolas Ferre | baf6332 | 2008-05-12 14:02:25 -0700 | [diff] [blame] | 518 | if (value < 2) { | 
| Nicolas Ferre | 1434058 | 2007-05-10 22:23:26 -0700 | [diff] [blame] | 519 | dev_notice(info->device, "Bypassing pixel clock divider\n"); | 
|  | 520 | lcdc_writel(sinfo, ATMEL_LCDC_LCDCON1, ATMEL_LCDC_BYPASS); | 
| Nicolas Ferre | 250a269 | 2007-07-21 04:37:59 -0700 | [diff] [blame] | 521 | } else { | 
| Nicolas Ferre | baf6332 | 2008-05-12 14:02:25 -0700 | [diff] [blame] | 522 | value = (value / 2) - 1; | 
|  | 523 | dev_dbg(info->device, "  * programming CLKVAL = 0x%08lx\n", | 
|  | 524 | value); | 
|  | 525 | lcdc_writel(sinfo, ATMEL_LCDC_LCDCON1, | 
|  | 526 | value << ATMEL_LCDC_CLKVAL_OFFSET); | 
| Nicolas Ferre | 250a269 | 2007-07-21 04:37:59 -0700 | [diff] [blame] | 527 | info->var.pixclock = KHZ2PICOS(clk_value_khz / (2 * (value + 1))); | 
|  | 528 | dev_dbg(info->device, "  updated pixclk:     %lu KHz\n", | 
|  | 529 | PICOS2KHZ(info->var.pixclock)); | 
|  | 530 | } | 
|  | 531 |  | 
| Nicolas Ferre | 1434058 | 2007-05-10 22:23:26 -0700 | [diff] [blame] | 532 |  | 
|  | 533 | /* Initialize control register 2 */ | 
|  | 534 | value = sinfo->default_lcdcon2; | 
|  | 535 |  | 
|  | 536 | if (!(info->var.sync & FB_SYNC_HOR_HIGH_ACT)) | 
|  | 537 | value |= ATMEL_LCDC_INVLINE_INVERTED; | 
|  | 538 | if (!(info->var.sync & FB_SYNC_VERT_HIGH_ACT)) | 
|  | 539 | value |= ATMEL_LCDC_INVFRAME_INVERTED; | 
|  | 540 |  | 
|  | 541 | switch (info->var.bits_per_pixel) { | 
|  | 542 | case 1:	value |= ATMEL_LCDC_PIXELSIZE_1; break; | 
|  | 543 | case 2: value |= ATMEL_LCDC_PIXELSIZE_2; break; | 
|  | 544 | case 4: value |= ATMEL_LCDC_PIXELSIZE_4; break; | 
|  | 545 | case 8: value |= ATMEL_LCDC_PIXELSIZE_8; break; | 
|  | 546 | case 15: /* fall through */ | 
|  | 547 | case 16: value |= ATMEL_LCDC_PIXELSIZE_16; break; | 
|  | 548 | case 24: value |= ATMEL_LCDC_PIXELSIZE_24; break; | 
|  | 549 | case 32: value |= ATMEL_LCDC_PIXELSIZE_32; break; | 
|  | 550 | default: BUG(); break; | 
|  | 551 | } | 
|  | 552 | dev_dbg(info->device, "  * LCDCON2 = %08lx\n", value); | 
|  | 553 | lcdc_writel(sinfo, ATMEL_LCDC_LCDCON2, value); | 
|  | 554 |  | 
|  | 555 | /* Vertical timing */ | 
|  | 556 | value = (info->var.vsync_len - 1) << ATMEL_LCDC_VPW_OFFSET; | 
|  | 557 | value |= info->var.upper_margin << ATMEL_LCDC_VBP_OFFSET; | 
|  | 558 | value |= info->var.lower_margin; | 
|  | 559 | dev_dbg(info->device, "  * LCDTIM1 = %08lx\n", value); | 
|  | 560 | lcdc_writel(sinfo, ATMEL_LCDC_TIM1, value); | 
|  | 561 |  | 
|  | 562 | /* Horizontal timing */ | 
|  | 563 | value = (info->var.right_margin - 1) << ATMEL_LCDC_HFP_OFFSET; | 
|  | 564 | value |= (info->var.hsync_len - 1) << ATMEL_LCDC_HPW_OFFSET; | 
|  | 565 | value |= (info->var.left_margin - 1); | 
|  | 566 | dev_dbg(info->device, "  * LCDTIM2 = %08lx\n", value); | 
|  | 567 | lcdc_writel(sinfo, ATMEL_LCDC_TIM2, value); | 
|  | 568 |  | 
| Nicolas Ferre | 250a269 | 2007-07-21 04:37:59 -0700 | [diff] [blame] | 569 | /* Horizontal value (aka line size) */ | 
|  | 570 | hozval_linesz = compute_hozval(info->var.xres, | 
|  | 571 | lcdc_readl(sinfo, ATMEL_LCDC_LCDCON2)); | 
|  | 572 |  | 
| Nicolas Ferre | 1434058 | 2007-05-10 22:23:26 -0700 | [diff] [blame] | 573 | /* Display size */ | 
| Nicolas Ferre | 250a269 | 2007-07-21 04:37:59 -0700 | [diff] [blame] | 574 | value = (hozval_linesz - 1) << ATMEL_LCDC_HOZVAL_OFFSET; | 
| Nicolas Ferre | 1434058 | 2007-05-10 22:23:26 -0700 | [diff] [blame] | 575 | value |= info->var.yres - 1; | 
| Nicolas Ferre | 250a269 | 2007-07-21 04:37:59 -0700 | [diff] [blame] | 576 | dev_dbg(info->device, "  * LCDFRMCFG = %08lx\n", value); | 
| Nicolas Ferre | 1434058 | 2007-05-10 22:23:26 -0700 | [diff] [blame] | 577 | lcdc_writel(sinfo, ATMEL_LCDC_LCDFRMCFG, value); | 
|  | 578 |  | 
|  | 579 | /* FIFO Threshold: Use formula from data sheet */ | 
|  | 580 | value = ATMEL_LCDC_FIFO_SIZE - (2 * ATMEL_LCDC_DMA_BURST_LEN + 3); | 
|  | 581 | lcdc_writel(sinfo, ATMEL_LCDC_FIFO, value); | 
|  | 582 |  | 
|  | 583 | /* Toggle LCD_MODE every frame */ | 
|  | 584 | lcdc_writel(sinfo, ATMEL_LCDC_MVAL, 0); | 
|  | 585 |  | 
|  | 586 | /* Disable all interrupts */ | 
|  | 587 | lcdc_writel(sinfo, ATMEL_LCDC_IDR, ~0UL); | 
| Nicolas Ferre | d22579b | 2008-07-23 21:31:20 -0700 | [diff] [blame] | 588 | /* Enable FIFO & DMA errors */ | 
|  | 589 | lcdc_writel(sinfo, ATMEL_LCDC_IER, ATMEL_LCDC_UFLWI | ATMEL_LCDC_OWRI | ATMEL_LCDC_MERI); | 
| Nicolas Ferre | 1434058 | 2007-05-10 22:23:26 -0700 | [diff] [blame] | 590 |  | 
| Nicolas Ferre | 1434058 | 2007-05-10 22:23:26 -0700 | [diff] [blame] | 591 | /* ...wait for DMA engine to become idle... */ | 
|  | 592 | while (lcdc_readl(sinfo, ATMEL_LCDC_DMACON) & ATMEL_LCDC_DMABUSY) | 
|  | 593 | msleep(10); | 
|  | 594 |  | 
| Haavard Skinnemoen | 3aa04f1 | 2008-09-13 02:33:23 -0700 | [diff] [blame] | 595 | atmel_lcdfb_start(sinfo); | 
| Nicolas Ferre | 1434058 | 2007-05-10 22:23:26 -0700 | [diff] [blame] | 596 |  | 
|  | 597 | dev_dbg(info->device, "  * DONE\n"); | 
|  | 598 |  | 
|  | 599 | return 0; | 
|  | 600 | } | 
|  | 601 |  | 
|  | 602 | static inline unsigned int chan_to_field(unsigned int chan, const struct fb_bitfield *bf) | 
|  | 603 | { | 
|  | 604 | chan &= 0xffff; | 
|  | 605 | chan >>= 16 - bf->length; | 
|  | 606 | return chan << bf->offset; | 
|  | 607 | } | 
|  | 608 |  | 
|  | 609 | /** | 
|  | 610 | *  	atmel_lcdfb_setcolreg - Optional function. Sets a color register. | 
|  | 611 | *      @regno: Which register in the CLUT we are programming | 
|  | 612 | *      @red: The red value which can be up to 16 bits wide | 
|  | 613 | *	@green: The green value which can be up to 16 bits wide | 
|  | 614 | *	@blue:  The blue value which can be up to 16 bits wide. | 
|  | 615 | *	@transp: If supported the alpha value which can be up to 16 bits wide. | 
|  | 616 | *      @info: frame buffer info structure | 
|  | 617 | * | 
|  | 618 | *  	Set a single color register. The values supplied have a 16 bit | 
|  | 619 | *  	magnitude which needs to be scaled in this function for the hardware. | 
|  | 620 | *	Things to take into consideration are how many color registers, if | 
|  | 621 | *	any, are supported with the current color visual. With truecolor mode | 
|  | 622 | *	no color palettes are supported. Here a psuedo palette is created | 
|  | 623 | *	which we store the value in pseudo_palette in struct fb_info. For | 
|  | 624 | *	pseudocolor mode we have a limited color palette. To deal with this | 
|  | 625 | *	we can program what color is displayed for a particular pixel value. | 
|  | 626 | *	DirectColor is similar in that we can program each color field. If | 
|  | 627 | *	we have a static colormap we don't need to implement this function. | 
|  | 628 | * | 
|  | 629 | *	Returns negative errno on error, or zero on success. In an | 
|  | 630 | *	ideal world, this would have been the case, but as it turns | 
|  | 631 | *	out, the other drivers return 1 on failure, so that's what | 
|  | 632 | *	we're going to do. | 
|  | 633 | */ | 
|  | 634 | static int atmel_lcdfb_setcolreg(unsigned int regno, unsigned int red, | 
|  | 635 | unsigned int green, unsigned int blue, | 
|  | 636 | unsigned int transp, struct fb_info *info) | 
|  | 637 | { | 
|  | 638 | struct atmel_lcdfb_info *sinfo = info->par; | 
|  | 639 | unsigned int val; | 
|  | 640 | u32 *pal; | 
|  | 641 | int ret = 1; | 
|  | 642 |  | 
|  | 643 | if (info->var.grayscale) | 
|  | 644 | red = green = blue = (19595 * red + 38470 * green | 
|  | 645 | + 7471 * blue) >> 16; | 
|  | 646 |  | 
|  | 647 | switch (info->fix.visual) { | 
|  | 648 | case FB_VISUAL_TRUECOLOR: | 
|  | 649 | if (regno < 16) { | 
|  | 650 | pal = info->pseudo_palette; | 
|  | 651 |  | 
|  | 652 | val  = chan_to_field(red, &info->var.red); | 
|  | 653 | val |= chan_to_field(green, &info->var.green); | 
|  | 654 | val |= chan_to_field(blue, &info->var.blue); | 
|  | 655 |  | 
|  | 656 | pal[regno] = val; | 
|  | 657 | ret = 0; | 
|  | 658 | } | 
|  | 659 | break; | 
|  | 660 |  | 
|  | 661 | case FB_VISUAL_PSEUDOCOLOR: | 
|  | 662 | if (regno < 256) { | 
|  | 663 | val  = ((red   >> 11) & 0x001f); | 
|  | 664 | val |= ((green >>  6) & 0x03e0); | 
|  | 665 | val |= ((blue  >>  1) & 0x7c00); | 
|  | 666 |  | 
|  | 667 | /* | 
|  | 668 | * TODO: intensity bit. Maybe something like | 
|  | 669 | *   ~(red[10] ^ green[10] ^ blue[10]) & 1 | 
|  | 670 | */ | 
|  | 671 |  | 
|  | 672 | lcdc_writel(sinfo, ATMEL_LCDC_LUT(regno), val); | 
|  | 673 | ret = 0; | 
|  | 674 | } | 
|  | 675 | break; | 
| Nicolas Ferre | 250a269 | 2007-07-21 04:37:59 -0700 | [diff] [blame] | 676 |  | 
|  | 677 | case FB_VISUAL_MONO01: | 
|  | 678 | if (regno < 2) { | 
|  | 679 | val = (regno == 0) ? 0x00 : 0x1F; | 
|  | 680 | lcdc_writel(sinfo, ATMEL_LCDC_LUT(regno), val); | 
|  | 681 | ret = 0; | 
|  | 682 | } | 
|  | 683 | break; | 
|  | 684 |  | 
| Nicolas Ferre | 1434058 | 2007-05-10 22:23:26 -0700 | [diff] [blame] | 685 | } | 
|  | 686 |  | 
|  | 687 | return ret; | 
|  | 688 | } | 
|  | 689 |  | 
|  | 690 | static int atmel_lcdfb_pan_display(struct fb_var_screeninfo *var, | 
|  | 691 | struct fb_info *info) | 
|  | 692 | { | 
|  | 693 | dev_dbg(info->device, "%s\n", __func__); | 
|  | 694 |  | 
|  | 695 | atmel_lcdfb_update_dma(info, var); | 
|  | 696 |  | 
|  | 697 | return 0; | 
|  | 698 | } | 
|  | 699 |  | 
|  | 700 | static struct fb_ops atmel_lcdfb_ops = { | 
|  | 701 | .owner		= THIS_MODULE, | 
|  | 702 | .fb_check_var	= atmel_lcdfb_check_var, | 
|  | 703 | .fb_set_par	= atmel_lcdfb_set_par, | 
|  | 704 | .fb_setcolreg	= atmel_lcdfb_setcolreg, | 
|  | 705 | .fb_pan_display	= atmel_lcdfb_pan_display, | 
|  | 706 | .fb_fillrect	= cfb_fillrect, | 
|  | 707 | .fb_copyarea	= cfb_copyarea, | 
|  | 708 | .fb_imageblit	= cfb_imageblit, | 
|  | 709 | }; | 
|  | 710 |  | 
|  | 711 | static irqreturn_t atmel_lcdfb_interrupt(int irq, void *dev_id) | 
|  | 712 | { | 
|  | 713 | struct fb_info *info = dev_id; | 
|  | 714 | struct atmel_lcdfb_info *sinfo = info->par; | 
|  | 715 | u32 status; | 
|  | 716 |  | 
|  | 717 | status = lcdc_readl(sinfo, ATMEL_LCDC_ISR); | 
| Nicolas Ferre | d22579b | 2008-07-23 21:31:20 -0700 | [diff] [blame] | 718 | if (status & ATMEL_LCDC_UFLWI) { | 
|  | 719 | dev_warn(info->device, "FIFO underflow %#x\n", status); | 
|  | 720 | /* reset DMA and FIFO to avoid screen shifting */ | 
|  | 721 | schedule_work(&sinfo->task); | 
|  | 722 | } | 
|  | 723 | lcdc_writel(sinfo, ATMEL_LCDC_ICR, status); | 
| Nicolas Ferre | 1434058 | 2007-05-10 22:23:26 -0700 | [diff] [blame] | 724 | return IRQ_HANDLED; | 
|  | 725 | } | 
|  | 726 |  | 
| Nicolas Ferre | d22579b | 2008-07-23 21:31:20 -0700 | [diff] [blame] | 727 | /* | 
|  | 728 | * LCD controller task (to reset the LCD) | 
|  | 729 | */ | 
|  | 730 | static void atmel_lcdfb_task(struct work_struct *work) | 
|  | 731 | { | 
|  | 732 | struct atmel_lcdfb_info *sinfo = | 
|  | 733 | container_of(work, struct atmel_lcdfb_info, task); | 
|  | 734 |  | 
|  | 735 | atmel_lcdfb_reset(sinfo); | 
|  | 736 | } | 
|  | 737 |  | 
| Nicolas Ferre | 1434058 | 2007-05-10 22:23:26 -0700 | [diff] [blame] | 738 | static int __init atmel_lcdfb_init_fbinfo(struct atmel_lcdfb_info *sinfo) | 
|  | 739 | { | 
|  | 740 | struct fb_info *info = sinfo->info; | 
|  | 741 | int ret = 0; | 
|  | 742 |  | 
| Nicolas Ferre | 1434058 | 2007-05-10 22:23:26 -0700 | [diff] [blame] | 743 | info->var.activate |= FB_ACTIVATE_FORCE | FB_ACTIVATE_NOW; | 
|  | 744 |  | 
|  | 745 | dev_info(info->device, | 
|  | 746 | "%luKiB frame buffer at %08lx (mapped at %p)\n", | 
|  | 747 | (unsigned long)info->fix.smem_len / 1024, | 
|  | 748 | (unsigned long)info->fix.smem_start, | 
|  | 749 | info->screen_base); | 
|  | 750 |  | 
|  | 751 | /* Allocate colormap */ | 
|  | 752 | ret = fb_alloc_cmap(&info->cmap, 256, 0); | 
|  | 753 | if (ret < 0) | 
|  | 754 | dev_err(info->device, "Alloc color map failed\n"); | 
|  | 755 |  | 
|  | 756 | return ret; | 
|  | 757 | } | 
|  | 758 |  | 
|  | 759 | static void atmel_lcdfb_start_clock(struct atmel_lcdfb_info *sinfo) | 
|  | 760 | { | 
|  | 761 | if (sinfo->bus_clk) | 
|  | 762 | clk_enable(sinfo->bus_clk); | 
|  | 763 | clk_enable(sinfo->lcdc_clk); | 
|  | 764 | } | 
|  | 765 |  | 
|  | 766 | static void atmel_lcdfb_stop_clock(struct atmel_lcdfb_info *sinfo) | 
|  | 767 | { | 
|  | 768 | if (sinfo->bus_clk) | 
|  | 769 | clk_disable(sinfo->bus_clk); | 
|  | 770 | clk_disable(sinfo->lcdc_clk); | 
|  | 771 | } | 
|  | 772 |  | 
|  | 773 |  | 
|  | 774 | static int __init atmel_lcdfb_probe(struct platform_device *pdev) | 
|  | 775 | { | 
|  | 776 | struct device *dev = &pdev->dev; | 
|  | 777 | struct fb_info *info; | 
|  | 778 | struct atmel_lcdfb_info *sinfo; | 
|  | 779 | struct atmel_lcdfb_info *pdata_sinfo; | 
| Nicolas Ferre | 968910b | 2008-07-23 21:31:34 -0700 | [diff] [blame] | 780 | struct fb_videomode fbmode; | 
| Nicolas Ferre | 1434058 | 2007-05-10 22:23:26 -0700 | [diff] [blame] | 781 | struct resource *regs = NULL; | 
|  | 782 | struct resource *map = NULL; | 
|  | 783 | int ret; | 
|  | 784 |  | 
|  | 785 | dev_dbg(dev, "%s BEGIN\n", __func__); | 
|  | 786 |  | 
|  | 787 | ret = -ENOMEM; | 
|  | 788 | info = framebuffer_alloc(sizeof(struct atmel_lcdfb_info), dev); | 
|  | 789 | if (!info) { | 
|  | 790 | dev_err(dev, "cannot allocate memory\n"); | 
|  | 791 | goto out; | 
|  | 792 | } | 
|  | 793 |  | 
|  | 794 | sinfo = info->par; | 
|  | 795 |  | 
|  | 796 | if (dev->platform_data) { | 
|  | 797 | pdata_sinfo = (struct atmel_lcdfb_info *)dev->platform_data; | 
|  | 798 | sinfo->default_bpp = pdata_sinfo->default_bpp; | 
|  | 799 | sinfo->default_dmacon = pdata_sinfo->default_dmacon; | 
|  | 800 | sinfo->default_lcdcon2 = pdata_sinfo->default_lcdcon2; | 
|  | 801 | sinfo->default_monspecs = pdata_sinfo->default_monspecs; | 
|  | 802 | sinfo->atmel_lcdfb_power_control = pdata_sinfo->atmel_lcdfb_power_control; | 
|  | 803 | sinfo->guard_time = pdata_sinfo->guard_time; | 
| Haavard Skinnemoen | ea757ac | 2008-08-12 15:08:57 -0700 | [diff] [blame] | 804 | sinfo->smem_len = pdata_sinfo->smem_len; | 
| David Brownell | a9a84c3 | 2008-02-06 01:39:26 -0800 | [diff] [blame] | 805 | sinfo->lcdcon_is_backlight = pdata_sinfo->lcdcon_is_backlight; | 
| Nicolas Ferre | fd08580 | 2008-04-28 02:15:21 -0700 | [diff] [blame] | 806 | sinfo->lcd_wiring_mode = pdata_sinfo->lcd_wiring_mode; | 
| Nicolas Ferre | 1434058 | 2007-05-10 22:23:26 -0700 | [diff] [blame] | 807 | } else { | 
|  | 808 | dev_err(dev, "cannot get default configuration\n"); | 
|  | 809 | goto free_info; | 
|  | 810 | } | 
|  | 811 | sinfo->info = info; | 
|  | 812 | sinfo->pdev = pdev; | 
|  | 813 |  | 
|  | 814 | strcpy(info->fix.id, sinfo->pdev->name); | 
|  | 815 | info->flags = ATMEL_LCDFB_FBINFO_DEFAULT; | 
|  | 816 | info->pseudo_palette = sinfo->pseudo_palette; | 
|  | 817 | info->fbops = &atmel_lcdfb_ops; | 
|  | 818 |  | 
|  | 819 | memcpy(&info->monspecs, sinfo->default_monspecs, sizeof(info->monspecs)); | 
|  | 820 | info->fix = atmel_lcdfb_fix; | 
|  | 821 |  | 
|  | 822 | /* Enable LCDC Clocks */ | 
|  | 823 | if (cpu_is_at91sam9261() || cpu_is_at32ap7000()) { | 
|  | 824 | sinfo->bus_clk = clk_get(dev, "hck1"); | 
|  | 825 | if (IS_ERR(sinfo->bus_clk)) { | 
|  | 826 | ret = PTR_ERR(sinfo->bus_clk); | 
|  | 827 | goto free_info; | 
|  | 828 | } | 
|  | 829 | } | 
|  | 830 | sinfo->lcdc_clk = clk_get(dev, "lcdc_clk"); | 
|  | 831 | if (IS_ERR(sinfo->lcdc_clk)) { | 
|  | 832 | ret = PTR_ERR(sinfo->lcdc_clk); | 
|  | 833 | goto put_bus_clk; | 
|  | 834 | } | 
|  | 835 | atmel_lcdfb_start_clock(sinfo); | 
|  | 836 |  | 
|  | 837 | ret = fb_find_mode(&info->var, info, NULL, info->monspecs.modedb, | 
|  | 838 | info->monspecs.modedb_len, info->monspecs.modedb, | 
|  | 839 | sinfo->default_bpp); | 
|  | 840 | if (!ret) { | 
|  | 841 | dev_err(dev, "no suitable video mode found\n"); | 
|  | 842 | goto stop_clk; | 
|  | 843 | } | 
|  | 844 |  | 
|  | 845 |  | 
|  | 846 | regs = platform_get_resource(pdev, IORESOURCE_MEM, 0); | 
|  | 847 | if (!regs) { | 
|  | 848 | dev_err(dev, "resources unusable\n"); | 
|  | 849 | ret = -ENXIO; | 
|  | 850 | goto stop_clk; | 
|  | 851 | } | 
|  | 852 |  | 
|  | 853 | sinfo->irq_base = platform_get_irq(pdev, 0); | 
|  | 854 | if (sinfo->irq_base < 0) { | 
|  | 855 | dev_err(dev, "unable to get irq\n"); | 
|  | 856 | ret = sinfo->irq_base; | 
|  | 857 | goto stop_clk; | 
|  | 858 | } | 
|  | 859 |  | 
|  | 860 | /* Initialize video memory */ | 
|  | 861 | map = platform_get_resource(pdev, IORESOURCE_MEM, 1); | 
|  | 862 | if (map) { | 
|  | 863 | /* use a pre-allocated memory buffer */ | 
|  | 864 | info->fix.smem_start = map->start; | 
|  | 865 | info->fix.smem_len = map->end - map->start + 1; | 
|  | 866 | if (!request_mem_region(info->fix.smem_start, | 
|  | 867 | info->fix.smem_len, pdev->name)) { | 
|  | 868 | ret = -EBUSY; | 
|  | 869 | goto stop_clk; | 
|  | 870 | } | 
|  | 871 |  | 
|  | 872 | info->screen_base = ioremap(info->fix.smem_start, info->fix.smem_len); | 
|  | 873 | if (!info->screen_base) | 
|  | 874 | goto release_intmem; | 
| Haavard Skinnemoen | 01d3a5e | 2008-04-28 02:15:19 -0700 | [diff] [blame] | 875 |  | 
|  | 876 | /* | 
|  | 877 | * Don't clear the framebuffer -- someone may have set | 
|  | 878 | * up a splash image. | 
|  | 879 | */ | 
| Nicolas Ferre | 1434058 | 2007-05-10 22:23:26 -0700 | [diff] [blame] | 880 | } else { | 
|  | 881 | /* alocate memory buffer */ | 
|  | 882 | ret = atmel_lcdfb_alloc_video_memory(sinfo); | 
|  | 883 | if (ret < 0) { | 
|  | 884 | dev_err(dev, "cannot allocate framebuffer: %d\n", ret); | 
|  | 885 | goto stop_clk; | 
|  | 886 | } | 
|  | 887 | } | 
|  | 888 |  | 
|  | 889 | /* LCDC registers */ | 
|  | 890 | info->fix.mmio_start = regs->start; | 
|  | 891 | info->fix.mmio_len = regs->end - regs->start + 1; | 
|  | 892 |  | 
|  | 893 | if (!request_mem_region(info->fix.mmio_start, | 
|  | 894 | info->fix.mmio_len, pdev->name)) { | 
|  | 895 | ret = -EBUSY; | 
|  | 896 | goto free_fb; | 
|  | 897 | } | 
|  | 898 |  | 
|  | 899 | sinfo->mmio = ioremap(info->fix.mmio_start, info->fix.mmio_len); | 
|  | 900 | if (!sinfo->mmio) { | 
|  | 901 | dev_err(dev, "cannot map LCDC registers\n"); | 
|  | 902 | goto release_mem; | 
|  | 903 | } | 
|  | 904 |  | 
| David Brownell | a9a84c3 | 2008-02-06 01:39:26 -0800 | [diff] [blame] | 905 | /* Initialize PWM for contrast or backlight ("off") */ | 
|  | 906 | init_contrast(sinfo); | 
|  | 907 |  | 
| Nicolas Ferre | 1434058 | 2007-05-10 22:23:26 -0700 | [diff] [blame] | 908 | /* interrupt */ | 
|  | 909 | ret = request_irq(sinfo->irq_base, atmel_lcdfb_interrupt, 0, pdev->name, info); | 
|  | 910 | if (ret) { | 
|  | 911 | dev_err(dev, "request_irq failed: %d\n", ret); | 
|  | 912 | goto unmap_mmio; | 
|  | 913 | } | 
|  | 914 |  | 
| Nicolas Ferre | d22579b | 2008-07-23 21:31:20 -0700 | [diff] [blame] | 915 | /* Some operations on the LCDC might sleep and | 
|  | 916 | * require a preemptible task context */ | 
|  | 917 | INIT_WORK(&sinfo->task, atmel_lcdfb_task); | 
|  | 918 |  | 
| Nicolas Ferre | 1434058 | 2007-05-10 22:23:26 -0700 | [diff] [blame] | 919 | ret = atmel_lcdfb_init_fbinfo(sinfo); | 
|  | 920 | if (ret < 0) { | 
|  | 921 | dev_err(dev, "init fbinfo failed: %d\n", ret); | 
|  | 922 | goto unregister_irqs; | 
|  | 923 | } | 
|  | 924 |  | 
|  | 925 | /* | 
|  | 926 | * This makes sure that our colour bitfield | 
|  | 927 | * descriptors are correctly initialised. | 
|  | 928 | */ | 
|  | 929 | atmel_lcdfb_check_var(&info->var, info); | 
|  | 930 |  | 
|  | 931 | ret = fb_set_var(info, &info->var); | 
|  | 932 | if (ret) { | 
|  | 933 | dev_warn(dev, "unable to set display parameters\n"); | 
|  | 934 | goto free_cmap; | 
|  | 935 | } | 
|  | 936 |  | 
|  | 937 | dev_set_drvdata(dev, info); | 
|  | 938 |  | 
|  | 939 | /* | 
|  | 940 | * Tell the world that we're ready to go | 
|  | 941 | */ | 
|  | 942 | ret = register_framebuffer(info); | 
|  | 943 | if (ret < 0) { | 
|  | 944 | dev_err(dev, "failed to register framebuffer device: %d\n", ret); | 
| Stanislaw Gruszka | 34a35bd | 2008-09-05 14:00:22 -0700 | [diff] [blame] | 945 | goto reset_drvdata; | 
| Nicolas Ferre | 1434058 | 2007-05-10 22:23:26 -0700 | [diff] [blame] | 946 | } | 
|  | 947 |  | 
| Nicolas Ferre | 968910b | 2008-07-23 21:31:34 -0700 | [diff] [blame] | 948 | /* add selected videomode to modelist */ | 
|  | 949 | fb_var_to_videomode(&fbmode, &info->var); | 
|  | 950 | fb_add_videomode(&fbmode, &info->modelist); | 
|  | 951 |  | 
| Nicolas Ferre | 1434058 | 2007-05-10 22:23:26 -0700 | [diff] [blame] | 952 | /* Power up the LCDC screen */ | 
|  | 953 | if (sinfo->atmel_lcdfb_power_control) | 
|  | 954 | sinfo->atmel_lcdfb_power_control(1); | 
|  | 955 |  | 
|  | 956 | dev_info(dev, "fb%d: Atmel LCDC at 0x%08lx (mapped at %p), irq %lu\n", | 
|  | 957 | info->node, info->fix.mmio_start, sinfo->mmio, sinfo->irq_base); | 
|  | 958 |  | 
|  | 959 | return 0; | 
|  | 960 |  | 
| Stanislaw Gruszka | 34a35bd | 2008-09-05 14:00:22 -0700 | [diff] [blame] | 961 | reset_drvdata: | 
|  | 962 | dev_set_drvdata(dev, NULL); | 
| Nicolas Ferre | 1434058 | 2007-05-10 22:23:26 -0700 | [diff] [blame] | 963 | free_cmap: | 
|  | 964 | fb_dealloc_cmap(&info->cmap); | 
|  | 965 | unregister_irqs: | 
| Nicolas Ferre | d22579b | 2008-07-23 21:31:20 -0700 | [diff] [blame] | 966 | cancel_work_sync(&sinfo->task); | 
| Nicolas Ferre | 1434058 | 2007-05-10 22:23:26 -0700 | [diff] [blame] | 967 | free_irq(sinfo->irq_base, info); | 
|  | 968 | unmap_mmio: | 
| David Brownell | a9a84c3 | 2008-02-06 01:39:26 -0800 | [diff] [blame] | 969 | exit_backlight(sinfo); | 
| Nicolas Ferre | 1434058 | 2007-05-10 22:23:26 -0700 | [diff] [blame] | 970 | iounmap(sinfo->mmio); | 
|  | 971 | release_mem: | 
|  | 972 | release_mem_region(info->fix.mmio_start, info->fix.mmio_len); | 
|  | 973 | free_fb: | 
|  | 974 | if (map) | 
|  | 975 | iounmap(info->screen_base); | 
|  | 976 | else | 
|  | 977 | atmel_lcdfb_free_video_memory(sinfo); | 
|  | 978 |  | 
|  | 979 | release_intmem: | 
|  | 980 | if (map) | 
|  | 981 | release_mem_region(info->fix.smem_start, info->fix.smem_len); | 
|  | 982 | stop_clk: | 
|  | 983 | atmel_lcdfb_stop_clock(sinfo); | 
|  | 984 | clk_put(sinfo->lcdc_clk); | 
|  | 985 | put_bus_clk: | 
|  | 986 | if (sinfo->bus_clk) | 
|  | 987 | clk_put(sinfo->bus_clk); | 
|  | 988 | free_info: | 
|  | 989 | framebuffer_release(info); | 
|  | 990 | out: | 
|  | 991 | dev_dbg(dev, "%s FAILED\n", __func__); | 
|  | 992 | return ret; | 
|  | 993 | } | 
|  | 994 |  | 
|  | 995 | static int __exit atmel_lcdfb_remove(struct platform_device *pdev) | 
|  | 996 | { | 
|  | 997 | struct device *dev = &pdev->dev; | 
|  | 998 | struct fb_info *info = dev_get_drvdata(dev); | 
| Stanislaw Gruszka | 34a35bd | 2008-09-05 14:00:22 -0700 | [diff] [blame] | 999 | struct atmel_lcdfb_info *sinfo; | 
| Nicolas Ferre | 1434058 | 2007-05-10 22:23:26 -0700 | [diff] [blame] | 1000 |  | 
| Stanislaw Gruszka | 34a35bd | 2008-09-05 14:00:22 -0700 | [diff] [blame] | 1001 | if (!info || !info->par) | 
| Nicolas Ferre | 1434058 | 2007-05-10 22:23:26 -0700 | [diff] [blame] | 1002 | return 0; | 
| Stanislaw Gruszka | 34a35bd | 2008-09-05 14:00:22 -0700 | [diff] [blame] | 1003 | sinfo = info->par; | 
| Nicolas Ferre | 1434058 | 2007-05-10 22:23:26 -0700 | [diff] [blame] | 1004 |  | 
| Nicolas Ferre | d22579b | 2008-07-23 21:31:20 -0700 | [diff] [blame] | 1005 | cancel_work_sync(&sinfo->task); | 
| David Brownell | a9a84c3 | 2008-02-06 01:39:26 -0800 | [diff] [blame] | 1006 | exit_backlight(sinfo); | 
| Nicolas Ferre | 1434058 | 2007-05-10 22:23:26 -0700 | [diff] [blame] | 1007 | if (sinfo->atmel_lcdfb_power_control) | 
|  | 1008 | sinfo->atmel_lcdfb_power_control(0); | 
|  | 1009 | unregister_framebuffer(info); | 
|  | 1010 | atmel_lcdfb_stop_clock(sinfo); | 
|  | 1011 | clk_put(sinfo->lcdc_clk); | 
|  | 1012 | if (sinfo->bus_clk) | 
|  | 1013 | clk_put(sinfo->bus_clk); | 
|  | 1014 | fb_dealloc_cmap(&info->cmap); | 
|  | 1015 | free_irq(sinfo->irq_base, info); | 
|  | 1016 | iounmap(sinfo->mmio); | 
|  | 1017 | release_mem_region(info->fix.mmio_start, info->fix.mmio_len); | 
|  | 1018 | if (platform_get_resource(pdev, IORESOURCE_MEM, 1)) { | 
|  | 1019 | iounmap(info->screen_base); | 
|  | 1020 | release_mem_region(info->fix.smem_start, info->fix.smem_len); | 
|  | 1021 | } else { | 
|  | 1022 | atmel_lcdfb_free_video_memory(sinfo); | 
|  | 1023 | } | 
|  | 1024 |  | 
|  | 1025 | dev_set_drvdata(dev, NULL); | 
|  | 1026 | framebuffer_release(info); | 
|  | 1027 |  | 
|  | 1028 | return 0; | 
|  | 1029 | } | 
|  | 1030 |  | 
| David Brownell | cf19a37 | 2008-04-28 02:15:20 -0700 | [diff] [blame] | 1031 | #ifdef CONFIG_PM | 
|  | 1032 |  | 
|  | 1033 | static int atmel_lcdfb_suspend(struct platform_device *pdev, pm_message_t mesg) | 
|  | 1034 | { | 
|  | 1035 | struct fb_info *info = platform_get_drvdata(pdev); | 
|  | 1036 | struct atmel_lcdfb_info *sinfo = info->par; | 
|  | 1037 |  | 
| Haavard Skinnemoen | 3aa04f1 | 2008-09-13 02:33:23 -0700 | [diff] [blame] | 1038 | /* | 
|  | 1039 | * We don't want to handle interrupts while the clock is | 
|  | 1040 | * stopped. It may take forever. | 
|  | 1041 | */ | 
|  | 1042 | lcdc_writel(sinfo, ATMEL_LCDC_IDR, ~0UL); | 
|  | 1043 |  | 
| David Brownell | cf19a37 | 2008-04-28 02:15:20 -0700 | [diff] [blame] | 1044 | sinfo->saved_lcdcon = lcdc_readl(sinfo, ATMEL_LCDC_CONTRAST_VAL); | 
|  | 1045 | lcdc_writel(sinfo, ATMEL_LCDC_CONTRAST_CTR, 0); | 
|  | 1046 | if (sinfo->atmel_lcdfb_power_control) | 
|  | 1047 | sinfo->atmel_lcdfb_power_control(0); | 
| Haavard Skinnemoen | 3aa04f1 | 2008-09-13 02:33:23 -0700 | [diff] [blame] | 1048 |  | 
|  | 1049 | atmel_lcdfb_stop(sinfo); | 
| David Brownell | cf19a37 | 2008-04-28 02:15:20 -0700 | [diff] [blame] | 1050 | atmel_lcdfb_stop_clock(sinfo); | 
| Haavard Skinnemoen | 3aa04f1 | 2008-09-13 02:33:23 -0700 | [diff] [blame] | 1051 |  | 
| David Brownell | cf19a37 | 2008-04-28 02:15:20 -0700 | [diff] [blame] | 1052 | return 0; | 
|  | 1053 | } | 
|  | 1054 |  | 
|  | 1055 | static int atmel_lcdfb_resume(struct platform_device *pdev) | 
|  | 1056 | { | 
|  | 1057 | struct fb_info *info = platform_get_drvdata(pdev); | 
|  | 1058 | struct atmel_lcdfb_info *sinfo = info->par; | 
|  | 1059 |  | 
|  | 1060 | atmel_lcdfb_start_clock(sinfo); | 
| Haavard Skinnemoen | 3aa04f1 | 2008-09-13 02:33:23 -0700 | [diff] [blame] | 1061 | atmel_lcdfb_start(sinfo); | 
| David Brownell | cf19a37 | 2008-04-28 02:15:20 -0700 | [diff] [blame] | 1062 | if (sinfo->atmel_lcdfb_power_control) | 
|  | 1063 | sinfo->atmel_lcdfb_power_control(1); | 
|  | 1064 | lcdc_writel(sinfo, ATMEL_LCDC_CONTRAST_CTR, sinfo->saved_lcdcon); | 
| Haavard Skinnemoen | 3aa04f1 | 2008-09-13 02:33:23 -0700 | [diff] [blame] | 1065 |  | 
|  | 1066 | /* Enable FIFO & DMA errors */ | 
|  | 1067 | lcdc_writel(sinfo, ATMEL_LCDC_IER, ATMEL_LCDC_UFLWI | 
|  | 1068 | | ATMEL_LCDC_OWRI | ATMEL_LCDC_MERI); | 
|  | 1069 |  | 
| David Brownell | cf19a37 | 2008-04-28 02:15:20 -0700 | [diff] [blame] | 1070 | return 0; | 
|  | 1071 | } | 
|  | 1072 |  | 
|  | 1073 | #else | 
|  | 1074 | #define atmel_lcdfb_suspend	NULL | 
|  | 1075 | #define atmel_lcdfb_resume	NULL | 
|  | 1076 | #endif | 
|  | 1077 |  | 
| Nicolas Ferre | 1434058 | 2007-05-10 22:23:26 -0700 | [diff] [blame] | 1078 | static struct platform_driver atmel_lcdfb_driver = { | 
|  | 1079 | .remove		= __exit_p(atmel_lcdfb_remove), | 
| David Brownell | cf19a37 | 2008-04-28 02:15:20 -0700 | [diff] [blame] | 1080 | .suspend	= atmel_lcdfb_suspend, | 
|  | 1081 | .resume		= atmel_lcdfb_resume, | 
| David Brownell | a9a84c3 | 2008-02-06 01:39:26 -0800 | [diff] [blame] | 1082 |  | 
| Nicolas Ferre | 1434058 | 2007-05-10 22:23:26 -0700 | [diff] [blame] | 1083 | .driver		= { | 
|  | 1084 | .name	= "atmel_lcdfb", | 
|  | 1085 | .owner	= THIS_MODULE, | 
|  | 1086 | }, | 
|  | 1087 | }; | 
|  | 1088 |  | 
|  | 1089 | static int __init atmel_lcdfb_init(void) | 
|  | 1090 | { | 
|  | 1091 | return platform_driver_probe(&atmel_lcdfb_driver, atmel_lcdfb_probe); | 
|  | 1092 | } | 
|  | 1093 |  | 
|  | 1094 | static void __exit atmel_lcdfb_exit(void) | 
|  | 1095 | { | 
|  | 1096 | platform_driver_unregister(&atmel_lcdfb_driver); | 
|  | 1097 | } | 
|  | 1098 |  | 
|  | 1099 | module_init(atmel_lcdfb_init); | 
|  | 1100 | module_exit(atmel_lcdfb_exit); | 
|  | 1101 |  | 
|  | 1102 | MODULE_DESCRIPTION("AT91/AT32 LCD Controller framebuffer driver"); | 
| Nicolas Ferre | 8f4c79c | 2008-01-14 00:55:13 -0800 | [diff] [blame] | 1103 | MODULE_AUTHOR("Nicolas Ferre <nicolas.ferre@atmel.com>"); | 
| Nicolas Ferre | 1434058 | 2007-05-10 22:23:26 -0700 | [diff] [blame] | 1104 | MODULE_LICENSE("GPL"); |