diff options
Diffstat (limited to 'arch/mips/loongson1/common')
-rw-r--r-- | arch/mips/loongson1/common/Makefile | 5 | ||||
-rw-r--r-- | arch/mips/loongson1/common/clock.c | 165 | ||||
-rw-r--r-- | arch/mips/loongson1/common/irq.c | 147 | ||||
-rw-r--r-- | arch/mips/loongson1/common/platform.c | 124 | ||||
-rw-r--r-- | arch/mips/loongson1/common/prom.c | 87 | ||||
-rw-r--r-- | arch/mips/loongson1/common/reset.c | 45 | ||||
-rw-r--r-- | arch/mips/loongson1/common/setup.c | 29 |
7 files changed, 602 insertions, 0 deletions
diff --git a/arch/mips/loongson1/common/Makefile b/arch/mips/loongson1/common/Makefile new file mode 100644 index 000000000000..b2797709ef5b --- /dev/null +++ b/arch/mips/loongson1/common/Makefile @@ -0,0 +1,5 @@ +# +# Makefile for common code of loongson1 based machines. +# + +obj-y += clock.o irq.o platform.o prom.o reset.o setup.o diff --git a/arch/mips/loongson1/common/clock.c b/arch/mips/loongson1/common/clock.c new file mode 100644 index 000000000000..2d98fb030596 --- /dev/null +++ b/arch/mips/loongson1/common/clock.c @@ -0,0 +1,165 @@ +/* + * Copyright (c) 2011 Zhang, Keguang <keguang.zhang@gmail.com> + * + * 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/list.h> +#include <linux/mutex.h> +#include <linux/clk.h> +#include <linux/err.h> +#include <asm/clock.h> +#include <asm/time.h> + +#include <loongson1.h> + +static LIST_HEAD(clocks); +static DEFINE_MUTEX(clocks_mutex); + +struct clk *clk_get(struct device *dev, const char *name) +{ + struct clk *c; + struct clk *ret = NULL; + + mutex_lock(&clocks_mutex); + list_for_each_entry(c, &clocks, node) { + if (!strcmp(c->name, name)) { + ret = c; + break; + } + } + mutex_unlock(&clocks_mutex); + + return ret; +} +EXPORT_SYMBOL(clk_get); + +unsigned long clk_get_rate(struct clk *clk) +{ + return clk->rate; +} +EXPORT_SYMBOL(clk_get_rate); + +static void pll_clk_init(struct clk *clk) +{ + u32 pll; + + pll = __raw_readl(LS1X_CLK_PLL_FREQ); + clk->rate = (12 + (pll & 0x3f)) * 33 / 2 + + ((pll >> 8) & 0x3ff) * 33 / 1024 / 2; + clk->rate *= 1000000; +} + +static void cpu_clk_init(struct clk *clk) +{ + u32 pll, ctrl; + + pll = clk_get_rate(clk->parent); + ctrl = __raw_readl(LS1X_CLK_PLL_DIV) & DIV_CPU; + clk->rate = pll / (ctrl >> DIV_CPU_SHIFT); +} + +static void ddr_clk_init(struct clk *clk) +{ + u32 pll, ctrl; + + pll = clk_get_rate(clk->parent); + ctrl = __raw_readl(LS1X_CLK_PLL_DIV) & DIV_DDR; + clk->rate = pll / (ctrl >> DIV_DDR_SHIFT); +} + +static void dc_clk_init(struct clk *clk) +{ + u32 pll, ctrl; + + pll = clk_get_rate(clk->parent); + ctrl = __raw_readl(LS1X_CLK_PLL_DIV) & DIV_DC; + clk->rate = pll / (ctrl >> DIV_DC_SHIFT); +} + +static struct clk_ops pll_clk_ops = { + .init = pll_clk_init, +}; + +static struct clk_ops cpu_clk_ops = { + .init = cpu_clk_init, +}; + +static struct clk_ops ddr_clk_ops = { + .init = ddr_clk_init, +}; + +static struct clk_ops dc_clk_ops = { + .init = dc_clk_init, +}; + +static struct clk pll_clk = { + .name = "pll", + .ops = &pll_clk_ops, +}; + +static struct clk cpu_clk = { + .name = "cpu", + .parent = &pll_clk, + .ops = &cpu_clk_ops, +}; + +static struct clk ddr_clk = { + .name = "ddr", + .parent = &pll_clk, + .ops = &ddr_clk_ops, +}; + +static struct clk dc_clk = { + .name = "dc", + .parent = &pll_clk, + .ops = &dc_clk_ops, +}; + +int clk_register(struct clk *clk) +{ + mutex_lock(&clocks_mutex); + list_add(&clk->node, &clocks); + if (clk->ops->init) + clk->ops->init(clk); + mutex_unlock(&clocks_mutex); + + return 0; +} +EXPORT_SYMBOL(clk_register); + +static struct clk *ls1x_clks[] = { + &pll_clk, + &cpu_clk, + &ddr_clk, + &dc_clk, +}; + +int __init ls1x_clock_init(void) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(ls1x_clks); i++) + clk_register(ls1x_clks[i]); + + return 0; +} + +void __init plat_time_init(void) +{ + struct clk *clk; + + /* Initialize LS1X clocks */ + ls1x_clock_init(); + + /* setup mips r4k timer */ + clk = clk_get(NULL, "cpu"); + if (IS_ERR(clk)) + panic("unable to get dc clock, err=%ld", PTR_ERR(clk)); + + mips_hpt_frequency = clk_get_rate(clk) / 2; +} diff --git a/arch/mips/loongson1/common/irq.c b/arch/mips/loongson1/common/irq.c new file mode 100644 index 000000000000..41bc8ffe7bba --- /dev/null +++ b/arch/mips/loongson1/common/irq.c @@ -0,0 +1,147 @@ +/* + * Copyright (c) 2011 Zhang, Keguang <keguang.zhang@gmail.com> + * + * 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/interrupt.h> +#include <linux/irq.h> +#include <asm/irq_cpu.h> + +#include <loongson1.h> +#include <irq.h> + +#define LS1X_INTC_REG(n, x) \ + ((void __iomem *)KSEG1ADDR(LS1X_INTC_BASE + (n * 0x18) + (x))) + +#define LS1X_INTC_INTISR(n) LS1X_INTC_REG(n, 0x0) +#define LS1X_INTC_INTIEN(n) LS1X_INTC_REG(n, 0x4) +#define LS1X_INTC_INTSET(n) LS1X_INTC_REG(n, 0x8) +#define LS1X_INTC_INTCLR(n) LS1X_INTC_REG(n, 0xc) +#define LS1X_INTC_INTPOL(n) LS1X_INTC_REG(n, 0x10) +#define LS1X_INTC_INTEDGE(n) LS1X_INTC_REG(n, 0x14) + +static void ls1x_irq_ack(struct irq_data *d) +{ + unsigned int bit = (d->irq - LS1X_IRQ_BASE) & 0x1f; + unsigned int n = (d->irq - LS1X_IRQ_BASE) >> 5; + + __raw_writel(__raw_readl(LS1X_INTC_INTCLR(n)) + | (1 << bit), LS1X_INTC_INTCLR(n)); +} + +static void ls1x_irq_mask(struct irq_data *d) +{ + unsigned int bit = (d->irq - LS1X_IRQ_BASE) & 0x1f; + unsigned int n = (d->irq - LS1X_IRQ_BASE) >> 5; + + __raw_writel(__raw_readl(LS1X_INTC_INTIEN(n)) + & ~(1 << bit), LS1X_INTC_INTIEN(n)); +} + +static void ls1x_irq_mask_ack(struct irq_data *d) +{ + unsigned int bit = (d->irq - LS1X_IRQ_BASE) & 0x1f; + unsigned int n = (d->irq - LS1X_IRQ_BASE) >> 5; + + __raw_writel(__raw_readl(LS1X_INTC_INTIEN(n)) + & ~(1 << bit), LS1X_INTC_INTIEN(n)); + __raw_writel(__raw_readl(LS1X_INTC_INTCLR(n)) + | (1 << bit), LS1X_INTC_INTCLR(n)); +} + +static void ls1x_irq_unmask(struct irq_data *d) +{ + unsigned int bit = (d->irq - LS1X_IRQ_BASE) & 0x1f; + unsigned int n = (d->irq - LS1X_IRQ_BASE) >> 5; + + __raw_writel(__raw_readl(LS1X_INTC_INTIEN(n)) + | (1 << bit), LS1X_INTC_INTIEN(n)); +} + +static struct irq_chip ls1x_irq_chip = { + .name = "LS1X-INTC", + .irq_ack = ls1x_irq_ack, + .irq_mask = ls1x_irq_mask, + .irq_mask_ack = ls1x_irq_mask_ack, + .irq_unmask = ls1x_irq_unmask, +}; + +static void ls1x_irq_dispatch(int n) +{ + u32 int_status, irq; + + /* Get pending sources, masked by current enables */ + int_status = __raw_readl(LS1X_INTC_INTISR(n)) & + __raw_readl(LS1X_INTC_INTIEN(n)); + + if (int_status) { + irq = LS1X_IRQ(n, __ffs(int_status)); + do_IRQ(irq); + } +} + +asmlinkage void plat_irq_dispatch(void) +{ + unsigned int pending; + + pending = read_c0_cause() & read_c0_status() & ST0_IM; + + if (pending & CAUSEF_IP7) + do_IRQ(TIMER_IRQ); + else if (pending & CAUSEF_IP2) + ls1x_irq_dispatch(0); /* INT0 */ + else if (pending & CAUSEF_IP3) + ls1x_irq_dispatch(1); /* INT1 */ + else if (pending & CAUSEF_IP4) + ls1x_irq_dispatch(2); /* INT2 */ + else if (pending & CAUSEF_IP5) + ls1x_irq_dispatch(3); /* INT3 */ + else if (pending & CAUSEF_IP6) + ls1x_irq_dispatch(4); /* INT4 */ + else + spurious_interrupt(); + +} + +struct irqaction cascade_irqaction = { + .handler = no_action, + .name = "cascade", + .flags = IRQF_NO_THREAD, +}; + +static void __init ls1x_irq_init(int base) +{ + int n; + + /* Disable interrupts and clear pending, + * setup all IRQs as high level triggered + */ + for (n = 0; n < 4; n++) { + __raw_writel(0x0, LS1X_INTC_INTIEN(n)); + __raw_writel(0xffffffff, LS1X_INTC_INTCLR(n)); + __raw_writel(0xffffffff, LS1X_INTC_INTPOL(n)); + /* set DMA0, DMA1 and DMA2 to edge trigger */ + __raw_writel(n ? 0x0 : 0xe000, LS1X_INTC_INTEDGE(n)); + } + + + for (n = base; n < LS1X_IRQS; n++) { + irq_set_chip_and_handler(n, &ls1x_irq_chip, + handle_level_irq); + } + + setup_irq(INT0_IRQ, &cascade_irqaction); + setup_irq(INT1_IRQ, &cascade_irqaction); + setup_irq(INT2_IRQ, &cascade_irqaction); + setup_irq(INT3_IRQ, &cascade_irqaction); +} + +void __init arch_init_irq(void) +{ + mips_cpu_irq_init(); + ls1x_irq_init(LS1X_IRQ_BASE); +} diff --git a/arch/mips/loongson1/common/platform.c b/arch/mips/loongson1/common/platform.c new file mode 100644 index 000000000000..e92d59c4bd78 --- /dev/null +++ b/arch/mips/loongson1/common/platform.c @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2011 Zhang, Keguang <keguang.zhang@gmail.com> + * + * 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/clk.h> +#include <linux/dma-mapping.h> +#include <linux/err.h> +#include <linux/phy.h> +#include <linux/serial_8250.h> +#include <linux/stmmac.h> +#include <asm-generic/sizes.h> + +#include <loongson1.h> + +#define LS1X_UART(_id) \ + { \ + .mapbase = LS1X_UART ## _id ## _BASE, \ + .irq = LS1X_UART ## _id ## _IRQ, \ + .iotype = UPIO_MEM, \ + .flags = UPF_IOREMAP | UPF_FIXED_TYPE, \ + .type = PORT_16550A, \ + } + +static struct plat_serial8250_port ls1x_serial8250_port[] = { + LS1X_UART(0), + LS1X_UART(1), + LS1X_UART(2), + LS1X_UART(3), + {}, +}; + +struct platform_device ls1x_uart_device = { + .name = "serial8250", + .id = PLAT8250_DEV_PLATFORM, + .dev = { + .platform_data = ls1x_serial8250_port, + }, +}; + +void __init ls1x_serial_setup(void) +{ + struct clk *clk; + struct plat_serial8250_port *p; + + clk = clk_get(NULL, "dc"); + if (IS_ERR(clk)) + panic("unable to get dc clock, err=%ld", PTR_ERR(clk)); + + for (p = ls1x_serial8250_port; p->flags != 0; ++p) + p->uartclk = clk_get_rate(clk); +} + +/* Synopsys Ethernet GMAC */ +static struct resource ls1x_eth0_resources[] = { + [0] = { + .start = LS1X_GMAC0_BASE, + .end = LS1X_GMAC0_BASE + SZ_64K - 1, + .flags = IORESOURCE_MEM, + }, + [1] = { + .name = "macirq", + .start = LS1X_GMAC0_IRQ, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct stmmac_mdio_bus_data ls1x_mdio_bus_data = { + .bus_id = 0, + .phy_mask = 0, +}; + +static struct plat_stmmacenet_data ls1x_eth_data = { + .bus_id = 0, + .phy_addr = -1, + .mdio_bus_data = &ls1x_mdio_bus_data, + .has_gmac = 1, + .tx_coe = 1, +}; + +struct platform_device ls1x_eth0_device = { + .name = "stmmaceth", + .id = 0, + .num_resources = ARRAY_SIZE(ls1x_eth0_resources), + .resource = ls1x_eth0_resources, + .dev = { + .platform_data = &ls1x_eth_data, + }, +}; + +/* USB EHCI */ +static u64 ls1x_ehci_dmamask = DMA_BIT_MASK(32); + +static struct resource ls1x_ehci_resources[] = { + [0] = { + .start = LS1X_EHCI_BASE, + .end = LS1X_EHCI_BASE + SZ_32K - 1, + .flags = IORESOURCE_MEM, + }, + [1] = { + .start = LS1X_EHCI_IRQ, + .flags = IORESOURCE_IRQ, + }, +}; + +struct platform_device ls1x_ehci_device = { + .name = "ls1x-ehci", + .id = -1, + .num_resources = ARRAY_SIZE(ls1x_ehci_resources), + .resource = ls1x_ehci_resources, + .dev = { + .dma_mask = &ls1x_ehci_dmamask, + }, +}; + +/* Real Time Clock */ +struct platform_device ls1x_rtc_device = { + .name = "ls1x-rtc", + .id = -1, +}; diff --git a/arch/mips/loongson1/common/prom.c b/arch/mips/loongson1/common/prom.c new file mode 100644 index 000000000000..1f8e49f9886d --- /dev/null +++ b/arch/mips/loongson1/common/prom.c @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2011 Zhang, Keguang <keguang.zhang@gmail.com> + * + * Modified from arch/mips/pnx833x/common/prom.c. + * + * 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/serial_reg.h> +#include <asm/bootinfo.h> + +#include <loongson1.h> +#include <prom.h> + +int prom_argc; +char **prom_argv, **prom_envp; +unsigned long memsize, highmemsize; + +char *prom_getenv(char *envname) +{ + char **env = prom_envp; + int i; + + i = strlen(envname); + + while (*env) { + if (strncmp(envname, *env, i) == 0 && *(*env+i) == '=') + return *env + i + 1; + env++; + } + + return 0; +} + +static inline unsigned long env_or_default(char *env, unsigned long dfl) +{ + char *str = prom_getenv(env); + return str ? simple_strtol(str, 0, 0) : dfl; +} + +void __init prom_init_cmdline(void) +{ + char *c = &(arcs_cmdline[0]); + int i; + + for (i = 1; i < prom_argc; i++) { + strcpy(c, prom_argv[i]); + c += strlen(prom_argv[i]); + if (i < prom_argc-1) + *c++ = ' '; + } + *c = 0; +} + +void __init prom_init(void) +{ + prom_argc = fw_arg0; + prom_argv = (char **)fw_arg1; + prom_envp = (char **)fw_arg2; + + prom_init_cmdline(); + + memsize = env_or_default("memsize", DEFAULT_MEMSIZE); + highmemsize = env_or_default("highmemsize", 0x0); +} + +void __init prom_free_prom_memory(void) +{ +} + +#define PORT(offset) (u8 *)(KSEG1ADDR(LS1X_UART0_BASE + offset)) + +void __init prom_putchar(char c) +{ + int timeout; + + timeout = 1024; + + while (((readb(PORT(UART_LSR)) & UART_LSR_THRE) == 0) + && (timeout-- > 0)) + ; + + writeb(c, PORT(UART_TX)); +} diff --git a/arch/mips/loongson1/common/reset.c b/arch/mips/loongson1/common/reset.c new file mode 100644 index 000000000000..fb979a784eca --- /dev/null +++ b/arch/mips/loongson1/common/reset.c @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2011 Zhang, Keguang <keguang.zhang@gmail.com> + * + * 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/io.h> +#include <linux/pm.h> +#include <asm/reboot.h> + +#include <loongson1.h> + +static void ls1x_restart(char *command) +{ + __raw_writel(0x1, LS1X_WDT_EN); + __raw_writel(0x5000000, LS1X_WDT_TIMER); + __raw_writel(0x1, LS1X_WDT_SET); +} + +static void ls1x_halt(void) +{ + while (1) { + if (cpu_wait) + cpu_wait(); + } +} + +static void ls1x_power_off(void) +{ + ls1x_halt(); +} + +static int __init ls1x_reboot_setup(void) +{ + _machine_restart = ls1x_restart; + _machine_halt = ls1x_halt; + pm_power_off = ls1x_power_off; + + return 0; +} + +arch_initcall(ls1x_reboot_setup); diff --git a/arch/mips/loongson1/common/setup.c b/arch/mips/loongson1/common/setup.c new file mode 100644 index 000000000000..62128cc27e68 --- /dev/null +++ b/arch/mips/loongson1/common/setup.c @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2011 Zhang, Keguang <keguang.zhang@gmail.com> + * + * 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 <asm/bootinfo.h> + +#include <prom.h> + +void __init plat_mem_setup(void) +{ + add_memory_region(0x0, (memsize << 20), BOOT_MEM_RAM); +} + +const char *get_system_type(void) +{ + unsigned int processor_id = (¤t_cpu_data)->processor_id; + + switch (processor_id & PRID_REV_MASK) { + case PRID_REV_LOONGSON1B: + return "LOONGSON LS1B"; + default: + return "LOONGSON (unknown)"; + } +} |