From: Martyn Welch <martyn.welch@ge.com>
To: gregkh@suse.de
Cc: Grant Likely <grant.likely@secretlab.ca>,
devel@driverdev.osuosl.org, manohar.vanga@cern.ch,
cota@braap.org, linux-kernel@vger.kernel.org
Subject: Re: [PATCH] Driver for GE PIO2 VME Card
Date: Fri, 04 Nov 2011 17:38:57 +0000 [thread overview]
Message-ID: <4EB42331.7070707@ge.com> (raw)
In-Reply-To: <20111104173126.30940.28135.stgit@ES-BJ21R4J>
On 04/11/11 17:33, Martyn Welch wrote:
> From: Martyn Welch <martyn.welch@gefanuc.com>
>
Agh, how did that get in there. As in the "signed-off-by" line my email
address is (and has been for a while now) "martyn.welch@ge.com".
Martyn
> This patch implements a driver for the GE PIO2 VME Parallel I/O Card. This
> card is a 6U VME Card, implementing 32 solid-state relay switched IO lines,
> in 4 groups of 8. The IO lines are provided as input, output or both as a
> build time option.
>
> Signed-off-by: Martyn Welch <martyn.welch@ge.com>
> ---
>
> This patch provides basic support for the GPIO on the PIO-2 at this point it
> does not provide support for IRQs generated by GPIO or the 6 counters also
> provided by the board. However, it does provide a good example of how to write
> a VME driver for real hardware.
>
> drivers/staging/vme/devices/Kconfig | 10 +
> drivers/staging/vme/devices/Makefile | 3
> 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 | 524 +++++++++++++++++++++++++++
> drivers/staging/vme/devices/vme_pio2_gpio.c | 227 ++++++++++++
> 6 files changed, 1084 insertions(+), 0 deletions(-)
> 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
>
>
> diff --git a/drivers/staging/vme/devices/Kconfig b/drivers/staging/vme/devices/Kconfig
> index ca5ba89..99f5414 100644
> --- a/drivers/staging/vme/devices/Kconfig
> +++ b/drivers/staging/vme/devices/Kconfig
> @@ -6,3 +6,13 @@ config VME_USER
> 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 http://vmelinux.org/.
> +config VME_PIO2
> + tristate "GE PIO2 VME"
> + help
> + If you say Y here you have a GE PIO2. The PIO2 is a 6U VME Card,
> + implementing 32 solid-state relay switched IO lines, in 4 groups of 8.
> + The IO lines are provided as input, output or both as a build time
> + option.
> +
> + Otherwise it is safe to say N here.
> +
> diff --git a/drivers/staging/vme/devices/Makefile b/drivers/staging/vme/devices/Makefile
> index 459742a..172512c 100644
> --- a/drivers/staging/vme/devices/Makefile
> +++ b/drivers/staging/vme/devices/Makefile
> @@ -3,3 +3,6 @@
> #
>
> 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 0000000..3c59313
> --- /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 __init pio2_gpio_init(struct pio2_card *);
> +void __exit 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 0000000..08e0d59
> --- /dev/null
> +++ b/drivers/staging/vme/devices/vme_pio2_cntr.c
> @@ -0,0 +1,71 @@
> +/*
> + * GE PIO2 Counter 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.
> + *
> + * The PIO-2 has 6 counters, currently this code just disables the interrupts
> + * and leaves them alone.
> + *
> + */
> +
> +#include <linux/device.h>
> +#include <linux/types.h>
> +#include <linux/gpio.h>
> +
> +#include "../vme.h"
> +#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 0000000..0b2db6a
> --- /dev/null
> +++ b/drivers/staging/vme/devices/vme_pio2_core.c
> @@ -0,0 +1,524 @@
> +/*
> + * 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.
> + */
> +
> +#include <linux/version.h>
> +#include <linux/module.h>
> +#include <linux/moduleparam.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 "../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 const char *variant[PIO2_CARDS_MAX];
> +static int variant_num;
> +
> +static int loopback;
> +
> +static int pio2_match(struct vme_dev *);
> +static int __devinit pio2_probe(struct vme_dev *);
> +static int __devexit 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 = __devexit_p(pio2_remove),
> +};
> +
> +
> +static int __init pio2_init(void)
> +{
> + int retval = 0;
> +
> + if (bus_num == 0) {
> + printk(KERN_ERR "%s: No cards, skipping registration\n",
> + driver_name);
> + goto err_nocard;
> + }
> +
> + if (bus_num > PIO2_CARDS_MAX) {
> + printk(KERN_ERR
> + "%s: Driver only able to handle %d PIO2 Cards\n",
> + driver_name, PIO2_CARDS_MAX);
> + bus_num = PIO2_CARDS_MAX;
> + }
> +
> + /* Register the PIO2 driver */
> + retval = vme_register_driver(&pio2_driver, bus_num);
> + if (retval != 0)
> + goto err_reg;
> +
> + return retval;
> +
> +err_reg:
> +err_nocard:
> + return retval;
> +}
> +
> +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");
> + return 0;
> + }
> +
> + if (vdev->num >= base_num) {
> + dev_err(&vdev->dev,
> + "The VME address for the cards registers must be specified");
> + return 0;
> + }
> +
> + if (vdev->num >= vector_num) {
> + dev_err(&vdev->dev,
> + "The IRQ vector used by the card must be specified");
> + return 0;
> + }
> +
> + if (vdev->num >= level_num) {
> + dev_err(&vdev->dev,
> + "The IRQ level used by the card must be specified");
> + return 0;
> + }
> +
> + if (vdev->num >= variant_num) {
> + dev_err(&vdev->dev, "The variant of the card must be specified");
> + return 0;
> + }
> +
> + return 1;
> +}
> +
> +static int __devinit 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) {
> + dev_err(&vdev->dev, "Unable to allocate card structure\n");
> + 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?");
> + 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 __devexit 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, 0);
> +
> +MODULE_PARM_DESC(base, "Base VME address for PIO2 Registers");
> +module_param_array(base, long, &base_num, 0);
> +
> +MODULE_PARM_DESC(vector, "VME IRQ Vector (Lower 4 bits masked)");
> +module_param_array(vector, int, &vector_num, 0);
> +
> +MODULE_PARM_DESC(level, "VME IRQ Level");
> +module_param_array(level, int, &level_num, 0);
> +
> +MODULE_PARM_DESC(variant, "Last 4 characters of PIO2 board variant");
> +module_param_array(variant, charp, &variant_num, 0);
> +
> +/* This is for debugging */
> +MODULE_PARM_DESC(loopback, "Enable loopback mode on all cards");
> +module_param(loopback, bool, 0);
> +
> +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);
> +
> diff --git a/drivers/staging/vme/devices/vme_pio2_gpio.c b/drivers/staging/vme/devices/vme_pio2_gpio.c
> new file mode 100644
> index 0000000..2ac88ad
> --- /dev/null
> +++ b/drivers/staging/vme/devices/vme_pio2_gpio.c
> @@ -0,0 +1,227 @@
> +/*
> + * GE PIO2 GPIO 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.
> + */
> +
> +#include <linux/version.h>
> +#include <linux/module.h>
> +#include <linux/moduleparam.h>
> +#include <linux/types.h>
> +#include <linux/kernel.h>
> +#include <linux/errno.h>
> +#include <linux/device.h>
> +#include <linux/platform_device.h>
> +#include <linux/ctype.h>
> +#include <linux/gpio.h>
> +#include <linux/slab.h>
> +
> +#include "../vme.h"
> +#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;
> + else
> + return 1;
> + } else {
> + if (card->bank[PIO2_CHANNEL_BANK[offset]].config != BOTH)
> + return 1;
> + else
> + 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 availabe 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 runtine\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 runtine\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 < 8; i++) {
> + retval = vme_master_write(card->window, &data, 1,
> + PIO2_REGS_INT_MASK[i]);
> + 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 __init pio2_gpio_init(struct pio2_card *card)
> +{
> + int retval = 0;
> + char *label;
> +
> + label = kmalloc(PIO2_NUM_CHANNELS, GFP_KERNEL);
> + if (label == NULL) {
> + dev_err(&card->vdev->dev, "Unable to allocate GPIO label\n");
> + return -ENOMEM;
> + }
> +
> + sprintf(label, "%s@%s", driver_name, dev_name(&card->vdev->dev));
> + 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 __exit pio2_gpio_exit(struct pio2_card *card)
> +{
> + const char *label = card->gc.label;
> +
> + if (gpiochip_remove(&(card->gc)))
> + dev_err(&card->vdev->dev, "Failed to remove GPIO");
> +
> + kfree(label);
> +}
> +
>
>
> --
> Martyn Welch (Principal Software Engineer) | Registered in England and
> GE Intelligent Platforms | Wales (3828642) at 100
> T +44(0)127322748 | Barbirolli Square, Manchester,
> E martyn.welch@ge.com | M2 3AB VAT:GB 927559189
> _______________________________________________
> devel mailing list
> devel@linuxdriverproject.org
> http://driverdev.linuxdriverproject.org/mailman/listinfo/devel
--
Martyn Welch (Principal Software Engineer) | Registered in England and
GE Intelligent Platforms | Wales (3828642) at 100
T +44(0)1327322748 | Barbirolli Square, Manchester,
E martyn.welch@ge.com | M2 3AB VAT:GB 927559189
next prev parent reply other threads:[~2011-11-04 17:39 UTC|newest]
Thread overview: 9+ messages / expand[flat|nested] mbox.gz Atom feed top
2011-11-04 17:33 [PATCH] Driver for GE PIO2 VME Card Martyn Welch
2011-11-04 17:38 ` Martyn Welch [this message]
2011-11-04 19:55 ` Paul Bolle
2011-11-04 20:00 ` Greg KH
2011-11-07 10:37 ` Martyn Welch
2011-11-07 23:05 ` Greg KH
2011-11-07 9:34 ` Martyn Welch
2011-11-07 9:55 ` Paul Bolle
2011-11-07 11:10 ` Martyn Welch
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=4EB42331.7070707@ge.com \
--to=martyn.welch@ge.com \
--cc=cota@braap.org \
--cc=devel@driverdev.osuosl.org \
--cc=grant.likely@secretlab.ca \
--cc=gregkh@suse.de \
--cc=linux-kernel@vger.kernel.org \
--cc=manohar.vanga@cern.ch \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.