From b975cdafa0e178cf8f3f8b94e4d33deae85f2396 Mon Sep 17 00:00:00 2001 From: Krzysztof Mazur Date: Thu, 23 Oct 2014 13:59:37 +0200 Subject: [PATCH 2/2] misc/ds1104: add initial DS1104 R&D card driver This is a basic driver for dSpace DS1104 cards. Signed-off-by: Krzysztof Mazur --- drivers/misc/Kconfig | 9 + drivers/misc/Makefile | 1 + drivers/misc/ds1104.c | 657 ++++++++++++++++++++++++++++++++++++++++++++ include/uapi/linux/ds1104.h | 10 + 4 files changed, 677 insertions(+) create mode 100644 drivers/misc/ds1104.c create mode 100644 include/uapi/linux/ds1104.h diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index b841180..9b83500 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -515,6 +515,15 @@ config VEXPRESS_SYSCFG bus. System Configuration interface is one of the possible means of generating transactions on this bus. +config DS1104 + tristate "dSPACE DS1104 R&D Controller board driver" + depends on PCI + default n + help + This driver supports the dSPACE DS1104 PCI boards. Currently + only the basic functionality is supported and the rest is offloaded + to the userspace driver. + source "drivers/misc/c2port/Kconfig" source "drivers/misc/eeprom/Kconfig" source "drivers/misc/cb710/Kconfig" diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile index 5497d02..21c3c0e 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -55,3 +55,4 @@ obj-y += mic/ obj-$(CONFIG_GENWQE) += genwqe/ obj-$(CONFIG_ECHO) += echo/ obj-$(CONFIG_VEXPRESS_SYSCFG) += vexpress-syscfg.o +obj-$(CONFIG_DS1104) += ds1104.o diff --git a/drivers/misc/ds1104.c b/drivers/misc/ds1104.c new file mode 100644 index 0000000..2bb1685 --- /dev/null +++ b/drivers/misc/ds1104.c @@ -0,0 +1,657 @@ +/* + * ds1104.c - R&D controller board + * + * Copyright (c) 2014 Krzysztof Mazur + * + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include + +#define DRV_NAME "ds1104" + +#define DS1104_RAM_SIZE (32UL << 20) /* 32 MiB */ +#define DS1104_DMA_SIZE (4UL << 20) /* 4 MiB */ + +#define MPC8240_IDBR 0x68 +#define MPC8240_IDBR_MCE 0x80000000 +#define MPC8240_IDBR_RUN 0x02000000 +#define MPC8240_IDBR_STOP 0x01000000 +#define MPC8240_OMBAR 0x300 +#define MPC8240_OTWR 0x308 +#define MPC8240_OTWR_4M 0x15 +#define MPC8240_ITWR 0x310 +#define MPC8240_ITWR_32M 0x18 + +#define MPC8240_DMA_DMR 0x100 +#define MPC8240_DMA_DSR 0x104 +#define MPC8240_DMA_CDAR 0x108 +#define MPC8240_DMA_SAR 0x110 +#define MPC8240_DMA_DAR 0x118 +#define MPC8240_DMA_BCR 0x120 +#define MPC8240_DMA_NDAR 0x124 + +#define MPC8240_DMA_MDR_IRQS 0x00080000 +#define MPC8240_DMA_MDR_EIE 0x00000100 +#define MPC8240_DMA_MDR_EITIE 0x00000080 +#define MPC8240_DMA_MDR_DL 0x00000008 /* 1 for PCI Memory */ +#define MPC8240_DMA_MDR_CTM 0x00000004 /* 1 for Direct DMA Mode */ +#define MPC8240_DMA_MDR_CC 0x00000002 /* Channel Continue */ +#define MPC8240_DMA_MDR_CS 0x00000001 /* Channel Start */ + +#define MPC8240_DMA_DSR_CB 0x00000004 /* Channel Busy */ + +#define MPC8240_CTT_LOCAL_LOCAL 0x00000000 +#define MPC8240_CTT_LOCAL_PCI 0x00000002 +#define MPC8240_CTT_PCI_LOCAL 0x00000004 +#define MPC8240_CTT_PCI_PCI 0x00000006 + +#define DS1104_MINOR_SHIFT 4 +#define DS1104_MINOR_TYPE_MASK ((1 << DS1104_MINOR_SHIFT) - 1) + +#define DS1104_MINOR_TYPE_MEM 0 +#define DS1104_MINOR_TYPE_DMA 1 + +struct ds1104 { + struct pci_dev *pdev; + void __iomem *mem; + void __iomem *regs; + unsigned int id; + struct list_head list; + struct mutex cmd_mutex; +}; + +static int ds1104_major; +static LIST_HEAD(ds1104_list); +static unsigned int ds1104_count; +static DEFINE_MUTEX(ds1104_lock); +static struct device *ds1104_dma_dev; +static void *ds1104_dma; +static dma_addr_t ds1104_dma_addr; +static unsigned int ds1104_dma_count; + +static int ds1104_alloc_dma(struct pci_dev *pdev) +{ + int err; + + err = -ENOMEM; + mutex_lock(&ds1104_lock); + if (!ds1104_dma) { + ds1104_dma = dma_zalloc_coherent(&pdev->dev, DS1104_DMA_SIZE, + &ds1104_dma_addr, GFP_KERNEL); + if (!ds1104_dma) + goto out; + ds1104_dma_dev = &pdev->dev; + } + ds1104_dma_count++; + err = 0; + +out: + mutex_unlock(&ds1104_lock); + return err; +} + +static void ds1104_put_dma(void) +{ + mutex_lock(&ds1104_lock); + ds1104_dma_count--; + if (!ds1104_dma_count) { + dev_info(ds1104_dma_dev, "freeing DMA memory\n"); + dma_free_coherent(ds1104_dma_dev, DS1104_DMA_SIZE, ds1104_dma, + ds1104_dma_addr); + } + mutex_unlock(&ds1104_lock); +} + +static int ds1104_stop(struct ds1104 *p) +{ + int err; + u32 x; + + err = -EIO; + x = readl(p->regs + MPC8240_IDBR); + if (x & MPC8240_IDBR_MCE) { + dev_err(&p->pdev->dev, "MCE already pending\n"); + goto err; + } + writel(MPC8240_IDBR_MCE | MPC8240_IDBR_STOP, p->regs + MPC8240_IDBR); + err = 0; +err: + return err; +} + +static int ds1104_run(struct ds1104 *p) +{ + int err; + u32 x; + + err = -EIO; + x = readl(p->regs + MPC8240_IDBR); + if (x & MPC8240_IDBR_MCE) { + dev_err(&p->pdev->dev, "MCE already pending\n"); + goto err; + } + writel(MPC8240_IDBR_MCE | MPC8240_IDBR_RUN, p->regs + MPC8240_IDBR); + err = 0; +err: + return err; +} + +static int ds1104_wait(struct ds1104 *p) +{ + unsigned int i; + u32 x; + + i = 8; + do { + msleep(20); + x = readl(p->regs + MPC8240_IDBR); + if (!(x & MPC8240_IDBR_MCE)) + return 0; + i--; + } while (i > 0); + return -ETIMEDOUT; +} + +static int ds1104_probe(struct pci_dev *pdev, const struct pci_device_id *id) +{ + struct ds1104 *p; + unsigned int card_id; + unsigned long flags; + u32 x; + int err; + + card_id = ds1104_count; + ds1104_count++; + + dev_info(&pdev->dev, "probing DS1104 card %d\n", card_id); + + err = -ENOMEM; + p = kzalloc(sizeof(*p), GFP_KERNEL); + if (!p) { + dev_err(&pdev->dev, "Cannot allocate memory\n"); + goto err; + } + p->id = card_id; + p->pdev = pdev; + pci_set_drvdata(pdev, p); + mutex_init(&p->cmd_mutex); + + err = pci_enable_device(pdev); + if (err) { + dev_err(&pdev->dev, "Cannot enable device, aborting\n"); + goto err_free; + } + + err = -EIO; + flags = pci_resource_flags(pdev, 0); + if ((flags & IORESOURCE_TYPE_BITS) != IORESOURCE_MEM) { + dev_err(&pdev->dev, "incorrect BAR0 type: 0x%08lx\n", flags); + goto err_disable; + } + + if (pci_resource_len(pdev, 0) != DS1104_RAM_SIZE) { + dev_err(&pdev->dev, "incorrect BAR0 length, no fixup?\n"); + goto err_disable; + } + + if (pci_resource_len(pdev, 1) < 4096) { + dev_err(&pdev->dev, "Invalid BAR1 length\n"); + goto err_disable; + } + + err = pci_request_regions(pdev, DRV_NAME); + if (err) { + dev_err(&pdev->dev, "Cannot obtain PCI resources, aborting\n"); + goto err_disable; + } + + err = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32)); + if (err) { + dev_err(&pdev->dev, "No usable DMA config, aborting\n"); + goto err_release; + } + + err = -EIO; + p->regs = pci_iomap(pdev, 1, 0); + if (!p->regs) { + dev_err(&pdev->dev, "Cannot map BAR1, aborting\n"); + goto err_release; + } + + err = ds1104_alloc_dma(pdev); + if (err) { + dev_err(&pdev->dev, "Cannot allocate DMA memory, aborting\n"); + goto err_unmap_regs; + } + + err = -EIO; + writel((u32) ds1104_dma_addr | MPC8240_OTWR_4M, p->regs + MPC8240_OTWR); + x = readl(p->regs + MPC8240_OTWR); + if (x != ((u32) ds1104_dma_addr | MPC8240_OTWR_4M)) { + dev_err(&pdev->dev, "Cannot write OTWR, aborting\n"); + goto err_put_dma; + } + writel(0x80000000, p->regs + MPC8240_OMBAR); + x = readl(p->regs + MPC8240_OMBAR); + if (x != 0x80000000) { + dev_err(&pdev->dev, "Cannot write OMBAR, aborting\n"); + goto err_put_dma; + } + + pci_set_master(pdev); + + writel(MPC8240_ITWR_32M, p->regs + MPC8240_ITWR); + x = readl(p->regs + MPC8240_ITWR); + if (x != MPC8240_ITWR_32M) { + dev_err(&pdev->dev, "ITWR write error, read %08x\n", x); + goto err_put_dma; + } + + p->mem = pci_iomap(pdev, 0, 0); + if (!p->mem) { + dev_err(&pdev->dev, "Cannot map BAR0, aborting\n"); + goto err_put_dma; + } + + mutex_lock(&ds1104_lock); + list_add_tail(&p->list, &ds1104_list); + mutex_unlock(&ds1104_lock); + + return 0; + +err_put_dma: + ds1104_put_dma(); +err_unmap_regs: + pci_iounmap(pdev, p->regs); +err_release: + pci_release_regions(pdev); +err_disable: + pci_disable_device(pdev); +err_free: + kfree(p); +err: + return err; +} + +static void ds1104_remove(struct pci_dev *pdev) +{ + struct ds1104 *p = pci_get_drvdata(pdev); + + list_del(&p->list); + pci_iounmap(p->pdev, p->mem); + ds1104_put_dma(); + pci_iounmap(p->pdev, p->regs); + pci_release_regions(pdev); + pci_disable_device(pdev); + mutex_destroy(&p->cmd_mutex); + kfree(p); +} + +static const struct pci_device_id ds1104_pci_tbl[] = { + { PCI_DEVICE(PCI_VENDOR_ID_MOTOROLA, 0x0003) }, + { 0, } +}; +MODULE_DEVICE_TABLE(pci, ds1104_pci_tbl); + +static struct pci_driver ds1104_driver = { + .name = DRV_NAME, + .id_table = ds1104_pci_tbl, + .probe = ds1104_probe, + .remove = ds1104_remove, +}; + +static inline unsigned long size_inside_page(unsigned long start, + unsigned long size) +{ + unsigned long sz; + + sz = PAGE_SIZE - (start & (PAGE_SIZE - 1)); + + return min(sz, size); +} + +static ssize_t ds1104_dev_read(struct file *file, char __user *buf, + size_t count, loff_t *ppos) +{ + struct ds1104 *dev = file->private_data; + void *page; + loff_t p = *ppos; + ssize_t read, sz; + int err; + + if (!count) + return 0; + + /* + * we copy data twice - once from DS1104 card to kernel buffer + * and the second time from kernel buffer to user space buffer + */ + page = (void *)__get_free_page(GFP_KERNEL); + err = -ENOMEM; + if (!page) + goto err; + + read = 0; + while (count > 0) { + unsigned long remaining; + + if (p >= DS1104_RAM_SIZE) + break; + + sz = size_inside_page(p, count); + memcpy_fromio(page, dev->mem + p, sz); + remaining = copy_to_user(buf, page, sz); + err = -EFAULT; + if (remaining) + goto err_free; + + buf += sz; + p += sz; + count -= sz; + read += sz; + } + free_page((unsigned long) page); + + *ppos += read; + return read; +err_free: + free_page((unsigned long) page); +err: + return err; +} + +static ssize_t ds1104_dev_write(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + struct ds1104 *dev = file->private_data; + void *page; + loff_t p = *ppos; + ssize_t written, sz; + int err; + + if (!count) + return 0; + + /* + * we copy data twice - once from the user space buffer to + * a kernel buffer and the second time from a kernel buffer + * to the DS1104 card + */ + page = (void *)__get_free_page(GFP_KERNEL); + err = -ENOMEM; + if (!page) + goto err; + + written = 0; + err = -EFAULT; + while (count > 0) { + unsigned long remaining; + + if (p >= DS1104_RAM_SIZE) + break; + + sz = size_inside_page(p, count); + remaining = copy_from_user(page, buf, sz); + sz -= remaining; + memcpy_toio(dev->mem + p, page, sz); + + buf += sz; + p += sz; + count -= sz; + written += sz; + + if (remaining) { + if (written) + break; + goto err_free; + } + } + free_page((unsigned long) page); + + *ppos += written; + return written; +err_free: + free_page((unsigned long) page); +err: + return err; +} + +static loff_t ds1104_dev_llseek(struct file *file, loff_t offset, int orig) +{ + loff_t retval = -EINVAL; + + mutex_lock(&file_inode(file)->i_mutex); + switch (orig) { + case SEEK_CUR: + offset += file->f_pos; + case SEEK_SET: + if (offset < 0) + break; + retval = offset; + file->f_pos = offset; + break; + case SEEK_END: + if (offset >= 0) + break; + offset += DS1104_RAM_SIZE; + if (offset < 0) + break; + retval = offset; + file->f_pos = offset; + break; + } + mutex_unlock(&file_inode(file)->i_mutex); + return retval; +} + +static long ds1104_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + struct ds1104 *dev = file->private_data; + int err = -ENOTTY; + + mutex_lock(&dev->cmd_mutex); + switch (cmd) { + case DS1104_STOP: + err = ds1104_stop(dev); + break; + case DS1104_RUN: + err = ds1104_run(dev); + break; + case DS1104_WAIT: + err = ds1104_wait(dev); + break; + } + mutex_unlock(&dev->cmd_mutex); + return err; +} + +static int ds1104_dev_mmap(struct file *file, struct vm_area_struct *vma) +{ + struct ds1104 *dev = file->private_data; + dma_addr_t addr; + + addr = pci_resource_start(dev->pdev, 0); + vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); + return vm_iomap_memory(vma, addr, DS1104_RAM_SIZE); +} + +static const struct file_operations ds1104_mem_dev_fops = { + .owner = THIS_MODULE, + .read = ds1104_dev_read, + .write = ds1104_dev_write, + .mmap = ds1104_dev_mmap, + .unlocked_ioctl = ds1104_ioctl, + .llseek = ds1104_dev_llseek, +}; + +static ssize_t ds1104_dma_read(struct file *file, char __user *buf, + size_t count, loff_t *ppos) +{ + unsigned long remaining; + loff_t p = *ppos; + ssize_t read; + + if (!count) + return 0; + + if (p >= DS1104_DMA_SIZE) + return 0; + + remaining = copy_to_user(buf, (char *) ds1104_dma + p, count); + if (remaining) + return -EFAULT; + + read = count; + + *ppos += read; + return read; +} + +static ssize_t ds1104_dma_write(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + unsigned long remaining; + loff_t p = *ppos; + ssize_t written; + + if (!count) + return 0; + + if (p >= DS1104_DMA_SIZE) + return -ENOSPC; + + written = 0; + remaining = copy_from_user((char *) ds1104_dma + p, buf, count); + written += count - remaining; + if (remaining && !written) + return -EFAULT; + + *ppos += written; + return written; +} + +static int ds1104_dma_mmap(struct file *file, struct vm_area_struct *vma) +{ + return vm_iomap_memory(vma, ds1104_dma_addr, DS1104_DMA_SIZE); +} + +static loff_t ds1104_dma_llseek(struct file *file, loff_t offset, int orig) +{ + loff_t retval = -EINVAL; + + mutex_lock(&file_inode(file)->i_mutex); + switch (orig) { + case SEEK_CUR: + offset += file->f_pos; + case SEEK_SET: + if (offset < 0) + break; + retval = offset; + file->f_pos = offset; + break; + case SEEK_END: + if (offset >= 0) + break; + offset += DS1104_DMA_SIZE; + if (offset < 0) + break; + retval = offset; + file->f_pos = offset; + break; + } + mutex_unlock(&file_inode(file)->i_mutex); + return retval; +} + +static const struct file_operations ds1104_dma_dev_fops = { + .owner = THIS_MODULE, + .read = ds1104_dma_read, + .write = ds1104_dma_write, + .mmap = ds1104_dma_mmap, + .llseek = ds1104_dma_llseek, +}; + +static int ds1104_dev_open(struct inode *inode, struct file *file) +{ + unsigned int minor = iminor(inode); + struct ds1104 *dev = NULL; + struct ds1104 *p; + const struct file_operations *fops; + int err = -ENXIO; + + mutex_lock(&ds1104_lock); + list_for_each_entry(p, &ds1104_list, list) { + if (p->id == (minor >> DS1104_MINOR_SHIFT)) { + dev = p; + break; + } + } + mutex_unlock(&ds1104_lock); + + if (!dev) + goto err; + + switch (minor & DS1104_MINOR_TYPE_MASK) { + case DS1104_MINOR_TYPE_MEM: + fops = &ds1104_mem_dev_fops; + break; + case DS1104_MINOR_TYPE_DMA: + fops = &ds1104_dma_dev_fops; + break; + default: + goto err; + } + + file->private_data = p; + file->f_op = fops; + return 0; +err: + return err; +} + +static const struct file_operations ds1104_dev_fops = { + .owner = THIS_MODULE, + .open = ds1104_dev_open, +}; + +static int __init ds1104_init(void) +{ + int err; + + err = register_chrdev(0, "ds1104", &ds1104_dev_fops); + if (err < 0) + goto err; + ds1104_major = err; + + err = pci_register_driver(&ds1104_driver); + if (err) + goto err_unregister_chrdev; + return 0; + +err_unregister_chrdev: + unregister_chrdev(ds1104_major, "ds1104"); +err: + return err; +} + +static void __exit ds1104_exit(void) +{ + unregister_chrdev(ds1104_major, "ds1104"); + pci_unregister_driver(&ds1104_driver); +} + +module_init(ds1104_init); +module_exit(ds1104_exit); + +MODULE_AUTHOR("Krzysztof Mazur "); +MODULE_DESCRIPTION("Driver for DS1104 R&D Controller board PCI device"); +MODULE_LICENSE("GPL"); diff --git a/include/uapi/linux/ds1104.h b/include/uapi/linux/ds1104.h new file mode 100644 index 0000000..b07c32e --- /dev/null +++ b/include/uapi/linux/ds1104.h @@ -0,0 +1,10 @@ +#ifndef _UAPI__DS1104__ +#define _UAPI__DS1104__ + +#include + +#define DS1104_STOP _IO(0xD5, 0x01) +#define DS1104_RUN _IO(0xD5, 0x02) +#define DS1104_WAIT _IO(0xD5, 0x03) + +#endif /* _UAPI__DS1104__ */ -- 2.1.2