mirror of
https://gitee.com/open-loongarch/linux-5.10.git
synced 2026-10-25 20:57:11 +08:00
1.add 2k300_gongkong board support 2.update dht11 driver 3.support EC200A-CN 4G model 4.goodix:reset: reset timing fixing 5.other bugs fix
343 lines
11 KiB
C
343 lines
11 KiB
C
/*************************************************************************
|
||
> File Name: DHT11.c
|
||
> 作者:YJK
|
||
> Mail: 745506980@qq.com
|
||
> Created Time: 2021年05月20日 星期四 21时33分51秒
|
||
************************************************************************/
|
||
/*************************************************************************
|
||
> File Name: DHT11.c
|
||
> 作者:OJT
|
||
> Created Time: 2024-06-14
|
||
************************************************************************/
|
||
#include<linux/init.h>
|
||
#include<linux/module.h>
|
||
#include <linux/platform_device.h>
|
||
#include <linux/of_gpio.h>
|
||
#include <linux/cdev.h>
|
||
#include <linux/miscdevice.h>
|
||
#include <linux/fs.h>
|
||
#include <linux/delay.h> //内核延时 忙等
|
||
#include <linux/uaccess.h>
|
||
#include <linux/gpio.h> // 包含 GPIO 支持
|
||
|
||
#define ERROR_PRINT(x) do{ \
|
||
printk(KERN_ERR x " is error!\n"); \
|
||
return -1; \
|
||
}while(0)
|
||
|
||
#define DEVICE_NAME "dht11"
|
||
|
||
#define HIGH 1 // 代表高电平
|
||
#define LOW 0 // 代表低电平
|
||
|
||
#define DHT_MAXCOUNT 32000 // 计数超时的最大值
|
||
|
||
// dht11响应信号 + 数据信号,一共 1 + 40 次低高电平变化
|
||
#define DHT_PULSES 41
|
||
// 低高电平变化中,低和高各变化一次,所以电平维持计数记录需要 (1 + 40) * 2 次
|
||
#define DHT_PULSES_SAMPLING_COUNT (DHT_PULSES * 2)
|
||
|
||
struct gpio_desc* dht11_gpiod; //DHT11_GPIO编号
|
||
|
||
struct dht11_info {
|
||
dev_t devt; // 实际上就是 u32 类型 用于记录主设备号
|
||
struct miscdevice dht11_device; // misc设备 用于创建 /dev/ 下的节点
|
||
};
|
||
|
||
struct dht11_info dht11;
|
||
|
||
/*
|
||
* dht11 采样时序
|
||
* 1. 起始信号
|
||
* 主机发送采集信号,此时 GPIO 由 CPU 控制,设置为输出状态
|
||
* 主机由高电平-->低电平(至少18ms)--->高电平(20-40us)
|
||
*
|
||
* 2. DHT响应信号,此时 GPIO 由 dht11 控制,所以应该设置为输入状态
|
||
* 低电平(80us)--->高电平(80us)
|
||
*
|
||
* 3. DHT数据传输信号,共40bit,即5字节数据开始传输,先发送的bit是字节里的高位(MSB)
|
||
* 数据开始 低电平(50us) ---> 数据0 高电平 26us-28us
|
||
* 数据开始 低电平(50us) ---> 数据1 高电平 70us
|
||
*/
|
||
|
||
static void end(void)
|
||
{
|
||
// 建议结束时,GPIO输出高,下次采样成功率高点
|
||
gpiod_direction_output(dht11_gpiod, HIGH);
|
||
}
|
||
|
||
ssize_t dht11_read(struct file *file, char __user * user_buf, size_t size, loff_t * loff)
|
||
{
|
||
int err;
|
||
unsigned char dht11_data[5];
|
||
unsigned int pulse_counts[DHT_PULSES_SAMPLING_COUNT];
|
||
volatile unsigned int i;
|
||
unsigned int pulse_low_average;
|
||
int data_index;
|
||
|
||
/*
|
||
* 下列算法解析:
|
||
*
|
||
* DHT_PULSES_SAMPLING_COUNT 为 82,因为从DHT11的响应信号开始,低高电平一共有82次
|
||
* 低 80us 高 80us 为2次,这是一开始的响应信号
|
||
* 低 50us 高 28us 或 70us,表示bit为0或1,这个有40次信号
|
||
* 所以 (1 + 40) * 2 = 82
|
||
* 数组输出化 0,因为这个数组是将电平维持时间通过计数形式表达
|
||
* 如果电平维持时间为 80us,那么 pulse_counts[i] 就会比较大
|
||
* 如果电平维持时间为 28us,那么 pulse_counts[i] 就会相比于80us时比较小
|
||
*
|
||
* 对于代表 低电平维持了 50us 的那些pulse_counts[i]而言(从i=2,i += 2)
|
||
* 由于维持的时间都相同,所以计数值都差不多,把这40个低电平50us的计数求平均数
|
||
* 那么这个平均数就代表 50us 的计数
|
||
* 当分辨bit是0还是1时,就需要看低电平50us后的高电平维持的时间,也就是计数值。
|
||
* 当计数值小于上面求的平均数,代表着高电平维持的时间少于50us,也就是在了 28us 左右,那么当前bit为0。
|
||
* 当计数值大于上面求的平均数,代表着低电平维持的时间多于50us,也就是在了 70us 左右,那么当前bit为1。
|
||
*
|
||
* 这样就能得到5个字节的数据。
|
||
*/
|
||
for (i = 0; i < DHT_PULSES_SAMPLING_COUNT; ++i)
|
||
pulse_counts[i] = 0;
|
||
for (i = 0; i < 5; ++i)
|
||
dht11_data[i] = 0;
|
||
|
||
// 实现 1. 起始信号 的时序
|
||
gpiod_direction_output(dht11_gpiod, LOW);
|
||
mdelay(20); //拉低20ms 要求是(最小18ms)
|
||
gpiod_direction_output(dht11_gpiod, HIGH);
|
||
udelay(40); //拉高40us 要求是(20-40us)
|
||
|
||
// 之后是DHT11响应信号的数据传输信号,所以 GPIO 要设置为输入状态
|
||
gpiod_direction_input(dht11_gpiod);
|
||
|
||
err = 0;
|
||
for (i = 0; i < DHT_PULSES_SAMPLING_COUNT; i += 2) {
|
||
// 根据前面所说,响应信号和数据传输信号,都是以低电平开始,高电平结束
|
||
// 这里就是前面说的计数,将电平维持时间转化为计数值
|
||
while(gpiod_get_value(dht11_gpiod) == LOW) {
|
||
if (++pulse_counts[i] >= DHT_MAXCOUNT) {
|
||
// failed
|
||
err = 1;
|
||
break;
|
||
}
|
||
}
|
||
if (err)
|
||
break;
|
||
while(gpiod_get_value(dht11_gpiod) == HIGH) {
|
||
if (++pulse_counts[i + 1] >= DHT_MAXCOUNT) {
|
||
// failed
|
||
err = 1;
|
||
break;
|
||
}
|
||
}
|
||
if (err)
|
||
break;
|
||
}
|
||
|
||
// 计数过大(DHT_MAXCOUNT),代表DHT11数据信号采样(计数)失败
|
||
if (err) {
|
||
// 计数失败会很常见,所以这个打印可以注释掉
|
||
pr_info("dht11 lost response! (GPIO : %d)\n", desc_to_gpio(dht11_gpiod));
|
||
end();
|
||
return -16;
|
||
}
|
||
|
||
/*
|
||
* 开始统计收集到的计数值,此时已经和DHT11没关系了
|
||
*/
|
||
|
||
// 跳过一开始的 80us 低 80us 高的响应信号 所以 i 不会等于0和1
|
||
|
||
// 算出低电平的维持50us时的平均值
|
||
pulse_low_average = 0;
|
||
for (i = 2; i < DHT_PULSES_SAMPLING_COUNT; i += 2)
|
||
pulse_low_average += pulse_counts[i];
|
||
pulse_low_average /= DHT_PULSES; //平均值
|
||
|
||
// 根据平均值,区分 bit 0 还是 bit 1
|
||
for (i = 3; i < DHT_PULSES_SAMPLING_COUNT; i += 2) {
|
||
/*
|
||
* 3 5 7 9 11 13 15 17
|
||
* 19 21 23 25 27 29 31 33
|
||
* 35 37 39 41 43 45 47 49
|
||
* 51 53 55 57 59 61 63 65
|
||
* 67 69 71 73 75 77 79 81
|
||
*
|
||
* 要算当前的 pulse_counts[i] 属于那个字节,也就是 data_index
|
||
* i从3开始,所以先减去3
|
||
* 8bit 一个字节,但是 pulse_counts[i - 1] pulse_counts[i] 构成一个bit
|
||
* 所以 8 * 2 = 16,所以要除以16,这样就算到了 data_index
|
||
*/
|
||
data_index = (i - 3) / 16;
|
||
dht11_data[data_index] <<= 1; // 前面说过,先来的bit属于高位,所以先左移一位,将高位向前移动
|
||
|
||
// 高于平均值代表是 bit1
|
||
if (pulse_counts[i] >= pulse_low_average)
|
||
dht11_data[data_index] |= 1;
|
||
// 由于前面已经左移一位,所以第 0 位一定是 0,所以遇到bit0,无需再置位
|
||
}
|
||
|
||
if (copy_to_user(user_buf, dht11_data, size) != 0)
|
||
ERROR_PRINT("dht11 date copy_to_user failed");
|
||
end();
|
||
return 0;
|
||
}
|
||
|
||
ssize_t dht11_write(struct file *file, const char __user *user_buf, size_t size, loff_t *loff)
|
||
{
|
||
int ret;
|
||
unsigned int gpio;
|
||
struct gpio_desc* new_gpiod_desc;
|
||
|
||
// 检查用户空间的buffer是否足够大以存储一个无符号整型也就是4字节
|
||
if (size < sizeof(gpio)) {
|
||
printk(KERN_WARNING "dht11_write: buffer too small\n");
|
||
return -EINVAL;
|
||
}
|
||
|
||
// 从用户空间复制数据到内核空间 不能直接 memcpy
|
||
ret = copy_from_user(&gpio, user_buf, sizeof(gpio));
|
||
if (ret != 0) {
|
||
printk(KERN_ERR "dht11_write: failed to copy data from user space\n");
|
||
return -EFAULT;
|
||
}
|
||
|
||
// 根据 GPIO 编号获取 gpiod_desc
|
||
new_gpiod_desc = gpio_to_desc(gpio);
|
||
if (new_gpiod_desc < 0)
|
||
return -1;
|
||
|
||
dht11_gpiod = new_gpiod_desc; // 更新 gpiod_desc 之后 read 调用时就会操作新的gpio
|
||
|
||
/* 返回写入的字节数 */
|
||
return sizeof(gpio);
|
||
}
|
||
|
||
int dht11_open(struct inode * inode, struct file * file)
|
||
{
|
||
return 0;
|
||
}
|
||
|
||
int dht11_release(struct inode * inode, struct file *file)
|
||
{
|
||
return 0;
|
||
}
|
||
|
||
/*
|
||
* 因为最终目的是为了对 /dev/dht11 节点进行io
|
||
* 所以需要实现这些内核接口,实现了才能进行操作
|
||
* open 和 release 对应 open 和 close 函数,但没有什么初始化和关闭操作,所以函数里面无需实现代码
|
||
*
|
||
* read 对应 read 函数,是为了读取5个字节的采样数据,也就是触发一次采样
|
||
*
|
||
* write 对应 write 函数,对于dht11而言,没什么要写入的,但是本驱动实现的是改变操作dht11的gpio的效果
|
||
* 比如一开始设备树里面注册的是gpio 88去控制的,但是实际使用时,改成了gpio 61去控制
|
||
* 那么write写入61这个数字所在int的指针,即可修改gpiod_desc(dht11_gpiod)为 gpio 61 的gpiod_desc
|
||
* 后续的read函数也是操作gpio 61这个引脚。
|
||
*/
|
||
struct file_operations fops = {
|
||
.open = dht11_open,
|
||
.release = dht11_release,
|
||
.read = dht11_read,
|
||
.write = dht11_write,
|
||
.owner = THIS_MODULE
|
||
};
|
||
|
||
/*
|
||
* 字符设备管理代码
|
||
* 下面的代码是为了向内核注册驱动和设备
|
||
*
|
||
* 这两个是为了注册驱动和注销驱动
|
||
* dht11_init
|
||
* dht11_exit
|
||
*
|
||
* 这两个是为了声明设备和删除设备
|
||
* dht11_probe
|
||
* dht11_remove
|
||
*
|
||
* 有了驱动,才会去找设备
|
||
* 而找到设备靠的是设备树里面有没有一个设备的 compatible 字段和
|
||
* of_match_table 里面 compatible 字段匹配
|
||
* 如果匹配了,就会调用 dht11_probe
|
||
*/
|
||
|
||
int dht11_probe(struct platform_device *pdev)
|
||
{
|
||
int ret;
|
||
|
||
/*
|
||
* dht11-gpios = <&gpio 61 GPIO_ACTIVE_HIGH>;
|
||
* 注意 GPIO_ACTIVE_HIGH 这个也要关注
|
||
* 如果 GPIO的高电平对应逻辑值 1,那么填 GPIO_ACTIVE_HIGH
|
||
* 否则 填GPIO_ACTIVE_LOW
|
||
*/
|
||
dht11_gpiod = devm_gpiod_get_optional(&pdev->dev, DEVICE_NAME, 0);
|
||
if (dht11_gpiod <= 0)
|
||
ERROR_PRINT("devm_gpiod_get_optional");
|
||
|
||
/*注册字符设备驱动*/
|
||
// 分配设备号
|
||
ret = alloc_chrdev_region(&dht11.devt, 0, 1, DEVICE_NAME);
|
||
if (ret != 0)
|
||
ERROR_PRINT("alloc_chrdev_region");
|
||
|
||
// 注册 misc 设备,在/dev/下生成 dht11 节点(文件)
|
||
dht11.dht11_device.minor = MISC_DYNAMIC_MINOR; // 动态分配次设备号
|
||
dht11.dht11_device.name = "dht11";
|
||
dht11.dht11_device.fops = &fops;
|
||
ret = misc_register(&dht11.dht11_device); // 这样 /dev 下就会生成对应节点
|
||
if (ret < 0) {
|
||
unregister_chrdev_region(dht11.devt, 1);
|
||
return ret;
|
||
}
|
||
|
||
printk(KERN_INFO "dht11 probe success! (GPIO: %d)\n", desc_to_gpio(dht11_gpiod));
|
||
return 0;
|
||
}
|
||
|
||
int dht11_remove(struct platform_device *pdev)
|
||
{
|
||
// 注销 misc 设备
|
||
misc_deregister(&dht11.dht11_device);
|
||
// 注销设备号
|
||
unregister_chrdev_region(dht11.devt, 1);
|
||
// 释放 GPIO 描述符
|
||
gpiod_put(dht11_gpiod);
|
||
return 0;
|
||
}
|
||
|
||
struct of_device_id of_match_table[] = {
|
||
{.compatible = "dht11-sensor"},
|
||
{}
|
||
}; // 设备树匹配
|
||
|
||
struct platform_driver pdrv = {
|
||
.probe = dht11_probe,
|
||
.remove = dht11_remove,
|
||
.driver = {
|
||
.name = DEVICE_NAME,
|
||
.owner = THIS_MODULE,
|
||
.of_match_table = of_match_table
|
||
},
|
||
};
|
||
|
||
static int dht11_init(void)
|
||
{
|
||
// 注册platform_driver
|
||
int ret = platform_driver_register(&pdrv);
|
||
if (ret != 0)
|
||
ERROR_PRINT("platform driver register");
|
||
|
||
return 0;
|
||
}
|
||
|
||
static void dht11_exit(void)
|
||
{
|
||
/*注销platform_driver*/
|
||
platform_driver_unregister(&pdrv);
|
||
}
|
||
|
||
module_init(dht11_init);
|
||
module_exit(dht11_exit);
|
||
|
||
MODULE_LICENSE("GPL");
|