最近刚看完freescale mma8451的驱动,并且一直了驱动,自己也没怎么改代码,不过读了一下代码,还是有点体会的,下面我就来分析一下。
首先看下代码结构,有兴趣的可以从一下方式获得代码,git@github.com:zhangjie201412/WorkSpace.git ,最好是先发mail给我,
jay@jay:~/mygit$ tree
.
└── kernel
├── arch
│ └── arm
│ └── mach-mx5
│ └── mx53_smd.c
├── drivers
│ └── hwmon
│ └── mma845x
│ ├── Makefile
│ ├── mma845x.c
│ ├── mma845x.h
│ ├── mma_char.c
│ ├── mma_core.c
│ ├── mma_input.c
│ ├── mma_regs.h
│ ├── mma_sysfs.c
│ └── mma_sysfs.h
└── include
└── linux
└── mma845x.h
其中一个头文件,然后是一个驱动包,里面内容蛮丰富的,还有就是要在板级文件里面注册platform的device端,我这里是基于iMX53的Android BSP
首先咱还是来找源头吧,找啊找,看名字就知道我们先要分析这个mma_core.c文件
static int __init init_mma845x(void)
{
/*
* register driver
*/
printk(KERN_INFO "add mma i2c driver\n");
return i2c_add_driver(&i2c_mma845x_driver);
}
/*!
* This function implements "exit" routine for driver
*/
static void __exit exit_mma845x(void)
{
printk(KERN_INFO "del mma i2c driver.\n");
#ifdef CONFIG_EARLYSUSPEND
// Register early_suspend and late_wakeup handlers
unregister_early_suspend(&mma845x_early_suspend_desc);
#endif
return i2c_del_driver(&i2c_mma845x_driver);
}
module_init(init_mma845x);
module_exit(exit_mma845x);
这个初始化和卸载模块就不多说了,注册了i2c类型的驱动,接着去platform文件中找注册的device端,mx53_smd.c
//+++add head file for g-sensor
#include <linux/mma845x.h>
//++++add for mma driver
static struct mxc_mma845x_platform_data mma845x_data = {
.gpio_pin_get = NULL,
.gpio_pin_put = NULL,
.int1 = gpio_to_irq(MX53_SMD_ACCL_INT1_IN), // ACCL_INT1 is gpio for MMA845X INT1
.int2 = gpio_to_irq(MX53_SMD_ACCL_INT2_IN), // ACCL_INT2 is gpio for MMA845X INT2
};
//-----add by Jay end
static struct i2c_board_info mxc_i2c0_board_info[] __initdata = {
{
.type = "mma845x",
.addr = 0x1C,
// .irq = gpio_to_irq(MX53_SMD_ACCL_INT1_IN), //+++++add by Jay for g-sensor interrupt mode
.platform_data = (void *)&mma845x_data, //+++add by Jay
},
{
.type = "ov5642",
.addr = 0x3C,
.platform_data = (void *)&camera_data,
},
{
.type = "mma7660",
.addr = 0x4C,
},
};
这里,填充了mma845x驱动的一些platform_data
然后是注册,
i2c_register_board_info(0, mxc_i2c0_board_info,
ARRAY_SIZE(mxc_i2c0_board_info));
这个函数会把i2c0上的所有驱动都挂到platform driver的device上去,进行匹配,这里我要提示的一点是,这里的2根GPIO 中断引脚,我们要设置为input
//+++++add by Jay for g-sensor interrupt mode to get data
gpio_request(MX53_SMD_ACCL_INT1_IN, "gsensor-irq");
msleep(5);
gpio_direction_input(MX53_SMD_ACCL_INT1_IN);
//-----end add
接下来分析mma_core.c中的probe函数,这里是最终要的一个函数了
/*!
* This function implements probe routine for driver
* client : pointer to i2c client
* id : i2c device id
* return 0 : Success
* return -ENOMEM : Memory allocation for pDev failed
* return -ENODEV : Platform data not found
*/
static int mma845x_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
int ret = 0,
i = 0;
struct ChipInfo_t *pChip = NULL;
struct mxc_mma_device_t *pDev = NULL;
int mask = 0;
plat_data =
(struct mxc_mma845x_platform_data *) client->dev.platform_data;
if (plat_data == NULL) {
dev_err(&client->dev, "lack of platform data!\n");
return -ENODEV;
}
pDev = kzalloc(sizeof(struct mxc_mma_device_t), GFP_KERNEL);
if (!pDev) {
pDev = NULL;
return -ENOMEM;
}
printk(KERN_INFO "\r\nProbing Module: %s %s\r\n", MODULE_NAME, DRIVER_VERSION);
printk(KERN_INFO "Freescale Android 2.3 Release: %s\r\n", ANDROID_RELEASE);
printk(KERN_INFO "Build Date: %s [%s]\r\n", __DATE__, __TIME__);
i2c_set_clientdata(client, pDev);
/*
* bind the right device to the driver
*/
ret = IdentifyChipset(client);
if (ret < 0) {
printk(KERN_INFO "%s:: Unable to identify device.\r\n", __func__);
return -EIO;
}
/*
* Associate chip layer
*/
for (i = 0; i < sizeof(ChipTable) / sizeof(ChipTable[0]); i++) {
if (ChipTable[i]->ChipId == ret) {
pChip = ChipTable[i];
pChip->ChipId = ret;
pChip->client = client;
pChip->ChipType = ChipType;
break;
}
}
if (i >= (sizeof(ChipTable) / sizeof(ChipTable[0]))) {
printk(KERN_INFO "Chipset not supported by MMA driver\r\n");
return -ENOMEM;
}
gpChip = pChip;
gpDev = pDev;
SetDefaultVal(pChip);
/*
* Inialize default event codes
*/
pDev->fm_event_type = 0x25;
pDev->ornt_event_type = 0x26;
pDev->trans_event_type = 0x27;
pDev->event_type_single = 0x28;
pDev->event_type_double = 0x29;
pDev->aflag = 1;
/*
* Initialize chipset
*/
pChip->Init(pChip);
pDev->pChip = pChip;
pDev->version = 1;
/*
* configure gpio as input for interrupt monitor
*/
if(plat_data->gpio_pin_get)
plat_data->gpio_pin_get();
/*
* Register character device
*/
pDev->major = register_chrdev(0, "mma", &mma_fops);
if (ret < 0) {
printk(KERN_INFO "%s:: Unable to register device\r\n", __func__);
goto error_disable_power;
}
strcpy(pDev->devname, "mma");
printk(KERN_INFO "%s:: Registered char dev \"%s\" @ %d\r\n", __func__,
pDev->devname, pDev->major);
/*
* Initialize input layer
*/
InitializeInputInterface(pDev);
/*
* Create sysfs entries
*/
InitializeSysfs( (struct i2c_client *)client);
/*
* Initialize semaphore for interrupt
*/
sema_init(&chip_ready, 0);
setup_timer(&stall_timer, stall_timer_fn, 0);
hIstThread = kthread_run(IntServiceThread, pDev, "mxc845x_ist");
if (IS_ERR(hIstThread)) {
printk(KERN_INFO "Error creating mxc845x ist.\n");
goto error_free_irq1;
}
if (plat_data->int1 > 0) {
set_irq_type(plat_data->int1, IRQF_TRIGGER_FALLING);
ret = request_irq(plat_data->int1, mma845x_interrupt,
IRQF_TRIGGER_FALLING, DEVICE_NAME, pDev);
if (ret) {
dev_err(&client->dev, "request_irq(%d) returned error %d\n",
plat_data->int1, ret);
goto error_disable_power;
}
}
else {
gpChip->DisableInt(INT_EN_FIFO | INT_EN_DRDY);
poll_mode = 1;
}
if (plat_data->int2 > 0){
printk(KERN_INFO "%s:: Configuring interrupt IRQ [%d]\r\n", __func__,
plat_data->int2);
set_irq_type(plat_data->int2, IRQF_TRIGGER_FALLING);
ret = request_irq(plat_data->int2, mma845x_interrupt,
IRQF_TRIGGER_FALLING, DEVICE_NAME, pDev);
if (ret) {
dev_err(&client->dev, "request_irq(%d) returned error %d\n",
plat_data->int2, ret);
goto error_free_irq1;
}
}
#ifdef CONFIG_EARLYSUSPEND
register_early_suspend(&mma845x_early_suspend_desc);
#endif
注释也写的蛮清楚的但是咱还是一起来看一下
plat_data =
(struct mxc_mma845x_platform_data *) client->dev.platform_data;
if (plat_data == NULL) {
dev_err(&client->dev, "lack of platform data!\n");
return -ENODEV;
}
这里是由probe函数传进来的client参数来得到platform_data,就是我们在板级文件中填充的那些个结构体,我觉得Linux设计的这个platform架构的驱动很好,他可以很好的把驱动和设备隔离开来,虽然有的时候还是会联系到一起,但是对于可移植性更强了,因为,驱动只是对设备的统一管理,而platform device端只需要配置cpu的gpio跟deivce的连接情况,还有一些soc中继承的功能的配置以及注册。
pDev = kzalloc(sizeof(struct mxc_mma_device_t), GFP_KERNEL);
if (!pDev) {
pDev = NULL;
return -ENOMEM;
}
printk(KERN_INFO "\r\nProbing Module: %s %s\r\n", MODULE_NAME, DRIVER_VERSION);
printk(KERN_INFO "Freescale Android 2.3 Release: %s\r\n", ANDROID_RELEASE);
printk(KERN_INFO "Build Date: %s [%s]\r\n", __DATE__, __TIME__);
接着这里是给我们驱动中定义的mxc_mma_device_t结构体分配内存,然后是一些kernel info的打印没什么好说了。
还是先来看一下这个结构体吧,
/*sensor platform data structure for mma8451/mma8452/mma8453*/
struct mxc_mma845x_platform_data {
void (*gpio_pin_get) (void);
void (*gpio_pin_put) (void);
int int1;
int int2;
};
这个结构体比较简单,定义了2个中断号,还有中断GPIO的get和put,我们这边对这2个函数没有实现,我猜想get函数是对中断GPIO的配置,put是对中断GPIO的free,拙见而已,
i2c_set_clientdata(client, pDev);
/*
* bind the right device to the driver
*/
ret = IdentifyChipset(client);
if (ret < 0) {
printk(KERN_INFO "%s:: Unable to identify device.\r\n", __func__);
return -EIO;
}
/*
* Associate chip layer
*/
for (i = 0; i < sizeof(ChipTable) / sizeof(ChipTable[0]); i++) {
if (ChipTable[i]->ChipId == ret) {
pChip = ChipTable[i];
pChip->ChipId = ret;
pChip->client = client;
pChip->ChipType = ChipType;
break;
}
}
if (i >= (sizeof(ChipTable) / sizeof(ChipTable[0]))) {
printk(KERN_INFO "Chipset not supported by MMA driver\r\n");
return -ENOMEM;
}
gpChip = pChip;
gpDev = pDev;
SetDefaultVal(pChip);
/*
* Inialize default event codes
*/
pDev->fm_event_type = 0x25;
pDev->ornt_event_type = 0x26;
pDev->trans_event_type = 0x27;
pDev->event_type_single = 0x28;
pDev->event_type_double = 0x29;
pDev->aflag = 1;
/*
* Initialize chipset
*/
pChip->Init(pChip);
pDev->pChip = pChip;
pDev->version = 1;
/*
* configure gpio as input for interrupt monitor
*/
if(plat_data->gpio_pin_get)
plat_data->gpio_pin_get();
/*
* Register character device
*/
这里首先是对mma_845x器件身份的识别,来看下IdentifyChipset函数
/*!
* This function Identify the chip connected on bus and associate client driver for the chipset
* client : i2c_client pointer for i2c bus attached to device
* return chip_id : Chip id of identified device
* return -1 : Device not identified
*/
static int IdentifyChipset(struct i2c_client *client)
{
int retVal = 0;
int ChipIdentified = 0;
retVal = i2c_smbus_read_byte_data(client, REG(WHO_AM_I));
switch (retVal) {
case ID_MMA8451:
{
printk("%s:: Found MMA8451 chipset with chip ID 0x%02x\r\n",
__func__, retVal);
ChipIdentified = 1;
}
break;
case ID_MMA8452:
{
printk("%s:: Found MMA8452 chipset with chip ID 0x%02x\r\n",
__func__, retVal);
ChipIdentified = 1;
}
break;
case ID_MMA8453:
{
printk("%s:: Found MMA8453 chipset with chip ID 0x%02x\r\n",
__func__, retVal);
ChipIdentified = 1;
}
break;
default:
{
printk("%s:: Not a valid MMA845X chipset. Chip ID 0x%02x\r\n",
__func__, retVal);
ChipIdentified = 0;
}
break;
读i2c bus上面mma传感器的WHO_AM_I 寄存器,来判断连上的是哪个device
/*
* Associate chip layer
*/
for (i = 0; i < sizeof(ChipTable) / sizeof(ChipTable[0]); i++) {
if (ChipTable[i]->ChipId == ret) {
pChip = ChipTable[i];
pChip->ChipId = ret;
pChip->client = client;
pChip->ChipType = ChipType;
break;
}
}
if (i >= (sizeof(ChipTable) / sizeof(ChipTable[0]))) {
printk(KERN_INFO "Chipset not supported by MMA driver\r\n");
return -ENOMEM;
}
gpChip = pChip;
gpDev = pDev;
SetDefaultVal(pChip);
这里首先是对芯片层的一些赋值,找到这里我们注册的是哪款芯片,然后把i2c client,chip id, chiptype等信息赋值给这里的局部变量,然后再把局部变量赋值给全局的几个指针,然后再试对芯片寄存器设置一些默认值。
/*
* Inialize default event codes
*/
pDev->fm_event_type = 0x25;
pDev->ornt_event_type = 0x26;
pDev->trans_event_type = 0x27;
pDev->event_type_single = 0x28;
pDev->event_type_double = 0x29;
pDev->aflag = 1;
/*
* Initialize chipset
*/
pChip->Init(pChip);
pDev->pChip = pChip;
pDev->version = 1;
然后是初始化芯片的事件类型,这里这块芯片分很多事件类型,有撞击事件,横竖切换事件等,接下来如果pChip结构体中Init成员函数非空的话就执行这个函数。
/*
* Register character device
*/
pDev->major = register_chrdev(0, "mma", &mma_fops);
if (ret < 0) {
printk(KERN_INFO "%s:: Unable to register device\r\n", __func__);
goto error_disable_power;
}
strcpy(pDev->devname, "mma");
printk(KERN_INFO "%s:: Registered char dev \"%s\" @ %d\r\n", __func__,
pDev->devname, pDev->major);
接下来注册了一个字符类型的驱动,咱来看看这个驱动是干啥用的,最主要的还是看mma_fops这个文件操作结构体中实现了哪些个东西,mma_char.c
/*
* This structure is the file operations structure, which specifies what
* callbacks functions the kernel should call when a user mode process
* attempts to perform these operations on the device.
*/
const struct file_operations mma_fops = {
.owner = THIS_MODULE,
.open = mma_open,
.ioctl = mma_ioctl,
.release = mma_release,
.read = mma_read,
};
这里有打开关闭,读,还有ioctl功能,
/*!
* Open call for accelerometer char driver.
* inode : Pointer to the node to be opened.
* file : Pointer to file structure.
* return 0 : After successful opening.
*/
static int mma_open(struct inode * inode, struct file * file)
{
pContext_t p = file->private_data;
if (!p) {
p = kmalloc(sizeof(*p), GFP_KERNEL);
if (!p)
return -ENOMEM;
p->threshold = 5;
p->read_pos = 0;
sema_init(&p->sem, 1);
file->private_data = p;
}
return 0;
}
首先是open函数,这里还要先看下pContext_t这个结构体
typedef struct
{
struct semaphore sem;
int threshold;
int read_pos;
}Context_t, *pContext_t;
里面有3个成员变量,乍看之下还不清楚是用来干嘛的,先不管,接着往下看就明白了,回到open函数中,首先把文件结构体中的数据给这个结构体,然后判断,这里的意思应该是如果open函数被打开了一次那么就会执行if中的代码,如果已经被打开了,那么就直接返回零,if语句中也就是多结构体的初始化,还有初始化了一个信号量,然后把p指针传给file->private,代表已经被打开了,非空。
/*!
* Release call for accelerometer char driver.
* inode : Pointer to the node to be opened.
* file : Pointer to file structure.
* return 0 : After successful release of resources.
*/
static int mma_release(struct inode *inode, struct file *file)
{
pContext_t p = file->private_data;
if (p)
{
mutex_destroy(&p->lock);
kfree(p);
p = NULL;
}
return 0;
}
release中的代码就不多说了,跟open函数正好相反。
然后是比较重要的ioctl和read function
/*!
* read call for accelerometer char driver.
* inode : Pointer to the node to be opened.
* file : Pointer to file structure.
* command : contains command for register.
* arg : contains data required to apply settings specified in cmd.
* return 0 : For successful
*/
static int mma_ioctl(struct inode *inode, struct file *filp,
unsigned int command, unsigned long arg)
{
pContext_t dev = filp->private_data;
u32 threshold = 0;
switch(command)
{
case MMA_GET_THRESHOLD:
{
if (copy_to_user(&arg, &(dev->threshold), sizeof(u32)))
{
printk("ioctl, copy to user failed\n");
return -EFAULT;
}
}
break;
case MMA_GET_THRESHOLD_VALUE:
{
return dev->threshold;
}
break;
case MMA_SET_THRESHOLD:
{
if (copy_from_user(&threshold, &arg, sizeof(u32)))
{
return -EFAULT;
}
if(threshold <= 0)
{
return -EINVAL;
}
dev->threshold = threshold;
}
break;
case MMA_SET_THRESHOLD_VALUE:
{
threshold = arg;
if(threshold <= 0)
{
printk("Invalid value [0x%x]\r\n", threshold);
return -EINVAL;
}
dev->threshold = threshold;
}
break;
case MMA_GET_ACCLEROMETER_FLAG:
{
return GetAcclerometerFlag();
}
break;
case MMA_SET_ACCLEROMETER_FLAG:
{
int AcclFlag = arg;
return SetAcclerometerFlag(AcclFlag);
}
break;
default:
{
printk("%s:: Invalid IOCTL [0x%x]\r\n", __func__, command);
}
}
return 0;
}
这个ioctl函数很简单,通过switch case语句,把用户传进去的参数分类,然后做出不同的动作,其中有对threshold的设置,读取,对acclerometer_flag的设置和获取等动作。
/*!
* read call for pressure char driver.
* file : Pointer to file structure.
* buf : Pointer to user buffer in which data will be read.
* size : Size of data be read.
* ppos : Pointer to Offset in the file.
* return 1. no of bytes read - For successful read \
2. ENOMEM - file private data pointer is NULL
*/
static ssize_t mma_read(struct file *file, char __user *buf, size_t size, loff_t *ppos)
{
int retval = 0;
unsigned short nr = 0;
pContext_t dev = file->private_data;
int bytestocopy = 0;
unsigned long bytescopied = 0;
char __user *buff = buf;
unsigned short hdr = 0xffff;
if (!dev)
{
return -ENOMEM;
}
/* Acquire semaphore to manage re-entrancy */
if (down_interruptible(&dev->sem))
return -ERESTARTSYS;
/* Loop till data available has crossed the watermark */
nr = GetAvailableData(dev->read_pos);
while (nr < dev->threshold )
{
/* Wait on accelerometer queue (AcclQ) till condition GetAvailableData(dev->read_pos) >= dev->threshold gets satisfied */
if (wait_event_interruptible(AcclDataQ,
(GetAvailableData(dev->read_pos) >= dev->threshold)))
return -ERESTARTSYS;
nr = GetAvailableData(dev->read_pos);
}
bytescopied = copy_to_user(buff, &hdr, sizeof(unsigned short));
retval += sizeof(unsigned short);
buff += sizeof(unsigned short);
bytescopied = copy_to_user(buff, &nr, sizeof(unsigned short));
retval += sizeof(unsigned short);
buff += sizeof(unsigned short);
/* Loop here to copy bytes to user buffer */
while(nr)
{
if(dev->read_pos + nr >= ACCL_FIFO_SIZE)
{
bytestocopy = ACCL_FIFO_SIZE - dev->read_pos ;
}
else
{
bytestocopy = nr;
}
/* Copy the required records to user buffer */
bytescopied = copy_to_user(buff, &AcclDataFifo[dev->read_pos], bytestocopy * sizeof(AcclData_t));
retval += bytestocopy * sizeof(AcclData_t);
buff += bytestocopy * sizeof(AcclData_t);
nr -= bytestocopy;
/* Increment the read_pos */
dev->read_pos += bytestocopy;
if(dev->read_pos >= ACCL_FIFO_SIZE)
dev->read_pos -= ACCL_FIFO_SIZE;
}
/* release the lock */
up(&dev->sem);
/* Return the number of bytes written to buffer */
return retval;
}
这个函数看上去很复杂,其实就做了一件事情,调用copy_to_user把数据传到用户空间,其实这里我很不明白这个字符驱动到底跟我们的mma器件有什么关系,因为这里read函数中我没有看到一点关于i2cread 的内容,所以我觉得,这里这个字符驱动是多余的,不过这里对等待队列的读操作有待学习。
我们也可以自己再这里加上对I2C的读操作来把这里的read函数挂到用户空间,我想这里的ioctl函数应该是来设置threshold的,应该是用在HAL层中来被设置的。
而这里的read函数也可以当做poll模式下来读取xyz的数据,大家可以看到在read中有作比较,当读回来的数据大于threshold时才把数据copy到用户空间。
OK,这个字符驱动暂时介绍到这边,下面我们接着看mma_core.c中的probe函数
/*
* Initialize input layer
*/
InitializeInputInterface(pDev);
/*
* Create sysfs entries
*/
InitializeSysfs( (struct i2c_client *)client);
/*
* Initialize semaphore for interrupt
*/
sema_init(&chip_ready, 0);
setup_timer(&stall_timer, stall_timer_fn, 0);
hIstThread = kthread_run(IntServiceThread, pDev, "mxc845x_ist");
if (IS_ERR(hIstThread)) {
printk(KERN_INFO "Error creating mxc845x ist.\n");
goto error_free_irq1;
}
if (plat_data->int1 > 0) {
set_irq_type(plat_data->int1, IRQF_TRIGGER_FALLING);
ret = request_irq(plat_data->int1, mma845x_interrupt,
IRQF_TRIGGER_FALLING, DEVICE_NAME, pDev);
if (ret) {
dev_err(&client->dev, "request_irq(%d) returned error %d\n",
plat_data->int1, ret);
goto error_disable_power;
}
}
else {
gpChip->DisableInt(INT_EN_FIFO | INT_EN_DRDY);
poll_mode = 1;
}
if (plat_data->int2 > 0){
printk(KERN_INFO "%s:: Configuring interrupt IRQ [%d]\r\n", __func__,
plat_data->int2);
set_irq_type(plat_data->int2, IRQF_TRIGGER_FALLING);
ret = request_irq(plat_data->int2, mma845x_interrupt,
IRQF_TRIGGER_FALLING, DEVICE_NAME, pDev);
if (ret) {
dev_err(&client->dev, "request_irq(%d) returned error %d\n",
plat_data->int2, ret);
goto error_free_irq1;
}
}
这里是一些列的初始化,input sybsystem、文件系统、信号量、定时器、线程的初始化,先看输入子系统初始化过程
int InitializeInputInterface(struct mxc_mma_device_t *pDev)
{
int RetVal = 0;
int i = 0;
if(pDev == NULL)
{
printk("%s: pDev => NULL pointer\r\n", __func__);
return -EPERM;
}
pDev->inp1 = input_allocate_device();
if (!pDev->inp1)
{
RetVal = -ENOMEM;
printk(KERN_ERR
"%s: Failed to allocate input device-1\n", __func__);
return RetVal;
}
set_bit(EV_ABS, pDev->inp1->evbit); // Accelerometer readings
/* yaw */
input_set_abs_params(pDev->inp1, ABS_RX, 0, 360, 0, 0);
/* pitch */
input_set_abs_params(pDev->inp1, ABS_RY, -180, 180, 0, 0);
/* roll */
input_set_abs_params(pDev->inp1, ABS_RZ, -90, 90, 0, 0);
/* x-axis acceleration */
input_set_abs_params(pDev->inp1, ABS_X, -32768, 32767, 0, 0);
/* y-axis acceleration */
input_set_abs_params(pDev->inp1, ABS_Y, -32768, 32767, 0, 0);
/* z-axis acceleration */
input_set_abs_params(pDev->inp1, ABS_Z, -32768, 32767, 0, 0);
pDev->inp1->name = "mma8451";
RetVal = input_register_device(pDev->inp1);
if (RetVal)
{
printk(KERN_ERR "%s: Unable to register input device: %s\n",__func__, pDev->inp1->name);
return RetVal;
}
/* Register input device 2 */
pDev->inp2 = input_allocate_device();
if (!pDev->inp2)
{
RetVal = -ENOMEM;
printk(KERN_ERR "%s: Failed to allocate input device-2\n", __func__);
return RetVal;
}
/* Initialize all event codes as this is a configurable param and may change runtime from user space */
for(i = 0x20; i < 0x40; i++)
input_set_abs_params(pDev->inp2, i, 0, 255, 0, 0);
pDev->inp2->mscbit[0] = BIT_MASK(MSC_RAW) | BIT_MASK(MSC_SCAN);
pDev->inp2->name = "Accl1";
set_bit(EV_ABS, pDev->inp2->evbit);
set_bit(EV_KEY, pDev->inp2->evbit);
set_bit(EV_MSC, pDev->inp2->evbit);
RetVal = input_register_device(pDev->inp2);
bitmap_fill(pDev->inp2->keybit, KEY_MAX);
if (RetVal)
{
printk(KERN_ERR "%s: Unable to register input device: %s\n", __func__, pDev->inp2->name);
return RetVal;
}
return RetVal;
}
分别调用input_allocate_device和input_register_device函数来注册input 驱动,然后就是设置input 驱动的类型,下面还注册了一个输入设备这里我的理解是这样的,input1主要是输出xyz方向上的重力加速度分量,而input2主要是用来输出一些碰撞和震动,还有横竖切换等事件。
下面是初始化文件系统
/*!
* This method is used to Initialize Sysfs.
* client : Pointer to i2c_client structure.
* return -EPERM : Device pointer is NULL.
* return 0 : Success.
*/
struct class *sensor_class_obj = NULL;
int InitializeSysfs(struct i2c_client *client)
{
int RetVal = 0;
int i = 0;
int Iterator = 0;
int Instance = 0;
struct mxc_mma_device_t *pDev = i2c_get_clientdata(client);
struct ChipInfo_t *pChip = pDev->pChip;
struct SysfsInfo_t *pSysfsInfo = pChip->pSysfsInfo;
if(pDev == NULL)
{
printk("%s: pDev => NULL pointer\r\n", __func__);
return -EPERM;
}
if(sensor_class_obj == NULL)
{
pDev->class = class_create(THIS_MODULE, "sensor");
if (IS_ERR(pDev->class))
{
printk(KERN_ERR "Unable to create class for Mxc MMA\n");
RetVal = PTR_ERR(pDev->class);
}
sensor_class_obj = pDev->class;
}
else
{
pDev->class = sensor_class_obj;
}
client->dev.class = pDev->class;
pDev->sys_device = device_create(pDev->class, NULL, MKDEV(pDev->major, 0), pDev,"mma");
if (IS_ERR(pDev->sys_device))
{
printk(KERN_ERR "Unable to create class device for Mxc Ipu\n");
RetVal = PTR_ERR(pDev->sys_device);
return RetVal;
}
dev_set_drvdata( (struct device *)&pDev->sys_device, pDev);
for(Iterator = 0; Iterator < pChip->SysfsInfoSize; Iterator++)
{
for(Instance = 0; Instance < pSysfsInfo[Iterator].Instance; Instance++)
{
/* Create motion_detection device */
if(pSysfsInfo[Iterator].grpName != NULL)
{
pDev->sys_motion_dev = device_create(pDev->class, pDev->sys_device, MKDEV(0, 0), pDev,
"%s%d", pSysfsInfo[Iterator].grpName, Instance);
}
else
{
pDev->sys_motion_dev = pDev->sys_device;
}
for(i=0; i < pSysfsInfo[Iterator].TotalEntries; i++)
{
if(sysfs_create_file(&pDev->sys_motion_dev->kobj, &pSysfsInfo[Iterator].AttrEntry[i].attr) < 0)
printk("%s sys file creation failed.\r\n", pSysfsInfo[Iterator].AttrEntry[i].attr.name);
}
}
}
return RetVal;
}
EXPORT_SYMBOL(sensor_class_obj);
这里代码很多,在mma_sysfs.c中,其实一点都不难,就是太多了有点复杂,看过内核驱动模型的同学应该很熟悉这里,这里就是建立了一个名为sensor的class,来作为这些文件的父类,具体的我不多说了,这里的文件太多了,也讲不完,可以一边参照data sheet一边看这里的文件时用来干嘛的,这里read/write 的时候就分别会回调这里相关的函数,然后去设置寄存器。大家可以看到下面就是这里建立的文件系统
/sys/class/sensor # ls -l
lrwxrwxrwx 1 root root 0 Jan 2 01:10 mma -> ../../devices/virtual/sensor/mma
lrwxrwxrwx 1 root root 0 Jan 2 01:10 motion_detection0 -> ../../devices/virtual/sensor/mma/motion_detection0
lrwxrwxrwx 1 root root 0 Jan 2 01:10 orientation_detection0 -> ../../devices/virtual/sensor/mma/orientation_detection0
lrwxrwxrwx 1 root root 0 Jan 2 01:10 tap_detection0 -> ../../devices/virtual/sensor/mma/tap_detection0
lrwxrwxrwx 1 root root 0 Jan 2 01:10 transient_detection0 -> ../../devices/virtual/sensor/mma/transient_detection0
/sys/class/sensor #
然后是初始化信号量和定时器,这里就不多说了。
接下来是创建了一个线程并执行他,这个线程是一个死循环,也就是我们这里最关键的一个函数,之后再说。
if (plat_data->int1 > 0) {
set_irq_type(plat_data->int1, IRQF_TRIGGER_FALLING);
ret = request_irq(plat_data->int1, mma845x_interrupt,
IRQF_TRIGGER_FALLING, DEVICE_NAME, pDev);
if (ret) {
dev_err(&client->dev, "request_irq(%d) returned error %d\n",
plat_data->int1, ret);
goto error_disable_power;
}
}
else {
gpChip->DisableInt(INT_EN_FIFO | INT_EN_DRDY);
poll_mode = 1;
}
if (plat_data->int2 > 0){
printk(KERN_INFO "%s:: Configuring interrupt IRQ [%d]\r\n", __func__,
plat_data->int2);
set_irq_type(plat_data->int2, IRQF_TRIGGER_FALLING);
ret = request_irq(plat_data->int2, mma845x_interrupt,
IRQF_TRIGGER_FALLING, DEVICE_NAME, pDev);
if (ret) {
dev_err(&client->dev, "request_irq(%d) returned error %d\n",
plat_data->int2, ret);
goto error_free_irq1;
}
然后这里是申请中断,如果我们使用中断模式的话就申请中断,如果没有使用中断模式的话这里有一个flag,poll_mode被设置成1,初始化的时候是0.
porbe函数就先讲到这里,这里主要还是做了一些初始化的东西,下面来分析一下这个驱动是如何工作的,其实相关的就是这里最重要的2个东西,一个是定时器,还有一个是这个thread的处理,我们下面接着看
/*!
* This function implements interrupt handler routine
* irq : Interrupt number
* dev_id
* return IRQ_RETVAL
*/
static irqreturn_t mma845x_interrupt(int irq, void *dev_id)
{
//+++add for debug
//printk("mma--irq\n");
up(&chip_ready);
return IRQ_RETVAL(1);
}
/*!
* This routine implements a call back for stall timer
* data : Pointer to device data structure
* return NONE
*/
static void stall_timer_fn(unsigned long data)
{
//++++add for debug
//printk("mma-- poll\n");
up(&chip_ready);
}
首先看下这里的中断响应函数和定时器响应函数,这里基本啥也没做,就是释放信号量,关键就在这,我们接着看
static int IntServiceThread(void *data)
{
wait_queue_t wait;
int ret = 0;
struct mxc_mma_device_t *pDev = (struct mxc_mma_device_t *) data;
struct ChipInfo_t *pChip = pDev->pChip;
char buff[256];
init_waitqueue_entry(&wait, current);
mod_timer(&stall_timer, jiffies + (HZ));
while (!done) {
do {
ret = down_interruptible(&chip_ready);
} while (ret == -EINTR);
if (!poll_mode) {
ret = pChip->GetIntSrc();
}
else
ret = SRC_DRDY;
if (SRC_DRDY & ret) {
pChip->Read(ACCL_DATA, (void *) buff);
UpdateAcclFiFo(buff);
if (!IsSuspended) {
if (pDev->aflag == 1)
ReportEvent(pDev, ACCL_DATA, buff);
}
}
if (SRC_FIFO & ret) {
int count = 0,
i = 0;
char *pBuff = (char *) buff;
printk(KERN_DEBUG "\t[FIFO] [%ld]\r\n", jiffies);
count = pChip->Read(FIFO_DATA, (void *) buff);
for (i = 0; i < count; i++) {
UpdateAcclFiFo(pBuff + (i * sizeof(AcclData_t)));
if (!IsSuspended) {
if (pDev->aflag == 1)
ReportEvent(pDev, ACCL_DATA,
pBuff + (i * sizeof(AcclData_t)));
}
}
}
/*
* Orientation
*/
if (SRC_LNDPRT & ret) {
char *pBuff = (char *) buff;
pChip->Read(ACCL_LNDPRT, (void *) buff);
if (!IsSuspended)
ReportEvent(pDev, ACCL_LNDPRT, pBuff);
}
/*
* Motion / Freefall interrupt
*/
if (SRC_FF_MT & ret) {
pChip->Read(ACCL_FF_MT, (void *) buff);
if (!IsSuspended)
ReportEvent(pDev, ACCL_FF_MT, buff);
}
if (SRC_FF_MT_2 & ret) {
pChip->Read(ACCL_FF_MT_2, (void *) buff);
if (!IsSuspended)
ReportEvent(pDev, ACCL_FF_MT_2, buff);
}
/*
* Motion / Freefall interrupt
*/
if (SRC_PULSE & ret) {
pChip->Read(ACCL_PULSE, (void *) buff);
if (!IsSuspended)
ReportEvent(pDev, ACCL_PULSE, buff);
}
if (SRC_TRANS & ret) {
pChip->Read(ACCL_TRANS, (void *) buff);
if (!IsSuspended)
ReportEvent(pDev, ACCL_TRANS, buff);
}
if (!IsSuspended) {
if (!poll_mode) {
mod_timer(&stall_timer, jiffies + (HZ));
}
else
mod_timer(&stall_timer, jiffies + pChip->poll_time);
}
}
return 0;
}
看下我们的线程,一开始是初始化了一堆变量,然后把data转化为我们这里的一个ChipInfo_t结构体指针,然后初始化等待队列,然后是修改定时器下次定时到达时间为1秒钟,然后是一个while循环,看下这个循环中到底做了啥事情。
首先是一个do while来试图获取信号量,这里信号量被初始化为0,所以这边是得不到的,就会一直等在这边,前面我们看过定时器和中断函数中都做了对信号量的释放动作,联想到这边就是说,我们的线程不是一直在走的,只有当发生定时器时间到达和中断发生了,才会让我们这里的线程继续往下走,也就实现了这里的polling和interrupt 这2中模式。
这个线程下面的代码我们都能猜到,就是通过I2C读取芯片中的数据,然后利用input子系统push到用户空间,如果是polling mode的话就修改下次定时到达的时间,这里的设计还是满巧妙的,有待学习,特别是我觉得代码写的比较规范。
下面来一张图来分析这里的处理流程。
画的太丑了,见谅!!