驱动开发之ADC驱动与通用设备树:
通用设备树:
让驱动去操作设备树,可以选择platform架构,也可以不选择platform架构。
vi -t /arch/arm/boot/dts/exynos4412-fs4412中:
566 ofled{
567 led2 = <&gpx2 7 0>;
568 led1 = <&gpx1 0 0>;
569 led3-4 = <&gpf3 4 0>,<&gpf3 5 0>;
570 };
接口:
1 struct device_node *of_find_node_by_path(const char *path)
2 功能:查找设备树节点信息(必须包含路径)
3 返回值:如果在设备树中找到了指定节点,返回值就指向了找到的节点。
1 static inline int of_get_named_gpio(struct device_node *np,const char *propname, int index)
2 参数1:of_find_node_by_path的返回值
3 参数2:属性名称
4 参数3:属性内容的索引值
5 返回值:引脚编号(系统动态分配的一个整数值,让这个整数值和实际的物理引脚建立关系)
----->此时内核还不认识这些引脚编号么
-------------->
1 int gpio_request(unsigned gpio, const char *label)
2 功能:注册引脚编号
3 参数1:引脚编号
4 参数2:引脚的别名
1 static inline int gpio_direction_output(unsigned gpio, int value)
2 功能:设置引脚为输出模式
3 参数1:引脚编号
4 参数2:指定的是数据寄存器中的默认值
1 static inline void gpio_set_value(unsigned gpio, int value)
2 功能:对指定引脚中的数据寄存器设置值
3 参数1:引脚编号
4 参数2:数据值
1 static inline int gpio_to_irq(unsigned int gpio)
2 功能:获取虚拟中断号
3 参数:gpio引脚编号
参考代码LED:
1 #include <linux/init.h> 2 #include <linux/module.h> 3 #include <linux/fs.h> 4 #include <linux/device.h> 5 #include <asm/gpio.h> 6 #include <linux/of.h> 7 #include <linux/of_gpio.h> 8 9 10 int major; 11 struct class *cls; 12 struct device *devs; 13 struct device_node *np; 14 15 int gpx2_7; 16 int gpx1_0; 17 int gpf3_4; 18 int gpf3_5; 19 20 int ofled_open(struct inode *inode,struct file *filp) 21 { 22 gpio_set_value(gpx2_7,1); 23 gpio_set_value(gpx1_0,1); 24 gpio_set_value(gpf3_4,1); 25 gpio_set_value(gpf3_5,1); 26 return 0; 27 } 28 29 int ofled_close(struct inode *inode,struct file *filp) 30 { 31 gpio_set_value(gpx2_7,0); 32 gpio_set_value(gpx1_0,0); 33 gpio_set_value(gpf3_4,0); 34 gpio_set_value(gpf3_5,0); 35 return 0; 36 } 37 38 struct file_operations fops = { 39 .owner = THIS_MODULE, 40 .open = ofled_open, 41 .release = ofled_close, 42 }; 43 44 int led_init(void) 45 { 46 major = register_chrdev(0,"ofled",&fops); 47 cls = class_create(THIS_MODULE,"ofled"); 48 devs = device_create(cls,NULL,MKDEV(major,0),NULL,"ofled"); 49 50 //查找节点名称 51 np = of_find_node_by_path("/ofled"); 52 53 //获取gpio引脚编号 54 gpx2_7 = of_get_named_gpio(np,"led2",0); 55 gpx1_0 = of_get_named_gpio(np,"led1",0); 56 gpf3_4 = of_get_named_gpio(np,"led3-4",0); 57 gpf3_5 = of_get_named_gpio(np,"led3-4",1); 58 59 //注册引脚编号 60 gpio_request(gpx2_7,NULL); 61 gpio_request(gpx1_0,NULL); 62 gpio_request(gpf3_4,NULL); 63 gpio_request(gpf3_5,NULL); 64 65 //设置输出模式 66 gpio_direction_output(gpx2_7,1); 67 gpio_direction_output(gpx1_0,1); 68 gpio_direction_output(gpf3_4,1); 69 gpio_direction_output(gpf3_5,1); 70 return 0; 71 } 72 module_init(led_init); 73 74 void led_exit(void) 75 { 76 return; 77 } 78 module_exit(led_exit); 79 80 MODULE_LICENSE("GPL");
1 #include <stdio.h> 2 #include <sys/types.h> 3 #include <sys/stat.h> 4 #include <fcntl.h> 5 6 int main(int argc, const char *argv[]) 7 { 8 int fd; 9 10 fd = open("/dev/ofled",O_RDWR); 11 12 sleep(3); 13 14 close(fd); 15 return 0; 16 }
参考代码KEY:
1 #include <linux/init.h> 2 #include <linux/module.h> 3 #include <linux/fs.h> 4 #include <linux/device.h> 5 #include <linux/of.h> 6 #include <linux/of_gpio.h> 7 #include <asm/gpio.h> 8 #include <linux/interrupt.h> 9 #include <linux/irqreturn.h> 10 #include <asm/uaccess.h> 11 #include <linux/sched.h> 12 13 struct device_node *np; 14 15 int major; 16 struct class *cls; 17 struct device *devs; 18 19 int gpx1_1; 20 int gpx1_2; 21 int gpx3_2; 22 23 int irqno1; 24 int irqno2; 25 int irqno3; 26 27 int key; 28 29 wait_queue_head_t keyq;//创建等待队列头 30 int flag = 0; 31 32 irqreturn_t fs4412_ofkey_handler(int irqno,void *id) //中断处理函数 33 { 34 if(irqno == irqno1)//判断哪个按键被按下执行相应的操作 35 key = 1; 36 37 if(irqno == irqno2) 38 key = 2; 39 40 if(irqno == irqno3) 41 key = 3; 42 43 wake_up_interruptible(&keyq);//唤醒 44 flag = 1; 45 return IRQ_HANDLED; 46 } 47 48 int fs4412_ofkey_open(struct inode *inode,struct file *filp) 49 { 50 return 0; 51 } 52 53 ssize_t fs4412_ofkey_read(struct file *filp,char __user *ubuf,size_t size,loff_t *off)//给应用层提供接口 54 { 55 int ret; 56 57 wait_event_interruptible(keyq,flag != 0);//阻塞等待,中断处理函数处理完成唤醒阻塞 58 ret = copy_to_user(ubuf,&key,sizeof(key));//将数据拷贝到应用层 59 60 flag = 0;//方便后面操作阻塞 61 return sizeof(key); 62 } 63 //文件操作结构体 64 struct file_operations fops = { 65 .owner = THIS_MODULE, 66 .open = fs4412_ofkey_open, 67 .read = fs4412_ofkey_read, 68 }; 69 70 int fs4412_ofkey_init(void) 71 { 72 int ret; 73 major = register_chrdev(0,"ofkey",&fops);//注册设备编号 74 cls = class_create(THIS_MODULE,"ofkey");//创建设备类(名为ofkey的目录) 75 devs = device_create(cls,NULL,MKDEV(major,0),NULL,"ofkey");//创建设备文件(链接文件) 76 77 np = of_find_node_by_path("/fskey");//查找设备树节点信息 78 79 gpx1_1 = of_get_named_gpio(np,"key1",0);//获取引脚编号,0表示索引值,设备树中led1 = <>(0),<>(1),<>(3)... 80 gpx1_2 = of_get_named_gpio(np,"key2",0); 81 gpx3_2 = of_get_named_gpio(np,"key3",0); 82 83 gpio_request(gpx1_1,NULL);//注册引脚编号,取别名不取默认为NULL 84 gpio_request(gpx1_2,NULL); 85 gpio_request(gpx3_2,NULL); 86 87 gpio_direction_input(gpx1_1);//设置引脚为输出模式 88 gpio_direction_input(gpx1_2); 89 gpio_direction_input(gpx3_2); 90 91 irqno1 = gpio_to_irq(gpx1_1);//获取中断号 92 irqno2 = gpio_to_irq(gpx1_2); 93 irqno3 = gpio_to_irq(gpx3_2); 94 95 ret = request_irq(irqno1,fs4412_ofkey_handler,IRQF_TRIGGER_FALLING,"key1",NULL);//注册中断 96 ret = request_irq(irqno2,fs4412_ofkey_handler,IRQF_TRIGGER_FALLING,"key2",NULL);//IRQF_TRIGGER_FALLING中断触发方式为下降沿触发 97 ret = request_irq(irqno3,fs4412_ofkey_handler,IRQF_TRIGGER_FALLING,"key3",NULL); 98 99 init_waitqueue_head(&keyq);//初始化等待队列头 100 return 0; 101 } 102 module_init(fs4412_ofkey_init);//模块加载 103 104 void fs4412_ofkey_exit(void) 105 { 106 return ; 107 } 108 module_exit(fs4412_ofkey_exit);//模块卸载 109 MODULE_LICENSE("GPL");//模块声明
1 #include <stdio.h> 2 #include <sys/types.h> 3 #include <sys/stat.h> 4 #include <fcntl.h> 5 6 int main(int argc, const char *argv[]) 7 { 8 int fd; 9 10 fd = open("/dev/ofkey",O_RDWR); 11 12 int key; 13 while(1) 14 { 15 read(fd,&key,sizeof(key)); 16 printf("key = %d\n",key); 17 } 18 close(fd); 19 return 0; 20 }
ADC底层驱动:
添加设备树:
1、查看原理图
XadcAIN3 不是中断引脚,只是指定了ad转换通道为3
2、查看芯片手册第十章
INTG10 [3] ADC General ADC
中断组合器的第十组,第三位控制了ADC中断
3、vi Documetation/devicetree/bindings/arm/samsung/interrupt-combiner.txt
4、需要使用寄存器
ADCCON:
[0] 代表ad转换的起始位,每次转换成功后都会被清零。每次转换数据成功后都会自动产生一次中断
[13,6] 设置的预分频值
[14] 使能预分频值
[16] 设置转换精度
ADCDAT:用来存放转换后的数据(驱动需要读取这里的内容)
CLRINTADC:中断清除寄存器,被写入任意值。
ADCMUX:设置AD转换通道为3
------>
1 fs4412-adc{
2 compatible = ",";
3 interrupt-parent = <&combiner>; vi exynos4.dtsi文件中找到combiner:
4 interrupts = <10 3>;
5 reg = <126c0000 0x20>;
6 };
1 获取指定(IORESOURCEB表示中断)资源类型:
2 platform_get_resource(pdev,IORESOURCE_IRQ,0);
参考代码ADC:
1 #include <linux/module.h> 2 #include <linux/fs.h> 3 #include <linux/platform_device.h> 4 #include <linux/device.h> 5 #include <asm/io.h> 6 #include <asm/uaccess.h> 7 #include <linux/interrupt.h> 8 #include <linux/sched.h> 9 #include <linux/irqreturn.h> 10 11 12 int major; 13 struct class *cls; 14 struct device *devs; 15 16 struct resource *res_mem; 17 struct resource *res_irq; 18 19 void __iomem *adc_base;//必须是void __iomem类型因为偏移地址不一样 20 wait_queue_head_t adcq; 21 int flag = 0; 22 23 irqreturn_t fs4412_adc_handler(int irqno,void *id) 24 { 25 //开始必须要清中断,内核没有进行清中断我们必须在驱动中手动清中断 26 writel(0,adc_base + 0x18); 27 wake_up_interruptible(&adcq); 28 flag = 1; 29 return IRQ_HANDLED; 30 } 31 32 struct of_device_id fs4412_adc_match_tbl[] = { 33 { 34 .compatible = "fs4412,adc", 35 }, 36 {}, 37 }; 38 39 int fs4412_adc_open(struct inode *inode,struct file *filp) 40 { 41 return 0; 42 } 43 44 ssize_t fs4412_adc_read(struct file *filp,char __user *ubuf,size_t size,loff_t *off) 45 { 46 int ret; 47 int data; 48 //开始转换数据 49 writel(readl(adc_base) | 1 << 0,adc_base);//转换完成后才会出现中断 50 51 //阻塞 52 wait_event_interruptible(adcq,flag != 0); 53 54 //只有转化完成且中断唤醒阻塞后才能读取数据,直接不能读取数据 55 data = readl(adc_base + 0x0c) & 0xfff; 56 ret = copy_to_user(ubuf,&data,sizeof(data)); 57 flag = 0; 58 return sizeof(data); 59 } 60 61 struct file_operations fops = { 62 .owner = THIS_MODULE, 63 .open = fs4412_adc_open, 64 .read = fs4412_adc_read, 65 }; 66 67 int fs4412_adc_probe(struct platform_device *pdev) 68 { 69 int ret; 70 printk("match ok\n"); 71 72 major = register_chrdev(0,"adc",&fops); 73 cls = class_create(THIS_MODULE,"adc"); 74 devs = device_create(cls,NULL,MKDEV(major,0),NULL,"adc"); 75 76 res_irq = platform_get_resource(pdev,IORESOURCE_IRQ,0);//获取指定(中断)资源类型 77 ret = request_irq(res_irq->start,fs4412_adc_handler,0,"adc",NULL); 78 79 80 res_mem = platform_get_resource(pdev,IORESOURCE_MEM,0);//获取指定(内存)资源类型 81 adc_base = ioremap(res_mem->start,0x20);//将物理地址映射成虚拟地址 82 83 writel(255 << 6 | 1 << 14 | 1 << 16,adc_base); 84 writel(3,adc_base + 0x1c); 85 86 init_waitqueue_head(&adcq); 87 return 0; 88 } 89 90 int fs4412_adc_remove(struct platform_device *pdev) 91 { 92 return 0; 93 } 94 95 struct platform_driver pdrv = { 96 .driver = { 97 .name = "adc", 98 .of_match_table = fs4412_adc_match_tbl, 99 }, 100 101 .probe = fs4412_adc_probe, 102 .remove = fs4412_adc_remove, 103 }; 104 module_platform_driver(pdrv); 105 MODULE_LICENSE("GPL");
1 #include <stdio.h> 2 #include <sys/types.h> 3 #include <sys/stat.h> 4 #include <fcntl.h> 5 6 int main(int argc, const char *argv[]) 7 { 8 int fd; 9 10 fd = open("/dev/adc",O_RDWR); 11 12 int data; 13 while(1) 14 { 15 read(fd,&data,sizeof(data)); 16 printf("data = %d\n",data); 17 sleep(1); 18 } 19 close(fd); 20 return 0; 21 }
总结:
1 驱动使用设备树时可以通过platform总线来和设备树匹配。 2 也可以不使用platform总线,需要查询节点名称,进而获取节点中的属性。 3 4 of_find_node_by_path();//查询设备树中指定节点。 5 6 引脚编号 of_get_named_gpio();//从设备树的属性中获取寄存器信息并且产生gpio引脚编号 7 8 gpio_request();//注册引脚编号 9 10 gpio_direction_output();//设置为输出模式 11 gpio_direction_input();//设置为输入模式 12 13 gpio_set_value();//设置寄存器的值 14 gpio_get_value();//获取寄存器的值 15 16 gpio_to_irq();//从设备树中获取虚拟中断号 17 18 adc设备树添加: 19 fs4412-adc{ 20 compatible = ","; 21 interrupt-parent = <&combiner>; 22 interrupts = <10 3>; 23 reg = <0x126c0000 0x20>; 24 }; 25 26 ADCCON:设置预分频值、使能预分频值、设置转换精度 27 [0]控制是否开始进行AD转换,当AD转换成功后会产生一次中断。 28 ADCDAT:存放转化后的数据 29 CLRINTADC:清除中断 30 ADCMUX:设置转换通道