Merge current mainline tree into linux-omap tree
[pandora-kernel.git] / drivers / dsp / dspgateway / ipbuf.c
1 /*
2  * This file is part of OMAP DSP driver (DSP Gateway version 3.3.1)
3  *
4  * Copyright (C) 2002-2006 Nokia Corporation. All rights reserved.
5  *
6  * Contact: Toshihiro Kobayashi <toshihiro.kobayashi@nokia.com>
7  *
8  * This program is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU General Public License
10  * version 2 as published by the Free Software Foundation.
11  *
12  * This program is distributed in the hope that it will be useful, but
13  * WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
20  * 02110-1301 USA
21  *
22  */
23
24 #include <linux/sched.h>
25 #include <linux/device.h>
26 #include <linux/interrupt.h>
27 #include <mach/mailbox.h>
28 #include "dsp_mbcmd.h"
29 #include "dsp.h"
30 #include "ipbuf.h"
31
32 static struct ipbuf_head *g_ipbuf;
33 struct ipbcfg ipbcfg;
34 struct ipbuf_sys *ipbuf_sys_da, *ipbuf_sys_ad;
35 static struct ipblink ipb_free = IPBLINK_INIT;
36 static int ipbuf_sys_hold_mem_active;
37
38 static ssize_t ipbuf_show(struct device *dev, struct device_attribute *attr,
39                           char *buf);
40 static struct device_attribute dev_attr_ipbuf = __ATTR_RO(ipbuf);
41
42 void ipbuf_stop(void)
43 {
44         int i;
45
46         device_remove_file(omap_dsp->dev, &dev_attr_ipbuf);
47
48         spin_lock(&ipb_free.lock);
49         RESET_IPBLINK(&ipb_free);
50         spin_unlock(&ipb_free.lock);
51
52         ipbcfg.ln = 0;
53         if (g_ipbuf) {
54                 kfree(g_ipbuf);
55                 g_ipbuf = NULL;
56         }
57         for (i = 0; i < ipbuf_sys_hold_mem_active; i++) {
58                 dsp_mem_disable((void *)daram_base);
59         }
60         ipbuf_sys_hold_mem_active = 0;
61 }
62
63 int ipbuf_config(u16 ln, u16 lsz, void *base)
64 {
65         size_t lsz_byte = ((size_t)lsz) << 1;
66         size_t size;
67         int ret = 0;
68         int i;
69
70         /*
71          * global IPBUF
72          */
73         if (((unsigned long)base) & 0x3) {
74                 printk(KERN_ERR
75                        "omapdsp: global ipbuf address(0x%p) is not "
76                        "32-bit aligned!\n", base);
77                 return -EINVAL;
78         }
79         size = lsz_byte * ln;
80         if (dsp_address_validate(base, size, "global ipbuf") < 0)
81                 return -EINVAL;
82
83         g_ipbuf = kmalloc(sizeof(struct ipbuf_head) * ln, GFP_KERNEL);
84         if (g_ipbuf == NULL) {
85                 printk(KERN_ERR
86                        "omapdsp: memory allocation for ipbuf failed.\n");
87                 return -ENOMEM;
88         }
89         for (i = 0; i < ln; i++) {
90                 void *top, *btm;
91
92                 top = base + (sizeof(struct ipbuf) + lsz_byte) * i;
93                 btm = base + (sizeof(struct ipbuf) + lsz_byte) * (i+1) - 1;
94                 g_ipbuf[i].p = (struct ipbuf *)top;
95                 g_ipbuf[i].bid = i;
96                 if (((unsigned long)top & 0xfffe0000) !=
97                     ((unsigned long)btm & 0xfffe0000)) {
98                         /*
99                          * an ipbuf line should not cross
100                          * 64k-word boundary.
101                          */
102                         printk(KERN_ERR
103                                "omapdsp: ipbuf[%d] crosses 64k-word boundary!\n"
104                                "  @0x%p, size=0x%08x\n", i, top, lsz_byte);
105                         ret = -EINVAL;
106                         goto free_out;
107                 }
108         }
109         ipbcfg.ln       = ln;
110         ipbcfg.lsz      = lsz;
111         ipbcfg.base     = base;
112         ipbcfg.bsycnt   = ln;   /* DSP holds all ipbufs initially. */
113         ipbcfg.cnt_full = 0;
114
115         pr_info("omapdsp: IPBUF configuration\n"
116                 "           %d words * %d lines at 0x%p.\n",
117                 ipbcfg.lsz, ipbcfg.ln, ipbcfg.base);
118
119         ret = device_create_file(omap_dsp->dev, &dev_attr_ipbuf);
120         if (ret)
121                 printk(KERN_ERR "device_create_file failed: %d\n", ret);
122
123         return ret;
124
125  free_out:
126         kfree(g_ipbuf);
127         g_ipbuf = NULL;
128         return ret;
129 }
130
131 int ipbuf_sys_config(void *p, arm_dsp_dir_t dir)
132 {
133         char *dir_str = (dir == DIR_D2A) ? "D2A" : "A2D";
134
135         if (((unsigned long)p) & 0x3) {
136                 printk(KERN_ERR
137                        "omapdsp: system ipbuf(%s) address(0x%p) is "
138                        "not 32-bit aligned!\n", dir_str, p);
139                 return -1;
140         }
141         if (dsp_address_validate(p, sizeof(struct ipbuf_sys),
142                                  "system ipbuf(%s)", dir_str) < 0)
143                 return -1;
144         if (dsp_mem_type(p, sizeof(struct ipbuf_sys)) != MEM_TYPE_EXTERN) {
145                 printk(KERN_WARNING
146                        "omapdsp: system ipbuf(%s) is placed in"
147                        " DSP internal memory.\n"
148                        "         It will prevent DSP from idling.\n", dir_str);
149                 ipbuf_sys_hold_mem_active++;
150                 /*
151                  * dsp_mem_enable() never fails because
152                  * it has been already enabled in dspcfg process and
153                  * this will just increment the usecount.
154                  */
155                 dsp_mem_enable((void *)daram_base);
156         }
157
158         if (dir == DIR_D2A)
159                 ipbuf_sys_da = p;
160         else
161                 ipbuf_sys_ad = p;
162
163         return 0;
164 }
165
166 int ipbuf_p_validate(void *p, arm_dsp_dir_t dir)
167 {
168         char *dir_str = (dir == DIR_D2A) ? "D2A" : "A2D";
169
170         if (((unsigned long)p) & 0x3) {
171                 printk(KERN_ERR
172                        "omapdsp: private ipbuf(%s) address(0x%p) is "
173                        "not 32-bit aligned!\n", dir_str, p);
174                 return -1;
175         }
176         return dsp_address_validate(p, sizeof(struct ipbuf_p),
177                                     "private ipbuf(%s)", dir_str);
178 }
179
180 /*
181  * Global IPBUF operations
182  */
183 struct ipbuf_head *bid_to_ipbuf(u16 bid)
184 {
185         return &g_ipbuf[bid];
186 }
187
188 struct ipbuf_head *get_free_ipbuf(u8 tid)
189 {
190         struct ipbuf_head *ipb_h;
191
192         if (dsp_mem_enable_ipbuf() < 0)
193                 return NULL;
194
195         spin_lock(&ipb_free.lock);
196
197         if (ipblink_empty(&ipb_free)) {
198                 /* FIXME: wait on queue when not available.  */
199                 ipb_h = NULL;
200                 goto out;
201         }
202         ipb_h = &g_ipbuf[ipb_free.top];
203         ipb_h->p->la = tid;     /* lock */
204         __ipblink_del_top(&ipb_free);
205 out:
206         spin_unlock(&ipb_free.lock);
207         dsp_mem_disable_ipbuf();
208
209         return ipb_h;
210 }
211
212 void release_ipbuf(struct ipbuf_head *ipb_h)
213 {
214         if (ipb_h->p->la == TID_FREE) {
215                 printk(KERN_WARNING
216                        "omapdsp: attempt to release unlocked IPBUF[%d].\n",
217                        ipb_h->bid);
218                 /*
219                  * FIXME: re-calc bsycnt
220                  */
221                 return;
222         }
223         ipb_h->p->la = TID_FREE;
224         ipb_h->p->sa = TID_FREE;
225         ipblink_add_tail(&ipb_free, ipb_h->bid);
226 }
227
228 static int try_yld(struct ipbuf_head *ipb_h)
229 {
230         int status;
231
232         ipb_h->p->sa = TID_ANON;
233         status = mbcompose_send(BKYLD, 0, ipb_h->bid);
234         if (status < 0) {
235                 /* DSP is busy and ARM keeps this line. */
236                 release_ipbuf(ipb_h);
237                 return status;
238         }
239
240         ipb_bsycnt_inc(&ipbcfg);
241         return 0;
242 }
243
244 /*
245  * balancing ipbuf lines with DSP
246  */
247 static void do_balance_ipbuf(struct work_struct *unused)
248 {
249         while (ipbcfg.bsycnt <= ipbcfg.ln / 4) {
250                 struct ipbuf_head *ipb_h;
251
252                 if ((ipb_h = get_free_ipbuf(TID_ANON)) == NULL)
253                         return;
254                 if (try_yld(ipb_h) < 0)
255                         return;
256         }
257 }
258
259 static DECLARE_WORK(balance_ipbuf_work, do_balance_ipbuf);
260
261 void balance_ipbuf(void)
262 {
263         schedule_work(&balance_ipbuf_work);
264 }
265
266 /* for process context */
267 void unuse_ipbuf(struct ipbuf_head *ipb_h)
268 {
269         if (ipbcfg.bsycnt > ipbcfg.ln / 4) {
270                 /* we don't have enough IPBUF lines. let's keep it. */
271                 release_ipbuf(ipb_h);
272         } else {
273                 /* we have enough IPBUF lines. let's return this line to DSP. */
274                 ipb_h->p->la = TID_ANON;
275                 try_yld(ipb_h);
276                 balance_ipbuf();
277         }
278 }
279
280 /* for interrupt context */
281 void unuse_ipbuf_nowait(struct ipbuf_head *ipb_h)
282 {
283         release_ipbuf(ipb_h);
284         balance_ipbuf();
285 }
286
287 /*
288  * functions called from mailbox interrupt routine
289  */
290
291 void mbox_err_ipbfull(void)
292 {
293         ipbcfg.cnt_full++;
294 }
295
296 /*
297  * sysfs files
298  */
299 static ssize_t ipbuf_show(struct device *dev, struct device_attribute *attr,
300                           char *buf)
301 {
302         int len = 0;
303         u16 bid;
304
305         for (bid = 0; bid < ipbcfg.ln; bid++) {
306                 struct ipbuf_head *ipb_h = &g_ipbuf[bid];
307                 u16 la = ipb_h->p->la;
308                 u16 ld = ipb_h->p->ld;
309                 u16 c  = ipb_h->p->c;
310
311                 if (len > PAGE_SIZE - 100) {
312                         len += sprintf(buf + len, "out of buffer.\n");
313                         goto finish;
314                 }
315
316                 len += sprintf(buf + len, "ipbuf[%d]: adr = 0x%p\n",
317                                bid, ipb_h->p);
318                 if (la == TID_FREE) {
319                         len += sprintf(buf + len,
320                                        "  DSPtask[%d]->Linux "
321                                        "(already read and now free for Linux)\n",
322                                        ld);
323                 } else if (ld == TID_FREE) {
324                         len += sprintf(buf + len,
325                                        "  Linux->DSPtask[%d] "
326                                        "(already read and now free for DSP)\n",
327                                        la);
328                 } else if (ipbuf_is_held(ld, bid)) {
329                         len += sprintf(buf + len,
330                                        "  DSPtask[%d]->Linux "
331                                        "(waiting to be read)\n"
332                                        "  count = %d\n", ld, c);
333                 } else {
334                         len += sprintf(buf + len,
335                                        "  Linux->DSPtask[%d] "
336                                        "(waiting to be read)\n"
337                                        "  count = %d\n", la, c);
338                 }
339         }
340
341         len += sprintf(buf + len, "\nFree IPBUF link: ");
342         spin_lock(&ipb_free.lock);
343         ipblink_for_each(bid, &ipb_free) {
344                 len += sprintf(buf + len, "%d ", bid);
345         }
346         spin_unlock(&ipb_free.lock);
347         len += sprintf(buf + len, "\n");
348         len += sprintf(buf + len, "IPBFULL error count: %ld\n",
349                        ipbcfg.cnt_full);
350
351 finish:
352         return len;
353 }