首先,在驱动部分,我们从简单一步一步来写。本文的驱动采用的是设备树获取信息方式。
1)简单些的,比如led灯,beep,采用的是paltform总线,gpio子系统。这里我以beep举例
gpio子系统:gpio子系统是内核中用来管理gpio资源的一套系统,它提供了很多关于GPIO的API函数。驱动程序中使用GPIO之前,我们需要先向gpio子系统先申请gpio,申请成功后才可以使用这个gpio,这样的话防止了gpio被复用。gpio子系统提供了关于一个gpio口的设置,如输入,输出方向,输出高或低电平。
platform总线:Linux系统要考虑到驱动的可重用行,因此提出了分层和分离得软件思路。由此platform总线应运而生。
分离
对于每个平台(SOC),我们都需要编写相应的I2C主机驱动,这个不能省略。但是,对于MPU6050,它仅是通过I2C进行通信的,所以,对于不同的平台,MPU6050这个设备驱动是可以进行重用的。所以,可以形成下面这样的思路。
(1)而对于不同平台的MPU6050驱动,可能MPU6050所挂载的I2C不同(2)一个SOC有多个MPU6050。那此时我们会需要改变这个驱动程序以此来适应SOC。你想想,对应(1),我们是不是需要改变驱动程序中的硬件信息。对应(2),我们是不是得根据挂载的MPU6050不同,改变硬件信息,有几个MPU6050,我们就需要几个对应的驱动程序。这是不是是的驱动程序变得繁琐且多。因此,为了驱动程序的重用性,提出了驱动分离的想法。即驱动和设备信息分离开来,通过一种总线连接,总线充当月老的角色,将设备和驱动连接起来。这个总线就是platform总线。使用了总线后,对于(1),我们只需要改变设备树中的硬件信息即可。对于(2),我们不需要多个驱动程序了,只需要一个驱动程序,在设备树中,用到的硬件信息去匹配这个驱动程序即可,减少了代码的冗余。
分层的话这里还没用到,后面用到我会讲。下面是关于beep的驱动代码和设备树文件。
beeper{
compatible = "alientek,beeper";
status = "okay";
default-state = "off";
beeper-gpio = <&gpio0 58 GPIO_ACTIVE_HIGH>;
};
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/irq.h>
#include <linux/wait.h>
#include <linux/poll.h>
#include <linux/fs.h>
#include <linux/fcntl.h>
#include <linux/platform_device.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#define GPIOBEEP_NAME "beep"
#define GPIOBEEP_CNT 1
struct beep_dev {
dev_t devid;
struct cdev cdev;
struct class *class;
struct device *device;
int gpio_beep;
};
static struct beep_dev beep;
static int beep_open(struct inode *inode,struct file *filp){
filp->private_data = &beep; // 使得在初始化后的结构体信息能被使用
return 0;
}
static ssize_t beep_read(struct file *filp,char __user *buf,size_t cnt,loff_t *offt){
return 0;
}
static ssize_t beep_write(struct file *filp,const char __user *buf,size_t cnt,loff_t *offt){
int ret;
char ker_buf[1];
ret = copy_from_user(ker_buf,buf,cnt);
if(ret < 0){
printk(KERN_ERR"Get from user data failed!\n");
return -EFAULT;
}
if(ker_buf[0] == 1){
gpio_set_value(beep.gpio_beep,1);
}else if(ker_buf[0] == 0){
gpio_set_value(beep.gpio_beep,0);
}
return 0;
}
static int beep_release(struct inode *inode,struct file *filp){
return 0;
}
static struct file_operations beep_fops = {
.owner = THIS_MODULE,
.open = beep_open,
.read = beep_read,
.write = beep_write,
.release = beep_release,
};
static int beep_init(struct platform_device *pdev){
struct device dev = pdev->dev;
int ret;
beep.gpio_beep= of_get_named_gpio(dev.of_node, "beep-gpio", 0);
if(!gpio_is_valid(beep.gpio_beep)){
printk(KERN_ERR"Get gpio num is %d\n",beep.gpio_beep);
return -1;
}
ret = gpio_request(beep.gpio_beep,"BEEP-GPIO");
if(ret < 0){
printk(KERN_ERR"Request gpio is failed!\n");
return ret;
}
gpio_direction_output(beep.gpio_beep,0);
return 0;
}
static int beep_probe(struct platform_device *pdev){
int ret;
ret = beep_init(pdev);
if(ret < 0){
return ret;
}
ret = alloc_chrdev_region(&beep.devid, 0, GPIOBEEP_CNT, GPIOBEEP_NAME);
if (ret)
goto out1;
beep.cdev.owner = THIS_MODULE;
cdev_init(&beep.cdev, &beep_fops);
/* 添加一个cdev */
ret = cdev_add(&beep.cdev, beep.devid, GPIOBEEP_CNT);
if (ret)
goto out2;
/* 创建类 */
beep.class = class_create(THIS_MODULE, GPIOBEEP_NAME);
if (IS_ERR(beep.class)) {
ret = PTR_ERR(beep.class);
goto out3;
}
/* 创建设备 */
beep.device = device_create(beep.class, NULL,
beep.devid, NULL, GPIOBEEP_NAME);
if (IS_ERR(beep.device)) {
ret = PTR_ERR(beep.device);
goto out4;
}
return 0;
out4:
class_destroy(beep.class);
out3:
cdev_del(&beep.cdev);
out2:
unregister_chrdev_region(beep.devid, GPIOBEEP_CNT);
out1:
gpio_free(beep.gpio_beep);
return ret;
}
static int beep_remove(struct platform_device *pdev)
{
/* 注销设备 */
device_destroy(beep.class, beep.devid);
/* 注销类 */
class_destroy(beep.class);
/* 删除cdev */
cdev_del(&beep.cdev);
/* 注销设备号 */
unregister_chrdev_region(beep.devid, GPIOBEEP_CNT);
/* 释放GPIO */
gpio_free(beep.gpio_beep);
return 0;
}
static const struct of_device_id beep_of_match[] = {
{.compatible = "alientek,beep"},
{ }
};
MODULE_DEVICE_TABLE(of, beep_of_match);
static struct platform_driver beep_driver = {
.driver = {
.name = "stm32mp1-beep", /* 驱动名字,用于和设备匹配 */
.of_match_table = beep_of_match, /* 设备树匹配表 */
},
.probe = beep_probe,
.remove = beep_remove,
};
module_platform_driver(beep_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("ALIENTEK");
MODULE_INFO(intree, "Y");
2)dht11温湿度模块
关于该模块,前面的硬件信息,我也讲过了。需要一个gpio口,根据官方给的时序进行传输,最后得到数据。具体的驱动代码我放在下面了。
dht11{
compatible = "alientek,dht11";
status = "okay";
default-state = "off";
dht11-gpio = <&gpio0 59 GPIO_ACTIVE_HIGH>;
};
#include <linux/module.h>
#include <linux/of_gpio.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include <linux/platform_device.h>
#include <linux/delay.h>
#define DHT11_NAME "dht11io"
#define DHT11_CNT 1
struct dht11_dev {
dev_t devid;
struct cdev cdev;
struct class *class;
struct device *device;
int dht11_gpio;
};
struct dht11_data {
unsigned char hmod_data;
unsigned char lmod_data;
unsigned char htem_data;
unsigned char ltem_data;
};
static struct dht11_dev dht11;
static struct dht11_data mtdata;
static void dht11_start(struct dht11_dev *dev){
gpio_direction_output(dev->dht11_gpio,1);
mdelay(30);
gpio_set_value(dev->dht11_gpio,0);
mdelay(20);
gpio_set_value(dev->dht11_gpio,1);
udelay(30);
gpio_direction_input(dev->dht11_gpio);
udelay(2);
}
static int dht11_reply(struct dht11_dev *dev){
unsigned char rey = 0;
while((gpio_get_value(dev->dht11_gpio) == 1) && rey < 100){
rey++;
udelay(1);
}
if(rey >= 100){
return -1;
}else{
rey = 0;
}
while((gpio_get_value(dev->dht11_gpio) == 0) && rey < 100){
rey++;
udelay(1);
}
if(rey >= 100){
return -1;
}else{
return 0;
}
}
static int dht11_read_bit(struct dht11_dev *dev){
unsigned char rey = 0;
while((gpio_get_value(dev->dht11_gpio) == 1) && rey < 100){
rey++;
udelay(1);
}
rey = 0;
while((gpio_get_value(dev->dht11_gpio) == 0) && rey < 100){
rey++;
udelay(1);
}
udelay(40);
if(gpio_get_value(dev->dht11_gpio) == 1){
return 1;
}else{
return 0;
}
}
static unsigned char dht11_read_byte(struct dht11_dev *dev){
unsigned char i,dat;
dat = 0;
for(i = 0;i < 8;i++){
dat <<= 1;
dat |= dht11_read_bit(dev);
}
return dat;
}
static int dht11_open(struct inode *inode,struct file *filp){
filp->private_data = &dht11;
return 0;
}
static ssize_t dht11_write(struct file *filp,const char __user *buf,size_t cnt,loff_t *offt){
return 0;
}
static ssize_t dht11_read(struct file *filp,char __user *buf,size_t cnt,loff_t *offt){
struct dht11_dev *dev = filp->private_data;
u8 kernel_buf[5];
int ret,i;
dht11_start(dev);
ret = dht11_reply(dev);
if(ret < 0){
return ret;
}
for(i = 0;i < 5;i++){
kernel_buf[4 - i] = dht11_read_byte(dev);
}
if(kernel_buf[4] + kernel_buf[3] + kernel_buf[2] + kernel_buf[1] == kernel_buf[0]){
mtdata.hmod_data = kernel_buf[4];
mtdata.lmod_data = kernel_buf[3];
mtdata.htem_data = kernel_buf[2];
mtdata.ltem_data = kernel_buf[1];
}
pr_info("DHT11: kernel_buf = [%u, %u, %u, %u]\n",
mtdata.hmod_data,mtdata.lmod_data,mtdata.htem_data,mtdata.ltem_data);
ret = copy_to_user(buf,&mtdata,sizeof(struct dht11_data));
if(ret){
return ret;
}
}
static int dht11_release(struct inode *inode,struct file *filp){
return 0;
}
static struct file_operations dht11_ops = {
.owner = THIS_MODULE,
.open = dht11_open,
.write = dht11_write,
.read = dht11_read,
.release = dht11_release,
};
static int dht11_probe(struct platform_device *pdev){
int ret;
//获得gpio编号
dht11.dht11_gpio = of_get_named_gpio(pdev->dev.of_node,"dht11-gpio",0);
if(!gpio_is_valid(dht11.dht11_gpio)){
printk(KERN_ERR"Gipo number id invalid!\r\n");
return -EINVAL;
}
//向系统申请gpio
ret = gpio_request(dht11.dht11_gpio,"DHT11");
if(ret){
printk(KERN_ERR"Gipo request failed!\r\n");
return -EINVAL;
}
//设备号
ret = alloc_chrdev_region(&dht11.devid,0,DHT11_CNT,DHT11_NAME);
if(ret)
goto out1;
//cdev
dht11.cdev.owner = THIS_MODULE;
cdev_init(&dht11.cdev,&dht11_ops);
ret = cdev_add(&dht11.cdev,dht11.devid,DHT11_CNT);
if(ret)
goto out2;
//创建类
dht11.class = class_create(THIS_MODULE,DHT11_NAME);
if(IS_ERR(dht11.class)){
ret = PTR_ERR(dht11.class);
goto out3;
}
//创建设备
dht11.device = device_create(dht11.class,&pdev->dev,dht11.devid,NULL,DHT11_NAME);
if(IS_ERR(dht11.device)){
ret = PTR_ERR(dht11.device);
goto out4;
}
return 0;
out4:
class_destroy(dht11.class);
out3:
cdev_del(&dht11.cdev);
out2:
unregister_chrdev_region(dht11.devid,DHT11_CNT);
out1:
gpio_free(dht11.dht11_gpio);
return ret;
}
static int dht11_remove(struct platform_device *pdev){
//注销设备
device_destroy(dht11.class,dht11.devid);
//注销类
class_destroy(dht11.class);
//注销字符设备
cdev_del(&dht11.cdev);
//注销设备号
unregister_chrdev_region(dht11.devid,DHT11_CNT);
//释放gpio
gpio_free(dht11.dht11_gpio);
}
//匹配列表
static const struct of_device_id dht11_of_match[] = {
{.compatible = "alientek,dht11"},
{/*Sentinel*/}
};
static struct platform_driver dht11_driver = {
.driver = {
.name = "zynq-dht11",
.of_match_table = dht11_of_match,
},
.probe = dht11_probe,
.remove = dht11_remove,
};
module_platform_driver(dht11_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("FUSU");
MODULE_INFO(intree, "Y");