Alsa-devel
Threads by month
- ----- 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
March 2015
- 148 participants
- 318 discussions
Any access to the component_list, codec_list and platform_list needs to be
properly locked by the client_mutex. Otherwise undefined behavior can occur
if the list is modified in one thread and concurrently accessed from another
thread.
This patch adds the missing locking to the debugfs file handlers that
display the registered components, as well as the various components
unregister functions.
Furthermore the client_lock is now held for the whole
snd_soc_instantiate_card() sequence to make sure that component removal does
not race against the card registration.
Reported-by: Takashi Iwai <tiwai(a)suse.de>
Signed-off-by: Lars-Peter Clausen <lars(a)metafoo.de>
---
sound/soc/soc-core.c | 41 ++++++++++++++++++++++++++++++-----------
1 file changed, 30 insertions(+), 11 deletions(-)
diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c
index 5c0658d..07aa543 100644
--- a/sound/soc/soc-core.c
+++ b/sound/soc/soc-core.c
@@ -347,6 +347,8 @@ static ssize_t codec_list_read_file(struct file *file, char __user *user_buf,
if (!buf)
return -ENOMEM;
+ mutex_lock(&client_mutex);
+
list_for_each_entry(codec, &codec_list, list) {
len = snprintf(buf + ret, PAGE_SIZE - ret, "%s\n",
codec->component.name);
@@ -358,6 +360,8 @@ static ssize_t codec_list_read_file(struct file *file, char __user *user_buf,
}
}
+ mutex_unlock(&client_mutex);
+
if (ret >= 0)
ret = simple_read_from_buffer(user_buf, count, ppos, buf, ret);
@@ -382,6 +386,8 @@ static ssize_t dai_list_read_file(struct file *file, char __user *user_buf,
if (!buf)
return -ENOMEM;
+ mutex_lock(&client_mutex);
+
list_for_each_entry(component, &component_list, list) {
list_for_each_entry(dai, &component->dai_list, list) {
len = snprintf(buf + ret, PAGE_SIZE - ret, "%s\n",
@@ -395,6 +401,8 @@ static ssize_t dai_list_read_file(struct file *file, char __user *user_buf,
}
}
+ mutex_unlock(&client_mutex);
+
ret = simple_read_from_buffer(user_buf, count, ppos, buf, ret);
kfree(buf);
@@ -418,6 +426,8 @@ static ssize_t platform_list_read_file(struct file *file,
if (!buf)
return -ENOMEM;
+ mutex_lock(&client_mutex);
+
list_for_each_entry(platform, &platform_list, list) {
len = snprintf(buf + ret, PAGE_SIZE - ret, "%s\n",
platform->component.name);
@@ -429,6 +439,8 @@ static ssize_t platform_list_read_file(struct file *file,
}
}
+ mutex_unlock(&client_mutex);
+
ret = simple_read_from_buffer(user_buf, count, ppos, buf, ret);
kfree(buf);
@@ -836,6 +848,8 @@ static struct snd_soc_component *soc_find_component(
{
struct snd_soc_component *component;
+ lockdep_assert_held(&client_mutex);
+
list_for_each_entry(component, &component_list, list) {
if (of_node) {
if (component->dev->of_node == of_node)
@@ -854,6 +868,8 @@ static struct snd_soc_dai *snd_soc_find_dai(
struct snd_soc_component *component;
struct snd_soc_dai *dai;
+ lockdep_assert_held(&client_mutex);
+
/* Find CPU DAI from registered DAIs*/
list_for_each_entry(component, &component_list, list) {
if (dlc->of_node && component->dev->of_node != dlc->of_node)
@@ -1508,6 +1524,7 @@ static int snd_soc_instantiate_card(struct snd_soc_card *card)
struct snd_soc_codec *codec;
int ret, i, order;
+ mutex_lock(&client_mutex);
mutex_lock_nested(&card->mutex, SND_SOC_CARD_CLASS_INIT);
/* bind DAIs */
@@ -1670,6 +1687,7 @@ static int snd_soc_instantiate_card(struct snd_soc_card *card)
card->instantiated = 1;
snd_soc_dapm_sync(&card->dapm);
mutex_unlock(&card->mutex);
+ mutex_unlock(&client_mutex);
return 0;
@@ -1688,6 +1706,7 @@ card_probe_error:
base_error:
mutex_unlock(&card->mutex);
+ mutex_unlock(&client_mutex);
return ret;
}
@@ -2721,13 +2740,6 @@ static void snd_soc_component_del_unlocked(struct snd_soc_component *component)
list_del(&component->list);
}
-static void snd_soc_component_del(struct snd_soc_component *component)
-{
- mutex_lock(&client_mutex);
- snd_soc_component_del_unlocked(component);
- mutex_unlock(&client_mutex);
-}
-
int snd_soc_register_component(struct device *dev,
const struct snd_soc_component_driver *cmpnt_drv,
struct snd_soc_dai_driver *dai_drv,
@@ -2775,14 +2787,17 @@ void snd_soc_unregister_component(struct device *dev)
{
struct snd_soc_component *cmpnt;
+ mutex_lock(&client_mutex);
list_for_each_entry(cmpnt, &component_list, list) {
if (dev == cmpnt->dev && cmpnt->registered_as_component)
goto found;
}
+ mutex_unlock(&client_mutex);
return;
found:
- snd_soc_component_del(cmpnt);
+ snd_soc_component_del_unlocked(cmpnt);
+ mutex_unlock(&client_mutex);
snd_soc_component_cleanup(cmpnt);
kfree(cmpnt);
}
@@ -2890,10 +2905,14 @@ struct snd_soc_platform *snd_soc_lookup_platform(struct device *dev)
{
struct snd_soc_platform *platform;
+ mutex_lock(&client_mutex);
list_for_each_entry(platform, &platform_list, list) {
- if (dev == platform->dev)
+ if (dev == platform->dev) {
+ mutex_unlock(&client_mutex);
return platform;
+ }
}
+ mutex_unlock(&client_mutex);
return NULL;
}
@@ -3098,15 +3117,15 @@ void snd_soc_unregister_codec(struct device *dev)
{
struct snd_soc_codec *codec;
+ mutex_lock(&client_mutex);
list_for_each_entry(codec, &codec_list, list) {
if (dev == codec->dev)
goto found;
}
+ mutex_unlock(&client_mutex);
return;
found:
-
- mutex_lock(&client_mutex);
list_del(&codec->list);
snd_soc_component_del_unlocked(&codec->component);
mutex_unlock(&client_mutex);
--
1.8.0
2
1
[alsa-devel] [PATCH] ALSA: hda - Fix regression of HD-audio controller fallback modes
by Takashi Iwai 08 Mar '15
by Takashi Iwai 08 Mar '15
08 Mar '15
The commit [63e51fd708f5: ALSA: hda - Don't take unresponsive D3
transition too serious] introduced a conditional fallback behavior to
the HD-audio controller depending on the flag set. However, it
introduced a silly bug, too, that the flag was evaluated in a reverse
way. This resulted in a regression of HD-audio controller driver
where it can't go to the fallback mode at communication errors.
Unfortunately (or fortunately?) this didn't come up until recently
because the affected code path is an error handling that happens only
on an unstable hardware chip. Most of recent chips work stably, thus
they didn't hit this problem. Now, we've got a regression report with
a VIA chip, and this seems indeed requiring the fallback to the
polling mode, and finally the bug was revealed.
The fix is a oneliner to remove the wrong logical NOT in the check.
(Lesson learned - be careful about double negation.)
The bug should be backported to stable, but the patch won't be
applicable to 3.13 or earlier because of the code splits. The stable
fix patches for earlier kernels will be posted later manually.
Bugzilla: https://bugzilla.kernel.org/show_bug.cgi?id=94021
Fixes: 63e51fd708f5 ('ALSA: hda - Don't take unresponsive D3 transition too serious')
Cc: <stable(a)vger.kernel.org> # v3.14+
Signed-off-by: Takashi Iwai <tiwai(a)suse.de>
---
sound/pci/hda/hda_controller.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/sound/pci/hda/hda_controller.c b/sound/pci/hda/hda_controller.c
index a2ce773bdc62..17c2637d842c 100644
--- a/sound/pci/hda/hda_controller.c
+++ b/sound/pci/hda/hda_controller.c
@@ -1164,7 +1164,7 @@ static unsigned int azx_rirb_get_response(struct hda_bus *bus,
}
}
- if (!bus->no_response_fallback)
+ if (bus->no_response_fallback)
return -1;
if (!chip->polling_mode && chip->poll_count < 2) {
--
2.3.1
1
0
This patch series attempts to split existing HDA code to create common hda
library. This is done in order to make HDA code available to other users like
upcoming Intel platforms which sport a HDA controller along with an I2S
link. This series is 1st patch series which will try to refactor HDA by
splitting it up and then adding ASoC HDA controller driver for these
platforms.
This series moves code to sound/hda, renames the files mostly. The second
series will do API changes and subsequent ones will be using HDA bus patches
which Takashi posted recently
Comments welcome...
~Vinod
Jeeja KP (5):
ALSA: hda: move hdaudio header files to global include
ALSA: HDA: create HDA common lib
ALSA: hdalib - fix existing checkpatch issues
ALSA: hda: rename sound/hda/hda* as sound/hda/hdalib
ALSA: hdalib: rename common header files as hdalib_*
.../hda/hda_priv.h => include/sound/hda_register.h | 10 +--
.../sound/hdalib_auto_parser.h | 6 +-
.../hda/hda_beep.h => include/sound/hdalib_beep.h | 6 +-
.../hda_codec.h => include/sound/hdalib_codec.h | 23 +++---
.../sound/hdalib_controller.h | 11 +--
.../hda_local.h => include/sound/hdalib_controls.h | 20 ++---
.../sound/hdalib_generic.h | 6 +-
include/sound/{hda_hwdep.h => hdalib_hwdep.h} | 4 +-
.../hda/hda_jack.h => include/sound/hdalib_jack.h | 6 +-
sound/Kconfig | 2 +
sound/Makefile | 2 +-
sound/hda/Kconfig | 85 ++++++++++++++++++++
sound/hda/Makefile | 21 +++++
sound/{pci => }/hda/hda_intel_trace.h | 0
sound/{pci => }/hda/hda_trace.h | 0
.../hda_auto_parser.c => hda/hdalib_auto_parser.c} | 7 +-
sound/{pci/hda/hda_beep.c => hda/hdalib_beep.c} | 8 +-
sound/{pci/hda/hda_codec.c => hda/hdalib_codec.c} | 21 +++--
.../hda_controller.c => hda/hdalib_controller.c} | 10 ++-
sound/{pci/hda/hda_eld.c => hda/hdalib_eld.c} | 4 +-
.../hda/hda_generic.c => hda/hdalib_generic.c} | 13 +--
sound/{pci/hda/hda_hwdep.c => hda/hdalib_hwdep.c} | 6 +-
sound/{pci/hda/hda_jack.c => hda/hdalib_jack.c} | 8 +-
sound/{pci/hda/hda_proc.c => hda/hdalib_proc.c} | 13 +--
sound/{pci/hda/hda_sysfs.c => hda/hdalib_sysfs.c} | 6 +-
sound/pci/hda/Kconfig | 44 ----------
sound/pci/hda/Makefile | 14 +---
sound/pci/hda/hda_i915.c | 3 +-
sound/pci/hda/hda_intel.c | 7 +-
sound/pci/hda/patch_hdmi.c | 10 ++-
sound/pci/hda/patch_realtek.c | 10 ++-
31 files changed, 229 insertions(+), 157 deletions(-)
rename sound/pci/hda/hda_priv.h => include/sound/hda_register.h (98%)
rename sound/pci/hda/hda_auto_parser.h => include/sound/hdalib_auto_parser.h (96%)
rename sound/pci/hda/hda_beep.h => include/sound/hdalib_beep.h (95%)
rename sound/pci/hda/hda_codec.h => include/sound/hdalib_codec.h (97%)
rename sound/pci/hda/hda_controller.h => include/sound/hdalib_controller.h (90%)
rename sound/pci/hda/hda_local.h => include/sound/hdalib_controls.h (98%)
rename sound/pci/hda/hda_generic.h => include/sound/hdalib_generic.h (99%)
rename include/sound/{hda_hwdep.h => hdalib_hwdep.h} (95%)
rename sound/pci/hda/hda_jack.h => include/sound/hdalib_jack.h (96%)
create mode 100644 sound/hda/Kconfig
create mode 100644 sound/hda/Makefile
rename sound/{pci => }/hda/hda_intel_trace.h (100%)
rename sound/{pci => }/hda/hda_trace.h (100%)
rename sound/{pci/hda/hda_auto_parser.c => hda/hdalib_auto_parser.c} (99%)
rename sound/{pci/hda/hda_beep.c => hda/hdalib_beep.c} (98%)
rename sound/{pci/hda/hda_codec.c => hda/hdalib_codec.c} (99%)
rename sound/{pci/hda/hda_controller.c => hda/hdalib_controller.c} (99%)
rename sound/{pci/hda/hda_eld.c => hda/hdalib_eld.c} (99%)
rename sound/{pci/hda/hda_generic.c => hda/hdalib_generic.c} (99%)
rename sound/{pci/hda/hda_hwdep.c => hda/hdalib_hwdep.c} (96%)
rename sound/{pci/hda/hda_jack.c => hda/hdalib_jack.c} (99%)
rename sound/{pci/hda/hda_proc.c => hda/hdalib_proc.c} (99%)
rename sound/{pci/hda/hda_sysfs.c => hda/hdalib_sysfs.c} (99%)
--
1.7.9.5
2
8
[alsa-devel] [PATCH 1/2] snd-waveartist: Introduce Rockwell WaveArtist RWA010 driver
by Ondrej Zary 08 Mar '15
by Ondrej Zary 08 Mar '15
08 Mar '15
This is a new ALSA driver driver for Rockwell WaveArtist RWA010 chips found
on some (rare) ISA sound cards, such as DCS Multimedia S717.
I wasn't able to get the old OSS WaveArtist driver to work with my card but it
was a great source of information about the chip (as the full datasheet is not
available - only a brief one and a design guide).
However, the OSS driver only supports few of the mixer registers so I had to
install the card in Windows and dump mixer registers while changing the mixer
settings. Then tried what the rest of the registers do and the result is a
fully working mixer which even supports more controls than the Windows driver.
Someone with a NetWinder can add support for it to this driver and then remove
the old OSS one.
Signed-off-by: Ondrej Zary <linux(a)rainbow-software.org>
---
include/sound/mpu401.h | 1 +
sound/isa/Kconfig | 11 +
sound/isa/Makefile | 2 +
sound/isa/waveartist.c | 1399 ++++++++++++++++++++++++++++++++++++++++++++++++
sound/isa/waveartist.h | 74 +++
5 files changed, 1487 insertions(+)
create mode 100644 sound/isa/waveartist.c
create mode 100644 sound/isa/waveartist.h
diff --git a/include/sound/mpu401.h b/include/sound/mpu401.h
index e942096..8fceeba 100644
--- a/include/sound/mpu401.h
+++ b/include/sound/mpu401.h
@@ -44,6 +44,7 @@
#define MPU401_HW_INTEL8X0 17 /* Intel8x0 driver */
#define MPU401_HW_PC98II 18 /* Roland PC98II */
#define MPU401_HW_AUREAL 19 /* Aureal Vortex */
+#define MPU401_HW_WAVEARTIST 20 /* Rockwell WaveArtist */
#define MPU401_INFO_INPUT (1 << 0) /* input stream */
#define MPU401_INFO_OUTPUT (1 << 1) /* output stream */
diff --git a/sound/isa/Kconfig b/sound/isa/Kconfig
index 0216475..7a3b4a2 100644
--- a/sound/isa/Kconfig
+++ b/sound/isa/Kconfig
@@ -454,5 +454,16 @@ config SND_MSND_CLASSIC
To compile this driver as a module, choose M here: the module
will be called snd-msnd-classic.
+config SND_WAVEARTIST
+ tristate "Rockwell WaveArtist RWA010"
+ select SND_OPL3_LIB
+ select SND_MPU401_UART
+ select SND_PCM
+ help
+ Say Y here to include support for Rockwell WaveArtist RWA010 chips.
+
+ To compile this driver as a module, choose M here: the module
+ will be called snd-waveartist.
+
endif # SND_ISA
diff --git a/sound/isa/Makefile b/sound/isa/Makefile
index 9a15f14..23e11e2 100644
--- a/sound/isa/Makefile
+++ b/sound/isa/Makefile
@@ -12,6 +12,7 @@ snd-es18xx-objs := es18xx.o
snd-opl3sa2-objs := opl3sa2.o
snd-sc6000-objs := sc6000.o
snd-sscape-objs := sscape.o
+snd-waveartist-objs := waveartist.o
# Toplevel Module Dependency
obj-$(CONFIG_SND_ADLIB) += snd-adlib.o
@@ -23,6 +24,7 @@ obj-$(CONFIG_SND_ES18XX) += snd-es18xx.o
obj-$(CONFIG_SND_OPL3SA2) += snd-opl3sa2.o
obj-$(CONFIG_SND_SC6000) += snd-sc6000.o
obj-$(CONFIG_SND_SSCAPE) += snd-sscape.o
+obj-$(CONFIG_SND_WAVEARTIST) += snd-waveartist.o
obj-$(CONFIG_SND) += ad1816a/ ad1848/ cs423x/ es1688/ galaxy/ gus/ msnd/ opti9xx/ \
sb/ wavefront/ wss/
diff --git a/sound/isa/waveartist.c b/sound/isa/waveartist.c
new file mode 100644
index 0000000..e5bda5c
--- /dev/null
+++ b/sound/isa/waveartist.c
@@ -0,0 +1,1399 @@
+/*
+ * Driver for Rockwell WaveArtist RWA010 soundcards
+ *
+ * Copyright (c) 2015 Ondrej Zary
+ *
+ * HW-related parts based on OSS WaveArtist driver by Hannu Savolainen
+ *
+ * ALSA code based on ES18xx driver by Christian Fischbach & Abramo Bagnara
+ * and also by OPL3-SA2 driver by Jaroslav Kysela
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/isa.h>
+#include <linux/pnp.h>
+#include <linux/isapnp.h>
+#include <asm/dma.h>
+#include <sound/core.h>
+#include <sound/control.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/mpu401.h>
+#include <sound/opl3.h>
+#include <sound/initval.h>
+#include "waveartist.h"
+
+MODULE_AUTHOR("Ondrej Zary");
+MODULE_DESCRIPTION("Driver for Rockwell WaveArtist RWA010 sound cards");
+MODULE_LICENSE("GPL");
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-MAX */
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* ID for this card */
+static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_ISAPNP;
+#ifdef CONFIG_PNP
+static bool isapnp[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 1};
+#endif
+static long port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; /* 0x250-0x3f0 */
+static long fm_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; /* 0x388-0x3f0 */
+static long midi_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;/* 0x300-0x3f0 */
+static int irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; /* 5,7,10,11 */
+static int dma1[SNDRV_CARDS] = SNDRV_DEFAULT_DMA; /* 5,6,7 */
+static int dma2[SNDRV_CARDS] = SNDRV_DEFAULT_DMA; /* 0,1,3 */
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for WaveArtist soundcard.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for WaveArtist soundcard.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable WaveArtist soundcard.");
+#ifdef CONFIG_PNP
+module_param_array(isapnp, bool, NULL, 0444);
+MODULE_PARM_DESC(isapnp, "PnP detection for specified soundcard.");
+#endif
+module_param_array(port, long, NULL, 0444);
+MODULE_PARM_DESC(port, "Port # for WaveArtist driver.");
+module_param_array(fm_port, long, NULL, 0444);
+MODULE_PARM_DESC(fm_port, "FM port # for WaveArtist driver.");
+module_param_array(midi_port, long, NULL, 0444);
+MODULE_PARM_DESC(midi_port, "MIDI port # for WaveArtist driver.");
+module_param_array(irq, int, NULL, 0444);
+MODULE_PARM_DESC(irq, "IRQ # for WaveArtist driver.");
+module_param_array(dma1, int, NULL, 0444);
+MODULE_PARM_DESC(dma1, "DMA1 # for WaveArtist driver.");
+module_param_array(dma2, int, NULL, 0444);
+MODULE_PARM_DESC(dma2, "DMA2 # for WaveArtist driver.");
+
+#ifdef CONFIG_PNP
+static int isa_registered;
+static int pnp_registered;
+#endif
+
+#define PFX "waveartist: "
+
+#ifdef CONFIG_PNP
+#define WA_DEVICE(pnpid) { \
+ .id = pnpid, \
+ .devs = { {"RSS5000"}, {"RSS5001"}, {"RSS5002"} } \
+}
+/*
+ * RSS5000 = WaveArtist, RSS5001 = SB, RSS5002 = MPU-401, RSS5003 = IDE,
+ * RSS5004 = gameport, RSS5005 = modem, RSS5006 = 3D
+ */
+
+static struct pnp_card_device_id snd_waveartist_pnpids[] = {
+ WA_DEVICE("RSS5000"), /* 16-bit decode */
+ WA_DEVICE("RSS5100"), /* 16-bit decode + modem */
+ WA_DEVICE("RSS5200"), /* 10-bit decode + modem */
+ WA_DEVICE("RSS5300"), /* 10-bit decode */
+ WA_DEVICE("RSS5400"), /* 16-bit decode + IDE */
+ WA_DEVICE("RSS5500"), /* 16-bit decode + modem + IDE */
+ WA_DEVICE("RSS5600"), /* 10-bit decode + IDE */
+ WA_DEVICE("RSS5700"), /* 10-bit decode + modem + IDE */
+ WA_DEVICE("RSS5800"), /* 10-bit decode + modem + 3D */
+ WA_DEVICE("RSS5900"), /* 10-bit decode + 3D */
+ WA_DEVICE("RSS5A00"), /* 16-bit decode + modem + 3D */
+ WA_DEVICE("RSS5B00"), /* 16-bit decode + 3D */
+ { .id = "" } /* end */
+};
+MODULE_DEVICE_TABLE(pnp_card, snd_waveartist_pnpids);
+#endif /* CONFIG_PNP */
+
+struct snd_waveartist {
+#ifdef CONFIG_PNP
+ struct pnp_dev *wa; /* WaveArtist device */
+ struct pnp_dev *sb; /* SB emulation device */
+ struct pnp_dev *mpu; /* MPU-401 device */
+#endif
+ unsigned long port; /* base port */
+ struct resource *res_port; /* base port resource */
+ int irq;
+ int dma_playback;
+ int dma_capture;
+
+ struct snd_card *card;
+ struct snd_pcm *pcm;
+ struct snd_pcm_substream *playback_substream;
+ struct snd_pcm_substream *capture_substream;
+
+ spinlock_t reg_lock;
+ struct snd_hwdep *synth;
+ struct snd_rawmidi *rmidi;
+
+ u16 image[20]; /* mixer registers image */
+};
+
+static inline void wa_outb(struct snd_waveartist *chip, u8 reg, u8 val)
+{
+ outb(val, chip->port + reg);
+}
+
+static inline u8 wa_inb(struct snd_waveartist *chip, u8 reg)
+{
+ return inb(chip->port + reg);
+}
+
+static inline void wa_outw(struct snd_waveartist *chip, u8 reg, u16 val)
+{
+ outw(val, chip->port + reg);
+}
+
+static inline u16 wa_inw(struct snd_waveartist *chip, u8 reg)
+{
+ return inw(chip->port + reg);
+}
+
+static inline void waveartist_set_ctlr(struct snd_waveartist *chip, u8 clear,
+ u8 set)
+{
+ clear = ~clear & wa_inb(chip, CTLR);
+ wa_outb(chip, CTLR, clear | set);
+}
+
+/* acknowledge IRQ */
+static inline void waveartist_iack(struct snd_waveartist *chip)
+{
+ u8 old_ctlr = wa_inb(chip, CTLR) & ~IRQ_ACK;
+
+ wa_outb(chip, CTLR, old_ctlr | IRQ_ACK);
+ wa_outb(chip, CTLR, old_ctlr);
+}
+
+static int waveartist_reset(struct snd_waveartist *chip)
+{
+ unsigned int timeout, res = -1;
+
+ waveartist_set_ctlr(chip, -1, RESET);
+ msleep(200);
+ waveartist_set_ctlr(chip, RESET, 0);
+
+ timeout = 500;
+ do {
+ mdelay(2);
+
+ if (wa_inb(chip, STATR) & CMD_RF) {
+ res = wa_inw(chip, CMDR);
+ if (res == 0x55aa)
+ break;
+ }
+ } while (--timeout);
+
+ if (timeout == 0) {
+ dev_warn(chip->card->dev, "WaveArtist: reset timeout (res=0x%x)\n",
+ res);
+ return 1;
+ }
+
+ return 0;
+}
+
+/* Helper function to send and receive words
+ * from WaveArtist. It handles all the handshaking
+ * and can send or receive multiple words.
+ */
+static int waveartist_cmd(struct snd_waveartist *chip,
+ int nr_cmd, u16 *cmd,
+ int nr_resp, u16 *resp)
+{
+ unsigned long flags;
+ unsigned int timed_out = 0, i;
+
+ spin_lock_irqsave(&chip->reg_lock, flags);
+ /*
+ * The chip can hang if we access the STATR register too quickly
+ * after a write. Do a dummy read to slow down.
+ */
+ wa_inb(chip, CTLR);
+
+ if (wa_inb(chip, STATR) & CMD_RF) {
+ /* flush the port */
+ wa_inw(chip, CMDR);
+ udelay(10);
+ }
+
+ for (i = 0; !timed_out && i < nr_cmd; i++) {
+ int count;
+
+ for (count = 5000; count; count--)
+ if (wa_inb(chip, STATR) & CMD_WE)
+ break;
+
+ if (!count)
+ timed_out = 1;
+ else
+ wa_outw(chip, CMDR, cmd[i]);
+ /* Another dummy read */
+ wa_inb(chip, CTLR);
+ }
+
+ for (i = 0; !timed_out && i < nr_resp; i++) {
+ int count;
+
+ for (count = 5000; count; count--)
+ if (wa_inb(chip, STATR) & CMD_RF)
+ break;
+
+ if (!count)
+ timed_out = 1;
+ else
+ resp[i] = wa_inw(chip, CMDR);
+ }
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+
+ return timed_out ? 1 : 0;
+}
+
+/* Send one command word */
+static inline int waveartist_cmd1(struct snd_waveartist *chip, u16 cmd)
+{
+ return waveartist_cmd(chip, 1, &cmd, 0, NULL);
+}
+
+/* Send one command, receive one word */
+static inline u16 waveartist_cmd1_r(struct snd_waveartist *chip, u16 cmd)
+{
+ u16 ret;
+
+ waveartist_cmd(chip, 1, &cmd, 1, &ret);
+
+ return ret;
+}
+
+/* Send a double command, receive one word (and throw it away) */
+static inline int waveartist_cmd2(struct snd_waveartist *chip, u16 cmd, u16 arg)
+{
+ u16 vals[2] = { cmd, arg };
+
+ return waveartist_cmd(chip, 2, vals, 1, vals);
+}
+
+/* Send a triple command */
+static inline int waveartist_cmd3(struct snd_waveartist *chip, u16 cmd,
+ u16 arg1, u16 arg2)
+{
+ u16 vals[3] = { cmd, arg1, arg2 };
+
+ return waveartist_cmd(chip, 3, vals, 0, NULL);
+}
+
+static u16 waveartist_getrev(struct snd_waveartist *chip)
+{
+ u16 temp[2];
+ u16 cmd = WACMD_GETREV;
+
+ waveartist_cmd(chip, 1, &cmd, 2, temp);
+
+ return temp[0];
+}
+
+static irqreturn_t snd_waveartist_interrupt(int irq, void *dev_id)
+{
+ u8 status, irqstatus;
+ struct snd_card *card = dev_id;
+ struct snd_waveartist *chip;
+
+ if (card == NULL)
+ return IRQ_NONE;
+
+ chip = card->private_data;
+
+ irqstatus = wa_inb(chip, IRQSTAT);
+ status = wa_inb(chip, STATR);
+
+ if (status & IRQ_REQ) /* clear interrupt */
+ waveartist_iack(chip);
+
+ if (irqstatus & IRQ_PCM) { /* PCM buffer done */
+ if ((status & DMA1) && chip->playback_substream)
+ snd_pcm_period_elapsed(chip->playback_substream);
+ if ((status & DMA0) && chip->capture_substream)
+ snd_pcm_period_elapsed(chip->capture_substream);
+ if (!(status & (DMA0 | DMA1)))
+ dev_warn(chip->card->dev, "Unknown PCM interrupt\n");
+ }
+
+ if (irqstatus & IRQ_SB) /* we do not use SB mode */
+ dev_warn(chip->card->dev, "Unexpected SB interrupt\n");
+
+ if ((irqstatus & IRQ_MPU) && chip->rmidi)
+ snd_mpu401_uart_interrupt(irq, chip->rmidi->private_data);
+
+ return IRQ_HANDLED;
+}
+
+#ifdef CONFIG_PM
+static int snd_waveartist_suspend(struct snd_card *card, pm_message_t state)
+{
+ struct snd_waveartist *chip = card->private_data;
+ int i;
+
+ if (!card)
+ return 0;
+
+ snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
+ snd_pcm_suspend_all(chip->pcm);
+
+ /* save mixer registers */
+ for (i = 0; i < ARRAY_SIZE(chip->image); i++)
+ chip->image[i] = waveartist_cmd1_r(chip,
+ WACMD_GET_LEVEL | i << 8);
+
+ return 0;
+}
+
+static int snd_waveartist_resume(struct snd_card *card)
+{
+ struct snd_waveartist *chip;
+ int i;
+
+ if (!card)
+ return 0;
+
+ chip = card->private_data;
+
+ /* restore mixer registers */
+ for (i = 0; i < 10; i += 2)
+ waveartist_cmd3(chip, WACMD_SET_MIXER,
+ chip->image[i], chip->image[i + 1]);
+ for (i = 10; i < ARRAY_SIZE(chip->image); i += 2)
+ waveartist_cmd3(chip, WACMD_SET_LEVEL |
+ ((i - 10) / 2) << 8,
+ chip->image[i], chip->image[i + 1]);
+
+
+ snd_power_change_state(card, SNDRV_CTL_POWER_D0);
+
+ return 0;
+}
+#endif /* CONFIG_PM */
+
+#ifdef CONFIG_PNP
+static void snd_waveartist_set_irq(struct pnp_dev *pdev, int irq)
+{
+ if (!pdev->active)
+ return;
+ isapnp_cfg_begin(isapnp_card_number(pdev), isapnp_csn_number(pdev));
+ isapnp_write_byte(0x70, irq); /* ISAPNP_CFG_IRQ */
+ isapnp_cfg_end();
+}
+
+static int snd_waveartist_pnp(int dev, struct snd_waveartist *chip,
+ struct pnp_card_link *card,
+ const struct pnp_card_device_id *id)
+{
+ chip->wa = pnp_request_card_device(card, id->devs[0].id, NULL);
+ if (!chip->wa)
+ return -EBUSY;
+
+ chip->sb = pnp_request_card_device(card, id->devs[1].id, NULL);
+ if (!chip->sb)
+ return -EBUSY;
+
+ chip->mpu = pnp_request_card_device(card, id->devs[2].id, NULL);
+ if (!chip->mpu)
+ return -EBUSY;
+
+ if (pnp_activate_dev(chip->wa) < 0) {
+ dev_err(chip->card->dev, "WA PnP configure failure\n");
+ return -EBUSY;
+ }
+ if (pnp_activate_dev(chip->sb) < 0) {
+ dev_err(chip->card->dev, "SB PnP configure failure\n");
+ return -EBUSY;
+ }
+ port[dev] = pnp_port_start(chip->wa, 0);
+ dma2[dev] = pnp_dma(chip->wa, 0);
+ fm_port[dev] = pnp_port_start(chip->sb, 1);
+ dma1[dev] = pnp_dma(chip->sb, 0);
+ irq[dev] = pnp_irq(chip->sb, 0);
+
+ /*
+ * The card uses only one IRQ (listed in the resources of SB device)
+ * which needs to be shared by WaveArtist and MPU-401 devices. They
+ * don't have an IRQ resource so it must be forced.
+ */
+ snd_waveartist_set_irq(chip->wa, irq[dev]);
+
+ /* allocate MPU-401 resources */
+ if (pnp_activate_dev(chip->mpu) < 0)
+ dev_err(chip->card->dev, "MPU-401 PnP configure failure: will be disabled\n");
+ else {
+ midi_port[dev] = pnp_port_start(chip->mpu, 0);
+ snd_waveartist_set_irq(chip->mpu, irq[dev]);
+ }
+
+ dev_dbg(chip->card->dev, "PnP WaveArtist: port=0x%lx, fm port=0x%lx, midi port=0x%lx\n",
+ port[dev], fm_port[dev], midi_port[dev]);
+ dev_dbg(chip->card->dev, "PnP WaveArtist: dma1=%i, dma2=%i, irq=%i\n",
+ dma1[dev], dma2[dev], irq[dev]);
+
+ return 0;
+}
+#endif /* CONFIG_PNP */
+
+static int snd_waveartist_playback_hw_params(
+ struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *hw_params)
+{
+ int err = snd_pcm_lib_malloc_pages(substream,
+ params_buffer_bytes(hw_params));
+
+ if (err < 0)
+ return err;
+
+ return 0;
+}
+
+static int snd_waveartist_pcm_hw_free(struct snd_pcm_substream *substream)
+{
+ return snd_pcm_lib_free_pages(substream);
+}
+
+static enum wa_format waveartist_format(snd_pcm_format_t format)
+{
+ if (snd_pcm_format_width(format) == 16)
+ return WA_FMT_S16;
+ if (snd_pcm_format_unsigned(format))
+ return WA_FMT_U8;
+ else
+ return WA_FMT_S8;
+}
+
+static u16 waveartist_rate(struct snd_pcm_runtime *runtime)
+{
+ return (runtime->rate << 16) / 44100;
+}
+
+static int snd_waveartist_playback_prepare(struct snd_pcm_substream *substream)
+{
+ struct snd_waveartist *chip = snd_pcm_substream_chip(substream);
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ unsigned int size = snd_pcm_lib_buffer_bytes(substream);
+ unsigned int count = snd_pcm_lib_period_bytes(substream);
+
+ /* Set rate */
+ if (waveartist_cmd2(chip, WACMD_OUTPUTSPEED, waveartist_rate(runtime)))
+ dev_warn(chip->card->dev, "error setting playback rate %dHz\n",
+ runtime->rate);
+ /* Set channel count */
+ if (waveartist_cmd2(chip, WACMD_OUTPUTCHANNELS, runtime->channels))
+ dev_warn(chip->card->dev, "error setting playback %d channels\n",
+ runtime->channels);
+ /* Set DMA channel */
+ if (waveartist_cmd2(chip, WACMD_OUTPUTDMA,
+ chip->dma_playback > 3 ? WA_DMA_16BIT : WA_DMA_8BIT))
+ dev_warn(chip->card->dev, "error setting playback data path\n");
+ /* Set format */
+ if (waveartist_cmd2(chip, WACMD_OUTPUTFORMAT,
+ waveartist_format(runtime->format)))
+ dev_warn(chip->card->dev, "error setting playback format %d\n",
+ runtime->format);
+ /* Set sample count */
+ if (waveartist_cmd2(chip, WACMD_OUTPUTSIZE, count - 1))
+ dev_warn(chip->card->dev, "error setting playback count %d\n",
+ count);
+ /* Configure DMA controller */
+ snd_dma_program(chip->dma_playback, runtime->dma_addr, size,
+ DMA_MODE_WRITE | DMA_AUTOINIT);
+
+ return 0;
+}
+
+static int snd_waveartist_playback_trigger(struct snd_pcm_substream *substream,
+ int cmd)
+{
+ struct snd_waveartist *chip = snd_pcm_substream_chip(substream);
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ waveartist_cmd1(chip, WACMD_OUTPUTSTART);
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ waveartist_cmd1(chip, WACMD_OUTPUTSTOP);
+ break;
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ waveartist_cmd1(chip, WACMD_OUTPUTPAUSE);
+ break;
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ waveartist_cmd1(chip, WACMD_OUTPUTRESUME);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int snd_waveartist_capture_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *hw_params)
+{
+ int err = snd_pcm_lib_malloc_pages(substream,
+ params_buffer_bytes(hw_params));
+
+ if (err < 0)
+ return err;
+
+ return 0;
+}
+
+static int snd_waveartist_capture_prepare(struct snd_pcm_substream *substream)
+{
+ struct snd_waveartist *chip = snd_pcm_substream_chip(substream);
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ unsigned int size = snd_pcm_lib_buffer_bytes(substream);
+ unsigned int count = snd_pcm_lib_period_bytes(substream);
+
+ /* Set rate */
+ if (waveartist_cmd2(chip, WACMD_INPUTSPEED, waveartist_rate(runtime)))
+ dev_warn(chip->card->dev, "error setting capture rate %dHz\n",
+ runtime->rate);
+ /* Set channel count */
+ if (waveartist_cmd2(chip, WACMD_INPUTCHANNELS, runtime->channels))
+ dev_warn(chip->card->dev, "error setting capture %d channels\n",
+ runtime->channels);
+ /* Set DMA channel */
+ if (waveartist_cmd2(chip, WACMD_INPUTDMA,
+ chip->dma_capture > 3 ? WA_DMA_16BIT : WA_DMA_8BIT))
+ dev_warn(chip->card->dev, "error setting capture data path\n");
+ /* Set format */
+ if (waveartist_cmd2(chip, WACMD_INPUTFORMAT,
+ waveartist_format(runtime->format)))
+ dev_warn(chip->card->dev, "error setting capture format %d\n",
+ runtime->format);
+ /* Set sample count */
+ if (waveartist_cmd2(chip, WACMD_INPUTSIZE, count - 1))
+ dev_warn(chip->card->dev, "error setting capture count %d\n",
+ count);
+ /* Configure DMA controller */
+ snd_dma_program(chip->dma_capture, runtime->dma_addr, size,
+ DMA_MODE_READ | DMA_AUTOINIT);
+
+ return 0;
+}
+
+static int snd_waveartist_capture_trigger(struct snd_pcm_substream *substream,
+ int cmd)
+{
+ struct snd_waveartist *chip = snd_pcm_substream_chip(substream);
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ waveartist_cmd1(chip, WACMD_INPUTSTART);
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ waveartist_cmd1(chip, WACMD_INPUTSTOP);
+ break;
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ waveartist_cmd1(chip, WACMD_INPUTPAUSE);
+ break;
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ waveartist_cmd1(chip, WACMD_INPUTRESUME);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static snd_pcm_uframes_t snd_waveartist_playback_pointer(
+ struct snd_pcm_substream *substream)
+{
+ struct snd_waveartist *chip = snd_pcm_substream_chip(substream);
+ size_t size = snd_pcm_lib_buffer_bytes(substream);
+ size_t ptr = snd_dma_pointer(chip->dma_playback, size);
+
+ return bytes_to_frames(substream->runtime, ptr);
+}
+
+static snd_pcm_uframes_t snd_waveartist_capture_pointer(
+ struct snd_pcm_substream *substream)
+{
+ struct snd_waveartist *chip = snd_pcm_substream_chip(substream);
+ size_t size = snd_pcm_lib_buffer_bytes(substream);
+ size_t ptr = snd_dma_pointer(chip->dma_capture, size);
+
+ return bytes_to_frames(substream->runtime, ptr);
+}
+
+static struct snd_pcm_hardware snd_waveartist_playback = {
+ .info = SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_PAUSE |
+ SNDRV_PCM_INFO_MMAP_VALID,
+ .formats = SNDRV_PCM_FMTBIT_U8 |
+ SNDRV_PCM_FMTBIT_S8 |
+ SNDRV_PCM_FMTBIT_S16_LE,
+ .rates = SNDRV_PCM_RATE_CONTINUOUS |
+ SNDRV_PCM_RATE_8000_44100,
+ .rate_min = 4000,
+ .rate_max = 44100,
+ .channels_min = 1,
+ .channels_max = 2,
+ .buffer_bytes_max = (128*1024),
+ .period_bytes_min = 64,
+ .period_bytes_max = (128*1024),
+ .periods_min = 1,
+ .periods_max = 1024,
+};
+
+static struct snd_pcm_hardware snd_waveartist_capture = {
+ .info = SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_PAUSE |
+ SNDRV_PCM_INFO_MMAP_VALID,
+ .formats = SNDRV_PCM_FMTBIT_U8 |
+ SNDRV_PCM_FMTBIT_S8 |
+ SNDRV_PCM_FMTBIT_S16_LE,
+ .rates = SNDRV_PCM_RATE_CONTINUOUS |
+ SNDRV_PCM_RATE_8000_44100,
+ .rate_min = 4000,
+ .rate_max = 44100,
+ .channels_min = 1,
+ .channels_max = 2,
+ .buffer_bytes_max = (128*1024),
+ .period_bytes_min = 64,
+ .period_bytes_max = (128*1024),
+ .periods_min = 1,
+ .periods_max = 1024,
+};
+
+static int snd_waveartist_playback_open(struct snd_pcm_substream *substream)
+{
+ struct snd_waveartist *chip = snd_pcm_substream_chip(substream);
+
+ chip->playback_substream = substream;
+ substream->runtime->hw = snd_waveartist_playback;
+
+ snd_pcm_limit_isa_dma_size(chip->dma_playback,
+ &substream->runtime->hw.buffer_bytes_max);
+ snd_pcm_limit_isa_dma_size(chip->dma_playback,
+ &substream->runtime->hw.period_bytes_max);
+
+ return 0;
+}
+
+static int snd_waveartist_capture_open(struct snd_pcm_substream *substream)
+{
+ struct snd_waveartist *chip = snd_pcm_substream_chip(substream);
+
+ chip->capture_substream = substream;
+ substream->runtime->hw = snd_waveartist_capture;
+
+ snd_pcm_limit_isa_dma_size(chip->dma_capture,
+ &substream->runtime->hw.buffer_bytes_max);
+ snd_pcm_limit_isa_dma_size(chip->dma_capture,
+ &substream->runtime->hw.period_bytes_max);
+
+ return 0;
+}
+
+static int snd_waveartist_playback_close(struct snd_pcm_substream *substream)
+{
+ struct snd_waveartist *chip = snd_pcm_substream_chip(substream);
+
+ chip->playback_substream = NULL;
+ snd_pcm_lib_free_pages(substream);
+
+ return 0;
+}
+
+static int snd_waveartist_capture_close(struct snd_pcm_substream *substream)
+{
+ struct snd_waveartist *chip = snd_pcm_substream_chip(substream);
+
+ chip->capture_substream = NULL;
+ snd_pcm_lib_free_pages(substream);
+
+ return 0;
+}
+
+static struct snd_pcm_ops snd_waveartist_playback_ops = {
+ .open = snd_waveartist_playback_open,
+ .close = snd_waveartist_playback_close,
+ .ioctl = snd_pcm_lib_ioctl,
+ .hw_params = snd_waveartist_playback_hw_params,
+ .hw_free = snd_waveartist_pcm_hw_free,
+ .prepare = snd_waveartist_playback_prepare,
+ .trigger = snd_waveartist_playback_trigger,
+ .pointer = snd_waveartist_playback_pointer,
+};
+
+static struct snd_pcm_ops snd_waveartist_capture_ops = {
+ .open = snd_waveartist_capture_open,
+ .close = snd_waveartist_capture_close,
+ .ioctl = snd_pcm_lib_ioctl,
+ .hw_params = snd_waveartist_capture_hw_params,
+ .hw_free = snd_waveartist_pcm_hw_free,
+ .prepare = snd_waveartist_capture_prepare,
+ .trigger = snd_waveartist_capture_trigger,
+ .pointer = snd_waveartist_capture_pointer,
+};
+
+static int snd_waveartist_pcm(struct snd_card *card)
+{
+ struct snd_waveartist *chip = card->private_data;
+ struct snd_pcm *pcm;
+ int err;
+
+ err = snd_pcm_new(card, "WaveArtist", 0, 1, 1, &pcm);
+ if (err < 0)
+ return err;
+
+ snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK,
+ &snd_waveartist_playback_ops);
+ snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE,
+ &snd_waveartist_capture_ops);
+
+ /* global setup */
+ pcm->private_data = chip;
+ pcm->info_flags = 0;
+ if (chip->dma_playback == chip->dma_capture)
+ pcm->info_flags |= SNDRV_PCM_INFO_HALF_DUPLEX;
+ strcpy(pcm->name, card->shortname);
+ chip->pcm = pcm;
+
+ snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
+ snd_dma_isa_data(), 64 * 1024,
+ chip->dma_playback > 3 || chip->dma_capture > 3 ?
+ 128 * 1024 : 64 * 1024);
+
+ return 0;
+}
+
+static void snd_waveartist_free(struct snd_card *card)
+{
+ struct snd_waveartist *chip = card->private_data;
+
+ release_and_free_resource(chip->res_port);
+ if (chip->irq >= 0)
+ free_irq(chip->irq, card);
+ if (chip->dma_playback >= 0) {
+ disable_dma(chip->dma_playback);
+ free_dma(chip->dma_playback);
+ }
+ if (chip->dma_capture >= 0 && chip->dma_capture != chip->dma_playback) {
+ disable_dma(chip->dma_capture);
+ free_dma(chip->dma_capture);
+ }
+}
+
+static int snd_waveartist_dev_free(struct snd_device *device)
+{
+ snd_waveartist_free(device->card);
+
+ return 0;
+}
+
+static int snd_waveartist_init(struct snd_waveartist *chip)
+{
+ if (waveartist_reset(chip))
+ return -ENODEV;
+ dev_dbg(chip->card->dev, "chip rev. 0x%x\n", waveartist_getrev(chip));
+
+ waveartist_cmd1(chip, WACMD_RST_MIXER); /* reset mixer */
+ waveartist_iack(chip); /* clear any pending interrupt */
+ waveartist_set_ctlr(chip, 0, DMA1_IE | DMA0_IE); /* enable DMA IRQs */
+ waveartist_iack(chip); /* clear any pending interrupt */
+
+ return 0;
+}
+
+static int snd_waveartist_new_device(struct snd_card *card,
+ unsigned long port,
+ unsigned long mpu_port,
+ unsigned long fm_port,
+ int irq, int dma1, int dma2)
+{
+ struct snd_waveartist *chip = card->private_data;
+ static struct snd_device_ops ops = {
+ .dev_free = snd_waveartist_dev_free,
+ };
+ int err;
+
+ spin_lock_init(&chip->reg_lock);
+ chip->card = card;
+ chip->port = port;
+ chip->irq = -1;
+ chip->dma_playback = -1;
+ chip->dma_capture = -1;
+
+ chip->res_port = request_region(port, 16, "WaveArtist");
+ if (!chip->res_port) {
+ snd_waveartist_free(card);
+ dev_err(chip->card->dev, "unable to grab ports 0x%lx-0x%lx\n",
+ port, port + 16 - 1);
+ return -EBUSY;
+ }
+
+ if (snd_waveartist_init(chip) < 0) {
+ snd_waveartist_free(card);
+ return -ENODEV;
+ }
+
+ if (request_irq(irq, snd_waveartist_interrupt, 0, "WaveArtist", card)) {
+ snd_waveartist_free(card);
+ dev_err(chip->card->dev, "unable to grab IRQ %d\n", irq);
+ return -EBUSY;
+ }
+ chip->irq = irq;
+
+ if (dma1 == dma2 || dma1 == SNDRV_AUTO_DMA || dma2 == SNDRV_AUTO_DMA) {
+ /* we have only one DMA channel */
+ if (dma1 == SNDRV_AUTO_DMA)
+ dma1 = dma2;
+ if (request_dma(dma1, "WaveArtist")) {
+ snd_waveartist_free(card);
+ dev_err(chip->card->dev, "unable to grab DMA %d\n",
+ dma1);
+ return -EBUSY;
+ }
+ chip->dma_playback = chip->dma_capture = dma1;
+ } else {
+ /*
+ * 2 channels: use 16-bit for playback and 8-bit for capture as
+ * full-duplex works better this way. However, the chip seems to
+ * have some band-width limit so full-duplex at 44kHz/16-bit/2ch
+ * is not possible - some frames are dropped during capture,
+ * resulting in too-fast recording. If capture is done using
+ * lower rate or 8-bit or mono, everything is fine.
+ */
+ if (dma2 > 3)
+ swap(dma1, dma2);
+
+ if (request_dma(dma1, "WaveArtist playback")) {
+ snd_waveartist_free(card);
+ dev_err(chip->card->dev, "unable to grab playback DMA %d\n",
+ dma1);
+ return -EBUSY;
+ }
+ chip->dma_playback = dma1;
+
+ if (dma2 != dma1 && request_dma(dma2, "WaveArtist capture")) {
+ snd_waveartist_free(card);
+ dev_err(chip->card->dev, "unable to grab capture DMA %d\n",
+ dma2);
+ return -EBUSY;
+ }
+ chip->dma_capture = dma2;
+ }
+
+ err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops);
+ if (err < 0) {
+ snd_waveartist_free(card);
+ return err;
+ }
+
+ return 0;
+}
+
+static int snd_waveartist_card_new(struct device *pdev, int dev,
+ struct snd_card **cardp)
+{
+ return snd_card_new(pdev, index[dev], id[dev], THIS_MODULE,
+ sizeof(struct snd_waveartist), cardp);
+}
+
+static int snd_waveartist_info_single(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ int mask = (kcontrol->private_value >> 16) & 0xff;
+
+ uinfo->type = mask == 1 ? SNDRV_CTL_ELEM_TYPE_BOOLEAN :
+ SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = 1;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = mask;
+
+ return 0;
+}
+
+static int snd_waveartist_get_single(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_waveartist *chip = snd_kcontrol_chip(kcontrol);
+ int reg = kcontrol->private_value & 0xff;
+ int shift = (kcontrol->private_value >> 8) & 0xff;
+ int mask = (kcontrol->private_value >> 16) & 0xff;
+ u16 val;
+
+ val = waveartist_cmd1_r(chip, WACMD_GET_LEVEL | reg << 8);
+ ucontrol->value.integer.value[0] = (val >> shift) & mask;
+
+ return 0;
+}
+
+static int snd_waveartist_put_single(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_waveartist *chip = snd_kcontrol_chip(kcontrol);
+ int reg = kcontrol->private_value & 0xff;
+ int shift = (kcontrol->private_value >> 8) & 0xff;
+ int mask = (kcontrol->private_value >> 16) & 0xff;
+ u16 val, old_val, new_val;
+
+ val = (ucontrol->value.integer.value[0] & mask);
+ mask <<= shift;
+ val <<= shift;
+
+ old_val = waveartist_cmd1_r(chip, WACMD_GET_LEVEL | reg << 8);
+ new_val = (old_val & ~mask) | (val & mask);
+
+ if (new_val == old_val)
+ return 0;
+
+ /* new_val already contains register number (bits 12..15), bit 11 set */
+ waveartist_cmd3(chip, WACMD_SET_MIXER, new_val, new_val);
+
+ return 1;
+}
+
+static int snd_waveartist_info_double(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ int mask = (kcontrol->private_value >> 24) & 0xff;
+
+ uinfo->type = mask == 1 ? SNDRV_CTL_ELEM_TYPE_BOOLEAN :
+ SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = 2;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = mask ? mask : 0x7fff;
+
+ return 0;
+}
+
+static int snd_waveartist_get_double(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_waveartist *chip = snd_kcontrol_chip(kcontrol);
+ int left_reg = kcontrol->private_value & 0xff;
+ int right_reg = (kcontrol->private_value >> 8) & 0xff;
+ int shift_left = (kcontrol->private_value >> 16) & 0x0f;
+ int shift_right = (kcontrol->private_value >> 20) & 0x0f;
+ int mask = (kcontrol->private_value >> 24) & 0xff;
+ u16 left, right;
+
+ if (mask == 0)
+ mask = 0x7fff;
+
+ left = waveartist_cmd1_r(chip, WACMD_GET_LEVEL | left_reg << 8);
+ if (left_reg != right_reg)
+ right = waveartist_cmd1_r(chip, WACMD_GET_LEVEL |
+ right_reg << 8);
+ else
+ right = left;
+
+ ucontrol->value.integer.value[0] = (left >> shift_left) & mask;
+ ucontrol->value.integer.value[1] = (right >> shift_right) & mask;
+
+ return 0;
+}
+
+static int snd_waveartist_put_double(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_waveartist *chip = snd_kcontrol_chip(kcontrol);
+ int left_reg = kcontrol->private_value & 0xff;
+ int right_reg = (kcontrol->private_value >> 8) & 0xff;
+ int shift_left = (kcontrol->private_value >> 16) & 0x0f;
+ int shift_right = (kcontrol->private_value >> 20) & 0x0f;
+ int mask = (kcontrol->private_value >> 24) & 0xff;
+ u16 val1, val2, mask1, mask2;
+ u16 old_left, old_right, new_left, new_right;
+
+ if (mask == 0)
+ mask = 0x7fff;
+
+ val1 = ucontrol->value.integer.value[0] & mask;
+ val2 = ucontrol->value.integer.value[1] & mask;
+ val1 <<= shift_left;
+ val2 <<= shift_right;
+ mask1 = mask << shift_left;
+ mask2 = mask << shift_right;
+
+ old_left = waveartist_cmd1_r(chip, WACMD_GET_LEVEL | left_reg << 8);
+ if (left_reg != right_reg)
+ old_right = waveartist_cmd1_r(chip, WACMD_GET_LEVEL |
+ right_reg << 8);
+ else
+ old_right = old_left;
+
+ new_left = (old_left & ~mask1) | (val1 & mask1);
+ new_right = (old_right & ~mask2) | (val2 & mask2);
+
+ if (new_left == old_left && new_right == old_right)
+ return 0;
+
+ if (left_reg < 10)
+ /* new_* already contain reg. num. (bits 12..15), bit 11 set */
+ waveartist_cmd3(chip, WACMD_SET_MIXER, new_left, new_right);
+ else
+ waveartist_cmd3(chip, WACMD_SET_LEVEL |
+ ((left_reg - 10) / 2) << 8,
+ new_left, new_right);
+
+ return 1;
+}
+
+static int snd_waveartist_info_mux(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ static const char * const mux_texts[] = {
+ "None", "Mix", "Line", "Phone", "CD", "Mic"
+ };
+
+ return snd_ctl_enum_info(uinfo, 2, ARRAY_SIZE(mux_texts), mux_texts);
+}
+
+static int snd_waveartist_get_mux(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_waveartist *chip = snd_kcontrol_chip(kcontrol);
+ int mux = waveartist_cmd1_r(chip, WACMD_GET_LEVEL | 0x08 << 8);
+
+ ucontrol->value.enumerated.item[0] = mux & 0x07;
+ ucontrol->value.enumerated.item[1] = (mux >> 3) & 0x07;
+
+ return 0;
+}
+
+static int snd_waveartist_put_mux(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_waveartist *chip = snd_kcontrol_chip(kcontrol);
+ u16 old_val, new_val;
+ u16 val1 = ucontrol->value.enumerated.item[0];
+ u16 val2 = ucontrol->value.enumerated.item[1];
+
+ old_val = waveartist_cmd1_r(chip, WACMD_GET_LEVEL | 0x08 << 8);
+ new_val = (old_val & ~0x3f) | (val1 & 0x07) | ((val2 & 0x07) << 3);
+ if (new_val == old_val)
+ return 0;
+
+ /* new_val already contains register number (bits 12..15), bit 11 set */
+ waveartist_cmd3(chip, WACMD_SET_MIXER, new_val, new_val);
+
+ return 1;
+}
+
+#define WAVEARTIST_SINGLE(xname, reg, shift, mask) \
+{ \
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
+ .info = snd_waveartist_info_single, \
+ .get = snd_waveartist_get_single, .put = snd_waveartist_put_single, \
+ .private_value = reg | (shift << 8) | (mask << 16) \
+}
+
+#define WAVEARTIST_DOUBLE(xname, left_reg, right_reg, shift_left, shift_right, \
+ mask) \
+{ \
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
+ .info = snd_waveartist_info_double, \
+ .get = snd_waveartist_get_double, .put = snd_waveartist_put_double, \
+ .private_value = left_reg | (right_reg << 8) | (shift_left << 16) | \
+ (shift_right << 20) | (mask << 24) \
+}
+
+static struct snd_kcontrol_new snd_waveartist_controls[] = {
+WAVEARTIST_DOUBLE("Master Playback Volume", 2, 6, 1, 1, 0x07),
+WAVEARTIST_DOUBLE("Master Playback Switch", 0, 4, 0, 0, 1),
+WAVEARTIST_DOUBLE("PCM Playback Volume", 10, 11, 0, 0, 0x00),/* 0x00 = 0x7fff */
+WAVEARTIST_DOUBLE("FM Playback Volume", 12, 13, 0, 0, 0x00),/* 0x00 = 0x7fff */
+/* WAVEARTIST_DOUBLE("Wavetable? Playback Volume", 14, 15, 0, 0, 0x00), */
+/* WAVEARTIST_DOUBLE("? Playback Volume", 16, 17, 0, 0, 0x00), */
+/* WAVEARTIST_DOUBLE("? Playback Volume", 18, 19, 0, 0, 0x00), */
+WAVEARTIST_DOUBLE("Digital Playback Switch", 3, 7, 10, 10, 1), /* PCM + FM */
+WAVEARTIST_DOUBLE("CD Playback Volume", 0, 4, 1, 1, 0x1f),
+WAVEARTIST_DOUBLE("CD Playback Switch", 3, 7, 6, 6, 1),
+WAVEARTIST_DOUBLE("Line Playback Volume", 0, 4, 6, 6, 0x1f),
+WAVEARTIST_DOUBLE("Line Playback Switch", 3, 7, 4, 4, 1),
+WAVEARTIST_DOUBLE("Phone Playback Volume", 1, 5, 6, 6, 0x1f),
+WAVEARTIST_DOUBLE("Phone Playback Switch", 3, 7, 5, 5, 1),
+WAVEARTIST_SINGLE("Mono Playback Volume", 8, 5, 0x1f),
+WAVEARTIST_DOUBLE("Mono Playback Switch", 3, 7, 9, 9, 1),
+WAVEARTIST_DOUBLE("Mic Playback Volume", 2, 6, 6, 6, 0x1f),
+WAVEARTIST_DOUBLE("Mic 2 Playback Volume", 1, 5, 1, 1, 0x1f),
+WAVEARTIST_DOUBLE("Mic Playback Switch", 3, 7, 7, 7, 1),
+WAVEARTIST_DOUBLE("Mic 2 Playback Switch", 3, 7, 8, 8, 1),
+WAVEARTIST_DOUBLE("Mic Gain", 2, 6, 4, 4, 0x03),
+WAVEARTIST_DOUBLE("Capture Volume", 3, 7, 0, 0, 0x0f),
+WAVEARTIST_SINGLE("Mono Output Playback Switch", 1, 0, 1),
+{
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Capture Source",
+ .info = snd_waveartist_info_mux,
+ .get = snd_waveartist_get_mux,
+ .put = snd_waveartist_put_mux,
+}
+};
+
+static int snd_waveartist_mixer(struct snd_card *card)
+{
+ struct snd_waveartist *chip = card->private_data;
+ int err;
+ unsigned int idx;
+
+ strcpy(card->mixername, chip->pcm->name);
+
+ for (idx = 0; idx < ARRAY_SIZE(snd_waveartist_controls); idx++) {
+ struct snd_kcontrol *kctl;
+
+ kctl = snd_ctl_new1(&snd_waveartist_controls[idx], chip);
+ err = snd_ctl_add(card, kctl);
+ if (err < 0)
+ return err;
+ }
+
+ return 0;
+}
+
+static int snd_waveartist_probe(struct snd_card *card, int dev)
+{
+ struct snd_waveartist *chip = card->private_data;
+ struct snd_opl3 *opl3;
+ int err;
+
+ err = snd_waveartist_new_device(card,
+ port[dev], midi_port[dev], fm_port[dev],
+ irq[dev], dma1[dev], dma2[dev]);
+ if (err < 0)
+ return err;
+
+ strcpy(card->driver, "WaveArtist");
+ strcpy(card->shortname, "Rockwell WaveArtist RWA010");
+ sprintf(card->longname, "%s at 0x%lx, irq %d, dma1 %d, dma2 %d",
+ card->shortname, chip->port, irq[dev], dma1[dev], dma2[dev]);
+
+ err = snd_waveartist_pcm(card);
+ if (err < 0)
+ return err;
+ err = snd_waveartist_mixer(card);
+ if (err < 0)
+ return err;
+
+ if (fm_port[dev] >= 0x388 && fm_port[dev] < 0x3f0) {
+ err = snd_opl3_create(card, fm_port[dev], fm_port[dev] + 2,
+ OPL3_HW_OPL3, 0, &opl3);
+ if (err < 0)
+ return err;
+ err = snd_opl3_timer_new(opl3, 1, 2);
+ if (err < 0)
+ return err;
+ err = snd_opl3_hwdep_new(opl3, 0, 1, &chip->synth);
+ if (err < 0)
+ return err;
+ }
+ if (midi_port[dev] >= 0x300 && midi_port[dev] < 0x3f0) {
+ err = snd_mpu401_uart_new(card, 0, MPU401_HW_WAVEARTIST,
+ midi_port[dev], MPU401_INFO_IRQ_HOOK,
+ -1, &chip->rmidi);
+ if (err < 0)
+ return err;
+ }
+
+ return snd_card_register(card);
+}
+
+static int snd_waveartist_isa_match(struct device *pdev,
+ unsigned int dev)
+{
+ if (!enable[dev])
+ return 0;
+#ifdef CONFIG_PNP
+ if (isapnp[dev])
+ return 0;
+#endif
+ if (port[dev] == SNDRV_AUTO_PORT) {
+ dev_err(pdev, "specify port\n");
+ return 0;
+ }
+ if (irq[dev] == SNDRV_AUTO_IRQ) {
+ dev_err(pdev, "specify irq\n");
+ return 0;
+ }
+ if (dma1[dev] == SNDRV_AUTO_DMA) {
+ dev_err(pdev, "specify dma1\n");
+ return 0;
+ }
+
+ return 1;
+}
+
+static int snd_waveartist_isa_probe(struct device *pdev,
+ unsigned int dev)
+{
+ struct snd_card *card;
+ int err;
+
+ err = snd_waveartist_card_new(pdev, dev, &card);
+ if (err < 0)
+ return err;
+ err = snd_waveartist_probe(card, dev);
+ if (err < 0) {
+ snd_card_free(card);
+ return err;
+ }
+ dev_set_drvdata(pdev, card);
+
+ return 0;
+}
+
+static int snd_waveartist_isa_remove(struct device *devptr,
+ unsigned int dev)
+{
+ snd_card_free(dev_get_drvdata(devptr));
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int snd_waveartist_isa_suspend(struct device *dev, unsigned int n,
+ pm_message_t state)
+{
+ return snd_waveartist_suspend(dev_get_drvdata(dev), state);
+}
+
+static int snd_waveartist_isa_resume(struct device *dev, unsigned int n)
+{
+ return snd_waveartist_resume(dev_get_drvdata(dev));
+}
+#endif
+
+static struct isa_driver waveartist_isa_driver = {
+ .match = snd_waveartist_isa_match,
+ .probe = snd_waveartist_isa_probe,
+ .remove = snd_waveartist_isa_remove,
+#ifdef CONFIG_PM
+ .suspend = snd_waveartist_isa_suspend,
+ .resume = snd_waveartist_isa_resume,
+#endif
+ .driver = { .name = "waveartist" },
+};
+
+#ifdef CONFIG_PNP
+static int snd_waveartist_pnp_detect(struct pnp_card_link *pcard,
+ const struct pnp_card_device_id *pid)
+{
+ static int dev;
+ int err;
+ struct snd_card *card;
+
+ for (; dev < SNDRV_CARDS; dev++) {
+ if (enable[dev] && isapnp[dev])
+ break;
+ }
+ if (dev >= SNDRV_CARDS)
+ return -ENODEV;
+
+ err = snd_waveartist_card_new(&pcard->card->dev, dev, &card);
+ if (err < 0)
+ return err;
+ err = snd_waveartist_pnp(dev, card->private_data, pcard, pid);
+ if (err < 0) {
+ dev_err(&pcard->card->dev, "PnP detection failed\n");
+ snd_card_free(card);
+ return err;
+ }
+ err = snd_waveartist_probe(card, dev);
+ if (err < 0) {
+ snd_card_free(card);
+ return err;
+ }
+ pnp_set_card_drvdata(pcard, card);
+ dev++;
+
+ return 0;
+}
+
+static void snd_waveartist_pnp_remove(struct pnp_card_link *pcard)
+{
+ struct snd_card *card = pnp_get_card_drvdata(pcard);
+ struct snd_waveartist *chip = card->private_data;
+
+ /* disable forced IRQs */
+ snd_waveartist_set_irq(chip->wa, 0);
+ snd_waveartist_set_irq(chip->mpu, 0);
+
+ snd_card_free(pnp_get_card_drvdata(pcard));
+ pnp_set_card_drvdata(pcard, NULL);
+}
+
+#ifdef CONFIG_PM
+static int snd_waveartist_pnp_suspend(struct pnp_card_link *pcard,
+ pm_message_t state)
+{
+ struct snd_card *card = pnp_get_card_drvdata(pcard);
+ struct snd_waveartist *chip = card->private_data;
+
+ snd_waveartist_suspend(card, state);
+
+ /* disable forced IRQs to prevent opps */
+ snd_waveartist_set_irq(chip->wa, 0);
+ snd_waveartist_set_irq(chip->mpu, 0);
+
+ return 0;
+}
+static int snd_waveartist_pnp_resume(struct pnp_card_link *pcard)
+{
+ struct snd_card *card = pnp_get_card_drvdata(pcard);
+ struct snd_waveartist *chip = card->private_data;
+
+ /* re-enable forced IRQs */
+ snd_waveartist_set_irq(chip->wa, chip->irq);
+ snd_waveartist_set_irq(chip->mpu, chip->irq);
+
+ return snd_waveartist_resume(pnp_get_card_drvdata(pcard));
+}
+#endif
+
+static struct pnp_card_driver waveartist_pnpc_driver = {
+ .flags = PNP_DRIVER_RES_DISABLE,
+ .name = "waveartist",
+ .id_table = snd_waveartist_pnpids,
+ .probe = snd_waveartist_pnp_detect,
+ .remove = snd_waveartist_pnp_remove,
+#ifdef CONFIG_PM
+ .suspend = snd_waveartist_pnp_suspend,
+ .resume = snd_waveartist_pnp_resume,
+#endif
+};
+#endif /* CONFIG_PNP */
+
+static int __init alsa_card_waveartist_init(void)
+{
+ int err;
+
+ err = isa_register_driver(&waveartist_isa_driver, SNDRV_CARDS);
+#ifdef CONFIG_PNP
+ if (!err)
+ isa_registered = 1;
+
+ err = pnp_register_card_driver(&waveartist_pnpc_driver);
+ if (!err)
+ pnp_registered = 1;
+
+ if (isa_registered)
+ err = 0;
+#endif
+ return err;
+}
+
+static void __exit alsa_card_waveartist_exit(void)
+{
+#ifdef CONFIG_PNP
+ if (pnp_registered)
+ pnp_unregister_card_driver(&waveartist_pnpc_driver);
+
+ if (isa_registered)
+#endif
+ isa_unregister_driver(&waveartist_isa_driver);
+}
+
+module_init(alsa_card_waveartist_init)
+module_exit(alsa_card_waveartist_exit)
diff --git a/sound/isa/waveartist.h b/sound/isa/waveartist.h
new file mode 100644
index 0000000..72254cf
--- /dev/null
+++ b/sound/isa/waveartist.h
@@ -0,0 +1,74 @@
+/*
+ * Rockwell WaveArtist RWA010 chip register definitions
+ *
+ * Based on OSS WaveArtist driver by Hannu Savolainen
+ */
+
+/* registers */
+#define CMDR 0 /* command (16-bit) */
+#define DATR 2 /* data (for PIO?) (16-bit) */
+#define CTLR 4 /* control */
+#define STATR 5 /* status */
+#define EXPCR1 6 /* expansion control 1 */
+#define EXPCR2 7 /* expansion control 2 */
+#define EXPDAT1 8 /* expansion data 1 (16-bit) */
+#define EXPDAT2 10 /* expansion data 2 (16-bit) */
+#define IRQSTAT 12 /* IRQ status */
+
+/* STATR register bit definitions */
+#define CMD_WE BIT(7) /* CMDR write empty (ready for write) */
+#define CMD_RF BIT(6) /* CMDR read full (ready for read) */
+#define DAT_WE BIT(5) /* DATR write empty (ready for write) */
+#define DAT_RF BIT(4) /* DATR read full (ready for read) */
+#define IRQ_REQ BIT(3) /* IRQ was requested */
+#define DMA1 BIT(2) /* DMA1 IRQ was requested */
+#define DMA0 BIT(1) /* DMA0 IRQ was requested */
+
+/* CTLR register bit definitions */
+#define CMD_WEIE BIT(7) /* CMDR write empty interrupt enable */
+#define CMD_RFIE BIT(6) /* CMDR read full interrupt enable */
+#define DAT_WEIE BIT(5) /* DATR write empty interrupt enable */
+#define DAT_RFIE BIT(4) /* DATR read full interrupt enable */
+#define RESET BIT(3) /* chip reset */
+#define DMA1_IE BIT(2) /* DMA1 interrupt enable */
+#define DMA0_IE BIT(1) /* DMA0 interrupt enable */
+#define IRQ_ACK BIT(0) /* IRQ acknowlege */
+
+/* IRQSTAT register bit definitions */
+#define IRQ_MPU BIT(2) /* MPU-401 */
+#define IRQ_SB BIT(1) /* SB emulation */
+#define IRQ_PCM BIT(0) /* native PCM */
+
+/* commands */
+#define WACMD_GETREV 0x00
+
+#define WACMD_INPUTFORMAT 0x10 /* 0=S8, 1=S16, 2=U8 */
+#define WACMD_INPUTCHANNELS 0x11 /* 1=mono, 2=Stereo */
+#define WACMD_INPUTSPEED 0x12 /* sampling rate */
+#define WACMD_INPUTDMA 0x13 /* 0=8bit, 1=16bit, 2=PIO */
+#define WACMD_INPUTSIZE 0x14 /* samples to interrupt */
+#define WACMD_INPUTSTART 0x15 /* start ADC */
+#define WACMD_INPUTPAUSE 0x16 /* pause ADC */
+#define WACMD_INPUTSTOP 0x17 /* stop ADC */
+#define WACMD_INPUTRESUME 0x18 /* resume ADC */
+#define WACMD_INPUTPIO 0x19 /* PIO ADC */
+
+#define WACMD_OUTPUTFORMAT 0x20 /* 0=S8, 1=S16, 2=U8 */
+#define WACMD_OUTPUTCHANNELS 0x21 /* 1=mono, 2=stereo */
+#define WACMD_OUTPUTSPEED 0x22 /* sampling rate */
+#define WACMD_OUTPUTDMA 0x23 /* 0=8bit, 1=16bit, 2=PIO */
+#define WACMD_OUTPUTSIZE 0x24 /* samples to interrupt */
+#define WACMD_OUTPUTSTART 0x25 /* start DAC */
+#define WACMD_OUTPUTPAUSE 0x26 /* pause DAC */
+#define WACMD_OUTPUTSTOP 0x27 /* stop DAC */
+#define WACMD_OUTPUTRESUME 0x28 /* resume DAC */
+#define WACMD_OUTPUTPIO 0x29 /* PIO DAC */
+
+#define WACMD_GET_LEVEL 0x30 /* read mixer reg */
+#define WACMD_SET_LEVEL 0x31 /* set mixer regs (10..19) */
+#define WACMD_SET_MIXER 0x32 /* set mixer regs (0..9) */
+#define WACMD_RST_MIXER 0x33 /* mixer reset */
+#define WACMD_SET_MONO 0x34 /* set mono mode (|0x000=L, |0x100=R) */
+
+enum wa_format { WA_FMT_S8 = 0, WA_FMT_S16, WA_FMT_U8 };
+enum wa_dma { WA_DMA_8BIT = 0, WA_DMA_16BIT, WA_DMA_PIO };
--
Ondrej Zary
1
1
Hi
A little over a year age I got a ECS KBN-1 which has on-board
HD-audio chip: card0, card1. Determined I needed to make card1
the default and someone was kind enough to send me a simple one
line "fix." So simplein fact, I didn't write it down.
Now I'm installing new software on a new partition and I need
that fix again, please. This time I promise to write it down
in my ECS user manual.
Thanks,
Russ
1
0
[alsa-devel] [PATCH][ASoC]Add DT bindings for generic ASoC AC97 CODEC driver
by Maciej S. Szmigiero 07 Mar '15
by Maciej S. Szmigiero 07 Mar '15
07 Mar '15
Add and document DT bindings for generic ASoC AC97 CODEC driver,
make it selectable in config.
Signed-off-by: Maciej Szmigiero <mail(a)maciej.szmigiero.name>
diff --git a/Documentation/devicetree/bindings/sound/ac97-generic-codec.txt b/Documentation/devicetree/bindings/sound/ac97-generic-codec.txt
new file mode 100644
index 0000000..ce98698
--- /dev/null
+++ b/Documentation/devicetree/bindings/sound/ac97-generic-codec.txt
@@ -0,0 +1,24 @@
+Generic ASoC AC97 CODEC driver
+
+Allows using codecs supported by generic ALSA AC97 code as codecs in ASoC.
+
+Required properties:
+
+ - compatible : "linux,ac97-codec"
+
+Optional properties:
+
+ - playback-rates : A list of supported playback rates.
+
+ - capture-rates : A list of supported capture rates.
+
+In case one or both of above properties are missing the relevant rate(s) are assumed
+to be 8000, 11025, 22050, 44100, 48000.
+
+Example:
+
+ac97codec: ac97-hifi {
+ compatible = "linux,ac97-codec";
+ playback-rates = <48000 44100 32000 24000 22050 16000 12000 11025 8000>;
+ capture-rates = <48000>;
+};
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
index 0bddd92..92d6d39 100644
--- a/sound/soc/codecs/Kconfig
+++ b/sound/soc/codecs/Kconfig
@@ -16,7 +16,7 @@ config SND_SOC_ALL_CODECS
select SND_SOC_88PM860X if MFD_88PM860X
select SND_SOC_L3
select SND_SOC_AB8500_CODEC if ABX500_CORE
- select SND_SOC_AC97_CODEC if SND_SOC_AC97_BUS
+ select SND_SOC_AC97_CODEC
select SND_SOC_AD1836 if SPI_MASTER
select SND_SOC_AD193X_SPI if SPI_MASTER
select SND_SOC_AD193X_I2C if I2C
@@ -210,8 +210,9 @@ config SND_SOC_AB8500_CODEC
tristate
config SND_SOC_AC97_CODEC
- tristate
+ tristate "Build generic ASoC AC97 CODEC driver"
select SND_AC97_CODEC
+ select SND_SOC_AC97_BUS
config SND_SOC_AD1836
tristate
diff --git a/sound/soc/codecs/ac97.c b/sound/soc/codecs/ac97.c
index d0ac723..25125bf 100644
--- a/sound/soc/codecs/ac97.c
+++ b/sound/soc/codecs/ac97.c
@@ -23,6 +23,12 @@
#include <sound/initval.h>
#include <sound/soc.h>
+struct ac97_private {
+ struct snd_ac97 *ac97;
+ struct snd_pcm_hw_constraint_list playback_rate_constraints;
+ struct snd_pcm_hw_constraint_list capture_rate_constraints;
+};
+
static const struct snd_soc_dapm_widget ac97_widgets[] = {
SND_SOC_DAPM_INPUT("RX"),
SND_SOC_DAPM_OUTPUT("TX"),
@@ -33,22 +39,41 @@ static const struct snd_soc_dapm_route ac97_routes[] = {
{ "TX", NULL, "AC97 Playback" },
};
+static const unsigned int default_rates[] = {
+ 8000, 11025, 22050, 44100, 48000
+};
+
+static int ac97_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ struct ac97_private *ac97 = snd_soc_codec_get_drvdata(codec);
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ snd_pcm_hw_constraint_list(substream->runtime, 0,
+ SNDRV_PCM_HW_PARAM_RATE,
+ &ac97->playback_rate_constraints);
+ else
+ snd_pcm_hw_constraint_list(substream->runtime, 0,
+ SNDRV_PCM_HW_PARAM_RATE,
+ &ac97->capture_rate_constraints);
+
+ return 0;
+}
+
static int ac97_prepare(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct snd_soc_codec *codec = dai->codec;
- struct snd_ac97 *ac97 = snd_soc_codec_get_drvdata(codec);
+ struct ac97_private *ac97 = snd_soc_codec_get_drvdata(codec);
int reg = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ?
AC97_PCM_FRONT_DAC_RATE : AC97_PCM_LR_ADC_RATE;
- return snd_ac97_set_rate(ac97, reg, substream->runtime->rate);
+ return snd_ac97_set_rate(ac97->ac97, reg, substream->runtime->rate);
}
-#define STD_AC97_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\
- SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_44100 |\
- SNDRV_PCM_RATE_48000)
-
static const struct snd_soc_dai_ops ac97_dai_ops = {
+ .startup = ac97_startup,
.prepare = ac97_prepare,
};
@@ -58,20 +83,21 @@ static struct snd_soc_dai_driver ac97_dai = {
.stream_name = "AC97 Playback",
.channels_min = 1,
.channels_max = 2,
- .rates = STD_AC97_RATES,
+ .rates = SNDRV_PCM_RATE_KNOT,
.formats = SND_SOC_STD_AC97_FMTS,},
.capture = {
.stream_name = "AC97 Capture",
.channels_min = 1,
.channels_max = 2,
- .rates = STD_AC97_RATES,
+ .rates = SNDRV_PCM_RATE_KNOT,
.formats = SND_SOC_STD_AC97_FMTS,},
.ops = &ac97_dai_ops,
};
static int ac97_soc_probe(struct snd_soc_codec *codec)
{
- struct snd_ac97 *ac97;
+ struct ac97_private *ac97 = snd_soc_codec_get_drvdata(codec);
+
struct snd_ac97_bus *ac97_bus;
struct snd_ac97_template ac97_template;
int ret;
@@ -83,31 +109,28 @@ static int ac97_soc_probe(struct snd_soc_codec *codec)
return ret;
memset(&ac97_template, 0, sizeof(struct snd_ac97_template));
- ret = snd_ac97_mixer(ac97_bus, &ac97_template, &ac97);
+ ret = snd_ac97_mixer(ac97_bus, &ac97_template, &ac97->ac97);
if (ret < 0)
return ret;
- snd_soc_codec_set_drvdata(codec, ac97);
-
return 0;
}
#ifdef CONFIG_PM
static int ac97_soc_suspend(struct snd_soc_codec *codec)
{
- struct snd_ac97 *ac97 = snd_soc_codec_get_drvdata(codec);
+ struct ac97_private *ac97 = snd_soc_codec_get_drvdata(codec);
- snd_ac97_suspend(ac97);
+ snd_ac97_suspend(ac97->ac97);
return 0;
}
static int ac97_soc_resume(struct snd_soc_codec *codec)
{
+ struct ac97_private *ac97 = snd_soc_codec_get_drvdata(codec);
- struct snd_ac97 *ac97 = snd_soc_codec_get_drvdata(codec);
-
- snd_ac97_resume(ac97);
+ snd_ac97_resume(ac97->ac97);
return 0;
}
@@ -127,8 +150,89 @@ static struct snd_soc_codec_driver soc_codec_dev_ac97 = {
.num_dapm_routes = ARRAY_SIZE(ac97_routes),
};
+#ifdef CONFIG_OF
+static int ac97_of_read_rates(struct platform_device *pdev,
+ const char *name,
+ struct snd_pcm_hw_constraint_list *cons)
+{
+ struct device_node *np = pdev->dev.of_node;
+ struct property *prop;
+ int len, count, ctr;
+ unsigned int *clist;
+
+ prop = of_find_property(np, name, &len);
+ if (!prop)
+ return 0;
+
+ count = len / sizeof(u32);
+ if (count <= 0)
+ return 0;
+
+ clist = devm_kzalloc(&pdev->dev, count * sizeof(unsigned int),
+ GFP_KERNEL);
+ if (!clist)
+ return -ENOMEM;
+
+ for (ctr = 0; ctr < count; ctr++) {
+ int ret;
+ u32 val;
+
+ ret = of_property_read_u32_index(np, name, ctr, &val);
+ if (ret)
+ return ret;
+
+ clist[ctr] = val;
+ }
+
+ cons->list = clist;
+ cons->count = ctr;
+
+ return 0;
+}
+#endif
+
static int ac97_probe(struct platform_device *pdev)
{
+ struct ac97_private *ac97 =
+ devm_kzalloc(&pdev->dev, sizeof(struct ac97_private),
+ GFP_KERNEL);
+#ifdef CONFIG_OF
+ struct device_node *np = pdev->dev.of_node;
+#endif
+
+ if (!ac97)
+ return -ENOMEM;
+
+#ifdef CONFIG_OF
+ if (np) {
+ int ret;
+
+ ret = ac97_of_read_rates(pdev, "playback-rates",
+ &ac97->playback_rate_constraints);
+ if (ret)
+ return ret;
+
+ ret = ac97_of_read_rates(pdev, "capture-rates",
+ &ac97->capture_rate_constraints);
+ if (ret)
+ return ret;
+ }
+#endif
+
+ if (!ac97->playback_rate_constraints.list) {
+ ac97->playback_rate_constraints.list = default_rates;
+ ac97->playback_rate_constraints.count =
+ ARRAY_SIZE(default_rates);
+ }
+
+ if (!ac97->capture_rate_constraints.list) {
+ ac97->capture_rate_constraints.list = default_rates;
+ ac97->capture_rate_constraints.count =
+ ARRAY_SIZE(default_rates);
+ }
+
+ platform_set_drvdata(pdev, ac97);
+
return snd_soc_register_codec(&pdev->dev,
&soc_codec_dev_ac97, &ac97_dai, 1);
}
@@ -139,9 +243,16 @@ static int ac97_remove(struct platform_device *pdev)
return 0;
}
+static const struct of_device_id ac97_codec_dt_ids[] = {
+ { .compatible = "linux,ac97-codec", },
+ { }
+};
+MODULE_DEVICE_TABLE(of, ac97_codec_dt_ids);
+
static struct platform_driver ac97_codec_driver = {
.driver = {
.name = "ac97-codec",
+ .of_match_table = of_match_ptr(ac97_codec_dt_ids),
},
.probe = ac97_probe,
2
5
[alsa-devel] [Patch V7 00/10] ASoC: QCOM: Add support for ipq806x SOC
by Kenneth Westfield 07 Mar '15
by Kenneth Westfield 07 Mar '15
07 Mar '15
From: Kenneth Westfield <kwestfie(a)codeaurora.org>
This patch series adds support for I2S audio playback on the Storm board, which
contains a Qualcomm Technologies ipq806x SOC and a Maxim max98357a DAC/amp.
The ipq806x SOC has audio-related hardware blocks in its low-power audio
subsystem (or LPASS). One of the relevant blocks in the LPASS is its low-power
audio interface (or LPAIF). This contains an MI2S port, which is what these
drivers are configured to use. The LPAIF also contains a DMA engine that is
dedicated to moving audio samples into the transmit FIFO of the MI2S port.
One bus from the MI2S port of the SOC is connected to the DAC/amp for stereo
playback. This bus is configured so that the SOC is bus master and consists of
DATA, LRCLK, and BCLK. The DAC/amp does not need MCLK to operate. In addition,
a single GPIO pin from the SOC is connected to the same DAC/amp, which gives
enable/disable control over the DAC/amp.
The specific drivers added are:
* a CPU DAI driver for controlling the MI2S port
* a platform driver for controlling the LPAIF DMA engine
* a machine driver that instantiates a dai-link for playback
The LPASS also contains clocks that need to be controlled. Those drivers have
been submitted as several separate patch series:
* [PATCH v3 0/8] qcom audio clock control drivers
http://lkml.org/lkml/2015/1/19/656
* [PATCH] clk: qcom: Properly change rates for ahbix clock
https://lkml.org/lkml/2015/2/25/706
* [PATCH] clk: qcom: Fix ipq806x LCC frequency tables
https://lkml.org/lkml/2015/2/26/774
Even though the ipq806x LPASS does not contain an audio DSP, other SOCs do have
one. For those SOCs, the audio DSP typically controls the hardware blocks in
the LPASS. Hence, different CPU DAI driver(s) would need to be used in order to
facilitate audio with the DSP. As such, the LPASS DT contains an adsp subnode,
which is disabled for this SOC. The same subnode should be enabled and
populated for other SOCs that do contain an audio DSP. Not using the audio DSP
would require different CPU DAI driver(s), in addition to possible bootloader
and/or firmware changes.
Corresponding additions to the device tree for the ipq806x SOC and its
documentation has also been added. Also, as this is a new directory, the
MAINTAINERS file has been updated as well.
= Changes since V6
[Patch V6 00/10] ASoC: QCOM: Add support for ipq806x SOC
http://mailman.alsa-project.org/pipermail/alsa-devel/2015-February/088218.h…
* Added REGMAP_MMIO selection for the CPU and platform drivers
* Modified the AHBIX frequency to match the corresponding LCC fix
* Tweaked the logging in the CPU driver probe.
= Changes since V5
[Patch V5 00/12] ASoC: QCOM: Add support for ipq806x SOC
http://mailman.alsa-project.org/pipermail/alsa-devel/2015-February/087832.h…
* Correctly use Storm as the build target label and DT binding label.
* Added audio DSP sub-node to the LPASS device tree, disabled for this SOC.
* Added logic to CPU DAI driver to fail the probe() if a DSP is present.
* Use the standard naming convention for the DAI link properties.
* General code cleanup.
= Changes since V4
[Patch V4 00/10] ASoC: QCOM: Add support for ipq806x SOC
http://mailman.alsa-project.org/pipermail/alsa-devel/2015-February/087499.h…
* Replaced simple-card with a machine driver to resolve the system clock
configuration, rather than having the CPU DAI driver do it.
* Added header files to avoid indirect header dependencies and implicit
forward declarations.
* Tweaked the ISR to match the conventions of the surrounding code.
* Removed the usage of the low-power memory as it is not needed.
* Removed the use of the DRV_NAME constant.
* Added explicit dependency on gpiolib for the codec driver.
* Moved the MODULE_DEVICE_TABLE macro inside the CONFIG_OF conditional.
* Modified the documentation to reflect the changes.
* General code cleanup.
= Changes since V3
[Patch V3 00/10] ASoC: QCOM: Add support for ipq806x SOC
http://mailman.alsa-project.org/pipermail/alsa-devel/2014-December/085694.h…
* Placed the content of the inline functions into the callbacks.
* Replaced use of readl/writel register access functions with regmap access
functions. Notable exception is the ISR, which uses ioread32/iowrite32.
* Rearranged the sequencing of the hardware block enables to fit within the
ASoC framework callbacks, while remaining functional.
REQ 1: The hardware requires the enable sequence to be:
LPAIF-DMA[enable],then LPAIF-MI2S[enable], then DAC-GPIO[enable]
REQ 2: The hardware requires the disable sequence to be:
DAC-GPIO[disable], then LPAIF-MI2S[disable]
* Corrected the implementation of the pointer callback.
* Utilize the LPM to buffer audio samples, rather than memory external to
LPASS.
* Corrected the interrupt clearing in the ISR.
* Implemented a default system clock (defined by the simple-card DT node), and
optional LPASS DT node modifiers that can alter the system clock in order to
expand the range of available bit clock frequencies.
* Addressed all of the remaining issues raised by Mark Brown.
* General code cleanup.
= Changes since V2
[Patch v2 00/11] ASoC: QCOM: Add support for ipq806x SOC
http://mailman.alsa-project.org/pipermail/alsa-devel/2014-December/085186.h…
* Removed the PCM platform driver from the DTS platform and tied it to the CPU
DAI driver.
* Changed I2S pinctrl to use generic naming convention and moved control to
CPU DAI driver. It should be controlled now by soc-core's pinctrl_pm_*
functionality.
* Added stub DAPM support in codec driver. As the DAC GPIO needs to be
enabled last when starting playback, and disabled first when stopping
playback, it seems as though the trigger function may be the place for this.
Suggestions are welcome for a better place to put this.
* Removed machine driver and tied DAI drivers to simple-audio-card.
* Packaged the build files and Maxim codec files together in one change.
* Removed QCOM as vendor from Maxim code and documentation.
* Separated the SOC and board definitions into the correct DTS files.
* Update device tree documentation to reflect changes.
* General code cleanup.
= Changes since V1
[PATCH 0/9] ASoC: QCOM: Add support for ipq806x SOC
http://mailman.alsa-project.org/pipermail/alsa-devel/2014-November/084322.h…
* Remove the native LPAIF driver, and move its functionality to the CPU DAI
driver.
* Add a codec driver to manage the pins going to the external DAC (previously
managed by the machine driver).
* Use devm_* and dev_* where possible.
* ISR only handles relevant DMA channel now.
* Update device tree documentation to reflect changes.
* General code cleanup.
Kenneth Westfield (10):
MAINTAINERS: Add QCOM audio ASoC maintainer
ASoC: qcom: Document LPASS CPU bindings
ASoC: qcom: Document Storm bindings
ASoC: qcom: add LPASS header files
ASoC: qcom: Add LPASS CPU DAI driver
ASoC: qcom: Add LPASS platform driver
ASoC: qcom: Add Storm machine driver
ASoC: qcom: Add ability to build QCOM drivers
ASoC: Allow for building QCOM drivers
ARM: dts: Model IPQ LPASS audio hardware
.../devicetree/bindings/sound/qcom,lpass-cpu.txt | 49 ++
Documentation/devicetree/bindings/sound/storm.txt | 23 +
MAINTAINERS | 7 +
arch/arm/boot/dts/qcom-ipq8064.dtsi | 26 +
sound/soc/Kconfig | 1 +
sound/soc/Makefile | 1 +
sound/soc/qcom/Kconfig | 25 +
sound/soc/qcom/Makefile | 11 +
sound/soc/qcom/lpass-cpu.c | 510 ++++++++++++++++++++
sound/soc/qcom/lpass-lpaif-ipq806x.h | 172 +++++++
sound/soc/qcom/lpass-platform.c | 526 +++++++++++++++++++++
sound/soc/qcom/lpass.h | 51 ++
sound/soc/qcom/storm.c | 162 +++++++
13 files changed, 1564 insertions(+)
create mode 100644 Documentation/devicetree/bindings/sound/qcom,lpass-cpu.txt
create mode 100644 Documentation/devicetree/bindings/sound/storm.txt
create mode 100644 sound/soc/qcom/Kconfig
create mode 100644 sound/soc/qcom/Makefile
create mode 100644 sound/soc/qcom/lpass-cpu.c
create mode 100644 sound/soc/qcom/lpass-lpaif-ipq806x.h
create mode 100644 sound/soc/qcom/lpass-platform.c
create mode 100644 sound/soc/qcom/lpass.h
create mode 100644 sound/soc/qcom/storm.c
--
The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum,
a Linux Foundation Collaborative Project
3
21
07 Mar '15
The jz4780 and jz4740 have very similar i2s blocks.
The slight difference is in Rx/Tx fifos.
And the bitclocks for input/output are different.
This patch adds jz4780 support to the driver
Signed-off-by: Zubair Lutfullah Kakakhel <Zubair.Kakakhel(a)imgtec.com>
---
Patch based on 4.0-rc2
Tested on the MIPS Creator CI20.
---
.../bindings/sound/ingenic,jz4740-i2s.txt | 2 +-
sound/soc/jz4740/jz4740-i2s.c | 63 ++++++++++++++++++----
2 files changed, 54 insertions(+), 11 deletions(-)
diff --git a/Documentation/devicetree/bindings/sound/ingenic,jz4740-i2s.txt b/Documentation/devicetree/bindings/sound/ingenic,jz4740-i2s.txt
index b414333..b623d50 100644
--- a/Documentation/devicetree/bindings/sound/ingenic,jz4740-i2s.txt
+++ b/Documentation/devicetree/bindings/sound/ingenic,jz4740-i2s.txt
@@ -1,7 +1,7 @@
Ingenic JZ4740 I2S controller
Required properties:
-- compatible : "ingenic,jz4740-i2s"
+- compatible : "ingenic,jz4740-i2s" or "ingenic,jz4780-i2s"
- reg : I2S registers location and length
- clocks : AIC and I2S PLL clock specifiers.
- clock-names: "aic" and "i2s"
diff --git a/sound/soc/jz4740/jz4740-i2s.c b/sound/soc/jz4740/jz4740-i2s.c
index 07f7781..630ea96 100644
--- a/sound/soc/jz4740/jz4740-i2s.c
+++ b/sound/soc/jz4740/jz4740-i2s.c
@@ -58,6 +58,12 @@
#define JZ_AIC_CONF_FIFO_RX_THRESHOLD_OFFSET 12
#define JZ_AIC_CONF_FIFO_TX_THRESHOLD_OFFSET 8
+#define JZ4780_AIC_CONF_FIFO_RX_THRESHOLD_OFFSET 24
+#define JZ4780_AIC_CONF_FIFO_TX_THRESHOLD_OFFSET 16
+#define JZ4780_AIC_CONF_FIFO_RX_THRESHOLD_MASK \
+ (0xf << JZ4780_AIC_CONF_FIFO_RX_THRESHOLD_OFFSET)
+#define JZ4780_AIC_CONF_FIFO_TX_THRESHOLD_MASK \
+ (0x1f << JZ4780_AIC_CONF_FIFO_TX_THRESHOLD_OFFSET)
#define JZ_AIC_CTRL_OUTPUT_SAMPLE_SIZE_MASK (0x7 << 19)
#define JZ_AIC_CTRL_INPUT_SAMPLE_SIZE_MASK (0x7 << 16)
@@ -79,6 +85,7 @@
#define JZ_AIC_CTRL_INPUT_SAMPLE_SIZE_OFFSET 16
#define JZ_AIC_I2S_FMT_DISABLE_BIT_CLK BIT(12)
+#define JZ_AIC_I2S_FMT_DISABLE_BIT_ICLK BIT(13)
#define JZ_AIC_I2S_FMT_ENABLE_SYS_CLK BIT(4)
#define JZ_AIC_I2S_FMT_MSB BIT(0)
@@ -87,6 +94,13 @@
#define JZ_AIC_CLK_DIV_MASK 0xf
#define I2SDIV_DV_SHIFT 8
#define I2SDIV_DV_MASK (0xf << I2SDIV_DV_SHIFT)
+#define I2SDIV_IDV_SHIFT 8
+#define I2SDIV_IDV_MASK (0xf << I2SDIV_IDV_SHIFT)
+
+enum jz47xx_i2s_version {
+ JZ_I2S_JZ4740,
+ JZ_I2S_JZ4780,
+};
struct jz4740_i2s {
struct resource *mem;
@@ -98,6 +112,8 @@ struct jz4740_i2s {
struct snd_dmaengine_dai_dma_data playback_dma_data;
struct snd_dmaengine_dai_dma_data capture_dma_data;
+
+ enum jz47xx_i2s_version version;
};
static inline uint32_t jz4740_i2s_read(const struct jz4740_i2s *i2s,
@@ -267,13 +283,22 @@ static int jz4740_i2s_hw_params(struct snd_pcm_substream *substream,
ctrl |= JZ_AIC_CTRL_MONO_TO_STEREO;
else
ctrl &= ~JZ_AIC_CTRL_MONO_TO_STEREO;
+
+ div_reg &= ~I2SDIV_DV_MASK;
+ div_reg |= (div - 1) << I2SDIV_DV_SHIFT;
} else {
ctrl &= ~JZ_AIC_CTRL_INPUT_SAMPLE_SIZE_MASK;
ctrl |= sample_size << JZ_AIC_CTRL_INPUT_SAMPLE_SIZE_OFFSET;
+
+ if (i2s->version >= JZ_I2S_JZ4780) {
+ div_reg &= ~I2SDIV_IDV_MASK;
+ div_reg |= (div - 1) << I2SDIV_IDV_SHIFT;
+ } else {
+ div_reg &= ~I2SDIV_DV_MASK;
+ div_reg |= (div - 1) << I2SDIV_DV_SHIFT;
+ }
}
- div_reg &= ~I2SDIV_DV_MASK;
- div_reg |= (div - 1) << I2SDIV_DV_SHIFT;
jz4740_i2s_write(i2s, JZ_REG_AIC_CTRL, ctrl);
jz4740_i2s_write(i2s, JZ_REG_AIC_CLK_DIV, div_reg);
@@ -369,11 +394,19 @@ static int jz4740_i2s_dai_probe(struct snd_soc_dai *dai)
snd_soc_dai_init_dma_data(dai, &i2s->playback_dma_data,
&i2s->capture_dma_data);
- conf = (7 << JZ_AIC_CONF_FIFO_RX_THRESHOLD_OFFSET) |
- (8 << JZ_AIC_CONF_FIFO_TX_THRESHOLD_OFFSET) |
- JZ_AIC_CONF_OVERFLOW_PLAY_LAST |
- JZ_AIC_CONF_I2S |
- JZ_AIC_CONF_INTERNAL_CODEC;
+ if (i2s->version >= JZ_I2S_JZ4780) {
+ conf = (7 << JZ4780_AIC_CONF_FIFO_RX_THRESHOLD_OFFSET) |
+ (8 << JZ4780_AIC_CONF_FIFO_TX_THRESHOLD_OFFSET) |
+ JZ_AIC_CONF_OVERFLOW_PLAY_LAST |
+ JZ_AIC_CONF_I2S |
+ JZ_AIC_CONF_INTERNAL_CODEC;
+ } else {
+ conf = (7 << JZ_AIC_CONF_FIFO_RX_THRESHOLD_OFFSET) |
+ (8 << JZ_AIC_CONF_FIFO_TX_THRESHOLD_OFFSET) |
+ JZ_AIC_CONF_OVERFLOW_PLAY_LAST |
+ JZ_AIC_CONF_I2S |
+ JZ_AIC_CONF_INTERNAL_CODEC;
+ }
jz4740_i2s_write(i2s, JZ_REG_AIC_CONF, JZ_AIC_CONF_RESET);
jz4740_i2s_write(i2s, JZ_REG_AIC_CONF, conf);
@@ -428,7 +461,8 @@ static const struct snd_soc_component_driver jz4740_i2s_component = {
#ifdef CONFIG_OF
static const struct of_device_id jz4740_of_matches[] = {
- { .compatible = "ingenic,jz4740-i2s" },
+ { .compatible = "ingenic,jz4740-i2s", .data = (void *)JZ_I2S_JZ4740 },
+ { .compatible = "ingenic,jz4780-i2s", .data = (void *)JZ_I2S_JZ4780 },
{ /* sentinel */ }
};
#endif
@@ -438,11 +472,16 @@ static int jz4740_i2s_dev_probe(struct platform_device *pdev)
struct jz4740_i2s *i2s;
struct resource *mem;
int ret;
+ const struct of_device_id *match;
i2s = devm_kzalloc(&pdev->dev, sizeof(*i2s), GFP_KERNEL);
if (!i2s)
return -ENOMEM;
+ match = of_match_device(jz4740_of_matches, &pdev->dev);
+ if (match)
+ i2s->version = (enum jz47xx_i2s_version)match->data;
+
mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
i2s->base = devm_ioremap_resource(&pdev->dev, mem);
if (IS_ERR(i2s->base))
@@ -465,8 +504,12 @@ static int jz4740_i2s_dev_probe(struct platform_device *pdev)
if (ret)
return ret;
- return devm_snd_dmaengine_pcm_register(&pdev->dev, NULL,
- SND_DMAENGINE_PCM_FLAG_COMPAT);
+ if (i2s->version >= JZ_I2S_JZ4780)
+ return devm_snd_dmaengine_pcm_register(&pdev->dev, NULL,
+ SND_DMAENGINE_PCM_FLAG_NO_RESIDUE);
+ else
+ return devm_snd_dmaengine_pcm_register(&pdev->dev, NULL,
+ SND_DMAENGINE_PCM_FLAG_COMPAT);
}
static struct platform_driver jz4740_i2s_driver = {
--
1.9.1
2
1
[alsa-devel] [PATCH 1/1] ASoC: Intel: remove conflicts when load/unload multiple firmware images
by han.lu@intel.com 06 Mar '15
by han.lu@intel.com 06 Mar '15
06 Mar '15
From: "Lu, Han" <han.lu(a)intel.com>
Details:
1. Unload all modules on fw_list of dsp when suspend, and reload all
modules on fw_list when resume.
2. A DSP expects only one scratch, but hsw_parse_fw_image() allocates
scratch blocks for each firmware image it parses. Move the allocate function
sst_block_alloc_scratch() out of hsw_parse_fw_image() to make sure a scratch
be allocated only after all firmware images be parsed.
Signed-off-by: Lu, Han <han.lu(a)intel.com>
diff --git a/sound/soc/intel/sst-haswell-dsp.c b/sound/soc/intel/sst-haswell-dsp.c
index c42ffae..402b728 100644
--- a/sound/soc/intel/sst-haswell-dsp.c
+++ b/sound/soc/intel/sst-haswell-dsp.c
@@ -207,9 +207,6 @@ static int hsw_parse_fw_image(struct sst_fw *sst_fw)
module = (void *)module + sizeof(*module) + module->mod_size;
}
- /* allocate scratch mem regions */
- sst_block_alloc_scratch(dsp);
-
return 0;
}
diff --git a/sound/soc/intel/sst-haswell-ipc.c b/sound/soc/intel/sst-haswell-ipc.c
index 394af56..863a9ca 100644
--- a/sound/soc/intel/sst-haswell-ipc.c
+++ b/sound/soc/intel/sst-haswell-ipc.c
@@ -1732,6 +1732,7 @@ static void sst_hsw_drop_all(struct sst_hsw *hsw)
int sst_hsw_dsp_load(struct sst_hsw *hsw)
{
struct sst_dsp *dsp = hsw->dsp;
+ struct sst_fw *sst_fw, *t;
int ret;
dev_dbg(hsw->dev, "loading audio DSP....");
@@ -1748,12 +1749,17 @@ int sst_hsw_dsp_load(struct sst_hsw *hsw)
return ret;
}
- ret = sst_fw_reload(hsw->sst_fw);
- if (ret < 0) {
- dev_err(hsw->dev, "error: SST FW reload failed\n");
- sst_dsp_dma_put_channel(dsp);
- return -ENOMEM;
+ list_for_each_entry_safe_reverse(sst_fw, t, &dsp->fw_list, list) {
+ ret = sst_fw_reload(sst_fw);
+ if (ret < 0) {
+ dev_err(hsw->dev, "error: SST FW reload failed\n");
+ sst_dsp_dma_put_channel(dsp);
+ return -ENOMEM;
+ }
}
+ ret = sst_block_alloc_scratch(hsw->dsp);
+ if (ret < 0)
+ return -EINVAL;
sst_dsp_dma_put_channel(dsp);
return 0;
@@ -1809,12 +1815,17 @@ int sst_hsw_dsp_runtime_suspend(struct sst_hsw *hsw)
int sst_hsw_dsp_runtime_sleep(struct sst_hsw *hsw)
{
- sst_fw_unload(hsw->sst_fw);
- sst_block_free_scratch(hsw->dsp);
+ struct sst_fw *sst_fw, *t;
+ struct sst_dsp *dsp = hsw->dsp;
+
+ list_for_each_entry_safe(sst_fw, t, &dsp->fw_list, list) {
+ sst_fw_unload(sst_fw);
+ }
+ sst_block_free_scratch(dsp);
hsw->boot_complete = false;
- sst_dsp_sleep(hsw->dsp);
+ sst_dsp_sleep(dsp);
return 0;
}
@@ -1943,6 +1954,11 @@ int sst_hsw_dsp_init(struct device *dev, struct sst_pdata *pdata)
goto fw_err;
}
+ /* allocate scratch mem regions */
+ ret = sst_block_alloc_scratch(hsw->dsp);
+ if (ret < 0)
+ goto boot_err;
+
/* wait for DSP boot completion */
sst_dsp_boot(hsw->dsp);
ret = wait_event_timeout(hsw->boot_wait, hsw->boot_complete,
--
1.9.1
2
1
[alsa-devel] Right interface for cellphone modem audio (was Re: [PATCHv2 0/2] N900 Modem Speech Support)
by Pavel Machek 06 Mar '15
by Pavel Machek 06 Mar '15
06 Mar '15
Hi!
> >>Userland access goes via /dev/cmt_speech. The API is implemented in
> >>libcmtspeechdata, which is used by ofono and the freesmartphone.org project.
> >Yes, the ABI is "tested" for some years, but it is not documented, and
> >it is very wrong ABI.
> >
> >I'm not sure what they do with the "read()". I was assuming it is
> >meant for passing voice data, but it can return at most 4 bytes,
> >AFAICT.
> >
> >We already have perfectly good ABI for passing voice data around. It
> >is called "ALSA". libcmtspeech will then become unneccessary, and the
> >daemon routing voice data will be as simple as "read sample from
>
> I'm no longer involved with cmt_speech (with this driver nor modems in
> general), but let me clarify some bits about the design.
Thanks a lot for your insights; high level design decisions are quite
hard to understand from C code.
> First, the team that designed the driver and the stack above had a lot of
> folks working also with ALSA (and the ALSA drivers have been merged to
> mainline long ago) and we considered ALSA on multiple occasions as the
> interface for this as well.
>
> Our take was that ALSA is not the right interface for cmt_speech. The
> cmt_speech interface in the modem is _not_ a PCM interface as modelled by
> ALSA. Specifically:
>
> - the interface is lossy in both directions
> - data is sent in packets, not a stream of samples (could be other things
> than PCM samples), with timing and meta-data
> - timing of uplink is of utmost importance
I see that you may not have data available in "downlink" scenario, but
how is it lossy in "uplink" scenario? Phone should always try to fill
the uplink, no? (Or do you detect silence and not transmit in this
case?) (Actually, I guess applications should be ready for "data not
ready" case even on "normal" hardware due to differing clocks.)
Packets vs. stream of samples... does userland need to know about the
packets? Could we simply hide it from the userland? As userland daemon
is (supposed to be) realtime, do we really need extra set of
timestamps? What other metadata are there?
Uplink timing... As the daemon is realtime, can it just send the data
at the right time? Also normally uplink would be filled, no?
> Some definite similarities:
> - the mmap interface to manage the PCM buffers (that is on purpose
> similar to that of ALSA)
>
> The interface was designed so that the audio mixer (e.g. Pulseaudio) is run
> with a soft real-time SCHED_FIFO/RR user-space thread that has full control
> over _when_ voice _packets_ are sent, and can receive packets with meta-data
> (see libcmtspeechdata interface, cmtspeech.h), and can detect and handle
> gaps in the received packets.
Well, packets are of fixed size, right? So the userland can simply
supply the right size in the common case. As for sending at the right
time... well... if the userspace is already real-time, that should be
easy.
Now, there's a difference in the downlink. Maybe ALSA people have an
idea what to do in this case? Perhaps we can just provide artificial
"zero" data?
> This is very different from modems that offer an actual PCM voice link for
> example over I2S to the application processor (there are lots of these on
> the market). When you walk out of coverage during a call with these modems,
> you'll still get samples over I2S, but not so with cmt_speech, so ALSA is
> not the right interface.
Yes, understood.
> Now, I'm not saying the interface is perfect, but just to give a bit of
> background, why a custom char-device interface was chosen.
Thanks and best regards,
Pavel
--
(english) http://www.livejournal.com/~pavelmachek
(cesky, pictures) http://atrey.karlin.mff.cuni.cz/~pavel/picture/horses/blog.html
2
1