79c50e23d4
Rename the first PCM device from "Analog" to "Multichannel" because it can be used for HDMI output on the Xonar HDAV. Signed-off-by: Clemens Ladisch <clemens@ladisch.de>
747 lines
21 KiB
C
747 lines
21 KiB
C
/*
|
|
* C-Media CMI8788 driver - PCM code
|
|
*
|
|
* Copyright (c) Clemens Ladisch <clemens@ladisch.de>
|
|
*
|
|
*
|
|
* This driver is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License, version 2.
|
|
*
|
|
* This driver 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 driver; if not, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*/
|
|
|
|
#include <linux/pci.h>
|
|
#include <sound/control.h>
|
|
#include <sound/core.h>
|
|
#include <sound/pcm.h>
|
|
#include <sound/pcm_params.h>
|
|
#include "oxygen.h"
|
|
|
|
/* most DMA channels have a 16-bit counter for 32-bit words */
|
|
#define BUFFER_BYTES_MAX ((1 << 16) * 4)
|
|
/* the multichannel DMA channel has a 24-bit counter */
|
|
#define BUFFER_BYTES_MAX_MULTICH ((1 << 24) * 4)
|
|
|
|
#define PERIOD_BYTES_MIN 64
|
|
|
|
#define DEFAULT_BUFFER_BYTES (BUFFER_BYTES_MAX / 2)
|
|
#define DEFAULT_BUFFER_BYTES_MULTICH (1024 * 1024)
|
|
|
|
static const struct snd_pcm_hardware oxygen_stereo_hardware = {
|
|
.info = SNDRV_PCM_INFO_MMAP |
|
|
SNDRV_PCM_INFO_MMAP_VALID |
|
|
SNDRV_PCM_INFO_INTERLEAVED |
|
|
SNDRV_PCM_INFO_PAUSE |
|
|
SNDRV_PCM_INFO_SYNC_START,
|
|
.formats = SNDRV_PCM_FMTBIT_S16_LE |
|
|
SNDRV_PCM_FMTBIT_S32_LE,
|
|
.rates = SNDRV_PCM_RATE_32000 |
|
|
SNDRV_PCM_RATE_44100 |
|
|
SNDRV_PCM_RATE_48000 |
|
|
SNDRV_PCM_RATE_64000 |
|
|
SNDRV_PCM_RATE_88200 |
|
|
SNDRV_PCM_RATE_96000 |
|
|
SNDRV_PCM_RATE_176400 |
|
|
SNDRV_PCM_RATE_192000,
|
|
.rate_min = 32000,
|
|
.rate_max = 192000,
|
|
.channels_min = 2,
|
|
.channels_max = 2,
|
|
.buffer_bytes_max = BUFFER_BYTES_MAX,
|
|
.period_bytes_min = PERIOD_BYTES_MIN,
|
|
.period_bytes_max = BUFFER_BYTES_MAX / 2,
|
|
.periods_min = 2,
|
|
.periods_max = BUFFER_BYTES_MAX / PERIOD_BYTES_MIN,
|
|
};
|
|
static const struct snd_pcm_hardware oxygen_multichannel_hardware = {
|
|
.info = SNDRV_PCM_INFO_MMAP |
|
|
SNDRV_PCM_INFO_MMAP_VALID |
|
|
SNDRV_PCM_INFO_INTERLEAVED |
|
|
SNDRV_PCM_INFO_PAUSE |
|
|
SNDRV_PCM_INFO_SYNC_START,
|
|
.formats = SNDRV_PCM_FMTBIT_S16_LE |
|
|
SNDRV_PCM_FMTBIT_S32_LE,
|
|
.rates = SNDRV_PCM_RATE_32000 |
|
|
SNDRV_PCM_RATE_44100 |
|
|
SNDRV_PCM_RATE_48000 |
|
|
SNDRV_PCM_RATE_64000 |
|
|
SNDRV_PCM_RATE_88200 |
|
|
SNDRV_PCM_RATE_96000 |
|
|
SNDRV_PCM_RATE_176400 |
|
|
SNDRV_PCM_RATE_192000,
|
|
.rate_min = 32000,
|
|
.rate_max = 192000,
|
|
.channels_min = 2,
|
|
.channels_max = 8,
|
|
.buffer_bytes_max = BUFFER_BYTES_MAX_MULTICH,
|
|
.period_bytes_min = PERIOD_BYTES_MIN,
|
|
.period_bytes_max = BUFFER_BYTES_MAX_MULTICH / 2,
|
|
.periods_min = 2,
|
|
.periods_max = BUFFER_BYTES_MAX_MULTICH / PERIOD_BYTES_MIN,
|
|
};
|
|
static const struct snd_pcm_hardware oxygen_ac97_hardware = {
|
|
.info = SNDRV_PCM_INFO_MMAP |
|
|
SNDRV_PCM_INFO_MMAP_VALID |
|
|
SNDRV_PCM_INFO_INTERLEAVED |
|
|
SNDRV_PCM_INFO_PAUSE |
|
|
SNDRV_PCM_INFO_SYNC_START,
|
|
.formats = SNDRV_PCM_FMTBIT_S16_LE,
|
|
.rates = SNDRV_PCM_RATE_48000,
|
|
.rate_min = 48000,
|
|
.rate_max = 48000,
|
|
.channels_min = 2,
|
|
.channels_max = 2,
|
|
.buffer_bytes_max = BUFFER_BYTES_MAX,
|
|
.period_bytes_min = PERIOD_BYTES_MIN,
|
|
.period_bytes_max = BUFFER_BYTES_MAX / 2,
|
|
.periods_min = 2,
|
|
.periods_max = BUFFER_BYTES_MAX / PERIOD_BYTES_MIN,
|
|
};
|
|
|
|
static const struct snd_pcm_hardware *const oxygen_hardware[PCM_COUNT] = {
|
|
[PCM_A] = &oxygen_stereo_hardware,
|
|
[PCM_B] = &oxygen_stereo_hardware,
|
|
[PCM_C] = &oxygen_stereo_hardware,
|
|
[PCM_SPDIF] = &oxygen_stereo_hardware,
|
|
[PCM_MULTICH] = &oxygen_multichannel_hardware,
|
|
[PCM_AC97] = &oxygen_ac97_hardware,
|
|
};
|
|
|
|
static inline unsigned int
|
|
oxygen_substream_channel(struct snd_pcm_substream *substream)
|
|
{
|
|
return (unsigned int)(uintptr_t)substream->runtime->private_data;
|
|
}
|
|
|
|
static int oxygen_open(struct snd_pcm_substream *substream,
|
|
unsigned int channel)
|
|
{
|
|
struct oxygen *chip = snd_pcm_substream_chip(substream);
|
|
struct snd_pcm_runtime *runtime = substream->runtime;
|
|
int err;
|
|
|
|
runtime->private_data = (void *)(uintptr_t)channel;
|
|
if (channel == PCM_B && chip->has_ac97_1 &&
|
|
(chip->model.device_config & CAPTURE_2_FROM_AC97_1))
|
|
runtime->hw = oxygen_ac97_hardware;
|
|
else
|
|
runtime->hw = *oxygen_hardware[channel];
|
|
switch (channel) {
|
|
case PCM_C:
|
|
runtime->hw.rates &= ~(SNDRV_PCM_RATE_32000 |
|
|
SNDRV_PCM_RATE_64000);
|
|
runtime->hw.rate_min = 44100;
|
|
break;
|
|
case PCM_MULTICH:
|
|
runtime->hw.channels_max = chip->model.dac_channels;
|
|
break;
|
|
}
|
|
if (chip->model.pcm_hardware_filter)
|
|
chip->model.pcm_hardware_filter(channel, &runtime->hw);
|
|
err = snd_pcm_hw_constraint_step(runtime, 0,
|
|
SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 32);
|
|
if (err < 0)
|
|
return err;
|
|
err = snd_pcm_hw_constraint_step(runtime, 0,
|
|
SNDRV_PCM_HW_PARAM_BUFFER_BYTES, 32);
|
|
if (err < 0)
|
|
return err;
|
|
if (runtime->hw.formats & SNDRV_PCM_FMTBIT_S32_LE) {
|
|
err = snd_pcm_hw_constraint_msbits(runtime, 0, 32, 24);
|
|
if (err < 0)
|
|
return err;
|
|
}
|
|
if (runtime->hw.channels_max > 2) {
|
|
err = snd_pcm_hw_constraint_step(runtime, 0,
|
|
SNDRV_PCM_HW_PARAM_CHANNELS,
|
|
2);
|
|
if (err < 0)
|
|
return err;
|
|
}
|
|
if (channel == PCM_MULTICH) {
|
|
err = snd_pcm_hw_constraint_minmax
|
|
(runtime, SNDRV_PCM_HW_PARAM_PERIOD_TIME, 0, 8192000);
|
|
if (err < 0)
|
|
return err;
|
|
}
|
|
snd_pcm_set_sync(substream);
|
|
chip->streams[channel] = substream;
|
|
|
|
mutex_lock(&chip->mutex);
|
|
chip->pcm_active |= 1 << channel;
|
|
if (channel == PCM_SPDIF) {
|
|
chip->spdif_pcm_bits = chip->spdif_bits;
|
|
chip->controls[CONTROL_SPDIF_PCM]->vd[0].access &=
|
|
~SNDRV_CTL_ELEM_ACCESS_INACTIVE;
|
|
snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_VALUE |
|
|
SNDRV_CTL_EVENT_MASK_INFO,
|
|
&chip->controls[CONTROL_SPDIF_PCM]->id);
|
|
}
|
|
mutex_unlock(&chip->mutex);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int oxygen_rec_a_open(struct snd_pcm_substream *substream)
|
|
{
|
|
return oxygen_open(substream, PCM_A);
|
|
}
|
|
|
|
static int oxygen_rec_b_open(struct snd_pcm_substream *substream)
|
|
{
|
|
return oxygen_open(substream, PCM_B);
|
|
}
|
|
|
|
static int oxygen_rec_c_open(struct snd_pcm_substream *substream)
|
|
{
|
|
return oxygen_open(substream, PCM_C);
|
|
}
|
|
|
|
static int oxygen_spdif_open(struct snd_pcm_substream *substream)
|
|
{
|
|
return oxygen_open(substream, PCM_SPDIF);
|
|
}
|
|
|
|
static int oxygen_multich_open(struct snd_pcm_substream *substream)
|
|
{
|
|
return oxygen_open(substream, PCM_MULTICH);
|
|
}
|
|
|
|
static int oxygen_ac97_open(struct snd_pcm_substream *substream)
|
|
{
|
|
return oxygen_open(substream, PCM_AC97);
|
|
}
|
|
|
|
static int oxygen_close(struct snd_pcm_substream *substream)
|
|
{
|
|
struct oxygen *chip = snd_pcm_substream_chip(substream);
|
|
unsigned int channel = oxygen_substream_channel(substream);
|
|
|
|
mutex_lock(&chip->mutex);
|
|
chip->pcm_active &= ~(1 << channel);
|
|
if (channel == PCM_SPDIF) {
|
|
chip->controls[CONTROL_SPDIF_PCM]->vd[0].access |=
|
|
SNDRV_CTL_ELEM_ACCESS_INACTIVE;
|
|
snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_VALUE |
|
|
SNDRV_CTL_EVENT_MASK_INFO,
|
|
&chip->controls[CONTROL_SPDIF_PCM]->id);
|
|
}
|
|
if (channel == PCM_SPDIF || channel == PCM_MULTICH)
|
|
oxygen_update_spdif_source(chip);
|
|
mutex_unlock(&chip->mutex);
|
|
|
|
chip->streams[channel] = NULL;
|
|
return 0;
|
|
}
|
|
|
|
static unsigned int oxygen_format(struct snd_pcm_hw_params *hw_params)
|
|
{
|
|
if (params_format(hw_params) == SNDRV_PCM_FORMAT_S32_LE)
|
|
return OXYGEN_FORMAT_24;
|
|
else
|
|
return OXYGEN_FORMAT_16;
|
|
}
|
|
|
|
static unsigned int oxygen_rate(struct snd_pcm_hw_params *hw_params)
|
|
{
|
|
switch (params_rate(hw_params)) {
|
|
case 32000:
|
|
return OXYGEN_RATE_32000;
|
|
case 44100:
|
|
return OXYGEN_RATE_44100;
|
|
default: /* 48000 */
|
|
return OXYGEN_RATE_48000;
|
|
case 64000:
|
|
return OXYGEN_RATE_64000;
|
|
case 88200:
|
|
return OXYGEN_RATE_88200;
|
|
case 96000:
|
|
return OXYGEN_RATE_96000;
|
|
case 176400:
|
|
return OXYGEN_RATE_176400;
|
|
case 192000:
|
|
return OXYGEN_RATE_192000;
|
|
}
|
|
}
|
|
|
|
static unsigned int oxygen_i2s_mclk(struct snd_pcm_hw_params *hw_params)
|
|
{
|
|
if (params_rate(hw_params) <= 96000)
|
|
return OXYGEN_I2S_MCLK_256;
|
|
else
|
|
return OXYGEN_I2S_MCLK_128;
|
|
}
|
|
|
|
static unsigned int oxygen_i2s_bits(struct snd_pcm_hw_params *hw_params)
|
|
{
|
|
if (params_format(hw_params) == SNDRV_PCM_FORMAT_S32_LE)
|
|
return OXYGEN_I2S_BITS_24;
|
|
else
|
|
return OXYGEN_I2S_BITS_16;
|
|
}
|
|
|
|
static unsigned int oxygen_play_channels(struct snd_pcm_hw_params *hw_params)
|
|
{
|
|
switch (params_channels(hw_params)) {
|
|
default: /* 2 */
|
|
return OXYGEN_PLAY_CHANNELS_2;
|
|
case 4:
|
|
return OXYGEN_PLAY_CHANNELS_4;
|
|
case 6:
|
|
return OXYGEN_PLAY_CHANNELS_6;
|
|
case 8:
|
|
return OXYGEN_PLAY_CHANNELS_8;
|
|
}
|
|
}
|
|
|
|
static const unsigned int channel_base_registers[PCM_COUNT] = {
|
|
[PCM_A] = OXYGEN_DMA_A_ADDRESS,
|
|
[PCM_B] = OXYGEN_DMA_B_ADDRESS,
|
|
[PCM_C] = OXYGEN_DMA_C_ADDRESS,
|
|
[PCM_SPDIF] = OXYGEN_DMA_SPDIF_ADDRESS,
|
|
[PCM_MULTICH] = OXYGEN_DMA_MULTICH_ADDRESS,
|
|
[PCM_AC97] = OXYGEN_DMA_AC97_ADDRESS,
|
|
};
|
|
|
|
static int oxygen_hw_params(struct snd_pcm_substream *substream,
|
|
struct snd_pcm_hw_params *hw_params)
|
|
{
|
|
struct oxygen *chip = snd_pcm_substream_chip(substream);
|
|
unsigned int channel = oxygen_substream_channel(substream);
|
|
int err;
|
|
|
|
err = snd_pcm_lib_malloc_pages(substream,
|
|
params_buffer_bytes(hw_params));
|
|
if (err < 0)
|
|
return err;
|
|
|
|
oxygen_write32(chip, channel_base_registers[channel],
|
|
(u32)substream->runtime->dma_addr);
|
|
if (channel == PCM_MULTICH) {
|
|
oxygen_write32(chip, OXYGEN_DMA_MULTICH_COUNT,
|
|
params_buffer_bytes(hw_params) / 4 - 1);
|
|
oxygen_write32(chip, OXYGEN_DMA_MULTICH_TCOUNT,
|
|
params_period_bytes(hw_params) / 4 - 1);
|
|
} else {
|
|
oxygen_write16(chip, channel_base_registers[channel] + 4,
|
|
params_buffer_bytes(hw_params) / 4 - 1);
|
|
oxygen_write16(chip, channel_base_registers[channel] + 6,
|
|
params_period_bytes(hw_params) / 4 - 1);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int oxygen_rec_a_hw_params(struct snd_pcm_substream *substream,
|
|
struct snd_pcm_hw_params *hw_params)
|
|
{
|
|
struct oxygen *chip = snd_pcm_substream_chip(substream);
|
|
int err;
|
|
|
|
err = oxygen_hw_params(substream, hw_params);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
spin_lock_irq(&chip->reg_lock);
|
|
oxygen_write8_masked(chip, OXYGEN_REC_FORMAT,
|
|
oxygen_format(hw_params) << OXYGEN_REC_FORMAT_A_SHIFT,
|
|
OXYGEN_REC_FORMAT_A_MASK);
|
|
oxygen_write16_masked(chip, OXYGEN_I2S_A_FORMAT,
|
|
oxygen_rate(hw_params) |
|
|
oxygen_i2s_mclk(hw_params) |
|
|
chip->model.adc_i2s_format |
|
|
oxygen_i2s_bits(hw_params),
|
|
OXYGEN_I2S_RATE_MASK |
|
|
OXYGEN_I2S_FORMAT_MASK |
|
|
OXYGEN_I2S_MCLK_MASK |
|
|
OXYGEN_I2S_BITS_MASK);
|
|
spin_unlock_irq(&chip->reg_lock);
|
|
|
|
mutex_lock(&chip->mutex);
|
|
chip->model.set_adc_params(chip, hw_params);
|
|
mutex_unlock(&chip->mutex);
|
|
return 0;
|
|
}
|
|
|
|
static int oxygen_rec_b_hw_params(struct snd_pcm_substream *substream,
|
|
struct snd_pcm_hw_params *hw_params)
|
|
{
|
|
struct oxygen *chip = snd_pcm_substream_chip(substream);
|
|
int is_ac97;
|
|
int err;
|
|
|
|
err = oxygen_hw_params(substream, hw_params);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
is_ac97 = chip->has_ac97_1 &&
|
|
(chip->model.device_config & CAPTURE_2_FROM_AC97_1);
|
|
|
|
spin_lock_irq(&chip->reg_lock);
|
|
oxygen_write8_masked(chip, OXYGEN_REC_FORMAT,
|
|
oxygen_format(hw_params) << OXYGEN_REC_FORMAT_B_SHIFT,
|
|
OXYGEN_REC_FORMAT_B_MASK);
|
|
if (!is_ac97)
|
|
oxygen_write16_masked(chip, OXYGEN_I2S_B_FORMAT,
|
|
oxygen_rate(hw_params) |
|
|
oxygen_i2s_mclk(hw_params) |
|
|
chip->model.adc_i2s_format |
|
|
oxygen_i2s_bits(hw_params),
|
|
OXYGEN_I2S_RATE_MASK |
|
|
OXYGEN_I2S_FORMAT_MASK |
|
|
OXYGEN_I2S_MCLK_MASK |
|
|
OXYGEN_I2S_BITS_MASK);
|
|
spin_unlock_irq(&chip->reg_lock);
|
|
|
|
if (!is_ac97) {
|
|
mutex_lock(&chip->mutex);
|
|
chip->model.set_adc_params(chip, hw_params);
|
|
mutex_unlock(&chip->mutex);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int oxygen_rec_c_hw_params(struct snd_pcm_substream *substream,
|
|
struct snd_pcm_hw_params *hw_params)
|
|
{
|
|
struct oxygen *chip = snd_pcm_substream_chip(substream);
|
|
int err;
|
|
|
|
err = oxygen_hw_params(substream, hw_params);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
spin_lock_irq(&chip->reg_lock);
|
|
oxygen_write8_masked(chip, OXYGEN_REC_FORMAT,
|
|
oxygen_format(hw_params) << OXYGEN_REC_FORMAT_C_SHIFT,
|
|
OXYGEN_REC_FORMAT_C_MASK);
|
|
spin_unlock_irq(&chip->reg_lock);
|
|
return 0;
|
|
}
|
|
|
|
static int oxygen_spdif_hw_params(struct snd_pcm_substream *substream,
|
|
struct snd_pcm_hw_params *hw_params)
|
|
{
|
|
struct oxygen *chip = snd_pcm_substream_chip(substream);
|
|
int err;
|
|
|
|
err = oxygen_hw_params(substream, hw_params);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
spin_lock_irq(&chip->reg_lock);
|
|
oxygen_clear_bits32(chip, OXYGEN_SPDIF_CONTROL,
|
|
OXYGEN_SPDIF_OUT_ENABLE);
|
|
oxygen_write8_masked(chip, OXYGEN_PLAY_FORMAT,
|
|
oxygen_format(hw_params) << OXYGEN_SPDIF_FORMAT_SHIFT,
|
|
OXYGEN_SPDIF_FORMAT_MASK);
|
|
oxygen_write32_masked(chip, OXYGEN_SPDIF_CONTROL,
|
|
oxygen_rate(hw_params) << OXYGEN_SPDIF_OUT_RATE_SHIFT,
|
|
OXYGEN_SPDIF_OUT_RATE_MASK);
|
|
oxygen_update_spdif_source(chip);
|
|
spin_unlock_irq(&chip->reg_lock);
|
|
return 0;
|
|
}
|
|
|
|
static int oxygen_multich_hw_params(struct snd_pcm_substream *substream,
|
|
struct snd_pcm_hw_params *hw_params)
|
|
{
|
|
struct oxygen *chip = snd_pcm_substream_chip(substream);
|
|
int err;
|
|
|
|
err = oxygen_hw_params(substream, hw_params);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
spin_lock_irq(&chip->reg_lock);
|
|
oxygen_write8_masked(chip, OXYGEN_PLAY_CHANNELS,
|
|
oxygen_play_channels(hw_params),
|
|
OXYGEN_PLAY_CHANNELS_MASK);
|
|
oxygen_write8_masked(chip, OXYGEN_PLAY_FORMAT,
|
|
oxygen_format(hw_params) << OXYGEN_MULTICH_FORMAT_SHIFT,
|
|
OXYGEN_MULTICH_FORMAT_MASK);
|
|
oxygen_write16_masked(chip, OXYGEN_I2S_MULTICH_FORMAT,
|
|
oxygen_rate(hw_params) |
|
|
chip->model.dac_i2s_format |
|
|
oxygen_i2s_bits(hw_params),
|
|
OXYGEN_I2S_RATE_MASK |
|
|
OXYGEN_I2S_FORMAT_MASK |
|
|
OXYGEN_I2S_BITS_MASK);
|
|
oxygen_update_dac_routing(chip);
|
|
oxygen_update_spdif_source(chip);
|
|
spin_unlock_irq(&chip->reg_lock);
|
|
|
|
mutex_lock(&chip->mutex);
|
|
chip->model.set_dac_params(chip, hw_params);
|
|
mutex_unlock(&chip->mutex);
|
|
return 0;
|
|
}
|
|
|
|
static int oxygen_hw_free(struct snd_pcm_substream *substream)
|
|
{
|
|
struct oxygen *chip = snd_pcm_substream_chip(substream);
|
|
unsigned int channel = oxygen_substream_channel(substream);
|
|
|
|
spin_lock_irq(&chip->reg_lock);
|
|
chip->interrupt_mask &= ~(1 << channel);
|
|
oxygen_write16(chip, OXYGEN_INTERRUPT_MASK, chip->interrupt_mask);
|
|
spin_unlock_irq(&chip->reg_lock);
|
|
|
|
return snd_pcm_lib_free_pages(substream);
|
|
}
|
|
|
|
static int oxygen_spdif_hw_free(struct snd_pcm_substream *substream)
|
|
{
|
|
struct oxygen *chip = snd_pcm_substream_chip(substream);
|
|
|
|
spin_lock_irq(&chip->reg_lock);
|
|
oxygen_clear_bits32(chip, OXYGEN_SPDIF_CONTROL,
|
|
OXYGEN_SPDIF_OUT_ENABLE);
|
|
spin_unlock_irq(&chip->reg_lock);
|
|
return oxygen_hw_free(substream);
|
|
}
|
|
|
|
static int oxygen_prepare(struct snd_pcm_substream *substream)
|
|
{
|
|
struct oxygen *chip = snd_pcm_substream_chip(substream);
|
|
unsigned int channel = oxygen_substream_channel(substream);
|
|
unsigned int channel_mask = 1 << channel;
|
|
|
|
spin_lock_irq(&chip->reg_lock);
|
|
oxygen_set_bits8(chip, OXYGEN_DMA_FLUSH, channel_mask);
|
|
oxygen_clear_bits8(chip, OXYGEN_DMA_FLUSH, channel_mask);
|
|
|
|
chip->interrupt_mask |= channel_mask;
|
|
oxygen_write16(chip, OXYGEN_INTERRUPT_MASK, chip->interrupt_mask);
|
|
spin_unlock_irq(&chip->reg_lock);
|
|
return 0;
|
|
}
|
|
|
|
static int oxygen_trigger(struct snd_pcm_substream *substream, int cmd)
|
|
{
|
|
struct oxygen *chip = snd_pcm_substream_chip(substream);
|
|
struct snd_pcm_substream *s;
|
|
unsigned int mask = 0;
|
|
int pausing;
|
|
|
|
switch (cmd) {
|
|
case SNDRV_PCM_TRIGGER_STOP:
|
|
case SNDRV_PCM_TRIGGER_START:
|
|
case SNDRV_PCM_TRIGGER_SUSPEND:
|
|
pausing = 0;
|
|
break;
|
|
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
|
|
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
|
|
pausing = 1;
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
snd_pcm_group_for_each_entry(s, substream) {
|
|
if (snd_pcm_substream_chip(s) == chip) {
|
|
mask |= 1 << oxygen_substream_channel(s);
|
|
snd_pcm_trigger_done(s, substream);
|
|
}
|
|
}
|
|
|
|
spin_lock(&chip->reg_lock);
|
|
if (!pausing) {
|
|
if (cmd == SNDRV_PCM_TRIGGER_START)
|
|
chip->pcm_running |= mask;
|
|
else
|
|
chip->pcm_running &= ~mask;
|
|
oxygen_write8(chip, OXYGEN_DMA_STATUS, chip->pcm_running);
|
|
} else {
|
|
if (cmd == SNDRV_PCM_TRIGGER_PAUSE_PUSH)
|
|
oxygen_set_bits8(chip, OXYGEN_DMA_PAUSE, mask);
|
|
else
|
|
oxygen_clear_bits8(chip, OXYGEN_DMA_PAUSE, mask);
|
|
}
|
|
spin_unlock(&chip->reg_lock);
|
|
return 0;
|
|
}
|
|
|
|
static snd_pcm_uframes_t oxygen_pointer(struct snd_pcm_substream *substream)
|
|
{
|
|
struct oxygen *chip = snd_pcm_substream_chip(substream);
|
|
struct snd_pcm_runtime *runtime = substream->runtime;
|
|
unsigned int channel = oxygen_substream_channel(substream);
|
|
u32 curr_addr;
|
|
|
|
/* no spinlock, this read should be atomic */
|
|
curr_addr = oxygen_read32(chip, channel_base_registers[channel]);
|
|
return bytes_to_frames(runtime, curr_addr - (u32)runtime->dma_addr);
|
|
}
|
|
|
|
static struct snd_pcm_ops oxygen_rec_a_ops = {
|
|
.open = oxygen_rec_a_open,
|
|
.close = oxygen_close,
|
|
.ioctl = snd_pcm_lib_ioctl,
|
|
.hw_params = oxygen_rec_a_hw_params,
|
|
.hw_free = oxygen_hw_free,
|
|
.prepare = oxygen_prepare,
|
|
.trigger = oxygen_trigger,
|
|
.pointer = oxygen_pointer,
|
|
};
|
|
|
|
static struct snd_pcm_ops oxygen_rec_b_ops = {
|
|
.open = oxygen_rec_b_open,
|
|
.close = oxygen_close,
|
|
.ioctl = snd_pcm_lib_ioctl,
|
|
.hw_params = oxygen_rec_b_hw_params,
|
|
.hw_free = oxygen_hw_free,
|
|
.prepare = oxygen_prepare,
|
|
.trigger = oxygen_trigger,
|
|
.pointer = oxygen_pointer,
|
|
};
|
|
|
|
static struct snd_pcm_ops oxygen_rec_c_ops = {
|
|
.open = oxygen_rec_c_open,
|
|
.close = oxygen_close,
|
|
.ioctl = snd_pcm_lib_ioctl,
|
|
.hw_params = oxygen_rec_c_hw_params,
|
|
.hw_free = oxygen_hw_free,
|
|
.prepare = oxygen_prepare,
|
|
.trigger = oxygen_trigger,
|
|
.pointer = oxygen_pointer,
|
|
};
|
|
|
|
static struct snd_pcm_ops oxygen_spdif_ops = {
|
|
.open = oxygen_spdif_open,
|
|
.close = oxygen_close,
|
|
.ioctl = snd_pcm_lib_ioctl,
|
|
.hw_params = oxygen_spdif_hw_params,
|
|
.hw_free = oxygen_spdif_hw_free,
|
|
.prepare = oxygen_prepare,
|
|
.trigger = oxygen_trigger,
|
|
.pointer = oxygen_pointer,
|
|
};
|
|
|
|
static struct snd_pcm_ops oxygen_multich_ops = {
|
|
.open = oxygen_multich_open,
|
|
.close = oxygen_close,
|
|
.ioctl = snd_pcm_lib_ioctl,
|
|
.hw_params = oxygen_multich_hw_params,
|
|
.hw_free = oxygen_hw_free,
|
|
.prepare = oxygen_prepare,
|
|
.trigger = oxygen_trigger,
|
|
.pointer = oxygen_pointer,
|
|
};
|
|
|
|
static struct snd_pcm_ops oxygen_ac97_ops = {
|
|
.open = oxygen_ac97_open,
|
|
.close = oxygen_close,
|
|
.ioctl = snd_pcm_lib_ioctl,
|
|
.hw_params = oxygen_hw_params,
|
|
.hw_free = oxygen_hw_free,
|
|
.prepare = oxygen_prepare,
|
|
.trigger = oxygen_trigger,
|
|
.pointer = oxygen_pointer,
|
|
};
|
|
|
|
static void oxygen_pcm_free(struct snd_pcm *pcm)
|
|
{
|
|
snd_pcm_lib_preallocate_free_for_all(pcm);
|
|
}
|
|
|
|
int oxygen_pcm_init(struct oxygen *chip)
|
|
{
|
|
struct snd_pcm *pcm;
|
|
int outs, ins;
|
|
int err;
|
|
|
|
outs = !!(chip->model.device_config & PLAYBACK_0_TO_I2S);
|
|
ins = !!(chip->model.device_config & (CAPTURE_0_FROM_I2S_1 |
|
|
CAPTURE_0_FROM_I2S_2));
|
|
if (outs | ins) {
|
|
err = snd_pcm_new(chip->card, "Multichannel",
|
|
0, outs, ins, &pcm);
|
|
if (err < 0)
|
|
return err;
|
|
if (outs)
|
|
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK,
|
|
&oxygen_multich_ops);
|
|
if (chip->model.device_config & CAPTURE_0_FROM_I2S_1)
|
|
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE,
|
|
&oxygen_rec_a_ops);
|
|
else if (chip->model.device_config & CAPTURE_0_FROM_I2S_2)
|
|
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE,
|
|
&oxygen_rec_b_ops);
|
|
pcm->private_data = chip;
|
|
pcm->private_free = oxygen_pcm_free;
|
|
strcpy(pcm->name, "Multichannel");
|
|
if (outs)
|
|
snd_pcm_lib_preallocate_pages(pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream,
|
|
SNDRV_DMA_TYPE_DEV,
|
|
snd_dma_pci_data(chip->pci),
|
|
DEFAULT_BUFFER_BYTES_MULTICH,
|
|
BUFFER_BYTES_MAX_MULTICH);
|
|
if (ins)
|
|
snd_pcm_lib_preallocate_pages(pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream,
|
|
SNDRV_DMA_TYPE_DEV,
|
|
snd_dma_pci_data(chip->pci),
|
|
DEFAULT_BUFFER_BYTES,
|
|
BUFFER_BYTES_MAX);
|
|
}
|
|
|
|
outs = !!(chip->model.device_config & PLAYBACK_1_TO_SPDIF);
|
|
ins = !!(chip->model.device_config & CAPTURE_1_FROM_SPDIF);
|
|
if (outs | ins) {
|
|
err = snd_pcm_new(chip->card, "Digital", 1, outs, ins, &pcm);
|
|
if (err < 0)
|
|
return err;
|
|
if (outs)
|
|
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK,
|
|
&oxygen_spdif_ops);
|
|
if (ins)
|
|
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE,
|
|
&oxygen_rec_c_ops);
|
|
pcm->private_data = chip;
|
|
pcm->private_free = oxygen_pcm_free;
|
|
strcpy(pcm->name, "Digital");
|
|
snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
|
|
snd_dma_pci_data(chip->pci),
|
|
DEFAULT_BUFFER_BYTES,
|
|
BUFFER_BYTES_MAX);
|
|
}
|
|
|
|
if (chip->has_ac97_1) {
|
|
outs = !!(chip->model.device_config & PLAYBACK_2_TO_AC97_1);
|
|
ins = !!(chip->model.device_config & CAPTURE_2_FROM_AC97_1);
|
|
} else {
|
|
outs = 0;
|
|
ins = !!(chip->model.device_config & CAPTURE_2_FROM_I2S_2);
|
|
}
|
|
if (outs | ins) {
|
|
err = snd_pcm_new(chip->card, outs ? "AC97" : "Analog2",
|
|
2, outs, ins, &pcm);
|
|
if (err < 0)
|
|
return err;
|
|
if (outs) {
|
|
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK,
|
|
&oxygen_ac97_ops);
|
|
oxygen_write8_masked(chip, OXYGEN_REC_ROUTING,
|
|
OXYGEN_REC_B_ROUTE_AC97_1,
|
|
OXYGEN_REC_B_ROUTE_MASK);
|
|
}
|
|
if (ins)
|
|
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE,
|
|
&oxygen_rec_b_ops);
|
|
pcm->private_data = chip;
|
|
pcm->private_free = oxygen_pcm_free;
|
|
strcpy(pcm->name, outs ? "Front Panel" : "Analog 2");
|
|
snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
|
|
snd_dma_pci_data(chip->pci),
|
|
DEFAULT_BUFFER_BYTES,
|
|
BUFFER_BYTES_MAX);
|
|
}
|
|
return 0;
|
|
}
|