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
}