From 57f0f512b273f60d52568b8c6b77e17f5636edc0 Mon Sep 17 00:00:00 2001 From: AndrĂ© Fabian Silva Delgado Date: Wed, 5 Aug 2015 17:04:01 -0300 Subject: Initial import --- drivers/staging/vme/Makefile | 1 + drivers/staging/vme/devices/Kconfig | 25 + drivers/staging/vme/devices/Makefile | 8 + drivers/staging/vme/devices/vme_pio2.h | 249 +++++++ drivers/staging/vme/devices/vme_pio2_cntr.c | 71 ++ drivers/staging/vme/devices/vme_pio2_core.c | 511 +++++++++++++++ drivers/staging/vme/devices/vme_pio2_gpio.c | 226 +++++++ drivers/staging/vme/devices/vme_user.c | 962 ++++++++++++++++++++++++++++ drivers/staging/vme/devices/vme_user.h | 58 ++ 9 files changed, 2111 insertions(+) create mode 100644 drivers/staging/vme/Makefile create mode 100644 drivers/staging/vme/devices/Kconfig create mode 100644 drivers/staging/vme/devices/Makefile create mode 100644 drivers/staging/vme/devices/vme_pio2.h create mode 100644 drivers/staging/vme/devices/vme_pio2_cntr.c create mode 100644 drivers/staging/vme/devices/vme_pio2_core.c create mode 100644 drivers/staging/vme/devices/vme_pio2_gpio.c create mode 100644 drivers/staging/vme/devices/vme_user.c create mode 100644 drivers/staging/vme/devices/vme_user.h (limited to 'drivers/staging/vme') diff --git a/drivers/staging/vme/Makefile b/drivers/staging/vme/Makefile new file mode 100644 index 000000000..accdb72e3 --- /dev/null +++ b/drivers/staging/vme/Makefile @@ -0,0 +1 @@ +obj-y += devices/ diff --git a/drivers/staging/vme/devices/Kconfig b/drivers/staging/vme/devices/Kconfig new file mode 100644 index 000000000..1d2ff0cc4 --- /dev/null +++ b/drivers/staging/vme/devices/Kconfig @@ -0,0 +1,25 @@ +comment "VME Device Drivers" + +config VME_USER + tristate "VME user space access driver" + depends on STAGING + help + If you say Y here you want to be able to access a limited number of + VME windows in a manner at least semi-compatible with the interface + provided with the original driver at . + + To compile this driver as a module, choose M here. The module will + be called vme_user. If unsure, say N. + +config VME_PIO2 + tristate "GE PIO2 VME" + depends on STAGING && GPIOLIB + help + Say Y here to include support for the GE PIO2. The PIO2 is a 6U VME + slave card, implementing 32 solid-state relay switched IO lines, in + 4 groups of 8. Each bank of IO lines is built to function as input, + output or both depending on the variant of the card. + + To compile this driver as a module, choose M here. The module will + be called vme_pio2. If unsure, say N. + diff --git a/drivers/staging/vme/devices/Makefile b/drivers/staging/vme/devices/Makefile new file mode 100644 index 000000000..172512cb5 --- /dev/null +++ b/drivers/staging/vme/devices/Makefile @@ -0,0 +1,8 @@ +# +# Makefile for the VME device drivers. +# + +obj-$(CONFIG_VME_USER) += vme_user.o + +vme_pio2-objs := vme_pio2_cntr.o vme_pio2_gpio.o vme_pio2_core.o +obj-$(CONFIG_VME_PIO2) += vme_pio2.o diff --git a/drivers/staging/vme/devices/vme_pio2.h b/drivers/staging/vme/devices/vme_pio2.h new file mode 100644 index 000000000..d5d94c43c --- /dev/null +++ b/drivers/staging/vme/devices/vme_pio2.h @@ -0,0 +1,249 @@ +#ifndef _VME_PIO2_H_ +#define _VME_PIO2_H_ + +#define PIO2_CARDS_MAX 32 + +#define PIO2_VARIANT_LENGTH 5 + +#define PIO2_NUM_CHANNELS 32 +#define PIO2_NUM_IRQS 11 +#define PIO2_NUM_CNTRS 6 + +#define PIO2_REGS_SIZE 0x40 + +#define PIO2_REGS_DATA0 0x0 +#define PIO2_REGS_DATA1 0x1 +#define PIO2_REGS_DATA2 0x2 +#define PIO2_REGS_DATA3 0x3 + +static const int PIO2_REGS_DATA[4] = { PIO2_REGS_DATA0, PIO2_REGS_DATA1, + PIO2_REGS_DATA2, PIO2_REGS_DATA3 }; + +#define PIO2_REGS_INT_STAT0 0x8 +#define PIO2_REGS_INT_STAT1 0x9 +#define PIO2_REGS_INT_STAT2 0xa +#define PIO2_REGS_INT_STAT3 0xb + +static const int PIO2_REGS_INT_STAT[4] = { PIO2_REGS_INT_STAT0, + PIO2_REGS_INT_STAT1, + PIO2_REGS_INT_STAT2, + PIO2_REGS_INT_STAT3 }; + +#define PIO2_REGS_INT_STAT_CNTR 0xc +#define PIO2_REGS_INT_MASK0 0x10 +#define PIO2_REGS_INT_MASK1 0x11 +#define PIO2_REGS_INT_MASK2 0x12 +#define PIO2_REGS_INT_MASK3 0x13 +#define PIO2_REGS_INT_MASK4 0x14 +#define PIO2_REGS_INT_MASK5 0x15 +#define PIO2_REGS_INT_MASK6 0x16 +#define PIO2_REGS_INT_MASK7 0x17 + +static const int PIO2_REGS_INT_MASK[8] = { PIO2_REGS_INT_MASK0, + PIO2_REGS_INT_MASK1, + PIO2_REGS_INT_MASK2, + PIO2_REGS_INT_MASK3, + PIO2_REGS_INT_MASK4, + PIO2_REGS_INT_MASK5, + PIO2_REGS_INT_MASK6, + PIO2_REGS_INT_MASK7 }; + + + +#define PIO2_REGS_CTRL 0x18 +#define PIO2_REGS_VME_VECTOR 0x19 +#define PIO2_REGS_CNTR0 0x20 +#define PIO2_REGS_CNTR1 0x22 +#define PIO2_REGS_CNTR2 0x24 +#define PIO2_REGS_CTRL_WRD0 0x26 +#define PIO2_REGS_CNTR3 0x28 +#define PIO2_REGS_CNTR4 0x2a +#define PIO2_REGS_CNTR5 0x2c +#define PIO2_REGS_CTRL_WRD1 0x2e + +#define PIO2_REGS_ID 0x30 + + +/* PIO2_REGS_DATAx (0x0 - 0x3) */ + +static const int PIO2_CHANNEL_BANK[32] = { 0, 0, 0, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 1, 1, 1, 1, + 2, 2, 2, 2, 2, 2, 2, 2, + 3, 3, 3, 3, 3, 3, 3, 3 }; + +#define PIO2_CHANNEL0_BIT (1 << 0) +#define PIO2_CHANNEL1_BIT (1 << 1) +#define PIO2_CHANNEL2_BIT (1 << 2) +#define PIO2_CHANNEL3_BIT (1 << 3) +#define PIO2_CHANNEL4_BIT (1 << 4) +#define PIO2_CHANNEL5_BIT (1 << 5) +#define PIO2_CHANNEL6_BIT (1 << 6) +#define PIO2_CHANNEL7_BIT (1 << 7) +#define PIO2_CHANNEL8_BIT (1 << 0) +#define PIO2_CHANNEL9_BIT (1 << 1) +#define PIO2_CHANNEL10_BIT (1 << 2) +#define PIO2_CHANNEL11_BIT (1 << 3) +#define PIO2_CHANNEL12_BIT (1 << 4) +#define PIO2_CHANNEL13_BIT (1 << 5) +#define PIO2_CHANNEL14_BIT (1 << 6) +#define PIO2_CHANNEL15_BIT (1 << 7) +#define PIO2_CHANNEL16_BIT (1 << 0) +#define PIO2_CHANNEL17_BIT (1 << 1) +#define PIO2_CHANNEL18_BIT (1 << 2) +#define PIO2_CHANNEL19_BIT (1 << 3) +#define PIO2_CHANNEL20_BIT (1 << 4) +#define PIO2_CHANNEL21_BIT (1 << 5) +#define PIO2_CHANNEL22_BIT (1 << 6) +#define PIO2_CHANNEL23_BIT (1 << 7) +#define PIO2_CHANNEL24_BIT (1 << 0) +#define PIO2_CHANNEL25_BIT (1 << 1) +#define PIO2_CHANNEL26_BIT (1 << 2) +#define PIO2_CHANNEL27_BIT (1 << 3) +#define PIO2_CHANNEL28_BIT (1 << 4) +#define PIO2_CHANNEL29_BIT (1 << 5) +#define PIO2_CHANNEL30_BIT (1 << 6) +#define PIO2_CHANNEL31_BIT (1 << 7) + +static const int PIO2_CHANNEL_BIT[32] = { PIO2_CHANNEL0_BIT, PIO2_CHANNEL1_BIT, + PIO2_CHANNEL2_BIT, PIO2_CHANNEL3_BIT, + PIO2_CHANNEL4_BIT, PIO2_CHANNEL5_BIT, + PIO2_CHANNEL6_BIT, PIO2_CHANNEL7_BIT, + PIO2_CHANNEL8_BIT, PIO2_CHANNEL9_BIT, + PIO2_CHANNEL10_BIT, PIO2_CHANNEL11_BIT, + PIO2_CHANNEL12_BIT, PIO2_CHANNEL13_BIT, + PIO2_CHANNEL14_BIT, PIO2_CHANNEL15_BIT, + PIO2_CHANNEL16_BIT, PIO2_CHANNEL17_BIT, + PIO2_CHANNEL18_BIT, PIO2_CHANNEL19_BIT, + PIO2_CHANNEL20_BIT, PIO2_CHANNEL21_BIT, + PIO2_CHANNEL22_BIT, PIO2_CHANNEL23_BIT, + PIO2_CHANNEL24_BIT, PIO2_CHANNEL25_BIT, + PIO2_CHANNEL26_BIT, PIO2_CHANNEL27_BIT, + PIO2_CHANNEL28_BIT, PIO2_CHANNEL29_BIT, + PIO2_CHANNEL30_BIT, PIO2_CHANNEL31_BIT + }; + +/* PIO2_REGS_INT_STAT_CNTR (0xc) */ +#define PIO2_COUNTER0 (1 << 0) +#define PIO2_COUNTER1 (1 << 1) +#define PIO2_COUNTER2 (1 << 2) +#define PIO2_COUNTER3 (1 << 3) +#define PIO2_COUNTER4 (1 << 4) +#define PIO2_COUNTER5 (1 << 5) + +static const int PIO2_COUNTER[6] = { PIO2_COUNTER0, PIO2_COUNTER1, + PIO2_COUNTER2, PIO2_COUNTER3, + PIO2_COUNTER4, PIO2_COUNTER5 }; + +/* PIO2_REGS_CTRL (0x18) */ +#define PIO2_VME_INT_MASK 0x7 +#define PIO2_LED (1 << 6) +#define PIO2_LOOP (1 << 7) + +/* PIO2_REGS_VME_VECTOR (0x19) */ +#define PIO2_VME_VECTOR_SPUR 0x0 +#define PIO2_VME_VECTOR_BANK0 0x1 +#define PIO2_VME_VECTOR_BANK1 0x2 +#define PIO2_VME_VECTOR_BANK2 0x3 +#define PIO2_VME_VECTOR_BANK3 0x4 +#define PIO2_VME_VECTOR_CNTR0 0x5 +#define PIO2_VME_VECTOR_CNTR1 0x6 +#define PIO2_VME_VECTOR_CNTR2 0x7 +#define PIO2_VME_VECTOR_CNTR3 0x8 +#define PIO2_VME_VECTOR_CNTR4 0x9 +#define PIO2_VME_VECTOR_CNTR5 0xa + +#define PIO2_VME_VECTOR_MASK 0xf0 + +static const int PIO2_VECTOR_BANK[4] = { PIO2_VME_VECTOR_BANK0, + PIO2_VME_VECTOR_BANK1, + PIO2_VME_VECTOR_BANK2, + PIO2_VME_VECTOR_BANK3 }; + +static const int PIO2_VECTOR_CNTR[6] = { PIO2_VME_VECTOR_CNTR0, + PIO2_VME_VECTOR_CNTR1, + PIO2_VME_VECTOR_CNTR2, + PIO2_VME_VECTOR_CNTR3, + PIO2_VME_VECTOR_CNTR4, + PIO2_VME_VECTOR_CNTR5 }; + +/* PIO2_REGS_CNTRx (0x20 - 0x24 & 0x28 - 0x2c) */ + +static const int PIO2_CNTR_DATA[6] = { PIO2_REGS_CNTR0, PIO2_REGS_CNTR1, + PIO2_REGS_CNTR2, PIO2_REGS_CNTR3, + PIO2_REGS_CNTR4, PIO2_REGS_CNTR5 }; + +/* PIO2_REGS_CTRL_WRDx (0x26 & 0x2e) */ + +static const int PIO2_CNTR_CTRL[6] = { PIO2_REGS_CTRL_WRD0, + PIO2_REGS_CTRL_WRD0, + PIO2_REGS_CTRL_WRD0, + PIO2_REGS_CTRL_WRD1, + PIO2_REGS_CTRL_WRD1, + PIO2_REGS_CTRL_WRD1 }; + +#define PIO2_CNTR_SC_DEV0 0 +#define PIO2_CNTR_SC_DEV1 (1 << 6) +#define PIO2_CNTR_SC_DEV2 (2 << 6) +#define PIO2_CNTR_SC_RDBACK (3 << 6) + +static const int PIO2_CNTR_SC_DEV[6] = { PIO2_CNTR_SC_DEV0, PIO2_CNTR_SC_DEV1, + PIO2_CNTR_SC_DEV2, PIO2_CNTR_SC_DEV0, + PIO2_CNTR_SC_DEV1, PIO2_CNTR_SC_DEV2 }; + +#define PIO2_CNTR_RW_LATCH 0 +#define PIO2_CNTR_RW_LSB (1 << 4) +#define PIO2_CNTR_RW_MSB (2 << 4) +#define PIO2_CNTR_RW_BOTH (3 << 4) + +#define PIO2_CNTR_MODE0 0 +#define PIO2_CNTR_MODE1 (1 << 1) +#define PIO2_CNTR_MODE2 (2 << 1) +#define PIO2_CNTR_MODE3 (3 << 1) +#define PIO2_CNTR_MODE4 (4 << 1) +#define PIO2_CNTR_MODE5 (5 << 1) + +#define PIO2_CNTR_BCD 1 + + + +enum pio2_bank_config { NOFIT, INPUT, OUTPUT, BOTH }; +enum pio2_int_config { NONE = 0, LOW2HIGH = 1, HIGH2LOW = 2, EITHER = 4 }; + +/* Bank configuration structure */ +struct pio2_io_bank { + enum pio2_bank_config config; + u8 value; + enum pio2_int_config irq[8]; +}; + +/* Counter configuration structure */ +struct pio2_cntr { + int mode; + int count; +}; + +struct pio2_card { + int id; + int bus; + long base; + int irq_vector; + int irq_level; + char variant[6]; + int led; + + struct vme_dev *vdev; + struct vme_resource *window; + + struct gpio_chip gc; + struct pio2_io_bank bank[4]; + + struct pio2_cntr cntr[6]; +}; + +int pio2_cntr_reset(struct pio2_card *); + +int pio2_gpio_reset(struct pio2_card *); +int pio2_gpio_init(struct pio2_card *); +void pio2_gpio_exit(struct pio2_card *); + +#endif /* _VME_PIO2_H_ */ diff --git a/drivers/staging/vme/devices/vme_pio2_cntr.c b/drivers/staging/vme/devices/vme_pio2_cntr.c new file mode 100644 index 000000000..6335471fa --- /dev/null +++ b/drivers/staging/vme/devices/vme_pio2_cntr.c @@ -0,0 +1,71 @@ +/* + * GE PIO2 Counter Driver + * + * Author: Martyn Welch + * 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. + * + * The PIO-2 has 6 counters, currently this code just disables the interrupts + * and leaves them alone. + * + */ + +#include +#include +#include +#include + +#include "vme_pio2.h" + +static int pio2_cntr_irq_set(struct pio2_card *card, int id) +{ + int retval; + u8 data; + + data = PIO2_CNTR_SC_DEV[id] | PIO2_CNTR_RW_BOTH | card->cntr[id].mode; + retval = vme_master_write(card->window, &data, 1, PIO2_CNTR_CTRL[id]); + if (retval < 0) + return retval; + + data = card->cntr[id].count & 0xFF; + retval = vme_master_write(card->window, &data, 1, PIO2_CNTR_DATA[id]); + if (retval < 0) + return retval; + + data = (card->cntr[id].count >> 8) & 0xFF; + retval = vme_master_write(card->window, &data, 1, PIO2_CNTR_DATA[id]); + if (retval < 0) + return retval; + + return 0; +} + +int pio2_cntr_reset(struct pio2_card *card) +{ + int i, retval = 0; + u8 reg; + + /* Clear down all timers */ + for (i = 0; i < 6; i++) { + card->cntr[i].mode = PIO2_CNTR_MODE5; + card->cntr[i].count = 0; + retval = pio2_cntr_irq_set(card, i); + if (retval < 0) + return retval; + } + + /* Ensure all counter interrupts are cleared */ + do { + retval = vme_master_read(card->window, ®, 1, + PIO2_REGS_INT_STAT_CNTR); + if (retval < 0) + return retval; + } while (reg != 0); + + return retval; +} + diff --git a/drivers/staging/vme/devices/vme_pio2_core.c b/drivers/staging/vme/devices/vme_pio2_core.c new file mode 100644 index 000000000..eabbcc710 --- /dev/null +++ b/drivers/staging/vme/devices/vme_pio2_core.c @@ -0,0 +1,511 @@ +/* + * GE PIO2 6U VME I/O Driver + * + * Author: Martyn Welch + * 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 +#include +#include +#include +#include +#include +#include +#include +#include + +#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(struct pio2_card), GFP_KERNEL); + if (card == NULL) { + 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]) == 0) { + 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 == NULL) { + 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, (void *)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, (void *)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, (void *)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 + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "vme_pio2.h" + +static const char driver_name[] = "pio2_gpio"; + +static struct pio2_card *gpio_to_pio2_card(struct gpio_chip *chip) +{ + return container_of(chip, struct pio2_card, gc); +} + +static int pio2_gpio_get(struct gpio_chip *chip, unsigned int offset) +{ + u8 reg; + int retval; + struct pio2_card *card = gpio_to_pio2_card(chip); + + if ((card->bank[PIO2_CHANNEL_BANK[offset]].config == OUTPUT) | + (card->bank[PIO2_CHANNEL_BANK[offset]].config == NOFIT)) { + + dev_err(&card->vdev->dev, "Channel not available as input\n"); + return 0; + } + + retval = vme_master_read(card->window, ®, 1, + PIO2_REGS_DATA[PIO2_CHANNEL_BANK[offset]]); + if (retval < 0) { + dev_err(&card->vdev->dev, "Unable to read from GPIO\n"); + return 0; + } + + /* + * Remember, input on channels configured as both input and output + * are inverted! + */ + if (reg & PIO2_CHANNEL_BIT[offset]) { + if (card->bank[PIO2_CHANNEL_BANK[offset]].config != BOTH) + return 0; + + return 1; + } + + if (card->bank[PIO2_CHANNEL_BANK[offset]].config != BOTH) + return 1; + + return 0; +} + +static void pio2_gpio_set(struct gpio_chip *chip, unsigned int offset, + int value) +{ + u8 reg; + int retval; + struct pio2_card *card = gpio_to_pio2_card(chip); + + if ((card->bank[PIO2_CHANNEL_BANK[offset]].config == INPUT) | + (card->bank[PIO2_CHANNEL_BANK[offset]].config == NOFIT)) { + + dev_err(&card->vdev->dev, "Channel not available as output\n"); + return; + } + + if (value) + reg = card->bank[PIO2_CHANNEL_BANK[offset]].value | + PIO2_CHANNEL_BIT[offset]; + else + reg = card->bank[PIO2_CHANNEL_BANK[offset]].value & + ~PIO2_CHANNEL_BIT[offset]; + + retval = vme_master_write(card->window, ®, 1, + PIO2_REGS_DATA[PIO2_CHANNEL_BANK[offset]]); + if (retval < 0) { + dev_err(&card->vdev->dev, "Unable to write to GPIO\n"); + return; + } + + card->bank[PIO2_CHANNEL_BANK[offset]].value = reg; +} + +/* Directionality configured at board build - send appropriate response */ +static int pio2_gpio_dir_in(struct gpio_chip *chip, unsigned offset) +{ + int data; + struct pio2_card *card = gpio_to_pio2_card(chip); + + if ((card->bank[PIO2_CHANNEL_BANK[offset]].config == OUTPUT) | + (card->bank[PIO2_CHANNEL_BANK[offset]].config == NOFIT)) { + dev_err(&card->vdev->dev, + "Channel directionality not configurable at runtime\n"); + + data = -EINVAL; + } else { + data = 0; + } + + return data; +} + +/* Directionality configured at board build - send appropriate response */ +static int pio2_gpio_dir_out(struct gpio_chip *chip, unsigned offset, int value) +{ + int data; + struct pio2_card *card = gpio_to_pio2_card(chip); + + if ((card->bank[PIO2_CHANNEL_BANK[offset]].config == INPUT) | + (card->bank[PIO2_CHANNEL_BANK[offset]].config == NOFIT)) { + dev_err(&card->vdev->dev, + "Channel directionality not configurable at runtime\n"); + + data = -EINVAL; + } else { + data = 0; + } + + return data; +} + +/* + * We return whether this has been successful - this is used in the probe to + * ensure we have a valid card. + */ +int pio2_gpio_reset(struct pio2_card *card) +{ + int retval = 0; + int i, j; + + u8 data = 0; + + /* Zero output registers */ + for (i = 0; i < 4; i++) { + retval = vme_master_write(card->window, &data, 1, + PIO2_REGS_DATA[i]); + if (retval < 0) + return retval; + card->bank[i].value = 0; + } + + /* Set input interrupt masks */ + for (i = 0; i < 4; i++) { + retval = vme_master_write(card->window, &data, 1, + PIO2_REGS_INT_MASK[i * 2]); + if (retval < 0) + return retval; + + retval = vme_master_write(card->window, &data, 1, + PIO2_REGS_INT_MASK[(i * 2) + 1]); + if (retval < 0) + return retval; + + for (j = 0; j < 8; j++) + card->bank[i].irq[j] = NONE; + } + + /* Ensure all I/O interrupts are cleared */ + for (i = 0; i < 4; i++) { + do { + retval = vme_master_read(card->window, &data, 1, + PIO2_REGS_INT_STAT[i]); + if (retval < 0) + return retval; + } while (data != 0); + } + + return 0; +} + +int pio2_gpio_init(struct pio2_card *card) +{ + int retval = 0; + char *label; + + label = kasprintf(GFP_KERNEL, + "%s@%s", driver_name, dev_name(&card->vdev->dev)); + if (label == NULL) + return -ENOMEM; + + card->gc.label = label; + + card->gc.ngpio = PIO2_NUM_CHANNELS; + /* Dynamic allocation of base */ + card->gc.base = -1; + /* Setup pointers to chip functions */ + card->gc.direction_input = pio2_gpio_dir_in; + card->gc.direction_output = pio2_gpio_dir_out; + card->gc.get = pio2_gpio_get; + card->gc.set = pio2_gpio_set; + + /* This function adds a memory mapped GPIO chip */ + retval = gpiochip_add(&(card->gc)); + if (retval) { + dev_err(&card->vdev->dev, "Unable to register GPIO\n"); + kfree(card->gc.label); + } + + return retval; +}; + +void pio2_gpio_exit(struct pio2_card *card) +{ + const char *label = card->gc.label; + + gpiochip_remove(&(card->gc)); + kfree(label); +} + diff --git a/drivers/staging/vme/devices/vme_user.c b/drivers/staging/vme/devices/vme_user.c new file mode 100644 index 000000000..19ba749bb --- /dev/null +++ b/drivers/staging/vme/devices/vme_user.c @@ -0,0 +1,962 @@ +/* + * VMEbus User access driver + * + * Author: Martyn Welch + * Copyright 2008 GE Intelligent Platforms Embedded Systems, Inc. + * + * Based on work by: + * Tom Armistead and Ajit Prem + * Copyright 2004 Motorola 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "vme_user.h" + +static const char driver_name[] = "vme_user"; + +static int bus[VME_USER_BUS_MAX]; +static unsigned int bus_num; + +/* Currently Documentation/devices.txt defines the following for VME: + * + * 221 char VME bus + * 0 = /dev/bus/vme/m0 First master image + * 1 = /dev/bus/vme/m1 Second master image + * 2 = /dev/bus/vme/m2 Third master image + * 3 = /dev/bus/vme/m3 Fourth master image + * 4 = /dev/bus/vme/s0 First slave image + * 5 = /dev/bus/vme/s1 Second slave image + * 6 = /dev/bus/vme/s2 Third slave image + * 7 = /dev/bus/vme/s3 Fourth slave image + * 8 = /dev/bus/vme/ctl Control + * + * It is expected that all VME bus drivers will use the + * same interface. For interface documentation see + * http://www.vmelinux.org/. + * + * However the VME driver at http://www.vmelinux.org/ is rather old and doesn't + * even support the tsi148 chipset (which has 8 master and 8 slave windows). + * We'll run with this for now as far as possible, however it probably makes + * sense to get rid of the old mappings and just do everything dynamically. + * + * So for now, we'll restrict the driver to providing 4 masters and 4 slaves as + * defined above and try to support at least some of the interface from + * http://www.vmelinux.org/ as an alternative the driver can be written + * providing a saner interface later. + * + * The vmelinux.org driver never supported slave images, the devices reserved + * for slaves were repurposed to support all 8 master images on the UniverseII! + * We shall support 4 masters and 4 slaves with this driver. + */ +#define VME_MAJOR 221 /* VME Major Device Number */ +#define VME_DEVS 9 /* Number of dev entries */ + +#define MASTER_MINOR 0 +#define MASTER_MAX 3 +#define SLAVE_MINOR 4 +#define SLAVE_MAX 7 +#define CONTROL_MINOR 8 + +#define PCI_BUF_SIZE 0x20000 /* Size of one slave image buffer */ + +/* + * Structure to handle image related parameters. + */ +struct image_desc { + void *kern_buf; /* Buffer address in kernel space */ + dma_addr_t pci_buf; /* Buffer address in PCI address space */ + unsigned long long size_buf; /* Buffer size */ + struct mutex mutex; /* Mutex for locking image */ + struct device *device; /* Sysfs device */ + struct vme_resource *resource; /* VME resource */ + int users; /* Number of current users */ + int mmap_count; /* Number of current mmap's */ +}; +static struct image_desc image[VME_DEVS]; + +struct driver_stats { + unsigned long reads; + unsigned long writes; + unsigned long ioctls; + unsigned long irqs; + unsigned long berrs; + unsigned long dmaerrors; + unsigned long timeouts; + unsigned long external; +}; +static struct driver_stats statistics; + +static struct cdev *vme_user_cdev; /* Character device */ +static struct class *vme_user_sysfs_class; /* Sysfs class */ +static struct vme_dev *vme_user_bridge; /* Pointer to user device */ + + +static const int type[VME_DEVS] = { MASTER_MINOR, MASTER_MINOR, + MASTER_MINOR, MASTER_MINOR, + SLAVE_MINOR, SLAVE_MINOR, + SLAVE_MINOR, SLAVE_MINOR, + CONTROL_MINOR + }; + + +static int vme_user_open(struct inode *, struct file *); +static int vme_user_release(struct inode *, struct file *); +static ssize_t vme_user_read(struct file *, char __user *, size_t, loff_t *); +static ssize_t vme_user_write(struct file *, const char __user *, size_t, + loff_t *); +static loff_t vme_user_llseek(struct file *, loff_t, int); +static long vme_user_unlocked_ioctl(struct file *, unsigned int, unsigned long); +static int vme_user_mmap(struct file *file, struct vm_area_struct *vma); + +static void vme_user_vm_open(struct vm_area_struct *vma); +static void vme_user_vm_close(struct vm_area_struct *vma); + +static int vme_user_match(struct vme_dev *); +static int vme_user_probe(struct vme_dev *); +static int vme_user_remove(struct vme_dev *); + +static const struct file_operations vme_user_fops = { + .open = vme_user_open, + .release = vme_user_release, + .read = vme_user_read, + .write = vme_user_write, + .llseek = vme_user_llseek, + .unlocked_ioctl = vme_user_unlocked_ioctl, + .compat_ioctl = vme_user_unlocked_ioctl, + .mmap = vme_user_mmap, +}; + +struct vme_user_vma_priv { + unsigned int minor; + atomic_t refcnt; +}; + +static const struct vm_operations_struct vme_user_vm_ops = { + .open = vme_user_vm_open, + .close = vme_user_vm_close, +}; + + +/* + * Reset all the statistic counters + */ +static void reset_counters(void) +{ + statistics.reads = 0; + statistics.writes = 0; + statistics.ioctls = 0; + statistics.irqs = 0; + statistics.berrs = 0; + statistics.dmaerrors = 0; + statistics.timeouts = 0; +} + +static int vme_user_open(struct inode *inode, struct file *file) +{ + int err; + unsigned int minor = MINOR(inode->i_rdev); + + mutex_lock(&image[minor].mutex); + /* Allow device to be opened if a resource is needed and allocated. */ + if (minor < CONTROL_MINOR && image[minor].resource == NULL) { + pr_err("No resources allocated for device\n"); + err = -EINVAL; + goto err_res; + } + + /* Increment user count */ + image[minor].users++; + + mutex_unlock(&image[minor].mutex); + + return 0; + +err_res: + mutex_unlock(&image[minor].mutex); + + return err; +} + +static int vme_user_release(struct inode *inode, struct file *file) +{ + unsigned int minor = MINOR(inode->i_rdev); + + mutex_lock(&image[minor].mutex); + + /* Decrement user count */ + image[minor].users--; + + mutex_unlock(&image[minor].mutex); + + return 0; +} + +/* + * We are going ot alloc a page during init per window for small transfers. + * Small transfers will go VME -> buffer -> user space. Larger (more than a + * page) transfers will lock the user space buffer into memory and then + * transfer the data directly into the user space buffers. + */ +static ssize_t resource_to_user(int minor, char __user *buf, size_t count, + loff_t *ppos) +{ + ssize_t retval; + ssize_t copied = 0; + + if (count <= image[minor].size_buf) { + /* We copy to kernel buffer */ + copied = vme_master_read(image[minor].resource, + image[minor].kern_buf, count, *ppos); + if (copied < 0) + return (int)copied; + + retval = __copy_to_user(buf, image[minor].kern_buf, + (unsigned long)copied); + if (retval != 0) { + copied = (copied - retval); + pr_info("User copy failed\n"); + return -EINVAL; + } + + } else { + /* XXX Need to write this */ + pr_info("Currently don't support large transfers\n"); + /* Map in pages from userspace */ + + /* Call vme_master_read to do the transfer */ + return -EINVAL; + } + + return copied; +} + +/* + * We are going to alloc a page during init per window for small transfers. + * Small transfers will go user space -> buffer -> VME. Larger (more than a + * page) transfers will lock the user space buffer into memory and then + * transfer the data directly from the user space buffers out to VME. + */ +static ssize_t resource_from_user(unsigned int minor, const char __user *buf, + size_t count, loff_t *ppos) +{ + ssize_t retval; + ssize_t copied = 0; + + if (count <= image[minor].size_buf) { + retval = __copy_from_user(image[minor].kern_buf, buf, + (unsigned long)count); + if (retval != 0) + copied = (copied - retval); + else + copied = count; + + copied = vme_master_write(image[minor].resource, + image[minor].kern_buf, copied, *ppos); + } else { + /* XXX Need to write this */ + pr_info("Currently don't support large transfers\n"); + /* Map in pages from userspace */ + + /* Call vme_master_write to do the transfer */ + return -EINVAL; + } + + return copied; +} + +static ssize_t buffer_to_user(unsigned int minor, char __user *buf, + size_t count, loff_t *ppos) +{ + void *image_ptr; + ssize_t retval; + + image_ptr = image[minor].kern_buf + *ppos; + + retval = __copy_to_user(buf, image_ptr, (unsigned long)count); + if (retval != 0) { + retval = (count - retval); + pr_warn("Partial copy to userspace\n"); + } else + retval = count; + + /* Return number of bytes successfully read */ + return retval; +} + +static ssize_t buffer_from_user(unsigned int minor, const char __user *buf, + size_t count, loff_t *ppos) +{ + void *image_ptr; + size_t retval; + + image_ptr = image[minor].kern_buf + *ppos; + + retval = __copy_from_user(image_ptr, buf, (unsigned long)count); + if (retval != 0) { + retval = (count - retval); + pr_warn("Partial copy to userspace\n"); + } else + retval = count; + + /* Return number of bytes successfully read */ + return retval; +} + +static ssize_t vme_user_read(struct file *file, char __user *buf, size_t count, + loff_t *ppos) +{ + unsigned int minor = MINOR(file_inode(file)->i_rdev); + ssize_t retval; + size_t image_size; + size_t okcount; + + if (minor == CONTROL_MINOR) + return 0; + + mutex_lock(&image[minor].mutex); + + /* XXX Do we *really* want this helper - we can use vme_*_get ? */ + image_size = vme_get_size(image[minor].resource); + + /* Ensure we are starting at a valid location */ + if ((*ppos < 0) || (*ppos > (image_size - 1))) { + mutex_unlock(&image[minor].mutex); + return 0; + } + + /* Ensure not reading past end of the image */ + if (*ppos + count > image_size) + okcount = image_size - *ppos; + else + okcount = count; + + switch (type[minor]) { + case MASTER_MINOR: + retval = resource_to_user(minor, buf, okcount, ppos); + break; + case SLAVE_MINOR: + retval = buffer_to_user(minor, buf, okcount, ppos); + break; + default: + retval = -EINVAL; + } + + mutex_unlock(&image[minor].mutex); + if (retval > 0) + *ppos += retval; + + return retval; +} + +static ssize_t vme_user_write(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + unsigned int minor = MINOR(file_inode(file)->i_rdev); + ssize_t retval; + size_t image_size; + size_t okcount; + + if (minor == CONTROL_MINOR) + return 0; + + mutex_lock(&image[minor].mutex); + + image_size = vme_get_size(image[minor].resource); + + /* Ensure we are starting at a valid location */ + if ((*ppos < 0) || (*ppos > (image_size - 1))) { + mutex_unlock(&image[minor].mutex); + return 0; + } + + /* Ensure not reading past end of the image */ + if (*ppos + count > image_size) + okcount = image_size - *ppos; + else + okcount = count; + + switch (type[minor]) { + case MASTER_MINOR: + retval = resource_from_user(minor, buf, okcount, ppos); + break; + case SLAVE_MINOR: + retval = buffer_from_user(minor, buf, okcount, ppos); + break; + default: + retval = -EINVAL; + } + + mutex_unlock(&image[minor].mutex); + + if (retval > 0) + *ppos += retval; + + return retval; +} + +static loff_t vme_user_llseek(struct file *file, loff_t off, int whence) +{ + unsigned int minor = MINOR(file_inode(file)->i_rdev); + size_t image_size; + loff_t res; + + if (minor == CONTROL_MINOR) + return -EINVAL; + + mutex_lock(&image[minor].mutex); + image_size = vme_get_size(image[minor].resource); + res = fixed_size_llseek(file, off, whence, image_size); + mutex_unlock(&image[minor].mutex); + + return res; +} + +/* + * The ioctls provided by the old VME access method (the one at vmelinux.org) + * are most certainly wrong as the effectively push the registers layout + * through to user space. Given that the VME core can handle multiple bridges, + * with different register layouts this is most certainly not the way to go. + * + * We aren't using the structures defined in the Motorola driver either - these + * are also quite low level, however we should use the definitions that have + * already been defined. + */ +static int vme_user_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct vme_master master; + struct vme_slave slave; + struct vme_irq_id irq_req; + unsigned long copied; + unsigned int minor = MINOR(inode->i_rdev); + int retval; + dma_addr_t pci_addr; + void __user *argp = (void __user *)arg; + + statistics.ioctls++; + + switch (type[minor]) { + case CONTROL_MINOR: + switch (cmd) { + case VME_IRQ_GEN: + copied = copy_from_user(&irq_req, argp, + sizeof(struct vme_irq_id)); + if (copied != 0) { + pr_warn("Partial copy from userspace\n"); + return -EFAULT; + } + + return vme_irq_generate(vme_user_bridge, + irq_req.level, + irq_req.statid); + } + break; + case MASTER_MINOR: + switch (cmd) { + case VME_GET_MASTER: + memset(&master, 0, sizeof(struct vme_master)); + + /* XXX We do not want to push aspace, cycle and width + * to userspace as they are + */ + retval = vme_master_get(image[minor].resource, + &master.enable, &master.vme_addr, + &master.size, &master.aspace, + &master.cycle, &master.dwidth); + + copied = copy_to_user(argp, &master, + sizeof(struct vme_master)); + if (copied != 0) { + pr_warn("Partial copy to userspace\n"); + return -EFAULT; + } + + return retval; + + case VME_SET_MASTER: + + if (image[minor].mmap_count != 0) { + pr_warn("Can't adjust mapped window\n"); + return -EPERM; + } + + copied = copy_from_user(&master, argp, sizeof(master)); + if (copied != 0) { + pr_warn("Partial copy from userspace\n"); + return -EFAULT; + } + + /* XXX We do not want to push aspace, cycle and width + * to userspace as they are + */ + return vme_master_set(image[minor].resource, + master.enable, master.vme_addr, master.size, + master.aspace, master.cycle, master.dwidth); + + break; + } + break; + case SLAVE_MINOR: + switch (cmd) { + case VME_GET_SLAVE: + memset(&slave, 0, sizeof(struct vme_slave)); + + /* XXX We do not want to push aspace, cycle and width + * to userspace as they are + */ + retval = vme_slave_get(image[minor].resource, + &slave.enable, &slave.vme_addr, + &slave.size, &pci_addr, &slave.aspace, + &slave.cycle); + + copied = copy_to_user(argp, &slave, + sizeof(struct vme_slave)); + if (copied != 0) { + pr_warn("Partial copy to userspace\n"); + return -EFAULT; + } + + return retval; + + case VME_SET_SLAVE: + + copied = copy_from_user(&slave, argp, sizeof(slave)); + if (copied != 0) { + pr_warn("Partial copy from userspace\n"); + return -EFAULT; + } + + /* XXX We do not want to push aspace, cycle and width + * to userspace as they are + */ + return vme_slave_set(image[minor].resource, + slave.enable, slave.vme_addr, slave.size, + image[minor].pci_buf, slave.aspace, + slave.cycle); + + break; + } + break; + } + + return -EINVAL; +} + +static long +vme_user_unlocked_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + int ret; + struct inode *inode = file_inode(file); + unsigned int minor = MINOR(inode->i_rdev); + + mutex_lock(&image[minor].mutex); + ret = vme_user_ioctl(inode, file, cmd, arg); + mutex_unlock(&image[minor].mutex); + + return ret; +} + +static void vme_user_vm_open(struct vm_area_struct *vma) +{ + struct vme_user_vma_priv *vma_priv = vma->vm_private_data; + + atomic_inc(&vma_priv->refcnt); +} + +static void vme_user_vm_close(struct vm_area_struct *vma) +{ + struct vme_user_vma_priv *vma_priv = vma->vm_private_data; + unsigned int minor = vma_priv->minor; + + if (!atomic_dec_and_test(&vma_priv->refcnt)) + return; + + mutex_lock(&image[minor].mutex); + image[minor].mmap_count--; + mutex_unlock(&image[minor].mutex); + + kfree(vma_priv); +} + +static int vme_user_master_mmap(unsigned int minor, struct vm_area_struct *vma) +{ + int err; + struct vme_user_vma_priv *vma_priv; + + mutex_lock(&image[minor].mutex); + + err = vme_master_mmap(image[minor].resource, vma); + if (err) { + mutex_unlock(&image[minor].mutex); + return err; + } + + vma_priv = kmalloc(sizeof(struct vme_user_vma_priv), GFP_KERNEL); + if (vma_priv == NULL) { + mutex_unlock(&image[minor].mutex); + return -ENOMEM; + } + + vma_priv->minor = minor; + atomic_set(&vma_priv->refcnt, 1); + vma->vm_ops = &vme_user_vm_ops; + vma->vm_private_data = vma_priv; + + image[minor].mmap_count++; + + mutex_unlock(&image[minor].mutex); + + return 0; +} + +static int vme_user_mmap(struct file *file, struct vm_area_struct *vma) +{ + unsigned int minor = MINOR(file_inode(file)->i_rdev); + + if (type[minor] == MASTER_MINOR) + return vme_user_master_mmap(minor, vma); + + return -ENODEV; +} + + +/* + * Unallocate a previously allocated buffer + */ +static void buf_unalloc(int num) +{ + if (image[num].kern_buf) { +#ifdef VME_DEBUG + pr_debug("UniverseII:Releasing buffer at %p\n", + image[num].pci_buf); +#endif + + vme_free_consistent(image[num].resource, image[num].size_buf, + image[num].kern_buf, image[num].pci_buf); + + image[num].kern_buf = NULL; + image[num].pci_buf = 0; + image[num].size_buf = 0; + +#ifdef VME_DEBUG + } else { + pr_debug("UniverseII: Buffer not allocated\n"); +#endif + } +} + +static struct vme_driver vme_user_driver = { + .name = driver_name, + .match = vme_user_match, + .probe = vme_user_probe, + .remove = vme_user_remove, +}; + + +static int __init vme_user_init(void) +{ + int retval = 0; + + pr_info("VME User Space Access Driver\n"); + + if (bus_num == 0) { + pr_err("No cards, skipping registration\n"); + retval = -ENODEV; + goto err_nocard; + } + + /* Let's start by supporting one bus, we can support more than one + * in future revisions if that ever becomes necessary. + */ + if (bus_num > VME_USER_BUS_MAX) { + pr_err("Driver only able to handle %d buses\n", + VME_USER_BUS_MAX); + bus_num = VME_USER_BUS_MAX; + } + + /* + * Here we just register the maximum number of devices we can and + * leave vme_user_match() to allow only 1 to go through to probe(). + * This way, if we later want to allow multiple user access devices, + * we just change the code in vme_user_match(). + */ + retval = vme_register_driver(&vme_user_driver, VME_MAX_SLOTS); + if (retval != 0) + goto err_reg; + + return retval; + +err_reg: +err_nocard: + return retval; +} + +static int vme_user_match(struct vme_dev *vdev) +{ + int i; + + int cur_bus = vme_bus_num(vdev); + int cur_slot = vme_slot_num(vdev); + + for (i = 0; i < bus_num; i++) + if ((cur_bus == bus[i]) && (cur_slot == vdev->num)) + return 1; + + return 0; +} + +/* + * In this simple access driver, the old behaviour is being preserved as much + * as practical. We will therefore reserve the buffers and request the images + * here so that we don't have to do it later. + */ +static int vme_user_probe(struct vme_dev *vdev) +{ + int i, err; + char *name; + + /* Save pointer to the bridge device */ + if (vme_user_bridge != NULL) { + dev_err(&vdev->dev, "Driver can only be loaded for 1 device\n"); + err = -EINVAL; + goto err_dev; + } + vme_user_bridge = vdev; + + /* Initialise descriptors */ + for (i = 0; i < VME_DEVS; i++) { + image[i].kern_buf = NULL; + image[i].pci_buf = 0; + mutex_init(&image[i].mutex); + image[i].device = NULL; + image[i].resource = NULL; + image[i].users = 0; + } + + /* Initialise statistics counters */ + reset_counters(); + + /* Assign major and minor numbers for the driver */ + err = register_chrdev_region(MKDEV(VME_MAJOR, 0), VME_DEVS, + driver_name); + if (err) { + dev_warn(&vdev->dev, "Error getting Major Number %d for driver.\n", + VME_MAJOR); + goto err_region; + } + + /* Register the driver as a char device */ + vme_user_cdev = cdev_alloc(); + if (!vme_user_cdev) { + err = -ENOMEM; + goto err_char; + } + vme_user_cdev->ops = &vme_user_fops; + vme_user_cdev->owner = THIS_MODULE; + err = cdev_add(vme_user_cdev, MKDEV(VME_MAJOR, 0), VME_DEVS); + if (err) { + dev_warn(&vdev->dev, "cdev_all failed\n"); + goto err_char; + } + + /* Request slave resources and allocate buffers (128kB wide) */ + for (i = SLAVE_MINOR; i < (SLAVE_MAX + 1); i++) { + /* XXX Need to properly request attributes */ + /* For ca91cx42 bridge there are only two slave windows + * supporting A16 addressing, so we request A24 supported + * by all windows. + */ + image[i].resource = vme_slave_request(vme_user_bridge, + VME_A24, VME_SCT); + if (image[i].resource == NULL) { + dev_warn(&vdev->dev, + "Unable to allocate slave resource\n"); + err = -ENOMEM; + goto err_slave; + } + image[i].size_buf = PCI_BUF_SIZE; + image[i].kern_buf = vme_alloc_consistent(image[i].resource, + image[i].size_buf, &image[i].pci_buf); + if (image[i].kern_buf == NULL) { + dev_warn(&vdev->dev, + "Unable to allocate memory for buffer\n"); + image[i].pci_buf = 0; + vme_slave_free(image[i].resource); + err = -ENOMEM; + goto err_slave; + } + } + + /* + * Request master resources allocate page sized buffers for small + * reads and writes + */ + for (i = MASTER_MINOR; i < (MASTER_MAX + 1); i++) { + /* XXX Need to properly request attributes */ + image[i].resource = vme_master_request(vme_user_bridge, + VME_A32, VME_SCT, VME_D32); + if (image[i].resource == NULL) { + dev_warn(&vdev->dev, + "Unable to allocate master resource\n"); + err = -ENOMEM; + goto err_master; + } + image[i].size_buf = PCI_BUF_SIZE; + image[i].kern_buf = kmalloc(image[i].size_buf, GFP_KERNEL); + if (image[i].kern_buf == NULL) { + err = -ENOMEM; + vme_master_free(image[i].resource); + goto err_master; + } + } + + /* Create sysfs entries - on udev systems this creates the dev files */ + vme_user_sysfs_class = class_create(THIS_MODULE, driver_name); + if (IS_ERR(vme_user_sysfs_class)) { + dev_err(&vdev->dev, "Error creating vme_user class.\n"); + err = PTR_ERR(vme_user_sysfs_class); + goto err_class; + } + + /* Add sysfs Entries */ + for (i = 0; i < VME_DEVS; i++) { + int num; + + switch (type[i]) { + case MASTER_MINOR: + name = "bus/vme/m%d"; + break; + case CONTROL_MINOR: + name = "bus/vme/ctl"; + break; + case SLAVE_MINOR: + name = "bus/vme/s%d"; + break; + default: + err = -EINVAL; + goto err_sysfs; + } + + num = (type[i] == SLAVE_MINOR) ? i - (MASTER_MAX + 1) : i; + image[i].device = device_create(vme_user_sysfs_class, NULL, + MKDEV(VME_MAJOR, i), NULL, name, num); + if (IS_ERR(image[i].device)) { + dev_info(&vdev->dev, "Error creating sysfs device\n"); + err = PTR_ERR(image[i].device); + goto err_sysfs; + } + } + + return 0; + +err_sysfs: + while (i > 0) { + i--; + device_destroy(vme_user_sysfs_class, MKDEV(VME_MAJOR, i)); + } + class_destroy(vme_user_sysfs_class); + + /* Ensure counter set correcty to unalloc all master windows */ + i = MASTER_MAX + 1; +err_master: + while (i > MASTER_MINOR) { + i--; + kfree(image[i].kern_buf); + vme_master_free(image[i].resource); + } + + /* + * Ensure counter set correcty to unalloc all slave windows and buffers + */ + i = SLAVE_MAX + 1; +err_slave: + while (i > SLAVE_MINOR) { + i--; + buf_unalloc(i); + vme_slave_free(image[i].resource); + } +err_class: + cdev_del(vme_user_cdev); +err_char: + unregister_chrdev_region(MKDEV(VME_MAJOR, 0), VME_DEVS); +err_region: +err_dev: + return err; +} + +static int vme_user_remove(struct vme_dev *dev) +{ + int i; + + /* Remove sysfs Entries */ + for (i = 0; i < VME_DEVS; i++) { + mutex_destroy(&image[i].mutex); + device_destroy(vme_user_sysfs_class, MKDEV(VME_MAJOR, i)); + } + class_destroy(vme_user_sysfs_class); + + for (i = MASTER_MINOR; i < (MASTER_MAX + 1); i++) { + kfree(image[i].kern_buf); + vme_master_free(image[i].resource); + } + + for (i = SLAVE_MINOR; i < (SLAVE_MAX + 1); i++) { + vme_slave_set(image[i].resource, 0, 0, 0, 0, VME_A32, 0); + buf_unalloc(i); + vme_slave_free(image[i].resource); + } + + /* Unregister device driver */ + cdev_del(vme_user_cdev); + + /* Unregiser the major and minor device numbers */ + unregister_chrdev_region(MKDEV(VME_MAJOR, 0), VME_DEVS); + + return 0; +} + +static void __exit vme_user_exit(void) +{ + vme_unregister_driver(&vme_user_driver); +} + + +MODULE_PARM_DESC(bus, "Enumeration of VMEbus to which the driver is connected"); +module_param_array(bus, int, &bus_num, 0); + +MODULE_DESCRIPTION("VME User Space Access Driver"); +MODULE_AUTHOR("Martyn Welch