input: sensors: add Freescale Accelerometer(MMA8X5X) driver.

Support for Freescale (MMA8451/MMA8452/MMA8453/MMA8652/MMA8653).

Change-Id: I505d73b1d6eb3be565f0cbbf1677cfe5c63595d0
Signed-off-by: Yan Zhang <b38342@freescale.com>
Signed-off-by: Wentao Xu <wentaox@codeaurora.org>
Git-commit: 65c115e988b26c694ece73edbb061dbd7f28ae0d
Git-repo: https://github.com/FSLSensors/sensors-linux-drv
[lix@codeaurora.org: Solve merging conflicts and compiling issue.]
Signed-off-by: Xiaocheng Li <lix@codeaurora.org>
diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig
index f6c647c..a052bd3 100644
--- a/drivers/input/misc/Kconfig
+++ b/drivers/input/misc/Kconfig
@@ -674,4 +674,22 @@
          To compile this driver as a module, choose M here: the
          module will be called bmp18x-i2c.
 
+config SENSORS_MMA8X5X
+	tristate "MMA8451/MMA8452/MMA8453/MMA8652/MMA8653 device driver"
+	depends on I2C && SYSFS
+	select INPUT_POLLDEV
+	default n
+	help
+	  If you say yes here you get support for the Freescale MMA8451/
+	  MMA8452/MMA8453/MMA8652/MMA8653  sensors.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called mma8x5x.
+
+config SENSORS_MMA_POSITION
+	int "MMA8x5x Accelerate Sensor Position Setting"
+	depends on SENSORS_MMA8X5X
+	default "0"
+	help
+	  this provide the sensor position setting , value is between 0~7
 endif
diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile
index 97d9782..4f29e05 100644
--- a/drivers/input/misc/Makefile
+++ b/drivers/input/misc/Makefile
@@ -63,3 +63,4 @@
 obj-$(CONFIG_STM_LIS3DH)		+= lis3dh_acc.o
 obj-$(CONFIG_BMP18X)			+= bmp18x-core.o
 obj-$(CONFIG_BMP18X_I2C)		+= bmp18x-i2c.o
+obj-$(CONFIG_SENSORS_MMA8X5X)	  	+= mma8x5x.o
diff --git a/drivers/input/misc/mma8x5x.c b/drivers/input/misc/mma8x5x.c
new file mode 100644
index 0000000..335b8e7
--- /dev/null
+++ b/drivers/input/misc/mma8x5x.c
@@ -0,0 +1,554 @@
+/*
+ *  mma8x5x.c - Linux kernel modules for 3-Axis Orientation/Motion
+ *  Detection Sensor MMA8451/MMA8452/MMA8453
+ *
+ *  Copyright (C) 2010-2011 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>
+
+
+#define MMA8X5X_I2C_ADDR	0x1D
+#define MMA8451_ID			0x1A
+#define MMA8452_ID			0x2A
+#define MMA8453_ID			0x3A
+#define MMA8652_ID			0x4A
+#define MMA8653_ID			0x5A
+
+
+#define POLL_INTERVAL_MIN	1
+#define POLL_INTERVAL_MAX	500
+#define POLL_INTERVAL		100 /* msecs */
+
+/* if sensor is standby ,set POLL_STOP_TIME to slow down the poll */
+#define POLL_STOP_TIME		200
+#define INPUT_FUZZ			32
+#define INPUT_FLAT			32
+#define MODE_CHANGE_DELAY_MS	100
+
+#define MMA8X5X_STATUS_ZYXDR	0x08
+#define MMA8X5X_BUF_SIZE	7
+/* register enum for mma8x5x registers */
+enum {
+	MMA8X5X_STATUS = 0x00,
+	MMA8X5X_OUT_X_MSB,
+	MMA8X5X_OUT_X_LSB,
+	MMA8X5X_OUT_Y_MSB,
+	MMA8X5X_OUT_Y_LSB,
+	MMA8X5X_OUT_Z_MSB,
+	MMA8X5X_OUT_Z_LSB,
+
+	MMA8X5X_F_SETUP = 0x09,
+	MMA8X5X_TRIG_CFG,
+	MMA8X5X_SYSMOD,
+	MMA8X5X_INT_SOURCE,
+	MMA8X5X_WHO_AM_I,
+	MMA8X5X_XYZ_DATA_CFG,
+	MMA8X5X_HP_FILTER_CUTOFF,
+
+	MMA8X5X_PL_STATUS,
+	MMA8X5X_PL_CFG,
+	MMA8X5X_PL_COUNT,
+	MMA8X5X_PL_BF_ZCOMP,
+	MMA8X5X_P_L_THS_REG,
+
+	MMA8X5X_FF_MT_CFG,
+	MMA8X5X_FF_MT_SRC,
+	MMA8X5X_FF_MT_THS,
+	MMA8X5X_FF_MT_COUNT,
+
+	MMA8X5X_TRANSIENT_CFG = 0x1D,
+	MMA8X5X_TRANSIENT_SRC,
+	MMA8X5X_TRANSIENT_THS,
+	MMA8X5X_TRANSIENT_COUNT,
+
+	MMA8X5X_PULSE_CFG,
+	MMA8X5X_PULSE_SRC,
+	MMA8X5X_PULSE_THSX,
+	MMA8X5X_PULSE_THSY,
+	MMA8X5X_PULSE_THSZ,
+	MMA8X5X_PULSE_TMLT,
+	MMA8X5X_PULSE_LTCY,
+	MMA8X5X_PULSE_WIND,
+
+	MMA8X5X_ASLP_COUNT,
+	MMA8X5X_CTRL_REG1,
+	MMA8X5X_CTRL_REG2,
+	MMA8X5X_CTRL_REG3,
+	MMA8X5X_CTRL_REG4,
+	MMA8X5X_CTRL_REG5,
+
+	MMA8X5X_OFF_X,
+	MMA8X5X_OFF_Y,
+	MMA8X5X_OFF_Z,
+
+	MMA8X5X_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,
+};
+struct mma8x5x_data_axis {
+	short x;
+	short y;
+	short z;
+};
+struct mma8x5x_data {
+	struct i2c_client *client;
+	struct input_polled_dev *poll_dev;
+	struct mutex data_lock;
+	int active;
+	int position;
+	u8 chip_id;
+	int mode;
+};
+/* Addresses scanned */
+static const unsigned short normal_i2c[] = {0x1c, 0x1d, I2C_CLIENT_END};
+
+static int mma8x5x_chip_id[] = {
+	MMA8451_ID,
+	MMA8452_ID,
+	MMA8453_ID,
+	MMA8652_ID,
+	MMA8653_ID,
+};
+static char *mma8x5x_names[] = {
+	"mma8451",
+	"mma8452",
+	"mma8453",
+	"mma8652",
+	"mma8653",
+};
+static int mma8x5x_position_setting[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 int mma8x5x_data_convert(struct mma8x5x_data *pdata,
+		struct mma8x5x_data_axis *axis_data)
+{
+	short rawdata[3], data[3];
+	int i, j;
+	int position = pdata->position ;
+	if (position < 0 || position > 7)
+		position = 0;
+	rawdata[0] = axis_data->x;
+	rawdata[1] = axis_data->y;
+	rawdata[2] = axis_data->z;
+	for (i = 0; i < 3 ; i++) {
+		data[i] = 0;
+		for (j = 0; j < 3; j++)
+			data[i] += rawdata[j] *
+				mma8x5x_position_setting[position][i][j];
+	}
+	axis_data->x = data[0];
+	axis_data->y = data[1];
+	axis_data->z = data[2];
+	return 0;
+}
+static int mma8x5x_check_id(int id)
+{
+	int i = 0;
+	for (i = 0; i < sizeof(mma8x5x_chip_id)/sizeof(mma8x5x_chip_id[0]);
+			i++)
+		if (id == mma8x5x_chip_id[i])
+			return 1;
+	return 0;
+}
+static char *mma8x5x_id2name(u8 id)
+{
+	return mma8x5x_names[(id >> 4)-1];
+}
+static int mma8x5x_device_init(struct i2c_client *client)
+{
+	int result;
+	struct mma8x5x_data *pdata = i2c_get_clientdata(client);
+	result = i2c_smbus_write_byte_data(client, MMA8X5X_CTRL_REG1, 0);
+	if (result < 0)
+		goto out;
+
+	result = i2c_smbus_write_byte_data(client, MMA8X5X_XYZ_DATA_CFG,
+					   pdata->mode);
+	if (result < 0)
+		goto out;
+	pdata->active = MMA_STANDBY;
+	msleep(MODE_CHANGE_DELAY_MS);
+	return 0;
+out:
+	dev_err(&client->dev, "error when init mma8x5x:(%d)", result);
+	return result;
+}
+static int mma8x5x_device_stop(struct i2c_client *client)
+{
+	u8 val;
+	val = i2c_smbus_read_byte_data(client, MMA8X5X_CTRL_REG1);
+	i2c_smbus_write_byte_data(client, MMA8X5X_CTRL_REG1, val & 0xfe);
+	return 0;
+}
+
+static int mma8x5x_read_data(struct i2c_client *client,
+		struct mma8x5x_data_axis *data)
+{
+	u8 tmp_data[MMA8X5X_BUF_SIZE];
+	int ret;
+
+	ret = i2c_smbus_read_i2c_block_data(client,
+					    MMA8X5X_OUT_X_MSB, 7, tmp_data);
+	if (ret < MMA8X5X_BUF_SIZE) {
+		dev_err(&client->dev, "i2c block read failed\n");
+		return -EIO;
+	}
+	data->x = ((tmp_data[0] << 8) & 0xff00) | tmp_data[1];
+	data->y = ((tmp_data[2] << 8) & 0xff00) | tmp_data[3];
+	data->z = ((tmp_data[4] << 8) & 0xff00) | tmp_data[5];
+	return 0;
+}
+
+static void mma8x5x_report_data(struct mma8x5x_data *pdata)
+{
+	struct input_polled_dev *poll_dev = pdata->poll_dev;
+	struct mma8x5x_data_axis data;
+	mutex_lock(&pdata->data_lock);
+	if (pdata->active == MMA_STANDBY) {
+		poll_dev->poll_interval = POLL_STOP_TIME;
+		/* if standby ,set as 10s to slow the poll. */
+		goto out;
+	} else {
+		if (poll_dev->poll_interval == POLL_STOP_TIME)
+			poll_dev->poll_interval = POLL_INTERVAL;
+	}
+	if (mma8x5x_read_data(pdata->client, &data) != 0)
+		goto out;
+	mma8x5x_data_convert(pdata, &data);
+	input_report_abs(poll_dev->input, ABS_X, data.x);
+	input_report_abs(poll_dev->input, ABS_Y, data.y);
+	input_report_abs(poll_dev->input, ABS_Z, data.z);
+	input_sync(poll_dev->input);
+out:
+	mutex_unlock(&pdata->data_lock);
+}
+
+static void mma8x5x_dev_poll(struct input_polled_dev *dev)
+{
+	struct mma8x5x_data *pdata = (struct mma8x5x_data *)dev->private;
+	mma8x5x_report_data(pdata);
+}
+
+static ssize_t mma8x5x_enable_show(struct device *dev,
+				   struct device_attribute *attr, char *buf)
+{
+	struct input_polled_dev *poll_dev = dev_get_drvdata(dev);
+	struct mma8x5x_data *pdata = (struct mma8x5x_data *)(poll_dev->private);
+	struct i2c_client *client = pdata->client;
+	u8 val;
+	int enable;
+
+	mutex_lock(&pdata->data_lock);
+	val = i2c_smbus_read_byte_data(client, MMA8X5X_CTRL_REG1);
+	if ((val & 0x01) && pdata->active == MMA_ACTIVED)
+		enable = 1;
+	else
+		enable = 0;
+	mutex_unlock(&pdata->data_lock);
+	return snprintf(buf, PAGE_SIZE, "%d\n", enable);
+}
+
+static ssize_t mma8x5x_enable_store(struct device *dev,
+				    struct device_attribute *attr,
+				    const char *buf, size_t count)
+{
+	struct input_polled_dev *poll_dev = dev_get_drvdata(dev);
+	struct mma8x5x_data *pdata = (struct mma8x5x_data *)(poll_dev->private);
+	struct i2c_client *client = pdata->client;
+	int ret;
+	unsigned long enable;
+	u8 val = 0;
+	ret = kstrtoul(buf, 10, &enable);
+	if (ret)
+		return ret;
+	mutex_lock(&pdata->data_lock);
+	enable = (enable > 0) ? 1 : 0;
+	if (enable && pdata->active == MMA_STANDBY) {
+		val = i2c_smbus_read_byte_data(client, MMA8X5X_CTRL_REG1);
+		ret = i2c_smbus_write_byte_data(client,
+				MMA8X5X_CTRL_REG1, val|0x01);
+		if (!ret) {
+			pdata->active = MMA_ACTIVED;
+			printk(KERN_INFO "mma enable setting active\n");
+		}
+	} else if (enable == 0  && pdata->active == MMA_ACTIVED) {
+		val = i2c_smbus_read_byte_data(client, MMA8X5X_CTRL_REG1);
+		ret = i2c_smbus_write_byte_data(client,
+			MMA8X5X_CTRL_REG1, val & 0xFE);
+		if (!ret) {
+			pdata->active = MMA_STANDBY;
+			printk(KERN_INFO "mma enable setting inactive\n");
+		}
+	}
+	mutex_unlock(&pdata->data_lock);
+	return count;
+}
+static ssize_t mma8x5x_position_show(struct device *dev,
+				   struct device_attribute *attr, char *buf)
+{
+	struct input_polled_dev *poll_dev = dev_get_drvdata(dev);
+	struct mma8x5x_data *pdata = (struct mma8x5x_data *)(poll_dev->private);
+	int position = 0;
+	mutex_lock(&pdata->data_lock);
+	position = pdata->position ;
+	mutex_unlock(&pdata->data_lock);
+	return snprintf(buf, PAGE_SIZE, "%d\n", position);
+}
+
+static ssize_t mma8x5x_position_store(struct device *dev,
+				    struct device_attribute *attr,
+				    const char *buf, size_t count)
+{
+	struct input_polled_dev *poll_dev = dev_get_drvdata(dev);
+	struct mma8x5x_data *pdata = (struct mma8x5x_data *)(poll_dev->private);
+	int position;
+	int ret;
+	ret = kstrtoint(buf, 10, &position);
+	if (ret)
+		return ret;
+	mutex_lock(&pdata->data_lock);
+	pdata->position = position;
+	mutex_unlock(&pdata->data_lock);
+	return count;
+}
+
+static DEVICE_ATTR(enable, S_IWUSR | S_IRUGO,
+		   mma8x5x_enable_show, mma8x5x_enable_store);
+static DEVICE_ATTR(position, S_IWUSR | S_IRUGO,
+		   mma8x5x_position_show, mma8x5x_position_store);
+
+static struct attribute *mma8x5x_attributes[] = {
+	&dev_attr_enable.attr,
+	&dev_attr_position.attr,
+	NULL
+};
+
+static const struct attribute_group mma8x5x_attr_group = {
+	.attrs = mma8x5x_attributes,
+};
+static int mma8x5x_detect(struct i2c_client *client,
+			  struct i2c_board_info *info)
+{
+	struct i2c_adapter *adapter = client->adapter;
+	int chip_id;
+	if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_READ_WORD_DATA))
+		return -ENODEV;
+	chip_id = i2c_smbus_read_byte_data(client, MMA8X5X_WHO_AM_I);
+	if (!mma8x5x_check_id(chip_id))
+		return -ENODEV;
+	printk(KERN_INFO "check %s i2c address 0x%x\n",
+		mma8x5x_id2name(chip_id), client->addr);
+	strlcpy(info->type, "mma8x5x", I2C_NAME_SIZE);
+	return 0;
+}
+static int __devinit mma8x5x_probe(struct i2c_client *client,
+				   const struct i2c_device_id *id)
+{
+	int result, chip_id;
+	struct input_dev *idev;
+	struct mma8x5x_data *pdata;
+	struct i2c_adapter *adapter;
+	struct input_polled_dev *poll_dev;
+	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;
+
+	chip_id = i2c_smbus_read_byte_data(client, MMA8X5X_WHO_AM_I);
+
+	if (!mma8x5x_check_id(chip_id)) {
+		dev_err(&client->dev,
+			"read chip ID 0x%x is not equal to 0x%x,0x%x,0x%x,0x%x,0x%x!\n",
+			chip_id, MMA8451_ID, MMA8452_ID, MMA8453_ID,
+			MMA8652_ID, MMA8653_ID);
+		result = -EINVAL;
+		goto err_out;
+	}
+	pdata = kzalloc(sizeof(struct mma8x5x_data), GFP_KERNEL);
+	if (!pdata) {
+		result = -ENOMEM;
+		dev_err(&client->dev, "alloc data memory error!\n");
+		goto err_out;
+	}
+	/* Initialize the MMA8X5X chip */
+	pdata->client = client;
+	pdata->chip_id = chip_id;
+	pdata->mode = MODE_2G;
+	pdata->position = CONFIG_SENSORS_MMA_POSITION;
+	mutex_init(&pdata->data_lock);
+	i2c_set_clientdata(client, pdata);
+	mma8x5x_device_init(client);
+	poll_dev = input_allocate_polled_device();
+	if (!poll_dev) {
+		result = -ENOMEM;
+		dev_err(&client->dev, "alloc poll device failed!\n");
+		goto err_alloc_poll_device;
+	}
+	poll_dev->poll = mma8x5x_dev_poll;
+	poll_dev->poll_interval = POLL_STOP_TIME;
+	poll_dev->poll_interval_min = POLL_INTERVAL_MIN;
+	poll_dev->poll_interval_max = POLL_INTERVAL_MAX;
+	poll_dev->private = pdata;
+	idev = poll_dev->input;
+	idev->name = "accelerometer";
+	idev->uniq = mma8x5x_id2name(pdata->chip_id);
+	idev->id.bustype = BUS_I2C;
+	idev->evbit[0] = BIT_MASK(EV_ABS);
+	input_set_abs_params(idev, ABS_X, -0x7fff, 0x7fff, 0, 0);
+	input_set_abs_params(idev, ABS_Y, -0x7fff, 0x7fff, 0, 0);
+	input_set_abs_params(idev, ABS_Z, -0x7fff, 0x7fff, 0, 0);
+	pdata->poll_dev = poll_dev;
+	result = input_register_polled_device(pdata->poll_dev);
+	if (result) {
+		dev_err(&client->dev, "register poll device failed!\n");
+		goto err_register_polled_device;
+	}
+	result = sysfs_create_group(&idev->dev.kobj, &mma8x5x_attr_group);
+	if (result) {
+		dev_err(&client->dev, "create device file failed!\n");
+		result = -EINVAL;
+		goto err_create_sysfs;
+	}
+	printk(KERN_INFO "mma8x5x device driver probe successfully\n");
+	return 0;
+err_create_sysfs:
+	input_unregister_polled_device(pdata->poll_dev);
+err_register_polled_device:
+	input_free_polled_device(poll_dev);
+err_alloc_poll_device:
+	kfree(pdata);
+err_out:
+	return result;
+}
+static int __devexit mma8x5x_remove(struct i2c_client *client)
+{
+	struct mma8x5x_data *pdata = i2c_get_clientdata(client);
+	struct input_polled_dev *poll_dev = pdata->poll_dev;
+	mma8x5x_device_stop(client);
+	if (pdata) {
+		input_unregister_polled_device(poll_dev);
+		input_free_polled_device(poll_dev);
+		kfree(pdata);
+	}
+	return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int mma8x5x_suspend(struct device *dev)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct mma8x5x_data *pdata = i2c_get_clientdata(client);
+	if (pdata->active == MMA_ACTIVED)
+		mma8x5x_device_stop(client);
+	return 0;
+}
+
+static int mma8x5x_resume(struct device *dev)
+{
+	int val = 0;
+	struct i2c_client *client = to_i2c_client(dev);
+	struct mma8x5x_data *pdata = i2c_get_clientdata(client);
+	if (pdata->active == MMA_ACTIVED) {
+		val = i2c_smbus_read_byte_data(client, MMA8X5X_CTRL_REG1);
+		i2c_smbus_write_byte_data(client, MMA8X5X_CTRL_REG1, val|0x01);
+	}
+	return 0;
+
+}
+#endif
+
+static const struct i2c_device_id mma8x5x_id[] = {
+	{"mma8x5x", 0},
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, mma8x5x_id);
+
+static SIMPLE_DEV_PM_OPS(mma8x5x_pm_ops, mma8x5x_suspend, mma8x5x_resume);
+static struct i2c_driver mma8x5x_driver = {
+	.class  = I2C_CLASS_HWMON,
+	.driver = {
+		   .name = "mma8x5x",
+		   .owner = THIS_MODULE,
+		   .pm = &mma8x5x_pm_ops,
+		   },
+	.probe = mma8x5x_probe,
+	.remove = __devexit_p(mma8x5x_remove),
+	.id_table = mma8x5x_id,
+	.detect = mma8x5x_detect,
+	.address_list = normal_i2c,
+};
+
+static int __init mma8x5x_init(void)
+{
+	/* register driver */
+	int res;
+
+	res = i2c_add_driver(&mma8x5x_driver);
+	if (res < 0) {
+		printk(KERN_INFO "add mma8x5x i2c driver failed\n");
+		return -ENODEV;
+	}
+	return res;
+}
+
+static void __exit mma8x5x_exit(void)
+{
+	i2c_del_driver(&mma8x5x_driver);
+}
+
+MODULE_AUTHOR("Freescale Semiconductor, Inc.");
+MODULE_DESCRIPTION("MMA8X5X 3-Axis Orientation/Motion Detection Sensor driver");
+MODULE_LICENSE("GPL");
+
+module_init(mma8x5x_init);
+module_exit(mma8x5x_exit);