From 3e8731740e17f01ec1ecce556ccdc4c42279ce1b Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Thu, 17 Nov 2005 10:15:37 +0100 Subject: [ALSA] Minor clean up and fixes for CS5535 audio driver Modules: Documentation,CS5535 driver Minor clean up and fixes for CS5535 audio driver. Added an entry in ALSA-Configuration.txt, too. Signed-off-by: Takashi Iwai --- Documentation/sound/alsa/ALSA-Configuration.txt | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'Documentation') diff --git a/Documentation/sound/alsa/ALSA-Configuration.txt b/Documentation/sound/alsa/ALSA-Configuration.txt index 2f27f391c7cc..23d1870d522f 100644 --- a/Documentation/sound/alsa/ALSA-Configuration.txt +++ b/Documentation/sound/alsa/ALSA-Configuration.txt @@ -410,6 +410,13 @@ Prior to version 0.9.0rc4 options had a 'snd_' prefix. This was removed. The power-management is supported. + Module snd-cs5535audio + ---------------------- + + Module for multifunction CS5535 companion PCI device + + Module supports up to 8 cards. + Module snd-dt019x ----------------- -- cgit v1.2.3-59-g8ed1b From 10e4097fb47b57d095204d3fad10b25e3b4d42a3 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Thu, 17 Nov 2005 11:04:55 +0100 Subject: [ALSA] [Trivial] Fix ac97_quirk option in document Modules: Documentation Fix a wrong option value for ac97_quirk option in the document. Signed-off-by: Takashi Iwai --- Documentation/sound/alsa/ALSA-Configuration.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'Documentation') diff --git a/Documentation/sound/alsa/ALSA-Configuration.txt b/Documentation/sound/alsa/ALSA-Configuration.txt index 23d1870d522f..84068baf587b 100644 --- a/Documentation/sound/alsa/ALSA-Configuration.txt +++ b/Documentation/sound/alsa/ALSA-Configuration.txt @@ -1481,7 +1481,7 @@ the proper value with this option. The following strings are accepted: - default Don't override the default setting - - disable Disable the quirk + - none Disable the quirk - hp_only Bind Master and Headphone controls as a single control - swap_hp Swap headphone and master controls - swap_surround Swap master and surround controls -- cgit v1.2.3-59-g8ed1b From 59de641ca37b88dd34d0e1d853800b488f642624 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Thu, 17 Nov 2005 11:05:34 +0100 Subject: [ALSA] Small update of Procfile.txt Modules: Documentation Small update of Procfile.txt for hda and usb proc files. Signed-off-by: Takashi Iwai --- Documentation/sound/alsa/Procfile.txt | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) (limited to 'Documentation') diff --git a/Documentation/sound/alsa/Procfile.txt b/Documentation/sound/alsa/Procfile.txt index 25c5d648aef6..1fe48846d78f 100644 --- a/Documentation/sound/alsa/Procfile.txt +++ b/Documentation/sound/alsa/Procfile.txt @@ -138,6 +138,22 @@ card*/codec97#0/ac97#?-?+regs # echo 02 9f1f > /proc/asound/card0/codec97#0/ac97#0-0+regs +USB Audio Streams +----------------- + +card*/stream* + Shows the assignment and the current status of each audio stream + of the given card. This information is very useful for debugging. + + +HD-Audio Codecs +--------------- + +card*/codec#* + Shows the general codec information and the attribute of each + widget node. + + Sequencer Information --------------------- -- cgit v1.2.3-59-g8ed1b From 446ab5f5039df4209a2e28752bd48c99007d3d82 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Thu, 17 Nov 2005 15:12:54 +0100 Subject: [ALSA] Remove xxx_t typedefs: Documentation Modules: Documentation Remove xxx_t typedefs from documentation. Signed-off-by: Takashi Iwai --- .../sound/alsa/DocBook/writing-an-alsa-driver.tmpl | 419 ++++++++++----------- Documentation/sound/alsa/hda_codec.txt | 14 +- 2 files changed, 214 insertions(+), 219 deletions(-) (limited to 'Documentation') diff --git a/Documentation/sound/alsa/DocBook/writing-an-alsa-driver.tmpl b/Documentation/sound/alsa/DocBook/writing-an-alsa-driver.tmpl index 260334c98d95..f2e59fe802bd 100644 --- a/Documentation/sound/alsa/DocBook/writing-an-alsa-driver.tmpl +++ b/Documentation/sound/alsa/DocBook/writing-an-alsa-driver.tmpl @@ -403,9 +403,8 @@ static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP; /* definition of the chip-specific record */ - typedef struct snd_mychip mychip_t; - struct snd_mychip { - snd_card_t *card; + struct mychip { + struct snd_card *card; // rest of implementation will be in the section // "PCI Resource Managements" }; @@ -413,7 +412,7 @@ /* chip-specific destructor * (see "PCI Resource Managements") */ - static int snd_mychip_free(mychip_t *chip) + static int snd_mychip_free(struct mychip *chip) { .... // will be implemented later... } @@ -421,22 +420,21 @@ /* component-destructor * (see "Management of Cards and Components") */ - static int snd_mychip_dev_free(snd_device_t *device) + static int snd_mychip_dev_free(struct snd_device *device) { - mychip_t *chip = device->device_data; - return snd_mychip_free(chip); + return snd_mychip_free(device->device_data); } /* chip-specific constructor * (see "Management of Cards and Components") */ - static int __devinit snd_mychip_create(snd_card_t *card, + static int __devinit snd_mychip_create(struct snd_card *card, struct pci_dev *pci, - mychip_t **rchip) + struct mychip **rchip) { - mychip_t *chip; + struct mychip *chip; int err; - static snd_device_ops_t ops = { + static struct snd_device_ops ops = { .dev_free = snd_mychip_dev_free, }; @@ -474,8 +472,8 @@ const struct pci_device_id *pci_id) { static int dev; - snd_card_t *card; - mychip_t *chip; + struct snd_card *card; + struct mychip *chip; int err; /* (1) */ @@ -582,7 +580,7 @@ @@ -605,7 +603,7 @@ @@ -830,7 +828,7 @@ After the card is created, you can attach the components (devices) to the card instance. On ALSA driver, a component is - represented as a snd_device_t object. + represented as a struct snd_device object. A component can be a PCM instance, a control interface, a raw MIDI interface, etc. Each of such instances has one component entry. @@ -891,14 +889,11 @@ The chip-specific information, e.g. the i/o port address, its resource pointer, or the irq number, is stored in the chip-specific record. - Usually, the chip-specific record is typedef'ed as - xxx_t like the following: @@ -918,12 +913,12 @@ - whether mychip_t is the type of the chip record. + whether struct mychip is the type of the chip record. @@ -932,7 +927,7 @@ private_data; + struct mychip *chip = (struct mychip *)card->private_data; ]]> @@ -954,8 +949,8 @@ @@ -1000,7 +995,7 @@ device_data; - return snd_mychip_free(chip); + return snd_mychip_free(device->device_data); } ]]> @@ -1087,15 +1081,15 @@ PCI Resource Managements Example port = pci_resource_start(pci, 0); if (request_irq(pci->irq, snd_mychip_interrupt, - SA_INTERRUPT|SA_SHIRQ, "My Chip", - (void *)chip)) { + SA_INTERRUPT|SA_SHIRQ, "My Chip", chip)) { printk(KERN_ERR "cannot grab irq %d\n", pci->irq); snd_mychip_free(chip); return -EBUSY; @@ -1268,14 +1261,14 @@ Now assume that this PCI device has an I/O port with 8 bytes - and an interrupt. Then mychip_t will have the + and an interrupt. Then struct mychip will have the following fields: irq, snd_mychip_interrupt, - SA_INTERRUPT|SA_SHIRQ, "My Chip", - (void *)chip)) { + SA_INTERRUPT|SA_SHIRQ, "My Chip", chip)) { printk(KERN_ERR "cannot grab irq %d\n", pci->irq); snd_mychip_free(chip); return -EBUSY; @@ -1372,7 +1364,7 @@ static irqreturn_t snd_mychip_interrupt(int irq, void *dev_id, struct pt_regs *regs) { - mychip_t *chip = dev_id; + struct mychip *chip = dev_id; .... return IRQ_HANDLED; } @@ -1487,7 +1479,7 @@ iobase_virt) @@ -1537,7 +1529,7 @@ Registration of Device Struct At some point, typically after calling snd_device_new(), - you need to register the struct device of the chip + you need to register the struct device of the chip you're handling for udev and co. ALSA provides a macro for compatibility with older kernels. Simply call like the following: @@ -1739,7 +1731,7 @@ .... /* hardware definition */ - static snd_pcm_hardware_t snd_mychip_playback_hw = { + static struct snd_pcm_hardware snd_mychip_playback_hw = { .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER | @@ -1758,7 +1750,7 @@ }; /* hardware definition */ - static snd_pcm_hardware_t snd_mychip_capture_hw = { + static struct snd_pcm_hardware snd_mychip_capture_hw = { .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER | @@ -1777,10 +1769,10 @@ }; /* open callback */ - static int snd_mychip_playback_open(snd_pcm_substream_t *substream) + static int snd_mychip_playback_open(struct snd_pcm_substream *substream) { - mychip_t *chip = snd_pcm_substream_chip(substream); - snd_pcm_runtime_t *runtime = substream->runtime; + struct mychip *chip = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; runtime->hw = snd_mychip_playback_hw; // more hardware-initialization will be done here @@ -1788,19 +1780,19 @@ } /* close callback */ - static int snd_mychip_playback_close(snd_pcm_substream_t *substream) + static int snd_mychip_playback_close(struct snd_pcm_substream *substream) { - mychip_t *chip = snd_pcm_substream_chip(substream); + struct mychip *chip = snd_pcm_substream_chip(substream); // the hardware-specific codes will be here return 0; } /* open callback */ - static int snd_mychip_capture_open(snd_pcm_substream_t *substream) + static int snd_mychip_capture_open(struct snd_pcm_substream *substream) { - mychip_t *chip = snd_pcm_substream_chip(substream); - snd_pcm_runtime_t *runtime = substream->runtime; + struct mychip *chip = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; runtime->hw = snd_mychip_capture_hw; // more hardware-initialization will be done here @@ -1808,33 +1800,33 @@ } /* close callback */ - static int snd_mychip_capture_close(snd_pcm_substream_t *substream) + static int snd_mychip_capture_close(struct snd_pcm_substream *substream) { - mychip_t *chip = snd_pcm_substream_chip(substream); + struct mychip *chip = snd_pcm_substream_chip(substream); // the hardware-specific codes will be here return 0; } /* hw_params callback */ - static int snd_mychip_pcm_hw_params(snd_pcm_substream_t *substream, - snd_pcm_hw_params_t * hw_params) + static int snd_mychip_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) { return snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params)); } /* hw_free callback */ - static int snd_mychip_pcm_hw_free(snd_pcm_substream_t *substream) + static int snd_mychip_pcm_hw_free(struct snd_pcm_substream *substream) { return snd_pcm_lib_free_pages(substream); } /* prepare callback */ - static int snd_mychip_pcm_prepare(snd_pcm_substream_t *substream) + static int snd_mychip_pcm_prepare(struct snd_pcm_substream *substream) { - mychip_t *chip = snd_pcm_substream_chip(substream); - snd_pcm_runtime_t *runtime = substream->runtime; + struct mychip *chip = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; /* set up the hardware with the current configuration * for example... @@ -1849,7 +1841,7 @@ } /* trigger callback */ - static int snd_mychip_pcm_trigger(snd_pcm_substream_t *substream, + static int snd_mychip_pcm_trigger(struct snd_pcm_substream *substream, int cmd) { switch (cmd) { @@ -1866,9 +1858,9 @@ /* pointer callback */ static snd_pcm_uframes_t - snd_mychip_pcm_pointer(snd_pcm_substream_t *substream) + snd_mychip_pcm_pointer(struct snd_pcm_substream *substream) { - mychip_t *chip = snd_pcm_substream_chip(substream); + struct mychip *chip = snd_pcm_substream_chip(substream); unsigned int current_ptr; /* get the current hardware pointer */ @@ -1877,7 +1869,7 @@ } /* operators */ - static snd_pcm_ops_t snd_mychip_playback_ops = { + static struct snd_pcm_ops snd_mychip_playback_ops = { .open = snd_mychip_playback_open, .close = snd_mychip_playback_close, .ioctl = snd_pcm_lib_ioctl, @@ -1889,7 +1881,7 @@ }; /* operators */ - static snd_pcm_ops_t snd_mychip_capture_ops = { + static struct snd_pcm_ops snd_mychip_capture_ops = { .open = snd_mychip_capture_open, .close = snd_mychip_capture_close, .ioctl = snd_pcm_lib_ioctl, @@ -1905,9 +1897,9 @@ */ /* create a pcm device */ - static int __devinit snd_mychip_new_pcm(mychip_t *chip) + static int __devinit snd_mychip_new_pcm(struct mychip *chip) { - snd_pcm_t *pcm; + struct snd_pcm *pcm; int err; if ((err = snd_pcm_new(chip->card, "My Chip", 0, 1, 1, @@ -1944,9 +1936,9 @@ card, "My Chip", 0, 1, 1, @@ -1989,13 +1981,13 @@ specify more numbers, but they must be handled properly in open/close, etc. callbacks. When you need to know which substream you are referring to, then it can be obtained from - snd_pcm_substream_t data passed to each callback + struct snd_pcm_substream data passed to each callback as follows: number; ]]> @@ -2024,7 +2016,7 @@ PCM Instance with a Destructor my_private_pcm_data); // do what you like else .... } - static int __devinit snd_mychip_new_pcm(mychip_t *chip) + static int __devinit snd_mychip_new_pcm(struct mychip *chip) { - snd_pcm_t *pcm; + struct snd_pcm *pcm; .... /* allocate your own data */ chip->my_private_pcm_data = kmalloc(...); @@ -2149,7 +2141,7 @@ @@ -2252,7 +2244,7 @@ struct _snd_pcm_runtime {
Hardware Description - The hardware descriptor (snd_pcm_hardware_t) + The hardware descriptor (struct snd_pcm_hardware) contains the definitions of the fundamental hardware configuration. Above all, you'll need to define this in @@ -2267,7 +2259,7 @@ struct _snd_pcm_runtime { runtime; + struct snd_pcm_runtime *runtime = substream->runtime; ... runtime->hw = snd_mychip_playback_hw; /* common definition */ if (chip->model == VERY_OLD_ONE) @@ -2282,7 +2274,7 @@ struct _snd_pcm_runtime { Running Status The running status can be referred via runtime->status. - This is the pointer to snd_pcm_mmap_status_t + This is the pointer to struct snd_pcm_mmap_status record. For example, you can get the current DMA hardware pointer via runtime->status->hw_ptr. @@ -2520,7 +2512,7 @@ struct _snd_pcm_runtime { The DMA application pointer can be referred via runtime->control, which points - snd_pcm_mmap_control_t record. + struct snd_pcm_mmap_control record. However, accessing directly to this value is not recommended.
@@ -2542,9 +2534,9 @@ struct _snd_pcm_runtime { runtime->private_data = data; @@ -2586,7 +2578,7 @@ struct _snd_pcm_runtime { The callback function takes at least the argument with - snd_pcm_substream_t pointer. For retrieving the + snd_pcm_substream pointer. For retrieving the chip record from the given substream instance, you can use the following macro. @@ -2594,7 +2586,7 @@ struct _snd_pcm_runtime { @@ -2616,7 +2608,7 @@ struct _snd_pcm_runtime { @@ -2631,10 +2623,10 @@ struct _snd_pcm_runtime { runtime; + struct mychip *chip = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; runtime->hw = snd_mychip_playback_hw; return 0; @@ -2667,7 +2659,7 @@ struct _snd_pcm_runtime { @@ -2682,7 +2674,7 @@ struct _snd_pcm_runtime { runtime->private_data); @@ -2709,8 +2701,8 @@ struct _snd_pcm_runtime { @@ -2785,7 +2777,7 @@ struct _snd_pcm_runtime { @@ -2820,7 +2812,7 @@ struct _snd_pcm_runtime { @@ -2869,7 +2861,7 @@ struct _snd_pcm_runtime { @@ -2939,7 +2931,7 @@ struct _snd_pcm_runtime { @@ -3067,7 +3059,7 @@ struct _snd_pcm_runtime { static irqreturn_t snd_mychip_interrupt(int irq, void *dev_id, struct pt_regs *regs) { - mychip_t *chip = dev_id; + struct mychip *chip = dev_id; spin_lock(&chip->lock); .... if (pcm_irq_invoked(chip)) { @@ -3111,7 +3103,7 @@ struct _snd_pcm_runtime { static irqreturn_t snd_mychip_interrupt(int irq, void *dev_id, struct pt_regs *regs) { - mychip_t *chip = dev_id; + struct mychip *chip = dev_id; spin_lock(&chip->lock); .... if (pcm_irq_invoked(chip)) { @@ -3221,13 +3213,13 @@ struct _snd_pcm_runtime { snd_pcm_hardware_t stucture (or in any + specified in the snd_pcm_hardware stucture (or in any other constraint_list). You can build a rule like this: Example of Hardware Constraints for Channels min < 2) { @@ -3298,12 +3291,13 @@ struct _snd_pcm_runtime { Example of Hardware Constraints for Channels bits[0] == SNDRV_PCM_FMTBIT_S16_LE) { @@ -3376,13 +3370,13 @@ struct _snd_pcm_runtime { callbacks: info, get and put. Then, define a - snd_kcontrol_new_t record, such as: + struct snd_kcontrol_new record, such as: Definition of a Control The info callback is used to get the detailed information of this control. This must store the - values of the given snd_ctl_elem_info_t + values of the given struct snd_ctl_elem_info object. For example, for a boolean control with a single element will be: @@ -3607,8 +3601,8 @@ struct _snd_pcm_runtime { Example of info callback type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; uinfo->count = 1; @@ -3642,8 +3636,8 @@ struct _snd_pcm_runtime { Example of get callback value.integer.value[0] = get_some_value(chip); return 0; } @@ -3717,8 +3711,8 @@ struct _snd_pcm_runtime { private_value & 0xff; int shift = (kcontrol->private_value >> 16) & 0xff; @@ -3754,10 +3748,10 @@ struct _snd_pcm_runtime { Example of put callback current_value != ucontrol->value.integer.value[0]) { @@ -3814,7 +3808,7 @@ struct _snd_pcm_runtime { where my_control is the - snd_kcontrol_new_t object defined above, and chip + struct snd_kcontrol_new object defined above, and chip is the object pointer to be passed to kcontrol->private_data which can be referred in callbacks. @@ -3822,7 +3816,7 @@ struct _snd_pcm_runtime { snd_ctl_new1() allocates a new - snd_kcontrol_t instance (that's why the definition + snd_kcontrol instance (that's why the definition of my_control can be with __devinitdata prefix), and snd_ctl_add assigns the given @@ -3849,7 +3843,7 @@ struct _snd_pcm_runtime { control id pointer for the notification. The event-mask specifies the types of notification, for example, in the above example, the change of control values is notified. - The id pointer is the pointer of snd_ctl_elem_id_t + The id pointer is the pointer of struct snd_ctl_elem_id to be notified. You can find some examples in es1938.c or es1968.c for hardware volume interrupts. @@ -3882,35 +3876,35 @@ struct _snd_pcm_runtime { Example of AC97 Interface private_data; + struct mychip *chip = ac97->private_data; .... // read a register value here from the codec return the_register_value; } - static void snd_mychip_ac97_write(ac97_t *ac97, + static void snd_mychip_ac97_write(struct snd_ac97 *ac97, unsigned short reg, unsigned short val) { - mychip_t *chip = ac97->private_data; + struct mychip *chip = ac97->private_data; .... // write the given register value to the codec } - static int snd_mychip_ac97(mychip_t *chip) + static int snd_mychip_ac97(struct mychip *chip) { - ac97_bus_t *bus; - ac97_template_t ac97; + struct snd_ac97_bus *bus; + struct snd_ac97_template ac97; int err; - static ac97_bus_ops_t ops = { + static struct snd_ac97_bus_ops ops = { .write = snd_mychip_ac97_write, .read = snd_mychip_ac97_read, }; @@ -3937,8 +3931,8 @@ struct _snd_pcm_runtime { - And then call snd_ac97_mixer() with an ac97_template_t + And then call snd_ac97_mixer() with an + struct snd_ac97_template record together with the bus pointer created above. private_data; + struct mychip *chip = ac97->private_data; .... return the_register_value; } @@ -4016,7 +4011,7 @@ struct _snd_pcm_runtime { @@ -4163,7 +4158,7 @@ struct _snd_pcm_runtime { Multiple Codecs When there are several codecs on the same card, you need to - call snd_ac97_new() multiple times with + call snd_ac97_mixer() multiple times with ac97.num=1 or greater. The num field specifies the codec number. @@ -4212,7 +4207,7 @@ struct _snd_pcm_runtime { @@ -4253,17 +4248,17 @@ struct _snd_pcm_runtime { Usually, the port address corresponds to the command port and port + 1 corresponds to the data port. If not, you may change the cport field of - mpu401_t manually - afterward. However, mpu401_t pointer is not + struct snd_mpu401 manually + afterward. However, snd_mpu401 pointer is not returned explicitly by snd_mpu401_uart_new(). You need to cast rmidi->private_data to - mpu401_t explicitly, + snd_mpu401 explicitly, private_data; ]]> @@ -4359,7 +4354,7 @@ struct _snd_pcm_runtime { card, "MyMIDI", 0, outs, ins, &rmidi); if (err < 0) return err; @@ -4419,7 +4414,7 @@ struct _snd_pcm_runtime { streams[SNDRV_RAWMIDI_STREAM_OUTPUT].substreams) { - substream = list_entry(list, snd_rawmidi_substream_t, list); + substream = list_entry(list, struct snd_rawmidi_substream, list); sprintf(substream->name, "My MIDI Port %d", substream->number + 1); } /* same for SNDRV_RAWMIDI_STREAM_INPUT */ @@ -4463,12 +4458,12 @@ struct _snd_pcm_runtime { If there is more than one port, your callbacks can determine the - port index from the snd_rawmidi_substream_t data passed to each + port index from the struct snd_rawmidi_substream data passed to each callback: number; ]]> @@ -4481,7 +4476,7 @@ struct _snd_pcm_runtime { @@ -4499,7 +4494,7 @@ struct _snd_pcm_runtime { @@ -4522,7 +4517,7 @@ struct _snd_pcm_runtime { @@ -4547,7 +4542,7 @@ struct _snd_pcm_runtime { @@ -4603,7 +4598,7 @@ struct _snd_pcm_runtime { @@ -4647,7 +4642,7 @@ struct _snd_pcm_runtime { @@ -4661,7 +4656,7 @@ struct _snd_pcm_runtime { This callback is optional. If you do not set - drain in the snd_rawmidi_ops_t + drain in the struct snd_rawmidi_ops structure, ALSA will simply wait for 50 milliseconds instead. @@ -4703,7 +4698,7 @@ struct _snd_pcm_runtime { @@ -4736,7 +4731,7 @@ struct _snd_pcm_runtime { @@ -4767,7 +4762,7 @@ struct _snd_pcm_runtime { @@ -4804,7 +4799,7 @@ struct _snd_pcm_runtime { @@ -4823,7 +4818,7 @@ struct _snd_pcm_runtime { private_data = p; hw->private_free = mydata_free; ]]> @@ -4835,9 +4830,9 @@ struct _snd_pcm_runtime { private_data; + struct mydata *p = hw->private_data; kfree(p); } ]]> @@ -5061,9 +5056,9 @@ struct _snd_pcm_runtime { @@ -5144,7 +5139,7 @@ struct _snd_pcm_runtime { @@ -5211,7 +5206,7 @@ struct _snd_pcm_runtime { dma_private; + struct snd_sg_buf *sgbuf = (struct snd_sg_buf_t*)substream->dma_private; ]]> @@ -5266,7 +5261,7 @@ struct _snd_pcm_runtime { #include /* get the physical page pointer on the given offset */ - static struct page *mychip_page(snd_pcm_substream_t *substream, + static struct page *mychip_page(struct snd_pcm_substream *substream, unsigned long offset) { void *pageptr = substream->runtime->dma_area + offset; @@ -5301,7 +5296,7 @@ struct _snd_pcm_runtime { @@ -5345,8 +5340,8 @@ struct _snd_pcm_runtime { @@ -5361,10 +5356,10 @@ struct _snd_pcm_runtime { private_data; + struct my_chip *chip = entry->private_data; snd_iprintf(buffer, "This is my chip!\n"); snd_iprintf(buffer, "Port = %ld\n", chip->port); @@ -5453,7 +5448,7 @@ struct _snd_pcm_runtime { pm_private_data; + struct mychip *chip = card->pm_private_data; /* (2) */ snd_pcm_suspend_all(chip->pcm); /* (3) */ @@ -5570,10 +5565,10 @@ struct _snd_pcm_runtime { pm_private_data; + struct mychip *chip = card->pm_private_data; /* (2) */ pci_enable_device(chip->pci); /* (3) */ @@ -5602,8 +5597,8 @@ struct _snd_pcm_runtime { const struct pci_device_id *pci_id) { .... - snd_card_t *card; - mychip_t *chip; + struct snd_card *card; + struct mychip *chip; .... snd_card_set_pm_callback(card, snd_my_suspend, snd_my_resume, chip); .... diff --git a/Documentation/sound/alsa/hda_codec.txt b/Documentation/sound/alsa/hda_codec.txt index e9d07b8f1acb..0be57ed81302 100644 --- a/Documentation/sound/alsa/hda_codec.txt +++ b/Documentation/sound/alsa/hda_codec.txt @@ -63,7 +63,7 @@ The bus instance is created via snd_hda_bus_new(). You need to pass the card instance, the template, and the pointer to store the resultant bus instance. -int snd_hda_bus_new(snd_card_t *card, const struct hda_bus_template *temp, +int snd_hda_bus_new(struct snd_card *card, const struct hda_bus_template *temp, struct hda_bus **busp); It returns zero if successful. A negative return value means any @@ -166,14 +166,14 @@ The ops field contains the following callback functions: struct hda_pcm_ops { int (*open)(struct hda_pcm_stream *info, struct hda_codec *codec, - snd_pcm_substream_t *substream); + struct snd_pcm_substream *substream); int (*close)(struct hda_pcm_stream *info, struct hda_codec *codec, - snd_pcm_substream_t *substream); + struct snd_pcm_substream *substream); int (*prepare)(struct hda_pcm_stream *info, struct hda_codec *codec, unsigned int stream_tag, unsigned int format, - snd_pcm_substream_t *substream); + struct snd_pcm_substream *substream); int (*cleanup)(struct hda_pcm_stream *info, struct hda_codec *codec, - snd_pcm_substream_t *substream); + struct snd_pcm_substream *substream); }; All are non-NULL, so you can call them safely without NULL check. @@ -284,7 +284,7 @@ parameter, and PCI subsystem IDs. If the matching entry is found, it returns the config field value. snd_hda_add_new_ctls() can be used to create and add control entries. -Pass the zero-terminated array of snd_kcontrol_new_t. The same array +Pass the zero-terminated array of struct snd_kcontrol_new. The same array can be passed to snd_hda_resume_ctls() for resume. Note that this will call control->put callback of these entries. So, put callback should check codec->in_resume and force to restore the @@ -292,7 +292,7 @@ given value if it's non-zero even if the value is identical with the cached value. Macros HDA_CODEC_VOLUME(), HDA_CODEC_MUTE() and their variables can be -used for the entry of snd_kcontrol_new_t. +used for the entry of struct snd_kcontrol_new. The input MUX helper callbacks for such a control are provided, too: snd_hda_input_mux_info() and snd_hda_input_mux_put(). See -- cgit v1.2.3-59-g8ed1b From a4efc230c60ad15584e723755316e67b3c708d67 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Thu, 17 Nov 2005 17:24:26 +0100 Subject: [ALSA] document - Add PM support Modules: Documentation Mark the drivers newly supporting PM in the documentation. Signed-off-by: Takashi Iwai --- Documentation/sound/alsa/ALSA-Configuration.txt | 52 +++++++++++++++++++++++++ 1 file changed, 52 insertions(+) (limited to 'Documentation') diff --git a/Documentation/sound/alsa/ALSA-Configuration.txt b/Documentation/sound/alsa/ALSA-Configuration.txt index 84068baf587b..9a58ed9e6952 100644 --- a/Documentation/sound/alsa/ALSA-Configuration.txt +++ b/Documentation/sound/alsa/ALSA-Configuration.txt @@ -148,6 +148,8 @@ Prior to version 0.9.0rc4 options had a 'snd_' prefix. This was removed. Module supports up to 8 cards. This module does not support autoprobe thus main port must be specified!!! Other ports are optional. + The power-management is supported. + Module snd-ad1889 ----------------- @@ -186,6 +188,8 @@ Prior to version 0.9.0rc4 options had a 'snd_' prefix. This was removed. Module supports up to 8 cards, autoprobe and PnP. + The power-management is supported. + Module snd-als4000 ------------------ @@ -196,6 +200,8 @@ Prior to version 0.9.0rc4 options had a 'snd_' prefix. This was removed. Module supports up to 8 cards, autoprobe and PnP. + The power-management is supported. + Module snd-atiixp ----------------- @@ -213,6 +219,8 @@ Prior to version 0.9.0rc4 options had a 'snd_' prefix. This was removed. implementation depends on the motherboard, and you'll need to choose the correct one via spdif_aclink module option. + The power-management is supported. + Module snd-atiixp-modem ----------------------- @@ -223,6 +231,8 @@ Prior to version 0.9.0rc4 options had a 'snd_' prefix. This was removed. Note: The default index value of this module is -2, i.e. the first slot is excluded. + The power-management is supported. + Module snd-au8810, snd-au8820, snd-au8830 ----------------------------------------- @@ -265,6 +275,8 @@ Prior to version 0.9.0rc4 options had a 'snd_' prefix. This was removed. Module supports up to 8 cards, PnP and autoprobe. + The power-management is supported. + Module snd-azt3328 ------------------ @@ -310,6 +322,8 @@ Prior to version 0.9.0rc4 options had a 'snd_' prefix. This was removed. Module supports up to 8 cards and autoprobe. + The power-management is supported. + Module snd-cmipci ----------------- @@ -323,6 +337,8 @@ Prior to version 0.9.0rc4 options had a 'snd_' prefix. This was removed. Module supports autoprobe and multiple chips (max 8). + The power-management is supported. + Module snd-cs4231 ----------------- @@ -433,6 +449,8 @@ Prior to version 0.9.0rc4 options had a 'snd_' prefix. This was removed. Module supports up to 8 cards. This module is enabled only with ISA PnP support. + The power-management is supported. + Module snd-dummy ---------------- @@ -440,6 +458,8 @@ Prior to version 0.9.0rc4 options had a 'snd_' prefix. This was removed. or input, but you may use this module for any application which requires a sound card (like RealPlayer). + The power-management is supported. + Module snd-emu10k1 ------------------ @@ -473,6 +493,8 @@ Prior to version 0.9.0rc4 options had a 'snd_' prefix. This was removed. * Creative Card 5.1 (c) 2003 [0x3fc3/0x7cff] * Creative Card all ins and outs [0x3fff/0x7fff] + The power-management is supported. + Module snd-emu10k1x ------------------- @@ -515,6 +537,8 @@ Prior to version 0.9.0rc4 options had a 'snd_' prefix. This was removed. Module supports up to 8 cards, PnP and autoprobe. + The power-management is supported. + Module snd-es1688 ----------------- @@ -554,6 +578,8 @@ Prior to version 0.9.0rc4 options had a 'snd_' prefix. This was removed. Module supports up to 8 cards and autoprobe. + The power-management is supported. + Module snd-es1968 ----------------- @@ -586,6 +612,8 @@ Prior to version 0.9.0rc4 options had a 'snd_' prefix. This was removed. Module supports up to 8 cards and autoprobe. + The power-management is supported. + Module snd-gusclassic --------------------- @@ -695,6 +723,8 @@ Prior to version 0.9.0rc4 options had a 'snd_' prefix. This was removed. (Usually SD_LPLIB register is more accurate than the position buffer.) + The power-management is supported. + Module snd-hdsp --------------- @@ -846,6 +876,8 @@ Prior to version 0.9.0rc4 options had a 'snd_' prefix. This was removed. Note: The default index value of this module is -2, i.e. the first slot is excluded. + The power-management is supported. + Module snd-interwave -------------------- @@ -1091,6 +1123,8 @@ Prior to version 0.9.0rc4 options had a 'snd_' prefix. This was removed. For ARM architecture only. + The power-management is supported. + Module snd-rme32 ---------------- @@ -1131,6 +1165,8 @@ Prior to version 0.9.0rc4 options had a 'snd_' prefix. This was removed. Module supports only one card. Module has no enable and index options. + The power-management is supported. + Module snd-sb8 -------------- @@ -1144,6 +1180,8 @@ Prior to version 0.9.0rc4 options had a 'snd_' prefix. This was removed. Module supports up to 8 cards and autoprobe. + The power-management is supported. + Module snd-sb16 and snd-sbawe ----------------------------- @@ -1170,6 +1208,8 @@ Prior to version 0.9.0rc4 options had a 'snd_' prefix. This was removed. half duplex mode through 8-bit DMA channel by disabling their 16-bit DMA channel. + The power-management is supported. + Module snd-sgalaxy ------------------ @@ -1182,6 +1222,8 @@ Prior to version 0.9.0rc4 options had a 'snd_' prefix. This was removed. Module supports up to 8 cards. + The power-management is supported. + Module snd-sscape ----------------- @@ -1363,6 +1405,8 @@ Prior to version 0.9.0rc4 options had a 'snd_' prefix. This was removed. Note: for the MPU401 on VIA823x, use snd-mpu401 driver additionally. The mpu_port option is for VIA686 chips only. + The power-management is supported. + Module snd-via82xx-modem ------------------------ @@ -1375,6 +1419,8 @@ Prior to version 0.9.0rc4 options had a 'snd_' prefix. This was removed. Note: The default index value of this module is -2, i.e. the first slot is excluded. + The power-management is supported. + Module snd-virmidi ------------------ @@ -1413,6 +1459,8 @@ Prior to version 0.9.0rc4 options had a 'snd_' prefix. This was removed. size is chosen. The possible IBL values can be found in /proc/asound/cardX/vx-status proc file. + The power-management is supported. + Module snd-vxpocket ------------------- @@ -1441,6 +1489,8 @@ Prior to version 0.9.0rc4 options had a 'snd_' prefix. This was removed. Note2: snd-vxp440 driver is merged to snd-vxpocket driver since ALSA 1.0.10. + The power-management is supported. + Module snd-ymfpci ----------------- @@ -1465,6 +1515,8 @@ Prior to version 0.9.0rc4 options had a 'snd_' prefix. This was removed. Note: the driver is build only when CONFIG_ISA is set. + The power-management is supported. + AC97 Quirk Option ================= -- cgit v1.2.3-59-g8ed1b From 5fe76e4dc60a2c3ff9b1143f5275a953db685e26 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Thu, 17 Nov 2005 17:26:09 +0100 Subject: [ALSA] document - Update PM support Modules: Documentation Update the description about the PCI PM support. Signed-off-by: Takashi Iwai --- .../sound/alsa/DocBook/writing-an-alsa-driver.tmpl | 185 ++++++++++++++++----- 1 file changed, 146 insertions(+), 39 deletions(-) (limited to 'Documentation') diff --git a/Documentation/sound/alsa/DocBook/writing-an-alsa-driver.tmpl b/Documentation/sound/alsa/DocBook/writing-an-alsa-driver.tmpl index f2e59fe802bd..4963d83d1511 100644 --- a/Documentation/sound/alsa/DocBook/writing-an-alsa-driver.tmpl +++ b/Documentation/sound/alsa/DocBook/writing-an-alsa-driver.tmpl @@ -18,8 +18,8 @@ - October 6, 2005 - 0.3.5 + November 17, 2005 + 0.3.6 @@ -2329,9 +2329,14 @@ struct _snd_pcm_runtime { PAUSE bit means that the pcm supports the pause operation, while the RESUME bit means that the pcm supports - the suspend/resume operation. If these flags - are set, the trigger callback below - must handle the corresponding commands. + the full suspend/resume operation. + If PAUSE flag is set, + the trigger callback below + must handle the corresponding (pause push/release) commands. + The suspend/resume trigger commands can be defined even without + RESUME flag. See + Power Management section for details. @@ -2903,8 +2908,8 @@ struct _snd_pcm_runtime { - When the pcm supports the suspend/resume operation - (i.e. SNDRV_PCM_INFO_RESUME flag is set), + When the pcm supports the suspend/resume operation, + regardless of full or partial suspend/resume support, SUSPEND and RESUME commands must be handled, too. These commands are issued when the power-management status is @@ -2913,6 +2918,8 @@ struct _snd_pcm_runtime { do suspend and resume of the pcm substream, and usually, they are identical with STOP and START commands, respectively. + See + Power Management section for details. @@ -5483,22 +5490,60 @@ struct _snd_pcm_runtime { CONFIG_PM. + + If the driver supports the suspend/resume + fully, that is, the device can be + properly resumed to the status at the suspend is called, + you can set SNDRV_PCM_INFO_RESUME flag + to pcm info field. Usually, this is possible when the + registers of ths chip can be safely saved and restored to the + RAM. If this is set, the trigger callback is called with + SNDRV_PCM_TRIGGER_RESUME after resume + callback is finished. + + + + Even if the driver doesn't support PM fully but only the + partial suspend/resume is possible, it's still worthy to + implement suspend/resume callbacks. In such a case, applications + would reset the status by calling + snd_pcm_prepare() and restart the stream + appropriately. Hence, you can define suspend/resume callbacks + below but don't set SNDRV_PCM_INFO_RESUME + info flag to the PCM. + + + + Note that the trigger with SUSPEND can be always called when + snd_pcm_suspend_all is called, + regardless of SNDRV_PCM_INFO_RESUME flag. + The RESUME flag affects only the behavior + of snd_pcm_resume(). + (Thus, in theory, + SNDRV_PCM_TRIGGER_RESUME isn't needed + to be handled in the trigger callback when no + SNDRV_PCM_INFO_RESUME flag is set. But, + it's better to keep it for compatibility reason.) + - ALSA provides the common power-management layer. Each card driver - needs to have only low-level suspend and resume callbacks. + In the earlier version of ALSA drivers, a common + power-management layer was provided, but it has been removed. + The driver needs to define the suspend/resume hooks according to + the bus the device is assigned. In the case of PCI driver, the + callbacks look like below: - Retrieve the chip data from pm_private_data field. + Retrieve the card and the chip data. + Call snd_power_change_state() with + SNDRV_CTL_POWER_D3hot to change the + power status. Call snd_pcm_suspend_all() to suspend the running PCM streams. + If AC97 codecs are used, call + snd_ac97_resume() for each codec. Save the register values if necessary. Stop the hardware if necessary. - Disable the PCI device by calling pci_disable_device(). + Disable the PCI device by calling + pci_disable_device(). Then, call + pci_save_state() at last. @@ -5525,18 +5577,24 @@ struct _snd_pcm_runtime { pm_private_data; + struct snd_card *card = pci_get_drvdata(pci); + struct mychip *chip = card->private_data; /* (2) */ - snd_pcm_suspend_all(chip->pcm); + snd_power_change_state(card, SNDRV_CTL_POWER_D3hot); /* (3) */ - snd_mychip_save_registers(chip); + snd_pcm_suspend_all(chip->pcm); /* (4) */ - snd_mychip_stop_hardware(chip); + snd_ac97_suspend(chip->ac97); /* (5) */ - pci_disable_device(chip->pci); + snd_mychip_save_registers(chip); + /* (6) */ + snd_mychip_stop_hardware(chip); + /* (7) */ + pci_disable_device(pci); + pci_save_state(pci); return 0; } ]]> @@ -5548,14 +5606,17 @@ struct _snd_pcm_runtime { The scheme of the real resume job is as following. - Retrieve the chip data from pm_private_data field. - Enable the pci device again by calling - pci_enable_device(). + Retrieve the card and the chip data. + Set up PCI. First, call pci_restore_state(). + Then enable the pci device again by calling pci_enable_device(). + Call pci_set_master() if necessary, too. Re-initialize the chip. Restore the saved registers if necessary. Resume the mixer, e.g. calling snd_ac97_resume(). Restart the hardware (if any). + Call snd_power_change_state() with + SNDRV_CTL_POWER_D0 to notify the processes. @@ -5565,12 +5626,15 @@ struct _snd_pcm_runtime { pm_private_data; + struct snd_card *card = pci_get_drvdata(pci); + struct mychip *chip = card->private_data; /* (2) */ - pci_enable_device(chip->pci); + pci_restore_state(pci); + pci_enable_device(pci); + pci_set_master(pci); /* (3) */ snd_mychip_reinit_chip(chip); /* (4) */ @@ -5579,6 +5643,8 @@ struct _snd_pcm_runtime { snd_ac97_resume(chip->ac97); /* (6) */ snd_mychip_restart_chip(chip); + /* (7) */ + snd_power_change_state(card, SNDRV_CTL_POWER_D0); return 0; } ]]> @@ -5587,8 +5653,48 @@ struct _snd_pcm_runtime { - OK, we have all callbacks now. Let's set up them now. In the - initialization of the card, add the following: + As shown in the above, it's better to save registers after + suspending the PCM operations via + snd_pcm_suspend_all() or + snd_pcm_suspend(). It means that the PCM + streams are already stoppped when the register snapshot is + taken. But, remind that you don't have to restart the PCM + stream in the resume callback. It'll be restarted via + trigger call with SNDRV_PCM_TRIGGER_RESUME + when necessary. + + + + OK, we have all callbacks now. Let's set them up. In the + initialization of the card, make sure that you can get the chip + data from the card instance, typically via + private_data field, in case you + created the chip data individually. + + + +private_data = chip; + .... + } +]]> + + + + When you created the chip data with + snd_card_new(), it's anyway accessible + via private_data field. @@ -5600,30 +5706,28 @@ struct _snd_pcm_runtime { struct snd_card *card; struct mychip *chip; .... - snd_card_set_pm_callback(card, snd_my_suspend, snd_my_resume, chip); + card = snd_card_new(index[dev], id[dev], THIS_MODULE, + sizeof(struct mychip)); + .... + chip = card->private_data; .... } ]]> - Here you don't have to put ifdef CONFIG_PM around, since it's already - checked in the header and expanded to empty if not needed. - If you need a space for saving the registers, you'll need to - allocate the buffer for it here, too, since it would be fatal + If you need a space for saving the registers, allocate the + buffer for it here, too, since it would be fatal if you cannot allocate a memory in the suspend phase. The allocated buffer should be released in the corresponding destructor. - And next, set suspend/resume callbacks to the pci_driver, - This can be done by passing a macro SND_PCI_PM_CALLBACKS - in the pci_driver struct. This macro is expanded to the correct - (global) callbacks if CONFIG_PM is set. + And next, set suspend/resume callbacks to the pci_driver. @@ -5633,7 +5737,10 @@ struct _snd_pcm_runtime { .id_table = snd_my_ids, .probe = snd_my_probe, .remove = __devexit_p(snd_my_remove), - SND_PCI_PM_CALLBACKS + #ifdef CONFIG_PM + .suspend = snd_my_suspend, + .resume = snd_my_resume, + #endif }; ]]> -- cgit v1.2.3-59-g8ed1b From 0ef797c5ca8a73853c827cf495caed44676cfe17 Mon Sep 17 00:00:00 2001 From: Clemens Ladisch Date: Mon, 21 Nov 2005 07:30:20 +0100 Subject: [ALSA] adjust documentation for higher card limit Modules: Documentation Fix all places in the docs where the card number limit is mentioned. Signed-off-by: Clemens Ladisch --- Documentation/sound/alsa/ALSA-Configuration.txt | 128 ++++++++++++------------ 1 file changed, 64 insertions(+), 64 deletions(-) (limited to 'Documentation') diff --git a/Documentation/sound/alsa/ALSA-Configuration.txt b/Documentation/sound/alsa/ALSA-Configuration.txt index 9a58ed9e6952..c30fd30a19e8 100644 --- a/Documentation/sound/alsa/ALSA-Configuration.txt +++ b/Documentation/sound/alsa/ALSA-Configuration.txt @@ -105,7 +105,7 @@ Prior to version 0.9.0rc4 options had a 'snd_' prefix. This was removed. Each of top level sound card module takes the following options. index - index (slot #) of sound card - - Values: 0 through 7 or negative + - Values: 0 through 31 or negative - If nonnegative, assign that index number - if negative, interpret as a bitmask of permissible indices; the first free permitted index is assigned @@ -134,7 +134,7 @@ Prior to version 0.9.0rc4 options had a 'snd_' prefix. This was removed. dma2 - second DMA # for AD1816A chip (PnP setup) clockfreq - Clock frequency for AD1816A chip (default = 0, 33000Hz) - Module supports up to 8 cards, autoprobe and PnP. + This module supports multiple cards, autoprobe and PnP. Module snd-ad1848 ----------------- @@ -145,7 +145,7 @@ Prior to version 0.9.0rc4 options had a 'snd_' prefix. This was removed. irq - IRQ # for AD1848 chip dma1 - DMA # for AD1848 chip (0,1,3) - Module supports up to 8 cards. This module does not support autoprobe + This module supports multiple cards. It does not support autoprobe thus main port must be specified!!! Other ports are optional. The power-management is supported. @@ -158,7 +158,7 @@ Prior to version 0.9.0rc4 options had a 'snd_' prefix. This was removed. ac97_quirk - AC'97 workaround for strange hardware See the description of intel8x0 module for details. - This module supports up to 8 cards. + This module supports multiple cards. Module snd-ali5451 ------------------ @@ -186,7 +186,7 @@ Prior to version 0.9.0rc4 options had a 'snd_' prefix. This was removed. mpu_irq - IRQ # for MPU-401 (PnP setup) fm_port - port # for OPL3 FM (PnP setup) - Module supports up to 8 cards, autoprobe and PnP. + This module supports multiple cards, autoprobe and PnP. The power-management is supported. @@ -198,7 +198,7 @@ Prior to version 0.9.0rc4 options had a 'snd_' prefix. This was removed. joystick_port - port # for legacy joystick support. 0 = disabled (default), 1 = auto-detect - Module supports up to 8 cards, autoprobe and PnP. + This module supports multiple cards, autoprobe and PnP. The power-management is supported. @@ -273,7 +273,7 @@ Prior to version 0.9.0rc4 options had a 'snd_' prefix. This was removed. dma1 - 1st DMA # for AZT2320 (WSS) chip (PnP setup) dma2 - 2nd DMA # for AZT2320 (WSS) chip (PnP setup) - Module supports up to 8 cards, PnP and autoprobe. + This module supports multiple cards, PnP and autoprobe. The power-management is supported. @@ -284,7 +284,7 @@ Prior to version 0.9.0rc4 options had a 'snd_' prefix. This was removed. joystick - Enable joystick (default off) - Module supports up to 8 cards. + This module supports multiple cards. Module snd-bt87x ---------------- @@ -294,7 +294,7 @@ Prior to version 0.9.0rc4 options had a 'snd_' prefix. This was removed. digital_rate - Override the default digital rate (Hz) load_all - Load the driver even if the card model isn't known - Module supports up to 8 cards. + This module supports multiple cards. Note: The default index value of this module is -2, i.e. the first slot is excluded. @@ -304,7 +304,7 @@ Prior to version 0.9.0rc4 options had a 'snd_' prefix. This was removed. Module for Creative Audigy LS and SB Live 24bit - Module supports up to 8 cards. + This module supports multiple cards. Module snd-cmi8330 @@ -320,7 +320,7 @@ Prior to version 0.9.0rc4 options had a 'snd_' prefix. This was removed. sbdma8 - 8bit DMA # for CMI8330 chip (SB16) sbdma16 - 16bit DMA # for CMI8330 chip (SB16) - Module supports up to 8 cards and autoprobe. + This module supports multiple cards and autoprobe. The power-management is supported. @@ -335,7 +335,7 @@ Prior to version 0.9.0rc4 options had a 'snd_' prefix. This was removed. (default = 1) joystick_port - Joystick port address (0 = disable, 1 = auto-detect) - Module supports autoprobe and multiple chips (max 8). + This module supports autoprobe and multiple cards. The power-management is supported. @@ -351,7 +351,7 @@ Prior to version 0.9.0rc4 options had a 'snd_' prefix. This was removed. dma1 - first DMA # for CS4231 chip dma2 - second DMA # for CS4231 chip - Module supports up to 8 cards. This module does not support autoprobe + This module supports multiple cards. This module does not support autoprobe thus main port must be specified!!! Other ports are optional. The power-management is supported. @@ -371,7 +371,7 @@ Prior to version 0.9.0rc4 options had a 'snd_' prefix. This was removed. dma2 - second DMA # for Yamaha CS4232 chip (0,1,3), -1 = disable isapnp - ISA PnP detection - 0 = disable, 1 = enable (default) - Module supports up to 8 cards. This module does not support autoprobe + This module supports multiple cards. This module does not support autoprobe thus main port must be specified!!! Other ports are optional. The power-management is supported. @@ -392,7 +392,7 @@ Prior to version 0.9.0rc4 options had a 'snd_' prefix. This was removed. dma2 - second DMA # for CS4236 chip (0,1,3), -1 = disable isapnp - ISA PnP detection - 0 = disable, 1 = enable (default) - Module supports up to 8 cards. This module does not support autoprobe + This module supports multiple cards. This module does not support autoprobe (if ISA PnP is not used) thus main port and control port must be specified!!! Other ports are optional. @@ -405,7 +405,7 @@ Prior to version 0.9.0rc4 options had a 'snd_' prefix. This was removed. dual_codec - Secondary codec ID (0 = disable, default) - Module supports up to 8 cards. + This module supports multiple cards. The power-management is supported. @@ -419,7 +419,7 @@ Prior to version 0.9.0rc4 options had a 'snd_' prefix. This was removed. thinkpad - Force to enable Thinkpad's CLKRUN control. mmap_valid - Support OSS mmap mode (default = 0). - Module supports up to 8 cards and autoprobe. + This module supports multiple cards and autoprobe. Usually external amp and CLKRUN controls are detected automatically from PCI sub vendor/device ids. If they don't work, give the options above explicitly. @@ -431,7 +431,7 @@ Prior to version 0.9.0rc4 options had a 'snd_' prefix. This was removed. Module for multifunction CS5535 companion PCI device - Module supports up to 8 cards. + This module supports multiple cards. Module snd-dt019x ----------------- @@ -446,7 +446,7 @@ Prior to version 0.9.0rc4 options had a 'snd_' prefix. This was removed. mpu_irq - IRQ # for MPU-401 (PnP setup) dma8 - DMA # (PnP setup) - Module supports up to 8 cards. This module is enabled only with + This module supports multiple cards. This module is enabled only with ISA PnP support. The power-management is supported. @@ -477,7 +477,7 @@ Prior to version 0.9.0rc4 options had a 'snd_' prefix. This was removed. given in MB unit. Default value is 128. enable_ir - enable IR - Module supports up to 8 cards and autoprobe. + This module supports multiple cards and autoprobe. Input & Output configurations [extin/extout] * Creative Card wo/Digital out [0x0003/0x1f03] @@ -500,7 +500,7 @@ Prior to version 0.9.0rc4 options had a 'snd_' prefix. This was removed. Module for Creative Emu10k1X (SB Live Dell OEM version) - Module supports up to 8 cards. + This module supports multiple cards. Module snd-ens1370 ------------------ @@ -511,7 +511,7 @@ Prior to version 0.9.0rc4 options had a 'snd_' prefix. This was removed. joystick - Enable joystick (default off) - Module supports up to 8 cards and autoprobe. + This module supports multiple cards and autoprobe. Module snd-ens1371 ------------------ @@ -524,7 +524,7 @@ Prior to version 0.9.0rc4 options had a 'snd_' prefix. This was removed. joystick_port - port # for joystick (0x200,0x208,0x210,0x218), 0 = disable (default), 1 = auto-detect - Module supports up to 8 cards and autoprobe. + This module supports multiple cards and autoprobe. Module snd-es968 ---------------- @@ -535,7 +535,7 @@ Prior to version 0.9.0rc4 options had a 'snd_' prefix. This was removed. irq - IRQ # for ES968 (SB8) chip (PnP setup) dma1 - DMA # for ES968 (SB8) chip (PnP setup) - Module supports up to 8 cards, PnP and autoprobe. + This module supports multiple cards, PnP and autoprobe. The power-management is supported. @@ -550,7 +550,7 @@ Prior to version 0.9.0rc4 options had a 'snd_' prefix. This was removed. mpu_irq - IRQ # for MPU-401 port (5,7,9,10) dma8 - DMA # for ES-1688 chip (0,1,3) - Module supports up to 8 cards and autoprobe (without MPU-401 port). + This module supports multiple cards and autoprobe (without MPU-401 port). Module snd-es18xx ----------------- @@ -565,8 +565,8 @@ Prior to version 0.9.0rc4 options had a 'snd_' prefix. This was removed. dma2 - first DMA # for ES-18xx chip (0,1,3) isapnp - ISA PnP detection - 0 = disable, 1 = enable (default) - Module supports up to 8 cards ISA PnP and autoprobe (without MPU-401 port - if native ISA PnP routines are not used). + This module supports multiple cards, ISA PnP and autoprobe (without MPU-401 + port if native ISA PnP routines are not used). When dma2 is equal with dma1, the driver works as half-duplex. The power-management is supported. @@ -576,7 +576,7 @@ Prior to version 0.9.0rc4 options had a 'snd_' prefix. This was removed. Module for sound cards based on ESS Solo-1 (ES1938,ES1946) chips. - Module supports up to 8 cards and autoprobe. + This module supports multiple cards and autoprobe. The power-management is supported. @@ -594,7 +594,7 @@ Prior to version 0.9.0rc4 options had a 'snd_' prefix. This was removed. enable_mpu - enable MPU401 (0 = off, 1 = on, 2 = auto (default)) joystick - enable joystick (default off) - Module supports up to 8 cards and autoprobe. + This module supports multiple cards and autoprobe. The power-management is supported. @@ -610,7 +610,7 @@ Prior to version 0.9.0rc4 options had a 'snd_' prefix. This was removed. - High 16-bits are video (radio) device number + 1 - example: 0x10002 (MediaForte 256-PCPR, device 1) - Module supports up to 8 cards and autoprobe. + This module supports multiple cards and autoprobe. The power-management is supported. @@ -627,7 +627,7 @@ Prior to version 0.9.0rc4 options had a 'snd_' prefix. This was removed. voices - GF1 voices limit (14-32) pcm_voices - reserved PCM voices - Module supports up to 8 cards and autoprobe. + This module supports multiple cards and autoprobe. Module snd-gusextreme --------------------- @@ -646,7 +646,7 @@ Prior to version 0.9.0rc4 options had a 'snd_' prefix. This was removed. voices - GF1 voices limit (14-32) pcm_voices - reserved PCM voices - Module supports up to 8 cards and autoprobe (without MPU-401 port). + This module supports multiple cards and autoprobe (without MPU-401 port). Module snd-gusmax ----------------- @@ -661,7 +661,7 @@ Prior to version 0.9.0rc4 options had a 'snd_' prefix. This was removed. voices - GF1 voices limit (14-32) pcm_voices - reserved PCM voices - Module supports up to 8 cards and autoprobe. + This module supports multiple cards and autoprobe. Module snd-hda-intel -------------------- @@ -730,7 +730,7 @@ Prior to version 0.9.0rc4 options had a 'snd_' prefix. This was removed. Module for RME Hammerfall DSP audio interface(s) - Module supports up to 8 cards. + This module supports multiple cards. Note: The firmware data can be automatically loaded via hotplug when CONFIG_FW_LOADER is set. Otherwise, you need to load @@ -788,7 +788,7 @@ Prior to version 0.9.0rc4 options had a 'snd_' prefix. This was removed. cs8427_timeout - reset timeout for the CS8427 chip (S/PDIF transciever) in msec resolution, default value is 500 (0.5 sec) - Module supports up to 8 cards and autoprobe. Note: The consumer part + This module supports multiple cards and autoprobe. Note: The consumer part is not used with all Envy24 based cards (for example in the MidiMan Delta serie). @@ -824,7 +824,7 @@ Prior to version 0.9.0rc4 options had a 'snd_' prefix. This was removed. aureon71, universe, k8x800, phase22, phase28, ms300, av710 - Module supports up to 8 cards and autoprobe. + This module supports multiple cards and autoprobe. Note: The supported board is detected by reading EEPROM or PCI SSID (if EEPROM isn't available). You can override the @@ -894,7 +894,7 @@ Prior to version 0.9.0rc4 options had a 'snd_' prefix. This was removed. effect - 1 = InterWave effects enable (default 0); requires 8 voices - Module supports up to 8 cards, autoprobe and ISA PnP. + This module supports multiple cards, autoprobe and ISA PnP. Module snd-interwave-stb ------------------------ @@ -914,14 +914,14 @@ Prior to version 0.9.0rc4 options had a 'snd_' prefix. This was removed. effect - 1 = InterWave effects enable (default 0); requires 8 voices - Module supports up to 8 cards, autoprobe and ISA PnP. + This module supports multiple cards, autoprobe and ISA PnP. Module snd-korg1212 ------------------- Module for Korg 1212 IO PCI card - Module supports up to 8 cards. + This module supports multiple cards. Module snd-maestro3 ------------------- @@ -933,7 +933,7 @@ Prior to version 0.9.0rc4 options had a 'snd_' prefix. This was removed. -1 for default pin (8 for allegro, 1 for others) - Module supports autoprobe and multiple chips (max 8). + This module supports autoprobe and multiple chips. Note: the binding of amplifier is dependent on hardware. If there is no sound even though all channels are unmuted, try to @@ -948,7 +948,7 @@ Prior to version 0.9.0rc4 options had a 'snd_' prefix. This was removed. Module for Digigram miXart8 sound cards. - Module supports multiple cards. + This module supports multiple cards. Note: One miXart8 board will be represented as 4 alsa cards. See MIXART.txt for details. @@ -967,7 +967,7 @@ Prior to version 0.9.0rc4 options had a 'snd_' prefix. This was removed. irq - IRQ number or -1 (disable) pnp - PnP detection - 0 = disable, 1 = enable (default) - Module supports multiple devices (max 8) and PnP. + This module supports multiple devices and PnP. Module snd-mtpav ---------------- @@ -1053,7 +1053,7 @@ Prior to version 0.9.0rc4 options had a 'snd_' prefix. This was removed. dma2 - second DMA # for Yamaha OPL3-SA chip (0,1,3), -1 = disable isapnp - ISA PnP detection - 0 = disable, 1 = enable (default) - Module supports up to 8 cards and ISA PnP. This module does not support + This module supports multiple cards and ISA PnP. It does not support autoprobe (if ISA PnP is not used) thus all ports must be specified!!! The power-management is supported. @@ -1131,14 +1131,14 @@ Prior to version 0.9.0rc4 options had a 'snd_' prefix. This was removed. Module for RME Digi32, Digi32 Pro and Digi32/8 (Sek'd Prodif32, Prodif96 and Prodif Gold) sound cards. - Module supports up to 8 cards. + This module supports multiple cards. Module snd-rme96 ---------------- Module for RME Digi96, Digi96/8 and Digi96/8 PRO/PAD/PST sound cards. - Module supports up to 8 cards. + This module supports multiple cards. Module snd-rme9652 ------------------ @@ -1148,7 +1148,7 @@ Prior to version 0.9.0rc4 options had a 'snd_' prefix. This was removed. precise_ptr - Enable precise pointer (doesn't work reliably). (default = 0) - Module supports up to 8 cards. + This module supports multiple cards. Note: snd-page-alloc module does the job which snd-hammerfall-mem module did formerly. It will allocate the buffers in advance @@ -1178,7 +1178,7 @@ Prior to version 0.9.0rc4 options had a 'snd_' prefix. This was removed. irq - IRQ # for SB DSP chip (5,7,9,10) dma8 - DMA # for SB DSP chip (1,3) - Module supports up to 8 cards and autoprobe. + This module supports multiple cards and autoprobe. The power-management is supported. @@ -1200,7 +1200,7 @@ Prior to version 0.9.0rc4 options had a 'snd_' prefix. This was removed. csp - ASP/CSP chip support - 0 = disable (default), 1 = enable isapnp - ISA PnP detection - 0 = disable, 1 = enable (default) - Module supports up to 8 cards, autoprobe and ISA PnP. + This module supports multiple cards, autoprobe and ISA PnP. Note: To use Vibra16X cards in 16-bit half duplex mode, you must disable 16bit DMA with dma16 = -1 module parameter. @@ -1220,7 +1220,7 @@ Prior to version 0.9.0rc4 options had a 'snd_' prefix. This was removed. irq - IRQ # (7,9,10,11) dma1 - DMA # - Module supports up to 8 cards. + This module supports multiple cards. The power-management is supported. @@ -1234,7 +1234,7 @@ Prior to version 0.9.0rc4 options had a 'snd_' prefix. This was removed. mpu_irq - MPU-401 IRQ # (PnP setup) dma - DMA # (PnP setup) - Module supports up to 8 cards. ISA PnP must be enabled. + This module supports multiple cards. ISA PnP must be enabled. You need sscape_ctl tool in alsa-tools package for loading the microcode. @@ -1243,21 +1243,21 @@ Prior to version 0.9.0rc4 options had a 'snd_' prefix. This was removed. Module for AMD7930 sound chips found on Sparcs. - Module supports up to 8 cards. + This module supports multiple cards. Module snd-sun-cs4231 (on sparc only) ------------------------------------- Module for CS4231 sound chips found on Sparcs. - Module supports up to 8 cards. + This module supports multiple cards. Module snd-sun-dbri (on sparc only) ----------------------------------- Module for DBRI sound chips found on Sparcs. - Module supports up to 8 cards. + This module supports multiple cards. Module snd-wavefront -------------------- @@ -1277,7 +1277,7 @@ Prior to version 0.9.0rc4 options had a 'snd_' prefix. This was removed. dma2 - DMA2 # for CS4232 PCM interface. isapnp - ISA PnP detection - 0 = disable, 1 = enable (default) - Module supports up to 8 cards and ISA PnP. + This module supports multiple cards and ISA PnP. Module snd-sonicvibes --------------------- @@ -1289,7 +1289,7 @@ Prior to version 0.9.0rc4 options had a 'snd_' prefix. This was removed. - SoundCard must have onboard SRAM for this. mge - Mic Gain Enable - 1 = enable, 0 = disable (default) - Module supports up to 8 cards and autoprobe. + This module supports multiple cards and autoprobe. Module snd-serial-u16550 ------------------------ @@ -1308,7 +1308,7 @@ Prior to version 0.9.0rc4 options had a 'snd_' prefix. This was removed. 0 = Soundcanvas, 1 = MS-124T, 2 = MS-124W S/A, 3 = MS-124W M/B, 4 = Generic - Module supports up to 8 cards. This module does not support autoprobe + This module supports multiple cards. This module does not support autoprobe thus the main port must be specified!!! Other options are optional. Module snd-trident @@ -1327,7 +1327,7 @@ Prior to version 0.9.0rc4 options had a 'snd_' prefix. This was removed. pcm_channels - max channels (voices) reserved for PCM wavetable_size - max wavetable size in kB (4-?kb) - Module supports up to 8 cards and autoprobe. + This module supports multiple cards and autoprobe. The power-management is supported. @@ -1339,14 +1339,14 @@ Prior to version 0.9.0rc4 options had a 'snd_' prefix. This was removed. vid - Vendor ID for the device (optional) pid - Product ID for the device (optional) - This module supports up to 8 cards, autoprobe and hotplugging. + This module supports multiple devices, autoprobe and hotplugging. Module snd-usb-usx2y -------------------- Module for Tascam USB US-122, US-224 and US-428 devices. - This module supports up to 8 cards, autoprobe and hotplugging. + This module supports multiple devices, autoprobe and hotplugging. Note: you need to load the firmware via usx2yloader utility included in alsa-tools and alsa-firmware packages. @@ -1428,9 +1428,9 @@ Prior to version 0.9.0rc4 options had a 'snd_' prefix. This was removed. This module creates virtual rawmidi devices which communicate to the corresponding ALSA sequencer ports. - midi_devs - MIDI devices # (1-8, default=4) + midi_devs - MIDI devices # (1-4, default=4) - Module supports up to 8 cards. + This module supports multiple cards. Module snd-vx222 ---------------- @@ -1440,7 +1440,7 @@ Prior to version 0.9.0rc4 options had a 'snd_' prefix. This was removed. mic - Enable Microphone on V222 Mic (NYI) ibl - Capture IBL size. (default = 0, minimum size) - Module supports up to 8 cards. + This module supports multiple cards. When the driver is compiled as a module and the hotplug firmware is supported, the firmware data is loaded via hotplug automatically. @@ -1468,7 +1468,7 @@ Prior to version 0.9.0rc4 options had a 'snd_' prefix. This was removed. ibl - Capture IBL size. (default = 0, minimum size) - Module supports up to 8 cards. The module is compiled only when + This module supports multiple cards. The module is compiled only when PCMCIA is supported on kernel. With the older 2.6.x kernel, to activate the driver via the card @@ -1504,7 +1504,7 @@ Prior to version 0.9.0rc4 options had a 'snd_' prefix. This was removed. 1 (auto-detect) rear_switch - enable shared rear/line-in switch (bool) - Module supports autoprobe and multiple chips (max 8). + This module supports autoprobe and multiple chips. The power-management is supported. -- cgit v1.2.3-59-g8ed1b From e12229b4d2b7863b1baaeca759aa87703bf9fdf8 Mon Sep 17 00:00:00 2001 From: Markus Bollinger Date: Tue, 6 Dec 2005 13:55:26 +0100 Subject: [ALSA] Add PCXHR driver Modules: Documentation,PCI drivers,Digigram PCXHR driver Add Digigram PCXHR driver. Signed-off-by: Takashi Iwai --- Documentation/sound/alsa/ALSA-Configuration.txt | 7 + sound/pci/Kconfig | 11 + sound/pci/Makefile | 1 + sound/pci/pcxhr/Makefile | 2 + sound/pci/pcxhr/pcxhr.c | 1367 +++++++++++++++++++++++ sound/pci/pcxhr/pcxhr.h | 188 ++++ sound/pci/pcxhr/pcxhr_core.c | 1214 ++++++++++++++++++++ sound/pci/pcxhr/pcxhr_core.h | 200 ++++ sound/pci/pcxhr/pcxhr_hwdep.c | 438 ++++++++ sound/pci/pcxhr/pcxhr_hwdep.h | 40 + sound/pci/pcxhr/pcxhr_mixer.c | 1020 +++++++++++++++++ sound/pci/pcxhr/pcxhr_mixer.h | 29 + 12 files changed, 4517 insertions(+) create mode 100644 sound/pci/pcxhr/Makefile create mode 100644 sound/pci/pcxhr/pcxhr.c create mode 100644 sound/pci/pcxhr/pcxhr.h create mode 100644 sound/pci/pcxhr/pcxhr_core.c create mode 100644 sound/pci/pcxhr/pcxhr_core.h create mode 100644 sound/pci/pcxhr/pcxhr_hwdep.c create mode 100644 sound/pci/pcxhr/pcxhr_hwdep.h create mode 100644 sound/pci/pcxhr/pcxhr_mixer.c create mode 100644 sound/pci/pcxhr/pcxhr_mixer.h (limited to 'Documentation') diff --git a/Documentation/sound/alsa/ALSA-Configuration.txt b/Documentation/sound/alsa/ALSA-Configuration.txt index c30fd30a19e8..d2578013e829 100644 --- a/Documentation/sound/alsa/ALSA-Configuration.txt +++ b/Documentation/sound/alsa/ALSA-Configuration.txt @@ -1103,6 +1103,13 @@ Prior to version 0.9.0rc4 options had a 'snd_' prefix. This was removed. This module supports only one card, autoprobe and PnP. + Module snd-pcxhr + ---------------- + + Module for Digigram PCXHR boards + + This module supports multiple cards. + Module snd-powermac (on ppc only) --------------------------------- diff --git a/sound/pci/Kconfig b/sound/pci/Kconfig index ef7bdc5a9657..1e2e19305e38 100644 --- a/sound/pci/Kconfig +++ b/sound/pci/Kconfig @@ -455,6 +455,17 @@ config SND_NM256 To compile this driver as a module, choose M here: the module will be called snd-nm256. +config SND_PCXHR + tristate "Digigram PCXHR" + depends on SND + select SND_PCM + select SND_HWDEP + help + Say Y here to include support for Digigram PCXHR boards. + + To compile this driver as a module, choose M here: the module + will be called snd-pcxhr. + config SND_RME32 tristate "RME Digi32, 32/8, 32 PRO" depends on SND diff --git a/sound/pci/Makefile b/sound/pci/Makefile index 82a9c734f84d..a6c3cd58fe94 100644 --- a/sound/pci/Makefile +++ b/sound/pci/Makefile @@ -61,6 +61,7 @@ obj-$(CONFIG_SND) += \ korg1212/ \ mixart/ \ nm256/ \ + pcxhr/ \ rme9652/ \ trident/ \ ymfpci/ \ diff --git a/sound/pci/pcxhr/Makefile b/sound/pci/pcxhr/Makefile new file mode 100644 index 000000000000..10473c05918d --- /dev/null +++ b/sound/pci/pcxhr/Makefile @@ -0,0 +1,2 @@ +snd-pcxhr-objs := pcxhr.o pcxhr_hwdep.o pcxhr_mixer.o pcxhr_core.o +obj-$(CONFIG_SND_PCXHR) += snd-pcxhr.o diff --git a/sound/pci/pcxhr/pcxhr.c b/sound/pci/pcxhr/pcxhr.c new file mode 100644 index 000000000000..b8c0853a8278 --- /dev/null +++ b/sound/pci/pcxhr/pcxhr.c @@ -0,0 +1,1367 @@ +/* + * Driver for Digigram pcxhr compatible soundcards + * + * main file with alsa callbacks + * + * Copyright (c) 2004 by Digigram + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "pcxhr.h" +#include "pcxhr_mixer.h" +#include "pcxhr_hwdep.h" +#include "pcxhr_core.h" + +#define DRIVER_NAME "pcxhr" + +MODULE_AUTHOR("Markus Bollinger "); +MODULE_DESCRIPTION("Digigram " DRIVER_NAME " " PCXHR_DRIVER_VERSION_STRING); +MODULE_LICENSE("GPL"); +MODULE_SUPPORTED_DEVICE("{{Digigram," DRIVER_NAME "}}"); + +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 mono[SNDRV_CARDS]; /* capture in mono only */ + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "Index value for Digigram " DRIVER_NAME " soundcard"); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string for Digigram " DRIVER_NAME " soundcard"); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "Enable Digigram " DRIVER_NAME " soundcard"); +module_param_array(mono, bool, NULL, 0444); +MODULE_PARM_DESC(mono, "Mono capture mode (default is stereo)"); + +enum { + PCI_ID_VX882HR, + PCI_ID_PCX882HR, + PCI_ID_VX881HR, + PCI_ID_PCX881HR, + PCI_ID_PCX1222HR, + PCI_ID_PCX1221HR, + PCI_ID_LAST +}; + +static struct pci_device_id pcxhr_ids[] = { + { 0x10b5, 0x9656, 0x1369, 0xb001, 0, 0, PCI_ID_VX882HR, }, /* VX882HR */ + { 0x10b5, 0x9656, 0x1369, 0xb101, 0, 0, PCI_ID_PCX882HR, }, /* PCX882HR */ + { 0x10b5, 0x9656, 0x1369, 0xb201, 0, 0, PCI_ID_VX881HR, }, /* VX881HR */ + { 0x10b5, 0x9656, 0x1369, 0xb301, 0, 0, PCI_ID_PCX881HR, }, /* PCX881HR */ + { 0x10b5, 0x9656, 0x1369, 0xb501, 0, 0, PCI_ID_PCX1222HR, }, /* PCX1222HR */ + { 0x10b5, 0x9656, 0x1369, 0xb701, 0, 0, PCI_ID_PCX1221HR, }, /* PCX1221HR */ + { 0, } +}; + +MODULE_DEVICE_TABLE(pci, pcxhr_ids); + +struct board_parameters { + char* board_name; + short playback_chips; + short capture_chips; + short firmware_num; +}; +static struct board_parameters pcxhr_board_params[] = { +[PCI_ID_VX882HR] = { "VX882HR", 4, 4, 41, }, +[PCI_ID_PCX882HR] = { "PCX882HR", 4, 4, 41, }, +[PCI_ID_VX881HR] = { "VX881HR", 4, 4, 41, }, +[PCI_ID_PCX881HR] = { "PCX881HR", 4, 4, 41, }, +[PCI_ID_PCX1222HR] = { "PCX1222HR", 6, 1, 42, }, +[PCI_ID_PCX1221HR] = { "PCX1221HR", 6, 1, 42, }, +}; + + +static int pcxhr_pll_freq_register(unsigned int freq, unsigned int* pllreg, + unsigned int* realfreq) +{ + unsigned int reg; + + if (freq < 6900 || freq > 110250) + return -EINVAL; + reg = (28224000 * 10) / freq; + reg = (reg + 5) / 10; + if (reg < 0x200) + *pllreg = reg + 0x800; + else if (reg < 0x400) + *pllreg = reg & 0x1ff; + else if (reg < 0x800) { + *pllreg = ((reg >> 1) & 0x1ff) + 0x200; + reg &= ~1; + } else { + *pllreg = ((reg >> 2) & 0x1ff) + 0x400; + reg &= ~3; + } + if (realfreq) + *realfreq = ((28224000 * 10) / reg + 5) / 10; + return 0; +} + + +#define PCXHR_FREQ_REG_MASK 0x1f +#define PCXHR_FREQ_QUARTZ_48000 0x00 +#define PCXHR_FREQ_QUARTZ_24000 0x01 +#define PCXHR_FREQ_QUARTZ_12000 0x09 +#define PCXHR_FREQ_QUARTZ_32000 0x08 +#define PCXHR_FREQ_QUARTZ_16000 0x04 +#define PCXHR_FREQ_QUARTZ_8000 0x0c +#define PCXHR_FREQ_QUARTZ_44100 0x02 +#define PCXHR_FREQ_QUARTZ_22050 0x0a +#define PCXHR_FREQ_QUARTZ_11025 0x06 +#define PCXHR_FREQ_PLL 0x05 +#define PCXHR_FREQ_QUARTZ_192000 0x10 +#define PCXHR_FREQ_QUARTZ_96000 0x18 +#define PCXHR_FREQ_QUARTZ_176400 0x14 +#define PCXHR_FREQ_QUARTZ_88200 0x1c +#define PCXHR_FREQ_QUARTZ_128000 0x12 +#define PCXHR_FREQ_QUARTZ_64000 0x1a + +#define PCXHR_FREQ_WORD_CLOCK 0x0f +#define PCXHR_FREQ_SYNC_AES 0x0e +#define PCXHR_FREQ_AES_1 0x07 +#define PCXHR_FREQ_AES_2 0x0b +#define PCXHR_FREQ_AES_3 0x03 +#define PCXHR_FREQ_AES_4 0x0d + +#define PCXHR_MODIFY_CLOCK_S_BIT 0x04 + +#define PCXHR_IRQ_TIMER_FREQ 92000 +#define PCXHR_IRQ_TIMER_PERIOD 48 + +static int pcxhr_get_clock_reg(struct pcxhr_mgr *mgr, unsigned int rate, + unsigned int *reg, unsigned int *freq) +{ + unsigned int val, realfreq, pllreg; + struct pcxhr_rmh rmh; + int err; + + realfreq = rate; + switch (mgr->use_clock_type) { + case PCXHR_CLOCK_TYPE_INTERNAL : /* clock by quartz or pll */ + switch (rate) { + case 48000 : val = PCXHR_FREQ_QUARTZ_48000; break; + case 24000 : val = PCXHR_FREQ_QUARTZ_24000; break; + case 12000 : val = PCXHR_FREQ_QUARTZ_12000; break; + case 32000 : val = PCXHR_FREQ_QUARTZ_32000; break; + case 16000 : val = PCXHR_FREQ_QUARTZ_16000; break; + case 8000 : val = PCXHR_FREQ_QUARTZ_8000; break; + case 44100 : val = PCXHR_FREQ_QUARTZ_44100; break; + case 22050 : val = PCXHR_FREQ_QUARTZ_22050; break; + case 11025 : val = PCXHR_FREQ_QUARTZ_11025; break; + case 192000 : val = PCXHR_FREQ_QUARTZ_192000; break; + case 96000 : val = PCXHR_FREQ_QUARTZ_96000; break; + case 176400 : val = PCXHR_FREQ_QUARTZ_176400; break; + case 88200 : val = PCXHR_FREQ_QUARTZ_88200; break; + case 128000 : val = PCXHR_FREQ_QUARTZ_128000; break; + case 64000 : val = PCXHR_FREQ_QUARTZ_64000; break; + default : + val = PCXHR_FREQ_PLL; + /* get the value for the pll register */ + err = pcxhr_pll_freq_register(rate, &pllreg, &realfreq); + if (err) + return err; + pcxhr_init_rmh(&rmh, CMD_ACCESS_IO_WRITE); + rmh.cmd[0] |= IO_NUM_REG_GENCLK; + rmh.cmd[1] = pllreg & MASK_DSP_WORD; + rmh.cmd[2] = pllreg >> 24; + rmh.cmd_len = 3; + err = pcxhr_send_msg(mgr, &rmh); + if (err < 0) { + snd_printk(KERN_ERR + "error CMD_ACCESS_IO_WRITE for PLL register : %x!\n", + err ); + return err; + } + } + break; + case PCXHR_CLOCK_TYPE_WORD_CLOCK : val = PCXHR_FREQ_WORD_CLOCK; break; + case PCXHR_CLOCK_TYPE_AES_SYNC : val = PCXHR_FREQ_SYNC_AES; break; + case PCXHR_CLOCK_TYPE_AES_1 : val = PCXHR_FREQ_AES_1; break; + case PCXHR_CLOCK_TYPE_AES_2 : val = PCXHR_FREQ_AES_2; break; + case PCXHR_CLOCK_TYPE_AES_3 : val = PCXHR_FREQ_AES_3; break; + case PCXHR_CLOCK_TYPE_AES_4 : val = PCXHR_FREQ_AES_4; break; + default : return -EINVAL; + } + *reg = val; + *freq = realfreq; + return 0; +} + + +int pcxhr_set_clock(struct pcxhr_mgr *mgr, unsigned int rate) +{ + unsigned int val, realfreq, speed; + struct pcxhr_rmh rmh; + int err, changed; + + if (rate == 0) + return 0; /* nothing to do */ + + err = pcxhr_get_clock_reg(mgr, rate, &val, &realfreq); + if (err) + return err; + + /* codec speed modes */ + if (rate < 55000) + speed = 0; /* single speed */ + else if (rate < 100000) + speed = 1; /* dual speed */ + else + speed = 2; /* quad speed */ + if (mgr->codec_speed != speed) { + pcxhr_init_rmh(&rmh, CMD_ACCESS_IO_WRITE); /* mute outputs */ + rmh.cmd[0] |= IO_NUM_REG_MUTE_OUT; + err = pcxhr_send_msg(mgr, &rmh); + if (err) + return err; + + pcxhr_init_rmh(&rmh, CMD_ACCESS_IO_WRITE); /* set speed ratio */ + rmh.cmd[0] |= IO_NUM_SPEED_RATIO; + rmh.cmd[1] = speed; + rmh.cmd_len = 2; + err = pcxhr_send_msg(mgr, &rmh); + if (err) + return err; + } + /* set the new frequency */ + snd_printdd("clock register : set %x\n", val); + err = pcxhr_write_io_num_reg_cont(mgr, PCXHR_FREQ_REG_MASK, val, &changed); + if (err) + return err; + mgr->sample_rate_real = realfreq; + mgr->cur_clock_type = mgr->use_clock_type; + + /* unmute after codec speed modes */ + if (mgr->codec_speed != speed) { + pcxhr_init_rmh(&rmh, CMD_ACCESS_IO_READ); /* unmute outputs */ + rmh.cmd[0] |= IO_NUM_REG_MUTE_OUT; + err = pcxhr_send_msg(mgr, &rmh); + if (err) + return err; + mgr->codec_speed = speed; /* save new codec speed */ + } + + if (changed) { + pcxhr_init_rmh(&rmh, CMD_MODIFY_CLOCK); + rmh.cmd[0] |= PCXHR_MODIFY_CLOCK_S_BIT; /* resync fifos */ + if (rate < PCXHR_IRQ_TIMER_FREQ) + rmh.cmd[1] = PCXHR_IRQ_TIMER_PERIOD; + else + rmh.cmd[1] = PCXHR_IRQ_TIMER_PERIOD * 2; + rmh.cmd[2] = rate; + rmh.cmd_len = 3; + err = pcxhr_send_msg(mgr, &rmh); + if (err) + return err; + } + snd_printdd("pcxhr_set_clock to %dHz (realfreq=%d)\n", rate, realfreq); + return 0; +} + + +int pcxhr_get_external_clock(struct pcxhr_mgr *mgr, enum pcxhr_clock_type clock_type, + int *sample_rate) +{ + struct pcxhr_rmh rmh; + unsigned char reg; + int err, rate; + + switch (clock_type) { + case PCXHR_CLOCK_TYPE_WORD_CLOCK : reg = REG_STATUS_WORD_CLOCK; break; + case PCXHR_CLOCK_TYPE_AES_SYNC : reg = REG_STATUS_AES_SYNC; break; + case PCXHR_CLOCK_TYPE_AES_1 : reg = REG_STATUS_AES_1; break; + case PCXHR_CLOCK_TYPE_AES_2 : reg = REG_STATUS_AES_2; break; + case PCXHR_CLOCK_TYPE_AES_3 : reg = REG_STATUS_AES_3; break; + case PCXHR_CLOCK_TYPE_AES_4 : reg = REG_STATUS_AES_4; break; + default : return -EINVAL; + } + pcxhr_init_rmh(&rmh, CMD_ACCESS_IO_READ); + rmh.cmd_len = 2; + rmh.cmd[0] |= IO_NUM_REG_STATUS; + if (mgr->last_reg_stat != reg) { + rmh.cmd[1] = reg; + err = pcxhr_send_msg(mgr, &rmh); + if (err) + return err; + udelay(100); /* wait minimum 2 sample_frames at 32kHz ! */ + mgr->last_reg_stat = reg; + } + rmh.cmd[1] = REG_STATUS_CURRENT; + err = pcxhr_send_msg(mgr, &rmh); + if (err) + return err; + switch (rmh.stat[1] & 0x0f) { + case REG_STATUS_SYNC_32000 : rate = 32000; break; + case REG_STATUS_SYNC_44100 : rate = 44100; break; + case REG_STATUS_SYNC_48000 : rate = 48000; break; + case REG_STATUS_SYNC_64000 : rate = 64000; break; + case REG_STATUS_SYNC_88200 : rate = 88200; break; + case REG_STATUS_SYNC_96000 : rate = 96000; break; + case REG_STATUS_SYNC_128000 : rate = 128000; break; + case REG_STATUS_SYNC_176400 : rate = 176400; break; + case REG_STATUS_SYNC_192000 : rate = 192000; break; + default: rate = 0; + } + snd_printdd("External clock is at %d Hz\n", rate); + *sample_rate = rate; + return 0; +} + + +/* + * start or stop playback/capture substream + */ +static int pcxhr_set_stream_state(struct pcxhr_stream *stream) +{ + int err; + struct snd_pcxhr *chip; + struct pcxhr_rmh rmh; + int stream_mask, start; + + if (stream->status == PCXHR_STREAM_STATUS_SCHEDULE_RUN) + start = 1; + else { + if (stream->status != PCXHR_STREAM_STATUS_SCHEDULE_STOP) { + snd_printk(KERN_ERR "ERROR pcxhr_set_stream_state CANNOT be stopped\n"); + return -EINVAL; + } + start = 0; + } + if (!stream->substream) + return -EINVAL; + + stream->timer_abs_periods = 0; + stream->timer_period_frag = 0; /* reset theoretical stream pos */ + stream->timer_buf_periods = 0; + stream->timer_is_synced = 0; + + stream_mask = stream->pipe->is_capture ? 1 : 1<substream->number; + + pcxhr_init_rmh(&rmh, start ? CMD_START_STREAM : CMD_STOP_STREAM); + pcxhr_set_pipe_cmd_params(&rmh, stream->pipe->is_capture, + stream->pipe->first_audio, 0, stream_mask); + + chip = snd_pcm_substream_chip(stream->substream); + + err = pcxhr_send_msg(chip->mgr, &rmh); + if (err) + snd_printk(KERN_ERR "ERROR pcxhr_set_stream_state err=%x;\n", err); + stream->status = start ? PCXHR_STREAM_STATUS_STARTED : PCXHR_STREAM_STATUS_STOPPED; + return err; +} + +#define HEADER_FMT_BASE_LIN 0xfed00000 +#define HEADER_FMT_BASE_FLOAT 0xfad00000 +#define HEADER_FMT_INTEL 0x00008000 +#define HEADER_FMT_24BITS 0x00004000 +#define HEADER_FMT_16BITS 0x00002000 +#define HEADER_FMT_UPTO11 0x00000200 +#define HEADER_FMT_UPTO32 0x00000100 +#define HEADER_FMT_MONO 0x00000080 + +static int pcxhr_set_format(struct pcxhr_stream *stream) +{ + int err, is_capture, sample_rate, stream_num; + struct snd_pcxhr *chip; + struct pcxhr_rmh rmh; + unsigned int header; + + switch (stream->format) { + case SNDRV_PCM_FORMAT_U8: + header = HEADER_FMT_BASE_LIN; + break; + case SNDRV_PCM_FORMAT_S16_LE: + header = HEADER_FMT_BASE_LIN | HEADER_FMT_16BITS | HEADER_FMT_INTEL; + break; + case SNDRV_PCM_FORMAT_S16_BE: + header = HEADER_FMT_BASE_LIN | HEADER_FMT_16BITS; + break; + case SNDRV_PCM_FORMAT_S24_3LE: + header = HEADER_FMT_BASE_LIN | HEADER_FMT_24BITS | HEADER_FMT_INTEL; + break; + case SNDRV_PCM_FORMAT_S24_3BE: + header = HEADER_FMT_BASE_LIN | HEADER_FMT_24BITS; + break; + case SNDRV_PCM_FORMAT_FLOAT_LE: + header = HEADER_FMT_BASE_FLOAT | HEADER_FMT_INTEL; + break; + default: + snd_printk(KERN_ERR "error pcxhr_set_format() : unknown format\n"); + return -EINVAL; + } + chip = snd_pcm_substream_chip(stream->substream); + + sample_rate = chip->mgr->sample_rate; + if (sample_rate <= 32000 && sample_rate !=0) { + if (sample_rate <= 11025) + header |= HEADER_FMT_UPTO11; + else + header |= HEADER_FMT_UPTO32; + } + if (stream->channels == 1) + header |= HEADER_FMT_MONO; + + is_capture = stream->pipe->is_capture; + stream_num = is_capture ? 0 : stream->substream->number; + + pcxhr_init_rmh(&rmh, is_capture ? CMD_FORMAT_STREAM_IN : CMD_FORMAT_STREAM_OUT); + pcxhr_set_pipe_cmd_params(&rmh, is_capture, stream->pipe->first_audio, stream_num, 0); + if (is_capture) + rmh.cmd[0] |= 1<<12; + rmh.cmd[1] = 0; + rmh.cmd[2] = header >> 8; + rmh.cmd[3] = (header & 0xff) << 16; + rmh.cmd_len = 4; + err = pcxhr_send_msg(chip->mgr, &rmh); + if (err) + snd_printk(KERN_ERR "ERROR pcxhr_set_format err=%x;\n", err); + return err; +} + +static int pcxhr_update_r_buffer(struct pcxhr_stream *stream) +{ + int err, is_capture, stream_num; + struct pcxhr_rmh rmh; + struct snd_pcm_substream *subs = stream->substream; + struct snd_pcxhr *chip = snd_pcm_substream_chip(subs); + + is_capture = (subs->stream == SNDRV_PCM_STREAM_CAPTURE); + stream_num = is_capture ? 0 : subs->number; + + snd_printdd("pcxhr_update_r_buffer(pcm%c%d) : addr(%p) bytes(%x) subs(%d)\n", + is_capture ? 'c' : 'p', + chip->chip_idx, (void*)subs->runtime->dma_addr, + subs->runtime->dma_bytes, subs->number); + + pcxhr_init_rmh(&rmh, CMD_UPDATE_R_BUFFERS); + pcxhr_set_pipe_cmd_params(&rmh, is_capture, stream->pipe->first_audio, stream_num, 0); + + snd_assert(subs->runtime->dma_bytes < 0x200000); /* max buffer size is 2 MByte */ + rmh.cmd[1] = subs->runtime->dma_bytes * 8; /* size in bits */ + rmh.cmd[2] = subs->runtime->dma_addr >> 24; /* most significant byte */ + rmh.cmd[2] |= 1<<19; /* this is a circular buffer */ + rmh.cmd[3] = subs->runtime->dma_addr & MASK_DSP_WORD; /* least 3 significant bytes */ + rmh.cmd_len = 4; + err = pcxhr_send_msg(chip->mgr, &rmh); + if (err) + snd_printk(KERN_ERR "ERROR CMD_UPDATE_R_BUFFERS err=%x;\n", err); + return err; +} + + +#if 0 +static int pcxhr_pipe_sample_count(struct pcxhr_stream *stream, snd_pcm_uframes_t *sample_count) +{ + struct pcxhr_rmh rmh; + int err; + pcxhr_t *chip = snd_pcm_substream_chip(stream->substream); + pcxhr_init_rmh(&rmh, CMD_PIPE_SAMPLE_COUNT); + pcxhr_set_pipe_cmd_params(&rmh, stream->pipe->is_capture, 0, 0, + 1<pipe->first_audio); + err = pcxhr_send_msg(chip->mgr, &rmh); + if (err == 0) { + *sample_count = ((snd_pcm_uframes_t)rmh.stat[0]) << 24; + *sample_count += (snd_pcm_uframes_t)rmh.stat[1]; + } + snd_printdd("PIPE_SAMPLE_COUNT = %lx\n", *sample_count); + return err; +} +#endif + +static inline int pcxhr_stream_scheduled_get_pipe(struct pcxhr_stream *stream, + struct pcxhr_pipe **pipe) +{ + if (stream->status == PCXHR_STREAM_STATUS_SCHEDULE_RUN) { + *pipe = stream->pipe; + return 1; + } + return 0; +} + +static void pcxhr_trigger_tasklet(unsigned long arg) +{ + unsigned long flags; + int i, j, err; + struct pcxhr_pipe *pipe; + struct snd_pcxhr *chip; + struct pcxhr_mgr *mgr = (struct pcxhr_mgr*)(arg); + int capture_mask = 0; + int playback_mask = 0; + +#ifdef CONFIG_SND_DEBUG_DETECT + struct timeval my_tv1, my_tv2; + do_gettimeofday(&my_tv1); +#endif + down(&mgr->setup_mutex); + + /* check the pipes concerned and build pipe_array */ + for (i = 0; i < mgr->num_cards; i++) { + chip = mgr->chip[i]; + for (j = 0; j < chip->nb_streams_capt; j++) { + if (pcxhr_stream_scheduled_get_pipe(&chip->capture_stream[j], &pipe)) + capture_mask |= (1 << pipe->first_audio); + } + for (j = 0; j < chip->nb_streams_play; j++) { + if (pcxhr_stream_scheduled_get_pipe(&chip->playback_stream[j], &pipe)) { + playback_mask |= (1 << pipe->first_audio); + break; /* add only once, as all playback streams of + * one chip use the same pipe + */ + } + } + } + if (capture_mask == 0 && playback_mask == 0) { + up(&mgr->setup_mutex); + snd_printk(KERN_ERR "pcxhr_trigger_tasklet : no pipes\n"); + return; + } + + snd_printdd("pcxhr_trigger_tasklet : playback_mask=%x capture_mask=%x\n", + playback_mask, capture_mask); + + /* synchronous stop of all the pipes concerned */ + err = pcxhr_set_pipe_state(mgr, playback_mask, capture_mask, 0); + if (err) { + up(&mgr->setup_mutex); + snd_printk(KERN_ERR "pcxhr_trigger_tasklet : error stop pipes (P%x C%x)\n", + playback_mask, capture_mask); + return; + } + + /* unfortunately the dsp lost format and buffer info with the stop pipe */ + for (i = 0; i < mgr->num_cards; i++) { + struct pcxhr_stream *stream; + chip = mgr->chip[i]; + for (j = 0; j < chip->nb_streams_capt; j++) { + stream = &chip->capture_stream[j]; + if (pcxhr_stream_scheduled_get_pipe(stream, &pipe)) { + err = pcxhr_set_format(stream); + err = pcxhr_update_r_buffer(stream); + } + } + for (j = 0; j < chip->nb_streams_play; j++) { + stream = &chip->playback_stream[j]; + if (pcxhr_stream_scheduled_get_pipe(stream, &pipe)) { + err = pcxhr_set_format(stream); + err = pcxhr_update_r_buffer(stream); + } + } + } + /* start all the streams */ + for (i = 0; i < mgr->num_cards; i++) { + struct pcxhr_stream *stream; + chip = mgr->chip[i]; + for (j = 0; j < chip->nb_streams_capt; j++) { + stream = &chip->capture_stream[j]; + if (pcxhr_stream_scheduled_get_pipe(stream, &pipe)) + err = pcxhr_set_stream_state(stream); + } + for (j = 0; j < chip->nb_streams_play; j++) { + stream = &chip->playback_stream[j]; + if (pcxhr_stream_scheduled_get_pipe(stream, &pipe)) + err = pcxhr_set_stream_state(stream); + } + } + + /* synchronous start of all the pipes concerned */ + err = pcxhr_set_pipe_state(mgr, playback_mask, capture_mask, 1); + if (err) { + up(&mgr->setup_mutex); + snd_printk(KERN_ERR "pcxhr_trigger_tasklet : error start pipes (P%x C%x)\n", + playback_mask, capture_mask); + return; + } + + /* put the streams into the running state now (increment pointer by interrupt) */ + spin_lock_irqsave(&mgr->lock, flags); + for ( i =0; i < mgr->num_cards; i++) { + struct pcxhr_stream *stream; + chip = mgr->chip[i]; + for(j = 0; j < chip->nb_streams_capt; j++) { + stream = &chip->capture_stream[j]; + if(stream->status == PCXHR_STREAM_STATUS_STARTED) + stream->status = PCXHR_STREAM_STATUS_RUNNING; + } + for (j = 0; j < chip->nb_streams_play; j++) { + stream = &chip->playback_stream[j]; + if (stream->status == PCXHR_STREAM_STATUS_STARTED) { + /* playback will already have advanced ! */ + stream->timer_period_frag += PCXHR_GRANULARITY; + stream->status = PCXHR_STREAM_STATUS_RUNNING; + } + } + } + spin_unlock_irqrestore(&mgr->lock, flags); + + up(&mgr->setup_mutex); + +#ifdef CONFIG_SND_DEBUG_DETECT + do_gettimeofday(&my_tv2); + snd_printdd("***TRIGGER TASKLET*** TIME = %ld (err = %x)\n", + my_tv2.tv_usec - my_tv1.tv_usec, err); +#endif +} + + +/* + * trigger callback + */ +static int pcxhr_trigger(struct snd_pcm_substream *subs, int cmd) +{ + struct pcxhr_stream *stream; + struct list_head *pos; + struct snd_pcm_substream *s; + int i; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + snd_printdd("SNDRV_PCM_TRIGGER_START\n"); + i = 0; + snd_pcm_group_for_each(pos, subs) { + s = snd_pcm_group_substream_entry(pos); + stream = s->runtime->private_data; + stream->status = PCXHR_STREAM_STATUS_SCHEDULE_RUN; + snd_pcm_trigger_done(s, subs); + i++; + } + if (i==1) { + snd_printdd("Only one Substream %c %d\n", + stream->pipe->is_capture ? 'C' : 'P', + stream->pipe->first_audio); + if (pcxhr_set_format(stream)) + return -EINVAL; + if (pcxhr_update_r_buffer(stream)) + return -EINVAL; + + if (pcxhr_set_stream_state(stream)) + return -EINVAL; + stream->status = PCXHR_STREAM_STATUS_RUNNING; + } else { + struct snd_pcxhr *chip = snd_pcm_substream_chip(subs); + tasklet_hi_schedule(&chip->mgr->trigger_taskq); + } + break; + case SNDRV_PCM_TRIGGER_STOP: + snd_printdd("SNDRV_PCM_TRIGGER_STOP\n"); + snd_pcm_group_for_each(pos, subs) { + s = snd_pcm_group_substream_entry(pos); + stream = s->runtime->private_data; + stream->status = PCXHR_STREAM_STATUS_SCHEDULE_STOP; + if (pcxhr_set_stream_state(stream)) + return -EINVAL; + snd_pcm_trigger_done(s, subs); + } + break; + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + /* TODO */ + default: + return -EINVAL; + } + return 0; +} + + +static int pcxhr_hardware_timer(struct pcxhr_mgr *mgr, int start) +{ + struct pcxhr_rmh rmh; + int err; + + pcxhr_init_rmh(&rmh, CMD_SET_TIMER_INTERRUPT); + if (start) { + mgr->dsp_time_last = PCXHR_DSP_TIME_INVALID; /* last dsp time invalid */ + rmh.cmd[0] |= PCXHR_GRANULARITY; + } + err = pcxhr_send_msg(mgr, &rmh); + if (err < 0) + snd_printk(KERN_ERR "error pcxhr_hardware_timer err(%x)\n", err); + return err; +} + +/* + * prepare callback for all pcms + */ +static int pcxhr_prepare(struct snd_pcm_substream *subs) +{ + struct snd_pcxhr *chip = snd_pcm_substream_chip(subs); + struct pcxhr_mgr *mgr = chip->mgr; + /* + struct pcxhr_stream *stream = (pcxhr_stream_t*)subs->runtime->private_data; + */ + int err = 0; + + snd_printdd("pcxhr_prepare : period_size(%lx) periods(%x) buffer_size(%lx)\n", + subs->runtime->period_size, subs->runtime->periods, + subs->runtime->buffer_size); + + /* + if(subs->runtime->period_size <= PCXHR_GRANULARITY) { + snd_printk(KERN_ERR "pcxhr_prepare : error period_size too small (%x)\n", + (unsigned int)subs->runtime->period_size); + return -EINVAL; + } + */ + + down(&mgr->setup_mutex); + + do { + /* if the stream was stopped before, format and buffer were reset */ + /* + if(stream->status == PCXHR_STREAM_STATUS_STOPPED) { + err = pcxhr_set_format(stream); + if(err) break; + err = pcxhr_update_r_buffer(stream); + if(err) break; + } + */ + + /* only the first stream can choose the sample rate */ + /* the further opened streams will be limited to its frequency (see open) */ + /* set the clock only once (first stream) */ + if (mgr->sample_rate == 0) { + err = pcxhr_set_clock(mgr, subs->runtime->rate); + if (err) + break; + mgr->sample_rate = subs->runtime->rate; + + err = pcxhr_hardware_timer(mgr, 1); /* start the DSP-timer */ + } + } while(0); /* do only once (so we can use break instead of goto) */ + + up(&mgr->setup_mutex); + + return err; +} + + +/* + * HW_PARAMS callback for all pcms + */ +static int pcxhr_hw_params(struct snd_pcm_substream *subs, + struct snd_pcm_hw_params *hw) +{ + struct snd_pcxhr *chip = snd_pcm_substream_chip(subs); + struct pcxhr_mgr *mgr = chip->mgr; + struct pcxhr_stream *stream = subs->runtime->private_data; + snd_pcm_format_t format; + int err; + int channels; + + /* set up channels */ + channels = params_channels(hw); + + /* set up format for the stream */ + format = params_format(hw); + + down(&mgr->setup_mutex); + + stream->channels = channels; + stream->format = format; + + /* set the format to the board */ + /* + err = pcxhr_set_format(stream); + if(err) { + up(&mgr->setup_mutex); + return err; + } + */ + /* allocate buffer */ + err = snd_pcm_lib_malloc_pages(subs, params_buffer_bytes(hw)); + + /* + if (err > 0) { + err = pcxhr_update_r_buffer(stream); + } + */ + up(&mgr->setup_mutex); + + return err; +} + +static int pcxhr_hw_free(struct snd_pcm_substream *subs) +{ + snd_pcm_lib_free_pages(subs); + return 0; +} + + +/* + * CONFIGURATION SPACE for all pcms, mono pcm must update channels_max + */ +static struct snd_pcm_hardware pcxhr_caps = +{ + .info = ( SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_MMAP_VALID | SNDRV_PCM_INFO_SYNC_START | + 0 /*SNDRV_PCM_INFO_PAUSE*/), + .formats = ( SNDRV_PCM_FMTBIT_U8 | + SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S16_BE | + SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_S24_3BE | + SNDRV_PCM_FMTBIT_FLOAT_LE ), + .rates = SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_192000, + .rate_min = 8000, + .rate_max = 192000, + .channels_min = 1, + .channels_max = 2, + .buffer_bytes_max = (32*1024), + /* 1 byte == 1 frame U8 mono (PCXHR_GRANULARITY is frames!) */ + .period_bytes_min = (2*PCXHR_GRANULARITY), + .period_bytes_max = (16*1024), + .periods_min = 2, + .periods_max = (32*1024/PCXHR_GRANULARITY), +}; + + +static int pcxhr_open(struct snd_pcm_substream *subs) +{ + struct snd_pcxhr *chip = snd_pcm_substream_chip(subs); + struct pcxhr_mgr *mgr = chip->mgr; + struct snd_pcm_runtime *runtime = subs->runtime; + struct pcxhr_stream *stream; + int is_capture; + + down(&mgr->setup_mutex); + + /* copy the struct snd_pcm_hardware struct */ + runtime->hw = pcxhr_caps; + + if( subs->stream == SNDRV_PCM_STREAM_PLAYBACK ) { + snd_printdd("pcxhr_open playback chip%d subs%d\n", + chip->chip_idx, subs->number); + is_capture = 0; + stream = &chip->playback_stream[subs->number]; + } else { + snd_printdd("pcxhr_open capture chip%d subs%d\n", + chip->chip_idx, subs->number); + is_capture = 1; + if (mgr->mono_capture) + runtime->hw.channels_max = 1; + else + runtime->hw.channels_min = 2; + stream = &chip->capture_stream[subs->number]; + } + if (stream->status != PCXHR_STREAM_STATUS_FREE){ + /* streams in use */ + snd_printk(KERN_ERR "pcxhr_open chip%d subs%d in use\n", + chip->chip_idx, subs->number); + up(&mgr->setup_mutex); + return -EBUSY; + } + + /* if a sample rate is already used or fixed by external clock, + * the stream cannot change + */ + if (mgr->sample_rate) + runtime->hw.rate_min = runtime->hw.rate_max = mgr->sample_rate; + else { + if (mgr->use_clock_type != PCXHR_CLOCK_TYPE_INTERNAL) { + int external_rate; + if (pcxhr_get_external_clock(mgr, mgr->use_clock_type, + &external_rate) || + external_rate == 0) { + /* cannot detect the external clock rate */ + up(&mgr->setup_mutex); + return -EBUSY; + } + runtime->hw.rate_min = runtime->hw.rate_max = external_rate; + } + } + + stream->status = PCXHR_STREAM_STATUS_OPEN; + stream->substream = subs; + stream->channels = 0; /* not configured yet */ + + runtime->private_data = stream; + + snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_BUFFER_BYTES, 4); + snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 4); + + mgr->ref_count_rate++; + + up(&mgr->setup_mutex); + return 0; +} + + +static int pcxhr_close(struct snd_pcm_substream *subs) +{ + struct snd_pcxhr *chip = snd_pcm_substream_chip(subs); + struct pcxhr_mgr *mgr = chip->mgr; + struct pcxhr_stream *stream = subs->runtime->private_data; + + down(&mgr->setup_mutex); + + snd_printdd("pcxhr_close chip%d subs%d\n", chip->chip_idx, subs->number); + + /* sample rate released */ + if (--mgr->ref_count_rate == 0) { + mgr->sample_rate = 0; /* the sample rate is no more locked */ + pcxhr_hardware_timer(mgr, 0); /* stop the DSP-timer */ + } + + stream->status = PCXHR_STREAM_STATUS_FREE; + stream->substream = NULL; + + up(&mgr->setup_mutex); + + return 0; +} + + +static snd_pcm_uframes_t pcxhr_stream_pointer(struct snd_pcm_substream *subs) +{ + unsigned long flags; + u_int32_t timer_period_frag; + int timer_buf_periods; + struct snd_pcxhr *chip = snd_pcm_substream_chip(subs); + struct snd_pcm_runtime *runtime = subs->runtime; + struct pcxhr_stream *stream = runtime->private_data; + + spin_lock_irqsave(&chip->mgr->lock, flags); + + /* get the period fragment and the nb of periods in the buffer */ + timer_period_frag = stream->timer_period_frag; + timer_buf_periods = stream->timer_buf_periods; + + spin_unlock_irqrestore(&chip->mgr->lock, flags); + + return (snd_pcm_uframes_t)((timer_buf_periods * runtime->period_size) + + timer_period_frag); +} + + +static struct snd_pcm_ops pcxhr_ops = { + .open = pcxhr_open, + .close = pcxhr_close, + .ioctl = snd_pcm_lib_ioctl, + .prepare = pcxhr_prepare, + .hw_params = pcxhr_hw_params, + .hw_free = pcxhr_hw_free, + .trigger = pcxhr_trigger, + .pointer = pcxhr_stream_pointer, +}; + +/* + */ +int pcxhr_create_pcm(struct snd_pcxhr *chip) +{ + int err; + struct snd_pcm *pcm; + char name[32]; + + sprintf(name, "pcxhr %d", chip->chip_idx); + if ((err = snd_pcm_new(chip->card, name, 0, + chip->nb_streams_play, + chip->nb_streams_capt, &pcm)) < 0) { + snd_printk(KERN_ERR "cannot create pcm %s\n", name); + return err; + } + pcm->private_data = chip; + + if (chip->nb_streams_play) + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &pcxhr_ops); + if (chip->nb_streams_capt) + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &pcxhr_ops); + + pcm->info_flags = 0; + strcpy(pcm->name, name); + + snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV, + snd_dma_pci_data(chip->mgr->pci), + 32*1024, 32*1024); + chip->pcm = pcm; + return 0; +} + +static int pcxhr_chip_free(struct snd_pcxhr *chip) +{ + kfree(chip); + return 0; +} + +static int pcxhr_chip_dev_free(struct snd_device *device) +{ + struct snd_pcxhr *chip = device->device_data; + return pcxhr_chip_free(chip); +} + + +/* + */ +static int __devinit pcxhr_create(struct pcxhr_mgr *mgr, struct snd_card *card, int idx) +{ + int err; + struct snd_pcxhr *chip; + static struct snd_device_ops ops = { + .dev_free = pcxhr_chip_dev_free, + }; + + mgr->chip[idx] = chip = kzalloc(sizeof(*chip), GFP_KERNEL); + if (! chip) { + snd_printk(KERN_ERR "cannot allocate chip\n"); + return -ENOMEM; + } + + chip->card = card; + chip->chip_idx = idx; + chip->mgr = mgr; + + if (idx < mgr->playback_chips) + /* stereo or mono streams */ + chip->nb_streams_play = PCXHR_PLAYBACK_STREAMS; + + if (idx < mgr->capture_chips) { + if (mgr->mono_capture) + chip->nb_streams_capt = 2; /* 2 mono streams (left+right) */ + else + chip->nb_streams_capt = 1; /* or 1 stereo stream */ + } + + if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops)) < 0) { + pcxhr_chip_free(chip); + return err; + } + + snd_card_set_dev(card, &mgr->pci->dev); + + return 0; +} + +/* proc interface */ +static void pcxhr_proc_info(struct snd_info_entry *entry, struct snd_info_buffer *buffer) +{ + struct snd_pcxhr *chip = entry->private_data; + struct pcxhr_mgr *mgr = chip->mgr; + + snd_iprintf(buffer, "\n%s\n", mgr->longname); + + /* stats available when embedded DSP is running */ + if (mgr->dsp_loaded & (1 << PCXHR_FIRMWARE_DSP_MAIN_INDEX)) { + struct pcxhr_rmh rmh; + short ver_maj = (mgr->dsp_version >> 16) & 0xff; + short ver_min = (mgr->dsp_version >> 8) & 0xff; + short ver_build = mgr->dsp_version & 0xff; + snd_iprintf(buffer, "module version %s\n", PCXHR_DRIVER_VERSION_STRING); + snd_iprintf(buffer, "dsp version %d.%d.%d\n", ver_maj, ver_min, ver_build); + if (mgr->board_has_analog) + snd_iprintf(buffer, "analog io available\n"); + else + snd_iprintf(buffer, "digital only board\n"); + + /* calc cpu load of the dsp */ + pcxhr_init_rmh(&rmh, CMD_GET_DSP_RESOURCES); + if( ! pcxhr_send_msg(mgr, &rmh) ) { + int cur = rmh.stat[0]; + int ref = rmh.stat[1]; + if (ref > 0) { + if (mgr->sample_rate_real != 0 && + mgr->sample_rate_real != 48000) { + ref = (ref * 48000) / mgr->sample_rate_real; + if (mgr->sample_rate_real >= PCXHR_IRQ_TIMER_FREQ) + ref *= 2; + } + cur = 100 - (100 * cur) / ref; + snd_iprintf(buffer, "cpu load %d%%\n", cur); + snd_iprintf(buffer, "buffer pool %d/%d kWords\n", + rmh.stat[2], rmh.stat[3]); + } + } + snd_iprintf(buffer, "dma granularity : %d\n", PCXHR_GRANULARITY); + snd_iprintf(buffer, "dsp time errors : %d\n", mgr->dsp_time_err); + snd_iprintf(buffer, "dsp async pipe xrun errors : %d\n", + mgr->async_err_pipe_xrun); + snd_iprintf(buffer, "dsp async stream xrun errors : %d\n", + mgr->async_err_stream_xrun); + snd_iprintf(buffer, "dsp async last other error : %x\n", + mgr->async_err_other_last); + /* debug zone dsp */ + rmh.cmd[0] = 0x4200 + PCXHR_SIZE_MAX_STATUS; + rmh.cmd_len = 1; + rmh.stat_len = PCXHR_SIZE_MAX_STATUS; + rmh.dsp_stat = 0; + rmh.cmd_idx = CMD_LAST_INDEX; + if( ! pcxhr_send_msg(mgr, &rmh) ) { + int i; + for (i = 0; i < rmh.stat_len; i++) + snd_iprintf(buffer, "debug[%02d] = %06x\n", i, rmh.stat[i]); + } + } else + snd_iprintf(buffer, "no firmware loaded\n"); + snd_iprintf(buffer, "\n"); +} +static void pcxhr_proc_sync(struct snd_info_entry *entry, struct snd_info_buffer *buffer) +{ + struct snd_pcxhr *chip = entry->private_data; + struct pcxhr_mgr *mgr = chip->mgr; + static char *texts[7] = { + "Internal", "Word", "AES Sync", "AES 1", "AES 2", "AES 3", "AES 4" + }; + + snd_iprintf(buffer, "\n%s\n", mgr->longname); + snd_iprintf(buffer, "Current Sample Clock\t: %s\n", texts[mgr->cur_clock_type]); + snd_iprintf(buffer, "Current Sample Rate\t= %d\n", mgr->sample_rate_real); + + /* commands available when embedded DSP is running */ + if (mgr->dsp_loaded & (1 << PCXHR_FIRMWARE_DSP_MAIN_INDEX)) { + int i, err, sample_rate; + for (i = PCXHR_CLOCK_TYPE_WORD_CLOCK; i< (3 + mgr->capture_chips); i++) { + err = pcxhr_get_external_clock(mgr, i, &sample_rate); + if (err) + break; + snd_iprintf(buffer, "%s Clock\t\t= %d\n", texts[i], sample_rate); + } + } else + snd_iprintf(buffer, "no firmware loaded\n"); + snd_iprintf(buffer, "\n"); +} + +static void __devinit pcxhr_proc_init(struct snd_pcxhr *chip) +{ + struct snd_info_entry *entry; + + if (! snd_card_proc_new(chip->card, "info", &entry)) + snd_info_set_text_ops(entry, chip, 1024, pcxhr_proc_info); + if (! snd_card_proc_new(chip->card, "sync", &entry)) + snd_info_set_text_ops(entry, chip, 1024, pcxhr_proc_sync); +} +/* end of proc interface */ + +/* + * release all the cards assigned to a manager instance + */ +static int pcxhr_free(struct pcxhr_mgr *mgr) +{ + unsigned int i; + + for (i = 0; i < mgr->num_cards; i++) { + if (mgr->chip[i]) + snd_card_free(mgr->chip[i]->card); + } + + /* reset board if some firmware was loaded */ + if(mgr->dsp_loaded) { + pcxhr_reset_board(mgr); + snd_printdd("reset pcxhr !\n"); + } + + /* release irq */ + if (mgr->irq >= 0) + free_irq(mgr->irq, mgr); + + pci_release_regions(mgr->pci); + + /* free hostport purgebuffer */ + if (mgr->hostport.area) { + snd_dma_free_pages(&mgr->hostport); + mgr->hostport.area = NULL; + } + + kfree(mgr->prmh); + + pci_disable_device(mgr->pci); + kfree(mgr); + return 0; +} + +/* + * probe function - creates the card manager + */ +static int __devinit pcxhr_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) +{ + static int dev; + struct pcxhr_mgr *mgr; + unsigned int i; + int err; + size_t size; + char *card_name; + + if (dev >= SNDRV_CARDS) + return -ENODEV; + if (! enable[dev]) { + dev++; + return -ENOENT; + } + + /* enable PCI device */ + if ((err = pci_enable_device(pci)) < 0) + return err; + pci_set_master(pci); + + /* check if we can restrict PCI DMA transfers to 32 bits */ + if (pci_set_dma_mask(pci, 0xffffffff) < 0) { + snd_printk(KERN_ERR "architecture does not support 32bit PCI busmaster DMA\n"); + pci_disable_device(pci); + return -ENXIO; + } + + /* alloc card manager */ + mgr = kzalloc(sizeof(*mgr), GFP_KERNEL); + if (! mgr) { + pci_disable_device(pci); + return -ENOMEM; + } + + snd_assert(pci_id->driver_data < PCI_ID_LAST, return -ENODEV); + card_name = pcxhr_board_params[pci_id->driver_data].board_name; + mgr->playback_chips = pcxhr_board_params[pci_id->driver_data].playback_chips; + mgr->capture_chips = pcxhr_board_params[pci_id->driver_data].capture_chips; + mgr->firmware_num = pcxhr_board_params[pci_id->driver_data].firmware_num; + mgr->mono_capture = mono[dev]; + + /* resource assignment */ + if ((err = pci_request_regions(pci, card_name)) < 0) { + kfree(mgr); + pci_disable_device(pci); + return err; + } + for (i = 0; i < 3; i++) + mgr->port[i] = pci_resource_start(pci, i); + + mgr->pci = pci; + mgr->irq = -1; + + if (request_irq(pci->irq, pcxhr_interrupt, SA_INTERRUPT|SA_SHIRQ, + card_name, mgr)) { + snd_printk(KERN_ERR "unable to grab IRQ %d\n", pci->irq); + pcxhr_free(mgr); + return -EBUSY; + } + mgr->irq = pci->irq; + + sprintf(mgr->shortname, "Digigram %s", card_name); + sprintf(mgr->longname, "%s at 0x%lx & 0x%lx, 0x%lx irq %i", mgr->shortname, + mgr->port[0], mgr->port[1], mgr->port[2], mgr->irq); + + /* ISR spinlock */ + spin_lock_init(&mgr->lock); + spin_lock_init(&mgr->msg_lock); + + /* init setup mutex*/ + init_MUTEX(&mgr->setup_mutex); + + /* init taslket */ + tasklet_init(&mgr->msg_taskq, pcxhr_msg_tasklet, (unsigned long) mgr); + tasklet_init(&mgr->trigger_taskq, pcxhr_trigger_tasklet, (unsigned long) mgr); + mgr->prmh = kmalloc(sizeof(*mgr->prmh) + + sizeof(u32) * (PCXHR_SIZE_MAX_LONG_STATUS - PCXHR_SIZE_MAX_STATUS), + GFP_KERNEL); + if (! mgr->prmh) { + pcxhr_free(mgr); + return -ENOMEM; + } + + for (i=0; i < PCXHR_MAX_CARDS; i++) { + struct snd_card *card; + char tmpid[16]; + int idx; + + if (i >= max(mgr->playback_chips, mgr->capture_chips)) + break; + mgr->num_cards++; + + if (index[dev] < 0) + idx = index[dev]; + else + idx = index[dev] + i; + + snprintf(tmpid, sizeof(tmpid), "%s-%d", id[dev] ? id[dev] : card_name, i); + card = snd_card_new(idx, tmpid, THIS_MODULE, 0); + + if (! card) { + snd_printk(KERN_ERR "cannot allocate the card %d\n", i); + pcxhr_free(mgr); + return -ENOMEM; + } + + strcpy(card->driver, DRIVER_NAME); + sprintf(card->shortname, "%s [PCM #%d]", mgr->shortname, i); + sprintf(card->longname, "%s [PCM #%d]", mgr->longname, i); + + if ((err = pcxhr_create(mgr, card, i)) < 0) { + pcxhr_free(mgr); + return err; + } + + if (i == 0) + /* init proc interface only for chip0 */ + pcxhr_proc_init(mgr->chip[i]); + + if ((err = snd_card_register(card)) < 0) { + pcxhr_free(mgr); + return err; + } + } + + /* create hostport purgebuffer */ + size = PAGE_ALIGN(sizeof(struct pcxhr_hostport)); + if (snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, snd_dma_pci_data(pci), + size, &mgr->hostport) < 0) { + pcxhr_free(mgr); + return -ENOMEM; + } + /* init purgebuffer */ + memset(mgr->hostport.area, 0, size); + + /* create a DSP loader */ + err = pcxhr_setup_firmware(mgr); + if (err < 0) { + pcxhr_free(mgr); + return err; + } + + pci_set_drvdata(pci, mgr); + dev++; + return 0; +} + +static void __devexit pcxhr_remove(struct pci_dev *pci) +{ + pcxhr_free(pci_get_drvdata(pci)); + pci_set_drvdata(pci, NULL); +} + +static struct pci_driver driver = { + .name = "Digigram pcxhr", + .id_table = pcxhr_ids, + .probe = pcxhr_probe, + .remove = __devexit_p(pcxhr_remove), +}; + +static int __init pcxhr_module_init(void) +{ + return pci_register_driver(&driver); +} + +static void __exit pcxhr_module_exit(void) +{ + pci_unregister_driver(&driver); +} + +module_init(pcxhr_module_init) +module_exit(pcxhr_module_exit) diff --git a/sound/pci/pcxhr/pcxhr.h b/sound/pci/pcxhr/pcxhr.h new file mode 100644 index 000000000000..049f2b3f2867 --- /dev/null +++ b/sound/pci/pcxhr/pcxhr.h @@ -0,0 +1,188 @@ +/* + * Driver for Digigram pcxhr soundcards + * + * main header file + * + * Copyright (c) 2004 by Digigram + * + * 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 + */ + +#ifndef __SOUND_PCXHR_H +#define __SOUND_PCXHR_H + +#include +#include + +#define PCXHR_DRIVER_VERSION 0x000804 /* 0.8.4 */ +#define PCXHR_DRIVER_VERSION_STRING "0.8.4" /* 0.8.4 */ + + +#define PCXHR_MAX_CARDS 6 +#define PCXHR_PLAYBACK_STREAMS 4 + +#define PCXHR_GRANULARITY 96 /* transfer granularity (should be min 96 and multiple of 48) */ +#define PCXHR_GRANULARITY_MIN 96 /* transfer granularity of pipes and the dsp time (MBOX4) */ + +struct snd_pcxhr; +struct pcxhr_mgr; + +struct pcxhr_stream; +struct pcxhr_pipe; + +enum pcxhr_clock_type { + PCXHR_CLOCK_TYPE_INTERNAL = 0, + PCXHR_CLOCK_TYPE_WORD_CLOCK, + PCXHR_CLOCK_TYPE_AES_SYNC, + PCXHR_CLOCK_TYPE_AES_1, + PCXHR_CLOCK_TYPE_AES_2, + PCXHR_CLOCK_TYPE_AES_3, + PCXHR_CLOCK_TYPE_AES_4, +}; + +struct pcxhr_mgr { + unsigned int num_cards; + struct snd_pcxhr *chip[PCXHR_MAX_CARDS]; + + struct pci_dev *pci; + + int irq; + + /* card access with 1 mem bar and 2 io bar's */ + unsigned long port[3]; + + /* share the name */ + char shortname[32]; /* short name of this soundcard */ + char longname[96]; /* name of this soundcard */ + + /* message tasklet */ + struct tasklet_struct msg_taskq; + struct pcxhr_rmh *prmh; + /* trigger tasklet */ + struct tasklet_struct trigger_taskq; + + spinlock_t lock; /* interrupt spinlock */ + spinlock_t msg_lock; /* message spinlock */ + + struct semaphore setup_mutex; /* mutex used in hw_params, open and close */ + struct semaphore mixer_mutex; /* mutex for mixer */ + + /* hardware interface */ + unsigned int dsp_loaded; /* bit flags of loaded dsp indices */ + unsigned int dsp_version; /* read from embedded once firmware is loaded */ + int board_has_analog; /* if 0 the board is digital only */ + int mono_capture; /* if 1 the board does mono capture */ + int playback_chips; /* 4 or 6 */ + int capture_chips; /* 4 or 1 */ + int firmware_num; /* 41 or 42 */ + + struct snd_dma_buffer hostport; + + enum pcxhr_clock_type use_clock_type; /* clock type selected by mixer */ + enum pcxhr_clock_type cur_clock_type; /* current clock type synced */ + int sample_rate; + int ref_count_rate; + int timer_toggle; /* timer interrupt toggles between the two values 0x200 and 0x300 */ + int dsp_time_last; /* the last dsp time (read by interrupt) */ + int dsp_time_err; /* dsp time errors */ + unsigned int src_it_dsp; /* dsp interrupt source */ + unsigned int io_num_reg_cont; /* backup of IO_NUM_REG_CONT */ + unsigned int codec_speed; /* speed mode of the codecs */ + unsigned int sample_rate_real; /* current real sample rate */ + int last_reg_stat; + int async_err_stream_xrun; + int async_err_pipe_xrun; + int async_err_other_last; +}; + + +enum pcxhr_stream_status { + PCXHR_STREAM_STATUS_FREE, + PCXHR_STREAM_STATUS_OPEN, + PCXHR_STREAM_STATUS_SCHEDULE_RUN, + PCXHR_STREAM_STATUS_STARTED, + PCXHR_STREAM_STATUS_RUNNING, + PCXHR_STREAM_STATUS_SCHEDULE_STOP, + PCXHR_STREAM_STATUS_STOPPED, + PCXHR_STREAM_STATUS_PAUSED +}; + +struct pcxhr_stream { + struct snd_pcm_substream *substream; + snd_pcm_format_t format; + struct pcxhr_pipe *pipe; + + enum pcxhr_stream_status status; /* free, open, running, draining, pause */ + + u_int64_t timer_abs_periods; /* timer: samples elapsed since TRIGGER_START (multiple of period_size) */ + u_int32_t timer_period_frag; /* timer: samples elapsed since last call to snd_pcm_period_elapsed (0..period_size) */ + u_int32_t timer_buf_periods; /* nb of periods in the buffer that have already elapsed */ + int timer_is_synced; /* if(0) : timer needs to be resynced with real hardware pointer */ + + int channels; +}; + + +enum pcxhr_pipe_status { + PCXHR_PIPE_UNDEFINED, + PCXHR_PIPE_DEFINED +}; + +struct pcxhr_pipe { + enum pcxhr_pipe_status status; + int is_capture; /* this is a capture pipe */ + int first_audio; /* first audio num */ +}; + + +struct snd_pcxhr { + struct snd_card *card; + struct pcxhr_mgr *mgr; + int chip_idx; /* zero based */ + + struct snd_pcm *pcm; /* PCM */ + + struct pcxhr_pipe playback_pipe; /* 1 stereo pipe only */ + struct pcxhr_pipe capture_pipe[2]; /* 1 stereo pipe or 2 mono pipes */ + + struct pcxhr_stream playback_stream[PCXHR_PLAYBACK_STREAMS]; + struct pcxhr_stream capture_stream[2]; /* 1 stereo stream or 2 mono streams */ + int nb_streams_play; + int nb_streams_capt; + + int analog_playback_active[2]; /* Mixer : Master Playback active (!mute) */ + int analog_playback_volume[2]; /* Mixer : Master Playback Volume */ + int analog_capture_volume[2]; /* Mixer : Master Capture Volume */ + int digital_playback_active[PCXHR_PLAYBACK_STREAMS][2]; /* Mixer : Digital Playback Active [streams][stereo]*/ + int digital_playback_volume[PCXHR_PLAYBACK_STREAMS][2]; /* Mixer : Digital Playback Volume [streams][stereo]*/ + int digital_capture_volume[2]; /* Mixer : Digital Capture Volume [stereo] */ + int monitoring_active[2]; /* Mixer : Monitoring Active */ + int monitoring_volume[2]; /* Mixer : Monitoring Volume */ + int audio_capture_source; /* Mixer : Audio Capture Source */ + unsigned char aes_bits[5]; /* Mixer : IEC958_AES bits */ +}; + +struct pcxhr_hostport +{ + char purgebuffer[6]; + char reserved[2]; +}; + +/* exported */ +int pcxhr_create_pcm(struct snd_pcxhr *chip); +int pcxhr_set_clock(struct pcxhr_mgr *mgr, unsigned int rate); +int pcxhr_get_external_clock(struct pcxhr_mgr *mgr, enum pcxhr_clock_type clock_type, int *sample_rate); + +#endif /* __SOUND_PCXHR_H */ diff --git a/sound/pci/pcxhr/pcxhr_core.c b/sound/pci/pcxhr/pcxhr_core.c new file mode 100644 index 000000000000..fa0d27e2c79b --- /dev/null +++ b/sound/pci/pcxhr/pcxhr_core.c @@ -0,0 +1,1214 @@ +/* + * Driver for Digigram pcxhr compatible soundcards + * + * low level interface with interrupt and message handling implementation + * + * Copyright (c) 2004 by Digigram + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include +#include "pcxhr.h" +#include "pcxhr_mixer.h" +#include "pcxhr_hwdep.h" +#include "pcxhr_core.h" + + +/* registers used on the PLX (port 1) */ +#define PCXHR_PLX_OFFSET_MIN 0x40 +#define PCXHR_PLX_MBOX0 0x40 +#define PCXHR_PLX_MBOX1 0x44 +#define PCXHR_PLX_MBOX2 0x48 +#define PCXHR_PLX_MBOX3 0x4C +#define PCXHR_PLX_MBOX4 0x50 +#define PCXHR_PLX_MBOX5 0x54 +#define PCXHR_PLX_MBOX6 0x58 +#define PCXHR_PLX_MBOX7 0x5C +#define PCXHR_PLX_L2PCIDB 0x64 +#define PCXHR_PLX_IRQCS 0x68 +#define PCXHR_PLX_CHIPSC 0x6C + +/* registers used on the DSP (port 2) */ +#define PCXHR_DSP_ICR 0x00 +#define PCXHR_DSP_CVR 0x04 +#define PCXHR_DSP_ISR 0x08 +#define PCXHR_DSP_IVR 0x0C +#define PCXHR_DSP_RXH 0x14 +#define PCXHR_DSP_TXH 0x14 +#define PCXHR_DSP_RXM 0x18 +#define PCXHR_DSP_TXM 0x18 +#define PCXHR_DSP_RXL 0x1C +#define PCXHR_DSP_TXL 0x1C +#define PCXHR_DSP_RESET 0x20 +#define PCXHR_DSP_OFFSET_MAX 0x20 + +/* access to the card */ +#define PCXHR_PLX 1 +#define PCXHR_DSP 2 + +#if (PCXHR_DSP_OFFSET_MAX > PCXHR_PLX_OFFSET_MIN) +#undef PCXHR_REG_TO_PORT(x) +#else +#define PCXHR_REG_TO_PORT(x) ((x)>PCXHR_DSP_OFFSET_MAX ? PCXHR_PLX : PCXHR_DSP) +#endif +#define PCXHR_INPB(mgr,x) inb((mgr)->port[PCXHR_REG_TO_PORT(x)] + (x)) +#define PCXHR_INPL(mgr,x) inl((mgr)->port[PCXHR_REG_TO_PORT(x)] + (x)) +#define PCXHR_OUTPB(mgr,x,data) outb((data), (mgr)->port[PCXHR_REG_TO_PORT(x)] + (x)) +#define PCXHR_OUTPL(mgr,x,data) outl((data), (mgr)->port[PCXHR_REG_TO_PORT(x)] + (x)) +/* attention : access the PCXHR_DSP_* registers with inb and outb only ! */ + +/* params used with PCXHR_PLX_MBOX0 */ +#define PCXHR_MBOX0_HF5 (1 << 0) +#define PCXHR_MBOX0_HF4 (1 << 1) +#define PCXHR_MBOX0_BOOT_HERE (1 << 23) +/* params used with PCXHR_PLX_IRQCS */ +#define PCXHR_IRQCS_ENABLE_PCIIRQ (1 << 8) +#define PCXHR_IRQCS_ENABLE_PCIDB (1 << 9) +#define PCXHR_IRQCS_ACTIVE_PCIDB (1 << 13) +/* params used with PCXHR_PLX_CHIPSC */ +#define PCXHR_CHIPSC_INIT_VALUE 0x100D767E +#define PCXHR_CHIPSC_RESET_XILINX (1 << 16) +#define PCXHR_CHIPSC_GPI_USERI (1 << 17) +#define PCXHR_CHIPSC_DATA_CLK (1 << 24) +#define PCXHR_CHIPSC_DATA_IN (1 << 26) + +/* params used with PCXHR_DSP_ICR */ +#define PCXHR_ICR_HI08_RREQ 0x01 +#define PCXHR_ICR_HI08_TREQ 0x02 +#define PCXHR_ICR_HI08_HDRQ 0x04 +#define PCXHR_ICR_HI08_HF0 0x08 +#define PCXHR_ICR_HI08_HF1 0x10 +#define PCXHR_ICR_HI08_HLEND 0x20 +#define PCXHR_ICR_HI08_INIT 0x80 +/* params used with PCXHR_DSP_CVR */ +#define PCXHR_CVR_HI08_HC 0x80 +/* params used with PCXHR_DSP_ISR */ +#define PCXHR_ISR_HI08_RXDF 0x01 +#define PCXHR_ISR_HI08_TXDE 0x02 +#define PCXHR_ISR_HI08_TRDY 0x04 +#define PCXHR_ISR_HI08_ERR 0x08 +#define PCXHR_ISR_HI08_CHK 0x10 +#define PCXHR_ISR_HI08_HREQ 0x80 + + +/* constants used for delay in msec */ +#define PCXHR_WAIT_DEFAULT 2 +#define PCXHR_WAIT_IT 25 +#define PCXHR_WAIT_IT_EXTRA 65 + +/* + * pcxhr_check_reg_bit - wait for the specified bit is set/reset on a register + * @reg: register to check + * @mask: bit mask + * @bit: resultant bit to be checked + * @time: time-out of loop in msec + * + * returns zero if a bit matches, or a negative error code. + */ +static int pcxhr_check_reg_bit(struct pcxhr_mgr *mgr, unsigned int reg, + unsigned char mask, unsigned char bit, int time, + unsigned char* read) +{ + int i = 0; + unsigned long end_time = jiffies + (time * HZ + 999) / 1000; + do { + *read = PCXHR_INPB(mgr, reg); + if ((*read & mask) == bit) { + if (i > 100) + snd_printdd("ATTENTION! check_reg(%x) loopcount=%d\n", + reg, i); + return 0; + } + i++; + } while (time_after_eq(end_time, jiffies)); + snd_printk(KERN_ERR "pcxhr_check_reg_bit: timeout, reg=%x, mask=0x%x, val=0x%x\n", + reg, mask, *read); + return -EIO; +} + +/* constants used with pcxhr_check_reg_bit() */ +#define PCXHR_TIMEOUT_DSP 200 + + +#define PCXHR_MASK_EXTRA_INFO 0x0000FE +#define PCXHR_MASK_IT_HF0 0x000100 +#define PCXHR_MASK_IT_HF1 0x000200 +#define PCXHR_MASK_IT_NO_HF0_HF1 0x000400 +#define PCXHR_MASK_IT_MANAGE_HF5 0x000800 +#define PCXHR_MASK_IT_WAIT 0x010000 +#define PCXHR_MASK_IT_WAIT_EXTRA 0x020000 + +#define PCXHR_IT_SEND_BYTE_XILINX (0x0000003C | PCXHR_MASK_IT_HF0) +#define PCXHR_IT_TEST_XILINX (0x0000003C | PCXHR_MASK_IT_HF1 | \ + PCXHR_MASK_IT_MANAGE_HF5) +#define PCXHR_IT_DOWNLOAD_BOOT (0x0000000C | PCXHR_MASK_IT_HF1 | \ + PCXHR_MASK_IT_MANAGE_HF5 | PCXHR_MASK_IT_WAIT) +#define PCXHR_IT_RESET_BOARD_FUNC (0x0000000C | PCXHR_MASK_IT_HF0 | \ + PCXHR_MASK_IT_MANAGE_HF5 | PCXHR_MASK_IT_WAIT_EXTRA) +#define PCXHR_IT_DOWNLOAD_DSP (0x0000000C | \ + PCXHR_MASK_IT_MANAGE_HF5 | PCXHR_MASK_IT_WAIT) +#define PCXHR_IT_DEBUG (0x0000005A | PCXHR_MASK_IT_NO_HF0_HF1) +#define PCXHR_IT_RESET_SEMAPHORE (0x0000005C | PCXHR_MASK_IT_NO_HF0_HF1) +#define PCXHR_IT_MESSAGE (0x00000074 | PCXHR_MASK_IT_NO_HF0_HF1) +#define PCXHR_IT_RESET_CHK (0x00000076 | PCXHR_MASK_IT_NO_HF0_HF1) +#define PCXHR_IT_UPDATE_RBUFFER (0x00000078 | PCXHR_MASK_IT_NO_HF0_HF1) + +static int pcxhr_send_it_dsp(struct pcxhr_mgr *mgr, unsigned int itdsp, int atomic) +{ + int err; + unsigned char reg; + + if (itdsp & PCXHR_MASK_IT_MANAGE_HF5) { + /* clear hf5 bit */ + PCXHR_OUTPL(mgr, PCXHR_PLX_MBOX0, + PCXHR_INPL(mgr, PCXHR_PLX_MBOX0) & ~PCXHR_MBOX0_HF5); + } + if ((itdsp & PCXHR_MASK_IT_NO_HF0_HF1) == 0) { + reg = PCXHR_ICR_HI08_RREQ | PCXHR_ICR_HI08_TREQ | PCXHR_ICR_HI08_HDRQ; + if (itdsp & PCXHR_MASK_IT_HF0) + reg |= PCXHR_ICR_HI08_HF0; + if (itdsp & PCXHR_MASK_IT_HF1) + reg |= PCXHR_ICR_HI08_HF1; + PCXHR_OUTPB(mgr, PCXHR_DSP_ICR, reg); + } + reg = (unsigned char)(((itdsp & PCXHR_MASK_EXTRA_INFO) >> 1) | PCXHR_CVR_HI08_HC); + PCXHR_OUTPB(mgr, PCXHR_DSP_CVR, reg); + if (itdsp & PCXHR_MASK_IT_WAIT) { + if (atomic) + mdelay(PCXHR_WAIT_IT); + else + msleep(PCXHR_WAIT_IT); + } + if (itdsp & PCXHR_MASK_IT_WAIT_EXTRA) { + if (atomic) + mdelay(PCXHR_WAIT_IT_EXTRA); + else + msleep(PCXHR_WAIT_IT); + } + /* wait for CVR_HI08_HC == 0 */ + err = pcxhr_check_reg_bit(mgr, PCXHR_DSP_CVR, PCXHR_CVR_HI08_HC, 0, + PCXHR_TIMEOUT_DSP, ®); + if (err) { + snd_printk(KERN_ERR "pcxhr_send_it_dsp : TIMEOUT CVR\n"); + return err; + } + if (itdsp & PCXHR_MASK_IT_MANAGE_HF5) { + /* wait for hf5 bit */ + err = pcxhr_check_reg_bit(mgr, PCXHR_PLX_MBOX0, PCXHR_MBOX0_HF5, + PCXHR_MBOX0_HF5, PCXHR_TIMEOUT_DSP, ®); + if (err) { + snd_printk(KERN_ERR "pcxhr_send_it_dsp : TIMEOUT HF5\n"); + return err; + } + } + return 0; /* retry not handled here */ +} + +void pcxhr_reset_xilinx_com(struct pcxhr_mgr *mgr) +{ + /* reset second xilinx */ + PCXHR_OUTPL(mgr, PCXHR_PLX_CHIPSC, + PCXHR_CHIPSC_INIT_VALUE & ~PCXHR_CHIPSC_RESET_XILINX); +} + +static void pcxhr_enable_irq(struct pcxhr_mgr *mgr, int enable) +{ + unsigned int reg = PCXHR_INPL(mgr, PCXHR_PLX_IRQCS); + /* enable/disable interrupts */ + if (enable) + reg |= (PCXHR_IRQCS_ENABLE_PCIIRQ | PCXHR_IRQCS_ENABLE_PCIDB); + else + reg &= ~(PCXHR_IRQCS_ENABLE_PCIIRQ | PCXHR_IRQCS_ENABLE_PCIDB); + PCXHR_OUTPL(mgr, PCXHR_PLX_IRQCS, reg); +} + +void pcxhr_reset_dsp(struct pcxhr_mgr *mgr) +{ + /* disable interrupts */ + pcxhr_enable_irq(mgr, 0); + + /* let's reset the DSP */ + PCXHR_OUTPB(mgr, PCXHR_DSP_RESET, 0); + msleep( PCXHR_WAIT_DEFAULT ); /* wait 2 msec */ + PCXHR_OUTPB(mgr, PCXHR_DSP_RESET, 3); + msleep( PCXHR_WAIT_DEFAULT ); /* wait 2 msec */ + + /* reset mailbox */ + PCXHR_OUTPL(mgr, PCXHR_PLX_MBOX0, 0); +} + +void pcxhr_enable_dsp(struct pcxhr_mgr *mgr) +{ + /* enable interrupts */ + pcxhr_enable_irq(mgr, 1); +} + +/* + * load the xilinx image + */ +int pcxhr_load_xilinx_binary(struct pcxhr_mgr *mgr, const struct firmware *xilinx, int second) +{ + unsigned int i; + unsigned int chipsc; + unsigned char data; + unsigned char mask; + unsigned char *image; + + /* test first xilinx */ + chipsc = PCXHR_INPL(mgr, PCXHR_PLX_CHIPSC); + if (!second) { + if (chipsc & PCXHR_CHIPSC_GPI_USERI) { + snd_printdd("no need to load first xilinx\n"); + return 0; /* first xilinx is already present and cannot be reset */ + } + } else { + if ((chipsc & PCXHR_CHIPSC_GPI_USERI) == 0) { + snd_printk(KERN_ERR "error loading first xilinx\n"); + return -EINVAL; + } + /* activate second xilinx */ + chipsc |= PCXHR_CHIPSC_RESET_XILINX; + PCXHR_OUTPL(mgr, PCXHR_PLX_CHIPSC, chipsc); + msleep( PCXHR_WAIT_DEFAULT ); /* wait 2 msec */ + } + image = xilinx->data; + for (i = 0; i < xilinx->size; i++, image++) { + data = *image; + mask = 0x80; + while (mask) { + chipsc &= ~(PCXHR_CHIPSC_DATA_CLK | PCXHR_CHIPSC_DATA_IN); + if (data & mask) + chipsc |= PCXHR_CHIPSC_DATA_IN; + PCXHR_OUTPL(mgr, PCXHR_PLX_CHIPSC, chipsc); + chipsc |= PCXHR_CHIPSC_DATA_CLK; + PCXHR_OUTPL(mgr, PCXHR_PLX_CHIPSC, chipsc); + mask >>= 1; + } + /* don't take too much time in this loop... */ + cond_resched(); + } + chipsc &= ~(PCXHR_CHIPSC_DATA_CLK | PCXHR_CHIPSC_DATA_IN); + PCXHR_OUTPL(mgr, PCXHR_PLX_CHIPSC, chipsc); + /* wait 2 msec (time to boot the xilinx before any access) */ + msleep( PCXHR_WAIT_DEFAULT ); + return 0; +} + +/* + * send an executable file to the DSP + */ +static int pcxhr_download_dsp(struct pcxhr_mgr *mgr, const struct firmware *dsp) +{ + int err; + unsigned int i; + unsigned int len; + unsigned char *data; + unsigned char dummy; + /* check the length of boot image */ + snd_assert(dsp->size > 0, return -EINVAL); + snd_assert(dsp->size % 3 == 0, return -EINVAL); + snd_assert(dsp->data, return -EINVAL); + /* transfert data buffer from PC to DSP */ + for (i = 0; i < dsp->size; i += 3) { + data = dsp->data + i; + if (i == 0) { + /* test data header consistency */ + len = (unsigned int)((data[0]<<16) + (data[1]<<8) + data[2]); + snd_assert((len==0) || (dsp->size == (len+2)*3), return -EINVAL); + } + /* wait DSP ready for new transfer */ + err = pcxhr_check_reg_bit(mgr, PCXHR_DSP_ISR, PCXHR_ISR_HI08_TRDY, + PCXHR_ISR_HI08_TRDY, PCXHR_TIMEOUT_DSP, &dummy); + if (err) { + snd_printk(KERN_ERR "dsp loading error at position %d\n", i); + return err; + } + /* send host data */ + PCXHR_OUTPB(mgr, PCXHR_DSP_TXH, data[0]); + PCXHR_OUTPB(mgr, PCXHR_DSP_TXM, data[1]); + PCXHR_OUTPB(mgr, PCXHR_DSP_TXL, data[2]); + + /* don't take too much time in this loop... */ + cond_resched(); + } + /* give some time to boot the DSP */ + msleep(PCXHR_WAIT_DEFAULT); + return 0; +} + +/* + * load the eeprom image + */ +int pcxhr_load_eeprom_binary(struct pcxhr_mgr *mgr, const struct firmware *eeprom) +{ + int err; + unsigned char reg; + + /* init value of the ICR register */ + reg = PCXHR_ICR_HI08_RREQ | PCXHR_ICR_HI08_TREQ | PCXHR_ICR_HI08_HDRQ; + if (PCXHR_INPL(mgr, PCXHR_PLX_MBOX0) & PCXHR_MBOX0_BOOT_HERE) { + /* no need to load the eeprom binary, but init the HI08 interface */ + PCXHR_OUTPB(mgr, PCXHR_DSP_ICR, reg | PCXHR_ICR_HI08_INIT); + msleep(PCXHR_WAIT_DEFAULT); + PCXHR_OUTPB(mgr, PCXHR_DSP_ICR, reg); + msleep(PCXHR_WAIT_DEFAULT); + snd_printdd("no need to load eeprom boot\n"); + return 0; + } + PCXHR_OUTPB(mgr, PCXHR_DSP_ICR, reg); + + err = pcxhr_download_dsp(mgr, eeprom); + if (err) + return err; + /* wait for chk bit */ + return pcxhr_check_reg_bit(mgr, PCXHR_DSP_ISR, PCXHR_ISR_HI08_CHK, + PCXHR_ISR_HI08_CHK, PCXHR_TIMEOUT_DSP, ®); +} + +/* + * load the boot image + */ +int pcxhr_load_boot_binary(struct pcxhr_mgr *mgr, const struct firmware *boot) +{ + int err; + unsigned int physaddr = mgr->hostport.addr; + unsigned char dummy; + + /* send the hostport address to the DSP (only the upper 24 bit !) */ + snd_assert((physaddr & 0xff) == 0, return -EINVAL); + PCXHR_OUTPL(mgr, PCXHR_PLX_MBOX1, (physaddr >> 8)); + + err = pcxhr_send_it_dsp(mgr, PCXHR_IT_DOWNLOAD_BOOT, 0); + if (err) + return err; + /* clear hf5 bit */ + PCXHR_OUTPL(mgr, PCXHR_PLX_MBOX0, + PCXHR_INPL(mgr, PCXHR_PLX_MBOX0) & ~PCXHR_MBOX0_HF5); + + err = pcxhr_download_dsp(mgr, boot); + if (err) + return err; + /* wait for hf5 bit */ + return pcxhr_check_reg_bit(mgr, PCXHR_PLX_MBOX0, PCXHR_MBOX0_HF5, + PCXHR_MBOX0_HF5, PCXHR_TIMEOUT_DSP, &dummy); +} + +/* + * load the final dsp image + */ +int pcxhr_load_dsp_binary(struct pcxhr_mgr *mgr, const struct firmware *dsp) +{ + int err; + unsigned char dummy; + err = pcxhr_send_it_dsp(mgr, PCXHR_IT_RESET_BOARD_FUNC, 0); + if (err) + return err; + err = pcxhr_send_it_dsp(mgr, PCXHR_IT_DOWNLOAD_DSP, 0); + if (err) + return err; + err = pcxhr_download_dsp(mgr, dsp); + if (err) + return err; + /* wait for chk bit */ + return pcxhr_check_reg_bit(mgr, PCXHR_DSP_ISR, PCXHR_ISR_HI08_CHK, + PCXHR_ISR_HI08_CHK, PCXHR_TIMEOUT_DSP, &dummy); +} + + +struct pcxhr_cmd_info { + u32 opcode; /* command word */ + u16 st_length; /* status length */ + u16 st_type; /* status type (RMH_SSIZE_XXX) */ +}; + +/* RMH status type */ +enum { + RMH_SSIZE_FIXED = 0, /* status size fix (st_length = 0..x) */ + RMH_SSIZE_ARG = 1, /* status size given in the LSB byte (used with st_length = 1) */ + RMH_SSIZE_MASK = 2, /* status size given in bitmask (used with st_length = 1) */ +}; + +/* + * Array of DSP commands + */ +static struct pcxhr_cmd_info pcxhr_dsp_cmds[] = { +[CMD_VERSION] = { 0x010000, 1, RMH_SSIZE_FIXED }, +[CMD_SUPPORTED] = { 0x020000, 4, RMH_SSIZE_FIXED }, +[CMD_TEST_IT] = { 0x040000, 1, RMH_SSIZE_FIXED }, +[CMD_SEND_IRQA] = { 0x070001, 0, RMH_SSIZE_FIXED }, +[CMD_ACCESS_IO_WRITE] = { 0x090000, 1, RMH_SSIZE_ARG }, +[CMD_ACCESS_IO_READ] = { 0x094000, 1, RMH_SSIZE_ARG }, +[CMD_ASYNC] = { 0x0a0000, 1, RMH_SSIZE_ARG }, +[CMD_MODIFY_CLOCK] = { 0x0d0000, 0, RMH_SSIZE_FIXED }, +[CMD_RESYNC_AUDIO_INPUTS] = { 0x0e0000, 0, RMH_SSIZE_FIXED }, +[CMD_GET_DSP_RESOURCES] = { 0x100000, 4, RMH_SSIZE_FIXED }, +[CMD_SET_TIMER_INTERRUPT] = { 0x110000, 0, RMH_SSIZE_FIXED }, +[CMD_RES_PIPE] = { 0x400000, 0, RMH_SSIZE_FIXED }, +[CMD_FREE_PIPE] = { 0x410000, 0, RMH_SSIZE_FIXED }, +[CMD_CONF_PIPE] = { 0x422101, 0, RMH_SSIZE_FIXED }, +[CMD_STOP_PIPE] = { 0x470004, 0, RMH_SSIZE_FIXED }, +[CMD_PIPE_SAMPLE_COUNT] = { 0x49a000, 2, RMH_SSIZE_FIXED }, +[CMD_CAN_START_PIPE] = { 0x4b0000, 1, RMH_SSIZE_FIXED }, +[CMD_START_STREAM] = { 0x802000, 0, RMH_SSIZE_FIXED }, +[CMD_STREAM_OUT_LEVEL_ADJUST] = { 0x822000, 0, RMH_SSIZE_FIXED }, +[CMD_STOP_STREAM] = { 0x832000, 0, RMH_SSIZE_FIXED }, +[CMD_UPDATE_R_BUFFERS] = { 0x840000, 0, RMH_SSIZE_FIXED }, +[CMD_FORMAT_STREAM_OUT] = { 0x860000, 0, RMH_SSIZE_FIXED }, +[CMD_FORMAT_STREAM_IN] = { 0x870000, 0, RMH_SSIZE_FIXED }, +[CMD_STREAM_SAMPLE_COUNT] = { 0x902000, 2, RMH_SSIZE_FIXED }, /* stat_len = nb_streams * 2 */ +[CMD_AUDIO_LEVEL_ADJUST] = { 0xc22000, 0, RMH_SSIZE_FIXED }, +}; + +#ifdef CONFIG_SND_DEBUG_DETECT +static char* cmd_names[] = { +[CMD_VERSION] = "CMD_VERSION", +[CMD_SUPPORTED] = "CMD_SUPPORTED", +[CMD_TEST_IT] = "CMD_TEST_IT", +[CMD_SEND_IRQA] = "CMD_SEND_IRQA", +[CMD_ACCESS_IO_WRITE] = "CMD_ACCESS_IO_WRITE", +[CMD_ACCESS_IO_READ] = "CMD_ACCESS_IO_READ", +[CMD_ASYNC] = "CMD_ASYNC", +[CMD_MODIFY_CLOCK] = "CMD_MODIFY_CLOCK", +[CMD_RESYNC_AUDIO_INPUTS] = "CMD_RESYNC_AUDIO_INPUTS", +[CMD_GET_DSP_RESOURCES] = "CMD_GET_DSP_RESOURCES", +[CMD_SET_TIMER_INTERRUPT] = "CMD_SET_TIMER_INTERRUPT", +[CMD_RES_PIPE] = "CMD_RES_PIPE", +[CMD_FREE_PIPE] = "CMD_FREE_PIPE", +[CMD_CONF_PIPE] = "CMD_CONF_PIPE", +[CMD_STOP_PIPE] = "CMD_STOP_PIPE", +[CMD_PIPE_SAMPLE_COUNT] = "CMD_PIPE_SAMPLE_COUNT", +[CMD_CAN_START_PIPE] = "CMD_CAN_START_PIPE", +[CMD_START_STREAM] = "CMD_START_STREAM", +[CMD_STREAM_OUT_LEVEL_ADJUST] = "CMD_STREAM_OUT_LEVEL_ADJUST", +[CMD_STOP_STREAM] = "CMD_STOP_STREAM", +[CMD_UPDATE_R_BUFFERS] = "CMD_UPDATE_R_BUFFERS", +[CMD_FORMAT_STREAM_OUT] = "CMD_FORMAT_STREAM_OUT", +[CMD_FORMAT_STREAM_IN] = "CMD_FORMAT_STREAM_IN", +[CMD_STREAM_SAMPLE_COUNT] = "CMD_STREAM_SAMPLE_COUNT", +[CMD_AUDIO_LEVEL_ADJUST] = "CMD_AUDIO_LEVEL_ADJUST", +}; +#endif + + +static int pcxhr_read_rmh_status(struct pcxhr_mgr *mgr, struct pcxhr_rmh *rmh) +{ + int err; + int i; + u32 data; + u32 size_mask; + unsigned char reg; + int max_stat_len; + + if (rmh->stat_len < PCXHR_SIZE_MAX_STATUS) + max_stat_len = PCXHR_SIZE_MAX_STATUS; + else max_stat_len = rmh->stat_len; + + for (i = 0; i < rmh->stat_len; i++) { + /* wait for receiver full */ + err = pcxhr_check_reg_bit(mgr, PCXHR_DSP_ISR, PCXHR_ISR_HI08_RXDF, + PCXHR_ISR_HI08_RXDF, PCXHR_TIMEOUT_DSP, ®); + if (err) { + snd_printk(KERN_ERR "ERROR RMH stat: ISR:RXDF=1 (ISR = %x; i=%d )\n", + reg, i); + return err; + } + /* read data */ + data = PCXHR_INPB(mgr, PCXHR_DSP_TXH) << 16; + data |= PCXHR_INPB(mgr, PCXHR_DSP_TXM) << 8; + data |= PCXHR_INPB(mgr, PCXHR_DSP_TXL); + + /* need to update rmh->stat_len on the fly ?? */ + if (i==0) { + if (rmh->dsp_stat != RMH_SSIZE_FIXED) { + if (rmh->dsp_stat == RMH_SSIZE_ARG) { + rmh->stat_len = (u16)(data & 0x0000ff) + 1; + data &= 0xffff00; + } else { + /* rmh->dsp_stat == RMH_SSIZE_MASK */ + rmh->stat_len = 1; + size_mask = data; + while (size_mask) { + if (size_mask & 1) + rmh->stat_len++; + size_mask >>= 1; + } + } + } + } +#ifdef CONFIG_SND_DEBUG_DETECT + if (rmh->cmd_idx < CMD_LAST_INDEX) + snd_printdd(" stat[%d]=%x\n", i, data); +#endif + if (i < max_stat_len) + rmh->stat[i] = data; + } + if (rmh->stat_len > max_stat_len) { + snd_printdd("PCXHR : rmh->stat_len=%x too big\n", rmh->stat_len); + rmh->stat_len = max_stat_len; + } + return 0; +} + +static int pcxhr_send_msg_nolock(struct pcxhr_mgr *mgr, struct pcxhr_rmh *rmh) +{ + int err; + int i; + u32 data; + unsigned char reg; + + snd_assert(rmh->cmd_lencmd[0]; + + if (rmh->cmd_len > 1) + data |= 0x008000; /* MASK_MORE_THAN_1_WORD_COMMAND */ + else + data &= 0xff7fff; /* MASK_1_WORD_COMMAND */ +#ifdef CONFIG_SND_DEBUG_DETECT + if (rmh->cmd_idx < CMD_LAST_INDEX) + snd_printdd("MSG cmd[0]=%x (%s)\n", data, cmd_names[rmh->cmd_idx]); +#endif + + err = pcxhr_check_reg_bit(mgr, PCXHR_DSP_ISR, PCXHR_ISR_HI08_TRDY, + PCXHR_ISR_HI08_TRDY, PCXHR_TIMEOUT_DSP, ®); + if (err) + return err; + PCXHR_OUTPB(mgr, PCXHR_DSP_TXH, (data>>16)&0xFF); + PCXHR_OUTPB(mgr, PCXHR_DSP_TXM, (data>>8)&0xFF); + PCXHR_OUTPB(mgr, PCXHR_DSP_TXL, (data&0xFF)); + + if (rmh->cmd_len > 1) { + /* send length */ + data = rmh->cmd_len - 1; + err = pcxhr_check_reg_bit(mgr, PCXHR_DSP_ISR, PCXHR_ISR_HI08_TRDY, + PCXHR_ISR_HI08_TRDY, PCXHR_TIMEOUT_DSP, ®); + if (err) + return err; + PCXHR_OUTPB(mgr, PCXHR_DSP_TXH, (data>>16)&0xFF); + PCXHR_OUTPB(mgr, PCXHR_DSP_TXM, (data>>8)&0xFF); + PCXHR_OUTPB(mgr, PCXHR_DSP_TXL, (data&0xFF)); + + for (i=1; i < rmh->cmd_len; i++) { + /* send other words */ + data = rmh->cmd[i]; +#ifdef CONFIG_SND_DEBUG_DETECT + if (rmh->cmd_idx < CMD_LAST_INDEX) + snd_printdd(" cmd[%d]=%x\n", i, data); +#endif + err = pcxhr_check_reg_bit(mgr, PCXHR_DSP_ISR, + PCXHR_ISR_HI08_TRDY, + PCXHR_ISR_HI08_TRDY, + PCXHR_TIMEOUT_DSP, ®); + if (err) + return err; + PCXHR_OUTPB(mgr, PCXHR_DSP_TXH, (data>>16)&0xFF); + PCXHR_OUTPB(mgr, PCXHR_DSP_TXM, (data>>8)&0xFF); + PCXHR_OUTPB(mgr, PCXHR_DSP_TXL, (data&0xFF)); + } + } + /* wait for chk bit */ + err = pcxhr_check_reg_bit(mgr, PCXHR_DSP_ISR, PCXHR_ISR_HI08_CHK, + PCXHR_ISR_HI08_CHK, PCXHR_TIMEOUT_DSP, ®); + if (err) + return err; + /* test status ISR */ + if (reg & PCXHR_ISR_HI08_ERR) { + /* ERROR, wait for receiver full */ + err = pcxhr_check_reg_bit(mgr, PCXHR_DSP_ISR, PCXHR_ISR_HI08_RXDF, + PCXHR_ISR_HI08_RXDF, PCXHR_TIMEOUT_DSP, ®); + if (err) { + snd_printk(KERN_ERR "ERROR RMH: ISR:RXDF=1 (ISR = %x)\n", reg); + return err; + } + /* read error code */ + data = PCXHR_INPB(mgr, PCXHR_DSP_TXH) << 16; + data |= PCXHR_INPB(mgr, PCXHR_DSP_TXM) << 8; + data |= PCXHR_INPB(mgr, PCXHR_DSP_TXL); + snd_printk(KERN_ERR "ERROR RMH(%d): 0x%x\n", rmh->cmd_idx, data); + err = -EINVAL; + } else { + /* read the response data */ + err = pcxhr_read_rmh_status(mgr, rmh); + } + /* reset semaphore */ + if (pcxhr_send_it_dsp(mgr, PCXHR_IT_RESET_SEMAPHORE, 1) < 0) + return -EIO; + return err; +} + + +/** + * pcxhr_init_rmh - initialize the RMH instance + * @rmh: the rmh pointer to be initialized + * @cmd: the rmh command to be set + */ +void pcxhr_init_rmh(struct pcxhr_rmh *rmh, int cmd) +{ + snd_assert(cmd < CMD_LAST_INDEX, return); + rmh->cmd[0] = pcxhr_dsp_cmds[cmd].opcode; + rmh->cmd_len = 1; + rmh->stat_len = pcxhr_dsp_cmds[cmd].st_length; + rmh->dsp_stat = pcxhr_dsp_cmds[cmd].st_type; + rmh->cmd_idx = cmd; +} + + +void pcxhr_set_pipe_cmd_params(struct pcxhr_rmh *rmh, int capture, + unsigned int param1, unsigned int param2, + unsigned int param3) +{ + snd_assert(param1 <= MASK_FIRST_FIELD); + if (capture) + rmh->cmd[0] |= 0x800; /* COMMAND_RECORD_MASK */ + if (param1) + rmh->cmd[0] |= (param1 << FIELD_SIZE); + if (param2) { + snd_assert(param2 <= MASK_FIRST_FIELD); + rmh->cmd[0] |= param2; + } + if(param3) { + snd_assert(param3 <= MASK_DSP_WORD); + rmh->cmd[1] = param3; + rmh->cmd_len = 2; + } +} + +/* + * pcxhr_send_msg - send a DSP message with spinlock + * @rmh: the rmh record to send and receive + * + * returns 0 if successful, or a negative error code. + */ +int pcxhr_send_msg(struct pcxhr_mgr *mgr, struct pcxhr_rmh *rmh) +{ + unsigned long flags; + int err; + spin_lock_irqsave(&mgr->msg_lock, flags); + err = pcxhr_send_msg_nolock(mgr, rmh); + spin_unlock_irqrestore(&mgr->msg_lock, flags); + return err; +} + +static inline int pcxhr_pipes_running(struct pcxhr_mgr *mgr) +{ + int start_mask = PCXHR_INPL(mgr, PCXHR_PLX_MBOX2); + /* least segnificant 12 bits are the pipe states for the playback audios */ + /* next 12 bits are the pipe states for the capture audios + * (PCXHR_PIPE_STATE_CAPTURE_OFFSET) + */ + start_mask &= 0xffffff; + snd_printdd("CMD_PIPE_STATE MBOX2=0x%06x\n", start_mask); + return start_mask; +} + +#define PCXHR_PIPE_STATE_CAPTURE_OFFSET 12 +#define MAX_WAIT_FOR_DSP 20 + +static int pcxhr_prepair_pipe_start(struct pcxhr_mgr *mgr, int audio_mask, int *retry) +{ + struct pcxhr_rmh rmh; + int err; + int audio = 0; + + *retry = 0; + while (audio_mask) { + if (audio_mask & 1) { + pcxhr_init_rmh(&rmh, CMD_CAN_START_PIPE); + if (audio < PCXHR_PIPE_STATE_CAPTURE_OFFSET) { + /* can start playback pipe */ + pcxhr_set_pipe_cmd_params(&rmh, 0, audio, 0, 0); + } else { + /* can start capture pipe */ + pcxhr_set_pipe_cmd_params(&rmh, 1, audio - + PCXHR_PIPE_STATE_CAPTURE_OFFSET, + 0, 0); + } + err = pcxhr_send_msg(mgr, &rmh); + if (err) { + snd_printk(KERN_ERR + "error pipe start (CMD_CAN_START_PIPE) err=%x!\n", + err); + return err; + } + /* if the pipe couldn't be prepaired for start, retry it later */ + if (rmh.stat[0] == 0) + *retry |= (1<>=1; + audio++; + } + return 0; +} + +static int pcxhr_stop_pipes(struct pcxhr_mgr *mgr, int audio_mask) +{ + struct pcxhr_rmh rmh; + int err; + int audio = 0; + + while (audio_mask) { + if (audio_mask & 1) { + pcxhr_init_rmh(&rmh, CMD_STOP_PIPE); + if (audio < PCXHR_PIPE_STATE_CAPTURE_OFFSET) { + /* stop playback pipe */ + pcxhr_set_pipe_cmd_params(&rmh, 0, audio, 0, 0); + } else { + /* stop capture pipe */ + pcxhr_set_pipe_cmd_params(&rmh, 1, audio - + PCXHR_PIPE_STATE_CAPTURE_OFFSET, + 0, 0); + } + err = pcxhr_send_msg(mgr, &rmh); + if (err) { + snd_printk(KERN_ERR + "error pipe stop (CMD_STOP_PIPE) err=%x!\n", + err); + return err; + } + } + audio_mask>>=1; + audio++; + } + return 0; +} + +static int pcxhr_toggle_pipes(struct pcxhr_mgr *mgr, int audio_mask) +{ + struct pcxhr_rmh rmh; + int err; + int audio = 0; + + while (audio_mask) { + if (audio_mask & 1) { + pcxhr_init_rmh(&rmh, CMD_CONF_PIPE); + if (audio < PCXHR_PIPE_STATE_CAPTURE_OFFSET) + pcxhr_set_pipe_cmd_params(&rmh, 0, 0, 0, 1 << audio); + else + pcxhr_set_pipe_cmd_params(&rmh, 1, 0, 0, + 1 << (audio - PCXHR_PIPE_STATE_CAPTURE_OFFSET)); + err = pcxhr_send_msg(mgr, &rmh); + if (err) { + snd_printk(KERN_ERR + "error pipe start (CMD_CONF_PIPE) err=%x!\n", + err); + return err; + } + } + audio_mask>>=1; + audio++; + } + /* now fire the interrupt on the card */ + pcxhr_init_rmh(&rmh, CMD_SEND_IRQA); + err = pcxhr_send_msg(mgr, &rmh); + if (err) { + snd_printk(KERN_ERR "error pipe start (CMD_SEND_IRQA) err=%x!\n", err ); + return err; + } + return 0; +} + + + +int pcxhr_set_pipe_state(struct pcxhr_mgr *mgr, int playback_mask, int capture_mask, int start) +{ + int state, i, err; + int audio_mask; + +#ifdef CONFIG_SND_DEBUG_DETECT + struct timeval my_tv1, my_tv2; + do_gettimeofday(&my_tv1); +#endif + audio_mask = (playback_mask | (capture_mask << PCXHR_PIPE_STATE_CAPTURE_OFFSET)); + /* current pipe state (playback + record) */ + state = pcxhr_pipes_running(mgr); + snd_printdd("pcxhr_set_pipe_state %s (mask %x current %x)\n", + start ? "START" : "STOP", audio_mask, state); + if (start) { + audio_mask &= ~state; /* start only pipes that are not yet started */ + state = audio_mask; + for (i = 0; i < MAX_WAIT_FOR_DSP; i++) { + err = pcxhr_prepair_pipe_start(mgr, state, &state); + if (err) + return err; + if (state == 0) + break; /* success, all pipes prepaired for start */ + mdelay(1); /* otherwise wait 1 millisecond and retry */ + } + } else { + audio_mask &= state; /* stop only pipes that are started */ + } + if (audio_mask == 0) + return 0; + + err = pcxhr_toggle_pipes(mgr, audio_mask); + if (err) + return err; + + i = 0; + while (1) { + state = pcxhr_pipes_running(mgr); + /* have all pipes the new state ? */ + if ((state & audio_mask) == (start ? audio_mask : 0)) + break; + if (++i >= MAX_WAIT_FOR_DSP * 100) { + snd_printk(KERN_ERR "error pipe start/stop (ED_NO_RESPONSE_AT_IRQA)\n"); + return -EBUSY; + } + udelay(10); /* wait 10 microseconds */ + } + if (!start) { + err = pcxhr_stop_pipes(mgr, audio_mask); + if (err) + return err; + } +#ifdef CONFIG_SND_DEBUG_DETECT + do_gettimeofday(&my_tv2); + snd_printdd("***SET PIPE STATE*** TIME = %ld (err = %x)\n", + my_tv2.tv_usec - my_tv1.tv_usec, err); +#endif + return 0; +} + +int pcxhr_write_io_num_reg_cont(struct pcxhr_mgr *mgr, unsigned int mask, + unsigned int value, int *changed) +{ + struct pcxhr_rmh rmh; + unsigned long flags; + int err; + + spin_lock_irqsave(&mgr->msg_lock, flags); + if ((mgr->io_num_reg_cont & mask) == value) { + snd_printdd("IO_NUM_REG_CONT mask %x already is set to %x\n", mask, value); + if (changed) + *changed = 0; + spin_unlock_irqrestore(&mgr->msg_lock, flags); + return 0; /* already programmed */ + } + pcxhr_init_rmh(&rmh, CMD_ACCESS_IO_WRITE); + rmh.cmd[0] |= IO_NUM_REG_CONT; + rmh.cmd[1] = mask; + rmh.cmd[2] = value; + rmh.cmd_len = 3; + err = pcxhr_send_msg_nolock(mgr, &rmh); + if (err == 0) { + mgr->io_num_reg_cont &= ~mask; + mgr->io_num_reg_cont |= value; + if (changed) + *changed = 1; + } + spin_unlock_irqrestore(&mgr->msg_lock, flags); + return err; +} + +#define PCXHR_IRQ_TIMER 0x000300 +#define PCXHR_IRQ_FREQ_CHANGE 0x000800 +#define PCXHR_IRQ_TIME_CODE 0x001000 +#define PCXHR_IRQ_NOTIFY 0x002000 +#define PCXHR_IRQ_ASYNC 0x008000 +#define PCXHR_IRQ_MASK 0x00bb00 +#define PCXHR_FATAL_DSP_ERR 0xff0000 + +enum pcxhr_async_err_src { + PCXHR_ERR_PIPE, + PCXHR_ERR_STREAM, + PCXHR_ERR_AUDIO +}; + +static int pcxhr_handle_async_err(struct pcxhr_mgr *mgr, u32 err, + enum pcxhr_async_err_src err_src, int pipe, + int is_capture) +{ +#ifdef CONFIG_SND_DEBUG_DETECT + static char* err_src_name[] = { + [PCXHR_ERR_PIPE] = "Pipe", + [PCXHR_ERR_STREAM] = "Stream", + [PCXHR_ERR_AUDIO] = "Audio" + }; +#endif + if (err & 0xfff) + err &= 0xfff; + else + err = ((err >> 12) & 0xfff); + if (!err) + return 0; + snd_printdd("CMD_ASYNC : Error %s %s Pipe %d err=%x\n", err_src_name[err_src], + is_capture ? "Record" : "Play", pipe, err); + if (err == 0xe01) + mgr->async_err_stream_xrun++; + else if (err == 0xe10) + mgr->async_err_pipe_xrun++; + else + mgr->async_err_other_last = (int)err; + return 1; +} + + +void pcxhr_msg_tasklet(unsigned long arg) +{ + struct pcxhr_mgr *mgr = (struct pcxhr_mgr *)(arg); + struct pcxhr_rmh *prmh = mgr->prmh; + int err; + int i, j; + + if (mgr->src_it_dsp & PCXHR_IRQ_FREQ_CHANGE) + snd_printdd("TASKLET : PCXHR_IRQ_FREQ_CHANGE event occured\n"); + if (mgr->src_it_dsp & PCXHR_IRQ_TIME_CODE) + snd_printdd("TASKLET : PCXHR_IRQ_TIME_CODE event occured\n"); + if (mgr->src_it_dsp & PCXHR_IRQ_NOTIFY) + snd_printdd("TASKLET : PCXHR_IRQ_NOTIFY event occured\n"); + if (mgr->src_it_dsp & PCXHR_IRQ_ASYNC) { + snd_printdd("TASKLET : PCXHR_IRQ_ASYNC event occured\n"); + + pcxhr_init_rmh(prmh, CMD_ASYNC); + prmh->cmd[0] |= 1; /* add SEL_ASYNC_EVENTS */ + /* this is the only one extra long response command */ + prmh->stat_len = PCXHR_SIZE_MAX_LONG_STATUS; + err = pcxhr_send_msg(mgr, prmh); + if (err) + snd_printk(KERN_ERR "ERROR pcxhr_msg_tasklet=%x;\n", err); + i = 1; + while (i < prmh->stat_len) { + int nb_audio = (prmh->stat[i] >> FIELD_SIZE) & MASK_FIRST_FIELD; + int nb_stream = (prmh->stat[i] >> (2*FIELD_SIZE)) & MASK_FIRST_FIELD; + int pipe = prmh->stat[i] & MASK_FIRST_FIELD; + int is_capture = prmh->stat[i] & 0x400000; + u32 err; + + if (prmh->stat[i] & 0x800000) { /* if BIT_END */ + snd_printdd("TASKLET : End%sPipe %d\n", + is_capture ? "Record" : "Play", pipe); + } + i++; + err = prmh->stat[i] ? prmh->stat[i] : prmh->stat[i+1]; + if (err) + pcxhr_handle_async_err(mgr, err, PCXHR_ERR_PIPE, + pipe, is_capture); + i += 2; + for (j = 0; j < nb_stream; j++) { + err = prmh->stat[i] ? prmh->stat[i] : prmh->stat[i+1]; + if (err) + pcxhr_handle_async_err(mgr, err, PCXHR_ERR_STREAM, + pipe, is_capture); + i += 2; + } + for (j = 0; j < nb_audio; j++) { + err = prmh->stat[i] ? prmh->stat[i] : prmh->stat[i+1]; + if (err) + pcxhr_handle_async_err(mgr, err, PCXHR_ERR_AUDIO, + pipe, is_capture); + i += 2; + } + } + } +} + +static u_int64_t pcxhr_stream_read_position(struct pcxhr_mgr *mgr, + struct pcxhr_stream *stream) +{ + u_int64_t hw_sample_count; + struct pcxhr_rmh rmh; + int err, stream_mask; + + stream_mask = stream->pipe->is_capture ? 1 : 1<substream->number; + + /* get sample count for one stream */ + pcxhr_init_rmh(&rmh, CMD_STREAM_SAMPLE_COUNT); + pcxhr_set_pipe_cmd_params(&rmh, stream->pipe->is_capture, + stream->pipe->first_audio, 0, stream_mask); + /* rmh.stat_len = 2; */ /* 2 resp data for each stream of the pipe */ + + err = pcxhr_send_msg(mgr, &rmh); + if (err) + return 0; + + hw_sample_count = ((u_int64_t)rmh.stat[0]) << 24; + hw_sample_count += (u_int64_t)rmh.stat[1]; + + snd_printdd("stream %c%d : abs samples real(%ld) timer(%ld)\n", + stream->pipe->is_capture ? 'C':'P', stream->substream->number, + (long unsigned int)hw_sample_count, + (long unsigned int)(stream->timer_abs_periods + + stream->timer_period_frag + PCXHR_GRANULARITY)); + + return hw_sample_count; +} + +static void pcxhr_update_timer_pos(struct pcxhr_mgr *mgr, + struct pcxhr_stream *stream, int samples_to_add) +{ + if (stream->substream && (stream->status == PCXHR_STREAM_STATUS_RUNNING)) { + u_int64_t new_sample_count; + int elapsed = 0; + int hardware_read = 0; + struct snd_pcm_runtime *runtime = stream->substream->runtime; + + if (samples_to_add < 0) { + stream->timer_is_synced = 0; + /* add default if no hardware_read possible */ + samples_to_add = PCXHR_GRANULARITY; + } + + if (!stream->timer_is_synced) { + if (stream->timer_abs_periods != 0 || + stream->timer_period_frag + PCXHR_GRANULARITY >= + runtime->period_size) { + new_sample_count = pcxhr_stream_read_position(mgr, stream); + hardware_read = 1; + if (new_sample_count >= PCXHR_GRANULARITY_MIN) { + /* sub security offset because of jitter and + * finer granularity of dsp time (MBOX4) + */ + new_sample_count -= PCXHR_GRANULARITY_MIN; + stream->timer_is_synced = 1; + } + } + } + if (!hardware_read) { + /* if we didn't try to sync the position, increment it + * by PCXHR_GRANULARITY every timer interrupt + */ + new_sample_count = stream->timer_abs_periods + + stream->timer_period_frag + samples_to_add; + } + while (1) { + u_int64_t new_elapse_pos = stream->timer_abs_periods + + runtime->period_size; + if (new_elapse_pos > new_sample_count) + break; + elapsed = 1; + stream->timer_buf_periods++; + if (stream->timer_buf_periods >= runtime->periods) + stream->timer_buf_periods = 0; + stream->timer_abs_periods = new_elapse_pos; + } + if (new_sample_count >= stream->timer_abs_periods) + stream->timer_period_frag = (u_int32_t)(new_sample_count - + stream->timer_abs_periods); + else + snd_printk(KERN_ERR "ERROR new_sample_count too small ??? %lx\n", + (long unsigned int)new_sample_count); + + if (elapsed) { + spin_unlock(&mgr->lock); + snd_pcm_period_elapsed(stream->substream); + spin_lock(&mgr->lock); + } + } +} + + +irqreturn_t pcxhr_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + struct pcxhr_mgr *mgr = dev_id; + unsigned int reg; + int i, j; + struct snd_pcxhr *chip; + + spin_lock(&mgr->lock); + + reg = PCXHR_INPL(mgr, PCXHR_PLX_IRQCS); + if (! (reg & PCXHR_IRQCS_ACTIVE_PCIDB)) { + spin_unlock(&mgr->lock); + return IRQ_NONE; /* this device did not cause the interrupt */ + } + + /* clear interrupt */ + reg = PCXHR_INPL(mgr, PCXHR_PLX_L2PCIDB); + PCXHR_OUTPL(mgr, PCXHR_PLX_L2PCIDB, reg); + + /* timer irq occured */ + if (reg & PCXHR_IRQ_TIMER) { + int timer_toggle = reg & PCXHR_IRQ_TIMER; + /* is a 24 bit counter */ + int dsp_time_new = PCXHR_INPL(mgr, PCXHR_PLX_MBOX4) & PCXHR_DSP_TIME_MASK; + int dsp_time_diff = dsp_time_new - mgr->dsp_time_last; + + if (dsp_time_diff < 0 && mgr->dsp_time_last != PCXHR_DSP_TIME_INVALID) { + snd_printdd("ERROR DSP TIME old(%d) new(%d) -> " + "resynchronize all streams\n", + mgr->dsp_time_last, dsp_time_new); + mgr->dsp_time_err++; + } +#ifdef CONFIG_SND_DEBUG_DETECT + if (dsp_time_diff == 0) + snd_printdd("ERROR DSP TIME NO DIFF time(%d)\n", dsp_time_new); + else if (dsp_time_diff >= (2*PCXHR_GRANULARITY)) + snd_printdd("ERROR DSP TIME TOO BIG old(%d) add(%d)\n", + mgr->dsp_time_last, dsp_time_new - mgr->dsp_time_last); +#endif + mgr->dsp_time_last = dsp_time_new; + + if (timer_toggle == mgr->timer_toggle) + snd_printk(KERN_ERR "ERROR TIMER TOGGLE\n"); + mgr->timer_toggle = timer_toggle; + + reg &= ~PCXHR_IRQ_TIMER; + for (i = 0; i < mgr->num_cards; i++) { + chip = mgr->chip[i]; + for (j = 0; j < chip->nb_streams_capt; j++) + pcxhr_update_timer_pos(mgr, &chip->capture_stream[j], + dsp_time_diff); + } + for (i = 0; i < mgr->num_cards; i++) { + chip = mgr->chip[i]; + for (j = 0; j < chip->nb_streams_play; j++) + pcxhr_update_timer_pos(mgr, &chip->playback_stream[j], + dsp_time_diff); + } + } + /* other irq's handled in the tasklet */ + if (reg & PCXHR_IRQ_MASK) { + + /* as we didn't request any notifications, some kind of xrun error + * will probably occured + */ + /* better resynchronize all streams next interrupt : */ + mgr->dsp_time_last = PCXHR_DSP_TIME_INVALID; + + mgr->src_it_dsp = reg; + tasklet_hi_schedule(&mgr->msg_taskq); + } +#ifdef CONFIG_SND_DEBUG_DETECT + if (reg & PCXHR_FATAL_DSP_ERR) + snd_printdd("FATAL DSP ERROR : %x\n", reg); +#endif + spin_unlock(&mgr->lock); + return IRQ_HANDLED; /* this device caused the interrupt */ +} diff --git a/sound/pci/pcxhr/pcxhr_core.h b/sound/pci/pcxhr/pcxhr_core.h new file mode 100644 index 000000000000..e7415d6d1826 --- /dev/null +++ b/sound/pci/pcxhr/pcxhr_core.h @@ -0,0 +1,200 @@ +/* + * Driver for Digigram pcxhr compatible soundcards + * + * low level interface with interrupt ans message handling + * + * Copyright (c) 2004 by Digigram + * + * 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 + */ + +#ifndef __SOUND_PCXHR_CORE_H +#define __SOUND_PCXHR_CORE_H + +struct firmware; +struct pcxhr_mgr; + +/* init and firmware download commands */ +void pcxhr_reset_xilinx_com(struct pcxhr_mgr *mgr); +void pcxhr_reset_dsp(struct pcxhr_mgr *mgr); +void pcxhr_enable_dsp(struct pcxhr_mgr *mgr); +int pcxhr_load_xilinx_binary(struct pcxhr_mgr *mgr, const struct firmware *xilinx, int second); +int pcxhr_load_eeprom_binary(struct pcxhr_mgr *mgr, const struct firmware *eeprom); +int pcxhr_load_boot_binary(struct pcxhr_mgr *mgr, const struct firmware *boot); +int pcxhr_load_dsp_binary(struct pcxhr_mgr *mgr, const struct firmware *dsp); + +/* DSP time available on MailBox4 register : 24 bit time samples() */ +#define PCXHR_DSP_TIME_MASK 0x00ffffff +#define PCXHR_DSP_TIME_INVALID 0x10000000 + + +#define PCXHR_SIZE_MAX_CMD 8 +#define PCXHR_SIZE_MAX_STATUS 16 +#define PCXHR_SIZE_MAX_LONG_STATUS 256 + +struct pcxhr_rmh { + u16 cmd_len; /* length of the command to send (WORDs) */ + u16 stat_len; /* length of the status received (WORDs) */ + u16 dsp_stat; /* status type, RMP_SSIZE_XXX */ + u16 cmd_idx; /* index of the command */ + u32 cmd[PCXHR_SIZE_MAX_CMD]; + u32 stat[PCXHR_SIZE_MAX_STATUS]; +}; + +enum { + CMD_VERSION, /* cmd_len = 2 stat_len = 1 */ + CMD_SUPPORTED, /* cmd_len = 1 stat_len = 4 */ + CMD_TEST_IT, /* cmd_len = 1 stat_len = 1 */ + CMD_SEND_IRQA, /* cmd_len = 1 stat_len = 0 */ + CMD_ACCESS_IO_WRITE, /* cmd_len >= 1 stat_len >= 1 */ + CMD_ACCESS_IO_READ, /* cmd_len >= 1 stat_len >= 1 */ + CMD_ASYNC, /* cmd_len = 1 stat_len = 1 */ + CMD_MODIFY_CLOCK, /* cmd_len = 3 stat_len = 0 */ + CMD_RESYNC_AUDIO_INPUTS, /* cmd_len = 1 stat_len = 0 */ + CMD_GET_DSP_RESOURCES, /* cmd_len = 1 stat_len = 4 */ + CMD_SET_TIMER_INTERRUPT, /* cmd_len = 1 stat_len = 0 */ + CMD_RES_PIPE, /* cmd_len = 2 stat_len = 0 */ + CMD_FREE_PIPE, /* cmd_len = 1 stat_len = 0 */ + CMD_CONF_PIPE, /* cmd_len = 2 stat_len = 0 */ + CMD_STOP_PIPE, /* cmd_len = 1 stat_len = 0 */ + CMD_PIPE_SAMPLE_COUNT, /* cmd_len = 2 stat_len = 2 */ + CMD_CAN_START_PIPE, /* cmd_len >= 1 stat_len = 1 */ + CMD_START_STREAM, /* cmd_len = 2 stat_len = 0 */ + CMD_STREAM_OUT_LEVEL_ADJUST, /* cmd_len >= 1 stat_len = 0 */ + CMD_STOP_STREAM, /* cmd_len = 2 stat_len = 0 */ + CMD_UPDATE_R_BUFFERS, /* cmd_len = 4 stat_len = 0 */ + CMD_FORMAT_STREAM_OUT, /* cmd_len >= 2 stat_len = 0 */ + CMD_FORMAT_STREAM_IN, /* cmd_len >= 4 stat_len = 0 */ + CMD_STREAM_SAMPLE_COUNT, /* cmd_len = 2 stat_len = (2 * nb_stream) */ + CMD_AUDIO_LEVEL_ADJUST, /* cmd_len = 3 stat_len = 0 */ + CMD_LAST_INDEX +}; + +#define MASK_DSP_WORD 0x00ffffff +#define MASK_ALL_STREAM 0x00ffffff +#define MASK_DSP_WORD_LEVEL 0x000001ff +#define MASK_FIRST_FIELD 0x0000001f +#define FIELD_SIZE 5 + +/* + init the rmh struct; by default cmd_len is set to 1 + */ +void pcxhr_init_rmh(struct pcxhr_rmh *rmh, int cmd); + +void pcxhr_set_pipe_cmd_params(struct pcxhr_rmh* rmh, int capture, unsigned int param1, + unsigned int param2, unsigned int param3); + +/* + send the rmh + */ +int pcxhr_send_msg(struct pcxhr_mgr *mgr, struct pcxhr_rmh *rmh); + + +/* values used for CMD_ACCESS_IO_WRITE and CMD_ACCESS_IO_READ */ +#define IO_NUM_REG_CONT 0 +#define IO_NUM_REG_GENCLK 1 +#define IO_NUM_REG_MUTE_OUT 2 +#define IO_NUM_SPEED_RATIO 4 +#define IO_NUM_REG_STATUS 5 +#define IO_NUM_REG_CUER 10 +#define IO_NUM_UER_CHIP_REG 11 +#define IO_NUM_REG_OUT_ANA_LEVEL 20 +#define IO_NUM_REG_IN_ANA_LEVEL 21 + + +#define REG_CONT_UNMUTE_INPUTS 0x020000 + +/* parameters used with register IO_NUM_REG_STATUS */ +#define REG_STATUS_OPTIONS 0 +#define REG_STATUS_AES_SYNC 8 +#define REG_STATUS_AES_1 9 +#define REG_STATUS_AES_2 10 +#define REG_STATUS_AES_3 11 +#define REG_STATUS_AES_4 12 +#define REG_STATUS_WORD_CLOCK 13 +#define REG_STATUS_INTER_SYNC 14 +#define REG_STATUS_CURRENT 0x80 +/* results */ +#define REG_STATUS_OPT_NO_VIDEO_SIGNAL 0x01 +#define REG_STATUS_OPT_DAUGHTER_MASK 0x1c +#define REG_STATUS_OPT_ANALOG_BOARD 0x00 +#define REG_STATUS_OPT_NO_DAUGHTER 0x1c +#define REG_STATUS_OPT_COMPANION_MASK 0xe0 +#define REG_STATUS_OPT_NO_COMPANION 0xe0 +#define REG_STATUS_SYNC_32000 0x00 +#define REG_STATUS_SYNC_44100 0x01 +#define REG_STATUS_SYNC_48000 0x02 +#define REG_STATUS_SYNC_64000 0x03 +#define REG_STATUS_SYNC_88200 0x04 +#define REG_STATUS_SYNC_96000 0x05 +#define REG_STATUS_SYNC_128000 0x06 +#define REG_STATUS_SYNC_176400 0x07 +#define REG_STATUS_SYNC_192000 0x08 + +int pcxhr_set_pipe_state(struct pcxhr_mgr *mgr, int playback_mask, int capture_mask, int start); + +int pcxhr_write_io_num_reg_cont(struct pcxhr_mgr *mgr, unsigned int mask, + unsigned int value, int *changed); + +/* codec parameters */ +#define CS8416_RUN 0x200401 +#define CS8416_FORMAT_DETECT 0x200b00 +#define CS8416_CSB0 0x201900 +#define CS8416_CSB1 0x201a00 +#define CS8416_CSB2 0x201b00 +#define CS8416_CSB3 0x201c00 +#define CS8416_CSB4 0x201d00 +#define CS8416_VERSION 0x207f00 + +#define CS8420_DATA_FLOW_CTL 0x200301 +#define CS8420_CLOCK_SRC_CTL 0x200401 +#define CS8420_RECEIVER_ERRORS 0x201000 +#define CS8420_SRC_RATIO 0x201e00 +#define CS8420_CSB0 0x202000 +#define CS8420_CSB1 0x202100 +#define CS8420_CSB2 0x202200 +#define CS8420_CSB3 0x202300 +#define CS8420_CSB4 0x202400 +#define CS8420_VERSION 0x207f00 + +#define CS4271_MODE_CTL_1 0x200101 +#define CS4271_DAC_CTL 0x200201 +#define CS4271_VOLMIX 0x200301 +#define CS4271_VOLMUTE_LEFT 0x200401 +#define CS4271_VOLMUTE_RIGHT 0x200501 +#define CS4271_ADC_CTL 0x200601 +#define CS4271_MODE_CTL_2 0x200701 + +#define CHIP_SIG_AND_MAP_SPI 0xff7f00 + +/* codec selection */ +#define CS4271_01_CS 0x160018 +#define CS4271_23_CS 0x160019 +#define CS4271_45_CS 0x16001a +#define CS4271_67_CS 0x16001b +#define CS4271_89_CS 0x16001c +#define CS4271_AB_CS 0x16001d +#define CS8420_01_CS 0x080090 +#define CS8420_23_CS 0x080092 +#define CS8420_45_CS 0x080094 +#define CS8420_67_CS 0x080096 +#define CS8416_01_CS 0x080098 + + +/* interrupt handling */ +irqreturn_t pcxhr_interrupt(int irq, void *dev_id, struct pt_regs *regs); +void pcxhr_msg_tasklet(unsigned long arg); + +#endif /* __SOUND_PCXHR_CORE_H */ diff --git a/sound/pci/pcxhr/pcxhr_hwdep.c b/sound/pci/pcxhr/pcxhr_hwdep.c new file mode 100644 index 000000000000..03517c10e99c --- /dev/null +++ b/sound/pci/pcxhr/pcxhr_hwdep.c @@ -0,0 +1,438 @@ +/* + * Driver for Digigram pcxhr compatible soundcards + * + * hwdep device manager + * + * Copyright (c) 2004 by Digigram + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "pcxhr.h" +#include "pcxhr_mixer.h" +#include "pcxhr_hwdep.h" +#include "pcxhr_core.h" + + +#if defined(CONFIG_FW_LOADER) || defined(CONFIG_FW_LOADER_MODULE) +#if !defined(CONFIG_USE_PCXHRLOADER) && !defined(CONFIG_SND_PCXHR) /* built-in kernel */ +#define SND_PCXHR_FW_LOADER /* use the standard firmware loader */ +#endif +#endif + + +/* + * get basic information and init pcxhr card + */ + +static int pcxhr_init_board(struct pcxhr_mgr *mgr) +{ + int err; + struct pcxhr_rmh rmh; + int card_streams; + + /* calc the number of all streams used */ + if (mgr->mono_capture) + card_streams = mgr->capture_chips * 2; + else + card_streams = mgr->capture_chips; + card_streams += mgr->playback_chips * PCXHR_PLAYBACK_STREAMS; + + /* enable interrupts */ + pcxhr_enable_dsp(mgr); + + pcxhr_init_rmh(&rmh, CMD_SUPPORTED); + err = pcxhr_send_msg(mgr, &rmh); + if (err) + return err; + /* test 8 or 12 phys out */ + snd_assert((rmh.stat[0] & MASK_FIRST_FIELD) == mgr->playback_chips*2, + return -EINVAL); + /* test 8 or 2 phys in */ + snd_assert(((rmh.stat[0] >> (2*FIELD_SIZE)) & MASK_FIRST_FIELD) == + mgr->capture_chips * 2, return -EINVAL); + /* test max nb substream per board */ + snd_assert((rmh.stat[1] & 0x5F) >= card_streams, return -EINVAL); + /* test max nb substream per pipe */ + snd_assert(((rmh.stat[1]>>7)&0x5F) >= PCXHR_PLAYBACK_STREAMS, return -EINVAL); + + pcxhr_init_rmh(&rmh, CMD_VERSION); + /* firmware num for DSP */ + rmh.cmd[0] |= mgr->firmware_num; + /* transfer granularity in samples (should be multiple of 48) */ + rmh.cmd[1] = (1<<23) + PCXHR_GRANULARITY; + rmh.cmd_len = 2; + err = pcxhr_send_msg(mgr, &rmh); + if (err) + return err; + snd_printdd("PCXHR DSP version is %d.%d.%d\n", + (rmh.stat[0]>>16)&0xff, (rmh.stat[0]>>8)&0xff, rmh.stat[0]&0xff); + mgr->dsp_version = rmh.stat[0]; + + /* get options */ + pcxhr_init_rmh(&rmh, CMD_ACCESS_IO_READ); + rmh.cmd[0] |= IO_NUM_REG_STATUS; + rmh.cmd[1] = REG_STATUS_OPTIONS; + rmh.cmd_len = 2; + err = pcxhr_send_msg(mgr, &rmh); + if (err) + return err; + + if ((rmh.stat[1] & REG_STATUS_OPT_DAUGHTER_MASK) == REG_STATUS_OPT_ANALOG_BOARD) + mgr->board_has_analog = 1; /* analog addon board available */ + else + /* analog addon board not available -> no support for instance */ + return -EINVAL; + + /* unmute inputs */ + err = pcxhr_write_io_num_reg_cont(mgr, REG_CONT_UNMUTE_INPUTS, + REG_CONT_UNMUTE_INPUTS, NULL); + if (err) + return err; + /* unmute outputs */ + pcxhr_init_rmh(&rmh, CMD_ACCESS_IO_READ); /* a write to IO_NUM_REG_MUTE_OUT mutes! */ + rmh.cmd[0] |= IO_NUM_REG_MUTE_OUT; + err = pcxhr_send_msg(mgr, &rmh); + return err; +} + +void pcxhr_reset_board(struct pcxhr_mgr *mgr) +{ + struct pcxhr_rmh rmh; + + if (mgr->dsp_loaded & (1 << PCXHR_FIRMWARE_DSP_MAIN_INDEX)) { + /* mute outputs */ + /* a read to IO_NUM_REG_MUTE_OUT register unmutes! */ + pcxhr_init_rmh(&rmh, CMD_ACCESS_IO_WRITE); + rmh.cmd[0] |= IO_NUM_REG_MUTE_OUT; + pcxhr_send_msg(mgr, &rmh); + /* mute inputs */ + pcxhr_write_io_num_reg_cont(mgr, REG_CONT_UNMUTE_INPUTS, 0, NULL); + } + /* reset pcxhr dsp */ + if (mgr->dsp_loaded & ( 1 << PCXHR_FIRMWARE_DSP_EPRM_INDEX)) + pcxhr_reset_dsp(mgr); + /* reset second xilinx */ + if (mgr->dsp_loaded & ( 1 << PCXHR_FIRMWARE_XLX_COM_INDEX)) + pcxhr_reset_xilinx_com(mgr); + return; +} + + +/* + * allocate a playback/capture pipe (pcmp0/pcmc0) + */ +static int pcxhr_dsp_allocate_pipe( struct pcxhr_mgr *mgr, struct pcxhr_pipe *pipe, + int is_capture, int pin) +{ + int stream_count, audio_count; + int err; + struct pcxhr_rmh rmh; + + if (is_capture) { + stream_count = 1; + if (mgr->mono_capture) + audio_count = 1; + else + audio_count = 2; + } else { + stream_count = PCXHR_PLAYBACK_STREAMS; + audio_count = 2; /* always stereo */ + } + snd_printdd("snd_add_ref_pipe pin(%d) pcm%c0\n", pin, is_capture ? 'c' : 'p'); + pipe->is_capture = is_capture; + pipe->first_audio = pin; + /* define pipe (P_PCM_ONLY_MASK (0x020000) is not necessary) */ + pcxhr_init_rmh(&rmh, CMD_RES_PIPE); + pcxhr_set_pipe_cmd_params(&rmh, is_capture, pin, audio_count, stream_count); + err = pcxhr_send_msg(mgr, &rmh); + if (err < 0) { + snd_printk(KERN_ERR "error pipe allocation (CMD_RES_PIPE) err=%x!\n", err ); + return err; + } + pipe->status = PCXHR_PIPE_DEFINED; + + return 0; +} + +/* + * free playback/capture pipe (pcmp0/pcmc0) + */ +#if 0 +static int pcxhr_dsp_free_pipe( struct pcxhr_mgr *mgr, struct pcxhr_pipe *pipe) +{ + struct pcxhr_rmh rmh; + int capture_mask = 0; + int playback_mask = 0; + int err = 0; + + if (pipe->is_capture) + capture_mask = (1 << pipe->first_audio); + else + playback_mask = (1 << pipe->first_audio); + + /* stop one pipe */ + err = pcxhr_set_pipe_state(mgr, playback_mask, capture_mask, 0); + if (err < 0) + snd_printk(KERN_ERR "error stopping pipe!\n"); + /* release the pipe */ + pcxhr_init_rmh(&rmh, CMD_FREE_PIPE); + pcxhr_set_pipe_cmd_params(&rmh, pipe->is_capture, pipe->first_audio, 0, 0); + err = pcxhr_send_msg(mgr, &rmh); + if (err < 0) + snd_printk(KERN_ERR "error pipe release (CMD_FREE_PIPE) err(%x)\n", err); + pipe->status = PCXHR_PIPE_UNDEFINED; + return err; +} +#endif + + +static int pcxhr_config_pipes(struct pcxhr_mgr *mgr) +{ + int err, i, j; + struct snd_pcxhr *chip; + struct pcxhr_pipe *pipe; + + /* allocate the pipes on the dsp */ + for (i = 0; i < mgr->num_cards; i++) { + chip = mgr->chip[i]; + if (chip->nb_streams_play) { + pipe = &chip->playback_pipe; + err = pcxhr_dsp_allocate_pipe( mgr, pipe, 0, i*2); + if (err) + return err; + for(j = 0; j < chip->nb_streams_play; j++) + chip->playback_stream[j].pipe = pipe; + } + for (j = 0; j < chip->nb_streams_capt; j++) { + pipe = &chip->capture_pipe[j]; + err = pcxhr_dsp_allocate_pipe(mgr, pipe, 1, i*2 + j); + if (err) + return err; + chip->capture_stream[j].pipe = pipe; + } + } + return 0; +} + +static int pcxhr_start_pipes(struct pcxhr_mgr *mgr) +{ + int i, j; + struct snd_pcxhr *chip; + int playback_mask = 0; + int capture_mask = 0; + + /* start all the pipes on the dsp */ + for (i = 0; i < mgr->num_cards; i++) { + chip = mgr->chip[i]; + if (chip->nb_streams_play) + playback_mask |= (1 << chip->playback_pipe.first_audio); + for (j = 0; j < chip->nb_streams_capt; j++) + capture_mask |= (1 << chip->capture_pipe[j].first_audio); + } + return pcxhr_set_pipe_state(mgr, playback_mask, capture_mask, 1); +} + + +static int pcxhr_dsp_load(struct pcxhr_mgr *mgr, int index, const struct firmware *dsp) +{ + int err, card_index; + + snd_printdd("loading dsp [%d] size = %Zd\n", index, dsp->size); + + switch (index) { + case PCXHR_FIRMWARE_XLX_INT_INDEX: + pcxhr_reset_xilinx_com(mgr); + return pcxhr_load_xilinx_binary(mgr, dsp, 0); + + case PCXHR_FIRMWARE_XLX_COM_INDEX: + pcxhr_reset_xilinx_com(mgr); + return pcxhr_load_xilinx_binary(mgr, dsp, 1); + + case PCXHR_FIRMWARE_DSP_EPRM_INDEX: + pcxhr_reset_dsp(mgr); + return pcxhr_load_eeprom_binary(mgr, dsp); + + case PCXHR_FIRMWARE_DSP_BOOT_INDEX: + return pcxhr_load_boot_binary(mgr, dsp); + + case PCXHR_FIRMWARE_DSP_MAIN_INDEX: + err = pcxhr_load_dsp_binary(mgr, dsp); + if (err) + return err; + break; /* continue with first init */ + default: + snd_printk(KERN_ERR "wrong file index\n"); + return -EFAULT; + } /* end of switch file index*/ + + /* first communication with embedded */ + err = pcxhr_init_board(mgr); + if (err < 0) { + snd_printk(KERN_ERR "pcxhr could not be set up\n"); + return err; + } + err = pcxhr_config_pipes(mgr); + if (err < 0) { + snd_printk(KERN_ERR "pcxhr pipes could not be set up\n"); + return err; + } + /* create devices and mixer in accordance with HW options*/ + for (card_index = 0; card_index < mgr->num_cards; card_index++) { + struct snd_pcxhr *chip = mgr->chip[card_index]; + + if ((err = pcxhr_create_pcm(chip)) < 0) + return err; + + if (card_index == 0) { + if ((err = pcxhr_create_mixer(chip->mgr)) < 0) + return err; + } + if ((err = snd_card_register(chip->card)) < 0) + return err; + } + err = pcxhr_start_pipes(mgr); + if (err < 0) { + snd_printk(KERN_ERR "pcxhr pipes could not be started\n"); + return err; + } + snd_printdd("pcxhr firmware downloaded and successfully set up\n"); + + return 0; +} + +/* + * fw loader entry + */ +#ifdef SND_PCXHR_FW_LOADER + +int pcxhr_setup_firmware(struct pcxhr_mgr *mgr) +{ + static char *fw_files[5] = { + "xi_1_882.dat", + "xc_1_882.dat", + "e321_512.e56", + "b321_512.b56", + "d321_512.d56" + }; + char path[32]; + + const struct firmware *fw_entry; + int i, err; + + for (i = 0; i < ARRAY_SIZE(fw_files); i++) { + sprintf(path, "pcxhr/%s", fw_files[i]); + if (request_firmware(&fw_entry, path, &mgr->pci->dev)) { + snd_printk(KERN_ERR "pcxhr: can't load firmware %s\n", path); + return -ENOENT; + } + /* fake hwdep dsp record */ + err = pcxhr_dsp_load(mgr, i, fw_entry); + release_firmware(fw_entry); + if (err < 0) + return err; + mgr->dsp_loaded |= 1 << i; + } + return 0; +} + +#else /* old style firmware loading */ + +/* pcxhr hwdep interface id string */ +#define PCXHR_HWDEP_ID "pcxhr loader" + + +static int pcxhr_hwdep_dsp_status(struct snd_hwdep *hw, + struct snd_hwdep_dsp_status *info) +{ + strcpy(info->id, "pcxhr"); + info->num_dsps = PCXHR_FIRMWARE_FILES_MAX_INDEX; + + if (hw->dsp_loaded & (1 << PCXHR_FIRMWARE_DSP_MAIN_INDEX)) + info->chip_ready = 1; + + info->version = PCXHR_DRIVER_VERSION; + return 0; +} + +static int pcxhr_hwdep_dsp_load(struct snd_hwdep *hw, + struct snd_hwdep_dsp_image *dsp) +{ + struct pcxhr_mgr *mgr = hw->private_data; + int err; + struct firmware fw; + + fw.size = dsp->length; + fw.data = vmalloc(fw.size); + if (! fw.data) { + snd_printk(KERN_ERR "pcxhr: cannot allocate dsp image (%d bytes)\n", + fw.size); + return -ENOMEM; + } + if (copy_from_user(fw.data, dsp->image, dsp->length)) { + vfree(fw.data); + return -EFAULT; + } + err = pcxhr_dsp_load(mgr, dsp->index, &fw); + vfree(fw.data); + if (err < 0) + return err; + mgr->dsp_loaded |= 1 << dsp->index; + return 0; +} + +static int pcxhr_hwdep_open(struct snd_hwdep *hw, struct file *file) +{ + return 0; +} + +static int pcxhr_hwdep_release(struct snd_hwdep *hw, struct file *file) +{ + return 0; +} + +int pcxhr_setup_firmware(struct pcxhr_mgr *mgr) +{ + int err; + struct snd_hwdep *hw; + + /* only create hwdep interface for first cardX (see "index" module parameter)*/ + if ((err = snd_hwdep_new(mgr->chip[0]->card, PCXHR_HWDEP_ID, 0, &hw)) < 0) + return err; + + hw->iface = SNDRV_HWDEP_IFACE_PCXHR; + hw->private_data = mgr; + hw->ops.open = pcxhr_hwdep_open; + hw->ops.release = pcxhr_hwdep_release; + hw->ops.dsp_status = pcxhr_hwdep_dsp_status; + hw->ops.dsp_load = pcxhr_hwdep_dsp_load; + hw->exclusive = 1; + mgr->dsp_loaded = 0; + sprintf(hw->name, PCXHR_HWDEP_ID); + + if ((err = snd_card_register(mgr->chip[0]->card)) < 0) + return err; + return 0; +} + +#endif /* SND_PCXHR_FW_LOADER */ diff --git a/sound/pci/pcxhr/pcxhr_hwdep.h b/sound/pci/pcxhr/pcxhr_hwdep.h new file mode 100644 index 000000000000..f561909dc05f --- /dev/null +++ b/sound/pci/pcxhr/pcxhr_hwdep.h @@ -0,0 +1,40 @@ +/* + * Driver for Digigram pcxhr compatible soundcards + * + * definitions and makros for basic card access + * + * Copyright (c) 2004 by Digigram + * + * 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 + */ + +#ifndef __SOUND_PCXHR_HWDEP_H +#define __SOUND_PCXHR_HWDEP_H + + +/* firmware status codes */ +#define PCXHR_FIRMWARE_XLX_INT_INDEX 0 +#define PCXHR_FIRMWARE_XLX_COM_INDEX 1 +#define PCXHR_FIRMWARE_DSP_EPRM_INDEX 2 +#define PCXHR_FIRMWARE_DSP_BOOT_INDEX 3 +#define PCXHR_FIRMWARE_DSP_MAIN_INDEX 4 +#define PCXHR_FIRMWARE_FILES_MAX_INDEX 5 + + +/* exported */ +int pcxhr_setup_firmware(struct pcxhr_mgr *mgr); +void pcxhr_reset_board(struct pcxhr_mgr *mgr); + +#endif /* __SOUND_PCXHR_HWDEP_H */ diff --git a/sound/pci/pcxhr/pcxhr_mixer.c b/sound/pci/pcxhr/pcxhr_mixer.c new file mode 100644 index 000000000000..760e733ac25e --- /dev/null +++ b/sound/pci/pcxhr/pcxhr_mixer.c @@ -0,0 +1,1020 @@ +#define __NO_VERSION__ +/* + * Driver for Digigram pcxhr compatible soundcards + * + * mixer callbacks + * + * Copyright (c) 2004 by Digigram + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include "pcxhr.h" +#include "pcxhr_hwdep.h" +#include "pcxhr_core.h" +#include +#include +#include "pcxhr_mixer.h" + + +#define PCXHR_ANALOG_CAPTURE_LEVEL_MIN 0 /* -96.0 dB */ +#define PCXHR_ANALOG_CAPTURE_LEVEL_MAX 255 /* +31.5 dB */ +#define PCXHR_ANALOG_CAPTURE_ZERO_LEVEL 224 /* +16.0 dB ( +31.5 dB - fix level +15.5 dB ) */ + +#define PCXHR_ANALOG_PLAYBACK_LEVEL_MIN 0 /* -128.0 dB */ +#define PCXHR_ANALOG_PLAYBACK_LEVEL_MAX 128 /* 0.0 dB */ +#define PCXHR_ANALOG_PLAYBACK_ZERO_LEVEL 104 /* -24.0 dB ( 0.0 dB - fix level +24.0 dB ) */ + +static int pcxhr_update_analog_audio_level(struct snd_pcxhr *chip, int is_capture, int channel) +{ + int err, vol; + struct pcxhr_rmh rmh; + + pcxhr_init_rmh(&rmh, CMD_ACCESS_IO_WRITE); + if (is_capture) { + rmh.cmd[0] |= IO_NUM_REG_IN_ANA_LEVEL; + rmh.cmd[2] = chip->analog_capture_volume[channel]; + } else { + rmh.cmd[0] |= IO_NUM_REG_OUT_ANA_LEVEL; + if (chip->analog_playback_active[channel]) + vol = chip->analog_playback_volume[channel]; + else + vol = PCXHR_ANALOG_PLAYBACK_LEVEL_MIN; + rmh.cmd[2] = PCXHR_ANALOG_PLAYBACK_LEVEL_MAX - vol; /* playback analog levels are inversed */ + } + rmh.cmd[1] = 1 << ((2 * chip->chip_idx) + channel); /* audio mask */ + rmh.cmd_len = 3; + err = pcxhr_send_msg(chip->mgr, &rmh); + if (err < 0) { + snd_printk(KERN_DEBUG "error update_analog_audio_level card(%d) " + "is_capture(%d) err(%x)\n", chip->chip_idx, is_capture, err); + return -EINVAL; + } + return 0; +} + +/* + * analog level control + */ +static int pcxhr_analog_vol_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 2; + if (kcontrol->private_value == 0) { /* playback */ + uinfo->value.integer.min = PCXHR_ANALOG_PLAYBACK_LEVEL_MIN; /* -128 dB */ + uinfo->value.integer.max = PCXHR_ANALOG_PLAYBACK_LEVEL_MAX; /* 0 dB */ + } else { /* capture */ + uinfo->value.integer.min = PCXHR_ANALOG_CAPTURE_LEVEL_MIN; /* -96 dB */ + uinfo->value.integer.max = PCXHR_ANALOG_CAPTURE_LEVEL_MAX; /* 31.5 dB */ + } + return 0; +} + +static int pcxhr_analog_vol_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pcxhr *chip = snd_kcontrol_chip(kcontrol); + down(&chip->mgr->mixer_mutex); + if (kcontrol->private_value == 0) { /* playback */ + ucontrol->value.integer.value[0] = chip->analog_playback_volume[0]; + ucontrol->value.integer.value[1] = chip->analog_playback_volume[1]; + } else { /* capture */ + ucontrol->value.integer.value[0] = chip->analog_capture_volume[0]; + ucontrol->value.integer.value[1] = chip->analog_capture_volume[1]; + } + up(&chip->mgr->mixer_mutex); + return 0; +} + +static int pcxhr_analog_vol_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pcxhr *chip = snd_kcontrol_chip(kcontrol); + int changed = 0; + int is_capture, i; + + down(&chip->mgr->mixer_mutex); + is_capture = (kcontrol->private_value != 0); + for (i = 0; i < 2; i++) { + int new_volume = ucontrol->value.integer.value[i]; + int* stored_volume = is_capture ? &chip->analog_capture_volume[i] : + &chip->analog_playback_volume[i]; + if (*stored_volume != new_volume) { + *stored_volume = new_volume; + changed = 1; + pcxhr_update_analog_audio_level(chip, is_capture, i); + } + } + up(&chip->mgr->mixer_mutex); + return changed; +} + +static struct snd_kcontrol_new pcxhr_control_analog_level = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + /* name will be filled later */ + .info = pcxhr_analog_vol_info, + .get = pcxhr_analog_vol_get, + .put = pcxhr_analog_vol_put, +}; + +/* shared */ +static int pcxhr_sw_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; + uinfo->count = 2; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 1; + return 0; +} + +static int pcxhr_audio_sw_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pcxhr *chip = snd_kcontrol_chip(kcontrol); + + down(&chip->mgr->mixer_mutex); + ucontrol->value.integer.value[0] = chip->analog_playback_active[0]; + ucontrol->value.integer.value[1] = chip->analog_playback_active[1]; + up(&chip->mgr->mixer_mutex); + return 0; +} + +static int pcxhr_audio_sw_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pcxhr *chip = snd_kcontrol_chip(kcontrol); + int i, changed = 0; + down(&chip->mgr->mixer_mutex); + for(i = 0; i < 2; i++) { + if (chip->analog_playback_active[i] != ucontrol->value.integer.value[i]) { + chip->analog_playback_active[i] = ucontrol->value.integer.value[i]; + changed = 1; + pcxhr_update_analog_audio_level(chip, 0, i); /* update playback levels */ + } + } + up(&chip->mgr->mixer_mutex); + return changed; +} + +static struct snd_kcontrol_new pcxhr_control_output_switch = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Master Playback Switch", + .info = pcxhr_sw_info, /* shared */ + .get = pcxhr_audio_sw_get, + .put = pcxhr_audio_sw_put +}; + + +#define PCXHR_DIGITAL_LEVEL_MIN 0x000 /* -110 dB */ +#define PCXHR_DIGITAL_LEVEL_MAX 0x1ff /* +18 dB */ +#define PCXHR_DIGITAL_ZERO_LEVEL 0x1b7 /* 0 dB */ + + +#define MORE_THAN_ONE_STREAM_LEVEL 0x000001 +#define VALID_STREAM_PAN_LEVEL_MASK 0x800000 +#define VALID_STREAM_LEVEL_MASK 0x400000 +#define VALID_STREAM_LEVEL_1_MASK 0x200000 +#define VALID_STREAM_LEVEL_2_MASK 0x100000 + +static int pcxhr_update_playback_stream_level(struct snd_pcxhr* chip, int idx) +{ + int err; + struct pcxhr_rmh rmh; + struct pcxhr_pipe *pipe = &chip->playback_pipe; + int left, right; + + if (chip->digital_playback_active[idx][0]) + left = chip->digital_playback_volume[idx][0]; + else + left = PCXHR_DIGITAL_LEVEL_MIN; + if (chip->digital_playback_active[idx][1]) + right = chip->digital_playback_volume[idx][1]; + else + right = PCXHR_DIGITAL_LEVEL_MIN; + + pcxhr_init_rmh(&rmh, CMD_STREAM_OUT_LEVEL_ADJUST); + /* add pipe and stream mask */ + pcxhr_set_pipe_cmd_params(&rmh, 0, pipe->first_audio, 0, 1<left / right->right panoramic level */ + rmh.cmd[0] |= MORE_THAN_ONE_STREAM_LEVEL; + rmh.cmd[2] = VALID_STREAM_PAN_LEVEL_MASK | VALID_STREAM_LEVEL_1_MASK; + rmh.cmd[2] |= (left << 10); + rmh.cmd[3] = VALID_STREAM_PAN_LEVEL_MASK | VALID_STREAM_LEVEL_2_MASK; + rmh.cmd[3] |= right; + rmh.cmd_len = 4; + + err = pcxhr_send_msg(chip->mgr, &rmh); + if (err < 0) { + snd_printk(KERN_DEBUG "error update_playback_stream_level " + "card(%d) err(%x)\n", chip->chip_idx, err); + return -EINVAL; + } + return 0; +} + +#define AUDIO_IO_HAS_MUTE_LEVEL 0x400000 +#define AUDIO_IO_HAS_MUTE_MONITOR_1 0x200000 +#define VALID_AUDIO_IO_DIGITAL_LEVEL 0x000001 +#define VALID_AUDIO_IO_MONITOR_LEVEL 0x000002 +#define VALID_AUDIO_IO_MUTE_LEVEL 0x000004 +#define VALID_AUDIO_IO_MUTE_MONITOR_1 0x000008 + +static int pcxhr_update_audio_pipe_level(struct snd_pcxhr* chip, int capture, int channel) +{ + int err; + struct pcxhr_rmh rmh; + struct pcxhr_pipe *pipe; + + if (capture) + pipe = &chip->capture_pipe[0]; + else + pipe = &chip->playback_pipe; + + pcxhr_init_rmh(&rmh, CMD_AUDIO_LEVEL_ADJUST); + /* add channel mask */ + pcxhr_set_pipe_cmd_params(&rmh, capture, 0, 0, 1 << (channel + pipe->first_audio)); + /* TODO : if mask (3 << pipe->first_audio) is used, left and right channel + * will be programmed to the same params + */ + if (capture) { + rmh.cmd[0] |= VALID_AUDIO_IO_DIGITAL_LEVEL; + /* VALID_AUDIO_IO_MUTE_LEVEL not yet handled (capture pipe level) */ + rmh.cmd[2] = chip->digital_capture_volume[channel]; + } else { + rmh.cmd[0] |= VALID_AUDIO_IO_MONITOR_LEVEL | VALID_AUDIO_IO_MUTE_MONITOR_1; + /* VALID_AUDIO_IO_DIGITAL_LEVEL and VALID_AUDIO_IO_MUTE_LEVEL not yet + * handled (playback pipe level) + */ + rmh.cmd[2] = chip->monitoring_volume[channel] << 10; + if (chip->monitoring_active[channel] == 0) + rmh.cmd[2] |= AUDIO_IO_HAS_MUTE_MONITOR_1; + } + rmh.cmd_len = 3; + + err = pcxhr_send_msg(chip->mgr, &rmh); + if(err<0) { + snd_printk(KERN_DEBUG "error update_audio_level card(%d) err(%x)\n", + chip->chip_idx, err); + return -EINVAL; + } + return 0; +} + + +/* shared */ +static int pcxhr_digital_vol_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 2; + uinfo->value.integer.min = PCXHR_DIGITAL_LEVEL_MIN; /* -109.5 dB */ + uinfo->value.integer.max = PCXHR_DIGITAL_LEVEL_MAX; /* 18.0 dB */ + return 0; +} + + +static int pcxhr_pcm_vol_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pcxhr *chip = snd_kcontrol_chip(kcontrol); + int idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id); /* index */ + int *stored_volume; + int is_capture = kcontrol->private_value; + + down(&chip->mgr->mixer_mutex); + if (is_capture) + stored_volume = chip->digital_capture_volume; /* digital capture */ + else + stored_volume = chip->digital_playback_volume[idx]; /* digital playback */ + ucontrol->value.integer.value[0] = stored_volume[0]; + ucontrol->value.integer.value[1] = stored_volume[1]; + up(&chip->mgr->mixer_mutex); + return 0; +} + +static int pcxhr_pcm_vol_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pcxhr *chip = snd_kcontrol_chip(kcontrol); + int idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id); /* index */ + int changed = 0; + int is_capture = kcontrol->private_value; + int *stored_volume; + int i; + + down(&chip->mgr->mixer_mutex); + if (is_capture) + stored_volume = chip->digital_capture_volume; /* digital capture */ + else + stored_volume = chip->digital_playback_volume[idx]; /* digital playback */ + for (i = 0; i < 2; i++) { + if (stored_volume[i] != ucontrol->value.integer.value[i]) { + stored_volume[i] = ucontrol->value.integer.value[i]; + changed = 1; + if (is_capture) /* update capture volume */ + pcxhr_update_audio_pipe_level(chip, 1, i); + } + } + if (! is_capture && changed) + pcxhr_update_playback_stream_level(chip, idx); /* update playback volume */ + up(&chip->mgr->mixer_mutex); + return changed; +} + +static struct snd_kcontrol_new snd_pcxhr_pcm_vol = +{ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + /* name will be filled later */ + /* count will be filled later */ + .info = pcxhr_digital_vol_info, /* shared */ + .get = pcxhr_pcm_vol_get, + .put = pcxhr_pcm_vol_put, +}; + + +static int pcxhr_pcm_sw_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pcxhr *chip = snd_kcontrol_chip(kcontrol); + int idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id); /* index */ + + down(&chip->mgr->mixer_mutex); + ucontrol->value.integer.value[0] = chip->digital_playback_active[idx][0]; + ucontrol->value.integer.value[1] = chip->digital_playback_active[idx][1]; + up(&chip->mgr->mixer_mutex); + return 0; +} + +static int pcxhr_pcm_sw_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pcxhr *chip = snd_kcontrol_chip(kcontrol); + int changed = 0; + int idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id); /* index */ + int i, j; + + down(&chip->mgr->mixer_mutex); + j = idx; + for (i = 0; i < 2; i++) { + if (chip->digital_playback_active[j][i] != ucontrol->value.integer.value[i]) { + chip->digital_playback_active[j][i] = ucontrol->value.integer.value[i]; + changed = 1; + } + } + if (changed) + pcxhr_update_playback_stream_level(chip, idx); + up(&chip->mgr->mixer_mutex); + return changed; +} + +static struct snd_kcontrol_new pcxhr_control_pcm_switch = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "PCM Playback Switch", + .count = PCXHR_PLAYBACK_STREAMS, + .info = pcxhr_sw_info, /* shared */ + .get = pcxhr_pcm_sw_get, + .put = pcxhr_pcm_sw_put +}; + + +/* + * monitoring level control + */ + +static int pcxhr_monitor_vol_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pcxhr *chip = snd_kcontrol_chip(kcontrol); + down(&chip->mgr->mixer_mutex); + ucontrol->value.integer.value[0] = chip->monitoring_volume[0]; + ucontrol->value.integer.value[1] = chip->monitoring_volume[1]; + up(&chip->mgr->mixer_mutex); + return 0; +} + +static int pcxhr_monitor_vol_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pcxhr *chip = snd_kcontrol_chip(kcontrol); + int changed = 0; + int i; + + down(&chip->mgr->mixer_mutex); + for (i = 0; i < 2; i++) { + if (chip->monitoring_volume[i] != ucontrol->value.integer.value[i]) { + chip->monitoring_volume[i] = ucontrol->value.integer.value[i]; + if(chip->monitoring_active[i]) /* do only when monitoring is unmuted */ + /* update monitoring volume and mute */ + pcxhr_update_audio_pipe_level(chip, 0, i); + changed = 1; + } + } + up(&chip->mgr->mixer_mutex); + return changed; +} + +static struct snd_kcontrol_new pcxhr_control_monitor_vol = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Monitoring Volume", + .info = pcxhr_digital_vol_info, /* shared */ + .get = pcxhr_monitor_vol_get, + .put = pcxhr_monitor_vol_put, +}; + +/* + * monitoring switch control + */ + +static int pcxhr_monitor_sw_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pcxhr *chip = snd_kcontrol_chip(kcontrol); + down(&chip->mgr->mixer_mutex); + ucontrol->value.integer.value[0] = chip->monitoring_active[0]; + ucontrol->value.integer.value[1] = chip->monitoring_active[1]; + up(&chip->mgr->mixer_mutex); + return 0; +} + +static int pcxhr_monitor_sw_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pcxhr *chip = snd_kcontrol_chip(kcontrol); + int changed = 0; + int i; + + down(&chip->mgr->mixer_mutex); + for (i = 0; i < 2; i++) { + if (chip->monitoring_active[i] != ucontrol->value.integer.value[i]) { + chip->monitoring_active[i] = ucontrol->value.integer.value[i]; + changed |= (1<mgr->mixer_mutex); + return (changed != 0); +} + +static struct snd_kcontrol_new pcxhr_control_monitor_sw = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Monitoring Switch", + .info = pcxhr_sw_info, /* shared */ + .get = pcxhr_monitor_sw_get, + .put = pcxhr_monitor_sw_put +}; + + + +/* + * audio source select + */ +#define PCXHR_SOURCE_AUDIO01_UER 0x000100 +#define PCXHR_SOURCE_AUDIO01_SYNC 0x000200 +#define PCXHR_SOURCE_AUDIO23_UER 0x000400 +#define PCXHR_SOURCE_AUDIO45_UER 0x001000 +#define PCXHR_SOURCE_AUDIO67_UER 0x040000 + +static int pcxhr_set_audio_source(struct snd_pcxhr* chip) +{ + struct pcxhr_rmh rmh; + unsigned int mask, reg; + unsigned int codec; + int err, use_src, changed; + + switch (chip->chip_idx) { + case 0 : mask = PCXHR_SOURCE_AUDIO01_UER; codec = CS8420_01_CS; break; + case 1 : mask = PCXHR_SOURCE_AUDIO23_UER; codec = CS8420_23_CS; break; + case 2 : mask = PCXHR_SOURCE_AUDIO45_UER; codec = CS8420_45_CS; break; + case 3 : mask = PCXHR_SOURCE_AUDIO67_UER; codec = CS8420_67_CS; break; + default: return -EINVAL; + } + reg = 0; /* audio source from analog plug */ + use_src = 0; /* do not activate codec SRC */ + + if (chip->audio_capture_source != 0) { + reg = mask; /* audio source from digital plug */ + if (chip->audio_capture_source == 2) + use_src = 1; + } + /* set the input source */ + pcxhr_write_io_num_reg_cont(chip->mgr, mask, reg, &changed); + /* resync them (otherwise channel inversion possible) */ + if (changed) { + pcxhr_init_rmh(&rmh, CMD_RESYNC_AUDIO_INPUTS); + rmh.cmd[0] |= (1 << chip->chip_idx); + err = pcxhr_send_msg(chip->mgr, &rmh); + if (err) + return err; + } + pcxhr_init_rmh(&rmh, CMD_ACCESS_IO_WRITE); /* set codec SRC on off */ + rmh.cmd_len = 3; + rmh.cmd[0] |= IO_NUM_UER_CHIP_REG; + rmh.cmd[1] = codec; + rmh.cmd[2] = (CS8420_DATA_FLOW_CTL & CHIP_SIG_AND_MAP_SPI) | (use_src ? 0x41 : 0x54); + err = pcxhr_send_msg(chip->mgr, &rmh); + if(err) + return err; + rmh.cmd[2] = (CS8420_CLOCK_SRC_CTL & CHIP_SIG_AND_MAP_SPI) | (use_src ? 0x41 : 0x49); + err = pcxhr_send_msg(chip->mgr, &rmh); + return err; +} + +static int pcxhr_audio_src_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + static char *texts[3] = {"Analog", "Digital", "Digi+SRC"}; + + uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; + uinfo->count = 1; + uinfo->value.enumerated.items = 3; + if (uinfo->value.enumerated.item > 2) + uinfo->value.enumerated.item = 2; + strcpy(uinfo->value.enumerated.name, + texts[uinfo->value.enumerated.item]); + return 0; +} + +static int pcxhr_audio_src_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pcxhr *chip = snd_kcontrol_chip(kcontrol); + ucontrol->value.enumerated.item[0] = chip->audio_capture_source; + return 0; +} + +static int pcxhr_audio_src_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pcxhr *chip = snd_kcontrol_chip(kcontrol); + int ret = 0; + + down(&chip->mgr->mixer_mutex); + if (chip->audio_capture_source != ucontrol->value.enumerated.item[0]) { + chip->audio_capture_source = ucontrol->value.enumerated.item[0]; + pcxhr_set_audio_source(chip); + ret = 1; + } + up(&chip->mgr->mixer_mutex); + return ret; +} + +static struct snd_kcontrol_new pcxhr_control_audio_src = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Capture Source", + .info = pcxhr_audio_src_info, + .get = pcxhr_audio_src_get, + .put = pcxhr_audio_src_put, +}; + + +/* + * clock type selection + * enum pcxhr_clock_type { + * PCXHR_CLOCK_TYPE_INTERNAL = 0, + * PCXHR_CLOCK_TYPE_WORD_CLOCK, + * PCXHR_CLOCK_TYPE_AES_SYNC, + * PCXHR_CLOCK_TYPE_AES_1, + * PCXHR_CLOCK_TYPE_AES_2, + * PCXHR_CLOCK_TYPE_AES_3, + * PCXHR_CLOCK_TYPE_AES_4, + * }; + */ + +static int pcxhr_clock_type_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + static char *texts[7] = { + "Internal", "WordClock", "AES Sync", "AES 1", "AES 2", "AES 3", "AES 4" + }; + struct pcxhr_mgr *mgr = snd_kcontrol_chip(kcontrol); + int clock_items = 3 + mgr->capture_chips; + + uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; + uinfo->count = 1; + uinfo->value.enumerated.items = clock_items; + if (uinfo->value.enumerated.item >= clock_items) + uinfo->value.enumerated.item = clock_items-1; + strcpy(uinfo->value.enumerated.name, + texts[uinfo->value.enumerated.item]); + return 0; +} + +static int pcxhr_clock_type_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct pcxhr_mgr *mgr = snd_kcontrol_chip(kcontrol); + ucontrol->value.enumerated.item[0] = mgr->use_clock_type; + return 0; +} + +static int pcxhr_clock_type_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct pcxhr_mgr *mgr = snd_kcontrol_chip(kcontrol); + int rate, ret = 0; + + down(&mgr->mixer_mutex); + if (mgr->use_clock_type != ucontrol->value.enumerated.item[0]) { + down(&mgr->setup_mutex); + mgr->use_clock_type = ucontrol->value.enumerated.item[0]; + if (mgr->use_clock_type) + pcxhr_get_external_clock(mgr, mgr->use_clock_type, &rate); + else + rate = mgr->sample_rate; + if (rate) { + pcxhr_set_clock(mgr, rate); + if (mgr->sample_rate) + mgr->sample_rate = rate; + } + up(&mgr->setup_mutex); + ret = 1; /* return 1 even if the set was not done. ok ? */ + } + up(&mgr->mixer_mutex); + return ret; +} + +static struct snd_kcontrol_new pcxhr_control_clock_type = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Clock Mode", + .info = pcxhr_clock_type_info, + .get = pcxhr_clock_type_get, + .put = pcxhr_clock_type_put, +}; + +/* + * clock rate control + * specific control that scans the sample rates on the external plugs + */ +static int pcxhr_clock_rate_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct pcxhr_mgr *mgr = snd_kcontrol_chip(kcontrol); + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 3 + mgr->capture_chips; + uinfo->value.integer.min = 0; /* clock not present */ + uinfo->value.integer.max = 192000; /* max sample rate 192 kHz */ + return 0; +} + +static int pcxhr_clock_rate_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct pcxhr_mgr *mgr = snd_kcontrol_chip(kcontrol); + int i, err, rate; + + down(&mgr->mixer_mutex); + for(i = 0; i < 3 + mgr->capture_chips; i++) { + if (i == PCXHR_CLOCK_TYPE_INTERNAL) + rate = mgr->sample_rate_real; + else { + err = pcxhr_get_external_clock(mgr, i, &rate); + if (err) + break; + } + ucontrol->value.integer.value[i] = rate; + } + up(&mgr->mixer_mutex); + return 0; +} + +static struct snd_kcontrol_new pcxhr_control_clock_rate = { + .access = SNDRV_CTL_ELEM_ACCESS_READ, + .iface = SNDRV_CTL_ELEM_IFACE_CARD, + .name = "Clock Rates", + .info = pcxhr_clock_rate_info, + .get = pcxhr_clock_rate_get, +}; + +/* + * IEC958 status bits + */ +static int pcxhr_iec958_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958; + uinfo->count = 1; + return 0; +} + +static int pcxhr_iec958_capture_byte(struct snd_pcxhr *chip, int aes_idx, unsigned char* aes_bits) +{ + int i, err; + unsigned char temp; + struct pcxhr_rmh rmh; + + pcxhr_init_rmh(&rmh, CMD_ACCESS_IO_READ); + rmh.cmd[0] |= IO_NUM_UER_CHIP_REG; + switch (chip->chip_idx) { + case 0: rmh.cmd[1] = CS8420_01_CS; break; /* use CS8416_01_CS for AES SYNC plug */ + case 1: rmh.cmd[1] = CS8420_23_CS; break; + case 2: rmh.cmd[1] = CS8420_45_CS; break; + case 3: rmh.cmd[1] = CS8420_67_CS; break; + default: return -EINVAL; + } + switch (aes_idx) { + case 0: rmh.cmd[2] = CS8420_CSB0; break; /* use CS8416_CSBx for AES SYNC plug */ + case 1: rmh.cmd[2] = CS8420_CSB1; break; + case 2: rmh.cmd[2] = CS8420_CSB2; break; + case 3: rmh.cmd[2] = CS8420_CSB3; break; + case 4: rmh.cmd[2] = CS8420_CSB4; break; + default: return -EINVAL; + } + rmh.cmd[1] &= 0x0fffff; /* size and code the chip id for the fpga */ + rmh.cmd[2] &= CHIP_SIG_AND_MAP_SPI; /* chip signature + map for spi read */ + rmh.cmd_len = 3; + err = pcxhr_send_msg(chip->mgr, &rmh); + if (err) + return err; + temp = 0; + for (i = 0; i < 8; i++) { + /* attention : reversed bit order (not with CS8416_01_CS) */ + temp <<= 1; + if (rmh.stat[1] & (1 << i)) + temp |= 1; + } + snd_printdd("read iec958 AES %d byte %d = 0x%x\n", chip->chip_idx, aes_idx, temp); + *aes_bits = temp; + return 0; +} + +static int pcxhr_iec958_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pcxhr *chip = snd_kcontrol_chip(kcontrol); + unsigned char aes_bits; + int i, err; + + down(&chip->mgr->mixer_mutex); + for(i = 0; i < 5; i++) { + if (kcontrol->private_value == 0) /* playback */ + aes_bits = chip->aes_bits[i]; + else { /* capture */ + err = pcxhr_iec958_capture_byte(chip, i, &aes_bits); + if (err) + break; + } + ucontrol->value.iec958.status[i] = aes_bits; + } + up(&chip->mgr->mixer_mutex); + return 0; +} + +static int pcxhr_iec958_mask_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int i; + for (i = 0; i < 5; i++) + ucontrol->value.iec958.status[i] = 0xff; + return 0; +} + +static int pcxhr_iec958_update_byte(struct snd_pcxhr *chip, int aes_idx, unsigned char aes_bits) +{ + int i, err, cmd; + unsigned char new_bits = aes_bits; + unsigned char old_bits = chip->aes_bits[aes_idx]; + struct pcxhr_rmh rmh; + + for (i = 0; i < 8; i++) { + if ((old_bits & 0x01) != (new_bits & 0x01)) { + cmd = chip->chip_idx & 0x03; /* chip index 0..3 */ + if(chip->chip_idx > 3) + /* new bit used if chip_idx>3 (PCX1222HR) */ + cmd |= 1 << 22; + cmd |= ((aes_idx << 3) + i) << 2; /* add bit offset */ + cmd |= (new_bits & 0x01) << 23; /* add bit value */ + pcxhr_init_rmh(&rmh, CMD_ACCESS_IO_WRITE); + rmh.cmd[0] |= IO_NUM_REG_CUER; + rmh.cmd[1] = cmd; + rmh.cmd_len = 2; + snd_printdd("write iec958 AES %d byte %d bit %d (cmd %x)\n", + chip->chip_idx, aes_idx, i, cmd); + err = pcxhr_send_msg(chip->mgr, &rmh); + if (err) + return err; + } + old_bits >>= 1; + new_bits >>= 1; + } + chip->aes_bits[aes_idx] = aes_bits; + return 0; +} + +static int pcxhr_iec958_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pcxhr *chip = snd_kcontrol_chip(kcontrol); + int i, changed = 0; + + /* playback */ + down(&chip->mgr->mixer_mutex); + for (i = 0; i < 5; i++) { + if (ucontrol->value.iec958.status[i] != chip->aes_bits[i]) { + pcxhr_iec958_update_byte(chip, i, ucontrol->value.iec958.status[i]); + changed = 1; + } + } + up(&chip->mgr->mixer_mutex); + return changed; +} + +static struct snd_kcontrol_new pcxhr_control_playback_iec958_mask = { + .access = SNDRV_CTL_ELEM_ACCESS_READ, + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,MASK), + .info = pcxhr_iec958_info, + .get = pcxhr_iec958_mask_get +}; +static struct snd_kcontrol_new pcxhr_control_playback_iec958 = { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,DEFAULT), + .info = pcxhr_iec958_info, + .get = pcxhr_iec958_get, + .put = pcxhr_iec958_put, + .private_value = 0 /* playback */ +}; + +static struct snd_kcontrol_new pcxhr_control_capture_iec958_mask = { + .access = SNDRV_CTL_ELEM_ACCESS_READ, + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = SNDRV_CTL_NAME_IEC958("",CAPTURE,MASK), + .info = pcxhr_iec958_info, + .get = pcxhr_iec958_mask_get +}; +static struct snd_kcontrol_new pcxhr_control_capture_iec958 = { + .access = SNDRV_CTL_ELEM_ACCESS_READ, + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = SNDRV_CTL_NAME_IEC958("",CAPTURE,DEFAULT), + .info = pcxhr_iec958_info, + .get = pcxhr_iec958_get, + .private_value = 1 /* capture */ +}; + +static void pcxhr_init_audio_levels(struct snd_pcxhr *chip) +{ + int i; + + for (i = 0; i < 2; i++) { + if (chip->nb_streams_play) { + int j; + /* at boot time the digital volumes are unmuted 0dB */ + for (j = 0; j < PCXHR_PLAYBACK_STREAMS; j++) { + chip->digital_playback_active[j][i] = 1; + chip->digital_playback_volume[j][i] = PCXHR_DIGITAL_ZERO_LEVEL; + } + /* after boot, only two bits are set on the uer interface */ + chip->aes_bits[0] = IEC958_AES0_PROFESSIONAL | IEC958_AES0_PRO_FS_48000; +/* only for test purpose, remove later */ +#ifdef CONFIG_SND_DEBUG + /* analog volumes for playback (is LEVEL_MIN after boot) */ + chip->analog_playback_active[i] = 1; + chip->analog_playback_volume[i] = PCXHR_ANALOG_PLAYBACK_ZERO_LEVEL; + pcxhr_update_analog_audio_level(chip, 0, i); +#endif +/* test end */ + } + if (chip->nb_streams_capt) { + /* at boot time the digital volumes are unmuted 0dB */ + chip->digital_capture_volume[i] = PCXHR_DIGITAL_ZERO_LEVEL; +/* only for test purpose, remove later */ +#ifdef CONFIG_SND_DEBUG + /* analog volumes for playback (is LEVEL_MIN after boot) */ + chip->analog_capture_volume[i] = PCXHR_ANALOG_CAPTURE_ZERO_LEVEL; + pcxhr_update_analog_audio_level(chip, 1, i); +#endif +/* test end */ + } + } + + return; +} + + +int pcxhr_create_mixer(struct pcxhr_mgr *mgr) +{ + struct snd_pcxhr *chip; + int err, i; + + init_MUTEX(&mgr->mixer_mutex); /* can be in another place */ + + for (i = 0; i < mgr->num_cards; i++) { + struct snd_kcontrol_new temp; + chip = mgr->chip[i]; + + if (chip->nb_streams_play) { + /* analog output level control */ + temp = pcxhr_control_analog_level; + temp.name = "Master Playback Volume"; + temp.private_value = 0; /* playback */ + if ((err = snd_ctl_add(chip->card, snd_ctl_new1(&temp, chip))) < 0) + return err; + /* output mute controls */ + if ((err = snd_ctl_add(chip->card, + snd_ctl_new1(&pcxhr_control_output_switch, + chip))) < 0) + return err; + + temp = snd_pcxhr_pcm_vol; + temp.name = "PCM Playback Volume"; + temp.count = PCXHR_PLAYBACK_STREAMS; + temp.private_value = 0; /* playback */ + if ((err = snd_ctl_add(chip->card, snd_ctl_new1(&temp, chip))) < 0) + return err; + + if ((err = snd_ctl_add(chip->card, + snd_ctl_new1(&pcxhr_control_pcm_switch, + chip))) < 0) + return err; + + /* IEC958 controls */ + if ((err = snd_ctl_add(chip->card, + snd_ctl_new1(&pcxhr_control_playback_iec958_mask, + chip))) < 0) + return err; + if ((err = snd_ctl_add(chip->card, + snd_ctl_new1(&pcxhr_control_playback_iec958, + chip))) < 0) + return err; + } + if (chip->nb_streams_capt) { + /* analog input level control only on first two chips !*/ + temp = pcxhr_control_analog_level; + temp.name = "Master Capture Volume"; + temp.private_value = 1; /* capture */ + if ((err = snd_ctl_add(chip->card, snd_ctl_new1(&temp, chip))) < 0) + return err; + + temp = snd_pcxhr_pcm_vol; + temp.name = "PCM Capture Volume"; + temp.count = 1; + temp.private_value = 1; /* capture */ + if ((err = snd_ctl_add(chip->card, snd_ctl_new1(&temp, chip))) < 0) + return err; + /* Audio source */ + if ((err = snd_ctl_add(chip->card, + snd_ctl_new1(&pcxhr_control_audio_src, + chip))) < 0) + return err; + /* IEC958 controls */ + if ((err = snd_ctl_add(chip->card, + snd_ctl_new1(&pcxhr_control_capture_iec958_mask, + chip))) < 0) + return err; + if ((err = snd_ctl_add(chip->card, + snd_ctl_new1(&pcxhr_control_capture_iec958, + chip))) < 0) + return err; + } + /* monitoring only if playback and capture device available */ + if (chip->nb_streams_capt > 0 && chip->nb_streams_play > 0) { + /* monitoring */ + if ((err = snd_ctl_add(chip->card, + snd_ctl_new1(&pcxhr_control_monitor_vol, + chip))) < 0) + return err; + if ((err = snd_ctl_add(chip->card, + snd_ctl_new1(&pcxhr_control_monitor_sw, + chip))) < 0) + return err; + } + + if (i == 0) { + /* clock mode only one control per pcxhr */ + if ((err = snd_ctl_add(chip->card, + snd_ctl_new1(&pcxhr_control_clock_type, + mgr))) < 0) + return err; + /* non standard control used to scan the external clock presence/frequencies */ + if ((err = snd_ctl_add(chip->card, + snd_ctl_new1(&pcxhr_control_clock_rate, + mgr))) < 0) + return err; + } + + /* init values for the mixer data */ + pcxhr_init_audio_levels(chip); + } + + return 0; +} diff --git a/sound/pci/pcxhr/pcxhr_mixer.h b/sound/pci/pcxhr/pcxhr_mixer.h new file mode 100644 index 000000000000..4348d0e55ba3 --- /dev/null +++ b/sound/pci/pcxhr/pcxhr_mixer.h @@ -0,0 +1,29 @@ +/* + * Driver for Digigram pcxhr compatible soundcards + * + * include file for mixer + * + * Copyright (c) 2004 by Digigram + * + * 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 + */ + +#ifndef __SOUND_PCXHR_MIXER_H +#define __SOUND_PCXHR_MIXER_H + +/* exported */ +int pcxhr_create_mixer(struct pcxhr_mgr *mgr); + +#endif /* __SOUND_PCXHR_MIXER_H */ -- cgit v1.2.3-59-g8ed1b