summaryrefslogtreecommitdiff
path: root/drivers/staging/cg2900/mfd/cg2900_char_devices.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/staging/cg2900/mfd/cg2900_char_devices.c')
-rw-r--r--drivers/staging/cg2900/mfd/cg2900_char_devices.c719
1 files changed, 719 insertions, 0 deletions
diff --git a/drivers/staging/cg2900/mfd/cg2900_char_devices.c b/drivers/staging/cg2900/mfd/cg2900_char_devices.c
new file mode 100644
index 00000000000..81d230227db
--- /dev/null
+++ b/drivers/staging/cg2900/mfd/cg2900_char_devices.c
@@ -0,0 +1,719 @@
+/*
+ * 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.
+ */
+#define NAME "cg2900_char_dev"
+#define pr_fmt(fmt) NAME ": " fmt "\n"
+
+#include <linux/compiler.h>
+#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/core.h>
+
+#include "cg2900.h"
+#include "cg2900_core.h"
+
+#define MAIN_DEV (dev->dev)
+
+/**
+ * struct char_dev_user - Stores device information.
+ * @dev: Current device.
+ * @miscdev: Registered device struct.
+ * @filp: Current file pointer.
+ * @name: Name of device.
+ * @rx_queue: Data queue.
+ * @rx_wait_queue: Wait queue.
+ * @reset_wait_queue: Reset Wait queue.
+ * @read_mutex: Read mutex.
+ * @write_mutex: Write mutex.
+ * @list: List header for inserting into device list.
+ */
+struct char_dev_user {
+ struct device *dev;
+ struct miscdevice miscdev;
+ struct file *filp;
+ char *name;
+ struct sk_buff_head rx_queue;
+ wait_queue_head_t rx_wait_queue;
+ wait_queue_head_t reset_wait_queue;
+ 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).
+ * @man_mutex: Management mutex.
+ * @dev_users: List of char dev users.
+ */
+struct char_info {
+ struct mutex open_mutex;
+ struct mutex man_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 the CG2900 driver.
+ */
+static void char_dev_read_cb(struct cg2900_user_data *dev, struct sk_buff *skb)
+{
+ struct char_dev_user *char_dev = dev_get_drvdata(dev->dev);
+
+ dev_dbg(dev->dev, "char_dev_read_cb len %d\n", skb->len);
+
+ 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_user_data *dev)
+{
+ struct char_dev_user *char_dev = dev_get_drvdata(dev->dev);
+
+ dev_dbg(dev->dev, "char_dev_reset_cb\n");
+
+ wake_up_interruptible(&char_dev->rx_wait_queue);
+ wake_up_interruptible(&char_dev->reset_wait_queue);
+}
+
+/**
+ * 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.
+ * -EINVAL if device cannot be found in device list.
+ * Error codes from cg2900->open.
+ */
+static int char_dev_open(struct inode *inode, struct file *filp)
+{
+ int err;
+ int minor;
+ struct char_dev_user *dev = NULL;
+ struct char_dev_user *tmp;
+ struct list_head *cursor;
+ struct cg2900_user_data *user;
+
+ mutex_lock(&char_info->open_mutex);
+
+ minor = iminor(inode);
+
+ /* Find the device for this file */
+ mutex_lock(&char_info->man_mutex);
+ 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;
+ }
+ }
+ mutex_unlock(&char_info->man_mutex);
+ if (!dev) {
+ pr_err("Could not identify device in inode");
+ err = -EINVAL;
+ goto error_handling;
+ }
+
+ filp->private_data = dev;
+ dev->filp = filp;
+ user = dev_get_platdata(dev->dev);
+
+ /* First initiate wait queues for this device. */
+ init_waitqueue_head(&dev->rx_wait_queue);
+ init_waitqueue_head(&dev->reset_wait_queue);
+
+ /* Register to CG2900 Driver */
+ err = user->open(user);
+ if (err) {
+ dev_err(MAIN_DEV,
+ "Couldn't register to CG2900 for H:4 channel %s\n",
+ dev->name);
+ goto error_handling;
+ }
+ dev_info(MAIN_DEV, "char_dev %s opened\n", dev->name);
+
+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 = filp->private_data;
+ struct cg2900_user_data *user;
+
+ pr_debug("char_dev_release");
+
+ if (!dev) {
+ pr_err("char_dev_release: Calling with NULL pointer");
+ return -EBADF;
+ }
+
+ mutex_lock(&char_info->open_mutex);
+ mutex_lock(&dev->read_mutex);
+ mutex_lock(&dev->write_mutex);
+
+ user = dev_get_platdata(dev->dev);
+ if (user->opened)
+ user->close(user);
+
+ dev_info(MAIN_DEV, "char_dev %s closed\n", dev->name);
+
+ filp->private_data = NULL;
+ dev->filp = NULL;
+ wake_up_interruptible(&dev->rx_wait_queue);
+ wake_up_interruptible(&dev->reset_wait_queue);
+
+ /* Purge the queue since the device is closed now */
+ skb_queue_purge(&dev->rx_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 = filp->private_data;
+ struct cg2900_user_data *user;
+ struct sk_buff *skb;
+ int bytes_to_copy;
+ int err = 0;
+
+ pr_debug("char_dev_read");
+
+ if (!dev) {
+ pr_err("char_dev_read: Calling with NULL pointer");
+ return -EBADF;
+ }
+ mutex_lock(&dev->read_mutex);
+
+ user = dev_get_platdata(dev->dev);
+
+ if (user->opened && skb_queue_empty(&dev->rx_queue)) {
+ err = wait_event_interruptible(dev->rx_wait_queue,
+ (!(skb_queue_empty(&dev->rx_queue))) ||
+ !user->opened);
+ if (err) {
+ dev_err(MAIN_DEV, "Failed to wait for event\n");
+ goto error_handling;
+ }
+ }
+
+ if (!user->opened) {
+ dev_err(MAIN_DEV, "Channel has been closed\n");
+ err = -EBADF;
+ goto error_handling;
+ }
+
+ skb = skb_dequeue(&dev->rx_queue);
+ if (!skb) {
+ dev_dbg(MAIN_DEV,
+ "skb queue is empty - return with zero bytes\n");
+ 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) {
+ dev_err(MAIN_DEV, "Error %d from copy_to_user\n", 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 = filp->private_data;
+ struct cg2900_user_data *user;
+ int err = 0;
+
+ pr_debug("char_dev_write");
+
+ if (!dev) {
+ pr_err("char_dev_write: Calling with NULL pointer");
+ return -EBADF;
+ }
+
+ user = dev_get_platdata(dev->dev);
+ if (!user->opened) {
+ dev_err(MAIN_DEV, "char_dev_write: Channel not opened\n");
+ return -EACCES;
+ }
+
+ mutex_lock(&dev->write_mutex);
+
+ skb = user->alloc_skb(count, GFP_ATOMIC);
+ if (!skb) {
+ dev_err(MAIN_DEV, "Couldn't allocate sk_buff with length %d\n",
+ count);
+ goto error_handling;
+ }
+
+ err = copy_from_user(skb_put(skb, count), buf, count);
+ if (err) {
+ dev_err(MAIN_DEV, "Error %d from copy_from_user\n", err);
+ kfree_skb(skb);
+ err = -EFAULT;
+ goto error_handling;
+ }
+
+ err = user->write(user, skb);
+ if (err) {
+ dev_err(MAIN_DEV, "cg2900_write failed (%d)\n", 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.
+ * -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 = filp->private_data;
+ struct cg2900_user_data *user;
+ struct cg2900_rev_data rev_data;
+ int err = 0;
+ int ret_val;
+ void __user *user_arg = (void __user *)arg;
+
+ if (!dev) {
+ pr_err("char_dev_unlocked_ioctl: Calling with NULL pointer");
+ return -EBADF;
+ }
+
+ dev_dbg(dev->dev, "char_dev_unlocked_ioctl for %s\n"
+ "\tDIR: %d\n"
+ "\tTYPE: %d\n"
+ "\tNR: %d\n"
+ "\tSIZE: %d",
+ dev->name, _IOC_DIR(cmd), _IOC_TYPE(cmd), _IOC_NR(cmd),
+ _IOC_SIZE(cmd));
+
+ user = dev_get_platdata(dev->dev);
+
+ switch (cmd) {
+ case CG2900_CHAR_DEV_IOCTL_RESET:
+ if (!user->opened)
+ return -EACCES;
+ dev_dbg(MAIN_DEV, "ioctl reset command for device %s\n",
+ dev->name);
+ err = user->reset(user);
+ break;
+
+ case CG2900_CHAR_DEV_IOCTL_CHECK4RESET:
+ if (user->opened)
+ ret_val = CG2900_CHAR_DEV_IOCTL_EVENT_IDLE;
+ else
+ ret_val = CG2900_CHAR_DEV_IOCTL_EVENT_RESET;
+
+ dev_dbg(MAIN_DEV, "ioctl check for reset command for device %s",
+ dev->name);
+
+ err = copy_to_user(user_arg, &ret_val, sizeof(ret_val));
+ if (err) {
+ dev_err(MAIN_DEV,
+ "Error %d from copy_to_user for reset\n", err);
+ return -EFAULT;
+ }
+ break;
+
+ case CG2900_CHAR_DEV_IOCTL_GET_REVISION:
+ if (!user->get_local_revision(user, &rev_data)) {
+ dev_err(MAIN_DEV, "No revision data available\n");
+ return -EIO;
+ }
+ dev_dbg(MAIN_DEV, "ioctl check for local revision info\n"
+ "\trevision 0x%04X\n"
+ "\tsub_version 0x%04X\n",
+ rev_data.revision, rev_data.sub_version);
+ err = copy_to_user(user_arg, &rev_data, sizeof(rev_data));
+ if (err) {
+ dev_err(MAIN_DEV,
+ "Error %d from copy_to_user for "
+ "revision\n", err);
+ return -EFAULT;
+ }
+ break;
+
+ default:
+ dev_err(MAIN_DEV, "Unknown ioctl command %08X\n", cmd);
+ err = -EINVAL;
+ break;
+ };
+
+ 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 = filp->private_data;
+ struct cg2900_user_data *user;
+ unsigned int mask = 0;
+
+ if (!dev) {
+ pr_debug("char_dev_poll: Device not open");
+ return POLLERR | POLLRDHUP;
+ }
+
+ user = dev_get_platdata(dev->dev);
+
+ poll_wait(filp, &dev->reset_wait_queue, wait);
+ poll_wait(filp, &dev->rx_wait_queue, wait);
+
+ if (!user->opened)
+ mask |= POLLERR | POLLRDHUP | POLLPRI;
+ else
+ mask |= POLLOUT; /* We can TX unless there is an error */
+
+ if (!(skb_queue_empty(&dev->rx_queue)))
+ mask |= POLLIN | POLLRDNORM;
+
+ 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
+};
+
+/**
+ * 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)
+{
+ if (!dev_usr)
+ return;
+
+ dev_dbg(dev_usr->dev,
+ "Removing char device %s with major %d and minor %d\n",
+ dev_usr->name,
+ MAJOR(dev_usr->miscdev.this_device->devt),
+ MINOR(dev_usr->miscdev.this_device->devt));
+
+ skb_queue_purge(&dev_usr->rx_queue);
+
+ mutex_destroy(&dev_usr->read_mutex);
+ mutex_destroy(&dev_usr->write_mutex);
+
+ dev_usr->dev = NULL;
+ if (dev_usr->filp)
+ dev_usr->filp->private_data = NULL;
+
+ /* Remove device node in file system. */
+ misc_deregister(&dev_usr->miscdev);
+ 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)
+{
+ int err = 0;
+ struct char_dev_user *dev_usr;
+ struct cg2900_user_data *user;
+ struct device *dev = &pdev->dev;
+
+ dev_dbg(&pdev->dev, "cg2900_char_probe\n");
+
+ user = dev_get_platdata(dev);
+ user->dev = dev;
+ user->read_cb = char_dev_read_cb;
+ user->reset_cb = char_dev_reset_cb;
+
+ dev_usr = kzalloc(sizeof(*dev_usr), GFP_KERNEL);
+ if (!dev_usr) {
+ dev_err(&pdev->dev, "Couldn't allocate dev_usr\n");
+ return -ENOMEM;
+ }
+
+ dev_set_drvdata(&pdev->dev, dev_usr);
+ dev_usr->dev = &pdev->dev;
+
+ /* Store device name */
+ dev_usr->name = user->channel_data.char_dev_name;
+
+ /* Prepare miscdevice struct before registering the device */
+ dev_usr->miscdev.minor = MISC_DYNAMIC_MINOR;
+ dev_usr->miscdev.name = dev_usr->name;
+ dev_usr->miscdev.nodename = dev_usr->name;
+ dev_usr->miscdev.fops = &char_dev_fops;
+ dev_usr->miscdev.parent = &pdev->dev;
+ dev_usr->miscdev.mode = S_IRUGO | S_IWUGO;
+
+ err = misc_register(&dev_usr->miscdev);
+ if (err) {
+ dev_err(&pdev->dev, "Error %d registering misc dev\n", err);
+ goto err_free_usr;
+ }
+
+ dev_dbg(&pdev->dev, "Added char device %s with major %d and minor %d\n",
+ dev_usr->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);
+
+ mutex_lock(&char_info->man_mutex);
+ list_add_tail(&dev_usr->list, &char_info->dev_users);
+ mutex_unlock(&char_info->man_mutex);
+
+ return 0;
+
+err_free_usr:
+ kfree(dev_usr);
+ dev_set_drvdata(&pdev->dev, NULL);
+ return err;
+}
+
+/**
+ * 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;
+ struct char_dev_user *user;
+
+ dev_dbg(&pdev->dev, "cg2900_char_remove\n");
+
+ user = dev_get_drvdata(&pdev->dev);
+
+ mutex_lock(&char_info->man_mutex);
+ list_for_each_safe(cursor, next, &char_info->dev_users) {
+ tmp = list_entry(cursor, struct char_dev_user, list);
+ if (tmp == user) {
+ list_del(cursor);
+ remove_dev(tmp);
+ dev_set_drvdata(&pdev->dev, NULL);
+ break;
+ }
+ }
+ mutex_unlock(&char_info->man_mutex);
+ 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)
+{
+ pr_debug("cg2900_char_init");
+
+ /* Initialize private data. */
+ char_info = kzalloc(sizeof(*char_info), GFP_ATOMIC);
+ if (!char_info) {
+ pr_err("Could not alloc char_info struct");
+ return -ENOMEM;
+ }
+
+ mutex_init(&char_info->open_mutex);
+ mutex_init(&char_info->man_mutex);
+ INIT_LIST_HEAD(&char_info->dev_users);
+
+ return platform_driver_register(&cg2900_char_driver);
+}
+
+/**
+ * cg2900_char_exit() - Remove module.
+ *
+ * Unregisters platform driver.
+ */
+static void __exit cg2900_char_exit(void)
+{
+ struct list_head *cursor, *next;
+ struct char_dev_user *tmp;
+
+ pr_debug("cg2900_char_exit");
+
+ platform_driver_unregister(&cg2900_char_driver);
+
+ if (!char_info)
+ return;
+
+ 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);
+ mutex_destroy(&char_info->man_mutex);
+
+ kfree(char_info);
+ char_info = NULL;
+}
+
+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");