1
0
mirror of https://github.com/aolofsson/oh.git synced 2025-01-30 02:32:53 +08:00

Merge pull request #69 from olajep/linux-gpio-driver

Linux gpio driver
This commit is contained in:
Andreas Olofsson 2016-04-22 08:24:31 -04:00
commit 22f36ce93d
12 changed files with 577 additions and 0 deletions

View File

@ -0,0 +1,33 @@
OH GPIO controller bindings
Required properties:
- compatible: "oh,gpio"
- reg: Address and length of the register set for the device
- interrupt-parent: phandle of the parent interrupt controller.
- interrupts : Should be the port interrupt shared by all 32 pins.
- gpio-controller : Marks the device node as a GPIO controller.
- #gpio-cells : Should be two.
- first cell is the pin number
- second cell is used to specify optional parameters (unused)
- interrupt-controller: Mark the device node as an interrupt controller.
- #interrupt-cells : Should be 2.
The first cell is the GPIO number.
The second cell is used to specify flags:
bits[3:0] trigger type and level flags:
1 = rising edge triggered.
2 = falling edge triggered.
4 = active high level-sensitive.
8 = active low level-sensitive.
Example:
gpio0: gpio0 {
compatible = "oh,gpio";
reg = <0xe000a000 0x1000>;
interrupt-parent = <&intc>;
interrupts = <57>;
gpio-controller;
#gpio-cells = <2>;
interrupt-controller;
#interrupt-cells = <2>;
};

View File

@ -0,0 +1 @@
obj-m := gpio-oh.o

View File

@ -0,0 +1,14 @@
ifneq ($(KERNELRELEASE),)
# kbuild part of makefile
obj-y := gpio-oh.o
else
# normal makefile
KDIR ?= /lib/modules/`uname -r`/build
default:
$(MAKE) -C $(KDIR) M=$$PWD
clean:
rm -fr gpio-oh.mod.c *.o *.ko .*.cmd modules.order Module.symvers .tmp_versions
endif

View File

@ -0,0 +1,28 @@
# OH GPIO Linux Kernel driver
## Compilation
### Against system kernel sources
```
make
```
### Custom source path
```
make KDIR=path/to/src/linux
```
### Cross compiling (example w/ custom build-dir)
```
make -C path/to/built/linux-builddir \
M=`pwd` \
ARCH=arm \
CROSS_COMPILE=arm-linux-gnueabihf- \
LOADADDR=0x8000
```
For more flags etc, see:
https://www.kernel.org/doc/Documentation/kbuild/modules.txt

View File

@ -0,0 +1,501 @@
/*
* Open Hardware GPIO device driver
*
* Copyright (C) 2016 Parallella Foundation
* Written by Ola Jeppsson <ola@adapteva.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* The full GNU General Public License is included in this distribution in the
* file called COPYING.
*/
/* ???: Interrupts implementation likely broken. Needs testing!!! */
#include <linux/bitops.h>
#include <linux/gpio/driver.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/spinlock.h>
#define DRIVERNAME "oh-gpio"
#define OH_GPIO_NR_GPIOS 64
#define OH_GPIO_DIR 0x00 /* set direction of pin */
#define OH_GPIO_IN 0x08 /* input data (read only) */
#define OH_GPIO_OUT 0x10 /* output data (write only) */
#define OH_GPIO_OUTCLR 0x18 /* alias, clears specific bits in GPIO_OUT */
#define OH_GPIO_OUTSET 0x20 /* alias, sets specific bits in GPIO_OUT */
#define OH_GPIO_OUTXOR 0x28 /* alias, toggles specific bits in GPIO_OUT */
#define OH_GPIO_IMASK 0x30 /* interrupt mask */
#define OH_GPIO_ITYPE 0x38 /* interrupt type (level/edge) */
#define OH_GPIO_IPOL 0x40 /* interrupt polarity (hi/rising / low/falling) */
#define OH_GPIO_ILAT 0x48 /* latched interrupts (read only) */
#define OH_GPIO_ILATCLR 0x50 /* clear an interrupt */
struct oh_gpio {
void __iomem *base_addr;
struct gpio_chip chip;
int irq;
spinlock_t lock;
};
static struct oh_gpio *to_oh_gpio(struct gpio_chip *gpio)
{
return container_of(gpio, struct oh_gpio, chip);
}
#ifdef writeq
#define oh_gpio_writeq writeq
#else
static inline void oh_gpio_writeq(u64 value, void __iomem *addr)
{
writel((u32) (value & 0xffffffff), addr);
writel((u32) (value >> 32), (u8 __iomem *) addr + 4);
}
#endif
#ifdef readq
#define oh_gpio_readq readq
#else
static inline u64 oh_gpio_readq(void __iomem *addr)
{
u64 lo, hi;
lo = readl(addr);
hi = readl((u8 __iomem *) addr + 4);
return (hi << 32 | lo);
}
#endif
static inline void oh_gpio_reg_write(u64 value, void __iomem *base,
unsigned long offset)
{
oh_gpio_writeq(value, (u8 __iomem *) base + offset);
}
static inline u64 oh_gpio_reg_read(void __iomem *base, unsigned long offset)
{
return oh_gpio_readq((u8 __iomem *) base + offset);
}
/**
* oh_gpio_get_value - Get value of the specified pin
* @chip: gpio chip device
* @pin: gpio pin number
*
* Return: 0 if the pin is low, 1 if pin is high.
*/
static int oh_gpio_get_value(struct gpio_chip *chip, unsigned pin)
{
u64 data;
unsigned long flags;
struct oh_gpio *gpio = to_oh_gpio(chip);
spin_lock_irqsave(&gpio->lock, flags);
data = oh_gpio_reg_read(gpio->base_addr, OH_GPIO_IN);
spin_unlock_irqrestore(&gpio->lock, flags);
return (data >> pin) & 1;
}
/**
* oh_gpio_set_value - Assign output value for pin
* @chip: gpio chip device
* @pin: gpio pin number
* @value: value used to modify the state of the specified pin
*/
static void oh_gpio_set_value(struct gpio_chip *chip, unsigned pin, int value)
{
u64 mask;
unsigned long flags;
struct oh_gpio *gpio = to_oh_gpio(chip);
value = !!value;
mask = value << pin;
spin_lock_irqsave(&gpio->lock, flags);
if (value)
oh_gpio_reg_write(mask, gpio->base_addr, OH_GPIO_OUTSET);
else
oh_gpio_reg_write(mask, gpio->base_addr, OH_GPIO_OUTCLR);
spin_unlock_irqrestore(&gpio->lock, flags);
}
/**
* oh_gpio_get_direction - Get direction of pin
* @chip: gpio chip device
* @pin: gpio pin number
*
* Return: direction of pin, 0=out, 1=in
*/
static int oh_gpio_get_direction(struct gpio_chip *chip, unsigned pin)
{
u64 dir;
unsigned long flags;
struct oh_gpio *gpio = to_oh_gpio(chip);
spin_lock_irqsave(&gpio->lock, flags);
dir = oh_gpio_reg_read(gpio->base_addr, OH_GPIO_DIR);
spin_unlock_irqrestore(&gpio->lock, flags);
return !((dir >> pin) & 1);
}
/**
* oh_gpio_direction_in - Set the direction of the specified GPIO pin as input
* @chip: gpio chip device
* @pin: gpio pin number
*
* Return: 0 always
*/
static int oh_gpio_direction_in(struct gpio_chip *chip, unsigned pin)
{
u64 mask, dir;
unsigned long flags;
struct oh_gpio *gpio = to_oh_gpio(chip);
mask = BIT_ULL(pin);
spin_lock_irqsave(&gpio->lock, flags);
dir = oh_gpio_reg_read(gpio->base_addr, OH_GPIO_DIR);
dir &= ~mask;
oh_gpio_reg_write(dir, gpio->base_addr, OH_GPIO_DIR);
spin_unlock_irqrestore(&gpio->lock, flags);
return 0;
}
/**
* oh_gpio_direction_out - Set the direction of the specified GPIO pin as output
* @chip: gpio chip device
* @pin: gpio pin number
* @value: value to be written to pin
*
* This function sets the direction of specified GPIO pin as output, and uses
* oh_gpio_set to set the specified pin value.
*
* Return: 0 always
*/
static int oh_gpio_direction_out(struct gpio_chip *chip, unsigned pin,
int value)
{
u64 mask, dir;
unsigned long flags;
struct oh_gpio *gpio = to_oh_gpio(chip);
mask = BIT_ULL(pin);
spin_lock_irqsave(&gpio->lock, flags);
dir = oh_gpio_reg_read(gpio->base_addr, OH_GPIO_DIR);
dir |= mask;
oh_gpio_reg_write(dir, gpio->base_addr, OH_GPIO_DIR);
spin_unlock_irqrestore(&gpio->lock, flags);
oh_gpio_set_value(chip, pin, value);
return 0;
}
/**
* oh_gpio_irq_mask - Disable interrupts for a gpio pin
* @irq_data: per irq and chip data passed down to chip functions
*
*/
static void oh_gpio_irq_mask(struct irq_data *irq_data)
{
u64 imask;
unsigned long flags;
unsigned pin;
struct oh_gpio *gpio = to_oh_gpio(irq_data_get_irq_chip_data(irq_data));
pin = irq_data->hwirq;
spin_lock_irqsave(&gpio->lock, flags);
imask = oh_gpio_reg_read(gpio->base_addr, OH_GPIO_IMASK);
imask |= BIT_ULL(pin);
oh_gpio_reg_write(imask, gpio->base_addr, OH_GPIO_IMASK);
spin_unlock_irqrestore(&gpio->lock, flags);
}
/**
* oh_gpio_irq_unmask - Enable interrupts for a gpio pin
* @irq_data: irq data containing irq number of gpio pin for the interrupt
* to enable
*/
static void oh_gpio_irq_unmask(struct irq_data *irq_data)
{
u64 imask;
unsigned long flags;
unsigned pin;
struct oh_gpio *gpio = to_oh_gpio(irq_data_get_irq_chip_data(irq_data));
pin = irq_data->hwirq;
spin_lock_irqsave(&gpio->lock, flags);
imask = oh_gpio_reg_read(gpio->base_addr, OH_GPIO_IMASK);
imask &= ~BIT_ULL(pin);
oh_gpio_reg_write(imask, gpio->base_addr, OH_GPIO_IMASK);
spin_unlock_irqrestore(&gpio->lock, flags);
}
/**
* oh_gpio_irq_ack - Clear interrupt latch of a gpio pin
* @irq_data: irq data containing irq number of gpio pin for the interrupt
* to clear
*/
static void oh_gpio_irq_ack(struct irq_data *irq_data)
{
unsigned long flags;
unsigned pin;
struct oh_gpio *gpio = to_oh_gpio(irq_data_get_irq_chip_data(irq_data));
pin = irq_data->hwirq;
spin_lock_irqsave(&gpio->lock, flags);
oh_gpio_reg_write(BIT_ULL(pin), gpio->base_addr, OH_GPIO_ILATCLR);
spin_unlock_irqrestore(&gpio->lock, flags);
}
/**
* oh_gpio_irq_enable - Enable interrupts for a gpio pin
* @irq_data: irq data containing irq number of gpio pin
*/
static void oh_gpio_irq_enable(struct irq_data *irq_data)
{
oh_gpio_irq_ack(irq_data);
oh_gpio_irq_unmask(irq_data);
}
/**
* oh_gpio_irq_set_type - Set the irq type for a gpio pin
* @irq_data: irq data containing irq number of gpio pin
* @type: interrupt type that is to be set for the gpio pin
*
* Return: 0 on success, negative on error.
*/
static int oh_gpio_irq_set_type(struct irq_data *irq_data, unsigned type)
{
u64 itype, ipol, mask;
unsigned pin;
unsigned long flags;
irq_flow_handler_t handler;
struct oh_gpio *gpio = to_oh_gpio(irq_data_get_irq_chip_data(irq_data));
pin = irq_data->hwirq;
mask = BIT_ULL(pin);
spin_lock_irqsave(&gpio->lock, flags);
itype = oh_gpio_reg_read(gpio->base_addr, OH_GPIO_ITYPE);
ipol = oh_gpio_reg_read(gpio->base_addr, OH_GPIO_IPOL);
switch (type) {
case IRQ_TYPE_EDGE_RISING:
itype |= mask;
ipol |= mask;
break;
case IRQ_TYPE_EDGE_FALLING:
itype |= mask;
ipol &= ~mask;
break;
case IRQ_TYPE_LEVEL_HIGH:
itype &= ~mask;
ipol |= mask;
break;
case IRQ_TYPE_LEVEL_LOW:
itype &= ~mask;
ipol &= ~mask;
break;
default:
spin_unlock_irqrestore(&gpio->lock, flags);
return -EINVAL;
}
oh_gpio_reg_write(itype, gpio->base_addr, OH_GPIO_ITYPE);
oh_gpio_reg_write(ipol, gpio->base_addr, OH_GPIO_IPOL);
spin_unlock_irqrestore(&gpio->lock, flags);
handler = type & IRQ_TYPE_LEVEL_MASK ? handle_level_irq
: handle_edge_irq;
irq_set_handler_locked(irq_data, handler);
return 0;
}
static struct irq_chip oh_gpio_irqchip = {
.name = DRIVERNAME,
.irq_enable = oh_gpio_irq_enable,
.irq_ack = oh_gpio_irq_ack,
.irq_mask = oh_gpio_irq_mask,
.irq_unmask = oh_gpio_irq_unmask,
.irq_set_type = oh_gpio_irq_set_type,
};
/**
* oh_gpio_irq_handler - IRQ handler
* @irq: oh_gpio irq number
* @devid: pointer to oh_gpio struct
*
* Reads the interrupt latch register and interrupt mask register to get the
* gpio pin number(s) that have pending interrupts. It then calls the generic
* irq handlers for those pins irqs.
*
* Note: Assumes ilat is NOT MASKED by imask (but instead irq_out is),
* which is not implemented in HDL now.
*
* Return: IRQ_HANDLED if any interrupts were handled, IRQ_NONE otherwise.
*/
static irqreturn_t oh_gpio_irq_handler(int irq, void *dev_id)
{
u64 pending, ilat, imask;
unsigned long flags, pending_lo, pending_hi;
int offset;
struct oh_gpio *gpio = dev_id;
struct irq_domain *irqdomain = gpio->chip.irqdomain;
spin_lock_irqsave(&gpio->lock, flags);
ilat = oh_gpio_reg_read(gpio->base_addr, OH_GPIO_ILAT);
imask = oh_gpio_reg_read(gpio->base_addr, OH_GPIO_IMASK);
spin_unlock_irqrestore(&gpio->lock, flags);
/* !!!: Assumes ilat is NOT MASKED by imask (but instead irq_out is),
* which is not implemented in HDL now
*/
/* Only unmasked interrupts are pending */
pending = ilat & ~imask;
/* No generic 64-bit for_each_set_bit. Need to split in high/low */
pending_lo = (unsigned long) ((pending >> 0) & 0xffffffff);
pending_hi = (unsigned long) ((pending >> 32) & 0xffffffff);
for_each_set_bit(offset, &pending_lo, 32)
generic_handle_irq(irq_find_mapping(irqdomain, offset));
for_each_set_bit(offset, &pending_hi, 32)
generic_handle_irq(irq_find_mapping(irqdomain, 32 + offset));
return pending ? IRQ_HANDLED : IRQ_NONE;
}
static const struct of_device_id oh_gpio_of_match[] = {
{ .compatible = "oh,gpio" },
{ }
};
MODULE_DEVICE_TABLE(of, oh_gpio_of_match);
/**
* oh_gpio_probe - Platform probe for a oh_gpio device
* @pdev: platform device
*
* Note: All interrupts are cleared + masked after function exits.
*
* Return: 0 on success, negative error otherwise.
*/
static int oh_gpio_probe(struct platform_device *pdev)
{
int ret;
struct oh_gpio *gpio;
struct gpio_chip *chip;
struct resource *res;
gpio = devm_kzalloc(&pdev->dev, sizeof(*gpio), GFP_KERNEL);
if (!gpio)
return -ENOMEM;
platform_set_drvdata(pdev, gpio);
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
gpio->base_addr = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(gpio->base_addr))
return PTR_ERR(gpio->base_addr);
gpio->irq = platform_get_irq(pdev, 0);
if (gpio->irq < 0) {
dev_err(&pdev->dev, "invalid IRQ\n");
return gpio->irq;
}
ret = devm_request_irq(&pdev->dev, gpio->irq, oh_gpio_irq_handler, 0,
dev_name(&pdev->dev), gpio);
if (ret) {
dev_err(&pdev->dev, "could not request IRQ\n");
return ret;
}
spin_lock_init(&gpio->lock);
/* configure the gpio chip */
chip = &gpio->chip;
chip->label = "oh_gpio";
chip->owner = THIS_MODULE;
chip->dev = &pdev->dev;
chip->base = -1;
chip->ngpio = OH_GPIO_NR_GPIOS;
chip->get = oh_gpio_get_value;
chip->set = oh_gpio_set_value;
chip->get_direction = oh_gpio_get_direction;
chip->direction_input = oh_gpio_direction_in;
chip->direction_output = oh_gpio_direction_out;
/* mask / clear all interrupts */
oh_gpio_reg_write(~0ULL, gpio->base_addr, OH_GPIO_IMASK);
oh_gpio_reg_write(~0ULL, gpio->base_addr, OH_GPIO_ILATCLR);
/* register gpio chip */
ret = gpiochip_add(chip);
if (ret) {
dev_err(&pdev->dev, "failed to add gpio chip\n");
return ret;
}
ret = gpiochip_irqchip_add(chip, &oh_gpio_irqchip, 0,
handle_level_irq, IRQ_TYPE_NONE);
if (ret) {
dev_err(&pdev->dev, "failed to add irq chip\n");
gpiochip_remove(chip);
return ret;
}
return 0;
}
/**
* oh_gpio_remove - Driver removal function
* @pdev: platform device
*
* Return: 0 always
*/
static int oh_gpio_remove(struct platform_device *pdev)
{
struct oh_gpio *gpio = platform_get_drvdata(pdev);
gpiochip_remove(&gpio->chip);
return 0;
}
static struct platform_driver oh_gpio_driver = {
.driver = {
.name = DRIVERNAME,
.of_match_table = oh_gpio_of_match,
},
.probe = oh_gpio_probe,
.remove = oh_gpio_remove,
};
module_platform_driver(oh_gpio_driver);
MODULE_AUTHOR("Ola Jeppsson <ola@adapteva.com>");
MODULE_DESCRIPTION("OH GPIO driver");
MODULE_VERSION("0.1");
MODULE_LICENSE("GPL");