blob: d77dc515043faf5c8a83e33bb89bd3280fc4047d [file] [log] [blame]
/*
* mma8x5x.c - Linux kernel modules for 3-Axis Orientation/Motion
* Detection Sensor MMA8451/MMA8452/MMA8453/MMA8652/MMA8653
*
* Copyright (C) 2012-2014 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 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/init.h>
#include <linux/slab.h>
#include <linux/i2c.h>
#include <linux/pm.h>
#include <linux/mutex.h>
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/hwmon-sysfs.h>
#include <linux/err.h>
#include <linux/poll.h>
#include <linux/hwmon.h>
#include <linux/input.h>
#include <linux/miscdevice.h>
#include <linux/regulator/consumer.h>
#define MMA8X5X_I2C_ADDR 0x1D
#define MMA8451_ID 0x1A
#define MMA8452_ID 0x2A
#define MMA8453_ID 0x3A
#define MMA8652_ID 0x4A
#define MMA8653_ID 0x5A
#define POLL_INTERVAL_MIN 1
#define POLL_INTERVAL_MAX 500
#define POLL_INTERVAL 100 /* msecs */
/* if sensor is standby ,set POLL_STOP_TIME to slow down the poll */
#define POLL_STOP_TIME 200
#define INPUT_FUZZ 32
#define INPUT_FLAT 32
#define MODE_CHANGE_DELAY_MS 100
#define MMA8X5X_STATUS_ZYXDR 0x08
#define MMA8X5X_BUF_SIZE 6
#define MMA8X5X_FIFO_SIZE 32
/* register enum for mma8x5x registers */
enum {
MMA8X5X_STATUS = 0x00,
MMA8X5X_OUT_X_MSB,
MMA8X5X_OUT_X_LSB,
MMA8X5X_OUT_Y_MSB,
MMA8X5X_OUT_Y_LSB,
MMA8X5X_OUT_Z_MSB,
MMA8X5X_OUT_Z_LSB,
MMA8X5X_F_SETUP = 0x09,
MMA8X5X_TRIG_CFG,
MMA8X5X_SYSMOD,
MMA8X5X_INT_SOURCE,
MMA8X5X_WHO_AM_I,
MMA8X5X_XYZ_DATA_CFG,
MMA8X5X_HP_FILTER_CUTOFF,
MMA8X5X_PL_STATUS,
MMA8X5X_PL_CFG,
MMA8X5X_PL_COUNT,
MMA8X5X_PL_BF_ZCOMP,
MMA8X5X_P_L_THS_REG,
MMA8X5X_FF_MT_CFG,
MMA8X5X_FF_MT_SRC,
MMA8X5X_FF_MT_THS,
MMA8X5X_FF_MT_COUNT,
MMA8X5X_TRANSIENT_CFG = 0x1D,
MMA8X5X_TRANSIENT_SRC,
MMA8X5X_TRANSIENT_THS,
MMA8X5X_TRANSIENT_COUNT,
MMA8X5X_PULSE_CFG,
MMA8X5X_PULSE_SRC,
MMA8X5X_PULSE_THSX,
MMA8X5X_PULSE_THSY,
MMA8X5X_PULSE_THSZ,
MMA8X5X_PULSE_TMLT,
MMA8X5X_PULSE_LTCY,
MMA8X5X_PULSE_WIND,
MMA8X5X_ASLP_COUNT,
MMA8X5X_CTRL_REG1,
MMA8X5X_CTRL_REG2,
MMA8X5X_CTRL_REG3,
MMA8X5X_CTRL_REG4,
MMA8X5X_CTRL_REG5,
MMA8X5X_OFF_X,
MMA8X5X_OFF_Y,
MMA8X5X_OFF_Z,
MMA8X5X_REG_END,
};
/* The sensitivity is represented in counts/g. In 2g mode the
sensitivity is 1024 counts/g. In 4g mode the sensitivity is 512
counts/g and in 8g mode the sensitivity is 256 counts/g.
*/
enum {
MODE_2G = 0,
MODE_4G,
MODE_8G,
};
enum {
MMA_STANDBY = 0,
MMA_ACTIVED,
};
#pragma pack(1)
struct mma8x5x_data_axis {
short x;
short y;
short z;
};
struct mma8x5x_fifo{
int count;
s64 period;
s64 timestamp;
struct mma8x5x_data_axis fifo_data[MMA8X5X_FIFO_SIZE];
};
#pragma pack()
struct mma8x5x_data {
struct i2c_client *client;
struct input_dev *idev;
struct delayed_work work;
struct mutex data_lock;
struct mma8x5x_fifo fifo;
wait_queue_head_t fifo_wq;
atomic_t fifo_ready;
int active;
int delay;
int position;
u8 chip_id;
int mode;
int awaken;
s64 period_rel;
int fifo_wakeup;
int fifo_timeout;
u32 int_pin;
};
static struct mma8x5x_data *p_mma8x5x_data;
/* Addresses scanned */
static const unsigned short normal_i2c[] = { 0x1c, 0x1d, I2C_CLIENT_END };
static int mma8x5x_chip_id[] = {
MMA8451_ID,
MMA8452_ID,
MMA8453_ID,
MMA8652_ID,
MMA8653_ID,
};
static char *mma8x5x_names[] = {
"mma8451",
"mma8452",
"mma8453",
"mma8652",
"mma8653",
};
static int mma8x5x_position_setting[8][3][3] = {
{ { 0, -1, 0 }, { 1, 0, 0 }, { 0, 0, 1 } },
{ { -1, 0, 0 }, { 0, -1, 0 }, { 0, 0, 1 } },
{ { 0, 1, 0 }, { -1, 0, 0 }, { 0, 0, 1 } },
{ { 1, 0, 0 }, { 0, 1, 0 }, { 0, 0, 1 } },
{ { 0, -1, 0 }, { -1, 0, 0 }, { 0, 0, -1 } },
{ { -1, 0, 0 }, { 0, 1, 0 }, { 0, 0, -1 } },
{ { 0, 1, 0 }, { 1, 0, 0 }, { 0, 0, -1 } },
{ { 1, 0, 0 }, { 0, -1, 0 }, { 0, 0, -1 } },
};
static int mma8x5x_data_convert(struct mma8x5x_data *pdata,
struct mma8x5x_data_axis *axis_data)
{
short rawdata[3], data[3];
int i, j;
int position = pdata->position;
if (position < 0 || position > 7)
position = 0;
rawdata[0] = axis_data->x;
rawdata[1] = axis_data->y;
rawdata[2] = axis_data->z;
for (i = 0; i < 3; i++) {
data[i] = 0;
for (j = 0; j < 3; j++)
data[i] += rawdata[j] *
mma8x5x_position_setting[position][i][j];
}
axis_data->x = data[0];
axis_data->y = data[1];
axis_data->z = data[2];
return 0;
}
static int mma8x5x_check_id(int id)
{
int i = 0;
for (i = 0; i < sizeof(mma8x5x_chip_id) /
sizeof(mma8x5x_chip_id[0]); i++)
if (id == mma8x5x_chip_id[i])
return 1;
return 0;
}
static char *mma8x5x_id2name(u8 id)
{
return mma8x5x_names[(id >> 4) - 1];
}
static int mma8x5x_i2c_read_fifo(struct i2c_client *client,
u8 reg, char *buf, int len)
{
char send_buf[] = {reg};
struct i2c_msg msgs[] = {
{
.addr = client->addr,
.flags = 0,
.len = 1,
.buf = send_buf,
},
{
.addr = client->addr,
.flags = I2C_M_RD,
.len = len,
.buf = buf,
},
};
if (i2c_transfer(client->adapter, msgs, 2) < 0) {
printk(KERN_ERR "mma8x5x: transfer error\n");
return -EIO;
} else
return len;
}
/*period is ms, return the real period per event*/
static s64 mma8x5x_odr_set(struct i2c_client *client, int period)
{
u8 odr;
u8 val;
s64 period_rel;
val = i2c_smbus_read_byte_data(client, MMA8X5X_CTRL_REG1);
/*Standby*/
i2c_smbus_write_byte_data(client, MMA8X5X_CTRL_REG1, val & (~0x01));
val &= ~(0x07 << 3);
if (period >= 640) {
/*1.56HZ*/
odr = 0x7;
period_rel = 640 * NSEC_PER_MSEC;
} else if (period >= 160) {
/*6.25HZ*/
odr = 0x06;
period_rel = 160 * NSEC_PER_MSEC;
} else if (period >= 80) {
/*12.5HZ*/
odr = 0x05;
period_rel = 80 * NSEC_PER_MSEC;
} else if (period >= 20) {
/*50HZ*/
odr = 0x04;
period_rel = 20 * NSEC_PER_MSEC;
} else if (period >= 10) {
/*100HZ*/
odr = 0x03;
period_rel = 10 * NSEC_PER_MSEC;
} else if (period >= 5) {
/*200HZ*/
odr = 0x02;
period_rel = 5 * NSEC_PER_MSEC;
} else if ((period * 2) >= 5) {
/*400HZ*/
odr = 0x01;
period_rel = 2500 * NSEC_PER_USEC;
} else {
/*800HZ*/
odr = 0x00;
period_rel = 1250 * NSEC_PER_USEC;
}
val |= (odr << 3);
/*Standby*/
i2c_smbus_write_byte_data(client, MMA8X5X_CTRL_REG1, val);
return period_rel;
}
static int mma8x5x_device_init(struct i2c_client *client)
{
int result;
struct mma8x5x_data *pdata = i2c_get_clientdata(client);
result = i2c_smbus_write_byte_data(client, MMA8X5X_CTRL_REG1, 0);
if (result < 0)
goto out;
result = i2c_smbus_write_byte_data(client, MMA8X5X_XYZ_DATA_CFG,
pdata->mode);
if (result < 0)
goto out;
pdata->active = MMA_STANDBY;
msleep(MODE_CHANGE_DELAY_MS);
return 0;
out:
dev_err(&client->dev, "error when init mma8x5x:(%d)", result);
return result;
}
static int mma8x5x_device_stop(struct i2c_client *client)
{
u8 val;
val = i2c_smbus_read_byte_data(client, MMA8X5X_CTRL_REG1);
i2c_smbus_write_byte_data(client, MMA8X5X_CTRL_REG1, val & 0xfe);
return 0;
}
static int mma8x5x_read_data(struct i2c_client *client,
struct mma8x5x_data_axis *data)
{
u8 tmp_data[MMA8X5X_BUF_SIZE];
int ret;
ret = i2c_smbus_read_i2c_block_data(client,
MMA8X5X_OUT_X_MSB,
MMA8X5X_BUF_SIZE, tmp_data);
if (ret < MMA8X5X_BUF_SIZE) {
dev_err(&client->dev, "i2c block read failed\n");
return -EIO;
}
data->x = ((tmp_data[0] << 8) & 0xff00) | tmp_data[1];
data->y = ((tmp_data[2] << 8) & 0xff00) | tmp_data[3];
data->z = ((tmp_data[4] << 8) & 0xff00) | tmp_data[5];
return 0;
}
static int mma8x5x_fifo_interrupt(struct i2c_client *client, int enable)
{
u8 val, sys_mode;
sys_mode = i2c_smbus_read_byte_data(client, MMA8X5X_CTRL_REG1);
/*standby*/
i2c_smbus_write_byte_data(client, MMA8X5X_CTRL_REG1,
(sys_mode & (~0x01)));
val = i2c_smbus_read_byte_data(client, MMA8X5X_CTRL_REG4);
val &= ~(0x01 << 6);
if (enable)
val |= (0x01 << 6);
i2c_smbus_write_byte_data(client, MMA8X5X_CTRL_REG4, val);
i2c_smbus_write_byte_data(client, MMA8X5X_CTRL_REG1, sys_mode);
return 0;
}
static int mma8x5x_fifo_setting(struct mma8x5x_data *pdata,
int time_out, int is_overwrite)
{
u8 val, sys_mode, pin_cfg;
struct i2c_client *client = pdata->client;
sys_mode = i2c_smbus_read_byte_data(client, MMA8X5X_CTRL_REG1);
/*standby*/
i2c_smbus_write_byte_data(client,
MMA8X5X_CTRL_REG1,
(sys_mode & (~0x01)));
pin_cfg = i2c_smbus_read_byte_data(client, MMA8X5X_CTRL_REG5);
val = i2c_smbus_read_byte_data(client, MMA8X5X_F_SETUP);
val &= ~(0x03 << 6);
if (time_out > 0) {
if (is_overwrite)
val |= (0x01 << 6);
else
val |= (0x02 << 6);
}
i2c_smbus_write_byte_data(client, MMA8X5X_F_SETUP, val);
/*route to pin 1*/
if (pdata->int_pin == 1)
i2c_smbus_write_byte_data(client,
MMA8X5X_CTRL_REG5,
pin_cfg | (0x01 << 6));
/*route to pin 1*/
else
i2c_smbus_write_byte_data(client,
MMA8X5X_CTRL_REG5,
pin_cfg & ~(0x01 << 6));
i2c_smbus_write_byte_data(client, MMA8X5X_CTRL_REG1, sys_mode);
if (time_out > 0) {
/*fifo len is 32*/
pdata->period_rel = mma8x5x_odr_set(client, time_out/32);
}
return 0;
}
static int mma8x5x_read_fifo_data(struct mma8x5x_data *pdata)
{
int count, cnt;
u8 buf[256], val;
int i, index;
struct i2c_client *client = pdata->client;
struct mma8x5x_fifo *pfifo = &pdata->fifo;
struct timespec ts;
val = i2c_smbus_read_byte_data(client, MMA8X5X_STATUS);
/*FIFO overflow*/
if (val & (0x01 << 7)) {
cnt = (val & 0x3f);
count = mma8x5x_i2c_read_fifo(client, MMA8X5X_OUT_X_MSB,
buf, MMA8X5X_BUF_SIZE * cnt);
if (count > 0) {
ktime_get_ts(&ts);
for (i = 0; i < count/MMA8X5X_BUF_SIZE ; i++) {
index = MMA8X5X_BUF_SIZE * i;
pfifo->fifo_data[i].x =
((buf[index] << 8) & 0xff00) | buf[index + 1];
pfifo->fifo_data[i].y =
((buf[index + 2] << 8) & 0xff00) | buf[index + 3];
pfifo->fifo_data[i].z =
((buf[index + 4] << 8) & 0xff00) | buf[index + 5];
mma8x5x_data_convert(pdata,
&pfifo->fifo_data[i]);
}
pfifo->period = pdata->period_rel;
pfifo->count = count / MMA8X5X_BUF_SIZE;
pfifo->timestamp = ((s64)ts.tv_sec) * NSEC_PER_SEC
+ ts.tv_nsec;
return 0;
}
}
return -1;
}
static void mma8x5x_report_data(struct mma8x5x_data *pdata)
{
struct mma8x5x_data_axis data;
int ret;
ret = mma8x5x_read_data(pdata->client, &data);
if (!ret) {
mma8x5x_data_convert(pdata, &data);
input_report_abs(pdata->idev, ABS_X, data.x);
input_report_abs(pdata->idev, ABS_Y, data.y);
input_report_abs(pdata->idev, ABS_Z, data.z);
input_sync(pdata->idev);
}
}
static void mma8x5x_work(struct mma8x5x_data *pdata)
{
int delay;
if (pdata->active == MMA_ACTIVED) {
delay = msecs_to_jiffies(pdata->delay);
if (delay >= HZ)
delay = round_jiffies_relative(delay);
schedule_delayed_work(&pdata->work, delay);
}
}
static void mma8x5x_dev_poll(struct work_struct *work)
{
struct mma8x5x_data *pdata = container_of(work,
struct mma8x5x_data,
work.work);
mma8x5x_report_data(pdata);
mma8x5x_work(pdata);
}
static irqreturn_t mma8x5x_irq_handler(int irq, void *dev)
{
int ret;
u8 int_src;
struct mma8x5x_data *pdata = (struct mma8x5x_data *)dev;
int_src = i2c_smbus_read_byte_data(pdata->client, MMA8X5X_INT_SOURCE);
if (int_src & (0x01 << 6)) {
ret = mma8x5x_read_fifo_data(pdata);
if (!ret) {
atomic_set(&pdata->fifo_ready, 1);
wake_up(&pdata->fifo_wq);
}
/*is just awken from suspend*/
if (pdata->awaken) {
/*10s timeout*/
mma8x5x_fifo_setting(pdata, pdata->fifo_timeout, 0);
mma8x5x_fifo_interrupt(pdata->client, 1);
pdata->awaken = 0;
}
}
return IRQ_HANDLED;
}
static ssize_t mma8x5x_enable_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct mma8x5x_data *pdata = dev_get_drvdata(dev);
struct i2c_client *client = pdata->client;
u8 val;
int enable;
mutex_lock(&pdata->data_lock);
val = i2c_smbus_read_byte_data(client, MMA8X5X_CTRL_REG1);
if ((val & 0x01) && pdata->active == MMA_ACTIVED)
enable = 1;
else
enable = 0;
mutex_unlock(&pdata->data_lock);
return sprintf(buf, "%d\n", enable);
}
static ssize_t mma8x5x_enable_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct mma8x5x_data *pdata = dev_get_drvdata(dev);
struct i2c_client *client = pdata->client;
int ret;
unsigned long enable;
u8 val = 0;
ret = strict_strtol(buf, 10, &enable);
if (ret) {
dev_err(dev, "string to long error\n");
return ret;
}
mutex_lock(&pdata->data_lock);
enable = (enable > 0) ? 1 : 0;
if (enable && pdata->active == MMA_STANDBY) {
val = i2c_smbus_read_byte_data(client, MMA8X5X_CTRL_REG1);
ret = i2c_smbus_write_byte_data(client,
MMA8X5X_CTRL_REG1,
val | 0x01);
if (!ret) {
pdata->active = MMA_ACTIVED;
/*continuous mode*/
if (pdata->fifo_timeout <= 0)
mma8x5x_work(pdata);
else {
/*fifo mode*/
mma8x5x_fifo_setting(pdata,
pdata->fifo_timeout, 0);
mma8x5x_fifo_interrupt(client, 1);
}
printk(KERN_INFO"mma enable setting active\n");
}
} else if (enable == 0 && pdata->active == MMA_ACTIVED) {
val = i2c_smbus_read_byte_data(client, MMA8X5X_CTRL_REG1);
ret = i2c_smbus_write_byte_data(client, MMA8X5X_CTRL_REG1,
val & 0xFE);
if (!ret) {
pdata->active = MMA_STANDBY;
if (pdata->fifo_timeout <= 0)
/*continuous mode*/
cancel_delayed_work_sync(&pdata->work);
else {
/*fifo mode*/
mma8x5x_fifo_setting(pdata, 0, 0);
mma8x5x_fifo_interrupt(client, 0);
}
printk(KERN_INFO"mma enable setting inactive\n");
}
}
mutex_unlock(&pdata->data_lock);
return count;
}
static ssize_t mma8x5x_delay_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct mma8x5x_data *pdata = dev_get_drvdata(dev);
int delay;
mutex_lock(&pdata->data_lock);
delay = pdata->delay;
mutex_unlock(&pdata->data_lock);
return sprintf(buf, "%d\n", delay);
}
static ssize_t mma8x5x_delay_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct mma8x5x_data *pdata = dev_get_drvdata(dev);
struct i2c_client *client = pdata->client;
int ret;
long delay;
ret = strict_strtol(buf, 10, &delay);
if (ret) {
dev_err(dev, "string to long error\n");
return ret;
}
mutex_lock(&pdata->data_lock);
cancel_delayed_work_sync(&pdata->work);
pdata->delay = (int)delay;
if (pdata->active == MMA_ACTIVED && pdata->fifo_timeout <= 0) {
mma8x5x_odr_set(client, (int)delay);
mma8x5x_work(pdata);
}
mutex_unlock(&pdata->data_lock);
return count;
}
static ssize_t mma8x5x_fifo_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
int count = 0;
struct mma8x5x_data *pdata = dev_get_drvdata(dev);
mutex_lock(&pdata->data_lock);
count = sprintf(&buf[count], "period poll :%d ms\n", pdata->delay);
count += sprintf(&buf[count], "period fifo :%lld ns\n",
pdata->period_rel);
count += sprintf(&buf[count], "timeout :%d ms\n", pdata->fifo_timeout);
/*is the interrupt enable*/
count += sprintf(&buf[count], "interrupt wake up: %s\n",
(pdata->fifo_wakeup ? "yes" : "no"));
mutex_unlock(&pdata->data_lock);
return count;
}
static ssize_t mma8x5x_fifo_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct mma8x5x_data *pdata = dev_get_drvdata(dev);
struct i2c_client *client = pdata->client;
int period, timeout, wakeup;
sscanf(buf, "%d,%d,%d", &period, &timeout, &wakeup);
printk(KERN_INFO"period %d ,timeout is %d, wake up is :%d\n",
period, timeout, wakeup);
if (timeout > 0) {
mutex_lock(&pdata->data_lock);
cancel_delayed_work_sync(&pdata->work);
pdata->delay = period;
mutex_unlock(&pdata->data_lock);
/*no overwirte fifo*/
mma8x5x_fifo_setting(pdata, timeout, 0);
mma8x5x_fifo_interrupt(client, 1);
pdata->fifo_timeout = timeout;
pdata->fifo_wakeup = wakeup;
} else {
/*no overwirte fifo*/
mma8x5x_fifo_setting(pdata, timeout, 0);
mma8x5x_fifo_interrupt(client, 0);
pdata->fifo_timeout = timeout;
pdata->fifo_wakeup = wakeup;
mutex_lock(&pdata->data_lock);
pdata->delay = period;
if (pdata->active == MMA_ACTIVED)
mma8x5x_work(pdata);
mutex_unlock(&pdata->data_lock);
}
return count;
}
static ssize_t mma8x5x_position_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct mma8x5x_data *pdata = dev_get_drvdata(dev);
int position = 0;
mutex_lock(&pdata->data_lock);
position = pdata->position;
mutex_unlock(&pdata->data_lock);
return sprintf(buf, "%d\n", position);
}
static ssize_t mma8x5x_position_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct mma8x5x_data *pdata = dev_get_drvdata(dev);
int ret;
long position;
ret = strict_strtol(buf, 10, &position);
if (ret) {
dev_err(dev, "string to long error\n");
return ret;
}
mutex_lock(&pdata->data_lock);
pdata->position = (int)position;
mutex_unlock(&pdata->data_lock);
return count;
}
static DEVICE_ATTR(enable, S_IWUSR | S_IRUGO,
mma8x5x_enable_show, mma8x5x_enable_store);
static DEVICE_ATTR(poll_delay, S_IWUSR | S_IRUGO,
mma8x5x_delay_show, mma8x5x_delay_store);
static DEVICE_ATTR(fifo, S_IWUSR | S_IRUGO,
mma8x5x_fifo_show, mma8x5x_fifo_store);
static DEVICE_ATTR(position, S_IWUSR | S_IRUGO,
mma8x5x_position_show, mma8x5x_position_store);
static struct attribute *mma8x5x_attributes[] = {
&dev_attr_enable.attr,
&dev_attr_poll_delay.attr,
&dev_attr_fifo.attr,
&dev_attr_position.attr,
NULL
};
static const struct attribute_group mma8x5x_attr_group = {
.attrs = mma8x5x_attributes,
};
static int mma8x5x_detect(struct i2c_client *client,
struct i2c_board_info *info)
{
struct i2c_adapter *adapter = client->adapter;
int chip_id;
if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_READ_WORD_DATA))
return -ENODEV;
chip_id = i2c_smbus_read_byte_data(client, MMA8X5X_WHO_AM_I);
if (!mma8x5x_check_id(chip_id))
return -ENODEV;
printk(KERN_INFO"check %s i2c address 0x%x\n",
mma8x5x_id2name(chip_id), client->addr);
strlcpy(info->type, "mma8x5x", I2C_NAME_SIZE);
return 0;
}
static int mma8x5x_open(struct inode *inode, struct file *file)
{
int err;
err = nonseekable_open(inode, file);
if (err)
return err;
file->private_data = p_mma8x5x_data;
return 0;
}
static ssize_t mma8x5x_read(struct file *file,
char __user *buf,
size_t size, loff_t *ppos)
{
struct mma8x5x_data *pdata = file->private_data;
int ret = 0;
if (!(file->f_flags & O_NONBLOCK)) {
ret = wait_event_interruptible(pdata->fifo_wq,
(atomic_read(&pdata->fifo_ready) != 0));
if (ret)
return ret;
}
if (!atomic_read(&pdata->fifo_ready))
return -ENODEV;
if (size < sizeof(struct mma8x5x_fifo)) {
printk(KERN_ERR"the buffer leght less than need\n");
return -ENOMEM;
}
if (!copy_to_user(buf, &pdata->fifo, sizeof(struct mma8x5x_fifo))) {
atomic_set(&pdata->fifo_ready, 0);
return size;
}
return -ENOMEM ;
}
static unsigned int mma8x5x_poll(struct file *file,
struct poll_table_struct *wait)
{
struct mma8x5x_data *pdata = file->private_data;
poll_wait(file, &pdata->fifo_wq, wait);
if (atomic_read(&pdata->fifo_ready))
return POLLIN | POLLRDNORM;
return 0;
}
static const struct file_operations mma8x5x_fops = {
.owner = THIS_MODULE,
.open = mma8x5x_open,
.read = mma8x5x_read,
.poll = mma8x5x_poll,
};
static struct miscdevice mma8x5x_dev = {
.minor = MISC_DYNAMIC_MINOR,
.name = "mma8x5x",
.fops = &mma8x5x_fops,
};
static int mma8x5x_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
int result, chip_id;
struct input_dev *idev;
struct mma8x5x_data *pdata;
struct i2c_adapter *adapter;
struct device_node *of_node = client->dev.of_node;
u32 pos = 0;
struct regulator *vdd, *vdd_io;
u32 irq_flag;
struct irq_data *irq_data;
vdd = devm_regulator_get(&client->dev, "vdd");
if (!IS_ERR(vdd)) {
result = regulator_enable(vdd);
if (result) {
dev_err(&client->dev, "vdd set voltage error\n");
return result;
}
}
vdd_io = devm_regulator_get(&client->dev, "vddio");
if (!IS_ERR(vdd_io)) {
result = regulator_enable(vdd_io);
if (result) {
dev_err(&client->dev, "vddio set voltage error\n");
return result;
}
}
adapter = to_i2c_adapter(client->dev.parent);
result = i2c_check_functionality(adapter,
I2C_FUNC_SMBUS_BYTE |
I2C_FUNC_SMBUS_BYTE_DATA);
if (!result)
goto err_out;
chip_id = i2c_smbus_read_byte_data(client, MMA8X5X_WHO_AM_I);
if (!mma8x5x_check_id(chip_id)) {
dev_err(&client->dev,
"read chip ID 0x%x is not equal to 0x%x,0x%x,0x%x,0x%x,0x%x!\n",
chip_id, MMA8451_ID, MMA8452_ID,
MMA8453_ID, MMA8652_ID, MMA8653_ID);
result = -EINVAL;
goto err_out;
}
pdata = kzalloc(sizeof(struct mma8x5x_data), GFP_KERNEL);
if (!pdata) {
result = -ENOMEM;
dev_err(&client->dev, "alloc data memory error!\n");
goto err_out;
}
/* Initialize the MMA8X5X chip */
memset(pdata, 0, sizeof(struct mma8x5x_data));
pdata->client = client;
pdata->chip_id = chip_id;
pdata->mode = MODE_2G;
pdata->fifo_wakeup = 0;
pdata->fifo_timeout = 0;
result = of_property_read_u32(of_node, "position", &pos);
if (result)
pos = 1;
pdata->position = (int)pos;
p_mma8x5x_data = pdata;
mutex_init(&pdata->data_lock);
i2c_set_clientdata(client, pdata);
mma8x5x_device_init(client);
idev = input_allocate_device();
if (!idev) {
result = -ENOMEM;
dev_err(&client->dev, "alloc input device failed!\n");
goto err_alloc_input_device;
}
idev->name = "FreescaleAccelerometer";
idev->uniq = mma8x5x_id2name(pdata->chip_id);
idev->id.bustype = BUS_I2C;
idev->evbit[0] = BIT_MASK(EV_ABS);
input_set_abs_params(idev, ABS_X, -0x7fff, 0x7fff, 0, 0);
input_set_abs_params(idev, ABS_Y, -0x7fff, 0x7fff, 0, 0);
input_set_abs_params(idev, ABS_Z, -0x7fff, 0x7fff, 0, 0);
dev_set_drvdata(&idev->dev, pdata);
pdata->idev = idev;
result = input_register_device(pdata->idev);
if (result) {
dev_err(&client->dev, "register input device failed!\n");
goto err_register_input_device;
}
pdata->delay = POLL_INTERVAL;
INIT_DELAYED_WORK(&pdata->work, mma8x5x_dev_poll);
result = sysfs_create_group(&idev->dev.kobj, &mma8x5x_attr_group);
if (result) {
dev_err(&client->dev, "create device file failed!\n");
result = -EINVAL;
goto err_create_sysfs;
}
init_waitqueue_head(&pdata->fifo_wq);
if (client->irq) {
irq_data = irq_get_irq_data(client->irq);
irq_flag = irqd_get_trigger_type(irq_data);
irq_flag |= IRQF_ONESHOT;
result = request_threaded_irq(client->irq, NULL,
mma8x5x_irq_handler,
irq_flag,
client->dev.driver->name,
pdata);
if (result < 0) {
dev_err(&client->dev,
"failed to register MMA8x5x irq %d!\n",
client->irq);
goto err_register_irq;
} else {
result = misc_register(&mma8x5x_dev);
if (result) {
dev_err(&client->dev,
"register fifo device error\n");
goto err_reigster_dev;
}
}
result = of_property_read_u32(of_node,
"interrupt-route",
&pdata->int_pin);
if (result) {
result = -EINVAL;
dev_err(&client->dev,
"Can't find interrupt-pin value\n");
goto err_reigster_dev;
}
if (pdata->int_pin == 0 || pdata->int_pin > 2) {
result = -EINVAL;
dev_err(&client->dev,
"The interrupt-pin value is invalid\n");
goto err_reigster_dev;
}
}
printk(KERN_INFO"mma8x5x device driver probe successfully\n");
return 0;
err_reigster_dev:
free_irq(client->irq, pdata);
err_register_irq:
sysfs_remove_group(&idev->dev.kobj, &mma8x5x_attr_group);
err_create_sysfs:
input_unregister_device(pdata->idev);
err_register_input_device:
input_free_device(idev);
err_alloc_input_device:
kfree(pdata);
err_out:
return result;
}
static int mma8x5x_remove(struct i2c_client *client)
{
struct mma8x5x_data *pdata = i2c_get_clientdata(client);
struct input_dev *idev = pdata->idev;
mma8x5x_device_stop(client);
if (pdata) {
sysfs_remove_group(&idev->dev.kobj, &mma8x5x_attr_group);
input_unregister_device(pdata->idev);
input_free_device(pdata->idev);
kfree(pdata);
}
return 0;
}
#ifdef CONFIG_PM_SLEEP
static int mma8x5x_suspend(struct device *dev)
{
struct i2c_client *client = to_i2c_client(dev);
struct mma8x5x_data *pdata = i2c_get_clientdata(client);
if (pdata->fifo_timeout <= 0) {
if (pdata->active == MMA_ACTIVED)
mma8x5x_device_stop(client);
} else {
if (pdata->active == MMA_ACTIVED) {
if (pdata->fifo_wakeup) {
/*10s timeout , overwrite*/
mma8x5x_fifo_setting(pdata, 10000, 0);
mma8x5x_fifo_interrupt(client, 1);
} else {
mma8x5x_fifo_interrupt(client, 0);
/*10s timeout , overwrite*/
mma8x5x_fifo_setting(pdata, 10000, 1);
}
}
}
return 0;
}
static int mma8x5x_resume(struct device *dev)
{
int val = 0;
struct i2c_client *client = to_i2c_client(dev);
struct mma8x5x_data *pdata = i2c_get_clientdata(client);
if (pdata->fifo_timeout <= 0) {
if (pdata->active == MMA_ACTIVED) {
val = i2c_smbus_read_byte_data(client,
MMA8X5X_CTRL_REG1);
i2c_smbus_write_byte_data(client,
MMA8X5X_CTRL_REG1, val | 0x01);
}
} else {
if (pdata->active == MMA_ACTIVED) {
mma8x5x_fifo_interrupt(client, 1);
/*Awake from suspend*/
pdata->awaken = 1;
}
}
return 0;
}
#endif
static const struct i2c_device_id mma8x5x_id[] = {
{"mma8451", 0},
{"mma8452", 0},
{"mma8453", 0},
{"mma8652", 0},
{"mma8653", 0},
{}
};
MODULE_DEVICE_TABLE(i2c, mma8x5x_id);
static SIMPLE_DEV_PM_OPS(mma8x5x_pm_ops, mma8x5x_suspend, mma8x5x_resume);
static struct i2c_driver mma8x5x_driver = {
.class = I2C_CLASS_HWMON,
.driver = {
.name = "mma8x5x",
.owner = THIS_MODULE,
.pm = &mma8x5x_pm_ops,
},
.probe = mma8x5x_probe,
.remove = mma8x5x_remove,
.id_table = mma8x5x_id,
.detect = mma8x5x_detect,
.address_list = normal_i2c,
};
module_i2c_driver(mma8x5x_driver);
MODULE_AUTHOR("Freescale Semiconductor, Inc.");
MODULE_DESCRIPTION("MMA8X5X 3-Axis Orientation/Motion Detection Sensor driver");
MODULE_LICENSE("GPL");