开发板:tiny4412(1611)
内核:linux4.4
编译器: arm-none-linux-gnueabi-gcc (gcc version 4.8.3 20140320)
mma7660连接的是i2c3和xeint25。
在以前的内核中(arch/arm/mach-exynos/mach-tiny4412.c)中,是这样初始化i2c控制器及设备的
static struct s3c2410_platform_i2c tiny4412_i2c3_data __initdata = { //__initdata宏用于数据定义,目的是将数据放入名叫.init.data的段。
.flags = 0,
.bus_num = 3,
.slave_addr = 0x10,
.frequency = 200*1000,
.sda_delay = 100,
};
static struct mma7660_platform_data mma7660_pdata = {
.irq = IRQ_EINT(25),
.poll_interval = 100,
.input_fuzz = 4,
.input_flat = 4,
};
static struct i2c_board_info smdk4x12_i2c_devs3[] __initdata = {
#ifdef CONFIG_MXC_MMA845X
{
.type = "mma845x",
.addr = 0x1D, /*mma845x i2c slave address*/
.platform_data = (void *)&mma845x_data,
},
#endif
#ifdef CONFIG_SENSORS_MMA7660
{
I2C_BOARD_INFO("mma7660", 0x4c),
.platform_data = &mma7660_pdata,
},
#endif
};
…………
s3c_i2c3_set_platdata(&tiny4412_i2c3_data); //设置i2c3控制器信息
i2c_register_board_info(3, smdk4x12_i2c_devs3,ARRAY_SIZE(smdk4x12_i2c_devs3));//注册挂接在i2c3上的设备的信息
…………
重要信息:MMA7660的器件地址是0x4c,I2C3的CLK信号新的频率为200KHz。
有了设备树之后:
在exynos4.dtsi文件下可以看到i2c_3的初始化,
i2c_3: i2c@13890000 {
#address-cells = <1>;
#size-cells = <0>;
compatible = "samsung,s3c2440-i2c";
reg = <0x13890000 0x100>;
interrupts = <0 61 0>;
clocks = <&clock CLK_I2C3>;
clock-names = "i2c";
pinctrl-names = "default";
pinctrl-0 = <&i2c3_bus>;
status = "disabled";
};
i2c_3是这个节点的标号,可以通过&i2c_3的方式来调用这个节点。引用时,添加的新属性会合并,相同属性会被合并。添加MMA7660和I2C的硬件信息,可以参考内核文档,Documentation/devicetree/bindings/i2c/i2c.txt和Documentation/devicetree/bindings/i2c/i2c-s3c2410.txt,中断资源的填写可以参考内核文档Documentation/devicetree/bindings/pinctrl/samsung-pinctrl.txt。
例子如下:
i2c@13870000 {
compatible = "samsung,s3c2440-i2c";
reg = <0x13870000 0x100>;
interrupts = <345>;
samsung,i2c-sda-delay = <100>;
samsung,i2c-max-bus-freq = <100000>;
/* Samsung GPIO variant begins here */
gpios = <&gpd1 2 0 /* SDA */
&gpd1 3 0 /* SCL */>;
/* Samsung GPIO variant ends here */
/* Pinctrl variant begins here */
pinctrl-0 = <&i2c3_bus>;
pinctrl-names = "default";
/* Pinctrl variant ends here */
#address-cells = <1>;
#size-cells = <0>;
wm8994@1a {
compatible = "wlf,wm8994";
reg = <0x1a>;
};
};
在exynos4412-tiny4412.dts下添加如下节点:
&i2c_3 {
samsung,i2c-sda-delay = <100>;
samsung,i2c-slave-addr = <0x10>;
samsung,i2c-max-bus-freq = <200000>;
status = "okay";
mma7660@4c {
compatible = "tiny4412,mma7660";
reg = <0x4c>;
interrupt-parent = <&gpx3>;
interrupts = <1 2>;
poll_interval = <100>;
input_fuzz = <4>;
input_flat = <4>;
status = "okay";
};
};
为i2c_3添加了3个自定义属性,覆写了status属性,并在其下添加了子节点,因为子节点的中断控制器不是i2c_3,所以添加interrupt-parent属性,关于如何填写这个中断资源,可以查看Documentation/devicetree/bindings/pinctrl/samsung-pinctrl.txt
对于interrupts = <1 2>,其中1表示GPX3_1,2表示的是下降沿触发。加载新的设备树后,可以在sys/firmware/devicetree/base/i2c@13890000下找到mma7660@4c节点。
grep 'samsung,s3c2440-i2c' -nr
i2c-s3c2410.c:154: { .compatible = "samsung,s3c2440-i2c", .data = (void *)QUIRK_S3C2440 },
可以在i2c-s3c2410.c找到i2c控制器的驱动,所以我们只要写i2c设备的驱动。
在i2c驱动中添加of_device_id属性,来匹配设备树节点
- static const struct of_device_id dt_ids[] = {
- { .compatible = "tiny4412,mma7660", },
- {},
- };
驱动匹配上节点之后,报出如下错误:
mma7660 3-004c: lack of platform data!
原因是client->dev.platform_data还没包含数据,所以我们现在往probe中添加解析设备树节点信息并设置client->dev.platform_data的函数。
static struct mma7660_platform_data *mma7660_parse_dt(struct device *dev){
struct mma7660_platform_data *pdata;
struct device_node *np = dev->of_node;
if (!np)
return NULL;
pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL);
if (!pdata) {
dev_err(dev, "failed to allocate platform data\n");
return NULL;
}
if (of_property_read_u32(np, "poll_interval", &pdata->poll_interval)) {
dev_err(dev, "failed to get poll_interval property\n");
return NULL;
}
if (of_property_read_u32(np, "input_fuzz", &pdata->input_fuzz)) {
dev_err(dev, "failed to get input_fuzz property\n");
return NULL;
}
if (of_property_read_u32(np, "input_flat", &pdata->input_flat)) {
dev_err(dev, "failed to get input_flat property\n");
return NULL;
}
pdata->irq=irq_of_parse_and_map(np, 0);
printk("%d",pdata->irq);
return pdata;
}
注册驱动后打印如下:
驱动:
/*
* linux/drivers/hwmon/mma7660.c
*
* 3-Axis Orientation/Motion Detection Sensor support
*
* Copyright (C) 2009-2010 Freescale Semiconductor Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* 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., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/delay.h>
#include <linux/i2c.h>
#include <linux/err.h>
#include <linux/input-polldev.h>
#include <linux/hwmon.h>
#include <linux/hwmon-sysfs.h>
#include <linux/mma7660.h>
#include <linux/of_irq.h>
#define MMA7660_NAME "mma7660"
#define POLL_INTERVAL 100
#define INPUT_FUZZ 4
#define INPUT_FLAT 4
static struct i2c_client *mma7660_client;
static struct device *hwmon_dev;
static struct input_polled_dev *mma7660_idev;
static struct mma7660_platform_data *plat_data;
static int last_tilt = 0;
//static int oper_mode;
/*-----------------------------------------------------------------------------
* MMA7660 operations
*/
#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 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_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));
*tilt = (val & 0xff);
return 0;
}
static int mma7660_initialize(struct i2c_client *client)
{
int val;
/* Using test mode to probe chip */
i2c_smbus_write_byte_data(client, MMA7660_MODE, 0x00);
mdelay(10);
i2c_smbus_write_byte_data(client, MMA7660_MODE, 0x04);
mdelay(10);
i2c_smbus_write_byte_data(client, MMA7660_XOUT, 0x3f);
i2c_smbus_write_byte_data(client, MMA7660_YOUT, 0x01);
i2c_smbus_write_byte_data(client, MMA7660_ZOUT, 0x15);
val = i2c_smbus_read_byte_data(client, MMA7660_ZOUT);
if (val != 0x15) {
dev_err(&client->dev, "no device\n");
return -ENODEV;
}
/* Goto standby mode for configuration */
i2c_smbus_write_byte_data(client, MMA7660_MODE, 0x00);
mdelay(10);
/* Sample rate: 64Hz / 16Hz; Filt: 3 samples */
i2c_smbus_write_byte_data(client, MMA7660_SR, ((2<<5) | (1<<3) | 1));
/* Sleep count */
i2c_smbus_write_byte_data(client, MMA7660_SPCNT, 0xA0);
/* Tap detect and debounce ~4ms */
i2c_smbus_write_byte_data(client, MMA7660_PDET, 4);
i2c_smbus_write_byte_data(client, MMA7660_PD, 15);
/* Enable interrupt except exiting Auto-Sleep */
i2c_smbus_write_byte_data(client, MMA7660_INTSU, 0xe7);
/* IPP, Auto-wake, auto-sleep and standby */
i2c_smbus_write_byte_data(client, MMA7660_MODE, 0x59);
mdelay(10);
/* Save current tilt status */
mma7660_read_tilt(client, &last_tilt);
mma7660_client = client;
return 0;
}
/*-----------------------------------------------------------------------------
* sysfs group support
*/
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_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_client, reg, val);
val = i2c_smbus_read_byte_data(mma7660_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_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_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_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, 0660,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_client, i, &axis[i]);
}
input_report_abs(mma7660_idev->input, ABS_X, axis[0]);
input_report_abs(mma7660_idev->input, ABS_Y, axis[1]);
input_report_abs(mma7660_idev->input, ABS_Z, axis[2]);
input_sync(mma7660_idev->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();
}
/*-----------------------------------------------------------------------------
* Interrupt handler
*/
static void mma7660_worker(struct work_struct *work)
{
int bafro, pola, shake, tap;
int val = 0;
mma7660_read_tilt(mma7660_client, &val);
/* TODO: report it ? */
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");
}
/* 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;
}
static struct mma7660_platform_data *mma7660_parse_dt(struct device *dev){
struct mma7660_platform_data *pdata;
struct device_node *np = dev->of_node;
if (!np)
return NULL;
pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL);
if (!pdata) {
dev_err(dev, "failed to allocate platform data\n");
return NULL;
}
if (of_property_read_u32(np, "poll_interval", &pdata->poll_interval)) {
dev_err(dev, "failed to get poll_interval property\n");
return NULL;
}
if (of_property_read_u32(np, "input_fuzz", &pdata->input_fuzz)) {
dev_err(dev, "failed to get input_fuzz property\n");
return NULL;
}
if (of_property_read_u32(np, "input_flat", &pdata->input_flat)) {
dev_err(dev, "failed to get input_flat property\n");
return NULL;
}
pdata->irq=irq_of_parse_and_map(np, 0);
printk("%d",pdata->irq);
return pdata;
}
/*-----------------------------------------------------------------------------
* I2C client driver interfaces
*/
static int mma7660_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent);
struct input_dev *idev;
int poll_interval = POLL_INTERVAL;
int input_fuzz = INPUT_FUZZ;
int input_flat = INPUT_FLAT;
int ret;
ret = i2c_check_functionality(adapter,
I2C_FUNC_SMBUS_BYTE | I2C_FUNC_SMBUS_BYTE_DATA);
if (!ret) {
dev_err(&client->dev, "I2C check functionality failed\n");
return -ENXIO;
}
client->dev.platform_data=mma7660_parse_dt(&client->dev);
plat_data = (struct mma7660_platform_data *)client->dev.platform_data;
if (plat_data == NULL) {
dev_err(&client->dev, "lack of platform data!\n");
return -ENODEV;
}
/* Get parameters from platfrom data */
if (plat_data->poll_interval > 0)
poll_interval = plat_data->poll_interval;
if (plat_data->input_fuzz > 0)
input_fuzz = plat_data->input_fuzz;
if (plat_data->input_flat > 0)
input_flat = plat_data->input_flat;
if (mma7660_initialize(client) < 0) {
goto error_init_client;
}
ret = sysfs_create_group(&client->dev.kobj, &mma7660_group);
if (ret) {
dev_err(&client->dev, "create sysfs group failed!\n");
goto error_init_client;
}
/* register to hwmon device */
hwmon_dev = hwmon_device_register(&client->dev);
if (IS_ERR(hwmon_dev)) {
dev_err(&client->dev, "hwmon register failed!\n");
ret = PTR_ERR(hwmon_dev);
goto error_rm_dev_file;
}
/* input poll device register */
mma7660_idev = input_allocate_polled_device();
if (!mma7660_idev) {
dev_err(&client->dev, "alloc poll device failed!\n");
ret = -ENOMEM;
goto error_rm_hwmon_dev;
}
mma7660_idev->poll = mma7660_dev_poll;
mma7660_idev->poll_interval = plat_data->poll_interval;
idev = mma7660_idev->input;
idev->name = MMA7660_NAME;
idev->id.bustype = BUS_I2C;
idev->id.vendor = 0x12FA;
idev->id.product = 0x7660;
idev->id.version = 0x0100;
idev->dev.parent = &client->dev;
set_bit(EV_ABS, idev->evbit);
set_bit(ABS_X, idev->absbit);
set_bit(ABS_Y, idev->absbit);
set_bit(ABS_Z, idev->absbit);
input_set_abs_params(idev, ABS_X, -512, 512, input_fuzz, input_flat);
input_set_abs_params(idev, ABS_Y, -512, 512, input_fuzz, input_flat);
input_set_abs_params(idev, ABS_Z, -512, 512, input_fuzz, input_flat);
ret = input_register_polled_device(mma7660_idev);
if (ret) {
dev_err(&client->dev, "register poll device failed!\n");
goto error_free_poll_dev;
}
/* register interrupt handle */
ret = request_irq(plat_data->irq, mma7660_interrupt,
IRQF_TRIGGER_FALLING, MMA7660_NAME, idev);
if (ret) {
dev_err(&client->dev, "request irq (%d) failed %d\n", plat_data->irq, ret);
goto error_rm_poll_dev;
}
dev_info(&client->dev, "MMA7660 device is probed successfully.\n");
#if 0
set_mod(1);
#endif
return 0;
error_rm_poll_dev:
input_unregister_polled_device(mma7660_idev);
error_free_poll_dev:
input_free_polled_device(mma7660_idev);
error_rm_hwmon_dev:
hwmon_device_unregister(hwmon_dev);
error_rm_dev_file:
sysfs_remove_group(&client->dev.kobj, &mma7660_group);
error_init_client:
mma7660_client = NULL;
return 0;
}
static int mma7660_remove(struct i2c_client *client)
{
free_irq(plat_data->irq, mma7660_idev->input);
input_unregister_polled_device(mma7660_idev);
input_free_polled_device(mma7660_idev);
hwmon_device_unregister(hwmon_dev);
sysfs_remove_group(&client->dev.kobj, &mma7660_group);
mma7660_client = NULL;
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 const struct of_device_id mma7660[] = {
{ .compatible = "freescale,mma7660", },
{},
};
MODULE_DEVICE_TABLE(of,mma7660);
static const struct i2c_device_id mma7660_ids[] = {
{ "mma7660", 0 },
{ },
};
MODULE_DEVICE_TABLE(i2c, mma7660_ids);
static struct i2c_driver i2c_mma7660_driver = {
.driver = {
.name = MMA7660_NAME,
.of_match_table=mma7660,
},
.probe = mma7660_probe,
.remove = mma7660_remove,
//.suspend = mma7660_suspend,
//.resume = mma7660_resume,
.id_table = mma7660_ids,
};
static int __init init_mma7660(void)
{
int ret;
ret = i2c_add_driver(&i2c_mma7660_driver);
printk(KERN_INFO "MMA7660 sensor driver registered.\n");
return ret;
}
static void __exit exit_mma7660(void)
{
i2c_del_driver(&i2c_mma7660_driver);
printk(KERN_INFO "MMA7660 sensor driver removed.\n");
}
module_init(init_mma7660);
module_exit(exit_mma7660);
MODULE_AUTHOR("Freescale Semiconductor, Inc.");
MODULE_DESCRIPTION("MMA7660 sensor driver");
MODULE_LICENSE("GPL");
测试:
cd /sys/devices/platform/13890000.i2c/i2c-3/3-004c/
可以看到如下输出: