| /* |
| * Copyright (c) by James Courtier-Dutton <James@superbug.demon.co.uk> |
| * Driver p16v chips |
| * Version: 0.25 |
| * |
| * FEATURES currently supported: |
| * Output fixed at S32_LE, 2 channel to hw:0,0 |
| * Rates: 44.1, 48, 96, 192. |
| * |
| * Changelog: |
| * 0.8 |
| * Use separate card based buffer for periods table. |
| * 0.9 |
| * Use 2 channel output streams instead of 8 channel. |
| * (8 channel output streams might be good for ASIO type output) |
| * Corrected speaker output, so Front -> Front etc. |
| * 0.10 |
| * Fixed missed interrupts. |
| * 0.11 |
| * Add Sound card model number and names. |
| * Add Analog volume controls. |
| * 0.12 |
| * Corrected playback interrupts. Now interrupt per period, instead of half period. |
| * 0.13 |
| * Use single trigger for multichannel. |
| * 0.14 |
| * Mic capture now works at fixed: S32_LE, 96000Hz, Stereo. |
| * 0.15 |
| * Force buffer_size / period_size == INTEGER. |
| * 0.16 |
| * Update p16v.c to work with changed alsa api. |
| * 0.17 |
| * Update p16v.c to work with changed alsa api. Removed boot_devs. |
| * 0.18 |
| * Merging with snd-emu10k1 driver. |
| * 0.19 |
| * One stereo channel at 24bit now works. |
| * 0.20 |
| * Added better register defines. |
| * 0.21 |
| * Integrated with snd-emu10k1 driver. |
| * 0.22 |
| * Removed #if 0 ... #endif |
| * 0.23 |
| * Implement different capture rates. |
| * 0.24 |
| * Implement different capture source channels. |
| * e.g. When HD Capture source is set to SPDIF, |
| * setting HD Capture channel to 0 captures from CDROM digital input. |
| * setting HD Capture channel to 1 captures from SPDIF in. |
| * 0.25 |
| * Include capture buffer sizes. |
| * |
| * BUGS: |
| * Some stability problems when unloading the snd-p16v kernel module. |
| * -- |
| * |
| * TODO: |
| * SPDIF out. |
| * Find out how to change capture sample rates. E.g. To record SPDIF at 48000Hz. |
| * Currently capture fixed at 48000Hz. |
| * |
| * -- |
| * GENERAL INFO: |
| * Model: SB0240 |
| * P16V Chip: CA0151-DBS |
| * Audigy 2 Chip: CA0102-IAT |
| * AC97 Codec: STAC 9721 |
| * ADC: Philips 1361T (Stereo 24bit) |
| * DAC: CS4382-K (8-channel, 24bit, 192Khz) |
| * |
| * This code was initally based on code from ALSA's emu10k1x.c which is: |
| * Copyright (c) by Francisco Moraes <fmoraes@nc.rr.com> |
| * |
| * 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 <sound/driver.h> |
| #include <linux/delay.h> |
| #include <linux/init.h> |
| #include <linux/interrupt.h> |
| #include <linux/pci.h> |
| #include <linux/slab.h> |
| #include <linux/moduleparam.h> |
| #include <sound/core.h> |
| #include <sound/initval.h> |
| #include <sound/pcm.h> |
| #include <sound/ac97_codec.h> |
| #include <sound/info.h> |
| #include <sound/emu10k1.h> |
| #include "p16v.h" |
| |
| #define SET_CHANNEL 0 /* Testing channel outputs 0=Front, 1=Center/LFE, 2=Unknown, 3=Rear */ |
| #define PCM_FRONT_CHANNEL 0 |
| #define PCM_REAR_CHANNEL 1 |
| #define PCM_CENTER_LFE_CHANNEL 2 |
| #define PCM_UNKNOWN_CHANNEL 3 |
| #define CONTROL_FRONT_CHANNEL 0 |
| #define CONTROL_REAR_CHANNEL 3 |
| #define CONTROL_CENTER_LFE_CHANNEL 1 |
| #define CONTROL_UNKNOWN_CHANNEL 2 |
| |
| /* Card IDs: |
| * Class 0401: 1102:0004 (rev 04) Subsystem: 1102:2002 -> Audigy2 ZS 7.1 Model:SB0350 |
| * Class 0401: 1102:0004 (rev 04) Subsystem: 1102:1007 -> Audigy2 6.1 Model:SB0240 |
| * Class 0401: 1102:0004 (rev 04) Subsystem: 1102:1002 -> Audigy2 Platinum Model:SB msb0240230009266 |
| * Class 0401: 1102:0004 (rev 04) Subsystem: 1102:2007 -> Audigy4 Pro Model:SB0380 M1SB0380472001901E |
| * |
| */ |
| |
| /* hardware definition */ |
| static snd_pcm_hardware_t snd_p16v_playback_hw = { |
| .info = (SNDRV_PCM_INFO_MMAP | |
| SNDRV_PCM_INFO_INTERLEAVED | |
| SNDRV_PCM_INFO_BLOCK_TRANSFER | |
| SNDRV_PCM_INFO_MMAP_VALID), |
| .formats = SNDRV_PCM_FMTBIT_S32_LE, /* Only supports 24-bit samples padded to 32 bits. */ |
| .rates = SNDRV_PCM_RATE_192000 | SNDRV_PCM_RATE_96000 | SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_44100, |
| .rate_min = 44100, |
| .rate_max = 192000, |
| .channels_min = 8, |
| .channels_max = 8, |
| .buffer_bytes_max = ((65536 - 64) * 8), |
| .period_bytes_min = 64, |
| .period_bytes_max = (65536 - 64), |
| .periods_min = 2, |
| .periods_max = 8, |
| .fifo_size = 0, |
| }; |
| |
| static snd_pcm_hardware_t snd_p16v_capture_hw = { |
| .info = (SNDRV_PCM_INFO_MMAP | |
| SNDRV_PCM_INFO_INTERLEAVED | |
| SNDRV_PCM_INFO_BLOCK_TRANSFER | |
| SNDRV_PCM_INFO_MMAP_VALID), |
| .formats = SNDRV_PCM_FMTBIT_S32_LE, |
| .rates = SNDRV_PCM_RATE_192000 | SNDRV_PCM_RATE_96000 | SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_44100, |
| .rate_min = 44100, |
| .rate_max = 192000, |
| .channels_min = 2, |
| .channels_max = 2, |
| .buffer_bytes_max = (65536 - 64), |
| .period_bytes_min = 64, |
| .period_bytes_max = (65536 - 128) >> 1, /* size has to be N*64 bytes */ |
| .periods_min = 2, |
| .periods_max = 2, |
| .fifo_size = 0, |
| }; |
| |
| static void snd_p16v_pcm_free_substream(snd_pcm_runtime_t *runtime) |
| { |
| emu10k1_pcm_t *epcm = runtime->private_data; |
| |
| if (epcm) { |
| //snd_printk("epcm free: %p\n", epcm); |
| kfree(epcm); |
| } |
| } |
| |
| /* open_playback callback */ |
| static int snd_p16v_pcm_open_playback_channel(snd_pcm_substream_t *substream, int channel_id) |
| { |
| emu10k1_t *emu = snd_pcm_substream_chip(substream); |
| emu10k1_voice_t *channel = &(emu->p16v_voices[channel_id]); |
| emu10k1_pcm_t *epcm; |
| snd_pcm_runtime_t *runtime = substream->runtime; |
| int err; |
| |
| epcm = kcalloc(1, sizeof(*epcm), GFP_KERNEL); |
| //snd_printk("epcm kcalloc: %p\n", epcm); |
| |
| if (epcm == NULL) |
| return -ENOMEM; |
| epcm->emu = emu; |
| epcm->substream = substream; |
| //snd_printk("epcm device=%d, channel_id=%d\n", substream->pcm->device, channel_id); |
| |
| runtime->private_data = epcm; |
| runtime->private_free = snd_p16v_pcm_free_substream; |
| |
| runtime->hw = snd_p16v_playback_hw; |
| |
| channel->emu = emu; |
| channel->number = channel_id; |
| |
| channel->use=1; |
| //snd_printk("p16v: open channel_id=%d, channel=%p, use=0x%x\n", channel_id, channel, channel->use); |
| //printk("open:channel_id=%d, chip=%p, channel=%p\n",channel_id, chip, channel); |
| //channel->interrupt = snd_p16v_pcm_channel_interrupt; |
| channel->epcm=epcm; |
| if ((err = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS)) < 0) |
| return err; |
| |
| return 0; |
| } |
| /* open_capture callback */ |
| static int snd_p16v_pcm_open_capture_channel(snd_pcm_substream_t *substream, int channel_id) |
| { |
| emu10k1_t *emu = snd_pcm_substream_chip(substream); |
| emu10k1_voice_t *channel = &(emu->p16v_capture_voice); |
| emu10k1_pcm_t *epcm; |
| snd_pcm_runtime_t *runtime = substream->runtime; |
| int err; |
| |
| epcm = kcalloc(1, sizeof(*epcm), GFP_KERNEL); |
| //snd_printk("epcm kcalloc: %p\n", epcm); |
| |
| if (epcm == NULL) |
| return -ENOMEM; |
| epcm->emu = emu; |
| epcm->substream = substream; |
| //snd_printk("epcm device=%d, channel_id=%d\n", substream->pcm->device, channel_id); |
| |
| runtime->private_data = epcm; |
| runtime->private_free = snd_p16v_pcm_free_substream; |
| |
| runtime->hw = snd_p16v_capture_hw; |
| |
| channel->emu = emu; |
| channel->number = channel_id; |
| |
| channel->use=1; |
| //snd_printk("p16v: open channel_id=%d, channel=%p, use=0x%x\n", channel_id, channel, channel->use); |
| //printk("open:channel_id=%d, chip=%p, channel=%p\n",channel_id, chip, channel); |
| //channel->interrupt = snd_p16v_pcm_channel_interrupt; |
| channel->epcm=epcm; |
| if ((err = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS)) < 0) |
| return err; |
| |
| return 0; |
| } |
| |
| |
| /* close callback */ |
| static int snd_p16v_pcm_close_playback(snd_pcm_substream_t *substream) |
| { |
| emu10k1_t *emu = snd_pcm_substream_chip(substream); |
| //snd_pcm_runtime_t *runtime = substream->runtime; |
| //emu10k1_pcm_t *epcm = runtime->private_data; |
| emu->p16v_voices[substream->pcm->device - emu->p16v_device_offset].use=0; |
| /* FIXME: maybe zero others */ |
| return 0; |
| } |
| |
| /* close callback */ |
| static int snd_p16v_pcm_close_capture(snd_pcm_substream_t *substream) |
| { |
| emu10k1_t *emu = snd_pcm_substream_chip(substream); |
| //snd_pcm_runtime_t *runtime = substream->runtime; |
| //emu10k1_pcm_t *epcm = runtime->private_data; |
| emu->p16v_capture_voice.use=0; |
| /* FIXME: maybe zero others */ |
| return 0; |
| } |
| |
| static int snd_p16v_pcm_open_playback_front(snd_pcm_substream_t *substream) |
| { |
| return snd_p16v_pcm_open_playback_channel(substream, PCM_FRONT_CHANNEL); |
| } |
| |
| static int snd_p16v_pcm_open_capture(snd_pcm_substream_t *substream) |
| { |
| // Only using channel 0 for now, but the card has 2 channels. |
| return snd_p16v_pcm_open_capture_channel(substream, 0); |
| } |
| |
| /* hw_params callback */ |
| static int snd_p16v_pcm_hw_params_playback(snd_pcm_substream_t *substream, |
| snd_pcm_hw_params_t * hw_params) |
| { |
| int result; |
| result = snd_pcm_lib_malloc_pages(substream, |
| params_buffer_bytes(hw_params)); |
| return result; |
| } |
| |
| /* hw_params callback */ |
| static int snd_p16v_pcm_hw_params_capture(snd_pcm_substream_t *substream, |
| snd_pcm_hw_params_t * hw_params) |
| { |
| int result; |
| result = snd_pcm_lib_malloc_pages(substream, |
| params_buffer_bytes(hw_params)); |
| return result; |
| } |
| |
| |
| /* hw_free callback */ |
| static int snd_p16v_pcm_hw_free_playback(snd_pcm_substream_t *substream) |
| { |
| int result; |
| result = snd_pcm_lib_free_pages(substream); |
| return result; |
| } |
| |
| /* hw_free callback */ |
| static int snd_p16v_pcm_hw_free_capture(snd_pcm_substream_t *substream) |
| { |
| int result; |
| result = snd_pcm_lib_free_pages(substream); |
| return result; |
| } |
| |
| |
| /* prepare playback callback */ |
| static int snd_p16v_pcm_prepare_playback(snd_pcm_substream_t *substream) |
| { |
| emu10k1_t *emu = snd_pcm_substream_chip(substream); |
| snd_pcm_runtime_t *runtime = substream->runtime; |
| int channel = substream->pcm->device - emu->p16v_device_offset; |
| u32 *table_base = (u32 *)(emu->p16v_buffer.area+(8*16*channel)); |
| u32 period_size_bytes = frames_to_bytes(runtime, runtime->period_size); |
| int i; |
| u32 tmp; |
| |
| //snd_printk("prepare:channel_number=%d, rate=%d, format=0x%x, channels=%d, buffer_size=%ld, period_size=%ld, periods=%u, frames_to_bytes=%d\n",channel, runtime->rate, runtime->format, runtime->channels, runtime->buffer_size, runtime->period_size, runtime->periods, frames_to_bytes(runtime, 1)); |
| //snd_printk("dma_addr=%x, dma_area=%p, table_base=%p\n",runtime->dma_addr, runtime->dma_area, table_base); |
| //snd_printk("dma_addr=%x, dma_area=%p, dma_bytes(size)=%x\n",emu->p16v_buffer.addr, emu->p16v_buffer.area, emu->p16v_buffer.bytes); |
| tmp = snd_emu10k1_ptr_read(emu, A_SPDIF_SAMPLERATE, channel); |
| switch (runtime->rate) { |
| case 44100: |
| snd_emu10k1_ptr_write(emu, A_SPDIF_SAMPLERATE, channel, (tmp & ~0xe0e0) | 0x8080); |
| break; |
| case 96000: |
| snd_emu10k1_ptr_write(emu, A_SPDIF_SAMPLERATE, channel, (tmp & ~0xe0e0) | 0x4040); |
| break; |
| case 192000: |
| snd_emu10k1_ptr_write(emu, A_SPDIF_SAMPLERATE, channel, (tmp & ~0xe0e0) | 0x2020); |
| break; |
| case 48000: |
| default: |
| snd_emu10k1_ptr_write(emu, A_SPDIF_SAMPLERATE, channel, (tmp & ~0xe0e0) | 0x0000); |
| break; |
| } |
| /* FIXME: Check emu->buffer.size before actually writing to it. */ |
| for(i=0; i < runtime->periods; i++) { |
| table_base[i*2]=runtime->dma_addr+(i*period_size_bytes); |
| table_base[(i*2)+1]=period_size_bytes<<16; |
| } |
| |
| snd_emu10k1_ptr20_write(emu, PLAYBACK_LIST_ADDR, channel, emu->p16v_buffer.addr+(8*16*channel)); |
| snd_emu10k1_ptr20_write(emu, PLAYBACK_LIST_SIZE, channel, (runtime->periods - 1) << 19); |
| snd_emu10k1_ptr20_write(emu, PLAYBACK_LIST_PTR, channel, 0); |
| snd_emu10k1_ptr20_write(emu, PLAYBACK_DMA_ADDR, channel, runtime->dma_addr); |
| //snd_emu10k1_ptr20_write(emu, PLAYBACK_PERIOD_SIZE, channel, frames_to_bytes(runtime, runtime->period_size)<<16); // buffer size in bytes |
| snd_emu10k1_ptr20_write(emu, PLAYBACK_PERIOD_SIZE, channel, 0); // buffer size in bytes |
| snd_emu10k1_ptr20_write(emu, PLAYBACK_POINTER, channel, 0); |
| snd_emu10k1_ptr20_write(emu, 0x07, channel, 0x0); |
| snd_emu10k1_ptr20_write(emu, 0x08, channel, 0); |
| |
| return 0; |
| } |
| |
| /* prepare capture callback */ |
| static int snd_p16v_pcm_prepare_capture(snd_pcm_substream_t *substream) |
| { |
| emu10k1_t *emu = snd_pcm_substream_chip(substream); |
| snd_pcm_runtime_t *runtime = substream->runtime; |
| int channel = substream->pcm->device - emu->p16v_device_offset; |
| u32 tmp; |
| //printk("prepare capture:channel_number=%d, rate=%d, format=0x%x, channels=%d, buffer_size=%ld, period_size=%ld, frames_to_bytes=%d\n",channel, runtime->rate, runtime->format, runtime->channels, runtime->buffer_size, runtime->period_size, frames_to_bytes(runtime, 1)); |
| tmp = snd_emu10k1_ptr_read(emu, A_SPDIF_SAMPLERATE, channel); |
| switch (runtime->rate) { |
| case 44100: |
| snd_emu10k1_ptr_write(emu, A_SPDIF_SAMPLERATE, channel, (tmp & ~0x0e00) | 0x0800); |
| break; |
| case 96000: |
| snd_emu10k1_ptr_write(emu, A_SPDIF_SAMPLERATE, channel, (tmp & ~0x0e00) | 0x0400); |
| break; |
| case 192000: |
| snd_emu10k1_ptr_write(emu, A_SPDIF_SAMPLERATE, channel, (tmp & ~0x0e00) | 0x0200); |
| break; |
| case 48000: |
| default: |
| snd_emu10k1_ptr_write(emu, A_SPDIF_SAMPLERATE, channel, (tmp & ~0x0e00) | 0x0000); |
| break; |
| } |
| /* FIXME: Check emu->buffer.size before actually writing to it. */ |
| snd_emu10k1_ptr20_write(emu, 0x13, channel, 0); |
| snd_emu10k1_ptr20_write(emu, CAPTURE_DMA_ADDR, channel, runtime->dma_addr); |
| snd_emu10k1_ptr20_write(emu, CAPTURE_BUFFER_SIZE, channel, frames_to_bytes(runtime, runtime->buffer_size)<<16); // buffer size in bytes |
| snd_emu10k1_ptr20_write(emu, CAPTURE_POINTER, channel, 0); |
| //snd_emu10k1_ptr20_write(emu, CAPTURE_SOURCE, 0x0, 0x333300e4); /* Select MIC or Line in */ |
| //snd_emu10k1_ptr20_write(emu, EXTENDED_INT_MASK, 0, snd_emu10k1_ptr20_read(emu, EXTENDED_INT_MASK, 0) | (0x110000<<channel)); |
| |
| return 0; |
| } |
| |
| static void snd_p16v_intr_enable(emu10k1_t *emu, unsigned int intrenb) |
| { |
| unsigned long flags; |
| unsigned int enable; |
| |
| spin_lock_irqsave(&emu->emu_lock, flags); |
| enable = inl(emu->port + INTE2) | intrenb; |
| outl(enable, emu->port + INTE2); |
| spin_unlock_irqrestore(&emu->emu_lock, flags); |
| } |
| |
| static void snd_p16v_intr_disable(emu10k1_t *emu, unsigned int intrenb) |
| { |
| unsigned long flags; |
| unsigned int disable; |
| |
| spin_lock_irqsave(&emu->emu_lock, flags); |
| disable = inl(emu->port + INTE2) & (~intrenb); |
| outl(disable, emu->port + INTE2); |
| spin_unlock_irqrestore(&emu->emu_lock, flags); |
| } |
| |
| /* trigger_playback callback */ |
| static int snd_p16v_pcm_trigger_playback(snd_pcm_substream_t *substream, |
| int cmd) |
| { |
| emu10k1_t *emu = snd_pcm_substream_chip(substream); |
| snd_pcm_runtime_t *runtime; |
| emu10k1_pcm_t *epcm; |
| int channel; |
| int result = 0; |
| struct list_head *pos; |
| snd_pcm_substream_t *s; |
| u32 basic = 0; |
| u32 inte = 0; |
| int running=0; |
| |
| switch (cmd) { |
| case SNDRV_PCM_TRIGGER_START: |
| running=1; |
| break; |
| case SNDRV_PCM_TRIGGER_STOP: |
| default: |
| running=0; |
| break; |
| } |
| snd_pcm_group_for_each(pos, substream) { |
| s = snd_pcm_group_substream_entry(pos); |
| runtime = s->runtime; |
| epcm = runtime->private_data; |
| channel = substream->pcm->device-emu->p16v_device_offset; |
| //snd_printk("p16v channel=%d\n",channel); |
| epcm->running = running; |
| basic |= (0x1<<channel); |
| inte |= (INTE2_PLAYBACK_CH_0_LOOP<<channel); |
| snd_pcm_trigger_done(s, substream); |
| } |
| //snd_printk("basic=0x%x, inte=0x%x\n",basic, inte); |
| |
| switch (cmd) { |
| case SNDRV_PCM_TRIGGER_START: |
| snd_p16v_intr_enable(emu, inte); |
| snd_emu10k1_ptr20_write(emu, BASIC_INTERRUPT, 0, snd_emu10k1_ptr20_read(emu, BASIC_INTERRUPT, 0)| (basic)); |
| break; |
| case SNDRV_PCM_TRIGGER_STOP: |
| snd_emu10k1_ptr20_write(emu, BASIC_INTERRUPT, 0, snd_emu10k1_ptr20_read(emu, BASIC_INTERRUPT, 0) & ~(basic)); |
| snd_p16v_intr_disable(emu, inte); |
| break; |
| default: |
| result = -EINVAL; |
| break; |
| } |
| return result; |
| } |
| |
| /* trigger_capture callback */ |
| static int snd_p16v_pcm_trigger_capture(snd_pcm_substream_t *substream, |
| int cmd) |
| { |
| emu10k1_t *emu = snd_pcm_substream_chip(substream); |
| snd_pcm_runtime_t *runtime = substream->runtime; |
| emu10k1_pcm_t *epcm = runtime->private_data; |
| int channel = 0; |
| int result = 0; |
| u32 inte = INTE2_CAPTURE_CH_0_LOOP | INTE2_CAPTURE_CH_0_HALF_LOOP; |
| |
| switch (cmd) { |
| case SNDRV_PCM_TRIGGER_START: |
| snd_p16v_intr_enable(emu, inte); |
| snd_emu10k1_ptr20_write(emu, BASIC_INTERRUPT, 0, snd_emu10k1_ptr20_read(emu, BASIC_INTERRUPT, 0)|(0x100<<channel)); |
| epcm->running = 1; |
| break; |
| case SNDRV_PCM_TRIGGER_STOP: |
| snd_emu10k1_ptr20_write(emu, BASIC_INTERRUPT, 0, snd_emu10k1_ptr20_read(emu, BASIC_INTERRUPT, 0) & ~(0x100<<channel)); |
| snd_p16v_intr_disable(emu, inte); |
| //snd_emu10k1_ptr20_write(emu, EXTENDED_INT_MASK, 0, snd_emu10k1_ptr20_read(emu, EXTENDED_INT_MASK, 0) & ~(0x110000<<channel)); |
| epcm->running = 0; |
| break; |
| default: |
| result = -EINVAL; |
| break; |
| } |
| return result; |
| } |
| |
| /* pointer_playback callback */ |
| static snd_pcm_uframes_t |
| snd_p16v_pcm_pointer_playback(snd_pcm_substream_t *substream) |
| { |
| emu10k1_t *emu = snd_pcm_substream_chip(substream); |
| snd_pcm_runtime_t *runtime = substream->runtime; |
| emu10k1_pcm_t *epcm = runtime->private_data; |
| snd_pcm_uframes_t ptr, ptr1, ptr2,ptr3,ptr4 = 0; |
| int channel = substream->pcm->device - emu->p16v_device_offset; |
| if (!epcm->running) |
| return 0; |
| |
| ptr3 = snd_emu10k1_ptr20_read(emu, PLAYBACK_LIST_PTR, channel); |
| ptr1 = snd_emu10k1_ptr20_read(emu, PLAYBACK_POINTER, channel); |
| ptr4 = snd_emu10k1_ptr20_read(emu, PLAYBACK_LIST_PTR, channel); |
| if (ptr3 != ptr4) ptr1 = snd_emu10k1_ptr20_read(emu, PLAYBACK_POINTER, channel); |
| ptr2 = bytes_to_frames(runtime, ptr1); |
| ptr2+= (ptr4 >> 3) * runtime->period_size; |
| ptr=ptr2; |
| if (ptr >= runtime->buffer_size) |
| ptr -= runtime->buffer_size; |
| |
| return ptr; |
| } |
| |
| /* pointer_capture callback */ |
| static snd_pcm_uframes_t |
| snd_p16v_pcm_pointer_capture(snd_pcm_substream_t *substream) |
| { |
| emu10k1_t *emu = snd_pcm_substream_chip(substream); |
| snd_pcm_runtime_t *runtime = substream->runtime; |
| emu10k1_pcm_t *epcm = runtime->private_data; |
| snd_pcm_uframes_t ptr, ptr1, ptr2 = 0; |
| int channel = 0; |
| |
| if (!epcm->running) |
| return 0; |
| |
| ptr1 = snd_emu10k1_ptr20_read(emu, CAPTURE_POINTER, channel); |
| ptr2 = bytes_to_frames(runtime, ptr1); |
| ptr=ptr2; |
| if (ptr >= runtime->buffer_size) { |
| ptr -= runtime->buffer_size; |
| printk("buffer capture limited!\n"); |
| } |
| //printk("ptr1 = 0x%lx, ptr2=0x%lx, ptr=0x%lx, buffer_size = 0x%x, period_size = 0x%x, bits=%d, rate=%d\n", ptr1, ptr2, ptr, (int)runtime->buffer_size, (int)runtime->period_size, (int)runtime->frame_bits, (int)runtime->rate); |
| |
| return ptr; |
| } |
| |
| /* operators */ |
| static snd_pcm_ops_t snd_p16v_playback_front_ops = { |
| .open = snd_p16v_pcm_open_playback_front, |
| .close = snd_p16v_pcm_close_playback, |
| .ioctl = snd_pcm_lib_ioctl, |
| .hw_params = snd_p16v_pcm_hw_params_playback, |
| .hw_free = snd_p16v_pcm_hw_free_playback, |
| .prepare = snd_p16v_pcm_prepare_playback, |
| .trigger = snd_p16v_pcm_trigger_playback, |
| .pointer = snd_p16v_pcm_pointer_playback, |
| }; |
| |
| static snd_pcm_ops_t snd_p16v_capture_ops = { |
| .open = snd_p16v_pcm_open_capture, |
| .close = snd_p16v_pcm_close_capture, |
| .ioctl = snd_pcm_lib_ioctl, |
| .hw_params = snd_p16v_pcm_hw_params_capture, |
| .hw_free = snd_p16v_pcm_hw_free_capture, |
| .prepare = snd_p16v_pcm_prepare_capture, |
| .trigger = snd_p16v_pcm_trigger_capture, |
| .pointer = snd_p16v_pcm_pointer_capture, |
| }; |
| |
| |
| int snd_p16v_free(emu10k1_t *chip) |
| { |
| // release the data |
| if (chip->p16v_buffer.area) { |
| snd_dma_free_pages(&chip->p16v_buffer); |
| //snd_printk("period lables free: %p\n", &chip->p16v_buffer); |
| } |
| return 0; |
| } |
| |
| static void snd_p16v_pcm_free(snd_pcm_t *pcm) |
| { |
| emu10k1_t *emu = pcm->private_data; |
| //snd_printk("snd_p16v_pcm_free pcm: called\n"); |
| snd_pcm_lib_preallocate_free_for_all(pcm); |
| emu->pcm = NULL; |
| } |
| |
| int snd_p16v_pcm(emu10k1_t *emu, int device, snd_pcm_t **rpcm) |
| { |
| snd_pcm_t *pcm; |
| snd_pcm_substream_t *substream; |
| int err; |
| int capture=1; |
| |
| //snd_printk("snd_p16v_pcm called. device=%d\n", device); |
| emu->p16v_device_offset = device; |
| if (rpcm) |
| *rpcm = NULL; |
| |
| if ((err = snd_pcm_new(emu->card, "p16v", device, 1, capture, &pcm)) < 0) |
| return err; |
| |
| pcm->private_data = emu; |
| pcm->private_free = snd_p16v_pcm_free; |
| // Single playback 8 channel device. |
| // Single capture 2 channel device. |
| snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_p16v_playback_front_ops); |
| snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_p16v_capture_ops); |
| |
| pcm->info_flags = 0; |
| pcm->dev_subclass = SNDRV_PCM_SUBCLASS_GENERIC_MIX; |
| strcpy(pcm->name, "p16v"); |
| emu->pcm = pcm; |
| |
| for(substream = pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream; |
| substream; |
| substream = substream->next) { |
| if ((err = snd_pcm_lib_preallocate_pages(substream, |
| SNDRV_DMA_TYPE_DEV, |
| snd_dma_pci_data(emu->pci), |
| ((65536 - 64) * 8), ((65536 - 64) * 8))) < 0) |
| return err; |
| //snd_printk("preallocate playback substream: err=%d\n", err); |
| } |
| |
| for (substream = pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream; |
| substream; |
| substream = substream->next) { |
| if ((err = snd_pcm_lib_preallocate_pages(substream, |
| SNDRV_DMA_TYPE_DEV, |
| snd_dma_pci_data(emu->pci), |
| 65536 - 64, 65536 - 64)) < 0) |
| return err; |
| //snd_printk("preallocate capture substream: err=%d\n", err); |
| } |
| |
| if (rpcm) |
| *rpcm = pcm; |
| |
| return 0; |
| } |
| |
| static int snd_p16v_volume_info(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t * uinfo) |
| { |
| uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; |
| uinfo->count = 2; |
| uinfo->value.integer.min = 0; |
| uinfo->value.integer.max = 255; |
| return 0; |
| } |
| |
| static int snd_p16v_volume_get(snd_kcontrol_t * kcontrol, |
| snd_ctl_elem_value_t * ucontrol, int reg, int high_low) |
| { |
| emu10k1_t *emu = snd_kcontrol_chip(kcontrol); |
| u32 value; |
| |
| value = snd_emu10k1_ptr20_read(emu, reg, high_low); |
| if (high_low == 1) { |
| ucontrol->value.integer.value[0] = 0xff - ((value >> 24) & 0xff); /* Left */ |
| ucontrol->value.integer.value[1] = 0xff - ((value >> 16) & 0xff); /* Right */ |
| } else { |
| ucontrol->value.integer.value[0] = 0xff - ((value >> 8) & 0xff); /* Left */ |
| ucontrol->value.integer.value[1] = 0xff - ((value >> 0) & 0xff); /* Right */ |
| } |
| return 0; |
| } |
| |
| static int snd_p16v_volume_get_spdif_front(snd_kcontrol_t * kcontrol, |
| snd_ctl_elem_value_t * ucontrol) |
| { |
| int high_low = 0; |
| int reg = PLAYBACK_VOLUME_MIXER7; |
| return snd_p16v_volume_get(kcontrol, ucontrol, reg, high_low); |
| } |
| |
| static int snd_p16v_volume_get_spdif_center_lfe(snd_kcontrol_t * kcontrol, |
| snd_ctl_elem_value_t * ucontrol) |
| { |
| int high_low = 1; |
| int reg = PLAYBACK_VOLUME_MIXER7; |
| return snd_p16v_volume_get(kcontrol, ucontrol, reg, high_low); |
| } |
| static int snd_p16v_volume_get_spdif_unknown(snd_kcontrol_t * kcontrol, |
| snd_ctl_elem_value_t * ucontrol) |
| { |
| int high_low = 0; |
| int reg = PLAYBACK_VOLUME_MIXER8; |
| return snd_p16v_volume_get(kcontrol, ucontrol, reg, high_low); |
| } |
| static int snd_p16v_volume_get_spdif_rear(snd_kcontrol_t * kcontrol, |
| snd_ctl_elem_value_t * ucontrol) |
| { |
| int high_low = 1; |
| int reg = PLAYBACK_VOLUME_MIXER8; |
| return snd_p16v_volume_get(kcontrol, ucontrol, reg, high_low); |
| } |
| |
| static int snd_p16v_volume_get_analog_front(snd_kcontrol_t * kcontrol, |
| snd_ctl_elem_value_t * ucontrol) |
| { |
| int high_low = 0; |
| int reg = PLAYBACK_VOLUME_MIXER9; |
| return snd_p16v_volume_get(kcontrol, ucontrol, reg, high_low); |
| } |
| |
| static int snd_p16v_volume_get_analog_center_lfe(snd_kcontrol_t * kcontrol, |
| snd_ctl_elem_value_t * ucontrol) |
| { |
| int high_low = 1; |
| int reg = PLAYBACK_VOLUME_MIXER9; |
| return snd_p16v_volume_get(kcontrol, ucontrol, reg, high_low); |
| } |
| static int snd_p16v_volume_get_analog_rear(snd_kcontrol_t * kcontrol, |
| snd_ctl_elem_value_t * ucontrol) |
| { |
| int high_low = 1; |
| int reg = PLAYBACK_VOLUME_MIXER10; |
| return snd_p16v_volume_get(kcontrol, ucontrol, reg, high_low); |
| } |
| |
| static int snd_p16v_volume_get_analog_unknown(snd_kcontrol_t * kcontrol, |
| snd_ctl_elem_value_t * ucontrol) |
| { |
| int high_low = 0; |
| int reg = PLAYBACK_VOLUME_MIXER10; |
| return snd_p16v_volume_get(kcontrol, ucontrol, reg, high_low); |
| } |
| |
| static int snd_p16v_volume_put(snd_kcontrol_t * kcontrol, |
| snd_ctl_elem_value_t * ucontrol, int reg, int high_low) |
| { |
| emu10k1_t *emu = snd_kcontrol_chip(kcontrol); |
| u32 value; |
| value = snd_emu10k1_ptr20_read(emu, reg, 0); |
| //value = value & 0xffff; |
| if (high_low == 1) { |
| value &= 0xffff; |
| value = value | ((0xff - ucontrol->value.integer.value[0]) << 24) | ((0xff - ucontrol->value.integer.value[1]) << 16); |
| } else { |
| value &= 0xffff0000; |
| value = value | ((0xff - ucontrol->value.integer.value[0]) << 8) | ((0xff - ucontrol->value.integer.value[1]) ); |
| } |
| snd_emu10k1_ptr20_write(emu, reg, 0, value); |
| return 1; |
| } |
| |
| static int snd_p16v_volume_put_spdif_front(snd_kcontrol_t * kcontrol, |
| snd_ctl_elem_value_t * ucontrol) |
| { |
| int high_low = 0; |
| int reg = PLAYBACK_VOLUME_MIXER7; |
| return snd_p16v_volume_put(kcontrol, ucontrol, reg, high_low); |
| } |
| |
| static int snd_p16v_volume_put_spdif_center_lfe(snd_kcontrol_t * kcontrol, |
| snd_ctl_elem_value_t * ucontrol) |
| { |
| int high_low = 1; |
| int reg = PLAYBACK_VOLUME_MIXER7; |
| return snd_p16v_volume_put(kcontrol, ucontrol, reg, high_low); |
| } |
| |
| static int snd_p16v_volume_put_spdif_unknown(snd_kcontrol_t * kcontrol, |
| snd_ctl_elem_value_t * ucontrol) |
| { |
| int high_low = 0; |
| int reg = PLAYBACK_VOLUME_MIXER8; |
| return snd_p16v_volume_put(kcontrol, ucontrol, reg, high_low); |
| } |
| |
| static int snd_p16v_volume_put_spdif_rear(snd_kcontrol_t * kcontrol, |
| snd_ctl_elem_value_t * ucontrol) |
| { |
| int high_low = 1; |
| int reg = PLAYBACK_VOLUME_MIXER8; |
| return snd_p16v_volume_put(kcontrol, ucontrol, reg, high_low); |
| } |
| |
| static int snd_p16v_volume_put_analog_front(snd_kcontrol_t * kcontrol, |
| snd_ctl_elem_value_t * ucontrol) |
| { |
| int high_low = 0; |
| int reg = PLAYBACK_VOLUME_MIXER9; |
| return snd_p16v_volume_put(kcontrol, ucontrol, reg, high_low); |
| } |
| |
| static int snd_p16v_volume_put_analog_center_lfe(snd_kcontrol_t * kcontrol, |
| snd_ctl_elem_value_t * ucontrol) |
| { |
| int high_low = 1; |
| int reg = PLAYBACK_VOLUME_MIXER9; |
| return snd_p16v_volume_put(kcontrol, ucontrol, reg, high_low); |
| } |
| |
| static int snd_p16v_volume_put_analog_rear(snd_kcontrol_t * kcontrol, |
| snd_ctl_elem_value_t * ucontrol) |
| { |
| int high_low = 1; |
| int reg = PLAYBACK_VOLUME_MIXER10; |
| return snd_p16v_volume_put(kcontrol, ucontrol, reg, high_low); |
| } |
| |
| static int snd_p16v_volume_put_analog_unknown(snd_kcontrol_t * kcontrol, |
| snd_ctl_elem_value_t * ucontrol) |
| { |
| int high_low = 0; |
| int reg = PLAYBACK_VOLUME_MIXER10; |
| return snd_p16v_volume_put(kcontrol, ucontrol, reg, high_low); |
| } |
| |
| static snd_kcontrol_new_t snd_p16v_volume_control_analog_front = |
| { |
| .iface = SNDRV_CTL_ELEM_IFACE_MIXER, |
| .name = "HD Analog Front Volume", |
| .info = snd_p16v_volume_info, |
| .get = snd_p16v_volume_get_analog_front, |
| .put = snd_p16v_volume_put_analog_front |
| }; |
| |
| static snd_kcontrol_new_t snd_p16v_volume_control_analog_center_lfe = |
| { |
| .iface = SNDRV_CTL_ELEM_IFACE_MIXER, |
| .name = "HD Analog Center/LFE Volume", |
| .info = snd_p16v_volume_info, |
| .get = snd_p16v_volume_get_analog_center_lfe, |
| .put = snd_p16v_volume_put_analog_center_lfe |
| }; |
| |
| static snd_kcontrol_new_t snd_p16v_volume_control_analog_unknown = |
| { |
| .iface = SNDRV_CTL_ELEM_IFACE_MIXER, |
| .name = "HD Analog Unknown Volume", |
| .info = snd_p16v_volume_info, |
| .get = snd_p16v_volume_get_analog_unknown, |
| .put = snd_p16v_volume_put_analog_unknown |
| }; |
| |
| static snd_kcontrol_new_t snd_p16v_volume_control_analog_rear = |
| { |
| .iface = SNDRV_CTL_ELEM_IFACE_MIXER, |
| .name = "HD Analog Rear Volume", |
| .info = snd_p16v_volume_info, |
| .get = snd_p16v_volume_get_analog_rear, |
| .put = snd_p16v_volume_put_analog_rear |
| }; |
| |
| static snd_kcontrol_new_t snd_p16v_volume_control_spdif_front = |
| { |
| .iface = SNDRV_CTL_ELEM_IFACE_MIXER, |
| .name = "HD SPDIF Front Volume", |
| .info = snd_p16v_volume_info, |
| .get = snd_p16v_volume_get_spdif_front, |
| .put = snd_p16v_volume_put_spdif_front |
| }; |
| |
| static snd_kcontrol_new_t snd_p16v_volume_control_spdif_center_lfe = |
| { |
| .iface = SNDRV_CTL_ELEM_IFACE_MIXER, |
| .name = "HD SPDIF Center/LFE Volume", |
| .info = snd_p16v_volume_info, |
| .get = snd_p16v_volume_get_spdif_center_lfe, |
| .put = snd_p16v_volume_put_spdif_center_lfe |
| }; |
| |
| static snd_kcontrol_new_t snd_p16v_volume_control_spdif_unknown = |
| { |
| .iface = SNDRV_CTL_ELEM_IFACE_MIXER, |
| .name = "HD SPDIF Unknown Volume", |
| .info = snd_p16v_volume_info, |
| .get = snd_p16v_volume_get_spdif_unknown, |
| .put = snd_p16v_volume_put_spdif_unknown |
| }; |
| |
| static snd_kcontrol_new_t snd_p16v_volume_control_spdif_rear = |
| { |
| .iface = SNDRV_CTL_ELEM_IFACE_MIXER, |
| .name = "HD SPDIF Rear Volume", |
| .info = snd_p16v_volume_info, |
| .get = snd_p16v_volume_get_spdif_rear, |
| .put = snd_p16v_volume_put_spdif_rear |
| }; |
| |
| static int snd_p16v_capture_source_info(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t * uinfo) |
| { |
| static char *texts[8] = { "SPDIF", "I2S", "SRC48", "SRCMulti_SPDIF", "SRCMulti_I2S", "CDIF", "FX", "AC97" }; |
| |
| uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; |
| uinfo->count = 1; |
| uinfo->value.enumerated.items = 8; |
| if (uinfo->value.enumerated.item > 7) |
| uinfo->value.enumerated.item = 7; |
| strcpy(uinfo->value.enumerated.name, texts[uinfo->value.enumerated.item]); |
| return 0; |
| } |
| |
| static int snd_p16v_capture_source_get(snd_kcontrol_t * kcontrol, |
| snd_ctl_elem_value_t * ucontrol) |
| { |
| emu10k1_t *emu = snd_kcontrol_chip(kcontrol); |
| |
| ucontrol->value.enumerated.item[0] = emu->p16v_capture_source; |
| return 0; |
| } |
| |
| static int snd_p16v_capture_source_put(snd_kcontrol_t * kcontrol, |
| snd_ctl_elem_value_t * ucontrol) |
| { |
| emu10k1_t *emu = snd_kcontrol_chip(kcontrol); |
| unsigned int val; |
| int change = 0; |
| u32 mask; |
| u32 source; |
| |
| val = ucontrol->value.enumerated.item[0] ; |
| change = (emu->p16v_capture_source != val); |
| if (change) { |
| emu->p16v_capture_source = val; |
| source = (val << 28) | (val << 24) | (val << 20) | (val << 16); |
| mask = snd_emu10k1_ptr20_read(emu, BASIC_INTERRUPT, 0) & 0xffff; |
| snd_emu10k1_ptr20_write(emu, BASIC_INTERRUPT, 0, source | mask); |
| } |
| return change; |
| } |
| |
| static snd_kcontrol_new_t snd_p16v_capture_source __devinitdata = |
| { |
| .iface = SNDRV_CTL_ELEM_IFACE_MIXER, |
| .name = "HD Capture source", |
| .info = snd_p16v_capture_source_info, |
| .get = snd_p16v_capture_source_get, |
| .put = snd_p16v_capture_source_put |
| }; |
| |
| static int snd_p16v_capture_channel_info(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t * uinfo) |
| { |
| static char *texts[4] = { "0", "1", "2", "3", }; |
| |
| uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; |
| uinfo->count = 1; |
| uinfo->value.enumerated.items = 4; |
| if (uinfo->value.enumerated.item > 3) |
| uinfo->value.enumerated.item = 3; |
| strcpy(uinfo->value.enumerated.name, texts[uinfo->value.enumerated.item]); |
| return 0; |
| } |
| |
| static int snd_p16v_capture_channel_get(snd_kcontrol_t * kcontrol, |
| snd_ctl_elem_value_t * ucontrol) |
| { |
| emu10k1_t *emu = snd_kcontrol_chip(kcontrol); |
| |
| ucontrol->value.enumerated.item[0] = emu->p16v_capture_channel; |
| return 0; |
| } |
| |
| static int snd_p16v_capture_channel_put(snd_kcontrol_t * kcontrol, |
| snd_ctl_elem_value_t * ucontrol) |
| { |
| emu10k1_t *emu = snd_kcontrol_chip(kcontrol); |
| unsigned int val; |
| int change = 0; |
| u32 tmp; |
| |
| val = ucontrol->value.enumerated.item[0] ; |
| change = (emu->p16v_capture_channel != val); |
| if (change) { |
| emu->p16v_capture_channel = val; |
| tmp = snd_emu10k1_ptr20_read(emu, CAPTURE_P16V_SOURCE, 0) & 0xfffc; |
| snd_emu10k1_ptr20_write(emu, CAPTURE_P16V_SOURCE, 0, tmp | val); |
| } |
| return change; |
| } |
| |
| static snd_kcontrol_new_t snd_p16v_capture_channel __devinitdata = |
| { |
| .iface = SNDRV_CTL_ELEM_IFACE_MIXER, |
| .name = "HD Capture channel", |
| .info = snd_p16v_capture_channel_info, |
| .get = snd_p16v_capture_channel_get, |
| .put = snd_p16v_capture_channel_put |
| }; |
| |
| int snd_p16v_mixer(emu10k1_t *emu) |
| { |
| int err; |
| snd_kcontrol_t *kctl; |
| snd_card_t *card = emu->card; |
| if ((kctl = snd_ctl_new1(&snd_p16v_volume_control_analog_front, emu)) == NULL) |
| return -ENOMEM; |
| if ((err = snd_ctl_add(card, kctl))) |
| return err; |
| if ((kctl = snd_ctl_new1(&snd_p16v_volume_control_analog_rear, emu)) == NULL) |
| return -ENOMEM; |
| if ((err = snd_ctl_add(card, kctl))) |
| return err; |
| if ((kctl = snd_ctl_new1(&snd_p16v_volume_control_analog_center_lfe, emu)) == NULL) |
| return -ENOMEM; |
| if ((err = snd_ctl_add(card, kctl))) |
| return err; |
| if ((kctl = snd_ctl_new1(&snd_p16v_volume_control_analog_unknown, emu)) == NULL) |
| return -ENOMEM; |
| if ((err = snd_ctl_add(card, kctl))) |
| return err; |
| if ((kctl = snd_ctl_new1(&snd_p16v_volume_control_spdif_front, emu)) == NULL) |
| return -ENOMEM; |
| if ((err = snd_ctl_add(card, kctl))) |
| return err; |
| if ((kctl = snd_ctl_new1(&snd_p16v_volume_control_spdif_rear, emu)) == NULL) |
| return -ENOMEM; |
| if ((err = snd_ctl_add(card, kctl))) |
| return err; |
| if ((kctl = snd_ctl_new1(&snd_p16v_volume_control_spdif_center_lfe, emu)) == NULL) |
| return -ENOMEM; |
| if ((err = snd_ctl_add(card, kctl))) |
| return err; |
| if ((kctl = snd_ctl_new1(&snd_p16v_volume_control_spdif_unknown, emu)) == NULL) |
| return -ENOMEM; |
| if ((err = snd_ctl_add(card, kctl))) |
| return err; |
| if ((kctl = snd_ctl_new1(&snd_p16v_capture_source, emu)) == NULL) |
| return -ENOMEM; |
| if ((err = snd_ctl_add(card, kctl))) |
| return err; |
| if ((kctl = snd_ctl_new1(&snd_p16v_capture_channel, emu)) == NULL) |
| return -ENOMEM; |
| if ((err = snd_ctl_add(card, kctl))) |
| return err; |
| return 0; |
| } |
| |