[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-id: <1409756150-30889-3-git-send-email-k.wrona@samsung.com>
Date: Wed, 03 Sep 2014 16:55:46 +0200
From: Karol Wrona <k.wrona@...sung.com>
To: Jonathan Cameron <jic23@...nel.org>, linux-iio@...r.kernel.org,
Arnd Bergmann <arnd@...db.de>, linux-kernel@...r.kernel.org
Cc: Bartlomiej Zolnierkiewicz <b.zolnierkie@...sung.com>,
Kyungmin Park <kyungmin.park@...sung.com>,
Karol Wrona <k.wrona@...sung.com>
Subject: [RFC/PATCH 2/6] misc: sensorhub: Add sensorhub driver
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.
Signed-off-by: Karol Wrona <k.wrona@...sung.com>
Acked-by: Kyungmin Park <kyungmin.park@...sung.com>
---
drivers/misc/Kconfig | 1 +
drivers/misc/Makefile | 1 +
drivers/misc/sensorhub/Kconfig | 13 +
drivers/misc/sensorhub/Makefile | 8 +
drivers/misc/sensorhub/ssp.h | 323 ++++++++++++++
drivers/misc/sensorhub/ssp_data.c | 61 +++
drivers/misc/sensorhub/ssp_dev.c | 782 +++++++++++++++++++++++++++++++++
drivers/misc/sensorhub/ssp_firmware.c | 571 ++++++++++++++++++++++++
drivers/misc/sensorhub/ssp_spi.c | 700 +++++++++++++++++++++++++++++
9 files changed, 2460 insertions(+)
create mode 100644 drivers/misc/sensorhub/Kconfig
create mode 100644 drivers/misc/sensorhub/Makefile
create mode 100644 drivers/misc/sensorhub/ssp.h
create mode 100644 drivers/misc/sensorhub/ssp_data.c
create mode 100644 drivers/misc/sensorhub/ssp_dev.c
create mode 100644 drivers/misc/sensorhub/ssp_firmware.c
create mode 100644 drivers/misc/sensorhub/ssp_spi.c
diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index b841180..faf8521 100644
--- a/drivers/misc/Kconfig
+++ b/drivers/misc/Kconfig
@@ -527,4 +527,5 @@ source "drivers/misc/vmw_vmci/Kconfig"
source "drivers/misc/mic/Kconfig"
source "drivers/misc/genwqe/Kconfig"
source "drivers/misc/echo/Kconfig"
+source "drivers/misc/sensorhub/Kconfig"
endmenu
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index 5497d02..9c36078 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -55,3 +55,4 @@ obj-y += mic/
obj-$(CONFIG_GENWQE) += genwqe/
obj-$(CONFIG_ECHO) += echo/
obj-$(CONFIG_VEXPRESS_SYSCFG) += vexpress-syscfg.o
+obj-$(CONFIG_SENSORS_SSP) += sensorhub/
diff --git a/drivers/misc/sensorhub/Kconfig b/drivers/misc/sensorhub/Kconfig
new file mode 100644
index 0000000..0af30ec
--- /dev/null
+++ b/drivers/misc/sensorhub/Kconfig
@@ -0,0 +1,13 @@
+#
+# sensor drivers configuration
+#
+config SENSORS_SSP
+ tristate "Sensors ssp"
+ default n
+ depends on SPI
+ help
+ SSP driver for sensor hub.
+ If you say yes here you get ssp support for
+ sensor hub.
+ To compile this driver as a module, choose M here: the
+ module will be called ssp_dev.
diff --git a/drivers/misc/sensorhub/Makefile b/drivers/misc/sensorhub/Makefile
new file mode 100644
index 0000000..754da8b
--- /dev/null
+++ b/drivers/misc/sensorhub/Makefile
@@ -0,0 +1,8 @@
+#
+# Makefile for the sensor drivers.
+#
+
+# Each configuration option enables a list of files.
+obj-$(CONFIG_SENSORS_SSP) += ssp_dev.o ssp_spi.o ssp_data.o \
+ ssp_firmware.o
+
diff --git a/drivers/misc/sensorhub/ssp.h b/drivers/misc/sensorhub/ssp.h
new file mode 100644
index 0000000..c49f958
--- /dev/null
+++ b/drivers/misc/sensorhub/ssp.h
@@ -0,0 +1,323 @@
+/*
+ * Copyright (C) 2011, 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/gpio.h>
+#include <linux/delay.h>
+#include <linux/regulator/consumer.h>
+#include <linux/spi/spi.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/common/ssp_sensors.h>
+
+#define DEVICE_ID 0x55
+
+#ifdef SSP_DBG
+#define ssp_info(fmt, ...) pr_info("[SSP] "fmt, ##__VA_ARGS__)
+
+#define ssp_dbg(format, ...) pr_info("[SSP] "format, ##__VA_ARGS__)
+#else
+#define ssp_info(fmt, ...)
+
+#define ssp_dbg(format, ...)
+#endif
+
+#define SSP_SW_RESET_TIME 3000
+/* Sensor polling in ms */
+#define DEFUALT_POLLING_DELAY 200
+#define DEFAULT_RETRIES 3
+#define DATA_PACKET_SIZE 960
+
+enum {
+ KERNEL_BINARY = 0,
+ KERNEL_CRASHED_BINARY,
+};
+
+enum {
+ INITIALIZATION_STATE = 0,
+ NO_SENSOR_STATE,
+ ADD_SENSOR_STATE,
+ RUNNING_SENSOR_STATE,
+};
+
+/* Firmware download STATE */
+enum {
+ FW_DL_STATE_FAIL = -1,
+ FW_DL_STATE_NONE = 0,
+ FW_DL_STATE_NEED_TO_SCHEDULE,
+ FW_DL_STATE_SCHEDULED,
+ FW_DL_STATE_DOWNLOADING,
+ FW_DL_STATE_SYNC,
+ FW_DL_STATE_DONE,
+};
+
+#define SSP_INVALID_REVISION 99999
+#define SSP_INVALID_REVISION2 0xFFFFFF
+
+/* AP -> SSP Instruction */
+#define MSG2SSP_INST_BYPASS_SENSOR_ADD 0xA1
+#define MSG2SSP_INST_BYPASS_SENSOR_REMOVE 0xA2
+#define MSG2SSP_INST_REMOVE_ALL 0xA3
+#define MSG2SSP_INST_CHANGE_DELAY 0xA4
+#define MSG2SSP_INST_LIBRARY_ADD 0xB1
+#define MSG2SSP_INST_LIBRARY_REMOVE 0xB2
+#define MSG2SSP_INST_LIB_NOTI 0xB4
+#define MSG2SSP_INST_LIB_DATA 0xC1
+
+#define MSG2SSP_AP_MCU_SET_GYRO_CAL 0xCD
+#define MSG2SSP_AP_MCU_SET_ACCEL_CAL 0xCE
+#define MSG2SSP_AP_STATUS_SHUTDOWN 0xD0
+#define MSG2SSP_AP_STATUS_WAKEUP 0xD1
+#define MSG2SSP_AP_STATUS_SLEEP 0xD2
+#define MSG2SSP_AP_STATUS_RESUME 0xD3
+#define MSG2SSP_AP_STATUS_SUSPEND 0xD4
+#define MSG2SSP_AP_STATUS_RESET 0xD5
+#define MSG2SSP_AP_STATUS_POW_CONNECTED 0xD6
+#define MSG2SSP_AP_STATUS_POW_DISCONNECTED 0xD7
+#define MSG2SSP_AP_TEMPHUMIDITY_CAL_DONE 0xDA
+#define MSG2SSP_AP_MCU_SET_DUMPMODE 0xDB
+#define MSG2SSP_AP_MCU_DUMP_CHECK 0xDC
+#define MSG2SSP_AP_MCU_BATCH_FLUSH 0xDD
+#define MSG2SSP_AP_MCU_BATCH_COUNT 0xDF
+
+#define MSG2SSP_AP_WHOAMI 0x0F
+#define MSG2SSP_AP_FIRMWARE_REV 0xF0
+#define MSG2SSP_AP_SENSOR_FORMATION 0xF1
+#define MSG2SSP_AP_SENSOR_PROXTHRESHOLD 0xF2
+#define MSG2SSP_AP_SENSOR_BARCODE_EMUL 0xF3
+#define MSG2SSP_AP_SENSOR_SCANNING 0xF4
+#define MSG2SSP_AP_SET_MAGNETIC_HWOFFSET 0xF5
+#define MSG2SSP_AP_GET_MAGNETIC_HWOFFSET 0xF6
+#define MSG2SSP_AP_SENSOR_GESTURE_CURRENT 0xF7
+#define MSG2SSP_AP_GET_THERM 0xF8
+#define MSG2SSP_AP_GET_BIG_DATA 0xF9
+#define MSG2SSP_AP_SET_BIG_DATA 0xFA
+#define MSG2SSP_AP_START_BIG_DATA 0xFB
+#define MSG2SSP_AP_SET_MAGNETIC_STATIC_MATRIX 0xFD
+#define MSG2SSP_AP_SENSOR_TILT 0xEA
+#define MSG2SSP_AP_MCU_SET_TIME 0xFE
+#define MSG2SSP_AP_MCU_GET_TIME 0xFF
+
+#define MSG2SSP_AP_FUSEROM 0X01
+/* voice data */
+#define TYPE_WAKE_UP_VOICE_SERVICE 0x01
+#define TYPE_WAKE_UP_VOICE_SOUND_SOURCE_AM 0x01
+#define TYPE_WAKE_UP_VOICE_SOUND_SOURCE_GRAMMER 0x02
+
+/* Factory Test */
+#define ACCELEROMETER_FACTORY 0x80
+#define GYROSCOPE_FACTORY 0x81
+#define GEOMAGNETIC_FACTORY 0x82
+#define PRESSURE_FACTORY 0x85
+#define GESTURE_FACTORY 0x86
+#define TEMPHUMIDITY_CRC_FACTORY 0x88
+#define GYROSCOPE_TEMP_FACTORY 0x8A
+#define GYROSCOPE_DPS_FACTORY 0x8B
+#define MCU_FACTORY 0x8C
+#define MCU_SLEEP_FACTORY 0x8D
+
+/* Factory data length */
+#define ACCEL_FACTORY_DATA_LENGTH 1
+#define GYRO_FACTORY_DATA_LENGTH 36
+#define MAGNETIC_FACTORY_DATA_LENGTH 26
+#define PRESSURE_FACTORY_DATA_LENGTH 1
+#define MCU_FACTORY_DATA_LENGTH 5
+#define GYRO_TEMP_FACTORY_DATA_LENGTH 2
+#define GYRO_DPS_FACTORY_DATA_LENGTH 1
+#define TEMPHUMIDITY_FACTORY_DATA_LENGTH 1
+#define MCU_SLEEP_FACTORY_DATA_LENGTH FACTORY_DATA_MAX
+#define GESTURE_FACTORY_DATA_LENGTH 4
+
+/* SSP -> AP ACK about write CMD */
+#define MSG_ACK 0x80 /* ACK from SSP to AP */
+#define MSG_NAK 0x70 /* NAK from SSP to AP */
+
+/* Accelerometer sensor*/
+/* 16bits */
+#define MAX_ACCEL_1G 16384
+#define MAX_ACCEL_2G 32767
+#define MIN_ACCEL_2G -32768
+#define MAX_ACCEL_4G 65536
+
+#define MAX_GYRO 32767
+#define MIN_GYRO -32768
+
+struct sensorhub_info {
+ char *fw_name;
+ char *fw_crashed_name;
+ unsigned int fw_rev;
+};
+
+struct calibraion_data {
+ s16 x;
+ s16 y;
+ s16 z;
+};
+
+/* ssp_msg options bit*/
+#define SSP_SPI 0 /* read write mask */
+#define SSP_RETURN 2 /* write and read option */
+#define SSP_GYRO_DPS 3 /* gyro dps mask */
+#define SSP_INDEX 3 /* data index mask */
+
+#define SSP_SPI_MASK (3 << SSP_SPI) /* read write mask */
+#define SSP_GYRO_DPS_MASK (3 << SSP_GYRO_DPS)
+ /* dump index mask. Index is up to 8191 */
+#define SSP_INDEX_MASK (8191 << SSP_INDEX)
+
+struct ssp_msg {
+ u8 cmd;
+ /* FIXME it is pushed to STM as first 9 bytes */
+ u16 length;
+ u16 options;
+ __le32 data;
+
+ struct list_head list;
+ struct completion *done;
+ char *buffer;
+ u8 free_buffer;
+ bool *dead_hook;
+ bool dead;
+} __attribute__((__packed__));
+
+enum {
+ AP2HUB_READ = 0,
+ AP2HUB_WRITE,
+ HUB2AP_WRITE,
+ AP2HUB_READY,
+ AP2HUB_RETURN
+};
+
+enum {
+ BIG_TYPE_DUMP = 0,
+ BIG_TYPE_READ_LIB,
+ BIG_TYPE_VOICE_NET,
+ BIG_TYPE_VOICE_GRAM,
+ BIG_TYPE_VOICE_PCM,
+ BIG_TYPE_TEMP,
+ BIG_TYPE_MAX,
+};
+
+struct ssp_data {
+ struct spi_device *spi;
+ struct sensorhub_info *sensorhub_info;
+ struct timer_list wdt_timer;
+ struct workqueue_struct *wdt_wq;
+ struct work_struct work_wdt;
+
+ struct regulator *hrm_supply_18;
+ struct regulator *hrm_supply_33;
+
+ struct delayed_work work_firmware;
+ struct delayed_work work_refresh;
+
+ bool shut_down;
+ bool mcu_dump_mode;
+ bool time_syncing;
+
+ unsigned char fuse_rom_data[3];
+ unsigned char mag_reg_data;
+ int library_length;
+ int check_status[SSP_SENSOR_MAX];
+
+ unsigned int com_fail_cnt;
+ unsigned int reset_cnt;
+ unsigned int time_out_cnt;
+ unsigned int irq_cnt;
+
+ unsigned int sensor_state;
+ 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;
+
+ u32 mag_matrix_size;
+ u8 *mag_matrix;
+
+ struct mutex comm_mutex;
+ struct mutex pending_mutex;
+
+ void (*get_positions)(int *, int *);
+
+ int mcu_reset;
+ int ap_mcu_int;
+ int mcu_ap_int;
+ struct list_head pending_list;
+
+ void (*ssp_big_task[BIG_TYPE_MAX])(struct work_struct *);
+ u64 timestamp;
+
+ struct iio_dev *sensor_devs[SSP_SENSOR_MAX];
+ atomic_t enable_refcount;
+};
+
+struct ssp_big {
+ struct ssp_data *data;
+ struct work_struct work;
+ u32 length;
+ u32 addr;
+};
+
+void ssp_enable(struct ssp_data *, bool);
+
+void clean_pending_list(struct ssp_data *);
+
+void toggle_mcu_reset(struct ssp_data *);
+
+int initialize_mcu(struct ssp_data *);
+
+void initialize_function_pointer(struct ssp_data *);
+
+int check_fwbl(struct ssp_data *);
+
+int ssp_send_cmd(struct ssp_data *, char, int);
+
+int ssp_send_instruction(struct ssp_data *, u8, u8, u8 *, u8);
+
+int flush(struct ssp_data *, u8);
+
+int select_irq_msg(struct ssp_data *);
+
+int get_chipid(struct ssp_data *);
+
+int get_fuserom_data(struct ssp_data *);
+
+int set_big_data_start(struct ssp_data *, u8 , u32);
+
+int set_sensor_position(struct ssp_data *);
+
+int set_magnetic_static_matrix(struct ssp_data *);
+
+unsigned int get_sensor_scanning_info(struct ssp_data *);
+
+unsigned int get_firmware_rev(struct ssp_data *);
+
+int forced_to_download_binary(struct ssp_data *, int);
+
+int queue_refresh_task(struct ssp_data *data, int delay);
+
+int ssp_start_big_data(struct ssp_data *data, u8 type, u32 len);
+
+#endif /* __SSP_SENSORHUB_H__ */
diff --git a/drivers/misc/sensorhub/ssp_data.c b/drivers/misc/sensorhub/ssp_data.c
new file mode 100644
index 0000000..4112cf2
--- /dev/null
+++ b/drivers/misc/sensorhub/ssp_data.c
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2012, 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"
+
+static void sync_sensor_state(struct ssp_data *data)
+{
+ int i, ret;
+
+ for (i = 0; i < SSP_SENSOR_MAX; ++i) {
+ if ((data->sensor_state << i) == 1)
+ ret = ssp_enable_sensor(data, i, data->delay_buf[i]);
+ if (ret < 0)
+ continue;
+ }
+
+ ret = ssp_send_cmd(data, MSG2SSP_AP_MCU_SET_DUMPMODE,
+ data->mcu_dump_mode);
+ if (ret < 0) {
+ pr_err("[SSP]: %s - MSG2SSP_AP_MCU_SET_DUMPMODE failed\n",
+ __func__);
+ }
+}
+
+void refresh_task(struct work_struct *work)
+{
+ struct ssp_data *data = container_of((struct delayed_work *)work,
+ struct ssp_data, work_refresh);
+
+ data->reset_cnt++;
+
+ if (initialize_mcu(data) > 0) {
+ sync_sensor_state(data);
+ if (data->last_ap_state != 0)
+ ssp_send_cmd(data, data->last_ap_state, 0);
+ if (data->last_resume_state != 0)
+ ssp_send_cmd(data, data->last_resume_state, 0);
+ data->time_out_cnt = 0;
+ }
+}
+
+int queue_refresh_task(struct ssp_data *data, int delay)
+{
+ cancel_delayed_work_sync(&data->work_refresh);
+
+ INIT_DELAYED_WORK(&data->work_refresh, refresh_task);
+
+ return queue_delayed_work(data->wdt_wq, &data->work_refresh,
+ msecs_to_jiffies(delay));
+}
diff --git a/drivers/misc/sensorhub/ssp_dev.c b/drivers/misc/sensorhub/ssp_dev.c
new file mode 100644
index 0000000..6b42a3b
--- /dev/null
+++ b/drivers/misc/sensorhub/ssp_dev.c
@@ -0,0 +1,782 @@
+/*
+ * Copyright (C) 2012, 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/module.h>
+#include <linux/interrupt.h>
+#include <linux/of.h>
+#include <linux/of_gpio.h>
+#include <linux/of_platform.h>
+#include <linux/io.h>
+#include <linux/iio/iio.h>
+#include "ssp.h"
+
+#define SSP_WDT_TIME (10 * HZ)
+#define LIMIT_RESET_CNT 20
+#define LIMIT_TIMEOUT_CNT 3
+
+const struct sensorhub_info rinato_info = {
+ .fw_name = "ssp_B2.fw",
+ .fw_crashed_name = "ssp_crashed.fw",
+ .fw_rev = 14031200,
+};
+
+const struct sensorhub_info thermostat_info = {
+ .fw_name = "thermostat_B2.fw",
+ .fw_crashed_name = "ssp_crashed.fw",
+ .fw_rev = 14080600,
+};
+
+static void reset_mcu(struct ssp_data *data)
+{
+ ssp_enable(data, false);
+ clean_pending_list(data);
+ toggle_mcu_reset(data);
+ ssp_enable(data, true);
+}
+
+static void wdt_work_func(struct work_struct *work)
+{
+ struct ssp_data *data = container_of(work, struct ssp_data, work_wdt);
+
+ ssp_dbg("%s(%u) - Sensor state: 0x%x, RC: %u, CC: %u\n", __func__,
+ data->irq_cnt, data->sensor_state, data->reset_cnt,
+ data->com_fail_cnt);
+
+ switch (data->fw_dl_state) {
+ case FW_DL_STATE_FAIL:
+ case FW_DL_STATE_DOWNLOADING:
+ case FW_DL_STATE_SYNC:
+ ssp_dbg("[SSP] : %s firmware downloading state = %d\n",
+ __func__, data->fw_dl_state);
+ return;
+ }
+ /* FIXME time_out_cnt should be atomc*/
+ if (data->time_out_cnt > LIMIT_TIMEOUT_CNT) {
+ if (data->com_fail_cnt < LIMIT_RESET_CNT) {
+ pr_info("[SSP] : %s - time_out_cnt(%u), pending(%u)\n",
+ __func__, data->time_out_cnt,
+ !list_empty(&data->pending_list));
+ data->com_fail_cnt++;
+ reset_mcu(data);
+ } else
+ ssp_enable(data, false);
+
+ data->time_out_cnt = 0;
+ }
+
+ data->irq_cnt = 0;
+}
+
+static void wdt_timer_func(unsigned long ptr)
+{
+ struct ssp_data *data = (struct ssp_data *)ptr;
+
+ queue_work(data->wdt_wq, &data->work_wdt);
+ mod_timer(&data->wdt_timer,
+ round_jiffies_up(jiffies + SSP_WDT_TIME));
+}
+
+static void enable_wdt_timer(struct ssp_data *data)
+{
+ mod_timer(&data->wdt_timer,
+ round_jiffies_up(jiffies + SSP_WDT_TIME));
+}
+
+static void disable_wdt_timer(struct ssp_data *data)
+{
+ del_timer_sync(&data->wdt_timer);
+ cancel_work_sync(&data->work_wdt);
+}
+
+static int initialize_wdt_timer(struct ssp_data *data)
+{
+ setup_timer(&data->wdt_timer, wdt_timer_func,
+ (unsigned long)data);
+
+ data->wdt_wq = create_singlethread_workqueue("ssp_wdt_wq");
+ if (IS_ERR(data->wdt_wq))
+ return PTR_ERR(data->wdt_wq);
+
+ INIT_WORK(&data->work_wdt, wdt_work_func);
+
+ return 0;
+}
+
+/**
+ * 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
+ */
+int ssp_enable_sensor(struct ssp_data *data, enum ssp_sensor_type type,
+ u32 delay)
+{
+ int ret = 0;
+ struct {
+ __le32 a;
+ __le32 b;
+ u8 c;
+ } __attribute__((__packed__)) 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 INITIALIZATION_STATE:
+ /*FIXME do calibration step*/
+ case ADD_SENSOR_STATE:
+ ret = ssp_send_instruction(data, MSG2SSP_INST_BYPASS_SENSOR_ADD,
+ type, (char *)&to_send, sizeof(to_send));
+ if (ret < 0) {
+ dev_err(&data->spi->dev, "Enabling sensor failed\n");
+ data->check_status[type] = NO_SENSOR_STATE;
+ goto derror;
+ }
+
+ data->sensor_enable |= 1 << type;
+ data->check_status[type] = RUNNING_SENSOR_STATE;
+ break;
+ case RUNNING_SENSOR_STATE:
+ ret = ssp_send_instruction(data,
+ MSG2SSP_INST_CHANGE_DELAY, type,
+ (char *)&to_send, sizeof(to_send));
+ if (ret < 0) {
+ dev_err(&data->spi->dev,
+ "Changing delay sensor failed\n");
+ goto derror;
+ }
+ break;
+ default:
+ data->check_status[type] = ADD_SENSOR_STATE;
+ break;
+ }
+
+ data->delay_buf[type] = delay;
+
+ if (atomic_inc_return(&data->enable_refcount) == 1)
+ enable_wdt_timer(data);
+
+ return 0;
+
+derror:
+ return -EIO;
+}
+EXPORT_SYMBOL(ssp_enable_sensor);
+
+/**
+ * ssp_change_delay() - changes data acquisition for sensor
+ * @data: sensorhub structure
+ * @type: SSP sensor type
+ * @delay: delay in ms
+ */
+int ssp_change_delay(struct ssp_data *data, enum ssp_sensor_type type,
+ u32 delay)
+{
+ int ret = 0;
+ struct {
+ __le32 a;
+ __le32 b;
+ u8 c;
+ } __attribute__((__packed__)) 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, MSG2SSP_INST_CHANGE_DELAY, type,
+ (char *)&to_send, sizeof(to_send));
+ if (ret < 0) {
+ dev_err(&data->spi->dev,
+ "Changing delay sensor failed\n");
+ return ret;
+ }
+
+ data->delay_buf[type] = delay;
+
+ return ret;
+}
+EXPORT_SYMBOL(ssp_change_delay);
+
+/**
+ * ssp_disable_sensor() - disables sensor
+ *
+ * @data: sensorhub structure
+ * @type: SSP sensor type
+ *
+ * Returns < 0 if not succeed
+ */
+int ssp_disable_sensor(struct ssp_data *data, enum ssp_sensor_type type)
+{
+ int ret = 0;
+ __le32 command;
+
+ if (data->sensor_enable & (1 << type)) {
+ command = cpu_to_le32(data->delay_buf[type]);
+ ret = ssp_send_instruction(data,
+ MSG2SSP_INST_BYPASS_SENSOR_REMOVE,
+ type, (char *)&command, sizeof(__le32));
+ if (ret < 0) {
+ dev_err(&data->spi->dev, "Remove sensor fail\n");
+ return ret;
+ }
+
+ data->sensor_enable &= (~(1 << type));
+ }
+
+ data->check_status[type] = ADD_SENSOR_STATE;
+
+ if (atomic_dec_and_test(&data->enable_refcount))
+ disable_wdt_timer(data);
+
+ return ret;
+}
+EXPORT_SYMBOL(ssp_disable_sensor);
+
+void ssp_enable(struct ssp_data *data, bool enable)
+{
+ dev_info(&data->spi->dev, "Enable = %d, old enable = %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_err(&data->spi->dev,
+ "Error / enable = %d, old enable = %d\n", enable,
+ data->shut_down);
+ }
+}
+
+static irqreturn_t sensordata_irq_thread_fn(int irq, void *dev_id)
+{
+ struct ssp_data *data = dev_id;
+
+ select_irq_msg(data);
+ data->irq_cnt++;
+
+ return IRQ_HANDLED;
+}
+
+static void initialize_variable(struct ssp_data *data)
+{
+ int sensor_index;
+
+ for (sensor_index = 0; sensor_index < SSP_SENSOR_MAX; sensor_index++) {
+ data->delay_buf[sensor_index] = DEFUALT_POLLING_DELAY;
+ data->batch_latency_buf[sensor_index] = 0;
+ data->batch_opt_buf[sensor_index] = 0;
+ data->check_status[sensor_index] = INITIALIZATION_STATE;
+ }
+
+ data->delay_buf[SSP_BIO_HRM_LIB] = 100;
+
+ data->shut_down = true;
+ data->time_syncing = true;
+
+ INIT_LIST_HEAD(&data->pending_list);
+
+ atomic_set(&data->enable_refcount, 0);
+}
+
+int initialize_mcu(struct ssp_data *data)
+{
+ int ret;
+
+ clean_pending_list(data);
+
+ ret = get_chipid(data);
+ if (ret != DEVICE_ID) {
+ dev_err(&data->spi->dev, "%s - MCU %s ret = %d\n", __func__,
+ ret < 0 ? "is not working" : "identyfication failed",
+ ret);
+ return ret >= 0 ? -ENODEV : ret;
+ }
+
+ dev_info(&data->spi->dev, "MCU device ID = %d, reading ID = %d\n",
+ DEVICE_ID, ret);
+
+ ret = set_sensor_position(data);
+ if (ret < 0) {
+ dev_err(&data->spi->dev, "%s - set_sensor_position failed\n",
+ __func__);
+ return ret;
+ }
+
+ ret = set_magnetic_static_matrix(data);
+ if (ret < 0)
+ dev_err(&data->spi->dev,
+ "%s - set_magnetic_static_matrix failed\n", __func__);
+
+ ret = get_fuserom_data(data);
+ if (ret < 0)
+ dev_err(&data->spi->dev,
+ "%s - get_fuserom_data failed\n", __func__);
+
+ data->sensor_state = get_sensor_scanning_info(data);
+ if (data->sensor_state == 0) {
+ dev_err(&data->spi->dev,
+ "%s - get_sensor_scanning_info failed\n", __func__);
+ return -1;
+ }
+
+ data->cur_firm_rev = get_firmware_rev(data);
+ dev_info(&data->spi->dev, "MCU Firm Rev : New = %8u\n",
+ data->cur_firm_rev);
+
+ return ssp_send_cmd(data, MSG2SSP_AP_MCU_DUMP_CHECK, 0);
+}
+
+static void work_function_firmware_update(struct work_struct *work)
+{
+ struct ssp_data *data = container_of((struct delayed_work *)work,
+ struct ssp_data, work_firmware);
+ int ret;
+
+ dev_info(&data->spi->dev, "%s, is called\n", __func__);
+
+ ret = forced_to_download_binary(data, KERNEL_BINARY);
+ if (ret < 0) {
+ dev_err(&data->spi->dev,
+ "%s, forced_to_download_binary failed!\n",
+ __func__);
+ return;
+ }
+
+ queue_refresh_task(data, SSP_SW_RESET_TIME);
+
+ dev_info(&data->spi->dev, "%s done\n", __func__);
+}
+
+#ifdef CONFIG_OF
+static struct of_device_id ssp_of_match[] = {
+ {
+ .compatible = "samsung,sensorhub-rinato",
+ .data = &rinato_info,
+ }, {
+ .compatible = "samsung,sensorhub-thermostat",
+ .data = &thermostat_info,
+ },
+};
+MODULE_DEVICE_TABLE(of, ssp_of_match);
+
+static struct ssp_data *ssp_parse_dt(struct device *dev)
+{
+ int ret, len;
+ struct ssp_data *pdata;
+ struct device_node *node = dev->of_node;
+ const void *prop;
+ const struct of_device_id *match;
+
+ pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL);
+ if (!pdata)
+ return NULL;
+
+ pdata->mcu_ap_int = of_get_named_gpio(node, "mcu-ap-int", 0);
+ if (pdata->mcu_ap_int < 0)
+ goto err_free_pd;
+
+ pdata->ap_mcu_int = of_get_named_gpio(node, "ap-mcu-int", 0);
+ if (pdata->ap_mcu_int < 0)
+ goto err_free_pd;
+
+ pdata->mcu_reset = of_get_named_gpio(node, "mcu-reset", 0);
+ if (pdata->mcu_reset < 0)
+ goto err_free_pd;
+
+ ret = devm_gpio_request_one(dev, pdata->ap_mcu_int, GPIOF_OUT_INIT_HIGH,
+ "ap-mcu-int");
+ if (ret)
+ goto err_free_pd;
+
+ ret = devm_gpio_request_one(dev, pdata->mcu_reset, GPIOF_OUT_INIT_HIGH,
+ "mcu-reset");
+ if (ret)
+ goto err_ap_mcu;
+
+ match = of_match_node(ssp_of_match, node);
+ if (!match)
+ goto err_ap_mcu;
+
+ pdata->sensorhub_info = (struct sensorhub_info *) match->data;
+
+ ret = of_platform_populate(node, NULL, NULL, dev);
+ if (ret < 0) {
+ dev_err(dev, "of_platform_populate fail\n");
+ goto err_ap_mcu;
+ }
+
+ prop = of_get_property(node, "mag-table", &len);
+ if (IS_ERR(prop)) {
+ dev_err(dev, "get prop failed\n");
+ goto err_mcu_reset;
+ }
+
+ pdata->mag_matrix_size = len;
+
+ pdata->mag_matrix = devm_kzalloc(dev, sizeof(len), GFP_KERNEL);
+ if (pdata->mag_matrix == NULL)
+ goto err_mcu_reset;
+
+ memcpy(pdata->mag_matrix, prop, len);
+
+ return pdata;
+
+err_mcu_reset:
+ devm_gpio_free(dev, pdata->mcu_reset);
+err_ap_mcu:
+ devm_gpio_free(dev, pdata->ap_mcu_int);
+err_free_pd:
+ kfree(pdata);
+ 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)
+{
+ /*TODO 3rd level device - hide it*/
+ 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 = 0;
+ struct ssp_data *data;
+
+ data = (struct ssp_data *)spi->dev.platform_data;
+ if (!data) {
+ data = ssp_parse_dt(&spi->dev);
+ if (!data) {
+ dev_err(&spi->dev,
+ "%s:Failed to find platform data\n", __func__);
+ return -ENODEV;
+ }
+ }
+
+ /* FIXME */
+ data->hrm_supply_18 = regulator_get(&spi->dev, "hrm18");
+ if (IS_ERR(data->hrm_supply_18)) {
+ data->hrm_supply_18 = NULL;
+ dev_err(&spi->dev, "Regulator get fail\n");
+ }
+
+ /* FIXME */
+ data->hrm_supply_33 = regulator_get(&spi->dev, "hrm33");
+ if (IS_ERR(data->hrm_supply_33)) {
+ data->hrm_supply_33 = NULL;
+ dev_err(&spi->dev, "Regulator get fail\n");
+ }
+
+ if (data->get_positions) {
+ data->get_positions(&data->accel_position,
+ &data->mag_position);
+ } else {
+ data->accel_position = 0;
+ data->mag_position = 0;
+ }
+
+ spi->mode = SPI_MODE_1;
+ if (spi_setup(spi)) {
+ dev_err(&spi->dev, "Failed to setup spi\n");
+ goto err_setup;
+ }
+
+ data->fw_dl_state = FW_DL_STATE_NONE;
+ data->spi = spi;
+ spi_set_drvdata(spi, data);
+
+ mutex_init(&data->comm_mutex);
+ mutex_init(&data->pending_mutex);
+
+ initialize_variable(data);
+ INIT_DELAYED_WORK(&data->work_firmware, work_function_firmware_update);
+
+ ret = initialize_wdt_timer(data);
+ if (ret < 0) {
+ dev_err(&spi->dev, "Could not create workqueue\n");
+ goto err_create_workqueue;
+ }
+
+ ret = request_threaded_irq(data->spi->irq, NULL,
+ sensordata_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;
+ }
+
+ disable_irq(data->spi->irq);
+ ssp_enable(data, true);
+
+ data->fw_dl_state = check_fwbl(data);
+
+ if (data->fw_dl_state == FW_DL_STATE_NONE) {
+ ret = initialize_mcu(data);
+ if (ret < 0) {
+ dev_err(&spi->dev, "Initialize_mcu failed\n");
+ goto err_read_reg;
+ }
+ }
+
+ if (data->hrm_supply_18) {
+ ret = regulator_enable(data->hrm_supply_18);
+ if (ret < 0) {
+ dev_err(&spi->dev, "Regulator enable fail\n");
+ goto err_read_reg;
+ }
+ }
+
+ if (data->hrm_supply_33) {
+ ret = regulator_enable(data->hrm_supply_33);
+ if (ret < 0) {
+ dev_err(&spi->dev,
+ "Regulator enable 3.3 V fail\n");
+ goto err_read_reg;
+ }
+ }
+
+ if (data->fw_dl_state == FW_DL_STATE_NEED_TO_SCHEDULE) {
+ dev_info(&spi->dev, "Firmware update is scheduled\n");
+ queue_delayed_work(system_power_efficient_wq,
+ &data->work_firmware, msecs_to_jiffies(1000));
+ data->fw_dl_state = FW_DL_STATE_SCHEDULED;
+ } else if (data->fw_dl_state == FW_DL_STATE_FAIL) {
+ data->shut_down = true;
+ }
+
+ return 0;
+
+err_read_reg:
+ free_irq(data->spi->irq, data);
+err_setup_irq:
+ destroy_workqueue(data->wdt_wq);
+err_create_workqueue:
+ mutex_destroy(&data->comm_mutex);
+ mutex_destroy(&data->pending_mutex);
+err_setup:
+ kfree(data);
+ dev_err(&spi->dev, "Probe failed!\n");
+
+ return ret;
+}
+
+static void ssp_shutdown(struct spi_device *spi)
+{
+ struct ssp_data *data = spi_get_drvdata(spi);
+
+ if (ssp_send_cmd(data, MSG2SSP_AP_STATUS_SHUTDOWN, 0) < 0)
+ dev_err(&data->spi->dev, "MSG2SSP_AP_STATUS_SHUTDOWN failed\n");
+
+ ssp_enable(data, false);
+ disable_wdt_timer(data);
+
+ if (data->hrm_supply_18)
+ regulator_put(data->hrm_supply_18);
+
+ if (data->hrm_supply_33)
+ regulator_put(data->hrm_supply_33);
+}
+
+
+static int ssp_remove(struct spi_device *spi)
+{
+ struct ssp_data *data = spi_get_drvdata(spi);
+
+ if (data->fw_dl_state >= FW_DL_STATE_SCHEDULED &&
+ data->fw_dl_state < FW_DL_STATE_DONE) {
+ dev_err(&data->spi->dev,
+ "cancel_delayed_work_sync state = %d\n",
+ data->fw_dl_state);
+ cancel_delayed_work_sync(&data->work_firmware);
+ }
+
+ if (ssp_send_cmd(data, MSG2SSP_AP_STATUS_SHUTDOWN, 0) < 0)
+ dev_err(&data->spi->dev, "MSG2SSP_AP_STATUS_SHUTDOWN failed\n");
+
+ ssp_enable(data, false);
+ disable_wdt_timer(data);
+
+ clean_pending_list(data);
+
+ free_irq(data->spi->irq, data);
+
+ del_timer_sync(&data->wdt_timer);
+ cancel_work_sync(&data->work_wdt);
+ destroy_workqueue(data->wdt_wq);
+
+ mutex_destroy(&data->comm_mutex);
+ mutex_destroy(&data->pending_mutex);
+
+ if (data->hrm_supply_18)
+ regulator_put(data->hrm_supply_18);
+
+ if (data->hrm_supply_33)
+ regulator_put(data->hrm_supply_33);
+
+ kfree(data->mag_matrix);
+
+#ifdef CONFIG_OF
+ of_platform_depopulate(&spi->dev);
+#endif
+
+ kfree(data);
+
+ return 0;
+}
+
+static int ssp_suspend(struct device *dev)
+{
+ int ret = 0;
+ struct ssp_data *data = spi_get_drvdata(to_spi_device(dev));
+
+ data->last_resume_state = MSG2SSP_AP_STATUS_SUSPEND;
+
+ if (atomic_read(&data->enable_refcount) > 0)
+ disable_wdt_timer(data);
+
+ if (ssp_send_cmd(data, MSG2SSP_AP_STATUS_SUSPEND, 0) < 0)
+ dev_err(&data->spi->dev,
+ "%s MSG2SSP_AP_STATUS_SUSPEND failed\n", __func__);
+
+ data->time_syncing = false;
+ disable_irq(data->spi->irq);
+
+ if (data->hrm_supply_18) {
+ ret = regulator_disable(data->hrm_supply_18);
+ if (ret < 0) {
+ dev_err(&data->spi->dev,
+ "Regulator disable fail\n");
+ }
+ }
+
+ if (data->hrm_supply_33) {
+ ret = regulator_disable(data->hrm_supply_33);
+ if (ret < 0) {
+ dev_err(&data->spi->dev,
+ "Regulator disable fail\n");
+ }
+ }
+
+ return ret;
+}
+
+static int ssp_resume(struct device *dev)
+{
+ int ret = 0;
+ struct ssp_data *data = spi_get_drvdata(to_spi_device(dev));
+
+ if (data->hrm_supply_18) {
+ ret = regulator_enable(data->hrm_supply_18);
+ if (ret < 0) {
+ dev_err(&data->spi->dev,
+ "Regulator enable hrm 1.8 fail\n");
+ } /* do not return jet */
+ }
+
+ if (data->hrm_supply_33) {
+ ret = regulator_enable(data->hrm_supply_33);
+ if (ret < 0) {
+ dev_err(&data->spi->dev,
+ "Regulator enable hrm 3.3 fail\n");
+ } /* do not return jet */
+ }
+
+ enable_irq(data->spi->irq);
+
+ if (atomic_read(&data->enable_refcount) > 0)
+ enable_wdt_timer(data);
+
+ if (ssp_send_cmd(data, MSG2SSP_AP_STATUS_RESUME, 0) < 0)
+ dev_err(&data->spi->dev,
+ "%s MSG2SSP_AP_STATUS_RESUME failed\n", __func__);
+
+ data->last_resume_state = MSG2SSP_AP_STATUS_RESUME;
+
+ return ret;
+}
+
+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,
+ .shutdown = ssp_shutdown,
+ .driver = {
+ .pm = &ssp_pm_ops,
+ .bus = &spi_bus_type,
+ .owner = THIS_MODULE,
+ .of_match_table = of_match_ptr(ssp_of_match),
+ .name = "ssp-spi"
+ },
+};
+
+static int __init ssp_stm_spi_init(void)
+{
+ int ret;
+
+ ret = spi_register_driver(&ssp_driver);
+ if (ret)
+ pr_err("Failed to register ssp-spi %x\n", ret);
+
+ return ret;
+}
+
+static void __exit ssp_stm_spi_exit(void)
+{
+ spi_unregister_driver(&ssp_driver);
+}
+
+module_init(ssp_stm_spi_init);
+module_exit(ssp_stm_spi_exit);
+
+MODULE_DESCRIPTION("ssp spi driver");
+MODULE_AUTHOR("Samsung Electronics");
+MODULE_LICENSE("GPL");
diff --git a/drivers/misc/sensorhub/ssp_firmware.c b/drivers/misc/sensorhub/ssp_firmware.c
new file mode 100644
index 0000000..2554d64
--- /dev/null
+++ b/drivers/misc/sensorhub/ssp_firmware.c
@@ -0,0 +1,571 @@
+/*
+ * Copyright (C) 2012, 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/firmware.h>
+#include "ssp.h"
+
+#define BOOT_SPI_HZ 500000
+#define NORM_SPI_HZ 5000000
+
+#define APP_SLAVE_ADDR 0x18
+#define BOOTLOADER_SLAVE_ADDR 0x26
+
+/* Bootloader mode status */
+#define BL_WAITING_BOOTLOAD_CMD 0xc0 /* valid 7 6 bit only */
+#define BL_WAITING_FRAME_DATA 0x80 /* valid 7 6 bit only */
+#define BL_FRAME_CRC_CHECK 0x02
+#define BL_FRAME_CRC_FAIL 0x03
+#define BL_FRAME_CRC_PASS 0x04
+#define BL_APP_CRC_FAIL 0x40 /* valid 7 8 bit only */
+#define BL_BOOT_STATUS_MASK 0x3f
+
+/* Command to unlock bootloader */
+#define BL_UNLOCK_CMD_MSB 0xaa
+#define BL_UNLOCK_CMD_LSB 0xdc
+
+#define SEND_ADDR_LEN 5
+#define BL_SPI_SOF 0x5A
+#define BL_ACK 0x79
+#define BL_ACK2 0xF9
+#define BL_NACK 0x1F
+
+#define STM_MAX_XFER_SIZE 256
+#define STM_MAX_BUFFER_SIZE 260
+#define STM_APP_ADDR 0x08000000
+
+#define BYTE_DELAY_READ 10
+#define BYTE_DELAY_WRITE 8
+
+#define DEF_ACK_ERASE_NUMBER 7000
+#define DEF_ACKCMD_NUMBER 30
+#define DEF_ACKROOF_NUMBER 20
+
+#define WMEM_COMMAND 0x31 /* Write Memory command */
+#define GO_COMMAND 0x21 /* GO command */
+#define EXT_ER_COMMAND 0x44 /* Erase Memory command */
+
+#define XOR_RMEM_COMMAND 0xEE /* Read Memory command */
+
+#define XOR_WMEM_COMMAND 0xCE /* Write Memory command */
+#define XOR_GO_COMMAND 0xDE /* GO command */
+#define XOR_EXT_ER_COMMAND 0xBB /* Erase Memory command */
+
+#define EXT_ER_DATA_LEN 3
+#define BLMODE_RETRYCOUNT 3
+
+struct stm32fwu_spi_cmd {
+ u8 cmd;
+ u8 xor_cmd;
+ u8 ack_pad; /* Send this when waiting for an ACK */
+ u8 reserved;
+ int status; /* ACK or NACK (or error) */
+ int timeout; /* This is number of retries */
+ int ack_loops;
+};
+
+static int stm32fwu_spi_wait_for_ack(struct spi_device *spi,
+ struct stm32fwu_spi_cmd *cmd, u8 dummy_bytes)
+{
+ struct spi_message m;
+ char tx_buf = 0x0;
+ char rx_buf = 0x0;
+ struct spi_transfer t = {
+ .tx_buf = &tx_buf,
+ .rx_buf = &rx_buf,
+ .len = 1,
+ .bits_per_word = 8,
+ };
+ int i = 0;
+ int ret;
+
+ while (i < cmd->timeout) {
+ tx_buf = dummy_bytes;
+ spi_message_init(&m);
+ spi_message_add_tail(&t, &m);
+
+ ret = spi_sync(spi, &m);
+ if (ret < 0) {
+ dev_err(&spi->dev, "%s: spi_sync error returned %d\n",
+ __func__, ret);
+ return ret;
+ } else if ((rx_buf == BL_ACK) || (rx_buf == BL_ACK2)) {
+ cmd->ack_loops = i;
+ return BL_ACK;
+ } else if (rx_buf == BL_NACK) {
+ return (int)rx_buf;
+ }
+ usleep_range(1000, 1100);
+ i++;
+ }
+
+ dev_err(&spi->dev, "%s, Timeout after %d loops\n", __func__,
+ cmd->timeout);
+
+ return -EIO;
+}
+
+static int stm32fwu_spi_send_cmd(struct spi_device *spi,
+ struct stm32fwu_spi_cmd *cmd)
+{
+ u8 tx_buf[3] = {0,};
+ u8 rx_buf[3] = {0,};
+ u8 dummy_byte = 0;
+ struct spi_message m;
+ int ret;
+ struct spi_transfer t = {
+ .tx_buf = tx_buf,
+ .rx_buf = rx_buf,
+ .len = 3,
+ .bits_per_word = 8,
+ };
+
+ spi_message_init(&m);
+
+ tx_buf[0] = BL_SPI_SOF;
+ tx_buf[1] = cmd->cmd;
+ tx_buf[2] = cmd->xor_cmd;
+
+ spi_message_add_tail(&t, &m);
+
+ ret = spi_sync(spi, &m);
+ if (ret < 0) {
+ dev_err(&spi->dev, "%s: spi_sync error returned %d\n",
+ __func__, ret);
+ return ret;
+ }
+
+ dummy_byte = cmd->ack_pad;
+
+ /* check for ack/nack and loop until found */
+ ret = stm32fwu_spi_wait_for_ack(spi, cmd, dummy_byte);
+ cmd->status = ret;
+ if (ret != BL_ACK) {
+ dev_err(&spi->dev, "%s, Got NAK or Error %d\n", __func__, ret);
+ return ret;
+ }
+
+ return ret;
+}
+
+
+static int stm32fwu_spi_write(struct spi_device *spi,
+ const u8 *buffer, ssize_t len)
+{
+ int ret;
+ u8 rx_buf[STM_MAX_BUFFER_SIZE] = {0,};
+ struct spi_message m;
+ struct spi_transfer t = {
+ .tx_buf = buffer,
+ .rx_buf = rx_buf,
+ .len = len,
+ .bits_per_word = 8,
+ };
+
+ spi_message_init(&m);
+ spi_message_add_tail(&t, &m);
+
+ ret = spi_sync(spi, &m);
+ if (ret < 0) {
+ dev_err(&spi->dev, "%s: spi_sync error returned %d\n",
+ __func__, ret);
+ return ret;
+ }
+
+ return len;
+}
+
+static int send_addr(struct spi_device *spi, u32 fw_addr, int send_short)
+{
+ int res;
+ int i = send_short;
+ int len = SEND_ADDR_LEN - send_short;
+ u8 header[SEND_ADDR_LEN];
+ struct stm32fwu_spi_cmd dummy_cmd;
+
+ dummy_cmd.timeout = DEF_ACKROOF_NUMBER;
+
+ header[0] = (u8)((fw_addr >> 24) & 0xFF);
+ header[1] = (u8)((fw_addr >> 16) & 0xFF);
+ header[2] = (u8)((fw_addr >> 8) & 0xFF);
+ header[3] = (u8)(fw_addr & 0xFF);
+ header[4] = header[0] ^ header[1] ^ header[2] ^ header[3];
+
+ res = stm32fwu_spi_write(spi, &header[i], len);
+ if (res < len) {
+ dev_err(&spi->dev, "%s, spi_write returned %d\n", __func__,
+ res);
+ return (res > 0) ? -EIO : res;
+ }
+
+ res = stm32fwu_spi_wait_for_ack(spi, &dummy_cmd, BL_ACK);
+ if (res != BL_ACK) {
+ dev_err(&spi->dev, "%s, rcv_ack returned 0x%x\n", __func__,
+ res);
+ return res;
+ }
+ return 0;
+}
+
+static int fw_write_stm(struct spi_device *spi, u32 fw_addr,
+ int len, const u8 *buffer)
+{
+ int res;
+ struct stm32fwu_spi_cmd cmd;
+ struct stm32fwu_spi_cmd dummy_cmd;
+ int i;
+ u8 xor = 0;
+ u8 send_buff[STM_MAX_BUFFER_SIZE] = {0,};
+
+ cmd.cmd = WMEM_COMMAND;
+ cmd.xor_cmd = XOR_WMEM_COMMAND;
+ cmd.timeout = DEF_ACKCMD_NUMBER;
+ cmd.ack_pad = (u8)((fw_addr >> 24) & 0xFF);
+
+ if (len > STM_MAX_XFER_SIZE) {
+ dev_err(&spi->dev,
+ "Can't send more than 256 bytes per transaction\n");
+ return -EINVAL;
+ }
+
+ send_buff[0] = len - 1;
+ memcpy(&send_buff[1], buffer, len);
+ for (i = 0; i < (len + 1); i++)
+ xor ^= send_buff[i];
+
+ send_buff[len + 1] = xor;
+
+ res = stm32fwu_spi_send_cmd(spi, &cmd);
+ if (res != BL_ACK) {
+ dev_err(&spi->dev, "%s, spi_send_cmd returned %d\n", __func__,
+ res);
+ return res;
+ }
+
+ if (cmd.ack_loops > 0)
+ res = send_addr(spi, fw_addr, 1);
+ else
+ res = send_addr(spi, fw_addr, 0);
+
+ if (res != 0) {
+ dev_err(&spi->dev, "%s, send_addr returned %d\n", __func__,
+ res);
+ return res;
+ }
+
+ res = stm32fwu_spi_write(spi, send_buff, len + 2);
+ if (res < len) {
+ dev_err(&spi->dev, "%s, spi_write returned %d\n", __func__,
+ res);
+ return ((res > 0) ? -EIO : res);
+ }
+
+ dummy_cmd.timeout = DEF_ACKROOF_NUMBER;
+
+ usleep_range(100, 150);
+
+ res = stm32fwu_spi_wait_for_ack(spi, &dummy_cmd, BL_ACK);
+ if (res == BL_ACK) {
+ return len;
+ } else if (res == BL_NACK) {
+ dev_err(&spi->dev,
+ "Got NAK waiting for WRITE_MEM to complete\n");
+ return -EPROTO;
+ }
+
+ dev_err(&spi->dev, "timeout waiting for ACK for WRITE_MEM command\n");
+ return -ETIME;
+}
+
+static int fw_erase_stm(struct spi_device *spi)
+{
+ struct stm32fwu_spi_cmd cmd;
+ struct stm32fwu_spi_cmd dummy_cmd;
+ int ret;
+ char buff[EXT_ER_DATA_LEN] = {0xff, 0xff, 0x00};
+
+ cmd.cmd = EXT_ER_COMMAND;
+ cmd.xor_cmd = XOR_EXT_ER_COMMAND;
+ cmd.timeout = DEF_ACKCMD_NUMBER;
+ cmd.ack_pad = 0xFF;
+
+ ret = stm32fwu_spi_send_cmd(spi, &cmd);
+ if (ret < 0) {
+ dev_err(&spi->dev, "fw_erase failed (-EIO)\n");
+ return -EIO;
+ } else if (ret != BL_ACK) {
+ return ret;
+ }
+
+ if (cmd.ack_loops == 0)
+ ret = stm32fwu_spi_write(spi, buff, EXT_ER_DATA_LEN);
+ else
+ ret = stm32fwu_spi_write(spi, buff, EXT_ER_DATA_LEN-1);
+
+ if (ret < (EXT_ER_DATA_LEN - cmd.ack_loops))
+ return -EPROTO;
+
+ dummy_cmd.timeout = DEF_ACK_ERASE_NUMBER;
+ ret = stm32fwu_spi_wait_for_ack(spi, &dummy_cmd, BL_ACK);
+
+ ssp_info("%s: stm32fwu_spi_wait_for_ack returned %d (0x%x)\n",
+ __func__, ret, ret);
+
+ if (ret == BL_ACK)
+ return 0;
+ else if (ret == BL_NACK)
+ return -EPROTO;
+
+ return -ETIME;
+
+}
+
+static int load_kernel_fw_bootmode(struct spi_device *spi, const char *pfn)
+{
+ int ret, remaining;
+ unsigned int pos = 0;
+ unsigned int fw_addr = STM_APP_ADDR;
+ int block = STM_MAX_XFER_SIZE;
+ int count = 0, err_count = 0, retry_count = 0;
+ const struct firmware *fw = NULL;
+
+ dev_info(&spi->dev, "ssp_load_fw start\n");
+
+ ret = request_firmware(&fw, pfn, &spi->dev);
+ if (ret) {
+ dev_err(&spi->dev, "Unable to open firmware %s\n", pfn);
+ return ret;
+ }
+
+ remaining = fw->size;
+ while (remaining > 0) {
+ if (block > remaining)
+ block = remaining;
+
+ while (retry_count < 3) {
+ ret = fw_write_stm(spi, fw_addr, block, fw->data + pos);
+ if (ret < block) {
+ dev_err(&spi->dev,
+ "Returned %d writing to addr 0x%08X\n",
+ ret,
+ fw_addr);
+ retry_count++;
+ err_count++;
+ } else {
+ retry_count = 0;
+ break;
+ }
+ }
+
+ if (ret < block) {
+ dev_err(&spi->dev,
+ "Writing MEM failed: %d, retry cont: %d\n",
+ ret, err_count);
+ ret = -EIO;
+ goto out_load_kernel;
+ }
+
+ remaining -= block;
+ pos += block;
+ fw_addr += block;
+ if (count++ == 20) {
+ dev_info(&spi->dev, "Updated %u bytes / %u bytes\n",
+ pos, fw->size);
+ count = 0;
+ }
+ }
+
+ dev_info(&spi->dev,
+ "Firmware download success.(%d bytes, retry %d)\n", pos,
+ err_count);
+
+out_load_kernel:
+ release_firmware(fw);
+ return ret;
+}
+
+static int change_to_bootmode(struct ssp_data *data)
+{
+ int ret, i;
+ char syncb = BL_SPI_SOF;
+ struct stm32fwu_spi_cmd dummy_cmd;
+
+ dev_info(&data->spi->dev, "ssp_change_to_bootmode\n");
+
+ dummy_cmd.timeout = DEF_ACKCMD_NUMBER;
+
+ gpio_set_value(data->mcu_reset, 0);
+ usleep_range(4000, 4100);
+ gpio_set_value(data->mcu_reset, 1);
+ msleep(45);
+
+ for (i = 0; i < 9; i++) {
+ gpio_set_value(data->mcu_reset, 0);
+ usleep_range(4000, 4100);
+ gpio_set_value(data->mcu_reset, 1);
+ usleep_range(15000, 15500);
+ }
+
+ ret = stm32fwu_spi_write(data->spi, &syncb, 1);
+ ssp_info("stm32fwu_spi_write(sync byte) returned %d\n", ret);
+
+ ret = stm32fwu_spi_wait_for_ack(data->spi, &dummy_cmd, BL_ACK);
+ ssp_info("stm32fwu_spi_wait_for_ack returned %d (0x%x)\n", ret, ret);
+
+ return ret;
+}
+
+void toggle_mcu_reset(struct ssp_data *data)
+{
+ gpio_set_value(data->mcu_reset, 0);
+ usleep_range(1000, 1200);
+ gpio_set_value(data->mcu_reset, 1);
+ msleep(50);
+}
+
+static int update_mcu_bin(struct ssp_data *data, int bin_type)
+{
+ int retry = BLMODE_RETRYCOUNT, ret = 0;
+ struct stm32fwu_spi_cmd cmd;
+
+ cmd.cmd = GO_COMMAND;
+ cmd.xor_cmd = XOR_GO_COMMAND;
+ cmd.timeout = 1000;
+ cmd.ack_pad = (u8)((STM_APP_ADDR >> 24) & 0xFF);
+
+ dev_info(&data->spi->dev, "update_mcu_bin\n");
+
+ do {
+ ret = change_to_bootmode(data);
+ dev_info(&data->spi->dev, "bootmode %d, retry = %d\n", ret,
+ 3 - retry);
+ } while (retry-- > 0 && ret != BL_ACK);
+
+ if (ret != BL_ACK) {
+ dev_err(&data->spi->dev, "%s, change_to_bootmode failed %d\n",
+ __func__, ret);
+ return ret;
+ }
+
+ ret = fw_erase_stm(data->spi);
+ if (ret < 0) {
+ dev_err(&data->spi->dev, "%s, fw_erase_stm failed %d\n",
+ __func__, ret);
+ return ret;
+ }
+
+ switch (bin_type) {
+ case KERNEL_BINARY:
+ ret = load_kernel_fw_bootmode(data->spi,
+ data->sensorhub_info->fw_name);
+ break;
+ case KERNEL_CRASHED_BINARY:
+ ret = load_kernel_fw_bootmode(data->spi,
+ data->sensorhub_info->fw_crashed_name);
+ break;
+ default:
+ dev_err(&data->spi->dev, "binary type error!!\n");
+ }
+
+ /* STM : GO USER ADDR */
+ stm32fwu_spi_send_cmd(data->spi, &cmd);
+ if (cmd.ack_loops > 0)
+ send_addr(data->spi, STM_APP_ADDR, 1);
+ else
+ send_addr(data->spi, STM_APP_ADDR, 0);
+
+ return ret;
+}
+
+int forced_to_download_binary(struct ssp_data *data, int bin_type)
+{
+ int ret = 0, retry = 3;
+
+ ssp_dbg("%s, mcu binany update!\n", __func__);
+ ssp_enable(data, false);
+
+ data->fw_dl_state = FW_DL_STATE_DOWNLOADING;
+ dev_info(&data->spi->dev, "%s, DL state = %d\n", __func__,
+ data->fw_dl_state);
+
+ data->spi->max_speed_hz = BOOT_SPI_HZ;
+ if (spi_setup(data->spi))
+ dev_err(&data->spi->dev, "failed to setup spi for ssp_boot\n");
+
+ do {
+ dev_info(&data->spi->dev, "%d try\n", 3 - retry);
+ ret = update_mcu_bin(data, bin_type);
+ } while (retry-- > 0 && ret < 0);
+
+ /* FIXME*/
+ data->spi->max_speed_hz = NORM_SPI_HZ;
+ if (spi_setup(data->spi))
+ dev_err(&data->spi->dev, "failed to setup spi for ssp_norm\n");
+
+ if (ret < 0) {
+ ssp_dbg("%s - update_mcu_bin failed!\n", __func__);
+ data->fw_dl_state = FW_DL_STATE_FAIL;
+ return ret;
+ }
+
+ data->fw_dl_state = FW_DL_STATE_SYNC;
+ dev_info(&data->spi->dev, "%s, DL state = %d\n", __func__,
+ data->fw_dl_state);
+
+ ssp_enable(data, true);
+
+ data->fw_dl_state = FW_DL_STATE_DONE;
+ dev_info(&data->spi->dev, "%s, DL state = %d\n", __func__,
+ data->fw_dl_state);
+
+ return ret;
+}
+
+int check_fwbl(struct ssp_data *data)
+{
+ int retries = 0;
+
+ while (retries++ < 5) {
+ data->cur_firm_rev = get_firmware_rev(data);
+ if (data->cur_firm_rev == SSP_INVALID_REVISION
+ || data->cur_firm_rev == SSP_INVALID_REVISION2
+ || data->cur_firm_rev < 0) {
+ pr_warn("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 FW_DL_STATE_NEED_TO_SCHEDULE;
+
+ } else {
+ if (data->cur_firm_rev != data->sensorhub_info->fw_rev) {
+ dev_info(&data->spi->dev,
+ "MCU Firm Rev : Old = %8u, New = %8u\n",
+ data->cur_firm_rev,
+ data->sensorhub_info->fw_rev);
+
+ return 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);
+ }
+
+ return FW_DL_STATE_NONE;
+}
diff --git a/drivers/misc/sensorhub/ssp_spi.c b/drivers/misc/sensorhub/ssp_spi.c
new file mode 100644
index 0000000..c546c4e
--- /dev/null
+++ b/drivers/misc/sensorhub/ssp_spi.c
@@ -0,0 +1,700 @@
+/*
+ * Copyright (C) 2012, 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 GET_MESSAGE_TYPE(data) (data & (3 << SSP_SPI))
+
+/* SSP -> AP Instruction */
+#define MSG2AP_INST_BYPASS_DATA 0x37
+#define MSG2AP_INST_LIBRARY_DATA 0x01
+#define MSG2AP_INST_DEBUG_DATA 0x03
+#define MSG2AP_INST_BIG_DATA 0x04
+#define MSG2AP_INST_META_DATA 0x05
+#define MSG2AP_INST_TIME_SYNC 0x06
+#define MSG2AP_INST_RESET 0x07
+
+#define UNINPLEMENTED -1
+
+static const int 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] = UNINPLEMENTED,
+ [SSP_GEOMAGNETIC_RAW] = UNINPLEMENTED,
+ [SSP_GEOMAGNETIC_SENSOR] = UNINPLEMENTED,
+ [SSP_PRESSURE_SENSOR] = UNINPLEMENTED,
+ [SSP_GESTURE_SENSOR] = UNINPLEMENTED,
+ [SSP_PROXIMITY_SENSOR] = UNINPLEMENTED,
+ [SSP_TEMPERATURE_HUMIDITY_SENSOR] = UNINPLEMENTED,
+ [SSP_LIGHT_SENSOR] = UNINPLEMENTED,
+ [SSP_PROXIMITY_RAW] = UNINPLEMENTED,
+ [SSP_ORIENTATION_SENSOR] = UNINPLEMENTED,
+ [SSP_STEP_DETECTOR] = UNINPLEMENTED,
+ [SSP_SIG_MOTION_SENSOR] = UNINPLEMENTED,
+ [SSP_GYRO_UNCALIB_SENSOR] = UNINPLEMENTED,
+ [SSP_GAME_ROTATION_VECTOR] = UNINPLEMENTED,
+ [SSP_ROTATION_VECTOR] = UNINPLEMENTED,
+ [SSP_STEP_COUNTER] = UNINPLEMENTED ,
+ [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,
+};
+
+static int 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 : -1;
+ }
+
+ ssp_dbg("[SSP]: MSG From MCU - %s\n", &data_frame[*data_index]);
+ *data_index += length;
+ return 0;
+}
+
+static void clean_msg(struct ssp_msg *msg)
+{
+ if (msg->free_buffer)
+ kfree(msg->buffer);
+ kfree(msg);
+}
+
+static int do_transfer(struct ssp_data *data, struct ssp_msg *msg,
+ struct completion *done, int timeout)
+{
+ int status = 0;
+ int delay_cnt = 0;
+ bool msg_dead = false, ssp_down = false;
+ /*FIXME REVISIT*/
+ const bool use_no_irq = msg->length == 0;
+
+ msg->dead_hook = &msg_dead;
+ msg->dead = false;
+ msg->done = done;
+
+ mutex_lock(&data->comm_mutex);
+
+ gpio_set_value_cansleep(data->ap_mcu_int, 0);
+ while (gpio_get_value_cansleep(data->mcu_ap_int)) {
+ usleep_range(3000, 3500);
+ ssp_down = data->shut_down;
+ if (ssp_down || delay_cnt++ > 500) {
+ dev_err(SSP_DEV, " %s - timeout 1\n", __func__);
+ gpio_set_value_cansleep(data->ap_mcu_int, 1);
+ status = -1;
+ goto exit;
+ }
+ }
+
+ status = spi_write(data->spi, msg, 9);
+ if (status != 0) {
+ dev_err(SSP_DEV, "%s spi_write fail\n", __func__);
+ gpio_set_value_cansleep(data->ap_mcu_int, 1);
+ status = -1;
+ goto exit;
+ }
+
+ if (!use_no_irq) {
+ mutex_lock(&data->pending_mutex);
+ list_add_tail(&msg->list, &data->pending_list);
+ mutex_unlock(&data->pending_mutex);
+ }
+
+ delay_cnt = 0;
+ gpio_set_value_cansleep(data->ap_mcu_int, 1);
+ while (!gpio_get_value_cansleep(data->mcu_ap_int)) {
+ usleep_range(3000, 3500);
+ ssp_down = data->shut_down;
+ if (ssp_down || delay_cnt++ > 500) {
+ dev_err(SSP_DEV, "%s - timeout 2\n", __func__);
+ status = -2;
+ goto exit;
+ }
+ }
+
+exit:
+ mutex_unlock(&data->comm_mutex);
+
+ if (ssp_down)
+ dev_err(SSP_DEV, "%s ssp down", __func__);
+
+ if (status == -1) {
+ data->time_out_cnt += ssp_down ? 0 : 1;
+ clean_msg(msg);
+ return status;
+ }
+
+ if (status == 0 && done != NULL)
+ if (wait_for_completion_timeout(done,
+ msecs_to_jiffies(timeout)) == 0)
+ status = -2;
+
+ mutex_lock(&data->pending_mutex);
+ if (!msg_dead) {
+ msg->done = NULL;
+ msg->dead_hook = NULL;
+
+ if (status != 0)
+ msg->dead = true;
+ if (status == -2)
+ data->time_out_cnt += ssp_down ? 0 : 1;
+ }
+ mutex_unlock(&data->pending_mutex);
+
+ if (use_no_irq)
+ clean_msg(msg);
+
+ return status;
+}
+
+static inline int ssp_spi_async(struct ssp_data *data, struct ssp_msg *msg)
+{
+ return 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);
+
+ /* len 0 means that we do not expect an answer after an interrupt */
+ if (msg->length == 0) {
+ dev_err(SSP_DEV, "%s length must not be 0\n", __func__);
+ clean_msg(msg);
+ return 0;
+ }
+
+ return do_transfer(data, msg, &done, timeout);
+}
+
+static int handle_big_data(struct ssp_data *data, char *dataframe,
+ int *idx) {
+ /* FIXME REWORK*/
+ u8 big_type = 0;
+ struct ssp_big *big = kzalloc(sizeof(*big), GFP_KERNEL);
+
+ big->data = data;
+ big_type = dataframe[(*idx)++];
+ memcpy(&big->length, dataframe + *idx, 4);
+ *idx += 4;
+ memcpy(&big->addr, dataframe + *idx, 4);
+ *idx += 4;
+
+ if (big_type >= BIG_TYPE_MAX) {
+ kfree(big);
+ return -EINVAL;
+ }
+
+ INIT_WORK(&big->work, data->ssp_big_task[big_type]);
+ queue_work(data->wdt_wq, &big->work);
+
+ return 0;
+}
+
+static int parse_dataframe(struct ssp_data *data, char *dataframe,
+ int len)
+{
+ int idx, sd, ret = 0;
+ u16 length = 0;
+ struct timespec ts;
+ struct ssp_sensor_data *sdata;
+ struct iio_dev **indio_devs = data->sensor_devs;
+
+ getnstimeofday(&ts);
+
+ for (idx = 0; idx < len;) {
+ switch (dataframe[idx++]) {
+ case 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 -1;
+ }
+
+ if (indio_devs[sd] != NULL) {
+ sdata = iio_priv(indio_devs[sd]);
+ if (sdata->process_data)
+ sdata->process_data(
+ indio_devs[sd],
+ &dataframe[idx],
+ data->timestamp);
+ }
+ idx += offset_map[sd];
+ break;
+ case MSG2AP_INST_DEBUG_DATA:
+ sd = print_mcu_debug(dataframe, &idx, len);
+ if (sd) {
+ dev_err(SSP_DEV,
+ "Mcu data frame3 error %d\n", sd);
+ return -1;
+ }
+ break;
+ case MSG2AP_INST_LIBRARY_DATA:
+ idx += length;
+ break;
+ case MSG2AP_INST_BIG_DATA:
+ handle_big_data(data, dataframe, &idx);
+ break;
+ case MSG2AP_INST_TIME_SYNC:
+ data->time_syncing = true;
+ break;
+ case MSG2AP_INST_RESET:
+ queue_refresh_task(data, 0);
+ break;
+ }
+ }
+
+ if (data->time_syncing)
+ data->timestamp = ts.tv_sec * 1000000000ULL + ts.tv_nsec;
+
+ return ret;
+}
+
+int select_irq_msg(struct ssp_data *data)
+{
+ bool found = false;
+ char *buffer;
+ unsigned char msg_type = 0;
+ char temp_buf[4] = {-1};
+ int ret = 0;
+ unsigned short length, msg_options;
+ __le16 len, mo;
+ struct ssp_msg *msg, *n;
+
+ ret = spi_read(data->spi, temp_buf, sizeof(temp_buf));
+ if (ret < 0) {
+ dev_err(SSP_DEV, "header read fail\n");
+ return ret;
+ }
+
+ memcpy(&mo, temp_buf, 2);
+ memcpy(&len, &temp_buf[2], 2);
+
+ length = le16_to_cpu(len);
+ msg_options = le16_to_cpu(mo);
+
+ msg_type = GET_MESSAGE_TYPE(msg_options);
+
+ switch (msg_type) {
+ case AP2HUB_READ:
+ case AP2HUB_WRITE:
+ mutex_lock(&data->pending_mutex);
+ if (!list_empty(&data->pending_list)) {
+ 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) {
+ dev_err(SSP_DEV, "Not match error %x\n",
+ msg_options);
+ goto exit;
+ }
+
+ if (msg->dead && !msg->free_buffer) {
+ msg->buffer = kzalloc(msg->length, GFP_KERNEL);
+ msg->free_buffer = 1;
+ } /* For dead msg, make a temporary buffer to read. */
+
+ if (msg_type == AP2HUB_READ)
+ ret = spi_read(data->spi, msg->buffer,
+ msg->length);
+ if (msg_type == AP2HUB_WRITE) {
+ ret = spi_write(data->spi, msg->buffer,
+ msg->length);
+ if (msg_options & AP2HUB_RETURN) {
+ msg->options =
+ AP2HUB_READ | AP2HUB_RETURN;
+ msg->length = 1;
+ list_add_tail(&msg->list,
+ &data->pending_list);
+ goto exit;
+ }
+ }
+
+ if (msg->done != NULL && !completion_done(msg->done))
+ complete(msg->done);
+ if (msg->dead_hook != NULL)
+ *(msg->dead_hook) = true;
+
+ clean_msg(msg);
+ } else
+ dev_err(SSP_DEV, "List empty error(%d)\n",
+ msg_type);
+exit:
+ mutex_unlock(&data->pending_mutex);
+ break;
+ case HUB2AP_WRITE:
+ buffer = kzalloc(length, GFP_KERNEL);
+ if (buffer == NULL) {
+ ret = -ENOMEM;
+ break;
+ }
+
+ ret = spi_read(data->spi, buffer, length);
+ if (ret < 0) {
+ dev_err(SSP_DEV, "spi read fail\n");
+ break;
+ }
+
+ /* FIXME no return ! */
+ parse_dataframe(data, buffer, length);
+
+ kfree(buffer);
+ break;
+
+ default:
+ dev_err(SSP_DEV, "unknown msg type\n");
+ break;
+ }
+
+ return ret;
+}
+
+void clean_pending_list(struct ssp_data *data)
+{
+ struct ssp_msg *msg, *n;
+
+ mutex_lock(&data->pending_mutex);
+ list_for_each_entry_safe(msg, n, &data->pending_list, list) {
+ list_del(&msg->list);
+ if (msg->done != NULL && !completion_done(msg->done))
+ complete(msg->done);
+ if (msg->dead_hook != NULL)
+ *(msg->dead_hook) = true;
+
+ clean_msg(msg);
+ }
+ mutex_unlock(&data->pending_mutex);
+}
+
+int ssp_send_cmd(struct ssp_data *data, char command, int arg)
+{
+ struct ssp_msg *msg = kzalloc(sizeof(*msg), GFP_KERNEL);
+
+ if (msg == NULL)
+ return -ENOMEM;
+
+
+ msg->cmd = command;
+ msg->length = 0;
+ msg->options = AP2HUB_WRITE;
+ msg->data = cpu_to_le32(arg);
+ msg->free_buffer = 0;
+
+ ssp_dbg("%s - command 0x%x %d\n", __func__, command, arg);
+
+ return ssp_spi_async(data, msg);
+}
+
+int ssp_send_instruction(struct ssp_data *data, unsigned char inst,
+ unsigned char sensor_type, unsigned char *send_buf,
+ unsigned char length)
+{
+ struct ssp_msg *msg;
+
+ if (data->fw_dl_state == 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->sensor_state & (1 << sensor_type)))
+ && (inst <= MSG2SSP_INST_CHANGE_DELAY)) {
+ dev_err(SSP_DEV, "%s - Bypass Inst Skip! - %u\n",
+ __func__, sensor_type);
+ return -1; /* just fail */
+ }
+
+ msg = kzalloc(sizeof(*msg), GFP_KERNEL);
+ if (msg == NULL)
+ return -ENOMEM;
+
+ msg->cmd = inst;
+ msg->length = length + 2;
+ msg->options = AP2HUB_WRITE;
+ msg->buffer = kzalloc(length + 2, GFP_KERNEL);
+ if (!msg->buffer) {
+ kfree(msg);
+ return -ENOMEM;
+ }
+ msg->free_buffer = 1;
+
+ msg->buffer[0] = sensor_type;
+ memcpy(&msg->buffer[1], send_buf, length);
+
+ ssp_dbg("%s - Inst = 0x%x, Sensor Type = 0x%x, data = %u\n",
+ __func__, command, sensor_type, msg->buffer[1]);
+
+ return ssp_spi_sync(data, msg, 1000);
+}
+
+int flush(struct ssp_data *data, u8 sensor_type)
+{
+ int ret = 0;
+ char buffer = 0;
+
+ struct ssp_msg *msg = kzalloc(sizeof(*msg), GFP_KERNEL);
+
+ if (!msg)
+ return -ENOMEM;
+
+ msg->cmd = MSG2SSP_AP_MCU_BATCH_FLUSH;
+ msg->length = 1;
+ msg->options = AP2HUB_READ;
+ msg->data = cpu_to_le32(sensor_type);
+ msg->buffer = &buffer;
+ msg->free_buffer = 0;
+
+ ret = ssp_spi_sync(data, msg, 1000);
+ if (ret < 0) {
+ dev_err(SSP_DEV, "%s - fail %d\n", __func__, ret);
+ return ret;
+ }
+
+ ssp_dbg("%s Sensor Type = 0x%x, data = %u\n", __func__, sensor_type,
+ buffer);
+
+ return buffer ? 0 : -1;
+}
+
+int get_chipid(struct ssp_data *data)
+{
+ int ret, retries = 0;
+ char buffer = 0;
+ struct ssp_msg *msg;
+
+ while (retries++ < 3) {
+ msg = kzalloc(sizeof(*msg), GFP_KERNEL);
+ if (!msg)
+ return -ENOMEM;
+
+ msg->cmd = MSG2SSP_AP_WHOAMI;
+ msg->length = 1;
+ msg->options = AP2HUB_READ;
+ msg->buffer = &buffer;
+ msg->free_buffer = 0;
+
+ ret = ssp_spi_sync(data, msg, 1000);
+ if (ret < 0) {
+ dev_err(SSP_DEV, "%s read fail\n", __func__);
+ continue;
+ }
+
+ if (buffer == DEVICE_ID)
+ return buffer;
+ }
+
+ dev_err(SSP_DEV, "%s get chip ID fail\n", __func__);
+
+ return -EIO;
+}
+
+
+int set_sensor_position(struct ssp_data *data)
+{
+ int ret = 0;
+
+ struct ssp_msg *msg = kzalloc(sizeof(*msg), GFP_KERNEL);
+
+ if (!msg)
+ return -ENOMEM;
+
+ msg->cmd = MSG2SSP_AP_SENSOR_FORMATION;
+ msg->length = 3;
+ msg->options = AP2HUB_WRITE;
+ msg->buffer = kzalloc(3, GFP_KERNEL);
+ if (!msg->buffer) {
+ kfree(msg);
+ return -ENOMEM;
+ }
+
+ msg->free_buffer = 1;
+ /*FIXME char = int*/
+ msg->buffer[0] = data->accel_position;
+ msg->buffer[1] = data->accel_position;
+ msg->buffer[2] = data->mag_position;
+
+ ret = ssp_spi_async(data, msg);
+ if (ret < 0) {
+ dev_err(SSP_DEV, "%s -fail to set_sensor_position %d\n",
+ __func__, ret);
+ } else {
+ dev_info(SSP_DEV,
+ "Sensor Posision A : %u, G : %u, M: %u, P: %u\n",
+ data->accel_position, data->accel_position,
+ data->mag_position, 0);
+ }
+
+ return ret;
+}
+
+int set_magnetic_static_matrix(struct ssp_data *data)
+{
+ struct ssp_msg *msg = kzalloc(sizeof(*msg), GFP_KERNEL);
+
+ if (msg == NULL)
+ return -ENOMEM;
+
+ msg->cmd = MSG2SSP_AP_SET_MAGNETIC_STATIC_MATRIX;
+ msg->length = data->mag_matrix_size;
+ msg->options = AP2HUB_WRITE;
+
+ msg->buffer = kzalloc(data->mag_matrix_size, GFP_KERNEL);
+ if (!msg->buffer) {
+ kfree(msg);
+ return -ENOMEM;
+ }
+
+ msg->free_buffer = 1;
+ /* matrix is u8 so do not bother with endianness*/
+ memcpy(msg->buffer, data->mag_matrix, data->mag_matrix_size);
+
+ return ssp_spi_async(data, msg);
+}
+
+unsigned int get_sensor_scanning_info(struct ssp_data *data)
+{
+ int ret, z;
+ __le32 result;
+ u32 cpu_result;
+ char bin[SSP_SENSOR_MAX + 1] = {0};
+
+ struct ssp_msg *msg = kzalloc(sizeof(*msg), GFP_KERNEL);
+
+ if (!msg)
+ return 0;
+
+ msg->cmd = MSG2SSP_AP_SENSOR_SCANNING;
+ msg->length = 4;
+ msg->options = AP2HUB_READ;
+ msg->buffer = (char *)&result;
+ msg->free_buffer = 0;
+
+ ret = ssp_spi_sync(data, msg, 1000);
+ if (ret < 0) {
+ dev_err(SSP_DEV, "%s - spi read fail %d\n", __func__, ret);
+ return 0;
+ }
+
+ cpu_result = le32_to_cpu(result);
+
+ for (z = 0; z < SSP_SENSOR_MAX; ++z)
+ bin[SSP_SENSOR_MAX - 1 - z] =
+ (cpu_result & (1 << z)) ? '1' : '0';
+
+ dev_info(SSP_DEV, "%s state: %s\n", __func__, bin);
+
+ return cpu_result;
+}
+
+unsigned int get_firmware_rev(struct ssp_data *data)
+{
+ int ret;
+ __le32 result;
+
+ struct ssp_msg *msg = kzalloc(sizeof(*msg), GFP_KERNEL);
+
+ if (!msg)
+ return SSP_INVALID_REVISION;
+
+ msg->cmd = MSG2SSP_AP_FIRMWARE_REV;
+ msg->length = 4;
+ msg->options = AP2HUB_READ;
+ msg->buffer = (char *)&result;
+ msg->free_buffer = 0;
+
+ ret = ssp_spi_sync(data, msg, 1000);
+ if (ret < 0) {
+ dev_err(SSP_DEV, "%s - transfer fail %d\n", __func__, ret);
+ return SSP_INVALID_REVISION;
+ }
+
+ return le32_to_cpu(result);
+}
+
+int get_fuserom_data(struct ssp_data *data)
+{
+ int ret = 0;
+ char buffer[3] = {0};
+
+ struct ssp_msg *msg = kzalloc(sizeof(*msg), GFP_KERNEL);
+
+ if (!msg)
+ return -ENOMEM;
+
+ msg->cmd = MSG2SSP_AP_FUSEROM;
+ msg->length = sizeof(buffer);
+ msg->options = AP2HUB_READ;
+ msg->buffer = buffer;
+ msg->free_buffer = 0;
+
+ ret = ssp_spi_sync(data, msg, 1000);
+ if (ret >= 0) {
+ data->fuse_rom_data[0] = buffer[0];
+ data->fuse_rom_data[1] = buffer[1];
+ data->fuse_rom_data[2] = buffer[2];
+ } else {
+ data->fuse_rom_data[0] = 0;
+ data->fuse_rom_data[1] = 0;
+ data->fuse_rom_data[2] = 0;
+ return ret;
+ }
+
+ dev_info(SSP_DEV, "FUSE ROM Data %d , %d, %d\n",
+ data->fuse_rom_data[0], data->fuse_rom_data[1],
+ data->fuse_rom_data[2]);
+
+ return 0;
+}
+
+int ssp_start_big_data(struct ssp_data *data, u8 type, u32 len)
+{
+ __le32 llen;
+
+ struct ssp_msg *msg = kzalloc(sizeof(*msg), GFP_KERNEL);
+
+ if (!msg)
+ return -ENOMEM;
+
+ msg->cmd = MSG2SSP_AP_START_BIG_DATA;
+ msg->length = 5;
+ msg->options = AP2HUB_WRITE;
+ msg->buffer = kzalloc(msg->length, GFP_KERNEL);
+ if (!msg->buffer) {
+ kfree(msg);
+ return -ENOMEM;
+ }
+
+ msg->free_buffer = 1;
+ msg->buffer[0] = type;
+
+ llen = cpu_to_le32(len);
+ memcpy(&msg->buffer, &llen, sizeof(unsigned int));
+
+ return ssp_spi_async(data, msg);
+}
--
1.7.9.5
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@...r.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/
Powered by blists - more mailing lists