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
}