165 lines
4.5 KiB
C
165 lines
4.5 KiB
C
/**
|
|
Network Driver for Beckhoff CCAT communication controller
|
|
Copyright (C) 2014 Beckhoff Automation GmbH
|
|
Author: Patrick Bruenn <p.bruenn@beckhoff.com>
|
|
|
|
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.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License along
|
|
with this program; if not, write to the Free Software Foundation, Inc.,
|
|
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
*/
|
|
|
|
#include <linux/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/gpio.h>
|
|
#include <linux/version.h>
|
|
#include "module.h"
|
|
|
|
/**
|
|
* struct ccat_gpio - CCAT GPIO function
|
|
* @ioaddr: PCI base address of the CCAT Update function
|
|
* @info: holds a copy of the CCAT Update function information block (read from PCI config space)
|
|
*/
|
|
struct ccat_gpio {
|
|
struct gpio_chip chip;
|
|
void __iomem *ioaddr;
|
|
struct mutex lock;
|
|
};
|
|
|
|
/** TODO implement in LED driver
|
|
#define TC_RED 0x01
|
|
#define TC_GREEN 0x02
|
|
#define TC_BLUE 0x04
|
|
#define FB1_RED 0x08
|
|
#define FB1_GREEN 0x10
|
|
#define FB1_BLUE 0x20
|
|
#define FB2_RED 0x40
|
|
#define FB2_GREEN 0x80
|
|
#define FB2_BLUE 0x100
|
|
*/
|
|
|
|
static int set_bit_in_register(struct mutex *lock, void __iomem * ioaddr,
|
|
unsigned nr, int val)
|
|
{
|
|
volatile unsigned long old;
|
|
|
|
mutex_lock(lock);
|
|
old = ioread32(ioaddr);
|
|
val ? set_bit(nr, &old) : clear_bit(nr, &old);
|
|
if (val)
|
|
set_bit(nr, &old);
|
|
else
|
|
clear_bit(nr, &old);
|
|
iowrite32(old, ioaddr);
|
|
mutex_unlock(lock);
|
|
return 0;
|
|
}
|
|
|
|
static int ccat_gpio_get_direction(struct gpio_chip *chip, unsigned nr)
|
|
{
|
|
struct ccat_gpio *gdev = container_of(chip, struct ccat_gpio, chip);
|
|
const size_t byte_offset = 4 * (nr / 32) + 0x8;
|
|
const u32 mask = 1 << (nr % 32);
|
|
|
|
return !(mask & ioread32(gdev->ioaddr + byte_offset));
|
|
}
|
|
|
|
static int ccat_gpio_direction_input(struct gpio_chip *chip, unsigned nr)
|
|
{
|
|
struct ccat_gpio *gdev = container_of(chip, struct ccat_gpio, chip);
|
|
|
|
return set_bit_in_register(&gdev->lock, gdev->ioaddr + 0x8, nr, 0);
|
|
}
|
|
|
|
static int ccat_gpio_direction_output(struct gpio_chip *chip, unsigned nr,
|
|
int val)
|
|
{
|
|
struct ccat_gpio *gdev = container_of(chip, struct ccat_gpio, chip);
|
|
|
|
return set_bit_in_register(&gdev->lock, gdev->ioaddr + 0x8, nr, 1);
|
|
}
|
|
|
|
static int ccat_gpio_get(struct gpio_chip *chip, unsigned nr)
|
|
{
|
|
struct ccat_gpio *gdev = container_of(chip, struct ccat_gpio, chip);
|
|
const size_t byte_off = 4 * (nr / 32);
|
|
const int mask = 1 << (nr % 32);
|
|
int dir_off;
|
|
int value;
|
|
|
|
/** omit direction changes before value was read */
|
|
mutex_lock(&gdev->lock);
|
|
dir_off = 0x10 * ccat_gpio_get_direction(chip, nr);
|
|
value = !(mask & ioread32(gdev->ioaddr + byte_off + dir_off));
|
|
mutex_unlock(&gdev->lock);
|
|
return value;
|
|
}
|
|
|
|
static void ccat_gpio_set(struct gpio_chip *chip, unsigned nr, int val)
|
|
{
|
|
struct ccat_gpio *gdev = container_of(chip, struct ccat_gpio, chip);
|
|
|
|
set_bit_in_register(&gdev->lock, gdev->ioaddr, nr, val);
|
|
}
|
|
|
|
static const struct gpio_chip ccat_gpio_chip = {
|
|
.label = KBUILD_MODNAME,
|
|
.owner = THIS_MODULE,
|
|
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3,12,0))
|
|
.get_direction = ccat_gpio_get_direction,
|
|
#endif
|
|
.direction_input = ccat_gpio_direction_input,
|
|
.get = ccat_gpio_get,
|
|
.direction_output = ccat_gpio_direction_output,
|
|
.set = ccat_gpio_set,
|
|
.dbg_show = NULL,
|
|
.base = -1,
|
|
.can_sleep = false
|
|
};
|
|
|
|
static int ccat_gpio_probe(struct ccat_function *func)
|
|
{
|
|
struct ccat_gpio *const gpio = kzalloc(sizeof(*gpio), GFP_KERNEL);
|
|
int ret;
|
|
|
|
if (!gpio)
|
|
return -ENOMEM;
|
|
|
|
gpio->ioaddr = func->ccat->bar_0 + func->info.addr;
|
|
memcpy(&gpio->chip, &ccat_gpio_chip, sizeof(gpio->chip));
|
|
gpio->chip.ngpio = func->info.num_gpios;
|
|
mutex_init(&gpio->lock);
|
|
|
|
ret = gpiochip_add(&gpio->chip);
|
|
if (ret) {
|
|
kfree(gpio);
|
|
return ret;
|
|
}
|
|
pr_info("registered %s as gpiochip%d with #%d GPIOs.\n",
|
|
gpio->chip.label, gpio->chip.base, gpio->chip.ngpio);
|
|
func->private_data = gpio;
|
|
return 0;
|
|
}
|
|
|
|
static void ccat_gpio_remove(struct ccat_function *func)
|
|
{
|
|
struct ccat_gpio *const gpio = func->private_data;
|
|
|
|
gpiochip_remove(&gpio->chip);
|
|
};
|
|
|
|
const struct ccat_driver gpio_driver = {
|
|
.type = CCATINFO_GPIO,
|
|
.probe = ccat_gpio_probe,
|
|
.remove = ccat_gpio_remove,
|
|
};
|