diff options
Diffstat (limited to 'sound/usb/pcm.c')
-rw-r--r-- | sound/usb/pcm.c | 87 |
1 files changed, 76 insertions, 11 deletions
diff --git a/sound/usb/pcm.c b/sound/usb/pcm.c index 099c0fe0d1e1..4cd917cf058e 100644 --- a/sound/usb/pcm.c +++ b/sound/usb/pcm.c @@ -1120,6 +1120,12 @@ static int snd_usb_pcm_open(struct snd_pcm_substream *substream, int direction) runtime->private_data = subs; subs->pcm_substream = substream; /* runtime PM is also done there */ + + /* initialize DSD/DOP context */ + subs->dsd_dop.byte_idx = 0; + subs->dsd_dop.channel = 0; + subs->dsd_dop.marker = 1; + return setup_hw_info(runtime, subs); } @@ -1214,6 +1220,56 @@ static void retire_capture_urb(struct snd_usb_substream *subs, snd_pcm_period_elapsed(subs->pcm_substream); } +static inline void fill_playback_urb_dsd_dop(struct snd_usb_substream *subs, + struct urb *urb, unsigned int bytes) +{ + struct snd_pcm_runtime *runtime = subs->pcm_substream->runtime; + unsigned int stride = runtime->frame_bits >> 3; + unsigned int dst_idx = 0; + unsigned int src_idx = subs->hwptr_done; + unsigned int wrap = runtime->buffer_size * stride; + u8 *dst = urb->transfer_buffer; + u8 *src = runtime->dma_area; + u8 marker[] = { 0x05, 0xfa }; + + /* + * The DSP DOP format defines a way to transport DSD samples over + * normal PCM data endpoints. It requires stuffing of marker bytes + * (0x05 and 0xfa, alternating per sample frame), and then expects + * 2 additional bytes of actual payload. The whole frame is stored + * LSB. + * + * Hence, for a stereo transport, the buffer layout looks like this, + * where L refers to left channel samples and R to right. + * + * L1 L2 0x05 R1 R2 0x05 L3 L4 0xfa R3 R4 0xfa + * L5 L6 0x05 R5 R6 0x05 L7 L8 0xfa R7 R8 0xfa + * ..... + * + */ + + while (bytes--) { + if (++subs->dsd_dop.byte_idx == 3) { + /* frame boundary? */ + dst[dst_idx++] = marker[subs->dsd_dop.marker]; + src_idx += 2; + subs->dsd_dop.byte_idx = 0; + + if (++subs->dsd_dop.channel % runtime->channels == 0) { + /* alternate the marker */ + subs->dsd_dop.marker++; + subs->dsd_dop.marker %= ARRAY_SIZE(marker); + subs->dsd_dop.channel = 0; + } + } else { + /* stuff the DSD payload */ + int idx = (src_idx + subs->dsd_dop.byte_idx - 1) % wrap; + dst[dst_idx++] = src[idx]; + subs->hwptr_done++; + } + } +} + static void prepare_playback_urb(struct snd_usb_substream *subs, struct urb *urb) { @@ -1270,19 +1326,28 @@ static void prepare_playback_urb(struct snd_usb_substream *subs, break; } bytes = frames * ep->stride; - if (subs->hwptr_done + bytes > runtime->buffer_size * stride) { - /* err, the transferred area goes over buffer boundary. */ - unsigned int bytes1 = - runtime->buffer_size * stride - subs->hwptr_done; - memcpy(urb->transfer_buffer, - runtime->dma_area + subs->hwptr_done, bytes1); - memcpy(urb->transfer_buffer + bytes1, - runtime->dma_area, bytes - bytes1); + + if (unlikely(subs->pcm_format == SNDRV_PCM_FORMAT_DSD_U16_LE && + subs->cur_audiofmt->dsd_dop)) { + fill_playback_urb_dsd_dop(subs, urb, bytes); } else { - memcpy(urb->transfer_buffer, - runtime->dma_area + subs->hwptr_done, bytes); + /* usual PCM */ + if (subs->hwptr_done + bytes > runtime->buffer_size * stride) { + /* err, the transferred area goes over buffer boundary. */ + unsigned int bytes1 = + runtime->buffer_size * stride - subs->hwptr_done; + memcpy(urb->transfer_buffer, + runtime->dma_area + subs->hwptr_done, bytes1); + memcpy(urb->transfer_buffer + bytes1, + runtime->dma_area, bytes - bytes1); + } else { + memcpy(urb->transfer_buffer, + runtime->dma_area + subs->hwptr_done, bytes); + } + + subs->hwptr_done += bytes; } - subs->hwptr_done += bytes; + if (subs->hwptr_done >= runtime->buffer_size * stride) subs->hwptr_done -= runtime->buffer_size * stride; |