LKML Archive on lore.kernel.org
help / color / mirror / Atom feed
From: Karol Wrona <k.wrona@samsung.com>
To: Jonathan Cameron <jic23@kernel.org>,
	linux-iio@vger.kernel.org, devicetree@vger.kernel.org,
	linux-kernel@vger.kernel.org
Cc: Hartmut Knaack <knaack.h@gmx.de>,
	Lars-Peter Clausen <lars@metafoo.de>,
	Peter Meerwald <pmeerw@pmeerw.net>,
	Rob Herring <robh+dt@kernel.org>, Pawel Moll <pawel.moll@arm.com>,
	Mark Rutland <mark.rutland@arm.com>,
	Ian Campbell <ijc+devicetree@hellion.org.uk>,
	Kumar Gala <galak@codeaurora.org>,
	Bartlomiej Zolnierkiewicz <b.zolnierkie@samsung.com>,
	Kyungmin Park <kyungmin.park@samsung.com>,
	Karol Wrona <wrona.vy@gmail.com>,
	Karol Wrona <k.wrona@samsung.com>
Subject: [PATCH v5 1/5] iio: common: ssp_sensors: Add sensorhub driver
Date: Wed, 28 Jan 2015 15:05:50 +0100	[thread overview]
Message-ID: <1422453954-27317-2-git-send-email-k.wrona@samsung.com> (raw)
In-Reply-To: <1422453954-27317-1-git-send-email-k.wrona@samsung.com>

Sensorhub  is MCU dedicated to collect data and manage several sensors.
Sensorhub is a spi device which provides a layer for IIO devices. It provides
some data parsing and common mechanism for sensorhub sensors.

Adds common sensorhub library for sensorhub driver and iio drivers
which uses sensorhub MCU to communicate with sensors.

Signed-off-by: Karol Wrona <k.wrona@samsung.com>
Acked-by: Kyungmin Park <kyungmin.park@samsung.com>
---
 drivers/iio/common/Kconfig               |    1 +
 drivers/iio/common/Makefile              |    1 +
 drivers/iio/common/ssp_sensors/Kconfig   |   26 ++
 drivers/iio/common/ssp_sensors/Makefile  |    8 +
 drivers/iio/common/ssp_sensors/ssp.h     |  257 +++++++++++
 drivers/iio/common/ssp_sensors/ssp_dev.c |  712 ++++++++++++++++++++++++++++++
 drivers/iio/common/ssp_sensors/ssp_spi.c |  608 +++++++++++++++++++++++++
 include/linux/iio/common/ssp_sensors.h   |   82 ++++
 8 files changed, 1695 insertions(+)
 create mode 100644 drivers/iio/common/ssp_sensors/Kconfig
 create mode 100644 drivers/iio/common/ssp_sensors/Makefile
 create mode 100644 drivers/iio/common/ssp_sensors/ssp.h
 create mode 100644 drivers/iio/common/ssp_sensors/ssp_dev.c
 create mode 100644 drivers/iio/common/ssp_sensors/ssp_spi.c
 create mode 100644 include/linux/iio/common/ssp_sensors.h

diff --git a/drivers/iio/common/Kconfig b/drivers/iio/common/Kconfig
index 0b6e97d..790f106 100644
--- a/drivers/iio/common/Kconfig
+++ b/drivers/iio/common/Kconfig
@@ -3,4 +3,5 @@
 #
 
 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
index 3112df0..b1e4d9c 100644
--- a/drivers/iio/common/Makefile
+++ b/drivers/iio/common/Makefile
@@ -8,4 +8,5 @@
 
 # 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/ssp_sensors/Kconfig b/drivers/iio/common/ssp_sensors/Kconfig
new file mode 100644
index 0000000..0ea4faf
--- /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 0000000..1e0389e
--- /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 0000000..b910e91
--- /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 0000000..6b22115
--- /dev/null
+++ b/drivers/iio/common/ssp_sensors/ssp_dev.c
@@ -0,0 +1,712 @@
+/*
+ *  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 = "ssp_B2.fw",
+	.fw_crashed_name = "ssp_crashed.fw",
+	.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 = "thermostat_B2.fw",
+	.fw_crashed_name = "ssp_crashed.fw",
+	.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 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 platform_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;
+}
+
+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;
+}
+
+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_spi.c b/drivers/iio/common/ssp_sensors/ssp_spi.c
new file mode 100644
index 0000000..704284a
--- /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/include/linux/iio/common/ssp_sensors.h b/include/linux/iio/common/ssp_sensors.h
new file mode 100644
index 0000000..f4d1b0e
--- /dev/null
+++ b/include/linux/iio/common/ssp_sensors.h
@@ -0,0 +1,82 @@
+/*
+ *  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_SENSORS_H_
+#define _SSP_SENSORS_H_
+
+#include <linux/iio/iio.h>
+
+#define SSP_TIME_SIZE				4
+#define SSP_ACCELEROMETER_SIZE			6
+#define SSP_GYROSCOPE_SIZE			6
+#define SSP_BIO_HRM_RAW_SIZE			8
+#define SSP_BIO_HRM_RAW_FAC_SIZE		36
+#define SSP_BIO_HRM_LIB_SIZE			8
+
+/**
+ * enum ssp_sensor_type - SSP sensor type
+ */
+enum ssp_sensor_type {
+	SSP_ACCELEROMETER_SENSOR = 0,
+	SSP_GYROSCOPE_SENSOR,
+	SSP_GEOMAGNETIC_UNCALIB_SENSOR,
+	SSP_GEOMAGNETIC_RAW,
+	SSP_GEOMAGNETIC_SENSOR,
+	SSP_PRESSURE_SENSOR,
+	SSP_GESTURE_SENSOR,
+	SSP_PROXIMITY_SENSOR,
+	SSP_TEMPERATURE_HUMIDITY_SENSOR,
+	SSP_LIGHT_SENSOR,
+	SSP_PROXIMITY_RAW,
+	SSP_ORIENTATION_SENSOR,
+	SSP_STEP_DETECTOR,
+	SSP_SIG_MOTION_SENSOR,
+	SSP_GYRO_UNCALIB_SENSOR,
+	SSP_GAME_ROTATION_VECTOR,
+	SSP_ROTATION_VECTOR,
+	SSP_STEP_COUNTER,
+	SSP_BIO_HRM_RAW,
+	SSP_BIO_HRM_RAW_FAC,
+	SSP_BIO_HRM_LIB,
+	SSP_SENSOR_MAX,
+};
+
+struct ssp_data;
+
+/**
+ * struct ssp_sensor_data - Sensor object
+ * @process_data:	Callback to feed sensor data.
+ * @type:		Used sensor type.
+ * @buffer:		Received data buffer.
+ */
+struct ssp_sensor_data {
+	int (*process_data)(struct iio_dev *indio_dev, void *buf,
+			    int64_t timestamp);
+	enum ssp_sensor_type type;
+	u8 *buffer;
+};
+
+void ssp_register_consumer(struct iio_dev *indio_dev,
+			   enum ssp_sensor_type type);
+
+int ssp_enable_sensor(struct ssp_data *data, enum ssp_sensor_type type,
+		      u32 delay);
+
+int ssp_disable_sensor(struct ssp_data *data, enum ssp_sensor_type type);
+
+u32 ssp_get_sensor_delay(struct ssp_data *data, enum ssp_sensor_type);
+
+int ssp_change_delay(struct ssp_data *data, enum ssp_sensor_type type,
+		     u32 delay);
+#endif /* _SSP_SENSORS_H_ */
-- 
1.7.9.5


  reply	other threads:[~2015-01-28 22:13 UTC|newest]

Thread overview: 13+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2015-01-28 14:05 [PATCH v5 0/5] iio: " Karol Wrona
2015-01-28 14:05 ` Karol Wrona [this message]
2015-01-29 18:35   ` [PATCH v5 1/5] iio: common: ssp_sensors: " Jonathan Cameron
2015-01-29 18:46     ` Karol Wrona
2015-01-29 18:53       ` Jonathan Cameron
2015-01-28 14:05 ` [PATCH v5 2/5] iio: sensorhub: Add sensorhub bindings Karol Wrona
2015-01-29 18:36   ` Jonathan Cameron
2015-01-28 14:05 ` [PATCH v5 3/5] iio: common: ssp_sensors: Add sensorhub iio commons Karol Wrona
2015-01-29 18:36   ` Jonathan Cameron
2015-01-28 14:05 ` [PATCH v5 4/5] iio: common: ssp_sensors: Add sensorhub accelerometer sensor Karol Wrona
2015-01-29 18:37   ` Jonathan Cameron
2015-01-28 14:05 ` [PATCH v5 5/5] iio: common: ssp_sensors: Add sensorhub gyroscope sensor Karol Wrona
2015-01-29 18:37   ` Jonathan Cameron

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=1422453954-27317-2-git-send-email-k.wrona@samsung.com \
    --to=k.wrona@samsung.com \
    --cc=b.zolnierkie@samsung.com \
    --cc=devicetree@vger.kernel.org \
    --cc=galak@codeaurora.org \
    --cc=ijc+devicetree@hellion.org.uk \
    --cc=jic23@kernel.org \
    --cc=knaack.h@gmx.de \
    --cc=kyungmin.park@samsung.com \
    --cc=lars@metafoo.de \
    --cc=linux-iio@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=mark.rutland@arm.com \
    --cc=pawel.moll@arm.com \
    --cc=pmeerw@pmeerw.net \
    --cc=robh+dt@kernel.org \
    --cc=wrona.vy@gmail.com \
    --subject='Re: [PATCH v5 1/5] iio: common: ssp_sensors: Add sensorhub driver' \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).