===========================================
本文系作者原创, 欢迎大家转载!
转载请注明出处:netwalker.blog.chinaunix.net
===========================================
硬件平台:
MainBoard:OK6410
CPU: S3C6410
RAM: 256M
FLASH:1G K9GAG08U0D
Kernel:Linux2.6.28
BootLoader:Uboot1.1.6
11. LINUX下led驱动解析
Linux2.6.28
中的LED驱动位于drivers/char/s3c6410_leds.c。
- static int __init s3c6410_leds_init(void)
- {
- int ret = 0;
- unsigned long tmp;
- dev_t devno;
-
- printk(KERN_NOTICE "enter s3c6410_leds_init\n");
- devno = MKDEV(LED_MAJOR,0);
-
- //申请设备号资源,对应到内核的HASH表chrdevs
- ret = register_chrdev_region(devno,1,DEVICE_NAME);
- if(ret<0)
- {
- printk(KERN_NOTICE "can not register led device");
- return ret;
- }
-
- //初始化字符设备cdev_leds,并安装操作函数
- cdev_init(&cdev_leds,&s3c6410_leds_fops);
- cdev_leds.owner = THIS_MODULE;
-
- //通告内核,这样就可以在/proc/devices下看到231 leds设备了
- ret =cdev_add(&cdev_leds,devno,1);
- if(ret)
- {
- printk(KERN_NOTICE "can not add leds device");
- return ret;
- }
-
- //创建/sys下的my_class类文件
- my_class = class_create(THIS_MODULE,"my_class");
- if(IS_ERR(my_class))
- {
- printk("Err: Failed in creating class\n");
- return -1;
- }
-
- // 创建/dev/leds文件,提供用户空间访问的接口
- device_create(my_class,NULL,MKDEV(LED_MAJOR,0),NULL,DEVICE_NAME);
- //与lowlevel_init.S初始化类似的操作,但是这里的地址都是虚拟地址
- //gpm0-3 pull up
- tmp = __raw_readl(S3C64XX_GPMPUD);
- tmp &= (~0xFF);
- tmp |= 0xaa;
- __raw_writel(tmp,S3C64XX_GPMPUD);
-
- //gpm0-3 output mode
- tmp = __raw_readl(S3C64XX_GPMCON);
- tmp &= (~0xFFFF);
- tmp |= 0x1111;
- __raw_writel(tmp,S3C64XX_GPMCON);
-
- //gpm0-3 output 0
- tmp = __raw_readl(S3C64XX_GPMDAT);
- tmp |= 0x10;
- __raw_writel(tmp,S3C64XX_GPMDAT);
-
- printk(DEVICE_NAME " initialized\n");
-
- return 0;
- }
相关的寄存器虚地址定义在arch/arm/plat-s3c64xx/include/plat/gpio-bank-m.h
- #define S3C64XX_GPMCON (S3C64XX_GPM_BASE + 0x00)
- #define S3C64XX_GPMDAT (S3C64XX_GPM_BASE + 0x04)
- #define S3C64XX_GPMPUD (S3C64XX_GPM_BASE + 0x08)
GPIO M端口的基地址定义在arch/arm/plat-s3c64xx/include/plat/regs-gpio.h
- #define S3C64XX_GPM_BASE (S3C64XX_VA_GPIO + 0x0820)
GPIO的虚地址定义在arch/arm/mach-s3c6400/include/mach/map.h
- #define S3C64XX_VA_GPIO S3C_ADDR(0x00500000)
GPIO的虚地址是有全局虚地址S3C_ADDR_BASE计算出来的,void __iomem __force *作用是强制转化为地址。arch/arm/plat-s3c/include/plat/map.h
- #define S3C_ADDR_BASE (0xF4000000)
- #ifndef __ASSEMBLY__
- #define S3C_ADDR(x) ((void __iomem __force *)S3C_ADDR_BASE + (x))
- #else
- #define S3C_ADDR(x) (S3C_ADDR_BASE + (x))
- #endif
由此可以得到GPM寄存器对应的虚地址分别为:
- S3C64XX_GPMCON 0xF4500820
- S3C64XX_GPMDAT 0xF4500824
- S3C64XX_GPMPUD 0xF4500828
驱动中的接口通过ioctl来提供,
- static struct file_operations s3c6410_leds_fops = {
- .owner = THIS_MODULE,
- .ioctl = s3c6410_leds_ioctl,
- };
- static int s3c6410_leds_ioctl( struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
- {
- unsigned long tmp;
- switch(cmd)
- {
- case 0: //熄灭
- case 1:// 点亮
- if(arg > 4)
- return -EINVAL;
- tmp = __raw_readl(S3C64XX_GPMDAT);
- if(cmd) //注意cmd为1时点亮动作,对应的位置0
- tmp &= (~(1<<arg));
- else
- tmp |= (1<<arg);
-
- __raw_writel(tmp,S3C64XX_GPMDAT);
- return 0;
- default:
- return -EINVAL;
- }
- }
尽管该驱动在编译进内核时,运行led或者led-player可以正常工作,但是当你把选项CONFIG_TE6410_LEDS=y改为CONFIG_TE6410_LEDS=m时,问题出现了。
进行insmode s3c6410_leds.ko没有问题,而进行rmmod s3c6410_leds时则会提示:
rmmod: chdir(/lib/modules): No such file or directory,关于此问题网上有很多解决方法,比如http://www.cnblogs.com/junmao/articles/1991495.html。
真正的问题不在于卸载,而在于卸载后再次insmod时提示加载失败,分析源码发现,驱动的卸载载函数存
在问题:
- static void __exit s3c6410_leds_exit(void)
- {
- /* added by lli_njupt */
- device_destroy(my_class, MKDEV(LED_MAJOR,0));
- class_destroy(my_class);
- cdev_del(&cdev_leds);
- unregister_chrdev_region(MKDEV(LED_MAJOR,0),1);
- printk(KERN_NOTICE "s3c2440_leds_exit\n");
- }
注意红色部分由笔者添加,加载失败的原因就在于没有从/dev/下注销leds,另外/sys下的class文件也需要注销。如此天下太平了。