commit: http://blackfin.uclinux.org/git/?p=linux-kernel;a=commitdiff;h=7518c5b209e7c5e29eed0d2ef403b20dad7202f9
branch: http://blackfin.uclinux.org/git/?p=linux-kernel;a=shortlog;h=refs/heads/trunk

draft linkport driver

Signed-off-by: Steven Miao <[email protected]>
Signed-off-by: Bob Liu <[email protected]>
---
 drivers/char/Kconfig   |   13 +
 drivers/char/Makefile  |    1 +
 drivers/char/bfin_lp.c |  606 ++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 620 insertions(+), 0 deletions(-)

diff --git a/drivers/char/Kconfig b/drivers/char/Kconfig
index 661d3be..d5fe016 100644
--- a/drivers/char/Kconfig
+++ b/drivers/char/Kconfig
@@ -56,6 +56,19 @@ config BFIN_SPORT
 
 	  If unsure, then say N.
 
+config BFIN_LINKPORT
+	tristate "Blackfin LINKPORT driver"
+	depends on BLACKFIN
+	default n
+	help
+	  This is a generic character interface to the LINKPORT peripheral that
+	  exists on bf609 and above.  It is a standalone driver that is
+	  only used for direct userspace access to the device and is not used
+	  in conjunction with any other driver.
+
+	  If unsure, then say N.
+
+
 config DEVKMEM
 	bool "/dev/kmem virtual device support"
 	default y
diff --git a/drivers/char/Makefile b/drivers/char/Makefile
index d2cafcd..79ff748 100644
--- a/drivers/char/Makefile
+++ b/drivers/char/Makefile
@@ -23,6 +23,7 @@ obj-$(CONFIG_BFIN_PPI)		+= bfin_ppi.o
 obj-$(CONFIG_BFIN_SIMPLE_TIMER)	+= bfin_simple_timer.o
 obj-$(CONFIG_BFIN_CRC)		+= bfin_crc.o
 obj-$(CONFIG_BFIN_SPORT)	+= bfin_sport.o
+obj-$(CONFIG_BFIN_LINKPORT)	+= bfin_lp.o
 
 obj-$(CONFIG_PRINTER)		+= lp.o
 
diff --git a/drivers/char/bfin_lp.c b/drivers/char/bfin_lp.c
new file mode 100644
index 0000000..e7c2103
--- /dev/null
+++ b/drivers/char/bfin_lp.c
@@ -0,0 +1,606 @@
+/*
+ * Blackfin Linkport driver
+ *
+ * Copyright 2012-2013 Analog Devices Inc.
+ *
+ * Licensed under the GPL-2 or later.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/sched.h>
+#include <linux/wait.h>
+#include <linux/poll.h>
+#include <linux/errno.h>
+#include <linux/fs.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/string.h>
+#include <linux/spinlock.h>
+#include <linux/delay.h>
+#include <linux/cdev.h>
+#include <linux/device.h>
+#include <linux/slab.h>
+#include <linux/kfifo.h>
+
+#include <asm/irq.h>
+#include <asm/blackfin.h>
+#include <asm/dma.h>
+#include <asm/cacheflush.h>
+#include <asm/portmux.h>
+
+#include <asm/gptimers.h>
+
+#define LINKPORT_DRVNAME "bfin-linkport"
+
+#define BFIN_LP_DMA_MODE
+
+#define LP_CTL_EN 0x1
+#define LP_CTL_TRAN 0x8
+#define LP_CTL_TRQMSK  0x10
+#define LP_CTL_RRQMSK  0x20
+#define LP_CTL_ITMSK  0x80
+
+#define LP_STAT_LTRQ 0x1
+#define LP_STAT_LRRQ 0x2
+#define LP_STAT_LPIT 0x8
+#define LP_STAT_FFST 0x70
+#define LP_STAT_LERR 0x80
+#define LP_STAT_LPBS 0x100
+
+struct bfin_lp_register {
+	uint32_t ctl;
+	uint32_t stat;
+	uint32_t div;
+	uint32_t cnt;
+	uint32_t tx;
+	uint32_t rx;
+	uint32_t tx_shadow;
+	uint32_t rx_shadow;
+};
+
+static const unsigned short per_req_lp0[] = {
+	P_LP0_CLK,
+	P_LP0_ACK,
+	P_LP0_D0,
+	P_LP0_D1,
+	P_LP0_D2,
+	P_LP0_D3,
+	P_LP0_D4,
+	P_LP0_D5,
+	P_LP0_D6,
+	P_LP0_D7,
+	0
+};
+
+static const unsigned short per_req_lp1[] = {
+	P_LP1_CLK,
+	P_LP1_ACK,
+	P_LP1_D0,
+	P_LP1_D1,
+	P_LP1_D2,
+	P_LP1_D3,
+	P_LP1_D4,
+	P_LP1_D5,
+	P_LP1_D6,
+	P_LP1_D7,
+	0
+};
+
+static const unsigned short per_req_lp2[] = {
+	P_LP2_CLK,
+	P_LP2_ACK,
+	P_LP2_D0,
+	P_LP2_D1,
+	P_LP2_D2,
+	P_LP2_D3,
+	P_LP2_D4,
+	P_LP2_D5,
+	P_LP2_D6,
+	P_LP2_D7,
+	0
+};
+
+static const unsigned short per_req_lp3[] = {
+	P_LP3_CLK,
+	P_LP3_ACK,
+	P_LP3_D0,
+	P_LP3_D1,
+	P_LP3_D2,
+	P_LP3_D3,
+	P_LP3_D4,
+	P_LP3_D5,
+	P_LP3_D6,
+	P_LP3_D7,
+	0
+};
+
+struct bfin_linkport {
+	struct list_head lp_dev;
+	struct class *class;
+	int major;
+	spinlock_t lp_dev_lock;
+};
+
+struct bfin_lp_dev {
+	struct list_head list;
+	struct device *device;
+	volatile struct bfin_lp_register __iomem *regs;
+	wait_queue_head_t rx_waitq;
+	int rx_fifo_cnt;
+	int tx_fifo_cnt;
+	spinlock_t lock;
+	int linkport_num;
+	int dma_chan;
+	int irq;
+	int status_irq;
+	const unsigned short *per_linkport;
+};
+
+struct bfin_linkport *linkport_dev;
+
+struct bfin_lp_dev lp_dev_info[4] = {
+	{
+		.regs = LP0_CTL,
+		.irq = IRQ_LP0,
+		.per_linkport = per_req_lp0,
+		.dma_chan = CH_LP0,
+	},
+	{
+		.regs = LP1_CTL,
+		.irq = IRQ_LP1,
+		.per_linkport = per_req_lp1,
+		.dma_chan = CH_LP1,
+	},
+	{
+		.regs = LP2_CTL,
+		.irq = IRQ_LP2,
+		.per_linkport = per_req_lp2,
+		.dma_chan = CH_LP2,
+	},
+	{
+		.regs = LP3_CTL,
+		.irq = IRQ_LP3,
+		.per_linkport = per_req_lp3,
+		.dma_chan = CH_LP3,
+	},
+};
+
+
+int bfin_lp_config_channel(struct bfin_lp_dev *lpdev, int direction)
+{
+	uint32_t reg;
+	if (direction)
+		reg = LP_CTL_TRAN | LP_CTL_RRQMSK | LP_CTL_RRQMSK | LP_CTL_ITMSK;
+	else
+		reg = LP_CTL_RRQMSK | LP_CTL_RRQMSK | LP_CTL_ITMSK;
+
+	lpdev->regs->ctl = reg;
+	SSYNC();
+	return 0;
+}
+
+int bfin_lp_tx_word(struct bfin_lp_dev *lpdev, uint32_t *buf, int count)
+{
+	uint32_t *word = buf;
+
+	lpdev->regs->div = 1;
+	lpdev->regs->ctl |= LP_CTL_EN;
+
+	while (count) {
+		if ((lpdev->regs->stat & LP_STAT_FFST) == 6)
+			return -EAGAIN;
+
+		while (lpdev->regs->stat & LP_STAT_LPBS);
+
+		lpdev->regs->tx = *word;
+		count--;
+		word++;
+	}
+
+	return 0;
+}
+
+int bfin_lp_rx_word(struct bfin_lp_dev *lpdev, uint32_t *buf, int count)
+{
+	uint32_t *word = buf;
+	while (count) {
+
+		if (!(lpdev->regs->stat & LP_STAT_LRRQ))
+			return -EAGAIN;
+
+		*word = lpdev->regs->rx;
+
+		count--;
+		word++;
+
+		while (lpdev->regs->stat & LP_STAT_LPBS);
+
+	}
+
+	return 0;
+}
+
+int bfin_lp_tx_dma(struct bfin_lp_dev *lpdev, void *buf, size_t count)
+{
+
+	set_dma_config(lpdev->dma_chan,
+			set_bfin_dma_config(DIR_READ, DMA_FLOW_STOP,
+				INTR_ON_BUF,
+				DIMENSION_LINEAR,
+				DATA_SIZE_32,
+				DMA_SYNC_RESTART));
+	set_dma_start_addr(lpdev->dma_chan, buf);
+	set_dma_x_count(lpdev->dma_chan, count);
+	set_dma_x_modify(lpdev->dma_chan, 1);
+	SSYNC();
+	enable_dma(lpdev->dma_chan);
+
+	return 0;
+}
+
+int bfin_lp_rx_dma(struct bfin_lp_dev *lpdev, void *buf, size_t count)
+{
+
+	set_dma_config(lpdev->dma_chan,
+			set_bfin_dma_config(DIR_WRITE, DMA_FLOW_STOP,
+				INTR_ON_BUF,
+				DIMENSION_LINEAR,
+				DATA_SIZE_32,
+				DMA_SYNC_RESTART));
+	set_dma_start_addr(lpdev->dma_chan, buf);
+	set_dma_x_count(lpdev->dma_chan, count);
+	set_dma_x_modify(lpdev->dma_chan, 1);
+	SSYNC();
+	enable_dma(lpdev->dma_chan);
+
+	return 0;
+}
+
+int bfin_lp_get_rx_fifo(struct bfin_lp_dev *lpdev)
+{
+	int state = (lpdev->regs->stat & LP_STAT_FFST) >> 4;
+
+	if (state <= 4)
+		return state;
+	else
+		return 0;
+}
+
+static irqreturn_t bfin_lp_irq(int irq, void *dev_id)
+{
+	struct bfin_lp_dev *dev = (struct bfin_lp_dev *)dev_id;
+	uint32_t word;
+	int cnt;
+	unsigned long flags;
+
+	cnt = bfin_lp_get_rx_fifo(dev);
+	if (cnt) {
+		spin_lock_irqsave(&dev->lock, flags);
+		dev->tx_fifo_cnt = cnt;
+		spin_unlock_irqrestore(&dev->lock, flags);
+		wake_up_interruptible(&dev->rx_waitq);
+	}
+
+	/* wake up read/write block. */
+	printk("bfin lp irq stat %x cnt %d\n", dev->regs->stat, cnt);
+
+	if (dev->regs->stat & LP_STAT_LTRQ) {
+		printk("send word\n");
+		dev->regs->tx = 0xffaa55bb;
+	}
+
+
+	return IRQ_HANDLED;
+}
+
+
+static int bfin_lp_open(struct inode *inode, struct file *filp)
+{
+	unsigned long flags;
+	struct bfin_lp_dev *dev;
+	unsigned int index = iminor(inode);
+	int ret = -EBUSY;
+
+	dev = &lp_dev_info[index];
+
+	spin_lock_irqsave(&dev->lock, flags);
+
+	filp->private_data = dev;
+
+	spin_unlock_irqrestore(&dev->lock, flags);
+
+	if (peripheral_request_list(dev->per_linkport, LINKPORT_DRVNAME)) {
+		printk("Requesting Peripherals failed\n");
+		return ret;
+	}
+
+	if (request_dma(dev->dma_chan, LINKPORT_DRVNAME) < 0) {
+		printk(KERN_NOTICE "Unable to attach Blackfin LINKPORT DMA channel\n");
+		goto free_per;
+	}
+
+	if (request_irq(dev->irq, bfin_lp_irq, 0, LINKPORT_DRVNAME, dev)) {
+		printk("Requesting irq failed\n");
+		goto free_dma;
+	}
+
+
+	dev->regs->div = 1;
+	SSYNC();
+
+	return 0;
+
+free_dma:
+	free_dma(dev->dma_chan);
+free_per:
+	peripheral_free_list(dev->per_linkport);
+	return ret;
+}
+
+static int bfin_lp_release(struct inode *inode, struct file *filp)
+{
+	struct bfin_lp_dev *dev = filp->private_data;
+	peripheral_free_list(dev->per_linkport);
+	free_dma(dev->dma_chan);
+	free_irq(dev->irq, dev);
+	return 0;
+}
+
+static ssize_t bfin_lp_read(struct file *filp, char *buf, size_t count, loff_t *pos)
+{
+	struct bfin_lp_dev *dev = filp->private_data;
+	int fifo_cnt = 0;
+	int rx_byte = 0;
+	uint32_t word[4];
+
+	printk("%s count %d\n", __func__, count);
+
+	bfin_lp_config_channel(dev, 0);
+
+	dev->regs->ctl |= LP_CTL_EN;
+
+
+	while (count) {
+		fifo_cnt = bfin_lp_get_rx_fifo(dev);
+		if (!fifo_cnt) {
+			wait_event_interruptible(dev->rx_waitq, dev->rx_fifo_cnt);
+			continue;
+		}
+
+		bfin_lp_rx_word(dev, word, 1);
+		count--;
+		rx_byte += 4;
+
+		if (put_user(word[0], (uint32_t *)(buf + rx_byte)))
+			return -EFAULT;
+
+	}
+
+	return count;
+}
+
+static ssize_t bfin_lp_write(struct file *filp, char *buf, size_t count, loff_t *pos)
+{
+	struct bfin_lp_dev *dev = filp->private_data;
+	unsigned long trans = count / 4 + 1;
+	uint32_t word;
+	int ret;
+
+	ret = kstrtou32_from_user(buf, 1, 16, &word);
+	printk("%s count %d\n", __func__, count);
+	bfin_lp_config_channel(dev, 1);
+
+	while (trans) {
+		ret = bfin_lp_tx_word(dev, &word, 1);
+		if (ret)
+			return 0;
+		trans--;
+	}
+
+	return count;
+}
+
+static long bfin_lp_ioctl(struct file *filp, uint cmd, unsigned long arg)
+{
+	return 0;
+}
+
+
+static const struct file_operations linkport_fops = {
+	.owner = THIS_MODULE,
+	.read = bfin_lp_read,
+	.write = bfin_lp_write,
+	.unlocked_ioctl = bfin_lp_ioctl,
+	.open = bfin_lp_open,
+	.release = bfin_lp_release,
+};
+
+static ssize_t
+linkport_status_show(struct class *class, struct class_attribute *attr, char *buf)
+{
+	char *p = buf;
+	struct bfin_lp_dev *dev;
+
+	printk("%s\n", __func__);
+	p += sprintf(p, "linkport status\n");
+	list_for_each_entry(dev, &linkport_dev->lp_dev, list) {
+		p += sprintf(p, "linkport num %d\n", dev->linkport_num);
+	}
+	return p - buf;
+}
+
+static ssize_t
+linkport_reg_show(struct class *class, struct class_attribute *attr, char *buf)
+{
+	char *p = buf;
+	struct bfin_lp_dev *dev;
+
+	printk("%s\n", __func__);
+	p += sprintf(p, "linkport status\n");
+	list_for_each_entry(dev, &linkport_dev->lp_dev, list) {
+		p += sprintf(p, "linkport num %d\n", dev->linkport_num);
+		p += sprintf(p, "\t clt %d\n", dev->regs->ctl);
+		p += sprintf(p, "\t stat %d\n", dev->regs->stat);
+	}
+	return (p - buf);
+}
+
+static ssize_t
+linkport_reg_store(struct class *class, struct class_attribute *attr, char *buf, size_t count)
+{
+	char *p = buf;
+	struct bfin_lp_dev *dev;
+	int rw = 0;
+	char buffer[64];
+	uint32_t res;
+	uint32_t addr;
+	uint32_t value = 0;
+	char *endp;
+
+	if (copy_from_user(buffer, buf, count))
+		return -EFAULT;
+
+	buffer[count] = '\0';
+
+	p = buffer;
+
+	while (*p == ' ')
+		p++;
+
+	if (p[0] == 'r')
+		rw = 0;
+	else if (p[0] == 'w')
+		rw = 1;
+	else
+		printk("-EINVAL\n");
+
+	if (p[1] < '0' && p[1] > '9')
+		printk("-EINVAL2\n");
+
+	res = simple_strtoul(&p[1], &endp, 10);
+
+	if (res == 8)
+		p += 2;
+	else if (res == 16)
+		p += 3;
+	else if (res == 32)
+		p += 3;
+	else
+		printk("-EINVAL3\n");
+
+
+	while (*p == ' ')
+		p++;
+
+	addr = simple_strtoul(p, &endp, 16);
+
+	if (rw) {
+		p = endp;
+		while (*p == ' ')
+			p++;
+
+		value = simple_strtoul(p, NULL, 16);
+		switch (res) {
+		case 8:
+			bfin_write8(addr, (uint8_t)value);
+			value = bfin_read8(addr);
+			break;
+		case 16:
+			bfin_write16(addr, (uint16_t)value);
+			value = bfin_read16(addr);
+			break;
+		case 32:
+			bfin_write32(addr, (uint32_t)value);
+			value = bfin_read32(addr);
+			break;
+		}
+		printk("write addr %08x reg %08x\n", addr, value);
+	} else {
+		switch (res) {
+		case 8:
+			value = bfin_read8(addr);
+			break;
+		case 16:
+			value = bfin_read16(addr);
+			break;
+		case 32:
+			value = bfin_read32(addr);
+			break;
+		}
+		printk("read addr %08x reg %08x\n", addr, value);
+	}
+	return count;
+}
+
+static struct class *linkport_class;
+static CLASS_ATTR(status, S_IRWXUGO, &linkport_status_show, NULL);
+static CLASS_ATTR(reg, S_IRWXUGO, &linkport_reg_show, &linkport_reg_store);
+
+/*
+ * bfin_linkport_init - Initialize module
+ *
+ *
+ */
+
+static int __init bfin_linkport_init(void)
+{
+	int err;
+	dev_t lp_dev;
+	int i;
+
+	printk("%s\n", __func__);
+
+	linkport_dev = kzalloc(sizeof(*linkport_dev), GFP_KERNEL);
+	if (!linkport_dev) {
+		return -ENOMEM;
+	}
+
+	linkport_dev->major = register_chrdev(0, "bfin-linkport", &linkport_fops);
+	if (linkport_dev->major < 0) {
+		err = linkport_dev->major;
+		printk("Error %d registering chrdev for device\n", err);
+		goto free;
+	}
+
+	lp_dev = MKDEV(linkport_dev->major, 0);
+
+	linkport_dev->class = class_create(THIS_MODULE, "linkport");
+	err = class_create_file(linkport_dev->class, &class_attr_status);
+	if (err) {
+		printk("Error %d registering class device\n", err);
+		goto free_chrdev;
+	}
+
+	err = class_create_file(linkport_dev->class, &class_attr_reg);
+	if (err) {
+		printk("Error %d registering class device\n", err);
+		class_remove_file(linkport_dev->class, &class_attr_status);
+		goto free_chrdev;
+	}
+
+	INIT_LIST_HEAD(&linkport_dev->lp_dev);
+	spin_lock_init(&linkport_dev->lp_dev_lock);
+
+	for (i = 0; i < 2; i++) {
+		struct device *dev;
+		dev = device_create(linkport_dev->class, NULL, lp_dev + i, &lp_dev_info[i], "linkport%d", i);
+		if (!dev)
+			goto free_chrdev;
+		lp_dev_info[i].device = dev;
+		lp_dev_info[i].linkport_num = i;
+		spin_lock_init(&lp_dev_info[i].lock);
+		init_waitqueue_head(&lp_dev_info[i].rx_waitq);
+		INIT_LIST_HEAD(&lp_dev_info[i].list);
+		list_add(&lp_dev_info[i].list, &linkport_dev->lp_dev);
+	}
+
+	return 0;
+
+free_chrdev:
+	unregister_chrdev(linkport_dev->major, "bfin-linkport");
+free:
+	kfree(linkport_dev);
+	return err;
+}
+module_init(bfin_linkport_init);
_______________________________________________
Linux-kernel-commits mailing list
[email protected]
https://blackfin.uclinux.org/mailman/listinfo/linux-kernel-commits

Reply via email to