Merge branch 'staging-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh...
[pandora-kernel.git] / drivers / staging / tidspbridge / core / ue_deh.c
1 /*
2  * ue_deh.c
3  *
4  * DSP-BIOS Bridge driver support functions for TI OMAP processors.
5  *
6  * Implements upper edge DSP exception handling (DEH) functions.
7  *
8  * Copyright (C) 2005-2006 Texas Instruments, Inc.
9  * Copyright (C) 2010 Felipe Contreras
10  *
11  * This package is free software; you can redistribute it and/or modify
12  * it under the terms of the GNU General Public License version 2 as
13  * published by the Free Software Foundation.
14  *
15  * THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
16  * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
17  * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
18  */
19
20 #include <linux/kernel.h>
21 #include <linux/interrupt.h>
22 #include <plat/dmtimer.h>
23
24 #include <dspbridge/dbdefs.h>
25 #include <dspbridge/dspdeh.h>
26 #include <dspbridge/dev.h>
27 #include "_tiomap.h"
28 #include "_deh.h"
29
30 #include <dspbridge/io_sm.h>
31 #include <dspbridge/drv.h>
32 #include <dspbridge/wdt.h>
33
34 static u32 fault_addr;
35
36 static void mmu_fault_dpc(unsigned long data)
37 {
38         struct deh_mgr *deh = (void *)data;
39
40         if (!deh)
41                 return;
42
43         bridge_deh_notify(deh, DSP_MMUFAULT, 0);
44 }
45
46 static irqreturn_t mmu_fault_isr(int irq, void *data)
47 {
48         struct deh_mgr *deh = data;
49         struct cfg_hostres *resources;
50         u32 event;
51
52         if (!deh)
53                 return IRQ_HANDLED;
54
55         resources = deh->hbridge_context->resources;
56         if (!resources) {
57                 dev_dbg(bridge, "%s: Failed to get Host Resources\n",
58                                 __func__);
59                 return IRQ_HANDLED;
60         }
61
62         hw_mmu_event_status(resources->dw_dmmu_base, &event);
63         if (event == HW_MMU_TRANSLATION_FAULT) {
64                 hw_mmu_fault_addr_read(resources->dw_dmmu_base, &fault_addr);
65                 dev_dbg(bridge, "%s: event=0x%x, fault_addr=0x%x\n", __func__,
66                                 event, fault_addr);
67                 /*
68                  * Schedule a DPC directly. In the future, it may be
69                  * necessary to check if DSP MMU fault is intended for
70                  * Bridge.
71                  */
72                 tasklet_schedule(&deh->dpc_tasklet);
73
74                 /* Disable the MMU events, else once we clear it will
75                  * start to raise INTs again */
76                 hw_mmu_event_disable(resources->dw_dmmu_base,
77                                 HW_MMU_TRANSLATION_FAULT);
78         } else {
79                 hw_mmu_event_disable(resources->dw_dmmu_base,
80                                 HW_MMU_ALL_INTERRUPTS);
81         }
82         return IRQ_HANDLED;
83 }
84
85 int bridge_deh_create(struct deh_mgr **ret_deh,
86                 struct dev_object *hdev_obj)
87 {
88         int status;
89         struct deh_mgr *deh;
90         struct bridge_dev_context *hbridge_context = NULL;
91
92         /*  Message manager will be created when a file is loaded, since
93          *  size of message buffer in shared memory is configurable in
94          *  the base image. */
95         /* Get Bridge context info. */
96         dev_get_bridge_context(hdev_obj, &hbridge_context);
97         /* Allocate IO manager object: */
98         deh = kzalloc(sizeof(*deh), GFP_KERNEL);
99         if (!deh) {
100                 status = -ENOMEM;
101                 goto err;
102         }
103
104         /* Create an NTFY object to manage notifications */
105         deh->ntfy_obj = kmalloc(sizeof(struct ntfy_object), GFP_KERNEL);
106         if (!deh->ntfy_obj) {
107                 status = -ENOMEM;
108                 goto err;
109         }
110         ntfy_init(deh->ntfy_obj);
111
112         /* Create a MMUfault DPC */
113         tasklet_init(&deh->dpc_tasklet, mmu_fault_dpc, (u32) deh);
114
115         /* Fill in context structure */
116         deh->hbridge_context = hbridge_context;
117
118         /* Install ISR function for DSP MMU fault */
119         status = request_irq(INT_DSP_MMU_IRQ, mmu_fault_isr, 0,
120                         "DspBridge\tiommu fault", deh);
121         if (status < 0)
122                 goto err;
123
124         *ret_deh = deh;
125         return 0;
126
127 err:
128         bridge_deh_destroy(deh);
129         *ret_deh = NULL;
130         return status;
131 }
132
133 int bridge_deh_destroy(struct deh_mgr *deh)
134 {
135         if (!deh)
136                 return -EFAULT;
137
138         /* If notification object exists, delete it */
139         if (deh->ntfy_obj) {
140                 ntfy_delete(deh->ntfy_obj);
141                 kfree(deh->ntfy_obj);
142         }
143         /* Disable DSP MMU fault */
144         free_irq(INT_DSP_MMU_IRQ, deh);
145
146         /* Free DPC object */
147         tasklet_kill(&deh->dpc_tasklet);
148
149         /* Deallocate the DEH manager object */
150         kfree(deh);
151
152         return 0;
153 }
154
155 int bridge_deh_register_notify(struct deh_mgr *deh, u32 event_mask,
156                 u32 notify_type,
157                 struct dsp_notification *hnotification)
158 {
159         if (!deh)
160                 return -EFAULT;
161
162         if (event_mask)
163                 return ntfy_register(deh->ntfy_obj, hnotification,
164                                 event_mask, notify_type);
165         else
166                 return ntfy_unregister(deh->ntfy_obj, hnotification);
167 }
168
169 #ifdef CONFIG_TIDSPBRIDGE_BACKTRACE
170 static void mmu_fault_print_stack(struct bridge_dev_context *dev_context)
171 {
172         struct cfg_hostres *resources;
173         struct hw_mmu_map_attrs_t map_attrs = {
174                 .endianism = HW_LITTLE_ENDIAN,
175                 .element_size = HW_ELEM_SIZE16BIT,
176                 .mixed_size = HW_MMU_CPUES,
177         };
178         void *dummy_va_addr;
179
180         resources = dev_context->resources;
181         dummy_va_addr = (void*)__get_free_page(GFP_ATOMIC);
182
183         /*
184          * Before acking the MMU fault, let's make sure MMU can only
185          * access entry #0. Then add a new entry so that the DSP OS
186          * can continue in order to dump the stack.
187          */
188         hw_mmu_twl_disable(resources->dw_dmmu_base);
189         hw_mmu_tlb_flush_all(resources->dw_dmmu_base);
190
191         hw_mmu_tlb_add(resources->dw_dmmu_base,
192                         virt_to_phys(dummy_va_addr), fault_addr,
193                         HW_PAGE_SIZE4KB, 1,
194                         &map_attrs, HW_SET, HW_SET);
195
196         dsp_clk_enable(DSP_CLK_GPT8);
197
198         dsp_gpt_wait_overflow(DSP_CLK_GPT8, 0xfffffffe);
199
200         /* Clear MMU interrupt */
201         hw_mmu_event_ack(resources->dw_dmmu_base,
202                         HW_MMU_TRANSLATION_FAULT);
203         dump_dsp_stack(dev_context);
204         dsp_clk_disable(DSP_CLK_GPT8);
205
206         hw_mmu_disable(resources->dw_dmmu_base);
207         free_page((unsigned long)dummy_va_addr);
208 }
209 #endif
210
211 static inline const char *event_to_string(int event)
212 {
213         switch (event) {
214         case DSP_SYSERROR: return "DSP_SYSERROR"; break;
215         case DSP_MMUFAULT: return "DSP_MMUFAULT"; break;
216         case DSP_PWRERROR: return "DSP_PWRERROR"; break;
217         case DSP_WDTOVERFLOW: return "DSP_WDTOVERFLOW"; break;
218         default: return "unkown event"; break;
219         }
220 }
221
222 void bridge_deh_notify(struct deh_mgr *deh, int event, int info)
223 {
224         struct bridge_dev_context *dev_context;
225         const char *str = event_to_string(event);
226
227         if (!deh)
228                 return;
229
230         dev_dbg(bridge, "%s: device exception", __func__);
231         dev_context = deh->hbridge_context;
232
233         switch (event) {
234         case DSP_SYSERROR:
235                 dev_err(bridge, "%s: %s, info=0x%x", __func__,
236                                 str, info);
237 #ifdef CONFIG_TIDSPBRIDGE_BACKTRACE
238                 dump_dl_modules(dev_context);
239                 dump_dsp_stack(dev_context);
240 #endif
241                 break;
242         case DSP_MMUFAULT:
243                 dev_err(bridge, "%s: %s, addr=0x%x", __func__,
244                                 str, fault_addr);
245 #ifdef CONFIG_TIDSPBRIDGE_BACKTRACE
246                 print_dsp_trace_buffer(dev_context);
247                 dump_dl_modules(dev_context);
248                 mmu_fault_print_stack(dev_context);
249 #endif
250                 break;
251         default:
252                 dev_err(bridge, "%s: %s", __func__, str);
253                 break;
254         }
255
256         /* Filter subsequent notifications when an error occurs */
257         if (dev_context->dw_brd_state != BRD_ERROR) {
258                 ntfy_notify(deh->ntfy_obj, event);
259 #ifdef CONFIG_TIDSPBRIDGE_RECOVERY
260                 bridge_recover_schedule();
261 #endif
262         }
263
264         /* Set the Board state as ERROR */
265         dev_context->dw_brd_state = BRD_ERROR;
266         /* Disable all the clocks that were enabled by DSP */
267         dsp_clock_disable_all(dev_context->dsp_per_clks);
268         /*
269          * Avoid the subsequent WDT if it happens once,
270          * also if fatal error occurs.
271          */
272         dsp_wdt_enable(false);
273 }