首先我們來回歸下怎麽寫一個字符設備驅動程序,下面以linux設備中的混雜設備為例
static int __init misc_init(void)
{
    int err;

#ifdef CONFIG_PROC_FS
    proc_create("misc", 0, NULL, &misc_proc_fops);
#endif
    misc_class = class_create(THIS_MODULE, "misc");
    err = PTR_ERR(misc_class);
    if (IS_ERR(misc_class))
        goto fail_remove;

    err = -EIO;
    if (register_chrdev(MISC_MAJOR,"misc",&misc_fops))
        goto fail_printk;
    misc_class->devnode = misc_devnode;
    return 0;

fail_printk:
    printk("unable to get major %d for misc devices\n", MISC_MAJOR);
    class_destroy(misc_class);
fail_remove:
    remove_proc_entry("misc", NULL);
    return err;
}
上面的主要步驟為申請一個主次設備號,構建一個file_opertiaon,並用register_chrdev註冊,構建類,並可以在類下創建設備節點。現在來看看內核是怎麽實現lcd驅動程序呢?
static int __init
fbmem_init(void)
{
    proc_create("fb", 0, NULL, &fb_proc_fops);//與proc文件系統有關

    if (register_chrdev(FB_MAJOR,"fb",&fb_fops))//註冊字符設備
        printk("unable to get major %d for fb devs\n", FB_MAJOR);

    fb_class = class_create(THIS_MODULE, "graphics");//創建類,為以後創建設備節點做準備
    if (IS_ERR(fb_class)) {
        printk(KERN_WARNING "Unable to create fb class; errno = %ld\n", PTR_ERR(fb_class));
        fb_class = NULL;
    }
    return 0;
}
從上面也可以看出,內核對於lcd驅動程序也是滿足一個主設備號為29的字符設備,那麽用戶空間訪問通過fb_fops結構體的成員函數會操作到LCD控制器硬件寄存器。下面來看看linux幀緩沖設備驅動的主要結構圖

framebuffer機制模仿顯卡的功能,將顯卡硬件結構抽象為一系列的數據結構,可以通過framebuffer的讀寫直接對顯存進行操作。framebuffer一個字符設備,主設備號,對應於/dev/fb0...32的設備文件。對於用戶程序而言,與字符設備並沒有什麽區別,用戶具有把其看成一塊內存,既可以讀也可以寫。那麽下面來看看
在這個圖中占主導地位的fb_info結構體
struct fb_info {
    atomic_t count;
    int node;             //子設備號
    int flags;
    struct mutex lock;        //互斥鎖
    struct mutex mm_lock;        /* Lock for fb_mmap and smem_* fields */
    struct fb_var_screeninfo var;    //當前緩沖區的可變參數
    struct fb_fix_screeninfo fix;    //固定參數
    struct fb_monspecs monspecs;    //當前顯示器標誌
    struct work_struct queue;    //幀緩沖事件隊列
    struct fb_pixmap pixmap;    //圖像硬件mapper
    struct fb_pixmap sprite;    //光標硬件mapper
    struct fb_cmap cmap;        //當前的調色板
    struct list_head modelist; /* mode list */
    struct fb_videomode *mode;    //當前的視頻模式

#ifdef CONFIG_FB_BACKLIGHT      
    /* assigned backlight device *//支持lcd背光的設置
    /* set before framebuffer registration,
     remove after unregister */
    struct backlight_device *bl_dev;  

    /* Backlight level curve */
    struct mutex bl_curve_mutex;    
    u8 bl_curve[FB_BACKLIGHT_LEVELS];
#endif
#ifdef CONFIG_FB_DEFERRED_IO
    struct delayed_work deferred_work;
    struct fb_deferred_io *fbdefio;
#endif

    struct fb_ops *fbops;     //幀緩沖操作函數集
    struct device *device;        //父設備
    struct device *dev;        /* This is this fb device */
    int class_flag; /* private sysfs flags */
#ifdef CONFIG_FB_TILEBLITTING
    struct fb_tile_ops *tileops; /* Tile Blitting */
#endif
    char __iomem *screen_base;    //虛擬基地址
    unsigned long screen_size;    /* Amount of ioremapped VRAM or 0 */
    void *pseudo_palette;        /* Fake palette of 16 colors */
#define FBINFO_STATE_RUNNING    0
#define FBINFO_STATE_SUSPENDED    1
    u32 state;            /* Hardware state i.e suspend */
    void *fbcon_par; /* fbcon use-only private area */
    /* From here on everything is device dependent */
    void *par;
    /* we need the PCI or similar aperture base/size not
     smem_start/size as smem_start may just be an object
     allocated inside the aperture so may not actually overlap */
    struct apertures_struct {
        unsigned int count;
        struct aperture {
            resource_size_t base;
            resource_size_t size;
        } ranges[0];
    } *apertures;
};
fb_ops結構體用來實現對幀緩沖的操作,這些函數需要我們自行編寫的。
struct fb_ops {
    /* open/release and usage marking */
    struct module *owner;
    int (*fb_open)(struct fb_info *info, int user);
    int (*fb_release)(struct fb_info *info, int user);//打開和釋放

    /* For framebuffers with strange non linear layouts or that do not
     * work with normal memory mapped access
     */
    ssize_t (*fb_read)(struct fb_info *info, char __user *buf,
             size_t count, loff_t *ppos);
    ssize_t (*fb_write)(struct fb_info *info, const char __user *buf,
             size_t count, loff_t *ppos);

    /* checks var and eventually tweaks it to something supported,
     * DO NOT MODIFY PAR *///檢測可變參數,並調整到支持的值
    int (*fb_check_var)(struct fb_var_screeninfo *var, struct fb_info *info);

    /* set the video mode according to info->var */
    int (*fb_set_par)(struct fb_info *info);//視頻設置模式

    /* set color register */
    int (*fb_setcolreg)(unsigned regno, unsigned red, unsigned green,
             unsigned blue, unsigned transp, struct fb_info *info);//設置color寄存器

    /* set color registers in batch */
    int (*fb_setcmap)(struct fb_cmap *cmap, struct fb_info *info);//批量設置color寄存器,設備顏色表

    /* blank display */
    int (*fb_blank)(int blank, struct fb_info *info);//顯示空白

    /* pan display */
    int (*fb_pan_display)(struct fb_var_screeninfo *var, struct fb_info *info);

    /* Draws a rectangle */
    void (*fb_fillrect) (struct fb_info *info, const struct fb_fillrect *rect);//填充矩形
    /* Copy data from area to another */
    void (*fb_copyarea) (struct fb_info *info, const struct fb_copyarea *region);//數據復制
    /* Draws a image to the display */
    void (*fb_imageblit) (struct fb_info *info, const struct fb_image *image);//圖形填充

    /* Draws cursor */
    int (*fb_cursor) (struct fb_info *info, struct fb_cursor *cursor);//繪制光標

    /* Rotates the display */
    void (*fb_rotate)(struct fb_info *info, int angle);//旋轉顯示

    /* wait for blit idle, optional */
    int (*fb_sync)(struct fb_info *info);//等待blit空閑

    /* perform fb specific ioctl (optional) */
    int (*fb_ioctl)(struct fb_info *info, unsigned int cmd,
            unsigned long arg);

    /* Handle 32bit compat ioctl (optional) */
    int (*fb_compat_ioctl)(struct fb_info *info, unsigned cmd,
            unsigned long arg);

    /* perform fb specific mmap */
    int (*fb_mmap)(struct fb_info *info, struct vm_area_struct *vma);

    /* get capability given var */
    void (*fb_get_caps)(struct fb_info *info, struct fb_blit_caps *caps,
             struct fb_var_screeninfo *var);

    /* teardown any resources to do with this framebuffer */
    void (*fb_destroy)(struct fb_info *info);

    /* called at KDB enter and leave time to prepare the console */
    int (*fb_debug_enter)(struct fb_info *info);
    int (*fb_debug_leave)(struct fb_info *info);
};

內核中定義了一個struct fb_info *registered_fb[FB_MAX]來存放屏的相關信息,下面來看看幀緩沖設備fb_info結構體的註冊。
int
register_framebuffer(struct fb_info *fb_info)
{
    int ret;

    mutex_lock(&registration_lock);
    ret = do_register_framebuffer(fb_info);
    mutex_unlock(&registration_lock);

    return ret;
}
static int do_register_framebuffer(struct fb_info *fb_info)
{
    int i;
    struct fb_event event;
    struct fb_videomode mode;

    if (fb_check_foreignness(fb_info))
        return -ENOSYS;

    do_remove_conflicting_framebuffers(fb_info->apertures, fb_info->fix.id,
                     fb_is_primary_device(fb_info));

    if (num_registered_fb == FB_MAX)
        return -ENXIO;

    num_registered_fb++;
    for (i = 0 ; i < FB_MAX; i++)
        if (!registered_fb[i])
            break;
    fb_info->node = i;
    atomic_set(&fb_info->count, 1);
    mutex_init(&fb_info->lock);
    mutex_init(&fb_info->mm_lock);

    fb_info->dev = device_create(fb_class, fb_info->device,
                 MKDEV(FB_MAJOR, i), NULL, "fb%d", i);//創建設備節點
    if (IS_ERR(fb_info->dev)) {
        /* Not fatal */
        printk(KERN_WARNING "Unable to create device for framebuffer %d; errno = %ld\n", i, PTR_ERR(fb_info->dev));
        fb_info->dev = NULL;
    } else
        fb_init_device(fb_info);//初始化fb的屬性文件

    if (fb_info->pixmap.addr == NULL) {
        fb_info->pixmap.addr = kmalloc(FBPIXMAPSIZE, GFP_KERNEL);
        if (fb_info->pixmap.addr) {
            fb_info->pixmap.size = FBPIXMAPSIZE;
            fb_info->pixmap.buf_align = 1;
            fb_info->pixmap.scan_align = 1;
            fb_info->pixmap.access_align = 32;
            fb_info->pixmap.flags = FB_PIXMAP_DEFAULT;
        }
    }    
    fb_info->pixmap.offset = 0;

    if (!fb_info->pixmap.blit_x)
        fb_info->pixmap.blit_x = ~(u32)0;

    if (!fb_info->pixmap.blit_y)
        fb_info->pixmap.blit_y = ~(u32)0;

    if (!fb_info->modelist.prev || !fb_info->modelist.next)
        INIT_LIST_HEAD(&fb_info->modelist);

    fb_var_to_videomode(&mode, &fb_info->var);
    fb_add_videomode(&mode, &fb_info->modelist);
    registered_fb[i] = fb_info;

    event.info = fb_info;
    if (!lock_fb_info(fb_info))
        return -ENODEV;
    fb_notifier_call_chain(FB_EVENT_FB_REGISTERED, &event);
    unlock_fb_info(fb_info);
    return 0;
}
這個函數主要是創建設備節點,初始化fb的屬性文件等一些初始化,下面來看看幀緩沖區驅動核心層的層次結構圖:

那麽從上面的圖可以總結出怎麽寫一個lcd的驅動程序的步驟
1.分配一個fb_info的結構體
2.設置
3.註冊register_framebuffer
4.硬件相關的初始化
上面將了一些framebuffer幀緩沖的有關的,那麽我們具體看看內核怎麽去實現lcd的驅動。
int __init s3c2410fb_init(void)
{
    int ret = platform_driver_register(&s3c2410fb_driver);

    if (ret == 0)
        ret = platform_driver_register(&s3c2412fb_driver);

    return ret;
}

static void __exit s3c2410fb_cleanup(void)
{
    platform_driver_unregister(&s3c2410fb_driver);
    platform_driver_unregister(&s3c2412fb_driver);
}

module_init(s3c2410fb_init);
module_exit(s3c2410fb_cleanup);
lcd驅動掛在platform總線上,註冊了一個s3c2410fb_driver的驅動,由總線驅動設備模型中,當註冊一個設備或者驅動的時候,會調用總線的match函數,而platform的match函數是匹配設備和驅動的名字
static struct platform_driver s3c2410fb_driver = {
    .probe        = s3c2410fb_probe,
    .remove        = __devexit_p(s3c2410fb_remove),
    .suspend    = s3c2410fb_suspend,
    .resume        = s3c2410fb_resume,
    .driver        = {
        .name    = "s3c2410-lcd",
        .owner    = THIS_MODULE,
    },
};

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        = &samsung_device_dma_mask,
        .coherent_dma_mask    = DMA_BIT_MASK(32),
    }
};
static struct resource s3c_lcd_resource[] = {
    [0] = DEFINE_RES_MEM(S3C24XX_PA_LCD, S3C24XX_SZ_LCD),//內存
    [1] = DEFINE_RES_IRQ(IRQ_LCD),//中斷號
};
驅動與設備匹配會,會調用probe函數

static int __devinit 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;
    if (mach_info == NULL) {
        dev_err(&pdev->dev,
            "no platform data for lcd, cannot attach\n");
        return -EINVAL;
    }

    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;

    irq = platform_get_irq(pdev, 0);//獲取中斷號
    if (irq < 0) {
        dev_err(&pdev->dev, "no irq for device\n");
        return -ENOENT;
    }

    fbinfo = framebuffer_alloc(sizeof(struct s3c2410fb_info), &pdev->dev);//分配一個fb_info結構體
    if (!fbinfo)
        return -ENOMEM;

    platform_set_drvdata(pdev, fbinfo);

    info = fbinfo->par;
    info->dev = &pdev->dev;
    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 = resource_size(res);
    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;
    }

    info->io = ioremap(res->start, size);//io map
    if (info->io == NULL) {
        dev_err(&pdev->dev, "ioremap() of registers failed\n");
        ret = -ENXIO;
        goto release_mem;
    }

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

    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結構體
    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;

    for (i = 0; i < 256; i++)
        info->palette_buffer[i] = PALETTE_BUFF_CLEAR;

    ret = request_irq(irq, s3c2410fb_irq, 0, pdev->name, info);
    if (ret) {
        dev_err(&pdev->dev, "cannot get irq %d - err %d\n", irq, ret);
        ret = -EBUSY;
        goto release_regs;
    }

    info->clk = clk_get(NULL, "lcd");
    if (IS_ERR(info->clk)) {
        printk(KERN_ERR "failed to get lcd clock source\n");
        ret = PTR_ERR(info->clk);
        goto release_irq;
    }

    clk_enable(info->clk);
    dprintk("got and enabled clock\n");

    msleep(1);

    info->clk_rate = clk_get_rate(info->clk);

    /* 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_check_var(&fbinfo->var, fbinfo);

    ret = s3c2410fb_cpufreq_register(info);
    if (ret < 0) {
        dev_err(&pdev->dev, "Failed to register cpufreq\n");
        goto free_video_memory;
    }

    ret = register_framebuffer(fbinfo);
    if (ret < 0) {
        printk(KERN_ERR "Failed to register framebuffer device: %d\n",
            ret);
        goto free_cpufreq;
    }

    /* 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_cpufreq:
    s3c2410fb_cpufreq_deregister(info);
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_mem_region(res->start, size);
dealloc_fb:
    platform_set_drvdata(pdev, NULL);
    framebuffer_release(fbinfo);
    return ret;
}
上面的一些步驟基本與總結的一個lcd的驅動程序的步驟相同,下面來看看用戶空間怎麽去訪問
app:read()-->kernel調用fbmem.c中的fb_read(),其他的接口類似。
static ssize_t
fb_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
{
    unsigned long p = *ppos;
    struct fb_info *info = file_fb_info(file);//獲取次設備號,根據次設備號從registered_fb數組中拿
    u8 *buffer, *dst;                         //到對應的fb_info
    u8 __iomem *src;
    int c, cnt = 0, err = 0;
    unsigned long total_size;

    if (!info || ! info->screen_base)
        return -ENODEV;

    if (info->state != FBINFO_STATE_RUNNING)
        return -EPERM;

    if (info->fbops->fb_read)//調用read函數
        return info->fbops->fb_read(info, buf, count, ppos);
    
    total_size = info->screen_size;

    if (total_size == 0)
        total_size = info->fix.smem_len;

    if (p >= total_size)
        return 0;

    if (count >= total_size)
        count = total_size;

    if (count + p > total_size)
        count = total_size - p;

    buffer = kmalloc((count > PAGE_SIZE) ? PAGE_SIZE : count,
             GFP_KERNEL);
    if (!buffer)
        return -ENOMEM;

    src = (u8 __iomem *) (info->screen_base + p);//獲得幀緩沖的起始地址+偏移量

    if (info->fbops->fb_sync)
        info->fbops->fb_sync(info);
      
    while (count) {                           //拷貝數據
        c = (count > PAGE_SIZE) ? PAGE_SIZE : count;
        dst = buffer;
        fb_memcpy_fromfb(dst, src, c);
        dst += c;
        src += c;

        if (copy_to_user(buf, buffer, c)) {//提交給應用層。
            err = -EFAULT;
            break;
        }
        *ppos += c;
        buf += c;
        cnt += c;
        count -= c;
    }

    kfree(buffer);

    return (err) ? err : cnt;
}

arrow
arrow
    全站熱搜

    主要步驟 發表在 痞客邦 留言(0) 人氣()