Add the rt linux 4.1.3-rt3 as base
[kvmfornfv.git] / kernel / drivers / iio / common / ssp_sensors / ssp_spi.c
diff --git a/kernel/drivers/iio/common/ssp_sensors/ssp_spi.c b/kernel/drivers/iio/common/ssp_sensors/ssp_spi.c
new file mode 100644 (file)
index 0000000..704284a
--- /dev/null
@@ -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;
+}