Commit | Line | Data |
---|---|---|
3c2a0909 S |
1 | /* linux/drivers/iommu/exynos_iommu.c |
2 | * | |
3 | * Copyright (c) 2013-2014 Samsung Electronics Co., Ltd. | |
4 | * http://www.samsung.com | |
5 | * | |
6 | * This program is free software; you can redistribute it and/or modify | |
7 | * it under the terms of the GNU General Public License version 2 as | |
8 | * published by the Free Software Foundation. | |
9 | */ | |
10 | ||
11 | #ifdef CONFIG_EXYNOS_IOMMU_DEBUG | |
12 | #define DEBUG | |
13 | #endif | |
14 | ||
15 | #include <linux/kernel.h> | |
16 | #include <linux/io.h> | |
17 | #include <linux/interrupt.h> | |
18 | #include <linux/clk.h> | |
19 | #include <linux/err.h> | |
20 | #include <linux/errno.h> | |
21 | #include <linux/device.h> | |
22 | ||
23 | #include <asm/pgtable.h> | |
24 | ||
25 | #include "exynos-iommu.h" | |
26 | ||
27 | #define CFG_LRU 0x1 | |
28 | #define CFG_MASK 0x0150FFFF /* Selecting bit 0-15, 20, 22 and 24 */ | |
29 | #define CFG_SYSSEL (1 << 22) /* System MMU 3.2 only */ | |
30 | ||
31 | #define PB_INFO_NUM(reg) ((reg) & 0xFF) /* System MMU 3.3 only */ | |
32 | ||
33 | #define REG_MMU_FLUSH 0x00C | |
34 | #define REG_MMU_FLUSH_ENTRY 0x010 | |
35 | #define REG_PT_BASE_ADDR 0x014 | |
36 | #define REG_INT_STATUS 0x018 | |
37 | #define REG_INT_CLEAR 0x01C | |
38 | #define REG_PB_INFO 0x400 | |
39 | #define REG_PB_LMM 0x404 | |
40 | #define REG_PB_INDICATE 0x408 | |
41 | #define REG_PB_CFG 0x40C | |
42 | #define REG_PB_START_ADDR 0x410 | |
43 | #define REG_PB_END_ADDR 0x414 | |
44 | #define REG_SPB_BASE_VPN 0x418 | |
45 | ||
46 | #define REG_PAGE_FAULT_ADDR 0x024 | |
47 | #define REG_AW_FAULT_ADDR 0x028 | |
48 | #define REG_AR_FAULT_ADDR 0x02C | |
49 | #define REG_DEFAULT_SLAVE_ADDR 0x030 | |
50 | #define REG_FAULT_TRANS_INFO 0x04C | |
51 | #define REG_L1TLB_READ_ENTRY 0x040 | |
52 | #define REG_L1TLB_ENTRY_PPN 0x044 | |
53 | #define REG_L1TLB_ENTRY_VPN 0x048 | |
54 | ||
55 | #define MAX_NUM_PBUF 6 | |
56 | #define SINGLE_PB_SIZE 16 | |
57 | ||
58 | #define NUM_MINOR_OF_SYSMMU_V3 4 | |
59 | ||
60 | #define MMU_TLB_ENT_NUM(val) ((val) & 0x7F) | |
61 | ||
62 | enum exynos_sysmmu_inttype { | |
63 | SYSMMU_PAGEFAULT, | |
64 | SYSMMU_AR_MULTIHIT, | |
65 | SYSMMU_AW_MULTIHIT, | |
66 | SYSMMU_BUSERROR, | |
67 | SYSMMU_AR_SECURITY, | |
68 | SYSMMU_AR_ACCESS, | |
69 | SYSMMU_AW_SECURITY, | |
70 | SYSMMU_AW_PROTECTION, /* 7 */ | |
71 | SYSMMU_FAULT_UNDEF, | |
72 | SYSMMU_FAULTS_NUM | |
73 | }; | |
74 | ||
75 | static unsigned short fault_reg_offset[9] = { | |
76 | REG_PAGE_FAULT_ADDR, | |
77 | REG_AR_FAULT_ADDR, | |
78 | REG_AW_FAULT_ADDR, | |
79 | REG_PAGE_FAULT_ADDR, | |
80 | REG_AR_FAULT_ADDR, | |
81 | REG_AR_FAULT_ADDR, | |
82 | REG_AW_FAULT_ADDR, | |
83 | REG_AW_FAULT_ADDR | |
84 | }; | |
85 | ||
86 | static char *sysmmu_fault_name[SYSMMU_FAULTS_NUM] = { | |
87 | "PAGE FAULT", | |
88 | "AR MULTI-HIT FAULT", | |
89 | "AW MULTI-HIT FAULT", | |
90 | "BUS ERROR", | |
91 | "AR SECURITY PROTECTION FAULT", | |
92 | "AR ACCESS PROTECTION FAULT", | |
93 | "AW SECURITY PROTECTION FAULT", | |
94 | "AW ACCESS PROTECTION FAULT", | |
95 | "UNDEFINED FAULT" | |
96 | }; | |
97 | ||
98 | static bool has_sysmmu_capable_pbuf(void __iomem *sfrbase, int *min) | |
99 | { | |
100 | unsigned int ver; | |
101 | ||
102 | ver = __raw_sysmmu_version(sfrbase); | |
103 | if (min) | |
104 | *min = MMU_MIN_VER(ver); | |
105 | return MMU_MAJ_VER(ver) == 3; | |
106 | } | |
107 | ||
108 | void __sysmmu_tlb_invalidate(struct sysmmu_drvdata *drvdata, | |
109 | dma_addr_t iova, size_t size) | |
110 | { | |
111 | void * __iomem sfrbase = drvdata->sfrbase; | |
112 | ||
113 | if (!WARN_ON(!sysmmu_block(sfrbase))) { | |
114 | __raw_writel(0x1, sfrbase + REG_MMU_FLUSH); | |
115 | SYSMMU_EVENT_LOG_TLB_INV_ALL(SYSMMU_DRVDATA_TO_LOG(drvdata)); | |
116 | } | |
117 | sysmmu_unblock(sfrbase); | |
118 | } | |
119 | ||
120 | void __sysmmu_tlb_invalidate_flpdcache(void __iomem *sfrbase, dma_addr_t iova) | |
121 | { | |
122 | if (__raw_sysmmu_version(sfrbase) == MAKE_MMU_VER(3, 3)) | |
123 | __raw_writel(iova | 0x1, sfrbase + REG_MMU_FLUSH_ENTRY); | |
124 | } | |
125 | ||
126 | void __sysmmu_tlb_invalidate_entry(void __iomem *sfrbase, dma_addr_t iova) | |
127 | { | |
128 | __raw_writel(iova | 0x1, sfrbase + REG_MMU_FLUSH_ENTRY); | |
129 | } | |
130 | ||
131 | void __sysmmu_set_ptbase(void __iomem *sfrbase, phys_addr_t pfn_pgtable) | |
132 | { | |
133 | __raw_writel(pfn_pgtable * PAGE_SIZE, sfrbase + REG_PT_BASE_ADDR); | |
134 | ||
135 | __raw_writel(0x1, sfrbase + REG_MMU_FLUSH); | |
136 | } | |
137 | ||
138 | static void __sysmmu_set_prefbuf(void __iomem *pbufbase, unsigned long base, | |
139 | unsigned long size, int idx) | |
140 | { | |
141 | __raw_writel(base, pbufbase + idx * 8); | |
142 | __raw_writel(size - 1 + base, pbufbase + 4 + idx * 8); | |
143 | } | |
144 | ||
145 | /* | |
146 | * Offset of prefetch buffer setting registers are different | |
147 | * between SysMMU 3.1 and 3.2. 3.3 has a single prefetch buffer setting. | |
148 | */ | |
149 | static unsigned short | |
150 | pbuf_offset[NUM_MINOR_OF_SYSMMU_V3] = {0x04C, 0x04C, 0x070, 0x410}; | |
151 | ||
152 | ||
153 | static void __sysmmu_set_pbuf_ver31(struct sysmmu_drvdata *drvdata, | |
154 | struct sysmmu_prefbuf prefbuf[], int num_bufs) | |
155 | { | |
156 | unsigned long cfg = | |
157 | __raw_readl(drvdata->sfrbase + REG_MMU_CFG) & CFG_MASK; | |
158 | ||
159 | /* Only the first 2 buffers are set to PB */ | |
160 | if (num_bufs >= 2) { | |
161 | /* Separate PB mode */ | |
162 | cfg |= 2 << 28; | |
163 | ||
164 | if (prefbuf[1].size == 0) | |
165 | prefbuf[1].size = 1; | |
166 | __sysmmu_set_prefbuf(drvdata->sfrbase + pbuf_offset[1], | |
167 | prefbuf[1].base, prefbuf[1].size, 1); | |
168 | SYSMMU_EVENT_LOG_PBLMM(SYSMMU_DRVDATA_TO_LOG(drvdata), | |
169 | cfg, num_bufs); | |
170 | SYSMMU_EVENT_LOG_PBSET(SYSMMU_DRVDATA_TO_LOG(drvdata), | |
171 | 1, prefbuf[1].base, | |
172 | prefbuf[1].base + prefbuf[1].size - 1); | |
173 | } else { | |
174 | /* Combined PB mode */ | |
175 | cfg |= 3 << 28; | |
176 | drvdata->num_pbufs = 1; | |
177 | drvdata->pbufs[0] = prefbuf[0]; | |
178 | SYSMMU_EVENT_LOG_PBLMM(SYSMMU_DRVDATA_TO_LOG(drvdata), | |
179 | cfg, num_bufs); | |
180 | } | |
181 | ||
182 | ||
183 | __raw_writel(cfg, drvdata->sfrbase + REG_MMU_CFG); | |
184 | ||
185 | if (prefbuf[0].size == 0) | |
186 | prefbuf[0].size = 1; | |
187 | __sysmmu_set_prefbuf(drvdata->sfrbase + pbuf_offset[1], | |
188 | prefbuf[0].base, prefbuf[0].size, 0); | |
189 | SYSMMU_EVENT_LOG_PBSET(SYSMMU_DRVDATA_TO_LOG(drvdata), | |
190 | 0, prefbuf[0].base, | |
191 | prefbuf[0].base + prefbuf[0].size - 1); | |
192 | } | |
193 | ||
194 | static void __sysmmu_set_pbuf_ver32(struct sysmmu_drvdata *drvdata, | |
195 | struct sysmmu_prefbuf prefbuf[], int num_bufs) | |
196 | { | |
197 | static char pbidx[3][3] = { /* [numbufs][PB index] */ | |
198 | /* Index of Prefetch buffer entries */ | |
199 | {1, 0, 0}, {3, 1, 0}, {3, 2, 1}, | |
200 | }; | |
201 | int i; | |
202 | unsigned long cfg = | |
203 | __raw_readl(drvdata->sfrbase + REG_MMU_CFG) & CFG_MASK; | |
204 | ||
205 | __raw_writel(0x1, drvdata->sfrbase + REG_MMU_FLUSH); | |
206 | ||
207 | cfg |= 7 << 16; /* enabling PB0 ~ PB2 */ | |
208 | ||
209 | switch (num_bufs) { | |
210 | case 1: | |
211 | /* Combined PB mode (0 ~ 2) */ | |
212 | cfg |= 1 << 19; | |
213 | break; | |
214 | case 2: | |
215 | /* Combined PB mode (0 ~ 1) */ | |
216 | cfg |= 1 << 21; | |
217 | break; | |
218 | case 3: | |
219 | break; | |
220 | default: | |
221 | num_bufs = 3; /* Only the first 3 buffers are set to PB */ | |
222 | } | |
223 | ||
224 | SYSMMU_EVENT_LOG_PBLMM(SYSMMU_DRVDATA_TO_LOG(drvdata), cfg, num_bufs); | |
225 | ||
226 | for (i = 0; i < 3; i++) { | |
227 | if (pbidx[num_bufs - 1][i]) { | |
228 | if (prefbuf[i].size == 0) { | |
229 | dev_err(drvdata->sysmmu, | |
230 | "%s: Trying to init PB[%d/%d]with zero-size\n", | |
231 | __func__, i, num_bufs); | |
232 | prefbuf[i].size = 1; | |
233 | } | |
234 | __sysmmu_set_prefbuf(drvdata->sfrbase + pbuf_offset[2], | |
235 | prefbuf[i].base, prefbuf[i].size, | |
236 | pbidx[num_bufs - 1][i] - 1); | |
237 | ||
238 | SYSMMU_EVENT_LOG_PBSET(SYSMMU_DRVDATA_TO_LOG(drvdata), | |
239 | pbidx[num_bufs - 1][i] - 1, | |
240 | prefbuf[i].base, | |
241 | prefbuf[i].base + prefbuf[i].size - 1); | |
242 | } | |
243 | } | |
244 | ||
245 | __raw_writel(cfg, drvdata->sfrbase + REG_MMU_CFG); | |
246 | } | |
247 | ||
248 | static unsigned int find_lmm_preset(unsigned int num_pb, unsigned int num_bufs) | |
249 | { | |
250 | static char lmm_preset[4][6] = { /* [num of PB][num of buffers] */ | |
251 | /* 1, 2, 3, 4, 5, 6 */ | |
252 | { 1, 1, 0, -1, -1, -1}, /* num of pb: 3 */ | |
253 | { 3, 2, 1, 0, -1, -1}, /* num of pb: 4 */ | |
254 | {-1, -1, -1, -1, -1, -1}, | |
255 | { 5, 5, 4, 2, 1, 0}, /* num of pb: 6 */ | |
256 | }; | |
257 | unsigned int lmm; | |
258 | ||
259 | BUG_ON(num_bufs > 6); | |
260 | lmm = lmm_preset[num_pb - 3][num_bufs - 1]; | |
261 | BUG_ON(lmm == -1); | |
262 | return lmm; | |
263 | } | |
264 | ||
265 | static unsigned int find_num_pb(unsigned int num_pb, unsigned int lmm) | |
266 | { | |
267 | static char lmm_preset[6][6] = { /* [pb_num - 1][pb_lmm] */ | |
268 | {0, 0, 0, 0, 0, 0}, | |
269 | {0, 0, 0, 0, 0, 0}, | |
270 | {3, 2, 0, 0, 0, 0}, | |
271 | {4, 3, 2, 1, 0, 0}, | |
272 | {0, 0, 0, 0, 0, 0}, | |
273 | {6, 5, 4, 3, 3, 2}, | |
274 | }; | |
275 | ||
276 | num_pb = lmm_preset[num_pb - 1][lmm]; | |
277 | BUG_ON(num_pb == 0); | |
278 | return num_pb; | |
279 | } | |
280 | ||
281 | static void __sysmmu_init_pb(void __iomem *sfrbase, unsigned int num_pb) | |
282 | { | |
283 | unsigned int i = 0; | |
284 | ||
285 | for (i = 0; i < num_pb; i++) { | |
286 | __raw_writel(i, sfrbase + REG_PB_INDICATE); | |
287 | __raw_writel(0, sfrbase + REG_PB_CFG); | |
288 | } | |
289 | ||
290 | __raw_writel(0x1, sfrbase + REG_MMU_FLUSH); | |
291 | } | |
292 | ||
293 | static void __sysmmu_set_pbuf_ver33(struct sysmmu_drvdata *drvdata, | |
294 | struct sysmmu_prefbuf prefbuf[], int num_bufs) | |
295 | { | |
296 | unsigned int i, num_pb, lmm; | |
297 | ||
298 | num_pb = PB_INFO_NUM(__raw_readl(drvdata->sfrbase + REG_PB_INFO)); | |
299 | ||
300 | __sysmmu_init_pb(drvdata->sfrbase, | |
301 | find_num_pb(num_pb, | |
302 | __raw_readl(drvdata->sfrbase + REG_PB_LMM))); | |
303 | ||
304 | lmm = find_lmm_preset(num_pb, (unsigned int)num_bufs); | |
305 | num_pb = find_num_pb(num_pb, lmm); | |
306 | ||
307 | __raw_writel(lmm, drvdata->sfrbase + REG_PB_LMM); | |
308 | ||
309 | SYSMMU_EVENT_LOG_PBLMM(SYSMMU_DRVDATA_TO_LOG(drvdata), lmm, num_bufs); | |
310 | ||
311 | for (i = 0; i < num_pb; i++) { | |
312 | __raw_writel(i, drvdata->sfrbase + REG_PB_INDICATE); | |
313 | if ((prefbuf[i].size > 0) && (i < num_bufs)) { | |
314 | __sysmmu_set_prefbuf(drvdata->sfrbase + pbuf_offset[3], | |
315 | prefbuf[i].base, prefbuf[i].size, 0); | |
316 | __raw_writel(prefbuf[i].config | 1, | |
317 | drvdata->sfrbase + REG_PB_CFG); | |
318 | SYSMMU_EVENT_LOG_PBSET(SYSMMU_DRVDATA_TO_LOG(drvdata), | |
319 | prefbuf[i].config | 1, prefbuf[i].base, | |
320 | prefbuf[i].size - 1 + prefbuf[i].base); | |
321 | } else { | |
322 | if (prefbuf[i].size == 0) { | |
323 | dev_err(drvdata->sysmmu, | |
324 | "%s: Trying to init PB[%d/%d]with zero-size\n", | |
325 | __func__, i, num_bufs); | |
326 | } | |
327 | ||
328 | SYSMMU_EVENT_LOG_PBSET(SYSMMU_DRVDATA_TO_LOG(drvdata), | |
329 | 0, 0, 0); | |
330 | } | |
331 | } | |
332 | } | |
333 | ||
334 | static void (*func_set_pbuf[NUM_MINOR_OF_SYSMMU_V3]) | |
335 | (struct sysmmu_drvdata *, struct sysmmu_prefbuf *, int) = { | |
336 | __sysmmu_set_pbuf_ver31, | |
337 | __sysmmu_set_pbuf_ver31, | |
338 | __sysmmu_set_pbuf_ver32, | |
339 | __sysmmu_set_pbuf_ver33, | |
340 | }; | |
341 | ||
342 | ||
343 | static void __sysmmu_disable_pbuf_ver31(struct sysmmu_drvdata *drvdata) | |
344 | { | |
345 | unsigned int cfg = __raw_readl(drvdata->sfrbase + REG_MMU_CFG); | |
346 | ||
347 | cfg &= CFG_MASK; | |
348 | __raw_writel(cfg, drvdata->sfrbase + REG_MMU_CFG); | |
349 | ||
350 | SYSMMU_EVENT_LOG_PBLMM(SYSMMU_DRVDATA_TO_LOG(drvdata), cfg, 0); | |
351 | } | |
352 | ||
353 | #define __sysmmu_disable_pbuf_ver32 __sysmmu_disable_pbuf_ver31 | |
354 | ||
355 | static void __sysmmu_disable_pbuf_ver33(struct sysmmu_drvdata *drvdata) | |
356 | { | |
357 | unsigned int i, num_pb; | |
358 | ||
359 | num_pb = PB_INFO_NUM(__raw_readl(drvdata->sfrbase + REG_PB_INFO)); | |
360 | ||
361 | __sysmmu_init_pb(drvdata->sfrbase, | |
362 | find_num_pb(num_pb, | |
363 | __raw_readl(drvdata->sfrbase + REG_PB_LMM))); | |
364 | ||
365 | __raw_writel(0, drvdata->sfrbase + REG_PB_LMM); | |
366 | ||
367 | SYSMMU_EVENT_LOG_PBLMM(SYSMMU_DRVDATA_TO_LOG(drvdata), 0, 0); | |
368 | ||
369 | for (i = 0; i < num_pb; i++) { | |
370 | __raw_writel(i, drvdata->sfrbase + REG_PB_INDICATE); | |
371 | __raw_writel(0, drvdata->sfrbase + REG_PB_CFG); | |
372 | SYSMMU_EVENT_LOG_PBSET(SYSMMU_DRVDATA_TO_LOG(drvdata), 0, 0, 0); | |
373 | } | |
374 | } | |
375 | ||
376 | static void (*func_disable_pbuf[NUM_MINOR_OF_SYSMMU_V3]) | |
377 | (struct sysmmu_drvdata *) = { | |
378 | __sysmmu_disable_pbuf_ver31, | |
379 | __sysmmu_disable_pbuf_ver31, | |
380 | __sysmmu_disable_pbuf_ver32, | |
381 | __sysmmu_disable_pbuf_ver33, | |
382 | }; | |
383 | ||
384 | static unsigned int __sysmmu_get_num_pb(struct sysmmu_drvdata *drvdata, | |
385 | int *min) | |
386 | { | |
387 | if (!has_sysmmu_capable_pbuf(drvdata->sfrbase, min)) | |
388 | return 0; | |
389 | ||
390 | switch (*min) { | |
391 | case 0: | |
392 | case 1: | |
393 | return 2; | |
394 | case 2: | |
395 | return 3; | |
396 | case 3: | |
397 | return PB_INFO_NUM(__raw_readl(drvdata->sfrbase + REG_PB_INFO)); | |
398 | default: | |
399 | BUG(); | |
400 | } | |
401 | ||
402 | return 0; | |
403 | } | |
404 | ||
405 | void __exynos_sysmmu_set_prefbuf_by_region(struct sysmmu_drvdata *drvdata, | |
406 | struct sysmmu_prefbuf pb_reg[], unsigned int num_reg) | |
407 | { | |
408 | unsigned int i; | |
409 | int num_bufs = 0; | |
410 | struct sysmmu_prefbuf prefbuf[6]; | |
411 | unsigned int version; | |
412 | ||
413 | version = __raw_sysmmu_version(drvdata->sfrbase); | |
414 | if (version < MAKE_MMU_VER(3, 0)) | |
415 | return; | |
416 | ||
417 | if ((num_reg == 0) || (pb_reg == NULL)) { | |
418 | /* Disabling prefetch buffers */ | |
419 | func_disable_pbuf[MMU_MIN_VER(version)](drvdata); | |
420 | return; | |
421 | } | |
422 | ||
423 | for (i = 0; i < num_reg; i++) { | |
424 | if (((pb_reg[i].config & SYSMMU_PBUFCFG_WRITE) && | |
425 | (drvdata->prop & SYSMMU_PROP_WRITE)) || | |
426 | (!(pb_reg[i].config & SYSMMU_PBUFCFG_WRITE) && | |
427 | (drvdata->prop & SYSMMU_PROP_READ))) | |
428 | prefbuf[num_bufs++] = pb_reg[i]; | |
429 | } | |
430 | ||
431 | func_set_pbuf[MMU_MIN_VER(version)](drvdata, prefbuf, num_bufs); | |
432 | } | |
433 | ||
434 | void __exynos_sysmmu_set_prefbuf_by_plane(struct sysmmu_drvdata *drvdata, | |
435 | unsigned int inplanes, unsigned int onplanes, | |
436 | unsigned int ipoption, unsigned int opoption) | |
437 | { | |
438 | unsigned int num_pb; | |
439 | int num_bufs, min; | |
440 | struct sysmmu_prefbuf prefbuf[6]; | |
441 | ||
442 | num_pb = __sysmmu_get_num_pb(drvdata, &min); | |
443 | if (num_pb == 0) /* No Prefetch buffers */ | |
444 | return; | |
445 | ||
446 | num_bufs = __prepare_prefetch_buffers_by_plane(drvdata, | |
447 | prefbuf, num_pb, inplanes, onplanes, | |
448 | ipoption, opoption); | |
449 | ||
450 | if (num_bufs == 0) | |
451 | func_disable_pbuf[min](drvdata); | |
452 | else | |
453 | func_set_pbuf[min](drvdata, prefbuf, num_bufs); | |
454 | } | |
455 | ||
456 | static void dump_sysmmu_pb_v31(void __iomem *sfrbase) | |
457 | { | |
458 | unsigned int cfg, i; | |
459 | cfg = __raw_readl(sfrbase + REG_MMU_CFG); | |
460 | ||
461 | switch ((cfg >> 28) & 0x3) { | |
462 | case 2: | |
463 | pr_crit("PB[1] [%#010x, %#010x] Cached VA: %08x\n", | |
464 | __raw_readl(sfrbase + 0x54), | |
465 | __raw_readl(sfrbase + 0x58), | |
466 | __raw_readl(sfrbase + 0x60)); | |
467 | for (i = 0; i < 16; i++) { | |
468 | __raw_writel((i << 4) | 1, sfrbase + 0x70); | |
469 | pr_crit("PB[1][%2d] %08x\n", i, | |
470 | __raw_readl(sfrbase + 0x74)); | |
471 | } | |
472 | /* fall trhough */ | |
473 | case 1: | |
474 | pr_crit("PB[0] [%#010x, %#010x] Cached VA: %08x\n", | |
475 | __raw_readl(sfrbase + 0x4C), | |
476 | __raw_readl(sfrbase + 0x50), | |
477 | __raw_readl(sfrbase + 0x5C)); | |
478 | for (i = 0; i < 16; i++) { | |
479 | __raw_writel((i << 4) | 1, sfrbase + 0x68); | |
480 | pr_crit("PB[0][%2d] %08x\n", i, | |
481 | __raw_readl(sfrbase + 0x6C)); | |
482 | } | |
483 | break; | |
484 | case 3: | |
485 | pr_crit("PB[0] [%#010x, %#010x] Cached VA: %08x\n", | |
486 | __raw_readl(sfrbase + 0x4C), | |
487 | __raw_readl(sfrbase + 0x50), | |
488 | __raw_readl(sfrbase + 0x5C)); | |
489 | for (i = 0; i < 32; i++) { | |
490 | __raw_writel((i << 4) | 1, sfrbase + 0x68); | |
491 | pr_crit("PB[0][%2d] %08x\n", i, | |
492 | __raw_readl(sfrbase + 0x6C)); | |
493 | } | |
494 | case 0: | |
495 | break; | |
496 | } | |
497 | } | |
498 | ||
499 | static void dump_sysmmu_pb_v32(void __iomem *sfrbase) | |
500 | { | |
501 | unsigned int cfg, i; | |
502 | cfg = __raw_readl(sfrbase + REG_MMU_CFG); | |
503 | ||
504 | if (cfg & (1 << 19)) { | |
505 | pr_crit("PB[0] [%#010x, %#010x] Cached VA: %08x\n", | |
506 | __raw_readl(sfrbase + 0x70), | |
507 | __raw_readl(sfrbase + 0x74), | |
508 | __raw_readl(sfrbase + 0x88)); | |
509 | for (i = 0; i < 64; i++) { | |
510 | __raw_writel((i << 4) | 1, sfrbase + 0x98); | |
511 | pr_crit("PB[0][%2d] %08x\n", i, | |
512 | __raw_readl(sfrbase + 0x9C)); | |
513 | } | |
514 | return; | |
515 | } else if (cfg & (1 << 21)) { | |
516 | pr_crit("PB[0] [%#010x, %#010x] Cached VA: %08x\n", | |
517 | __raw_readl(sfrbase + 0x70), | |
518 | __raw_readl(sfrbase + 0x74), | |
519 | __raw_readl(sfrbase + 0x88)); | |
520 | for (i = 0; i < 32; i++) { | |
521 | __raw_writel((i << 4) | 1, sfrbase + 0x98); | |
522 | pr_crit("PB[0][%2d] %08x\n", i, | |
523 | __raw_readl(sfrbase + 0x9C)); | |
524 | } | |
525 | ||
526 | if ((cfg & (1 << 18)) == 0) | |
527 | return; | |
528 | ||
529 | pr_crit("PB[2] [%#010x, %#010x] Cached VA: %08x\n", | |
530 | __raw_readl(sfrbase + 0x80), | |
531 | __raw_readl(sfrbase + 0x84), | |
532 | __raw_readl(sfrbase + 0x90)); | |
533 | for (i = 0; i < 32; i++) { | |
534 | __raw_writel((i << 4) | 1, sfrbase + 0xA8); | |
535 | pr_crit("PB[2][%2d] %08x\n", i, | |
536 | __raw_readl(sfrbase + 0xAC)); | |
537 | } | |
538 | ||
539 | return; | |
540 | } | |
541 | ||
542 | if (cfg & (1 << 16)) { | |
543 | pr_crit("PB[0] [%#010x, %#010x] Cached VA: %08x\n", | |
544 | __raw_readl(sfrbase + 0x70), | |
545 | __raw_readl(sfrbase + 0x74), | |
546 | __raw_readl(sfrbase + 0x88)); | |
547 | for (i = 0; i < 16; i++) { | |
548 | __raw_writel((i << 4) | 1, sfrbase + 0x98); | |
549 | pr_crit("PB[0][%2d] %08x\n", i, | |
550 | __raw_readl(sfrbase + 0x9C)); | |
551 | } | |
552 | } | |
553 | ||
554 | if (cfg & (1 << 17)) { | |
555 | pr_crit("PB[1] [%#010x, %#010x] Cached VA: %08x\n", | |
556 | __raw_readl(sfrbase + 0x78), | |
557 | __raw_readl(sfrbase + 0x7C), | |
558 | __raw_readl(sfrbase + 0x8C)); | |
559 | for (i = 0; i < 16; i++) { | |
560 | __raw_writel((i << 4) | 1, sfrbase + 0xA0); | |
561 | pr_crit("PB[1][%2d] %08x\n", i, | |
562 | __raw_readl(sfrbase + 0xA4)); | |
563 | } | |
564 | } | |
565 | ||
566 | if (cfg & (1 << 18)) { | |
567 | pr_crit("PB[2] [%#010x, %#010x] Cached VA: %08x\n", | |
568 | __raw_readl(sfrbase + 0x80), | |
569 | __raw_readl(sfrbase + 0x84), | |
570 | __raw_readl(sfrbase + 0x90)); | |
571 | for (i = 0; i < 32; i++) { | |
572 | __raw_writel((i << 4) | 1, sfrbase + 0xA8); | |
573 | pr_crit("PB[2][%2d] %08x\n", i, | |
574 | __raw_readl(sfrbase + 0xAC)); | |
575 | } | |
576 | } | |
577 | } | |
578 | ||
579 | void dump_sysmmu_tlb_pb(void __iomem *sfrbase) | |
580 | { | |
581 | unsigned int i, capa, lmm, tlb_ent_num, ver; | |
582 | ||
583 | ver = MMU_RAW_VER(__raw_readl(sfrbase + REG_MMU_VERSION)); | |
584 | ||
585 | pr_crit("---------- System MMU Status -----------------------------\n"); | |
586 | pr_crit("VERSION %d.%d, MMU_CFG: %#010x, MMU_STATUS: %#010x\n", | |
587 | MMU_MAJ_VER(ver), MMU_MIN_VER(ver), | |
588 | __raw_readl(sfrbase + REG_MMU_CFG), | |
589 | __raw_readl(sfrbase + REG_MMU_STATUS)); | |
590 | ||
591 | /* TODO: dump tlb with vpn for sysmmu v1 */ | |
592 | if (MMU_MAJ_VER(ver) < 2) | |
593 | return; | |
594 | ||
595 | pr_crit("---------- Level 1 TLB -----------------------------------\n"); | |
596 | ||
597 | tlb_ent_num = MMU_TLB_ENT_NUM(__raw_readl(sfrbase + REG_MMU_VERSION)); | |
598 | for (i = 0; i < tlb_ent_num; i++) { | |
599 | __raw_writel((i << 4) | 1, sfrbase + REG_L1TLB_READ_ENTRY); | |
600 | pr_crit("[%02d] VPN: %#010x, PPN: %#010x\n", | |
601 | i, __raw_readl(sfrbase + REG_L1TLB_ENTRY_VPN), | |
602 | __raw_readl(sfrbase + REG_L1TLB_ENTRY_PPN)); | |
603 | } | |
604 | ||
605 | if (MMU_MAJ_VER(ver) < 3) | |
606 | return; | |
607 | ||
608 | pr_crit("---------- Prefetch Buffers ------------------------------\n"); | |
609 | ||
610 | if (MMU_MIN_VER(ver) < 2) { | |
611 | dump_sysmmu_pb_v31(sfrbase); | |
612 | return; | |
613 | } | |
614 | ||
615 | if (MMU_MIN_VER(ver) < 3) { | |
616 | dump_sysmmu_pb_v32(sfrbase); | |
617 | return; | |
618 | } | |
619 | ||
620 | capa = __raw_readl(sfrbase + REG_PB_INFO); | |
621 | lmm = __raw_readl(sfrbase + REG_PB_LMM); | |
622 | ||
623 | pr_crit("PB_INFO: %#010x, PB_LMM: %#010x\n", capa, lmm); | |
624 | ||
625 | capa = find_num_pb(capa & 0xFF, lmm); | |
626 | ||
627 | for (i = 0; i < capa; i++) { | |
628 | __raw_writel(i, sfrbase + REG_PB_INDICATE); | |
629 | pr_crit("PB[%d] = CFG: %#010x, START: %#010x, END: %#010x\n", i, | |
630 | __raw_readl(sfrbase + REG_PB_CFG), | |
631 | __raw_readl(sfrbase + REG_PB_START_ADDR), | |
632 | __raw_readl(sfrbase + REG_PB_END_ADDR)); | |
633 | ||
634 | pr_crit("PB[%d]_SUB0 BASE_VPN = %#010x\n", i, | |
635 | __raw_readl(sfrbase + REG_SPB_BASE_VPN)); | |
636 | __raw_writel(i | 0x100, sfrbase + REG_PB_INDICATE); | |
637 | pr_crit("PB[%d]_SUB1 BASE_VPN = %#010x\n", i, | |
638 | __raw_readl(sfrbase + REG_SPB_BASE_VPN)); | |
639 | } | |
640 | ||
641 | /* Reading L2TLB is not provided by H/W */ | |
642 | } | |
643 | ||
644 | static void show_fault_information(struct sysmmu_drvdata *drvdata, | |
645 | enum exynos_sysmmu_inttype itype, | |
646 | unsigned long fault_addr) | |
647 | { | |
648 | unsigned int info; | |
649 | phys_addr_t pgtable; | |
650 | unsigned int version; | |
651 | ||
652 | pgtable = __raw_readl(drvdata->sfrbase + REG_PT_BASE_ADDR); | |
653 | ||
654 | pr_crit("----------------------------------------------------------\n"); | |
655 | pr_crit("%s %s at %#010lx by %s (page table @ %#010x)\n", | |
656 | dev_name(drvdata->sysmmu), | |
657 | sysmmu_fault_name[itype], fault_addr, | |
658 | dev_name(drvdata->master), pgtable); | |
659 | ||
660 | if (itype== SYSMMU_FAULT_UNDEF) { | |
661 | pr_crit("The fault is not caused by this System MMU.\n"); | |
662 | pr_crit("Please check IRQ and SFR base address.\n"); | |
663 | goto finish; | |
664 | } | |
665 | ||
666 | version = __raw_sysmmu_version(drvdata->sfrbase); | |
667 | if (version == MAKE_MMU_VER(3, 3)) { | |
668 | info = __raw_readl(drvdata->sfrbase + | |
669 | REG_FAULT_TRANS_INFO); | |
670 | pr_crit("AxID: %#x, AxLEN: %#x RW: %s\n", | |
671 | info & 0xFFFF, (info >> 16) & 0xF, | |
672 | (info >> 20) ? "WRITE" : "READ"); | |
673 | } | |
674 | ||
675 | if (pgtable != drvdata->pgtable) | |
676 | pr_crit("Page table base of driver: %#010x\n", | |
677 | drvdata->pgtable); | |
678 | ||
679 | if (itype == SYSMMU_BUSERROR) { | |
680 | pr_crit("System MMU has failed to access page table\n"); | |
681 | goto finish; | |
682 | } | |
683 | ||
684 | if (!pfn_valid(pgtable >> PAGE_SHIFT)) { | |
685 | pr_crit("Page table base is not in a valid memory region\n"); | |
686 | } else { | |
687 | sysmmu_pte_t *ent; | |
688 | ent = section_entry(phys_to_virt(pgtable), fault_addr); | |
689 | pr_crit("Lv1 entry: %#010x\n", *ent); | |
690 | ||
691 | if (lv1ent_page(ent)) { | |
692 | ent = page_entry(ent, fault_addr); | |
693 | pr_crit("Lv2 entry: %#010x\n", *ent); | |
694 | } | |
695 | } | |
696 | ||
697 | dump_sysmmu_tlb_pb(drvdata->sfrbase); | |
698 | ||
699 | finish: | |
700 | pr_crit("----------------------------------------------------------\n"); | |
701 | } | |
702 | ||
703 | irqreturn_t exynos_sysmmu_irq(int irq, void *dev_id) | |
704 | { | |
705 | /* SYSMMU is in blocked when interrupt occurred. */ | |
706 | struct sysmmu_drvdata *drvdata = dev_id; | |
707 | unsigned int itype; | |
708 | unsigned long addr = -1; | |
709 | int ret = -ENOSYS; | |
710 | int flags = 0; | |
711 | ||
712 | WARN(!is_sysmmu_active(drvdata), | |
713 | "Fault occurred while System MMU %s is not enabled!\n", | |
714 | dev_name(drvdata->sysmmu)); | |
715 | ||
716 | itype = __ffs(__raw_readl(drvdata->sfrbase + REG_INT_STATUS)); | |
717 | ||
718 | if (WARN_ON(!((itype >= 0) && (itype < SYSMMU_FAULT_UNDEF)))) | |
719 | itype = SYSMMU_FAULT_UNDEF; | |
720 | else | |
721 | addr = __raw_readl(drvdata->sfrbase + fault_reg_offset[itype]); | |
722 | ||
723 | show_fault_information(drvdata, itype, addr); | |
724 | ||
725 | if (drvdata->domain) /* master is set if drvdata->domain exists */ | |
726 | ret = report_iommu_fault(drvdata->domain, | |
727 | drvdata->master, addr, flags); | |
728 | ||
729 | panic("Unrecoverable System MMU Fault!!"); | |
730 | ||
731 | return IRQ_HANDLED; | |
732 | } | |
733 | ||
734 | void __sysmmu_init_config(struct sysmmu_drvdata *drvdata) | |
735 | { | |
736 | unsigned long cfg = CFG_LRU | CFG_QOS(drvdata->qos); | |
737 | unsigned int version; | |
738 | ||
739 | __raw_writel(0, drvdata->sfrbase + REG_MMU_CTRL); | |
740 | ||
741 | version = __raw_sysmmu_version(drvdata->sfrbase); | |
742 | if (version < MAKE_MMU_VER(3, 0)) | |
743 | goto set_cfg; | |
744 | ||
745 | if (MMU_MAJ_VER(version) != 3) | |
746 | panic("%s: Failed to read version (%d.%d), master: %s\n", | |
747 | dev_name(drvdata->sysmmu), MMU_MAJ_VER(version), | |
748 | MMU_MIN_VER(version), dev_name(drvdata->master)); | |
749 | ||
750 | ||
751 | if (MMU_MIN_VER(version) < 2) | |
752 | goto set_pb; | |
753 | ||
754 | BUG_ON(MMU_MIN_VER(version) > 3); | |
755 | ||
756 | cfg |= CFG_FLPDCACHE; | |
757 | cfg |= (MMU_MIN_VER(version) == 2) ? CFG_SYSSEL : CFG_ACGEN; | |
758 | cfg |= CFG_QOS_OVRRIDE; | |
759 | ||
760 | set_pb: | |
761 | __exynos_sysmmu_set_prefbuf_by_plane(drvdata, 0, 0, | |
762 | SYSMMU_PBUFCFG_DEFAULT_INPUT, | |
763 | SYSMMU_PBUFCFG_DEFAULT_OUTPUT); | |
764 | set_cfg: | |
765 | cfg |= __raw_readl(drvdata->sfrbase + REG_MMU_CFG) & ~CFG_MASK; | |
766 | __raw_writel(cfg, drvdata->sfrbase + REG_MMU_CFG); | |
767 | } | |
768 | ||
769 | void dump_sysmmu_ppc_cnt(struct sysmmu_drvdata *drvdata) | |
770 | { | |
771 | static char ppc_event_name_preset[4][8] = { /* [type][event_name] */ | |
772 | {8, 8, -1, -1, 0, 0, -1, -1}, /*SYSMMU v1.2 */ | |
773 | {1, 1, 2, 2, 0, 0, -1, -1}, /* SYSMMU v2.1 */ | |
774 | {8, 8, 5, 5, 0, 0, 4, 4}, /* SYSMMU v3.1/2 */ | |
775 | {0, 8, 4, 5, 3, 9, 6, 7}, /* SYSMMU v3.3 */ | |
776 | }; | |
777 | ||
778 | unsigned int ver, type, offset; | |
779 | int i, maj; | |
780 | u32 cfg; | |
781 | ||
782 | ver = __raw_sysmmu_version(drvdata->sfrbase); | |
783 | maj = MMU_MAJ_VER(ver); | |
784 | ||
785 | offset = (maj < 2) ? 0x40 : 0x58; | |
786 | ||
787 | pr_crit("------------- System MMU PPC Status --------------\n"); | |
788 | for (i = 0; i < drvdata->event_cnt; i++) { | |
789 | int event, write = 0; | |
790 | unsigned char preset; | |
791 | cfg = __raw_readl(drvdata->sfrbase + | |
792 | REG_PPC_EVENT_SEL(offset, i)); | |
793 | event = cfg & 0xf; | |
794 | if (ver == MAKE_MMU_VER(3, 3)) { | |
795 | type = maj; | |
796 | if (event > 0x7) { | |
797 | write = 1; | |
798 | event -= 0x8; | |
799 | }; | |
800 | } else { | |
801 | type = maj - 1; | |
802 | write = !(event % 2); | |
803 | } | |
804 | ||
805 | if (ppc_event_name_preset[type][event] < 0) { | |
806 | pr_err("PPC event value is unknown"); | |
807 | continue; | |
808 | } | |
809 | ||
810 | preset = ppc_event_name_preset[type][event]; | |
811 | pr_crit("%s %s %s CNT : %d", dev_name(drvdata->sysmmu), | |
812 | write ? "WRITE" : "READ", ppc_event_name[preset], | |
813 | __raw_readl(drvdata->sfrbase + REG_PPC_PMCNT(i))); | |
814 | } | |
815 | pr_crit("--------------------------------------------------\n"); | |
816 | } | |
817 | ||
818 | int sysmmu_set_ppc_event(struct sysmmu_drvdata *drvdata, int event) | |
819 | { | |
820 | static char ppc_event_preset[4][2][10] = { /* [type][write][event] */ | |
821 | { /* SYSMMU v1.2 */ | |
822 | {5, -1, -1, -1, -1, -1, -1, -1, 1, -1}, | |
823 | {4, -1, -1, -1, -1, -1, -1, -1, 0, -1}, | |
824 | }, | |
825 | { /* SYSMMU v2.x */ | |
826 | {5, 1, 3, -1, -1, -1, -1, -1, -1, -1}, | |
827 | {4, 0, 2, -1, -1, -1, -1, -1, -1, -1}, | |
828 | }, | |
829 | { /* SYSMMU v3.1/2 */ | |
830 | {5, -1, -1, -1, 7, 3, -1, -1, 1, -1}, | |
831 | {4, -1, -1, -1, 6, 2, -1, -1, 0, -1}, | |
832 | }, | |
833 | { /* SYSMMU v3.3 */ | |
834 | {0, -1, -1, 4, 2, 3, 6, 7, 1, 5}, | |
835 | {8, -1, -1, 12, 10, 11, 14, 15, 9, 13}, | |
836 | }, | |
837 | }; | |
838 | unsigned int ver, type, offset; | |
839 | u32 cfg, write = 0; | |
840 | char event_sel; | |
841 | ||
842 | if (event < 0 || event > TOTAL_ID_NUM) | |
843 | return -EINVAL; | |
844 | ||
845 | if (event > READ_FLPD_MISS_PREFETCH) { | |
846 | write = 1; | |
847 | event -= 0x10; | |
848 | } | |
849 | ||
850 | ver = __raw_sysmmu_version(drvdata->sfrbase); | |
851 | ||
852 | offset = (MMU_MAJ_VER(ver) < 2) ? 0x40 : 0x58; | |
853 | ||
854 | if (!(ver == MAKE_MMU_VER(3, 3))) | |
855 | type = MMU_MAJ_VER(ver) - 1; | |
856 | else | |
857 | type = MMU_MAJ_VER(ver); | |
858 | ||
859 | ||
860 | event_sel = ppc_event_preset[type][write][event]; | |
861 | if (event_sel < 0) | |
862 | return -EINVAL; | |
863 | ||
864 | if (!drvdata->event_cnt) | |
865 | __raw_writel(0x1, drvdata->sfrbase + REG_PPC_PMNC); | |
866 | ||
867 | __raw_writel(event_sel, drvdata->sfrbase + | |
868 | REG_PPC_EVENT_SEL(offset, drvdata->event_cnt)); | |
869 | cfg = __raw_readl(drvdata->sfrbase + | |
870 | REG_PPC_CNTENS); | |
871 | __raw_writel(cfg | 0x1 << drvdata->event_cnt, | |
872 | drvdata->sfrbase + REG_PPC_CNTENS); | |
873 | ||
874 | return 0; | |
875 | } |