| /* |
| * GE PIO2 6U VME I/O Driver |
| * |
| * Author: Martyn Welch <martyn.welch@ge.com> |
| * Copyright 2009 GE Intelligent Platforms Embedded Systems, Inc. |
| * |
| * 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; either version 2 of the License, or (at your |
| * option) any later version. |
| */ |
| |
| #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |
| |
| #include <linux/module.h> |
| #include <linux/types.h> |
| #include <linux/kernel.h> |
| #include <linux/errno.h> |
| #include <linux/device.h> |
| #include <linux/ctype.h> |
| #include <linux/gpio.h> |
| #include <linux/slab.h> |
| #include <linux/vme.h> |
| |
| #include "vme_pio2.h" |
| |
| static const char driver_name[] = "pio2"; |
| |
| static int bus[PIO2_CARDS_MAX]; |
| static int bus_num; |
| static long base[PIO2_CARDS_MAX]; |
| static int base_num; |
| static int vector[PIO2_CARDS_MAX]; |
| static int vector_num; |
| static int level[PIO2_CARDS_MAX]; |
| static int level_num; |
| static char *variant[PIO2_CARDS_MAX]; |
| static int variant_num; |
| |
| static bool loopback; |
| |
| static int pio2_match(struct vme_dev *); |
| static int pio2_probe(struct vme_dev *); |
| static int pio2_remove(struct vme_dev *); |
| |
| static int pio2_get_led(struct pio2_card *card) |
| { |
| /* Can't read hardware, state saved in structure */ |
| return card->led; |
| } |
| |
| static int pio2_set_led(struct pio2_card *card, int state) |
| { |
| u8 reg; |
| int retval; |
| |
| reg = card->irq_level; |
| |
| /* Register state inverse of led state */ |
| if (!state) |
| reg |= PIO2_LED; |
| |
| if (loopback) |
| reg |= PIO2_LOOP; |
| |
| retval = vme_master_write(card->window, ®, 1, PIO2_REGS_CTRL); |
| if (retval < 0) |
| return retval; |
| |
| card->led = state ? 1 : 0; |
| |
| return 0; |
| } |
| |
| static void pio2_int(int level, int vector, void *ptr) |
| { |
| int vec, i, channel, retval; |
| u8 reg; |
| struct pio2_card *card = ptr; |
| |
| vec = vector & ~PIO2_VME_VECTOR_MASK; |
| |
| switch (vec) { |
| case 0: |
| dev_warn(&card->vdev->dev, "Spurious Interrupt\n"); |
| break; |
| case 1: |
| case 2: |
| case 3: |
| case 4: |
| /* Channels 0 to 7 */ |
| retval = vme_master_read(card->window, ®, 1, |
| PIO2_REGS_INT_STAT[vec - 1]); |
| if (retval < 0) { |
| dev_err(&card->vdev->dev, |
| "Unable to read IRQ status register\n"); |
| return; |
| } |
| for (i = 0; i < 8; i++) { |
| channel = ((vec - 1) * 8) + i; |
| if (reg & PIO2_CHANNEL_BIT[channel]) |
| dev_info(&card->vdev->dev, |
| "Interrupt on I/O channel %d\n", |
| channel); |
| } |
| break; |
| case 5: |
| case 6: |
| case 7: |
| case 8: |
| case 9: |
| case 10: |
| /* Counters are dealt with by their own handler */ |
| dev_err(&card->vdev->dev, |
| "Counter interrupt\n"); |
| break; |
| } |
| } |
| |
| /* |
| * We return whether this has been successful - this is used in the probe to |
| * ensure we have a valid card. |
| */ |
| static int pio2_reset_card(struct pio2_card *card) |
| { |
| int retval = 0; |
| u8 data = 0; |
| |
| /* Clear main register*/ |
| retval = vme_master_write(card->window, &data, 1, PIO2_REGS_CTRL); |
| if (retval < 0) |
| return retval; |
| |
| /* Clear VME vector */ |
| retval = vme_master_write(card->window, &data, 1, PIO2_REGS_VME_VECTOR); |
| if (retval < 0) |
| return retval; |
| |
| /* Reset GPIO */ |
| retval = pio2_gpio_reset(card); |
| if (retval < 0) |
| return retval; |
| |
| /* Reset counters */ |
| retval = pio2_cntr_reset(card); |
| if (retval < 0) |
| return retval; |
| |
| return 0; |
| } |
| |
| static struct vme_driver pio2_driver = { |
| .name = driver_name, |
| .match = pio2_match, |
| .probe = pio2_probe, |
| .remove = pio2_remove, |
| }; |
| |
| static int __init pio2_init(void) |
| { |
| if (bus_num == 0) { |
| pr_err("No cards, skipping registration\n"); |
| return -ENODEV; |
| } |
| |
| if (bus_num > PIO2_CARDS_MAX) { |
| pr_err("Driver only able to handle %d PIO2 Cards\n", |
| PIO2_CARDS_MAX); |
| bus_num = PIO2_CARDS_MAX; |
| } |
| |
| /* Register the PIO2 driver */ |
| return vme_register_driver(&pio2_driver, bus_num); |
| } |
| |
| static int pio2_match(struct vme_dev *vdev) |
| { |
| if (vdev->num >= bus_num) { |
| dev_err(&vdev->dev, |
| "The enumeration of the VMEbus to which the board is connected must be specified\n"); |
| return 0; |
| } |
| |
| if (vdev->num >= base_num) { |
| dev_err(&vdev->dev, |
| "The VME address for the cards registers must be specified\n"); |
| return 0; |
| } |
| |
| if (vdev->num >= vector_num) { |
| dev_err(&vdev->dev, |
| "The IRQ vector used by the card must be specified\n"); |
| return 0; |
| } |
| |
| if (vdev->num >= level_num) { |
| dev_err(&vdev->dev, |
| "The IRQ level used by the card must be specified\n"); |
| return 0; |
| } |
| |
| if (vdev->num >= variant_num) { |
| dev_err(&vdev->dev, "The variant of the card must be specified\n"); |
| return 0; |
| } |
| |
| return 1; |
| } |
| |
| static int pio2_probe(struct vme_dev *vdev) |
| { |
| struct pio2_card *card; |
| int retval; |
| int i; |
| u8 reg; |
| int vec; |
| |
| card = kzalloc(sizeof(*card), GFP_KERNEL); |
| if (!card) { |
| retval = -ENOMEM; |
| goto err_struct; |
| } |
| |
| card->id = vdev->num; |
| card->bus = bus[card->id]; |
| card->base = base[card->id]; |
| card->irq_vector = vector[card->id]; |
| card->irq_level = level[card->id] & PIO2_VME_INT_MASK; |
| strncpy(card->variant, variant[card->id], PIO2_VARIANT_LENGTH); |
| card->vdev = vdev; |
| |
| for (i = 0; i < PIO2_VARIANT_LENGTH; i++) { |
| if (!isdigit(card->variant[i])) { |
| dev_err(&card->vdev->dev, "Variant invalid\n"); |
| retval = -EINVAL; |
| goto err_variant; |
| } |
| } |
| |
| /* |
| * Bottom 4 bits of VME interrupt vector used to determine source, |
| * provided vector should only use upper 4 bits. |
| */ |
| if (card->irq_vector & ~PIO2_VME_VECTOR_MASK) { |
| dev_err(&card->vdev->dev, |
| "Invalid VME IRQ Vector, vector must not use lower 4 bits\n"); |
| retval = -EINVAL; |
| goto err_vector; |
| } |
| |
| /* |
| * There is no way to determine the build variant or whether each bank |
| * is input, output or both at run time. The inputs are also inverted |
| * if configured as both. |
| * |
| * We pass in the board variant and use that to determine the |
| * configuration of the banks. |
| */ |
| for (i = 1; i < PIO2_VARIANT_LENGTH; i++) { |
| switch (card->variant[i]) { |
| case '0': |
| card->bank[i - 1].config = NOFIT; |
| break; |
| case '1': |
| case '2': |
| case '3': |
| case '4': |
| card->bank[i - 1].config = INPUT; |
| break; |
| case '5': |
| card->bank[i - 1].config = OUTPUT; |
| break; |
| case '6': |
| case '7': |
| case '8': |
| case '9': |
| card->bank[i - 1].config = BOTH; |
| break; |
| } |
| } |
| |
| /* Get a master window and position over regs */ |
| card->window = vme_master_request(vdev, VME_A24, VME_SCT, VME_D16); |
| if (!card->window) { |
| dev_err(&card->vdev->dev, |
| "Unable to assign VME master resource\n"); |
| retval = -EIO; |
| goto err_window; |
| } |
| |
| retval = vme_master_set(card->window, 1, card->base, 0x10000, VME_A24, |
| VME_SCT | VME_USER | VME_DATA, VME_D16); |
| if (retval) { |
| dev_err(&card->vdev->dev, |
| "Unable to configure VME master resource\n"); |
| goto err_set; |
| } |
| |
| /* |
| * There is also no obvious register which we can probe to determine |
| * whether the provided base is valid. If we can read the "ID Register" |
| * offset and the reset function doesn't error, assume we have a valid |
| * location. |
| */ |
| retval = vme_master_read(card->window, ®, 1, PIO2_REGS_ID); |
| if (retval < 0) { |
| dev_err(&card->vdev->dev, "Unable to read from device\n"); |
| goto err_read; |
| } |
| |
| dev_dbg(&card->vdev->dev, "ID Register:%x\n", reg); |
| |
| /* |
| * Ensure all the I/O is cleared. We can't read back the states, so |
| * this is the only method we have to ensure that the I/O is in a known |
| * state. |
| */ |
| retval = pio2_reset_card(card); |
| if (retval) { |
| dev_err(&card->vdev->dev, |
| "Failed to reset card, is location valid?\n"); |
| retval = -ENODEV; |
| goto err_reset; |
| } |
| |
| /* Configure VME Interrupts */ |
| reg = card->irq_level; |
| if (pio2_get_led(card)) |
| reg |= PIO2_LED; |
| if (loopback) |
| reg |= PIO2_LOOP; |
| retval = vme_master_write(card->window, ®, 1, PIO2_REGS_CTRL); |
| if (retval < 0) |
| return retval; |
| |
| /* Set VME vector */ |
| retval = vme_master_write(card->window, &card->irq_vector, 1, |
| PIO2_REGS_VME_VECTOR); |
| if (retval < 0) |
| return retval; |
| |
| /* Attach spurious interrupt handler. */ |
| vec = card->irq_vector | PIO2_VME_VECTOR_SPUR; |
| |
| retval = vme_irq_request(vdev, card->irq_level, vec, |
| &pio2_int, card); |
| if (retval < 0) { |
| dev_err(&card->vdev->dev, |
| "Unable to attach VME interrupt vector0x%x, level 0x%x\n", |
| vec, card->irq_level); |
| goto err_irq; |
| } |
| |
| /* Attach GPIO interrupt handlers. */ |
| for (i = 0; i < 4; i++) { |
| vec = card->irq_vector | PIO2_VECTOR_BANK[i]; |
| |
| retval = vme_irq_request(vdev, card->irq_level, vec, |
| &pio2_int, card); |
| if (retval < 0) { |
| dev_err(&card->vdev->dev, |
| "Unable to attach VME interrupt vector0x%x, level 0x%x\n", |
| vec, card->irq_level); |
| goto err_gpio_irq; |
| } |
| } |
| |
| /* Attach counter interrupt handlers. */ |
| for (i = 0; i < 6; i++) { |
| vec = card->irq_vector | PIO2_VECTOR_CNTR[i]; |
| |
| retval = vme_irq_request(vdev, card->irq_level, vec, |
| &pio2_int, card); |
| if (retval < 0) { |
| dev_err(&card->vdev->dev, |
| "Unable to attach VME interrupt vector0x%x, level 0x%x\n", |
| vec, card->irq_level); |
| goto err_cntr_irq; |
| } |
| } |
| |
| /* Register IO */ |
| retval = pio2_gpio_init(card); |
| if (retval < 0) { |
| dev_err(&card->vdev->dev, |
| "Unable to register with GPIO framework\n"); |
| goto err_gpio; |
| } |
| |
| /* Set LED - This also sets interrupt level */ |
| retval = pio2_set_led(card, 0); |
| if (retval < 0) { |
| dev_err(&card->vdev->dev, "Unable to set LED\n"); |
| goto err_led; |
| } |
| |
| dev_set_drvdata(&card->vdev->dev, card); |
| |
| dev_info(&card->vdev->dev, |
| "PIO2 (variant %s) configured at 0x%lx\n", card->variant, |
| card->base); |
| |
| return 0; |
| |
| err_led: |
| pio2_gpio_exit(card); |
| err_gpio: |
| i = 6; |
| err_cntr_irq: |
| while (i > 0) { |
| i--; |
| vec = card->irq_vector | PIO2_VECTOR_CNTR[i]; |
| vme_irq_free(vdev, card->irq_level, vec); |
| } |
| |
| i = 4; |
| err_gpio_irq: |
| while (i > 0) { |
| i--; |
| vec = card->irq_vector | PIO2_VECTOR_BANK[i]; |
| vme_irq_free(vdev, card->irq_level, vec); |
| } |
| |
| vec = (card->irq_vector & PIO2_VME_VECTOR_MASK) | PIO2_VME_VECTOR_SPUR; |
| vme_irq_free(vdev, card->irq_level, vec); |
| err_irq: |
| pio2_reset_card(card); |
| err_reset: |
| err_read: |
| vme_master_set(card->window, 0, 0, 0, VME_A16, 0, VME_D16); |
| err_set: |
| vme_master_free(card->window); |
| err_window: |
| err_vector: |
| err_variant: |
| kfree(card); |
| err_struct: |
| return retval; |
| } |
| |
| static int pio2_remove(struct vme_dev *vdev) |
| { |
| int vec; |
| int i; |
| |
| struct pio2_card *card = dev_get_drvdata(&vdev->dev); |
| |
| pio2_gpio_exit(card); |
| |
| for (i = 0; i < 6; i++) { |
| vec = card->irq_vector | PIO2_VECTOR_CNTR[i]; |
| vme_irq_free(vdev, card->irq_level, vec); |
| } |
| |
| for (i = 0; i < 4; i++) { |
| vec = card->irq_vector | PIO2_VECTOR_BANK[i]; |
| vme_irq_free(vdev, card->irq_level, vec); |
| } |
| |
| vec = (card->irq_vector & PIO2_VME_VECTOR_MASK) | PIO2_VME_VECTOR_SPUR; |
| vme_irq_free(vdev, card->irq_level, vec); |
| |
| pio2_reset_card(card); |
| |
| vme_master_set(card->window, 0, 0, 0, VME_A16, 0, VME_D16); |
| |
| vme_master_free(card->window); |
| |
| kfree(card); |
| |
| return 0; |
| } |
| |
| static void __exit pio2_exit(void) |
| { |
| vme_unregister_driver(&pio2_driver); |
| } |
| |
| /* These are required for each board */ |
| MODULE_PARM_DESC(bus, "Enumeration of VMEbus to which the board is connected"); |
| module_param_array(bus, int, &bus_num, S_IRUGO); |
| |
| MODULE_PARM_DESC(base, "Base VME address for PIO2 Registers"); |
| module_param_array(base, long, &base_num, S_IRUGO); |
| |
| MODULE_PARM_DESC(vector, "VME IRQ Vector (Lower 4 bits masked)"); |
| module_param_array(vector, int, &vector_num, S_IRUGO); |
| |
| MODULE_PARM_DESC(level, "VME IRQ Level"); |
| module_param_array(level, int, &level_num, S_IRUGO); |
| |
| MODULE_PARM_DESC(variant, "Last 4 characters of PIO2 board variant"); |
| module_param_array(variant, charp, &variant_num, S_IRUGO); |
| |
| /* This is for debugging */ |
| MODULE_PARM_DESC(loopback, "Enable loopback mode on all cards"); |
| module_param(loopback, bool, S_IRUGO); |
| |
| MODULE_DESCRIPTION("GE PIO2 6U VME I/O Driver"); |
| MODULE_AUTHOR("Martyn Welch <martyn.welch@ge.com"); |
| MODULE_LICENSE("GPL"); |
| |
| module_init(pio2_init); |
| module_exit(pio2_exit); |
| |