diff options
Diffstat (limited to 'drivers/hwmon/mma8x5x.c')
-rw-r--r-- | drivers/hwmon/mma8x5x.c | 907 |
1 files changed, 907 insertions, 0 deletions
diff --git a/drivers/hwmon/mma8x5x.c b/drivers/hwmon/mma8x5x.c new file mode 100644 index 000000000000..4b13a2521f86 --- /dev/null +++ b/drivers/hwmon/mma8x5x.c @@ -0,0 +1,907 @@ +/* + * 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> + +#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; // is just awake from suspend + s64 period_rel; + int fifo_wakeup; + int fifo_timeout; +}; +static struct mma8x5x_data * p_mma8x5x_data = NULL; + +/* 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); + i2c_smbus_write_byte_data(client, MMA8X5X_CTRL_REG1,val &(~0x01)); //standby + 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); + i2c_smbus_write_byte_data(client, MMA8X5X_CTRL_REG1,val); //standby + 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); + i2c_smbus_write_byte_data(client,MMA8X5X_CTRL_REG1,(sys_mode & (~0x01))); //standby + 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); + i2c_smbus_write_byte_data(client,MMA8X5X_CTRL_REG1,(sys_mode & (~0x01))); //standby + 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); + i2c_smbus_write_byte_data(client,MMA8X5X_CTRL_REG5,pin_cfg |(0x01 << 6));//route to pin 1 + i2c_smbus_write_byte_data(client,MMA8X5X_CTRL_REG1,sys_mode); + if(time_out > 0){ + pdata->period_rel = mma8x5x_odr_set(client,time_out/32); //fifo len is 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); + if(val & (0x01 << 7)) //fifo overflow + { + 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); + } + if(pdata->awaken) /*is just awken from suspend*/ + { + mma8x5x_fifo_setting(pdata,pdata->fifo_timeout,0); //10s timeout + 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; + + enable = simple_strtoul(buf, NULL, 10); + 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; + if(pdata->fifo_timeout <= 0) //continuous mode + mma8x5x_work(pdata); + else{ /*fifo mode*/ + mma8x5x_fifo_setting(pdata,pdata->fifo_timeout,0); //no overwirte fifo + 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); //no overwirte fifo + 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 delay; + delay = simple_strtoul(buf, NULL, 10); + mutex_lock(&pdata->data_lock); + cancel_delayed_work_sync(&pdata->work); + pdata->delay = delay; + if(pdata->active == MMA_ACTIVED && pdata->fifo_timeout <= 0){ + mma8x5x_odr_set(client,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); + count += sprintf(&buf[count],"interrupt wake up: %s\n", (pdata->fifo_wakeup ? "yes" : "no")); /*is the interrupt enable*/ + 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("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); + mma8x5x_fifo_setting(pdata,timeout,0); //no overwirte fifo + mma8x5x_fifo_interrupt(client,1); + pdata->fifo_timeout = timeout; + pdata->fifo_wakeup = wakeup; + }else{ + mma8x5x_fifo_setting(pdata,timeout,0); //no overwirte fifo + 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 position; + position = simple_strtoul(buf, NULL, 10); + mutex_lock(&pdata->data_lock); + pdata->position = 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; + 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; + + printk(KERN_ERR"%s here 7", __func__); + 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; + } + + printk(KERN_ERR"%s here 6", __func__); + /* 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; + + printk(KERN_ERR"%s here 0", __func__); + result = of_property_read_u32(of_node, "position",&pos ); + + printk(KERN_ERR"%s here result=%d pos=%d", __func__, result, pos); + if (result) + pos = 1; + pdata->position = (int)pos; + p_mma8x5x_data = pdata; + mutex_init(&pdata->data_lock); + i2c_set_clientdata(client, pdata); + + printk(KERN_ERR"%s here 12", __func__); + mma8x5x_device_init(client); + + printk(KERN_ERR"%s here 13", __func__); + idev = input_allocate_device(); + if (!idev) { + result = -ENOMEM; + dev_err(&client->dev, "alloc input device failed!\n"); + goto err_alloc_input_device; + } + printk(KERN_ERR"%s here 1", __func__); + 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 ; + printk(KERN_ERR"%s here 2", __func__); + 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){ + printk(KERN_ERR"%s here 3", __func__); + result= request_threaded_irq(client->irq,NULL, mma8x5x_irq_handler, + IRQF_TRIGGER_LOW | IRQF_ONESHOT, 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; + } + } + } + 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){ + mma8x5x_fifo_setting(pdata,10000,0); //10s timeout , overwrite + mma8x5x_fifo_interrupt(client,1); + }else{ + mma8x5x_fifo_interrupt(client,0); + mma8x5x_fifo_setting(pdata,10000,1); //10s timeout , overwrite + } + } + } + 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); + pdata->awaken = 1; //awake from suspend + } + } + return 0; + +} +#endif + +static const struct i2c_device_id mma8x5x_id[] = { + { "mma8x5x", 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"); + |