diff options
Diffstat (limited to 'sound/aoa/soundbus/i2sbus/i2sbus-core.c')
-rw-r--r-- | sound/aoa/soundbus/i2sbus/i2sbus-core.c | 387 |
1 files changed, 387 insertions, 0 deletions
diff --git a/sound/aoa/soundbus/i2sbus/i2sbus-core.c b/sound/aoa/soundbus/i2sbus/i2sbus-core.c new file mode 100644 index 00000000000..f268dacdaa0 --- /dev/null +++ b/sound/aoa/soundbus/i2sbus/i2sbus-core.c | |||
@@ -0,0 +1,387 @@ | |||
1 | /* | ||
2 | * i2sbus driver | ||
3 | * | ||
4 | * Copyright 2006 Johannes Berg <johannes@sipsolutions.net> | ||
5 | * | ||
6 | * GPL v2, can be found in COPYING. | ||
7 | */ | ||
8 | |||
9 | #include <linux/module.h> | ||
10 | #include <asm/macio.h> | ||
11 | #include <asm/dbdma.h> | ||
12 | #include <linux/pci.h> | ||
13 | #include <linux/interrupt.h> | ||
14 | #include <sound/driver.h> | ||
15 | #include <sound/core.h> | ||
16 | #include <linux/dma-mapping.h> | ||
17 | #include "../soundbus.h" | ||
18 | #include "i2sbus.h" | ||
19 | |||
20 | MODULE_LICENSE("GPL"); | ||
21 | MODULE_AUTHOR("Johannes Berg <johannes@sipsolutions.net>"); | ||
22 | MODULE_DESCRIPTION("Apple Soundbus: I2S support"); | ||
23 | /* for auto-loading, declare that we handle this weird | ||
24 | * string that macio puts into the relevant device */ | ||
25 | MODULE_ALIAS("of:Ni2sTi2sC"); | ||
26 | |||
27 | static struct of_device_id i2sbus_match[] = { | ||
28 | { .name = "i2s" }, | ||
29 | { } | ||
30 | }; | ||
31 | |||
32 | static int alloc_dbdma_descriptor_ring(struct i2sbus_dev *i2sdev, | ||
33 | struct dbdma_command_mem *r, | ||
34 | int numcmds) | ||
35 | { | ||
36 | /* one more for rounding */ | ||
37 | r->size = (numcmds+1) * sizeof(struct dbdma_cmd); | ||
38 | /* We use the PCI APIs for now until the generic one gets fixed | ||
39 | * enough or until we get some macio-specific versions | ||
40 | */ | ||
41 | r->space = dma_alloc_coherent( | ||
42 | &macio_get_pci_dev(i2sdev->macio)->dev, | ||
43 | r->size, | ||
44 | &r->bus_addr, | ||
45 | GFP_KERNEL); | ||
46 | |||
47 | if (!r->space) return -ENOMEM; | ||
48 | |||
49 | memset(r->space, 0, r->size); | ||
50 | r->cmds = (void*)DBDMA_ALIGN(r->space); | ||
51 | r->bus_cmd_start = r->bus_addr + | ||
52 | (dma_addr_t)((char*)r->cmds - (char*)r->space); | ||
53 | |||
54 | return 0; | ||
55 | } | ||
56 | |||
57 | static void free_dbdma_descriptor_ring(struct i2sbus_dev *i2sdev, | ||
58 | struct dbdma_command_mem *r) | ||
59 | { | ||
60 | if (!r->space) return; | ||
61 | |||
62 | dma_free_coherent(&macio_get_pci_dev(i2sdev->macio)->dev, | ||
63 | r->size, r->space, r->bus_addr); | ||
64 | } | ||
65 | |||
66 | static void i2sbus_release_dev(struct device *dev) | ||
67 | { | ||
68 | struct i2sbus_dev *i2sdev; | ||
69 | int i; | ||
70 | |||
71 | i2sdev = container_of(dev, struct i2sbus_dev, sound.ofdev.dev); | ||
72 | |||
73 | if (i2sdev->intfregs) iounmap(i2sdev->intfregs); | ||
74 | if (i2sdev->out.dbdma) iounmap(i2sdev->out.dbdma); | ||
75 | if (i2sdev->in.dbdma) iounmap(i2sdev->in.dbdma); | ||
76 | for (i=0;i<3;i++) | ||
77 | if (i2sdev->allocated_resource[i]) | ||
78 | release_and_free_resource(i2sdev->allocated_resource[i]); | ||
79 | free_dbdma_descriptor_ring(i2sdev, &i2sdev->out.dbdma_ring); | ||
80 | free_dbdma_descriptor_ring(i2sdev, &i2sdev->in.dbdma_ring); | ||
81 | for (i=0;i<3;i++) | ||
82 | free_irq(i2sdev->interrupts[i], i2sdev); | ||
83 | i2sbus_control_remove_dev(i2sdev->control, i2sdev); | ||
84 | mutex_destroy(&i2sdev->lock); | ||
85 | kfree(i2sdev); | ||
86 | } | ||
87 | |||
88 | static irqreturn_t i2sbus_bus_intr(int irq, void *devid, struct pt_regs *regs) | ||
89 | { | ||
90 | struct i2sbus_dev *dev = devid; | ||
91 | u32 intreg; | ||
92 | |||
93 | spin_lock(&dev->low_lock); | ||
94 | intreg = in_le32(&dev->intfregs->intr_ctl); | ||
95 | |||
96 | /* acknowledge interrupt reasons */ | ||
97 | out_le32(&dev->intfregs->intr_ctl, intreg); | ||
98 | |||
99 | spin_unlock(&dev->low_lock); | ||
100 | |||
101 | return IRQ_HANDLED; | ||
102 | } | ||
103 | |||
104 | static int force; | ||
105 | module_param(force, int, 0444); | ||
106 | MODULE_PARM_DESC(force, "Force loading i2sbus even when" | ||
107 | " no layout-id property is present"); | ||
108 | |||
109 | /* FIXME: look at device node refcounting */ | ||
110 | static int i2sbus_add_dev(struct macio_dev *macio, | ||
111 | struct i2sbus_control *control, | ||
112 | struct device_node *np) | ||
113 | { | ||
114 | struct i2sbus_dev *dev; | ||
115 | struct device_node *child = NULL, *sound = NULL; | ||
116 | int i; | ||
117 | static const char *rnames[] = { "i2sbus: %s (control)", | ||
118 | "i2sbus: %s (tx)", | ||
119 | "i2sbus: %s (rx)" }; | ||
120 | static irqreturn_t (*ints[])(int irq, void *devid, | ||
121 | struct pt_regs *regs) = { | ||
122 | i2sbus_bus_intr, | ||
123 | i2sbus_tx_intr, | ||
124 | i2sbus_rx_intr | ||
125 | }; | ||
126 | |||
127 | if (strlen(np->name) != 5) | ||
128 | return 0; | ||
129 | if (strncmp(np->name, "i2s-", 4)) | ||
130 | return 0; | ||
131 | |||
132 | if (np->n_intrs != 3) | ||
133 | return 0; | ||
134 | |||
135 | dev = kzalloc(sizeof(struct i2sbus_dev), GFP_KERNEL); | ||
136 | if (!dev) | ||
137 | return 0; | ||
138 | |||
139 | i = 0; | ||
140 | while ((child = of_get_next_child(np, child))) { | ||
141 | if (strcmp(child->name, "sound") == 0) { | ||
142 | i++; | ||
143 | sound = child; | ||
144 | } | ||
145 | } | ||
146 | if (i == 1) { | ||
147 | u32 *layout_id; | ||
148 | layout_id = (u32*) get_property(sound, "layout-id", NULL); | ||
149 | if (layout_id) { | ||
150 | snprintf(dev->sound.modalias, 32, | ||
151 | "sound-layout-%d", *layout_id); | ||
152 | force = 1; | ||
153 | } | ||
154 | } | ||
155 | /* for the time being, until we can handle non-layout-id | ||
156 | * things in some fabric, refuse to attach if there is no | ||
157 | * layout-id property or we haven't been forced to attach. | ||
158 | * When there are two i2s busses and only one has a layout-id, | ||
159 | * then this depends on the order, but that isn't important | ||
160 | * either as the second one in that case is just a modem. */ | ||
161 | if (!force) { | ||
162 | kfree(dev); | ||
163 | return -ENODEV; | ||
164 | } | ||
165 | |||
166 | mutex_init(&dev->lock); | ||
167 | spin_lock_init(&dev->low_lock); | ||
168 | dev->sound.ofdev.node = np; | ||
169 | dev->sound.ofdev.dma_mask = macio->ofdev.dma_mask; | ||
170 | dev->sound.ofdev.dev.dma_mask = &dev->sound.ofdev.dma_mask; | ||
171 | dev->sound.ofdev.dev.parent = &macio->ofdev.dev; | ||
172 | dev->sound.ofdev.dev.release = i2sbus_release_dev; | ||
173 | dev->sound.attach_codec = i2sbus_attach_codec; | ||
174 | dev->sound.detach_codec = i2sbus_detach_codec; | ||
175 | dev->sound.pcmid = -1; | ||
176 | dev->macio = macio; | ||
177 | dev->control = control; | ||
178 | dev->bus_number = np->name[4] - 'a'; | ||
179 | INIT_LIST_HEAD(&dev->sound.codec_list); | ||
180 | |||
181 | for (i=0;i<3;i++) { | ||
182 | dev->interrupts[i] = -1; | ||
183 | snprintf(dev->rnames[i], sizeof(dev->rnames[i]), rnames[i], np->name); | ||
184 | } | ||
185 | for (i=0;i<3;i++) { | ||
186 | if (request_irq(np->intrs[i].line, ints[i], 0, dev->rnames[i], dev)) | ||
187 | goto err; | ||
188 | dev->interrupts[i] = np->intrs[i].line; | ||
189 | } | ||
190 | |||
191 | for (i=0;i<3;i++) { | ||
192 | if (of_address_to_resource(np, i, &dev->resources[i])) | ||
193 | goto err; | ||
194 | /* if only we could use our resource dev->resources[i]... | ||
195 | * but request_resource doesn't know about parents and | ||
196 | * contained resources... */ | ||
197 | dev->allocated_resource[i] = | ||
198 | request_mem_region(dev->resources[i].start, | ||
199 | dev->resources[i].end - | ||
200 | dev->resources[i].start + 1, | ||
201 | dev->rnames[i]); | ||
202 | if (!dev->allocated_resource[i]) { | ||
203 | printk(KERN_ERR "i2sbus: failed to claim resource %d!\n", i); | ||
204 | goto err; | ||
205 | } | ||
206 | } | ||
207 | /* should do sanity checking here about length of them */ | ||
208 | dev->intfregs = ioremap(dev->resources[0].start, | ||
209 | dev->resources[0].end-dev->resources[0].start+1); | ||
210 | dev->out.dbdma = ioremap(dev->resources[1].start, | ||
211 | dev->resources[1].end-dev->resources[1].start+1); | ||
212 | dev->in.dbdma = ioremap(dev->resources[2].start, | ||
213 | dev->resources[2].end-dev->resources[2].start+1); | ||
214 | if (!dev->intfregs || !dev->out.dbdma || !dev->in.dbdma) | ||
215 | goto err; | ||
216 | |||
217 | if (alloc_dbdma_descriptor_ring(dev, &dev->out.dbdma_ring, | ||
218 | MAX_DBDMA_COMMANDS)) | ||
219 | goto err; | ||
220 | if (alloc_dbdma_descriptor_ring(dev, &dev->in.dbdma_ring, | ||
221 | MAX_DBDMA_COMMANDS)) | ||
222 | goto err; | ||
223 | |||
224 | if (i2sbus_control_add_dev(dev->control, dev)) { | ||
225 | printk(KERN_ERR "i2sbus: control layer didn't like bus\n"); | ||
226 | goto err; | ||
227 | } | ||
228 | |||
229 | if (soundbus_add_one(&dev->sound)) { | ||
230 | printk(KERN_DEBUG "i2sbus: device registration error!\n"); | ||
231 | goto err; | ||
232 | } | ||
233 | |||
234 | /* enable this cell */ | ||
235 | i2sbus_control_cell(dev->control, dev, 1); | ||
236 | i2sbus_control_enable(dev->control, dev); | ||
237 | i2sbus_control_clock(dev->control, dev, 1); | ||
238 | |||
239 | return 1; | ||
240 | err: | ||
241 | for (i=0;i<3;i++) | ||
242 | if (dev->interrupts[i] != -1) | ||
243 | free_irq(dev->interrupts[i], dev); | ||
244 | free_dbdma_descriptor_ring(dev, &dev->out.dbdma_ring); | ||
245 | free_dbdma_descriptor_ring(dev, &dev->in.dbdma_ring); | ||
246 | if (dev->intfregs) iounmap(dev->intfregs); | ||
247 | if (dev->out.dbdma) iounmap(dev->out.dbdma); | ||
248 | if (dev->in.dbdma) iounmap(dev->in.dbdma); | ||
249 | for (i=0;i<3;i++) | ||
250 | if (dev->allocated_resource[i]) | ||
251 | release_and_free_resource(dev->allocated_resource[i]); | ||
252 | mutex_destroy(&dev->lock); | ||
253 | kfree(dev); | ||
254 | return 0; | ||
255 | } | ||
256 | |||
257 | static int i2sbus_probe(struct macio_dev* dev, const struct of_device_id *match) | ||
258 | { | ||
259 | struct device_node *np = NULL; | ||
260 | int got = 0, err; | ||
261 | struct i2sbus_control *control = NULL; | ||
262 | |||
263 | err = i2sbus_control_init(dev, &control); | ||
264 | if (err) | ||
265 | return err; | ||
266 | if (!control) { | ||
267 | printk(KERN_ERR "i2sbus_control_init API breakage\n"); | ||
268 | return -ENODEV; | ||
269 | } | ||
270 | |||
271 | while ((np = of_get_next_child(dev->ofdev.node, np))) { | ||
272 | if (device_is_compatible(np, "i2sbus") || | ||
273 | device_is_compatible(np, "i2s-modem")) { | ||
274 | got += i2sbus_add_dev(dev, control, np); | ||
275 | } | ||
276 | } | ||
277 | |||
278 | if (!got) { | ||
279 | /* found none, clean up */ | ||
280 | i2sbus_control_destroy(control); | ||
281 | return -ENODEV; | ||
282 | } | ||
283 | |||
284 | dev->ofdev.dev.driver_data = control; | ||
285 | |||
286 | return 0; | ||
287 | } | ||
288 | |||
289 | static int i2sbus_remove(struct macio_dev* dev) | ||
290 | { | ||
291 | struct i2sbus_control *control = dev->ofdev.dev.driver_data; | ||
292 | struct i2sbus_dev *i2sdev, *tmp; | ||
293 | |||
294 | list_for_each_entry_safe(i2sdev, tmp, &control->list, item) | ||
295 | soundbus_remove_one(&i2sdev->sound); | ||
296 | |||
297 | return 0; | ||
298 | } | ||
299 | |||
300 | #ifdef CONFIG_PM | ||
301 | static int i2sbus_suspend(struct macio_dev* dev, pm_message_t state) | ||
302 | { | ||
303 | struct i2sbus_control *control = dev->ofdev.dev.driver_data; | ||
304 | struct codec_info_item *cii; | ||
305 | struct i2sbus_dev* i2sdev; | ||
306 | int err, ret = 0; | ||
307 | |||
308 | list_for_each_entry(i2sdev, &control->list, item) { | ||
309 | /* Notify Alsa */ | ||
310 | if (i2sdev->sound.pcm) { | ||
311 | /* Suspend PCM streams */ | ||
312 | snd_pcm_suspend_all(i2sdev->sound.pcm); | ||
313 | /* Probably useless as we handle | ||
314 | * power transitions ourselves */ | ||
315 | snd_power_change_state(i2sdev->sound.pcm->card, | ||
316 | SNDRV_CTL_POWER_D3hot); | ||
317 | } | ||
318 | /* Notify codecs */ | ||
319 | list_for_each_entry(cii, &i2sdev->sound.codec_list, list) { | ||
320 | err = 0; | ||
321 | if (cii->codec->suspend) | ||
322 | err = cii->codec->suspend(cii, state); | ||
323 | if (err) | ||
324 | ret = err; | ||
325 | } | ||
326 | } | ||
327 | return ret; | ||
328 | } | ||
329 | |||
330 | static int i2sbus_resume(struct macio_dev* dev) | ||
331 | { | ||
332 | struct i2sbus_control *control = dev->ofdev.dev.driver_data; | ||
333 | struct codec_info_item *cii; | ||
334 | struct i2sbus_dev* i2sdev; | ||
335 | int err, ret = 0; | ||
336 | |||
337 | list_for_each_entry(i2sdev, &control->list, item) { | ||
338 | /* Notify codecs so they can re-initialize */ | ||
339 | list_for_each_entry(cii, &i2sdev->sound.codec_list, list) { | ||
340 | err = 0; | ||
341 | if (cii->codec->resume) | ||
342 | err = cii->codec->resume(cii); | ||
343 | if (err) | ||
344 | ret = err; | ||
345 | } | ||
346 | /* Notify Alsa */ | ||
347 | if (i2sdev->sound.pcm) { | ||
348 | /* Same comment as above, probably useless */ | ||
349 | snd_power_change_state(i2sdev->sound.pcm->card, | ||
350 | SNDRV_CTL_POWER_D0); | ||
351 | } | ||
352 | } | ||
353 | |||
354 | return ret; | ||
355 | } | ||
356 | #endif /* CONFIG_PM */ | ||
357 | |||
358 | static int i2sbus_shutdown(struct macio_dev* dev) | ||
359 | { | ||
360 | return 0; | ||
361 | } | ||
362 | |||
363 | static struct macio_driver i2sbus_drv = { | ||
364 | .name = "soundbus-i2s", | ||
365 | .owner = THIS_MODULE, | ||
366 | .match_table = i2sbus_match, | ||
367 | .probe = i2sbus_probe, | ||
368 | .remove = i2sbus_remove, | ||
369 | #ifdef CONFIG_PM | ||
370 | .suspend = i2sbus_suspend, | ||
371 | .resume = i2sbus_resume, | ||
372 | #endif | ||
373 | .shutdown = i2sbus_shutdown, | ||
374 | }; | ||
375 | |||
376 | static int __init soundbus_i2sbus_init(void) | ||
377 | { | ||
378 | return macio_register_driver(&i2sbus_drv); | ||
379 | } | ||
380 | |||
381 | static void __exit soundbus_i2sbus_exit(void) | ||
382 | { | ||
383 | macio_unregister_driver(&i2sbus_drv); | ||
384 | } | ||
385 | |||
386 | module_init(soundbus_i2sbus_init); | ||
387 | module_exit(soundbus_i2sbus_exit); | ||