/*
* Generic GPIO driver for logic cells found in the Nomadik SoC
*
* Copyright (C) 2008,2009 STMicroelectronics
* Copyright (C) 2009 Alessandro Rubini <rubini@unipv.it>
* Rewritten based on work by Prafulla WADASKAR <prafulla.wadaskar@st.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.
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/platform_device.h>
#include <linux/io.h>
#include <linux/clk.h>
#include <linux/err.h>
#include <linux/gpio.h>
#include <linux/spinlock.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/slab.h>
#include <plat/pincfg.h>
#include <mach/hardware.h>
#include <mach/gpio.h>
/*
* The GPIO module in the Nomadik family of Systems-on-Chip is an
* AMBA device, managing 32 pins and alternate functions. The logic block
* is currently used in the Nomadik and ux500.
*
* Symbols in this file are called "nmk_gpio" for "nomadik gpio"
*/
static const u32 backup_regs[] = {
NMK_GPIO_PDIS,
NMK_GPIO_DIR,
NMK_GPIO_AFSLA,
NMK_GPIO_AFSLB,
NMK_GPIO_SLPC,
NMK_GPIO_RIMSC,
NMK_GPIO_FIMSC,
NMK_GPIO_RWIMSC,
NMK_GPIO_FWIMSC,
};
struct nmk_gpio_chip {
struct gpio_chip chip;
void __iomem *addr;
struct clk *clk;
unsigned int bank;
unsigned int parent_irq;
unsigned int secondary_parent_irq;
u32 (*get_secondary_status)(unsigned int bank);
spinlock_t lock;
/* Keep track of configured edges */
u32 edge_rising;
u32 edge_falling;
u32 backup[ARRAY_SIZE(backup_regs)];
/* Bitmap, 1 = pull up, 0 = pull down */
u32 pull;
};
static void __nmk_gpio_set_mode(struct nmk_gpio_chip *nmk_chip,
unsigned offset, int gpio_mode)
{
u32 bit = 1 << offset;
u32 afunc, bfunc;
afunc = readl(nmk_chip->addr + NMK_GPIO_AFSLA) & ~bit;
bfunc = readl(nmk_chip->addr + NMK_GPIO_AFSLB) & ~bit;
if (gpio_mode & NMK_GPIO_ALT_A)
afunc |= bit;
if (gpio_mode & NMK_GPIO_ALT_B)
bfunc |= bit;
writel(afunc, nmk_chip->addr + NMK_GPIO_AFSLA);
writel(bfunc, nmk_chip->addr + NMK_GPIO_AFSLB);
}
static void __nmk_gpio_set_slpm(struct nmk_gpio_chip *nmk_chip,
unsigned offset, enum nmk_gpio_slpm mode)
{
u32 bit = 1 << offset;
u32 slpm;
slpm = readl(nmk_chip->addr + NMK_GPIO_SLPC);
if (mode == NMK_GPIO_SLPM_NOCHANGE)
slpm |= bit;
else
slpm &= ~bit;
writel(slpm, nmk_chip->addr + NMK_GPIO_SLPC);
}
static void __nmk_gpio_set_pull(struct nmk_gpio_chip *nmk_chip,
unsigned offset, enum nmk_gpio_pull pull)
{
u32 bit = 1 << offset;
u32 pdis;
pdis = readl(nmk_chip->addr + NMK_GPIO_PDIS);
if (pull == NMK_GPIO_PULL_NONE)
pdis |= bit;
else
pdis &= ~bit;
writel(pdis, nmk_chip->addr + NMK_GPIO_PDIS);
if (pull == NMK_GPIO_PULL_UP) {
nmk_chip->pull |= bit;
writel(bit, nmk_chip->addr + NMK_GPIO_DATS);
} else if (pull == NMK_GPIO_PULL_DOWN) {
nmk_chip->pull &= ~bit;
writel(bit, nmk_chip->addr + NMK_GPIO_DATC);
}
}
static void __nmk_gpio_make_input(struct nmk_gpio_chip *nmk_chip,
unsigned offset)
{
writel(1 << offset, nmk_chip->addr + NMK_GPIO_DIRC);
}
static void __nmk_gpio_set_output(struct nmk_gpio_chip *nmk_chip,
unsigned offset, int val)
{
if (val)
writel(1 << offset, nmk_chip->addr + NMK_GPIO_DATS);
else
writel(1 << offset, nmk_chip->addr + NMK_GPIO_DATC);
}
static void __nmk_gpio_make_output(struct nmk_gpio_chip *nmk_chip,
unsigned offset, int val)
{
writel(1 << offset, nmk_chip->addr + NMK_GPIO_DIRS);
__nmk_gpio_set_output(nmk_chip, offset, val);
}
static void __nmk_config_pin(struct nmk_gpio_chip *nmk_chip, unsigned offset,
pin_cfg_t cfg, bool sleep)
{
static const char *afnames[] = {
[NMK_GPIO_ALT_GPIO] = "GPIO",
[NMK_GPIO_ALT_A] = "A",
[NMK_GPIO_ALT_B] = "B",
[NMK_GPIO_ALT_C] = "C"
};
static const char *pullnames[] = {
[NMK_GPIO_PULL_NONE] = "none",
[NMK_GPIO_PULL_UP] = "up",
[NMK_GPIO_PULL_DOWN] =