327 lines
9.6 KiB
C
327 lines
9.6 KiB
C
|
/* arch/arm/mach-msm/board-sapphire-gpio.c
|
||
|
* Copyright (C) 2007-2009 HTC Corporation.
|
||
|
* Author: Thomas Tsai <thomas_tsai@htc.com>
|
||
|
*
|
||
|
* 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.
|
||
|
*
|
||
|
* 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.
|
||
|
*/
|
||
|
|
||
|
#include <linux/kernel.h>
|
||
|
#include <linux/errno.h>
|
||
|
#include <linux/irq.h>
|
||
|
#include <linux/pm.h>
|
||
|
#include <linux/sysdev.h>
|
||
|
|
||
|
#include <linux/io.h>
|
||
|
#include <linux/gpio.h>
|
||
|
#include <asm/mach-types.h>
|
||
|
|
||
|
#include "gpio_chip.h"
|
||
|
#include "board-sapphire.h"
|
||
|
|
||
|
#ifdef DEBUG_SAPPHIRE_GPIO
|
||
|
#define DBG(fmt, arg...) printk(KERN_INFO "%s: " fmt "\n", __func__, ## arg)
|
||
|
#else
|
||
|
#define DBG(fmt, arg...) do {} while (0)
|
||
|
#endif
|
||
|
|
||
|
#define SAPPHIRE_CPLD_INT_STATUS (SAPPHIRE_CPLD_BASE + 0x0E)
|
||
|
#define SAPPHIRE_CPLD_INT_LEVEL (SAPPHIRE_CPLD_BASE + 0x08)
|
||
|
#define SAPPHIRE_CPLD_INT_MASK (SAPPHIRE_CPLD_BASE + 0x0C)
|
||
|
|
||
|
/*CPLD misc reg offset*/
|
||
|
static const int _g_CPLD_MISCn_Offset[] = { 0x0A, /*misc1 reg*/
|
||
|
0x00, /*misc2 reg*/
|
||
|
0x02, /*misc3 reg*/
|
||
|
0x04, /*misc4 reg*/
|
||
|
0x06}; /*misc5 reg*/
|
||
|
/*CPLD INT Bank*/
|
||
|
/*BANK0: int1 status, int2 level, int3 mask*/
|
||
|
static const int _g_INT_BANK_Offset[][3] = {{0x0E, 0x08, 0x0C} };
|
||
|
|
||
|
static uint8_t sapphire_cpld_initdata[4] = {
|
||
|
[0] = 0x80, /* for serial debug UART3, low current misc2*/
|
||
|
[1] = 0x34, /* jog & tp enable, I2C pull misc3*/
|
||
|
[3] = 0x04, /* mmdi 32k en misc5*/
|
||
|
};
|
||
|
|
||
|
/*save current working int mask, so the value can be restored after resume.
|
||
|
Sapphire has only bank0.*/
|
||
|
static uint8_t sapphire_int_mask[] = {
|
||
|
[0] = 0xfb, /* enable all interrupts, bit 2 is not used */
|
||
|
};
|
||
|
|
||
|
/*Sleep have to prepare the wake up source in advance.
|
||
|
default to disable all wakeup sources when suspend.*/
|
||
|
static uint8_t sapphire_sleep_int_mask[] = {
|
||
|
[0] = 0x00, /* bit2 is not used */
|
||
|
};
|
||
|
|
||
|
static int sapphire_suspended;
|
||
|
|
||
|
static int sapphire_gpio_read(struct gpio_chip *chip, unsigned n)
|
||
|
{
|
||
|
if (n < SAPPHIRE_GPIO_INT_B0_BASE) /*MISCn*/
|
||
|
return !!(readb(CPLD_GPIO_REG(n)) & CPLD_GPIO_BIT_POS_MASK(n));
|
||
|
else if (n <= SAPPHIRE_GPIO_END) /*gpio n is INT pin*/
|
||
|
return !!(readb(CPLD_INT_LEVEL_REG_G(n)) &
|
||
|
CPLD_GPIO_BIT_POS_MASK(n));
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/*CPLD Write only register :MISC2, MISC3, MISC4, MISC5 => reg=0,2,4,6
|
||
|
Reading from write-only registers is undefined, so the writing value
|
||
|
should be kept in shadow for later usage.*/
|
||
|
int sapphire_gpio_write(struct gpio_chip *chip, unsigned n, unsigned on)
|
||
|
{
|
||
|
unsigned long flags;
|
||
|
uint8_t reg_val;
|
||
|
if (n > SAPPHIRE_GPIO_END)
|
||
|
return -1;
|
||
|
|
||
|
local_irq_save(flags);
|
||
|
reg_val = readb(CPLD_GPIO_REG(n));
|
||
|
if (on)
|
||
|
reg_val |= CPLD_GPIO_BIT_POS_MASK(n);
|
||
|
else
|
||
|
reg_val &= ~CPLD_GPIO_BIT_POS_MASK(n);
|
||
|
writeb(reg_val, CPLD_GPIO_REG(n));
|
||
|
|
||
|
DBG("gpio=%d, l=0x%x\r\n", n, readb(SAPPHIRE_CPLD_INT_LEVEL));
|
||
|
|
||
|
local_irq_restore(flags);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int sapphire_gpio_configure(struct gpio_chip *chip, unsigned int gpio,
|
||
|
unsigned long flags)
|
||
|
{
|
||
|
if (flags & (GPIOF_OUTPUT_LOW | GPIOF_OUTPUT_HIGH))
|
||
|
sapphire_gpio_write(chip, gpio, flags & GPIOF_OUTPUT_HIGH);
|
||
|
|
||
|
DBG("gpio=%d, l=0x%x\r\n", gpio, readb(SAPPHIRE_CPLD_INT_LEVEL));
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int sapphire_gpio_get_irq_num(struct gpio_chip *chip, unsigned int gpio,
|
||
|
unsigned int *irqp, unsigned long *irqnumflagsp)
|
||
|
{
|
||
|
DBG("gpio=%d, l=0x%x\r\n", gpio, readb(SAPPHIRE_CPLD_INT_LEVEL));
|
||
|
DBG("SAPPHIRE_GPIO_INT_B0_BASE=%d, SAPPHIRE_GPIO_LAST_INT=%d\r\n",
|
||
|
SAPPHIRE_GPIO_INT_B0_BASE, SAPPHIRE_GPIO_LAST_INT);
|
||
|
if ((gpio < SAPPHIRE_GPIO_INT_B0_BASE) ||
|
||
|
(gpio > SAPPHIRE_GPIO_LAST_INT))
|
||
|
return -ENOENT;
|
||
|
*irqp = SAPPHIRE_GPIO_TO_INT(gpio);
|
||
|
DBG("*irqp=%d\r\n", *irqp);
|
||
|
if (irqnumflagsp)
|
||
|
*irqnumflagsp = 0;
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/*write 1 to clear INT status bit.*/
|
||
|
static void sapphire_gpio_irq_ack(unsigned int irq)
|
||
|
{
|
||
|
/*write 1 to clear*/
|
||
|
writeb(SAPPHIRE_INT_BIT_MASK(irq), CPLD_INT_STATUS_REG(irq));
|
||
|
}
|
||
|
|
||
|
/*unmask/enable the INT
|
||
|
static void sapphire_gpio_irq_unmask(unsigned int irq)*/
|
||
|
static void sapphire_gpio_irq_enable(unsigned int irq)
|
||
|
{
|
||
|
unsigned long flags;
|
||
|
uint8_t reg_val;
|
||
|
|
||
|
local_irq_save(flags); /*disabling all interrupts*/
|
||
|
|
||
|
reg_val = readb(CPLD_INT_MASK_REG(irq)) | SAPPHIRE_INT_BIT_MASK(irq);
|
||
|
DBG("(irq=%d,0x%x, 0x%x)\r\n", irq, CPLD_INT_MASK_REG(irq),
|
||
|
SAPPHIRE_INT_BIT_MASK(irq));
|
||
|
DBG("sapphire_suspended=%d\r\n", sapphire_suspended);
|
||
|
/*printk(KERN_INFO "sapphire_gpio_irq_mask irq %d => %d:%02x\n",
|
||
|
irq, bank, reg_val);*/
|
||
|
if (!sapphire_suspended)
|
||
|
writeb(reg_val, CPLD_INT_MASK_REG(irq));
|
||
|
|
||
|
reg_val = readb(CPLD_INT_MASK_REG(irq));
|
||
|
DBG("reg_val= 0x%x\r\n", reg_val);
|
||
|
DBG("l=0x%x\r\n", readb(SAPPHIRE_CPLD_INT_LEVEL));
|
||
|
|
||
|
local_irq_restore(flags); /*restore the interrupts*/
|
||
|
}
|
||
|
|
||
|
/*mask/disable INT
|
||
|
static void sapphire_gpio_irq_mask(unsigned int irq)*/
|
||
|
static void sapphire_gpio_irq_disable(unsigned int irq)
|
||
|
{
|
||
|
unsigned long flags;
|
||
|
uint8_t reg_val;
|
||
|
|
||
|
local_irq_save(flags);
|
||
|
reg_val = readb(CPLD_INT_MASK_REG(irq)) & ~SAPPHIRE_INT_BIT_MASK(irq);
|
||
|
/*CPLD INT MASK is r/w now.*/
|
||
|
|
||
|
/*printk(KERN_INFO "sapphire_gpio_irq_unmask irq %d => %d:%02x\n",
|
||
|
irq, bank, reg_val);*/
|
||
|
DBG("(%d,0x%x, 0x%x, 0x%x)\r\n", irq, reg_val, CPLD_INT_MASK_REG(irq),
|
||
|
SAPPHIRE_INT_BIT_MASK(irq));
|
||
|
DBG("sapphire_suspended=%d\r\n", sapphire_suspended);
|
||
|
if (!sapphire_suspended)
|
||
|
writeb(reg_val, CPLD_INT_MASK_REG(irq));
|
||
|
|
||
|
reg_val = readb(CPLD_INT_MASK_REG(irq));
|
||
|
DBG("reg_val= 0x%x\r\n", reg_val);
|
||
|
DBG("l=0x%x\r\n", readb(SAPPHIRE_CPLD_INT_LEVEL));
|
||
|
|
||
|
local_irq_restore(flags);
|
||
|
}
|
||
|
|
||
|
/*preparing enable/disable wake source before sleep*/
|
||
|
int sapphire_gpio_irq_set_wake(unsigned int irq, unsigned int on)
|
||
|
{
|
||
|
unsigned long flags;
|
||
|
uint8_t mask = SAPPHIRE_INT_BIT_MASK(irq);
|
||
|
|
||
|
local_irq_save(flags);
|
||
|
|
||
|
if (on) /*wake on -> mask the bit*/
|
||
|
sapphire_sleep_int_mask[CPLD_INT_TO_BANK(irq)] |= mask;
|
||
|
else /*no wake -> unmask the bit*/
|
||
|
sapphire_sleep_int_mask[CPLD_INT_TO_BANK(irq)] &= ~mask;
|
||
|
local_irq_restore(flags);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/*Sapphire has only one INT Bank.*/
|
||
|
static void sapphire_gpio_irq_handler(unsigned int irq, struct irq_desc *desc)
|
||
|
{
|
||
|
int j;
|
||
|
unsigned v;
|
||
|
int int_base = SAPPHIRE_INT_START;
|
||
|
|
||
|
v = readb(SAPPHIRE_CPLD_INT_STATUS); /*INT1 status reg, BANK0*/
|
||
|
|
||
|
for (j = 0; j < 8 ; j++) { /*8 bit per bank*/
|
||
|
if (v & (1U << j)) { /*got the INT Bit*/
|
||
|
DBG("generic_handle_irq j=0x%x\r\n", j);
|
||
|
generic_handle_irq(int_base + j);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
desc->chip->ack(irq); /*clear CPLD INT in SOC side.*/
|
||
|
DBG("irq=%d, l=0x%x\r\n", irq, readb(SAPPHIRE_CPLD_INT_LEVEL));
|
||
|
}
|
||
|
|
||
|
/*Save current working sources before sleep, so we can restore it after
|
||
|
* resume.*/
|
||
|
static int sapphire_sysdev_suspend(struct sys_device *dev, pm_message_t state)
|
||
|
{
|
||
|
sapphire_suspended = 1;
|
||
|
/*save current masking*/
|
||
|
sapphire_int_mask[0] = readb(SAPPHIRE_CPLD_BASE +
|
||
|
SAPPHIRE_GPIO_INT_B0_MASK_REG);
|
||
|
|
||
|
/*set waking source before sleep.*/
|
||
|
writeb(sapphire_sleep_int_mask[0],
|
||
|
SAPPHIRE_CPLD_BASE + SAPPHIRE_GPIO_INT_B0_MASK_REG);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/*All the registers will be kept till a power loss...*/
|
||
|
int sapphire_sysdev_resume(struct sys_device *dev)
|
||
|
{
|
||
|
/*restore the working mask saved before sleep*/
|
||
|
writeb(sapphire_int_mask[0], SAPPHIRE_CPLD_BASE +
|
||
|
SAPPHIRE_GPIO_INT_B0_MASK_REG);
|
||
|
sapphire_suspended = 0;
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* linux/irq.h :: struct irq_chip
|
||
|
* @enable: enable the interrupt (defaults to chip->unmask if NULL)
|
||
|
* @disable: disable the interrupt (defaults to chip->mask if NULL)
|
||
|
* @ack: start of a new interrupt
|
||
|
* @mask: mask an interrupt source
|
||
|
* @mask_ack: ack and mask an interrupt source
|
||
|
* @unmask: unmask an interrupt source
|
||
|
*/
|
||
|
static struct irq_chip sapphire_gpio_irq_chip = {
|
||
|
.name = "sapphiregpio",
|
||
|
.ack = sapphire_gpio_irq_ack,
|
||
|
.mask = sapphire_gpio_irq_disable, /*sapphire_gpio_irq_mask,*/
|
||
|
.unmask = sapphire_gpio_irq_enable, /*sapphire_gpio_irq_unmask,*/
|
||
|
.set_wake = sapphire_gpio_irq_set_wake,
|
||
|
/*.set_type = sapphire_gpio_irq_set_type,*/
|
||
|
};
|
||
|
|
||
|
/*Thomas:For CPLD*/
|
||
|
static struct gpio_chip sapphire_gpio_chip = {
|
||
|
.start = SAPPHIRE_GPIO_START,
|
||
|
.end = SAPPHIRE_GPIO_END,
|
||
|
.configure = sapphire_gpio_configure,
|
||
|
.get_irq_num = sapphire_gpio_get_irq_num,
|
||
|
.read = sapphire_gpio_read,
|
||
|
.write = sapphire_gpio_write,
|
||
|
/* .read_detect_status = sapphire_gpio_read_detect_status,
|
||
|
.clear_detect_status = sapphire_gpio_clear_detect_status */
|
||
|
};
|
||
|
|
||
|
struct sysdev_class sapphire_sysdev_class = {
|
||
|
.name = "sapphiregpio_irq",
|
||
|
.suspend = sapphire_sysdev_suspend,
|
||
|
.resume = sapphire_sysdev_resume,
|
||
|
};
|
||
|
|
||
|
static struct sys_device sapphire_irq_device = {
|
||
|
.cls = &sapphire_sysdev_class,
|
||
|
};
|
||
|
|
||
|
int sapphire_init_gpio(void)
|
||
|
{
|
||
|
int i;
|
||
|
if (!machine_is_sapphire())
|
||
|
return 0;
|
||
|
|
||
|
DBG("%d,%d\r\n", SAPPHIRE_INT_START, SAPPHIRE_INT_END);
|
||
|
DBG("NR_MSM_IRQS=%d, NR_GPIO_IRQS=%d\r\n", NR_MSM_IRQS, NR_GPIO_IRQS);
|
||
|
for (i = SAPPHIRE_INT_START; i <= SAPPHIRE_INT_END; i++) {
|
||
|
set_irq_chip(i, &sapphire_gpio_irq_chip);
|
||
|
set_irq_handler(i, handle_edge_irq);
|
||
|
set_irq_flags(i, IRQF_VALID);
|
||
|
}
|
||
|
|
||
|
register_gpio_chip(&sapphire_gpio_chip);
|
||
|
|
||
|
/*setup CPLD INT connecting to SOC's gpio 17 */
|
||
|
set_irq_type(MSM_GPIO_TO_INT(17), IRQF_TRIGGER_HIGH);
|
||
|
set_irq_chained_handler(MSM_GPIO_TO_INT(17), sapphire_gpio_irq_handler);
|
||
|
set_irq_wake(MSM_GPIO_TO_INT(17), 1);
|
||
|
|
||
|
if (sysdev_class_register(&sapphire_sysdev_class) == 0)
|
||
|
sysdev_register(&sapphire_irq_device);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
int sapphire_init_cpld(unsigned int sys_rev)
|
||
|
{
|
||
|
int i;
|
||
|
|
||
|
for (i = 0; i < ARRAY_SIZE(sapphire_cpld_initdata); i++)
|
||
|
writeb(sapphire_cpld_initdata[i], SAPPHIRE_CPLD_BASE + i * 2);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
postcore_initcall(sapphire_init_gpio);
|