aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--drivers/media/Kconfig14
-rw-r--r--drivers/media/Makefile1
-rw-r--r--drivers/media/mdtv/Kconfig36
-rw-r--r--drivers/media/mdtv/Makefile8
-rw-r--r--drivers/media/mdtv/smschar.c575
-rw-r--r--drivers/media/mdtv/smschar.h7
-rw-r--r--drivers/media/mdtv/smscharioctl.h17
-rw-r--r--drivers/media/mdtv/smscoreapi.c1170
-rw-r--r--drivers/media/mdtv/smscoreapi.h101
-rw-r--r--drivers/media/mdtv/smsdvb.c438
-rw-r--r--drivers/media/mdtv/smskdefs.h21
-rw-r--r--drivers/media/mdtv/smsnet.c447
-rw-r--r--drivers/media/mdtv/smstypes.h361
-rw-r--r--drivers/media/mdtv/smsusb.c428
14 files changed, 3624 insertions, 0 deletions
diff --git a/drivers/media/Kconfig b/drivers/media/Kconfig
index 7a7803b5d49..e6a5879ea5f 100644
--- a/drivers/media/Kconfig
+++ b/drivers/media/Kconfig
@@ -115,6 +115,20 @@ source "drivers/media/radio/Kconfig"
source "drivers/media/dvb/Kconfig"
+#
+# Mobile Digital TV devices (DVB-H, T-DMB, etc.)
+#
+menuconfig MDTV_ADAPTERS
+ bool "Mobile Digital TV adapter"
+ default y
+
+if MDTV_ADAPTERS
+
+source "drivers/media/mdtv/Kconfig"
+
+endif # MDTV_ADAPTERS
+
+
config DAB
boolean "DAB adapters"
---help---
diff --git a/drivers/media/Makefile b/drivers/media/Makefile
index 09a829d8a7e..ec2102bcb28 100644
--- a/drivers/media/Makefile
+++ b/drivers/media/Makefile
@@ -6,3 +6,4 @@ obj-y += common/ video/
obj-$(CONFIG_VIDEO_DEV) += radio/
obj-$(CONFIG_DVB_CORE) += dvb/
+obj-$(CONFIG_MDTV_ADAPTERS) += mdtv/ \ No newline at end of file
diff --git a/drivers/media/mdtv/Kconfig b/drivers/media/mdtv/Kconfig
new file mode 100644
index 00000000000..f3bae45000d
--- /dev/null
+++ b/drivers/media/mdtv/Kconfig
@@ -0,0 +1,36 @@
+#
+# Mobile Digital TV device configuration
+#
+
+config MDTV_SIANO_STELLAR_COMMON
+ tristate "Siano SMS10xx adapter"
+ default m
+ ---help---
+ Choose Y here if you have SMS10xx chipset.
+
+ In order to control the SMS10xx chipset you will need SMS Host Control library.
+
+ Further documentation on this driver can be found on the WWW at
+ <http://www.siano-ms.com/>.
+
+ To compile this driver as a module, choose M here: the
+ modules will be called smschar and smsnet.
+
+config MDTV_SIANO_STELLAR_USB
+ tristate "Siano SMS10xx USB dongle support"
+ depends on MDTV_SIANO_STELLAR_COMMON
+ default m
+ ---help---
+ Choose Y here if you have USB dongle with SMS10xx chipset.
+
+ In order to control the SMS10xx chipset you will need SMS Host Control library.
+
+ Further documentation on this driver can be found on the WWW at
+ <http://www.siano-ms.com/>.
+
+ To compile this driver as a module, choose M here: the
+ module will be called smsusb.
+
+
+
+
diff --git a/drivers/media/mdtv/Makefile b/drivers/media/mdtv/Makefile
new file mode 100644
index 00000000000..1e54d8f796d
--- /dev/null
+++ b/drivers/media/mdtv/Makefile
@@ -0,0 +1,8 @@
+#
+# Makefile for the kernel MDTV driver
+#
+
+obj-$(CONFIG_MDTV_SIANO_STELLAR_COMMON) += smschar.o smsnet.o
+obj-$(CONFIG_MDTV_SIANO_STELLAR_USB) += smsusb.o
+
+EXTRA_CFLAGS +=
diff --git a/drivers/media/mdtv/smschar.c b/drivers/media/mdtv/smschar.c
new file mode 100644
index 00000000000..0477ad0ed6d
--- /dev/null
+++ b/drivers/media/mdtv/smschar.c
@@ -0,0 +1,575 @@
+/*!
+
+ \file smschar.c
+
+ \brief Implementation of smscore client for cdev based access
+
+ \par Copyright (c), 2005-2008 Siano Mobile Silicon, Inc.
+
+ \par This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License version 3 as
+ published by the Free Software Foundation;
+
+ Software distributed under the License is distributed on an "AS
+ IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ implied.
+
+ \author Anatoly Greenblat
+
+*/
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+
+#include <linux/kernel.h> /* printk() */
+#include <linux/fs.h> /* everything... */
+#include <linux/types.h> /* size_t */
+#include <linux/cdev.h>
+#include <linux/sched.h>
+#include <asm/system.h> /* cli(), *_flags */
+#include <asm/uaccess.h> /* copy_*_user */
+
+#include "smskdefs.h" // page, scatterlist, kmutex
+#include "smscoreapi.h"
+#include "smstypes.h"
+
+#include "smscharioctl.h"
+
+#define SMS_CHR_MAX_Q_LEN 10 // max number of packets allowed to be pending on queue
+#define SMSCHAR_NR_DEVS 7
+
+typedef struct _smschar_device
+{
+ struct cdev cdev; //!< Char device structure - kernel's device model representation
+
+ wait_queue_head_t waitq; /* Processes waiting */
+ spinlock_t lock; //!< critical section
+ int pending_count;
+ struct list_head pending_data; //!< list of pending data
+
+ smscore_buffer_t *currentcb;
+
+ int device_index;
+
+ smscore_device_t *coredev;
+ smscore_client_t *smsclient;
+} smschar_device_t;
+
+//! Holds the major number of the device node. may be changed at load time.
+int smschar_major = 251;
+
+//! Holds the first minor number of the device node. may be changed at load time.
+int smschar_minor = 0;
+
+// macros that allow the load time parameters change
+module_param ( smschar_major, int, S_IRUGO );
+module_param ( smschar_minor, int, S_IRUGO );
+
+#ifdef SMSCHAR_DEBUG
+
+ #undef PERROR
+# define PERROR(fmt, args...) printk( KERN_INFO "smschar error: line %d- %s(): " fmt,__LINE__, __FUNCTION__, ## args)
+ #undef PWARNING
+# define PWARNING(fmt, args...) printk( KERN_INFO "smschar warning: line %d- %s(): " fmt,__LINE__, __FUNCTION__, ## args)
+ #undef PDEBUG /* undef it, just in case */
+# define PDEBUG(fmt, args...) printk( KERN_INFO "smschar - %s(): " fmt, __FUNCTION__, ## args)
+
+#else /* not debugging: nothing */
+
+ #define PDEBUG(fmt, args...)
+ #define PERROR(fmt, args...)
+ #define PWARNING(fmt, args...)
+
+#endif
+
+smschar_device_t smschar_devices[SMSCHAR_NR_DEVS];
+static int g_smschar_inuse = 0;
+
+/**
+ * unregisters sms client and returns all queued buffers
+ *
+ * @param dev pointer to the client context (smschar parameters block)
+ *
+ */
+void smschar_unregister_client(smschar_device_t* dev)
+{
+ unsigned long flags;
+
+ if (dev->coredev && dev->smsclient)
+ {
+ wake_up_interruptible(&dev->waitq);
+
+ spin_lock_irqsave(&dev->lock, flags);
+
+ while (!list_empty(&dev->pending_data))
+ {
+ smscore_buffer_t *cb = (smscore_buffer_t *) dev->pending_data.next;
+ list_del(&cb->entry);
+
+ smscore_putbuffer(dev->coredev, cb);
+
+ dev->pending_count --;
+ }
+
+ if (dev->currentcb)
+ {
+ smscore_putbuffer(dev->coredev, dev->currentcb);
+ dev->currentcb = NULL;
+ dev->pending_count --;
+ }
+
+ smscore_unregister_client(dev->smsclient);
+ dev->smsclient = NULL;
+
+ spin_unlock_irqrestore(&dev->lock, flags);
+ }
+}
+
+/**
+ * queues incoming buffers into buffers queue
+ *
+ * @param context pointer to the client context (smschar parameters block)
+ * @param cb pointer to incoming buffer descriptor
+ *
+ * @return 0 on success, <0 on queue overflow.
+ */
+int smschar_onresponse(void *context, smscore_buffer_t *cb)
+{
+ smschar_device_t *dev = context;
+ unsigned long flags;
+
+ spin_lock_irqsave(&dev->lock, flags);
+
+ if (dev->pending_count > SMS_CHR_MAX_Q_LEN)
+ {
+ spin_unlock_irqrestore(&dev->lock, flags);
+ return -EBUSY;
+ }
+
+ dev->pending_count ++;
+
+ // if data channel, remove header
+ if (dev->device_index)
+ {
+ cb->size -= sizeof(SmsMsgHdr_ST);
+ cb->offset += sizeof(SmsMsgHdr_ST);
+ }
+
+ list_add_tail(&cb->entry, &dev->pending_data);
+ spin_unlock_irqrestore(&dev->lock, flags);
+
+ if (waitqueue_active(&dev->waitq))
+ wake_up_interruptible(&dev->waitq);
+
+ return 0;
+}
+
+/**
+ * handles device removal event
+ *
+ * @param context pointer to the client context (smschar parameters block)
+ *
+ */
+void smschar_onremove(void *context)
+{
+ smschar_device_t *dev = (smschar_device_t *) context;
+
+ smschar_unregister_client(dev);
+ dev->coredev = NULL;
+}
+
+/**
+ * registers client associated with the node
+ *
+ * @param inode Inode concerned.
+ * @param file File concerned.
+ *
+ * @return 0 on success, <0 on error.
+ */
+int smschar_open (struct inode *inode, struct file *file)
+{
+ smschar_device_t *dev = container_of(inode->i_cdev, smschar_device_t, cdev);
+ int rc = -ENODEV;
+
+ PDEBUG("entering index %d\n", dev->device_index);
+
+ if (dev->coredev)
+ {
+ smsclient_params_t params;
+
+ params.initial_id = dev->device_index ? dev->device_index : SMS_HOST_LIB;
+ params.data_type = dev->device_index ? MSG_SMS_DAB_CHANNEL : 0;
+ params.onresponse_handler = smschar_onresponse;
+ params.onremove_handler = smschar_onremove;
+ params.context = dev;
+
+ rc = smscore_register_client(dev->coredev, &params, &dev->smsclient);
+ if (!rc)
+ {
+ file->private_data = dev;
+ }
+ }
+
+ PDEBUG("exiting, rc %d\n", rc);
+
+ return rc;
+}
+
+/**
+ * unregisters client associated with the node
+ *
+ * @param inode Inode concerned.
+ * @param file File concerned.
+ *
+ */
+int smschar_release(struct inode *inode, struct file *file)
+{
+ smschar_unregister_client(file->private_data);
+
+ PDEBUG("exiting\n");
+
+ return 0;
+}
+
+/**
+ * copies data from buffers in incoming queue into a user buffer
+ *
+ * @param file File structure.
+ * @param buf Source buffer.
+ * @param count Size of source buffer.
+ * @param f_pos Position in file (ignored).
+ *
+ * @return Number of bytes read, or <0 on error.
+ */
+ssize_t smschar_read ( struct file * file, char __user * buf, size_t count, loff_t * f_pos )
+{
+ smschar_device_t *dev = file->private_data;
+ unsigned long flags;
+ int copied = 0;
+
+ if (!dev->coredev || !dev->smsclient)
+ {
+ PERROR("no client\n");
+ return -ENODEV;
+ }
+
+ while (copied != count)
+ {
+ if (0 > wait_event_interruptible(dev->waitq, !list_empty(&dev->pending_data)))
+ {
+ PERROR("wait_event_interruptible error\n");
+ return -ENODEV;
+ }
+
+ if (!dev->smsclient)
+ {
+ PERROR("no client\n");
+ return -ENODEV;
+ }
+
+ spin_lock_irqsave(&dev->lock, flags);
+
+ while (!list_empty(&dev->pending_data) && (copied != count))
+ {
+ smscore_buffer_t *cb = (smscore_buffer_t *) dev->pending_data.next;
+ int actual_size = min(((int) count - copied), cb->size);
+
+ copy_to_user(&buf[copied], &((char*)cb->p)[cb->offset], actual_size);
+
+ copied += actual_size;
+ cb->offset += actual_size;
+ cb->size -= actual_size;
+
+ if (!cb->size)
+ {
+ list_del(&cb->entry);
+ smscore_putbuffer(dev->coredev, cb);
+
+ dev->pending_count --;
+ }
+ }
+
+ spin_unlock_irqrestore(&dev->lock, flags);
+ }
+
+ return copied;
+}
+
+/**
+ * sends the buffer to the associated device
+ *
+ * @param file File structure.
+ * @param buf Source buffer.
+ * @param count Size of source buffer.
+ * @param f_pos Position in file (ignored).
+ *
+ * @return Number of bytes read, or <0 on error.
+ */
+ssize_t smschar_write(struct file *file, const char __user *buf, size_t count, loff_t *f_pos)
+{
+ smschar_device_t *dev = file->private_data;
+ void *buffer;
+
+ if (!dev->smsclient)
+ {
+ PERROR("no client\n");
+ return -ENODEV;
+ }
+
+ buffer = kmalloc(ALIGN(count, SMS_ALLOC_ALIGNMENT) + SMS_DMA_ALIGNMENT, GFP_KERNEL | GFP_DMA);
+ if (buffer)
+ {
+ void *msg_buffer = (void*) SMS_ALIGN_ADDRESS(buffer);
+
+ if (!copy_from_user(msg_buffer, buf, count))
+ smsclient_sendrequest(dev->smsclient, msg_buffer, count);
+ else
+ count = 0;
+
+ kfree(buffer);
+ }
+
+ return count;
+}
+
+int smschar_mmap(struct file *file, struct vm_area_struct *vma)
+{
+ smschar_device_t *dev = file->private_data;
+ return smscore_map_common_buffer(dev->coredev, vma);
+}
+
+/**
+ * waits until buffer inserted into a queue. when inserted buffer offset are reported
+ * to the calling process. previously reported buffer is returned to smscore pool
+ *
+ * @param dev pointer to smschar parameters block
+ * @param touser pointer to a structure that receives incoming buffer offsets
+ *
+ * @return 0 on success, <0 on error.
+ */
+int smschar_wait_get_buffer(smschar_device_t* dev, smschar_buffer_t* touser)
+{
+ unsigned long flags;
+ int rc;
+
+ spin_lock_irqsave(&dev->lock, flags);
+
+ if (dev->currentcb)
+ {
+ smscore_putbuffer(dev->coredev, dev->currentcb);
+ dev->currentcb = NULL;
+ dev->pending_count --;
+ }
+
+ spin_unlock_irqrestore(&dev->lock, flags);
+
+ rc = wait_event_interruptible(dev->waitq, !list_empty(&dev->pending_data));
+ if (rc < 0)
+ {
+ PERROR("wait_event_interruptible error\n");
+ return rc;
+ }
+
+ if (!dev->smsclient)
+ {
+ PERROR("no client\n");
+ return -ENODEV;
+ }
+
+ spin_lock_irqsave(&dev->lock, flags);
+
+ if (!list_empty(&dev->pending_data))
+ {
+ smscore_buffer_t *cb = (smscore_buffer_t *) dev->pending_data.next;
+
+ touser->offset = cb->offset_in_common + cb->offset;
+ touser->size = cb->size;
+
+ list_del(&cb->entry);
+
+ dev->currentcb = cb;
+ }
+ else
+ {
+ touser->offset = 0;
+ touser->size = 0;
+ }
+
+ spin_unlock_irqrestore(&dev->lock, flags);
+
+ return 0;
+}
+
+int smschar_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
+{
+ smschar_device_t *dev = file->private_data;
+ void __user *up = (void __user *) arg;
+
+ if (!dev->coredev || !dev->smsclient)
+ {
+ PERROR("no client\n");
+ return -ENODEV;
+ }
+
+ switch(cmd)
+ {
+ case SMSCHAR_SET_DEVICE_MODE:
+ return smscore_set_device_mode(dev->coredev, (int) arg);
+
+ case SMSCHAR_GET_DEVICE_MODE:
+ {
+ if (put_user(smscore_get_device_mode(dev->coredev), (int*) up))
+ return -EFAULT;
+
+ break;
+ }
+
+ case SMSCHAR_GET_BUFFER_SIZE:
+ {
+ if (put_user(smscore_get_common_buffer_size(dev->coredev), (int*) up))
+ return -EFAULT;
+
+ break;
+ }
+
+ case SMSCHAR_WAIT_GET_BUFFER:
+ {
+ smschar_buffer_t touser;
+ int rc;
+
+ rc = smschar_wait_get_buffer(dev, &touser);
+ if (rc < 0)
+ return rc;
+
+ if (copy_to_user(up, &touser, sizeof(smschar_buffer_t)))
+ return -EFAULT;
+
+ break;
+ }
+
+ default:
+ return -ENOIOCTLCMD;
+ }
+
+ return 0;
+}
+
+struct file_operations smschar_fops =
+{
+ .owner = THIS_MODULE,
+ .read = smschar_read,
+ .write = smschar_write,
+ .open = smschar_open,
+ .release = smschar_release,
+ .mmap = smschar_mmap,
+ .ioctl = smschar_ioctl,
+};
+
+static int smschar_setup_cdev ( smschar_device_t *dev, int index )
+{
+ int rc, devno = MKDEV ( smschar_major, smschar_minor + index );
+
+ cdev_init ( &dev->cdev, &smschar_fops );
+
+ dev->cdev.owner = THIS_MODULE;
+ dev->cdev.ops = &smschar_fops;
+
+ kobject_set_name(&dev->cdev.kobj, "Siano_sms%d", index);
+
+ rc = cdev_add ( &dev->cdev, devno, 1 );
+
+ PDEBUG("exiting %p %d, rc %d\n", dev, index, rc);
+
+ return rc;
+}
+
+/**
+ * smschar callback that called when device plugged in/out. the function
+ * register or unregisters char device interface according to plug in/out
+ *
+ * @param coredev pointer to device that is being plugged in/out
+ * @param device pointer to system device object
+ * @param arrival 1 on plug-on, 0 othewise
+ *
+ * @return 0 on success, <0 on error.
+ */
+int smschar_hotplug(smscore_device_t* coredev, struct device* device, int arrival)
+{
+ int rc = 0, i;
+
+ PDEBUG("entering %d\n", arrival);
+
+ if (arrival)
+ {
+ // currently only 1 instance supported
+ if (!g_smschar_inuse)
+ {
+ /* data notification callbacks assignment */
+ memset ( smschar_devices, 0, SMSCHAR_NR_DEVS * sizeof ( smschar_device_t ) );
+
+ /* Initialize each device. */
+ for (i = 0; i < SMSCHAR_NR_DEVS; i++)
+ {
+ smschar_setup_cdev ( &smschar_devices[i], i );
+
+ INIT_LIST_HEAD(&smschar_devices[i].pending_data);
+ spin_lock_init(&smschar_devices[i].lock);
+ init_waitqueue_head(&smschar_devices[i].waitq);
+
+ smschar_devices[i].coredev = coredev;
+ smschar_devices[i].device_index = i;
+ }
+
+ g_smschar_inuse = 1;
+ }
+ }
+ else
+ {
+ // currently only 1 instance supported
+ if (g_smschar_inuse)
+ {
+ /* Get rid of our char dev entries */
+ for(i = 0; i < SMSCHAR_NR_DEVS; i++)
+ cdev_del(&smschar_devices[i].cdev);
+
+ g_smschar_inuse = 0;
+ }
+ }
+
+ PDEBUG("exiting, rc %d\n", rc);
+
+ return rc; /* succeed */
+}
+
+int smschar_initialize(void)
+{
+ dev_t devno = MKDEV ( smschar_major, smschar_minor );
+ int rc;
+
+ if(smschar_major)
+ {
+ rc = register_chrdev_region ( devno, SMSCHAR_NR_DEVS, "smschar" );
+ }
+ else
+ {
+ rc = alloc_chrdev_region ( &devno, smschar_minor, SMSCHAR_NR_DEVS, "smschar" );
+ smschar_major = MAJOR ( devno );
+ }
+
+ if (rc < 0)
+ {
+ PWARNING ( "smschar: can't get major %d\n", smschar_major );
+ return rc;
+ }
+
+ return smscore_register_hotplug(smschar_hotplug);
+}
+
+void smschar_terminate(void)
+{
+ dev_t devno = MKDEV ( smschar_major, smschar_minor );
+
+ unregister_chrdev_region(devno, SMSCHAR_NR_DEVS);
+ smscore_unregister_hotplug(smschar_hotplug);
+}
diff --git a/drivers/media/mdtv/smschar.h b/drivers/media/mdtv/smschar.h
new file mode 100644
index 00000000000..1cd2f32a7f7
--- /dev/null
+++ b/drivers/media/mdtv/smschar.h
@@ -0,0 +1,7 @@
+#ifndef __smschar_h__
+#define __smschar_h__
+
+extern int smschar_initialize(void);
+extern void smschar_terminate(void);
+
+#endif // __smschar_h__
diff --git a/drivers/media/mdtv/smscharioctl.h b/drivers/media/mdtv/smscharioctl.h
new file mode 100644
index 00000000000..e57b89efc49
--- /dev/null
+++ b/drivers/media/mdtv/smscharioctl.h
@@ -0,0 +1,17 @@
+#ifndef __smscharioctl_h__
+#define __smscharioctl_h__
+
+#include <linux/ioctl.h>
+
+typedef struct _smschar_buffer_t
+{
+ unsigned long offset; // offset in common buffer (mapped to user space)
+ int size;
+} smschar_buffer_t;
+
+#define SMSCHAR_SET_DEVICE_MODE _IOW('K', 0, int)
+#define SMSCHAR_GET_DEVICE_MODE _IOR('K', 1, int)
+#define SMSCHAR_GET_BUFFER_SIZE _IOR('K', 2, int)
+#define SMSCHAR_WAIT_GET_BUFFER _IOR('K', 3, smschar_buffer_t)
+
+#endif // __smscharioctl_h__
diff --git a/drivers/media/mdtv/smscoreapi.c b/drivers/media/mdtv/smscoreapi.c
new file mode 100644
index 00000000000..a354912391a
--- /dev/null
+++ b/drivers/media/mdtv/smscoreapi.c
@@ -0,0 +1,1170 @@
+/*!
+
+ \file smscoreapi.c
+
+ \brief Siano core API module
+ This file contains implementation for the interface to sms core component
+
+ \par Copyright (c), 2005-2008 Siano Mobile Silicon, Inc.
+
+ \par This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License version 3 as
+ published by the Free Software Foundation;
+
+ Software distributed under the License is distributed on an "AS
+ IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ implied.
+
+ \author Anatoly Greenblat
+
+*/
+
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/dma-mapping.h>
+#include <linux/delay.h>
+#include <asm/io.h>
+
+#include "smskdefs.h" // device, page, scatterlist, kmutex
+
+#include <linux/firmware.h>
+
+#include "smscoreapi.h"
+#include "smstypes.h"
+
+#include "smschar.h"
+
+typedef struct _smscore_device_notifyee
+{
+ struct list_head entry;
+ hotplug_t hotplug;
+} smscore_device_notifyee_t;
+
+typedef struct _smscore_client
+{
+ struct list_head entry;
+ smscore_device_t *coredev;
+
+ void *context;
+
+ int data_type;
+
+ onresponse_t onresponse_handler;
+ onremove_t onremove_handler;
+} *psmscore_client_t;
+
+typedef struct _smscore_subclient
+{
+ struct list_head entry;
+ smscore_client_t *client;
+
+ int id;
+} smscore_subclient_t;
+
+typedef struct _smscore_device
+{
+ struct list_head entry;
+
+ struct list_head clients;
+ struct list_head subclients;
+ spinlock_t clientslock;
+
+ struct list_head buffers;
+ spinlock_t bufferslock;
+ int num_buffers;
+
+ void *common_buffer;
+ int common_buffer_size;
+ dma_addr_t common_buffer_phys;
+
+ void *context;
+ struct device *device;
+
+ char devpath[32];
+ unsigned long device_flags;
+
+ setmode_t setmode_handler;
+ detectmode_t detectmode_handler;
+ sendrequest_t sendrequest_handler;
+ preload_t preload_handler;
+ postload_t postload_handler;
+
+ int mode, modes_supported;
+
+ struct completion version_ex_done, data_download_done, trigger_done;
+ struct completion init_device_done, reload_start_done, resume_done;
+} *psmscore_device_t;
+
+typedef struct _smscore_registry_entry
+{
+ struct list_head entry;
+ char devpath[32];
+ int mode;
+} smscore_registry_entry_t;
+
+struct list_head g_smscore_notifyees;
+struct list_head g_smscore_devices;
+kmutex_t g_smscore_deviceslock;
+
+struct list_head g_smscore_registry;
+kmutex_t g_smscore_registrylock;
+
+static int default_mode = 1;
+module_param(default_mode, int, 0644);
+MODULE_PARM_DESC(default_mode, "default firmware id (device mode)");
+
+int smscore_registry_getmode(char* devpath)
+{
+ smscore_registry_entry_t *entry;
+ struct list_head *next;
+
+ kmutex_lock(&g_smscore_registrylock);
+
+ for (next = g_smscore_registry.next; next != &g_smscore_registry; next = next->next)
+ {
+ entry = (smscore_registry_entry_t *) next;
+
+ if (!strcmp(entry->devpath, devpath))
+ {
+ kmutex_unlock(&g_smscore_registrylock);
+ return entry->mode;
+ }
+ }
+
+ entry = (smscore_registry_entry_t *) kmalloc(sizeof(smscore_registry_entry_t), GFP_KERNEL);
+ if (entry)
+ {
+ entry->mode = default_mode;
+ strcpy(entry->devpath, devpath);
+
+ list_add(&entry->entry, &g_smscore_registry);
+ }
+
+ kmutex_unlock(&g_smscore_registrylock);
+
+ return default_mode;
+}
+
+void smscore_registry_setmode(char* devpath, int mode)
+{
+ smscore_registry_entry_t *entry;
+ struct list_head *next;
+
+ kmutex_lock(&g_smscore_registrylock);
+
+ for (next = g_smscore_registry.next; next != &g_smscore_registry; next = next->next)
+ {
+ entry = (smscore_registry_entry_t *) next;
+
+ if (!strcmp(entry->devpath, devpath))
+ {
+ entry->mode = mode;
+ break;
+ }
+ }
+
+ kmutex_unlock(&g_smscore_registrylock);
+}
+
+
+void list_add_locked(struct list_head *new, struct list_head *head, spinlock_t* lock)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(lock, flags);
+
+ list_add(new, head);
+
+ spin_unlock_irqrestore(lock, flags);
+}
+
+/**
+ * register a client callback that called when device plugged in/unplugged
+ * NOTE: if devices exist callback is called immediately for each device
+ *
+ * @param hotplug callback
+ *
+ * @return 0 on success, <0 on error.
+ */
+int smscore_register_hotplug(hotplug_t hotplug)
+{
+ smscore_device_notifyee_t *notifyee;
+ struct list_head *next, *first;
+ int rc = 0;
+
+ kmutex_lock(&g_smscore_deviceslock);
+
+ notifyee = kmalloc(sizeof(smscore_device_notifyee_t), GFP_KERNEL);
+ if (notifyee)
+ {
+ // now notify callback about existing devices
+ first = &g_smscore_devices;
+ for (next = first->next; next != first && !rc; next = next->next)
+ {
+ smscore_device_t *coredev = (smscore_device_t *) next;
+ rc = hotplug(coredev, coredev->device, 1);
+ }
+
+ if (rc >= 0)
+ {
+ notifyee->hotplug = hotplug;
+ list_add(&notifyee->entry, &g_smscore_notifyees);
+ }
+ else
+ kfree(notifyee);
+ }
+ else
+ rc = -ENOMEM;
+
+ kmutex_unlock(&g_smscore_deviceslock);
+
+ return rc;
+}
+
+/**
+ * unregister a client callback that called when device plugged in/unplugged
+ *
+ * @param hotplug callback
+ *
+ */
+void smscore_unregister_hotplug(hotplug_t hotplug)
+{
+ struct list_head *next, *first;
+
+ kmutex_lock(&g_smscore_deviceslock);
+
+ first = &g_smscore_notifyees;
+
+ for (next = first->next; next != first;)
+ {
+ smscore_device_notifyee_t *notifyee = (smscore_device_notifyee_t *) next;
+ next = next->next;
+
+ if (notifyee->hotplug == hotplug)
+ {
+ list_del(&notifyee->entry);
+ kfree(notifyee);
+ }
+ }
+
+ kmutex_unlock(&g_smscore_deviceslock);
+}
+
+void smscore_notify_clients(smscore_device_t *coredev)
+{
+ smscore_client_t* client;
+
+ // the client must call smscore_unregister_client from remove handler
+ while (!list_empty(&coredev->clients))
+ {
+ client = (smscore_client_t *) coredev->clients.next;
+ client->onremove_handler(client->context);
+ }
+}
+
+int smscore_notify_callbacks(smscore_device_t *coredev, struct device *device, int arrival)
+{
+ struct list_head *next, *first;
+ int rc = 0;
+
+ // note: must be called under g_deviceslock
+
+ first = &g_smscore_notifyees;
+
+ for (next = first->next; next != first; next = next->next)
+ {
+ rc = ((smscore_device_notifyee_t *) next)->hotplug(coredev, device, arrival);
+ if (rc < 0)
+ break;
+ }
+
+ return rc;
+}
+
+smscore_buffer_t *smscore_createbuffer(u8* buffer, void* common_buffer, dma_addr_t common_buffer_phys)
+{
+ smscore_buffer_t *cb = kmalloc(sizeof(smscore_buffer_t), GFP_KERNEL);
+ if (!cb)
+ {
+ printk(KERN_INFO "%s kmalloc(...) failed\n", __FUNCTION__);
+ return NULL;
+ }
+
+ cb->p = buffer;
+ cb->offset_in_common = buffer - (u8*) common_buffer;
+ cb->phys = common_buffer_phys + cb->offset_in_common;
+
+ return cb;
+}
+
+/**
+ * creates coredev object for a device, prepares buffers, creates buffer mappings, notifies
+ * registered hotplugs about new device.
+ *
+ * @param params device pointer to struct with device specific parameters and handlers
+ * @param coredev pointer to a value that receives created coredev object
+ *
+ * @return 0 on success, <0 on error.
+ */
+int smscore_register_device(smsdevice_params_t *params, smscore_device_t **coredev)
+{
+ smscore_device_t* dev;
+ u8 *buffer;
+
+ dev = kzalloc(sizeof(smscore_device_t), GFP_KERNEL);
+ if (!dev)
+ {
+ printk(KERN_INFO "%s kzalloc(...) failed\n", __FUNCTION__);
+ return -ENOMEM;
+ }
+
+ // init list entry so it could be safe in smscore_unregister_device
+ INIT_LIST_HEAD(&dev->entry);
+
+ // init queues
+ INIT_LIST_HEAD(&dev->clients);
+ INIT_LIST_HEAD(&dev->subclients);
+ INIT_LIST_HEAD(&dev->buffers);
+
+ // init locks
+ spin_lock_init(&dev->clientslock);
+ spin_lock_init(&dev->bufferslock);
+
+ // init completion events
+ init_completion(&dev->version_ex_done);
+ init_completion(&dev->data_download_done);
+ init_completion(&dev->trigger_done);
+ init_completion(&dev->init_device_done);
+ init_completion(&dev->reload_start_done);
+ init_completion(&dev->resume_done);
+
+ // alloc common buffer
+ dev->common_buffer_size = params->buffer_size * params->num_buffers;
+ dev->common_buffer = dma_alloc_coherent(NULL, dev->common_buffer_size, &dev->common_buffer_phys, GFP_KERNEL | GFP_DMA);
+ if (!dev->common_buffer)
+ {
+ smscore_unregister_device(dev);
+ return -ENOMEM;
+ }
+
+ // prepare dma buffers
+ for (buffer = dev->common_buffer; dev->num_buffers < params->num_buffers; dev->num_buffers ++, buffer += params->buffer_size)
+ {
+ smscore_buffer_t *cb = smscore_createbuffer(buffer, dev->common_buffer, dev->common_buffer_phys);
+ if (!cb)
+ {
+ smscore_unregister_device(dev);
+ return -ENOMEM;
+ }
+
+ smscore_putbuffer(dev, cb);
+ }
+
+ printk(KERN_INFO "%s allocated %d buffers\n", __FUNCTION__, dev->num_buffers);
+
+ dev->mode = DEVICE_MODE_NONE;
+ dev->context = params->context;
+ dev->device = params->device;
+ dev->setmode_handler = params->setmode_handler;
+ dev->detectmode_handler = params->detectmode_handler;
+ dev->sendrequest_handler = params->sendrequest_handler;
+ dev->preload_handler = params->preload_handler;
+ dev->postload_handler = params->postload_handler;
+
+ dev->device_flags = params->flags;
+ strcpy(dev->devpath, params->devpath);
+
+ // add device to devices list
+ kmutex_lock(&g_smscore_deviceslock);
+ list_add(&dev->entry, &g_smscore_devices);
+ kmutex_unlock(&g_smscore_deviceslock);
+
+ *coredev = dev;
+
+ printk(KERN_INFO "%s device %p created\n", __FUNCTION__, dev);
+
+ return 0;
+}
+
+/**
+ * sets initial device mode and notifies client hotplugs that device is ready
+ *
+ * @param coredev pointer to a coredev object returned by smscore_register_device
+ *
+ * @return 0 on success, <0 on error.
+ */
+int smscore_start_device(smscore_device_t *coredev)
+{
+ int rc = smscore_set_device_mode(coredev, smscore_registry_getmode(coredev->devpath));
+ if (rc < 0)
+ return rc;
+
+ kmutex_lock(&g_smscore_deviceslock);
+
+ rc = smscore_notify_callbacks(coredev, coredev->device, 1);
+
+ printk(KERN_INFO "%s device %p started, rc %d\n", __FUNCTION__, coredev, rc);
+
+ kmutex_unlock(&g_smscore_deviceslock);
+
+ return rc;
+}
+
+int smscore_sendrequest_and_wait(smscore_device_t *coredev, void* buffer, size_t size, struct completion *completion)
+{
+ int rc = coredev->sendrequest_handler(coredev->context, buffer, size);
+ if (rc < 0)
+ return rc;
+
+ return wait_for_completion_timeout(completion, msecs_to_jiffies(1000)) ? 0 : -ETIME;
+}
+
+int smscore_load_firmware_family2(smscore_device_t *coredev, void *buffer, size_t size)
+{
+ SmsFirmware_ST* firmware = (SmsFirmware_ST*) buffer;
+ SmsMsgHdr_ST *msg;
+ UINT32 mem_address = firmware->StartAddress;
+ u8* payload = firmware->Payload;
+ int rc = 0;
+
+ if (coredev->preload_handler)
+ {
+ rc = coredev->preload_handler(coredev->context);
+ if (rc < 0)
+ return rc;
+ }
+
+ // PAGE_SIZE buffer shall be enough and dma aligned
+ msg = (SmsMsgHdr_ST *) kmalloc(PAGE