Commit 164882b8 authored by Jaroslav Kysela's avatar Jaroslav Kysela

ALSA update

  - added AZT3328 driver
  - added Terratec Aureon support to ICE1724 driver
  - fixed possible PCI posting problems
    - ENS1370, ENS1371, FM801, ICE1712, ICE1724, VIA82xx
  - AC'97 code
    - added new IDs
    - fixed typo in S/PDIF code
    - C-Media related fixes
  - USB driver
    - added nrpacks module option
    - added hack for AudioTrak Optoplay
    - removed OLD_USB stuff
parent cb4acf11
......@@ -187,6 +187,13 @@ Module parameters
Module supports up to 8 cards, PnP and autoprobe.
Module snd-azt3328
------------------
Module for soundcards based on Aztech AZF3328 PCI chip.
Module supports up to 8 cards.
Module snd-cmi8330
------------------
......
......@@ -2906,7 +2906,8 @@ struct _snd_pcm_runtime {
</para>
<para>
This callback may be called multiple times, too.
This function is always called before the close callback is called.
Also, the callback may be called multiple times, too.
Keep track whether the resource was already released.
</para>
</section>
......@@ -3007,14 +3008,16 @@ struct _snd_pcm_runtime {
</para>
<para>
When the pcm supports the suspend/resume operation,
When the pcm supports the suspend/resume operation
(i.e. <constant>SNDRV_PCM_INFO_RESUME</constant> flag is set),
<constant>SUSPEND</constant> and <constant>RESUME</constant>
commands must be handled, too. Obviously it does suspend and
resume of the pcm substream. Usually, the
<constant>SUSPEND</constant> is identical with
<constant>STOP</constant> command and the
<constant>RESUME</constant> is identical with
<constant>START</constant> command.
commands must be handled, too.
These commands are issued when the power-management status is
changed. Obviously, the <constant>SUSPEND</constant> and
<constant>RESUME</constant>
do suspend and resume of the pcm substream, and usually, they
are identical with <constant>STOP</constant> and
<constant>START</constant> commands, respectively.
</para>
<para>
......@@ -3331,8 +3334,95 @@ struct _snd_pcm_runtime {
</para>
<para>
There are many different constraints. You can even define your
own constraint rules. I won't explain the details here, rather I
There are many different constraints.
Look in <filename>sound/asound.h</filename> for a complete list.
You can even define your own constraint rules.
For example, let's suppose my_chip can manage a substream of 1 channel
if and only if the format is S16_LE, otherwise it supports any format
specified in the <type>snd_pcm_hardware_t</type> stucture (or in any
other constraint_list). You can build a rule like this:
<example>
<title>Example of Hardware Constraints for Channels</title>
<programlisting>
<![CDATA[
static int hw_rule_format_by_channels(snd_pcm_hw_params_t *params,
snd_pcm_hw_rule_t *rule)
{
snd_interval_t *c = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS);
snd_mask_t *f = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT);
snd_mask_t fmt;
snd_mask_any(&fmt); // Init the struct
if (c->min < 2) {
fmt.bits[0] &= SNDRV_PCM_FMTBIT_S16_LE;
return snd_mask_refine(f, &fmt);
}
return 0;
}
]]>
</programlisting>
</example>
</para>
<para>
Then you need to call this function to add your rule:
<informalexample>
<programlisting>
<![CDATA[
snd_pcm_hw_rule_add(substream->runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS,
hw_rule_channels_by_format, 0, SNDRV_PCM_HW_PARAM_FORMAT,
-1);
]]>
</programlisting>
</informalexample>
</para>
<para>
The rule function is called when an application sets the number of
channels. But an application can set the format before the number of
channels. Thus you also need to define the inverse rule:
<example>
<title>Example of Hardware Constraints for Channels</title>
<programlisting>
<![CDATA[
static int hw_rule_channels_by_format(snd_pcm_hw_params_t *params,
snd_pcm_hw_rule_t *rule)
{
snd_interval_t *c = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS);
snd_mask_t *f = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT);
snd_interval_t ch;
snd_interval_any(&ch);
if (f->bits[0] == SNDRV_PCM_FMTBIT_S16_LE) {
ch.min = ch.max = 1;
ch.integer = 1;
return snd_interval_refine(c, &ch);
}
return 0;
}
]]>
</programlisting>
</example>
</para>
<para>
...and in the open callback:
<informalexample>
<programlisting>
<![CDATA[
snd_pcm_hw_rule_add(substream->runtime, 0, SNDRV_PCM_HW_PARAM_FORMAT,
hw_rule_format_by_channels, 0, SNDRV_PCM_HW_PARAM_CHANNELS,
-1);
]]>
</programlisting>
</informalexample>
</para>
<para>
I won't explain more details here, rather I
would like to say, <quote>Luke, use the source.</quote>
</para>
</section>
......@@ -5756,6 +5846,10 @@ struct _snd_pcm_runtime {
Kevin Conder reformatted the original plain-text to the
DocBook format.
</para>
<para>
Giuliano Pochini corrected typos and contributed the example codes
in the hardware constraints section.
</para>
</chapter>
......
......@@ -37,11 +37,13 @@ struct snd_ak4xxx_ops {
void (*set_rate_val)(akm4xxx_t *ak, unsigned int rate);
};
#define AK4XXX_IMAGE_SIZE (AK4XXX_MAX_CHIPS * 16) /* 64 bytes */
struct snd_akm4xxx {
snd_card_t *card;
unsigned int num_adcs; /* AK4524 or AK4528 ADCs */
unsigned int num_dacs; /* AK4524 or AK4528 DACs */
unsigned char images[AK4XXX_MAX_CHIPS][16]; /* saved register image */
unsigned char images[AK4XXX_IMAGE_SIZE]; /* saved register image */
unsigned char ipga_gain[AK4XXX_MAX_CHIPS][2]; /* saved register image for IPGA (AK4528) */
unsigned long private_value[AK4XXX_MAX_CHIPS]; /* helper for driver */
void *private_data[AK4XXX_MAX_CHIPS]; /* helper for driver */
......@@ -58,4 +60,9 @@ void snd_akm4xxx_reset(akm4xxx_t *ak, int state);
void snd_akm4xxx_init(akm4xxx_t *ak);
int snd_akm4xxx_build_controls(akm4xxx_t *ak);
#define snd_akm4xxx_get(ak,chip,reg) (ak)->images[(chip) * 16 + (reg)]
#define snd_akm4xxx_set(ak,chip,reg,val) ((ak)->images[(chip) * 16 + (reg)] = (val))
#define snd_akm4xxx_get_ipga(ak,chip,reg) (ak)->ipga_gain[chip][(reg)-4]
#define snd_akm4xxx_set_ipga(ak,chip,reg,val) ((ak)->ipga_gain[chip][(reg)-4] = (val))
#endif /* __SOUND_AK4XXX_ADDA_H */
......@@ -25,6 +25,7 @@
#include <linux/sched.h> /* wake_up() */
#include <asm/semaphore.h> /* struct semaphore */
#include <linux/rwsem.h> /* struct rw_semaphore */
#include <linux/workqueue.h> /* struct workqueue_struct */
/* Typedef's */
typedef struct timespec snd_timestamp_t;
......@@ -158,6 +159,7 @@ struct _snd_card {
spinlock_t files_lock; /* lock the files for this card */
int shutdown; /* this card is going down */
wait_queue_head_t shutdown_sleep;
struct work_struct free_workq; /* for free in workqueue */
#ifdef CONFIG_PM
int (*set_power_state) (snd_card_t *card, unsigned int state);
......
/* include/version.h. Generated by configure. */
#define CONFIG_SND_VERSION "0.9.4"
#define CONFIG_SND_DATE " (Sat May 31 13:37:06 2003 UTC)"
#define CONFIG_SND_DATE " (Fri Jun 06 09:23:03 2003 UTC)"
......@@ -45,6 +45,7 @@ obj-$(CONFIG_SND_MTPAV) += snd-rawmidi.o snd.o snd-timer.o
obj-$(CONFIG_SND_MPU401) += snd-rawmidi.o snd.o snd-timer.o
obj-$(CONFIG_SND_ALS100) += snd-pcm.o snd-timer.o snd-page-alloc.o snd.o snd-rawmidi.o snd-hwdep.o
obj-$(CONFIG_SND_AZT2320) += snd-pcm.o snd-timer.o snd-page-alloc.o snd.o snd-rawmidi.o snd-hwdep.o
obj-$(CONFIG_SND_AZT3328) += snd-pcm.o snd-timer.o snd-page-alloc.o snd.o snd-rawmidi.o snd-hwdep.o
obj-$(CONFIG_SND_CMI8330) += snd-pcm.o snd-timer.o snd-page-alloc.o snd.o
obj-$(CONFIG_SND_DT019X) += snd-pcm.o snd-timer.o snd-page-alloc.o snd.o snd-rawmidi.o snd-hwdep.o
obj-$(CONFIG_SND_ES18XX) += snd-pcm.o snd-timer.o snd-page-alloc.o snd.o snd-rawmidi.o snd-hwdep.o
......
......@@ -26,7 +26,6 @@
#include <linux/slab.h>
#include <linux/time.h>
#include <linux/ctype.h>
#include <linux/workqueue.h>
#include <sound/core.h>
#include <sound/control.h>
#include <sound/info.h>
......@@ -50,6 +49,8 @@ static void snd_card_id_read(snd_info_entry_t *entry, snd_info_buffer_t * buffer
snd_iprintf(buffer, "%s\n", entry->card->id);
}
static void snd_card_free_thread(void * __card);
/**
* snd_card_new - create and initialize a soundcard structure
* @idx: card index (address) [0 ... (SNDRV_CARDS-1)]
......@@ -115,6 +116,7 @@ snd_card_t *snd_card_new(int idx, const char *xid,
INIT_LIST_HEAD(&card->ctl_files);
spin_lock_init(&card->files_lock);
init_waitqueue_head(&card->shutdown_sleep);
INIT_WORK(&card->free_workq, snd_card_free_thread, card);
#ifdef CONFIG_PM
init_MUTEX(&card->power_lock);
init_waitqueue_head(&card->power_sleep);
......@@ -327,16 +329,15 @@ static void snd_card_free_thread(void * __card)
*/
int snd_card_free_in_thread(snd_card_t * card)
{
DECLARE_WORK(works, snd_card_free_thread, card);
if (card->files == NULL) {
snd_card_free(card);
return 0;
}
if (schedule_work(&works))
if (schedule_work(&card->free_workq))
return 0;
snd_printk(KERN_ERR "kernel_thread failed in snd_card_free_in_thread for card %i\n", card->number);
snd_printk(KERN_ERR "schedule_work() failed in snd_card_free_in_thread for card %i\n", card->number);
/* try to free the structure immediately */
snd_card_free(card);
return -EFAULT;
......@@ -354,6 +355,10 @@ static void choose_default_id(snd_card_t * card)
id++;
}
id = card->id;
while (*spos != '\0' && !isalnum(*spos))
spos++;
if (isdigit(*spos))
*id++ = isalpha(card->shortname[0]) ? card->shortname[0] : 'D';
while (*spos != '\0' && (size_t)(id - card->id) < sizeof(card->id) - 1) {
if (isalnum(*spos))
*id++ = *spos;
......@@ -363,6 +368,9 @@ static void choose_default_id(snd_card_t * card)
id = card->id;
if (*id == '\0')
strcpy(id, "default");
while (1) {
if (loops-- == 0) {
snd_printk(KERN_ERR "unable to choose default card id (%s)", id);
......@@ -421,7 +429,7 @@ int snd_card_register(snd_card_t * card)
write_unlock(&snd_card_rwlock);
return 0;
}
if (!card->id[0])
if (card->id[0] == '\0')
choose_default_id(card);
snd_cards[card->number] = card;
snd_cards_count++;
......
......@@ -862,7 +862,7 @@ static struct action_ops snd_pcm_action_pause = {
static int snd_pcm_pause(snd_pcm_substream_t *substream, int push)
{
return snd_pcm_action(&snd_pcm_action_pause, substream, 0);
return snd_pcm_action(&snd_pcm_action_pause, substream, push);
}
#ifdef CONFIG_PM
......
......@@ -43,6 +43,7 @@ obj-$(call sequencer,$(CONFIG_SND_MTPAV)) += $(RAWMIDI_OBJS)
obj-$(call sequencer,$(CONFIG_SND_MPU401)) += $(RAWMIDI_OBJS)
obj-$(call sequencer,$(CONFIG_SND_ALS100)) += $(RAWMIDI_OBJS) $(OPL3_OBJS)
obj-$(call sequencer,$(CONFIG_SND_AZT2320)) += $(RAWMIDI_OBJS) $(OPL3_OBJS)
obj-$(call sequencer,$(CONFIG_SND_AZT3328)) += $(RAWMIDI_OBJS) $(OPL3_OBJS)
obj-$(call sequencer,$(CONFIG_SND_DT019X)) += $(RAWMIDI_OBJS) $(OPL3_OBJS)
obj-$(call sequencer,$(CONFIG_SND_ES18XX)) += $(RAWMIDI_OBJS) $(OPL3_OBJS)
obj-$(call sequencer,$(CONFIG_SND_OPL3SA2)) += $(RAWMIDI_OBJS) $(OPL3_OBJS)
......
......@@ -19,6 +19,7 @@ sequencer = $(if $(subst y,,$(CONFIG_SND_SEQUENCER)),$(if $(1),m),$(if $(CONFIG_
# Toplevel Module Dependency
obj-$(call sequencer,$(CONFIG_SND_ALS100)) += snd-ainstr-fm.o
obj-$(call sequencer,$(CONFIG_SND_AZT2320)) += snd-ainstr-fm.o
obj-$(call sequencer,$(CONFIG_SND_AZT3328)) += snd-ainstr-fm.o
obj-$(call sequencer,$(CONFIG_SND_DT019X)) += snd-ainstr-fm.o
obj-$(call sequencer,$(CONFIG_SND_ES18XX)) += snd-ainstr-fm.o
obj-$(call sequencer,$(CONFIG_SND_OPL3SA2)) += snd-ainstr-fm.o
......
......@@ -10,6 +10,7 @@ snd-mpu401-uart-objs := mpu401_uart.o
obj-$(CONFIG_SND_MPU401) += snd-mpu401.o snd-mpu401-uart.o
obj-$(CONFIG_SND_ALS100) += snd-mpu401-uart.o
obj-$(CONFIG_SND_AZT2320) += snd-mpu401-uart.o
obj-$(CONFIG_SND_AZT3328) += snd-mpu401-uart.o
obj-$(CONFIG_SND_DT019X) += snd-mpu401-uart.o
obj-$(CONFIG_SND_ES18XX) += snd-mpu401-uart.o
obj-$(CONFIG_SND_OPL3SA2) += snd-mpu401-uart.o
......
......@@ -17,6 +17,7 @@ endif
# Toplevel Module Dependency
obj-$(CONFIG_SND_ALS100) += $(OPL3_OBJS)
obj-$(CONFIG_SND_AZT2320) += $(OPL3_OBJS)
obj-$(CONFIG_SND_AZT3328) += $(OPL3_OBJS)
obj-$(CONFIG_SND_DT019X) += $(OPL3_OBJS)
obj-$(CONFIG_SND_ES18XX) += $(OPL3_OBJS)
obj-$(CONFIG_SND_OPL3SA2) += $(OPL3_OBJS)
......
......@@ -42,12 +42,12 @@ void snd_akm4xxx_write(akm4xxx_t *ak, int chip, unsigned char reg, unsigned char
/* save the data */
if (ak->type == SND_AK4524 || ak->type == SND_AK4528) {
if ((reg != 0x04 && reg != 0x05) || (reg & 0x80) == 0)
ak->images[chip][reg] = val;
snd_akm4xxx_set(ak, chip, reg, val);
else
ak->ipga_gain[chip][reg-4] = val;
snd_akm4xxx_set_ipga(ak, chip, reg, val);
} else {
/* AK4529, or else */
ak->images[chip][reg] = val;
snd_akm4xxx_set(ak, chip, reg, val);
}
ak->ops.unlock(ak, chip);
}
......@@ -72,12 +72,12 @@ void snd_akm4xxx_reset(akm4xxx_t *ak, int state)
continue;
/* DAC volumes */
for (reg = 0x04; reg < (ak->type == SND_AK4528 ? 0x06 : 0x08); reg++)
snd_akm4xxx_write(ak, chip, reg, ak->images[chip][reg]);
snd_akm4xxx_write(ak, chip, reg, snd_akm4xxx_get(ak, chip, reg));
if (ak->type == SND_AK4528)
continue;
/* IPGA */
for (reg = 0x04; reg < 0x06; reg++)
snd_akm4xxx_write(ak, chip, reg, ak->ipga_gain[chip][reg-4]);
snd_akm4xxx_write(ak, chip, reg, snd_akm4xxx_get_ipga(ak, chip, reg));
}
break;
case SND_AK4529:
......@@ -89,7 +89,7 @@ void snd_akm4xxx_reset(akm4xxx_t *ak, int state)
return;
for (reg = 0x00; reg < 0x0a; reg++)
if (reg != 0x01)
snd_akm4xxx_write(ak, 0, reg, ak->images[0][reg]);
snd_akm4xxx_write(ak, 0, reg, snd_akm4xxx_get(ak, 0, reg));
break;
case SND_AK4381:
for (chip = 0; chip < ak->num_dacs/2; chip++) {
......@@ -97,7 +97,7 @@ void snd_akm4xxx_reset(akm4xxx_t *ak, int state)
if (state)
continue;
for (reg = 0x01; reg < 0x05; reg++)
snd_akm4xxx_write(ak, chip, reg, ak->images[chip][reg]);
snd_akm4xxx_write(ak, chip, reg, snd_akm4xxx_get(ak, chip, reg));
}
break;
}
......@@ -240,7 +240,7 @@ static int snd_akm4xxx_volume_get(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t
int addr = AK_GET_ADDR(kcontrol->private_value);
int invert = AK_GET_INVERT(kcontrol->private_value);
unsigned int mask = AK_GET_MASK(kcontrol->private_value);
unsigned char val = ak->images[chip][addr];
unsigned char val = snd_akm4xxx_get(ak, chip, addr);
ucontrol->value.integer.value[0] = invert ? mask - val : val;
return 0;
......@@ -258,7 +258,7 @@ static int snd_akm4xxx_volume_put(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t
if (invert)
nval = mask - nval;
change = ak->images[chip][addr] != nval;
change = snd_akm4xxx_get(ak, chip, addr) != nval;
if (change)
snd_akm4xxx_write(ak, chip, addr, nval);
return change;
......@@ -278,7 +278,7 @@ static int snd_akm4xxx_ipga_gain_get(snd_kcontrol_t *kcontrol, snd_ctl_elem_valu
akm4xxx_t *ak = _snd_kcontrol_chip(kcontrol);
int chip = AK_GET_CHIP(kcontrol->private_value);
int addr = AK_GET_ADDR(kcontrol->private_value);
ucontrol->value.integer.value[0] = ak->ipga_gain[chip][addr-4] & 0x7f;
ucontrol->value.integer.value[0] = snd_akm4xxx_get_ipga(ak, chip, addr) & 0x7f;
return 0;
}
......@@ -288,7 +288,7 @@ static int snd_akm4xxx_ipga_gain_put(snd_kcontrol_t *kcontrol, snd_ctl_elem_valu
int chip = AK_GET_CHIP(kcontrol->private_value);
int addr = AK_GET_ADDR(kcontrol->private_value);
unsigned char nval = (ucontrol->value.integer.value[0] % 37) | 0x80;
int change = ak->ipga_gain[chip][addr] != nval;
int change = snd_akm4xxx_get_ipga(ak, chip, addr) != nval;
if (change)
snd_akm4xxx_write(ak, chip, addr, nval);
return change;
......@@ -314,7 +314,7 @@ static int snd_akm4xxx_deemphasis_get(snd_kcontrol_t * kcontrol, snd_ctl_elem_va
int chip = AK_GET_CHIP(kcontrol->private_value);
int addr = AK_GET_ADDR(kcontrol->private_value);
int shift = AK_GET_SHIFT(kcontrol->private_value);
ucontrol->value.enumerated.item[0] = (ak->images[chip][addr] >> shift) & 3;
ucontrol->value.enumerated.item[0] = (snd_akm4xxx_get(ak, chip, addr) >> shift) & 3;
return 0;
}
......@@ -327,8 +327,8 @@ static int snd_akm4xxx_deemphasis_put(snd_kcontrol_t *kcontrol, snd_ctl_elem_val
unsigned char nval = ucontrol->value.enumerated.item[0] & 3;
int change;
nval = (nval << shift) | (ak->images[chip][addr] & ~(3 << shift));
change = ak->images[chip][addr] != nval;
nval = (nval << shift) | (snd_akm4xxx_get(ak, chip, addr) & ~(3 << shift));
change = snd_akm4xxx_get(ak, chip, addr) != nval;
if (change)
snd_akm4xxx_write(ak, chip, addr, nval);
return change;
......
......@@ -9,6 +9,12 @@ config SND_ALI5451
help
Say 'Y' or 'M' to include support for ALI PCI Audio M5451 sound core.
config SND_AZT3328
tristate "Aztech AZF3328 / PCI168 (EXPERIMENTAL)"
depends on SND && EXPERIMENTAL
help
Say 'Y' or 'M' to include support for Aztech AZF3328 (PCI168) soundcards.
config SND_CS46XX
tristate "Cirrus Logic (Sound Fusion) CS4280/CS461x/CS462x/CS463x"
depends on SND && SOUND_GAMEPORT
......@@ -181,7 +187,7 @@ config SND_VX222
tristate "Digigram VX222"
depends on SND
help
Say 'Y' or 'M' to include support for Digigram VX222 soundcards
Say 'Y' or 'M' to include support for Digigram VX222 soundcards.
endmenu
......@@ -4,6 +4,7 @@
#
snd-als4000-objs := als4000.o
snd-azt3328-objs := azt3328.o
snd-cmipci-objs := cmipci.o
snd-cs4281-objs := cs4281.o
snd-ens1370-objs := ens1370.o
......@@ -33,5 +34,6 @@ obj-$(CONFIG_SND_RME32) += snd-rme32.o
obj-$(CONFIG_SND_RME96) += snd-rme96.o
obj-$(CONFIG_SND_SONICVIBES) += snd-sonicvibes.o
obj-$(CONFIG_SND_VIA82XX) += snd-via82xx.o
obj-$(CONFIG_SND_AZT3328) += snd-azt3328.o
obj-$(CONFIG_SND) += ac97/ ali5451/ cs46xx/ emu10k1/ korg1212/ nm256/ rme9652/ trident/ ymfpci/ ice1712/ vx222/
......@@ -103,6 +103,7 @@ static const ac97_codec_id_t snd_ac97_codec_ids[] = {
{ 0x41445363, 0xffffffff, "AD1886A", patch_ad1881, NULL },
{ 0x41445370, 0xffffffff, "AD1980", patch_ad1980, NULL },
{ 0x41445372, 0xffffffff, "AD1981A", patch_ad1881, NULL },
{ 0x41445374, 0xffffffff, "AD1981B", patch_ad1881, NULL },
{ 0x41445375, 0xffffffff, "AD1985", patch_ad1980, NULL },
{ 0x414c4300, 0xfffffff0, "RL5306", NULL, NULL },
{ 0x414c4310, 0xfffffff0, "RL5382", NULL, NULL },
......@@ -112,6 +113,7 @@ static const ac97_codec_id_t snd_ac97_codec_ids[] = {
{ 0x414c4730, 0xffffffff, "ALC101", NULL, NULL },
{ 0x414c4740, 0xfffffff0, "ALC202", NULL, NULL },
{ 0x414c4750, 0xfffffff0, "ALC250", NULL, NULL },
{ 0x414c4770, 0xfffffff0, "ALC203", NULL, NULL },
{ 0x434d4941, 0xffffffff, "CMI9738", NULL, NULL },
{ 0x434d4961, 0xffffffff, "CMI9739", patch_cm9739, NULL },
{ 0x43525900, 0xfffffff8, "CS4297", NULL, NULL },
......@@ -122,6 +124,7 @@ static const ac97_codec_id_t snd_ac97_codec_ids[] = {
{ 0x43525948, 0xfffffff8, "CS4201", NULL, NULL },
{ 0x43525958, 0xfffffff8, "CS4205", patch_cirrus_spdif, NULL },
{ 0x43525960, 0xfffffff8, "CS4291", NULL, NULL },
{ 0x43525970, 0xfffffff8, "CS4202", NULL, NULL },
{ 0x43585421, 0xffffffff, "HSD11246", NULL, NULL }, // SmartMC II
{ 0x43585428, 0xfffffff8, "Cx20468", patch_conexant, NULL }, // SmartAMC fixme: the mask might be different
{ 0x44543031, 0xfffffff0, "DT0398", NULL, NULL },
......@@ -130,10 +133,13 @@ static const ac97_codec_id_t snd_ac97_codec_ids[] = {
{ 0x48525300, 0xffffff00, "HMP9701", NULL, NULL },
{ 0x49434501, 0xffffffff, "ICE1230", NULL, NULL },
{ 0x49434511, 0xffffffff, "ICE1232", NULL, NULL }, // alias VIA VT1611A?
{ 0x49434514, 0xffffffff, "ICE1232A", NULL, NULL },
{ 0x49434551, 0xffffffff, "VT1616", NULL, NULL },
{ 0x49434552, 0xffffffff, "VT1616i", NULL, NULL }, // VT1616 compatible (chipset integrated)
{ 0x49544520, 0xffffffff, "IT2226E", NULL, NULL },
{ 0x4e534300, 0xffffffff, "LM4540/43/45/46/48", NULL, NULL }, // only guess --jk
{ 0x4e534331, 0xffffffff, "LM4549", NULL, NULL },
{ 0x4e534350, 0xffffffff, "LM4550", NULL, NULL },
{ 0x50534304, 0xffffffff, "UCB1400", NULL, NULL },
{ 0x53494c20, 0xffffffe0, "Si3036/8", NULL, NULL },
{ 0x54524102, 0xffffffff, "TR28022", NULL, NULL },
......@@ -159,6 +165,7 @@ static const ac97_codec_id_t snd_ac97_codec_ids[] = {
{ 0x83847609, 0xffffffff, "STAC9721/23", patch_sigmatel_stac9721, NULL },
{ 0x83847644, 0xffffffff, "STAC9744", patch_sigmatel_stac9744, NULL },
{ 0x83847650, 0xffffffff, "STAC9750/51", NULL, NULL }, // patch?
{ 0x83847652, 0xffffffff, "STAC9752/53", NULL, NULL }, // patch?
{ 0x83847656, 0xffffffff, "STAC9756/57", patch_sigmatel_stac9756, NULL },
{ 0x83847666, 0xffffffff, "STAC9766/67", NULL, NULL }, // patch?
{ 0, 0, NULL, NULL, NULL }
......@@ -183,7 +190,7 @@ static const char *snd_ac97_stereo_enhancements[] =
/* 14 */ "Binaura 3D Audio Enhancement",
/* 15 */ "ESS Technology Stereo Enhancement",
/* 16 */ "Harman International VMAx",
/* 17 */ "Nvidea 3D Stereo Enhancement",
/* 17 */ "Nvidea/IC Ensemble/KS Waves 3D Stereo Enhancement",
/* 18 */ "Philips Incredible Sound",
/* 19 */ "Texas Instruments 3D Stereo Enhancement",
/* 20 */ "VLSI Technology 3D Stereo Enhancement",
......@@ -1672,18 +1679,18 @@ static int snd_ac97_mixer_build(snd_card_t * card, ac97_t * ac97)
ac97->spec.ad18xx.pcmreg[2] = 0x9f1f;
}
} else {
unsigned int pcm_ctrls = 2;
/* FIXME: C-Media chips have no PCM volume!! */
if (/*ac97->id == 0x434d4941 ||*/
ac97->id == 0x434d4942 ||
ac97->id == 0x434d4961)
goto no_pcm;
for (idx = 0; idx < 2; idx++)
pcm_ctrls = 1;
for (idx = 0; idx < pcm_ctrls; idx++)
if ((err = snd_ctl_add(card, snd_ac97_cnew(&snd_ac97_controls_pcm[idx], ac97))) < 0)
return err;
}
snd_ac97_write_cache(ac97, AC97_PCM, 0x9f1f);
no_pcm:
/* build Capture controls */
for (idx = 0; idx < 3; idx++)
if ((err = snd_ctl_add(card, snd_ac97_cnew(&snd_ac97_controls_capture[idx], ac97))) < 0)
......@@ -2074,6 +2081,10 @@ int snd_ac97_mixer(snd_card_t * card, ac97_t * _ac97, ac97_t ** rac97)
ac97->card = card;
spin_lock_init(&ac97->reg_lock);
if (ac97->pci) {
pci_read_config_word(ac97->pci, PCI_SUBSYSTEM_VENDOR_ID, &ac97->subsystem_vendor);
pci_read_config_word(ac97->pci, PCI_SUBSYSTEM_ID, &ac97->subsystem_device);
}
if (ac97->reset) {
ac97->reset(ac97);
goto __access_ok;
......@@ -2713,7 +2724,7 @@ static int set_spdif_rate(ac97_t *ac97, unsigned short rate)
}
spin_lock(&ac97->reg_lock);
old = ac97->regs[reg] & ~mask;
old = ac97->regs[reg] & mask;
spin_unlock(&ac97->reg_lock);
if (old != bits) {
snd_ac97_update_bits(ac97, AC97_EXTENDED_STATUS, AC97_EA_SPDIF, 0);
......
/*
* azt3328.c - driver for Aztech AZF3328 based soundcards (e.g. PCI168).
* Copyright (C) 2002 by Andreas Mohr <hw7oshyuv3001@sneakemail.com>
*
* Framework borrowed from Bart Hartgers's als4000.c.
* Driver developed on PCI168 AP(W) version (PCI rev. 10, subsystem ID 1801),
* found in a Fujitsu-Siemens PC ("Cordant", aluminum case).
* Other versions are:
* PCI168 A(W), sub ID 1800
* PCI168 A/AP, sub ID 8000
* Please give me feedback in case you try my driver with one of these!!
*
* GPL LICENSE
* 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
*
* NOTES
* Since Aztech does not provide any chipset documentation,
* even on repeated request to various addresses,
* and the answer that was finally given was negative
* (and I was stupid enough to manage to get hold of a PCI168 soundcard
* in the first place >:-P}),
* I was forced to base this driver on reverse engineering
* (3 weeks' worth of evenings filled with driver work).
* (and no, I did NOT go the easy way: to pick up a PCI128 for 9 Euros)
*
* The AZF3328 chip (note: AZF3328, *not* AZT3328, that's just the driver name
* for compatibility reasons) has the following features:
*
* - builtin AC97 conformant codec (SNR over 80dB)
* (really AC97 compliant?? I really doubt it when looking
* at the mixer register layout)
* - builtin genuine OPL3
* - full duplex 16bit playback/record at independent sampling rate
* - MPU401 (+ legacy address support) FIXME: how to enable legacy addr??
* - game port (legacy address support)
* - built-in General DirectX timer having a 20 bits counter
* with 1us resolution (FIXME: where is it?)
* - I2S serial port for external DAC
* - supports 33MHz PCI spec 2.1, PCI power management 1.0, compliant with ACPI
* - supports hardware volume control
* - single chip low cost solution (128 pin QFP)
* - supports programmable Sub-vendor and Sub-system ID
* required for Microsoft's logo compliance (FIXME: where?)
* - PCI168 AP(W) card: power amplifier with 4 Watts/channel at 4 Ohms
*
* Certain PCI versions of this card are susceptible to DMA traffic underruns
* in some systems (resulting in sound crackling/clicking/popping),
* probably because they don't have a DMA FIFO buffer or so.
* Overview (PCI ID/PCI subID/PCI rev.):
* - no DMA crackling on SiS735: 0x50DC/0x1801/16
* - unknown performance: 0x50DC/0x1801/10
*
* Crackling happens with VIA chipsets or, in my case, an SiS735, which is
* supposed to be very fast and supposed to get rid of crackling much
* better than a VIA, yet ironically I still get crackling, like many other
* people with the same chipset.
* Possible remedies:
* - plug card into a different PCI slot, preferrably one that isn't shared
* too much (this helps a lot, but not completely!)
* - get rid of PCI VGA card, use AGP instead
* - upgrade or downgrade BIOS
* - fiddle with PCI latency settings (setpci -v -s BUSID latency_timer=XX)
* Not too helpful.
* - Disable ACPI/power management/"Auto Detect RAM/PCI Clk" in BIOS
*
* BUGS
* - when Ctrl-C'ing mpg321, the playback loops a bit
* (premature DMA playback reset?)
* - full-duplex sometimes breaks (IRQ management issues?).
* Once even a spontaneous REBOOT happened!!!
*
* TODO
* - test MPU401 MIDI playback etc.
* - power management (CONFIG_PM). See e.g. intel8x0 or cs4281.
* This would be nice since the chip runs a bit hot, and it's *required*
* anyway for proper ACPI power management. In other words: rest
* assured that I *will* implement this very soon; as soon as Linux 2.5.x
* has power management that's bugfree enough to work properly on my desktop.
* - figure out what all unknown port bits are responsible for
*/
#include <sound/driver.h>
#include <asm/io.h>
#include <linux/init.h>
#include <linux/pci.h>
#include <linux/delay.h>
#include <linux/slab.h>
#include <sound/core.h>
#include <sound/control.h>
#include <sound/pcm.h>
#include <sound/rawmidi.h>
#include <sound/mpu401.h>
#include <sound/opl3.h>
#define SNDRV_GET_ID
#include <sound/initval.h>
#include "azt3328.h"
MODULE_AUTHOR("Andreas Mohr <hw7oshyuv3001@sneakemail.com>");
MODULE_DESCRIPTION("Aztech AZF3328 (PCI168)");
MODULE_LICENSE("GPL");
MODULE_CLASSES("{sound}");
MODULE_DEVICES("{{Aztech,AZF3328}}");
#define DEBUG_MISC 0
#define DEBUG_CALLS 0
#define DEBUG_MIXER 0
#define DEBUG_PLAY_REC 0
#define DEBUG_IO 0
#define MIXER_TESTING 0
#if DEBUG_MISC
#define snd_azf3328_dbgmisc(format, args...) printk(KERN_ERR format, ##args)
#else
#define snd_azf3328_dbgmisc(format, args...)
#endif
#if DEBUG_CALLS
#define snd_azf3328_dbgcalls(format, args...) printk(format, ##args)
#define snd_azf3328_dbgcallenter() printk(KERN_ERR "entering %s\n", __FUNCTION__)
#define snd_azf3328_dbgcallleave() printk(KERN_ERR "leaving %s\n", __FUNCTION__)
#else
#define snd_azf3328_dbgcalls(format, args...)
#define snd_azf3328_dbgcallenter()
#define snd_azf3328_dbgcallleave()
#endif
#if DEBUG_MIXER
#define snd_azf3328_dbgmixer(format, args...) printk(format, ##args)
#else
#define snd_azf3328_dbgmixer(format, args...)
#endif
#if DEBUG_PLAY_REC
#define snd_azf3328_dbgplay(format, args...) printk(KERN_ERR format, ##args)
#else
#define snd_azf3328_dbgplay(format, args...)
#endif
#if DEBUG_IO
#define snd_azf3328_dbgio(chip, where) \
printk(KERN_ERR "%s: IDX_IO_PLAY_FLAGS %04x, IDX_IO_PLAY_IRQMASK %04x, IDX_IO_IRQSTATUS %04x\n", where, inw(chip->codec_port+IDX_IO_PLAY_FLAGS), inw(chip->codec_port+IDX_IO_PLAY_IRQMASK), inw(chip->codec_port+IDX_IO_IRQSTATUS))
#else
#define snd_azf3328_dbgio(chip, where)
#endif
static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-MAX */
static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* ID for this card */
static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP; /* Enable this card */
static int joystick[SNDRV_CARDS] =
{-1}; /* "unset" as default */
MODULE_PARM(index, "1-" __MODULE_STRING(SNDRV_CARDS) "i");
MODULE_PARM_DESC(index, "Index value for AZF3328 soundcard.");
MODULE_PARM_SYNTAX(index, SNDRV_INDEX_DESC);
MODULE_PARM(id, "1-" __MODULE_STRING(SNDRV_CARDS) "s");
MODULE_PARM_DESC(id, "ID string for AZF3328 soundcard.");
MODULE_PARM_SYNTAX(id, SNDRV_ID_DESC);
MODULE_PARM(enable, "1-" __MODULE_STRING(SNDRV_CARDS) "i");
MODULE_PARM_DESC(enable, "Enable AZF3328 soundcard.");
MODULE_PARM_SYNTAX(enable, SNDRV_INDEX_DESC);
MODULE_PARM(joystick, "1-" __MODULE_STRING(SNDRV_CARDS) "i");
MODULE_PARM_DESC(joystick, "Forced joystick port enable for AZF3328 soundcard. (0 = force disable)");
MODULE_PARM_SYNTAX(joystick, SNDRV_ENABLED);
typedef struct _snd_azf3328 azf3328_t;
#define chip_t azf3328_t
struct _snd_azf3328 {
int irq;
unsigned long codec_port;
struct resource *res_codec_port;
unsigned long io2_port;
struct resource *res_io2_port;
unsigned long mpu_port;
struct resource *res_mpu_port;
unsigned long synth_port;
struct resource *res_synth_port;
unsigned long mixer_port;
struct resource *res_mixer_port;
unsigned long game_port;
struct pci_dev *pci;
snd_card_t *card;
snd_pcm_t *pcm;
snd_rawmidi_t *rmidi;
snd_pcm_substream_t *playback_substream;
snd_pcm_substream_t *capture_substream;
unsigned int is_playing;
unsigned int is_recording;
spinlock_t reg_lock;
};
static struct pci_device_id snd_azf3328_ids[] __devinitdata = {
{ 0x122D, 0x50DC, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, /* PCI168/3328 */
{ 0x122D, 0x80DA, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, /* 3328 */
{ 0, }
};
MODULE_DEVICE_TABLE(pci, snd_azf3328_ids);
void snd_azf3328_io2_write(azf3328_t *chip, int reg, unsigned char value)
{
outb(value, chip->io2_port + reg);
}
unsigned char snd_azf3328_io2_read(azf3328_t *chip, int reg)
{
return inb(chip->io2_port + reg);
}
void snd_azf3328_mixer_write(azf3328_t *chip, int reg, unsigned long value, int type)
{
switch(type) {
case WORD_VALUE:
outw(value, chip->mixer_port + reg);
break;
case DWORD_VALUE:
outl(value, chip->mixer_port + reg);
break;
case BYTE_VALUE:
outb(value, chip->mixer_port + reg);
break;
}
}
unsigned long snd_azf3328_mixer_read(azf3328_t *chip, int reg, int type)
{
unsigned long res = 0;
switch(type) {
case WORD_VALUE:
res = (unsigned long)inw(chip->mixer_port + reg);
break;
case DWORD_VALUE:
res = (unsigned long)inl(chip->mixer_port + reg);
break;
case BYTE_VALUE:
res = (unsigned long)inb(chip->mixer_port + reg);
break;
}
return res;
}
void snd_azf3328_mixer_set_mute(azf3328_t *chip, int reg, int do_mute)
{
unsigned char oldval;
/* the mute bit is on the *second* (i.e. right) register of a
* left/right channel setting */
oldval = inb(chip->mixer_port + reg + 1);
if (do_mute)
oldval |= 0x80;
else
oldval &= ~0x80;
outb(oldval, chip->mixer_port + reg + 1);
}
void snd_azf3328_mixer_write_volume_gradually(azf3328_t *chip, int reg, unsigned char dst_vol_left, unsigned char dst_vol_right, int chan_sel, int delay)
{
unsigned char curr_vol_left = 0, curr_vol_right = 0;
int left_done = 0, right_done = 0;
snd_azf3328_dbgcallenter();
if (chan_sel & SET_CHAN_LEFT)
curr_vol_left = inb(chip->mixer_port + reg + 1);
else
left_done = 1;
if (chan_sel & SET_CHAN_RIGHT)
curr_vol_right = inb(chip->mixer_port + reg + 0);
else
right_done = 1;
/* take care of muting flag (0x80) contained in left channel */
if (curr_vol_left & 0x80)
dst_vol_left |= 0x80;
else
dst_vol_left &= ~0x80;
do
{
if (!left_done)
{
if (curr_vol_left > dst_vol_left)
curr_vol_left--;
else
if (curr_vol_left < dst_vol_left)
curr_vol_left++;
else
left_done = 1;
outb(curr_vol_left, chip->mixer_port + reg + 1);
}
if (!right_done)
{
if (curr_vol_right > dst_vol_right)
curr_vol_right--;
else
if (curr_vol_right < dst_vol_right)
curr_vol_right++;
else
right_done = 1;
/* during volume change, the right channel is crackling
* somewhat more than the left channel, unfortunately.
* This seems to be a hardware issue. */
outb(curr_vol_right, chip->mixer_port + reg + 0);
}
if (delay)
mdelay(delay);
}
while ((!left_done) || (!right_done));
snd_azf3328_dbgcallleave();
}
/*
* general mixer element
*/
typedef struct azf3328_mixer_reg {
unsigned int reg;
unsigned int lchan_shift, rchan_shift;
unsigned int mask;
unsigned int invert: 1;
unsigned int stereo: 1;
unsigned int enum_c: 4;
} azf3328_mixer_reg_t;
#define COMPOSE_MIXER_REG(reg,lchan_shift,rchan_shift,mask,invert,stereo,enum_c) \
((reg) | (lchan_shift << 8) | (rchan_shift << 12) | (mask << 16) | (invert << 24) | (stereo << 25) | (enum_c << 26))
static void snd_azf3328_mixer_reg_decode(azf3328_mixer_reg_t *r, unsigned long val)
{
r->reg = val & 0xff;
r->lchan_shift = (val >> 8) & 0x0f;
r->rchan_shift = (val >> 12) & 0x0f;
r->mask = (val >> 16) & 0xff;
r->invert = (val >> 24) & 1;
r->stereo = (val >> 25) & 1;
r->enum_c = (val >> 26) & 0x0f;
}
/*
* mixer switches/volumes
*/
#define AZF3328_MIXER_SWITCH(xname, reg, shift, invert) \
{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
.info = snd_azf3328_info_mixer, \
.get = snd_azf3328_get_mixer, .put = snd_azf3328_put_mixer, \
.private_value = COMPOSE_MIXER_REG(reg, shift, 0, 0x1, invert, 0, 0), \
}
#define AZF3328_MIXER_VOL_STEREO(xname, reg, mask, invert) \
{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
.info = snd_azf3328_info_mixer, \
.get = snd_azf3328_get_mixer, .put = snd_azf3328_put_mixer, \
.private_value = COMPOSE_MIXER_REG(reg, 8, 0, mask, invert, 1, 0), \
}
#define AZF3328_MIXER_VOL_MONO(xname, reg, mask, is_right_chan) \
{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
.info = snd_azf3328_info_mixer, \
.get = snd_azf3328_get_mixer, .put = snd_azf3328_put_mixer, \
.private_value = COMPOSE_MIXER_REG(reg, is_right_chan ? 0 : 8, 0, mask, 1, 0, 0), \
}
#define AZF3328_MIXER_VOL_SPECIAL(xname, reg, mask, shift, invert) \
{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
.info = snd_azf3328_info_mixer, \
.get = snd_azf3328_get_mixer, .put = snd_azf3328_put_mixer, \
.private_value = COMPOSE_MIXER_REG(reg, shift, 0, mask, invert, 0, 0), \
}
#define AZF3328_MIXER_ENUM(xname, reg, enum_c, shift) \
{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
.info = snd_azf3328_info_mixer_enum, \
.get = snd_azf3328_get_mixer_enum, .put = snd_azf3328_put_mixer_enum, \
.private_value = COMPOSE_MIXER_REG(reg, shift, 0, 0, 0, 0, enum_c), \
}
static int snd_azf3328_info_mixer(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t *uinfo)
{
azf3328_mixer_reg_t reg;
snd_azf3328_dbgcallenter();
snd_azf3328_mixer_reg_decode(&reg, kcontrol->private_value);
uinfo->type = reg.mask == 1 ? SNDRV_CTL_ELEM_TYPE_BOOLEAN : SNDRV_CTL_ELEM_TYPE_INTEGER;
uinfo->count = reg.stereo + 1;
uinfo->value.integer.min = 0;
uinfo->value.integer.max = reg.mask;
snd_azf3328_dbgcallleave();
return 0;
}
static int snd_azf3328_get_mixer(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
{
azf3328_t *chip = snd_kcontrol_chip(kcontrol);
azf3328_mixer_reg_t reg;
unsigned int oreg, val;
snd_azf3328_dbgcallenter();
snd_azf3328_mixer_reg_decode(&reg, kcontrol->private_value);
oreg = inw(chip->mixer_port + reg.reg);
val = (oreg >> reg.lchan_shift) & reg.mask;
if (reg.invert)
val = reg.mask - val;
ucontrol->value.integer.value[0] = val;
if (reg.stereo) {
val = (oreg >> reg.rchan_shift) & reg.mask;
if (reg.invert)
val = reg.mask - val;
ucontrol->value.integer.value[1] = val;
}
snd_azf3328_dbgmixer("get: %02x is %04x -> vol %02lx|%02lx (shift %02d|%02d, mask %02x, inv. %d, stereo %d)\n", reg.reg, oreg, ucontrol->value.integer.value[0], ucontrol->value.integer.value[1], reg.lchan_shift, reg.rchan_shift, reg.mask, reg.invert, reg.stereo);
snd_azf3328_dbgcallleave();
return 0;
}
static int snd_azf3328_put_mixer(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
{
azf3328_t *chip = snd_kcontrol_chip(kcontrol);
azf3328_mixer_reg_t reg;
unsigned int oreg, nreg, val;
snd_azf3328_dbgcallenter();
snd_azf3328_mixer_reg_decode(&reg, kcontrol->private_value);
oreg = inw(chip->mixer_port + reg.reg);
val = ucontrol->value.integer.value[0] & reg.mask;
if (reg.invert)
val = reg.mask - val;
nreg = oreg & ~(reg.mask << reg.lchan_shift);
nreg |= (val << reg.lchan_shift);
if (reg.stereo) {
val = ucontrol->value.integer.value[1] & reg.mask;
if (reg.invert)
val = reg.mask - val;
nreg &= ~(reg.mask << reg.rchan_shift);
nreg |= (val << reg.rchan_shift);
}
if (reg.mask >= 0x07) /* it's a volume control, so better take care */
snd_azf3328_mixer_write_volume_gradually(chip, reg.reg, nreg >> 8, nreg & 0xff, SET_CHAN_LEFT|SET_CHAN_RIGHT, 0); /* just set both channels, doesn't matter */
else
outw(nreg, chip->mixer_port + reg.reg);
snd_azf3328_dbgmixer("put: %02x to %02lx|%02lx, oreg %04x; shift %02d|%02d -> nreg %04x; after: %04x\n", reg.reg, ucontrol->value.integer.value[0], ucontrol->value.integer.value[1], oreg, reg.lchan_shift, reg.rchan_shift, nreg, inw(chip->mixer_port + reg.reg));
snd_azf3328_dbgcallleave();
return (nreg != oreg);
}
static int snd_azf3328_info_mixer_enum(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t * uinfo)
{
azf3328_mixer_reg_t reg;
static char *texts1[2] = { "ModemOut1", "ModemOut2" };
static char *texts2[2] = { "MonoSelectSource1", "MonoSelectSource2" };
static char *texts3[8] = {
"Mic", "CD", "Video", "Aux", "Line",
"Mix", "Mix Mono", "Phone"
};
snd_azf3328_mixer_reg_decode(&reg, kcontrol->private_value);
uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
uinfo->count = (reg.reg == IDX_MIXER_REC_SELECT) ? 2 : 1;
uinfo->value.enumerated.items = reg.enum_c;
if (uinfo->value.enumerated.item > reg.enum_c - 1U)
uinfo->value.enumerated.item = reg.enum_c - 1U;
if (reg.reg == IDX_MIXER_ADVCTL2)
{
if (reg.lchan_shift == 8) /* modem out sel */
strcpy(uinfo->value.enumerated.name, texts1[uinfo->value.enumerated.item]);
else /* mono sel source */
strcpy(uinfo->value.enumerated.name, texts2[uinfo->value.enumerated.item]);
}
else
strcpy(uinfo->value.enumerated.name, texts3[uinfo->value.enumerated.item]
);
return 0;
}
static int snd_azf3328_get_mixer_enum(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
{
azf3328_mixer_reg_t reg;
azf3328_t *chip = snd_kcontrol_chip(kcontrol);
unsigned short val;
snd_azf3328_mixer_reg_decode(&reg, kcontrol->private_value);
val = inw(chip->mixer_port + reg.reg);
if (reg.reg == IDX_MIXER_REC_SELECT)
{
ucontrol->value.enumerated.item[0] = (val >> 8) & (reg.enum_c - 1);
ucontrol->value.enumerated.item[1] = (val >> 0) & (reg.enum_c - 1);
}
else
ucontrol->value.enumerated.item[0] = (val >> reg.lchan_shift) & (reg.enum_c - 1);
snd_azf3328_dbgmixer("get_enum: %02x is %04x -> %d|%d (shift %02d, enum_c %d)\n", reg.reg, val, ucontrol->value.enumerated.item[0], ucontrol->value.enumerated.item[1], reg.lchan_shift, reg.enum_c);
return 0;
}
static int snd_azf3328_put_mixer_enum(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
{
azf3328_mixer_reg_t reg;
azf3328_t *chip = snd_kcontrol_chip(kcontrol);
unsigned int oreg, nreg, val;
snd_azf3328_mixer_reg_decode(&reg, kcontrol->private_value);
oreg = inw(chip->mixer_port + reg.reg);
val = oreg;
if (reg.reg == IDX_MIXER_REC_SELECT)
{
if (ucontrol->value.enumerated.item[0] > reg.enum_c - 1U ||
ucontrol->value.enumerated.item[1] > reg.enum_c - 1U)
return -EINVAL;
val = (ucontrol->value.enumerated.item[0] << 8) |
(ucontrol->value.enumerated.item[1] << 0);
}
else
{
if (ucontrol->value.enumerated.item[0] > reg.enum_c - 1U)
return -EINVAL;
val &= ~((reg.enum_c - 1) << reg.lchan_shift);
val |= (ucontrol->value.enumerated.item[0] << reg.lchan_shift);
}
outw(val, chip->mixer_port + reg.reg);
nreg = val;
snd_azf3328_dbgmixer("put_enum: %02x to %04x, oreg %04x\n", reg.reg, val, oreg);
return (nreg != oreg);
}
#define NUM_CONTROLS(ary) (sizeof(ary) / sizeof(snd_kcontrol_new_t))
static snd_kcontrol_new_t snd_azf3328_mixer_controls[] __devinitdata = {
AZF3328_MIXER_SWITCH("Master Playback Switch", IDX_MIXER_PLAY_MASTER, 15, 1),
AZF3328_MIXER_VOL_STEREO("Master Playback Volume", IDX_MIXER_PLAY_MASTER, 0x1f, 1),
AZF3328_MIXER_SWITCH("Wave Playback Switch", IDX_MIXER_WAVEOUT, 15, 1),
AZF3328_MIXER_VOL_STEREO("Wave Playback Volume", IDX_MIXER_WAVEOUT, 0x1f, 1),
AZF3328_MIXER_SWITCH("Wave Playback 3D Bypass", IDX_MIXER_ADVCTL2, 7, 1),
AZF3328_MIXER_SWITCH("FM Playback Switch", IDX_MIXER_FMSYNTH, 15, 1),
AZF3328_MIXER_VOL_STEREO("FM Playback Volume", IDX_MIXER_FMSYNTH, 0x1f, 1),
AZF3328_MIXER_SWITCH("CD Playback Switch", IDX_MIXER_CDAUDIO, 15, 1),
AZF3328_MIXER_VOL_STEREO("CD Playback Volume", IDX_MIXER_CDAUDIO, 0x1f, 1),
AZF3328_MIXER_SWITCH("Capture Switch", IDX_MIXER_REC_VOLUME, 15, 1),
AZF3328_MIXER_VOL_STEREO("Capture Volume", IDX_MIXER_REC_VOLUME, 0x0f, 0),
AZF3328_MIXER_ENUM("Capture Source", IDX_MIXER_REC_SELECT, 8, 0),
AZF3328_MIXER_SWITCH("Mic Playback Switch", IDX_MIXER_MIC, 15, 1),
AZF3328_MIXER_VOL_MONO("Mic Playback Volume", IDX_MIXER_MIC, 0x1f, 1),
AZF3328_MIXER_SWITCH("Mic Boost (+20dB)", IDX_MIXER_MIC, 6, 0),
AZF3328_MIXER_SWITCH("Line Playback Switch", IDX_MIXER_LINEIN, 15, 1),
AZF3328_MIXER_VOL_STEREO("Line Playback Volume", IDX_MIXER_LINEIN, 0x1f, 1),
AZF3328_MIXER_SWITCH("PCBeep Playback Switch", IDX_MIXER_PCBEEP, 15, 1),
AZF3328_MIXER_VOL_SPECIAL("PCBeep Playback Volume", IDX_MIXER_PCBEEP, 0x0f, 1, 1),
AZF3328_MIXER_SWITCH("Video Playback Switch", IDX_MIXER_VIDEO, 15, 1),
AZF3328_MIXER_VOL_STEREO("Video Playback Volume", IDX_MIXER_VIDEO, 0x1f, 1),
AZF3328_MIXER_SWITCH("Aux Playback Switch", IDX_MIXER_AUX, 15, 1),
AZF3328_MIXER_VOL_STEREO("Aux Playback Volume", IDX_MIXER_AUX, 0x1f, 1),
AZF3328_MIXER_SWITCH("Modem Playback Switch", IDX_MIXER_MODEMOUT, 15, 1),
AZF3328_MIXER_VOL_MONO("Modem Playback Volume", IDX_MIXER_MODEMOUT, 0x1f, 1),
AZF3328_MIXER_SWITCH("Modem Capture Switch", IDX_MIXER_MODEMIN, 15, 1),
AZF3328_MIXER_VOL_MONO("Modem Capture Volume", IDX_MIXER_MODEMIN, 0x1f, 1),
AZF3328_MIXER_ENUM("Modem Out Select", IDX_MIXER_ADVCTL2, 2, 8),
AZF3328_MIXER_ENUM("Mono Select Source", IDX_MIXER_ADVCTL2, 2, 9),
AZF3328_MIXER_VOL_SPECIAL("Tone Control - Treble", IDX_MIXER_BASSTREBLE, 0x07, 1, 0),
AZF3328_MIXER_VOL_SPECIAL("Tone Control - Bass", IDX_MIXER_BASSTREBLE, 0x07, 9, 0),
AZF3328_MIXER_SWITCH("3D Control - Toggle", IDX_MIXER_ADVCTL2, 13, 0),
AZF3328_MIXER_VOL_SPECIAL("3D Control - Volume", IDX_MIXER_ADVCTL1, 0x07, 1, 0), /* "3D Width" */
AZF3328_MIXER_VOL_SPECIAL("3D Control - Space", IDX_MIXER_ADVCTL1, 0x03, 8, 0), /* "Hifi 3D" */
#if MIXER_TESTING
AZF3328_MIXER_SWITCH("0", IDX_MIXER_ADVCTL2, 0, 0),
AZF3328_MIXER_SWITCH("1", IDX_MIXER_ADVCTL2, 1, 0),
AZF3328_MIXER_SWITCH("2", IDX_MIXER_ADVCTL2, 2, 0),
AZF3328_MIXER_SWITCH("3", IDX_MIXER_ADVCTL2, 3, 0),
AZF3328_MIXER_SWITCH("4", IDX_MIXER_ADVCTL2, 4, 0),
AZF3328_MIXER_SWITCH("5", IDX_MIXER_ADVCTL2, 5, 0),
AZF3328_MIXER_SWITCH("6", IDX_MIXER_ADVCTL2, 6, 0),
AZF3328_MIXER_SWITCH("7", IDX_MIXER_ADVCTL2, 7, 0),
AZF3328_MIXER_SWITCH("8", IDX_MIXER_ADVCTL2, 8, 0),
AZF3328_MIXER_SWITCH("9", IDX_MIXER_ADVCTL2, 9, 0),
AZF3328_MIXER_SWITCH("10", IDX_MIXER_ADVCTL2, 10, 0),
AZF3328_MIXER_SWITCH("11", IDX_MIXER_ADVCTL2, 11, 0),
AZF3328_MIXER_SWITCH("12", IDX_MIXER_ADVCTL2, 12, 0),
AZF3328_MIXER_SWITCH("13", IDX_MIXER_ADVCTL2, 13, 0),
AZF3328_MIXER_SWITCH("14", IDX_MIXER_ADVCTL2, 14, 0),
AZF3328_MIXER_SWITCH("15", IDX_MIXER_ADVCTL2, 15, 0),
#endif
};
#define AZF3328_INIT_VALUES (sizeof(snd_azf3328_init_values)/sizeof(unsigned int)/2)
static unsigned int snd_azf3328_init_values[][2] = {
{ IDX_MIXER_PLAY_MASTER, MIXER_MUTE_MASK|0x1f1f },
{ IDX_MIXER_MODEMOUT, MIXER_MUTE_MASK|0x1f1f },
{ IDX_MIXER_BASSTREBLE, 0x0000 },
{ IDX_MIXER_PCBEEP, MIXER_MUTE_MASK|0x1f1f },
{ IDX_MIXER_MODEMIN, MIXER_MUTE_MASK|0x1f1f },
{ IDX_MIXER_MIC, MIXER_MUTE_MASK|0x001f },
{ IDX_MIXER_LINEIN, MIXER_MUTE_MASK|0x1f1f },
{ IDX_MIXER_CDAUDIO, MIXER_MUTE_MASK|0x1f1f },
{ IDX_MIXER_VIDEO, MIXER_MUTE_MASK|0x1f1f },
{ IDX_MIXER_AUX, MIXER_MUTE_MASK|0x1f1f },
{ IDX_MIXER_WAVEOUT, MIXER_MUTE_MASK|0x1f1f },
{ IDX_MIXER_FMSYNTH, MIXER_MUTE_MASK|0x1f1f },
{ IDX_MIXER_REC_VOLUME, MIXER_MUTE_MASK|0x0707 },
};
static int __devinit snd_azf3328_mixer_new(azf3328_t *chip)
{
snd_card_t *card;
snd_kcontrol_new_t *sw;
unsigned int idx;
int err;
snd_azf3328_dbgcallenter();
snd_assert(chip != NULL && chip->card != NULL, return -EINVAL);
card = chip->card;
/* mixer reset */
snd_azf3328_mixer_write(chip, IDX_MIXER_RESET, 0x0, WORD_VALUE);
/* mute and zero volume channels */
for (idx = 0; idx < AZF3328_INIT_VALUES; idx++) {
snd_azf3328_mixer_write(chip, snd_azf3328_init_values[idx][0], snd_azf3328_init_values[idx][1], WORD_VALUE);
}
/* add mixer controls */
sw = snd_azf3328_mixer_controls;
for (idx = 0; idx < NUM_CONTROLS(snd_azf3328_mixer_controls); idx++, sw++) {
if ((err = snd_ctl_add(chip->card, snd_ctl_new1(sw, chip))) < 0)
return err;
}
snd_component_add(card, "AZF3328 mixer");
strcpy(card->mixername, "AZF3328 mixer");
snd_azf3328_dbgcallleave();
return 0;
}
static int snd_azf3328_hw_params(snd_pcm_substream_t * substream,
snd_pcm_hw_params_t * hw_params)
{
int res;
snd_azf3328_dbgcallenter();
res = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params));
snd_azf3328_dbgcallleave();
return res;
}
static int snd_azf3328_hw_free(snd_pcm_substream_t * substream)
{
snd_azf3328_dbgcallenter();
snd_pcm_lib_free_pages(substream);
snd_azf3328_dbgcallleave();
return 0;
}
static void snd_azf3328_setfmt(azf3328_t *chip,
unsigned int reg,
unsigned int bitrate,
unsigned int format_width,
unsigned int channels
)
{
unsigned int val = 0xff00;
unsigned long flags;
snd_azf3328_dbgcallenter();
switch (bitrate) {
case 5512: val |= 0x0d; break; /* the AZF3328 names it "5510" for some strange reason */
case 6620: val |= 0x0b; break;
case 8000: val |= 0x00; break;
case 9600: val |= 0x08; break;
case 11025: val |= 0x01; break;
case 16000: val |= 0x02; break;
case 22050: val |= 0x03; break;
case 32000: val |= 0x04; break;
case 44100: val |= 0x05; break;
case 48000: val |= 0x06; break;
case 64000: val |= 0x07; break;
default:
snd_printk("unknown bitrate %d, assuming 44.1kHz!\n", bitrate);
val |= 0x05; /* 44100 */
break;
}
/* val = 0xff07; 3m27.993s (65301Hz; -> 64000Hz???) */
/* val = 0xff09; 17m15.098s (13123,478Hz; -> 12000Hz???) */
/* val = 0xff0a; 47m30.599s (4764,891Hz; -> 4800Hz???) */
/* val = 0xff0c; 57m0.510s (4010,263Hz; -> 4000Hz???) */
/* val = 0xff05; 5m11.556s (... -> 44100Hz) */
/* val = 0xff03; 10m21.529s (21872,463Hz; -> 22050Hz???) */
/* val = 0xff0f; 20m41.883s (10937,993Hz; -> 11025Hz???) */
/* val = 0xff0d; 41m23.135s (5523,600Hz; -> 5512Hz???) */
/* val = 0xff0e; 28m30.777s (8017Hz; -> 8000Hz???) */
if (channels == 2)
val |= SOUNDFORMAT_FLAG_2CHANNELS;
if (format_width == 16)
val |= SOUNDFORMAT_FLAG_16BIT;
spin_lock_irqsave(&chip->reg_lock, flags);
/* set bitrate/format */
outw(val, chip->codec_port+reg);
/* changing the bitrate/format settings switches off the
* audio output with an annoying click in case of 8/16bit format change
* (maybe shutting down DAC/ADC?), thus immediately
* do some tweaking to reenable it and get rid of the clicking
* (FIXME: yes, it works, but what exactly am I doing here?? :)
* FIXME: does this have some side effects for full-duplex
* or other dramatic side effects? */
if (reg == IDX_IO_PLAY_SOUNDFORMAT) /* only do it for playback */
outw(inw(chip->codec_port + IDX_IO_PLAY_FLAGS)|DMA_PLAY_SOMETHING1|DMA_PLAY_SOMETHING2|SOMETHING_ALMOST_ALWAYS_SET|DMA_EPILOGUE_SOMETHING|DMA_SOMETHING_ELSE, chip->codec_port + IDX_IO_PLAY_FLAGS);
spin_unlock_irqrestore(&chip->reg_lock, flags);
snd_azf3328_dbgcallleave();
}
static void snd_azf3328_setdmaa(azf3328_t *chip,
long unsigned int addr,
unsigned int count,
unsigned int size,
int do_recording)
{
long unsigned int addr1;
long unsigned int addr2;
unsigned int count1;
unsigned int count2;
unsigned long flags;
int reg_offs = do_recording ? 0x20 : 0x00;
snd_azf3328_dbgcallenter();
/* AZF3328 uses a two buffer pointer DMA playback approach */
if (!chip->is_playing)
{
addr1 = addr;
addr2 = addr+(size/2);
count1 = (size/2)-1;
count2 = (size/2)-1;
#if DEBUG_PLAY_REC
snd_azf3328_dbgplay("setting dma: buf1 %08lx[%d], buf2 %08lx[%d]\n", addr1, count1, addr2, count2);
#endif
spin_lock_irqsave(&chip->reg_lock, flags);
outl(addr1, chip->codec_port+reg_offs+IDX_IO_PLAY_DMA_START_1);
outl(addr2, chip->codec_port+reg_offs+IDX_IO_PLAY_DMA_START_2);
outw(count1, chip->codec_port+reg_offs+IDX_IO_PLAY_DMA_LEN_1);
outw(count2, chip->codec_port+reg_offs+IDX_IO_PLAY_DMA_LEN_2);
spin_unlock_irqrestore(&chip->reg_lock, flags);
}
snd_azf3328_dbgcallleave();
}
static int snd_azf3328_playback_prepare(snd_pcm_substream_t *substream)
{
#if 0
azf3328_t *chip = snd_pcm_substream_chip(substream);
snd_pcm_runtime_t *runtime = substream->runtime;
unsigned int size = snd_pcm_lib_buffer_bytes(substream);
unsigned int count = snd_pcm_lib_period_bytes(substream);
#endif
snd_azf3328_dbgcallenter();
#if 0
snd_azf3328_setfmt(chip, IDX_IO_PLAY_SOUNDFORMAT, runtime->rate, snd_pcm_format_width(runtime->format), runtime->channels);
snd_azf3328_setdmaa(chip, runtime->dma_addr, count, size, 0);
#endif
snd_azf3328_dbgcallleave();
return 0;
}
static int snd_azf3328_capture_prepare(snd_pcm_substream_t * substream)
{
#if 0
azf3328_t *chip = snd_pcm_substream_chip(substream);
snd_pcm_runtime_t *runtime = substream->runtime;
unsigned int size = snd_pcm_lib_buffer_bytes(substream);
unsigned int count = snd_pcm_lib_period_bytes(substream);
#endif
snd_azf3328_dbgcallenter();
#if 0
snd_azf3328_setfmt(chip, IDX_IO_REC_SOUNDFORMAT, runtime->rate, snd_pcm_format_width(runtime->format), runtime->channels);
snd_azf3328_setdmaa(chip, runtime->dma_addr, count, size, 1);
#endif
snd_azf3328_dbgcallleave();
return 0;
}
static int snd_azf3328_playback_trigger(snd_pcm_substream_t * substream, int cmd)
{
unsigned long flags;
azf3328_t *chip = snd_pcm_substream_chip(substream);
snd_pcm_runtime_t *runtime = substream->runtime;
int result = 0;
unsigned int status1;
snd_azf3328_dbgcalls("snd_azf3328_playback_trigger cmd %d\n", cmd);
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
snd_azf3328_dbgio(chip, "trigger1");
/* mute WaveOut */
snd_azf3328_mixer_set_mute(chip, IDX_MIXER_WAVEOUT, 1);
snd_azf3328_setfmt(chip, IDX_IO_PLAY_SOUNDFORMAT, runtime->rate, snd_pcm_format_width(runtime->format), runtime->channels);
spin_lock_irqsave(&chip->reg_lock, flags);
/* stop playback */
status1 = inw(chip->codec_port+IDX_IO_PLAY_FLAGS);
status1 &= ~DMA_RESUME;
outw(status1, chip->codec_port+IDX_IO_PLAY_FLAGS);
/* FIXME: clear interrupts or what??? */
outw(0xffff, chip->codec_port+IDX_IO_PLAY_IRQMASK);
spin_unlock_irqrestore(&chip->reg_lock, flags);
snd_azf3328_setdmaa(chip, runtime->dma_addr, snd_pcm_lib_period_bytes(substream), snd_pcm_lib_buffer_bytes(substream), 0);
spin_lock_irqsave(&chip->reg_lock, flags);
#if WIN9X
/* FIXME: enable playback/recording??? */
status1 |= DMA_PLAY_SOMETHING1 | DMA_PLAY_SOMETHING2;
outw(status1, chip->codec_port+IDX_IO_PLAY_FLAGS);
/* start playback again */
/* FIXME: what is this value (0x0010)??? */
status1 |= DMA_RESUME | DMA_EPILOGUE_SOMETHING;
outw(status1, chip->codec_port+IDX_IO_PLAY_FLAGS);
#else /* NT4 */
outw(0x00, chip->codec_port+IDX_IO_PLAY_FLAGS);
outw(DMA_PLAY_SOMETHING1, chip->codec_port+IDX_IO_PLAY_FLAGS);
outw(DMA_PLAY_SOMETHING1|DMA_PLAY_SOMETHING2, chip->codec_port+IDX_IO_PLAY_FLAGS);
outw(DMA_RESUME|SOMETHING_ALMOST_ALWAYS_SET|DMA_EPILOGUE_SOMETHING|DMA_SOMETHING_ELSE, chip->codec_port+IDX_IO_PLAY_FLAGS);
#endif
spin_unlock_irqrestore(&chip->reg_lock, flags);
/* now unmute WaveOut */
snd_azf3328_mixer_set_mute(chip, IDX_MIXER_WAVEOUT, 0);
snd_azf3328_dbgio(chip, "trigger2");
chip->is_playing = 1;
break;
case SNDRV_PCM_TRIGGER_STOP:
/* mute WaveOut */
snd_azf3328_mixer_set_mute(chip, IDX_MIXER_WAVEOUT, 1);
spin_lock_irqsave(&chip->reg_lock, flags);
/* stop playback */
status1 = inw(chip->codec_port+IDX_IO_PLAY_FLAGS);
status1 &= ~DMA_RESUME;
outw(status1, chip->codec_port+IDX_IO_PLAY_FLAGS);
status1 |= DMA_PLAY_SOMETHING1;
outw(status1, chip->codec_port+IDX_IO_PLAY_FLAGS);
status1 &= ~DMA_PLAY_SOMETHING1;
outw(status1, chip->codec_port+IDX_IO_PLAY_FLAGS);
spin_unlock_irqrestore(&chip->reg_lock, flags);
/* now unmute WaveOut */
snd_azf3328_mixer_set_mute(chip, IDX_MIXER_WAVEOUT, 0);
chip->is_playing = 0;
break;
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
snd_printk("FIXME: SNDRV_PCM_TRIGGER_PAUSE_PUSH NIY!\n");
break;
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
snd_printk("FIXME: SNDRV_PCM_TRIGGER_PAUSE_RELEASE NIY!\n");
break;
default:
return -EINVAL;
}
snd_azf3328_dbgcallleave();
return result;
}
/* this is just analogous to playback; I'm not quite sure whether recording
* should actually be triggered like that */
static int snd_azf3328_capture_trigger(snd_pcm_substream_t * substream, int cmd)
{
unsigned long flags;
azf3328_t *chip = snd_pcm_substream_chip(substream);
snd_pcm_runtime_t *runtime = substream->runtime;
int result = 0;
unsigned int status1;
snd_azf3328_dbgcalls("snd_azf3328_capture_trigger cmd %d\n", cmd);
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
snd_azf3328_dbgio(chip, "trigger1");
snd_azf3328_setfmt(chip, IDX_IO_REC_SOUNDFORMAT, runtime->rate, snd_pcm_format_width(runtime->format), runtime->channels);
spin_lock_irqsave(&chip->reg_lock, flags);
/* stop recording */
status1 = inw(chip->codec_port+IDX_IO_REC_FLAGS);
status1 &= ~DMA_RESUME;
outw(status1, chip->codec_port+IDX_IO_REC_FLAGS);
/* FIXME: clear interrupts or what??? */
outw(0xffff, chip->codec_port+IDX_IO_REC_IRQMASK);
spin_unlock_irqrestore(&chip->reg_lock, flags);
snd_azf3328_setdmaa(chip, runtime->dma_addr, snd_pcm_lib_period_bytes(substream), snd_pcm_lib_buffer_bytes(substream), 1);
spin_lock_irqsave(&chip->reg_lock, flags);
#if WIN9X
/* FIXME: enable playback/recording??? */
status1 |= DMA_PLAY_SOMETHING1 | DMA_PLAY_SOMETHING2;
outw(status1, chip->codec_port+IDX_IO_REC_FLAGS);
/* start playback again */
/* FIXME: what is this value (0x0010)??? */
status1 |= DMA_RESUME | DMA_EPILOGUE_SOMETHING;
outw(status1, chip->codec_port+IDX_IO_REC_FLAGS);
#else
outw(0x00, chip->codec_port+IDX_IO_REC_FLAGS);
outw(DMA_PLAY_SOMETHING1, chip->codec_port+IDX_IO_REC_FLAGS);
outw(DMA_PLAY_SOMETHING1|DMA_PLAY_SOMETHING2, chip->codec_port+IDX_IO_REC_FLAGS);
outw(DMA_RESUME|SOMETHING_ALMOST_ALWAYS_SET|DMA_EPILOGUE_SOMETHING|DMA_SOMETHING_ELSE, chip->codec_port+IDX_IO_REC_FLAGS);
#endif
spin_unlock_irqrestore(&chip->reg_lock, flags);
snd_azf3328_dbgio(chip, "trigger2");
chip->is_playing = 1;
break;
case SNDRV_PCM_TRIGGER_STOP:
spin_lock_irqsave(&chip->reg_lock, flags);
/* stop recording */
status1 = inw(chip->codec_port+IDX_IO_REC_FLAGS);
status1 &= ~DMA_RESUME;
outw(status1, chip->codec_port+IDX_IO_REC_FLAGS);
status1 |= DMA_PLAY_SOMETHING1;
outw(status1, chip->codec_port+IDX_IO_REC_FLAGS);
status1 &= ~DMA_PLAY_SOMETHING1;
outw(status1, chip->codec_port+IDX_IO_REC_FLAGS);
spin_unlock_irqrestore(&chip->reg_lock, flags);
chip->is_playing = 0;
break;
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
snd_printk("FIXME: SNDRV_PCM_TRIGGER_PAUSE_PUSH NIY!\n");
break;
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
snd_printk("FIXME: SNDRV_PCM_TRIGGER_PAUSE_RELEASE NIY!\n");
break;
default:
return -EINVAL;
}
snd_azf3328_dbgcallleave();
return result;
}
static snd_pcm_uframes_t snd_azf3328_playback_pointer(snd_pcm_substream_t * substream)
{
azf3328_t *chip = snd_pcm_substream_chip(substream);
unsigned long bufptr, playptr;
unsigned long result;
snd_pcm_uframes_t frmres;
unsigned long flags;
spin_lock_irqsave(&chip->reg_lock, flags);
#if QUERY_HARDWARE
bufptr = inl(chip->codec_port+IDX_IO_PLAY_DMA_START_1);
#else
bufptr = substream->runtime->dma_addr;
#endif
playptr = inl(chip->codec_port+IDX_IO_PLAY_DMA_CURRPOS);
spin_unlock_irqrestore(&chip->reg_lock, flags);
result = playptr - bufptr;
frmres = bytes_to_frames( substream->runtime, result );
snd_azf3328_dbgplay("result %lx, playptr %lx (base %x), frames %ld\n", result, playptr, substream->runtime->dma_addr, frmres);
return frmres;
}
static snd_pcm_uframes_t snd_azf3328_capture_pointer(snd_pcm_substream_t * substream)
{
azf3328_t *chip = snd_pcm_substream_chip(substream);
unsigned long bufptr, recptr;
unsigned long result;
snd_pcm_uframes_t frmres;
unsigned long flags;
spin_lock_irqsave(&chip->reg_lock, flags);
#if QUERY_HARDWARE
bufptr = inl(chip->codec_port+IDX_IO_REC_DMA_START_1);
#else
bufptr = substream->runtime->dma_addr;
#endif
recptr = inl(chip->codec_port+IDX_IO_REC_DMA_CURRPOS);
spin_unlock_irqrestore(&chip->reg_lock, flags);
result = recptr - bufptr;
frmres = bytes_to_frames( substream->runtime, result );
snd_azf3328_dbgplay("result %lx, rec ptr %lx (base %x), frames %ld\n", result, recptr, substream->runtime->dma_addr, frmres);
return frmres;
}
static irqreturn_t snd_azf3328_interrupt(int irq, void *dev_id, struct pt_regs *regs)
{
azf3328_t *chip = snd_magic_cast(azf3328_t, dev_id, return IRQ_NONE);
unsigned int status, which;
static unsigned long count = 0;
status = inw(chip->codec_port+IDX_IO_IRQSTATUS);
/* fast path out, to ease interrupt sharing */
if (!(status & (IRQ_PLAYBACK|IRQ_RECORDING|IRQ_MPU401|IRQ_SOMEIRQ)))
return IRQ_NONE; /* must be interrupt for another device */
snd_azf3328_dbgplay("Interrupt %ld!\nIDX_IO_PLAY_FLAGS %04x, IDX_IO_PLAY_IRQMASK %04x, IDX_IO_IRQSTATUS %04x\n", count, inw(chip->codec_port+IDX_IO_PLAY_FLAGS), inw(chip->codec_port+IDX_IO_PLAY_IRQMASK), inw(chip->codec_port+IDX_IO_IRQSTATUS));
if (status & IRQ_PLAYBACK)
{
spin_lock(&chip->reg_lock);
which = inw(chip->codec_port+IDX_IO_PLAY_IRQMASK);
if (which & IRQ_FINISHED_PLAYBUF_1)
/* ack IRQ */
outw(which | IRQ_FINISHED_PLAYBUF_1, chip->codec_port+IDX_IO_PLAY_IRQMASK);
if (which & IRQ_FINISHED_PLAYBUF_2)
/* ack IRQ */
outw(which | IRQ_FINISHED_PLAYBUF_2, chip->codec_port+IDX_IO_PLAY_IRQMASK);
if (which & IRQ_PLAY_SOMETHING)
{
snd_azf3328_dbgplay("azt3328: unknown play IRQ type occurred, please report!\n");
}
if (chip->pcm && chip->playback_substream)
{
snd_azf3328_dbgplay("which %x, playptr %lx\n", which, inl(chip->codec_port+IDX_IO_PLAY_DMA_CURRPOS));
snd_pcm_period_elapsed(chip->playback_substream);
snd_azf3328_dbgplay("period done, playptr %lx.\n", inl(chip->codec_port+IDX_IO_PLAY_DMA_CURRPOS));
}
else
snd_azf3328_dbgplay("azt3328: ouch, irq handler problem!\n");
spin_unlock(&chip->reg_lock);
}
if (status & IRQ_RECORDING)
{
spin_lock(&chip->reg_lock);
which = inw(chip->codec_port+IDX_IO_REC_IRQMASK);
if (which & IRQ_FINISHED_RECBUF_1)
/* ack interrupt */
outw(which | IRQ_FINISHED_RECBUF_1, chip->codec_port+IDX_IO_REC_IRQMASK);
if (which & IRQ_FINISHED_RECBUF_2)
/* ack interrupt */
outw(which | IRQ_FINISHED_RECBUF_2, chip->codec_port+IDX_IO_REC_IRQMASK);
if (which & IRQ_REC_SOMETHING)
{
snd_azf3328_dbgplay("azt3328: unknown rec IRQ type occurred, please report!\n");
}
if (chip->pcm && chip->capture_substream)
{
snd_azf3328_dbgplay("which %x, recptr %lx\n", which, inl(chip->codec_port+IDX_IO_REC_DMA_CURRPOS));
spin_unlock(&chip->reg_lock);
snd_pcm_period_elapsed(chip->capture_substream);
spin_lock(&chip->reg_lock);
snd_azf3328_dbgplay("period done, recptr %lx.\n", inl(chip->codec_port+IDX_IO_REC_DMA_CURRPOS));
}
spin_unlock(&chip->reg_lock);
}
if (status & IRQ_MPU401)
snd_mpu401_uart_interrupt(irq, chip->rmidi->private_data, regs);
if (status & IRQ_SOMEIRQ)
snd_azf3328_dbgplay("azt3328: unknown IRQ type occurred, please report!\n");
count++;
return IRQ_HANDLED;
}
/*****************************************************************/
static snd_pcm_hardware_t snd_azf3328_playback =
{
/* FIXME!! Correct? */
.info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_MMAP_VALID),
.formats = SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_U8 |
SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_U16_LE,
.rates = SNDRV_PCM_RATE_8000_48000 | SNDRV_PCM_RATE_64000 | SNDRV_PCM_RATE_KNOT,
.rate_min = 5512,
.rate_max = 64000,
.channels_min = 1,
.channels_max = 2,
.buffer_bytes_max = 65536,
.period_bytes_min = 64,
.period_bytes_max = 65536,
.periods_min = 1,
.periods_max = 1024,
/* FIXME: maybe that card actually has a FIFO?
* Hmm, it seems newer revisions do have one, but we still don't know
* its size... */
.fifo_size = 0,
};
static snd_pcm_hardware_t snd_azf3328_capture =
{
/* FIXME */
.info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_MMAP_VALID),
.formats = SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_U8 |
SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_U16_LE,
.rates = SNDRV_PCM_RATE_8000_48000 | SNDRV_PCM_RATE_64000 | SNDRV_PCM_RATE_KNOT,
.rate_min = 5512,
.rate_max = 64000,
.channels_min = 1,
.channels_max = 2,
.buffer_bytes_max = 65536,
.period_bytes_min = 64,
.period_bytes_max = 65536,
.periods_min = 1,
.periods_max = 1024,
.fifo_size = 0,
};
static unsigned int snd_azf3328_fixed_rates[] = {
5512, 6620, 8000, 9600, 11025, 16000, 22050, 32000, 44100, 48000, 64000
};
static snd_pcm_hw_constraint_list_t snd_azf3328_hw_constraints_rates = {
.count = ARRAY_SIZE(snd_azf3328_fixed_rates),
.list = snd_azf3328_fixed_rates,
.mask = 0,
};
/*****************************************************************/
static int snd_azf3328_playback_open(snd_pcm_substream_t * substream)
{
azf3328_t *chip = snd_pcm_substream_chip(substream);
snd_pcm_runtime_t *runtime = substream->runtime;
snd_azf3328_dbgcallenter();
chip->playback_substream = substream;
runtime->hw = snd_azf3328_playback;
snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
&snd_azf3328_hw_constraints_rates);
snd_azf3328_dbgcallleave();
return 0;
}
static int snd_azf3328_capture_open(snd_pcm_substream_t * substream)
{
azf3328_t *chip = snd_pcm_substream_chip(substream);
snd_pcm_runtime_t *runtime = substream->runtime;
snd_azf3328_dbgcallenter();
chip->capture_substream = substream;
runtime->hw = snd_azf3328_capture;
snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
&snd_azf3328_hw_constraints_rates);
snd_azf3328_dbgcallleave();
return 0;
}
static int snd_azf3328_playback_close(snd_pcm_substream_t * substream)
{
azf3328_t *chip = snd_pcm_substream_chip(substream);
snd_azf3328_dbgcallenter();
chip->playback_substream = NULL;
snd_azf3328_dbgcallleave();
return 0;
}
static int snd_azf3328_capture_close(snd_pcm_substream_t * substream)
{
azf3328_t *chip = snd_pcm_substream_chip(substream);
snd_azf3328_dbgcallenter();
chip->capture_substream = NULL;
snd_azf3328_dbgcallleave();
return 0;
}
/******************************************************************/
static snd_pcm_ops_t snd_azf3328_playback_ops = {
.open = snd_azf3328_playback_open,
.close = snd_azf3328_playback_close,
.ioctl = snd_pcm_lib_ioctl,
.hw_params = snd_azf3328_hw_params,
.hw_free = snd_azf3328_hw_free,
.prepare = snd_azf3328_playback_prepare,
.trigger = snd_azf3328_playback_trigger,
.pointer = snd_azf3328_playback_pointer
};
static snd_pcm_ops_t snd_azf3328_capture_ops = {
.open = snd_azf3328_capture_open,
.close = snd_azf3328_capture_close,
.ioctl = snd_pcm_lib_ioctl,
.hw_params = snd_azf3328_hw_params,
.hw_free = snd_azf3328_hw_free,
.prepare = snd_azf3328_capture_prepare,
.trigger = snd_azf3328_capture_trigger,
.pointer = snd_azf3328_capture_pointer
};
static void snd_azf3328_pcm_free(snd_pcm_t *pcm)
{
azf3328_t *chip = snd_magic_cast(azf3328_t, pcm->private_data, return);
chip->pcm = NULL;
snd_pcm_lib_preallocate_free_for_all(pcm);
}
static int __devinit snd_azf3328_pcm(azf3328_t *chip, int device)
{
snd_pcm_t *pcm;
int err;
snd_azf3328_dbgcallenter();
if ((err = snd_pcm_new(chip->card, "AZF3328 DSP", device, 1, 1, &pcm)) < 0)
return err;
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_azf3328_playback_ops);
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_azf3328_capture_ops);
pcm->private_data = chip;
pcm->private_free = snd_azf3328_pcm_free;
pcm->info_flags = 0;
strcpy(pcm->name, chip->card->shortname);
chip->pcm = pcm;
snd_pcm_lib_preallocate_pci_pages_for_all(chip->pci, pcm, 64*1024, 64*1024);
snd_azf3328_dbgcallleave();
return 0;
}
/******************************************************************/
static int snd_azf3328_free(azf3328_t *chip)
{
if (chip->irq < 0)
goto __end_hw;
/* interrupt setup - mask everything */
/* FIXME */
synchronize_irq(chip->irq);
__end_hw:
if (chip->res_codec_port) {
release_resource(chip->res_codec_port);
kfree_nocheck(chip->res_codec_port);
}
if (chip->res_io2_port) {
release_resource(chip->res_io2_port);
kfree_nocheck(chip->res_io2_port);
}
if (chip->res_mpu_port) {
release_resource(chip->res_mpu_port);
kfree_nocheck(chip->res_mpu_port);
}
if (chip->res_synth_port) {
release_resource(chip->res_synth_port);
kfree_nocheck(chip->res_synth_port);
}
if (chip->res_mixer_port) {
release_resource(chip->res_mixer_port);
kfree_nocheck(chip->res_mixer_port);
}
if (chip->irq >= 0)
free_irq(chip->irq, (void *)chip);
snd_magic_kfree(chip);
return 0;
}
static int snd_azf3328_dev_free(snd_device_t *device)
{
azf3328_t *chip = snd_magic_cast(azf3328_t, device->device_data, return -ENXIO);
return snd_azf3328_free(chip);
}
#if 0
/* check whether a bit can be modified */
static void snd_azf3328_test_bit(unsigned int reg, int bit)
{
unsigned char val, valoff, valon;
val = inb(reg);
outb(val & ~(1 << bit), reg);
valoff = inb(reg);
outb(val|(1 << bit), reg);
valon = inb(reg);
outb(val, reg);
printk(KERN_ERR "reg %04x bit %d: %02x %02x %02x\n", reg, bit, val, valoff, valon);
}
#endif
static int __devinit snd_azf3328_create(snd_card_t * card,
struct pci_dev *pci,
unsigned long device_type,
azf3328_t ** rchip)
{
unsigned long flags;
azf3328_t *chip;
int err;
static snd_device_ops_t ops = {
.dev_free = snd_azf3328_dev_free,
};
u16 tmp;
*rchip = NULL;
if ((err = pci_enable_device(pci)) < 0)
return err;
chip = snd_magic_kcalloc(azf3328_t, 0, GFP_KERNEL);
if (chip == NULL)
return -ENOMEM;
spin_lock_init(&chip->reg_lock);
chip->card = card;
chip->pci = pci;
chip->irq = -1;
/* check if we can restrict PCI DMA transfers to 24 bits */
if (!pci_dma_supported(pci, 0x00ffffff)) {
snd_printk("architecture does not support 24bit PCI busmaster DMA\n");
return -ENXIO;
}
pci_set_dma_mask(pci, 0x00ffffff);
chip->codec_port = pci_resource_start(pci, 0);
if ((chip->res_codec_port = request_region(chip->codec_port, 0x80, "Aztech AZF3328 I/O")) == NULL) {
snd_azf3328_free(chip);
snd_printk("unable to grab I/O port at 0x%lx-0x%lx\n", chip->codec_port, chip->codec_port + 0x80 - 1);
return -EBUSY;
}
chip->io2_port = pci_resource_start(pci, 1);
if ((chip->res_io2_port = request_region(chip->io2_port, 0x08, "Aztech AZF3328 I/O 2")) == NULL) {
snd_azf3328_free(chip);
snd_printk("unable to grab I/O 2 port at 0x%lx-0x%lx\n", chip->io2_port, chip->io2_port + 0x08 - 1);
return -EBUSY;
}
chip->mpu_port = pci_resource_start(pci, 2);
if ((chip->res_mpu_port = request_region(chip->mpu_port, 0x04, "Aztech AZF3328 MPU401")) == NULL) {
snd_azf3328_free(chip);
snd_printk("unable to grab MPU401 port at 0x%lx-0x%lx\n", chip->mpu_port, chip->mpu_port + 0x04 - 1);
return -EBUSY;
}
chip->synth_port = pci_resource_start(pci, 3);
if ((chip->res_synth_port = request_region(chip->synth_port, 0x08, "Aztech AZF3328 OPL3")) == NULL) {
snd_azf3328_free(chip);
snd_printk("unable to grab OPL3 port at 0x%lx-0x%lx\n", chip->synth_port, chip->synth_port + 0x08 - 1);
return -EBUSY;
}
chip->mixer_port = pci_resource_start(pci, 4);
if ((chip->res_mixer_port = request_region(chip->mixer_port, 0x40, "Aztech AZF3328 Mixer")) == NULL) {
snd_azf3328_free(chip);
snd_printk("unable to grab mixer port at 0x%lx-0x%lx\n", chip->mixer_port, chip->mixer_port + 0x40 - 1);
return -EBUSY;
}
if (request_irq(pci->irq, snd_azf3328_interrupt, SA_INTERRUPT|SA_SHIRQ, card->shortname, (void *)chip)) {
snd_azf3328_free(chip);
snd_printk("unable to grab IRQ %d\n", pci->irq);
return -EBUSY;
}
chip->irq = pci->irq;
pci_set_master(pci);
synchronize_irq(chip->irq);
snd_azf3328_dbgmisc("codec_port 0x%lx, io2_port 0x%lx, mpu_port 0x%lx, synth_port 0x%lx, mixer_port 0x%lx, irq %d\n", chip->codec_port, chip->io2_port, chip->mpu_port, chip->synth_port, chip->mixer_port, chip->irq);
snd_azf3328_dbgmisc("io2 %02x %02x %02x %02x %02x %02x\n", snd_azf3328_io2_read(chip, 0), snd_azf3328_io2_read(chip, 1), snd_azf3328_io2_read(chip, 2), snd_azf3328_io2_read(chip, 3), snd_azf3328_io2_read(chip, 4), snd_azf3328_io2_read(chip, 5));
for (tmp=0; tmp <= 0x01; tmp += 1)
snd_azf3328_dbgmisc("0x%02x: opl 0x%04x, mpu300 0x%04x, mpu310 0x%04x, mpu320 0x%04x, mpu330 0x%04x\n", tmp, inb(0x388 + tmp), inb(0x300 + tmp), inb(0x310 + tmp), inb(0x320 + tmp), inb(0x330 + tmp));
/* create mixer interface & switches */
if ((err = snd_azf3328_mixer_new(chip)) < 0)
return err;
if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops)) < 0) {
snd_azf3328_free(chip);
return err;
}
#if 0
/* set very low bitrate to reduce noise and power consumption? */
snd_azf3328_setfmt(chip, IDX_IO_PLAY_SOUNDFORMAT, 5512, 8, 1);
#endif
/* standard chip init stuff */
spin_lock_irqsave(&chip->reg_lock, flags);
outb(DMA_PLAY_SOMETHING2|DMA_EPILOGUE_SOMETHING|DMA_SOMETHING_ELSE, chip->codec_port + IDX_IO_PLAY_FLAGS);
outb(DMA_PLAY_SOMETHING2|DMA_EPILOGUE_SOMETHING|DMA_SOMETHING_ELSE, chip->codec_port + IDX_IO_SOMETHING_FLAGS);
outb(DMA_PLAY_SOMETHING2|DMA_EPILOGUE_SOMETHING|DMA_SOMETHING_ELSE, chip->codec_port + IDX_IO_REC_FLAGS);
outb(0x0, chip->codec_port + IDX_IO_IRQ63H);
spin_unlock_irqrestore(&chip->reg_lock, flags);
*rchip = chip;
return 0;
}
static void __devinit snd_azf3328_config_joystick(azf3328_t *chip, int joystick)
{
int i, detected = 0, activate = 0;
char *msg = NULL;
unsigned char val;
if (joystick == -1) /* auto detection/activation */
{
for (i=0x200; i <= 0x207; i++)
if (inb(i) != 0xff)
detected = 1; /* other joy found, don't activate */
}
if ((joystick == -1) && (detected == 1))
{
activate = 0;
msg = "DISABLED (address occupied by another joystick port)";
}
else
if ((joystick == -1) && (detected == 0))
{
activate = 1;
msg = "ENABLED (via autodetect)";
}
else
if (joystick == 0)
{
activate = 0;
msg = "DISABLED (forced)";
}
else
if (joystick == 1)
{
activate = 1;
msg = "ENABLED (Warning: forced!)";
}
val = inb(chip->io2_port + IDX_IO2_LEGACY_ADDR);
if (activate)
val |= LEGACY_JOY;
else
val &= ~LEGACY_JOY;
outb(val, chip->io2_port + IDX_IO2_LEGACY_ADDR);
#ifdef MODULE
printk("azt3328: Joystick port: %s.\n", msg);
#endif
}
static int __devinit snd_azf3328_probe(struct pci_dev *pci,
const struct pci_device_id *pci_id)
{
static int dev;
snd_card_t *card;
azf3328_t *chip;
opl3_t *opl3;
int err;
snd_azf3328_dbgcallenter();
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;
strcpy(card->driver, "AZF3328");
strcpy(card->shortname, "Aztech AZF3328 (PCI168)");
if ((err = snd_azf3328_create(card, pci, pci_id->driver_data, &chip)) < 0) {
snd_card_free(card);
return err;
}
if ((err = snd_mpu401_uart_new( card, 0, MPU401_HW_MPU401,
chip->mpu_port, 1, pci->irq, 0,
&chip->rmidi)) < 0) {
snd_card_free(card);
snd_printk("azf3328: no MPU-401 device at 0x%lx?\n", chip->mpu_port);
return err;
}
if ((err = snd_azf3328_pcm(chip, 0)) < 0) {
snd_card_free(card);
return err;
}
if (snd_opl3_create(card, chip->synth_port, chip->synth_port+2,
OPL3_HW_AUTO, 1, &opl3) < 0) {
snd_printk("azf3328: no OPL3 device at 0x%lx-0x%lx?\n",
chip->synth_port, chip->synth_port+2 );
} else {
if ((err = snd_opl3_hwdep_new(opl3, 0, 1, NULL)) < 0) {
snd_card_free(card);
return err;
}
}
snd_azf3328_dbgio(chip, "create");
sprintf(card->longname, "%s at 0x%lx, irq %i",
card->shortname, chip->codec_port, chip->irq);
if ((err = snd_card_register(card)) < 0) {
snd_card_free(card);
return err;
}
#ifdef MODULE
printk(
"azt3328: Experimental driver for Aztech AZF3328-based soundcards such as PCI168.\n"
"azt3328: ZERO support from Aztech: you might think hard about future purchase.\n"
"azt3328: Feel free to contact hw7oshyuv3001@sneakemail.com for bug reports etc.!\n");
#endif
snd_azf3328_config_joystick(chip, joystick[dev]);
pci_set_drvdata(pci, chip);
dev++;
snd_azf3328_dbgcallleave();
return 0;
}
static void __devexit snd_azf3328_remove(struct pci_dev *pci)
{
azf3328_t *chip = snd_magic_cast(azf3328_t, pci_get_drvdata(pci), return);
snd_azf3328_dbgcallenter();
/* reset (close) mixer */
snd_azf3328_mixer_set_mute(chip, IDX_MIXER_PLAY_MASTER, 1); /* first mute master volume */
snd_azf3328_mixer_write(chip, IDX_MIXER_RESET, 0x0, WORD_VALUE);
if (chip)
snd_card_free(chip->card);
pci_set_drvdata(pci, NULL);
snd_azf3328_dbgcallleave();
}
static struct pci_driver driver = {
.name = "AZF3328",
.id_table = snd_azf3328_ids,
.probe = snd_azf3328_probe,
.remove = __devexit_p(snd_azf3328_remove),
};
static int __init alsa_card_azf3328_init(void)
{
int err;
snd_azf3328_dbgcallenter();
if ((err = pci_module_init(&driver)) < 0)
{
#ifdef MODULE
printk(KERN_ERR "azt3328: no AZF3328 based soundcards found or device busy\n");
#endif
return err;
}
snd_azf3328_dbgcallleave();
return 0;
}
static void __exit alsa_card_azf3328_exit(void)
{
snd_azf3328_dbgcallenter();
pci_unregister_driver(&driver);
snd_azf3328_dbgcallleave();
}
module_init(alsa_card_azf3328_init)
module_exit(alsa_card_azf3328_exit)
#ifndef MODULE
/* format is: snd-azf3328=enable,index,id */
static int __init alsa_card_azf3328_setup(char *str)
{
static unsigned __initdata nr_dev = 0;
snd_azf3328_dbgcallenter();
if (nr_dev >= SNDRV_CARDS)
return 0;
(void)(get_option(&str,&enable[nr_dev]) == 2 &&
get_option(&str,&index[nr_dev]) == 2 &&
get_id(&str,&id[nr_dev]) == 2);
nr_dev++;
snd_azf3328_dbgcallleave();
return 1;
}
__setup("snd-azf3328=", alsa_card_azf3328_setup);
#endif /* ifndef MODULE */
#ifndef __SOUND_AZF3328_H
#define __SOUND_AZF3328_H
/* type argument to use for the I/O functions */
#define WORD_VALUE 0x1000
#define DWORD_VALUE 0x2000
#define BYTE_VALUE 0x4000
/*** main I/O area port indices ***/
/* (only 0x70 of 0x80 bytes saved/restored by Windows driver) */
/* the driver initialisation suggests a layout of 3 main areas:
* from 0x00 (playback), from 0x20 (recording) and from 0x40 (maybe DirectX
* timer ???). and probably another area from 0x60 to 0x6f
* (IRQ management, power management etc. ???). */
/* playback area */
#define IDX_IO_PLAY_FLAGS 0x00
/* able to reactivate output after output muting due to 8/16bit
* output change, just like 0x0002.
* 0x0001 is the only bit that's able to start the DMA counter */
#define DMA_RESUME 0x0001 /* paused if cleared ? */
/* 0x0002 *temporarily* set during DMA stopping. hmm
* both 0x0002 and 0x0004 set in playback setup. */
/* able to reactivate output after output muting due to 8/16bit
* output change, just like 0x0001. */
#define DMA_PLAY_SOMETHING1 0x0002 /* \ alternated (toggled) */
/* 0x0004: NOT able to reactivate output */
#define DMA_PLAY_SOMETHING2 0x0004 /* / bits */
#define SOMETHING_ALMOST_ALWAYS_SET 0x0008 /* ???; can be modified */
#define DMA_EPILOGUE_SOMETHING 0x0010
#define DMA_SOMETHING_ELSE 0x0020 /* ??? */
#define SOMETHING_UNMODIFIABLE 0xffc0 /* unused ? not modifiable */
#define IDX_IO_PLAY_IRQMASK 0x02
/* write back to flags in case flags are set, in order to ACK IRQ in handler
* (bit 1 of port 0x64 indicates interrupt for one of these three types)
* sometimes in this case it just writes 0xffff to globally ACK all IRQs
* settings written are not reflected when reading back, though.
* seems to be IRQ, too (frequently used: port |= 0x07 !), but who knows ? */
#define IRQ_PLAY_SOMETHING 0x0001 /* something & ACK */
#define IRQ_FINISHED_PLAYBUF_1 0x0002 /* 1st dmabuf finished & ACK */
#define IRQ_FINISHED_PLAYBUF_2 0x0004 /* 2nd dmabuf finished & ACK */
#define IRQMASK_SOME_STATUS_1 0x0008 /* \ related bits */
#define IRQMASK_SOME_STATUS_2 0x0010 /* / (checked together in loop) */
#define IRQMASK_UNMODIFIABLE 0xffe0 /* unused ? not modifiable */
#define IDX_IO_PLAY_DMA_START_1 0x04 /* start address of 1st DMA play area */
#define IDX_IO_PLAY_DMA_START_2 0x08 /* start address of 2nd DMA play area */
#define IDX_IO_PLAY_DMA_LEN_1 0x0c /* length of 1st DMA play area */
#define IDX_IO_PLAY_DMA_LEN_2 0x0e /* length of 2nd DMA play area */
#define IDX_IO_PLAY_DMA_CURRPOS 0x10 /* current DMA position */
#define IDX_IO_PLAY_DMA_CURROFS 0x14 /* offset within current DMA play area */
#define IDX_IO_PLAY_SOUNDFORMAT 0x16
/* all unspecified bits can't be modified */
#define SOUNDFORMAT_FREQUENCY_MASK 0x000f
/* all _SUSPECTED_ values are not used by Windows drivers, so we don't
* have any hard facts, only rough measurements */
#define SOUNDFORMAT_FREQ_SUSPECTED_4000 0x0c
#define SOUNDFORMAT_FREQ_SUSPECTED_4800 0x0a
#define SOUNDFORMAT_FREQ_5510 0x0d
#define SOUNDFORMAT_FREQ_6620 0x0b
#define SOUNDFORMAT_FREQ_8000 0x00 /* also 0x0e ? */
#define SOUNDFORMAT_FREQ_9600 0x08
#define SOUNDFORMAT_FREQ_SUSPECTED_12000 0x09
#define SOUNDFORMAT_FREQ_11025 0x01 /* also 0x0f ? */
#define SOUNDFORMAT_FREQ_16000 0x02
#define SOUNDFORMAT_FREQ_22050 0x03
#define SOUNDFORMAT_FREQ_32000 0x04
#define SOUNDFORMAT_FREQ_44100 0x05
#define SOUNDFORMAT_FREQ_48000 0x06
#define SOUNDFORMAT_FREQ_SUSPECTED_64000 0x07
#define SOUNDFORMAT_FLAG_16BIT 0x0010
#define SOUNDFORMAT_FLAG_2CHANNELS 0x0020
/* recording area (see also: playback bit flag definitions) */
#define IDX_IO_REC_FLAGS 0x20 /* ?? */
#define IDX_IO_REC_IRQMASK 0x22 /* ?? */
#define IRQ_REC_SOMETHING 0x0001 /* something & ACK */
#define IRQ_FINISHED_RECBUF_1 0x0002 /* 1st dmabuf finished & ACK */
#define IRQ_FINISHED_RECBUF_2 0x0004 /* 2nd dmabuf finished & ACK */
/* hmm, maybe these are just the corresponding *recording* flags ?
* but OTOH they are most likely at port 0x22 instead */
#define IRQMASK_SOME_STATUS_1 0x0008 /* \ related bits */
#define IRQMASK_SOME_STATUS_2 0x0010 /* / (checked together in loop) */
#define IDX_IO_REC_DMA_START_1 0x24
#define IDX_IO_REC_DMA_START_2 0x28
#define IDX_IO_REC_DMA_LEN_1 0x2c
#define IDX_IO_REC_DMA_LEN_2 0x2e
#define IDX_IO_REC_DMA_CURRPOS 0x30
#define IDX_IO_REC_DMA_CURROFS 0x34
#define IDX_IO_REC_SOUNDFORMAT 0x36
/* some third area ? (after playback and recording) */
#define IDX_IO_SOMETHING_FLAGS 0x40 /* gets set to 0x34 just like port 0x0 and 0x20 on card init */
/* general */
#define IDX_IO_60H 0x60 /* writing 0xffff returns 0xffff */
#define IDX_IO_62H 0x62 /* writing to WORD 0x0062 can hang the box ! --> responsible for IRQ management as a whole ?? */
#define IDX_IO_IRQ63H 0x63 /* FIXME !! */
#define IO_IRQ63H_SOMETHING 0x04 /* being set in IRQ handler in case port 0x00 had 0x0020 set upon IRQ handler */
#define IDX_IO_IRQSTATUS 0x64
#define IRQ_PLAYBACK 0x0001
#define IRQ_RECORDING 0x0002
#define IRQ_MPU401 0x0010
#define IRQ_SOMEIRQ 0x0020 /* ???? */
#define IRQ_WHO_KNOWS_UNUSED 0x00e0 /* probably unused */
#define IDX_IO_66H 0x66 /* writing 0xffff returns 0x0000 */
#define IDX_IO_SOME_VALUE 0x68 /* this is always set to 0x3ff, and writable; maybe some buffer limit, but I couldn't find out more */
#define IDX_IO_6AH 0x6A /* this WORD can be set to have bits 0x0028 activated; actually inhibits PCM playback !!! maybe power management ?? */
#define IDX_IO_6CH 0x6C /* this WORD can have all its bits activated ? */
#define IDX_IO_6EH 0x6E /* writing 0xffff returns 0x83fe */
/* further I/O indices not saved/restored, so probably not used */
/*** I/O 2 area port indices ***/
/* (only 0x06 of 0x08 bytes saved/restored by Windows driver) */
#define IDX_IO2_LEGACY_ADDR 0x04
#define LEGACY_SOMETHING 0x01 /* OPL3 ?? */
#define LEGACY_JOY 0x08
/*** mixer I/O area port indices ***/
/* (only 0x22 of 0x40 bytes saved/restored by Windows driver)
* generally spoken: AC97 register index = AZF3328 mixer reg index + 2
* (in other words: AZF3328 NOT fully AC97 compliant) */
#define MIXER_VOLUME_RIGHT_MASK 0x001f
#define MIXER_VOLUME_LEFT_MASK 0x1f00
#define MIXER_MUTE_MASK 0x8000
#define IDX_MIXER_RESET 0x00 /* does NOT seem to have AC97 ID bits */
#define IDX_MIXER_PLAY_MASTER 0x02
#define IDX_MIXER_MODEMOUT 0x04
#define IDX_MIXER_BASSTREBLE 0x06
#define MIXER_BASSTREBLE_TREBLE_VOLUME_MASK 0x000e
#define MIXER_BASSTREBLE_BASS_VOLUME_MASK 0x0e00
#define IDX_MIXER_PCBEEP 0x08
#define IDX_MIXER_MODEMIN 0x0a
#define IDX_MIXER_MIC 0x0c
#define MIXER_MIC_MICGAIN_20DB_ENHANCEMENT_MASK 0x0040
#define IDX_MIXER_LINEIN 0x0e
#define IDX_MIXER_CDAUDIO 0x10
#define IDX_MIXER_VIDEO 0x12
#define IDX_MIXER_AUX 0x14
#define IDX_MIXER_WAVEOUT 0x16
#define IDX_MIXER_FMSYNTH 0x18
#define IDX_MIXER_REC_SELECT 0x1a
#define MIXER_REC_SELECT_MIC 0x00
#define MIXER_REC_SELECT_CD 0x01
#define MIXER_REC_SELECT_VIDEO 0x02
#define MIXER_REC_SELECT_AUX 0x03
#define MIXER_REC_SELECT_LINEIN 0x04
#define MIXER_REC_SELECT_MIXSTEREO 0x05
#define MIXER_REC_SELECT_MIXMONO 0x06
#define MIXER_REC_SELECT_MONOIN 0x07
#define IDX_MIXER_REC_VOLUME 0x1c
#define IDX_MIXER_ADVCTL1 0x1e
/* unlisted bits are unmodifiable */
#define MIXER_ADVCTL1_3DWIDTH_MASK 0x000e
#define MIXER_ADVCTL1_HIFI3D_MASK 0x0300
#define IDX_MIXER_ADVCTL2 0x20 /* resembles AC97_GENERAL_PURPOSE reg ! */
/* unlisted bits are unmodifiable */
#define MIXER_ADVCTL2_BIT7 0x0080 /* WaveOut 3D Bypass ? mutes WaveOut at LineOut */
#define MIXER_ADVCTL2_BIT8 0x0100 /* is this Modem Out Select ? */
#define MIXER_ADVCTL2_BIT9 0x0200 /* Mono Select Source ? */
#define MIXER_ADVCTL2_BIT13 0x2000 /* 3D enable ? */
#define MIXER_ADVCTL2_BIT15 0x8000 /* unknown */
#define IDX_MIXER_SOMETHING30H 0x30 /* used, but unknown ??? */
/* driver internal flags */
#define SET_CHAN_LEFT 1
#define SET_CHAN_RIGHT 2
#endif /* __SOUND_AZF3328_H */
......@@ -1634,8 +1634,10 @@ static int __devinit snd_ensoniq_1370_mixer(ensoniq_t * ensoniq)
/* try reset AK4531 */
outw(ES_1370_CODEC_WRITE(AK4531_RESET, 0x02), ES_REG(ensoniq, 1370_CODEC));
inw(ES_REG(ensoniq, 1370_CODEC));
udelay(100);
outw(ES_1370_CODEC_WRITE(AK4531_RESET, 0x03), ES_REG(ensoniq, 1370_CODEC));
inw(ES_REG(ensoniq, 1370_CODEC));
udelay(100);
memset(&ak4531, 0, sizeof(ak4531));
......@@ -1975,6 +1977,7 @@ static int __devinit snd_ensoniq_create(snd_card_t * card,
}
/* AC'97 warm reset to start the bitclk */
outl(ensoniq->ctrl | ES_1371_SYNC_RES, ES_REG(ensoniq, CONTROL));
inl(ES_REG(ensoniq, CONTROL));
udelay(20);
outl(ensoniq->ctrl, ES_REG(ensoniq, CONTROL));
/* Init the sample rate converter */
......
......@@ -959,6 +959,7 @@ static int __devinit snd_fm801_create(snd_card_t * card,
/* codec cold reset + AC'97 warm reset */
outw((1<<5)|(1<<6), FM801_REG(chip, CODEC_CTRL));
inw(FM801_REG(chip, CODEC_CTRL)); /* flush posting data */
udelay(100);
outw(0, FM801_REG(chip, CODEC_CTRL));
......
......@@ -4,7 +4,7 @@
#
snd-ice1712-objs := ice1712.o delta.o hoontech.o ews.o ak4xxx.o
snd-ice1724-objs := ice1724.o amp.o revo.o ak4xxx.o
snd-ice1724-objs := ice1724.o amp.o revo.o aureon.o ak4xxx.o
# Toplevel Module Dependency
obj-$(CONFIG_SND_ICE1712) += snd-ice1712.o
......
......@@ -97,17 +97,6 @@ static void snd_ice1712_akm4xxx_write(akm4xxx_t *ak, int chip,
udelay(1);
}
/* save the data */
if (ak->type == SND_AK4524 || ak->type == SND_AK4528) {
if ((addr != 0x04 && addr != 0x05) || (data & 0x80) == 0)
ak->images[chip][addr] = data;
else
ak->ipga_gain[chip][addr-4] = data;
} else {
/* AK4529, or else */
ak->images[chip][addr] = data;
}
if (priv->cs_mask == priv->cs_addr) {
if (priv->cif) {
/* assert a cs pulse to trigger */
......
/*
* ALSA driver for ICEnsemble VT1724 (Envy24HT)
*
* Lowlevel functions for Terratec Aureon cards
*
* Copyright (c) 2003 Takashi Iwai <tiwai@suse.de>
*
* 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
*
*
* NOTES:
*
* - we reuse the akm4xxx_t record for storing the wm8770 codec data.
* both wm and akm codecs are pretty similar, so we can integrate
* both controls in the future, once if wm codecs are reused in
* many boards.
*
* - writing over SPI is implemented but reading is not yet.
* the SPDIF-in channel status, etc. can be read from CS chip.
*
* - DAC digital volumes are not implemented in the mixer.
* if they show better response than DAC analog volumes, we can use them
* instead.
*
* - Aureon boards are equipped with AC97 codec, too. it's used to do
* the analog mixing but not easily controllable (it's not connected
* directly from envy24ht chip). so let's leave it as it is.
*
*/
#include <sound/driver.h>
#include <asm/io.h>
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <sound/core.h>
#include "ice1712.h"
#include "envy24ht.h"
#include "aureon.h"
/* WM8770 registers */
#define WM_DAC_ATTEN 0x00 /* DAC1-8 analog attenuation */
#define WM_DAC_MASTER_ATTEN 0x08 /* DAC master analog attenuation */
#define WM_DAC_DIG_ATTEN 0x09 /* DAC1-8 digital attenuation */
#define WM_DAC_DIG_MATER_ATTEN 0x11 /* DAC master digital attenuation */
#define WM_PHASE_SWAP 0x12 /* DAC phase */
#define WM_DAC_CTRL1 0x13 /* DAC control bits */
#define WM_MUTE 0x14 /* mute controls */
#define WM_DAC_CTRL2 0x15 /* de-emphasis and zefo-flag */
#define WM_INT_CTRL 0x16 /* interface control */
#define WM_MASTER 0x17 /* master clock and mode */
#define WM_POWERDOWN 0x18 /* power-down controls */
#define WM_ADC_GAIN 0x19 /* ADC gain L(19)/R(1a) */
#define WM_ADC_MUX 0x1b /* input MUX */
#define WM_OUT_MUX1 0x1c /* output MUX */
#define WM_OUT_MUX2 0x1e /* output MUX */
#define WM_RESET 0x1f /* software reset */
/*
* write data in the SPI mode
*/
static void aureon_spi_write(ice1712_t *ice, unsigned int cs, unsigned int data, int bits)
{
unsigned int tmp;
int i;
tmp = snd_ice1712_gpio_read(ice);
snd_ice1712_gpio_set_mask(ice, ~(AUREON_WM_RW|AUREON_WM_DATA|AUREON_WM_CLK|
AUREON_WM_CS|AUREON_CS8415_CS));
tmp |= AUREON_WM_RW;
tmp &= ~cs;
snd_ice1712_gpio_write(ice, tmp);
udelay(1);
for (i = bits - 1; i >= 0; i--) {
tmp &= ~AUREON_WM_CLK;
snd_ice1712_gpio_write(ice, tmp);
udelay(1);
if (data & (1 << i))
tmp |= AUREON_WM_DATA;
else
tmp &= ~AUREON_WM_DATA;
snd_ice1712_gpio_write(ice, tmp);
udelay(1);
tmp |= AUREON_WM_CLK;
snd_ice1712_gpio_write(ice, tmp);
udelay(1);
}
tmp &= ~AUREON_WM_CLK;
tmp |= cs;
snd_ice1712_gpio_write(ice, tmp);
udelay(1);
tmp |= AUREON_WM_CLK;
snd_ice1712_gpio_write(ice, tmp);
udelay(1);
}
/*
* get the current register value of WM codec
*/
static unsigned short wm_get(ice1712_t *ice, int reg)
{
reg <<= 1;
return ((unsigned short)ice->akm[0].images[reg] << 8) |
ice->akm[0].images[reg + 1];
}
/*
* set the register value of WM codec and remember it
*/
static void wm_put(ice1712_t *ice, int reg, unsigned short val)
{
aureon_spi_write(ice, AUREON_WM_CS, (reg << 9) | (val & 0x1ff), 16);
reg <<= 1;
ice->akm[0].images[reg] = val >> 8;
ice->akm[0].images[reg + 1] = val;
}
/*
* DAC volume attenuation mixer control
*/
static int wm_dac_vol_info(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t *uinfo)
{
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
uinfo->count = 1;
uinfo->value.integer.min = 0; /* mute */
uinfo->value.integer.max = 101; /* 0dB */
return 0;
}
static int wm_dac_vol_get(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
{
ice1712_t *ice = snd_kcontrol_chip(kcontrol);
int idx;
unsigned short vol;
down(&ice->gpio_mutex);
if (kcontrol->private_value)
idx = WM_DAC_MASTER_ATTEN;
else
idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id) + WM_DAC_ATTEN;
vol = wm_get(ice, idx) & 0x7f;
if (vol <= 0x1a)
ucontrol->value.integer.value[0] = 0;
else
ucontrol->value.integer.value[0] = vol - 0x1a;
up(&ice->gpio_mutex);
return 0;
}
static int wm_dac_vol_put(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
{
ice1712_t *ice = snd_kcontrol_chip(kcontrol);
int idx;
unsigned short ovol, nvol;
int change;
snd_ice1712_save_gpio_status(ice);
if (kcontrol->private_value)
idx = WM_DAC_MASTER_ATTEN;
else
idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id) + WM_DAC_ATTEN;
nvol = ucontrol->value.integer.value[0] + 0x1a;
ovol = wm_get(ice, idx) & 0x7f;
change = (ovol != nvol);
if (change) {
if (nvol <= 0x1a && ovol <= 0x1a)
change = 0;
else
wm_put(ice, idx, nvol | 0x100);
}
snd_ice1712_restore_gpio_status(ice);
return change;
}
/*
* ADC gain mixer control
*/
static int wm_adc_vol_info(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t *uinfo)
{
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
uinfo->count = 1;
uinfo->value.integer.min = 0; /* -12dB */
uinfo->value.integer.max = 0x1f; /* 19dB */
return 0;
}
static int wm_adc_vol_get(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
{
ice1712_t *ice = snd_kcontrol_chip(kcontrol);
int idx;
unsigned short vol;
down(&ice->gpio_mutex);
idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id) + WM_ADC_GAIN;
vol = wm_get(ice, idx) & 0x1f;
ucontrol->value.integer.value[0] = vol;
up(&ice->gpio_mutex);
return 0;
}
static int wm_adc_vol_put(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
{
ice1712_t *ice = snd_kcontrol_chip(kcontrol);
int idx;
unsigned short ovol, nvol;
int change;
snd_ice1712_save_gpio_status(ice);
idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id) + WM_ADC_GAIN;
nvol = ucontrol->value.integer.value[0];
ovol = wm_get(ice, idx) & 0x1f;
change = (ovol != nvol);
if (change)
wm_put(ice, idx, nvol);
snd_ice1712_restore_gpio_status(ice);
return change;
}
/*
* ADC input mux mixer control
*/
static int wm_adc_mux_info(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t *uinfo)
{
static char *texts[] = {
"CD Left",
"CD Right",
"Aux Left",
"Aux Right",
"Line Left",
"Line Right",
"Mic Left",
"Mic Right",
};
uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
uinfo->count = 2;
uinfo->value.enumerated.items = 8;
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 wm_adc_mux_get(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t *ucontrol)
{
ice1712_t *ice = snd_kcontrol_chip(kcontrol);
unsigned short val;
down(&ice->gpio_mutex);
val = wm_get(ice, WM_ADC_MUX);
ucontrol->value.integer.value[0] = val & 7;
ucontrol->value.integer.value[1] = (val >> 4) & 7;
up(&ice->gpio_mutex);
return 0;
}
static int wm_adc_mux_put(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t *ucontrol)
{
ice1712_t *ice = snd_kcontrol_chip(kcontrol);
unsigned short oval, nval;
int change;
snd_ice1712_save_gpio_status(ice);
oval = wm_get(ice, WM_ADC_MUX);
nval = oval & ~0x77;
nval |= ucontrol->value.integer.value[0] & 7;
nval |= (ucontrol->value.integer.value[1] & 7) << 4;
change = (oval != nval);
if (change)
wm_put(ice, WM_ADC_MUX, nval);
snd_ice1712_restore_gpio_status(ice);
return 0;
}
/*
* mixers
*/
static snd_kcontrol_new_t aureon51_dac_control __devinitdata = {
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "DAC Volume",
.count = 6,
.info = wm_dac_vol_info,
.get = wm_dac_vol_get,
.put = wm_dac_vol_put,
};
static snd_kcontrol_new_t aureon71_dac_control __devinitdata = {
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "DAC Volume",
.count = 8,
.info = wm_dac_vol_info,
.get = wm_dac_vol_get,
.put = wm_dac_vol_put,
};
static snd_kcontrol_new_t wm_controls[] __devinitdata = {
{
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "Master Playback Volume",
.info = wm_dac_vol_info,
.get = wm_dac_vol_get,
.put = wm_dac_vol_put,
.private_value = 1,
},
{
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "ADC Volume",
.count = 2,
.info = wm_adc_vol_info,
.get = wm_adc_vol_get,
.put = wm_adc_vol_put,
},
{
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "Capture Route",
.info = wm_adc_mux_info,
.get = wm_adc_mux_get,
.put = wm_adc_mux_put,
},
};
static int __devinit aureon_add_controls(ice1712_t *ice)
{
unsigned int i;
int err;
if (ice->eeprom.subvendor == VT1724_SUBDEVICE_AUREON51_SKY)
err = snd_ctl_add(ice->card, snd_ctl_new1(&aureon51_dac_control, ice));
else
err = snd_ctl_add(ice->card, snd_ctl_new1(&aureon71_dac_control, ice));
if (err < 0)
return err;
for (i = 0; i < ARRAY_SIZE(wm_controls); i++) {
err = snd_ctl_add(ice->card, snd_ctl_new1(&wm_controls[i], ice));
if (err < 0)
return err;
}
return 0;
}
/*
* initialize the chip
*/
static int __devinit aureon_init(ice1712_t *ice)
{
static unsigned short wm_inits[] = {
0x16, 0x122, /* I2S, normal polarity, 24bit */
0x17, 0x022, /* 256fs, slave mode */
0x18, 0x000, /* All power-up */
0x00, 0, /* DAC1 analog mute */
0x01, 0, /* DAC2 analog mute */
0x02, 0, /* DAC3 analog mute */
0x03, 0, /* DAC4 analog mute */
0x04, 0, /* DAC5 analog mute */
0x05, 0, /* DAC6 analog mute */
0x06, 0, /* DAC7 analog mute */
0x07, 0, /* DAC8 analog mute */
0x08, 0x100, /* master analog mute */
0x09, 0xff, /* DAC1 digital full */
0x0a, 0xff, /* DAC2 digital full */
0x0b, 0xff, /* DAC3 digital full */
0x0c, 0xff, /* DAC4 digital full */
0x0d, 0xff, /* DAC5 digital full */
0x0e, 0xff, /* DAC6 digital full */
0x0f, 0xff, /* DAC7 digital full */
0x10, 0xff, /* DAC8 digital full */
0x11, 0x1ff, /* master digital full */
0x12, 0x000, /* phase normal */
0x13, 0x090, /* unmute DAC L/R */
0x14, 0x000, /* all unmute */
0x15, 0x000, /* no deemphasis, no ZFLG */
0x19, 0x000, /* -12dB ADC/L */
0x1a, 0x000, /* -12dB ADC/R */
0x1b, 0x000, /* ADC Mux */
0x1c, 0x009, /* Out Mux1 */
0x1d, 0x009, /* Out Mux2 */
};
static unsigned short cs_inits[] = {
0x0441, /* RUN */
0x0100, /* no mute */
0x0200, /* */
0x0600, /* slave, 24bit */
};
unsigned int tmp;
unsigned int i;
if (ice->eeprom.subvendor == VT1724_SUBDEVICE_AUREON51_SKY)
ice->num_total_dacs = 6;
else
ice->num_total_dacs = 8;
/* to remeber the register values */
ice->akm = snd_kcalloc(sizeof(akm4xxx_t), GFP_KERNEL);
if (! ice->akm)
return -ENOMEM;
ice->akm_codecs = 1;
snd_ice1712_gpio_set_dir(ice, 0xbfffff); /* fix this for the time being */
/* reset the wm codec as the SPI mode */
snd_ice1712_save_gpio_status(ice);
snd_ice1712_gpio_set_mask(ice, ~(AUREON_WM_RESET|AUREON_WM_CS|AUREON_CS8415_CS));
tmp = snd_ice1712_gpio_read(ice);
tmp &= ~AUREON_WM_RESET;
snd_ice1712_gpio_write(ice, tmp);
udelay(1);
tmp |= AUREON_WM_CS | AUREON_CS8415_CS;
snd_ice1712_gpio_write(ice, tmp);
udelay(1);
tmp |= AUREON_WM_RESET;
snd_ice1712_gpio_write(ice, tmp);
udelay(1);
/* initialize WM8770 codec */
for (i = 0; i < ARRAY_SIZE(wm_inits); i += 2)
wm_put(ice, wm_inits[i], wm_inits[i+1]);
/* initialize CS8415A codec */
for (i = 0; i < ARRAY_SIZE(cs_inits); i++)
aureon_spi_write(ice, AUREON_CS8415_CS,
cs_inits[i] | 0x200000, 24);
snd_ice1712_restore_gpio_status(ice);
return 0;
}
/*
* Aureon board don't provide the EEPROM data except for the vendor IDs.
* hence the driver needs to sets up it properly.
*/
static unsigned char aureon51_eeprom[] __devinitdata = {
0x12, /* SYSCONF: clock 512, mpu401, spdif-in/ADC, 3DACs */
0x80, /* ACLINK: I2S */
0xf8, /* I2S: vol, 96k, 24bit, 192k */
0xc2, /* SPDIF: out-en, out-int, spdif-in */
0xff, /* GPIO_DIR */
0xff, /* GPIO_DIR1 */
0xbf, /* GPIO_DIR2 */
0xff, /* GPIO_MASK */
0xff, /* GPIO_MASK1 */
0xff, /* GPIO_MASK2 */
0x00, /* GPIO_STATE */
0x00, /* GPIO_STATE1 */
0x00, /* GPIO_STATE2 */
};
static unsigned char aureon71_eeprom[] __devinitdata = {
0x13, /* SYSCONF: clock 512, mpu401, spdif-in/ADC, 4DACs */
0x80, /* ACLINK: I2S */
0xf8, /* I2S: vol, 96k, 24bit, 192k */
0xc2, /* SPDIF: out-en, out-int, spdif-in */
0xff, /* GPIO_DIR */
0xff, /* GPIO_DIR1 */
0xbf, /* GPIO_DIR2 */
0x00, /* GPIO_MASK */
0x00, /* GPIO_MASK1 */
0x00, /* GPIO_MASK2 */
0x00, /* GPIO_STATE */
0x00, /* GPIO_STATE1 */
0x00, /* GPIO_STATE2 */
};
/* entry point */
struct snd_ice1712_card_info snd_vt1724_aureon_cards[] __devinitdata = {
{
.subvendor = VT1724_SUBDEVICE_AUREON51_SKY,
.name = "Terratec Aureon 5.1-Sky",
.chip_init = aureon_init,
.build_controls = aureon_add_controls,
.eeprom_size = sizeof(aureon51_eeprom),
.eeprom_data = aureon51_eeprom,
},
{
.subvendor = VT1724_SUBDEVICE_AUREON71_SPACE,
.name = "Terratec Aureon 7.1-Space",
.chip_init = aureon_init,
.build_controls = aureon_add_controls,
.eeprom_size = sizeof(aureon71_eeprom),
.eeprom_data = aureon71_eeprom,
},
{ } /* terminator */
};
#ifndef __SOUND_AUREON_H
#define __SOUND_AUREON_H
/*
* ALSA driver for VIA VT1724 (Envy24HT)
*
* Lowlevel functions for Terratec Aureon cards
*
* Copyright (c) 2003 Takashi Iwai <tiwai@suse.de>
*
* 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
*
*/
#define AUREON_DEVICE_DESC "{Terratec,Aureon 5.1 Sky},"\
"{Terratec,Aureon 7.1 Space},"
#define VT1724_SUBDEVICE_AUREON51_SKY 0x3b154711 /* Aureon 5.1 Sky */
#define VT1724_SUBDEVICE_AUREON71_SPACE 0x3b154511 /* Aureon 7.1 Space */
extern struct snd_ice1712_card_info snd_vt1724_aureon_cards[];
/* GPIO bits */
#define AUREON_CS8415_CS (1 << 23)
#define AUREON_CS8415_CDTO (1 << 22)
#define AUREON_WM_RESET (1 << 20)
#define AUREON_WM_CLK (1 << 19)
#define AUREON_WM_DATA (1 << 18)
#define AUREON_WM_RW (1 << 17)
#define AUREON_AC97_RESET (1 << 16)
#define AUREON_DIGITAL_SEL1 (1 << 15)
#define AUREON_HP_SEL (1 << 14)
#define AUREON_WM_CS (1 << 12)
#endif /* __SOUND_AUREON_H */
......@@ -298,11 +298,13 @@ static snd_kcontrol_new_t snd_ice1712_mixer_digmix_route_ac97 __devinitdata = {
static void snd_ice1712_set_gpio_dir(ice1712_t *ice, unsigned int data)
{
snd_ice1712_write(ice, ICE1712_IREG_GPIO_DIRECTION, data);
inb(ICEREG(ice, DATA)); /* dummy read for pci-posting */
}
static void snd_ice1712_set_gpio_mask(ice1712_t *ice, unsigned int data)
{
snd_ice1712_write(ice, ICE1712_IREG_GPIO_WRITE_MASK, data);
inb(ICEREG(ice, DATA)); /* dummy read for pci-posting */
}
static unsigned int snd_ice1712_get_gpio_data(ice1712_t *ice)
......@@ -313,6 +315,7 @@ static unsigned int snd_ice1712_get_gpio_data(ice1712_t *ice)
static void snd_ice1712_set_gpio_data(ice1712_t *ice, unsigned int val)
{
snd_ice1712_write(ice, ICE1712_IREG_GPIO_DATA, val);
inb(ICEREG(ice, DATA)); /* dummy read for pci-posting */
}
......
......@@ -42,6 +42,7 @@
/* lowlevel routines */
#include "amp.h"
#include "revo.h"
#include "aureon.h"
MODULE_AUTHOR("Jaroslav Kysela <perex@suse.cz>");
MODULE_DESCRIPTION("ICEnsemble ICE1724 (Envy24HT)");
......@@ -50,6 +51,7 @@ MODULE_CLASSES("{sound}");
MODULE_DEVICES("{"
REVO_DEVICE_DESC
AMP_AUDIO2000_DEVICE_DESC
AUREON_DEVICE_DESC
"{VIA,VT1724},"
"{ICEnsemble,Generic ICE1724},"
"{ICEnsemble,Generic Envy24HT}}");
......@@ -172,6 +174,7 @@ static unsigned short snd_vt1724_ac97_read(ac97_t *ac97, unsigned short reg)
static void snd_vt1724_set_gpio_dir(ice1712_t *ice, unsigned int data)
{
outl(data, ICEREG1724(ice, GPIO_DIRECTION));
inw(ICEREG1724(ice, GPIO_DIRECTION)); /* dummy read for pci-posting */
}
/* set the gpio mask (0 = writable) */
......@@ -179,12 +182,14 @@ static void snd_vt1724_set_gpio_mask(ice1712_t *ice, unsigned int data)
{
outw(data, ICEREG1724(ice, GPIO_WRITE_MASK));
outb((data >> 16) & 0xff, ICEREG1724(ice, GPIO_WRITE_MASK_22));
inw(ICEREG1724(ice, GPIO_WRITE_MASK)); /* dummy read for pci-posting */
}
static void snd_vt1724_set_gpio_data(ice1712_t *ice, unsigned int data)
{
outw(data, ICEREG1724(ice, GPIO_DATA));
outb(data >> 16, ICEREG1724(ice, GPIO_DATA_22));
inw(ICEREG1724(ice, GPIO_DATA)); /* dummy read for pci-posting */
}
static unsigned int snd_vt1724_get_gpio_data(ice1712_t *ice)
......@@ -415,6 +420,7 @@ static void snd_vt1724_set_pro_rate(ice1712_t *ice, unsigned int rate, int force
val &= ~VT1724_MT_I2S_MCLK_128X; /* 256x MCLK */
if (val != old) {
outb(val, ICEMT1724(ice, I2S_FORMAT));
if (ice->eeprom.subvendor == VT1724_SUBDEVICE_REVOLUTION71) {
/* FIXME: is this revo only? */
/* assert PRST# to converters; MT05 bit 7 */
outb(inb(ICEMT1724(ice, AC97_CMD)) | 0x80, ICEMT1724(ice, AC97_CMD));
......@@ -425,6 +431,7 @@ static void snd_vt1724_set_pro_rate(ice1712_t *ice, unsigned int rate, int force
outb(inb(ICEMT1724(ice, AC97_CMD)) & ~0x80, ICEMT1724(ice, AC97_CMD));
}
}
}
spin_unlock_irqrestore(&ice->reg_lock, flags);
/* set up codecs */
......@@ -1549,6 +1556,7 @@ static struct snd_ice1712_card_info no_matched __devinitdata;
static struct snd_ice1712_card_info *card_tables[] __devinitdata = {
snd_vt1724_revo_cards,
snd_vt1724_amp_cards,
snd_vt1724_aureon_cards,
0,
};
......@@ -1572,6 +1580,7 @@ static int __devinit snd_vt1724_read_eeprom(ice1712_t *ice)
{
int dev = 0xa0; /* EEPROM device address */
unsigned int i, size;
struct snd_ice1712_card_info **tbl, *c;
if ((inb(ICEREG1724(ice, I2C_CTRL)) & VT1724_I2C_EEPROM) == 0) {
snd_printk("ICE1724 has not detected EEPROM\n");
......@@ -1581,6 +1590,23 @@ static int __devinit snd_vt1724_read_eeprom(ice1712_t *ice)
(snd_vt1724_read_i2c(ice, dev, 0x01) << 8) |
(snd_vt1724_read_i2c(ice, dev, 0x02) << 16) |
(snd_vt1724_read_i2c(ice, dev, 0x03) << 24);
/* if the EEPROM is given by the driver, use it */
for (tbl = card_tables; *tbl; tbl++) {
for (c = *tbl; c->subvendor; c++) {
if (c->subvendor == ice->eeprom.subvendor) {
if (! c->eeprom_size || ! c->eeprom_data)
goto found;
snd_printdd("using the defined eeprom..\n");
ice->eeprom.version = 2;
ice->eeprom.size = c->eeprom_size + 6;
memcpy(ice->eeprom.data, c->eeprom_data, c->eeprom_size);
goto read_skipped;
}
}
}
found:
ice->eeprom.size = snd_vt1724_read_i2c(ice, dev, 0x04);
if (ice->eeprom.size < 6)
ice->eeprom.size = 32;
......@@ -1597,6 +1623,7 @@ static int __devinit snd_vt1724_read_eeprom(ice1712_t *ice)
for (i = 0; i < size; i++)
ice->eeprom.data[i] = snd_vt1724_read_i2c(ice, dev, i + 6);
read_skipped:
ice->eeprom.gpiomask = eeprom_triple(ice, ICE_EEP2_GPIO_MASK);
ice->eeprom.gpiostate = eeprom_triple(ice, ICE_EEP2_GPIO_STATE);
ice->eeprom.gpiodir = eeprom_triple(ice, ICE_EEP2_GPIO_DIR);
......
......@@ -59,14 +59,14 @@ static void revo_set_rate_val(akm4xxx_t *ak, unsigned int rate)
reg = 1;
shift = 3;
}
tmp = ak->images[0][reg];
tmp = snd_akm4xxx_get(ak, 0, reg);
old = (tmp >> shift) & 0x03;
if (old == dfs)
return;
/* reset DFS */
snd_akm4xxx_reset(ak, 1);
tmp = ak->images[0][reg];
tmp = snd_akm4xxx_get(ak, 0, reg);
tmp &= ~(0x03 << shift);
tmp |= dfs << shift;
snd_akm4xxx_write(ak, 0, reg, tmp);
......@@ -121,6 +121,7 @@ static struct snd_ak4xxx_private akm_revo_surround_priv __devinitdata = {
static int __devinit revo_init(ice1712_t *ice)
{
akm4xxx_t *ak;
int err;
/* determine I2C, DACs and ADCs */
switch (ice->eeprom.subvendor) {
......@@ -139,8 +140,10 @@ static int __devinit revo_init(ice1712_t *ice)
ice->akm_codecs = 2;
switch (ice->eeprom.subvendor) {
case VT1724_SUBDEVICE_REVOLUTION71:
snd_ice1712_akm4xxx_init(ak, &akm_revo_front, &akm_revo_front_priv, ice);
snd_ice1712_akm4xxx_init(ak + 1, &akm_revo_surround, &akm_revo_surround_priv, ice);
if ((err = snd_ice1712_akm4xxx_init(ak, &akm_revo_front, &akm_revo_front_priv, ice)) < 0)
return err;
if ((err = snd_ice1712_akm4xxx_init(ak + 1, &akm_revo_surround, &akm_revo_surround_priv, ice)) < 0)
return err;
/* unmute all codecs */
snd_ice1712_gpio_write_bits(ice, VT1724_REVO_MUTE, VT1724_REVO_MUTE);
break;
......
......@@ -564,6 +564,7 @@ static void snd_via82xx_channel_reset(via82xx_t *chip, viadev_t *viadev)
{
outb(VIA_REG_CTRL_PAUSE | VIA_REG_CTRL_TERMINATE | VIA_REG_CTRL_RESET,
VIADEV_REG(viadev, OFFSET_CONTROL));
inb(VIADEV_REG(viadev, OFFSET_CONTROL));
udelay(50);
/* disable interrupts */
outb(0x00, VIADEV_REG(viadev, OFFSET_CONTROL));
......
......@@ -1211,8 +1211,11 @@ static void snd_pmac_suspend(pmac_t *chip)
spin_lock_irqsave(&chip->reg_lock, flags);
snd_pmac_beep_stop(chip);
spin_unlock_irqrestore(&chip->reg_lock, flags);
if (chip->irq >= 0)
disable_irq(chip->irq);
if (chip->tx_irq >= 0)
disable_irq(chip->tx_irq);
if (chip->rx_irq >= 0)
disable_irq(chip->rx_irq);
snd_pmac_sound_feature(chip, 0);
snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
......@@ -1237,8 +1240,11 @@ static void snd_pmac_resume(pmac_t *chip)
snd_pmac_pcm_set_format(chip);
if (chip->irq >= 0)
enable_irq(chip->irq);
if (chip->tx_irq >= 0)
enable_irq(chip->tx_irq);
if (chip->rx_irq >= 0)
enable_irq(chip->rx_irq);
snd_power_change_state(card, SNDRV_CTL_POWER_D0);
......
......@@ -27,6 +27,7 @@
#include <linux/kmod.h>
#include <linux/slab.h>
#include <linux/interrupt.h>
#include <linux/workqueue.h>
#include <sound/core.h>
#include <asm/io.h>
#include <asm/irq.h>
......@@ -79,7 +80,7 @@ typedef struct pmac_gpio {
int active_state;
} pmac_gpio_t;
typedef struct pmac_tumber_t {
typedef struct pmac_tumbler_t {
pmac_keywest_t i2c;
pmac_gpio_t audio_reset;
pmac_gpio_t amp_mute;
......@@ -92,11 +93,12 @@ typedef struct pmac_tumber_t {
unsigned int mix_vol[VOL_IDX_LAST_MIX][2]; /* stereo volumes for tas3004 */
int drc_range;
int drc_enable;
#ifdef CONFIG_PMAC_PBOOK
struct work_struct resume_workq;
#endif
} pmac_tumbler_t;
#define number_of(ary) (sizeof(ary) / sizeof(ary[0]))
/*
*/
......@@ -168,16 +170,16 @@ static int tumbler_set_master_volume(pmac_tumbler_t *mix)
left_vol = 0;
else {
left_vol = mix->master_vol[0];
if (left_vol >= number_of(master_volume_table))
left_vol = number_of(master_volume_table) - 1;
if (left_vol >= ARRAY_SIZE(master_volume_table))
left_vol = ARRAY_SIZE(master_volume_table) - 1;
left_vol = master_volume_table[left_vol];
}
if (! mix->master_switch[1])
right_vol = 0;
else {
right_vol = mix->master_vol[1];
if (right_vol >= number_of(master_volume_table))
right_vol = number_of(master_volume_table) - 1;
if (right_vol >= ARRAY_SIZE(master_volume_table))
right_vol = ARRAY_SIZE(master_volume_table) - 1;
right_vol = master_volume_table[right_vol];
}
......@@ -203,7 +205,7 @@ static int tumbler_info_master_volume(snd_kcontrol_t *kcontrol, snd_ctl_elem_inf
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
uinfo->count = 2;
uinfo->value.integer.min = 0;
uinfo->value.integer.max = number_of(master_volume_table) - 1;
uinfo->value.integer.max = ARRAY_SIZE(master_volume_table) - 1;
return 0;
}
......@@ -479,7 +481,7 @@ static struct tumbler_mono_vol tumbler_pcm_vol_info = {
.index = VOL_IDX_PCM_MONO,
.reg = TAS_REG_PCM,
.bytes = 3,
.max = number_of(mixer_volume_table),
.max = ARRAY_SIZE(mixer_volume_table),
.table = mixer_volume_table,
};
......@@ -487,7 +489,7 @@ static struct tumbler_mono_vol tumbler_bass_vol_info = {
.index = VOL_IDX_BASS,
.reg = TAS_REG_BASS,
.bytes = 1,
.max = number_of(bass_volume_table),
.max = ARRAY_SIZE(bass_volume_table),
.table = bass_volume_table,
};
......@@ -495,7 +497,7 @@ static struct tumbler_mono_vol tumbler_treble_vol_info = {
.index = VOL_IDX_TREBLE,
.reg = TAS_REG_TREBLE,
.bytes = 1,
.max = number_of(treble_volume_table),
.max = ARRAY_SIZE(treble_volume_table),
.table = treble_volume_table,
};
......@@ -504,7 +506,7 @@ static struct tumbler_mono_vol snapper_bass_vol_info = {
.index = VOL_IDX_BASS,
.reg = TAS_REG_BASS,
.bytes = 1,
.max = number_of(snapper_bass_volume_table),
.max = ARRAY_SIZE(snapper_bass_volume_table),
.table = snapper_bass_volume_table,
};
......@@ -512,7 +514,7 @@ static struct tumbler_mono_vol snapper_treble_vol_info = {
.index = VOL_IDX_TREBLE,
.reg = TAS_REG_TREBLE,
.bytes = 1,
.max = number_of(snapper_treble_volume_table),
.max = ARRAY_SIZE(snapper_treble_volume_table),
.table = snapper_treble_volume_table,
};
......@@ -546,8 +548,8 @@ static int snapper_set_mix_vol1(pmac_tumbler_t *mix, int idx, int ch, int reg)
unsigned char block[9];
vol = mix->mix_vol[idx][ch];
if (vol >= number_of(mixer_volume_table)) {
vol = number_of(mixer_volume_table) - 1;
if (vol >= ARRAY_SIZE(mixer_volume_table)) {
vol = ARRAY_SIZE(mixer_volume_table) - 1;
mix->mix_vol[idx][ch] = vol;
}
......@@ -579,7 +581,7 @@ static int snapper_info_mix(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t *uinfo
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
uinfo->count = 2;
uinfo->value.integer.min = 0;
uinfo->value.integer.max = number_of(mixer_volume_table) - 1;
uinfo->value.integer.max = ARRAY_SIZE(mixer_volume_table) - 1;
return 0;
}
......@@ -877,29 +879,47 @@ static void tumbler_reset_audio(pmac_t *chip)
#ifdef CONFIG_PMAC_PBOOK
/* resume mixer */
static void tumbler_resume(pmac_t *chip)
/* we call the i2c transfer in a workqueue because it may need either schedule()
* or completion from timer interrupts.
*/
static void tumbler_resume_work(void *arg)
{
pmac_t *chip = (pmac_t *)arg;
pmac_tumbler_t *mix = chip->mixer_data;
snd_assert(mix, return);
tumbler_reset_audio(chip);
if (mix->i2c.client)
tumbler_init_client(&mix->i2c);
if (mix->i2c.client) {
if (tumbler_init_client(&mix->i2c) < 0)
printk(KERN_ERR "tumbler_init_client error\n");
} else
printk(KERN_ERR "tumbler: i2c is not initialized\n");
if (chip->model == PMAC_TUMBLER) {
tumbler_set_mono_volume(mix, &tumbler_pcm_vol_info);
tumbler_set_mono_volume(mix, &tumbler_bass_vol_info);
tumbler_set_mono_volume(mix, &tumbler_treble_vol_info);
tumbler_set_drc(mix);
} else {
snapper_set_mix_vol(mix, VOL_IDX_PCM);
snapper_set_mix_vol(mix, VOL_IDX_PCM2);
snapper_set_mix_vol(mix, VOL_IDX_ADC);
tumbler_set_mono_volume(mix, &tumbler_bass_vol_info);
tumbler_set_mono_volume(mix, &tumbler_treble_vol_info);
snapper_set_drc(mix);
}
tumbler_set_drc(mix);
tumbler_set_master_volume(mix);
if (chip->update_automute)
chip->update_automute(chip, 0);
}
static void tumbler_resume(pmac_t *chip)
{
pmac_tumbler_t *mix = chip->mixer_data;
snd_assert(mix, return);
INIT_WORK(&mix->resume_workq, tumbler_resume_work, chip);
if (schedule_work(&mix->resume_workq))
return;
printk(KERN_ERR "ALSA tumbler: cannot schedule resume-workqueue.\n");
}
#endif
/* initialize tumbler */
......@@ -1001,12 +1021,12 @@ int __init snd_pmac_tumbler_init(pmac_t *chip)
sprintf(chip->card->mixername, "PowerMac %s", chipname);
if (chip->model == PMAC_TUMBLER) {
for (i = 0; i < number_of(tumbler_mixers); i++) {
for (i = 0; i < ARRAY_SIZE(tumbler_mixers); i++) {
if ((err = snd_ctl_add(chip->card, snd_ctl_new1(&tumbler_mixers[i], chip))) < 0)
return err;
}
} else {
for (i = 0; i < number_of(snapper_mixers); i++) {
for (i = 0; i < ARRAY_SIZE(snapper_mixers); i++) {
if ((err = snd_ctl_add(chip->card, snd_ctl_new1(&snapper_mixers[i], chip))) < 0)
return err;
}
......
......@@ -55,6 +55,7 @@ static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* ID for this card */
static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP; /* Enable this card */
static int vid[SNDRV_CARDS] = { [0 ... (SNDRV_CARDS-1)] = -1 }; /* Vendor ID for this card */
static int pid[SNDRV_CARDS] = { [0 ... (SNDRV_CARDS-1)] = -1 }; /* Product ID for this card */
static int nrpacks = 4; /* max. number of packets per urb */
MODULE_PARM(index, "1-" __MODULE_STRING(SNDRV_CARDS) "i");
MODULE_PARM_DESC(index, "Index value for the USB audio adapter.");
......@@ -71,6 +72,9 @@ MODULE_PARM_SYNTAX(vid, SNDRV_ENABLED ",allows:{{-1,0xffff}},base:16");
MODULE_PARM(pid, "1-" __MODULE_STRING(SNDRV_CARDS) "i");
MODULE_PARM_DESC(pid, "Product ID for the USB audio device.");
MODULE_PARM_SYNTAX(pid, SNDRV_ENABLED ",allows:{{-1,0xffff}},base:16");
MODULE_PARM(nrpacks, "i");
MODULE_PARM_DESC(nrpacks, "Max. number of packets per URB.");
MODULE_PARM_SYNTAX(nrpacks, SNDRV_ENABLED ",allows:{{2,10}}");
/*
......@@ -98,7 +102,7 @@ MODULE_PARM_SYNTAX(pid, SNDRV_ENABLED ",allows:{{-1,0xffff}},base:16");
*
*/
#define NRPACKS 4 /* 4ms per urb */
#define MAX_PACKS 10
#define MAX_URBS 5 /* max. 20ms long packets */
#define SYNC_URBS 2 /* always two urbs for sync */
#define MIN_PACKS_URB 1 /* minimum 1 packet per urb */
......@@ -176,7 +180,7 @@ struct snd_usb_substream {
unsigned int nurbs; /* # urbs */
snd_urb_ctx_t dataurb[MAX_URBS]; /* data urb table */
snd_urb_ctx_t syncurb[SYNC_URBS]; /* sync urb table */
char syncbuf[SYNC_URBS * NRPACKS * 3]; /* sync buffer; it's so small - let's get static */
char syncbuf[SYNC_URBS * MAX_PACKS * 3]; /* sync buffer; it's so small - let's get static */
char *tmpbuf; /* temporary buffer for playback */
u64 formats; /* format bitmasks (all or'ed) */
......@@ -839,7 +843,7 @@ static int init_substream_urbs(snd_usb_substream_t *subs, snd_pcm_runtime_t *run
/* allocate a temporary buffer for playback */
if (is_playback) {
subs->tmpbuf = kmalloc(maxsize * NRPACKS, GFP_KERNEL);
subs->tmpbuf = kmalloc(maxsize * nrpacks, GFP_KERNEL);
if (! subs->tmpbuf) {
snd_printk(KERN_ERR "cannot malloc tmpbuf\n");
return -ENOMEM;
......@@ -850,16 +854,16 @@ static int init_substream_urbs(snd_usb_substream_t *subs, snd_pcm_runtime_t *run
total_packs = (frames_to_bytes(runtime, runtime->period_size) + maxsize - 1) / maxsize;
if (total_packs < 2 * MIN_PACKS_URB)
total_packs = 2 * MIN_PACKS_URB;
subs->nurbs = (total_packs + NRPACKS - 1) / NRPACKS;
subs->nurbs = (total_packs + nrpacks - 1) / nrpacks;
if (subs->nurbs > MAX_URBS) {
/* too much... */
subs->nurbs = MAX_URBS;
total_packs = MAX_URBS * NRPACKS;
total_packs = MAX_URBS * nrpacks;
}
n = total_packs;
for (i = 0; i < subs->nurbs; i++) {
npacks[i] = n > NRPACKS ? NRPACKS : n;
n -= NRPACKS;
npacks[i] = n > nrpacks ? nrpacks : n;
n -= nrpacks;
}
if (subs->nurbs <= 1) {
/* too little - we need at least two packets
......@@ -918,14 +922,14 @@ static int init_substream_urbs(snd_usb_substream_t *subs, snd_pcm_runtime_t *run
snd_urb_ctx_t *u = &subs->syncurb[i];
u->index = i;
u->subs = subs;
u->packets = NRPACKS;
u->packets = nrpacks;
u->urb = usb_alloc_urb(u->packets, GFP_KERNEL);
if (! u->urb) {
release_substream_urbs(subs, 0);
return -ENOMEM;
}
u->urb->transfer_buffer = subs->syncbuf + i * NRPACKS * 3;
u->urb->transfer_buffer_length = NRPACKS * 3;
u->urb->transfer_buffer = subs->syncbuf + i * nrpacks * 3;
u->urb->transfer_buffer_length = nrpacks * 3;
u->urb->dev = subs->dev;
u->urb->pipe = subs->syncpipe;
u->urb->transfer_flags = URB_ISO_ASAP | UNLINK_FLAGS;
......@@ -1096,16 +1100,6 @@ static int set_format(snd_usb_substream_t *subs, snd_pcm_runtime_t *runtime)
attr = fmt->ep_attr & EP_ATTR_MASK;
if ((is_playback && attr == EP_ATTR_ASYNC) ||
(! is_playback && attr == EP_ATTR_ADAPTIVE)) {
/*
* QUIRK: plantronics headset has adaptive-in
* although it's really not...
*/
if ((dev->descriptor.idVendor == 0x047f &&
dev->descriptor.idProduct == 0x0ca1) ||
/* Griffin iMic (note that there is an older model 77d:223) */
(dev->descriptor.idVendor == 0x077d &&
dev->descriptor.idProduct == 0x07af))
goto _ok;
/* check endpoint */
if (altsd->bNumEndpoints < 2 ||
get_endpoint(alts, 1)->bmAttributes != 0x01 ||
......@@ -1129,7 +1123,6 @@ static int set_format(snd_usb_substream_t *subs, snd_pcm_runtime_t *runtime)
subs->syncinterval = get_endpoint(alts, 1)->bRefresh;
}
_ok:
if ((err = init_usb_pitch(dev, subs->interface, alts, fmt)) < 0 ||
(err = init_usb_sample_rate(dev, subs->interface, alts, fmt,
runtime->rate)) < 0)
......@@ -1497,7 +1490,7 @@ static int setup_hw_info(snd_pcm_runtime_t *runtime, snd_usb_substream_t *subs)
/* set the period time minimum 1ms */
snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_PERIOD_TIME,
1000 * MIN_PACKS_URB,
/*(NRPACKS * MAX_URBS) * 1000*/ UINT_MAX);
/*(nrpacks * MAX_URBS) * 1000*/ UINT_MAX);
if (check_hw_params_convention(subs)) {
hwc_debug("setting extra hw constraints...\n");
......@@ -1656,11 +1649,9 @@ void *snd_usb_find_csint_desc(void *buffer, int buflen, void *after, u8 dsubtype
* entry point for linux usb interface
*/
#ifndef OLD_USB
static int usb_audio_probe(struct usb_interface *intf,
const struct usb_device_id *id);
static void usb_audio_disconnect(struct usb_interface *intf);
#endif
static struct usb_device_id usb_audio_ids [] = {
#include "usbquirks.h"
......@@ -1677,9 +1668,6 @@ static struct usb_driver usb_audio_driver = {
.name = "snd-usb-audio",
.probe = usb_audio_probe,
.disconnect = usb_audio_disconnect,
#ifdef OLD_USB
.driver_list = LIST_HEAD_INIT(usb_audio_driver.driver_list),
#endif
.id_table = usb_audio_ids,
};
......@@ -1944,7 +1932,7 @@ static int parse_audio_format_i_type(struct usb_device *dev, struct audioformat
switch (format) {
case 0: /* some devices don't define this correctly... */
snd_printdd(KERN_INFO "%d:%u:%d : format type 0 is detected, processed as PCM\n",
dev->devnum, iface_no, altno);
dev->devnum, fp->iface, fp->altsetting);
/* fall-through */
case USB_AUDIO_FORMAT_PCM:
if (sample_width > sample_bytes * 8) {
......@@ -2238,6 +2226,33 @@ static int parse_audio_endpoints(snd_usb_audio_t *chip, int iface_no)
fp->maxpacksize = get_endpoint(alts, 0)->wMaxPacketSize;
fp->attributes = csep[3];
/* some quirks for attributes here */
/* workaround for AudioTrak Optoplay */
if (dev->descriptor.idVendor == 0x0a92 &&
dev->descriptor.idProduct == 0x0053) {
/* Optoplay sets the sample rate attribute although
* it seems not supporting it in fact.
*/
fp->attributes &= ~EP_CS_ATTR_SAMPLE_RATE;
}
/*
* plantronics headset and Griffin iMic have set adaptive-in
* although it's really not...
*/
if ((dev->descriptor.idVendor == 0x047f &&
dev->descriptor.idProduct == 0x0ca1) ||
/* Griffin iMic (note that there is an older model 77d:223) */
(dev->descriptor.idVendor == 0x077d &&
dev->descriptor.idProduct == 0x07af)) {
fp->ep_attr &= ~EP_ATTR_MASK;
if (stream == SNDRV_PCM_STREAM_PLAYBACK)
fp->ep_attr |= EP_ATTR_ADAPTIVE;
else
fp->ep_attr |= EP_ATTR_SYNC;
}
/* ok, let's parse further... */
if (parse_audio_format(dev, fp, format, fmt, stream) < 0) {
if (fp->rate_table)
kfree(fp->rate_table);
......@@ -2461,6 +2476,11 @@ static int snd_usb_extigy_boot_quirk(struct usb_device *dev, struct usb_interfac
/*
* audio-interface quirks
*
* returns zero if no standard audio/MIDI parsing is needed.
* returns a postive value if standard audio/midi interfaces are parsed
* after this.
* returns a negative value at error.
*/
static int snd_usb_create_quirk(snd_usb_audio_t *chip,
struct usb_interface *iface,
......@@ -2749,7 +2769,6 @@ static void snd_usb_audio_disconnect(struct usb_device *dev, void *ptr)
}
}
#ifndef OLD_USB
/*
* new 2.5 USB kernel API
*/
......@@ -2770,12 +2789,14 @@ static void usb_audio_disconnect(struct usb_interface *intf)
snd_usb_audio_disconnect(interface_to_usbdev(intf),
dev_get_drvdata(&intf->dev));
}
#endif
static int __init snd_usb_audio_init(void)
{
if (nrpacks < 2 || nrpacks > MAX_PACKS) {
printk(KERN_WARNING "invalid nrpacks value.\n");
return -EINVAL;
}
usb_register(&usb_audio_driver);
return 0;
}
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment