From a8c384139e9116702b80ac98595f7f38e924db77 Mon Sep 17 00:00:00 2001 From: Brandon Philips Date: Thu, 3 Apr 2008 15:39:59 -0700 Subject: aectc: add the aectc driver From: Brandon Philips TODO: - provide a description of what this driver is - make it work properly :) Signed-off-by: Brandon Philips Signed-off-by: Greg Kroah-Hartman --- drivers/misc/Kconfig | 7 drivers/misc/Makefile | 1 drivers/misc/aectc/Makefile | 1 drivers/misc/aectc/aec_tc.c | 349 ++++++++++++++++++++++++++++++++++++++++++++ drivers/misc/aectc/aectc.h | 18 ++ drivers/misc/aectc/read.c | 63 +++++++ drivers/misc/aectc/write.c | 64 ++++++++ 7 files changed, 503 insertions(+) --- /dev/null +++ b/drivers/misc/aectc/aec_tc.c @@ -0,0 +1,349 @@ +/* + * aec_tc.c -- simple driver for Adrienne Electronics Corp time code PCI device + * + * Copyright (C) 2008 Brandon Philips + * + * 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., 59 + * Temple Place, Suite 330, Boston, MA 02111-1307, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "aectc.h" + +struct aectc_dev { + void __iomem *map; + struct mutex mutex; + struct cdev cdev; +}; + +static struct mutex aectc_mutex; +static int aectc_nr_devs = 4; +static int aectc_cur_devs; +static int aectc_minor; +static int aectc_major; + +#define PCI_VENDOR_ID_AEC 0xaecb +#define PCI_DEVICE_ID_AEC_VITCLTC 0x6250 + +#define RW1_REG_START 0x20 /* Inclusive */ +#define RW1_REG_END 0x3F + +#define RW2_REG_START 0x60 /* Inclusive */ +#define RW2_REG_END 0x7F + +#define RES_REG_START 0x80 /* Inclusive */ +#define RES_REG_END 0xFD + +#define INT_ENABLE_ADDR 0xFC +#define INT_ENABLE 0x10 +#define INT_DISABLE 0x0 + +#define INTA_DRVR_ADDR 0xFE +#define INTA_ENABLED_FLAG 0x08 +#define INTA_FLAG 0x02 + +static struct pci_device_id ids[] = { + { PCI_DEVICE(PCI_VENDOR_ID_AEC, PCI_DEVICE_ID_AEC_VITCLTC), }, + { 0, } +}; +MODULE_DEVICE_TABLE(pci, ids); + +static irqreturn_t aectc_irq(int irq, void *dev_id) +{ + struct aectc_dev *adev = (struct aectc_dev *)dev_id; + unsigned char * __iomem *int_flag = adev->map + INTA_DRVR_ADDR; + unsigned char status = ioread8(int_flag); + + if (!((status & INTA_ENABLED_FLAG) && (status & INTA_FLAG))) + return IRQ_NONE; + + printk(KERN_INFO "aectc interrupt received\n"); + + return IRQ_HANDLED; +} + +static int regread(struct aectc_dev *dev, struct aectc_reg *reg) +{ + if (reg->reg > 0xFF) + return -EINVAL; + + switch (reg->length) { + case 8: + reg->data = ioread8(dev->map + reg->reg); + break; + case 16: + reg->data = ioread16(dev->map + reg->reg); + break; + case 32: + reg->data = ioread32(dev->map + reg->reg); + break; + default: + return -EINVAL; + } + + return 0; +} + + +/* TODO: Make sure we are writing to a valid RW area */ +static int regwrite(struct aectc_dev *dev, struct aectc_reg *reg) +{ + /* Refuse to write if value larger than length */ + if (reg->data >= (1<length)) + return -EINVAL; + + if (reg->reg > 0xFF) + return -EINVAL; + + switch (reg->length) { + case 8: + iowrite8((u8)reg->data, dev->map + reg->reg); + break; + case 16: + iowrite16((u16)reg->data, dev->map + reg->reg); + break; + case 32: + iowrite32((u32)reg->data, dev->map + reg->reg); + break; + default: + return -EINVAL; + } + + return 0; +} + + +static int aectc_ioctl(struct inode *inode, struct file *filp, + unsigned int cmd, unsigned long arg) +{ + int retval = 0; + struct aectc_reg reg; + + struct aectc_dev *dev; + dev = container_of(inode->i_cdev, struct aectc_dev, cdev); + + if (_IOC_DIR(cmd) & _IOC_READ) + retval = !access_ok(VERIFY_WRITE, (void __user *)arg, + _IOC_SIZE(cmd)); + else if (_IOC_DIR(cmd) & _IOC_WRITE) + retval = !access_ok(VERIFY_READ, (void __user *)arg, + _IOC_SIZE(cmd)); + if (retval) + return -EFAULT; + + retval = copy_from_user(®, (void __user *)arg, sizeof(reg)); + if (retval) + return retval; + + printk(KERN_DEBUG "aectc: ioctl addr: %x length: %x val: %x\n", reg.reg, + reg.length, reg.data); + + switch (cmd) { + case AEC_IOC_WRITEREG: + return regwrite(dev, ®); + break; + case AEC_IOC_READREG: + retval = regread(dev, ®); + if (retval) + return retval; + return copy_to_user((void __user *)arg, ®, sizeof(reg)); + break; + default: + return -ENOTTY; + } + + return -ENOTTY; +} + +static int aectc_open(struct inode *inode, struct file *filp) +{ + return 0; +} + +static int aectc_release(struct inode *inode, struct file *filp) +{ + return 0; +} + +static struct file_operations aectc_fops = { + .owner = THIS_MODULE, + .ioctl = aectc_ioctl, + .open = aectc_open, + .release = aectc_release, +}; + +static void aectc_setup_cdev(struct aectc_dev *dev) +{ + int err, devno = MKDEV(aectc_major, aectc_minor); + + cdev_init(&dev->cdev, &aectc_fops); + dev->cdev.owner = THIS_MODULE; + dev->cdev.ops = &aectc_fops; + + err = cdev_add(&dev->cdev, devno, 1); + if (err) + printk(KERN_NOTICE "Error %d adding aectc", err); +} + +static void print_board_data(struct aectc_dev *a) +{ + int cap; + + printk("PCI-TC board vendor: %x%x number: %x%x revision: %c%c\n", + ioread8(a->map + 0x01), + ioread8(a->map + 0x00), + ioread8(a->map + 0x03), + ioread8(a->map + 0x02), + ioread8(a->map + 0x06), + ioread8(a->map + 0x07)); + + cap = ioread8(a->map + 0x08); + if (cap) + printk(KERN_INFO "Capabilities: "); + if (cap & 7) + printk(KERN_INFO "VITC generator "); + if (cap & 6) + printk(KERN_INFO "VITC reader "); + if (cap & 5) + printk(KERN_INFO "LTC generator "); + if (cap & 4) + printk(KERN_INFO "LTC reader "); + if (cap & 3) + printk(KERN_INFO "OSD option "); + if (cap & 2) + printk(KERN_INFO "L21 reader "); + if (cap & 1) + printk(KERN_INFO "serial option "); + if (cap) + printk(KERN_INFO "\n"); +} + +static int __devinit probe(struct pci_dev *dev, const struct pci_device_id *id) +{ + struct aectc_dev *adev; + int ret; + + adev = kzalloc(sizeof(struct aectc_dev), GFP_KERNEL); + + if (pci_enable_device(dev)) + goto out_free; + + if (pci_request_regions(dev, "aectc")) + goto out_disable; + + adev->map = pci_iomap(dev, 0, 0); + if (!adev->map) + goto out_release; + + ret = request_irq(dev->irq, aectc_irq, IRQF_DISABLED | IRQF_SHARED, + "aectc", adev); + + if (ret) + goto out_unmap; + + mutex_lock(&aectc_mutex); + if (aectc_cur_devs < aectc_nr_devs) + aectc_cur_devs++; + else { + mutex_unlock(&aectc_mutex); + return -ENOSPC; + } + aectc_setup_cdev(adev); + mutex_unlock(&aectc_mutex); + + iowrite32(INT_ENABLE, adev->map + INT_ENABLE_ADDR); + if (ioread8(adev->map + INTA_DRVR_ADDR) & INTA_ENABLED_FLAG) + printk(KERN_INFO "aectc: interrupts successfully enabled\n"); + + print_board_data(adev); + pci_set_drvdata(dev, adev); + + return 0; + +out_unmap: + pci_iounmap(dev, adev->map); +out_release: + pci_release_regions(dev); +out_disable: + pci_disable_device(dev); +out_free: + kfree(dev); + return -ENODEV; +} + +static void remove(struct pci_dev *dev) +{ + struct aectc_dev *adev = pci_get_drvdata(dev); + + /* Disable IRQ */ + iowrite32(INT_DISABLE, adev->map + INT_ENABLE_ADDR); + + free_irq(dev->irq, dev); + pci_release_regions(dev); + pci_disable_device(dev); + pci_set_drvdata(dev, NULL); + iounmap(adev->map); + cdev_del(&adev->cdev); + mutex_lock(&aectc_mutex); + aectc_cur_devs--; + mutex_unlock(&aectc_mutex); + + kfree(adev); +} + +static struct pci_driver pci_driver = { + .name = "aectc", + .id_table = ids, + .probe = probe, + .remove = remove, +}; + +static int __init aectc_init(void) +{ + int retval; + dev_t dev = 0; + + retval = alloc_chrdev_region(&dev, aectc_minor, aectc_nr_devs, "aectc"); + aectc_major = MAJOR(dev); + + if (retval < 0) { + printk(KERN_WARNING "aectc: can't get minor %d\n", aectc_minor); + return retval; + } + + printk(KERN_INFO "aectc major/minor: %i %i\n", aectc_major, 0); + + mutex_init(&aectc_mutex); + + return pci_register_driver(&pci_driver); +} + +static void __exit aectc_exit(void) +{ + pci_unregister_driver(&pci_driver); +} + +MODULE_LICENSE("GPL"); + +module_init(aectc_init); +module_exit(aectc_exit); --- /dev/null +++ b/drivers/misc/aectc/aectc.h @@ -0,0 +1,18 @@ +#ifndef _SCULL_H_ +#define _SCULL_H_ + +#include +#include + +struct aectc_reg { + __u16 reg; + __u16 length; + __u32 data; +}; + +#define AECTC_IOC_MAGIC 0xEC + +#define AEC_IOC_READREG _IOWR(AECTC_IOC_MAGIC, 0, struct aectc_reg) +#define AEC_IOC_WRITEREG _IOW(AECTC_IOC_MAGIC, 0, struct aectc_reg) + +#endif --- /dev/null +++ b/drivers/misc/aectc/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_AECTC) := aec_tc.o --- /dev/null +++ b/drivers/misc/aectc/read.c @@ -0,0 +1,63 @@ +/* + * read.c -- simple application for reading registers from a Adrienne + * Electronics Corp time code device under Linux + * + * Copyright (C) 2008 Brandon Philips + * + * 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., 59 + * Temple Place, Suite 330, Boston, MA 02111-1307, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "aectc.h" + +int main(int argc, char **argv) +{ + struct aectc_reg reg; + int fd; + + if (argc == 4) { + sscanf(argv[1], "%lx", ®.reg); + reg.length = atoi(argv[2]); + } else { + fprintf(stderr, "Usage: %s reg length file\n", argv[0]); + exit(1); + } + + fd = open(argv[3], O_RDWR); + + if (fd == -1) { + fprintf(stderr, "Couldn't open %s\n", argv[3]); + exit(1); + } + + if (ioctl(fd, AEC_IOC_READREG, ®) < 0) { + fprintf(stderr,"%s: ioctl(stdin, AEC_IOC_READREG): %s\n", + argv[0], strerror(errno)); + exit(1); + } + + printf("%x\n", reg.data); + exit(0); +} + --- /dev/null +++ b/drivers/misc/aectc/write.c @@ -0,0 +1,64 @@ +/* + * read.c -- simple application for writing registers on a Adrienne + * Electronics Corp time code device under Linux + * + * Copyright (C) 2008 Brandon Philips + * + * 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., 59 + * Temple Place, Suite 330, Boston, MA 02111-1307, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "aectc.h" + +int main(int argc, char **argv) +{ + struct aectc_reg reg; + int fd; + + if (argc == 5) { + sscanf(argv[1], "%lx", ®.reg); + sscanf(argv[2], "%lx", ®.data); + reg.length = atoi(argv[3]); + } else { + fprintf(stderr, "Usage: %s reg data length file\n", argv[0]); + exit(1); + } + + fd = open(argv[4], O_RDWR); + + if (fd == -1) { + fprintf(stderr, "Couldn't open %s\n", argv[3]); + exit(1); + } + + if (ioctl(fd, AEC_IOC_WRITEREG, ®) < 0) { + fprintf(stderr,"%s: ioctl(fd, AEC_IOC_READREG): %s\n", + argv[0], strerror(errno)); + exit(1); + } + + printf("%x\n", reg.data); + exit(0); +} + --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -481,4 +481,11 @@ config ME4000 help FIXME! +config AECTC + tristate "AETC video timestamp device" + depends on PCI + default n + help + Provides support for something that needs to be documented here. + endif # MISC_DEVICES --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -31,3 +31,4 @@ obj-$(CONFIG_SGI_XP) += sgi-xp/ obj-$(CONFIG_SGI_GRU) += sgi-gru/ obj-$(CONFIG_HP_ILO) += hpilo.o obj-$(CONFIG_ME4000) += me4000.o +obj-$(CONFIG_AECTC) += aectc/