diff options
Diffstat (limited to 'drivers/ipack')
-rw-r--r-- | drivers/ipack/Kconfig | 24 | ||||
-rw-r--r-- | drivers/ipack/Makefile | 6 | ||||
-rw-r--r-- | drivers/ipack/carriers/Kconfig | 7 | ||||
-rw-r--r-- | drivers/ipack/carriers/Makefile | 1 | ||||
-rw-r--r-- | drivers/ipack/carriers/tpci200.c | 627 | ||||
-rw-r--r-- | drivers/ipack/carriers/tpci200.h | 167 | ||||
-rw-r--r-- | drivers/ipack/devices/Kconfig | 6 | ||||
-rw-r--r-- | drivers/ipack/devices/Makefile | 1 | ||||
-rw-r--r-- | drivers/ipack/devices/ipoctal.c | 753 | ||||
-rw-r--r-- | drivers/ipack/devices/ipoctal.h | 42 | ||||
-rw-r--r-- | drivers/ipack/devices/scc2698.h | 228 | ||||
-rw-r--r-- | drivers/ipack/ipack.c | 481 |
12 files changed, 2343 insertions, 0 deletions
diff --git a/drivers/ipack/Kconfig b/drivers/ipack/Kconfig new file mode 100644 index 00000000000..3949e558956 --- /dev/null +++ b/drivers/ipack/Kconfig @@ -0,0 +1,24 @@ +# +# IPACK configuration. +# + +menuconfig IPACK_BUS + tristate "IndustryPack bus support" + depends on HAS_IOMEM + ---help--- + This option provides support for the IndustryPack framework. There + are IndustryPack carrier boards, which interface another bus (such as + PCI) to an IndustryPack bus, and IndustryPack modules, that are + hosted on these buses. While IndustryPack modules can provide a + large variety of functionality, they are most often found in + industrial control applications. + + Say N if unsure. + +if IPACK_BUS + +source "drivers/ipack/carriers/Kconfig" + +source "drivers/ipack/devices/Kconfig" + +endif # IPACK diff --git a/drivers/ipack/Makefile b/drivers/ipack/Makefile new file mode 100644 index 00000000000..6f14ade0f8f --- /dev/null +++ b/drivers/ipack/Makefile @@ -0,0 +1,6 @@ +# +# Makefile for the IPACK bridge device drivers. +# +obj-$(CONFIG_IPACK_BUS) += ipack.o +obj-y += devices/ +obj-y += carriers/ diff --git a/drivers/ipack/carriers/Kconfig b/drivers/ipack/carriers/Kconfig new file mode 100644 index 00000000000..922ff5c35ac --- /dev/null +++ b/drivers/ipack/carriers/Kconfig @@ -0,0 +1,7 @@ +config BOARD_TPCI200 + tristate "Support for the TEWS TPCI-200 IndustryPack carrier board" + depends on IPACK_BUS + depends on PCI + help + This driver adds support for the TEWS TPCI200 IndustryPack carrier board. + default n diff --git a/drivers/ipack/carriers/Makefile b/drivers/ipack/carriers/Makefile new file mode 100644 index 00000000000..d8b76459300 --- /dev/null +++ b/drivers/ipack/carriers/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_BOARD_TPCI200) += tpci200.o diff --git a/drivers/ipack/carriers/tpci200.c b/drivers/ipack/carriers/tpci200.c new file mode 100644 index 00000000000..0246b1fddff --- /dev/null +++ b/drivers/ipack/carriers/tpci200.c @@ -0,0 +1,627 @@ +/** + * tpci200.c + * + * driver for the TEWS TPCI-200 device + * + * Copyright (C) 2009-2012 CERN (www.cern.ch) + * Author: Nicolas Serafini, EIC2 SA + * Author: Samuel Iglesias Gonsalvez <siglesias@igalia.com> + * + * 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/module.h> +#include <linux/slab.h> +#include "tpci200.h" + +static const u16 tpci200_status_timeout[] = { + TPCI200_A_TIMEOUT, + TPCI200_B_TIMEOUT, + TPCI200_C_TIMEOUT, + TPCI200_D_TIMEOUT, +}; + +static const u16 tpci200_status_error[] = { + TPCI200_A_ERROR, + TPCI200_B_ERROR, + TPCI200_C_ERROR, + TPCI200_D_ERROR, +}; + +static const size_t tpci200_space_size[IPACK_SPACE_COUNT] = { + [IPACK_IO_SPACE] = TPCI200_IO_SPACE_SIZE, + [IPACK_ID_SPACE] = TPCI200_ID_SPACE_SIZE, + [IPACK_INT_SPACE] = TPCI200_INT_SPACE_SIZE, + [IPACK_MEM8_SPACE] = TPCI200_MEM8_SPACE_SIZE, + [IPACK_MEM16_SPACE] = TPCI200_MEM16_SPACE_SIZE, +}; + +static const size_t tpci200_space_interval[IPACK_SPACE_COUNT] = { + [IPACK_IO_SPACE] = TPCI200_IO_SPACE_INTERVAL, + [IPACK_ID_SPACE] = TPCI200_ID_SPACE_INTERVAL, + [IPACK_INT_SPACE] = TPCI200_INT_SPACE_INTERVAL, + [IPACK_MEM8_SPACE] = TPCI200_MEM8_SPACE_INTERVAL, + [IPACK_MEM16_SPACE] = TPCI200_MEM16_SPACE_INTERVAL, +}; + +static struct tpci200_board *check_slot(struct ipack_device *dev) +{ + struct tpci200_board *tpci200; + + if (dev == NULL) + return NULL; + + + tpci200 = dev_get_drvdata(dev->bus->parent); + + if (tpci200 == NULL) { + dev_info(&dev->dev, "carrier board not found\n"); + return NULL; + } + + if (dev->slot >= TPCI200_NB_SLOT) { + dev_info(&dev->dev, + "Slot [%d:%d] doesn't exist! Last tpci200 slot is %d.\n", + dev->bus->bus_nr, dev->slot, TPCI200_NB_SLOT-1); + return NULL; + } + + return tpci200; +} + +static void tpci200_clear_mask(struct tpci200_board *tpci200, + __le16 __iomem *addr, u16 mask) +{ + unsigned long flags; + spin_lock_irqsave(&tpci200->regs_lock, flags); + iowrite16(ioread16(addr) & (~mask), addr); + spin_unlock_irqrestore(&tpci200->regs_lock, flags); +} + +static void tpci200_set_mask(struct tpci200_board *tpci200, + __le16 __iomem *addr, u16 mask) +{ + unsigned long flags; + spin_lock_irqsave(&tpci200->regs_lock, flags); + iowrite16(ioread16(addr) | mask, addr); + spin_unlock_irqrestore(&tpci200->regs_lock, flags); +} + +static void tpci200_unregister(struct tpci200_board *tpci200) +{ + free_irq(tpci200->info->pdev->irq, (void *) tpci200); + + pci_iounmap(tpci200->info->pdev, tpci200->info->interface_regs); + pci_iounmap(tpci200->info->pdev, tpci200->info->cfg_regs); + + 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_MEM16_SPACE_BAR); + pci_release_region(tpci200->info->pdev, TPCI200_MEM8_SPACE_BAR); + pci_release_region(tpci200->info->pdev, TPCI200_CFG_MEM_BAR); + + pci_disable_device(tpci200->info->pdev); + pci_dev_put(tpci200->info->pdev); +} + +static void tpci200_enable_irq(struct tpci200_board *tpci200, + int islot) +{ + tpci200_set_mask(tpci200, + &tpci200->info->interface_regs->control[islot], + TPCI200_INT0_EN | TPCI200_INT1_EN); +} + +static void tpci200_disable_irq(struct tpci200_board *tpci200, + int islot) +{ + tpci200_clear_mask(tpci200, + &tpci200->info->interface_regs->control[islot], + TPCI200_INT0_EN | TPCI200_INT1_EN); +} + +static irqreturn_t tpci200_slot_irq(struct slot_irq *slot_irq) +{ + irqreturn_t ret; + + if (!slot_irq) + return -ENODEV; + ret = slot_irq->handler(slot_irq->arg); + + return ret; +} + +static irqreturn_t tpci200_interrupt(int irq, void *dev_id) +{ + struct tpci200_board *tpci200 = (struct tpci200_board *) dev_id; + struct slot_irq *slot_irq; + irqreturn_t ret; + u16 status_reg; + int i; + + /* Read status register */ + status_reg = ioread16(&tpci200->info->interface_regs->status); + + /* Did we cause the interrupt? */ + if (!(status_reg & TPCI200_SLOT_INT_MASK)) + return IRQ_NONE; + + /* callback to the IRQ handler for the corresponding slot */ + rcu_read_lock(); + for (i = 0; i < TPCI200_NB_SLOT; i++) { + if (!(status_reg & ((TPCI200_A_INT0 | TPCI200_A_INT1) << (2 * i)))) + continue; + slot_irq = rcu_dereference(tpci200->slots[i].irq); + ret = tpci200_slot_irq(slot_irq); + if (ret == -ENODEV) { + dev_info(&tpci200->info->pdev->dev, + "No registered ISR for slot [%d:%d]!. IRQ will be disabled.\n", + tpci200->number, i); + tpci200_disable_irq(tpci200, i); + } + } + rcu_read_unlock(); + + return IRQ_HANDLED; +} + +static int tpci200_free_irq(struct ipack_device *dev) +{ + struct slot_irq *slot_irq; + struct tpci200_board *tpci200; + + tpci200 = check_slot(dev); + if (tpci200 == NULL) + return -EINVAL; + + if (mutex_lock_interruptible(&tpci200->mutex)) + return -ERESTARTSYS; + + if (tpci200->slots[dev->slot].irq == NULL) { + mutex_unlock(&tpci200->mutex); + return -EINVAL; + } + + tpci200_disable_irq(tpci200, dev->slot); + slot_irq = tpci200->slots[dev->slot].irq; + /* uninstall handler */ + RCU_INIT_POINTER(tpci200->slots[dev->slot].irq, NULL); + synchronize_rcu(); + kfree(slot_irq); + mutex_unlock(&tpci200->mutex); + return 0; +} + +static int tpci200_request_irq(struct ipack_device *dev, + irqreturn_t (*handler)(void *), void *arg) +{ + int res = 0; + struct slot_irq *slot_irq; + struct tpci200_board *tpci200; + + tpci200 = check_slot(dev); + if (tpci200 == NULL) + return -EINVAL; + + if (mutex_lock_interruptible(&tpci200->mutex)) + return -ERESTARTSYS; + + if (tpci200->slots[dev->slot].irq != NULL) { + dev_err(&dev->dev, + "Slot [%d:%d] IRQ already registered !\n", + dev->bus->bus_nr, + dev->slot); + res = -EINVAL; + goto out_unlock; + } + + slot_irq = kzalloc(sizeof(struct slot_irq), GFP_KERNEL); + if (slot_irq == NULL) { + dev_err(&dev->dev, + "Slot [%d:%d] unable to allocate memory for IRQ !\n", + dev->bus->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->handler = handler; + slot_irq->arg = arg; + slot_irq->holder = dev; + + rcu_assign_pointer(tpci200->slots[dev->slot].irq, slot_irq); + tpci200_enable_irq(tpci200, dev->slot); + +out_unlock: + mutex_unlock(&tpci200->mutex); + return res; +} + +static int tpci200_register(struct tpci200_board *tpci200) +{ + int i; + int res; + phys_addr_t ioidint_base; + unsigned short slot_ctrl; + + if (pci_enable_device(tpci200->info->pdev) < 0) + return -ENODEV; + + /* Request IP interface register (Bar 2) */ + res = pci_request_region(tpci200->info->pdev, TPCI200_IP_INTERFACE_BAR, + "Carrier IP interface registers"); + if (res) { + dev_err(&tpci200->info->pdev->dev, + "(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_disable_pci; + } + + /* 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) { + dev_err(&tpci200->info->pdev->dev, + "(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 MEM8 space (Bar 5) */ + res = pci_request_region(tpci200->info->pdev, TPCI200_MEM8_SPACE_BAR, + "Carrier MEM8 space"); + if (res) { + dev_err(&tpci200->info->pdev->dev, + "(bn 0x%X, sn 0x%X) failed to allocate PCI resource for BAR 5!", + tpci200->info->pdev->bus->number, + tpci200->info->pdev->devfn); + goto out_release_ioid_int_space; + } + + /* Request MEM16 space (Bar 4) */ + res = pci_request_region(tpci200->info->pdev, TPCI200_MEM16_SPACE_BAR, + "Carrier MEM16 space"); + if (res) { + dev_err(&tpci200->info->pdev->dev, + "(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_mem8_space; + } + + /* Map internal tpci200 driver user space */ + tpci200->info->interface_regs = + ioremap_nocache(pci_resource_start(tpci200->info->pdev, + TPCI200_IP_INTERFACE_BAR), + TPCI200_IFACE_SIZE); + + /* Initialize lock that protects interface_regs */ + spin_lock_init(&tpci200->regs_lock); + + ioidint_base = pci_resource_start(tpci200->info->pdev, + TPCI200_IO_ID_INT_SPACES_BAR); + tpci200->mod_mem[IPACK_IO_SPACE] = ioidint_base + TPCI200_IO_SPACE_OFF; + tpci200->mod_mem[IPACK_ID_SPACE] = ioidint_base + TPCI200_ID_SPACE_OFF; + tpci200->mod_mem[IPACK_INT_SPACE] = + ioidint_base + TPCI200_INT_SPACE_OFF; + tpci200->mod_mem[IPACK_MEM8_SPACE] = + pci_resource_start(tpci200->info->pdev, + TPCI200_MEM8_SPACE_BAR); + tpci200->mod_mem[IPACK_MEM16_SPACE] = + pci_resource_start(tpci200->info->pdev, + TPCI200_MEM16_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; + for (i = 0; i < TPCI200_NB_SLOT; i++) + writew(slot_ctrl, &tpci200->info->interface_regs->control[i]); + + res = request_irq(tpci200->info->pdev->irq, + tpci200_interrupt, IRQF_SHARED, + KBUILD_MODNAME, (void *) tpci200); + if (res) { + dev_err(&tpci200->info->pdev->dev, + "(bn 0x%X, sn 0x%X) unable to register IRQ !", + tpci200->info->pdev->bus->number, + tpci200->info->pdev->devfn); + goto out_release_ioid_int_space; + } + + return 0; + +out_release_mem8_space: + pci_release_region(tpci200->info->pdev, TPCI200_MEM8_SPACE_BAR); +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_disable_pci: + pci_disable_device(tpci200->info->pdev); + return res; +} + +static int tpci200_get_clockrate(struct ipack_device *dev) +{ + struct tpci200_board *tpci200 = check_slot(dev); + __le16 __iomem *addr; + + if (!tpci200) + return -ENODEV; + + addr = &tpci200->info->interface_regs->control[dev->slot]; + return (ioread16(addr) & TPCI200_CLK32) ? 32 : 8; +} + +static int tpci200_set_clockrate(struct ipack_device *dev, int mherz) +{ + struct tpci200_board *tpci200 = check_slot(dev); + __le16 __iomem *addr; + + if (!tpci200) + return -ENODEV; + + addr = &tpci200->info->interface_regs->control[dev->slot]; + + switch (mherz) { + case 8: + tpci200_clear_mask(tpci200, addr, TPCI200_CLK32); + break; + case 32: + tpci200_set_mask(tpci200, addr, TPCI200_CLK32); + break; + default: + return -EINVAL; + } + return 0; +} + +static int tpci200_get_error(struct ipack_device *dev) +{ + struct tpci200_board *tpci200 = check_slot(dev); + __le16 __iomem *addr; + u16 mask; + + if (!tpci200) + return -ENODEV; + + addr = &tpci200->info->interface_regs->status; + mask = tpci200_status_error[dev->slot]; + return (ioread16(addr) & mask) ? 1 : 0; +} + +static int tpci200_get_timeout(struct ipack_device *dev) +{ + struct tpci200_board *tpci200 = check_slot(dev); + __le16 __iomem *addr; + u16 mask; + + if (!tpci200) + return -ENODEV; + + addr = &tpci200->info->interface_regs->status; + mask = tpci200_status_timeout[dev->slot]; + + return (ioread16(addr) & mask) ? 1 : 0; +} + +static int tpci200_reset_timeout(struct ipack_device *dev) +{ + struct tpci200_board *tpci200 = check_slot(dev); + __le16 __iomem *addr; + u16 mask; + + if (!tpci200) + return -ENODEV; + + addr = &tpci200->info->interface_regs->status; + mask = tpci200_status_timeout[dev->slot]; + + iowrite16(mask, addr); + return 0; +} + +static void tpci200_uninstall(struct tpci200_board *tpci200) +{ + tpci200_unregister(tpci200); + kfree(tpci200->slots); +} + +static const struct ipack_bus_ops tpci200_bus_ops = { + .request_irq = tpci200_request_irq, + .free_irq = tpci200_free_irq, + .get_clockrate = tpci200_get_clockrate, + .set_clockrate = tpci200_set_clockrate, + .get_error = tpci200_get_error, + .get_timeout = tpci200_get_timeout, + .reset_timeout = tpci200_reset_timeout, +}; + +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) + return -ENOMEM; + + res = tpci200_register(tpci200); + if (res) { + kfree(tpci200->slots); + tpci200->slots = NULL; + return res; + } + + mutex_init(&tpci200->mutex); + return 0; +} + +static void tpci200_release_device(struct ipack_device *dev) +{ + kfree(dev); +} + +static int tpci200_create_device(struct tpci200_board *tpci200, int i) +{ + enum ipack_space space; + struct ipack_device *dev = + kzalloc(sizeof(struct ipack_device), GFP_KERNEL); + if (!dev) + return -ENOMEM; + dev->slot = i; + dev->bus = tpci200->info->ipack_bus; + dev->release = tpci200_release_device; + + for (space = 0; space < IPACK_SPACE_COUNT; space++) { + dev->region[space].start = + tpci200->mod_mem[space] + + tpci200_space_interval[space] * i; + dev->region[space].size = tpci200_space_size[space]; + } + return ipack_device_register(dev); +} + +static int tpci200_pci_probe(struct pci_dev *pdev, + const struct pci_device_id *id) +{ + int ret, i; + struct tpci200_board *tpci200; + u32 reg32; + + tpci200 = kzalloc(sizeof(struct tpci200_board), GFP_KERNEL); + if (!tpci200) + return -ENOMEM; + + tpci200->info = kzalloc(sizeof(struct tpci200_infos), GFP_KERNEL); + if (!tpci200->info) { + ret = -ENOMEM; + goto out_err_info; + } + + pci_dev_get(pdev); + + /* Obtain a mapping of the carrier's PCI configuration registers */ + ret = pci_request_region(pdev, TPCI200_CFG_MEM_BAR, + KBUILD_MODNAME " Configuration Memory"); + if (ret) { + dev_err(&pdev->dev, "Failed to allocate PCI Configuration Memory"); + ret = -EBUSY; + goto out_err_pci_request; + } + tpci200->info->cfg_regs = ioremap_nocache( + pci_resource_start(pdev, TPCI200_CFG_MEM_BAR), + pci_resource_len(pdev, TPCI200_CFG_MEM_BAR)); + if (!tpci200->info->cfg_regs) { + dev_err(&pdev->dev, "Failed to map PCI Configuration Memory"); + ret = -EFAULT; + goto out_err_ioremap; + } + + /* Disable byte swapping for 16 bit IP module access. This will ensure + * that the Industrypack big endian byte order is preserved by the + * carrier. */ + reg32 = ioread32(tpci200->info->cfg_regs + LAS1_DESC); + reg32 |= 1 << LAS_BIT_BIGENDIAN; + iowrite32(reg32, tpci200->info->cfg_regs + LAS1_DESC); + + reg32 = ioread32(tpci200->info->cfg_regs + LAS2_DESC); + reg32 |= 1 << LAS_BIT_BIGENDIAN; + iowrite32(reg32, tpci200->info->cfg_regs + LAS2_DESC); + + /* 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) { + dev_err(&pdev->dev, "error during tpci200 install\n"); + ret = -ENODEV; + goto out_err_install; + } + + /* 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) { + dev_err(&pdev->dev, + "error registering the carrier on ipack driver\n"); + ret = -EFAULT; + goto out_err_bus_register; + } + + /* save the bus number given by ipack to logging purpose */ + tpci200->number = tpci200->info->ipack_bus->bus_nr; + dev_set_drvdata(&pdev->dev, tpci200); + + for (i = 0; i < TPCI200_NB_SLOT; i++) + tpci200_create_device(tpci200, i); + return 0; + +out_err_bus_register: + tpci200_uninstall(tpci200); +out_err_install: + iounmap(tpci200->info->cfg_regs); +out_err_ioremap: + pci_release_region(pdev, TPCI200_CFG_MEM_BAR); +out_err_pci_request: + pci_dev_put(pdev); + kfree(tpci200->info); +out_err_info: + kfree(tpci200); + return ret; +} + +static void __tpci200_pci_remove(struct tpci200_board *tpci200) +{ + ipack_bus_unregister(tpci200->info->ipack_bus); + tpci200_uninstall(tpci200); + + kfree(tpci200->info); + kfree(tpci200); +} + +static void tpci200_pci_remove(struct pci_dev *dev) +{ + struct tpci200_board *tpci200 = pci_get_drvdata(dev); + + __tpci200_pci_remove(tpci200); +} + +static DEFINE_PCI_DEVICE_TABLE(tpci200_idtable) = { + { TPCI200_VENDOR_ID, TPCI200_DEVICE_ID, TPCI200_SUBVENDOR_ID, + TPCI200_SUBDEVICE_ID }, + { 0, }, +}; + +MODULE_DEVICE_TABLE(pci, tpci200_idtable); + +static struct pci_driver tpci200_pci_drv = { + .name = "tpci200", + .id_table = tpci200_idtable, + .probe = tpci200_pci_probe, + .remove = tpci200_pci_remove, +}; + +module_pci_driver(tpci200_pci_drv); + +MODULE_DESCRIPTION("TEWS TPCI-200 device driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/ipack/carriers/tpci200.h b/drivers/ipack/carriers/tpci200.h new file mode 100644 index 00000000000..a7a151dab83 --- /dev/null +++ b/drivers/ipack/carriers/tpci200.h @@ -0,0 +1,167 @@ +/** + * tpci200.h + * + * driver for the carrier TEWS TPCI-200 + * + * Copyright (C) 2009-2012 CERN (www.cern.ch) + * Author: Nicolas Serafini, EIC2 SA + * Author: Samuel Iglesias Gonsalvez <siglesias@igalia.com> + * + * 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/limits.h> +#include <linux/pci.h> +#include <linux/spinlock.h> +#include <linux/swab.h> +#include <linux/io.h> +#include <linux/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_CFG_MEM_BAR 0 +#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 + +struct tpci200_regs { + __le16 revision; + /* writes to control should occur with the mutex held to protect + * read-modify-write operations */ + __le16 control[4]; + __le16 reset; + __le16 status; + u8 reserved[242]; +} __packed; + +#define TPCI200_IFACE_SIZE 0x100 + +#define TPCI200_IO_SPACE_OFF 0x0000 +#define TPCI200_IO_SPACE_INTERVAL 0x0100 +#define TPCI200_IO_SPACE_SIZE 0x0080 +#define TPCI200_ID_SPACE_OFF 0x0080 +#define TPCI200_ID_SPACE_INTERVAL 0x0100 +#define TPCI200_ID_SPACE_SIZE 0x0040 +#define TPCI200_INT_SPACE_OFF 0x00C0 +#define TPCI200_INT_SPACE_INTERVAL 0x0100 +#define TPCI200_INT_SPACE_SIZE 0x0040 +#define TPCI200_IOIDINT_SIZE 0x0400 + +#define TPCI200_MEM8_SPACE_INTERVAL 0x00400000 +#define TPCI200_MEM8_SPACE_SIZE 0x00400000 +#define TPCI200_MEM16_SPACE_INTERVAL 0x00800000 +#define TPCI200_MEM16_SPACE_SIZE 0x00800000 + +/* control field in tpci200_regs */ +#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 + +/* reset field in tpci200_regs */ +#define TPCI200_A_RESET 0x0001 +#define TPCI200_B_RESET 0x0002 +#define TPCI200_C_RESET 0x0004 +#define TPCI200_D_RESET 0x0008 + +/* status field in tpci200_regs */ +#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 + +/* PCI Configuration registers. The PCI bridge is a PLX Technology PCI9030. */ +#define LAS1_DESC 0x2C +#define LAS2_DESC 0x30 + +/* Bits in the LAS?_DESC registers */ +#define LAS_BIT_BIGENDIAN 24 + +#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 + * + */ +struct slot_irq { + struct ipack_device *holder; + int vector; + irqreturn_t (*handler)(void *); + void *arg; +}; + +/** + * 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 + * @int_phys INT physical base address register of the slot + * @mem_phys MEM physical base address register of the slot + * + */ +struct tpci200_slot { + struct slot_irq *irq; +}; + +/** + * 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) + * + */ +struct tpci200_infos { + struct pci_dev *pdev; + struct pci_device_id *id_table; + struct tpci200_regs __iomem *interface_regs; + void __iomem *cfg_regs; + struct ipack_bus_device *ipack_bus; +}; +struct tpci200_board { + unsigned int number; + struct mutex mutex; + spinlock_t regs_lock; + struct tpci200_slot *slots; + struct tpci200_infos *info; + phys_addr_t mod_mem[IPACK_SPACE_COUNT]; +}; + +#endif /* _TPCI200_H_ */ diff --git a/drivers/ipack/devices/Kconfig b/drivers/ipack/devices/Kconfig new file mode 100644 index 00000000000..0b82fdc198c --- /dev/null +++ b/drivers/ipack/devices/Kconfig @@ -0,0 +1,6 @@ +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/ipack/devices/Makefile b/drivers/ipack/devices/Makefile new file mode 100644 index 00000000000..6de18bda4a9 --- /dev/null +++ b/drivers/ipack/devices/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_SERIAL_IPOCTAL) += ipoctal.o diff --git a/drivers/ipack/devices/ipoctal.c b/drivers/ipack/devices/ipoctal.c new file mode 100644 index 00000000000..576d53d9267 --- /dev/null +++ b/drivers/ipack/devices/ipoctal.c @@ -0,0 +1,753 @@ +/** + * ipoctal.c + * + * driver for the GE IP-OCTAL boards + * + * Copyright (C) 2009-2012 CERN (www.cern.ch) + * Author: Nicolas Serafini, EIC2 SA + * Author: Samuel Iglesias Gonsalvez <siglesias@igalia.com> + * + * 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> +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/sched.h> +#include <linux/tty.h> +#include <linux/serial.h> +#include <linux/tty_flip.h> +#include <linux/slab.h> +#include <linux/atomic.h> +#include <linux/io.h> +#include <linux/ipack.h> +#include "ipoctal.h" +#include "scc2698.h" + +#define IP_OCTAL_ID_SPACE_VECTOR 0x41 +#define IP_OCTAL_NB_BLOCKS 4 + +static const struct tty_operations ipoctal_fops; + +struct ipoctal_channel { + struct ipoctal_stats stats; + unsigned int nb_bytes; + wait_queue_head_t queue; + spinlock_t lock; + unsigned int pointer_read; + unsigned int pointer_write; + atomic_t open; + struct tty_port tty_port; + union scc2698_channel __iomem *regs; + union scc2698_block __iomem *block_regs; + unsigned int board_id; + unsigned char *board_write; + u8 isr_rx_rdy_mask; + u8 isr_tx_rdy_mask; +}; + +struct ipoctal { + struct ipack_device *dev; + unsigned int board_id; + struct ipoctal_channel channel[NR_CHANNELS]; + unsigned char write; + struct tty_driver *tty_drv; + u8 __iomem *mem8_space; + u8 __iomem *int_space; +}; + +static int ipoctal_port_activate(struct tty_port *port, struct tty_struct *tty) +{ + struct ipoctal_channel *channel; + + channel = dev_get_drvdata(tty->dev); + + iowrite8(CR_ENABLE_RX, &channel->regs->w.cr); + return 0; +} + +static int ipoctal_open(struct tty_struct *tty, struct file *file) +{ + int res; + struct ipoctal_channel *channel; + + channel = dev_get_drvdata(tty->dev); + + if (atomic_read(&channel->open)) + return -EBUSY; + + tty->driver_data = channel; + + res = tty_port_open(&channel->tty_port, tty, file); + if (res) + return res; + + atomic_inc(&channel->open); + 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 ipoctal_channel *channel) +{ + ipoctal_reset_stats(&channel->stats); + channel->pointer_read = 0; + channel->pointer_write = 0; + channel->nb_bytes = 0; +} + +static void ipoctal_close(struct tty_struct *tty, struct file *filp) +{ + struct ipoctal_channel *channel = tty->driver_data; + + tty_port_close(&channel->tty_port, tty, filp); + + if (atomic_dec_and_test(&channel->open)) + ipoctal_free_channel(channel); +} + +static int ipoctal_get_icount(struct tty_struct *tty, + struct serial_icounter_struct *icount) +{ + struct ipoctal_channel *channel = tty->driver_data; + + icount->cts = 0; + icount->dsr = 0; + icount->rng = 0; + icount->dcd = 0; + icount->rx = channel->stats.rx; + icount->tx = channel->stats.tx; + icount->frame = channel->stats.framing_err; + icount->parity = channel->stats.parity_err; + icount->brk = channel->stats.rcv_break; + return 0; +} + +static void ipoctal_irq_rx(struct ipoctal_channel *channel, + struct tty_struct *tty, u8 sr) +{ + unsigned char value; + unsigned char flag = TTY_NORMAL; + u8 isr; + + do { + value = ioread8(&channel->regs->r.rhr); + /* Error: count statistics */ + if (sr & SR_ERROR) { + iowrite8(CR_CMD_RESET_ERR_STATUS, &channel->regs->w.cr); + + if (sr & SR_OVERRUN_ERROR) { + channel->stats.overrun_err++; + /* Overrun doesn't affect the current character*/ + tty_insert_flip_char(tty, 0, TTY_OVERRUN); + } + if (sr & SR_PARITY_ERROR) { + channel->stats.parity_err++; + flag = TTY_PARITY; + } + if (sr & SR_FRAMING_ERROR) { + channel->stats.framing_err++; + flag = TTY_FRAME; + } + if (sr & SR_RECEIVED_BREAK) { + iowrite8(CR_CMD_RESET_BREAK_CHANGE, &channel->regs->w.cr); + channel->stats.rcv_break++; + flag = TTY_BREAK; + } + } + tty_insert_flip_char(tty, value, flag); + + /* Check if there are more characters in RX FIFO + * If there are more, the isr register for this channel + * has enabled the RxRDY|FFULL bit. + */ + isr = ioread8(&channel->block_regs->r.isr); + sr = ioread8(&channel->regs->r.sr); + } while (isr & channel->isr_rx_rdy_mask); + + tty_flip_buffer_push(tty); +} + +static void ipoctal_irq_tx(struct ipoctal_channel *channel) +{ + unsigned char value; + unsigned int *pointer_write = &channel->pointer_write; + + if (channel->nb_bytes <= 0) { + channel->nb_bytes = 0; + return; + } + + value = channel->tty_port.xmit_buf[*pointer_write]; + iowrite8(value, &channel->regs->w.thr); + channel->stats.tx++; + (*pointer_write)++; + *pointer_write = *pointer_write % PAGE_SIZE; + channel->nb_bytes--; + + if ((channel->nb_bytes == 0) && + (waitqueue_active(&channel->queue))) { + + if (channel->board_id != IPACK1_DEVICE_ID_SBS_OCTAL_485) { + *channel->board_write = 1; + wake_up_interruptible(&channel->queue); + } + } +} + +static void ipoctal_irq_channel(struct ipoctal_channel *channel) +{ + u8 isr, sr; + struct tty_struct *tty; + + /* If there is no client, skip the check */ + if (!atomic_read(&channel->open)) + return; + + tty = tty_port_tty_get(&channel->tty_port); + if (!tty) + return; + /* The HW is organized in pair of channels. See which register we need + * to read from */ + isr = ioread8(&channel->block_regs->r.isr); + sr = ioread8(&channel->regs->r.sr); + + /* In case of RS-485, change from TX to RX when finishing TX. + * Half-duplex. */ + if ((channel->board_id == IPACK1_DEVICE_ID_SBS_OCTAL_485) && + (sr & SR_TX_EMPTY) && (channel->nb_bytes == 0)) { + iowrite8(CR_DISABLE_TX, &channel->regs->w.cr); + iowrite8(CR_CMD_NEGATE_RTSN, &channel->regs->w.cr); + iowrite8(CR_ENABLE_RX, &channel->regs->w.cr); + *channel->board_write = 1; + wake_up_interruptible(&channel->queue); + } + + /* RX data */ + if ((isr & channel->isr_rx_rdy_mask) && (sr & SR_RX_READY)) + ipoctal_irq_rx(channel, tty, sr); + + /* TX of each character */ + if ((isr & channel->isr_tx_rdy_mask) && (sr & SR_TX_READY)) + ipoctal_irq_tx(channel); + + tty_flip_buffer_push(tty); + tty_kref_put(tty); +} + +static irqreturn_t ipoctal_irq_handler(void *arg) +{ + unsigned int i; + struct ipoctal *ipoctal = (struct ipoctal *) arg; + + /* Check all channels */ + for (i = 0; i < NR_CHANNELS; i++) + ipoctal_irq_channel(&ipoctal->channel[i]); + + /* Clear the IPack device interrupt */ + readw(ipoctal->int_space + ACK_INT_REQ0); + readw(ipoctal->int_space + ACK_INT_REQ1); + + return IRQ_HANDLED; +} + +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) +{ + int res; + int i; + struct tty_driver *tty; + char name[20]; + struct ipoctal_channel *channel; + struct ipack_region *region; + void __iomem *addr; + union scc2698_channel __iomem *chan_regs; + union scc2698_block __iomem *block_regs; + + ipoctal->board_id = ipoctal->dev->id_device; + + region = &ipoctal->dev->region[IPACK_IO_SPACE]; + addr = devm_ioremap_nocache(&ipoctal->dev->dev, + region->start, region->size); + if (!addr) { + dev_err(&ipoctal->dev->dev, + "Unable to map slot [%d:%d] IO space!\n", + bus_nr, slot); + return -EADDRNOTAVAIL; + } + /* Save the virtual address to access the registers easily */ + chan_regs = + (union scc2698_channel __iomem *) addr; + block_regs = + (union scc2698_block __iomem *) addr; + + region = &ipoctal->dev->region[IPACK_INT_SPACE]; + ipoctal->int_space = + devm_ioremap_nocache(&ipoctal->dev->dev, + region->start, region->size); + if (!ipoctal->int_space) { + dev_err(&ipoctal->dev->dev, + "Unable to map slot [%d:%d] INT space!\n", + bus_nr, slot); + return -EADDRNOTAVAIL; + } + + region = &ipoctal->dev->region[IPACK_MEM8_SPACE]; + ipoctal->mem8_space = + devm_ioremap_nocache(&ipoctal->dev->dev, + region->start, 0x8000); + if (!addr) { + dev_err(&ipoctal->dev->dev, + "Unable to map slot [%d:%d] MEM8 space!\n", + bus_nr, slot); + return -EADDRNOTAVAIL; + } + + + /* Disable RX and TX before touching anything */ + for (i = 0; i < NR_CHANNELS ; i++) { + struct ipoctal_channel *channel = &ipoctal->channel[i]; + channel->regs = chan_regs + i; + channel->block_regs = block_regs + (i >> 1); + channel->board_write = &ipoctal->write; + channel->board_id = ipoctal->board_id; + if (i & 1) { + channel->isr_tx_rdy_mask = ISR_TxRDY_B; + channel->isr_rx_rdy_mask = ISR_RxRDY_FFULL_B; + } else { + channel->isr_tx_rdy_mask = ISR_TxRDY_A; + channel->isr_rx_rdy_mask = ISR_RxRDY_FFULL_A; + } + + iowrite8(CR_DISABLE_RX | CR_DISABLE_TX, &channel->regs->w.cr); + iowrite8(CR_CMD_RESET_RX, &channel->regs->w.cr); + iowrite8(CR_CMD_RESET_TX, &channel->regs->w.cr); + iowrite8(MR1_CHRL_8_BITS | MR1_ERROR_CHAR | MR1_RxINT_RxRDY, + &channel->regs->w.mr); /* mr1 */ + iowrite8(0, &channel->regs->w.mr); /* mr2 */ + iowrite8(TX_CLK_9600 | RX_CLK_9600, &channel->regs->w.csr); + } + + for (i = 0; i < IP_OCTAL_NB_BLOCKS; i++) { + iowrite8(ACR_BRG_SET2, &block_regs[i].w.acr); + iowrite8(OPCR_MPP_OUTPUT | OPCR_MPOa_RTSN | OPCR_MPOb_RTSN, + &block_regs[i].w.opcr); + iowrite8(IMR_TxRDY_A | IMR_RxRDY_FFULL_A | IMR_DELTA_BREAK_A | + IMR_TxRDY_B | IMR_RxRDY_FFULL_B | IMR_DELTA_BREAK_B, + &block_regs[i].w.imr); + } + + /* + * 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, + ipoctal_irq_handler, ipoctal); + /* Dummy write */ + iowrite8(1, ipoctal->mem8_space + 1); + + /* Register the TTY device */ + + /* Each IP-OCTAL channel is a TTY port */ + tty = alloc_tty_driver(NR_CHANNELS); + + if (!tty) + return -ENOMEM; + + /* Fill struct tty_driver with ipoctal data */ + tty->owner = THIS_MODULE; + tty->driver_name = KBUILD_MODNAME; + sprintf(name, KBUILD_MODNAME ".%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) { + dev_err(&ipoctal->dev->dev, "Can't register tty driver.\n"); + put_tty_driver(tty); + return res; + } + + /* Save struct tty_driver for use it when uninstalling the device */ + ipoctal->tty_drv = tty; + + for (i = 0; i < NR_CHANNELS; i++) { + struct device *tty_dev; + + channel = &ipoctal->channel[i]; + tty_port_init(&channel->tty_port); + tty_port_alloc_xmit_buf(&channel->tty_port); + channel->tty_port.ops = &ipoctal_tty_port_ops; + + ipoctal_reset_stats(&channel->stats); + channel->nb_bytes = 0; + init_waitqueue_head(&channel->queue); + + spin_lock_init(&channel->lock); + channel->pointer_read = 0; + channel->pointer_write = 0; + tty_dev = tty_port_register_device(&channel->tty_port, tty, i, NULL); + if (IS_ERR(tty_dev)) { + dev_err(&ipoctal->dev->dev, "Failed to register tty device.\n"); + tty_port_destroy(&channel->tty_port); + continue; + } + dev_set_drvdata(tty_dev, channel); + + /* + * Enable again the RX. TX will be enabled when + * there is something to send + */ + iowrite8(CR_ENABLE_RX, &channel->regs->w.cr); + } + + return 0; +} + +static inline int ipoctal_copy_write_buffer(struct ipoctal_channel *channel, + const unsigned char *buf, + int count) +{ + unsigned long flags; + int i; + unsigned int *pointer_read = &channel->pointer_read; + + /* Copy the bytes from the user buffer to the internal one */ + for (i = 0; i < count; i++) { + if (i <= (PAGE_SIZE - channel->nb_bytes)) { + spin_lock_irqsave(&channel->lock, flags); + channel->tty_port.xmit_buf[*pointer_read] = buf[i]; + *pointer_read = (*pointer_read + 1) % PAGE_SIZE; + channel->nb_bytes++; + spin_unlock_irqrestore(&channel->lock, flags); + } else { + break; + } + } + return i; +} + +static int ipoctal_write_tty(struct tty_struct *tty, + const unsigned char *buf, int count) +{ + struct ipoctal_channel *channel = tty->driver_data; + unsigned int char_copied; + + char_copied = ipoctal_copy_write_buffer(channel, buf, count); + + /* As the IP-OCTAL 485 only supports half duplex, do it manually */ + if (channel->board_id == IPACK1_DEVICE_ID_SBS_OCTAL_485) { + iowrite8(CR_DISABLE_RX, &channel->regs->w.cr); + iowrite8(CR_CMD_ASSERT_RTSN, &channel->regs->w.cr); + } + + /* + * Send a packet and then disable TX to avoid failure after several send + * operations + */ + iowrite8(CR_ENABLE_TX, &channel->regs->w.cr); + wait_event_interruptible(channel->queue, *channel->board_write); + iowrite8(CR_DISABLE_TX, &channel->regs->w.cr); + + *channel->board_write = 0; + return char_copied; +} + +static int ipoctal_write_room(struct tty_struct *tty) +{ + struct ipoctal_channel *channel = tty->driver_data; + + return PAGE_SIZE - channel->nb_bytes; +} + +static int ipoctal_chars_in_buffer(struct tty_struct *tty) +{ + struct ipoctal_channel *channel = tty->driver_data; + + return channel->nb_bytes; +} + +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; + struct ipoctal_channel *channel = tty->driver_data; + speed_t baud; + + cflag = tty->termios.c_cflag; + + /* Disable and reset everything before change the setup */ + iowrite8(CR_DISABLE_RX | CR_DISABLE_TX, &channel->regs->w.cr); + iowrite8(CR_CMD_RESET_RX, &channel->regs->w.cr); + iowrite8(CR_CMD_RESET_TX, &channel->regs->w.cr); + iowrite8(CR_CMD_RESET_ERR_STATUS, &channel->regs->w.cr); + iowrite8(CR_CMD_RESET_MR, &channel->regs->w.cr); + + /* 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 (channel->board_id) { + case IPACK1_DEVICE_ID_SBS_OCTAL_232: + if (cflag & CRTSCTS) { + mr1 |= MR1_RxRTS_CONTROL_ON; + mr2 |= MR2_TxRTS_CONTROL_OFF | MR2_CTS_ENABLE_TX_ON; + } else { + mr1 |= MR1_RxRTS_CONTROL_OFF; + mr2 |= MR2_TxRTS_CONTROL_OFF | MR2_CTS_ENABLE_TX_OFF; + } + break; + case IPACK1_DEVICE_ID_SBS_OCTAL_422: + mr1 |= MR1_RxRTS_CONTROL_OFF; + mr2 |= MR2_TxRTS_CONTROL_OFF | MR2_CTS_ENABLE_TX_OFF; + break; + case IPACK1_DEVICE_ID_SBS_OCTAL_485: + mr1 |= MR1_RxRTS_CONTROL_OFF; + mr2 |= MR2_TxRTS_CONTROL_ON | MR2_CTS_ENABLE_TX_OFF; + break; + default: + return; + break; + } + + baud = tty_get_baud_rate(tty); + tty_termios_encode_baud_rate(&tty->termios, baud, baud); + + /* Set baud rate */ + switch (baud) { + 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 */ + iowrite8(mr1, &channel->regs->w.mr); + iowrite8(mr2, &channel->regs->w.mr); + iowrite8(csr, &channel->regs->w.csr); + + /* Enable again the RX */ + iowrite8(CR_ENABLE_RX, &channel->regs->w.cr); +} + +static void ipoctal_hangup(struct tty_struct *tty) +{ + unsigned long flags; + struct ipoctal_channel *channel = tty->driver_data; + + if (channel == NULL) + return; + + spin_lock_irqsave(&channel->lock, flags); + channel->nb_bytes = 0; + channel->pointer_read = 0; + channel->pointer_write = 0; + spin_unlock_irqrestore(&channel->lock, flags); + + tty_port_hangup(&channel->tty_port); + + iowrite8(CR_DISABLE_RX | CR_DISABLE_TX, &channel->regs->w.cr); + iowrite8(CR_CMD_RESET_RX, &channel->regs->w.cr); + iowrite8(CR_CMD_RESET_TX, &channel->regs->w.cr); + iowrite8(CR_CMD_RESET_ERR_STATUS, &channel->regs->w.cr); + iowrite8(CR_CMD_RESET_MR, &channel->regs->w.cr); + + clear_bit(ASYNCB_INITIALIZED, &channel->tty_port.flags); + wake_up_interruptible(&channel->tty_port.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_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->bus_nr, dev->slot); + if (res) + goto out_uninst; + + dev_set_drvdata(&dev->dev, ipoctal); + return 0; + +out_uninst: + kfree(ipoctal); + return res; +} + +static void __ipoctal_remove(struct ipoctal *ipoctal) +{ + int i; + + ipoctal->dev->bus->ops->free_irq(ipoctal->dev); + + for (i = 0; i < NR_CHANNELS; i++) { + struct ipoctal_channel *channel = &ipoctal->channel[i]; + tty_unregister_device(ipoctal->tty_drv, i); + tty_port_free_xmit_buf(&channel->tty_port); + tty_port_destroy(&channel->tty_port); + } + + tty_unregister_driver(ipoctal->tty_drv); + put_tty_driver(ipoctal->tty_drv); + kfree(ipoctal); +} + +static void ipoctal_remove(struct ipack_device *idev) +{ + __ipoctal_remove(dev_get_drvdata(&idev->dev)); +} + +static DEFINE_IPACK_DEVICE_TABLE(ipoctal_ids) = { + { IPACK_DEVICE(IPACK_ID_VERSION_1, IPACK1_VENDOR_ID_SBS, + IPACK1_DEVICE_ID_SBS_OCTAL_232) }, + { IPACK_DEVICE(IPACK_ID_VERSION_1, IPACK1_VENDOR_ID_SBS, + IPACK1_DEVICE_ID_SBS_OCTAL_422) }, + { IPACK_DEVICE(IPACK_ID_VERSION_1, IPACK1_VENDOR_ID_SBS, + IPACK1_DEVICE_ID_SBS_OCTAL_485) }, + { 0, }, +}; + +MODULE_DEVICE_TABLE(ipack, ipoctal_ids); + +static const struct ipack_driver_ops ipoctal_drv_ops = { + .probe = ipoctal_probe, + .remove = ipoctal_remove, +}; + +static struct ipack_driver driver = { + .ops = &ipoctal_drv_ops, + .id_table = ipoctal_ids, +}; + +static int __init ipoctal_init(void) +{ + return ipack_driver_register(&driver, THIS_MODULE, KBUILD_MODNAME); +} + +static void __exit ipoctal_exit(void) +{ + 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/ipack/devices/ipoctal.h b/drivers/ipack/devices/ipoctal.h new file mode 100644 index 00000000000..28f1c423315 --- /dev/null +++ b/drivers/ipack/devices/ipoctal.h @@ -0,0 +1,42 @@ +/** + * ipoctal.h + * + * driver for the IPOCTAL boards + + * Copyright (C) 2009-2012 CERN (www.cern.ch) + * Author: Nicolas Serafini, EIC2 SA + * Author: Samuel Iglesias Gonsalvez <siglesias@igalia.com> + * + * 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)) + +/** + * 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/ipack/devices/scc2698.h b/drivers/ipack/devices/scc2698.h new file mode 100644 index 00000000000..2ad6acd513f --- /dev/null +++ b/drivers/ipack/devices/scc2698.h @@ -0,0 +1,228 @@ +/* + * scc2698.h + * + * driver for the IPOCTAL boards + * + * Copyright (C) 2009-2012 CERN (www.cern.ch) + * Author: Nicolas Serafini, EIC2 SA + * Author: Samuel Iglesias Gonsalvez <siglesias@igalia.com> + * + * 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_ + +/* + * union scc2698_channel - Channel access to scc2698 IO + * + * dn value are only spacer. + * + */ +union scc2698_channel { + struct { + u8 d0, mr; /* Mode register 1/2*/ + u8 d1, sr; /* Status register */ + u8 d2, r1; /* reserved */ + u8 d3, rhr; /* Receive holding register (R) */ + u8 junk[8]; /* other crap for block control */ + } __packed r; /* Read access */ + struct { + u8 d0, mr; /* Mode register 1/2 */ + u8 d1, csr; /* Clock select register */ + u8 d2, cr; /* Command register */ + u8 d3, thr; /* Transmit holding register */ + u8 junk[8]; /* other crap for block control */ + } __packed w; /* Write access */ +}; + +/* + * union 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. + * + */ +union scc2698_block { + struct { + u8 d0, mra; /* Mode register 1/2 (a) */ + u8 d1, sra; /* Status register (a) */ + u8 d2, r1; /* reserved */ + u8 d3, rhra; /* Receive holding register (a) */ + u8 d4, ipcr; /* Input port change register of block */ + u8 d5, isr; /* Interrupt status register of block */ + u8 d6, ctur; /* Counter timer upper register of block */ + u8 d7, ctlr; /* Counter timer lower register of block */ + u8 d8, mrb; /* Mode register 1/2 (b) */ + u8 d9, srb; /* Status register (b) */ + u8 da, r2; /* reserved */ + u8 db, rhrb; /* Receive holding register (b) */ + u8 dc, r3; /* reserved */ + u8 dd, ip; /* Input port register of block */ + u8 de, ctg; /* Start counter timer of block */ + u8 df, cts; /* Stop counter timer of block */ + } __packed r; /* Read access */ + struct { + u8 d0, mra; /* Mode register 1/2 (a) */ + u8 d1, csra; /* Clock select register (a) */ + u8 d2, cra; /* Command register (a) */ + u8 d3, thra; /* Transmit holding register (a) */ + u8 d4, acr; /* Auxiliary control register of block */ + u8 d5, imr; /* Interrupt mask register of block */ + u8 d6, ctu; /* Counter timer upper register of block */ + u8 d7, ctl; /* Counter timer lower register of block */ + u8 d8, mrb; /* Mode register 1/2 (b) */ + u8 d9, csrb; /* Clock select register (a) */ + u8 da, crb; /* Command register (b) */ + u8 db, thrb; /* Transmit holding register (b) */ + u8 dc, r1; /* reserved */ + u8 dd, opcr; /* Output port configuration register of block */ + u8 de, r2; /* reserved */ + u8 df, r3; /* reserved */ + } __packed w; /* Write access */ +}; + +#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) + +#define ACK_INT_REQ0 0 +#define ACK_INT_REQ1 2 + +#endif /* SCC2698_H_ */ diff --git a/drivers/ipack/ipack.c b/drivers/ipack/ipack.c new file mode 100644 index 00000000000..7ec6b208b1c --- /dev/null +++ b/drivers/ipack/ipack.c @@ -0,0 +1,481 @@ +/* + * Industry-pack bus support functions. + * + * Copyright (C) 2011-2012 CERN (www.cern.ch) + * Author: Samuel Iglesias Gonsalvez <siglesias@igalia.com> + * + * 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/module.h> +#include <linux/slab.h> +#include <linux/idr.h> +#include <linux/io.h> +#include <linux/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) + +static DEFINE_IDA(ipack_ida); + +static void ipack_device_release(struct device *dev) +{ + struct ipack_device *device = to_ipack_dev(dev); + kfree(device->id); + device->release(device); +} + +static inline const struct ipack_device_id * +ipack_match_one_device(const struct ipack_device_id *id, + const struct ipack_device *device) +{ + if ((id->format == IPACK_ANY_FORMAT || + id->format == device->id_format) && + (id->vendor == IPACK_ANY_ID || id->vendor == device->id_vendor) && + (id->device == IPACK_ANY_ID || id->device == device->id_device)) + return id; + return NULL; +} + +static const struct ipack_device_id * +ipack_match_id(const struct ipack_device_id *ids, struct ipack_device *idev) +{ + if (ids) { + while (ids->vendor || ids->device) { + if (ipack_match_one_device(ids, idev)) + return ids; + ids++; + } + } + return NULL; +} + +static int ipack_bus_match(struct device *dev, struct device_driver *drv) +{ + struct ipack_device *idev = to_ipack_dev(dev); + struct ipack_driver *idrv = to_ipack_driver(drv); + const struct ipack_device_id *found_id; + + found_id = ipack_match_id(idrv->id_table, idev); + return found_id ? 1 : 0; +} + +static int ipack_bus_probe(struct device *device) +{ + struct ipack_device *dev = to_ipack_dev(device); + struct ipack_driver *drv = to_ipack_driver(device->driver); + + if (!drv->ops->probe) + return -EINVAL; + + return drv->ops->probe(dev); +} + +static int ipack_bus_remove(struct device *device) +{ + struct ipack_device *dev = to_ipack_dev(device); + struct ipack_driver *drv = to_ipack_driver(device->driver); + + if (!drv->ops->remove) + return -EINVAL; + + drv->ops->remove(dev); + return 0; +} + +static int ipack_uevent(struct device *dev, struct kobj_uevent_env *env) +{ + struct ipack_device *idev; + + if (!dev) + return -ENODEV; + + idev = to_ipack_dev(dev); + + if (add_uevent_var(env, + "MODALIAS=ipack:f%02Xv%08Xd%08X", idev->id_format, + idev->id_vendor, idev->id_device)) + return -ENOMEM; + + return 0; +} + +#define ipack_device_attr(field, format_string) \ +static ssize_t \ +field##_show(struct device *dev, struct device_attribute *attr, \ + char *buf) \ +{ \ + struct ipack_device *idev = to_ipack_dev(dev); \ + return sprintf(buf, format_string, idev->field); \ +} + +static ssize_t id_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + unsigned int i, c, l, s; + struct ipack_device *idev = to_ipack_dev(dev); + + + switch (idev->id_format) { + case IPACK_ID_VERSION_1: + l = 0x7; s = 1; break; + case IPACK_ID_VERSION_2: + l = 0xf; s = 2; break; + default: + return -EIO; + } + c = 0; + for (i = 0; i < idev->id_avail; i++) { + if (i > 0) { + if ((i & l) == 0) + buf[c++] = '\n'; + else if ((i & s) == 0) + buf[c++] = ' '; + } + sprintf(&buf[c], "%02x", idev->id[i]); + c += 2; + } + buf[c++] = '\n'; + return c; +} + +static ssize_t +id_vendor_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct ipack_device *idev = to_ipack_dev(dev); + switch (idev->id_format) { + case IPACK_ID_VERSION_1: + return sprintf(buf, "0x%02x\n", idev->id_vendor); + case IPACK_ID_VERSION_2: + return sprintf(buf, "0x%06x\n", idev->id_vendor); + default: + return -EIO; + } +} + +static ssize_t +id_device_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct ipack_device *idev = to_ipack_dev(dev); + switch (idev->id_format) { + case IPACK_ID_VERSION_1: + return sprintf(buf, "0x%02x\n", idev->id_device); + case IPACK_ID_VERSION_2: + return sprintf(buf, "0x%04x\n", idev->id_device); + default: + return -EIO; + } +} + +static ssize_t modalias_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct ipack_device *idev = to_ipack_dev(dev); + + return sprintf(buf, "ipac:f%02Xv%08Xd%08X", idev->id_format, + idev->id_vendor, idev->id_device); +} + +ipack_device_attr(id_format, "0x%hhu\n"); + +static struct device_attribute ipack_dev_attrs[] = { + __ATTR_RO(id), + __ATTR_RO(id_device), + __ATTR_RO(id_format), + __ATTR_RO(id_vendor), + __ATTR_RO(modalias), +}; + +static struct bus_type ipack_bus_type = { + .name = "ipack", + .probe = ipack_bus_probe, + .match = ipack_bus_match, + .remove = ipack_bus_remove, + .dev_attrs = ipack_dev_attrs, + .uevent = ipack_uevent, +}; + +struct ipack_bus_device *ipack_bus_register(struct device *parent, int slots, + const 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 = ida_simple_get(&ipack_ida, 0, 0, GFP_KERNEL); + 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); + +static int ipack_unregister_bus_member(struct device *dev, void *data) +{ + struct ipack_device *idev = to_ipack_dev(dev); + struct ipack_bus_device *bus = data; + + if (idev->bus == bus) + ipack_device_unregister(idev); + + return 1; +} + +int ipack_bus_unregister(struct ipack_bus_device *bus) +{ + bus_for_each_dev(&ipack_bus_type, NULL, bus, + ipack_unregister_bus_member); + ida_simple_remove(&ipack_ida, bus->bus_nr); + kfree(bus); + return 0; +} +EXPORT_SYMBOL_GPL(ipack_bus_unregister); + +int ipack_driver_register(struct ipack_driver *edrv, struct module *owner, + const 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); + +static u16 ipack_crc_byte(u16 crc, u8 c) +{ + int i; + + crc ^= c << 8; + for (i = 0; i < 8; i++) + crc = (crc << 1) ^ ((crc & 0x8000) ? 0x1021 : 0); + return crc; +} + +/* + * The algorithm in lib/crc-ccitt.c does not seem to apply since it uses the + * opposite bit ordering. + */ +static u8 ipack_calc_crc1(struct ipack_device *dev) +{ + u8 c; + u16 crc; + unsigned int i; + + crc = 0xffff; + for (i = 0; i < dev->id_avail; i++) { + c = (i != 11) ? dev->id[i] : 0; + crc = ipack_crc_byte(crc, c); + } + crc = ~crc; + return crc & 0xff; +} + +static u16 ipack_calc_crc2(struct ipack_device *dev) +{ + u8 c; + u16 crc; + unsigned int i; + + crc = 0xffff; + for (i = 0; i < dev->id_avail; i++) { + c = ((i != 0x18) && (i != 0x19)) ? dev->id[i] : 0; + crc = ipack_crc_byte(crc, c); + } + crc = ~crc; + return crc; +} + +static void ipack_parse_id1(struct ipack_device *dev) +{ + u8 *id = dev->id; + u8 crc; + + dev->id_vendor = id[4]; + dev->id_device = id[5]; + dev->speed_8mhz = 1; + dev->speed_32mhz = (id[7] == 'H'); + crc = ipack_calc_crc1(dev); + dev->id_crc_correct = (crc == id[11]); + if (!dev->id_crc_correct) { + dev_warn(&dev->dev, "ID CRC invalid found 0x%x, expected 0x%x.\n", + id[11], crc); + } +} + +static void ipack_parse_id2(struct ipack_device *dev) +{ + __be16 *id = (__be16 *) dev->id; + u16 flags, crc; + + dev->id_vendor = ((be16_to_cpu(id[3]) & 0xff) << 16) + + be16_to_cpu(id[4]); + dev->id_device = be16_to_cpu(id[5]); + flags = be16_to_cpu(id[10]); + dev->speed_8mhz = !!(flags & 2); + dev->speed_32mhz = !!(flags & 4); + crc = ipack_calc_crc2(dev); + dev->id_crc_correct = (crc == be16_to_cpu(id[12])); + if (!dev->id_crc_correct) { + dev_warn(&dev->dev, "ID CRC invalid found 0x%x, expected 0x%x.\n", + id[11], crc); + } +} + +static int ipack_device_read_id(struct ipack_device *dev) +{ + u8 __iomem *idmem; + int i; + int ret = 0; + + idmem = ioremap(dev->region[IPACK_ID_SPACE].start, + dev->region[IPACK_ID_SPACE].size); + if (!idmem) { + dev_err(&dev->dev, "error mapping memory\n"); + return -ENOMEM; + } + + /* Determine ID PROM Data Format. If we find the ids "IPAC" or "IPAH" + * we are dealing with a IndustryPack format 1 device. If we detect + * "VITA4 " (16 bit big endian formatted) we are dealing with a + * IndustryPack format 2 device */ + if ((ioread8(idmem + 1) == 'I') && + (ioread8(idmem + 3) == 'P') && + (ioread8(idmem + 5) == 'A') && + ((ioread8(idmem + 7) == 'C') || + (ioread8(idmem + 7) == 'H'))) { + dev->id_format = IPACK_ID_VERSION_1; + dev->id_avail = ioread8(idmem + 0x15); + if ((dev->id_avail < 0x0c) || (dev->id_avail > 0x40)) { + dev_warn(&dev->dev, "invalid id size"); + dev->id_avail = 0x0c; + } + } else if ((ioread8(idmem + 0) == 'I') && + (ioread8(idmem + 1) == 'V') && + (ioread8(idmem + 2) == 'A') && + (ioread8(idmem + 3) == 'T') && + (ioread8(idmem + 4) == ' ') && + (ioread8(idmem + 5) == '4')) { + dev->id_format = IPACK_ID_VERSION_2; + dev->id_avail = ioread16be(idmem + 0x16); + if ((dev->id_avail < 0x1a) || (dev->id_avail > 0x40)) { + dev_warn(&dev->dev, "invalid id size"); + dev->id_avail = 0x1a; + } + } else { + dev->id_format = IPACK_ID_VERSION_INVALID; + dev->id_avail = 0; + } + + if (!dev->id_avail) { + ret = -ENODEV; + goto out; + } + + /* Obtain the amount of memory required to store a copy of the complete + * ID ROM contents */ + dev->id = kmalloc(dev->id_avail, GFP_KERNEL); + if (!dev->id) { + dev_err(&dev->dev, "dev->id alloc failed.\n"); + ret = -ENOMEM; + goto out; + } + for (i = 0; i < dev->id_avail; i++) { + if (dev->id_format == IPACK_ID_VERSION_1) + dev->id[i] = ioread8(idmem + (i << 1) + 1); + else + dev->id[i] = ioread8(idmem + i); + } + + /* now we can finally work with the copy */ + switch (dev->id_format) { + case IPACK_ID_VERSION_1: + ipack_parse_id1(dev); + break; + case IPACK_ID_VERSION_2: + ipack_parse_id2(dev); + break; + } + +out: + iounmap(idmem); + + return ret; +} + +int ipack_device_register(struct ipack_device *dev) +{ + int ret; + + dev->dev.bus = &ipack_bus_type; + dev->dev.release = ipack_device_release; + dev->dev.parent = dev->bus->parent; + dev_set_name(&dev->dev, + "ipack-dev.%u.%u", dev->bus->bus_nr, dev->slot); + + if (dev->bus->ops->set_clockrate(dev, 8)) + dev_warn(&dev->dev, "failed to switch to 8 MHz operation for reading of device ID.\n"); + if (dev->bus->ops->reset_timeout(dev)) + dev_warn(&dev->dev, "failed to reset potential timeout."); + + ret = ipack_device_read_id(dev); + if (ret < 0) { + dev_err(&dev->dev, "error reading device id section.\n"); + return ret; + } + + /* if the device supports 32 MHz operation, use it. */ + if (dev->speed_32mhz) { + ret = dev->bus->ops->set_clockrate(dev, 32); + if (ret < 0) + dev_err(&dev->dev, "failed to switch to 32 MHz operation.\n"); + } + + ret = device_register(&dev->dev); + if (ret < 0) + kfree(dev->id); + + return ret; +} +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) +{ + ida_init(&ipack_ida); + return bus_register(&ipack_bus_type); +} + +static void __exit ipack_exit(void) +{ + bus_unregister(&ipack_bus_type); + ida_destroy(&ipack_ida); +} + +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"); |