开发板为友善之臂的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;
}
- 结构体 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);
}
- 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");