diff options
Diffstat (limited to 'sound/usb')
-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 { | |||
28 | unsigned int *rate_table; /* rate table */ | 28 | unsigned int *rate_table; /* rate table */ |
29 | unsigned char clock; /* associated clock */ | 29 | unsigned char clock; /* associated clock */ |
30 | struct snd_pcm_chmap_elem *chmap; /* (optional) channel map */ | 30 | struct snd_pcm_chmap_elem *chmap; /* (optional) channel map */ |
31 | bool dsd_dop; /* add DOP headers in case of DSD samples */ | ||
31 | }; | 32 | }; |
32 | 33 | ||
33 | struct snd_usb_substream; | 34 | struct snd_usb_substream; |
@@ -139,6 +140,12 @@ struct snd_usb_substream { | |||
139 | 140 | ||
140 | int last_frame_number; /* stored frame number */ | 141 | int last_frame_number; /* stored frame number */ |
141 | int last_delay; /* stored delay */ | 142 | int last_delay; /* stored delay */ |
143 | |||
144 | struct { | ||
145 | int marker; | ||
146 | int channel; | ||
147 | int byte_idx; | ||
148 | } dsd_dop; | ||
142 | }; | 149 | }; |
143 | 150 | ||
144 | struct snd_usb_stream { | 151 | 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, | |||
578 | int is_playback = usb_pipeout(ep->pipe); | 578 | int is_playback = usb_pipeout(ep->pipe); |
579 | int frame_bits = snd_pcm_format_physical_width(pcm_format) * channels; | 579 | int frame_bits = snd_pcm_format_physical_width(pcm_format) * channels; |
580 | 580 | ||
581 | if (pcm_format == SNDRV_PCM_FORMAT_DSD_U16_LE && fmt->dsd_dop) { | ||
582 | /* | ||
583 | * When operating in DSD DOP mode, the size of a sample frame | ||
584 | * in hardware differs from the actual physical format width | ||
585 | * because we need to make room for the DOP markers. | ||
586 | */ | ||
587 | frame_bits += channels << 3; | ||
588 | } | ||
589 | |||
581 | ep->datainterval = fmt->datainterval; | 590 | ep->datainterval = fmt->datainterval; |
582 | ep->stride = frame_bits >> 3; | 591 | ep->stride = frame_bits >> 3; |
583 | ep->silence_value = pcm_format == SNDRV_PCM_FORMAT_U8 ? 0x80 : 0; | 592 | 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) | |||
1120 | runtime->private_data = subs; | 1120 | runtime->private_data = subs; |
1121 | subs->pcm_substream = substream; | 1121 | subs->pcm_substream = substream; |
1122 | /* runtime PM is also done there */ | 1122 | /* runtime PM is also done there */ |
1123 | |||
1124 | /* initialize DSD/DOP context */ | ||
1125 | subs->dsd_dop.byte_idx = 0; | ||
1126 | subs->dsd_dop.channel = 0; | ||
1127 | subs->dsd_dop.marker = 1; | ||
1128 | |||
1123 | return setup_hw_info(runtime, subs); | 1129 | return setup_hw_info(runtime, subs); |
1124 | } | 1130 | } |
1125 | 1131 | ||
@@ -1214,6 +1220,56 @@ static void retire_capture_urb(struct snd_usb_substream *subs, | |||
1214 | snd_pcm_period_elapsed(subs->pcm_substream); | 1220 | snd_pcm_period_elapsed(subs->pcm_substream); |
1215 | } | 1221 | } |
1216 | 1222 | ||
1223 | static inline void fill_playback_urb_dsd_dop(struct snd_usb_substream *subs, | ||
1224 | struct urb *urb, unsigned int bytes) | ||
1225 | { | ||
1226 | struct snd_pcm_runtime *runtime = subs->pcm_substream->runtime; | ||
1227 | unsigned int stride = runtime->frame_bits >> 3; | ||
1228 | unsigned int dst_idx = 0; | ||
1229 | unsigned int src_idx = subs->hwptr_done; | ||
1230 | unsigned int wrap = runtime->buffer_size * stride; | ||
1231 | u8 *dst = urb->transfer_buffer; | ||
1232 | u8 *src = runtime->dma_area; | ||
1233 | u8 marker[] = { 0x05, 0xfa }; | ||
1234 | |||
1235 | /* | ||
1236 | * The DSP DOP format defines a way to transport DSD samples over | ||
1237 | * normal PCM data endpoints. It requires stuffing of marker bytes | ||
1238 | * (0x05 and 0xfa, alternating per sample frame), and then expects | ||
1239 | * 2 additional bytes of actual payload. The whole frame is stored | ||
1240 | * LSB. | ||
1241 | * | ||
1242 | * Hence, for a stereo transport, the buffer layout looks like this, | ||
1243 | * where L refers to left channel samples and R to right. | ||
1244 | * | ||
1245 | * L1 L2 0x05 R1 R2 0x05 L3 L4 0xfa R3 R4 0xfa | ||
1246 | * L5 L6 0x05 R5 R6 0x05 L7 L8 0xfa R7 R8 0xfa | ||
1247 | * ..... | ||
1248 | * | ||
1249 | */ | ||
1250 | |||
1251 | while (bytes--) { | ||
1252 | if (++subs->dsd_dop.byte_idx == 3) { | ||
1253 | /* frame boundary? */ | ||
1254 | dst[dst_idx++] = marker[subs->dsd_dop.marker]; | ||
1255 | src_idx += 2; | ||
1256 | subs->dsd_dop.byte_idx = 0; | ||
1257 | |||
1258 | if (++subs->dsd_dop.channel % runtime->channels == 0) { | ||
1259 | /* alternate the marker */ | ||
1260 | subs->dsd_dop.marker++; | ||
1261 | subs->dsd_dop.marker %= ARRAY_SIZE(marker); | ||
1262 | subs->dsd_dop.channel = 0; | ||
1263 | } | ||
1264 | } else { | ||
1265 | /* stuff the DSD payload */ | ||
1266 | int idx = (src_idx + subs->dsd_dop.byte_idx - 1) % wrap; | ||
1267 | dst[dst_idx++] = src[idx]; | ||
1268 | subs->hwptr_done++; | ||
1269 | } | ||
1270 | } | ||
1271 | } | ||
1272 | |||
1217 | static void prepare_playback_urb(struct snd_usb_substream *subs, | 1273 | static void prepare_playback_urb(struct snd_usb_substream *subs, |
1218 | struct urb *urb) | 1274 | struct urb *urb) |
1219 | { | 1275 | { |
@@ -1270,19 +1326,28 @@ static void prepare_playback_urb(struct snd_usb_substream *subs, | |||
1270 | break; | 1326 | break; |
1271 | } | 1327 | } |
1272 | bytes = frames * ep->stride; | 1328 | bytes = frames * ep->stride; |
1273 | if (subs->hwptr_done + bytes > runtime->buffer_size * stride) { | 1329 | |
1274 | /* err, the transferred area goes over buffer boundary. */ | 1330 | if (unlikely(subs->pcm_format == SNDRV_PCM_FORMAT_DSD_U16_LE && |
1275 | unsigned int bytes1 = | 1331 | subs->cur_audiofmt->dsd_dop)) { |
1276 | runtime->buffer_size * stride - subs->hwptr_done; | 1332 | fill_playback_urb_dsd_dop(subs, urb, bytes); |
1277 | memcpy(urb->transfer_buffer, | ||
1278 | runtime->dma_area + subs->hwptr_done, bytes1); | ||
1279 | memcpy(urb->transfer_buffer + bytes1, | ||
1280 | runtime->dma_area, bytes - bytes1); | ||
1281 | } else { | 1333 | } else { |
1282 | memcpy(urb->transfer_buffer, | 1334 | /* usual PCM */ |
1283 | runtime->dma_area + subs->hwptr_done, bytes); | 1335 | if (subs->hwptr_done + bytes > runtime->buffer_size * stride) { |
1336 | /* err, the transferred area goes over buffer boundary. */ | ||
1337 | unsigned int bytes1 = | ||
1338 | runtime->buffer_size * stride - subs->hwptr_done; | ||
1339 | memcpy(urb->transfer_buffer, | ||
1340 | runtime->dma_area + subs->hwptr_done, bytes1); | ||
1341 | memcpy(urb->transfer_buffer + bytes1, | ||
1342 | runtime->dma_area, bytes - bytes1); | ||
1343 | } else { | ||
1344 | memcpy(urb->transfer_buffer, | ||
1345 | runtime->dma_area + subs->hwptr_done, bytes); | ||
1346 | } | ||
1347 | |||
1348 | subs->hwptr_done += bytes; | ||
1284 | } | 1349 | } |
1285 | subs->hwptr_done += bytes; | 1350 | |
1286 | if (subs->hwptr_done >= runtime->buffer_size * stride) | 1351 | if (subs->hwptr_done >= runtime->buffer_size * stride) |
1287 | subs->hwptr_done -= runtime->buffer_size * stride; | 1352 | subs->hwptr_done -= runtime->buffer_size * stride; |
1288 | 1353 | ||