diff options
author | Dong Aisheng <aisheng.dong@nxp.com> | 2019-12-02 18:05:28 +0800 |
---|---|---|
committer | Dong Aisheng <aisheng.dong@nxp.com> | 2019-12-02 18:05:28 +0800 |
commit | 7b8618cba8c3d24d0aecf6900f9b00b3be330fb1 (patch) | |
tree | e943dc87d7d84c0a9851d525b3f1d176a95850a3 /drivers/hwmon | |
parent | 37f302047d2f2a51005383dc303eb35bcaa3ee53 (diff) | |
parent | d2615a1773ae96effa40786bb3fc3faa90c7250c (diff) |
Merge branch 'sensor/next' into next
* sensor/next: (29 commits)
LF-99 hwmon: mag3110: correct processing order after probe error
MLK-22296-4 misc: mpl3115: Fix build warning when CONFIG_PM_SLEEP=n
MLK-17061-1 sensor: set sensor interrupt pins as open-drain
hwmon: mma8451: Add regulator_disable to avoid WARN_ON
hwmon: mag3110: Add regulator_disable to avoid WARN_ON
...
Diffstat (limited to 'drivers/hwmon')
-rw-r--r-- | drivers/hwmon/Kconfig | 15 | ||||
-rw-r--r-- | drivers/hwmon/Makefile | 2 | ||||
-rw-r--r-- | drivers/hwmon/mag3110.c | 665 | ||||
-rw-r--r-- | drivers/hwmon/mxc_mma8451.c | 603 |
4 files changed, 1285 insertions, 0 deletions
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 30cecddda6ed..2fa4666d5b07 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -1959,4 +1959,19 @@ config SENSORS_ATK0110 endif # ACPI +config SENSORS_MAG3110 + tristate "Freescale MAG3110 e-compass sensor" + depends on I2C && SYSFS + help + If you say yes here you get support for the Freescale MAG3110 + e-compass sensor. + This driver can also be built as a module. If so, the module + will be called mag3110. + +config MXC_MMA8451 + tristate "MMA8451 device driver" + depends on I2C + select INPUT_POLLDEV + default y + endif # HWMON diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 3045950a2f91..b033e6733b56 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -178,6 +178,8 @@ obj-$(CONFIG_SENSORS_W83L786NG) += w83l786ng.o obj-$(CONFIG_SENSORS_WM831X) += wm831x-hwmon.o obj-$(CONFIG_SENSORS_WM8350) += wm8350-hwmon.o obj-$(CONFIG_SENSORS_XGENE) += xgene-hwmon.o +obj-$(CONFIG_SENSORS_MAG3110) += mag3110.o +obj-$(CONFIG_MXC_MMA8451) += mxc_mma8451.o obj-$(CONFIG_SENSORS_OCC) += occ/ obj-$(CONFIG_PMBUS) += pmbus/ diff --git a/drivers/hwmon/mag3110.c b/drivers/hwmon/mag3110.c new file mode 100644 index 000000000000..a7f355c8078e --- /dev/null +++ b/drivers/hwmon/mag3110.c @@ -0,0 +1,665 @@ +/* + * + * Copyright (C) 2011-2014 Freescale Semiconductor, Inc. + * + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <linux/i2c.h> +#include <linux/irq.h> +#include <linux/platform_device.h> +#include <linux/input-polldev.h> +#include <linux/hwmon.h> +#include <linux/input.h> +#include <linux/wait.h> +#include <linux/workqueue.h> +#include <linux/of.h> +#include <linux/regulator/consumer.h> + +#define MAG3110_DRV_NAME "mag3110" +#define MAG3110_ID (0xC4) +#define MAG3110_XYZ_DATA_LEN (6) +#define MAG3110_STATUS_ZYXDR (0x08) +#define MAG3110_AC_MASK (0x01) +#define MAG3110_AC_OFFSET (0) +#define MAG3110_DR_MODE_MASK (0x7 << 5) +#define MAG3110_DR_MODE_OFFSET (5) + +#define POLL_INTERVAL_MAX (500) +#define POLL_INTERVAL (100) +#define INT_TIMEOUT (1000) +#define DEFAULT_POSITION (2) + +/* register enum for mag3110 registers */ +enum { + MAG3110_DR_STATUS = 0x00, + MAG3110_OUT_X_MSB, + MAG3110_OUT_X_LSB, + MAG3110_OUT_Y_MSB, + MAG3110_OUT_Y_LSB, + MAG3110_OUT_Z_MSB, + MAG3110_OUT_Z_LSB, + MAG3110_WHO_AM_I, + + MAG3110_OFF_X_MSB, + MAG3110_OFF_X_LSB, + MAG3110_OFF_Y_MSB, + MAG3110_OFF_Y_LSB, + MAG3110_OFF_Z_MSB, + MAG3110_OFF_Z_LSB, + + MAG3110_DIE_TEMP, + + MAG3110_CTRL_REG1 = 0x10, + MAG3110_CTRL_REG2, +}; + +enum { + MAG_STANDBY, + MAG_ACTIVED +}; + +struct mag3110_data { + struct i2c_client *client; + struct input_polled_dev *poll_dev; + struct device *hwmon_dev; + wait_queue_head_t waitq; + bool data_ready; + u8 ctl_reg1; + int active; + int position; + int use_irq; +}; + +static short MAGHAL[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 struct mag3110_data *mag3110_pdata; +static DEFINE_MUTEX(mag3110_lock); + +/* + * This function do one mag3110 register read. + */ +static int mag3110_adjust_position(short *x, short *y, short *z) +{ + short rawdata[3], data[3]; + int i, j; + int position = mag3110_pdata->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] * MAGHAL[position][i][j]; + } + *x = data[0]; + *y = data[1]; + *z = data[2]; + return 0; +} + +static int mag3110_read_reg(struct i2c_client *client, u8 reg) +{ + return i2c_smbus_read_byte_data(client, reg); +} + +/* + * This function do one mag3110 register write. + */ +static int mag3110_write_reg(struct i2c_client *client, u8 reg, char value) +{ + int ret; + + ret = i2c_smbus_write_byte_data(client, reg, value); + if (ret < 0) + dev_err(&client->dev, "i2c write failed\n"); + return ret; +} + +/* + * This function do multiple mag3110 registers read. + */ +static int mag3110_read_block_data(struct i2c_client *client, u8 reg, + int count, u8 *addr) +{ + if (i2c_smbus_read_i2c_block_data(client, reg, count, addr) < count) { + dev_err(&client->dev, "i2c block read failed\n"); + return -1; + } + + return count; +} + +/* + * Initialization function + */ +static int mag3110_init_client(struct i2c_client *client) +{ + int val, ret; + + /* enable automatic resets */ + val = 0x80; + ret = mag3110_write_reg(client, MAG3110_CTRL_REG2, val); + + /* set default data rate to 10HZ */ + val = mag3110_read_reg(client, MAG3110_CTRL_REG1); + val |= (0x0 << MAG3110_DR_MODE_OFFSET); + ret = mag3110_write_reg(client, MAG3110_CTRL_REG1, val); + + return ret; +} + +/* + * read sensor data from mag3110 + */ +static int mag3110_read_data(short *x, short *y, short *z) +{ + struct mag3110_data *data; + u8 tmp_data[MAG3110_XYZ_DATA_LEN]; + int retry = 3; + int result; + + if (!mag3110_pdata || mag3110_pdata->active == MAG_STANDBY) + return -EINVAL; + + data = mag3110_pdata; + if (data->use_irq && !wait_event_interruptible_timeout + (data->waitq, data->data_ready != 0, + msecs_to_jiffies(INT_TIMEOUT))) { + dev_dbg(&data->client->dev, "interrupt not received\n"); + return -ETIME; + } + + do { + msleep(1); + result = i2c_smbus_read_byte_data(data->client, + MAG3110_DR_STATUS); + retry--; + } while (!(result & MAG3110_STATUS_ZYXDR) && retry > 0); + /* Clear data_ready flag after data is read out */ + if (retry == 0) + return -EINVAL; + + data->data_ready = 0; + + while (i2c_smbus_read_byte_data(data->client, MAG3110_DR_STATUS)) { + if (mag3110_read_block_data(data->client, + MAG3110_OUT_X_MSB, MAG3110_XYZ_DATA_LEN, + tmp_data) < 0) + return -1; + } + + *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) +{ + struct input_dev *idev; + short x, y, z; + + mutex_lock(&mag3110_lock); + if (mag3110_read_data(&x, &y, &z) != 0) + goto out; + mag3110_adjust_position(&x, &y, &z); + idev = mag3110_pdata->poll_dev->input; + input_report_abs(idev, ABS_X, x); + input_report_abs(idev, ABS_Y, y); + input_report_abs(idev, ABS_Z, z); + input_sync(idev); +out: + mutex_unlock(&mag3110_lock); +} + +static void mag3110_dev_poll(struct input_polled_dev *dev) +{ + report_abs(); +} + +static irqreturn_t mag3110_irq_handler(int irq, void *dev_id) +{ + int result; + u8 tmp_data[MAG3110_XYZ_DATA_LEN]; + result = i2c_smbus_read_byte_data(mag3110_pdata->client, + MAG3110_DR_STATUS); + if (!(result & MAG3110_STATUS_ZYXDR)) + return IRQ_NONE; + + mag3110_pdata->data_ready = 1; + + if (mag3110_pdata->active == MAG_STANDBY) + /* + * Since the mode will be changed, sometimes irq will + * be handled in StandBy mode because of interrupt latency. + * So just clear the interrutp flag via reading block data. + */ + mag3110_read_block_data(mag3110_pdata->client, + MAG3110_OUT_X_MSB, + MAG3110_XYZ_DATA_LEN, tmp_data); + else + wake_up_interruptible(&mag3110_pdata->waitq); + + return IRQ_HANDLED; +} + +static ssize_t mag3110_enable_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct i2c_client *client; + int val; + mutex_lock(&mag3110_lock); + client = mag3110_pdata->client; + val = mag3110_read_reg(client, MAG3110_CTRL_REG1) & MAG3110_AC_MASK; + + mutex_unlock(&mag3110_lock); + return sprintf(buf, "%d\n", val); +} + +static ssize_t mag3110_enable_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct i2c_client *client; + int reg, ret; + long enable; + u8 tmp_data[MAG3110_XYZ_DATA_LEN]; + + ret = kstrtol(buf, 10, &enable); + if (ret) { + dev_err(dev, "string to long error\n"); + return ret; + } + + mutex_lock(&mag3110_lock); + client = mag3110_pdata->client; + + reg = mag3110_read_reg(client, MAG3110_CTRL_REG1); + if (enable && mag3110_pdata->active == MAG_STANDBY) { + reg |= MAG3110_AC_MASK; + ret = mag3110_write_reg(client, MAG3110_CTRL_REG1, reg); + if (!ret) + mag3110_pdata->active = MAG_ACTIVED; + } else if (!enable && mag3110_pdata->active == MAG_ACTIVED) { + reg &= ~MAG3110_AC_MASK; + ret = mag3110_write_reg(client, MAG3110_CTRL_REG1, reg); + if (!ret) + mag3110_pdata->active = MAG_STANDBY; + } + + /* Read out MSB data to clear interrupt flag */ + msleep(100); + mag3110_read_block_data(mag3110_pdata->client, MAG3110_OUT_X_MSB, + MAG3110_XYZ_DATA_LEN, tmp_data); + mutex_unlock(&mag3110_lock); + return count; +} + +static DEVICE_ATTR(enable, S_IWUSR | S_IRUGO, + mag3110_enable_show, mag3110_enable_store); + +static ssize_t mag3110_dr_mode_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct i2c_client *client; + int val; + + client = mag3110_pdata->client; + val = (mag3110_read_reg(client, MAG3110_CTRL_REG1) + & MAG3110_DR_MODE_MASK) >> MAG3110_DR_MODE_OFFSET; + + return sprintf(buf, "%d\n", val); +} + +static ssize_t mag3110_dr_mode_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct i2c_client *client; + int reg, ret; + unsigned long val; + + /* This must be done when mag3110 is disabled */ + if ((kstrtoul(buf, 10, &val) < 0) || (val > 7)) + return -EINVAL; + + client = mag3110_pdata->client; + reg = mag3110_read_reg(client, MAG3110_CTRL_REG1) & + ~MAG3110_DR_MODE_MASK; + reg |= (val << MAG3110_DR_MODE_OFFSET); + /* MAG3110_CTRL_REG1 bit 5-7: data rate mode */ + ret = mag3110_write_reg(client, MAG3110_CTRL_REG1, reg); + if (ret < 0) + return ret; + + return count; +} + +static DEVICE_ATTR(dr_mode, S_IWUSR | S_IRUGO, + mag3110_dr_mode_show, mag3110_dr_mode_store); + +static ssize_t mag3110_position_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int val; + mutex_lock(&mag3110_lock); + val = mag3110_pdata->position; + mutex_unlock(&mag3110_lock); + return sprintf(buf, "%d\n", val); +} + +static ssize_t mag3110_position_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + long position; + int ret; + ret = kstrtol(buf, 10, &position); + if (ret) { + dev_err(dev, "string to long error\n"); + return ret; + } + + mutex_lock(&mag3110_lock); + mag3110_pdata->position = (int)position; + mutex_unlock(&mag3110_lock); + return count; +} + +static DEVICE_ATTR(position, S_IWUSR | S_IRUGO, + mag3110_position_show, mag3110_position_store); + +static struct attribute *mag3110_attributes[] = { + &dev_attr_enable.attr, + &dev_attr_dr_mode.attr, + &dev_attr_position.attr, + NULL +}; + +static const struct attribute_group mag3110_attr_group = { + .attrs = mag3110_attributes, +}; + +static int mag3110_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct i2c_adapter *adapter; + struct input_dev *idev = NULL; + struct mag3110_data *data; + int ret = 0; + struct regulator *vdd, *vdd_io; + u32 pos = 0; + struct device_node *of_node = client->dev.of_node; + u32 irq_flag; + struct irq_data *irq_data = NULL; + bool shared_irq = of_property_read_bool(of_node, "shared-interrupt"); + + vdd = NULL; + vdd_io = NULL; + + vdd = devm_regulator_get(&client->dev, "vdd"); + if (!IS_ERR(vdd)) { + ret = regulator_enable(vdd); + if (ret) { + dev_err(&client->dev, "vdd set voltage error\n"); + return ret; + } + } + + vdd_io = devm_regulator_get(&client->dev, "vddio"); + if (!IS_ERR(vdd_io)) { + ret = regulator_enable(vdd_io); + if (ret) { + dev_err(&client->dev, "vddio set voltage error\n"); + return ret; + } + } + + adapter = to_i2c_adapter(client->dev.parent); + if (!i2c_check_functionality(adapter, + I2C_FUNC_SMBUS_BYTE | + I2C_FUNC_SMBUS_BYTE_DATA | + I2C_FUNC_SMBUS_I2C_BLOCK)) { + ret = -EIO; + goto error_disable_reg; + } + + dev_info(&client->dev, "check mag3110 chip ID\n"); + ret = mag3110_read_reg(client, MAG3110_WHO_AM_I); + if (MAG3110_ID != ret) { + dev_err(&client->dev, + "read chip ID 0x%x is not equal to 0x%x!\n", ret, + MAG3110_ID); + ret = -EINVAL; + goto error_disable_reg; + } + + data = kzalloc(sizeof(struct mag3110_data), GFP_KERNEL); + if (!data) { + ret = -ENOMEM; + goto error_disable_reg; + } + + data->client = client; + i2c_set_clientdata(client, data); + /* Init queue */ + init_waitqueue_head(&data->waitq); + + data->hwmon_dev = hwmon_device_register(&client->dev); + if (IS_ERR(data->hwmon_dev)) { + dev_err(&client->dev, "hwmon register failed!\n"); + ret = PTR_ERR(data->hwmon_dev); + goto error_rm_dev_sysfs; + } + + if (client->irq > 0) { + data->use_irq = 1; + irq_data = irq_get_irq_data(client->irq); + } + + /*input poll device register */ + data->poll_dev = input_allocate_polled_device(); + if (!data->poll_dev) { + dev_err(&client->dev, "alloc poll device failed!\n"); + ret = -ENOMEM; + goto error_rm_hwmon_dev; + } + data->poll_dev->poll = mag3110_dev_poll; + data->poll_dev->poll_interval = POLL_INTERVAL; + data->poll_dev->poll_interval_max = POLL_INTERVAL_MAX; + idev = data->poll_dev->input; + idev->name = MAG3110_DRV_NAME; + idev->id.bustype = BUS_I2C; + idev->evbit[0] = BIT_MASK(EV_ABS); + input_set_abs_params(idev, ABS_X, -15000, 15000, 0, 0); + input_set_abs_params(idev, ABS_Y, -15000, 15000, 0, 0); + input_set_abs_params(idev, ABS_Z, -15000, 15000, 0, 0); + ret = input_register_polled_device(data->poll_dev); + if (ret) { + dev_err(&client->dev, "register poll device failed!\n"); + goto error_free_poll_dev; + } + + /*create device group in sysfs as user interface */ + ret = sysfs_create_group(&idev->dev.kobj, &mag3110_attr_group); + if (ret) { + dev_err(&client->dev, "create device file failed!\n"); + ret = -EINVAL; + goto error_rm_poll_dev; + } + + if (data->use_irq) { + irq_flag = irqd_get_trigger_type(irq_data); + irq_flag |= IRQF_ONESHOT; + if (shared_irq) + irq_flag |= IRQF_SHARED; + ret = request_threaded_irq(client->irq, NULL, mag3110_irq_handler, + irq_flag, client->dev.driver->name, idev); + if (ret < 0) { + dev_err(&client->dev, "failed to register irq %d!\n", + client->irq); + goto error_rm_dev_sysfs; + } + } + + /* Initialize mag3110 chip */ + mag3110_init_client(client); + mag3110_pdata = data; + mag3110_pdata->active = MAG_STANDBY; + ret = of_property_read_u32(of_node, "position", &pos); + if (ret) + pos = DEFAULT_POSITION; + mag3110_pdata->position = (int)pos; + dev_info(&client->dev, "mag3110 is probed\n"); + return 0; +error_rm_dev_sysfs: + sysfs_remove_group(&client->dev.kobj, &mag3110_attr_group); +error_rm_poll_dev: + input_unregister_polled_device(data->poll_dev); +error_free_poll_dev: + input_free_polled_device(data->poll_dev); +error_rm_hwmon_dev: + hwmon_device_unregister(data->hwmon_dev); + + kfree(data); + mag3110_pdata = NULL; +error_disable_reg: + if (!IS_ERR(vdd)) + regulator_disable(vdd); + if (!IS_ERR(vdd_io)) + regulator_disable(vdd_io); + + return ret; +} + +static int mag3110_remove(struct i2c_client *client) +{ + struct mag3110_data *data; + int ret; + + data = i2c_get_clientdata(client); + + data->ctl_reg1 = mag3110_read_reg(client, MAG3110_CTRL_REG1); + ret = mag3110_write_reg(client, MAG3110_CTRL_REG1, + data->ctl_reg1 & ~MAG3110_AC_MASK); + + free_irq(client->irq, data); + input_unregister_polled_device(data->poll_dev); + input_free_polled_device(data->poll_dev); + hwmon_device_unregister(data->hwmon_dev); + sysfs_remove_group(&client->dev.kobj, &mag3110_attr_group); + kfree(data); + mag3110_pdata = NULL; + + return ret; +} + +#ifdef CONFIG_PM +static int mag3110_suspend(struct device *dev) +{ + int ret = 0; + struct i2c_client *client = to_i2c_client(dev); + struct mag3110_data *data = i2c_get_clientdata(client); + + if (data->active == MAG_ACTIVED) { + data->ctl_reg1 = mag3110_read_reg(client, MAG3110_CTRL_REG1); + ret = mag3110_write_reg(client, MAG3110_CTRL_REG1, + data->ctl_reg1 & ~MAG3110_AC_MASK); + } + return ret; +} + +static int mag3110_resume(struct device *dev) +{ + int ret = 0; + struct i2c_client *client = to_i2c_client(dev); + struct mag3110_data *data = i2c_get_clientdata(client); + u8 tmp_data[MAG3110_XYZ_DATA_LEN]; + + if (data->active == MAG_ACTIVED) { + ret = mag3110_write_reg(client, MAG3110_CTRL_REG1, + data->ctl_reg1); + + if (data->ctl_reg1 & MAG3110_AC_MASK) { + /* Read out MSB data to clear interrupt + flag automatically */ + mag3110_read_block_data(client, MAG3110_OUT_X_MSB, + MAG3110_XYZ_DATA_LEN, tmp_data); + } + } + return ret; +} + +static const struct dev_pm_ops mag3110_dev_pm_ops = { + .suspend = mag3110_suspend, + .resume = mag3110_resume, +}; +#define MAG3110_DEV_PM_OPS (&mag3110_dev_pm_ops) + +#else +#define MAG3110_DEV_PM_OPS NULL +#endif /* CONFIG_PM */ + +static const struct i2c_device_id mag3110_id[] = { + {MAG3110_DRV_NAME, 0}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, mag3110_id); +static struct i2c_driver mag3110_driver = { + .driver = { + .name = MAG3110_DRV_NAME, + .owner = THIS_MODULE, + .pm = MAG3110_DEV_PM_OPS, + }, + .probe = mag3110_probe, + .remove = mag3110_remove, + .id_table = mag3110_id, +}; + +static int __init mag3110_init(void) +{ + return i2c_add_driver(&mag3110_driver); +} + +static void __exit mag3110_exit(void) +{ + i2c_del_driver(&mag3110_driver); +} + +module_init(mag3110_init); +module_exit(mag3110_exit); +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("Freescale mag3110 3-axis magnetometer driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/mxc_mma8451.c b/drivers/hwmon/mxc_mma8451.c new file mode 100644 index 000000000000..3c6dbbe71bc1 --- /dev/null +++ b/drivers/hwmon/mxc_mma8451.c @@ -0,0 +1,603 @@ +/* + * 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); |