![Linus Torvalds](/assets/img/avatar_default.png)
Here's the set of driver core patches for 3.19-rc1. They are dominated by the removal of the .owner field in platform drivers. They touch a lot of files, but they are "simple" changes, just removing a line in a structure. Other than that, a few minor driver core and debugfs changes. There are some ath9k patches coming in through this tree that have been acked by the wireless maintainers as they relied on the debugfs changes. Everything has been in linux-next for a while. Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> -----BEGIN PGP SIGNATURE----- Version: GnuPG v2 iEYEABECAAYFAlSOD20ACgkQMUfUDdst+ylLPACg2QrW1oHhdTMT9WI8jihlHVRM 53kAoLeteByQ3iVwWurwwseRPiWa8+MI =OVRS -----END PGP SIGNATURE----- Merge tag 'driver-core-3.19-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/driver-core Pull driver core update from Greg KH: "Here's the set of driver core patches for 3.19-rc1. They are dominated by the removal of the .owner field in platform drivers. They touch a lot of files, but they are "simple" changes, just removing a line in a structure. Other than that, a few minor driver core and debugfs changes. There are some ath9k patches coming in through this tree that have been acked by the wireless maintainers as they relied on the debugfs changes. Everything has been in linux-next for a while" * tag 'driver-core-3.19-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/driver-core: (324 commits) Revert "ath: ath9k: use debugfs_create_devm_seqfile() helper for seq_file entries" fs: debugfs: add forward declaration for struct device type firmware class: Deletion of an unnecessary check before the function call "vunmap" firmware loader: fix hung task warning dump devcoredump: provide a one-way disable function device: Add dev_<level>_once variants ath: ath9k: use debugfs_create_devm_seqfile() helper for seq_file entries ath: use seq_file api for ath9k debugfs files debugfs: add helper function to create device related seq_file drivers/base: cacheinfo: remove noisy error boot message Revert "core: platform: add warning if driver has no owner" drivers: base: support cpu cache information interface to userspace via sysfs drivers: base: add cpu_device_create to support per-cpu devices topology: replace custom attribute macros with standard DEVICE_ATTR* cpumask: factor out show_cpumap into separate helper function driver core: Fix unbalanced device reference in drivers_probe driver core: fix race with userland in device_add() sysfs/kernfs: make read requests on pre-alloc files use the buffer. sysfs/kernfs: allow attributes to request write buffer be pre-allocated. fs: sysfs: return EGBIG on write if offset is larger than file size ...
728 lines
18 KiB
C
728 lines
18 KiB
C
/*
|
|
* SPEAr platform PLGPIO driver
|
|
*
|
|
* Copyright (C) 2012 ST Microelectronics
|
|
* Viresh Kumar <viresh.kumar@linaro.org>
|
|
*
|
|
* This file is licensed under the terms of the GNU General Public
|
|
* License version 2. This program is licensed "as is" without any
|
|
* warranty of any kind, whether express or implied.
|
|
*/
|
|
|
|
#include <linux/clk.h>
|
|
#include <linux/err.h>
|
|
#include <linux/gpio/driver.h>
|
|
#include <linux/io.h>
|
|
#include <linux/module.h>
|
|
#include <linux/of.h>
|
|
#include <linux/of_platform.h>
|
|
#include <linux/pinctrl/consumer.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/pm.h>
|
|
#include <linux/spinlock.h>
|
|
|
|
#define MAX_GPIO_PER_REG 32
|
|
#define PIN_OFFSET(pin) (pin % MAX_GPIO_PER_REG)
|
|
#define REG_OFFSET(base, reg, pin) (base + reg + (pin / MAX_GPIO_PER_REG) \
|
|
* sizeof(int *))
|
|
|
|
/*
|
|
* plgpio pins in all machines are not one to one mapped, bitwise with registers
|
|
* bits. These set of macros define register masks for which below functions
|
|
* (pin_to_offset and offset_to_pin) are required to be called.
|
|
*/
|
|
#define PTO_ENB_REG 0x001
|
|
#define PTO_WDATA_REG 0x002
|
|
#define PTO_DIR_REG 0x004
|
|
#define PTO_IE_REG 0x008
|
|
#define PTO_RDATA_REG 0x010
|
|
#define PTO_MIS_REG 0x020
|
|
|
|
struct plgpio_regs {
|
|
u32 enb; /* enable register */
|
|
u32 wdata; /* write data register */
|
|
u32 dir; /* direction set register */
|
|
u32 rdata; /* read data register */
|
|
u32 ie; /* interrupt enable register */
|
|
u32 mis; /* mask interrupt status register */
|
|
u32 eit; /* edge interrupt type */
|
|
};
|
|
|
|
/*
|
|
* struct plgpio: plgpio driver specific structure
|
|
*
|
|
* lock: lock for guarding gpio registers
|
|
* base: base address of plgpio block
|
|
* chip: gpio framework specific chip information structure
|
|
* p2o: function ptr for pin to offset conversion. This is required only for
|
|
* machines where mapping b/w pin and offset is not 1-to-1.
|
|
* o2p: function ptr for offset to pin conversion. This is required only for
|
|
* machines where mapping b/w pin and offset is not 1-to-1.
|
|
* p2o_regs: mask of registers for which p2o and o2p are applicable
|
|
* regs: register offsets
|
|
* csave_regs: context save registers for standby/sleep/hibernate cases
|
|
*/
|
|
struct plgpio {
|
|
spinlock_t lock;
|
|
void __iomem *base;
|
|
struct clk *clk;
|
|
struct gpio_chip chip;
|
|
int (*p2o)(int pin); /* pin_to_offset */
|
|
int (*o2p)(int offset); /* offset_to_pin */
|
|
u32 p2o_regs;
|
|
struct plgpio_regs regs;
|
|
#ifdef CONFIG_PM_SLEEP
|
|
struct plgpio_regs *csave_regs;
|
|
#endif
|
|
};
|
|
|
|
/* register manipulation inline functions */
|
|
static inline u32 is_plgpio_set(void __iomem *base, u32 pin, u32 reg)
|
|
{
|
|
u32 offset = PIN_OFFSET(pin);
|
|
void __iomem *reg_off = REG_OFFSET(base, reg, pin);
|
|
u32 val = readl_relaxed(reg_off);
|
|
|
|
return !!(val & (1 << offset));
|
|
}
|
|
|
|
static inline void plgpio_reg_set(void __iomem *base, u32 pin, u32 reg)
|
|
{
|
|
u32 offset = PIN_OFFSET(pin);
|
|
void __iomem *reg_off = REG_OFFSET(base, reg, pin);
|
|
u32 val = readl_relaxed(reg_off);
|
|
|
|
writel_relaxed(val | (1 << offset), reg_off);
|
|
}
|
|
|
|
static inline void plgpio_reg_reset(void __iomem *base, u32 pin, u32 reg)
|
|
{
|
|
u32 offset = PIN_OFFSET(pin);
|
|
void __iomem *reg_off = REG_OFFSET(base, reg, pin);
|
|
u32 val = readl_relaxed(reg_off);
|
|
|
|
writel_relaxed(val & ~(1 << offset), reg_off);
|
|
}
|
|
|
|
/* gpio framework specific routines */
|
|
static int plgpio_direction_input(struct gpio_chip *chip, unsigned offset)
|
|
{
|
|
struct plgpio *plgpio = container_of(chip, struct plgpio, chip);
|
|
unsigned long flags;
|
|
|
|
/* get correct offset for "offset" pin */
|
|
if (plgpio->p2o && (plgpio->p2o_regs & PTO_DIR_REG)) {
|
|
offset = plgpio->p2o(offset);
|
|
if (offset == -1)
|
|
return -EINVAL;
|
|
}
|
|
|
|
spin_lock_irqsave(&plgpio->lock, flags);
|
|
plgpio_reg_set(plgpio->base, offset, plgpio->regs.dir);
|
|
spin_unlock_irqrestore(&plgpio->lock, flags);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int plgpio_direction_output(struct gpio_chip *chip, unsigned offset,
|
|
int value)
|
|
{
|
|
struct plgpio *plgpio = container_of(chip, struct plgpio, chip);
|
|
unsigned long flags;
|
|
unsigned dir_offset = offset, wdata_offset = offset, tmp;
|
|
|
|
/* get correct offset for "offset" pin */
|
|
if (plgpio->p2o && (plgpio->p2o_regs & (PTO_DIR_REG | PTO_WDATA_REG))) {
|
|
tmp = plgpio->p2o(offset);
|
|
if (tmp == -1)
|
|
return -EINVAL;
|
|
|
|
if (plgpio->p2o_regs & PTO_DIR_REG)
|
|
dir_offset = tmp;
|
|
if (plgpio->p2o_regs & PTO_WDATA_REG)
|
|
wdata_offset = tmp;
|
|
}
|
|
|
|
spin_lock_irqsave(&plgpio->lock, flags);
|
|
if (value)
|
|
plgpio_reg_set(plgpio->base, wdata_offset,
|
|
plgpio->regs.wdata);
|
|
else
|
|
plgpio_reg_reset(plgpio->base, wdata_offset,
|
|
plgpio->regs.wdata);
|
|
|
|
plgpio_reg_reset(plgpio->base, dir_offset, plgpio->regs.dir);
|
|
spin_unlock_irqrestore(&plgpio->lock, flags);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int plgpio_get_value(struct gpio_chip *chip, unsigned offset)
|
|
{
|
|
struct plgpio *plgpio = container_of(chip, struct plgpio, chip);
|
|
|
|
if (offset >= chip->ngpio)
|
|
return -EINVAL;
|
|
|
|
/* get correct offset for "offset" pin */
|
|
if (plgpio->p2o && (plgpio->p2o_regs & PTO_RDATA_REG)) {
|
|
offset = plgpio->p2o(offset);
|
|
if (offset == -1)
|
|
return -EINVAL;
|
|
}
|
|
|
|
return is_plgpio_set(plgpio->base, offset, plgpio->regs.rdata);
|
|
}
|
|
|
|
static void plgpio_set_value(struct gpio_chip *chip, unsigned offset, int value)
|
|
{
|
|
struct plgpio *plgpio = container_of(chip, struct plgpio, chip);
|
|
|
|
if (offset >= chip->ngpio)
|
|
return;
|
|
|
|
/* get correct offset for "offset" pin */
|
|
if (plgpio->p2o && (plgpio->p2o_regs & PTO_WDATA_REG)) {
|
|
offset = plgpio->p2o(offset);
|
|
if (offset == -1)
|
|
return;
|
|
}
|
|
|
|
if (value)
|
|
plgpio_reg_set(plgpio->base, offset, plgpio->regs.wdata);
|
|
else
|
|
plgpio_reg_reset(plgpio->base, offset, plgpio->regs.wdata);
|
|
}
|
|
|
|
static int plgpio_request(struct gpio_chip *chip, unsigned offset)
|
|
{
|
|
struct plgpio *plgpio = container_of(chip, struct plgpio, chip);
|
|
int gpio = chip->base + offset;
|
|
unsigned long flags;
|
|
int ret = 0;
|
|
|
|
if (offset >= chip->ngpio)
|
|
return -EINVAL;
|
|
|
|
ret = pinctrl_request_gpio(gpio);
|
|
if (ret)
|
|
return ret;
|
|
|
|
if (!IS_ERR(plgpio->clk)) {
|
|
ret = clk_enable(plgpio->clk);
|
|
if (ret)
|
|
goto err0;
|
|
}
|
|
|
|
if (plgpio->regs.enb == -1)
|
|
return 0;
|
|
|
|
/*
|
|
* put gpio in IN mode before enabling it. This make enabling gpio safe
|
|
*/
|
|
ret = plgpio_direction_input(chip, offset);
|
|
if (ret)
|
|
goto err1;
|
|
|
|
/* get correct offset for "offset" pin */
|
|
if (plgpio->p2o && (plgpio->p2o_regs & PTO_ENB_REG)) {
|
|
offset = plgpio->p2o(offset);
|
|
if (offset == -1) {
|
|
ret = -EINVAL;
|
|
goto err1;
|
|
}
|
|
}
|
|
|
|
spin_lock_irqsave(&plgpio->lock, flags);
|
|
plgpio_reg_set(plgpio->base, offset, plgpio->regs.enb);
|
|
spin_unlock_irqrestore(&plgpio->lock, flags);
|
|
return 0;
|
|
|
|
err1:
|
|
if (!IS_ERR(plgpio->clk))
|
|
clk_disable(plgpio->clk);
|
|
err0:
|
|
pinctrl_free_gpio(gpio);
|
|
return ret;
|
|
}
|
|
|
|
static void plgpio_free(struct gpio_chip *chip, unsigned offset)
|
|
{
|
|
struct plgpio *plgpio = container_of(chip, struct plgpio, chip);
|
|
int gpio = chip->base + offset;
|
|
unsigned long flags;
|
|
|
|
if (offset >= chip->ngpio)
|
|
return;
|
|
|
|
if (plgpio->regs.enb == -1)
|
|
goto disable_clk;
|
|
|
|
/* get correct offset for "offset" pin */
|
|
if (plgpio->p2o && (plgpio->p2o_regs & PTO_ENB_REG)) {
|
|
offset = plgpio->p2o(offset);
|
|
if (offset == -1)
|
|
return;
|
|
}
|
|
|
|
spin_lock_irqsave(&plgpio->lock, flags);
|
|
plgpio_reg_reset(plgpio->base, offset, plgpio->regs.enb);
|
|
spin_unlock_irqrestore(&plgpio->lock, flags);
|
|
|
|
disable_clk:
|
|
if (!IS_ERR(plgpio->clk))
|
|
clk_disable(plgpio->clk);
|
|
|
|
pinctrl_free_gpio(gpio);
|
|
}
|
|
|
|
/* PLGPIO IRQ */
|
|
static void plgpio_irq_disable(struct irq_data *d)
|
|
{
|
|
struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
|
|
struct plgpio *plgpio = container_of(gc, struct plgpio, chip);
|
|
int offset = d->hwirq;
|
|
unsigned long flags;
|
|
|
|
/* get correct offset for "offset" pin */
|
|
if (plgpio->p2o && (plgpio->p2o_regs & PTO_IE_REG)) {
|
|
offset = plgpio->p2o(offset);
|
|
if (offset == -1)
|
|
return;
|
|
}
|
|
|
|
spin_lock_irqsave(&plgpio->lock, flags);
|
|
plgpio_reg_set(plgpio->base, offset, plgpio->regs.ie);
|
|
spin_unlock_irqrestore(&plgpio->lock, flags);
|
|
}
|
|
|
|
static void plgpio_irq_enable(struct irq_data *d)
|
|
{
|
|
struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
|
|
struct plgpio *plgpio = container_of(gc, struct plgpio, chip);
|
|
int offset = d->hwirq;
|
|
unsigned long flags;
|
|
|
|
/* get correct offset for "offset" pin */
|
|
if (plgpio->p2o && (plgpio->p2o_regs & PTO_IE_REG)) {
|
|
offset = plgpio->p2o(offset);
|
|
if (offset == -1)
|
|
return;
|
|
}
|
|
|
|
spin_lock_irqsave(&plgpio->lock, flags);
|
|
plgpio_reg_reset(plgpio->base, offset, plgpio->regs.ie);
|
|
spin_unlock_irqrestore(&plgpio->lock, flags);
|
|
}
|
|
|
|
static int plgpio_irq_set_type(struct irq_data *d, unsigned trigger)
|
|
{
|
|
struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
|
|
struct plgpio *plgpio = container_of(gc, struct plgpio, chip);
|
|
int offset = d->hwirq;
|
|
void __iomem *reg_off;
|
|
unsigned int supported_type = 0, val;
|
|
|
|
if (offset >= plgpio->chip.ngpio)
|
|
return -EINVAL;
|
|
|
|
if (plgpio->regs.eit == -1)
|
|
supported_type = IRQ_TYPE_LEVEL_HIGH;
|
|
else
|
|
supported_type = IRQ_TYPE_EDGE_RISING | IRQ_TYPE_EDGE_FALLING;
|
|
|
|
if (!(trigger & supported_type))
|
|
return -EINVAL;
|
|
|
|
if (plgpio->regs.eit == -1)
|
|
return 0;
|
|
|
|
reg_off = REG_OFFSET(plgpio->base, plgpio->regs.eit, offset);
|
|
val = readl_relaxed(reg_off);
|
|
|
|
offset = PIN_OFFSET(offset);
|
|
if (trigger & IRQ_TYPE_EDGE_RISING)
|
|
writel_relaxed(val | (1 << offset), reg_off);
|
|
else
|
|
writel_relaxed(val & ~(1 << offset), reg_off);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct irq_chip plgpio_irqchip = {
|
|
.name = "PLGPIO",
|
|
.irq_enable = plgpio_irq_enable,
|
|
.irq_disable = plgpio_irq_disable,
|
|
.irq_set_type = plgpio_irq_set_type,
|
|
};
|
|
|
|
static void plgpio_irq_handler(unsigned irq, struct irq_desc *desc)
|
|
{
|
|
struct gpio_chip *gc = irq_desc_get_handler_data(desc);
|
|
struct plgpio *plgpio = container_of(gc, struct plgpio, chip);
|
|
struct irq_chip *irqchip = irq_desc_get_chip(desc);
|
|
int regs_count, count, pin, offset, i = 0;
|
|
unsigned long pending;
|
|
|
|
count = plgpio->chip.ngpio;
|
|
regs_count = DIV_ROUND_UP(count, MAX_GPIO_PER_REG);
|
|
|
|
chained_irq_enter(irqchip, desc);
|
|
/* check all plgpio MIS registers for a possible interrupt */
|
|
for (; i < regs_count; i++) {
|
|
pending = readl_relaxed(plgpio->base + plgpio->regs.mis +
|
|
i * sizeof(int *));
|
|
if (!pending)
|
|
continue;
|
|
|
|
/* clear interrupts */
|
|
writel_relaxed(~pending, plgpio->base + plgpio->regs.mis +
|
|
i * sizeof(int *));
|
|
/*
|
|
* clear extra bits in last register having gpios < MAX/REG
|
|
* ex: Suppose there are max 102 plgpios. then last register
|
|
* must have only (102 - MAX_GPIO_PER_REG * 3) = 6 relevant bits
|
|
* so, we must not take other 28 bits into consideration for
|
|
* checking interrupt. so clear those bits.
|
|
*/
|
|
count = count - i * MAX_GPIO_PER_REG;
|
|
if (count < MAX_GPIO_PER_REG)
|
|
pending &= (1 << count) - 1;
|
|
|
|
for_each_set_bit(offset, &pending, MAX_GPIO_PER_REG) {
|
|
/* get correct pin for "offset" */
|
|
if (plgpio->o2p && (plgpio->p2o_regs & PTO_MIS_REG)) {
|
|
pin = plgpio->o2p(offset);
|
|
if (pin == -1)
|
|
continue;
|
|
} else
|
|
pin = offset;
|
|
|
|
/* get correct irq line number */
|
|
pin = i * MAX_GPIO_PER_REG + pin;
|
|
generic_handle_irq(
|
|
irq_find_mapping(gc->irqdomain, pin));
|
|
}
|
|
}
|
|
chained_irq_exit(irqchip, desc);
|
|
}
|
|
|
|
/*
|
|
* pin to offset and offset to pin converter functions
|
|
*
|
|
* In spear310 there is inconsistency among bit positions in plgpio regiseters,
|
|
* for different plgpio pins. For example: for pin 27, bit offset is 23, pin
|
|
* 28-33 are not supported, pin 95 has offset bit 95, bit 100 has offset bit 1
|
|
*/
|
|
static int spear310_p2o(int pin)
|
|
{
|
|
int offset = pin;
|
|
|
|
if (pin <= 27)
|
|
offset += 4;
|
|
else if (pin <= 33)
|
|
offset = -1;
|
|
else if (pin <= 97)
|
|
offset -= 2;
|
|
else if (pin <= 101)
|
|
offset = 101 - pin;
|
|
else
|
|
offset = -1;
|
|
|
|
return offset;
|
|
}
|
|
|
|
static int spear310_o2p(int offset)
|
|
{
|
|
if (offset <= 3)
|
|
return 101 - offset;
|
|
else if (offset <= 31)
|
|
return offset - 4;
|
|
else
|
|
return offset + 2;
|
|
}
|
|
|
|
static int plgpio_probe_dt(struct platform_device *pdev, struct plgpio *plgpio)
|
|
{
|
|
struct device_node *np = pdev->dev.of_node;
|
|
int ret = -EINVAL;
|
|
u32 val;
|
|
|
|
if (of_machine_is_compatible("st,spear310")) {
|
|
plgpio->p2o = spear310_p2o;
|
|
plgpio->o2p = spear310_o2p;
|
|
plgpio->p2o_regs = PTO_WDATA_REG | PTO_DIR_REG | PTO_IE_REG |
|
|
PTO_RDATA_REG | PTO_MIS_REG;
|
|
}
|
|
|
|
if (!of_property_read_u32(np, "st-plgpio,ngpio", &val)) {
|
|
plgpio->chip.ngpio = val;
|
|
} else {
|
|
dev_err(&pdev->dev, "DT: Invalid ngpio field\n");
|
|
goto end;
|
|
}
|
|
|
|
if (!of_property_read_u32(np, "st-plgpio,enb-reg", &val))
|
|
plgpio->regs.enb = val;
|
|
else
|
|
plgpio->regs.enb = -1;
|
|
|
|
if (!of_property_read_u32(np, "st-plgpio,wdata-reg", &val)) {
|
|
plgpio->regs.wdata = val;
|
|
} else {
|
|
dev_err(&pdev->dev, "DT: Invalid wdata reg\n");
|
|
goto end;
|
|
}
|
|
|
|
if (!of_property_read_u32(np, "st-plgpio,dir-reg", &val)) {
|
|
plgpio->regs.dir = val;
|
|
} else {
|
|
dev_err(&pdev->dev, "DT: Invalid dir reg\n");
|
|
goto end;
|
|
}
|
|
|
|
if (!of_property_read_u32(np, "st-plgpio,ie-reg", &val)) {
|
|
plgpio->regs.ie = val;
|
|
} else {
|
|
dev_err(&pdev->dev, "DT: Invalid ie reg\n");
|
|
goto end;
|
|
}
|
|
|
|
if (!of_property_read_u32(np, "st-plgpio,rdata-reg", &val)) {
|
|
plgpio->regs.rdata = val;
|
|
} else {
|
|
dev_err(&pdev->dev, "DT: Invalid rdata reg\n");
|
|
goto end;
|
|
}
|
|
|
|
if (!of_property_read_u32(np, "st-plgpio,mis-reg", &val)) {
|
|
plgpio->regs.mis = val;
|
|
} else {
|
|
dev_err(&pdev->dev, "DT: Invalid mis reg\n");
|
|
goto end;
|
|
}
|
|
|
|
if (!of_property_read_u32(np, "st-plgpio,eit-reg", &val))
|
|
plgpio->regs.eit = val;
|
|
else
|
|
plgpio->regs.eit = -1;
|
|
|
|
return 0;
|
|
|
|
end:
|
|
return ret;
|
|
}
|
|
static int plgpio_probe(struct platform_device *pdev)
|
|
{
|
|
struct plgpio *plgpio;
|
|
struct resource *res;
|
|
int ret, irq;
|
|
|
|
plgpio = devm_kzalloc(&pdev->dev, sizeof(*plgpio), GFP_KERNEL);
|
|
if (!plgpio) {
|
|
dev_err(&pdev->dev, "memory allocation fail\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
|
plgpio->base = devm_ioremap_resource(&pdev->dev, res);
|
|
if (IS_ERR(plgpio->base))
|
|
return PTR_ERR(plgpio->base);
|
|
|
|
ret = plgpio_probe_dt(pdev, plgpio);
|
|
if (ret) {
|
|
dev_err(&pdev->dev, "DT probe failed\n");
|
|
return ret;
|
|
}
|
|
|
|
plgpio->clk = devm_clk_get(&pdev->dev, NULL);
|
|
if (IS_ERR(plgpio->clk))
|
|
dev_warn(&pdev->dev, "clk_get() failed, work without it\n");
|
|
|
|
#ifdef CONFIG_PM_SLEEP
|
|
plgpio->csave_regs = devm_kzalloc(&pdev->dev,
|
|
sizeof(*plgpio->csave_regs) *
|
|
DIV_ROUND_UP(plgpio->chip.ngpio, MAX_GPIO_PER_REG),
|
|
GFP_KERNEL);
|
|
if (!plgpio->csave_regs) {
|
|
dev_err(&pdev->dev, "csave registers memory allocation fail\n");
|
|
return -ENOMEM;
|
|
}
|
|
#endif
|
|
|
|
platform_set_drvdata(pdev, plgpio);
|
|
spin_lock_init(&plgpio->lock);
|
|
|
|
plgpio->chip.base = -1;
|
|
plgpio->chip.request = plgpio_request;
|
|
plgpio->chip.free = plgpio_free;
|
|
plgpio->chip.direction_input = plgpio_direction_input;
|
|
plgpio->chip.direction_output = plgpio_direction_output;
|
|
plgpio->chip.get = plgpio_get_value;
|
|
plgpio->chip.set = plgpio_set_value;
|
|
plgpio->chip.label = dev_name(&pdev->dev);
|
|
plgpio->chip.dev = &pdev->dev;
|
|
plgpio->chip.owner = THIS_MODULE;
|
|
plgpio->chip.of_node = pdev->dev.of_node;
|
|
|
|
if (!IS_ERR(plgpio->clk)) {
|
|
ret = clk_prepare(plgpio->clk);
|
|
if (ret) {
|
|
dev_err(&pdev->dev, "clk prepare failed\n");
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
ret = gpiochip_add(&plgpio->chip);
|
|
if (ret) {
|
|
dev_err(&pdev->dev, "unable to add gpio chip\n");
|
|
goto unprepare_clk;
|
|
}
|
|
|
|
irq = platform_get_irq(pdev, 0);
|
|
if (irq < 0) {
|
|
dev_info(&pdev->dev, "PLGPIO registered without IRQs\n");
|
|
return 0;
|
|
}
|
|
|
|
ret = gpiochip_irqchip_add(&plgpio->chip,
|
|
&plgpio_irqchip,
|
|
0,
|
|
handle_simple_irq,
|
|
IRQ_TYPE_NONE);
|
|
if (ret) {
|
|
dev_err(&pdev->dev, "failed to add irqchip to gpiochip\n");
|
|
goto remove_gpiochip;
|
|
}
|
|
|
|
gpiochip_set_chained_irqchip(&plgpio->chip,
|
|
&plgpio_irqchip,
|
|
irq,
|
|
plgpio_irq_handler);
|
|
|
|
dev_info(&pdev->dev, "PLGPIO registered with IRQs\n");
|
|
|
|
return 0;
|
|
|
|
remove_gpiochip:
|
|
dev_info(&pdev->dev, "Remove gpiochip\n");
|
|
gpiochip_remove(&plgpio->chip);
|
|
unprepare_clk:
|
|
if (!IS_ERR(plgpio->clk))
|
|
clk_unprepare(plgpio->clk);
|
|
|
|
return ret;
|
|
}
|
|
|
|
#ifdef CONFIG_PM_SLEEP
|
|
static int plgpio_suspend(struct device *dev)
|
|
{
|
|
struct plgpio *plgpio = dev_get_drvdata(dev);
|
|
int i, reg_count = DIV_ROUND_UP(plgpio->chip.ngpio, MAX_GPIO_PER_REG);
|
|
void __iomem *off;
|
|
|
|
for (i = 0; i < reg_count; i++) {
|
|
off = plgpio->base + i * sizeof(int *);
|
|
|
|
if (plgpio->regs.enb != -1)
|
|
plgpio->csave_regs[i].enb =
|
|
readl_relaxed(plgpio->regs.enb + off);
|
|
if (plgpio->regs.eit != -1)
|
|
plgpio->csave_regs[i].eit =
|
|
readl_relaxed(plgpio->regs.eit + off);
|
|
plgpio->csave_regs[i].wdata = readl_relaxed(plgpio->regs.wdata +
|
|
off);
|
|
plgpio->csave_regs[i].dir = readl_relaxed(plgpio->regs.dir +
|
|
off);
|
|
plgpio->csave_regs[i].ie = readl_relaxed(plgpio->regs.ie + off);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* This is used to correct the values in end registers. End registers contain
|
|
* extra bits that might be used for other purpose in platform. So, we shouldn't
|
|
* overwrite these bits. This macro, reads given register again, preserves other
|
|
* bit values (non-plgpio bits), and retain captured value (plgpio bits).
|
|
*/
|
|
#define plgpio_prepare_reg(__reg, _off, _mask, _tmp) \
|
|
{ \
|
|
_tmp = readl_relaxed(plgpio->regs.__reg + _off); \
|
|
_tmp &= ~_mask; \
|
|
plgpio->csave_regs[i].__reg = \
|
|
_tmp | (plgpio->csave_regs[i].__reg & _mask); \
|
|
}
|
|
|
|
static int plgpio_resume(struct device *dev)
|
|
{
|
|
struct plgpio *plgpio = dev_get_drvdata(dev);
|
|
int i, reg_count = DIV_ROUND_UP(plgpio->chip.ngpio, MAX_GPIO_PER_REG);
|
|
void __iomem *off;
|
|
u32 mask, tmp;
|
|
|
|
for (i = 0; i < reg_count; i++) {
|
|
off = plgpio->base + i * sizeof(int *);
|
|
|
|
if (i == reg_count - 1) {
|
|
mask = (1 << (plgpio->chip.ngpio - i *
|
|
MAX_GPIO_PER_REG)) - 1;
|
|
|
|
if (plgpio->regs.enb != -1)
|
|
plgpio_prepare_reg(enb, off, mask, tmp);
|
|
|
|
if (plgpio->regs.eit != -1)
|
|
plgpio_prepare_reg(eit, off, mask, tmp);
|
|
|
|
plgpio_prepare_reg(wdata, off, mask, tmp);
|
|
plgpio_prepare_reg(dir, off, mask, tmp);
|
|
plgpio_prepare_reg(ie, off, mask, tmp);
|
|
}
|
|
|
|
writel_relaxed(plgpio->csave_regs[i].wdata, plgpio->regs.wdata +
|
|
off);
|
|
writel_relaxed(plgpio->csave_regs[i].dir, plgpio->regs.dir +
|
|
off);
|
|
|
|
if (plgpio->regs.eit != -1)
|
|
writel_relaxed(plgpio->csave_regs[i].eit,
|
|
plgpio->regs.eit + off);
|
|
|
|
writel_relaxed(plgpio->csave_regs[i].ie, plgpio->regs.ie + off);
|
|
|
|
if (plgpio->regs.enb != -1)
|
|
writel_relaxed(plgpio->csave_regs[i].enb,
|
|
plgpio->regs.enb + off);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
static SIMPLE_DEV_PM_OPS(plgpio_dev_pm_ops, plgpio_suspend, plgpio_resume);
|
|
|
|
static const struct of_device_id plgpio_of_match[] = {
|
|
{ .compatible = "st,spear-plgpio" },
|
|
{}
|
|
};
|
|
MODULE_DEVICE_TABLE(of, plgpio_of_match);
|
|
|
|
static struct platform_driver plgpio_driver = {
|
|
.probe = plgpio_probe,
|
|
.driver = {
|
|
.name = "spear-plgpio",
|
|
.pm = &plgpio_dev_pm_ops,
|
|
.of_match_table = plgpio_of_match,
|
|
},
|
|
};
|
|
|
|
static int __init plgpio_init(void)
|
|
{
|
|
return platform_driver_register(&plgpio_driver);
|
|
}
|
|
subsys_initcall(plgpio_init);
|
|
|
|
MODULE_AUTHOR("Viresh Kumar <viresh.kumar@linaro.org>");
|
|
MODULE_DESCRIPTION("STMicroelectronics SPEAr PLGPIO driver");
|
|
MODULE_LICENSE("GPL");
|