blob: f0174238276bb2f8b1aa5aa28b416828ccd45a0c [file] [log] [blame]
/*
* drivers/pinctrl/ambarella/pinctrl-amb.c
*
* History:
* 2013/12/18 - [Cao Rongrong] created file
*
* Copyright (C) 2012-2016, Ambarella, Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/gpio.h>
#include <linux/io.h>
#include <linux/slab.h>
#include <linux/syscore_ops.h>
#include <linux/irqdomain.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/irqchip/chained_irq.h>
#include <plat/iav_helper.h>
#include <plat/pinctrl.h>
#if defined(CONFIG_PM)
struct amb_gpio_regs {
u32 irq_wake_mask;
u32 data;
u32 dir;
u32 is;
u32 ibe;
u32 iev;
u32 ie;
u32 afsel;
u32 mask;
};
struct amb_gpio_regs amb_gpio_pm[GPIO_INSTANCES];
#endif
struct amb_gpio_chip {
int irq[GPIO_INSTANCES];
void __iomem *regbase[GPIO_INSTANCES];
void __iomem *iomux_base;
struct gpio_chip *gc;
struct irq_domain *domain;
struct ambarella_service gpio_service;
};
static int ambarella_gpio_service(void *arg, void *result);
/* gpiolib gpio_request callback function */
static int amb_gpio_request(struct gpio_chip *gc, unsigned pin)
{
return pinctrl_request_gpio(gc->base + pin);
}
/* gpiolib gpio_set callback function */
static void amb_gpio_free(struct gpio_chip *gc, unsigned pin)
{
pinctrl_free_gpio(gc->base + pin);
}
/* gpiolib gpio_free callback function */
static void amb_gpio_set(struct gpio_chip *gc, unsigned pin, int value)
{
struct amb_gpio_chip *amb_gpio = dev_get_drvdata(gc->dev);
void __iomem *regbase;
u32 bank, offset, mask;
bank = PINID_TO_BANK(pin);
offset = PINID_TO_OFFSET(pin);
regbase = amb_gpio->regbase[bank];
mask = (0x1 << offset);
amba_writel(regbase + GPIO_MASK_OFFSET, mask);
if (value == GPIO_LOW)
mask = 0;
amba_writel(regbase + GPIO_DATA_OFFSET, mask);
}
/* gpiolib gpio_get callback function */
static int amb_gpio_get(struct gpio_chip *gc, unsigned pin)
{
struct amb_gpio_chip *amb_gpio = dev_get_drvdata(gc->dev);
void __iomem *regbase;
u32 bank, offset, mask, data;
bank = PINID_TO_BANK(pin);
offset = PINID_TO_OFFSET(pin);
regbase = amb_gpio->regbase[bank];
mask = (0x1 << offset);
amba_writel(regbase + GPIO_MASK_OFFSET, mask);
data = amba_readl(regbase + GPIO_DATA_OFFSET);
data = (data >> offset) & 0x1;
return (data ? GPIO_HIGH : GPIO_LOW);
}
/* gpiolib gpio_get_direction callback function */
static int amb_gpio_get_direction(struct gpio_chip *gc, unsigned pin)
{
struct amb_gpio_chip *amb_gpio = dev_get_drvdata(gc->dev);
void __iomem *regbase;
u32 bank, offset, mask, data;
bank = PINID_TO_BANK(pin);
offset = PINID_TO_OFFSET(pin);
regbase = amb_gpio->regbase[bank];
mask = (0x1 << offset);
amba_writel(regbase + GPIO_MASK_OFFSET, mask);
data = amba_readl(regbase + GPIO_DIR_OFFSET);
data = (data >> offset) & 0x1;
return (data ? GPIOF_DIR_OUT : GPIOF_DIR_IN);
}
/* gpiolib gpio_direction_input callback function */
static int amb_gpio_direction_input(struct gpio_chip *gc, unsigned pin)
{
return pinctrl_gpio_direction_input(gc->base + pin);
}
/* gpiolib gpio_direction_output callback function */
static int amb_gpio_direction_output(struct gpio_chip *gc,
unsigned pin, int value)
{
int rval;
rval = pinctrl_gpio_direction_output(gc->base + pin);
if (rval < 0)
return rval;
amb_gpio_set(gc, pin, value);
return 0;
}
/* gpiolib gpio_to_irq callback function */
static int amb_gpio_to_irq(struct gpio_chip *gc, unsigned pin)
{
struct amb_gpio_chip *amb_gpio = dev_get_drvdata(gc->dev);
return irq_create_mapping(amb_gpio->domain, pin);
}
static void amb_gpio_dbg_show(struct seq_file *s, struct gpio_chip *gc)
{
struct amb_gpio_chip *amb_gpio = dev_get_drvdata(gc->dev);
void __iomem *iomux_base = amb_gpio->iomux_base;
void __iomem *regbase;
u32 afsel = 0, data = 0, dir = 0, mask = 0;
u32 iomux0 = 0, iomux1 = 0, iomux2 = 0, alt = 0;
u32 i, bank, offset;
for (i = 0; i < gc->ngpio; i++) {
offset = PINID_TO_OFFSET(i);
if (offset == 0) {
bank = PINID_TO_BANK(i);
regbase = amb_gpio->regbase[bank];
afsel = amba_readl(regbase + GPIO_AFSEL_OFFSET);
dir = amba_readl(regbase + GPIO_DIR_OFFSET);
mask = amba_readl(regbase + GPIO_MASK_OFFSET);
amba_writel(regbase + GPIO_MASK_OFFSET, ~afsel);
data = amba_readl(regbase + GPIO_DATA_OFFSET);
amba_writel(regbase + GPIO_MASK_OFFSET, mask);
seq_printf(s, "\nGPIO[%d]:\t[%d - %d]\n",
bank, i, i + GPIO_BANK_SIZE - 1);
seq_printf(s, "GPIO_BASE:\t0x%08X\n", (u32)regbase);
seq_printf(s, "GPIO_AFSEL:\t0x%08X\n", afsel);
seq_printf(s, "GPIO_DIR:\t0x%08X\n", dir);
seq_printf(s, "GPIO_MASK:\t0x%08X:0x%08X\n", mask, ~afsel);
seq_printf(s, "GPIO_DATA:\t0x%08X\n", data);
if (iomux_base != NULL) {
iomux0 = amba_readl(iomux_base + bank * 12);
iomux1 = amba_readl(iomux_base + bank * 12 + 4);
iomux2 = amba_readl(iomux_base + bank * 12 + 8);
seq_printf(s, "IOMUX_REG%d_0:\t0x%08X\n", bank, iomux0);
seq_printf(s, "IOMUX_REG%d_1:\t0x%08X\n", bank, iomux1);
seq_printf(s, "IOMUX_REG%d_2:\t0x%08X\n", bank, iomux2);
}
}
seq_printf(s, " gpio-%-3d", gc->base + i);
if (iomux_base != NULL) {
alt = ((iomux2 >> offset) & 1) << 2;
alt |= ((iomux1 >> offset) & 1) << 1;
alt |= ((iomux0 >> offset) & 1) << 0;
if (alt != 0)
seq_printf(s, " [HW ] (alt%d)\n", alt);
else {
const char *label = gpiochip_is_requested(gc, i);
label = label ? : "";
seq_printf(s, " [GPIO] (%-20.20s) %s %s\n", label,
(dir & (1 << offset)) ? "out" : "in ",
(data & (1 << offset)) ? "hi" : "lo");
}
} else {
if (afsel & (1 << offset)) {
seq_printf(s, " [HW ]\n");
} else {
const char *label = gpiochip_is_requested(gc, i);
label = label ? : "";
seq_printf(s, " [GPIO] (%-20.20s) %s %s\n", label,
(dir & (1 << offset)) ? "out" : "in ",
(data & (1 << offset)) ? "hi" : "lo");
}
}
}
}
static struct gpio_chip amb_gc = {
.label = "ambarella-gpio",
.base = 0,
.ngpio = AMBGPIO_SIZE,
.request = amb_gpio_request,
.free = amb_gpio_free,
.direction_input = amb_gpio_direction_input,
.direction_output = amb_gpio_direction_output,
.get = amb_gpio_get,
.set = amb_gpio_set,
.get_direction = amb_gpio_get_direction,
.to_irq = amb_gpio_to_irq,
.dbg_show = amb_gpio_dbg_show,
.owner = THIS_MODULE,
};
static void amb_gpio_irq_enable(struct irq_data *data)
{
struct amb_gpio_chip *amb_gpio = dev_get_drvdata(amb_gc.dev);
void __iomem *regbase = irq_data_get_irq_chip_data(data);
void __iomem *iomux_base = amb_gpio->iomux_base;
u32 i, bank, offset, val;
bank = PINID_TO_BANK(data->hwirq);
offset = PINID_TO_OFFSET(data->hwirq);
/* make sure the pin is in gpio input mode */
if (!gpiochip_is_requested(&amb_gc, data->hwirq)) {
amba_clrbitsl(regbase + GPIO_AFSEL_OFFSET, 0x1 << offset);
amba_clrbitsl(regbase + GPIO_DIR_OFFSET, 0x1 << offset);
if (iomux_base) {
for (i = 0; i < 3; i++) {
val = amba_readl(iomux_base + IOMUX_REG_OFFSET(bank, i));
val &= (~(0x1 << offset));
amba_writel(iomux_base + IOMUX_REG_OFFSET(bank, i), val);
}
amba_writel(iomux_base + IOMUX_CTRL_SET_OFFSET, 0x1);
amba_writel(iomux_base + IOMUX_CTRL_SET_OFFSET, 0x0);
}
}
amba_writel(regbase + GPIO_IC_OFFSET, 0x1 << offset);
amba_setbitsl(regbase + GPIO_IE_OFFSET, 0x1 << offset);
}
static void amb_gpio_irq_disable(struct irq_data *data)
{
void __iomem *regbase = irq_data_get_irq_chip_data(data);
u32 offset = PINID_TO_OFFSET(data->hwirq);
amba_clrbitsl(regbase + GPIO_IE_OFFSET, 0x1 << offset);
amba_writel(regbase + GPIO_IC_OFFSET, 0x1 << offset);
}
static void amb_gpio_irq_ack(struct irq_data *data)
{
void __iomem *regbase = irq_data_get_irq_chip_data(data);
u32 offset = PINID_TO_OFFSET(data->hwirq);
amba_writel(regbase + GPIO_IC_OFFSET, 0x1 << offset);
}
static void amb_gpio_irq_mask(struct irq_data *data)
{
void __iomem *regbase = irq_data_get_irq_chip_data(data);
u32 offset = PINID_TO_OFFSET(data->hwirq);
amba_clrbitsl(regbase + GPIO_IE_OFFSET, 0x1 << offset);
}
static void amb_gpio_irq_mask_ack(struct irq_data *data)
{
void __iomem *regbase = irq_data_get_irq_chip_data(data);
u32 offset = PINID_TO_OFFSET(data->hwirq);
amba_clrbitsl(regbase + GPIO_IE_OFFSET, 0x1 << offset);
amba_writel(regbase + GPIO_IC_OFFSET, 0x1 << offset);
}
static void amb_gpio_irq_unmask(struct irq_data *data)
{
void __iomem *regbase = irq_data_get_irq_chip_data(data);
u32 offset = PINID_TO_OFFSET(data->hwirq);
amba_setbitsl(regbase + GPIO_IE_OFFSET, 0x1 << offset);
}
static int amb_gpio_irq_set_type(struct irq_data *data, unsigned int type)
{
void __iomem *regbase = irq_data_get_irq_chip_data(data);
struct irq_desc *desc = irq_to_desc(data->irq);
u32 offset = PINID_TO_OFFSET(data->hwirq);
u32 mask, bit, sense, bothedges, event;
mask = ~(0x1 << offset);
bit = (0x1 << offset);
sense = amba_readl(regbase + GPIO_IS_OFFSET);
bothedges = amba_readl(regbase + GPIO_IBE_OFFSET);
event = amba_readl(regbase + GPIO_IEV_OFFSET);
switch (type) {
case IRQ_TYPE_EDGE_RISING:
sense &= mask;
bothedges &= mask;
event |= bit;
desc->handle_irq = handle_edge_irq;
break;
case IRQ_TYPE_EDGE_FALLING:
sense &= mask;
bothedges &= mask;
event &= mask;
desc->handle_irq = handle_edge_irq;
break;
case IRQ_TYPE_EDGE_BOTH:
sense &= mask;
bothedges |= bit;
event &= mask;
desc->handle_irq = handle_edge_irq;
break;
case IRQ_TYPE_LEVEL_HIGH:
sense |= bit;
bothedges &= mask;
event |= bit;
desc->handle_irq = handle_level_irq;
break;
case IRQ_TYPE_LEVEL_LOW:
sense |= bit;
bothedges &= mask;
event &= mask;
desc->handle_irq = handle_level_irq;
break;
default:
pr_err("%s: irq[%d] type[%d] fail!\n",
__func__, data->irq, type);
return -EINVAL;
}
amba_writel(regbase + GPIO_IS_OFFSET, sense);
amba_writel(regbase + GPIO_IBE_OFFSET, bothedges);
amba_writel(regbase + GPIO_IEV_OFFSET, event);
/* clear obsolete irq */
amba_writel(regbase + GPIO_IC_OFFSET, 0x1 << offset);
return 0;
}
static int amb_gpio_irq_set_wake(struct irq_data *data, unsigned int on)
{
#if defined(CONFIG_PM)
u32 bank = PINID_TO_BANK(data->hwirq);
u32 offset = PINID_TO_OFFSET(data->hwirq);
if (on) {
amb_gpio_pm[bank].irq_wake_mask |= (1 << offset);
} else {
amb_gpio_pm[bank].irq_wake_mask &= ~(1 << offset);
}
#endif
return 0;
}
static struct irq_chip amb_gpio_irqchip = {
.name = "GPIO",
.irq_enable = amb_gpio_irq_enable,
.irq_disable = amb_gpio_irq_disable,
.irq_ack = amb_gpio_irq_ack,
.irq_mask = amb_gpio_irq_mask,
.irq_mask_ack = amb_gpio_irq_mask_ack,
.irq_unmask = amb_gpio_irq_unmask,
.irq_set_type = amb_gpio_irq_set_type,
.irq_set_wake = amb_gpio_irq_set_wake,
.flags = IRQCHIP_SET_TYPE_MASKED | IRQCHIP_MASK_ON_SUSPEND,
};
static int amb_gpio_irqdomain_map(struct irq_domain *d,
unsigned int irq, irq_hw_number_t hwirq)
{
struct amb_gpio_chip *amb_gpio;
amb_gpio = (struct amb_gpio_chip *)d->host_data;
irq_set_chip_and_handler(irq, &amb_gpio_irqchip, handle_level_irq);
irq_set_chip_data(irq, amb_gpio->regbase[PINID_TO_BANK(hwirq)]);
set_irq_flags(irq, IRQF_VALID | IRQF_PROBE);
return 0;
}
const struct irq_domain_ops amb_gpio_irq_domain_ops = {
.map = amb_gpio_irqdomain_map,
.xlate = irq_domain_xlate_twocell,
};
static void amb_gpio_handle_irq(unsigned int irq, struct irq_desc *desc)
{
struct irq_chip *irqchip;
struct amb_gpio_chip *amb_gpio;
u32 i, gpio_mis, gpio_hwirq, gpio_irq;
irqchip = irq_desc_get_chip(desc);
chained_irq_enter(irqchip, desc);
amb_gpio = irq_get_handler_data(irq);
/* find the GPIO bank generating this irq */
for (i = 0; i < GPIO_INSTANCES; i++) {
if (amb_gpio->irq[i] == irq)
break;
}
if (i == GPIO_INSTANCES)
return;
gpio_mis = amba_readl(amb_gpio->regbase[i] + GPIO_MIS_OFFSET);
if (gpio_mis) {
gpio_hwirq = i * GPIO_BANK_SIZE + ffs(gpio_mis) - 1;
gpio_irq = irq_find_mapping(amb_gpio->domain, gpio_hwirq);
generic_handle_irq(gpio_irq);
}
chained_irq_exit(irqchip, desc);
}
static int amb_gpio_probe(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node;
struct device_node *parent;
struct amb_gpio_chip *amb_gpio;
int i, rval;
amb_gpio = devm_kzalloc(&pdev->dev, sizeof(*amb_gpio), GFP_KERNEL);
if (!amb_gpio) {
dev_err(&pdev->dev, "failed to allocate memory for private data\n");
return -ENOMEM;
}
parent = of_get_parent(np);
for (i = 0; i < GPIO_INSTANCES; i++) {
amb_gpio->regbase[i] = of_iomap(parent, i);
if (amb_gpio->regbase[i] == NULL) {
dev_err(&pdev->dev, "devm_ioremap() failed\n");
return -ENOMEM;
}
amba_writel(amb_gpio->regbase[i] + GPIO_ENABLE_OFFSET, 0xffffffff);
amb_gpio->irq[i] = irq_of_parse_and_map(np, i);
if (amb_gpio->irq[i] == 0) {
dev_err(&pdev->dev, "no irq for gpio[%d]!\n", i);
return -ENXIO;
}
}
/* iomux_base will get NULL if not existed */
amb_gpio->iomux_base = of_iomap(parent, i);
of_node_put(parent);
amb_gpio->gc = &amb_gc;
amb_gpio->gc->dev = &pdev->dev;
rval = gpiochip_add(amb_gpio->gc);
if (rval) {
dev_err(&pdev->dev,
"failed to register gpio_chip %s\n", amb_gpio->gc->label);
return rval;
}
/* Initialize GPIO irq */
amb_gpio->domain = irq_domain_add_linear(np, amb_gpio->gc->ngpio,
&amb_gpio_irq_domain_ops, amb_gpio);
if (!amb_gpio->domain) {
pr_err("%s: Failed to create irqdomain\n", np->full_name);
return -ENOSYS;
}
for (i = 0; i < GPIO_INSTANCES; i++) {
irq_set_irq_type(amb_gpio->irq[i], IRQ_TYPE_LEVEL_HIGH);
irq_set_handler_data(amb_gpio->irq[i], amb_gpio);
irq_set_chained_handler(amb_gpio->irq[i], amb_gpio_handle_irq);
}
platform_set_drvdata(pdev, amb_gpio);
dev_info(&pdev->dev, "Ambarella GPIO driver registered\n");
/* register ambarella gpio service for private operation */
amb_gpio->gpio_service.service = AMBARELLA_SERVICE_GPIO;
amb_gpio->gpio_service.func = ambarella_gpio_service;
ambarella_register_service(&amb_gpio->gpio_service);
return 0;
}
#ifdef CONFIG_PM
static int amb_gpio_irq_suspend(void)
{
struct amb_gpio_chip *amb_gpio;
struct amb_gpio_regs *pm;
void __iomem *regbase;
int i;
amb_gpio = dev_get_drvdata(amb_gc.dev);
if (amb_gpio == NULL) {
pr_err("No device for ambarella gpio irq\n");
return -ENODEV;
}
for (i = 0; i < GPIO_INSTANCES; i++) {
regbase = amb_gpio->regbase[i];
pm = &amb_gpio_pm[i];
pm->afsel = amba_readl(regbase + GPIO_AFSEL_OFFSET);
pm->dir = amba_readl(regbase + GPIO_DIR_OFFSET);
pm->is = amba_readl(regbase + GPIO_IS_OFFSET);
pm->ibe = amba_readl(regbase + GPIO_IBE_OFFSET);
pm->iev = amba_readl(regbase + GPIO_IEV_OFFSET);
pm->ie = amba_readl(regbase + GPIO_IE_OFFSET);
pm->mask = ~pm->afsel;
amba_writel(regbase + GPIO_MASK_OFFSET, pm->mask);
pm->data = amba_readl(regbase + GPIO_DATA_OFFSET);
if (pm->irq_wake_mask) {
amba_writel(regbase + GPIO_IE_OFFSET, pm->irq_wake_mask);
pr_info("gpio_irq[%p]: irq_wake[0x%08X]\n",
regbase, pm->irq_wake_mask);
}
}
return 0;
}
static void amb_gpio_irq_resume(void)
{
struct amb_gpio_chip *amb_gpio;
struct amb_gpio_regs *pm;
void __iomem *regbase;
int i;
amb_gpio = dev_get_drvdata(amb_gc.dev);
if (amb_gpio == NULL) {
pr_err("No device for ambarella gpio irq\n");
return;
}
for (i = 0; i < GPIO_INSTANCES; i++) {
regbase = amb_gpio->regbase[i];
pm = &amb_gpio_pm[i];
amba_writel(regbase + GPIO_AFSEL_OFFSET, pm->afsel);
amba_writel(regbase + GPIO_DIR_OFFSET, pm->dir);
amba_writel(regbase + GPIO_MASK_OFFSET, pm->mask);
amba_writel(regbase + GPIO_DATA_OFFSET, pm->data);
amba_writel(regbase + GPIO_IS_OFFSET, pm->is);
amba_writel(regbase + GPIO_IBE_OFFSET, pm->ibe);
amba_writel(regbase + GPIO_IEV_OFFSET, pm->iev);
amba_writel(regbase + GPIO_IE_OFFSET, pm->ie);
amba_writel(regbase + GPIO_ENABLE_OFFSET, 0xffffffff);
}
}
struct syscore_ops amb_gpio_irq_syscore_ops = {
.suspend = amb_gpio_irq_suspend,
.resume = amb_gpio_irq_resume,
};
#endif
static const struct of_device_id amb_gpio_dt_match[] = {
{ .compatible = "ambarella,gpio" },
{},
};
MODULE_DEVICE_TABLE(of, amb_gpio_dt_match);
static struct platform_driver amb_gpio_driver = {
.probe = amb_gpio_probe,
.driver = {
.name = "ambarella-gpio",
.owner = THIS_MODULE,
.of_match_table = of_match_ptr(amb_gpio_dt_match),
},
};
static int __init amb_gpio_drv_register(void)
{
#ifdef CONFIG_PM
register_syscore_ops(&amb_gpio_irq_syscore_ops);
#endif
return platform_driver_register(&amb_gpio_driver);
}
postcore_initcall(amb_gpio_drv_register);
MODULE_AUTHOR("Cao Rongrong <rrcao@ambarella.com>");
MODULE_DESCRIPTION("Ambarella SoC GPIO driver");
MODULE_LICENSE("GPL v2");
static int ambarella_gpio_service(void *arg, void *result)
{
struct ambsvc_gpio *gpio_svc = arg;
u32 *value = result;
int rval = 0;
BUG_ON(!gpio_svc);
switch (gpio_svc->svc_id) {
case AMBSVC_GPIO_REQUEST:
{
rval = gpio_request(gpio_svc->gpio, "gpio");
break;
}
case AMBSVC_GPIO_OUTPUT:
rval = gpio_direction_output(gpio_svc->gpio, gpio_svc->value);
break;
case AMBSVC_GPIO_INPUT:
rval = gpio_direction_input(gpio_svc->gpio);
if (rval >= 0 && value)
*value = gpio_get_value_cansleep(gpio_svc->gpio);
break;
case AMBSVC_GPIO_FREE:
gpio_free(gpio_svc->gpio);
break;
case AMBSVC_GPIO_TO_IRQ:
*value = gpio_to_irq(gpio_svc->gpio);
break;
default:
pr_err("%s: Invalid gpio service (%d)\n", __func__, gpio_svc->svc_id);
rval = -EINVAL;
break;
}
return rval;
}