diff options
Diffstat (limited to 'drivers/sensorhub/stm/ssp_sensorhub.c')
-rwxr-xr-x | drivers/sensorhub/stm/ssp_sensorhub.c | 622 |
1 files changed, 622 insertions, 0 deletions
diff --git a/drivers/sensorhub/stm/ssp_sensorhub.c b/drivers/sensorhub/stm/ssp_sensorhub.c new file mode 100755 index 0000000..4a817e5 --- /dev/null +++ b/drivers/sensorhub/stm/ssp_sensorhub.c @@ -0,0 +1,622 @@ +/* + * 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_KERNEL); + 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 if (buf[0] == MSG2SSP_INST_LIB_NOTI) { + if (buf[2] == MSG2SSP_AP_STATUS_WAKEUP) { + ret = ssp_send_cmd(hub_data->ssp_data, MSG2SSP_AP_STATUS_WAKEUP); + enable_debug_timer(hub_data->ssp_data); + if (ret != SUCCESS) + pr_err("[SSP] : %s MSG2SSP_AP_STATUS_WAKEUP failed(%d)\n", + __func__, ret); + } else if (buf[2] == MSG2SSP_AP_STATUS_SLEEP) { + disable_debug_timer(hub_data->ssp_data); + ret = ssp_send_cmd(hub_data->ssp_data, MSG2SSP_AP_STATUS_SLEEP); + if (ret != SUCCESS) + pr_err("[SSP] : %s MSG2SSP_AP_STATUS_SLEEP failed(%d)\n", + __func__, ret); + } else if (buf[2] == MSG2SSP_AP_STATUS_POW_CONNECTED) { + ret = ssp_send_cmd(hub_data->ssp_data, MSG2SSP_AP_STATUS_POW_CONNECTED); + if (ret != SUCCESS) + pr_err("[SSP] : %s MSG2SSP_AP_STATUS_POW_CONNECTED failed(%d)\n", + __func__, ret); + } else if (buf[2] == MSG2SSP_AP_STATUS_POW_DISCONNECTED) { + ret = ssp_send_cmd(hub_data->ssp_data, MSG2SSP_AP_STATUS_POW_DISCONNECTED); + if (ret != SUCCESS) + pr_err("[SSP] : %s MSG2SSP_AP_STATUS_POW_DISCONNECTED failed(%d)\n", + __func__, ret); + } else if (buf[2] == MSG2SSP_AP_STATUS_CALL_IDLE) { + ret = ssp_send_cmd(hub_data->ssp_data, MSG2SSP_AP_STATUS_CALL_IDLE); + if (ret != SUCCESS) + pr_err("[SSP] : %s MSG2SSP_AP_STATUS_CALL_IDLE failed(%d)\n", + __func__, ret); + } else if (buf[2] == MSG2SSP_AP_STATUS_CALL_ACTIVE) { + ret = ssp_send_cmd(hub_data->ssp_data, MSG2SSP_AP_STATUS_CALL_ACTIVE); + if (ret != SUCCESS) + pr_err("[SSP] : %s MSG2SSP_AP_STATUS_CALL_ACTIVE failed(%d)\n", + __func__, ret); + } else + pr_err("[SSP] : %s wrong MSG2SSP_INST_LIB_NOTI(%d)\n", + __func__, buf[0]); + return count; + } */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 ret = 0; + + if (unlikely(list_empty(&hub_data->events_head.list))) { + sensorhub_info("no library data"); + return 0; + } + + 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); + return -ret; + } + + ssp_sensorhub_print_data(__func__, + hub_data->first_event->library_data, + hub_data->first_event->library_length); + + complete(&hub_data->sensorhub_completion); + return hub_data->first_event->library_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_length + || !hub_data->large_library_data)) { + 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_length( + struct ssp_sensorhub_data *hub_data, int length) +{ + input_report_rel(hub_data->sensorhub_input_dev, DATA, length); + input_sync(hub_data->sensorhub_input_dev); + wake_lock_timeout(&hub_data->sensorhub_wake_lock, WAKE_LOCK_TIMEOUT); +} + +static void ssp_sensorhub_report_large_length( + struct ssp_sensorhub_data *hub_data, int length) +{ + input_report_rel(hub_data->sensorhub_input_dev, LARGE_DATA, length); + 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); + + /* 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)) { + sensorhub_info("list is full... overwrite new event"); + spin_lock_bh(&hub_data->sensorhub_lock); + list_del(&hub_data->first_event->list); + hub_data->first_event + = list_first_entry(&hub_data->events_head.list, + struct sensorhub_event, list); + spin_unlock_bh(&hub_data->sensorhub_lock); + } + + /* 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_KERNEL); + if (unlikely(!hub_data->events[hub_data->event_number].library_data)) { + sensorhub_err("allocate memory for library err"); + 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 */ + spin_lock_bh(&hub_data->sensorhub_lock); + 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; + } + + /* get ready new event if the previous transfer has succeeded */ + if (likely(!hub_data->transferring)) { + spin_lock_bh(&hub_data->sensorhub_lock); + /* first in first out */ + hub_data->first_event + = list_first_entry(&hub_data->events_head.list, + struct sensorhub_event, list); + hub_data->transferring = true; + spin_unlock_bh(&hub_data->sensorhub_lock); + } + + /* report sensorhub event length to user */ + ssp_sensorhub_report_length(hub_data, + hub_data->first_event->library_length); + + /* wait until transfer finished */ + ret = wait_for_completion_timeout( + &hub_data->sensorhub_completion, COMPLETION_TIMEOUT); + if (likely(ret > 0)) + hub_data->transferring = false; + else if (unlikely(ret == 0)) + sensorhub_err("wait timed out"); + else + sensorhub_err("wait for completion err(%d)", ret); + + /* remove first event in the list if transfer succeeded */ + if (likely(!hub_data->transferring)) { + struct list_head *list; + int events = 0; + + /* remove first event from the list */ + spin_lock_bh(&hub_data->sensorhub_lock); + list_del(&hub_data->first_event->list); + spin_unlock_bh(&hub_data->sensorhub_lock); + + /* how many events remain in the list after removing? */ + list_for_each(list, &hub_data->events_head.list) + events++; + if (unlikely(events)) + sensorhub_info("%d events remain", events); + } + } + + 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) +{ + char send_data[2] = { 0, }; + char receive_data[5] = { 0, }; + char *msg_data; /* Nth msg data */ + static int pos; /* large_library_data current position */ + 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; + + send_data[0] = MSG2SSP_STT; + send_data[1] = sub_cmd; + sensorhub_info("sub_cmd = 0x%x", sub_cmd); + + /* receive_data[0-1] : total length + * receive_data[2] >> 4 : total msg number + * receive_data[2] & 0x0F : current msg number */ + ret = ssp_read_data(hub_data->ssp_data, send_data, 2, + receive_data, 5, 3); + if (unlikely(ret < 0)) { + sensorhub_err("MSG2SSP_STT spi 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_read_data(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_length(hub_data, + hub_data->large_library_length); + 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"); |