diff options
Diffstat (limited to 'arch/mips/loongson64/loongson-3')
-rw-r--r-- | arch/mips/loongson64/loongson-3/Makefile | 10 | ||||
-rw-r--r-- | arch/mips/loongson64/loongson-3/acpi_init.c | 151 | ||||
-rw-r--r-- | arch/mips/loongson64/loongson-3/cop2-ex.c | 64 | ||||
-rw-r--r-- | arch/mips/loongson64/loongson-3/dma.c | 25 | ||||
-rw-r--r-- | arch/mips/loongson64/loongson-3/hpet.c | 289 | ||||
-rw-r--r-- | arch/mips/loongson64/loongson-3/irq.c | 158 | ||||
-rw-r--r-- | arch/mips/loongson64/loongson-3/numa.c | 296 | ||||
-rw-r--r-- | arch/mips/loongson64/loongson-3/platform.c | 46 | ||||
-rw-r--r-- | arch/mips/loongson64/loongson-3/smp.c | 752 | ||||
-rw-r--r-- | arch/mips/loongson64/loongson-3/smp.h | 31 |
10 files changed, 1822 insertions, 0 deletions
diff --git a/arch/mips/loongson64/loongson-3/Makefile b/arch/mips/loongson64/loongson-3/Makefile new file mode 100644 index 000000000..b5a0c2fa5 --- /dev/null +++ b/arch/mips/loongson64/loongson-3/Makefile @@ -0,0 +1,10 @@ +# +# Makefile for Loongson-3 family machines +# +obj-y += irq.o cop2-ex.o platform.o acpi_init.o dma.o + +obj-$(CONFIG_SMP) += smp.o + +obj-$(CONFIG_NUMA) += numa.o + +obj-$(CONFIG_RS780_HPET) += hpet.o diff --git a/arch/mips/loongson64/loongson-3/acpi_init.c b/arch/mips/loongson64/loongson-3/acpi_init.c new file mode 100644 index 000000000..8d7c119dd --- /dev/null +++ b/arch/mips/loongson64/loongson-3/acpi_init.c @@ -0,0 +1,151 @@ +// SPDX-License-Identifier: GPL-2.0 +#include <linux/io.h> +#include <linux/init.h> +#include <linux/ioport.h> +#include <linux/export.h> + +#define SBX00_ACPI_IO_BASE 0x800 +#define SBX00_ACPI_IO_SIZE 0x100 + +#define ACPI_PM_EVT_BLK (SBX00_ACPI_IO_BASE + 0x00) /* 4 bytes */ +#define ACPI_PM_CNT_BLK (SBX00_ACPI_IO_BASE + 0x04) /* 2 bytes */ +#define ACPI_PMA_CNT_BLK (SBX00_ACPI_IO_BASE + 0x0F) /* 1 byte */ +#define ACPI_PM_TMR_BLK (SBX00_ACPI_IO_BASE + 0x18) /* 4 bytes */ +#define ACPI_GPE0_BLK (SBX00_ACPI_IO_BASE + 0x10) /* 8 bytes */ +#define ACPI_END (SBX00_ACPI_IO_BASE + 0x80) + +#define PM_INDEX 0xCD6 +#define PM_DATA 0xCD7 +#define PM2_INDEX 0xCD0 +#define PM2_DATA 0xCD1 + +/* + * SCI interrupt need acpi space, allocate here + */ + +static int __init register_acpi_resource(void) +{ + request_region(SBX00_ACPI_IO_BASE, SBX00_ACPI_IO_SIZE, "acpi"); + return 0; +} + +static void pmio_write_index(u16 index, u8 reg, u8 value) +{ + outb(reg, index); + outb(value, index + 1); +} + +static u8 pmio_read_index(u16 index, u8 reg) +{ + outb(reg, index); + return inb(index + 1); +} + +void pm_iowrite(u8 reg, u8 value) +{ + pmio_write_index(PM_INDEX, reg, value); +} +EXPORT_SYMBOL(pm_iowrite); + +u8 pm_ioread(u8 reg) +{ + return pmio_read_index(PM_INDEX, reg); +} +EXPORT_SYMBOL(pm_ioread); + +void pm2_iowrite(u8 reg, u8 value) +{ + pmio_write_index(PM2_INDEX, reg, value); +} +EXPORT_SYMBOL(pm2_iowrite); + +u8 pm2_ioread(u8 reg) +{ + return pmio_read_index(PM2_INDEX, reg); +} +EXPORT_SYMBOL(pm2_ioread); + +static void acpi_hw_clear_status(void) +{ + u16 value; + + /* PMStatus: Clear WakeStatus/PwrBtnStatus */ + value = inw(ACPI_PM_EVT_BLK); + value |= (1 << 8 | 1 << 15); + outw(value, ACPI_PM_EVT_BLK); + + /* GPEStatus: Clear all generated events */ + outl(inl(ACPI_GPE0_BLK), ACPI_GPE0_BLK); +} + +void acpi_registers_setup(void) +{ + u32 value; + + /* PM Status Base */ + pm_iowrite(0x20, ACPI_PM_EVT_BLK & 0xff); + pm_iowrite(0x21, ACPI_PM_EVT_BLK >> 8); + + /* PM Control Base */ + pm_iowrite(0x22, ACPI_PM_CNT_BLK & 0xff); + pm_iowrite(0x23, ACPI_PM_CNT_BLK >> 8); + + /* GPM Base */ + pm_iowrite(0x28, ACPI_GPE0_BLK & 0xff); + pm_iowrite(0x29, ACPI_GPE0_BLK >> 8); + + /* ACPI End */ + pm_iowrite(0x2e, ACPI_END & 0xff); + pm_iowrite(0x2f, ACPI_END >> 8); + + /* IO Decode: When AcpiDecodeEnable set, South-Bridge uses the contents + * of the PM registers at index 0x20~0x2B to decode ACPI I/O address. */ + pm_iowrite(0x0e, 1 << 3); + + /* SCI_EN set */ + outw(1, ACPI_PM_CNT_BLK); + + /* Enable to generate SCI */ + pm_iowrite(0x10, pm_ioread(0x10) | 1); + + /* GPM3/GPM9 enable */ + value = inl(ACPI_GPE0_BLK + 4); + outl(value | (1 << 14) | (1 << 22), ACPI_GPE0_BLK + 4); + + /* Set GPM9 as input */ + pm_iowrite(0x8d, pm_ioread(0x8d) & (~(1 << 1))); + + /* Set GPM9 as non-output */ + pm_iowrite(0x94, pm_ioread(0x94) | (1 << 3)); + + /* GPM3 config ACPI trigger SCIOUT */ + pm_iowrite(0x33, pm_ioread(0x33) & (~(3 << 4))); + + /* GPM9 config ACPI trigger SCIOUT */ + pm_iowrite(0x3d, pm_ioread(0x3d) & (~(3 << 2))); + + /* GPM3 config falling edge trigger */ + pm_iowrite(0x37, pm_ioread(0x37) & (~(1 << 6))); + + /* No wait for STPGNT# in ACPI Sx state */ + pm_iowrite(0x7c, pm_ioread(0x7c) | (1 << 6)); + + /* Set GPM3 pull-down enable */ + value = pm2_ioread(0xf6); + value |= ((1 << 7) | (1 << 3)); + pm2_iowrite(0xf6, value); + + /* Set GPM9 pull-down enable */ + value = pm2_ioread(0xf8); + value |= ((1 << 5) | (1 << 1)); + pm2_iowrite(0xf8, value); +} + +int __init sbx00_acpi_init(void) +{ + register_acpi_resource(); + acpi_registers_setup(); + acpi_hw_clear_status(); + + return 0; +} diff --git a/arch/mips/loongson64/loongson-3/cop2-ex.c b/arch/mips/loongson64/loongson-3/cop2-ex.c new file mode 100644 index 000000000..621d6af5f --- /dev/null +++ b/arch/mips/loongson64/loongson-3/cop2-ex.c @@ -0,0 +1,64 @@ +/* + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + * + * Copyright (C) 2014 Lemote Corporation. + * written by Huacai Chen <chenhc@lemote.com> + * + * based on arch/mips/cavium-octeon/cpu.c + * Copyright (C) 2009 Wind River Systems, + * written by Ralf Baechle <ralf@linux-mips.org> + */ +#include <linux/init.h> +#include <linux/sched.h> +#include <linux/notifier.h> +#include <linux/ptrace.h> + +#include <asm/fpu.h> +#include <asm/cop2.h> +#include <asm/current.h> +#include <asm/mipsregs.h> + +static int loongson_cu2_call(struct notifier_block *nfb, unsigned long action, + void *data) +{ + int fpu_owned; + int fr = !test_thread_flag(TIF_32BIT_FPREGS); + + switch (action) { + case CU2_EXCEPTION: + preempt_disable(); + fpu_owned = __is_fpu_owner(); + if (!fr) + set_c0_status(ST0_CU1 | ST0_CU2); + else + set_c0_status(ST0_CU1 | ST0_CU2 | ST0_FR); + enable_fpu_hazard(); + KSTK_STATUS(current) |= (ST0_CU1 | ST0_CU2); + if (fr) + KSTK_STATUS(current) |= ST0_FR; + else + KSTK_STATUS(current) &= ~ST0_FR; + /* If FPU is owned, we needn't init or restore fp */ + if (!fpu_owned) { + set_thread_flag(TIF_USEDFPU); + if (!used_math()) { + _init_fpu(current->thread.fpu.fcr31); + set_used_math(); + } else + _restore_fp(current); + } + preempt_enable(); + + return NOTIFY_STOP; /* Don't call default notifier */ + } + + return NOTIFY_OK; /* Let default notifier send signals */ +} + +static int __init loongson_cu2_setup(void) +{ + return cu2_notifier(loongson_cu2_call, 0); +} +early_initcall(loongson_cu2_setup); diff --git a/arch/mips/loongson64/loongson-3/dma.c b/arch/mips/loongson64/loongson-3/dma.c new file mode 100644 index 000000000..5e86635f7 --- /dev/null +++ b/arch/mips/loongson64/loongson-3/dma.c @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: GPL-2.0 +#include <linux/dma-direct.h> +#include <linux/init.h> +#include <linux/swiotlb.h> + +dma_addr_t __phys_to_dma(struct device *dev, phys_addr_t paddr) +{ + /* We extract 2bit node id (bit 44~47, only bit 44~45 used now) from + * Loongson-3's 48bit address space and embed it into 40bit */ + long nid = (paddr >> 44) & 0x3; + return ((nid << 44) ^ paddr) | (nid << 37); +} + +phys_addr_t __dma_to_phys(struct device *dev, dma_addr_t daddr) +{ + /* We extract 2bit node id (bit 44~47, only bit 44~45 used now) from + * Loongson-3's 48bit address space and embed it into 40bit */ + long nid = (daddr >> 37) & 0x3; + return ((nid << 37) ^ daddr) | (nid << 44); +} + +void __init plat_swiotlb_setup(void) +{ + swiotlb_init(1); +} diff --git a/arch/mips/loongson64/loongson-3/hpet.c b/arch/mips/loongson64/loongson-3/hpet.c new file mode 100644 index 000000000..ed15430ad --- /dev/null +++ b/arch/mips/loongson64/loongson-3/hpet.c @@ -0,0 +1,289 @@ +// SPDX-License-Identifier: GPL-2.0 +#include <linux/init.h> +#include <linux/pci.h> +#include <linux/percpu.h> +#include <linux/delay.h> +#include <linux/spinlock.h> +#include <linux/interrupt.h> + +#include <asm/hpet.h> +#include <asm/time.h> + +#define SMBUS_CFG_BASE (loongson_sysconf.ht_control_base + 0x0300a000) +#define SMBUS_PCI_REG40 0x40 +#define SMBUS_PCI_REG64 0x64 +#define SMBUS_PCI_REGB4 0xb4 + +#define HPET_MIN_CYCLES 16 +#define HPET_MIN_PROG_DELTA (HPET_MIN_CYCLES * 12) + +static DEFINE_SPINLOCK(hpet_lock); +DEFINE_PER_CPU(struct clock_event_device, hpet_clockevent_device); + +static unsigned int smbus_read(int offset) +{ + return *(volatile unsigned int *)(SMBUS_CFG_BASE + offset); +} + +static void smbus_write(int offset, int data) +{ + *(volatile unsigned int *)(SMBUS_CFG_BASE + offset) = data; +} + +static void smbus_enable(int offset, int bit) +{ + unsigned int cfg = smbus_read(offset); + + cfg |= bit; + smbus_write(offset, cfg); +} + +static int hpet_read(int offset) +{ + return *(volatile unsigned int *)(HPET_MMIO_ADDR + offset); +} + +static void hpet_write(int offset, int data) +{ + *(volatile unsigned int *)(HPET_MMIO_ADDR + offset) = data; +} + +static void hpet_start_counter(void) +{ + unsigned int cfg = hpet_read(HPET_CFG); + + cfg |= HPET_CFG_ENABLE; + hpet_write(HPET_CFG, cfg); +} + +static void hpet_stop_counter(void) +{ + unsigned int cfg = hpet_read(HPET_CFG); + + cfg &= ~HPET_CFG_ENABLE; + hpet_write(HPET_CFG, cfg); +} + +static void hpet_reset_counter(void) +{ + hpet_write(HPET_COUNTER, 0); + hpet_write(HPET_COUNTER + 4, 0); +} + +static void hpet_restart_counter(void) +{ + hpet_stop_counter(); + hpet_reset_counter(); + hpet_start_counter(); +} + +static void hpet_enable_legacy_int(void) +{ + /* Do nothing on Loongson-3 */ +} + +static int hpet_set_state_periodic(struct clock_event_device *evt) +{ + int cfg; + + spin_lock(&hpet_lock); + + pr_info("set clock event to periodic mode!\n"); + /* stop counter */ + hpet_stop_counter(); + + /* enables the timer0 to generate a periodic interrupt */ + cfg = hpet_read(HPET_T0_CFG); + cfg &= ~HPET_TN_LEVEL; + cfg |= HPET_TN_ENABLE | HPET_TN_PERIODIC | HPET_TN_SETVAL | + HPET_TN_32BIT; + hpet_write(HPET_T0_CFG, cfg); + + /* set the comparator */ + hpet_write(HPET_T0_CMP, HPET_COMPARE_VAL); + udelay(1); + hpet_write(HPET_T0_CMP, HPET_COMPARE_VAL); + + /* start counter */ + hpet_start_counter(); + + spin_unlock(&hpet_lock); + return 0; +} + +static int hpet_set_state_shutdown(struct clock_event_device *evt) +{ + int cfg; + + spin_lock(&hpet_lock); + + cfg = hpet_read(HPET_T0_CFG); + cfg &= ~HPET_TN_ENABLE; + hpet_write(HPET_T0_CFG, cfg); + + spin_unlock(&hpet_lock); + return 0; +} + +static int hpet_set_state_oneshot(struct clock_event_device *evt) +{ + int cfg; + + spin_lock(&hpet_lock); + + pr_info("set clock event to one shot mode!\n"); + cfg = hpet_read(HPET_T0_CFG); + /* + * set timer0 type + * 1 : periodic interrupt + * 0 : non-periodic(oneshot) interrupt + */ + cfg &= ~HPET_TN_PERIODIC; + cfg |= HPET_TN_ENABLE | HPET_TN_32BIT; + hpet_write(HPET_T0_CFG, cfg); + + spin_unlock(&hpet_lock); + return 0; +} + +static int hpet_tick_resume(struct clock_event_device *evt) +{ + spin_lock(&hpet_lock); + hpet_enable_legacy_int(); + spin_unlock(&hpet_lock); + + return 0; +} + +static int hpet_next_event(unsigned long delta, + struct clock_event_device *evt) +{ + u32 cnt; + s32 res; + + cnt = hpet_read(HPET_COUNTER); + cnt += (u32) delta; + hpet_write(HPET_T0_CMP, cnt); + + res = (s32)(cnt - hpet_read(HPET_COUNTER)); + + return res < HPET_MIN_CYCLES ? -ETIME : 0; +} + +static irqreturn_t hpet_irq_handler(int irq, void *data) +{ + int is_irq; + struct clock_event_device *cd; + unsigned int cpu = smp_processor_id(); + + is_irq = hpet_read(HPET_STATUS); + if (is_irq & HPET_T0_IRS) { + /* clear the TIMER0 irq status register */ + hpet_write(HPET_STATUS, HPET_T0_IRS); + cd = &per_cpu(hpet_clockevent_device, cpu); + cd->event_handler(cd); + return IRQ_HANDLED; + } + return IRQ_NONE; +} + +static struct irqaction hpet_irq = { + .handler = hpet_irq_handler, + .flags = IRQF_NOBALANCING | IRQF_TIMER, + .name = "hpet", +}; + +/* + * hpet address assignation and irq setting should be done in bios. + * but pmon don't do this, we just setup here directly. + * The operation under is normal. unfortunately, hpet_setup process + * is before pci initialize. + * + * { + * struct pci_dev *pdev; + * + * pdev = pci_get_device(PCI_VENDOR_ID_ATI, PCI_DEVICE_ID_ATI_SBX00_SMBUS, NULL); + * pci_write_config_word(pdev, SMBUS_PCI_REGB4, HPET_ADDR); + * + * ... + * } + */ +static void hpet_setup(void) +{ + /* set hpet base address */ + smbus_write(SMBUS_PCI_REGB4, HPET_ADDR); + + /* enable decoding of access to HPET MMIO*/ + smbus_enable(SMBUS_PCI_REG40, (1 << 28)); + + /* HPET irq enable */ + smbus_enable(SMBUS_PCI_REG64, (1 << 10)); + + hpet_enable_legacy_int(); +} + +void __init setup_hpet_timer(void) +{ + unsigned int cpu = smp_processor_id(); + struct clock_event_device *cd; + + hpet_setup(); + + cd = &per_cpu(hpet_clockevent_device, cpu); + cd->name = "hpet"; + cd->rating = 100; + cd->features = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT; + cd->set_state_shutdown = hpet_set_state_shutdown; + cd->set_state_periodic = hpet_set_state_periodic; + cd->set_state_oneshot = hpet_set_state_oneshot; + cd->tick_resume = hpet_tick_resume; + cd->set_next_event = hpet_next_event; + cd->irq = HPET_T0_IRQ; + cd->cpumask = cpumask_of(cpu); + clockevent_set_clock(cd, HPET_FREQ); + cd->max_delta_ns = clockevent_delta2ns(0x7fffffff, cd); + cd->max_delta_ticks = 0x7fffffff; + cd->min_delta_ns = clockevent_delta2ns(HPET_MIN_PROG_DELTA, cd); + cd->min_delta_ticks = HPET_MIN_PROG_DELTA; + + clockevents_register_device(cd); + setup_irq(HPET_T0_IRQ, &hpet_irq); + pr_info("hpet clock event device register\n"); +} + +static u64 hpet_read_counter(struct clocksource *cs) +{ + return (u64)hpet_read(HPET_COUNTER); +} + +static void hpet_suspend(struct clocksource *cs) +{ +} + +static void hpet_resume(struct clocksource *cs) +{ + hpet_setup(); + hpet_restart_counter(); +} + +static struct clocksource csrc_hpet = { + .name = "hpet", + /* mips clocksource rating is less than 300, so hpet is better. */ + .rating = 300, + .read = hpet_read_counter, + .mask = CLOCKSOURCE_MASK(32), + /* oneshot mode work normal with this flag */ + .flags = CLOCK_SOURCE_IS_CONTINUOUS, + .suspend = hpet_suspend, + .resume = hpet_resume, + .mult = 0, + .shift = 10, +}; + +int __init init_hpet_clocksource(void) +{ + csrc_hpet.mult = clocksource_hz2mult(HPET_FREQ, csrc_hpet.shift); + return clocksource_register_hz(&csrc_hpet, HPET_FREQ); +} + +arch_initcall(init_hpet_clocksource); diff --git a/arch/mips/loongson64/loongson-3/irq.c b/arch/mips/loongson64/loongson-3/irq.c new file mode 100644 index 000000000..5605061f5 --- /dev/null +++ b/arch/mips/loongson64/loongson-3/irq.c @@ -0,0 +1,158 @@ +// SPDX-License-Identifier: GPL-2.0 +#include <loongson.h> +#include <irq.h> +#include <linux/interrupt.h> +#include <linux/init.h> + +#include <asm/irq_cpu.h> +#include <asm/i8259.h> +#include <asm/mipsregs.h> + +#include "smp.h" + +extern void loongson3_send_irq_by_ipi(int cpu, int irqs); + +unsigned int irq_cpu[16] = {[0 ... 15] = -1}; +unsigned int ht_irq[] = {0, 1, 3, 4, 5, 6, 7, 8, 12, 14, 15}; +unsigned int local_irq = 1<<0 | 1<<1 | 1<<2 | 1<<7 | 1<<8 | 1<<12; + +int plat_set_irq_affinity(struct irq_data *d, const struct cpumask *affinity, + bool force) +{ + unsigned int cpu; + struct cpumask new_affinity; + + /* I/O devices are connected on package-0 */ + cpumask_copy(&new_affinity, affinity); + for_each_cpu(cpu, affinity) + if (cpu_data[cpu].package > 0) + cpumask_clear_cpu(cpu, &new_affinity); + + if (cpumask_empty(&new_affinity)) + return -EINVAL; + + cpumask_copy(d->common->affinity, &new_affinity); + + return IRQ_SET_MASK_OK_NOCOPY; +} + +static void ht_irqdispatch(void) +{ + unsigned int i, irq; + struct irq_data *irqd; + struct cpumask affinity; + + irq = LOONGSON_HT1_INT_VECTOR(0); + LOONGSON_HT1_INT_VECTOR(0) = irq; /* Acknowledge the IRQs */ + + for (i = 0; i < ARRAY_SIZE(ht_irq); i++) { + if (!(irq & (0x1 << ht_irq[i]))) + continue; + + /* handled by local core */ + if (local_irq & (0x1 << ht_irq[i])) { + do_IRQ(ht_irq[i]); + continue; + } + + irqd = irq_get_irq_data(ht_irq[i]); + cpumask_and(&affinity, irqd->common->affinity, cpu_active_mask); + if (cpumask_empty(&affinity)) { + do_IRQ(ht_irq[i]); + continue; + } + + irq_cpu[ht_irq[i]] = cpumask_next(irq_cpu[ht_irq[i]], &affinity); + if (irq_cpu[ht_irq[i]] >= nr_cpu_ids) + irq_cpu[ht_irq[i]] = cpumask_first(&affinity); + + if (irq_cpu[ht_irq[i]] == 0) { + do_IRQ(ht_irq[i]); + continue; + } + + /* balanced by other cores */ + loongson3_send_irq_by_ipi(irq_cpu[ht_irq[i]], (0x1 << ht_irq[i])); + } +} + +#define UNUSED_IPS (CAUSEF_IP5 | CAUSEF_IP4 | CAUSEF_IP1 | CAUSEF_IP0) + +void mach_irq_dispatch(unsigned int pending) +{ + if (pending & CAUSEF_IP7) + do_IRQ(LOONGSON_TIMER_IRQ); +#if defined(CONFIG_SMP) + if (pending & CAUSEF_IP6) + loongson3_ipi_interrupt(NULL); +#endif + if (pending & CAUSEF_IP3) + ht_irqdispatch(); + if (pending & CAUSEF_IP2) + do_IRQ(LOONGSON_UART_IRQ); + if (pending & UNUSED_IPS) { + pr_err("%s : spurious interrupt\n", __func__); + spurious_interrupt(); + } +} + +static inline void mask_loongson_irq(struct irq_data *d) { } +static inline void unmask_loongson_irq(struct irq_data *d) { } + + /* For MIPS IRQs which shared by all cores */ +static struct irq_chip loongson_irq_chip = { + .name = "Loongson", + .irq_ack = mask_loongson_irq, + .irq_mask = mask_loongson_irq, + .irq_mask_ack = mask_loongson_irq, + .irq_unmask = unmask_loongson_irq, + .irq_eoi = unmask_loongson_irq, +}; + +void irq_router_init(void) +{ + int i; + + /* route LPC int to cpu core0 int 0 */ + LOONGSON_INT_ROUTER_LPC = + LOONGSON_INT_COREx_INTy(loongson_sysconf.boot_cpu_id, 0); + /* route HT1 int0 ~ int7 to cpu core0 INT1*/ + for (i = 0; i < 8; i++) + LOONGSON_INT_ROUTER_HT1(i) = + LOONGSON_INT_COREx_INTy(loongson_sysconf.boot_cpu_id, 1); + /* enable HT1 interrupt */ + LOONGSON_HT1_INTN_EN(0) = 0xffffffff; + /* enable router interrupt intenset */ + LOONGSON_INT_ROUTER_INTENSET = + LOONGSON_INT_ROUTER_INTEN | (0xffff << 16) | 0x1 << 10; +} + +void __init mach_init_irq(void) +{ + struct irq_chip *chip; + + clear_c0_status(ST0_IM | ST0_BEV); + + irq_router_init(); + mips_cpu_irq_init(); + init_i8259_irqs(); + chip = irq_get_chip(I8259A_IRQ_BASE); + chip->irq_set_affinity = plat_set_irq_affinity; + + irq_set_chip_and_handler(LOONGSON_UART_IRQ, + &loongson_irq_chip, handle_percpu_irq); + irq_set_chip_and_handler(LOONGSON_BRIDGE_IRQ, + &loongson_irq_chip, handle_percpu_irq); + + set_c0_status(STATUSF_IP2 | STATUSF_IP3 | STATUSF_IP6); +} + +#ifdef CONFIG_HOTPLUG_CPU + +void fixup_irqs(void) +{ + irq_cpu_offline(); + clear_c0_status(ST0_IM); +} + +#endif diff --git a/arch/mips/loongson64/loongson-3/numa.c b/arch/mips/loongson64/loongson-3/numa.c new file mode 100644 index 000000000..9717106de --- /dev/null +++ b/arch/mips/loongson64/loongson-3/numa.c @@ -0,0 +1,296 @@ +/* + * Copyright (C) 2010 Loongson Inc. & Lemote Inc. & + * Institute of Computing Technology + * Author: Xiang Gao, gaoxiang@ict.ac.cn + * Huacai Chen, chenhc@lemote.com + * Xiaofu Meng, Shuangshuang Zhang + * + * 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/init.h> +#include <linux/kernel.h> +#include <linux/mm.h> +#include <linux/mmzone.h> +#include <linux/export.h> +#include <linux/nodemask.h> +#include <linux/swap.h> +#include <linux/memblock.h> +#include <linux/bootmem.h> +#include <linux/pfn.h> +#include <linux/highmem.h> +#include <asm/page.h> +#include <asm/pgalloc.h> +#include <asm/sections.h> +#include <linux/irq.h> +#include <asm/bootinfo.h> +#include <asm/mc146818-time.h> +#include <asm/time.h> +#include <asm/wbflush.h> +#include <boot_param.h> + +static struct node_data prealloc__node_data[MAX_NUMNODES]; +unsigned char __node_distances[MAX_NUMNODES][MAX_NUMNODES]; +EXPORT_SYMBOL(__node_distances); +struct node_data *__node_data[MAX_NUMNODES]; +EXPORT_SYMBOL(__node_data); + +static void enable_lpa(void) +{ + unsigned long value; + + value = __read_32bit_c0_register($16, 3); + value |= 0x00000080; + __write_32bit_c0_register($16, 3, value); + value = __read_32bit_c0_register($16, 3); + pr_info("CP0_Config3: CP0 16.3 (0x%lx)\n", value); + + value = __read_32bit_c0_register($5, 1); + value |= 0x20000000; + __write_32bit_c0_register($5, 1, value); + value = __read_32bit_c0_register($5, 1); + pr_info("CP0_PageGrain: CP0 5.1 (0x%lx)\n", value); +} + +static void cpu_node_probe(void) +{ + int i; + + nodes_clear(node_possible_map); + nodes_clear(node_online_map); + for (i = 0; i < loongson_sysconf.nr_nodes; i++) { + node_set_state(num_online_nodes(), N_POSSIBLE); + node_set_online(num_online_nodes()); + } + + pr_info("NUMA: Discovered %d cpus on %d nodes\n", + loongson_sysconf.nr_cpus, num_online_nodes()); +} + +static int __init compute_node_distance(int row, int col) +{ + int package_row = row * loongson_sysconf.cores_per_node / + loongson_sysconf.cores_per_package; + int package_col = col * loongson_sysconf.cores_per_node / + loongson_sysconf.cores_per_package; + + if (col == row) + return 0; + else if (package_row == package_col) + return 40; + else + return 100; +} + +static void __init init_topology_matrix(void) +{ + int row, col; + + for (row = 0; row < MAX_NUMNODES; row++) + for (col = 0; col < MAX_NUMNODES; col++) + __node_distances[row][col] = -1; + + for_each_online_node(row) { + for_each_online_node(col) { + __node_distances[row][col] = + compute_node_distance(row, col); + } + } +} + +static unsigned long nid_to_addroffset(unsigned int nid) +{ + unsigned long result; + switch (nid) { + case 0: + default: + result = NODE0_ADDRSPACE_OFFSET; + break; + case 1: + result = NODE1_ADDRSPACE_OFFSET; + break; + case 2: + result = NODE2_ADDRSPACE_OFFSET; + break; + case 3: + result = NODE3_ADDRSPACE_OFFSET; + break; + } + return result; +} + +static void __init szmem(unsigned int node) +{ + u32 i, mem_type; + static unsigned long num_physpages = 0; + u64 node_id, node_psize, start_pfn, end_pfn, mem_start, mem_size; + + /* Parse memory information and activate */ + for (i = 0; i < loongson_memmap->nr_map; i++) { + node_id = loongson_memmap->map[i].node_id; + if (node_id != node) + continue; + + mem_type = loongson_memmap->map[i].mem_type; + mem_size = loongson_memmap->map[i].mem_size; + mem_start = loongson_memmap->map[i].mem_start; + + switch (mem_type) { + case SYSTEM_RAM_LOW: + start_pfn = ((node_id << 44) + mem_start) >> PAGE_SHIFT; + node_psize = (mem_size << 20) >> PAGE_SHIFT; + end_pfn = start_pfn + node_psize; + num_physpages += node_psize; + pr_info("Node%d: mem_type:%d, mem_start:0x%llx, mem_size:0x%llx MB\n", + (u32)node_id, mem_type, mem_start, mem_size); + pr_info(" start_pfn:0x%llx, end_pfn:0x%llx, num_physpages:0x%lx\n", + start_pfn, end_pfn, num_physpages); + add_memory_region((node_id << 44) + mem_start, + (u64)mem_size << 20, BOOT_MEM_RAM); + memblock_add_node(PFN_PHYS(start_pfn), + PFN_PHYS(end_pfn - start_pfn), node); + break; + case SYSTEM_RAM_HIGH: + start_pfn = ((node_id << 44) + mem_start) >> PAGE_SHIFT; + node_psize = (mem_size << 20) >> PAGE_SHIFT; + end_pfn = start_pfn + node_psize; + num_physpages += node_psize; + pr_info("Node%d: mem_type:%d, mem_start:0x%llx, mem_size:0x%llx MB\n", + (u32)node_id, mem_type, mem_start, mem_size); + pr_info(" start_pfn:0x%llx, end_pfn:0x%llx, num_physpages:0x%lx\n", + start_pfn, end_pfn, num_physpages); + add_memory_region((node_id << 44) + mem_start, + (u64)mem_size << 20, BOOT_MEM_RAM); + memblock_add_node(PFN_PHYS(start_pfn), + PFN_PHYS(end_pfn - start_pfn), node); + break; + case SYSTEM_RAM_RESERVED: + pr_info("Node%d: mem_type:%d, mem_start:0x%llx, mem_size:0x%llx MB\n", + (u32)node_id, mem_type, mem_start, mem_size); + add_memory_region((node_id << 44) + mem_start, + (u64)mem_size << 20, BOOT_MEM_RESERVED); + memblock_reserve(((node_id << 44) + mem_start), + mem_size << 20); + break; + } + } +} + +static void __init node_mem_init(unsigned int node) +{ + unsigned long bootmap_size; + unsigned long node_addrspace_offset; + unsigned long start_pfn, end_pfn, freepfn; + + node_addrspace_offset = nid_to_addroffset(node); + pr_info("Node%d's addrspace_offset is 0x%lx\n", + node, node_addrspace_offset); + + get_pfn_range_for_nid(node, &start_pfn, &end_pfn); + freepfn = start_pfn; + if (node == 0) + freepfn = PFN_UP(__pa_symbol(&_end)); /* kernel end address */ + pr_info("Node%d: start_pfn=0x%lx, end_pfn=0x%lx, freepfn=0x%lx\n", + node, start_pfn, end_pfn, freepfn); + + __node_data[node] = prealloc__node_data + node; + + NODE_DATA(node)->bdata = &bootmem_node_data[node]; + NODE_DATA(node)->node_start_pfn = start_pfn; + NODE_DATA(node)->node_spanned_pages = end_pfn - start_pfn; + + bootmap_size = init_bootmem_node(NODE_DATA(node), freepfn, + start_pfn, end_pfn); + free_bootmem_with_active_regions(node, end_pfn); + if (node == 0) /* used by finalize_initrd() */ + max_low_pfn = end_pfn; + + /* This is reserved for the kernel and bdata->node_bootmem_map */ + reserve_bootmem_node(NODE_DATA(node), start_pfn << PAGE_SHIFT, + ((freepfn - start_pfn) << PAGE_SHIFT) + bootmap_size, + BOOTMEM_DEFAULT); + + if (node == 0 && node_end_pfn(0) >= (0xffffffff >> PAGE_SHIFT)) { + /* Reserve 0xfe000000~0xffffffff for RS780E integrated GPU */ + reserve_bootmem_node(NODE_DATA(node), + (node_addrspace_offset | 0xfe000000), + 32 << 20, BOOTMEM_DEFAULT); + } + + sparse_memory_present_with_active_regions(node); +} + +static __init void prom_meminit(void) +{ + unsigned int node, cpu, active_cpu = 0; + + cpu_node_probe(); + init_topology_matrix(); + + for (node = 0; node < loongson_sysconf.nr_nodes; node++) { + if (node_online(node)) { + szmem(node); + node_mem_init(node); + cpumask_clear(&__node_data[(node)]->cpumask); + } + } + for (cpu = 0; cpu < loongson_sysconf.nr_cpus; cpu++) { + node = cpu / loongson_sysconf.cores_per_node; + if (node >= num_online_nodes()) + node = 0; + + if (loongson_sysconf.reserved_cpus_mask & (1<<cpu)) + continue; + + cpumask_set_cpu(active_cpu, &__node_data[(node)]->cpumask); + pr_info("NUMA: set cpumask cpu %d on node %d\n", active_cpu, node); + + active_cpu++; + } +} + +void __init paging_init(void) +{ + unsigned node; + unsigned long zones_size[MAX_NR_ZONES] = {0, }; + + pagetable_init(); + + for_each_online_node(node) { + unsigned long start_pfn, end_pfn; + + get_pfn_range_for_nid(node, &start_pfn, &end_pfn); + + if (end_pfn > max_low_pfn) + max_low_pfn = end_pfn; + } +#ifdef CONFIG_ZONE_DMA32 + zones_size[ZONE_DMA32] = MAX_DMA32_PFN; +#endif + zones_size[ZONE_NORMAL] = max_low_pfn; + free_area_init_nodes(zones_size); +} + +void __init mem_init(void) +{ + high_memory = (void *) __va(get_num_physpages() << PAGE_SHIFT); + free_all_bootmem(); + setup_zero_pages(); /* This comes from node 0 */ + mem_init_print_info(NULL); +} + +/* All PCI device belongs to logical Node-0 */ +int pcibus_to_node(struct pci_bus *bus) +{ + return 0; +} +EXPORT_SYMBOL(pcibus_to_node); + +void __init prom_init_numa_memory(void) +{ + enable_lpa(); + prom_meminit(); +} +EXPORT_SYMBOL(prom_init_numa_memory); diff --git a/arch/mips/loongson64/loongson-3/platform.c b/arch/mips/loongson64/loongson-3/platform.c new file mode 100644 index 000000000..0db4cc319 --- /dev/null +++ b/arch/mips/loongson64/loongson-3/platform.c @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2009 Lemote Inc. + * Author: Wu Zhangjin, wuzhangjin@gmail.com + * Xiang Yu, xiangy@lemote.com + * Chen Huacai, chenhc@lemote.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/err.h> +#include <linux/slab.h> +#include <linux/platform_device.h> +#include <asm/bootinfo.h> +#include <boot_param.h> +#include <loongson_hwmon.h> +#include <workarounds.h> + +static int __init loongson3_platform_init(void) +{ + int i; + struct platform_device *pdev; + + if (loongson_sysconf.ecname[0] != '\0') + platform_device_register_simple(loongson_sysconf.ecname, -1, NULL, 0); + + for (i = 0; i < loongson_sysconf.nr_sensors; i++) { + if (loongson_sysconf.sensors[i].type > SENSOR_FAN) + continue; + + pdev = kzalloc(sizeof(struct platform_device), GFP_KERNEL); + if (!pdev) + return -ENOMEM; + + pdev->name = loongson_sysconf.sensors[i].name; + pdev->id = loongson_sysconf.sensors[i].id; + pdev->dev.platform_data = &loongson_sysconf.sensors[i]; + platform_device_register(pdev); + } + + return 0; +} + +arch_initcall(loongson3_platform_init); diff --git a/arch/mips/loongson64/loongson-3/smp.c b/arch/mips/loongson64/loongson-3/smp.c new file mode 100644 index 000000000..fea95d003 --- /dev/null +++ b/arch/mips/loongson64/loongson-3/smp.c @@ -0,0 +1,752 @@ +/* + * Copyright (C) 2010, 2011, 2012, Lemote, Inc. + * Author: Chen Huacai, chenhc@lemote.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. + * + * 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. + * + */ + +#include <linux/init.h> +#include <linux/cpu.h> +#include <linux/sched.h> +#include <linux/sched/hotplug.h> +#include <linux/sched/task_stack.h> +#include <linux/smp.h> +#include <linux/cpufreq.h> +#include <asm/processor.h> +#include <asm/time.h> +#include <asm/clock.h> +#include <asm/tlbflush.h> +#include <asm/cacheflush.h> +#include <loongson.h> +#include <workarounds.h> + +#include "smp.h" + +DEFINE_PER_CPU(int, cpu_state); + +static void *ipi_set0_regs[16]; +static void *ipi_clear0_regs[16]; +static void *ipi_status0_regs[16]; +static void *ipi_en0_regs[16]; +static void *ipi_mailbox_buf[16]; +static uint32_t core0_c0count[NR_CPUS]; + +/* read a 32bit value from ipi register */ +#define loongson3_ipi_read32(addr) readl(addr) +/* read a 64bit value from ipi register */ +#define loongson3_ipi_read64(addr) readq(addr) +/* write a 32bit value to ipi register */ +#define loongson3_ipi_write32(action, addr) \ + do { \ + writel(action, addr); \ + __wbflush(); \ + } while (0) +/* write a 64bit value to ipi register */ +#define loongson3_ipi_write64(action, addr) \ + do { \ + writeq(action, addr); \ + __wbflush(); \ + } while (0) + +static void ipi_set0_regs_init(void) +{ + ipi_set0_regs[0] = (void *) + (SMP_CORE_GROUP0_BASE + SMP_CORE0_OFFSET + SET0); + ipi_set0_regs[1] = (void *) + (SMP_CORE_GROUP0_BASE + SMP_CORE1_OFFSET + SET0); + ipi_set0_regs[2] = (void *) + (SMP_CORE_GROUP0_BASE + SMP_CORE2_OFFSET + SET0); + ipi_set0_regs[3] = (void *) + (SMP_CORE_GROUP0_BASE + SMP_CORE3_OFFSET + SET0); + ipi_set0_regs[4] = (void *) + (SMP_CORE_GROUP1_BASE + SMP_CORE0_OFFSET + SET0); + ipi_set0_regs[5] = (void *) + (SMP_CORE_GROUP1_BASE + SMP_CORE1_OFFSET + SET0); + ipi_set0_regs[6] = (void *) + (SMP_CORE_GROUP1_BASE + SMP_CORE2_OFFSET + SET0); + ipi_set0_regs[7] = (void *) + (SMP_CORE_GROUP1_BASE + SMP_CORE3_OFFSET + SET0); + ipi_set0_regs[8] = (void *) + (SMP_CORE_GROUP2_BASE + SMP_CORE0_OFFSET + SET0); + ipi_set0_regs[9] = (void *) + (SMP_CORE_GROUP2_BASE + SMP_CORE1_OFFSET + SET0); + ipi_set0_regs[10] = (void *) + (SMP_CORE_GROUP2_BASE + SMP_CORE2_OFFSET + SET0); + ipi_set0_regs[11] = (void *) + (SMP_CORE_GROUP2_BASE + SMP_CORE3_OFFSET + SET0); + ipi_set0_regs[12] = (void *) + (SMP_CORE_GROUP3_BASE + SMP_CORE0_OFFSET + SET0); + ipi_set0_regs[13] = (void *) + (SMP_CORE_GROUP3_BASE + SMP_CORE1_OFFSET + SET0); + ipi_set0_regs[14] = (void *) + (SMP_CORE_GROUP3_BASE + SMP_CORE2_OFFSET + SET0); + ipi_set0_regs[15] = (void *) + (SMP_CORE_GROUP3_BASE + SMP_CORE3_OFFSET + SET0); +} + +static void ipi_clear0_regs_init(void) +{ + ipi_clear0_regs[0] = (void *) + (SMP_CORE_GROUP0_BASE + SMP_CORE0_OFFSET + CLEAR0); + ipi_clear0_regs[1] = (void *) + (SMP_CORE_GROUP0_BASE + SMP_CORE1_OFFSET + CLEAR0); + ipi_clear0_regs[2] = (void *) + (SMP_CORE_GROUP0_BASE + SMP_CORE2_OFFSET + CLEAR0); + ipi_clear0_regs[3] = (void *) + (SMP_CORE_GROUP0_BASE + SMP_CORE3_OFFSET + CLEAR0); + ipi_clear0_regs[4] = (void *) + (SMP_CORE_GROUP1_BASE + SMP_CORE0_OFFSET + CLEAR0); + ipi_clear0_regs[5] = (void *) + (SMP_CORE_GROUP1_BASE + SMP_CORE1_OFFSET + CLEAR0); + ipi_clear0_regs[6] = (void *) + (SMP_CORE_GROUP1_BASE + SMP_CORE2_OFFSET + CLEAR0); + ipi_clear0_regs[7] = (void *) + (SMP_CORE_GROUP1_BASE + SMP_CORE3_OFFSET + CLEAR0); + ipi_clear0_regs[8] = (void *) + (SMP_CORE_GROUP2_BASE + SMP_CORE0_OFFSET + CLEAR0); + ipi_clear0_regs[9] = (void *) + (SMP_CORE_GROUP2_BASE + SMP_CORE1_OFFSET + CLEAR0); + ipi_clear0_regs[10] = (void *) + (SMP_CORE_GROUP2_BASE + SMP_CORE2_OFFSET + CLEAR0); + ipi_clear0_regs[11] = (void *) + (SMP_CORE_GROUP2_BASE + SMP_CORE3_OFFSET + CLEAR0); + ipi_clear0_regs[12] = (void *) + (SMP_CORE_GROUP3_BASE + SMP_CORE0_OFFSET + CLEAR0); + ipi_clear0_regs[13] = (void *) + (SMP_CORE_GROUP3_BASE + SMP_CORE1_OFFSET + CLEAR0); + ipi_clear0_regs[14] = (void *) + (SMP_CORE_GROUP3_BASE + SMP_CORE2_OFFSET + CLEAR0); + ipi_clear0_regs[15] = (void *) + (SMP_CORE_GROUP3_BASE + SMP_CORE3_OFFSET + CLEAR0); +} + +static void ipi_status0_regs_init(void) +{ + ipi_status0_regs[0] = (void *) + (SMP_CORE_GROUP0_BASE + SMP_CORE0_OFFSET + STATUS0); + ipi_status0_regs[1] = (void *) + (SMP_CORE_GROUP0_BASE + SMP_CORE1_OFFSET + STATUS0); + ipi_status0_regs[2] = (void *) + (SMP_CORE_GROUP0_BASE + SMP_CORE2_OFFSET + STATUS0); + ipi_status0_regs[3] = (void *) + (SMP_CORE_GROUP0_BASE + SMP_CORE3_OFFSET + STATUS0); + ipi_status0_regs[4] = (void *) + (SMP_CORE_GROUP1_BASE + SMP_CORE0_OFFSET + STATUS0); + ipi_status0_regs[5] = (void *) + (SMP_CORE_GROUP1_BASE + SMP_CORE1_OFFSET + STATUS0); + ipi_status0_regs[6] = (void *) + (SMP_CORE_GROUP1_BASE + SMP_CORE2_OFFSET + STATUS0); + ipi_status0_regs[7] = (void *) + (SMP_CORE_GROUP1_BASE + SMP_CORE3_OFFSET + STATUS0); + ipi_status0_regs[8] = (void *) + (SMP_CORE_GROUP2_BASE + SMP_CORE0_OFFSET + STATUS0); + ipi_status0_regs[9] = (void *) + (SMP_CORE_GROUP2_BASE + SMP_CORE1_OFFSET + STATUS0); + ipi_status0_regs[10] = (void *) + (SMP_CORE_GROUP2_BASE + SMP_CORE2_OFFSET + STATUS0); + ipi_status0_regs[11] = (void *) + (SMP_CORE_GROUP2_BASE + SMP_CORE3_OFFSET + STATUS0); + ipi_status0_regs[12] = (void *) + (SMP_CORE_GROUP3_BASE + SMP_CORE0_OFFSET + STATUS0); + ipi_status0_regs[13] = (void *) + (SMP_CORE_GROUP3_BASE + SMP_CORE1_OFFSET + STATUS0); + ipi_status0_regs[14] = (void *) + (SMP_CORE_GROUP3_BASE + SMP_CORE2_OFFSET + STATUS0); + ipi_status0_regs[15] = (void *) + (SMP_CORE_GROUP3_BASE + SMP_CORE3_OFFSET + STATUS0); +} + +static void ipi_en0_regs_init(void) +{ + ipi_en0_regs[0] = (void *) + (SMP_CORE_GROUP0_BASE + SMP_CORE0_OFFSET + EN0); + ipi_en0_regs[1] = (void *) + (SMP_CORE_GROUP0_BASE + SMP_CORE1_OFFSET + EN0); + ipi_en0_regs[2] = (void *) + (SMP_CORE_GROUP0_BASE + SMP_CORE2_OFFSET + EN0); + ipi_en0_regs[3] = (void *) + (SMP_CORE_GROUP0_BASE + SMP_CORE3_OFFSET + EN0); + ipi_en0_regs[4] = (void *) + (SMP_CORE_GROUP1_BASE + SMP_CORE0_OFFSET + EN0); + ipi_en0_regs[5] = (void *) + (SMP_CORE_GROUP1_BASE + SMP_CORE1_OFFSET + EN0); + ipi_en0_regs[6] = (void *) + (SMP_CORE_GROUP1_BASE + SMP_CORE2_OFFSET + EN0); + ipi_en0_regs[7] = (void *) + (SMP_CORE_GROUP1_BASE + SMP_CORE3_OFFSET + EN0); + ipi_en0_regs[8] = (void *) + (SMP_CORE_GROUP2_BASE + SMP_CORE0_OFFSET + EN0); + ipi_en0_regs[9] = (void *) + (SMP_CORE_GROUP2_BASE + SMP_CORE1_OFFSET + EN0); + ipi_en0_regs[10] = (void *) + (SMP_CORE_GROUP2_BASE + SMP_CORE2_OFFSET + EN0); + ipi_en0_regs[11] = (void *) + (SMP_CORE_GROUP2_BASE + SMP_CORE3_OFFSET + EN0); + ipi_en0_regs[12] = (void *) + (SMP_CORE_GROUP3_BASE + SMP_CORE0_OFFSET + EN0); + ipi_en0_regs[13] = (void *) + (SMP_CORE_GROUP3_BASE + SMP_CORE1_OFFSET + EN0); + ipi_en0_regs[14] = (void *) + (SMP_CORE_GROUP3_BASE + SMP_CORE2_OFFSET + EN0); + ipi_en0_regs[15] = (void *) + (SMP_CORE_GROUP3_BASE + SMP_CORE3_OFFSET + EN0); +} + +static void ipi_mailbox_buf_init(void) +{ + ipi_mailbox_buf[0] = (void *) + (SMP_CORE_GROUP0_BASE + SMP_CORE0_OFFSET + BUF); + ipi_mailbox_buf[1] = (void *) + (SMP_CORE_GROUP0_BASE + SMP_CORE1_OFFSET + BUF); + ipi_mailbox_buf[2] = (void *) + (SMP_CORE_GROUP0_BASE + SMP_CORE2_OFFSET + BUF); + ipi_mailbox_buf[3] = (void *) + (SMP_CORE_GROUP0_BASE + SMP_CORE3_OFFSET + BUF); + ipi_mailbox_buf[4] = (void *) + (SMP_CORE_GROUP1_BASE + SMP_CORE0_OFFSET + BUF); + ipi_mailbox_buf[5] = (void *) + (SMP_CORE_GROUP1_BASE + SMP_CORE1_OFFSET + BUF); + ipi_mailbox_buf[6] = (void *) + (SMP_CORE_GROUP1_BASE + SMP_CORE2_OFFSET + BUF); + ipi_mailbox_buf[7] = (void *) + (SMP_CORE_GROUP1_BASE + SMP_CORE3_OFFSET + BUF); + ipi_mailbox_buf[8] = (void *) + (SMP_CORE_GROUP2_BASE + SMP_CORE0_OFFSET + BUF); + ipi_mailbox_buf[9] = (void *) + (SMP_CORE_GROUP2_BASE + SMP_CORE1_OFFSET + BUF); + ipi_mailbox_buf[10] = (void *) + (SMP_CORE_GROUP2_BASE + SMP_CORE2_OFFSET + BUF); + ipi_mailbox_buf[11] = (void *) + (SMP_CORE_GROUP2_BASE + SMP_CORE3_OFFSET + BUF); + ipi_mailbox_buf[12] = (void *) + (SMP_CORE_GROUP3_BASE + SMP_CORE0_OFFSET + BUF); + ipi_mailbox_buf[13] = (void *) + (SMP_CORE_GROUP3_BASE + SMP_CORE1_OFFSET + BUF); + ipi_mailbox_buf[14] = (void *) + (SMP_CORE_GROUP3_BASE + SMP_CORE2_OFFSET + BUF); + ipi_mailbox_buf[15] = (void *) + (SMP_CORE_GROUP3_BASE + SMP_CORE3_OFFSET + BUF); +} + +/* + * Simple enough, just poke the appropriate ipi register + */ +static void loongson3_send_ipi_single(int cpu, unsigned int action) +{ + loongson3_ipi_write32((u32)action, ipi_set0_regs[cpu_logical_map(cpu)]); +} + +static void +loongson3_send_ipi_mask(const struct cpumask *mask, unsigned int action) +{ + unsigned int i; + + for_each_cpu(i, mask) + loongson3_ipi_write32((u32)action, ipi_set0_regs[cpu_logical_map(i)]); +} + +#define IPI_IRQ_OFFSET 6 + +void loongson3_send_irq_by_ipi(int cpu, int irqs) +{ + loongson3_ipi_write32(irqs << IPI_IRQ_OFFSET, ipi_set0_regs[cpu_logical_map(cpu)]); +} + +void loongson3_ipi_interrupt(struct pt_regs *regs) +{ + int i, cpu = smp_processor_id(); + unsigned int action, c0count, irqs; + + /* Load the ipi register to figure out what we're supposed to do */ + action = loongson3_ipi_read32(ipi_status0_regs[cpu_logical_map(cpu)]); + irqs = action >> IPI_IRQ_OFFSET; + + /* Clear the ipi register to clear the interrupt */ + loongson3_ipi_write32((u32)action, ipi_clear0_regs[cpu_logical_map(cpu)]); + + if (action & SMP_RESCHEDULE_YOURSELF) + scheduler_ipi(); + + if (action & SMP_CALL_FUNCTION) { + irq_enter(); + generic_smp_call_function_interrupt(); + irq_exit(); + } + + if (action & SMP_ASK_C0COUNT) { + BUG_ON(cpu != 0); + c0count = read_c0_count(); + c0count = c0count ? c0count : 1; + for (i = 1; i < nr_cpu_ids; i++) + core0_c0count[i] = c0count; + __wbflush(); /* Let others see the result ASAP */ + } + + if (irqs) { + int irq; + while ((irq = ffs(irqs))) { + do_IRQ(irq-1); + irqs &= ~(1<<(irq-1)); + } + } +} + +#define MAX_LOOPS 800 +/* + * SMP init and finish on secondary CPUs + */ +static void loongson3_init_secondary(void) +{ + int i; + uint32_t initcount; + unsigned int cpu = smp_processor_id(); + unsigned int imask = STATUSF_IP7 | STATUSF_IP6 | + STATUSF_IP3 | STATUSF_IP2; + + /* Set interrupt mask, but don't enable */ + change_c0_status(ST0_IM, imask); + + for (i = 0; i < num_possible_cpus(); i++) + loongson3_ipi_write32(0xffffffff, ipi_en0_regs[cpu_logical_map(i)]); + + per_cpu(cpu_state, cpu) = CPU_ONLINE; + cpu_set_core(&cpu_data[cpu], + cpu_logical_map(cpu) % loongson_sysconf.cores_per_package); + cpu_data[cpu].package = + cpu_logical_map(cpu) / loongson_sysconf.cores_per_package; + + i = 0; + core0_c0count[cpu] = 0; + loongson3_send_ipi_single(0, SMP_ASK_C0COUNT); + while (!core0_c0count[cpu]) { + i++; + cpu_relax(); + } + + if (i > MAX_LOOPS) + i = MAX_LOOPS; + if (cpu_data[cpu].package) + initcount = core0_c0count[cpu] + i; + else /* Local access is faster for loops */ + initcount = core0_c0count[cpu] + i/2; + + write_c0_count(initcount); +} + +static void loongson3_smp_finish(void) +{ + int cpu = smp_processor_id(); + + write_c0_compare(read_c0_count() + mips_hpt_frequency/HZ); + local_irq_enable(); + loongson3_ipi_write64(0, + (void *)(ipi_mailbox_buf[cpu_logical_map(cpu)]+0x0)); + pr_info("CPU#%d finished, CP0_ST=%x\n", + smp_processor_id(), read_c0_status()); +} + +static void __init loongson3_smp_setup(void) +{ + int i = 0, num = 0; /* i: physical id, num: logical id */ + + init_cpu_possible(cpu_none_mask); + + /* For unified kernel, NR_CPUS is the maximum possible value, + * loongson_sysconf.nr_cpus is the really present value */ + while (i < loongson_sysconf.nr_cpus) { + if (loongson_sysconf.reserved_cpus_mask & (1<<i)) { + /* Reserved physical CPU cores */ + __cpu_number_map[i] = -1; + } else { + __cpu_number_map[i] = num; + __cpu_logical_map[num] = i; + set_cpu_possible(num, true); + num++; + } + i++; + } + pr_info("Detected %i available CPU(s)\n", num); + + while (num < loongson_sysconf.nr_cpus) { + __cpu_logical_map[num] = -1; + num++; + } + + ipi_set0_regs_init(); + ipi_clear0_regs_init(); + ipi_status0_regs_init(); + ipi_en0_regs_init(); + ipi_mailbox_buf_init(); + cpu_set_core(&cpu_data[0], + cpu_logical_map(0) % loongson_sysconf.cores_per_package); + cpu_data[0].package = cpu_logical_map(0) / loongson_sysconf.cores_per_package; +} + +static void __init loongson3_prepare_cpus(unsigned int max_cpus) +{ + init_cpu_present(cpu_possible_mask); + per_cpu(cpu_state, smp_processor_id()) = CPU_ONLINE; +} + +/* + * Setup the PC, SP, and GP of a secondary processor and start it runing! + */ +static int loongson3_boot_secondary(int cpu, struct task_struct *idle) +{ + unsigned long startargs[4]; + + pr_info("Booting CPU#%d...\n", cpu); + + /* startargs[] are initial PC, SP and GP for secondary CPU */ + startargs[0] = (unsigned long)&smp_bootstrap; + startargs[1] = (unsigned long)__KSTK_TOS(idle); + startargs[2] = (unsigned long)task_thread_info(idle); + startargs[3] = 0; + + pr_debug("CPU#%d, func_pc=%lx, sp=%lx, gp=%lx\n", + cpu, startargs[0], startargs[1], startargs[2]); + + loongson3_ipi_write64(startargs[3], + (void *)(ipi_mailbox_buf[cpu_logical_map(cpu)]+0x18)); + loongson3_ipi_write64(startargs[2], + (void *)(ipi_mailbox_buf[cpu_logical_map(cpu)]+0x10)); + loongson3_ipi_write64(startargs[1], + (void *)(ipi_mailbox_buf[cpu_logical_map(cpu)]+0x8)); + loongson3_ipi_write64(startargs[0], + (void *)(ipi_mailbox_buf[cpu_logical_map(cpu)]+0x0)); + return 0; +} + +#ifdef CONFIG_HOTPLUG_CPU + +static int loongson3_cpu_disable(void) +{ + unsigned long flags; + unsigned int cpu = smp_processor_id(); + + if (cpu == 0) + return -EBUSY; + + set_cpu_online(cpu, false); + calculate_cpu_foreign_map(); + local_irq_save(flags); + fixup_irqs(); + local_irq_restore(flags); + local_flush_tlb_all(); + + return 0; +} + + +static void loongson3_cpu_die(unsigned int cpu) +{ + while (per_cpu(cpu_state, cpu) != CPU_DEAD) + cpu_relax(); + + mb(); +} + +/* To shutdown a core in Loongson 3, the target core should go to CKSEG1 and + * flush all L1 entries at first. Then, another core (usually Core 0) can + * safely disable the clock of the target core. loongson3_play_dead() is + * called via CKSEG1 (uncached and unmmaped) */ +static void loongson3a_r1_play_dead(int *state_addr) +{ + register int val; + register long cpuid, core, node, count; + register void *addr, *base, *initfunc; + + __asm__ __volatile__( + " .set push \n" + " .set noreorder \n" + " li %[addr], 0x80000000 \n" /* KSEG0 */ + "1: cache 0, 0(%[addr]) \n" /* flush L1 ICache */ + " cache 0, 1(%[addr]) \n" + " cache 0, 2(%[addr]) \n" + " cache 0, 3(%[addr]) \n" + " cache 1, 0(%[addr]) \n" /* flush L1 DCache */ + " cache 1, 1(%[addr]) \n" + " cache 1, 2(%[addr]) \n" + " cache 1, 3(%[addr]) \n" + " addiu %[sets], %[sets], -1 \n" + " bnez %[sets], 1b \n" + " addiu %[addr], %[addr], 0x20 \n" + " li %[val], 0x7 \n" /* *state_addr = CPU_DEAD; */ + " sw %[val], (%[state_addr]) \n" + " sync \n" + " cache 21, (%[state_addr]) \n" /* flush entry of *state_addr */ + " .set pop \n" + : [addr] "=&r" (addr), [val] "=&r" (val) + : [state_addr] "r" (state_addr), + [sets] "r" (cpu_data[smp_processor_id()].dcache.sets)); + + __asm__ __volatile__( + " .set push \n" + " .set noreorder \n" + " .set mips64 \n" + " mfc0 %[cpuid], $15, 1 \n" + " andi %[cpuid], 0x3ff \n" + " dli %[base], 0x900000003ff01000 \n" + " andi %[core], %[cpuid], 0x3 \n" + " sll %[core], 8 \n" /* get core id */ + " or %[base], %[base], %[core] \n" + " andi %[node], %[cpuid], 0xc \n" + " dsll %[node], 42 \n" /* get node id */ + " or %[base], %[base], %[node] \n" + "1: li %[count], 0x100 \n" /* wait for init loop */ + "2: bnez %[count], 2b \n" /* limit mailbox access */ + " addiu %[count], -1 \n" + " ld %[initfunc], 0x20(%[base]) \n" /* get PC via mailbox */ + " beqz %[initfunc], 1b \n" + " nop \n" + " ld $sp, 0x28(%[base]) \n" /* get SP via mailbox */ + " ld $gp, 0x30(%[base]) \n" /* get GP via mailbox */ + " ld $a1, 0x38(%[base]) \n" + " jr %[initfunc] \n" /* jump to initial PC */ + " nop \n" + " .set pop \n" + : [core] "=&r" (core), [node] "=&r" (node), + [base] "=&r" (base), [cpuid] "=&r" (cpuid), + [count] "=&r" (count), [initfunc] "=&r" (initfunc) + : /* No Input */ + : "a1"); +} + +static void loongson3a_r2r3_play_dead(int *state_addr) +{ + register int val; + register long cpuid, core, node, count; + register void *addr, *base, *initfunc; + + __asm__ __volatile__( + " .set push \n" + " .set noreorder \n" + " li %[addr], 0x80000000 \n" /* KSEG0 */ + "1: cache 0, 0(%[addr]) \n" /* flush L1 ICache */ + " cache 0, 1(%[addr]) \n" + " cache 0, 2(%[addr]) \n" + " cache 0, 3(%[addr]) \n" + " cache 1, 0(%[addr]) \n" /* flush L1 DCache */ + " cache 1, 1(%[addr]) \n" + " cache 1, 2(%[addr]) \n" + " cache 1, 3(%[addr]) \n" + " addiu %[sets], %[sets], -1 \n" + " bnez %[sets], 1b \n" + " addiu %[addr], %[addr], 0x40 \n" + " li %[addr], 0x80000000 \n" /* KSEG0 */ + "2: cache 2, 0(%[addr]) \n" /* flush L1 VCache */ + " cache 2, 1(%[addr]) \n" + " cache 2, 2(%[addr]) \n" + " cache 2, 3(%[addr]) \n" + " cache 2, 4(%[addr]) \n" + " cache 2, 5(%[addr]) \n" + " cache 2, 6(%[addr]) \n" + " cache 2, 7(%[addr]) \n" + " cache 2, 8(%[addr]) \n" + " cache 2, 9(%[addr]) \n" + " cache 2, 10(%[addr]) \n" + " cache 2, 11(%[addr]) \n" + " cache 2, 12(%[addr]) \n" + " cache 2, 13(%[addr]) \n" + " cache 2, 14(%[addr]) \n" + " cache 2, 15(%[addr]) \n" + " addiu %[vsets], %[vsets], -1 \n" + " bnez %[vsets], 2b \n" + " addiu %[addr], %[addr], 0x40 \n" + " li %[val], 0x7 \n" /* *state_addr = CPU_DEAD; */ + " sw %[val], (%[state_addr]) \n" + " sync \n" + " cache 21, (%[state_addr]) \n" /* flush entry of *state_addr */ + " .set pop \n" + : [addr] "=&r" (addr), [val] "=&r" (val) + : [state_addr] "r" (state_addr), + [sets] "r" (cpu_data[smp_processor_id()].dcache.sets), + [vsets] "r" (cpu_data[smp_processor_id()].vcache.sets)); + + __asm__ __volatile__( + " .set push \n" + " .set noreorder \n" + " .set mips64 \n" + " mfc0 %[cpuid], $15, 1 \n" + " andi %[cpuid], 0x3ff \n" + " dli %[base], 0x900000003ff01000 \n" + " andi %[core], %[cpuid], 0x3 \n" + " sll %[core], 8 \n" /* get core id */ + " or %[base], %[base], %[core] \n" + " andi %[node], %[cpuid], 0xc \n" + " dsll %[node], 42 \n" /* get node id */ + " or %[base], %[base], %[node] \n" + "1: li %[count], 0x100 \n" /* wait for init loop */ + "2: bnez %[count], 2b \n" /* limit mailbox access */ + " addiu %[count], -1 \n" + " ld %[initfunc], 0x20(%[base]) \n" /* get PC via mailbox */ + " beqz %[initfunc], 1b \n" + " nop \n" + " ld $sp, 0x28(%[base]) \n" /* get SP via mailbox */ + " ld $gp, 0x30(%[base]) \n" /* get GP via mailbox */ + " ld $a1, 0x38(%[base]) \n" + " jr %[initfunc] \n" /* jump to initial PC */ + " nop \n" + " .set pop \n" + : [core] "=&r" (core), [node] "=&r" (node), + [base] "=&r" (base), [cpuid] "=&r" (cpuid), + [count] "=&r" (count), [initfunc] "=&r" (initfunc) + : /* No Input */ + : "a1"); +} + +static void loongson3b_play_dead(int *state_addr) +{ + register int val; + register long cpuid, core, node, count; + register void *addr, *base, *initfunc; + + __asm__ __volatile__( + " .set push \n" + " .set noreorder \n" + " li %[addr], 0x80000000 \n" /* KSEG0 */ + "1: cache 0, 0(%[addr]) \n" /* flush L1 ICache */ + " cache 0, 1(%[addr]) \n" + " cache 0, 2(%[addr]) \n" + " cache 0, 3(%[addr]) \n" + " cache 1, 0(%[addr]) \n" /* flush L1 DCache */ + " cache 1, 1(%[addr]) \n" + " cache 1, 2(%[addr]) \n" + " cache 1, 3(%[addr]) \n" + " addiu %[sets], %[sets], -1 \n" + " bnez %[sets], 1b \n" + " addiu %[addr], %[addr], 0x20 \n" + " li %[val], 0x7 \n" /* *state_addr = CPU_DEAD; */ + " sw %[val], (%[state_addr]) \n" + " sync \n" + " cache 21, (%[state_addr]) \n" /* flush entry of *state_addr */ + " .set pop \n" + : [addr] "=&r" (addr), [val] "=&r" (val) + : [state_addr] "r" (state_addr), + [sets] "r" (cpu_data[smp_processor_id()].dcache.sets)); + + __asm__ __volatile__( + " .set push \n" + " .set noreorder \n" + " .set mips64 \n" + " mfc0 %[cpuid], $15, 1 \n" + " andi %[cpuid], 0x3ff \n" + " dli %[base], 0x900000003ff01000 \n" + " andi %[core], %[cpuid], 0x3 \n" + " sll %[core], 8 \n" /* get core id */ + " or %[base], %[base], %[core] \n" + " andi %[node], %[cpuid], 0xc \n" + " dsll %[node], 42 \n" /* get node id */ + " or %[base], %[base], %[node] \n" + " dsrl %[node], 30 \n" /* 15:14 */ + " or %[base], %[base], %[node] \n" + "1: li %[count], 0x100 \n" /* wait for init loop */ + "2: bnez %[count], 2b \n" /* limit mailbox access */ + " addiu %[count], -1 \n" + " ld %[initfunc], 0x20(%[base]) \n" /* get PC via mailbox */ + " beqz %[initfunc], 1b \n" + " nop \n" + " ld $sp, 0x28(%[base]) \n" /* get SP via mailbox */ + " ld $gp, 0x30(%[base]) \n" /* get GP via mailbox */ + " ld $a1, 0x38(%[base]) \n" + " jr %[initfunc] \n" /* jump to initial PC */ + " nop \n" + " .set pop \n" + : [core] "=&r" (core), [node] "=&r" (node), + [base] "=&r" (base), [cpuid] "=&r" (cpuid), + [count] "=&r" (count), [initfunc] "=&r" (initfunc) + : /* No Input */ + : "a1"); +} + +void play_dead(void) +{ + int *state_addr; + unsigned int cpu = smp_processor_id(); + void (*play_dead_at_ckseg1)(int *); + + idle_task_exit(); + switch (read_c0_prid() & PRID_REV_MASK) { + case PRID_REV_LOONGSON3A_R1: + default: + play_dead_at_ckseg1 = + (void *)CKSEG1ADDR((unsigned long)loongson3a_r1_play_dead); + break; + case PRID_REV_LOONGSON3A_R2: + case PRID_REV_LOONGSON3A_R3_0: + case PRID_REV_LOONGSON3A_R3_1: + play_dead_at_ckseg1 = + (void *)CKSEG1ADDR((unsigned long)loongson3a_r2r3_play_dead); + break; + case PRID_REV_LOONGSON3B_R1: + case PRID_REV_LOONGSON3B_R2: + play_dead_at_ckseg1 = + (void *)CKSEG1ADDR((unsigned long)loongson3b_play_dead); + break; + } + state_addr = &per_cpu(cpu_state, cpu); + mb(); + play_dead_at_ckseg1(state_addr); +} + +static int loongson3_disable_clock(unsigned int cpu) +{ + uint64_t core_id = cpu_core(&cpu_data[cpu]); + uint64_t package_id = cpu_data[cpu].package; + + if ((read_c0_prid() & PRID_REV_MASK) == PRID_REV_LOONGSON3A_R1) { + LOONGSON_CHIPCFG(package_id) &= ~(1 << (12 + core_id)); + } else { + if (!(loongson_sysconf.workarounds & WORKAROUND_CPUHOTPLUG)) + LOONGSON_FREQCTRL(package_id) &= ~(1 << (core_id * 4 + 3)); + } + return 0; +} + +static int loongson3_enable_clock(unsigned int cpu) +{ + uint64_t core_id = cpu_core(&cpu_data[cpu]); + uint64_t package_id = cpu_data[cpu].package; + + if ((read_c0_prid() & PRID_REV_MASK) == PRID_REV_LOONGSON3A_R1) { + LOONGSON_CHIPCFG(package_id) |= 1 << (12 + core_id); + } else { + if (!(loongson_sysconf.workarounds & WORKAROUND_CPUHOTPLUG)) + LOONGSON_FREQCTRL(package_id) |= 1 << (core_id * 4 + 3); + } + return 0; +} + +static int register_loongson3_notifier(void) +{ + return cpuhp_setup_state_nocalls(CPUHP_MIPS_SOC_PREPARE, + "mips/loongson:prepare", + loongson3_enable_clock, + loongson3_disable_clock); +} +early_initcall(register_loongson3_notifier); + +#endif + +const struct plat_smp_ops loongson3_smp_ops = { + .send_ipi_single = loongson3_send_ipi_single, + .send_ipi_mask = loongson3_send_ipi_mask, + .init_secondary = loongson3_init_secondary, + .smp_finish = loongson3_smp_finish, + .boot_secondary = loongson3_boot_secondary, + .smp_setup = loongson3_smp_setup, + .prepare_cpus = loongson3_prepare_cpus, +#ifdef CONFIG_HOTPLUG_CPU + .cpu_disable = loongson3_cpu_disable, + .cpu_die = loongson3_cpu_die, +#endif +}; diff --git a/arch/mips/loongson64/loongson-3/smp.h b/arch/mips/loongson64/loongson-3/smp.h new file mode 100644 index 000000000..957bde81e --- /dev/null +++ b/arch/mips/loongson64/loongson-3/smp.h @@ -0,0 +1,31 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __LOONGSON_SMP_H_ +#define __LOONGSON_SMP_H_ + +/* for Loongson-3 smp support */ +extern unsigned long long smp_group[4]; + +/* 4 groups(nodes) in maximum in numa case */ +#define SMP_CORE_GROUP0_BASE (smp_group[0]) +#define SMP_CORE_GROUP1_BASE (smp_group[1]) +#define SMP_CORE_GROUP2_BASE (smp_group[2]) +#define SMP_CORE_GROUP3_BASE (smp_group[3]) + +/* 4 cores in each group(node) */ +#define SMP_CORE0_OFFSET 0x000 +#define SMP_CORE1_OFFSET 0x100 +#define SMP_CORE2_OFFSET 0x200 +#define SMP_CORE3_OFFSET 0x300 + +/* ipi registers offsets */ +#define STATUS0 0x00 +#define EN0 0x04 +#define SET0 0x08 +#define CLEAR0 0x0c +#define STATUS1 0x10 +#define MASK1 0x14 +#define SET1 0x18 +#define CLEAR1 0x1c +#define BUF 0x20 + +#endif |