diff options
author | Johannes Berg <johannes@sipsolutions.net> | 2006-06-21 15:42:43 +0200 |
---|---|---|
committer | Jaroslav Kysela <perex@suse.cz> | 2006-06-22 21:34:38 +0200 |
commit | f3d9478b2ce468c3115b02ecae7e975990697f15 (patch) | |
tree | 8397671cbf251c54bf539016468164e08d0c45ee /sound/aoa/soundbus | |
parent | 41f0cd3a0c4c6547860cf3b1c2d7968008e6c071 (diff) |
[ALSA] snd-aoa: add snd-aoa
This large patch adds all of snd-aoa.
Consisting of many modules, it currently replaces snd-powermac
for all layout-id based machines and handles many more (for
example new powerbooks and powermacs with digital output that
previously couldn't be used at all).
It also has support for all layout-IDs that Apple has (judging
from their Info.plist file) but not all are tested.
The driver currently has 2 known regressions over snd-powermac:
* it doesn't handle powermac 7,2 and 7,3
* it doesn't have a DRC control on snapper-based machines
I will fix those during the 2.6.18 development cycle.
Signed-off-by: Johannes Berg <johannes@sipsolutions.net>
Signed-off-by: Takashi Iwai <tiwai@suse.de>
Diffstat (limited to 'sound/aoa/soundbus')
-rw-r--r-- | sound/aoa/soundbus/Kconfig | 14 | ||||
-rw-r--r-- | sound/aoa/soundbus/Makefile | 3 | ||||
-rw-r--r-- | sound/aoa/soundbus/core.c | 250 | ||||
-rw-r--r-- | sound/aoa/soundbus/i2sbus/Makefile | 2 | ||||
-rw-r--r-- | sound/aoa/soundbus/i2sbus/i2sbus-control.c | 192 | ||||
-rw-r--r-- | sound/aoa/soundbus/i2sbus/i2sbus-control.h | 37 | ||||
-rw-r--r-- | sound/aoa/soundbus/i2sbus/i2sbus-core.c | 387 | ||||
-rw-r--r-- | sound/aoa/soundbus/i2sbus/i2sbus-interface.h | 187 | ||||
-rw-r--r-- | sound/aoa/soundbus/i2sbus/i2sbus-pcm.c | 1021 | ||||
-rw-r--r-- | sound/aoa/soundbus/i2sbus/i2sbus.h | 112 | ||||
-rw-r--r-- | sound/aoa/soundbus/soundbus.h | 202 | ||||
-rw-r--r-- | sound/aoa/soundbus/sysfs.c | 43 |
12 files changed, 2450 insertions, 0 deletions
diff --git a/sound/aoa/soundbus/Kconfig b/sound/aoa/soundbus/Kconfig new file mode 100644 index 00000000000..d532d27a9f5 --- /dev/null +++ b/sound/aoa/soundbus/Kconfig @@ -0,0 +1,14 @@ +config SND_AOA_SOUNDBUS + tristate "Apple Soundbus support" + depends on SOUND && SND_PCM && EXPERIMENTAL + ---help--- + This option enables the generic driver for the soundbus + support on Apple machines. + + It is required for the sound bus implementations. + +config SND_AOA_SOUNDBUS_I2S + tristate "I2S bus support" + depends on SND_AOA_SOUNDBUS && PCI + ---help--- + This option enables support for Apple I2S busses. diff --git a/sound/aoa/soundbus/Makefile b/sound/aoa/soundbus/Makefile new file mode 100644 index 00000000000..0e61f5aa06b --- /dev/null +++ b/sound/aoa/soundbus/Makefile @@ -0,0 +1,3 @@ +obj-$(CONFIG_SND_AOA_SOUNDBUS) += snd-aoa-soundbus.o +snd-aoa-soundbus-objs := core.o sysfs.o +obj-$(CONFIG_SND_AOA_SOUNDBUS_I2S) += i2sbus/ diff --git a/sound/aoa/soundbus/core.c b/sound/aoa/soundbus/core.c new file mode 100644 index 00000000000..abe84a76c83 --- /dev/null +++ b/sound/aoa/soundbus/core.c @@ -0,0 +1,250 @@ +/* + * soundbus + * + * Copyright 2006 Johannes Berg <johannes@sipsolutions.net> + * + * GPL v2, can be found in COPYING. + */ + +#include <linux/module.h> +#include "soundbus.h" + +MODULE_AUTHOR("Johannes Berg <johannes@sipsolutions.net>"); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Apple Soundbus"); + +struct soundbus_dev *soundbus_dev_get(struct soundbus_dev *dev) +{ + struct device *tmp; + + if (!dev) + return NULL; + tmp = get_device(&dev->ofdev.dev); + if (tmp) + return to_soundbus_device(tmp); + else + return NULL; +} +EXPORT_SYMBOL_GPL(soundbus_dev_get); + +void soundbus_dev_put(struct soundbus_dev *dev) +{ + if (dev) + put_device(&dev->ofdev.dev); +} +EXPORT_SYMBOL_GPL(soundbus_dev_put); + +static int soundbus_probe(struct device *dev) +{ + int error = -ENODEV; + struct soundbus_driver *drv; + struct soundbus_dev *soundbus_dev; + + drv = to_soundbus_driver(dev->driver); + soundbus_dev = to_soundbus_device(dev); + + if (!drv->probe) + return error; + + soundbus_dev_get(soundbus_dev); + + error = drv->probe(soundbus_dev); + if (error) + soundbus_dev_put(soundbus_dev); + + return error; +} + + +static int soundbus_uevent(struct device *dev, char **envp, int num_envp, + char *buffer, int buffer_size) +{ + struct soundbus_dev * soundbus_dev; + struct of_device * of; + char *scratch, *compat, *compat2; + int i = 0; + int length, cplen, cplen2, seen = 0; + + if (!dev) + return -ENODEV; + + soundbus_dev = to_soundbus_device(dev); + if (!soundbus_dev) + return -ENODEV; + + of = &soundbus_dev->ofdev; + + /* stuff we want to pass to /sbin/hotplug */ + envp[i++] = scratch = buffer; + length = scnprintf (scratch, buffer_size, "OF_NAME=%s", of->node->name); + ++length; + buffer_size -= length; + if ((buffer_size <= 0) || (i >= num_envp)) + return -ENOMEM; + scratch += length; + + envp[i++] = scratch; + length = scnprintf (scratch, buffer_size, "OF_TYPE=%s", of->node->type); + ++length; + buffer_size -= length; + if ((buffer_size <= 0) || (i >= num_envp)) + return -ENOMEM; + scratch += length; + + /* Since the compatible field can contain pretty much anything + * it's not really legal to split it out with commas. We split it + * up using a number of environment variables instead. */ + + compat = (char *) get_property(of->node, "compatible", &cplen); + compat2 = compat; + cplen2= cplen; + while (compat && cplen > 0) { + envp[i++] = scratch; + length = scnprintf (scratch, buffer_size, + "OF_COMPATIBLE_%d=%s", seen, compat); + ++length; + buffer_size -= length; + if ((buffer_size <= 0) || (i >= num_envp)) + return -ENOMEM; + scratch += length; + length = strlen (compat) + 1; + compat += length; + cplen -= length; + seen++; + } + + envp[i++] = scratch; + length = scnprintf (scratch, buffer_size, "OF_COMPATIBLE_N=%d", seen); + ++length; + buffer_size -= length; + if ((buffer_size <= 0) || (i >= num_envp)) + return -ENOMEM; + scratch += length; + + envp[i++] = scratch; + length = scnprintf (scratch, buffer_size, "MODALIAS=%s", + soundbus_dev->modalias); + + buffer_size -= length; + if ((buffer_size <= 0) || (i >= num_envp)) + return -ENOMEM; + + envp[i] = NULL; + + return 0; +} + +static int soundbus_device_remove(struct device *dev) +{ + struct soundbus_dev * soundbus_dev = to_soundbus_device(dev); + struct soundbus_driver * drv = to_soundbus_driver(dev->driver); + + if (dev->driver && drv->remove) + drv->remove(soundbus_dev); + soundbus_dev_put(soundbus_dev); + + return 0; +} + +static void soundbus_device_shutdown(struct device *dev) +{ + struct soundbus_dev * soundbus_dev = to_soundbus_device(dev); + struct soundbus_driver * drv = to_soundbus_driver(dev->driver); + + if (dev->driver && drv->shutdown) + drv->shutdown(soundbus_dev); +} + +#ifdef CONFIG_PM + +static int soundbus_device_suspend(struct device *dev, pm_message_t state) +{ + struct soundbus_dev * soundbus_dev = to_soundbus_device(dev); + struct soundbus_driver * drv = to_soundbus_driver(dev->driver); + + if (dev->driver && drv->suspend) + return drv->suspend(soundbus_dev, state); + return 0; +} + +static int soundbus_device_resume(struct device * dev) +{ + struct soundbus_dev * soundbus_dev = to_soundbus_device(dev); + struct soundbus_driver * drv = to_soundbus_driver(dev->driver); + + if (dev->driver && drv->resume) + return drv->resume(soundbus_dev); + return 0; +} + +#endif /* CONFIG_PM */ + +extern struct device_attribute soundbus_dev_attrs[]; + +static struct bus_type soundbus_bus_type = { + .name = "aoa-soundbus", + .probe = soundbus_probe, + .uevent = soundbus_uevent, + .remove = soundbus_device_remove, + .shutdown = soundbus_device_shutdown, +#ifdef CONFIG_PM + .suspend = soundbus_device_suspend, + .resume = soundbus_device_resume, +#endif + .dev_attrs = soundbus_dev_attrs, +}; + +static int __init soundbus_init(void) +{ + return bus_register(&soundbus_bus_type); +} + +static void __exit soundbus_exit(void) +{ + bus_unregister(&soundbus_bus_type); +} + +int soundbus_add_one(struct soundbus_dev *dev) +{ + static int devcount; + + /* sanity checks */ + if (!dev->attach_codec || + !dev->ofdev.node || + dev->pcmname || + dev->pcmid != -1) { + printk(KERN_ERR "soundbus: adding device failed sanity check!\n"); + return -EINVAL; + } + + snprintf(dev->ofdev.dev.bus_id, BUS_ID_SIZE, "soundbus:%x", ++devcount); + dev->ofdev.dev.bus = &soundbus_bus_type; + return of_device_register(&dev->ofdev); +} +EXPORT_SYMBOL_GPL(soundbus_add_one); + +void soundbus_remove_one(struct soundbus_dev *dev) +{ + of_device_unregister(&dev->ofdev); +} +EXPORT_SYMBOL_GPL(soundbus_remove_one); + +int soundbus_register_driver(struct soundbus_driver *drv) +{ + /* initialize common driver fields */ + drv->driver.name = drv->name; + drv->driver.bus = &soundbus_bus_type; + + /* register with core */ + return driver_register(&drv->driver); +} +EXPORT_SYMBOL_GPL(soundbus_register_driver); + +void soundbus_unregister_driver(struct soundbus_driver *drv) +{ + driver_unregister(&drv->driver); +} +EXPORT_SYMBOL_GPL(soundbus_unregister_driver); + +module_init(soundbus_init); +module_exit(soundbus_exit); diff --git a/sound/aoa/soundbus/i2sbus/Makefile b/sound/aoa/soundbus/i2sbus/Makefile new file mode 100644 index 00000000000..e57a5cf6565 --- /dev/null +++ b/sound/aoa/soundbus/i2sbus/Makefile @@ -0,0 +1,2 @@ +obj-$(CONFIG_SND_AOA_SOUNDBUS_I2S) += snd-aoa-i2sbus.o +snd-aoa-i2sbus-objs := i2sbus-core.o i2sbus-pcm.o i2sbus-control.o diff --git a/sound/aoa/soundbus/i2sbus/i2sbus-control.c b/sound/aoa/soundbus/i2sbus/i2sbus-control.c new file mode 100644 index 00000000000..f50407952d3 --- /dev/null +++ b/sound/aoa/soundbus/i2sbus/i2sbus-control.c @@ -0,0 +1,192 @@ +/* + * i2sbus driver -- bus control routines + * + * Copyright 2006 Johannes Berg <johannes@sipsolutions.net> + * + * GPL v2, can be found in COPYING. + */ + +#include <asm/io.h> +#include <linux/delay.h> +#include <asm/prom.h> +#include <asm/macio.h> +#include <asm/pmac_feature.h> +#include <asm/pmac_pfunc.h> +#include "i2sbus.h" + +int i2sbus_control_init(struct macio_dev* dev, struct i2sbus_control **c) +{ + *c = kzalloc(sizeof(struct i2sbus_control), GFP_KERNEL); + if (!*c) + return -ENOMEM; + + INIT_LIST_HEAD(&(*c)->list); + + if (of_address_to_resource(dev->ofdev.node, 0, &(*c)->rsrc)) + goto err; + /* we really should be using feature calls instead of mapping + * these registers. It's safe for now since no one else is + * touching them... */ + (*c)->controlregs = ioremap((*c)->rsrc.start, + sizeof(struct i2s_control_regs)); + if (!(*c)->controlregs) + goto err; + + return 0; + err: + kfree(*c); + *c = NULL; + return -ENODEV; +} + +void i2sbus_control_destroy(struct i2sbus_control *c) +{ + iounmap(c->controlregs); + kfree(c); +} + +/* this is serialised externally */ +int i2sbus_control_add_dev(struct i2sbus_control *c, + struct i2sbus_dev *i2sdev) +{ + struct device_node *np; + + np = i2sdev->sound.ofdev.node; + i2sdev->enable = pmf_find_function(np, "enable"); + i2sdev->cell_enable = pmf_find_function(np, "cell-enable"); + i2sdev->clock_enable = pmf_find_function(np, "clock-enable"); + i2sdev->cell_disable = pmf_find_function(np, "cell-disable"); + i2sdev->clock_disable = pmf_find_function(np, "clock-disable"); + + /* if the bus number is not 0 or 1 we absolutely need to use + * the platform functions -- there's nothing in Darwin that + * would allow seeing a system behind what the FCRs are then, + * and I don't want to go parsing a bunch of platform functions + * by hand to try finding a system... */ + if (i2sdev->bus_number != 0 && i2sdev->bus_number != 1 && + (!i2sdev->enable || + !i2sdev->cell_enable || !i2sdev->clock_enable || + !i2sdev->cell_disable || !i2sdev->clock_disable)) { + pmf_put_function(i2sdev->enable); + pmf_put_function(i2sdev->cell_enable); + pmf_put_function(i2sdev->clock_enable); + pmf_put_function(i2sdev->cell_disable); + pmf_put_function(i2sdev->clock_disable); + return -ENODEV; + } + + list_add(&i2sdev->item, &c->list); + + return 0; +} + +void i2sbus_control_remove_dev(struct i2sbus_control *c, + struct i2sbus_dev *i2sdev) +{ + /* this is serialised externally */ + list_del(&i2sdev->item); + if (list_empty(&c->list)) + i2sbus_control_destroy(c); +} + +int i2sbus_control_enable(struct i2sbus_control *c, + struct i2sbus_dev *i2sdev) +{ + struct pmf_args args = { .count = 0 }; + int cc; + + if (i2sdev->enable) + return pmf_call_one(i2sdev->enable, &args); + + switch (i2sdev->bus_number) { + case 0: + cc = in_le32(&c->controlregs->cell_control); + out_le32(&c->controlregs->cell_control, cc | CTRL_CLOCK_INTF_0_ENABLE); + break; + case 1: + cc = in_le32(&c->controlregs->cell_control); + out_le32(&c->controlregs->cell_control, cc | CTRL_CLOCK_INTF_1_ENABLE); + break; + default: + return -ENODEV; + } + return 0; +} + +int i2sbus_control_cell(struct i2sbus_control *c, + struct i2sbus_dev *i2sdev, + int enable) +{ + struct pmf_args args = { .count = 0 }; + int cc; + + switch (enable) { + case 0: + if (i2sdev->cell_disable) + return pmf_call_one(i2sdev->cell_disable, &args); + break; + case 1: + if (i2sdev->cell_enable) + return pmf_call_one(i2sdev->cell_enable, &args); + break; + default: + printk(KERN_ERR "i2sbus: INVALID CELL ENABLE VALUE\n"); + return -ENODEV; + } + switch (i2sdev->bus_number) { + case 0: + cc = in_le32(&c->controlregs->cell_control); + cc &= ~CTRL_CLOCK_CELL_0_ENABLE; + cc |= enable * CTRL_CLOCK_CELL_0_ENABLE; + out_le32(&c->controlregs->cell_control, cc); + break; + case 1: + cc = in_le32(&c->controlregs->cell_control); + cc &= ~CTRL_CLOCK_CELL_1_ENABLE; + cc |= enable * CTRL_CLOCK_CELL_1_ENABLE; + out_le32(&c->controlregs->cell_control, cc); + break; + default: + return -ENODEV; + } + return 0; +} + +int i2sbus_control_clock(struct i2sbus_control *c, + struct i2sbus_dev *i2sdev, + int enable) +{ + struct pmf_args args = { .count = 0 }; + int cc; + + switch (enable) { + case 0: + if (i2sdev->clock_disable) + return pmf_call_one(i2sdev->clock_disable, &args); + break; + case 1: + if (i2sdev->clock_enable) + return pmf_call_one(i2sdev->clock_enable, &args); + break; + default: + printk(KERN_ERR "i2sbus: INVALID CLOCK ENABLE VALUE\n"); + return -ENODEV; + } + switch (i2sdev->bus_number) { + case 0: + cc = in_le32(&c->controlregs->cell_control); + cc &= ~CTRL_CLOCK_CLOCK_0_ENABLE; + cc |= enable * CTRL_CLOCK_CLOCK_0_ENABLE; + out_le32(&c->controlregs->cell_control, cc); + break; + case 1: + cc = in_le32(&c->controlregs->cell_control); + cc &= ~CTRL_CLOCK_CLOCK_1_ENABLE; + cc |= enable * CTRL_CLOCK_CLOCK_1_ENABLE; + out_le32(&c->controlregs->cell_control, cc); + break; + default: + return -ENODEV; + } + return 0; +} diff --git a/sound/aoa/soundbus/i2sbus/i2sbus-control.h b/sound/aoa/soundbus/i2sbus/i2sbus-control.h new file mode 100644 index 00000000000..bb05550f730 --- /dev/null +++ b/sound/aoa/soundbus/i2sbus/i2sbus-control.h @@ -0,0 +1,37 @@ +/* + * i2sbus driver -- bus register definitions + * + * Copyright 2006 Johannes Berg <johannes@sipsolutions.net> + * + * GPL v2, can be found in COPYING. + */ +#ifndef __I2SBUS_CONTROLREGS_H +#define __I2SBUS_CONTROLREGS_H + +/* i2s control registers, at least what we know about them */ + +#define __PAD(m,n) u8 __pad##m[n] +#define _PAD(line, n) __PAD(line, n) +#define PAD(n) _PAD(__LINE__, (n)) +struct i2s_control_regs { + PAD(0x38); + __le32 fcr0; /* 0x38 (unknown) */ + __le32 cell_control; /* 0x3c (fcr1) */ + __le32 fcr2; /* 0x40 (unknown) */ + __le32 fcr3; /* 0x44 (fcr3) */ + __le32 clock_control; /* 0x48 (unknown) */ + PAD(4); + /* total size: 0x50 bytes */ +} __attribute__((__packed__)); + +#define CTRL_CLOCK_CELL_0_ENABLE (1<<10) +#define CTRL_CLOCK_CLOCK_0_ENABLE (1<<12) +#define CTRL_CLOCK_SWRESET_0 (1<<11) +#define CTRL_CLOCK_INTF_0_ENABLE (1<<13) + +#define CTRL_CLOCK_CELL_1_ENABLE (1<<17) +#define CTRL_CLOCK_CLOCK_1_ENABLE (1<<18) +#define CTRL_CLOCK_SWRESET_1 (1<<19) +#define CTRL_CLOCK_INTF_1_ENABLE (1<<20) + +#endif /* __I2SBUS_CONTROLREGS_H */ 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 @@ +/* + * i2sbus driver + * + * Copyright 2006 Johannes Berg <johannes@sipsolutions.net> + * + * GPL v2, can be found in COPYING. + */ + +#include <linux/module.h> +#include <asm/macio.h> +#include <asm/dbdma.h> +#include <linux/pci.h> +#include <linux/interrupt.h> +#include <sound/driver.h> +#include <sound/core.h> +#include <linux/dma-mapping.h> +#include "../soundbus.h" +#include "i2sbus.h" + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Johannes Berg <johannes@sipsolutions.net>"); +MODULE_DESCRIPTION("Apple Soundbus: I2S support"); +/* for auto-loading, declare that we handle this weird + * string that macio puts into the relevant device */ +MODULE_ALIAS("of:Ni2sTi2sC"); + +static struct of_device_id i2sbus_match[] = { + { .name = "i2s" }, + { } +}; + +static int alloc_dbdma_descriptor_ring(struct i2sbus_dev *i2sdev, + struct dbdma_command_mem *r, + int numcmds) +{ + /* one more for rounding */ + r->size = (numcmds+1) * sizeof(struct dbdma_cmd); + /* We use the PCI APIs for now until the generic one gets fixed + * enough or until we get some macio-specific versions + */ + r->space = dma_alloc_coherent( + &macio_get_pci_dev(i2sdev->macio)->dev, + r->size, + &r->bus_addr, + GFP_KERNEL); + + if (!r->space) return -ENOMEM; + + memset(r->space, 0, r->size); + r->cmds = (void*)DBDMA_ALIGN(r->space); + r->bus_cmd_start = r->bus_addr + + (dma_addr_t)((char*)r->cmds - (char*)r->space); + + return 0; +} + +static void free_dbdma_descriptor_ring(struct i2sbus_dev *i2sdev, + struct dbdma_command_mem *r) +{ + if (!r->space) return; + + dma_free_coherent(&macio_get_pci_dev(i2sdev->macio)->dev, + r->size, r->space, r->bus_addr); +} + +static void i2sbus_release_dev(struct device *dev) +{ + struct i2sbus_dev *i2sdev; + int i; + + i2sdev = container_of(dev, struct i2sbus_dev, sound.ofdev.dev); + + if (i2sdev->intfregs) iounmap(i2sdev->intfregs); + if (i2sdev->out.dbdma) iounmap(i2sdev->out.dbdma); + if (i2sdev->in.dbdma) iounmap(i2sdev->in.dbdma); + for (i=0;i<3;i++) + if (i2sdev->allocated_resource[i]) + release_and_free_resource(i2sdev->allocated_resource[i]); + free_dbdma_descriptor_ring(i2sdev, &i2sdev->out.dbdma_ring); + free_dbdma_descriptor_ring(i2sdev, &i2sdev->in.dbdma_ring); + for (i=0;i<3;i++) + free_irq(i2sdev->interrupts[i], i2sdev); + i2sbus_control_remove_dev(i2sdev->control, i2sdev); + mutex_destroy(&i2sdev->lock); + kfree(i2sdev); +} + +static irqreturn_t i2sbus_bus_intr(int irq, void *devid, struct pt_regs *regs) +{ + struct i2sbus_dev *dev = devid; + u32 intreg; + + spin_lock(&dev->low_lock); + intreg = in_le32(&dev->intfregs->intr_ctl); + + /* acknowledge interrupt reasons */ + out_le32(&dev->intfregs->intr_ctl, intreg); + + spin_unlock(&dev->low_lock); + + return IRQ_HANDLED; +} + +static int force; +module_param(force, int, 0444); +MODULE_PARM_DESC(force, "Force loading i2sbus even when" + " no layout-id property is present"); + +/* FIXME: look at device node refcounting */ +static int i2sbus_add_dev(struct macio_dev *macio, + struct i2sbus_control *control, + struct device_node *np) +{ + struct i2sbus_dev *dev; + struct device_node *child = NULL, *sound = NULL; + int i; + static const char *rnames[] = { "i2sbus: %s (control)", + "i2sbus: %s (tx)", + "i2sbus: %s (rx)" }; + static irqreturn_t (*ints[])(int irq, void *devid, + struct pt_regs *regs) = { + i2sbus_bus_intr, + i2sbus_tx_intr, + i2sbus_rx_intr + }; + + if (strlen(np->name) != 5) + return 0; + if (strncmp(np->name, "i2s-", 4)) + return 0; + + if (np->n_intrs != 3) + return 0; + + dev = kzalloc(sizeof(struct i2sbus_dev), GFP_KERNEL); + if (!dev) + return 0; + + i = 0; + while ((child = of_get_next_child(np, child))) { + if (strcmp(child->name, "sound") == 0) { + i++; + sound = child; + } + } + if (i == 1) { + u32 *layout_id; + layout_id = (u32*) get_property(sound, "layout-id", NULL); + if (layout_id) { + snprintf(dev->sound.modalias, 32, + "sound-layout-%d", *layout_id); + force = 1; + } + } + /* for the time being, until we can handle non-layout-id + * things in some fabric, refuse to attach if there is no + * layout-id property or we haven't been forced to attach. + * When there are two i2s busses and only one has a layout-id, + * then this depends on the order, but that isn't important + * either as the second one in that case is just a modem. */ + if (!force) { + kfree(dev); + return -ENODEV; + } + + mutex_init(&dev->lock); + spin_lock_init(&dev->low_lock); + dev->sound.ofdev.node = np; + dev->sound.ofdev.dma_mask = macio->ofdev.dma_mask; + dev->sound.ofdev.dev.dma_mask = &dev->sound.ofdev.dma_mask; + dev->sound.ofdev.dev.parent = &macio->ofdev.dev; + dev->sound.ofdev.dev.release = i2sbus_release_dev; + dev->sound.attach_codec = i2sbus_attach_codec; + dev->sound.detach_codec = i2sbus_detach_codec; + dev->sound.pcmid = -1; + dev->macio = macio; + dev->control = control; + dev->bus_number = np->name[4] - 'a'; + INIT_LIST_HEAD(&dev->sound.codec_list); + + for (i=0;i<3;i++) { + dev->interrupts[i] = -1; + snprintf(dev->rnames[i], sizeof(dev->rnames[i]), rnames[i], np->name); + } + for (i=0;i<3;i++) { + if (request_irq(np->intrs[i].line, ints[i], 0, dev->rnames[i], dev)) + goto err; + dev->interrupts[i] = np->intrs[i].line; + } + + for (i=0;i<3;i++) { + if (of_address_to_resource(np, i, &dev->resources[i])) + goto err; + /* if only we could use our resource dev->resources[i]... + * but request_resource doesn't know about parents and + * contained resources... */ + dev->allocated_resource[i] = + request_mem_region(dev->resources[i].start, + dev->resources[i].end - + dev->resources[i].start + 1, + dev->rnames[i]); + if (!dev->allocated_resource[i]) { + printk(KERN_ERR "i2sbus: failed to claim resource %d!\n", i); + goto err; + } + } + /* should do sanity checking here about length of them */ + dev->intfregs = ioremap(dev->resources[0].start, + dev->resources[0].end-dev->resources[0].start+1); + dev->out.dbdma = ioremap(dev->resources[1].start, + dev->resources[1].end-dev->resources[1].start+1); + dev->in.dbdma = ioremap(dev->resources[2].start, + dev->resources[2].end-dev->resources[2].start+1); + if (!dev->intfregs || !dev->out.dbdma || !dev->in.dbdma) + goto err; + + if (alloc_dbdma_descriptor_ring(dev, &dev->out.dbdma_ring, + MAX_DBDMA_COMMANDS)) + goto err; + if (alloc_dbdma_descriptor_ring(dev, &dev->in.dbdma_ring, + MAX_DBDMA_COMMANDS)) + goto err; + + if (i2sbus_control_add_dev(dev->control, dev)) { + printk(KERN_ERR "i2sbus: control layer didn't like bus\n"); + goto err; + } + + if (soundbus_add_one(&dev->sound)) { + printk(KERN_DEBUG "i2sbus: device registration error!\n"); + goto err; + } + + /* enable this cell */ + i2sbus_control_cell(dev->control, dev, 1); + i2sbus_control_enable(dev->control, dev); + i2sbus_control_clock(dev->control, dev, 1); + + return 1; + err: + for (i=0;i<3;i++) + if (dev->interrupts[i] != -1) + free_irq(dev->interrupts[i], dev); + free_dbdma_descriptor_ring(dev, &dev->out.dbdma_ring); + free_dbdma_descriptor_ring(dev, &dev->in.dbdma_ring); + if (dev->intfregs) iounmap(dev->intfregs); + if (dev->out.dbdma) iounmap(dev->out.dbdma); + if (dev->in.dbdma) iounmap(dev->in.dbdma); + for (i=0;i<3;i++) + if (dev->allocated_resource[i]) + release_and_free_resource(dev->allocated_resource[i]); + mutex_destroy(&dev->lock); + kfree(dev); + return 0; +} + +static int i2sbus_probe(struct macio_dev* dev, const struct of_device_id *match) +{ + struct device_node *np = NULL; + int got = 0, err; + struct i2sbus_control *control = NULL; + + err = i2sbus_control_init(dev, &control); + if (err) + return err; + if (!control) { + printk(KERN_ERR "i2sbus_control_init API breakage\n"); + return -ENODEV; + } + + while ((np = of_get_next_child(dev->ofdev.node, np))) { + if (device_is_compatible(np, "i2sbus") || + device_is_compatible(np, "i2s-modem")) { + got += i2sbus_add_dev(dev, control, np); + } + } + + if (!got) { + /* found none, clean up */ + i2sbus_control_destroy(control); + return -ENODEV; + } + + dev->ofdev.dev.driver_data = control; + + return 0; +} + +static int i2sbus_remove(struct macio_dev* dev) +{ + struct i2sbus_control *control = dev->ofdev.dev.driver_data; + struct i2sbus_dev *i2sdev, *tmp; + + list_for_each_entry_safe(i2sdev, tmp, &control->list, item) + soundbus_remove_one(&i2sdev->sound); + + return 0; +} + +#ifdef CONFIG_PM +static int i2sbus_suspend(struct macio_dev* dev, pm_message_t state) +{ + struct i2sbus_control *control = dev->ofdev.dev.driver_data; + struct codec_info_item *cii; + struct i2sbus_dev* i2sdev; + int err, ret = 0; + + list_for_each_entry(i2sdev, &control->list, item) { + /* Notify Alsa */ + if (i2sdev->sound.pcm) { + /* Suspend PCM streams */ + snd_pcm_suspend_all(i2sdev->sound.pcm); + /* Probably useless as we handle + * power transitions ourselves */ + snd_power_change_state(i2sdev->sound.pcm->card, + SNDRV_CTL_POWER_D3hot); + } + /* Notify codecs */ + list_for_each_entry(cii, &i2sdev->sound.codec_list, list) { + err = 0; + if (cii->codec->suspend) + err = cii->codec->suspend(cii, state); + if (err) + ret = err; + } + } + return ret; +} + +static int i2sbus_resume(struct macio_dev* dev) +{ + struct i2sbus_control *control = dev->ofdev.dev.driver_data; + struct codec_info_item *cii; + struct i2sbus_dev* i2sdev; + int err, ret = 0; + + list_for_each_entry(i2sdev, &control->list, item) { + /* Notify codecs so they can re-initialize */ + list_for_each_entry(cii, &i2sdev->sound.codec_list, list) { + err = 0; + if (cii->codec->resume) + err = cii->codec->resume(cii); + if (err) + ret = err; + } + /* Notify Alsa */ + if (i2sdev->sound.pcm) { + /* Same comment as above, probably useless */ + snd_power_change_state(i2sdev->sound.pcm->card, + SNDRV_CTL_POWER_D0); + } + } + + return ret; +} +#endif /* CONFIG_PM */ + +static int i2sbus_shutdown(struct macio_dev* dev) +{ + return 0; +} + +static struct macio_driver i2sbus_drv = { + .name = "soundbus-i2s", + .owner = THIS_MODULE, + .match_table = i2sbus_match, + .probe = i2sbus_probe, + .remove = i2sbus_remove, +#ifdef CONFIG_PM + .suspend = i2sbus_suspend, + .resume = i2sbus_resume, +#endif + .shutdown = i2sbus_shutdown, +}; + +static int __init soundbus_i2sbus_init(void) +{ + return macio_register_driver(&i2sbus_drv); +} + +static void __exit soundbus_i2sbus_exit(void) +{ + macio_unregister_driver(&i2sbus_drv); +} + +module_init(soundbus_i2sbus_init); +module_exit(soundbus_i2sbus_exit); diff --git a/sound/aoa/soundbus/i2sbus/i2sbus-interface.h b/sound/aoa/soundbus/i2sbus/i2sbus-interface.h new file mode 100644 index 00000000000..c6b5f5452d2 --- /dev/null +++ b/sound/aoa/soundbus/i2sbus/i2sbus-interface.h @@ -0,0 +1,187 @@ +/* + * i2sbus driver -- interface register definitions + * + * Copyright 2006 Johannes Berg <johannes@sipsolutions.net> + * + * GPL v2, can be found in COPYING. + */ +#ifndef __I2SBUS_INTERFACE_H +#define __I2SBUS_INTERFACE_H + +/* i2s bus control registers, at least what we know about them */ + +#define __PAD(m,n) u8 __pad##m[n] +#define _PAD(line, n) __PAD(line, n) +#define PAD(n) _PAD(__LINE__, (n)) +struct i2s_interface_regs { + __le32 intr_ctl; /* 0x00 */ + PAD(12); + __le32 serial_format; /* 0x10 */ + PAD(12); + __le32 codec_msg_out; /* 0x20 */ + PAD(12); + __le32 codec_msg_in; /* 0x30 */ + PAD(12); + __le32 frame_count; /* 0x40 */ + PAD(12); + __le32 frame_match; /* 0x50 */ + PAD(12); + __le32 data_word_sizes; /* 0x60 */ + PAD(12); + __le32 peak_level_sel; /* 0x70 */ + PAD(12); + __le32 peak_level_in0; /* 0x80 */ + PAD(12); + __le32 peak_level_in1; /* 0x90 */ + PAD(12); + /* total size: 0x100 bytes */ +} __attribute__((__packed__)); + +/* interrupt register is just a bitfield with + * interrupt enable and pending bits */ +#define I2S_REG_INTR_CTL 0x00 +# define I2S_INT_FRAME_COUNT (1<<31) +# define I2S_PENDING_FRAME_COUNT (1<<30) +# define I2S_INT_MESSAGE_FLAG (1<<29) +# define I2S_PENDING_MESSAGE_FLAG (1<<28) +# define I2S_INT_NEW_PEAK (1<<27) +# define I2S_PENDING_NEW_PEAK (1<<26) +# define I2S_INT_CLOCKS_STOPPED (1<<25) +# define I2S_PENDING_CLOCKS_STOPPED (1<<24) +# define I2S_INT_EXTERNAL_SYNC_ERROR (1<<23) +# define I2S_PENDING_EXTERNAL_SYNC_ERROR (1<<22) +# define I2S_INT_EXTERNAL_SYNC_OK (1<<21) +# define I2S_PENDING_EXTERNAL_SYNC_OK (1<<20) +# define I2S_INT_NEW_SAMPLE_RATE (1<<19) +# define I2S_PENDING_NEW_SAMPLE_RATE (1<<18) +# define I2S_INT_STATUS_FLAG (1<<17) +# define I2S_PENDING_STATUS_FLAG (1<<16) + +/* serial format register is more interesting :) + * It contains: + * - clock source + * - MClk divisor + * - SClk divisor + * - SClk master flag + * - serial format (sony, i2s 64x, i2s 32x, dav, silabs) + * - external sample frequency interrupt (don't understand) + * - external sample frequency + */ +#define I2S_REG_SERIAL_FORMAT 0x10 +/* clock source. You get either 18.432, 45.1584 or 49.1520 MHz */ +# define I2S_SF_CLOCK_SOURCE_SHIFT 30 +# define I2S_SF_CLOCK_SOURCE_MASK (3<<I2S_SF_CLOCK_SOURCE_SHIFT) +# define I2S_SF_CLOCK_SOURCE_18MHz (0<<I2S_SF_CLOCK_SOURCE_SHIFT) +# define I2S_SF_CLOCK_SOURCE_45MHz (1<<I2S_SF_CLOCK_SOURCE_SHIFT) +# define I2S_SF_CLOCK_SOURCE_49MHz (2<<I2S_SF_CLOCK_SOURCE_SHIFT) +/* also, let's define the exact clock speeds here, in Hz */ +#define I2S_CLOCK_SPEED_18MHz 18432000 +#define I2S_CLOCK_SPEED_45MHz 45158400 +#define I2S_CLOCK_SPEED_49MHz 49152000 +/* MClk is the clock that drives the codec, usually called its 'system clock'. + * It is derived by taking only every 'divisor' tick of the clock. + */ +# define I2S_SF_MCLKDIV_SHIFT 24 +# define I2S_SF_MCLKDIV_MASK (0x1F<<I2S_SF_MCLKDIV_SHIFT) +# define I2S_SF_MCLKDIV_1 (0x14<<I2S_SF_MCLKDIV_SHIFT) +# define I2S_SF_MCLKDIV_3 (0x13<<I2S_SF_MCLKDIV_SHIFT) +# define I2S_SF_MCLKDIV_5 (0x12<<I2S_SF_MCLKDIV_SHIFT) +# define I2S_SF_MCLKDIV_14 (0x0E<<I2S_SF_MCLKDIV_SHIFT) +# define I2S_SF_MCLKDIV_OTHER(div) (((div/2-1)<<I2S_SF_MCLKDIV_SHIFT)&I2S_SF_MCLKDIV_MASK) +static inline int i2s_sf_mclkdiv(int div, int *out) +{ + int d; + + switch(div) { + case 1: *out |= I2S_SF_MCLKDIV_1; return 0; + case 3: *out |= I2S_SF_MCLKDIV_3; return 0; + case 5: *out |= I2S_SF_MCLKDIV_5; return 0; + case 14: *out |= I2S_SF_MCLKDIV_14; return 0; + default: + if (div%2) return -1; + d = div/2-1; |