From d681518a56d25d21d73a421174d189242adc68c7 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Thu, 23 Mar 2006 16:06:23 +0100 Subject: [ALSA] Add support of LG LW20 laptop Add support of LG LW20 laptop with ALC880 codec (ALSA bug#1572). Signed-off-by: Takashi Iwai --- Documentation/sound/alsa/ALSA-Configuration.txt | 1 + sound/pci/hda/patch_realtek.c | 93 +++++++++++++++++++++++++ 2 files changed, 94 insertions(+) diff --git a/Documentation/sound/alsa/ALSA-Configuration.txt b/Documentation/sound/alsa/ALSA-Configuration.txt index 1def6049784c..baf18c6afdd6 100644 --- a/Documentation/sound/alsa/ALSA-Configuration.txt +++ b/Documentation/sound/alsa/ALSA-Configuration.txt @@ -701,6 +701,7 @@ Prior to version 0.9.0rc4 options had a 'snd_' prefix. This was removed. uniwill 3-jack F1734 2-jack lg LG laptop (m1 express dual) + lg-lw LG LW20 laptop test for testing/debugging purpose, almost all controls can be adjusted. Appearing only when compiled with $CONFIG_SND_DEBUG=y diff --git a/sound/pci/hda/patch_realtek.c b/sound/pci/hda/patch_realtek.c index 4c6c9ec8ea5b..6b45635b3ea3 100644 --- a/sound/pci/hda/patch_realtek.c +++ b/sound/pci/hda/patch_realtek.c @@ -52,6 +52,7 @@ enum { ALC880_CLEVO, ALC880_TCL_S700, ALC880_LG, + ALC880_LG_LW, #ifdef CONFIG_SND_DEBUG ALC880_TEST, #endif @@ -1426,6 +1427,82 @@ static void alc880_lg_unsol_event(struct hda_codec *codec, unsigned int res) alc880_lg_automute(codec); } +/* + * LG LW20 + * + * Pin assignment: + * Speaker-out: 0x14 + * Mic-In: 0x18 + * Built-in Mic-In: 0x19 (?) + * HP-Out: 0x1b + * SPDIF-Out: 0x1e + */ + +/* seems analog CD is not working */ +static struct hda_input_mux alc880_lg_lw_capture_source = { + .num_items = 2, + .items = { + { "Mic", 0x0 }, + { "Internal Mic", 0x1 }, + }, +}; + +static struct snd_kcontrol_new alc880_lg_lw_mixer[] = { + HDA_CODEC_VOLUME("Master Playback Volume", 0x0c, 0x0, HDA_OUTPUT), + HDA_BIND_MUTE("Master Playback Switch", 0x0c, 2, HDA_INPUT), + HDA_CODEC_VOLUME("Mic Playback Volume", 0x0b, 0x0, HDA_INPUT), + HDA_CODEC_MUTE("Mic Playback Switch", 0x0b, 0x0, HDA_INPUT), + HDA_CODEC_VOLUME("Internal Mic Playback Volume", 0x0b, 0x01, HDA_INPUT), + HDA_CODEC_MUTE("Internal Mic Playback Switch", 0x0b, 0x01, HDA_INPUT), + { } /* end */ +}; + +static struct hda_verb alc880_lg_lw_init_verbs[] = { + /* set capture source to mic-in */ + {0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x08, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x09, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(7)}, + /* speaker-out */ + {0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP}, + {0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + /* HP-out */ + {0x13, AC_VERB_SET_CONNECT_SEL, 0x00}, + {0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP}, + {0x1b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + /* mic-in to input */ + {0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80}, + {0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + /* built-in mic */ + {0x19, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80}, + {0x19, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + /* jack sense */ + {0x1b, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | 0x1}, + { } +}; + +/* toggle speaker-output according to the hp-jack state */ +static void alc880_lg_lw_automute(struct hda_codec *codec) +{ + unsigned int present; + + present = snd_hda_codec_read(codec, 0x1b, 0, + AC_VERB_GET_PIN_SENSE, 0) & 0x80000000; + snd_hda_codec_amp_update(codec, 0x14, 0, HDA_OUTPUT, 0, + 0x80, present ? 0x80 : 0); + snd_hda_codec_amp_update(codec, 0x14, 1, HDA_OUTPUT, 0, + 0x80, present ? 0x80 : 0); +} + +static void alc880_lg_lw_unsol_event(struct hda_codec *codec, unsigned int res) +{ + /* Looks like the unsol event is incompatible with the standard + * definition. 4bit tag is placed at 28 bit! + */ + if ((res >> 28) == 0x01) + alc880_lg_lw_automute(codec); +} + /* * Common callbacks */ @@ -2078,6 +2155,9 @@ static struct hda_board_config alc880_cfg_tbl[] = { { .modelname = "lg", .config = ALC880_LG }, { .pci_subvendor = 0x1854, .pci_subdevice = 0x003b, .config = ALC880_LG }, + { .modelname = "lg-lw", .config = ALC880_LG_LW }, + { .pci_subvendor = 0x1854, .pci_subdevice = 0x0018, .config = ALC880_LG_LW }, + #ifdef CONFIG_SND_DEBUG { .modelname = "test", .config = ALC880_TEST }, #endif @@ -2268,6 +2348,19 @@ static struct alc_config_preset alc880_presets[] = { .unsol_event = alc880_lg_unsol_event, .init_hook = alc880_lg_automute, }, + [ALC880_LG_LW] = { + .mixers = { alc880_lg_lw_mixer }, + .init_verbs = { alc880_volume_init_verbs, + alc880_lg_lw_init_verbs }, + .num_dacs = 1, + .dac_nids = alc880_dac_nids, + .dig_out_nid = ALC880_DIGOUT_NID, + .num_channel_mode = ARRAY_SIZE(alc880_2_jack_modes), + .channel_mode = alc880_2_jack_modes, + .input_mux = &alc880_lg_lw_capture_source, + .unsol_event = alc880_lg_lw_unsol_event, + .init_hook = alc880_lg_lw_automute, + }, #ifdef CONFIG_SND_DEBUG [ALC880_TEST] = { .mixers = { alc880_test_mixer }, -- cgit v1.2.3-59-g8ed1b From c960a03beef8e1bdd26b1658d0ce87902f4a08f2 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Thu, 23 Mar 2006 17:06:28 +0100 Subject: [ALSA] hda-codec - Fix VREF level of Mic inputs on STAC92xx codecs Fixed VREF level of Mic inputs on STAC92xx codecs (ALSA bug#1953). Signed-off-by: Takashi Iwai --- sound/pci/hda/patch_sigmatel.c | 35 +++++++++++++++++++++++++++++------ 1 file changed, 29 insertions(+), 6 deletions(-) diff --git a/sound/pci/hda/patch_sigmatel.c b/sound/pci/hda/patch_sigmatel.c index b56ca4019392..e5fdb64eb02c 100644 --- a/sound/pci/hda/patch_sigmatel.c +++ b/sound/pci/hda/patch_sigmatel.c @@ -534,6 +534,22 @@ static int stac92xx_build_pcms(struct hda_codec *codec) return 0; } +static unsigned int stac92xx_get_vref(struct hda_codec *codec, hda_nid_t nid) +{ + unsigned int pincap = snd_hda_param_read(codec, nid, + AC_PAR_PIN_CAP); + pincap = (pincap & AC_PINCAP_VREF) >> AC_PINCAP_VREF_SHIFT; + if (pincap & AC_PINCAP_VREF_100) + return AC_PINCTL_VREF_100; + if (pincap & AC_PINCAP_VREF_80) + return AC_PINCTL_VREF_80; + if (pincap & AC_PINCAP_VREF_50) + return AC_PINCTL_VREF_50; + if (pincap & AC_PINCAP_VREF_GRD) + return AC_PINCTL_VREF_GRD; + return 0; +} + static void stac92xx_auto_set_pinctl(struct hda_codec *codec, hda_nid_t nid, int pin_type) { @@ -571,9 +587,12 @@ static int stac92xx_io_switch_put(struct snd_kcontrol *kcontrol, struct snd_ctl_ if (val) stac92xx_auto_set_pinctl(codec, nid, AC_PINCTL_OUT_EN); - else - stac92xx_auto_set_pinctl(codec, nid, AC_PINCTL_IN_EN); - + else { + unsigned int pinctl = AC_PINCTL_IN_EN; + if (io_idx) /* set VREF for mic */ + pinctl |= stac92xx_get_vref(codec, nid); + stac92xx_auto_set_pinctl(codec, nid, pinctl); + } return 1; } @@ -951,9 +970,13 @@ static int stac92xx_init(struct hda_codec *codec) stac92xx_auto_init_hp_out(codec); } for (i = 0; i < AUTO_PIN_LAST; i++) { - if (cfg->input_pins[i]) - stac92xx_auto_set_pinctl(codec, cfg->input_pins[i], - AC_PINCTL_IN_EN); + hda_nid_t nid = cfg->input_pins[i]; + if (nid) { + unsigned int pinctl = AC_PINCTL_IN_EN; + if (i == AUTO_PIN_MIC || i == AUTO_PIN_FRONT_MIC) + pinctl |= stac92xx_get_vref(codec, nid); + stac92xx_auto_set_pinctl(codec, nid, pinctl); + } } if (cfg->dig_out_pin) stac92xx_auto_set_pinctl(codec, cfg->dig_out_pin, -- cgit v1.2.3-59-g8ed1b From c04d092bde6a5dce632dec595f3974a35ed2cc2a Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Thu, 23 Mar 2006 17:11:32 +0100 Subject: [ALSA] via82xx - Add dxs entry for EPoX EP-8KRAI Added the dxs_support entry for EPoX EP-8KRAI (ALSA bug#1423). Signed-off-by: Takashi Iwai --- sound/pci/via82xx.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/pci/via82xx.c b/sound/pci/via82xx.c index 1957d29c119e..1e7398de2865 100644 --- a/sound/pci/via82xx.c +++ b/sound/pci/via82xx.c @@ -2373,6 +2373,7 @@ static int __devinit check_dxs_list(struct pci_dev *pci) { .subvendor = 0x161f, .subdevice = 0x2032, .action = VIA_DXS_48K }, /* m680x machines */ { .subvendor = 0x1631, .subdevice = 0xe004, .action = VIA_DXS_ENABLE }, /* Easy Note 3174, Packard Bell */ { .subvendor = 0x1695, .subdevice = 0x3005, .action = VIA_DXS_ENABLE }, /* EPoX EP-8K9A */ + { .subvendor = 0x1695, .subdevice = 0x300c, .action = VIA_DXS_SRC }, /* EPoX EP-8KRAI */ { .subvendor = 0x1695, .subdevice = 0x300e, .action = VIA_DXS_SRC }, /* EPoX 9HEAI */ { .subvendor = 0x16f3, .subdevice = 0x6405, .action = VIA_DXS_SRC }, /* Jetway K8M8MS */ { .subvendor = 0x1734, .subdevice = 0x1078, .action = VIA_DXS_SRC }, /* FSC Amilo L7300 */ -- cgit v1.2.3-59-g8ed1b From 6285ae1df13d55454d3de48504cb97e0cde4ecfa Mon Sep 17 00:00:00 2001 From: Alan Horstmann Date: Fri, 24 Mar 2006 18:40:09 +0100 Subject: [ALSA] ice1712 - Fix wrong register value for DMX 6FIRE I have just discovered I made an error in the register value set in 'Limit dmx6fire to 6 dacs' patch (bug1472). The value set should be '2a' not '0a' as in the original patch, which unintentionally disables the 2nd MPU 401 UART. Signed-off-by: Alan Horstmann Signed-off-by: Takashi Iwai --- sound/pci/ice1712/ice1712.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/pci/ice1712/ice1712.c b/sound/pci/ice1712/ice1712.c index b88eeba2f5d1..32f8415558a5 100644 --- a/sound/pci/ice1712/ice1712.c +++ b/sound/pci/ice1712/ice1712.c @@ -2402,7 +2402,7 @@ static int __devinit snd_ice1712_chip_init(struct snd_ice1712 *ice) if (ice->eeprom.subvendor == ICE1712_SUBDEVICE_DMX6FIRE && !ice->dxr_enable) { /* Limit active ADCs and DACs to 6; */ /* Note: DXR extension not supported */ - pci_write_config_byte(ice->pci, 0x60, 0x0a); + pci_write_config_byte(ice->pci, 0x60, 0x2a); } else { pci_write_config_byte(ice->pci, 0x60, ice->eeprom.data[ICE_EEP1_CODEC]); } -- cgit v1.2.3-59-g8ed1b From cbac4b0cb62d01cb0aaec7778410b8856f01186b Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Mon, 27 Mar 2006 12:38:07 +0200 Subject: [ALSA] Cleanup unused argument for snd_power_wait() Removed the unused file argument of snd_power_wait(). Signed-off-by: Takashi Iwai --- include/sound/core.h | 2 +- sound/core/control.c | 6 +++--- sound/core/control_compat.c | 6 +++--- sound/core/init.c | 9 +-------- sound/core/pcm_native.c | 10 +++++----- 5 files changed, 13 insertions(+), 20 deletions(-) diff --git a/include/sound/core.h b/include/sound/core.h index 7f32c12b4a0a..d1d043f6cb80 100644 --- a/include/sound/core.h +++ b/include/sound/core.h @@ -170,7 +170,7 @@ static inline void snd_power_change_state(struct snd_card *card, unsigned int st } /* init.c */ -int snd_power_wait(struct snd_card *card, unsigned int power_state, struct file *file); +int snd_power_wait(struct snd_card *card, unsigned int power_state); #else /* ! CONFIG_PM */ diff --git a/sound/core/control.c b/sound/core/control.c index 574745314e70..22565c9b9603 100644 --- a/sound/core/control.c +++ b/sound/core/control.c @@ -664,7 +664,7 @@ static int snd_ctl_elem_info_user(struct snd_ctl_file *ctl, if (copy_from_user(&info, _info, sizeof(info))) return -EFAULT; snd_power_lock(ctl->card); - result = snd_power_wait(ctl->card, SNDRV_CTL_POWER_D0, NULL); + result = snd_power_wait(ctl->card, SNDRV_CTL_POWER_D0); if (result >= 0) result = snd_ctl_elem_info(ctl, &info); snd_power_unlock(ctl->card); @@ -718,7 +718,7 @@ static int snd_ctl_elem_read_user(struct snd_card *card, return -EFAULT; } snd_power_lock(card); - result = snd_power_wait(card, SNDRV_CTL_POWER_D0, NULL); + result = snd_power_wait(card, SNDRV_CTL_POWER_D0); if (result >= 0) result = snd_ctl_elem_read(card, control); snd_power_unlock(card); @@ -783,7 +783,7 @@ static int snd_ctl_elem_write_user(struct snd_ctl_file *file, } card = file->card; snd_power_lock(card); - result = snd_power_wait(card, SNDRV_CTL_POWER_D0, NULL); + result = snd_power_wait(card, SNDRV_CTL_POWER_D0); if (result >= 0) result = snd_ctl_elem_write(card, file, control); snd_power_unlock(card); diff --git a/sound/core/control_compat.c b/sound/core/control_compat.c index 84fef5084e17..3c0161bb5ba4 100644 --- a/sound/core/control_compat.c +++ b/sound/core/control_compat.c @@ -109,7 +109,7 @@ static int snd_ctl_elem_info_compat(struct snd_ctl_file *ctl, goto error; snd_power_lock(ctl->card); - err = snd_power_wait(ctl->card, SNDRV_CTL_POWER_D0, NULL); + err = snd_power_wait(ctl->card, SNDRV_CTL_POWER_D0); if (err >= 0) err = snd_ctl_elem_info(ctl, data); snd_power_unlock(ctl->card); @@ -294,7 +294,7 @@ static int snd_ctl_elem_read_user_compat(struct snd_card *card, goto error; snd_power_lock(card); - err = snd_power_wait(card, SNDRV_CTL_POWER_D0, NULL); + err = snd_power_wait(card, SNDRV_CTL_POWER_D0); if (err >= 0) err = snd_ctl_elem_read(card, data); snd_power_unlock(card); @@ -320,7 +320,7 @@ static int snd_ctl_elem_write_user_compat(struct snd_ctl_file *file, goto error; snd_power_lock(card); - err = snd_power_wait(card, SNDRV_CTL_POWER_D0, NULL); + err = snd_power_wait(card, SNDRV_CTL_POWER_D0); if (err >= 0) err = snd_ctl_elem_write(card, file, data); snd_power_unlock(card); diff --git a/sound/core/init.c b/sound/core/init.c index 5bb8a8b23d51..39ed2e5bb0af 100644 --- a/sound/core/init.c +++ b/sound/core/init.c @@ -722,13 +722,12 @@ int snd_card_file_remove(struct snd_card *card, struct file *file) * snd_power_wait - wait until the power-state is changed. * @card: soundcard structure * @power_state: expected power state - * @file: file structure for the O_NONBLOCK check (optional) * * Waits until the power-state is changed. * * Note: the power lock must be active before call. */ -int snd_power_wait(struct snd_card *card, unsigned int power_state, struct file *file) +int snd_power_wait(struct snd_card *card, unsigned int power_state) { wait_queue_t wait; int result = 0; @@ -745,12 +744,6 @@ int snd_power_wait(struct snd_card *card, unsigned int power_state, struct file } if (snd_power_get_state(card) == power_state) break; -#if 0 /* block all devices */ - if (file && (file->f_flags & O_NONBLOCK)) { - result = -EAGAIN; - break; - } -#endif set_current_state(TASK_UNINTERRUPTIBLE); snd_power_unlock(card); schedule_timeout(30 * HZ); diff --git a/sound/core/pcm_native.c b/sound/core/pcm_native.c index 01f150f0990e..7010eb271cc7 100644 --- a/sound/core/pcm_native.c +++ b/sound/core/pcm_native.c @@ -1170,7 +1170,7 @@ static int snd_pcm_resume(struct snd_pcm_substream *substream) int res; snd_power_lock(card); - if ((res = snd_power_wait(card, SNDRV_CTL_POWER_D0, substream->ffile)) >= 0) + if ((res = snd_power_wait(card, SNDRV_CTL_POWER_D0)) >= 0) res = snd_pcm_action_lock_irq(&snd_pcm_action_resume, substream, 0); snd_power_unlock(card); return res; @@ -1198,7 +1198,7 @@ static int snd_pcm_xrun(struct snd_pcm_substream *substream) snd_power_lock(card); if (runtime->status->state == SNDRV_PCM_STATE_SUSPENDED) { - result = snd_power_wait(card, SNDRV_CTL_POWER_D0, substream->ffile); + result = snd_power_wait(card, SNDRV_CTL_POWER_D0); if (result < 0) goto _unlock; } @@ -1319,7 +1319,7 @@ int snd_pcm_prepare(struct snd_pcm_substream *substream) struct snd_card *card = substream->pcm->card; snd_power_lock(card); - if ((res = snd_power_wait(card, SNDRV_CTL_POWER_D0, substream->ffile)) >= 0) + if ((res = snd_power_wait(card, SNDRV_CTL_POWER_D0)) >= 0) res = snd_pcm_action_nonatomic(&snd_pcm_action_prepare, substream, 0); snd_power_unlock(card); return res; @@ -1410,7 +1410,7 @@ static int snd_pcm_drain(struct snd_pcm_substream *substream) snd_power_lock(card); if (runtime->status->state == SNDRV_PCM_STATE_SUSPENDED) { - result = snd_power_wait(card, SNDRV_CTL_POWER_D0, substream->ffile); + result = snd_power_wait(card, SNDRV_CTL_POWER_D0); if (result < 0) { snd_power_unlock(card); return result; @@ -1533,7 +1533,7 @@ static int snd_pcm_drop(struct snd_pcm_substream *substream) snd_power_lock(card); if (runtime->status->state == SNDRV_PCM_STATE_SUSPENDED) { - result = snd_power_wait(card, SNDRV_CTL_POWER_D0, substream->ffile); + result = snd_power_wait(card, SNDRV_CTL_POWER_D0); if (result < 0) goto _unlock; } -- cgit v1.2.3-59-g8ed1b From 1841f613fd2e73f09d3fa2beeccf2f8d978ec2db Mon Sep 17 00:00:00 2001 From: Martin Langer Date: Mon, 27 Mar 2006 12:41:01 +0200 Subject: [ALSA] Add snd-miro driver Added snd-miro driver for miroSOUND PCM by Martin Langer. Signed-off-by: Takashi Iwai --- Documentation/sound/alsa/ALSA-Configuration.txt | 17 + sound/isa/Kconfig | 14 + sound/isa/opti9xx/Makefile | 2 + sound/isa/opti9xx/miro.c | 1457 +++++++++++++++++++++++ sound/isa/opti9xx/miro.h | 73 ++ 5 files changed, 1563 insertions(+) create mode 100644 sound/isa/opti9xx/miro.c create mode 100644 sound/isa/opti9xx/miro.h diff --git a/Documentation/sound/alsa/ALSA-Configuration.txt b/Documentation/sound/alsa/ALSA-Configuration.txt index baf18c6afdd6..1ac20940d8f9 100644 --- a/Documentation/sound/alsa/ALSA-Configuration.txt +++ b/Documentation/sound/alsa/ALSA-Configuration.txt @@ -1014,6 +1014,23 @@ Prior to version 0.9.0rc4 options had a 'snd_' prefix. This was removed. The power-management is supported. + Module snd-miro + --------------- + + Module for Miro soundcards: miroSOUND PCM 1 pro, + miroSOUND PCM 12, + miroSOUND PCM 20 Radio. + + port - Port # (0x530,0x604,0xe80,0xf40) + irq - IRQ # (5,7,9,10,11) + dma1 - 1st dma # (0,1,3) + dma2 - 2nd dma # (0,1) + mpu_port - MPU-401 port # (0x300,0x310,0x320,0x330) + mpu_irq - MPU-401 irq # (5,7,9,10) + fm_port - FM Port # (0x388) + wss - enable WSS mode + ide - enable onboard ide support + Module snd-mixart ----------------- diff --git a/sound/isa/Kconfig b/sound/isa/Kconfig index ff8fef932786..2a1c7334210a 100644 --- a/sound/isa/Kconfig +++ b/sound/isa/Kconfig @@ -292,6 +292,20 @@ config SND_OPTI93X To compile this driver as a module, choose M here: the module will be called snd-opti93x. +config SND_MIRO + tristate "Miro miroSOUND PCM1pro/PCM12/PCM20radio driver" + depends on SND + select SND_OPL4_LIB + select SND_CS4231_LIB + select SND_MPU401_UART + select SND_PCM + help + Say 'Y' or 'M' to include support for Miro miroSOUND PCM1 pro, + miroSOUND PCM12 and miroSOUND PCM20 Radio soundcards. + + To compile this driver as a module, choose M here: the module + will be called snd-miro. + config SND_SB8 tristate "Sound Blaster 1.0/2.0/Pro (8-bit)" depends on SND diff --git a/sound/isa/opti9xx/Makefile b/sound/isa/opti9xx/Makefile index 28c64070cd56..0e41bfd5a403 100644 --- a/sound/isa/opti9xx/Makefile +++ b/sound/isa/opti9xx/Makefile @@ -6,8 +6,10 @@ snd-opti92x-ad1848-objs := opti92x-ad1848.o snd-opti92x-cs4231-objs := opti92x-cs4231.o snd-opti93x-objs := opti93x.o +snd-miro-objs := miro.o # Toplevel Module Dependency obj-$(CONFIG_SND_OPTI92X_AD1848) += snd-opti92x-ad1848.o obj-$(CONFIG_SND_OPTI92X_CS4231) += snd-opti92x-cs4231.o obj-$(CONFIG_SND_OPTI93X) += snd-opti93x.o +obj-$(CONFIG_SND_MIRO) += snd-miro.o diff --git a/sound/isa/opti9xx/miro.c b/sound/isa/opti9xx/miro.c new file mode 100644 index 000000000000..49ba334c0d24 --- /dev/null +++ b/sound/isa/opti9xx/miro.c @@ -0,0 +1,1457 @@ +/* + * ALSA soundcard driver for Miro miroSOUND PCM1 pro + * miroSOUND PCM12 + * miroSOUND PCM20 Radio + * + * Copyright (C) 2004-2005 Martin Langer + * + * Based on OSS ACI and ALSA OPTi9xx drivers + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#define SNDRV_LEGACY_FIND_FREE_IRQ +#define SNDRV_LEGACY_FIND_FREE_DMA +#include +#include "miro.h" + +MODULE_AUTHOR("Martin Langer "); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Miro miroSOUND PCM1 pro, PCM12, PCM20 Radio"); +MODULE_SUPPORTED_DEVICE("{{Miro,miroSOUND PCM1 pro}, " + "{Miro,miroSOUND PCM12}, " + "{Miro,miroSOUND PCM20 Radio}}"); + +static int index = SNDRV_DEFAULT_IDX1; /* Index 0-MAX */ +static char *id = SNDRV_DEFAULT_STR1; /* ID for this card */ +static long port = SNDRV_DEFAULT_PORT1; /* 0x530,0xe80,0xf40,0x604 */ +static long mpu_port = SNDRV_DEFAULT_PORT1; /* 0x300,0x310,0x320,0x330 */ +static long fm_port = SNDRV_DEFAULT_PORT1; /* 0x388 */ +static int irq = SNDRV_DEFAULT_IRQ1; /* 5,7,9,10,11 */ +static int mpu_irq = SNDRV_DEFAULT_IRQ1; /* 5,7,9,10 */ +static int dma1 = SNDRV_DEFAULT_DMA1; /* 0,1,3 */ +static int dma2 = SNDRV_DEFAULT_DMA1; /* 0,1,3 */ +static int wss; +static int ide; + +module_param(index, int, 0444); +MODULE_PARM_DESC(index, "Index value for miro soundcard."); +module_param(id, charp, 0444); +MODULE_PARM_DESC(id, "ID string for miro soundcard."); +module_param(port, long, 0444); +MODULE_PARM_DESC(port, "WSS port # for miro driver."); +module_param(mpu_port, long, 0444); +MODULE_PARM_DESC(mpu_port, "MPU-401 port # for miro driver."); +module_param(fm_port, long, 0444); +MODULE_PARM_DESC(fm_port, "FM Port # for miro driver."); +module_param(irq, int, 0444); +MODULE_PARM_DESC(irq, "WSS irq # for miro driver."); +module_param(mpu_irq, int, 0444); +MODULE_PARM_DESC(mpu_irq, "MPU-401 irq # for miro driver."); +module_param(dma1, int, 0444); +MODULE_PARM_DESC(dma1, "1st dma # for miro driver."); +module_param(dma2, int, 0444); +MODULE_PARM_DESC(dma2, "2nd dma # for miro driver."); +module_param(wss, int, 0444); +MODULE_PARM_DESC(wss, "wss mode"); +module_param(ide, int, 0444); +MODULE_PARM_DESC(ide, "enable ide port"); + +#define OPTi9XX_HW_DETECT 0 +#define OPTi9XX_HW_82C928 1 +#define OPTi9XX_HW_82C929 2 +#define OPTi9XX_HW_82C924 3 +#define OPTi9XX_HW_82C925 4 +#define OPTi9XX_HW_82C930 5 +#define OPTi9XX_HW_82C931 6 +#define OPTi9XX_HW_82C933 7 +#define OPTi9XX_HW_LAST OPTi9XX_HW_82C933 + +#define OPTi9XX_MC_REG(n) n + + +struct snd_miro { + unsigned short hardware; + unsigned char password; + char name[7]; + + struct resource *res_mc_base; + struct resource *res_aci_port; + + unsigned long mc_base; + unsigned long mc_base_size; + unsigned long pwd_reg; + + spinlock_t lock; + struct snd_card *card; + struct snd_pcm *pcm; + + long wss_base; + int irq; + int dma1; + int dma2; + + long fm_port; + + long mpu_port; + int mpu_irq; + + unsigned long aci_port; + int aci_vendor; + int aci_product; + int aci_version; + int aci_amp; + int aci_preamp; + int aci_solomode; + + struct mutex aci_mutex; +}; + +static void snd_miro_proc_init(struct snd_miro * miro); + +#define DRIVER_NAME "snd-miro" + +static struct platform_device *device; + +static char * snd_opti9xx_names[] = { + "unkown", + "82C928", "82C929", + "82C924", "82C925", + "82C930", "82C931", "82C933" +}; + +/* + * ACI control + */ + +static int aci_busy_wait(struct snd_miro * miro) +{ + long timeout; + unsigned char byte; + + for (timeout = 1; timeout <= ACI_MINTIME+30; timeout++) { + if (((byte=inb(miro->aci_port + ACI_REG_BUSY)) & 1) == 0) { + if (timeout >= ACI_MINTIME) + snd_printd("aci ready in round %ld.\n", + timeout-ACI_MINTIME); + return byte; + } + if (timeout >= ACI_MINTIME) { + long out=10*HZ; + switch (timeout-ACI_MINTIME) { + case 0 ... 9: + out /= 10; + case 10 ... 19: + out /= 10; + case 20 ... 30: + out /= 10; + default: + set_current_state(TASK_UNINTERRUPTIBLE); + schedule_timeout(out); + break; + } + } + } + snd_printk(KERN_ERR "aci_busy_wait() time out\n"); + return -EBUSY; +} + +static inline int aci_write(struct snd_miro * miro, unsigned char byte) +{ + if (aci_busy_wait(miro) >= 0) { + outb(byte, miro->aci_port + ACI_REG_COMMAND); + return 0; + } else { + snd_printk(KERN_ERR "aci busy, aci_write(0x%x) stopped.\n", byte); + return -EBUSY; + } +} + +static inline int aci_read(struct snd_miro * miro) +{ + unsigned char byte; + + if (aci_busy_wait(miro) >= 0) { + byte=inb(miro->aci_port + ACI_REG_STATUS); + return byte; + } else { + snd_printk(KERN_ERR "aci busy, aci_read() stopped.\n"); + return -EBUSY; + } +} + +static int aci_cmd(struct snd_miro * miro, int write1, int write2, int write3) +{ + int write[] = {write1, write2, write3}; + int value, i; + + if (mutex_lock_interruptible(&miro->aci_mutex)) + return -EINTR; + + for (i=0; i<3; i++) { + if (write[i]< 0 || write[i] > 255) + break; + else { + value = aci_write(miro, write[i]); + if (value < 0) + goto out; + } + } + + value = aci_read(miro); + +out: mutex_unlock(&miro->aci_mutex); + return value; +} + +static int aci_getvalue(struct snd_miro * miro, unsigned char index) +{ + return aci_cmd(miro, ACI_STATUS, index, -1); +} + +static int aci_setvalue(struct snd_miro * miro, unsigned char index, int value) +{ + return aci_cmd(miro, index, value, -1); +} + +/* + * MIXER part + */ + +static int snd_miro_info_capture(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; + uinfo->count = 1; + + return 0; +} + +static int snd_miro_get_capture(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_miro *miro = snd_kcontrol_chip(kcontrol); + int value; + + if ((value = aci_getvalue(miro, ACI_S_GENERAL)) < 0) { + snd_printk(KERN_ERR "snd_miro_get_capture() failed: %d\n", value); + return value; + } + + ucontrol->value.integer.value[0] = value & 0x20; + + return 0; +} + +static int snd_miro_put_capture(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_miro *miro = snd_kcontrol_chip(kcontrol); + int change, value, error; + + value = !(ucontrol->value.integer.value[0]); + + if ((error = aci_setvalue(miro, ACI_SET_SOLOMODE, value)) < 0) { + snd_printk(KERN_ERR "snd_miro_put_capture() failed: %d\n", error); + return error; + } + + change = (value != miro->aci_solomode); + miro->aci_solomode = value; + + return change; +} + +static int snd_miro_info_preamp(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 3; + + return 0; +} + +static int snd_miro_get_preamp(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_miro *miro = snd_kcontrol_chip(kcontrol); + int value; + + if (miro->aci_version <= 176) { + + /* + OSS says it's not readable with versions < 176. + But it doesn't work on my card, + which is a PCM12 with aci_version = 176. + */ + + ucontrol->value.integer.value[0] = miro->aci_preamp; + return 0; + } + + if ((value = aci_getvalue(miro, ACI_GET_PREAMP)) < 0) { + snd_printk(KERN_ERR "snd_miro_get_preamp() failed: %d\n", value); + return value; + } + + ucontrol->value.integer.value[0] = value; + + return 0; +} + +static int snd_miro_put_preamp(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_miro *miro = snd_kcontrol_chip(kcontrol); + int error, value, change; + + value = ucontrol->value.integer.value[0]; + + if ((error = aci_setvalue(miro, ACI_SET_PREAMP, value)) < 0) { + snd_printk(KERN_ERR "snd_miro_put_preamp() failed: %d\n", error); + return error; + } + + change = (value != miro->aci_preamp); + miro->aci_preamp = value; + + return change; +} + +static int snd_miro_info_amp(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; + uinfo->count = 1; + + return 0; +} + +static int snd_miro_get_amp(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_miro *miro = snd_kcontrol_chip(kcontrol); + ucontrol->value.integer.value[0] = miro->aci_amp; + + return 0; +} + +static int snd_miro_put_amp(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_miro *miro = snd_kcontrol_chip(kcontrol); + int error, value, change; + + value = ucontrol->value.integer.value[0]; + + if ((error = aci_setvalue(miro, ACI_SET_POWERAMP, value)) < 0) { + snd_printk(KERN_ERR "snd_miro_put_amp() to %d failed: %d\n", value, error); + return error; + } + + change = (value != miro->aci_amp); + miro->aci_amp = value; + + return change; +} + +#define MIRO_DOUBLE(ctl_name, ctl_index, get_right_reg, set_right_reg) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .name = ctl_name, \ + .index = ctl_index, \ + .info = snd_miro_info_double, \ + .get = snd_miro_get_double, \ + .put = snd_miro_put_double, \ + .private_value = get_right_reg | (set_right_reg << 8) \ +} + +static int snd_miro_info_double(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + int reg = kcontrol->private_value & 0xff; + + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 2; + + if ((reg >= ACI_GET_EQ1) && (reg <= ACI_GET_EQ7)) { + + /* equalizer elements */ + + uinfo->value.integer.min = - 0x7f; + uinfo->value.integer.max = 0x7f; + } else { + + /* non-equalizer elements */ + + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 0x20; + } + + return 0; +} + +static int snd_miro_get_double(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *uinfo) +{ + struct snd_miro *miro = snd_kcontrol_chip(kcontrol); + int left_val, right_val; + + int right_reg = kcontrol->private_value & 0xff; + int left_reg = right_reg + 1; + + if ((right_val = aci_getvalue(miro, right_reg)) < 0) { + snd_printk(KERN_ERR "aci_getvalue(%d) failed: %d\n", right_reg, right_val); + return right_val; + } + + if ((left_val = aci_getvalue(miro, left_reg)) < 0) { + snd_printk(KERN_ERR "aci_getvalue(%d) failed: %d\n", left_reg, left_val); + return left_val; + } + + if ((right_reg >= ACI_GET_EQ1) && (right_reg <= ACI_GET_EQ7)) { + + /* equalizer elements */ + + if (left_val < 0x80) { + uinfo->value.integer.value[0] = left_val; + } else { + uinfo->value.integer.value[0] = 0x80 - left_val; + } + + if (right_val < 0x80) { + uinfo->value.integer.value[1] = right_val; + } else { + uinfo->value.integer.value[1] = 0x80 - right_val; + } + + } else { + + /* non-equalizer elements */ + + uinfo->value.integer.value[0] = 0x20 - left_val; + uinfo->value.integer.value[1] = 0x20 - right_val; + } + + return 0; +} + +static int snd_miro_put_double(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_miro *miro = snd_kcontrol_chip(kcontrol); + int left, right, left_old, right_old; + int setreg_left, setreg_right, getreg_left, getreg_right; + int change, error; + + left = ucontrol->value.integer.value[0]; + right = ucontrol->value.integer.value[1]; + + setreg_right = (kcontrol->private_value >> 8) & 0xff; + if (setreg_right == ACI_SET_MASTER) { + setreg_left = setreg_right + 1; + } else { + setreg_left = setreg_right + 8; + } + + getreg_right = kcontrol->private_value & 0xff; + getreg_left = getreg_right + 1; + + if ((left_old = aci_getvalue(miro, getreg_left)) < 0) { + snd_printk(KERN_ERR "aci_getvalue(%d) failed: %d\n", getreg_left, left_old); + return left_old; + } + + if ((right_old = aci_getvalue(miro, getreg_right)) < 0) { + snd_printk(KERN_ERR "aci_getvalue(%d) failed: %d\n", getreg_right, right_old); + return right_old; + } + + if ((getreg_right >= ACI_GET_EQ1) && (getreg_right <= ACI_GET_EQ7)) { + + /* equalizer elements */ + + if (left_old > 0x80) + left_old = 0x80 - left_old; + if (right_old > 0x80) + right_old = 0x80 - right_old; + + if (left >= 0) { + if ((error = aci_setvalue(miro, setreg_left, left)) < 0) { + snd_printk(KERN_ERR "aci_setvalue(%d) failed: %d\n", + left, error); + return error; + } + } else { + if ((error = aci_setvalue(miro, setreg_left, 0x80 - left)) < 0) { + snd_printk(KERN_ERR "aci_setvalue(%d) failed: %d\n", + 0x80 - left, error); + return error; + } + } + + if (right >= 0) { + if ((error = aci_setvalue(miro, setreg_right, right)) < 0) { + snd_printk(KERN_ERR "aci_setvalue(%d) failed: %d\n", + right, error); + return error; + } + } else { + if ((error = aci_setvalue(miro, setreg_right, 0x80 - right)) < 0) { + snd_printk(KERN_ERR "aci_setvalue(%d) failed: %d\n", + 0x80 - right, error); + return error; + } + } + + } else { + + /* non-equalizer elements */ + + left_old = 0x20 - left_old; + right_old = 0x20 - right_old; + + if ((error = aci_setvalue(miro, setreg_left, 0x20 - left)) < 0) { + snd_printk(KERN_ERR "aci_setvalue(%d) failed: %d\n", + 0x20 - left, error); + return error; + } + if ((error = aci_setvalue(miro, setreg_right, 0x20 - right)) < 0) { + snd_printk(KERN_ERR "aci_setvalue(%d) failed: %d\n", + 0x20 - right, error); + return error; + } + } + + change = (left != left_old) || (right != right_old); + + return change; +} + +static struct snd_kcontrol_new snd_miro_controls[] = { +MIRO_DOUBLE("Master Playback Volume", 0, ACI_GET_MASTER, ACI_SET_MASTER), +MIRO_DOUBLE("Mic Playback Volume", 1, ACI_GET_MIC, ACI_SET_MIC), +MIRO_DOUBLE("Line Playback Volume", 1, ACI_GET_LINE, ACI_SET_LINE), +MIRO_DOUBLE("CD Playback Volume", 0, ACI_GET_CD, ACI_SET_CD), +MIRO_DOUBLE("Synth Playback Volume", 0, ACI_GET_SYNTH, ACI_SET_SYNTH), +MIRO_DOUBLE("PCM Playback Volume", 1, ACI_GET_PCM, ACI_SET_PCM), +MIRO_DOUBLE("Aux Playback Volume", 2, ACI_GET_LINE2, ACI_SET_LINE2), +}; + +/* Equalizer with seven bands (only PCM20) + from -12dB up to +12dB on each band */ +static struct snd_kcontrol_new snd_miro_eq_controls[] = { +MIRO_DOUBLE("Tone Control - 28 Hz", 0, ACI_GET_EQ1, ACI_SET_EQ1), +MIRO_DOUBLE("Tone Control - 160 Hz", 0, ACI_GET_EQ2, ACI_SET_EQ2), +MIRO_DOUBLE("Tone Control - 400 Hz", 0, ACI_GET_EQ3, ACI_SET_EQ3), +MIRO_DOUBLE("Tone Control - 1 kHz", 0, ACI_GET_EQ4, ACI_SET_EQ4), +MIRO_DOUBLE("Tone Control - 2.5 kHz", 0, ACI_GET_EQ5, ACI_SET_EQ5), +MIRO_DOUBLE("Tone Control - 6.3 kHz", 0, ACI_GET_EQ6, ACI_SET_EQ6), +MIRO_DOUBLE("Tone Control - 16 kHz", 0, ACI_GET_EQ7, ACI_SET_EQ7), +}; + +static struct snd_kcontrol_new snd_miro_radio_control[] = { +MIRO_DOUBLE("Radio Playback Volume", 0, ACI_GET_LINE1, ACI_SET_LINE1), +}; + +static struct snd_kcontrol_new snd_miro_line_control[] = { +MIRO_DOUBLE("Line Playback Volume", 2, ACI_GET_LINE1, ACI_SET_LINE1), +}; + +static struct snd_kcontrol_new snd_miro_preamp_control[] = { +{ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Mic Boost", + .index = 1, + .info = snd_miro_info_preamp, + .get = snd_miro_get_preamp, + .put = snd_miro_put_preamp, +}}; + +static struct snd_kcontrol_new snd_miro_amp_control[] = { +{ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Line Boost", + .index = 0, + .info = snd_miro_info_amp, + .get = snd_miro_get_amp, + .put = snd_miro_put_amp, +}}; + +static struct snd_kcontrol_new snd_miro_capture_control[] = { +{ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "PCM Capture Switch", + .index = 0, + .info = snd_miro_info_capture, + .get = snd_miro_get_capture, + .put = snd_miro_put_capture, +}}; + +static unsigned char aci_init_values[][2] __initdata = { + { ACI_SET_MUTE, 0x00 }, + { ACI_SET_POWERAMP, 0x00 }, + { ACI_SET_PREAMP, 0x00 }, + { ACI_SET_SOLOMODE, 0x00 }, + { ACI_SET_MIC + 0, 0x20 }, + { ACI_SET_MIC + 8, 0x20 }, + { ACI_SET_LINE + 0, 0x20 }, + { ACI_SET_LINE + 8, 0x20 }, + { ACI_SET_CD + 0, 0x20 }, + { ACI_SET_CD + 8, 0x20 }, + { ACI_SET_PCM + 0, 0x20 }, + { ACI_SET_PCM + 8, 0x20 }, + { ACI_SET_LINE1 + 0, 0x20 }, + { ACI_SET_LINE1 + 8, 0x20 }, + { ACI_SET_LINE2 + 0, 0x20 }, + { ACI_SET_LINE2 + 8, 0x20 }, + { ACI_SET_SYNTH + 0, 0x20 }, + { ACI_SET_SYNTH + 8, 0x20 }, + { ACI_SET_MASTER + 0, 0x20 }, + { ACI_SET_MASTER + 1, 0x20 }, +}; + +static int __init snd_set_aci_init_values(struct snd_miro *miro) +{ + int idx, error; + + /* enable WSS on PCM1 */ + + if ((miro->aci_product == 'A') && wss) { + if ((error = aci_setvalue(miro, ACI_SET_WSS, wss)) < 0) { + snd_printk(KERN_ERR "enabling WSS mode failed\n"); + return error; + } + } + + /* enable IDE port */ + + if (ide) { + if ((error = aci_setvalue(miro, ACI_SET_IDE, ide)) < 0) { + snd_printk(KERN_ERR "enabling IDE port failed\n"); + return error; + } + } + + /* set common aci values */ + + for (idx = 0; idx < ARRAY_SIZE(aci_init_values); idx++) + if ((error = aci_setvalue(miro, aci_init_values[idx][0], + aci_init_values[idx][1])) < 0) { + snd_printk(KERN_ERR "aci_setvalue(%d) failed: %d\n", + aci_init_values[idx][0], error); + return error; + } + + miro->aci_amp = 0; + miro->aci_preamp = 0; + miro->aci_solomode = 1; + + return 0; +} + +static int snd_miro_mixer(struct snd_miro *miro) +{ + struct snd_card *card; + unsigned int idx; + int err; + + snd_assert(miro != NULL && miro->card != NULL, return -EINVAL); + + card = miro->card; + + switch (miro->hardware) { + case OPTi9XX_HW_82C924: + strcpy(card->mixername, "ACI & OPTi924"); + break; + case OPTi9XX_HW_82C929: + strcpy(card->mixername, "ACI & OPTi929"); + break; + default: + snd_BUG(); + break; + } + + for (idx = 0; idx < ARRAY_SIZE(snd_miro_controls); idx++) { + if ((err = snd_ctl_add(card, snd_ctl_new1(&snd_miro_controls[idx], miro))) < 0) + return err; + } + + if ((miro->aci_product == 'A') || (miro->aci_product == 'B')) { + /* PCM1/PCM12 with power-amp and Line 2 */ + if ((err = snd_ctl_add(card, snd_ctl_new1(&snd_miro_line_control[0], miro))) < 0) + return err; + if ((err = snd_ctl_add(card, snd_ctl_new1(&snd_miro_amp_control[0], miro))) < 0) + return err; + } + + if ((miro->aci_product == 'B') || (miro->aci_product == 'C')) { + /* PCM12/PCM20 with mic-preamp */ + if ((err = snd_ctl_add(card, snd_ctl_new1(&snd_miro_preamp_control[0], miro))) < 0) + return err; + if (miro->aci_version >= 176) + if ((err = snd_ctl_add(card, snd_ctl_new1(&snd_miro_capture_control[0], miro))) < 0) + return err; + } + + if (miro->aci_product == 'C') { + /* PCM20 with radio and 7 band equalizer */ + if ((err = snd_ctl_add(card, snd_ctl_new1(&snd_miro_radio_control[0], miro))) < 0) + return err; + for (idx = 0; idx < ARRAY_SIZE(snd_miro_eq_controls); idx++) { + if ((err = snd_ctl_add(card, snd_ctl_new1(&snd_miro_eq_controls[idx], miro))) < 0) + return err; + } + } + + return 0; +} + +static long snd_legacy_find_free_ioport(long *port_table, long size) +{ + while (*port_table != -1) { + struct resource *res; + if ((res = request_region(*port_table, size, + "ALSA test")) != NULL) { + release_resource(res); + kfree_nocheck(res); + return *port_table; + } + port_table++; + } + return -1; +} + +static int __init snd_miro_init(struct snd_miro *chip, unsigned short hardware) +{ + static int opti9xx_mc_size[] = {7, 7, 10, 10, 2, 2, 2}; + + chip->hardware = hardware; + strcpy(chip->name, snd_opti9xx_names[hardware]); + + chip->mc_base_size = opti9xx_mc_size[hardware]; + + spin_lock_init(&chip->lock); + + chip->wss_base = -1; + chip->irq = -1; + chip->dma1 = -1; + chip->dma2 = -1; + chip->fm_port = -1; + chip->mpu_port = -1; + chip->mpu_irq = -1; + + switch (hardware) { + case OPTi9XX_HW_82C929: + chip->mc_base = 0xf8c; + chip->password = 0xe3; + chip->pwd_reg = 3; + break; + + case OPTi9XX_HW_82C924: + chip->mc_base = 0xf8c; + chip->password = 0xe5; + chip->pwd_reg = 3; + break; + + default: + snd_printk(KERN_ERR "sorry, no support for %d\n", hardware); + return -ENODEV; + } + + return 0; +} + +static unsigned char snd_miro_read(struct snd_miro *chip, + unsigned char reg) +{ + unsigned long flags; + unsigned char retval = 0xff; + + spin_lock_irqsave(&chip->lock, flags); + outb(chip->password, chip->mc_base + chip->pwd_reg); + + switch (chip->hardware) { + case OPTi9XX_HW_82C924: + if (reg > 7) { + outb(reg, chip->mc_base + 8); + outb(chip->password, chip->mc_base + chip->pwd_reg); + retval = inb(chip->mc_base + 9); + break; + } + + case OPTi9XX_HW_82C929: + retval = inb(chip->mc_base + reg); + break; + + default: + snd_printk(KERN_ERR "sorry, no support for %d\n", chip->hardware); + } + + spin_unlock_irqrestore(&chip->lock, flags); + return retval; +} + +static void snd_miro_write(struct snd_miro *chip, unsigned char reg, + unsigned char value) +{ + unsigned long flags; + + spin_lock_irqsave(&chip->lock, flags); + outb(chip->password, chip->mc_base + chip->pwd_reg); + + switch (chip->hardware) { + case OPTi9XX_HW_82C924: + if (reg > 7) { + outb(reg, chip->mc_base + 8); + outb(chip->password, chip->mc_base + chip->pwd_reg); + outb(value, chip->mc_base + 9); + break; + } + + case OPTi9XX_HW_82C929: + outb(value, chip->mc_base + reg); + break; + + default: + snd_printk(KERN_ERR "sorry, no support for %d\n", chip->hardware); + } + + spin_unlock_irqrestore(&chip->lock, flags); +} + + +#define snd_miro_write_mask(chip, reg, value, mask) \ + snd_miro_write(chip, reg, \ + (snd_miro_read(chip, reg) & ~(mask)) | ((value) & (mask))) + +/* + * Proc Interface + */ + +static void snd_miro_proc_read(struct snd_info_entry * entry, + struct snd_info_buffer *buffer) +{ + struct snd_miro *miro = (struct snd_miro *) entry->private_data; + char* model = "unknown"; + + /* miroSOUND PCM1 pro, early PCM12 */ + + if ((miro->hardware == OPTi9XX_HW_82C929) && + (miro->aci_vendor == 'm') && + (miro->aci_product == 'A')) { + switch(miro->aci_version) { + case 3: + model = "miroSOUND PCM1 pro"; + break; + default: + model = "miroSOUND PCM1 pro / (early) PCM12"; + break; + } + } + + /* miroSOUND PCM12, PCM12 (Rev. E), PCM12 pnp */ + + if ((miro->hardware == OPTi9XX_HW_82C924) && + (miro->aci_vendor == 'm') && + (miro->aci_product == 'B')) { + switch(miro->aci_version) { + case 4: + model = "miroSOUND PCM12"; + break; + case 176: + model = "miroSOUND PCM12 (Rev. E)"; + break; + default: + model = "miroSOUND PCM12 / PCM12 pnp"; + break; + } + } + + /* miroSOUND PCM20 radio */ + + if ((miro->hardware == OPTi9XX_HW_82C924) && + (miro->aci_vendor == 'm') && + (miro->aci_product == 'C')) { + switch(miro->aci_version) { + case 7: + model = "miroSOUND PCM20 radio (Rev. E)"; + break; + default: + model = "miroSOUND PCM20 radio"; + break; + } + } + + snd_iprintf(buffer, "\nGeneral information:\n"); + snd_iprintf(buffer, " model : %s\n", model); + snd_iprintf(buffer, " opti : %s\n", miro->name); + snd_iprintf(buffer, " codec : %s\n", miro->pcm->name); + snd_iprintf(buffer, " port : 0x%lx\n", miro->wss_base); + snd_iprintf(buffer, " irq : %d\n", miro->irq); + snd_iprintf(buffer, " dma : %d,%d\n\n", miro->dma1, miro->dma2); + + snd_iprintf(buffer, "MPU-401:\n"); + snd_iprintf(buffer, " port : 0x%lx\n", miro->mpu_port); + snd_iprintf(buffer, " irq : %d\n\n", miro->mpu_irq); + + snd_iprintf(buffer, "ACI information:\n"); + snd_iprintf(buffer, " vendor : "); + switch(miro->aci_vendor) { + case 'm': + snd_iprintf(buffer, "Miro\n"); + break; + default: + snd_iprintf(buffer, "unknown (0x%x)\n", miro->aci_vendor); + break; + } + + snd_iprintf(buffer, " product : "); + switch(miro->aci_product) { + case 'A': + snd_iprintf(buffer, "miroSOUND PCM1 pro / (early) PCM12\n"); + break; + case 'B': + snd_iprintf(buffer, "miroSOUND PCM12\n"); + break; + case 'C': + snd_iprintf(buffer, "miroSOUND PCM20 radio\n"); + break; + default: + snd_iprintf(buffer, "unknown (0x%x)\n", miro->aci_product); + break; + } + + snd_iprintf(buffer, " firmware: %d (0x%x)\n", + miro->aci_version, miro->aci_version); + snd_iprintf(buffer, " port : 0x%lx-0x%lx\n", + miro->aci_port, miro->aci_port+2); + snd_iprintf(buffer, " wss : 0x%x\n", wss); + snd_iprintf(buffer, " ide : 0x%x\n", ide); + snd_iprintf(buffer, " solomode: 0x%x\n", miro->aci_solomode); + snd_iprintf(buffer, " amp : 0x%x\n", miro->aci_amp); + snd_iprintf(buffer, " preamp : 0x%x\n", miro->aci_preamp); +} + +static void __init snd_miro_proc_init(struct snd_miro * miro) +{ + struct snd_info_entry *entry; + + if (! snd_card_proc_new(miro->card, "miro", &entry)) + snd_info_set_text_ops(entry, miro, 1024, snd_miro_proc_read); +} + +/* + * Init + */ + +static int __init snd_miro_configure(struct snd_miro *chip) +{ + unsigned char wss_base_bits; + unsigned char irq_bits; + unsigned char dma_bits; + unsigned char mpu_port_bits = 0; + unsigned char mpu_irq_bits; + unsigned long flags; + + switch (chip->hardware) { + case OPTi9XX_HW_82C924: + snd_miro_write_mask(chip, OPTi9XX_MC_REG(6), 0x02, 0x02); + snd_miro_write_mask(chip, OPTi9XX_MC_REG(1), 0x80, 0x80); + snd_miro_write_mask(chip, OPTi9XX_MC_REG(2), 0x20, 0x20); /* OPL4 */ + snd_miro_write_mask(chip, OPTi9XX_MC_REG(3), 0xf0, 0xff); + snd_miro_write_mask(chip, OPTi9XX_MC_REG(5), 0x02, 0x02); + break; + case OPTi9XX_HW_82C929: + /* untested init commands for OPTi929 */ + snd_miro_write_mask(chip, OPTi9XX_MC_REG(1), 0x80, 0x80); + snd_miro_write_mask(chip, OPTi9XX_MC_REG(2), 0x20, 0x20); /* OPL4 */ + snd_miro_write_mask(chip, OPTi9XX_MC_REG(4), 0x00, 0x0c); + snd_miro_write_mask(chip, OPTi9XX_MC_REG(5), 0x02, 0x02); + break; + default: + snd_printk(KERN_ERR "chip %d not supported\n", chip->hardware); + return -EINVAL; + } + + switch (chip->wss_base) { + case 0x530: + wss_base_bits = 0x00; + break; + case 0x604: + wss_base_bits = 0x03; + break; + case 0xe80: + wss_base_bits = 0x01; + break; + case 0xf40: + wss_base_bits = 0x02; + break; + default: + snd_printk(KERN_ERR "WSS port 0x%lx not valid\n", chip->wss_base); + goto __skip_base; + } + snd_miro_write_mask(chip, OPTi9XX_MC_REG(1), wss_base_bits << 4, 0x30); + +__skip_base: + switch (chip->irq) { + case 5: + irq_bits = 0x05; + break; + case 7: + irq_bits = 0x01; + break; + case 9: + irq_bits = 0x02; + break; + case 10: + irq_bits = 0x03; + break; + case 11: + irq_bits = 0x04; + break; + default: + snd_printk(KERN_ERR "WSS irq # %d not valid\n", chip->irq); + goto __skip_resources; + } + + switch (chip->dma1) { + case 0: + dma_bits = 0x01; + break; + case 1: + dma_bits = 0x02; + break; + case 3: + dma_bits = 0x03; + break; + default: + snd_printk(KERN_ERR "WSS dma1 # %d not valid\n", chip->dma1); + goto __skip_resources; + } + + if (chip->dma1 == chip->dma2) { + snd_printk(KERN_ERR "don't want to share dmas\n"); + return -EBUSY; + } + + switch (chip->dma2) { + case 0: + case 1: + break; + default: + snd_printk(KERN_ERR "WSS dma2 # %d not valid\n", chip->dma2); + goto __skip_resources; + } + dma_bits |= 0x04; + + spin_lock_irqsave(&chip->lock, flags); + outb(irq_bits << 3 | dma_bits, chip->wss_base); + spin_unlock_irqrestore(&chip->lock, flags); + +__skip_resources: + if (chip->hardware > OPTi9XX_HW_82C928) { + switch (chip->mpu_port) { + case 0: + case -1: + break; + case 0x300: + mpu_port_bits = 0x03; + break; + case 0x310: + mpu_port_bits = 0x02; + break; + case 0x320: + mpu_port_bits = 0x01; + break; + case 0x330: + mpu_port_bits = 0x00; + break; + default: + snd_printk(KERN_ERR "MPU-401 port 0x%lx not valid\n", + chip->mpu_port); + goto __skip_mpu; + } + + switch (chip->mpu_irq) { + case 5: + mpu_irq_bits = 0x02; + break; + case 7: + mpu_irq_bits = 0x03; + break; + case 9: + mpu_irq_bits = 0x00; + break; + case 10: + mpu_irq_bits = 0x01; + break; + default: + snd_printk(KERN_ERR "MPU-401 irq # %d not valid\n", + chip->mpu_irq); + goto __skip_mpu; + } + + snd_miro_write_mask(chip, OPTi9XX_MC_REG(6), + (chip->mpu_port <= 0) ? 0x00 : + 0x80 | mpu_port_bits << 5 | mpu_irq_bits << 3, + 0xf8); + } +__skip_mpu: + + return 0; +} + +static int __init snd_card_miro_detect(struct snd_card *card, struct snd_miro *chip) +{ + int i, err; + unsigned char value; + + for (i = OPTi9XX_HW_82C929; i <= OPTi9XX_HW_82C924; i++) { + + if ((err = snd_miro_init(chip, i)) < 0) + return err; + + if ((chip->res_mc_base = request_region(chip->mc_base, chip->mc_base_size, "OPTi9xx MC")) == NULL) + continue; + + value = snd_miro_read(chip, OPTi9XX_MC_REG(1)); + if ((value != 0xff) && (value != inb(chip->mc_base + 1))) + if (value == snd_miro_read(chip, OPTi9XX_MC_REG(1))) + return 1; + + release_resource(chip->res_mc_base); + kfree_nocheck(chip->res_mc_base); + chip->res_mc_base = NULL; + + } + + return -ENODEV; +} + +static int __init snd_card_miro_aci_detect(struct snd_card *card, struct snd_miro * miro) +{ + unsigned char regval; + int i; + + mutex_init(&miro->aci_mutex); + + /* get ACI port from OPTi9xx MC 4 */ + + miro->mc_base = 0xf8c; + regval=inb(miro->mc_base + 4); + miro->aci_port = (regval & 0x10) ? 0x344: 0x354; + + if ((miro->res_aci_port = request_region(miro->aci_port, 3, "miro aci")) == NULL) { + snd_printk(KERN_ERR "aci i/o area 0x%lx-0x%lx already used.\n", + miro->aci_port, miro->aci_port+2); + return -ENOMEM; + } + + /* force ACI into a known state */ + for (i = 0; i < 3; i++) + if (aci_cmd(miro, ACI_ERROR_OP, -1, -1) < 0) { + snd_card_free(card); + snd_printk(KERN_ERR "can't force aci into known state.\n"); + return -ENXIO; + } + + if ((miro->aci_vendor=aci_cmd(miro, ACI_READ_IDCODE, -1, -1)) < 0 || + (miro->aci_product=aci_cmd(miro, ACI_READ_IDCODE, -1, -1)) < 0) { + snd_card_free(card); + snd_printk(KERN_ERR "can't read aci id on 0x%lx.\n", miro->aci_port); + return -ENXIO; + } + + if ((miro->aci_version=aci_cmd(miro, ACI_READ_VERSION, -1, -1)) < 0) { + snd_card_free(card); + snd_printk(KERN_ERR "can't read aci version on 0x%lx.\n", + miro->aci_port); + return -ENXIO; + } + + if (aci_cmd(miro, ACI_INIT, -1, -1) < 0 || + aci_cmd(miro, ACI_ERROR_OP, ACI_ERROR_OP, ACI_ERROR_OP) < 0 || + aci_cmd(miro, ACI_ERROR_OP, ACI_ERROR_OP, ACI_ERROR_OP) < 0) { + snd_printk(KERN_ERR "can't initialize aci.\n"); + return -ENXIO; + } + + return 0; +} + +static void snd_card_miro_free(struct snd_card *card) +{ + struct snd_miro *miro = card->private_data; + + release_and_free_resource(miro->res_aci_port); + release_and_free_resource(miro->res_mc_base); +} + +static int __init snd_miro_probe(struct platform_device *devptr) +{ + static long possible_ports[] = {0x530, 0xe80, 0xf40, 0x604, -1}; + static long possible_mpu_ports[] = {0x330, 0x300, 0x310, 0x320, -1}; + static int possible_irqs[] = {11, 9, 10, 7, -1}; + static int possible_mpu_irqs[] = {10, 5, 9, 7, -1}; + static int possible_dma1s[] = {3, 1, 0, -1}; + static int possible_dma2s[][2] = {{1,-1}, {0,-1}, {-1,-1}, {0,-1}}; + + int error; + struct snd_miro *miro; + struct snd_cs4231 *codec; + struct snd_timer *timer; + struct snd_card *card; + struct snd_pcm *pcm; + struct snd_rawmidi *rmidi; + + if (!(card = snd_card_new(index, id, THIS_MODULE, + sizeof(struct snd_miro)))) + return -ENOMEM; + + card->private_free = snd_card_miro_free; + miro = card->private_data; + miro->card = card; + + if ((error = snd_card_miro_aci_detect(card, miro)) < 0) { + snd_card_free(card); + snd_printk(KERN_ERR "unable to detect aci chip\n"); + return -ENODEV; + } + + /* init proc interface */ + snd_miro_proc_init(miro); + + if ((error = snd_card_miro_detect(card, miro)) < 0) { + snd_card_free(card); + snd_printk(KERN_ERR "unable to detect OPTi9xx chip\n"); + return -ENODEV; + } + + if (! miro->res_mc_base && + (miro->res_mc_base = request_region(miro->mc_base, miro->mc_base_size, + "miro (OPTi9xx MC)")) == NULL) { + snd_card_free(card); + snd_printk(KERN_ERR "request for OPTI9xx MC failed\n"); + return -ENOMEM; + } + + miro->wss_base = port; + miro->fm_port = fm_port; + miro->mpu_port = mpu_port; + miro->irq = irq; + miro->mpu_irq = mpu_irq; + miro->dma1 = dma1; + miro->dma2 = dma2; + + if (miro->wss_base == SNDRV_AUTO_PORT) { + if ((miro->wss_base = snd_legacy_find_free_ioport(possible_ports, 4)) < 0) { + snd_card_free(card); + snd_printk(KERN_ERR "unable to find a free WSS port\n"); + return -EBUSY; + } + } + + if (miro->mpu_port == SNDRV_AUTO_PORT) { + if ((miro->mpu_port = snd_legacy_find_free_ioport(possible_mpu_ports, 2)) < 0) { + snd_card_free(card); + snd_printk(KERN_ERR "unable to find a free MPU401 port\n"); + return -EBUSY; + } + } + if (miro->irq == SNDRV_AUTO_IRQ) { + if ((miro->irq = snd_legacy_find_free_irq(possible_irqs)) < 0) { + snd_card_free(card); + snd_printk(KERN_ERR "unable to find a free IRQ\n"); + return -EBUSY; + } + } + if (miro->mpu_irq == SNDRV_AUTO_IRQ) { + if ((miro->mpu_irq = snd_legacy_find_free_irq(possible_mpu_irqs)) < 0) { + snd_card_free(card); + snd_printk(KERN_ERR "unable to find a free MPU401 IRQ\n"); + return -EBUSY; + } + } + if (miro->dma1 == SNDRV_AUTO_DMA) { + if ((miro->dma1 = snd_legacy_find_free_dma(possible_dma1s)) < 0) { + snd_card_free(card); + snd_printk(KERN_ERR "unable to find a free DMA1\n"); + return -EBUSY; + } + } + if (miro->dma2 == SNDRV_AUTO_DMA) { + if ((miro->dma2 = snd_legacy_find_free_dma(possible_dma2s[miro->dma1 % 4])) < 0) { + snd_card_free(card); + snd_printk(KERN_ERR "unable to find a free DMA2\n"); + return -EBUSY; + } + } + + if ((error = snd_miro_configure(miro))) { + snd_card_free(card); + return error; + } + + if ((error = snd_cs4231_create(card, miro->wss_base + 4, -1, + miro->irq, miro->dma1, miro->dma2, + CS4231_HW_AD1845, + 0, + &codec)) < 0) { + snd_card_free(card); + return error; + } + + if ((error = snd_cs4231_pcm(codec, 0, &pcm)) < 0) { + snd_card_free(card); + return error; + } + if ((error = snd_cs4231_mixer(codec)) < 0) { + snd_card_free(card); + return error; + } + if ((error = snd_cs4231_timer(codec, 0, &timer)) < 0) { + snd_card_free(card); + return error; + } + + miro->pcm = pcm; + + if ((error = snd_miro_mixer(miro)) < 0) { + snd_card_free(card); + return error; + } + + if (miro->aci_vendor == 'm') { + /* It looks like a miro sound card. */ + switch (miro->aci_product) { + case 'A': + sprintf(card->shortname, + "miroSOUND PCM1 pro / PCM12"); + break; + case 'B': + sprintf(card->shortname, + "miroSOUND PCM12"); + break; + case 'C': + sprintf(card->shortname, + "miroSOUND PCM20 radio"); + break; + default: + sprintf(card->shortname, + "unknown miro"); + snd_printk(KERN_INFO "unknown miro aci id\n"); + break; + } + } else { + snd_printk(KERN_INFO "found unsupported aci card\n"); + sprintf(card->shortname, "unknown Cardinal Technologies"); + } + + strcpy(card->driver, "miro"); + sprintf(card->longname, "%s: OPTi%s, %s at 0x%lx, irq %d, dma %d&%d", + card->shortname, miro->name, pcm->name, miro->wss_base + 4, + miro->irq, miro->dma1, miro->dma2); + + if (miro->mpu_port <= 0 || miro->mpu_port == SNDRV_AUTO_PORT) + rmidi = NULL; + else + if ((error = snd_mpu401_uart_new(card, 0, MPU401_HW_MPU401, + miro->mpu_port, 0, miro->mpu_irq, SA_INTERRUPT, + &rmidi))) + snd_printk(KERN_WARNING "no MPU-401 device at 0x%lx?\n", miro->mpu_port); + + if (miro->fm_port > 0 && miro->fm_port != SNDRV_AUTO_PORT) { + struct snd_opl3 *opl3 = NULL; + struct snd_opl4 *opl4; + if (snd_opl4_create(card, miro->fm_port, miro->fm_port - 8, + 2, &opl3, &opl4) < 0) + snd_printk(KERN_WARNING "no OPL4 device at 0x%lx\n", miro->fm_port); + } + + if ((error = snd_set_aci_init_values(miro)) < 0) { + snd_card_free(card); + return error; + } + + snd_card_set_dev(card, &devptr->dev); + + if ((error = snd_card_register(card))) { + snd_card_free(card); + return error; + } + + platform_set_drvdata(devptr, card); + return 0; +} + +static int __devexit snd_miro_remove(struct platform_device *devptr) +{ + snd_card_free(platform_get_drvdata(devptr)); + platform_set_drvdata(devptr, NULL); + return 0; +} + +static struct platform_driver snd_miro_driver = { + .probe = snd_miro_probe, + .remove = __devexit_p(snd_miro_remove), + /* FIXME: suspend/resume */ + .driver = { + .name = DRIVER_NAME + }, +}; + +static int __init alsa_card_miro_init(void) +{ + int error; + + if ((error = platform_driver_register(&snd_miro_driver)) < 0) + return error; + device = platform_device_register_simple(DRIVER_NAME, -1, NULL, 0); + if (! IS_ERR(device)) + return 0; +#ifdef MODULE + printk(KERN_ERR "no miro soundcard found\n"); +#endif + platform_driver_unregister(&snd_miro_driver); + return PTR_ERR(device); +} + +static void __exit alsa_card_miro_exit(void) +{ + platform_device_unregister(device); + platform_driver_unregister(&snd_miro_driver); +} + +module_init(alsa_card_miro_init) +module_exit(alsa_card_miro_exit) diff --git a/sound/isa/opti9xx/miro.h b/sound/isa/opti9xx/miro.h new file mode 100644 index 000000000000..6e1385b8e07e --- /dev/null +++ b/sound/isa/opti9xx/miro.h @@ -0,0 +1,73 @@ +#ifndef _MIRO_H_ +#define _MIRO_H_ + +#define ACI_REG_COMMAND 0 /* write register offset */ +#define ACI_REG_STATUS 1 /* read register offset */ +#define ACI_REG_BUSY 2 /* busy register offset */ +#define ACI_REG_RDS 2 /* PCM20: RDS register offset */ +#define ACI_MINTIME 500 /* ACI time out limit */ + +#define ACI_SET_MUTE 0x0d +#define ACI_SET_POWERAMP 0x0f +#define ACI_SET_TUNERMUTE 0xa3 +#define ACI_SET_TUNERMONO 0xa4 +#define ACI_SET_IDE 0xd0 +#define ACI_SET_WSS 0xd1 +#define ACI_SET_SOLOMODE 0xd2 +#define ACI_SET_PREAMP 0x03 +#define ACI_GET_PREAMP 0x21 +#define ACI_WRITE_TUNE 0xa7 +#define ACI_READ_TUNERSTEREO 0xa8 +#define ACI_READ_TUNERSTATION 0xa9 +#define ACI_READ_VERSION 0xf1 +#define ACI_READ_IDCODE 0xf2 +#define ACI_INIT 0xff +#define ACI_STATUS 0xf0 +#define ACI_S_GENERAL 0x00 +#define ACI_ERROR_OP 0xdf + +/* ACI Mixer */ + +/* These are the values for the right channel GET registers. + Add an offset of 0x01 for the left channel register. + (left=right+0x01) */ + +#define ACI_GET_MASTER 0x03 +#define ACI_GET_MIC 0x05 +#define ACI_GET_LINE 0x07 +#define ACI_GET_CD 0x09 +#define ACI_GET_SYNTH 0x0b +#define ACI_GET_PCM 0x0d +#define ACI_GET_LINE1 0x10 /* Radio on PCM20 */ +#define ACI_GET_LINE2 0x12 + +#define ACI_GET_EQ1 0x22 /* from Bass ... */ +#define ACI_GET_EQ2 0x24 +#define ACI_GET_EQ3 0x26 +#define ACI_GET_EQ4 0x28 +#define ACI_GET_EQ5 0x2a +#define ACI_GET_EQ6 0x2c +#define ACI_GET_EQ7 0x2e /* ... to Treble */ + +/* And these are the values for the right channel SET registers. + For left channel access you have to add an offset of 0x08. + MASTER is an exception, which needs an offset of 0x01 */ + +#define ACI_SET_MASTER 0x00 +#define ACI_SET_MIC 0x30 +#define ACI_SET_LINE 0x31 +#define ACI_SET_CD 0x34 +#define ACI_SET_SYNTH 0x33 +#define ACI_SET_PCM 0x32 +#define ACI_SET_LINE1 0x35 /* Radio on PCM20 */ +#define ACI_SET_LINE2 0x36 + +#define ACI_SET_EQ1 0x40 /* from Bass ... */ +#define ACI_SET_EQ2 0x41 +#define ACI_SET_EQ3 0x42 +#define ACI_SET_EQ4 0x43 +#define ACI_SET_EQ5 0x44 +#define ACI_SET_EQ6 0x45 +#define ACI_SET_EQ7 0x46 /* ... to Treble */ + +#endif /* _MIRO_H_ */ -- cgit v1.2.3-59-g8ed1b From 675b4e5981941be5e826ada99b86e65e517b84ce Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Mon, 27 Mar 2006 12:46:34 +0200 Subject: [ALSA] Make CONFIG_SND_CS46XX_NEW_DSP yes as default Removed from CONFIG_EXPERIMENTAL from CONFIG_SND_CS46XX_NEW_DSP, and make default to yes. This option works fine for years. Signed-off-by: Takashi Iwai --- sound/pci/Kconfig | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/sound/pci/Kconfig b/sound/pci/Kconfig index 1e2e19305e38..5517442aae6a 100644 --- a/sound/pci/Kconfig +++ b/sound/pci/Kconfig @@ -195,8 +195,9 @@ config SND_CS46XX will be called snd-cs46xx. config SND_CS46XX_NEW_DSP - bool "Cirrus Logic (Sound Fusion) New DSP support (EXPERIMENTAL)" - depends on SND_CS46XX && EXPERIMENTAL + bool "Cirrus Logic (Sound Fusion) New DSP support" + depends on SND_CS46XX + default y help Say Y here to use a new DSP image for SPDIF and dual codecs. -- cgit v1.2.3-59-g8ed1b From 505cb341ef39c0e75e074d49988a044b66fd4f99 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Mon, 27 Mar 2006 12:51:52 +0200 Subject: [ALSA] hda-codec - Fix unsol event initialization at resume of stac92xx Fix the re-initialization of unsolicited events at resume of stac92xx codecs. Signed-off-by: Takashi Iwai --- sound/pci/hda/patch_sigmatel.c | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/sound/pci/hda/patch_sigmatel.c b/sound/pci/hda/patch_sigmatel.c index e5fdb64eb02c..abe9493f0a2c 100644 --- a/sound/pci/hda/patch_sigmatel.c +++ b/sound/pci/hda/patch_sigmatel.c @@ -786,13 +786,8 @@ static int stac92xx_auto_create_hp_ctls(struct hda_codec *codec, struct auto_pin return 0; wid_caps = get_wcaps(codec, pin); - if (wid_caps & AC_WCAP_UNSOL_CAP) { - /* Enable unsolicited responses on the HP widget */ - snd_hda_codec_write(codec, pin, 0, - AC_VERB_SET_UNSOLICITED_ENABLE, - STAC_UNSOL_ENABLE); + if (wid_caps & AC_WCAP_UNSOL_CAP) spec->hp_detect = 1; - } nid = snd_hda_codec_read(codec, pin, 0, AC_VERB_GET_CONNECT_LIST, 0) & 0xff; for (i = 0; i < cfg->line_outs; i++) { @@ -915,13 +910,8 @@ static int stac9200_auto_create_hp_ctls(struct hda_codec *codec, return 0; wid_caps = get_wcaps(codec, pin); - if (wid_caps & AC_WCAP_UNSOL_CAP) { - /* Enable unsolicited responses on the HP widget */ - snd_hda_codec_write(codec, pin, 0, - AC_VERB_SET_UNSOLICITED_ENABLE, - STAC_UNSOL_ENABLE); + if (wid_caps & AC_WCAP_UNSOL_CAP) spec->hp_detect = 1; - } return 0; } @@ -963,6 +953,10 @@ static int stac92xx_init(struct hda_codec *codec) /* set up pins */ if (spec->hp_detect) { + /* Enable unsolicited responses on the HP widget */ + snd_hda_codec_write(codec, cfg->hp_pin, 0, + AC_VERB_SET_UNSOLICITED_ENABLE, + STAC_UNSOL_ENABLE); /* fake event to set up pins */ codec->patch_ops.unsol_event(codec, STAC_HP_EVENT << 26); } else { -- cgit v1.2.3-59-g8ed1b From 2125cad29100f88670a483a2291ffdbeae0cd5fc Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Mon, 27 Mar 2006 12:52:22 +0200 Subject: [ALSA] hda-codec - Fix noisy output wtih AD1986A 3stack model Fixed the noisy output wtih AD1986A 3stack model using 2 channels. Signed-off-by: Takashi Iwai --- sound/pci/hda/patch_analog.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/sound/pci/hda/patch_analog.c b/sound/pci/hda/patch_analog.c index 32401bd8c229..2bfe37e8543c 100644 --- a/sound/pci/hda/patch_analog.c +++ b/sound/pci/hda/patch_analog.c @@ -44,6 +44,7 @@ struct ad198x_spec { * dig_out_nid and hp_nid are optional */ unsigned int cur_eapd; + unsigned int need_dac_fix; /* capture */ unsigned int num_adc_nids; @@ -836,10 +837,14 @@ static int patch_ad1986a(struct hda_codec *codec) case AD1986A_3STACK: spec->num_mixers = 2; spec->mixers[1] = ad1986a_3st_mixers; - spec->num_init_verbs = 2; + spec->num_init_verbs = 3; spec->init_verbs[1] = ad1986a_3st_init_verbs; + spec->init_verbs[2] = ad1986a_ch2_init; spec->channel_mode = ad1986a_modes; spec->num_channel_mode = ARRAY_SIZE(ad1986a_modes); + spec->need_dac_fix = 1; + spec->multiout.max_channels = 2; + spec->multiout.num_dacs = 1; break; case AD1986A_LAPTOP: spec->mixers[0] = ad1986a_laptop_mixers; @@ -1555,6 +1560,8 @@ static int ad198x_ch_mode_put(struct snd_kcontrol *kcontrol, { struct hda_codec *codec = snd_kcontrol_chip(kcontrol); struct ad198x_spec *spec = codec->spec; + if (spec->need_dac_fix) + spec->multiout.num_dacs = spec->multiout.max_channels / 2; return snd_hda_ch_mode_put(codec, ucontrol, spec->channel_mode, spec->num_channel_mode, &spec->multiout.max_channels); } -- cgit v1.2.3-59-g8ed1b From 109a9638f0fe38915838b7b9acd98e7cfa91797f Mon Sep 17 00:00:00 2001 From: Peter Gruber Date: Mon, 27 Mar 2006 13:10:28 +0200 Subject: [ALSA] Add snd-riptide driver for Conexant Riptide chip Add snd-riptide driver for Conexant Riptide chip by Peter Gruber. Signed-off-by: Takashi Iwai --- Documentation/sound/alsa/ALSA-Configuration.txt | 14 + sound/pci/Kconfig | 13 + sound/pci/Makefile | 1 + sound/pci/riptide/Makefile | 3 + sound/pci/riptide/riptide.c | 2226 +++++++++++++++++++++++ 5 files changed, 2257 insertions(+) create mode 100644 sound/pci/riptide/Makefile create mode 100644 sound/pci/riptide/riptide.c diff --git a/Documentation/sound/alsa/ALSA-Configuration.txt b/Documentation/sound/alsa/ALSA-Configuration.txt index 1ac20940d8f9..5f79efa777a7 100644 --- a/Documentation/sound/alsa/ALSA-Configuration.txt +++ b/Documentation/sound/alsa/ALSA-Configuration.txt @@ -1220,6 +1220,20 @@ Prior to version 0.9.0rc4 options had a 'snd_' prefix. This was removed. The power-management is supported. + Module snd-riptide + ------------------ + + Module for Conexant Riptide chip + + joystick_port - Joystick port # (default: 0x200) + mpu_port - MPU401 port # (default: 0x330) + opl3_port - OPL3 port # (default: 0x388) + + This module supports multiple cards. + The driver requires the firmware loader support on kernel. + You need to install the firmware file "riptide.hex" to the standard + firmware path (e.g. /lib/firmware). + Module snd-rme32 ---------------- diff --git a/sound/pci/Kconfig b/sound/pci/Kconfig index 5517442aae6a..933ce36a0bbb 100644 --- a/sound/pci/Kconfig +++ b/sound/pci/Kconfig @@ -467,6 +467,19 @@ config SND_PCXHR To compile this driver as a module, choose M here: the module will be called snd-pcxhr. +config SND_RIPTIDE + tristate "Conexant Riptide" + depends on SND + depends on FW_LOADER + select SND_OPL3_LIB + select SND_MPU401_UART + select SND_AC97_CODEC + help + Say 'Y' or 'M' to include support for Conexant Riptide chip. + + To compile this driver as a module, choose M here: the module + will be called snd-riptide + config SND_RME32 tristate "RME Digi32, 32/8, 32 PRO" depends on SND diff --git a/sound/pci/Makefile b/sound/pci/Makefile index a6c3cd58fe94..daa4253ed65f 100644 --- a/sound/pci/Makefile +++ b/sound/pci/Makefile @@ -62,6 +62,7 @@ obj-$(CONFIG_SND) += \ mixart/ \ nm256/ \ pcxhr/ \ + riptide/ \ rme9652/ \ trident/ \ ymfpci/ \ diff --git a/sound/pci/riptide/Makefile b/sound/pci/riptide/Makefile new file mode 100644 index 000000000000..dcd2e64e4818 --- /dev/null +++ b/sound/pci/riptide/Makefile @@ -0,0 +1,3 @@ +snd-riptide-objs := riptide.o + +obj-$(CONFIG_SND_RIPTIDE) += snd-riptide.o diff --git a/sound/pci/riptide/riptide.c b/sound/pci/riptide/riptide.c new file mode 100644 index 000000000000..5b3e49900498 --- /dev/null +++ b/sound/pci/riptide/riptide.c @@ -0,0 +1,2226 @@ +/* + * Driver for the Conexant Riptide Soundchip + * + * Copyright (c) 2004 Peter Gruber + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +/* + History: + - 02/15/2004 first release + + This Driver is based on the OSS Driver version from Linuxant (riptide-0.6lnxtbeta03111100) + credits from the original files: + + MODULE NAME: cnxt_rt.h + AUTHOR: K. Lazarev (Transcribed by KNL) + HISTORY: Major Revision Date By + ----------------------------- -------- ----- + Created 02/1/2000 KNL + + MODULE NAME: int_mdl.c + AUTHOR: Konstantin Lazarev (Transcribed by KNL) + HISTORY: Major Revision Date By + ----------------------------- -------- ----- + Created 10/01/99 KNL + + MODULE NAME: riptide.h + AUTHOR: O. Druzhinin (Transcribed by OLD) + HISTORY: Major Revision Date By + ----------------------------- -------- ----- + Created 10/16/97 OLD + + MODULE NAME: Rp_Cmdif.cpp + AUTHOR: O. Druzhinin (Transcribed by OLD) + K. Lazarev (Transcribed by KNL) + HISTORY: Major Revision Date By + ----------------------------- -------- ----- + Adopted from NT4 driver 6/22/99 OLD + Ported to Linux 9/01/99 KNL + + MODULE NAME: rt_hw.c + AUTHOR: O. Druzhinin (Transcribed by OLD) + C. Lazarev (Transcribed by CNL) + HISTORY: Major Revision Date By + ----------------------------- -------- ----- + Created 11/18/97 OLD + Hardware functions for RipTide 11/24/97 CNL + (ES1) are coded + Hardware functions for RipTide 12/24/97 CNL + (A0) are coded + Hardware functions for RipTide 03/20/98 CNL + (A1) are coded + Boot loader is included 05/07/98 CNL + Redesigned for WDM 07/27/98 CNL + Redesigned for Linux 09/01/99 CNL + + MODULE NAME: rt_hw.h + AUTHOR: C. Lazarev (Transcribed by CNL) + HISTORY: Major Revision Date By + ----------------------------- -------- ----- + Created 11/18/97 CNL + + MODULE NAME: rt_mdl.c + AUTHOR: Konstantin Lazarev (Transcribed by KNL) + HISTORY: Major Revision Date By + ----------------------------- -------- ----- + Created 10/01/99 KNL + + MODULE NAME: mixer.h + AUTHOR: K. Kenney + HISTORY: Major Revision Date By + ----------------------------- -------- ----- + Created from MS W95 Sample 11/28/95 KRS + RipTide 10/15/97 KRS + Adopted for Windows NT driver 01/20/98 CNL +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(CONFIG_GAMEPORT) || (defined(MODULE) && defined(CONFIG_GAMEPORT_MODULE)) +#define SUPPORT_JOYSTICK 1 +#endif + +MODULE_AUTHOR("Peter Gruber "); +MODULE_DESCRIPTION("riptide"); +MODULE_LICENSE("GPL"); +MODULE_SUPPORTED_DEVICE("{{Conexant,Riptide}}"); + +static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; +static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; +static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE; + +#ifdef SUPPORT_JOYSTICK +static int joystick_port[SNDRV_CARDS] = { [0 ... (SNDRV_CARDS - 1)] = 0x200 }; +#endif +static int mpu_port[SNDRV_CARDS] = { [0 ... (SNDRV_CARDS - 1)] = 0x330 }; +static int opl3_port[SNDRV_CARDS] = { [0 ... (SNDRV_CARDS - 1)] = 0x388 }; + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "Index value for Riptide soundcard."); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string for Riptide soundcard."); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "Enable Riptide soundcard."); +#ifdef SUPPORT_JOYSTICK +module_param_array(joystick_port, int, NULL, 0444); +MODULE_PARM_DESC(joystick_port, "Joystick port # for Riptide soundcard."); +#endif +module_param_array(mpu_port, int, NULL, 0444); +MODULE_PARM_DESC(mpu_port, "MPU401 port # for Riptide driver."); +module_param_array(opl3_port, int, NULL, 0444); +MODULE_PARM_DESC(opl3_port, "OPL3 port # for Riptide driver."); + +/* + */ + +#define MPU401_HW_RIPTIDE MPU401_HW_MPU401 +#define OPL3_HW_RIPTIDE OPL3_HW_OPL3 + +#define PCI_EXT_CapId 0x40 +#define PCI_EXT_NextCapPrt 0x41 +#define PCI_EXT_PWMC 0x42 +#define PCI_EXT_PWSCR 0x44 +#define PCI_EXT_Data00 0x46 +#define PCI_EXT_PMSCR_BSE 0x47 +#define PCI_EXT_SB_Base 0x48 +#define PCI_EXT_FM_Base 0x4a +#define PCI_EXT_MPU_Base 0x4C +#define PCI_EXT_Game_Base 0x4E +#define PCI_EXT_Legacy_Mask 0x50 +#define PCI_EXT_AsicRev 0x52 +#define PCI_EXT_Reserved3 0x53 + +#define LEGACY_ENABLE_ALL 0x8000 /* legacy device options */ +#define LEGACY_ENABLE_SB 0x4000 +#define LEGACY_ENABLE_FM 0x2000 +#define LEGACY_ENABLE_MPU_INT 0x1000 +#define LEGACY_ENABLE_MPU 0x0800 +#define LEGACY_ENABLE_GAMEPORT 0x0400 + +#define MAX_WRITE_RETRY 10 /* cmd interface limits */ +#define MAX_ERROR_COUNT 10 +#define CMDIF_TIMEOUT 500000 +#define RESET_TRIES 5 + +#define READ_PORT_ULONG(p) inl((unsigned long)&(p)) +#define WRITE_PORT_ULONG(p,x) outl(x,(unsigned long)&(p)) + +#define READ_AUDIO_CONTROL(p) READ_PORT_ULONG(p->audio_control) +#define WRITE_AUDIO_CONTROL(p,x) WRITE_PORT_ULONG(p->audio_control,x) +#define UMASK_AUDIO_CONTROL(p,x) WRITE_PORT_ULONG(p->audio_control,READ_PORT_ULONG(p->audio_control)|x) +#define MASK_AUDIO_CONTROL(p,x) WRITE_PORT_ULONG(p->audio_control,READ_PORT_ULONG(p->audio_control)&x) +#define READ_AUDIO_STATUS(p) READ_PORT_ULONG(p->audio_status) + +#define SET_GRESET(p) UMASK_AUDIO_CONTROL(p,0x0001) /* global reset switch */ +#define UNSET_GRESET(p) MASK_AUDIO_CONTROL(p,~0x0001) +#define SET_AIE(p) UMASK_AUDIO_CONTROL(p,0x0004) /* interrupt enable */ +#define UNSET_AIE(p) MASK_AUDIO_CONTROL(p,~0x0004) +#define SET_AIACK(p) UMASK_AUDIO_CONTROL(p,0x0008) /* interrupt acknowledge */ +#define UNSET_AIACKT(p) MASKAUDIO_CONTROL(p,~0x0008) +#define SET_ECMDAE(p) UMASK_AUDIO_CONTROL(p,0x0010) +#define UNSET_ECMDAE(p) MASK_AUDIO_CONTROL(p,~0x0010) +#define SET_ECMDBE(p) UMASK_AUDIO_CONTROL(p,0x0020) +#define UNSET_ECMDBE(p) MASK_AUDIO_CONTROL(p,~0x0020) +#define SET_EDATAF(p) UMASK_AUDIO_CONTROL(p,0x0040) +#define UNSET_EDATAF(p) MASK_AUDIO_CONTROL(p,~0x0040) +#define SET_EDATBF(p) UMASK_AUDIO_CONTROL(p,0x0080) +#define UNSET_EDATBF(p) MASK_AUDIO_CONTROL(p,~0x0080) +#define SET_ESBIRQON(p) UMASK_AUDIO_CONTROL(p,0x0100) +#define UNSET_ESBIRQON(p) MASK_AUDIO_CONTROL(p,~0x0100) +#define SET_EMPUIRQ(p) UMASK_AUDIO_CONTROL(p,0x0200) +#define UNSET_EMPUIRQ(p) MASK_AUDIO_CONTROL(p,~0x0200) +#define IS_CMDE(a) (READ_PORT_ULONG(a->stat)&0x1) /* cmd empty */ +#define IS_DATF(a) (READ_PORT_ULONG(a->stat)&0x2) /* data filled */ +#define IS_READY(p) (READ_AUDIO_STATUS(p)&0x0001) +#define IS_DLREADY(p) (READ_AUDIO_STATUS(p)&0x0002) +#define IS_DLERR(p) (READ_AUDIO_STATUS(p)&0x0004) +#define IS_GERR(p) (READ_AUDIO_STATUS(p)&0x0008) /* error ! */ +#define IS_CMDAEIRQ(p) (READ_AUDIO_STATUS(p)&0x0010) +#define IS_CMDBEIRQ(p) (READ_AUDIO_STATUS(p)&0x0020) +#define IS_DATAFIRQ(p) (READ_AUDIO_STATUS(p)&0x0040) +#define IS_DATBFIRQ(p) (READ_AUDIO_STATUS(p)&0x0080) +#define IS_EOBIRQ(p) (READ_AUDIO_STATUS(p)&0x0100) /* interrupt status */ +#define IS_EOSIRQ(p) (READ_AUDIO_STATUS(p)&0x0200) +#define IS_EOCIRQ(p) (READ_AUDIO_STATUS(p)&0x0400) +#define IS_UNSLIRQ(p) (READ_AUDIO_STATUS(p)&0x0800) +#define IS_SBIRQ(p) (READ_AUDIO_STATUS(p)&0x1000) +#define IS_MPUIRQ(p) (READ_AUDIO_STATUS(p)&0x2000) + +#define RESP 0x00000001 /* command flags */ +#define PARM 0x00000002 +#define CMDA 0x00000004 +#define CMDB 0x00000008 +#define NILL 0x00000000 + +#define LONG0(a) ((u32)a) /* shifts and masks */ +#define BYTE0(a) (LONG0(a)&0xff) +#define BYTE1(a) (BYTE0(a)<<8) +#define BYTE2(a) (BYTE0(a)<<16) +#define BYTE3(a) (BYTE0(a)<<24) +#define WORD0(a) (LONG0(a)&0xffff) +#define WORD1(a) (WORD0(a)<<8) +#define WORD2(a) (WORD0(a)<<16) +#define TRINIB0(a) (LONG0(a)&0xffffff) +#define TRINIB1(a) (TRINIB0(a)<<8) + +#define RET(a) ((union cmdret *)(a)) + +#define SEND_GETV(p,b) sendcmd(p,RESP,GETV,0,RET(b)) /* get version */ +#define SEND_GETC(p,b,c) sendcmd(p,PARM|RESP,GETC,c,RET(b)) +#define SEND_GUNS(p,b) sendcmd(p,RESP,GUNS,0,RET(b)) +#define SEND_SCID(p,b) sendcmd(p,RESP,SCID,0,RET(b)) +#define SEND_RMEM(p,b,c,d) sendcmd(p,PARM|RESP,RMEM|BYTE1(b),LONG0(c),RET(d)) /* memory access for firmware write */ +#define SEND_SMEM(p,b,c) sendcmd(p,PARM,SMEM|BYTE1(b),LONG0(c),RET(0)) /* memory access for firmware write */ +#define SEND_WMEM(p,b,c) sendcmd(p,PARM,WMEM|BYTE1(b),LONG0(c),RET(0)) /* memory access for firmware write */ +#define SEND_SDTM(p,b,c) sendcmd(p,PARM|RESP,SDTM|TRINIB1(b),0,RET(c)) /* memory access for firmware write */ +#define SEND_GOTO(p,b) sendcmd(p,PARM,GOTO,LONG0(b),RET(0)) /* memory access for firmware write */ +#define SEND_SETDPLL(p) sendcmd(p,0,ARM_SETDPLL,0,RET(0)) +#define SEND_SSTR(p,b,c) sendcmd(p,PARM,SSTR|BYTE3(b),LONG0(c),RET(0)) /* start stream */ +#define SEND_PSTR(p,b) sendcmd(p,PARM,PSTR,BYTE3(b),RET(0)) /* pause stream */ +#define SEND_KSTR(p,b) sendcmd(p,PARM,KSTR,BYTE3(b),RET(0)) /* stop stream */ +#define SEND_KDMA(p) sendcmd(p,0,KDMA,0,RET(0)) /* stop all dma */ +#define SEND_GPOS(p,b,c,d) sendcmd(p,PARM|RESP,GPOS,BYTE3(c)|BYTE2(b),RET(d)) /* get position in dma */ +#define SEND_SETF(p,b,c,d,e,f,g) sendcmd(p,PARM,SETF|WORD1(b)|BYTE3(c),d|BYTE1(e)|BYTE2(f)|BYTE3(g),RET(0)) /* set sample format at mixer */ +#define SEND_GSTS(p,b,c,d) sendcmd(p,PARM|RESP,GSTS,BYTE3(c)|BYTE2(b),RET(d)) +#define SEND_NGPOS(p,b,c,d) sendcmd(p,PARM|RESP,NGPOS,BYTE3(c)|BYTE2(b),RET(d)) +#define SEND_PSEL(p,b,c) sendcmd(p,PARM,PSEL,BYTE2(b)|BYTE3(c),RET(0)) /* activate lbus path */ +#define SEND_PCLR(p,b,c) sendcmd(p,PARM,PCLR,BYTE2(b)|BYTE3(c),RET(0)) /* deactivate lbus path */ +#define SEND_PLST(p,b) sendcmd(p,PARM,PLST,BYTE3(b),RET(0)) +#define SEND_RSSV(p,b,c,d) sendcmd(p,PARM|RESP,RSSV,BYTE2(b)|BYTE3(c),RET(d)) +#define SEND_LSEL(p,b,c,d,e,f,g,h) sendcmd(p,PARM,LSEL|BYTE1(b)|BYTE2(c)|BYTE3(d),BYTE0(e)|BYTE1(f)|BYTE2(g)|BYTE3(h),RET(0)) /* select paths for internal connections */ +#define SEND_SSRC(p,b,c,d,e) sendcmd(p,PARM,SSRC|BYTE1(b)|WORD2(c),WORD0(d)|WORD2(e),RET(0)) /* configure source */ +#define SEND_SLST(p,b) sendcmd(p,PARM,SLST,BYTE3(b),RET(0)) +#define SEND_RSRC(p,b,c) sendcmd(p,RESP,RSRC|BYTE1(b),0,RET(c)) /* read source config */ +#define SEND_SSRB(p,b,c) sendcmd(p,PARM,SSRB|BYTE1(b),WORD2(c),RET(0)) +#define SEND_SDGV(p,b,c,d,e) sendcmd(p,PARM,SDGV|BYTE2(b)|BYTE3(c),WORD0(d)|WORD2(e),RET(0)) /* set digital mixer */ +#define SEND_RDGV(p,b,c,d) sendcmd(p,PARM|RESP,RDGV|BYTE2(b)|BYTE3(c),0,RET(d)) /* read digital mixer */ +#define SEND_DLST(p,b) sendcmd(p,PARM,DLST,BYTE3(b),RET(0)) +#define SEND_SACR(p,b,c) sendcmd(p,PARM,SACR,WORD0(b)|WORD2(c),RET(0)) /* set AC97 register */ +#define SEND_RACR(p,b,c) sendcmd(p,PARM|RESP,RACR,WORD2(b),RET(c)) /* get AC97 register */ +#define SEND_ALST(p,b) sendcmd(p,PARM,ALST,BYTE3(b),RET(0)) +#define SEND_TXAC(p,b,c,d,e,f) sendcmd(p,PARM,TXAC|BYTE1(b)|WORD2(c),WORD0(d)|BYTE2(e)|BYTE3(f),RET(0)) +#define SEND_RXAC(p,b,c,d) sendcmd(p,PARM|RESP,RXAC,BYTE2(b)|BYTE3(c),RET(d)) +#define SEND_SI2S(p,b) sendcmd(p,PARM,SI2S,WORD2(b),RET(0)) + +#define EOB_STATUS 0x80000000 /* status flags : block boundary */ +#define EOS_STATUS 0x40000000 /* : stoppped */ +#define EOC_STATUS 0x20000000 /* : stream end */ +#define ERR_STATUS 0x10000000 +#define EMPTY_STATUS 0x08000000 + +#define IEOB_ENABLE 0x1 /* enable interrupts for status notification above */ +#define IEOS_ENABLE 0x2 +#define IEOC_ENABLE 0x4 +#define RDONCE 0x8 +#define DESC_MAX_MASK 0xff + +#define ST_PLAY 0x1 /* stream states */ +#define ST_STOP 0x2 +#define ST_PAUSE 0x4 + +#define I2S_INTDEC 3 /* config for I2S link */ +#define I2S_MERGER 0 +#define I2S_SPLITTER 0 +#define I2S_MIXER 7 +#define I2S_RATE 44100 + +#define MODEM_INTDEC 4 /* config for modem link */ +#define MODEM_MERGER 3 +#define MODEM_SPLITTER 0 +#define MODEM_MIXER 11 + +#define FM_INTDEC 3 /* config for FM/OPL3 link */ +#define FM_MERGER 0 +#define FM_SPLITTER 0 +#define FM_MIXER 9 + +#define SPLIT_PATH 0x80 /* path splitting flag */ + +enum FIRMWARE { + DATA_REC = 0, EXT_END_OF_FILE, EXT_SEG_ADDR_REC, EXT_GOTO_CMD_REC, + EXT_LIN_ADDR_REC, +}; + +enum CMDS { + GETV = 0x00, GETC, GUNS, SCID, RMEM = + 0x10, SMEM, WMEM, SDTM, GOTO, SSTR = + 0x20, PSTR, KSTR, KDMA, GPOS, SETF, GSTS, NGPOS, PSEL = + 0x30, PCLR, PLST, RSSV, LSEL, SSRC = 0x40, SLST, RSRC, SSRB, SDGV = + 0x50, RDGV, DLST, SACR = 0x60, RACR, ALST, TXAC, RXAC, SI2S = + 0x70, ARM_SETDPLL = 0x72, +}; + +enum E1SOURCE { + ARM2LBUS_FIFO0 = 0, ARM2LBUS_FIFO1, ARM2LBUS_FIFO2, ARM2LBUS_FIFO3, + ARM2LBUS_FIFO4, ARM2LBUS_FIFO5, ARM2LBUS_FIFO6, ARM2LBUS_FIFO7, + ARM2LBUS_FIFO8, ARM2LBUS_FIFO9, ARM2LBUS_FIFO10, ARM2LBUS_FIFO11, + ARM2LBUS_FIFO12, ARM2LBUS_FIFO13, ARM2LBUS_FIFO14, ARM2LBUS_FIFO15, + INTER0_OUT, INTER1_OUT, INTER2_OUT, INTER3_OUT, INTER4_OUT, + INTERM0_OUT, INTERM1_OUT, INTERM2_OUT, INTERM3_OUT, INTERM4_OUT, + INTERM5_OUT, INTERM6_OUT, DECIMM0_OUT, DECIMM1_OUT, DECIMM2_OUT, + DECIMM3_OUT, DECIM0_OUT, SR3_4_OUT, OPL3_SAMPLE, ASRC0, ASRC1, + ACLNK2PADC, ACLNK2MODEM0RX, ACLNK2MIC, ACLNK2MODEM1RX, ACLNK2HNDMIC, + DIGITAL_MIXER_OUT0, GAINFUNC0_OUT, GAINFUNC1_OUT, GAINFUNC2_OUT, + GAINFUNC3_OUT, GAINFUNC4_OUT, SOFTMODEMTX, SPLITTER0_OUTL, + SPLITTER0_OUTR, SPLITTER1_OUTL, SPLITTER1_OUTR, SPLITTER2_OUTL, + SPLITTER2_OUTR, SPLITTER3_OUTL, SPLITTER3_OUTR, MERGER0_OUT, + MERGER1_OUT, MERGER2_OUT, MERGER3_OUT, ARM2LBUS_FIFO_DIRECT, NO_OUT +}; + +enum E2SINK { + LBUS2ARM_FIFO0 = 0, LBUS2ARM_FIFO1, LBUS2ARM_FIFO2, LBUS2ARM_FIFO3, + LBUS2ARM_FIFO4, LBUS2ARM_FIFO5, LBUS2ARM_FIFO6, LBUS2ARM_FIFO7, + INTER0_IN, INTER1_IN, INTER2_IN, INTER3_IN, INTER4_IN, INTERM0_IN, + INTERM1_IN, INTERM2_IN, INTERM3_IN, INTERM4_IN, INTERM5_IN, INTERM6_IN, + DECIMM0_IN, DECIMM1_IN, DECIMM2_IN, DECIMM3_IN, DECIM0_IN, SR3_4_IN, + PDAC2ACLNK, MODEM0TX2ACLNK, MODEM1TX2ACLNK, HNDSPK2ACLNK, + DIGITAL_MIXER_IN0, DIGITAL_MIXER_IN1, DIGITAL_MIXER_IN2, + DIGITAL_MIXER_IN3, DIGITAL_MIXER_IN4, DIGITAL_MIXER_IN5, + DIGITAL_MIXER_IN6, DIGITAL_MIXER_IN7, DIGITAL_MIXER_IN8, + DIGITAL_MIXER_IN9, DIGITAL_MIXER_IN10, DIGITAL_MIXER_IN11, + GAINFUNC0_IN, GAINFUNC1_IN, GAINFUNC2_IN, GAINFUNC3_IN, GAINFUNC4_IN, + SOFTMODEMRX, SPLITTER0_IN, SPLITTER1_IN, SPLITTER2_IN, SPLITTER3_IN, + MERGER0_INL, MERGER0_INR, MERGER1_INL, MERGER1_INR, MERGER2_INL, + MERGER2_INR, MERGER3_INL, MERGER3_INR, E2SINK_MAX +}; + +enum LBUS_SINK { + LS_SRC_INTERPOLATOR = 0, LS_SRC_INTERPOLATORM, LS_SRC_DECIMATOR, + LS_SRC_DECIMATORM, LS_MIXER_IN, LS_MIXER_GAIN_FUNCTION, + LS_SRC_SPLITTER, LS_SRC_MERGER, LS_NONE1, LS_NONE2, +}; + +enum RT_CHANNEL_IDS { + M0TX = 0, M1TX, TAMTX, HSSPKR, PDAC, DSNDTX0, DSNDTX1, DSNDTX2, + DSNDTX3, DSNDTX4, DSNDTX5, DSNDTX6, DSNDTX7, WVSTRTX, COP3DTX, SPARE, + M0RX, HSMIC, M1RX, CLEANRX, MICADC, PADC, COPRX1, COPRX2, + CHANNEL_ID_COUNTER +}; + +enum { SB_CMD = 0, MODEM_CMD, I2S_CMD0, I2S_CMD1, FM_CMD, MAX_CMD }; + +struct lbuspath { + unsigned char *noconv; + unsigned char *stereo; + unsigned char *mono; +}; + +struct cmdport { + u32 data1; /* cmd,param */ + u32 data2; /* param */ + u32 stat; /* status */ + u32 pad[5]; +}; + +struct riptideport { + u32 audio_control; /* status registers */ + u32 audio_status; + u32 pad[2]; + struct cmdport port[2]; /* command ports */ +}; + +struct cmdif { + struct riptideport *hwport; + spinlock_t lock; + unsigned int cmdcnt; /* cmd statistics */ + unsigned int cmdtime; + unsigned int cmdtimemax; + unsigned int cmdtimemin; + unsigned int errcnt; + int is_reset; +}; + +struct riptide_firmware { + u16 ASIC; + u16 CODEC; + u16 AUXDSP; + u16 PROG; +}; + +union cmdret { + u8 retbytes[8]; + u16 retwords[4]; + u32 retlongs[2]; +}; + +union firmware_version { + union cmdret ret; + struct riptide_firmware firmware; +}; + +#define get_pcmhwdev(substream) (struct pcmhw *)(substream->runtime->private_data) + +#define PLAYBACK_SUBSTREAMS 3 +struct snd_riptide { + struct snd_card *card; + struct pci_dev *pci; + const struct firmware *fw_entry; + + struct cmdif *cif; + + struct snd_pcm *pcm; + struct snd_pcm *pcm_i2s; + struct snd_rawmidi *rmidi; + struct snd_opl3 *opl3; + struct snd_ac97 *ac97; + struct snd_ac97_bus *ac97_bus; + + struct snd_pcm_substream *playback_substream[PLAYBACK_SUBSTREAMS]; + struct snd_pcm_substream *capture_substream; + + int openstreams; + + int irq; + unsigned long port; + unsigned short mpuaddr; + unsigned short opladdr; +#ifdef SUPPORT_JOYSTICK + unsigned short gameaddr; +#endif + struct resource *res_port; + + unsigned short device_id; + + union firmware_version firmware; + + spinlock_t lock; + struct tasklet_struct riptide_tq; + struct snd_info_entry *proc_entry; + + unsigned long received_irqs; + unsigned long handled_irqs; +#ifdef CONFIG_PM + int in_suspend; +#endif +}; + +struct sgd { /* scatter gather desriptor */ + u32 dwNextLink; + u32 dwSegPtrPhys; + u32 dwSegLen; + u32 dwStat_Ctl; +}; + +struct pcmhw { /* pcm descriptor */ + struct lbuspath paths; + unsigned char *lbuspath; + unsigned char source; + unsigned char intdec[2]; + unsigned char mixer; + unsigned char id; + unsigned char state; + unsigned int rate; + unsigned int channels; + snd_pcm_format_t format; + struct snd_dma_buffer sgdlist; + struct sgd *sgdbuf; + unsigned int size; + unsigned int pages; + unsigned int oldpos; + unsigned int pointer; +}; + +#define CMDRET_ZERO (union cmdret){{(u32)0, (u32) 0}} + +static int sendcmd(struct cmdif *cif, u32 flags, u32 cmd, u32 parm, + union cmdret *ret); +static int getsourcesink(struct cmdif *cif, unsigned char source, + unsigned char sink, unsigned char *a, + unsigned char *b); +static int snd_riptide_initialize(struct snd_riptide *chip); +static int riptide_reset(struct cmdif *cif, struct snd_riptide *chip); + +/* + */ + +static struct pci_device_id snd_riptide_ids[] = { + { + .vendor = 0x127a,.device = 0x4310, + .subvendor = PCI_ANY_ID,.subdevice = PCI_ANY_ID, + }, + { + .vendor = 0x127a,.device = 0x4320, + .subvendor = PCI_ANY_ID,.subdevice = PCI_ANY_ID, + }, + { + .vendor = 0x127a,.device = 0x4330, + .subvendor = PCI_ANY_ID,.subdevice = PCI_ANY_ID, + }, + { + .vendor = 0x127a,.device = 0x4340, + .subvendor = PCI_ANY_ID,.subdevice = PCI_ANY_ID, + }, + {0,}, +}; + +#ifdef SUPPORT_JOYSTICK +static struct pci_device_id snd_riptide_joystick_ids[] = { + { + .vendor = 0x127a,.device = 0x4312, + .subvendor = PCI_ANY_ID,.subdevice = PCI_ANY_ID, + }, + { + .vendor = 0x127a,.device = 0x4322, + .subvendor = PCI_ANY_ID,.subdevice = PCI_ANY_ID, + }, + {.vendor = 0x127a,.device = 0x4332, + .subvendor = PCI_ANY_ID,.subdevice = PCI_ANY_ID, + }, + {.vendor = 0x127a,.device = 0x4342, + .subvendor = PCI_ANY_ID,.subdevice = PCI_ANY_ID, + }, + {0,}, +}; +#endif + +MODULE_DEVICE_TABLE(pci, snd_riptide_ids); + +/* + */ + +static unsigned char lbusin2out[E2SINK_MAX + 1][2] = { + {NO_OUT, LS_NONE1}, {NO_OUT, LS_NONE2}, {NO_OUT, LS_NONE1}, {NO_OUT, + LS_NONE2}, + {NO_OUT, LS_NONE1}, {NO_OUT, LS_NONE2}, {NO_OUT, LS_NONE1}, {NO_OUT, + LS_NONE2}, + {INTER0_OUT, LS_SRC_INTERPOLATOR}, {INTER1_OUT, LS_SRC_INTERPOLATOR}, + {INTER2_OUT, LS_SRC_INTERPOLATOR}, {INTER3_OUT, LS_SRC_INTERPOLATOR}, + {INTER4_OUT, LS_SRC_INTERPOLATOR}, {INTERM0_OUT, LS_SRC_INTERPOLATORM}, + {INTERM1_OUT, LS_SRC_INTERPOLATORM}, {INTERM2_OUT, + LS_SRC_INTERPOLATORM}, + {INTERM3_OUT, LS_SRC_INTERPOLATORM}, {INTERM4_OUT, + LS_SRC_INTERPOLATORM}, + {INTERM5_OUT, LS_SRC_INTERPOLATORM}, {INTERM6_OUT, + LS_SRC_INTERPOLATORM}, + {DECIMM0_OUT, LS_SRC_DECIMATORM}, {DECIMM1_OUT, LS_SRC_DECIMATORM}, + {DECIMM2_OUT, LS_SRC_DECIMATORM}, {DECIMM3_OUT, LS_SRC_DECIMATORM}, + {DECIM0_OUT, LS_SRC_DECIMATOR}, {SR3_4_OUT, LS_NONE1}, {NO_OUT, + LS_NONE2}, + {NO_OUT, LS_NONE1}, {NO_OUT, LS_NONE2}, {NO_OUT, LS_NONE1}, + {DIGITAL_MIXER_OUT0, LS_MIXER_IN}, {DIGITAL_MIXER_OUT0, LS_MIXER_IN}, + {DIGITAL_MIXER_OUT0, LS_MIXER_IN}, {DIGITAL_MIXER_OUT0, LS_MIXER_IN}, + {DIGITAL_MIXER_OUT0, LS_MIXER_IN}, {DIGITAL_MIXER_OUT0, LS_MIXER_IN}, + {DIGITAL_MIXER_OUT0, LS_MIXER_IN}, {DIGITAL_MIXER_OUT0, LS_MIXER_IN}, + {DIGITAL_MIXER_OUT0, LS_MIXER_IN}, {DIGITAL_MIXER_OUT0, LS_MIXER_IN}, + {DIGITAL_MIXER_OUT0, LS_MIXER_IN}, {DIGITAL_MIXER_OUT0, LS_MIXER_IN}, + {GAINFUNC0_OUT, LS_MIXER_GAIN_FUNCTION}, {GAINFUNC1_OUT, + LS_MIXER_GAIN_FUNCTION}, + {GAINFUNC2_OUT, LS_MIXER_GAIN_FUNCTION}, {GAINFUNC3_OUT, + LS_MIXER_GAIN_FUNCTION}, + {GAINFUNC4_OUT, LS_MIXER_GAIN_FUNCTION}, {SOFTMODEMTX, LS_NONE1}, + {SPLITTER0_OUTL, LS_SRC_SPLITTER}, {SPLITTER1_OUTL, LS_SRC_SPLITTER}, + {SPLITTER2_OUTL, LS_SRC_SPLITTER}, {SPLITTER3_OUTL, LS_SRC_SPLITTER}, + {MERGER0_OUT, LS_SRC_MERGER}, {MERGER0_OUT, LS_SRC_MERGER}, + {MERGER1_OUT, LS_SRC_MERGER}, + {MERGER1_OUT, LS_SRC_MERGER}, {MERGER2_OUT, LS_SRC_MERGER}, + {MERGER2_OUT, LS_SRC_MERGER}, + {MERGER3_OUT, LS_SRC_MERGER}, {MERGER3_OUT, LS_SRC_MERGER}, {NO_OUT, + LS_NONE2}, +}; + +static unsigned char lbus_play_opl3[] = { + DIGITAL_MIXER_IN0 + FM_MIXER, 0xff +}; +static unsigned char lbus_play_modem[] = { + DIGITAL_MIXER_IN0 + MODEM_MIXER, 0xff +}; +static unsigned char lbus_play_i2s[] = { + INTER0_IN + I2S_INTDEC, DIGITAL_MIXER_IN0 + I2S_MIXER, 0xff +}; +static unsigned char lbus_play_out[] = { + PDAC2ACLNK, 0xff +}; +static unsigned char lbus_play_outhp[] = { + HNDSPK2ACLNK, 0xff +}; +static unsigned char lbus_play_noconv1[] = { + DIGITAL_MIXER_IN0, 0xff +}; +static unsigned char lbus_play_stereo1[] = { + INTER0_IN, DIGITAL_MIXER_IN0, 0xff +}; +static unsigned char lbus_play_mono1[] = { + INTERM0_IN, DIGITAL_MIXER_IN0, 0xff +}; +static unsigned char lbus_play_noconv2[] = { + DIGITAL_MIXER_IN1, 0xff +}; +static unsigned char lbus_play_stereo2[] = { + INTER1_IN, DIGITAL_MIXER_IN1, 0xff +}; +static unsigned char lbus_play_mono2[] = { + INTERM1_IN, DIGITAL_MIXER_IN1, 0xff +}; +static unsigned char lbus_play_noconv3[] = { + DIGITAL_MIXER_IN2, 0xff +}; +static unsigned char lbus_play_stereo3[] = { + INTER2_IN, DIGITAL_MIXER_IN2, 0xff +}; +static unsigned char lbus_play_mono3[] = { + INTERM2_IN, DIGITAL_MIXER_IN2, 0xff +}; +static unsigned char lbus_rec_noconv1[] = { + LBUS2ARM_FIFO5, 0xff +}; +static unsigned char lbus_rec_stereo1[] = { + DECIM0_IN, LBUS2ARM_FIFO5, 0xff +}; +static unsigned char lbus_rec_mono1[] = { + DECIMM3_IN, LBUS2ARM_FIFO5, 0xff +}; + +static unsigned char play_ids[] = { 4, 1, 2, }; +static unsigned char play_sources[] = { + ARM2LBUS_FIFO4, ARM2LBUS_FIFO1, ARM2LBUS_FIFO2, +}; +static struct lbuspath lbus_play_paths[] = { + { + .noconv = lbus_play_noconv1, + .stereo = lbus_play_stereo1, + .mono = lbus_play_mono1, + }, + { + .noconv = lbus_play_noconv2, + .stereo = lbus_play_stereo2, + .mono = lbus_play_mono2, + }, + { + .noconv = lbus_play_noconv3, + .stereo = lbus_play_stereo3, + .mono = lbus_play_mono3, + }, +}; +static struct lbuspath lbus_rec_path = { + .noconv = lbus_rec_noconv1, + .stereo = lbus_rec_stereo1, + .mono = lbus_rec_mono1, +}; + +#define FIRMWARE_VERSIONS 1 +static union firmware_version firmware_versions[] = { + { + .firmware.ASIC = 3,.firmware.CODEC = 2, + .firmware.AUXDSP = 3,.firmware.PROG = 773, + }, +}; + +static u32 atoh(unsigned char *in, unsigned int len) +{ + u32 sum = 0; + unsigned int mult = 1; + unsigned char c; + + while (len) { + c = in[len - 1]; + if ((c >= '0') && (c <= '9')) + sum += mult * (c - '0'); + else if ((c >= 'A') && (c <= 'F')) + sum += mult * (c - ('A' - 10)); + else if ((c >= 'a') && (c <= 'f')) + sum += mult * (c - ('a' - 10)); + mult *= 16; + --len; + } + return sum; +} + +static int senddata(struct cmdif *cif, unsigned char *in, u32 offset) +{ + u32 addr; + u32 data; + u32 i; + unsigned char *p; + + i = atoh(&in[1], 2); + addr = offset + atoh(&in[3], 4); + if (SEND_SMEM(cif, 0, addr) != 0) + return -EACCES; + p = in + 9; + while (i) { + data = atoh(p, 8); + if (SEND_WMEM(cif, 2, + ((data & 0x0f0f0f0f) << 4) | ((data & 0xf0f0f0f0) + >> 4))) + return -EACCES; + i -= 4; + p += 8; + } + return 0; +} + +static int loadfirmware(struct cmdif *cif, unsigned char *img, + unsigned int size) +{ + unsigned char *in; + u32 laddr, saddr, t, val; + int err = 0; + + laddr = saddr = 0; + while (size > 0 && err == 0) { + in = img; + if (in[0] == ':') { + t = atoh(&in[7], 2); + switch (t) { + case DATA_REC: + err = senddata(cif, in, laddr + saddr); + break; + case EXT_SEG_ADDR_REC: + saddr = atoh(&in[9], 4) << 4; + break; + case EXT_LIN_ADDR_REC: + laddr = atoh(&in[9], 4) << 16; + break; + case EXT_GOTO_CMD_REC: + val = atoh(&in[9], 8); + if (SEND_GOTO(cif, val) != 0) + err = -EACCES; + break; + case EXT_END_OF_FILE: + size = 0; + break; + default: + break; + } + while (size > 0) { + size--; + if (*img++ == '\n') + break; + } + } + } + snd_printdd("load firmware return %d\n", err); + return err; +} + +static void +alloclbuspath(struct cmdif *cif, unsigned char source, + unsigned char *path, unsigned char *mixer, unsigned char *s) +{ + while (*path != 0xff) { + unsigned char sink, type; + + sink = *path & (~SPLIT_PATH); + if (sink != E2SINK_MAX) { + snd_printdd("alloc path 0x%x->0x%x\n", source, sink); + SEND_PSEL(cif, source, sink); + source = lbusin2out[sink][0]; + type = lbusin2out[sink][1]; + if (type == LS_MIXER_IN) { + if (mixer) + *mixer = sink - DIGITAL_MIXER_IN0; + } + if (type == LS_SRC_DECIMATORM || + type == LS_SRC_DECIMATOR || + type == LS_SRC_INTERPOLATORM || + type == LS_SRC_INTERPOLATOR) { + if (s) { + if (s[0] != 0xff) + s[1] = sink; + else + s[0] = sink; + } + } + } + if (*path++ & SPLIT_PATH) { + unsigned char *npath = path; + + while (*npath != 0xff) + npath++; + alloclbuspath(cif, source + 1, ++npath, mixer, s); + } + } +} + +static void +freelbuspath(struct cmdif *cif, unsigned char source, unsigned char *path) +{ + while (*path != 0xff) { + unsigned char sink; + + sink = *path & (~SPLIT_PATH); + if (sink != E2SINK_MAX) { + snd_printdd("free path 0x%x->0x%x\n", source, sink); + SEND_PCLR(cif, source, sink); + source = lbusin2out[sink][0]; + } + if (*path++ & SPLIT_PATH) { + unsigned char *npath = path; + + while (*npath != 0xff) + npath++; + freelbuspath(cif, source + 1, ++npath); + } + } +} + +static int writearm(struct cmdif *cif, u32 addr, u32 data, u32 mask) +{ + union cmdret rptr = CMDRET_ZERO; + unsigned int i = MAX_WRITE_RETRY; + int flag = 1; + + SEND_RMEM(cif, 0x02, addr, &rptr); + rptr.retlongs[0] &= (~mask); + + while (--i) { + SEND_SMEM(cif, 0x01, addr); + SEND_WMEM(cif, 0x02, (rptr.retlongs[0] | data)); + SEND_RMEM(cif, 0x02, addr, &rptr); + if ((rptr.retlongs[0] & data) == data) { + flag = 0; + break; + } else + rptr.retlongs[0] &= ~mask; + } + snd_printdd("send arm 0x%x 0x%x 0x%x return %d\n", addr, data, mask, + flag); + return flag; +} + +static int sendcmd(struct cmdif *cif, u32 flags, u32 cmd, u32 parm, + union cmdret *ret) +{ + int i, j; + int err; + unsigned int time = 0; + unsigned long irqflags; + struct riptideport *hwport; + struct cmdport *cmdport = NULL; + + snd_assert(cif, return -EINVAL); + + hwport = cif->hwport; + if (cif->errcnt > MAX_ERROR_COUNT) { + if (cif->is_reset) { + snd_printk(KERN_ERR + "Riptide: Too many failed cmds, reinitializing\n"); + if (riptide_reset(cif, NULL) == 0) { + cif->errcnt = 0; + return -EIO; + } + } + snd_printk(KERN_ERR "Riptide: Initialization failed.\n"); + return -EINVAL; + } + if (ret) { + ret->retlongs[0] = 0; + ret->retlongs[1] = 0; + } + i = 0; + spin_lock_irqsave(&cif->lock, irqflags); + while (i++ < CMDIF_TIMEOUT && !IS_READY(cif->hwport)) + udelay(10); + if (i >= CMDIF_TIMEOUT) { + err = -EBUSY; + goto errout; + } + + err = 0; + for (j = 0, time = 0; time < CMDIF_TIMEOUT; j++, time += 2) { + cmdport = &(hwport->port[j % 2]); + if (IS_DATF(cmdport)) { /* free pending data */ + READ_PORT_ULONG(cmdport->data1); + READ_PORT_ULONG(cmdport->data2); + } + if (IS_CMDE(cmdport)) { + if (flags & PARM) /* put data */ + WRITE_PORT_ULONG(cmdport->data2, parm); + WRITE_PORT_ULONG(cmdport->data1, cmd); /* write cmd */ + if ((flags & RESP) && ret) { + while (!IS_DATF(cmdport) && + time++ < CMDIF_TIMEOUT) + udelay(10); + if (time < CMDIF_TIMEOUT) { /* read response */ + ret->retlongs[0] = + READ_PORT_ULONG(cmdport->data1); + ret->retlongs[1] = + READ_PORT_ULONG(cmdport->data2); + } else { + err = -ENOSYS; + goto errout; + } + } + break; + } + udelay(20); + } + if (time == CMDIF_TIMEOUT) { + err = -ENODATA; + goto errout; + } + spin_unlock_irqrestore(&cif->lock, irqflags); + + cif->cmdcnt++; /* update command statistics */ + cif->cmdtime += time; + if (time > cif->cmdtimemax) + cif->cmdtimemax = time; + if (time < cif->cmdtimemin) + cif->cmdtimemin = time; + if ((cif->cmdcnt) % 1000 == 0) + snd_printdd + ("send cmd %d time: %d mintime: %d maxtime %d err: %d\n", + cif->cmdcnt, cif->cmdtime, cif->cmdtimemin, + cif->cmdtimemax, cif->errcnt); + return 0; + + errout: + cif->errcnt++; + spin_unlock_irqrestore(&cif->lock, irqflags); + snd_printdd + ("send cmd %d hw: 0x%x flag: 0x%x cmd: 0x%x parm: 0x%x ret: 0x%x 0x%x CMDE: %d DATF: %d failed %d\n", + cif->cmdcnt, (int)((void *)&(cmdport->stat) - (void *)hwport), + flags, cmd, parm, ret ? ret->retlongs[0] : 0, + ret ? ret->retlongs[1] : 0, IS_CMDE(cmdport), IS_DATF(cmdport), + err); + return err; +} + +static int +setmixer(struct cmdif *cif, short num, unsigned short rval, unsigned short lval) +{ + union cmdret rptr = CMDRET_ZERO; + int i = 0; + + snd_printdd("sent mixer %d: 0x%d 0x%d\n", num, rval, lval); + do { + SEND_SDGV(cif, num, num, rval, lval); + SEND_RDGV(cif, num, num, &rptr); + if (rptr.retwords[0] == lval && rptr.retwords[1] == rval) + return 0; + } while (i++ < MAX_WRITE_RETRY); + snd_printdd("sent mixer failed\n"); + return -EIO; +} + +static int getpaths(struct cmdif *cif, unsigned char *o) +{ + unsigned char src[E2SINK_MAX]; + unsigned char sink[E2SINK_MAX]; + int i, j = 0; + + for (i = 0; i < E2SINK_MAX; i++) { + getsourcesink(cif, i, i, &src[i], &sink[i]); + if (sink[i] < E2SINK_MAX) { + o[j++] = sink[i]; + o[j++] = i; + } + } + return j; +} + +static int +getsourcesink(struct cmdif *cif, unsigned char source, unsigned char sink, + unsigned char *a, unsigned char *b) +{ + union cmdret rptr = CMDRET_ZERO; + + if (SEND_RSSV(cif, source, sink, &rptr) && + SEND_RSSV(cif, source, sink, &rptr)) + return -EIO; + *a = rptr.retbytes[0]; + *b = rptr.retbytes[1]; + snd_printdd("getsourcesink 0x%x 0x%x\n", *a, *b); + return 0; +} + +static int +getsamplerate(struct cmdif *cif, unsigned char *intdec, unsigned int *rate) +{ + unsigned char *s; + unsigned int p[2] = { 0, 0 }; + int i; + union cmdret rptr = CMDRET_ZERO; + + s = intdec; + for (i = 0; i < 2; i++) { + if (*s != 0xff) { + if (SEND_RSRC(cif, *s, &rptr) && + SEND_RSRC(cif, *s, &rptr)) + return -EIO; + p[i] += rptr.retwords[1]; + p[i] *= rptr.retwords[2]; + p[i] += rptr.retwords[3]; + p[i] /= 65536; + } + s++; + } + if (p[0]) { + if (p[1] != p[0]) + snd_printdd("rates differ %d %d\n", p[0], p[1]); + *rate = (unsigned int)p[0]; + } else + *rate = (unsigned int)p[1]; + snd_printdd("getsampleformat %d %d %d\n", intdec[0], intdec[1], *rate); + return 0; +} + +static int +setsampleformat(struct cmdif *cif, + unsigned char mixer, unsigned char id, + unsigned char channels, unsigned char format) +{ + unsigned char w, ch, sig, order; + + snd_printdd + ("setsampleformat mixer: %d id: %d channels: %d format: %d\n", + mixer, id, channels, format); + ch = channels == 1; + w = snd_pcm_format_width(format) == 8; + sig = snd_pcm_format_unsigned(format) != 0; + order = snd_pcm_format_big_endian(format) != 0; + + if (SEND_SETF(cif, mixer, w, ch, order, sig, id) && + SEND_SETF(cif, mixer, w, ch, order, sig, id)) { + snd_printdd("setsampleformat failed\n"); + return -EIO; + } + return 0; +} + +static int +setsamplerate(struct cmdif *cif, unsigned char *intdec, unsigned int rate) +{ + u32 D, M, N; + union cmdret rptr = CMDRET_ZERO; + int i; + + snd_printdd("setsamplerate intdec: %d,%d rate: %d\n", intdec[0], + intdec[1], rate); + D = 48000; + M = ((rate == 48000) ? 47999 : rate) * 65536; + N = M % D; + M /= D; + for (i = 0; i < 2; i++) { + if (*intdec != 0xff) { + do { + SEND_SSRC(cif, *intdec, D, M, N); + SEND_RSRC(cif, *intdec, &rptr); + } while (rptr.retwords[1] != D && + rptr.retwords[2] != M && + rptr.retwords[3] != N && + i++ < MAX_WRITE_RETRY); + if (i == MAX_WRITE_RETRY) { + snd_printdd("sent samplerate %d: %d failed\n", + *intdec, rate); + return -EIO; + } + } + intdec++; + } + return 0; +} + +static int +getmixer(struct cmdif *cif, short num, unsigned short *rval, + unsigned short *lval) +{ + union cmdret rptr = CMDRET_ZERO; + + if (SEND_RDGV(cif, num, num, &rptr) && SEND_RDGV(cif, num, num, &rptr)) + return -EIO; + *rval = rptr.retwords[0]; + *lval = rptr.retwords[1]; + snd_printdd("got mixer %d: 0x%d 0x%d\n", num, *rval, *lval); + return 0; +} + +static void riptide_handleirq(unsigned long dev_id) +{ + struct snd_riptide *chip = (void *)dev_id; + struct cmdif *cif = chip->cif; + struct snd_pcm_substream *substream[PLAYBACK_SUBSTREAMS + 1]; + struct snd_pcm_runtime *runtime; + struct pcmhw *data = NULL; + unsigned int pos, period_bytes; + struct sgd *c; + int i, j; + unsigned int flag; + + if (!cif) + return; + + for (i = 0; i < PLAYBACK_SUBSTREAMS; i++) + substream[i] = chip->playback_substream[i]; + substream[i] = chip->capture_substream; + for (i = 0; i < PLAYBACK_SUBSTREAMS + 1; i++) { + if (substream[i] && + (runtime = substream[i]->runtime) && + (data = runtime->private_data) && data->state != ST_STOP) { + pos = 0; + for (j = 0; j < data->pages; j++) { + c = &data->sgdbuf[j]; + flag = le32_to_cpu(c->dwStat_Ctl); + if (flag & EOB_STATUS) + pos += le32_to_cpu(c->dwSegLen); + if (flag & EOC_STATUS) + pos += le32_to_cpu(c->dwSegLen); + if ((flag & EOS_STATUS) + && (data->state == ST_PLAY)) { + data->state = ST_STOP; + snd_printk(KERN_ERR + "Riptide: DMA stopped unexpectedly\n"); + } + c->dwStat_Ctl = + cpu_to_le32(flag & + ~(EOS_STATUS | EOB_STATUS | + EOC_STATUS)); + } + data->pointer += pos; + pos += data->oldpos; + if (data->state != ST_STOP) { + period_bytes = + frames_to_bytes(runtime, + runtime->period_size); + snd_printdd + ("interrupt 0x%x after 0x%lx of 0x%lx frames in period\n", + READ_AUDIO_STATUS(cif->hwport), + bytes_to_frames(runtime, pos), + runtime->period_size); + j = 0; + if (pos >= period_bytes) { + j++; + while (pos >= period_bytes) + pos -= period_bytes; + } + data->oldpos = pos; + if (j > 0) + snd_pcm_period_elapsed(substream[i]); + } + } + } +} + +#ifdef CONFIG_PM +static int riptide_suspend(struct pci_dev *pci, pm_message_t state) +{ + struct snd_card *card = pci_get_drvdata(pci); + struct snd_riptide *chip = card->private_data; + + chip->in_suspend = 1; + snd_power_change_state(card, SNDRV_CTL_POWER_D3hot); + snd_pcm_suspend_all(chip->pcm); + snd_ac97_suspend(chip->ac97); + pci_set_power_state(pci, PCI_D3hot); + pci_disable_device(pci); + pci_save_state(pci); + return 0; +} + +static int riptide_resume(struct pci_dev *pci) +{ + struct snd_card *card = pci_get_drvdata(pci); + struct snd_riptide *chip = card->private_data; + + pci_restore_state(pci); + pci_enable_device(pci); + pci_set_power_state(pci, PCI_D0); + pci_set_master(pci); + snd_riptide_initialize(chip); + snd_ac97_resume(chip->ac97); + snd_power_change_state(card, SNDRV_CTL_POWER_D0); + chip->in_suspend = 0; + return 0; +} +#endif + +static int riptide_reset(struct cmdif *cif, struct snd_riptide *chip) +{ + int timeout, tries; + union cmdret rptr = CMDRET_ZERO; + union firmware_version firmware; + int i, j, err, has_firmware; + + if (!cif) + return -EINVAL; + + cif->cmdcnt = 0; + cif->cmdtime = 0; + cif->cmdtimemax = 0; + cif->cmdtimemin = 0xffffffff; + cif->errcnt = 0; + cif->is_reset = 0; + + tries = RESET_TRIES; + has_firmware = 0; + while (has_firmware == 0 && tries-- > 0) { + for (i = 0; i < 2; i++) { + WRITE_PORT_ULONG(cif->hwport->port[i].data1, 0); + WRITE_PORT_ULONG(cif->hwport->port[i].data2, 0); + } + SET_GRESET(cif->hwport); + udelay(100); + UNSET_GRESET(cif->hwport); + udelay(100); + + for (timeout = 100000; --timeout; udelay(10)) { + if (IS_READY(cif->hwport) && !IS_GERR(cif->hwport)) + break; + } + if (timeout == 0) { + snd_printk(KERN_ERR + "Riptide: device not ready, audio status: 0x%x ready: %d gerr: %d\n", + READ_AUDIO_STATUS(cif->hwport), + IS_READY(cif->hwport), IS_GERR(cif->hwport)); + return -EIO; + } else { + snd_printdd + ("Riptide: audio status: 0x%x ready: %d gerr: %d\n", + READ_AUDIO_STATUS(cif->hwport), + IS_READY(cif->hwport), IS_GERR(cif->hwport)); + } + + SEND_GETV(cif, &rptr); + for (i = 0; i < 4; i++) + firmware.ret.retwords[i] = rptr.retwords[i]; + + snd_printdd + ("Firmware version: ASIC: %d CODEC %d AUXDSP %d PROG %d\n", + firmware.firmware.ASIC, firmware.firmware.CODEC, + firmware.firmware.AUXDSP, firmware.firmware.PROG); + + for (j = 0; j < FIRMWARE_VERSIONS; j++) { + has_firmware = 1; + for (i = 0; i < 4; i++) { + if (firmware_versions[j].ret.retwords[i] != + firmware.ret.retwords[i]) + has_firmware = 0; + } + if (has_firmware) + break; + } + + if (chip != NULL && has_firmware == 0) { + snd_printdd("Writing Firmware\n"); + if (!chip->fw_entry) { + if ((err = + request_firmware(&chip->fw_entry, + "riptide.hex", + &chip->pci->dev)) != 0) { + snd_printk(KERN_ERR + "Riptide: Firmware not available %d\n", + err); + return -EIO; + } + } + err = loadfirmware(cif, chip->fw_entry->data, + chip->fw_entry->size); + if (err) + snd_printk(KERN_ERR + "Riptide: Could not load firmware %d\n", + err); + } + } + + SEND_SACR(cif, 0, AC97_RESET); + SEND_RACR(cif, AC97_RESET, &rptr); + snd_printdd("AC97: 0x%x 0x%x\n", rptr.retlongs[0], rptr.retlongs[1]); + + SEND_PLST(cif, 0); + SEND_SLST(cif, 0); + SEND_DLST(cif, 0); + SEND_ALST(cif, 0); + SEND_KDMA(cif); + + writearm(cif, 0x301F8, 1, 1); + writearm(cif, 0x301F4, 1, 1); + + SEND_LSEL(cif, MODEM_CMD, 0, 0, MODEM_INTDEC, MODEM_MERGER, + MODEM_SPLITTER, MODEM_MIXER); + setmixer(cif, MODEM_MIXER, 0x7fff, 0x7fff); + alloclbuspath(cif, ARM2LBUS_FIFO13, lbus_play_modem, NULL, NULL); + + SEND_LSEL(cif, FM_CMD, 0, 0, FM_INTDEC, FM_MERGER, FM_SPLITTER, + FM_MIXER); + setmixer(cif, FM_MIXER, 0x7fff, 0x7fff); + writearm(cif, 0x30648 + FM_MIXER * 4, 0x01, 0x00000005); + writearm(cif, 0x301A8, 0x02, 0x00000002); + writearm(cif, 0x30264, 0x08, 0xffffffff); + alloclbuspath(cif, OPL3_SAMPLE, lbus_play_opl3, NULL, NULL); + + SEND_SSRC(cif, I2S_INTDEC, 48000, + ((u32) I2S_RATE * 65536) / 48000, + ((u32) I2S_RATE * 65536) % 48000); + SEND_LSEL(cif, I2S_CMD0, 0, 0, I2S_INTDEC, I2S_MERGER, I2S_SPLITTER, + I2S_MIXER); + SEND_SI2S(cif, 1); + alloclbuspath(cif, ARM2LBUS_FIFO0, lbus_play_i2s, NULL, NULL); + alloclbuspath(cif, DIGITAL_MIXER_OUT0, lbus_play_out, NULL, NULL); + alloclbuspath(cif, DIGITAL_MIXER_OUT0, lbus_play_outhp, NULL, NULL); + + SET_AIACK(cif->hwport); + SET_AIE(cif->hwport); + SET_AIACK(cif->hwport); + cif->is_reset = 1; + if (chip) { + for (i = 0; i < 4; i++) + chip->firmware.ret.retwords[i] = + firmware.ret.retwords[i]; + } + + return 0; +} + +static struct snd_pcm_hardware snd_riptide_playback = { + .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_MMAP_VALID), + .formats = + SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8 + | SNDRV_PCM_FMTBIT_U16_LE, + .rates = SNDRV_PCM_RATE_KNOT | SNDRV_PCM_RATE_8000_48000, + .rate_min = 5500, + .rate_max = 48000, + .channels_min = 1, + .channels_max = 2, + .buffer_bytes_max = (64 * 1024), + .period_bytes_min = PAGE_SIZE >> 1, + .period_bytes_max = PAGE_SIZE << 8, + .periods_min = 2, + .periods_max = 64, + .fifo_size = 0, +}; +static struct snd_pcm_hardware snd_riptide_capture = { + .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_MMAP_VALID), + .formats = + SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8 + | SNDRV_PCM_FMTBIT_U16_LE, + .rates = SNDRV_PCM_RATE_KNOT | SNDRV_PCM_RATE_8000_48000, + .rate_min = 5500, + .rate_max = 48000, + .channels_min = 1, + .channels_max = 2, + .buffer_bytes_max = (64 * 1024), + .period_bytes_min = PAGE_SIZE >> 1, + .period_bytes_max = PAGE_SIZE << 3, + .periods_min = 2, + .periods_max = 64, + .fifo_size = 0, +}; + +static snd_pcm_uframes_t snd_riptide_pointer(struct snd_pcm_substream + *substream) +{ + struct snd_riptide *chip = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct pcmhw *data = get_pcmhwdev(substream); + struct cmdif *cif = chip->cif; + union cmdret rptr = CMDRET_ZERO; + snd_pcm_uframes_t ret; + + SEND_GPOS(cif, 0, data->id, &rptr); + if (data->size && runtime->period_size) { + snd_printdd + ("pointer stream %d position 0x%x(0x%x in buffer) bytes 0x%lx(0x%lx in period) frames\n", + data->id, rptr.retlongs[1], rptr.retlongs[1] % data->size, + bytes_to_frames(runtime, rptr.retlongs[1]), + bytes_to_frames(runtime, + rptr.retlongs[1]) % runtime->period_size); + if (rptr.retlongs[1] > data->pointer) + ret = + bytes_to_frames(runtime, + rptr.retlongs[1] % data->size); + else + ret = + bytes_to_frames(runtime, + data->pointer % data->size); + } else { + snd_printdd("stream not started or strange parms (%d %ld)\n", + data->size, runtime->period_size); + ret = bytes_to_frames(runtime, 0); + } + return ret; +} + +static int snd_riptide_trigger(struct snd_pcm_substream *substream, int cmd) +{ + int i, j; + struct snd_riptide *chip = snd_pcm_substream_chip(substream); + struct pcmhw *data = get_pcmhwdev(substream); + struct cmdif *cif = chip->cif; + union cmdret rptr = CMDRET_ZERO; + + spin_lock(&chip->lock); + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + if (!(data->state & ST_PLAY)) { + SEND_SSTR(cif, data->id, data->sgdlist.addr); + SET_AIE(cif->hwport); + data->state = ST_PLAY; + if (data->mixer != 0xff) + setmixer(cif, data->mixer, 0x7fff, 0x7fff); + chip->openstreams++; + data->oldpos = 0; + data->pointer = 0; + } + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + if (data->mixer != 0xff) + setmixer(cif, data->mixer, 0, 0); + setmixer(cif, data->mixer, 0, 0); + SEND_KSTR(cif, data->id); + data->state = ST_STOP; + chip->openstreams--; + j = 0; + do { + i = rptr.retlongs[1]; + SEND_GPOS(cif, 0, data->id, &rptr); + udelay(1); + } while (i != rptr.retlongs[1] && j++ < MAX_WRITE_RETRY); + if (j >= MAX_WRITE_RETRY) + snd_printk(KERN_ERR "Riptide: Could not stop stream!"); + break; + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + if (!(data->state & ST_PAUSE)) { + SEND_PSTR(cif, data->id); + data->state |= ST_PAUSE; + chip->openstreams--; + } + break; + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + if (data->state & ST_PAUSE) { + SEND_SSTR(cif, data->id, data->sgdlist.addr); + data->state &= ~ST_PAUSE; + chip->openstreams++; + } + break; + default: + spin_unlock(&chip->lock); + return -EINVAL; + } + spin_unlock(&chip->lock); + return 0; +} + +static int snd_riptide_prepare(struct snd_pcm_substream *substream) +{ + struct snd_riptide *chip = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_sg_buf *sgbuf = snd_pcm_substream_sgbuf(substream); + struct pcmhw *data = get_pcmhwdev(substream); + struct cmdif *cif = chip->cif; + unsigned char *lbuspath = NULL; + unsigned int rate, channels; + int err = 0; + snd_pcm_format_t format; + + snd_assert(cif && data, return -EINVAL); + + snd_printdd("prepare id %d ch: %d f:0x%x r:%d\n", data->id, + runtime->channels, runtime->format, runtime->rate); + + spin_lock_irq(&chip->lock); + channels = runtime->channels; + format = runtime->format; + rate = runtime->rate; + switch (channels) { + case 1: + if (rate == 48000 && format == SNDRV_PCM_FORMAT_S16_LE) + lbuspath = data->paths.noconv; + else + lbuspath = data->paths.mono; + break; + case 2: + if (rate == 48000 && format == SNDRV_PCM_FORMAT_S16_LE) + lbuspath = data->paths.noconv; + else + lbuspath = data->paths.stereo; + break; + } + snd_printdd("use sgdlist at 0x%p and buffer at 0x%p\n", + data->sgdlist.area, sgbuf); + if (data->sgdlist.area && sgbuf) { + unsigned int i, j, size, pages, f, pt, period; + struct sgd *c, *p = NULL; + + size = frames_to_bytes(runtime, runtime->buffer_size); + period = frames_to_bytes(runtime, runtime->period_size); + f = PAGE_SIZE; + while ((size + (f >> 1) - 1) <= (f << 7) && (f << 1) > period) + f = f >> 1; + pages = (size + f - 1) / f; + data->size = size; + data->pages = pages; + snd_printdd + ("create sgd size: 0x%x pages %d of size 0x%x for period 0x%x\n", + size, pages, f, period); + pt = 0; + j = 0; + for (i = 0; i < pages; i++) { + c = &data->sgdbuf[i]; + if (p) + p->dwNextLink = cpu_to_le32(data->sgdlist.addr + + (i * + sizeof(struct + sgd))); + c->dwNextLink = cpu_to_le32(data->sgdlist.addr); + c->dwSegPtrPhys = + cpu_to_le32(sgbuf->table[j].addr + pt); + pt = (pt + f) % PAGE_SIZE; + if (pt == 0) + j++; + c->dwSegLen = cpu_to_le32(f); + c->dwStat_Ctl = + cpu_to_le32(IEOB_ENABLE | IEOS_ENABLE | + IEOC_ENABLE); + p = c; + size -= f; + } + data->sgdbuf[i].dwSegLen = cpu_to_le32(size); + } + if (lbuspath && lbuspath != data->lbuspath) { + if (data->lbuspath) + freelbuspath(cif, data->source, data->lbuspath); + alloclbuspath(cif, data->source, lbuspath, + &data->mixer, data->intdec); + data->lbuspath = lbuspath; + data->rate = 0; + } + if (data->rate != rate || data->format != format || + data->channels != channels) { + data->rate = rate; + data->format = format; + data->channels = channels; + if (setsampleformat + (cif, data->mixer, data->id, channels, format) + || setsamplerate(cif, data->intdec, rate)) + err = -EIO; + } + spin_unlock_irq(&chip->lock); + return err; +} + +static int +snd_riptide_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct snd_riptide *chip = snd_pcm_substream_chip(substream); + struct pcmhw *data = get_pcmhwdev(substream); + struct snd_dma_buffer *sgdlist = &data->sgdlist; + int err; + + snd_printdd("hw params id %d (sgdlist: 0x%p 0x%lx %d)\n", data->id, + sgdlist->area, (unsigned long)sgdlist->addr, + (int)sgdlist->bytes); + if (sgdlist->area) + snd_dma_free_pages(sgdlist); + if ((err = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, + snd_dma_pci_data(chip->pci), + sizeof(struct sgd) * (DESC_MAX_MASK + 1), + sgdlist)) < 0) { + snd_printk(KERN_ERR "Riptide: failed to alloc %d dma bytes\n", + (int)sizeof(struct sgd) * (DESC_MAX_MASK + 1)); + return err; + } + data->sgdbuf = (struct sgd *)sgdlist->area; + return snd_pcm_lib_malloc_pages(substream, + params_buffer_bytes(hw_params)); +} + +static int snd_riptide_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_riptide *chip = snd_pcm_substream_chip(substream); + struct pcmhw *data = get_pcmhwdev(substream); + struct cmdif *cif = chip->cif; + + if (cif && data) { + if (data->lbuspath) + freelbuspath(cif, data->source, data->lbuspath); + data->lbuspath = NULL; + data->source = 0xff; + data->intdec[0] = 0xff; + data->intdec[1] = 0xff; + + if (data->sgdlist.area) { + snd_dma_free_pages(&data->sgdlist); + data->sgdlist.area = NULL; + } + } + return snd_pcm_lib_free_pages(substream); +} + +static int snd_riptide_playback_open(struct snd_pcm_substream *substream) +{ + struct snd_riptide *chip = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct pcmhw *data; + int index = substream->number; + + chip->playback_substream[index] = substream; + runtime->hw = snd_riptide_playback; + data = kzalloc(sizeof(struct pcmhw), GFP_KERNEL); + data->paths = lbus_play_paths[index]; + data->id = play_ids[index]; + data->source = play_sources[index]; + data->intdec[0] = 0xff; + data->intdec[1] = 0xff; + data->state = ST_STOP; + runtime->private_data = data; + return snd_pcm_hw_constraint_integer(runtime, + SNDRV_PCM_HW_PARAM_PERIODS); +} + +static int snd_riptide_capture_open(struct snd_pcm_substream *substream) +{ + struct snd_riptide *chip = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct pcmhw *data; + + chip->capture_substream = substream; + runtime->hw = snd_riptide_capture; + data = kzalloc(sizeof(struct pcmhw), GFP_KERNEL); + data->paths = lbus_rec_path; + data->id = PADC; + data->source = ACLNK2PADC; + data->intdec[0] = 0xff; + data->intdec[1] = 0xff; + data->state = ST_STOP; + runtime->private_data = data; + return snd_pcm_hw_constraint_integer(runtime, + SNDRV_PCM_HW_PARAM_PERIODS); +} + +static int snd_riptide_playback_close(struct snd_pcm_substream *substream) +{ + struct snd_riptide *chip = snd_pcm_substream_chip(substream); + struct pcmhw *data = get_pcmhwdev(substream); + int index = substream->number; + + substream->runtime->private_data = NULL; + chip->playback_substream[index] = NULL; + kfree(data); + return 0; +} + +static int snd_riptide_capture_close(struct snd_pcm_substream *substream) +{ + struct snd_riptide *chip = snd_pcm_substream_chip(substream); + struct pcmhw *data = get_pcmhwdev(substream); + + substream->runtime->private_data = NULL; + chip->capture_substream = NULL; + kfree(data); + return 0; +} + +static struct snd_pcm_ops snd_riptide_playback_ops = { + .open = snd_riptide_playback_open, + .close = snd_riptide_playback_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_riptide_hw_params, + .hw_free = snd_riptide_hw_free, + .prepare = snd_riptide_prepare, + .page = snd_pcm_sgbuf_ops_page, + .trigger = snd_riptide_trigger, + .pointer = snd_riptide_pointer, +}; +static struct snd_pcm_ops snd_riptide_capture_ops = { + .open = snd_riptide_capture_open, + .close = snd_riptide_capture_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_riptide_hw_params, + .hw_free = snd_riptide_hw_free, + .prepare = snd_riptide_prepare, + .page = snd_pcm_sgbuf_ops_page, + .trigger = snd_riptide_trigger, + .pointer = snd_riptide_pointer, +}; + +static int __devinit +snd_riptide_pcm(struct snd_riptide *chip, int device, struct snd_pcm **rpcm) +{ + struct snd_pcm *pcm; + int err; + + if (rpcm) + *rpcm = NULL; + if ((err = + snd_pcm_new(chip->card, "RIPTIDE", device, PLAYBACK_SUBSTREAMS, 1, + &pcm)) < 0) + return err; + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, + &snd_riptide_playback_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, + &snd_riptide_capture_ops); + pcm->private_data = chip; + pcm->info_flags = 0; + strcpy(pcm->name, "RIPTIDE"); + chip->pcm = pcm; + snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV_SG, + snd_dma_pci_data(chip->pci), + 64 * 1024, 128 * 1024); + if (rpcm) + *rpcm = pcm; + return 0; +} + +static irqreturn_t +snd_riptide_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + struct snd_riptide *chip = dev_id; + struct cmdif *cif = chip->cif; + + if (cif) { + chip->received_irqs++; + if (IS_EOBIRQ(cif->hwport) || IS_EOSIRQ(cif->hwport) || + IS_EOCIRQ(cif->hwport)) { + chip->handled_irqs++; + tasklet_hi_schedule(&chip->riptide_tq); + } + if (chip->rmidi && IS_MPUIRQ(cif->hwport)) { + chip->handled_irqs++; + snd_mpu401_uart_interrupt(irq, + chip->rmidi->private_data, + regs); + } + SET_AIACK(cif->hwport); + } + return IRQ_HANDLED; +} + +static void +snd_riptide_codec_write(struct snd_ac97 *ac97, unsigned short reg, + unsigned short val) +{ + struct snd_riptide *chip = ac97->private_data; + struct cmdif *cif = chip->cif; + union cmdret rptr = CMDRET_ZERO; + int i = 0; + + snd_assert(cif, return); + + snd_printdd("Write AC97 reg 0x%x 0x%x\n", reg, val); + do { + SEND_SACR(cif, val, reg); + SEND_RACR(cif, reg, &rptr); + } while (rptr.retwords[1] != val && i++ < MAX_WRITE_RETRY); + if (i == MAX_WRITE_RETRY) + snd_printdd("Write AC97 reg failed\n"); +} + +static unsigned short snd_riptide_codec_read(struct snd_ac97 *ac97, + unsigned short reg) +{ + struct snd_riptide *chip = ac97->private_data; + struct cmdif *cif = chip->cif; + union cmdret rptr = CMDRET_ZERO; + + snd_assert(cif, return 0); + + if (SEND_RACR(cif, reg, &rptr) != 0) + SEND_RACR(cif, reg, &rptr); + snd_printdd("Read AC97 reg 0x%x got 0x%x\n", reg, rptr.retwords[1]); + return rptr.retwords[1]; +} + +static int snd_riptide_initialize(struct snd_riptide *chip) +{ + struct cmdif *cif; + unsigned int device_id; + int err; + + snd_assert(chip, return -EINVAL); + + cif = chip->cif; + if (!cif) { + if ((cif = kzalloc(sizeof(struct cmdif), GFP_KERNEL)) == NULL) + return -ENOMEM; + cif->hwport = (struct riptideport *)chip->port; + spin_lock_init(&cif->lock); + chip->cif = cif; + } + cif->is_reset = 0; + if ((err = riptide_reset(cif, chip)) != 0) + return err; + device_id = chip->device_id; + switch (device_id) { + case 0x4310: + case 0x4320: + case 0x4330: + snd_printdd("Modem enable?\n"); + SEND_SETDPLL(cif); + break; + } + snd_printdd("Enabling MPU IRQs\n"); + if (chip->rmidi) + SET_EMPUIRQ(cif->hwport); + return err; +} + +static int snd_riptide_free(struct snd_riptide *chip) +{ + struct cmdif *cif; + + snd_assert(chip, return 0); + + if ((cif = chip->cif)) { + SET_GRESET(cif->hwport); + udelay(100); + UNSET_GRESET(cif->hwport); + kfree(chip->cif); + } + if (chip->fw_entry) + release_firmware(chip->fw_entry); + if (chip->res_port) { + release_resource(chip->res_port); + kfree_nocheck(chip->res_port); + } + if (chip->irq >= 0) + free_irq(chip->irq, chip); + kfree(chip); + return 0; +} + +static int snd_riptide_dev_free(struct snd_device *device) +{ + struct snd_riptide *chip = device->device_data; + + return snd_riptide_free(chip); +} + +static int __devinit +snd_riptide_create(struct snd_card *card, struct pci_dev *pci, + struct snd_riptide **rchip) +{ + struct snd_riptide *chip; + struct riptideport *hwport; + int err; + static struct snd_device_ops ops = { + .dev_free = snd_riptide_dev_free, + }; + + *rchip = NULL; + if ((err = pci_enable_device(pci)) < 0) + return err; + if (!(chip = kzalloc(sizeof(struct snd_riptide), GFP_KERNEL))) + return -ENOMEM; + + spin_lock_init(&chip->lock); + chip->card = card; + chip->pci = pci; + chip->irq = -1; + chip->openstreams = 0; + chip->port = pci_resource_start(pci, 0); + chip->received_irqs = 0; + chip->handled_irqs = 0; + chip->cif = NULL; + tasklet_init(&chip->riptide_tq, riptide_handleirq, (unsigned long)chip); + + if ((chip->res_port = + request_region(chip->port, 64, "RIPTIDE")) == NULL) { + snd_printk(KERN_ERR + "Riptide: unable to grab region 0x%lx-0x%lx\n", + chip->port, chip->port + 64 - 1); + snd_riptide_free(chip); + return -EBUSY; + } + hwport = (struct riptideport *)chip->port; + UNSET_AIE(hwport); + + if (request_irq + (pci->irq, snd_riptide_interrupt, SA_INTERRUPT | SA_SHIRQ, + "RIPTIDE", chip)) { + snd_printk(KERN_ERR "Riptide: unable to grab IRQ %d\n", + pci->irq); + snd_riptide_free(chip); + return -EBUSY; + } + chip->irq = pci->irq; + chip->device_id = pci->device; + pci_set_master(pci); + if ((err = snd_riptide_initialize(chip)) < 0) { + snd_riptide_free(chip); + return err; + } + + if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops)) < 0) { + snd_riptide_free(chip); + return err; + } + + *rchip = chip; + return 0; +} + +static void +snd_riptide_proc_read(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_riptide *chip = entry->private_data; + struct pcmhw *data; + int i; + struct cmdif *cif = NULL; + unsigned char p[256]; + unsigned short rval = 0, lval = 0; + unsigned int rate; + + if (!chip) + return; + + snd_iprintf(buffer, "%s\n\n", chip->card->longname); + snd_iprintf(buffer, "Device ID: 0x%x\nReceived IRQs: (%ld)%ld\nPorts:", + chip->device_id, chip->handled_irqs, chip->received_irqs); + for (i = 0; i < 64; i += 4) + snd_iprintf(buffer, "%c%02x: %08x", + (i % 16) ? ' ' : '\n', i, inl(chip->port + i)); + if ((cif = chip->cif)) { + snd_iprintf(buffer, + "\nVersion: ASIC: %d CODEC: %d AUXDSP: %d PROG: %d", + chip->firmware.firmware.ASIC, + chip->firmware.firmware.CODEC, + chip->firmware.firmware.AUXDSP, + chip->firmware.firmware.PROG); + snd_iprintf(buffer, "\nDigital mixer:"); + for (i = 0; i < 12; i++) { + getmixer(cif, i, &rval, &lval); + snd_iprintf(buffer, "\n %d: %d %d", i, rval, lval); + } + snd_iprintf(buffer, + "\nARM Commands num: %d failed: %d time: %d max: %d min: %d", + cif->cmdcnt, cif->errcnt, + cif->cmdtime, cif->cmdtimemax, cif->cmdtimemin); + } + snd_iprintf(buffer, "\nOpen streams %d:\n", chip->openstreams); + for (i = 0; i < PLAYBACK_SUBSTREAMS; i++) { + if (chip->playback_substream[i] + && chip->playback_substream[i]->runtime + && (data = + chip->playback_substream[i]->runtime->private_data)) { + snd_iprintf(buffer, + "stream: %d mixer: %d source: %d (%d,%d)\n", + data->id, data->mixer, data->source, + data->intdec[0], data->intdec[1]); + if (!(getsamplerate(cif, data->intdec, &rate))) + snd_iprintf(buffer, "rate: %d\n", rate); + } + } + if (chip->capture_substream + && chip->capture_substream->runtime + && (data = chip->capture_substream->runtime->private_data)) { + snd_iprintf(buffer, + "stream: %d mixer: %d source: %d (%d,%d)\n", + data->id, data->mixer, + data->source, data->intdec[0], data->intdec[1]); + if (!(getsamplerate(cif, data->intdec, &rate))) + snd_iprintf(buffer, "rate: %d\n", rate); + } + snd_iprintf(buffer, "Paths:\n"); + i = getpaths(cif, p); + while (i--) { + snd_iprintf(buffer, "%x->%x ", p[i - 1], p[i]); + i--; + } + snd_iprintf(buffer, "\n"); +} + +static void __devinit snd_riptide_proc_init(struct snd_riptide *chip) +{ + struct snd_info_entry *entry; + + if (!snd_card_proc_new(chip->card, "riptide", &entry)) + snd_info_set_text_ops(entry, chip, 4096, snd_riptide_proc_read); +} + +static int __devinit snd_riptide_mixer(struct snd_riptide *chip) +{ + struct snd_ac97_bus *pbus; + struct snd_ac97_template ac97; + int err = 0; + static struct snd_ac97_bus_ops ops = { + .write = snd_riptide_codec_write, + .read = snd_riptide_codec_read, + }; + + memset(&ac97, 0, sizeof(ac97)); + ac97.private_data = chip; + ac97.scaps = AC97_SCAP_SKIP_MODEM; + + if ((err = snd_ac97_bus(chip->card, 0, &ops, chip, &pbus)) < 0) + return err; + + chip->ac97_bus = pbus; + ac97.pci = chip->pci; + if ((err = snd_ac97_mixer(pbus, &ac97, &chip->ac97)) < 0) + return err; + return err; +} + +#ifdef SUPPORT_JOYSTICK +static int have_joystick; +static struct pci_dev *riptide_gameport_pci; +static struct gameport *riptide_gameport; + +static int __devinit +snd_riptide_joystick_probe(struct pci_dev *pci, const struct pci_device_id *id) +{ + static int dev; + + if (dev >= SNDRV_CARDS) + return -ENODEV; + if (!enable[dev]) { + dev++; + return -ENOENT; + } + + if (joystick_port[dev]) { + riptide_gameport = gameport_allocate_port(); + if (riptide_gameport) { + if (!request_region + (joystick_port[dev], 8, "Riptide gameport")) { + snd_printk(KERN_WARNING + "Riptide: cannot grab gameport 0x%x\n", + joystick_port[dev]); + gameport_free_port(riptide_gameport); + riptide_gameport = NULL; + } else { + riptide_gameport_pci = pci; + riptide_gameport->io = joystick_port[dev]; + gameport_register_port(riptide_gameport); + } + } + } + dev++; + return 0; +} + +static void __devexit snd_riptide_joystick_remove(struct pci_dev *pci) +{ + if (riptide_gameport) { + if (riptide_gameport_pci == pci) { + release_region(riptide_gameport->io, 8); + riptide_gameport_pci = NULL; + gameport_unregister_port(riptide_gameport); + riptide_gameport = NULL; + } + } +} +#endif + +static int __devinit +snd_card_riptide_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) +{ + static int dev; + struct snd_card *card; + struct snd_riptide *chip; + unsigned short addr; + int err = 0; + + if (dev >= SNDRV_CARDS) + return -ENODEV; + if (!enable[dev]) { + dev++; + return -ENOENT; + } + + card = snd_card_new(index[dev], id[dev], THIS_MODULE, 0); + if (card == NULL) + return -ENOMEM; + if ((err = snd_riptide_create(card, pci, &chip)) < 0) { + snd_card_free(card); + return err; + } + card->private_data = chip; + if ((err = snd_riptide_pcm(chip, 0, NULL)) < 0) { + snd_card_free(card); + return err; + } + if ((err = snd_riptide_mixer(chip)) < 0) { + snd_card_free(card); + return err; + } + pci_write_config_word(chip->pci, PCI_EXT_Legacy_Mask, LEGACY_ENABLE_ALL + | (opl3_port[dev] ? LEGACY_ENABLE_FM : 0) +#ifdef SUPPORT_JOYSTICK + | (joystick_port[dev] ? LEGACY_ENABLE_GAMEPORT : + 0) +#endif + | (mpu_port[dev] + ? (LEGACY_ENABLE_MPU_INT | LEGACY_ENABLE_MPU) : + 0) + | ((chip->irq << 4) & 0xF0)); + if ((addr = mpu_port[dev]) != 0) { + pci_write_config_word(chip->pci, PCI_EXT_MPU_Base, addr); + if ((err = snd_mpu401_uart_new(card, 0, MPU401_HW_RIPTIDE, + addr, 0, chip->irq, 0, + &chip->rmidi)) < 0) + snd_printk(KERN_WARNING + "Riptide: Can't Allocate MPU at 0x%x\n", + addr); + else + chip->mpuaddr = addr; + } + if ((addr = opl3_port[dev]) != 0) { + pci_write_config_word(chip->pci, PCI_EXT_FM_Base, addr); + if ((err = snd_opl3_create(card, addr, addr + 2, + OPL3_HW_RIPTIDE, 0, + &chip->opl3)) < 0) + snd_printk(KERN_WARNING + "Riptide: Can't Allocate OPL3 at 0x%x\n", + addr); + else { + chip->opladdr = addr; + if ((err = + snd_opl3_hwdep_new(chip->opl3, 0, 1, NULL)) < 0) + snd_printk(KERN_WARNING + "Riptide: Can't Allocate OPL3-HWDEP\n"); + } + } +#ifdef SUPPORT_JOYSTICK + if ((addr = joystick_port[dev]) != 0) { + pci_write_config_word(chip->pci, PCI_EXT_Game_Base, addr); + chip->gameaddr = addr; + } +#endif + + strcpy(card->driver, "RIPTIDE"); + strcpy(card->shortname, "Riptide"); +#ifdef SUPPORT_JOYSTICK + snprintf(card->longname, sizeof(card->longname), + "%s at 0x%lx, irq %i mpu 0x%x opl3 0x%x gameport 0x%x", + card->shortname, chip->port, chip->irq, chip->mpuaddr, + chip->opladdr, chip->gameaddr); +#else + snprintf(card->longname, sizeof(card->longname), + "%s at 0x%lx, irq %i mpu 0x%x opl3 0x%x", + card->shortname, chip->port, chip->irq, chip->mpuaddr, + chip->opladdr); +#endif + snd_riptide_proc_init(chip); + if ((err = snd_card_register(card)) < 0) { + snd_card_free(card); + return err; + } + pci_set_drvdata(pci, card); + dev++; + return 0; +} + +static void __devexit snd_card_riptide_remove(struct pci_dev *pci) +{ + snd_card_free(pci_get_drvdata(pci)); + pci_set_drvdata(pci, NULL); +} + +static struct pci_driver driver = { + .name = "RIPTIDE", + .id_table = snd_riptide_ids, + .probe = snd_card_riptide_probe, + .remove = __devexit_p(snd_card_riptide_remove), +#ifdef CONFIG_PM + .suspend = riptide_suspend, + .resume = riptide_resume, +#endif +}; + +#ifdef SUPPORT_JOYSTICK +static struct pci_driver joystick_driver = { + .name = "Riptide Joystick", + .id_table = snd_riptide_joystick_ids, + .probe = snd_riptide_joystick_probe, + .remove = __devexit_p(snd_riptide_joystick_remove), +}; +#endif + +static int __init alsa_card_riptide_init(void) +{ + int err; + if ((err = pci_register_driver(&driver)) < 0) + return err; +#if defined(SUPPORT_JOYSTICK) + if (pci_register_driver(&joystick_driver) < 0) { + have_joystick = 0; + snd_printk(KERN_INFO "no joystick found\n"); + } else + have_joystick = 1; +#endif + return 0; +} + +static void __exit alsa_card_riptide_exit(void) +{ + pci_unregister_driver(&driver); +#if defined(SUPPORT_JOYSTICK) + if (have_joystick) + pci_unregister_driver(&joystick_driver); +#endif +} + +module_init(alsa_card_riptide_init); +module_exit(alsa_card_riptide_exit); -- cgit v1.2.3-59-g8ed1b From b3a70d5ece60684c00d7d94ccc42741efdf99336 Mon Sep 17 00:00:00 2001 From: Ash Willis Date: Mon, 27 Mar 2006 13:20:40 +0200 Subject: [ALSA] Add snd-als300 driver for Avance Logic ALS300/ALS300+ soundcards Added snd-als300 driver for Avance Logic ALS300/ALS300+ soundcards by Ash Willis. Signed-off-by: Takashi Iwai --- Documentation/sound/alsa/ALSA-Configuration.txt | 9 + sound/pci/Kconfig | 12 + sound/pci/Makefile | 2 + sound/pci/als300.c | 866 ++++++++++++++++++++++++ 4 files changed, 889 insertions(+) create mode 100644 sound/pci/als300.c diff --git a/Documentation/sound/alsa/ALSA-Configuration.txt b/Documentation/sound/alsa/ALSA-Configuration.txt index 5f79efa777a7..a8c3c7e847cf 100644 --- a/Documentation/sound/alsa/ALSA-Configuration.txt +++ b/Documentation/sound/alsa/ALSA-Configuration.txt @@ -190,6 +190,15 @@ Prior to version 0.9.0rc4 options had a 'snd_' prefix. This was removed. The power-management is supported. + Module snd-als300 + ----------------- + + Module for Avance Logic ALS300 and ALS300+ + + This module supports multiple cards. + + The power-management is supported. + Module snd-als4000 ------------------ diff --git a/sound/pci/Kconfig b/sound/pci/Kconfig index 933ce36a0bbb..a2081803a827 100644 --- a/sound/pci/Kconfig +++ b/sound/pci/Kconfig @@ -15,6 +15,18 @@ config SND_AD1889 To compile this as a module, choose M here: the module will be called snd-ad1889. +config SND_ALS300 + tristate "Avance Logic ALS300/ALS300+" + depends on SND + select SND_PCM + select SND_AC97_CODEC + select SND_OPL3_LIB + help + Say 'Y' or 'M' to include support for Avance Logic ALS300/ALS300+ + + To compile this driver as a module, choose M here: the module + will be called snd-als300 + config SND_ALS4000 tristate "Avance Logic ALS4000" depends on SND && ISA_DMA_API diff --git a/sound/pci/Makefile b/sound/pci/Makefile index daa4253ed65f..cba5105aafea 100644 --- a/sound/pci/Makefile +++ b/sound/pci/Makefile @@ -4,6 +4,7 @@ # snd-ad1889-objs := ad1889.o +snd-als300-objs := als300.o snd-als4000-objs := als4000.o snd-atiixp-objs := atiixp.o snd-atiixp-modem-objs := atiixp_modem.o @@ -27,6 +28,7 @@ snd-via82xx-modem-objs := via82xx_modem.o # Toplevel Module Dependency obj-$(CONFIG_SND_AD1889) += snd-ad1889.o +obj-$(CONFIG_SND_ALS300) += snd-als300.o obj-$(CONFIG_SND_ALS4000) += snd-als4000.o obj-$(CONFIG_SND_ATIIXP) += snd-atiixp.o obj-$(CONFIG_SND_ATIIXP_MODEM) += snd-atiixp-modem.o diff --git a/sound/pci/als300.c b/sound/pci/als300.c new file mode 100644 index 000000000000..37b80570a5c6 --- /dev/null +++ b/sound/pci/als300.c @@ -0,0 +1,866 @@ +/* + * als300.c - driver for Avance Logic ALS300/ALS300+ soundcards. + * Copyright (C) 2005 by Ash Willis + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * TODO + * 4 channel playback for ALS300+ + * gameport + * mpu401 + * opl3 + * + * NOTES + * The BLOCK_COUNTER registers for the ALS300(+) return a figure related to + * the position in the current period, NOT the whole buffer. It is important + * to know which period we are in so we can calculate the correct pointer. + * This is why we always use 2 periods. We can then use a flip-flop variable + * to keep track of what period we are in. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + +/* snd_als300_set_irq_flag */ +#define IRQ_DISABLE 0 +#define IRQ_ENABLE 1 + +/* I/O port layout */ +#define AC97_ACCESS 0x00 +#define AC97_READ 0x04 +#define AC97_STATUS 0x06 +#define AC97_DATA_AVAIL (1<<6) +#define AC97_BUSY (1<<7) +#define ALS300_IRQ_STATUS 0x07 /* ALS300 Only */ +#define IRQ_PLAYBACK (1<<3) +#define IRQ_CAPTURE (1<<2) +#define GCR_DATA 0x08 +#define GCR_INDEX 0x0C +#define ALS300P_DRAM_IRQ_STATUS 0x0D /* ALS300+ Only */ +#define MPU_IRQ_STATUS 0x0E /* ALS300 Rev. E+, ALS300+ */ +#define ALS300P_IRQ_STATUS 0x0F /* ALS300+ Only */ + +/* General Control Registers */ +#define PLAYBACK_START 0x80 +#define PLAYBACK_END 0x81 +#define PLAYBACK_CONTROL 0x82 +#define TRANSFER_START (1<<16) +#define FIFO_PAUSE (1<<17) +#define RECORD_START 0x83 +#define RECORD_END 0x84 +#define RECORD_CONTROL 0x85 +#define DRAM_WRITE_CONTROL 0x8B +#define WRITE_TRANS_START (1<<16) +#define DRAM_MODE_2 (1<<17) +#define MISC_CONTROL 0x8C +#define IRQ_SET_BIT (1<<15) +#define VMUTE_NORMAL (1<<20) +#define MMUTE_NORMAL (1<<21) +#define MUS_VOC_VOL 0x8E +#define PLAYBACK_BLOCK_COUNTER 0x9A +#define RECORD_BLOCK_COUNTER 0x9B + +#define DEBUG_CALLS 1 +#define DEBUG_PLAY_REC 1 + +#if DEBUG_CALLS +#define snd_als300_dbgcalls(format, args...) printk(format, ##args) +#define snd_als300_dbgcallenter() printk(KERN_ERR "--> %s\n", __FUNCTION__) +#define snd_als300_dbgcallleave() printk(KERN_ERR "<-- %s\n", __FUNCTION__) +#else +#define snd_als300_dbgcalls(format, args...) +#define snd_als300_dbgcallenter() +#define snd_als300_dbgcallleave() +#endif + +#if DEBUG_PLAY_REC +#define snd_als300_dbgplay(format, args...) printk(KERN_ERR format, ##args) +#else +#define snd_als300_dbgplay(format, args...) +#endif + +enum {DEVICE_ALS300, DEVICE_ALS300_PLUS}; + +MODULE_AUTHOR("Ash Willis "); +MODULE_DESCRIPTION("Avance Logic ALS300"); +MODULE_LICENSE("GPL"); +MODULE_SUPPORTED_DEVICE("{{Avance Logic,ALS300},{Avance Logic,ALS300+}}"); + +static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; +static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; +static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP; + +struct snd_als300 { + unsigned long port; + spinlock_t reg_lock; + struct snd_card *card; + struct pci_dev *pci; + + struct snd_pcm *pcm; + struct snd_pcm_substream *playback_substream; + struct snd_pcm_substream *capture_substream; + + struct snd_ac97 *ac97; + struct snd_opl3 *opl3; + + struct resource *res_port; + + int irq; + + int chip_type; /* ALS300 or ALS300+ */ + + char revision; +}; + +struct snd_als300_substream_data { + int period_flipflop; + int control_register; + int block_counter_register; +}; + +static struct pci_device_id snd_als300_ids[] = { + { 0x4005, 0x0300, PCI_ANY_ID, PCI_ANY_ID, 0, 0, DEVICE_ALS300 }, + { 0x4005, 0x0308, PCI_ANY_ID, PCI_ANY_ID, 0, 0, DEVICE_ALS300_PLUS }, + { 0, } +}; + +MODULE_DEVICE_TABLE(pci, snd_als300_ids); + +static inline u32 snd_als300_gcr_read(unsigned long port, unsigned short reg) +{ + outb(reg, port+GCR_INDEX); + return inl(port+GCR_DATA); +} + +static inline void snd_als300_gcr_write(unsigned long port, + unsigned short reg, u32 val) +{ + outb(reg, port+GCR_INDEX); + outl(val, port+GCR_DATA); +} + +/* Enable/Disable Interrupts */ +static void snd_als300_set_irq_flag(struct snd_als300 *chip, int cmd) +{ + u32 tmp = snd_als300_gcr_read(chip->port, MISC_CONTROL); + snd_als300_dbgcallenter(); + + /* boolean XOR check, since old vs. new hardware have + directly reversed bit setting for ENABLE and DISABLE. + ALS300+ acts like newer versions of ALS300 */ + if (((chip->revision > 5 || chip->chip_type == DEVICE_ALS300_PLUS) ^ + (cmd == IRQ_ENABLE)) == 0) + tmp |= IRQ_SET_BIT; + else + tmp &= ~IRQ_SET_BIT; + snd_als300_gcr_write(chip->port, MISC_CONTROL, tmp); + snd_als300_dbgcallleave(); +} + +static int snd_als300_free(struct snd_als300 *chip) +{ + snd_als300_dbgcallenter(); + snd_als300_set_irq_flag(chip, IRQ_DISABLE); + if (chip->irq >= 0) + free_irq(chip->irq, (void *)chip); + pci_release_regions(chip->pci); + pci_disable_device(chip->pci); + kfree(chip); + snd_als300_dbgcallleave(); + return 0; +} + +static int snd_als300_dev_free(struct snd_device *device) +{ + struct snd_als300 *chip = device->device_data; + return snd_als300_free(chip); +} + +static irqreturn_t snd_als300_interrupt(int irq, void *dev_id, + struct pt_regs *regs) +{ + u8 status; + struct snd_als300 *chip = dev_id; + struct snd_als300_substream_data *data; + + status = inb(chip->port+ALS300_IRQ_STATUS); + if (!status) /* shared IRQ, for different device?? Exit ASAP! */ + return IRQ_NONE; + + /* ACK everything ASAP */ + outb(status, chip->port+ALS300_IRQ_STATUS); + if (status & IRQ_PLAYBACK) { + if (chip->pcm && chip->playback_substream) { + data = chip->playback_substream->runtime->private_data; + data->period_flipflop ^= 1; + snd_pcm_period_elapsed(chip->playback_substream); + snd_als300_dbgplay("IRQ_PLAYBACK\n"); + } + } + if (status & IRQ_CAPTURE) { + if (chip->pcm && chip->capture_substream) { + data = chip->capture_substream->runtime->private_data; + data->period_flipflop ^= 1; + snd_pcm_period_elapsed(chip->capture_substream); + snd_als300_dbgplay("IRQ_CAPTURE\n"); + } + } + return IRQ_HANDLED; +} + +static irqreturn_t snd_als300plus_interrupt(int irq, void *dev_id, + struct pt_regs *regs) +{ + u8 general, mpu, dram; + struct snd_als300 *chip = dev_id; + struct snd_als300_substream_data *data; + + general = inb(chip->port+ALS300P_IRQ_STATUS); + mpu = inb(chip->port+MPU_IRQ_STATUS); + dram = inb(chip->port+ALS300P_DRAM_IRQ_STATUS); + + /* shared IRQ, for different device?? Exit ASAP! */ + if ((general == 0) && ((mpu & 0x80) == 0) && ((dram & 0x01) == 0)) + return IRQ_NONE; + + if (general & IRQ_PLAYBACK) { + if (chip->pcm && chip->playback_substream) { + outb(IRQ_PLAYBACK, chip->port+ALS300P_IRQ_STATUS); + data = chip->playback_substream->runtime->private_data; + data->period_flipflop ^= 1; + snd_pcm_period_elapsed(chip->playback_substream); + snd_als300_dbgplay("IRQ_PLAYBACK\n"); + } + } + if (general & IRQ_CAPTURE) { + if (chip->pcm && chip->capture_substream) { + outb(IRQ_CAPTURE, chip->port+ALS300P_IRQ_STATUS); + data = chip->capture_substream->runtime->private_data; + data->period_flipflop ^= 1; + snd_pcm_period_elapsed(chip->capture_substream); + snd_als300_dbgplay("IRQ_CAPTURE\n"); + } + } + /* FIXME: Ack other interrupt types. Not important right now as + * those other devices aren't enabled. */ + return IRQ_HANDLED; +} + +static void __devexit snd_als300_remove(struct pci_dev *pci) +{ + snd_als300_dbgcallenter(); + snd_card_free(pci_get_drvdata(pci)); + pci_set_drvdata(pci, NULL); + snd_als300_dbgcallleave(); +} + +static unsigned short snd_als300_ac97_read(struct snd_ac97 *ac97, + unsigned short reg) +{ + int i; + struct snd_als300 *chip = ac97->private_data; + + for (i = 0; i < 1000; i++) { + if ((inb(chip->port+AC97_STATUS) & (AC97_BUSY)) == 0) + break; + udelay(10); + } + outl((reg << 24) | (1 << 31), chip->port+AC97_ACCESS); + + for (i = 0; i < 1000; i++) { + if ((inb(chip->port+AC97_STATUS) & (AC97_DATA_AVAIL)) != 0) + break; + udelay(10); + } + return inw(chip->port+AC97_READ); +} + +static void snd_als300_ac97_write(struct snd_ac97 *ac97, + unsigned short reg, unsigned short val) +{ + int i; + struct snd_als300 *chip = ac97->private_data; + + for (i = 0; i < 1000; i++) { + if ((inb(chip->port+AC97_STATUS) & (AC97_BUSY)) == 0) + break; + udelay(10); + } + outl((reg << 24) | val, chip->port+AC97_ACCESS); +} + +static int snd_als300_ac97(struct snd_als300 *chip) +{ + struct snd_ac97_bus *bus; + struct snd_ac97_template ac97; + int err; + static struct snd_ac97_bus_ops ops = { + .write = snd_als300_ac97_write, + .read = snd_als300_ac97_read, + }; + + snd_als300_dbgcallenter(); + if ((err = snd_ac97_bus(chip->card, 0, &ops, NULL, &bus)) < 0) + return err; + + memset(&ac97, 0, sizeof(ac97)); + ac97.private_data = chip; + + snd_als300_dbgcallleave(); + return snd_ac97_mixer(bus, &ac97, &chip->ac97); +} + +/* hardware definition + * + * In AC97 mode, we always use 48k/16bit/stereo. + * Any request to change data type is ignored by + * the card when it is running outside of legacy + * mode. + */ +static struct snd_pcm_hardware snd_als300_playback_hw = +{ + .info = (SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_MMAP_VALID), + .formats = SNDRV_PCM_FMTBIT_S16, + .rates = SNDRV_PCM_RATE_48000, + .rate_min = 48000, + .rate_max = 48000, + .channels_min = 2, + .channels_max = 2, + .buffer_bytes_max = 64 * 1024, + .period_bytes_min = 64, + .period_bytes_max = 32 * 1024, + .periods_min = 2, + .periods_max = 2, +}; + +static struct snd_pcm_hardware snd_als300_capture_hw = +{ + .info = (SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_MMAP_VALID), + .formats = SNDRV_PCM_FMTBIT_S16, + .rates = SNDRV_PCM_RATE_48000, + .rate_min = 48000, + .rate_max = 48000, + .channels_min = 2, + .channels_max = 2, + .buffer_bytes_max = 64 * 1024, + .period_bytes_min = 64, + .period_bytes_max = 32 * 1024, + .periods_min = 2, + .periods_max = 2, +}; + +static int snd_als300_playback_open(struct snd_pcm_substream *substream) +{ + struct snd_als300 *chip = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_als300_substream_data *data = kzalloc(sizeof(*data), + GFP_KERNEL); + + snd_als300_dbgcallenter(); + chip->playback_substream = substream; + runtime->hw = snd_als300_playback_hw; + runtime->private_data = data; + data->control_register = PLAYBACK_CONTROL; + data->block_counter_register = PLAYBACK_BLOCK_COUNTER; + snd_als300_dbgcallleave(); + return 0; +} + +static int snd_als300_playback_close(struct snd_pcm_substream *substream) +{ + struct snd_als300 *chip = snd_pcm_substream_chip(substream); + struct snd_als300_substream_data *data; + + data = substream->runtime->private_data; + snd_als300_dbgcallenter(); + kfree(data); + chip->playback_substream = NULL; + snd_pcm_lib_free_pages(substream); + snd_als300_dbgcallleave(); + return 0; +} + +static int snd_als300_capture_open(struct snd_pcm_substream *substream) +{ + struct snd_als300 *chip = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_als300_substream_data *data = kzalloc(sizeof(*data), + GFP_KERNEL); + + snd_als300_dbgcallenter(); + chip->capture_substream = substream; + runtime->hw = snd_als300_capture_hw; + runtime->private_data = data; + data->control_register = RECORD_CONTROL; + data->block_counter_register = RECORD_BLOCK_COUNTER; + snd_als300_dbgcallleave(); + return 0; +} + +static int snd_als300_capture_close(struct snd_pcm_substream *substream) +{ + struct snd_als300 *chip = snd_pcm_substream_chip(substream); + struct snd_als300_substream_data *data; + + data = substream->runtime->private_data; + snd_als300_dbgcallenter(); + kfree(data); + chip->capture_substream = NULL; + snd_pcm_lib_free_pages(substream); + snd_als300_dbgcallleave(); + return 0; +} + +static int snd_als300_pcm_hw_params(struct snd_pcm_substream *substream, + snd_pcm_hw_params_t * hw_params) +{ + return snd_pcm_lib_malloc_pages(substream, + params_buffer_bytes(hw_params)); +} + +static int snd_als300_pcm_hw_free(struct snd_pcm_substream *substream) +{ + return snd_pcm_lib_free_pages(substream); +} + +static int snd_als300_playback_prepare(struct snd_pcm_substream *substream) +{ + u32 tmp; + struct snd_als300 *chip = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + unsigned short period_bytes = snd_pcm_lib_period_bytes(substream); + unsigned short buffer_bytes = snd_pcm_lib_buffer_bytes(substream); + + snd_als300_dbgcallenter(); + spin_lock_irq(&chip->reg_lock); + tmp = snd_als300_gcr_read(chip->port, PLAYBACK_CONTROL); + tmp &= ~TRANSFER_START; + + snd_als300_dbgplay("Period bytes: %d Buffer bytes %d\n", + period_bytes, buffer_bytes); + + /* set block size */ + tmp &= 0xffff0000; + tmp |= period_bytes - 1; + snd_als300_gcr_write(chip->port, PLAYBACK_CONTROL, tmp); + + /* set dma area */ + snd_als300_gcr_write(chip->port, PLAYBACK_START, + runtime->dma_addr); + snd_als300_gcr_write(chip->port, PLAYBACK_END, + runtime->dma_addr + buffer_bytes - 1); + spin_unlock_irq(&chip->reg_lock); + snd_als300_dbgcallleave(); + return 0; +} + +static int snd_als300_capture_prepare(struct snd_pcm_substream *substream) +{ + u32 tmp; + struct snd_als300 *chip = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + unsigned short period_bytes = snd_pcm_lib_period_bytes(substream); + unsigned short buffer_bytes = snd_pcm_lib_buffer_bytes(substream); + + snd_als300_dbgcallenter(); + spin_lock_irq(&chip->reg_lock); + tmp = snd_als300_gcr_read(chip->port, RECORD_CONTROL); + tmp &= ~TRANSFER_START; + + snd_als300_dbgplay("Period bytes: %d Buffer bytes %d\n", period_bytes, + buffer_bytes); + + /* set block size */ + tmp &= 0xffff0000; + tmp |= period_bytes - 1; + + /* set dma area */ + snd_als300_gcr_write(chip->port, RECORD_CONTROL, tmp); + snd_als300_gcr_write(chip->port, RECORD_START, + runtime->dma_addr); + snd_als300_gcr_write(chip->port, RECORD_END, + runtime->dma_addr + buffer_bytes - 1); + spin_unlock_irq(&chip->reg_lock); + snd_als300_dbgcallleave(); + return 0; +} + +static int snd_als300_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_als300 *chip = snd_pcm_substream_chip(substream); + u32 tmp; + struct snd_als300_substream_data *data; + unsigned short reg; + int ret = 0; + + data = substream->runtime->private_data; + reg = data->control_register; + + snd_als300_dbgcallenter(); + spin_lock(&chip->reg_lock); + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + tmp = snd_als300_gcr_read(chip->port, reg); + data->period_flipflop = 1; + snd_als300_gcr_write(chip->port, reg, tmp | TRANSFER_START); + snd_als300_dbgplay("TRIGGER START\n"); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + tmp = snd_als300_gcr_read(chip->port, reg); + snd_als300_gcr_write(chip->port, reg, tmp & ~TRANSFER_START); + snd_als300_dbgplay("TRIGGER STOP\n"); + break; + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + tmp = snd_als300_gcr_read(chip->port, reg); + snd_als300_gcr_write(chip->port, reg, tmp | FIFO_PAUSE); + snd_als300_dbgplay("TRIGGER PAUSE\n"); + break; + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + tmp = snd_als300_gcr_read(chip->port, reg); + snd_als300_gcr_write(chip->port, reg, tmp & ~FIFO_PAUSE); + snd_als300_dbgplay("TRIGGER RELEASE\n"); + break; + default: + snd_als300_dbgplay("TRIGGER INVALID\n"); + ret = -EINVAL; + } + spin_unlock(&chip->reg_lock); + snd_als300_dbgcallleave(); + return ret; +} + +static snd_pcm_uframes_t snd_als300_pointer(struct snd_pcm_substream *substream) +{ + u16 current_ptr; + struct snd_als300 *chip = snd_pcm_substream_chip(substream); + struct snd_als300_substream_data *data; + unsigned short period_bytes; + + data = substream->runtime->private_data; + period_bytes = snd_pcm_lib_period_bytes(substream); + + snd_als300_dbgcallenter(); + spin_lock(&chip->reg_lock); + current_ptr = (u16) snd_als300_gcr_read(chip->port, + data->block_counter_register) + 4; + spin_unlock(&chip->reg_lock); + if (current_ptr > period_bytes) + current_ptr = 0; + else + current_ptr = period_bytes - current_ptr; + + if (data->period_flipflop == 0) + current_ptr += period_bytes; + snd_als300_dbgplay("Pointer (bytes): %d\n", current_ptr); + snd_als300_dbgcallleave(); + return bytes_to_frames(substream->runtime, current_ptr); +} + +static struct snd_pcm_ops snd_als300_playback_ops = { + .open = snd_als300_playback_open, + .close = snd_als300_playback_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_als300_pcm_hw_params, + .hw_free = snd_als300_pcm_hw_free, + .prepare = snd_als300_playback_prepare, + .trigger = snd_als300_trigger, + .pointer = snd_als300_pointer, +}; + +static struct snd_pcm_ops snd_als300_capture_ops = { + .open = snd_als300_capture_open, + .close = snd_als300_capture_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_als300_pcm_hw_params, + .hw_free = snd_als300_pcm_hw_free, + .prepare = snd_als300_capture_prepare, + .trigger = snd_als300_trigger, + .pointer = snd_als300_pointer, +}; + +static int __devinit snd_als300_new_pcm(struct snd_als300 *chip) +{ + struct snd_pcm *pcm; + int err; + + snd_als300_dbgcallenter(); + err = snd_pcm_new(chip->card, "ALS300", 0, 1, 1, &pcm); + if (err < 0) + return err; + pcm->private_data = chip; + strcpy(pcm->name, "ALS300"); + chip->pcm = pcm; + + /* set operators */ + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, + &snd_als300_playback_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, + &snd_als300_capture_ops); + + /* pre-allocation of buffers */ + snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV, + snd_dma_pci_data(chip->pci), 64*1024, 64*1024); + snd_als300_dbgcallleave(); + return 0; +} + +static void snd_als300_init(struct snd_als300 *chip) +{ + unsigned long flags; + u32 tmp; + + snd_als300_dbgcallenter(); + spin_lock_irqsave(&chip->reg_lock, flags); + chip->revision = (snd_als300_gcr_read(chip->port, MISC_CONTROL) >> 16) + & 0x0000000F; + /* Setup DRAM */ + tmp = snd_als300_gcr_read(chip->port, DRAM_WRITE_CONTROL); + snd_als300_gcr_write(chip->port, DRAM_WRITE_CONTROL, + (tmp | DRAM_MODE_2) + & ~WRITE_TRANS_START); + + /* Enable IRQ output */ + snd_als300_set_irq_flag(chip, IRQ_ENABLE); + + /* Unmute hardware devices so their outputs get routed to + * the onboard mixer */ + tmp = snd_als300_gcr_read(chip->port, MISC_CONTROL); + snd_als300_gcr_write(chip->port, MISC_CONTROL, + tmp | VMUTE_NORMAL | MMUTE_NORMAL); + + /* Reset volumes */ + snd_als300_gcr_write(chip->port, MUS_VOC_VOL, 0); + + /* Make sure playback transfer is stopped */ + tmp = snd_als300_gcr_read(chip->port, PLAYBACK_CONTROL); + snd_als300_gcr_write(chip->port, PLAYBACK_CONTROL, + tmp & ~TRANSFER_START); + spin_unlock_irqrestore(&chip->reg_lock, flags); + snd_als300_dbgcallleave(); +} + +static int __devinit snd_als300_create(snd_card_t *card, + struct pci_dev *pci, int chip_type, + struct snd_als300 **rchip) +{ + struct snd_als300 *chip; + void *irq_handler; + int err; + + static snd_device_ops_t ops = { + .dev_free = snd_als300_dev_free, + }; + *rchip = NULL; + + snd_als300_dbgcallenter(); + if ((err = pci_enable_device(pci)) < 0) + return err; + + if (pci_set_dma_mask(pci, 0x0fffffff) < 0 || + pci_set_consistent_dma_mask(pci, 0x0fffffff) < 0) { + printk(KERN_ERR "error setting 28bit DMA mask\n"); + pci_disable_device(pci); + return -ENXIO; + } + pci_set_master(pci); + + chip = kzalloc(sizeof(*chip), GFP_KERNEL); + if (chip == NULL) { + pci_disable_device(pci); + return -ENOMEM; + } + + chip->card = card; + chip->pci = pci; + chip->irq = -1; + chip->chip_type = chip_type; + spin_lock_init(&chip->reg_lock); + + if ((err = pci_request_regions(pci, "ALS300")) < 0) { + kfree(chip); + pci_disable_device(pci); + return err; + } + chip->port = pci_resource_start(pci, 0); + + if (chip->chip_type == DEVICE_ALS300_PLUS) + irq_handler = snd_als300plus_interrupt; + else + irq_handler = snd_als300_interrupt; + + if (request_irq(pci->irq, irq_handler, SA_INTERRUPT|SA_SHIRQ, + card->shortname, (void *)chip)) { + snd_printk(KERN_ERR "unable to grab IRQ %d\n", pci->irq); + snd_als300_free(chip); + return -EBUSY; + } + chip->irq = pci->irq; + + + snd_als300_init(chip); + + if (snd_als300_ac97(chip) < 0) { + snd_printk(KERN_WARNING "Could not create ac97\n"); + snd_als300_free(chip); + return err; + } + + if ((err = snd_als300_new_pcm(chip)) < 0) { + snd_printk(KERN_WARNING "Could not create PCM\n"); + snd_als300_free(chip); + return err; + } + + if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, + chip, &ops)) < 0) { + snd_als300_free(chip); + return err; + } + + snd_card_set_dev(card, &pci->dev); + + *rchip = chip; + snd_als300_dbgcallleave(); + return 0; +} + +#ifdef CONFIG_PM +static int snd_als300_suspend(struct pci_dev *pci, pm_message_t state) +{ + struct snd_card *card = pci_get_drvdata(pci); + struct snd_als300 *chip = card->private_data; + + snd_power_change_state(card, SNDRV_CTL_POWER_D3hot); + snd_pcm_suspend_all(chip->pcm); + snd_ac97_suspend(chip->ac97); + + pci_set_power_state(pci, PCI_D3hot); + pci_disable_device(pci); + pci_save_state(pci); + return 0; +} + +static int snd_als300_resume(struct pci_dev *pci) +{ + struct snd_card *card = pci_get_drvdata(pci); + struct snd_als300 *chip = card->private_data; + + pci_restore_state(pci); + pci_enable_device(pci); + pci_set_power_state(pci, PCI_D0); + pci_set_master(pci); + + snd_als300_init(chip); + snd_ac97_resume(chip->ac97); + + snd_power_change_state(card, SNDRV_CTL_POWER_D0); + return 0; +} +#endif + +static int __devinit snd_als300_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) +{ + static int dev; + struct snd_card *card; + struct snd_als300 *chip; + int err, chip_type; + + if (dev >= SNDRV_CARDS) + return -ENODEV; + if (!enable[dev]) { + dev++; + return -ENOENT; + } + + card = snd_card_new(index[dev], id[dev], THIS_MODULE, 0); + + if (card == NULL) + return -ENOMEM; + + chip_type = pci_id->driver_data; + + if ((err = snd_als300_create(card, pci, chip_type, &chip)) < 0) { + snd_card_free(card); + return err; + } + card->private_data = chip; + + strcpy(card->driver, "ALS300"); + if (chip->chip_type == DEVICE_ALS300_PLUS) + /* don't know much about ALS300+ yet + * print revision number for now */ + sprintf(card->shortname, "ALS300+ (Rev. %d)", chip->revision); + else + sprintf(card->shortname, "ALS300 (Rev. %c)", 'A' + + chip->revision - 1); + sprintf(card->longname, "%s at 0x%lx irq %i", + card->shortname, chip->port, chip->irq); + + if ((err = snd_card_register(card)) < 0) { + snd_card_free(card); + return err; + } + pci_set_drvdata(pci, card); + dev++; + return 0; +} + +static struct pci_driver driver = { + .name = "ALS300", + .id_table = snd_als300_ids, + .probe = snd_als300_probe, + .remove = __devexit_p(snd_als300_remove), +#ifdef CONFIG_PM + .suspend = snd_als300_suspend, + .resume = snd_als300_resume, +#endif +}; + +static int __init alsa_card_als300_init(void) +{ + return pci_register_driver(&driver); +} + +static void __exit alsa_card_als300_exit(void) +{ + pci_unregister_driver(&driver); +} + +module_init(alsa_card_als300_init) +module_exit(alsa_card_als300_exit) -- cgit v1.2.3-59-g8ed1b From 10d150e186d069cc7e2075c4823049949933777c Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Mon, 27 Mar 2006 13:42:39 +0200 Subject: [ALSA] Remove obsolete kfree_nocheck call Fixed the compilation, removing obsolete kfree_nocheck() call for memory debugging. Signed-off-by: Takashi Iwai --- sound/isa/opti9xx/miro.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/sound/isa/opti9xx/miro.c b/sound/isa/opti9xx/miro.c index 49ba334c0d24..09384d03dc31 100644 --- a/sound/isa/opti9xx/miro.c +++ b/sound/isa/opti9xx/miro.c @@ -743,8 +743,7 @@ static long snd_legacy_find_free_ioport(long *port_table, long size) struct resource *res; if ((res = request_region(*port_table, size, "ALSA test")) != NULL) { - release_resource(res); - kfree_nocheck(res); + release_and_free_resource(res); return *port_table; } port_table++; @@ -1150,8 +1149,7 @@ static int __init snd_card_miro_detect(struct snd_card *card, struct snd_miro *c if (value == snd_miro_read(chip, OPTi9XX_MC_REG(1))) return 1; - release_resource(chip->res_mc_base); - kfree_nocheck(chip->res_mc_base); + release_and_free_resource(chip->res_mc_base); chip->res_mc_base = NULL; } -- cgit v1.2.3-59-g8ed1b From 43560116d1f70de274915fdec0745c6e02feef30 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Mon, 27 Mar 2006 13:45:51 +0200 Subject: [ALSA] Remove obsolete kfree_nocheck call Fixed the compilation, removing obsolete kfree_nocheck() call for memory debugging. Signed-off-by: Takashi Iwai --- sound/pci/riptide/riptide.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/sound/pci/riptide/riptide.c b/sound/pci/riptide/riptide.c index 5b3e49900498..f148ee434a6b 100644 --- a/sound/pci/riptide/riptide.c +++ b/sound/pci/riptide/riptide.c @@ -1838,10 +1838,7 @@ static int snd_riptide_free(struct snd_riptide *chip) } if (chip->fw_entry) release_firmware(chip->fw_entry); - if (chip->res_port) { - release_resource(chip->res_port); - kfree_nocheck(chip->res_port); - } + release_and_free_resource(chip->res_port); if (chip->irq >= 0) free_irq(chip->irq, chip); kfree(chip); -- cgit v1.2.3-59-g8ed1b From 82756b2785c5f08204e7f3dab64e12d4533bfe89 Mon Sep 17 00:00:00 2001 From: Rene Herman Date: Mon, 27 Mar 2006 13:50:11 +0200 Subject: [ALSA] ISA drivers bailing on first !enable[i] Fix the wrong check of enable option in cmi8330 driver. Signed-off-by: Takashi Iwai --- sound/isa/cmi8330.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sound/isa/cmi8330.c b/sound/isa/cmi8330.c index fa63048a8b9d..bc0f5ebf5d3c 100644 --- a/sound/isa/cmi8330.c +++ b/sound/isa/cmi8330.c @@ -693,9 +693,9 @@ static int __init alsa_card_cmi8330_init(void) if ((err = platform_driver_register(&snd_cmi8330_driver)) < 0) return err; - for (i = 0; i < SNDRV_CARDS && enable[i]; i++) { + for (i = 0; i < SNDRV_CARDS; i++) { struct platform_device *device; - if (is_isapnp_selected(i)) + if (! enable[i] || is_isapnp_selected(i)) continue; device = platform_device_register_simple(CMI8330_DRIVER, i, NULL, 0); -- cgit v1.2.3-59-g8ed1b From bf1bbb5a49eec51c30d341606885507b501b37e8 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Mon, 27 Mar 2006 16:22:45 +0200 Subject: [ALSA] Tiny clean up of PCM codes - Make snd_pcm_prepare() static - Clean up snd_pcm_kernel_*_ioctl() functions, reduce exports Signed-off-by: Takashi Iwai --- include/sound/pcm.h | 3 --- sound/core/oss/pcm_oss.c | 16 ++++++++-------- sound/core/pcm.c | 2 -- sound/core/pcm_native.c | 39 +++++++++++++-------------------------- 4 files changed, 21 insertions(+), 39 deletions(-) diff --git a/include/sound/pcm.h b/include/sound/pcm.h index 15b885660bf0..7cf6a30c1526 100644 --- a/include/sound/pcm.h +++ b/include/sound/pcm.h @@ -460,7 +460,6 @@ int snd_pcm_info_user(struct snd_pcm_substream *substream, struct snd_pcm_info __user *info); int snd_pcm_status(struct snd_pcm_substream *substream, struct snd_pcm_status *status); -int snd_pcm_prepare(struct snd_pcm_substream *substream); int snd_pcm_start(struct snd_pcm_substream *substream); int snd_pcm_stop(struct snd_pcm_substream *substream, int status); int snd_pcm_drain_done(struct snd_pcm_substream *substream); @@ -468,8 +467,6 @@ int snd_pcm_drain_done(struct snd_pcm_substream *substream); int snd_pcm_suspend(struct snd_pcm_substream *substream); int snd_pcm_suspend_all(struct snd_pcm *pcm); #endif -int snd_pcm_kernel_playback_ioctl(struct snd_pcm_substream *substream, unsigned int cmd, void *arg); -int snd_pcm_kernel_capture_ioctl(struct snd_pcm_substream *substream, unsigned int cmd, void *arg); int snd_pcm_kernel_ioctl(struct snd_pcm_substream *substream, unsigned int cmd, void *arg); int snd_pcm_open_substream(struct snd_pcm *pcm, int stream, struct snd_pcm_substream **rsubstream); void snd_pcm_release_substream(struct snd_pcm_substream *substream); diff --git a/sound/core/oss/pcm_oss.c b/sound/core/oss/pcm_oss.c index f8302b703a30..54be0bba6bd2 100644 --- a/sound/core/oss/pcm_oss.c +++ b/sound/core/oss/pcm_oss.c @@ -959,12 +959,12 @@ static int snd_pcm_oss_reset(struct snd_pcm_oss_file *pcm_oss_file) substream = pcm_oss_file->streams[SNDRV_PCM_STREAM_PLAYBACK]; if (substream != NULL) { - snd_pcm_kernel_playback_ioctl(substream, SNDRV_PCM_IOCTL_DROP, NULL); + snd_pcm_kernel_ioctl(substream, SNDRV_PCM_IOCTL_DROP, NULL); substream->runtime->oss.prepare = 1; } substream = pcm_oss_file->streams[SNDRV_PCM_STREAM_CAPTURE]; if (substream != NULL) { - snd_pcm_kernel_capture_ioctl(substream, SNDRV_PCM_IOCTL_DROP, NULL); + snd_pcm_kernel_ioctl(substream, SNDRV_PCM_IOCTL_DROP, NULL); substream->runtime->oss.prepare = 1; } return 0; @@ -979,7 +979,7 @@ static int snd_pcm_oss_post(struct snd_pcm_oss_file *pcm_oss_file) if (substream != NULL) { if ((err = snd_pcm_oss_make_ready(substream)) < 0) return err; - snd_pcm_kernel_playback_ioctl(substream, SNDRV_PCM_IOCTL_START, NULL); + snd_pcm_kernel_ioctl(substream, SNDRV_PCM_IOCTL_START, NULL); } /* note: all errors from the start action are ignored */ /* OSS apps do not know, how to handle them */ @@ -1108,7 +1108,7 @@ static int snd_pcm_oss_sync(struct snd_pcm_oss_file *pcm_oss_file) __direct: saved_f_flags = substream->ffile->f_flags; substream->ffile->f_flags &= ~O_NONBLOCK; - err = snd_pcm_kernel_playback_ioctl(substream, SNDRV_PCM_IOCTL_DRAIN, NULL); + err = snd_pcm_kernel_ioctl(substream, SNDRV_PCM_IOCTL_DRAIN, NULL); substream->ffile->f_flags = saved_f_flags; if (err < 0) return err; @@ -1120,7 +1120,7 @@ static int snd_pcm_oss_sync(struct snd_pcm_oss_file *pcm_oss_file) if ((err = snd_pcm_oss_make_ready(substream)) < 0) return err; runtime = substream->runtime; - err = snd_pcm_kernel_capture_ioctl(substream, SNDRV_PCM_IOCTL_DROP, NULL); + err = snd_pcm_kernel_ioctl(substream, SNDRV_PCM_IOCTL_DROP, NULL); if (err < 0) return err; runtime->oss.buffer_used = 0; @@ -1437,7 +1437,7 @@ static int snd_pcm_oss_set_trigger(struct snd_pcm_oss_file *pcm_oss_file, int tr cmd = SNDRV_PCM_IOCTL_DROP; runtime->oss.prepare = 1; } - err = snd_pcm_kernel_playback_ioctl(psubstream, cmd, NULL); + err = snd_pcm_kernel_ioctl(psubstream, cmd, NULL); if (err < 0) return err; } @@ -1458,7 +1458,7 @@ static int snd_pcm_oss_set_trigger(struct snd_pcm_oss_file *pcm_oss_file, int tr cmd = SNDRV_PCM_IOCTL_DROP; runtime->oss.prepare = 1; } - err = snd_pcm_kernel_capture_ioctl(csubstream, cmd, NULL); + err = snd_pcm_kernel_ioctl(csubstream, cmd, NULL); if (err < 0) return err; } @@ -1495,7 +1495,7 @@ static int snd_pcm_oss_get_odelay(struct snd_pcm_oss_file *pcm_oss_file) runtime = substream->runtime; if (runtime->oss.params || runtime->oss.prepare) return 0; - err = snd_pcm_kernel_playback_ioctl(substream, SNDRV_PCM_IOCTL_DELAY, &delay); + err = snd_pcm_kernel_ioctl(substream, SNDRV_PCM_IOCTL_DELAY, &delay); if (err == -EPIPE) delay = 0; /* hack for broken OSS applications */ else if (err < 0) diff --git a/sound/core/pcm.c b/sound/core/pcm.c index 3da6a38c2d0f..1e9878fed37f 100644 --- a/sound/core/pcm.c +++ b/sound/core/pcm.c @@ -1111,8 +1111,6 @@ EXPORT_SYMBOL(snd_pcm_link_rwlock); EXPORT_SYMBOL(snd_pcm_suspend); EXPORT_SYMBOL(snd_pcm_suspend_all); #endif -EXPORT_SYMBOL(snd_pcm_kernel_playback_ioctl); -EXPORT_SYMBOL(snd_pcm_kernel_capture_ioctl); EXPORT_SYMBOL(snd_pcm_kernel_ioctl); EXPORT_SYMBOL(snd_pcm_mmap_data); #if SNDRV_PCM_INFO_MMAP_IOMEM diff --git a/sound/core/pcm_native.c b/sound/core/pcm_native.c index 7010eb271cc7..13efe39e4cc6 100644 --- a/sound/core/pcm_native.c +++ b/sound/core/pcm_native.c @@ -1313,7 +1313,7 @@ static struct action_ops snd_pcm_action_prepare = { * * Prepare the PCM substream to be triggerable. */ -int snd_pcm_prepare(struct snd_pcm_substream *substream) +static int snd_pcm_prepare(struct snd_pcm_substream *substream) { int res; struct snd_card *card = substream->pcm->card; @@ -2736,41 +2736,28 @@ static long snd_pcm_capture_ioctl(struct file *file, unsigned int cmd, return snd_pcm_capture_ioctl1(pcm_file->substream, cmd, (void __user *)arg); } -int snd_pcm_kernel_playback_ioctl(struct snd_pcm_substream *substream, - unsigned int cmd, void *arg) -{ - mm_segment_t fs; - int result; - - fs = snd_enter_user(); - result = snd_pcm_playback_ioctl1(substream, cmd, (void __user *)arg); - snd_leave_user(fs); - return result; -} - -int snd_pcm_kernel_capture_ioctl(struct snd_pcm_substream *substream, - unsigned int cmd, void *arg) +int snd_pcm_kernel_ioctl(struct snd_pcm_substream *substream, + unsigned int cmd, void *arg) { mm_segment_t fs; int result; fs = snd_enter_user(); - result = snd_pcm_capture_ioctl1(substream, cmd, (void __user *)arg); - snd_leave_user(fs); - return result; -} - -int snd_pcm_kernel_ioctl(struct snd_pcm_substream *substream, - unsigned int cmd, void *arg) -{ switch (substream->stream) { case SNDRV_PCM_STREAM_PLAYBACK: - return snd_pcm_kernel_playback_ioctl(substream, cmd, arg); + result = snd_pcm_playback_ioctl1(substream, + cmd, (void __user *)arg); + break; case SNDRV_PCM_STREAM_CAPTURE: - return snd_pcm_kernel_capture_ioctl(substream, cmd, arg); + result = snd_pcm_capture_ioctl1(substream, + cmd, (void __user *)arg); + break; default: - return -EINVAL; + result = -EINVAL; + break; } + snd_leave_user(fs); + return result; } static ssize_t snd_pcm_read(struct file *file, char __user *buf, size_t count, -- cgit v1.2.3-59-g8ed1b From 3bf75f9b90c981f18f27a0d35a44f488ab68c8ea Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Mon, 27 Mar 2006 16:40:49 +0200 Subject: [ALSA] Clean up PCM codes (take 2) - Clean up initialization and destruction of substream instance Now snd_pcm_open_substream() alone does most initialization jobs. Add pcm_release callback for cleaning up at snd_pcm_release_substream() - Tidy up PCM oss code Signed-off-by: Takashi Iwai --- include/sound/pcm.h | 12 ++-- include/sound/pcm_oss.h | 1 - sound/core/oss/pcm_oss.c | 151 ++++++++++++++--------------------------------- sound/core/pcm.c | 12 ++-- sound/core/pcm_lib.c | 49 --------------- sound/core/pcm_native.c | 108 +++++++++++++++++---------------- 6 files changed, 114 insertions(+), 219 deletions(-) diff --git a/include/sound/pcm.h b/include/sound/pcm.h index 7cf6a30c1526..66b1f08b42b9 100644 --- a/include/sound/pcm.h +++ b/include/sound/pcm.h @@ -369,6 +369,7 @@ struct snd_pcm_substream { /* -- assigned files -- */ struct snd_pcm_file *file; struct file *ffile; + void (*pcm_release)(struct snd_pcm_substream *); #if defined(CONFIG_SND_PCM_OSS) || defined(CONFIG_SND_PCM_OSS_MODULE) /* -- OSS things -- */ struct snd_pcm_oss_substream oss; @@ -381,13 +382,10 @@ struct snd_pcm_substream { struct snd_info_entry *proc_prealloc_entry; /* misc flags */ unsigned int no_mmap_ctrl: 1; + unsigned int hw_opened: 1; }; -#if defined(CONFIG_SND_PCM_OSS) || defined(CONFIG_SND_PCM_OSS_MODULE) -#define SUBSTREAM_BUSY(substream) ((substream)->file != NULL || ((substream)->oss.file != NULL)) -#else #define SUBSTREAM_BUSY(substream) ((substream)->file != NULL) -#endif struct snd_pcm_str { @@ -468,8 +466,12 @@ int snd_pcm_suspend(struct snd_pcm_substream *substream); int snd_pcm_suspend_all(struct snd_pcm *pcm); #endif int snd_pcm_kernel_ioctl(struct snd_pcm_substream *substream, unsigned int cmd, void *arg); -int snd_pcm_open_substream(struct snd_pcm *pcm, int stream, struct snd_pcm_substream **rsubstream); +int snd_pcm_open_substream(struct snd_pcm *pcm, int stream, struct file *file, + struct snd_pcm_substream **rsubstream); void snd_pcm_release_substream(struct snd_pcm_substream *substream); +int snd_pcm_attach_substream(struct snd_pcm *pcm, int stream, struct file *file, + struct snd_pcm_substream **rsubstream); +void snd_pcm_detach_substream(struct snd_pcm_substream *substream); void snd_pcm_vma_notify_data(void *client, void *data); int snd_pcm_mmap_data(struct snd_pcm_substream *substream, struct file *file, struct vm_area_struct *area); diff --git a/include/sound/pcm_oss.h b/include/sound/pcm_oss.h index bff0778e1969..1d522aaa66df 100644 --- a/include/sound/pcm_oss.h +++ b/include/sound/pcm_oss.h @@ -70,7 +70,6 @@ struct snd_pcm_oss_file { struct snd_pcm_oss_substream { unsigned oss: 1; /* oss mode */ struct snd_pcm_oss_setup *setup; /* active setup */ - struct snd_pcm_oss_file *file; }; struct snd_pcm_oss_stream { diff --git a/sound/core/oss/pcm_oss.c b/sound/core/oss/pcm_oss.c index 54be0bba6bd2..c056cbfe5519 100644 --- a/sound/core/oss/pcm_oss.c +++ b/sound/core/oss/pcm_oss.c @@ -1671,6 +1671,18 @@ static struct snd_pcm_oss_setup *snd_pcm_oss_look_for_setup(struct snd_pcm *pcm, return NULL; } +static void snd_pcm_oss_release_substream(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime; + runtime = substream->runtime; + vfree(runtime->oss.buffer); + runtime->oss.buffer = NULL; +#ifdef CONFIG_SND_PCM_OSS_PLUGINS + snd_pcm_oss_plugin_clear(substream); +#endif + substream->oss.oss = 0; +} + static void snd_pcm_oss_init_substream(struct snd_pcm_substream *substream, struct snd_pcm_oss_setup *setup, int minor) @@ -1679,6 +1691,10 @@ static void snd_pcm_oss_init_substream(struct snd_pcm_substream *substream, substream->oss.oss = 1; substream->oss.setup = setup; + if (setup->nonblock) + substream->ffile->f_flags |= O_NONBLOCK; + else + substream->ffile->f_flags &= ~O_NONBLOCK; runtime = substream->runtime; runtime->oss.params = 1; runtime->oss.trigger = 1; @@ -1697,18 +1713,7 @@ static void snd_pcm_oss_init_substream(struct snd_pcm_substream *substream, runtime->oss.fragshift = 0; runtime->oss.maxfrags = 0; runtime->oss.subdivision = 0; -} - -static void snd_pcm_oss_release_substream(struct snd_pcm_substream *substream) -{ - struct snd_pcm_runtime *runtime; - runtime = substream->runtime; - vfree(runtime->oss.buffer); -#ifdef CONFIG_SND_PCM_OSS_PLUGINS - snd_pcm_oss_plugin_clear(substream); -#endif - substream->oss.file = NULL; - substream->oss.oss = 0; + substream->pcm_release = snd_pcm_oss_release_substream; } static int snd_pcm_oss_release_file(struct snd_pcm_oss_file *pcm_oss_file) @@ -1717,23 +1722,8 @@ static int snd_pcm_oss_release_file(struct snd_pcm_oss_file *pcm_oss_file) snd_assert(pcm_oss_file != NULL, return -ENXIO); for (cidx = 0; cidx < 2; ++cidx) { struct snd_pcm_substream *substream = pcm_oss_file->streams[cidx]; - struct snd_pcm_runtime *runtime; - if (substream == NULL) - continue; - runtime = substream->runtime; - - snd_pcm_stream_lock_irq(substream); - if (snd_pcm_running(substream)) - snd_pcm_stop(substream, SNDRV_PCM_STATE_SETUP); - snd_pcm_stream_unlock_irq(substream); - if (substream->ffile != NULL) { - if (substream->ops->hw_free != NULL) - substream->ops->hw_free(substream); - substream->ops->close(substream); - substream->ffile = NULL; - } - snd_pcm_oss_release_substream(substream); - snd_pcm_release_substream(substream); + if (substream) + snd_pcm_release_substream(substream); } kfree(pcm_oss_file); return 0; @@ -1743,12 +1733,11 @@ static int snd_pcm_oss_open_file(struct file *file, struct snd_pcm *pcm, struct snd_pcm_oss_file **rpcm_oss_file, int minor, - struct snd_pcm_oss_setup *psetup, - struct snd_pcm_oss_setup *csetup) + struct snd_pcm_oss_setup **setup) { - int err = 0; + int idx, err; struct snd_pcm_oss_file *pcm_oss_file; - struct snd_pcm_substream *psubstream = NULL, *csubstream = NULL; + struct snd_pcm_substream *substream; unsigned int f_mode = file->f_mode; snd_assert(rpcm_oss_file != NULL, return -EINVAL); @@ -1761,73 +1750,31 @@ static int snd_pcm_oss_open_file(struct file *file, if ((f_mode & (FMODE_WRITE|FMODE_READ)) == (FMODE_WRITE|FMODE_READ) && (pcm->info_flags & SNDRV_PCM_INFO_HALF_DUPLEX)) f_mode = FMODE_WRITE; - if ((f_mode & FMODE_WRITE) && !(psetup && psetup->disable)) { - if ((err = snd_pcm_open_substream(pcm, SNDRV_PCM_STREAM_PLAYBACK, - &psubstream)) < 0) { + + for (idx = 0; idx < 2; idx++) { + if (! setup[idx] || setup[idx]->disable) + continue; + if (idx == SNDRV_PCM_STREAM_PLAYBACK) { + if (! (f_mode & FMODE_WRITE)) + continue; + } else { + if (! (f_mode & FMODE_READ)) + continue; + } + err = snd_pcm_open_substream(pcm, idx, file, &substream); + if (err < 0) { snd_pcm_oss_release_file(pcm_oss_file); return err; } - pcm_oss_file->streams[SNDRV_PCM_STREAM_PLAYBACK] = psubstream; - } - if ((f_mode & FMODE_READ) && !(csetup && csetup->disable)) { - if ((err = snd_pcm_open_substream(pcm, SNDRV_PCM_STREAM_CAPTURE, - &csubstream)) < 0) { - if (!(f_mode & FMODE_WRITE) || err != -ENODEV) { - snd_pcm_oss_release_file(pcm_oss_file); - return err; - } else { - csubstream = NULL; - } - } - pcm_oss_file->streams[SNDRV_PCM_STREAM_CAPTURE] = csubstream; + + pcm_oss_file->streams[idx] = substream; + snd_pcm_oss_init_substream(substream, setup[idx], minor); } - if (psubstream == NULL && csubstream == NULL) { + if (! pcm_oss_file->streams[0] && pcm_oss_file->streams[1]) { snd_pcm_oss_release_file(pcm_oss_file); return -EINVAL; } - if (psubstream != NULL) { - psubstream->oss.file = pcm_oss_file; - err = snd_pcm_hw_constraints_init(psubstream); - if (err < 0) { - snd_printd("snd_pcm_hw_constraint_init failed\n"); - snd_pcm_oss_release_file(pcm_oss_file); - return err; - } - if ((err = psubstream->ops->open(psubstream)) < 0) { - snd_pcm_oss_release_file(pcm_oss_file); - return err; - } - psubstream->ffile = file; - err = snd_pcm_hw_constraints_complete(psubstream); - if (err < 0) { - snd_printd("snd_pcm_hw_constraint_complete failed\n"); - snd_pcm_oss_release_file(pcm_oss_file); - return err; - } - snd_pcm_oss_init_substream(psubstream, psetup, minor); - } - if (csubstream != NULL) { - csubstream->oss.file = pcm_oss_file; - err = snd_pcm_hw_constraints_init(csubstream); - if (err < 0) { - snd_printd("snd_pcm_hw_constraint_init failed\n"); - snd_pcm_oss_release_file(pcm_oss_file); - return err; - } - if ((err = csubstream->ops->open(csubstream)) < 0) { - snd_pcm_oss_release_file(pcm_oss_file); - return err; - } - csubstream->ffile = file; - err = snd_pcm_hw_constraints_complete(csubstream); - if (err < 0) { - snd_printd("snd_pcm_hw_constraint_complete failed\n"); - snd_pcm_oss_release_file(pcm_oss_file); - return err; - } - snd_pcm_oss_init_substream(csubstream, csetup, minor); - } file->private_data = pcm_oss_file; *rpcm_oss_file = pcm_oss_file; @@ -1852,7 +1799,7 @@ static int snd_pcm_oss_open(struct inode *inode, struct file *file) char task_name[32]; struct snd_pcm *pcm; struct snd_pcm_oss_file *pcm_oss_file; - struct snd_pcm_oss_setup *psetup = NULL, *csetup = NULL; + struct snd_pcm_oss_setup *setup[2]; int nonblock; wait_queue_t wait; @@ -1873,23 +1820,13 @@ static int snd_pcm_oss_open(struct inode *inode, struct file *file) err = -EFAULT; goto __error; } + memset(setup, 0, sizeof(*setup)); if (file->f_mode & FMODE_WRITE) - psetup = snd_pcm_oss_look_for_setup(pcm, SNDRV_PCM_STREAM_PLAYBACK, task_name); + setup[0] = snd_pcm_oss_look_for_setup(pcm, SNDRV_PCM_STREAM_PLAYBACK, task_name); if (file->f_mode & FMODE_READ) - csetup = snd_pcm_oss_look_for_setup(pcm, SNDRV_PCM_STREAM_CAPTURE, task_name); + setup[1] = snd_pcm_oss_look_for_setup(pcm, SNDRV_PCM_STREAM_CAPTURE, task_name); nonblock = !!(file->f_flags & O_NONBLOCK); - if (psetup && !psetup->disable) { - if (psetup->nonblock) - nonblock = 1; - else if (psetup->block) - nonblock = 0; - } else if (csetup && !csetup->disable) { - if (csetup->nonblock) - nonblock = 1; - else if (csetup->block) - nonblock = 0; - } if (!nonblock) nonblock = nonblock_open; @@ -1898,7 +1835,7 @@ static int snd_pcm_oss_open(struct inode *inode, struct file *file) mutex_lock(&pcm->open_mutex); while (1) { err = snd_pcm_oss_open_file(file, pcm, &pcm_oss_file, - iminor(inode), psetup, csetup); + iminor(inode), setup); if (err >= 0) break; if (err == -EAGAIN) { diff --git a/sound/core/pcm.c b/sound/core/pcm.c index 1e9878fed37f..5d7eb123b999 100644 --- a/sound/core/pcm.c +++ b/sound/core/pcm.c @@ -777,8 +777,9 @@ static void snd_pcm_tick_timer_func(unsigned long data) snd_pcm_tick_elapsed(substream); } -int snd_pcm_open_substream(struct snd_pcm *pcm, int stream, - struct snd_pcm_substream **rsubstream) +int snd_pcm_attach_substream(struct snd_pcm *pcm, int stream, + struct file *file, + struct snd_pcm_substream **rsubstream) { struct snd_pcm_str * pstr; struct snd_pcm_substream *substream; @@ -793,7 +794,7 @@ int snd_pcm_open_substream(struct snd_pcm *pcm, int stream, *rsubstream = NULL; snd_assert(pcm != NULL, return -ENXIO); pstr = &pcm->streams[stream]; - if (pstr->substream == NULL) + if (pstr->substream == NULL || pstr->substream_count == 0) return -ENODEV; card = pcm->card; @@ -807,8 +808,6 @@ int snd_pcm_open_substream(struct snd_pcm *pcm, int stream, } up_read(&card->controls_rwsem); - if (pstr->substream_count == 0) - return -ENODEV; switch (stream) { case SNDRV_PCM_STREAM_PLAYBACK: if (pcm->info_flags & SNDRV_PCM_INFO_HALF_DUPLEX) { @@ -874,12 +873,13 @@ int snd_pcm_open_substream(struct snd_pcm *pcm, int stream, substream->runtime = runtime; substream->private_data = pcm->private_data; + substream->ffile = file; pstr->substream_opened++; *rsubstream = substream; return 0; } -void snd_pcm_release_substream(struct snd_pcm_substream *substream) +void snd_pcm_detach_substream(struct snd_pcm_substream *substream) { struct snd_pcm_runtime *runtime; substream->file = NULL; diff --git a/sound/core/pcm_lib.c b/sound/core/pcm_lib.c index eeba2f060955..230a940d00bd 100644 --- a/sound/core/pcm_lib.c +++ b/sound/core/pcm_lib.c @@ -2299,19 +2299,7 @@ snd_pcm_sframes_t snd_pcm_lib_write(struct snd_pcm_substream *substream, const v if (runtime->status->state == SNDRV_PCM_STATE_OPEN) return -EBADFD; - snd_assert(substream->ffile != NULL, return -ENXIO); nonblock = !!(substream->ffile->f_flags & O_NONBLOCK); -#if defined(CONFIG_SND_PCM_OSS) || defined(CONFIG_SND_PCM_OSS_MODULE) - if (substream->oss.oss) { - struct snd_pcm_oss_setup *setup = substream->oss.setup; - if (setup != NULL) { - if (setup->nonblock) - nonblock = 1; - else if (setup->block) - nonblock = 0; - } - } -#endif if (runtime->access != SNDRV_PCM_ACCESS_RW_INTERLEAVED && runtime->channels > 1) @@ -2374,19 +2362,7 @@ snd_pcm_sframes_t snd_pcm_lib_writev(struct snd_pcm_substream *substream, if (runtime->status->state == SNDRV_PCM_STATE_OPEN) return -EBADFD; - snd_assert(substream->ffile != NULL, return -ENXIO); nonblock = !!(substream->ffile->f_flags & O_NONBLOCK); -#if defined(CONFIG_SND_PCM_OSS) || defined(CONFIG_SND_PCM_OSS_MODULE) - if (substream->oss.oss) { - struct snd_pcm_oss_setup *setup = substream->oss.setup; - if (setup != NULL) { - if (setup->nonblock) - nonblock = 1; - else if (setup->block) - nonblock = 0; - } - } -#endif if (runtime->access != SNDRV_PCM_ACCESS_RW_NONINTERLEAVED) return -EINVAL; @@ -2596,19 +2572,7 @@ snd_pcm_sframes_t snd_pcm_lib_read(struct snd_pcm_substream *substream, void __u if (runtime->status->state == SNDRV_PCM_STATE_OPEN) return -EBADFD; - snd_assert(substream->ffile != NULL, return -ENXIO); nonblock = !!(substream->ffile->f_flags & O_NONBLOCK); -#if defined(CONFIG_SND_PCM_OSS) || defined(CONFIG_SND_PCM_OSS_MODULE) - if (substream->oss.oss) { - struct snd_pcm_oss_setup *setup = substream->oss.setup; - if (setup != NULL) { - if (setup->nonblock) - nonblock = 1; - else if (setup->block) - nonblock = 0; - } - } -#endif if (runtime->access != SNDRV_PCM_ACCESS_RW_INTERLEAVED) return -EINVAL; return snd_pcm_lib_read1(substream, (unsigned long)buf, size, nonblock, snd_pcm_lib_read_transfer); @@ -2665,20 +2629,7 @@ snd_pcm_sframes_t snd_pcm_lib_readv(struct snd_pcm_substream *substream, if (runtime->status->state == SNDRV_PCM_STATE_OPEN) return -EBADFD; - snd_assert(substream->ffile != NULL, return -ENXIO); nonblock = !!(substream->ffile->f_flags & O_NONBLOCK); -#if defined(CONFIG_SND_PCM_OSS) || defined(CONFIG_SND_PCM_OSS_MODULE) - if (substream->oss.oss) { - struct snd_pcm_oss_setup *setup = substream->oss.setup; - if (setup != NULL) { - if (setup->nonblock) - nonblock = 1; - else if (setup->block) - nonblock = 0; - } - } -#endif - if (runtime->access != SNDRV_PCM_ACCESS_RW_NONINTERLEAVED) return -EINVAL; return snd_pcm_lib_read1(substream, (unsigned long)bufs, frames, nonblock, snd_pcm_lib_readv_transfer); diff --git a/sound/core/pcm_native.c b/sound/core/pcm_native.c index 13efe39e4cc6..964e4c47a7f1 100644 --- a/sound/core/pcm_native.c +++ b/sound/core/pcm_native.c @@ -1995,28 +1995,63 @@ static void snd_pcm_remove_file(struct snd_pcm_str *str, } } -static int snd_pcm_release_file(struct snd_pcm_file * pcm_file) +static void pcm_release_private(struct snd_pcm_substream *substream) { - struct snd_pcm_substream *substream; - struct snd_pcm_runtime *runtime; - struct snd_pcm_str * str; + struct snd_pcm_file *pcm_file = substream->file; - snd_assert(pcm_file != NULL, return -ENXIO); - substream = pcm_file->substream; - snd_assert(substream != NULL, return -ENXIO); - runtime = substream->runtime; - str = substream->pstr; snd_pcm_unlink(substream); - if (substream->ffile != NULL) { + snd_pcm_remove_file(substream->pstr, pcm_file); + kfree(pcm_file); +} + +void snd_pcm_release_substream(struct snd_pcm_substream *substream) +{ + snd_pcm_drop(substream); + if (substream->pcm_release) + substream->pcm_release(substream); + if (substream->hw_opened) { if (substream->ops->hw_free != NULL) substream->ops->hw_free(substream); substream->ops->close(substream); - substream->ffile = NULL; + substream->hw_opened = 0; } - snd_pcm_remove_file(str, pcm_file); - snd_pcm_release_substream(substream); - kfree(pcm_file); + snd_pcm_detach_substream(substream); +} + +int snd_pcm_open_substream(struct snd_pcm *pcm, int stream, + struct file *file, + struct snd_pcm_substream **rsubstream) +{ + struct snd_pcm_substream *substream; + int err; + + err = snd_pcm_attach_substream(pcm, stream, file, &substream); + if (err < 0) + return err; + substream->no_mmap_ctrl = 0; + err = snd_pcm_hw_constraints_init(substream); + if (err < 0) { + snd_printd("snd_pcm_hw_constraints_init failed\n"); + goto error; + } + + if ((err = substream->ops->open(substream)) < 0) + goto error; + + substream->hw_opened = 1; + + err = snd_pcm_hw_constraints_complete(substream); + if (err < 0) { + snd_printd("snd_pcm_hw_constraints_complete failed\n"); + goto error; + } + + *rsubstream = substream; return 0; + + error: + snd_pcm_release_substream(substream); + return err; } static int snd_pcm_open_file(struct file *file, @@ -2024,52 +2059,29 @@ static int snd_pcm_open_file(struct file *file, int stream, struct snd_pcm_file **rpcm_file) { - int err = 0; struct snd_pcm_file *pcm_file; struct snd_pcm_substream *substream; struct snd_pcm_str *str; + int err; snd_assert(rpcm_file != NULL, return -EINVAL); *rpcm_file = NULL; + err = snd_pcm_open_substream(pcm, stream, file, &substream); + if (err < 0) + return err; + pcm_file = kzalloc(sizeof(*pcm_file), GFP_KERNEL); if (pcm_file == NULL) { + snd_pcm_release_substream(substream); return -ENOMEM; } - - if ((err = snd_pcm_open_substream(pcm, stream, &substream)) < 0) { - kfree(pcm_file); - return err; - } - str = substream->pstr; substream->file = pcm_file; - substream->no_mmap_ctrl = 0; - + substream->pcm_release = pcm_release_private; pcm_file->substream = substream; - snd_pcm_add_file(str, pcm_file); - err = snd_pcm_hw_constraints_init(substream); - if (err < 0) { - snd_printd("snd_pcm_hw_constraints_init failed\n"); - snd_pcm_release_file(pcm_file); - return err; - } - - if ((err = substream->ops->open(substream)) < 0) { - snd_pcm_release_file(pcm_file); - return err; - } - substream->ffile = file; - - err = snd_pcm_hw_constraints_complete(substream); - if (err < 0) { - snd_printd("snd_pcm_hw_constraints_complete failed\n"); - snd_pcm_release_file(pcm_file); - return err; - } - file->private_data = pcm_file; *rpcm_file = pcm_file; return 0; @@ -2158,10 +2170,9 @@ static int snd_pcm_release(struct inode *inode, struct file *file) snd_assert(substream != NULL, return -ENXIO); snd_assert(!atomic_read(&substream->runtime->mmap_count), ); pcm = substream->pcm; - snd_pcm_drop(substream); fasync_helper(-1, file, 0, &substream->runtime->fasync); mutex_lock(&pcm->open_mutex); - snd_pcm_release_file(pcm_file); + snd_pcm_release_substream(substream); mutex_unlock(&pcm->open_mutex); wake_up(&pcm->open_wait); module_put(pcm->card->module); @@ -2480,11 +2491,6 @@ static int snd_pcm_sync_ptr(struct snd_pcm_substream *substream, return 0; } -static int snd_pcm_playback_ioctl1(struct snd_pcm_substream *substream, - unsigned int cmd, void __user *arg); -static int snd_pcm_capture_ioctl1(struct snd_pcm_substream *substream, - unsigned int cmd, void __user *arg); - static int snd_pcm_common_ioctl1(struct snd_pcm_substream *substream, unsigned int cmd, void __user *arg) { -- cgit v1.2.3-59-g8ed1b From 060d77b9c04acd7aef60790398a53f731db8c8fe Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Mon, 27 Mar 2006 16:44:52 +0200 Subject: [ALSA] Fix / clean up PCM-OSS setup hooks - Fix possible race of referring the setup hook from the running PCM - Fix memory leak in an error path of proc write - Clean up the setup hook parser Signed-off-by: Takashi Iwai --- include/sound/pcm_oss.h | 2 +- sound/core/oss/pcm_oss.c | 133 ++++++++++++++++++++++------------------------- 2 files changed, 64 insertions(+), 71 deletions(-) diff --git a/include/sound/pcm_oss.h b/include/sound/pcm_oss.h index 1d522aaa66df..39df2baca18a 100644 --- a/include/sound/pcm_oss.h +++ b/include/sound/pcm_oss.h @@ -69,7 +69,7 @@ struct snd_pcm_oss_file { struct snd_pcm_oss_substream { unsigned oss: 1; /* oss mode */ - struct snd_pcm_oss_setup *setup; /* active setup */ + struct snd_pcm_oss_setup setup; /* active setup */ }; struct snd_pcm_oss_stream { diff --git a/sound/core/oss/pcm_oss.c b/sound/core/oss/pcm_oss.c index c056cbfe5519..91114c7aeff5 100644 --- a/sound/core/oss/pcm_oss.c +++ b/sound/core/oss/pcm_oss.c @@ -208,9 +208,8 @@ static int snd_pcm_oss_period_size(struct snd_pcm_substream *substream, oss_buffer_size = runtime->oss.mmap_bytes; } - if (substream->oss.setup && - substream->oss.setup->period_size > 16) - oss_period_size = substream->oss.setup->period_size; + if (substream->oss.setup.period_size > 16) + oss_period_size = substream->oss.setup.period_size; else if (runtime->oss.fragshift) { oss_period_size = 1 << runtime->oss.fragshift; if (oss_period_size > oss_buffer_size / 2) @@ -252,10 +251,8 @@ static int snd_pcm_oss_period_size(struct snd_pcm_substream *substream, oss_periods = oss_buffer_size / oss_period_size; - if (substream->oss.setup) { - if (substream->oss.setup->periods > 1) - oss_periods = substream->oss.setup->periods; - } + if (substream->oss.setup.periods > 1) + oss_periods = substream->oss.setup.periods; s = snd_pcm_hw_param_value_max(slave_params, SNDRV_PCM_HW_PARAM_PERIODS, NULL); if (runtime->oss.maxfrags && s > runtime->oss.maxfrags) @@ -341,12 +338,10 @@ static int snd_pcm_oss_change_params(struct snd_pcm_substream *substream) goto failure; } - if (atomic_read(&runtime->mmap_count)) { + if (atomic_read(&runtime->mmap_count)) direct = 1; - } else { - struct snd_pcm_oss_setup *setup = substream->oss.setup; - direct = (setup != NULL && setup->direct); - } + else + direct = substream->oss.setup.direct; _snd_pcm_hw_params_any(sparams); _snd_pcm_hw_param_setinteger(sparams, SNDRV_PCM_HW_PARAM_PERIODS); @@ -482,7 +477,7 @@ static int snd_pcm_oss_change_params(struct snd_pcm_substream *substream) 1 : runtime->period_size; sw_params->xfer_align = 1; if (atomic_read(&runtime->mmap_count) || - (substream->oss.setup && substream->oss.setup->nosilence)) { + substream->oss.setup.nosilence) { sw_params->silence_threshold = 0; sw_params->silence_size = 0; } else { @@ -843,7 +838,7 @@ static ssize_t snd_pcm_oss_write1(struct snd_pcm_substream *substream, const cha buf += tmp; bytes -= tmp; xfer += tmp; - if ((substream->oss.setup != NULL && substream->oss.setup->partialfrag) || + if (substream->oss.setup.partialfrag || runtime->oss.buffer_used == runtime->oss.period_bytes) { tmp = snd_pcm_oss_write2(substream, runtime->oss.buffer + runtime->oss.period_ptr, runtime->oss.buffer_used - runtime->oss.period_ptr, 1); @@ -1214,12 +1209,10 @@ static int snd_pcm_oss_get_formats(struct snd_pcm_oss_file *pcm_oss_file) if ((err = snd_pcm_oss_get_active_substream(pcm_oss_file, &substream)) < 0) return err; - if (atomic_read(&substream->runtime->mmap_count)) { + if (atomic_read(&substream->runtime->mmap_count)) direct = 1; - } else { - struct snd_pcm_oss_setup *setup = substream->oss.setup; - direct = (setup != NULL && setup->direct); - } + else + direct = substream->oss.setup.direct; if (!direct) return AFMT_MU_LAW | AFMT_U8 | AFMT_S16_LE | AFMT_S16_BE | @@ -1555,8 +1548,7 @@ static int snd_pcm_oss_get_ptr(struct snd_pcm_oss_file *pcm_oss_file, int stream } else { delay = snd_pcm_oss_bytes(substream, delay); if (stream == SNDRV_PCM_STREAM_PLAYBACK) { - struct snd_pcm_oss_setup *setup = substream->oss.setup; - if (setup && setup->buggyptr) + if (substream->oss.setup.buggyptr) info.blocks = (runtime->oss.buffer_bytes - delay - fixup) / runtime->oss.period_bytes; else info.blocks = (delay + fixup) / runtime->oss.period_bytes; @@ -1638,37 +1630,34 @@ static int snd_pcm_oss_get_mapbuf(struct snd_pcm_oss_file *pcm_oss_file, int str return -EINVAL; } -static struct snd_pcm_oss_setup *snd_pcm_oss_look_for_setup(struct snd_pcm *pcm, int stream, const char *task_name) +static const char *strip_task_path(const char *path) { - const char *ptr, *ptrl; - struct snd_pcm_oss_setup *setup; - - mutex_lock(&pcm->streams[stream].oss.setup_mutex); - for (setup = pcm->streams[stream].oss.setup_list; setup; setup = setup->next) { - if (!strcmp(setup->task_name, task_name)) { - mutex_unlock(&pcm->streams[stream].oss.setup_mutex); - return setup; - } - } - ptr = ptrl = task_name; - while (*ptr) { + const char *ptr, *ptrl = NULL; + for (ptr = path; *ptr; ptr++) { if (*ptr == '/') ptrl = ptr + 1; - ptr++; - } - if (ptrl == task_name) { - goto __not_found; - return NULL; } - for (setup = pcm->streams[stream].oss.setup_list; setup; setup = setup->next) { - if (!strcmp(setup->task_name, ptrl)) { - mutex_unlock(&pcm->streams[stream].oss.setup_mutex); - return setup; + return ptrl; +} + +static void snd_pcm_oss_look_for_setup(struct snd_pcm *pcm, int stream, + const char *task_name, + struct snd_pcm_oss_setup *rsetup) +{ + struct snd_pcm_oss_setup *setup; + + mutex_lock(&pcm->streams[stream].oss.setup_mutex); + do { + for (setup = pcm->streams[stream].oss.setup_list; setup; + setup = setup->next) { + if (!strcmp(setup->task_name, task_name)) + goto out; } - } - __not_found: + } while ((task_name = strip_task_path(task_name)) != NULL); + out: + if (setup) + *rsetup = *setup; mutex_unlock(&pcm->streams[stream].oss.setup_mutex); - return NULL; } static void snd_pcm_oss_release_substream(struct snd_pcm_substream *substream) @@ -1690,7 +1679,7 @@ static void snd_pcm_oss_init_substream(struct snd_pcm_substream *substream, struct snd_pcm_runtime *runtime; substream->oss.oss = 1; - substream->oss.setup = setup; + substream->oss.setup = *setup; if (setup->nonblock) substream->ffile->f_flags |= O_NONBLOCK; else @@ -1733,7 +1722,7 @@ static int snd_pcm_oss_open_file(struct file *file, struct snd_pcm *pcm, struct snd_pcm_oss_file **rpcm_oss_file, int minor, - struct snd_pcm_oss_setup **setup) + struct snd_pcm_oss_setup *setup) { int idx, err; struct snd_pcm_oss_file *pcm_oss_file; @@ -1752,7 +1741,7 @@ static int snd_pcm_oss_open_file(struct file *file, f_mode = FMODE_WRITE; for (idx = 0; idx < 2; idx++) { - if (! setup[idx] || setup[idx]->disable) + if (setup[idx].disable) continue; if (idx == SNDRV_PCM_STREAM_PLAYBACK) { if (! (f_mode & FMODE_WRITE)) @@ -1768,7 +1757,7 @@ static int snd_pcm_oss_open_file(struct file *file, } pcm_oss_file->streams[idx] = substream; - snd_pcm_oss_init_substream(substream, setup[idx], minor); + snd_pcm_oss_init_substream(substream, &setup[idx], minor); } if (! pcm_oss_file->streams[0] && pcm_oss_file->streams[1]) { @@ -1799,7 +1788,7 @@ static int snd_pcm_oss_open(struct inode *inode, struct file *file) char task_name[32]; struct snd_pcm *pcm; struct snd_pcm_oss_file *pcm_oss_file; - struct snd_pcm_oss_setup *setup[2]; + struct snd_pcm_oss_setup setup[2]; int nonblock; wait_queue_t wait; @@ -1822,9 +1811,11 @@ static int snd_pcm_oss_open(struct inode *inode, struct file *file) } memset(setup, 0, sizeof(*setup)); if (file->f_mode & FMODE_WRITE) - setup[0] = snd_pcm_oss_look_for_setup(pcm, SNDRV_PCM_STREAM_PLAYBACK, task_name); + snd_pcm_oss_look_for_setup(pcm, SNDRV_PCM_STREAM_PLAYBACK, + task_name, &setup[0]); if (file->f_mode & FMODE_READ) - setup[1] = snd_pcm_oss_look_for_setup(pcm, SNDRV_PCM_STREAM_CAPTURE, task_name); + snd_pcm_oss_look_for_setup(pcm, SNDRV_PCM_STREAM_CAPTURE, + task_name, &setup[1]); nonblock = !!(file->f_flags & O_NONBLOCK); if (!nonblock) @@ -2249,13 +2240,8 @@ static void snd_pcm_oss_proc_read(struct snd_info_entry *entry, static void snd_pcm_oss_proc_free_setup_list(struct snd_pcm_str * pstr) { - unsigned int idx; - struct snd_pcm_substream *substream; struct snd_pcm_oss_setup *setup, *setupn; - for (idx = 0, substream = pstr->substream; - idx < pstr->substream_count; idx++, substream = substream->next) - substream->oss.setup = NULL; for (setup = pstr->oss.setup_list, pstr->oss.setup_list = NULL; setup; setup = setupn) { setupn = setup->next; @@ -2316,21 +2302,28 @@ static void snd_pcm_oss_proc_write(struct snd_info_entry *entry, } } while (*str); if (setup == NULL) { - setup = kmalloc(sizeof(struct snd_pcm_oss_setup), GFP_KERNEL); - if (setup) { - if (pstr->oss.setup_list == NULL) { - pstr->oss.setup_list = setup; - } else { - for (setup1 = pstr->oss.setup_list; setup1->next; setup1 = setup1->next); - setup1->next = setup; - } - template.task_name = kstrdup(task_name, GFP_KERNEL); - } else { + setup = kmalloc(sizeof(*setup), GFP_KERNEL); + if (! setup) { + buffer->error = -ENOMEM; + mutex_lock(&pstr->oss.setup_mutex); + return; + } + if (pstr->oss.setup_list == NULL) + pstr->oss.setup_list = setup; + else { + for (setup1 = pstr->oss.setup_list; + setup1->next; setup1 = setup1->next); + setup1->next = setup; + } + template.task_name = kstrdup(task_name, GFP_KERNEL); + if (! template.task_name) { + kfree(setup); buffer->error = -ENOMEM; + mutex_lock(&pstr->oss.setup_mutex); + return; } } - if (setup) - *setup = template; + *setup = template; mutex_unlock(&pstr->oss.setup_mutex); } } -- cgit v1.2.3-59-g8ed1b From cf40a310a7aaf1944eea3e01e9c120b31850c3b6 Mon Sep 17 00:00:00 2001 From: Rene Herman Date: Tue, 28 Mar 2006 12:38:20 +0200 Subject: [ALSA] AdLib FM card driver Attached you'll find an ALSA driver for AdLib FM cards. An AdLib card is just an OPL2, which was already supported by sound/drivers/opl3, so only very minimal bus-glue is needed. The patch applies cleanly to both 2.6.16 and 2.6.16-mm1. The driver has been tested with an actual ancient 8-bit ISA AdLib card and works fine. It also works fine for an OPL3 {,emulation} as still found on many ISA soundcards but given that AdLib cards don't have their own mixer, upping the volume from 0 might be a problem without the card driver already loaded and driving the OPL3. Signed-off-by: Takashi Iwai --- Documentation/sound/alsa/ALSA-Configuration.txt | 28 +++++ sound/isa/Kconfig | 9 ++ sound/isa/Makefile | 2 + sound/isa/adlib.c | 161 ++++++++++++++++++++++++ 4 files changed, 200 insertions(+) create mode 100644 sound/isa/adlib.c diff --git a/Documentation/sound/alsa/ALSA-Configuration.txt b/Documentation/sound/alsa/ALSA-Configuration.txt index a8c3c7e847cf..0ee2c7dfc482 100644 --- a/Documentation/sound/alsa/ALSA-Configuration.txt +++ b/Documentation/sound/alsa/ALSA-Configuration.txt @@ -120,6 +120,34 @@ Prior to version 0.9.0rc4 options had a 'snd_' prefix. This was removed. enable - enable card - Default: enabled, for PCI and ISA PnP cards + Module snd-adlib + ---------------- + + Module for AdLib FM cards. + + port - port # for OPL chip + + This module supports multiple cards. It does not support autoprobe, so + the port must be specified. For actual AdLib FM cards it will be 0x388. + Note that this card does not have PCM support and no mixer; only FM + synthesis. + + Make sure you have "sbiload" from the alsa-tools package available and, + after loading the module, find out the assigned ALSA sequencer port + number through "sbiload -l". Example output: + + Port Client name Port name + 64:0 OPL2 FM synth OPL2 FM Port + + Load the std.sb and drums.sb patches also supplied by sbiload: + + sbiload -p 64:0 std.sb drums.sb + + If you use this driver to drive an OPL3, you can use std.o3 and drums.o3 + instead. To have the card produce sound, use aplaymidi from alsa-utils: + + aplaymidi -p 64:0 foo.mid + Module snd-ad1816a ------------------ diff --git a/sound/isa/Kconfig b/sound/isa/Kconfig index 2a1c7334210a..557c4de22960 100644 --- a/sound/isa/Kconfig +++ b/sound/isa/Kconfig @@ -11,6 +11,15 @@ config SND_CS4231_LIB tristate select SND_PCM +config SND_ADLIB + tristate "AdLib FM card" + select SND_OPL3_LIB + help + Say Y here to include support for AdLib FM cards. + + To compile this driver as a module, choose M here: the module + will be called snd-adlib. + config SND_AD1816A tristate "Analog Devices SoundPort AD1816A" depends on SND && PNP && ISA diff --git a/sound/isa/Makefile b/sound/isa/Makefile index 05724eb7bfe4..bb317ccc170f 100644 --- a/sound/isa/Makefile +++ b/sound/isa/Makefile @@ -3,6 +3,7 @@ # Copyright (c) 2001 by Jaroslav Kysela # +snd-adlib-objs := adlib.o snd-als100-objs := als100.o snd-azt2320-objs := azt2320.o snd-cmi8330-objs := cmi8330.o @@ -13,6 +14,7 @@ snd-sgalaxy-objs := sgalaxy.o snd-sscape-objs := sscape.o # Toplevel Module Dependency +obj-$(CONFIG_SND_ADLIB) += snd-adlib.o obj-$(CONFIG_SND_ALS100) += snd-als100.o obj-$(CONFIG_SND_AZT2320) += snd-azt2320.o obj-$(CONFIG_SND_CMI8330) += snd-cmi8330.o diff --git a/sound/isa/adlib.c b/sound/isa/adlib.c new file mode 100644 index 000000000000..a253a14e6a45 --- /dev/null +++ b/sound/isa/adlib.c @@ -0,0 +1,161 @@ +/* + * AdLib FM card driver. + */ + +#include +#include +#include +#include +#include +#include +#include + +#define CRD_NAME "AdLib FM" +#define DRV_NAME "snd_adlib" + +MODULE_DESCRIPTION(CRD_NAME); +MODULE_AUTHOR("Rene Herman"); +MODULE_LICENSE("GPL"); + +static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; +static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; +static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE; +static long port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "Index value for " CRD_NAME " soundcard."); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string for " CRD_NAME " soundcard."); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "Enable " CRD_NAME " soundcard."); +module_param_array(port, long, NULL, 0444); +MODULE_PARM_DESC(port, "Port # for " CRD_NAME " driver."); + +static struct platform_device *devices[SNDRV_CARDS]; + +static void snd_adlib_free(struct snd_card *card) +{ + release_and_free_resource(card->private_data); +} + +static int __devinit snd_adlib_probe(struct platform_device *device) +{ + struct snd_card *card; + struct snd_opl3 *opl3; + + int error; + int i = device->id; + + if (port[i] == SNDRV_AUTO_PORT) { + snd_printk(KERN_ERR DRV_NAME ": please specify port\n"); + error = -EINVAL; + goto out0; + } + + card = snd_card_new(index[i], id[i], THIS_MODULE, 0); + if (!card) { + snd_printk(KERN_ERR DRV_NAME ": could not create card\n"); + error = -EINVAL; + goto out0; + } + + card->private_data = request_region(port[i], 4, CRD_NAME); + if (!card->private_data) { + snd_printk(KERN_ERR DRV_NAME ": could not grab ports\n"); + error = -EBUSY; + goto out1; + } + card->private_free = snd_adlib_free; + + error = snd_opl3_create(card, port[i], port[i] + 2, OPL3_HW_AUTO, 1, &opl3); + if (error < 0) { + snd_printk(KERN_ERR DRV_NAME ": could not create OPL\n"); + goto out1; + } + + error = snd_opl3_hwdep_new(opl3, 0, 0, NULL); + if (error < 0) { + snd_printk(KERN_ERR DRV_NAME ": could not create FM\n"); + goto out1; + } + + strcpy(card->driver, DRV_NAME); + strcpy(card->shortname, CRD_NAME); + sprintf(card->longname, CRD_NAME " at %#lx", port[i]); + + snd_card_set_dev(card, &device->dev); + + error = snd_card_register(card); + if (error < 0) { + snd_printk(KERN_ERR DRV_NAME ": could not register card\n"); + goto out1; + } + + platform_set_drvdata(device, card); + return 0; + +out1: snd_card_free(card); + out0: error = -EINVAL; /* FIXME: should be the original error code */ + return error; +} + +static int __devexit snd_adlib_remove(struct platform_device *device) +{ + snd_card_free(platform_get_drvdata(device)); + platform_set_drvdata(device, NULL); + return 0; +} + +static struct platform_driver snd_adlib_driver = { + .probe = snd_adlib_probe, + .remove = __devexit_p(snd_adlib_remove), + + .driver = { + .name = DRV_NAME + } +}; + +static int __init alsa_card_adlib_init(void) +{ + int i, cards; + + if (platform_driver_register(&snd_adlib_driver) < 0) { + snd_printk(KERN_ERR DRV_NAME ": could not register driver\n"); + return -ENODEV; + } + + for (cards = 0, i = 0; i < SNDRV_CARDS; i++) { + struct platform_device *device; + + if (!enable[i]) + continue; + + device = platform_device_register_simple(DRV_NAME, i, NULL, 0); + if (IS_ERR(device)) + continue; + + devices[i] = device; + cards++; + } + + if (!cards) { +#ifdef MODULE + printk(KERN_ERR CRD_NAME " soundcard not found or device busy\n"); +#endif + platform_driver_unregister(&snd_adlib_driver); + return -ENODEV; + } + return 0; +} + +static void __exit alsa_card_adlib_exit(void) +{ + int i; + + for (i = 0; i < SNDRV_CARDS; i++) + platform_device_unregister(devices[i]); + platform_driver_unregister(&snd_adlib_driver); +} + +module_init(alsa_card_adlib_init); +module_exit(alsa_card_adlib_exit); -- cgit v1.2.3-59-g8ed1b From a1e8d2da03b3a1017aab01d49666ec9b67927de5 Mon Sep 17 00:00:00 2001 From: Jonathan Woithe Date: Tue, 28 Mar 2006 12:47:09 +0200 Subject: [ALSA] HDA/Realtek: multiple input mux definitions and pin mode additions The following patch relative to CVS from 20060324 adds the following features to the Realtek HDA codec. 1) Define two new pin modes: ALC_PIN_DIR_IN_NOMICBIAS and ALC_PIN_DIR_INOUT_NOMICBIAS. These can be used with jack mode switch definitions in mixers to prevent the user being offered the mic bias options if the hardware doesn't support it. 2) Add the ability to have different input mux definitions for different ADCs. This is needed because the ALC260 chip uses different mux layouts for the two onboard ADCs. A new field (num_mux_defs) was added to the alc_spec and alc_config_preset structures to support this. 3) Adjust numerous comments to make them consistent with the above changes. 4) Utilise the new multi-mux definition functionality for the ALC260 fujitsu model to allow recording of the mixer output. 5) Utilise the new multi-mux definition functionality for the ALC260 test model to make the mux selections a little less confusing. 6) Allow the headphone jack of the ALC260 acer model to be retasked in the mixer. 6) Utilise the new multi-mux definition functionality for the ALC260 acer model to give access to the mixer output and the retasked headphone jack. At this stage the *_NOMICBIAS modes are not used. We have reports that the "Line" jack of at least some Acer models doesn't pass the bias out, and we also know that NIDs 0x0f and 0x10 don't seem to accept the mic bias requests at all. However, I feel we need to collect more evidence on both counts before committing to the use of *_NOMICBIAS. In the case of the Acers, it's not clear whether this issue (probably caused by the inclusion of DC blocking capacitors) affects all Acer models or just a small number. With the issue with NIDs 0x0f and 0x10 it's unclear whether this is a hardware bug which will be addressed in later chip revisions or if it's an intentional restriction. The datasheet makes no mention of the restriction so at this stage I'm inclined to consider it a hardware bug. Comments in the source reflect this reasoning. On a similar theme, the headphone jack of the Fujitsu S7020 also doesn't appear to pass mic bias voltage. I'm still investigating this however. With the ability to retask the headphone jack, owners of ALC260-based Acer laptops should now be able to record 4 channels of audio if they desire. The multiple mux definitions allow this jack to be presented from both ADCs (since this mux input is one of those which differs between the muxes). This patch has been tested on a Fujitsu S7020 laptop and appears to behave itself both for the "test" and "fujitsu" models. Definitions using only a single mux specification also work. Other ALC chips should be fine but I cannot test these myself. The "auto" modes should also continue to function but again I have not verified this. Signed-off-by: Takashi Iwai --- sound/pci/hda/patch_realtek.c | 205 ++++++++++++++++++++++++++++++------------ 1 file changed, 149 insertions(+), 56 deletions(-) diff --git a/sound/pci/hda/patch_realtek.c b/sound/pci/hda/patch_realtek.c index 6b45635b3ea3..66bbdb60f50b 100644 --- a/sound/pci/hda/patch_realtek.c +++ b/sound/pci/hda/patch_realtek.c @@ -132,6 +132,7 @@ struct alc_spec { hda_nid_t dig_in_nid; /* digital-in NID; optional */ /* capture source */ + unsigned int num_mux_defs; const struct hda_input_mux *input_mux; unsigned int cur_mux[3]; @@ -173,6 +174,7 @@ struct alc_config_preset { hda_nid_t dig_in_nid; unsigned int num_channel_mode; const struct hda_channel_mode *channel_mode; + unsigned int num_mux_defs; const struct hda_input_mux *input_mux; void (*unsol_event)(struct hda_codec *, unsigned int); void (*init_hook)(struct hda_codec *); @@ -186,7 +188,10 @@ static int alc_mux_enum_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_ { struct hda_codec *codec = snd_kcontrol_chip(kcontrol); struct alc_spec *spec = codec->spec; - return snd_hda_input_mux_info(spec->input_mux, uinfo); + unsigned int mux_idx = snd_ctl_get_ioffidx(kcontrol, &uinfo->id); + if (mux_idx >= spec->num_mux_defs) + mux_idx = 0; + return snd_hda_input_mux_info(&spec->input_mux[mux_idx], uinfo); } static int alc_mux_enum_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) @@ -204,7 +209,8 @@ static int alc_mux_enum_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_v struct hda_codec *codec = snd_kcontrol_chip(kcontrol); struct alc_spec *spec = codec->spec; unsigned int adc_idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id); - return snd_hda_input_mux_put(codec, spec->input_mux, ucontrol, + unsigned int mux_idx = adc_idx >= spec->num_mux_defs ? 0 : adc_idx; + return snd_hda_input_mux_put(codec, &spec->input_mux[mux_idx], ucontrol, spec->adc_nids[adc_idx], &spec->cur_mux[adc_idx]); } @@ -246,7 +252,8 @@ static int alc_ch_mode_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_va * states other than HiZ (eg: PIN_VREFxx) and revert to HiZ if any of these * are requested. Therefore order this list so that this behaviour will not * cause problems when mixer clients move through the enum sequentially. - * NIDs 0x0f and 0x10 have been observed to have this behaviour. + * NIDs 0x0f and 0x10 have been observed to have this behaviour as of + * March 2006. */ static char *alc_pin_mode_names[] = { "Mic 50pc bias", "Mic 80pc bias", @@ -256,19 +263,27 @@ static unsigned char alc_pin_mode_values[] = { PIN_VREF50, PIN_VREF80, PIN_IN, PIN_OUT, PIN_HP, }; /* The control can present all 5 options, or it can limit the options based - * in the pin being assumed to be exclusively an input or an output pin. + * in the pin being assumed to be exclusively an input or an output pin. In + * addition, "input" pins may or may not process the mic bias option + * depending on actual widget capability (NIDs 0x0f and 0x10 don't seem to + * accept requests for bias as of chip versions up to March 2006) and/or + * wiring in the computer. */ -#define ALC_PIN_DIR_IN 0x00 -#define ALC_PIN_DIR_OUT 0x01 -#define ALC_PIN_DIR_INOUT 0x02 +#define ALC_PIN_DIR_IN 0x00 +#define ALC_PIN_DIR_OUT 0x01 +#define ALC_PIN_DIR_INOUT 0x02 +#define ALC_PIN_DIR_IN_NOMICBIAS 0x03 +#define ALC_PIN_DIR_INOUT_NOMICBIAS 0x04 -/* Info about the pin modes supported by the three different pin directions. +/* Info about the pin modes supported by the different pin direction modes. * For each direction the minimum and maximum values are given. */ -static signed char alc_pin_mode_dir_info[3][2] = { +static signed char alc_pin_mode_dir_info[5][2] = { { 0, 2 }, /* ALC_PIN_DIR_IN */ { 3, 4 }, /* ALC_PIN_DIR_OUT */ { 0, 4 }, /* ALC_PIN_DIR_INOUT */ + { 2, 2 }, /* ALC_PIN_DIR_IN_NOMICBIAS */ + { 2, 4 }, /* ALC_PIN_DIR_INOUT_NOMICBIAS */ }; #define alc_pin_mode_min(_dir) (alc_pin_mode_dir_info[_dir][0]) #define alc_pin_mode_max(_dir) (alc_pin_mode_dir_info[_dir][1]) @@ -330,9 +345,10 @@ static int alc_pin_mode_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_v * input modes. * * Dynamically switching the input/output buffers probably - * reduces noise slightly, particularly on input. However, - * havingboth input and output buffers enabled - * simultaneously doesn't seem to be problematic. + * reduces noise slightly (particularly on input) so we'll + * do it. However, having both input and output buffers + * enabled simultaneously doesn't seem to be problematic if + * this turns out to be necessary in the future. */ if (val <= 2) { snd_hda_codec_write(codec,nid,0,AC_VERB_SET_AMP_GAIN_MUTE, @@ -484,6 +500,9 @@ static void setup_preset(struct alc_spec *spec, const struct alc_config_preset * spec->multiout.dig_out_nid = preset->dig_out_nid; spec->multiout.hp_nid = preset->hp_nid; + spec->num_mux_defs = preset->num_mux_defs; + if (! spec->num_mux_defs) + spec->num_mux_defs = 1; spec->input_mux = preset->input_mux; spec->num_adc_nids = preset->num_adc_nids; @@ -2686,6 +2705,7 @@ static int alc880_parse_auto_config(struct hda_codec *codec) spec->init_verbs[spec->num_init_verbs++] = alc880_volume_init_verbs; + spec->num_mux_defs = 1; spec->input_mux = &spec->private_imux; return 1; @@ -2815,30 +2835,56 @@ static struct hda_input_mux alc260_capture_source = { }; /* On Fujitsu S702x laptops capture only makes sense from Mic/LineIn jack, - * headphone jack and the internal CD lines. + * headphone jack and the internal CD lines since these are the only pins at + * which audio can appear. For flexibility, also allow the option of + * recording the mixer output on the second ADC (ADC0 doesn't have a + * connection to the mixer output). */ -static struct hda_input_mux alc260_fujitsu_capture_source = { - .num_items = 3, - .items = { - { "Mic/Line", 0x0 }, - { "CD", 0x4 }, - { "Headphone", 0x2 }, +static struct hda_input_mux alc260_fujitsu_capture_sources[2] = { + { + .num_items = 3, + .items = { + { "Mic/Line", 0x0 }, + { "CD", 0x4 }, + { "Headphone", 0x2 }, + }, }, + { + .num_items = 4, + .items = { + { "Mic/Line", 0x0 }, + { "CD", 0x4 }, + { "Headphone", 0x2 }, + { "Mixer", 0x5 }, + }, + }, + }; -/* Acer TravelMate(/Extensa/Aspire) notebooks have similar configutation to - * the Fujitsu S702x, but jacks are marked differently. We won't allow - * retasking the Headphone jack, so it won't be available here. +/* Acer TravelMate(/Extensa/Aspire) notebooks have similar configuration to + * the Fujitsu S702x, but jacks are marked differently. */ -static struct hda_input_mux alc260_acer_capture_source = { - .num_items = 3, - .items = { - { "Mic", 0x0 }, - { "Line", 0x2 }, - { "CD", 0x4 }, +static struct hda_input_mux alc260_acer_capture_sources[2] = { + { + .num_items = 4, + .items = { + { "Mic", 0x0 }, + { "Line", 0x2 }, + { "CD", 0x4 }, + { "Headphone", 0x5 }, + }, + }, + { + .num_items = 5, + .items = { + { "Mic", 0x0 }, + { "Line", 0x2 }, + { "CD", 0x4 }, + { "Headphone", 0x6 }, + { "Mixer", 0x5 }, + }, }, }; - /* * This is just place-holder, so there's something for alc_build_pcms to look * at when it calculates the maximum number of channels. ALC260 has no mixer @@ -2899,6 +2945,9 @@ static struct snd_kcontrol_new alc260_hp_3013_mixer[] = { { } /* end */ }; +/* Fujitsu S702x series laptops. ALC260 pin usage: Mic/Line jack = 0x12, + * HP jack = 0x14, CD audio = 0x16, internal speaker = 0x10. + */ static struct snd_kcontrol_new alc260_fujitsu_mixer[] = { HDA_CODEC_VOLUME("Headphone Playback Volume", 0x08, 0x0, HDA_OUTPUT), HDA_BIND_MUTE("Headphone Playback Switch", 0x08, 2, HDA_INPUT), @@ -2915,9 +2964,28 @@ static struct snd_kcontrol_new alc260_fujitsu_mixer[] = { { } /* end */ }; +/* Mixer for Acer TravelMate(/Extensa/Aspire) notebooks. Note that current + * versions of the ALC260 don't act on requests to enable mic bias from NID + * 0x0f (used to drive the headphone jack in these laptops). The ALC260 + * datasheet doesn't mention this restriction. At this stage it's not clear + * whether this behaviour is intentional or is a hardware bug in chip + * revisions available in early 2006. Therefore for now allow the + * "Headphone Jack Mode" control to span all choices, but if it turns out + * that the lack of mic bias for this NID is intentional we could change the + * mode from ALC_PIN_DIR_INOUT to ALC_PIN_DIR_INOUT_NOMICBIAS. + * + * In addition, Acer TravelMate(/Extensa/Aspire) notebooks in early 2006 + * don't appear to make the mic bias available from the "line" jack, even + * though the NID used for this jack (0x14) can supply it. The theory is + * that perhaps Acer have included blocking capacitors between the ALC260 + * and the output jack. If this turns out to be the case for all such + * models the "Line Jack Mode" mode could be changed from ALC_PIN_DIR_INOUT + * to ALC_PIN_DIR_INOUT_NOMICBIAS. + */ static struct snd_kcontrol_new alc260_acer_mixer[] = { HDA_CODEC_VOLUME("Master Playback Volume", 0x08, 0x0, HDA_OUTPUT), HDA_BIND_MUTE("Master Playback Switch", 0x08, 2, HDA_INPUT), + ALC_PIN_MODE("Headphone Jack Mode", 0x0f, ALC_PIN_DIR_INOUT), HDA_CODEC_VOLUME("CD Playback Volume", 0x07, 0x04, HDA_INPUT), HDA_CODEC_MUTE("CD Playback Switch", 0x07, 0x04, HDA_INPUT), HDA_CODEC_VOLUME("Mic Playback Volume", 0x07, 0x0, HDA_INPUT), @@ -3131,7 +3199,8 @@ static struct hda_verb alc260_hp_3013_init_verbs[] = { }; /* Initialisation sequence for ALC260 as configured in Fujitsu S702x - * laptops. + * laptops. ALC260 pin usage: Mic/Line jack = 0x12, HP jack = 0x14, CD + * audio = 0x16, internal speaker = 0x10. */ static struct hda_verb alc260_fujitsu_init_verbs[] = { /* Disable all GPIOs */ @@ -3278,10 +3347,10 @@ static struct hda_verb alc260_acer_init_verbs[] = { {0x04, AC_VERB_SET_CONNECT_SEL, 0x00}, /* Do similar with the second ADC: mute capture input amp and - * set ADC connection to line (on line1 pin) + * set ADC connection to mic to match ALSA's default state. */ {0x05, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, - {0x05, AC_VERB_SET_CONNECT_SEL, 0x02}, + {0x05, AC_VERB_SET_CONNECT_SEL, 0x00}, /* Mute all inputs to mixer widget (even unconnected ones) */ {0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, /* mic1 pin */ @@ -3306,26 +3375,35 @@ static hda_nid_t alc260_test_dac_nids[1] = { static hda_nid_t alc260_test_adc_nids[2] = { 0x04, 0x05, }; -/* This is a bit messy since the two input muxes in the ALC260 have slight - * variations in their signal assignments. The ideal way to deal with this - * is to extend alc_spec.input_mux to allow a different input MUX for each - * ADC. For the purposes of the test model it's sufficient to just list - * both options for affected signal indices. The separate input mux - * functionality only needs to be considered if a model comes along which - * actually uses signals 0x5, 0x6 and 0x7 for something which makes sense to - * record. +/* For testing the ALC260, each input MUX needs its own definition since + * the signal assignments are different. This assumes that the first ADC + * is NID 0x04. */ -static struct hda_input_mux alc260_test_capture_source = { - .num_items = 8, - .items = { - { "MIC1 pin", 0x0 }, - { "MIC2 pin", 0x1 }, - { "LINE1 pin", 0x2 }, - { "LINE2 pin", 0x3 }, - { "CD pin", 0x4 }, - { "LINE-OUT pin (cap1), Mixer (cap2)", 0x5 }, - { "HP-OUT pin (cap1), LINE-OUT pin (cap2)", 0x6 }, - { "HP-OUT pin (cap2 only)", 0x7 }, +static struct hda_input_mux alc260_test_capture_sources[2] = { + { + .num_items = 7, + .items = { + { "MIC1 pin", 0x0 }, + { "MIC2 pin", 0x1 }, + { "LINE1 pin", 0x2 }, + { "LINE2 pin", 0x3 }, + { "CD pin", 0x4 }, + { "LINE-OUT pin", 0x5 }, + { "HP-OUT pin", 0x6 }, + }, + }, + { + .num_items = 8, + .items = { + { "MIC1 pin", 0x0 }, + { "MIC2 pin", 0x1 }, + { "LINE1 pin", 0x2 }, + { "LINE2 pin", 0x3 }, + { "CD pin", 0x4 }, + { "Mixer", 0x5 }, + { "LINE-OUT pin", 0x6 }, + { "HP-OUT pin", 0x7 }, + }, }, }; static struct snd_kcontrol_new alc260_test_mixer[] = { @@ -3337,7 +3415,17 @@ static struct snd_kcontrol_new alc260_test_mixer[] = { HDA_CODEC_VOLUME("LOUT1 Playback Volume", 0x08, 0x0, HDA_OUTPUT), HDA_BIND_MUTE("LOUT1 Playback Switch", 0x08, 2, HDA_INPUT), - /* Modes for retasking pin widgets */ + /* Modes for retasking pin widgets + * Note: the ALC260 doesn't seem to act on requests to enable mic + * bias from NIDs 0x0f and 0x10. The ALC260 datasheet doesn't + * mention this restriction. At this stage it's not clear whether + * this behaviour is intentional or is a hardware bug in chip + * revisions available at least up until early 2006. Therefore for + * now allow the "HP-OUT" and "LINE-OUT" Mode controls to span all + * choices, but if it turns out that the lack of mic bias for these + * NIDs is intentional we could change their modes from + * ALC_PIN_DIR_INOUT to ALC_PIN_DIR_INOUT_NOMICBIAS. + */ ALC_PIN_MODE("HP-OUT pin mode", 0x10, ALC_PIN_DIR_INOUT), ALC_PIN_MODE("LINE-OUT pin mode", 0x0f, ALC_PIN_DIR_INOUT), ALC_PIN_MODE("LINE2 pin mode", 0x15, ALC_PIN_DIR_INOUT), @@ -3699,6 +3787,7 @@ static int alc260_parse_auto_config(struct hda_codec *codec) spec->init_verbs[spec->num_init_verbs++] = alc260_volume_init_verbs; + spec->num_mux_defs = 1; spec->input_mux = &spec->private_imux; /* check whether NID 0x04 is valid */ @@ -3804,7 +3893,8 @@ static struct alc_config_preset alc260_presets[] = { .adc_nids = alc260_dual_adc_nids, .num_channel_mode = ARRAY_SIZE(alc260_modes), .channel_mode = alc260_modes, - .input_mux = &alc260_fujitsu_capture_source, + .num_mux_defs = ARRAY_SIZE(alc260_fujitsu_capture_sources), + .input_mux = alc260_fujitsu_capture_sources, }, [ALC260_ACER] = { .mixers = { alc260_acer_mixer, @@ -3816,7 +3906,8 @@ static struct alc_config_preset alc260_presets[] = { .adc_nids = alc260_dual_adc_nids, .num_channel_mode = ARRAY_SIZE(alc260_modes), .channel_mode = alc260_modes, - .input_mux = &alc260_acer_capture_source, + .num_mux_defs = ARRAY_SIZE(alc260_acer_capture_sources), + .input_mux = alc260_acer_capture_sources, }, #ifdef CONFIG_SND_DEBUG [ALC260_TEST] = { @@ -3829,7 +3920,8 @@ static struct alc_config_preset alc260_presets[] = { .adc_nids = alc260_test_adc_nids, .num_channel_mode = ARRAY_SIZE(alc260_modes), .channel_mode = alc260_modes, - .input_mux = &alc260_test_capture_source, + .num_mux_defs = ARRAY_SIZE(alc260_test_capture_sources), + .input_mux = alc260_test_capture_sources, }, #endif }; @@ -3921,7 +4013,6 @@ static struct hda_input_mux alc882_capture_source = { { "CD", 0x4 }, }, }; - #define alc882_mux_enum_info alc_mux_enum_info #define alc882_mux_enum_get alc_mux_enum_get @@ -4823,6 +4914,7 @@ static int alc262_parse_auto_config(struct hda_codec *codec) spec->mixers[spec->num_mixers++] = spec->kctl_alloc; spec->init_verbs[spec->num_init_verbs++] = alc262_volume_init_verbs; + spec->num_mux_defs = 1; spec->input_mux = &spec->private_imux; return 1; @@ -5499,6 +5591,7 @@ static int alc861_parse_auto_config(struct hda_codec *codec) spec->init_verbs[spec->num_init_verbs++] = alc861_auto_init_verbs; + spec->num_mux_defs = 1; spec->input_mux = &spec->private_imux; spec->adc_nids = alc861_adc_nids; -- cgit v1.2.3-59-g8ed1b From 0b2dcd5d6a9a3e27fdd67053e526388f9f2ea33b Mon Sep 17 00:00:00 2001 From: Andreas Mohr Date: Tue, 28 Mar 2006 12:56:14 +0200 Subject: [ALSA] maestro3.c: fix BUG, optimization - fix brown-paper-bag locking bug (lock() / return / unlock()) - improve central function snd_m3_update_ptr() (avoid expensive integer divisions) - add cpu_relax() to busy-wait I/O loop as recommended (does this require special macro support in ALSA for older kernels??) - constify several structs - spelling updates Signed-off-by: Andreas Mohr Signed-off-by: Takashi Iwai --- sound/pci/maestro3.c | 57 ++++++++++++++++++++++++++++++++++------------------ 1 file changed, 37 insertions(+), 20 deletions(-) diff --git a/sound/pci/maestro3.c b/sound/pci/maestro3.c index 44393e190929..9c90d901e6b9 100644 --- a/sound/pci/maestro3.c +++ b/sound/pci/maestro3.c @@ -831,8 +831,8 @@ struct snd_m3 { struct snd_pcm *pcm; struct pci_dev *pci; - struct m3_quirk *quirk; - struct m3_hv_quirk *hv_quirk; + const struct m3_quirk *quirk; + const struct m3_hv_quirk *hv_quirk; int dacs_active; int timer_users; @@ -892,7 +892,7 @@ static struct pci_device_id snd_m3_ids[] = { MODULE_DEVICE_TABLE(pci, snd_m3_ids); -static struct m3_quirk m3_quirk_list[] = { +static const struct m3_quirk m3_quirk_list[] = { /* panasonic CF-28 "toughbook" */ { .name = "Panasonic CF-28", @@ -950,7 +950,7 @@ static struct m3_quirk m3_quirk_list[] = { }; /* These values came from the Windows driver. */ -static struct m3_hv_quirk m3_hv_quirk_list[] = { +static const struct m3_hv_quirk m3_hv_quirk_list[] = { /* Allegro chips */ { 0x125D, 0x1988, 0x0E11, 0x002E, HV_CTRL_ENABLE | HV_BUTTON_FROM_GD, 0 }, { 0x125D, 0x1988, 0x0E11, 0x0094, HV_CTRL_ENABLE | HV_BUTTON_FROM_GD, 0 }, @@ -1361,7 +1361,7 @@ static void snd_m3_pcm_setup2(struct snd_m3 *chip, struct m3_dma *s, } -static struct play_vals { +static const struct play_vals { u16 addr, val; } pv[] = { {CDATA_LEFT_VOLUME, ARB_VOLUME}, @@ -1428,7 +1428,7 @@ snd_m3_playback_setup(struct snd_m3 *chip, struct m3_dma *s, /* * Native record driver */ -static struct rec_vals { +static const struct rec_vals { u16 addr, val; } rv[] = { {CDATA_LEFT_VOLUME, ARB_VOLUME}, @@ -1598,12 +1598,26 @@ static void snd_m3_update_ptr(struct snd_m3 *chip, struct m3_dma *s) if (! s->running) return; - hwptr = snd_m3_get_pointer(chip, s, subs) % s->dma_size; - diff = (s->dma_size + hwptr - s->hwptr) % s->dma_size; + hwptr = snd_m3_get_pointer(chip, s, subs); + + /* try to avoid expensive modulo divisions */ + if (hwptr >= s->dma_size) + hwptr %= s->dma_size; + + diff = s->dma_size + hwptr - s->hwptr; + if (diff >= s->dma_size) + diff %= s->dma_size; + s->hwptr = hwptr; s->count += diff; + if (s->count >= (signed)s->period_size) { - s->count %= s->period_size; + + if (s->count < 2 * (signed)s->period_size) + s->count -= (signed)s->period_size; + else + s->count %= s->period_size; + spin_unlock(&chip->reg_lock); snd_pcm_period_elapsed(subs); spin_lock(&chip->reg_lock); @@ -1942,6 +1956,7 @@ static int snd_m3_ac97_wait(struct snd_m3 *chip) do { if (! (snd_m3_inb(chip, 0x30) & 1)) return 0; + cpu_relax(); } while (i-- > 0); snd_printk(KERN_ERR "ac97 serial bus busy\n"); @@ -1953,16 +1968,18 @@ snd_m3_ac97_read(struct snd_ac97 *ac97, unsigned short reg) { struct snd_m3 *chip = ac97->private_data; unsigned long flags; - unsigned short data; + unsigned short data = 0xffff; if (snd_m3_ac97_wait(chip)) - return 0xffff; + goto fail; spin_lock_irqsave(&chip->ac97_lock, flags); snd_m3_outb(chip, 0x80 | (reg & 0x7f), CODEC_COMMAND); if (snd_m3_ac97_wait(chip)) - return 0xffff; + goto fail_unlock; data = snd_m3_inw(chip, CODEC_DATA); +fail_unlock: spin_unlock_irqrestore(&chip->ac97_lock, flags); +fail: return data; } @@ -2121,7 +2138,7 @@ static int __devinit snd_m3_mixer(struct snd_m3 *chip) * DSP Code images */ -static u16 assp_kernel_image[] __devinitdata = { +static const u16 assp_kernel_image[] __devinitdata = { 0x7980, 0x0030, 0x7980, 0x03B4, 0x7980, 0x03B4, 0x7980, 0x00FB, 0x7980, 0x00DD, 0x7980, 0x03B4, 0x7980, 0x0332, 0x7980, 0x0287, 0x7980, 0x03B4, 0x7980, 0x03B4, 0x7980, 0x03B4, 0x7980, 0x03B4, 0x7980, 0x031A, 0x7980, 0x03B4, 0x7980, 0x022F, 0x7980, 0x03B4, 0x7980, 0x03B4, 0x7980, 0x03B4, @@ -2208,7 +2225,7 @@ static u16 assp_kernel_image[] __devinitdata = { * Mini sample rate converter code image * that is to be loaded at 0x400 on the DSP. */ -static u16 assp_minisrc_image[] __devinitdata = { +static const u16 assp_minisrc_image[] __devinitdata = { 0xBF80, 0x101E, 0x906E, 0x006E, 0x8B88, 0x6980, 0xEF88, 0x906F, 0x0D6F, 0x6900, 0xEB08, 0x0412, 0xBC20, 0x696E, 0xB801, 0x906E, 0x7980, 0x0403, 0xB90E, 0x8807, 0xBE43, 0xBF01, 0xBE47, 0xBE41, @@ -2251,7 +2268,7 @@ static u16 assp_minisrc_image[] __devinitdata = { */ #define MINISRC_LPF_LEN 10 -static u16 minisrc_lpf[MINISRC_LPF_LEN] __devinitdata = { +static const u16 minisrc_lpf[MINISRC_LPF_LEN] __devinitdata = { 0X0743, 0X1104, 0X0A4C, 0XF88D, 0X242C, 0X1023, 0X1AA9, 0X0B60, 0XEFDD, 0X186F }; @@ -2358,7 +2375,7 @@ static int __devinit snd_m3_assp_client_init(struct snd_m3 *chip, struct m3_dma */ /* - * align instance address to 256 bytes so that it's + * align instance address to 256 bytes so that its * shifted list address is aligned. * list address = (mem address >> 1) >> 7; */ @@ -2647,8 +2664,8 @@ snd_m3_create(struct snd_card *card, struct pci_dev *pci, { struct snd_m3 *chip; int i, err; - struct m3_quirk *quirk; - struct m3_hv_quirk *hv_quirk; + const struct m3_quirk *quirk; + const struct m3_hv_quirk *hv_quirk; static struct snd_device_ops ops = { .dev_free = snd_m3_dev_free, }; @@ -2843,12 +2860,12 @@ snd_m3_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) } #if 0 /* TODO: not supported yet */ - /* TODO enable midi irq and i/o */ + /* TODO enable MIDI IRQ and I/O */ err = snd_mpu401_uart_new(chip->card, 0, MPU401_HW_MPU401, chip->iobase + MPU401_DATA_PORT, 1, chip->irq, 0, &chip->rmidi); if (err < 0) - printk(KERN_WARNING "maestro3: no midi support.\n"); + printk(KERN_WARNING "maestro3: no MIDI support.\n"); #endif pci_set_drvdata(pci, card); -- cgit v1.2.3-59-g8ed1b From 14790f1c73cfa4d4a22ac10b4501b4831380683c Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Tue, 28 Mar 2006 17:58:28 +0200 Subject: [ALSA] Test volume resolution of usb audio at initialization Test the volume of usb audio whether actually it works and adjusts the resolution value according to it. Some USB audio devices report a lower resolution than it reacts. The only possible check is to write and read a volume value. Signed-off-by: Takashi Iwai --- sound/usb/usbmixer.c | 37 ++++++++++++++++++++++++++++++++++--- 1 file changed, 34 insertions(+), 3 deletions(-) diff --git a/sound/usb/usbmixer.c b/sound/usb/usbmixer.c index 8d08b34a1cb5..ce86283ee0fa 100644 --- a/sound/usb/usbmixer.c +++ b/sound/usb/usbmixer.c @@ -306,8 +306,8 @@ static int get_relative_value(struct usb_mixer_elem_info *cval, int val) cval->res = 1; if (val < cval->min) return 0; - else if (val > cval->max) - return (cval->max - cval->min) / cval->res; + else if (val >= cval->max) + return (cval->max - cval->min + cval->res - 1) / cval->res; else return (val - cval->min) / cval->res; } @@ -670,6 +670,36 @@ static int get_min_max(struct usb_mixer_elem_info *cval, int default_min) } if (cval->res == 0) cval->res = 1; + + /* Additional checks for the proper resolution + * + * Some devices report smaller resolutions than actually + * reacting. They don't return errors but simply clip + * to the lower aligned value. + */ + if (cval->min + cval->res < cval->max) { + int last_valid_res = cval->res; + int saved, test, check; + get_cur_mix_value(cval, minchn, &saved); + for (;;) { + test = saved; + if (test < cval->max) + test += cval->res; + else + test -= cval->res; + if (test < cval->min || test > cval->max || + set_cur_mix_value(cval, minchn, test) || + get_cur_mix_value(cval, minchn, &check)) { + cval->res = last_valid_res; + break; + } + if (test == check) + break; + cval->res *= 2; + } + set_cur_mix_value(cval, minchn, saved); + } + cval->initialized = 1; } return 0; @@ -695,7 +725,8 @@ static int mixer_ctl_feature_info(struct snd_kcontrol *kcontrol, struct snd_ctl_ if (! cval->initialized) get_min_max(cval, 0); uinfo->value.integer.min = 0; - uinfo->value.integer.max = (cval->max - cval->min) / cval->res; + uinfo->value.integer.max = + (cval->max - cval->min + cval->res - 1) / cval->res; } return 0; } -- cgit v1.2.3-59-g8ed1b From e860f00047108ec97ac58c0d1bf59ae23e35f81c Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Wed, 29 Mar 2006 11:38:01 +0200 Subject: [ALSA] cs4281 - Fix the check of right channel Fix the check of right channel in mixer volume put callback. Signed-off-by: Takashi Iwai --- sound/pci/cs4281.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/pci/cs4281.c b/sound/pci/cs4281.c index 4f65ec56bf35..4221dfe8bd7e 100644 --- a/sound/pci/cs4281.c +++ b/sound/pci/cs4281.c @@ -1046,7 +1046,7 @@ static int snd_cs4281_put_volume(struct snd_kcontrol *kcontrol, snd_cs4281_pokeBA0(chip, regL, volL); change = 1; } - if (ucontrol->value.integer.value[0] != volL) { + if (ucontrol->value.integer.value[1] != volR) { volR = CS_VOL_MASK - (ucontrol->value.integer.value[1] & CS_VOL_MASK); snd_cs4281_pokeBA0(chip, regR, volR); change = 1; -- cgit v1.2.3-59-g8ed1b From 38223daa1aa98d0a6f35ba7addcfefc756a04f5e Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Wed, 29 Mar 2006 12:33:38 +0200 Subject: [ALSA] cs4281 - Fix the check of timeout in probe Fix the check of timeout in probe routines to work properly reagrdless of HZ (ALSA bug#1976). Signed-off-by: Takashi Iwai --- sound/pci/cs4281.c | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/sound/pci/cs4281.c b/sound/pci/cs4281.c index 4221dfe8bd7e..ac4e73f69c1d 100644 --- a/sound/pci/cs4281.c +++ b/sound/pci/cs4281.c @@ -1416,7 +1416,7 @@ static int __devinit snd_cs4281_create(struct snd_card *card, static int snd_cs4281_chip_init(struct cs4281 *chip) { unsigned int tmp; - int timeout; + unsigned long end_time; int retry_count = 2; /* Having EPPMC.FPDN=1 prevent proper chip initialisation */ @@ -1496,7 +1496,7 @@ static int snd_cs4281_chip_init(struct cs4281 *chip) /* * Wait for the DLL ready signal from the clock logic. */ - timeout = 100; + end_time = jiffies + HZ; do { /* * Read the AC97 status register to see if we've seen a CODEC @@ -1504,8 +1504,8 @@ static int snd_cs4281_chip_init(struct cs4281 *chip) */ if (snd_cs4281_peekBA0(chip, BA0_CLKCR1) & BA0_CLKCR1_DLLRDY) goto __ok0; - msleep(1); - } while (timeout-- > 0); + schedule_timeout_uninterruptible(1); + } while (time_after_eq(end_time, jiffies)); snd_printk(KERN_ERR "DLLRDY not seen\n"); return -EIO; @@ -1522,7 +1522,7 @@ static int snd_cs4281_chip_init(struct cs4281 *chip) /* * Wait for the codec ready signal from the AC97 codec. */ - timeout = 100; + end_time = jiffies + HZ; do { /* * Read the AC97 status register to see if we've seen a CODEC @@ -1530,20 +1530,20 @@ static int snd_cs4281_chip_init(struct cs4281 *chip) */ if (snd_cs4281_peekBA0(chip, BA0_ACSTS) & BA0_ACSTS_CRDY) goto __ok1; - msleep(1); - } while (timeout-- > 0); + schedule_timeout_uninterruptible(1); + } while (time_after_eq(end_time, jiffies)); snd_printk(KERN_ERR "never read codec ready from AC'97 (0x%x)\n", snd_cs4281_peekBA0(chip, BA0_ACSTS)); return -EIO; __ok1: if (chip->dual_codec) { - timeout = 100; + end_time = jiffies + HZ; do { if (snd_cs4281_peekBA0(chip, BA0_ACSTS2) & BA0_ACSTS_CRDY) goto __codec2_ok; - msleep(1); - } while (timeout-- > 0); + schedule_timeout_uninterruptible(1); + } while (time_after_eq(end_time, jiffies)); snd_printk(KERN_INFO "secondary codec doesn't respond. disable it...\n"); chip->dual_codec = 0; __codec2_ok: ; @@ -1561,7 +1561,7 @@ static int snd_cs4281_chip_init(struct cs4281 *chip) * the codec is pumping ADC data across the AC-link. */ - timeout = 100; + end_time = jiffies + HZ; do { /* * Read the input slot valid register and see if input slots 3 @@ -1569,8 +1569,8 @@ static int snd_cs4281_chip_init(struct cs4281 *chip) */ if ((snd_cs4281_peekBA0(chip, BA0_ACISV) & (BA0_ACISV_SLV(3) | BA0_ACISV_SLV(4))) == (BA0_ACISV_SLV(3) | BA0_ACISV_SLV(4))) goto __ok2; - msleep(1); - } while (timeout-- > 0); + schedule_timeout_uninterruptible(1); + } while (time_after_eq(end_time, jiffies)); if (--retry_count > 0) goto __retry; -- cgit v1.2.3-59-g8ed1b From 89be83f8eef781a801898c08a5317ed463fe872f Mon Sep 17 00:00:00 2001 From: Felix Kuehling Date: Fri, 31 Mar 2006 12:33:59 +0200 Subject: [ALSA] hda-intel - Add support of ATI SB600 This patch adds support for high definition audio on ATI SB600. Signed-off-by: Felix Kuehling Signed-off-by: Takashi Iwai --- sound/pci/hda/hda_intel.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sound/pci/hda/hda_intel.c b/sound/pci/hda/hda_intel.c index c096606970ff..0ad60ae29011 100644 --- a/sound/pci/hda/hda_intel.c +++ b/sound/pci/hda/hda_intel.c @@ -81,6 +81,7 @@ MODULE_SUPPORTED_DEVICE("{{Intel, ICH6}," "{Intel, ESB2}," "{Intel, ICH8}," "{ATI, SB450}," + "{ATI, SB600}," "{VIA, VT8251}," "{VIA, VT8237A}," "{SiS, SIS966}," @@ -1619,6 +1620,7 @@ static struct pci_device_id azx_ids[] = { { 0x8086, 0x269a, PCI_ANY_ID, PCI_ANY_ID, 0, 0, AZX_DRIVER_ICH }, /* ESB2 */ { 0x8086, 0x284b, PCI_ANY_ID, PCI_ANY_ID, 0, 0, AZX_DRIVER_ICH }, /* ICH8 */ { 0x1002, 0x437b, PCI_ANY_ID, PCI_ANY_ID, 0, 0, AZX_DRIVER_ATI }, /* ATI SB450 */ + { 0x1002, 0x4383, PCI_ANY_ID, PCI_ANY_ID, 0, 0, AZX_DRIVER_ATI }, /* ATI SB600 */ { 0x1106, 0x3288, PCI_ANY_ID, PCI_ANY_ID, 0, 0, AZX_DRIVER_VIA }, /* VIA VT8251/VT8237A */ { 0x1039, 0x7502, PCI_ANY_ID, PCI_ANY_ID, 0, 0, AZX_DRIVER_SIS }, /* SIS966 */ { 0x10b9, 0x5461, PCI_ANY_ID, PCI_ANY_ID, 0, 0, AZX_DRIVER_ULI }, /* ULI M5461 */ -- cgit v1.2.3-59-g8ed1b From ce7415f496e21775156b08452d22211f8c3ccc53 Mon Sep 17 00:00:00 2001 From: OGAWA Hirofumi Date: Fri, 31 Mar 2006 12:36:14 +0200 Subject: [ALSA] sound/pci/hda: use create_singlethread_workqueue() process_unsol_events() seems to assume a singlethread one (IOW, racey). So, this patch uses create_singlethread_workqueue() instead of create_workqueue(). Signed-off-by: OGAWA Hirofumi Signed-off-by: Takashi Iwai --- sound/pci/hda/hda_codec.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/pci/hda/hda_codec.c b/sound/pci/hda/hda_codec.c index b42dff7ceed0..5bee3b536478 100644 --- a/sound/pci/hda/hda_codec.c +++ b/sound/pci/hda/hda_codec.c @@ -295,7 +295,7 @@ static int init_unsol_queue(struct hda_bus *bus) snd_printk(KERN_ERR "hda_codec: can't allocate unsolicited queue\n"); return -ENOMEM; } - unsol->workq = create_workqueue("hda_codec"); + unsol->workq = create_singlethread_workqueue("hda_codec"); if (! unsol->workq) { snd_printk(KERN_ERR "hda_codec: can't create workqueue\n"); kfree(unsol); -- cgit v1.2.3-59-g8ed1b From 0bd43b5bc9e61e9dc48ad5ee68737316e5d94b60 Mon Sep 17 00:00:00 2001 From: Markus Bollinger Date: Fri, 31 Mar 2006 12:48:51 +0200 Subject: [ALSA] pcxhr - Fix the crash with REV01 board On a new board revision for pcxhr boards, the PCXHR_CHIPSC_GPI_USERI bit is no more supported. The cards concerned have a REV01 in their PCI ID. As the current driver tests this bit and does not load the first Xilinx binary when it's 1, the card will crash on Xilinx access over PCI. (the PCI will freeze ....) The fix (fix to version 1.0.11rc4) works for both REV00 and REV01 cards. Signed-off-by: Takashi Iwai --- sound/pci/pcxhr/pcxhr_core.c | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/sound/pci/pcxhr/pcxhr_core.c b/sound/pci/pcxhr/pcxhr_core.c index fdc652c6992d..c40f59062684 100644 --- a/sound/pci/pcxhr/pcxhr_core.c +++ b/sound/pci/pcxhr/pcxhr_core.c @@ -274,12 +274,9 @@ int pcxhr_load_xilinx_binary(struct pcxhr_mgr *mgr, const struct firmware *xilin /* test first xilinx */ chipsc = PCXHR_INPL(mgr, PCXHR_PLX_CHIPSC); - if (!second) { - if (chipsc & PCXHR_CHIPSC_GPI_USERI) { - snd_printdd("no need to load first xilinx\n"); - return 0; /* first xilinx is already present and cannot be reset */ - } - } else { + /* REV01 cards do not support the PCXHR_CHIPSC_GPI_USERI bit anymore */ + /* this bit will always be 1; no possibility to test presence of first xilinx */ + if(second) { if ((chipsc & PCXHR_CHIPSC_GPI_USERI) == 0) { snd_printk(KERN_ERR "error loading first xilinx\n"); return -EINVAL; -- cgit v1.2.3-59-g8ed1b From af9b70ac0044d126b28d28894cd890447c0a9dc1 Mon Sep 17 00:00:00 2001 From: Maximilian Rehkopf Date: Fri, 31 Mar 2006 13:10:35 +0200 Subject: [ALSA] Add Aux input switch control for Aureon Universe This patch adds a mixer control which allows the user to switch the Aux playback between the internal Aux jack, Wavetable, and Rear Line-In on Aureon Universe cards. For switching, a PCA9554 (8-line GPIO with I2C interface) and a 74HC4052 (dual 4-way mux/demux) are used. Output 0 and 1 of the PCA9554 are connected to the select pins of the 74HC4052. The I2C interface of the PCA9554 is connected to the card's internal SPI bus which is also used to control the WM8770 and CS8415. SPI and I2C on the same lines... To communicate with the PCA9554 the WM8770 and CS8415 are disabled and an I2C Stop Condition is generated before the Start Condition (needed for synchronisation because other SPI traffic appear to confuse the PCA9554). Then a normal I2C data transfer takes place. Programming must be done ridiculously slow; in theory, 4.7us is the minimum delay time for normal-speed I2C according to the datasheet, but even with 10us switching was unreliable. The Windows driver from Terratec does the programming very slowly, too (checked with an oscilloscope). PCA9554 datasheet: http://www.semiconductors.philips.com/acrobat/datasheets/PCA9554_9554A_6.pdf 74HC4052 datasheet: http://www.semiconductors.philips.com/acrobat/datasheets/74HC_HCT4052_4.pdf Signed-off-by: Maximilian Rehkopf Signed-off-by: Takashi Iwai --- sound/pci/ice1712/aureon.c | 163 +++++++++++++++++++++++++++++++++++++++++++- sound/pci/ice1712/ice1712.h | 1 + 2 files changed, 161 insertions(+), 3 deletions(-) diff --git a/sound/pci/ice1712/aureon.c b/sound/pci/ice1712/aureon.c index 7e6608b14abc..336dc489aee1 100644 --- a/sound/pci/ice1712/aureon.c +++ b/sound/pci/ice1712/aureon.c @@ -87,7 +87,151 @@ #define CS8415_C_BUFFER 0x20 #define CS8415_ID 0x7F -static void aureon_ac97_write(struct snd_ice1712 *ice, unsigned short reg, unsigned short val) { +/* PCA9554 registers */ +#define PCA9554_DEV 0x40 /* I2C device address */ +#define PCA9554_IN 0x00 /* input port */ +#define PCA9554_OUT 0x01 /* output port */ +#define PCA9554_INVERT 0x02 /* input invert */ +#define PCA9554_DIR 0x03 /* port directions */ + +/* + * Aureon Universe additional controls using PCA9554 + */ + +/* + * Send data to pca9554 + */ +static void aureon_pca9554_write(struct snd_ice1712 *ice, unsigned char reg, + unsigned char data) +{ + unsigned int tmp; + int i, j; + unsigned char dev = PCA9554_DEV; /* ID 0100000, write */ + unsigned char val = 0; + + tmp = snd_ice1712_gpio_read(ice); + + snd_ice1712_gpio_set_mask(ice, ~(AUREON_SPI_MOSI|AUREON_SPI_CLK| + AUREON_WM_RW|AUREON_WM_CS| + AUREON_CS8415_CS)); + tmp |= AUREON_WM_RW; + tmp |= AUREON_CS8415_CS | AUREON_WM_CS; /* disable SPI devices */ + + tmp &= ~AUREON_SPI_MOSI; + tmp &= ~AUREON_SPI_CLK; + snd_ice1712_gpio_write(ice, tmp); + udelay(50); + + /* + * send i2c stop condition and start condition + * to obtain sane state + */ + tmp |= AUREON_SPI_CLK; + snd_ice1712_gpio_write(ice, tmp); + udelay(50); + tmp |= AUREON_SPI_MOSI; + snd_ice1712_gpio_write(ice, tmp); + udelay(100); + tmp &= ~AUREON_SPI_MOSI; + snd_ice1712_gpio_write(ice, tmp); + udelay(50); + tmp &= ~AUREON_SPI_CLK; + snd_ice1712_gpio_write(ice, tmp); + udelay(100); + /* + * send device address, command and value, + * skipping ack cycles inbetween + */ + for (j = 0; j < 3; j++) { + switch(j) { + case 0: val = dev; break; + case 1: val = reg; break; + case 2: val = data; break; + } + for (i = 7; i >= 0; i--) { + tmp &= ~AUREON_SPI_CLK; + snd_ice1712_gpio_write(ice, tmp); + udelay(40); + if (val & (1 << i)) + tmp |= AUREON_SPI_MOSI; + else + tmp &= ~AUREON_SPI_MOSI; + snd_ice1712_gpio_write(ice, tmp); + udelay(40); + tmp |= AUREON_SPI_CLK; + snd_ice1712_gpio_write(ice, tmp); + udelay(40); + } + tmp &= ~AUREON_SPI_CLK; + snd_ice1712_gpio_write(ice, tmp); + udelay(40); + tmp |= AUREON_SPI_CLK; + snd_ice1712_gpio_write(ice, tmp); + udelay(40); + tmp &= ~AUREON_SPI_CLK; + snd_ice1712_gpio_write(ice, tmp); + udelay(40); + } + tmp &= ~AUREON_SPI_CLK; + snd_ice1712_gpio_write(ice, tmp); + udelay(40); + tmp &= ~AUREON_SPI_MOSI; + snd_ice1712_gpio_write(ice, tmp); + udelay(40); + tmp |= AUREON_SPI_CLK; + snd_ice1712_gpio_write(ice, tmp); + udelay(50); + tmp |= AUREON_SPI_MOSI; + snd_ice1712_gpio_write(ice, tmp); + udelay(100); +} + +static int aureon_universe_inmux_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + char *texts[3] = {"Internal Aux", "Wavetable", "Rear Line-In"}; + + uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; + uinfo->count = 1; + uinfo->value.enumerated.items = 3; + if(uinfo->value.enumerated.item >= uinfo->value.enumerated.items) + uinfo->value.enumerated.item = uinfo->value.enumerated.items - 1; + strcpy(uinfo->value.enumerated.name, texts[uinfo->value.enumerated.item]); + return 0; +} + +static int aureon_universe_inmux_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + ucontrol->value.integer.value[0] = ice->spec.aureon.pca9554_out; + return 0; +} + +static int aureon_universe_inmux_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + unsigned char oval, nval; + int change; + + snd_ice1712_save_gpio_status(ice); + + oval = ice->spec.aureon.pca9554_out; + nval = ucontrol->value.integer.value[0]; + if ((change = (oval != nval))) { + aureon_pca9554_write(ice, PCA9554_OUT, nval); + ice->spec.aureon.pca9554_out = nval; + } + snd_ice1712_restore_gpio_status(ice); + + return change; +} + + +static void aureon_ac97_write(struct snd_ice1712 *ice, unsigned short reg, + unsigned short val) +{ unsigned int tmp; /* Send address to XILINX chip */ @@ -146,7 +290,8 @@ static unsigned short aureon_ac97_read(struct snd_ice1712 *ice, unsigned short r /* * Initialize STAC9744 chip */ -static int aureon_ac97_init (struct snd_ice1712 *ice) { +static int aureon_ac97_init (struct snd_ice1712 *ice) +{ int i; static unsigned short ac97_defaults[] = { 0x00, 0x9640, @@ -1598,7 +1743,15 @@ static struct snd_kcontrol_new universe_ac97_controls[] __devinitdata = { .get = aureon_ac97_vol_get, .put = aureon_ac97_vol_put, .private_value = AC97_VIDEO|AUREON_AC97_STEREO - } + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Aux Source", + .info = aureon_universe_inmux_info, + .get = aureon_universe_inmux_get, + .put = aureon_universe_inmux_put + } + }; @@ -1856,6 +2009,10 @@ static int __devinit aureon_init(struct snd_ice1712 *ice) } snd_ice1712_restore_gpio_status(ice); + + /* initialize PCA9554 pin directions & set default input*/ + aureon_pca9554_write(ice, PCA9554_DIR, 0x00); + aureon_pca9554_write(ice, PCA9554_OUT, 0x00); /* internal AUX */ ice->spec.aureon.master[0] = WM_VOL_MUTE; ice->spec.aureon.master[1] = WM_VOL_MUTE; diff --git a/sound/pci/ice1712/ice1712.h b/sound/pci/ice1712/ice1712.h index f9b22d4a3932..053f8e56fd68 100644 --- a/sound/pci/ice1712/ice1712.h +++ b/sound/pci/ice1712/ice1712.h @@ -373,6 +373,7 @@ struct snd_ice1712 { unsigned int cs8415_mux; unsigned short master[2]; unsigned short vol[8]; + unsigned char pca9554_out; } aureon; /* AC97 register cache for Phase28 */ struct phase28_spec { -- cgit v1.2.3-59-g8ed1b From c2f60c523aa34cf6d4913d6efc670890bd456fd5 Mon Sep 17 00:00:00 2001 From: Frederik Deweerdt Date: Fri, 31 Mar 2006 13:13:23 +0200 Subject: [ALSA] Kconfig SND_SEQUENCER_OSS help text fix Fix misleading help text for SND_SEQUENCER_OSS config option. Signed-off-by: Frederik Deweerdt Signed-off-by: Takashi Iwai --- sound/core/Kconfig | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/sound/core/Kconfig b/sound/core/Kconfig index 9dd121bb5638..8efc1b12f3a8 100644 --- a/sound/core/Kconfig +++ b/sound/core/Kconfig @@ -92,8 +92,9 @@ config SND_SEQUENCER_OSS Many programs still use the OSS API, so say Y. - To compile this driver as a module, choose M here: the module - will be called snd-seq-oss. + If you choose M in "Sequencer support" (SND_SEQUENCER), + this will be compiled as a module. The module will be called + snd-seq-oss. config SND_RTCTIMER tristate "RTC Timer support" -- cgit v1.2.3-59-g8ed1b