diff options
Diffstat (limited to 'target/linux/brcm63xx/files/arch/mips/bcm963xx')
9 files changed, 1516 insertions, 0 deletions
diff --git a/target/linux/brcm63xx/files/arch/mips/bcm963xx/Makefile b/target/linux/brcm63xx/files/arch/mips/bcm963xx/Makefile new file mode 100644 index 000000000..a9d1e5597 --- /dev/null +++ b/target/linux/brcm63xx/files/arch/mips/bcm963xx/Makefile @@ -0,0 +1,9 @@ +# +# Makefile for the Broadcom BCM963xx SoC specific parts of the kernel +# +# Copyright (C) 2004 Broadcom Corporation +# +obj-y := irq.o prom.o setup.o time.o ser_init.o int-handler.o info.o wdt.o + +SRCBASE := $(TOPDIR) +EXTRA_CFLAGS += -I$(SRCBASE)/include diff --git a/target/linux/brcm63xx/files/arch/mips/bcm963xx/info.c b/target/linux/brcm63xx/files/arch/mips/bcm963xx/info.c new file mode 100644 index 000000000..d50b601e2 --- /dev/null +++ b/target/linux/brcm63xx/files/arch/mips/bcm963xx/info.c @@ -0,0 +1,102 @@ +/* + * $Id$ + * + * Copyright (C) 2007 OpenWrt.org + * Copyright (C) 2007 Gabor Juhos <juhosg at openwrt.org> + * Copyright (C) 2007 Florian Fainelli <florian@openwrt.org> + * + * 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. + */ + +#include <linux/types.h> +#include <linux/autoconf.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/module.h> + +#include <asm/bootinfo.h> +#include <asm/addrspace.h> +#include <asm/string.h> +#include <asm/mach-bcm963xx/bootloaders.h> + +static char *boot_loader_names[BOOT_LOADER_LAST+1] = { + [BOOT_LOADER_UNKNOWN] = "Unknown", + [BOOT_LOADER_CFE] = "CFE", + [BOOT_LOADER_REDBOOT] = "RedBoot", + [BOOT_LOADER_CFE2] = "CFEv2" +}; + +/* boot loaders specific definitions */ +#define CFE_EPTSEAL 0x43464531 /* CFE1 is the magic number to recognize CFE from other bootloaders */ + +int boot_loader_type; +/* + * Boot loader detection routines + */ +static int __init detect_cfe(void) +{ + /* + * This method only works, when we are booted directly from the CFE. + */ + uint32_t cfe_handle = (uint32_t) fw_arg0; + uint32_t cfe_a1_val = (uint32_t) fw_arg1; + uint32_t cfe_entry = (uint32_t) fw_arg2; + uint32_t cfe_seal = (uint32_t) fw_arg3; + + /* Check for CFE by finding the CFE magic number */ + if (cfe_seal != CFE_EPTSEAL) + /* We are not booted from CFE */ + return 0; + + /* cfe_a1_val must be 0, because only one CPU present in the ADM5120 SoC */ + if (cfe_a1_val != 0) + return 0; + + /* The cfe_handle, and the cfe_entry must be kernel mode addresses */ + if ((cfe_handle < KSEG0) || (cfe_entry < KSEG0)) + return 0; + + return 1; +} + +static int __init detect_redboot(void) +{ + /* On Inventel Livebox, the boot loader is passed as a command line argument, check for it */ + if (!strncmp(arcs_cmdline, "boot_loader=RedBoot", 19)) + return 1; + return 0; +} + +void __init detect_bootloader(void) +{ + if (detect_cfe()) { + boot_loader_type = BOOT_LOADER_CFE; + } + + if (detect_redboot()) { + boot_loader_type = BOOT_LOADER_REDBOOT; + } + else { + /* Some devices are using CFE, but it is not detected as is */ + boot_loader_type = BOOT_LOADER_CFE2; + } + printk("Boot loader is : %s\n", boot_loader_names[boot_loader_type]); +} + +void __init detect_board(void) +{ + switch (boot_loader_type) + { + case BOOT_LOADER_CFE: + break; + case BOOT_LOADER_REDBOOT: + break; + default: + break; + } +} + +EXPORT_SYMBOL(boot_loader_type); diff --git a/target/linux/brcm63xx/files/arch/mips/bcm963xx/int-handler.S b/target/linux/brcm63xx/files/arch/mips/bcm963xx/int-handler.S new file mode 100644 index 000000000..a7a9c9d20 --- /dev/null +++ b/target/linux/brcm63xx/files/arch/mips/bcm963xx/int-handler.S @@ -0,0 +1,59 @@ +/* +<:copyright-gpl + Copyright 2002 Broadcom Corp. All Rights Reserved. + + This program is free software; you can distribute it and/or modify it + under the terms of the GNU General Public License (Version 2) as + published by the Free Software Foundation. + + This program is distributed in the hope 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. +:> +*/ +/* + * Generic interrupt handler for Broadcom MIPS boards + */ + +#include <linux/autoconf.h> + +#include <asm/asm.h> +#include <asm/mipsregs.h> +#include <asm/regdef.h> +#include <asm/stackframe.h> + +/* + * MIPS IRQ Source + * -------- ------ + * 0 Software (ignored) + * 1 Software (ignored) + * 2 Combined hardware interrupt (hw0) + * 3 Hardware + * 4 Hardware + * 5 Hardware + * 6 Hardware + * 7 R4k timer + */ + + .text + .set noreorder + .set noat + .align 5 + NESTED(brcmIRQ, PT_SIZE, sp) + SAVE_ALL + CLI + .set noreorder + .set at + + jal plat_irq_dispatch + move a0, sp + + j ret_from_irq + nop + + END(brcmIRQ) diff --git a/target/linux/brcm63xx/files/arch/mips/bcm963xx/irq.c b/target/linux/brcm63xx/files/arch/mips/bcm963xx/irq.c new file mode 100644 index 000000000..962cd374d --- /dev/null +++ b/target/linux/brcm63xx/files/arch/mips/bcm963xx/irq.c @@ -0,0 +1,258 @@ +/* +<:copyright-gpl + Copyright 2002 Broadcom Corp. All Rights Reserved. + + This program is free software; you can distribute it and/or modify it + under the terms of the GNU General Public License (Version 2) as + published by the Free Software Foundation. + + This program is distributed in the hope 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. +:> +*/ +/* + * Interrupt control functions for Broadcom 963xx MIPS boards + */ + +#include <asm/atomic.h> + +#include <linux/delay.h> +#include <linux/init.h> +#include <linux/ioport.h> +#include <linux/irq.h> +#include <linux/interrupt.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/module.h> + +#include <asm/irq.h> +#include <asm/mipsregs.h> +#include <asm/addrspace.h> +#include <asm/signal.h> +#include <6348_map_part.h> +#include <6348_intr.h> +#include <bcm_map_part.h> +#include <bcm_intr.h> + +static void irq_dispatch_int(struct pt_regs *regs) +{ + unsigned int pendingIrqs; + static unsigned int irqBit; + static unsigned int isrNumber = 31; + + pendingIrqs = PERF->IrqStatus & PERF->IrqMask; + if (!pendingIrqs) { + return; + } + + while (1) { + irqBit <<= 1; + isrNumber++; + if (isrNumber == 32) { + isrNumber = 0; + irqBit = 0x1; + } + if (pendingIrqs & irqBit) { + PERF->IrqMask &= ~irqBit; // mask + do_IRQ(isrNumber + INTERNAL_ISR_TABLE_OFFSET); + break; + } + } +} + +static void irq_dispatch_ext(uint32 irq) +{ + if (!(PERF->ExtIrqCfg & (1 << (irq - INTERRUPT_ID_EXTERNAL_0 + EI_MASK_SHFT)))) { + printk("**** Ext IRQ mask. Should not dispatch ****\n"); + } + /* disable and clear interrupt in the controller */ + PERF->ExtIrqCfg |= (1 << (irq - INTERRUPT_ID_EXTERNAL_0 + EI_CLEAR_SHFT)); + PERF->ExtIrqCfg &= ~(1 << (irq - INTERRUPT_ID_EXTERNAL_0 + EI_MASK_SHFT)); + do_IRQ(irq); +} + + +extern void brcm_timer_interrupt(struct pt_regs *regs); + +asmlinkage void plat_irq_dispatch(struct pt_regs *regs) +{ + u32 cause; + while((cause = (read_c0_cause()& CAUSEF_IP))) { + if (cause & CAUSEF_IP7) + brcm_timer_interrupt(regs); + else if (cause & CAUSEF_IP2) + irq_dispatch_int(regs); + else if (cause & CAUSEF_IP3) + irq_dispatch_ext(INTERRUPT_ID_EXTERNAL_0); + else if (cause & CAUSEF_IP4) + irq_dispatch_ext(INTERRUPT_ID_EXTERNAL_1); + else if (cause & CAUSEF_IP5) + irq_dispatch_ext(INTERRUPT_ID_EXTERNAL_2); + else if (cause & CAUSEF_IP6) + irq_dispatch_ext(INTERRUPT_ID_EXTERNAL_3); + local_irq_disable(); + } +} + + +void enable_brcm_irq(unsigned int irq) +{ + unsigned long flags; + + local_irq_save(flags); + if( irq >= INTERNAL_ISR_TABLE_OFFSET ) { + PERF->IrqMask |= (1 << (irq - INTERNAL_ISR_TABLE_OFFSET)); + } + else if (irq >= INTERRUPT_ID_EXTERNAL_0 && irq <= INTERRUPT_ID_EXTERNAL_3) { + /* enable and clear interrupt in the controller */ + PERF->ExtIrqCfg |= (1 << (irq - INTERRUPT_ID_EXTERNAL_0 + EI_CLEAR_SHFT)); + PERF->ExtIrqCfg |= (1 << (irq - INTERRUPT_ID_EXTERNAL_0 + EI_MASK_SHFT)); + } + local_irq_restore(flags); +} + +void disable_brcm_irq(unsigned int irq) +{ + unsigned long flags; + + local_irq_save(flags); + if( irq >= INTERNAL_ISR_TABLE_OFFSET ) { + PERF->IrqMask &= ~(1 << (irq - INTERNAL_ISR_TABLE_OFFSET)); + } + else if (irq >= INTERRUPT_ID_EXTERNAL_0 && irq <= INTERRUPT_ID_EXTERNAL_3) { + /* disable interrupt in the controller */ + PERF->ExtIrqCfg &= ~(1 << (irq - INTERRUPT_ID_EXTERNAL_0 + EI_MASK_SHFT)); + } + local_irq_restore(flags); +} + +void ack_brcm_irq(unsigned int irq) +{ + /* Already done in brcm_irq_dispatch */ +} + +unsigned int startup_brcm_irq(unsigned int irq) +{ + enable_brcm_irq(irq); + + return 0; /* never anything pending */ +} + +unsigned int startup_brcm_none(unsigned int irq) +{ + return 0; +} + +void end_brcm_irq(unsigned int irq) +{ + if (!(irq_desc[irq].status & (IRQ_DISABLED|IRQ_INPROGRESS))) + enable_brcm_irq(irq); +} + +void end_brcm_none(unsigned int irq) +{ +} + +static struct hw_interrupt_type brcm_irq_type = { + .typename = "MIPS", + .startup = startup_brcm_irq, + .shutdown = disable_brcm_irq, + .enable = enable_brcm_irq, + .disable = disable_brcm_irq, + .ack = ack_brcm_irq, + .end = end_brcm_irq, + .set_affinity = NULL +}; + +static struct hw_interrupt_type brcm_irq_no_end_type = { + .typename = "MIPS", + .startup = startup_brcm_none, + .shutdown = disable_brcm_irq, + .enable = enable_brcm_irq, + .disable = disable_brcm_irq, + .ack = ack_brcm_irq, + .end = end_brcm_none, + .set_affinity = NULL +}; + +void __init arch_init_irq(void) +{ + int i; + + clear_c0_status(ST0_BEV); + change_c0_status(ST0_IM, (IE_IRQ0 | IE_IRQ1 | IE_IRQ2 | IE_IRQ3 | IE_IRQ4)); + + for (i = 0; i < NR_IRQS; i++) { + irq_desc[i].status = IRQ_DISABLED; + irq_desc[i].action = 0; + irq_desc[i].depth = 1; + irq_desc[i].chip = &brcm_irq_type; + } +} + +int request_external_irq(unsigned int irq, + FN_HANDLER handler, + unsigned long irqflags, + const char * devname, + void *dev_id) +{ + unsigned long flags; + + local_irq_save(flags); + + PERF->ExtIrqCfg |= (1 << (irq - INTERRUPT_ID_EXTERNAL_0 + EI_CLEAR_SHFT)); // Clear + PERF->ExtIrqCfg &= ~(1 << (irq - INTERRUPT_ID_EXTERNAL_0 + EI_MASK_SHFT)); // Mask + PERF->ExtIrqCfg &= ~(1 << (irq - INTERRUPT_ID_EXTERNAL_0 + EI_INSENS_SHFT)); // Edge insesnsitive + PERF->ExtIrqCfg |= (1 << (irq - INTERRUPT_ID_EXTERNAL_0 + EI_LEVEL_SHFT)); // Level triggered + PERF->ExtIrqCfg &= ~(1 << (irq - INTERRUPT_ID_EXTERNAL_0 + EI_SENSE_SHFT)); // Low level + + local_irq_restore(flags); + + return( request_irq(irq, handler, irqflags, devname, dev_id) ); +} + +/* VxWorks compatibility function(s). */ + +unsigned int BcmHalMapInterrupt(FN_HANDLER pfunc, unsigned int param, + unsigned int interruptId) +{ + int nRet = -1; + char *devname; + + devname = kmalloc(16, GFP_KERNEL); + if (devname) + sprintf( devname, "brcm_%d", interruptId ); + + /* Set the IRQ description to not automatically enable the interrupt at + * the end of an ISR. The driver that handles the interrupt must + * explicitly call BcmHalInterruptEnable or enable_brcm_irq. This behavior + * is consistent with interrupt handling on VxWorks. + */ + irq_desc[interruptId].chip = &brcm_irq_no_end_type; + + if( interruptId >= INTERNAL_ISR_TABLE_OFFSET ) + { + printk("BcmHalMapInterrupt : internal IRQ\n"); + nRet = request_irq( interruptId, pfunc, SA_SAMPLE_RANDOM | SA_INTERRUPT, devname, (void *) param ); + } + else if (interruptId >= INTERRUPT_ID_EXTERNAL_0 && interruptId <= INTERRUPT_ID_EXTERNAL_3) + { + printk("BcmHalMapInterrupt : external IRQ\n"); + nRet = request_external_irq( interruptId, pfunc, SA_SAMPLE_RANDOM | SA_INTERRUPT, devname, (void *) param ); + } + + return( nRet ); +} + + +EXPORT_SYMBOL(enable_brcm_irq); +EXPORT_SYMBOL(disable_brcm_irq); +EXPORT_SYMBOL(request_external_irq); +EXPORT_SYMBOL(BcmHalMapInterrupt); + diff --git a/target/linux/brcm63xx/files/arch/mips/bcm963xx/prom.c b/target/linux/brcm63xx/files/arch/mips/bcm963xx/prom.c new file mode 100644 index 000000000..e02d31c9e --- /dev/null +++ b/target/linux/brcm63xx/files/arch/mips/bcm963xx/prom.c @@ -0,0 +1,73 @@ +/* + Copyright 2004 Broadcom Corp. All Rights Reserved. + Copyright 2007 OpenWrt,org, Florian Fainelli <florian@openwrt.org> + + This program is free software; you can distribute it and/or modify it + under the terms of the GNU General Public License (Version 2) as + published by the Free Software Foundation. + + This program is distributed in the hope 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. +*/ +/* + * prom.c: PROM library initialization code. + * + */ +#include <linux/init.h> +#include <linux/mm.h> +#include <linux/sched.h> +#include <linux/bootmem.h> +#include <linux/blkdev.h> + +#include <asm/addrspace.h> +#include <asm/bootinfo.h> +#include <asm/cpu.h> +#include <asm/time.h> +#include <asm/mach-bcm963xx/bootloaders.h> +#include <asm/mach-bcm963xx/6348_map_part.h> + +#include "../cfe/cfe_private.h" + +extern void __init detect_bootloader(void); +extern void serial_init(void); +extern int boot_loader_type; + +#define MACH_BCM MACH_BCM96348 + +const char *get_system_type(void) +{ + return "Broadcom BCM963xx"; +} + +void __init prom_init(void) +{ + serial_init(); + + printk("%s prom init\n", get_system_type() ); + + PERF->IrqMask = 0; + + /* Detect the bootloader */ + detect_bootloader(); + + /* Do further initialisations depending on the bootloader */ + if (boot_loader_type == BOOT_LOADER_CFE || boot_loader_type == BOOT_LOADER_CFE2) { + cfe_setup(fw_arg0, fw_arg1, fw_arg2, fw_arg3); + } + /* Register 16MB RAM minus the ADSL SDRAM by default */ + add_memory_region(0, (0x01000000 - ADSL_SDRAM_IMAGE_SIZE), BOOT_MEM_RAM); + + mips_machgroup = MACH_GROUP_BRCM; + mips_machtype = MACH_BCM; +} + +void __init prom_free_prom_memory(void) +{ + /* We do not have any memory to free */ +} diff --git a/target/linux/brcm63xx/files/arch/mips/bcm963xx/ser_init.c b/target/linux/brcm63xx/files/arch/mips/bcm963xx/ser_init.c new file mode 100644 index 000000000..bb745aec6 --- /dev/null +++ b/target/linux/brcm63xx/files/arch/mips/bcm963xx/ser_init.c @@ -0,0 +1,181 @@ +/* +<:copyright-gpl + Copyright 2004 Broadcom Corp. All Rights Reserved. + + This program is free software; you can distribute it and/or modify it + under the terms of the GNU General Public License (Version 2) as + published by the Free Software Foundation. + + This program is distributed in the hope 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. +:> +*/ +/* + * Broadcom bcm63xx serial port initialization, also prepare for printk + * by registering with console_init + * + */ + +#include <linux/autoconf.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/kernel.h> +#include <linux/types.h> +#include <linux/console.h> +#include <linux/sched.h> + +#include <asm/addrspace.h> +#include <asm/irq.h> +#include <asm/reboot.h> +#include <asm/gdb-stub.h> +#include <asm/mc146818rtc.h> + +#include <bcm_map_part.h> +#include <6348_map_part.h> +#include <board.h> + +#define SER63XX_DEFAULT_BAUD 115200 +#define BD_BCM63XX_TIMER_CLOCK_INPUT (FPERIPH) +#define stUart ((volatile Uart * const) UART_BASE) + +// Transmit interrupts +#define TXINT (TXFIFOEMT | TXUNDERR | TXOVFERR) +// Receive interrupts +#define RXINT (RXFIFONE | RXOVFERR) + +/* -------------------------------------------------------------------------- + Name: serial_init + Purpose: Initalize the UART +-------------------------------------------------------------------------- */ +void __init serial_init(void) +{ + u32 tmpVal = SER63XX_DEFAULT_BAUD; + ULONG clockFreqHz; + +#if defined(CONFIG_BCM96345) + // Make sure clock is ticking + PERF->blkEnables |= UART_CLK_EN; +#endif + + /* Dissable channel's receiver and transmitter. */ + stUart->control &= ~(BRGEN|TXEN|RXEN); + + /*--------------------------------------------------------------------*/ + /* Write the table value to the clock select register. */ + /* DPullen - this is the equation to use: */ + /* value = clockFreqHz / baud / 32-1; */ + /* (snmod) Actually you should also take into account any necessary */ + /* rounding. Divide by 16, look at lsb, if 0, divide by 2 */ + /* and subtract 1. If 1, just divide by 2 */ + /*--------------------------------------------------------------------*/ + clockFreqHz = BD_BCM63XX_TIMER_CLOCK_INPUT; + tmpVal = (clockFreqHz / tmpVal) / 16; + if( tmpVal & 0x01 ) + tmpVal /= 2; //Rounding up, so sub is already accounted for + else + tmpVal = (tmpVal / 2) - 1; // Rounding down so we must sub 1 + stUart->baudword = tmpVal; + + /* Finally, re-enable the transmitter and receiver. */ + stUart->control |= (BRGEN|TXEN|RXEN); + + stUart->config = (BITS8SYM | ONESTOP); + // Set the FIFO interrupt depth ... stUart->fifocfg = 0xAA; + stUart->fifoctl = RSTTXFIFOS | RSTRXFIFOS; + stUart->intMask = 0; + stUart->intMask = RXINT | TXINT; +} + + +/* prom_putc() + * Output a character to the UART + */ +void prom_putc(char c) +{ + /* Wait for Tx uffer to empty */ + while (! (READ16(stUart->intStatus) & TXFIFOEMT)); + /* Send character */ + stUart->Data = c; +} + +/* prom_puts() + * Write a string to the UART + */ +void prom_puts(const char *s) +{ + while (*s) { + if (*s == '\n') { + prom_putc('\r'); + } + prom_putc(*s++); + } +} + + +/* prom_getc_nowait() + * Returns a character from the UART + * Returns -1 if no characters available or corrupted + */ +int prom_getc_nowait(void) +{ + uint16 uStatus; + int cData = -1; + + uStatus = READ16(stUart->intStatus); + + if (uStatus & RXFIFONE) { /* Do we have a character? */ + cData = READ16(stUart->Data) & 0xff; /* Read character */ + if (uStatus & (RXFRAMERR | RXPARERR)) { /* If we got an error, throw it away */ + cData = -1; + } + } + + return cData; +} + +/* prom_getc() + * Returns a charcter from the serial port + * Will block until it receives a valid character +*/ +char prom_getc(void) +{ + int cData = -1; + + /* Loop until we get a valid character */ + while(cData == -1) { + cData = prom_getc_nowait(); + } + return (char) cData; +} + +/* prom_testc() + * Returns 0 if no characters available + */ +int prom_testc(void) +{ + uint16 uStatus; + + uStatus = READ16(stUart->intStatus); + + return (uStatus & RXFIFONE); +} + +#if defined (CONFIG_REMOTE_DEBUG) +/* Prevent other code from writing to the serial port */ +void _putc(char c) { } +void _puts(const char *ptr) { } +#else +/* Low level outputs call prom routines */ +void _putc(char c) { + prom_putc(c); +} +void _puts(const char *ptr) { + prom_puts(ptr); +} +#endif diff --git a/target/linux/brcm63xx/files/arch/mips/bcm963xx/setup.c b/target/linux/brcm63xx/files/arch/mips/bcm963xx/setup.c new file mode 100644 index 000000000..70c6ebe60 --- /dev/null +++ b/target/linux/brcm63xx/files/arch/mips/bcm963xx/setup.c @@ -0,0 +1,472 @@ +/* +<:copyright-gpl + Copyright 2002 Broadcom Corp. All Rights Reserved. + + This program is free software; you can distribute it and/or modify it + under the terms of the GNU General Public License (Version 2) as + published by the Free Software Foundation. + + This program is distributed in the hope 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. +:> +*/ +/* + * Generic setup routines for Broadcom 963xx MIPS boards + */ + +#include <linux/autoconf.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/kernel.h> +#include <linux/kdev_t.h> +#include <linux/types.h> +#include <linux/console.h> +#include <linux/sched.h> +#include <linux/mm.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/pm.h> +#include <linux/bootmem.h> + +#include <asm/addrspace.h> +#include <asm/bcache.h> +#include <asm/irq.h> +#include <asm/time.h> +#include <asm/reboot.h> +#include <asm/gdb-stub.h> +#include <asm/bootinfo.h> +#include <asm/cpu.h> +#include <asm/mach-bcm963xx/bootloaders.h> + +extern void brcm_time_init(void); +extern int boot_loader_type; + +#include <linux/pci.h> +#include <linux/delay.h> +#include <bcm_map_part.h> +#include <6348_map_part.h> +#include <bcmpci.h> + +static volatile MpiRegisters * mpi = (MpiRegisters *)(MPI_BASE); + +/* This function should be in a board specific directory. For now, + * assume that all boards that include this file use a Broadcom chip + * with a soft reset bit in the PLL control register. + */ +static void brcm_machine_restart(char *command) +{ + const unsigned long ulSoftReset = 0x00000001; + unsigned long *pulPllCtrl = (unsigned long *) 0xfffe0008; + *pulPllCtrl |= ulSoftReset; +} + +static void brcm_machine_halt(void) +{ + printk("System halted\n"); + while (1); +} + +static void mpi_SetLocalPciConfigReg(uint32 reg, uint32 value) +{ + /* write index then value */ + mpi->pcicfgcntrl = PCI_CFG_REG_WRITE_EN + reg;; + mpi->pcicfgdata = value; +} + +static uint32 mpi_GetLocalPciConfigReg(uint32 reg) +{ + /* write index then get value */ + mpi->pcicfgcntrl = PCI_CFG_REG_WRITE_EN + reg;; + return mpi->pcicfgdata; +} + +/* + * mpi_ResetPcCard: Set/Reset the PcCard + */ +static void mpi_ResetPcCard(int cardtype, BOOL bReset) +{ + if (cardtype == MPI_CARDTYPE_NONE) { + return; + } + + if (cardtype == MPI_CARDTYPE_CARDBUS) { + bReset = ! bReset; + } + + if (bReset) { + mpi->pcmcia_cntl1 = (mpi->pcmcia_cntl1 & ~PCCARD_CARD_RESET); + } else { + mpi->pcmcia_cntl1 = (mpi->pcmcia_cntl1 | PCCARD_CARD_RESET); + } +} + +/* + * mpi_ConfigCs: Configure an MPI/EBI chip select + */ +static void mpi_ConfigCs(uint32 cs, uint32 base, uint32 size, uint32 flags) +{ + mpi->cs[cs].base = ((base & 0x1FFFFFFF) | size); + mpi->cs[cs].config = flags; +} + +/* + * mpi_InitPcmciaSpace + */ +static void mpi_InitPcmciaSpace(void) +{ + // ChipSelect 4 controls PCMCIA Memory accesses + mpi_ConfigCs(PCMCIA_COMMON_BASE, pcmciaMem, EBI_SIZE_1M, (EBI_WORD_WIDE|EBI_ENABLE)); + // ChipSelect 5 controls PCMCIA Attribute accesses + mpi_ConfigCs(PCMCIA_ATTRIBUTE_BASE, pcmciaAttr, EBI_SIZE_1M, (EBI_WORD_WIDE|EBI_ENABLE)); + // ChipSelect 6 controls PCMCIA I/O accesses + mpi_ConfigCs(PCMCIA_IO_BASE, pcmciaIo, EBI_SIZE_64K, (EBI_WORD_WIDE|EBI_ENABLE)); + + mpi->pcmcia_cntl2 = ((PCMCIA_ATTR_ACTIVE << RW_ACTIVE_CNT_BIT) | + (PCMCIA_ATTR_INACTIVE << INACTIVE_CNT_BIT) | + (PCMCIA_ATTR_CE_SETUP << CE_SETUP_CNT_BIT) | + (PCMCIA_ATTR_CE_HOLD << CE_HOLD_CNT_BIT)); + + mpi->pcmcia_cntl2 |= (PCMCIA_HALFWORD_EN | PCMCIA_BYTESWAP_DIS); +} + +/* + * cardtype_vcc_detect: PC Card's card detect and voltage sense connection + * + * CD1#/ CD2#/ VS1#/ VS2#/ Card Initial Vcc + * CCD1# CCD2# CVS1 CVS2 Type + * + * GND GND open open 16-bit 5 vdc + * + * GND GND GND open 16-bit 3.3 vdc + * + * GND GND open GND 16-bit x.x vdc + * + * GND GND GND GND 16-bit 3.3 & x.x vdc + * + *==================================================================== + * + * CVS1 GND CCD1# open CardBus 3.3 vdc + * + * GND CVS2 open CCD2# CardBus x.x vdc + * + * GND CVS1 CCD2# open CardBus y.y vdc + * + * GND CVS2 GND CCD2# CardBus 3.3 & x.x vdc + * + * CVS2 GND open CCD1# CardBus x.x & y.y vdc + * + * GND CVS1 CCD2# open CardBus 3.3, x.x & y.y vdc + * + */ +static int cardtype_vcc_detect(void) +{ + uint32 data32; + int cardtype; + + cardtype = MPI_CARDTYPE_NONE; + mpi->pcmcia_cntl1 = 0x0000A000; // Turn on the output enables and drive + // the CVS pins to 0. + data32 = mpi->pcmcia_cntl1; + switch (data32 & 0x00000003) // Test CD1# and CD2#, see if card is plugged in. + { + case 0x00000003: // No Card is in the slot. + printk("mpi: No Card is in the PCMCIA slot\n"); + break; + + case 0x00000002: // Partial insertion, No CD2#. + printk("mpi: Card in the PCMCIA slot partial insertion, no CD2 signal\n"); + break; + + case 0x00000001: // Partial insertion, No CD1#. + printk("mpi: Card in the PCMCIA slot partial insertion, no CD1 signal\n"); + break; + + case 0x00000000: + mpi->pcmcia_cntl1 = 0x0000A0C0; // Turn off the CVS output enables and + // float the CVS pins. + mdelay(1); + data32 = mpi->pcmcia_cntl1; + // Read the Register. + switch (data32 & 0x0000000C) // See what is on the CVS pins. + { + case 0x00000000: // CVS1 and CVS2 are tied to ground, only 1 option. + printk("mpi: Detected 3.3 & x.x 16-bit PCMCIA card\n"); + cardtype = MPI_CARDTYPE_PCMCIA; + break; + + case 0x00000004: // CVS1 is open or tied to CCD1/CCD2 and CVS2 is tied to ground. + // 2 valid voltage options. + switch (data32 & 0x00000003) // Test the values of CCD1 and CCD2. + { + case 0x00000003: // CCD1 and CCD2 are tied to 1 of the CVS pins. + // This is not a valid combination. + printk("mpi: Unknown card plugged into slot\n"); + break; + + case 0x00000002: // CCD2 is tied to either CVS1 or CVS2. + mpi->pcmcia_cntl1 = 0x0000A080; // Drive CVS1 to a 0. + mdelay(1); + data32 = mpi->pcmcia_cntl1; + if (data32 & 0x00000002) { // CCD2 is tied to CVS2, not valid. + printk("mpi: Unknown card plugged into slot\n"); + } else { // CCD2 is tied to CVS1. + printk("mpi: Detected 3.3, x.x and y.y Cardbus card\n"); + cardtype = MPI_CARDTYPE_CARDBUS; + } + break; + + case 0x00000001: // CCD1 is tied to either CVS1 or CVS2. + // This is not a valid combination. + printk("mpi: Unknown card plugged into slot\n"); + break; + + case 0x00000000: // CCD1 and CCD2 are tied to ground. + printk("mpi: Detected x.x vdc 16-bit PCMCIA card\n"); + cardtype = MPI_CARDTYPE_PCMCIA; + break; + } + break; + + case 0x00000008: // CVS2 is open or tied to CCD1/CCD2 and CVS1 is tied to ground. + // 2 valid voltage options. + switch (data32 & 0x00000003) // Test the values of CCD1 and CCD2. + { + case 0x00000003: // CCD1 and CCD2 are tied to 1 of the CVS pins. + // This is not a valid combination. + printk("mpi: Unknown card plugged into slot\n"); + break; + + case 0x00000002: // CCD2 is tied to either CVS1 or CVS2. + mpi->pcmcia_cntl1 = 0x0000A040; // Drive CVS2 to a 0. + mdelay(1); + data32 = mpi->pcmcia_cntl1; + if (data32 & 0x00000002) { // CCD2 is tied to CVS1, not valid. + printk("mpi: Unknown card plugged into slot\n"); + } else {// CCD2 is tied to CVS2. + printk("mpi: Detected 3.3 and x.x Cardbus card\n"); + cardtype = MPI_CARDTYPE_CARDBUS; + } + break; + + case 0x00000001: // CCD1 is tied to either CVS1 or CVS2. + // This is not a valid combination. + printk("mpi: Unknown card plugged into slot\n"); + break; + + case 0x00000000: // CCD1 and CCD2 are tied to ground. + cardtype = MPI_CARDTYPE_PCMCIA; + printk("mpi: Detected 3.3 vdc 16-bit PCMCIA card\n"); + break; + } + break; + + case 0x0000000C: // CVS1 and CVS2 are open or tied to CCD1/CCD2. + // 5 valid voltage options. + + switch (data32 & 0x00000003) // Test the values of CCD1 and CCD2. + { + case 0x00000003: // CCD1 and CCD2 are tied to 1 of the CVS pins. + // This is not a valid combination. + printk("mpi: Unknown card plugged into slot\n"); + break; + + case 0x00000002: // CCD2 is tied to either CVS1 or CVS2. + // CCD1 is tied to ground. + mpi->pcmcia_cntl1 = 0x0000A040; // Drive CVS2 to a 0. + mdelay(1); + data32 = mpi->pcmcia_cntl1; + if (data32 & 0x00000002) { // CCD2 is tied to CVS1. + printk("mpi: Detected y.y vdc Cardbus card\n"); + } else { // CCD2 is tied to CVS2. + printk("mpi: Detected x.x vdc Cardbus card\n"); + } + cardtype = MPI_CARDTYPE_CARDBUS; + break; + + case 0x00000001: // CCD1 is tied to either CVS1 or CVS2. + // CCD2 is tied to ground. + + mpi->pcmcia_cntl1 = 0x0000A040; // Drive CVS2 to a 0. + mdelay(1); + data32 = mpi->pcmcia_cntl1; + if (data32 & 0x00000001) {// CCD1 is tied to CVS1. + printk("mpi: Detected 3.3 vdc Cardbus card\n"); + } else { // CCD1 is tied to CVS2. + printk("mpi: Detected x.x and y.y Cardbus card\n"); + } + cardtype = MPI_CARDTYPE_CARDBUS; + break; + + case 0x00000000: // CCD1 and CCD2 are tied to ground. + cardtype = MPI_CARDTYPE_PCMCIA; + printk("mpi: Detected 5 vdc 16-bit PCMCIA card\n"); + break; + } + break; + + default: + printk("mpi: Unknown card plugged into slot\n"); + break; + + } + } + return cardtype; +} + +/* + * mpi_DetectPcCard: Detect the plugged in PC-Card + * Return: < 0 => Unknown card detected + * 0 => No card detected + * 1 => 16-bit card detected + * 2 => 32-bit CardBus card detected + */ +static int mpi_DetectPcCard(void) +{ + int cardtype; + + cardtype = cardtype_vcc_detect(); + switch(cardtype) { + case MPI_CARDTYPE_PCMCIA: + mpi->pcmcia_cntl1 &= ~0x0000e000; // disable enable bits + //mpi->pcmcia_cntl1 = (mpi->pcmcia_cntl1 & ~PCCARD_CARD_RESET); + mpi->pcmcia_cntl1 |= (PCMCIA_ENABLE | PCMCIA_GPIO_ENABLE); + mpi_InitPcmciaSpace(); + mpi_ResetPcCard(cardtype, FALSE); + // Hold card in reset for 10ms + mdelay(10); + mpi_ResetPcCard(cardtype, TRUE); + // Let card come out of reset + mdelay(100); + break; + case MPI_CARDTYPE_CARDBUS: + // 8 => CardBus Enable + // 1 => PCI Slot Number + // C => Float VS1 & VS2 + mpi->pcmcia_cntl1 = (mpi->pcmcia_cntl1 & 0xFFFF0000) | + CARDBUS_ENABLE | + (CARDBUS_SLOT << 8)| + VS2_OEN | + VS1_OEN; + /* access to this memory window will be to/from CardBus */ + mpi->l2pmremap1 |= CARDBUS_MEM; + + // Need to reset the Cardbus Card. There's no CardManager to do this, + // and we need to be ready for PCI configuration. + mpi_ResetPcCard(cardtype, FALSE); + // Hold card in reset for 10ms + mdelay(10); + mpi_ResetPcCard(cardtype, TRUE); + // Let card come out of reset + mdelay(100); + break; + default: + break; + } + return cardtype; +} + +static int mpi_init(void) +{ + unsigned long data; + unsigned int chipid, chiprev, sdramsize; + + printk("Broadcom BCM963xx MPI\n"); + chipid = (PERF->RevID & 0xFFFF0000) >> 16; + chiprev = (PERF->RevID & 0xFF); + + if (boot_loader_type == BOOT_LOADER_CFE) + sdramsize = boot_mem_map.map[0].size; + else + sdramsize = 0x01000000; + /* + * Init the pci interface + */ + data = GPIO->GPIOMode; // GPIO mode register + data |= GROUP2_PCI | GROUP1_MII_PCCARD; // PCI internal arbiter + Cardbus + GPIO->GPIOMode = data; // PCI internal arbiter + + /* + * In the BCM6348 CardBus support is defaulted to Slot 0 + * because there is no external IDSEL for CardBus. To disable + * the CardBus and allow a standard PCI card in Slot 0 + * set the cbus_idsel field to 0x1f. + */ + /* + uData = mpi->pcmcia_cntl1; + uData |= CARDBUS_IDSEL; + mpi->pcmcia_cntl1 = uData; + */ + // Setup PCI I/O Window range. Give 64K to PCI I/O + mpi->l2piorange = ~(BCM_PCI_IO_SIZE_64KB-1); + // UBUS to PCI I/O base address + mpi->l2piobase = BCM_PCI_IO_BASE & BCM_PCI_ADDR_MASK; + // UBUS to PCI I/O Window remap + mpi->l2pioremap = (BCM_PCI_IO_BASE | MEM_WINDOW_EN); + + // enable PCI related GPIO pins and data swap between system and PCI bus + mpi->locbuscntrl = (EN_PCI_GPIO | DIR_U2P_NOSWAP); + + /* Enable 6348 BusMaster and Memory access mode */ + data = mpi_GetLocalPciConfigReg(PCI_COMMAND); + data |= (PCI_COMMAND_MEMORY | PCI_COMMAND_MASTER); + mpi_SetLocalPciConfigReg(PCI_COMMAND, data); + + /* Configure two 16 MByte PCI to System memory regions. */ + /* These memory regions are used when PCI device is a bus master */ + /* Accesses to the SDRAM from PCI bus will be "byte swapped" for this region */ + mpi_SetLocalPciConfigReg(PCI_BASE_ADDRESS_3, BCM_HOST_MEM_SPACE1); + mpi->sp0remap = 0x0; + + /* Accesses to the SDRAM from PCI bus will not be "byte swapped" for this region */ + mpi_SetLocalPciConfigReg(PCI_BASE_ADDRESS_4, BCM_HOST_MEM_SPACE2); + mpi->sp1remap = 0x0; + mpi->pcimodesel |= (PCI_BAR2_NOSWAP | 0x40); + + if ((chipid == 0x6348) && (chiprev == 0xb0)) { + mpi->sp0range = ~(sdramsize-1); + mpi->sp1range = ~(sdramsize-1); + } + /* + * Change 6348 PCI Cfg Reg. offset 0x40 to PCI memory read retry count infinity + * by set 0 in bit 8~15. This resolve read Bcm4306 srom return 0xffff in + * first read. + */ + data = mpi_GetLocalPciConfigReg(BRCM_PCI_CONFIG_TIMER); + data &= ~BRCM_PCI_CONFIG_TIMER_RETRY_MASK; + data |= 0x00000080; + mpi_SetLocalPciConfigReg(BRCM_PCI_CONFIG_TIMER, data); + + /* enable pci interrupt */ + mpi->locintstat |= (EXT_PCI_INT << 16); + + mpi_DetectPcCard(); + + ioport_resource.start = BCM_PCI_IO_BASE; + ioport_resource.end = BCM_PCI_IO_BASE + BCM_PCI_IO_SIZE_64KB; + +#if defined(CONFIG_USB) + PERF->blkEnables |= USBH_CLK_EN; + mdelay(100); + *USBH_NON_OHCI = NON_OHCI_BYTE_SWAP; +#endif + + return 0; +} + +void __init plat_mem_setup(void) +{ + _machine_restart = brcm_machine_restart; + _machine_halt = brcm_machine_halt; + pm_power_off = brcm_machine_halt; + + board_time_init = brcm_time_init; + + /* mpi initialization */ + mpi_init(); +} diff --git a/target/linux/brcm63xx/files/arch/mips/bcm963xx/time.c b/target/linux/brcm63xx/files/arch/mips/bcm963xx/time.c new file mode 100644 index 000000000..8b82ada37 --- /dev/null +++ b/target/linux/brcm63xx/files/arch/mips/bcm963xx/time.c @@ -0,0 +1,116 @@ +/* +<:copyright-gpl + Copyright 2004 Broadcom Corp. All Rights Reserved. + + This program is free software; you can distribute it and/or modify it + under the terms of the GNU General Public License (Version 2) as + published by the Free Software Foundation. + + This program is distributed in the hope 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. +:> +*/ +/* + * Setup time for Broadcom 963xx MIPS boards + */ + +#include <linux/autoconf.h> +#include <linux/init.h> +#include <linux/kernel_stat.h> +#include <linux/sched.h> +#include <linux/spinlock.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/time.h> +#include <linux/timex.h> + +#include <asm/mipsregs.h> +#include <asm/ptrace.h> +#include <asm/div64.h> +#include <asm/time.h> + +#include <6348_map_part.h> +#include <6348_intr.h> +#include <bcm_map_part.h> +#include <bcm_intr.h> + +static unsigned long r4k_offset; /* Amount to increment compare reg each time */ +static unsigned long r4k_cur; /* What counter should be at next timer irq */ + +/* ********************************************************************* + * calculateCpuSpeed() + * Calculate the BCM6348 CPU speed by reading the PLL strap register + * and applying the following formula: + * cpu_clk = (.25 * 64MHz freq) * (N1 + 1) * (N2 + 2) / (M1_CPU + 1) + * Input parameters: + * none + * Return value: + * none + ********************************************************************* */ + +static inline unsigned long __init calculateCpuSpeed(void) +{ + u32 pllStrap = PERF->PllStrap; + int n1 = (pllStrap & PLL_N1_MASK) >> PLL_N1_SHFT; + int n2 = (pllStrap & PLL_N2_MASK) >> PLL_N2_SHFT; + int m1cpu = (pllStrap & PLL_M1_CPU_MASK) >> PLL_M1_CPU_SHFT; + + return (16 * (n1 + 1) * (n2 + 2) / (m1cpu + 1)) * 1000000; +} + + +static inline unsigned long __init cal_r4koff(void) +{ + mips_hpt_frequency = calculateCpuSpeed() / 2; + return (mips_hpt_frequency / HZ); +} + + +/* + * There are a lot of conceptually broken versions of the MIPS timer interrupt + * handler floating around. This one is rather different, but the algorithm + * is provably more robust. + */ +irqreturn_t brcm_timer_interrupt(struct pt_regs *regs) +{ + int irq = MIPS_TIMER_INT; + + irq_enter(); + kstat_this_cpu.irqs[irq]++; + + timer_interrupt(irq, regs); + irq_exit(); + return IRQ_HANDLED; +} + + +void __init brcm_time_init(void) +{ + unsigned int est_freq, flags; + local_irq_save(flags); + + printk("calculating r4koff... "); + r4k_offset = cal_r4koff(); + printk("%08lx(%d)\n", r4k_offset, (int)r4k_offset); + + est_freq = 2 * r4k_offset * HZ; + est_freq += 5000; /* round */ + est_freq -= est_freq % 10000; + printk("CPU frequency %d.%02d MHz\n", est_freq / 1000000, + (est_freq % 1000000) * 100 / 1000000); + local_irq_restore(flags); +} + + +void __init plat_timer_setup(struct irqaction *irq) +{ + r4k_cur = (read_c0_count() + r4k_offset); + write_c0_compare(r4k_cur); + set_c0_status(IE_IRQ5); +} diff --git a/target/linux/brcm63xx/files/arch/mips/bcm963xx/wdt.c b/target/linux/brcm63xx/files/arch/mips/bcm963xx/wdt.c new file mode 100644 index 000000000..0ea36a67f --- /dev/null +++ b/target/linux/brcm63xx/files/arch/mips/bcm963xx/wdt.c @@ -0,0 +1,246 @@ +/* + * Watchdog driver for the BCM963xx devices + * + * Copyright (C) 2007 OpenWrt.org + * Florian Fainelli <florian@openwrt.org> + * + * 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. + */ + +#include <linux/module.h> +#include <linux/types.h> +#include <linux/errno.h> +#include <linux/miscdevice.h> +#include <linux/fs.h> +#include <linux/init.h> +#include <linux/notifier.h> +#include <linux/watchdog.h> +#include <linux/timer.h> +#include <linux/jiffies.h> +#include <linux/completion.h> +#include <linux/ioport.h> + +typedef struct bcm963xx_timer { + unsigned short unused0; + unsigned char timer_mask; +#define TIMER0EN 0x01 +#define TIMER1EN 0x02 +#define TIMER2EN 0x04 + unsigned char timer_ints; +#define TIMER0 0x01 +#define TIMER1 0x02 +#define TIMER2 0x04 +#define WATCHDOG 0x08 + unsigned long timer_ctl0; + unsigned long timer_ctl1; + unsigned long timer_ctl2; +#define TIMERENABLE 0x80000000 +#define RSTCNTCLR 0x40000000 + unsigned long timer_cnt0; + unsigned long timer_cnt1; + unsigned long timer_cnt2; + unsigned long wdt_def_count; + + /* Write 0xff00 0x00ff to Start timer + * Write 0xee00 0x00ee to Stop and re-load default count + * Read from this register returns current watch dog count + */ + unsigned long wdt_ctl; + + /* Number of 40-MHz ticks for WD Reset pulse to last */ + unsigned long wdt_rst_count; +} bcm963xx_timer; + +static struct bcm963xx_wdt_device { + struct completion stop; + volatile int running; + struct timer_list timer; + volatile int queue; + int default_ticks; + unsigned long inuse; +} bcm963xx_wdt_device; + +static int ticks = 1000; + +#define WDT_BASE 0xfffe0200 +#define WDT ((volatile bcm963xx_timer * const) WDT_BASE) + +#define BCM963XX_INTERVAL (HZ/10+1) + +static void bcm963xx_wdt_trigger(unsigned long unused) +{ + if (bcm963xx_wdt_device.running) + ticks--; + + /* Load the default ticking value into the reset counter register */ + WDT->wdt_rst_count = bcm963xx_wdt_device.default_ticks; + + if (bcm963xx_wdt_device.queue && ticks) { + bcm963xx_wdt_device.timer.expires = jiffies + BCM963XX_INTERVAL; + add_timer(&bcm963xx_wdt_device.timer); + } + else { + complete(&bcm963xx_wdt_device.stop); + } +} + +static void bcm963xx_wdt_reset(void) +{ + ticks = bcm963xx_wdt_device.default_ticks; + /* Also reload default count */ + WDT->wdt_def_count = ticks; + WDT->wdt_ctl = 0xee00; + WDT->wdt_ctl = 0x00ee; +} + +static void bcm963xx_wdt_start(void) +{ + if (!bcm963xx_wdt_device.queue) { + bcm963xx_wdt_device.queue; + /* Enable the watchdog by writing 0xff00 ,then 0x00ff to the control register */ + WDT->wdt_ctl = 0xff00; + WDT->wdt_ctl = 0x00ff; + bcm963xx_wdt_device.timer.expires = jiffies + BCM963XX_INTERVAL; + add_timer(&bcm963xx_wdt_device.timer); + } + bcm963xx_wdt_device.running++; +} + +static int bcm963xx_wdt_stop(void) +{ + if (bcm963xx_wdt_device.running) + bcm963xx_wdt_device.running = 0; + + ticks = bcm963xx_wdt_device.default_ticks; + + /* Stop the watchdog by writing 0xee00 then 0x00ee to the control register */ + WDT->wdt_ctl = 0xee00; + WDT->wdt_ctl = 0x00ee; + + return -EIO; +} + +static int bcm963xx_wdt_open(struct inode *inode, struct file *file) +{ + if (test_and_set_bit(0, &bcm963xx_wdt_device.inuse)) + return -EBUSY; + return nonseekable_open(inode, file); +} + +static int bcm963xx_wdt_release(struct inode *inode, struct file *file) +{ + clear_bit(0, &bcm963xx_wdt_device.inuse); + return 0; +} + +static int bcm963xx_wdt_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + void __user *argp = (void __user *)arg; + unsigned int value; + + static struct watchdog_info ident = { + .options = WDIOF_CARDRESET, + .identity = "BCM963xx WDT", + }; + + switch (cmd) { + case WDIOC_KEEPALIVE: + bcm963xx_wdt_reset(); + break; + case WDIOC_GETSTATUS: + /* Reading from the control register will return the current value */ + value = WDT->wdt_ctl; + if ( copy_to_user(argp, &value, sizeof(int)) ) + return -EFAULT; + break; + case WDIOC_GETSUPPORT: + if ( copy_to_user(argp, &ident, sizeof(ident)) ) + return -EFAULT; + break; + case WDIOC_SETOPTIONS: + if ( copy_from_user(&value, argp, sizeof(int)) ) + return -EFAULT; + switch(value) { + case WDIOS_ENABLECARD: + bcm963xx_wdt_start(); + break; + case WDIOS_DISABLECARD: + bcm963xx_wdt_stop(); + break; + default: + return -EINVAL; + } + break; + default: + return -ENOTTY; + } + return 0; +} + +static int bcm963xx_wdt_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos) +{ + if (!count) + return -EIO; + bcm963xx_wdt_reset(); + return count; +} + +static const struct file_operations bcm963xx_wdt_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .write = bcm963xx_wdt_write, + .ioctl = bcm963xx_wdt_ioctl, + .open = bcm963xx_wdt_open, + .release = bcm963xx_wdt_release, +}; + +static struct miscdevice bcm963xx_wdt_miscdev = { + .minor = WATCHDOG_MINOR, + .name = "watchdog", + .fops = &bcm963xx_wdt_fops, +}; + +static void __exit bcm963xx_wdt_exit(void) +{ + if (bcm963xx_wdt_device.queue ){ + bcm963xx_wdt_device.queue = 0; + wait_for_completion(&bcm963xx_wdt_device.stop); + } + misc_deregister(&bcm963xx_wdt_miscdev); +} + +static int __init bcm963xx_wdt_init(void) +{ + int ret = 0; + + printk("Broadcom BCM963xx Watchdog timer\n"); + + ret = misc_register(&bcm963xx_wdt_miscdev); + if (ret) { + printk(KERN_CRIT "Cannot register miscdev on minor=%d (err=%d)\n", WATCHDOG_MINOR, ret); + return ret; + } + init_completion(&bcm963xx_wdt_device.stop); + bcm963xx_wdt_device.queue = 0; + + clear_bit(0, &bcm963xx_wdt_device.inuse); + + init_timer(&bcm963xx_wdt_device.timer); + bcm963xx_wdt_device.timer.function = bcm963xx_wdt_trigger; + bcm963xx_wdt_device.timer.data = 0; + + bcm963xx_wdt_device.default_ticks = ticks; + return ret; +} + + +module_init(bcm963xx_wdt_init); +module_exit(bcm963xx_wdt_exit); + +MODULE_AUTHOR("Florian Fainelli <florian@openwrt.org>"); +MODULE_DESCRIPTION("Broadcom BCM963xx Watchdog driver"); +MODULE_LICENSE("GPL"); |