summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRui Miguel Silva <rui.silva@linaro.org>2017-09-20 22:16:30 +0100
committerBryan O'Donoghue <bryan.odonoghue@linaro.org>2019-05-01 11:51:17 +0100
commit30a97a3d54d2df74444d4da6c52589a688cdccb7 (patch)
tree6f40505581260d473648e77922c5b273135ff45a
parent48bf524926908664b220a41d1f7007c795200923 (diff)
iio: accel: fxos8700: add core driver for fxos8700
The NXP FXOS8700 is a accelerometer/magnetometer combo IC, this add support for the core functionalities using iio subsystem. It supports enable/disable each function (but not both, it is not supported by the chip), and reading axis values from the sysfs along with the respective scale. Signed-off-by: Rui Miguel Silva <rui.silva@linaro.org>
-rw-r--r--drivers/iio/accel/Kconfig10
-rw-r--r--drivers/iio/accel/Makefile1
-rw-r--r--drivers/iio/accel/fxos8700.h181
-rw-r--r--drivers/iio/accel/fxos8700_core.c560
4 files changed, 752 insertions, 0 deletions
diff --git a/drivers/iio/accel/Kconfig b/drivers/iio/accel/Kconfig
index 15de262015df..5648beb44250 100644
--- a/drivers/iio/accel/Kconfig
+++ b/drivers/iio/accel/Kconfig
@@ -134,6 +134,16 @@ config DMARD10
Choosing M will build the driver as a module. If so, the module
will be called dmard10.
+config FXOS8700_ACCEL_MAGN
+ tristate "NXP FXOS8700 Accelerometer / Magnetometer Driver"
+ select IIO_BUFFER
+ select REGMAP
+ help
+ Say yes here to build support for the FXOS8700 accelerometer /
+ magnetometer.
+
+ This is a combo module with both accelerometer and magnetometer.
+
config HID_SENSOR_ACCEL_3D
depends on HID_SENSOR_HUB
select IIO_BUFFER
diff --git a/drivers/iio/accel/Makefile b/drivers/iio/accel/Makefile
index 703e7c21f547..2ec7d765fa22 100644
--- a/drivers/iio/accel/Makefile
+++ b/drivers/iio/accel/Makefile
@@ -17,6 +17,7 @@ obj-$(CONFIG_DA311) += da311.o
obj-$(CONFIG_DMARD06) += dmard06.o
obj-$(CONFIG_DMARD09) += dmard09.o
obj-$(CONFIG_DMARD10) += dmard10.o
+obj-$(CONFIG_FXOS8700_ACCEL_MAGN) += fxos8700_core.o
obj-$(CONFIG_HID_SENSOR_ACCEL_3D) += hid-sensor-accel-3d.o
obj-$(CONFIG_KXCJK1013) += kxcjk-1013.o
obj-$(CONFIG_KXSD9) += kxsd9.o
diff --git a/drivers/iio/accel/fxos8700.h b/drivers/iio/accel/fxos8700.h
new file mode 100644
index 000000000000..2f008ae8c036
--- /dev/null
+++ b/drivers/iio/accel/fxos8700.h
@@ -0,0 +1,181 @@
+/*
+ * Driver for NXP FXOS8700 accelerometer/magnetometer header
+ *
+ * Copyright (C) 2017 Linaro Ltd.
+ *
+ */
+
+#ifndef FXOS8700_H_
+#define FXOS8700_H_
+
+#define FXOS8700_REG_STATUS 0x00
+#define FXOS8700_REG_OUT_X_MSB 0X01
+#define FXOS8700_REG_OUT_X_LSB 0x02
+#define FXOS8700_REG_OUT_Y_MSB 0x03
+#define FXOS8700_REG_OUT_Y_LSB 0x04
+#define FXOS8700_REG_OUT_Z_MSB 0x05
+#define FXOS8700_REG_OUT_Z_LSB 0x06
+#define FXOS8700_REG_F_SETUP 0x09
+#define FXOS8700_REG_TRIG_CFG 0x0a
+#define FXOS8700_REG_SYSMOD 0x0B
+#define FXOS8700_REG_INT_SOURCE 0x0c
+#define FXOS8700_REG_WHO_AM_I 0x0d
+#define FXOS8700_REG_XYZ_DATA_CFG 0x0e
+#define FXOS8700_REG_HP_FILTER_CUTOFF 0x0f
+#define FXOS8700_REG_PL_STATUS 0x10
+#define FXOS8700_REG_PL_CFG 0x11
+#define FXOS8700_REG_PL_COUNT 0x12
+#define FXOS8700_REG_PL_BF_ZCOMP 0x13
+#define FXOS8700_REG_PL_P_L_THS_REG 0x14
+#define FXOS8700_REG_FFMT_CFG 0x15
+#define FXOS8700_REG_FFMT_SRC 0x16
+#define FXOS8700_REG_FFMT_THS 0x17
+#define FXOS8700_REG_FFMT_COUNT 0x18
+#define FXOS8700_REG_TRANSIDENT1_CFG 0x1d
+#define FXOS8700_REG_TRANSIDENT1_SRC 0x1e
+#define FXOS8700_REG_TRANSIDENT1_THS 0x1f
+#define FXOS8700_REG_TRANSIDENT1_COUNT 0x20
+#define FXOS8700_REG_PULSE_CFG 0x21
+#define FXOS8700_REG_PULSE_SRC 0x22
+#define FXOS8700_REG_PULSE_THSX 0x23
+#define FXOS8700_REG_PULSE_THSY 0x24
+#define FXOS8700_REG_PULSE_THSZ 0x25
+#define FXOS8700_REG_PULSE_TMLT 0x26
+#define FXOS8700_REG_PULSE_LTCY 0x27
+#define FXOS8700_REG_PULSE_WIND 0x28
+#define FXOS8700_REG_ATSLP_COUNT 0x29
+#define FXOS8700_REG_CTRL_REG1 0x2a
+#define FXOS8700_REG_CTRL_REG2 0x2b
+#define FXOS8700_REG_CTRL_REG3 0x2c
+#define FXOS8700_REG_CTRL_REG4 0x2d
+#define FXOS8700_REG_CTRL_REG5 0x2e
+#define FXOS8700_REG_OFF_X 0x2f
+#define FXOS8700_REG_OFF_Y 0x30
+#define FXOS8700_REG_OFF_Z 0x31
+#define FXOS8700_REG_M_DR_STATUS 0x32
+#define FXOS8700_REG_M_OUT_X_MSB 0x33
+#define FXOS8700_REG_M_OUT_X_LSB 0x34
+#define FXOS8700_REG_M_OUT_Y_MSB 0x35
+#define FXOS8700_REG_M_OUT_Y_LSB 0x36
+#define FXOS8700_REG_M_OUT_Z_MSB 0x37
+#define FXOS8700_REG_M_OUT_Z_LSB 0x38
+#define FXOS8700_REG_CMP_X_MSB 0x39
+#define FXOS8700_REG_CMP_X_LSB 0x3a
+#define FXOS8700_REG_CMP_Y_MSB 0x3b
+#define FXOS8700_REG_CMP_Y_LSB 0x3c
+#define FXOS8700_REG_CMP_Z_MSB 0x3d
+#define FXOS8700_REG_CMP_Z_LSB 0x3e
+#define FXOS8700_REG_M_OFF_X_MSB 0x3f
+#define FXOS8700_REG_M_OFF_X_LSB 0x40
+#define FXOS8700_REG_M_OFF_Y_MSB 0x41
+#define FXOS8700_REG_M_OFF_Y_LSB 0x42
+#define FXOS8700_REG_M_OFF_Z_MSB 0x43
+#define FXOS8700_REG_M_OFF_Z_LSB 0x44
+#define FXOS8700_REG_MAX_X_MSB 0x45
+#define FXOS8700_REG_MAX_X_LSB 0x46
+#define FXOS8700_REG_MAX_Y_MSB 0x47
+#define FXOS8700_REG_MAX_Y_LSB 0x48
+#define FXOS8700_REG_MAX_Z_MSB 0x49
+#define FXOS8700_REG_MAX_Z_LSB 0x4a
+#define FXOS8700_REG_MIN_X_MSB 0x4b
+#define FXOS8700_REG_MIN_X_LSB 0x4c
+#define FXOS8700_REG_MIN_Y_MSB 0x4d
+#define FXOS8700_REG_MIN_Y_LSB 0x4e
+#define FXOS8700_REG_MIN_Z_MSB 0x4f
+#define FXOS8700_REG_MIN_Z_LSB 0x50
+#define FXOS8700_REG_M_TEMP 0x51
+#define FXOS8700_REG_MAG_THS_CFG 0x52
+#define FXOS8700_REG_MAG_THS_SRC 0x53
+#define FXOS8700_REG_MAG_THS_THS_X1 0x54
+#define FXOS8700_REG_MAG_THS_THS_X0 0x55
+#define FXOS8700_REG_MAG_THS_THS_Y1 0x56
+#define FXOS8700_REG_MAG_THS_THS_Y0 0x57
+#define FXOS8700_REG_MAG_THS_THS_Z1 0x58
+#define FXOS8700_REG_MAG_THS_THS_Z0 0x59
+#define FXOS8700_REG_MAG_THS_CUNT 0x5a
+#define FXOS8700_REG_M_CTRL_REG1 0x5b
+#define FXOS8700_REG_M_CTRL_REG2 0x5c
+#define FXOS8700_REG_M_CTRL_REG3 0x5d
+#define FXOS8700_REG_M_INT_SOURCE 0x5e
+#define FXOS8700_REG_G_VECM_CFG 0x5f
+#define FXOS8700_REG_G_VECM_THS_MSB 0x60
+#define FXOS8700_REG_G_VECM_THS_LSB 0x61
+#define FXOS8700_REG_G_VECM_CNT 0x62
+#define FXOS8700_REG_G_VECM_INITX_MSB 0x63
+#define FXOS8700_REG_G_VECM_INITX_LSB 0x64
+#define FXOS8700_REG_G_VECM_INITY_MSB 0x65
+#define FXOS8700_REG_G_VECM_INITY_LSB 0x66
+#define FXOS8700_REG_G_VECM_INITZ_MSB 0x67
+#define FXOS8700_REG_G_VECM_INITZ_LSB 0x68
+#define FXOS8700_REG_M_VECM_CFG 0x69
+#define FXOS8700_REG_M_VECM_THS_MSB 0x6a
+#define FXOS8700_REG_M_VECM_THS_LSB 0x6b
+#define FXOS8700_REG_M_VECM_CNT 0x6d
+#define FXOS8700_REG_M_VECM_INITX_MSB 0x6d
+#define FXOS8700_REG_M_VECM_INITX_LSB 0x6e
+#define FXOS8700_REG_M_VECM_INITY_MSB 0x6f
+#define FXOS8700_REG_M_VECM_INITY_LSB 0x70
+#define FXOS8700_REG_M_VECM_INITZ_MSB 0x71
+#define FXOS8700_REG_M_VECM_INITZ_LSB 0x72
+#define FXOS8700_REG_G_FFMT_THS_X1 0x73
+#define FXOS8700_REG_G_FFMT_THS_X0 0x74
+#define FXOS8700_REG_G_FFMT_THS_Y1 0x75
+#define FXOS8700_REG_G_FFMT_THS_Y0 0x76
+#define FXOS8700_REG_G_FFMT_THS_Z1 0x77
+#define FXOS8700_REG_G_FFMT_THS_Z0 0x78
+#define FXOS8700_REG_G_TRAN_INIT_MSB 0x79
+#define FXOS8700_REG_G_TRAN_INIT_LSB_X 0x7a
+#define FXOS8700_REG_G_TRAN_INIT_LSB_Y 0x7b
+#define FXOS8700_REG_G_TRAN_INIT_LSB_Z 0x7d
+#define FXOS8700_REG_TM_NVM_LOCK 0x7e
+#define FXOS8700_REG_NVM_DATA0_35 0x80
+#define FXOS8700_REG_NVM_DATA_BNK3 0xa4
+#define FXOS8700_REG_NVM_DATA_BNK2 0xa5
+#define FXOS8700_REG_NVM_DATA_BNK1 0xa6
+#define FXOS8700_REG_NVM_DATA_BNK0 0xa7
+
+enum fxos8700_fields {
+ F_DR_STATUS,
+ F_OUT_X_MSB,
+ F_OUT_X_LSB,
+ F_OUT_Y_MSB,
+ F_OUT_Y_LSB,
+ F_OUT_Z_MSB,
+ F_OUT_Z_LSB,
+ /* WHO_AM_I */
+ F_WHO_AM_I,
+ /* CTRL_REG1 */
+ F_ASLP_RATE, F_DR, F_INOISE, F_F_READ, F_ACTIVE,
+ /* CTRL_M_REG1 */
+ F_M_ACAL, F_M_RST, F_M_OST, F_M_OS, F_M_HMS,
+ /* MAX FIELDS */
+ F_MAX_FIELDS,
+};
+
+static const struct reg_field fxos8700_reg_fields[] = {
+ [F_DR_STATUS] = REG_FIELD(FXOS8700_REG_STATUS, 0, 7),
+ [F_OUT_X_MSB] = REG_FIELD(FXOS8700_REG_OUT_X_MSB, 0, 7),
+ [F_OUT_X_LSB] = REG_FIELD(FXOS8700_REG_OUT_X_LSB, 0, 7),
+ [F_OUT_Y_MSB] = REG_FIELD(FXOS8700_REG_OUT_Y_MSB, 0, 7),
+ [F_OUT_Y_LSB] = REG_FIELD(FXOS8700_REG_OUT_Y_LSB, 0, 7),
+ [F_OUT_Z_MSB] = REG_FIELD(FXOS8700_REG_OUT_Z_MSB, 0, 7),
+ [F_OUT_Z_LSB] = REG_FIELD(FXOS8700_REG_OUT_Z_LSB, 0, 7),
+ [F_WHO_AM_I] = REG_FIELD(FXOS8700_REG_WHO_AM_I, 0, 7),
+ [F_ASLP_RATE] = REG_FIELD(FXOS8700_REG_CTRL_REG1, 6, 7),
+ [F_DR] = REG_FIELD(FXOS8700_REG_CTRL_REG1, 3, 5),
+ [F_INOISE] = REG_FIELD(FXOS8700_REG_CTRL_REG1, 2, 2),
+ [F_F_READ] = REG_FIELD(FXOS8700_REG_CTRL_REG1, 1, 1),
+ [F_ACTIVE] = REG_FIELD(FXOS8700_REG_CTRL_REG1, 0, 0),
+ [F_M_ACAL] = REG_FIELD(FXOS8700_REG_M_CTRL_REG1, 7, 7),
+ [F_M_RST] = REG_FIELD(FXOS8700_REG_M_CTRL_REG1, 6, 6),
+ [F_M_OST] = REG_FIELD(FXOS8700_REG_M_CTRL_REG1, 5, 5),
+ [F_M_OS] = REG_FIELD(FXOS8700_REG_M_CTRL_REG1, 2, 4),
+ [F_M_HMS] = REG_FIELD(FXOS8700_REG_M_CTRL_REG1, 0, 1),
+};
+
+extern const struct dev_pm_ops fxos8700_pm_ops;
+
+int fxos8700_core_probe(struct device *dev, struct regmap *regmap,
+ const char *name);
+void fxos8700_core_remove(struct device *dev);
+#endif
diff --git a/drivers/iio/accel/fxos8700_core.c b/drivers/iio/accel/fxos8700_core.c
new file mode 100644
index 000000000000..6c2103c67f6c
--- /dev/null
+++ b/drivers/iio/accel/fxos8700_core.c
@@ -0,0 +1,560 @@
+/*
+ * Driver for NXP FXOS8700 Accelerometer and Magnetometer - Core
+ *
+ * Copyright (C) 2017 Linaro Ltd.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/pm.h>
+#include <linux/pm_runtime.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+#include <linux/iio/buffer.h>
+#include <linux/iio/trigger.h>
+#include <linux/iio/events.h>
+#include <linux/iio/trigger_consumer.h>
+#include <linux/iio/triggered_buffer.h>
+#include <linux/regmap.h>
+
+#include "fxos8700.h"
+
+#define FXOS8700_CHIP_ID 0xC4
+#define FXOS8700_PRE_CHIP_ID 0xC7
+
+#define FXOS8700_MODE_STANDBY 0x00
+#define FXOS8700_MODE_ACTIVE 0x01
+
+#define FXOS8700_ACCEL_ON 0x00
+#define FXOS8700_MAGN_ON 0x01
+#define FXOS8700_HYBRID_ON 0x03
+
+struct fxos8700_data {
+ u8 chip_id;
+ u8 mode;
+ u8 prev_mode;
+ bool accel_enabled;
+ bool magn_enabled;
+ struct regmap *regmap;
+ struct regmap_field *regmap_fields[F_MAX_FIELDS];
+ struct mutex mutex; /* protect data */
+};
+
+static int fxos8700_pm_get(struct fxos8700_data *data)
+{
+ struct device *dev = regmap_get_device(data->regmap);
+ int ret;
+
+ ret = pm_runtime_get_sync(dev);
+ if (ret < 0)
+ pm_runtime_put_noidle(dev);
+
+ return ret;
+}
+
+static int fxos8700_pm_put(struct fxos8700_data *data)
+{
+ struct device *dev = regmap_get_device(data->regmap);
+ int ret;
+
+ pm_runtime_mark_last_busy(dev);
+ ret = pm_runtime_put_autosuspend(dev);
+
+ return ret;
+}
+
+static int fxos8700_mode_get(struct fxos8700_data *data)
+{
+ unsigned int mode;
+ int ret;
+
+ ret = regmap_field_read(data->regmap_fields[F_ACTIVE], &mode);
+ if (ret < 0)
+ return ret;
+
+ return mode;
+}
+
+static int fxos8700_mode_set(struct fxos8700_data *data, u8 mode)
+{
+ int ret;
+
+ if (mode > FXOS8700_MODE_ACTIVE)
+ return -EINVAL;
+
+ if (mode == FXOS8700_MODE_ACTIVE)
+ ret = regmap_field_write(data->regmap_fields[F_ACTIVE], 1);
+ else
+ ret = regmap_field_write(data->regmap_fields[F_ACTIVE], 0);
+ if (ret < 0)
+ return ret;
+
+ data->prev_mode = data->mode;
+ data->mode = mode;
+
+ return ret;
+}
+
+static int fxos8700_pre_write(struct fxos8700_data *data)
+{
+ int actual_mode;
+ int ret;
+
+ actual_mode = fxos8700_mode_get(data);
+
+ if (actual_mode < 0)
+ return actual_mode;
+
+ ret = fxos8700_mode_set(data, FXOS8700_MODE_STANDBY);
+ if (ret < 0)
+ return ret;
+
+ /* going from active to standby varies from 0 to 300ms */
+ msleep_interruptible(300);
+
+ return ret;
+}
+
+static int fxos8700_post_write(struct fxos8700_data *data)
+{
+ return fxos8700_mode_set(data, data->prev_mode);
+}
+
+static int fxos8700_axis_get(struct fxos8700_data *data, unsigned long addr,
+ int type, int *val)
+{
+ struct device *dev = regmap_get_device(data->regmap);
+ __be16 axis_be;
+ unsigned int reg;
+ int bits;
+ int ret_pm;
+ int ret;
+
+ mutex_lock(&data->mutex);
+ ret = fxos8700_pm_get(data);
+ if (ret < 0)
+ goto data_unlock;
+
+ reg = addr;
+
+ /* For magnetometer we need to first read OUT_X_MSB to update the other
+ * axis, not needed of course if we are getting OUT_X
+ */
+ if (type == IIO_MAGN && reg != FXOS8700_REG_OUT_X_MSB) {
+ ret = regmap_bulk_read(data->regmap, FXOS8700_REG_OUT_X_MSB,
+ &axis_be, sizeof(axis_be));
+ if (ret < 0) {
+ dev_err(dev, "failed to read axis: %d\n", ret);
+ goto pm_put;
+ }
+ }
+
+ ret = regmap_bulk_read(data->regmap, reg, &axis_be, sizeof(axis_be));
+ if (ret < 0) {
+ dev_err(dev, "failed to read axis: %d\n", ret);
+ goto pm_put;
+ }
+
+ /* For accelerometer only use a complement by 2' 14 bits */
+ if (type == IIO_ACCEL) {
+ bits = 14;
+ axis_be = (axis_be >> 2);
+ } else {
+ bits = 16;
+ }
+
+ *val = sign_extend32(be16_to_cpu(axis_be), bits);
+
+ ret = IIO_VAL_INT;
+
+pm_put:
+ ret_pm = fxos8700_pm_put(data);
+ if (ret_pm < 0)
+ ret = ret_pm;
+
+data_unlock:
+ mutex_unlock(&data->mutex);
+
+ return ret;
+}
+
+static int fxos8700_hms_get(struct fxos8700_data *data, int type, int *val)
+{
+ unsigned int hms;
+ int ret_pm;
+ int ret;
+
+ mutex_lock(&data->mutex);
+ ret = fxos8700_pm_get(data);
+ if (ret < 0)
+ goto data_unlock;
+
+ ret = regmap_field_read(data->regmap_fields[F_M_HMS], &hms);
+ if (ret < 0)
+ goto pm_put;
+
+ *val = 0;
+
+ if (type == IIO_MAGN) {
+ if ((hms == FXOS8700_MAGN_ON) || (hms == FXOS8700_HYBRID_ON))
+ *val = 1;
+ } else {
+ if ((hms == FXOS8700_ACCEL_ON) || (hms == FXOS8700_HYBRID_ON))
+ *val = 1;
+ }
+
+ ret = IIO_VAL_INT;
+
+pm_put:
+ ret_pm = fxos8700_pm_put(data);
+ if (ret_pm < 0)
+ ret = ret_pm;
+
+data_unlock:
+ mutex_unlock(&data->mutex);
+
+ return ret;
+}
+
+static int fxos8700_hms_set(struct fxos8700_data *data, u8 hms)
+{
+ struct device *dev = regmap_get_device(data->regmap);
+ int ret;
+
+ mutex_lock(&data->mutex);
+ ret = fxos8700_pre_write(data);
+ if (ret < 0)
+ goto post_write;
+
+ ret = regmap_field_write(data->regmap_fields[F_M_HMS], hms);
+ if (ret < 0)
+ goto post_write;
+
+ if (hms == FXOS8700_HYBRID_ON) {
+ data->magn_enabled = true;
+ data->accel_enabled = true;
+ } else if (hms == FXOS8700_ACCEL_ON) {
+ data->magn_enabled = false;
+ data->accel_enabled = true;
+ } else if (hms == FXOS8700_MAGN_ON) {
+ data->accel_enabled = false;
+ data->magn_enabled = true;
+ }
+
+post_write:
+ fxos8700_post_write(data);
+
+ mutex_unlock(&data->mutex);
+
+ dev_info(dev, "accelerometer: %s magnetometer: %s\n",
+ data->accel_enabled ? "on" : "off",
+ data->magn_enabled ? "on" : "off");
+
+ return ret;
+}
+
+static int fxos8700_osr_set(struct fxos8700_data *data, u8 osr)
+{
+ int ret;
+
+ mutex_lock(&data->mutex);
+ ret = fxos8700_pre_write(data);
+ if (ret < 0)
+ goto post_write;
+
+ ret = regmap_field_write(data->regmap_fields[F_M_OS], osr);
+ if (ret < 0)
+ goto post_write;
+
+post_write:
+ fxos8700_post_write(data);
+
+ mutex_unlock(&data->mutex);
+
+ return ret;
+}
+
+static int fxos8700_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan, int *val,
+ int *val2, long mask)
+{
+ struct fxos8700_data *data = iio_priv(indio_dev);
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ switch (chan->type) {
+ case IIO_MAGN:
+ case IIO_ACCEL:
+ return fxos8700_axis_get(data, chan->address,
+ chan->type, val);
+ default:
+ return -EINVAL;
+ }
+ case IIO_CHAN_INFO_ENABLE:
+ return fxos8700_hms_get(data, chan->type, val);
+ case IIO_CHAN_INFO_SCALE:
+ switch (chan->type) {
+ case IIO_MAGN:
+ *val = 0;
+ *val2 = 1;
+ return IIO_VAL_INT_PLUS_MICRO;
+ case IIO_ACCEL:
+ *val = 0;
+ *val2 = 244; /* at +/-2g is 244mg/LSB */
+ return IIO_VAL_INT_PLUS_MICRO;
+ default:
+ return -EINVAL;
+ }
+ default:
+ return -EINVAL;
+ }
+}
+
+static int fxos8700_write_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan, int val,
+ int val2, long mask)
+{
+ struct fxos8700_data *data = iio_priv(indio_dev);
+ struct device *dev = regmap_get_device(data->regmap);
+ u8 hms;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_ENABLE:
+ if (chan->type == IIO_MAGN) {
+ if (data->magn_enabled == !!val)
+ return 0;
+
+ data->magn_enabled = !!val;
+ }
+
+ if (chan->type == IIO_ACCEL) {
+ if (data->accel_enabled == !!val)
+ return 0;
+
+ data->accel_enabled = !!val;
+ }
+
+ if (data->magn_enabled && data->accel_enabled) {
+ hms = FXOS8700_HYBRID_ON;
+ } else if (data->magn_enabled) {
+ hms = FXOS8700_MAGN_ON;
+ } else if (data->accel_enabled) {
+ hms = FXOS8700_ACCEL_ON;
+ } else {
+ dev_err(dev, "both accel and magn can not be off at the same time\n");
+ return -EINVAL;
+ }
+
+ return fxos8700_hms_set(data, hms);
+ default:
+ return -EINVAL;
+ }
+}
+
+#define FXOS8700_ACCEL_CHANNEL(_axis) { \
+ .type = IIO_ACCEL, \
+ .address = FXOS8700_REG_OUT_##_axis##_MSB, \
+ .modified = 1, \
+ .channel2 = IIO_MOD_##_axis, \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
+ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \
+ BIT(IIO_CHAN_INFO_ENABLE), \
+}
+
+#define FXOS8700_MAGN_CHANNEL(_axis) { \
+ .type = IIO_MAGN, \
+ .address = FXOS8700_REG_M_OUT_##_axis##_MSB, \
+ .modified = 1, \
+ .channel2 = IIO_MOD_##_axis, \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
+ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \
+ BIT(IIO_CHAN_INFO_ENABLE), \
+}
+
+static const struct iio_chan_spec fxos8700_channels[] = {
+ FXOS8700_ACCEL_CHANNEL(X),
+ FXOS8700_ACCEL_CHANNEL(Y),
+ FXOS8700_ACCEL_CHANNEL(Z),
+ FXOS8700_MAGN_CHANNEL(X),
+ FXOS8700_MAGN_CHANNEL(Y),
+ FXOS8700_MAGN_CHANNEL(Z),
+};
+
+static const struct iio_info fxos8700_info = {
+ .driver_module = THIS_MODULE,
+ .read_raw = &fxos8700_read_raw,
+ .write_raw = &fxos8700_write_raw,
+};
+
+static int fxos8700_chip_init(struct fxos8700_data *data)
+{
+ struct device *dev = regmap_get_device(data->regmap);
+ unsigned int chip_id;
+ int ret;
+
+ ret = regmap_field_read(data->regmap_fields[F_WHO_AM_I], &chip_id);
+ if (ret < 0)
+ return ret;
+
+ if (chip_id != FXOS8700_CHIP_ID && chip_id != FXOS8700_PRE_CHIP_ID) {
+ dev_err(dev, "chip id %d is not supported\n", chip_id);
+ return -EINVAL;
+ }
+
+ data->chip_id = chip_id;
+
+ ret = fxos8700_mode_set(data, FXOS8700_MODE_STANDBY);
+ if (ret < 0)
+ return ret;
+
+ /* Hybrid mode set - accel on and magnet on */
+ ret = fxos8700_hms_set(data, FXOS8700_HYBRID_ON);
+ if (ret < 0) {
+ dev_err(dev, "failed to set hybrid mode: %d\n", ret);
+ return ret;
+ }
+
+ /* Set oversampling to 8x for ODR 200Hz */
+ ret = fxos8700_osr_set(data, 0x07);
+ if (ret < 0)
+ dev_err(dev, "failed to set OSR: %d\n", ret);
+
+ return ret;
+}
+
+int fxos8700_core_probe(struct device *dev, struct regmap *regmap,
+ const char *name)
+{
+ struct fxos8700_data *data;
+ struct iio_dev *indio_dev;
+ struct regmap_field *f;
+ int ret;
+ int i;
+
+ indio_dev = devm_iio_device_alloc(dev, sizeof(*data));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ data = iio_priv(indio_dev);
+ dev_set_drvdata(dev, indio_dev);
+ data->regmap = regmap;
+
+ for (i = 0; i < F_MAX_FIELDS; i++) {
+ f = devm_regmap_field_alloc(dev, data->regmap,
+ fxos8700_reg_fields[i]);
+ if (IS_ERR(f)) {
+ dev_err(dev, "failed to alloc regmap field %d: %ld\n",
+ i, PTR_ERR(f));
+ return PTR_ERR(f);
+ }
+ data->regmap_fields[i] = f;
+ }
+
+ mutex_init(&data->mutex);
+
+ ret = fxos8700_chip_init(data);
+ if (ret < 0)
+ return ret;
+
+ indio_dev->dev.parent = dev;
+ indio_dev->channels = fxos8700_channels;
+ indio_dev->num_channels = ARRAY_SIZE(fxos8700_channels);
+ indio_dev->name = name;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+ indio_dev->info = &fxos8700_info;
+
+ ret = pm_runtime_set_active(dev);
+ if (ret)
+ return ret;
+
+ pm_runtime_enable(dev);
+ pm_runtime_set_autosuspend_delay(dev, 4000);
+ pm_runtime_use_autosuspend(dev);
+
+ fxos8700_mode_set(data, FXOS8700_MODE_ACTIVE);
+
+ ret = iio_device_register(indio_dev);
+ if (ret < 0) {
+ dev_err(dev, "unable to register iio device: %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(fxos8700_core_probe);
+
+void fxos8700_core_remove(struct device *dev)
+{
+ struct iio_dev *indio_dev = dev_get_drvdata(dev);
+ struct fxos8700_data *data = iio_priv(indio_dev);
+
+ iio_device_unregister(indio_dev);
+
+ pm_runtime_disable(dev);
+ pm_runtime_set_suspended(dev);
+ pm_runtime_put_noidle(dev);
+
+ fxos8700_mode_set(data, FXOS8700_MODE_STANDBY);
+}
+EXPORT_SYMBOL_GPL(fxos8700_core_remove);
+
+#ifdef CONFIG_PM_SLEEP
+static int fxos8700_suspend(struct device *dev)
+{
+ struct iio_dev *indio_dev = dev_get_drvdata(dev);
+ struct fxos8700_data *data = iio_priv(indio_dev);
+
+ fxos8700_mode_set(data, FXOS8700_MODE_STANDBY);
+
+ return 0;
+}
+
+static int fxos8700_resume(struct device *dev)
+{
+ struct iio_dev *indio_dev = dev_get_drvdata(dev);
+ struct fxos8700_data *data = iio_priv(indio_dev);
+
+ fxos8700_mode_set(data, data->prev_mode);
+
+ return 0;
+}
+#endif
+
+#ifdef CONFIG_PM
+static int fxos8700_runtime_suspend(struct device *dev)
+{
+ struct iio_dev *indio_dev = dev_get_drvdata(dev);
+ struct fxos8700_data *data = iio_priv(indio_dev);
+ int ret;
+
+ ret = fxos8700_mode_set(data, FXOS8700_MODE_STANDBY);
+ if (ret < 0)
+ return -EAGAIN;
+
+ return 0;
+}
+
+static int fxos8700_runtime_resume(struct device *dev)
+{
+ struct iio_dev *indio_dev = dev_get_drvdata(dev);
+ struct fxos8700_data *data = iio_priv(indio_dev);
+ int ret;
+
+ ret = fxos8700_mode_set(data, FXOS8700_MODE_ACTIVE);
+ if (ret < 0)
+ return -EAGAIN;
+
+ return 0;
+}
+#endif
+
+const struct dev_pm_ops fxos8700_pm_ops = {
+ SET_SYSTEM_SLEEP_PM_OPS(fxos8700_suspend, fxos8700_resume)
+ SET_RUNTIME_PM_OPS(fxos8700_runtime_suspend, fxos8700_runtime_resume,
+ NULL)
+};
+EXPORT_SYMBOL_GPL(fxos8700_pm_ops);
+
+MODULE_AUTHOR("Rui Miguel Silva <rui.silva@linaro.org>");
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("FXOS8700 Accel/Magn driver");