mpl3115.c 10 KB
/*
 *  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");