| /** |
| * 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); |