diff options
Diffstat (limited to 'sound')
-rw-r--r-- | sound/usb/card.h | 7 | ||||
-rw-r--r-- | sound/usb/endpoint.c | 9 | ||||
-rw-r--r-- | sound/usb/pcm.c | 87 |
3 files changed, 92 insertions, 11 deletions
diff --git a/sound/usb/card.h b/sound/usb/card.h index d32ea411545a..ac55477ce6dd 100644 --- a/sound/usb/card.h +++ b/sound/usb/card.h @@ -28,6 +28,7 @@ struct audioformat { unsigned int *rate_table; /* rate table */ unsigned char clock; /* associated clock */ struct snd_pcm_chmap_elem *chmap; /* (optional) channel map */ + bool dsd_dop; /* add DOP headers in case of DSD samples */ }; struct snd_usb_substream; @@ -139,6 +140,12 @@ struct snd_usb_substream { int last_frame_number; /* stored frame number */ int last_delay; /* stored delay */ + + struct { + int marker; + int channel; + int byte_idx; + } dsd_dop; }; struct snd_usb_stream { diff --git a/sound/usb/endpoint.c b/sound/usb/endpoint.c index 7e9c55a73540..32d0b41a1ff6 100644 --- a/sound/usb/endpoint.c +++ b/sound/usb/endpoint.c @@ -578,6 +578,15 @@ static int data_ep_set_params(struct snd_usb_endpoint *ep, int is_playback = usb_pipeout(ep->pipe); int frame_bits = snd_pcm_format_physical_width(pcm_format) * channels; + if (pcm_format == SNDRV_PCM_FORMAT_DSD_U16_LE && fmt->dsd_dop) { + /* + * When operating in DSD DOP mode, the size of a sample frame + * in hardware differs from the actual physical format width + * because we need to make room for the DOP markers. + */ + frame_bits += channels << 3; + } + ep->datainterval = fmt->datainterval; ep->stride = frame_bits >> 3; ep->silence_value = pcm_format == SNDRV_PCM_FORMAT_U8 ? 0x80 : 0; 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; |