forked from torvalds/linux
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
gpio: add generic single-register fixed-direction GPIO driver
Add a simple, generic, single register fixed-direction GPIO driver. This is able to support a single register with a mixture of inputs and outputs. This is different from gpio-mmio and gpio-74xx-mmio: * gpio-mmio doesn't allow a fixed direction, it assumes there is always a direction register. * gpio-74xx-mmio only supports all-in or all-out setups * gpio-74xx-mmio is DT only, this needs to support legacy too * they don't double-read when getting the GPIO value, as required by some implementations that this driver supports * we need to always do 32-bit reads, which bgpio doesn't guarantee * the current output state may not be readable from the hardware register - reading may reflect input status but not output status. Signed-off-by: Russell King <[email protected]> Signed-off-by: Linus Walleij <[email protected]>
- Loading branch information
Showing
4 changed files
with
183 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,164 @@ | ||
/* | ||
* gpio-reg: single register individually fixed-direction GPIOs | ||
* | ||
* Copyright (C) 2016 Russell King | ||
* | ||
* This software is licensed under the terms of the GNU General Public | ||
* License version 2, as published by the Free Software Foundation, and | ||
* may be copied, distributed, and modified under those terms. | ||
*/ | ||
#include <linux/gpio/driver.h> | ||
#include <linux/gpio/gpio-reg.h> | ||
#include <linux/io.h> | ||
#include <linux/slab.h> | ||
#include <linux/spinlock.h> | ||
|
||
struct gpio_reg { | ||
struct gpio_chip gc; | ||
spinlock_t lock; | ||
u32 direction; | ||
u32 out; | ||
void __iomem *reg; | ||
}; | ||
|
||
#define to_gpio_reg(x) container_of(x, struct gpio_reg, gc) | ||
|
||
static int gpio_reg_get_direction(struct gpio_chip *gc, unsigned offset) | ||
{ | ||
struct gpio_reg *r = to_gpio_reg(gc); | ||
|
||
return r->direction & BIT(offset) ? 1 : 0; | ||
} | ||
|
||
static int gpio_reg_direction_output(struct gpio_chip *gc, unsigned offset, | ||
int value) | ||
{ | ||
struct gpio_reg *r = to_gpio_reg(gc); | ||
|
||
if (r->direction & BIT(offset)) | ||
return -ENOTSUPP; | ||
|
||
gc->set(gc, offset, value); | ||
return 0; | ||
} | ||
|
||
static int gpio_reg_direction_input(struct gpio_chip *gc, unsigned offset) | ||
{ | ||
struct gpio_reg *r = to_gpio_reg(gc); | ||
|
||
return r->direction & BIT(offset) ? 0 : -ENOTSUPP; | ||
} | ||
|
||
static void gpio_reg_set(struct gpio_chip *gc, unsigned offset, int value) | ||
{ | ||
struct gpio_reg *r = to_gpio_reg(gc); | ||
unsigned long flags; | ||
u32 val, mask = BIT(offset); | ||
|
||
spin_lock_irqsave(&r->lock, flags); | ||
val = r->out; | ||
if (value) | ||
val |= mask; | ||
else | ||
val &= ~mask; | ||
r->out = val; | ||
writel_relaxed(val, r->reg); | ||
spin_unlock_irqrestore(&r->lock, flags); | ||
} | ||
|
||
static int gpio_reg_get(struct gpio_chip *gc, unsigned offset) | ||
{ | ||
struct gpio_reg *r = to_gpio_reg(gc); | ||
u32 val, mask = BIT(offset); | ||
|
||
if (r->direction & mask) { | ||
/* | ||
* double-read the value, some registers latch after the | ||
* first read. | ||
*/ | ||
readl_relaxed(r->reg); | ||
val = readl_relaxed(r->reg); | ||
} else { | ||
val = r->out; | ||
} | ||
return !!(val & mask); | ||
} | ||
|
||
static void gpio_reg_set_multiple(struct gpio_chip *gc, unsigned long *mask, | ||
unsigned long *bits) | ||
{ | ||
struct gpio_reg *r = to_gpio_reg(gc); | ||
unsigned long flags; | ||
|
||
spin_lock_irqsave(&r->lock, flags); | ||
r->out = (r->out & ~*mask) | (*bits & *mask); | ||
writel_relaxed(r->out, r->reg); | ||
spin_unlock_irqrestore(&r->lock, flags); | ||
} | ||
|
||
/** | ||
* gpio_reg_init - add a fixed in/out register as gpio | ||
* @dev: optional struct device associated with this register | ||
* @base: start gpio number, or -1 to allocate | ||
* @num: number of GPIOs, maximum 32 | ||
* @label: GPIO chip label | ||
* @direction: bitmask of fixed direction, one per GPIO signal, 1 = in | ||
* @def_out: initial GPIO output value | ||
* @names: array of %num strings describing each GPIO signal | ||
* | ||
* Add a single-register GPIO device containing up to 32 GPIO signals, | ||
* where each GPIO has a fixed input or output configuration. Only | ||
* input GPIOs are assumed to be readable from the register, and only | ||
* then after a double-read. Output values are assumed not to be | ||
* readable. | ||
*/ | ||
struct gpio_chip *gpio_reg_init(struct device *dev, void __iomem *reg, | ||
int base, int num, const char *label, u32 direction, u32 def_out, | ||
const char *const *names) | ||
{ | ||
struct gpio_reg *r; | ||
int ret; | ||
|
||
if (dev) | ||
r = devm_kzalloc(dev, sizeof(*r), GFP_KERNEL); | ||
else | ||
r = kzalloc(sizeof(*r), GFP_KERNEL); | ||
|
||
if (!r) | ||
return ERR_PTR(-ENOMEM); | ||
|
||
spin_lock_init(&r->lock); | ||
|
||
r->gc.label = label; | ||
r->gc.get_direction = gpio_reg_get_direction; | ||
r->gc.direction_input = gpio_reg_direction_input; | ||
r->gc.direction_output = gpio_reg_direction_output; | ||
r->gc.set = gpio_reg_set; | ||
r->gc.get = gpio_reg_get; | ||
r->gc.set_multiple = gpio_reg_set_multiple; | ||
r->gc.base = base; | ||
r->gc.ngpio = num; | ||
r->gc.names = names; | ||
r->direction = direction; | ||
r->out = def_out; | ||
r->reg = reg; | ||
|
||
if (dev) | ||
ret = devm_gpiochip_add_data(dev, &r->gc, r); | ||
else | ||
ret = gpiochip_add_data(&r->gc, r); | ||
|
||
return ret ? ERR_PTR(ret) : &r->gc; | ||
} | ||
|
||
int gpio_reg_resume(struct gpio_chip *gc) | ||
{ | ||
struct gpio_reg *r = to_gpio_reg(gc); | ||
unsigned long flags; | ||
|
||
spin_lock_irqsave(&r->lock, flags); | ||
writel_relaxed(r->out, r->reg); | ||
spin_unlock_irqrestore(&r->lock, flags); | ||
|
||
return 0; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
#ifndef GPIO_REG_H | ||
#define GPIO_REG_H | ||
|
||
struct device; | ||
|
||
struct gpio_chip *gpio_reg_init(struct device *dev, void __iomem *reg, | ||
int base, int num, const char *label, u32 direction, u32 def_out, | ||
const char *const *names); | ||
|
||
int gpio_reg_resume(struct gpio_chip *gc); | ||
|
||
#endif |