lkml.org 
[lkml]   [2010]   [Oct]   [22]   [last100]   RSS Feed
Views: [wrap][no wrap]   [headers]  [forward] 
 
Messages in this thread
    Patch in this message
    /
    Date
    Subject[PATCH 2/9] mfd: Add char devices for the ST-Ericsson CG2900.
    From
    This patch adds char devices to the ST-Ericsson CG2900 driver.
    The reason for this is to allow users of CG2900, such as GPS, to
    be placed in user space.

    Signed-off-by: Par-Gunnar Hjalmdahl <par-gunnar.p.hjalmdahl@stericsson.com>
    ---
    drivers/mfd/cg2900/Makefile | 2 +
    drivers/mfd/cg2900/cg2900_char_devices.c | 745 ++++++++++++++++++++++++++++++
    2 files changed, 747 insertions(+), 0 deletions(-)
    create mode 100644 drivers/mfd/cg2900/cg2900_char_devices.c

    diff --git a/drivers/mfd/cg2900/Makefile b/drivers/mfd/cg2900/Makefile
    index a736101..0ac9bc6 100644
    --- a/drivers/mfd/cg2900/Makefile
    +++ b/drivers/mfd/cg2900/Makefile
    @@ -5,3 +5,5 @@
    obj-$(CONFIG_MFD_CG2900) += cg2900_core.o
    export-objs := cg2900_core.o

    +obj-$(CONFIG_MFD_CG2900) += cg2900_char_devices.o
    +
    diff --git a/drivers/mfd/cg2900/cg2900_char_devices.c
    b/drivers/mfd/cg2900/cg2900_char_devices.c
    new file mode 100644
    index 0000000..6b8e0b0
    --- /dev/null
    +++ b/drivers/mfd/cg2900/cg2900_char_devices.c
    @@ -0,0 +1,745 @@
    +/*
    + * drivers/mfd/cg2900/cg2900_char_devices.c
    + *
    + * Copyright (C) ST-Ericsson SA 2010
    + * Authors:
    + * Par-Gunnar Hjalmdahl (par-gunnar.p.hjalmdahl@stericsson.com) for
    ST-Ericsson.
    + * Henrik Possung (henrik.possung@stericsson.com) for ST-Ericsson.
    + * Josef Kindberg (josef.kindberg@stericsson.com) for ST-Ericsson.
    + * Dariusz Szymszak (dariusz.xd.szymczak@stericsson.com) for ST-Ericsson.
    + * Kjell Andersson (kjell.k.andersson@stericsson.com) for ST-Ericsson.
    + * License terms: GNU General Public License (GPL), version 2
    + *
    + * Linux Bluetooth HCI H:4 Driver for ST-Ericsson connectivity controller.
    + */
    +
    +#include <linux/device.h>
    +#include <linux/fs.h>
    +#include <linux/file.h>
    +#include <linux/kernel.h>
    +#include <linux/list.h>
    +#include <linux/miscdevice.h>
    +#include <linux/module.h>
    +#include <linux/mutex.h>
    +#include <linux/platform_device.h>
    +#include <linux/poll.h>
    +#include <linux/sched.h>
    +#include <linux/skbuff.h>
    +#include <linux/mfd/cg2900.h>
    +
    +#include "cg2900_core.h"
    +#include "cg2900_debug.h"
    +
    +#define NAME "CharDev "
    +
    +/* Ioctls */
    +#define CG2900_CHAR_DEV_IOCTL_RESET _IOW('U', 210, int)
    +#define CG2900_CHAR_DEV_IOCTL_CHECK4RESET _IOR('U', 212, int)
    +#define CG2900_CHAR_DEV_IOCTL_GET_REVISION _IOR('U', 213, int)
    +#define CG2900_CHAR_DEV_IOCTL_GET_SUB_VER _IOR('U', 214, int)
    +
    +#define CG2900_CHAR_DEV_IOCTL_EVENT_RESET 1
    +#define CG2900_CHAR_DEV_IOCTL_EVENT_CLOSED 2
    +
    +/* Internal type definitions */
    +
    +/**
    + * enum char_reset_state - Reset state.
    + * @CG2900_CHAR_IDLE: Idle state.
    + * @CG2900_CHAR_RESET: Reset state.
    + */
    +enum char_reset_state {
    + CG2900_CHAR_IDLE,
    + CG2900_CHAR_RESET
    +};
    +
    +/**
    + * struct char_dev_user - Stores device information.
    + * @dev: Registered CG2900 Core device.
    + * @miscdev: Registered device struct.
    + * @name: Name of device.
    + * @rx_queue: Data queue.
    + * @rx_wait_queue: Wait queue.
    + * @reset_wait_queue: Reset Wait queue.
    + * @reset_state: Reset state.
    + * @read_mutex: Read mutex.
    + * @write_mutex: Write mutex.
    + * @list: List header for inserting into device list.
    + */
    +struct char_dev_user {
    + struct cg2900_device *dev;
    + struct miscdevice *miscdev;
    + char *name;
    + struct sk_buff_head rx_queue;
    + wait_queue_head_t rx_wait_queue;
    + wait_queue_head_t reset_wait_queue;
    + enum char_reset_state reset_state;
    + struct mutex read_mutex;
    + struct mutex write_mutex;
    + struct list_head list;
    +};
    +
    +/**
    + * struct char_info - Stores all current users.
    + * @open_mutex: Open mutex (used for both open and release).
    + * @dev_users: List of char dev users.
    + */
    +struct char_info {
    + struct mutex open_mutex;
    + struct list_head dev_users;
    +};
    +
    +static struct char_info *char_info;
    +
    +/**
    + * char_dev_read_cb() - Handle data received from controller.
    + * @dev: Device receiving data.
    + * @skb: Buffer with data coming from controller.
    + *
    + * The char_dev_read_cb() function handles data received from
    STE-CG2900 driver.
    + */
    +static void char_dev_read_cb(struct cg2900_device *dev, struct sk_buff *skb)
    +{
    + struct char_dev_user *char_dev = (struct char_dev_user *)dev->user_data;
    +
    + CG2900_INFO("char_dev_read_cb");
    +
    + if (!char_dev) {
    + CG2900_ERR("No char dev! Exiting");
    + kfree_skb(skb);
    + return;
    + }
    +
    + skb_queue_tail(&char_dev->rx_queue, skb);
    +
    + wake_up_interruptible(&char_dev->rx_wait_queue);
    +}
    +
    +/**
    + * char_dev_reset_cb() - Handle reset from controller.
    + * @dev: Device resetting.
    + *
    + * The char_dev_reset_cb() function handles reset from the CG2900 driver.
    + */
    +static void char_dev_reset_cb(struct cg2900_device *dev)
    +{
    + struct char_dev_user *char_dev = (struct char_dev_user *)dev->user_data;
    +
    + CG2900_INFO("char_dev_reset_cb");
    +
    + if (!char_dev) {
    + CG2900_ERR("char_dev == NULL");
    + return;
    + }
    +
    + char_dev->reset_state = CG2900_CHAR_RESET;
    + /*
    + * The device will be freed by CG2900 Core when this function is
    + * finished.
    + */
    + char_dev->dev = NULL;
    +
    + wake_up_interruptible(&char_dev->rx_wait_queue);
    + wake_up_interruptible(&char_dev->reset_wait_queue);
    +}
    +
    +static struct cg2900_callbacks char_cb = {
    + .read_cb = char_dev_read_cb,
    + .reset_cb = char_dev_reset_cb
    +};
    +
    +/**
    + * char_dev_open() - Open char device.
    + * @inode: Device driver information.
    + * @filp: Pointer to the file struct.
    + *
    + * The char_dev_open() function opens the char device.
    + *
    + * Returns:
    + * 0 if there is no error.
    + * -EACCES if device was already registered to driver or if registration
    + * failed.
    + */
    +static int char_dev_open(struct inode *inode, struct file *filp)
    +{
    + int err = 0;
    + int minor;
    + struct char_dev_user *dev = NULL;
    + struct char_dev_user *tmp;
    + struct list_head *cursor;
    +
    + mutex_lock(&char_info->open_mutex);
    +
    + minor = iminor(inode);
    +
    + /* Find the device for this file */
    + list_for_each(cursor, &char_info->dev_users) {
    + tmp = list_entry(cursor, struct char_dev_user, list);
    + if (tmp->miscdev->minor == minor) {
    + dev = tmp;
    + break;
    + }
    + }
    + if (!dev) {
    + CG2900_ERR("Could not identify device in inode");
    + err = -EINVAL;
    + goto error_handling;
    + }
    +
    + filp->private_data = dev;
    +
    + CG2900_INFO("char_dev_open %s", dev->name);
    +
    + if (dev->dev) {
    + CG2900_ERR("Device already registered to CG2900 Driver");
    + err = -EACCES;
    + goto error_handling;
    + }
    + /* First initiate wait queues for this device. */
    + init_waitqueue_head(&dev->rx_wait_queue);
    + init_waitqueue_head(&dev->reset_wait_queue);
    +
    + dev->reset_state = CG2900_CHAR_IDLE;
    +
    + /* Register to CG2900 Driver */
    + dev->dev = cg2900_register_user(dev->name, &char_cb);
    + if (dev->dev)
    + dev->dev->user_data = dev;
    + else {
    + CG2900_ERR("Couldn't register to CG2900 for H:4 channel %s",
    + dev->name);
    + err = -EACCES;
    + }
    +
    +error_handling:
    + mutex_unlock(&char_info->open_mutex);
    + return err;
    +}
    +
    +/**
    + * char_dev_release() - Release char device.
    + * @inode: Device driver information.
    + * @filp: Pointer to the file struct.
    + *
    + * The char_dev_release() function release the char device.
    + *
    + * Returns:
    + * 0 if there is no error.
    + * -EBADF if NULL pointer was supplied in private data.
    + */
    +static int char_dev_release(struct inode *inode, struct file *filp)
    +{
    + int err = 0;
    + struct char_dev_user *dev = (struct char_dev_user *)filp->private_data;
    +
    + CG2900_INFO("char_dev_release");
    +
    + if (!dev) {
    + CG2900_ERR("Calling with NULL pointer");
    + return -EBADF;
    + }
    +
    + mutex_lock(&char_info->open_mutex);
    + mutex_lock(&dev->read_mutex);
    + mutex_lock(&dev->write_mutex);
    +
    + if (dev->reset_state == CG2900_CHAR_IDLE)
    + cg2900_deregister_user(dev->dev);
    +
    + dev->dev = NULL;
    + filp->private_data = NULL;
    + wake_up_interruptible(&dev->rx_wait_queue);
    + wake_up_interruptible(&dev->reset_wait_queue);
    +
    + mutex_unlock(&dev->write_mutex);
    + mutex_unlock(&dev->read_mutex);
    + mutex_unlock(&char_info->open_mutex);
    +
    + return err;
    +}
    +
    +/**
    + * char_dev_read() - Queue and copy buffer to user.
    + * @filp: Pointer to the file struct.
    + * @buf: Received buffer.
    + * @count: Size of buffer.
    + * @f_pos: Position in buffer.
    + *
    + * The char_dev_read() function queues and copy the received buffer to
    + * the user space char device. If no data is available this function
    will block.
    + *
    + * Returns:
    + * Bytes successfully read (could be 0).
    + * -EBADF if NULL pointer was supplied in private data.
    + * -EFAULT if copy_to_user fails.
    + * Error codes from wait_event_interruptible.
    + */
    +static ssize_t char_dev_read(struct file *filp, char __user *buf, size_t count,
    + loff_t *f_pos)
    +{
    + struct char_dev_user *dev = (struct char_dev_user *)filp->private_data;
    + struct sk_buff *skb;
    + int bytes_to_copy;
    + int err = 0;
    +
    + CG2900_INFO("char_dev_read");
    +
    + if (!dev) {
    + CG2900_ERR("Calling with NULL pointer");
    + return -EBADF;
    + }
    + mutex_lock(&dev->read_mutex);
    +
    + if (skb_queue_empty(&dev->rx_queue)) {
    + err = wait_event_interruptible(dev->rx_wait_queue,
    + (!(skb_queue_empty(&dev->rx_queue))) ||
    + (CG2900_CHAR_RESET == dev->reset_state) ||
    + (dev->dev == NULL));
    + if (err) {
    + CG2900_ERR("Failed to wait for event");
    + goto error_handling;
    + }
    + }
    +
    + if (!dev->dev) {
    + CG2900_DBG("dev is empty - return with negative bytes");
    + err = -EBADF;
    + goto error_handling;
    + }
    +
    + skb = skb_dequeue(&dev->rx_queue);
    + if (!skb) {
    + CG2900_DBG("skb queue is empty - return with zero bytes");
    + bytes_to_copy = 0;
    + goto finished;
    + }
    +
    + bytes_to_copy = min(count, skb->len);
    +
    + err = copy_to_user(buf, skb->data, bytes_to_copy);
    + if (err) {
    + skb_queue_head(&dev->rx_queue, skb);
    + err = -EFAULT;
    + goto error_handling;
    + }
    +
    + skb_pull(skb, bytes_to_copy);
    +
    + if (skb->len > 0)
    + skb_queue_head(&dev->rx_queue, skb);
    + else
    + kfree_skb(skb);
    +
    + goto finished;
    +
    +error_handling:
    + mutex_unlock(&dev->read_mutex);
    + return (ssize_t)err;
    +finished:
    + mutex_unlock(&dev->read_mutex);
    + return bytes_to_copy;
    +}
    +
    +/**
    + * char_dev_write() - Copy buffer from user and write to CG2900 driver.
    + * @filp: Pointer to the file struct.
    + * @buf: Write buffer.
    + * @count: Size of the buffer write.
    + * @f_pos: Position of buffer.
    + *
    + * Returns:
    + * Bytes successfully written (could be 0).
    + * -EBADF if NULL pointer was supplied in private data.
    + * -EFAULT if copy_from_user fails.
    + */
    +static ssize_t char_dev_write(struct file *filp, const char __user *buf,
    + size_t count, loff_t *f_pos)
    +{
    + struct sk_buff *skb;
    + struct char_dev_user *dev = (struct char_dev_user *)filp->private_data;
    + int err = 0;
    +
    + CG2900_INFO("char_dev_write");
    +
    + if (!dev) {
    + CG2900_ERR("Calling with NULL pointer");
    + return -EBADF;
    + }
    + mutex_lock(&dev->write_mutex);
    +
    + skb = cg2900_alloc_skb(count, GFP_ATOMIC);
    + if (!skb) {
    + CG2900_ERR("Couldn't allocate sk_buff with length %d", count);
    + goto error_handling;
    + }
    +
    + if (copy_from_user(skb_put(skb, count), buf, count)) {
    + kfree_skb(skb);
    + err = -EFAULT;
    + goto error_handling;
    + }
    +
    + err = cg2900_write(dev->dev, skb);
    + if (err) {
    + CG2900_ERR("cg2900_write failed (%d)", err);
    + kfree_skb(skb);
    + goto error_handling;
    + }
    +
    + mutex_unlock(&dev->write_mutex);
    + return count;
    +
    +error_handling:
    + mutex_unlock(&dev->write_mutex);
    + return err;
    +}
    +
    +/**
    + * char_dev_unlocked_ioctl() - Handle IOCTL call to the interface.
    + * @filp: Pointer to the file struct.
    + * @cmd: IOCTL command.
    + * @arg: IOCTL argument.
    + *
    + * Returns:
    + * 0 if there is no error.
    + * -EBADF if NULL pointer was supplied in private data.
    + * -EINVAL if supplied cmd is not supported.
    + * For cmd CG2900_CHAR_DEV_IOCTL_CHECK4RESET 0x01 is returned if device is
    + * reset and 0x02 is returned if device is closed.
    + */
    +static long char_dev_unlocked_ioctl(struct file *filp, unsigned int cmd,
    + unsigned long arg)
    +{
    + struct char_dev_user *dev = (struct char_dev_user *)filp->private_data;
    + struct cg2900_rev_data rev_data;
    + int err = 0;
    +
    + CG2900_INFO("char_dev_unlocked_ioctl cmd %d for %s", cmd, dev->name);
    + CG2900_DBG("DIR: %d, TYPE: %d, NR: %d, SIZE: %d",
    + _IOC_DIR(cmd), _IOC_TYPE(cmd), _IOC_NR(cmd),
    + _IOC_SIZE(cmd));
    +
    + switch (cmd) {
    + case CG2900_CHAR_DEV_IOCTL_RESET:
    + if (!dev) {
    + err = -EBADF;
    + goto error_handling;
    + }
    + CG2900_INFO("ioctl reset command for device %s", dev->name);
    + err = cg2900_reset(dev->dev);
    + break;
    +
    + case CG2900_CHAR_DEV_IOCTL_CHECK4RESET:
    + if (!dev) {
    + CG2900_INFO("ioctl check for reset command for device");
    + /* Return positive value if closed */
    + err = CG2900_CHAR_DEV_IOCTL_EVENT_CLOSED;
    + } else if (dev->reset_state == CG2900_CHAR_RESET) {
    + CG2900_INFO("ioctl check for reset command for device "
    + "%s", dev->name);
    + /* Return positive value if reset */
    + err = CG2900_CHAR_DEV_IOCTL_EVENT_RESET;
    + }
    + break;
    +
    + case CG2900_CHAR_DEV_IOCTL_GET_REVISION:
    + CG2900_INFO("ioctl check for local revision info");
    + if (cg2900_get_local_revision(&rev_data)) {
    + CG2900_DBG("Read revision data revision %d "
    + "sub_version %d",
    + rev_data.revision, rev_data.sub_version);
    + err = rev_data.revision;
    + } else {
    + CG2900_DBG("No revision data available");
    + err = -EIO;
    + }
    + break;
    +
    + case CG2900_CHAR_DEV_IOCTL_GET_SUB_VER:
    + CG2900_INFO("ioctl check for local sub-version info");
    + if (cg2900_get_local_revision(&rev_data)) {
    + CG2900_DBG("Read revision data revision %d "
    + "sub_version %d",
    + rev_data.revision, rev_data.sub_version);
    + err = rev_data.sub_version;
    + } else {
    + CG2900_DBG("No revision data available");
    + err = -EIO;
    + }
    + break;
    +
    + default:
    + CG2900_ERR("Unknown ioctl command %08X", cmd);
    + err = -EINVAL;
    + break;
    + };
    +
    +error_handling:
    + return err;
    +}
    +
    +/**
    + * char_dev_poll() - Handle POLL call to the interface.
    + * @filp: Pointer to the file struct.
    + * @wait: Poll table supplied to caller.
    + *
    + * Returns:
    + * Mask of current set POLL values
    + */
    +static unsigned int char_dev_poll(struct file *filp, poll_table *wait)
    +{
    + struct char_dev_user *dev = (struct char_dev_user *)filp->private_data;
    + unsigned int mask = 0;
    +
    + if (!dev) {
    + CG2900_DBG("Device not open");
    + return POLLERR | POLLRDHUP;
    + }
    +
    + poll_wait(filp, &dev->reset_wait_queue, wait);
    + poll_wait(filp, &dev->rx_wait_queue, wait);
    +
    + if (!dev->dev)
    + mask |= POLLERR | POLLRDHUP;
    + else
    + mask |= POLLOUT; /* We can TX unless there is an error */
    +
    + if (!(skb_queue_empty(&dev->rx_queue)))
    + mask |= POLLIN | POLLRDNORM;
    +
    + if (CG2900_CHAR_RESET == dev->reset_state)
    + mask |= POLLPRI;
    +
    + return mask;
    +}
    +
    +/*
    + * struct char_dev_fops - Char devices file operations.
    + * @read: Function that reads from the char device.
    + * @write: Function that writes to the char device.
    + * @unlocked_ioctl: Function that performs IO operations with
    + * the char device.
    + * @poll: Function that checks if there are possible operations
    + * with the char device.
    + * @open: Function that opens the char device.
    + * @release: Function that release the char device.
    + */
    +static const struct file_operations char_dev_fops = {
    + .read = char_dev_read,
    + .write = char_dev_write,
    + .unlocked_ioctl = char_dev_unlocked_ioctl,
    + .poll = char_dev_poll,
    + .open = char_dev_open,
    + .release = char_dev_release
    +};
    +
    +/**
    + * setup_dev() - Set up the char device structure for device.
    + * @parent: Parent device pointer.
    + * @name: Name of registered device.
    + *
    + * The setup_dev() function sets up the char_dev structure for this device.
    + *
    + * Returns:
    + * 0 if there is no error.
    + * -EINVAL if NULL pointer has been supplied.
    + * Error codes from cdev_add and device_create.
    + */
    +static int setup_dev(struct device *parent, char *name)
    +{
    + int err = 0;
    + struct char_dev_user *dev_usr;
    +
    + CG2900_INFO(NAME "setup_dev");
    +
    + dev_usr = kzalloc(sizeof(*dev_usr), GFP_KERNEL);
    + if (!dev_usr) {
    + CG2900_ERR("Couldn't allocate dev_usr");
    + return -ENOMEM;
    + }
    +
    + /* Store device name */
    + dev_usr->name = name;
    +
    + dev_usr->miscdev = kzalloc(sizeof(*(dev_usr->miscdev)),
    + GFP_KERNEL);
    + if (!dev_usr->miscdev) {
    + CG2900_ERR("Couldn't allocate char_dev");
    + err = -ENOMEM;
    + goto err_free_usr;
    + }
    +
    + /* Prepare miscdevice struct before registering the device */
    + dev_usr->miscdev->minor = MISC_DYNAMIC_MINOR;
    + dev_usr->miscdev->name = name;
    + dev_usr->miscdev->fops = &char_dev_fops;
    + dev_usr->miscdev->parent = parent;
    +
    + err = misc_register(dev_usr->miscdev);
    + if (err) {
    + CG2900_ERR("Error %d registering misc dev!", err);
    + goto err_free_dev;
    + }
    +
    + CG2900_INFO("Added char device %s with major 0x%X and minor 0x%X",
    + name, MAJOR(dev_usr->miscdev->this_device->devt),
    + MINOR(dev_usr->miscdev->this_device->devt));
    +
    + mutex_init(&dev_usr->read_mutex);
    + mutex_init(&dev_usr->write_mutex);
    +
    + skb_queue_head_init(&dev_usr->rx_queue);
    +
    + list_add_tail(&dev_usr->list, &char_info->dev_users);
    + return 0;
    +
    +err_free_dev:
    + kfree(dev_usr->miscdev);
    + dev_usr->miscdev = NULL;
    +err_free_usr:
    + kfree(dev_usr);
    + return err;
    +}
    +
    +/**
    + * remove_dev() - Remove char device structure for device.
    + * @dev_usr: Char device user.
    + *
    + * The remove_dev() function releases the char_dev structure for this device.
    + */
    +static void remove_dev(struct char_dev_user *dev_usr)
    +{
    + CG2900_INFO(NAME "remove_dev");
    +
    + if (!dev_usr)
    + return;
    +
    + skb_queue_purge(&dev_usr->rx_queue);
    +
    + mutex_destroy(&dev_usr->read_mutex);
    + mutex_destroy(&dev_usr->write_mutex);
    +
    + /* Remove device node in file system. */
    + misc_deregister(dev_usr->miscdev);
    + kfree(dev_usr->miscdev);
    + dev_usr->miscdev = NULL;
    +
    + kfree(dev_usr);
    +}
    +
    +/**
    + * cg2900_char_probe() - Initialize char device module.
    + * @pdev: Platform device.
    + *
    + * Returns:
    + * 0 if success.
    + * -ENOMEM if allocation fails.
    + * -EACCES if device already have been initiated.
    + */
    +static int __devinit cg2900_char_probe(struct platform_device *pdev)
    +{
    + struct device *parent;
    +
    + CG2900_INFO("cg2900_char_probe");
    +
    + if (char_info) {
    + CG2900_ERR("Char devices already initiated");
    + return -EACCES;
    + }
    +
    + parent = pdev->dev.parent;
    +
    + /* Initialize private data. */
    + char_info = kzalloc(sizeof(*char_info), GFP_ATOMIC);
    + if (!char_info) {
    + CG2900_ERR("Could not alloc char_info struct.");
    + return -ENOMEM;
    + }
    +
    + mutex_init(&char_info->open_mutex);
    + INIT_LIST_HEAD(&char_info->dev_users);
    +
    + setup_dev(parent, CG2900_BT_CMD);
    + setup_dev(parent, CG2900_BT_ACL);
    + setup_dev(parent, CG2900_BT_EVT);
    + setup_dev(parent, CG2900_FM_RADIO);
    + setup_dev(parent, CG2900_GNSS);
    + setup_dev(parent, CG2900_DEBUG);
    + setup_dev(parent, CG2900_STE_TOOLS);
    + setup_dev(parent, CG2900_HCI_LOGGER);
    + setup_dev(parent, CG2900_US_CTRL);
    + setup_dev(parent, CG2900_BT_AUDIO);
    + setup_dev(parent, CG2900_FM_RADIO_AUDIO);
    + setup_dev(parent, CG2900_CORE);
    +
    + return 0;
    +}
    +
    +/**
    + * cg2900_char_remove() - Release the char device module.
    + * @pdev: Platform device.
    + *
    + * Returns:
    + * 0 if success (always success).
    + */
    +static int __devexit cg2900_char_remove(struct platform_device *pdev)
    +{
    + struct list_head *cursor, *next;
    + struct char_dev_user *tmp;
    +
    + CG2900_INFO("cg2900_char_remove");
    +
    + if (!char_info)
    + return 0;
    +
    + list_for_each_safe(cursor, next, &char_info->dev_users) {
    + tmp = list_entry(cursor, struct char_dev_user, list);
    + list_del(cursor);
    + remove_dev(tmp);
    + }
    +
    + mutex_destroy(&char_info->open_mutex);
    +
    + kfree(char_info);
    + char_info = NULL;
    + return 0;
    +}
    +
    +static struct platform_driver cg2900_char_driver = {
    + .driver = {
    + .name = "cg2900-chardev",
    + .owner = THIS_MODULE,
    + },
    + .probe = cg2900_char_probe,
    + .remove = __devexit_p(cg2900_char_remove),
    +};
    +
    +/**
    + * cg2900_char_init() - Initialize module.
    + *
    + * Registers platform driver.
    + */
    +static int __init cg2900_char_init(void)
    +{
    + CG2900_INFO("cg2900_char_init");
    + return platform_driver_register(&cg2900_char_driver);
    +}
    +
    +/**
    + * cg2900_char_exit() - Remove module.
    + *
    + * Unregisters platform driver.
    + */
    +static void __exit cg2900_char_exit(void)
    +{
    + CG2900_INFO("cg2900_char_exit");
    + platform_driver_unregister(&cg2900_char_driver);
    +}
    +
    +module_init(cg2900_char_init);
    +module_exit(cg2900_char_exit);
    +
    +MODULE_AUTHOR("Henrik Possung ST-Ericsson");
    +MODULE_AUTHOR("Par-Gunnar Hjalmdahl ST-Ericsson");
    +MODULE_LICENSE("GPL v2");
    +MODULE_DESCRIPTION("ST-Ericsson CG2900 Char Devices Driver");
    --
    1.6.3.3

    \
     
     \ /
      Last update: 2010-10-22 12:39    [W:0.087 / U:0.924 seconds]
    ©2003-2016 Jasper Spaans. hosted at Digital OceanAdvertise on this site