字符驱动设计----我的学习参考资料

 我的学习参考资料:

1  驱动设计的总体框架(对于每种类型的驱动设计,最好画出模型图)
2  参考现有实例化的驱动
3  针对某一具体硬件,自己写驱动来实现
 接下来以字符驱动设计为例,也是mini2440led驱动实现。
1 字符设备驱动模型如下图所示,这是一个总体调用框架图,具体的字符设备驱动模型参照另外一篇引用的博客【字符设备驱动模型】,驱动层主要做的工作是file_operations结构体中一些关键函数的实现,包括open,read,ioctl。本例中主要实现open,ioctl。
    

2 现有驱动模型实例

  1. #include <linux/module.h>   
  2. #include <linux/kernel.h>   
  3. #include <linux/fs.h>   
  4. #include <linux/init.h>   
  5. #include <linux/delay.h>   
  6. #include <asm/irq.h>   
  7. #include <asm/arch/regs-gpio.h>   
  8. #include <asm/hardware.h>   
  9.   
  10. #define DEVICE_NAME     "leds"  /* 加载模式后,执行”cat /proc/devices”命令看到的设备名称 */   
  11. #define LED_MAJOR       231     /* 主设备号 */   
  12.   
  13. /* 应用程序执行ioctl(fd, cmd, arg)时的第2个参数 */  
  14. #define IOCTL_LED_ON    0   
  15. #define IOCTL_LED_OFF   1   
  16.   
  17. /* 用来指定LED所用的GPIO引脚 */  
  18. static unsigned long led_table [] = {  
  19.     S3C2410_GPB5,  
  20.     S3C2410_GPB6,  
  21.     S3C2410_GPB7,  
  22.     S3C2410_GPB8,  
  23. };  
  24.   
  25. /* 用来指定GPIO引脚的功能:输出 */  
  26. static unsigned int led_cfg_table [] = {  
  27.     S3C2410_GPB5_OUTP,  
  28.     S3C2410_GPB6_OUTP,  
  29.     S3C2410_GPB7_OUTP,  
  30.     S3C2410_GPB8_OUTP,  
  31. };  
  32.   
  33. /* 应用程序对设备文件/dev/leds执行open(...)时, 
  34.  * 就会调用s3c24xx_leds_open函数 
  35.  */  
  36. static int s3c24xx_leds_open(struct inode *inode, struct file *file)  
  37. {  
  38.     int i;  
  39.       
  40.     for (i = 0; i < 4; i++) {  
  41.         // 设置GPIO引脚的功能:本驱动中LED所涉及的GPIO引脚设为输出功能   
  42.         s3c2410_gpio_cfgpin(led_table[i], led_cfg_table[i]);  
  43.     }  
  44.     return 0;  
  45. }  
  46.   
  47. /* 应用程序对设备文件/dev/leds执行ioclt(...)时, 
  48.  * 就会调用s3c24xx_leds_ioctl函数 
  49.  */  
  50. static int s3c24xx_leds_ioctl(  
  51.     struct inode *inode,   
  52.     struct file *file,   
  53.     unsigned int cmd,   
  54.     unsigned long arg)  
  55. {  
  56.     if (arg > 4) {  
  57.         return -EINVAL;  
  58.     }  
  59.       
  60.     switch(cmd) {  
  61.     case IOCTL_LED_ON:  
  62.         // 设置指定引脚的输出电平为0   
  63.         s3c2410_gpio_setpin(led_table[arg], 0);  
  64.         return 0;  
  65.   
  66.     case IOCTL_LED_OFF:  
  67.         // 设置指定引脚的输出电平为1   
  68.         s3c2410_gpio_setpin(led_table[arg], 1);  
  69.         return 0;  
  70.   
  71.     default:  
  72.         return -EINVAL;  
  73.     }  
  74. }  
  75.   
  76. /* 这个结构是字符设备驱动程序的核心 
  77.  * 当应用程序操作设备文件时所调用的open、read、write等函数, 
  78.  * 最终会调用这个结构中指定的对应函数 
  79.  */  
  80. static struct file_operations s3c24xx_leds_fops = {  
  81.     .owner  =   THIS_MODULE,    /* 这是一个宏,推向编译模块时自动创建的__this_module变量 */  
  82.     .open   =   s3c24xx_leds_open,       
  83.     .ioctl  =   s3c24xx_leds_ioctl,  
  84. };  
  85.   
  86. /* 
  87.  * 执行“insmod s3c24xx_leds.ko”命令时就会调用这个函数 
  88.  */  
  89. static int __init s3c24xx_leds_init(void)  
  90. {  
  91.     int ret;  
  92.   
  93.     /* 注册字符设备驱动程序 
  94.      * 参数为主设备号、设备名字、file_operations结构; 
  95.      * 这样,主设备号就和具体的file_operations结构联系起来了, 
  96.      * 操作主设备为LED_MAJOR的设备文件时,就会调用s3c24xx_leds_fops中的相关成员函数 
  97.      * LED_MAJOR可以设为0,表示由内核自动分配主设备号 
  98.      */  
  99.     ret = register_chrdev(LED_MAJOR, DEVICE_NAME, &s3c24xx_leds_fops);  
  100.     if (ret < 0) {  
  101.       printk(DEVICE_NAME " can't register major number\n");  
  102.       return ret;  
  103.     }  
  104.       
  105.     printk(DEVICE_NAME " initialized\n");  
  106.     return 0;  
  107. }  
  108.   
  109. /* 
  110.  * 执行”rmmod s3c24xx_leds.ko”命令时就会调用这个函数  
  111.  */  
  112. static void __exit s3c24xx_leds_exit(void)  
  113. {  
  114.     /* 卸载驱动程序 */  
  115.     unregister_chrdev(LED_MAJOR, DEVICE_NAME);  
  116. }  
  117.   
  118. /* 这两行指定驱动程序的初始化函数和卸载函数 */  
  119. module_init(s3c24xx_leds_init);  
  120. module_exit(s3c24xx_leds_exit);  
  121.   
  122. /* 描述驱动程序的一些信息,不是必须的 */  
  123. MODULE_AUTHOR("http://my.csdn.net/czxyhll.");        // 驱动程序的作者  
  124. MODULE_DESCRIPTION("S3C2410/S3C2440 LED Driver");   // 一些描述信息   
  125. MODULE_LICENSE("GPL");                              // 遵循的协议  
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <asm/irq.h>
#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h>

#define DEVICE_NAME     "leds"  /* 加载模式后,执行”cat /proc/devices”命令看到的设备名称 */
#define LED_MAJOR       231     /* 主设备号 */

/* 应用程序执行ioctl(fd, cmd, arg)时的第2个参数 */
#define IOCTL_LED_ON    0
#define IOCTL_LED_OFF   1

/* 用来指定LED所用的GPIO引脚 */
static unsigned long led_table [] = {
    S3C2410_GPB5,
    S3C2410_GPB6,
    S3C2410_GPB7,
    S3C2410_GPB8,
};

/* 用来指定GPIO引脚的功能:输出 */
static unsigned int led_cfg_table [] = {
    S3C2410_GPB5_OUTP,
    S3C2410_GPB6_OUTP,
    S3C2410_GPB7_OUTP,
    S3C2410_GPB8_OUTP,
};

/* 应用程序对设备文件/dev/leds执行open(...)时,
 * 就会调用s3c24xx_leds_open函数
 */
static int s3c24xx_leds_open(struct inode *inode, struct file *file)
{
    int i;
    
    for (i = 0; i < 4; i++) {
        // 设置GPIO引脚的功能:本驱动中LED所涉及的GPIO引脚设为输出功能
        s3c2410_gpio_cfgpin(led_table[i], led_cfg_table[i]);
    }
    return 0;
}

/* 应用程序对设备文件/dev/leds执行ioclt(...)时,
 * 就会调用s3c24xx_leds_ioctl函数
 */
static int s3c24xx_leds_ioctl(
    struct inode *inode, 
    struct file *file, 
    unsigned int cmd, 
    unsigned long arg)
{
    if (arg > 4) {
        return -EINVAL;
    }
    
    switch(cmd) {
    case IOCTL_LED_ON:
        // 设置指定引脚的输出电平为0
        s3c2410_gpio_setpin(led_table[arg], 0);
        return 0;

    case IOCTL_LED_OFF:
        // 设置指定引脚的输出电平为1
        s3c2410_gpio_setpin(led_table[arg], 1);
        return 0;

    default:
        return -EINVAL;
    }
}

/* 这个结构是字符设备驱动程序的核心
 * 当应用程序操作设备文件时所调用的open、read、write等函数,
 * 最终会调用这个结构中指定的对应函数
 */
static struct file_operations s3c24xx_leds_fops = {
    .owner  =   THIS_MODULE,    /* 这是一个宏,推向编译模块时自动创建的__this_module变量 */
    .open   =   s3c24xx_leds_open,     
    .ioctl  =   s3c24xx_leds_ioctl,
};

/*
 * 执行“insmod s3c24xx_leds.ko”命令时就会调用这个函数
 */
static int __init s3c24xx_leds_init(void)
{
    int ret;

    /* 注册字符设备驱动程序
     * 参数为主设备号、设备名字、file_operations结构;
     * 这样,主设备号就和具体的file_operations结构联系起来了,
     * 操作主设备为LED_MAJOR的设备文件时,就会调用s3c24xx_leds_fops中的相关成员函数
     * LED_MAJOR可以设为0,表示由内核自动分配主设备号
     */
    ret = register_chrdev(LED_MAJOR, DEVICE_NAME, &s3c24xx_leds_fops);
    if (ret < 0) {
      printk(DEVICE_NAME " can't register major number\n");
      return ret;
    }
    
    printk(DEVICE_NAME " initialized\n");
    return 0;
}

/*
 * 执行”rmmod s3c24xx_leds.ko”命令时就会调用这个函数 
 */
static void __exit s3c24xx_leds_exit(void)
{
    /* 卸载驱动程序 */
    unregister_chrdev(LED_MAJOR, DEVICE_NAME);
}

/* 这两行指定驱动程序的初始化函数和卸载函数 */
module_init(s3c24xx_leds_init);
module_exit(s3c24xx_leds_exit);

/* 描述驱动程序的一些信息,不是必须的 */
MODULE_AUTHOR("http://my.csdn.net/czxyhll.");        // 驱动程序的作者
MODULE_DESCRIPTION("S3C2410/S3C2440 LED Driver");   // 一些描述信息
MODULE_LICENSE("GPL");                              // 遵循的协议

现在就对字符设备驱动进行分析:
1 在open函数里有s3c2410_gpio_cfgpin(led_table[i], led_cfg_table[i]),此函数实现相应GPIO的功能,包括输入输出及其他功能。这个函数看似简单,如果想彻底弄明白还是有一定的难度,应为此函数的实现涉及到I/O内存空间,物理地址PA与虚拟地址VA的映射,GPIO寄存器的方面的知识。先附上其函数原型。

  1. void s3c2410_gpio_cfgpin(unsigned int pin, unsigned int function)  
  2. {  
  3.     void __iomem *base = S3C24XX_GPIO_BASE(pin);  
  4.     unsigned long mask;  
  5.     unsigned long con;  
  6.     unsigned long flags;  
  7.   
  8.     if (pin < S3C2410_GPIO_BANKB) {  
  9.         mask = 1 << S3C2410_GPIO_OFFSET(pin);  
  10.     } else {  
  11.         mask = 3 << S3C2410_GPIO_OFFSET(pin)*2;  
  12.     }  
  13.   
  14.     switch (function) {  
  15.     case S3C2410_GPIO_LEAVE:  
  16.         mask = 0;  
  17.         function = 0;  
  18.         break;  
  19.   
  20.     case S3C2410_GPIO_INPUT:  
  21.     case S3C2410_GPIO_OUTPUT:  
  22.     case S3C2410_GPIO_SFN2:  
  23.     case S3C2410_GPIO_SFN3:  
  24.         if (pin < S3C2410_GPIO_BANKB) {  
  25.             function -= 1;  
  26.             function &= 1;  
  27.             function <<= S3C2410_GPIO_OFFSET(pin);  
  28.         } else {  
  29.             function &= 3;  
  30.             function <<= S3C2410_GPIO_OFFSET(pin)*2;  
  31.         }  
  32.     }  
  33.   
  34.     /* modify the specified register wwith IRQs off */  
  35.   
  36.     local_irq_save(flags);  
  37.   
  38.     con  = __raw_readl( );  
  39.     con &= ~mask;  
  40.     con |= function;  
  41.   
  42.     __raw_writel(con, base + 0x00);  
  43.   
  44.     local_irq_restore(flags);  
  45. }  
void s3c2410_gpio_cfgpin(unsigned int pin, unsigned int function)
{
	void __iomem *base = S3C24XX_GPIO_BASE(pin);
	unsigned long mask;
	unsigned long con;
	unsigned long flags;

	if (pin < S3C2410_GPIO_BANKB) {
		mask = 1 << S3C2410_GPIO_OFFSET(pin);
	} else {
		mask = 3 << S3C2410_GPIO_OFFSET(pin)*2;
	}

	switch (function) {
	case S3C2410_GPIO_LEAVE:
		mask = 0;
		function = 0;
		break;

	case S3C2410_GPIO_INPUT:
	case S3C2410_GPIO_OUTPUT:
	case S3C2410_GPIO_SFN2:
	case S3C2410_GPIO_SFN3:
		if (pin < S3C2410_GPIO_BANKB) {
			function -= 1;
			function &= 1;
			function <<= S3C2410_GPIO_OFFSET(pin);
		} else {
			function &= 3;
			function <<= S3C2410_GPIO_OFFSET(pin)*2;
		}
	}

	/* modify the specified register wwith IRQs off */

	local_irq_save(flags);

	con  = __raw_readl( );
	con &= ~mask;
	con |= function;

	__raw_writel(con, base + 0x00);

	local_irq_restore(flags);
}


此函数里比较关键的两点:1 传递过来的实参值 led_table[i], led_cfg_table[i]。这里讨论当i=0的情况,即led_table[0]=S3C2410_GPB5,led_cfg_table[0]=S3C2410_GPB5_OUTP的值。
2 *base = S3C24XX_GPIO_BASE(pin),*base到底等于多少?
3 GPIO寄存器的读写。

先讨论第1点,S3C2410_GPB5的值,根据以下宏定义  #define S3C2410_GPB5   S3C2410_GPIONO(S3C2410_GPIO_BANKB, 5)
#define S3C2410_GPIONO(bank,offset) ((bank) + (offset))
#define S3C2410_GPIO_BANKB   (32*1)
可获得S3C2410_GPB5=32*1+5=37=0b100101=pin  //取i=0时
S3C2410_GPB5_OUTP的值,根据以下宏定义    #define S3C2410_GPB5_OUTP    (0x01 << 10)
可获知 S3C2410_GPB5_OUTP= (0x01 << 10)=function   //对应GPB5[11:10],参考芯片手册

现在来讨论第2点,*base的值,这个比较麻烦。
#define S3C24XX_GPIO_BASE(x)  S3C2410_GPIO_BASE(x)
#define S3C2410_GPIO_BASE(pin)   ((((pin) & ~31) >> 1) + S3C24XX_VA_GPIO)
#define S3C24XX_VA_GPIO   ((S3C24XX_PA_GPIO - S3C24XX_PA_UART) + S3C24XX_VA_UART)
#define S3C24XX_PA_GPIO     S3C2410_PA_GPIO
#define S3C2410_PA_GPIO   (0x56000000)

#define S3C24XX_PA_UART     S3C2410_PA_UART
#define S3C2410_PA_UART   (0x50000000)

#define S3C24XX_VA_UART   S3C_VA_UART
#define S3C_VA_UART S3C_ADDR(0x01000000)/* UART */
#define S3C_ADDR(x) (S3C_ADDR_BASE + (x))
#define S3C_ADDR_BASE (0xF4000000)   // I/O内存地址基地址,为什么从0xF4000000开始,

自底向上进行分析,S3C24XX_VA_UART=(0xF4000000+0x01000000)=0xF5000000   //Uart的虚拟起始基地址,这个值也可以是其他数值,只要映射时不冲突就行,分配时尽量往高端,为了使动态映射区足够大供内核使用。
S3C2410_PA_UART=0x50000000   //uart物理地址
S3C24XX_VA_GPIO=(0x56000000-0x50000000)+0xF5000000=0xFB000000      //GPIO的虚拟起始基址,看到这儿有什么想法,最直接的一个想法是对于I/0端口映射是线性的。参照下图
 *base=S3C2410_GPIO_BASE(37)=((((37) & ~31) >> 1) + 0xFB000000 =0b10000+0xFB000000=0xFB000010  //GPBCON对于的虚拟地址
  

((((pin) & ~31) >> 1) 的含义很明确,就是确定是哪个GPXCON寄存器的地址(X=A,B...H)。

  1. 接下来讨论GPIO寄存器的读写,代码如下。  
  2.     local_irq_save(flags);   //关中断   
  3.     con  = __raw_readl( );      
  4.     con &= ~mask;  
  5.     con |= function;  
  6.     __raw_writel(con, base + 0x00);   //设置相应寄存器   
  7.     local_irq_restore(flags);       //开中断  
接下来讨论GPIO寄存器的读写,代码如下。
	local_irq_save(flags);   //关中断
	con  = __raw_readl( );    
	con &= ~mask;
	con |= function;
	__raw_writel(con, base + 0x00);   //设置相应寄存器
	local_irq_restore(flags);       //开中断

这几条语句就是设置GPBCON寄存器为function功能,这里设置GPB5[11:10]引脚为输出功能

接下来讨论第二个函数 s3c2410_gpio_setpin(led_table[arg], 0)
函数原型如下:

  1. void s3c2410_gpio_setpin(unsigned int pin, unsigned int to)  
  2. {  
  3.     void __iomem *base = S3C24XX_GPIO_BASE(pin);  
  4.     unsigned long offs = S3C2410_GPIO_OFFSET(pin);  
  5.     unsigned long flags;  
  6.     unsigned long dat;  
  7.   
  8.     local_irq_save(flags);  
  9.   
  10.     dat = __raw_readl(base + 0x04);  
  11.     dat &= ~(1 << offs);  
  12.     dat |= to << offs;  
  13.     __raw_writel(dat, base + 0x04);  
  14.   
  15.     local_irq_restore(flags);  
  16. }  
void s3c2410_gpio_setpin(unsigned int pin, unsigned int to)
{
	void __iomem *base = S3C24XX_GPIO_BASE(pin);
	unsigned long offs = S3C2410_GPIO_OFFSET(pin);
	unsigned long flags;
	unsigned long dat;

	local_irq_save(flags);

	dat = __raw_readl(base + 0x04);
	dat &= ~(1 << offs);
	dat |= to << offs;
	__raw_writel(dat, base + 0x04);

	local_irq_restore(flags);
}

EXPORT_SYMBOL(s3c2410_gpio_setpin);
S3C24XX_GPIO_BASE(pin)  //上文中已经分析了其用途,主要是获取每组寄存器的基地址。例如GPBCON
S3C2410_GPIO_OFFSET(pin)  //寄存器组内偏移地址。例如GPB5
local_irq_save(flags);   //关闭中断
dat = __raw_readl(base + 0x04);    //读寄存器内容,base+0x04表示寄存器GPXDAT (X=A,B,C...F)
dat &= ~(1 << offs);   // 清空其要设置的位,保持其他位不变
dat |= to << offs;    //  
__raw_writel(dat, base + 0x04);  //  设置寄存器GPXDAT
local_irq_restore(flags);   //开中断
刚开始感觉mini2440led驱动设计比较难,很多地方不懂,待仔细分析后,主要难道就是上面重点分析的两个函数。这里有几个函数没有展开进行分析,像__raw_readl() ,__raw_writel(),local_irq_save(),local_irq_restore()。这些函数大家可以展开分析。好了,写的手有点发酸了,该休息了。

  1. <PRE></PRE>  
  2. <PRE></PRE>  
  3. <PRE></PRE>  
  4. <PRE></PRE>  
  5. <PRE></PRE>  
  6. <PRE></PRE>  
  7. <PRE></PRE>  
  8. <PRE></PRE>  
  9. <PRE></PRE>  
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值