blob: 07dd8fdd3137cb43554c03be2553a91cbda5b56a [file] [log] [blame]
/*
* Copyright (c) 2011 Jamie Iles
*
* 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.
*
* All enquiries to support@picochip.com
*/
#include <linux/init.h>
#include <linux/io.h>
#include <linux/ioport.h>
#include <linux/irq.h>
#include <linux/irqdomain.h>
#include <linux/module.h>
#include <linux/basic_mmio_gpio.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/platform_device.h>
#include <linux/interrupt.h>
#define INT_EN_REG_OFFS 0x30
#define INT_MASK_REG_OFFS 0x34
#define INT_TYPE_REG_OFFS 0x38
#define INT_POLARITY_REG_OFFS 0x3c
#define INT_STATUS_REG_OFFS 0x40
#define EOI_REG_OFFS 0x4c
#define EXT_PORT_OFFS 0x50
#define DW_MAX_BANK 4
struct dwapb_gpio;
struct dwapb_gpio_bank {
struct bgpio_chip bgc;
bool is_registered;
struct dwapb_gpio *gpio;
struct irq_domain *domain;
};
struct dwapb_gpio {
struct device_node *of_node;
struct device *dev;
void __iomem *regs;
struct dwapb_gpio_bank *banks;
unsigned int nr_banks;
struct irq_chip_generic *irq_gc;
unsigned long toggle_edge;
#ifdef CONFIG_PM_SLEEP
u32 dr[DW_MAX_BANK];
u32 ddr[DW_MAX_BANK];
u32 int_type;
u32 int_en;
u32 int_polarity;
#endif
};
static unsigned int dwapb_gpio_nr_banks(struct device_node *of_node)
{
unsigned int nr_banks = 0;
struct device_node *np;
for_each_child_of_node(of_node, np)
++nr_banks;
return nr_banks;
}
static int dwapb_gpio_to_irq(struct gpio_chip *gc, unsigned offset)
{
struct dwapb_gpio_bank *bank;
bank = container_of((struct bgpio_chip *)gc, struct dwapb_gpio_bank, bgc);
return irq_find_mapping(bank->domain, offset);
}
static void dwapb_toggle_trigger(struct dwapb_gpio *gpio, unsigned int offs)
{
u32 v = readl(gpio->regs + INT_POLARITY_REG_OFFS);
u32 in = readl(gpio->regs + EXT_PORT_OFFS);
if (in & BIT(offs))
v &= ~BIT(offs);
else
v |= BIT(offs);
writel(v, gpio->regs + INT_POLARITY_REG_OFFS);
}
static struct dwapb_gpio_bank* dwapb_irq_get_bank(struct dwapb_gpio *gpio, u32 irq)
{
return &gpio->banks[0];
}
static void dwapb_irq_handler(u32 irq, struct irq_desc *desc)
{
struct dwapb_gpio *gpio;
struct dwapb_gpio_bank *bank;
u32 irq_status;
gpio = irq_get_handler_data(irq);
bank = dwapb_irq_get_bank(gpio, irq);
irq_status = readl(gpio->regs + INT_STATUS_REG_OFFS);
while (irq_status) {
int irqoffset = fls(irq_status) - 1;
int irq = irq_find_mapping(bank->domain, irqoffset);
generic_handle_irq(irq);
irq_status &= ~(1 << irqoffset);
if (gpio->toggle_edge & BIT(irqoffset))
dwapb_toggle_trigger(gpio, irqoffset);
}
}
static void dwapb_irq_enable(struct irq_data *d)
{
struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d);
struct dwapb_gpio *gpio = gc->private;
u32 val = readl(gpio->regs + INT_EN_REG_OFFS);
val |= 1 << d->hwirq;
writel(val, gpio->regs + INT_EN_REG_OFFS);
}
static void dwapb_irq_disable(struct irq_data *d)
{
struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d);
struct dwapb_gpio *gpio = gc->private;
u32 val = readl(gpio->regs + INT_EN_REG_OFFS);
val &= ~(1 << d->hwirq);
writel(val, gpio->regs + INT_EN_REG_OFFS);
}
static int dwapb_irq_set_type(struct irq_data *d, u32 type)
{
struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d);
struct dwapb_gpio *gpio = gc->private;
int bit = d->hwirq;
unsigned long level, polarity;
if (type & ~(IRQ_TYPE_EDGE_RISING | IRQ_TYPE_EDGE_FALLING |
IRQ_TYPE_LEVEL_HIGH | IRQ_TYPE_LEVEL_LOW))
return -EINVAL;
level = readl(gpio->regs + INT_TYPE_REG_OFFS);
polarity = readl(gpio->regs + INT_POLARITY_REG_OFFS);
gpio->toggle_edge &= ~BIT(bit);
if (type == IRQ_TYPE_EDGE_BOTH) {
gpio->toggle_edge |= BIT(bit);
level |= (1 << bit);
dwapb_toggle_trigger(gpio, bit);
writel(level, gpio->regs + INT_TYPE_REG_OFFS);
return 0;
} else if (type == IRQ_TYPE_EDGE_RISING) {
level |= (1 << bit);
polarity |= (1 << bit);
} else if (type == IRQ_TYPE_EDGE_FALLING) {
level |= (1 << bit);
polarity &= ~(1 << bit);
} else if (type == IRQ_TYPE_LEVEL_HIGH) {
level &= ~(1 << bit);
polarity |= (1 << bit);
} else if (type == IRQ_TYPE_LEVEL_LOW) {
level &= ~(1 << bit);
polarity &= ~(1 << bit);
} else {
return -EINVAL;
}
writel(level, gpio->regs + INT_TYPE_REG_OFFS);
writel(polarity, gpio->regs + INT_POLARITY_REG_OFFS);
return 0;
}
static int dwapb_create_irqchip(struct dwapb_gpio *gpio,
struct dwapb_gpio_bank *bank,
unsigned int irq_base)
{
struct irq_chip_type *ct;
gpio->irq_gc = irq_alloc_generic_chip("gpio-dwapb", 1, irq_base,
gpio->regs, handle_level_irq);
if (!gpio->irq_gc)
return -EIO;
bank->domain->of_node = of_node_get(bank->bgc.gc.of_node);
gpio->irq_gc->private = gpio;
ct = gpio->irq_gc->chip_types;
ct->chip.irq_ack = irq_gc_ack_set_bit;
ct->chip.irq_mask = irq_gc_mask_set_bit;
ct->chip.irq_unmask = irq_gc_mask_clr_bit;
ct->chip.irq_set_type = dwapb_irq_set_type;
ct->chip.irq_enable = dwapb_irq_enable;
ct->chip.irq_disable = dwapb_irq_disable;
ct->regs.ack = EOI_REG_OFFS;
ct->regs.mask = INT_MASK_REG_OFFS;
irq_setup_generic_chip(gpio->irq_gc, IRQ_MSK(bank->bgc.gc.ngpio),
IRQ_GC_INIT_NESTED_LOCK, IRQ_NOREQUEST, 0);
return 0;
}
static int dwapb_configure_irqs(struct dwapb_gpio *gpio,
struct dwapb_gpio_bank *bank)
{
unsigned int m, irq, ngpio = bank->bgc.gc.ngpio;
int irq_base;
for (m = 0; m < ngpio; ++m) {
irq = irq_of_parse_and_map(bank->bgc.gc.of_node, m);
if (!irq && m == 0) {
dev_warn(gpio->dev, "no irq for bank %s\n",
bank->bgc.gc.of_node->full_name);
return -ENXIO;
} else if (!irq) {
break;
}
irq_set_chained_handler(irq, dwapb_irq_handler);
irq_set_handler_data(irq, gpio);
}
bank->bgc.gc.to_irq = dwapb_gpio_to_irq;
irq_base = irq_alloc_descs(-1, 0, ngpio, NUMA_NO_NODE);
if (irq_base < 0)
return irq_base;
/* only support irq in the first bank */
bank->domain = irq_domain_add_legacy(gpio->of_node,
gpio->banks->bgc.gc.ngpio,
irq_base, 0,
&irq_domain_simple_ops, NULL);
if (dwapb_create_irqchip(gpio, bank, irq_base))
goto out_free_descs;
return 0;
out_free_descs:
irq_free_descs(irq_base, ngpio);
return -EIO;
}
static int dwapb_gpio_add_bank(struct dwapb_gpio *gpio,
struct device_node *bank_np,
unsigned int offs)
{
struct dwapb_gpio_bank *bank;
u32 bank_idx, ngpio;
int err;
if (of_property_read_u32(bank_np, "reg", &bank_idx)) {
dev_err(gpio->dev, "invalid bank index for %s\n",
bank_np->full_name);
return -EINVAL;
}
bank = &gpio->banks[offs];
bank->gpio = gpio;
if (of_property_read_u32(bank_np, "nr-gpio", &ngpio)) {
dev_err(gpio->dev, "failed to get number of gpios for %s\n",
bank_np->full_name);
return -EINVAL;
}
err = bgpio_init(&bank->bgc, gpio->dev, 4,
gpio->regs + 0x50 + (bank_idx * 0x4),
gpio->regs + 0x00 + (bank_idx * 0xc),
NULL, gpio->regs + 0x04 + (bank_idx * 0xc), NULL,
false);
if (err) {
dev_err(gpio->dev, "failed to init gpio chip for %s\n",
bank_np->full_name);
return err;
}
bank->bgc.gc.ngpio = ngpio;
bank->bgc.gc.of_node = bank_np;
bank->bgc.dir = bank->bgc.read_reg(bank->bgc.reg_dir);
bank->bgc.data = bank->bgc.read_reg(bank->bgc.reg_set);
/*
* Only bank A can provide interrupts in all configurations of the IP.
*/
if (bank_idx == 0 && of_get_property(bank_np, "interrupt-controller", NULL))
dwapb_configure_irqs(gpio, bank);
err = gpiochip_add(&bank->bgc.gc);
if (err)
dev_err(gpio->dev, "failed to register gpiochip for %s\n",
bank_np->full_name);
else
bank->is_registered = true;
return err;
}
static void dwapb_gpio_unregister(struct dwapb_gpio *gpio)
{
unsigned int m;
for (m = 0; m < gpio->nr_banks; ++m)
if (gpio->banks[m].is_registered)
WARN_ON(gpiochip_remove(&gpio->banks[m].bgc.gc));
of_node_put(gpio->of_node);
}
#ifdef CONFIG_PM_SLEEP
static int dwapb_gpio_resume(struct device *dev)
{
unsigned long flags;
struct dwapb_gpio *gpio = (struct dwapb_gpio *)dev_get_drvdata(dev);
int b;
local_irq_save(flags);
for (b = 0; b < gpio->nr_banks; b++) {
writel(gpio->ddr[b], gpio->regs + 0x04 + (b * 0xc));
writel(gpio->dr[b], gpio->regs + 0x00 + (b * 0xc));
}
writel(gpio->int_type, gpio->regs + INT_TYPE_REG_OFFS);
writel(gpio->int_polarity, gpio->regs + INT_POLARITY_REG_OFFS);
writel(gpio->int_en, gpio->regs + INT_EN_REG_OFFS);
local_irq_restore(flags);
return 0;
}
static int dwapb_gpio_suspend(struct device *dev)
{
unsigned long flags;
struct dwapb_gpio *gpio = (struct dwapb_gpio *)dev_get_drvdata(dev);
int b;
local_irq_save(flags);
for (b = 0; b < gpio->nr_banks; b++) {
gpio->dr[b] = readl(gpio->regs + 0x00 + (b * 0xc));
gpio->ddr[b] = readl(gpio->regs + 0x04 + (b * 0xc));
}
gpio->int_type = readl(gpio->regs + INT_TYPE_REG_OFFS);
gpio->int_polarity = readl(gpio->regs + INT_POLARITY_REG_OFFS);
gpio->int_en = readl(gpio->regs + INT_EN_REG_OFFS);
local_irq_restore(flags);
return 0;
}
#endif
static const struct dev_pm_ops dwapb_gpio_pm_ops = {
SET_SYSTEM_SLEEP_PM_OPS(dwapb_gpio_suspend, dwapb_gpio_resume)
};
static int __devinit dwapb_gpio_probe(struct platform_device *pdev)
{
struct resource *res;
struct dwapb_gpio *gpio;
struct device_node *np;
const char *dev_name;
int err;
unsigned int offs = 0;
gpio = devm_kzalloc(&pdev->dev, sizeof(*gpio), GFP_KERNEL);
if (!gpio)
return -ENOMEM;
gpio->dev = &pdev->dev;
gpio->nr_banks = dwapb_gpio_nr_banks(pdev->dev.of_node);
if (!gpio->nr_banks || (gpio->nr_banks > DW_MAX_BANK))
return -EINVAL;
gpio->banks = devm_kzalloc(&pdev->dev, gpio->nr_banks *
sizeof(*gpio->banks), GFP_KERNEL);
if (!gpio->banks)
return -ENOMEM;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res) {
dev_err(&pdev->dev, "failed to get iomem\n");
return -ENXIO;
}
gpio->regs = devm_ioremap(&pdev->dev, res->start, resource_size(res));
if (!gpio->regs)
return -ENOMEM;
gpio->of_node = of_node_get(pdev->dev.of_node);
dev_name = of_get_property(gpio->of_node, "dev_name", NULL);
if (dev_name)
dev_set_name(gpio->dev, dev_name);
for_each_child_of_node(pdev->dev.of_node, np) {
err = dwapb_gpio_add_bank(gpio, np, offs++);
if (err)
goto out_unregister;
}
platform_set_drvdata(pdev, gpio);
return 0;
out_unregister:
dwapb_gpio_unregister(gpio);
return err;
}
static const struct of_device_id dwapb_of_match_table[] = {
{ .compatible = "snps,dw-apb-gpio" },
{ /* Sentinel */ }
};
static struct platform_driver dwapb_gpio_driver = {
.driver = {
.name = "gpio-dwapb",
.owner = THIS_MODULE,
.pm = &dwapb_gpio_pm_ops,
.of_match_table = dwapb_of_match_table,
},
.probe = dwapb_gpio_probe,
};
static int __init dwapb_gpio_init(void)
{
return platform_driver_register(&dwapb_gpio_driver);
}
postcore_initcall(dwapb_gpio_init);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Jamie Iles");
MODULE_DESCRIPTION("DesignWare APB GPIO driver");