Chinaunix首页 | 论坛 | 博客
  • 博客访问: 641129
  • 博文数量: 75
  • 博客积分: 7001
  • 博客等级: 少将
  • 技术积分: 1465
  • 用 户 组: 普通用户
  • 注册时间: 2007-07-11 17:39
文章分类

全部博文(75)

文章存档

2010年(1)

2009年(25)

2008年(49)

我的朋友

分类: LINUX

2009-08-24 22:12:51

Analysis to frame buffer driver on development board smdk2410

 

This article briefly introduces the implementation of frame buffer driver for smdk2410. This driver provides great flexibility to configure more display panels that are supported by LCD controller of development board smdk2410.

 

First of all, let’s see several key data structures.

 

struct s3c2410fb_mach_info {

 

         struct s3c2410fb_display *displays;        /* attached diplays info */

         unsigned num_displays;                    /* number of defined displays */

         unsigned default_display;

 

         /* GPIOs */

         unsigned long  gpcup;

         unsigned long  gpcup_mask;

         unsigned long  gpccon;

         unsigned long  gpccon_mask;

         unsigned long  gpdup;

         unsigned long  gpdup_mask;

         unsigned long  gpdcon;

         unsigned long  gpdcon_mask;

 

         /* lpc3600 control register */

         unsigned long  lpcsel;

};

 

This structure describes the overall configuration of the smdk2410 board for the LCD controller equipped on it. For example, struct s3c2410fb_display *displays, the static configurations to all the supported display panels; unsigned num_displays tells you the number of defined(supported) displays; unsigned default_display informs you of the current attached display panel, and some configuration values to GPIOs.

 

/* LCD description */

struct s3c2410fb_display {

         /* LCD type */

         unsigned type;

 

         /* Screen size */

         unsigned short width;

         unsigned short height;

 

         /* Screen info */

         unsigned short xres;

         unsigned short yres;

         unsigned short bpp;

 

         unsigned pixclock;             /* pixclock in picoseconds */

         unsigned short left_margin;  /* value in pixels (TFT) or HCLKs (STN) */

         unsigned short right_margin; /* value in pixels (TFT) or HCLKs (STN) */

         unsigned short hsync_len;    /* value in pixels (TFT) or HCLKs (STN) */

         unsigned short upper_margin;        /* value in lines (TFT) or 0 (STN) */

         unsigned short lower_margin;         /* value in lines (TFT) or 0 (STN) */

         unsigned short vsync_len;       /* value in lines (TFT) or 0 (STN) */

 

         /* lcd configuration registers */

         unsigned long  lcdcon5;

};

 

struct s3c2410fb_display defines all the necessary properties for one display panel. Eg, the width, length, the x, y resolutions, the pixel clock with unite(picoseconds), and the left, right, upper, lower margins.

 

 

struct s3c2410fb_info {

         struct device             *dev;

         struct clk           *clk;

 

         struct resource                  *mem;

         void __iomem           *io;

         void __iomem           *irq_base;

 

         enum s3c_drv_type          drv_type;

         struct s3c2410fb_hw       regs;

 

         unsigned int              palette_ready;

 

         /* keep these registers in case we need to re-write palette */

         u32                     palette_buffer[256];

         u32                     pseudo_pal[16];

};

 

struct s3c2410fb_info defines all the parameters for the frame buffer driver. For example, the mem is used to store the register region, and the “IO” is the virtual addresses mapped for the control registers of LCD controller. The “drv_type” identifies which board the controller is armed on, SMDK2410 or SMDK2412. And the “regs” keeps the current configuration values of LCD controller registers for the current display panel, from register con1 to con5.

Some important data structures for Linux Frame Buffer Driver, struct fb_info, struct fb_var_screeninfo, struct fb_fix_screeninfo fix, truct fb_cmap cmap, etc., are introduced in details in the excellent article << Writing Linux LCD drivers>> wrote by JimSheng, you can get it here,

 

 

Registration of display panel

 

/* LCD driver info */

 

static struct s3c2410fb_display qt2410_lcd_cfg[] __initdata = {

         {

                   /* Configuration for 640x480 SHARP LQ080V3DG01 */

                   .lcdcon5 = S3C2410_LCDCON5_FRM565 |

                               S3C2410_LCDCON5_INVVLINE |

                               S3C2410_LCDCON5_INVVFRAME |

                               S3C2410_LCDCON5_PWREN |

                               S3C2410_LCDCON5_HWSWP,

 

                   .type                  = S3C2410_LCDCON1_TFT,

                   .width                = 640,

                   .height               = 480,

 

                   .pixclock   = 40000, /* HCLK/4 */

                   .xres                   = 640,

                   .yres                   = 480,

                   .bpp          = 16,

                   .left_margin     = 44,

                   .right_margin  = 116,

                   .hsync_len        = 96,

                   .upper_margin         = 19,

                   .lower_margin          = 11,

                   .vsync_len         = 15,

         },

         {

                   /* Configuration for 480x640 toppoly TD028TTEC1 */

                   .lcdcon5 = S3C2410_LCDCON5_FRM565 |

                               S3C2410_LCDCON5_INVVLINE |

                               S3C2410_LCDCON5_INVVFRAME |

                               S3C2410_LCDCON5_PWREN |

                               S3C2410_LCDCON5_HWSWP,

 

                   .type                  = S3C2410_LCDCON1_TFT,

                   .width                = 480,

                   .height               = 640,

                   .pixclock   = 40000, /* HCLK/4 */

                   .xres                   = 480,

                   .yres                   = 640,

                   .bpp          = 16,

                   .left_margin     = 8,

                   .right_margin  = 24,

                   .hsync_len        = 8,

                   .upper_margin         = 2,

                   .lower_margin          = 4,

                   .vsync_len         = 2,

         },

         {

                   /* Config for 240x320 LCD */

                   .lcdcon5 = S3C2410_LCDCON5_FRM565 |

                               S3C2410_LCDCON5_INVVLINE |

                               S3C2410_LCDCON5_INVVFRAME |

                               S3C2410_LCDCON5_PWREN |

                               S3C2410_LCDCON5_HWSWP,

 

                   .type                  = S3C2410_LCDCON1_TFT,

                   .width                = 240,

                   .height               = 320,

                   .pixclock   = 100000, /* HCLK/10 */

                   .xres                   = 240,

                   .yres                   = 320,

                   .bpp          = 16,

                   .left_margin     = 13,

                   .right_margin  = 8,

                   .hsync_len        = 4,

                   .upper_margin         = 2,

                   .lower_margin          = 7,

                   .vsync_len         = 4,

         },

};

 

 

static struct s3c2410fb_mach_info qt2410_fb_info __initdata = {

         .displays          = qt2410_lcd_cfg,

         .num_displays         = ARRAY_SIZE(qt2410_lcd_cfg),

         .default_display = 0,

 

         .lpcsel                = ((0xCE6) & ~7) | 1<<4,

};

There are 3 types display panels supported by the LCD controller, now, for the specified board, on which the first type display panel is armed designated by “default_display”. The above configuration of the board for LCD portion is defined in structure qt2410_fb_info, the variable will be set to platform_data stuff of platform device by s3c24xx_fb_set_platdata(&qt2410_fb_info) function.

 

/* LCD Controller */

 

static struct resource s3c_lcd_resource[] = {

         [0] = {

                   .start = S3C24XX_PA_LCD,

                   .end   = S3C24XX_PA_LCD + S3C24XX_SZ_LCD - 1,

                   .flags = IORESOURCE_MEM,

         },

         [1] = {

                   .start = IRQ_LCD,

                   .end   = IRQ_LCD,

                   .flags = IORESOURCE_IRQ,

         }

 

};

struct platform_device s3c_device_lcd = {

         .name                  = "s3c2410-lcd",

         .id                = -1,

         .num_resources         = ARRAY_SIZE(s3c_lcd_resource),

         .resource   = s3c_lcd_resource,

         .dev              = {

                   .dma_mask               = &s3c_device_lcd_dmamask,

                   .coherent_dma_mask     = 0xffffffffUL

         }

};

 

This is the definition of platform_device s3c_device_lcd.

 

void __init s3c24xx_fb_set_platdata(struct s3c2410fb_mach_info *pd)

{

         struct s3c2410fb_mach_info *npd;

 

         npd = kmalloc(sizeof(*npd), GFP_KERNEL);

         if (npd) {

                   memcpy(npd, pd, sizeof(*npd));

                   s3c_device_lcd.dev.platform_data = npd;

         } else {

                   printk(KERN_ERR "no memory for LCD platform data\n");

         }

}

 

The s3c24xx_fb_set_platdata() stores the global board info for the LCD portion into the platform_data of platform_device s3c_device_lcd, which will be registered to the system by general kernel API platform_add_devices(qt2410_devices, ARRAY_SIZE(qt2410_devices)); which is used to register more than one platform devices, in essence, the platform_add_devices() calls platform_device_register() API to add each of devices described in array qt2410_devices.

 

Registration of frame buffer driver for smdk2410

 

static struct platform_driver s3c2410fb_driver = {

         .probe                = s3c2410fb_probe,

         .remove            = s3c2410fb_remove,

         .suspend  = s3c2410fb_suspend,

         .resume            = s3c2410fb_resume,

         .driver                = {

                   .name       = "s3c2410-lcd",

                   .owner     = THIS_MODULE,

         },

};

 

platform_driver_register(&s3c2410fb_driver) will register s3c2410fb_driver, due to the identified name of s3c2410fb_driver and s3c_device_lcd, the probe() callback will be triggered.

static int __init s3c2410fb_probe(struct platform_device *pdev)

{

         return s3c24xxfb_probe(pdev, DRV_S3C2410);

}

 

static int __init s3c24xxfb_probe(struct platform_device *pdev,

                                       enum s3c_drv_type drv_type)

{

         struct s3c2410fb_info *info;

         struct s3c2410fb_display *display;

         struct fb_info *fbinfo;

         struct s3c2410fb_mach_info *mach_info;

         struct resource *res;

         int ret;

         int irq;

         int i;

         int size;

         u32 lcdcon1;

 

         mach_info = pdev->dev.platform_data;

----- > because s3c24xx_fb_set_platdata(&qt2410_fb_info) is called, qt2410_fb_info is set to dev.platform_data of truct platform_device s3c_device_lcd, and the pdev points to struct platform_device s3c_device_lcd, mach_info is used to reference qt2410_fb_info.

 

         if (mach_info == NULL) {

                   dev_err(&pdev->dev,

                            "no platform data for lcd, cannot attach\n");

                   return -EINVAL;

         }

---- > if the machine configuration for the LCD controller is not provide, we cannot do any initialization without adequate information.

 

         if (mach_info->default_display >= mach_info->num_displays) {

                   dev_err(&pdev->dev, "default is %d but only %d displays\n",

                            mach_info->default_display, mach_info->num_displays);

                   return -EINVAL;

         }

 

         display = mach_info->displays + mach_info->default_display;

--- > herein, we get the display information, which is attached to the LCD controller.

 

         irq = platform_get_irq(pdev, 0);

         if (irq < 0) {

                   dev_err(&pdev->dev, "no irq for device\n");

                   return -ENOENT;

         }

--- > obtain the interrupt number of LCD controller.

 

         fbinfo = framebuffer_alloc(sizeof(struct s3c2410fb_info), &pdev->dev);

         if (!fbinfo)

                   return -ENOMEM;

 

         platform_set_drvdata(pdev, fbinfo);

--- > Create a frame buffer device for the current display panel, and set it to the platform_data of platform_device pdev->dev->platform_data. framebuffer_alloc - creates a new frame buffer info structure  @size: size of driver private data, can be zero @dev: pointer to the device for this fb, this can be NULL, it Creates a new frame buffer info structure. Also reserves @size bytes for driver private data (info->par). info->par (if any) will be aligned to sizeof(long).

 

         info = fbinfo->par;

--- > struct s3c2410fb_info *info points to the reserved space of type sizeof(struct s3c2410fb_info through framebuffer_alloc(sizeof(struct s3c2410fb_info), &pdev->dev), then by use of which, we can fill its content.

 

 

         info->dev = &pdev->dev;

---- > herein, we can obtain the global configuration of truct s3c2410fb_mach_info.

 

         info->drv_type = drv_type;

 

         res = platform_get_resource(pdev, IORESOURCE_MEM, 0);

         if (res == NULL) {

                   dev_err(&pdev->dev, "failed to get memory registers\n");

                   ret = -ENXIO;

                   goto dealloc_fb;

         }

 

         size = (res->end - res->start) + 1;

         info->mem = request_mem_region(res->start, size, pdev->name);

         if (info->mem == NULL) {

                   dev_err(&pdev->dev, "failed to get memory region\n");

                   ret = -ENOENT;

                   goto dealloc_fb;

         }

---- > herein, reserve physical space of resource of display panel and save it to info->mem.

 

         info->io = ioremap(res->start, size);

         if (info->io == NULL) {

                   dev_err(&pdev->dev, "ioremap() of registers failed\n");

                   ret = -ENXIO;

                   goto release_mem;

         }

--- > map the physical space to virtual address and save it to info->io.

 

         info->irq_base = info->io + ((drv_type == DRV_S3C2412) ? S3C2412_LCDINTBASE : S3C2410_LCDINTBASE);

--- > obtain the interrupt controller physical base address and save it to info->irq_base.

 

         dprintk("devinit\n");

 

         strcpy(fbinfo->fix.id, driver_name);

 

         /* Stop the video */

         lcdcon1 = readl(info->io + S3C2410_LCDCON1);

         writel(lcdcon1 & ~S3C2410_LCDCON1_ENVID, info->io + S3C2410_LCDCON1);

 

         fbinfo->fix.type             = FB_TYPE_PACKED_PIXELS;

         fbinfo->fix.type_aux             = 0;

         fbinfo->fix.xpanstep             = 0;

         fbinfo->fix.ypanstep              = 0;

         fbinfo->fix.ywrapstep           = 0;

         fbinfo->fix.accel            = FB_ACCEL_NONE;

 

         fbinfo->var.nonstd       = 0;

         fbinfo->var.activate     = FB_ACTIVATE_NOW;

         fbinfo->var.accel_flags     = 0;

         fbinfo->var.vmode        = FB_VMODE_NONINTERLACED;

 

         fbinfo->fbops                 = &s3c2410fb_ops;

         fbinfo->flags                  = FBINFO_FLAG_DEFAULT;

         fbinfo->pseudo_palette      = &info->pseudo_pal;

---- > fill the corresponding stuffs of struct fbinfo.

 

         for (i = 0; i < 256; i++)

                   info->palette_buffer[i] = PALETTE_BUFF_CLEAR;

 

         ret = request_irq(irq, s3c2410fb_irq, IRQF_DISABLED, pdev->name, info);

         if (ret) {

                   dev_err(&pdev->dev, "cannot get irq %d - err %d\n", irq, ret);

                   ret = -EBUSY;

                   goto release_regs;

         }

--- > register interrupt service routine.

 

         info->clk = clk_get(NULL, "lcd");

         if (!info->clk || IS_ERR(info->clk)) {

                   printk(KERN_ERR "failed to get lcd clock source\n");

                   ret = -ENOENT;

                   goto release_irq;

         }

---- > obtain the clock of LCD controller.

 

         clk_enable(info->clk);

         dprintk("got and enabled clock\n");

---- > enable the clock of LCD controller.

 

         msleep(1);

 

         /* find maximum required memory size for display */

         for (i = 0; i < mach_info->num_displays; i++) {

                   unsigned long smem_len = mach_info->displays[i].xres;

 

                   smem_len *= mach_info->displays[i].yres;

                   smem_len *= mach_info->displays[i].bpp;

                   smem_len >>= 3;

                   if (fbinfo->fix.smem_len < smem_len)

                            fbinfo->fix.smem_len = smem_len;

         }

 

         /* Initialize video memory */

         ret = s3c2410fb_map_video_memory(fbinfo);

         if (ret) {

                   printk(KERN_ERR "Failed to allocate video RAM: %d\n", ret);

                   ret = -ENOMEM;

                   goto release_clock;

         }

 

         dprintk("got video memory\n");

 

         fbinfo->var.xres = display->xres;

         fbinfo->var.yres = display->yres;

         fbinfo->var.bits_per_pixel = display->bpp;

 

         s3c2410fb_init_registers(fbinfo);

---- > s3c2410fb_init_registers - Initialise all LCD-related registers

 

         s3c2410fb_check_var(&fbinfo->var, fbinfo);

 

         ret = register_framebuffer(fbinfo);

--- > register_framebuffer - registers a frame buffer device

 

         if (ret < 0) {

                   printk(KERN_ERR "Failed to register framebuffer device: %d\n",

                            ret);

                   goto free_video_memory;

         }

 

         /* create device files */

         ret = device_create_file(&pdev->dev, &dev_attr_debug);

         if (ret) {

                   printk(KERN_ERR "failed to add debug attribute\n");

         }

 

         printk(KERN_INFO "fb%d: %s frame buffer device\n",

                   fbinfo->node, fbinfo->fix.id);

 

         return 0;

 

free_video_memory:

         s3c2410fb_unmap_video_memory(fbinfo);

release_clock:

         clk_disable(info->clk);

         clk_put(info->clk);

release_irq:

         free_irq(irq, info);

release_regs:

         iounmap(info->io);

release_mem:

         release_resource(info->mem);

         kfree(info->mem);

dealloc_fb:

         platform_set_drvdata(pdev, NULL);

         framebuffer_release(fbinfo);

         return ret;

}

 

The patch file for s3c2410fb of linux-2.6.29:

文件:linux-2.6.29-s3c2410fb.rar
大小:8KB
下载:下载




阅读(1727) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~