blob: f20fb8e5ef78fcdb8cc707023cd32c08c024c5e3 [file] [log] [blame] [edit]
/*
* mma8451.c - Linux kernel modules for 3-Axis Orientation/Motion
* Detection Sensor
*
* Copyright (C) 2010-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/hwmon.h>
#include <linux/input-polldev.h>
#include <linux/of.h>
#include <linux/regulator/consumer.h>
#define MMA8451_I2C_ADDR 0x1C
#define MMA8451_ID 0x1A
#define MMA8452_ID 0x2A
#define MMA8453_ID 0x3A
#define POLL_INTERVAL_MIN 1
#define POLL_INTERVAL_MAX 500
#define POLL_INTERVAL 100 /* msecs */
#define INPUT_FUZZ 32
#define INPUT_FLAT 32
#define MODE_CHANGE_DELAY_MS 100
#define MMA8451_STATUS_ZYXDR 0x08
#define MMA8451_BUF_SIZE 7
#define DEFAULT_POSITION 0
/* register enum for mma8451 registers */
enum {
MMA8451_STATUS = 0x00,
MMA8451_OUT_X_MSB,
MMA8451_OUT_X_LSB,
MMA8451_OUT_Y_MSB,
MMA8451_OUT_Y_LSB,
MMA8451_OUT_Z_MSB,
MMA8451_OUT_Z_LSB,
MMA8451_F_SETUP = 0x09,
MMA8451_TRIG_CFG,
MMA8451_SYSMOD,
MMA8451_INT_SOURCE,
MMA8451_WHO_AM_I,
MMA8451_XYZ_DATA_CFG,
MMA8451_HP_FILTER_CUTOFF,
MMA8451_PL_STATUS,
MMA8451_PL_CFG,
MMA8451_PL_COUNT,
MMA8451_PL_BF_ZCOMP,
MMA8451_P_L_THS_REG,
MMA8451_FF_MT_CFG,
MMA8451_FF_MT_SRC,
MMA8451_FF_MT_THS,
MMA8451_FF_MT_COUNT,
MMA8451_TRANSIENT_CFG = 0x1D,
MMA8451_TRANSIENT_SRC,
MMA8451_TRANSIENT_THS,
MMA8451_TRANSIENT_COUNT,
MMA8451_PULSE_CFG,
MMA8451_PULSE_SRC,
MMA8451_PULSE_THSX,
MMA8451_PULSE_THSY,
MMA8451_PULSE_THSZ,
MMA8451_PULSE_TMLT,
MMA8451_PULSE_LTCY,
MMA8451_PULSE_WIND,
MMA8451_ASLP_COUNT,
MMA8451_CTRL_REG1,
MMA8451_CTRL_REG2,
MMA8451_CTRL_REG3,
MMA8451_CTRL_REG4,
MMA8451_CTRL_REG5,
MMA8451_OFF_X,
MMA8451_OFF_Y,
MMA8451_OFF_Z,
MMA8451_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,
};
/* mma8451 status */
struct mma8451_status {
u8 mode;
u8 ctl_reg1;
int active;
int position;
};
static struct mma8451_status mma_status;
static struct input_polled_dev *mma8451_idev;
static struct device *hwmon_dev;
static struct i2c_client *mma8451_i2c_client;
static int senstive_mode = MODE_2G;
static int ACCHAL[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 DEFINE_MUTEX(mma8451_lock);
static int mma8451_adjust_position(short *x, short *y, short *z)
{
short rawdata[3], data[3];
int i, j;
int position = mma_status.position;
if (position < 0 || position > 7)
position = 0;
rawdata[0] = *x;
rawdata[1] = *y;
rawdata[2] = *z;
for (i = 0; i < 3; i++) {
data[i] = 0;
for (j = 0; j < 3; j++)
data[i] += rawdata[j] * ACCHAL[position][i][j];
}
*x = data[0];
*y = data[1];
*z = data[2];
return 0;
}
static int mma8451_change_mode(struct i2c_client *client, int mode)
{
int result;
mma_status.ctl_reg1 = 0;
result = i2c_smbus_write_byte_data(client, MMA8451_CTRL_REG1, 0);
if (result < 0)
goto out;
mma_status.active = MMA_STANDBY;
result = i2c_smbus_write_byte_data(client, MMA8451_XYZ_DATA_CFG,
mode);
if (result < 0)
goto out;
mdelay(MODE_CHANGE_DELAY_MS);
mma_status.mode = mode;
return 0;
out:
dev_err(&client->dev, "error when init mma8451:(%d)", result);
return result;
}
static int mma8451_read_data(short *x, short *y, short *z)
{
u8 tmp_data[MMA8451_BUF_SIZE];
int ret;
ret = i2c_smbus_read_i2c_block_data(mma8451_i2c_client,
MMA8451_OUT_X_MSB, 7, tmp_data);
if (ret < MMA8451_BUF_SIZE) {
dev_err(&mma8451_i2c_client->dev, "i2c block read failed\n");
return -EIO;
}
*x = ((tmp_data[0] << 8) & 0xff00) | tmp_data[1];
*y = ((tmp_data[2] << 8) & 0xff00) | tmp_data[3];
*z = ((tmp_data[4] << 8) & 0xff00) | tmp_data[5];
return 0;
}
static void report_abs(void)
{
short x, y, z;
int result;
int retry = 3;
mutex_lock(&mma8451_lock);
if (mma_status.active == MMA_STANDBY)
goto out;
/* wait for the data ready */
do {
result = i2c_smbus_read_byte_data(mma8451_i2c_client,
MMA8451_STATUS);
retry--;
msleep(1);
} while (!(result & MMA8451_STATUS_ZYXDR) && retry > 0);
if (retry == 0)
goto out;
if (mma8451_read_data(&x, &y, &z) != 0)
goto out;
mma8451_adjust_position(&x, &y, &z);
input_report_abs(mma8451_idev->input, ABS_X, x);
input_report_abs(mma8451_idev->input, ABS_Y, y);
input_report_abs(mma8451_idev->input, ABS_Z, z);
input_sync(mma8451_idev->input);
out:
mutex_unlock(&mma8451_lock);
}
static void mma8451_dev_poll(struct input_polled_dev *dev)
{
report_abs();
}
static ssize_t mma8451_enable_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct i2c_client *client;
u8 val;
int enable;
mutex_lock(&mma8451_lock);
client = mma8451_i2c_client;
val = i2c_smbus_read_byte_data(client, MMA8451_CTRL_REG1);
if ((val & 0x01) && mma_status.active == MMA_ACTIVED)
enable = 1;
else
enable = 0;
mutex_unlock(&mma8451_lock);
return sprintf(buf, "%d\n", enable);
}
static ssize_t mma8451_enable_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct i2c_client *client;
int ret;
unsigned long enable;
u8 val = 0;
ret = kstrtoul(buf, 10, &enable);
if (ret) {
dev_err(dev, "string transform error\n");
return ret;
}
mutex_lock(&mma8451_lock);
client = mma8451_i2c_client;
enable = (enable > 0) ? 1 : 0;
if (enable && mma_status.active == MMA_STANDBY) {
val = i2c_smbus_read_byte_data(client, MMA8451_CTRL_REG1);
ret =
i2c_smbus_write_byte_data(client, MMA8451_CTRL_REG1,
val | 0x01);
if (!ret)
mma_status.active = MMA_ACTIVED;
} else if (enable == 0 && mma_status.active == MMA_ACTIVED) {
val = i2c_smbus_read_byte_data(client, MMA8451_CTRL_REG1);
ret =
i2c_smbus_write_byte_data(client, MMA8451_CTRL_REG1,
val & 0xFE);
if (!ret)
mma_status.active = MMA_STANDBY;
}
mutex_unlock(&mma8451_lock);
return count;
}
static ssize_t mma8451_position_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
int position = 0;
mutex_lock(&mma8451_lock);
position = mma_status.position;
mutex_unlock(&mma8451_lock);
return sprintf(buf, "%d\n", position);
}
static ssize_t mma8451_position_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
unsigned long position;
int ret;
ret = kstrtoul(buf, 10, &position);
if (ret) {
dev_err(dev, "string transform error\n");
return ret;
}
mutex_lock(&mma8451_lock);
mma_status.position = (int)position;
mutex_unlock(&mma8451_lock);
return count;
}
static ssize_t mma8451_scalemode_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
int mode = 0;
mutex_lock(&mma8451_lock);
mode = (int)mma_status.mode;
mutex_unlock(&mma8451_lock);
return sprintf(buf, "%d\n", mode);
}
static ssize_t mma8451_scalemode_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
unsigned long mode;
int ret, active_save;
struct i2c_client *client = mma8451_i2c_client;
ret = kstrtoul(buf, 10, &mode);
if (ret) {
dev_err(dev, "string transform error\n");
goto out;
}
if (mode > MODE_8G) {
dev_warn(dev, "not supported mode\n");
ret = count;
goto out;
}
mutex_lock(&mma8451_lock);
if (mode == mma_status.mode) {
ret = count;
goto out_unlock;
}
active_save = mma_status.active;
ret = mma8451_change_mode(client, mode);
if (ret)
goto out_unlock;
if (active_save == MMA_ACTIVED) {
ret = i2c_smbus_write_byte_data(client, MMA8451_CTRL_REG1, 1);
if (ret)
goto out_unlock;
mma_status.active = active_save;
}
out_unlock:
mutex_unlock(&mma8451_lock);
out:
return ret;
}
static DEVICE_ATTR(enable, S_IWUSR | S_IRUGO,
mma8451_enable_show, mma8451_enable_store);
static DEVICE_ATTR(position, S_IWUSR | S_IRUGO,
mma8451_position_show, mma8451_position_store);
static DEVICE_ATTR(scalemode, S_IWUSR | S_IRUGO,
mma8451_scalemode_show, mma8451_scalemode_store);
static struct attribute *mma8451_attributes[] = {
&dev_attr_enable.attr,
&dev_attr_position.attr,
&dev_attr_scalemode.attr,
NULL
};
static const struct attribute_group mma8451_attr_group = {
.attrs = mma8451_attributes,
};
static int mma8451_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
int result, client_id;
struct input_dev *idev;
struct i2c_adapter *adapter;
u32 pos;
struct device_node *of_node = client->dev.of_node;
struct regulator *vdd, *vdd_io;
mma8451_i2c_client = client;
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;
client_id = i2c_smbus_read_byte_data(client, MMA8451_WHO_AM_I);
if (client_id != MMA8451_ID && client_id != MMA8452_ID
&& client_id != MMA8453_ID) {
dev_err(&client->dev,
"read chip ID 0x%x is not equal to 0x%x or 0x%x!\n",
result, MMA8451_ID, MMA8452_ID);
result = -EINVAL;
goto err_out;
}
/* Initialize the MMA8451 chip */
result = mma8451_change_mode(client, senstive_mode);
if (result) {
dev_err(&client->dev,
"error when init mma8451 chip:(%d)\n", result);
goto err_out;
}
hwmon_dev = hwmon_device_register(&client->dev);
if (!hwmon_dev) {
result = -ENOMEM;
dev_err(&client->dev, "error when register hwmon device\n");
goto err_out;
}
mma8451_idev = input_allocate_polled_device();
if (!mma8451_idev) {
result = -ENOMEM;
dev_err(&client->dev, "alloc poll device failed!\n");
goto err_alloc_poll_device;
}
mma8451_idev->poll = mma8451_dev_poll;
mma8451_idev->poll_interval = POLL_INTERVAL;
mma8451_idev->poll_interval_min = POLL_INTERVAL_MIN;
mma8451_idev->poll_interval_max = POLL_INTERVAL_MAX;
idev = mma8451_idev->input;
idev->name = "mma845x";
idev->id.bustype = BUS_I2C;
idev->evbit[0] = BIT_MASK(EV_ABS);
input_set_abs_params(idev, ABS_X, -8192, 8191, INPUT_FUZZ, INPUT_FLAT);
input_set_abs_params(idev, ABS_Y, -8192, 8191, INPUT_FUZZ, INPUT_FLAT);
input_set_abs_params(idev, ABS_Z, -8192, 8191, INPUT_FUZZ, INPUT_FLAT);
result = input_register_polled_device(mma8451_idev);
if (result) {
dev_err(&client->dev, "register poll device failed!\n");
goto err_register_polled_device;
}
result = sysfs_create_group(&idev->dev.kobj, &mma8451_attr_group);
if (result) {
dev_err(&client->dev, "create device file failed!\n");
result = -EINVAL;
goto err_register_polled_device;
}
result = of_property_read_u32(of_node, "position", &pos);
if (result)
pos = DEFAULT_POSITION;
mma_status.position = (int)pos;
return 0;
err_register_polled_device:
input_free_polled_device(mma8451_idev);
err_alloc_poll_device:
hwmon_device_unregister(&client->dev);
err_out:
return result;
}
static int mma8451_stop_chip(struct i2c_client *client)
{
int ret = 0;
if (mma_status.active == MMA_ACTIVED) {
mma_status.ctl_reg1 = i2c_smbus_read_byte_data(client,
MMA8451_CTRL_REG1);
ret = i2c_smbus_write_byte_data(client, MMA8451_CTRL_REG1,
mma_status.ctl_reg1 & 0xFE);
}
return ret;
}
static int mma8451_remove(struct i2c_client *client)
{
int ret;
ret = mma8451_stop_chip(client);
hwmon_device_unregister(hwmon_dev);
return ret;
}
#ifdef CONFIG_PM_SLEEP
static int mma8451_suspend(struct device *dev)
{
struct i2c_client *client = to_i2c_client(dev);
return mma8451_stop_chip(client);
}
static int mma8451_resume(struct device *dev)
{
int ret = 0;
struct i2c_client *client = to_i2c_client(dev);
if (mma_status.active == MMA_ACTIVED)
ret = i2c_smbus_write_byte_data(client, MMA8451_CTRL_REG1,
mma_status.ctl_reg1);
return ret;
}
#endif
static const struct i2c_device_id mma8451_id[] = {
{"mma8451", 0},
};
MODULE_DEVICE_TABLE(i2c, mma8451_id);
static SIMPLE_DEV_PM_OPS(mma8451_pm_ops, mma8451_suspend, mma8451_resume);
static struct i2c_driver mma8451_driver = {
.driver = {
.name = "mma8451",
.owner = THIS_MODULE,
.pm = &mma8451_pm_ops,
},
.probe = mma8451_probe,
.remove = mma8451_remove,
.id_table = mma8451_id,
};
static int __init mma8451_init(void)
{
/* register driver */
int res;
res = i2c_add_driver(&mma8451_driver);
if (res < 0) {
printk(KERN_INFO "add mma8451 i2c driver failed\n");
return -ENODEV;
}
return res;
}
static void __exit mma8451_exit(void)
{
i2c_del_driver(&mma8451_driver);
}
MODULE_AUTHOR("Freescale Semiconductor, Inc.");
MODULE_DESCRIPTION("MMA8451 3-Axis Orientation/Motion Detection Sensor driver");
MODULE_LICENSE("GPL");
module_init(mma8451_init);
module_exit(mma8451_exit);