Linux MISC驱动
MISC驱动也叫杂项驱动,但是本质上就是一个字符设备驱动,通常嵌套在platform总线驱动中。
MISC设备驱动的主设备号都为10,不同设备使用不同的从设备号,也就是说,这些不同的设备在设备树上的位置处于MISC设备节点的子节点的层次。即 根节点 --> MISC节点 --> 设备节点。
miscdevice 结构体
minor:子设备的设备号;
name:设备名字;
fops:操作函数集合,一个结构体变量,存放各种操作函数;
注册MISC设备API
/*
@function: 用于注册MISC设备
@param:
misc:要注册的 MISC 设备
@return:
0,成功;负数,失败
*/
int misc_register(struct miscdevice * misc)
MISC的优点
- MISC驱动本质上是一个字符设备驱动,但是内核集成一些API函数来帮助我们注册设备,这也就使得原来繁琐的字符设备驱动的注册和注销过程缩减了,即 init 和 exit 这两个函数内的内容。
- 所有的不知道在设备树上放哪的设备驱动节点,都可以挂载在MISC这个节点上,相当于作为MISC节点的子节点,那么这些设备的父设备号实际上就是MISC设备的设备号,而这些设备自身的设备号,就是MISC设备的子设备号。这样就节约了主设备号的浪费,就等于是原来只是一根树枝,现在是有很多枝杈的树枝。
学习过程中的猜想
MISC对于只需要一个IO进行通信的设备应该算是很友好的,那么一些依靠单总线协议的设备应该也能依靠MISC来编写相关驱动,例如DS18B20温湿度传感器等。
MISC设备下beep设备驱动代码编写
#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.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/platform_device.h>
#include <linux/miscdevice.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#define MISCBEEP_NAME "miscbeep" /* 设备名字 */
#define MISCBEEP_MINOR 144 /* 子设备号 */
#define BEEPOFF 0
#define BEEPON 1
/* MISC_beep 设备结构体 */
struct miscbeep_dev
{
dev_t devid; /* 设备号 */
struct cdev cdev; /* cdev */
struct class *class; /* 类 */
struct device *device; /* 设备 */
struct device_node *nd; /* 设备节点 */
int beep_gpio; /* beep 所使用的 GPIO 编号 */
};
/* beep 设备 */
struct miscbeep_dev miscbeep;
/* 打开设备 */
static int miscbeep_open(struct inode *inode, struct file *filp)
{
filp->private_data = &miscbeep;
return 0;
}
/* 向设备写数据 */
static ssize_t miscbeep_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
int retvalue;
unsigned char databuf[1];
unsigned char beepstat;
struct miscbeep_dev *dev = filp->private_data;
retvalue = copy_from_user(databuf, buf, cnt);
beepstat = databuf[0];
if (beepstat == BEEPON)
{
gpio_set_value(dev->beep_gpio, 0); /* 打开蜂鸣器 */
}
else if (beepstat == BEEPOFF)
{
gpio_set_value(dev->beep_gpio, 1); /* 关闭蜂鸣器 */
}
return 0;
}
/* 设备操作函数 */
static struct file_operations miscbeep_fops = {
.owner = THIS_MODULE,
.open = miscbeep_open,
.write = miscbeep_write,
};
/* MISC 设备结构体 */
static struct miscdevice beep_miscdev = {
.minor = MISCBEEP_MINOR,
.name = MISCBEEP_NAME,
.fops = &miscbeep_fops,
};
/* flatform 驱动的 probe 函数,当驱动与设备匹配以后此函数就会执行 */
static int miscbeep_probe(struct platform_device *dev)
{
int ret = 0;
printk("beep driver and device was matched!\r\n");
/* 1.获取设备节点:beep */
miscbeep.nd = of_find_node_by_path("/beep");
if(miscbeep.nd == NULL)
{
printk("beep node not find!\r\n");
return -EINVAL;
}
/* 2.获取设备树中的GPIO属性,得到 beep 使用的 beep 编号 */
miscbeep.beep_gpio = of_get_named_gpio(miscbeep.nd, "beep-gpio", 0);
if(miscbeep.beep_gpio < 0)
{
printk("can't get beep-gpio");
return 0;
}
/* 3.设置 GPIO5_IO01 为输出,并且输出高电平, 默认关闭 beep */
ret = gpio_direction_output(miscbeep.beep_gpio, 1);
if(ret < 0)
{
printk("can't set gpio!\r\n");
}
/* 注册 misc 设备,取代了注册字符设备的那5个步骤 */
ret = misc_register(&beep_miscdev);
if(ret < 0)
{
printk("misc device register failed!\r\n");
return -EFAULT;
}
return 0;
}
/* remove 函数,移除 platform 驱动的时候此函数会执行 */
static int miscbeep_remove(struct platform_device *dev)
{
gpio_set_value(miscbeep.beep_gpio, 1);
/* 注销 misc 设备驱动 */
misc_deregister(&beep_miscdev);
return 0;
}
/* 匹配列表 */
static const struct of_device_id beep_of_match[] = {
{ .compatible = "atkalpha-beep" },
{ /* sentinel */ }
};
/* platform 驱动结构体 */
static struct platform_driver beep_driver = {
.driver = {
.name = "imx6ul-beep",
.of_match_table = beep_of_match,
},
.probe = miscbeep_probe,
.remove = miscbeep_remove,
};
/* 驱动入口函数 */
static int __init miscbeep_init(void)
{
return platform_driver_register(&beep_driver);
}
/* 驱动出口函数 */
static void __exit miscbeep_exit(void)
{
platform_driver_unregister(&beep_driver);
}
module_init(miscbeep_init);
module_exit(miscbeep_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Swiler");