aboutsummaryrefslogtreecommitdiffstats
path: root/sound/usb
diff options
context:
space:
mode:
authorDaniel Mack <zonque@gmail.com>2013-04-16 12:01:38 -0400
committerTakashi Iwai <tiwai@suse.de>2013-04-18 04:03:32 -0400
commitd24f5061ee7b9b58a7e97f3c2a72f0a9b115e7e0 (patch)
treef394f2599adac67b13c31dce99e83c3948f6d452 /sound/usb
parent8a2a74d2b7667b2719858caf9eb2b5a92460195d (diff)
ALSA: snd-usb: add support for DSD DOP stream transport
In order to provide a compatibility way for pushing DSD samples through ordinary PCM channels, the "DoP open Standard" was invented. See http://www.dsd-guide.com for the official document. The host is required to stuff DSD marker bytes (0x05, 0xfa, alternating) in the MSB of 24 bit wide samples on the bus, in addition to the 16 bits of actual DSD sample payload. To support this, the hardware and software stride logic in the driver has to be tweaked a bit, as we make the userspace believe we're operating on 16 bit samples, while we in fact push one more byte per channel down to the hardware. The DOP runtime information is stored in struct snd_usb_substream, so we can keep track of our state across multiple calls to prepare_playback_urb_dsd_dop(). Signed-off-by: Daniel Mack <zonque@gmail.com> Signed-off-by: Takashi Iwai <tiwai@suse.de>
Diffstat (limited to 'sound/usb')
-rw-r--r--sound/usb/card.h7
-rw-r--r--sound/usb/endpoint.c9
-rw-r--r--sound/usb/pcm.c87
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
33struct snd_usb_substream; 34struct 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
144struct snd_usb_stream { 151struct 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
1223static 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
1217static void prepare_playback_urb(struct snd_usb_substream *subs, 1273static 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