summaryrefslogtreecommitdiff
path: root/drivers/iio/common
diff options
context:
space:
mode:
authorAndré Fabian Silva Delgado <emulatorman@parabola.nu>2015-08-05 17:04:01 -0300
committerAndré Fabian Silva Delgado <emulatorman@parabola.nu>2015-08-05 17:04:01 -0300
commit57f0f512b273f60d52568b8c6b77e17f5636edc0 (patch)
tree5e910f0e82173f4ef4f51111366a3f1299037a7b /drivers/iio/common
Initial import
Diffstat (limited to 'drivers/iio/common')
-rw-r--r--drivers/iio/common/Kconfig7
-rw-r--r--drivers/iio/common/Makefile12
-rw-r--r--drivers/iio/common/hid-sensors/Kconfig28
-rw-r--r--drivers/iio/common/hid-sensors/Makefile7
-rw-r--r--drivers/iio/common/hid-sensors/hid-sensor-attributes.c397
-rw-r--r--drivers/iio/common/hid-sensors/hid-sensor-trigger.c220
-rw-r--r--drivers/iio/common/hid-sensors/hid-sensor-trigger.h32
-rw-r--r--drivers/iio/common/ssp_sensors/Kconfig26
-rw-r--r--drivers/iio/common/ssp_sensors/Makefile8
-rw-r--r--drivers/iio/common/ssp_sensors/ssp.h257
-rw-r--r--drivers/iio/common/ssp_sensors/ssp_dev.c714
-rw-r--r--drivers/iio/common/ssp_sensors/ssp_iio.c107
-rw-r--r--drivers/iio/common/ssp_sensors/ssp_iio_sensor.h71
-rw-r--r--drivers/iio/common/ssp_sensors/ssp_spi.c608
-rw-r--r--drivers/iio/common/st_sensors/Kconfig14
-rw-r--r--drivers/iio/common/st_sensors/Makefile10
-rw-r--r--drivers/iio/common/st_sensors/st_sensors_buffer.c128
-rw-r--r--drivers/iio/common/st_sensors/st_sensors_core.c556
-rw-r--r--drivers/iio/common/st_sensors/st_sensors_i2c.c112
-rw-r--r--drivers/iio/common/st_sensors/st_sensors_spi.c122
-rw-r--r--drivers/iio/common/st_sensors/st_sensors_trigger.c77
21 files changed, 3513 insertions, 0 deletions
diff --git a/drivers/iio/common/Kconfig b/drivers/iio/common/Kconfig
new file mode 100644
index 000000000..790f106d7
--- /dev/null
+++ b/drivers/iio/common/Kconfig
@@ -0,0 +1,7 @@
+#
+# IIO common modules
+#
+
+source "drivers/iio/common/hid-sensors/Kconfig"
+source "drivers/iio/common/ssp_sensors/Kconfig"
+source "drivers/iio/common/st_sensors/Kconfig"
diff --git a/drivers/iio/common/Makefile b/drivers/iio/common/Makefile
new file mode 100644
index 000000000..b1e4d9c95
--- /dev/null
+++ b/drivers/iio/common/Makefile
@@ -0,0 +1,12 @@
+#
+# Makefile for the IIO common modules.
+# Common modules contains modules, which can be shared among multiple
+# IIO modules. For example if the trigger processing is common for
+# multiple IIO modules then this can be moved to a common module
+# instead of duplicating in each module.
+#
+
+# When adding new entries keep the list in alphabetical order
+obj-y += hid-sensors/
+obj-y += ssp_sensors/
+obj-y += st_sensors/
diff --git a/drivers/iio/common/hid-sensors/Kconfig b/drivers/iio/common/hid-sensors/Kconfig
new file mode 100644
index 000000000..39188b72c
--- /dev/null
+++ b/drivers/iio/common/hid-sensors/Kconfig
@@ -0,0 +1,28 @@
+#
+# Hid Sensor common modules
+#
+menu "Hid Sensor IIO Common"
+
+config HID_SENSOR_IIO_COMMON
+ tristate "Common modules for all HID Sensor IIO drivers"
+ depends on HID_SENSOR_HUB
+ select HID_SENSOR_IIO_TRIGGER if IIO_BUFFER
+ help
+ Say yes here to build support for HID sensor to use
+ HID sensor common processing for attributes and IIO triggers.
+ There are many attributes which can be shared among multiple
+ HID sensor drivers, this module contains processing for those
+ attributes.
+
+config HID_SENSOR_IIO_TRIGGER
+ tristate "Common module (trigger) for all HID Sensor IIO drivers"
+ depends on HID_SENSOR_HUB && HID_SENSOR_IIO_COMMON
+ select IIO_TRIGGER
+ help
+ Say yes here to build trigger support for HID sensors.
+ Triggers will be send if all requested attributes were read.
+
+ If this driver is compiled as a module, it will be named
+ hid-sensor-trigger.
+
+endmenu
diff --git a/drivers/iio/common/hid-sensors/Makefile b/drivers/iio/common/hid-sensors/Makefile
new file mode 100644
index 000000000..22e7c5a82
--- /dev/null
+++ b/drivers/iio/common/hid-sensors/Makefile
@@ -0,0 +1,7 @@
+#
+# Makefile for the Hid sensor common modules.
+#
+
+obj-$(CONFIG_HID_SENSOR_IIO_COMMON) += hid-sensor-iio-common.o
+obj-$(CONFIG_HID_SENSOR_IIO_TRIGGER) += hid-sensor-trigger.o
+hid-sensor-iio-common-y := hid-sensor-attributes.o
diff --git a/drivers/iio/common/hid-sensors/hid-sensor-attributes.c b/drivers/iio/common/hid-sensors/hid-sensor-attributes.c
new file mode 100644
index 000000000..e81f43476
--- /dev/null
+++ b/drivers/iio/common/hid-sensors/hid-sensor-attributes.c
@@ -0,0 +1,397 @@
+/*
+ * HID Sensors Driver
+ * Copyright (c) 2012, Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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.,
+ * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+#include <linux/device.h>
+#include <linux/platform_device.h>
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/slab.h>
+#include <linux/hid-sensor-hub.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+
+static struct {
+ u32 usage_id;
+ int unit; /* 0 for default others from HID sensor spec */
+ int scale_val0; /* scale, whole number */
+ int scale_val1; /* scale, fraction in micros */
+} unit_conversion[] = {
+ {HID_USAGE_SENSOR_ACCEL_3D, 0, 9, 806650},
+ {HID_USAGE_SENSOR_ACCEL_3D,
+ HID_USAGE_SENSOR_UNITS_METERS_PER_SEC_SQRD, 1, 0},
+ {HID_USAGE_SENSOR_ACCEL_3D,
+ HID_USAGE_SENSOR_UNITS_G, 9, 806650},
+
+ {HID_USAGE_SENSOR_GYRO_3D, 0, 0, 17453},
+ {HID_USAGE_SENSOR_GYRO_3D,
+ HID_USAGE_SENSOR_UNITS_RADIANS_PER_SECOND, 1, 0},
+ {HID_USAGE_SENSOR_GYRO_3D,
+ HID_USAGE_SENSOR_UNITS_DEGREES_PER_SECOND, 0, 17453},
+
+ {HID_USAGE_SENSOR_COMPASS_3D, 0, 0, 1000},
+ {HID_USAGE_SENSOR_COMPASS_3D, HID_USAGE_SENSOR_UNITS_GAUSS, 1, 0},
+
+ {HID_USAGE_SENSOR_INCLINOMETER_3D, 0, 0, 17453},
+ {HID_USAGE_SENSOR_INCLINOMETER_3D,
+ HID_USAGE_SENSOR_UNITS_DEGREES, 0, 17453},
+ {HID_USAGE_SENSOR_INCLINOMETER_3D,
+ HID_USAGE_SENSOR_UNITS_RADIANS, 1, 0},
+
+ {HID_USAGE_SENSOR_ALS, 0, 1, 0},
+ {HID_USAGE_SENSOR_ALS, HID_USAGE_SENSOR_UNITS_LUX, 1, 0},
+
+ {HID_USAGE_SENSOR_PRESSURE, 0, 100000, 0},
+ {HID_USAGE_SENSOR_PRESSURE, HID_USAGE_SENSOR_UNITS_PASCAL, 1, 0},
+};
+
+static int pow_10(unsigned power)
+{
+ int i;
+ int ret = 1;
+ for (i = 0; i < power; ++i)
+ ret = ret * 10;
+
+ return ret;
+}
+
+static void simple_div(int dividend, int divisor, int *whole,
+ int *micro_frac)
+{
+ int rem;
+ int exp = 0;
+
+ *micro_frac = 0;
+ if (divisor == 0) {
+ *whole = 0;
+ return;
+ }
+ *whole = dividend/divisor;
+ rem = dividend % divisor;
+ if (rem) {
+ while (rem <= divisor) {
+ rem *= 10;
+ exp++;
+ }
+ *micro_frac = (rem / divisor) * pow_10(6-exp);
+ }
+}
+
+static void split_micro_fraction(unsigned int no, int exp, int *val1, int *val2)
+{
+ *val1 = no/pow_10(exp);
+ *val2 = no%pow_10(exp) * pow_10(6-exp);
+}
+
+/*
+VTF format uses exponent and variable size format.
+For example if the size is 2 bytes
+0x0067 with VTF16E14 format -> +1.03
+To convert just change to 0x67 to decimal and use two decimal as E14 stands
+for 10^-2.
+Negative numbers are 2's complement
+*/
+static void convert_from_vtf_format(u32 value, int size, int exp,
+ int *val1, int *val2)
+{
+ int sign = 1;
+
+ if (value & BIT(size*8 - 1)) {
+ value = ((1LL << (size * 8)) - value);
+ sign = -1;
+ }
+ exp = hid_sensor_convert_exponent(exp);
+ if (exp >= 0) {
+ *val1 = sign * value * pow_10(exp);
+ *val2 = 0;
+ } else {
+ split_micro_fraction(value, -exp, val1, val2);
+ if (*val1)
+ *val1 = sign * (*val1);
+ else
+ *val2 = sign * (*val2);
+ }
+}
+
+static u32 convert_to_vtf_format(int size, int exp, int val1, int val2)
+{
+ u32 value;
+ int sign = 1;
+
+ if (val1 < 0 || val2 < 0)
+ sign = -1;
+ exp = hid_sensor_convert_exponent(exp);
+ if (exp < 0) {
+ value = abs(val1) * pow_10(-exp);
+ value += abs(val2) / pow_10(6+exp);
+ } else
+ value = abs(val1) / pow_10(exp);
+ if (sign < 0)
+ value = ((1LL << (size * 8)) - value);
+
+ return value;
+}
+
+s32 hid_sensor_read_poll_value(struct hid_sensor_common *st)
+{
+ s32 value = 0;
+ int ret;
+
+ ret = sensor_hub_get_feature(st->hsdev,
+ st->poll.report_id,
+ st->poll.index, sizeof(value), &value);
+
+ if (ret < 0 || value < 0) {
+ return -EINVAL;
+ } else {
+ if (st->poll.units == HID_USAGE_SENSOR_UNITS_SECOND)
+ value = value * 1000;
+ }
+
+ return value;
+}
+EXPORT_SYMBOL(hid_sensor_read_poll_value);
+
+int hid_sensor_read_samp_freq_value(struct hid_sensor_common *st,
+ int *val1, int *val2)
+{
+ s32 value;
+ int ret;
+
+ ret = sensor_hub_get_feature(st->hsdev,
+ st->poll.report_id,
+ st->poll.index, sizeof(value), &value);
+ if (ret < 0 || value < 0) {
+ *val1 = *val2 = 0;
+ return -EINVAL;
+ } else {
+ if (st->poll.units == HID_USAGE_SENSOR_UNITS_MILLISECOND)
+ simple_div(1000, value, val1, val2);
+ else if (st->poll.units == HID_USAGE_SENSOR_UNITS_SECOND)
+ simple_div(1, value, val1, val2);
+ else {
+ *val1 = *val2 = 0;
+ return -EINVAL;
+ }
+ }
+
+ return IIO_VAL_INT_PLUS_MICRO;
+}
+EXPORT_SYMBOL(hid_sensor_read_samp_freq_value);
+
+int hid_sensor_write_samp_freq_value(struct hid_sensor_common *st,
+ int val1, int val2)
+{
+ s32 value;
+ int ret;
+
+ if (val1 < 0 || val2 < 0)
+ ret = -EINVAL;
+
+ value = val1 * pow_10(6) + val2;
+ if (value) {
+ if (st->poll.units == HID_USAGE_SENSOR_UNITS_MILLISECOND)
+ value = pow_10(9)/value;
+ else if (st->poll.units == HID_USAGE_SENSOR_UNITS_SECOND)
+ value = pow_10(6)/value;
+ else
+ value = 0;
+ }
+ ret = sensor_hub_set_feature(st->hsdev, st->poll.report_id,
+ st->poll.index, sizeof(value), &value);
+ if (ret < 0 || value < 0)
+ ret = -EINVAL;
+
+ return ret;
+}
+EXPORT_SYMBOL(hid_sensor_write_samp_freq_value);
+
+int hid_sensor_read_raw_hyst_value(struct hid_sensor_common *st,
+ int *val1, int *val2)
+{
+ s32 value;
+ int ret;
+
+ ret = sensor_hub_get_feature(st->hsdev,
+ st->sensitivity.report_id,
+ st->sensitivity.index, sizeof(value),
+ &value);
+ if (ret < 0 || value < 0) {
+ *val1 = *val2 = 0;
+ return -EINVAL;
+ } else {
+ convert_from_vtf_format(value, st->sensitivity.size,
+ st->sensitivity.unit_expo,
+ val1, val2);
+ }
+
+ return IIO_VAL_INT_PLUS_MICRO;
+}
+EXPORT_SYMBOL(hid_sensor_read_raw_hyst_value);
+
+int hid_sensor_write_raw_hyst_value(struct hid_sensor_common *st,
+ int val1, int val2)
+{
+ s32 value;
+ int ret;
+
+ value = convert_to_vtf_format(st->sensitivity.size,
+ st->sensitivity.unit_expo,
+ val1, val2);
+ ret = sensor_hub_set_feature(st->hsdev, st->sensitivity.report_id,
+ st->sensitivity.index, sizeof(value),
+ &value);
+ if (ret < 0 || value < 0)
+ ret = -EINVAL;
+
+ return ret;
+}
+EXPORT_SYMBOL(hid_sensor_write_raw_hyst_value);
+
+/*
+ * This fuction applies the unit exponent to the scale.
+ * For example:
+ * 9.806650 ->exp:2-> val0[980]val1[665000]
+ * 9.000806 ->exp:2-> val0[900]val1[80600]
+ * 0.174535 ->exp:2-> val0[17]val1[453500]
+ * 1.001745 ->exp:0-> val0[1]val1[1745]
+ * 1.001745 ->exp:2-> val0[100]val1[174500]
+ * 1.001745 ->exp:4-> val0[10017]val1[450000]
+ * 9.806650 ->exp:-2-> val0[0]val1[98066]
+ */
+static void adjust_exponent_micro(int *val0, int *val1, int scale0,
+ int scale1, int exp)
+{
+ int i;
+ int x;
+ int res;
+ int rem;
+
+ if (exp > 0) {
+ *val0 = scale0 * pow_10(exp);
+ res = 0;
+ if (exp > 6) {
+ *val1 = 0;
+ return;
+ }
+ for (i = 0; i < exp; ++i) {
+ x = scale1 / pow_10(5 - i);
+ res += (pow_10(exp - 1 - i) * x);
+ scale1 = scale1 % pow_10(5 - i);
+ }
+ *val0 += res;
+ *val1 = scale1 * pow_10(exp);
+ } else if (exp < 0) {
+ exp = abs(exp);
+ if (exp > 6) {
+ *val0 = *val1 = 0;
+ return;
+ }
+ *val0 = scale0 / pow_10(exp);
+ rem = scale0 % pow_10(exp);
+ res = 0;
+ for (i = 0; i < (6 - exp); ++i) {
+ x = scale1 / pow_10(5 - i);
+ res += (pow_10(5 - exp - i) * x);
+ scale1 = scale1 % pow_10(5 - i);
+ }
+ *val1 = rem * pow_10(6 - exp) + res;
+ } else {
+ *val0 = scale0;
+ *val1 = scale1;
+ }
+}
+
+int hid_sensor_format_scale(u32 usage_id,
+ struct hid_sensor_hub_attribute_info *attr_info,
+ int *val0, int *val1)
+{
+ int i;
+ int exp;
+
+ *val0 = 1;
+ *val1 = 0;
+
+ for (i = 0; i < ARRAY_SIZE(unit_conversion); ++i) {
+ if (unit_conversion[i].usage_id == usage_id &&
+ unit_conversion[i].unit == attr_info->units) {
+ exp = hid_sensor_convert_exponent(
+ attr_info->unit_expo);
+ adjust_exponent_micro(val0, val1,
+ unit_conversion[i].scale_val0,
+ unit_conversion[i].scale_val1, exp);
+ break;
+ }
+ }
+
+ return IIO_VAL_INT_PLUS_MICRO;
+}
+EXPORT_SYMBOL(hid_sensor_format_scale);
+
+static
+int hid_sensor_get_reporting_interval(struct hid_sensor_hub_device *hsdev,
+ u32 usage_id,
+ struct hid_sensor_common *st)
+{
+ sensor_hub_input_get_attribute_info(hsdev,
+ HID_FEATURE_REPORT, usage_id,
+ HID_USAGE_SENSOR_PROP_REPORT_INTERVAL,
+ &st->poll);
+ /* Default unit of measure is milliseconds */
+ if (st->poll.units == 0)
+ st->poll.units = HID_USAGE_SENSOR_UNITS_MILLISECOND;
+ return 0;
+
+}
+
+int hid_sensor_parse_common_attributes(struct hid_sensor_hub_device *hsdev,
+ u32 usage_id,
+ struct hid_sensor_common *st)
+{
+
+
+ hid_sensor_get_reporting_interval(hsdev, usage_id, st);
+
+ sensor_hub_input_get_attribute_info(hsdev,
+ HID_FEATURE_REPORT, usage_id,
+ HID_USAGE_SENSOR_PROP_REPORT_STATE,
+ &st->report_state);
+
+ sensor_hub_input_get_attribute_info(hsdev,
+ HID_FEATURE_REPORT, usage_id,
+ HID_USAGE_SENSOR_PROY_POWER_STATE,
+ &st->power_state);
+
+ sensor_hub_input_get_attribute_info(hsdev,
+ HID_FEATURE_REPORT, usage_id,
+ HID_USAGE_SENSOR_PROP_SENSITIVITY_ABS,
+ &st->sensitivity);
+
+ hid_dbg(hsdev->hdev, "common attributes: %x:%x, %x:%x, %x:%x %x:%x\n",
+ st->poll.index, st->poll.report_id,
+ st->report_state.index, st->report_state.report_id,
+ st->power_state.index, st->power_state.report_id,
+ st->sensitivity.index, st->sensitivity.report_id);
+
+ return 0;
+}
+EXPORT_SYMBOL(hid_sensor_parse_common_attributes);
+
+MODULE_AUTHOR("Srinivas Pandruvada <srinivas.pandruvada@intel.com>");
+MODULE_DESCRIPTION("HID Sensor common attribute processing");
+MODULE_LICENSE("GPL");
diff --git a/drivers/iio/common/hid-sensors/hid-sensor-trigger.c b/drivers/iio/common/hid-sensors/hid-sensor-trigger.c
new file mode 100644
index 000000000..595511022
--- /dev/null
+++ b/drivers/iio/common/hid-sensors/hid-sensor-trigger.c
@@ -0,0 +1,220 @@
+/*
+ * HID Sensors Driver
+ * Copyright (c) 2012, Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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.,
+ * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+#include <linux/device.h>
+#include <linux/platform_device.h>
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include <linux/hid-sensor-hub.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/trigger.h>
+#include <linux/iio/sysfs.h>
+#include "hid-sensor-trigger.h"
+
+static int _hid_sensor_power_state(struct hid_sensor_common *st, bool state)
+{
+ int state_val;
+ int report_val;
+ s32 poll_value = 0;
+
+ if (state) {
+ if (!atomic_read(&st->user_requested_state))
+ return 0;
+ if (sensor_hub_device_open(st->hsdev))
+ return -EIO;
+
+ atomic_inc(&st->data_ready);
+
+ state_val = hid_sensor_get_usage_index(st->hsdev,
+ st->power_state.report_id,
+ st->power_state.index,
+ HID_USAGE_SENSOR_PROP_POWER_STATE_D0_FULL_POWER_ENUM);
+ report_val = hid_sensor_get_usage_index(st->hsdev,
+ st->report_state.report_id,
+ st->report_state.index,
+ HID_USAGE_SENSOR_PROP_REPORTING_STATE_ALL_EVENTS_ENUM);
+
+ poll_value = hid_sensor_read_poll_value(st);
+ } else {
+ int val;
+
+ val = atomic_dec_if_positive(&st->data_ready);
+ if (val < 0)
+ return 0;
+
+ sensor_hub_device_close(st->hsdev);
+ state_val = hid_sensor_get_usage_index(st->hsdev,
+ st->power_state.report_id,
+ st->power_state.index,
+ HID_USAGE_SENSOR_PROP_POWER_STATE_D4_POWER_OFF_ENUM);
+ report_val = hid_sensor_get_usage_index(st->hsdev,
+ st->report_state.report_id,
+ st->report_state.index,
+ HID_USAGE_SENSOR_PROP_REPORTING_STATE_NO_EVENTS_ENUM);
+ }
+
+ if (state_val >= 0) {
+ state_val += st->power_state.logical_minimum;
+ sensor_hub_set_feature(st->hsdev, st->power_state.report_id,
+ st->power_state.index, sizeof(state_val),
+ &state_val);
+ }
+
+ if (report_val >= 0) {
+ report_val += st->report_state.logical_minimum;
+ sensor_hub_set_feature(st->hsdev, st->report_state.report_id,
+ st->report_state.index,
+ sizeof(report_val),
+ &report_val);
+ }
+
+ sensor_hub_get_feature(st->hsdev, st->power_state.report_id,
+ st->power_state.index,
+ sizeof(state_val), &state_val);
+ if (state && poll_value)
+ msleep_interruptible(poll_value * 2);
+
+ return 0;
+}
+EXPORT_SYMBOL(hid_sensor_power_state);
+
+int hid_sensor_power_state(struct hid_sensor_common *st, bool state)
+{
+
+#ifdef CONFIG_PM
+ int ret;
+
+ atomic_set(&st->user_requested_state, state);
+ if (state)
+ ret = pm_runtime_get_sync(&st->pdev->dev);
+ else {
+ pm_runtime_mark_last_busy(&st->pdev->dev);
+ ret = pm_runtime_put_autosuspend(&st->pdev->dev);
+ }
+ if (ret < 0) {
+ if (state)
+ pm_runtime_put_noidle(&st->pdev->dev);
+ return ret;
+ }
+
+ return 0;
+#else
+ atomic_set(&st->user_requested_state, state);
+ return _hid_sensor_power_state(st, state);
+#endif
+}
+
+static int hid_sensor_data_rdy_trigger_set_state(struct iio_trigger *trig,
+ bool state)
+{
+ return hid_sensor_power_state(iio_trigger_get_drvdata(trig), state);
+}
+
+void hid_sensor_remove_trigger(struct hid_sensor_common *attrb)
+{
+ iio_trigger_unregister(attrb->trigger);
+ iio_trigger_free(attrb->trigger);
+}
+EXPORT_SYMBOL(hid_sensor_remove_trigger);
+
+static const struct iio_trigger_ops hid_sensor_trigger_ops = {
+ .owner = THIS_MODULE,
+ .set_trigger_state = &hid_sensor_data_rdy_trigger_set_state,
+};
+
+int hid_sensor_setup_trigger(struct iio_dev *indio_dev, const char *name,
+ struct hid_sensor_common *attrb)
+{
+ int ret;
+ struct iio_trigger *trig;
+
+ trig = iio_trigger_alloc("%s-dev%d", name, indio_dev->id);
+ if (trig == NULL) {
+ dev_err(&indio_dev->dev, "Trigger Allocate Failed\n");
+ ret = -ENOMEM;
+ goto error_ret;
+ }
+
+ trig->dev.parent = indio_dev->dev.parent;
+ iio_trigger_set_drvdata(trig, attrb);
+ trig->ops = &hid_sensor_trigger_ops;
+ ret = iio_trigger_register(trig);
+
+ if (ret) {
+ dev_err(&indio_dev->dev, "Trigger Register Failed\n");
+ goto error_free_trig;
+ }
+ attrb->trigger = trig;
+ indio_dev->trig = iio_trigger_get(trig);
+
+ ret = pm_runtime_set_active(&indio_dev->dev);
+ if (ret)
+ goto error_unreg_trigger;
+
+ iio_device_set_drvdata(indio_dev, attrb);
+ pm_suspend_ignore_children(&attrb->pdev->dev, true);
+ pm_runtime_enable(&attrb->pdev->dev);
+ /* Default to 3 seconds, but can be changed from sysfs */
+ pm_runtime_set_autosuspend_delay(&attrb->pdev->dev,
+ 3000);
+ pm_runtime_use_autosuspend(&attrb->pdev->dev);
+
+ return ret;
+error_unreg_trigger:
+ iio_trigger_unregister(trig);
+error_free_trig:
+ iio_trigger_free(trig);
+error_ret:
+ return ret;
+}
+EXPORT_SYMBOL(hid_sensor_setup_trigger);
+
+#ifdef CONFIG_PM
+static int hid_sensor_suspend(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct iio_dev *indio_dev = platform_get_drvdata(pdev);
+ struct hid_sensor_common *attrb = iio_device_get_drvdata(indio_dev);
+
+ return _hid_sensor_power_state(attrb, false);
+}
+
+static int hid_sensor_resume(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct iio_dev *indio_dev = platform_get_drvdata(pdev);
+ struct hid_sensor_common *attrb = iio_device_get_drvdata(indio_dev);
+
+ return _hid_sensor_power_state(attrb, true);
+}
+
+#endif
+
+const struct dev_pm_ops hid_sensor_pm_ops = {
+ SET_SYSTEM_SLEEP_PM_OPS(hid_sensor_suspend, hid_sensor_resume)
+ SET_RUNTIME_PM_OPS(hid_sensor_suspend,
+ hid_sensor_resume, NULL)
+};
+EXPORT_SYMBOL(hid_sensor_pm_ops);
+
+MODULE_AUTHOR("Srinivas Pandruvada <srinivas.pandruvada@intel.com>");
+MODULE_DESCRIPTION("HID Sensor trigger processing");
+MODULE_LICENSE("GPL");
diff --git a/drivers/iio/common/hid-sensors/hid-sensor-trigger.h b/drivers/iio/common/hid-sensors/hid-sensor-trigger.h
new file mode 100644
index 000000000..9f4713f42
--- /dev/null
+++ b/drivers/iio/common/hid-sensors/hid-sensor-trigger.h
@@ -0,0 +1,32 @@
+/*
+ * HID Sensors Driver
+ * Copyright (c) 2012, Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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.,
+ * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+#ifndef _HID_SENSOR_TRIGGER_H
+#define _HID_SENSOR_TRIGGER_H
+
+#include <linux/pm.h>
+#include <linux/pm_runtime.h>
+
+extern const struct dev_pm_ops hid_sensor_pm_ops;
+
+int hid_sensor_setup_trigger(struct iio_dev *indio_dev, const char *name,
+ struct hid_sensor_common *attrb);
+void hid_sensor_remove_trigger(struct hid_sensor_common *attrb);
+int hid_sensor_power_state(struct hid_sensor_common *st, bool state);
+
+#endif
diff --git a/drivers/iio/common/ssp_sensors/Kconfig b/drivers/iio/common/ssp_sensors/Kconfig
new file mode 100644
index 000000000..0ea4faf01
--- /dev/null
+++ b/drivers/iio/common/ssp_sensors/Kconfig
@@ -0,0 +1,26 @@
+#
+# SSP sensor drivers and commons configuration
+#
+menu "SSP Sensor Common"
+
+config IIO_SSP_SENSORS_COMMONS
+ tristate "Commons for all SSP Sensor IIO drivers"
+ depends on IIO_SSP_SENSORHUB
+ select IIO_BUFFER
+ select IIO_KFIFO_BUF
+ help
+ Say yes here to build commons for SSP sensors.
+ To compile this as a module, choose M here: the module
+ will be called ssp_iio.
+
+config IIO_SSP_SENSORHUB
+ tristate "Samsung Sensorhub driver"
+ depends on SPI
+ select MFD_CORE
+ help
+ SSP driver for sensorhub.
+ If you say yes here you get ssp support for sensorhub.
+ To compile this driver as a module, choose M here: the
+ module will be called sensorhub.
+
+endmenu
diff --git a/drivers/iio/common/ssp_sensors/Makefile b/drivers/iio/common/ssp_sensors/Makefile
new file mode 100644
index 000000000..1e0389eb0
--- /dev/null
+++ b/drivers/iio/common/ssp_sensors/Makefile
@@ -0,0 +1,8 @@
+#
+# Makefile for SSP sensor drivers and commons.
+#
+
+sensorhub-objs := ssp_dev.o ssp_spi.o
+obj-$(CONFIG_IIO_SSP_SENSORHUB) += sensorhub.o
+
+obj-$(CONFIG_IIO_SSP_SENSORS_COMMONS) += ssp_iio.o
diff --git a/drivers/iio/common/ssp_sensors/ssp.h b/drivers/iio/common/ssp_sensors/ssp.h
new file mode 100644
index 000000000..b910e91d7
--- /dev/null
+++ b/drivers/iio/common/ssp_sensors/ssp.h
@@ -0,0 +1,257 @@
+/*
+ * Copyright (C) 2014, Samsung Electronics Co. Ltd. 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.
+ *
+ */
+
+#ifndef __SSP_SENSORHUB_H__
+#define __SSP_SENSORHUB_H__
+
+#include <linux/delay.h>
+#include <linux/gpio.h>
+#include <linux/iio/common/ssp_sensors.h>
+#include <linux/iio/iio.h>
+#include <linux/spi/spi.h>
+
+#define SSP_DEVICE_ID 0x55
+
+#ifdef SSP_DBG
+#define ssp_dbg(format, ...) pr_info("[SSP] "format, ##__VA_ARGS__)
+#else
+#define ssp_dbg(format, ...)
+#endif
+
+#define SSP_SW_RESET_TIME 3000
+/* Sensor polling in ms */
+#define SSP_DEFAULT_POLLING_DELAY 200
+#define SSP_DEFAULT_RETRIES 3
+#define SSP_DATA_PACKET_SIZE 960
+#define SSP_HEADER_BUFFER_SIZE 4
+
+enum {
+ SSP_KERNEL_BINARY = 0,
+ SSP_KERNEL_CRASHED_BINARY,
+};
+
+enum {
+ SSP_INITIALIZATION_STATE = 0,
+ SSP_NO_SENSOR_STATE,
+ SSP_ADD_SENSOR_STATE,
+ SSP_RUNNING_SENSOR_STATE,
+};
+
+/* Firmware download STATE */
+enum {
+ SSP_FW_DL_STATE_FAIL = -1,
+ SSP_FW_DL_STATE_NONE = 0,
+ SSP_FW_DL_STATE_NEED_TO_SCHEDULE,
+ SSP_FW_DL_STATE_SCHEDULED,
+ SSP_FW_DL_STATE_DOWNLOADING,
+ SSP_FW_DL_STATE_SYNC,
+ SSP_FW_DL_STATE_DONE,
+};
+
+#define SSP_INVALID_REVISION 99999
+#define SSP_INVALID_REVISION2 0xffffff
+
+/* AP -> SSP Instruction */
+#define SSP_MSG2SSP_INST_BYPASS_SENSOR_ADD 0xa1
+#define SSP_MSG2SSP_INST_BYPASS_SENSOR_RM 0xa2
+#define SSP_MSG2SSP_INST_REMOVE_ALL 0xa3
+#define SSP_MSG2SSP_INST_CHANGE_DELAY 0xa4
+#define SSP_MSG2SSP_INST_LIBRARY_ADD 0xb1
+#define SSP_MSG2SSP_INST_LIBRARY_REMOVE 0xb2
+#define SSP_MSG2SSP_INST_LIB_NOTI 0xb4
+#define SSP_MSG2SSP_INST_LIB_DATA 0xc1
+
+#define SSP_MSG2SSP_AP_MCU_SET_GYRO_CAL 0xcd
+#define SSP_MSG2SSP_AP_MCU_SET_ACCEL_CAL 0xce
+#define SSP_MSG2SSP_AP_STATUS_SHUTDOWN 0xd0
+#define SSP_MSG2SSP_AP_STATUS_WAKEUP 0xd1
+#define SSP_MSG2SSP_AP_STATUS_SLEEP 0xd2
+#define SSP_MSG2SSP_AP_STATUS_RESUME 0xd3
+#define SSP_MSG2SSP_AP_STATUS_SUSPEND 0xd4
+#define SSP_MSG2SSP_AP_STATUS_RESET 0xd5
+#define SSP_MSG2SSP_AP_STATUS_POW_CONNECTED 0xd6
+#define SSP_MSG2SSP_AP_STATUS_POW_DISCONNECTED 0xd7
+#define SSP_MSG2SSP_AP_TEMPHUMIDITY_CAL_DONE 0xda
+#define SSP_MSG2SSP_AP_MCU_SET_DUMPMODE 0xdb
+#define SSP_MSG2SSP_AP_MCU_DUMP_CHECK 0xdc
+#define SSP_MSG2SSP_AP_MCU_BATCH_FLUSH 0xdd
+#define SSP_MSG2SSP_AP_MCU_BATCH_COUNT 0xdf
+
+#define SSP_MSG2SSP_AP_WHOAMI 0x0f
+#define SSP_MSG2SSP_AP_FIRMWARE_REV 0xf0
+#define SSP_MSG2SSP_AP_SENSOR_FORMATION 0xf1
+#define SSP_MSG2SSP_AP_SENSOR_PROXTHRESHOLD 0xf2
+#define SSP_MSG2SSP_AP_SENSOR_BARCODE_EMUL 0xf3
+#define SSP_MSG2SSP_AP_SENSOR_SCANNING 0xf4
+#define SSP_MSG2SSP_AP_SET_MAGNETIC_HWOFFSET 0xf5
+#define SSP_MSG2SSP_AP_GET_MAGNETIC_HWOFFSET 0xf6
+#define SSP_MSG2SSP_AP_SENSOR_GESTURE_CURRENT 0xf7
+#define SSP_MSG2SSP_AP_GET_THERM 0xf8
+#define SSP_MSG2SSP_AP_GET_BIG_DATA 0xf9
+#define SSP_MSG2SSP_AP_SET_BIG_DATA 0xfa
+#define SSP_MSG2SSP_AP_START_BIG_DATA 0xfb
+#define SSP_MSG2SSP_AP_SET_MAGNETIC_STATIC_MATRIX 0xfd
+#define SSP_MSG2SSP_AP_SENSOR_TILT 0xea
+#define SSP_MSG2SSP_AP_MCU_SET_TIME 0xfe
+#define SSP_MSG2SSP_AP_MCU_GET_TIME 0xff
+
+#define SSP_MSG2SSP_AP_FUSEROM 0x01
+
+/* voice data */
+#define SSP_TYPE_WAKE_UP_VOICE_SERVICE 0x01
+#define SSP_TYPE_WAKE_UP_VOICE_SOUND_SOURCE_AM 0x01
+#define SSP_TYPE_WAKE_UP_VOICE_SOUND_SOURCE_GRAMMER 0x02
+
+/* Factory Test */
+#define SSP_ACCELEROMETER_FACTORY 0x80
+#define SSP_GYROSCOPE_FACTORY 0x81
+#define SSP_GEOMAGNETIC_FACTORY 0x82
+#define SSP_PRESSURE_FACTORY 0x85
+#define SSP_GESTURE_FACTORY 0x86
+#define SSP_TEMPHUMIDITY_CRC_FACTORY 0x88
+#define SSP_GYROSCOPE_TEMP_FACTORY 0x8a
+#define SSP_GYROSCOPE_DPS_FACTORY 0x8b
+#define SSP_MCU_FACTORY 0x8c
+#define SSP_MCU_SLEEP_FACTORY 0x8d
+
+/* SSP -> AP ACK about write CMD */
+#define SSP_MSG_ACK 0x80 /* ACK from SSP to AP */
+#define SSP_MSG_NAK 0x70 /* NAK from SSP to AP */
+
+struct ssp_sensorhub_info {
+ char *fw_name;
+ char *fw_crashed_name;
+ unsigned int fw_rev;
+ const u8 * const mag_table;
+ const unsigned int mag_length;
+};
+
+/* ssp_msg options bit */
+#define SSP_RW 0
+#define SSP_INDEX 3
+
+#define SSP_AP2HUB_READ 0
+#define SSP_AP2HUB_WRITE 1
+#define SSP_HUB2AP_WRITE 2
+#define SSP_AP2HUB_READY 3
+#define SSP_AP2HUB_RETURN 4
+
+/**
+ * struct ssp_data - ssp platformdata structure
+ * @spi: spi device
+ * @sensorhub_info: info about sensorhub board specific features
+ * @wdt_timer: watchdog timer
+ * @work_wdt: watchdog work
+ * @work_firmware: firmware upgrade work queue
+ * @work_refresh: refresh work queue for reset request from MCU
+ * @shut_down: shut down flag
+ * @mcu_dump_mode: mcu dump mode for debug
+ * @time_syncing: time syncing indication flag
+ * @timestamp: previous time in ns calculated for time syncing
+ * @check_status: status table for each sensor
+ * @com_fail_cnt: communication fail count
+ * @reset_cnt: reset count
+ * @timeout_cnt: timeout count
+ * @available_sensors: available sensors seen by sensorhub (bit array)
+ * @cur_firm_rev: cached current firmware revision
+ * @last_resume_state: last AP resume/suspend state used to handle the PM
+ * state of ssp
+ * @last_ap_state: (obsolete) sleep notification for MCU
+ * @sensor_enable: sensor enable mask
+ * @delay_buf: data acquisition intervals table
+ * @batch_latency_buf: yet unknown but existing in communication protocol
+ * @batch_opt_buf: yet unknown but existing in communication protocol
+ * @accel_position: yet unknown but existing in communication protocol
+ * @mag_position: yet unknown but existing in communication protocol
+ * @fw_dl_state: firmware download state
+ * @comm_lock: lock protecting the handshake
+ * @pending_lock: lock protecting pending list and completion
+ * @mcu_reset_gpio: mcu reset line
+ * @ap_mcu_gpio: ap to mcu gpio line
+ * @mcu_ap_gpio: mcu to ap gpio line
+ * @pending_list: pending list for messages queued to be sent/read
+ * @sensor_devs: registered IIO devices table
+ * @enable_refcount: enable reference count for wdt (watchdog timer)
+ * @header_buffer: cache aligned buffer for packet header
+ */
+struct ssp_data {
+ struct spi_device *spi;
+ struct ssp_sensorhub_info *sensorhub_info;
+ struct timer_list wdt_timer;
+ struct work_struct work_wdt;
+ struct delayed_work work_refresh;
+
+ bool shut_down;
+ bool mcu_dump_mode;
+ bool time_syncing;
+ int64_t timestamp;
+
+ int check_status[SSP_SENSOR_MAX];
+
+ unsigned int com_fail_cnt;
+ unsigned int reset_cnt;
+ unsigned int timeout_cnt;
+
+ unsigned int available_sensors;
+ unsigned int cur_firm_rev;
+
+ char last_resume_state;
+ char last_ap_state;
+
+ unsigned int sensor_enable;
+ u32 delay_buf[SSP_SENSOR_MAX];
+ s32 batch_latency_buf[SSP_SENSOR_MAX];
+ s8 batch_opt_buf[SSP_SENSOR_MAX];
+
+ int accel_position;
+ int mag_position;
+ int fw_dl_state;
+
+ struct mutex comm_lock;
+ struct mutex pending_lock;
+
+ int mcu_reset_gpio;
+ int ap_mcu_gpio;
+ int mcu_ap_gpio;
+
+ struct list_head pending_list;
+
+ struct iio_dev *sensor_devs[SSP_SENSOR_MAX];
+ atomic_t enable_refcount;
+
+ __le16 header_buffer[SSP_HEADER_BUFFER_SIZE / sizeof(__le16)]
+ ____cacheline_aligned;
+};
+
+void ssp_clean_pending_list(struct ssp_data *data);
+
+int ssp_command(struct ssp_data *data, char command, int arg);
+
+int ssp_send_instruction(struct ssp_data *data, u8 inst, u8 sensor_type,
+ u8 *send_buf, u8 length);
+
+int ssp_irq_msg(struct ssp_data *data);
+
+int ssp_get_chipid(struct ssp_data *data);
+
+int ssp_set_magnetic_matrix(struct ssp_data *data);
+
+unsigned int ssp_get_sensor_scanning_info(struct ssp_data *data);
+
+unsigned int ssp_get_firmware_rev(struct ssp_data *data);
+
+int ssp_queue_ssp_refresh_task(struct ssp_data *data, unsigned int delay);
+
+#endif /* __SSP_SENSORHUB_H__ */
diff --git a/drivers/iio/common/ssp_sensors/ssp_dev.c b/drivers/iio/common/ssp_sensors/ssp_dev.c
new file mode 100644
index 000000000..0748301d3
--- /dev/null
+++ b/drivers/iio/common/ssp_sensors/ssp_dev.c
@@ -0,0 +1,714 @@
+/*
+ * Copyright (C) 2014, Samsung Electronics Co. Ltd. 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.
+ *
+ */
+
+#include <linux/iio/iio.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/mfd/core.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_gpio.h>
+#include <linux/of_platform.h>
+#include "ssp.h"
+
+#define SSP_WDT_TIME 10000
+#define SSP_LIMIT_RESET_CNT 20
+#define SSP_LIMIT_TIMEOUT_CNT 3
+
+/* It is possible that it is max clk rate for version 1.0 of bootcode */
+#define SSP_BOOT_SPI_HZ 400000
+
+/*
+ * These fields can look enigmatic but this structure is used mainly to flat
+ * some values and depends on command type.
+ */
+struct ssp_instruction {
+ __le32 a;
+ __le32 b;
+ u8 c;
+} __attribute__((__packed__));
+
+static const u8 ssp_magnitude_table[] = {110, 85, 171, 71, 203, 195, 0, 67,
+ 208, 56, 175, 244, 206, 213, 0, 92, 250, 0, 55, 48, 189, 252, 171,
+ 243, 13, 45, 250};
+
+static const struct ssp_sensorhub_info ssp_rinato_info = {
+ .fw_name = "/*(DEBLOBBED)*/",
+ .fw_crashed_name = "/*(DEBLOBBED)*/",
+ .fw_rev = 14052300,
+ .mag_table = ssp_magnitude_table,
+ .mag_length = ARRAY_SIZE(ssp_magnitude_table),
+};
+
+static const struct ssp_sensorhub_info ssp_thermostat_info = {
+ .fw_name = "/*(DEBLOBBED)*/",
+ .fw_crashed_name = "/*(DEBLOBBED)*/",
+ .fw_rev = 14080600,
+ .mag_table = ssp_magnitude_table,
+ .mag_length = ARRAY_SIZE(ssp_magnitude_table),
+};
+
+static const struct mfd_cell sensorhub_sensor_devs[] = {
+ {
+ .name = "ssp-accelerometer",
+ },
+ {
+ .name = "ssp-gyroscope",
+ },
+};
+
+static void ssp_toggle_mcu_reset_gpio(struct ssp_data *data)
+{
+ gpio_set_value(data->mcu_reset_gpio, 0);
+ usleep_range(1000, 1200);
+ gpio_set_value(data->mcu_reset_gpio, 1);
+ msleep(50);
+}
+
+static void ssp_sync_available_sensors(struct ssp_data *data)
+{
+ int i, ret;
+
+ for (i = 0; i < SSP_SENSOR_MAX; ++i) {
+ if (data->available_sensors & BIT(i)) {
+ ret = ssp_enable_sensor(data, i, data->delay_buf[i]);
+ if (ret < 0) {
+ dev_err(&data->spi->dev,
+ "Sync sensor nr: %d fail\n", i);
+ continue;
+ }
+ }
+ }
+
+ ret = ssp_command(data, SSP_MSG2SSP_AP_MCU_SET_DUMPMODE,
+ data->mcu_dump_mode);
+ if (ret < 0)
+ dev_err(&data->spi->dev,
+ "SSP_MSG2SSP_AP_MCU_SET_DUMPMODE failed\n");
+}
+
+static void ssp_enable_mcu(struct ssp_data *data, bool enable)
+{
+ dev_info(&data->spi->dev, "current shutdown = %d, old = %d\n", enable,
+ data->shut_down);
+
+ if (enable && data->shut_down) {
+ data->shut_down = false;
+ enable_irq(data->spi->irq);
+ enable_irq_wake(data->spi->irq);
+ } else if (!enable && !data->shut_down) {
+ data->shut_down = true;
+ disable_irq(data->spi->irq);
+ disable_irq_wake(data->spi->irq);
+ } else {
+ dev_warn(&data->spi->dev, "current shutdown = %d, old = %d\n",
+ enable, data->shut_down);
+ }
+}
+
+/*
+ * This function is the first one which communicates with the mcu so it is
+ * possible that the first attempt will fail
+ */
+static int ssp_check_fwbl(struct ssp_data *data)
+{
+ int retries = 0;
+
+ while (retries++ < 5) {
+ data->cur_firm_rev = ssp_get_firmware_rev(data);
+ if (data->cur_firm_rev == SSP_INVALID_REVISION ||
+ data->cur_firm_rev == SSP_INVALID_REVISION2) {
+ dev_warn(&data->spi->dev,
+ "Invalid revision, trying %d time\n", retries);
+ } else {
+ break;
+ }
+ }
+
+ if (data->cur_firm_rev == SSP_INVALID_REVISION ||
+ data->cur_firm_rev == SSP_INVALID_REVISION2) {
+ dev_err(&data->spi->dev, "SSP_INVALID_REVISION\n");
+ return SSP_FW_DL_STATE_NEED_TO_SCHEDULE;
+ }
+
+ dev_info(&data->spi->dev,
+ "MCU Firm Rev : Old = %8u, New = %8u\n",
+ data->cur_firm_rev,
+ data->sensorhub_info->fw_rev);
+
+ if (data->cur_firm_rev != data->sensorhub_info->fw_rev)
+ return SSP_FW_DL_STATE_NEED_TO_SCHEDULE;
+
+ return SSP_FW_DL_STATE_NONE;
+}
+
+static void ssp_reset_mcu(struct ssp_data *data)
+{
+ ssp_enable_mcu(data, false);
+ ssp_clean_pending_list(data);
+ ssp_toggle_mcu_reset_gpio(data);
+ ssp_enable_mcu(data, true);
+}
+
+static void ssp_wdt_work_func(struct work_struct *work)
+{
+ struct ssp_data *data = container_of(work, struct ssp_data, work_wdt);
+
+ dev_err(&data->spi->dev, "%s - Sensor state: 0x%x, RC: %u, CC: %u\n",
+ __func__, data->available_sensors, data->reset_cnt,
+ data->com_fail_cnt);
+
+ ssp_reset_mcu(data);
+ data->com_fail_cnt = 0;
+ data->timeout_cnt = 0;
+}
+
+static void ssp_wdt_timer_func(unsigned long ptr)
+{
+ struct ssp_data *data = (struct ssp_data *)ptr;
+
+ switch (data->fw_dl_state) {
+ case SSP_FW_DL_STATE_FAIL:
+ case SSP_FW_DL_STATE_DOWNLOADING:
+ case SSP_FW_DL_STATE_SYNC:
+ goto _mod;
+ }
+
+ if (data->timeout_cnt > SSP_LIMIT_TIMEOUT_CNT ||
+ data->com_fail_cnt > SSP_LIMIT_RESET_CNT)
+ queue_work(system_power_efficient_wq, &data->work_wdt);
+_mod:
+ mod_timer(&data->wdt_timer, jiffies + msecs_to_jiffies(SSP_WDT_TIME));
+}
+
+static void ssp_enable_wdt_timer(struct ssp_data *data)
+{
+ mod_timer(&data->wdt_timer, jiffies + msecs_to_jiffies(SSP_WDT_TIME));
+}
+
+static void ssp_disable_wdt_timer(struct ssp_data *data)
+{
+ del_timer_sync(&data->wdt_timer);
+ cancel_work_sync(&data->work_wdt);
+}
+
+/**
+ * ssp_get_sensor_delay() - gets sensor data acquisition period
+ * @data: sensorhub structure
+ * @type: SSP sensor type
+ *
+ * Returns acquisition period in ms
+ */
+u32 ssp_get_sensor_delay(struct ssp_data *data, enum ssp_sensor_type type)
+{
+ return data->delay_buf[type];
+}
+EXPORT_SYMBOL(ssp_get_sensor_delay);
+
+/**
+ * ssp_enable_sensor() - enables data acquisition for sensor
+ * @data: sensorhub structure
+ * @type: SSP sensor type
+ * @delay: delay in ms
+ *
+ * Returns 0 or negative value in case of error
+ */
+int ssp_enable_sensor(struct ssp_data *data, enum ssp_sensor_type type,
+ u32 delay)
+{
+ int ret;
+ struct ssp_instruction to_send;
+
+ to_send.a = cpu_to_le32(delay);
+ to_send.b = cpu_to_le32(data->batch_latency_buf[type]);
+ to_send.c = data->batch_opt_buf[type];
+
+ switch (data->check_status[type]) {
+ case SSP_INITIALIZATION_STATE:
+ /* do calibration step, now just enable */
+ case SSP_ADD_SENSOR_STATE:
+ ret = ssp_send_instruction(data,
+ SSP_MSG2SSP_INST_BYPASS_SENSOR_ADD,
+ type,
+ (u8 *)&to_send, sizeof(to_send));
+ if (ret < 0) {
+ dev_err(&data->spi->dev, "Enabling sensor failed\n");
+ data->check_status[type] = SSP_NO_SENSOR_STATE;
+ goto derror;
+ }
+
+ data->sensor_enable |= BIT(type);
+ data->check_status[type] = SSP_RUNNING_SENSOR_STATE;
+ break;
+ case SSP_RUNNING_SENSOR_STATE:
+ ret = ssp_send_instruction(data,
+ SSP_MSG2SSP_INST_CHANGE_DELAY, type,
+ (u8 *)&to_send, sizeof(to_send));
+ if (ret < 0) {
+ dev_err(&data->spi->dev,
+ "Changing sensor delay failed\n");
+ goto derror;
+ }
+ break;
+ default:
+ data->check_status[type] = SSP_ADD_SENSOR_STATE;
+ break;
+ }
+
+ data->delay_buf[type] = delay;
+
+ if (atomic_inc_return(&data->enable_refcount) == 1)
+ ssp_enable_wdt_timer(data);
+
+ return 0;
+
+derror:
+ return ret;
+}
+EXPORT_SYMBOL(ssp_enable_sensor);
+
+/**
+ * ssp_change_delay() - changes data acquisition for sensor
+ * @data: sensorhub structure
+ * @type: SSP sensor type
+ * @delay: delay in ms
+ *
+ * Returns 0 or negative value in case of error
+ */
+int ssp_change_delay(struct ssp_data *data, enum ssp_sensor_type type,
+ u32 delay)
+{
+ int ret;
+ struct ssp_instruction to_send;
+
+ to_send.a = cpu_to_le32(delay);
+ to_send.b = cpu_to_le32(data->batch_latency_buf[type]);
+ to_send.c = data->batch_opt_buf[type];
+
+ ret = ssp_send_instruction(data, SSP_MSG2SSP_INST_CHANGE_DELAY, type,
+ (u8 *)&to_send, sizeof(to_send));
+ if (ret < 0) {
+ dev_err(&data->spi->dev, "Changing sensor delay failed\n");
+ return ret;
+ }
+
+ data->delay_buf[type] = delay;
+
+ return 0;
+}
+EXPORT_SYMBOL(ssp_change_delay);
+
+/**
+ * ssp_disable_sensor() - disables sensor
+ *
+ * @data: sensorhub structure
+ * @type: SSP sensor type
+ *
+ * Returns 0 or negative value in case of error
+ */
+int ssp_disable_sensor(struct ssp_data *data, enum ssp_sensor_type type)
+{
+ int ret;
+ __le32 command;
+
+ if (data->sensor_enable & BIT(type)) {
+ command = cpu_to_le32(data->delay_buf[type]);
+
+ ret = ssp_send_instruction(data,
+ SSP_MSG2SSP_INST_BYPASS_SENSOR_RM,
+ type, (u8 *)&command,
+ sizeof(command));
+ if (ret < 0) {
+ dev_err(&data->spi->dev, "Remove sensor fail\n");
+ return ret;
+ }
+
+ data->sensor_enable &= ~BIT(type);
+ }
+
+ data->check_status[type] = SSP_ADD_SENSOR_STATE;
+
+ if (atomic_dec_and_test(&data->enable_refcount))
+ ssp_disable_wdt_timer(data);
+
+ return 0;
+}
+EXPORT_SYMBOL(ssp_disable_sensor);
+
+static irqreturn_t ssp_irq_thread_fn(int irq, void *dev_id)
+{
+ struct ssp_data *data = dev_id;
+
+ /*
+ * This wrapper is done to preserve error path for ssp_irq_msg, also
+ * it is defined in different file.
+ */
+ ssp_irq_msg(data);
+
+ return IRQ_HANDLED;
+}
+
+static int ssp_initialize_mcu(struct ssp_data *data)
+{
+ int ret;
+
+ ssp_clean_pending_list(data);
+
+ ret = ssp_get_chipid(data);
+ if (ret != SSP_DEVICE_ID) {
+ dev_err(&data->spi->dev, "%s - MCU %s ret = %d\n", __func__,
+ ret < 0 ? "is not working" : "identification failed",
+ ret);
+ return ret < 0 ? ret : -ENODEV;
+ }
+
+ dev_info(&data->spi->dev, "MCU device ID = %d\n", ret);
+
+ /*
+ * needs clarification, for now do not want to export all transfer
+ * methods to sensors' drivers
+ */
+ ret = ssp_set_magnetic_matrix(data);
+ if (ret < 0) {
+ dev_err(&data->spi->dev,
+ "%s - ssp_set_magnetic_matrix failed\n", __func__);
+ return ret;
+ }
+
+ data->available_sensors = ssp_get_sensor_scanning_info(data);
+ if (data->available_sensors == 0) {
+ dev_err(&data->spi->dev,
+ "%s - ssp_get_sensor_scanning_info failed\n", __func__);
+ return -EIO;
+ }
+
+ data->cur_firm_rev = ssp_get_firmware_rev(data);
+ dev_info(&data->spi->dev, "MCU Firm Rev : New = %8u\n",
+ data->cur_firm_rev);
+
+ return ssp_command(data, SSP_MSG2SSP_AP_MCU_DUMP_CHECK, 0);
+}
+
+/*
+ * sensorhub can request its reinitialization as some brutal and rare error
+ * handling. It can be requested from the MCU.
+ */
+static void ssp_refresh_task(struct work_struct *work)
+{
+ struct ssp_data *data = container_of((struct delayed_work *)work,
+ struct ssp_data, work_refresh);
+
+ dev_info(&data->spi->dev, "refreshing\n");
+
+ data->reset_cnt++;
+
+ if (ssp_initialize_mcu(data) >= 0) {
+ ssp_sync_available_sensors(data);
+ if (data->last_ap_state != 0)
+ ssp_command(data, data->last_ap_state, 0);
+
+ if (data->last_resume_state != 0)
+ ssp_command(data, data->last_resume_state, 0);
+
+ data->timeout_cnt = 0;
+ data->com_fail_cnt = 0;
+ }
+}
+
+int ssp_queue_ssp_refresh_task(struct ssp_data *data, unsigned int delay)
+{
+ cancel_delayed_work_sync(&data->work_refresh);
+
+ return queue_delayed_work(system_power_efficient_wq,
+ &data->work_refresh,
+ msecs_to_jiffies(delay));
+}
+
+#ifdef CONFIG_OF
+static const struct of_device_id ssp_of_match[] = {
+ {
+ .compatible = "samsung,sensorhub-rinato",
+ .data = &ssp_rinato_info,
+ }, {
+ .compatible = "samsung,sensorhub-thermostat",
+ .data = &ssp_thermostat_info,
+ },
+ {},
+};
+MODULE_DEVICE_TABLE(of, ssp_of_match);
+
+static struct ssp_data *ssp_parse_dt(struct device *dev)
+{
+ int ret;
+ struct ssp_data *data;
+ struct device_node *node = dev->of_node;
+ const struct of_device_id *match;
+
+ data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return NULL;
+
+ data->mcu_ap_gpio = of_get_named_gpio(node, "mcu-ap-gpios", 0);
+ if (data->mcu_ap_gpio < 0)
+ goto err_free_pd;
+
+ data->ap_mcu_gpio = of_get_named_gpio(node, "ap-mcu-gpios", 0);
+ if (data->ap_mcu_gpio < 0)
+ goto err_free_pd;
+
+ data->mcu_reset_gpio = of_get_named_gpio(node, "mcu-reset-gpios", 0);
+ if (data->mcu_reset_gpio < 0)
+ goto err_free_pd;
+
+ ret = devm_gpio_request_one(dev, data->ap_mcu_gpio, GPIOF_OUT_INIT_HIGH,
+ "ap-mcu-gpios");
+ if (ret)
+ goto err_free_pd;
+
+ ret = devm_gpio_request_one(dev, data->mcu_reset_gpio,
+ GPIOF_OUT_INIT_HIGH, "mcu-reset-gpios");
+ if (ret)
+ goto err_ap_mcu;
+
+ match = of_match_node(ssp_of_match, node);
+ if (!match)
+ goto err_mcu_reset_gpio;
+
+ data->sensorhub_info = (struct ssp_sensorhub_info *)match->data;
+
+ dev_set_drvdata(dev, data);
+
+ return data;
+
+err_mcu_reset_gpio:
+ devm_gpio_free(dev, data->mcu_reset_gpio);
+err_ap_mcu:
+ devm_gpio_free(dev, data->ap_mcu_gpio);
+err_free_pd:
+ devm_kfree(dev, data);
+ return NULL;
+}
+#else
+static struct ssp_data *ssp_parse_dt(struct device *pdev)
+{
+ return NULL;
+}
+#endif
+
+/**
+ * ssp_register_consumer() - registers iio consumer in ssp framework
+ *
+ * @indio_dev: consumer iio device
+ * @type: ssp sensor type
+ */
+void ssp_register_consumer(struct iio_dev *indio_dev, enum ssp_sensor_type type)
+{
+ struct ssp_data *data = dev_get_drvdata(indio_dev->dev.parent->parent);
+
+ data->sensor_devs[type] = indio_dev;
+}
+EXPORT_SYMBOL(ssp_register_consumer);
+
+static int ssp_probe(struct spi_device *spi)
+{
+ int ret, i;
+ struct ssp_data *data;
+
+ data = ssp_parse_dt(&spi->dev);
+ if (!data) {
+ dev_err(&spi->dev, "Failed to find platform data\n");
+ return -ENODEV;
+ }
+
+ ret = mfd_add_devices(&spi->dev, -1, sensorhub_sensor_devs,
+ ARRAY_SIZE(sensorhub_sensor_devs), NULL, 0, NULL);
+ if (ret < 0) {
+ dev_err(&spi->dev, "mfd add devices fail\n");
+ return ret;
+ }
+
+ spi->mode = SPI_MODE_1;
+ ret = spi_setup(spi);
+ if (ret < 0) {
+ dev_err(&spi->dev, "Failed to setup spi\n");
+ return ret;
+ }
+
+ data->fw_dl_state = SSP_FW_DL_STATE_NONE;
+ data->spi = spi;
+ spi_set_drvdata(spi, data);
+
+ mutex_init(&data->comm_lock);
+
+ for (i = 0; i < SSP_SENSOR_MAX; ++i) {
+ data->delay_buf[i] = SSP_DEFAULT_POLLING_DELAY;
+ data->batch_latency_buf[i] = 0;
+ data->batch_opt_buf[i] = 0;
+ data->check_status[i] = SSP_INITIALIZATION_STATE;
+ }
+
+ data->delay_buf[SSP_BIO_HRM_LIB] = 100;
+
+ data->time_syncing = true;
+
+ mutex_init(&data->pending_lock);
+ INIT_LIST_HEAD(&data->pending_list);
+
+ atomic_set(&data->enable_refcount, 0);
+
+ INIT_WORK(&data->work_wdt, ssp_wdt_work_func);
+ INIT_DELAYED_WORK(&data->work_refresh, ssp_refresh_task);
+
+ setup_timer(&data->wdt_timer, ssp_wdt_timer_func, (unsigned long)data);
+
+ ret = request_threaded_irq(data->spi->irq, NULL,
+ ssp_irq_thread_fn,
+ IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
+ "SSP_Int", data);
+ if (ret < 0) {
+ dev_err(&spi->dev, "Irq request fail\n");
+ goto err_setup_irq;
+ }
+
+ /* Let's start with enabled one so irq balance could be ok */
+ data->shut_down = false;
+
+ /* just to avoid unbalanced irq set wake up */
+ enable_irq_wake(data->spi->irq);
+
+ data->fw_dl_state = ssp_check_fwbl(data);
+ if (data->fw_dl_state == SSP_FW_DL_STATE_NONE) {
+ ret = ssp_initialize_mcu(data);
+ if (ret < 0) {
+ dev_err(&spi->dev, "Initialize_mcu failed\n");
+ goto err_read_reg;
+ }
+ } else {
+ dev_err(&spi->dev, "Firmware version not supported\n");
+ ret = -EPERM;
+ goto err_read_reg;
+ }
+
+ return 0;
+
+err_read_reg:
+ free_irq(data->spi->irq, data);
+err_setup_irq:
+ mutex_destroy(&data->pending_lock);
+ mutex_destroy(&data->comm_lock);
+
+ dev_err(&spi->dev, "Probe failed!\n");
+
+ return ret;
+}
+
+static int ssp_remove(struct spi_device *spi)
+{
+ struct ssp_data *data = spi_get_drvdata(spi);
+
+ if (ssp_command(data, SSP_MSG2SSP_AP_STATUS_SHUTDOWN, 0) < 0)
+ dev_err(&data->spi->dev,
+ "SSP_MSG2SSP_AP_STATUS_SHUTDOWN failed\n");
+
+ ssp_enable_mcu(data, false);
+ ssp_disable_wdt_timer(data);
+
+ ssp_clean_pending_list(data);
+
+ free_irq(data->spi->irq, data);
+
+ del_timer_sync(&data->wdt_timer);
+ cancel_work_sync(&data->work_wdt);
+
+ mutex_destroy(&data->comm_lock);
+ mutex_destroy(&data->pending_lock);
+
+ mfd_remove_devices(&spi->dev);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int ssp_suspend(struct device *dev)
+{
+ int ret;
+ struct ssp_data *data = spi_get_drvdata(to_spi_device(dev));
+
+ data->last_resume_state = SSP_MSG2SSP_AP_STATUS_SUSPEND;
+
+ if (atomic_read(&data->enable_refcount) > 0)
+ ssp_disable_wdt_timer(data);
+
+ ret = ssp_command(data, SSP_MSG2SSP_AP_STATUS_SUSPEND, 0);
+ if (ret < 0) {
+ dev_err(&data->spi->dev,
+ "%s SSP_MSG2SSP_AP_STATUS_SUSPEND failed\n", __func__);
+
+ ssp_enable_wdt_timer(data);
+ return ret;
+ }
+
+ data->time_syncing = false;
+ disable_irq(data->spi->irq);
+
+ return 0;
+}
+
+static int ssp_resume(struct device *dev)
+{
+ int ret;
+ struct ssp_data *data = spi_get_drvdata(to_spi_device(dev));
+
+ enable_irq(data->spi->irq);
+
+ if (atomic_read(&data->enable_refcount) > 0)
+ ssp_enable_wdt_timer(data);
+
+ ret = ssp_command(data, SSP_MSG2SSP_AP_STATUS_RESUME, 0);
+ if (ret < 0) {
+ dev_err(&data->spi->dev,
+ "%s SSP_MSG2SSP_AP_STATUS_RESUME failed\n", __func__);
+ ssp_disable_wdt_timer(data);
+ return ret;
+ }
+
+ /* timesyncing is set by MCU */
+ data->last_resume_state = SSP_MSG2SSP_AP_STATUS_RESUME;
+
+ return 0;
+}
+#endif /* CONFIG_PM_SLEEP */
+
+static const struct dev_pm_ops ssp_pm_ops = {
+ SET_SYSTEM_SLEEP_PM_OPS(ssp_suspend, ssp_resume)
+};
+
+static struct spi_driver ssp_driver = {
+ .probe = ssp_probe,
+ .remove = ssp_remove,
+ .driver = {
+ .pm = &ssp_pm_ops,
+ .bus = &spi_bus_type,
+ .owner = THIS_MODULE,
+ .of_match_table = of_match_ptr(ssp_of_match),
+ .name = "sensorhub"
+ },
+};
+
+module_spi_driver(ssp_driver);
+
+MODULE_DESCRIPTION("ssp sensorhub driver");
+MODULE_AUTHOR("Samsung Electronics");
+MODULE_LICENSE("GPL");
diff --git a/drivers/iio/common/ssp_sensors/ssp_iio.c b/drivers/iio/common/ssp_sensors/ssp_iio.c
new file mode 100644
index 000000000..a3ae165f8
--- /dev/null
+++ b/drivers/iio/common/ssp_sensors/ssp_iio.c
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2014, Samsung Electronics Co. Ltd. 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.
+ *
+ */
+
+#include <linux/iio/common/ssp_sensors.h>
+#include <linux/iio/kfifo_buf.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include "ssp_iio_sensor.h"
+
+/**
+ * ssp_common_buffer_postenable() - generic postenable callback for ssp buffer
+ *
+ * @indio_dev: iio device
+ *
+ * Returns 0 or negative value in case of error
+ */
+int ssp_common_buffer_postenable(struct iio_dev *indio_dev)
+{
+ struct ssp_sensor_data *spd = iio_priv(indio_dev);
+ struct ssp_data *data = dev_get_drvdata(indio_dev->dev.parent->parent);
+
+ /* the allocation is made in post because scan size is known in this
+ * moment
+ * */
+ spd->buffer = kmalloc(indio_dev->scan_bytes, GFP_KERNEL | GFP_DMA);
+ if (!spd->buffer)
+ return -ENOMEM;
+
+ return ssp_enable_sensor(data, spd->type,
+ ssp_get_sensor_delay(data, spd->type));
+}
+EXPORT_SYMBOL(ssp_common_buffer_postenable);
+
+/**
+ * ssp_common_buffer_postdisable() - generic postdisable callback for ssp buffer
+ *
+ * @indio_dev: iio device
+ *
+ * Returns 0 or negative value in case of error
+ */
+int ssp_common_buffer_postdisable(struct iio_dev *indio_dev)
+{
+ int ret;
+ struct ssp_sensor_data *spd = iio_priv(indio_dev);
+ struct ssp_data *data = dev_get_drvdata(indio_dev->dev.parent->parent);
+
+ ret = ssp_disable_sensor(data, spd->type);
+ if (ret < 0)
+ return ret;
+
+ kfree(spd->buffer);
+
+ return ret;
+}
+EXPORT_SYMBOL(ssp_common_buffer_postdisable);
+
+/**
+ * ssp_common_process_data() - Common process data callback for ssp sensors
+ *
+ * @indio_dev: iio device
+ * @buf: source buffer
+ * @len: sensor data length
+ * @timestamp: system timestamp
+ *
+ * Returns 0 or negative value in case of error
+ */
+int ssp_common_process_data(struct iio_dev *indio_dev, void *buf,
+ unsigned int len, int64_t timestamp)
+{
+ __le32 time;
+ int64_t calculated_time;
+ struct ssp_sensor_data *spd = iio_priv(indio_dev);
+
+ if (indio_dev->scan_bytes == 0)
+ return 0;
+
+ /*
+ * it always sends full set of samples, remember about available masks
+ */
+ memcpy(spd->buffer, buf, len);
+
+ if (indio_dev->scan_timestamp) {
+ memcpy(&time, &((char *)buf)[len], SSP_TIME_SIZE);
+ calculated_time =
+ timestamp + (int64_t)le32_to_cpu(time) * 1000000;
+ }
+
+ return iio_push_to_buffers_with_timestamp(indio_dev, spd->buffer,
+ calculated_time);
+}
+EXPORT_SYMBOL(ssp_common_process_data);
+
+MODULE_AUTHOR("Karol Wrona <k.wrona@samsung.com>");
+MODULE_DESCRIPTION("Samsung sensorhub commons");
+MODULE_LICENSE("GPL");
diff --git a/drivers/iio/common/ssp_sensors/ssp_iio_sensor.h b/drivers/iio/common/ssp_sensors/ssp_iio_sensor.h
new file mode 100644
index 000000000..541c6590d
--- /dev/null
+++ b/drivers/iio/common/ssp_sensors/ssp_iio_sensor.h
@@ -0,0 +1,71 @@
+#ifndef __SSP_IIO_SENSOR_H__
+#define __SSP_IIO_SENSOR_H__
+
+#define SSP_CHANNEL_AG(_type, _mod, _index) \
+{ \
+ .type = _type,\
+ .modified = 1,\
+ .channel2 = _mod,\
+ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SAMP_FREQ),\
+ .scan_index = _index,\
+ .scan_type = {\
+ .sign = 's',\
+ .realbits = 16,\
+ .storagebits = 16,\
+ .shift = 0,\
+ .endianness = IIO_LE,\
+ },\
+}
+
+/* It is defined here as it is a mixed timestamp */
+#define SSP_CHAN_TIMESTAMP(_si) { \
+ .type = IIO_TIMESTAMP, \
+ .channel = -1, \
+ .scan_index = _si, \
+ .scan_type = { \
+ .sign = 's', \
+ .realbits = 64, \
+ .storagebits = 64, \
+ }, \
+}
+
+#define SSP_MS_PER_S 1000
+#define SSP_INVERTED_SCALING_FACTOR 1000000U
+
+#define SSP_FACTOR_WITH_MS \
+ (SSP_INVERTED_SCALING_FACTOR * SSP_MS_PER_S)
+
+int ssp_common_buffer_postenable(struct iio_dev *indio_dev);
+
+int ssp_common_buffer_postdisable(struct iio_dev *indio_dev);
+
+int ssp_common_process_data(struct iio_dev *indio_dev, void *buf,
+ unsigned int len, int64_t timestamp);
+
+/* Converts time in ms to frequency */
+static inline void ssp_convert_to_freq(u32 time, int *integer_part,
+ int *fractional)
+{
+ if (time == 0) {
+ *fractional = 0;
+ *integer_part = 0;
+ return;
+ }
+
+ *integer_part = SSP_FACTOR_WITH_MS / time;
+ *fractional = *integer_part % SSP_INVERTED_SCALING_FACTOR;
+ *integer_part = *integer_part / SSP_INVERTED_SCALING_FACTOR;
+}
+
+/* Converts frequency to time in ms */
+static inline int ssp_convert_to_time(int integer_part, int fractional)
+{
+ u64 value;
+
+ value = (u64)integer_part * SSP_INVERTED_SCALING_FACTOR + fractional;
+ if (value == 0)
+ return 0;
+
+ return div64_u64((u64)SSP_FACTOR_WITH_MS, value);
+}
+#endif /* __SSP_IIO_SENSOR_H__ */
diff --git a/drivers/iio/common/ssp_sensors/ssp_spi.c b/drivers/iio/common/ssp_sensors/ssp_spi.c
new file mode 100644
index 000000000..704284a47
--- /dev/null
+++ b/drivers/iio/common/ssp_sensors/ssp_spi.c
@@ -0,0 +1,608 @@
+/*
+ * Copyright (C) 2014, Samsung Electronics Co. Ltd. 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.
+ *
+ */
+
+#include "ssp.h"
+
+#define SSP_DEV (&data->spi->dev)
+#define SSP_GET_MESSAGE_TYPE(data) (data & (3 << SSP_RW))
+
+/*
+ * SSP -> AP Instruction
+ * They tell what packet type can be expected. In the future there will
+ * be less of them. BYPASS means common sensor packets with accel, gyro,
+ * hrm etc. data. LIBRARY and META are mock-up's for now.
+ */
+#define SSP_MSG2AP_INST_BYPASS_DATA 0x37
+#define SSP_MSG2AP_INST_LIBRARY_DATA 0x01
+#define SSP_MSG2AP_INST_DEBUG_DATA 0x03
+#define SSP_MSG2AP_INST_BIG_DATA 0x04
+#define SSP_MSG2AP_INST_META_DATA 0x05
+#define SSP_MSG2AP_INST_TIME_SYNC 0x06
+#define SSP_MSG2AP_INST_RESET 0x07
+
+#define SSP_UNIMPLEMENTED -1
+
+struct ssp_msg_header {
+ u8 cmd;
+ __le16 length;
+ __le16 options;
+ __le32 data;
+} __attribute__((__packed__));
+
+struct ssp_msg {
+ u16 length;
+ u16 options;
+ struct list_head list;
+ struct completion *done;
+ struct ssp_msg_header *h;
+ char *buffer;
+};
+
+static const int ssp_offset_map[SSP_SENSOR_MAX] = {
+ [SSP_ACCELEROMETER_SENSOR] = SSP_ACCELEROMETER_SIZE +
+ SSP_TIME_SIZE,
+ [SSP_GYROSCOPE_SENSOR] = SSP_GYROSCOPE_SIZE +
+ SSP_TIME_SIZE,
+ [SSP_GEOMAGNETIC_UNCALIB_SENSOR] = SSP_UNIMPLEMENTED,
+ [SSP_GEOMAGNETIC_RAW] = SSP_UNIMPLEMENTED,
+ [SSP_GEOMAGNETIC_SENSOR] = SSP_UNIMPLEMENTED,
+ [SSP_PRESSURE_SENSOR] = SSP_UNIMPLEMENTED,
+ [SSP_GESTURE_SENSOR] = SSP_UNIMPLEMENTED,
+ [SSP_PROXIMITY_SENSOR] = SSP_UNIMPLEMENTED,
+ [SSP_TEMPERATURE_HUMIDITY_SENSOR] = SSP_UNIMPLEMENTED,
+ [SSP_LIGHT_SENSOR] = SSP_UNIMPLEMENTED,
+ [SSP_PROXIMITY_RAW] = SSP_UNIMPLEMENTED,
+ [SSP_ORIENTATION_SENSOR] = SSP_UNIMPLEMENTED,
+ [SSP_STEP_DETECTOR] = SSP_UNIMPLEMENTED,
+ [SSP_SIG_MOTION_SENSOR] = SSP_UNIMPLEMENTED,
+ [SSP_GYRO_UNCALIB_SENSOR] = SSP_UNIMPLEMENTED,
+ [SSP_GAME_ROTATION_VECTOR] = SSP_UNIMPLEMENTED,
+ [SSP_ROTATION_VECTOR] = SSP_UNIMPLEMENTED,
+ [SSP_STEP_COUNTER] = SSP_UNIMPLEMENTED,
+ [SSP_BIO_HRM_RAW] = SSP_BIO_HRM_RAW_SIZE +
+ SSP_TIME_SIZE,
+ [SSP_BIO_HRM_RAW_FAC] = SSP_BIO_HRM_RAW_FAC_SIZE +
+ SSP_TIME_SIZE,
+ [SSP_BIO_HRM_LIB] = SSP_BIO_HRM_LIB_SIZE +
+ SSP_TIME_SIZE,
+};
+
+#define SSP_HEADER_SIZE (sizeof(struct ssp_msg_header))
+#define SSP_HEADER_SIZE_ALIGNED (ALIGN(SSP_HEADER_SIZE, 4))
+
+static struct ssp_msg *ssp_create_msg(u8 cmd, u16 len, u16 opt, u32 data)
+{
+ struct ssp_msg_header h;
+ struct ssp_msg *msg;
+
+ msg = kzalloc(sizeof(*msg), GFP_KERNEL);
+ if (!msg)
+ return NULL;
+
+ h.cmd = cmd;
+ h.length = cpu_to_le16(len);
+ h.options = cpu_to_le16(opt);
+ h.data = cpu_to_le32(data);
+
+ msg->buffer = kzalloc(SSP_HEADER_SIZE_ALIGNED + len,
+ GFP_KERNEL | GFP_DMA);
+ if (!msg->buffer) {
+ kfree(msg);
+ return NULL;
+ }
+
+ msg->length = len;
+ msg->options = opt;
+
+ memcpy(msg->buffer, &h, SSP_HEADER_SIZE);
+
+ return msg;
+}
+
+/*
+ * It is a bit heavy to do it this way but often the function is used to compose
+ * the message from smaller chunks which are placed on the stack. Often the
+ * chunks are small so memcpy should be optimalized.
+ */
+static inline void ssp_fill_buffer(struct ssp_msg *m, unsigned int offset,
+ const void *src, unsigned int len)
+{
+ memcpy(&m->buffer[SSP_HEADER_SIZE_ALIGNED + offset], src, len);
+}
+
+static inline void ssp_get_buffer(struct ssp_msg *m, unsigned int offset,
+ void *dest, unsigned int len)
+{
+ memcpy(dest, &m->buffer[SSP_HEADER_SIZE_ALIGNED + offset], len);
+}
+
+#define SSP_GET_BUFFER_AT_INDEX(m, index) \
+ (m->buffer[SSP_HEADER_SIZE_ALIGNED + index])
+#define SSP_SET_BUFFER_AT_INDEX(m, index, val) \
+ (m->buffer[SSP_HEADER_SIZE_ALIGNED + index] = val)
+
+static void ssp_clean_msg(struct ssp_msg *m)
+{
+ kfree(m->buffer);
+ kfree(m);
+}
+
+static int ssp_print_mcu_debug(char *data_frame, int *data_index,
+ int received_len)
+{
+ int length = data_frame[(*data_index)++];
+
+ if (length > received_len - *data_index || length <= 0) {
+ ssp_dbg("[SSP]: MSG From MCU-invalid debug length(%d/%d)\n",
+ length, received_len);
+ return length ? length : -EPROTO;
+ }
+
+ ssp_dbg("[SSP]: MSG From MCU - %s\n", &data_frame[*data_index]);
+
+ *data_index += length;
+
+ return 0;
+}
+
+/*
+ * It was designed that way - additional lines to some kind of handshake,
+ * please do not ask why - only the firmware guy can know it.
+ */
+static int ssp_check_lines(struct ssp_data *data, bool state)
+{
+ int delay_cnt = 0;
+
+ gpio_set_value_cansleep(data->ap_mcu_gpio, state);
+
+ while (gpio_get_value_cansleep(data->mcu_ap_gpio) != state) {
+ usleep_range(3000, 3500);
+
+ if (data->shut_down || delay_cnt++ > 500) {
+ dev_err(SSP_DEV, "%s:timeout, hw ack wait fail %d\n",
+ __func__, state);
+
+ if (!state)
+ gpio_set_value_cansleep(data->ap_mcu_gpio, 1);
+
+ return -ETIMEDOUT;
+ }
+ }
+
+ return 0;
+}
+
+static int ssp_do_transfer(struct ssp_data *data, struct ssp_msg *msg,
+ struct completion *done, int timeout)
+{
+ int status;
+ /*
+ * check if this is a short one way message or the whole transfer has
+ * second part after an interrupt
+ */
+ const bool use_no_irq = msg->length == 0;
+
+ if (data->shut_down)
+ return -EPERM;
+
+ msg->done = done;
+
+ mutex_lock(&data->comm_lock);
+
+ status = ssp_check_lines(data, false);
+ if (status < 0)
+ goto _error_locked;
+
+ status = spi_write(data->spi, msg->buffer, SSP_HEADER_SIZE);
+ if (status < 0) {
+ gpio_set_value_cansleep(data->ap_mcu_gpio, 1);
+ dev_err(SSP_DEV, "%s spi_write fail\n", __func__);
+ goto _error_locked;
+ }
+
+ if (!use_no_irq) {
+ mutex_lock(&data->pending_lock);
+ list_add_tail(&msg->list, &data->pending_list);
+ mutex_unlock(&data->pending_lock);
+ }
+
+ status = ssp_check_lines(data, true);
+ if (status < 0) {
+ if (!use_no_irq) {
+ mutex_lock(&data->pending_lock);
+ list_del(&msg->list);
+ mutex_unlock(&data->pending_lock);
+ }
+ goto _error_locked;
+ }
+
+ mutex_unlock(&data->comm_lock);
+
+ if (!use_no_irq && done)
+ if (wait_for_completion_timeout(done,
+ msecs_to_jiffies(timeout)) ==
+ 0) {
+ mutex_lock(&data->pending_lock);
+ list_del(&msg->list);
+ mutex_unlock(&data->pending_lock);
+
+ data->timeout_cnt++;
+ return -ETIMEDOUT;
+ }
+
+ return 0;
+
+_error_locked:
+ mutex_unlock(&data->comm_lock);
+ data->timeout_cnt++;
+ return status;
+}
+
+static inline int ssp_spi_sync_command(struct ssp_data *data,
+ struct ssp_msg *msg)
+{
+ return ssp_do_transfer(data, msg, NULL, 0);
+}
+
+static int ssp_spi_sync(struct ssp_data *data, struct ssp_msg *msg,
+ int timeout)
+{
+ DECLARE_COMPLETION_ONSTACK(done);
+
+ if (WARN_ON(!msg->length))
+ return -EPERM;
+
+ return ssp_do_transfer(data, msg, &done, timeout);
+}
+
+static int ssp_handle_big_data(struct ssp_data *data, char *dataframe, int *idx)
+{
+ /* mock-up, it will be changed with adding another sensor types */
+ *idx += 8;
+ return 0;
+}
+
+static int ssp_parse_dataframe(struct ssp_data *data, char *dataframe, int len)
+{
+ int idx, sd;
+ struct timespec ts;
+ struct ssp_sensor_data *spd;
+ struct iio_dev **indio_devs = data->sensor_devs;
+
+ getnstimeofday(&ts);
+
+ for (idx = 0; idx < len;) {
+ switch (dataframe[idx++]) {
+ case SSP_MSG2AP_INST_BYPASS_DATA:
+ sd = dataframe[idx++];
+ if (sd < 0 || sd >= SSP_SENSOR_MAX) {
+ dev_err(SSP_DEV,
+ "Mcu data frame1 error %d\n", sd);
+ return -EPROTO;
+ }
+
+ if (indio_devs[sd]) {
+ spd = iio_priv(indio_devs[sd]);
+ if (spd->process_data)
+ spd->process_data(indio_devs[sd],
+ &dataframe[idx],
+ data->timestamp);
+ } else {
+ dev_err(SSP_DEV, "no client for frame\n");
+ }
+
+ idx += ssp_offset_map[sd];
+ break;
+ case SSP_MSG2AP_INST_DEBUG_DATA:
+ sd = ssp_print_mcu_debug(dataframe, &idx, len);
+ if (sd) {
+ dev_err(SSP_DEV,
+ "Mcu data frame3 error %d\n", sd);
+ return sd;
+ }
+ break;
+ case SSP_MSG2AP_INST_LIBRARY_DATA:
+ idx += len;
+ break;
+ case SSP_MSG2AP_INST_BIG_DATA:
+ ssp_handle_big_data(data, dataframe, &idx);
+ break;
+ case SSP_MSG2AP_INST_TIME_SYNC:
+ data->time_syncing = true;
+ break;
+ case SSP_MSG2AP_INST_RESET:
+ ssp_queue_ssp_refresh_task(data, 0);
+ break;
+ }
+ }
+
+ if (data->time_syncing)
+ data->timestamp = ts.tv_sec * 1000000000ULL + ts.tv_nsec;
+
+ return 0;
+}
+
+/* threaded irq */
+int ssp_irq_msg(struct ssp_data *data)
+{
+ bool found = false;
+ char *buffer;
+ u8 msg_type;
+ int ret;
+ u16 length, msg_options;
+ struct ssp_msg *msg, *n;
+
+ ret = spi_read(data->spi, data->header_buffer, SSP_HEADER_BUFFER_SIZE);
+ if (ret < 0) {
+ dev_err(SSP_DEV, "header read fail\n");
+ return ret;
+ }
+
+ length = le16_to_cpu(data->header_buffer[1]);
+ msg_options = le16_to_cpu(data->header_buffer[0]);
+
+ if (length == 0) {
+ dev_err(SSP_DEV, "length received from mcu is 0\n");
+ return -EINVAL;
+ }
+
+ msg_type = SSP_GET_MESSAGE_TYPE(msg_options);
+
+ switch (msg_type) {
+ case SSP_AP2HUB_READ:
+ case SSP_AP2HUB_WRITE:
+ /*
+ * this is a small list, a few elements - the packets can be
+ * received with no order
+ */
+ mutex_lock(&data->pending_lock);
+ list_for_each_entry_safe(msg, n, &data->pending_list, list) {
+ if (msg->options == msg_options) {
+ list_del(&msg->list);
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ /*
+ * here can be implemented dead messages handling
+ * but the slave should not send such ones - it is to
+ * check but let's handle this
+ */
+ buffer = kmalloc(length, GFP_KERNEL | GFP_DMA);
+ if (!buffer) {
+ ret = -ENOMEM;
+ goto _unlock;
+ }
+
+ /* got dead packet so it is always an error */
+ ret = spi_read(data->spi, buffer, length);
+ if (ret >= 0)
+ ret = -EPROTO;
+
+ kfree(buffer);
+
+ dev_err(SSP_DEV, "No match error %x\n",
+ msg_options);
+
+ goto _unlock;
+ }
+
+ if (msg_type == SSP_AP2HUB_READ)
+ ret = spi_read(data->spi,
+ &msg->buffer[SSP_HEADER_SIZE_ALIGNED],
+ msg->length);
+
+ if (msg_type == SSP_AP2HUB_WRITE) {
+ ret = spi_write(data->spi,
+ &msg->buffer[SSP_HEADER_SIZE_ALIGNED],
+ msg->length);
+ if (msg_options & SSP_AP2HUB_RETURN) {
+ msg->options =
+ SSP_AP2HUB_READ | SSP_AP2HUB_RETURN;
+ msg->length = 1;
+
+ list_add_tail(&msg->list, &data->pending_list);
+ goto _unlock;
+ }
+ }
+
+ if (msg->done)
+ if (!completion_done(msg->done))
+ complete(msg->done);
+_unlock:
+ mutex_unlock(&data->pending_lock);
+ break;
+ case SSP_HUB2AP_WRITE:
+ buffer = kzalloc(length, GFP_KERNEL | GFP_DMA);
+ if (!buffer)
+ return -ENOMEM;
+
+ ret = spi_read(data->spi, buffer, length);
+ if (ret < 0) {
+ dev_err(SSP_DEV, "spi read fail\n");
+ kfree(buffer);
+ break;
+ }
+
+ ret = ssp_parse_dataframe(data, buffer, length);
+
+ kfree(buffer);
+ break;
+
+ default:
+ dev_err(SSP_DEV, "unknown msg type\n");
+ return -EPROTO;
+ }
+
+ return ret;
+}
+
+void ssp_clean_pending_list(struct ssp_data *data)
+{
+ struct ssp_msg *msg, *n;
+
+ mutex_lock(&data->pending_lock);
+ list_for_each_entry_safe(msg, n, &data->pending_list, list) {
+ list_del(&msg->list);
+
+ if (msg->done)
+ if (!completion_done(msg->done))
+ complete(msg->done);
+ }
+ mutex_unlock(&data->pending_lock);
+}
+
+int ssp_command(struct ssp_data *data, char command, int arg)
+{
+ int ret;
+ struct ssp_msg *msg;
+
+ msg = ssp_create_msg(command, 0, SSP_AP2HUB_WRITE, arg);
+ if (!msg)
+ return -ENOMEM;
+
+ ssp_dbg("%s - command 0x%x %d\n", __func__, command, arg);
+
+ ret = ssp_spi_sync_command(data, msg);
+ ssp_clean_msg(msg);
+
+ return ret;
+}
+
+int ssp_send_instruction(struct ssp_data *data, u8 inst, u8 sensor_type,
+ u8 *send_buf, u8 length)
+{
+ int ret;
+ struct ssp_msg *msg;
+
+ if (data->fw_dl_state == SSP_FW_DL_STATE_DOWNLOADING) {
+ dev_err(SSP_DEV, "%s - Skip Inst! DL state = %d\n",
+ __func__, data->fw_dl_state);
+ return -EBUSY;
+ } else if (!(data->available_sensors & BIT(sensor_type)) &&
+ (inst <= SSP_MSG2SSP_INST_CHANGE_DELAY)) {
+ dev_err(SSP_DEV, "%s - Bypass Inst Skip! - %u\n",
+ __func__, sensor_type);
+ return -EIO; /* just fail */
+ }
+
+ msg = ssp_create_msg(inst, length + 2, SSP_AP2HUB_WRITE, 0);
+ if (!msg)
+ return -ENOMEM;
+
+ ssp_fill_buffer(msg, 0, &sensor_type, 1);
+ ssp_fill_buffer(msg, 1, send_buf, length);
+
+ ssp_dbg("%s - Inst = 0x%x, Sensor Type = 0x%x, data = %u\n",
+ __func__, inst, sensor_type, send_buf[1]);
+
+ ret = ssp_spi_sync(data, msg, 1000);
+ ssp_clean_msg(msg);
+
+ return ret;
+}
+
+int ssp_get_chipid(struct ssp_data *data)
+{
+ int ret;
+ char buffer;
+ struct ssp_msg *msg;
+
+ msg = ssp_create_msg(SSP_MSG2SSP_AP_WHOAMI, 1, SSP_AP2HUB_READ, 0);
+ if (!msg)
+ return -ENOMEM;
+
+ ret = ssp_spi_sync(data, msg, 1000);
+
+ buffer = SSP_GET_BUFFER_AT_INDEX(msg, 0);
+
+ ssp_clean_msg(msg);
+
+ return ret < 0 ? ret : buffer;
+}
+
+int ssp_set_magnetic_matrix(struct ssp_data *data)
+{
+ int ret;
+ struct ssp_msg *msg;
+
+ msg = ssp_create_msg(SSP_MSG2SSP_AP_SET_MAGNETIC_STATIC_MATRIX,
+ data->sensorhub_info->mag_length, SSP_AP2HUB_WRITE,
+ 0);
+ if (!msg)
+ return -ENOMEM;
+
+ ssp_fill_buffer(msg, 0, data->sensorhub_info->mag_table,
+ data->sensorhub_info->mag_length);
+
+ ret = ssp_spi_sync(data, msg, 1000);
+ ssp_clean_msg(msg);
+
+ return ret;
+}
+
+unsigned int ssp_get_sensor_scanning_info(struct ssp_data *data)
+{
+ int ret;
+ __le32 result;
+ u32 cpu_result = 0;
+
+ struct ssp_msg *msg = ssp_create_msg(SSP_MSG2SSP_AP_SENSOR_SCANNING, 4,
+ SSP_AP2HUB_READ, 0);
+ if (!msg)
+ return 0;
+
+ ret = ssp_spi_sync(data, msg, 1000);
+ if (ret < 0) {
+ dev_err(SSP_DEV, "%s - spi read fail %d\n", __func__, ret);
+ goto _exit;
+ }
+
+ ssp_get_buffer(msg, 0, &result, 4);
+ cpu_result = le32_to_cpu(result);
+
+ dev_info(SSP_DEV, "%s state: 0x%08x\n", __func__, cpu_result);
+
+_exit:
+ ssp_clean_msg(msg);
+ return cpu_result;
+}
+
+unsigned int ssp_get_firmware_rev(struct ssp_data *data)
+{
+ int ret;
+ __le32 result;
+
+ struct ssp_msg *msg = ssp_create_msg(SSP_MSG2SSP_AP_FIRMWARE_REV, 4,
+ SSP_AP2HUB_READ, 0);
+ if (!msg)
+ return SSP_INVALID_REVISION;
+
+ ret = ssp_spi_sync(data, msg, 1000);
+ if (ret < 0) {
+ dev_err(SSP_DEV, "%s - transfer fail %d\n", __func__, ret);
+ ret = SSP_INVALID_REVISION;
+ goto _exit;
+ }
+
+ ssp_get_buffer(msg, 0, &result, 4);
+ ret = le32_to_cpu(result);
+
+_exit:
+ ssp_clean_msg(msg);
+ return ret;
+}
diff --git a/drivers/iio/common/st_sensors/Kconfig b/drivers/iio/common/st_sensors/Kconfig
new file mode 100644
index 000000000..865f1ca33
--- /dev/null
+++ b/drivers/iio/common/st_sensors/Kconfig
@@ -0,0 +1,14 @@
+#
+# STMicroelectronics sensors common library
+#
+
+config IIO_ST_SENSORS_I2C
+ tristate
+
+config IIO_ST_SENSORS_SPI
+ tristate
+
+config IIO_ST_SENSORS_CORE
+ tristate
+ select IIO_ST_SENSORS_I2C if I2C
+ select IIO_ST_SENSORS_SPI if SPI_MASTER
diff --git a/drivers/iio/common/st_sensors/Makefile b/drivers/iio/common/st_sensors/Makefile
new file mode 100644
index 000000000..9f3e24f30
--- /dev/null
+++ b/drivers/iio/common/st_sensors/Makefile
@@ -0,0 +1,10 @@
+#
+# Makefile for the STMicroelectronics sensor common modules.
+#
+
+obj-$(CONFIG_IIO_ST_SENSORS_I2C) += st_sensors_i2c.o
+obj-$(CONFIG_IIO_ST_SENSORS_SPI) += st_sensors_spi.o
+obj-$(CONFIG_IIO_ST_SENSORS_CORE) += st_sensors.o
+st_sensors-y := st_sensors_core.o
+st_sensors-$(CONFIG_IIO_BUFFER) += st_sensors_buffer.o
+st_sensors-$(CONFIG_IIO_TRIGGER) += st_sensors_trigger.o
diff --git a/drivers/iio/common/st_sensors/st_sensors_buffer.c b/drivers/iio/common/st_sensors/st_sensors_buffer.c
new file mode 100644
index 000000000..e18bc6782
--- /dev/null
+++ b/drivers/iio/common/st_sensors/st_sensors_buffer.c
@@ -0,0 +1,128 @@
+/*
+ * STMicroelectronics sensors buffer library driver
+ *
+ * Copyright 2012-2013 STMicroelectronics Inc.
+ *
+ * Denis Ciocca <denis.ciocca@st.com>
+ *
+ * Licensed under the GPL-2.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/trigger.h>
+#include <linux/interrupt.h>
+#include <linux/iio/buffer.h>
+#include <linux/iio/trigger_consumer.h>
+#include <linux/iio/triggered_buffer.h>
+#include <linux/irqreturn.h>
+
+#include <linux/iio/common/st_sensors.h>
+
+
+int st_sensors_get_buffer_element(struct iio_dev *indio_dev, u8 *buf)
+{
+ u8 *addr;
+ int i, n = 0, len;
+ struct st_sensor_data *sdata = iio_priv(indio_dev);
+ unsigned int num_data_channels = sdata->num_data_channels;
+ unsigned int byte_for_channel =
+ indio_dev->channels[0].scan_type.storagebits >> 3;
+
+ addr = kmalloc(num_data_channels, GFP_KERNEL);
+ if (!addr) {
+ len = -ENOMEM;
+ goto st_sensors_get_buffer_element_error;
+ }
+
+ for (i = 0; i < num_data_channels; i++) {
+ if (test_bit(i, indio_dev->active_scan_mask)) {
+ addr[n] = indio_dev->channels[i].address;
+ n++;
+ }
+ }
+ switch (n) {
+ case 1:
+ len = sdata->tf->read_multiple_byte(&sdata->tb, sdata->dev,
+ addr[0], byte_for_channel, buf, sdata->multiread_bit);
+ break;
+ case 2:
+ if ((addr[1] - addr[0]) == byte_for_channel) {
+ len = sdata->tf->read_multiple_byte(&sdata->tb,
+ sdata->dev, addr[0], byte_for_channel * n,
+ buf, sdata->multiread_bit);
+ } else {
+ u8 *rx_array;
+ rx_array = kmalloc(byte_for_channel * num_data_channels,
+ GFP_KERNEL);
+ if (!rx_array) {
+ len = -ENOMEM;
+ goto st_sensors_free_memory;
+ }
+
+ len = sdata->tf->read_multiple_byte(&sdata->tb,
+ sdata->dev, addr[0],
+ byte_for_channel * num_data_channels,
+ rx_array, sdata->multiread_bit);
+ if (len < 0) {
+ kfree(rx_array);
+ goto st_sensors_free_memory;
+ }
+
+ for (i = 0; i < n * byte_for_channel; i++) {
+ if (i < n)
+ buf[i] = rx_array[i];
+ else
+ buf[i] = rx_array[n + i];
+ }
+ kfree(rx_array);
+ len = byte_for_channel * n;
+ }
+ break;
+ case 3:
+ len = sdata->tf->read_multiple_byte(&sdata->tb, sdata->dev,
+ addr[0], byte_for_channel * num_data_channels,
+ buf, sdata->multiread_bit);
+ break;
+ default:
+ len = -EINVAL;
+ goto st_sensors_free_memory;
+ }
+ if (len != byte_for_channel * n) {
+ len = -EIO;
+ goto st_sensors_free_memory;
+ }
+
+st_sensors_free_memory:
+ kfree(addr);
+st_sensors_get_buffer_element_error:
+ return len;
+}
+EXPORT_SYMBOL(st_sensors_get_buffer_element);
+
+irqreturn_t st_sensors_trigger_handler(int irq, void *p)
+{
+ int len;
+ struct iio_poll_func *pf = p;
+ struct iio_dev *indio_dev = pf->indio_dev;
+ struct st_sensor_data *sdata = iio_priv(indio_dev);
+
+ len = st_sensors_get_buffer_element(indio_dev, sdata->buffer_data);
+ if (len < 0)
+ goto st_sensors_get_buffer_element_error;
+
+ iio_push_to_buffers_with_timestamp(indio_dev, sdata->buffer_data,
+ pf->timestamp);
+
+st_sensors_get_buffer_element_error:
+ iio_trigger_notify_done(indio_dev->trig);
+
+ return IRQ_HANDLED;
+}
+EXPORT_SYMBOL(st_sensors_trigger_handler);
+
+MODULE_AUTHOR("Denis Ciocca <denis.ciocca@st.com>");
+MODULE_DESCRIPTION("STMicroelectronics ST-sensors buffer");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/iio/common/st_sensors/st_sensors_core.c b/drivers/iio/common/st_sensors/st_sensors_core.c
new file mode 100644
index 000000000..8dd0477e2
--- /dev/null
+++ b/drivers/iio/common/st_sensors/st_sensors_core.c
@@ -0,0 +1,556 @@
+/*
+ * STMicroelectronics sensors core library driver
+ *
+ * Copyright 2012-2013 STMicroelectronics Inc.
+ *
+ * Denis Ciocca <denis.ciocca@st.com>
+ *
+ * Licensed under the GPL-2.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include <linux/iio/iio.h>
+#include <linux/regulator/consumer.h>
+#include <linux/of.h>
+#include <asm/unaligned.h>
+#include <linux/iio/common/st_sensors.h>
+
+
+#define ST_SENSORS_WAI_ADDRESS 0x0f
+
+static inline u32 st_sensors_get_unaligned_le24(const u8 *p)
+{
+ return (s32)((p[0] | p[1] << 8 | p[2] << 16) << 8) >> 8;
+}
+
+static int st_sensors_write_data_with_mask(struct iio_dev *indio_dev,
+ u8 reg_addr, u8 mask, u8 data)
+{
+ int err;
+ u8 new_data;
+ struct st_sensor_data *sdata = iio_priv(indio_dev);
+
+ err = sdata->tf->read_byte(&sdata->tb, sdata->dev, reg_addr, &new_data);
+ if (err < 0)
+ goto st_sensors_write_data_with_mask_error;
+
+ new_data = ((new_data & (~mask)) | ((data << __ffs(mask)) & mask));
+ err = sdata->tf->write_byte(&sdata->tb, sdata->dev, reg_addr, new_data);
+
+st_sensors_write_data_with_mask_error:
+ return err;
+}
+
+static int st_sensors_match_odr(struct st_sensor_settings *sensor_settings,
+ unsigned int odr, struct st_sensor_odr_avl *odr_out)
+{
+ int i, ret = -EINVAL;
+
+ for (i = 0; i < ST_SENSORS_ODR_LIST_MAX; i++) {
+ if (sensor_settings->odr.odr_avl[i].hz == 0)
+ goto st_sensors_match_odr_error;
+
+ if (sensor_settings->odr.odr_avl[i].hz == odr) {
+ odr_out->hz = sensor_settings->odr.odr_avl[i].hz;
+ odr_out->value = sensor_settings->odr.odr_avl[i].value;
+ ret = 0;
+ break;
+ }
+ }
+
+st_sensors_match_odr_error:
+ return ret;
+}
+
+int st_sensors_set_odr(struct iio_dev *indio_dev, unsigned int odr)
+{
+ int err;
+ struct st_sensor_odr_avl odr_out = {0, 0};
+ struct st_sensor_data *sdata = iio_priv(indio_dev);
+
+ err = st_sensors_match_odr(sdata->sensor_settings, odr, &odr_out);
+ if (err < 0)
+ goto st_sensors_match_odr_error;
+
+ if ((sdata->sensor_settings->odr.addr ==
+ sdata->sensor_settings->pw.addr) &&
+ (sdata->sensor_settings->odr.mask ==
+ sdata->sensor_settings->pw.mask)) {
+ if (sdata->enabled == true) {
+ err = st_sensors_write_data_with_mask(indio_dev,
+ sdata->sensor_settings->odr.addr,
+ sdata->sensor_settings->odr.mask,
+ odr_out.value);
+ } else {
+ err = 0;
+ }
+ } else {
+ err = st_sensors_write_data_with_mask(indio_dev,
+ sdata->sensor_settings->odr.addr,
+ sdata->sensor_settings->odr.mask,
+ odr_out.value);
+ }
+ if (err >= 0)
+ sdata->odr = odr_out.hz;
+
+st_sensors_match_odr_error:
+ return err;
+}
+EXPORT_SYMBOL(st_sensors_set_odr);
+
+static int st_sensors_match_fs(struct st_sensor_settings *sensor_settings,
+ unsigned int fs, int *index_fs_avl)
+{
+ int i, ret = -EINVAL;
+
+ for (i = 0; i < ST_SENSORS_FULLSCALE_AVL_MAX; i++) {
+ if (sensor_settings->fs.fs_avl[i].num == 0)
+ goto st_sensors_match_odr_error;
+
+ if (sensor_settings->fs.fs_avl[i].num == fs) {
+ *index_fs_avl = i;
+ ret = 0;
+ break;
+ }
+ }
+
+st_sensors_match_odr_error:
+ return ret;
+}
+
+static int st_sensors_set_fullscale(struct iio_dev *indio_dev, unsigned int fs)
+{
+ int err, i = 0;
+ struct st_sensor_data *sdata = iio_priv(indio_dev);
+
+ err = st_sensors_match_fs(sdata->sensor_settings, fs, &i);
+ if (err < 0)
+ goto st_accel_set_fullscale_error;
+
+ err = st_sensors_write_data_with_mask(indio_dev,
+ sdata->sensor_settings->fs.addr,
+ sdata->sensor_settings->fs.mask,
+ sdata->sensor_settings->fs.fs_avl[i].value);
+ if (err < 0)
+ goto st_accel_set_fullscale_error;
+
+ sdata->current_fullscale = (struct st_sensor_fullscale_avl *)
+ &sdata->sensor_settings->fs.fs_avl[i];
+ return err;
+
+st_accel_set_fullscale_error:
+ dev_err(&indio_dev->dev, "failed to set new fullscale.\n");
+ return err;
+}
+
+int st_sensors_set_enable(struct iio_dev *indio_dev, bool enable)
+{
+ u8 tmp_value;
+ int err = -EINVAL;
+ bool found = false;
+ struct st_sensor_odr_avl odr_out = {0, 0};
+ struct st_sensor_data *sdata = iio_priv(indio_dev);
+
+ if (enable) {
+ tmp_value = sdata->sensor_settings->pw.value_on;
+ if ((sdata->sensor_settings->odr.addr ==
+ sdata->sensor_settings->pw.addr) &&
+ (sdata->sensor_settings->odr.mask ==
+ sdata->sensor_settings->pw.mask)) {
+ err = st_sensors_match_odr(sdata->sensor_settings,
+ sdata->odr, &odr_out);
+ if (err < 0)
+ goto set_enable_error;
+ tmp_value = odr_out.value;
+ found = true;
+ }
+ err = st_sensors_write_data_with_mask(indio_dev,
+ sdata->sensor_settings->pw.addr,
+ sdata->sensor_settings->pw.mask, tmp_value);
+ if (err < 0)
+ goto set_enable_error;
+
+ sdata->enabled = true;
+
+ if (found)
+ sdata->odr = odr_out.hz;
+ } else {
+ err = st_sensors_write_data_with_mask(indio_dev,
+ sdata->sensor_settings->pw.addr,
+ sdata->sensor_settings->pw.mask,
+ sdata->sensor_settings->pw.value_off);
+ if (err < 0)
+ goto set_enable_error;
+
+ sdata->enabled = false;
+ }
+
+set_enable_error:
+ return err;
+}
+EXPORT_SYMBOL(st_sensors_set_enable);
+
+int st_sensors_set_axis_enable(struct iio_dev *indio_dev, u8 axis_enable)
+{
+ struct st_sensor_data *sdata = iio_priv(indio_dev);
+
+ return st_sensors_write_data_with_mask(indio_dev,
+ sdata->sensor_settings->enable_axis.addr,
+ sdata->sensor_settings->enable_axis.mask,
+ axis_enable);
+}
+EXPORT_SYMBOL(st_sensors_set_axis_enable);
+
+void st_sensors_power_enable(struct iio_dev *indio_dev)
+{
+ struct st_sensor_data *pdata = iio_priv(indio_dev);
+ int err;
+
+ /* Regulators not mandatory, but if requested we should enable them. */
+ pdata->vdd = devm_regulator_get_optional(indio_dev->dev.parent, "vdd");
+ if (!IS_ERR(pdata->vdd)) {
+ err = regulator_enable(pdata->vdd);
+ if (err != 0)
+ dev_warn(&indio_dev->dev,
+ "Failed to enable specified Vdd supply\n");
+ }
+
+ pdata->vdd_io = devm_regulator_get_optional(indio_dev->dev.parent, "vddio");
+ if (!IS_ERR(pdata->vdd_io)) {
+ err = regulator_enable(pdata->vdd_io);
+ if (err != 0)
+ dev_warn(&indio_dev->dev,
+ "Failed to enable specified Vdd_IO supply\n");
+ }
+}
+EXPORT_SYMBOL(st_sensors_power_enable);
+
+void st_sensors_power_disable(struct iio_dev *indio_dev)
+{
+ struct st_sensor_data *pdata = iio_priv(indio_dev);
+
+ if (!IS_ERR(pdata->vdd))
+ regulator_disable(pdata->vdd);
+
+ if (!IS_ERR(pdata->vdd_io))
+ regulator_disable(pdata->vdd_io);
+}
+EXPORT_SYMBOL(st_sensors_power_disable);
+
+static int st_sensors_set_drdy_int_pin(struct iio_dev *indio_dev,
+ struct st_sensors_platform_data *pdata)
+{
+ struct st_sensor_data *sdata = iio_priv(indio_dev);
+
+ switch (pdata->drdy_int_pin) {
+ case 1:
+ if (sdata->sensor_settings->drdy_irq.mask_int1 == 0) {
+ dev_err(&indio_dev->dev,
+ "DRDY on INT1 not available.\n");
+ return -EINVAL;
+ }
+ sdata->drdy_int_pin = 1;
+ break;
+ case 2:
+ if (sdata->sensor_settings->drdy_irq.mask_int2 == 0) {
+ dev_err(&indio_dev->dev,
+ "DRDY on INT2 not available.\n");
+ return -EINVAL;
+ }
+ sdata->drdy_int_pin = 2;
+ break;
+ default:
+ dev_err(&indio_dev->dev, "DRDY on pdata not valid.\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+#ifdef CONFIG_OF
+static struct st_sensors_platform_data *st_sensors_of_probe(struct device *dev,
+ struct st_sensors_platform_data *defdata)
+{
+ struct st_sensors_platform_data *pdata;
+ struct device_node *np = dev->of_node;
+ u32 val;
+
+ if (!np)
+ return NULL;
+
+ pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL);
+ if (!of_property_read_u32(np, "st,drdy-int-pin", &val) && (val <= 2))
+ pdata->drdy_int_pin = (u8) val;
+ else
+ pdata->drdy_int_pin = defdata ? defdata->drdy_int_pin : 1;
+
+ return pdata;
+}
+#else
+static struct st_sensors_platform_data *st_sensors_of_probe(struct device *dev,
+ struct st_sensors_platform_data *defdata)
+{
+ return NULL;
+}
+#endif
+
+int st_sensors_init_sensor(struct iio_dev *indio_dev,
+ struct st_sensors_platform_data *pdata)
+{
+ struct st_sensor_data *sdata = iio_priv(indio_dev);
+ struct st_sensors_platform_data *of_pdata;
+ int err = 0;
+
+ /* If OF/DT pdata exists, it will take precedence of anything else */
+ of_pdata = st_sensors_of_probe(indio_dev->dev.parent, pdata);
+ if (of_pdata)
+ pdata = of_pdata;
+
+ if (pdata) {
+ err = st_sensors_set_drdy_int_pin(indio_dev, pdata);
+ if (err < 0)
+ return err;
+ }
+
+ err = st_sensors_set_enable(indio_dev, false);
+ if (err < 0)
+ return err;
+
+ if (sdata->current_fullscale) {
+ err = st_sensors_set_fullscale(indio_dev,
+ sdata->current_fullscale->num);
+ if (err < 0)
+ return err;
+ } else
+ dev_info(&indio_dev->dev, "Full-scale not possible\n");
+
+ err = st_sensors_set_odr(indio_dev, sdata->odr);
+ if (err < 0)
+ return err;
+
+ /* set BDU */
+ err = st_sensors_write_data_with_mask(indio_dev,
+ sdata->sensor_settings->bdu.addr,
+ sdata->sensor_settings->bdu.mask, true);
+ if (err < 0)
+ return err;
+
+ err = st_sensors_set_axis_enable(indio_dev, ST_SENSORS_ENABLE_ALL_AXIS);
+
+ return err;
+}
+EXPORT_SYMBOL(st_sensors_init_sensor);
+
+int st_sensors_set_dataready_irq(struct iio_dev *indio_dev, bool enable)
+{
+ int err;
+ u8 drdy_mask;
+ struct st_sensor_data *sdata = iio_priv(indio_dev);
+
+ if (!sdata->sensor_settings->drdy_irq.addr)
+ return 0;
+
+ /* Enable/Disable the interrupt generator 1. */
+ if (sdata->sensor_settings->drdy_irq.ig1.en_addr > 0) {
+ err = st_sensors_write_data_with_mask(indio_dev,
+ sdata->sensor_settings->drdy_irq.ig1.en_addr,
+ sdata->sensor_settings->drdy_irq.ig1.en_mask,
+ (int)enable);
+ if (err < 0)
+ goto st_accel_set_dataready_irq_error;
+ }
+
+ if (sdata->drdy_int_pin == 1)
+ drdy_mask = sdata->sensor_settings->drdy_irq.mask_int1;
+ else
+ drdy_mask = sdata->sensor_settings->drdy_irq.mask_int2;
+
+ /* Enable/Disable the interrupt generator for data ready. */
+ err = st_sensors_write_data_with_mask(indio_dev,
+ sdata->sensor_settings->drdy_irq.addr,
+ drdy_mask, (int)enable);
+
+st_accel_set_dataready_irq_error:
+ return err;
+}
+EXPORT_SYMBOL(st_sensors_set_dataready_irq);
+
+int st_sensors_set_fullscale_by_gain(struct iio_dev *indio_dev, int scale)
+{
+ int err = -EINVAL, i;
+ struct st_sensor_data *sdata = iio_priv(indio_dev);
+
+ for (i = 0; i < ST_SENSORS_FULLSCALE_AVL_MAX; i++) {
+ if ((sdata->sensor_settings->fs.fs_avl[i].gain == scale) &&
+ (sdata->sensor_settings->fs.fs_avl[i].gain != 0)) {
+ err = 0;
+ break;
+ }
+ }
+ if (err < 0)
+ goto st_sensors_match_scale_error;
+
+ err = st_sensors_set_fullscale(indio_dev,
+ sdata->sensor_settings->fs.fs_avl[i].num);
+
+st_sensors_match_scale_error:
+ return err;
+}
+EXPORT_SYMBOL(st_sensors_set_fullscale_by_gain);
+
+static int st_sensors_read_axis_data(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *ch, int *data)
+{
+ int err;
+ u8 *outdata;
+ struct st_sensor_data *sdata = iio_priv(indio_dev);
+ unsigned int byte_for_channel = ch->scan_type.storagebits >> 3;
+
+ outdata = kmalloc(byte_for_channel, GFP_KERNEL);
+ if (!outdata)
+ return -ENOMEM;
+
+ err = sdata->tf->read_multiple_byte(&sdata->tb, sdata->dev,
+ ch->address, byte_for_channel,
+ outdata, sdata->multiread_bit);
+ if (err < 0)
+ goto st_sensors_free_memory;
+
+ if (byte_for_channel == 2)
+ *data = (s16)get_unaligned_le16(outdata);
+ else if (byte_for_channel == 3)
+ *data = (s32)st_sensors_get_unaligned_le24(outdata);
+
+st_sensors_free_memory:
+ kfree(outdata);
+
+ return err;
+}
+
+int st_sensors_read_info_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *ch, int *val)
+{
+ int err;
+ struct st_sensor_data *sdata = iio_priv(indio_dev);
+
+ mutex_lock(&indio_dev->mlock);
+ if (indio_dev->currentmode == INDIO_BUFFER_TRIGGERED) {
+ err = -EBUSY;
+ goto out;
+ } else {
+ err = st_sensors_set_enable(indio_dev, true);
+ if (err < 0)
+ goto out;
+
+ msleep((sdata->sensor_settings->bootime * 1000) / sdata->odr);
+ err = st_sensors_read_axis_data(indio_dev, ch, val);
+ if (err < 0)
+ goto out;
+
+ *val = *val >> ch->scan_type.shift;
+
+ err = st_sensors_set_enable(indio_dev, false);
+ }
+out:
+ mutex_unlock(&indio_dev->mlock);
+
+ return err;
+}
+EXPORT_SYMBOL(st_sensors_read_info_raw);
+
+int st_sensors_check_device_support(struct iio_dev *indio_dev,
+ int num_sensors_list,
+ const struct st_sensor_settings *sensor_settings)
+{
+ u8 wai;
+ int i, n, err;
+ struct st_sensor_data *sdata = iio_priv(indio_dev);
+
+ err = sdata->tf->read_byte(&sdata->tb, sdata->dev,
+ ST_SENSORS_DEFAULT_WAI_ADDRESS, &wai);
+ if (err < 0) {
+ dev_err(&indio_dev->dev, "failed to read Who-Am-I register.\n");
+ goto read_wai_error;
+ }
+
+ for (i = 0; i < num_sensors_list; i++) {
+ if (sensor_settings[i].wai == wai)
+ break;
+ }
+ if (i == num_sensors_list)
+ goto device_not_supported;
+
+ for (n = 0; n < ARRAY_SIZE(sensor_settings[i].sensors_supported); n++) {
+ if (strcmp(indio_dev->name,
+ &sensor_settings[i].sensors_supported[n][0]) == 0)
+ break;
+ }
+ if (n == ARRAY_SIZE(sensor_settings[i].sensors_supported)) {
+ dev_err(&indio_dev->dev, "device name and WhoAmI mismatch.\n");
+ goto sensor_name_mismatch;
+ }
+
+ sdata->sensor_settings =
+ (struct st_sensor_settings *)&sensor_settings[i];
+
+ return i;
+
+device_not_supported:
+ dev_err(&indio_dev->dev, "device not supported: WhoAmI (0x%x).\n", wai);
+sensor_name_mismatch:
+ err = -ENODEV;
+read_wai_error:
+ return err;
+}
+EXPORT_SYMBOL(st_sensors_check_device_support);
+
+ssize_t st_sensors_sysfs_sampling_frequency_avail(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ int i, len = 0;
+ struct iio_dev *indio_dev = dev_get_drvdata(dev);
+ struct st_sensor_data *sdata = iio_priv(indio_dev);
+
+ mutex_lock(&indio_dev->mlock);
+ for (i = 0; i < ST_SENSORS_ODR_LIST_MAX; i++) {
+ if (sdata->sensor_settings->odr.odr_avl[i].hz == 0)
+ break;
+
+ len += scnprintf(buf + len, PAGE_SIZE - len, "%d ",
+ sdata->sensor_settings->odr.odr_avl[i].hz);
+ }
+ mutex_unlock(&indio_dev->mlock);
+ buf[len - 1] = '\n';
+
+ return len;
+}
+EXPORT_SYMBOL(st_sensors_sysfs_sampling_frequency_avail);
+
+ssize_t st_sensors_sysfs_scale_avail(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ int i, len = 0;
+ struct iio_dev *indio_dev = dev_get_drvdata(dev);
+ struct st_sensor_data *sdata = iio_priv(indio_dev);
+
+ mutex_lock(&indio_dev->mlock);
+ for (i = 0; i < ST_SENSORS_FULLSCALE_AVL_MAX; i++) {
+ if (sdata->sensor_settings->fs.fs_avl[i].num == 0)
+ break;
+
+ len += scnprintf(buf + len, PAGE_SIZE - len, "0.%06u ",
+ sdata->sensor_settings->fs.fs_avl[i].gain);
+ }
+ mutex_unlock(&indio_dev->mlock);
+ buf[len - 1] = '\n';
+
+ return len;
+}
+EXPORT_SYMBOL(st_sensors_sysfs_scale_avail);
+
+MODULE_AUTHOR("Denis Ciocca <denis.ciocca@st.com>");
+MODULE_DESCRIPTION("STMicroelectronics ST-sensors core");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/iio/common/st_sensors/st_sensors_i2c.c b/drivers/iio/common/st_sensors/st_sensors_i2c.c
new file mode 100644
index 000000000..98cfee296
--- /dev/null
+++ b/drivers/iio/common/st_sensors/st_sensors_i2c.c
@@ -0,0 +1,112 @@
+/*
+ * STMicroelectronics sensors i2c library driver
+ *
+ * Copyright 2012-2013 STMicroelectronics Inc.
+ *
+ * Denis Ciocca <denis.ciocca@st.com>
+ *
+ * Licensed under the GPL-2.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/iio/iio.h>
+#include <linux/of_device.h>
+
+#include <linux/iio/common/st_sensors_i2c.h>
+
+
+#define ST_SENSORS_I2C_MULTIREAD 0x80
+
+static unsigned int st_sensors_i2c_get_irq(struct iio_dev *indio_dev)
+{
+ struct st_sensor_data *sdata = iio_priv(indio_dev);
+
+ return to_i2c_client(sdata->dev)->irq;
+}
+
+static int st_sensors_i2c_read_byte(struct st_sensor_transfer_buffer *tb,
+ struct device *dev, u8 reg_addr, u8 *res_byte)
+{
+ int err;
+
+ err = i2c_smbus_read_byte_data(to_i2c_client(dev), reg_addr);
+ if (err < 0)
+ goto st_accel_i2c_read_byte_error;
+
+ *res_byte = err & 0xff;
+
+st_accel_i2c_read_byte_error:
+ return err < 0 ? err : 0;
+}
+
+static int st_sensors_i2c_read_multiple_byte(
+ struct st_sensor_transfer_buffer *tb, struct device *dev,
+ u8 reg_addr, int len, u8 *data, bool multiread_bit)
+{
+ if (multiread_bit)
+ reg_addr |= ST_SENSORS_I2C_MULTIREAD;
+
+ return i2c_smbus_read_i2c_block_data(to_i2c_client(dev),
+ reg_addr, len, data);
+}
+
+static int st_sensors_i2c_write_byte(struct st_sensor_transfer_buffer *tb,
+ struct device *dev, u8 reg_addr, u8 data)
+{
+ return i2c_smbus_write_byte_data(to_i2c_client(dev), reg_addr, data);
+}
+
+static const struct st_sensor_transfer_function st_sensors_tf_i2c = {
+ .read_byte = st_sensors_i2c_read_byte,
+ .write_byte = st_sensors_i2c_write_byte,
+ .read_multiple_byte = st_sensors_i2c_read_multiple_byte,
+};
+
+void st_sensors_i2c_configure(struct iio_dev *indio_dev,
+ struct i2c_client *client, struct st_sensor_data *sdata)
+{
+ i2c_set_clientdata(client, indio_dev);
+
+ indio_dev->dev.parent = &client->dev;
+ indio_dev->name = client->name;
+
+ sdata->dev = &client->dev;
+ sdata->tf = &st_sensors_tf_i2c;
+ sdata->get_irq_data_ready = st_sensors_i2c_get_irq;
+}
+EXPORT_SYMBOL(st_sensors_i2c_configure);
+
+#ifdef CONFIG_OF
+/**
+ * st_sensors_of_i2c_probe() - device tree probe for ST I2C sensors
+ * @client: the I2C client device for the sensor
+ * @match: the OF match table for the device, containing compatible strings
+ * but also a .data field with the corresponding internal kernel name
+ * used by this sensor.
+ *
+ * In effect this function matches a compatible string to an internal kernel
+ * name for a certain sensor device, so that the rest of the autodetection can
+ * rely on that name from this point on. I2C client devices will be renamed
+ * to match the internal kernel convention.
+ */
+void st_sensors_of_i2c_probe(struct i2c_client *client,
+ const struct of_device_id *match)
+{
+ const struct of_device_id *of_id;
+
+ of_id = of_match_device(match, &client->dev);
+ if (!of_id)
+ return;
+
+ /* The name from the OF match takes precedence if present */
+ strncpy(client->name, of_id->data, sizeof(client->name));
+ client->name[sizeof(client->name) - 1] = '\0';
+}
+EXPORT_SYMBOL(st_sensors_of_i2c_probe);
+#endif
+
+MODULE_AUTHOR("Denis Ciocca <denis.ciocca@st.com>");
+MODULE_DESCRIPTION("STMicroelectronics ST-sensors i2c driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/iio/common/st_sensors/st_sensors_spi.c b/drivers/iio/common/st_sensors/st_sensors_spi.c
new file mode 100644
index 000000000..5b377373f
--- /dev/null
+++ b/drivers/iio/common/st_sensors/st_sensors_spi.c
@@ -0,0 +1,122 @@
+/*
+ * STMicroelectronics sensors spi library driver
+ *
+ * Copyright 2012-2013 STMicroelectronics Inc.
+ *
+ * Denis Ciocca <denis.ciocca@st.com>
+ *
+ * Licensed under the GPL-2.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/iio/iio.h>
+
+#include <linux/iio/common/st_sensors_spi.h>
+
+
+#define ST_SENSORS_SPI_MULTIREAD 0xc0
+#define ST_SENSORS_SPI_READ 0x80
+
+static unsigned int st_sensors_spi_get_irq(struct iio_dev *indio_dev)
+{
+ struct st_sensor_data *sdata = iio_priv(indio_dev);
+
+ return to_spi_device(sdata->dev)->irq;
+}
+
+static int st_sensors_spi_read(struct st_sensor_transfer_buffer *tb,
+ struct device *dev, u8 reg_addr, int len, u8 *data, bool multiread_bit)
+{
+ int err;
+
+ struct spi_transfer xfers[] = {
+ {
+ .tx_buf = tb->tx_buf,
+ .bits_per_word = 8,
+ .len = 1,
+ },
+ {
+ .rx_buf = tb->rx_buf,
+ .bits_per_word = 8,
+ .len = len,
+ }
+ };
+
+ mutex_lock(&tb->buf_lock);
+ if ((multiread_bit) && (len > 1))
+ tb->tx_buf[0] = reg_addr | ST_SENSORS_SPI_MULTIREAD;
+ else
+ tb->tx_buf[0] = reg_addr | ST_SENSORS_SPI_READ;
+
+ err = spi_sync_transfer(to_spi_device(dev), xfers, ARRAY_SIZE(xfers));
+ if (err)
+ goto acc_spi_read_error;
+
+ memcpy(data, tb->rx_buf, len);
+ mutex_unlock(&tb->buf_lock);
+ return len;
+
+acc_spi_read_error:
+ mutex_unlock(&tb->buf_lock);
+ return err;
+}
+
+static int st_sensors_spi_read_byte(struct st_sensor_transfer_buffer *tb,
+ struct device *dev, u8 reg_addr, u8 *res_byte)
+{
+ return st_sensors_spi_read(tb, dev, reg_addr, 1, res_byte, false);
+}
+
+static int st_sensors_spi_read_multiple_byte(
+ struct st_sensor_transfer_buffer *tb, struct device *dev,
+ u8 reg_addr, int len, u8 *data, bool multiread_bit)
+{
+ return st_sensors_spi_read(tb, dev, reg_addr, len, data, multiread_bit);
+}
+
+static int st_sensors_spi_write_byte(struct st_sensor_transfer_buffer *tb,
+ struct device *dev, u8 reg_addr, u8 data)
+{
+ int err;
+
+ struct spi_transfer xfers = {
+ .tx_buf = tb->tx_buf,
+ .bits_per_word = 8,
+ .len = 2,
+ };
+
+ mutex_lock(&tb->buf_lock);
+ tb->tx_buf[0] = reg_addr;
+ tb->tx_buf[1] = data;
+
+ err = spi_sync_transfer(to_spi_device(dev), &xfers, 1);
+ mutex_unlock(&tb->buf_lock);
+
+ return err;
+}
+
+static const struct st_sensor_transfer_function st_sensors_tf_spi = {
+ .read_byte = st_sensors_spi_read_byte,
+ .write_byte = st_sensors_spi_write_byte,
+ .read_multiple_byte = st_sensors_spi_read_multiple_byte,
+};
+
+void st_sensors_spi_configure(struct iio_dev *indio_dev,
+ struct spi_device *spi, struct st_sensor_data *sdata)
+{
+ spi_set_drvdata(spi, indio_dev);
+
+ indio_dev->dev.parent = &spi->dev;
+ indio_dev->name = spi->modalias;
+
+ sdata->dev = &spi->dev;
+ sdata->tf = &st_sensors_tf_spi;
+ sdata->get_irq_data_ready = st_sensors_spi_get_irq;
+}
+EXPORT_SYMBOL(st_sensors_spi_configure);
+
+MODULE_AUTHOR("Denis Ciocca <denis.ciocca@st.com>");
+MODULE_DESCRIPTION("STMicroelectronics ST-sensors spi driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/iio/common/st_sensors/st_sensors_trigger.c b/drivers/iio/common/st_sensors/st_sensors_trigger.c
new file mode 100644
index 000000000..8d8ca6f1e
--- /dev/null
+++ b/drivers/iio/common/st_sensors/st_sensors_trigger.c
@@ -0,0 +1,77 @@
+/*
+ * STMicroelectronics sensors trigger library driver
+ *
+ * Copyright 2012-2013 STMicroelectronics Inc.
+ *
+ * Denis Ciocca <denis.ciocca@st.com>
+ *
+ * Licensed under the GPL-2.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/trigger.h>
+#include <linux/interrupt.h>
+
+#include <linux/iio/common/st_sensors.h>
+
+
+int st_sensors_allocate_trigger(struct iio_dev *indio_dev,
+ const struct iio_trigger_ops *trigger_ops)
+{
+ int err;
+ struct st_sensor_data *sdata = iio_priv(indio_dev);
+
+ sdata->trig = iio_trigger_alloc("%s-trigger", indio_dev->name);
+ if (sdata->trig == NULL) {
+ err = -ENOMEM;
+ dev_err(&indio_dev->dev, "failed to allocate iio trigger.\n");
+ goto iio_trigger_alloc_error;
+ }
+
+ err = request_threaded_irq(sdata->get_irq_data_ready(indio_dev),
+ iio_trigger_generic_data_rdy_poll,
+ NULL,
+ IRQF_TRIGGER_RISING,
+ sdata->trig->name,
+ sdata->trig);
+ if (err)
+ goto request_irq_error;
+
+ iio_trigger_set_drvdata(sdata->trig, indio_dev);
+ sdata->trig->ops = trigger_ops;
+ sdata->trig->dev.parent = sdata->dev;
+
+ err = iio_trigger_register(sdata->trig);
+ if (err < 0) {
+ dev_err(&indio_dev->dev, "failed to register iio trigger.\n");
+ goto iio_trigger_register_error;
+ }
+ indio_dev->trig = iio_trigger_get(sdata->trig);
+
+ return 0;
+
+iio_trigger_register_error:
+ free_irq(sdata->get_irq_data_ready(indio_dev), sdata->trig);
+request_irq_error:
+ iio_trigger_free(sdata->trig);
+iio_trigger_alloc_error:
+ return err;
+}
+EXPORT_SYMBOL(st_sensors_allocate_trigger);
+
+void st_sensors_deallocate_trigger(struct iio_dev *indio_dev)
+{
+ struct st_sensor_data *sdata = iio_priv(indio_dev);
+
+ iio_trigger_unregister(sdata->trig);
+ free_irq(sdata->get_irq_data_ready(indio_dev), sdata->trig);
+ iio_trigger_free(sdata->trig);
+}
+EXPORT_SYMBOL(st_sensors_deallocate_trigger);
+
+MODULE_AUTHOR("Denis Ciocca <denis.ciocca@st.com>");
+MODULE_DESCRIPTION("STMicroelectronics ST-sensors trigger");
+MODULE_LICENSE("GPL v2");