Alsa-devel
Threads by month
- ----- 2025 -----
- May
- April
- March
- February
- January
- ----- 2024 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2023 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2022 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2021 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2020 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2019 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2018 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2017 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2016 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2015 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2014 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2013 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2012 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2011 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2010 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2009 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2008 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2007 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- 6 participants
- 51096 discussions
It's done when it's done
--
249 Spiele für nur 1 Preis. Die GMX Spieleflatrate schon ab 9,90 Euro.
Neu: Asterix bei den Olympischen Spielen: http://flat.games.gmx.de
1
0
Hello
Headset: Plantronics DSP 400 (047f:0ca1)
Distro: Ubuntu 8.04
output from tsalsa script (from #alsa) : http://nopaste.com/p/a8yymxldp
Problemdescription:
After some time(~0.5-1.5 hours) listening to music my headset suddenly
stops playing. If I try:
When the headset crashes dmesg says
ALSA /usr/src/modules/alsa-driver/usb/usbaudio.c:943: timeout: still 3
active urbs...
at the beginning the number of urbs were 4 but I blacklisted the usbhid
module (my headset has a volumecontrol) since then it hangs by 3.
/etc/init.d/alsa-utils stop cardid I get the message
"amixer: Mixer hw:1 load error: Invalid argument"
I also downloaded alsa-source (from the reposity)and built the
alsa-driver. Because
when I try sudo alsa force-unload or modprobe -r
modprobe hangs and I can't Terminate it.
When the headset crashes dmesg says
ALSA /usr/src/modules/alsa-driver/usb/usbaudio.c:943: timeout: still 3
active urbs...
at the beginning the number of urbs were 4 but I blacklisted the usbhid
module (my headset has a volumecontrol) since then it hangs by 3.
So the only thing what I can do is reboot to get it working again. It's
really annoying.
I first asked at #also for help but they couldn't help (except that it's
maybe somehow related to the usb subsystem) me but gave me the advice to
post my problem here.
Thanks
Anselm
1
0

16 May '08
Hello.
The attached patch adds back the
compatibility code, allowing the
driver to work with older alsa-libs.
The removal was premature, it breaks
the real-life configs, I am sorry
about that.
Takashi, could you please apply?
It is a straight-forward revert of:
http://hg-mirror.alsa-project.org/alsa-kernel/diff/26b0243ab4fa/drivers/pcs…
2
1
Hi,
there are some patches floating around on my experimental tree, which
I'd like to merge to the ALSA upstream. At least, the following two
are ready for merge:
- Use menuconfig in ALSA kconfig
- Convert CONFIG_SND_DEBUG_DETECT to CONFIG_SND_DEBUG_VERBOSE
The first one is to change from menu to menuconfig in ALSA Kconfig
files. There have been some patches by others, but they haven't been
applied because of the problem with alsa-driver build. Now I hacked
alsa-driver build again so that it works with menuconfig somehow.
The drawback is that it changes the configure option. Instead of
--with-cards and --with-card-options, now we have a single
--with-kconfigs. I think, however, it's rather an improvement.
The next one, renaming to CONFIG_SND_DEBUG_VERBOSE, is easy.
The reason is that CONFIG_SND_DEBUG_DETECT and snd_printdd() are used
almostly exclusively for verbose debug messages. This may reduce some
misunderstanding about this option.
The patches can be found on my experimental tree:
git://git.kernel.org/pub/scm/linux/kernel/git/tiwai/sound-unstable-2.6.git
git://git.kernel.org/pub/scm/linux/kernel/git/tiwai/alsa-driver-build-unsta…
If there is no objection, I'll apply the above two changes in the next
week.
thanks,
Takashi
2
2

16 May '08
This patch cleans up the drivers for the Freescale MPC8610 HPCD reference
board so that they compile and load. Functionality has not been tested, so
the drivers themselves probably don't work, but at least the kernel boots.
Signed-off-by: Timur Tabi <timur(a)freescale.com>
---
A few other patches are needed for before this driver can even pretend to
work:
Add CS4270 i2c data to fsl_soc.c
CS4270 node is misplaced in the MPC8610 device tree
Add null pointer check to of_find_property
These patches are waiting to be applied to the PowerPC repository.
sound/soc/Makefile | 2 +-
sound/soc/codecs/cs4270.c | 59 ++-
sound/soc/fsl/fsl_dma.c | 296 ++++++-------
sound/soc/fsl/fsl_dma.h | 20 +-
sound/soc/fsl/fsl_ssi.c | 682 ++++++++++++++++++++---------
sound/soc/fsl/fsl_ssi.h | 64 ++-
sound/soc/fsl/mpc8610_hpcd.c | 995 +++++++++++++++---------------------------
7 files changed, 1066 insertions(+), 1052 deletions(-)
rewrite sound/soc/fsl/mpc8610_hpcd.c (60%)
diff --git a/sound/soc/Makefile b/sound/soc/Makefile
index 478664c..22393cd 100644
--- a/sound/soc/Makefile
+++ b/sound/soc/Makefile
@@ -1,4 +1,4 @@
snd-soc-core-objs := soc-core.o soc-dapm.o
obj-$(CONFIG_SND_SOC) += snd-soc-core.o
-obj-$(CONFIG_SND_SOC) += codecs/ at91/ pxa/ s3c24xx/ sh/ fsl/ imx/ au1x/ davinci/ omap/
+obj-$(CONFIG_SND_SOC) += codecs/ at91/ pxa/ s3c24xx/ sh/ fsl/ imx/ au1x/ davinci/ omap/ fsl/
diff --git a/sound/soc/codecs/cs4270.c b/sound/soc/codecs/cs4270.c
index b47e1d8..13b9d56 100644
--- a/sound/soc/codecs/cs4270.c
+++ b/sound/soc/codecs/cs4270.c
@@ -465,6 +465,29 @@ static const struct snd_kcontrol_new cs4270_snd_controls[] = {
CS4270_VOLA, CS4270_VOLB, 0, 0xFF, 1)
};
+static int cs4270_codec_init(struct snd_soc_codec *codec,
+ struct snd_soc_card *soc_card)
+{
+ int ret;
+ unsigned int i;
+
+ /* Add the non-DAPM controls */
+
+ for (i = 0; i < ARRAY_SIZE(cs4270_snd_controls); i++) {
+ struct snd_kcontrol *kctrl;
+
+ kctrl = snd_soc_cnew(&cs4270_snd_controls[i], codec, NULL);
+
+ ret = snd_ctl_add(soc_card->card, kctrl);
+ if (ret < 0) {
+ dev_err(soc_card->card->dev, "could not add control\n");
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
/*
* Initialize the I2C interface of the CS4270
*
@@ -474,11 +497,11 @@ static const struct snd_kcontrol_new cs4270_snd_controls[] = {
* Note: snd_soc_new_pcms() must be called before this function can be called,
* because of snd_ctl_add().
*/
-static int cs4270_i2c_probe(struct i2c_client *client)
+static int cs4270_i2c_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
{
struct cs4270_private *cs4270;
struct snd_soc_codec *codec = NULL;
- int i;
int ret = 0;
int registered = 0; /* 1 == the codec has been registered */
@@ -499,7 +522,9 @@ static int cs4270_i2c_probe(struct i2c_client *client)
/*
* Normally, we'd call snd_soc_new_codec, but that function
- * allocates a snd_soc_codec struct, and we don't want that.
+ * allocates a snd_soc_codec struct, and we don't want that. So we have
+ * to initialize all the fields ourselves. This might break in the
+ * future.
*/
cs4270 = kzalloc(sizeof(struct cs4270_private), GFP_KERNEL);
if (!cs4270) {
@@ -516,7 +541,7 @@ static int cs4270_i2c_probe(struct i2c_client *client)
goto error;
}
- strcpy(cs4270->name, "CS4270");
+ strcpy(cs4270->name, "cirrus,cs4270");
cs4270->playback.stream_name = "Playback";
cs4270->playback.channels_min = 1;
@@ -541,6 +566,7 @@ static int cs4270_i2c_probe(struct i2c_client *client)
codec->name = cs4270->name;
codec->control_data = client;
+ codec->init = cs4270_codec_init;
ret = snd_soc_register_codec(codec, &client->dev);
if (ret < 0) {
@@ -549,6 +575,7 @@ static int cs4270_i2c_probe(struct i2c_client *client)
}
registered = 1;
+ cs4270->dai_new.name = cs4270->name;
cs4270->dai_new.playback = &cs4270->playback;
cs4270->dai_new.capture = &cs4270->capture;
cs4270->dai_new.ops = &cs4270->dai_ops;
@@ -560,18 +587,6 @@ static int cs4270_i2c_probe(struct i2c_client *client)
goto error;
}
- /* Add the non-DAPM controls */
-
- for (i = 0; i < ARRAY_SIZE(cs4270_snd_controls); i++) {
- struct snd_kcontrol *kctrl;
-
- kctrl = snd_soc_cnew(&cs4270_snd_controls[i], codec, NULL);
-
- ret = snd_ctl_add(codec->soc_card->card, kctrl);
- if (ret < 0)
- goto error;
- }
-
i2c_set_clientdata(client, codec);
return 0;
@@ -604,12 +619,18 @@ static int cs4270_i2c_remove(struct i2c_client *client)
return 0;
}
+static const struct i2c_device_id cs4270_id[] = {
+ {"cs4270", 0},
+ {}
+};
+MODULE_DEVICE_TABLE(i2c, cs4270_id);
+
static struct i2c_driver cs4270_i2c_driver = {
.driver = {
.name = "cs4270",
.owner = THIS_MODULE,
},
- .id = I2C_DRIVERID_CS4270,
+ .id_table = cs4270_id,
.probe = cs4270_i2c_probe,
.remove = cs4270_i2c_remove,
};
@@ -623,7 +644,7 @@ static int __init cs4270_init(void)
{
int ret;
- printk(KERN_INFO "Cirrus Logic CS4270 ALSA SoC codec driver\n");
+ printk(KERN_INFO "Cirrus Logic CS4270 ASoC codec driver\n");
/* i2c_add_driver() will call cs4270_i2c_probe() */
ret = i2c_add_driver(&cs4270_i2c_driver);
@@ -648,5 +669,5 @@ module_init(cs4270_init);
module_exit(cs4270_exit);
MODULE_AUTHOR("Timur Tabi <timur(a)freescale.com>");
-MODULE_DESCRIPTION("Cirrus Logic CS4270 ALSA SoC Codec Driver");
+MODULE_DESCRIPTION("Cirrus Logic CS4270 ASoC codec driver");
MODULE_LICENSE("GPL");
diff --git a/sound/soc/fsl/fsl_dma.c b/sound/soc/fsl/fsl_dma.c
index 2511eee..94ed933 100644
--- a/sound/soc/fsl/fsl_dma.c
+++ b/sound/soc/fsl/fsl_dma.c
@@ -11,6 +11,15 @@
* This driver implements ASoC support for the Elo DMA controller, which is
* the DMA controller on Freescale 83xx, 85xx, and 86xx SOCs. In ALSA terms,
* the PCM driver is what handles the DMA buffer.
+ *
+ * The DMA driver is not really a stand-alone driver as much as it's a library
+ * for the SSI driver. This is because the DMA channels cannot really stand
+ * alone. A particular SSI device needs to know which two DMA channels to use
+ * for playback and capture. If the DMA driver were a regular OF driver, it
+ * would get probed for each DMA channel, and then it would have to figure out
+ * which SSI to use for those channels. That's just too complicated. So we
+ * load like a normal platform driver and wait for the SSI driver to give us the
+ * information we need via cpu_dai->dma_data.
*/
#include <linux/module.h>
@@ -19,8 +28,9 @@
#include <linux/dma-mapping.h>
#include <linux/interrupt.h>
#include <linux/delay.h>
+#include <linux/of_device.h>
+#include <linux/of_platform.h>
-#include <sound/driver.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
@@ -52,34 +62,13 @@
#define FSLDMA_PCM_RATES (SNDRV_PCM_RATE_5512 | SNDRV_PCM_RATE_8000_192000 | \
SNDRV_PCM_RATE_CONTINUOUS)
-/* DMA global data. This structure is used by fsl_dma_open() to determine
- * which DMA channels to assign to a substream. Unfortunately, ASoC V1 does
- * not allow the soc_card driver to provide this information to the PCM
- * driver in advance, and there's no way to differentiate between the two
- * DMA controllers. So for now, this driver only supports one SSI device
- * using two DMA channels. We cannot support multiple DMA devices.
- *
- * ssi_stx_phys: bus address of SSI STX register
- * ssi_srx_phys: bus address of SSI SRX register
- * dma_channel: pointer to the DMA channel's registers
- * irq: IRQ for this DMA channel
- * assigned: set to 1 if that DMA channel is assigned to a substream
- */
-static struct {
- dma_addr_t ssi_stx_phys;
- dma_addr_t ssi_srx_phys;
- struct ccsr_dma_channel __iomem *dma_channel[2];
- unsigned int irq[2];
- unsigned int assigned[2];
-} dma_global_data;
-
/*
* The number of DMA links to use. Two is the bare minimum, but if you
* have really small links you might need more.
*/
#define NUM_DMA_LINKS 2
-/** fsl_dma_private: p-substream DMA data
+/** fsl_dma_private: per-substream DMA data
*
* Each substream has a 1-to-1 association with a DMA channel.
*
@@ -104,12 +93,8 @@ static struct {
*/
struct fsl_dma_private {
struct fsl_dma_link_descriptor link[NUM_DMA_LINKS];
- unsigned int controller_id;
- unsigned int channel_id;
- struct ccsr_dma_channel __iomem *dma_channel;
- unsigned int irq;
+ struct fsl_dma_info *dma_info;
struct snd_pcm_substream *substream;
- dma_addr_t ssi_sxx_phys;
dma_addr_t ld_buf_phys;
unsigned int current_link;
dma_addr_t dma_buf_phys;
@@ -143,7 +128,7 @@ static const struct snd_pcm_hardware fsl_dma_hardware = {
.rate_min = 5512,
.rate_max = 192000,
.period_bytes_min = 512, /* A reasonable limit */
- .period_bytes_max = (u32) -1,
+ .period_bytes_max = (uint32_t) -1,
.periods_min = NUM_DMA_LINKS,
.periods_max = (unsigned int) -1,
.buffer_bytes_max = 128 * 1024, /* A reasonable limit */
@@ -205,9 +190,10 @@ static void fsl_dma_update_pointers(struct fsl_dma_private *dma_private)
static irqreturn_t fsl_dma_isr(int irq, void *dev_id)
{
struct fsl_dma_private *dma_private = dev_id;
- struct ccsr_dma_channel __iomem *dma_channel = dma_private->dma_channel;
+ struct ccsr_dma_channel __iomem *dma_channel =
+ dma_private->dma_info->channel;
irqreturn_t ret = IRQ_NONE;
- u32 sr, sr2 = 0;
+ uint32_t sr, sr2 = 0;
/* We got an interrupt, so read the status register to see what we
were interrupted for.
@@ -216,9 +202,9 @@ static irqreturn_t fsl_dma_isr(int irq, void *dev_id)
if (sr & CCSR_DMA_SR_TE) {
dev_err(dma_private->substream->pcm->card->dev,
- "DMA transmit error (controller=%u channel=%u irq=%u\n",
- dma_private->controller_id,
- dma_private->channel_id, irq);
+ "DMA%u transmit error (channel=%u irq=%u)\n",
+ dma_private->dma_info->controller_id,
+ dma_private->dma_info->channel_id, irq);
fsl_dma_abort_stream(dma_private->substream);
sr2 |= CCSR_DMA_SR_TE;
ret = IRQ_HANDLED;
@@ -230,8 +216,8 @@ static irqreturn_t fsl_dma_isr(int irq, void *dev_id)
if (sr & CCSR_DMA_SR_PE) {
dev_err(dma_private->substream->pcm->card->dev,
"DMA%u programming error (channel=%u irq=%u)\n",
- dma_private->controller_id,
- dma_private->channel_id, irq);
+ dma_private->dma_info->controller_id,
+ dma_private->dma_info->channel_id, irq);
fsl_dma_abort_stream(dma_private->substream);
sr2 |= CCSR_DMA_SR_PE;
ret = IRQ_HANDLED;
@@ -281,9 +267,12 @@ static irqreturn_t fsl_dma_isr(int irq, void *dev_id)
* This function is called when the codec driver calls snd_soc_new_pcms(),
* once for each .dai_link in the soc_card driver's snd_soc_card
* structure.
+ *
+ * The DMA controller does support 36-bit addresses for the DMA buffers, but
+ * supporting that is clunky so we restrict ourselves to 32-bit addresses.
*/
-static int fsl_dma_new(struct snd_card *card, struct snd_soc_codec_dai *dai,
- struct snd_pcm *pcm)
+static int fsl_dma_new(struct snd_soc_platform *platform,
+ struct snd_card *card, int playback, int capture, struct snd_pcm *pcm)
{
static u64 fsl_dma_dmamask = DMA_BIT_MASK(32);
int ret;
@@ -294,28 +283,40 @@ static int fsl_dma_new(struct snd_card *card, struct snd_soc_codec_dai *dai,
if (!card->dev->coherent_dma_mask)
card->dev->coherent_dma_mask = fsl_dma_dmamask;
- ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, pcm->dev,
- fsl_dma_hardware.buffer_bytes_max,
- &pcm->streams[0].substream->dma_buffer);
- if (ret) {
- dev_err(card->dev,
- "Can't allocate playback DMA buffer (size=%u)\n",
- fsl_dma_hardware.buffer_bytes_max);
- return -ENOMEM;
+ if (playback) {
+ ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, pcm->dev,
+ fsl_dma_hardware.buffer_bytes_max,
+ &pcm->streams[0].substream->dma_buffer);
+ if (ret) {
+ dev_err(card->dev,
+ "cannot allocate playback buffer (size=%u)\n",
+ fsl_dma_hardware.buffer_bytes_max);
+ goto error;
+ }
}
- ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, pcm->dev,
- fsl_dma_hardware.buffer_bytes_max,
- &pcm->streams[1].substream->dma_buffer);
- if (ret) {
- snd_dma_free_pages(&pcm->streams[0].substream->dma_buffer);
- dev_err(card->dev,
- "Can't allocate capture DMA buffer (size=%u)\n",
- fsl_dma_hardware.buffer_bytes_max);
- return -ENOMEM;
+ if (capture) {
+ ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, pcm->dev,
+ fsl_dma_hardware.buffer_bytes_max,
+ &pcm->streams[1].substream->dma_buffer);
+ if (ret) {
+ dev_err(card->dev,
+ "cannot allocate capture buffer (size=%u)\n",
+ fsl_dma_hardware.buffer_bytes_max);
+ goto error;
+ }
}
return 0;
+
+error:
+ if (pcm->streams[0].substream->dma_buffer.area)
+ snd_dma_free_pages(&pcm->streams[0].substream->dma_buffer);
+
+ if (pcm->streams[1].substream->dma_buffer.area)
+ snd_dma_free_pages(&pcm->streams[1].substream->dma_buffer);
+
+ return ret;
}
/**
@@ -326,6 +327,10 @@ static int fsl_dma_new(struct snd_card *card, struct snd_soc_codec_dai *dai,
static int fsl_dma_open(struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_soc_pcm_runtime *pcm_runtime = substream->private_data;
+ struct snd_soc_dai *cpu_dai = pcm_runtime->cpu_dai;
+ struct fsl_dma_info *dma_info = cpu_dai->dma_data;
+
struct fsl_dma_private *dma_private;
dma_addr_t ld_buf_phys;
unsigned int channel;
@@ -345,12 +350,6 @@ static int fsl_dma_open(struct snd_pcm_substream *substream)
channel = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 0 : 1;
- if (dma_global_data.assigned[channel]) {
- dev_err(substream->pcm->card->dev,
- "DMA channel already assigned\n");
- return -EBUSY;
- }
-
dma_private = dma_alloc_coherent(substream->pcm->dev,
sizeof(struct fsl_dma_private), &ld_buf_phys, GFP_KERNEL);
if (!dma_private) {
@@ -358,34 +357,24 @@ static int fsl_dma_open(struct snd_pcm_substream *substream)
"can't allocate DMA private data\n");
return -ENOMEM;
}
+
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
- dma_private->ssi_sxx_phys = dma_global_data.ssi_stx_phys;
+ dma_private->dma_info = &dma_info[0];
else
- dma_private->ssi_sxx_phys = dma_global_data.ssi_srx_phys;
-
- dma_private->dma_channel = dma_global_data.dma_channel[channel];
- dma_private->irq = dma_global_data.irq[channel];
- dma_private->substream = substream;
- dma_private->ld_buf_phys = ld_buf_phys;
- dma_private->dma_buf_phys = substream->dma_buffer.addr;
-
- /* We only support one DMA controller for now */
- dma_private->controller_id = 0;
- dma_private->channel_id = channel;
+ dma_private->dma_info = &dma_info[1];
- ret = request_irq(dma_private->irq, fsl_dma_isr, 0, "DMA", dma_private);
+ ret = request_irq(dma_private->dma_info->irq,
+ fsl_dma_isr, 0, "DMA", dma_private);
if (ret) {
dev_err(substream->pcm->card->dev,
"can't register ISR for IRQ %u (ret=%i)\n",
- dma_private->irq, ret);
+ dma_private->dma_info->irq, ret);
dma_free_coherent(substream->pcm->dev,
sizeof(struct fsl_dma_private),
dma_private, dma_private->ld_buf_phys);
return ret;
}
- dma_global_data.assigned[channel] = 1;
-
snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
snd_soc_set_runtime_hwparams(substream, &fsl_dma_hardware);
runtime->private_data = dma_private;
@@ -463,11 +452,12 @@ static int fsl_dma_hw_params(struct snd_pcm_substream *substream,
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct fsl_dma_private *dma_private = runtime->private_data;
- struct ccsr_dma_channel __iomem *dma_channel = dma_private->dma_channel;
+ struct ccsr_dma_channel __iomem *dma_channel =
+ dma_private->dma_info->channel;
dma_addr_t temp_addr; /* Pointer to next period */
u64 temp_link; /* Pointer to next link descriptor */
- u32 mr; /* Temporary variable for MR register */
+ uint32_t mr; /* Temporary variable for MR register */
unsigned int i;
@@ -599,13 +589,14 @@ static int fsl_dma_prepare(struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct fsl_dma_private *dma_private = runtime->private_data;
- struct ccsr_dma_channel __iomem *dma_channel = dma_private->dma_channel;
- u32 mr;
+ struct ccsr_dma_channel __iomem *dma_channel =
+ dma_private->dma_info->channel;
+ uint32_t mr;
unsigned int i;
dma_addr_t ssi_sxx_phys; /* Bus address of SSI STX register */
unsigned int frame_size; /* Number of bytes per frame */
- ssi_sxx_phys = dma_private->ssi_sxx_phys;
+ ssi_sxx_phys = dma_private->dma_info->ssi_sxx_phys;
mr = in_be32(&dma_channel->mr) & ~(CCSR_DMA_MR_BWC_MASK |
CCSR_DMA_MR_SAHTS_MASK | CCSR_DMA_MR_DAHTS_MASK);
@@ -676,7 +667,8 @@ static snd_pcm_uframes_t fsl_dma_pointer(struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct fsl_dma_private *dma_private = runtime->private_data;
- struct ccsr_dma_channel __iomem *dma_channel = dma_private->dma_channel;
+ struct ccsr_dma_channel __iomem *dma_channel =
+ dma_private->dma_info->channel;
dma_addr_t position;
snd_pcm_uframes_t frames;
@@ -713,7 +705,7 @@ static int fsl_dma_hw_free(struct snd_pcm_substream *substream)
if (dma_private) {
struct ccsr_dma_channel __iomem *dma_channel;
- dma_channel = dma_private->dma_channel;
+ dma_channel = dma_private->dma_info->channel;
/* Stop the DMA */
out_be32(&dma_channel->mr, CCSR_DMA_MR_CA);
@@ -742,11 +734,10 @@ static int fsl_dma_close(struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct fsl_dma_private *dma_private = runtime->private_data;
- int dir = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 0 : 1;
if (dma_private) {
- if (dma_private->irq)
- free_irq(dma_private->irq, dma_private);
+ if (dma_private->dma_info->irq)
+ free_irq(dma_private->dma_info->irq, dma_private);
if (dma_private->ld_buf_phys) {
dma_unmap_single(substream->pcm->dev,
@@ -761,8 +752,6 @@ static int fsl_dma_close(struct snd_pcm_substream *substream)
substream->runtime->private_data = NULL;
}
- dma_global_data.assigned[dir] = 0;
-
return 0;
}
@@ -794,95 +783,92 @@ static struct snd_pcm_ops fsl_dma_ops = {
.pointer = fsl_dma_pointer,
};
-/**
- * fsl_dma_configure: store the DMA parameters from the fabric driver.
- *
- * This function is called by the ASoC fabric driver to give us the DMA and
- * SSI channel information.
- *
- * Unfortunately, ASoC V1 does make it possible to determine the DMA/SSI
- * data when a substream is created, so for now we need to store this data
- * into a global variable. This means that we can only support one DMA
- * controller, and hence only one SSI.
- */
-int fsl_dma_configure(struct fsl_dma_info *dma_info)
-{
- static int initialized;
-
- /* We only support one DMA controller for now */
- if (initialized)
- return 0;
-
- dma_global_data.ssi_stx_phys = dma_info->ssi_stx_phys;
- dma_global_data.ssi_srx_phys = dma_info->ssi_srx_phys;
- dma_global_data.dma_channel[0] = dma_info->dma_channel[0];
- dma_global_data.dma_channel[1] = dma_info->dma_channel[1];
- dma_global_data.irq[0] = dma_info->dma_irq[0];
- dma_global_data.irq[1] = dma_info->dma_irq[1];
- dma_global_data.assigned[0] = 0;
- dma_global_data.assigned[1] = 0;
-
- initialized = 1;
- return 1;
-}
-EXPORT_SYMBOL_GPL(fsl_dma_configure);
+const char fsl_platform_id[] = "fsl_pcm";
+EXPORT_SYMBOL_GPL(fsl_platform_id);
+static struct snd_soc_platform_new fsl_dma_platform = {
+ .name = fsl_platform_id,
+ .pcm_ops = &fsl_dma_ops,
+ .pcm_new = fsl_dma_new,
+ .pcm_free = fsl_dma_free_dma_buffers,
+};
/*
- * TODO: We may want to query the device tree here for our DMA and SSI
- * properties. It may also beneficial to merge in the SSI driver.
*/
-static int fsl_pcm_probe(struct device *dev)
+static int fsl_dma_probe(struct platform_device *pdev)
{
- struct snd_soc_platform *platform = to_snd_soc_platform(dev);
+ struct snd_soc_platform *platform;
int ret;
- platform->pcm_ops = &fsl_dma_ops;
- platform->pcm_new = fsl_pcm_new,
- platform->pcm_free = fsl_dma_free_dma_buffers,
-/* TODO: DAI can be config here after dev tree parse */
- ret = snd_soc_platform_add_dai(platform, imx_ssi, ARRAY_SIZE(imx_ssi));
- if (ret < 0)
- return ret;
-*/
- ret = snd_soc_register_platform(platform);
+ platform = snd_soc_new_platform(&fsl_dma_platform);
+ if (!platform) {
+ dev_err(&pdev->dev, "unable to allocate platform\n");
+ return -ENOMEM;
+ }
+
+ platform_set_drvdata(pdev, platform);
+
+ ret = snd_soc_register_platform(platform, &pdev->dev);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "could not register platform\n");
+ snd_soc_free_platform(platform);
+ }
+
return ret;
}
-static int fsl_pcm_remove(struct device *dev)
+static int fsl_dma_remove(struct platform_device *pdev)
{
- struct snd_soc_platform *platform = to_snd_soc_platform(dev);
+ struct snd_soc_platform *platform = platform_get_drvdata(pdev);
+
+ if (platform)
+ snd_soc_free_platform(platform);
- snd_soc_unregister_platform(platform);
return 0;
}
-const char fsl_platform_id[] = "fsl_pcm";
-EXPORT_SYMBOL_GPL(fsl_platform_id);
-
-static struct device_driver fsl_pcm_driver = {
- .name = fsl_platform_id,
- .owner = THIS_MODULE,
- .bus = &asoc_bus_type,
- .probe = fsl_pcm_probe,
- .remove = __devexit_p(fsl_pcm_remove),
-// .suspend = fsl_pcm_suspend,
-// .resume = fsl_pcm_resume,
+static struct platform_driver fsl_dma_driver = {
+ .driver = {
+ .name = fsl_platform_id,
+ .owner = THIS_MODULE,
+ },
+ .probe = fsl_dma_probe,
+ .remove = __devexit_p(fsl_dma_remove),
};
-static __init int fsl_pcm_init(void)
+static struct platform_device *pdev;
+
+static __init int fsl_dma_init(void)
{
- return driver_register(&fsl_pcm_driver);
+ int ret;
+
+ printk(KERN_INFO "Freescale Elo DMA ASoC PCM driver\n");
+
+ ret = platform_driver_register(&fsl_dma_driver);
+ if (ret < 0) {
+ pr_err("fsl-dma: could not register platform\n");
+ return ret;
+ }
+
+ pdev = platform_device_register_simple(fsl_platform_id, 0, NULL, 0);
+ if (!pdev) {
+ pr_err("fsl-dma: could not register platform\n");
+ return ret;
+ }
+
+ return 0;
}
-static __exit void fsl_pcm_exit(void)
+static __exit void fsl_dma_exit(void)
{
- driver_unregister(&fsl_pcm_driver);
+ platform_device_unregister(pdev);
+ platform_driver_unregister(&fsl_dma_driver);
}
-module_init(fsl_pcm_init);
-module_exit(fsl_pcm_exit);
+module_init(fsl_dma_init);
+module_exit(fsl_dma_exit);
MODULE_AUTHOR("Timur Tabi <timur(a)freescale.com>");
-MODULE_DESCRIPTION("Freescale Elo DMA ASoC PCM module");
+MODULE_DESCRIPTION("Freescale Elo DMA ASoC PCM driver");
MODULE_LICENSE("GPL");
+
diff --git a/sound/soc/fsl/fsl_dma.h b/sound/soc/fsl/fsl_dma.h
index 05febed..ed4592f 100644
--- a/sound/soc/fsl/fsl_dma.h
+++ b/sound/soc/fsl/fsl_dma.h
@@ -126,20 +126,18 @@ struct fsl_dma_link_descriptor {
u8 res[4]; /* Reserved */
} __attribute__ ((aligned(32), packed));
-/* DMA information needed to create a snd_soc_cpu_dai object
+/* Per-stream DMA information that the SSI driver passes to the DMA driver
*
- * ssi_stx_phys: bus address of SSI STX register to use
- * ssi_srx_phys: bus address of SSI SRX register to use
- * dma[0]: points to the DMA channel to use for playback
- * dma[1]: points to the DMA channel to use for capture
- * dma_irq[0]: IRQ of the DMA channel to use for playback
- * dma_irq[1]: IRQ of the DMA channel to use for capture
+ * ssi_sxx_phys: bus address of SSI STX or SRX register to use
+ * channel: points to the DMA channel device to use
+ * irq: IRQ of the DMA channel
*/
struct fsl_dma_info {
- dma_addr_t ssi_stx_phys;
- dma_addr_t ssi_srx_phys;
- struct ccsr_dma_channel __iomem *dma_channel[2];
- unsigned int dma_irq[2];
+ dma_addr_t ssi_sxx_phys;
+ struct ccsr_dma_channel __iomem *channel;
+ unsigned int irq;
+ unsigned int controller_id;
+ unsigned int channel_id;
};
extern const char fsl_platform_id[];
diff --git a/sound/soc/fsl/fsl_ssi.c b/sound/soc/fsl/fsl_ssi.c
index 9f194bc..75576b2 100644
--- a/sound/soc/fsl/fsl_ssi.c
+++ b/sound/soc/fsl/fsl_ssi.c
@@ -14,8 +14,9 @@
#include <linux/interrupt.h>
#include <linux/device.h>
#include <linux/delay.h>
+#include <linux/of_device.h>
+#include <linux/of_platform.h>
-#include <sound/driver.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
@@ -24,6 +25,7 @@
#include <asm/immap_86xx.h>
+#include "fsl_dma.h"
#include "fsl_ssi.h"
/**
@@ -62,56 +64,6 @@
#endif
/**
- * fsl_ssi_private: per-SSI private data
- *
- * @name: short name for this device ("SSI0", "SSI1", etc)
- * @ssi: pointer to the SSI's registers
- * @ssi_phys: physical address of the SSI registers
- * @irq: IRQ of this SSI
- * @dev: struct device pointer
- * @playback: the number of playback streams opened
- * @capture: the number of capture streams opened
- * @cpu_dai: the CPU DAI for this device
- * @dev_attr: the sysfs device attribute structure
- * @stats: SSI statistics
- */
-struct fsl_ssi_private {
- char name[8];
- struct ccsr_ssi __iomem *ssi;
- dma_addr_t ssi_phys;
- unsigned int irq;
- struct device *dev;
- unsigned int playback;
- unsigned int capture;
- struct snd_soc_cpu_dai cpu_dai;
- struct device_attribute dev_attr;
-
- struct {
- unsigned int rfrc;
- unsigned int tfrc;
- unsigned int cmdau;
- unsigned int cmddu;
- unsigned int rxt;
- unsigned int rdr1;
- unsigned int rdr0;
- unsigned int tde1;
- unsigned int tde0;
- unsigned int roe1;
- unsigned int roe0;
- unsigned int tue1;
- unsigned int tue0;
- unsigned int tfs;
- unsigned int rfs;
- unsigned int tls;
- unsigned int rls;
- unsigned int rff1;
- unsigned int rff0;
- unsigned int tfe1;
- unsigned int tfe0;
- } stats;
-};
-
-/**
* fsl_ssi_isr: SSI interrupt handler
*
* Although it's possible to use the interrupt handler to send and receive
@@ -121,12 +73,12 @@ struct fsl_ssi_private {
* This interrupt handler is used only to gather statistics.
*
* @irq: IRQ of the SSI device
- * @dev_id: pointer to the ssi_private structure for this SSI device
+ * @dev_id: pointer to the ssi_info structure for this SSI device
*/
static irqreturn_t fsl_ssi_isr(int irq, void *dev_id)
{
- struct fsl_ssi_private *ssi_private = dev_id;
- struct ccsr_ssi __iomem *ssi = ssi_private->ssi;
+ struct fsl_ssi_info *ssi_info = dev_id;
+ struct ccsr_ssi __iomem *ssi = ssi_info->ssi;
irqreturn_t ret = IRQ_NONE;
__be32 sisr;
__be32 sisr2 = 0;
@@ -138,113 +90,113 @@ static irqreturn_t fsl_ssi_isr(int irq, void *dev_id)
sisr = in_be32(&ssi->sisr) & in_be32(&ssi->sier);
if (sisr & CCSR_SSI_SISR_RFRC) {
- ssi_private->stats.rfrc++;
+ ssi_info->stats.rfrc++;
sisr2 |= CCSR_SSI_SISR_RFRC;
ret = IRQ_HANDLED;
}
if (sisr & CCSR_SSI_SISR_TFRC) {
- ssi_private->stats.tfrc++;
+ ssi_info->stats.tfrc++;
sisr2 |= CCSR_SSI_SISR_TFRC;
ret = IRQ_HANDLED;
}
if (sisr & CCSR_SSI_SISR_CMDAU) {
- ssi_private->stats.cmdau++;
+ ssi_info->stats.cmdau++;
ret = IRQ_HANDLED;
}
if (sisr & CCSR_SSI_SISR_CMDDU) {
- ssi_private->stats.cmddu++;
+ ssi_info->stats.cmddu++;
ret = IRQ_HANDLED;
}
if (sisr & CCSR_SSI_SISR_RXT) {
- ssi_private->stats.rxt++;
+ ssi_info->stats.rxt++;
ret = IRQ_HANDLED;
}
if (sisr & CCSR_SSI_SISR_RDR1) {
- ssi_private->stats.rdr1++;
+ ssi_info->stats.rdr1++;
ret = IRQ_HANDLED;
}
if (sisr & CCSR_SSI_SISR_RDR0) {
- ssi_private->stats.rdr0++;
+ ssi_info->stats.rdr0++;
ret = IRQ_HANDLED;
}
if (sisr & CCSR_SSI_SISR_TDE1) {
- ssi_private->stats.tde1++;
+ ssi_info->stats.tde1++;
ret = IRQ_HANDLED;
}
if (sisr & CCSR_SSI_SISR_TDE0) {
- ssi_private->stats.tde0++;
+ ssi_info->stats.tde0++;
ret = IRQ_HANDLED;
}
if (sisr & CCSR_SSI_SISR_ROE1) {
- ssi_private->stats.roe1++;
+ ssi_info->stats.roe1++;
sisr2 |= CCSR_SSI_SISR_ROE1;
ret = IRQ_HANDLED;
}
if (sisr & CCSR_SSI_SISR_ROE0) {
- ssi_private->stats.roe0++;
+ ssi_info->stats.roe0++;
sisr2 |= CCSR_SSI_SISR_ROE0;
ret = IRQ_HANDLED;
}
if (sisr & CCSR_SSI_SISR_TUE1) {
- ssi_private->stats.tue1++;
+ ssi_info->stats.tue1++;
sisr2 |= CCSR_SSI_SISR_TUE1;
ret = IRQ_HANDLED;
}
if (sisr & CCSR_SSI_SISR_TUE0) {
- ssi_private->stats.tue0++;
+ ssi_info->stats.tue0++;
sisr2 |= CCSR_SSI_SISR_TUE0;
ret = IRQ_HANDLED;
}
if (sisr & CCSR_SSI_SISR_TFS) {
- ssi_private->stats.tfs++;
+ ssi_info->stats.tfs++;
ret = IRQ_HANDLED;
}
if (sisr & CCSR_SSI_SISR_RFS) {
- ssi_private->stats.rfs++;
+ ssi_info->stats.rfs++;
ret = IRQ_HANDLED;
}
if (sisr & CCSR_SSI_SISR_TLS) {
- ssi_private->stats.tls++;
+ ssi_info->stats.tls++;
ret = IRQ_HANDLED;
}
if (sisr & CCSR_SSI_SISR_RLS) {
- ssi_private->stats.rls++;
+ ssi_info->stats.rls++;
ret = IRQ_HANDLED;
}
if (sisr & CCSR_SSI_SISR_RFF1) {
- ssi_private->stats.rff1++;
+ ssi_info->stats.rff1++;
ret = IRQ_HANDLED;
}
if (sisr & CCSR_SSI_SISR_RFF0) {
- ssi_private->stats.rff0++;
+ ssi_info->stats.rff0++;
ret = IRQ_HANDLED;
}
if (sisr & CCSR_SSI_SISR_TFE1) {
- ssi_private->stats.tfe1++;
+ ssi_info->stats.tfe1++;
ret = IRQ_HANDLED;
}
if (sisr & CCSR_SSI_SISR_TFE0) {
- ssi_private->stats.tfe0++;
+ ssi_info->stats.tfe0++;
ret = IRQ_HANDLED;
}
@@ -263,24 +215,24 @@ static irqreturn_t fsl_ssi_isr(int irq, void *dev_id)
* If this is the first stream open, then grab the IRQ and program most of
* the SSI registers.
*/
-static int fsl_ssi_startup(struct snd_pcm_substream *substream)
+static int fsl_ssi_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *cpu_dai)
{
- struct snd_soc_pcm_runtime *rtd = substream->private_data;
- struct fsl_ssi_private *ssi_private = rtd->dai->cpu_dai->private_data;
+ struct fsl_ssi_info *ssi_info = cpu_dai->private_data;
/*
* If this is the first stream opened, then request the IRQ
* and initialize the SSI registers.
*/
- if (!ssi_private->playback && !ssi_private->capture) {
- struct ccsr_ssi __iomem *ssi = ssi_private->ssi;
+ if (!ssi_info->playback && !ssi_info->capture) {
+ struct ccsr_ssi __iomem *ssi = ssi_info->ssi;
int ret;
- ret = request_irq(ssi_private->irq, fsl_ssi_isr, 0,
- ssi_private->name, ssi_private);
+ ret = request_irq(ssi_info->irq, fsl_ssi_isr, 0,
+ ssi_info->name, ssi_info);
if (ret < 0) {
dev_err(substream->pcm->card->dev,
- "could not claim irq %u\n", ssi_private->irq);
+ "could not claim irq %u\n", ssi_info->irq);
return ret;
}
@@ -344,10 +296,12 @@ static int fsl_ssi_startup(struct snd_pcm_substream *substream)
}
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
- ssi_private->playback++;
+ ssi_info->playback++;
if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
- ssi_private->capture++;
+ ssi_info->capture++;
+
+ cpu_dai->dma_data = ssi_info->dma_info;
return 0;
}
@@ -365,17 +319,19 @@ static int fsl_ssi_startup(struct snd_pcm_substream *substream)
* Note: The SxCCR.DC and SxCCR.PM bits are only used if the SSI is the
* clock master.
*/
-static int fsl_ssi_prepare(struct snd_pcm_substream *substream)
+static int fsl_ssi_prepare(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *cpu_dai)
{
struct snd_pcm_runtime *runtime = substream->runtime;
- struct snd_soc_pcm_runtime *rtd = substream->private_data;
- struct fsl_ssi_private *ssi_private = rtd->dai->cpu_dai->private_data;
-
- struct ccsr_ssi __iomem *ssi = ssi_private->ssi;
- u32 wl;
+ struct fsl_ssi_info *ssi_info = cpu_dai->private_data;
+ struct ccsr_ssi __iomem *ssi = ssi_info->ssi;
+ uint32_t wl;
wl = CCSR_SSI_SxCCR_WL(snd_pcm_format_width(runtime->format));
+ /* FIXME: We should read and write the registers manually so as to
+ minimize the amount of time the SSI is disabled. */
+
clrbits32(&ssi->scr, CCSR_SSI_SCR_SSIEN);
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
@@ -397,11 +353,12 @@ static int fsl_ssi_prepare(struct snd_pcm_substream *substream)
* The DMA channel is in external master start and pause mode, which
* means the SSI completely controls the flow of data.
*/
-static int fsl_ssi_trigger(struct snd_pcm_substream *substream, int cmd)
+static int fsl_ssi_trigger(struct snd_pcm_substream *substream, int cmd,
+ struct snd_soc_dai *cpu_dai)
{
- struct snd_soc_pcm_runtime *rtd = substream->private_data;
- struct fsl_ssi_private *ssi_private = rtd->dai->cpu_dai->private_data;
- struct ccsr_ssi __iomem *ssi = ssi_private->ssi;
+
+ struct fsl_ssi_info *ssi_info = cpu_dai->private_data;
+ struct ccsr_ssi __iomem *ssi = ssi_info->ssi;
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
@@ -417,7 +374,7 @@ static int fsl_ssi_trigger(struct snd_pcm_substream *substream, int cmd)
* to put data into its FIFO. Without it, ALSA starts
* to complain about overruns.
*/
- msleep(1);
+ mdelay(1);
}
break;
@@ -442,27 +399,28 @@ static int fsl_ssi_trigger(struct snd_pcm_substream *substream, int cmd)
*
* Shutdown the SSI if there are no other substreams open.
*/
-static void fsl_ssi_shutdown(struct snd_pcm_substream *substream)
+static void fsl_ssi_shutdown(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *cpu_dai)
{
- struct snd_soc_pcm_runtime *rtd = substream->private_data;
- struct fsl_ssi_private *ssi_private = rtd->dai->cpu_dai->private_data;
+
+ struct fsl_ssi_info *ssi_info = cpu_dai->private_data;
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
- ssi_private->playback--;
+ ssi_info->playback--;
if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
- ssi_private->capture--;
+ ssi_info->capture--;
/*
* If this is the last active substream, disable the SSI and release
* the IRQ.
*/
- if (!ssi_private->playback && !ssi_private->capture) {
- struct ccsr_ssi __iomem *ssi = ssi_private->ssi;
+ if (!ssi_info->playback && !ssi_info->capture) {
+ struct ccsr_ssi __iomem *ssi = ssi_info->ssi;
clrbits32(&ssi->scr, CCSR_SSI_SCR_SSIEN);
- free_irq(ssi_private->irq, ssi_private);
+ free_irq(ssi_info->irq, ssi_info);
}
}
@@ -480,8 +438,8 @@ static void fsl_ssi_shutdown(struct snd_pcm_substream *substream)
* @freq: the frequency of the given clock ID, currently ignored
* @dir: SND_SOC_CLOCK_IN (clock slave) or SND_SOC_CLOCK_OUT (clock master)
*/
-static int fsl_ssi_set_sysclk(struct snd_soc_cpu_dai *cpu_dai,
- int clk_id, unsigned int freq, int dir)
+static int fsl_ssi_set_sysclk(struct snd_soc_dai *dai, int clk_id,
+ unsigned int freq, int dir)
{
return (dir == SND_SOC_CLOCK_IN) ? 0 : -EINVAL;
@@ -498,7 +456,7 @@ static int fsl_ssi_set_sysclk(struct snd_soc_cpu_dai *cpu_dai,
*
* @format: one of SND_SOC_DAIFMT_xxx
*/
-static int fsl_ssi_set_fmt(struct snd_soc_cpu_dai *cpu_dai, unsigned int format)
+static int fsl_ssi_set_fmt(struct snd_soc_dai *dai, unsigned int format)
{
return (format == SND_SOC_DAIFMT_I2S) ? 0 : -EINVAL;
}
@@ -511,142 +469,448 @@ static int fsl_ssi_set_fmt(struct snd_soc_cpu_dai *cpu_dai, unsigned int format)
static ssize_t fsl_sysfs_ssi_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
- struct fsl_ssi_private *ssi_private =
- container_of(attr, struct fsl_ssi_private, dev_attr);
+ struct fsl_ssi_info *ssi_info =
+ container_of(attr, struct fsl_ssi_info, dev_attr);
ssize_t length;
- length = sprintf(buf, "rfrc=%u", ssi_private->stats.rfrc);
- length += sprintf(buf + length, "\ttfrc=%u", ssi_private->stats.tfrc);
- length += sprintf(buf + length, "\tcmdau=%u", ssi_private->stats.cmdau);
- length += sprintf(buf + length, "\tcmddu=%u", ssi_private->stats.cmddu);
- length += sprintf(buf + length, "\trxt=%u", ssi_private->stats.rxt);
- length += sprintf(buf + length, "\trdr1=%u", ssi_private->stats.rdr1);
- length += sprintf(buf + length, "\trdr0=%u", ssi_private->stats.rdr0);
- length += sprintf(buf + length, "\ttde1=%u", ssi_private->stats.tde1);
- length += sprintf(buf + length, "\ttde0=%u", ssi_private->stats.tde0);
- length += sprintf(buf + length, "\troe1=%u", ssi_private->stats.roe1);
- length += sprintf(buf + length, "\troe0=%u", ssi_private->stats.roe0);
- length += sprintf(buf + length, "\ttue1=%u", ssi_private->stats.tue1);
- length += sprintf(buf + length, "\ttue0=%u", ssi_private->stats.tue0);
- length += sprintf(buf + length, "\ttfs=%u", ssi_private->stats.tfs);
- length += sprintf(buf + length, "\trfs=%u", ssi_private->stats.rfs);
- length += sprintf(buf + length, "\ttls=%u", ssi_private->stats.tls);
- length += sprintf(buf + length, "\trls=%u", ssi_private->stats.rls);
- length += sprintf(buf + length, "\trff1=%u", ssi_private->stats.rff1);
- length += sprintf(buf + length, "\trff0=%u", ssi_private->stats.rff0);
- length += sprintf(buf + length, "\ttfe1=%u", ssi_private->stats.tfe1);
- length += sprintf(buf + length, "\ttfe0=%u\n", ssi_private->stats.tfe0);
+ length = sprintf(buf, "rfrc=%u", ssi_info->stats.rfrc);
+ length += sprintf(buf + length, "\ttfrc=%u", ssi_info->stats.tfrc);
+ length += sprintf(buf + length, "\tcmdau=%u", ssi_info->stats.cmdau);
+ length += sprintf(buf + length, "\tcmddu=%u", ssi_info->stats.cmddu);
+ length += sprintf(buf + length, "\trxt=%u", ssi_info->stats.rxt);
+ length += sprintf(buf + length, "\trdr1=%u", ssi_info->stats.rdr1);
+ length += sprintf(buf + length, "\trdr0=%u", ssi_info->stats.rdr0);
+ length += sprintf(buf + length, "\ttde1=%u", ssi_info->stats.tde1);
+ length += sprintf(buf + length, "\ttde0=%u", ssi_info->stats.tde0);
+ length += sprintf(buf + length, "\troe1=%u", ssi_info->stats.roe1);
+ length += sprintf(buf + length, "\troe0=%u", ssi_info->stats.roe0);
+ length += sprintf(buf + length, "\ttue1=%u", ssi_info->stats.tue1);
+ length += sprintf(buf + length, "\ttue0=%u", ssi_info->stats.tue0);
+ length += sprintf(buf + length, "\ttfs=%u", ssi_info->stats.tfs);
+ length += sprintf(buf + length, "\trfs=%u", ssi_info->stats.rfs);
+ length += sprintf(buf + length, "\ttls=%u", ssi_info->stats.tls);
+ length += sprintf(buf + length, "\trls=%u", ssi_info->stats.rls);
+ length += sprintf(buf + length, "\trff1=%u", ssi_info->stats.rff1);
+ length += sprintf(buf + length, "\trff0=%u", ssi_info->stats.rff0);
+ length += sprintf(buf + length, "\ttfe1=%u", ssi_info->stats.tfe1);
+ length += sprintf(buf + length, "\ttfe0=%u\n", ssi_info->stats.tfe0);
return length;
}
+static struct snd_soc_dai_caps playback = {
+ /* The SSI does not support monaural audio. */
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = FSLSSI_I2S_RATES,
+ .formats = FSLSSI_I2S_FORMATS,
+};
+
+static struct snd_soc_dai_caps capture = {
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = FSLSSI_I2S_RATES,
+ .formats = FSLSSI_I2S_FORMATS,
+};
+
+static struct snd_soc_dai_ops ops = {
+ .startup = fsl_ssi_startup,
+ .prepare = fsl_ssi_prepare,
+ .shutdown = fsl_ssi_shutdown,
+ .trigger = fsl_ssi_trigger,
+
+ .set_sysclk = fsl_ssi_set_sysclk,
+ .set_fmt = fsl_ssi_set_fmt,
+};
+
+static struct device_node *find_dma_node(unsigned int controller,
+ unsigned int channel)
+{
+ struct device_node *np = NULL;
+ struct device_node *np2 = NULL;
+ const uint32_t *iprop;
+
+ for_each_compatible_node(np, NULL, "fsl,eloplus-dma") {
+ iprop = of_get_property(np, "cell-index", NULL);
+ if (!iprop) {
+ pr_err("fsl-ssi: cell-index property not found\n");
+ return NULL;
+ }
+ if (*iprop == controller)
+ break;
+ }
+
+ if (!np) {
+ pr_err("fsl-ssi: cannot find node for DMA controller %u\n",
+ controller);
+ return NULL;
+ }
+
+ for_each_child_of_node(np, np2) {
+ if (!of_device_is_compatible(np2, "fsl,eloplus-dma-channel"))
+ continue;
+
+ iprop = of_get_property(np2, "cell-index", NULL);
+ if (!iprop) {
+ pr_err("fsl-ssi: cell-index property not found\n");
+ return NULL;
+ }
+ if (*iprop == channel)
+ break;
+ }
+
+ if (!np2) {
+ pr_err("fsl-ssi: cannot find node for DMA channel %u\n",
+ channel);
+ return NULL;
+ }
+
+ of_node_put(np);
+
+ return np2;
+}
+
/**
- * fsl_ssi_create_dai: create a snd_soc_cpu_dai structure
- *
- * This function is called by the soc_card driver to create a snd_soc_cpu_dai
- * structure. The function creates an ssi_private object, which contains
- * the snd_soc_cpu_dai. It also creates the sysfs statistics device.
+ * Initialize a dma_info structure from a pointer to a DMA node
*/
-struct snd_soc_cpu_dai *fsl_ssi_create_dai(struct fsl_ssi_info *ssi_info)
+static int get_dma_info(struct device_node *np, struct fsl_dma_info *dma_info)
+{
+ const uint32_t *iprop;
+
+ if (!np)
+ return 0;
+
+ iprop = of_get_property(of_get_parent(np), "cell-index", NULL);
+ if (!iprop)
+ return 0;
+
+ dma_info->controller_id = *iprop;
+
+ iprop = of_get_property(np, "cell-index", NULL);
+ if (!iprop)
+ return 0;
+
+ dma_info->channel_id = *iprop;
+
+ dma_info->channel = of_iomap(np, 0);
+ dma_info->irq = irq_of_parse_and_map(np, 0);
+
+ return 1;
+}
+static int fsl_ssi_probe(struct of_device *ofdev,
+ const struct of_device_id *match)
{
- struct snd_soc_cpu_dai *fsl_ssi_dai;
- struct fsl_ssi_private *ssi_private;
- int ret = 0;
- struct device_attribute *dev_attr;
-
- ssi_private = kzalloc(sizeof(struct fsl_ssi_private), GFP_KERNEL);
- if (!ssi_private) {
- dev_err(ssi_info->dev, "could not allocate DAI object\n");
- return NULL;
+ struct device_node *np = ofdev->node;
+ struct device_node *codec_np = NULL;
+ struct device_node *dma_np[2] = {NULL, NULL};
+ const phandle *codec_ph;
+ const phandle *dma_ph;
+ const char *sprop;
+ const uint32_t *iprop;
+ struct resource res;
+
+ struct fsl_ssi_info *ssi_info;
+ int ret = -ENODEV;
+
+ struct snd_soc_dai_new dai_template;
+
+ ssi_info = kzalloc(sizeof(struct fsl_ssi_info), GFP_KERNEL);
+ if (!ssi_info) {
+ dev_err(&ofdev->dev, "cannot allocate ssi_info\n");
+ return -ENOMEM;
+ }
+
+ ssi_info->dev = &ofdev->dev;
+
+ /*
+ * We are only interested in SSIs with a codec phandle in them, so let's
+ * make sure this SSI has one.
+ */
+ codec_ph = of_get_property(np, "codec-handle", NULL);
+ if (!codec_ph) {
+ dev_dbg(&ofdev->dev, "no codec handle for this SSI\n");
+ goto error;
+ }
+
+ codec_np = of_find_node_by_phandle(*codec_ph);
+ if (!codec_np) {
+ dev_err(&ofdev->dev, "codec handle does not exist\n");
+ goto error;
+ }
+
+ /* The MPC8610 HPCD only knows about the CS4270 codec, so reject
+ * anything else.
+ * FIXME: This should be unnecessary
+ */
+ if (!of_device_is_compatible(codec_np, "cirrus,cs4270")) {
+ dev_dbg(&ofdev->dev, "unknown codec %s\n",
+ (char *) of_get_property(codec_np, "compatible", NULL));
+ goto error;
+ }
+
+ /* Get the device ID */
+ iprop = of_get_property(np, "cell-index", NULL);
+ if (!iprop) {
+ dev_err(&ofdev->dev, "cell-index property not found\n");
+ ret = -EINVAL;
+ goto error;
+ }
+ ssi_info->id = *iprop;
+
+ strcpy(ssi_info->name, "fsl,mpc8610-ssi");
+
+ /* Get the serial format and clock direction. */
+ sprop = of_get_property(np, "fsl,mode", NULL);
+ if (!sprop) {
+ dev_err(&ofdev->dev, "fsl,mode property not found\n");
+ ret = -EINVAL;
+ goto error;
}
- memcpy(&ssi_private->cpu_dai, &fsl_ssi_dai_template,
- sizeof(struct snd_soc_cpu_dai));
- fsl_ssi_dai = &ssi_private->cpu_dai;
- dev_attr = &ssi_private->dev_attr;
+ if (strcasecmp(sprop, "i2s-slave") == 0) {
+ ssi_info->dai_format = SND_SOC_DAIFMT_I2S;
+ ssi_info->codec_clk_direction = SND_SOC_CLOCK_OUT;
+ ssi_info->cpu_clk_direction = SND_SOC_CLOCK_IN;
- sprintf(ssi_private->name, "ssi%u", (u8) ssi_info->id);
- ssi_private->ssi = ssi_info->ssi;
- ssi_private->ssi_phys = ssi_info->ssi_phys;
- ssi_private->irq = ssi_info->irq;
- ssi_private->dev = ssi_info->dev;
+ /*
+ * In i2s-slave mode, the codec has its own clock source, so we
+ * need to get the frequency from the device tree and pass it to
+ * the codec driver.
+ */
+ iprop = of_get_property(codec_np, "clock-frequency", NULL);
+ if (!iprop || !*iprop) {
+ dev_err(&ofdev->dev, "codec bus-frequency property "
+ "is missing or invalid\n");
+ ret = -EINVAL;
+ goto error;
+ }
+ ssi_info->clk_frequency = *iprop;
+ } else if (strcasecmp(sprop, "i2s-master") == 0) {
+ ssi_info->dai_format = SND_SOC_DAIFMT_I2S;
+ ssi_info->codec_clk_direction = SND_SOC_CLOCK_IN;
+ ssi_info->cpu_clk_direction = SND_SOC_CLOCK_OUT;
+ } else if (strcasecmp(sprop, "lj-slave") == 0) {
+ ssi_info->dai_format = SND_SOC_DAIFMT_LEFT_J;
+ ssi_info->codec_clk_direction = SND_SOC_CLOCK_OUT;
+ ssi_info->cpu_clk_direction = SND_SOC_CLOCK_IN;
+ } else if (strcasecmp(sprop, "lj-master") == 0) {
+ ssi_info->dai_format = SND_SOC_DAIFMT_LEFT_J;
+ ssi_info->codec_clk_direction = SND_SOC_CLOCK_IN;
+ ssi_info->cpu_clk_direction = SND_SOC_CLOCK_OUT;
+ } else if (strcasecmp(sprop, "rj-master") == 0) {
+ ssi_info->dai_format = SND_SOC_DAIFMT_RIGHT_J;
+ ssi_info->codec_clk_direction = SND_SOC_CLOCK_OUT;
+ ssi_info->cpu_clk_direction = SND_SOC_CLOCK_IN;
+ } else if (strcasecmp(sprop, "rj-master") == 0) {
+ ssi_info->dai_format = SND_SOC_DAIFMT_RIGHT_J;
+ ssi_info->codec_clk_direction = SND_SOC_CLOCK_IN;
+ ssi_info->cpu_clk_direction = SND_SOC_CLOCK_OUT;
+ } else if (strcasecmp(sprop, "ac97-slave") == 0) {
+ ssi_info->dai_format = SND_SOC_DAIFMT_AC97;
+ ssi_info->codec_clk_direction = SND_SOC_CLOCK_OUT;
+ ssi_info->cpu_clk_direction = SND_SOC_CLOCK_IN;
+ } else if (strcasecmp(sprop, "ac97-master") == 0) {
+ ssi_info->dai_format = SND_SOC_DAIFMT_AC97;
+ ssi_info->codec_clk_direction = SND_SOC_CLOCK_IN;
+ ssi_info->cpu_clk_direction = SND_SOC_CLOCK_OUT;
+ } else {
+ dev_err(&ofdev->dev,
+ "unrecognized fsl,mode property \"%s\"\n", sprop);
+ ret = -EINVAL;
+ goto error;
+ }
- ssi_private->dev->driver_data = fsl_ssi_dai;
+ if (!ssi_info->clk_frequency) {
+ dev_err(&ofdev->dev, "unknown clock frequency\n");
+ ret = -EINVAL;
+ goto error;
+ }
- /* Initialize the the device_attribute structure */
- dev_attr->attr.name = "ssi-stats";
- dev_attr->attr.mode = S_IRUGO;
- dev_attr->show = fsl_sysfs_ssi_show;
+ /* Read the SSI hardware information from the device tree */
+ ret = of_address_to_resource(np, 0, &res);
+ if (ret) {
+ dev_err(&ofdev->dev, "could not obtain SSI address\n");
+ goto error;
+ }
+ if (!res.start) {
+ dev_err(&ofdev->dev, "invalid SSI address\n");
+ goto error;
+ }
+ ssi_info->ssi_phys = res.start;
+
+ ssi_info->ssi = ioremap(ssi_info->ssi_phys, sizeof(struct ccsr_ssi));
+ if (!ssi_info->ssi) {
+ dev_err(&ofdev->dev, "could not map SSI address %x\n",
+ ssi_info->ssi_phys);
+ ret = -EINVAL;
+ goto error;
+ }
- ret = device_create_file(ssi_private->dev, dev_attr);
+ /* Get the IRQ of the SSI */
+ ssi_info->irq = irq_of_parse_and_map(np, 0);
+ if (!ssi_info->irq) {
+ dev_err(&ofdev->dev, "could not get SSI IRQ\n");
+ ret = -EINVAL;
+ goto error;
+ }
+
+ ssi_info->dma_info[0].ssi_sxx_phys = ssi_info->ssi_phys +
+ offsetof(struct ccsr_ssi, stx0);
+ ssi_info->dma_info[1].ssi_sxx_phys = ssi_info->ssi_phys +
+ offsetof(struct ccsr_ssi, srx0);
+
+ /*
+ * Get the DMA information. If it's an older device tree (i.e. without
+ * an "fsl,playback-dma" property), then we assume that SSI1 uses DMA1
+ * Channels 0 and 1, and SSI2 uses DMA2 Channels 0 and 1.
+ */
+ dma_ph = of_get_property(np, "fsl,playback-dma", NULL);
+ if (!dma_ph) {
+ dev_warn(&ofdev->dev, "please update your device tree\n");
+ dma_np[0] = find_dma_node(ssi_info->id, 0);
+ dma_np[1] = find_dma_node(ssi_info->id, 1);
+ } else {
+ dma_np[0] = of_find_node_by_phandle(*dma_ph);
+ dma_ph = of_get_property(np, "fsl,capture-dma", NULL);
+ if (dma_ph)
+ dma_np[1] = of_find_node_by_phandle(*dma_ph);
+ }
+
+ if (!get_dma_info(dma_np[0], &ssi_info->dma_info[0])) {
+ dev_err(&ofdev->dev, "could not obtain playback DMA info\n");
+ goto error;
+ }
+
+ if (!get_dma_info(dma_np[1], &ssi_info->dma_info[1])) {
+ dev_err(&ofdev->dev, "could not obtain capture DMA info\n");
+ goto error;
+ }
+
+ memset(&dai_template, 0, sizeof(dai_template));
+ dai_template.name = ssi_info->name;
+ dai_template.id = ssi_info->id;
+ dai_template.playback = &playback;
+ dai_template.capture = &capture;
+ dai_template.ops = &ops;
+
+ ssi_info->dai =
+ snd_soc_register_platform_dai(&dai_template, &ofdev->dev);
+ if (!ssi_info->dai) {
+ dev_err(&ofdev->dev, "could not register platform DAI\n");
+ ret = -ENOMEM;
+ goto error;
+ }
+
+ ssi_info->dai->private_data = ssi_info;
+
+ ssi_info->dev_attr.attr.name = ssi_info->name;
+ ssi_info->dev_attr.attr.mode = S_IRUGO;
+ ssi_info->dev_attr.show = fsl_sysfs_ssi_show;
+
+ ret = device_create_file(&ofdev->dev, &ssi_info->dev_attr);
if (ret) {
- dev_err(ssi_info->dev, "could not create sysfs %s file\n",
- ssi_private->dev_attr.attr.name);
- kfree(fsl_ssi_dai);
- return NULL;
+ dev_err(&ofdev->dev, "could not create sysfs %s file\n",
+ ssi_info->dev_attr.attr.name);
+ goto error;
}
- fsl_ssi_dai->private_data = ssi_private;
- fsl_ssi_dai->name = ssi_private->name;
- fsl_ssi_dai->id = ssi_info->id;
+ dev_set_drvdata(&ofdev->dev, ssi_info);
+
+
+
+ return 0;
+
+error:
+
+ if (ssi_info->dai)
+ snd_soc_unregister_platform_dai(ssi_info->dai);
+
+ if (ssi_info->irq)
+ irq_dispose_mapping(ssi_info->irq);
+
+ if (ssi_info->ssi)
+ iounmap(ssi_info->ssi);
- return fsl_ssi_dai;
+ kfree(ssi_info);
+
+ return ret;
}
-EXPORT_SYMBOL_GPL(fsl_ssi_create_dai);
/**
- * fsl_ssi_destroy_dai: destroy the snd_soc_cpu_dai object
+ * fsl_ssi_remove: remove the OF device
*
- * This function undoes the operations of fsl_ssi_create_dai()
+ * This function is called when the OF device is removed.
*/
-void fsl_ssi_destroy_dai(struct snd_soc_cpu_dai *fsl_ssi_dai)
+static int fsl_ssi_remove(struct of_device *ofdev)
{
- struct fsl_ssi_private *ssi_private =
- container_of(fsl_ssi_dai, struct fsl_ssi_private, cpu_dai);
+ struct fsl_ssi_info *ssi_info = dev_get_drvdata(&ofdev->dev);
+
+ if (ssi_info->dai)
+ snd_soc_unregister_platform_dai(ssi_info->dai);
+
+ if (ssi_info->irq)
+ irq_dispose_mapping(ssi_info->irq);
+
+ if (ssi_info->ssi)
+ iounmap(ssi_info->ssi);
- device_remove_file(ssi_private->dev, &ssi_private->dev_attr);
+ kfree(ssi_info);
- kfree(ssi_private);
+ dev_set_drvdata(&ofdev->dev, NULL);
+
+ return 0;
}
-/*
- * TODO: we probably want to remove this dai template and dynamically
- * create our DAI in platform probe() based on device tree parse data.
+static struct of_device_id fsl_ssi_match[] = {
+ {
+ .compatible = "fsl,mpc8610-ssi",
+ },
+ {}
+};
+MODULE_DEVICE_TABLE(of, fsl_ssi_match);
+
+static struct of_platform_driver fsl_ssi_of_driver = {
+ .owner = THIS_MODULE,
+ .name = "fsl_ssi",
+ .match_table = fsl_ssi_match,
+ .probe = fsl_ssi_probe,
+ .remove = fsl_ssi_remove,
+};
+
+/**
+ * fsl_ssi_init: fabric driver initialization.
+ *
+ * This function is called when this module is loaded.
*/
-struct snd_soc_dai fsl_ssi_template = {
+static int __init fsl_ssi_init(void)
{
- .name = "SSI0-0",
- .id = IMX_DAI_SSI0,
- .new = fsl_ssi_create_dai,
- .free = fsl_ssi_destroy_dai,
-
- .playback = {
- /* The SSI does not support monaural audio. */
- .channels_min = 2,
- .channels_max = 2,
- .rates = FSLSSI_I2S_RATES,
- .formats = FSLSSI_I2S_FORMATS,
- },
- .capture = {
- .channels_min = 2,
- .channels_max = 2,
- .rates = FSLSSI_I2S_RATES,
- .formats = FSLSSI_I2S_FORMATS,
- },
+ int ret;
- /* alsa ops */
- .startup = fsl_ssi_startup,
- .prepare = fsl_ssi_prepare,
- .shutdown = fsl_ssi_shutdown,
- .trigger = fsl_ssi_trigger,
+ printk(KERN_INFO "Freescale SSI ASoC driver\n");
- /* dai ops */
- .set_sysclk = fsl_ssi_set_sysclk,
- .set_fmt = fsl_ssi_set_fmt,
-},
-EXPORT_SYMBOL_GPL(fsl_ssi_template);
+ ret = of_register_platform_driver(&fsl_ssi_of_driver);
+
+ if (ret)
+ printk(KERN_ERR
+ "fsl-ssi: failed to register SSI driver\n");
+
+ return ret;
+}
+
+/**
+ * fsl_ssi_exit: fabric driver exit
+ *
+ * This function is called when this driver is unloaded.
+ */
+static void __exit fsl_ssi_exit(void)
+{
+ of_unregister_platform_driver(&fsl_ssi_of_driver);
+}
+
+module_init(fsl_ssi_init);
+module_exit(fsl_ssi_exit);
MODULE_AUTHOR("Timur Tabi <timur(a)freescale.com>");
-MODULE_DESCRIPTION("Freescale Synchronous Serial Interface (SSI) ASoC Driver");
+MODULE_DESCRIPTION("Freescale SSI ASoC driver");
MODULE_LICENSE("GPL");
diff --git a/sound/soc/fsl/fsl_ssi.h b/sound/soc/fsl/fsl_ssi.h
index 0aaafa4..05fd84d 100644
--- a/sound/soc/fsl/fsl_ssi.h
+++ b/sound/soc/fsl/fsl_ssi.h
@@ -196,29 +196,63 @@ struct ccsr_ssi {
#define CCSR_SSI_SOR_WAIT(x) (((x) & 3) << CCSR_SSI_SOR_WAIT_SHIFT)
#define CCSR_SSI_SOR_SYNRST 0x00000001
-/* Instantiation data for an SSI interface
+/**
+ * fsl_ssi_info: per-SSI private data
*
- * This structure contains all the information that the the SSI driver needs
- * to instantiate an SSI interface with ALSA. The soc_card driver should
- * create this structure, fill it in, call fsl_ssi_create_dai(), and then
- * delete the structure.
- *
- * id: which SSI this is (0, 1, etc. )
- * ssi: pointer to the SSI's registers
- * ssi_phys: physical address of the SSI registers
- * irq: IRQ of this SSI
- * dev: struct device, used to create the sysfs statistics file
-*/
+ * @name: short name for this device ("SSI0", "SSI1", etc)
+ * @ssi: pointer to the SSI's registers
+ * @ssi_phys: physical address of the SSI registers
+ * @irq: IRQ of this SSI
+ * @dev: struct device pointer
+ * @playback: the number of playback streams opened
+ * @capture: the number of capture streams opened
+ * @cpu_dai: the CPU DAI for this device
+ * @dev_attr: the sysfs device attribute structure
+ * @stats: SSI statistics
+ */
struct fsl_ssi_info {
+ char name[16];
unsigned int id;
struct ccsr_ssi __iomem *ssi;
dma_addr_t ssi_phys;
unsigned int irq;
struct device *dev;
-};
+ unsigned int playback;
+ unsigned int capture;
+ struct snd_soc_dai *dai;
+ struct device_attribute dev_attr;
-struct snd_soc_cpu_dai *fsl_ssi_create_dai(struct fsl_ssi_info *ssi_info);
-void fsl_ssi_destroy_dai(struct snd_soc_cpu_dai *fsl_ssi_dai);
+ unsigned int dai_format;
+ unsigned int codec_clk_direction;
+ unsigned int cpu_clk_direction;
+ unsigned long clk_frequency;
+
+ struct fsl_dma_info dma_info[2];
+
+ struct {
+ unsigned int rfrc;
+ unsigned int tfrc;
+ unsigned int cmdau;
+ unsigned int cmddu;
+ unsigned int rxt;
+ unsigned int rdr1;
+ unsigned int rdr0;
+ unsigned int tde1;
+ unsigned int tde0;
+ unsigned int roe1;
+ unsigned int roe0;
+ unsigned int tue1;
+ unsigned int tue0;
+ unsigned int tfs;
+ unsigned int rfs;
+ unsigned int tls;
+ unsigned int rls;
+ unsigned int rff1;
+ unsigned int rff0;
+ unsigned int tfe1;
+ unsigned int tfe0;
+ } stats;
+};
#endif
diff --git a/sound/soc/fsl/mpc8610_hpcd.c b/sound/soc/fsl/mpc8610_hpcd.c
dissimilarity index 60%
index e154e3f..a857e57 100644
--- a/sound/soc/fsl/mpc8610_hpcd.c
+++ b/sound/soc/fsl/mpc8610_hpcd.c
@@ -1,642 +1,353 @@
-/**
- * Freescale MPC8610HPCD ALSA SoC Fabric driver
- *
- * Author: Timur Tabi <timur(a)freescale.com>
- *
- * Copyright 2007-2008 Freescale Semiconductor, Inc. This file is licensed
- * under the terms of the GNU General Public License version 2. This
- * program is licensed "as is" without any warranty of any kind, whether
- * express or implied.
- */
-
-#include <linux/module.h>
-#include <linux/interrupt.h>
-#include <linux/of_device.h>
-#include <linux/of_platform.h>
-#include <sound/soc.h>
-#include <asm/immap_86xx.h>
-
-#include "../codecs/cs4270.h"
-#include "fsl_dma.h"
-#include "fsl_ssi.h"
-
-/**
- * mpc8610_hpcd_data: fabric-specific ASoC device data
- *
- * This structure contains data for a single sound platform device on an
- * MPC8610 HPCD. Some of the data is taken from the device tree.
- */
-struct mpc8610_hpcd_data {
- struct snd_soc_device sound_devdata;
- struct snd_soc_dai_link dai;
- struct snd_soc_card soc_card;
- unsigned int dai_format;
- unsigned int codec_clk_direction;
- unsigned int cpu_clk_direction;
- unsigned int clk_frequency;
- struct ccsr_guts __iomem *guts;
- struct ccsr_ssi __iomem *ssi;
- unsigned int ssi_id; /* 0 = SSI1, 1 = SSI2, etc */
- unsigned int ssi_irq;
- unsigned int dma_id; /* 0 = DMA1, 1 = DMA2, etc */
- unsigned int dma_irq[2];
- struct ccsr_dma_channel __iomem *dma[2];
- unsigned int dma_channel_id[2]; /* 0 = ch 0, 1 = ch 1, etc*/
-};
-
-/**
- * mpc8610_hpcd_audio_init: initalize the board
- *
- * This function is called when platform_device_add() is called. It is used
- * to initialize the board-specific hardware.
- *
- * Here we program the DMACR and PMUXCR registers.
- */
-static int mpc8610_hpcd_audio_init(struct snd_soc_card *soc_card)
-{
- struct mpc8610_hpcd_data *soc_card_data = soc_card->private_data;
-
- /* Program the signal routing between the SSI and the DMA */
- guts_set_dmacr(soc_card_data->guts, soc_card_data->dma_id + 1,
- soc_card_data->dma_channel_id[0], CCSR_GUTS_DMACR_DEV_SSI);
- guts_set_dmacr(soc_card_data->guts, soc_card_data->dma_id + 1,
- soc_card_data->dma_channel_id[1], CCSR_GUTS_DMACR_DEV_SSI);
-
- guts_set_pmuxcr_dma(soc_card_data->guts, soc_card_data->dma_id,
- soc_card_data->dma_channel_id[0], 0);
- guts_set_pmuxcr_dma(soc_card_data->guts, soc_card_data->dma_id,
- soc_card_data->dma_channel_id[1], 0);
-
- guts_set_pmuxcr_dma(soc_card_data->guts, 1, 0, 0);
- guts_set_pmuxcr_dma(soc_card_data->guts, 1, 3, 0);
- guts_set_pmuxcr_dma(soc_card_data->guts, 0, 3, 0);
-
- switch (soc_card_data->ssi_id) {
- case 0:
- clrsetbits_be32(&soc_card_data->guts->pmuxcr,
- CCSR_GUTS_PMUXCR_SSI1_MASK, CCSR_GUTS_PMUXCR_SSI1_SSI);
- break;
- case 1:
- clrsetbits_be32(&soc_card_data->guts->pmuxcr,
- CCSR_GUTS_PMUXCR_SSI2_MASK, CCSR_GUTS_PMUXCR_SSI2_SSI);
- break;
- }
-
- return 0;
-}
-
-/**
- * mpc8610_hpcd_startup: program the board with various hardware parameters
- *
- * This function takes board-specific information, like clock frequencies
- * and serial data formats, and passes that information to the codec and
- * transport drivers.
- */
-static int mpc8610_hpcd_startup(struct snd_pcm_substream *substream)
-{
- struct snd_soc_pcm_runtime *rtd = substream->private_data;
- struct snd_soc_dai *codec_dai = rtd->dai->codec_dai;
- struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai;
- struct mpc8610_hpcd_data *soc_card_data =
- rtd->socdev->dev->platform_data;
- int ret = 0;
-
- /* Tell the CPU driver what the serial protocol is. */
- ret = snd_soc_dai_set_fmt(cpu_dai, soc_card_data->dai_format);
- if (ret < 0) {
- dev_err(substream->pcm->card->dev,
- "could not set CPU driver audio format\n");
- return ret;
- }
-
- /* Tell the codec driver what the serial protocol is. */
- ret = snd_soc_dai_set_fmt(codec_dai, soc_card_data->dai_format);
- if (ret < 0) {
- dev_err(substream->pcm->card->dev,
- "could not set codec driver audio format\n");
- return ret;
- }
-
- /*
- * Tell the CPU driver what the clock frequency is, and whether it's a
- * slave or master.
- */
- ret = snd_soc_dai_set_sysclk(cpu_dai, 0, soc_card_data->clk_frequency,
- soc_card_data->cpu_clk_direction);
- if (ret < 0) {
- dev_err(substream->pcm->card->dev,
- "could not set CPU driver clock parameters\n");
- return ret;
- }
-
- /*
- * Tell the codec driver what the MCLK frequency is, and whether it's
- * a slave or master.
- */
- ret = snd_soc_dai_set_sysclk(codec_dai, 0, soc_card_data->clk_frequency,
- soc_card_data->codec_clk_direction);
- if (ret < 0) {
- dev_err(substream->pcm->card->dev,
- "could not set codec driver clock params\n");
- return ret;
- }
-
- return 0;
-}
-
-/**
- * mpc8610_hpcd_audio_exit: Remove the sound device
- *
- * This function is called to remove the sound device for one SSI. We
- * de-program the DMACR and PMUXCR register.
- */
-int mpc8610_hpcd_audio_exit(struct snd_soc_card *soc_card)
-{
- struct mpc8610_hpcd_data *soc_card_data = soc_card->private_data;
-
- /* Restore the signal routing */
-
- guts_set_dmacr(soc_card_data->guts, soc_card_data->dma_id + 1,
- soc_card_data->dma_channel_id[0], 0);
- guts_set_dmacr(soc_card_data->guts, soc_card_data->dma_id + 1,
- soc_card_data->dma_channel_id[1], 0);
-
- switch (soc_card_data->ssi_id) {
- case 0:
- clrsetbits_be32(&soc_card_data->guts->pmuxcr,
- CCSR_GUTS_PMUXCR_SSI1_MASK, CCSR_GUTS_PMUXCR_SSI1_LA);
- break;
- case 1:
- clrsetbits_be32(&soc_card_data->guts->pmuxcr,
- CCSR_GUTS_PMUXCR_SSI2_MASK, CCSR_GUTS_PMUXCR_SSI1_LA);
- break;
- }
-
- return 0;
-}
-
-/**
- * mpc8610_hpcd_ops: ASoC fabric driver operations
- */
-static struct snd_soc_ops mpc8610_hpcd_ops = {
- .startup = mpc8610_hpcd_startup,
-};
-
-/**
- * mpc8610_hpcd_probe: OF probe function for the fabric driver
- *
- * This function gets called when an SSI node is found in the device tree.
- *
- * Although this is a fabric driver, the SSI node is the "master" node with
- * respect to audio hardware connections. Therefore, we create a new ASoC
- * device for each new SSI node that has a codec attached.
- *
- * FIXME: Currently, we only support one DMA controller, so if there are
- * multiple SSI nodes with codecs, only the first will be supported.
- *
- * FIXME: Even if we did support multiple DMA controllers, we have no
- * mechanism for assigning DMA controllers and channels to the individual
- * SSI devices. We also probably aren't compatible with the generic Elo DMA
- * device driver.
- */
-static int mpc8610_hpcd_probe(struct of_device *ofdev,
- const struct of_device_id *match)
-{
- struct snd_soc_card *soc_card;
- struct device_node *np = ofdev->node;
- struct device_node *codec_np = NULL;
- struct device_node *guts_np = NULL;
- struct device_node *dma_np = NULL;
- struct device_node *dma_channel_np = NULL;
- const phandle *codec_ph;
- const char *sprop;
- const u32 *iprop;
- struct resource res;
- struct mpc8610_hpcd_data *soc_card_data;
-
-/* TODO: ssi_info and dma_info could now be created in the platform driver
- * during it's probe. A lot of this code could be moved to platform probe() */
-
- struct fsl_ssi_info ssi_info;
- struct fsl_dma_info dma_info;
- int ret = -ENODEV;
-
- soc_card_data = kzalloc(sizeof(struct mpc8610_hpcd_data), GFP_KERNEL);
- if (!soc_card_data)
- return -ENOMEM;
-
- memset(&ssi_info, 0, sizeof(ssi_info));
- memset(&dma_info, 0, sizeof(dma_info));
-
- ssi_info.dev = &ofdev->dev;
-
- /*
- * We are only interested in SSIs with a codec phandle in them, so let's
- * make sure this SSI has one.
- */
- codec_ph = of_get_property(np, "codec-handle", NULL);
- if (!codec_ph)
- goto error;
-
- codec_np = of_find_node_by_phandle(*codec_ph);
- if (!codec_np)
- goto error;
-
- /* The MPC8610 HPCD only knows about the CS4270 codec, so reject
- anything else. */
- if (!of_device_is_compatible(codec_np, "cirrus,cs4270"))
- goto error;
-
- /* Get the device ID */
- iprop = of_get_property(np, "cell-index", NULL);
- if (!iprop) {
- dev_err(&ofdev->dev, "cell-index property not found\n");
- ret = -EINVAL;
- goto error;
- }
- soc_card_data->ssi_id = *iprop;
- ssi_info.id = *iprop;
-
- /* Get the serial format and clock direction. */
- sprop = of_get_property(np, "fsl,mode", NULL);
- if (!sprop) {
- dev_err(&ofdev->dev, "fsl,mode property not found\n");
- ret = -EINVAL;
- goto error;
- }
-
- if (strcasecmp(sprop, "i2s-slave") == 0) {
- soc_card_data->dai_format = SND_SOC_DAIFMT_I2S;
- soc_card_data->codec_clk_direction = SND_SOC_CLOCK_OUT;
- soc_card_data->cpu_clk_direction = SND_SOC_CLOCK_IN;
-
- /*
- * In i2s-slave mode, the codec has its own clock source, so we
- * need to get the frequency from the device tree and pass it to
- * the codec driver.
- */
- iprop = of_get_property(codec_np, "clock-frequency", NULL);
- if (!iprop || !*iprop) {
- dev_err(&ofdev->dev, "codec bus-frequency property "
- "is missing or invalid\n");
- ret = -EINVAL;
- goto error;
- }
- soc_card_data->clk_frequency = *iprop;
- } else if (strcasecmp(sprop, "i2s-master") == 0) {
- soc_card_data->dai_format = SND_SOC_DAIFMT_I2S;
- soc_card_data->codec_clk_direction = SND_SOC_CLOCK_IN;
- soc_card_data->cpu_clk_direction = SND_SOC_CLOCK_OUT;
- } else if (strcasecmp(sprop, "lj-slave") == 0) {
- soc_card_data->dai_format = SND_SOC_DAIFMT_LEFT_J;
- soc_card_data->codec_clk_direction = SND_SOC_CLOCK_OUT;
- soc_card_data->cpu_clk_direction = SND_SOC_CLOCK_IN;
- } else if (strcasecmp(sprop, "lj-master") == 0) {
- soc_card_data->dai_format = SND_SOC_DAIFMT_LEFT_J;
- soc_card_data->codec_clk_direction = SND_SOC_CLOCK_IN;
- soc_card_data->cpu_clk_direction = SND_SOC_CLOCK_OUT;
- } else if (strcasecmp(sprop, "rj-master") == 0) {
- soc_card_data->dai_format = SND_SOC_DAIFMT_RIGHT_J;
- soc_card_data->codec_clk_direction = SND_SOC_CLOCK_OUT;
- soc_card_data->cpu_clk_direction = SND_SOC_CLOCK_IN;
- } else if (strcasecmp(sprop, "rj-master") == 0) {
- soc_card_data->dai_format = SND_SOC_DAIFMT_RIGHT_J;
- soc_card_data->codec_clk_direction = SND_SOC_CLOCK_IN;
- soc_card_data->cpu_clk_direction = SND_SOC_CLOCK_OUT;
- } else if (strcasecmp(sprop, "ac97-slave") == 0) {
- soc_card_data->dai_format = SND_SOC_DAIFMT_AC97;
- soc_card_data->codec_clk_direction = SND_SOC_CLOCK_OUT;
- soc_card_data->cpu_clk_direction = SND_SOC_CLOCK_IN;
- } else if (strcasecmp(sprop, "ac97-master") == 0) {
- soc_card_data->dai_format = SND_SOC_DAIFMT_AC97;
- soc_card_data->codec_clk_direction = SND_SOC_CLOCK_IN;
- soc_card_data->cpu_clk_direction = SND_SOC_CLOCK_OUT;
- } else {
- dev_err(&ofdev->dev,
- "unrecognized fsl,mode property \"%s\"\n", sprop);
- ret = -EINVAL;
- goto error;
- }
-
- if (!soc_card_data->clk_frequency) {
- dev_err(&ofdev->dev, "unknown clock frequency\n");
- ret = -EINVAL;
- goto error;
- }
-
- /* Read the SSI information from the device tree */
- ret = of_address_to_resource(np, 0, &res);
- if (ret) {
- dev_err(&ofdev->dev, "could not obtain SSI address\n");
- goto error;
- }
- if (!res.start) {
- dev_err(&ofdev->dev, "invalid SSI address\n");
- goto error;
- }
- ssi_info.ssi_phys = res.start;
-
- soc_card_data->ssi = ioremap(ssi_info.ssi_phys, sizeof(struct ccsr_ssi));
- if (!soc_card_data->ssi) {
- dev_err(&ofdev->dev, "could not map SSI address %x\n",
- ssi_info.ssi_phys);
- ret = -EINVAL;
- goto error;
- }
- ssi_info.ssi = soc_card_data->ssi;
-
-
- /* Get the IRQ of the SSI */
- soc_card_data->ssi_irq = irq_of_parse_and_map(np, 0);
- if (!soc_card_data->ssi_irq) {
- dev_err(&ofdev->dev, "could not get SSI IRQ\n");
- ret = -EINVAL;
- goto error;
- }
- ssi_info.irq = soc_card_data->ssi_irq;
-
-
- /* Map the global utilities registers. */
- guts_np = of_find_compatible_node(NULL, NULL, "fsl,mpc8610-guts");
- if (!guts_np) {
- dev_err(&ofdev->dev, "could not obtain address of GUTS\n");
- ret = -EINVAL;
- goto error;
- }
- soc_card_data->guts = of_iomap(guts_np, 0);
- of_node_put(guts_np);
- if (!soc_card_data->guts) {
- dev_err(&ofdev->dev, "could not map GUTS\n");
- ret = -EINVAL;
- goto error;
- }
-
- /* Find the DMA channels to use. For now, we always use the first DMA
- controller. */
- for_each_compatible_node(dma_np, NULL, "fsl,mpc8610-dma") {
- iprop = of_get_property(dma_np, "cell-index", NULL);
- if (iprop && (*iprop == 0)) {
- of_node_put(dma_np);
- break;
- }
- }
- if (!dma_np) {
- dev_err(&ofdev->dev, "could not find DMA node\n");
- ret = -EINVAL;
- goto error;
- }
- soc_card_data->dma_id = *iprop;
-
- /*
- * Find the DMA channels to use. For now, we always use DMA channel 0
- * for playback, and DMA channel 1 for capture.
- */
- while ((dma_channel_np = of_get_next_child(dma_np, dma_channel_np))) {
- iprop = of_get_property(dma_channel_np, "cell-index", NULL);
- /* Is it DMA channel 0? */
- if (iprop && (*iprop == 0)) {
- /* dma_channel[0] and dma_irq[0] are for playback */
- dma_info.dma_channel[0] = of_iomap(dma_channel_np, 0);
- dma_info.dma_irq[0] =
- irq_of_parse_and_map(dma_channel_np, 0);
- soc_card_data->dma_channel_id[0] = *iprop;
- continue;
- }
- if (iprop && (*iprop == 1)) {
- /* dma_channel[1] and dma_irq[1] are for capture */
- dma_info.dma_channel[1] = of_iomap(dma_channel_np, 0);
- dma_info.dma_irq[1] =
- irq_of_parse_and_map(dma_channel_np, 0);
- soc_card_data->dma_channel_id[1] = *iprop;
- continue;
- }
- }
- if (!dma_info.dma_channel[0] || !dma_info.dma_channel[1] ||
- !dma_info.dma_irq[0] || !dma_info.dma_irq[1]) {
- dev_err(&ofdev->dev, "could not find DMA channels\n");
- ret = -EINVAL;
- goto error;
- }
-
- dma_info.ssi_stx_phys = ssi_info.ssi_phys +
- offsetof(struct ccsr_ssi, stx0);
- dma_info.ssi_srx_phys = ssi_info.ssi_phys +
- offsetof(struct ccsr_ssi, srx0);
-
- /* We have the DMA information, so tell the DMA driver what it is */
- if (!fsl_dma_configure(&dma_info)) {
- dev_err(&ofdev->dev, "could not instantiate DMA device\n");
- ret = -EBUSY;
- goto error;
- }
-
-
-#if 0 /* V1 TODO: remove */
- /*
- * Initialize our DAI data structure. We should probably get this
- * information from the device tree.
- */
- soc_card_data->dai.name = "CS4270";
- soc_card_data->dai.stream_name = "CS4270";
-
- soc_card_data->dai.cpu_dai = fsl_ssi_create_dai(&ssi_info);
- soc_card_data->dai.codec_dai = &cs4270_dai; /* The codec_dai we want */
- soc_card_data->dai.ops = &mpc8610_hpcd_ops;
-
- mpc8610_hpcd_soc_card.dai_link = &soc_card_data->dai;
-
- /* Allocate a new audio platform device structure */
- sound_device = platform_device_alloc("soc-audio", -1);
- if (!sound_device) {
- dev_err(&ofdev->dev, "platform device allocation failed\n");
- ret = -ENOMEM;
- goto error;
- }
-
- soc_card_data->sound_devdata.soc_card = &mpc8610_hpcd_soc_card;
- soc_card_data->sound_devdata.codec_dev = &soc_codec_device_cs4270;
- soc_card_data->sound_devdata.platform = &fsl_soc_platform;
-
- sound_device->dev.platform_data = soc_card_data;
-
-
- /* Set the platform device and ASoC device to point to each other */
- platform_set_drvdata(sound_device, &soc_card_data->sound_devdata);
-
- soc_card_data->sound_devdata.dev = &sound_device->dev;
-
-
- /* Tell ASoC to probe us. This will call mpc8610_hpcd_soc_card.probe(),
- if it exists. */
- ret = platform_device_add(sound_device);
-
- if (ret) {
- dev_err(&ofdev->dev, "platform device add failed\n");
- goto error;
- }
-#else /* V2 */
- soc_card = snd_soc_card_create("MPC8610", &ofdev->dev,
- SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
- if (soc_card == NULL)
- return -ENOMEM;
-
- soc_card->longname = "CS4270";
- soc_card->init = mpc8610_hpcd_audio_init;
- soc_card->exit = mpc8610_hpcd_audio_exit;
- soc_card->private_data = soc_card_data;
-
- /* TODO: the number, ID and config of codecs, platforms,
- * pcm's could be found in dev tree */
- ret = snd_soc_codec_create(soc_card, cs4270_codec_id);
- if (ret < 0)
- goto err;
-
- ret = snd_soc_platform_create(soc_card, fsl_platform_id);
- if (ret < 0)
- goto err;
-
- ret = snd_soc_pcm_create(soc_card, "HiFi", &mpc8610_hpcd_ops,
- CS4270_HIFI_DAI, FSL_DAI_SSI0, 1, 1);
- if (ret < 0)
- goto err;
-
- /* every has been added at this point */
- dev_set_drvdata(&ofdev->dev, soc_card);
- ret = snd_soc_card_register(soc_card);
- if (ret < 0)
- goto err;
-
-#endif
-
- dev_set_drvdata(&ofdev->dev, sound_device);
-
- return 0;
-
-error:
-
- of_node_put(codec_np);
- of_node_put(guts_np);
- of_node_put(dma_np);
- of_node_put(dma_channel_np);
-
- if (ssi_info.ssi)
- iounmap(ssi_info.ssi);
-
- if (ssi_info.irq)
- irq_dispose_mapping(ssi_info.irq);
-
- if (dma_info.dma_channel[0])
- iounmap(dma_info.dma_channel[0]);
-
- if (dma_info.dma_channel[1])
- iounmap(dma_info.dma_channel[1]);
-
- if (dma_info.dma_irq[0])
- irq_dispose_mapping(dma_info.dma_irq[0]);
-
- if (dma_info.dma_irq[1])
- irq_dispose_mapping(dma_info.dma_irq[1]);
-
- if (soc_card_data->guts)
- iounmap(soc_card_data->guts);
-
- kfree(soc_card_data);
-
- snd_soc_card_free(soc_card);
- return ret;
-}
-
-/**
- * mpc8610_hpcd_remove: remove the OF device
- *
- * This function is called when the OF device is removed.
- */
-static int mpc8610_hpcd_remove(struct of_device *ofdev)
-{
- struct snd_soc_card *soc_card = dev_get_drvdata(&ofdev->dev);
- struct mpc8610_hpcd_data *soc_card_data = soc_card->private_data;
-
-/* TODO: some of this will move to platform remove() */
-
- if (soc_card_data->dai.cpu_dai)
- fsl_ssi_destroy_dai(soc_card_data->dai.cpu_dai);
-
- if (soc_card_data->ssi)
- iounmap(soc_card_data->ssi);
-
- if (soc_card_data->dma[0])
- iounmap(soc_card_data->dma[0]);
-
- if (soc_card_data->dma[1])
- iounmap(soc_card_data->dma[1]);
-
- if (soc_card_data->dma_irq[0])
- irq_dispose_mapping(soc_card_data->dma_irq[0]);
-
- if (soc_card_data->dma_irq[1])
- irq_dispose_mapping(soc_card_data->dma_irq[1]);
-
- if (soc_card_data->guts)
- iounmap(soc_card_data->guts);
-
- kfree(soc_card_data);
-
- snd_soc_card_free(soc_card);
-
- dev_set_drvdata(&ofdev->dev, NULL);
-
- return 0;
-}
-
-static struct of_device_id mpc8610_hpcd_match[] = {
- {
- .compatible = "fsl,mpc8610-ssi",
- },
- {}
-};
-MODULE_DEVICE_TABLE(of, mpc8610_hpcd_match);
-
-static struct of_platform_driver mpc8610_hpcd_of_driver = {
- .owner = THIS_MODULE,
- .name = "mpc8610_hpcd",
- .match_table = mpc8610_hpcd_match,
- .probe = mpc8610_hpcd_probe,
- .remove = mpc8610_hpcd_remove,
-};
-
-/**
- * mpc8610_hpcd_init: fabric driver initialization.
- *
- * This function is called when this module is loaded.
- */
-static int __init mpc8610_hpcd_init(void)
-{
- int ret;
-
- printk(KERN_INFO "Freescale MPC8610 HPCD ALSA SoC fabric driver\n");
-
- ret = of_register_platform_driver(&mpc8610_hpcd_of_driver);
-
- if (ret)
- printk(KERN_ERR
- "mpc8610-hpcd: failed to register platform driver\n");
-
- return ret;
-}
-
-/**
- * mpc8610_hpcd_exit: fabric driver exit
- *
- * This function is called when this driver is unloaded.
- */
-static void __exit mpc8610_hpcd_exit(void)
-{
- of_unregister_platform_driver(&mpc8610_hpcd_of_driver);
-}
-
-module_init(mpc8610_hpcd_init);
-module_exit(mpc8610_hpcd_exit);
-
-MODULE_AUTHOR("Timur Tabi <timur(a)freescale.com>");
-MODULE_DESCRIPTION("Freescale MPC8610 HPCD ALSA SoC fabric driver");
-MODULE_LICENSE("GPL");
+/**
+ * Freescale MPC8610HPCD ALSA SoC Fabric driver
+ *
+ * Author: Timur Tabi <timur(a)freescale.com>
+ *
+ * Copyright 2007-2008 Freescale Semiconductor, Inc. This file is licensed
+ * under the terms of the GNU General Public License version 2. This
+ * program is licensed "as is" without any warranty of any kind, whether
+ * express or implied.
+ */
+
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/of_device.h>
+#include <linux/of_platform.h>
+
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <asm/immap_86xx.h>
+
+#include "../codecs/cs4270.h"
+#include "fsl_dma.h"
+#include "fsl_ssi.h"
+
+/**
+ * mpc8610_hpcd_data: fabric-specific ASoC device data
+ *
+ * This structure contains data for a single sound platform device on an
+ * MPC8610 HPCD. Some of the data is taken from the device tree.
+ */
+struct mpc8610_hpcd_data {
+ struct snd_soc_card soc_card;
+ unsigned int dai_format;
+ unsigned int codec_clk_direction;
+ unsigned int cpu_clk_direction;
+ unsigned int clk_frequency;
+ struct ccsr_guts __iomem *guts;
+};
+
+/**
+ * mpc8610_hpcd_audio_init: initalize the board
+ *
+ * This function is called when platform_device_add() is called. It is used
+ * to initialize the board-specific hardware.
+ *
+ * Here we program the DMACR and PMUXCR registers.
+ */
+static int mpc8610_hpcd_audio_init(struct snd_soc_card *soc_card)
+{
+ struct mpc8610_hpcd_data *soc_card_data = soc_card->private_data;
+ struct snd_soc_dai *cpu_dai =
+ snd_soc_card_get_dai(soc_card, "fsl,mpc8610-ssi");
+ struct fsl_ssi_info *ssi_info = cpu_dai->private_data;
+
+ /* Program the signal routing between the SSI and the DMA */
+ guts_set_dmacr(soc_card_data->guts,
+ ssi_info->dma_info[0].controller_id,
+ ssi_info->dma_info[0].channel_id, CCSR_GUTS_DMACR_DEV_SSI);
+ guts_set_dmacr(soc_card_data->guts,
+ ssi_info->dma_info[1].controller_id,
+ ssi_info->dma_info[1].channel_id, CCSR_GUTS_DMACR_DEV_SSI);
+
+ guts_set_pmuxcr_dma(soc_card_data->guts,
+ ssi_info->dma_info[0].controller_id,
+ ssi_info->dma_info[0].channel_id, 0);
+ guts_set_pmuxcr_dma(soc_card_data->guts,
+ ssi_info->dma_info[1].controller_id,
+ ssi_info->dma_info[1].channel_id, 0);
+
+ /* FIXME: Magic numbers? */
+ guts_set_pmuxcr_dma(soc_card_data->guts, 1, 0, 0);
+ guts_set_pmuxcr_dma(soc_card_data->guts, 1, 3, 0);
+ guts_set_pmuxcr_dma(soc_card_data->guts, 0, 3, 0);
+
+ switch (ssi_info->id) {
+ case 0:
+ clrsetbits_be32(&soc_card_data->guts->pmuxcr,
+ CCSR_GUTS_PMUXCR_SSI1_MASK, CCSR_GUTS_PMUXCR_SSI1_SSI);
+ break;
+ case 1:
+ clrsetbits_be32(&soc_card_data->guts->pmuxcr,
+ CCSR_GUTS_PMUXCR_SSI2_MASK, CCSR_GUTS_PMUXCR_SSI2_SSI);
+ break;
+ }
+
+ return 0;
+}
+
+/**
+ * mpc8610_hpcd_startup: program the board with various hardware parameters
+ *
+ * This function takes board-specific information, like clock frequencies
+ * and serial data formats, and passes that information to the codec and
+ * transport drivers.
+ */
+static int mpc8610_hpcd_startup(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ struct mpc8610_hpcd_data *soc_card_data = rtd->soc_card->private_data;
+ int ret = 0;
+
+ /* Tell the CPU driver what the serial protocol is. */
+ ret = snd_soc_dai_set_fmt(cpu_dai, soc_card_data->dai_format);
+ if (ret < 0) {
+ dev_err(substream->pcm->card->dev,
+ "could not set CPU driver audio format\n");
+ return ret;
+ }
+
+ /* Tell the codec driver what the serial protocol is. */
+ ret = snd_soc_dai_set_fmt(codec_dai, soc_card_data->dai_format);
+ if (ret < 0) {
+ dev_err(substream->pcm->card->dev,
+ "could not set codec driver audio format\n");
+ return ret;
+ }
+
+ /*
+ * Tell the CPU driver what the clock frequency is, and whether it's a
+ * slave or master.
+ */
+ ret = snd_soc_dai_set_sysclk(cpu_dai, 0, soc_card_data->clk_frequency,
+ soc_card_data->cpu_clk_direction);
+ if (ret < 0) {
+ dev_err(substream->pcm->card->dev,
+ "could not set CPU driver clock parameters\n");
+ return ret;
+ }
+
+ /*
+ * Tell the codec driver what the MCLK frequency is, and whether it's
+ * a slave or master.
+ */
+ ret = snd_soc_dai_set_sysclk(codec_dai, 0, soc_card_data->clk_frequency,
+ soc_card_data->codec_clk_direction);
+ if (ret < 0) {
+ dev_err(substream->pcm->card->dev,
+ "could not set codec driver clock params\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+/**
+ * mpc8610_hpcd_audio_exit: Remove the sound device
+ *
+ * This function is called to remove the sound device for one SSI. We
+ * de-program the DMACR and PMUXCR register.
+ */
+void mpc8610_hpcd_audio_exit(struct snd_soc_card *soc_card)
+{
+ struct mpc8610_hpcd_data *soc_card_data = soc_card->private_data;
+ struct snd_soc_dai *cpu_dai =
+ snd_soc_card_get_dai(soc_card, "fsl,mpc8610-ssi");
+ struct fsl_ssi_info *ssi_info = cpu_dai->private_data;
+
+ /* Restore the signal routing */
+
+ guts_set_dmacr(soc_card_data->guts,
+ ssi_info->dma_info[0].controller_id,
+ ssi_info->dma_info[0].channel_id, 0);
+ guts_set_dmacr(soc_card_data->guts,
+ ssi_info->dma_info[1].controller_id,
+ ssi_info->dma_info[1].channel_id, 0);
+
+ switch (ssi_info->id) {
+ case 0:
+ clrsetbits_be32(&soc_card_data->guts->pmuxcr,
+ CCSR_GUTS_PMUXCR_SSI1_MASK, CCSR_GUTS_PMUXCR_SSI1_LA);
+ break;
+ case 1:
+ clrsetbits_be32(&soc_card_data->guts->pmuxcr,
+ CCSR_GUTS_PMUXCR_SSI2_MASK, CCSR_GUTS_PMUXCR_SSI1_LA);
+ break;
+ }
+}
+
+/**
+ * mpc8610_hpcd_ops: ASoC fabric driver operations
+ */
+static struct snd_soc_ops mpc8610_hpcd_ops = {
+ .startup = mpc8610_hpcd_startup,
+};
+
+static struct snd_soc_pcm_config mpc8610_pcm_config = {
+ .name = "MPC8610 HPCD",
+ .codec = "cirrus,cs4270",
+ .codec_dai = "cirrus,cs4270",
+ .platform = "fsl_pcm",
+ .cpu_dai = "fsl,mpc8610-ssi",
+ .ops = &mpc8610_hpcd_ops,
+ .playback = 1,
+ .capture = 1,
+};
+
+/**
+ * mpc8610_hpcd_probe: OF probe function for the fabric driver
+ *
+ * This function gets called when an SSI node is found in the device tree.
+ *
+ * Although this is a fabric driver, the SSI node is the "master" node with
+ * respect to audio hardware connections. Therefore, we create a new ASoC
+ * device for each new SSI node that has a codec attached.
+ */
+static int mpc8610_hpcd_probe(struct of_device *ofdev,
+ const struct of_device_id *match)
+{
+ struct snd_soc_card *soc_card = NULL;
+ struct device_node *guts_np = NULL;
+ struct mpc8610_hpcd_data *soc_card_data;
+ int ret = -ENODEV;
+
+ soc_card_data = kzalloc(sizeof(struct mpc8610_hpcd_data), GFP_KERNEL);
+ if (!soc_card_data) {
+ dev_err(&ofdev->dev, "could not allocate card structure\n");
+ return -ENOMEM;
+ }
+
+ /* Map the global utilities registers. */
+ guts_np = of_find_compatible_node(NULL, NULL, "fsl,mpc8610-guts");
+ if (!guts_np) {
+ dev_err(&ofdev->dev, "could not obtain address of GUTS\n");
+ ret = -EINVAL;
+ goto error;
+ }
+ soc_card_data->guts = of_iomap(guts_np, 0);
+ of_node_put(guts_np);
+ if (!soc_card_data->guts) {
+ dev_err(&ofdev->dev, "could not map GUTS\n");
+ ret = -EINVAL;
+ goto error;
+ }
+
+ soc_card = snd_soc_card_create("MPC8610", &ofdev->dev,
+ SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
+ if (!soc_card) {
+ dev_err(&ofdev->dev, "could not create card\n");
+ goto error;
+ }
+
+ soc_card->longname = "MPC8610HPCD";
+ soc_card->init = mpc8610_hpcd_audio_init;
+ soc_card->exit = mpc8610_hpcd_audio_exit;
+ soc_card->private_data = soc_card_data;
+ soc_card->dev = &ofdev->dev;
+
+ ret = snd_soc_card_create_pcms(soc_card, &mpc8610_pcm_config, 1);
+ if (ret) {
+ dev_err(&ofdev->dev, "could not create PCMs\n");
+ goto error;
+ }
+
+ /* every has been added at this point */
+ dev_set_drvdata(&ofdev->dev, soc_card);
+ ret = snd_soc_card_register(soc_card);
+ if (ret) {
+ dev_err(&ofdev->dev, "could not register card\n");
+ goto error;
+ }
+
+ return 0;
+
+error:
+ if (soc_card_data->guts)
+ iounmap(soc_card_data->guts);
+
+ kfree(soc_card_data);
+
+ if (soc_card)
+ snd_soc_card_free(soc_card);
+
+ return ret;
+}
+
+/**
+ * mpc8610_hpcd_remove: remove the OF device
+ *
+ * This function is called when the OF device is removed.
+ */
+static int mpc8610_hpcd_remove(struct of_device *ofdev)
+{
+ struct snd_soc_card *soc_card = dev_get_drvdata(&ofdev->dev);
+ struct mpc8610_hpcd_data *soc_card_data = soc_card->private_data;
+
+ if (soc_card_data->guts)
+ iounmap(soc_card_data->guts);
+
+ kfree(soc_card_data);
+
+ snd_soc_card_free(soc_card);
+
+ dev_set_drvdata(&ofdev->dev, NULL);
+
+ return 0;
+}
+
+static struct of_device_id mpc8610_hpcd_match[] = {
+ {
+ .compatible = "fsl,mpc8610-ssi",
+ },
+ {}
+};
+MODULE_DEVICE_TABLE(of, mpc8610_hpcd_match);
+
+static struct of_platform_driver mpc8610_hpcd_of_driver = {
+ .owner = THIS_MODULE,
+ .name = "mpc8610_hpcd",
+ .match_table = mpc8610_hpcd_match,
+ .probe = mpc8610_hpcd_probe,
+ .remove = mpc8610_hpcd_remove,
+};
+
+/**
+ * mpc8610_hpcd_init: fabric driver initialization.
+ *
+ * This function is called when this module is loaded.
+ */
+static int __init mpc8610_hpcd_init(void)
+{
+ int ret;
+
+ printk(KERN_INFO "Freescale MPC8610 HPCD ALSA SoC fabric driver\n");
+
+ ret = of_register_platform_driver(&mpc8610_hpcd_of_driver);
+
+ if (ret)
+ printk(KERN_ERR
+ "mpc8610-hpcd: failed to register platform driver\n");
+
+ return ret;
+}
+
+/**
+ * mpc8610_hpcd_exit: fabric driver exit
+ *
+ * This function is called when this driver is unloaded.
+ */
+static void __exit mpc8610_hpcd_exit(void)
+{
+ of_unregister_platform_driver(&mpc8610_hpcd_of_driver);
+}
+
+module_init(mpc8610_hpcd_init);
+module_exit(mpc8610_hpcd_exit);
+
+MODULE_AUTHOR("Timur Tabi <timur(a)freescale.com>");
+MODULE_DESCRIPTION("Freescale MPC8610 HPCD ALSA SoC fabric driver");
+MODULE_LICENSE("GPL");
--
1.5.5
1
0
Hi,
I built linux-2.6.25.2 kernel with ASoC driver for SMDK2412 board.
The following is dmesg,
Advanced Linux Sound Architecture Driver Version 1.0.16rc2 (Thu Jan 31
16:40:16
ASoC version 0.13.2
ALSA device
list:
No soundcards found.
I traced the soc_core.c and it never reaches soc_probe() routine.
That's why "No soundcards found."
Could you give me some light ?
Appreciated!
5
10
Heya!
In addition to the buffering issues pointed out on http://mailman.alsa-project.org/pipermail/alsa-devel/2008-April/007354.html
I now have some more issues with the way buffering works in ALSA.
This time I am pretty sure it's a bug in the HDA drivers.
PulseAudio basically does this (again, as part of the glitch-free playback
model, see http://0pointer.de/blog/projects/pulse-glitch-free.html):
for (;;) {
for (;;) {
snd_pcm_hwsync();
n = snd_pcm_avail_update();
if (!n)
break;
fill_up(n);
}
usleep(buffer_time - 20ms);
}
The buffer time is 370ms, so we usually sleep for 350ms.
It's a lot more complex actually, due to deviating timing and some
code that we don't enter a busy loop if the CPU is slower then the
sound card and so on and so on. However, basically it's this
algorithm.
This works fine on USB. However on HDA it sometimes happens that
snd_pcm_avail_update() is not properly updated at the right
time. Instead it seems to return what was current one iteration
earlier. I.e. we fill up the hw buffer until _avail_update() returns
0. Then assuming that it is fully filled up we go to sleep for
350ms. When we wake up we query _avail_update() again and it will
immediately return 0. We thus don't write anything to the
device. Instead we go to sleep for another 350ms. Of course, when we
wake up again the buffer will already have underrun (since 700 ms
passed since the last write to the buffer and th buffer is only 370ms
long) and we have a problem. In short: _avail_update() told us that
everything was alright and we trusted it and it lied to us.
The happens to be configured to 2 periods. So even if _avail_update()
has some kind of period granularity (?) it should always have told us
after we come back from the sleeping that at least *one* period is
free. But it said 0 frames. Nada. Rien. Nichts. Kaputt.
I *am* calling snd_pcm_hwsync(). So I'd assume that _avail_update()
would be as up to date as it gets. But it isn't. :-(
This happens regardless if I open the device as "front:0" (i.e. with
softvol in line) or as "hw:0" (no plugins).
This doesn't always happen. It seems to depend on the machine how
often this happens. On one machine I can reliably reproduce this after
30 iterations.
Lennart
--
Lennart Poettering Red Hat, Inc.
lennart [at] poettering [dot] net ICQ# 11060553
http://0pointer.net/lennart/ GnuPG 0x1A015CC4
3
5

Re: [alsa-devel] Intel hda ICH7 not worked anymore after using wine (SOLVED!). Problems again
by Xavi de Blas 16 May '08
by Xavi de Blas 16 May '08
16 May '08
Hello, we have been talking on this thread:
http://mailman.alsa-project.org/pipermail/alsa-devel/2007-July/001939.html
because i had a problem with my hda that Takashi said it's really
specific to my hardware:
http://mailman.alsa-project.org/pipermail/alsa-devel/2007-July/001963.html
Now with kernel 2.6.24-16 and alsa 1.0.16, i have no audio again:
i tried now using the patch Tobin made:
http://mailman.alsa-project.org/pipermail/alsa-devel/2007-July/001934.html
and it' doesn't work. In fact, i haven't used the patch, i only added the line:
{ .id = 0x51d41981, .name = "AD1981", .patch = patch_ad1981 },
after the line:
{ .id = 0x11d41981, .name = "AD1981", .patch = patch_ad1981 },
on: /usr/local/src/alsa-driver-1.0.16/alsa-kernel/pci/hda/patch_analog.c
./configure, make, sudo make install
on reboot, the error is the same as before:
dmesg|grep alsa
[ 41.284537] ALSA
/build/buildd/linux-ubuntu-modules-2.6.24-2.6.24/debian/build/build-generic/sound/alsa-driver/pci/hda/hda_codec.c:230:
hda_codec: invalid dep_range_val 53:1a
here is my /proc/asound/card0/codec#0
http://pastebin.ca/1018596
Thanks a lot
2007/7/9 Xavi de Blas <xaviblas(a)gmail.com>:
> It worked!!!
>
> Thanks a lot
>
> I thought my hardware was broken!
>
> 2007/7/9, Tobin Davis <tdavis(a)dsl-only.net>:
>>
>> Patch needs to be applied as follows:
>>
>> cd alsa-driver-1.0.14/alsa-kernel
>> patch -p1 < LenovoX60S.patch
>> cd ..
>>
>> Then you can rerun configure && make && make install.
>>
>> Tobin
>>
>> On Mon, 2007-07-09 at 17:25 +0200, Xavi de Blas wrote:
>>
>> 2007/7/9, Tobin Davis <tdavis(a)dsl-only.net>:
>> > Sorry for missing this thread. I just woke up.
>> >
>> > I looked at your first pastebin output, and the actual audio codec shows
>> > up as 0x51d41981, when it should be 0x11d41981. This is why the Analog
>> > driver patch isn't detecting it.
>> >
>> > Can you try this patch against alsa-driver-1.0.14? It should detect
>> > this codec. Still not sure why it is misconfigured, unless there is a
>> > bios issue.
>> >
>>
>> I'm afraid, probably when i tried the Linuxant codecs for the modem it
>> broke the
>> bios. Not sure, but sound worked, and continued working some reboots
>> later. But when i tried a few things with wine and rebooted, sound
>> never worked again
>>
>> When i aply the patch (probably i don't do it good) there are problems:
>>
>>
>> xavier@corall:/usr/local/src/alsa-src/alsa-driver-1.0.14$ cat
>> LenovoX60S.patch | patch -p1
>> patching file pci/hda/patch_analog.c
>> Hunk #1 FAILED at 3402.
>> 1 out of 1 hunk FAILED -- saving rejects to file
>> pci/hda/patch_analog.c.rej
>>
>>
>> xavier@corall:/usr/local/src/alsa-src/alsa-driver-1.0.14/pci/hda$ cat
>> patch_analog.c.rej
>> *************** struct hda_codec_preset snd_hda_preset_a
>> *** 3402,3407 ****
>> { .id = 0x11d41882, .name = "AD1882", .patch = patch_ad1882 },
>> { .id = 0x11d41884, .name = "AD1884", .patch = patch_ad1884 },
>> { .id = 0x11d41981, .name = "AD1981", .patch = patch_ad1981 },
>> { .id = 0x11d41983, .name = "AD1983", .patch = patch_ad1983 },
>> { .id = 0x11d41984, .name = "AD1984", .patch = patch_ad1984 },
>> { .id = 0x11d41986, .name = "AD1986A", .patch = patch_ad1986a },
>> --- 3402,3408 ----
>> { .id = 0x11d41882, .name = "AD1882", .patch = patch_ad1882 },
>> { .id = 0x11d41884, .name = "AD1884", .patch = patch_ad1884 },
>> { .id = 0x11d41981, .name = "AD1981", .patch = patch_ad1981 },
>> + { .id = 0x51d41981, .name = "AD1981", .patch = patch_ad1981 },
>> { .id = 0x11d41983, .name = "AD1983", .patch = patch_ad1983 },
>> { .id = 0x11d41984, .name = "AD1984", .patch = patch_ad1984 },
>> { .id = 0x11d41986, .name = "AD1986A", .patch = patch_ad1986a },
>>
>>
>> The patch is ok?
>>
>> Thanks
>>
>> (Tobin sorry for sending messge two times)
>>
>> --
>> Tobin Davis <tdavis(a)dsl-only.net>
>
2
3

[alsa-devel] [PATCH] PCI168 snd-azt3328 Linux driver: another huge update
by Andreas Mohr 16 May '08
by Andreas Mohr 16 May '08
16 May '08
Hello all (and especially my fellow driver users!),
ok, color me crazy, but somehow I really felt like doing another round
of Undocumented Hacking (tm) again, with my nice sound card,
after an overly long total inactivity.
- figured out "Digital(ly) Enhanced Game Port" functionality,
implemented support for it (eliminating gameport polling overhead)
- removed optional joystick activation, gameport now enabled unconditionally,
since we now support it via the PCI I/O space, not via conflict-prone
legacy I/O (which I was thus able to DISABLE now)!
- fix playback bug (a muted wave output would get unmuted upon start of
playback, of course this is not what we want, thus remember mute state)
- implement partial power management: when idle, lower clock rate and disable
codec (reduced noise!), and disable gameport circuit when unused
- instantiate OPL3 timer, too
- much better implementation of snd_azf3328_mixer_write_volume_gradually()
- slightly optimized interrupt handling
- lots of cleanup
This time, I also found a way to verify proper OPL3 operation
via MIDI file playback (emulation via synth hardware).
Patch done and hardware-tested against 2.6.26-rc1, which should hopefully
match against current ALSA.
Everything checked against checkpatch.pl (lots of corrections there as well).
Oh, and please don't ask me to split up everything again, lest I'll kill myself
- way too many changes this time, and frankly I don't care whether it's
debuggable or not since probably not too many people use this driver anyway
and I'll fix any problems if/when they turn up (I'm still quite actively
enjoying this card myself, thank you very much ;).
Note that the new version has an object size increase of merely
about 500 bytes, despite all these improvements!
Given these additions, the azt3328 driver should now easily have managed to
make the PCI168 the best-supported fully undocumented soundcard
in the Linux universe ;)
(one could say that this card is "a wee bit too old" already,
but it's giving me a very nice peace of mind to know that it's
almost fully supported now, and that it's now a prime example
of really good Linux hardware support)
Signed-off-by: Andreas Mohr <andi(a)lisas.de>
--- linux-2.6.26-rc1.orig/sound/pci/azt3328.c 2008-05-03 20:59:44.000000000 +0200
+++ linux-2.6.26-rc1/sound/pci/azt3328.c 2008-05-14 22:47:15.000000000 +0200
@@ -1,6 +1,6 @@
/*
* azt3328.c - driver for Aztech AZF3328 based soundcards (e.g. PCI168).
- * Copyright (C) 2002, 2005, 2006, 2007 by Andreas Mohr <andi AT lisas.de>
+ * Copyright (C) 2002, 2005 - 2008 by Andreas Mohr <andi AT lisas.de>
*
* Framework borrowed from Bart Hartgers's als4000.c.
* Driver developed on PCI168 AP(W) version (PCI rev. 10, subsystem ID 1801),
@@ -35,9 +35,20 @@
* (3 weeks' worth of evenings filled with driver work).
* (and no, I did NOT go the easy way: to pick up a SB PCI128 for 9 Euros)
*
+ * It is quite likely that the AZF3328 chip is the PCI cousin of the
+ * AZF3318 ("azt1020 pnp", "MM Pro 16") ISA chip, given very similar specs.
+ *
* The AZF3328 chip (note: AZF3328, *not* AZT3328, that's just the driver name
- * for compatibility reasons) has the following features:
+ * for compatibility reasons) from Azfin (joint-venture of Aztech and Fincitec,
+ * Fincitec acquired by National Semiconductor in 2002, together with the
+ * Fincitec-related company ARSmikro) has the following features:
*
+ * - compatibility & compliance:
+ * - Microsoft PC 97 ("PC 97 Hardware Design Guide",
+ * http://www.microsoft.com/whdc/archive/pcguides.mspx)
+ * - Microsoft PC 98 Baseline Audio
+ * - MPU401 UART
+ * - Sound Blaster Emulation (DOS Box)
* - builtin AC97 conformant codec (SNR over 80dB)
* Note that "conformant" != "compliant"!! this chip's mixer register layout
* *differs* from the standard AC97 layout:
@@ -48,21 +59,28 @@
* addresses illegally. So far unfortunately it looks like the very flexible
* ALSA AC97 support is still not enough to easily compensate for such a
* grave layout violation despite all tweaks and quirks mechanisms it offers.
- * - builtin genuine OPL3
+ * - builtin genuine OPL3 - verified to work fine, 20080506
* - full duplex 16bit playback/record at independent sampling rate
- * - MPU401 (+ legacy address support) FIXME: how to enable legacy addr??
+ * - MPU401 (+ legacy address support, claimed by one official spec sheet)
+ * FIXME: how to enable legacy addr??
* - game port (legacy address support)
- * - builtin 3D enhancement (said to be YAMAHA Ymersion)
* - builtin DirectInput support, helps reduce CPU overhead (interrupt-driven
- * features supported)
+ * features supported). - See common term "Digital Enhanced Game Port"...
+ * (probably DirectInput 3.0 spec - confirm)
+ * - builtin 3D enhancement (said to be YAMAHA Ymersion)
* - built-in General DirectX timer having a 20 bits counter
* with 1us resolution (see below!)
- * - I2S serial port for external DAC
+ * - I2S serial output port for external DAC
* - supports 33MHz PCI spec 2.1, PCI power management 1.0, compliant with ACPI
* - supports hardware volume control
* - single chip low cost solution (128 pin QFP)
* - supports programmable Sub-vendor and Sub-system ID
* required for Microsoft's logo compliance (FIXME: where?)
+ * At least the Trident 4D Wave DX has one bit somewhere
+ * to enable writes to PCI subsystem VID registers, that should be it.
+ * This might easily be in extended PCI reg space, since PCI168 also has
+ * some custom data starting at 0x80. What kind of config settings
+ * are located in our extended PCI space anyway??
* - PCI168 AP(W) card: power amplifier with 4 Watts/channel at 4 Ohms
*
* Note that this driver now is actually *better* than the Windows driver,
@@ -74,6 +92,23 @@
* - "timidity -iAv -B2,8 -Os -EFreverb=0"
* - "pmidi -p 128:0 jazz.mid"
*
+ * OPL3 hardware playback testing, try something like:
+ * cat /proc/asound/hwdep
+ * and
+ * aconnect -o
+ * Then use
+ * sbiload -Dhw:x,y --opl3 /usr/share/sounds/opl3/std.o3 ......./drums.o3
+ * where x,y is the xx-yy number as given in hwdep.
+ * Then try
+ * pmidi -p 21:0 jazz.mid
+ * Oh, and make sure to unmute the FM mixer control (doh!)
+ * NOTE: power use during OPL3 playback is _VERY_ high (70W --> 90W!)
+ * despite no CPU activity, possibly due to hindering ACPI idling somehow.
+ * Shouldn't be a problem of the AZF3328 chip itself, I'd hope.
+ * Higher PCM / FM mixer levels seem to conflict (causes crackling),
+ * at least sometimes. Maybe even use with hardware sequencer timer above :)
+ * adplay/adplug-utils might soon offer hardware-based OPL3 playback, too.
+ *
* Certain PCI versions of this card are susceptible to DMA traffic underruns
* in some systems (resulting in sound crackling/clicking/popping),
* probably because they don't have a DMA FIFO buffer or so.
@@ -87,6 +122,8 @@
* better than a VIA, yet ironically I still get crackling, like many other
* people with the same chipset.
* Possible remedies:
+ * - use speaker (amplifier) output instead of headphone output
+ * (in case crackling is due to overloaded output clipping)
* - plug card into a different PCI slot, preferrably one that isn't shared
* too much (this helps a lot, but not completely!)
* - get rid of PCI VGA card, use AGP instead
@@ -94,18 +131,23 @@
* - fiddle with PCI latency settings (setpci -v -s BUSID latency_timer=XX)
* Not too helpful.
* - Disable ACPI/power management/"Auto Detect RAM/PCI Clk" in BIOS
- *
+ *
* BUGS
* - full-duplex might *still* be problematic, not fully tested recently
* - (non-bug) "Bass/Treble or 3D settings don't work" - they do get evaluated
* if you set PCM output switch to "pre 3D" instead of "post 3D".
* If this can't be set, then get a mixer application that Isn't Stupid (tm)
* (e.g. kmix, gamix) - unfortunately several are!!
- *
+ * - locking is not entirely clean, especially the audio stream activity
+ * ints --> may be racy
+ * - an _unconnected_ secondary joystick at the gameport will be reported
+ * to be "active" (floating values, not precisely -1) due to the way we need
+ * to read the Digital Enhanced Game Port. Not sure whether it is fixable.
+ *
* TODO
* - test MPU401 MIDI playback etc.
- * - add some power micro-management (disable various units of the card
- * as long as they're unused). However this requires I/O ports which I
+ * - add more power micro-management (disable various units of the card
+ * as long as they're unused). However this requires more I/O ports which I
* haven't figured out yet and which thus might not even exist...
* The standard suspend/resume functionality could probably make use of
* some improvement, too...
@@ -113,6 +155,7 @@
* - figure out some cleverly evil scheme to possibly make ALSA AC97 code
* fully accept our quite incompatible ""AC97"" mixer and thus save some
* code (but I'm not too optimistic that doing this is possible at all)
+ * - use MMIO (memory-mapped I/O)? Slightly faster access, e.g. for gameport.
*/
#include <asm/io.h>
@@ -138,7 +181,7 @@
MODULE_SUPPORTED_DEVICE("{{Aztech,AZF3328}}");
#if defined(CONFIG_GAMEPORT) || (defined(MODULE) && defined(CONFIG_GAMEPORT_MODULE))
-#define SUPPORT_JOYSTICK 1
+#define SUPPORT_GAMEPORT 1
#endif
#define DEBUG_MISC 0
@@ -147,13 +190,14 @@
#define DEBUG_PLAY_REC 0
#define DEBUG_IO 0
#define DEBUG_TIMER 0
+#define DEBUG_GAME 0
#define MIXER_TESTING 0
#if DEBUG_MISC
#define snd_azf3328_dbgmisc(format, args...) printk(KERN_ERR format, ##args)
#else
#define snd_azf3328_dbgmisc(format, args...)
-#endif
+#endif
#if DEBUG_CALLS
#define snd_azf3328_dbgcalls(format, args...) printk(format, ##args)
@@ -163,25 +207,31 @@
#define snd_azf3328_dbgcalls(format, args...)
#define snd_azf3328_dbgcallenter()
#define snd_azf3328_dbgcallleave()
-#endif
+#endif
#if DEBUG_MIXER
#define snd_azf3328_dbgmixer(format, args...) printk(format, ##args)
#else
#define snd_azf3328_dbgmixer(format, args...)
-#endif
+#endif
#if DEBUG_PLAY_REC
#define snd_azf3328_dbgplay(format, args...) printk(KERN_ERR format, ##args)
#else
#define snd_azf3328_dbgplay(format, args...)
-#endif
+#endif
#if DEBUG_MISC
#define snd_azf3328_dbgtimer(format, args...) printk(KERN_ERR format, ##args)
#else
#define snd_azf3328_dbgtimer(format, args...)
-#endif
+#endif
+
+#if DEBUG_GAME
+#define snd_azf3328_dbggame(format, args...) printk(KERN_ERR format, ##args)
+#else
+#define snd_azf3328_dbggame(format, args...)
+#endif
static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-MAX */
module_param_array(index, int, NULL, 0444);
@@ -195,39 +245,44 @@
module_param_array(enable, bool, NULL, 0444);
MODULE_PARM_DESC(enable, "Enable AZF3328 soundcard.");
-#ifdef SUPPORT_JOYSTICK
-static int joystick[SNDRV_CARDS];
-module_param_array(joystick, bool, NULL, 0444);
-MODULE_PARM_DESC(joystick, "Enable joystick for AZF3328 soundcard.");
-#endif
-
static int seqtimer_scaling = 128;
module_param(seqtimer_scaling, int, 0444);
MODULE_PARM_DESC(seqtimer_scaling, "Set 1024000Hz sequencer timer scale factor (lockup danger!). Default 128.");
+typedef struct {
+ struct snd_pcm_substream *substream;
+ int enabled;
+ int running;
+ unsigned long portbase;
+} snd_azf3328_audio_stream_t;
+
+typedef enum {
+ AZF_PLAYBACK = 0,
+ AZF_CAPTURE = 1,
+} snd_azf3328_stream_index_t;
+
struct snd_azf3328 {
/* often-used fields towards beginning, then grouped */
- unsigned long codec_port;
- unsigned long io2_port;
- unsigned long mpu_port;
- unsigned long synth_port;
- unsigned long mixer_port;
+
+ unsigned long codec_io; /* usually 0xb000, size 128 */
+ unsigned long game_io; /* usually 0xb400, size 8 */
+ unsigned long mpu_io; /* usually 0xb800, size 4 */
+ unsigned long opl3_io; /* usually 0xbc00, size 8 */
+ unsigned long mixer_io; /* usually 0xc000, size 64 */
spinlock_t reg_lock;
struct snd_timer *timer;
-
+
struct snd_pcm *pcm;
- struct snd_pcm_substream *playback_substream;
- struct snd_pcm_substream *capture_substream;
- unsigned int is_playing;
- unsigned int is_recording;
+ snd_azf3328_audio_stream_t audio_stream[2];
struct snd_card *card;
struct snd_rawmidi *rmidi;
-#ifdef SUPPORT_JOYSTICK
+#ifdef SUPPORT_GAMEPORT
struct gameport *gameport;
+ int axes[4];
#endif
struct pci_dev *pci;
@@ -237,9 +292,9 @@
/* register value containers for power management
* Note: not always full I/O range preserved (just like Win driver!) */
u16 saved_regs_codec [AZF_IO_SIZE_CODEC_PM / 2];
- u16 saved_regs_io2 [AZF_IO_SIZE_IO2_PM / 2];
+ u16 saved_regs_game [AZF_IO_SIZE_GAME_PM / 2];
u16 saved_regs_mpu [AZF_IO_SIZE_MPU_PM / 2];
- u16 saved_regs_synth[AZF_IO_SIZE_SYNTH_PM / 2];
+ u16 saved_regs_opl3[AZF_IO_SIZE_OPL3_PM / 2];
u16 saved_regs_mixer[AZF_IO_SIZE_MIXER_PM / 2];
#endif
};
@@ -252,126 +307,181 @@
MODULE_DEVICE_TABLE(pci, snd_azf3328_ids);
+
+static int
+snd_azf3328_io_reg_setb(unsigned reg, u8 mask, int do_set)
+{
+ u8 prev = inb(reg), new;
+
+ new = (do_set) ? prev|mask : prev & ~mask;
+ /* we need to always write the new value no matter whether it differs or not,
+ * since some register bits don't indicate their setting */
+ outb(new, reg);
+ if (new != prev)
+ return 1;
+
+ return 0;
+}
+
+static int
+snd_azf3328_io_reg_setw(unsigned reg, u16 mask, int do_set)
+{
+ u16 prev = inw(reg), new;
+
+ new = (do_set) ? prev|mask : prev & ~mask;
+ /* we need to always write the new value no matter whether it differs or not,
+ * since some register bits don't indicate their setting */
+ outw(new, reg);
+ if (new != prev)
+ return 1;
+
+ return 0;
+}
+
static inline void
-snd_azf3328_codec_outb(const struct snd_azf3328 *chip, int reg, u8 value)
+snd_azf3328_codec_outb(const struct snd_azf3328 *chip, unsigned reg, u8 value)
{
- outb(value, chip->codec_port + reg);
+ outb(value, chip->codec_io + reg);
}
static inline u8
-snd_azf3328_codec_inb(const struct snd_azf3328 *chip, int reg)
+snd_azf3328_codec_inb(const struct snd_azf3328 *chip, unsigned reg)
{
- return inb(chip->codec_port + reg);
+ return inb(chip->codec_io + reg);
}
static inline void
-snd_azf3328_codec_outw(const struct snd_azf3328 *chip, int reg, u16 value)
+snd_azf3328_codec_outw(const struct snd_azf3328 *chip, unsigned reg, u16 value)
{
- outw(value, chip->codec_port + reg);
+ outw(value, chip->codec_io + reg);
}
static inline u16
-snd_azf3328_codec_inw(const struct snd_azf3328 *chip, int reg)
+snd_azf3328_codec_inw(const struct snd_azf3328 *chip, unsigned reg)
+{
+ return inw(chip->codec_io + reg);
+}
+
+static inline void
+snd_azf3328_codec_outl(const struct snd_azf3328 *chip, unsigned reg, u32 value)
{
- return inw(chip->codec_port + reg);
+ outl(value, chip->codec_io + reg);
+}
+
+static inline u32
+snd_azf3328_codec_inl(const struct snd_azf3328 *chip, unsigned reg)
+{
+ return inl(chip->codec_io + reg);
}
static inline void
-snd_azf3328_codec_outl(const struct snd_azf3328 *chip, int reg, u32 value)
+snd_azf3328_game_outb(const struct snd_azf3328 *chip, unsigned reg, u8 value)
{
- outl(value, chip->codec_port + reg);
+ outb(value, chip->game_io + reg);
}
static inline void
-snd_azf3328_io2_outb(const struct snd_azf3328 *chip, int reg, u8 value)
+snd_azf3328_game_outw(const struct snd_azf3328 *chip, unsigned reg, u16 value)
{
- outb(value, chip->io2_port + reg);
+ outw(value, chip->game_io + reg);
}
static inline u8
-snd_azf3328_io2_inb(const struct snd_azf3328 *chip, int reg)
+snd_azf3328_game_inb(const struct snd_azf3328 *chip, unsigned reg)
+{
+ return inb(chip->game_io + reg);
+}
+
+static inline u16
+snd_azf3328_game_inw(const struct snd_azf3328 *chip, unsigned reg)
{
- return inb(chip->io2_port + reg);
+ return inw(chip->game_io + reg);
}
static inline void
-snd_azf3328_mixer_outw(const struct snd_azf3328 *chip, int reg, u16 value)
+snd_azf3328_mixer_outw(const struct snd_azf3328 *chip, unsigned reg, u16 value)
{
- outw(value, chip->mixer_port + reg);
+ outw(value, chip->mixer_io + reg);
}
static inline u16
-snd_azf3328_mixer_inw(const struct snd_azf3328 *chip, int reg)
+snd_azf3328_mixer_inw(const struct snd_azf3328 *chip, unsigned reg)
{
- return inw(chip->mixer_port + reg);
+ return inw(chip->mixer_io + reg);
}
-static void
-snd_azf3328_mixer_set_mute(const struct snd_azf3328 *chip, int reg, int do_mute)
+#define AZF_MUTE_BIT 0x80
+
+static int
+snd_azf3328_mixer_set_mute(const struct snd_azf3328 *chip,
+ unsigned reg, int do_mute
+)
{
- unsigned long portbase = chip->mixer_port + reg + 1;
- unsigned char oldval;
+ unsigned long portbase = chip->mixer_io + reg + 1;
+ int updated;
/* the mute bit is on the *second* (i.e. right) register of a
* left/right channel setting */
- oldval = inb(portbase);
- if (do_mute)
- oldval |= 0x80;
- else
- oldval &= ~0x80;
- outb(oldval, portbase);
+ updated = snd_azf3328_io_reg_setb(portbase, AZF_MUTE_BIT, do_mute);
+
+ /* indicate whether it was muted before */
+ return (do_mute) ? !updated : updated;
}
static void
-snd_azf3328_mixer_write_volume_gradually(const struct snd_azf3328 *chip, int reg, unsigned char dst_vol_left, unsigned char dst_vol_right, int chan_sel, int delay)
+snd_azf3328_mixer_write_volume_gradually(const struct snd_azf3328 *chip,
+ unsigned reg,
+ unsigned char dst_vol_left,
+ unsigned char dst_vol_right,
+ int chan_sel, int delay
+)
{
- unsigned long portbase = chip->mixer_port + reg;
+ unsigned long portbase = chip->mixer_io + reg;
unsigned char curr_vol_left = 0, curr_vol_right = 0;
- int left_done = 0, right_done = 0;
-
+ int left_change = 0, right_change = 0;
+
snd_azf3328_dbgcallenter();
- if (chan_sel & SET_CHAN_LEFT)
+
+ if (chan_sel & SET_CHAN_LEFT) {
curr_vol_left = inb(portbase + 1);
- else
- left_done = 1;
- if (chan_sel & SET_CHAN_RIGHT)
+
+ /* take care of muting flag contained in left channel */
+ if (curr_vol_left & AZF_MUTE_BIT)
+ dst_vol_left |= AZF_MUTE_BIT;
+ else
+ dst_vol_left &= ~AZF_MUTE_BIT;
+
+ left_change = (curr_vol_left > dst_vol_left) ? -1 : 1;
+ }
+
+ if (chan_sel & SET_CHAN_RIGHT) {
curr_vol_right = inb(portbase + 0);
- else
- right_done = 1;
-
- /* take care of muting flag (0x80) contained in left channel */
- if (curr_vol_left & 0x80)
- dst_vol_left |= 0x80;
- else
- dst_vol_left &= ~0x80;
+
+ right_change = (curr_vol_right > dst_vol_right) ? -1 : 1;
+ }
do {
- if (!left_done) {
- if (curr_vol_left > dst_vol_left)
- curr_vol_left--;
- else
- if (curr_vol_left < dst_vol_left)
- curr_vol_left++;
- else
- left_done = 1;
- outb(curr_vol_left, portbase + 1);
+ if (left_change) {
+ if (curr_vol_left != dst_vol_left) {
+ curr_vol_left += left_change;
+ outb(curr_vol_left, portbase + 1);
+ } else
+ left_change = 0;
}
- if (!right_done) {
- if (curr_vol_right > dst_vol_right)
- curr_vol_right--;
- else
- if (curr_vol_right < dst_vol_right)
- curr_vol_right++;
- else
- right_done = 1;
+ if (right_change) {
+ if (curr_vol_right != dst_vol_right) {
+ curr_vol_right += right_change;
+
/* during volume change, the right channel is crackling
* somewhat more than the left channel, unfortunately.
* This seems to be a hardware issue. */
- outb(curr_vol_right, portbase + 0);
+ outb(curr_vol_right, portbase + 0);
+ } else
+ right_change = 0;
}
if (delay)
mdelay(delay);
- } while ((!left_done) || (!right_done));
+ } while ((left_change) || (right_change));
snd_azf3328_dbgcallleave();
}
@@ -379,7 +489,7 @@
* general mixer element
*/
struct azf3328_mixer_reg {
- unsigned int reg;
+ unsigned reg;
unsigned int lchan_shift, rchan_shift;
unsigned int mask;
unsigned int invert: 1;
@@ -544,13 +654,14 @@
"Mix", "Mic"
};
static const char * const texts3[] = {
- "Mic", "CD", "Video", "Aux",
+ "Mic", "CD", "Video", "Aux",
"Line", "Mix", "Mix Mono", "Phone"
};
static const char * const texts4[] = {
"pre 3D", "post 3D"
};
struct azf3328_mixer_reg reg;
+ const char *p = NULL;
snd_azf3328_mixer_reg_decode(®, kcontrol->private_value);
uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
@@ -561,18 +672,20 @@
if (reg.reg == IDX_MIXER_ADVCTL2) {
switch(reg.lchan_shift) {
case 8: /* modem out sel */
- strcpy(uinfo->value.enumerated.name, texts1[uinfo->value.enumerated.item]);
+ p = texts1[uinfo->value.enumerated.item];
break;
case 9: /* mono sel source */
- strcpy(uinfo->value.enumerated.name, texts2[uinfo->value.enumerated.item]);
+ p = texts2[uinfo->value.enumerated.item];
break;
case 15: /* PCM Out Path */
- strcpy(uinfo->value.enumerated.name, texts4[uinfo->value.enumerated.item]);
+ p = texts4[uinfo->value.enumerated.item];
break;
}
} else
- strcpy(uinfo->value.enumerated.name, texts3[uinfo->value.enumerated.item]
-);
+ if (reg.reg == IDX_MIXER_REC_SELECT)
+ p = texts3[uinfo->value.enumerated.item];
+
+ strcpy(uinfo->value.enumerated.name, p);
return 0;
}
@@ -583,7 +696,7 @@
struct snd_azf3328 *chip = snd_kcontrol_chip(kcontrol);
struct azf3328_mixer_reg reg;
unsigned short val;
-
+
snd_azf3328_mixer_reg_decode(®, kcontrol->private_value);
val = snd_azf3328_mixer_inw(chip, reg.reg);
if (reg.reg == IDX_MIXER_REC_SELECT) {
@@ -605,7 +718,7 @@
struct snd_azf3328 *chip = snd_kcontrol_chip(kcontrol);
struct azf3328_mixer_reg reg;
unsigned int oreg, nreg, val;
-
+
snd_azf3328_mixer_reg_decode(®, kcontrol->private_value);
oreg = snd_azf3328_mixer_inw(chip, reg.reg);
val = oreg;
@@ -717,15 +830,16 @@
snd_azf3328_mixer_outw(chip, IDX_MIXER_RESET, 0x0000);
/* mute and zero volume channels */
- for (idx = 0; idx < ARRAY_SIZE(snd_azf3328_init_values); idx++) {
+ for (idx = 0; idx < ARRAY_SIZE(snd_azf3328_init_values); ++idx) {
snd_azf3328_mixer_outw(chip,
snd_azf3328_init_values[idx][0],
snd_azf3328_init_values[idx][1]);
}
-
+
/* add mixer controls */
sw = snd_azf3328_mixer_controls;
- for (idx = 0; idx < ARRAY_SIZE(snd_azf3328_mixer_controls); idx++, sw++) {
+ for (idx = 0; idx < ARRAY_SIZE(snd_azf3328_mixer_controls);
+ ++idx, ++sw) {
if ((err = snd_ctl_add(chip->card, snd_ctl_new1(sw, chip))) < 0)
return err;
}
@@ -757,8 +871,8 @@
}
static void
-snd_azf3328_setfmt(struct snd_azf3328 *chip,
- unsigned int reg,
+snd_azf3328_codec_setfmt(struct snd_azf3328 *chip,
+ unsigned reg,
unsigned int bitrate,
unsigned int format_width,
unsigned int channels
@@ -769,24 +883,25 @@
snd_azf3328_dbgcallenter();
switch (bitrate) {
- case 4000: val |= SOUNDFORMAT_FREQ_SUSPECTED_4000; break;
- case 4800: val |= SOUNDFORMAT_FREQ_SUSPECTED_4800; break;
- case 5512: val |= SOUNDFORMAT_FREQ_5510; break; /* the AZF3328 names it "5510" for some strange reason */
- case 6620: val |= SOUNDFORMAT_FREQ_6620; break;
- case 8000: val |= SOUNDFORMAT_FREQ_8000; break;
- case 9600: val |= SOUNDFORMAT_FREQ_9600; break;
- case 11025: val |= SOUNDFORMAT_FREQ_11025; break;
- case 13240: val |= SOUNDFORMAT_FREQ_SUSPECTED_13240; break;
- case 16000: val |= SOUNDFORMAT_FREQ_16000; break;
- case 22050: val |= SOUNDFORMAT_FREQ_22050; break;
- case 32000: val |= SOUNDFORMAT_FREQ_32000; break;
- case 44100: val |= SOUNDFORMAT_FREQ_44100; break;
- case 48000: val |= SOUNDFORMAT_FREQ_48000; break;
- case 66200: val |= SOUNDFORMAT_FREQ_SUSPECTED_66200; break;
+ case AZF_FREQ_4000: val |= SOUNDFORMAT_FREQ_SUSPECTED_4000; break;
+ case AZF_FREQ_4800: val |= SOUNDFORMAT_FREQ_SUSPECTED_4800; break;
+ case AZF_FREQ_5512:
+ /* the AZF3328 names it "5510" for some strange reason */
+ val |= SOUNDFORMAT_FREQ_5510; break;
+ case AZF_FREQ_6620: val |= SOUNDFORMAT_FREQ_6620; break;
+ case AZF_FREQ_8000: val |= SOUNDFORMAT_FREQ_8000; break;
+ case AZF_FREQ_9600: val |= SOUNDFORMAT_FREQ_9600; break;
+ case AZF_FREQ_11025: val |= SOUNDFORMAT_FREQ_11025; break;
+ case AZF_FREQ_13240: val |= SOUNDFORMAT_FREQ_SUSPECTED_13240; break;
+ case AZF_FREQ_16000: val |= SOUNDFORMAT_FREQ_16000; break;
+ case AZF_FREQ_22050: val |= SOUNDFORMAT_FREQ_22050; break;
+ case AZF_FREQ_32000: val |= SOUNDFORMAT_FREQ_32000; break;
default:
snd_printk(KERN_WARNING "unknown bitrate %d, assuming 44.1kHz!\n", bitrate);
- val |= SOUNDFORMAT_FREQ_44100;
- break;
+ /* fall-through */
+ case AZF_FREQ_44100: val |= SOUNDFORMAT_FREQ_44100; break;
+ case AZF_FREQ_48000: val |= SOUNDFORMAT_FREQ_48000; break;
+ case AZF_FREQ_66200: val |= SOUNDFORMAT_FREQ_SUSPECTED_66200; break;
}
/* val = 0xff07; 3m27.993s (65301Hz; -> 64000Hz???) hmm, 66120, 65967, 66123 */
/* val = 0xff09; 17m15.098s (13123,478Hz; -> 12000Hz???) hmm, 13237.2Hz? */
@@ -805,10 +920,10 @@
val |= SOUNDFORMAT_FLAG_16BIT;
spin_lock_irqsave(&chip->reg_lock, flags);
-
+
/* set bitrate/format */
snd_azf3328_codec_outw(chip, reg, val);
-
+
/* changing the bitrate/format settings switches off the
* audio output with an annoying click in case of 8/16bit format change
* (maybe shutting down DAC/ADC?), thus immediately
@@ -830,31 +945,81 @@
snd_azf3328_dbgcallleave();
}
+static inline void
+snd_azf3328_codec_setfmt_lowpower(struct snd_azf3328 *chip,
+ unsigned reg
+)
+{
+ /* choose lowest frequency for low power consumption.
+ * While this will cause louder noise due to rather coarse frequency,
+ * it should never matter since output should always
+ * get disabled properly when idle anyway. */
+ snd_azf3328_codec_setfmt(chip, reg, AZF_FREQ_4000, 8, 1);
+}
+
+static inline void
+snd_azf3328_codec_enable(struct snd_azf3328 *chip, int enable)
+{
+ /* no idea what exactly is being done here, but I strongly assume it's
+ * PM related */
+ snd_azf3328_io_reg_setw(
+ chip->codec_io+IDX_IO_6AH,
+ IO_6A_PAUSE_PLAYBACK_BIT8,
+ !enable
+ );
+}
+
+static void
+snd_azf3328_codec_activity(struct snd_azf3328 *chip,
+ snd_azf3328_stream_index_t stream_type,
+ int enable
+)
+{
+ int need_change = (chip->audio_stream[stream_type].running != enable);
+
+ snd_azf3328_dbgplay(
+ "codec_activity: type %d, enable %d, need_change %d\n",
+ stream_type, enable, need_change
+ );
+ if (need_change) {
+ snd_azf3328_stream_index_t other =
+ (stream_type == AZF_PLAYBACK) ?
+ AZF_CAPTURE : AZF_PLAYBACK;
+ /* small check to prevent shutting down the other party
+ * in case it's active */
+ if (!((!enable) && (chip->audio_stream[other].running)))
+ snd_azf3328_codec_enable(chip, enable);
+
+ /* ...and adjust clock, too
+ * (reduce noise and power consumption) */
+ if (!enable)
+ snd_azf3328_codec_setfmt_lowpower(
+ chip,
+ chip->audio_stream[stream_type].portbase
+ + IDX_IO_PLAY_SOUNDFORMAT
+ );
+ }
+ chip->audio_stream[stream_type].running = enable;
+}
+
static void
snd_azf3328_setdmaa(struct snd_azf3328 *chip,
long unsigned int addr,
unsigned int count,
unsigned int size,
- int do_recording)
+ snd_azf3328_stream_index_t stream_type
+)
{
- unsigned long flags, portbase;
- unsigned int is_running;
-
snd_azf3328_dbgcallenter();
- if (do_recording) {
- /* access capture registers, i.e. skip playback reg section */
- portbase = chip->codec_port + 0x20;
- is_running = chip->is_recording;
- } else {
- /* access the playback register section */
- portbase = chip->codec_port + 0x00;
- is_running = chip->is_playing;
- }
+ if (!chip->audio_stream[stream_type].running) {
+ /* AZF3328 uses a two buffer pointer DMA playback approach */
+
+ unsigned long flags, portbase, addr_area2;
+
+ /* width 32bit (prevent overflow): */
+ unsigned long count_areas, count_tmp;
- /* AZF3328 uses a two buffer pointer DMA playback approach */
- if (!is_running) {
- unsigned long addr_area2;
- unsigned long count_areas, count_tmp; /* width 32bit -- overflow!! */
+ portbase = chip->audio_stream[stream_type].portbase;
count_areas = size/2;
addr_area2 = addr+count_areas;
count_areas--; /* max. index */
@@ -884,11 +1049,11 @@
snd_azf3328_dbgcallenter();
#if 0
- snd_azf3328_setfmt(chip, IDX_IO_PLAY_SOUNDFORMAT,
+ snd_azf3328_codec_setfmt(chip, IDX_IO_PLAY_SOUNDFORMAT,
runtime->rate,
snd_pcm_format_width(runtime->format),
runtime->channels);
- snd_azf3328_setdmaa(chip, runtime->dma_addr, count, size, 0);
+ snd_azf3328_setdmaa(chip, runtime->dma_addr, count, size, AZF_PLAYBACK);
#endif
snd_azf3328_dbgcallleave();
return 0;
@@ -906,11 +1071,11 @@
snd_azf3328_dbgcallenter();
#if 0
- snd_azf3328_setfmt(chip, IDX_IO_REC_SOUNDFORMAT,
+ snd_azf3328_codec_setfmt(chip, IDX_IO_REC_SOUNDFORMAT,
runtime->rate,
snd_pcm_format_width(runtime->format),
runtime->channels);
- snd_azf3328_setdmaa(chip, runtime->dma_addr, count, size, 1);
+ snd_azf3328_setdmaa(chip, runtime->dma_addr, count, size, AZF_CAPTURE);
#endif
snd_azf3328_dbgcallleave();
return 0;
@@ -923,6 +1088,7 @@
struct snd_pcm_runtime *runtime = substream->runtime;
int result = 0;
unsigned int status1;
+ int previously_muted;
snd_azf3328_dbgcalls("snd_azf3328_playback_trigger cmd %d\n", cmd);
@@ -930,20 +1096,22 @@
case SNDRV_PCM_TRIGGER_START:
snd_azf3328_dbgplay("START PLAYBACK\n");
- /* mute WaveOut */
- snd_azf3328_mixer_set_mute(chip, IDX_MIXER_WAVEOUT, 1);
+ /* mute WaveOut (avoid clicking during setup) */
+ previously_muted =
+ snd_azf3328_mixer_set_mute(chip, IDX_MIXER_WAVEOUT, 1);
- snd_azf3328_setfmt(chip, IDX_IO_PLAY_SOUNDFORMAT,
+ snd_azf3328_codec_setfmt(chip, IDX_IO_PLAY_SOUNDFORMAT,
runtime->rate,
snd_pcm_format_width(runtime->format),
runtime->channels);
spin_lock(&chip->reg_lock);
- /* stop playback */
status1 = snd_azf3328_codec_inw(chip, IDX_IO_PLAY_FLAGS);
+
+ /* stop playback */
status1 &= ~DMA_RESUME;
snd_azf3328_codec_outw(chip, IDX_IO_PLAY_FLAGS, status1);
-
+
/* FIXME: clear interrupts or what??? */
snd_azf3328_codec_outw(chip, IDX_IO_PLAY_IRQTYPE, 0xffff);
spin_unlock(&chip->reg_lock);
@@ -951,7 +1119,7 @@
snd_azf3328_setdmaa(chip, runtime->dma_addr,
snd_pcm_lib_period_bytes(substream),
snd_pcm_lib_buffer_bytes(substream),
- 0);
+ AZF_PLAYBACK);
spin_lock(&chip->reg_lock);
#ifdef WIN9X
@@ -978,30 +1146,34 @@
DMA_SOMETHING_ELSE);
#endif
spin_unlock(&chip->reg_lock);
+ snd_azf3328_codec_activity(chip, AZF_PLAYBACK, 1);
/* now unmute WaveOut */
- snd_azf3328_mixer_set_mute(chip, IDX_MIXER_WAVEOUT, 0);
+ if (!previously_muted)
+ snd_azf3328_mixer_set_mute(chip, IDX_MIXER_WAVEOUT, 0);
- chip->is_playing = 1;
snd_azf3328_dbgplay("STARTED PLAYBACK\n");
break;
case SNDRV_PCM_TRIGGER_RESUME:
snd_azf3328_dbgplay("RESUME PLAYBACK\n");
/* resume playback if we were active */
- if (chip->is_playing)
+ spin_lock(&chip->reg_lock);
+ if (chip->audio_stream[AZF_PLAYBACK].running)
snd_azf3328_codec_outw(chip, IDX_IO_PLAY_FLAGS,
snd_azf3328_codec_inw(chip, IDX_IO_PLAY_FLAGS) | DMA_RESUME);
+ spin_unlock(&chip->reg_lock);
break;
case SNDRV_PCM_TRIGGER_STOP:
snd_azf3328_dbgplay("STOP PLAYBACK\n");
- /* mute WaveOut */
- snd_azf3328_mixer_set_mute(chip, IDX_MIXER_WAVEOUT, 1);
+ /* mute WaveOut (avoid clicking during setup) */
+ previously_muted =
+ snd_azf3328_mixer_set_mute(chip, IDX_MIXER_WAVEOUT, 1);
spin_lock(&chip->reg_lock);
- /* stop playback */
status1 = snd_azf3328_codec_inw(chip, IDX_IO_PLAY_FLAGS);
+ /* stop playback */
status1 &= ~DMA_RESUME;
snd_azf3328_codec_outw(chip, IDX_IO_PLAY_FLAGS, status1);
@@ -1013,10 +1185,12 @@
status1 &= ~DMA_PLAY_SOMETHING1;
snd_azf3328_codec_outw(chip, IDX_IO_PLAY_FLAGS, status1);
spin_unlock(&chip->reg_lock);
-
+ snd_azf3328_codec_activity(chip, AZF_PLAYBACK, 0);
+
/* now unmute WaveOut */
- snd_azf3328_mixer_set_mute(chip, IDX_MIXER_WAVEOUT, 0);
- chip->is_playing = 0;
+ if (!previously_muted)
+ snd_azf3328_mixer_set_mute(chip, IDX_MIXER_WAVEOUT, 0);
+
snd_azf3328_dbgplay("STOPPED PLAYBACK\n");
break;
case SNDRV_PCM_TRIGGER_SUSPEND:
@@ -1035,7 +1209,7 @@
printk(KERN_ERR "FIXME: unknown trigger mode!\n");
return -EINVAL;
}
-
+
snd_azf3328_dbgcallleave();
return result;
}
@@ -1057,7 +1231,7 @@
snd_azf3328_dbgplay("START CAPTURE\n");
- snd_azf3328_setfmt(chip, IDX_IO_REC_SOUNDFORMAT,
+ snd_azf3328_codec_setfmt(chip, IDX_IO_REC_SOUNDFORMAT,
runtime->rate,
snd_pcm_format_width(runtime->format),
runtime->channels);
@@ -1067,7 +1241,7 @@
status1 = snd_azf3328_codec_inw(chip, IDX_IO_REC_FLAGS);
status1 &= ~DMA_RESUME;
snd_azf3328_codec_outw(chip, IDX_IO_REC_FLAGS, status1);
-
+
/* FIXME: clear interrupts or what??? */
snd_azf3328_codec_outw(chip, IDX_IO_REC_IRQTYPE, 0xffff);
spin_unlock(&chip->reg_lock);
@@ -1075,7 +1249,7 @@
snd_azf3328_setdmaa(chip, runtime->dma_addr,
snd_pcm_lib_period_bytes(substream),
snd_pcm_lib_buffer_bytes(substream),
- 1);
+ AZF_CAPTURE);
spin_lock(&chip->reg_lock);
#ifdef WIN9X
@@ -1102,16 +1276,18 @@
DMA_SOMETHING_ELSE);
#endif
spin_unlock(&chip->reg_lock);
+ snd_azf3328_codec_activity(chip, AZF_CAPTURE, 1);
- chip->is_recording = 1;
snd_azf3328_dbgplay("STARTED CAPTURE\n");
break;
case SNDRV_PCM_TRIGGER_RESUME:
snd_azf3328_dbgplay("RESUME CAPTURE\n");
/* resume recording if we were active */
- if (chip->is_recording)
+ spin_lock(&chip->reg_lock);
+ if (chip->audio_stream[AZF_CAPTURE].running)
snd_azf3328_codec_outw(chip, IDX_IO_REC_FLAGS,
snd_azf3328_codec_inw(chip, IDX_IO_REC_FLAGS) | DMA_RESUME);
+ spin_unlock(&chip->reg_lock);
break;
case SNDRV_PCM_TRIGGER_STOP:
snd_azf3328_dbgplay("STOP CAPTURE\n");
@@ -1129,8 +1305,8 @@
status1 &= ~DMA_PLAY_SOMETHING1;
snd_azf3328_codec_outw(chip, IDX_IO_REC_FLAGS, status1);
spin_unlock(&chip->reg_lock);
-
- chip->is_recording = 0;
+ snd_azf3328_codec_activity(chip, AZF_CAPTURE, 0);
+
snd_azf3328_dbgplay("STOPPED CAPTURE\n");
break;
case SNDRV_PCM_TRIGGER_SUSPEND:
@@ -1149,7 +1325,7 @@
printk(KERN_ERR "FIXME: unknown trigger mode!\n");
return -EINVAL;
}
-
+
snd_azf3328_dbgcallleave();
return result;
}
@@ -1162,11 +1338,11 @@
snd_pcm_uframes_t frmres;
#ifdef QUERY_HARDWARE
- bufptr = inl(chip->codec_port+IDX_IO_PLAY_DMA_START_1);
+ bufptr = snd_azf3328_codec_inl(chip, IDX_IO_PLAY_DMA_START_1);
#else
bufptr = substream->runtime->dma_addr;
#endif
- result = inl(chip->codec_port+IDX_IO_PLAY_DMA_CURRPOS);
+ result = snd_azf3328_codec_inl(chip, IDX_IO_PLAY_DMA_CURRPOS);
/* calculate offset */
result -= bufptr;
@@ -1183,11 +1359,11 @@
snd_pcm_uframes_t frmres;
#ifdef QUERY_HARDWARE
- bufptr = inl(chip->codec_port+IDX_IO_REC_DMA_START_1);
+ bufptr = snd_azf3328_codec_inl(chip, IDX_IO_REC_DMA_START_1);
#else
bufptr = substream->runtime->dma_addr;
#endif
- result = inl(chip->codec_port+IDX_IO_REC_DMA_CURRPOS);
+ result = snd_azf3328_codec_inl(chip, IDX_IO_REC_DMA_CURRPOS);
/* calculate offset */
result -= bufptr;
@@ -1196,27 +1372,232 @@
return frmres;
}
+/******************************************************************/
+
+#ifdef SUPPORT_GAMEPORT
+static inline void
+snd_azf3328_gameport_irq_enable(struct snd_azf3328 *chip, int enable)
+{
+ snd_azf3328_io_reg_setb(
+ chip->game_io+IDX_GAME_HWCONFIG,
+ GAME_HWCFG_IRQ_ENABLE,
+ enable
+ );
+}
+
+static inline void
+snd_azf3328_gameport_legacy_address_enable(struct snd_azf3328 *chip, int enable)
+{
+ snd_azf3328_io_reg_setb(
+ chip->game_io+IDX_GAME_HWCONFIG,
+ GAME_HWCFG_LEGACY_ADDRESS_ENABLE,
+ enable
+ );
+}
+
+static inline void
+snd_azf3328_gameport_axis_circuit_enable(struct snd_azf3328 *chip, int enable)
+{
+ snd_azf3328_io_reg_setw(
+ chip->codec_io+IDX_IO_6AH,
+ IO_6A_SOMETHING2_GAMEPORT,
+ !enable
+ );
+}
+
+static inline void
+snd_azf3328_gameport_interrupt(struct snd_azf3328 *chip)
+{
+ /*
+ * skeleton handler only
+ * (we do not want axis reading in interrupt handler - too much load!)
+ */
+ snd_azf3328_dbggame("gameport irq\n");
+
+ /* this should ACK the gameport IRQ properly, hopefully. */
+ snd_azf3328_game_inw(chip, IDX_GAME_AXIS_VALUE);
+}
+
+static int
+snd_azf3328_gameport_open(struct gameport *gameport, int mode)
+{
+ struct snd_azf3328 *chip = gameport_get_port_data(gameport);
+ int res;
+
+ snd_azf3328_dbggame("gameport_open, mode %d\n", mode);
+ switch (mode) {
+ case GAMEPORT_MODE_COOKED:
+ case GAMEPORT_MODE_RAW:
+ res = 0;
+ break;
+ default:
+ res = -1;
+ break;
+ }
+
+ snd_azf3328_gameport_axis_circuit_enable(chip, (res == 0));
+
+ return res;
+}
+
+static void
+snd_azf3328_gameport_close(struct gameport *gameport)
+{
+ struct snd_azf3328 *chip = gameport_get_port_data(gameport);
+
+ snd_azf3328_dbggame("gameport_close\n");
+ snd_azf3328_gameport_axis_circuit_enable(chip, 0);
+}
+
+static int
+snd_azf3328_gameport_cooked_read(struct gameport *gameport,
+ int *axes,
+ int *buttons
+)
+{
+ struct snd_azf3328 *chip = gameport_get_port_data(gameport);
+ int i;
+ u8 val;
+
+ snd_assert(chip, return 0);
+
+ spin_lock(&chip->reg_lock);
+ val = snd_azf3328_game_inb(chip, IDX_GAME_LEGACY_COMPATIBLE);
+ *buttons = (~(val) >> 4) & 0xf;
+
+ /* ok, this one is a bit dirty: cooked_read is being polled by a timer,
+ * thus we're atomic and cannot actively wait in here
+ * (which would be useful for us since it probably would be better
+ * to trigger a measurement in here, then wait a short amount of
+ * time until it's finished, then read values of _this_ measurement).
+ *
+ * Thus we simply resort to reading values if they're available already
+ * and trigger the next measurement.
+ */
+
+ val = snd_azf3328_game_inb(chip, IDX_GAME_AXES_CONFIG);
+ if (val & GAME_AXES_SAMPLING_READY) {
+ for (i = 0; i < 4; ++i) {
+ /* configure the axis to read */
+ val = (i << 4) | 0x0f;
+ snd_azf3328_game_outb(chip, IDX_GAME_AXES_CONFIG, val);
+
+ chip->axes[i] = snd_azf3328_game_inw(
+ chip, IDX_GAME_AXIS_VALUE
+ );
+ }
+ }
+
+ /* trigger next axes sampling, to be evaluated the next time we
+ * enter this function */
+
+ /* for some very, very strange reason we cannot enable
+ * Measurement Ready monitoring for all axes here,
+ * at least not when only one joystick connected */
+ val = 0x03; /* we're able to monitor axes 1 and 2 only */
+ snd_azf3328_game_outb(chip, IDX_GAME_AXES_CONFIG, val);
+
+ snd_azf3328_game_outw(chip, IDX_GAME_AXIS_VALUE, 0xffff);
+ spin_unlock(&chip->reg_lock);
+
+ for (i = 0; i < 4; i++) {
+ axes[i] = chip->axes[i];
+ if (axes[i] == 0xffff)
+ axes[i] = -1;
+ }
+
+ snd_azf3328_dbggame("cooked_read: axes %d %d %d %d buttons %d\n",
+ axes[0], axes[1], axes[2], axes[3], *buttons
+ );
+
+ return 0;
+}
+
+static int __devinit
+snd_azf3328_gameport(struct snd_azf3328 *chip, int dev)
+{
+ struct gameport *gp;
+
+ int io_port = chip->game_io;
+
+ chip->gameport = gp = gameport_allocate_port();
+ if (!gp) {
+ printk(KERN_ERR "azt3328: cannot alloc memory for gameport\n");
+ return -ENOMEM;
+ }
+
+ gameport_set_name(gp, "AZF3328 Gameport");
+ gameport_set_phys(gp, "pci%s/gameport0", pci_name(chip->pci));
+ gameport_set_dev_parent(gp, &chip->pci->dev);
+ gp->io = io_port;
+ gameport_set_port_data(gp, chip);
+
+ gp->open = snd_azf3328_gameport_open;
+ gp->close = snd_azf3328_gameport_close;
+ gp->fuzz = 16; /* seems ok */
+ gp->cooked_read = snd_azf3328_gameport_cooked_read;
+
+ /* DISABLE legacy address: we don't need it! */
+ snd_azf3328_gameport_legacy_address_enable(chip, 0);
+
+ snd_azf3328_gameport_axis_circuit_enable(chip, 0);
+
+ gameport_register_port(chip->gameport);
+
+ return 0;
+}
+
+static void
+snd_azf3328_gameport_free(struct snd_azf3328 *chip)
+{
+ if (chip->gameport) {
+ gameport_unregister_port(chip->gameport);
+ chip->gameport = NULL;
+ }
+ snd_azf3328_gameport_irq_enable(chip, 0);
+}
+#else
+static inline int
+snd_azf3328_gameport(struct snd_azf3328 *chip, int dev) { return -ENOSYS; }
+static inline void
+snd_azf3328_gameport_free(struct snd_azf3328 *chip) { }
+static inline void
+snd_azf3328_gameport_interrupt(struct snd_azf3328 *chip)
+{
+ printk(KERN_WARNING "huh, game port IRQ occurred!?\n");
+}
+#endif /* SUPPORT_GAMEPORT */
+
+/******************************************************************/
+
static irqreturn_t
snd_azf3328_interrupt(int irq, void *dev_id)
{
struct snd_azf3328 *chip = dev_id;
u8 status, which;
+#if DEBUG_PLAY_REC
static unsigned long irq_count;
+#endif
status = snd_azf3328_codec_inb(chip, IDX_IO_IRQSTATUS);
/* fast path out, to ease interrupt sharing */
- if (!(status & (IRQ_PLAYBACK|IRQ_RECORDING|IRQ_MPU401|IRQ_TIMER)))
+ if (!(status &
+ (IRQ_PLAYBACK|IRQ_RECORDING|IRQ_GAMEPORT|IRQ_MPU401|IRQ_TIMER)
+ ))
return IRQ_NONE; /* must be interrupt for another device */
snd_azf3328_dbgplay("Interrupt %ld!\nIDX_IO_PLAY_FLAGS %04x, IDX_IO_PLAY_IRQTYPE %04x, IDX_IO_IRQSTATUS %04x\n",
- irq_count,
+ irq_count++,
snd_azf3328_codec_inw(chip, IDX_IO_PLAY_FLAGS),
snd_azf3328_codec_inw(chip, IDX_IO_PLAY_IRQTYPE),
status);
-
+
if (status & IRQ_TIMER) {
- /* snd_azf3328_dbgplay("timer %ld\n", inl(chip->codec_port+IDX_IO_TIMER_VALUE) & TIMER_VALUE_MASK); */
+ /* snd_azf3328_dbgplay("timer %ld\n",
+ snd_azf3328_codec_inl(chip, IDX_IO_TIMER_VALUE)
+ & TIMER_VALUE_MASK
+ ); */
if (chip->timer)
snd_timer_interrupt(chip->timer, chip->timer->sticks);
/* ACK timer */
@@ -1232,11 +1613,16 @@
snd_azf3328_codec_outb(chip, IDX_IO_PLAY_IRQTYPE, which);
spin_unlock(&chip->reg_lock);
- if (chip->pcm && chip->playback_substream) {
- snd_pcm_period_elapsed(chip->playback_substream);
+ if (chip->pcm && chip->audio_stream[AZF_PLAYBACK].substream) {
+ snd_pcm_period_elapsed(
+ chip->audio_stream[AZF_PLAYBACK].substream
+ );
snd_azf3328_dbgplay("PLAY period done (#%x), @ %x\n",
which,
- inl(chip->codec_port+IDX_IO_PLAY_DMA_CURRPOS));
+ snd_azf3328_codec_inl(
+ chip, IDX_IO_PLAY_DMA_CURRPOS
+ )
+ );
} else
snd_azf3328_dbgplay("azt3328: ouch, irq handler problem!\n");
if (which & IRQ_PLAY_SOMETHING)
@@ -1249,16 +1635,23 @@
snd_azf3328_codec_outb(chip, IDX_IO_REC_IRQTYPE, which);
spin_unlock(&chip->reg_lock);
- if (chip->pcm && chip->capture_substream) {
- snd_pcm_period_elapsed(chip->capture_substream);
+ if (chip->pcm && chip->audio_stream[AZF_CAPTURE].substream) {
+ snd_pcm_period_elapsed(
+ chip->audio_stream[AZF_CAPTURE].substream
+ );
snd_azf3328_dbgplay("REC period done (#%x), @ %x\n",
which,
- inl(chip->codec_port+IDX_IO_REC_DMA_CURRPOS));
+ snd_azf3328_codec_inl(
+ chip, IDX_IO_REC_DMA_CURRPOS
+ )
+ );
} else
snd_azf3328_dbgplay("azt3328: ouch, irq handler problem!\n");
if (which & IRQ_REC_SOMETHING)
snd_azf3328_dbgplay("azt3328: unknown rec IRQ type occurred, please report!\n");
}
+ if (status & IRQ_GAMEPORT)
+ snd_azf3328_gameport_interrupt(chip);
/* MPU401 has less critical IRQ requirements
* than timer and playback/recording, right? */
if (status & IRQ_MPU401) {
@@ -1268,7 +1661,6 @@
* If so, then I don't know how... */
snd_azf3328_dbgplay("azt3328: MPU401 IRQ\n");
}
- irq_count++;
return IRQ_HANDLED;
}
@@ -1287,8 +1679,8 @@
.rates = SNDRV_PCM_RATE_5512 |
SNDRV_PCM_RATE_8000_48000 |
SNDRV_PCM_RATE_KNOT,
- .rate_min = 4000,
- .rate_max = 66200,
+ .rate_min = AZF_FREQ_4000,
+ .rate_max = AZF_FREQ_66200,
.channels_min = 1,
.channels_max = 2,
.buffer_bytes_max = 65536,
@@ -1315,8 +1707,8 @@
.rates = SNDRV_PCM_RATE_5512 |
SNDRV_PCM_RATE_8000_48000 |
SNDRV_PCM_RATE_KNOT,
- .rate_min = 4000,
- .rate_max = 66200,
+ .rate_min = AZF_FREQ_4000,
+ .rate_max = AZF_FREQ_66200,
.channels_min = 1,
.channels_max = 2,
.buffer_bytes_max = 65536,
@@ -1329,10 +1721,24 @@
static unsigned int snd_azf3328_fixed_rates[] = {
- 4000, 4800, 5512, 6620, 8000, 9600, 11025, 13240, 16000, 22050, 32000,
- 44100, 48000, 66200 };
+ AZF_FREQ_4000,
+ AZF_FREQ_4800,
+ AZF_FREQ_5512,
+ AZF_FREQ_6620,
+ AZF_FREQ_8000,
+ AZF_FREQ_9600,
+ AZF_FREQ_11025,
+ AZF_FREQ_13240,
+ AZF_FREQ_16000,
+ AZF_FREQ_22050,
+ AZF_FREQ_32000,
+ AZF_FREQ_44100,
+ AZF_FREQ_48000,
+ AZF_FREQ_66200
+};
+
static struct snd_pcm_hw_constraint_list snd_azf3328_hw_constraints_rates = {
- .count = ARRAY_SIZE(snd_azf3328_fixed_rates),
+ .count = ARRAY_SIZE(snd_azf3328_fixed_rates),
.list = snd_azf3328_fixed_rates,
.mask = 0,
};
@@ -1346,7 +1752,7 @@
struct snd_pcm_runtime *runtime = substream->runtime;
snd_azf3328_dbgcallenter();
- chip->playback_substream = substream;
+ chip->audio_stream[AZF_PLAYBACK].substream = substream;
runtime->hw = snd_azf3328_playback;
snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
&snd_azf3328_hw_constraints_rates);
@@ -1361,7 +1767,7 @@
struct snd_pcm_runtime *runtime = substream->runtime;
snd_azf3328_dbgcallenter();
- chip->capture_substream = substream;
+ chip->audio_stream[AZF_CAPTURE].substream = substream;
runtime->hw = snd_azf3328_capture;
snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
&snd_azf3328_hw_constraints_rates);
@@ -1375,7 +1781,7 @@
struct snd_azf3328 *chip = snd_pcm_substream_chip(substream);
snd_azf3328_dbgcallenter();
- chip->playback_substream = NULL;
+ chip->audio_stream[AZF_PLAYBACK].substream = NULL;
snd_azf3328_dbgcallleave();
return 0;
}
@@ -1386,7 +1792,7 @@
struct snd_azf3328 *chip = snd_pcm_substream_chip(substream);
snd_azf3328_dbgcallenter();
- chip->capture_substream = NULL;
+ chip->audio_stream[AZF_CAPTURE].substream = NULL;
snd_azf3328_dbgcallleave();
return 0;
}
@@ -1441,102 +1847,8 @@
/******************************************************************/
-#ifdef SUPPORT_JOYSTICK
-static int __devinit
-snd_azf3328_config_joystick(struct snd_azf3328 *chip, int dev)
-{
- struct gameport *gp;
- struct resource *r;
-
- if (!joystick[dev])
- return -ENODEV;
-
- if (!(r = request_region(0x200, 8, "AZF3328 gameport"))) {
- printk(KERN_WARNING "azt3328: cannot reserve joystick ports\n");
- return -EBUSY;
- }
-
- chip->gameport = gp = gameport_allocate_port();
- if (!gp) {
- printk(KERN_ERR "azt3328: cannot allocate memory for gameport\n");
- release_and_free_resource(r);
- return -ENOMEM;
- }
-
- gameport_set_name(gp, "AZF3328 Gameport");
- gameport_set_phys(gp, "pci%s/gameport0", pci_name(chip->pci));
- gameport_set_dev_parent(gp, &chip->pci->dev);
- gp->io = 0x200;
- gameport_set_port_data(gp, r);
-
- snd_azf3328_io2_outb(chip, IDX_IO2_LEGACY_ADDR,
- snd_azf3328_io2_inb(chip, IDX_IO2_LEGACY_ADDR) | LEGACY_JOY);
-
- gameport_register_port(chip->gameport);
-
- return 0;
-}
-
-static void
-snd_azf3328_free_joystick(struct snd_azf3328 *chip)
-{
- if (chip->gameport) {
- struct resource *r = gameport_get_port_data(chip->gameport);
-
- gameport_unregister_port(chip->gameport);
- chip->gameport = NULL;
- /* disable gameport */
- snd_azf3328_io2_outb(chip, IDX_IO2_LEGACY_ADDR,
- snd_azf3328_io2_inb(chip, IDX_IO2_LEGACY_ADDR) & ~LEGACY_JOY);
- release_and_free_resource(r);
- }
-}
-#else
-static inline int
-snd_azf3328_config_joystick(struct snd_azf3328 *chip, int dev) { return -ENOSYS; }
-static inline void
-snd_azf3328_free_joystick(struct snd_azf3328 *chip) { }
-#endif
-
-/******************************************************************/
-
-static int
-snd_azf3328_free(struct snd_azf3328 *chip)
-{
- if (chip->irq < 0)
- goto __end_hw;
-
- /* reset (close) mixer */
- snd_azf3328_mixer_set_mute(chip, IDX_MIXER_PLAY_MASTER, 1); /* first mute master volume */
- snd_azf3328_mixer_outw(chip, IDX_MIXER_RESET, 0x0000);
-
- /* interrupt setup - mask everything (FIXME!) */
- /* well, at least we know how to disable the timer IRQ */
- snd_azf3328_codec_outb(chip, IDX_IO_TIMER_VALUE + 3, 0x00);
-
- if (chip->irq >= 0)
- synchronize_irq(chip->irq);
-__end_hw:
- snd_azf3328_free_joystick(chip);
- if (chip->irq >= 0)
- free_irq(chip->irq, chip);
- pci_release_regions(chip->pci);
- pci_disable_device(chip->pci);
-
- kfree(chip);
- return 0;
-}
-
-static int
-snd_azf3328_dev_free(struct snd_device *device)
-{
- struct snd_azf3328 *chip = device->device_data;
- return snd_azf3328_free(chip);
-}
-
-/******************************************************************/
-
-/*** NOTE: the physical timer resolution actually is 1024000 ticks per second,
+/*** NOTE: the physical timer resolution actually is 1024000 ticks per second
+ *** (probably derived from main crystal via a divider of 24),
*** but announcing those attributes to user-space would make programs
*** configure the timer to a 1 tick value, resulting in an absolutely fatal
*** timer IRQ storm.
@@ -1564,7 +1876,7 @@
delay = 49; /* minimum time is 49 ticks */
}
snd_azf3328_dbgtimer("setting timer countdown value %d, add COUNTDOWN|IRQ\n", delay);
- delay |= TIMER_ENABLE_COUNTDOWN | TIMER_ENABLE_IRQ;
+ delay |= TIMER_COUNTDOWN_ENABLE | TIMER_IRQ_ENABLE;
spin_lock_irqsave(&chip->reg_lock, flags);
snd_azf3328_codec_outl(chip, IDX_IO_TIMER_VALUE, delay);
spin_unlock_irqrestore(&chip->reg_lock, flags);
@@ -1582,7 +1894,7 @@
chip = snd_timer_chip(timer);
spin_lock_irqsave(&chip->reg_lock, flags);
/* disable timer countdown and interrupt */
- /* FIXME: should we write TIMER_ACK_IRQ here? */
+ /* FIXME: should we write TIMER_IRQ_ACK here? */
snd_azf3328_codec_outb(chip, IDX_IO_TIMER_VALUE + 3, 0);
spin_unlock_irqrestore(&chip->reg_lock, flags);
snd_azf3328_dbgcallleave();
@@ -1626,9 +1938,10 @@
snd_azf3328_timer_hw.resolution *= seqtimer_scaling;
snd_azf3328_timer_hw.ticks /= seqtimer_scaling;
- if ((err = snd_timer_new(chip->card, "AZF3328", &tid, &timer)) < 0) {
+
+ err = snd_timer_new(chip->card, "AZF3328", &tid, &timer);
+ if (err < 0)
goto out;
- }
strcpy(timer->name, "AZF3328 timer");
timer->private_data = chip;
@@ -1636,6 +1949,8 @@
chip->timer = timer;
+ snd_azf3328_timer_stop(timer);
+
err = 0;
out:
@@ -1645,10 +1960,44 @@
/******************************************************************/
+static int
+snd_azf3328_free(struct snd_azf3328 *chip)
+{
+ if (chip->irq < 0)
+ goto __end_hw;
+
+ /* reset (close) mixer:
+ * first mute master volume, then reset
+ */
+ snd_azf3328_mixer_set_mute(chip, IDX_MIXER_PLAY_MASTER, 1);
+ snd_azf3328_mixer_outw(chip, IDX_MIXER_RESET, 0x0000);
+
+ snd_azf3328_timer_stop(chip->timer);
+ snd_azf3328_gameport_free(chip);
+
+ if (chip->irq >= 0)
+ synchronize_irq(chip->irq);
+__end_hw:
+ if (chip->irq >= 0)
+ free_irq(chip->irq, chip);
+ pci_release_regions(chip->pci);
+ pci_disable_device(chip->pci);
+
+ kfree(chip);
+ return 0;
+}
+
+static int
+snd_azf3328_dev_free(struct snd_device *device)
+{
+ struct snd_azf3328 *chip = device->device_data;
+ return snd_azf3328_free(chip);
+}
+
#if 0
/* check whether a bit can be modified */
static void
-snd_azf3328_test_bit(unsigned int reg, int bit)
+snd_azf3328_test_bit(unsigned unsigned reg, int bit)
{
unsigned char val, valoff, valon;
@@ -1659,42 +2008,74 @@
outb(val|(1 << bit), reg);
valon = inb(reg);
-
+
outb(val, reg);
- printk(KERN_ERR "reg %04x bit %d: %02x %02x %02x\n", reg, bit, val, valoff, valon);
+ printk(KERN_ERR "reg %04x bit %d: %02x %02x %02x\n",
+ reg, bit, val, valoff, valon
+ );
}
#endif
-#if DEBUG_MISC
-static void
+static inline void
snd_azf3328_debug_show_ports(const struct snd_azf3328 *chip)
{
+#if DEBUG_MISC
u16 tmp;
- snd_azf3328_dbgmisc("codec_port 0x%lx, io2_port 0x%lx, mpu_port 0x%lx, synth_port 0x%lx, mixer_port 0x%lx, irq %d\n", chip->codec_port, chip->io2_port, chip->mpu_port, chip->synth_port, chip->mixer_port, chip->irq);
-
- snd_azf3328_dbgmisc("io2 %02x %02x %02x %02x %02x %02x\n", snd_azf3328_io2_inb(chip, 0), snd_azf3328_io2_inb(chip, 1), snd_azf3328_io2_inb(chip, 2), snd_azf3328_io2_inb(chip, 3), snd_azf3328_io2_inb(chip, 4), snd_azf3328_io2_inb(chip, 5));
-
- for (tmp=0; tmp <= 0x01; tmp += 1)
- snd_azf3328_dbgmisc("0x%02x: opl 0x%04x, mpu300 0x%04x, mpu310 0x%04x, mpu320 0x%04x, mpu330 0x%04x\n", tmp, inb(0x388 + tmp), inb(0x300 + tmp), inb(0x310 + tmp), inb(0x320 + tmp), inb(0x330 + tmp));
+ snd_azf3328_dbgmisc(
+ "codec_io 0x%lx, game_io 0x%lx, mpu_io 0x%lx, "
+ "opl3_io 0x%lx, mixer_io 0x%lx, irq %d\n",
+ chip->codec_io, chip->game_io, chip->mpu_io,
+ chip->opl3_io, chip->mixer_io, chip->irq
+ );
+
+ snd_azf3328_dbgmisc("game %02x %02x %02x %02x %02x %02x\n",
+ snd_azf3328_game_inb(chip, 0),
+ snd_azf3328_game_inb(chip, 1),
+ snd_azf3328_game_inb(chip, 2),
+ snd_azf3328_game_inb(chip, 3),
+ snd_azf3328_game_inb(chip, 4),
+ snd_azf3328_game_inb(chip, 5)
+ );
+
+ for (tmp = 0; tmp < 0x07; tmp += 1)
+ snd_azf3328_dbgmisc("mpu_io 0x%04x\n", inb(chip->mpu_io + tmp));
+
+ for (tmp = 0; tmp <= 0x07; tmp += 1)
+ snd_azf3328_dbgmisc("0x%02x: game200 0x%04x, game208 0x%04x\n",
+ tmp, inb(0x200 + tmp), inb(0x208 + tmp));
+
+ for (tmp = 0; tmp <= 0x01; tmp += 1)
+ snd_azf3328_dbgmisc(
+ "0x%02x: mpu300 0x%04x, mpu310 0x%04x, mpu320 0x%04x, "
+ "mpu330 0x%04x opl388 0x%04x opl38c 0x%04x\n",
+ tmp,
+ inb(0x300 + tmp),
+ inb(0x310 + tmp),
+ inb(0x320 + tmp),
+ inb(0x330 + tmp),
+ inb(0x388 + tmp),
+ inb(0x38c + tmp)
+ );
for (tmp = 0; tmp < AZF_IO_SIZE_CODEC; tmp += 2)
- snd_azf3328_dbgmisc("codec 0x%02x: 0x%04x\n", tmp, snd_azf3328_codec_inw(chip, tmp));
+ snd_azf3328_dbgmisc("codec 0x%02x: 0x%04x\n",
+ tmp, snd_azf3328_codec_inw(chip, tmp)
+ );
for (tmp = 0; tmp < AZF_IO_SIZE_MIXER; tmp += 2)
- snd_azf3328_dbgmisc("mixer 0x%02x: 0x%04x\n", tmp, snd_azf3328_mixer_inw(chip, tmp));
+ snd_azf3328_dbgmisc("mixer 0x%02x: 0x%04x\n",
+ tmp, snd_azf3328_mixer_inw(chip, tmp)
+ );
+#endif /* DEBUG_MISC */
}
-#else
-static inline void
-snd_azf3328_debug_show_ports(const struct snd_azf3328 *chip) {}
-#endif
static int __devinit
snd_azf3328_create(struct snd_card *card,
- struct pci_dev *pci,
- unsigned long device_type,
- struct snd_azf3328 ** rchip)
+ struct pci_dev *pci,
+ unsigned long device_type,
+ struct snd_azf3328 **rchip)
{
struct snd_azf3328 *chip;
int err;
@@ -1705,7 +2086,8 @@
*rchip = NULL;
- if ((err = pci_enable_device(pci)) < 0)
+ err = pci_enable_device(pci);
+ if (err < 0)
return err;
chip = kzalloc(sizeof(*chip), GFP_KERNEL);
@@ -1721,20 +2103,25 @@
/* check if we can restrict PCI DMA transfers to 24 bits */
if (pci_set_dma_mask(pci, DMA_24BIT_MASK) < 0 ||
pci_set_consistent_dma_mask(pci, DMA_24BIT_MASK) < 0) {
- snd_printk(KERN_ERR "architecture does not support 24bit PCI busmaster DMA\n");
+ snd_printk(KERN_ERR "architecture does not support "
+ "24bit PCI busmaster DMA\n"
+ );
err = -ENXIO;
goto out_err;
}
- if ((err = pci_request_regions(pci, "Aztech AZF3328")) < 0) {
+ err = pci_request_regions(pci, "Aztech AZF3328");
+ if (err < 0)
goto out_err;
- }
- chip->codec_port = pci_resource_start(pci, 0);
- chip->io2_port = pci_resource_start(pci, 1);
- chip->mpu_port = pci_resource_start(pci, 2);
- chip->synth_port = pci_resource_start(pci, 3);
- chip->mixer_port = pci_resource_start(pci, 4);
+ chip->codec_io = pci_resource_start(pci, 0);
+ chip->game_io = pci_resource_start(pci, 1);
+ chip->mpu_io = pci_resource_start(pci, 2);
+ chip->opl3_io = pci_resource_start(pci, 3);
+ chip->mixer_io = pci_resource_start(pci, 4);
+
+ chip->audio_stream[AZF_PLAYBACK].portbase = chip->codec_io + 0x00;
+ chip->audio_stream[AZF_CAPTURE].portbase = chip->codec_io + 0x20;
if (request_irq(pci->irq, snd_azf3328_interrupt,
IRQF_SHARED, card->shortname, chip)) {
@@ -1747,29 +2134,29 @@
synchronize_irq(chip->irq);
snd_azf3328_debug_show_ports(chip);
-
- if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops)) < 0) {
+
+ err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops);
+ if (err < 0)
goto out_err;
- }
/* create mixer interface & switches */
- if ((err = snd_azf3328_mixer_new(chip)) < 0)
+ err = snd_azf3328_mixer_new(chip);
+ if (err < 0)
goto out_err;
-#if 0
- /* set very low bitrate to reduce noise and power consumption? */
- snd_azf3328_setfmt(chip, IDX_IO_PLAY_SOUNDFORMAT, 5512, 8, 1);
-#endif
+ /* shutdown codecs to save power */
+ /* have snd_azf3328_codec_activity() act properly */
+ chip->audio_stream[AZF_PLAYBACK].running = 1;
+ snd_azf3328_codec_activity(chip, AZF_PLAYBACK, 0);
/* standard chip init stuff */
- /* default IRQ init value */
+ /* default IRQ init value */
tmp = DMA_PLAY_SOMETHING2|DMA_EPILOGUE_SOMETHING|DMA_SOMETHING_ELSE;
spin_lock_irq(&chip->reg_lock);
snd_azf3328_codec_outb(chip, IDX_IO_PLAY_FLAGS, tmp);
snd_azf3328_codec_outb(chip, IDX_IO_REC_FLAGS, tmp);
snd_azf3328_codec_outb(chip, IDX_IO_SOMETHING_FLAGS, tmp);
- snd_azf3328_codec_outb(chip, IDX_IO_TIMER_VALUE + 3, 0x00); /* disable timer */
spin_unlock_irq(&chip->reg_lock);
snd_card_set_dev(card, &pci->dev);
@@ -1805,52 +2192,61 @@
return -ENOENT;
}
- card = snd_card_new(index[dev], id[dev], THIS_MODULE, 0 );
+ card = snd_card_new(index[dev], id[dev], THIS_MODULE, 0);
if (card == NULL)
return -ENOMEM;
strcpy(card->driver, "AZF3328");
strcpy(card->shortname, "Aztech AZF3328 (PCI168)");
- if ((err = snd_azf3328_create(card, pci, pci_id->driver_data, &chip)) < 0) {
+ err = snd_azf3328_create(card, pci, pci_id->driver_data, &chip);
+ if (err < 0)
goto out_err;
- }
card->private_data = chip;
- if ((err = snd_mpu401_uart_new( card, 0, MPU401_HW_MPU401,
- chip->mpu_port, MPU401_INFO_INTEGRATED,
- pci->irq, 0, &chip->rmidi)) < 0) {
- snd_printk(KERN_ERR "azf3328: no MPU-401 device at 0x%lx?\n", chip->mpu_port);
+ err = snd_mpu401_uart_new(
+ card, 0, MPU401_HW_MPU401, chip->mpu_io, MPU401_INFO_INTEGRATED,
+ pci->irq, 0, &chip->rmidi
+ );
+ if (err < 0) {
+ snd_printk(KERN_ERR "azf3328: no MPU-401 device at 0x%lx?\n",
+ chip->mpu_io
+ );
goto out_err;
}
- if ((err = snd_azf3328_timer(chip, 0)) < 0) {
+ err = snd_azf3328_timer(chip, 0);
+ if (err < 0)
goto out_err;
- }
- if ((err = snd_azf3328_pcm(chip, 0)) < 0) {
+ err = snd_azf3328_pcm(chip, 0);
+ if (err < 0)
goto out_err;
- }
- if (snd_opl3_create(card, chip->synth_port, chip->synth_port+2,
+ if (snd_opl3_create(card, chip->opl3_io, chip->opl3_io+2,
OPL3_HW_AUTO, 1, &opl3) < 0) {
snd_printk(KERN_ERR "azf3328: no OPL3 device at 0x%lx-0x%lx?\n",
- chip->synth_port, chip->synth_port+2 );
+ chip->opl3_io, chip->opl3_io+2
+ );
} else {
- if ((err = snd_opl3_hwdep_new(opl3, 0, 1, NULL)) < 0) {
+ /* need to use IDs 1, 2 since ID 0 is snd_azf3328_timer above */
+ err = snd_opl3_timer_new(opl3, 1, 2);
+ if (err < 0)
+ goto out_err;
+ err = snd_opl3_hwdep_new(opl3, 0, 1, NULL);
+ if (err < 0)
goto out_err;
- }
}
opl3->private_data = chip;
sprintf(card->longname, "%s at 0x%lx, irq %i",
- card->shortname, chip->codec_port, chip->irq);
+ card->shortname, chip->codec_io, chip->irq);
- if ((err = snd_card_register(card)) < 0) {
+ err = snd_card_register(card);
+ if (err < 0)
goto out_err;
- }
#ifdef MODULE
printk(
@@ -1861,19 +2257,18 @@
1024000 / seqtimer_scaling, seqtimer_scaling);
#endif
- if (snd_azf3328_config_joystick(chip, dev) < 0)
- snd_azf3328_io2_outb(chip, IDX_IO2_LEGACY_ADDR,
- snd_azf3328_io2_inb(chip, IDX_IO2_LEGACY_ADDR) & ~LEGACY_JOY);
+ snd_azf3328_gameport(chip, dev);
pci_set_drvdata(pci, card);
dev++;
err = 0;
goto out;
-
+
out_err:
+ snd_printk(KERN_ERR "azf3328: something failed, exiting\n");
snd_card_free(card);
-
+
out:
snd_azf3328_dbgcallleave();
return err;
@@ -1894,27 +2289,27 @@
{
struct snd_card *card = pci_get_drvdata(pci);
struct snd_azf3328 *chip = card->private_data;
- int reg;
+ unsigned reg;
snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
-
+
snd_pcm_suspend_all(chip->pcm);
- for (reg = 0; reg < AZF_IO_SIZE_MIXER_PM / 2; reg++)
- chip->saved_regs_mixer[reg] = inw(chip->mixer_port + reg * 2);
+ for (reg = 0; reg < AZF_IO_SIZE_MIXER_PM / 2; ++reg)
+ chip->saved_regs_mixer[reg] = inw(chip->mixer_io + reg * 2);
/* make sure to disable master volume etc. to prevent looping sound */
snd_azf3328_mixer_set_mute(chip, IDX_MIXER_PLAY_MASTER, 1);
snd_azf3328_mixer_set_mute(chip, IDX_MIXER_WAVEOUT, 1);
-
- for (reg = 0; reg < AZF_IO_SIZE_CODEC_PM / 2; reg++)
- chip->saved_regs_codec[reg] = inw(chip->codec_port + reg * 2);
- for (reg = 0; reg < AZF_IO_SIZE_IO2_PM / 2; reg++)
- chip->saved_regs_io2[reg] = inw(chip->io2_port + reg * 2);
- for (reg = 0; reg < AZF_IO_SIZE_MPU_PM / 2; reg++)
- chip->saved_regs_mpu[reg] = inw(chip->mpu_port + reg * 2);
- for (reg = 0; reg < AZF_IO_SIZE_SYNTH_PM / 2; reg++)
- chip->saved_regs_synth[reg] = inw(chip->synth_port + reg * 2);
+
+ for (reg = 0; reg < AZF_IO_SIZE_CODEC_PM / 2; ++reg)
+ chip->saved_regs_codec[reg] = inw(chip->codec_io + reg * 2);
+ for (reg = 0; reg < AZF_IO_SIZE_GAME_PM / 2; ++reg)
+ chip->saved_regs_game[reg] = inw(chip->game_io + reg * 2);
+ for (reg = 0; reg < AZF_IO_SIZE_MPU_PM / 2; ++reg)
+ chip->saved_regs_mpu[reg] = inw(chip->mpu_io + reg * 2);
+ for (reg = 0; reg < AZF_IO_SIZE_OPL3_PM / 2; ++reg)
+ chip->saved_regs_opl3[reg] = inw(chip->opl3_io + reg * 2);
pci_disable_device(pci);
pci_save_state(pci);
@@ -1927,7 +2322,7 @@
{
struct snd_card *card = pci_get_drvdata(pci);
struct snd_azf3328 *chip = card->private_data;
- int reg;
+ unsigned reg;
pci_set_power_state(pci, PCI_D0);
pci_restore_state(pci);
@@ -1939,23 +2334,21 @@
}
pci_set_master(pci);
- for (reg = 0; reg < AZF_IO_SIZE_IO2_PM / 2; reg++)
- outw(chip->saved_regs_io2[reg], chip->io2_port + reg * 2);
- for (reg = 0; reg < AZF_IO_SIZE_MPU_PM / 2; reg++)
- outw(chip->saved_regs_mpu[reg], chip->mpu_port + reg * 2);
- for (reg = 0; reg < AZF_IO_SIZE_SYNTH_PM / 2; reg++)
- outw(chip->saved_regs_synth[reg], chip->synth_port + reg * 2);
- for (reg = 0; reg < AZF_IO_SIZE_MIXER_PM / 2; reg++)
- outw(chip->saved_regs_mixer[reg], chip->mixer_port + reg * 2);
- for (reg = 0; reg < AZF_IO_SIZE_CODEC_PM / 2; reg++)
- outw(chip->saved_regs_codec[reg], chip->codec_port + reg * 2);
+ for (reg = 0; reg < AZF_IO_SIZE_GAME_PM / 2; ++reg)
+ outw(chip->saved_regs_game[reg], chip->game_io + reg * 2);
+ for (reg = 0; reg < AZF_IO_SIZE_MPU_PM / 2; ++reg)
+ outw(chip->saved_regs_mpu[reg], chip->mpu_io + reg * 2);
+ for (reg = 0; reg < AZF_IO_SIZE_OPL3_PM / 2; ++reg)
+ outw(chip->saved_regs_opl3[reg], chip->opl3_io + reg * 2);
+ for (reg = 0; reg < AZF_IO_SIZE_MIXER_PM / 2; ++reg)
+ outw(chip->saved_regs_mixer[reg], chip->mixer_io + reg * 2);
+ for (reg = 0; reg < AZF_IO_SIZE_CODEC_PM / 2; ++reg)
+ outw(chip->saved_regs_codec[reg], chip->codec_io + reg * 2);
snd_power_change_state(card, SNDRV_CTL_POWER_D0);
return 0;
}
-#endif
-
-
+#endif /* CONFIG_PM */
static struct pci_driver driver = {
--- linux-2.6.26-rc1.orig/sound/pci/azt3328.h 2008-05-03 20:59:44.000000000 +0200
+++ linux-2.6.26-rc1/sound/pci/azt3328.h 2008-05-14 22:47:12.000000000 +0200
@@ -54,7 +54,10 @@
#define SOUNDFORMAT_XTAL1 0x00
#define SOUNDFORMAT_XTAL2 0x01
/* all _SUSPECTED_ values are not used by Windows drivers, so we don't
- * have any hard facts, only rough measurements */
+ * have any hard facts, only rough measurements.
+ * All we know is that the crystal used on the board has 24.576MHz,
+ * like many soundcards (which results in the frequencies below when
+ * using certain divider values selected by the values below) */
#define SOUNDFORMAT_FREQ_SUSPECTED_4000 0x0c | SOUNDFORMAT_XTAL1
#define SOUNDFORMAT_FREQ_SUSPECTED_4800 0x0a | SOUNDFORMAT_XTAL1
#define SOUNDFORMAT_FREQ_5510 0x0c | SOUNDFORMAT_XTAL2
@@ -72,6 +75,26 @@
#define SOUNDFORMAT_FLAG_16BIT 0x0010
#define SOUNDFORMAT_FLAG_2CHANNELS 0x0020
+/* define frequency helpers, for maximum value safety */
+enum {
+#define AZF_FREQ(rate) AZF_FREQ_##rate = rate
+ AZF_FREQ(4000),
+ AZF_FREQ(4800),
+ AZF_FREQ(5512),
+ AZF_FREQ(6620),
+ AZF_FREQ(8000),
+ AZF_FREQ(9600),
+ AZF_FREQ(11025),
+ AZF_FREQ(13240),
+ AZF_FREQ(16000),
+ AZF_FREQ(22050),
+ AZF_FREQ(32000),
+ AZF_FREQ(44100),
+ AZF_FREQ(48000),
+ AZF_FREQ(66200),
+#undef AZF_FREQ
+} AZF_FREQUENCIES;
+
/** recording area (see also: playback bit flag definitions) **/
#define IDX_IO_REC_FLAGS 0x20 /* ??, PU:0x0000 */
#define IDX_IO_REC_IRQTYPE 0x22 /* ??, PU:0x0000 */
@@ -97,40 +120,164 @@
/** DirectX timer, main interrupt area (FIXME: and something else?) **/
#define IDX_IO_TIMER_VALUE 0x60 /* found this timer area by pure luck :-) */
- #define TIMER_VALUE_MASK 0x000fffffUL /* timer countdown value; triggers IRQ when timer is finished */
- #define TIMER_ENABLE_COUNTDOWN 0x01000000UL /* activate the timer countdown */
- #define TIMER_ENABLE_IRQ 0x02000000UL /* trigger timer IRQ on zero transition */
- #define TIMER_ACK_IRQ 0x04000000UL /* being set in IRQ handler in case port 0x00 (hmm, not port 0x64!?!?) had 0x0020 set upon IRQ handler */
+ /* timer countdown value; triggers IRQ when timer is finished */
+ #define TIMER_VALUE_MASK 0x000fffffUL
+ /* activate timer countdown */
+ #define TIMER_COUNTDOWN_ENABLE 0x01000000UL
+ /* trigger timer IRQ on zero transition */
+ #define TIMER_IRQ_ENABLE 0x02000000UL
+ /* being set in IRQ handler in case port 0x00 (hmm, not port 0x64!?!?)
+ * had 0x0020 set upon IRQ handler */
+ #define TIMER_IRQ_ACK 0x04000000UL
#define IDX_IO_IRQSTATUS 0x64
- #define IRQ_PLAYBACK 0x0001
- #define IRQ_RECORDING 0x0002
- #define IRQ_MPU401 0x0010
- #define IRQ_TIMER 0x0020 /* DirectX timer */
- #define IRQ_UNKNOWN1 0x0040 /* probably unused, or possibly I2S port? or gameport IRQ? */
- #define IRQ_UNKNOWN2 0x0080 /* probably unused, or possibly I2S port? or gameport IRQ? */
+ /* some IRQ bit in here might also be used to signal a power-management timer
+ * timeout, to request shutdown of the chip (e.g. AD1815JS has such a thing).
+ * Some OPL3 hardware (e.g. in LM4560) has some special timer hardware which
+ * can trigger an OPL3 timer IRQ, so maybe there's such a thing as well... */
+
+ #define IRQ_PLAYBACK 0x0001
+ #define IRQ_RECORDING 0x0002
+ #define IRQ_UNKNOWN1 0x0004 /* most probably I2S port */
+ #define IRQ_GAMEPORT 0x0008 /* Interrupt of Digital(ly) Enhanced Game Port */
+ #define IRQ_MPU401 0x0010
+ #define IRQ_TIMER 0x0020 /* DirectX timer */
+ #define IRQ_UNKNOWN2 0x0040 /* probably unused, or possibly I2S port? */
+ #define IRQ_UNKNOWN3 0x0080 /* probably unused, or possibly I2S port? */
#define IDX_IO_66H 0x66 /* writing 0xffff returns 0x0000 */
-#define IDX_IO_SOME_VALUE 0x68 /* this is set to e.g. 0x3ff or 0x300, and writable; maybe some buffer limit, but I couldn't find out more, PU:0x00ff */
-#define IDX_IO_6AH 0x6A /* this WORD can be set to have bits 0x0028 activated (FIXME: correct??); actually inhibits PCM playback!!! maybe power management?? */
- #define IO_6A_PAUSE_PLAYBACK 0x0200 /* bit 9; sure, this pauses playback, but what the heck is this really about?? */
-#define IDX_IO_6CH 0x6C
-#define IDX_IO_6EH 0x6E /* writing 0xffff returns 0x83fe */
-/* further I/O indices not saved/restored, so probably not used */
+ /* this is set to e.g. 0x3ff or 0x300, and writable;
+ * maybe some buffer limit, but I couldn't find out more, PU:0x00ff: */
+#define IDX_IO_SOME_VALUE 0x68
+ #define IO_68_RANDOM_TOGGLE1 0x0100 /* toggles randomly */
+ #define IO_68_RANDOM_TOGGLE2 0x0200 /* toggles randomly */
+ /* umm, nope, behaviour of these bits changes depending on what we wrote
+ * to 0x6b!! */
+
+/* this WORD can be set to have bits 0x0028 activated (FIXME: correct??);
+ * actually inhibits PCM playback!!! maybe power management??: */
+#define IDX_IO_6AH 0x6A
+ /* bit 5: enabling this will activate permanent counting of bytes 2/3
+ * at gameport I/O (0xb402/3) (equal values each) and cause
+ * gameport legacy I/O at 0x0200 to be _DISABLED_!
+ * Is this Digital Enhanced Game Port Enable??? Or maybe it's Testmode
+ * for Enhanced Digital Gameport (see 4D Wave DX card): */
+ #define IO_6A_SOMETHING1_GAMEPORT 0x0020
+ /* bit 8; sure, this _pauses_ playback (later resumes at same spot!),
+ * but what the heck is this really about??: */
+ #define IO_6A_PAUSE_PLAYBACK_BIT8 0x0100
+ /* bit 9; sure, this _pauses_ playback (later resumes at same spot!),
+ * but what the heck is this really about??: */
+ #define IO_6A_PAUSE_PLAYBACK_BIT9 0x0200
+ /* BIT8 and BIT9 are _NOT_ able to affect OPL3 MIDI playback,
+ * thus it suggests influence on PCM only!!
+ * However OTOH there seems to be no bit anywhere around here
+ * which is able to disable OPL3... */
+ /* bit 10: enabling this actually changes values at legacy gameport
+ * I/O address (0x200); is this enabling of the Digital Enhanced Game Port???
+ * Or maybe this simply switches off the NE558 circuit, since enabling this
+ * still lets us evaluate button states, but not axis states */
+ #define IO_6A_SOMETHING2_GAMEPORT 0x0400
+ /* writing 0x0300: causes quite some crackling during
+ * PC activity such as switching windows (PCI traffic??
+ * --> FIFO/timing settings???) */
+ /* writing 0x0100 plus/or 0x0200 inhibits playback */
+ /* since the Windows .INF file has Flag_Enable_JoyStick and
+ * Flag_Enable_SB_DOS_Emulation directly together, it stands to reason
+ * that some other bit in this same register might be responsible
+ * for SB DOS Emulation activation (note that the file did NOT define
+ * a switch for OPL3!) */
+#define IDX_IO_6CH 0x6C /* unknown; fully read-writable */
+#define IDX_IO_6EH 0x6E
+ /* writing 0xffff returns 0x83fe (or 0x03fe only).
+ * writing 0x83 (and only 0x83!!) to 0x6f will cause 0x6c to switch
+ * from 0000 to ffff. */
+
+/* further I/O indices not saved/restored and not readable after writing,
+ * so probably not used */
-/*** I/O 2 area port indices ***/
+/*** Gameport area port indices ***/
/* (only 0x06 of 0x08 bytes saved/restored by Windows driver) */
-#define AZF_IO_SIZE_IO2 0x08
-#define AZF_IO_SIZE_IO2_PM 0x06
+#define AZF_IO_SIZE_GAME 0x08
+#define AZF_IO_SIZE_GAME_PM 0x06
-#define IDX_IO2_LEGACY_ADDR 0x04
- #define LEGACY_SOMETHING 0x01 /* OPL3?? */
- #define LEGACY_JOY 0x08
+enum {
+ AZF_GAME_LEGACY_IO_PORT = 0x200
+} AZF_GAME_CONFIGS;
+
+#define IDX_GAME_LEGACY_COMPATIBLE 0x00
+ /* in some operation mode, writing anything to this port
+ * triggers an interrupt:
+ * yup, that's in case IDX_GAME_01H has one of the
+ * axis measurement bits enabled
+ * (and of course one needs to have GAME_HWCFG_IRQ_ENABLE, too) */
+
+#define IDX_GAME_AXES_CONFIG 0x01
+ /* NOTE: layout of this register awfully similar (read: "identical??")
+ * to AD1815JS.pdf (p.29) */
+
+ /* enables axis 1 (X axis) measurement: */
+ #define GAME_AXES_ENABLE_1 0x01
+ /* enables axis 2 (Y axis) measurement: */
+ #define GAME_AXES_ENABLE_2 0x02
+ /* enables axis 3 (X axis) measurement: */
+ #define GAME_AXES_ENABLE_3 0x04
+ /* enables axis 4 (Y axis) measurement: */
+ #define GAME_AXES_ENABLE_4 0x08
+ /* selects the current axis to read the measured value of
+ * (at IDX_GAME_AXIS_VALUE):
+ * 00 = axis 1, 01 = axis 2, 10 = axis 3, 11 = axis 4: */
+ #define GAME_AXES_READ_MASK 0x30
+ /* enable to have the latch continuously accept ADC values
+ * (and continuously cause interrupts in case interrupts are enabled);
+ * AD1815JS.pdf says it's ~16ms interval there: */
+ #define GAME_AXES_LATCH_ENABLE 0x40
+ /* joystick data (measured axes) ready for reading: */
+ #define GAME_AXES_SAMPLING_READY 0x80
+
+ /* NOTE: other card specs (SiS960 and others!) state that the
+ * game position latches should be frozen when reading and be freed
+ * (== reset?) after reading!!!
+ * Freezing most likely means disabling 0x40 (GAME_AXES_LATCH_ENABLE),
+ * but how to free the value? */
+ /* An internet search for "gameport latch ADC" should provide some insight
+ * into how to program such a gameport system. */
+
+ /* writing 0xf0 to 01H once reset both counters to 0, in some special mode!?
+ * yup, in case 6AH 0x20 is not enabled
+ * (and 0x40 is sufficient, 0xf0 is not needed) */
+
+#define IDX_GAME_AXIS_VALUE 0x02
+ /* R: value of currently configured axis (word value!);
+ * W: trigger axis measurement */
+
+#define IDX_GAME_HWCONFIG 0x04
+ /* note: bits 4 to 7 are never set (== 0) when reading!
+ * --> reserved bits? */
+ /* enables IRQ notification upon axes measurement ready: */
+ #define GAME_HWCFG_IRQ_ENABLE 0x01
+ /* these bits choose a different frequency for the
+ * internal ADC counter increment.
+ * hmm, seems to be a combo of bits:
+ * 00 --> standard frequency
+ * 10 --> 1/2
+ * 01 --> 1/20
+ * 11 --> 1/200: */
+ #define GAME_HWCFG_ADC_COUNTER_FREQ_MASK 0x06
+
+ /* enable gameport legacy I/O address (0x200)
+ * I was unable to locate any configurability for a different address: */
+ #define GAME_HWCFG_LEGACY_ADDRESS_ENABLE 0x08
+/*** MPU401 ***/
#define AZF_IO_SIZE_MPU 0x04
#define AZF_IO_SIZE_MPU_PM 0x04
-#define AZF_IO_SIZE_SYNTH 0x08
-#define AZF_IO_SIZE_SYNTH_PM 0x06
+/*** OPL3 synth ***/
+#define AZF_IO_SIZE_OPL3 0x08
+#define AZF_IO_SIZE_OPL3_PM 0x06
+/* hmm, given that a standard OPL3 has 4 registers only,
+ * there might be some enhanced functionality lurking at the end
+ * (especially since register 0x04 has a "non-empty" value 0xfe) */
/*** mixer I/O area port indices ***/
/* (only 0x22 of 0x40 bytes saved/restored by Windows driver)
2
3

16 May '08
Peter Wurmsdobler wrote:
> What ioctl request code would I have to pass in order to reach this
> control's put/get methods?
snd_ctl_elem_write() (alsa-lib/src/control/control.c)
snd_ctl_hw_elem_write() (alsa-lib/src/control/control_hw.c)
ioctl(SNDRV_CTL_IOCTL_ELEM_WRITE)
snd_ctl_ioctl() (linux/sound/core/control.c)
snd_ctl_elem_write_user()
snd_ctl_elem_write()
kctl->put()
HTH
Clemens
2
1