| /* |
| * 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}, |
| { /* 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); |