重力傳感器代碼分析node
重力傳感器驅動的功能,主要是向HAL層提供IOCTRL接口,並經過input設備上報數據。芯片實際數據的讀取是採用i2c協議讀取原始數據,而且做爲i2c設備掛載在系統上工做的。linux
1、調用關係算法
採用模塊化的編程方式,一下介紹函數的調用關係。編程
module_init(aac_MMAxxxxx_init);數組
module_exit(aac_MMAxxxxx_exit);安全
模塊中定義了驅動初始化和退出函數,具體實現以下模塊化
static int __init aac_MMAxxxxxFC_init(void)函數
{spa
int ret;線程
if ((ret = i2c_add_driver(&aac_MMAxxxxxFC_i2c_driver))) {
printk(KERN_WARNING "aac_MMAxxxxxFC_init failed. /n");
return ret;
}
return ret;
}
static void __exit aac_MMAxxxxxFC_exit(void)
{
i2c_del_driver(&aac_MMAxxxxxFC_i2c_driver);
}
調用i2c_add_driver函數將aac_MMAxxxxxFC_i2c_driver驅動添加實現了初始化函數,exit函數則調用i2c_del_driver函數將aac_MMAxxxxxFC_i2c_driver驅動刪除。
對於aac_MMAxxxxxFC_i2c_driver驅動結構體,由6個參數實現該結構體。Driver用模塊名來填充,probe、remove、suspend、resume分別用相對應的函數來填充。Id_table用aac_ MMAxxxxxFC_id來填充索引表。實現代碼以下:
static struct i2c_driver aac_MMAxxxxxFC_i2c_driver = {
.driver = {
.name = MMAxxxxx_MODULE_NAME,
},
.probe = aac_MMAxxxxxFC_probe,
.remove = aac_MMAxxxxxFC_remove,
.id_table = aac_MMAxxxxxFC_id,
#ifndef CONFIG_HAS_EARLYSUSPEND
.suspend = mmaxxxxx_suspend,
.resume = mmaxxxxx_resume,
#endif
};
1.一、 對於結構體中索引表aac_MMAxxxxxFC_id數組,具體實現以下:
static const struct i2c_device_id aac_MMAxxxxxFC_id[] = {
{ MMAxxxxx_MODULE_NAME, 0 },
{ }
};
1.二、 MMAxxxxx_MODULE_NAME表示驅動名。在頭文件中定義了具體名字"mmaxxxxx"。
1.三、 aac_MMAxxxxxFC_probe函數是i2c驅動尋找設備的經典實現,這裏將具體分析下實現過程。實現思路是首先註冊i2c功能函數類型,而後分配misc設備空間並註冊,接下來分配輸入設備空間並註冊,注意將misc設備獲取數據傳給input設備數據中。最後建立工做隊列,實現位置信息數據處理。
具體代碼以下:
1.3.1調用i2c_check_functionality,函數返回咱們須要類型的i2c適配器
if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
ret = -ENODEV;
goto exit0;
}
而且將入口函數參數client賦值給靜態全局變量g_client
g_client = client;
1.3.2調用kzalloc函數給空結構體賦值,空結構體的意義在於尋址。調用sysfs_create_group函數將設備體建立到mmaxxxxx_attr_group組,主要爲調試使用,關於具體調試文件系統將在文章後面章節介紹。調用misc_register函數註冊misc設備mmaxxxxx_misc_device。
mmaxxxxx_misc_data = kzalloc(sizeof(struct mmaxxxxx_data), GFP_KERNEL);
if (!mmaxxxxx_misc_data) {
ret = -ENOMEM;
goto exit1;
}
//init sysfs entry
ret = sysfs_create_group(&client->dev.kobj, &mmaxxxxx_attr_group);
if (ret)
goto exit2;
//misc_register
ret = misc_register(&mmaxxxxx_misc_device);
if (ret < 0) {
dev_err(&client->dev, "mmaxxxxx_device register failed/n");
goto exit3;
}
1.3.3調用input_allocate_device函數給input設備分配空間
//input_allocate
input = input_allocate_device();
if (!input) {
ret = -ENOMEM;
printk("input device allocate failed/n");
goto exit4;
}
對input各個屬性項目填充,name、phys表示映射的物理端口、id.bustype、id.vendor、id.product、
input->name = "mmaxxxxx";
input->phys = "mmaxxxxx/input0";
input->id.bustype = BUS_HOST;
input->id.vendor = 0x0001;
input->id.product = 0x0001;
input->id.version = 0x0100;
//evbit選擇了事件類型,absbit表示了絕對值的數據
input->evbit[0] = BIT(EV_ABS);
input->absbit[0] = BIT(ABS_X) | BIT(ABS_Y) | BIT(ABS_Z);
//將input賦值給全局變量g_input_dev設備。
g_input_dev = input;
ret = input_register_device(g_input_dev);
if (ret) {
printk("unable to register input polled device /n");
goto exit5;
}
//把misc設備數據賦值給輸入設備系統數據
input_set_drvdata(g_input_dev, mmaxxxxx_misc_data);
1.3.4調用init_MUTEX函數將信號量申請爲互斥體信號,在分別在mmaxxxxx_misc_ioctl、asensor_thread、mmaxxxxx_resume函數中調用,確保時間可以安全賦值。
而後建立工做隊列asensor_wq,調用INIT_DELAYED_WORK函數asensor_thread數據處理函數添加到工做任務asensor_delayed_work當中。注意,此時工做隊列並無開始工做,須要mmaxxxxx_misc_ioctl接到HAL層的控制信息時纔打開端口,進行工做。
init_MUTEX(&sem_thread);
asensor_wq = create_workqueue("MMAxxxxxFC_workqueue");
if (!asensor_wq) {
printk("can't create a workqueue/n");
ret = -1;
goto exit6;
}
INIT_DELAYED_WORK(&asensor_delayed_work, asensor_thread);
1.四、 aac_MMAxxxxxFC_remove函數是做爲probe函數的反過程實現的,主要是取消工做隊列和釋放相關資源。
具體代碼實現以下
ret = cancel_delayed_work(&asensor_delayed_work);
if(ret == 0){
flush_workqueue(asensor_wq);
}
destroy_workqueue(asensor_wq);
asensor_wq = NULL;
#ifdef CONFIG_HAS_EARLYSUSPEND
unregister_early_suspend(&early_suspend);
#endif
//clean input
mmaxxxxx_input_cleanup();
//sysfs
sysfs_remove_group(&client->dev.kobj, &mmaxxxxx_attr_group);
//misc
misc_deregister(&mmaxxxxx_misc_device);
1.五、 mmaxxxxx_suspend和mmaxxxxx_resume函數主要是睡眠和喚醒時做用,關掉外設電源並釋放佔用的相關資源。喚醒時實現反過程。
static int mmaxxxxx_suspend(struct i2c_client *ic, pm_message_t mesg)
{
printk("########mmaxxxxx_suspend");
cancel_delayed_work_sync(&asensor_delayed_work);
aac_MMAxxxxxFC_close_client();
return 0;
}
static int mmaxxxxx_resume(struct i2c_client *ic)
{
printk("########mmaxxxxx_resume");
aac_MMAxxxxxFC_init_client();
down_interruptible(&sem_thread);
time_delay.tv_sec = 0;
time_delay.tv_usec = read_interval;
up(&sem_thread);
if(1 == thread_flag)
{
printk("########resume!/n");
queue_delayed_work(asensor_wq, &asensor_delayed_work, timeval_to_jiffies(&time_delay));
}
return 0;
}
2. 特殊處理函數
2.1 misc控制函數
主要做用是處理HAL層的IOCTL命令,起到打開、關閉的任務。
首先定義了混雜設備結構體mmaxxxxx_misc_device,該結構體體由3個field組成,第一個表示misc設備的此設備號,第二個爲misc設備的名字,第三個爲misc操做結構體。操做結構體由咱們自行定義。
static struct miscdevice mmaxxxxx_misc_device = {
.minor = MISC_DYNAMIC_MINOR,
.name = "mmaxxxxx",
.fops = &mmaxxxxx_misc_fops,
};
而後定義Misc操做結構體,該結構體由3個field組成,第一個表示全部者,屬性固定爲本模塊,即THIS_MODULE。第二個表示打開函數,處理數據信息,第三個表示控制函數,處理misc設備的相關控制命令。
static const struct file_operations mmaxxxxx_misc_fops = {
.owner = THIS_MODULE,
.open = mmaxxxxx_misc_open,
.ioctl = mmaxxxxx_misc_ioctl,
};
做爲傳感器輸入設備,打開函數使用的也是數據流,因此定位數據沒有意義。這種狀況下,不能簡單不聲明lseek操做,由於默認方法是容許定位的。默認定位的方法是調用lseek函數在數據區往上或往下定位數據。在open方法中調用nonseekable_open()時,它會通知內核設備不支持lseek。
函數實現以下:
static int mmaxxxxx_misc_open(struct inode *inode, struct file *file)
{
int err;
err = nonseekable_open(inode, file);
if (err < 0)
return err;
file->private_data = mmaxxxxx_misc_data;
return 0;
}
這裏注意的是文件的私有數據賦值對象爲mmaxxxxx_misc_data,是一個空結構體變量。難道也僅僅是爲了尋址麼?
Ioctl函數做爲misc設備核心的操做函數,主要做用是經過HAL層中相關command字的控制,給應用層提供了控制方法,最終實現設備體的狀態獲取,延時,激活,關閉,如匹配字不符合,則控制參數有誤退出。
Ioctl函數中主要包括的控制命令爲MMAxxxxx_IOCTL_GET_STATE、MMAxxxxx_IOCTL_SET_DELAY、MMAxxxxx_IOCTL_ENABLE、MMAxxxxx_IOCTL_DISABLE幾個命令。
具體可參考代碼。如MMAxxxxx_IOCTL_GET_STATE中主要經過copy_to_user將線程標示位賦值給參數argp,從而獲取狀態。其餘幾個具體參考代碼。
static int mmaxxxxx_misc_ioctl(struct inode *inode, struct file *file,
unsigned int cmd, unsigned long arg)
{
void __user *argp = (void __user *)arg;
int32_t interval;
switch( cmd )
{
case MMAxxxxx_IOCTL_GET_STATE:
{
if(copy_to_user(argp, &thread_flag, sizeof(thread_flag)))
return -EFAULT;
//printk("MMAxxxxx_IOCTL_GET_STAT/n");
break;
}
case MMAxxxxx_IOCTL_ENABLE:
{
aac_MMAxxxxxFC_init_client();
thread_flag = 1;
down_interruptible(&sem_thread);
time_delay.tv_sec = 0;
time_delay.tv_usec = read_interval;
up(&sem_thread);
queue_delayed_work(asensor_wq, &asensor_delayed_work, timeval_to_jiffies(&time_delay));
//printk("MMAxxxxx_IOCTL_ENABLE/n");
break;
}
}
}
在MMAxxxxx_IOCTL_ENABLE控制命令下,經過原子操做定義了延時的時間,將工做任務asensor_delayed_work添加到工做隊列asensor_wq中,這樣就循環開始了該工做。
2.2 工做函數asensor_thread
工做函數中主要是經過i2c線讀取相關的輸出數據。I2c讀取的方式這裏再也不詳述,這裏主要經過調用i2c_smbus_read_i2c_block_data函數,讀取連續三個地址的數值,經過數據處理,根據硬件相關的貼片方式,輸出正確的xyz結果。
處理過的結果用自定義的結構體保存。
struct _mmaxxxxx_data{
int x_data;
int y_data;
int z_data;
};
這裏有個須要處理的地方就是有些芯片靈敏度太高,能夠經過濾波算法進行相關的去抖動處理。具體參考後續文章。
3 調試信息控制文件接口
這裏經過static int g_print = 0來實現是否輸出打印信息,介紹相關知識以前,須要先了解linux內核中sys文件系統的介紹。sysfs 屬性的功能只能靠閱讀源代碼來理解。在內核中, sysfs 屬性通常是由 __ATTR 系列的宏來聲明的,如對設備的使用 DEVICE_ATTR ,對總線使用 BUS_ATTR ,對驅動使用 DRIVER_ATTR ,對類別(class)使用 CLASS_ATTR, 這四個高級的宏來自於 <include/linux/device.h>, 都是以更低層的來自 <include/linux/sysfs.h> 中的 __ATTR/__ATRR_RO 宏實現;所以咱們在內核源碼樹中相應位置 drivers/scsi/ 找到這幾個宏的使用狀況,能夠獲得在 drivers/scsi/scsi_sysfs.c 中。
下面經過代碼介紹DEVICE_ATTR的添加過程。
3.1定義控制變量
static int g_print = 0;
3.2定義mmaxxxxx_show_print函數
功能主要是將g_print打印到內存當中。
static ssize_t mmaxxxxx_show_print(struct device *dev,
struct device_attribute *attr, char *buf)
{
return sprintf(buf, "%d/n", g_print);
}
3.3定義mmaxxxxx_store_print函數
功能主要是獲取buf中存在的控制值。
static ssize_t mmaxxxxx_store_print(struct device *dev,
struct device_attribute *attr, char *buf,size_t count)
{
unsigned long val = simple_strtoul(buf, NULL, 10);
//adjust_light(val);
//ggg=val;
g_print = val;
return count;
}
3.4填充設備屬性DEVICE_ATTR
static DEVICE_ATTR(print, S_IWUSR | S_IRUGO,mmaxxxxx_show_print, mmaxxxxx_store_print);
其中有四個參數,分別表示是稱、權限位、讀函數、寫函數。
3.5屬性數組mmaxxxxx_attributes
主要是填充設備屬性位置。
static struct attribute *mmaxxxxx_attributes[] = {
&dev_attr_print.attr,
NULL
};
3.6將屬性數組加到屬性組(group)裏。
static const struct attribute_group mmaxxxxx_attr_group = {
.attrs = mmaxxxxx_attributes,
};
至此完成了屬性組的添加工做,經過adb鏈接去硬件系統中對應的文件爲sys/devices/i2c-0/x-xxxx/print,x-xxxx對應的是芯片的地址線。
4 總結
文章中採用標準模塊化得方法,調用內核函數,將i2c模塊掛載到內核系統當中,並經過misc設備留接口給上層提供調用。在模塊工做過程當中,經過i2c讀函數獲取了實時的位置信息,並經過input設備將數據上報給用戶層。