Initial Contribution
msm-2.6.38: tag AU_LINUX_ANDROID_GINGERBREAD.02.03.04.00.142
Signed-off-by: Bryan Huntsman <bryanh@codeaurora.org>
diff --git a/arch/arm/mach-msm/smd_pkt.c b/arch/arm/mach-msm/smd_pkt.c
new file mode 100644
index 0000000..781d4fc
--- /dev/null
+++ b/arch/arm/mach-msm/smd_pkt.c
@@ -0,0 +1,864 @@
+/* Copyright (c) 2008-2011, Code Aurora Forum. 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 version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * 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.
+ *
+ */
+/*
+ * SMD Packet Driver -- Provides a binary SMD non-muxed packet port
+ * interface.
+ */
+
+#include <linux/slab.h>
+#include <linux/cdev.h>
+#include <linux/module.h>
+#include <linux/fs.h>
+#include <linux/device.h>
+#include <linux/sched.h>
+#include <linux/spinlock.h>
+#include <linux/mutex.h>
+#include <linux/delay.h>
+#include <linux/uaccess.h>
+#include <linux/workqueue.h>
+#include <linux/platform_device.h>
+#include <linux/completion.h>
+#include <linux/msm_smd_pkt.h>
+#include <linux/poll.h>
+#include <asm/ioctls.h>
+
+#include <mach/msm_smd.h>
+#include <mach/peripheral-loader.h>
+
+#include "smd_private.h"
+#ifdef CONFIG_ARCH_FSM9XXX
+#define NUM_SMD_PKT_PORTS 4
+#else
+#define NUM_SMD_PKT_PORTS 12
+#endif
+
+#define LOOPBACK_INX (NUM_SMD_PKT_PORTS - 1)
+
+#define DEVICE_NAME "smdpkt"
+
+struct smd_pkt_dev {
+ struct cdev cdev;
+ struct device *devicep;
+ void *pil;
+ struct platform_driver driver;
+
+ struct smd_channel *ch;
+ struct mutex ch_lock;
+ struct mutex rx_lock;
+ struct mutex tx_lock;
+ wait_queue_head_t ch_read_wait_queue;
+ wait_queue_head_t ch_write_wait_queue;
+ wait_queue_head_t ch_opened_wait_queue;
+
+ int i;
+
+ int blocking_write;
+ int needed_space;
+ int is_open;
+ unsigned ch_size;
+ uint open_modem_wait;
+
+ int has_reset;
+ int do_reset_notification;
+ struct completion ch_allocated;
+
+} *smd_pkt_devp[NUM_SMD_PKT_PORTS];
+
+struct class *smd_pkt_classp;
+static dev_t smd_pkt_number;
+static struct delayed_work loopback_work;
+static void check_and_wakeup_reader(struct smd_pkt_dev *smd_pkt_devp);
+static void check_and_wakeup_writer(struct smd_pkt_dev *smd_pkt_devp);
+static uint32_t is_modem_smsm_inited(void);
+
+static int msm_smd_pkt_debug_mask;
+module_param_named(debug_mask, msm_smd_pkt_debug_mask,
+ int, S_IRUGO | S_IWUSR | S_IWGRP);
+#define DEBUG
+
+#ifdef DEBUG
+#define D_DUMP_BUFFER(prestr, cnt, buf) \
+do { \
+ if (msm_smd_pkt_debug_mask) \
+ print_hex_dump(KERN_DEBUG, prestr, \
+ DUMP_PREFIX_NONE, 16, 1, \
+ buf, cnt, 1); \
+} while (0)
+#else
+#define D_DUMP_BUFFER(prestr, cnt, buf) do {} while (0)
+#endif
+
+#ifdef DEBUG
+#define D(x...) if (msm_smd_pkt_debug_mask) printk(x)
+#else
+#define D(x...) do {} while (0)
+#endif
+
+static ssize_t open_timeout_store(struct device *d,
+ struct device_attribute *attr,
+ const char *buf,
+ size_t n)
+{
+ int i;
+ unsigned long tmp;
+ for (i = 0; i < NUM_SMD_PKT_PORTS; ++i) {
+ if (smd_pkt_devp[i]->devicep == d)
+ break;
+ }
+ if (!strict_strtoul(buf, 10, &tmp)) {
+ smd_pkt_devp[i]->open_modem_wait = tmp;
+ return n;
+ } else {
+ pr_err("%s: unable to convert: %s to an int\n", __func__,
+ buf);
+ return -EINVAL;
+ }
+}
+
+static ssize_t open_timeout_show(struct device *d,
+ struct device_attribute *attr,
+ char *buf)
+{
+ int i;
+ for (i = 0; i < NUM_SMD_PKT_PORTS; ++i) {
+ if (smd_pkt_devp[i]->devicep == d)
+ break;
+ }
+ return sprintf(buf, "%d\n", smd_pkt_devp[i]->open_modem_wait);
+}
+
+static DEVICE_ATTR(open_timeout, 0664, open_timeout_show, open_timeout_store);
+
+static int notify_reset(struct smd_pkt_dev *smd_pkt_devp)
+{
+ smd_pkt_devp->do_reset_notification = 0;
+
+ return -ENETRESET;
+}
+
+static void clean_and_signal(struct smd_pkt_dev *smd_pkt_devp)
+{
+ smd_pkt_devp->do_reset_notification = 1;
+ smd_pkt_devp->has_reset = 1;
+
+ smd_pkt_devp->is_open = 0;
+
+ wake_up_interruptible(&smd_pkt_devp->ch_read_wait_queue);
+ wake_up_interruptible(&smd_pkt_devp->ch_write_wait_queue);
+ wake_up_interruptible(&smd_pkt_devp->ch_opened_wait_queue);
+}
+
+static void loopback_probe_worker(struct work_struct *work)
+{
+
+ /* Wait for the modem SMSM to be inited for the SMD
+ ** Loopback channel to be allocated at the modem. Since
+ ** the wait need to be done atmost once, using msleep
+ ** doesn't degrade the performance. */
+ if (!is_modem_smsm_inited())
+ schedule_delayed_work(&loopback_work, msecs_to_jiffies(1000));
+ else
+ smsm_change_state(SMSM_APPS_STATE,
+ 0, SMSM_SMD_LOOPBACK);
+
+}
+
+static long smd_pkt_ioctl(struct file *file, unsigned int cmd,
+ unsigned long arg)
+{
+ int ret;
+ struct smd_pkt_dev *smd_pkt_devp;
+
+ smd_pkt_devp = file->private_data;
+ if (!smd_pkt_devp)
+ return -EINVAL;
+
+ switch (cmd) {
+ case TIOCMGET:
+ ret = smd_tiocmget(smd_pkt_devp->ch);
+ break;
+ case TIOCMSET:
+ ret = smd_tiocmset(smd_pkt_devp->ch, arg, ~arg);
+ break;
+ case SMD_PKT_IOCTL_BLOCKING_WRITE:
+ ret = get_user(smd_pkt_devp->blocking_write, (int *)arg);
+ break;
+ default:
+ ret = -1;
+ }
+
+ return ret;
+}
+
+ssize_t smd_pkt_read(struct file *file,
+ char __user *buf,
+ size_t count,
+ loff_t *ppos)
+{
+ int r;
+ int bytes_read;
+ struct smd_pkt_dev *smd_pkt_devp;
+ struct smd_channel *chl;
+
+ D(KERN_ERR "%s: read %i bytes\n",
+ __func__, count);
+
+ smd_pkt_devp = file->private_data;
+
+ if (!smd_pkt_devp || !smd_pkt_devp->ch)
+ return -EINVAL;
+
+ if (smd_pkt_devp->do_reset_notification) {
+ /* notify client that a reset occurred */
+ return notify_reset(smd_pkt_devp);
+ }
+
+ chl = smd_pkt_devp->ch;
+wait_for_packet:
+ r = wait_event_interruptible(smd_pkt_devp->ch_read_wait_queue,
+ (smd_cur_packet_size(chl) > 0 &&
+ smd_read_avail(chl) >=
+ smd_cur_packet_size(chl)) ||
+ smd_pkt_devp->has_reset);
+
+ if (smd_pkt_devp->has_reset)
+ return notify_reset(smd_pkt_devp);
+
+ if (r < 0) {
+ /* qualify error message */
+ if (r != -ERESTARTSYS) {
+ /* we get this anytime a signal comes in */
+ printk(KERN_ERR "ERROR:%s:%i:%s: "
+ "wait_event_interruptible ret %i\n",
+ __FILE__,
+ __LINE__,
+ __func__,
+ r
+ );
+ }
+ return r;
+ }
+
+ /* Here we have a whole packet waiting for us */
+
+ mutex_lock(&smd_pkt_devp->rx_lock);
+ bytes_read = smd_cur_packet_size(smd_pkt_devp->ch);
+
+ D(KERN_ERR "%s: after wait_event_interruptible bytes_read = %i\n",
+ __func__, bytes_read);
+
+ if (bytes_read == 0 ||
+ bytes_read < smd_read_avail(smd_pkt_devp->ch)) {
+ D(KERN_ERR "%s: Nothing to read\n", __func__);
+ mutex_unlock(&smd_pkt_devp->rx_lock);
+ goto wait_for_packet;
+ }
+
+ if (bytes_read > count) {
+ printk(KERN_ERR "packet size %i > buffer size %i, "
+ "dropping packet!", bytes_read, count);
+ smd_read(smd_pkt_devp->ch, 0, bytes_read);
+ mutex_unlock(&smd_pkt_devp->rx_lock);
+ return -EINVAL;
+ }
+
+ if (smd_read_user_buffer(smd_pkt_devp->ch, buf, bytes_read)
+ != bytes_read) {
+ mutex_unlock(&smd_pkt_devp->rx_lock);
+ if (smd_pkt_devp->has_reset)
+ return notify_reset(smd_pkt_devp);
+
+ printk(KERN_ERR "user read: not enough data?!\n");
+ return -EINVAL;
+ }
+ D_DUMP_BUFFER("read: ", bytes_read, buf);
+ mutex_unlock(&smd_pkt_devp->rx_lock);
+
+ D(KERN_ERR "%s: just read %i bytes\n",
+ __func__, bytes_read);
+
+ /* check and wakeup read threads waiting on this device */
+ check_and_wakeup_reader(smd_pkt_devp);
+
+ return bytes_read;
+}
+
+ssize_t smd_pkt_write(struct file *file,
+ const char __user *buf,
+ size_t count,
+ loff_t *ppos)
+{
+ int r = 0;
+ struct smd_pkt_dev *smd_pkt_devp;
+ DEFINE_WAIT(write_wait);
+
+ D(KERN_ERR "%s: writting %i bytes\n",
+ __func__, count);
+
+ smd_pkt_devp = file->private_data;
+
+ if (!smd_pkt_devp || !smd_pkt_devp->ch)
+ return -EINVAL;
+
+ if (count > smd_pkt_devp->ch_size)
+ return -EINVAL;
+
+ if (smd_pkt_devp->do_reset_notification) {
+ /* notify client that a reset occurred */
+ return notify_reset(smd_pkt_devp);
+ }
+
+ if (smd_pkt_devp->blocking_write) {
+ for (;;) {
+ mutex_lock(&smd_pkt_devp->tx_lock);
+ if (smd_pkt_devp->has_reset) {
+ smd_disable_read_intr(smd_pkt_devp->ch);
+ mutex_unlock(&smd_pkt_devp->tx_lock);
+ return notify_reset(smd_pkt_devp);
+ }
+ if (signal_pending(current)) {
+ smd_disable_read_intr(smd_pkt_devp->ch);
+ mutex_unlock(&smd_pkt_devp->tx_lock);
+ return -ERESTARTSYS;
+ }
+
+ prepare_to_wait(&smd_pkt_devp->ch_write_wait_queue,
+ &write_wait, TASK_INTERRUPTIBLE);
+ smd_enable_read_intr(smd_pkt_devp->ch);
+ if (smd_write_avail(smd_pkt_devp->ch) < count) {
+ if (!smd_pkt_devp->needed_space ||
+ count < smd_pkt_devp->needed_space)
+ smd_pkt_devp->needed_space = count;
+ mutex_unlock(&smd_pkt_devp->tx_lock);
+ schedule();
+ } else
+ break;
+ }
+ finish_wait(&smd_pkt_devp->ch_write_wait_queue, &write_wait);
+ smd_disable_read_intr(smd_pkt_devp->ch);
+ if (smd_pkt_devp->has_reset) {
+ mutex_unlock(&smd_pkt_devp->tx_lock);
+ return notify_reset(smd_pkt_devp);
+ }
+ if (signal_pending(current)) {
+ mutex_unlock(&smd_pkt_devp->tx_lock);
+ return -ERESTARTSYS;
+ }
+ } else {
+ if (smd_pkt_devp->has_reset)
+ return notify_reset(smd_pkt_devp);
+ if (signal_pending(current))
+ return -ERESTARTSYS;
+
+ mutex_lock(&smd_pkt_devp->tx_lock);
+ if (smd_write_avail(smd_pkt_devp->ch) < count) {
+ D(KERN_ERR "%s: Not enough space to write\n",
+ __func__);
+ mutex_unlock(&smd_pkt_devp->tx_lock);
+ return -ENOMEM;
+ }
+ }
+
+ D_DUMP_BUFFER("write: ", count, buf);
+
+ smd_pkt_devp->needed_space = 0;
+
+ r = smd_write_user_buffer(smd_pkt_devp->ch, buf, count);
+ if (r != count) {
+ mutex_unlock(&smd_pkt_devp->tx_lock);
+ if (smd_pkt_devp->has_reset)
+ return notify_reset(smd_pkt_devp);
+
+ printk(KERN_ERR "ERROR:%s:%i:%s: "
+ "smd_write(ch,buf,count = %i) ret %i.\n",
+ __FILE__,
+ __LINE__,
+ __func__,
+ count,
+ r);
+ return r;
+ }
+ mutex_unlock(&smd_pkt_devp->tx_lock);
+
+ D(KERN_ERR "%s: just wrote %i bytes\n",
+ __func__, count);
+
+ return count;
+}
+
+static unsigned int smd_pkt_poll(struct file *file, poll_table *wait)
+{
+ struct smd_pkt_dev *smd_pkt_devp;
+ unsigned int mask = 0;
+
+ smd_pkt_devp = file->private_data;
+ if (!smd_pkt_devp)
+ return POLLERR;
+
+ poll_wait(file, &smd_pkt_devp->ch_read_wait_queue, wait);
+ if (smd_read_avail(smd_pkt_devp->ch))
+ mask |= POLLIN | POLLRDNORM;
+
+ return mask;
+}
+
+static void check_and_wakeup_reader(struct smd_pkt_dev *smd_pkt_devp)
+{
+ int sz;
+
+ if (!smd_pkt_devp || !smd_pkt_devp->ch)
+ return;
+
+ sz = smd_cur_packet_size(smd_pkt_devp->ch);
+ if (sz == 0) {
+ D(KERN_ERR "%s: packet size is 0\n", __func__);
+ return;
+ }
+ if (sz > smd_read_avail(smd_pkt_devp->ch)) {
+ D(KERN_ERR "%s: packet size is %i - "
+ "the whole packet isn't here\n",
+ __func__, sz);
+ return;
+ }
+
+ /* here we have a packet of size sz ready */
+ wake_up_interruptible(&smd_pkt_devp->ch_read_wait_queue);
+ D(KERN_ERR "%s: after wake_up\n", __func__);
+}
+
+static void check_and_wakeup_writer(struct smd_pkt_dev *smd_pkt_devp)
+{
+ int sz;
+
+ if (!smd_pkt_devp || !smd_pkt_devp->ch)
+ return;
+
+ sz = smd_write_avail(smd_pkt_devp->ch);
+ if (sz >= smd_pkt_devp->needed_space) {
+ D(KERN_ERR "%s: %d bytes Write Space available\n",
+ __func__, sz);
+ smd_disable_read_intr(smd_pkt_devp->ch);
+ wake_up_interruptible(&smd_pkt_devp->ch_write_wait_queue);
+ }
+}
+
+static void ch_notify(void *priv, unsigned event)
+{
+ struct smd_pkt_dev *smd_pkt_devp = priv;
+
+ if (smd_pkt_devp->ch == 0)
+ return;
+
+ switch (event) {
+ case SMD_EVENT_DATA: {
+ D(KERN_ERR "%s: data\n", __func__);
+ check_and_wakeup_reader(smd_pkt_devp);
+ if (smd_pkt_devp->blocking_write)
+ check_and_wakeup_writer(smd_pkt_devp);
+ D(KERN_ERR "%s: data after check_and_wakeup\n", __func__);
+ break;
+ }
+ case SMD_EVENT_OPEN:
+ D(KERN_ERR "%s: smd opened\n",
+ __func__);
+
+ smd_pkt_devp->has_reset = 0;
+ smd_pkt_devp->is_open = 1;
+ wake_up_interruptible(&smd_pkt_devp->ch_opened_wait_queue);
+ break;
+ case SMD_EVENT_CLOSE:
+ smd_pkt_devp->is_open = 0;
+ printk(KERN_ERR "%s: smd closed\n",
+ __func__);
+
+ /* put port into reset state */
+ clean_and_signal(smd_pkt_devp);
+ if (smd_pkt_devp->i == LOOPBACK_INX)
+ schedule_delayed_work(&loopback_work,
+ msecs_to_jiffies(1000));
+ break;
+ }
+}
+
+#ifdef CONFIG_ARCH_FSM9XXX
+static char *smd_pkt_dev_name[] = {
+ "smdcntl1",
+ "smdcntl2",
+ "smd22",
+ "smd_pkt_loopback",
+};
+
+static char *smd_ch_name[] = {
+ "DATA6_CNTL",
+ "DATA7_CNTL",
+ "DATA22",
+ "LOOPBACK",
+};
+
+static uint32_t smd_ch_edge[] = {
+ SMD_APPS_QDSP,
+ SMD_APPS_QDSP,
+ SMD_APPS_QDSP,
+ SMD_APPS_QDSP
+};
+#else
+static char *smd_pkt_dev_name[] = {
+ "smdcntl0",
+ "smdcntl1",
+ "smdcntl2",
+ "smdcntl3",
+ "smdcntl4",
+ "smdcntl5",
+ "smdcntl6",
+ "smdcntl7",
+ "smd22",
+ "smd_sns_dsps",
+ "apr_apps_user",
+ "smd_pkt_loopback",
+};
+
+static char *smd_ch_name[] = {
+ "DATA5_CNTL",
+ "DATA6_CNTL",
+ "DATA7_CNTL",
+ "DATA8_CNTL",
+ "DATA9_CNTL",
+ "DATA12_CNTL",
+ "DATA13_CNTL",
+ "DATA14_CNTL",
+ "DATA22",
+ "SENSOR",
+ "apr_apps_user",
+ "LOOPBACK",
+};
+
+static uint32_t smd_ch_edge[] = {
+ SMD_APPS_MODEM,
+ SMD_APPS_MODEM,
+ SMD_APPS_MODEM,
+ SMD_APPS_MODEM,
+ SMD_APPS_MODEM,
+ SMD_APPS_MODEM,
+ SMD_APPS_MODEM,
+ SMD_APPS_MODEM,
+ SMD_APPS_MODEM,
+ SMD_APPS_DSPS,
+ SMD_APPS_QDSP,
+ SMD_APPS_MODEM,
+};
+#endif
+
+static int smd_pkt_dummy_probe(struct platform_device *pdev)
+{
+ int i;
+
+ for (i = 0; i < NUM_SMD_PKT_PORTS; i++) {
+ if (!strcmp(pdev->name, smd_ch_name[i])) {
+ complete_all(&smd_pkt_devp[i]->ch_allocated);
+ break;
+ }
+ }
+ return 0;
+}
+
+static uint32_t is_modem_smsm_inited(void)
+{
+ uint32_t modem_state;
+ uint32_t ready_state = (SMSM_INIT | SMSM_SMDINIT);
+
+ modem_state = smsm_get_state(SMSM_MODEM_STATE);
+ return (modem_state & ready_state) == ready_state;
+}
+
+int smd_pkt_open(struct inode *inode, struct file *file)
+{
+ int r = 0;
+ struct smd_pkt_dev *smd_pkt_devp;
+ char *peripheral = NULL;
+
+ smd_pkt_devp = container_of(inode->i_cdev, struct smd_pkt_dev, cdev);
+
+ if (!smd_pkt_devp)
+ return -EINVAL;
+
+ file->private_data = smd_pkt_devp;
+
+ mutex_lock(&smd_pkt_devp->ch_lock);
+ if (smd_pkt_devp->ch == 0) {
+
+ if (smd_ch_edge[smd_pkt_devp->i] == SMD_APPS_MODEM)
+ peripheral = "modem";
+ else if (smd_ch_edge[smd_pkt_devp->i] == SMD_APPS_QDSP)
+ peripheral = "q6";
+
+ if (peripheral) {
+ smd_pkt_devp->pil = pil_get(peripheral);
+ if (IS_ERR(smd_pkt_devp->pil)) {
+ r = PTR_ERR(smd_pkt_devp->pil);
+ goto out;
+ }
+
+ /* Wait for the modem SMSM to be inited for the SMD
+ ** Loopback channel to be allocated at the modem. Since
+ ** the wait need to be done atmost once, using msleep
+ ** doesn't degrade the performance. */
+ if (!strcmp(smd_ch_name[smd_pkt_devp->i], "LOOPBACK")) {
+ if (!is_modem_smsm_inited())
+ msleep(5000);
+ smsm_change_state(SMSM_APPS_STATE,
+ 0, SMSM_SMD_LOOPBACK);
+ msleep(100);
+ }
+
+ /*
+ * Wait for a packet channel to be allocated so we know
+ * the modem is ready enough.
+ */
+ if (smd_pkt_devp->open_modem_wait) {
+ r = wait_for_completion_interruptible_timeout(
+ &smd_pkt_devp->ch_allocated,
+ msecs_to_jiffies(
+ smd_pkt_devp->open_modem_wait
+ * 1000));
+ if (r == 0)
+ r = -ETIMEDOUT;
+ if (r < 0) {
+ pr_err("%s: wait failed for smd port:"
+ " %d\n", __func__, r);
+ goto release_pil;
+ }
+ }
+ }
+
+ r = smd_named_open_on_edge(smd_ch_name[smd_pkt_devp->i],
+ smd_ch_edge[smd_pkt_devp->i],
+ &smd_pkt_devp->ch,
+ smd_pkt_devp,
+ ch_notify);
+ if (r < 0) {
+ pr_err("%s: %s open failed %d\n", __func__,
+ smd_ch_name[smd_pkt_devp->i], r);
+ goto release_pil;
+ }
+
+ r = wait_event_interruptible_timeout(
+ smd_pkt_devp->ch_opened_wait_queue,
+ smd_pkt_devp->is_open, (2 * HZ));
+ if (r == 0)
+ r = -ETIMEDOUT;
+
+ if (r < 0) {
+ pr_err("%s: wait failed for smd open: %d\n",
+ __func__, r);
+ } else if (!smd_pkt_devp->is_open) {
+ pr_err("%s: Invalid open notification\n", __func__);
+ r = -ENODEV;
+ } else {
+ smd_disable_read_intr(smd_pkt_devp->ch);
+ smd_pkt_devp->ch_size =
+ smd_write_avail(smd_pkt_devp->ch);
+ r = 0;
+ }
+ }
+release_pil:
+ if (peripheral && (r < 0))
+ pil_put(smd_pkt_devp->pil);
+out:
+ mutex_unlock(&smd_pkt_devp->ch_lock);
+
+ return r;
+}
+
+int smd_pkt_release(struct inode *inode, struct file *file)
+{
+ int r = 0;
+ struct smd_pkt_dev *smd_pkt_devp = file->private_data;
+
+ if (!smd_pkt_devp)
+ return -EINVAL;
+
+ clean_and_signal(smd_pkt_devp);
+
+ mutex_lock(&smd_pkt_devp->ch_lock);
+ if (smd_pkt_devp->ch != 0) {
+ r = smd_close(smd_pkt_devp->ch);
+ smd_pkt_devp->ch = 0;
+ smd_pkt_devp->blocking_write = 0;
+ if (smd_pkt_devp->pil)
+ pil_put(smd_pkt_devp->pil);
+ }
+ mutex_unlock(&smd_pkt_devp->ch_lock);
+
+ smd_pkt_devp->has_reset = 0;
+ smd_pkt_devp->do_reset_notification = 0;
+
+ return r;
+}
+
+static const struct file_operations smd_pkt_fops = {
+ .owner = THIS_MODULE,
+ .open = smd_pkt_open,
+ .release = smd_pkt_release,
+ .read = smd_pkt_read,
+ .write = smd_pkt_write,
+ .poll = smd_pkt_poll,
+ .unlocked_ioctl = smd_pkt_ioctl,
+};
+
+static int __init smd_pkt_init(void)
+{
+ int i;
+ int r;
+
+ r = alloc_chrdev_region(&smd_pkt_number,
+ 0,
+ NUM_SMD_PKT_PORTS,
+ DEVICE_NAME);
+ if (IS_ERR_VALUE(r)) {
+ printk(KERN_ERR "ERROR:%s:%i:%s: "
+ "alloc_chrdev_region() ret %i.\n",
+ __FILE__,
+ __LINE__,
+ __func__,
+ r);
+ goto error0;
+ }
+
+ smd_pkt_classp = class_create(THIS_MODULE, DEVICE_NAME);
+ if (IS_ERR(smd_pkt_classp)) {
+ printk(KERN_ERR "ERROR:%s:%i:%s: "
+ "class_create() ENOMEM\n",
+ __FILE__,
+ __LINE__,
+ __func__);
+ r = -ENOMEM;
+ goto error1;
+ }
+
+ for (i = 0; i < NUM_SMD_PKT_PORTS; ++i) {
+ smd_pkt_devp[i] = kzalloc(sizeof(struct smd_pkt_dev),
+ GFP_KERNEL);
+ if (IS_ERR(smd_pkt_devp[i])) {
+ printk(KERN_ERR "ERROR:%s:%i:%s kmalloc() ENOMEM\n",
+ __FILE__,
+ __LINE__,
+ __func__);
+ r = -ENOMEM;
+ goto error2;
+ }
+
+ smd_pkt_devp[i]->i = i;
+
+ init_waitqueue_head(&smd_pkt_devp[i]->ch_read_wait_queue);
+ init_waitqueue_head(&smd_pkt_devp[i]->ch_write_wait_queue);
+ smd_pkt_devp[i]->is_open = 0;
+ init_waitqueue_head(&smd_pkt_devp[i]->ch_opened_wait_queue);
+
+ mutex_init(&smd_pkt_devp[i]->ch_lock);
+ mutex_init(&smd_pkt_devp[i]->rx_lock);
+ mutex_init(&smd_pkt_devp[i]->tx_lock);
+ init_completion(&smd_pkt_devp[i]->ch_allocated);
+
+ cdev_init(&smd_pkt_devp[i]->cdev, &smd_pkt_fops);
+ smd_pkt_devp[i]->cdev.owner = THIS_MODULE;
+
+ r = cdev_add(&smd_pkt_devp[i]->cdev,
+ (smd_pkt_number + i),
+ 1);
+
+ if (IS_ERR_VALUE(r)) {
+ printk(KERN_ERR "%s:%i:%s: cdev_add() ret %i\n",
+ __FILE__,
+ __LINE__,
+ __func__,
+ r);
+ kfree(smd_pkt_devp[i]);
+ goto error2;
+ }
+
+ smd_pkt_devp[i]->devicep =
+ device_create(smd_pkt_classp,
+ NULL,
+ (smd_pkt_number + i),
+ NULL,
+ smd_pkt_dev_name[i]);
+
+ if (IS_ERR(smd_pkt_devp[i]->devicep)) {
+ printk(KERN_ERR "%s:%i:%s: "
+ "device_create() ENOMEM\n",
+ __FILE__,
+ __LINE__,
+ __func__);
+ r = -ENOMEM;
+ cdev_del(&smd_pkt_devp[i]->cdev);
+ kfree(smd_pkt_devp[i]);
+ goto error2;
+ }
+ if (device_create_file(smd_pkt_devp[i]->devicep,
+ &dev_attr_open_timeout))
+ pr_err("%s: unable to create device attr on #%d\n",
+ __func__, i);
+
+ smd_pkt_devp[i]->driver.probe = smd_pkt_dummy_probe;
+ smd_pkt_devp[i]->driver.driver.name = smd_ch_name[i];
+ smd_pkt_devp[i]->driver.driver.owner = THIS_MODULE;
+ r = platform_driver_register(&smd_pkt_devp[i]->driver);
+ if (r)
+ goto error2;
+ }
+
+ INIT_DELAYED_WORK(&loopback_work, loopback_probe_worker);
+
+ D(KERN_INFO "SMD Packet Port Driver Initialized.\n");
+ return 0;
+
+ error2:
+ if (i > 0) {
+ while (--i >= 0) {
+ platform_driver_unregister(&smd_pkt_devp[i]->driver);
+ cdev_del(&smd_pkt_devp[i]->cdev);
+ kfree(smd_pkt_devp[i]);
+ device_destroy(smd_pkt_classp,
+ MKDEV(MAJOR(smd_pkt_number), i));
+ }
+ }
+
+ class_destroy(smd_pkt_classp);
+ error1:
+ unregister_chrdev_region(MAJOR(smd_pkt_number), NUM_SMD_PKT_PORTS);
+ error0:
+ return r;
+}
+
+static void __exit smd_pkt_cleanup(void)
+{
+ int i;
+
+ for (i = 0; i < NUM_SMD_PKT_PORTS; ++i) {
+ platform_driver_unregister(&smd_pkt_devp[i]->driver);
+ cdev_del(&smd_pkt_devp[i]->cdev);
+ kfree(smd_pkt_devp[i]);
+ device_destroy(smd_pkt_classp,
+ MKDEV(MAJOR(smd_pkt_number), i));
+ }
+
+ class_destroy(smd_pkt_classp);
+
+ unregister_chrdev_region(MAJOR(smd_pkt_number), NUM_SMD_PKT_PORTS);
+}
+
+module_init(smd_pkt_init);
+module_exit(smd_pkt_cleanup);
+
+MODULE_DESCRIPTION("MSM Shared Memory Packet Port");
+MODULE_LICENSE("GPL v2");