1. 程式人生 > 其它 >Android Sensor Input型別 (二) Device Driver

Android Sensor Input型別 (二) Device Driver

SENSORS 裝置驅動

1.1 Device tree 配置

在msm8909平臺上,根據硬體原理圖設計得知sensors 是掛載在BLSP1 QUP1 上,所以需要在 i2c1 的節點下配置,以磁感測器mmc3416 為例;

    mpu6050@68 {
        compatible = "invn,mpu6050";
        reg = <0x68>;
        pinctrl-names = "mpu_default","mpu_sleep";
        pinctrl-0 = <&mpu6050_default>;
        pinctrl-1 = <&mpu6050_sleep>;
        interrupt-parent = <&msm_gpio>;
        interrupts = <96 0x1>;
        vdd-supply = <&pm8909_l17>;
        vlogic-supply = <&pm8909_l6>;
        invn,gpio-int = <&msm_gpio 96 0x1>;
        invn,place = "Portrait Down";
    };
    mmc3416x@30 { /* Magnetic field sensor */
        compatible = "memsic,mmc3416x";
        reg = <0x30>;
        vdd-supply = <&pm8909_l17>;
        vio-supply = <&pm8909_l6>;
        memsic,dir = "obverse-x-axis-forward";
        memsic,auto-report;
    };

從以上兩個裝置樹的資訊可知 在sensors device tree 的配置中主要是配置,ic 的供電,i2c 從裝置地址, 中斷gpio 腳,以及特有的sensor 屬性等,具體的作用,待解析裝置驅動再做簡要的說明。

1.2 裝置驅動編譯

在msm8909平臺上,sensors 存放的目錄一般是選擇如下路徑下:
msm8909/code/kernel/drivers/input/misc/
以mpu6050 和 mmc3416 為例,需要在
msm8909/code/kernel/arch/arm/configs/msm8909-1gb_defconfig 中將編譯的巨集控開啟,如下配置:

CONFIG_SENSORS_MPU6050=y
CONFIG_SENSORS_MMC3416X=y

編譯完成後,檢視out 目錄是否生成對應的.o 檔案。

1.3 裝置驅動解析

以mmc3416 為例解析驅動的邏輯

1.3.1 裝置驅動註冊

static struct of_device_id mmc3416x_match_table[] = {
    { .compatible = "memsic,mmc3416x", },
    { },
};
static struct i2c_driver mmc3416x_driver = {
    .probe      = mmc3416x_probe,
    .remove     = mmc3416x_remove,
    .id_table   = mmc3416x_id,
    .driver     = {
        .owner  = THIS_MODULE,
        .name   = MMC3416X_I2C_NAME,
        .of_match_table = mmc3416x_match_table,
        .pm = &mmc3416x_pm_ops,
    },
};
module_i2c_driver(mmc3416x_driver);

在mmc3416x.c的驅動中,首先是使用module_i2c_driver將其註冊i2c裝置總線上, 這個介面是moudle_init 和 i2c_add_driver 結合後的二次封裝,被註冊的i2c_driver是mmc3416x_driver

這裡主要關注註冊資訊中的 of_match_table 屬性;mmc3416x_match_table首個元素的compatible與device tree中配置的compatible相同,則mmc3416x_probe將會被呼叫。

1.3.2 probe 流程分析

這裡以mmc3416x.c 驅動 為例,其邏輯相對而言最簡單,但是對於msm8909 平臺的sensors 驅動架構來說核心結構都在。 整個probe 函式 精簡後如下:

static int mmc3416x_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
struct mmc3416x_data *memsic; 
//定義裝置結構,後面會介紹其內容
if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) ; 
//檢測申請的i2c是否可用
memsic = devm_kzalloc(&client->dev, sizeof(struct mmc3416x_data), GFP_KERNEL);
//為裝置結構申請空間
if (client->dev.of_node)
mmc3416x_parse_dt(client, memsic);
 //如果獲取到裝置樹node,解析其裝置樹資訊。
	//這裡就是使用of函式族 獲取了memsic,dir及 memsic,auto-report填充到裝置結構中。
else
    memsic->dir = 0;
    memsic->auto_report = 1; //如果未獲取到裝置樹node,使用一個預設值。
   	memsic->i2c = client;         //填充i2c client 到裝置結構中。
   	dev_set_drvdata(&client->dev, memsic); //設定device私有資料,即將裝置結構填充到 device的私有資料中去。
    mutex_init(&memsic->ecompass_lock);
    mutex_init(&memsic->ops_lock); //初始化兩個互斥鎖
memsic->regmap = devm_regmap_init_i2c(client, &mmc3416x_regmap_config);
//申請以後 regmap,i2c相關的讀寫都是通過 regmap相關介面來完成。
    res = mmc3416x_power_init(memsic);   //獲取裝置樹配置的 vdd,vio。
    res = mmc3416x_check_device(memsic); //通過regmap_read讀取裝置id,判斷裝置.
memsic->idev = mmc3416x_init_input(client);
// 輸入子系統註冊
    memsic->data_wq = NULL;
if (memsic->auto_report) {  
//如果支援自動上報,則註冊一個等待佇列
        dev_dbg(&client->dev, "auto report is enabled\n");
        INIT_DELAYED_WORK(&memsic->dwork, mmc3416x_poll);
        memsic->data_wq =
        create_freezable_workqueue("mmc3416_data_work");
    }
memsic->cdev = sensors_cdev;  
//將定義好的sensors_classdev填充到裝置結構的cdev中
memsic->cdev.sensors_enable = mmc3416x_set_enable; 
//填充sensors_enable
memsic->cdev.sensors_poll_delay = mmc3416x_set_poll_delay; 
//填充sensors_poll_delay
res = sensors_classdev_register(&memsic->idev->dev, &memsic->cdev);
//繫結device註冊填充好的sensors_classde到 sensors class中。
    res = mmc3416x_power_set(memsic, false); //失能電
memsic->poll_interval = MMC3416X_DEFAULT_INTERVAL_MS; 
//設定輪詢時間間隔
}

probe總結:

平臺上的sensors驅動結構,probe主要做且都會做的一個步驟主要就是以下幾步:

  • 封裝裝置結構,主要是獲取電,gpio等資訊,初始化用到的資料結構並填充到device私有資料中。
  • 註冊輸入子系統
  • 填充並註冊sensors_classdev到 sensor class中去
  • 設定延時工作佇列,probe結束,然後就是等待排程

1.3.3 驅動driver 分析

根據上小節的總結,我們對這幾個主要的地方做更深一步的分析。
首先先來看下sensor 裝置結構的構成,這裡還是以mmc3416x 為例。

struct mmc3416x_data {
struct mutex ecompass_lock;
struct mutex ops_lock; //互斥鎖
struct workqueue_struct *data_wq; //工作佇列
struct delayed_work dwork; //延時執行的work
struct sensors_classdev cdev; //sensors_class 裝置結構
struct mmc3416x_vec last;
struct i2c_client *i2c; //i2c client
struct input_dev *idev; //input device
struct regulator *vdd; //2.8v 電
struct regulator *vio; //1.8v 電
struct regmap *regmap; //獲取regmap 對i2c 通訊介面的封裝
int dir; //獲取的dir
int auto_report; //獲取的是否自動上報的配置
int enable;
int poll_interval; //輪詢時間間隔的設定
int power_enabled; //上電的狀態
unsigned long timeout; //超時時間
};

相關輸入子系統的註冊:

static struct input_dev *mmc3416x_init_input(struct i2c_client *client)
{
struct input_dev *input = NULL;    
input = devm_input_allocate_device(&client->dev);  
//申請一個 input device
    input->name = "compass";
    input->phys = "mmc3416x/input0";
    input->id.bustype = BUS_I2C;
    //填充input相關的裝置資訊
    __set_bit(EV_ABS, input->evbit);
    //設定輸入事件為ABS類,即絕對座標類
    input_set_abs_params(input, ABS_X, -2047, 2047, 0, 0); 
    input_set_abs_params(input, ABS_Y, -2047, 2047, 0, 0);
    input_set_abs_params(input, ABS_Z, -2047, 2047, 0, 0);
    //設定事件程式碼為ABS_X, ABS_Y, ABS_Z ,並設定了abs相關的座標範圍
    input_set_capability(input, EV_REL, REL_X);
    input_set_capability(input, EV_REL, REL_Y);
    input_set_capability(input, EV_REL, REL_Z);
// 設定了 輸入事件為REL類,即相對座標類,支援事件程式碼分別為 REL_X, REL_Y, REL_Z。
    status = input_register_device(input);  //註冊這個input裝置到輸入子系統。
    return input;
}

接下來是填充sensors_classdev ,並註冊到 sensor_class中, 這裡的核心就是根據sensor_class的要求,將所有資訊通過sensors_classdev傳遞上去,
如果是裝置資訊直接賦值,如果是操作函式則通過函式指標回撥。關於msm8909使用的sensor_class的架構留在下一章詳細分析,這裡主要分析被傳遞的裝置資訊和回撥的函式介面。

以下是mmc3416x用到的sensors_classdev裝置資訊,更多的內容請檢視sensors_classdev的結構體型別定義。

static struct sensors_classdev sensors_cdev = {
    .name = "mmc3416x-mag",                     //sensor name
    .vendor = "MEMSIC, Inc",                    //廠商資訊
    .version = 1,                                //版本號
    .handle = SENSORS_MAGNETIC_FIELD_HANDLE,  //
    .type = SENSOR_TYPE_MAGNETIC_FIELD,        //2表示type為 磁力感測器
    .max_range = "1228.8",                         
    .resolution = "0.0488228125",
    .sensor_power = "0.35",
    .min_delay = 10000,
    .max_delay = 10000,
    .fifo_reserved_event_count = 0,
    .fifo_max_event_count = 0,
    .enabled = 0,
    .delay_msec = MMC3416X_DEFAULT_INTERVAL_MS,
    .sensors_enable = NULL,                      
    .sensors_poll_delay = NULL,                  
};

在probe中設定了兩個函式的回撥:

	memsic->cdev.sensors_enable = mmc3416x_set_enable;
	memsic->cdev.sensors_poll_delay = mmc3416x_set_poll_delay;

這兩個函式最終的目的最終都是為例呼叫工作佇列的處理函式,就輪詢讀取sensor獲取的座標資訊。

下面是這兩個回撥的實現:

static int mmc3416x_set_enable(struct sensors_classdev *sensors_cdev,
        unsigned int enable)
{
    struct mmc3416x_data *memsic = container_of(sensors_cdev,
            struct mmc3416x_data, cdev);
    //通過cdev獲取到sensor裝置結構體。
mutex_lock(&memsic->ops_lock);
if (enable && (!memsic->enable)) {
rc = mmc3416x_power_set(memsic, true); //上電
      rc = regmap_write(memsic->regmap, MMC3416X_REG_CTRL,MMC3416X_CTRL_TM);
      // 傳送TM命令 在讀資料之前
        memsic->timeout = jiffies;
        if (memsic->auto_report)
            queue_delayed_work(memsic->data_wq,
                &memsic->dwork,
                msecs_to_jiffies(memsic->poll_interval)); //呼叫延時工作佇列
} 
else if ((!enable) && memsic->enable) {
        if (memsic->auto_report)
            cancel_delayed_work_sync(&memsic->dwork);
        if (mmc3416x_power_set(memsic, false)) 
            //接收到enable = 0,輪訓結束,下電
    }
    memsic->enable = enable;
}

static int mmc3416x_set_poll_delay(struct sensors_classdev *sensors_cdev,
        unsigned int delay_msec)
{
    struct mmc3416x_data *memsic = container_of(sensors_cdev,
            struct mmc3416x_data, cdev);
    //通過cdev獲取到sensor裝置結構體。
    mutex_lock(&memsic->ops_lock);
    if (memsic->poll_interval != delay_msec)
        memsic->poll_interval = delay_msec;
     //根據傳入的時間引數更新 延時執行的時間,就輪詢間隔。
    if (memsic->auto_report && memsic->enable)
        mod_delayed_work(system_wq, &memsic->dwork,
                msecs_to_jiffies(delay_msec));
    mutex_unlock(&memsic->ops_lock);

    return 0;
}

最後就來分析下工作佇列的處理函式是如何呼叫工作的,根據初始化的延時執行work的處理函式INIT_DELAYED_WORK(&memsic->dwork, mmc3416x_poll); 這個work被假如到data_wq工作佇列中。

mmc3416x_poll

static void mmc3416x_poll(struct work_struct *work)
{
    int ret;
    s8 *tmp;
    struct mmc3416x_vec vec;
    struct mmc3416x_vec report;
    struct mmc3416x_data *memsic = container_of((struct delayed_work *)work,
            struct mmc3416x_data, dwork);
//通過cdev獲取到sensor裝置結構體。
    ktime_t timestamp;
    vec.x = vec.y = vec.z = 0;
    ret = mmc3416x_read_xyz(memsic, &vec);

    tmp = &mmc3416x_rotation_matrix[memsic->dir][0];
    report.x = tmp[0] * vec.x + tmp[1] * vec.y + tmp[2] * vec.z;
    report.y = tmp[3] * vec.x + tmp[4] * vec.y + tmp[5] * vec.z;
    report.z = tmp[6] * vec.x + tmp[7] * vec.y + tmp[8] * vec.z;

    timestamp = ktime_get_boottime();
    input_report_abs(memsic->idev, ABS_X, report.x);
    input_report_abs(memsic->idev, ABS_Y, report.y);
    input_report_abs(memsic->idev, ABS_Z, report.z);
    input_event(memsic->idev,
            EV_SYN, SYN_TIME_SEC,
            ktime_to_timespec(timestamp).tv_sec);
    input_event(memsic->idev,
        EV_SYN, SYN_TIME_NSEC,
        ktime_to_timespec(timestamp).tv_nsec);
    input_sync(memsic->idev);
    //上報資訊
exit:
    queue_delayed_work(memsic->data_wq,
            &memsic->dwork,
            msecs_to_jiffies(memsic->poll_interval));
    //再次排程work,延時時間為poll_interval
}