上一章节有介绍过这颗IC,是freescale的一个低精度的重力传感器吧。我认为,可以区分8个方向。x,y,z三轴。精度6bit,采样值范围[-32,+32].还有测量shake和tap等数据。
这边的驱动没有自己写.只是有看懂它原先的驱动是如何实现和工作的.
它的驱动引入了一个 hwmon device的注册。具体的作用是在sys/class/hwmon下生成了一个目录hwmonx。
它可以添加你设备的一些属性attr,以此来方便用户去read那些属性的状态。网上也有说借此来导出内核的信息非常方便,几乎所有的sensor都喜欢这样用。我们可以借用内核导出x,y,z三轴的值,来方便查看数据.介于此,我们可以添加相关的属性,当访问这些属性文件时,实则是在调用对应的接口函数。比如读相应的寄存器,再显示给用户界面。
另外一个:传感器元件,都有一个特性。周期性的采样数据并上报数据。
我们再实现触摸屏驱动的时候,当你有手触摸时,立即便会上报一次数据,否则则没有.按键也是一样。
像有的温度传感器,基本上是每次间隔一定的时间,才能采样一次准确的数据。并不是立刻就能获得数据的.需要一定的时间。
所以这边引入了一个input子系统的扩展:input_polled_dev.
我们依旧把它作为输入子系统来实现。上报x,y,z三轴的绝对坐标.
所以整个驱动的框架没变。新多出来的功能是 属性导出功能,hwmon设备的引入,和input_polled_dev的引入。
程序我都有注释过,初始化完毕后,当有触发过中断时,程序便会更新一次TILT状态寄存器。此时我们可以去读我们需要的数据。根据读到的数据做出相应的判断.比如说是否旋转屏幕。想一想手机的自动旋转功能,当我们把屏幕横着放时,画面会对应做旋转,这里面也是通过sensor来识别当前的位置,根据位置做出画面的相应调整。
常用的传感器很多很多。温度传感器,压力传感器,靠近传感器,声音传感器,加速度传感器等等。
我们今天实现的只是很小的一类中的特定一个传感器而已。
驱动实例:
/*
* Copyright 2011 Freescale Semiconductor, Inc. All Rights Reserved.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA
*/
#ifndef __LINUX_MMA7660_H
#define __LINUX_MMA7660_H
#include <linux/types.h>
#ifdef __KERNEL__
/* MMA7760 Registers */
#define MMA7660_XOUT 0x00 // 6-bit output value X
#define MMA7660_YOUT 0x01 // 6-bit output value Y
#define MMA7660_ZOUT 0x02 // 6-bit output value Z
#define MMA7660_TILT 0x03 // Tilt status
#define MMA7660_SRST 0x04 // Sampling Rate Status
#define MMA7660_SPCNT 0x05 // Sleep Count
#define MMA7660_INTSU 0x06 // Interrupt Setup
#define MMA7660_MODE 0x07 // Mode
#define MMA7660_SR 0x08 // Auto-Wake/Sleep and Debounce Filter
#define MMA7660_PDET 0x09 // Tap Detection
#define MMA7660_PD 0x0a // Tap Debounce Count
struct mma7660_platform_data {
/* eint connected to chip */
int irq;
/* parameters for input device */
int poll_interval;
int input_fuzz;
int input_flat;
};
#endif /* __KERNEL__ */
#endif /* __LINUX_MMA7660_H */
这边因为是挂在IIC总线上,同样,IIC设备的实例化我们还是按照老方法来做:
static struct mma7660_platform_data mma7660_pdata = {
.irq = IRQ_EINT(11),
.poll_interval = 100,
.input_fuzz = 4,
.input_flat = 4,
};
static struct i2c_board_info smdkv210_i2c_devs0[] __initdata = {
{ I2C_BOARD_INFO("24c08", 0x50), },
{
I2C_BOARD_INFO("mma7660", 0x4c),
.platform_data = &mma7660_pdata,
},
};
同样,我们可以看到,在iic0上挂了两个设备,一个是24c08,一个是mma7660,因为slave addr的不同,我们可以访问到不同的从设备。
驱动代码:
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/i2c.h>
#include <asm/uaccess.h>
#include <linux/slab.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/input-polldev.h>
#include <linux/hwmon.h>
#include <linux/hwmon-sysfs.h>
#include <linux/mma7660.h>
#include <mach/hardware.h>
#define MMA7660_NAME "mma7660"
#define POLL_INTERVAL 100
#define INPUT_FUZZ 4
#define INPUT_FLAT 4
#define __need_retry(__v) (__v & (1 << 6))
#define __is_negative(__v) (__v & (1 << 5))
static const char *mma7660_bafro[] = {
"Unknown", "Front", "Back"
};
static const char *mma7660_pola[] = {
"Unknown",
"Left", "Right",
"Rsvd", "Rsvd",
"Down", "Up",
"Rsvd",
};
static const struct i2c_device_id mma7660_id[] =
{
{"mma7660",0},
{}
};
struct mma7660_dev
{
int poll_interval;
int input_fuzz;
int input_flat;
struct device *hwmon_dev; //hwmon dev
struct input_polled_dev *ip_dev; //input poll dev
struct i2c_client * mma_iic_client; //iic client
struct mma7660_platform_data *pdata; // platform data
};
static int last_tilt = 0;
static int oper_mode;
static struct input_dev *i_dev = NULL;
static struct mma7660_dev *mma7660_struct = NULL;
static int mma7660_read_tilt(struct i2c_client *client, int *tilt);
static void mma7660_worker(struct work_struct *work)
{
#if 0
int bafro, pola, shake, tap;
#endif
int val = 0;
mma7660_read_tilt(mma7660_struct->mma_iic_client, &val);
#if 0
bafro = val & 0x03;
if(bafro != (last_tilt & 0x03))
{
printk("%s\n", mma7660_bafro[bafro]);
}
pola = (val >> 2) & 0x07;
if(pola != ((last_tilt >> 2) & 0x07))
{
printk("%s\n", mma7660_pola[pola]);
}
shake = (val >> 5) & 0x01;
if(shake && shake != ((last_tilt >> 5) & 0x01))
{
printk("Shake\n");
}
tap = (val >> 7) & 0x01;
if(tap && tap != ((last_tilt >> 7) & 0x01))
{
printk("Tap\n");
}
#endif
/* Save current status */
last_tilt = val;
}
DECLARE_WORK(mma7660_work, mma7660_worker);
static irqreturn_t mma7660_interrupt(int irq, void *dev_id)
{
schedule_work(&mma7660_work);
return IRQ_HANDLED;
}
/*read the data from reg TILT */
static int mma7660_read_tilt(struct i2c_client *client, int *tilt)
{
int val;
do
{
val = i2c_smbus_read_byte_data(client, MMA7660_TILT);
if(val < 0)
{
dev_err(&client->dev, "Read register %02x failed, %d\n",MMA7660_TILT, val);
return -EIO;
}
}while(__need_retry(val));//when bit6 is 1,can't read value,so do this to wait the TILT reg ready to read
*tilt = (val & 0xff);
return 0;
}
static int mma7660_read_xyz(struct i2c_client *client, int idx, int *xyz)
{
int val;
do
{
val = i2c_smbus_read_byte_data(client, idx + MMA7660_XOUT);
if(val < 0)
{
dev_err(&client->dev, "Read register %02x failed, %d\n",idx + MMA7660_XOUT, val);
return -EIO;
}
}while(__need_retry(val));
*xyz = __is_negative(val) ? (val | ~0x3f) : (val & 0x3f);
return 0;
}
static int mma7660_initial(struct i2c_client *client)
{
int val;
//1.set mode to stand by mode first by set bit0 (mode bit)to 0
i2c_smbus_write_byte_data(client, MMA7660_MODE, 0x00);
mdelay(10);
// 2.set mode to test mode,it should set in stand by mode by set the bit 2(ton bit) to 1
i2c_smbus_write_byte_data(client, MMA7660_MODE, 0x04);
mdelay(10);
//3.in test mode can set the reg MMA7660_XOUT,MMA7660_YOUT,MMA7660_ZOUT
//3.1 clear the bit6(alert bit)
i2c_smbus_write_byte_data(client, MMA7660_XOUT, 0x3f);
//3.2 clear the bit6(alert bit)
i2c_smbus_write_byte_data(client, MMA7660_YOUT, 0x3f);
//3.3 clear the bit6(alert bit)
i2c_smbus_write_byte_data(client, MMA7660_ZOUT, 0x3f);
val = i2c_smbus_read_byte_data(client, MMA7660_ZOUT);
if(val != 0x3f)
{
printk("2.test write and read reg 0x02 but get wrong data: %d, no dev!!!\n",val);
return -ENODEV;
}
//reset to stand by mode
i2c_smbus_write_byte_data(client, MMA7660_MODE, 0x00);
mdelay(10);
//AMSR[2:0]bit0-bit2 :samples rate: 001----64 samples/sec in Active and Auto-Sleep Mode
//AWSR[1:0]:bit4-bit3: samples rate: 01-----16 Samples/Second Auto-Wake Mode
//bit7-bit5 :samples rate 001----- 3samples/sec TILT
i2c_smbus_write_byte_data(client, MMA7660_SR, ((2<<5) | (1<<3) | 1));
//sleep count:160,when reach to the timeout time,will go in auto wake mode,ps
i2c_smbus_write_byte_data(client, MMA7660_SPCNT, 0xA0);
//Tap detection threshold is ±4 counts
//X-axis,Y-axis,Z-axis is enabled for tap detection
i2c_smbus_write_byte_data(client, MMA7660_PDET, 4);
//Tap detection debounce filtering requires 16 adjacent tap detection tests to be the same to trigger a tap event and set the Tap
//bit in the TILT (0x03) register, and optionally set an interrupt if PDINT is set in the INTSU (0x06) register. Tap detection
//response time is nominally 1.04 ms.
i2c_smbus_write_byte_data(client, MMA7660_PD, 15);
/* Enable interrupt except exiting Auto-Sleep */
//FBINT bit 0 ---1: Front/Back position change causes an interrupt
//PLINT bit 1---1:Up/Down/Right/Left position change causes an interrupt
//PDINT bit 2---1: Successful tap detection causes an interrupt
//ASINT bit 3---0: Exiting Auto-Sleep does not cause an interrupt
//GINT bit4---0:There is not an automatic interrupt after everymeasurement
//SHINTX bit 5---1:Shake detected on the X-axis causes an interrupt, and sets the Shake bit in the TILT register
//SHINTY bit6---1: Shake detected on the Y-axis causes an interrupt, and sets the Shake bit in the TILT register
//SHINTZ bit7---1: Shake detected on the Z-axis causes an interrupt, and sets the Shake bit in the TILT register.
i2c_smbus_write_byte_data(client, MMA7660_INTSU, 0xe7);
/* IPP, Auto-wake, auto-sleep and standby */
//Active mode: bit0 set to 1 ,bit 2 set o 0
// AWE bit3----1:Auto-Wake is enabled
//ASE bit4----1: Auto-Sleep is enabled
//SCPS bit5---0: The prescaler is divide-by-1. The 8-bit internal Sleep Counter input clock is the samples per second set by
//AMSR[2:0], so the clock range is 120 Hz to 1 Hz depending on AMSR[2:0] setting. Sleep Counter timeout range is
//256 times the prescaled clock
//IPP bit6---1: Interrupt output INT is push-pull
//IAH bit7---0: Interrupt output INT is active low
i2c_smbus_write_byte_data(client, MMA7660_MODE, 0x59);
mdelay(10);
/* Save current tilt status */
mma7660_read_tilt(client, &last_tilt);
return 0;
}
/* attr*/
static ssize_t mma7660_show_regs(struct device *dev,struct device_attribute *attr, char *buf)
{
int reg, val;
int i, len = 0;
for(reg = 0; reg < 0x0b; reg++)
{
val = i2c_smbus_read_byte_data(mma7660_struct->mma_iic_client, reg);
len += sprintf(buf + len, "REG: 0x%02x = 0x%02x ...... [ ", reg, val);
for(i = 7; i >= 0; i--)
{
len += sprintf(buf + len, "%d", (val >> i) & 1);
if((i % 4) == 0)
{
len += sprintf(buf + len, " ");
}
}
len += sprintf(buf + len, "]\n");
}
return len;
}
static ssize_t mma7660_write_reg(struct device *dev,struct device_attribute *attr, const char *buf, size_t count)
{
unsigned int reg, val;
int ret;
ret = sscanf(buf, "%x %x", ®, &val);
if(ret == 2)
{
if (reg >= 0 && reg <= 0x0a)
{
i2c_smbus_write_byte_data(mma7660_struct->mma_iic_client, reg, val);
val = i2c_smbus_read_byte_data(mma7660_struct->mma_iic_client, reg);
printk("REG: 0x%02x = 0x%02x\n", reg, val);
}
}
return count;
}
static ssize_t mma7660_show_xyz_g(struct device *dev,struct device_attribute *attr, char *buf)
{
int axis[3];
int i;
for (i = 0; i < 3; i++)
{
mma7660_read_xyz(mma7660_struct->mma_iic_client, i, &axis[i]);
}
return sprintf(buf, "%3d, %3d, %3d\n", axis[0], axis[1], axis[2]);
}
static ssize_t mma7660_show_axis_g(struct device *dev,struct device_attribute *attr, char *buf)
{
int n = to_sensor_dev_attr(attr)->index;
int val;
mma7660_read_xyz(mma7660_struct->mma_iic_client, n, &val);
return sprintf(buf, "%3d\n", val);
}
static ssize_t mma7660_show_tilt(struct device *dev,struct device_attribute *attr, char *buf)
{
int val = 0, len = 0;
mma7660_read_tilt(mma7660_struct->mma_iic_client, &val);
len += sprintf(buf + len, "%s", mma7660_bafro[val & 0x03]);
len += sprintf(buf + len, ", %s", mma7660_pola[(val >> 2) & 0x07]);
if(val & (1 << 5))
{
len += sprintf(buf + len, ", Tap");
}
if(val & (1 << 7))
{
len += sprintf(buf + len, ", Shake");
}
len += sprintf(buf + len, "\n");
return len;
}
static SENSOR_DEVICE_ATTR(registers, S_IRUGO | S_IWUGO,mma7660_show_regs, mma7660_write_reg, 0);
static SENSOR_DEVICE_ATTR(x_axis_g, S_IRUGO, mma7660_show_axis_g, NULL, 0);
static SENSOR_DEVICE_ATTR(y_axis_g, S_IRUGO, mma7660_show_axis_g, NULL, 1);
static SENSOR_DEVICE_ATTR(z_axis_g, S_IRUGO, mma7660_show_axis_g, NULL, 2);
static SENSOR_DEVICE_ATTR(all_axis_g, S_IRUGO, mma7660_show_xyz_g, NULL, 0);
static SENSOR_DEVICE_ATTR(tilt_status, S_IRUGO, mma7660_show_tilt, NULL, 0);
static struct attribute* mma7660_attrs[] =
{
&sensor_dev_attr_registers.dev_attr.attr,
&sensor_dev_attr_x_axis_g.dev_attr.attr,
&sensor_dev_attr_y_axis_g.dev_attr.attr,
&sensor_dev_attr_z_axis_g.dev_attr.attr,
&sensor_dev_attr_all_axis_g.dev_attr.attr,
&sensor_dev_attr_tilt_status.dev_attr.attr,
NULL
};
static const struct attribute_group mma7660_group = {
.attrs = mma7660_attrs,
};
//Input interfaces
static void mma7660_report_abs(void)
{
int axis[3];
int i;
for(i = 0; i < 3; i++)
{
mma7660_read_xyz(mma7660_struct->mma_iic_client, i, &axis[i]);
}
input_report_abs(mma7660_struct->ip_dev->input, ABS_X, axis[0]);
input_report_abs(mma7660_struct->ip_dev->input, ABS_Y, axis[1]);
input_report_abs(mma7660_struct->ip_dev->input, ABS_Z, axis[2]);
input_sync(mma7660_struct->ip_dev->input);
//printk("3-Axis ... %3d, %3d, %3d\n", axis[0], axis[1], axis[2]);
}
static void mma7660_dev_poll(struct input_polled_dev *dev)
{
mma7660_report_abs();
}
/*****int (*probe)(struct i2c_client *, const struct i2c_device_id *)*****/
static int mma7660_probe(struct i2c_client *uc_i2c_client, const struct i2c_device_id * uc_i2c_id_table)
{
int err=0;
//check iic
if(!i2c_check_functionality(uc_i2c_client->adapter,I2C_FUNC_I2C))
{
err = -ENODEV;
goto FAIL_CHECK_FUNC;
}
//check platdata
if(!uc_i2c_client->dev.platform_data)
{
err = -ENODATA;
goto FAIL_NO_PLATFORM_DATA;
}
//allooc buf
mma7660_struct = kzalloc(sizeof(struct mma7660_dev),GFP_KERNEL);
if(!mma7660_struct)
{
err=-ENOMEM;
goto FAIL_KZALLOC;
}
//initial start
mma7660_struct->mma_iic_client = uc_i2c_client;
mma7660_struct->pdata = uc_i2c_client->dev.platform_data;
mma7660_struct->poll_interval =((mma7660_struct->pdata->poll_interval >0) ? mma7660_struct->pdata->poll_interval : POLL_INTERVAL);
mma7660_struct->input_flat =((mma7660_struct->pdata->input_flat >0) ? mma7660_struct->pdata->input_flat : INPUT_FLAT);
mma7660_struct->input_fuzz =((mma7660_struct->pdata->input_fuzz >0) ? mma7660_struct->pdata->input_fuzz : INPUT_FUZZ);
if(mma7660_struct->pdata->irq)
{
mma7660_struct->mma_iic_client->irq = mma7660_struct->pdata->irq;
}
else
{
err = -ENODATA;
printk("3.the platformdata no irq data\n");
goto FAIL_NO_IRQ_DATA;
}
//initial the dev
err = mma7660_initial(mma7660_struct->mma_iic_client);
if(err < 0)
{
goto FAIL_MMA7660_INIT;
}
err = sysfs_create_group(&mma7660_struct->mma_iic_client->dev.kobj, &mma7660_group);
if(err)
{
printk("4.create sysfs group failed!\n");
goto FAIL_CREATE_GROUP;
}
// register to hwmon device
mma7660_struct->hwmon_dev = hwmon_device_register(&mma7660_struct->mma_iic_client->dev);
if(IS_ERR(mma7660_struct->hwmon_dev))
{
err = PTR_ERR(mma7660_struct->hwmon_dev);
printk("5.hwmon register failed!\n");
goto FAIL_HWMON_REGISTER;
}
//alloc the input poll device
mma7660_struct->ip_dev = input_allocate_polled_device();
if(!mma7660_struct->ip_dev)
{
err = -ENOMEM;
printk("6.alloc poll device failed!\n");
goto FAIL_ALLLOC_INPUT_PDEV;
}
mma7660_struct->ip_dev->poll = mma7660_dev_poll;
mma7660_struct->ip_dev->poll_interval =mma7660_struct->pdata->poll_interval;
i_dev = mma7660_struct->ip_dev->input;
i_dev->name = MMA7660_NAME;
i_dev->id.bustype = BUS_I2C;
i_dev->id.vendor = 0x12FA;
i_dev->id.product = 0x7660;
i_dev->id.version = 0x0100;
i_dev->dev.parent = &mma7660_struct->mma_iic_client->dev;
set_bit(EV_ABS, i_dev->evbit);
set_bit(ABS_X, i_dev->absbit);
set_bit(ABS_Y, i_dev->absbit);
set_bit(ABS_Z, i_dev->absbit);
input_set_abs_params(i_dev, ABS_X, -512, 512, mma7660_struct->input_fuzz, mma7660_struct->input_flat);
input_set_abs_params(i_dev, ABS_Y, -512, 512, mma7660_struct->input_fuzz, mma7660_struct->input_flat);
input_set_abs_params(i_dev, ABS_Z, -512, 512, mma7660_struct->input_fuzz, mma7660_struct->input_flat);
err = input_register_polled_device(mma7660_struct->ip_dev);
if(err)
{
printk("7.register poll device failed!");
goto FAIL_INPUT_REGISTER_PDEV;
}
//register interrupt handle
err = request_irq(mma7660_struct->pdata->irq, mma7660_interrupt,IRQF_TRIGGER_FALLING, MMA7660_NAME, NULL);
if(err)
{
printk("8.request irq failed!\n");
goto FAIL_REQUEST_IRQ;
}
printk("mma7660 driver probe success!\n");
return 0;
FAIL_REQUEST_IRQ:
input_unregister_polled_device(mma7660_struct->ip_dev);
FAIL_INPUT_REGISTER_PDEV:
input_free_polled_device(mma7660_struct->ip_dev);
FAIL_ALLLOC_INPUT_PDEV:
hwmon_device_unregister(mma7660_struct->hwmon_dev);
FAIL_HWMON_REGISTER:
sysfs_remove_group(&mma7660_struct->mma_iic_client->dev.kobj,&mma7660_group);
FAIL_CREATE_GROUP:
FAIL_MMA7660_INIT:
FAIL_NO_IRQ_DATA:
kfree(mma7660_struct);
FAIL_KZALLOC:
FAIL_NO_PLATFORM_DATA:
FAIL_CHECK_FUNC:
return err;
}
/*****int (*remove)(struct i2c_client *)*****/
static int mma7660_remove(struct i2c_client *uc_i2c_client)
{
disable_irq(mma7660_struct->pdata->irq);
free_irq(mma7660_struct->pdata->irq,NULL);
input_unregister_polled_device(mma7660_struct->ip_dev);
input_free_polled_device(mma7660_struct->ip_dev);
hwmon_device_unregister(mma7660_struct->hwmon_dev);
sysfs_remove_group(&mma7660_struct->mma_iic_client->dev.kobj,&mma7660_group);
return 0;
}
static int mma7660_suspend(struct i2c_client *client, pm_message_t state)
{
int ret;
oper_mode = i2c_smbus_read_byte_data(client, MMA7660_MODE);
ret = i2c_smbus_write_byte_data(client, MMA7660_MODE, 0);
if(ret)
{
printk("%s: set mode (0) for suspend failed, ret = %d\n",MMA7660_NAME, ret);
}
return 0;
}
static int mma7660_resume(struct i2c_client *client)
{
int ret;
ret = i2c_smbus_write_byte_data(client, MMA7660_MODE, oper_mode);
if (ret)
{
printk("%s: set mode (%d) for resume failed, ret = %d\n",MMA7660_NAME, oper_mode, ret);
}
return 0;
}
static struct i2c_driver mma7660_drv =
{
.driver =
{
.name = MMA7660_NAME,
.owner= THIS_MODULE,
},
.probe = mma7660_probe,
.remove = mma7660_remove,
.suspend = mma7660_suspend,
.resume = mma7660_resume,
//match use
.id_table = mma7660_id,
};
int __init mma7660_init(void)
{
i2c_add_driver(&mma7660_drv);
return 0;
}
void __exit mma7660_exit(void)
{
i2c_del_driver(&mma7660_drv);
}
module_init(mma7660_init);
module_exit(mma7660_exit);
MODULE_LICENSE("GPL");
整个驱动最难理解的地方应该还是在probe函数里面。当初始化完成以后,大部分的函数都是在做读操作而已。我们可以根据芯片手册来了解它的配置。同时比较难理解的是它本次采用的驱动框架。其他没什么难的,和之气的输入子系统都是一样的。
实验现象: