简介
mm7660跟exynos 4412通过i2c通信占用的引脚有:中断引脚XEINT25、i2c数据线i2cSDA3_OUT和i2c时钟线i2cSCL3_OUT。我们已经了解了上节了samsung的i2c现在我们需要控制器dts中配置i2c然后移植相关信息mma7660驱动到linux-4.20。
dts
mma7660使用的是i2c因此,这里需要配置i2c3。
&i2c_3 {
samsung,i2c-sda-delay = <100>; //esynos4412所有dts中的delay都是100,所以这个选择是100 samsung,i2c-max-bus-freq = <200000>; //最高400 kbit/s,当然,你可以填1万万 Supports up to 400 kbit/s in the Fast mode. status = "okay"; mma7660: mma7660@4c {
compatible = "tiny4412,mma7660"; reg = <0x4c>; //设备地址 interrupt-parent = <&gpx3>; interrupts = <1 IRQ_TYPE_EDGE_FALLING>; /* 选中的是GPX3_1 */ input_fuzz = <4>; input_flat = <4>; poll_interval = <100>; status = "okay"; }; };
mma7660.c
mma7660移植参考友好臂官方驱动程序,以下是我移植后的程序。
diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig old mode 100644 new mode 100755 index ca59a2b..79b52da --- a/drivers/input/misc/Kconfig +++ b/drivers/input/misc/Kconfig @@ -12,6 +12,12 @@ menuconfig INPUT_MISC if INPUT_MISC +config INPUT_MMA7660 + tristate "Tiny4412 Accelerometer mma7660" + help + Say Y here to support Accelerometer mma7660 for tiny4412. + If unsure, say N. + config INPUT_88PM860X_ONKEY tristate "88PM860x ONKEY support" depends on MFD_88PM860X diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile old mode 100644 new mode 100755 index 9d0f9d1..2aa2600 --- a/drivers/input/misc/Makefile +++ b/drivers/input/misc/Makefile @@ -5,6 +5,7 @@ # Each configuration option enables a list of files. +obj-$(CONFIG_INPUT_MMA7660) += mma7660.o obj-$(CONFIG_INPUT_88PM860X_ONKEY) += 88pm860x_onkey.o obj-$(CONFIG_INPUT_88PM80X_ONKEY) += 88pm80x_onkey.o obj-$(CONFIG_INPUT_AB8500_PONKEY) += ab8500-ponkey.o
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/i2c.h>
#include <linux/input.h>
#include <linux/hwmon.h>
#include <linux/hwmon-sysfs.h>
#include <linux/input-polldev.h>
#include <linux/of_device.h>
#include <linux/interrupt.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/mutex.h>
#include <linux/log2.h>
#include <linux/bitops.h>
#include <linux/jiffies.h>
#include <linux/property.h>
#include <linux/acpi.h>
#include <linux/regmap.h>
#include <linux/pm_runtime.h>
#include <linux/gpio/consumer.h>
#include <linux/err.h>
/* 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
static const char *mma7660_bafro[] = {
"Unknown", "Front", "Back"
};
static const char *mma7660_pola[] = {
"Unknown",
"Left", "Right",
"Rsvd", "Rsvd",
"Down", "Up",
"Rsvd",
};
#define MMA7660_NAME "mma7660"
#define INPUT_FUZZ 4 //default vale
#define INPUT_FLAT 4 //default vale
#define POLL_INTERVAL 100 //default vale
#define __need_retry(__v) (__v & (1 << 6))
#define __is_negative(__v) (__v & (1 << 5))
static int last_tilt = 0;
static struct device *hwmon_dev;
struct regmap *g_regmap = NULL;
static struct input_polled_dev *mma7660_idev;
static struct i2c_client *mma7660_client;
static const struct i2c_device_id mma7660_id[] = {
{
MMA7660_NAME, 0 },
{
},
};
MODULE_DEVICE_TABLE(i2c, mma7660_id);
static const struct of_device_id mma7660_dt_ids[] = {
{
.compatible = "tiny4412,mma7660", },
{
/* sentinel */ }
};
MODULE_DEVICE_TABLE(of, mma7660_dt_ids);
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,
};
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("Tap\n");
}
tap = (val >> 7) & 0x01;
if (tap && tap != ((last_tilt >> 7) & 0x01)) {
printk("Shake\n");
}
/* Save current status */
last_tilt = val;
}
DECLARE_WORK(mma7660_work, mma7660_worker);
static irqreturn_t mma7660_interrupt(int irq, void *data)
{
printk("mma7660_interrupt\n");
schedule_work(&mma7660_work);
printk("mma7660_interrupt ok\n");
return IRQ_HANDLED;
}
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();
}
static int mma7760_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct device *dev = &client->dev;
struct device_node *of_node = client->dev.of_node;
struct regmap_config regmap_config = {
};
struct regmap *regmap;
struct input_dev *idev;
int input_fuzz = INPUT_FUZZ;
int input_flat = INPUT_FLAT;
int poll_interval = POLL_INTERVAL;
int ret;
u32 val;
printk("enter %s\n", __func__);
ret = i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE | I2C_FUNC_SMBUS_BYTE_DATA);
if (!ret) {
dev_err(&client->dev, "I2C check functionality failed\n");
return -ENXIO;
}
if (of_node && of_match_device(mma7660_dt_ids, dev)){
ret = device_property_read_u32(dev, "input_fuzz", &val);
if (!ret)
input_fuzz = val;
ret = device_property_read_u32(dev, "input_flat", &val);
if (!ret)
input_flat = val;
ret = device_property_read_u32(dev, "poll_interval", &val);
if (!ret)
poll_interval = val;
}
regmap_config.val_bits = 8;
regmap_config.reg_bits = 8;
regmap_config.disable_locking = true;
regmap = devm_regmap_init_i2c(client, ®map_config); //这个可以去掉,当时为了比较regmap后的i2c是否可以兼容smbus,结果阅读
//源码发现优先使用i2c的操作接口,除非控制器不支持i2c,不然是不会调用smbus
//函数去通信
if (IS_ERR(regmap))
return PTR_ERR(regmap);
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;
}
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;
}
mma7660_idev = input_allocate_polled_device();
if (!mma7660_idev) {
dev_err(&client->dev, "alloc poll device failed!\n");
ret = -ENOMEM;
goto error_init_client;
}
mma7660_idev->poll = mma7660_dev_poll;
mma7660_idev->poll_interval = 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;
}
printk(" client->irq:%d\n",client->irq);
ret = devm_request_irq(dev, client->irq, mma7660_interrupt , IRQF_TRIGGER_FALLING, "mma7660", NULL);
if (ret) {
dev_err(&client->dev, "request irq (%d) failed %d\n", client->irq, ret);
goto error_rm_poll_dev;
}
printk("exit %s input_fuzz:%d input_flat:%d poll_interval:%d \n", __func__,input_fuzz,input_flat,poll_interval);
ret = 0;
return ret;
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:
return ret;
}
static struct i2c_driver mma7660_driver = {
.driver = {
.name = MMA7660_NAME,
.of_match_table = mma7660_dt_ids,
},
.probe = mma7760_probe,
.id_table = mma7660_id,
};
module_i2c_driver(mma7660_driver);
MODULE_AUTHOR("xiaohuangji, Inc.");
MODULE_DESCRIPTION("tiny4412 Accelerometer");
MODULE_LICENSE("GPL");