diff options
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/Kconfig | 2 | ||||
-rw-r--r-- | drivers/Makefile | 2 | ||||
-rw-r--r-- | drivers/clk/Makefile | 1 | ||||
-rw-r--r-- | drivers/clk/clk-bcm2835.c | 59 | ||||
-rw-r--r-- | drivers/clocksource/Makefile | 2 | ||||
-rw-r--r-- | drivers/clocksource/bcm2835_timer.c | 161 | ||||
-rw-r--r-- | drivers/irqchip/Kconfig | 1 | ||||
-rw-r--r-- | drivers/irqchip/Makefile | 1 | ||||
-rw-r--r-- | drivers/irqchip/irq-bcm2835.c | 223 |
9 files changed, 452 insertions, 0 deletions
diff --git a/drivers/Kconfig b/drivers/Kconfig index ece958d3762e..36d3daa19a74 100644 --- a/drivers/Kconfig +++ b/drivers/Kconfig @@ -152,4 +152,6 @@ source "drivers/vme/Kconfig" source "drivers/pwm/Kconfig" +source "drivers/irqchip/Kconfig" + endmenu diff --git a/drivers/Makefile b/drivers/Makefile index 5b421840c48d..8c30e73cd94c 100644 --- a/drivers/Makefile +++ b/drivers/Makefile @@ -5,6 +5,8 @@ # Rewritten to use lists instead of if-statements. # +obj-y += irqchip/ + # GPIO must come after pinctrl as gpios may need to mux pins etc obj-y += pinctrl/ obj-y += gpio/ diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile index b7b862077d88..2b861625bdae 100644 --- a/drivers/clk/Makefile +++ b/drivers/clk/Makefile @@ -4,6 +4,7 @@ obj-$(CONFIG_CLKDEV_LOOKUP) += clkdev.o obj-$(CONFIG_COMMON_CLK) += clk.o clk-fixed-rate.o clk-gate.o \ clk-mux.o clk-divider.o clk-fixed-factor.o # SoCs specific +obj-$(CONFIG_ARCH_BCM2835) += clk-bcm2835.o obj-$(CONFIG_ARCH_NOMADIK) += clk-nomadik.o obj-$(CONFIG_ARCH_HIGHBANK) += clk-highbank.o obj-$(CONFIG_ARCH_MXS) += mxs/ diff --git a/drivers/clk/clk-bcm2835.c b/drivers/clk/clk-bcm2835.c new file mode 100644 index 000000000000..67ad16b20b81 --- /dev/null +++ b/drivers/clk/clk-bcm2835.c @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2010 Broadcom + * Copyright (C) 2012 Stephen Warren + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <linux/clk-provider.h> +#include <linux/clkdev.h> +#include <linux/clk/bcm2835.h> + +/* + * These are fixed clocks. They're probably not all root clocks and it may + * be possible to turn them on and off but until this is mapped out better + * it's the only way they can be used. + */ +void __init bcm2835_init_clocks(void) +{ + struct clk *clk; + int ret; + + clk = clk_register_fixed_rate(NULL, "sys_pclk", NULL, CLK_IS_ROOT, + 250000000); + if (!clk) + pr_err("sys_pclk not registered\n"); + + clk = clk_register_fixed_rate(NULL, "apb_pclk", NULL, CLK_IS_ROOT, + 126000000); + if (!clk) + pr_err("apb_pclk not registered\n"); + + clk = clk_register_fixed_rate(NULL, "uart0_pclk", NULL, CLK_IS_ROOT, + 3000000); + if (!clk) + pr_err("uart0_pclk not registered\n"); + ret = clk_register_clkdev(clk, NULL, "20201000.uart"); + if (ret) + pr_err("uart0_pclk alias not registered\n"); + + clk = clk_register_fixed_rate(NULL, "uart1_pclk", NULL, CLK_IS_ROOT, + 125000000); + if (!clk) + pr_err("uart1_pclk not registered\n"); + ret = clk_register_clkdev(clk, NULL, "20215000.uart"); + if (ret) + pr_err("uart0_pclk alias not registered\n"); +} diff --git a/drivers/clocksource/Makefile b/drivers/clocksource/Makefile index 65919901a301..603be366f762 100644 --- a/drivers/clocksource/Makefile +++ b/drivers/clocksource/Makefile @@ -13,4 +13,6 @@ obj-$(CONFIG_DW_APB_TIMER) += dw_apb_timer.o obj-$(CONFIG_DW_APB_TIMER_OF) += dw_apb_timer_of.o obj-$(CONFIG_CLKSRC_DBX500_PRCMU) += clksrc-dbx500-prcmu.o obj-$(CONFIG_ARMADA_370_XP_TIMER) += time-armada-370-xp.o +obj-$(CONFIG_ARCH_BCM2835) += bcm2835_timer.o + obj-$(CONFIG_CLKSRC_ARM_GENERIC) += arm_generic.o diff --git a/drivers/clocksource/bcm2835_timer.c b/drivers/clocksource/bcm2835_timer.c new file mode 100644 index 000000000000..bc19f12c20ce --- /dev/null +++ b/drivers/clocksource/bcm2835_timer.c @@ -0,0 +1,161 @@ +/* + * Copyright 2012 Simon Arlott + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <linux/bcm2835_timer.h> +#include <linux/bitops.h> +#include <linux/clockchips.h> +#include <linux/clocksource.h> +#include <linux/interrupt.h> +#include <linux/irqreturn.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of_address.h> +#include <linux/of_irq.h> +#include <linux/of_platform.h> +#include <linux/slab.h> +#include <linux/string.h> + +#include <asm/sched_clock.h> +#include <asm/irq.h> + +#define REG_CONTROL 0x00 +#define REG_COUNTER_LO 0x04 +#define REG_COUNTER_HI 0x08 +#define REG_COMPARE(n) (0x0c + (n) * 4) +#define MAX_TIMER 3 +#define DEFAULT_TIMER 3 + +struct bcm2835_timer { + void __iomem *control; + void __iomem *compare; + int match_mask; + struct clock_event_device evt; + struct irqaction act; +}; + +static void __iomem *system_clock __read_mostly; + +static u32 notrace bcm2835_sched_read(void) +{ + return readl_relaxed(system_clock); +} + +static void bcm2835_time_set_mode(enum clock_event_mode mode, + struct clock_event_device *evt_dev) +{ + switch (mode) { + case CLOCK_EVT_MODE_ONESHOT: + case CLOCK_EVT_MODE_UNUSED: + case CLOCK_EVT_MODE_SHUTDOWN: + case CLOCK_EVT_MODE_RESUME: + break; + default: + WARN(1, "%s: unhandled event mode %d\n", __func__, mode); + break; + } +} + +static int bcm2835_time_set_next_event(unsigned long event, + struct clock_event_device *evt_dev) +{ + struct bcm2835_timer *timer = container_of(evt_dev, + struct bcm2835_timer, evt); + writel_relaxed(readl_relaxed(system_clock) + event, + timer->compare); + return 0; +} + +static irqreturn_t bcm2835_time_interrupt(int irq, void *dev_id) +{ + struct bcm2835_timer *timer = dev_id; + void (*event_handler)(struct clock_event_device *); + if (readl_relaxed(timer->control) & timer->match_mask) { + writel_relaxed(timer->match_mask, timer->control); + + event_handler = ACCESS_ONCE(timer->evt.event_handler); + if (event_handler) + event_handler(&timer->evt); + return IRQ_HANDLED; + } else { + return IRQ_NONE; + } +} + +static struct of_device_id bcm2835_time_match[] __initconst = { + { .compatible = "brcm,bcm2835-system-timer" }, + {} +}; + +static void __init bcm2835_time_init(void) +{ + struct device_node *node; + void __iomem *base; + u32 freq; + int irq; + struct bcm2835_timer *timer; + + node = of_find_matching_node(NULL, bcm2835_time_match); + if (!node) + panic("No bcm2835 timer node"); + + base = of_iomap(node, 0); + if (!base) + panic("Can't remap registers"); + + if (of_property_read_u32(node, "clock-frequency", &freq)) + panic("Can't read clock-frequency"); + + system_clock = base + REG_COUNTER_LO; + setup_sched_clock(bcm2835_sched_read, 32, freq); + + clocksource_mmio_init(base + REG_COUNTER_LO, node->name, + freq, 300, 32, clocksource_mmio_readl_up); + + irq = irq_of_parse_and_map(node, DEFAULT_TIMER); + if (irq <= 0) + panic("Can't parse IRQ"); + + timer = kzalloc(sizeof(*timer), GFP_KERNEL); + if (!timer) + panic("Can't allocate timer struct\n"); + + timer->control = base + REG_CONTROL; + timer->compare = base + REG_COMPARE(DEFAULT_TIMER); + timer->match_mask = BIT(DEFAULT_TIMER); + timer->evt.name = node->name; + timer->evt.rating = 300; + timer->evt.features = CLOCK_EVT_FEAT_ONESHOT; + timer->evt.set_mode = bcm2835_time_set_mode; + timer->evt.set_next_event = bcm2835_time_set_next_event; + timer->evt.cpumask = cpumask_of(0); + timer->act.name = node->name; + timer->act.flags = IRQF_TIMER | IRQF_SHARED; + timer->act.dev_id = timer; + timer->act.handler = bcm2835_time_interrupt; + + if (setup_irq(irq, &timer->act)) + panic("Can't set up timer IRQ\n"); + + clockevents_config_and_register(&timer->evt, freq, 0xf, 0xffffffff); + + pr_info("bcm2835: system timer (irq = %d)\n", irq); +} + +struct sys_timer bcm2835_timer = { + .init = bcm2835_time_init, +}; diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig new file mode 100644 index 000000000000..1bb8bf6d7fd4 --- /dev/null +++ b/drivers/irqchip/Kconfig @@ -0,0 +1 @@ +# empty diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile new file mode 100644 index 000000000000..054321db4350 --- /dev/null +++ b/drivers/irqchip/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_ARCH_BCM2835) += irq-bcm2835.o diff --git a/drivers/irqchip/irq-bcm2835.c b/drivers/irqchip/irq-bcm2835.c new file mode 100644 index 000000000000..dc670ccc6978 --- /dev/null +++ b/drivers/irqchip/irq-bcm2835.c @@ -0,0 +1,223 @@ +/* + * Copyright 2010 Broadcom + * Copyright 2012 Simon Arlott, Chris Boot, Stephen Warren + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + * + * Quirk 1: Shortcut interrupts don't set the bank 1/2 register pending bits + * + * If an interrupt fires on bank 1 that isn't in the shortcuts list, bit 8 + * on bank 0 is set to signify that an interrupt in bank 1 has fired, and + * to look in the bank 1 status register for more information. + * + * If an interrupt fires on bank 1 that _is_ in the shortcuts list, its + * shortcut bit in bank 0 is set as well as its interrupt bit in the bank 1 + * status register, but bank 0 bit 8 is _not_ set. + * + * Quirk 2: You can't mask the register 1/2 pending interrupts + * + * In a proper cascaded interrupt controller, the interrupt lines with + * cascaded interrupt controllers on them are just normal interrupt lines. + * You can mask the interrupts and get on with things. With this controller + * you can't do that. + * + * Quirk 3: The shortcut interrupts can't be (un)masked in bank 0 + * + * Those interrupts that have shortcuts can only be masked/unmasked in + * their respective banks' enable/disable registers. Doing so in the bank 0 + * enable/disable registers has no effect. + * + * The FIQ control register: + * Bits 0-6: IRQ (index in order of interrupts from banks 1, 2, then 0) + * Bit 7: Enable FIQ generation + * Bits 8+: Unused + * + * An interrupt must be disabled before configuring it for FIQ generation + * otherwise both handlers will fire at the same time! + */ + +#include <linux/io.h> +#include <linux/slab.h> +#include <linux/of_address.h> +#include <linux/of_irq.h> +#include <linux/irqdomain.h> +#include <linux/irqchip/bcm2835.h> + +#include <asm/exception.h> + +/* Put the bank and irq (32 bits) into the hwirq */ +#define MAKE_HWIRQ(b, n) ((b << 5) | (n)) +#define HWIRQ_BANK(i) (i >> 5) +#define HWIRQ_BIT(i) BIT(i & 0x1f) + +#define NR_IRQS_BANK0 8 +#define BANK0_HWIRQ_MASK 0xff +/* Shortcuts can't be disabled so any unknown new ones need to be masked */ +#define SHORTCUT1_MASK 0x00007c00 +#define SHORTCUT2_MASK 0x001f8000 +#define SHORTCUT_SHIFT 10 +#define BANK1_HWIRQ BIT(8) +#define BANK2_HWIRQ BIT(9) +#define BANK0_VALID_MASK (BANK0_HWIRQ_MASK | BANK1_HWIRQ | BANK2_HWIRQ \ + | SHORTCUT1_MASK | SHORTCUT2_MASK) + +#define REG_FIQ_CONTROL 0x0c + +#define NR_BANKS 3 +#define IRQS_PER_BANK 32 + +static int reg_pending[] __initconst = { 0x00, 0x04, 0x08 }; +static int reg_enable[] __initconst = { 0x18, 0x10, 0x14 }; +static int reg_disable[] __initconst = { 0x24, 0x1c, 0x20 }; +static int bank_irqs[] __initconst = { 8, 32, 32 }; + +static const int shortcuts[] = { + 7, 9, 10, 18, 19, /* Bank 1 */ + 21, 22, 23, 24, 25, 30 /* Bank 2 */ +}; + +struct armctrl_ic { + void __iomem *base; + void __iomem *pending[NR_BANKS]; + void __iomem *enable[NR_BANKS]; + void __iomem *disable[NR_BANKS]; + struct irq_domain *domain; +}; + +static struct armctrl_ic intc __read_mostly; + +static void armctrl_mask_irq(struct irq_data *d) +{ + writel_relaxed(HWIRQ_BIT(d->hwirq), intc.disable[HWIRQ_BANK(d->hwirq)]); +} + +static void armctrl_unmask_irq(struct irq_data *d) +{ + writel_relaxed(HWIRQ_BIT(d->hwirq), intc.enable[HWIRQ_BANK(d->hwirq)]); +} + +static struct irq_chip armctrl_chip = { + .name = "ARMCTRL-level", + .irq_mask = armctrl_mask_irq, + .irq_unmask = armctrl_unmask_irq +}; + +static int armctrl_xlate(struct irq_domain *d, struct device_node *ctrlr, + const u32 *intspec, unsigned int intsize, + unsigned long *out_hwirq, unsigned int *out_type) +{ + if (WARN_ON(intsize != 2)) + return -EINVAL; + + if (WARN_ON(intspec[0] >= NR_BANKS)) + return -EINVAL; + + if (WARN_ON(intspec[1] >= IRQS_PER_BANK)) + return -EINVAL; + + if (WARN_ON(intspec[0] == 0 && intspec[1] >= NR_IRQS_BANK0)) + return -EINVAL; + + *out_hwirq = MAKE_HWIRQ(intspec[0], intspec[1]); + *out_type = IRQ_TYPE_NONE; + return 0; +} + +static struct irq_domain_ops armctrl_ops = { + .xlate = armctrl_xlate +}; + +static int __init armctrl_of_init(struct device_node *node, + struct device_node *parent) +{ + void __iomem *base; + int irq, b, i; + + base = of_iomap(node, 0); + if (!base) + panic("%s: unable to map IC registers\n", + node->full_name); + + intc.domain = irq_domain_add_linear(node, MAKE_HWIRQ(NR_BANKS, 0), + &armctrl_ops, NULL); + if (!intc.domain) + panic("%s: unable to create IRQ domain\n", node->full_name); + + for (b = 0; b < NR_BANKS; b++) { + intc.pending[b] = base + reg_pending[b]; + intc.enable[b] = base + reg_enable[b]; + intc.disable[b] = base + reg_disable[b]; + + for (i = 0; i < bank_irqs[b]; i++) { + irq = irq_create_mapping(intc.domain, MAKE_HWIRQ(b, i)); + BUG_ON(irq <= 0); + irq_set_chip_and_handler(irq, &armctrl_chip, + handle_level_irq); + set_irq_flags(irq, IRQF_VALID | IRQF_PROBE); + } + } + return 0; +} + +static struct of_device_id irq_of_match[] __initconst = { + { .compatible = "brcm,bcm2835-armctrl-ic", .data = armctrl_of_init } +}; + +void __init bcm2835_init_irq(void) +{ + of_irq_init(irq_of_match); +} + +/* + * Handle each interrupt across the entire interrupt controller. This reads the + * status register before handling each interrupt, which is necessary given that + * handle_IRQ may briefly re-enable interrupts for soft IRQ handling. + */ + +static void armctrl_handle_bank(int bank, struct pt_regs *regs) +{ + u32 stat, irq; + + while ((stat = readl_relaxed(intc.pending[bank]))) { + irq = MAKE_HWIRQ(bank, ffs(stat) - 1); + handle_IRQ(irq_linear_revmap(intc.domain, irq), regs); + } +} + +static void armctrl_handle_shortcut(int bank, struct pt_regs *regs, + u32 stat) +{ + u32 irq = MAKE_HWIRQ(bank, shortcuts[ffs(stat >> SHORTCUT_SHIFT) - 1]); + handle_IRQ(irq_linear_revmap(intc.domain, irq), regs); +} + +asmlinkage void __exception_irq_entry bcm2835_handle_irq( + struct pt_regs *regs) +{ + u32 stat, irq; + + while ((stat = readl_relaxed(intc.pending[0]) & BANK0_VALID_MASK)) { + if (stat & BANK0_HWIRQ_MASK) { + irq = MAKE_HWIRQ(0, ffs(stat & BANK0_HWIRQ_MASK) - 1); + handle_IRQ(irq_linear_revmap(intc.domain, irq), regs); + } else if (stat & SHORTCUT1_MASK) { + armctrl_handle_shortcut(1, regs, stat & SHORTCUT1_MASK); + } else if (stat & SHORTCUT2_MASK) { + armctrl_handle_shortcut(2, regs, stat & SHORTCUT2_MASK); + } else if (stat & BANK1_HWIRQ) { + armctrl_handle_bank(1, regs); + } else if (stat & BANK2_HWIRQ) { + armctrl_handle_bank(2, regs); + } else { + BUG(); + } + } +} |