mirror of
https://github.com/aolofsson/oh.git
synced 2025-02-07 06:44:09 +08:00
commit
22f36ce93d
@ -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>;
|
||||||
|
};
|
1
src/gpio/driver/linux/Kbuild
Normal file
1
src/gpio/driver/linux/Kbuild
Normal file
@ -0,0 +1 @@
|
|||||||
|
obj-m := gpio-oh.o
|
14
src/gpio/driver/linux/Makefile
Normal file
14
src/gpio/driver/linux/Makefile
Normal 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
|
28
src/gpio/driver/linux/README.md
Normal file
28
src/gpio/driver/linux/README.md
Normal 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
|
501
src/gpio/driver/linux/gpio-oh.c
Normal file
501
src/gpio/driver/linux/gpio-oh.c
Normal 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");
|
Loading…
x
Reference in New Issue
Block a user