LKML Archive on lore.kernel.org
help / color / mirror / Atom feed
From: Jonathan Cameron <jic23@kernel.org>
To: Karol Wrona <k.wrona@samsung.com>,
	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>
Subject: Re: [PATCH v5 1/5] iio: common: ssp_sensors: Add sensorhub driver
Date: Thu, 29 Jan 2015 18:35:49 +0000	[thread overview]
Message-ID: <54CA7D85.20409@kernel.org> (raw)
In-Reply-To: <1422453954-27317-2-git-send-email-k.wrona@samsung.com>

On 28/01/15 14:05, Karol Wrona wrote:
> 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>
A couple of build errors from this one...

I've fixed them up and applied to the togreg branch of iio.git.
Pushed out as testing.  If you get a chance to check I didn't
mess anything up that would be great.
> ---
>  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
This file isn't in this patch.
> 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)
> +{
This has incorrect arguements.. So causes a build error.
> +	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_ */
> 


  reply	other threads:[~2015-01-29 18:35 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 ` [PATCH v5 1/5] iio: common: ssp_sensors: " Karol Wrona
2015-01-29 18:35   ` Jonathan Cameron [this message]
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=54CA7D85.20409@kernel.org \
    --to=jic23@kernel.org \
    --cc=b.zolnierkie@samsung.com \
    --cc=devicetree@vger.kernel.org \
    --cc=galak@codeaurora.org \
    --cc=ijc+devicetree@hellion.org.uk \
    --cc=k.wrona@samsung.com \
    --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).