| /* |
| * Copyright (C) 2012 Broadcom Corporation |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License version 2 as |
| * published by the Free Software Foundation. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, write to the Free Software |
| * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. |
| */ |
| |
| #include <linux/init.h> |
| #include <linux/types.h> |
| #include <linux/dma-mapping.h> |
| #include <linux/mm.h> |
| #include <linux/sched.h> |
| #include <linux/spinlock.h> |
| #include <linux/bitops.h> |
| #include <linux/module.h> |
| #include <linux/version.h> |
| #include <linux/compiler.h> |
| |
| #include <asm/cpu-info.h> |
| #include <asm/mipsregs.h> |
| #include <asm/barrier.h> |
| #include <asm/cacheflush.h> |
| #include <asm/r4kcache.h> |
| #include <asm/asm-offsets.h> |
| #include <asm/inst.h> |
| #include <asm/fpu.h> |
| #include <asm/hazards.h> |
| #include <asm/cpu-features.h> |
| #include <linux/brcmstb/brcmstb.h> |
| #include <dma-coherence.h> |
| |
| /*********************************************************************** |
| * MIPS features, caches, and bus interface |
| ***********************************************************************/ |
| |
| void brcmstb_cpu_setup(void) |
| { |
| #if defined(CONFIG_CPU_BMIPS3300) |
| |
| unsigned long cbr = __BMIPS_GET_CBR(); |
| |
| /* Set BIU to async mode */ |
| set_c0_brcm_bus_pll(BIT(22)); |
| __sync(); |
| |
| #ifdef BCHP_MISB_BRIDGE_WG_MODE_N_TIMEOUT |
| /* Enable write gathering */ |
| BDEV_WR_RB(BCHP_MISB_BRIDGE_WG_MODE_N_TIMEOUT, 0x264); |
| |
| /* Enable split mode */ |
| BDEV_WR_RB(BCHP_MISB_BRIDGE_MISB_SPLIT_MODE, 0x1); |
| __sync(); |
| #endif |
| |
| /* put the BIU back in sync mode */ |
| clear_c0_brcm_bus_pll(BIT(22)); |
| |
| /* clear BHTD to enable branch history table */ |
| clear_c0_brcm_reset(BIT(16)); |
| |
| /* Flush and enable RAC */ |
| DEV_WR_RB(cbr + BMIPS_RAC_CONFIG, 0x100); |
| DEV_WR_RB(cbr + BMIPS_RAC_CONFIG, 0xf); |
| DEV_WR_RB(cbr + BMIPS_RAC_ADDRESS_RANGE, 0x0fff0000); |
| |
| #elif defined(CONFIG_CPU_BMIPS4380) |
| |
| unsigned long cbr = __BMIPS_GET_CBR(); |
| |
| /* CRBMIPS438X-164: CBG workaround */ |
| switch (read_c0_prid()) { |
| case 0x2a040: |
| case 0x2a042: |
| case 0x2a044: |
| case 0x2a060: |
| DEV_UNSET(cbr + BMIPS_L2_CONFIG, 0x07000000); |
| } |
| |
| /* clear BHTD to enable branch history table */ |
| clear_c0_brcm_config_0(BIT(21)); |
| |
| /* XI/ROTR enable */ |
| if (kernel_uses_smartmips_rixi) { |
| set_c0_brcm_config_0(BIT(23)); |
| set_c0_brcm_cmt_ctrl(BIT(15)); |
| } |
| |
| #elif defined(CONFIG_CPU_BMIPS5000) |
| |
| /* enable RDHWR, BRDHWR */ |
| set_c0_brcm_config(BIT(17) | BIT(21)); |
| |
| if (kernel_uses_smartmips_rixi) { |
| /* XI enable */ |
| set_c0_brcm_config(BIT(27)); |
| |
| /* enable MIPS32R2 ROR instruction for XI TLB handlers */ |
| __asm__ __volatile__( |
| " li $8, 0x5a455048\n" |
| " .word 0x4088b00f\n" /* mtc0 $8, $22, 15 */ |
| " nop; nop; nop\n" |
| " .word 0x4008b008\n" /* mfc0 $8, $22, 8 */ |
| " lui $9, 0x0100\n" |
| " or $8, $9\n" |
| " .word 0x4088b008\n" /* mtc0 $8, $22, 8 */ |
| " sync\n" |
| " li $8, 0x0\n" |
| " .word 0x4088b00f\n" |
| " nop; nop; nop\n" |
| : : : "$8", "$9"); |
| } |
| |
| #if defined(CONFIG_BCM7425) |
| /* Disable PREF 30 */ |
| __asm__ __volatile__( |
| " li $8, 0x5a455048\n" |
| " .word 0x4088b00f\n" |
| " nop; nop; nop\n" |
| " .word 0x4008b008\n" |
| " lui $9, 0x0800\n" |
| " or $8, $8, $9\n" |
| " .word 0x4088b008\n" |
| " sync\n" |
| " li $8, 0x0\n" |
| " .word 0x4088b00f\n" |
| " nop; nop; nop\n" |
| : : : "$8", "$9"); |
| #endif |
| |
| #if defined(CONFIG_BCM7425) || defined(CONFIG_BCM7429) |
| /* Disable JTB and CRS */ |
| __asm__ __volatile__( |
| " li $8, 0x5a455048\n" |
| " .word 0x4088b00f\n" |
| " nop; nop; nop\n" |
| " .word 0x4008b008\n" |
| " li $9, 0xfbffffff\n" |
| " and $8, $8, $9\n" |
| " li $9, 0x0400c000\n" |
| " or $8, $8, $9\n" |
| " .word 0x4088b008\n" |
| " sync\n" |
| " li $8, 0x0\n" |
| " .word 0x4088b00f\n" |
| " nop; nop; nop\n" |
| : : : "$8", "$9"); |
| #endif |
| #endif |
| } |
| |
| /*********************************************************************** |
| * Simulate privileged instructions (RDHWR, MFC0) and unaligned accesses |
| ***********************************************************************/ |
| |
| #define OPCODE 0xfc000000 |
| #define BASE 0x03e00000 |
| #define RT 0x001f0000 |
| #define OFFSET 0x0000ffff |
| #define LL 0xc0000000 |
| #define SC 0xe0000000 |
| #define SPEC0 0x00000000 |
| #define SPEC3 0x7c000000 |
| #define RD 0x0000f800 |
| #define FUNC 0x0000003f |
| #define SYNC 0x0000000f |
| #define RDHWR 0x0000003b |
| |
| #define BRDHWR 0xec000000 |
| #define OP_MFC0 0x40000000 |
| |
| int brcm_simulate_opcode(struct pt_regs *regs, unsigned int opcode) |
| { |
| struct thread_info *ti = task_thread_info(current); |
| int rd = (opcode & RD) >> 11; |
| int rt = (opcode & RT) >> 16; |
| |
| /* PR34054: use alternate RDHWR instruction encoding */ |
| if (((opcode & OPCODE) == BRDHWR && (opcode & FUNC) == RDHWR) |
| || ((opcode & OPCODE) == SPEC3 && (opcode & FUNC) == RDHWR)) { |
| |
| if (rd == 29) { |
| regs->regs[rt] = ti->tp_value; |
| atomic_inc(&brcm_rdhwr_count); |
| return 0; |
| } |
| } |
| |
| /* emulate MFC0 $15 for optimized memcpy() CPU detection */ |
| if ((opcode & OPCODE) == OP_MFC0 && |
| (opcode & OFFSET) == (15 << 11)) { |
| regs->regs[rt] = read_c0_prid(); |
| return 0; |
| } |
| |
| return -1; /* unhandled */ |
| } |
| |
| int brcm_unaligned_fp(void __user *addr, union mips_instruction *insn, |
| struct pt_regs *regs) |
| { |
| unsigned int op = insn->i_format.opcode; |
| unsigned int rt = insn->i_format.rt; |
| unsigned int res; |
| int wordlen = 8; |
| |
| /* on r4k, only the even slots ($f0, $f2, ...) are used */ |
| u8 *fprptr = (u8 *)current + THREAD_FPR0 + (rt >> 1) * |
| (THREAD_FPR2 - THREAD_FPR0); |
| |
| if (op == lwc1_op || op == swc1_op) { |
| wordlen = 4; |
| #ifdef __LITTLE_ENDIAN |
| /* LE: LSW ($f0) precedes MSW ($f1) */ |
| fprptr += (rt & 1) ? 4 : 0; |
| #else |
| /* BE: MSW ($f1) precedes LSW ($f0) */ |
| fprptr += (rt & 1) ? 0 : 4; |
| #endif |
| } |
| |
| preempt_disable(); |
| if (is_fpu_owner()) |
| save_fp(current); |
| else |
| own_fpu(1); |
| |
| if (op == lwc1_op || op == ldc1_op) { |
| if (!access_ok(VERIFY_READ, addr, wordlen)) |
| goto sigbus; |
| /* |
| * FPR load: copy from user struct to kernel saved |
| * register struct, then restore all FPRs |
| */ |
| __asm__ __volatile__ ( |
| "1: lb %0, 0(%3)\n" |
| " sb %0, 0(%2)\n" |
| " addiu %2, 1\n" |
| " addiu %3, 1\n" |
| " addiu %1, -1\n" |
| " bnez %1, 1b\n" |
| " li %0, 0\n" |
| "3:\n" |
| " .section .fixup,\"ax\"\n" |
| "4: li %0, %4\n" |
| " j 3b\n" |
| " .previous\n" |
| " .section __ex_table,\"a\"\n" |
| STR(PTR)" 1b,4b\n" |
| " .previous\n" |
| : "=&r" (res), "+r" (wordlen), |
| "+r" (fprptr), "+r" (addr) |
| : "i" (-EFAULT)); |
| if (res) |
| goto fault; |
| |
| restore_fp(current); |
| } else { |
| if (!access_ok(VERIFY_WRITE, addr, wordlen)) |
| goto sigbus; |
| /* |
| * FPR store: copy from kernel saved register struct |
| * to user struct |
| */ |
| __asm__ __volatile__ ( |
| "2: lb %0, 0(%2)\n" |
| "1: sb %0, 0(%3)\n" |
| " addiu %2, 1\n" |
| " addiu %3, 1\n" |
| " addiu %1, -1\n" |
| " bnez %1, 2b\n" |
| " li %0, 0\n" |
| "3:\n" |
| " .section .fixup,\"ax\"\n" |
| "4: li %0, %4\n" |
| " j 3b\n" |
| " .previous\n" |
| " .section __ex_table,\"a\"\n" |
| STR(PTR)" 1b,4b\n" |
| " .previous\n" |
| : "=&r" (res), "+r" (wordlen), |
| "+r" (fprptr), "+r" (addr) |
| : "i" (-EFAULT)); |
| if (res) |
| goto fault; |
| } |
| preempt_enable(); |
| |
| atomic_inc(&brcm_unaligned_fp_count); |
| return 0; |
| |
| sigbus: |
| preempt_enable(); |
| return -EINVAL; |
| |
| fault: |
| preempt_enable(); |
| return -EFAULT; |
| } |
| |
| /*********************************************************************** |
| * CPU divisor / PLL manipulation |
| ***********************************************************************/ |
| /* |
| * 0: CP0 COUNT/COMPARE frequency depends on divisor |
| * 1: CP0 COUNT/COMPARE frequency does not depend on divisor |
| */ |
| static int fixed_counter_freq; |
| |
| #if defined(CONFIG_BCM7425B0) || defined(CONFIG_BCM7344B0) || \ |
| defined(CONFIG_BCM7346B0) |
| /* SWLINUX-2063: MIPS cannot enter divide-by-N mode */ |
| #define BROKEN_MIPS_DIVIDER |
| #endif |
| |
| /* MIPS active standby on 7550 */ |
| #define CPU_PLL_MODE1 216000 |
| |
| /* |
| * current ADJUSTED base frequency (reflects the current PLL settings) |
| * brcm_cpu_khz (in time.c) always has the ORIGINAL clock frequency and |
| * is never changed after bootup |
| */ |
| unsigned long brcm_adj_cpu_khz; |
| |
| /* multiplier used in brcm_fixup_ticks to scale the # of ticks |
| * 0 - no fixup needed |
| * any other value - factor * 2^16 */ |
| static unsigned long fixup_ticks_ratio; |
| |
| /* current CPU divisor, as set by the user */ |
| static __maybe_unused int cpu_div = 1; |
| |
| /* |
| * MIPS clockevent code always assumes the original boot-time CP0 clock rate. |
| * This function scales the number of ticks according to the current HW |
| * settings. |
| */ |
| unsigned long brcm_fixup_ticks(unsigned long delta) |
| { |
| unsigned long long tmp = delta; |
| |
| if (unlikely(!brcm_adj_cpu_khz)) |
| brcm_adj_cpu_khz = brcm_cpu_khz; |
| |
| if (likely(!fixup_ticks_ratio)) |
| return delta; |
| |
| tmp *= fixup_ticks_ratio; |
| tmp >>= 16; |
| |
| return (unsigned long)tmp; |
| } |
| |
| static unsigned int orig_udelay_val[NR_CPUS]; |
| |
| struct spd_change { |
| int old_div; |
| int new_div; |
| int old_base; |
| int new_base; |
| }; |
| |
| void brcm_set_cpu_speed(void *arg) |
| { |
| struct spd_change *c = arg; |
| uint32_t new_div = (uint32_t)c->new_div; |
| unsigned long __maybe_unused count, compare, delta; |
| signed long sdelta; |
| int cpu = smp_processor_id(); |
| uint32_t __maybe_unused tmp0, tmp1, tmp2, tmp3; |
| |
| /* scale udelay_val */ |
| if (!orig_udelay_val[cpu]) |
| orig_udelay_val[cpu] = current_cpu_data.udelay_val; |
| |
| if (c->new_base == brcm_cpu_khz) |
| current_cpu_data.udelay_val = orig_udelay_val[cpu] / new_div; |
| else |
| current_cpu_data.udelay_val = |
| (unsigned long long)orig_udelay_val[cpu] * |
| c->new_base / (new_div * c->old_base); |
| |
| /* scale any pending timer events */ |
| compare = read_c0_compare(); |
| count = read_c0_count(); |
| |
| sdelta = (long)compare - (long)count; |
| if (sdelta > 0) { |
| if (!fixed_counter_freq) |
| delta = ((unsigned long long)sdelta * |
| c->old_div * c->new_base) / |
| (new_div * c->old_base); |
| else |
| delta = ((unsigned long long)sdelta * |
| c->new_base) / c->old_base; |
| write_c0_compare(read_c0_count() + delta); |
| } |
| |
| if (cpu != 0) |
| return; |
| |
| #if defined(CONFIG_BRCM_CPU_PLL) |
| brcm_adj_cpu_khz = c->new_base; |
| #if defined(CONFIG_BCM7550) |
| if (brcm_adj_cpu_khz == CPU_PLL_MODE1) { |
| /* 216Mhz */ |
| BDEV_WR_RB(BCHP_VCXO_CTL_CONFIG_FSM_PLL_NEXT_CFG_3A, |
| 0x801b2806); |
| BDEV_WR_RB(BCHP_VCXO_CTL_CONFIG_FSM_PLL_NEXT_CFG_3B, |
| 0x00300618); |
| } else { |
| /* 324Mhz */ |
| BDEV_WR_RB(BCHP_VCXO_CTL_CONFIG_FSM_PLL_NEXT_CFG_3A, |
| 0x801b2806); |
| BDEV_WR_RB(BCHP_VCXO_CTL_CONFIG_FSM_PLL_NEXT_CFG_3B, |
| 0x00300418); |
| } |
| BDEV_WR_RB(BCHP_VCXO_CTL_CONFIG_FSM_PLL_UPDATE, 1); |
| #else |
| #error CPU PLL adjustment not supported on this chip |
| #endif |
| #endif |
| |
| if ((brcm_adj_cpu_khz == brcm_cpu_khz) && |
| (fixed_counter_freq || new_div == 1)) { |
| fixup_ticks_ratio = 0; |
| } else { |
| fixup_ticks_ratio = |
| ((unsigned long long)brcm_adj_cpu_khz << 16) / |
| (unsigned long long)brcm_cpu_khz; |
| if (!fixed_counter_freq) |
| fixup_ticks_ratio /= new_div; |
| } |
| |
| printk(KERN_DEBUG "ratio=%lu adj=%lu freq=%lu new_div=%d\n", |
| fixup_ticks_ratio, brcm_adj_cpu_khz, brcm_cpu_khz, new_div); |
| new_div = ffs(new_div) - 1; |
| |
| /* see BMIPS datasheet, CP0 register $22 */ |
| |
| #if defined(CONFIG_CPU_BMIPS3300) |
| change_c0_brcm_bus_pll(0x07 << 22, (new_div << 23) | (0 << 22)); |
| #elif defined(CONFIG_CPU_BMIPS5000) |
| change_c0_brcm_mode(0x0f << 4, (1 << 7) | (new_div << 4)); |
| #elif defined(CONFIG_CPU_BMIPS4380) |
| __asm__ __volatile__( |
| " .set push\n" |
| " .set noreorder\n" |
| " .set nomacro\n" |
| " .set mips32\n" |
| /* get kseg1 address for CBA into %3 */ |
| " mfc0 %3, $22, 6\n" |
| " li %2, 0xfffc0000\n" |
| " and %3, %2\n" |
| " li %2, 0xa0000000\n" |
| " add %3, %2\n" |
| /* %1 = async bit, %2 = mask out everything but 30:28 */ |
| " lui %1, 0x1000\n" |
| " lui %2, 0x8fff\n" |
| " beqz %0, 1f\n" |
| " ori %2, 0xffff\n" |
| /* handle SYNC to ASYNC */ |
| " sync\n" |
| " mfc0 %4, $22, 5\n" |
| " and %4, %2\n" |
| " or %4, %1\n" |
| " mtc0 %4, $22, 5\n" |
| " nop\n" |
| " nop\n" |
| " lw %2, 4(%3)\n" |
| " sw %2, 4(%3)\n" |
| " sync\n" |
| " sll %0, 29\n" |
| " or %4, %0\n" |
| " mtc0 %4, $22, 5\n" |
| " nop; nop; nop; nop\n" |
| " nop; nop; nop; nop\n" |
| " nop; nop; nop; nop\n" |
| " nop; nop; nop; nop\n" |
| " b 2f\n" |
| " nop\n" |
| /* handle ASYNC to SYNC */ |
| "1:\n" |
| " mfc0 %4, $22, 5\n" |
| " and %4, %2\n" |
| " or %4, %1\n" |
| " mtc0 %4, $22, 5\n" |
| " nop; nop; nop; nop\n" |
| " nop; nop; nop; nop\n" |
| " sync\n" |
| " and %4, %2\n" |
| " mtc0 %4, $22, 5\n" |
| " nop\n" |
| " nop\n" |
| " lw %2, 4(%3)\n" |
| " sw %2, 4(%3)\n" |
| " sync\n" |
| "2:\n" |
| " .set pop\n" |
| : "+r" (new_div), |
| "=&r" (tmp0), "=&r" (tmp1), "=&r" (tmp2), "=&r" (tmp3)); |
| #endif |
| } |
| |
| #ifdef CONFIG_BRCM_CPU_DIV |
| |
| ssize_t brcm_pm_show_cpu_div(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| return snprintf(buf, PAGE_SIZE, "%d\n", cpu_div); |
| } |
| |
| ssize_t brcm_pm_store_cpu_div(struct device *dev, |
| struct device_attribute *attr, const char *buf, size_t count) |
| { |
| int val; |
| struct spd_change chg; |
| |
| if (sscanf(buf, "%d", &val) != 1) |
| return -EINVAL; |
| |
| if (val != 1 && val != 2 && val != 4 && val != 8 |
| #if defined(CONFIG_CPU_BMIPS5000) |
| && val != 16 |
| #endif |
| ) |
| return -EINVAL; |
| |
| #if defined(BROKEN_MIPS_DIVIDER) |
| return val == 1 ? count : -EINVAL; |
| #endif |
| |
| chg.old_div = cpu_div; |
| chg.new_div = val; |
| chg.old_base = brcm_adj_cpu_khz; |
| chg.new_base = brcm_adj_cpu_khz; |
| |
| on_each_cpu(brcm_set_cpu_speed, &chg, 1); |
| cpu_div = val; |
| return count; |
| } |
| |
| #endif /* CONFIG_BRCM_CPU_DIV */ |
| |
| #ifdef CONFIG_BRCM_CPU_PLL |
| |
| static int cpu_pll_mode; |
| |
| ssize_t brcm_pm_show_cpu_pll(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| return snprintf(buf, PAGE_SIZE, "%d\n", cpu_pll_mode); |
| } |
| |
| ssize_t brcm_pm_store_cpu_pll(struct device *dev, |
| struct device_attribute *attr, const char *buf, size_t count) |
| { |
| int val; |
| struct spd_change chg; |
| |
| if (sscanf(buf, "%d", &val) != 1) |
| return -EINVAL; |
| |
| if (cpu_pll_mode == val) |
| return count; |
| |
| switch (val) { |
| case 0: |
| chg.new_base = brcm_cpu_khz; |
| break; |
| case 1: |
| chg.new_base = CPU_PLL_MODE1; |
| break; |
| default: |
| return -EINVAL; |
| } |
| |
| chg.old_div = cpu_div; |
| chg.new_div = cpu_div; |
| chg.old_base = brcm_adj_cpu_khz; |
| on_each_cpu(brcm_set_cpu_speed, &chg, 1); |
| |
| cpu_pll_mode = val; |
| return count; |
| } |
| |
| #endif /* CONFIG_BRCM_CPU_PLL */ |
| |
| static int bmips_check_caps(void) |
| { |
| unsigned long __maybe_unused config; |
| #ifdef CONFIG_CPU_BMIPS5000 |
| fixed_counter_freq = 1; |
| #elif defined(CONFIG_CPU_BMIPS4380) |
| config = read_c0_brcm_config(); |
| fixed_counter_freq = !!(config & 0x40); |
| #else |
| fixed_counter_freq = 0; |
| #endif |
| printk(KERN_INFO "PM: CP0 COUNT/COMPARE frequency %s on divisor\n", |
| fixed_counter_freq ? "does not depend" : "depends"); |
| return 0; |
| } |
| late_initcall(bmips_check_caps); |