summaryrefslogtreecommitdiff
path: root/drivers/input/misc/mpl3115.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/input/misc/mpl3115.c')
-rw-r--r--drivers/input/misc/mpl3115.c356
1 files changed, 356 insertions, 0 deletions
diff --git a/drivers/input/misc/mpl3115.c b/drivers/input/misc/mpl3115.c
new file mode 100644
index 000000000000..4bd5420e4e93
--- /dev/null
+++ b/drivers/input/misc/mpl3115.c
@@ -0,0 +1,356 @@
+/*
+ * Copyright (C) 2010-2015 Freescale , 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>
+
+#define MPL3115_DRV_NAME "mpl3115"
+#define ABS_TEMPTERAURE ABS_MISC
+
+#define INPUT_FUZZ 32
+#define INPUT_FLAT 32
+#define MPL_ACTIVED 1
+#define MPL_STANDBY 0
+#define POLL_INTERVAL_MAX 500
+#define POLL_INTERVAL 100
+#define POLL_INTERVAL_MIN 1
+#define MPL3115_ID 0xc4
+#define MPLL_ACTIVE_MASK 0x01
+#define MPL3115_STATUS_DR 0x08
+
+/* MPL3115A register address */
+#define MPL3115_STATUS 0x00
+#define MPL3115_PRESSURE_DATA 0x01
+#define MPL3115_DR_STATUS 0x06
+#define MPL3115_DELTA_DATA 0x07
+#define MPL3115_WHO_AM_I 0x0c
+#define MPL3115_FIFO_STATUS 0x0d
+#define MPL3115_FIFO_DATA 0x0e
+#define MPL3115_FIFO_SETUP 0x0e
+#define MPL3115_TIME_DELAY 0x10
+#define MPL3115_SYS_MODE 0x11
+#define MPL3115_INT_SORCE 0x12
+#define MPL3115_PT_DATA_CFG 0x13
+#define MPL3115_BAR_IN_MSB 0x14
+#define MPL3115_P_ARLARM_MSB 0x16
+#define MPL3115_T_ARLARM 0x18
+#define MPL3115_P_ARLARM_WND_MSB 0x19
+#define MPL3115_T_ARLARM_WND 0x1b
+#define MPL3115_P_MIN_DATA 0x1c
+#define MPL3115_T_MIN_DATA 0x1f
+#define MPL3115_P_MAX_DATA 0x21
+#define MPL3115_T_MAX_DATA 0x24
+#define MPL3115_CTRL_REG1 0x26
+#define MPL3115_CTRL_REG2 0x27
+#define MPL3115_CTRL_REG3 0x28
+#define MPL3115_CTRL_REG4 0x29
+#define MPL3115_CTRL_REG5 0x2a
+#define MPL3115_OFFSET_P 0x2b
+#define MPL3115_OFFSET_T 0x2c
+#define MPL3115_OFFSET_H 0x2d
+
+#define DATA_SHIFT_BIT(data, bit) ((data << bit) & (0xff << bit))
+
+struct mpl3115_data {
+ struct i2c_client *client;
+ struct input_polled_dev *poll_dev;
+ struct mutex data_lock;
+ int active;
+};
+
+static int mpl3115_i2c_read(struct i2c_client *client, u8 reg, u8 *values, u8 length)
+{
+ return i2c_smbus_read_i2c_block_data(client, reg, length, values);
+}
+
+static int mpl3115_i2c_write(struct i2c_client *client, u8 reg, const u8 *values, u8 length)
+{
+ return i2c_smbus_write_i2c_block_data(client, reg, length, values);
+}
+
+static ssize_t mpl3115_enable_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ int val;
+ u8 sysmode;
+
+ struct input_polled_dev *poll_dev = dev_get_drvdata(dev);
+ struct mpl3115_data *pdata = (struct mpl3115_data *)(poll_dev->private);
+ struct i2c_client *client = pdata->client;
+ mutex_lock(&pdata->data_lock);
+ mpl3115_i2c_read(client, MPL3115_CTRL_REG1, &sysmode, 1);
+ sysmode &= MPLL_ACTIVE_MASK;
+ val = (sysmode ? 1 : 0);
+ mutex_unlock(&pdata->data_lock);
+
+ return sprintf(buf, "%d\n", val);
+}
+
+static ssize_t mpl3115_enable_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ int ret, enable;
+ u8 val;
+ struct input_polled_dev *poll_dev = dev_get_drvdata(dev);
+ struct mpl3115_data *pdata = (struct mpl3115_data *)(poll_dev->private);
+ struct i2c_client *client = pdata->client;
+
+ enable = simple_strtoul(buf, NULL, 10);
+ mutex_lock(&pdata->data_lock);
+ mpl3115_i2c_read(client, MPL3115_CTRL_REG1, &val, 1);
+ if (enable && pdata->active == MPL_STANDBY) {
+ val |= MPLL_ACTIVE_MASK;
+ ret = mpl3115_i2c_write(client, MPL3115_CTRL_REG1, &val, 1);
+ if (!ret)
+ pdata->active = MPL_ACTIVED;
+ printk("mpl3115 set active\n");
+ } else if (!enable && pdata->active == MPL_ACTIVED) {
+ val &= ~MPLL_ACTIVE_MASK;
+ ret = mpl3115_i2c_write(client, MPL3115_CTRL_REG1, &val, 1);
+ if (!ret)
+ pdata->active = MPL_STANDBY;
+ printk("mpl3115 set inactive\n");
+ }
+ mutex_unlock(&pdata->data_lock);
+
+ return count;
+}
+
+static DEVICE_ATTR(enable, S_IWUSR | S_IRUGO, mpl3115_enable_show, mpl3115_enable_store);
+
+static struct attribute *mpl3115_attributes[] = {
+ &dev_attr_enable.attr,
+ NULL
+};
+
+static const struct attribute_group mpl3115_attr_group = {
+ .attrs = mpl3115_attributes,
+};
+
+static void mpl3115_device_init(struct i2c_client *client)
+{
+ u8 val[8];
+ struct device_node *np = client->dev.of_node;
+
+ /* set interrupt pin as open-drain */
+ if (of_get_property(np, "interrupt-open-drain", NULL)) {
+ val[0] = 0x11;
+ mpl3115_i2c_write(client, MPL3115_CTRL_REG3, val, 1);
+ }
+
+ val[0] = 0x28;
+ mpl3115_i2c_write(client, MPL3115_CTRL_REG1, val, 1);
+}
+
+static int mpl3115_read_data(struct i2c_client *client, int *pres, short *temp)
+{
+ u8 tmp[5];
+
+ mpl3115_i2c_read(client, MPL3115_PRESSURE_DATA, tmp, 5);
+ *pres = (DATA_SHIFT_BIT(tmp[0], 24) | DATA_SHIFT_BIT(tmp[1], 16) | DATA_SHIFT_BIT(tmp[2], 8)) >> 12;
+ *temp = (DATA_SHIFT_BIT(tmp[3], 8) | DATA_SHIFT_BIT(tmp[4], 0)) >> 4;
+ return 0;
+}
+
+static void report_abs(struct mpl3115_data *pdata)
+{
+ struct input_dev *idev;
+ int pressure = 0;
+ short temperature = 0;
+
+ mutex_lock(&pdata->data_lock);
+ if (pdata->active == MPL_STANDBY)
+ goto out;
+ idev = pdata->poll_dev->input;
+ mpl3115_read_data(pdata->client, &pressure, &temperature);
+ input_report_abs(idev, ABS_PRESSURE, pressure);
+ input_report_abs(idev, ABS_TEMPTERAURE, temperature);
+ input_sync(idev);
+out:
+ mutex_unlock(&pdata->data_lock);
+}
+
+static void mpl3115_dev_poll(struct input_polled_dev *dev)
+{
+ struct mpl3115_data *pdata = (struct mpl3115_data *)dev->private;
+
+ report_abs(pdata);
+}
+
+static int mpl3115_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ int result, client_id;
+ struct input_dev *idev;
+ struct i2c_adapter *adapter;
+ struct mpl3115_data *pdata;
+
+ 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, MPL3115_WHO_AM_I);
+ printk("read mpl3115 chip id 0x%x\n", client_id);
+ if (client_id != MPL3115_ID) {
+ dev_err(&client->dev,
+ "read chip ID 0x%x is not equal to 0x%x!\n",
+ result, MPL3115_ID);
+ result = -EINVAL;
+ goto err_out;
+ }
+ pdata = kzalloc(sizeof(struct mpl3115_data), GFP_KERNEL);
+ if (!pdata)
+ goto err_out;
+ pdata->client = client;
+ i2c_set_clientdata(client, pdata);
+ mutex_init(&pdata->data_lock);
+ pdata->poll_dev = input_allocate_polled_device();
+ if (!pdata->poll_dev) {
+ result = -ENOMEM;
+ dev_err(&client->dev, "alloc poll device failed!\n");
+ goto err_alloc_data;
+ }
+ pdata->poll_dev->poll = mpl3115_dev_poll;
+ pdata->poll_dev->private = pdata;
+ pdata->poll_dev->poll_interval = POLL_INTERVAL;
+ pdata->poll_dev->poll_interval_min = POLL_INTERVAL_MIN;
+ pdata->poll_dev->poll_interval_max = POLL_INTERVAL_MAX;
+ idev = pdata->poll_dev->input;
+ idev->name = MPL3115_DRV_NAME;
+ idev->id.bustype = BUS_I2C;
+ idev->evbit[0] = BIT_MASK(EV_ABS);
+
+ input_set_abs_params(idev, ABS_PRESSURE, -0x7FFFFFFF, 0x7FFFFFFF, 0, 0);
+ input_set_abs_params(idev, ABS_TEMPTERAURE, -0x7FFFFFFF, 0x7FFFFFFF, 0, 0);
+ result = input_register_polled_device(pdata->poll_dev);
+ if (result) {
+ dev_err(&client->dev, "register poll device failed!\n");
+ goto error_free_poll_dev;
+ }
+ result = sysfs_create_group(&idev->dev.kobj, &mpl3115_attr_group);
+ if (result) {
+ dev_err(&client->dev, "create device file failed!\n");
+ result = -EINVAL;
+ goto error_register_polled_device;
+ }
+ mpl3115_device_init(client);
+ printk("mpl3115 device driver probe successfully");
+ return 0;
+error_register_polled_device:
+ input_unregister_polled_device(pdata->poll_dev);
+error_free_poll_dev:
+ input_free_polled_device(pdata->poll_dev);
+err_alloc_data:
+ kfree(pdata);
+err_out:
+ return result;
+}
+
+static int mpl3115_stop_chip(struct i2c_client *client)
+{
+ u8 val;
+ int ret;
+
+ mpl3115_i2c_read(client, MPL3115_CTRL_REG1, &val, 1);
+ val &= ~MPLL_ACTIVE_MASK;
+ ret = mpl3115_i2c_write(client, MPL3115_CTRL_REG1, &val, 1);
+
+ return 0;
+}
+static int mpl3115_start_chip(struct i2c_client *client)
+{
+ u8 val;
+ int ret;
+
+ mpl3115_i2c_read(client, MPL3115_CTRL_REG1, &val, 1);
+ val |= MPLL_ACTIVE_MASK;
+ ret = mpl3115_i2c_write(client, MPL3115_CTRL_REG1, &val, 1);
+
+ return 0;
+}
+static int mpl3115_remove(struct i2c_client *client)
+{
+ struct mpl3115_data *pdata = i2c_get_clientdata(client);
+ struct input_dev *idev = pdata->poll_dev->input;
+
+ mpl3115_stop_chip(client);
+ sysfs_remove_group(&idev->dev.kobj, &mpl3115_attr_group);
+ input_unregister_polled_device(pdata->poll_dev);
+ kfree(pdata);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int mpl3115_suspend(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct mpl3115_data *pdata = i2c_get_clientdata(client);
+ if (pdata->active == MPL_ACTIVED)
+ mpl3115_stop_chip(client);
+ return 0;
+}
+
+static int mpl3115_resume(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct mpl3115_data *pdata = i2c_get_clientdata(client);
+ if (pdata->active == MPL_ACTIVED)
+ mpl3115_start_chip(client);
+ return 0;
+}
+#endif
+
+static const struct i2c_device_id mpl3115_id[] = {
+ {MPL3115_DRV_NAME, 0},
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(i2c, mpl3115_id);
+
+static SIMPLE_DEV_PM_OPS(mpl3115_pm_ops, mpl3115_suspend, mpl3115_resume);
+static struct i2c_driver mpl3115_driver = {
+ .driver = {
+ .name = MPL3115_DRV_NAME,
+ .owner = THIS_MODULE,
+ .pm = &mpl3115_pm_ops,
+ },
+ .probe = mpl3115_probe,
+ .remove = mpl3115_remove,
+ .id_table = mpl3115_id,
+};
+
+module_i2c_driver(mpl3115_driver);
+
+MODULE_AUTHOR("Freescale Semiconductor, Inc.");
+MODULE_DESCRIPTION("MPL3115 Smart Pressure Sensor driver");
+MODULE_LICENSE("GPL");