以下的纯粹是无聊的写着玩!
设备树的添加:
i2c@f9925000 { /* BLSP-1 QUP-3 */ //I2C 的总线地址
st480@0c{
compatible="senodia,st480";
reg=<0x0c>;
vdd-supply=<&pm8110_l19>;
vio-supply=<&pm8110_l14>;
};
};
I2C的总线驱动:适配器的实现,算法的对应等。
然后就是设备的注册完成。
module_init(st480_init);
i2c_add_driver(&st480_driver);//I2C-core中提供了具体的实现方法。
static struct i2c_driver st480_driver = {
.probe = st480_probe,
.remove = st480_remove,
.id_table = st480_id_table,
.driver = {
.name = ST480_I2C_NAME,
.owner = THIS_MODULE,
.of_match_table = st480_match_table,
},
};
probe的具体内容:
static int st480_probe(struct i2c_client *client, const struct i2c_device_id *
id)
{
int err = 0;
struct task_struct *thread;
mdelay(50);
if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
//判定适配器能力
}
/* Allocate memory for driver data */
st480 = kzalloc(sizeof(struct st480_data), GFP_KERNEL);
if (!st480) {
err = -ENOMEM;
goto exit1;
}
if (client->dev.of_node) {
err = st480_compass_parse_dt(&client->dev, st480);
//获取的设备树中的pdata数据。
}
st480->client = client;
i2c_set_clientdata(client, st480);
//client的数据结构下有devices的数据体,client->dev->p->st480
//INIT_DELAYED_WORK(&st480->work, st480_input_func);
init_waitqueue_head(&st480->mag_wq);
//初始化一个等待队列头。
/*等待队列在linux内核中有着举足轻重的作用,很多linux驱动都或多或少涉及到了等待队列。因此,对于linux内核及驱动开发者来说,掌握等待队列是必须课之一。 Linux内核的等待队列是以双循环链表为基础数据结构,与进程调度机制紧密结合,能够用于实现核心的异步事件通知机制。它有两种数据结构:等待队列头(wait_queue_head_t)和等待队列项(wait_queue_t)。等待队列头和等待队列项中都包含一个list_head类型的域作为”连接件”。它通过一个双链表和把等待task的头,和等待的进程列表链接起来。*/
st480->mag_wkp_flag = 0;
hrtimer_init(&st480->mag_timer, CLOCK_BOOTTIME, HRTIMER_MODE_REL);
//高精度定时器初始化。
st480->mag_timer.function = mag_timer_handle;
//定时的处理函数handle。
st480->mag_task = kthread_run(mag_poll_thread,st480, "mag_sns");
//内核创建线程的时候经常会用到kthread_run()这样的一个调用。参一函。参二实参。是类//似于应用层的pthread_create();
if (st480_setup(st480->client) != 0) {
dev_err(&client->dev, "st480 setup error!\n");
goto exit2;
}
//器件初始化校准等。
/* Declare input device */
st480->input_dev = input_allocate_device();
//定义一个input设备
if (!st480->input_dev) {
err = -ENOMEM;
dev_err(&client->dev, "failed to allocate input device\n");
goto exit3;
}
/* Setup input device */
set_bit(EV_ABS, st480->input_dev->evbit);
//设置这个input设备的事件能力
/* x-axis of raw magnetic vector (-32768, 32767) */
input_set_abs_params(st480->input_dev, ABS_X, ABSMIN_MAG, ABSMAX_MAG, 0, 0);
/* y-axis of raw magnetic vector (-32768, 32767) */
input_set_abs_params(st480->input_dev, ABS_Y, ABSMIN_MAG, ABSMAX_MAG, 0, 0);
/* z-axis of raw magnetic vector (-32768, 32767) */
input_set_abs_params(st480->input_dev, ABS_Z, ABSMIN_MAG, ABSMAX_MAG, 0, 0);
//设置X Y Z绝对的值范围。
/* Set name */
st480->input_dev->name = "compass";
//初始化input设备的名字。
st480->input_dev->id.bustype = BUS_I2C;
//初始化input设备的总线类型
/* Register */
err = input_register_device(st480->input_dev);
//注册这个input设备。
if (err) {
dev_err(&client->dev, "SENODIA st480_probe: Unable to register input device\
n");
goto exit4;
}
err = misc_register(&st480_device);
//注册一个杂损设备
if (err) {
dev_err(&client->dev, "SENODIA st480_probe: st480_device register failed\n");
goto exit5;
}
rwlock_init(&st480->lock);
//读写锁的初始化
/* As default, report all information */
atomic_set(&st480->m_flag, 1);
atomic_set(&mv_flag, 1);
atomic_set(&rm_flag, 1);
atomic_set(&mrv_flag, 1);
st480->poll_interval = ST480_DEFAULT_DELAY;//控制读取的间隙
st480->cdev = sensors_cdev;
st480->cdev.sensors_enable = st480_cdev_set_enable;
//设备的定时器的启动
st480->cdev.sensors_poll_delay = st480_cdev_set_poll_delay;
//设备的上报速度的设置
err = sensors_classdev_register(&client->dev, &st480->cdev);
//drivers/sensors/sensors_class.c
/**
* sensors_classdev_register - register a new object of sensors_classdev class.
* @parent: The device to register.
* @sensors_cdev: the sensors_classdev structure for this device.
*/
//int sensors_classdev_register(struct device *parent,
// struct sensors_classdev //*sensors_cdev)
//{
// sensors_cdev->dev = device_create(sensors_class, parent, 0,
// sensors_cdev, "%s", //sensors_cdev->name);
// if (IS_ERR(sensors_cdev->dev))
// return PTR_ERR(sensors_cdev->dev);
//
// down_write(&sensors_list_lock);
// list_add_tail(&sensors_cdev->node, &sensors_list);
// up_write(&sensors_list_lock);
//
// pr_debug("Registered sensors device: %s\n",
// sensors_cdev->name);
// return 0;
//}
//EXPORT_SYMBOL(sensors_classdev_register);
if (err) {
dev_err(&client->dev, "sensors class register failed!\n");
goto exit6;
}
st480->st480_class = class_create(THIS_MODULE, "st480");
/*一个struct class结构体类型变量对应一个类,内核同时提供了class_create(…)函数,可以用它来创建一个类,这个类存放于sysfs下面,一旦创建好了这个类/sys/class/st480,再调用device_create(…)函数来在/dev目录下创建相应的设备节点。这样,加载模块的时候,用户空间中的udev会自动响应device_create(…)函数,去/sysfs下寻找对应的类从而创建设备节点。/dev/st480*/
if (IS_ERR(st480->st480_class)) {
pr_err("%s, create st480_class is failed.(err=%ld)\n",
__func__, IS_ERR(st480->st480_class));
goto exit7;
}
err = sensors_register(st480->st480_class, st480->factory_device, st480,
sensor_attrs, MODULE_NAME);
/*
int sensors_register(struct class *st480_class, struct device *dev, void *
drvdata,
struct device_attribute *attributes[], char *name)
{
int ret = 0;
dev = device_create(st480_class, NULL, 0, drvdata, "%s", name);
//在st480这个类下创建一个名叫ST480的设备
if (IS_ERR(dev)) {
ret = PTR_ERR(dev);
pr_err("[SENSORS CORE] device_create failed![%d]\n", ret);
return ret;
}
set_sensor_attr(dev, attributes);
//创建设备属性文件和调试的接口
return ret;
}
*/
if (err) {
pr_err("%s, failed to sensors_register (%d)\n",
__func__, err);
goto exit8;
}
#if ST480_AUTO_TEST
thread=kthread_run(auto_test_read,NULL,"st480_read_test");
#endif
printk("st480 probe done.");
return 0;
exit8:
class_destroy(st480->st480_class);
exit7:
exit6:
misc_deregister(&st480_device);
exit5:
input_unregister_device(st480->input_dev);
exit4:
input_free_device(st480->input_dev);
exit3:
exit2:
hrtimer_cancel(&st480->mag_timer);
kthread_stop(st480->mag_task);
kfree(st480);
exit1:
exit0:
return err;
}
probe完成了。
一下说说它的设计模式。
被probe的不说,一个设备的数据获取预处理,用户的接口,是驱动的设计逻辑,
这个驱动通过input子系统的框架上报事件,数据的获取是通过当设备被使能时,首先使用高精度定时的方式,触发一个驱动中线程。触发方式是:
wake_up_interruptible(&sensor->mag_wq);
唤醒 mag_wq 指定的注册在等待队列上的进程。该函数不能直接的立即唤醒进程,而是由调度程序转换上下文,调整为可运行状态。(可以看到这是异步的)
wait_event_interruptible(sensor->mag_wq,
((sensor->mag_wkp_flag != 0) || kthread_should_stop()));
这样就是定时的方式异步的唤醒一个等待队列。就可以完成读取和上报的操作。
这种设计也运用在中断的下半步的异步操作。(工作队列(work queue))。