All of lore.kernel.org
 help / color / mirror / Atom feed
From: Carlos Chinea <carlos.chinea@nokia.com>
To: linux-kernel@vger.kernel.org
Cc: linux-omap@vger.kernel.org, Andras Domokos <andras.domokos@nokia.com>
Subject: [RFC PATCH 4/5] HSI CHAR: Add HSI char device driver
Date: Fri, 23 Apr 2010 18:15:27 +0300	[thread overview]
Message-ID: <1272035728-6933-5-git-send-email-carlos.chinea@nokia.com> (raw)
In-Reply-To: <1272035728-6933-1-git-send-email-carlos.chinea@nokia.com>

From: Andras Domokos <andras.domokos@nokia.com>

Add HSI char device driver to the kernel.

Signed-off-by: Andras Domokos <andras.domokos@nokia.com>
---
 drivers/hsi/clients/hsi_char.c | 1078 ++++++++++++++++++++++++++++++++++++++++
 include/linux/hsi/hsi_char.h   |   79 +++
 2 files changed, 1157 insertions(+), 0 deletions(-)
 create mode 100644 drivers/hsi/clients/hsi_char.c
 create mode 100644 include/linux/hsi/hsi_char.h

diff --git a/drivers/hsi/clients/hsi_char.c b/drivers/hsi/clients/hsi_char.c
new file mode 100644
index 0000000..b30d912
--- /dev/null
+++ b/drivers/hsi/clients/hsi_char.c
@@ -0,0 +1,1078 @@
+/*
+ * hsi-char.c
+ *
+ * HSI character device driver, implements the character device
+ * interface.
+ *
+ * Copyright (C) 2010 Nokia Corporation. All rights reserved.
+ *
+ * Contact: Andras Domokos <andras.domokos@nokia.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ */
+
+#include <linux/errno.h>
+#include <linux/module.h>
+#include <linux/types.h>
+#include <asm/atomic.h>
+#include <linux/init.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/file.h>
+#include <linux/mm.h>
+#include <linux/fs.h>
+#include <linux/cdev.h>
+#include <linux/poll.h>
+#include <asm/mach-types.h>
+#include <linux/ioctl.h>
+#include <linux/uaccess.h>
+#include <linux/sched.h>
+
+#include <linux/hsi/hsi.h>
+#include <linux/hsi/hsi_char.h>
+
+#define HSI_FCK_DIV2		92500
+
+#define HSI_CHAR_CHANNELS	8
+#define HSI_CHAR_DEVS		8
+#define HSI_CHAR_MSGS		4
+
+#define HSI_CHST_UNAVAIL	0 /* SBZ! */
+#define HSI_CHST_AVAIL		1
+
+#define HSI_CHST_CLOSED		(0 << 4)
+#define HSI_CHST_CLOSING	(1 << 4)
+#define HSI_CHST_OPENING	(2 << 4)
+#define HSI_CHST_OPENED		(3 << 4)
+
+#define HSI_CHST_READOFF	(0 << 8)
+#define HSI_CHST_READON		(1 << 8)
+#define HSI_CHST_READING	(2 << 8)
+
+#define HSI_CHST_WRITEOFF	(0 << 12)
+#define HSI_CHST_WRITEON	(1 << 12)
+#define HSI_CHST_WRITING	(2 << 12)
+
+#define HSI_CHST_OC_MASK	0xf0
+#define HSI_CHST_RD_MASK	0xf00
+#define HSI_CHST_WR_MASK	0xf000
+
+#define HSI_CHST_OC(c)		((c)->state & HSI_CHST_OC_MASK)
+#define HSI_CHST_RD(c)		((c)->state & HSI_CHST_RD_MASK)
+#define HSI_CHST_WR(c)		((c)->state & HSI_CHST_WR_MASK)
+
+#define HSI_CHST_OC_SET(c, v) \
+	do { \
+		(c)->state &= ~HSI_CHST_OC_MASK; \
+		(c)->state |= v; \
+	} while (0);
+
+#define HSI_CHST_RD_SET(c, v) \
+	do { \
+		(c)->state &= ~HSI_CHST_RD_MASK; \
+		(c)->state |= v; \
+	} while (0);
+
+#define HSI_CHST_WR_SET(c, v) \
+	do { \
+		(c)->state &= ~HSI_CHST_WR_MASK; \
+		(c)->state |= v; \
+	} while (0);
+
+#define HSI_CHAR_POLL_RST	(-1)
+#define HSI_CHAR_POLL_OFF	0
+#define HSI_CHAR_POLL_ON	1
+
+struct hsi_char_channel {
+	int ch;
+	unsigned int state;
+	int wlrefcnt;
+	int rxpoll;
+	struct hsi_client *cl;
+	struct list_head free_msgs_list;
+	struct list_head rx_msgs_queue;
+	struct list_head tx_msgs_queue;
+	int poll_event;
+	spinlock_t lock;
+	struct fasync_struct *async_queue;
+	wait_queue_head_t rx_wait;
+	wait_queue_head_t tx_wait;
+};
+
+struct hsi_char_client_data {
+	atomic_t refcnt;
+	int attached;
+	atomic_t breq;
+	struct hsi_char_channel channels[HSI_CHAR_DEVS];
+};
+
+static unsigned int max_data_size = 0x1000;
+module_param(max_data_size, uint, 1);
+MODULE_PARM_DESC(max_data_size, "max read/write data size [4,8..65536] (^2)");
+
+static int channels_map[HSI_CHAR_DEVS] = {0, -1, -1 , -1, -1, -1, -1, -1};
+module_param_array(channels_map, int, NULL, 0);
+MODULE_PARM_DESC(channels_map, "Array of HSI channels ([0...7]) to be probed");
+
+static dev_t hsi_char_dev;
+static struct hsi_char_client_data hsi_char_cl_data;
+
+static int hsi_char_rx_poll(struct hsi_char_channel *channel);
+
+static int __devinit hsi_char_probe(struct device *dev)
+{
+	struct hsi_char_client_data *cl_data = &hsi_char_cl_data;
+	struct hsi_char_channel *channel = cl_data->channels;
+	struct hsi_client *cl = to_hsi_client(dev);
+	int i;
+
+	for (i = 0; i < HSI_CHAR_DEVS; i++) {
+		if (channel->state == HSI_CHST_AVAIL)
+			channel->cl = cl;
+		channel++;
+	}
+	cl->hsi_start_rx = NULL;
+	cl->hsi_stop_rx = NULL;
+	atomic_set(&cl_data->refcnt, 0);
+	atomic_set(&cl_data->breq, 1);
+	cl_data->attached = 0;
+	hsi_client_set_drvdata(cl, cl_data);
+
+	return 0;
+}
+
+static int __devexit hsi_char_remove(struct device *dev)
+{
+	struct hsi_client *cl = to_hsi_client(dev);
+	struct hsi_char_client_data *cl_data = hsi_client_drvdata(cl);
+	struct hsi_char_channel *channel = cl_data->channels;
+	int i;
+
+	for (i = 0; i < HSI_CHAR_DEVS; i++) {
+		if (!(channel->state & HSI_CHST_AVAIL))
+			continue;
+		if (cl_data->attached) {
+			hsi_release_port(channel->cl);
+			cl_data->attached = 0;
+		}
+		channel->state = HSI_CHST_UNAVAIL;
+		channel->cl = NULL;
+		channel++;
+	}
+
+	return 0;
+}
+
+static int hsi_char_fasync(int fd, struct file *file, int on)
+{
+	struct hsi_char_channel *channel = file->private_data;
+
+	if (fasync_helper(fd, file, on, &channel->async_queue) < 0)
+		return -EIO;
+
+	return 0;
+}
+
+static unsigned int hsi_char_poll(struct file *file, poll_table *wait)
+{
+	struct hsi_char_channel *channel = file->private_data;
+	unsigned int ret;
+
+	spin_lock_bh(&channel->lock);
+	poll_wait(file, &channel->rx_wait, wait);
+	poll_wait(file, &channel->tx_wait, wait);
+	ret = channel->poll_event;
+	spin_unlock_bh(&channel->lock);
+	hsi_char_rx_poll(channel);
+
+	return ret;
+}
+
+static inline void hsi_char_msg_len_set(struct hsi_msg *msg, unsigned int len)
+{
+	msg->sgt.sgl->length = len;
+}
+
+static inline unsigned int hsi_char_msg_len_get(struct hsi_msg *msg)
+{
+	return msg->sgt.sgl->length;
+}
+
+static void hsi_char_data_available(struct hsi_msg *msg)
+{
+	struct hsi_char_client_data *cl_data = hsi_client_drvdata(msg->cl);
+	struct hsi_char_channel *channel = cl_data->channels + msg->channel;
+	int ret;
+
+	if (msg->status == HSI_STATUS_ERROR) {
+		ret = hsi_async_read(channel->cl, msg);
+		if (ret < 0) {
+			list_add_tail(&msg->link, &channel->free_msgs_list);
+			spin_lock_bh(&channel->lock);
+			list_add_tail(&msg->link, &channel->free_msgs_list);
+			channel->rxpoll = HSI_CHAR_POLL_OFF;
+			spin_unlock_bh(&channel->lock);
+		}
+	} else {
+		spin_lock_bh(&channel->lock);
+		channel->rxpoll = HSI_CHAR_POLL_OFF;
+		channel->poll_event |= (POLLIN | POLLRDNORM);
+		spin_unlock_bh(&channel->lock);
+		spin_lock_bh(&channel->lock);
+		list_add_tail(&msg->link, &channel->free_msgs_list);
+		spin_unlock_bh(&channel->lock);
+		wake_up_interruptible(&channel->rx_wait);
+	}
+}
+
+static void hsi_char_rx_poll_destructor(struct hsi_msg *msg)
+{
+	struct hsi_char_client_data *cl_data = hsi_client_drvdata(msg->cl);
+	struct hsi_char_channel *channel = cl_data->channels + msg->channel;
+
+	spin_lock_bh(&channel->lock);
+	list_add_tail(&msg->link, &channel->free_msgs_list);
+	channel->rxpoll = HSI_CHAR_POLL_RST;
+	spin_unlock_bh(&channel->lock);
+}
+
+static int hsi_char_rx_poll(struct hsi_char_channel *channel)
+{
+	struct hsi_msg *msg;
+	int ret = 0;
+
+	spin_lock_bh(&channel->lock);
+	if (list_empty(&channel->free_msgs_list)) {
+		ret = -ENOMEM;
+		goto out;
+	}
+	if (channel->rxpoll == HSI_CHAR_POLL_ON)
+		goto out;
+	msg = list_first_entry(&channel->free_msgs_list, struct hsi_msg, link);
+	list_del(&msg->link);
+	channel->rxpoll = HSI_CHAR_POLL_ON;
+	spin_unlock_bh(&channel->lock);
+	hsi_char_msg_len_set(msg, 0);
+	msg->complete = hsi_char_data_available;
+	msg->destructor = hsi_char_rx_poll_destructor;
+	/* don't touch msg->context! */
+	ret = hsi_async_read(channel->cl, msg);
+	spin_lock_bh(&channel->lock);
+	if (ret < 0) {
+		list_add_tail(&msg->link, &channel->free_msgs_list);
+		channel->rxpoll = HSI_CHAR_POLL_OFF;
+		goto out;
+	}
+out:
+	spin_unlock_bh(&channel->lock);
+
+	return ret;
+}
+
+static void hsi_char_rx_poll_rst(struct hsi_client *cl)
+{
+	struct hsi_char_client_data *cl_data = hsi_client_drvdata(cl);
+	struct hsi_char_channel *channel = cl_data->channels;
+	int i;
+
+	for (i = 0; i < HSI_CHAR_DEVS; i++) {
+		if ((HSI_CHST_OC(channel) == HSI_CHST_OPENED) &&
+			(channel->rxpoll == HSI_CHAR_POLL_RST))
+			hsi_char_rx_poll(channel);
+		channel++;
+	}
+}
+
+static void hsi_char_rx_completed(struct hsi_msg *msg)
+{
+	struct hsi_char_client_data *cl_data = hsi_client_drvdata(msg->cl);
+	struct hsi_char_channel *channel = cl_data->channels + msg->channel;
+
+	spin_lock_bh(&channel->lock);
+	list_add_tail(&msg->link, &channel->rx_msgs_queue);
+	spin_unlock_bh(&channel->lock);
+	wake_up_interruptible(&channel->rx_wait);
+}
+
+static void hsi_char_rx_msg_destructor(struct hsi_msg *msg)
+{
+	struct hsi_char_client_data *cl_data = hsi_client_drvdata(msg->cl);
+	struct hsi_char_channel *channel = cl_data->channels + msg->channel;
+
+	spin_lock_bh(&channel->lock);
+	list_add_tail(&msg->link, &channel->free_msgs_list);
+	HSI_CHST_RD_SET(channel, HSI_CHST_READOFF);
+	spin_unlock_bh(&channel->lock);
+}
+
+static void hsi_char_rx_cancel(struct hsi_char_channel *channel)
+{
+	hsi_flush(channel->cl);
+	hsi_char_rx_poll_rst(channel->cl);
+}
+
+static void hsi_char_tx_completed(struct hsi_msg *msg)
+{
+	struct hsi_char_client_data *cl_data = hsi_client_drvdata(msg->cl);
+	struct hsi_char_channel *channel = cl_data->channels + msg->channel;
+
+	spin_lock_bh(&channel->lock);
+	list_add_tail(&msg->link, &channel->tx_msgs_queue);
+	spin_unlock_bh(&channel->lock);
+	wake_up_interruptible(&channel->tx_wait);
+}
+
+static void hsi_char_tx_msg_destructor(struct hsi_msg *msg)
+{
+	struct hsi_char_client_data *cl_data = hsi_client_drvdata(msg->cl);
+	struct hsi_char_channel *channel = cl_data->channels + msg->channel;
+
+	spin_lock_bh(&channel->lock);
+	list_add_tail(&msg->link, &channel->free_msgs_list);
+	HSI_CHST_WR_SET(channel, HSI_CHST_WRITEOFF);
+	spin_unlock_bh(&channel->lock);
+}
+
+static void hsi_char_tx_cancel(struct hsi_char_channel *channel)
+{
+	hsi_flush(channel->cl);
+	hsi_char_rx_poll_rst(channel->cl);
+}
+
+static ssize_t hsi_char_read(struct file *file, char __user *buf,
+				size_t len, loff_t *ppos)
+{
+	struct hsi_char_channel *channel = file->private_data;
+	struct hsi_msg *msg = NULL;
+	ssize_t ret;
+
+	if (len == 0) {
+		channel->poll_event &= ~POLLPRI;
+		return 0;
+	}
+	channel->poll_event &= ~POLLPRI;
+
+	if (!IS_ALIGNED(len, sizeof(u32)))
+		return -EINVAL;
+
+	if (len > max_data_size)
+		len = max_data_size;
+
+	spin_lock_bh(&channel->lock);
+	if (HSI_CHST_RD(channel) != HSI_CHST_READOFF) {
+		ret = -EBUSY;
+		goto out;
+	}
+	if (list_empty(&channel->free_msgs_list)) {
+		ret = -ENOMEM;
+		goto out;
+	}
+	msg = list_first_entry(&channel->free_msgs_list, struct hsi_msg, link);
+	list_del(&msg->link);
+	spin_unlock_bh(&channel->lock);
+	hsi_char_msg_len_set(msg, len);
+	msg->complete = hsi_char_rx_completed;
+	msg->destructor = hsi_char_rx_msg_destructor;
+	ret = hsi_async_read(channel->cl, msg);
+	spin_lock_bh(&channel->lock);
+	if (ret < 0)
+		goto out;
+	HSI_CHST_RD_SET(channel, HSI_CHST_READING);
+	msg = NULL;
+
+	for ( ; ; ) {
+		DEFINE_WAIT(wait);
+
+		if (!list_empty(&channel->rx_msgs_queue)) {
+			msg = list_first_entry(&channel->rx_msgs_queue,
+					struct hsi_msg, link);
+			HSI_CHST_RD_SET(channel, HSI_CHST_READOFF);
+			channel->poll_event &= ~(POLLIN | POLLRDNORM);
+			list_del(&msg->link);
+			spin_unlock_bh(&channel->lock);
+			if (msg->status == HSI_STATUS_ERROR) {
+				ret = -EIO;
+			} else {
+				ret = copy_to_user((void __user *)buf,
+						msg->context,
+						hsi_char_msg_len_get(msg));
+				if (ret)
+					ret = -EFAULT;
+				else
+					ret = hsi_char_msg_len_get(msg);
+			}
+			spin_lock_bh(&channel->lock);
+			break;
+		} else if (signal_pending(current)) {
+			spin_unlock_bh(&channel->lock);
+			hsi_char_rx_cancel(channel);
+			spin_lock_bh(&channel->lock);
+			HSI_CHST_RD_SET(channel, HSI_CHST_READOFF);
+			ret = -EINTR;
+			break;
+		}
+
+		prepare_to_wait(&channel->rx_wait, &wait, TASK_INTERRUPTIBLE);
+		spin_unlock_bh(&channel->lock);
+
+		schedule();
+
+		spin_lock_bh(&channel->lock);
+		finish_wait(&channel->rx_wait, &wait);
+	}
+out:
+	if (msg)
+		list_add_tail(&msg->link, &channel->free_msgs_list);
+	spin_unlock_bh(&channel->lock);
+
+	return ret;
+}
+
+static ssize_t hsi_char_write(struct file *file, const char __user *buf,
+				size_t len, loff_t *ppos)
+{
+	struct hsi_char_channel *channel = file->private_data;
+	struct hsi_msg *msg = NULL;
+	ssize_t ret;
+
+	if ((len == 0) || !IS_ALIGNED(len, sizeof(u32)))
+		return -EINVAL;
+
+	if (len > max_data_size)
+		len = max_data_size;
+
+	spin_lock_bh(&channel->lock);
+	if (HSI_CHST_WR(channel) != HSI_CHST_WRITEOFF) {
+		ret = -EBUSY;
+		goto out;
+	}
+	if (list_empty(&channel->free_msgs_list)) {
+		ret = -ENOMEM;
+		goto out;
+	}
+	msg = list_first_entry(&channel->free_msgs_list, struct hsi_msg, link);
+	list_del(&msg->link);
+	HSI_CHST_WR_SET(channel, HSI_CHST_WRITEON);
+	spin_unlock_bh(&channel->lock);
+
+	if (copy_from_user(msg->context, (void __user *)buf, len)) {
+		spin_lock_bh(&channel->lock);
+		HSI_CHST_WR_SET(channel, HSI_CHST_WRITEOFF);
+		ret = -EFAULT;
+		goto out;
+	}
+
+	hsi_char_msg_len_set(msg, len);
+	msg->complete = hsi_char_tx_completed;
+	msg->destructor = hsi_char_tx_msg_destructor;
+	ret = hsi_async_write(channel->cl, msg);
+	spin_lock_bh(&channel->lock);
+	if (ret < 0) {
+		HSI_CHST_WR_SET(channel, HSI_CHST_WRITEOFF);
+		goto out;
+	}
+	HSI_CHST_WR_SET(channel, HSI_CHST_WRITING);
+	channel->poll_event &= ~(POLLOUT | POLLWRNORM);
+	msg = NULL;
+
+	for ( ; ; ) {
+		DEFINE_WAIT(wait);
+
+		if (!list_empty(&channel->tx_msgs_queue)) {
+			msg = list_first_entry(&channel->tx_msgs_queue,
+					struct hsi_msg, link);
+			list_del(&msg->link);
+			HSI_CHST_WR_SET(channel, HSI_CHST_WRITEOFF);
+			channel->poll_event |= (POLLOUT | POLLWRNORM);
+			if (msg->status == HSI_STATUS_ERROR)
+				ret = -EIO;
+			else
+				ret = hsi_char_msg_len_get(msg);
+			break;
+		} else if (signal_pending(current)) {
+			spin_unlock_bh(&channel->lock);
+			hsi_char_tx_cancel(channel);
+			spin_lock_bh(&channel->lock);
+			HSI_CHST_WR_SET(channel, HSI_CHST_WRITEOFF);
+			ret = -EINTR;
+			break;
+		}
+		prepare_to_wait(&channel->tx_wait, &wait, TASK_INTERRUPTIBLE);
+		spin_unlock_bh(&channel->lock);
+
+		schedule();
+
+		spin_lock_bh(&channel->lock);
+		finish_wait(&channel->tx_wait, &wait);
+	}
+out:
+	if (msg)
+		list_add_tail(&msg->link, &channel->free_msgs_list);
+
+	spin_unlock_bh(&channel->lock);
+
+	return ret;
+}
+
+static void hsi_char_bcast_break(struct hsi_client *cl)
+{
+	struct hsi_char_client_data *cl_data = hsi_client_drvdata(cl);
+	struct hsi_char_channel *channel = cl_data->channels;
+	int i;
+
+	for (i = 0; i < HSI_CHAR_DEVS; i++) {
+		if (HSI_CHST_OC(channel) != HSI_CHST_OPENED)
+			continue;
+		channel->poll_event |= POLLPRI;
+		wake_up_interruptible(&channel->rx_wait);
+		wake_up_interruptible(&channel->tx_wait);
+		channel++;
+	}
+}
+
+static void hsi_char_break_received(struct hsi_msg *msg)
+{
+	struct hsi_char_client_data *cl_data = hsi_client_drvdata(msg->cl);
+	int ret;
+
+	hsi_char_bcast_break(msg->cl);
+	ret = hsi_async_read(msg->cl, msg);
+	if (ret < 0) {
+		hsi_free_msg(msg);
+		atomic_inc(&cl_data->breq);
+	}
+}
+
+static void hsi_char_break_req_destructor(struct hsi_msg *msg)
+{
+	struct hsi_char_client_data *cl_data = hsi_client_drvdata(msg->cl);
+
+	hsi_free_msg(msg);
+	atomic_inc(&cl_data->breq);
+}
+
+static int hsi_char_break_request(struct hsi_client *cl)
+{
+	struct hsi_char_client_data *cl_data = hsi_client_drvdata(cl);
+	struct hsi_msg *msg;
+	int ret = 0;
+
+	if (!atomic_dec_and_test(&cl_data->breq)) {
+		atomic_inc(&cl_data->breq);
+		return -EBUSY;
+	}
+	msg = hsi_alloc_msg(0, GFP_KERNEL);
+	if (!msg)
+		return -ENOMEM;
+	msg->break_frame = 1;
+	msg->complete = hsi_char_break_received;
+	msg->destructor = hsi_char_break_req_destructor;
+	ret = hsi_async_read(cl, msg);
+	if (ret < 0)
+		hsi_free_msg(msg);
+
+	return ret;
+}
+
+static int hsi_char_break_send(struct hsi_client *cl)
+{
+	struct hsi_msg *msg;
+	int ret = 0;
+
+	msg = hsi_alloc_msg(0, GFP_ATOMIC);
+	if (!msg)
+		return -ENOMEM;
+	msg->break_frame = 1;
+	msg->complete = hsi_free_msg;
+	msg->destructor = hsi_free_msg;
+	ret = hsi_async_write(cl, msg);
+	if (ret < 0)
+		hsi_free_msg(msg);
+
+	return ret;
+}
+
+static void hsi_char_reset(struct hsi_client *cl)
+{
+	hsi_flush(cl);
+	hsi_char_rx_poll_rst(cl);
+}
+
+#define HSI_CHAR_RX	0
+#define HSI_CHAR_TX	1
+
+static inline int ssi_check_common_cfg(struct hsi_config *cfg)
+{
+	if ((cfg->mode != HSI_MODE_STREAM) && (cfg->mode != HSI_MODE_FRAME))
+		return -EINVAL;
+	if ((cfg->channels == 0) || (cfg->channels > HSI_CHAR_CHANNELS))
+		return -EINVAL;
+	if (cfg->channels & (cfg->channels - 1))
+		return -EINVAL;
+	if ((cfg->flow != HSI_FLOW_SYNC) && (cfg->flow != HSI_FLOW_PIPE))
+		return -EINVAL;
+
+	return 0;
+}
+
+static inline int ssi_check_rx_cfg(struct hsi_config *cfg)
+{
+	return ssi_check_common_cfg(cfg);
+}
+
+static inline int ssi_check_tx_cfg(struct hsi_config *cfg)
+{
+	int ret = ssi_check_common_cfg(cfg);
+
+	if (ret < 0)
+		return ret;
+	if ((cfg->arb_mode != HSI_ARB_RR) && (cfg->arb_mode != HSI_ARB_PRIO))
+		return -EINVAL;
+
+	return 0;
+}
+
+static inline int hsi_char_cfg_set(struct hsi_client *cl,
+	struct hsi_config *cfg, int dir)
+{
+	struct hsi_config *rxtx_cfg;
+	int ret = 0;
+
+	if (dir == HSI_CHAR_RX) {
+		rxtx_cfg = &cl->rx_cfg;
+		ret = ssi_check_rx_cfg(cfg);
+	} else {
+		rxtx_cfg = &cl->tx_cfg;
+		ret = ssi_check_tx_cfg(cfg);
+	}
+	if (ret < 0)
+		return ret;
+
+	*rxtx_cfg = *cfg;
+	ret = hsi_setup(cl);
+	if (ret < 0)
+		return ret;
+
+	if ((dir == HSI_CHAR_RX) && (cfg->mode == HSI_MODE_FRAME))
+		hsi_char_break_request(cl);
+
+	return ret;
+}
+
+static inline void hsi_char_cfg_get(struct hsi_client *cl,
+	struct hsi_config *cfg, int dir)
+{
+	struct hsi_config *rxtx_cfg;
+
+	if (dir == HSI_CHAR_RX)
+		rxtx_cfg = &cl->rx_cfg;
+	else
+		rxtx_cfg = &cl->tx_cfg;
+	*cfg = *rxtx_cfg;
+}
+
+static inline int hsi_char_rx_cfgo_set(struct hsi_client *cl,
+	struct ssi_rx_config *rx_cfg)
+{
+	struct hsi_config cfg;
+
+	cfg.mode = rx_cfg->mode;
+	cfg.flow = HSI_FLOW_SYNC;
+	cfg.channels = rx_cfg->channels;
+	cfg.speed = 0;
+	cfg.arb_mode = 0;
+
+	return hsi_char_cfg_set(cl, &cfg, HSI_CHAR_RX);
+}
+
+static inline void hsi_char_rx_cfgo_get(struct hsi_client *cl,
+	struct ssi_rx_config *rx_cfg)
+{
+	rx_cfg->mode = cl->rx_cfg.mode;
+	rx_cfg->frame_size = 31;
+	rx_cfg->channels = cl->rx_cfg.channels;
+	rx_cfg->timeout = 0;
+}
+
+static inline int hsi_char_tx_cfgo_set(struct hsi_client *cl,
+	struct ssi_tx_config *tx_cfg)
+{
+	struct hsi_config cfg;
+
+	cfg.mode = tx_cfg->mode;
+	cfg.flow = HSI_FLOW_SYNC;
+	cfg.channels = tx_cfg->channels;
+	if (tx_cfg->divisor == 0)
+		cfg.speed = HSI_FCK_DIV2 + 10000;
+	else
+		cfg.speed = (HSI_FCK_DIV2 - 1) / tx_cfg->divisor;
+	cfg.arb_mode = tx_cfg->arb_mode;
+
+	return hsi_char_cfg_set(cl, &cfg, HSI_CHAR_TX);
+}
+
+static inline void hsi_char_tx_cfgo_get(struct hsi_client *cl,
+	struct ssi_tx_config *tx_cfg)
+{
+	tx_cfg->mode = cl->tx_cfg.mode;
+	tx_cfg->frame_size = 31;
+	tx_cfg->channels = cl->tx_cfg.channels;
+	tx_cfg->divisor = HSI_FCK_DIV2 / cl->tx_cfg.speed;
+	tx_cfg->arb_mode = cl->tx_cfg.arb_mode;
+}
+
+static int hsi_char_ioctl(struct inode *inode, struct file *file,
+				unsigned int cmd, unsigned long arg)
+{
+	struct hsi_char_channel *channel = file->private_data;
+	unsigned int state;
+	struct ssi_rx_config rx_cfg;
+	struct ssi_tx_config tx_cfg;
+	struct hsi_config cfg;
+	int ret = 0, dir = HSI_CHAR_TX;
+
+	if (HSI_CHST_OC(channel) != HSI_CHST_OPENED)
+		return -EINVAL;
+
+	switch (cmd) {
+	case CS_SEND_BREAK:
+		return hsi_char_break_send(channel->cl);
+	case CS_FLUSH_RX:
+	case CS_FLUSH_TX:
+		break;
+	case CS_RESET:
+		hsi_char_reset(channel->cl);
+		break;
+	case CS_SET_PM:
+		if (copy_from_user(&state, (void __user *)arg, sizeof(state)))
+			return -EFAULT;
+		if (state == HSI_CHAR_PM_DISABLE) {
+			ret = hsi_start_tx(channel->cl);
+			if (!ret)
+				channel->wlrefcnt++;
+		} else if ((state == HSI_CHAR_PM_ENABLE)
+				&& (channel->wlrefcnt > 0)) {
+			ret = hsi_stop_tx(channel->cl);
+			if (!ret)
+				channel->wlrefcnt--;
+		} else {
+			ret = -EINVAL;
+		}
+		break;
+	case CS_SET_RX:
+		if (copy_from_user(&rx_cfg, (void __user *)arg, sizeof(rx_cfg)))
+			return -EFAULT;
+		return hsi_char_rx_cfgo_set(channel->cl, &rx_cfg);
+	case CS_GET_RX:
+		hsi_char_rx_cfgo_get(channel->cl, &rx_cfg);
+		if (copy_to_user((void __user *)arg, &rx_cfg, sizeof(rx_cfg)))
+			return -EFAULT;
+		break;
+	case CS_SET_TX:
+		if (copy_from_user(&tx_cfg, (void __user *)arg, sizeof(tx_cfg)))
+			return -EFAULT;
+		return hsi_char_tx_cfgo_set(channel->cl, &tx_cfg);
+	case CS_GET_TX:
+		hsi_char_tx_cfgo_get(channel->cl, &tx_cfg);
+		if (copy_to_user((void __user *)arg, &tx_cfg, sizeof(tx_cfg)))
+			return -EFAULT;
+		break;
+	case CS_SET_RX_CFG:
+		dir = HSI_CHAR_RX;
+	case CS_SET_TX_CFG:
+		if (copy_from_user(&cfg, (void __user *)arg, sizeof(cfg)))
+			return -EFAULT;
+		return hsi_char_cfg_set(channel->cl, &cfg, dir);
+	case CS_GET_RX_CFG:
+		dir = HSI_CHAR_RX;
+	case CS_GET_TX_CFG:
+		hsi_char_cfg_get(channel->cl, &cfg, dir);
+		if (copy_to_user((void __user *)arg, &cfg, sizeof(cfg)))
+			return -EFAULT;
+	default:
+		return -ENOIOCTLCMD;
+	}
+
+	return ret;
+}
+
+static inline struct hsi_msg *hsi_char_msg_alloc(unsigned int alloc_size)
+{
+	struct hsi_msg *msg;
+	void *buf;
+
+	msg = hsi_alloc_msg(1, GFP_KERNEL);
+	if (!msg)
+		goto out;
+	buf = kmalloc(alloc_size, GFP_KERNEL);
+	if (!buf) {
+		hsi_free_msg(msg);
+		goto out;
+	}
+	sg_init_one(msg->sgt.sgl, buf, alloc_size);
+	msg->context = buf;
+	return msg;
+out:
+	return NULL;
+}
+
+static inline void hsi_char_msg_free(struct hsi_msg *msg)
+{
+	msg->complete = NULL;
+	msg->destructor = NULL;
+	kfree(sg_virt(msg->sgt.sgl));
+	hsi_free_msg(msg);
+}
+
+static inline void hsi_char_msgs_free(struct hsi_char_channel *channel)
+{
+	struct hsi_msg *msg, *tmp;
+
+	list_for_each_entry_safe(msg, tmp, &channel->free_msgs_list, link) {
+		list_del(&msg->link);
+		hsi_char_msg_free(msg);
+	}
+	list_for_each_entry_safe(msg, tmp, &channel->rx_msgs_queue, link) {
+		list_del(&msg->link);
+		hsi_char_msg_free(msg);
+	}
+	list_for_each_entry_safe(msg, tmp, &channel->tx_msgs_queue, link) {
+		list_del(&msg->link);
+		hsi_char_msg_free(msg);
+	}
+}
+
+static inline int hsi_char_msgs_alloc(struct hsi_char_channel *channel)
+{
+	struct hsi_msg *msg;
+	int i;
+
+	for (i = 0; i < HSI_CHAR_MSGS; i++) {
+		msg = hsi_char_msg_alloc(max_data_size);
+		if (!msg)
+			goto out;
+		msg->channel = channel->ch;
+		list_add_tail(&msg->link, &channel->free_msgs_list);
+	}
+	return 0;
+out:
+	hsi_char_msgs_free(channel);
+
+	return -ENOMEM;
+}
+
+static int hsi_char_open(struct inode *inode, struct file *file)
+{
+	struct hsi_char_client_data *cl_data = &hsi_char_cl_data;
+	struct hsi_char_channel *channel = cl_data->channels + iminor(inode);
+	int ret = 0, refcnt;
+
+	if (channel->state == HSI_CHST_UNAVAIL)
+		return -ENODEV;
+
+	spin_lock_bh(&channel->lock);
+	if (HSI_CHST_OC(channel) != HSI_CHST_CLOSED) {
+		ret = -EBUSY;
+		goto out;
+	}
+	HSI_CHST_OC_SET(channel, HSI_CHST_OPENING);
+	spin_unlock_bh(&channel->lock);
+
+	refcnt = atomic_inc_return(&cl_data->refcnt);
+	if (refcnt == 1) {
+		if (cl_data->attached) {
+			atomic_dec(&cl_data->refcnt);
+			spin_lock_bh(&channel->lock);
+			HSI_CHST_OC_SET(channel, HSI_CHST_CLOSED);
+			ret = -EBUSY;
+			goto out;
+		}
+		ret = hsi_claim_port(channel->cl, 0);
+		if (ret < 0) {
+			atomic_dec(&cl_data->refcnt);
+			spin_lock_bh(&channel->lock);
+			HSI_CHST_OC_SET(channel, HSI_CHST_CLOSED);
+			goto out;
+		}
+		hsi_setup(channel->cl);
+	} else if (!cl_data->attached) {
+		atomic_dec(&cl_data->refcnt);
+		spin_lock_bh(&channel->lock);
+		HSI_CHST_OC_SET(channel, HSI_CHST_CLOSED);
+		ret = -ENODEV;
+		goto out;
+	}
+	ret = hsi_char_msgs_alloc(channel);
+
+	if (ret < 0) {
+		refcnt = atomic_dec_return(&cl_data->refcnt);
+		if (!refcnt)
+			hsi_release_port(channel->cl);
+		spin_lock_bh(&channel->lock);
+		HSI_CHST_OC_SET(channel, HSI_CHST_CLOSED);
+		goto out;
+	}
+	if (refcnt == 1)
+		cl_data->attached = 1;
+	channel->wlrefcnt = 0;
+	channel->rxpoll = HSI_CHAR_POLL_OFF;
+	channel->poll_event = (POLLOUT | POLLWRNORM);
+	file->private_data = channel;
+	spin_lock_bh(&channel->lock);
+	HSI_CHST_OC_SET(channel, HSI_CHST_OPENED);
+out:
+	spin_unlock_bh(&channel->lock);
+
+	return ret;
+}
+
+static int hsi_char_release(struct inode *inode, struct file *file)
+{
+	struct hsi_char_channel *channel = file->private_data;
+	struct hsi_char_client_data *cl_data = hsi_client_drvdata(channel->cl);
+	int ret = 0, refcnt;
+
+	spin_lock_bh(&channel->lock);
+	if (HSI_CHST_OC(channel) != HSI_CHST_OPENED)
+		goto out;
+	HSI_CHST_OC_SET(channel, HSI_CHST_CLOSING);
+	spin_unlock_bh(&channel->lock);
+
+	hsi_flush(channel->cl);
+	while (channel->wlrefcnt > 0) {
+		hsi_stop_tx(channel->cl);
+		channel->wlrefcnt--;
+	}
+
+	refcnt = atomic_dec_return(&cl_data->refcnt);
+	if (!refcnt) {
+		hsi_release_port(channel->cl);
+		cl_data->attached = 0;
+	}
+
+	hsi_char_msgs_free(channel);
+
+	spin_lock_bh(&channel->lock);
+	HSI_CHST_OC_SET(channel, HSI_CHST_CLOSED);
+	HSI_CHST_RD_SET(channel, HSI_CHST_READOFF);
+	HSI_CHST_WR_SET(channel, HSI_CHST_WRITEOFF);
+out:
+	spin_unlock_bh(&channel->lock);
+
+	return ret;
+}
+
+static const struct file_operations hsi_char_fops = {
+	.owner		= THIS_MODULE,
+	.read		= hsi_char_read,
+	.write		= hsi_char_write,
+	.poll		= hsi_char_poll,
+	.ioctl		= hsi_char_ioctl,
+	.open		= hsi_char_open,
+	.release	= hsi_char_release,
+	.fasync		= hsi_char_fasync,
+};
+
+struct hsi_client_driver hsi_char_driver = {
+	.driver = {
+		.name = "hsi_char",
+		.owner = THIS_MODULE,
+		.probe = hsi_char_probe,
+		.remove = hsi_char_remove,
+	},
+};
+
+static inline void hsi_char_channel_init(struct hsi_char_channel *channel)
+{
+	channel->state = HSI_CHST_AVAIL;
+	INIT_LIST_HEAD(&channel->free_msgs_list);
+	init_waitqueue_head(&channel->rx_wait);
+	init_waitqueue_head(&channel->tx_wait);
+	spin_lock_init(&channel->lock);
+	INIT_LIST_HEAD(&channel->rx_msgs_queue);
+	INIT_LIST_HEAD(&channel->tx_msgs_queue);
+}
+
+static struct cdev hsi_char_cdev;
+
+static int __init hsi_char_init(void)
+{
+	char devname[] = "hsi_char";
+	struct hsi_char_client_data *cl_data = &hsi_char_cl_data;
+	struct hsi_char_channel *channel = cl_data->channels;
+	unsigned long ch_mask = 0;
+	int ret, i;
+
+	if ((max_data_size < 4) || (max_data_size > 0x10000) ||
+		(max_data_size & (max_data_size - 1))) {
+		pr_err("Invalid max read/write data size");
+		return -EINVAL;
+	}
+
+	for (i = 0; i < HSI_CHAR_DEVS && channels_map[i] >= 0; i++) {
+		if (channels_map[i] >= HSI_CHAR_DEVS) {
+			pr_err("Invalid HSI/SSI channel specified");
+			return -EINVAL;
+		}
+		set_bit(channels_map[i], &ch_mask);
+	}
+
+	if (i == 0) {
+		pr_err("No HSI channels available");
+		return -EINVAL;
+	}
+
+	memset(cl_data->channels, 0, sizeof(cl_data->channels));
+	for (i = 0; i < HSI_CHAR_DEVS; i++) {
+		channel->ch = i;
+		channel->state = HSI_CHST_UNAVAIL;
+		if (test_bit(i, &ch_mask))
+			hsi_char_channel_init(channel);
+		channel++;
+	}
+
+	ret = hsi_register_client_driver(&hsi_char_driver);
+	if (ret) {
+		pr_err("Error while registering HSI/SSI driver %d", ret);
+		return ret;
+	}
+
+	ret = alloc_chrdev_region(&hsi_char_dev, 0, HSI_CHAR_DEVS, devname);
+	if (ret < 0) {
+		hsi_unregister_client_driver(&hsi_char_driver);
+		return ret;
+	}
+
+	cdev_init(&hsi_char_cdev, &hsi_char_fops);
+	cdev_add(&hsi_char_cdev, hsi_char_dev, HSI_CHAR_DEVS);
+	pr_info("HSI/SSI char device loaded\n");
+
+	return 0;
+}
+module_init(hsi_char_init);
+
+static void __exit hsi_char_exit(void)
+{
+	cdev_del(&hsi_char_cdev);
+	unregister_chrdev_region(hsi_char_dev, HSI_CHAR_DEVS);
+	hsi_unregister_client_driver(&hsi_char_driver);
+	pr_info("HSI char device removed\n");
+}
+module_exit(hsi_char_exit);
+
+MODULE_AUTHOR("Andras Domokos <andras.domokos@nokia.com>");
+MODULE_ALIAS("hsi:hsi_char");
+MODULE_DESCRIPTION("HSI character device");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/hsi/hsi_char.h b/include/linux/hsi/hsi_char.h
new file mode 100644
index 0000000..d24e52b
--- /dev/null
+++ b/include/linux/hsi/hsi_char.h
@@ -0,0 +1,79 @@
+/*
+ * hsi_char.h
+ *
+ * Part of the HSI character device driver.
+ *
+ * Copyright (C) 2010 Nokia Corporation. All rights reserved.
+ *
+ * Contact: Andras Domokos <andras.domokos at nokia.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ */
+
+
+#ifndef __HSI_CHAR_H
+#define __HSI_CHAR_H
+
+#include <linux/hsi/hsi.h>
+
+#define HSI_CHAR_BASE		'S'
+#define CS_IOW(num, dtype)	_IOW(HSI_CHAR_BASE, num, dtype)
+#define CS_IOR(num, dtype)	_IOR(HSI_CHAR_BASE, num, dtype)
+#define CS_IOWR(num, dtype)	_IOWR(HSI_CHAR_BASE, num, dtype)
+#define CS_IO(num)		_IO(HSI_CHAR_BASE, num)
+
+#define CS_SEND_BREAK		CS_IO(1)
+#define CS_FLUSH_RX		CS_IO(2)
+#define CS_FLUSH_TX		CS_IO(3)
+#define CS_RESET		CS_IO(4)
+#define CS_SET_PM		CS_IOW(5, unsigned int)
+#define CS_SET_RX		CS_IOW(7, struct ssi_rx_config)
+#define CS_GET_RX		CS_IOW(8, struct ssi_rx_config)
+#define CS_SET_TX		CS_IOW(9, struct ssi_tx_config)
+#define CS_GET_TX		CS_IOW(10, struct ssi_tx_config)
+#define CS_SET_RX_CFG		CS_IOW(11, struct hsi_config)
+#define CS_GET_RX_CFG		CS_IOW(12, struct hsi_config)
+#define CS_SET_TX_CFG		CS_IOW(13, struct hsi_config)
+#define CS_GET_TX_CFG		CS_IOW(14, struct hsi_config)
+
+#define SSI_MODE_STREAM		1
+#define SSI_MODE_FRAME		2
+
+#define SSI_ARBMODE_RR		0
+#define SSI_ARBMODE_PRIO	1
+
+#define HSI_CHAR_PM_DISABLE	0
+#define HSI_CHAR_PM_ENABLE	1
+
+#define CS_SET_WAKELINE		CS_SET_PM
+#define WAKE_UP			HSI_CHAR_PM_DISABLE
+#define WAKE_DOWN		HSI_CHAR_PM_ENABLE
+
+struct ssi_tx_config {
+	u32 mode;
+	u32 frame_size;
+	u32 channels;
+	u32 divisor;
+	u32 arb_mode;
+};
+
+struct ssi_rx_config {
+	u32 mode;
+	u32 frame_size;
+	u32 channels;
+	u32 timeout;
+};
+
+#endif /* __HSI_CHAR_H */
-- 
1.5.6.5


  parent reply	other threads:[~2010-04-23 15:13 UTC|newest]

Thread overview: 12+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2010-04-23 15:15 [RFC PATCH 0/5] HSI framework and drivers Carlos Chinea
2010-04-23 15:15 ` [RFC PATCH 1/5] HSI: Introducing HSI framework Carlos Chinea
2010-04-23 17:36   ` Randy Dunlap
2010-04-23 15:15 ` [RFC PATCH 2/5] OMAP SSI: Introducing OMAP SSI driver Carlos Chinea
2010-04-23 15:15 ` [RFC PATCH 3/5] OMAP SSI: Add OMAP SSI to the kernel configuration Carlos Chinea
2010-04-23 15:15 ` Carlos Chinea [this message]
2010-04-23 15:15 ` [RFC PATCH 5/5] HSI CHAR: Add HSI char device " Carlos Chinea
2010-04-23 16:02 ` [RFC PATCH 0/5] HSI framework and drivers Paul Walmsley
2010-04-23 19:44   ` Kai.Vehmanen
2010-04-23 19:44     ` Kai.Vehmanen
2010-04-26  9:32     ` Carlos Chinea
2010-04-26  9:32       ` Carlos Chinea

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=1272035728-6933-5-git-send-email-carlos.chinea@nokia.com \
    --to=carlos.chinea@nokia.com \
    --cc=andras.domokos@nokia.com \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-omap@vger.kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.