📄 atmel_lcdfb.c
字号:
/* * Driver for AT91/AT32 LCD Controller * * Copyright (C) 2007 Atmel Corporation * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of this archive for * more details. */#include <linux/kernel.h>#include <linux/platform_device.h>#include <linux/dma-mapping.h>#include <linux/interrupt.h>#include <linux/clk.h>#include <linux/fb.h>#include <linux/init.h>#include <linux/delay.h>#include <linux/backlight.h>#include <mach/board.h>#include <mach/cpu.h>#include <mach/gpio.h>#include <video/atmel_lcdc.h>#define lcdc_readl(sinfo, reg) __raw_readl((sinfo)->mmio+(reg))#define lcdc_writel(sinfo, reg, val) __raw_writel((val), (sinfo)->mmio+(reg))/* configurable parameters */#define ATMEL_LCDC_CVAL_DEFAULT 0xc8#define ATMEL_LCDC_DMA_BURST_LEN 8#if defined(CONFIG_ARCH_AT91SAM9263) || defined(CONFIG_ARCH_AT91CAP9) || \ defined(CONFIG_ARCH_AT91SAM9RL)#define ATMEL_LCDC_FIFO_SIZE 2048#else#define ATMEL_LCDC_FIFO_SIZE 512#endif#if defined(CONFIG_ARCH_AT91)#define ATMEL_LCDFB_FBINFO_DEFAULT (FBINFO_DEFAULT \ | FBINFO_PARTIAL_PAN_OK \ | FBINFO_HWACCEL_YPAN)static inline void atmel_lcdfb_update_dma2d(struct atmel_lcdfb_info *sinfo, struct fb_var_screeninfo *var){}#elif defined(CONFIG_AVR32)#define ATMEL_LCDFB_FBINFO_DEFAULT (FBINFO_DEFAULT \ | FBINFO_PARTIAL_PAN_OK \ | FBINFO_HWACCEL_XPAN \ | FBINFO_HWACCEL_YPAN)static void atmel_lcdfb_update_dma2d(struct atmel_lcdfb_info *sinfo, struct fb_var_screeninfo *var){ u32 dma2dcfg; u32 pixeloff; pixeloff = (var->xoffset * var->bits_per_pixel) & 0x1f; dma2dcfg = ((var->xres_virtual - var->xres) * var->bits_per_pixel) / 8; dma2dcfg |= pixeloff << ATMEL_LCDC_PIXELOFF_OFFSET; lcdc_writel(sinfo, ATMEL_LCDC_DMA2DCFG, dma2dcfg); /* Update configuration */ lcdc_writel(sinfo, ATMEL_LCDC_DMACON, lcdc_readl(sinfo, ATMEL_LCDC_DMACON) | ATMEL_LCDC_DMAUPDT);}#endifstatic const u32 contrast_ctr = ATMEL_LCDC_PS_DIV8 | ATMEL_LCDC_POL_POSITIVE | ATMEL_LCDC_ENA_PWMENABLE;#ifdef CONFIG_BACKLIGHT_ATMEL_LCDC/* some bl->props field just changed */static int atmel_bl_update_status(struct backlight_device *bl){ struct atmel_lcdfb_info *sinfo = bl_get_data(bl); int power = sinfo->bl_power; int brightness = bl->props.brightness; /* REVISIT there may be a meaningful difference between * fb_blank and power ... there seem to be some cases * this doesn't handle correctly. */ if (bl->props.fb_blank != sinfo->bl_power) power = bl->props.fb_blank; else if (bl->props.power != sinfo->bl_power) power = bl->props.power; if (brightness < 0 && power == FB_BLANK_UNBLANK) brightness = lcdc_readl(sinfo, ATMEL_LCDC_CONTRAST_VAL); else if (power != FB_BLANK_UNBLANK) brightness = 0; lcdc_writel(sinfo, ATMEL_LCDC_CONTRAST_VAL, brightness); lcdc_writel(sinfo, ATMEL_LCDC_CONTRAST_CTR, brightness ? contrast_ctr : 0); bl->props.fb_blank = bl->props.power = sinfo->bl_power = power; return 0;}static int atmel_bl_get_brightness(struct backlight_device *bl){ struct atmel_lcdfb_info *sinfo = bl_get_data(bl); return lcdc_readl(sinfo, ATMEL_LCDC_CONTRAST_VAL);}static struct backlight_ops atmel_lcdc_bl_ops = { .update_status = atmel_bl_update_status, .get_brightness = atmel_bl_get_brightness,};static void init_backlight(struct atmel_lcdfb_info *sinfo){ struct backlight_device *bl; sinfo->bl_power = FB_BLANK_UNBLANK; if (sinfo->backlight) return; bl = backlight_device_register("backlight", &sinfo->pdev->dev, sinfo, &atmel_lcdc_bl_ops); if (IS_ERR(bl)) { dev_err(&sinfo->pdev->dev, "error %ld on backlight register\n", PTR_ERR(bl)); return; } sinfo->backlight = bl; bl->props.power = FB_BLANK_UNBLANK; bl->props.fb_blank = FB_BLANK_UNBLANK; bl->props.max_brightness = 0xff; bl->props.brightness = atmel_bl_get_brightness(bl);}static void exit_backlight(struct atmel_lcdfb_info *sinfo){ if (sinfo->backlight) backlight_device_unregister(sinfo->backlight);}#elsestatic void init_backlight(struct atmel_lcdfb_info *sinfo){ dev_warn(&sinfo->pdev->dev, "backlight control is not available\n");}static void exit_backlight(struct atmel_lcdfb_info *sinfo){}#endifstatic void init_contrast(struct atmel_lcdfb_info *sinfo){ /* have some default contrast/backlight settings */ lcdc_writel(sinfo, ATMEL_LCDC_CONTRAST_CTR, contrast_ctr); lcdc_writel(sinfo, ATMEL_LCDC_CONTRAST_VAL, ATMEL_LCDC_CVAL_DEFAULT); if (sinfo->lcdcon_is_backlight) init_backlight(sinfo);}static struct fb_fix_screeninfo atmel_lcdfb_fix __initdata = { .type = FB_TYPE_PACKED_PIXELS, .visual = FB_VISUAL_TRUECOLOR, .xpanstep = 0, .ypanstep = 1, .ywrapstep = 0, .accel = FB_ACCEL_NONE,};static unsigned long compute_hozval(unsigned long xres, unsigned long lcdcon2){ unsigned long value; if (!(cpu_is_at91sam9261() || cpu_is_at32ap7000())) return xres; value = xres; if ((lcdcon2 & ATMEL_LCDC_DISTYPE) != ATMEL_LCDC_DISTYPE_TFT) { /* STN display */ if ((lcdcon2 & ATMEL_LCDC_DISTYPE) == ATMEL_LCDC_DISTYPE_STNCOLOR) { value *= 3; } if ( (lcdcon2 & ATMEL_LCDC_IFWIDTH) == ATMEL_LCDC_IFWIDTH_4 || ( (lcdcon2 & ATMEL_LCDC_IFWIDTH) == ATMEL_LCDC_IFWIDTH_8 && (lcdcon2 & ATMEL_LCDC_SCANMOD) == ATMEL_LCDC_SCANMOD_DUAL )) value = DIV_ROUND_UP(value, 4); else value = DIV_ROUND_UP(value, 8); } return value;}static void atmel_lcdfb_stop_nowait(struct atmel_lcdfb_info *sinfo){ /* Turn off the LCD controller and the DMA controller */ lcdc_writel(sinfo, ATMEL_LCDC_PWRCON, sinfo->guard_time << ATMEL_LCDC_GUARDT_OFFSET); /* Wait for the LCDC core to become idle */ while (lcdc_readl(sinfo, ATMEL_LCDC_PWRCON) & ATMEL_LCDC_BUSY) msleep(10); lcdc_writel(sinfo, ATMEL_LCDC_DMACON, 0);}static void atmel_lcdfb_stop(struct atmel_lcdfb_info *sinfo){ atmel_lcdfb_stop_nowait(sinfo); /* Wait for DMA engine to become idle... */ while (lcdc_readl(sinfo, ATMEL_LCDC_DMACON) & ATMEL_LCDC_DMABUSY) msleep(10);}static void atmel_lcdfb_start(struct atmel_lcdfb_info *sinfo){ lcdc_writel(sinfo, ATMEL_LCDC_DMACON, sinfo->default_dmacon); lcdc_writel(sinfo, ATMEL_LCDC_PWRCON, (sinfo->guard_time << ATMEL_LCDC_GUARDT_OFFSET) | ATMEL_LCDC_PWR);}static void atmel_lcdfb_update_dma(struct fb_info *info, struct fb_var_screeninfo *var){ struct atmel_lcdfb_info *sinfo = info->par; struct fb_fix_screeninfo *fix = &info->fix; unsigned long dma_addr; dma_addr = (fix->smem_start + var->yoffset * fix->line_length + var->xoffset * var->bits_per_pixel / 8); dma_addr &= ~3UL; /* Set framebuffer DMA base address and pixel offset */ lcdc_writel(sinfo, ATMEL_LCDC_DMABADDR1, dma_addr); atmel_lcdfb_update_dma2d(sinfo, var);}static inline void atmel_lcdfb_free_video_memory(struct atmel_lcdfb_info *sinfo){ struct fb_info *info = sinfo->info; dma_free_writecombine(info->device, info->fix.smem_len, info->screen_base, info->fix.smem_start);}/** * atmel_lcdfb_alloc_video_memory - Allocate framebuffer memory * @sinfo: the frame buffer to allocate memory for */static int atmel_lcdfb_alloc_video_memory(struct atmel_lcdfb_info *sinfo){ struct fb_info *info = sinfo->info; struct fb_var_screeninfo *var = &info->var; unsigned int smem_len; smem_len = (var->xres_virtual * var->yres_virtual * ((var->bits_per_pixel + 7) / 8)); info->fix.smem_len = max(smem_len, sinfo->smem_len); info->screen_base = dma_alloc_writecombine(info->device, info->fix.smem_len, (dma_addr_t *)&info->fix.smem_start, GFP_KERNEL); if (!info->screen_base) { return -ENOMEM; } memset(info->screen_base, 0, info->fix.smem_len); return 0;}static const struct fb_videomode *atmel_lcdfb_choose_mode(struct fb_var_screeninfo *var, struct fb_info *info){ struct fb_videomode varfbmode; const struct fb_videomode *fbmode = NULL; fb_var_to_videomode(&varfbmode, var); fbmode = fb_find_nearest_mode(&varfbmode, &info->modelist); if (fbmode) fb_videomode_to_var(var, fbmode); return fbmode;}/** * atmel_lcdfb_check_var - Validates a var passed in. * @var: frame buffer variable screen structure * @info: frame buffer structure that represents a single frame buffer * * Checks to see if the hardware supports the state requested by * var passed in. This function does not alter the hardware * state!!! This means the data stored in struct fb_info and * struct atmel_lcdfb_info do not change. This includes the var * inside of struct fb_info. Do NOT change these. This function * can be called on its own if we intent to only test a mode and * not actually set it. The stuff in modedb.c is a example of * this. If the var passed in is slightly off by what the * hardware can support then we alter the var PASSED in to what * we can do. If the hardware doesn't support mode change a * -EINVAL will be returned by the upper layers. You don't need * to implement this function then. If you hardware doesn't * support changing the resolution then this function is not * needed. In this case the driver would just provide a var that * represents the static state the screen is in. * * Returns negative errno on error, or zero on success. */static int atmel_lcdfb_check_var(struct fb_var_screeninfo *var, struct fb_info *info){ struct device *dev = info->device; struct atmel_lcdfb_info *sinfo = info->par; unsigned long clk_value_khz; clk_value_khz = clk_get_rate(sinfo->lcdc_clk) / 1000; dev_dbg(dev, "%s:\n", __func__); if (!(var->pixclock && var->bits_per_pixel)) { /* choose a suitable mode if possible */ if (!atmel_lcdfb_choose_mode(var, info)) { dev_err(dev, "needed value not specified\n"); return -EINVAL; } } dev_dbg(dev, " resolution: %ux%u\n", var->xres, var->yres); dev_dbg(dev, " pixclk: %lu KHz\n", PICOS2KHZ(var->pixclock)); dev_dbg(dev, " bpp: %u\n", var->bits_per_pixel); dev_dbg(dev, " clk: %lu KHz\n", clk_value_khz); if ((PICOS2KHZ(var->pixclock) * var->bits_per_pixel / 8) > clk_value_khz) { dev_err(dev, "%lu KHz pixel clock is too fast\n", PICOS2KHZ(var->pixclock)); return -EINVAL; } /* Do not allow to have real resoulution larger than virtual */ if (var->xres > var->xres_virtual) var->xres_virtual = var->xres; if (var->yres > var->yres_virtual) var->yres_virtual = var->yres; /* Force same alignment for each line */ var->xres = (var->xres + 3) & ~3UL; var->xres_virtual = (var->xres_virtual + 3) & ~3UL; var->red.msb_right = var->green.msb_right = var->blue.msb_right = 0; var->transp.msb_right = 0;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -