| // SPDX-License-Identifier: GPL-2.0 |
| |
| #include <linux/smp.h> |
| #include <linux/types.h> |
| #include <asm/cpu.h> |
| #include <asm/cpu-info.h> |
| #include <asm/elf.h> |
| |
| #include <loongson_regs.h> |
| #include <cpucfg-emul.h> |
| |
| static bool is_loongson(struct cpuinfo_mips *c) |
| { |
| switch (c->processor_id & PRID_COMP_MASK) { |
| case PRID_COMP_LEGACY: |
| return ((c->processor_id & PRID_IMP_MASK) == |
| PRID_IMP_LOONGSON_64C); |
| |
| case PRID_COMP_LOONGSON: |
| return true; |
| |
| default: |
| return false; |
| } |
| } |
| |
| static u32 get_loongson_fprev(struct cpuinfo_mips *c) |
| { |
| return c->fpu_id & LOONGSON_FPREV_MASK; |
| } |
| |
| static bool cpu_has_uca(void) |
| { |
| u32 diag = read_c0_diag(); |
| u32 new_diag; |
| |
| if (diag & LOONGSON_DIAG_UCAC) |
| /* UCA is already enabled. */ |
| return true; |
| |
| /* See if UCAC bit can be flipped on. This should be safe. */ |
| new_diag = diag | LOONGSON_DIAG_UCAC; |
| write_c0_diag(new_diag); |
| new_diag = read_c0_diag(); |
| write_c0_diag(diag); |
| |
| return (new_diag & LOONGSON_DIAG_UCAC) != 0; |
| } |
| |
| static void probe_uca(struct cpuinfo_mips *c) |
| { |
| if (cpu_has_uca()) |
| c->loongson3_cpucfg_data[0] |= LOONGSON_CFG1_LSUCA; |
| } |
| |
| static void decode_loongson_config6(struct cpuinfo_mips *c) |
| { |
| u32 config6 = read_c0_config6(); |
| |
| if (config6 & LOONGSON_CONF6_SFBEN) |
| c->loongson3_cpucfg_data[0] |= LOONGSON_CFG1_SFBP; |
| if (config6 & LOONGSON_CONF6_LLEXC) |
| c->loongson3_cpucfg_data[0] |= LOONGSON_CFG1_LLEXC; |
| if (config6 & LOONGSON_CONF6_SCRAND) |
| c->loongson3_cpucfg_data[0] |= LOONGSON_CFG1_SCRAND; |
| } |
| |
| static void patch_cpucfg_sel1(struct cpuinfo_mips *c) |
| { |
| u64 ases = c->ases; |
| u64 options = c->options; |
| u32 data = c->loongson3_cpucfg_data[0]; |
| |
| if (options & MIPS_CPU_FPU) { |
| data |= LOONGSON_CFG1_FP; |
| data |= get_loongson_fprev(c) << LOONGSON_CFG1_FPREV_OFFSET; |
| } |
| if (ases & MIPS_ASE_LOONGSON_MMI) |
| data |= LOONGSON_CFG1_MMI; |
| if (ases & MIPS_ASE_MSA) |
| data |= LOONGSON_CFG1_MSA1; |
| |
| c->loongson3_cpucfg_data[0] = data; |
| } |
| |
| static void patch_cpucfg_sel2(struct cpuinfo_mips *c) |
| { |
| u64 ases = c->ases; |
| u64 options = c->options; |
| u32 data = c->loongson3_cpucfg_data[1]; |
| |
| if (ases & MIPS_ASE_LOONGSON_EXT) |
| data |= LOONGSON_CFG2_LEXT1; |
| if (ases & MIPS_ASE_LOONGSON_EXT2) |
| data |= LOONGSON_CFG2_LEXT2; |
| if (options & MIPS_CPU_LDPTE) |
| data |= LOONGSON_CFG2_LSPW; |
| |
| if (ases & MIPS_ASE_VZ) |
| data |= LOONGSON_CFG2_LVZP; |
| else |
| data &= ~LOONGSON_CFG2_LVZREV; |
| |
| c->loongson3_cpucfg_data[1] = data; |
| } |
| |
| static void patch_cpucfg_sel3(struct cpuinfo_mips *c) |
| { |
| u64 ases = c->ases; |
| u32 data = c->loongson3_cpucfg_data[2]; |
| |
| if (ases & MIPS_ASE_LOONGSON_CAM) { |
| data |= LOONGSON_CFG3_LCAMP; |
| } else { |
| data &= ~LOONGSON_CFG3_LCAMREV; |
| data &= ~LOONGSON_CFG3_LCAMNUM; |
| data &= ~LOONGSON_CFG3_LCAMKW; |
| data &= ~LOONGSON_CFG3_LCAMVW; |
| } |
| |
| c->loongson3_cpucfg_data[2] = data; |
| } |
| |
| void loongson3_cpucfg_synthesize_data(struct cpuinfo_mips *c) |
| { |
| /* Only engage the logic on Loongson processors. */ |
| if (!is_loongson(c)) |
| return; |
| |
| /* CPUs with CPUCFG support don't need to synthesize anything. */ |
| if (cpu_has_cfg()) |
| goto have_cpucfg_now; |
| |
| c->loongson3_cpucfg_data[0] = 0; |
| c->loongson3_cpucfg_data[1] = 0; |
| c->loongson3_cpucfg_data[2] = 0; |
| |
| /* Add CPUCFG features non-discoverable otherwise. */ |
| switch (c->processor_id & (PRID_IMP_MASK | PRID_REV_MASK)) { |
| case PRID_IMP_LOONGSON_64R | PRID_REV_LOONGSON2K_R1_0: |
| case PRID_IMP_LOONGSON_64R | PRID_REV_LOONGSON2K_R1_1: |
| case PRID_IMP_LOONGSON_64R | PRID_REV_LOONGSON2K_R1_2: |
| case PRID_IMP_LOONGSON_64R | PRID_REV_LOONGSON2K_R1_3: |
| decode_loongson_config6(c); |
| probe_uca(c); |
| |
| c->loongson3_cpucfg_data[0] |= (LOONGSON_CFG1_LSLDR0 | |
| LOONGSON_CFG1_LSSYNCI | LOONGSON_CFG1_LLSYNC | |
| LOONGSON_CFG1_TGTSYNC); |
| c->loongson3_cpucfg_data[1] |= (LOONGSON_CFG2_LBT1 | |
| LOONGSON_CFG2_LBT2 | LOONGSON_CFG2_LPMP | |
| LOONGSON_CFG2_LPM_REV2); |
| c->loongson3_cpucfg_data[2] = 0; |
| break; |
| |
| case PRID_IMP_LOONGSON_64C | PRID_REV_LOONGSON3A_R1: |
| c->loongson3_cpucfg_data[0] |= (LOONGSON_CFG1_LSLDR0 | |
| LOONGSON_CFG1_LSSYNCI | LOONGSON_CFG1_LSUCA | |
| LOONGSON_CFG1_LLSYNC | LOONGSON_CFG1_TGTSYNC); |
| c->loongson3_cpucfg_data[1] |= (LOONGSON_CFG2_LBT1 | |
| LOONGSON_CFG2_LPMP | LOONGSON_CFG2_LPM_REV1); |
| c->loongson3_cpucfg_data[2] |= ( |
| LOONGSON_CFG3_LCAM_REV1 | |
| LOONGSON_CFG3_LCAMNUM_REV1 | |
| LOONGSON_CFG3_LCAMKW_REV1 | |
| LOONGSON_CFG3_LCAMVW_REV1); |
| break; |
| |
| case PRID_IMP_LOONGSON_64C | PRID_REV_LOONGSON3B_R1: |
| case PRID_IMP_LOONGSON_64C | PRID_REV_LOONGSON3B_R2: |
| c->loongson3_cpucfg_data[0] |= (LOONGSON_CFG1_LSLDR0 | |
| LOONGSON_CFG1_LSSYNCI | LOONGSON_CFG1_LSUCA | |
| LOONGSON_CFG1_LLSYNC | LOONGSON_CFG1_TGTSYNC); |
| c->loongson3_cpucfg_data[1] |= (LOONGSON_CFG2_LBT1 | |
| LOONGSON_CFG2_LPMP | LOONGSON_CFG2_LPM_REV1); |
| c->loongson3_cpucfg_data[2] |= ( |
| LOONGSON_CFG3_LCAM_REV1 | |
| LOONGSON_CFG3_LCAMNUM_REV1 | |
| LOONGSON_CFG3_LCAMKW_REV1 | |
| LOONGSON_CFG3_LCAMVW_REV1); |
| break; |
| |
| case PRID_IMP_LOONGSON_64C | PRID_REV_LOONGSON3A_R2_0: |
| case PRID_IMP_LOONGSON_64C | PRID_REV_LOONGSON3A_R2_1: |
| case PRID_IMP_LOONGSON_64C | PRID_REV_LOONGSON3A_R3_0: |
| case PRID_IMP_LOONGSON_64C | PRID_REV_LOONGSON3A_R3_1: |
| decode_loongson_config6(c); |
| probe_uca(c); |
| |
| c->loongson3_cpucfg_data[0] |= (LOONGSON_CFG1_CNT64 | |
| LOONGSON_CFG1_LSLDR0 | LOONGSON_CFG1_LSPREF | |
| LOONGSON_CFG1_LSPREFX | LOONGSON_CFG1_LSSYNCI | |
| LOONGSON_CFG1_LLSYNC | LOONGSON_CFG1_TGTSYNC); |
| c->loongson3_cpucfg_data[1] |= (LOONGSON_CFG2_LBT1 | |
| LOONGSON_CFG2_LBT2 | LOONGSON_CFG2_LBTMMU | |
| LOONGSON_CFG2_LPMP | LOONGSON_CFG2_LPM_REV1 | |
| LOONGSON_CFG2_LVZ_REV1); |
| c->loongson3_cpucfg_data[2] |= (LOONGSON_CFG3_LCAM_REV1 | |
| LOONGSON_CFG3_LCAMNUM_REV1 | |
| LOONGSON_CFG3_LCAMKW_REV1 | |
| LOONGSON_CFG3_LCAMVW_REV1); |
| break; |
| |
| default: |
| /* It is possible that some future Loongson cores still do |
| * not have CPUCFG, so do not emulate anything for these |
| * cores. |
| */ |
| return; |
| } |
| |
| /* This feature is set by firmware, but all known Loongson-64 systems |
| * are configured this way. |
| */ |
| c->loongson3_cpucfg_data[0] |= LOONGSON_CFG1_CDMAP; |
| |
| /* Patch in dynamically probed bits. */ |
| patch_cpucfg_sel1(c); |
| patch_cpucfg_sel2(c); |
| patch_cpucfg_sel3(c); |
| |
| have_cpucfg_now: |
| /* We have usable CPUCFG now, emulated or not. |
| * Announce CPUCFG availability to userspace via hwcap. |
| */ |
| elf_hwcap |= HWCAP_LOONGSON_CPUCFG; |
| } |