]> www.pilppa.org Git - linux-2.6-omap-h63xx.git/blob - arch/arm/plat-omap/dsp/ipbuf.c
943943bddb261ea2b45294b399e85c1f0b65f332
[linux-2.6-omap-h63xx.git] / arch / arm / plat-omap / dsp / 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 <asm/arch/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         printk(KERN_INFO
116                "omapdsp: IPBUF configuration\n"
117                "           %d words * %d lines at 0x%p.\n",
118                ipbcfg.lsz, ipbcfg.ln, ipbcfg.base);
119
120         ret = device_create_file(omap_dsp->dev, &dev_attr_ipbuf);
121         if (ret)
122                 printk(KERN_ERR "device_create_file failed: %d\n", ret);
123
124         return ret;
125
126 free_out:
127         kfree(g_ipbuf);
128         g_ipbuf = NULL;
129         return ret;
130 }
131
132 int ipbuf_sys_config(void *p, arm_dsp_dir_t dir)
133 {
134         char *dir_str = (dir == DIR_D2A) ? "D2A" : "A2D";
135
136         if (((unsigned long)p) & 0x3) {
137                 printk(KERN_ERR
138                        "omapdsp: system ipbuf(%s) address(0x%p) is "
139                        "not 32-bit aligned!\n", dir_str, p);
140                 return -1;
141         }
142         if (dsp_address_validate(p, sizeof(struct ipbuf_sys),
143                                  "system ipbuf(%s)", dir_str) < 0)
144                 return -1;
145         if (dsp_mem_type(p, sizeof(struct ipbuf_sys)) != MEM_TYPE_EXTERN) {
146                 printk(KERN_WARNING
147                        "omapdsp: system ipbuf(%s) is placed in"
148                        " DSP internal memory.\n"
149                        "         It will prevent DSP from idling.\n", dir_str);
150                 ipbuf_sys_hold_mem_active++;
151                 /*
152                  * dsp_mem_enable() never fails because
153                  * it has been already enabled in dspcfg process and
154                  * this will just increment the usecount.
155                  */
156                 dsp_mem_enable((void *)daram_base);
157         }
158
159         if (dir == DIR_D2A)
160                 ipbuf_sys_da = p;
161         else
162                 ipbuf_sys_ad = p;
163
164         return 0;
165 }
166
167 int ipbuf_p_validate(void *p, arm_dsp_dir_t dir)
168 {
169         char *dir_str = (dir == DIR_D2A) ? "D2A" : "A2D";
170
171         if (((unsigned long)p) & 0x3) {
172                 printk(KERN_ERR
173                        "omapdsp: private ipbuf(%s) address(0x%p) is "
174                        "not 32-bit aligned!\n", dir_str, p);
175                 return -1;
176         }
177         return dsp_address_validate(p, sizeof(struct ipbuf_p),
178                                     "private ipbuf(%s)", dir_str);
179 }
180
181 /*
182  * Global IPBUF operations
183  */
184 struct ipbuf_head *bid_to_ipbuf(u16 bid)
185 {
186         return &g_ipbuf[bid];
187 }
188
189 struct ipbuf_head *get_free_ipbuf(u8 tid)
190 {
191         struct ipbuf_head *ipb_h;
192
193         if (dsp_mem_enable_ipbuf() < 0)
194                 return NULL;
195
196         spin_lock(&ipb_free.lock);
197
198         if (ipblink_empty(&ipb_free)) {
199                 /* FIXME: wait on queue when not available.  */
200                 ipb_h = NULL;
201                 goto out;
202         }
203         ipb_h = &g_ipbuf[ipb_free.top];
204         ipb_h->p->la = tid;     /* lock */
205         __ipblink_del_top(&ipb_free);
206 out:
207         spin_unlock(&ipb_free.lock);
208         dsp_mem_disable_ipbuf();
209
210         return ipb_h;
211 }
212
213 void release_ipbuf(struct ipbuf_head *ipb_h)
214 {
215         if (ipb_h->p->la == TID_FREE) {
216                 printk(KERN_WARNING
217                        "omapdsp: attempt to release unlocked IPBUF[%d].\n",
218                        ipb_h->bid);
219                 /*
220                  * FIXME: re-calc bsycnt
221                  */
222                 return;
223         }
224         ipb_h->p->la = TID_FREE;
225         ipb_h->p->sa = TID_FREE;
226         ipblink_add_tail(&ipb_free, ipb_h->bid);
227 }
228
229 static int try_yld(struct ipbuf_head *ipb_h)
230 {
231         int status;
232
233         ipb_h->p->sa = TID_ANON;
234         status = mbcompose_send(BKYLD, 0, ipb_h->bid);
235         if (status < 0) {
236                 /* DSP is busy and ARM keeps this line. */
237                 release_ipbuf(ipb_h);
238                 return status;
239         }
240
241         ipb_bsycnt_inc(&ipbcfg);
242         return 0;
243 }
244
245 /*
246  * balancing ipbuf lines with DSP
247  */
248 static void do_balance_ipbuf(struct work_struct *unused)
249 {
250         while (ipbcfg.bsycnt <= ipbcfg.ln / 4) {
251                 struct ipbuf_head *ipb_h;
252
253                 if ((ipb_h = get_free_ipbuf(TID_ANON)) == NULL)
254                         return;
255                 if (try_yld(ipb_h) < 0)
256                         return;
257         }
258 }
259
260 static DECLARE_WORK(balance_ipbuf_work, do_balance_ipbuf);
261
262 void balance_ipbuf(void)
263 {
264         schedule_work(&balance_ipbuf_work);
265 }
266
267 /* for process context */
268 void unuse_ipbuf(struct ipbuf_head *ipb_h)
269 {
270         if (ipbcfg.bsycnt > ipbcfg.ln / 4) {
271                 /* we don't have enough IPBUF lines. let's keep it. */
272                 release_ipbuf(ipb_h);
273         } else {
274                 /* we have enough IPBUF lines. let's return this line to DSP. */
275                 ipb_h->p->la = TID_ANON;
276                 try_yld(ipb_h);
277                 balance_ipbuf();
278         }
279 }
280
281 /* for interrupt context */
282 void unuse_ipbuf_nowait(struct ipbuf_head *ipb_h)
283 {
284         release_ipbuf(ipb_h);
285         balance_ipbuf();
286 }
287
288 /*
289  * functions called from mailbox interrupt routine
290  */
291
292 void mbox_err_ipbfull(void)
293 {
294         ipbcfg.cnt_full++;
295 }
296
297 /*
298  * sysfs files
299  */
300 static ssize_t ipbuf_show(struct device *dev, struct device_attribute *attr,
301                           char *buf)
302 {
303         int len = 0;
304         u16 bid;
305
306         for (bid = 0; bid < ipbcfg.ln; bid++) {
307                 struct ipbuf_head *ipb_h = &g_ipbuf[bid];
308                 u16 la = ipb_h->p->la;
309                 u16 ld = ipb_h->p->ld;
310                 u16 c  = ipb_h->p->c;
311
312                 if (len > PAGE_SIZE - 100) {
313                         len += sprintf(buf + len, "out of buffer.\n");
314                         goto finish;
315                 }
316
317                 len += sprintf(buf + len, "ipbuf[%d]: adr = 0x%p\n",
318                                bid, ipb_h->p);
319                 if (la == TID_FREE) {
320                         len += sprintf(buf + len,
321                                        "  DSPtask[%d]->Linux "
322                                        "(already read and now free for Linux)\n",
323                                        ld);
324                 } else if (ld == TID_FREE) {
325                         len += sprintf(buf + len,
326                                        "  Linux->DSPtask[%d] "
327                                        "(already read and now free for DSP)\n",
328                                        la);
329                 } else if (ipbuf_is_held(ld, bid)) {
330                         len += sprintf(buf + len,
331                                        "  DSPtask[%d]->Linux "
332                                        "(waiting to be read)\n"
333                                        "  count = %d\n", ld, c);
334                 } else {
335                         len += sprintf(buf + len,
336                                        "  Linux->DSPtask[%d] "
337                                        "(waiting to be read)\n"
338                                        "  count = %d\n", la, c);
339                 }
340         }
341
342         len += sprintf(buf + len, "\nFree IPBUF link: ");
343         spin_lock(&ipb_free.lock);
344         ipblink_for_each(bid, &ipb_free) {
345                 len += sprintf(buf + len, "%d ", bid);
346         }
347         spin_unlock(&ipb_free.lock);
348         len += sprintf(buf + len, "\n");
349         len += sprintf(buf + len, "IPBFULL error count: %ld\n",
350                        ipbcfg.cnt_full);
351
352 finish:
353         return len;
354 }