diff options
-rw-r--r-- | drivers/media/Kconfig | 14 | ||||
-rw-r--r-- | drivers/media/Makefile | 1 | ||||
-rw-r--r-- | drivers/media/mdtv/Kconfig | 36 | ||||
-rw-r--r-- | drivers/media/mdtv/Makefile | 8 | ||||
-rw-r--r-- | drivers/media/mdtv/smschar.c | 575 | ||||
-rw-r--r-- | drivers/media/mdtv/smschar.h | 7 | ||||
-rw-r--r-- | drivers/media/mdtv/smscharioctl.h | 17 | ||||
-rw-r--r-- | drivers/media/mdtv/smscoreapi.c | 1170 | ||||
-rw-r--r-- | drivers/media/mdtv/smscoreapi.h | 101 | ||||
-rw-r--r-- | drivers/media/mdtv/smsdvb.c | 438 | ||||
-rw-r--r-- | drivers/media/mdtv/smskdefs.h | 21 | ||||
-rw-r--r-- | drivers/media/mdtv/smsnet.c | 447 | ||||
-rw-r--r-- | drivers/media/mdtv/smstypes.h | 361 | ||||
-rw-r--r-- | drivers/media/mdtv/smsusb.c | 428 |
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, ¶ms, &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(¬ifyee->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(¬ifyee->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 |