diff options
author | GuanXuetao <gxt@mprc.pku.edu.cn> | 2011-01-15 18:19:35 +0800 |
---|---|---|
committer | GuanXuetao <gxt@mprc.pku.edu.cn> | 2011-03-17 09:19:10 +0800 |
commit | 752bcb4d02ccfd5e7a8d810424154169b4cca8ae (patch) | |
tree | d435061fe9405f70d51aef55392062460515ca30 /arch/unicore32/kernel | |
parent | 02b2ee16cc31df2b23d6f6c68a597d947f6c10e8 (diff) | |
download | linux-752bcb4d02ccfd5e7a8d810424154169b4cca8ae.tar.bz2 |
unicore32 core architecture: interrupts ang gpio handling
This patch implements interrupts and gpio handling.
UniCore32 has 9 gpio interrupt sources.
And gpio device operations are also here.
Signed-off-by: Guan Xuetao <gxt@mprc.pku.edu.cn>
Diffstat (limited to 'arch/unicore32/kernel')
-rw-r--r-- | arch/unicore32/kernel/gpio.c | 122 | ||||
-rw-r--r-- | arch/unicore32/kernel/irq.c | 426 |
2 files changed, 548 insertions, 0 deletions
diff --git a/arch/unicore32/kernel/gpio.c b/arch/unicore32/kernel/gpio.c new file mode 100644 index 000000000000..4cb28308bb5f --- /dev/null +++ b/arch/unicore32/kernel/gpio.c @@ -0,0 +1,122 @@ +/* + * linux/arch/unicore32/kernel/gpio.c + * + * Code specific to PKUnity SoC and UniCore ISA + * + * Maintained by GUAN Xue-tao <gxt@mprc.pku.edu.cn> + * Copyright (C) 2001-2010 Guan Xuetao + * + * 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. + */ +/* in FPGA, no GPIO support */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/gpio.h> +#include <mach/hardware.h> + +#ifdef CONFIG_LEDS +#include <linux/leds.h> +#include <linux/platform_device.h> + +static const struct gpio_led puv3_gpio_leds[] = { + { .name = "cpuhealth", .gpio = GPO_CPU_HEALTH, .active_low = 0, + .default_trigger = "heartbeat", }, + { .name = "hdd_led", .gpio = GPO_HDD_LED, .active_low = 1, + .default_trigger = "ide-disk", }, +}; + +static const struct gpio_led_platform_data puv3_gpio_led_data = { + .num_leds = ARRAY_SIZE(puv3_gpio_leds), + .leds = (void *) puv3_gpio_leds, +}; + +static struct platform_device puv3_gpio_gpio_leds = { + .name = "leds-gpio", + .id = -1, + .dev = { + .platform_data = (void *) &puv3_gpio_led_data, + } +}; + +static int __init puv3_gpio_leds_init(void) +{ + platform_device_register(&puv3_gpio_gpio_leds); + return 0; +} + +device_initcall(puv3_gpio_leds_init); +#endif + +static int puv3_gpio_get(struct gpio_chip *chip, unsigned offset) +{ + return GPIO_GPLR & GPIO_GPIO(offset); +} + +static void puv3_gpio_set(struct gpio_chip *chip, unsigned offset, int value) +{ + if (value) + GPIO_GPSR = GPIO_GPIO(offset); + else + GPIO_GPCR = GPIO_GPIO(offset); +} + +static int puv3_direction_input(struct gpio_chip *chip, unsigned offset) +{ + unsigned long flags; + + local_irq_save(flags); + GPIO_GPDR &= ~GPIO_GPIO(offset); + local_irq_restore(flags); + return 0; +} + +static int puv3_direction_output(struct gpio_chip *chip, unsigned offset, + int value) +{ + unsigned long flags; + + local_irq_save(flags); + puv3_gpio_set(chip, offset, value); + GPIO_GPDR |= GPIO_GPIO(offset); + local_irq_restore(flags); + return 0; +} + +static struct gpio_chip puv3_gpio_chip = { + .label = "gpio", + .direction_input = puv3_direction_input, + .direction_output = puv3_direction_output, + .set = puv3_gpio_set, + .get = puv3_gpio_get, + .base = 0, + .ngpio = GPIO_MAX + 1, +}; + +void __init puv3_init_gpio(void) +{ + GPIO_GPDR = GPIO_DIR; +#if defined(CONFIG_PUV3_NB0916) || defined(CONFIG_PUV3_SMW0919) \ + || defined(CONFIG_PUV3_DB0913) + gpio_set_value(GPO_WIFI_EN, 1); + gpio_set_value(GPO_HDD_LED, 1); + gpio_set_value(GPO_VGA_EN, 1); + gpio_set_value(GPO_LCD_EN, 1); + gpio_set_value(GPO_CAM_PWR_EN, 0); + gpio_set_value(GPO_LCD_VCC_EN, 1); + gpio_set_value(GPO_SOFT_OFF, 1); + gpio_set_value(GPO_BT_EN, 1); + gpio_set_value(GPO_FAN_ON, 0); + gpio_set_value(GPO_SPKR, 0); + gpio_set_value(GPO_CPU_HEALTH, 1); + gpio_set_value(GPO_LAN_SEL, 1); +/* + * DO NOT modify the GPO_SET_V1 and GPO_SET_V2 in kernel + * gpio_set_value(GPO_SET_V1, 1); + * gpio_set_value(GPO_SET_V2, 1); + */ +#endif + gpiochip_add(&puv3_gpio_chip); +} diff --git a/arch/unicore32/kernel/irq.c b/arch/unicore32/kernel/irq.c new file mode 100644 index 000000000000..7c211f597833 --- /dev/null +++ b/arch/unicore32/kernel/irq.c @@ -0,0 +1,426 @@ +/* + * linux/arch/unicore32/kernel/irq.c + * + * Code specific to PKUnity SoC and UniCore ISA + * + * Copyright (C) 2001-2010 GUAN Xue-tao + * + * 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_stat.h> +#include <linux/module.h> +#include <linux/signal.h> +#include <linux/ioport.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/random.h> +#include <linux/smp.h> +#include <linux/init.h> +#include <linux/seq_file.h> +#include <linux/errno.h> +#include <linux/list.h> +#include <linux/kallsyms.h> +#include <linux/proc_fs.h> +#include <linux/sysdev.h> +#include <linux/gpio.h> + +#include <asm/system.h> +#include <mach/hardware.h> + +#include "setup.h" + +/* + * PKUnity GPIO edge detection for IRQs: + * IRQs are generated on Falling-Edge, Rising-Edge, or both. + * Use this instead of directly setting GRER/GFER. + */ +static int GPIO_IRQ_rising_edge; +static int GPIO_IRQ_falling_edge; +static int GPIO_IRQ_mask = 0; + +#define GPIO_MASK(irq) (1 << (irq - IRQ_GPIO0)) + +static int puv3_gpio_type(unsigned int irq, unsigned int type) +{ + unsigned int mask; + + if (irq < IRQ_GPIOHIGH) + mask = 1 << irq; + else + mask = GPIO_MASK(irq); + + if (type == IRQ_TYPE_PROBE) { + if ((GPIO_IRQ_rising_edge | GPIO_IRQ_falling_edge) & mask) + return 0; + type = IRQ_TYPE_EDGE_RISING | IRQ_TYPE_EDGE_FALLING; + } + + if (type & IRQ_TYPE_EDGE_RISING) + GPIO_IRQ_rising_edge |= mask; + else + GPIO_IRQ_rising_edge &= ~mask; + if (type & IRQ_TYPE_EDGE_FALLING) + GPIO_IRQ_falling_edge |= mask; + else + GPIO_IRQ_falling_edge &= ~mask; + + GPIO_GRER = GPIO_IRQ_rising_edge & GPIO_IRQ_mask; + GPIO_GFER = GPIO_IRQ_falling_edge & GPIO_IRQ_mask; + + return 0; +} + +/* + * GPIO IRQs must be acknowledged. This is for IRQs from 0 to 7. + */ +static void puv3_low_gpio_ack(unsigned int irq) +{ + GPIO_GEDR = (1 << irq); +} + +static void puv3_low_gpio_mask(unsigned int irq) +{ + INTC_ICMR &= ~(1 << irq); +} + +static void puv3_low_gpio_unmask(unsigned int irq) +{ + INTC_ICMR |= 1 << irq; +} + +static int puv3_low_gpio_wake(unsigned int irq, unsigned int on) +{ + if (on) + PM_PWER |= 1 << irq; + else + PM_PWER &= ~(1 << irq); + return 0; +} + +static struct irq_chip puv3_low_gpio_chip = { + .name = "GPIO-low", + .ack = puv3_low_gpio_ack, + .mask = puv3_low_gpio_mask, + .unmask = puv3_low_gpio_unmask, + .set_type = puv3_gpio_type, + .set_wake = puv3_low_gpio_wake, +}; + +/* + * IRQ8 (GPIO0 through 27) handler. We enter here with the + * irq_controller_lock held, and IRQs disabled. Decode the IRQ + * and call the handler. + */ +static void +puv3_gpio_handler(unsigned int irq, struct irq_desc *desc) +{ + unsigned int mask; + + mask = GPIO_GEDR; + do { + /* + * clear down all currently active IRQ sources. + * We will be processing them all. + */ + GPIO_GEDR = mask; + + irq = IRQ_GPIO0; + do { + if (mask & 1) + generic_handle_irq(irq); + mask >>= 1; + irq++; + } while (mask); + mask = GPIO_GEDR; + } while (mask); +} + +/* + * GPIO0-27 edge IRQs need to be handled specially. + * In addition, the IRQs are all collected up into one bit in the + * interrupt controller registers. + */ +static void puv3_high_gpio_ack(unsigned int irq) +{ + unsigned int mask = GPIO_MASK(irq); + + GPIO_GEDR = mask; +} + +static void puv3_high_gpio_mask(unsigned int irq) +{ + unsigned int mask = GPIO_MASK(irq); + + GPIO_IRQ_mask &= ~mask; + + GPIO_GRER &= ~mask; + GPIO_GFER &= ~mask; +} + +static void puv3_high_gpio_unmask(unsigned int irq) +{ + unsigned int mask = GPIO_MASK(irq); + + GPIO_IRQ_mask |= mask; + + GPIO_GRER = GPIO_IRQ_rising_edge & GPIO_IRQ_mask; + GPIO_GFER = GPIO_IRQ_falling_edge & GPIO_IRQ_mask; +} + +static int puv3_high_gpio_wake(unsigned int irq, unsigned int on) +{ + if (on) + PM_PWER |= PM_PWER_GPIOHIGH; + else + PM_PWER &= ~PM_PWER_GPIOHIGH; + return 0; +} + +static struct irq_chip puv3_high_gpio_chip = { + .name = "GPIO-high", + .ack = puv3_high_gpio_ack, + .mask = puv3_high_gpio_mask, + .unmask = puv3_high_gpio_unmask, + .set_type = puv3_gpio_type, + .set_wake = puv3_high_gpio_wake, +}; + +/* + * We don't need to ACK IRQs on the PKUnity unless they're GPIOs + * this is for internal IRQs i.e. from 8 to 31. + */ +static void puv3_mask_irq(unsigned int irq) +{ + INTC_ICMR &= ~(1 << irq); +} + +static void puv3_unmask_irq(unsigned int irq) +{ + INTC_ICMR |= (1 << irq); +} + +/* + * Apart form GPIOs, only the RTC alarm can be a wakeup event. + */ +static int puv3_set_wake(unsigned int irq, unsigned int on) +{ + if (irq == IRQ_RTCAlarm) { + if (on) + PM_PWER |= PM_PWER_RTC; + else + PM_PWER &= ~PM_PWER_RTC; + return 0; + } + return -EINVAL; +} + +static struct irq_chip puv3_normal_chip = { + .name = "PKUnity-v3", + .ack = puv3_mask_irq, + .mask = puv3_mask_irq, + .unmask = puv3_unmask_irq, + .set_wake = puv3_set_wake, +}; + +static struct resource irq_resource = { + .name = "irqs", + .start = PKUNITY_INTC_BASE, + .end = PKUNITY_INTC_BASE + 0xFFFFF, +}; + +static struct puv3_irq_state { + unsigned int saved; + unsigned int icmr; + unsigned int iclr; + unsigned int iccr; +} puv3_irq_state; + +static int puv3_irq_suspend(struct sys_device *dev, pm_message_t state) +{ + struct puv3_irq_state *st = &puv3_irq_state; + + st->saved = 1; + st->icmr = INTC_ICMR; + st->iclr = INTC_ICLR; + st->iccr = INTC_ICCR; + + /* + * Disable all GPIO-based interrupts. + */ + INTC_ICMR &= ~(0x1ff); + + /* + * Set the appropriate edges for wakeup. + */ + GPIO_GRER = PM_PWER & GPIO_IRQ_rising_edge; + GPIO_GFER = PM_PWER & GPIO_IRQ_falling_edge; + + /* + * Clear any pending GPIO interrupts. + */ + GPIO_GEDR = GPIO_GEDR; + + return 0; +} + +static int puv3_irq_resume(struct sys_device *dev) +{ + struct puv3_irq_state *st = &puv3_irq_state; + + if (st->saved) { + INTC_ICCR = st->iccr; + INTC_ICLR = st->iclr; + + GPIO_GRER = GPIO_IRQ_rising_edge & GPIO_IRQ_mask; + GPIO_GFER = GPIO_IRQ_falling_edge & GPIO_IRQ_mask; + + INTC_ICMR = st->icmr; + } + return 0; +} + +static struct sysdev_class puv3_irq_sysclass = { + .name = "pkunity-irq", + .suspend = puv3_irq_suspend, + .resume = puv3_irq_resume, +}; + +static struct sys_device puv3_irq_device = { + .id = 0, + .cls = &puv3_irq_sysclass, +}; + +static int __init puv3_irq_init_devicefs(void) +{ + sysdev_class_register(&puv3_irq_sysclass); + return sysdev_register(&puv3_irq_device); +} + +device_initcall(puv3_irq_init_devicefs); + +void __init init_IRQ(void) +{ + unsigned int irq; + + request_resource(&iomem_resource, &irq_resource); + + /* disable all IRQs */ + INTC_ICMR = 0; + + /* all IRQs are IRQ, not REAL */ + INTC_ICLR = 0; + + /* clear all GPIO edge detects */ + GPIO_GPIR = FMASK(8, 0) & ~FIELD(1, 1, GPI_SOFF_REQ); + GPIO_GFER = 0; + GPIO_GRER = 0; + GPIO_GEDR = 0x0FFFFFFF; + + INTC_ICCR = 1; + + for (irq = 0; irq < IRQ_GPIOHIGH; irq++) { + set_irq_chip(irq, &puv3_low_gpio_chip); + set_irq_handler(irq, handle_edge_irq); + irq_modify_status(irq, + IRQ_NOREQUEST | IRQ_NOPROBE | IRQ_NOAUTOEN, + 0); + } + + for (irq = IRQ_GPIOHIGH + 1; irq < IRQ_GPIO0; irq++) { + set_irq_chip(irq, &puv3_normal_chip); + set_irq_handler(irq, handle_level_irq); + irq_modify_status(irq, + IRQ_NOREQUEST | IRQ_NOAUTOEN, + IRQ_NOPROBE); + } + + for (irq = IRQ_GPIO0; irq <= IRQ_GPIO27; irq++) { + set_irq_chip(irq, &puv3_high_gpio_chip); + set_irq_handler(irq, handle_edge_irq); + irq_modify_status(irq, + IRQ_NOREQUEST | IRQ_NOPROBE | IRQ_NOAUTOEN, + 0); + } + + /* + * Install handler for GPIO 0-27 edge detect interrupts + */ + set_irq_chip(IRQ_GPIOHIGH, &puv3_normal_chip); + set_irq_chained_handler(IRQ_GPIOHIGH, puv3_gpio_handler); + +#ifdef CONFIG_PUV3_GPIO + puv3_init_gpio(); +#endif +} + +int show_interrupts(struct seq_file *p, void *v) +{ + int i = *(loff_t *) v, cpu; + struct irq_desc *desc; + struct irqaction *action; + unsigned long flags; + + if (i == 0) { + char cpuname[12]; + + seq_printf(p, " "); + for_each_present_cpu(cpu) { + sprintf(cpuname, "CPU%d", cpu); + seq_printf(p, " %10s", cpuname); + } + seq_putc(p, '\n'); + } + + if (i < nr_irqs) { + desc = irq_to_desc(i); + raw_spin_lock_irqsave(&desc->lock, flags); + action = desc->action; + if (!action) + goto unlock; + + seq_printf(p, "%3d: ", i); + for_each_present_cpu(cpu) + seq_printf(p, "%10u ", kstat_irqs_cpu(i, cpu)); + seq_printf(p, " %10s", desc->chip->name ? : "-"); + seq_printf(p, " %s", action->name); + for (action = action->next; action; action = action->next) + seq_printf(p, ", %s", action->name); + + seq_putc(p, '\n'); +unlock: + raw_spin_unlock_irqrestore(&desc->lock, flags); + } else if (i == nr_irqs) { + seq_printf(p, "Error in interrupt!\n"); + } + return 0; +} + +/* + * do_IRQ handles all hardware IRQ's. Decoded IRQs should not + * come via this function. Instead, they should provide their + * own 'handler' + */ +asmlinkage void asm_do_IRQ(unsigned int irq, struct pt_regs *regs) +{ + struct pt_regs *old_regs = set_irq_regs(regs); + + irq_enter(); + + /* + * Some hardware gives randomly wrong interrupts. Rather + * than crashing, do something sensible. + */ + if (unlikely(irq >= nr_irqs)) { + if (printk_ratelimit()) + printk(KERN_WARNING "Bad IRQ%u\n", irq); + ack_bad_irq(irq); + } else { + generic_handle_irq(irq); + } + + irq_exit(); + set_irq_regs(old_regs); +} + |