| /* |
| * File - hsi_protocol.c |
| * |
| * Implements HSI protocol for Infineon Modem. |
| * |
| * Copyright (C) 2011 Samsung Electronics. |
| * |
| * Author: Rupesh Gujare <[email protected]> |
| * |
| * 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 <[email protected]> / 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); |