/* * s6000 gpio driver * * Copyright (c) 2009 emlix GmbH * Authors: Oskar Schirmer <oskar@scara.com> * Johannes Weiner <hannes@cmpxchg.org> * Daniel Gloeckner <dg@emlix.com> */ #include <linux/bitops.h> #include <linux/kernel.h> #include <linux/module.h> #include <linux/init.h> #include <linux/io.h> #include <linux/irq.h> #include <linux/gpio.h> #include <variant/hardware.h> #define IRQ_BASE XTENSA_NR_IRQS #define S6_GPIO_DATA 0x000 #define S6_GPIO_IS 0x404 #define S6_GPIO_IBE 0x408 #define S6_GPIO_IEV 0x40C #define S6_GPIO_IE 0x410 #define S6_GPIO_RIS 0x414 #define S6_GPIO_MIS 0x418 #define S6_GPIO_IC 0x41C #define S6_GPIO_AFSEL 0x420 #define S6_GPIO_DIR 0x800 #define S6_GPIO_BANK(nr) ((nr) * 0x1000) #define S6_GPIO_MASK(nr) (4 << (nr)) #define S6_GPIO_OFFSET(nr) \ (S6_GPIO_BANK((nr) >> 3) + S6_GPIO_MASK((nr) & 7)) static int direction_input(struct gpio_chip *chip, unsigned int off) { writeb(0, S6_REG_GPIO + S6_GPIO_DIR + S6_GPIO_OFFSET(off)); return 0; } static int get(struct gpio_chip *chip, unsigned int off) { return readb(S6_REG_GPIO + S6_GPIO_DATA + S6_GPIO_OFFSET(off)); } static int direction_output(struct gpio_chip *chip, unsigned int off, int val) { unsigned rel = S6_GPIO_OFFSET(off); writeb(~0, S6_REG_GPIO + S6_GPIO_DIR + rel); writeb(val ? ~0 : 0, S6_REG_GPIO + S6_GPIO_DATA + rel); return 0; } static void set(struct gpio_chip *chip, unsigned int off, int val) { writeb(val ? ~0 : 0, S6_REG_GPIO + S6_GPIO_DATA + S6_GPIO_OFFSET(off)); } static int to_irq(struct gpio_chip *chip, unsigned offset) { if (offset < 8) return offset + IRQ_BASE; return -EINVAL; } static struct gpio_chip gpiochip = { .owner = THIS_MODULE, .direction_input = direction_input, .get = get, .direction_output = direction_output, .set = set, .to_irq = to_irq, .base = 0, .ngpio = 24, .can_sleep = 0, /* no blocking io needed */ .exported = 0, /* no exporting to userspace */ }; int s6_gpio_init(u32 afsel) { writeb(afsel, S6_REG_GPIO + S6_GPIO_BANK(0) + S6_GPIO_AFSEL); writeb(afsel >> 8, S6_REG_GPIO + S6_GPIO_BANK(1) + S6_GPIO_AFSEL); writeb(afsel >> 16, S6_REG_GPIO + S6_GPIO_BANK(2) + S6_GPIO_AFSEL); return gpiochip_add(&gpiochip); } static void ack(struct irq_data *d) { writeb(1 << (d->irq - IRQ_BASE), S6_REG_GPIO + S6_GPIO_IC); } static void mask(struct irq_data *d) { u8 r = readb(S6_REG_GPIO + S6_GPIO_IE); r &= ~(1 << (d->irq - IRQ_BASE)); writeb(r, S6_REG_GPIO + S6_GPIO_IE); } static void unmask(struct irq_data *d) { u8 m = readb(S6_REG_GPIO + S6_GPIO_IE); m |= 1 << (d->irq - IRQ_BASE); writeb(m, S6_REG_GPIO + S6_GPIO_IE); } static int set_type(struct irq_data *d, unsigned int type) { const u8 m = 1 << (d->irq - IRQ_BASE); irq_flow_handler_t handler; u8 reg; if (type == IRQ_TYPE_PROBE) { if ((readb(S6_REG_GPIO + S6_GPIO_BANK(0) + S6_GPIO_AFSEL) & m) || (readb(S6_REG_GPIO + S6_GPIO_BANK(0) + S6_GPIO_IE) & m) || readb(S6_REG_GPIO + S6_GPIO_BANK(0) + S6_GPIO_DIR + S6_GPIO_MASK(irq - IRQ_BASE))) return 0; type = IRQ_TYPE_EDGE_BOTH; } reg = readb(S6_REG_GPIO + S6_GPIO_BANK(0) + S6_GPIO_IS); if (type & (IRQ_TYPE_LEVEL_LOW | IRQ_TYPE_LEVEL_HIGH)) { reg |= m; handler = handle_level_irq; } else { reg &= ~m; handler = handle_edge_irq; } writeb(reg, S6_REG_GPIO + S6_GPIO_BANK(0) + S6_GPIO_IS); __irq_set_handler_locked(irq, handler); reg = readb(S6_REG_GPIO + S6_GPIO_BANK(0) + S6_GPIO_IEV); if (type & (IRQ_TYPE_LEVEL_HIGH | IRQ_TYPE_EDGE_RISING)) reg |= m; else reg &= ~m; writeb(reg, S6_REG_GPIO + S6_GPIO_BANK(0) + S6_GPIO_IEV); reg = readb(S6_REG_GPIO + S6_GPIO_BANK(0) + S6_GPIO_IBE); if ((type & IRQ_TYPE_EDGE_BOTH) == IRQ_TYPE_EDGE_BOTH) reg |= m; else reg &= ~m; writeb(reg, S6_REG_GPIO + S6_GPIO_BANK(0) + S6_GPIO_IBE); return 0; } static struct irq_chip gpioirqs = { .name = "GPIO", .irq_ack = ack, .irq_mask = mask, .irq_unmask = unmask, .irq_set_type = set_type, }; static u8 demux_masks[4]; static void demux_irqs(unsigned int irq, struct irq_desc *desc) { struct irq_chip *chip = irq_desc_get_chip(desc); u8 *mask = irq_desc_get_handler_data(desc); u8 pending; int cirq; chip->irq_mask(&desc->irq_data); chip->irq_ack(&desc->irq_data); pending = readb(S6_REG_GPIO + S6_GPIO_BANK(0) + S6_GPIO_MIS) & *mask; cirq = IRQ_BASE - 1; while (pending) { int n = ffs(pending); cirq += n; pending >>= n; generic_handle_irq(cirq); } chip->irq_unmask(&desc->irq_data); } extern const signed char *platform_irq_mappings[XTENSA_NR_IRQS]; void __init variant_init_irq(void) { int irq, n; writeb(0, S6_REG_GPIO + S6_GPIO_BANK(0) + S6_GPIO_IE); for (irq = n = 0; irq < XTENSA_NR_IRQS; irq++) { const signed char *mapping = platform_irq_mappings[irq]; int alone = 1; u8 mask; if (!mapping) continue; for(mask = 0; *mapping != -1; mapping++) switch (*mapping) { case S6_INTC_GPIO(0): mask |= 1 << 0; break; case S6_INTC_GPIO(1): mask |= 1 << 1; break; case S6_INTC_GPIO(2): mask |= 1 << 2; break; case S6_INTC_GPIO(3): mask |= 0x1f << 3; break; default: alone = 0; } if (mask) { int cirq, i; if (!alone) { printk(KERN_ERR "chained irq chips can't share" " parent irq %i\n", irq); continue; } demux_masks[n] = mask; cirq = IRQ_BASE - 1; do { i = ffs(mask); cirq += i; mask >>= i; irq_set_chip(cirq, &gpioirqs); irq_set_irq_type(irq, IRQ_TYPE_LEVEL_LOW); } while (mask); irq_set_handler_data(irq, demux_masks + n); irq_set_chained_handler(irq, demux_irqs); if (++n == ARRAY_SIZE(demux_masks)) break; } } }