开发板为友善之臂的tiny4412,显示屏配的是S70(群创AT070TN92)。写个笔记缕一缕思路,有错还恳请提出。

MODULE

首先像之前的驱动一样,将这个LCD驱动注册在platform总线上。作为一个字符设备,当然有read,write,open,close,lseek等功能,但是在此之前先要注册平台驱动(platform_driver__register())。

int func_init(void)
{
    printk("func_init\n");
    return platform_driver_register(&tdrv);
}

void func_exit(void)
{
    printk("func exit\n");
    platform_driver_unregister(&tdrv);
}

没有对应的信息,内核当然无法知道你要注册什么。这些信息则放在tdrv结构体。

struct platform_driver tdrv = {
    .probe = test_probe,
    .remove = test_remove, 
    .driver = {
        .name = DRVNAME, //名字
        .owner = THIS_MODULE, //占用
    },
};

Platform bus

注册了驱动,就要实现插入和拔出设备(probe/remove)对应的函数。

int test_probe (struct platform_device *pdev)
{
    int ret;
    struct test_lcd *lcd_dev; //结构体在下面有写[1]
    struct resource * res; //申请资源
    printk("driver: test probe\n");

    lcd_dev = kzalloc(sizeof(struct test_lcd), GFP_KERNEL);//分配[2]
    if(IS_ERR_OR_NULL(lcd_dev))
    {//分配失败
        ret = -ENOMEM;
        printk("%d\n", __LINE__);
        goto ERROR_kzalloc;
    }

    res = platform_get_resource(pdev, IORESOURCE_MEM, 0);   //内存资源[3]
    if(IS_ERR_OR_NULL(res))
    {//内存资源申请失败
        ret = -EBUSY;
        printk("%d\n", __LINE__);
        goto ERROR_get_mem_res;
    }

    lcd_dev->size = resource_size(res);

    lcd_dev->reg = ioremap(res->start, lcd_dev->size);//动态映射[4]
    if(IS_ERR_OR_NULL(lcd_dev->reg))
    {
        ret = -ENOMEM;
        printk("%d\n", __LINE__);
        goto ERROR_ioremap;
    }

    lcd_dev->platdata = pdev->dev.platform_data;//资源信息[5]

    lcd_dev->lcd = clk_get(&pdev->dev, "lcd");//获取并绑定时钟
    if(IS_ERR_OR_NULL(lcd_dev->lcd))
    {
        ret = -EBUSY;
        printk("%d\n", __LINE__);
        goto ERROR_clk_get_lcd;
    }

    clk_enable(lcd_dev->lcd);//开启

    lcd_dev->sclk_fimd = clk_get(&pdev->dev, "sclk_fimd");
    if(IS_ERR_OR_NULL(lcd_dev->sclk_fimd))
    {
        ret = -EBUSY;
        printk("%d\n", __LINE__);
        goto ERROR_clk_get_fimd;
    }

    clk_enable(lcd_dev->sclk_fimd);

    lcd_dev->smem_len = W * H * BPP32 / 8;
    lcd_dev->virt = dma_alloc_coherent(NULL, lcd_dev->smem_len, &lcd_dev->phys, GFP_KERNEL);//返回两个虚拟地址和物理地址(kzmalloc虽然只返回虚拟但可以转换,只能用于直接映射,有cache)
    if(IS_ERR_OR_NULL(lcd_dev->virt))
    {
        ret = -ENOMEM;
        printk("%d\n", __LINE__);
        goto ERROR_dma_alloc;
    }
//对应的操作集
    lcd_dev->fops.owner = THIS_MODULE;
    lcd_dev->fops.open = test_open;
    lcd_dev->fops.release = test_release;
    lcd_dev->fops.write = test_write;
    lcd_dev->fops.llseek = test_llseek;

    lcd_dev->mdev.name = "fb";
    lcd_dev->mdev.minor = MISC_DYNAMIC_MINOR;
    lcd_dev->mdev.fops = &lcd_dev->fops;
//注册杂设备
    ret = misc_register(&lcd_dev->mdev);
    if(IS_ERR_VALUE(ret))
    {
        goto ERROR_misc;
    }

    lcd_dev->platdata->setup_gpio();
    lcd_init(lcd_dev);

/*
    p = lcd_dev->virt;
    for(i = 0; i < W * H; i++)
    {
        p[i] = 0xff;
    }
*/

    platform_set_drvdata(pdev, lcd_dev);

    return 0;

ERROR_misc:
    dma_free_coherent(NULL, W * H * BPP32 / 8, lcd_dev->virt, lcd_dev->phys);
ERROR_dma_alloc:
    clk_disable(lcd_dev->sclk_fimd);
    clk_put(lcd_dev->sclk_fimd);
ERROR_clk_get_fimd:
    clk_disable(lcd_dev->lcd);//解绑
    clk_put(lcd_dev->lcd);//放回
ERROR_clk_get_lcd:
    iounmap(lcd_dev->reg);//解除映射
ERROR_ioremap:
ERROR_get_mem_res:
    kfree(lcd_dev);//释放
ERROR_kzalloc:
    return ret;
}

  1. 结构体 test_lcd
struct test_lcd{
    void *reg;
    int size;

    struct s3c_fb_platdata *platdata;
    struct clk *lcd;
    struct clk *sclk_fimd;

    void *virt;
    unsigned int phys;
    unsigned int smem_len;

    struct file_operations fops;
    struct miscdevice mdev;
}; 

2.kzalloc linux3.5/include/linux/slab.h
只需要大小,函数返回地址

/**
 * kzalloc - allocate memory. The memory is set to zero.
 * @size: how many bytes of memory are required.
 * @flags: the type of memory to allocate (see kmalloc).
 */
static inline void *kzalloc(size_t size, gfp_t flags)
{
    return kmalloc(size, flags | __GFP_ZERO);
}
  1. get resource

/**
 * platform_get_resource - get a resource for a device
 * @dev: platform device
 * @type: resource type
 * @num: resource index
 */
struct resource *platform_get_resource(struct platform_device *dev, unsigned int type, unsigned int num)
{
    int i;

    for (i = 0; i < dev->num_resources; i++) {
        struct resource *r = &dev->resource[i];

        if (type == resource_type(r) && num-- == 0)
            return r;
    }
    return NULL;
}
EXPORT_SYMBOL_GPL(platform_get_resource);

——4. 动态映射
arch/arm/include/asm/io.h
建立物理地址和虚拟地址的映射。
———–这一块可以跳过

    #define ioremap(cookie,size)        __arm_ioremap((cookie), (size), MT_DEVICE)

arch/arm/mm/ioremap.c

void __iomem *
__arm_ioremap(unsigned long phys_addr, size_t size, unsigned int mtype)
{
    return arch_ioremap_caller(phys_addr, size, mtype,
        __builtin_return_address(0));
}
EXPORT_SYMBOL(__arm_ioremap);
void __iomem * (*arch_ioremap_caller)(unsigned long, size_t,
                      unsigned int, void *) =
    __arm_ioremap_caller;
void __iomem *__arm_ioremap_caller(unsigned long phys_addr, size_t size,
    unsigned int mtype, void *caller)
{
    unsigned long last_addr;
    unsigned long offset = phys_addr & ~PAGE_MASK;
    unsigned long pfn = __phys_to_pfn(phys_addr);

    /*
     * Don't allow wraparound or zero size
     */
    last_addr = phys_addr + size - 1;
    if (!size || last_addr < phys_addr)
        return NULL;

    return __arm_ioremap_pfn_caller(pfn, offset, size, mtype,
            caller);
}
void __iomem * __arm_ioremap_pfn_caller(unsigned long pfn,
    unsigned long offset, size_t size, unsigned int mtype, void *caller)
{
    const struct mem_type *type;
    int err;
    unsigned long addr;
    struct vm_struct * area;

#ifndef CONFIG_ARM_LPAE
    /*
     * High mappings must be supersection aligned
     */
    if (pfn >= 0x100000 && (__pfn_to_phys(pfn) & ~SUPERSECTION_MASK))
        return NULL;
#endif

    type = get_mem_type(mtype);
    if (!type)
        return NULL;

    /*
     * Page align the mapping size, taking account of any offset.
     */
    size = PAGE_ALIGN(offset + size);

    /*
     * Try to reuse one of the static mapping whenever possible.
     */
    read_lock(&vmlist_lock);
    for (area = vmlist; area; area = area->next) {
        if (!size || (sizeof(phys_addr_t) == 4 && pfn >= 0x100000))
            break;
        if (!(area->flags & VM_ARM_STATIC_MAPPING))
            continue;
        if ((area->flags & VM_ARM_MTYPE_MASK) != VM_ARM_MTYPE(mtype))
            continue;
        if (__phys_to_pfn(area->phys_addr) > pfn ||
            __pfn_to_phys(pfn) + size-1 > area->phys_addr + area->size-1)
            continue;
        /* we can drop the lock here as we know *area is static */
        read_unlock(&vmlist_lock);
        addr = (unsigned long)area->addr;
        addr += __pfn_to_phys(pfn) - area->phys_addr;
        return (void __iomem *) (offset + addr);
    }
    read_unlock(&vmlist_lock);

    /*
     * Don't allow RAM to be mapped - this causes problems with ARMv6+
     */
    if (WARN_ON(pfn_valid(pfn)))
        return NULL;

    area = get_vm_area_caller(size, VM_IOREMAP, caller);
    if (!area)
        return NULL;
    addr = (unsigned long)area->addr;

#if !defined(CONFIG_SMP) && !defined(CONFIG_ARM_LPAE)
    if (DOMAIN_IO == 0 &&
        (((cpu_architecture() >= CPU_ARCH_ARMv6) && (get_cr() & CR_XP)) ||
           cpu_is_xsc3()) && pfn >= 0x100000 &&
           !((__pfn_to_phys(pfn) | size | addr) & ~SUPERSECTION_MASK)) {
        area->flags |= VM_ARM_SECTION_MAPPING;
        err = remap_area_supersections(addr, pfn, size, type);
    } else if (!((__pfn_to_phys(pfn) | size | addr) & ~PMD_MASK)) {
        area->flags |= VM_ARM_SECTION_MAPPING;
        err = remap_area_sections(addr, pfn, size, type);
    } else
#endif
        err = ioremap_page_range(addr, addr + size, __pfn_to_phys(pfn),
                     __pgprot(type->prot_pte));

    if (err) {
        vunmap((void *)addr);
        return NULL;
    }

    flush_cache_vmap(addr, addr + size);
    return (void __iomem *) (offset + addr);
}
# define __iomem    __attribute__((noderef, address_space(2)))

. 5. platdata
/linux3.5/include/linux/platform_device.h

struct platform_device {
    const char  * name;
    int     id;
    struct device   dev;
    u32     num_resources;
    struct resource * resource;

    const struct platform_device_id *id_entry;

    /* MFD cell pointer */
    struct mfd_cell *mfd_cell;

    /* arch specific additions */
    struct pdev_archdata    archdata;
};

操作集

//chrdev/
ssize_t test_write (struct file *filp, const char __user *buf, size_t size, loff_t *offset)
{
    int ret;
    struct test_lcd *lcd_dev;
    lcd_dev = filp->private_data; //提出地址
    //printk("test write\n");

    if(size > (lcd_dev->smem_len - *offset))
        size = lcd_dev->smem_len - *offset;

    ret = copy_from_user(lcd_dev->virt + *offset, buf, size);   
    *offset += size - ret;

    return size - ret;
}

int test_open (struct inode *inode, struct file *filp)
{
    struct test_lcd *lcd_dev;
    lcd_dev = container_of(filp->f_op, struct test_lcd, fops);//找出首地址
    filp->private_data = lcd_dev;//保存到上层函数,其他的函数就可以用这个地址来查找数据

    //printk("test open\n");
    return 0;
}

int test_release (struct inode *inode, struct file *filp)
{
    struct test_lcd *lcd_dev;
    lcd_dev = filp->private_data;

    //printk("test release \n");
    return 0;
}

loff_t test_llseek (struct file *filp, loff_t offset, int whence)
{
    loff_t cur;
    struct test_lcd *lcd_dev;
    lcd_dev = filp->private_data;
    cur = filp->f_pos;
    switch(whence)
    {
        case SEEK_SET:
            cur = offset;
            break;
        case SEEK_CUR:
            cur += offset;
            break;
        case SEEK_END:
            cur += offset;
            break;
        default:
            return -EINVAL;
    }

    if(cur > lcd_dev->smem_len)
        cur = lcd_dev->smem_len;

    filp->f_pos = cur;

    return cur;
}

自定义设置

要按照三星的用户手册来配置寄存器,内核提供了writel, readl来读写寄存器。

//asm board/
void lcd_init(struct test_lcd *lcd_dev)
{
    unsigned int val;

    val = (9<<6);
    writel(val, lcd_dev->reg + 0x0);

    val = (7<<5);
    writel(val, lcd_dev->reg + 0x4);

    val = (21<<16)|(21<<8)|0;
    writel(val, lcd_dev->reg + 0x10);
    val = (44<<16)|(209<<8)|0;
    writel(val, lcd_dev->reg + 0x14);
    val = (479<<11)|799;
    writel(val, lcd_dev->reg + 0x18);

    //buffer format
    val = (1<<15)|(0xb<<2)|1;
    writel(val, lcd_dev->reg + 0x20);

    //x, y
    val = 0;
    writel(val, lcd_dev->reg + 0x40);
    val = (800<<11)|480;
    writel(val, lcd_dev->reg + 0x44);

    //buffer addr
    val = lcd_dev->phys;
    writel(val, lcd_dev->reg + 0xa0);
    val = lcd_dev->phys + lcd_dev->smem_len;
    writel(val, lcd_dev->reg + 0xd0);
    val = W * BPP32 / 8;
    writel(val, lcd_dev->reg + 0x100);

    val = (1<<0);
    writel(val, lcd_dev->reg + 0x34);

    val = readl(lcd_dev->reg + 0x0);
    val |= 0x3;
    writel(val, lcd_dev->reg + 0x0);
}

void lcd_uninit(struct test_lcd *lcd_dev)
{
    unsigned int val;
    val = readl(lcd_dev->reg + 0x0);
    val &= ~0x3;
    writel(val, lcd_dev->reg + 0x0);
}
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/gpio.h>
#include <linux/sched.h>
#include <linux/wait.h>
#include <linux/poll.h>
#include <linux/device.h>
#include <linux/platform_device.h>
#include <linux/miscdevice.h>
#include <linux/io.h>
#include <linux/interrupt.h>
#include <linux/slab.h>
#include <linux/clk.h>
#include <linux/dma-mapping.h>

#include <plat/fb.h>
//#include "test.h"

#define DRVNAME         "exynos4-fb"
#define MISCNAME        "test-fb"

#define W               800
#define H               480
#define BPP24           24
#define BPP32           32

struct test_lcd{
    void *reg;
    int size;

    struct s3c_fb_platdata *platdata;
    struct clk *lcd;
    struct clk *sclk_fimd;

    void *virt;
    unsigned int phys;
    unsigned int smem_len;

    struct file_operations fops;
    struct miscdevice mdev;
};

//asm board/
void lcd_init(struct test_lcd *lcd_dev)
{
    unsigned int val;

    val = (9<<6);
    writel(val, lcd_dev->reg + 0x0);

    val = (7<<5);
    writel(val, lcd_dev->reg + 0x4);

    val = (21<<16)|(21<<8)|0;
    writel(val, lcd_dev->reg + 0x10);
    val = (44<<16)|(209<<8)|0;
    writel(val, lcd_dev->reg + 0x14);
    val = (479<<11)|799;
    writel(val, lcd_dev->reg + 0x18);

    //buffer format
    val = (1<<15)|(0xb<<2)|1;
    writel(val, lcd_dev->reg + 0x20);

    //x, y
    val = 0;
    writel(val, lcd_dev->reg + 0x40);
    val = (800<<11)|480;
    writel(val, lcd_dev->reg + 0x44);

    //buffer addr
    val = lcd_dev->phys;
    writel(val, lcd_dev->reg + 0xa0);
    val = lcd_dev->phys + lcd_dev->smem_len;
    writel(val, lcd_dev->reg + 0xd0);
    val = W * BPP32 / 8;
    writel(val, lcd_dev->reg + 0x100);

    val = (1<<0);
    writel(val, lcd_dev->reg + 0x34);

    val = readl(lcd_dev->reg + 0x0);
    val |= 0x3;
    writel(val, lcd_dev->reg + 0x0);
}

void lcd_uninit(struct test_lcd *lcd_dev)
{
    unsigned int val;
    val = readl(lcd_dev->reg + 0x0);
    val &= ~0x3;
    writel(val, lcd_dev->reg + 0x0);
}

//chrdev/
ssize_t test_write (struct file *filp, const char __user *buf, size_t size, loff_t *offset)
{
    int ret;
    struct test_lcd *lcd_dev;
    lcd_dev = filp->private_data;
    //printk("test write\n");

    if(size > (lcd_dev->smem_len - *offset))
        size = lcd_dev->smem_len - *offset;

    ret = copy_from_user(lcd_dev->virt + *offset, buf, size);   
    *offset += size - ret;

    return size - ret;
}

int test_open (struct inode *inode, struct file *filp)
{
    struct test_lcd *lcd_dev;
    lcd_dev = container_of(filp->f_op, struct test_lcd, fops);
    filp->private_data = lcd_dev;

    //printk("test open\n");
    return 0;
}

int test_release (struct inode *inode, struct file *filp)
{
    struct test_lcd *lcd_dev;
    lcd_dev = filp->private_data;

    //printk("test release \n");
    return 0;
}

loff_t test_llseek (struct file *filp, loff_t offset, int whence)
{
    loff_t cur;
    struct test_lcd *lcd_dev;
    lcd_dev = filp->private_data;
    cur = filp->f_pos;
    switch(whence)
    {
        case SEEK_SET:
            cur = offset;
            break;
        case SEEK_CUR:
            cur += offset;
            break;
        case SEEK_END:
            cur += offset;
            break;
        default:
            return -EINVAL;
    }

    if(cur > lcd_dev->smem_len)
        cur = lcd_dev->smem_len;

    filp->f_pos = cur;

    return cur;
}

/platform_bus
int test_probe (struct platform_device *pdev)
{
    int ret;
    struct test_lcd *lcd_dev;
    struct resource * res;
    //int i; unsigned int *p;
    printk("driver: test probe\n");

    lcd_dev = kzalloc(sizeof(struct test_lcd), GFP_KERNEL);
    if(IS_ERR_OR_NULL(lcd_dev))
    {
        ret = -ENOMEM;
        printk("%d\n", __LINE__);
        goto ERROR_kzalloc;
    }

    res = platform_get_resource(pdev, IORESOURCE_MEM, 0);   
    if(IS_ERR_OR_NULL(res))
    {
        ret = -EBUSY;
        printk("%d\n", __LINE__);
        goto ERROR_get_mem_res;
    }

    lcd_dev->size = resource_size(res);

    lcd_dev->reg = ioremap(res->start, lcd_dev->size);
    if(IS_ERR_OR_NULL(lcd_dev->reg))
    {
        ret = -ENOMEM;
        printk("%d\n", __LINE__);
        goto ERROR_ioremap;
    }

    lcd_dev->platdata = pdev->dev.platform_data;

    lcd_dev->lcd = clk_get(&pdev->dev, "lcd");
    if(IS_ERR_OR_NULL(lcd_dev->lcd))
    {
        ret = -EBUSY;
        printk("%d\n", __LINE__);
        goto ERROR_clk_get_lcd;
    }

    clk_enable(lcd_dev->lcd);

    lcd_dev->sclk_fimd = clk_get(&pdev->dev, "sclk_fimd");
    if(IS_ERR_OR_NULL(lcd_dev->sclk_fimd))
    {
        ret = -EBUSY;
        printk("%d\n", __LINE__);
        goto ERROR_clk_get_fimd;
    }

    clk_enable(lcd_dev->sclk_fimd);

    lcd_dev->smem_len = W * H * BPP32 / 8;
    lcd_dev->virt = dma_alloc_coherent(NULL, lcd_dev->smem_len, &lcd_dev->phys, GFP_KERNEL);
    if(IS_ERR_OR_NULL(lcd_dev->virt))
    {
        ret = -ENOMEM;
        printk("%d\n", __LINE__);
        goto ERROR_dma_alloc;
    }

    lcd_dev->fops.owner = THIS_MODULE;
    lcd_dev->fops.open = test_open;
    lcd_dev->fops.release = test_release;
    lcd_dev->fops.write = test_write;
    lcd_dev->fops.llseek = test_llseek;

    lcd_dev->mdev.name = "fb";
    lcd_dev->mdev.minor = MISC_DYNAMIC_MINOR;
    lcd_dev->mdev.fops = &lcd_dev->fops;

    ret = misc_register(&lcd_dev->mdev);
    if(IS_ERR_VALUE(ret))
    {
        goto ERROR_misc;
    }

    lcd_dev->platdata->setup_gpio();
    lcd_init(lcd_dev);

/*
    p = lcd_dev->virt;
    for(i = 0; i < W * H; i++)
    {
        p[i] = 0xff;
    }
*/

    platform_set_drvdata(pdev, lcd_dev);

    return 0;

ERROR_misc:
    dma_free_coherent(NULL, W * H * BPP32 / 8, lcd_dev->virt, lcd_dev->phys);
ERROR_dma_alloc:
    clk_disable(lcd_dev->sclk_fimd);
    clk_put(lcd_dev->sclk_fimd);
ERROR_clk_get_fimd:
    clk_disable(lcd_dev->lcd);
    clk_put(lcd_dev->lcd);
ERROR_clk_get_lcd:
    iounmap(lcd_dev->reg);
ERROR_ioremap:
ERROR_get_mem_res:
    kfree(lcd_dev);
ERROR_kzalloc:
    return ret;
}

int test_remove (struct platform_device *pdev)
{
    struct test_lcd *lcd_dev;
    lcd_dev = platform_get_drvdata(pdev);
    printk("driver: test remove\n");

    lcd_uninit(lcd_dev);
    misc_deregister(&lcd_dev->mdev);
    dma_free_coherent(NULL, lcd_dev->smem_len, lcd_dev->virt, lcd_dev->phys);
    clk_disable(lcd_dev->sclk_fimd);
    clk_put(lcd_dev->sclk_fimd);
    clk_disable(lcd_dev->lcd);
    clk_put(lcd_dev->lcd);
    iounmap(lcd_dev->reg);
    kfree(lcd_dev);

    return 0;
}

struct platform_driver tdrv = {
    .probe = test_probe,
    .remove = test_remove,
    .driver = {
        .name = DRVNAME, 
        .owner = THIS_MODULE, 
    },
};

//module///
int func_init(void)
{
    printk("func_init\n");
    return platform_driver_register(&tdrv);
}

void func_exit(void)
{
    printk("func exit\n");
    platform_driver_unregister(&tdrv);
}

module_init(func_init);
module_exit(func_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("xxxx");
MODULE_VERSION("0.1");


更多推荐

[学习笔记] TINY441的LCD显示