diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2012-05-22 16:34:21 -0700 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2012-05-22 16:34:21 -0700 |
commit | fb09bafda67041b74a668dc9d77735e36bd33d3b (patch) | |
tree | 2dd32b65062a95045468fdcab366ecdb8e4fcac6 /drivers/staging/ipack | |
parent | 94b5aff4c6f72fee6b0f49d49e4fa8b204e8ded9 (diff) | |
parent | c3c6cc91b0ae7b3d598488ad0b593bafba4a0817 (diff) |
Merge tag 'staging-3.5-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/staging
Pull staging tree changes from Greg Kroah-Hartman:
"Here is the big staging tree pull request for the 3.5-rc1 merge
window.
Loads of changes here, and we just narrowly added more lines than we
added:
622 files changed, 28356 insertions(+), 26059 deletions(-)
But, good news is that there is a number of subsystems that moved out
of the staging tree, to their respective "real" portions of the
kernel.
Code that moved out was:
- iio core code
- mei driver
- vme core and bridge drivers
There was one broken network driver that moved into staging as a step
before it is removed from the tree (pc300), and there was a few new
drivers added to the tree:
- new iio drivers
- gdm72xx wimax USB driver
- ipack subsystem and 2 drivers
All of the movements around have acks from the various subsystem
maintainers, and all of this has been in the linux-next tree for a
while.
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>"
Fixed up various trivial conflicts, along with a non-trivial one found
in -next and pointed out by Olof Johanssen: a clean - but incorrect -
merge of the arch/arm/boot/dts/at91sam9g20.dtsi file. Fix up manually
as per Stephen Rothwell.
* tag 'staging-3.5-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/staging: (536 commits)
Staging: bcm: Remove two unused variables from Adapter.h
Staging: bcm: Removes the volatile type definition from Adapter.h
Staging: bcm: Rename all "INT" to "int" in Adapter.h
Staging: bcm: Fix warning: __packed vs. __attribute__((packed)) in Adapter.h
Staging: bcm: Correctly format all comments in Adapter.h
Staging: bcm: Fix all whitespace issues in Adapter.h
Staging: bcm: Properly format braces in Adapter.h
Staging: ipack/bridges/tpci200: remove unneeded casts
Staging: ipack/bridges/tpci200: remove TPCI200_SHORTNAME constant
Staging: ipack: remove board_name and bus_name fields from struct ipack_device
Staging: ipack: improve the register of a bus and a device in the bus.
staging: comedi: cleanup all the comedi_driver 'detach' functions
staging: comedi: remove all 'default N' in Kconfig
staging: line6/config.h: Delete unused header
staging: gdm72xx depends on NET
staging: gdm72xx: Set up parent link in sysfs for gdm72xx devices
staging: drm/omap: initial dmabuf/prime import support
staging: drm/omap: dmabuf/prime mmap support
pstore/ram: Add ECC support
pstore/ram: Switch to persistent_ram routines
...
Diffstat (limited to 'drivers/staging/ipack')
-rw-r--r-- | drivers/staging/ipack/Kconfig | 20 | ||||
-rw-r--r-- | drivers/staging/ipack/Makefile | 6 | ||||
-rw-r--r-- | drivers/staging/ipack/TODO | 46 | ||||
-rw-r--r-- | drivers/staging/ipack/bridges/Kconfig | 8 | ||||
-rw-r--r-- | drivers/staging/ipack/bridges/Makefile | 1 | ||||
-rw-r--r-- | drivers/staging/ipack/bridges/tpci200.c | 1141 | ||||
-rw-r--r-- | drivers/staging/ipack/bridges/tpci200.h | 162 | ||||
-rw-r--r-- | drivers/staging/ipack/devices/Kconfig | 7 | ||||
-rw-r--r-- | drivers/staging/ipack/devices/Makefile | 1 | ||||
-rw-r--r-- | drivers/staging/ipack/devices/ipoctal.c | 901 | ||||
-rw-r--r-- | drivers/staging/ipack/devices/ipoctal.h | 80 | ||||
-rw-r--r-- | drivers/staging/ipack/devices/scc2698.h | 228 | ||||
-rw-r--r-- | drivers/staging/ipack/ipack.c | 205 | ||||
-rw-r--r-- | drivers/staging/ipack/ipack.h | 183 |
14 files changed, 2989 insertions, 0 deletions
diff --git a/drivers/staging/ipack/Kconfig b/drivers/staging/ipack/Kconfig new file mode 100644 index 000000000000..af321789dddb --- /dev/null +++ b/drivers/staging/ipack/Kconfig @@ -0,0 +1,20 @@ +# +# IPACK configuration. +# + +menuconfig IPACK_BUS + tristate "IndustryPack bus support" + ---help--- + If you say Y here you get support for the IndustryPack Framework + for drivers for many types of boards that support this industrial + bus. The IndustryPack Framework is a virtual bus allowing to + communicate between carrier and mezzanine cards connected through + this bus. + +if IPACK_BUS + +source "drivers/staging/ipack/bridges/Kconfig" + +source "drivers/staging/ipack/devices/Kconfig" + +endif # IPACK diff --git a/drivers/staging/ipack/Makefile b/drivers/staging/ipack/Makefile new file mode 100644 index 000000000000..85ff223616fd --- /dev/null +++ b/drivers/staging/ipack/Makefile @@ -0,0 +1,6 @@ +# +# Makefile for the IPACK bridge device drivers. +# +obj-$(CONFIG_IPACK_BUS) += ipack.o +obj-y += devices/ +obj-y += bridges/ diff --git a/drivers/staging/ipack/TODO b/drivers/staging/ipack/TODO new file mode 100644 index 000000000000..3a45a53afd88 --- /dev/null +++ b/drivers/staging/ipack/TODO @@ -0,0 +1,46 @@ + TODO + ==== +Introduction +============ + +These drivers add support for IndustryPack devices: carrier and mezzanine +boards. + +The ipack driver is just an abstraction of the bus providing the common +operations between the two kind of boards. + +TODO +==== + +TPCI-200 +-------- + +* It receives the name of the mezzanine plugged in each slot by SYSFS. + No autodetection supported yet, because the mezzanine driver could not be + loaded at the time that the tpci200 driver loads. + +* It has a linked list with the tpci200 devices it is managing. Get rid of it + and use driver_for_each_device() instead. + +IP-OCTAL +-------- + +* It has a linked list which saves the devices it is currently + managing. It should use the driver_for_each_device() function. It is not there + due to the impossibility of using container_of macro to recover the + corresponding "struct ipoctal" because the attribute "struct ipack_device" is + a pointer. This code should be refactored. + +Ipack +----- + +* The structures and API exported can be improved a lot. For example, the + way to unregistering mezzanine devices, doing the mezzanine driver a call to + remove_device() to notify the carrier driver, or the opposite with the call to + the ipack_driver_ops' remove() function could be improved. + + +Contact +======= + +Contact: Samuel Iglesias Gonsalvez <siglesias@igalia.com>
\ No newline at end of file diff --git a/drivers/staging/ipack/bridges/Kconfig b/drivers/staging/ipack/bridges/Kconfig new file mode 100644 index 000000000000..97c837ea7a03 --- /dev/null +++ b/drivers/staging/ipack/bridges/Kconfig @@ -0,0 +1,8 @@ +config BOARD_TPCI200 + tristate "TEWS TPCI-200 support for IndustryPack bus" + depends on IPACK_BUS + depends on PCI + help + This driver supports the TEWS TPCI200 device for the IndustryPack bus. + default n + diff --git a/drivers/staging/ipack/bridges/Makefile b/drivers/staging/ipack/bridges/Makefile new file mode 100644 index 000000000000..d8b76459300f --- /dev/null +++ b/drivers/staging/ipack/bridges/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_BOARD_TPCI200) += tpci200.o diff --git a/drivers/staging/ipack/bridges/tpci200.c b/drivers/staging/ipack/bridges/tpci200.c new file mode 100644 index 000000000000..ad2870750235 --- /dev/null +++ b/drivers/staging/ipack/bridges/tpci200.c @@ -0,0 +1,1141 @@ +/** + * tpci200.c + * + * driver for the TEWS TPCI-200 device + * Copyright (c) 2009 Nicolas Serafini, EIC2 SA + * Copyright (c) 2010,2011 Samuel Iglesias Gonsalvez <siglesia@cern.ch>, CERN + * Copyright (c) 2012 Samuel Iglesias Gonsalvez <siglesias@igalia.com>, Igalia + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; version 2 of the License. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/module.h> +#include "tpci200.h" + +static struct ipack_bus_ops tpci200_bus_ops; + +/* TPCI200 controls registers */ +static int control_reg[] = { + TPCI200_CONTROL_A_REG, + TPCI200_CONTROL_B_REG, + TPCI200_CONTROL_C_REG, + TPCI200_CONTROL_D_REG +}; + +/* Linked list to save the registered devices */ +static LIST_HEAD(tpci200_list); + +static int tpci200_slot_unregister(struct ipack_device *dev); + +static struct tpci200_board *check_slot(struct ipack_device *dev) +{ + struct tpci200_board *tpci200; + int found = 0; + + if (dev == NULL) { + pr_info("Slot doesn't exist.\n"); + return NULL; + } + + list_for_each_entry(tpci200, &tpci200_list, list) { + if (tpci200->number == dev->bus_nr) { + found = 1; + break; + } + } + + if (!found) { + pr_err("Carrier not found\n"); + return NULL; + } + + if (dev->slot >= TPCI200_NB_SLOT) { + pr_info("Slot [%d:%d] doesn't exist! Last tpci200 slot is %d.\n", + dev->bus_nr, dev->slot, TPCI200_NB_SLOT-1); + return NULL; + } + + BUG_ON(tpci200->slots == NULL); + if (tpci200->slots[dev->slot].dev == NULL) { + pr_info("Slot [%d:%d] is not registered !\n", dev->bus_nr, + dev->slot); + return NULL; + } + + return tpci200; +} + +static inline unsigned char __tpci200_read8(void __iomem *address, + unsigned long offset) +{ + return ioread8(address + (offset^1)); +} + +static inline unsigned short __tpci200_read16(void __iomem *address, + unsigned long offset) +{ + return ioread16(address + offset); +} + +static inline unsigned int __tpci200_read32(void __iomem *address, + unsigned long offset) +{ + return swahw32(ioread32(address + offset)); +} + +static inline void __tpci200_write8(unsigned char value, + void __iomem *address, unsigned long offset) +{ + iowrite8(value, address+(offset^1)); +} + +static inline void __tpci200_write16(unsigned short value, + void __iomem *address, + unsigned long offset) +{ + iowrite16(value, address+offset); +} + +static inline void __tpci200_write32(unsigned int value, + void __iomem *address, + unsigned long offset) +{ + iowrite32(swahw32(value), address+offset); +} + +static struct ipack_addr_space *get_slot_address_space(struct ipack_device *dev, + int space) +{ + struct ipack_addr_space *addr; + + switch (space) { + case IPACK_IO_SPACE: + addr = &dev->io_space; + break; + case IPACK_ID_SPACE: + addr = &dev->id_space; + break; + case IPACK_MEM_SPACE: + addr = &dev->mem_space; + break; + default: + pr_err("Slot [%d:%d] space number %d doesn't exist !\n", + dev->bus_nr, dev->slot, space); + return NULL; + break; + } + + if ((addr->size == 0) || (addr->address == NULL)) { + pr_err("Error, slot space not mapped !\n"); + return NULL; + } + + return addr; +} + +static int tpci200_read8(struct ipack_device *dev, int space, + unsigned long offset, unsigned char *value) +{ + struct ipack_addr_space *addr; + struct tpci200_board *tpci200; + + tpci200 = check_slot(dev); + if (tpci200 == NULL) + return -EINVAL; + + addr = get_slot_address_space(dev, space); + if (addr == NULL) + return -EINVAL; + + if (offset >= addr->size) { + pr_err("Error, slot space offset error !\n"); + return -EFAULT; + } + + *value = __tpci200_read8(addr->address, offset); + + return 0; +} + +static int tpci200_read16(struct ipack_device *dev, int space, + unsigned long offset, unsigned short *value) +{ + struct ipack_addr_space *addr; + struct tpci200_board *tpci200; + + tpci200 = check_slot(dev); + if (tpci200 == NULL) + return -EINVAL; + + addr = get_slot_address_space(dev, space); + if (addr == NULL) + return -EINVAL; + + if ((offset+2) >= addr->size) { + pr_err("Error, slot space offset error !\n"); + return -EFAULT; + } + *value = __tpci200_read16(addr->address, offset); + + return 0; +} + +static int tpci200_read32(struct ipack_device *dev, int space, + unsigned long offset, unsigned int *value) +{ + struct ipack_addr_space *addr; + struct tpci200_board *tpci200; + + tpci200 = check_slot(dev); + if (tpci200 == NULL) + return -EINVAL; + + addr = get_slot_address_space(dev, space); + if (addr == NULL) + return -EINVAL; + + if ((offset+4) >= addr->size) { + pr_err("Error, slot space offset error !\n"); + return -EFAULT; + } + + *value = __tpci200_read32(addr->address, offset); + + return 0; +} + +static int tpci200_write8(struct ipack_device *dev, int space, + unsigned long offset, unsigned char value) +{ + struct ipack_addr_space *addr; + struct tpci200_board *tpci200; + + tpci200 = check_slot(dev); + if (tpci200 == NULL) + return -EINVAL; + + addr = get_slot_address_space(dev, space); + if (addr == NULL) + return -EINVAL; + + if (offset >= addr->size) { + pr_err("Error, slot space offset error !\n"); + return -EFAULT; + } + + __tpci200_write8(value, addr->address, offset); + + return 0; +} + +static int tpci200_write16(struct ipack_device *dev, int space, + unsigned long offset, unsigned short value) +{ + struct ipack_addr_space *addr; + struct tpci200_board *tpci200; + + tpci200 = check_slot(dev); + if (tpci200 == NULL) + return -EINVAL; + + addr = get_slot_address_space(dev, space); + if (addr == NULL) + return -EINVAL; + + if ((offset+2) >= addr->size) { + pr_err("Error, slot space offset error !\n"); + return -EFAULT; + } + + __tpci200_write16(value, addr->address, offset); + + return 0; +} + +static int tpci200_write32(struct ipack_device *dev, int space, + unsigned long offset, unsigned int value) +{ + struct ipack_addr_space *addr; + struct tpci200_board *tpci200; + + tpci200 = check_slot(dev); + if (tpci200 == NULL) + return -EINVAL; + + addr = get_slot_address_space(dev, space); + if (addr == NULL) + return -EINVAL; + + if ((offset+4) >= addr->size) { + pr_err("Error, slot space offset error !\n"); + return -EFAULT; + } + + __tpci200_write32(value, addr->address, offset); + + return 0; +} + +static void tpci200_unregister(struct tpci200_board *tpci200) +{ + int i; + + free_irq(tpci200->info->pdev->irq, (void *) tpci200); + + pci_iounmap(tpci200->info->pdev, tpci200->info->interface_regs); + pci_iounmap(tpci200->info->pdev, tpci200->info->ioidint_space); + pci_iounmap(tpci200->info->pdev, tpci200->info->mem8_space); + + pci_release_region(tpci200->info->pdev, TPCI200_IP_INTERFACE_BAR); + pci_release_region(tpci200->info->pdev, TPCI200_IO_ID_INT_SPACES_BAR); + pci_release_region(tpci200->info->pdev, TPCI200_MEM8_SPACE_BAR); + + pci_disable_device(tpci200->info->pdev); + pci_dev_put(tpci200->info->pdev); + + kfree(tpci200->info); + + for (i = 0; i < TPCI200_NB_SLOT; i++) { + tpci200->slots[i].io_phys.address = NULL; + tpci200->slots[i].io_phys.size = 0; + tpci200->slots[i].id_phys.address = NULL; + tpci200->slots[i].id_phys.size = 0; + tpci200->slots[i].mem_phys.address = NULL; + tpci200->slots[i].mem_phys.size = 0; + } +} + +static irqreturn_t tpci200_interrupt(int irq, void *dev_id) +{ + struct tpci200_board *tpci200 = (struct tpci200_board *) dev_id; + int i; + unsigned long flags; + unsigned short status_reg, reg_value; + unsigned short unhandled_ints = 0; + irqreturn_t ret = IRQ_NONE; + + spin_lock_irqsave(&tpci200->info->access_lock, flags); + + /* Read status register */ + status_reg = readw(tpci200->info->interface_regs + + TPCI200_STATUS_REG); + + if (status_reg & TPCI200_SLOT_INT_MASK) { + unhandled_ints = status_reg & TPCI200_SLOT_INT_MASK; + /* callback to the IRQ handler for the corresponding slot */ + for (i = 0; i < TPCI200_NB_SLOT; i++) { + if ((tpci200->slots[i].irq != NULL) && + (status_reg & ((TPCI200_A_INT0 | TPCI200_A_INT1) << (2*i)))) { + + ret = tpci200->slots[i].irq->handler(tpci200->slots[i].irq->arg); + + /* Dummy reads */ + readw(tpci200->slots[i].dev->io_space.address + + 0xC0); + readw(tpci200->slots[i].dev->io_space.address + + 0xC2); + + unhandled_ints &= ~(((TPCI200_A_INT0 | TPCI200_A_INT1) << (2*i))); + } + } + } + /* Interrupt not handled are disabled */ + if (unhandled_ints) { + for (i = 0; i < TPCI200_NB_SLOT; i++) { + if (unhandled_ints & ((TPCI200_INT0_EN | TPCI200_INT1_EN) << (2*i))) { + pr_info("No registered ISR for slot [%d:%d]!. IRQ will be disabled.\n", + tpci200->number, i); + reg_value = readw( + tpci200->info->interface_regs + + control_reg[i]); + reg_value &= + ~(TPCI200_INT0_EN | TPCI200_INT1_EN); + writew(reg_value, + (tpci200->info->interface_regs + + control_reg[i])); + } + } + } + + spin_unlock_irqrestore(&tpci200->info->access_lock, flags); + return ret; +} + +#ifdef CONFIG_SYSFS + +static struct ipack_device *tpci200_slot_register(unsigned int tpci200_number, + unsigned int slot_position) +{ + int found = 0; + struct ipack_device *dev; + struct tpci200_board *tpci200; + + list_for_each_entry(tpci200, &tpci200_list, list) { + if (tpci200->number == tpci200_number) { + found = 1; + break; + } + } + + if (!found) { + pr_err("carrier board not found for the device\n"); + return NULL; + } + + if (slot_position >= TPCI200_NB_SLOT) { + pr_info("Slot [%d:%d] doesn't exist!\n", tpci200_number, + slot_position); + return NULL; + } + + if (mutex_lock_interruptible(&tpci200->mutex)) + return NULL; + + if (tpci200->slots[slot_position].dev != NULL) { + pr_err("Slot [%d:%d] already installed !\n", tpci200_number, + slot_position); + goto err_unlock; + } + + /* + * Give the same IRQ number as the slot number. + * The TPCI200 has assigned his own two IRQ by PCI bus driver + */ + dev = ipack_device_register(tpci200->info->ipack_bus, + slot_position, slot_position); + if (dev == NULL) { + pr_info("Slot [%d:%d] Unable to register an ipack device\n", + tpci200_number, slot_position); + goto err_unlock; + } + + tpci200->slots[slot_position].dev = dev; + mutex_unlock(&tpci200->mutex); + return dev; + +err_unlock: + mutex_unlock(&tpci200->mutex); + return NULL; +} + +static ssize_t tpci200_store_board(struct device *pdev, const char *buf, + size_t count, int slot) +{ + struct tpci200_board *card = dev_get_drvdata(pdev); + struct ipack_device *dev = card->slots[slot].dev; + + if (dev != NULL) + return -EBUSY; + + dev = tpci200_slot_register(card->number, slot); + if (dev == NULL) + return -ENODEV; + + return count; +} + +static ssize_t tpci200_show_board(struct device *pdev, char *buf, int slot) +{ + struct tpci200_board *card = dev_get_drvdata(pdev); + struct ipack_device *dev = card->slots[slot].dev; + + if (dev != NULL) + return snprintf(buf, PAGE_SIZE, "%s\n", dev_name(&dev->dev)); + else + return snprintf(buf, PAGE_SIZE, "none\n"); +} + +static ssize_t tpci200_show_description(struct device *pdev, + struct device_attribute *attr, + char *buf) +{ + return snprintf(buf, PAGE_SIZE, + "TEWS tpci200 carrier PCI for Industry-pack mezzanines.\n"); +} + +static ssize_t tpci200_show_board_slot0(struct device *pdev, + struct device_attribute *attr, + char *buf) +{ + return tpci200_show_board(pdev, buf, 0); +} + +static ssize_t tpci200_store_board_slot0(struct device *pdev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + return tpci200_store_board(pdev, buf, count, 0); +} + +static ssize_t tpci200_show_board_slot1(struct device *pdev, + struct device_attribute *attr, + char *buf) +{ + return tpci200_show_board(pdev, buf, 1); +} + +static ssize_t tpci200_store_board_slot1(struct device *pdev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + return tpci200_store_board(pdev, buf, count, 1); +} + +static ssize_t tpci200_show_board_slot2(struct device *pdev, + struct device_attribute *attr, + char *buf) +{ + return tpci200_show_board(pdev, buf, 2); +} + +static ssize_t tpci200_store_board_slot2(struct device *pdev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + return tpci200_store_board(pdev, buf, count, 2); +} + + +static ssize_t tpci200_show_board_slot3(struct device *pdev, + struct device_attribute *attr, + char *buf) +{ + return tpci200_show_board(pdev, buf, 3); +} + +static ssize_t tpci200_store_board_slot3(struct device *pdev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + return tpci200_store_board(pdev, buf, count, 3); +} + +/* Declaration of the device attributes for the TPCI200 */ +static DEVICE_ATTR(description, S_IRUGO, + tpci200_show_description, NULL); +static DEVICE_ATTR(board_slot0, S_IRUGO | S_IWUSR, + tpci200_show_board_slot0, tpci200_store_board_slot0); +static DEVICE_ATTR(board_slot1, S_IRUGO | S_IWUSR, + tpci200_show_board_slot1, tpci200_store_board_slot1); +static DEVICE_ATTR(board_slot2, S_IRUGO | S_IWUSR, + tpci200_show_board_slot2, tpci200_store_board_slot2); +static DEVICE_ATTR(board_slot3, S_IRUGO | S_IWUSR, + tpci200_show_board_slot3, tpci200_store_board_slot3); + +static struct attribute *tpci200_attrs[] = { + &dev_attr_description.attr, + &dev_attr_board_slot0.attr, + &dev_attr_board_slot1.attr, + &dev_attr_board_slot2.attr, + &dev_attr_board_slot3.attr, + NULL, +}; + +static struct attribute_group tpci200_attr_group = { + .attrs = tpci200_attrs, +}; + +static int tpci200_create_sysfs_files(struct tpci200_board *card) +{ + return sysfs_create_group(&card->info->pdev->dev.kobj, + &tpci200_attr_group); +} + +static void tpci200_remove_sysfs_files(struct tpci200_board *card) +{ + sysfs_remove_group(&card->info->pdev->dev.kobj, &tpci200_attr_group); +} + +#else + +static int tpci200_create_sysfs_files(struct tpci200_board *card) +{ + return 0; +} + +static void tpci200_remove_sysfs_files(struct tpci200_board *card) +{ +} + +#endif /* CONFIG_SYSFS */ + +static int tpci200_register(struct tpci200_board *tpci200) +{ + int i; + int res; + unsigned long ioidint_base; + unsigned long mem_base; + unsigned short slot_ctrl; + + if (pci_enable_device(tpci200->info->pdev) < 0) + return -ENODEV; + + if (tpci200_create_sysfs_files(tpci200) < 0) { + pr_err("failed creating sysfs files\n"); + res = -EFAULT; + goto out_disable_pci; + } + + /* Request IP interface register (Bar 2) */ + res = pci_request_region(tpci200->info->pdev, TPCI200_IP_INTERFACE_BAR, + "Carrier IP interface registers"); + if (res) { + pr_err("(bn 0x%X, sn 0x%X) failed to allocate PCI resource for BAR 2 !", + tpci200->info->pdev->bus->number, + tpci200->info->pdev->devfn); + goto out_remove_sysfs; + } + + /* Request IO ID INT space (Bar 3) */ + res = pci_request_region(tpci200->info->pdev, + TPCI200_IO_ID_INT_SPACES_BAR, + "Carrier IO ID INT space"); + if (res) { + pr_err("(bn 0x%X, sn 0x%X) failed to allocate PCI resource for BAR 3 !", + tpci200->info->pdev->bus->number, + tpci200->info->pdev->devfn); + goto out_release_ip_space; + } + + /* Request MEM space (Bar 4) */ + res = pci_request_region(tpci200->info->pdev, TPCI200_MEM8_SPACE_BAR, + "Carrier MEM space"); + if (res) { + pr_err("(bn 0x%X, sn 0x%X) failed to allocate PCI resource for BAR 4!", + tpci200->info->pdev->bus->number, + tpci200->info->pdev->devfn); + goto out_release_ioid_int_space; + } + + /* Map internal tpci200 driver user space */ + tpci200->info->interface_regs = + ioremap(pci_resource_start(tpci200->info->pdev, + TPCI200_IP_INTERFACE_BAR), + TPCI200_IFACE_SIZE); + tpci200->info->ioidint_space = + ioremap(pci_resource_start(tpci200->info->pdev, + TPCI200_IO_ID_INT_SPACES_BAR), + TPCI200_IOIDINT_SIZE); + tpci200->info->mem8_space = + ioremap(pci_resource_start(tpci200->info->pdev, + TPCI200_MEM8_SPACE_BAR), + TPCI200_MEM8_SIZE); + + spin_lock_init(&tpci200->info->access_lock); + ioidint_base = pci_resource_start(tpci200->info->pdev, + TPCI200_IO_ID_INT_SPACES_BAR); + mem_base = pci_resource_start(tpci200->info->pdev, + TPCI200_MEM8_SPACE_BAR); + + /* Set the default parameters of the slot + * INT0 disabled, level sensitive + * INT1 disabled, level sensitive + * error interrupt disabled + * timeout interrupt disabled + * recover time disabled + * clock rate 8 MHz + */ + slot_ctrl = 0; + + /* Set all slot physical address space */ + for (i = 0; i < TPCI200_NB_SLOT; i++) { + tpci200->slots[i].io_phys.address = + (void __iomem *)ioidint_base + + TPCI200_IO_SPACE_OFF + TPCI200_IO_SPACE_GAP*i; + tpci200->slots[i].io_phys.size = TPCI200_IO_SPACE_SIZE; + + tpci200->slots[i].id_phys.address = + (void __iomem *)ioidint_base + + TPCI200_ID_SPACE_OFF + TPCI200_ID_SPACE_GAP*i; + tpci200->slots[i].id_phys.size = TPCI200_ID_SPACE_SIZE; + + tpci200->slots[i].mem_phys.address = + (void __iomem *)mem_base + TPCI200_MEM8_GAP*i; + tpci200->slots[i].mem_phys.size = TPCI200_MEM8_SIZE; + + writew(slot_ctrl, (tpci200->info->interface_regs + + control_reg[i])); + } + + res = request_irq(tpci200->info->pdev->irq, + tpci200_interrupt, IRQF_SHARED, + KBUILD_MODNAME, (void *) tpci200); + if (res) { + pr_err("(bn 0x%X, sn 0x%X) unable to register IRQ !", + tpci200->info->pdev->bus->number, + tpci200->info->pdev->devfn); + tpci200_unregister(tpci200); + goto out_err; + } + + return 0; + +out_release_ioid_int_space: + pci_release_region(tpci200->info->pdev, TPCI200_IO_ID_INT_SPACES_BAR); +out_release_ip_space: + pci_release_region(tpci200->info->pdev, TPCI200_IP_INTERFACE_BAR); +out_remove_sysfs: + tpci200_remove_sysfs_files(tpci200); +out_disable_pci: + pci_disable_device(tpci200->info->pdev); +out_err: + return res; +} + +static int __tpci200_request_irq(struct tpci200_board *tpci200, + struct ipack_device *dev) +{ + unsigned short slot_ctrl; + + /* Set the default parameters of the slot + * INT0 enabled, level sensitive + * INT1 enabled, level sensitive + * error interrupt disabled + * timeout interrupt disabled + * recover time disabled + * clock rate 8 MHz + */ + slot_ctrl = TPCI200_INT0_EN | TPCI200_INT1_EN; + writew(slot_ctrl, (tpci200->info->interface_regs + + control_reg[dev->slot])); + + return 0; +} + +static void __tpci200_free_irq(struct tpci200_board *tpci200, + struct ipack_device *dev) +{ + unsigned short slot_ctrl; + + /* Set the default parameters of the slot + * INT0 disabled, level sensitive + * INT1 disabled, level sensitive + * error interrupt disabled + * timeout interrupt disabled + * recover time disabled + * clock rate 8 MHz + */ + slot_ctrl = 0; + writew(slot_ctrl, (tpci200->info->interface_regs + + control_reg[dev->slot])); +} + +static int tpci200_free_irq(struct ipack_device *dev) +{ + int res; + struct slot_irq *slot_irq; + struct tpci200_board *tpci200; + + tpci200 = check_slot(dev); + if (tpci200 == NULL) { + res = -EINVAL; + goto out; + } + + if (mutex_lock_interruptible(&tpci200->mutex)) { + res = -ERESTARTSYS; + goto out; + } + + if (tpci200->slots[dev->slot].irq == NULL) { + res = -EINVAL; + goto out_unlock; + } + + __tpci200_free_irq(tpci200, dev); + slot_irq = tpci200->slots[dev->slot].irq; + tpci200->slots[dev->slot].irq = NULL; + kfree(slot_irq); + +out_unlock: + mutex_unlock(&tpci200->mutex); +out: + return res; +} + +static int tpci200_slot_unmap_space(struct ipack_device *dev, int space) +{ + int res; + struct ipack_addr_space *virt_addr_space; + struct tpci200_board *tpci200; + + tpci200 = check_slot(dev); + if (tpci200 == NULL) { + res = -EINVAL; + goto out; + } + + if (mutex_lock_interruptible(&tpci200->mutex)) { + res = -ERESTARTSYS; + goto out; + } + + switch (space) { + case IPACK_IO_SPACE: + if (dev->io_space.address == NULL) { + pr_info("Slot [%d:%d] IO space not mapped !\n", + dev->bus_nr, dev->slot); + goto out_unlock; + } + virt_addr_space = &dev->io_space; + break; + case IPACK_ID_SPACE: + if (dev->id_space.address == NULL) { + pr_info("Slot [%d:%d] ID space not mapped !\n", + dev->bus_nr, dev->slot); + goto out_unlock; + } + virt_addr_space = &dev->id_space; + break; + case IPACK_MEM_SPACE: + if (dev->mem_space.address == NULL) { + pr_info("Slot [%d:%d] MEM space not mapped !\n", + dev->bus_nr, dev->slot); + goto out_unlock; + } + virt_addr_space = &dev->mem_space; + break; + default: + pr_err("Slot [%d:%d] space number %d doesn't exist !\n", + dev->bus_nr, dev->slot, space); + res = -EINVAL; + goto out_unlock; + break; + } + + iounmap(virt_addr_space->address); + + virt_addr_space->address = NULL; + virt_addr_space->size = 0; +out_unlock: + mutex_unlock(&tpci200->mutex); +out: + return res; +} + +static int tpci200_slot_unregister(struct ipack_device *dev) +{ + struct tpci200_board *tpci200; + + if (dev == NULL) + return -ENODEV; + + tpci200 = check_slot(dev); + if (tpci200 == NULL) + return -EINVAL; + + tpci200_free_irq(dev); + + if (mutex_lock_interruptible(&tpci200->mutex)) + return -ERESTARTSYS; + + ipack_device_unregister(dev); + tpci200->slots[dev->slot].dev = NULL; + mutex_unlock(&tpci200->mutex); + + return 0; +} + +static int tpci200_slot_map_space(struct ipack_device *dev, + unsigned int memory_size, int space) +{ + int res; + unsigned int size_to_map; + void __iomem *phys_address; + struct ipack_addr_space *virt_addr_space; + struct tpci200_board *tpci200; + + tpci200 = check_slot(dev); + if (tpci200 == NULL) { + res = -EINVAL; + goto out; + } + + if (mutex_lock_interruptible(&tpci200->mutex)) { + res = -ERESTARTSYS; + goto out; + } + + switch (space) { + case IPACK_IO_SPACE: + if (dev->io_space.address != NULL) { + pr_err("Slot [%d:%d] IO space already mapped !\n", + tpci200->number, dev->slot); + res = -EINVAL; + goto out_unlock; + } + virt_addr_space = &dev->io_space; + + phys_address = tpci200->slots[dev->slot].io_phys.address; + size_to_map = tpci200->slots[dev->slot].io_phys.size; + break; + case IPACK_ID_SPACE: + if (dev->id_space.address != NULL) { + pr_err("Slot [%d:%d] ID space already mapped !\n", + tpci200->number, dev->slot); + res = -EINVAL; + goto out_unlock; + } + virt_addr_space = &dev->id_space; + + phys_address = tpci200->slots[dev->slot].id_phys.address; + size_to_map = tpci200->slots[dev->slot].id_phys.size; + break; + case IPACK_MEM_SPACE: + if (dev->mem_space.address != NULL) { + pr_err("Slot [%d:%d] MEM space already mapped !\n", + tpci200->number, dev->slot); + res = -EINVAL; + goto out_unlock; + } + virt_addr_space = &dev->mem_space; + + if (memory_size > tpci200->slots[dev->slot].mem_phys.size) { + pr_err("Slot [%d:%d] request is 0x%X memory, only 0x%X available !\n", + dev->bus_nr, dev->slot, memory_size, + tpci200->slots[dev->slot].mem_phys.size); + res = -EINVAL; + goto out_unlock; + } + + phys_address = tpci200->slots[dev->slot].mem_phys.address; + size_to_map = memory_size; + break; + default: + pr_err("Slot [%d:%d] space %d doesn't exist !\n", + tpci200->number, dev->slot, space); + res = -EINVAL; + goto out_unlock; + break; + } + + virt_addr_space->size = size_to_map; + virt_addr_space->address = + ioremap((unsigned long)phys_address, size_to_map); + +out_unlock: + mutex_unlock(&tpci200->mutex); +out: + return res; +} + +static int tpci200_request_irq(struct ipack_device *dev, int vector, + int (*handler)(void *), void *arg) +{ + int res; + struct slot_irq *slot_irq; + struct tpci200_board *tpci200; + + tpci200 = check_slot(dev); + if (tpci200 == NULL) { + res = -EINVAL; + goto out; + } + + if (mutex_lock_interruptible(&tpci200->mutex)) { + res = -ERESTARTSYS; + goto out; + } + + if (tpci200->slots[dev->slot].irq != NULL) { + pr_err("Slot [%d:%d] IRQ already registered !\n", dev->bus_nr, + dev->slot); + res = -EINVAL; + goto out_unlock; + } + + slot_irq = kzalloc(sizeof(struct slot_irq), GFP_KERNEL); + if (slot_irq == NULL) { + pr_err("Slot [%d:%d] unable to allocate memory for IRQ !\n", + dev->bus_nr, dev->slot); + res = -ENOMEM; + goto out_unlock; + } + + /* + * WARNING: Setup Interrupt Vector in the IndustryPack device + * before an IRQ request. + * Read the User Manual of your IndustryPack device to know + * where to write the vector in memory. + */ + slot_irq->vector = vector; + slot_irq->handler = handler; + slot_irq->arg = arg; + slot_irq->name = dev_name(&dev->dev); + + tpci200->slots[dev->slot].irq = slot_irq; + res = __tpci200_request_irq(tpci200, dev); + +out_unlock: + mutex_unlock(&tpci200->mutex); +out: + return res; +} + +static void tpci200_slot_remove(struct tpci200_slot *slot) +{ + if ((slot->dev == NULL) || + (slot->dev->driver->ops->remove == NULL)) + return; + + slot->dev->driver->ops->remove(slot->dev); +} + +static void tpci200_uninstall(struct tpci200_board *tpci200) +{ + int i; + + for (i = 0; i < TPCI200_NB_SLOT; i++) + tpci200_slot_remove(&tpci200->slots[i]); + + tpci200_unregister(tpci200); + kfree(tpci200->slots); +} + +static struct ipack_bus_ops tpci200_bus_ops = { + .map_space = tpci200_slot_map_space, + .unmap_space = tpci200_slot_unmap_space, + .request_irq = tpci200_request_irq, + .free_irq = tpci200_free_irq, + .read8 = tpci200_read8, + .read16 = tpci200_read16, + .read32 = tpci200_read32, + .write8 = tpci200_write8, + .write16 = tpci200_write16, + .write32 = tpci200_write32, + .remove_device = tpci200_slot_unregister, +}; + +static int tpci200_install(struct tpci200_board *tpci200) +{ + int res; + + tpci200->slots = kzalloc( + TPCI200_NB_SLOT * sizeof(struct tpci200_slot), GFP_KERNEL); + if (tpci200->slots == NULL) { + res = -ENOMEM; + goto out_err; + } + + res = tpci200_register(tpci200); + if (res) + goto out_free; + + mutex_init(&tpci200->mutex); + return 0; + +out_free: + kfree(tpci200->slots); + tpci200->slots = NULL; +out_err: + return res; +} + +static int tpci200_pciprobe(struct pci_dev *pdev, + const struct pci_device_id *id) +{ + int ret; + struct tpci200_board *tpci200; + + tpci200 = kzalloc(sizeof(struct tpci200_board), GFP_KERNEL); + if (!tpci200) + return -ENOMEM; + + tpci200->info = kzalloc(sizeof(struct tpci200_infos), GFP_KERNEL); + if (!tpci200->info) { + kfree(tpci200); + return -ENOMEM; + } + + /* Save struct pci_dev pointer */ + tpci200->info->pdev = pdev; + tpci200->info->id_table = (struct pci_device_id *)id; + + /* register the device and initialize it */ + ret = tpci200_install(tpci200); + if (ret) { + pr_err("Error during tpci200 install !\n"); + kfree(tpci200->info); + kfree(tpci200); + return -ENODEV; + } + + /* Register the carrier in the industry pack bus driver */ + tpci200->info->ipack_bus = ipack_bus_register(&pdev->dev, + TPCI200_NB_SLOT, + &tpci200_bus_ops); + if (!tpci200->info->ipack_bus) { + pr_err("error registering the carrier on ipack driver\n"); + tpci200_uninstall(tpci200); + kfree(tpci200->info); + kfree(tpci200); + return -EFAULT; + } + + /* save the bus number given by ipack to logging purpose */ + tpci200->number = tpci200->info->ipack_bus->bus_nr; + dev_set_drvdata(&pdev->dev, tpci200); + /* add the registered device in an internal linked list */ + list_add_tail(&tpci200->list, &tpci200_list); + return ret; +} + +static void __tpci200_pci_remove(struct tpci200_board *tpci200) +{ + tpci200_uninstall(tpci200); + tpci200_remove_sysfs_files(tpci200); + list_del(&tpci200->list); + ipack_bus_unregister(tpci200->info->ipack_bus); + kfree(tpci200->info); + kfree(tpci200); +} + +static void __devexit tpci200_pci_remove(struct pci_dev *dev) +{ + struct tpci200_board *tpci200, *next; + + /* Search the registered device to uninstall it */ + list_for_each_entry_safe(tpci200, next, &tpci200_list, list) { + if (tpci200->info->pdev == dev) { + __tpci200_pci_remove(tpci200); + break; + } + } +} + +static struct pci_device_id tpci200_idtable[2] = { + { TPCI200_VENDOR_ID, TPCI200_DEVICE_ID, TPCI200_SUBVENDOR_ID, + TPCI200_SUBDEVICE_ID }, + { 0, }, +}; + +static struct pci_driver tpci200_pci_drv = { + .name = "tpci200", + .id_table = tpci200_idtable, + .probe = tpci200_pciprobe, + .remove = __devexit_p(tpci200_pci_remove), +}; + +static int __init tpci200_drvr_init_module(void) +{ + return pci_register_driver(&tpci200_pci_drv); +} + +static void __exit tpci200_drvr_exit_module(void) +{ + struct tpci200_board *tpci200, *next; + + list_for_each_entry_safe(tpci200, next, &tpci200_list, list) + __tpci200_pci_remove(tpci200); + + pci_unregister_driver(&tpci200_pci_drv); +} + +MODULE_DESCRIPTION("TEWS TPCI-200 device driver"); +MODULE_LICENSE("GPL"); +module_init(tpci200_drvr_init_module); +module_exit(tpci200_drvr_exit_module); diff --git a/drivers/staging/ipack/bridges/tpci200.h b/drivers/staging/ipack/bridges/tpci200.h new file mode 100644 index 000000000000..0b547eedddc2 --- /dev/null +++ b/drivers/staging/ipack/bridges/tpci200.h @@ -0,0 +1,162 @@ +/** + * tpci200.h + * + * driver for the carrier TEWS TPCI-200 + * Copyright (c) 2009 Nicolas Serafini, EIC2 SA + * Copyright (c) 2010,2011 Samuel Iglesias Gonsalvez <siglesia@cern.ch>, CERN + * Copyright (c) 2012 Samuel Iglesias Gonsalvez <siglesias@igalia.com>, Igalia + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; version 2 of the License. + */ + +#ifndef _TPCI200_H_ +#define _TPCI200_H_ + +#include <linux/version.h> +#include <linux/limits.h> +#include <linux/pci.h> +#include <linux/spinlock.h> +#include <linux/interrupt.h> +#include <linux/swab.h> +#include <linux/io.h> + +#include "../ipack.h" + +#define TPCI200_NB_SLOT 0x4 +#define TPCI200_NB_BAR 0x6 + +#define TPCI200_VENDOR_ID 0x1498 +#define TPCI200_DEVICE_ID 0x30C8 +#define TPCI200_SUBVENDOR_ID 0x1498 +#define TPCI200_SUBDEVICE_ID 0x300A + +#define TPCI200_IP_INTERFACE_BAR 2 +#define TPCI200_IO_ID_INT_SPACES_BAR 3 +#define TPCI200_MEM16_SPACE_BAR 4 +#define TPCI200_MEM8_SPACE_BAR 5 + +#define TPCI200_REVISION_REG 0x00 +#define TPCI200_CONTROL_A_REG 0x02 +#define TPCI200_CONTROL_B_REG 0x04 +#define TPCI200_CONTROL_C_REG 0x06 +#define TPCI200_CONTROL_D_REG 0x08 +#define TPCI200_RESET_REG 0x0A +#define TPCI200_STATUS_REG 0x0C + +#define TPCI200_IFACE_SIZE 0x100 + +#define TPCI200_IO_SPACE_OFF 0x0000 +#define TPCI200_IO_SPACE_GAP 0x0100 +#define TPCI200_IO_SPACE_SIZE 0x0080 +#define TPCI200_ID_SPACE_OFF 0x0080 +#define TPCI200_ID_SPACE_GAP 0x0100 +#define TPCI200_ID_SPACE_SIZE 0x0040 +#define TPCI200_INT_SPACE_OFF 0x00C0 +#define TPCI200_INT_SPACE_GAP 0x0100 +#define TPCI200_INT_SPACE_SIZE 0x0040 +#define TPCI200_IOIDINT_SIZE 0x0400 + +#define TPCI200_MEM8_GAP 0x00400000 +#define TPCI200_MEM8_SIZE 0x00400000 +#define TPCI200_MEM16_GAP 0x00800000 +#define TPCI200_MEM16_SIZE 0x00800000 + +#define TPCI200_INT0_EN 0x0040 +#define TPCI200_INT1_EN 0x0080 +#define TPCI200_INT0_EDGE 0x0010 +#define TPCI200_INT1_EDGE 0x0020 +#define TPCI200_ERR_INT_EN 0x0008 +#define TPCI200_TIME_INT_EN 0x0004 +#define TPCI200_RECOVER_EN 0x0002 +#define TPCI200_CLK32 0x0001 + +#define TPCI200_A_RESET 0x0001 +#define TPCI200_B_RESET 0x0002 +#define TPCI200_C_RESET 0x0004 +#define TPCI200_D_RESET 0x0008 + +#define TPCI200_A_TIMEOUT 0x1000 +#define TPCI200_B_TIMEOUT 0x2000 +#define TPCI200_C_TIMEOUT 0x4000 +#define TPCI200_D_TIMEOUT 0x8000 + +#define TPCI200_A_ERROR 0x0100 +#define TPCI200_B_ERROR 0x0200 +#define TPCI200_C_ERROR 0x0400 +#define TPCI200_D_ERROR 0x0800 + +#define TPCI200_A_INT0 0x0001 +#define TPCI200_A_INT1 0x0002 +#define TPCI200_B_INT0 0x0004 +#define TPCI200_B_INT1 0x0008 +#define TPCI200_C_INT0 0x0010 +#define TPCI200_C_INT1 0x0020 +#define TPCI200_D_INT0 0x0040 +#define TPCI200_D_INT1 0x0080 + +#define TPCI200_SLOT_INT_MASK 0x00FF + +#define VME_IOID_SPACE "IOID" +#define VME_MEM_SPACE "MEM" + +/** + * struct slot_irq - slot IRQ definition. + * @vector Vector number + * @handler Handler called when IRQ arrives + * @arg Handler argument + * @name IRQ name + * + */ +struct slot_irq { + int vector; + int (*handler)(void *); + void *arg; + const char *name; +}; + +/** + * struct tpci200_slot - data specific to the tpci200 slot. + * @slot_id Slot identification gived to external interface + * @irq Slot IRQ infos + * @io_phys IO physical base address register of the slot + * @id_phys ID physical base address register of the slot + * @mem_phys MEM physical base address register of the slot + * + */ +struct tpci200_slot { + struct ipack_device *dev; + struct slot_irq *irq; + struct ipack_addr_space io_phys; + struct ipack_addr_space id_phys; + struct ipack_addr_space mem_phys; +}; + +/** + * struct tpci200_infos - informations specific of the TPCI200 tpci200. + * @pci_dev PCI device + * @interface_regs Pointer to IP interface space (Bar 2) + * @ioidint_space Pointer to IP ID, IO and INT space (Bar 3) + * @mem8_space Pointer to MEM space (Bar 4) + * @access_lock Mutex lock for simultaneous access + * + */ +struct tpci200_infos { + struct pci_dev *pdev; + struct pci_device_id *id_table; + void __iomem *interface_regs; + void __iomem *ioidint_space; + void __iomem *mem8_space; + spinlock_t access_lock; + struct ipack_bus_device *ipack_bus; +}; +struct tpci200_board { + struct list_head list; + unsigned int number; + struct mutex mutex; + struct tpci200_slot *slots; + struct tpci200_infos *info; +}; + +#endif /* _TPCI200_H_ */ diff --git a/drivers/staging/ipack/devices/Kconfig b/drivers/staging/ipack/devices/Kconfig new file mode 100644 index 000000000000..39f71888a584 --- /dev/null +++ b/drivers/staging/ipack/devices/Kconfig @@ -0,0 +1,7 @@ +config SERIAL_IPOCTAL + tristate "IndustryPack IP-OCTAL uart support" + depends on IPACK_BUS + help + This driver supports the IPOCTAL serial port device for the IndustryPack bus. + default n + diff --git a/drivers/staging/ipack/devices/Makefile b/drivers/staging/ipack/devices/Makefile new file mode 100644 index 000000000000..6de18bda4a9a --- /dev/null +++ b/drivers/staging/ipack/devices/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_SERIAL_IPOCTAL) += ipoctal.o diff --git a/drivers/staging/ipack/devices/ipoctal.c b/drivers/staging/ipack/devices/ipoctal.c new file mode 100644 index 000000000000..29f6fa841d23 --- /dev/null +++ b/drivers/staging/ipack/devices/ipoctal.c @@ -0,0 +1,901 @@ +/** + * ipoctal.c + * + * driver for the GE IP-OCTAL boards + * Copyright (c) 2009 Nicolas Serafini, EIC2 SA + * Copyright (c) 2010,2011 Samuel Iglesias Gonsalvez <siglesia@cern.ch>, CERN + * Copyright (c) 2012 Samuel Iglesias Gonsalvez <siglesias@igalia.com>, Igalia + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; version 2 of the License. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/device.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/interrupt.h> +#include <linux/fs.h> +#include <linux/delay.h> +#include <linux/sched.h> +#include <linux/tty.h> +#include <linux/serial.h> +#include <linux/tty_flip.h> +#include <linux/slab.h> +#include <linux/uaccess.h> +#include <linux/atomic.h> +#include "../ipack.h" +#include "ipoctal.h" +#include "scc2698.h" + +#define IP_OCTAL_MANUFACTURER_ID 0xF0 +#define IP_OCTAL_232_ID 0x22 +#define IP_OCTAL_422_ID 0x2A +#define IP_OCTAL_485_ID 0x48 + +#define IP_OCTAL_ID_SPACE_VECTOR 0x41 +#define IP_OCTAL_NB_BLOCKS 4 + +static struct ipack_driver driver; +static const struct tty_operations ipoctal_fops; + +struct ipoctal { + struct list_head list; + struct ipack_device *dev; + unsigned int board_id; + struct scc2698_channel *chan_regs; + struct scc2698_block *block_regs; + struct ipoctal_stats chan_stats[NR_CHANNELS]; + char *buffer[NR_CHANNELS]; + unsigned int nb_bytes[NR_CHANNELS]; + unsigned int count_wr[NR_CHANNELS]; + struct ipoctal_config chan_config[NR_CHANNELS]; + wait_queue_head_t queue[NR_CHANNELS]; + unsigned short error_flag[NR_CHANNELS]; + spinlock_t lock[NR_CHANNELS]; + unsigned int pointer_read[NR_CHANNELS]; + unsigned int pointer_write[NR_CHANNELS]; + atomic_t open[NR_CHANNELS]; + unsigned char write; + struct tty_port tty_port[NR_CHANNELS]; + struct tty_driver *tty_drv; +}; + +/* Linked list to save the registered devices */ +static LIST_HEAD(ipoctal_list); + +static inline void ipoctal_write_io_reg(struct ipoctal *ipoctal, + unsigned char *dest, + unsigned char value) +{ + unsigned long offset; + + offset = ((void __iomem *) dest) - ipoctal->dev->io_space.address; + ipoctal->dev->bus->ops->write8(ipoctal->dev, IPACK_IO_SPACE, offset, + value); +} + +static inline void ipoctal_write_cr_cmd(struct ipoctal *ipoctal, + unsigned char *dest, + unsigned char value) +{ + ipoctal_write_io_reg(ipoctal, dest, value); +} + +static inline unsigned char ipoctal_read_io_reg(struct ipoctal *ipoctal, + unsigned char *src) +{ + unsigned long offset; + unsigned char value; + + offset = ((void __iomem *) src) - ipoctal->dev->io_space.address; + ipoctal->dev->bus->ops->read8(ipoctal->dev, IPACK_IO_SPACE, offset, + &value); + return value; +} + +static struct ipoctal *ipoctal_find_board(struct tty_struct *tty) +{ + struct ipoctal *p; + + list_for_each_entry(p, &ipoctal_list, list) { + if (tty->driver->major == p->tty_drv->major) + return p; + } + + return NULL; +} + +static int ipoctal_port_activate(struct tty_port *port, struct tty_struct *tty) +{ + struct ipoctal *ipoctal; + int channel = tty->index; + + ipoctal = ipoctal_find_board(tty); + + if (ipoctal == NULL) { + pr_err("Device not found. Major %d\n", tty->driver->major); + return -ENODEV; + } + + ipoctal_write_io_reg(ipoctal, &ipoctal->chan_regs[channel].u.w.cr, + CR_ENABLE_RX); + tty->driver_data = ipoctal; + + return 0; +} + +static int ipoctal_open(struct tty_struct *tty, struct file *file) +{ + int channel = tty->index; + int res; + struct ipoctal *ipoctal; + + ipoctal = ipoctal_find_board(tty); + + if (ipoctal == NULL) { + pr_err("Device not found. Major %d\n", tty->driver->major); + return -ENODEV; + } + + if (atomic_read(&ipoctal->open[channel])) + return -EBUSY; + + res = tty_port_open(&ipoctal->tty_port[channel], tty, file); + if (res) + return res; + + atomic_inc(&ipoctal->open[channel]); + return 0; +} + +static void ipoctal_reset_stats(struct ipoctal_stats *stats) +{ + stats->tx = 0; + stats->rx = 0; + stats->rcv_break = 0; + stats->framing_err = 0; + stats->overrun_err = 0; + stats->parity_err = 0; +} + +static void ipoctal_free_channel(struct tty_struct *tty) +{ + int channel = tty->index; + struct ipoctal *ipoctal = tty->driver_data; + + if (ipoctal == NULL) + return; + + ipoctal_reset_stats(&ipoctal->chan_stats[channel]); + ipoctal->pointer_read[channel] = 0; + ipoctal->pointer_write[channel] = 0; + ipoctal->nb_bytes[channel] = 0; +} + +static void ipoctal_close(struct tty_struct *tty, struct file *filp) +{ + int channel = tty->index; + struct ipoctal *ipoctal = tty->driver_data; + + tty_port_close(&ipoctal->tty_port[channel], tty, filp); + + if (atomic_dec_and_test(&ipoctal->open[channel])) + ipoctal_free_channel(tty); +} + +static int ipoctal_get_icount(struct tty_struct *tty, + struct serial_icounter_struct *icount) +{ + struct ipoctal *ipoctal = tty->driver_data; + int channel = tty->index; + + icount->cts = 0; + icount->dsr = 0; + icount->rng = 0; + icount->dcd = 0; + icount->rx = ipoctal->chan_stats[channel].rx; + icount->tx = ipoctal->chan_stats[channel].tx; + icount->frame = ipoctal->chan_stats[channel].framing_err; + icount->parity = ipoctal->chan_stats[channel].parity_err; + icount->brk = ipoctal->chan_stats[channel].rcv_break; + return 0; +} + +static int ipoctal_irq_handler(void *arg) +{ + unsigned int channel; + unsigned int block; + unsigned char isr; + unsigned char sr; + unsigned char isr_tx_rdy, isr_rx_rdy; + unsigned char value; + unsigned char flag; + struct tty_struct *tty; + struct ipoctal *ipoctal = (struct ipoctal *) arg; + + /* Check all channels */ + for (channel = 0; channel < NR_CHANNELS; channel++) { + /* If there is no client, skip the check */ + if (!atomic_read(&ipoctal->open[channel])) + continue; + + tty = tty_port_tty_get(&ipoctal->tty_port[channel]); + if (!tty) + continue; + + /* + * The HW is organized in pair of channels. + * See which register we need to read from + */ + block = channel / 2; + isr = ipoctal_read_io_reg(ipoctal, + &ipoctal->block_regs[block].u.r.isr); + sr = ipoctal_read_io_reg(ipoctal, + &ipoctal->chan_regs[channel].u.r.sr); + + if ((channel % 2) == 1) { + isr_tx_rdy = isr & ISR_TxRDY_B; + isr_rx_rdy = isr & ISR_RxRDY_FFULL_B; + } else { + isr_tx_rdy = isr & ISR_TxRDY_A; + isr_rx_rdy = isr & ISR_RxRDY_FFULL_A; + } + + /* In case of RS-485, change from TX to RX when finishing TX. + * Half-duplex. + */ + if ((ipoctal->board_id == IP_OCTAL_485_ID) && + (sr & SR_TX_EMPTY) && + (ipoctal->nb_bytes[channel] == 0)) { + ipoctal_write_io_reg(ipoctal, + &ipoctal->chan_regs[channel].u.w.cr, + CR_DISABLE_TX); + ipoctal_write_cr_cmd(ipoctal, + &ipoctal->chan_regs[channel].u.w.cr, + CR_CMD_NEGATE_RTSN); + ipoctal_write_io_reg(ipoctal, + &ipoctal->chan_regs[channel].u.w.cr, + CR_ENABLE_RX); + ipoctal->write = 1; + wake_up_interruptible(&ipoctal->queue[channel]); + } + + /* RX data */ + if (isr_rx_rdy && (sr & SR_RX_READY)) { + value = ipoctal_read_io_reg(ipoctal, + &ipoctal->chan_regs[channel].u.r.rhr); + flag = TTY_NORMAL; + + /* Error: count statistics */ + if (sr & SR_ERROR) { + ipoctal_write_cr_cmd(ipoctal, + &ipoctal->chan_regs[channel].u.w.cr, + CR_CMD_RESET_ERR_STATUS); + + if (sr & SR_OVERRUN_ERROR) { + ipoctal->error_flag[channel] |= UART_OVERRUN; + ipoctal->chan_stats[channel].overrun_err++; + /* Overrun doesn't affect the current character*/ + tty_insert_flip_char(tty, 0, TTY_OVERRUN); + } + if (sr & SR_PARITY_ERROR) { + ipoctal->error_flag[channel] |= UART_PARITY; + ipoctal->chan_stats[channel].parity_err++; + flag = TTY_PARITY; + } + if (sr & SR_FRAMING_ERROR) { + ipoctal->error_flag[channel] |= UART_FRAMING; + ipoctal->chan_stats[channel].framing_err++; + flag = TTY_FRAME; + } + if (sr & SR_RECEIVED_BREAK) { + ipoctal->error_flag[channel] |= UART_BREAK; + ipoctal->chan_stats[channel].rcv_break++; + flag = TTY_BREAK; + } + } + + tty_insert_flip_char(tty, value, flag); + } + + /* TX of each character */ + if (isr_tx_rdy && (sr & SR_TX_READY)) { + unsigned int *pointer_write = + &ipoctal->pointer_write[channel]; + + if (ipoctal->nb_bytes[channel] <= 0) { + ipoctal->nb_bytes[channel] = 0; + continue; + } + spin_lock(&ipoctal->lock[channel]); + value = ipoctal->buffer[channel][*pointer_write]; + ipoctal_write_io_reg(ipoctal, + &ipoctal->chan_regs[channel].u.w.thr, + value); + ipoctal->chan_stats[channel].tx++; + ipoctal->count_wr[channel]++; + (*pointer_write)++; + *pointer_write = *pointer_write % PAGE_SIZE; + ipoctal->nb_bytes[channel]--; + spin_unlock(&ipoctal->lock[channel]); + + if ((ipoctal->nb_bytes[channel] == 0) && + (waitqueue_active(&ipoctal->queue[channel]))) { + + if (ipoctal->board_id != IP_OCTAL_485_ID) { + ipoctal->write = 1; + wake_up_interruptible(&ipoctal->queue[channel]); + } + } + } + + tty_flip_buffer_push(tty); + tty_kref_put(tty); + } + return IRQ_HANDLED; +} + +static int ipoctal_check_model(struct ipack_device *dev, unsigned char *id) +{ + unsigned char manufacturerID; + unsigned char board_id; + + dev->bus->ops->read8(dev, IPACK_ID_SPACE, + IPACK_IDPROM_OFFSET_MANUFACTURER_ID, &manufacturerID); + if (manufacturerID != IP_OCTAL_MANUFACTURER_ID) + return -ENODEV; + + dev->bus->ops->read8(dev, IPACK_ID_SPACE, + IPACK_IDPROM_OFFSET_MODEL, (unsigned char *)&board_id); + + switch (board_id) { + case IP_OCTAL_232_ID: + case IP_OCTAL_422_ID: + case IP_OCTAL_485_ID: + *id = board_id; + break; + default: + return -ENODEV; + } + + return 0; +} + +static const struct tty_port_operations ipoctal_tty_port_ops = { + .dtr_rts = NULL, + .activate = ipoctal_port_activate, +}; + +static int ipoctal_inst_slot(struct ipoctal *ipoctal, unsigned int bus_nr, + unsigned int slot, unsigned int vector) +{ + int res = 0; + int i; + struct tty_driver *tty; + char name[20]; + unsigned char board_id; + + res = ipoctal->dev->bus->ops->map_space(ipoctal->dev, 0, + IPACK_ID_SPACE); + if (res) { + pr_err("Unable to map slot [%d:%d] ID space!\n", bus_nr, slot); + return res; + } + + res = ipoctal_check_model(ipoctal->dev, &board_id); + if (res) { + ipoctal->dev->bus->ops->unmap_space(ipoctal->dev, + IPACK_ID_SPACE); + goto out_unregister_id_space; + } + ipoctal->board_id = board_id; + + res = ipoctal->dev->bus->ops->map_space(ipoctal->dev, 0, + IPACK_IO_SPACE); + if (res) { + pr_err("Unable to map slot [%d:%d] IO space!\n", bus_nr, slot); + goto out_unregister_id_space; + } + + res = ipoctal->dev->bus->ops->map_space(ipoctal->dev, + 0x8000, IPACK_MEM_SPACE); + if (res) { + pr_err("Unable to map slot [%d:%d] MEM space!\n", bus_nr, slot); + goto out_unregister_io_space; + } + + /* Save the virtual address to access the registers easily */ + ipoctal->chan_regs = + (struct scc2698_channel *) ipoctal->dev->io_space.address; + ipoctal->block_regs = + (struct scc2698_block *) ipoctal->dev->io_space.address; + + /* Disable RX and TX before touching anything */ + for (i = 0; i < NR_CHANNELS ; i++) { + ipoctal_write_io_reg(ipoctal, &ipoctal->chan_regs[i].u.w.cr, + CR_DISABLE_RX | CR_DISABLE_TX); + } + + for (i = 0; i < IP_OCTAL_NB_BLOCKS; i++) { + ipoctal_write_io_reg(ipoctal, + &ipoctal->block_regs[i].u.w.acr, + ACR_BRG_SET2); + ipoctal_write_io_reg(ipoctal, + &ipoctal->block_regs[i].u.w.opcr, + OPCR_MPP_OUTPUT | OPCR_MPOa_RTSN | + OPCR_MPOb_RTSN); + ipoctal_write_io_reg(ipoctal, + &ipoctal->block_regs[i].u.w.imr, + IMR_TxRDY_A | IMR_RxRDY_FFULL_A | + IMR_DELTA_BREAK_A | IMR_TxRDY_B | + IMR_RxRDY_FFULL_B | IMR_DELTA_BREAK_B); + } + + /* + * IP-OCTAL has different addresses to copy its IRQ vector. + * Depending of the carrier these addresses are accesible or not. + * More info in the datasheet. + */ + ipoctal->dev->bus->ops->request_irq(ipoctal->dev, vector, + ipoctal_irq_handler, ipoctal); + ipoctal->dev->bus->ops->write8(ipoctal->dev, IPACK_ID_SPACE, 0, vector); + + /* Register the TTY device */ + + /* Each IP-OCTAL channel is a TTY port */ + tty = alloc_tty_driver(NR_CHANNELS); + + if (!tty) { + res = -ENOMEM; + goto out_unregister_slot_unmap; + } + + /* Fill struct tty_driver with ipoctal data */ + tty->owner = THIS_MODULE; + tty->driver_name = "ipoctal"; + sprintf(name, "ipoctal.%d.%d.", bus_nr, slot); + tty->name = name; + tty->major = 0; + + tty->minor_start = 0; + tty->type = TTY_DRIVER_TYPE_SERIAL; + tty->subtype = SERIAL_TYPE_NORMAL; + tty->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV; + tty->init_termios = tty_std_termios; + tty->init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL; + tty->init_termios.c_ispeed = 9600; + tty->init_termios.c_ospeed = 9600; + + tty_set_operations(tty, &ipoctal_fops); + res = tty_register_driver(tty); + if (res) { + pr_err("Can't register tty driver.\n"); + put_tty_driver(tty); + goto out_unregister_slot_unmap; + } + + /* Save struct tty_driver for use it when uninstalling the device */ + ipoctal->tty_drv = tty; + + for (i = 0; i < NR_CHANNELS; i++) { + tty_port_init(&ipoctal->tty_port[i]); + tty_port_alloc_xmit_buf(&ipoctal->tty_port[i]); + ipoctal->tty_port[i].ops = &ipoctal_tty_port_ops; + + ipoctal_reset_stats(&ipoctal->chan_stats[i]); + ipoctal->nb_bytes[i] = 0; + init_waitqueue_head(&ipoctal->queue[i]); + ipoctal->error_flag[i] = UART_NOERROR; + + spin_lock_init(&ipoctal->lock[i]); + ipoctal->pointer_read[i] = 0; + ipoctal->pointer_write[i] = 0; + ipoctal->nb_bytes[i] = 0; + tty_register_device(tty, i, NULL); + + /* + * Enable again the RX. TX will be enabled when + * there is something to send + */ + ipoctal_write_io_reg(ipoctal, &ipoctal->chan_regs[i].u.w.cr, + CR_ENABLE_RX); + } + + return 0; + +out_unregister_slot_unmap: + ipoctal->dev->bus->ops->unmap_space(ipoctal->dev, IPACK_ID_SPACE); +out_unregister_io_space: + ipoctal->dev->bus->ops->unmap_space(ipoctal->dev, IPACK_IO_SPACE); +out_unregister_id_space: + ipoctal->dev->bus->ops->unmap_space(ipoctal->dev, IPACK_MEM_SPACE); + return res; +} + +static inline int ipoctal_copy_write_buffer(struct ipoctal *ipoctal, + unsigned int channel, + const unsigned char *buf, + int count) +{ + unsigned long flags; + int i; + unsigned int *pointer_read = &ipoctal->pointer_read[channel]; + + /* Copy the bytes from the user buffer to the internal one */ + for (i = 0; i < count; i++) { + if (i <= (PAGE_SIZE - ipoctal->nb_bytes[channel])) { + spin_lock_irqsave(&ipoctal->lock[channel], flags); + ipoctal->tty_port[channel].xmit_buf[*pointer_read] = buf[i]; + *pointer_read = (*pointer_read + 1) % PAGE_SIZE; + ipoctal->nb_bytes[channel]++; + spin_unlock_irqrestore(&ipoctal->lock[channel], flags); + } else { + break; + } + } + return i; +} + +static int ipoctal_write(struct ipoctal *ipoctal, unsigned int channel, + const unsigned char *buf, int count) +{ + ipoctal->nb_bytes[channel] = 0; + ipoctal->count_wr[channel] = 0; + + ipoctal_copy_write_buffer(ipoctal, channel, buf, count); + + ipoctal->error_flag[channel] = UART_NOERROR; + + /* As the IP-OCTAL 485 only supports half duplex, do it manually */ + if (ipoctal->board_id == IP_OCTAL_485_ID) { + ipoctal_write_io_reg(ipoctal, + &ipoctal->chan_regs[channel].u.w.cr, + CR_DISABLE_RX); + ipoctal_write_cr_cmd(ipoctal, + &ipoctal->chan_regs[channel].u.w.cr, + CR_CMD_ASSERT_RTSN); + } + + /* + * Send a packet and then disable TX to avoid failure after several send + * operations + */ + ipoctal_write_io_reg(ipoctal, + &ipoctal->chan_regs[channel].u.w.cr, + CR_ENABLE_TX); + wait_event_interruptible(ipoctal->queue[channel], ipoctal->write); + ipoctal_write_io_reg(ipoctal, + &ipoctal->chan_regs[channel].u.w.cr, + CR_DISABLE_TX); + + ipoctal->write = 0; + return ipoctal->count_wr[channel]; +} + +static int ipoctal_write_tty(struct tty_struct *tty, + const unsigned char *buf, int count) +{ + unsigned int channel = tty->index; + struct ipoctal *ipoctal = tty->driver_data; + + return ipoctal_write(ipoctal, channel, buf, count); +} + +static int ipoctal_write_room(struct tty_struct *tty) +{ + int channel = tty->index; + struct ipoctal *ipoctal = tty->driver_data; + + return PAGE_SIZE - ipoctal->nb_bytes[channel]; +} + +static int ipoctal_chars_in_buffer(struct tty_struct *tty) +{ + int channel = tty->index; + struct ipoctal *ipoctal = tty->driver_data; + + return ipoctal->nb_bytes[channel]; +} + +static void ipoctal_set_termios(struct tty_struct *tty, + struct ktermios *old_termios) +{ + unsigned int cflag; + unsigned char mr1 = 0; + unsigned char mr2 = 0; + unsigned char csr = 0; + unsigned int channel = tty->index; + struct ipoctal *ipoctal = tty->driver_data; + speed_t baud; + + cflag = tty->termios->c_cflag; + + /* Disable and reset everything before change the setup */ + ipoctal_write_io_reg(ipoctal, &ipoctal->chan_regs[channel].u.w.cr, + CR_DISABLE_RX | CR_DISABLE_TX); + ipoctal_write_cr_cmd(ipoctal, &ipoctal->chan_regs[channel].u.w.cr, + CR_CMD_RESET_RX); + ipoctal_write_cr_cmd(ipoctal, &ipoctal->chan_regs[channel].u.w.cr, + CR_CMD_RESET_TX); + ipoctal_write_cr_cmd(ipoctal, &ipoctal->chan_regs[channel].u.w.cr, + CR_CMD_RESET_ERR_STATUS); + ipoctal_write_cr_cmd(ipoctal, &ipoctal->chan_regs[channel].u.w.cr, + CR_CMD_RESET_MR); + + /* Set Bits per chars */ + switch (cflag & CSIZE) { + case CS6: + mr1 |= MR1_CHRL_6_BITS; + break; + case CS7: + mr1 |= MR1_CHRL_7_BITS; + break; + case CS8: + default: + mr1 |= MR1_CHRL_8_BITS; + /* By default, select CS8 */ + tty->termios->c_cflag = (cflag & ~CSIZE) | CS8; + break; + } + + /* Set Parity */ + if (cflag & PARENB) + if (cflag & PARODD) + mr1 |= MR1_PARITY_ON | MR1_PARITY_ODD; + else + mr1 |= MR1_PARITY_ON | MR1_PARITY_EVEN; + else + mr1 |= MR1_PARITY_OFF; + + /* Mark or space parity is not supported */ + tty->termios->c_cflag &= ~CMSPAR; + + /* Set stop bits */ + if (cflag & CSTOPB) + mr2 |= MR2_STOP_BITS_LENGTH_2; + else + mr2 |= MR2_STOP_BITS_LENGTH_1; + + /* Set the flow control */ + switch (ipoctal->board_id) { + case IP_OCTAL_232_ID: + if (cflag & CRTSCTS) { + mr1 |= MR1_RxRTS_CONTROL_ON; + mr2 |= MR2_TxRTS_CONTROL_OFF | MR2_CTS_ENABLE_TX_ON; + ipoctal->chan_config[channel].flow_control = 1; + } else { + mr1 |= MR1_RxRTS_CONTROL_OFF; + mr2 |= MR2_TxRTS_CONTROL_OFF | MR2_CTS_ENABLE_TX_OFF; + ipoctal->chan_config[channel].flow_control = 0; + } + break; + case IP_OCTAL_422_ID: + mr1 |= MR1_RxRTS_CONTROL_OFF; + mr2 |= MR2_TxRTS_CONTROL_OFF | MR2_CTS_ENABLE_TX_OFF; + ipoctal->chan_config[channel].flow_control = 0; + break; + case IP_OCTAL_485_ID: + mr1 |= MR1_RxRTS_CONTROL_OFF; + mr2 |= MR2_TxRTS_CONTROL_ON | MR2_CTS_ENABLE_TX_OFF; + ipoctal->chan_config[channel].flow_control = 0; + break; + default: + return; + break; + } + + baud = tty_get_baud_rate(tty); + tty_termios_encode_baud_rate(tty->termios, baud, baud); + + /* Set baud rate */ + switch (tty->termios->c_ospeed) { + case 75: + csr |= TX_CLK_75 | RX_CLK_75; + break; + case 110: + csr |= TX_CLK_110 | RX_CLK_110; + break; + case 150: + csr |= TX_CLK_150 | RX_CLK_150; + break; + case 300: + csr |= TX_CLK_300 | RX_CLK_300; + break; + case 600: + csr |= TX_CLK_600 | RX_CLK_600; + break; + case 1200: + csr |= TX_CLK_1200 | RX_CLK_1200; + break; + case 1800: + csr |= TX_CLK_1800 | RX_CLK_1800; + break; + case 2000: + csr |= TX_CLK_2000 | RX_CLK_2000; + break; + case 2400: + csr |= TX_CLK_2400 | RX_CLK_2400; + break; + case 4800: + csr |= TX_CLK_4800 | RX_CLK_4800; + break; + case 9600: + csr |= TX_CLK_9600 | RX_CLK_9600; + break; + case 19200: + csr |= TX_CLK_19200 | RX_CLK_19200; + break; + case 38400: + default: + csr |= TX_CLK_38400 | RX_CLK_38400; + /* In case of default, we establish 38400 bps */ + tty_termios_encode_baud_rate(tty->termios, 38400, 38400); + break; + } + + mr1 |= MR1_ERROR_CHAR; + mr1 |= MR1_RxINT_RxRDY; + + /* Write the control registers */ + ipoctal_write_io_reg(ipoctal, &ipoctal->chan_regs[channel].u.w.mr, mr1); + ipoctal_write_io_reg(ipoctal, &ipoctal->chan_regs[channel].u.w.mr, mr2); + ipoctal_write_io_reg(ipoctal, &ipoctal->chan_regs[channel].u.w.csr, csr); + + /* save the setup in the structure */ + ipoctal->chan_config[channel].baud = tty_get_baud_rate(tty); + ipoctal->chan_config[channel].bits_per_char = cflag & CSIZE; + ipoctal->chan_config[channel].parity = cflag & PARENB; + ipoctal->chan_config[channel].stop_bits = cflag & CSTOPB; + + /* Enable again the RX */ + ipoctal_write_io_reg(ipoctal, &ipoctal->chan_regs[channel].u.w.cr, + CR_ENABLE_RX); +} + +static void ipoctal_hangup(struct tty_struct *tty) +{ + unsigned long flags; + int channel = tty->index; + struct ipoctal *ipoctal = tty->driver_data; + + if (ipoctal == NULL) + return; + + spin_lock_irqsave(&ipoctal->lock[channel], flags); + ipoctal->nb_bytes[channel] = 0; + ipoctal->pointer_read[channel] = 0; + ipoctal->pointer_write[channel] = 0; + spin_unlock_irqrestore(&ipoctal->lock[channel], flags); + + tty_port_hangup(&ipoctal->tty_port[channel]); + + ipoctal_write_io_reg(ipoctal, &ipoctal->chan_regs[channel].u.w.cr, + CR_DISABLE_RX | CR_DISABLE_TX); + ipoctal_write_cr_cmd(ipoctal, &ipoctal->chan_regs[channel].u.w.cr, + CR_CMD_RESET_RX); + ipoctal_write_cr_cmd(ipoctal, &ipoctal->chan_regs[channel].u.w.cr, + CR_CMD_RESET_TX); + ipoctal_write_cr_cmd(ipoctal, &ipoctal->chan_regs[channel].u.w.cr, + CR_CMD_RESET_ERR_STATUS); + ipoctal_write_cr_cmd(ipoctal, &ipoctal->chan_regs[channel].u.w.cr, + CR_CMD_RESET_MR); + + clear_bit(ASYNCB_INITIALIZED, &ipoctal->tty_port[channel].flags); + wake_up_interruptible(&ipoctal->tty_port[channel].open_wait); +} + +static const struct tty_operations ipoctal_fops = { + .ioctl = NULL, + .open = ipoctal_open, + .close = ipoctal_close, + .write = ipoctal_write_tty, + .set_termios = ipoctal_set_termios, + .write_room = ipoctal_write_room, + .chars_in_buffer = ipoctal_chars_in_buffer, + .get_icount = ipoctal_get_icount, + .hangup = ipoctal_hangup, +}; + +static int ipoctal_match(struct ipack_device *dev) +{ + int res; + unsigned char board_id; + + if ((!dev->bus->ops) || (!dev->bus->ops->map_space) || + (!dev->bus->ops->unmap_space)) + return 0; + + res = dev->bus->ops->map_space(dev, 0, IPACK_ID_SPACE); + if (res) + return 0; + + res = ipoctal_check_model(dev, &board_id); + dev->bus->ops->unmap_space(dev, IPACK_ID_SPACE); + if (!res) + return 1; + + return 0; +} + +static int ipoctal_probe(struct ipack_device *dev) +{ + int res; + struct ipoctal *ipoctal; + + ipoctal = kzalloc(sizeof(struct ipoctal), GFP_KERNEL); + if (ipoctal == NULL) + return -ENOMEM; + + ipoctal->dev = dev; + res = ipoctal_inst_slot(ipoctal, dev->bus_nr, dev->slot, dev->irq); + if (res) + goto out_uninst; + + list_add_tail(&ipoctal->list, &ipoctal_list); + return 0; + +out_uninst: + kfree(ipoctal); + return res; +} + +static void __ipoctal_remove(struct ipoctal *ipoctal) +{ + int i; + + for (i = 0; i < NR_CHANNELS; i++) { + tty_unregister_device(ipoctal->tty_drv, i); + tty_port_free_xmit_buf(&ipoctal->tty_port[i]); + } + + tty_unregister_driver(ipoctal->tty_drv); + put_tty_driver(ipoctal->tty_drv); + + /* Tell the carrier board to free all the resources for this device */ + if (ipoctal->dev->bus->ops->remove_device != NULL) + ipoctal->dev->bus->ops->remove_device(ipoctal->dev); + + list_del(&ipoctal->list); + kfree(ipoctal); +} + +static void ipoctal_remove(struct ipack_device *device) +{ + struct ipoctal *ipoctal, *next; + + list_for_each_entry_safe(ipoctal, next, &ipoctal_list, list) { + if (ipoctal->dev == device) + __ipoctal_remove(ipoctal); + } +} + +static struct ipack_driver_ops ipoctal_drv_ops = { + .match = ipoctal_match, + .probe = ipoctal_probe, + .remove = ipoctal_remove, +}; + +static int __init ipoctal_init(void) +{ + driver.ops = &ipoctal_drv_ops; + return ipack_driver_register(&driver, THIS_MODULE, KBUILD_MODNAME); +} + +static void __exit ipoctal_exit(void) +{ + struct ipoctal *p, *next; + + list_for_each_entry_safe(p, next, &ipoctal_list, list) + __ipoctal_remove(p); + + ipack_driver_unregister(&driver); +} + +MODULE_DESCRIPTION("IP-Octal 232, 422 and 485 device driver"); +MODULE_LICENSE("GPL"); + +module_init(ipoctal_init); +module_exit(ipoctal_exit); diff --git a/drivers/staging/ipack/devices/ipoctal.h b/drivers/staging/ipack/devices/ipoctal.h new file mode 100644 index 000000000000..266f3617069d --- /dev/null +++ b/drivers/staging/ipack/devices/ipoctal.h @@ -0,0 +1,80 @@ +/** + * ipoctal.h + * + * driver for the IPOCTAL boards + * Copyright (c) 2009 Nicolas Serafini, EIC2 SA + * Copyright (c) 2010,2011 Samuel Iglesias Gonsalvez <siglesia@cern.ch>, CERN + * Copyright (c) 2012 Samuel Iglesias Gonsalvez <siglesias@igalia.com>, Igalia + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; version 2 of the License. + */ + +#ifndef _IPOCTAL_H +#define _IPOCTAL_H_ + +#define NR_CHANNELS 8 +#define IPOCTAL_MAX_BOARDS 16 +#define MAX_DEVICES (NR_CHANNELS * IPOCTAL_MAX_BOARDS) +#define RELEVANT_IFLAG(iflag) ((iflag) & (IGNBRK|BRKINT|IGNPAR|PARMRK|INPCK)) + +/** + * enum uart_parity_e - UART supported parity. + */ +enum uart_parity_e { + UART_NONE = 0, + UART_ODD = 1, + UART_EVEN = 2, +}; + +/** + * enum uart_error - UART error type + * + */ +enum uart_error { + UART_NOERROR = 0, /* No error during transmission */ + UART_TIMEOUT = 1 << 0, /* Timeout error */ + UART_OVERRUN = 1 << 1, /* Overrun error */ + UART_PARITY = 1 << 2, /* Parity error */ + UART_FRAMING = 1 << 3, /* Framing error */ + UART_BREAK = 1 << 4, /* Received break */ +}; + +/** + * struct ipoctal_config - Serial configuration + * + * @baud: Baud rate + * @stop_bits: Stop bits (1 or 2) + * @bits_per_char: data size in bits + * @parity + * @flow_control: Flow control management (RTS/CTS) (0 disabled, 1 enabled) + */ +struct ipoctal_config { + unsigned int baud; + unsigned int stop_bits; + unsigned int bits_per_char; + unsigned short parity; + unsigned int flow_control; +}; + +/** + * struct ipoctal_stats -- Stats since last reset + * + * @tx: Number of transmitted bytes + * @rx: Number of received bytes + * @overrun: Number of overrun errors + * @parity_err: Number of parity errors + * @framing_err: Number of framing errors + * @rcv_break: Number of break received + */ +struct ipoctal_stats { + unsigned long tx; + unsigned long rx; + unsigned long overrun_err; + unsigned long parity_err; + unsigned long framing_err; + unsigned long rcv_break; +}; + +#endif /* _IPOCTAL_H_ */ diff --git a/drivers/staging/ipack/devices/scc2698.h b/drivers/staging/ipack/devices/scc2698.h new file mode 100644 index 000000000000..47f6269985fd --- /dev/null +++ b/drivers/staging/ipack/devices/scc2698.h @@ -0,0 +1,228 @@ +/* + * scc2698.h + * + * driver for the IPOCTAL boards + * Copyright (c) 2009 Nicolas Serafini, EIC2 SA + * Copyright (c) 2010,2011 Samuel Iglesias Gonsalvez <siglesia@cern.ch>, CERN + * Copyright (c) 2012 Samuel Iglesias Gonsalvez <siglesias@igalia.com>, Igalia + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; version 2 of the License. + */ + +#ifndef SCC2698_H_ +#define SCC2698_H_ + +/* + * struct scc2698_channel - Channel access to scc2698 IO + * + * dn value are only spacer. + * + */ +struct scc2698_channel { + union { + struct { + unsigned char d0, mr; /* Mode register 1/2*/ + unsigned char d1, sr; /* Status register */ + unsigned char d2, r1; /* reserved */ + unsigned char d3, rhr; /* Receive holding register (R) */ + unsigned char junk[8]; /* other crap for block control */ + } r; /* Read access */ + struct { + unsigned char d0, mr; /* Mode register 1/2 */ + unsigned char d1, csr; /* Clock select register */ + unsigned char d2, cr; /* Command register */ + unsigned char d3, thr; /* Transmit holding register */ + unsigned char junk[8]; /* other crap for block control */ + } w; /* Write access */ + } u; +}; + +/* + * struct scc2698_block - Block access to scc2698 IO + * + * The scc2698 contain 4 block. + * Each block containt two channel a and b. + * dn value are only spacer. + * + */ +struct scc2698_block { + union { + struct { + unsigned char d0, mra; /* Mode register 1/2 (a) */ + unsigned char d1, sra; /* Status register (a) */ + unsigned char d2, r1; /* reserved */ + unsigned char d3, rhra; /* Receive holding register (a) */ + unsigned char d4, ipcr; /* Input port change register of block */ + unsigned char d5, isr; /* Interrupt status register of block */ + unsigned char d6, ctur; /* Counter timer upper register of block */ + unsigned char d7, ctlr; /* Counter timer lower register of block */ + unsigned char d8, mrb; /* Mode register 1/2 (b) */ + unsigned char d9, srb; /* Status register (b) */ + unsigned char da, r2; /* reserved */ + unsigned char db, rhrb; /* Receive holding register (b) */ + unsigned char dc, r3; /* reserved */ + unsigned char dd, ip; /* Input port register of block */ + unsigned char de, ctg; /* Start counter timer of block */ + unsigned char df, cts; /* Stop counter timer of block */ + } r; /* Read access */ + struct { + unsigned char d0, mra; /* Mode register 1/2 (a) */ + unsigned char d1, csra; /* Clock select register (a) */ + unsigned char d2, cra; /* Command register (a) */ + unsigned char d3, thra; /* Transmit holding register (a) */ + unsigned char d4, acr; /* Auxiliary control register of block */ + unsigned char d5, imr; /* Interrupt mask register of block */ + unsigned char d6, ctu; /* Counter timer upper register of block */ + unsigned char d7, ctl; /* Counter timer lower register of block */ + unsigned char d8, mrb; /* Mode register 1/2 (b) */ + unsigned char d9, csrb; /* Clock select register (a) */ + unsigned char da, crb; /* Command register (b) */ + unsigned char db, thrb; /* Transmit holding register (b) */ + unsigned char dc, r1; /* reserved */ + unsigned char dd, opcr; /* Output port configuration register of block */ + unsigned char de, r2; /* reserved */ + unsigned char df, r3; /* reserved */ + } w; /* Write access */ + } u; +} ; + +#define MR1_CHRL_5_BITS (0x0 << 0) +#define MR1_CHRL_6_BITS (0x1 << 0) +#define MR1_CHRL_7_BITS (0x2 << 0) +#define MR1_CHRL_8_BITS (0x3 << 0) +#define MR1_PARITY_EVEN (0x1 << 2) +#define MR1_PARITY_ODD (0x0 << 2) +#define MR1_PARITY_ON (0x0 << 3) +#define MR1_PARITY_FORCE (0x1 << 3) +#define MR1_PARITY_OFF (0x2 << 3) +#define MR1_PARITY_SPECIAL (0x3 << 3) +#define MR1_ERROR_CHAR (0x0 << 5) +#define MR1_ERROR_BLOCK (0x1 << 5) +#define MR1_RxINT_RxRDY (0x0 << 6) +#define MR1_RxINT_FFULL (0x1 << 6) +#define MR1_RxRTS_CONTROL_ON (0x1 << 7) +#define MR1_RxRTS_CONTROL_OFF (0x0 << 7) + +#define MR2_STOP_BITS_LENGTH_1 (0x7 << 0) +#define MR2_STOP_BITS_LENGTH_2 (0xF << 0) +#define MR2_CTS_ENABLE_TX_ON (0x1 << 4) +#define MR2_CTS_ENABLE_TX_OFF (0x0 << 4) +#define MR2_TxRTS_CONTROL_ON (0x1 << 5) +#define MR2_TxRTS_CONTROL_OFF (0x0 << 5) +#define MR2_CH_MODE_NORMAL (0x0 << 6) +#define MR2_CH_MODE_ECHO (0x1 << 6) +#define MR2_CH_MODE_LOCAL (0x2 << 6) +#define MR2_CH_MODE_REMOTE (0x3 << 6) + +#define CR_ENABLE_RX (0x1 << 0) +#define CR_DISABLE_RX (0x1 << 1) +#define CR_ENABLE_TX (0x1 << 2) +#define CR_DISABLE_TX (0x1 << 3) +#define CR_CMD_RESET_MR (0x1 << 4) +#define CR_CMD_RESET_RX (0x2 << 4) +#define CR_CMD_RESET_TX (0x3 << 4) +#define CR_CMD_RESET_ERR_STATUS (0x4 << 4) +#define CR_CMD_RESET_BREAK_CHANGE (0x5 << 4) +#define CR_CMD_START_BREAK (0x6 << 4) +#define CR_CMD_STOP_BREAK (0x7 << 4) +#define CR_CMD_ASSERT_RTSN (0x8 << 4) +#define CR_CMD_NEGATE_RTSN (0x9 << 4) +#define CR_CMD_SET_TIMEOUT_MODE (0xA << 4) +#define CR_CMD_DISABLE_TIMEOUT_MODE (0xC << 4) + +#define SR_RX_READY (0x1 << 0) +#define SR_FIFO_FULL (0x1 << 1) +#define SR_TX_READY (0x1 << 2) +#define SR_TX_EMPTY (0x1 << 3) +#define SR_OVERRUN_ERROR (0x1 << 4) +#define SR_PARITY_ERROR (0x1 << 5) +#define SR_FRAMING_ERROR (0x1 << 6) +#define SR_RECEIVED_BREAK (0x1 << 7) + +#define SR_ERROR (0xF0) + +#define ACR_DELTA_IP0_IRQ_EN (0x1 << 0) +#define ACR_DELTA_IP1_IRQ_EN (0x1 << 1) +#define ACR_DELTA_IP2_IRQ_EN (0x1 << 2) +#define ACR_DELTA_IP3_IRQ_EN (0x1 << 3) +#define ACR_CT_Mask (0x7 << 4) +#define ACR_CExt (0x0 << 4) +#define ACR_CTxCA (0x1 << 4) +#define ACR_CTxCB (0x2 << 4) +#define ACR_CClk16 (0x3 << 4) +#define ACR_TExt (0x4 << 4) +#define ACR_TExt16 (0x5 << 4) +#define ACR_TClk (0x6 << 4) +#define ACR_TClk16 (0x7 << 4) +#define ACR_BRG_SET1 (0x0 << 7) +#define ACR_BRG_SET2 (0x1 << 7) + +#define TX_CLK_75 (0x0 << 0) +#define TX_CLK_110 (0x1 << 0) +#define TX_CLK_38400 (0x2 << 0) +#define TX_CLK_150 (0x3 << 0) +#define TX_CLK_300 (0x4 << 0) +#define TX_CLK_600 (0x5 << 0) +#define TX_CLK_1200 (0x6 << 0) +#define TX_CLK_2000 (0x7 << 0) +#define TX_CLK_2400 (0x8 << 0) +#define TX_CLK_4800 (0x9 << 0) +#define TX_CLK_1800 (0xA << 0) +#define TX_CLK_9600 (0xB << 0) +#define TX_CLK_19200 (0xC << 0) +#define RX_CLK_75 (0x0 << 4) +#define RX_CLK_110 (0x1 << 4) +#define RX_CLK_38400 (0x2 << 4) +#define RX_CLK_150 (0x3 << 4) +#define RX_CLK_300 (0x4 << 4) +#define RX_CLK_600 (0x5 << 4) +#define RX_CLK_1200 (0x6 << 4) +#define RX_CLK_2000 (0x7 << 4) +#define RX_CLK_2400 (0x8 << 4) +#define RX_CLK_4800 (0x9 << 4) +#define RX_CLK_1800 (0xA << 4) +#define RX_CLK_9600 (0xB << 4) +#define RX_CLK_19200 (0xC << 4) + +#define OPCR_MPOa_RTSN (0x0 << 0) +#define OPCR_MPOa_C_TO (0x1 << 0) +#define OPCR_MPOa_TxC1X (0x2 << 0) +#define OPCR_MPOa_TxC16X (0x3 << 0) +#define OPCR_MPOa_RxC1X (0x4 << 0) +#define OPCR_MPOa_RxC16X (0x5 << 0) +#define OPCR_MPOa_TxRDY (0x6 << 0) +#define OPCR_MPOa_RxRDY_FF (0x7 << 0) + +#define OPCR_MPOb_RTSN (0x0 << 4) +#define OPCR_MPOb_C_TO (0x1 << 4) +#define OPCR_MPOb_TxC1X (0x2 << 4) +#define OPCR_MPOb_TxC16X (0x3 << 4) +#define OPCR_MPOb_RxC1X (0x4 << 4) +#define OPCR_MPOb_RxC16X (0x5 << 4) +#define OPCR_MPOb_TxRDY (0x6 << 4) +#define OPCR_MPOb_RxRDY_FF (0x7 << 4) + +#define OPCR_MPP_INPUT (0x0 << 7) +#define OPCR_MPP_OUTPUT (0x1 << 7) + +#define IMR_TxRDY_A (0x1 << 0) +#define IMR_RxRDY_FFULL_A (0x1 << 1) +#define IMR_DELTA_BREAK_A (0x1 << 2) +#define IMR_COUNTER_READY (0x1 << 3) +#define IMR_TxRDY_B (0x1 << 4) +#define IMR_RxRDY_FFULL_B (0x1 << 5) +#define IMR_DELTA_BREAK_B (0x1 << 6) +#define IMR_INPUT_PORT_CHANGE (0x1 << 7) + +#define ISR_TxRDY_A (0x1 << 0) +#define ISR_RxRDY_FFULL_A (0x1 << 1) +#define ISR_DELTA_BREAK_A (0x1 << 2) +#define ISR_COUNTER_READY (0x1 << 3) +#define ISR_TxRDY_B (0x1 << 4) +#define ISR_RxRDY_FFULL_B (0x1 << 5) +#define ISR_DELTA_BREAK_B (0x1 << 6) +#define ISR_INPUT_PORT_CHANGE (0x1 << 7) + +#endif /* SCC2698_H_ */ diff --git a/drivers/staging/ipack/ipack.c b/drivers/staging/ipack/ipack.c new file mode 100644 index 000000000000..2b4fa51bf167 --- /dev/null +++ b/drivers/staging/ipack/ipack.c @@ -0,0 +1,205 @@ +/* + * Industry-pack bus support functions. + * + * (C) 2011 Samuel Iglesias Gonsalvez <siglesia@cern.ch>, CERN + * (C) 2012 Samuel Iglesias Gonsalvez <siglesias@igalia.com>, Igalia + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; version 2 of the License. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/slab.h> +#include "ipack.h" + +#define to_ipack_dev(device) container_of(device, struct ipack_device, dev) +#define to_ipack_driver(drv) container_of(drv, struct ipack_driver, driver) + +/* used when allocating bus numbers */ +#define IPACK_MAXBUS 64 + +static DEFINE_MUTEX(ipack_mutex); + +struct ipack_busmap { + unsigned long busmap[IPACK_MAXBUS / (8*sizeof(unsigned long))]; +}; +static struct ipack_busmap busmap; + +static void ipack_device_release(struct device *dev) +{ + struct ipack_device *device = to_ipack_dev(dev); + kfree(device); +} + +static int ipack_bus_match(struct device *device, struct device_driver *driver) +{ + int ret; + struct ipack_device *dev = to_ipack_dev(device); + struct ipack_driver *drv = to_ipack_driver(driver); + + if ((!drv->ops) || (!drv->ops->match)) + return -EINVAL; + + ret = drv->ops->match(dev); + if (ret) + dev->driver = drv; + + return 0; +} + +static int ipack_bus_probe(struct device *device) +{ + struct ipack_device *dev = to_ipack_dev(device); + + if (!dev->driver->ops->probe) + return -EINVAL; + + return dev->driver->ops->probe(dev); +} + +static int ipack_bus_remove(struct device *device) +{ + struct ipack_device *dev = to_ipack_dev(device); + + if (!dev->driver->ops->remove) + return -EINVAL; + + dev->driver->ops->remove(dev); + return 0; +} + +static struct bus_type ipack_bus_type = { + .name = "ipack", + .probe = ipack_bus_probe, + .match = ipack_bus_match, + .remove = ipack_bus_remove, +}; + +static int ipack_assign_bus_number(void) +{ + int busnum; + + mutex_lock(&ipack_mutex); + busnum = find_next_zero_bit(busmap.busmap, IPACK_MAXBUS, 1); + + if (busnum >= IPACK_MAXBUS) { + pr_err("too many buses\n"); + busnum = -1; + goto error_find_busnum; + } + + set_bit(busnum, busmap.busmap); + +error_find_busnum: + mutex_unlock(&ipack_mutex); + return busnum; +} + +struct ipack_bus_device *ipack_bus_register(struct device *parent, int slots, + struct ipack_bus_ops *ops) +{ + int bus_nr; + struct ipack_bus_device *bus; + + bus = kzalloc(sizeof(struct ipack_bus_device), GFP_KERNEL); + if (!bus) + return NULL; + + bus_nr = ipack_assign_bus_number(); + if (bus_nr < 0) { + kfree(bus); + return NULL; + } + + bus->bus_nr = bus_nr; + bus->parent = parent; + bus->slots = slots; + bus->ops = ops; + return bus; +} +EXPORT_SYMBOL_GPL(ipack_bus_register); + +int ipack_bus_unregister(struct ipack_bus_device *bus) +{ + mutex_lock(&ipack_mutex); + clear_bit(bus->bus_nr, busmap.busmap); + mutex_unlock(&ipack_mutex); + kfree(bus); + return 0; +} +EXPORT_SYMBOL_GPL(ipack_bus_unregister); + +int ipack_driver_register(struct ipack_driver *edrv, struct module *owner, + char *name) +{ + edrv->driver.owner = owner; + edrv->driver.name = name; + edrv->driver.bus = &ipack_bus_type; + return driver_register(&edrv->driver); +} +EXPORT_SYMBOL_GPL(ipack_driver_register); + +void ipack_driver_unregister(struct ipack_driver *edrv) +{ + driver_unregister(&edrv->driver); +} +EXPORT_SYMBOL_GPL(ipack_driver_unregister); + +struct ipack_device *ipack_device_register(struct ipack_bus_device *bus, + int slot, int irqv) +{ + int ret; + struct ipack_device *dev; + + dev = kzalloc(sizeof(struct ipack_device), GFP_KERNEL); + if (!dev) + return NULL; + + dev->dev.bus = &ipack_bus_type; + dev->dev.release = ipack_device_release; + dev->dev.parent = bus->parent; + dev->slot = slot; + dev->bus_nr = bus->bus_nr; + dev->irq = irqv; + dev->bus = bus; + dev_set_name(&dev->dev, + "ipack-dev.%u.%u", dev->bus_nr, dev->slot); + + ret = device_register(&dev->dev); + if (ret < 0) { + pr_err("error registering the device.\n"); + dev->driver->ops->remove(dev); + kfree(dev); + return NULL; + } + + return dev; +} +EXPORT_SYMBOL_GPL(ipack_device_register); + +void ipack_device_unregister(struct ipack_device *dev) +{ + device_unregister(&dev->dev); +} +EXPORT_SYMBOL_GPL(ipack_device_unregister); + +static int __init ipack_init(void) +{ + return bus_register(&ipack_bus_type); +} + +static void __exit ipack_exit(void) +{ + bus_unregister(&ipack_bus_type); +} + +module_init(ipack_init); +module_exit(ipack_exit); + +MODULE_AUTHOR("Samuel Iglesias Gonsalvez <siglesias@igalia.com>"); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Industry-pack bus core"); diff --git a/drivers/staging/ipack/ipack.h b/drivers/staging/ipack/ipack.h new file mode 100644 index 000000000000..8bc001e3ca4e --- /dev/null +++ b/drivers/staging/ipack/ipack.h @@ -0,0 +1,183 @@ +/* + * Industry-pack bus. + * + * (C) 2011 Samuel Iglesias Gonsalvez <siglesia@cern.ch>, CERN + * (C) 2012 Samuel Iglesias Gonsalvez <siglesias@igalia.com>, Igalia + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; version 2 of the License. + */ + +#include <linux/device.h> + +#define IPACK_IDPROM_OFFSET_I 0x01 +#define IPACK_IDPROM_OFFSET_P 0x03 +#define IPACK_IDPROM_OFFSET_A 0x05 +#define IPACK_IDPROM_OFFSET_C 0x07 +#define IPACK_IDPROM_OFFSET_MANUFACTURER_ID 0x09 +#define IPACK_IDPROM_OFFSET_MODEL 0x0B +#define IPACK_IDPROM_OFFSET_REVISION 0x0D +#define IPACK_IDPROM_OFFSET_RESERVED 0x0F +#define IPACK_IDPROM_OFFSET_DRIVER_ID_L 0x11 +#define IPACK_IDPROM_OFFSET_DRIVER_ID_H 0x13 +#define IPACK_IDPROM_OFFSET_NUM_BYTES 0x15 +#define IPACK_IDPROM_OFFSET_CRC 0x17 + +struct ipack_bus_ops; +struct ipack_driver; + +enum ipack_space { + IPACK_IO_SPACE = 0, + IPACK_ID_SPACE = 1, + IPACK_MEM_SPACE = 2, +}; + +/** + * struct ipack_addr_space - Virtual address space mapped for a specified type. + * + * @address: virtual address + * @size: size of the mapped space + */ +struct ipack_addr_space { + void __iomem *address; + unsigned int size; +}; + +/** + * struct ipack_device + * + * @bus_nr: IP bus number where the device is plugged + * @slot: Slot where the device is plugged in the carrier board + * @irq: IRQ vector + * @driver: Pointer to the ipack_driver that manages the device + * @bus: ipack_bus_device where the device is plugged to. + * @id_space: Virtual address to ID space. + * @io_space: Virtual address to IO space. + * @mem_space: Virtual address to MEM space. + * @dev: device in kernel representation. + * + * Warning: Direct access to mapped memory is possible but the endianness + * is not the same with PCI carrier or VME carrier. The endianness is managed + * by the carrier board throught bus->ops. + */ +struct ipack_device { + unsigned int bus_nr; + unsigned int slot; + unsigned int irq; + struct ipack_driver *driver; + struct ipack_bus_device *bus; + struct ipack_addr_space id_space; + struct ipack_addr_space io_space; + struct ipack_addr_space mem_space; + struct device dev; +}; + +/** + * struct ipack_driver_ops -- callbacks to mezzanine driver for installing/removing one device + * + * @match: Match function + * @probe: Probe function + * @remove: tell the driver that the carrier board wants to remove one device + */ + +struct ipack_driver_ops { + int (*match) (struct ipack_device *dev); + int (*probe) (struct ipack_device *dev); + void (*remove) (struct ipack_device *dev); +}; + +/** + * struct ipack_driver -- Specific data to each ipack board driver + * + * @driver: Device driver kernel representation + * @ops: Mezzanine driver operations specific for the ipack bus. + */ +struct ipack_driver { + struct device_driver driver; + struct ipack_driver_ops *ops; +}; + +/** + * struct ipack_bus_ops - available operations on a bridge module + * + * @map_space: map IP address space + * @unmap_space: unmap IP address space + * @request_irq: request IRQ + * @free_irq: free IRQ + * @read8: read unsigned char + * @read16: read unsigned short + * @read32: read unsigned int + * @write8: read unsigned char + * @write16: read unsigned short + * @write32: read unsigned int + * @remove_device: tell the bridge module that the device has been removed + */ +struct ipack_bus_ops { + int (*map_space) (struct ipack_device *dev, unsigned int memory_size, int space); + int (*unmap_space) (struct ipack_device *dev, int space); + int (*request_irq) (struct ipack_device *dev, int vector, int (*handler)(void *), void *arg); + int (*free_irq) (struct ipack_device *dev); + int (*read8) (struct ipack_device *dev, int space, unsigned long offset, unsigned char *value); + int (*read16) (struct ipack_device *dev, int space, unsigned long offset, unsigned short *value); + int (*read32) (struct ipack_device *dev, int space, unsigned long offset, unsigned int *value); + int (*write8) (struct ipack_device *dev, int space, unsigned long offset, unsigned char value); + int (*write16) (struct ipack_device *dev, int space, unsigned long offset, unsigned short value); + int (*write32) (struct ipack_device *dev, int space, unsigned long offset, unsigned int value); + int (*remove_device) (struct ipack_device *dev); +}; + +/** + * struct ipack_bus_device + * + * @dev: pointer to carrier device + * @slots: number of slots available + * @bus_nr: ipack bus number + * @ops: bus operations for the mezzanine drivers + */ +struct ipack_bus_device { + struct device *parent; + int slots; + int bus_nr; + struct ipack_bus_ops *ops; +}; + +/** + * ipack_bus_register -- register a new ipack bus + * + * @parent: pointer to the parent device, if any. + * @slots: number of slots available in the bus device. + * @ops: bus operations for the mezzanine drivers. + * + * The carrier board device should call this function to register itself as + * available bus device in ipack. + */ +struct ipack_bus_device *ipack_bus_register(struct device *parent, int slots, + struct ipack_bus_ops *ops); + +/** + * ipack_bus_unregister -- unregister an ipack bus + */ +int ipack_bus_unregister(struct ipack_bus_device *bus); + +/** + * ipack_driver_register -- Register a new driver + * + * Called by a ipack driver to register itself as a driver + * that can manage ipack devices. + */ +int ipack_driver_register(struct ipack_driver *edrv, struct module *owner, char *name); +void ipack_driver_unregister(struct ipack_driver *edrv); + +/** + * ipack_device_register -- register a new mezzanine device + * + * @bus: ipack bus device it is plugged to. + * @slot: slot position in the bus device. + * @irqv: IRQ vector for the mezzanine. + * + * Register a new ipack device (mezzanine device). The call is done by + * the carrier device driver. + */ +struct ipack_device *ipack_device_register(struct ipack_bus_device *bus, int slot, int irqv); +void ipack_device_unregister(struct ipack_device *dev); |