diff options
Diffstat (limited to 'drivers/sensorhub/ssp_sensorhub.c')
-rw-r--r-- | drivers/sensorhub/ssp_sensorhub.c | 444 |
1 files changed, 444 insertions, 0 deletions
diff --git a/drivers/sensorhub/ssp_sensorhub.c b/drivers/sensorhub/ssp_sensorhub.c new file mode 100644 index 0000000..8330beb --- /dev/null +++ b/drivers/sensorhub/ssp_sensorhub.c @@ -0,0 +1,444 @@ +/* + * 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" + +/* sensorhub ioctl command */ +#define SENSORHUB_IOCTL_MAGIC 'S' +#define IOCTL_READ_CONTEXT_DATA _IOR(SENSORHUB_IOCTL_MAGIC, 3, char *) + + +static ssize_t ssp_sensorhub_write(struct file *file, const char __user *buf, + size_t count, loff_t *pos) +{ + struct ssp_data *data = container_of(file->private_data, + struct ssp_data, sensorhub_device); + int ret = 0; + int i; + u8 instruction = buf[0]; + + if (count <= 0) { + pr_err("%s: library command length err(%d)", __func__, count); + return -EINVAL; + } + + for (i = 0; i < count; i++) + pr_info("%s[%d] = %d", __func__, i, buf[i]); + + if (buf[0] == MSG2SSP_INST_LIBRARY_REMOVE) + instruction = REMOVE_LIBRARY; + else if (buf[0] == MSG2SSP_INST_LIBRARY_ADD) + instruction = ADD_LIBRARY; + + ret = send_instruction(data, instruction, + (u8)buf[1], (u8 *)(buf+2), count-2); + if (ret < 0) + pr_err("%s: send library command err(%d)", __func__, ret); + + return ret; +} + +static long ssp_sensorhub_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + void __user *argp = (void __user *)arg; + struct ssp_data *data = container_of(file->private_data, + struct ssp_data, sensorhub_device); + struct sensorhub_event *event; + int ret = 0; + int i; + + switch (cmd) { + case IOCTL_READ_CONTEXT_DATA: + /* for receive_msg */ + if (!data->large_library_length && + data->large_library_data == NULL) { + if (list_empty(&data->events_head.list)) { + pr_err("%s: list empty!", __func__); + complete(&data->transfer_done); + goto exit; + } + + event = list_first_entry(&data->events_head.list, + struct sensorhub_event, list); + if (IS_ERR(event)) { + pr_err("%s: no sensor event entry", __func__); + complete(&data->transfer_done); + goto exit; + } + + ret = copy_to_user(argp, + event->library_data, event->library_length); + if (ret < 0) { + pr_err("%s: send library datar err(%d)", + __func__, ret); + complete(&data->transfer_done); + goto exit; + } + + for (i = 0; i < event->library_length; i++) { + pr_info("%s[%d] = %d", + __func__, i, event->library_data[i]); + } + + list_del(&event->list); + complete(&data->transfer_done); + + /* for receive_large_msg */ + } else { + pr_info("%s: receive_large_msg ioctl", __func__); + ret = copy_to_user(argp, data->large_library_data, + data->large_library_length); + if (ret < 0) { + pr_err("%s: send large library data err(%d)", + __func__, ret); + goto exit; + } + + kfree(data->large_library_data); + data->large_library_length = 0; + } + break; + + default: + pr_err("%s: icotl cmd err(%d)", __func__, cmd); + ret = -EINVAL; + } + +exit: + return ret; +} + +static const struct file_operations ssp_sensorhub_fops = { + .owner = THIS_MODULE, + .open = nonseekable_open, + .write = ssp_sensorhub_write, + .unlocked_ioctl = ssp_sensorhub_ioctl, +}; + +void ssp_report_sensorhub_notice(struct ssp_data *data, char notice) +{ + input_report_rel(data->sensorhub_input_dev, REL_RY, notice); + input_sync(data->sensorhub_input_dev); + + if (notice == MSG2SSP_AP_STATUS_WAKEUP) + pr_info("%s: wake up", __func__); + else if (notice == MSG2SSP_AP_STATUS_SLEEP) + pr_info("%s: sleep", __func__); + else if (notice == MSG2SSP_AP_STATUS_RESET) + pr_info("%s: reset", __func__); +} + +static void ssp_report_sensorhub_length(struct ssp_data *data, + int library_length) +{ + input_report_rel(data->sensorhub_input_dev, REL_RX, library_length); + input_sync(data->sensorhub_input_dev); + pr_info("%s = %d", __func__, library_length); +} + +static int ssp_queue_sensorhub_events(struct ssp_data *data, + char *dataframe, int start, int end) +{ + int length = end - start; + int i = 0; + + if (length <= 0) { + pr_err("%s: library length err(%d)", __func__, length); + return -EINVAL; + } + + /* allocate memory for new event */ + if (data->events[data->event_number].library_data != NULL) + kfree(data->events[data->event_number].library_data); + + data->events[data->event_number].library_data + = kzalloc(length * sizeof(char), GFP_KERNEL); + if (data->events[data->event_number].library_data == NULL) { + pr_err("%s: allocate memory for library data err", __func__); + return -ENOMEM; + } + + /* copy sensorhub event into queue */ + while (start < end) { + data->events[data->event_number].library_data[i++] + = dataframe[start++]; + pr_info("%s[%d] = %d", __func__, i-1, + data->events[data->event_number].library_data[i-1]); + } + data->events[data->event_number].library_length = length; + + /* add new sensorhug event at the end of queue */ + list_add_tail(&data->events[data->event_number].list, + &data->events_head.list); + + /* do not exceed max queue number */ + if (data->event_number++ >= LIBRARY_MAX_NUM - 1) + data->event_number = 0; + + return length; +} + +static int ssp_receive_large_msg(struct ssp_data *data, u8 sub_cmd) +{ + char send_data[2] = { 0, }; + char receive_data[2] = { 0, }; + char *large_msg_data; /* Nth large msg data */ + int length = 0; /* length of Nth large msg */ + int data_locater = 0; /* large_library_data current position */ + int total_msg_number; /* total number of large msg */ + int msg_number; /* current number of large msg */ + int ret = 0; + + waiting_wakeup_mcu(data); + + /* receive the first msg length */ + send_data[0] = MSG2SSP_STT; + send_data[1] = sub_cmd; + + /* receive_data(msg length) is two byte because msg is large */ + ret = ssp_i2c_read(data, send_data, 2, receive_data, 2); + if (ret < 0) { + pr_err("%s: MSG2SSP_STT i2c err(%d)", __func__, ret); + return ret; + } + + /* get the first msg length */ + length = ((unsigned int)receive_data[0] << 8) + + (unsigned int)receive_data[1]; + if (length < 3) { + /* do not print err message with power-up */ + if (sub_cmd != SUBCMD_POWEREUP) + pr_err("%s: 1st large msg data not ready(length=%d)", + __func__, length); + return -EINVAL; + } + + /* receive the first msg data */ + send_data[0] = MSG2SSP_SRM; + large_msg_data = kzalloc((length * sizeof(char)), GFP_KERNEL); + ret = ssp_i2c_read(data, send_data, 1, + large_msg_data, length); + if (ret < 0) { + pr_err("%s: receive 1st large msg err(%d)", __func__, ret); + kfree(large_msg_data); + return ret; + } + + /* empty the previous large library data */ + if (data->large_library_length != 0) + kfree(data->large_library_data); + + /* large_msg_data[0] of the first msg: total number of large msg + * large_msg_data[1-2] of the first msg: total msg length + * large_msg_data[3-N] of the first msg: the first msg data itself */ + total_msg_number = large_msg_data[0]; + data->large_library_length = (int)((unsigned int)large_msg_data[1] << 8) + + (unsigned int)large_msg_data[2]; + data->large_library_data + = kzalloc((data->large_library_length * sizeof(char)), + GFP_KERNEL); + + /* copy the fist msg data into large_library_data */ + memcpy(data->large_library_data, &large_msg_data[3], + (length - 3) * sizeof(char)); + kfree(large_msg_data); + + data_locater = length - 3; + + /* 2nd, 3rd,...Nth msg */ + for (msg_number = 0; msg_number < total_msg_number; msg_number++) { + /* receive Nth msg length */ + send_data[0] = MSG2SSP_STT; + send_data[1] = 0x81 + msg_number; + + /* receive_data(msg length) is two byte because msg is large */ + ret = ssp_i2c_read(data, send_data, 2, receive_data, 2); + if (ret < 0) { + pr_err("%s: MSG2SSP_STT i2c err(%d)", + __func__, ret); + return ret; + } + + /* get the Nth msg length */ + length = ((unsigned int)receive_data[0] << 8) + + (unsigned int)receive_data[1]; + if (length <= 0) { + pr_err("%s: %dth large msg data not ready(length=%d)", + __func__, msg_number + 2, length); + return -EINVAL; + } + + large_msg_data = kzalloc((length * sizeof(char)), + GFP_KERNEL); + + /* receive Nth msg data */ + send_data[0] = MSG2SSP_SRM; + ret = ssp_i2c_read(data, send_data, 1, + large_msg_data, length); + if (ret < 0) { + pr_err("%s: recieve %dth large msg err(%d)", + __func__, msg_number + 2, ret); + kfree(large_msg_data); + return ret; + } + + /* copy(append) Nth msg data into large_library_data */ + memcpy(&data->large_library_data[data_locater], + large_msg_data, length * sizeof(char)); + data_locater += length; + kfree(large_msg_data); + } + + return data->large_library_length; +} + +static int ssp_senosrhub_thread_func(void *arg) +{ + struct ssp_data *data = (struct ssp_data *)arg; + struct sensorhub_event *event; + int ret = 0; + + while (!kthread_should_stop() || !list_empty(&data->events_head.list)) { + /* run if only event queue is not empty */ + wait_event_interruptible(data->sensorhub_waitqueue, + kthread_should_stop() || + !list_empty(&data->events_head.list)); + + /* first in first out */ + event = list_first_entry(&data->events_head.list, + struct sensorhub_event, list); + if (IS_ERR(event)) { + pr_err("%s: no sensor event entry", __func__); + continue; + } + + /* report sensorhub event to user */ + ssp_report_sensorhub_length(data, event->library_length); + wake_lock_timeout(&data->sensorhub_wake_lock, 5*HZ); + + /* wait until user gets data */ + ret = wait_for_completion_timeout(&data->transfer_done, 3*HZ); + if (ret == 0) { + pr_err("%s: wait timed out", __func__); + } else if (ret < 0) { + pr_err("%s: wait_for_completion_timeout err(%d)", + __func__, ret); + } + } + + return ret; +} + +int ssp_handle_sensorhub_data(struct ssp_data *data, char *dataframe, + int start, int end) +{ + /* add new sensorhub event into queue */ + int ret = ssp_queue_sensorhub_events(data, dataframe, start, end); + if (ret < 0) + pr_err("%s: ssp_queue_sensorhub_events err(%d)", __func__, ret); + wake_up(&data->sensorhub_waitqueue); + + return ret; +} + +int ssp_handle_sensorhub_large_data(struct ssp_data *data, u8 sub_cmd) +{ + /* receive large size of library data */ + int ret = ssp_receive_large_msg(data, sub_cmd); + if (ret >= 0) { + ssp_report_sensorhub_length(data, data->large_library_length); + wake_lock_timeout(&data->sensorhub_wake_lock, 3*HZ); + } else { + pr_err("%s: ssp_receive_large_msg err(%d)", __func__, ret); + } + + return ret; +} + +int ssp_initialize_sensorhub(struct ssp_data *data) +{ + int ret; + + /* allocate sensorhub input devices */ + data->sensorhub_input_dev = input_allocate_device(); + if (!data->sensorhub_input_dev) { + pr_err("%s: allocate sensorhub input devices err", __func__); + ret = -ENOMEM; + goto err_input_allocate_device_sensorhub; + } + + wake_lock_init(&data->sensorhub_wake_lock, WAKE_LOCK_SUSPEND, + "sensorhub_wake_lock"); + INIT_LIST_HEAD(&data->events_head.list); + init_waitqueue_head(&data->sensorhub_waitqueue); + init_completion(&data->transfer_done); + + ret = input_register_device(data->sensorhub_input_dev); + if (ret < 0) { + pr_err("%s: could not register sensorhub input device(%d)", + __func__, ret); + input_free_device(data->sensorhub_input_dev); + goto err_input_register_device_sensorhub; + } + + data->sensorhub_input_dev->name = "ssp_context"; + input_set_drvdata(data->sensorhub_input_dev, data); + input_set_capability(data->sensorhub_input_dev, EV_REL, REL_RX); + input_set_capability(data->sensorhub_input_dev, EV_REL, REL_RY); + + /* create sensorhub device node */ + data->sensorhub_device.minor = MISC_DYNAMIC_MINOR; + data->sensorhub_device.name = "ssp_sensorhub"; + data->sensorhub_device.fops = &ssp_sensorhub_fops; + + ret = misc_register(&data->sensorhub_device); + if (ret < 0) { + pr_err("%s: misc_register() failed", __func__); + goto err_misc_register; + } + + data->sensorhub_task = kthread_run(ssp_senosrhub_thread_func, + (void *)data, "ssp_sensorhub_task"); + if (IS_ERR(data->sensorhub_task)) { + ret = PTR_ERR(data->sensorhub_task); + goto err_kthread_create; + } + + return 0; + +err_kthread_create: + misc_deregister(&data->sensorhub_device); +err_misc_register: + input_unregister_device(data->sensorhub_input_dev); +err_input_register_device_sensorhub: + complete_all(&data->transfer_done); + wake_lock_destroy(&data->sensorhub_wake_lock); +err_input_allocate_device_sensorhub: + return ret; +} + +void ssp_remove_sensorhub(struct ssp_data *data) +{ + complete_all(&data->transfer_done); + kthread_stop(data->sensorhub_task); + misc_deregister(&data->sensorhub_device); + input_unregister_device(data->sensorhub_input_dev); + wake_lock_destroy(&data->sensorhub_wake_lock); +} + +MODULE_DESCRIPTION("Samsung Sensor Platform(SSP) sensorhub driver"); +MODULE_AUTHOR("Samsung Electronics"); +MODULE_LICENSE("GPL"); |