linux/sound/pci/ymfpci/ymfpci_main.c

2358 lines
68 KiB
C
Raw Normal View History

// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (c) by Jaroslav Kysela <perex@perex.cz>
* Routines for control of YMF724/740/744/754 chips
*/
#include <linux/delay.h>
#include <linux/firmware.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/pci.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/mutex.h>
#include <linux/module.h>
#include <linux/io.h>
#include <sound/core.h>
#include <sound/control.h>
#include <sound/info.h>
#include <sound/tlv.h>
#include "ymfpci.h"
#include <sound/asoundef.h>
#include <sound/mpu401.h>
#include <asm/byteorder.h>
/*
* common I/O routines
*/
static void snd_ymfpci_irq_wait(struct snd_ymfpci *chip);
static inline void snd_ymfpci_writeb(struct snd_ymfpci *chip, u32 offset, u8 val)
{
writeb(val, chip->reg_area_virt + offset);
}
static inline u16 snd_ymfpci_readw(struct snd_ymfpci *chip, u32 offset)
{
return readw(chip->reg_area_virt + offset);
}
static inline void snd_ymfpci_writew(struct snd_ymfpci *chip, u32 offset, u16 val)
{
writew(val, chip->reg_area_virt + offset);
}
static inline u32 snd_ymfpci_readl(struct snd_ymfpci *chip, u32 offset)
{
return readl(chip->reg_area_virt + offset);
}
static inline void snd_ymfpci_writel(struct snd_ymfpci *chip, u32 offset, u32 val)
{
writel(val, chip->reg_area_virt + offset);
}
static int snd_ymfpci_codec_ready(struct snd_ymfpci *chip, int secondary)
{
unsigned long end_time;
u32 reg = secondary ? YDSXGR_SECSTATUSADR : YDSXGR_PRISTATUSADR;
end_time = jiffies + msecs_to_jiffies(750);
do {
if ((snd_ymfpci_readw(chip, reg) & 0x8000) == 0)
return 0;
schedule_timeout_uninterruptible(1);
} while (time_before(jiffies, end_time));
dev_err(chip->card->dev,
"codec_ready: codec %i is not ready [0x%x]\n",
secondary, snd_ymfpci_readw(chip, reg));
return -EBUSY;
}
static void snd_ymfpci_codec_write(struct snd_ac97 *ac97, u16 reg, u16 val)
{
struct snd_ymfpci *chip = ac97->private_data;
u32 cmd;
snd_ymfpci_codec_ready(chip, 0);
cmd = ((YDSXG_AC97WRITECMD | reg) << 16) | val;
snd_ymfpci_writel(chip, YDSXGR_AC97CMDDATA, cmd);
}
static u16 snd_ymfpci_codec_read(struct snd_ac97 *ac97, u16 reg)
{
struct snd_ymfpci *chip = ac97->private_data;
if (snd_ymfpci_codec_ready(chip, 0))
return ~0;
snd_ymfpci_writew(chip, YDSXGR_AC97CMDADR, YDSXG_AC97READCMD | reg);
if (snd_ymfpci_codec_ready(chip, 0))
return ~0;
if (chip->device_id == PCI_DEVICE_ID_YAMAHA_744 && chip->rev < 2) {
int i;
for (i = 0; i < 600; i++)
snd_ymfpci_readw(chip, YDSXGR_PRISTATUSDATA);
}
return snd_ymfpci_readw(chip, YDSXGR_PRISTATUSDATA);
}
/*
* Misc routines
*/
static u32 snd_ymfpci_calc_delta(u32 rate)
{
switch (rate) {
case 8000: return 0x02aaab00;
case 11025: return 0x03accd00;
case 16000: return 0x05555500;
case 22050: return 0x07599a00;
case 32000: return 0x0aaaab00;
case 44100: return 0x0eb33300;
default: return ((rate << 16) / 375) << 5;
}
}
static const u32 def_rate[8] = {
100, 2000, 8000, 11025, 16000, 22050, 32000, 48000
};
static u32 snd_ymfpci_calc_lpfK(u32 rate)
{
u32 i;
static const u32 val[8] = {
0x00570000, 0x06AA0000, 0x18B20000, 0x20930000,
0x2B9A0000, 0x35A10000, 0x3EAA0000, 0x40000000
};
if (rate == 44100)
return 0x40000000; /* FIXME: What's the right value? */
for (i = 0; i < 8; i++)
if (rate <= def_rate[i])
return val[i];
return val[0];
}
static u32 snd_ymfpci_calc_lpfQ(u32 rate)
{
u32 i;
static const u32 val[8] = {
0x35280000, 0x34A70000, 0x32020000, 0x31770000,
0x31390000, 0x31C90000, 0x33D00000, 0x40000000
};
if (rate == 44100)
return 0x370A0000;
for (i = 0; i < 8; i++)
if (rate <= def_rate[i])
return val[i];
return val[0];
}
/*
* Hardware start management
*/
static void snd_ymfpci_hw_start(struct snd_ymfpci *chip)
{
unsigned long flags;
spin_lock_irqsave(&chip->reg_lock, flags);
if (chip->start_count++ > 0)
goto __end;
snd_ymfpci_writel(chip, YDSXGR_MODE,
snd_ymfpci_readl(chip, YDSXGR_MODE) | 3);
chip->active_bank = snd_ymfpci_readl(chip, YDSXGR_CTRLSELECT) & 1;
__end:
spin_unlock_irqrestore(&chip->reg_lock, flags);
}
static void snd_ymfpci_hw_stop(struct snd_ymfpci *chip)
{
unsigned long flags;
long timeout = 1000;
spin_lock_irqsave(&chip->reg_lock, flags);
if (--chip->start_count > 0)
goto __end;
snd_ymfpci_writel(chip, YDSXGR_MODE,
snd_ymfpci_readl(chip, YDSXGR_MODE) & ~3);
while (timeout-- > 0) {
if ((snd_ymfpci_readl(chip, YDSXGR_STATUS) & 2) == 0)
break;
}
if (atomic_read(&chip->interrupt_sleep_count)) {
atomic_set(&chip->interrupt_sleep_count, 0);
wake_up(&chip->interrupt_sleep);
}
__end:
spin_unlock_irqrestore(&chip->reg_lock, flags);
}
/*
* Playback voice management
*/
static int voice_alloc(struct snd_ymfpci *chip,
enum snd_ymfpci_voice_type type, int pair,
struct snd_ymfpci_voice **rvoice)
{
struct snd_ymfpci_voice *voice, *voice2;
int idx;
*rvoice = NULL;
for (idx = 0; idx < YDSXG_PLAYBACK_VOICES; idx += pair ? 2 : 1) {
voice = &chip->voices[idx];
voice2 = pair ? &chip->voices[idx+1] : NULL;
if (voice->use || (voice2 && voice2->use))
continue;
voice->use = 1;
if (voice2)
voice2->use = 1;
switch (type) {
case YMFPCI_PCM:
voice->pcm = 1;
if (voice2)
voice2->pcm = 1;
break;
case YMFPCI_SYNTH:
voice->synth = 1;
break;
case YMFPCI_MIDI:
voice->midi = 1;
break;
}
snd_ymfpci_hw_start(chip);
if (voice2)
snd_ymfpci_hw_start(chip);
*rvoice = voice;
return 0;
}
return -ENOMEM;
}
static int snd_ymfpci_voice_alloc(struct snd_ymfpci *chip,
enum snd_ymfpci_voice_type type, int pair,
struct snd_ymfpci_voice **rvoice)
{
unsigned long flags;
int result;
if (snd_BUG_ON(!rvoice))
return -EINVAL;
if (snd_BUG_ON(pair && type != YMFPCI_PCM))
return -EINVAL;
spin_lock_irqsave(&chip->voice_lock, flags);
for (;;) {
result = voice_alloc(chip, type, pair, rvoice);
if (result == 0 || type != YMFPCI_PCM)
break;
/* TODO: synth/midi voice deallocation */
break;
}
spin_unlock_irqrestore(&chip->voice_lock, flags);
return result;
}
static int snd_ymfpci_voice_free(struct snd_ymfpci *chip, struct snd_ymfpci_voice *pvoice)
{
unsigned long flags;
if (snd_BUG_ON(!pvoice))
return -EINVAL;
snd_ymfpci_hw_stop(chip);
spin_lock_irqsave(&chip->voice_lock, flags);
if (pvoice->number == chip->src441_used) {
chip->src441_used = -1;
pvoice->ypcm->use_441_slot = 0;
}
pvoice->use = pvoice->pcm = pvoice->synth = pvoice->midi = 0;
pvoice->ypcm = NULL;
pvoice->interrupt = NULL;
spin_unlock_irqrestore(&chip->voice_lock, flags);
return 0;
}
/*
* PCM part
*/
static void snd_ymfpci_pcm_interrupt(struct snd_ymfpci *chip, struct snd_ymfpci_voice *voice)
{
struct snd_ymfpci_pcm *ypcm;
u32 pos, delta;
ypcm = voice->ypcm;
if (!ypcm)
return;
if (ypcm->substream == NULL)
return;
spin_lock(&chip->reg_lock);
if (ypcm->running) {
pos = le32_to_cpu(voice->bank[chip->active_bank].start);
if (pos < ypcm->last_pos)
delta = pos + (ypcm->buffer_size - ypcm->last_pos);
else
delta = pos - ypcm->last_pos;
ypcm->period_pos += delta;
ypcm->last_pos = pos;
if (ypcm->period_pos >= ypcm->period_size) {
/*
dev_dbg(chip->card->dev,
"done - active_bank = 0x%x, start = 0x%x\n",
chip->active_bank,
voice->bank[chip->active_bank].start);
*/
ypcm->period_pos %= ypcm->period_size;
spin_unlock(&chip->reg_lock);
snd_pcm_period_elapsed(ypcm->substream);
spin_lock(&chip->reg_lock);
}
if (unlikely(ypcm->update_pcm_vol)) {
unsigned int subs = ypcm->substream->number;
unsigned int next_bank = 1 - chip->active_bank;
struct snd_ymfpci_playback_bank *bank;
__le32 volume;
bank = &voice->bank[next_bank];
volume = cpu_to_le32(chip->pcm_mixer[subs].left << 15);
bank->left_gain_end = volume;
if (ypcm->output_rear)
bank->eff2_gain_end = volume;
if (ypcm->voices[1])
bank = &ypcm->voices[1]->bank[next_bank];
volume = cpu_to_le32(chip->pcm_mixer[subs].right << 15);
bank->right_gain_end = volume;
if (ypcm->output_rear)
bank->eff3_gain_end = volume;
ypcm->update_pcm_vol--;
}
}
spin_unlock(&chip->reg_lock);
}
static void snd_ymfpci_pcm_capture_interrupt(struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct snd_ymfpci_pcm *ypcm = runtime->private_data;
struct snd_ymfpci *chip = ypcm->chip;
u32 pos, delta;
spin_lock(&chip->reg_lock);
if (ypcm->running) {
pos = le32_to_cpu(chip->bank_capture[ypcm->capture_bank_number][chip->active_bank]->start) >> ypcm->shift;
if (pos < ypcm->last_pos)
delta = pos + (ypcm->buffer_size - ypcm->last_pos);
else
delta = pos - ypcm->last_pos;
ypcm->period_pos += delta;
ypcm->last_pos = pos;
if (ypcm->period_pos >= ypcm->period_size) {
ypcm->period_pos %= ypcm->period_size;
/*
dev_dbg(chip->card->dev,
"done - active_bank = 0x%x, start = 0x%x\n",
chip->active_bank,
voice->bank[chip->active_bank].start);
*/
spin_unlock(&chip->reg_lock);
snd_pcm_period_elapsed(substream);
spin_lock(&chip->reg_lock);
}
}
spin_unlock(&chip->reg_lock);
}
static int snd_ymfpci_playback_trigger(struct snd_pcm_substream *substream,
int cmd)
{
struct snd_ymfpci *chip = snd_pcm_substream_chip(substream);
struct snd_ymfpci_pcm *ypcm = substream->runtime->private_data;
struct snd_kcontrol *kctl = NULL;
int result = 0;
spin_lock(&chip->reg_lock);
if (ypcm->voices[0] == NULL) {
result = -EINVAL;
goto __unlock;
}
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
case SNDRV_PCM_TRIGGER_RESUME:
chip->ctrl_playback[ypcm->voices[0]->number + 1] = cpu_to_le32(ypcm->voices[0]->bank_addr);
if (ypcm->voices[1] != NULL && !ypcm->use_441_slot)
chip->ctrl_playback[ypcm->voices[1]->number + 1] = cpu_to_le32(ypcm->voices[1]->bank_addr);
ypcm->running = 1;
break;
case SNDRV_PCM_TRIGGER_STOP:
if (substream->pcm == chip->pcm && !ypcm->use_441_slot) {
kctl = chip->pcm_mixer[substream->number].ctl;
kctl->vd[0].access |= SNDRV_CTL_ELEM_ACCESS_INACTIVE;
}
fallthrough;
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
case SNDRV_PCM_TRIGGER_SUSPEND:
chip->ctrl_playback[ypcm->voices[0]->number + 1] = 0;
if (ypcm->voices[1] != NULL && !ypcm->use_441_slot)
chip->ctrl_playback[ypcm->voices[1]->number + 1] = 0;
ypcm->running = 0;
break;
default:
result = -EINVAL;
break;
}
__unlock:
spin_unlock(&chip->reg_lock);
if (kctl)
snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_INFO, &kctl->id);
return result;
}
static int snd_ymfpci_capture_trigger(struct snd_pcm_substream *substream,
int cmd)
{
struct snd_ymfpci *chip = snd_pcm_substream_chip(substream);
struct snd_ymfpci_pcm *ypcm = substream->runtime->private_data;
int result = 0;
u32 tmp;
spin_lock(&chip->reg_lock);
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
case SNDRV_PCM_TRIGGER_RESUME:
tmp = snd_ymfpci_readl(chip, YDSXGR_MAPOFREC) | (1 << ypcm->capture_bank_number);
snd_ymfpci_writel(chip, YDSXGR_MAPOFREC, tmp);
ypcm->running = 1;
break;
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
case SNDRV_PCM_TRIGGER_SUSPEND:
tmp = snd_ymfpci_readl(chip, YDSXGR_MAPOFREC) & ~(1 << ypcm->capture_bank_number);
snd_ymfpci_writel(chip, YDSXGR_MAPOFREC, tmp);
ypcm->running = 0;
break;
default:
result = -EINVAL;
break;
}
spin_unlock(&chip->reg_lock);
return result;
}
static int snd_ymfpci_pcm_voice_alloc(struct snd_ymfpci_pcm *ypcm, int voices)
{
int err;
if (ypcm->voices[1] != NULL && voices < 2) {
snd_ymfpci_voice_free(ypcm->chip, ypcm->voices[1]);
ypcm->voices[1] = NULL;
}
if (voices == 1 && ypcm->voices[0] != NULL)
return 0; /* already allocated */
if (voices == 2 && ypcm->voices[0] != NULL && ypcm->voices[1] != NULL)
return 0; /* already allocated */
if (voices > 1) {
if (ypcm->voices[0] != NULL && ypcm->voices[1] == NULL) {
snd_ymfpci_voice_free(ypcm->chip, ypcm->voices[0]);
ypcm->voices[0] = NULL;
}
}
err = snd_ymfpci_voice_alloc(ypcm->chip, YMFPCI_PCM, voices > 1, &ypcm->voices[0]);
if (err < 0)
return err;
ypcm->voices[0]->ypcm = ypcm;
ypcm->voices[0]->interrupt = snd_ymfpci_pcm_interrupt;
if (voices > 1) {
ypcm->voices[1] = &ypcm->chip->voices[ypcm->voices[0]->number + 1];
ypcm->voices[1]->ypcm = ypcm;
}
return 0;
}
static void snd_ymfpci_pcm_init_voice(struct snd_ymfpci_pcm *ypcm, unsigned int voiceidx,
struct snd_pcm_runtime *runtime,
int has_pcm_volume)
{
struct snd_ymfpci_voice *voice = ypcm->voices[voiceidx];
u32 format;
u32 delta = snd_ymfpci_calc_delta(runtime->rate);
u32 lpfQ = snd_ymfpci_calc_lpfQ(runtime->rate);
u32 lpfK = snd_ymfpci_calc_lpfK(runtime->rate);
struct snd_ymfpci_playback_bank *bank;
unsigned int nbank;
__le32 vol_left, vol_right;
u8 use_left, use_right;
unsigned long flags;
if (snd_BUG_ON(!voice))
return;
if (runtime->channels == 1) {
use_left = 1;
use_right = 1;
} else {
use_left = (voiceidx & 1) == 0;
use_right = !use_left;
}
if (has_pcm_volume) {
vol_left = cpu_to_le32(ypcm->chip->pcm_mixer
[ypcm->substream->number].left << 15);
vol_right = cpu_to_le32(ypcm->chip->pcm_mixer
[ypcm->substream->number].right << 15);
} else {
vol_left = cpu_to_le32(0x40000000);
vol_right = cpu_to_le32(0x40000000);
}
spin_lock_irqsave(&ypcm->chip->voice_lock, flags);
format = runtime->channels == 2 ? 0x00010000 : 0;
if (snd_pcm_format_width(runtime->format) == 8)
format |= 0x80000000;
else if (ypcm->chip->device_id == PCI_DEVICE_ID_YAMAHA_754 &&
runtime->rate == 44100 && runtime->channels == 2 &&
voiceidx == 0 && (ypcm->chip->src441_used == -1 ||
ypcm->chip->src441_used == voice->number)) {
ypcm->chip->src441_used = voice->number;
ypcm->use_441_slot = 1;
format |= 0x10000000;
}
if (ypcm->chip->src441_used == voice->number &&
(format & 0x10000000) == 0) {
ypcm->chip->src441_used = -1;
ypcm->use_441_slot = 0;
}
if (runtime->channels == 2 && (voiceidx & 1) != 0)
format |= 1;
spin_unlock_irqrestore(&ypcm->chip->voice_lock, flags);
for (nbank = 0; nbank < 2; nbank++) {
bank = &voice->bank[nbank];
memset(bank, 0, sizeof(*bank));
bank->format = cpu_to_le32(format);
bank->base = cpu_to_le32(runtime->dma_addr);
bank->loop_end = cpu_to_le32(ypcm->buffer_size);
bank->lpfQ = cpu_to_le32(lpfQ);
bank->delta =
bank->delta_end = cpu_to_le32(delta);
bank->lpfK =
bank->lpfK_end = cpu_to_le32(lpfK);
bank->eg_gain =
bank->eg_gain_end = cpu_to_le32(0x40000000);
if (ypcm->output_front) {
if (use_left) {
bank->left_gain =
bank->left_gain_end = vol_left;
}
if (use_right) {
bank->right_gain =
bank->right_gain_end = vol_right;
}
}
if (ypcm->output_rear) {
if (!ypcm->swap_rear) {
if (use_left) {
bank->eff2_gain =
bank->eff2_gain_end = vol_left;
}
if (use_right) {
bank->eff3_gain =
bank->eff3_gain_end = vol_right;
}
} else {
/* The SPDIF out channels seem to be swapped, so we have
* to swap them here, too. The rear analog out channels
* will be wrong, but otherwise AC3 would not work.
*/
if (use_left) {
bank->eff3_gain =
bank->eff3_gain_end = vol_left;
}
if (use_right) {
bank->eff2_gain =
bank->eff2_gain_end = vol_right;
}
}
}
}
}
static int snd_ymfpci_ac3_init(struct snd_ymfpci *chip)
{
if (snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, &chip->pci->dev,
4096, &chip->ac3_tmp_base) < 0)
return -ENOMEM;
chip->bank_effect[3][0]->base =
chip->bank_effect[3][1]->base = cpu_to_le32(chip->ac3_tmp_base.addr);
chip->bank_effect[3][0]->loop_end =
chip->bank_effect[3][1]->loop_end = cpu_to_le32(1024);
chip->bank_effect[4][0]->base =
chip->bank_effect[4][1]->base = cpu_to_le32(chip->ac3_tmp_base.addr + 2048);
chip->bank_effect[4][0]->loop_end =
chip->bank_effect[4][1]->loop_end = cpu_to_le32(1024);
spin_lock_irq(&chip->reg_lock);
snd_ymfpci_writel(chip, YDSXGR_MAPOFEFFECT,
snd_ymfpci_readl(chip, YDSXGR_MAPOFEFFECT) | 3 << 3);
spin_unlock_irq(&chip->reg_lock);
return 0;
}
static int snd_ymfpci_ac3_done(struct snd_ymfpci *chip)
{
spin_lock_irq(&chip->reg_lock);
snd_ymfpci_writel(chip, YDSXGR_MAPOFEFFECT,
snd_ymfpci_readl(chip, YDSXGR_MAPOFEFFECT) & ~(3 << 3));
spin_unlock_irq(&chip->reg_lock);
// snd_ymfpci_irq_wait(chip);
if (chip->ac3_tmp_base.area) {
snd_dma_free_pages(&chip->ac3_tmp_base);
chip->ac3_tmp_base.area = NULL;
}
return 0;
}
static int snd_ymfpci_playback_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *hw_params)
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct snd_ymfpci_pcm *ypcm = runtime->private_data;
int err;
err = snd_ymfpci_pcm_voice_alloc(ypcm, params_channels(hw_params));
if (err < 0)
return err;
return 0;
}
static int snd_ymfpci_playback_hw_free(struct snd_pcm_substream *substream)
{
struct snd_ymfpci *chip = snd_pcm_substream_chip(substream);
struct snd_pcm_runtime *runtime = substream->runtime;
struct snd_ymfpci_pcm *ypcm;
if (runtime->private_data == NULL)
return 0;
ypcm = runtime->private_data;
/* wait, until the PCI operations are not finished */
snd_ymfpci_irq_wait(chip);
if (ypcm->voices[1]) {
snd_ymfpci_voice_free(chip, ypcm->voices[1]);
ypcm->voices[1] = NULL;
}
if (ypcm->voices[0]) {
snd_ymfpci_voice_free(chip, ypcm->voices[0]);
ypcm->voices[0] = NULL;
}
return 0;
}
static int snd_ymfpci_playback_prepare(struct snd_pcm_substream *substream)
{
struct snd_ymfpci *chip = snd_pcm_substream_chip(substream);
struct snd_pcm_runtime *runtime = substream->runtime;
struct snd_ymfpci_pcm *ypcm = runtime->private_data;
struct snd_kcontrol *kctl;
unsigned int nvoice;
ypcm->period_size = runtime->period_size;
ypcm->buffer_size = runtime->buffer_size;
ypcm->period_pos = 0;
ypcm->last_pos = 0;
for (nvoice = 0; nvoice < runtime->channels; nvoice++)
snd_ymfpci_pcm_init_voice(ypcm, nvoice, runtime,
substream->pcm == chip->pcm);
if (substream->pcm == chip->pcm && !ypcm->use_441_slot) {
kctl = chip->pcm_mixer[substream->number].ctl;
kctl->vd[0].access &= ~SNDRV_CTL_ELEM_ACCESS_INACTIVE;
snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_INFO, &kctl->id);
}
return 0;
}
static int snd_ymfpci_capture_hw_free(struct snd_pcm_substream *substream)
{
struct snd_ymfpci *chip = snd_pcm_substream_chip(substream);
/* wait, until the PCI operations are not finished */
snd_ymfpci_irq_wait(chip);
return 0;
}
static int snd_ymfpci_capture_prepare(struct snd_pcm_substream *substream)
{
struct snd_ymfpci *chip = snd_pcm_substream_chip(substream);
struct snd_pcm_runtime *runtime = substream->runtime;
struct snd_ymfpci_pcm *ypcm = runtime->private_data;
struct snd_ymfpci_capture_bank * bank;
int nbank;
u32 rate, format;
ypcm->period_size = runtime->period_size;
ypcm->buffer_size = runtime->buffer_size;
ypcm->period_pos = 0;
ypcm->last_pos = 0;
ypcm->shift = 0;
rate = ((48000 * 4096) / runtime->rate) - 1;
format = 0;
if (runtime->channels == 2) {
format |= 2;
ypcm->shift++;
}
if (snd_pcm_format_width(runtime->format) == 8)
format |= 1;
else
ypcm->shift++;
switch (ypcm->capture_bank_number) {
case 0:
snd_ymfpci_writel(chip, YDSXGR_RECFORMAT, format);
snd_ymfpci_writel(chip, YDSXGR_RECSLOTSR, rate);
break;
case 1:
snd_ymfpci_writel(chip, YDSXGR_ADCFORMAT, format);
snd_ymfpci_writel(chip, YDSXGR_ADCSLOTSR, rate);
break;
}
for (nbank = 0; nbank < 2; nbank++) {
bank = chip->bank_capture[ypcm->capture_bank_number][nbank];
bank->base = cpu_to_le32(runtime->dma_addr);
bank->loop_end = cpu_to_le32(ypcm->buffer_size << ypcm->shift);
bank->start = 0;
bank->num_of_loops = 0;
}
return 0;
}
static snd_pcm_uframes_t snd_ymfpci_playback_pointer(struct snd_pcm_substream *substream)
{
struct snd_ymfpci *chip = snd_pcm_substream_chip(substream);
struct snd_pcm_runtime *runtime = substream->runtime;
struct snd_ymfpci_pcm *ypcm = runtime->private_data;
struct snd_ymfpci_voice *voice = ypcm->voices[0];
if (!(ypcm->running && voice))
return 0;
return le32_to_cpu(voice->bank[chip->active_bank].start);
}
static snd_pcm_uframes_t snd_ymfpci_capture_pointer(struct snd_pcm_substream *substream)
{
struct snd_ymfpci *chip = snd_pcm_substream_chip(substream);
struct snd_pcm_runtime *runtime = substream->runtime;
struct snd_ymfpci_pcm *ypcm = runtime->private_data;
if (!ypcm->running)
return 0;
return le32_to_cpu(chip->bank_capture[ypcm->capture_bank_number][chip->active_bank]->start) >> ypcm->shift;
}
static void snd_ymfpci_irq_wait(struct snd_ymfpci *chip)
{
wait_queue_entry_t wait;
int loops = 4;
while (loops-- > 0) {
if ((snd_ymfpci_readl(chip, YDSXGR_MODE) & 3) == 0)
continue;
init_waitqueue_entry(&wait, current);
add_wait_queue(&chip->interrupt_sleep, &wait);
atomic_inc(&chip->interrupt_sleep_count);
schedule_timeout_uninterruptible(msecs_to_jiffies(50));
remove_wait_queue(&chip->interrupt_sleep, &wait);
}
}
IRQ: Maintain regs pointer globally rather than passing to IRQ handlers Maintain a per-CPU global "struct pt_regs *" variable which can be used instead of passing regs around manually through all ~1800 interrupt handlers in the Linux kernel. The regs pointer is used in few places, but it potentially costs both stack space and code to pass it around. On the FRV arch, removing the regs parameter from all the genirq function results in a 20% speed up of the IRQ exit path (ie: from leaving timer_interrupt() to leaving do_IRQ()). Where appropriate, an arch may override the generic storage facility and do something different with the variable. On FRV, for instance, the address is maintained in GR28 at all times inside the kernel as part of general exception handling. Having looked over the code, it appears that the parameter may be handed down through up to twenty or so layers of functions. Consider a USB character device attached to a USB hub, attached to a USB controller that posts its interrupts through a cascaded auxiliary interrupt controller. A character device driver may want to pass regs to the sysrq handler through the input layer which adds another few layers of parameter passing. I've build this code with allyesconfig for x86_64 and i386. I've runtested the main part of the code on FRV and i386, though I can't test most of the drivers. I've also done partial conversion for powerpc and MIPS - these at least compile with minimal configurations. This will affect all archs. Mostly the changes should be relatively easy. Take do_IRQ(), store the regs pointer at the beginning, saving the old one: struct pt_regs *old_regs = set_irq_regs(regs); And put the old one back at the end: set_irq_regs(old_regs); Don't pass regs through to generic_handle_irq() or __do_IRQ(). In timer_interrupt(), this sort of change will be necessary: - update_process_times(user_mode(regs)); - profile_tick(CPU_PROFILING, regs); + update_process_times(user_mode(get_irq_regs())); + profile_tick(CPU_PROFILING); I'd like to move update_process_times()'s use of get_irq_regs() into itself, except that i386, alone of the archs, uses something other than user_mode(). Some notes on the interrupt handling in the drivers: (*) input_dev() is now gone entirely. The regs pointer is no longer stored in the input_dev struct. (*) finish_unlinks() in drivers/usb/host/ohci-q.c needs checking. It does something different depending on whether it's been supplied with a regs pointer or not. (*) Various IRQ handler function pointers have been moved to type irq_handler_t. Signed-Off-By: David Howells <dhowells@redhat.com> (cherry picked from 1b16e7ac850969f38b375e511e3fa2f474a33867 commit)
2006-10-05 13:55:46 +00:00
static irqreturn_t snd_ymfpci_interrupt(int irq, void *dev_id)
{
struct snd_ymfpci *chip = dev_id;
u32 status, nvoice, mode;
struct snd_ymfpci_voice *voice;
status = snd_ymfpci_readl(chip, YDSXGR_STATUS);
if (status & 0x80000000) {
chip->active_bank = snd_ymfpci_readl(chip, YDSXGR_CTRLSELECT) & 1;
spin_lock(&chip->voice_lock);
for (nvoice = 0; nvoice < YDSXG_PLAYBACK_VOICES; nvoice++) {
voice = &chip->voices[nvoice];
if (voice->interrupt)
voice->interrupt(chip, voice);
}
for (nvoice = 0; nvoice < YDSXG_CAPTURE_VOICES; nvoice++) {
if (chip->capture_substream[nvoice])
snd_ymfpci_pcm_capture_interrupt(chip->capture_substream[nvoice]);
}
#if 0
for (nvoice = 0; nvoice < YDSXG_EFFECT_VOICES; nvoice++) {
if (chip->effect_substream[nvoice])
snd_ymfpci_pcm_effect_interrupt(chip->effect_substream[nvoice]);
}
#endif
spin_unlock(&chip->voice_lock);
spin_lock(&chip->reg_lock);
snd_ymfpci_writel(chip, YDSXGR_STATUS, 0x80000000);
mode = snd_ymfpci_readl(chip, YDSXGR_MODE) | 2;
snd_ymfpci_writel(chip, YDSXGR_MODE, mode);
spin_unlock(&chip->reg_lock);
if (atomic_read(&chip->interrupt_sleep_count)) {
atomic_set(&chip->interrupt_sleep_count, 0);
wake_up(&chip->interrupt_sleep);
}
}
status = snd_ymfpci_readw(chip, YDSXGR_INTFLAG);
if (status & 1) {
if (chip->timer)
snd_timer_interrupt(chip->timer, chip->timer_ticks);
}
snd_ymfpci_writew(chip, YDSXGR_INTFLAG, status);
if (chip->rawmidi)
IRQ: Maintain regs pointer globally rather than passing to IRQ handlers Maintain a per-CPU global "struct pt_regs *" variable which can be used instead of passing regs around manually through all ~1800 interrupt handlers in the Linux kernel. The regs pointer is used in few places, but it potentially costs both stack space and code to pass it around. On the FRV arch, removing the regs parameter from all the genirq function results in a 20% speed up of the IRQ exit path (ie: from leaving timer_interrupt() to leaving do_IRQ()). Where appropriate, an arch may override the generic storage facility and do something different with the variable. On FRV, for instance, the address is maintained in GR28 at all times inside the kernel as part of general exception handling. Having looked over the code, it appears that the parameter may be handed down through up to twenty or so layers of functions. Consider a USB character device attached to a USB hub, attached to a USB controller that posts its interrupts through a cascaded auxiliary interrupt controller. A character device driver may want to pass regs to the sysrq handler through the input layer which adds another few layers of parameter passing. I've build this code with allyesconfig for x86_64 and i386. I've runtested the main part of the code on FRV and i386, though I can't test most of the drivers. I've also done partial conversion for powerpc and MIPS - these at least compile with minimal configurations. This will affect all archs. Mostly the changes should be relatively easy. Take do_IRQ(), store the regs pointer at the beginning, saving the old one: struct pt_regs *old_regs = set_irq_regs(regs); And put the old one back at the end: set_irq_regs(old_regs); Don't pass regs through to generic_handle_irq() or __do_IRQ(). In timer_interrupt(), this sort of change will be necessary: - update_process_times(user_mode(regs)); - profile_tick(CPU_PROFILING, regs); + update_process_times(user_mode(get_irq_regs())); + profile_tick(CPU_PROFILING); I'd like to move update_process_times()'s use of get_irq_regs() into itself, except that i386, alone of the archs, uses something other than user_mode(). Some notes on the interrupt handling in the drivers: (*) input_dev() is now gone entirely. The regs pointer is no longer stored in the input_dev struct. (*) finish_unlinks() in drivers/usb/host/ohci-q.c needs checking. It does something different depending on whether it's been supplied with a regs pointer or not. (*) Various IRQ handler function pointers have been moved to type irq_handler_t. Signed-Off-By: David Howells <dhowells@redhat.com> (cherry picked from 1b16e7ac850969f38b375e511e3fa2f474a33867 commit)
2006-10-05 13:55:46 +00:00
snd_mpu401_uart_interrupt(irq, chip->rawmidi->private_data);
return IRQ_HANDLED;
}
static const struct snd_pcm_hardware snd_ymfpci_playback =
{
.info = (SNDRV_PCM_INFO_MMAP |
SNDRV_PCM_INFO_MMAP_VALID |
SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_BLOCK_TRANSFER |
SNDRV_PCM_INFO_PAUSE |
SNDRV_PCM_INFO_RESUME),
.formats = SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE,
.rates = SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_48000,
.rate_min = 8000,
.rate_max = 48000,
.channels_min = 1,
.channels_max = 2,
.buffer_bytes_max = 256 * 1024, /* FIXME: enough? */
.period_bytes_min = 64,
.period_bytes_max = 256 * 1024, /* FIXME: enough? */
.periods_min = 3,
.periods_max = 1024,
.fifo_size = 0,
};
static const struct snd_pcm_hardware snd_ymfpci_capture =
{
.info = (SNDRV_PCM_INFO_MMAP |
SNDRV_PCM_INFO_MMAP_VALID |
SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_BLOCK_TRANSFER |
SNDRV_PCM_INFO_PAUSE |
SNDRV_PCM_INFO_RESUME),
.formats = SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE,
.rates = SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_48000,
.rate_min = 8000,
.rate_max = 48000,
.channels_min = 1,
.channels_max = 2,
.buffer_bytes_max = 256 * 1024, /* FIXME: enough? */
.period_bytes_min = 64,
.period_bytes_max = 256 * 1024, /* FIXME: enough? */
.periods_min = 3,
.periods_max = 1024,
.fifo_size = 0,
};
static void snd_ymfpci_pcm_free_substream(struct snd_pcm_runtime *runtime)
{
kfree(runtime->private_data);
}
static int snd_ymfpci_playback_open_1(struct snd_pcm_substream *substream)
{
struct snd_ymfpci *chip = snd_pcm_substream_chip(substream);
struct snd_pcm_runtime *runtime = substream->runtime;
struct snd_ymfpci_pcm *ypcm;
int err;
runtime->hw = snd_ymfpci_playback;
/* FIXME? True value is 256/48 = 5.33333 ms */
err = snd_pcm_hw_constraint_minmax(runtime,
SNDRV_PCM_HW_PARAM_PERIOD_TIME,
5334, UINT_MAX);
if (err < 0)
return err;
err = snd_pcm_hw_rule_noresample(runtime, 48000);
if (err < 0)
return err;
ypcm = kzalloc(sizeof(*ypcm), GFP_KERNEL);
if (ypcm == NULL)
return -ENOMEM;
ypcm->chip = chip;
ypcm->type = PLAYBACK_VOICE;
ypcm->substream = substream;
runtime->private_data = ypcm;
runtime->private_free = snd_ymfpci_pcm_free_substream;
return 0;
}
/* call with spinlock held */
static void ymfpci_open_extension(struct snd_ymfpci *chip)
{
if (! chip->rear_opened) {
if (! chip->spdif_opened) /* set AC3 */
snd_ymfpci_writel(chip, YDSXGR_MODE,
snd_ymfpci_readl(chip, YDSXGR_MODE) | (1 << 30));
/* enable second codec (4CHEN) */
snd_ymfpci_writew(chip, YDSXGR_SECCONFIG,
(snd_ymfpci_readw(chip, YDSXGR_SECCONFIG) & ~0x0330) | 0x0010);
}
}
/* call with spinlock held */
static void ymfpci_close_extension(struct snd_ymfpci *chip)
{
if (! chip->rear_opened) {
if (! chip->spdif_opened)
snd_ymfpci_writel(chip, YDSXGR_MODE,
snd_ymfpci_readl(chip, YDSXGR_MODE) & ~(1 << 30));
snd_ymfpci_writew(chip, YDSXGR_SECCONFIG,
(snd_ymfpci_readw(chip, YDSXGR_SECCONFIG) & ~0x0330) & ~0x0010);
}
}
static int snd_ymfpci_playback_open(struct snd_pcm_substream *substream)
{
struct snd_ymfpci *chip = snd_pcm_substream_chip(substream);
struct snd_pcm_runtime *runtime = substream->runtime;
struct snd_ymfpci_pcm *ypcm;
int err;
err = snd_ymfpci_playback_open_1(substream);
if (err < 0)
return err;
ypcm = runtime->private_data;
ypcm->output_front = 1;
ypcm->output_rear = chip->mode_dup4ch ? 1 : 0;
ypcm->swap_rear = 0;
spin_lock_irq(&chip->reg_lock);
if (ypcm->output_rear) {
ymfpci_open_extension(chip);
chip->rear_opened++;
}
spin_unlock_irq(&chip->reg_lock);
return 0;
}
static int snd_ymfpci_playback_spdif_open(struct snd_pcm_substream *substream)
{
struct snd_ymfpci *chip = snd_pcm_substream_chip(substream);
struct snd_pcm_runtime *runtime = substream->runtime;
struct snd_ymfpci_pcm *ypcm;
int err;
err = snd_ymfpci_playback_open_1(substream);
if (err < 0)
return err;
ypcm = runtime->private_data;
ypcm->output_front = 0;
ypcm->output_rear = 1;
ypcm->swap_rear = 1;
spin_lock_irq(&chip->reg_lock);
snd_ymfpci_writew(chip, YDSXGR_SPDIFOUTCTRL,
snd_ymfpci_readw(chip, YDSXGR_SPDIFOUTCTRL) | 2);
ymfpci_open_extension(chip);
chip->spdif_pcm_bits = chip->spdif_bits;
snd_ymfpci_writew(chip, YDSXGR_SPDIFOUTSTATUS, chip->spdif_pcm_bits);
chip->spdif_opened++;
spin_unlock_irq(&chip->reg_lock);
chip->spdif_pcm_ctl->vd[0].access &= ~SNDRV_CTL_ELEM_ACCESS_INACTIVE;
snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_VALUE |
SNDRV_CTL_EVENT_MASK_INFO, &chip->spdif_pcm_ctl->id);
return 0;
}
static int snd_ymfpci_playback_4ch_open(struct snd_pcm_substream *substream)
{
struct snd_ymfpci *chip = snd_pcm_substream_chip(substream);
struct snd_pcm_runtime *runtime = substream->runtime;
struct snd_ymfpci_pcm *ypcm;
int err;
err = snd_ymfpci_playback_open_1(substream);
if (err < 0)
return err;
ypcm = runtime->private_data;
ypcm->output_front = 0;
ypcm->output_rear = 1;
ypcm->swap_rear = 0;
spin_lock_irq(&chip->reg_lock);
ymfpci_open_extension(chip);
chip->rear_opened++;
spin_unlock_irq(&chip->reg_lock);
return 0;
}
static int snd_ymfpci_capture_open(struct snd_pcm_substream *substream,
u32 capture_bank_number)
{
struct snd_ymfpci *chip = snd_pcm_substream_chip(substream);
struct snd_pcm_runtime *runtime = substream->runtime;
struct snd_ymfpci_pcm *ypcm;
int err;
runtime->hw = snd_ymfpci_capture;
/* FIXME? True value is 256/48 = 5.33333 ms */
err = snd_pcm_hw_constraint_minmax(runtime,
SNDRV_PCM_HW_PARAM_PERIOD_TIME,
5334, UINT_MAX);
if (err < 0)
return err;
err = snd_pcm_hw_rule_noresample(runtime, 48000);
if (err < 0)
return err;
ypcm = kzalloc(sizeof(*ypcm), GFP_KERNEL);
if (ypcm == NULL)
return -ENOMEM;
ypcm->chip = chip;
ypcm->type = capture_bank_number + CAPTURE_REC;
ypcm->substream = substream;
ypcm->capture_bank_number = capture_bank_number;
chip->capture_substream[capture_bank_number] = substream;
runtime->private_data = ypcm;
runtime->private_free = snd_ymfpci_pcm_free_substream;
snd_ymfpci_hw_start(chip);
return 0;
}
static int snd_ymfpci_capture_rec_open(struct snd_pcm_substream *substream)
{
return snd_ymfpci_capture_open(substream, 0);
}
static int snd_ymfpci_capture_ac97_open(struct snd_pcm_substream *substream)
{
return snd_ymfpci_capture_open(substream, 1);
}
static int snd_ymfpci_playback_close_1(struct snd_pcm_substream *substream)
{
return 0;
}
static int snd_ymfpci_playback_close(struct snd_pcm_substream *substream)
{
struct snd_ymfpci *chip = snd_pcm_substream_chip(substream);
struct snd_ymfpci_pcm *ypcm = substream->runtime->private_data;
spin_lock_irq(&chip->reg_lock);
if (ypcm->output_rear && chip->rear_opened > 0) {
chip->rear_opened--;
ymfpci_close_extension(chip);
}
spin_unlock_irq(&chip->reg_lock);
return snd_ymfpci_playback_close_1(substream);
}
static int snd_ymfpci_playback_spdif_close(struct snd_pcm_substream *substream)
{
struct snd_ymfpci *chip = snd_pcm_substream_chip(substream);
spin_lock_irq(&chip->reg_lock);
chip->spdif_opened = 0;
ymfpci_close_extension(chip);
snd_ymfpci_writew(chip, YDSXGR_SPDIFOUTCTRL,
snd_ymfpci_readw(chip, YDSXGR_SPDIFOUTCTRL) & ~2);
snd_ymfpci_writew(chip, YDSXGR_SPDIFOUTSTATUS, chip->spdif_bits);
spin_unlock_irq(&chip->reg_lock);
chip->spdif_pcm_ctl->vd[0].access |= SNDRV_CTL_ELEM_ACCESS_INACTIVE;
snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_VALUE |
SNDRV_CTL_EVENT_MASK_INFO, &chip->spdif_pcm_ctl->id);
return snd_ymfpci_playback_close_1(substream);
}
static int snd_ymfpci_playback_4ch_close(struct snd_pcm_substream *substream)
{
struct snd_ymfpci *chip = snd_pcm_substream_chip(substream);
spin_lock_irq(&chip->reg_lock);
if (chip->rear_opened > 0) {
chip->rear_opened--;
ymfpci_close_extension(chip);
}
spin_unlock_irq(&chip->reg_lock);
return snd_ymfpci_playback_close_1(substream);
}
static int snd_ymfpci_capture_close(struct snd_pcm_substream *substream)
{
struct snd_ymfpci *chip = snd_pcm_substream_chip(substream);
struct snd_pcm_runtime *runtime = substream->runtime;
struct snd_ymfpci_pcm *ypcm = runtime->private_data;
if (ypcm != NULL) {
chip->capture_substream[ypcm->capture_bank_number] = NULL;
snd_ymfpci_hw_stop(chip);
}
return 0;
}
static const struct snd_pcm_ops snd_ymfpci_playback_ops = {
.open = snd_ymfpci_playback_open,
.close = snd_ymfpci_playback_close,
.hw_params = snd_ymfpci_playback_hw_params,
.hw_free = snd_ymfpci_playback_hw_free,
.prepare = snd_ymfpci_playback_prepare,
.trigger = snd_ymfpci_playback_trigger,
.pointer = snd_ymfpci_playback_pointer,
};
static const struct snd_pcm_ops snd_ymfpci_capture_rec_ops = {
.open = snd_ymfpci_capture_rec_open,
.close = snd_ymfpci_capture_close,
.hw_free = snd_ymfpci_capture_hw_free,
.prepare = snd_ymfpci_capture_prepare,
.trigger = snd_ymfpci_capture_trigger,
.pointer = snd_ymfpci_capture_pointer,
};
int snd_ymfpci_pcm(struct snd_ymfpci *chip, int device)
{
struct snd_pcm *pcm;
int err;
err = snd_pcm_new(chip->card, "YMFPCI", device, 32, 1, &pcm);
if (err < 0)
return err;
pcm->private_data = chip;
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_ymfpci_playback_ops);
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_ymfpci_capture_rec_ops);
/* global setup */
pcm->info_flags = 0;
strcpy(pcm->name, "YMFPCI");
chip->pcm = pcm;
snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV,
&chip->pci->dev, 64*1024, 256*1024);
return snd_pcm_add_chmap_ctls(pcm, SNDRV_PCM_STREAM_PLAYBACK,
snd_pcm_std_chmaps, 2, 0, NULL);
}
static const struct snd_pcm_ops snd_ymfpci_capture_ac97_ops = {
.open = snd_ymfpci_capture_ac97_open,
.close = snd_ymfpci_capture_close,
.hw_free = snd_ymfpci_capture_hw_free,
.prepare = snd_ymfpci_capture_prepare,
.trigger = snd_ymfpci_capture_trigger,
.pointer = snd_ymfpci_capture_pointer,
};
int snd_ymfpci_pcm2(struct snd_ymfpci *chip, int device)
{
struct snd_pcm *pcm;
int err;
err = snd_pcm_new(chip->card, "YMFPCI - PCM2", device, 0, 1, &pcm);
if (err < 0)
return err;
pcm->private_data = chip;
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_ymfpci_capture_ac97_ops);
/* global setup */
pcm->info_flags = 0;
sprintf(pcm->name, "YMFPCI - %s",
chip->device_id == PCI_DEVICE_ID_YAMAHA_754 ? "Direct Recording" : "AC'97");
chip->pcm2 = pcm;
snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV,
&chip->pci->dev, 64*1024, 256*1024);
return 0;
}
static const struct snd_pcm_ops snd_ymfpci_playback_spdif_ops = {
.open = snd_ymfpci_playback_spdif_open,
.close = snd_ymfpci_playback_spdif_close,
.hw_params = snd_ymfpci_playback_hw_params,
.hw_free = snd_ymfpci_playback_hw_free,
.prepare = snd_ymfpci_playback_prepare,
.trigger = snd_ymfpci_playback_trigger,
.pointer = snd_ymfpci_playback_pointer,
};
int snd_ymfpci_pcm_spdif(struct snd_ymfpci *chip, int device)
{
struct snd_pcm *pcm;
int err;
err = snd_pcm_new(chip->card, "YMFPCI - IEC958", device, 1, 0, &pcm);
if (err < 0)
return err;
pcm->private_data = chip;
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_ymfpci_playback_spdif_ops);
/* global setup */
pcm->info_flags = 0;
strcpy(pcm->name, "YMFPCI - IEC958");
chip->pcm_spdif = pcm;
snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV,
&chip->pci->dev, 64*1024, 256*1024);
return 0;
}
static const struct snd_pcm_ops snd_ymfpci_playback_4ch_ops = {
.open = snd_ymfpci_playback_4ch_open,
.close = snd_ymfpci_playback_4ch_close,
.hw_params = snd_ymfpci_playback_hw_params,
.hw_free = snd_ymfpci_playback_hw_free,
.prepare = snd_ymfpci_playback_prepare,
.trigger = snd_ymfpci_playback_trigger,
.pointer = snd_ymfpci_playback_pointer,
};
static const struct snd_pcm_chmap_elem surround_map[] = {
{ .channels = 1,
.map = { SNDRV_CHMAP_MONO } },
{ .channels = 2,
.map = { SNDRV_CHMAP_RL, SNDRV_CHMAP_RR } },
{ }
};
int snd_ymfpci_pcm_4ch(struct snd_ymfpci *chip, int device)
{
struct snd_pcm *pcm;
int err;
err = snd_pcm_new(chip->card, "YMFPCI - Rear", device, 1, 0, &pcm);
if (err < 0)
return err;
pcm->private_data = chip;
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_ymfpci_playback_4ch_ops);
/* global setup */
pcm->info_flags = 0;
strcpy(pcm->name, "YMFPCI - Rear PCM");
chip->pcm_4ch = pcm;
snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV,
&chip->pci->dev, 64*1024, 256*1024);
return snd_pcm_add_chmap_ctls(pcm, SNDRV_PCM_STREAM_PLAYBACK,
surround_map, 2, 0, NULL);
}
static int snd_ymfpci_spdif_default_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 snd_ymfpci_spdif_default_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_ymfpci *chip = snd_kcontrol_chip(kcontrol);
spin_lock_irq(&chip->reg_lock);
ucontrol->value.iec958.status[0] = (chip->spdif_bits >> 0) & 0xff;
ucontrol->value.iec958.status[1] = (chip->spdif_bits >> 8) & 0xff;
ucontrol->value.iec958.status[3] = IEC958_AES3_CON_FS_48000;
spin_unlock_irq(&chip->reg_lock);
return 0;
}
static int snd_ymfpci_spdif_default_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_ymfpci *chip = snd_kcontrol_chip(kcontrol);
unsigned int val;
int change;
val = ((ucontrol->value.iec958.status[0] & 0x3e) << 0) |
(ucontrol->value.iec958.status[1] << 8);
spin_lock_irq(&chip->reg_lock);
change = chip->spdif_bits != val;
chip->spdif_bits = val;
if ((snd_ymfpci_readw(chip, YDSXGR_SPDIFOUTCTRL) & 1) && chip->pcm_spdif == NULL)
snd_ymfpci_writew(chip, YDSXGR_SPDIFOUTSTATUS, chip->spdif_bits);
spin_unlock_irq(&chip->reg_lock);
return change;
}
static const struct snd_kcontrol_new snd_ymfpci_spdif_default =
{
.iface = SNDRV_CTL_ELEM_IFACE_PCM,
.name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,DEFAULT),
.info = snd_ymfpci_spdif_default_info,
.get = snd_ymfpci_spdif_default_get,
.put = snd_ymfpci_spdif_default_put
};
static int snd_ymfpci_spdif_mask_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 snd_ymfpci_spdif_mask_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_ymfpci *chip = snd_kcontrol_chip(kcontrol);
spin_lock_irq(&chip->reg_lock);
ucontrol->value.iec958.status[0] = 0x3e;
ucontrol->value.iec958.status[1] = 0xff;
spin_unlock_irq(&chip->reg_lock);
return 0;
}
static const struct snd_kcontrol_new snd_ymfpci_spdif_mask =
{
.access = SNDRV_CTL_ELEM_ACCESS_READ,
.iface = SNDRV_CTL_ELEM_IFACE_PCM,
.name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,CON_MASK),
.info = snd_ymfpci_spdif_mask_info,
.get = snd_ymfpci_spdif_mask_get,
};
static int snd_ymfpci_spdif_stream_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 snd_ymfpci_spdif_stream_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_ymfpci *chip = snd_kcontrol_chip(kcontrol);
spin_lock_irq(&chip->reg_lock);
ucontrol->value.iec958.status[0] = (chip->spdif_pcm_bits >> 0) & 0xff;
ucontrol->value.iec958.status[1] = (chip->spdif_pcm_bits >> 8) & 0xff;
ucontrol->value.iec958.status[3] = IEC958_AES3_CON_FS_48000;
spin_unlock_irq(&chip->reg_lock);
return 0;
}
static int snd_ymfpci_spdif_stream_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_ymfpci *chip = snd_kcontrol_chip(kcontrol);
unsigned int val;
int change;
val = ((ucontrol->value.iec958.status[0] & 0x3e) << 0) |
(ucontrol->value.iec958.status[1] << 8);
spin_lock_irq(&chip->reg_lock);
change = chip->spdif_pcm_bits != val;
chip->spdif_pcm_bits = val;
if ((snd_ymfpci_readw(chip, YDSXGR_SPDIFOUTCTRL) & 2))
snd_ymfpci_writew(chip, YDSXGR_SPDIFOUTSTATUS, chip->spdif_pcm_bits);
spin_unlock_irq(&chip->reg_lock);
return change;
}
static const struct snd_kcontrol_new snd_ymfpci_spdif_stream =
{
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_INACTIVE,
.iface = SNDRV_CTL_ELEM_IFACE_PCM,
.name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,PCM_STREAM),
.info = snd_ymfpci_spdif_stream_info,
.get = snd_ymfpci_spdif_stream_get,
.put = snd_ymfpci_spdif_stream_put
};
static int snd_ymfpci_drec_source_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *info)
{
static const char *const texts[3] = {"AC'97", "IEC958", "ZV Port"};
return snd_ctl_enum_info(info, 1, 3, texts);
}
static int snd_ymfpci_drec_source_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *value)
{
struct snd_ymfpci *chip = snd_kcontrol_chip(kcontrol);
u16 reg;
spin_lock_irq(&chip->reg_lock);
reg = snd_ymfpci_readw(chip, YDSXGR_GLOBALCTRL);
spin_unlock_irq(&chip->reg_lock);
if (!(reg & 0x100))
value->value.enumerated.item[0] = 0;
else
value->value.enumerated.item[0] = 1 + ((reg & 0x200) != 0);
return 0;
}
static int snd_ymfpci_drec_source_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *value)
{
struct snd_ymfpci *chip = snd_kcontrol_chip(kcontrol);
u16 reg, old_reg;
spin_lock_irq(&chip->reg_lock);
old_reg = snd_ymfpci_readw(chip, YDSXGR_GLOBALCTRL);
if (value->value.enumerated.item[0] == 0)
reg = old_reg & ~0x100;
else
reg = (old_reg & ~0x300) | 0x100 | ((value->value.enumerated.item[0] == 2) << 9);
snd_ymfpci_writew(chip, YDSXGR_GLOBALCTRL, reg);
spin_unlock_irq(&chip->reg_lock);
return reg != old_reg;
}
static const struct snd_kcontrol_new snd_ymfpci_drec_source = {
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "Direct Recording Source",
.info = snd_ymfpci_drec_source_info,
.get = snd_ymfpci_drec_source_get,
.put = snd_ymfpci_drec_source_put
};
/*
* Mixer controls
*/
#define YMFPCI_SINGLE(xname, xindex, reg, shift) \
{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \
.info = snd_ymfpci_info_single, \
.get = snd_ymfpci_get_single, .put = snd_ymfpci_put_single, \
.private_value = ((reg) | ((shift) << 16)) }
#define snd_ymfpci_info_single snd_ctl_boolean_mono_info
static int snd_ymfpci_get_single(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_ymfpci *chip = snd_kcontrol_chip(kcontrol);
int reg = kcontrol->private_value & 0xffff;
unsigned int shift = (kcontrol->private_value >> 16) & 0xff;
unsigned int mask = 1;
switch (reg) {
case YDSXGR_SPDIFOUTCTRL: break;
case YDSXGR_SPDIFINCTRL: break;
default: return -EINVAL;
}
ucontrol->value.integer.value[0] =
(snd_ymfpci_readl(chip, reg) >> shift) & mask;
return 0;
}
static int snd_ymfpci_put_single(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_ymfpci *chip = snd_kcontrol_chip(kcontrol);
int reg = kcontrol->private_value & 0xffff;
unsigned int shift = (kcontrol->private_value >> 16) & 0xff;
unsigned int mask = 1;
int change;
unsigned int val, oval;
switch (reg) {
case YDSXGR_SPDIFOUTCTRL: break;
case YDSXGR_SPDIFINCTRL: break;
default: return -EINVAL;
}
val = (ucontrol->value.integer.value[0] & mask);
val <<= shift;
spin_lock_irq(&chip->reg_lock);
oval = snd_ymfpci_readl(chip, reg);
val = (oval & ~(mask << shift)) | val;
change = val != oval;
snd_ymfpci_writel(chip, reg, val);
spin_unlock_irq(&chip->reg_lock);
return change;
}
static const DECLARE_TLV_DB_LINEAR(db_scale_native, TLV_DB_GAIN_MUTE, 0);
#define YMFPCI_DOUBLE(xname, xindex, reg) \
{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_TLV_READ, \
.info = snd_ymfpci_info_double, \
.get = snd_ymfpci_get_double, .put = snd_ymfpci_put_double, \
.private_value = reg, \
.tlv = { .p = db_scale_native } }
static int snd_ymfpci_info_double(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
{
unsigned int reg = kcontrol->private_value;
if (reg < 0x80 || reg >= 0xc0)
return -EINVAL;
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
uinfo->count = 2;
uinfo->value.integer.min = 0;
uinfo->value.integer.max = 16383;
return 0;
}
static int snd_ymfpci_get_double(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
struct snd_ymfpci *chip = snd_kcontrol_chip(kcontrol);
unsigned int reg = kcontrol->private_value;
unsigned int shift_left = 0, shift_right = 16, mask = 16383;
unsigned int val;
if (reg < 0x80 || reg >= 0xc0)
return -EINVAL;
spin_lock_irq(&chip->reg_lock);
val = snd_ymfpci_readl(chip, reg);
spin_unlock_irq(&chip->reg_lock);
ucontrol->value.integer.value[0] = (val >> shift_left) & mask;
ucontrol->value.integer.value[1] = (val >> shift_right) & mask;
return 0;
}
static int snd_ymfpci_put_double(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
struct snd_ymfpci *chip = snd_kcontrol_chip(kcontrol);
unsigned int reg = kcontrol->private_value;
unsigned int shift_left = 0, shift_right = 16, mask = 16383;
int change;
unsigned int val1, val2, oval;
if (reg < 0x80 || reg >= 0xc0)
return -EINVAL;
val1 = ucontrol->value.integer.value[0] & mask;
val2 = ucontrol->value.integer.value[1] & mask;
val1 <<= shift_left;
val2 <<= shift_right;
spin_lock_irq(&chip->reg_lock);
oval = snd_ymfpci_readl(chip, reg);
val1 = (oval & ~((mask << shift_left) | (mask << shift_right))) | val1 | val2;
change = val1 != oval;
snd_ymfpci_writel(chip, reg, val1);
spin_unlock_irq(&chip->reg_lock);
return change;
}
static int snd_ymfpci_put_nativedacvol(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_ymfpci *chip = snd_kcontrol_chip(kcontrol);
unsigned int reg = YDSXGR_NATIVEDACOUTVOL;
unsigned int reg2 = YDSXGR_BUF441OUTVOL;
int change;
unsigned int value, oval;
value = ucontrol->value.integer.value[0] & 0x3fff;
value |= (ucontrol->value.integer.value[1] & 0x3fff) << 16;
spin_lock_irq(&chip->reg_lock);
oval = snd_ymfpci_readl(chip, reg);
change = value != oval;
snd_ymfpci_writel(chip, reg, value);
snd_ymfpci_writel(chip, reg2, value);
spin_unlock_irq(&chip->reg_lock);
return change;
}
/*
* 4ch duplication
*/
#define snd_ymfpci_info_dup4ch snd_ctl_boolean_mono_info
static int snd_ymfpci_get_dup4ch(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
struct snd_ymfpci *chip = snd_kcontrol_chip(kcontrol);
ucontrol->value.integer.value[0] = chip->mode_dup4ch;
return 0;
}
static int snd_ymfpci_put_dup4ch(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
struct snd_ymfpci *chip = snd_kcontrol_chip(kcontrol);
int change;
change = (ucontrol->value.integer.value[0] != chip->mode_dup4ch);
if (change)
chip->mode_dup4ch = !!ucontrol->value.integer.value[0];
return change;
}
static const struct snd_kcontrol_new snd_ymfpci_dup4ch = {
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "4ch Duplication",
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
.info = snd_ymfpci_info_dup4ch,
.get = snd_ymfpci_get_dup4ch,
.put = snd_ymfpci_put_dup4ch,
};
static const struct snd_kcontrol_new snd_ymfpci_controls[] = {
{
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "Wave Playback Volume",
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE |
SNDRV_CTL_ELEM_ACCESS_TLV_READ,
.info = snd_ymfpci_info_double,
.get = snd_ymfpci_get_double,
.put = snd_ymfpci_put_nativedacvol,
.private_value = YDSXGR_NATIVEDACOUTVOL,
.tlv = { .p = db_scale_native },
},
YMFPCI_DOUBLE("Wave Capture Volume", 0, YDSXGR_NATIVEDACLOOPVOL),
YMFPCI_DOUBLE("Digital Capture Volume", 0, YDSXGR_NATIVEDACINVOL),
YMFPCI_DOUBLE("Digital Capture Volume", 1, YDSXGR_NATIVEADCINVOL),
YMFPCI_DOUBLE("ADC Playback Volume", 0, YDSXGR_PRIADCOUTVOL),
YMFPCI_DOUBLE("ADC Capture Volume", 0, YDSXGR_PRIADCLOOPVOL),
YMFPCI_DOUBLE("ADC Playback Volume", 1, YDSXGR_SECADCOUTVOL),
YMFPCI_DOUBLE("ADC Capture Volume", 1, YDSXGR_SECADCLOOPVOL),
YMFPCI_DOUBLE("FM Legacy Playback Volume", 0, YDSXGR_LEGACYOUTVOL),
YMFPCI_DOUBLE(SNDRV_CTL_NAME_IEC958("AC97 ", PLAYBACK,VOLUME), 0, YDSXGR_ZVOUTVOL),
YMFPCI_DOUBLE(SNDRV_CTL_NAME_IEC958("", CAPTURE,VOLUME), 0, YDSXGR_ZVLOOPVOL),
YMFPCI_DOUBLE(SNDRV_CTL_NAME_IEC958("AC97 ",PLAYBACK,VOLUME), 1, YDSXGR_SPDIFOUTVOL),
YMFPCI_DOUBLE(SNDRV_CTL_NAME_IEC958("",CAPTURE,VOLUME), 1, YDSXGR_SPDIFLOOPVOL),
YMFPCI_SINGLE(SNDRV_CTL_NAME_IEC958("",PLAYBACK,SWITCH), 0, YDSXGR_SPDIFOUTCTRL, 0),
YMFPCI_SINGLE(SNDRV_CTL_NAME_IEC958("",CAPTURE,SWITCH), 0, YDSXGR_SPDIFINCTRL, 0),
YMFPCI_SINGLE(SNDRV_CTL_NAME_IEC958("Loop",NONE,NONE), 0, YDSXGR_SPDIFINCTRL, 4),
};
/*
* GPIO
*/
static int snd_ymfpci_get_gpio_out(struct snd_ymfpci *chip, int pin)
{
u16 reg, mode;
unsigned long flags;
spin_lock_irqsave(&chip->reg_lock, flags);
reg = snd_ymfpci_readw(chip, YDSXGR_GPIOFUNCENABLE);
reg &= ~(1 << (pin + 8));
reg |= (1 << pin);
snd_ymfpci_writew(chip, YDSXGR_GPIOFUNCENABLE, reg);
/* set the level mode for input line */
mode = snd_ymfpci_readw(chip, YDSXGR_GPIOTYPECONFIG);
mode &= ~(3 << (pin * 2));
snd_ymfpci_writew(chip, YDSXGR_GPIOTYPECONFIG, mode);
snd_ymfpci_writew(chip, YDSXGR_GPIOFUNCENABLE, reg | (1 << (pin + 8)));
mode = snd_ymfpci_readw(chip, YDSXGR_GPIOINSTATUS);
spin_unlock_irqrestore(&chip->reg_lock, flags);
return (mode >> pin) & 1;
}
static int snd_ymfpci_set_gpio_out(struct snd_ymfpci *chip, int pin, int enable)
{
u16 reg;
unsigned long flags;
spin_lock_irqsave(&chip->reg_lock, flags);
reg = snd_ymfpci_readw(chip, YDSXGR_GPIOFUNCENABLE);
reg &= ~(1 << pin);
reg &= ~(1 << (pin + 8));
snd_ymfpci_writew(chip, YDSXGR_GPIOFUNCENABLE, reg);
snd_ymfpci_writew(chip, YDSXGR_GPIOOUTCTRL, enable << pin);
snd_ymfpci_writew(chip, YDSXGR_GPIOFUNCENABLE, reg | (1 << (pin + 8)));
spin_unlock_irqrestore(&chip->reg_lock, flags);
return 0;
}
#define snd_ymfpci_gpio_sw_info snd_ctl_boolean_mono_info
static int snd_ymfpci_gpio_sw_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
struct snd_ymfpci *chip = snd_kcontrol_chip(kcontrol);
int pin = (int)kcontrol->private_value;
ucontrol->value.integer.value[0] = snd_ymfpci_get_gpio_out(chip, pin);
return 0;
}
static int snd_ymfpci_gpio_sw_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
struct snd_ymfpci *chip = snd_kcontrol_chip(kcontrol);
int pin = (int)kcontrol->private_value;
if (snd_ymfpci_get_gpio_out(chip, pin) != ucontrol->value.integer.value[0]) {
snd_ymfpci_set_gpio_out(chip, pin, !!ucontrol->value.integer.value[0]);
ucontrol->value.integer.value[0] = snd_ymfpci_get_gpio_out(chip, pin);
return 1;
}
return 0;
}
static const struct snd_kcontrol_new snd_ymfpci_rear_shared = {
.name = "Shared Rear/Line-In Switch",
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.info = snd_ymfpci_gpio_sw_info,
.get = snd_ymfpci_gpio_sw_get,
.put = snd_ymfpci_gpio_sw_put,
.private_value = 2,
};
/*
* PCM voice volume
*/
static int snd_ymfpci_pcm_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 = 0;
uinfo->value.integer.max = 0x8000;
return 0;
}
static int snd_ymfpci_pcm_vol_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_ymfpci *chip = snd_kcontrol_chip(kcontrol);
unsigned int subs = kcontrol->id.subdevice;
ucontrol->value.integer.value[0] = chip->pcm_mixer[subs].left;
ucontrol->value.integer.value[1] = chip->pcm_mixer[subs].right;
return 0;
}
static int snd_ymfpci_pcm_vol_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_ymfpci *chip = snd_kcontrol_chip(kcontrol);
unsigned int subs = kcontrol->id.subdevice;
struct snd_pcm_substream *substream;
unsigned long flags;
if (ucontrol->value.integer.value[0] != chip->pcm_mixer[subs].left ||
ucontrol->value.integer.value[1] != chip->pcm_mixer[subs].right) {
chip->pcm_mixer[subs].left = ucontrol->value.integer.value[0];
chip->pcm_mixer[subs].right = ucontrol->value.integer.value[1];
if (chip->pcm_mixer[subs].left > 0x8000)
chip->pcm_mixer[subs].left = 0x8000;
if (chip->pcm_mixer[subs].right > 0x8000)
chip->pcm_mixer[subs].right = 0x8000;
substream = (struct snd_pcm_substream *)kcontrol->private_value;
spin_lock_irqsave(&chip->voice_lock, flags);
if (substream->runtime && substream->runtime->private_data) {
struct snd_ymfpci_pcm *ypcm = substream->runtime->private_data;
if (!ypcm->use_441_slot)
ypcm->update_pcm_vol = 2;
}
spin_unlock_irqrestore(&chip->voice_lock, flags);
return 1;
}
return 0;
}
static const struct snd_kcontrol_new snd_ymfpci_pcm_volume = {
.iface = SNDRV_CTL_ELEM_IFACE_PCM,
.name = "PCM Playback Volume",
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE |
SNDRV_CTL_ELEM_ACCESS_INACTIVE,
.info = snd_ymfpci_pcm_vol_info,
.get = snd_ymfpci_pcm_vol_get,
.put = snd_ymfpci_pcm_vol_put,
};
/*
* Mixer routines
*/
static void snd_ymfpci_mixer_free_ac97_bus(struct snd_ac97_bus *bus)
{
struct snd_ymfpci *chip = bus->private_data;
chip->ac97_bus = NULL;
}
static void snd_ymfpci_mixer_free_ac97(struct snd_ac97 *ac97)
{
struct snd_ymfpci *chip = ac97->private_data;
chip->ac97 = NULL;
}
int snd_ymfpci_mixer(struct snd_ymfpci *chip, int rear_switch)
{
struct snd_ac97_template ac97;
struct snd_kcontrol *kctl;
struct snd_pcm_substream *substream;
unsigned int idx;
int err;
static const struct snd_ac97_bus_ops ops = {
.write = snd_ymfpci_codec_write,
.read = snd_ymfpci_codec_read,
};
err = snd_ac97_bus(chip->card, 0, &ops, chip, &chip->ac97_bus);
if (err < 0)
return err;
chip->ac97_bus->private_free = snd_ymfpci_mixer_free_ac97_bus;
chip->ac97_bus->no_vra = 1; /* YMFPCI doesn't need VRA */
memset(&ac97, 0, sizeof(ac97));
ac97.private_data = chip;
ac97.private_free = snd_ymfpci_mixer_free_ac97;
err = snd_ac97_mixer(chip->ac97_bus, &ac97, &chip->ac97);
if (err < 0)
return err;
/* to be sure */
snd_ac97_update_bits(chip->ac97, AC97_EXTENDED_STATUS,
AC97_EA_VRA|AC97_EA_VRM, 0);
for (idx = 0; idx < ARRAY_SIZE(snd_ymfpci_controls); idx++) {
err = snd_ctl_add(chip->card, snd_ctl_new1(&snd_ymfpci_controls[idx], chip));
if (err < 0)
return err;
}
if (chip->ac97->ext_id & AC97_EI_SDAC) {
kctl = snd_ctl_new1(&snd_ymfpci_dup4ch, chip);
err = snd_ctl_add(chip->card, kctl);
if (err < 0)
return err;
}
/* add S/PDIF control */
if (snd_BUG_ON(!chip->pcm_spdif))
return -ENXIO;
kctl = snd_ctl_new1(&snd_ymfpci_spdif_default, chip);
err = snd_ctl_add(chip->card, kctl);
if (err < 0)
return err;
kctl->id.device = chip->pcm_spdif->device;
kctl = snd_ctl_new1(&snd_ymfpci_spdif_mask, chip);
err = snd_ctl_add(chip->card, kctl);
if (err < 0)
return err;
kctl->id.device = chip->pcm_spdif->device;
kctl = snd_ctl_new1(&snd_ymfpci_spdif_stream, chip);
err = snd_ctl_add(chip->card, kctl);
if (err < 0)
return err;
kctl->id.device = chip->pcm_spdif->device;
chip->spdif_pcm_ctl = kctl;
/* direct recording source */
if (chip->device_id == PCI_DEVICE_ID_YAMAHA_754) {
kctl = snd_ctl_new1(&snd_ymfpci_drec_source, chip);
err = snd_ctl_add(chip->card, kctl);
if (err < 0)
return err;
}
/*
* shared rear/line-in
*/
if (rear_switch) {
err = snd_ctl_add(chip->card, snd_ctl_new1(&snd_ymfpci_rear_shared, chip));
if (err < 0)
return err;
}
/* per-voice volume */
substream = chip->pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream;
for (idx = 0; idx < 32; ++idx) {
kctl = snd_ctl_new1(&snd_ymfpci_pcm_volume, chip);
if (!kctl)
return -ENOMEM;
kctl->id.device = chip->pcm->device;
kctl->id.subdevice = idx;
kctl->private_value = (unsigned long)substream;
err = snd_ctl_add(chip->card, kctl);
if (err < 0)
return err;
chip->pcm_mixer[idx].left = 0x8000;
chip->pcm_mixer[idx].right = 0x8000;
chip->pcm_mixer[idx].ctl = kctl;
substream = substream->next;
}
return 0;
}
/*
* timer
*/
static int snd_ymfpci_timer_start(struct snd_timer *timer)
{
struct snd_ymfpci *chip;
unsigned long flags;
unsigned int count;
chip = snd_timer_chip(timer);
spin_lock_irqsave(&chip->reg_lock, flags);
if (timer->sticks > 1) {
chip->timer_ticks = timer->sticks;
count = timer->sticks - 1;
} else {
/*
* Divisor 1 is not allowed; fake it by using divisor 2 and
* counting two ticks for each interrupt.
*/
chip->timer_ticks = 2;
count = 2 - 1;
}
snd_ymfpci_writew(chip, YDSXGR_TIMERCOUNT, count);
snd_ymfpci_writeb(chip, YDSXGR_TIMERCTRL, 0x03);
spin_unlock_irqrestore(&chip->reg_lock, flags);
return 0;
}
static int snd_ymfpci_timer_stop(struct snd_timer *timer)
{
struct snd_ymfpci *chip;
unsigned long flags;
chip = snd_timer_chip(timer);
spin_lock_irqsave(&chip->reg_lock, flags);
snd_ymfpci_writeb(chip, YDSXGR_TIMERCTRL, 0x00);
spin_unlock_irqrestore(&chip->reg_lock, flags);
return 0;
}
static int snd_ymfpci_timer_precise_resolution(struct snd_timer *timer,
unsigned long *num, unsigned long *den)
{
*num = 1;
*den = 96000;
return 0;
}
static const struct snd_timer_hardware snd_ymfpci_timer_hw = {
.flags = SNDRV_TIMER_HW_AUTO,
.resolution = 10417, /* 1 / 96 kHz = 10.41666...us */
.ticks = 0x10000,
.start = snd_ymfpci_timer_start,
.stop = snd_ymfpci_timer_stop,
.precise_resolution = snd_ymfpci_timer_precise_resolution,
};
int snd_ymfpci_timer(struct snd_ymfpci *chip, int device)
{
struct snd_timer *timer = NULL;
struct snd_timer_id tid;
int err;
tid.dev_class = SNDRV_TIMER_CLASS_CARD;
tid.dev_sclass = SNDRV_TIMER_SCLASS_NONE;
tid.card = chip->card->number;
tid.device = device;
tid.subdevice = 0;
err = snd_timer_new(chip->card, "YMFPCI", &tid, &timer);
if (err >= 0) {
strcpy(timer->name, "YMFPCI timer");
timer->private_data = chip;
timer->hw = snd_ymfpci_timer_hw;
}
chip->timer = timer;
return err;
}
/*
* proc interface
*/
static void snd_ymfpci_proc_read(struct snd_info_entry *entry,
struct snd_info_buffer *buffer)
{
struct snd_ymfpci *chip = entry->private_data;
int i;
snd_iprintf(buffer, "YMFPCI\n\n");
for (i = 0; i <= YDSXGR_WORKBASE; i += 4)
snd_iprintf(buffer, "%04x: %04x\n", i, snd_ymfpci_readl(chip, i));
}
static int snd_ymfpci_proc_init(struct snd_card *card, struct snd_ymfpci *chip)
{
return snd_card_ro_proc_new(card, "ymfpci", chip, snd_ymfpci_proc_read);
}
/*
* initialization routines
*/
static void snd_ymfpci_aclink_reset(struct pci_dev * pci)
{
u8 cmd;
pci_read_config_byte(pci, PCIR_DSXG_CTRL, &cmd);
#if 0 // force to reset
if (cmd & 0x03) {
#endif
pci_write_config_byte(pci, PCIR_DSXG_CTRL, cmd & 0xfc);
pci_write_config_byte(pci, PCIR_DSXG_CTRL, cmd | 0x03);
pci_write_config_byte(pci, PCIR_DSXG_CTRL, cmd & 0xfc);
pci_write_config_word(pci, PCIR_DSXG_PWRCTRL1, 0);
pci_write_config_word(pci, PCIR_DSXG_PWRCTRL2, 0);
#if 0
}
#endif
}
static void snd_ymfpci_enable_dsp(struct snd_ymfpci *chip)
{
snd_ymfpci_writel(chip, YDSXGR_CONFIG, 0x00000001);
}
static void snd_ymfpci_disable_dsp(struct snd_ymfpci *chip)
{
u32 val;
int timeout = 1000;
val = snd_ymfpci_readl(chip, YDSXGR_CONFIG);
if (val)
snd_ymfpci_writel(chip, YDSXGR_CONFIG, 0x00000000);
while (timeout-- > 0) {
val = snd_ymfpci_readl(chip, YDSXGR_STATUS);
if ((val & 0x00000002) == 0)
break;
}
}
static int snd_ymfpci_request_firmware(struct snd_ymfpci *chip)
{
int err, is_1e;
const char *name;
err = request_firmware(&chip->dsp_microcode, "yamaha/ds1_dsp.fw",
&chip->pci->dev);
if (err >= 0) {
if (chip->dsp_microcode->size != YDSXG_DSPLENGTH) {
dev_err(chip->card->dev,
"DSP microcode has wrong size\n");
err = -EINVAL;
}
}
if (err < 0)
return err;
is_1e = chip->device_id == PCI_DEVICE_ID_YAMAHA_724F ||
chip->device_id == PCI_DEVICE_ID_YAMAHA_740C ||
chip->device_id == PCI_DEVICE_ID_YAMAHA_744 ||
chip->device_id == PCI_DEVICE_ID_YAMAHA_754;
name = is_1e ? "yamaha/ds1e_ctrl.fw" : "yamaha/ds1_ctrl.fw";
err = request_firmware(&chip->controller_microcode, name,
&chip->pci->dev);
if (err >= 0) {
if (chip->controller_microcode->size != YDSXG_CTRLLENGTH) {
dev_err(chip->card->dev,
"controller microcode has wrong size\n");
err = -EINVAL;
}
}
if (err < 0)
return err;
return 0;
}
MODULE_FIRMWARE("yamaha/ds1_dsp.fw");
MODULE_FIRMWARE("yamaha/ds1_ctrl.fw");
MODULE_FIRMWARE("yamaha/ds1e_ctrl.fw");
static void snd_ymfpci_download_image(struct snd_ymfpci *chip)
{
int i;
u16 ctrl;
const __le32 *inst;
snd_ymfpci_writel(chip, YDSXGR_NATIVEDACOUTVOL, 0x00000000);
snd_ymfpci_disable_dsp(chip);
snd_ymfpci_writel(chip, YDSXGR_MODE, 0x00010000);
snd_ymfpci_writel(chip, YDSXGR_MODE, 0x00000000);
snd_ymfpci_writel(chip, YDSXGR_MAPOFREC, 0x00000000);
snd_ymfpci_writel(chip, YDSXGR_MAPOFEFFECT, 0x00000000);
snd_ymfpci_writel(chip, YDSXGR_PLAYCTRLBASE, 0x00000000);
snd_ymfpci_writel(chip, YDSXGR_RECCTRLBASE, 0x00000000);
snd_ymfpci_writel(chip, YDSXGR_EFFCTRLBASE, 0x00000000);
ctrl = snd_ymfpci_readw(chip, YDSXGR_GLOBALCTRL);
snd_ymfpci_writew(chip, YDSXGR_GLOBALCTRL, ctrl & ~0x0007);
/* setup DSP instruction code */
inst = (const __le32 *)chip->dsp_microcode->data;
for (i = 0; i < YDSXG_DSPLENGTH / 4; i++)
snd_ymfpci_writel(chip, YDSXGR_DSPINSTRAM + (i << 2),
le32_to_cpu(inst[i]));
/* setup control instruction code */
inst = (const __le32 *)chip->controller_microcode->data;
for (i = 0; i < YDSXG_CTRLLENGTH / 4; i++)
snd_ymfpci_writel(chip, YDSXGR_CTRLINSTRAM + (i << 2),
le32_to_cpu(inst[i]));
snd_ymfpci_enable_dsp(chip);
}
static int snd_ymfpci_memalloc(struct snd_ymfpci *chip)
{
long size, playback_ctrl_size;
int voice, bank, reg;
u8 *ptr;
dma_addr_t ptr_addr;
playback_ctrl_size = 4 + 4 * YDSXG_PLAYBACK_VOICES;
chip->bank_size_playback = snd_ymfpci_readl(chip, YDSXGR_PLAYCTRLSIZE) << 2;
chip->bank_size_capture = snd_ymfpci_readl(chip, YDSXGR_RECCTRLSIZE) << 2;
chip->bank_size_effect = snd_ymfpci_readl(chip, YDSXGR_EFFCTRLSIZE) << 2;
chip->work_size = YDSXG_DEFAULT_WORK_SIZE;
size = ALIGN(playback_ctrl_size, 0x100) +
ALIGN(chip->bank_size_playback * 2 * YDSXG_PLAYBACK_VOICES, 0x100) +
ALIGN(chip->bank_size_capture * 2 * YDSXG_CAPTURE_VOICES, 0x100) +
ALIGN(chip->bank_size_effect * 2 * YDSXG_EFFECT_VOICES, 0x100) +
chip->work_size;
/* work_ptr must be aligned to 256 bytes, but it's already
covered with the kernel page allocation mechanism */
chip->work_ptr = snd_devm_alloc_pages(&chip->pci->dev,
SNDRV_DMA_TYPE_DEV, size);
if (!chip->work_ptr)
return -ENOMEM;
ptr = chip->work_ptr->area;
ptr_addr = chip->work_ptr->addr;
memset(ptr, 0, size); /* for sure */
chip->bank_base_playback = ptr;
chip->bank_base_playback_addr = ptr_addr;
chip->ctrl_playback = (__le32 *)ptr;
chip->ctrl_playback[0] = cpu_to_le32(YDSXG_PLAYBACK_VOICES);
ptr += ALIGN(playback_ctrl_size, 0x100);
ptr_addr += ALIGN(playback_ctrl_size, 0x100);
for (voice = 0; voice < YDSXG_PLAYBACK_VOICES; voice++) {
chip->voices[voice].number = voice;
chip->voices[voice].bank = (struct snd_ymfpci_playback_bank *)ptr;
chip->voices[voice].bank_addr = ptr_addr;
for (bank = 0; bank < 2; bank++) {
chip->bank_playback[voice][bank] = (struct snd_ymfpci_playback_bank *)ptr;
ptr += chip->bank_size_playback;
ptr_addr += chip->bank_size_playback;
}
}
ptr = (char *)ALIGN((unsigned long)ptr, 0x100);
ptr_addr = ALIGN(ptr_addr, 0x100);
chip->bank_base_capture = ptr;
chip->bank_base_capture_addr = ptr_addr;
for (voice = 0; voice < YDSXG_CAPTURE_VOICES; voice++)
for (bank = 0; bank < 2; bank++) {
chip->bank_capture[voice][bank] = (struct snd_ymfpci_capture_bank *)ptr;
ptr += chip->bank_size_capture;
ptr_addr += chip->bank_size_capture;
}
ptr = (char *)ALIGN((unsigned long)ptr, 0x100);
ptr_addr = ALIGN(ptr_addr, 0x100);
chip->bank_base_effect = ptr;
chip->bank_base_effect_addr = ptr_addr;
for (voice = 0; voice < YDSXG_EFFECT_VOICES; voice++)
for (bank = 0; bank < 2; bank++) {
chip->bank_effect[voice][bank] = (struct snd_ymfpci_effect_bank *)ptr;
ptr += chip->bank_size_effect;
ptr_addr += chip->bank_size_effect;
}
ptr = (char *)ALIGN((unsigned long)ptr, 0x100);
ptr_addr = ALIGN(ptr_addr, 0x100);
chip->work_base = ptr;
chip->work_base_addr = ptr_addr;
snd_BUG_ON(ptr + chip->work_size !=
chip->work_ptr->area + chip->work_ptr->bytes);
snd_ymfpci_writel(chip, YDSXGR_PLAYCTRLBASE, chip->bank_base_playback_addr);
snd_ymfpci_writel(chip, YDSXGR_RECCTRLBASE, chip->bank_base_capture_addr);
snd_ymfpci_writel(chip, YDSXGR_EFFCTRLBASE, chip->bank_base_effect_addr);
snd_ymfpci_writel(chip, YDSXGR_WORKBASE, chip->work_base_addr);
snd_ymfpci_writel(chip, YDSXGR_WORKSIZE, chip->work_size >> 2);
/* S/PDIF output initialization */
chip->spdif_bits = chip->spdif_pcm_bits = SNDRV_PCM_DEFAULT_CON_SPDIF & 0xffff;
snd_ymfpci_writew(chip, YDSXGR_SPDIFOUTCTRL, 0);
snd_ymfpci_writew(chip, YDSXGR_SPDIFOUTSTATUS, chip->spdif_bits);
/* S/PDIF input initialization */
snd_ymfpci_writew(chip, YDSXGR_SPDIFINCTRL, 0);
/* digital mixer setup */
for (reg = 0x80; reg < 0xc0; reg += 4)
snd_ymfpci_writel(chip, reg, 0);
snd_ymfpci_writel(chip, YDSXGR_NATIVEDACOUTVOL, 0x3fff3fff);
snd_ymfpci_writel(chip, YDSXGR_BUF441OUTVOL, 0x3fff3fff);
snd_ymfpci_writel(chip, YDSXGR_ZVOUTVOL, 0x3fff3fff);
snd_ymfpci_writel(chip, YDSXGR_SPDIFOUTVOL, 0x3fff3fff);
snd_ymfpci_writel(chip, YDSXGR_NATIVEADCINVOL, 0x3fff3fff);
snd_ymfpci_writel(chip, YDSXGR_NATIVEDACINVOL, 0x3fff3fff);
snd_ymfpci_writel(chip, YDSXGR_PRIADCLOOPVOL, 0x3fff3fff);
snd_ymfpci_writel(chip, YDSXGR_LEGACYOUTVOL, 0x3fff3fff);
return 0;
}
static void snd_ymfpci_free(struct snd_card *card)
{
struct snd_ymfpci *chip = card->private_data;
u16 ctrl;
snd_ymfpci_writel(chip, YDSXGR_NATIVEDACOUTVOL, 0);
snd_ymfpci_writel(chip, YDSXGR_BUF441OUTVOL, 0);
snd_ymfpci_writel(chip, YDSXGR_LEGACYOUTVOL, 0);
snd_ymfpci_writel(chip, YDSXGR_STATUS, ~0);
snd_ymfpci_disable_dsp(chip);
snd_ymfpci_writel(chip, YDSXGR_PLAYCTRLBASE, 0);
snd_ymfpci_writel(chip, YDSXGR_RECCTRLBASE, 0);
snd_ymfpci_writel(chip, YDSXGR_EFFCTRLBASE, 0);
snd_ymfpci_writel(chip, YDSXGR_WORKBASE, 0);
snd_ymfpci_writel(chip, YDSXGR_WORKSIZE, 0);
ctrl = snd_ymfpci_readw(chip, YDSXGR_GLOBALCTRL);
snd_ymfpci_writew(chip, YDSXGR_GLOBALCTRL, ctrl & ~0x0007);
snd_ymfpci_ac3_done(chip);
snd_ymfpci_free_gameport(chip);
pci_write_config_word(chip->pci, 0x40, chip->old_legacy_ctrl);
release_firmware(chip->dsp_microcode);
release_firmware(chip->controller_microcode);
}
static int snd_ymfpci_suspend(struct device *dev)
{
struct snd_card *card = dev_get_drvdata(dev);
struct snd_ymfpci *chip = card->private_data;
unsigned int i, legacy_reg_count = DSXG_PCI_NUM_SAVED_LEGACY_REGS;
if (chip->pci->device >= 0x0010) /* YMF 744/754 */
legacy_reg_count = DSXG_PCI_NUM_SAVED_REGS;
snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
snd_ac97_suspend(chip->ac97);
for (i = 0; i < YDSXGR_NUM_SAVED_REGS; i++)
chip->saved_regs[i] = snd_ymfpci_readl(chip, saved_regs_index[i]);
chip->saved_ydsxgr_mode = snd_ymfpci_readl(chip, YDSXGR_MODE);
for (i = 0; i < legacy_reg_count; i++)
pci_read_config_word(chip->pci, pci_saved_regs_index[i],
chip->saved_dsxg_pci_regs + i);
snd_ymfpci_writel(chip, YDSXGR_NATIVEDACOUTVOL, 0);
snd_ymfpci_writel(chip, YDSXGR_BUF441OUTVOL, 0);
snd_ymfpci_disable_dsp(chip);
return 0;
}
static int snd_ymfpci_resume(struct device *dev)
{
struct pci_dev *pci = to_pci_dev(dev);
struct snd_card *card = dev_get_drvdata(dev);
struct snd_ymfpci *chip = card->private_data;
unsigned int i, legacy_reg_count = DSXG_PCI_NUM_SAVED_LEGACY_REGS;
if (chip->pci->device >= 0x0010) /* YMF 744/754 */
legacy_reg_count = DSXG_PCI_NUM_SAVED_REGS;
snd_ymfpci_aclink_reset(pci);
snd_ymfpci_codec_ready(chip, 0);
snd_ymfpci_download_image(chip);
udelay(100);
for (i = 0; i < YDSXGR_NUM_SAVED_REGS; i++)
snd_ymfpci_writel(chip, saved_regs_index[i], chip->saved_regs[i]);
snd_ac97_resume(chip->ac97);
for (i = 0; i < legacy_reg_count; i++)
pci_write_config_word(chip->pci, pci_saved_regs_index[i],
chip->saved_dsxg_pci_regs[i]);
/* start hw again */
if (chip->start_count > 0) {
spin_lock_irq(&chip->reg_lock);
snd_ymfpci_writel(chip, YDSXGR_MODE, chip->saved_ydsxgr_mode);
chip->active_bank = snd_ymfpci_readl(chip, YDSXGR_CTRLSELECT);
spin_unlock_irq(&chip->reg_lock);
}
snd_power_change_state(card, SNDRV_CTL_POWER_D0);
return 0;
}
DEFINE_SIMPLE_DEV_PM_OPS(snd_ymfpci_pm, snd_ymfpci_suspend, snd_ymfpci_resume);
int snd_ymfpci_create(struct snd_card *card,
struct pci_dev *pci,
unsigned short old_legacy_ctrl)
{
struct snd_ymfpci *chip = card->private_data;
int err;
/* enable PCI device */
err = pcim_enable_device(pci);
if (err < 0)
return err;
chip->old_legacy_ctrl = old_legacy_ctrl;
spin_lock_init(&chip->reg_lock);
spin_lock_init(&chip->voice_lock);
init_waitqueue_head(&chip->interrupt_sleep);
atomic_set(&chip->interrupt_sleep_count, 0);
chip->card = card;
chip->pci = pci;
chip->irq = -1;
chip->device_id = pci->device;
chip->rev = pci->revision;
err = pci_request_regions(pci, "YMFPCI");
if (err < 0)
return err;
chip->reg_area_phys = pci_resource_start(pci, 0);
chip->reg_area_virt = devm_ioremap(&pci->dev, chip->reg_area_phys, 0x8000);
if (!chip->reg_area_virt) {
dev_err(chip->card->dev,
"unable to grab memory region 0x%lx-0x%lx\n",
chip->reg_area_phys, chip->reg_area_phys + 0x8000 - 1);
return -EBUSY;
}
pci_set_master(pci);
chip->src441_used = -1;
if (devm_request_irq(&pci->dev, pci->irq, snd_ymfpci_interrupt, IRQF_SHARED,
KBUILD_MODNAME, chip)) {
dev_err(chip->card->dev, "unable to grab IRQ %d\n", pci->irq);
return -EBUSY;
}
chip->irq = pci->irq;
card->sync_irq = chip->irq;
card->private_free = snd_ymfpci_free;
snd_ymfpci_aclink_reset(pci);
if (snd_ymfpci_codec_ready(chip, 0) < 0)
return -EIO;
err = snd_ymfpci_request_firmware(chip);
if (err < 0) {
dev_err(chip->card->dev, "firmware request failed: %d\n", err);
return err;
}
snd_ymfpci_download_image(chip);
udelay(100); /* seems we need a delay after downloading image.. */
if (snd_ymfpci_memalloc(chip) < 0)
return -EIO;
err = snd_ymfpci_ac3_init(chip);
if (err < 0)
return err;
snd_ymfpci_proc_init(card, chip);
return 0;
}