From: Takashi Iwai <tiwai@suse.de>
To: alsa-devel@lists.sourceforge.net
Subject: Tring to support RME32 full-duplex
Date: Fri, 18 Jun 2004 17:17:05 +0200 [thread overview]
Message-ID: <s5hk6y4opzi.wl@alsa2.suse.de> (raw)
[-- Attachment #1: Type: text/plain, Size: 241 bytes --]
Hi,
(I believe this interests mainly Martin.)
The patch below adds (hopefully) the support the full-duplex on
RME32. You'll need to applay the mmap-iomem patch before this one.
If you have an RME32, please test and debug it ;)
Takashi
[-- Attachment #2: Type: text/plain, Size: 35454 bytes --]
Index: alsa-kernel/pci/rme32.c
===================================================================
RCS file: /suse/tiwai/cvs/alsa/alsa-kernel/pci/rme32.c,v
retrieving revision 1.36
diff -u -r1.36 rme32.c
--- alsa-kernel/pci/rme32.c 18 Jun 2004 13:57:32 -0000 1.36
+++ alsa-kernel/pci/rme32.c 18 Jun 2004 15:10:48 -0000
@@ -52,6 +52,19 @@
* patch would be welcome!
*
* ****************************************************************************
+ *
+ * "The story after the long seeking" -- tiwai
+ *
+ * Ok, the situation regarding the full duplex is now improved a bit.
+ * In the fullduplex mode (given by the module parameter), the hardware buffer
+ * is split to halves for read and write directions at the DMA pointer.
+ * That is, the half above the current DMA pointer is used for write, and
+ * the half below is used for read. To mangle this strange behavior, an
+ * software intermediate buffer is introduced. This is, of course, not good
+ * from the viewpoint of the data transfer efficiency. However, this allows
+ * you to use arbitrary buffer sizes, instead of the fixed I/O buffer size.
+ *
+ * ****************************************************************************
*/
@@ -68,6 +81,7 @@
#include <sound/control.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
+#include <sound/pcm-indirect.h>
#include <sound/asoundef.h>
#include <sound/initval.h>
@@ -76,6 +90,7 @@
static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-MAX */
static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* ID for this card */
static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP; /* Enable this card */
+static int fullduplex[SNDRV_CARDS]; // = {[0 ... (SNDRV_CARDS - 1)] = 1};
static int boot_devs;
module_param_array(index, int, boot_devs, 0444);
@@ -87,6 +102,8 @@
module_param_array(enable, bool, boot_devs, 0444);
MODULE_PARM_DESC(enable, "Enable RME Digi32 soundcard.");
MODULE_PARM_SYNTAX(enable, SNDRV_ENABLE_DESC);
+module_param_array(fullduplex, bool, boot_devs, 0444);
+MODULE_PARM_DESC(fullduplex, "Support full-duplex mode.");
MODULE_AUTHOR("Martin Langer <martin-langer@gmx.de>");
MODULE_DESCRIPTION("RME Digi32, Digi32/8, Digi32 PRO");
MODULE_LICENSE("GPL");
@@ -168,6 +185,9 @@
/* Block sizes in bytes */
#define RME32_BLOCK_SIZE 8192
+/* Software intermediate buffer (max) size */
+#define RME32_MID_BUFFER_SIZE (1024*1024)
+
/* Hardware revisions */
#define RME32_32_REVISION 192
#define RME32_328_REVISION_OLD 100
@@ -213,6 +233,12 @@
size_t playback_periodsize; /* in bytes, zero if not used */
size_t capture_periodsize; /* in bytes, zero if not used */
+ unsigned int fullduplex_mode;
+ int running;
+
+ snd_pcm_indirect_t playback_pcm;
+ snd_pcm_indirect_t capture_pcm;
+
snd_card_t *card;
snd_pcm_t *spdif_pcm;
snd_pcm_t *adat_pcm;
@@ -239,33 +265,16 @@
static int snd_rme32_capture_prepare(snd_pcm_substream_t * substream);
-static int
-snd_rme32_playback_trigger(snd_pcm_substream_t * substream, int cmd);
-
-static int
-snd_rme32_capture_trigger(snd_pcm_substream_t * substream, int cmd);
-
-static snd_pcm_uframes_t
-snd_rme32_playback_pointer(snd_pcm_substream_t * substream);
-
-static snd_pcm_uframes_t
-snd_rme32_capture_pointer(snd_pcm_substream_t * substream);
+static int snd_rme32_pcm_trigger(snd_pcm_substream_t * substream, int cmd);
static void snd_rme32_proc_init(rme32_t * rme32);
static int snd_rme32_create_switches(snd_card_t * card, rme32_t * rme32);
-static inline unsigned int snd_rme32_playback_ptr(rme32_t * rme32)
-{
-
- return (readl(rme32->iobase + RME32_IO_GET_POS)
- & RME32_RCR_AUDIO_ADDR_MASK) >> rme32->playback_frlog;
-}
-
-static inline unsigned int snd_rme32_capture_ptr(rme32_t * rme32)
+static inline unsigned int snd_rme32_pcm_byteptr(rme32_t * rme32)
{
return (readl(rme32->iobase + RME32_IO_GET_POS)
- & RME32_RCR_AUDIO_ADDR_MASK) >> rme32->capture_frlog;
+ & RME32_RCR_AUDIO_ADDR_MASK);
}
static int snd_rme32_ratecode(int rate)
@@ -281,6 +290,7 @@
return 0;
}
+/* silence callback for halfduplex mode */
static int snd_rme32_playback_silence(snd_pcm_substream_t * substream, int channel, /* not used (interleaved data) */
snd_pcm_uframes_t pos,
snd_pcm_uframes_t count)
@@ -292,6 +302,7 @@
return 0;
}
+/* copy callback for halfduplex mode */
static int snd_rme32_playback_copy(snd_pcm_substream_t * substream, int channel, /* not used (interleaved data) */
snd_pcm_uframes_t pos,
void *src, snd_pcm_uframes_t count)
@@ -305,6 +316,7 @@
return 0;
}
+/* copy callback for halfduplex mode */
static int snd_rme32_capture_copy(snd_pcm_substream_t * substream, int channel, /* not used (interleaved data) */
snd_pcm_uframes_t pos,
void *dst, snd_pcm_uframes_t count)
@@ -320,14 +332,15 @@
}
/*
- * Digital output capabilites (S/PDIF)
+ * SPDIF I/O capabilites (half-duplex mode)
*/
-static snd_pcm_hardware_t snd_rme32_playback_spdif_info = {
+static snd_pcm_hardware_t snd_rme32_spdif_info = {
.info = (SNDRV_PCM_INFO_MMAP |
SNDRV_PCM_INFO_MMAP_VALID |
SNDRV_PCM_INFO_MMAP_IOMEM |
SNDRV_PCM_INFO_INTERLEAVED |
- SNDRV_PCM_INFO_PAUSE),
+ 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 |
@@ -346,41 +359,16 @@
};
/*
- * Digital input capabilites (S/PDIF)
+ * ADAT I/O capabilites (half-duplex mode)
*/
-static snd_pcm_hardware_t snd_rme32_capture_spdif_info = {
- .info = (SNDRV_PCM_INFO_MMAP |
- SNDRV_PCM_INFO_MMAP_VALID |
- SNDRV_PCM_INFO_MMAP_IOMEM |
- SNDRV_PCM_INFO_INTERLEAVED |
- SNDRV_PCM_INFO_PAUSE),
- .formats = (SNDRV_PCM_FMTBIT_S16_LE |
- SNDRV_PCM_FMTBIT_S32_LE),
- .rates = (SNDRV_PCM_RATE_32000 |
- SNDRV_PCM_RATE_44100 |
- SNDRV_PCM_RATE_48000),
- .rate_min = 32000,
- .rate_max = 48000,
- .channels_min = 2,
- .channels_max = 2,
- .buffer_bytes_max = RME32_BUFFER_SIZE,
- .period_bytes_min = RME32_BLOCK_SIZE,
- .period_bytes_max = RME32_BLOCK_SIZE,
- .periods_min = RME32_BUFFER_SIZE / RME32_BLOCK_SIZE,
- .periods_max = RME32_BUFFER_SIZE / RME32_BLOCK_SIZE,
- .fifo_size = 0,
-};
-
-/*
- * Digital output capabilites (ADAT)
- */
-static snd_pcm_hardware_t snd_rme32_playback_adat_info =
+static snd_pcm_hardware_t snd_rme32_adat_info =
{
.info = (SNDRV_PCM_INFO_MMAP |
SNDRV_PCM_INFO_MMAP_VALID |
SNDRV_PCM_INFO_MMAP_IOMEM |
SNDRV_PCM_INFO_INTERLEAVED |
- SNDRV_PCM_INFO_PAUSE),
+ SNDRV_PCM_INFO_PAUSE |
+ SNDRV_PCM_INFO_SYNC_START),
.formats= SNDRV_PCM_FMTBIT_S16_LE,
.rates = (SNDRV_PCM_RATE_44100 |
SNDRV_PCM_RATE_48000),
@@ -397,28 +385,54 @@
};
/*
- * Digital input capabilites (ADAT)
+ * SPDIF I/O capabilites (full-duplex mode)
+ */
+static snd_pcm_hardware_t snd_rme32_spdif_fd_info = {
+ .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),
+ .rate_min = 32000,
+ .rate_max = 48000,
+ .channels_min = 2,
+ .channels_max = 2,
+ .buffer_bytes_max = RME32_MID_BUFFER_SIZE,
+ .period_bytes_min = RME32_BLOCK_SIZE,
+ .period_bytes_max = RME32_BLOCK_SIZE,
+ .periods_min = 2,
+ .periods_max = RME32_MID_BUFFER_SIZE / RME32_BLOCK_SIZE,
+ .fifo_size = 0,
+};
+
+/*
+ * ADAT I/O capabilites (full-duplex mode)
*/
-static snd_pcm_hardware_t snd_rme32_capture_adat_info =
+static snd_pcm_hardware_t snd_rme32_adat_fd_info =
{
.info = (SNDRV_PCM_INFO_MMAP |
SNDRV_PCM_INFO_MMAP_VALID |
- SNDRV_PCM_INFO_MMAP_IOMEM |
SNDRV_PCM_INFO_INTERLEAVED |
- SNDRV_PCM_INFO_PAUSE),
- .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ SNDRV_PCM_INFO_PAUSE |
+ SNDRV_PCM_INFO_SYNC_START),
+ .formats= SNDRV_PCM_FMTBIT_S16_LE,
.rates = (SNDRV_PCM_RATE_44100 |
SNDRV_PCM_RATE_48000),
.rate_min = 44100,
.rate_max = 48000,
- .channels_min = 8,
+ .channels_min = 8,
.channels_max = 8,
- .buffer_bytes_max = RME32_BUFFER_SIZE,
+ .buffer_bytes_max = RME32_MID_BUFFER_SIZE,
.period_bytes_min = RME32_BLOCK_SIZE,
.period_bytes_max = RME32_BLOCK_SIZE,
- .periods_min = RME32_BUFFER_SIZE / RME32_BLOCK_SIZE,
- .periods_max = RME32_BUFFER_SIZE / RME32_BLOCK_SIZE,
- .fifo_size = 0,
+ .periods_min = 2,
+ .periods_max = RME32_MID_BUFFER_SIZE / RME32_BLOCK_SIZE,
+ .fifo_size = 0,
};
static void snd_rme32_reset_dac(rme32_t *rme32)
@@ -680,9 +694,15 @@
rme32_t *rme32 = _snd_pcm_substream_chip(substream);
snd_pcm_runtime_t *runtime = substream->runtime;
- runtime->dma_area = (void *)(rme32->iobase + RME32_IO_DATA_BUFFER);
- runtime->dma_addr = rme32->port + RME32_IO_DATA_BUFFER;
- runtime->dma_bytes = RME32_BUFFER_SIZE;
+ if (rme32->fullduplex_mode) {
+ err = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(params));
+ if (err < 0)
+ return err;
+ } else {
+ runtime->dma_area = (void *)(rme32->iobase + RME32_IO_DATA_BUFFER);
+ runtime->dma_addr = rme32->port + RME32_IO_DATA_BUFFER;
+ runtime->dma_bytes = RME32_BUFFER_SIZE;
+ }
spin_lock_irq(&rme32->lock);
if ((rme32->rcreg & RME32_RCR_KMODE) &&
@@ -730,9 +750,15 @@
rme32_t *rme32 = _snd_pcm_substream_chip(substream);
snd_pcm_runtime_t *runtime = substream->runtime;
- runtime->dma_area = (void *)(rme32->iobase + RME32_IO_DATA_BUFFER);
- runtime->dma_addr = rme32->port + RME32_IO_DATA_BUFFER;
- runtime->dma_bytes = RME32_BUFFER_SIZE;
+ if (rme32->fullduplex_mode) {
+ err = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(params));
+ if (err < 0)
+ return err;
+ } else {
+ runtime->dma_area = (void *)rme32->iobase + RME32_IO_DATA_BUFFER;
+ runtime->dma_addr = rme32->port + RME32_IO_DATA_BUFFER;
+ runtime->dma_bytes = RME32_BUFFER_SIZE;
+ }
spin_lock_irqsave(&rme32->lock, flags);
/* enable AutoSync for record-preparing */
@@ -777,17 +803,15 @@
return 0;
}
-static void snd_rme32_playback_start(rme32_t * rme32, int from_pause)
+static int snd_rme32_pcm_hw_free(snd_pcm_substream_t * substream)
{
- if (!from_pause) {
- writel(0, rme32->iobase + RME32_IO_RESET_POS);
- }
-
- rme32->wcreg |= RME32_WCR_START;
- writel(rme32->wcreg, rme32->iobase + RME32_IO_CONTROL_REGISTER);
+ rme32_t *rme32 = _snd_pcm_substream_chip(substream);
+ if (! rme32->fullduplex_mode)
+ return 0;
+ return snd_pcm_lib_free_pages(substream);
}
-static void snd_rme32_capture_start(rme32_t * rme32, int from_pause)
+static void snd_rme32_pcm_start(rme32_t * rme32, int from_pause)
{
if (!from_pause) {
writel(0, rme32->iobase + RME32_IO_RESET_POS);
@@ -797,7 +821,7 @@
writel(rme32->wcreg, rme32->iobase + RME32_IO_CONTROL_REGISTER);
}
-static void snd_rme32_playback_stop(rme32_t * rme32)
+static void snd_rme32_pcm_stop(rme32_t * rme32)
{
/*
* Check if there is an unconfirmed IRQ, if so confirm it, or else
@@ -811,16 +835,7 @@
if (rme32->wcreg & RME32_WCR_SEL)
rme32->wcreg |= RME32_WCR_MUTE;
writel(rme32->wcreg, rme32->iobase + RME32_IO_CONTROL_REGISTER);
-}
-
-static void snd_rme32_capture_stop(rme32_t * rme32)
-{
- rme32->rcreg = readl(rme32->iobase + RME32_IO_CONTROL_REGISTER);
- if (rme32->rcreg & RME32_RCR_IRQ) {
- writel(0, rme32->iobase + RME32_IO_CONFIRM_ACTION_IRQ);
- }
- rme32->wcreg &= ~RME32_WCR_START;
- writel(rme32->wcreg, rme32->iobase + RME32_IO_CONTROL_REGISTER);
+ writel(0, rme32->iobase + RME32_IO_RESET_POS);
}
static irqreturn_t
@@ -854,6 +869,18 @@
.mask = 0
};
+static void snd_rme32_set_buffer_constraint(rme32_t *rme32, snd_pcm_runtime_t *runtime)
+{
+ if (! rme32->fullduplex_mode) {
+ snd_pcm_hw_constraint_minmax(runtime,
+ SNDRV_PCM_HW_PARAM_BUFFER_BYTES,
+ RME32_BUFFER_SIZE, RME32_BUFFER_SIZE);
+ snd_pcm_hw_constraint_list(runtime, 0,
+ SNDRV_PCM_HW_PARAM_PERIOD_BYTES,
+ &hw_constraints_period_bytes);
+ }
+}
+
static int snd_rme32_playback_spdif_open(snd_pcm_substream_t * substream)
{
unsigned long flags;
@@ -873,7 +900,10 @@
rme32->playback_substream = substream;
spin_unlock_irqrestore(&rme32->lock, flags);
- runtime->hw = snd_rme32_playback_spdif_info;
+ if (rme32->fullduplex_mode)
+ runtime->hw = snd_rme32_spdif_fd_info;
+ else
+ runtime->hw = snd_rme32_spdif_info;
if (rme32->pci->device == PCI_DEVICE_ID_DIGI32_PRO) {
runtime->hw.rates |= SNDRV_PCM_RATE_64000 | SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000;
runtime->hw.rate_max = 96000;
@@ -885,12 +915,8 @@
runtime->hw.rate_min = rate;
runtime->hw.rate_max = rate;
}
- snd_pcm_hw_constraint_minmax(runtime,
- SNDRV_PCM_HW_PARAM_BUFFER_BYTES,
- RME32_BUFFER_SIZE, RME32_BUFFER_SIZE);
- snd_pcm_hw_constraint_list(runtime, 0,
- SNDRV_PCM_HW_PARAM_PERIOD_BYTES,
- &hw_constraints_period_bytes);
+
+ snd_rme32_set_buffer_constraint(rme32, runtime);
rme32->wcreg_spdif_stream = rme32->wcreg_spdif;
rme32->spdif_ctl->vd[0].access &= ~SNDRV_CTL_ELEM_ACCESS_INACTIVE;
@@ -916,7 +942,10 @@
rme32->capture_substream = substream;
spin_unlock_irqrestore(&rme32->lock, flags);
- runtime->hw = snd_rme32_capture_spdif_info;
+ if (rme32->fullduplex_mode)
+ runtime->hw = snd_rme32_spdif_fd_info;
+ else
+ runtime->hw = snd_rme32_spdif_info;
if (RME32_PRO_WITH_8414(rme32)) {
runtime->hw.rates |= SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000;
runtime->hw.rate_max = 96000;
@@ -930,12 +959,7 @@
runtime->hw.rate_max = rate;
}
- snd_pcm_hw_constraint_minmax(runtime,
- SNDRV_PCM_HW_PARAM_BUFFER_BYTES,
- RME32_BUFFER_SIZE, RME32_BUFFER_SIZE);
- snd_pcm_hw_constraint_list(runtime, 0,
- SNDRV_PCM_HW_PARAM_PERIOD_BYTES,
- &hw_constraints_period_bytes);
+ snd_rme32_set_buffer_constraint(rme32, runtime);
return 0;
}
@@ -960,7 +984,10 @@
rme32->playback_substream = substream;
spin_unlock_irqrestore(&rme32->lock, flags);
- runtime->hw = snd_rme32_playback_adat_info;
+ if (rme32->fullduplex_mode)
+ runtime->hw = snd_rme32_adat_fd_info;
+ else
+ runtime->hw = snd_rme32_adat_info;
if ((rme32->rcreg & RME32_RCR_KMODE) &&
(rate = snd_rme32_capture_getrate(rme32, &dummy)) > 0) {
/* AutoSync */
@@ -968,10 +995,8 @@
runtime->hw.rate_min = rate;
runtime->hw.rate_max = rate;
}
- snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_BUFFER_BYTES,
- RME32_BUFFER_SIZE, RME32_BUFFER_SIZE);
- snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_BYTES,
- &hw_constraints_period_bytes);
+
+ snd_rme32_set_buffer_constraint(rme32, runtime);
return 0;
}
@@ -983,7 +1008,10 @@
rme32_t *rme32 = _snd_pcm_substream_chip(substream);
snd_pcm_runtime_t *runtime = substream->runtime;
- runtime->hw = snd_rme32_capture_adat_info;
+ if (rme32->fullduplex_mode)
+ runtime->hw = snd_rme32_adat_fd_info;
+ else
+ runtime->hw = snd_rme32_adat_info;
if ((rate = snd_rme32_capture_getrate(rme32, &isadat)) > 0) {
if (!isadat) {
return -EIO;
@@ -1003,10 +1031,7 @@
rme32->capture_substream = substream;
spin_unlock_irqrestore(&rme32->lock, flags);
- snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_BUFFER_BYTES,
- RME32_BUFFER_SIZE, RME32_BUFFER_SIZE);
- snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_BYTES,
- &hw_constraints_period_bytes);
+ snd_rme32_set_buffer_constraint(rme32, runtime);
return 0;
}
@@ -1045,139 +1070,174 @@
static int snd_rme32_playback_prepare(snd_pcm_substream_t * substream)
{
rme32_t *rme32 = _snd_pcm_substream_chip(substream);
- unsigned long flags;
- spin_lock_irqsave(&rme32->lock, flags);
- if (RME32_ISWORKING(rme32)) {
- snd_rme32_playback_stop(rme32);
+ spin_lock(&rme32->lock);
+ if (rme32->fullduplex_mode) {
+ memset(&rme32->playback_pcm, 0, sizeof(rme32->playback_pcm));
+ rme32->playback_pcm.hw_buffer_size = RME32_BUFFER_SIZE;
+ rme32->playback_pcm.hw_queue_size = RME32_BUFFER_SIZE / 2;
+ rme32->playback_pcm.sw_buffer_size = snd_pcm_lib_buffer_bytes(substream);
+ } else {
+ writel(0, rme32->iobase + RME32_IO_RESET_POS);
}
- writel(0, rme32->iobase + RME32_IO_RESET_POS);
if (rme32->wcreg & RME32_WCR_SEL)
rme32->wcreg &= ~RME32_WCR_MUTE;
writel(rme32->wcreg, rme32->iobase + RME32_IO_CONTROL_REGISTER);
- spin_unlock_irqrestore(&rme32->lock, flags);
+ spin_unlock(&rme32->lock);
return 0;
}
static int snd_rme32_capture_prepare(snd_pcm_substream_t * substream)
{
rme32_t *rme32 = _snd_pcm_substream_chip(substream);
- unsigned long flags;
- spin_lock_irqsave(&rme32->lock, flags);
- if (RME32_ISWORKING(rme32)) {
- snd_rme32_capture_stop(rme32);
+ spin_lock(&rme32->lock);
+ if (rme32->fullduplex_mode) {
+ memset(&rme32->capture_pcm, 0, sizeof(rme32->capture_pcm));
+ rme32->capture_pcm.hw_buffer_size = RME32_BUFFER_SIZE;
+ rme32->capture_pcm.hw_queue_size = RME32_BUFFER_SIZE / 2;
+ rme32->capture_pcm.sw_buffer_size = snd_pcm_lib_buffer_bytes(substream);
+ } else {
+ writel(0, rme32->iobase + RME32_IO_RESET_POS);
}
- writel(0, rme32->iobase + RME32_IO_RESET_POS);
- spin_unlock_irqrestore(&rme32->lock, flags);
+ spin_unlock(&rme32->lock);
return 0;
}
static int
-snd_rme32_playback_trigger(snd_pcm_substream_t * substream, int cmd)
+snd_rme32_pcm_trigger(snd_pcm_substream_t * substream, int cmd)
{
rme32_t *rme32 = _snd_pcm_substream_chip(substream);
- switch (cmd) {
- case SNDRV_PCM_TRIGGER_START:
- if (!RME32_ISWORKING(rme32)) {
- if (substream != rme32->playback_substream) {
- return -EBUSY;
+ struct list_head *pos;
+ snd_pcm_substream_t *s;
+
+ spin_lock(&rme32->lock);
+ snd_pcm_group_for_each(pos, substream) {
+ s = snd_pcm_group_substream_entry(pos);
+ if (s != rme32->playback_substream &&
+ s != rme32->capture_substream)
+ continue;
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ rme32->running++;
+ if (rme32->fullduplex_mode) {
+ /* remember the current DMA position */
+ if (s == rme32->playback_substream) {
+ rme32->playback_pcm.hw_data = snd_rme32_pcm_byteptr(rme32);
+ s->ops->ack(s); /* prefill buffer */
+ } else {
+ rme32->capture_pcm.hw_data = snd_rme32_pcm_byteptr(rme32);
+ }
}
- snd_rme32_playback_start(rme32, 0);
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ rme32->running--;
+ break;
}
+ snd_pcm_trigger_done(s, substream);
+ }
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ if (rme32->running && ! RME32_ISWORKING(rme32))
+ snd_rme32_pcm_start(rme32, 0);
break;
-
case SNDRV_PCM_TRIGGER_STOP:
- if (RME32_ISWORKING(rme32)) {
- if (substream != rme32->playback_substream) {
- return -EBUSY;
- }
- snd_rme32_playback_stop(rme32);
- }
+ if (! rme32->running && RME32_ISWORKING(rme32))
+ snd_rme32_pcm_stop(rme32);
break;
-
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
- if (RME32_ISWORKING(rme32)) {
- snd_rme32_playback_stop(rme32);
- }
+ if (rme32->running && RME32_ISWORKING(rme32))
+ snd_rme32_pcm_stop(rme32);
break;
-
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
- if (!RME32_ISWORKING(rme32)) {
- snd_rme32_playback_start(rme32, 1);
- }
+ if (rme32->running && ! RME32_ISWORKING(rme32))
+ snd_rme32_pcm_start(rme32, 1);
break;
-
- default:
- return -EINVAL;
}
+ spin_unlock(&rme32->lock);
return 0;
}
-static int
-snd_rme32_capture_trigger(snd_pcm_substream_t * substream, int cmd)
+/* pointer callback for halfduplex mode */
+static snd_pcm_uframes_t
+snd_rme32_playback_pointer(snd_pcm_substream_t * substream)
{
rme32_t *rme32 = _snd_pcm_substream_chip(substream);
+ return snd_rme32_pcm_byteptr(rme32) >> rme32->playback_frlog;
+}
- switch (cmd) {
- case SNDRV_PCM_TRIGGER_START:
- if (!RME32_ISWORKING(rme32)) {
- if (substream != rme32->capture_substream) {
- return -EBUSY;
- }
- snd_rme32_capture_start(rme32, 0);
- }
- break;
+static snd_pcm_uframes_t
+snd_rme32_capture_pointer(snd_pcm_substream_t * substream)
+{
+ rme32_t *rme32 = _snd_pcm_substream_chip(substream);
+ return snd_rme32_pcm_byteptr(rme32) >> rme32->capture_frlog;
+}
- case SNDRV_PCM_TRIGGER_STOP:
- if (RME32_ISWORKING(rme32)) {
- if (substream != rme32->capture_substream) {
- return -EBUSY;
- }
- snd_rme32_capture_stop(rme32);
- }
- break;
- case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
- if (RME32_ISWORKING(rme32)) {
- snd_rme32_capture_stop(rme32);
- }
- break;
+/* ack and pointer callbacks for fullduplex mode */
+static void snd_rme32_pb_trans_copy(snd_pcm_substream_t *substream,
+ snd_pcm_indirect_t *rec, size_t bytes)
+{
+ rme32_t *rme32 = _snd_pcm_substream_chip(substream);
+ memcpy_toio(rme32->iobase + RME32_IO_DATA_BUFFER + rec->hw_data,
+ substream->runtime->dma_area + rec->sw_data, bytes);
+}
- case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
- if (!RME32_ISWORKING(rme32)) {
- snd_rme32_capture_start(rme32, 1);
- }
- break;
+static int snd_rme32_playback_fd_ack(snd_pcm_substream_t *substream)
+{
+ rme32_t *rme32 = _snd_pcm_substream_chip(substream);
+ snd_pcm_indirect_playback_transfer(substream, &rme32->playback_pcm,
+ snd_rme32_pb_trans_copy);
+ return 0;
+}
- default:
- return -EINVAL;
- }
+static void snd_rme32_cp_trans_copy(snd_pcm_substream_t *substream,
+ snd_pcm_indirect_t *rec, size_t bytes)
+{
+ rme32_t *rme32 = _snd_pcm_substream_chip(substream);
+ memcpy_fromio(substream->runtime->dma_area + rec->sw_data,
+ rme32->iobase + RME32_IO_DATA_BUFFER + rec->hw_data,
+ bytes);
+}
+static int snd_rme32_capture_fd_ack(snd_pcm_substream_t *substream)
+{
+ rme32_t *rme32 = _snd_pcm_substream_chip(substream);
+ snd_pcm_indirect_capture_transfer(substream, &rme32->capture_pcm,
+ snd_rme32_cp_trans_copy);
return 0;
}
static snd_pcm_uframes_t
-snd_rme32_playback_pointer(snd_pcm_substream_t * substream)
+snd_rme32_playback_fd_pointer(snd_pcm_substream_t * substream)
{
rme32_t *rme32 = _snd_pcm_substream_chip(substream);
- return snd_rme32_playback_ptr(rme32);
+ size_t ptr;
+
+ ptr = readl(rme32->iobase + RME32_IO_GET_POS) & RME32_RCR_AUDIO_ADDR_MASK;
+ return snd_pcm_indirect_playback_pointer(substream, &rme32->playback_pcm, ptr);
}
static snd_pcm_uframes_t
-snd_rme32_capture_pointer(snd_pcm_substream_t * substream)
+snd_rme32_capture_fd_pointer(snd_pcm_substream_t * substream)
{
rme32_t *rme32 = _snd_pcm_substream_chip(substream);
- return snd_rme32_capture_ptr(rme32);
+ size_t ptr;
+
+ ptr = readl(rme32->iobase + RME32_IO_GET_POS) & RME32_RCR_AUDIO_ADDR_MASK;
+ return snd_pcm_indirect_capture_pointer(substream, &rme32->playback_pcm, ptr);
}
+/* for halfduplex mode */
static snd_pcm_ops_t snd_rme32_playback_spdif_ops = {
.open = snd_rme32_playback_spdif_open,
.close = snd_rme32_playback_close,
.ioctl = snd_pcm_lib_ioctl,
.hw_params = snd_rme32_playback_hw_params,
+ .hw_free = snd_rme32_pcm_hw_free,
.prepare = snd_rme32_playback_prepare,
- .trigger = snd_rme32_playback_trigger,
+ .trigger = snd_rme32_pcm_trigger,
.pointer = snd_rme32_playback_pointer,
.copy = snd_rme32_playback_copy,
.silence = snd_rme32_playback_silence,
@@ -1188,8 +1248,9 @@
.close = snd_rme32_capture_close,
.ioctl = snd_pcm_lib_ioctl,
.hw_params = snd_rme32_capture_hw_params,
+ .hw_free = snd_rme32_pcm_hw_free,
.prepare = snd_rme32_capture_prepare,
- .trigger = snd_rme32_capture_trigger,
+ .trigger = snd_rme32_pcm_trigger,
.pointer = snd_rme32_capture_pointer,
.copy = snd_rme32_capture_copy,
};
@@ -1200,7 +1261,7 @@
.ioctl = snd_pcm_lib_ioctl,
.hw_params = snd_rme32_playback_hw_params,
.prepare = snd_rme32_playback_prepare,
- .trigger = snd_rme32_playback_trigger,
+ .trigger = snd_rme32_pcm_trigger,
.pointer = snd_rme32_playback_pointer,
.copy = snd_rme32_playback_copy,
.silence = snd_rme32_playback_silence,
@@ -1212,11 +1273,58 @@
.ioctl = snd_pcm_lib_ioctl,
.hw_params = snd_rme32_capture_hw_params,
.prepare = snd_rme32_capture_prepare,
- .trigger = snd_rme32_capture_trigger,
+ .trigger = snd_rme32_pcm_trigger,
.pointer = snd_rme32_capture_pointer,
.copy = snd_rme32_capture_copy,
};
+/* for fullduplex mode */
+static snd_pcm_ops_t snd_rme32_playback_spdif_fd_ops = {
+ .open = snd_rme32_playback_spdif_open,
+ .close = snd_rme32_playback_close,
+ .ioctl = snd_pcm_lib_ioctl,
+ .hw_params = snd_rme32_playback_hw_params,
+ .hw_free = snd_rme32_pcm_hw_free,
+ .prepare = snd_rme32_playback_prepare,
+ .trigger = snd_rme32_pcm_trigger,
+ .pointer = snd_rme32_playback_fd_pointer,
+ .ack = snd_rme32_playback_fd_ack,
+};
+
+static snd_pcm_ops_t snd_rme32_capture_spdif_fd_ops = {
+ .open = snd_rme32_capture_spdif_open,
+ .close = snd_rme32_capture_close,
+ .ioctl = snd_pcm_lib_ioctl,
+ .hw_params = snd_rme32_capture_hw_params,
+ .hw_free = snd_rme32_pcm_hw_free,
+ .prepare = snd_rme32_capture_prepare,
+ .trigger = snd_rme32_pcm_trigger,
+ .pointer = snd_rme32_capture_fd_pointer,
+ .ack = snd_rme32_capture_fd_ack,
+};
+
+static snd_pcm_ops_t snd_rme32_playback_adat_fd_ops = {
+ .open = snd_rme32_playback_adat_open,
+ .close = snd_rme32_playback_close,
+ .ioctl = snd_pcm_lib_ioctl,
+ .hw_params = snd_rme32_playback_hw_params,
+ .prepare = snd_rme32_playback_prepare,
+ .trigger = snd_rme32_pcm_trigger,
+ .pointer = snd_rme32_playback_fd_pointer,
+ .ack = snd_rme32_capture_fd_ack,
+};
+
+static snd_pcm_ops_t snd_rme32_capture_adat_fd_ops = {
+ .open = snd_rme32_capture_adat_open,
+ .close = snd_rme32_capture_close,
+ .ioctl = snd_pcm_lib_ioctl,
+ .hw_params = snd_rme32_capture_hw_params,
+ .prepare = snd_rme32_capture_prepare,
+ .trigger = snd_rme32_pcm_trigger,
+ .pointer = snd_rme32_capture_fd_pointer,
+ .ack = snd_rme32_capture_fd_ack,
+};
+
static void snd_rme32_free(void *private_data)
{
rme32_t *rme32 = (rme32_t *) private_data;
@@ -1225,8 +1333,7 @@
return;
}
if (rme32->irq >= 0) {
- snd_rme32_playback_stop(rme32);
- snd_rme32_capture_stop(rme32);
+ snd_rme32_pcm_stop(rme32);
free_irq(rme32->irq, (void *) rme32);
rme32->irq = -1;
}
@@ -1294,12 +1401,22 @@
rme32->spdif_pcm->private_data = rme32;
rme32->spdif_pcm->private_free = snd_rme32_free_spdif_pcm;
strcpy(rme32->spdif_pcm->name, "Digi32 IEC958");
- snd_pcm_set_ops(rme32->spdif_pcm, SNDRV_PCM_STREAM_PLAYBACK,
- &snd_rme32_playback_spdif_ops);
- snd_pcm_set_ops(rme32->spdif_pcm, SNDRV_PCM_STREAM_CAPTURE,
- &snd_rme32_capture_spdif_ops);
-
- rme32->spdif_pcm->info_flags = SNDRV_PCM_INFO_HALF_DUPLEX;
+ if (rme32->fullduplex_mode) {
+ snd_pcm_set_ops(rme32->spdif_pcm, SNDRV_PCM_STREAM_PLAYBACK,
+ &snd_rme32_playback_spdif_fd_ops);
+ snd_pcm_set_ops(rme32->spdif_pcm, SNDRV_PCM_STREAM_CAPTURE,
+ &snd_rme32_capture_spdif_fd_ops);
+ snd_pcm_lib_preallocate_pages_for_all(rme32->spdif_pcm, SNDRV_DMA_TYPE_CONTINUOUS,
+ snd_dma_continuous_data(GFP_KERNEL),
+ 0, RME32_MID_BUFFER_SIZE);
+ rme32->spdif_pcm->info_flags = SNDRV_PCM_INFO_JOINT_DUPLEX;
+ } else {
+ snd_pcm_set_ops(rme32->spdif_pcm, SNDRV_PCM_STREAM_PLAYBACK,
+ &snd_rme32_playback_spdif_ops);
+ snd_pcm_set_ops(rme32->spdif_pcm, SNDRV_PCM_STREAM_CAPTURE,
+ &snd_rme32_capture_spdif_ops);
+ rme32->spdif_pcm->info_flags = SNDRV_PCM_INFO_HALF_DUPLEX;
+ }
/* set up ALSA pcm device for ADAT */
if ((pci->device == PCI_DEVICE_ID_DIGI32) ||
@@ -1316,12 +1433,22 @@
rme32->adat_pcm->private_data = rme32;
rme32->adat_pcm->private_free = snd_rme32_free_adat_pcm;
strcpy(rme32->adat_pcm->name, "Digi32 ADAT");
- snd_pcm_set_ops(rme32->adat_pcm, SNDRV_PCM_STREAM_PLAYBACK,
- &snd_rme32_playback_adat_ops);
- snd_pcm_set_ops(rme32->adat_pcm, SNDRV_PCM_STREAM_CAPTURE,
- &snd_rme32_capture_adat_ops);
-
- rme32->adat_pcm->info_flags = SNDRV_PCM_INFO_HALF_DUPLEX;
+ if (rme32->fullduplex_mode) {
+ snd_pcm_set_ops(rme32->adat_pcm, SNDRV_PCM_STREAM_PLAYBACK,
+ &snd_rme32_playback_adat_fd_ops);
+ snd_pcm_set_ops(rme32->adat_pcm, SNDRV_PCM_STREAM_CAPTURE,
+ &snd_rme32_capture_adat_fd_ops);
+ snd_pcm_lib_preallocate_pages_for_all(rme32->adat_pcm, SNDRV_DMA_TYPE_CONTINUOUS,
+ snd_dma_continuous_data(GFP_KERNEL),
+ 0, RME32_MID_BUFFER_SIZE);
+ rme32->adat_pcm->info_flags = SNDRV_PCM_INFO_JOINT_DUPLEX;
+ } else {
+ snd_pcm_set_ops(rme32->adat_pcm, SNDRV_PCM_STREAM_PLAYBACK,
+ &snd_rme32_playback_adat_ops);
+ snd_pcm_set_ops(rme32->adat_pcm, SNDRV_PCM_STREAM_CAPTURE,
+ &snd_rme32_capture_adat_ops);
+ rme32->adat_pcm->info_flags = SNDRV_PCM_INFO_HALF_DUPLEX;
+ }
}
@@ -1329,8 +1456,7 @@
rme32->capture_periodsize = 0;
/* make sure playback/capture is stopped, if by some reason active */
- snd_rme32_playback_stop(rme32);
- snd_rme32_capture_stop(rme32);
+ snd_rme32_pcm_stop(rme32);
/* reset DAC */
snd_rme32_reset_dac(rme32);
@@ -1375,6 +1501,10 @@
snd_iprintf(buffer, " (index #%d)\n", rme32->card->number + 1);
snd_iprintf(buffer, "\nGeneral settings\n");
+ if (rme32->fullduplex_mode)
+ snd_iprintf(buffer, " Full-duplex mode\n");
+ else
+ snd_iprintf(buffer, " Half-duplex mode\n");
if (RME32_PRO_WITH_8414(rme32)) {
snd_iprintf(buffer, " receiver: CS8414\n");
} else {
@@ -1818,7 +1948,7 @@
int idx, err;
snd_kcontrol_t *kctl;
- for (idx = 0; idx < 7; idx++) {
+ for (idx = 0; idx < (int)ARRAY_SIZE(snd_rme32_controls); idx++) {
if ((err = snd_ctl_add(card, kctl = snd_ctl_new1(&snd_rme32_controls[idx], rme32))) < 0)
return err;
if (idx == 1) /* IEC958 (S/PDIF) Stream */
Index: alsa-kernel/include/pcm-indirect.h
===================================================================
RCS file: alsa-kernel/include/pcm-indirect.h
diff -N alsa-kernel/include/pcm-indirect.h
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ alsa-kernel/include/pcm-indirect.h 18 Jun 2004 15:11:39 -0000
@@ -0,0 +1,159 @@
+/*
+ * Helper functions for indirect PCM data transfer
+ *
+ * Copyright (c) by Takashi Iwai <tiwai@suse.de>
+ * Jaroslav Kysela <perex@suse.cz>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#ifndef __SOUND_PCM_INDIRECT_H
+#define __SOUND_PCM_INDIRECT_H
+
+#include <sound/pcm.h>
+
+typedef struct sndrv_pcm_indirect {
+ unsigned int hw_buffer_size; /* Byte size of hardware buffer */
+ unsigned int hw_queue_size; /* Max queue size of hw buffer (0 = buffer size) */
+ unsigned int hw_data; /* Offset to next dst (or src) in hw ring buffer */
+ unsigned int hw_io; /* Ring buffer hw pointer */
+ int hw_ready; /* Bytes ready for play (or captured) in hw ring buffer */
+ unsigned int sw_buffer_size; /* Byte size of software buffer */
+ unsigned int sw_data; /* Offset to next dst (or src) in sw ring buffer */
+ unsigned int sw_io; /* Current software pointer in bytes */
+ int sw_ready; /* Bytes ready to be transferred to/from hw */
+ size_t appl_ptr; /* Last seen appl_ptr */
+} snd_pcm_indirect_t;
+
+typedef void (*snd_pcm_indirect_copy_t)(snd_pcm_substream_t *substream,
+ snd_pcm_indirect_t *rec, size_t bytes);
+
+static inline void
+snd_pcm_indirect_playback_transfer(snd_pcm_substream_t *substream,
+ snd_pcm_indirect_t *rec,
+ snd_pcm_indirect_copy_t copy)
+{
+ snd_pcm_runtime_t *runtime = substream->runtime;
+ snd_pcm_uframes_t appl_ptr = runtime->control->appl_ptr;
+ snd_pcm_sframes_t diff = appl_ptr - rec->appl_ptr;
+ snd_pcm_uframes_t qsize;
+
+ if (diff) {
+ if (diff < -(snd_pcm_sframes_t) (runtime->boundary / 2))
+ diff += runtime->boundary;
+ rec->sw_ready += frames_to_bytes(runtime, diff);
+ rec->appl_ptr = appl_ptr;
+ }
+ qsize = rec->hw_queue_size ? rec->hw_queue_size : rec->hw_buffer_size;
+ while (rec->hw_ready < qsize && rec->sw_ready > 0) {
+ size_t hw_to_end = rec->hw_buffer_size - rec->hw_data;
+ size_t sw_to_end = rec->sw_buffer_size - rec->sw_data;
+ size_t bytes = rec->hw_buffer_size - rec->hw_ready;
+ if (rec->sw_ready < (int)bytes)
+ bytes = rec->sw_ready;
+ if (hw_to_end < bytes)
+ bytes = hw_to_end;
+ if (sw_to_end < bytes)
+ bytes = sw_to_end;
+ if (! bytes)
+ break;
+ copy(substream, rec, bytes);
+ rec->hw_data += bytes;
+ if ((int)rec->hw_data == rec->hw_buffer_size)
+ rec->hw_data = 0;
+ rec->sw_data += bytes;
+ if (rec->sw_data == rec->sw_buffer_size)
+ rec->sw_data = 0;
+ rec->hw_ready += bytes;
+ rec->sw_ready -= bytes;
+ }
+}
+
+static inline snd_pcm_uframes_t
+snd_pcm_indirect_playback_pointer(snd_pcm_substream_t *substream,
+ snd_pcm_indirect_t *rec, size_t ptr)
+{
+ ssize_t bytes = ptr - rec->hw_io;
+ if (bytes < 0)
+ bytes += rec->hw_buffer_size;
+ rec->hw_io = ptr;
+ rec->hw_ready -= bytes;
+ rec->sw_io += bytes;
+ if (rec->sw_io >= rec->sw_buffer_size)
+ rec->sw_io -= rec->sw_buffer_size;
+ if (substream->ops->ack)
+ substream->ops->ack(substream);
+ return bytes_to_frames(substream->runtime, rec->sw_io);
+}
+
+
+static inline void
+snd_pcm_indirect_capture_transfer(snd_pcm_substream_t *substream,
+ snd_pcm_indirect_t *rec,
+ snd_pcm_indirect_copy_t copy)
+{
+ snd_pcm_runtime_t *runtime = substream->runtime;
+ snd_pcm_uframes_t appl_ptr = runtime->control->appl_ptr;
+ snd_pcm_sframes_t diff = appl_ptr - rec->appl_ptr;
+
+ if (diff) {
+ if (diff < -(snd_pcm_sframes_t) (runtime->boundary / 2))
+ diff += runtime->boundary;
+ rec->sw_ready -= frames_to_bytes(runtime, diff);
+ rec->appl_ptr = appl_ptr;
+ }
+ while (rec->hw_ready > 0 &&
+ rec->sw_ready < (int)rec->sw_buffer_size) {
+ size_t hw_to_end = rec->hw_buffer_size - rec->hw_data;
+ size_t sw_to_end = rec->sw_buffer_size - rec->sw_data;
+ size_t bytes = rec->sw_buffer_size - rec->sw_ready;
+ if (rec->hw_ready < (int)bytes)
+ bytes = rec->hw_ready;
+ if (hw_to_end < bytes)
+ bytes = hw_to_end;
+ if (sw_to_end < bytes)
+ bytes = sw_to_end;
+ if (! bytes)
+ break;
+ copy(substream, rec, bytes);
+ rec->hw_data += bytes;
+ if ((int)rec->hw_data == rec->hw_buffer_size)
+ rec->hw_data = 0;
+ rec->sw_data += bytes;
+ if (rec->sw_data == rec->sw_buffer_size)
+ rec->sw_data = 0;
+ rec->hw_ready -= bytes;
+ rec->sw_ready += bytes;
+ }
+}
+
+static inline snd_pcm_uframes_t
+snd_pcm_indirect_capture_pointer(snd_pcm_substream_t *substream,
+ snd_pcm_indirect_t *rec, size_t ptr)
+{
+ ssize_t bytes = ptr - rec->hw_io;
+ if (bytes < 0)
+ bytes += rec->hw_buffer_size;
+ rec->hw_io = ptr;
+ rec->hw_ready += bytes;
+ rec->sw_io += bytes;
+ if (rec->sw_io >= rec->sw_buffer_size)
+ rec->sw_io -= rec->sw_buffer_size;
+ if (substream->ops->ack)
+ substream->ops->ack(substream);
+ return bytes_to_frames(substream->runtime, rec->sw_io);
+}
+
+#endif /* __SOUND_PCM_INDIRECT_H */
reply other threads:[~2004-06-18 15:17 UTC|newest]
Thread overview: [no followups] expand[flat|nested] mbox.gz Atom feed
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=s5hk6y4opzi.wl@alsa2.suse.de \
--to=tiwai@suse.de \
--cc=alsa-devel@lists.sourceforge.net \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox