/*
* driver/mfd/asic3.c
*
* Compaq ASIC3 support.
*
* 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.
*
* Copyright 2001 Compaq Computer Corporation.
* Copyright 2004-2005 Phil Blundell
* Copyright 2007-2008 OpenedHand Ltd.
*
* Authors: Phil Blundell <pb@handhelds.org>,
* Samuel Ortiz <sameo@openedhand.com>
*
*/
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/irq.h>
#include <linux/gpio.h>
#include <linux/io.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
#include <linux/platform_device.h>
#include <linux/mfd/asic3.h>
#include <linux/mfd/core.h>
#include <linux/mfd/ds1wm.h>
#include <linux/mfd/tmio.h>
enum {
ASIC3_CLOCK_SPI,
ASIC3_CLOCK_OWM,
ASIC3_CLOCK_PWM0,
ASIC3_CLOCK_PWM1,
ASIC3_CLOCK_LED0,
ASIC3_CLOCK_LED1,
ASIC3_CLOCK_LED2,
ASIC3_CLOCK_SD_HOST,
ASIC3_CLOCK_SD_BUS,
ASIC3_CLOCK_SMBUS,
ASIC3_CLOCK_EX0,
ASIC3_CLOCK_EX1,
};
struct asic3_clk {
int enabled;
unsigned int cdex;
unsigned long rate;
};
#define INIT_CDEX(_name, _rate) \
[ASIC3_CLOCK_##_name] = { \
.cdex = CLOCK_CDEX_##_name, \
.rate = _rate, \
}
static struct asic3_clk asic3_clk_init[] __initdata = {
INIT_CDEX(SPI, 0),
INIT_CDEX(OWM, 5000000),
INIT_CDEX(PWM0, 0),
INIT_CDEX(PWM1, 0),
INIT_CDEX(LED0, 0),
INIT_CDEX(LED1, 0),
INIT_CDEX(LED2, 0),
INIT_CDEX(SD_HOST, 24576000),
INIT_CDEX(SD_BUS, 12288000),
INIT_CDEX(SMBUS, 0),
INIT_CDEX(EX0, 32768),
INIT_CDEX(EX1, 24576000),
};
struct asic3 {
void __iomem *mapping;
unsigned int bus_shift;
unsigned int irq_nr;
unsigned int irq_base;
spinlock_t lock;
u16 irq_bothedge[4];
struct gpio_chip gpio;
struct device *dev;
void __iomem *tmio_cnf;
struct asic3_clk clocks[ARRAY_SIZE(asic3_clk_init)];
};
static int asic3_gpio_get(struct gpio_chip *chip, unsigned offset);
static inline void asic3_write_register(struct asic3 *asic,
unsigned int reg, u32 value)
{
iowrite16(value, asic->mapping +
(reg >> asic->bus_shift));
}
static inline u32 asic3_read_register(struct asic3 *asic,
unsigned int reg)
{
return ioread16(asic->mapping +
(reg >> asic->bus_shift));
}
static void asic3_set_register(struct asic3 *asic, u32 reg, u32 bits, bool set)
{
unsigned long flags;
u32 val;
spin_lock_irqsave(&asic->lock, flags);
val = asic3_read_register(asic, reg);
if (set)
val |= bits;
else
val &= ~bits;
asic3_write_register(asic, reg, val);
spin_unlock_irqrestore(&asic->lock, flags);
}
/* IRQs */
#define MAX_ASIC_ISR_LOOPS 20
#define ASIC3_GPIO_BASE_INCR \
(ASIC3_GPIO_B_BASE - ASIC3_GPIO_A_BASE)
static void asic3_irq_flip_edge(struct asic3 *asic,
u32 base, int bit)
{
u16 edge;
unsigned long flags;
spin_lock_irqsave(&asic->lock, flags);
edge = asic3_read_register(asic,
base + ASIC3_GPIO_EDGE_TRIGGER);
edge ^= bit;
asic3_write_register(asic,
base + ASIC3_GPIO_EDGE_TRIGGER, edge);
spin_unlock_irqrestore(&asic->lock, flags);
}
static void asic3_irq_demux(unsigned int irq, struct irq_desc *desc)
{
int iter, i;
unsigned long flags;
struct asic3 *asic;
desc->irq_data.chip->irq_ack(&desc->irq_data);
asic = get_irq_data(irq);
for (iter = 0 ; iter < MAX_ASIC_ISR_LOOPS; iter++) {
u32 status;
int bank;
spin_lock_irqsave(&asic->lock, flags);
status = asic3_read_register(asic,
ASIC3_OFFSET(INTR, P_INT_STAT));
spin_unlock_irqrestore(&asic->lock, flags);
/* Check all ten register bits */
if ((status & 0x3ff) == 0)
break;
/* Handle GPIO IRQs */
for (bank = 0; bank < ASIC3_NUM_GPIO_BANKS; bank++) {
if (status & (1 << bank)) {
unsigned long base, istat;
base = ASIC3_GPIO_A_BASE
+ bank * ASIC3_GPIO_BASE_INCR;
spin_lock_irqsave(&