[PATCH] add support for Freescale's MMA8653FC 10 bit accelerometer

From: Martin Kepplinger
Date: Tue Mar 17 2015 - 13:09:51 EST


From: Martin Kepplinger <martin.kepplinger@xxxxxxxxxxxxxxxxxxxxx>

Signed-off-by: Martin Kepplinger <martin.kepplinger@xxxxxxxxxxxxxxxxxxxxx>
Signed-off-by: Christoph Muellner <christoph.muellner@xxxxxxxxxxxxxxxxxxxxx>
---
.../testing/sysfs-bus-i2c-devices-fsl-mma8653fc | 36 +
.../devicetree/bindings/misc/fsl,mma8653fc.txt | 95 +++
MAINTAINERS | 6 +
drivers/input/misc/Kconfig | 11 +
drivers/input/misc/Makefile | 1 +
drivers/input/misc/mma8653fc.c | 907 +++++++++++++++++++++
6 files changed, 1056 insertions(+)
create mode 100644 Documentation/ABI/testing/sysfs-bus-i2c-devices-fsl-mma8653fc
create mode 100644 Documentation/devicetree/bindings/misc/fsl,mma8653fc.txt
create mode 100644 drivers/input/misc/mma8653fc.c

diff --git a/Documentation/ABI/testing/sysfs-bus-i2c-devices-fsl-mma8653fc b/Documentation/ABI/testing/sysfs-bus-i2c-devices-fsl-mma8653fc
new file mode 100644
index 0000000..beebd8d
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-bus-i2c-devices-fsl-mma8653fc
@@ -0,0 +1,36 @@
+What: /sys/bus/i2c/drivers/mma8653fc/*/standby
+Date: March 2015
+Contact: Martin Kepplinger <martin.kepplinger@xxxxxxxxxxxxxxxxxxxxx>
+Description:
+ Write 0 to this in order to turn on the device, and 1 to turn
+ it off. Read to see if it is turned on or off.
+
+
+What: /sys/bus/i2c/drivers/mma8653fc/*/currentmode
+Date: March 2015
+Contact: Martin Kepplinger <martin.kepplinger@xxxxxxxxxxxxxxxxxxxxx>
+Description:
+ Reading this provides the current state of the device, read
+ directly from a register. This can be "standby", "wake" or
+ "sleep".
+
+
+What: /sys/bus/i2c/drivers/mma8653fc/*/position
+Date: March 2015
+Contact: Martin Kepplinger <martin.kepplinger@xxxxxxxxxxxxxxxxxxxxx>
+Description:
+ Read only. Without interrupts enabled gets current position
+ values by reading. Poll "position" with interrupt conditions
+ set, to get notified; see Documentation/.../fsl,mma8653fc.txt
+
+ position file format:
+ "x y z [landscape/portrait status] [front/back status]"
+ landscape/portrait status char:
+ r landscape right
+ d portrait down
+ u portrait up
+ l landscape left
+ front/back status char:
+ f front facing
+ b back facing
+
diff --git a/Documentation/devicetree/bindings/misc/fsl,mma8653fc.txt b/Documentation/devicetree/bindings/misc/fsl,mma8653fc.txt
new file mode 100644
index 0000000..9065550
--- /dev/null
+++ b/Documentation/devicetree/bindings/misc/fsl,mma8653fc.txt
@@ -0,0 +1,95 @@
+Freescale MMA8653FC 3-axis Accelerometer
+
+Required properties:
+- compatible
+ "fsl,mma8653fc"
+- reg
+ I2C address
+
+Optional properties:
+
+- interrupt-parent
+ GPIO interrupt line
+- interrupts
+ GPIO interrupt address
+- int1
+ set to use interrupt line 1 instead of 2
+- int_active_high
+ set interrupt line active high
+- ir_freefall_motion_x
+ activate freefall/motion interrupts on x axis
+- ir_freefall_motion_y
+ activate freefall/motion interrupts on y axis
+- ir_freefall_motion_z
+ activate freefall/motion interrupts on z axis
+- irq_threshold
+ 0 < value < 8000: threshold for motion interrupts in mg
+- ir_landscape_portrait
+ activate landscape/portrait interrupts
+- ir_data_ready:
+ activate data-ready interrupts
+ Interrupt events can be activated in any combination.
+- range
+ 2, 4, or 8: range in g, default: 2
+- auto_wake_sleep
+ auto sleep mode (lower frequency)
+- motion_mode
+ use motion mode instead of freefall mode (trigger if >threshold).
+ per default an interrupt occurs if motion values fall below the
+ value set in "threshold" and therefore can detect free fall on the
+ vertical axis (depending on the position of the device).
+ Setting this values inverts the behaviour and an interrupt occurs
+ above the threshold value, so usually activate horizontal axis in
+ this case.
+
+- x-offset
+ 0 < value < 500: calibration offset in mg
+ this value has an offset of 250 itself:
+ 0 is -250mg, 250 is 0 mg, 500 is 250mg
+- y-offset
+ see x-offset
+- z-offset
+ see x-offset
+
+Example 1:
+for a device laying on flat ground to recognize acceleration over 100mg.
+x-axis is calibrated to +10mg. Adapt interrupt line to your device.
+
+mma8653fc@1d {
+ compatible = "fsl,mma8653fc";
+ interrupt-parent = <&gpio1>;
+ interrupts = <5 0>;
+ reg = <0x1d>;
+
+ range = <2>;
+ motion_mode;
+ ir_freefall_motion_x;
+ ir_freefall_motion_y;
+ irq_threshold = <100>;
+ x-offset = <160>;
+};
+
+Example 2:
+for a device mounted on a wall with y being the vertical axis. This recognizes
+y-acceleration below 800mg, so free fall or changing the orientation of the
+device (y not being the vertical axis and having ~1000mg anymore).
+
+mma8653fc@1d {
+ compatible = "fsl,mma8653fc";
+ interrupt-parent = <&gpio1>;
+ interrupts = <5 0>;
+ reg = <0x1d>;
+
+ range = <2>;
+ ir_freefall_motion_y;
+ irq_threshold = <800>;
+};
+
+Example 3:
+minimal example. lets users read current acceleration values. No polling
+is available.
+
+mma8653fc@1d {
+ compatible = "fsl,mma8653fc";
+ reg = <0x1d>;
+};
diff --git a/MAINTAINERS b/MAINTAINERS
index 0e1abe8..b8c597b 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -4104,6 +4104,12 @@ S: Maintained
F: include/linux/platform_data/video-imxfb.h
F: drivers/video/fbdev/imxfb.c

+FREESCALE MMA8653FC ACCELEROMETER I2C DRIVER
+M: Martin Kepplinger <martin.kepplinger@xxxxxxxxxxxxxxxxxxxxx>
+S: Maintained
+F: drivers/input/misc/mma8653fc.c
+F: Documentation/devicetree/bindings/i2c/mma8653fc.txt
+
FREESCALE QUAD SPI DRIVER
M: Han Xu <han.xu@xxxxxxxxxxxxx>
L: linux-mtd@xxxxxxxxxxxxxxxxxxx
diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig
index 6deb8da..70d9bf5 100644
--- a/drivers/input/misc/Kconfig
+++ b/drivers/input/misc/Kconfig
@@ -208,6 +208,17 @@ config INPUT_MMA8450
To compile this driver as a module, choose M here: the
module will be called mma8450.

+config INPUT_MMA8653FC
+ tristate "MMA8653FC - Freescale's 3-Axis, 10-bit Digital Accelerometer"
+ depends on I2C
+ default n
+ help
+ Say Y here if you want to support Freescale's MMA8653FC Accelerometer
+ through I2C interface.
+
+ To compile this driver as a module, choose M here: the
+ module will be called mma8653fc.
+
config INPUT_MPU3050
tristate "MPU3050 Triaxial gyroscope sensor"
depends on I2C
diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile
index 403a1a5..0bcd878 100644
--- a/drivers/input/misc/Makefile
+++ b/drivers/input/misc/Makefile
@@ -43,6 +43,7 @@ obj-$(CONFIG_INPUT_MAX8925_ONKEY) += max8925_onkey.o
obj-$(CONFIG_INPUT_MAX8997_HAPTIC) += max8997_haptic.o
obj-$(CONFIG_INPUT_MC13783_PWRBUTTON) += mc13783-pwrbutton.o
obj-$(CONFIG_INPUT_MMA8450) += mma8450.o
+obj-$(CONFIG_INPUT_MMA8653FC) += mma8653fc.o
obj-$(CONFIG_INPUT_MPU3050) += mpu3050.o
obj-$(CONFIG_INPUT_PALMAS_PWRBUTTON) += palmas-pwrbutton.o
obj-$(CONFIG_INPUT_PCAP) += pcap_keys.o
diff --git a/drivers/input/misc/mma8653fc.c b/drivers/input/misc/mma8653fc.c
new file mode 100644
index 0000000..7938094
--- /dev/null
+++ b/drivers/input/misc/mma8653fc.c
@@ -0,0 +1,907 @@
+/*
+ * mma8653fc.c - Support for Freescale MMA8653FC 3-axis 10-bit accelerometer
+ *
+ * Copyright (c) 2014 Theobroma Systems Design and Consulting GmbH
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/device.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/i2c.h>
+#include <linux/types.h>
+#include <linux/of_device.h>
+#include <linux/of_irq.h>
+
+#define DRV_NAME "mma8653fc"
+#define MMA8653FC_DEVICE_ID 0x5a
+
+#define MMA8653FC_STATUS 0x00
+
+#define ZYXOW_MASK 0x80
+#define ZYXDR 0x08
+
+#define MMA8653FC_WHO_AM_I 0x0d
+
+#define MMA8653FC_SYSMOD 0x0b
+#define STATE_STANDBY 0x00
+#define STATE_WAKE 0x01
+#define STATE_SLEEP 0x02
+
+#define MMA8450_STATUS_ZXYDR 0x08
+
+/*
+ * 10 bit output data registers
+ * MSB: 7:0 bits 9:2 of data word
+ * LSB: 7:6 bits 1:0 of data word
+ */
+#define MMA8653FC_OUT_X_MSB 0x01
+#define MMA8653FC_OUT_X_LSB 0x02
+#define MMA8653FC_OUT_Y_MSB 0x03
+#define MMA8653FC_OUT_Y_LSB 0x04
+#define MMA8653FC_OUT_Z_MSB 0x05
+#define MMA8653FC_OUT_Z_LSB 0x06
+
+/*
+ * Portrait/Landscape Status
+ */
+#define MMA8653FC_PL_STATUS 0x10
+
+#define NEWLP 0x80
+#define LAPO_HIGH 0x04
+#define LAPO_LOW 0x02
+#define BAFRO 0x01
+
+/*
+ * Portrait/Landscape Configuration
+ */
+#define MMA8653FC_PL_CFG 0x11
+
+#define PL_EN (1 << 6)
+
+/*
+ * Data calibration registers
+ */
+#define MMA8653FC_OFF_X 0x2f
+#define MMA8653FC_OFF_Y 0x30
+#define MMA8653FC_OFF_Z 0x31
+
+/* 0 to 500 for dts, but -250 to 250 in mg */
+#define DEFAULT_OFF 250
+
+/*
+ * bits 1:0 dynamic range
+ * 00 +/- 2g
+ * 01 +/- 4g
+ * 10 +/- 8g
+ *
+ * HPF_Out bit 4 - data high pass or low pass filtered
+ */
+#define MMA8653FC_XYZ_DATA_CFG 0x0e
+
+#define RANGE_MASK 0x03
+#define RANGE2G 0x00
+#define RANGE4G 0x01
+#define RANGE8G 0x02
+/* values for calculation */
+#define SHIFT_2G 8
+#define INCR_2G 128
+#define SHIFT_4G 7
+#define INCR_4G 64
+#define SHIFT_8G 6
+#define INCR_8G 32
+#define DYN_RANGE_2G 2
+#define DYN_RANGE_4G 4
+#define DYN_RANGE_8G 8
+
+/*
+ * System Control Reg 1
+ */
+#define MMA8653FC_CTRL_REG1 0x2a
+
+#define ACTIVE_BIT (1 << 0)
+#define ODR_MASK 0x38
+#define ODR_DEFAULT 0x20 /* 50 Hz */
+#define ASLP_RATE_MASK 0xc0
+#define ASLP_RATE_DEFAULT 0x80 /* 6.25 Hz */
+
+/*
+ * Sys Control Reg 2
+ *
+ * auto-sleep enable, software reset
+ */
+#define MMA8653FC_CTRL_REG2 0x2b
+
+#define SLPE (1 << 2)
+#define SELFTEST (1 << 7)
+#define SOFT_RESET (1 << 6)
+
+/*
+ * Interrupt Source
+ */
+#define MMA8653FC_INT_SOURCE 0x0c
+
+#define SRC_ASLP (1 << 7)
+#define SRC_LNDPRT (1 << 4)
+#define SRC_FF_MT (1 << 2)
+#define SRC_DRDY (1 << 0)
+
+/*
+ * Interrupt Control Register
+ *
+ * default: active low
+ * default push-pull, not open-drain
+ */
+#define MMA8653FC_CTRL_REG3 0x2c
+
+#define WAKE_LNDPRT (1 << 5)
+#define WAKE_FF_MT (1 << 3)
+#define IPOL (1 << 1)
+#define PP_OD (1 << 0)
+
+/*
+ * Interrupt Enable Register
+ */
+#define MMA8653FC_CTRL_REG4 0x2d
+
+#define INT_EN_ASLP (1 << 7)
+#define INT_EN_LNDPRT (1 << 4)
+#define INT_EN_FF_MT (1 << 2)
+#define INT_EN_DRDY (1 << 0)
+
+/*
+ * Interrupt Configuration Register
+ * bit value 0 ... INT2 (default)
+ * bit value 1 ... INT1
+ */
+#define MMA8653FC_CTRL_REG5 0x2e
+
+#define INT_CFG_ASLP (1 << 7)
+#define INT_CFG_LNDPRT (1 << 4)
+#define INT_CFG_FF_MT (1 << 2)
+#define INT_CFG_DRDY (1 << 0)
+
+/*
+ * Freefall / Motion Configuration Register
+ *
+ * Event Latch enable/disable, motion or freefall mode
+ * and event flag enable per axis
+ */
+#define MMA8653FC_FF_MT_CFG 0x15
+
+#define FF_MT_CFG_ELE (1 << 7)
+#define FF_MT_CFG_OAE (1 << 6)
+#define FF_MT_CFG_ZEFE (1 << 5)
+#define FF_MT_CFG_YEFE (1 << 4)
+#define FF_MT_CFG_XEFE (1 << 3)
+
+/*
+ * Freefall / Motion Source Register
+ */
+#define MMA8653FC_FF_MT_SRC 0x16
+
+/*
+ * Freefall / Motion Threshold Register
+ *
+ * define motion threshold
+ * 0.063 g/LSB, 127 counts(0x7f) (7 bit from LSB)
+ * range: 0.063g - 8g
+ */
+#define MMA8653FC_FF_MT_THS 0x17
+
+struct axis_triple {
+ s16 x;
+ s16 y;
+ s16 z;
+};
+
+struct mma8653fc_pdata {
+ s8 x_axis_offset;
+ s8 y_axis_offset;
+ s8 z_axis_offset;
+ bool auto_wake_sleep;
+ u32 range;
+ bool int1;
+ bool int_active_high;
+ bool motion_mode;
+ u8 freefall_motion_thr;
+ bool int_src_data_ready;
+ bool int_src_ff_mt_x;
+ bool int_src_ff_mt_y;
+ bool int_src_ff_mt_z;
+ bool int_src_lndprt;
+ bool int_src_aslp;
+};
+
+struct mma8653fc {
+ struct i2c_client *client;
+ struct mutex mutex;
+ struct mma8653fc_pdata pdata;
+ struct axis_triple saved;
+ char orientation;
+ char bafro;
+ bool standby;
+ int irq;
+ unsigned int_mask;
+ u8 (*read)(struct mma8653fc *, unsigned char);
+ int (*write)(struct mma8653fc *, unsigned char, unsigned char);
+};
+
+/* defaults */
+static const struct mma8653fc_pdata mma8653fc_default_init = {
+ .range = 2,
+ .x_axis_offset = DEFAULT_OFF,
+ .y_axis_offset = DEFAULT_OFF,
+ .z_axis_offset = DEFAULT_OFF,
+ .auto_wake_sleep = false,
+ .int1 = false,
+ .int_active_high = false,
+ .motion_mode = false,
+ .freefall_motion_thr = 5,
+ .int_src_data_ready = false,
+ .int_src_ff_mt_x = false,
+ .int_src_ff_mt_y = false,
+ .int_src_ff_mt_z = false,
+ .int_src_lndprt = false,
+ .int_src_aslp = false,
+};
+
+static void mma8653fc_get_triple(struct mma8653fc *mma)
+{
+ u8 buf[6];
+ u8 status;
+
+ buf[0] = 0;
+
+ status = mma->read(mma, MMA8653FC_STATUS);
+ if (status & ZYXOW_MASK)
+ dev_dbg(&mma->client->dev, "previous read not completed\n");
+
+ buf[0] = mma->read(mma, MMA8653FC_OUT_X_MSB);
+ buf[1] = mma->read(mma, MMA8653FC_OUT_X_LSB);
+ buf[2] = mma->read(mma, MMA8653FC_OUT_Y_MSB);
+ buf[3] = mma->read(mma, MMA8653FC_OUT_Y_LSB);
+ buf[4] = mma->read(mma, MMA8653FC_OUT_Z_MSB);
+ buf[5] = mma->read(mma, MMA8653FC_OUT_Z_LSB);
+
+ mutex_lock(&mma->mutex);
+ /* move from registers to s16 */
+ mma->saved.x = (buf[1] | (buf[0] << 8)) >> 6;
+ mma->saved.y = (buf[3] | (buf[2] << 8)) >> 6;
+ mma->saved.z = (buf[5] | (buf[4] << 8)) >> 6;
+ mma->saved.x = sign_extend32(mma->saved.x, 9);
+ mma->saved.y = sign_extend32(mma->saved.y, 9);
+ mma->saved.z = sign_extend32(mma->saved.z, 9);
+
+ /* calc g, see data sheet and application note */
+ switch (mma->pdata.range) {
+ case DYN_RANGE_2G:
+ mma->saved.x = le16_to_cpu((1000 * mma->saved.x +
+ INCR_2G) >> SHIFT_2G);
+ mma->saved.y = le16_to_cpu((1000 * mma->saved.y +
+ INCR_2G) >> SHIFT_2G);
+ mma->saved.z = le16_to_cpu((1000 * mma->saved.z +
+ INCR_2G) >> SHIFT_2G);
+ break;
+ case DYN_RANGE_4G:
+ mma->saved.x = le16_to_cpu((1000 * mma->saved.x +
+ INCR_4G) >> SHIFT_4G);
+ mma->saved.y = le16_to_cpu((1000 * mma->saved.y +
+ INCR_4G) >> SHIFT_4G);
+ mma->saved.z = le16_to_cpu((1000 * mma->saved.z +
+ INCR_4G) >> SHIFT_4G);
+ break;
+ case DYN_RANGE_8G:
+ mma->saved.x = le16_to_cpu((1000 * mma->saved.x +
+ INCR_8G) >> SHIFT_8G);
+ mma->saved.y = le16_to_cpu((1000 * mma->saved.y +
+ INCR_8G) >> SHIFT_8G);
+ mma->saved.z = le16_to_cpu((1000 * mma->saved.z +
+ INCR_8G) >> SHIFT_8G);
+ break;
+ default:
+ dev_err(&mma->client->dev, "internal data corrupt\n");
+ }
+ mutex_unlock(&mma->mutex);
+}
+
+static void mma8653fc_get_orientation(struct mma8653fc *mma, u8 byte)
+{
+ if ((byte & LAPO_HIGH) && !(LAPO_LOW))
+ mma->orientation = 'r'; /* landscape right */
+ if (!(byte & LAPO_HIGH) && (byte & LAPO_LOW))
+ mma->orientation = 'd'; /* portrait down */
+ if (!(byte & LAPO_HIGH) && !(byte & LAPO_LOW))
+ mma->orientation = 'u'; /* portrait up */
+ if ((byte & LAPO_HIGH) && (byte & LAPO_LOW))
+ mma->orientation = 'l'; /* landscape left */
+
+ if (byte & BAFRO)
+ mma->bafro = 'b'; /* back facing */
+ else
+ mma->bafro = 'f'; /* front facing */
+}
+
+static irqreturn_t mma8653fc_irq(int irq, void *handle)
+{
+ struct mma8653fc *mma = handle;
+ u8 int_src;
+ u8 byte;
+
+ int_src = mma->read(mma, MMA8653FC_INT_SOURCE);
+ if (int_src & SRC_DRDY)
+ /* data ready handle */
+ if (int_src & SRC_FF_MT) {
+ /* freefall/motion change handle */
+ dev_dbg(&mma->client->dev,
+ "freefall or motion change\n");
+ byte = mma->read(mma, MMA8653FC_FF_MT_SRC);
+ }
+ if (int_src & SRC_LNDPRT) {
+ /* landscape/portrait change handle */
+ dev_dbg(&mma->client->dev,
+ "landscape / portrait change\n");
+ byte = mma->read(mma, MMA8653FC_PL_STATUS);
+ mma8653fc_get_orientation(mma, byte);
+ }
+ if (int_src & SRC_ASLP)
+ /* autosleep change handle */
+ mma8653fc_get_triple(mma);
+
+ sysfs_notify(&mma->client->dev.kobj, NULL, "position");
+
+ return IRQ_HANDLED;
+}
+
+static void __mma8653fc_enable(struct mma8653fc *mma)
+{
+ u8 byte;
+
+ byte = mma->read(mma, MMA8653FC_CTRL_REG1);
+ mma->write(mma, MMA8653FC_CTRL_REG1, byte | ACTIVE_BIT);
+}
+
+static void __mma8653fc_disable(struct mma8653fc *mma)
+{
+ u8 byte;
+
+ byte = mma->read(mma, MMA8653FC_CTRL_REG1);
+ mma->write(mma, MMA8653FC_CTRL_REG1, byte & ~ACTIVE_BIT);
+}
+
+static ssize_t mma8653fc_standby_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct mma8653fc *mma = i2c_get_clientdata(client);
+
+ return scnprintf(buf, PAGE_SIZE, "%u\n", mma->standby);
+}
+
+static ssize_t mma8653fc_standby_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct mma8653fc *mma = i2c_get_clientdata(client);
+ unsigned int val;
+ int error;
+
+ error = kstrtouint(buf, 10, &val);
+ if (error)
+ return error;
+
+ mutex_lock(&mma->mutex);
+
+ if (val) {
+ if (!mma->standby)
+ __mma8653fc_disable(mma);
+ } else {
+ if (mma->standby)
+ __mma8653fc_enable(mma);
+ }
+
+ mma->standby = !!val;
+
+ mutex_unlock(&mma->mutex);
+
+ return count;
+}
+
+static DEVICE_ATTR(standby, 0664, mma8653fc_standby_show,
+ mma8653fc_standby_store);
+
+static ssize_t mma8653fc_currentmode_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct mma8653fc *mma = i2c_get_clientdata(client);
+ ssize_t count;
+ u8 byte;
+
+ byte = mma->read(mma, MMA8653FC_SYSMOD);
+ if (byte < 0)
+ return byte;
+
+ switch (byte) {
+ case STATE_STANDBY:
+ count = scnprintf(buf, PAGE_SIZE, "standby\n");
+ break;
+ case STATE_WAKE:
+ count = scnprintf(buf, PAGE_SIZE, "wake\n");
+ break;
+ case STATE_SLEEP:
+ count = scnprintf(buf, PAGE_SIZE, "sleep\n");
+ break;
+ default:
+ count = scnprintf(buf, PAGE_SIZE, "unknown\n");
+ }
+ return count;
+}
+static DEVICE_ATTR(currentmode, S_IRUGO, mma8653fc_currentmode_show, NULL);
+
+static ssize_t mma8653fc_position_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct mma8653fc *mma = i2c_get_clientdata(client);
+ ssize_t count;
+ u8 byte;
+
+ if (!mma->irq) {
+ byte = mma->read(mma, MMA8653FC_PL_STATUS);
+ if (byte & NEWLP)
+ mma8653fc_get_orientation(mma, byte);
+
+ byte = mma->read(mma, MMA8653FC_STATUS);
+ if (byte & ZYXDR)
+ mma8653fc_get_triple(mma);
+
+ msleep(20);
+ dev_dbg(&client->dev, "data polled\n");
+ }
+ mutex_lock(&mma->mutex);
+ count = scnprintf(buf, PAGE_SIZE, "%d %d %d %c %c\n",
+ mma->saved.x, mma->saved.y, mma->saved.z,
+ mma->orientation, mma->bafro);
+ mutex_unlock(&mma->mutex);
+
+ return count;
+}
+static DEVICE_ATTR(position, S_IRUGO, mma8653fc_position_show, NULL);
+
+static struct attribute *mma8653fc_attributes[] = {
+ &dev_attr_position.attr,
+ &dev_attr_standby.attr,
+ &dev_attr_currentmode.attr,
+ NULL,
+};
+
+static const struct attribute_group mma8653fc_attr_group = {
+ .attrs = mma8653fc_attributes,
+};
+
+static u8 mma8653fc_read(struct mma8653fc *mma, unsigned char reg)
+{
+ u8 val;
+
+ val = i2c_smbus_read_byte_data(mma->client, reg);
+ if (val < 0) {
+ dev_err(&mma->client->dev,
+ "failed to read %x register\n", reg);
+ }
+ return val;
+}
+
+static int mma8653fc_write(struct mma8653fc *mma, unsigned char reg,
+ unsigned char val)
+{
+ int ret;
+
+ ret = i2c_smbus_write_byte_data(mma->client, reg, val);
+ if (ret) {
+ dev_err(&mma->client->dev,
+ "failed to write %x register\n", reg);
+ }
+ return ret;
+}
+
+static const struct of_device_id mma8653fc_dt_ids[] = {
+ { .compatible = "fsl,mma8653fc", },
+ { }
+};
+MODULE_DEVICE_TABLE(of, mma8653fc_dt_ids);
+
+static const struct mma8653fc_pdata *mma8653fc_probe_dt(struct device *dev)
+{
+ struct mma8653fc_pdata *pdata;
+ struct device_node *node = dev->of_node;
+ const struct of_device_id *match;
+ u32 testu32;
+ s32 tests32;
+
+ if (!node) {
+ dev_err(dev, "no associated DT data\n");
+ return ERR_PTR(-EINVAL);
+ }
+
+ match = of_match_device(mma8653fc_dt_ids, dev);
+ if (!match) {
+ dev_err(dev, "unknown device model\n");
+ return ERR_PTR(-EINVAL);
+ }
+
+ pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL);
+ if (!pdata)
+ return ERR_PTR(-ENOMEM);
+
+ *pdata = mma8653fc_default_init;
+
+ /* overwrite from dts */
+ testu32 = pdata->x_axis_offset;
+ tests32 = 0;
+ of_property_read_u32(node, "x-offset", &testu32);
+ tests32 = testu32 - DEFAULT_OFF;
+ if ((tests32) && (tests32 <= DEFAULT_OFF) &&
+ (tests32 >= -DEFAULT_OFF)) {
+ dev_info(dev, "use %dmg offset on X axis\n", tests32);
+ /* calc register value, resolution: 1.96mg */
+ pdata->x_axis_offset = (s8) (tests32 / 2);
+ }
+ testu32 = pdata->y_axis_offset;
+ tests32 = 0;
+ of_property_read_u32(node, "y-offset", &testu32);
+ tests32 = testu32 - DEFAULT_OFF;
+ if ((tests32) && (tests32 <= DEFAULT_OFF) &&
+ (tests32 >= -DEFAULT_OFF)) {
+ dev_info(dev, "use %dmg offset on Y axis\n", tests32);
+ pdata->y_axis_offset = (s8) (tests32 / 2);
+ }
+ testu32 = pdata->z_axis_offset;
+ tests32 = 0;
+ of_property_read_u32(node, "z-offset", &testu32);
+ tests32 = testu32 - DEFAULT_OFF;
+ if ((tests32) && (tests32 <= DEFAULT_OFF) &&
+ (tests32 >= -DEFAULT_OFF)) {
+ dev_info(dev, "use %dmg offset on Z axis\n", tests32);
+ pdata->z_axis_offset = (s8) (tests32 / 2);
+ }
+
+ testu32 = 0;
+ of_property_read_u32(node, "range", &testu32);
+ if ((testu32) && (testu32 != 2) && (testu32 != 4) && (testu32 != 8)) {
+ dev_warn(dev, "wrong value for full scale range in dtb\n");
+ } else {
+ if (testu32)
+ pdata->range = testu32;
+ }
+
+ if (of_property_read_bool(node, "auto_wake_sleep"))
+ pdata->auto_wake_sleep = true;
+
+ if (of_property_read_bool(node, "int1"))
+ pdata->int1 = true;
+
+ if (of_property_read_bool(node, "int_active_high"))
+ pdata->int_active_high = true;
+
+ if (of_property_read_bool(node, "motion_mode"))
+ pdata->motion_mode = true;
+
+ if (of_property_read_bool(node, "ir_data_ready"))
+ pdata->int_src_data_ready = true;
+
+ if (of_property_read_bool(node, "ir_freefall_motion_x"))
+ pdata->int_src_ff_mt_x = true;
+
+ if (of_property_read_bool(node, "ir_freefall_motion_y"))
+ pdata->int_src_ff_mt_y = true;
+
+ if (of_property_read_bool(node, "ir_freefall_motion_z"))
+ pdata->int_src_ff_mt_z = true;
+
+ if (of_property_read_bool(node, "ir_auto_wake"))
+ pdata->int_src_aslp = true;
+
+ if (of_property_read_bool(node, "ir_landscape_portrait"))
+ pdata->int_src_lndprt = true;
+
+ testu32 = 0;
+ of_property_read_u32(node, "irq_threshold", &testu32);
+ /* always uses maximum range +/- 8000g, resolution 63mg */
+ if ((testu32 <= 8000) && (testu32 > 0)) {
+ dev_dbg(dev, "use freefall / motion threshold %dmg\n",
+ testu32);
+ /* calculate register value from mg */
+ pdata->freefall_motion_thr = (testu32 / 63) + 1;
+ }
+
+ return pdata;
+}
+static int mma8653fc_i2c_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct mma8653fc *mma;
+ const struct mma8653fc_pdata *pdata;
+ int err;
+ u8 byte;
+
+ err = i2c_check_functionality(client->adapter,
+ I2C_FUNC_SMBUS_BYTE_DATA);
+ if (!err) {
+ dev_err(&client->dev, "SMBUS Byte Data not Supported\n");
+ return -EIO;
+ }
+
+ mma = kzalloc(sizeof(*mma), GFP_KERNEL);
+ if (!mma) {
+ err = -ENOMEM;
+ goto err_out;
+ }
+
+ pdata = dev_get_platdata(&client->dev);
+ if (!pdata) {
+ pdata = mma8653fc_probe_dt(&client->dev);
+ if (IS_ERR(pdata)) {
+ err = PTR_ERR(pdata);
+ pr_err("pdata from DT failed\n");
+ goto err_free_mem;
+ }
+ }
+ mma->pdata = *pdata;
+ pdata = &mma->pdata;
+ mma->client = client;
+ mma->read = &mma8653fc_read;
+ mma->write = &mma8653fc_write;
+
+ mutex_init(&mma->mutex);
+
+ i2c_set_clientdata(client, mma);
+
+ err = sysfs_create_group(&client->dev.kobj, &mma8653fc_attr_group);
+ if (err)
+ goto err_free_mem;
+
+ mma->irq = irq_of_parse_and_map(client->dev.of_node, 0);
+ if (!mma->irq) {
+ dev_err(&client->dev, "Unable to parse or map IRQ\n");
+ goto no_irq;
+ }
+
+ err = irq_set_irq_type(mma->irq, IRQ_TYPE_EDGE_FALLING);
+ if (err) {
+ dev_err(&client->dev, "set_irq_type failed\n");
+ goto err_free_mem;
+ }
+
+ err = request_threaded_irq(mma->irq, NULL, mma8653fc_irq, IRQF_ONESHOT,
+ dev_name(&mma->client->dev), mma);
+ if (err) {
+ dev_err(&client->dev, "irq %d busy?\n", mma->irq);
+ goto err_free_mem;
+ }
+
+ if (mma->write(mma, MMA8653FC_CTRL_REG2, SOFT_RESET))
+ goto err_free_irq;
+
+ __mma8653fc_disable(mma);
+ mma->standby = true;
+
+ /* enable desired interrupts */
+ mma->orientation = '\0';
+ mma->bafro = '\0';
+ byte = 0;
+ if (pdata->int_src_data_ready) {
+ byte |= INT_EN_DRDY;
+ dev_dbg(&client->dev, "DATA READY interrupt source enabled\n");
+ }
+ if (pdata->int_src_ff_mt_x || pdata->int_src_ff_mt_y ||
+ pdata->int_src_ff_mt_z) {
+ byte |= INT_EN_FF_MT;
+ dev_dbg(&client->dev, "FF MT interrupt source enabled\n");
+ }
+ if (pdata->int_src_lndprt) {
+ if (mma->write(mma, MMA8653FC_PL_CFG, PL_EN))
+ goto err_free_irq;
+ byte |= INT_EN_LNDPRT;
+ dev_dbg(&client->dev, "LNDPRT interrupt source enabled\n");
+ }
+ if (pdata->int_src_aslp) {
+ byte |= INT_EN_ASLP;
+ dev_dbg(&client->dev, "ASLP interrupt source enabled\n");
+ }
+ if (mma->write(mma, MMA8653FC_CTRL_REG4, byte))
+ goto err_free_irq;
+
+ /* force everything to line 1 */
+ if (pdata->int1) {
+ if (mma->write(mma, MMA8653FC_CTRL_REG5,
+ (INT_CFG_ASLP | INT_CFG_LNDPRT |
+ INT_CFG_FF_MT | INT_CFG_DRDY)))
+ goto err_free_irq;
+ dev_dbg(&client->dev, "using interrupt line 1\n");
+ }
+
+ /* force active high */
+ if (pdata->int_active_high) {
+ byte = mma->read(mma, MMA8653FC_CTRL_REG3);
+ if (byte < 0)
+ goto err_free_irq;
+ byte |= IPOL;
+ if (mma->write(mma, MMA8653FC_CTRL_REG3, byte))
+ goto err_free_irq;
+ dev_dbg(&client->dev, "using active high on interrupt line\n");
+ }
+no_irq:
+ /* range mode */
+ byte = mma->read(mma, MMA8653FC_XYZ_DATA_CFG);
+ byte &= ~RANGE_MASK;
+ switch (pdata->range) {
+ case DYN_RANGE_2G:
+ byte |= RANGE2G;
+ dev_dbg(&client->dev, "use 2g range\n");
+ break;
+ case DYN_RANGE_4G:
+ byte |= RANGE4G;
+ dev_dbg(&client->dev, "use 4g range\n");
+ break;
+ case DYN_RANGE_8G:
+ byte |= RANGE8G;
+ dev_dbg(&client->dev, "use 8g range\n");
+ break;
+ default:
+ dev_err(&client->dev, "wrong range mode value\n");
+ goto err_free_irq;
+ }
+ if (mma->write(mma, MMA8653FC_XYZ_DATA_CFG, byte))
+ goto err_free_irq;
+
+ /* data calibration offsets */
+ if (pdata->x_axis_offset) {
+ if (mma->write(mma, MMA8653FC_OFF_X, pdata->x_axis_offset))
+ goto err_free_irq;
+ }
+ if (pdata->y_axis_offset) {
+ if (mma->write(mma, MMA8653FC_OFF_Y, pdata->y_axis_offset))
+ goto err_free_irq;
+ }
+ if (pdata->z_axis_offset) {
+ if (mma->write(mma, MMA8653FC_OFF_Z, pdata->z_axis_offset))
+ goto err_free_irq;
+ }
+
+ /* if autosleep, wake on both landscape and motion changes */
+ if (pdata->auto_wake_sleep) {
+ byte = 0;
+ byte |= WAKE_LNDPRT;
+ byte |= WAKE_FF_MT;
+ if (mma->write(mma, MMA8653FC_CTRL_REG3, byte))
+ goto err_free_irq;
+ if (mma->write(mma, MMA8653FC_CTRL_REG2, SLPE))
+ goto err_free_irq;
+ dev_dbg(&client->dev, "auto sleep enabled\n");
+ }
+
+ /* data rates */
+ byte = 0;
+ byte = mma->read(mma, MMA8653FC_CTRL_REG1);
+ if (byte < 0)
+ goto err_free_irq;
+ byte &= ~ODR_MASK;
+ byte |= ODR_DEFAULT;
+ byte &= ~ASLP_RATE_MASK;
+ byte |= ASLP_RATE_DEFAULT;
+ if (mma->write(mma, MMA8653FC_CTRL_REG1, byte))
+ goto err_free_irq;
+
+ /* freefall / motion config */
+ byte = 0;
+ if (pdata->motion_mode) {
+ byte |= FF_MT_CFG_OAE;
+ dev_dbg(&client->dev, "detect motion instead of freefall\n");
+ }
+ byte |= FF_MT_CFG_ELE;
+ if (pdata->int_src_ff_mt_x)
+ byte |= FF_MT_CFG_XEFE;
+ if (pdata->int_src_ff_mt_y)
+ byte |= FF_MT_CFG_YEFE;
+ if (pdata->int_src_ff_mt_z)
+ byte |= FF_MT_CFG_ZEFE;
+ if (mma->write(mma, MMA8653FC_FF_MT_CFG, byte))
+ goto err_free_irq;
+
+ if (pdata->freefall_motion_thr) {
+ if (mma->write(mma, MMA8653FC_FF_MT_THS,
+ pdata->freefall_motion_thr))
+ goto err_free_irq;
+ /* calculate back to mg */
+ dev_dbg(&client->dev, "threshold set to %dmg\n",
+ (63 * pdata->freefall_motion_thr) - 1);
+ }
+
+ byte = mma->read(mma, MMA8653FC_WHO_AM_I);
+ if (byte != MMA8653FC_DEVICE_ID) {
+ dev_err(&client->dev, "wrong device for driver\n");
+ goto err_free_irq;
+ }
+ dev_info(&client->dev,
+ "accelerometer driver loaded. device id %x\n", byte);
+
+ return 0;
+
+ err_free_irq:
+ if (mma->irq)
+ free_irq(mma->irq, mma);
+ err_free_mem:
+ kfree(mma);
+ err_out:
+ return err;
+}
+
+static int mma8653fc_remove(struct i2c_client *client)
+{
+ struct mma8653fc *mma = i2c_get_clientdata(client);
+
+ free_irq(mma->irq, mma);
+ dev_dbg(&client->dev, "unregistered accelerometer\n");
+ kfree(mma);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int mma8653fc_suspend(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct mma8653fc *mma = i2c_get_clientdata(client);
+
+ __mma8653fc_disable(mma);
+
+ return 0;
+}
+
+static int mma8653fc_resume(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct mma8653fc *mma = i2c_get_clientdata(client);
+
+ __mma8653fc_enable(mma);
+
+ return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(mma8653fc_pm_ops, mma8653fc_suspend, mma8653fc_resume);
+#define MMA8653FC_PM_OPS (&mma8653fc_pm_ops)
+#else
+#define MMA8653FC_PM_OPS NULL
+#endif
+
+static const struct i2c_device_id mma8653fc_id[] = {
+ { DRV_NAME, 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, mma8653fc_id);
+
+static struct i2c_driver mma8653fc_driver = {
+ .driver = {
+ .name = DRV_NAME,
+ .owner = THIS_MODULE,
+ .of_match_table = mma8653fc_dt_ids,
+ .pm = MMA8653FC_PM_OPS,
+ },
+ .probe = mma8653fc_i2c_probe,
+ .remove = mma8653fc_remove,
+ .id_table = mma8653fc_id,
+};
+
+module_i2c_driver(mma8653fc_driver);
+
+MODULE_AUTHOR("Martin Kepplinger <martin.kepplinger@xxxxxxxxxxxxxxxxxxxxx");
+MODULE_DESCRIPTION("Freescale's MMA8653FC Three-Axis Accelerometer I2C Driver");
+MODULE_LICENSE("GPL");
--
2.1.4

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/