Free Electrons

Embedded Linux Experts

/*
 * 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, &reg, 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, &reg, 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 < 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 __devinit 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 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);
}