upload tizen1.0 source
[kernel/linux-2.6.36.git] / arch / arm / plat-s5p / sysmmu.c
1 #include <linux/slab.h>
2 #include <linux/module.h>
3 #include <linux/err.h>
4 #include <linux/delay.h>
5 #include <linux/platform_device.h>
6 #include <linux/io.h>
7 #include <linux/irq.h>
8 #include <linux/interrupt.h>
9 #include <asm/pgtable.h>
10 #include <asm/bitops.h>
11
12 #include <plat/s5p-clock.h>
13 #include <mach/map.h>
14
15 #include <plat/sysmmu.h>
16 #include <plat/map-s5p.h>
17 #include <plat/clock.h>
18
19 /* debug macro */
20 #ifdef CONFIG_S5P_SYSMMU_DEBUG
21 static char *sysmmu_fault_name[8] = {
22         "PAGE FAULT",
23         "AR MULTI-HIT FAULT",
24         "AW MULTI-HIT FAULT",
25         "BUS ERROR",
26         "AR SECURITY PROTECTION FAULT",
27         "AR ACCESS PROTECTION FAULT",
28         "AW SECURITY PROTECTION FAULT",
29         "AW ACCESS PROTECTION FAULT"
30 };
31 #define LOG(fmt, arg...) printk(KERN_INFO "[SYSMMU] " fmt "\n", ##arg)
32 #define LOG_IF(cond, fmt, arg...) if (cond) LOG(fmt, ##arg)
33 #define LOGE(fmt, arg...)\
34         printk(KERN_ERR "[SYSMMU:%s] " fmt "\n", __func__, ##arg)
35 #define LOGE_IF(cond, fmt, arg...) if (cond) LOGE(fmt, ##arg)
36 #else
37 #define LOG(fmt, arg...) do { } while (0)
38 #define LOG_IF(cond, fmt, arg...) do { } while (0)
39 #define LOGE(fmt, arg...) do { } while (0)
40 #define LOGE_IF(cond, fmt, arg...) do { } while (0)
41 #endif
42
43 /*
44  *  0 : S5P_PA_SYSMMU_MDMA              (0x10A40000),
45  *  1 : S5P_PA_SYSMMU_SSS               (0x10A50000),
46  *  2 : S5P_PA_SYSMMU_FIMC0             (0x11A20000),
47  *  3 : S5P_PA_SYSMMU_FIMC1             (0x11A30000),
48  *  4 : S5P_PA_SYSMMU_FIMC2             (0x11A40000),
49  *  5 : S5P_PA_SYSMMU_FIMC3             (0x11A50000),
50  *  6 : S5P_PA_SYSMMU_JPEG              (0x11A60000),
51  *  7 : S5P_PA_SYSMMU_FIMD0             (0x11E20000),
52  *  8 : S5P_PA_SYSMMU_FIMD1             (0x12220000),
53  *  9 : S5P_PA_SYSMMU_PCIe              (0x12620000),
54  * 10 : S5P_PA_SYSMMU_G2D               (0x12A20000),
55  * 11 : S5P_PA_SYSMMU_ROTATOR           (0x12A30000),
56  * 12 : S5P_PA_SYSMMU_MDMA2             (0x12A40000),
57  * 13 : S5P_PA_SYSMMU_TV                (0x12E20000),
58  * 14 : S5P_PA_SYSMMU_MFC_L             (0x13620000),
59  * 15 : S5P_PA_SYSMMU_MFC_R             (0x13630000)
60 */
61
62 static unsigned long sysmmu_states; /* 1 bits for each system MMUs */
63
64 static inline int set_sysmmu_active(sysmmu_ips ips)
65 {
66         /* return true if it is not set */
67         return !test_and_set_bit(ips, &sysmmu_states);
68 }
69
70 static inline int set_sysmmu_inactive(sysmmu_ips ips)
71 {
72         /* return true if it is set */
73         return test_and_clear_bit(ips, &sysmmu_states);
74 }
75
76 static inline int is_sysmmu_active(sysmmu_ips ips)
77 {
78         /* BUG_ON(ips >= SYSMMU_TOTAL_IPNUM); */
79         return sysmmu_states & (1 << ips);
80 }
81
82 #if defined(CONFIG_S5P_SYSMMU_DEBUG) || defined(CONFIG_S5P_SYSMMU_PROFILE)
83 /* This character strings must be same with clock names
84  * specified in clock.c */
85 static const char *sysmmu_name[SYSMMU_TOTAL_IPNUM] = {
86         "mmu_mdma",
87         "mmu_sss",
88         "mmu_fimc0",
89         "mmu_fimc1",
90         "mmu_fimc2",
91         "mmu_fimc3",
92         "mmu_jpeg",
93         "mmu_fimd0",
94         "mmu_fimd1",
95         "mmu_pcie",
96         "mmu_g2d",
97         "mmu_rotator",
98         "mmu_mdma2",
99         "mmu_tv",
100         "mmu_mfc_l",
101         "mmu_mfc_r"
102 };
103 #endif
104
105 static void __iomem *sysmmusfrs[SYSMMU_TOTAL_IPNUM];
106
107 #ifdef CONFIG_S5P_SYSMMU_PROFILE
108 #define CTRL_ENABLE     0xD
109 #define CTRL_BLOCK      0xF
110 static inline void sysmmu_start_profile(sysmmu_ips ips)
111 {
112         printk(KERN_INFO "[SYSMMU:%s] Initialized profiling constructs.",
113                         sysmmu_name[ips]);
114         __raw_writel(0x8000000F, sysmmusfrs[ips] + S5P_PPC_INTENS);
115         __raw_writel(0x8000000F, sysmmusfrs[ips] + S5P_PPC_CNTENS);
116         __raw_writel(0x8000000F, sysmmusfrs[ips] + S5P_PPC_FLAG);
117         __raw_writel(0x0, sysmmusfrs[ips] + S5P_PPC_CCNT);
118         __raw_writel(0x6, sysmmusfrs[ips] + S5P_PPC_PMNC);
119
120         __raw_writel(0x1, sysmmusfrs[ips] + S5P_PPC_PMNC); /* START PPC */
121 }
122
123 static inline void sysmmu_stop_profile(sysmmu_ips ips)
124 {
125         __raw_writel(0x0, sysmmusfrs[ips] + S5P_PPC_PMNC); /* STOP PPC */
126 }
127
128 static inline void sysmmu_show_profile(sysmmu_ips ips)
129 {
130         unsigned long ovf;
131
132         printk(KERN_INFO "[SYSMMU:%s] Profiling result while %d cycles:\n",
133                 sysmmu_name[ips], __raw_readl(sysmmusfrs[ips] + S5P_PPC_CCNT));
134
135         ovf = __raw_readl(sysmmusfrs[ips] + S5P_PPC_FLAG);
136         if (!ovf) {
137                 printk(KERN_INFO "[SYSMMU:%s] %d AW misses in %d tranxs.\n",
138                         sysmmu_name[ips],
139                         __raw_readl(sysmmusfrs[ips] + S5P_PPC_PMCNT2),
140                         __raw_readl(sysmmusfrs[ips] + S5P_PPC_PMCNT0));
141                 printk(KERN_INFO "[SYSMMU:%s] %d AR misses in %d tranxs.\n",
142                         sysmmu_name[ips],
143                         __raw_readl(sysmmusfrs[ips] + S5P_PPC_PMCNT3),
144                         __raw_readl(sysmmusfrs[ips] + S5P_PPC_PMCNT1));
145         } else {
146                 printk(KERN_INFO "[SYSMMU:%s] "
147                         "Overflow(%ld) occurred in performance monitor.\n",
148                         sysmmu_name[ips], ovf);
149         }
150 }
151 #else
152 #define CTRL_ENABLE     0x5
153 #define CTRL_BLOCK      0x7
154 #define sysmmu_start_profile(ips) do { } while (0)
155 #define sysmmu_stop_profile(ips) do { } while (0)
156 #define sysmmu_show_profile(ips) do { } while (0)
157 #endif
158 #define CTRL_DISABLE    0x0
159
160 static int (*fault_handlers[SYSMMU_TOTAL_IPNUM])(
161                 enum SYSMMU_INTERRUPT_TYPE itype,
162                 unsigned long pgtable_base,
163                 unsigned long fault_addr);
164
165 static inline void sysmmu_block(sysmmu_ips ips)
166 {
167         __raw_writel(CTRL_BLOCK, sysmmusfrs[ips] + S5P_MMU_CTRL);
168         LOG("%s is blocked.", sysmmu_name[ips]);
169 }
170
171 static inline void sysmmu_unblock(sysmmu_ips ips)
172 {
173         __raw_writel(CTRL_ENABLE, sysmmusfrs[ips] + S5P_MMU_CTRL);
174         LOG("%s is unblocked.", sysmmu_name[ips]);
175 }
176
177 static inline void __sysmmu_tlb_invalidate(sysmmu_ips ips)
178 {
179         __raw_writel(0x1, sysmmusfrs[ips] + S5P_MMU_FLUSH);
180         LOG("TLB of %s is invalidated.", sysmmu_name[ips]);
181 }
182
183 static inline void __sysmmu_set_ptbase(sysmmu_ips ips, unsigned long pgd)
184 {
185         if (unlikely(pgd == 0)) {
186                 pgd = (unsigned long)ZERO_PAGE(0);
187                 __raw_writel(0x20, sysmmusfrs[ips] + S5P_MMU_CFG); /* 4KB LV1 */
188         } else {
189                 __raw_writel(0x0, sysmmusfrs[ips] + S5P_MMU_CFG); /* 16KB LV1 */
190         }
191
192         __raw_writel(pgd, sysmmusfrs[ips] + S5P_PT_BASE_ADDR);
193
194         LOG("Page table base of %s is initialized with 0x%08lX.",
195                                                         sysmmu_name[ips], pgd);
196         __sysmmu_tlb_invalidate(ips);
197 }
198
199 void sysmmu_set_tablebase_pgd(sysmmu_ips ips, unsigned long pgd)
200 {
201         if (is_sysmmu_active(ips)) {
202                 sysmmu_block(ips);
203                 __sysmmu_set_ptbase(ips, pgd);
204                 sysmmu_unblock(ips);
205         } else {
206                 LOG("%s is disabled. Skipping initializing page table base.",
207                                                         sysmmu_name[ips]);
208         }
209 }
210
211 void sysmmu_set_fault_handler(sysmmu_ips ips,
212                         int (*handler)(enum SYSMMU_INTERRUPT_TYPE itype,
213                                         unsigned long pgtable_base,
214                                         unsigned long fault_addr))
215 {
216         BUG_ON(!((ips >= SYSMMU_MDMA) && (ips < SYSMMU_TOTAL_IPNUM)));
217         fault_handlers[ips] = handler;
218 }
219
220 static unsigned short fault_reg_offset[SYSMMU_FAULTS_NUM] = {
221         S5P_PAGE_FAULT_ADDR,
222         S5P_AR_FAULT_ADDR,
223         S5P_AW_FAULT_ADDR,
224         S5P_DEFAULT_SLAVE_ADDR,
225         S5P_AR_FAULT_ADDR,
226         S5P_AR_FAULT_ADDR,
227         S5P_AW_FAULT_ADDR,
228         S5P_AW_FAULT_ADDR
229 };
230
231 /**
232  * sysmmu_irq - [GENERIC] irq service routine
233  * @irq: irq number
234  * @dev_id: pointer to private data
235  *
236  */
237 static irqreturn_t sysmmu_irq(int irq, void *dev_id)
238 {
239         /* SYSMMU is in blocked when interrupt occurred. */
240         unsigned long base = 0;
241         sysmmu_ips ips = (sysmmu_ips)dev_id;
242         enum SYSMMU_INTERRUPT_TYPE itype;
243
244         BUG_ON(!is_sysmmu_active(ips));
245
246         itype = (enum SYSMMU_INTERRUPT_TYPE)
247                 __ffs(__raw_readl(sysmmusfrs[ips] + S5P_INT_STATUS));
248 #ifdef CONFIG_S5P_SYSMMU_PROFILE
249         if (!((itype >= 0) && (itype < 8))) {
250                 unsigned long ovflag;
251
252                 ovflag = __raw_readl(sysmmusfrs[ips] + S5P_PPC_FLAG);
253
254                 BUG_ON(!ovflag);
255                 LOG("Interupt by overflow(flag: 0x%08lX) of PPC counters of %s",
256                                                 ovflag, sysmmu_name[ips]);
257         }
258 #else
259         BUG_ON(!((itype >= 0) && (itype < 8)));
260 #endif
261
262         /* call fault handler */
263         LOGE("%s occurred by %s.", sysmmu_fault_name[itype], sysmmu_name[ips]);
264
265         if (fault_handlers[ips]) {
266                 unsigned long addr;
267
268                 base = __raw_readl(sysmmusfrs[ips] + S5P_PT_BASE_ADDR);
269                 addr = __raw_readl(sysmmusfrs[ips] + fault_reg_offset[itype]);
270
271                 if (fault_handlers[ips](itype, base, addr)) {
272                         __raw_writel(1 << itype,
273                                         sysmmusfrs[ips] + S5P_INT_CLEAR);
274                         LOGE("%s from %s is resolved. Retrying translation.",
275                                 sysmmu_fault_name[itype], sysmmu_name[ips]);
276                 } else {
277                         base = 0;
278                 }
279         }
280
281         sysmmu_unblock(ips);
282
283         if (!base)
284                 LOGE("%s from %s is not handled.",
285                         sysmmu_fault_name[itype], sysmmu_name[ips]);
286
287         return IRQ_HANDLED;
288 }
289
290 #ifdef CONFIG_S5P_SYSMMU_CLKGATING
291 static struct clk *sysmmu_clk[SYSMMU_TOTAL_IPNUM];
292
293 static inline void sysmmu_clk_init(sysmmu_ips ips, struct device *dev)
294 {
295         sysmmu_clk[ips] = clk_get(dev, "sysmmu");
296         if (IS_ERR(sysmmu_clk[ips]))
297                 sysmmu_clk[ips] = NULL;
298
299 }
300 static inline void sysmmu_clk_enable(sysmmu_ips ips)
301 {
302         if (sysmmu_clk[ips])
303                 clk_enable(sysmmu_clk[ips]);
304 }
305
306 static inline void sysmmu_clk_disable(sysmmu_ips ips)
307 {
308         if (sysmmu_clk[ips])
309                 clk_disable(sysmmu_clk[ips]);
310 }
311 #else
312 #define sysmmu_clk_init(ips, dev) do { } while (0)
313 #define sysmmu_clk_enable(ips) do { } while (0)
314 #define sysmmu_clk_disable(ips) do { } while (0)
315 #endif
316
317 void sysmmu_on(sysmmu_ips ips, unsigned long pgd)
318 {
319         LOG_IF(is_sysmmu_active(ips),
320                         "%s is already enabled.", sysmmu_name[ips]);
321
322         if (set_sysmmu_active(ips)) {
323                 sysmmu_clk_enable(ips);
324
325                 __sysmmu_set_ptbase(ips, pgd);
326
327                 __raw_writel(CTRL_ENABLE, sysmmusfrs[ips] + S5P_MMU_CTRL);
328
329                 sysmmu_start_profile(ips);
330                 LOG("%s is enabled.", sysmmu_name[ips]);
331         }
332 }
333
334 void sysmmu_off(sysmmu_ips ips)
335 {
336         LOG_IF(!is_sysmmu_active(ips),
337                         "%s is already disabled.", sysmmu_name[ips]);
338         if (set_sysmmu_inactive(ips)) {
339                 __raw_writel(CTRL_DISABLE, sysmmusfrs[ips] + S5P_MMU_CTRL);
340
341                 sysmmu_stop_profile(ips);
342                 sysmmu_show_profile(ips);
343
344                 sysmmu_clk_disable(ips);
345                 LOG("%s is disabled.", sysmmu_name[ips]);
346         }
347 }
348
349 void sysmmu_tlb_invalidate(sysmmu_ips ips)
350 {
351         LOG_IF(!is_sysmmu_active(ips),
352                 "%s is disabled. Skipping invalidating TLB.", sysmmu_name[ips]);
353         if (is_sysmmu_active(ips)) {
354                 sysmmu_block(ips);
355                 __sysmmu_tlb_invalidate(ips);
356                 sysmmu_unblock(ips);
357         }
358 }
359
360 static int sysmmu_probe(struct platform_device *pdev)
361 {
362         sysmmu_ips id;
363         struct resource *res, *ioarea;
364         int ret;
365         int irq;
366
367         res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
368         if (!res) {
369                 LOGE("Failed probing system MMU: "
370                                                 "failed to get resource.");
371                 return -ENOENT;
372         }
373
374         id = (sysmmu_ips)pdev->id;
375         if (id >= SYSMMU_TOTAL_IPNUM) {
376                 LOGE("Unknown System MMU ID %d.", id);
377                 return -ENOENT;
378         }
379
380         ioarea = request_mem_region(res->start, resource_size(res), pdev->name);
381         if (ioarea == NULL) {
382                 LOGE("Failed probing system MMU: "
383                                         "failed to request memory region.");
384                 return -ENOMEM;
385         }
386
387         sysmmusfrs[id] = ioremap(res->start, resource_size(res));
388         if (!sysmmusfrs[id]) {
389                 LOGE("Failed probing system MMU: "
390                                                 "failed to call ioremap().");
391                 ret = -ENOENT;
392                 goto err_ioremap;
393         }
394
395         irq = platform_get_irq(pdev, 0);
396         if (irq <= 0) {
397                 LOGE("Failed probing system MMU: "
398                                                 "failed to get irq resource.");
399                 ret = irq;
400                 goto err_irq;
401         }
402
403         if (request_irq(irq, sysmmu_irq, 0, dev_name(&pdev->dev), (void *)id)) {
404                 LOGE("Failed probing system MMU: "
405                                                 "failed to request irq.");
406                 ret = -ENOENT;
407                 goto err_irq;
408         }
409
410         sysmmu_clk_init(id, &pdev->dev);
411
412         LOG("Probing system MMU succeeded.");
413         return 0;
414
415 err_irq:
416         iounmap(sysmmusfrs[id]);
417 err_ioremap:
418         release_resource(ioarea);
419         kfree(ioarea);
420         LOG("Probing system MMU failed.");
421         return ret;
422 }
423
424 static int sysmmu_remove(struct platform_device *pdev)
425 {
426         return 0;
427 }
428
429 static int sysmmu_suspend(struct platform_device *pdev, pm_message_t state)
430 {
431         return 0;
432 }
433
434 static int sysmmu_resume(struct platform_device *pdev)
435 {
436         int ret = 0;
437
438         return ret;
439 }
440
441 static struct platform_driver s5p_sysmmu_driver = {
442         .probe = sysmmu_probe,
443         .remove = sysmmu_remove,
444         .suspend = sysmmu_suspend,
445         .resume = sysmmu_resume,
446         .driver = {
447                 .owner = THIS_MODULE,
448                 .name = "s5p-sysmmu",
449         },
450 };
451
452 static int __init sysmmu_register(void)
453 {
454         int ret;
455
456         ret = platform_driver_register(&s5p_sysmmu_driver);
457         LOGE_IF(ret, "Failed to register system MMU driver.");
458         return ret;
459 }
460
461 static void __exit sysmmu_unregister(void)
462 {
463         platform_driver_unregister(&s5p_sysmmu_driver);
464 }
465
466 module_init(sysmmu_register);
467 module_exit(sysmmu_unregister);
468
469 MODULE_AUTHOR("Donguk Ryu <du.ryu@samsung.com>");
470 MODULE_DESCRIPTION("Samsung System MMU driver");
471 MODULE_LICENSE("GPL");