604 lines
14 KiB
C
604 lines
14 KiB
C
/*
|
|
* 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:
|
|
if (!IS_ERR(vdd))
|
|
regulator_disable(vdd);
|
|
if (!IS_ERR(vdd_io))
|
|
regulator_disable(vdd_io);
|
|
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},
|
|
{ /* sentinel */ }
|
|
};
|
|
|
|
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);
|