aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/sensorhub/atmel/ssp_sensorhub.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/sensorhub/atmel/ssp_sensorhub.c')
-rwxr-xr-xdrivers/sensorhub/atmel/ssp_sensorhub.c566
1 files changed, 566 insertions, 0 deletions
diff --git a/drivers/sensorhub/atmel/ssp_sensorhub.c b/drivers/sensorhub/atmel/ssp_sensorhub.c
new file mode 100755
index 00000000000..b526e261add
--- /dev/null
+++ b/drivers/sensorhub/atmel/ssp_sensorhub.c
@@ -0,0 +1,566 @@
+/*
+ * Copyright (C) 2013, 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 int ssp_sensorhub_print_data(const char *func_name,
+ const char *data, int length)
+{
+ char buf[6];
+ char *log_str;
+ int log_size = strlen(func_name) + 2 + sizeof(buf) * length + 1;
+ int i;
+
+ log_str = kzalloc(log_size, GFP_ATOMIC);
+ if (unlikely(!log_str)) {
+ sensorhub_err("allocate memory for data log err");
+ return -ENOMEM;
+ }
+
+ for (i = 0; i < length; i++) {
+ if (i == 0) {
+ strlcat(log_str, func_name, log_size);
+ strlcat(log_str, ": ", log_size);
+ } else {
+ strlcat(log_str, ", ", log_size);
+ }
+ snprintf(buf, sizeof(buf), "%d", (signed char)data[i]);
+ strlcat(log_str, buf, log_size);
+ }
+
+ pr_info("%s", log_str);
+ kfree(log_str);
+ return log_size;
+}
+
+static ssize_t ssp_sensorhub_write(struct file *file, const char __user *buf,
+ size_t count, loff_t *pos)
+{
+ struct ssp_sensorhub_data *hub_data
+ = container_of(file->private_data,
+ struct ssp_sensorhub_data, sensorhub_device);
+ unsigned char instruction;
+ int ret = 0;
+
+ if (unlikely(count < 2)) {
+ sensorhub_err("library data length err(%d)", count);
+ return -EINVAL;
+ }
+
+ ssp_sensorhub_print_data(__func__, buf, count);
+
+ if (unlikely(hub_data->ssp_data->bSspShutdown)) {
+ sensorhub_err("stop sending library data(shutdown)");
+ return -EBUSY;
+ }
+
+ if (buf[0] == MSG2SSP_INST_LIBRARY_REMOVE)
+ instruction = REMOVE_LIBRARY;
+ else if (buf[0] == MSG2SSP_INST_LIBRARY_ADD)
+ instruction = ADD_LIBRARY;
+ else
+ instruction = buf[0];
+
+ ret = send_instruction(hub_data->ssp_data, instruction,
+ (unsigned char)buf[1], (unsigned char *)(buf+2), count-2);
+ if (unlikely(ret <= 0)) {
+ sensorhub_err("send library data err(%d)", ret);
+ /* i2c transfer fail */
+ if (ret == ERROR)
+ return -EIO;
+ /* i2c transfer done but no ack from MCU */
+ else if (ret == FAIL)
+ return -EAGAIN;
+ }
+
+ return count;
+}
+
+static ssize_t ssp_sensorhub_read(struct file *file, char __user *buf,
+ size_t count, loff_t *pos)
+{
+ struct ssp_sensorhub_data *hub_data
+ = container_of(file->private_data,
+ struct ssp_sensorhub_data, sensorhub_device);
+ int retries = MAX_DATA_COPY_TRY;
+ int length = 0;
+ int ret = 0;
+
+ spin_lock_bh(&hub_data->sensorhub_lock);
+ if (unlikely(list_empty(&hub_data->events_head.list))) {
+ sensorhub_info("no library data");
+ goto exit;
+ }
+
+ /* first in first out */
+ hub_data->first_event
+ = list_first_entry(&hub_data->events_head.list,
+ struct sensorhub_event, list);
+ length = hub_data->first_event->library_length;
+
+ /* remove first event from the list */
+ list_del(&hub_data->first_event->list);
+
+ while (retries--) {
+ ret = copy_to_user(buf,
+ hub_data->first_event->library_data,
+ hub_data->first_event->library_length);
+ if (likely(!ret))
+ break;
+ }
+ if (unlikely(ret)) {
+ sensorhub_err("read library data err(%d)", ret);
+ goto exit;
+ }
+
+ ssp_sensorhub_print_data(__func__,
+ hub_data->first_event->library_data,
+ hub_data->first_event->library_length);
+
+ complete(&hub_data->sensorhub_completion);
+
+exit:
+ spin_unlock_bh(&hub_data->sensorhub_lock);
+ return ret ? -ret : length;
+}
+
+static long ssp_sensorhub_ioctl(struct file *file, unsigned int cmd,
+ unsigned long arg)
+{
+ struct ssp_sensorhub_data *hub_data
+ = container_of(file->private_data,
+ struct ssp_sensorhub_data, sensorhub_device);
+ void __user *argp = (void __user *)arg;
+ int retries = MAX_DATA_COPY_TRY;
+ int length = hub_data->large_library_length;
+ int ret = 0;
+
+ switch (cmd) {
+ case IOCTL_READ_LARGE_CONTEXT_DATA:
+ if (unlikely(!hub_data->large_library_data
+ || !hub_data->large_library_length)) {
+ sensorhub_info("no large library data");
+ return 0;
+ }
+
+ while (retries--) {
+ ret = copy_to_user(argp,
+ hub_data->large_library_data,
+ hub_data->large_library_length);
+ if (likely(!ret))
+ break;
+ }
+ if (unlikely(ret)) {
+ sensorhub_err("read large library data err(%d)", ret);
+ return -ret;
+ }
+
+ ssp_sensorhub_print_data(__func__,
+ hub_data->large_library_data,
+ hub_data->large_library_length);
+
+ kfree(hub_data->large_library_data);
+ hub_data->large_library_length = 0;
+ break;
+
+ default:
+ sensorhub_err("ioctl cmd err(%d)", cmd);
+ return -EINVAL;
+ }
+
+ return length;
+}
+
+static struct file_operations ssp_sensorhub_fops = {
+ .owner = THIS_MODULE,
+ .open = nonseekable_open,
+ .write = ssp_sensorhub_write,
+ .read = ssp_sensorhub_read,
+ .unlocked_ioctl = ssp_sensorhub_ioctl,
+};
+
+void ssp_sensorhub_report_notice(struct ssp_data *ssp_data, char notice)
+{
+ struct ssp_sensorhub_data *hub_data = ssp_data->hub_data;
+
+ input_report_rel(hub_data->sensorhub_input_dev, NOTICE, notice);
+ input_sync(hub_data->sensorhub_input_dev);
+
+ if (notice == MSG2SSP_AP_STATUS_WAKEUP)
+ sensorhub_info("wake up");
+ else if (notice == MSG2SSP_AP_STATUS_SLEEP)
+ sensorhub_info("sleep");
+ else if (notice == MSG2SSP_AP_STATUS_RESET)
+ sensorhub_info("reset");
+ else
+ sensorhub_err("invalid notice(0x%x)", notice);
+}
+
+static void ssp_sensorhub_report_library(struct ssp_sensorhub_data *hub_data)
+{
+ input_report_rel(hub_data->sensorhub_input_dev, DATA, DATA);
+ input_sync(hub_data->sensorhub_input_dev);
+ wake_lock_timeout(&hub_data->sensorhub_wake_lock, WAKE_LOCK_TIMEOUT);
+}
+
+static void ssp_sensorhub_report_large_library(
+ struct ssp_sensorhub_data *hub_data)
+{
+ input_report_rel(hub_data->sensorhub_input_dev, LARGE_DATA, LARGE_DATA);
+ input_sync(hub_data->sensorhub_input_dev);
+ wake_lock_timeout(&hub_data->sensorhub_wake_lock, WAKE_LOCK_TIMEOUT);
+}
+
+static int ssp_sensorhub_list(struct ssp_sensorhub_data *hub_data,
+ char *dataframe, int start, int end)
+{
+ struct list_head *list;
+ int length = end - start;
+ int events = 0;
+
+ if (unlikely(length <= 0)) {
+ sensorhub_err("library length err(%d)", length);
+ return -EINVAL;
+ }
+
+ ssp_sensorhub_print_data(__func__, dataframe+start, length);
+
+ spin_lock_bh(&hub_data->sensorhub_lock);
+ /* how many events in the list? */
+ list_for_each(list, &hub_data->events_head.list)
+ events++;
+
+ /* overwrite new event if list is full */
+ if (unlikely(events >= LIST_SIZE)) {
+ struct sensorhub_event *oldest_event
+ = list_first_entry(&hub_data->events_head.list,
+ struct sensorhub_event, list);
+ list_del(&oldest_event->list);
+ sensorhub_info("overwrite event");
+ }
+
+ /* allocate memory for new event */
+ kfree(hub_data->events[hub_data->event_number].library_data);
+ hub_data->events[hub_data->event_number].library_data
+ = kzalloc(length * sizeof(char), GFP_ATOMIC);
+ if (unlikely(!hub_data->events[hub_data->event_number].library_data)) {
+ sensorhub_err("allocate memory for library err");
+ spin_unlock_bh(&hub_data->sensorhub_lock);
+ return -ENOMEM;
+ }
+
+ /* copy new event into memory */
+ memcpy(hub_data->events[hub_data->event_number].library_data,
+ dataframe+start, length);
+ hub_data->events[hub_data->event_number].library_length = length;
+
+ /* add new event into the end of list */
+ list_add_tail(&hub_data->events[hub_data->event_number].list,
+ &hub_data->events_head.list);
+ spin_unlock_bh(&hub_data->sensorhub_lock);
+
+ /* not to overflow max list capacity */
+ if (hub_data->event_number++ >= LIST_SIZE - 1)
+ hub_data->event_number = 0;
+
+ return events + (events >= LIST_SIZE ? 0 : 1);
+}
+
+static int ssp_sensorhub_thread(void *arg)
+{
+ struct ssp_sensorhub_data *hub_data = (struct ssp_sensorhub_data *)arg;
+ int ret = 0;
+
+ while (likely(!kthread_should_stop())) {
+ /* run thread if list is not empty */
+ wait_event_interruptible(hub_data->sensorhub_wq,
+ kthread_should_stop() ||
+ !list_empty(&hub_data->events_head.list));
+
+ /* exit thread if kthread should stop */
+ if (unlikely(kthread_should_stop())) {
+ sensorhub_info("kthread_stop()");
+ break;
+ }
+
+ /* report sensorhub event to user */
+ ssp_sensorhub_report_library(hub_data);
+
+ /* wait until transfer finished */
+ ret = wait_for_completion_timeout(
+ &hub_data->sensorhub_completion, COMPLETION_TIMEOUT);
+ if (unlikely(!ret))
+ sensorhub_err("wait timed out");
+ else if (unlikely(ret < 0))
+ sensorhub_err("wait for completion err(%d)", ret);
+ }
+
+ return 0;
+}
+
+int ssp_sensorhub_handle_data(struct ssp_data *ssp_data, char *dataframe,
+ int start, int end)
+{
+ struct ssp_sensorhub_data *hub_data = ssp_data->hub_data;
+
+ /* add new sensorhub event into list */
+ int ret = ssp_sensorhub_list(hub_data, dataframe, start, end);
+ wake_up(&hub_data->sensorhub_wq);
+
+ return ret;
+}
+
+static int ssp_sensorhub_receive_large_data(struct ssp_sensorhub_data *hub_data,
+ unsigned char sub_cmd)
+{
+ static int pos; /* large_library_data current position */
+ char send_data[2] = { 0, }; /* send data */
+ char receive_data[5] = { 0, }; /* receive data */
+ char *msg_data; /* Nth msg data */
+ int total_length = 0; /* total length */
+ int msg_length = 0; /* Nth msg length */
+ int total_msg_number; /* total msg number */
+ int msg_number; /* current msg number */
+ int ret = 0;
+
+ sensorhub_info("sub_cmd = 0x%x", sub_cmd);
+
+ send_data[0] = MSG2SSP_STT;
+ send_data[1] = sub_cmd;
+
+ /* receive_data[0-1] : total length
+ * receive_data[2] >> 4 : total msg number
+ * receive_data[2] & 0x0F : current msg number */
+ ret = ssp_i2c_read(hub_data->ssp_data, send_data, sizeof(send_data),
+ receive_data, sizeof(receive_data), DEFAULT_RETRIES);
+ if (unlikely(ret < 0)) {
+ sensorhub_err("MSG2SSP_STT i2c err(%d)", ret);
+ return ret;
+ }
+
+ /* get total length */
+ total_length = ((unsigned int)receive_data[0] << 8)
+ + (unsigned int)receive_data[1];
+ sensorhub_info("total length = %d", total_length);
+
+ total_msg_number = (int)(receive_data[2] >> 4);
+ msg_number = (int)(receive_data[2] & 0x0F);
+
+ /* if this is the first msg */
+ if (msg_number <= 1) {
+ /* empty previous large_library_data */
+ if (hub_data->large_library_length != 0)
+ kfree(hub_data->large_library_data);
+
+ /* allocate new memory for large_library_data */
+ hub_data->large_library_data
+ = kzalloc((total_length * sizeof(char)), GFP_KERNEL);
+ if (unlikely(!hub_data->large_library_data)) {
+ sensorhub_err("allocate memory for large library err");
+ return -ENOMEM;
+ }
+ hub_data->large_library_length = total_length;
+ }
+
+ /* get the Nth msg length */
+ msg_length = ((unsigned int)receive_data[3] << 8)
+ + (unsigned int)receive_data[4];
+ sensorhub_info("%dth msg length = %d", msg_number, msg_length);
+
+ /* receive the Nth msg data */
+ send_data[0] = MSG2SSP_SRM;
+ msg_data = kzalloc((msg_length * sizeof(char)), GFP_KERNEL);
+ if (unlikely(!msg_data)) {
+ sensorhub_err("allocate memory for msg data err");
+ return -ENOMEM;
+ }
+
+ ret = ssp_i2c_read(hub_data->ssp_data, send_data, 1,
+ msg_data, msg_length, 0);
+ if (unlikely(ret < 0)) {
+ sensorhub_err("receive %dth msg err(%d)", msg_number, ret);
+ kfree(msg_data);
+ return ret;
+ }
+
+ /* copy the Nth msg data into large_library_data */
+ memcpy(&hub_data->large_library_data[pos],
+ &msg_data[0], msg_length * sizeof(char));
+ kfree(msg_data);
+ pos += msg_length;
+
+ if (msg_number < total_msg_number) {
+ /* still receiving msg data */
+ sensorhub_info("current msg length = %d(%d/%d)",
+ msg_length, msg_number, total_msg_number);
+ } else {
+ /* finish receiving msg data */
+ sensorhub_info("total msg length = %d(%d/%d)",
+ pos, msg_number, total_msg_number);
+ pos = 0;
+ }
+
+ return msg_number;
+}
+
+int ssp_sensorhub_handle_large_data(struct ssp_data *ssp_data,
+ unsigned char sub_cmd)
+{
+ struct ssp_sensorhub_data *hub_data = ssp_data->hub_data;
+ static bool err;
+ static int current_msg_number = 1;
+ int total_msg_number = (int)(sub_cmd >> 4);
+ int msg_number = (int)(sub_cmd & 0x0F);
+ int ret;
+
+ /* skip the rest transfer if error occurs */
+ if (unlikely(err)) {
+ if (msg_number <= 1) {
+ current_msg_number = 1;
+ err = false;
+ } else {
+ return -EIO;
+ }
+ }
+
+ /* next msg is the right one? */
+ if (current_msg_number++ != msg_number) {
+ sensorhub_err("next msg should be %dth but %dth",
+ current_msg_number - 1, msg_number);
+ sensorhub_err("skip the rest %d msg transfer",
+ total_msg_number - msg_number);
+ err = true;
+ return -EINVAL;
+ }
+
+ /* receive large library data */
+ ret = ssp_sensorhub_receive_large_data(hub_data, sub_cmd);
+ if (unlikely(ret < 0)) {
+ sensorhub_err("receive large msg err(%d/%d)(%d)",
+ msg_number, total_msg_number, ret);
+ sensorhub_err("skip the rest %d msg transfer",
+ total_msg_number - msg_number);
+ err = true;
+ return ret;
+ }
+
+ /* finally ready to go to user */
+ if (msg_number >= total_msg_number) {
+ ssp_sensorhub_report_large_library(hub_data);
+ current_msg_number = 1;
+ }
+
+ return ret;
+}
+
+int ssp_sensorhub_initialize(struct ssp_data *ssp_data)
+{
+ struct ssp_sensorhub_data *hub_data;
+ int ret;
+
+ /* allocate memory for sensorhub data */
+ hub_data = kzalloc(sizeof(*hub_data), GFP_KERNEL);
+ if (!hub_data) {
+ sensorhub_err("allocate memory for sensorhub data err");
+ ret = -ENOMEM;
+ goto exit;
+ }
+ hub_data->ssp_data = ssp_data;
+ ssp_data->hub_data = hub_data;
+
+ /* init wakelock, list, waitqueue, completion and spinlock */
+ wake_lock_init(&hub_data->sensorhub_wake_lock, WAKE_LOCK_SUSPEND,
+ "ssp_sensorhub_wake_lock");
+ INIT_LIST_HEAD(&hub_data->events_head.list);
+ init_waitqueue_head(&hub_data->sensorhub_wq);
+ init_completion(&hub_data->sensorhub_completion);
+ spin_lock_init(&hub_data->sensorhub_lock);
+
+ /* allocate sensorhub input device */
+ hub_data->sensorhub_input_dev = input_allocate_device();
+ if (!hub_data->sensorhub_input_dev) {
+ sensorhub_err("allocate sensorhub input device err");
+ ret = -ENOMEM;
+ goto err_input_allocate_device_sensorhub;
+ }
+
+ /* set sensorhub input device */
+ input_set_drvdata(hub_data->sensorhub_input_dev, hub_data);
+ hub_data->sensorhub_input_dev->name = "ssp_context";
+ input_set_capability(hub_data->sensorhub_input_dev, EV_REL, DATA);
+ input_set_capability(hub_data->sensorhub_input_dev, EV_REL, LARGE_DATA);
+ input_set_capability(hub_data->sensorhub_input_dev, EV_REL, NOTICE);
+
+ /* register sensorhub input device */
+ ret = input_register_device(hub_data->sensorhub_input_dev);
+ if (ret < 0) {
+ sensorhub_err("register sensorhub input device err(%d)", ret);
+ input_free_device(hub_data->sensorhub_input_dev);
+ goto err_input_register_device_sensorhub;
+ }
+
+ /* register sensorhub misc device */
+ hub_data->sensorhub_device.minor = MISC_DYNAMIC_MINOR;
+ hub_data->sensorhub_device.name = "ssp_sensorhub";
+ hub_data->sensorhub_device.fops = &ssp_sensorhub_fops;
+
+ ret = misc_register(&hub_data->sensorhub_device);
+ if (ret < 0) {
+ sensorhub_err("register sensorhub misc device err(%d)", ret);
+ goto err_misc_register;
+ }
+
+ /* create and run sensorhub thread */
+ hub_data->sensorhub_task = kthread_run(ssp_sensorhub_thread,
+ (void *)hub_data, "ssp_sensorhub_thread");
+ if (IS_ERR(hub_data->sensorhub_task)) {
+ ret = PTR_ERR(hub_data->sensorhub_task);
+ goto err_kthread_create;
+ }
+
+ return 0;
+
+err_kthread_create:
+ misc_deregister(&hub_data->sensorhub_device);
+err_misc_register:
+ input_unregister_device(hub_data->sensorhub_input_dev);
+err_input_register_device_sensorhub:
+err_input_allocate_device_sensorhub:
+ complete_all(&hub_data->sensorhub_completion);
+ wake_lock_destroy(&hub_data->sensorhub_wake_lock);
+ kfree(hub_data);
+exit:
+ return ret;
+}
+
+void ssp_sensorhub_remove(struct ssp_data *ssp_data)
+{
+ struct ssp_sensorhub_data *hub_data = ssp_data->hub_data;
+
+ ssp_sensorhub_fops.write = NULL;
+ ssp_sensorhub_fops.read = NULL;
+ ssp_sensorhub_fops.unlocked_ioctl = NULL;
+
+ kthread_stop(hub_data->sensorhub_task);
+ misc_deregister(&hub_data->sensorhub_device);
+ input_unregister_device(hub_data->sensorhub_input_dev);
+ complete_all(&hub_data->sensorhub_completion);
+ wake_lock_destroy(&hub_data->sensorhub_wake_lock);
+ kfree(hub_data);
+}
+
+MODULE_DESCRIPTION("Seamless Sensor Platform(SSP) sensorhub driver");
+MODULE_AUTHOR("Samsung Electronics");
+MODULE_LICENSE("GPL");