[linux 驱动]misc设备驱动详解与实战
目录
1 描述
2 结构体
2.1 miscdevice
2.2 file_operations
3 注册和注销
3.1 misc_register
3.2 misc_deregister
4 解析 misc 内核源码
4.1 核心代码
4.2 函数解析
4.2.1 class_create_file
4.2.2 class_destroy
4.2.3 register_chrdev
5 示例
5.1 简单示例
5.2 实战示例
1 描述
所有的 MISC 设备驱动的主设备号都为 10,不同的设备使用不同的从设备号。随着 Linux字符设备驱动的不断增加,设备号变得越来越紧张,尤其是主设备号,MISC 设备驱动就用于解决此问题。MISC 设备会自动创建 cdev
所有的 misc 设备都属于同一个类,/sys/class/misc 目录下就是 misc 这个类的所有设备,每个设备对应一个子目录。
zwzn2064@zwzn2064-CVN-Z690D5-GAMING-PRO:$ ls /sys/class/misc
autofs fuse loop-control rfkill uinput
cpu_dma_latency hpet mcelog snapshot vfio
device-mapper hw_random microcode tun vga_arbiter
ecryptfs kvm psaux udmabuf
zwzn2064@zwzn2064-CVN-Z690D5-GAMING-PRO:$
驱动与设备匹配成功以后在/dev/ 文件夹下生成相应设备驱动文件,如 /dev/kvm 的主设备为 10,次设备号为 232
zwzn2064@zwzn2064-CVN-Z690D5-GAMING-PRO:$ ls /dev/kvm -l
crw------- 1 root root 10, 232 9月 11 10:17 /dev/kvm
zwzn2064@zwzn2064-CVN-Z690D5-GAMING-PRO:$
Linux 系统已经预定义了一些 MISC 设备的子设备号,我们在使用的时候可以从这些预定义的子设备号中挑选一个,当然也可以自己定义,只要 这个子设备号没有被其他设备使用接口。
15 #define PSMOUSE_MINOR 1
16 #define MS_BUSMOUSE_MINOR 2 /* unused */
17 #define ATIXL_BUSMOUSE_MINOR 3 /* unused */
18 /*#define AMIGAMOUSE_MINOR 4 FIXME OBSOLETE */
19 #define ATARIMOUSE_MINOR 5 /* unused */
20 #define SUN_MOUSE_MINOR 6 /* unused */
21 #define APOLLO_MOUSE_MINOR 7 /* unused */
22 #define PC110PAD_MINOR 9 /* unused */
23 /*#define ADB_MOUSE_MINOR 10 FIXME OBSOLETE */
24 #define WATCHDOG_MINOR 130 /* Watchdog timer */
25 #define TEMP_MINOR 131 /* Temperature Sensor */
26 #define APM_MINOR_DEV 134
27 #define RTC_MINOR 135
28 #define EFI_RTC_MINOR 136 /* EFI Time services */
29 #define VHCI_MINOR 137
30 #define SUN_OPENPROM_MINOR 139
31 #define DMAPI_MINOR 140 /* unused */
32 #define NVRAM_MINOR 144
33 #define SGI_MMTIMER 153
34 #define STORE_QUEUE_MINOR 155 /* unused */
35 #define I2O_MINOR 166
36 #define HWRNG_MINOR 183
37 #define MICROCODE_MINOR 184
38 #define IRNET_MINOR 187
39 #define D7S_MINOR 193
40 #define VFIO_MINOR 196
41 #define TUN_MINOR 200
42 #define CUSE_MINOR 203
43 #define MWAVE_MINOR 219 /* ACP/Mwave Modem */
44 #define MPT_MINOR 220
45 #define MPT2SAS_MINOR 221
46 #define MPT3SAS_MINOR 222
47 #define UINPUT_MINOR 223
48 #define MISC_MCELOG_MINOR 227
49 #define HPET_MINOR 228
50 #define FUSE_MINOR 229
51 #define KVM_MINOR 232
52 #define BTRFS_MINOR 234
53 #define AUTOFS_MINOR 235
54 #define MAPPER_CTRL_MINOR 236
55 #define LOOP_CTRL_MINOR 237
56 #define VHOST_NET_MINOR 238
57 #define UHID_MINOR 239
58 #define USERIO_MINOR 240
59 #define VHOST_VSOCK_MINOR 241
60 #define RFKILL_MINOR 242
61 #define MISC_DYNAMIC_MINOR 255
2 结构体
2.1 miscdevice
miscdevice 结构体,它在 Linux 内核中用于描述和管理一个“杂项设备”(miscellaneous device)。这个结构体主要用于注册和管理那些不属于主流设备驱动类别的小型设备。
66 struct miscdevice {
67 int minor;
68 const char *name;
69 const struct file_operations *fops;
70 struct list_head list;
71 struct device *parent;
72 struct device *this_device;
73 const struct attribute_group **groups;
74 const char *nodename;
75 umode_t mode;
76 };
int minor:
设备的次设备号(minor number),用来区分同一主设备号下的不同设备。在设备文件中,这通常用于识别不同的设备实例。
const char *name:
设备的名称,通常用于表示设备的标识符。当此设备注册成功以后就会在/dev 目录下生成一个名为 name 的设备文件
const struct file_operations *fops:
指向 file_operations 结构体的指针,该结构体定义了设备的文件操作方法,比如 open, read, write, ioctl 等。这些函数实现了设备的行为。
struct list_head list:
用于在全局设备列表中将 miscdevice 结构体节点连接起来。这是 Linux 内核中常用的链表结构,便于管理和遍历多个设备实例。
struct device *parent:
设备的父设备指针。如果设备有父设备,这个字段指向其父设备。通常用于设备树中表示设备层次结构。
struct device *this_device:
指向该设备本身的结构体,用于进一步的设备管理和操作。
const struct attribute_group **groups:
指向 attribute_group 结构体的指针数组,用于设备的 sysfs 属性管理。sysfs 是一个虚拟文件系统,用于在用户空间和内核之间提供设备的属性接口。
const char *nodename:
设备的节点名称,通常用于设备树的设备节点标识。在设备树中,节点名称用于描述硬件设备的属性和结构。
umode_t mode:
设备的文件权限模式,定义了设备文件的访问权限(例如,读、写权限)。这是文件系统中设备文件的权限设置。
2.2 file_operations
该结构体定义了设备的文件操作方法,比如 open, read, write, ioctl 等。这些函数实现了设备的行为。
1807 struct file_operations {
1808 struct module *owner;
1809 loff_t (*llseek) (struct file *, loff_t, int);
1810 ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
1811 ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
1812 ssize_t (*read_iter) (struct kiocb *, struct iov_iter *);
1813 ssize_t (*write_iter) (struct kiocb *, struct iov_iter *);
1814 int (*iterate) (struct file *, struct dir_context *);
1815 int (*iterate_shared) (struct file *, struct dir_context *);
1816 __poll_t (*poll) (struct file *, struct poll_table_struct *);
1817 long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
1818 long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
1819 int (*mmap) (struct file *, struct vm_area_struct *);
1820 unsigned long mmap_supported_flags;
1821 int (*open) (struct inode *, struct file *);
1822 int (*flush) (struct file *, fl_owner_t id);
1823 int (*release) (struct inode *, struct file *);
1824 int (*fsync) (struct file *, loff_t, loff_t, int datasync);
1825 int (*fasync) (int, struct file *, int);
1826 int (*lock) (struct file *, int, struct file_lock *);
1827 ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
1828 unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
1829 int (*check_flags)(int);
1830 int (*flock) (struct file *, int, struct file_lock *);
1831 ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
1832 ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
1833 int (*setlease)(struct file *, long, struct file_lock **, void **);
1834 long (*fallocate)(struct file *file, int mode, loff_t offset,
1835 loff_t len);
1836 void (*show_fdinfo)(struct seq_file *m, struct file *f);
1837 #ifndef CONFIG_MMU
1838 unsigned (*mmap_capabilities)(struct file *);
1839 #endif
1840 ssize_t (*copy_file_range)(struct file *, loff_t, struct file *,
1841 loff_t, size_t, unsigned int);
1842 int (*clone_file_range)(struct file *, loff_t, struct file *, loff_t,
1843 u64);
1844 int (*dedupe_file_range)(struct file *, loff_t, struct file *, loff_t,
1845 u64);
1846 int (*fadvise)(struct file *, loff_t, loff_t, int);
1847
1848 ANDROID_KABI_RESERVE(1);
1849 ANDROID_KABI_RESERVE(2);
1850 ANDROID_KABI_RESERVE(3);
1851 ANDROID_KABI_RESERVE(4);
1852 } __randomize_layout;
3 注册和注销
3.1 misc_register
函数原型 | int misc_register(struct miscdevice *misc) | |
参数 | struct miscdevice *misc | struct miscdevice *misc 结构体包含设备的主要信息,如设备号、设备名称和文件操作结构 |
返回值 | int | 成功:0 失败:负数 |
功能 | 注册一个杂项设备(misc device),并将其添加到内核中 |
173 int misc_register(struct miscdevice *misc)
174 {
175 dev_t dev;
176 int err = 0;
177 bool is_dynamic = (misc->minor == MISC_DYNAMIC_MINOR);
178
179 INIT_LIST_HEAD(&misc->list);
180
181 mutex_lock(&misc_mtx);
182
183 if (is_dynamic) {
184 int i = find_first_zero_bit(misc_minors, DYNAMIC_MINORS);
185
186 if (i >= DYNAMIC_MINORS) {
187 err = -EBUSY;
188 goto out;
189 }
190 misc->minor = DYNAMIC_MINORS - i - 1;
191 set_bit(i, misc_minors);
192 } else {
193 struct miscdevice *c;
194
195 list_for_each_entry(c, &misc_list, list) {
196 if (c->minor == misc->minor) {
197 err = -EBUSY;
198 goto out;
199 }
200 }
201 }
202
203 dev = MKDEV(MISC_MAJOR, misc->minor);
204
205 misc->this_device =
206 device_create_with_groups(misc_class, misc->parent, dev,
207 misc, misc->groups, "%s", misc->name);
208 if (IS_ERR(misc->this_device)) {
209 if (is_dynamic) {
210 int i = DYNAMIC_MINORS - misc->minor - 1;
211
212 if (i < DYNAMIC_MINORS && i >= 0)
213 clear_bit(i, misc_minors);
214 misc->minor = MISC_DYNAMIC_MINOR;
215 }
216 err = PTR_ERR(misc->this_device);
217 goto out;
218 }
219
220 /*
221 * Add it to the front, so that later devices can "override"
222 * earlier defaults
223 */
224 list_add(&misc->list, &misc_list);
225 out:
226 mutex_unlock(&misc_mtx);
227 return err;
228 }
3363 struct device *device_create_with_groups(struct class *class,
3364 struct device *parent, dev_t devt,
3365 void *drvdata,
3366 const struct attribute_group **groups,
3367 const char *fmt, ...)
3368 {
3369 va_list vargs;
3370 struct device *dev;
3371
3372 va_start(vargs, fmt);
3373 dev = device_create_groups_vargs(class, parent, devt, drvdata, groups,
3374 fmt, vargs);
3375 va_end(vargs);
3376 return dev;
3377 }
3.2 misc_deregister
函数原型 | void misc_deregister(struct miscdevice *misc) | |
参数 | struct miscdevice *misc | struct miscdevice *misc 结构体包含设备的主要信息,如设备号、设备名称和文件操作结构 |
返回值 | ||
功能 | 注销一个杂项设备(misc device) |
238 void misc_deregister(struct miscdevice *misc)
239 {
240 int i = DYNAMIC_MINORS - misc->minor - 1;
241
242 if (WARN_ON(list_empty(&misc->list)))
243 return;
244
245 mutex_lock(&misc_mtx);
246 list_del(&misc->list);
247 device_destroy(misc_class, MKDEV(MISC_MAJOR, misc->minor));
248 if (i < DYNAMIC_MINORS && i >= 0)
249 clear_bit(i, misc_minors);
250 mutex_unlock(&misc_mtx);
251 }
3395 void device_destroy(struct class *class, dev_t devt)
3396 {
3397 struct device *dev;
3398
3399 dev = class_find_device(class, NULL, &devt, __match_devt);
3400 if (dev) {
3401 put_device(dev);
3402 device_unregister(dev);
3403 }
3404 }
4 解析 misc 内核源码
4.1 核心代码
#define MISC_MAJOR 10
147 static struct class *misc_class;
148
149 static const struct file_operations misc_fops = {
150 .owner = THIS_MODULE,
151 .open = misc_open,
152 .llseek = noop_llseek,
153 };
267 static int __init misc_init(void)
268 {
269 int err;
270 struct proc_dir_entry *ret;
271
272 ret = proc_create_seq("misc", 0, NULL, &misc_seq_ops);
273 misc_class = class_create(THIS_MODULE, "misc");
274 err = PTR_ERR(misc_class);
275 if (IS_ERR(misc_class))
276 goto fail_remove;
277
278 err = -EIO;
279 if (register_chrdev(MISC_MAJOR, "misc", &misc_fops))
280 goto fail_printk;
281 misc_class->devnode = misc_devnode;
282 return 0;
283
284 fail_printk:
285 pr_err("unable to get major %d for misc devices\n", MISC_MAJOR);
286 class_destroy(misc_class);
287 fail_remove:
288 if (ret)
289 remove_proc_entry("misc", NULL);
290 return err;
291 }
292 #ifdef CONFIG_ROCKCHIP_THUNDER_BOOT
293 arch_initcall_sync(misc_init);
294 #else
295 subsys_initcall(misc_init);
296 #endif
通过函数class_create(THIS_MODULE, "misc");创建了 misc 类,生成/sys/class/misc 文件
zwzn2064@zwzn2064-CVN-Z690D5-GAMING-PRO:~/sdb1/android11/kernel$ ls /sys/class/
ata_device block devfreq-event gpio i2c-dev mem nvme powercap printer remoteproc scsi_generic thermal vc watchdog
ata_link bsg dma graphics input misc nvme-subsystem power_supply ptp rfkill scsi_host tpm vfio wmi_bus
ata_port dax dmi hidraw iommu mmc_host pci_bus ppdev pwm rtc sound tpmrm virtio-ports
backlight devcoredump extcon hwmon leds nd pci_epc ppp rapidio_port scsi_device spi_master tty vtconsole
bdi devfreq firmware i2c-adapter mdio_bus net phy pps regulator scsi_disk spi_slave usbmisc wakeup
zwzn2064@zwzn2064-CVN-Z690D5-GAMING-PRO:~/sdb1/android11/kernel$
4.2 函数解析
4.2.1 class_create_file
函数原型 | #define class_create(owner, name) \ ({ \ static struct lock_class_key __key; \ __class_create(owner, name, &__key); \ }) struct class *__class_create(struct module *owner, const char *name, struct lock_class_key *key) | |
参数 | struct module *owner | 指向该设备类所属模块的指针。模块是内核中可加载和卸载的代码块,这个参数允许内核跟踪哪个模块创建了设备类。如果设备类是由内核核心代码(而非可加载模块)创建的,则此参数可能为 NULL |
const char *name | 设备类的名称 | |
struct lock_class_key *key | 指向锁类键的指针,用于锁调试。锁类键是内核中用于调试锁竞争和死锁问题的一种机制。如果不需要锁调试,此参数可以传递 NULL | |
返回值 | struct class * | 成功:class结构体指针 失败:NULL |
功能 | 用于创建新的设备类。设备类的创建允许内核将具有相似功能的设备组织在一起,并提供了通过 /sys/class/ 目录与用户空间交互的接口 |
219 struct class *__class_create(struct module *owner, const char *name,
220 struct lock_class_key *key)
221 {
222 struct class *cls;
223 int retval;
224
225 cls = kzalloc(sizeof(*cls), GFP_KERNEL);
226 if (!cls) {
227 retval = -ENOMEM;
228 goto error;
229 }
230
231 cls->name = name;
232 cls->owner = owner;
233 cls->class_release = class_create_release;
234
235 retval = __class_register(cls, key);
236 if (retval)
237 goto error;
238
239 return cls;
240
241 error:
242 kfree(cls);
243 return ERR_PTR(retval);
244 }
4.2.2 class_destroy
函数原型 | void class_destroy(struct class *cls) | |
参数 | struct class *cls | 要摧毁的设备类的指针 |
返回值 | ||
功能 | 销毁一个设备类 |
254 void class_destroy(struct class *cls)
255 {
256 if ((cls == NULL) || (IS_ERR(cls)))
257 return;
258
259 class_unregister(cls);
260 }
192 void class_unregister(struct class *cls)
193 {
194 pr_debug("device class '%s': unregistering\n", cls->name);
195 class_remove_groups(cls, cls->class_groups);
196 kset_unregister(&cls->p->subsys);
197 }
4.2.3 register_chrdev
函数原型 | int register_chrdev(unsigned int major, const char *name,const struct file_operations *fops) | |
参数 | unsigned int major | 设备的主设备号 |
const char *name | 设备的名称 | |
const struct file_operations *fops | 指向 file_operations 结构体的指针,用于定义设备的操作函数 | |
返回值 | int | 成功:设备的主设备号 失败:负数 |
功能 | 注册一个字符设备 |
2703 static inline int register_chrdev(unsigned int major, const char *name,
2704 const struct file_operations *fops)
2705 {
2706 return __register_chrdev(major, 0, 256, name, fops);
2707 }
277 int __register_chrdev(unsigned int major, unsigned int baseminor,
278 unsigned int count, const char *name,
279 const struct file_operations *fops)
280 {
281 struct char_device_struct *cd;
282 struct cdev *cdev;
283 int err = -ENOMEM;
284
285 cd = __register_chrdev_region(major, baseminor, count, name);
286 if (IS_ERR(cd))
287 return PTR_ERR(cd);
288
289 cdev = cdev_alloc();
290 if (!cdev)
291 goto out2;
292
293 cdev->owner = fops->owner;
294 cdev->ops = fops;
295 kobject_set_name(&cdev->kobj, "%s", name);
296
297 err = cdev_add(cdev, MKDEV(cd->major, baseminor), count);
298 if (err)
299 goto out;
300
301 cd->cdev = cdev;
302
303 return major ? 0 : cd->major;
304 out:
305 kobject_put(&cdev->kobj);
306 out2:
307 kfree(__unregister_chrdev_region(cd->major, baseminor, count));
308 return err;
309 }
5 示例
5.1 简单示例
示例代码如下,.minor = MISC_DYNAMIC_MINOR表示此设备号随系统自动分配,misc 设备名称为misc_test。
#include "linux/miscdevice.h"
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
static int spirit_mcu_open(struct inode *inode, struct file *file)
{
return 0;
}
static int spirit_mcu_release(struct inode *inode, struct file *file)
{
return 0;
}
const struct file_operations misc_fops = {
.owner = THIS_MODULE,
.open = spirit_mcu_open,
.release = spirit_mcu_release,
};
static struct miscdevice misc_device = {
.minor = MISC_DYNAMIC_MINOR,
.name = "misc_test",
.fops = &misc_fops
};
static int misc_test_init(void)
{
int ret;
ret = misc_register(&misc_device);
if(ret)
{
printk(KERN_ERR "register misc device error\n");
goto failed;
}
printk("register misc device ok\r\n");
return 0;
failed:
return ret;
}
static void misc_test_exit(void)
{
printk("misc_deregister\r\n");
misc_deregister(&misc_device);
}
module_init(misc_test_init);
module_exit(misc_test_exit);
MODULE_LICENSE("GPL");
insmod misc_test.ko 之后,生成了/sys/class/misc/misc_test/文件夹和/dev/misc_test 文件节点,使用“ls /dev/misc_test -l”命令可以看到,misc_test 的主设备号为 10,此设备号自动分配的,为 49
console:/data # insmod misc_test.ko
[80439.091818] register misc device ok
console:/data # ls /sys/class/misc/
ashmem iep network_throughput uinput
cpu_dma_latency ion opteearmtz00 usb_accessory
crypto loop-control rfkill vendor_storage
device-mapper mali0 rga watchdog
fuse memory_bandwidth sw_sync
hdmi_hdcp1x misc_test tun
hw_random network_latency uhid
console:/data # ls /sys/class/misc/misc_test
dev power subsystem uevent
console:/data #
console:/data # ls /dev/misc_test -l
crw------- 1 root root 10, 49 2024-09-14 14:12 /dev/misc_test
console:/data #
/sys/class/misc/misc_test 和 /dev/misc_test 的区别
/sys/class/misc/misc_test
路径: 这是一个在 sysfs 文件系统中的节点。sysfs 是一个虚拟文件系统,用于提供关于内核对象和系统状态的信息。
作用: /sys/class/misc/misc_test 主要用于展示设备的属性、状态和其他信息。它是一个设备类的虚拟目录,通过 sysfs 提供设备的相关数据。
功能: 在这个路径下,你可以找到与设备相关的属性文件,它们用于读取设备的状态或控制设备的行为。这些文件是内核提供的,通常不直接用于设备的 I/O 操作,而是用于查看或修改设备的配置参数。
/dev/misc_test
路径: 这是一个在 dev 文件系统中的节点。dev 是一个虚拟文件系统,用于访问设备文件。
作用: /dev/misc_test 是实际的设备节点,用于与设备进行 I/O 操作。通过这个设备节点,用户空间程序可以进行读写操作来与设备进行交互。
功能: 这是用户空间程序与设备进行交互的接口。设备驱动程序通过将字符设备注册到这个路径,使得用户空间程序能够使用标准的文件操作系统调用(如 read、write、ioctl)来操作设备。
5.2 实战示例
实战代码如下所示。
static long spirit_mcu_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
int ret = 0;
unsigned char value;
struct mcu_req req;
struct DeviceInfomation deviceInfo;
struct spirit_mcu *spirit_mcu = i2c_get_clientdata(mcu_i2c_client);
void __user *argp = (void __user *)arg;
printk("spirit_mcu_ioctl cmd[%d]\n",cmd);
switch(cmd) {
case MCU_CMD_SYNC:
if (copy_from_user(&req, argp, sizeof(struct mcu_req))) {
printk(KERN_ERR "copy_from_user failed.\n");
ret = -EFAULT;
break;
}
ret = mcu_process(spirit_mcu,&req,&value);
if((req.opcode & OP_READ_BIT) != 0){
req.mode = value;
if (unlikely(copy_to_user(argp, &req, sizeof (struct mcu_req)))) {
printk(KERN_ERR "copy_to_user failed.\n");
ret = -EFAULT;
}
}
break;
case MCU_CMD_DEVICE_INFO:
if (copy_from_user(&deviceInfo, argp, sizeof(struct DeviceInfomation))) {
printk(KERN_ERR "copy_from_user failed.\n");
ret = -EFAULT;
break;
}
deviceInfo = spirit_mcu->deviceInfo;
if (unlikely(copy_to_user(argp, &deviceInfo, sizeof(struct DeviceInfomation)))) {
printk(KERN_ERR "copy_to_user failed.\n");
ret = -EFAULT;
break;
}
break;
case MCU_WDT_FEED_CONTROL:
if (copy_from_user(&req, argp, sizeof(struct mcu_req))) {
printk(KERN_ERR "copy_from_user failed.\n");
ret = -EFAULT;
break;
}
if(req.mode == 1){
spirit_mcu->userfeed = 0;
schedule_delayed_work(&spirit_mcu->work, msecs_to_jiffies(WATCHDOG_FEED_COUNT));
}else {
spirit_mcu->userfeed = 1;
mcu_watchdog_feed(spirit_mcu);
cancel_delayed_work_sync(&spirit_mcu->work);
}
break;
case MCU_WDT_USER_FEED:
if(spirit_mcu->userfeed == 1){
mcu_watchdog_feed(spirit_mcu);
}
break;
default:
break;
}
mutex_unlock(&spirit_mcu->m_lock);
printk("mcu ioctrl end\n");
return ret;
}
static int spirit_mcu_open(struct inode *inode, struct file *file)
{
return 0;
}
static int spirit_mcu_release(struct inode *inode, struct file *file)
{
return 0;
}
const struct file_operations spirit_mcu_operations = {
.owner = THIS_MODULE,
.open = spirit_mcu_open,
.release = spirit_mcu_release,
.unlocked_ioctl = spirit_mcu_ioctl,
};
static struct miscdevice spirit_mcu_misc_driver = {
.minor = MISC_DYNAMIC_MINOR,
.name = "spirit_mcu",
.fops = &spirit_mcu_operations
};
static int spirit_mcu_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
int ret = 0;
struct spirit_mcu *spirit_mcu;
struct device_node *np = client->dev.of_node;
printk("%s: probe\n", __FUNCTION__);
spirit_mcu = devm_kzalloc(&client->dev, sizeof(struct spirit_mcu), GFP_KERNEL);
if (!spirit_mcu)
return -ENOMEM;
spirit_mcu->regmap = devm_regmap_init_i2c(client, &mcu_regmap_config);
if (IS_ERR(spirit_mcu->regmap)) {
dev_err(&client->dev, "regmap initialization failed\n");
return PTR_ERR(spirit_mcu->regmap);
}
spirit_mcu->userfeed = 0;
i2c_set_clientdata(client, spirit_mcu);
spirit_mcu->i2c = client;
spirit_mcu->np = np;
mcu_i2c_client = client;
ret = misc_register(&spirit_mcu_misc_driver);
if(ret)
{
printk(KERN_ERR "register mcu misc device error\n");
goto failed;
}
printk("%s: probe ok!!\n", __FUNCTION__);
return 0;
failed:
return ret;
}
static const struct i2c_device_id spirit_mcu_id[] = {
{ "spirit_mcu", 0 },
{ }
};
static struct i2c_driver spirit_mcu_driver = {
.driver = {
.name = "spirit_mcu",
.owner = THIS_MODULE,
},
.probe = spirit_mcu_probe,
.id_table = spirit_mcu_id,
};
static int __init spirit_mcu_init(void)
{
return i2c_add_driver(&spirit_mcu_driver);
}
static void __exit spirit_mcu_exit(void)
{
i2c_del_driver(&spirit_mcu_driver);
}
MODULE_AUTHOR("neilnee@jwele.com.cn");
MODULE_DESCRIPTION("spirit mcu driver");
MODULE_LICENSE("GPL");
late_initcall(spirit_mcu_init);
module_exit(spirit_mcu_exit);