1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * linux/arch/arm/kernel/pj4-cp0.c |
4 | * |
5 | * PJ4 iWMMXt coprocessor context switching and handling |
6 | * |
7 | * Copyright (c) 2010 Marvell International Inc. |
8 | */ |
9 | |
10 | #include <linux/types.h> |
11 | #include <linux/kernel.h> |
12 | #include <linux/signal.h> |
13 | #include <linux/sched.h> |
14 | #include <linux/init.h> |
15 | #include <linux/io.h> |
16 | #include <asm/thread_notify.h> |
17 | #include <asm/cputype.h> |
18 | |
19 | static int iwmmxt_do(struct notifier_block *self, unsigned long cmd, void *t) |
20 | { |
21 | struct thread_info *thread = t; |
22 | |
23 | switch (cmd) { |
24 | case THREAD_NOTIFY_FLUSH: |
25 | /* |
26 | * flush_thread() zeroes thread->fpstate, so no need |
27 | * to do anything here. |
28 | * |
29 | * FALLTHROUGH: Ensure we don't try to overwrite our newly |
30 | * initialised state information on the first fault. |
31 | */ |
32 | |
33 | case THREAD_NOTIFY_EXIT: |
34 | iwmmxt_task_release(thread); |
35 | break; |
36 | |
37 | case THREAD_NOTIFY_SWITCH: |
38 | iwmmxt_task_switch(thread); |
39 | break; |
40 | } |
41 | |
42 | return NOTIFY_DONE; |
43 | } |
44 | |
45 | static struct notifier_block __maybe_unused iwmmxt_notifier_block = { |
46 | .notifier_call = iwmmxt_do, |
47 | }; |
48 | |
49 | |
50 | static u32 __init pj4_cp_access_read(void) |
51 | { |
52 | u32 value; |
53 | |
54 | __asm__ __volatile__ ( |
55 | "mrc p15, 0, %0, c1, c0, 2\n\t" |
56 | : "=r" (value)); |
57 | return value; |
58 | } |
59 | |
60 | static void __init pj4_cp_access_write(u32 value) |
61 | { |
62 | u32 temp; |
63 | |
64 | __asm__ __volatile__ ( |
65 | "mcr p15, 0, %1, c1, c0, 2\n\t" |
66 | #ifdef CONFIG_THUMB2_KERNEL |
67 | "isb\n\t" |
68 | #else |
69 | "mrc p15, 0, %0, c1, c0, 2\n\t" |
70 | "mov %0, %0\n\t" |
71 | "sub pc, pc, #4\n\t" |
72 | #endif |
73 | : "=r" (temp) : "r" (value)); |
74 | } |
75 | |
76 | static int __init pj4_get_iwmmxt_version(void) |
77 | { |
78 | u32 cp_access, wcid; |
79 | |
80 | cp_access = pj4_cp_access_read(); |
81 | pj4_cp_access_write(value: cp_access | 0xf); |
82 | |
83 | /* check if coprocessor 0 and 1 are available */ |
84 | if ((pj4_cp_access_read() & 0xf) != 0xf) { |
85 | pj4_cp_access_write(value: cp_access); |
86 | return -ENODEV; |
87 | } |
88 | |
89 | /* read iWMMXt coprocessor id register p1, c0 */ |
90 | __asm__ __volatile__ ("mrc p1, 0, %0, c0, c0, 0\n" : "=r" (wcid)); |
91 | |
92 | pj4_cp_access_write(value: cp_access); |
93 | |
94 | /* iWMMXt v1 */ |
95 | if ((wcid & 0xffffff00) == 0x56051000) |
96 | return 1; |
97 | /* iWMMXt v2 */ |
98 | if ((wcid & 0xffffff00) == 0x56052000) |
99 | return 2; |
100 | |
101 | return -EINVAL; |
102 | } |
103 | |
104 | /* |
105 | * Disable CP0/CP1 on boot, and let call_fpe() and the iWMMXt lazy |
106 | * switch code handle iWMMXt context switching. |
107 | */ |
108 | static int __init pj4_cp0_init(void) |
109 | { |
110 | u32 __maybe_unused cp_access; |
111 | int vers; |
112 | |
113 | if (!cpu_is_pj4()) |
114 | return 0; |
115 | |
116 | vers = pj4_get_iwmmxt_version(); |
117 | if (vers < 0) |
118 | return 0; |
119 | |
120 | #ifndef CONFIG_IWMMXT |
121 | pr_info("PJ4 iWMMXt coprocessor detected, but kernel support is missing.\n" ); |
122 | #else |
123 | cp_access = pj4_cp_access_read() & ~0xf; |
124 | pj4_cp_access_write(cp_access); |
125 | |
126 | pr_info("PJ4 iWMMXt v%d coprocessor enabled.\n" , vers); |
127 | elf_hwcap |= HWCAP_IWMMXT; |
128 | thread_register_notifier(&iwmmxt_notifier_block); |
129 | register_iwmmxt_undef_handler(); |
130 | #endif |
131 | |
132 | return 0; |
133 | } |
134 | |
135 | late_initcall(pj4_cp0_init); |
136 | |