ST480的驱动分析(三轴磁力计ST480MC)

以下的纯粹是无聊的写着玩!
设备树的添加:

    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))。

本文档的主要内容详细介绍的是MPU9250芯片的相关资料合集,主要内容包括了:使用MPU9250芯片进行的运动驱动器程序,英文森运动传感器通用评估委员会(UEVB)用户指南,MPU-9250产品中文说明书,MPU-9250产品英文数据手册,MPU-9250寄存器和说明,叉积法融合陀螺和加速度核心程序详解,姿态解算说明   MPU9250是一-个QFN封装的复合芯片(MCM), 它由2部分组成。一组是3轴加速度还有3轴陀螺仪,另一组则是AKM公司的AK8963 3轴磁力计。所以,MPU9250是一款9轴运动跟踪装置,他在小小的3X3X1mm的封装中融合了3轴加速度,3 轴陀螺仪以及数字运动处理器(DMP) 并且兼容MPU6515。其完美的I2C方案,可直接输出9轴的全部数据。一体化的设计,运动性的融合,时钟校准功能,让开发者避开了繁琐复杂的芯片选择和外设成本,保证最佳的性能。本芯片也为兼容其它传感器开放了辅助12C接口,比如连接压力传感器。   MPU9250的具有三个16位加速度AD输出,三个16位陀螺仪AD输出,三个6位磁力计AD输出。精密的慢速和快速运动跟踪,提供给客户全量程的可编程陀螺仪参数选择(土250,土500,士1000, and土2000 。/秒(dps)),可编程的加速度参数选择土2g,土4g,土8g, 土16g, 以及最大磁力计可达到土4800uT。   其他业界领先的功能还有可编程的数字滤波器, 40-85 C时带高精度的1%的时钟漂移,嵌入了温度传感器,并且带有可编程中断。该装置提供12C和SPI的接口,2. 4-3. 6V的供电电压,还有单独的数字10口,支持1.71V到VDD。   通信采用400KHz的12C和1MHz的SPI,若需要更快的速度,可以用SPI在20MHz的RDDLER RIDLER模式下直接读取传感器和中断寄存器。   采用CMOS-MEMS的制作平台,让传感器以低成本的高性能集成在- -个3x3x1mm的芯片内,并且能承受住10, 000g 的震动冲击。 应用领域 无需触碰操作的技术 手势控制 体感游戏控制器 位置查找服务 手机等便携式游戏设备 PS4或XBOX等游戏手柄控制器 3D电视遥控器或机顶盒 3D鼠标 可穿戴的健康智能设备
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值