diff options
Diffstat (limited to 'drivers/omap_hsi/hsi_protocol.c')
-rw-r--r-- | drivers/omap_hsi/hsi_protocol.c | 308 |
1 files changed, 308 insertions, 0 deletions
diff --git a/drivers/omap_hsi/hsi_protocol.c b/drivers/omap_hsi/hsi_protocol.c new file mode 100644 index 0000000..e1451e7 --- /dev/null +++ b/drivers/omap_hsi/hsi_protocol.c @@ -0,0 +1,308 @@ +/* + * File - hsi_protocol.c + * + * Implements HSI protocol for Infineon Modem. + * + * Copyright (C) 2011 Samsung Electronics. + * + * Author: Rupesh Gujare <rupesh.g@samsung.com> + * + * This package is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED + * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. + */ + +#if 0 +#define DEBUG 1 +#endif + +#include <linux/errno.h> +#include <linux/module.h> +#include <linux/types.h> +#include <linux/init.h> +#include <linux/device.h> +#include <linux/err.h> +#include <linux/mm.h> +#include <linux/fs.h> +#include <linux/slab.h> +#include <linux/poll.h> +#include <linux/sched.h> +#include <linux/io.h> + +#include "hsi-protocol-if.h" +#include <linux/hsi_driver_if.h> + +#define DRIVER_VERSION "1.0" + +char test_data[10] = "abcdefghij"; + +dev_t hsi_protocol_dev; + +struct protocol_queue { + struct list_head list; + u32 *data; + unsigned int count; +}; + +struct hsi_protocol { + unsigned int opened; + int poll_event; + struct list_head rx_queue; + struct list_head tx_queue; + spinlock_t lock; /* Serialize access to driver data and API */ + struct fasync_struct *async_queue; + wait_queue_head_t rx_wait; + wait_queue_head_t tx_wait; + wait_queue_head_t poll_wait; +}; + +static struct hsi_protocol hsi_protocol_data[HSI_MAX_CHANNELS]; + +void if_notify(int ch, struct hsi_event *ev) +{ + struct protocol_queue *entry; + + pr_debug("%s, ev = {0x%x, 0x%p, %u}\n", + __func__, ev->event, ev->data, ev->count); + + spin_lock(&hsi_protocol_data[ch].lock); + +/* Not Required */ + /*if (!hsi_protocol_data[ch].opened) { + pr_debug("%s, device not opened\n!", __func__); + printk(KERN_INFO "%s, device not opened\n!", __func__); + spin_unlock(&hsi_protocol_data[ch].lock); + return; + }*/ + + switch (HSI_EV_TYPE(ev->event)) { + case HSI_EV_IN: + entry = kmalloc(sizeof(*entry), GFP_ATOMIC); + if (!entry) { + pr_err("HSI-CHAR: entry allocation failed.\n"); + spin_unlock(&hsi_protocol_data[ch].lock); + return; + } + entry->data = ev->data; + entry->count = ev->count; + list_add_tail(&entry->list, &hsi_protocol_data[ch].rx_queue); + spin_unlock(&hsi_protocol_data[ch].lock); + pr_debug("%s, HSI_EV_IN\n", __func__); + wake_up_interruptible(&hsi_protocol_data[ch].rx_wait); + break; + case HSI_EV_OUT: + entry = kmalloc(sizeof(*entry), GFP_ATOMIC); + if (!entry) { + pr_err("HSI-CHAR: entry allocation failed.\n"); + spin_unlock(&hsi_protocol_data[ch].lock); + return; + } + entry->data = ev->data; + entry->count = ev->count; + hsi_protocol_data[ch].poll_event |= (POLLOUT | POLLWRNORM); + list_add_tail(&entry->list, &hsi_protocol_data[ch].tx_queue); + spin_unlock(&hsi_protocol_data[ch].lock); + pr_debug("%s, HSI_EV_OUT\n", __func__); + wake_up_interruptible(&hsi_protocol_data[ch].tx_wait); + break; + case HSI_EV_EXCEP: + hsi_protocol_data[ch].poll_event |= POLLPRI; + spin_unlock(&hsi_protocol_data[ch].lock); + pr_debug("%s, HSI_EV_EXCEP\n", __func__); + wake_up_interruptible(&hsi_protocol_data[ch].poll_wait); + break; + case HSI_EV_AVAIL: + hsi_protocol_data[ch].poll_event |= (POLLIN | POLLRDNORM); + spin_unlock(&hsi_protocol_data[ch].lock); + pr_debug("%s, HSI_EV_AVAIL\n", __func__); + wake_up_interruptible(&hsi_protocol_data[ch].poll_wait); + break; + default: + spin_unlock(&hsi_protocol_data[ch].lock); + break; + } +} + +int hsi_proto_read(int ch, u32 *buffer, int count) +{ + DECLARE_WAITQUEUE(wait, current); + u32 *data; + unsigned int data_len = 0; + struct protocol_queue *entry; + int ret, recv_data = 0; + + /*if (count > MAX_HSI_IPC_BUFFER) + count = MAX_HSI_IPC_BUFFER; + + data = kmalloc(count, GFP_ATOMIC);*/ + + ret = if_hsi_read(ch, buffer, count); + if (ret < 0) { + pr_err("Can not submit read. READ Error\n"); + goto out2; + } + + spin_lock_bh(&hsi_protocol_data[ch].lock); + add_wait_queue(&hsi_protocol_data[ch].rx_wait, &wait); + spin_unlock_bh(&hsi_protocol_data[ch].lock); + + for (;;) { + data = NULL; + data_len = 0; + + set_current_state(TASK_INTERRUPTIBLE); + + spin_lock_bh(&hsi_protocol_data[ch].lock); + if (!list_empty(&hsi_protocol_data[ch].rx_queue)) { + entry = list_entry(hsi_protocol_data[ch].rx_queue.next, + struct protocol_queue, list); + data = entry->data; + data_len = entry->count; + list_del(&entry->list); + kfree(entry); + } + spin_unlock_bh(&hsi_protocol_data[ch].lock); + + pr_debug("%s, data = 0x%p, data_len = %d\n", + __func__, data, data_len); + + if (data_len) { + pr_debug("%s, RX finished, ch-> %d, length = %d\n", + __func__, ch, count); + spin_lock_bh(&hsi_protocol_data[ch].lock); + hsi_protocol_data[ch].poll_event &= + ~(POLLIN | POLLRDNORM); + spin_unlock_bh(&hsi_protocol_data[ch].lock); + if_hsi_poll(ch); +#if 0 + memcpy(buffer, data, count); +#endif + recv_data += data_len; +#if 0 + buffer += data_len; + if ((recv_data == count) || (recv_data >= MAX_HSI_IPC_BUFFER)) +#endif + break; + } else if (signal_pending(current)) { + pr_debug("%s, ERESTARTSYS\n", __func__); + recv_data = -EAGAIN; + if_hsi_cancel_read(ch); + /* goto out; */ + break; + } + + /*printk(KERN_DEBUG "%s, going to sleep...\n", __func__); */ + schedule(); + /*printk(KERN_DEBUG "%s, woke up\n", __func__); */ + } + +/*out:*/ + __set_current_state(TASK_RUNNING); + remove_wait_queue(&hsi_protocol_data[ch].rx_wait, &wait); + +out2: + /*To Do- Set bit if data to be received is + * greater than 512K Bytes and return to IPC call + */ + + return recv_data; +} + +int hsi_proto_write(int ch, u32 *buffer, int length) +{ + + DECLARE_WAITQUEUE(wait, current); + u32 *data; + unsigned int data_len = 0, ret = -1; + struct protocol_queue *entry; + + ret = if_hsi_write(ch, buffer, length); + if (ret < 0) { + pr_err("HSI Write ERROR %s\n", __func__); + goto out2; + } else + spin_lock_bh(&hsi_protocol_data[ch].lock); + hsi_protocol_data[ch].poll_event &= ~(POLLOUT | POLLWRNORM); + add_wait_queue(&hsi_protocol_data[ch].tx_wait, &wait); + spin_unlock_bh(&hsi_protocol_data[ch].lock); + + for (;;) { + data = NULL; + data_len = 0; + + set_current_state(TASK_INTERRUPTIBLE); + spin_lock_bh(&hsi_protocol_data[ch].lock); + if (!list_empty(&hsi_protocol_data[ch].tx_queue)) { + entry = list_entry(hsi_protocol_data[ch].tx_queue.next, + struct protocol_queue, list); + data = entry->data; + data_len = entry->count; + list_del(&entry->list); + kfree(entry); + } + spin_unlock_bh(&hsi_protocol_data[ch].lock); + + if (data_len) { + pr_debug("%s, TX finished, data_len = %d, ch-> %d\n", + __func__, length, ch); + ret = data_len; + break; + } else if (signal_pending(current)) { + pr_debug("%s, ERESTARTSYS\n", __func__); + ret = -ERESTARTSYS; + goto out; + } + + schedule(); + } + +out: + __set_current_state(TASK_RUNNING); + remove_wait_queue(&hsi_protocol_data[ch].tx_wait, &wait); + +out2: + return ret; +} +EXPORT_SYMBOL(hsi_proto_write); + +static int __init hsi_protocol_init(void) +{ + int i, ret = 0; + + pr_info("HSI Infineon Protocol driver version " DRIVER_VERSION "\n"); + + for (i = 0; i < HSI_MAX_CHANNELS; i++) { + init_waitqueue_head(&hsi_protocol_data[i].rx_wait); + init_waitqueue_head(&hsi_protocol_data[i].tx_wait); + init_waitqueue_head(&hsi_protocol_data[i].poll_wait); + spin_lock_init(&hsi_protocol_data[i].lock); + hsi_protocol_data[i].opened = 0; + INIT_LIST_HEAD(&hsi_protocol_data[i].rx_queue); + INIT_LIST_HEAD(&hsi_protocol_data[i].tx_queue); + } + + printk(KERN_INFO "hsi_protocol_init : hsi_mux_setting Done.\n"); + + ret = if_hsi_init(); + + return ret; +} + + +static void __exit hsi_protocol_exit(void) +{ + if_hsi_exit(); +} + + +MODULE_AUTHOR("Rupesh Gujare <rupesh.g@samsung.com> / Samsung Electronics"); +MODULE_DESCRIPTION("HSI Protocol for Infineon Modem"); +MODULE_LICENSE("GPL"); +MODULE_VERSION(DRIVER_VERSION); + +module_init(hsi_protocol_init); +module_exit(hsi_protocol_exit); |