• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3  * Helper functions for indirect PCM data transfer to a simple FIFO in
4  * hardware (small, no possibility to read "hardware io position",
5  * updating position done by interrupt, ...)
6  *
7  *  Copyright (c) by 2007  Joachim Foerster <JOFT@gmx.de>
8  *
9  *  Based on "pcm-indirect.h" (alsa-driver-1.0.13) by
10  *
11  *  Copyright (c) by Takashi Iwai <tiwai@suse.de>
12  *                   Jaroslav Kysela <perex@suse.cz>
13  */
14 
15 /* snd_printk/d() */
16 #include <sound/core.h>
17 /* struct snd_pcm_substream, struct snd_pcm_runtime, snd_pcm_uframes_t
18  * snd_pcm_period_elapsed() */
19 #include <sound/pcm.h>
20 
21 #include "pcm-indirect2.h"
22 
23 #ifdef SND_PCM_INDIRECT2_STAT
24 /* jiffies */
25 #include <linux/jiffies.h>
26 
snd_pcm_indirect2_stat(struct snd_pcm_substream * substream,struct snd_pcm_indirect2 * rec)27 void snd_pcm_indirect2_stat(struct snd_pcm_substream *substream,
28 			    struct snd_pcm_indirect2 *rec)
29 {
30 	struct snd_pcm_runtime *runtime = substream->runtime;
31 	int i;
32 	int j;
33 	int k;
34 	int seconds = (rec->lastbytetime - rec->firstbytetime) / HZ;
35 
36 	snd_printk(KERN_DEBUG "STAT: mul_elapsed: %u, mul_elapsed_real: %d, "
37 		   "irq_occurred: %d\n",
38 		   rec->mul_elapsed, rec->mul_elapsed_real, rec->irq_occured);
39 	snd_printk(KERN_DEBUG "STAT: min_multiple: %d (irqs/period)\n",
40 		   rec->min_multiple);
41 	snd_printk(KERN_DEBUG "STAT: firstbytetime: %lu, lastbytetime: %lu, "
42 		   "firstzerotime: %lu\n",
43 		 rec->firstbytetime, rec->lastbytetime, rec->firstzerotime);
44 	snd_printk(KERN_DEBUG "STAT: bytes2hw: %u Bytes => (by runtime->rate) "
45 		   "length: %d s\n",
46 		 rec->bytes2hw, rec->bytes2hw / 2 / 2 / runtime->rate);
47 	snd_printk(KERN_DEBUG "STAT: (by measurement) length: %d => "
48 		   "rate: %d Bytes/s = %d Frames/s|Hz\n",
49 		   seconds, rec->bytes2hw / seconds,
50 		   rec->bytes2hw / 2 / 2 / seconds);
51 	snd_printk(KERN_DEBUG
52 		   "STAT: zeros2hw: %u = %d ms ~ %d * %d zero copies\n",
53 		   rec->zeros2hw, ((rec->zeros2hw / 2 / 2) * 1000) /
54 		   runtime->rate,
55 		   rec->zeros2hw / (rec->hw_buffer_size / 2),
56 		   (rec->hw_buffer_size / 2));
57 	snd_printk(KERN_DEBUG "STAT: pointer_calls: %u, lastdifftime: %u\n",
58 		   rec->pointer_calls, rec->lastdifftime);
59 	snd_printk(KERN_DEBUG "STAT: sw_io: %d, sw_data: %d\n", rec->sw_io,
60 		   rec->sw_data);
61 	snd_printk(KERN_DEBUG "STAT: byte_sizes[]:\n");
62 	k = 0;
63 	for (j = 0; j < 8; j++) {
64 		for (i = j * 8; i < (j + 1) * 8; i++)
65 			if (rec->byte_sizes[i] != 0) {
66 				snd_printk(KERN_DEBUG "%u: %u",
67 					   i, rec->byte_sizes[i]);
68 				k++;
69 			}
70 		if (((k % 8) == 0) && (k != 0)) {
71 			snd_printk(KERN_DEBUG "\n");
72 			k = 0;
73 		}
74 	}
75 	snd_printk(KERN_DEBUG "\n");
76 	snd_printk(KERN_DEBUG "STAT: zero_sizes[]:\n");
77 	for (j = 0; j < 8; j++) {
78 		k = 0;
79 		for (i = j * 8; i < (j + 1) * 8; i++)
80 			if (rec->zero_sizes[i] != 0)
81 				snd_printk(KERN_DEBUG "%u: %u",
82 					   i, rec->zero_sizes[i]);
83 			else
84 				k++;
85 		if (!k)
86 			snd_printk(KERN_DEBUG "\n");
87 	}
88 	snd_printk(KERN_DEBUG "\n");
89 	snd_printk(KERN_DEBUG "STAT: min_adds[]:\n");
90 	for (j = 0; j < 8; j++) {
91 		if (rec->min_adds[j] != 0)
92 			snd_printk(KERN_DEBUG "%u: %u", j, rec->min_adds[j]);
93 	}
94 	snd_printk(KERN_DEBUG "\n");
95 	snd_printk(KERN_DEBUG "STAT: mul_adds[]:\n");
96 	for (j = 0; j < 8; j++) {
97 		if (rec->mul_adds[j] != 0)
98 			snd_printk(KERN_DEBUG "%u: %u", j, rec->mul_adds[j]);
99 	}
100 	snd_printk(KERN_DEBUG "\n");
101 	snd_printk(KERN_DEBUG
102 		   "STAT: zero_times_saved: %d, zero_times_notsaved: %d\n",
103 		   rec->zero_times_saved, rec->zero_times_notsaved);
104 	/* snd_printk(KERN_DEBUG "STAT: zero_times[]\n");
105 	i = 0;
106 	for (j = 0; j < 3750; j++) {
107 		if (rec->zero_times[j] != 0) {
108 			snd_printk(KERN_DEBUG "%u: %u", j, rec->zero_times[j]);
109 			i++;
110 		}
111 		if (((i % 8) == 0) && (i != 0))
112 			snd_printk(KERN_DEBUG "\n");
113 	}
114 	snd_printk(KERN_DEBUG "\n"); */
115 	return;
116 }
117 #endif
118 
119 /*
120  * _internal_ helper function for playback/capture transfer function
121  */
122 static void
snd_pcm_indirect2_increase_min_periods(struct snd_pcm_substream * substream,struct snd_pcm_indirect2 * rec,int isplay,int iscopy,unsigned int bytes)123 snd_pcm_indirect2_increase_min_periods(struct snd_pcm_substream *substream,
124 				       struct snd_pcm_indirect2 *rec,
125 				       int isplay, int iscopy,
126 				       unsigned int bytes)
127 {
128 	if (rec->min_periods >= 0) {
129 		if (iscopy) {
130 			rec->sw_io += bytes;
131 			if (rec->sw_io >= rec->sw_buffer_size)
132 				rec->sw_io -= rec->sw_buffer_size;
133 		} else if (isplay) {
134 			/* If application does not write data in multiples of
135 			 * a period, move sw_data to the next correctly aligned
136 			 * position, so that sw_io can converge to it (in the
137 			 * next step).
138 			 */
139 			if (!rec->check_alignment) {
140 				if (rec->bytes2hw %
141 				    snd_pcm_lib_period_bytes(substream)) {
142 					unsigned bytes2hw_aligned =
143 					    (1 +
144 					     (rec->bytes2hw /
145 					      snd_pcm_lib_period_bytes
146 					      (substream))) *
147 					    snd_pcm_lib_period_bytes
148 					    (substream);
149 					rec->sw_data =
150 					    bytes2hw_aligned %
151 					    rec->sw_buffer_size;
152 #ifdef SND_PCM_INDIRECT2_STAT
153 					snd_printk(KERN_DEBUG
154 						   "STAT: @re-align: aligned "
155 						   "bytes2hw to next period "
156 						   "size boundary: %d "
157 						   "(instead of %d)\n",
158 						   bytes2hw_aligned,
159 						   rec->bytes2hw);
160 					snd_printk(KERN_DEBUG
161 						   "STAT: @re-align: sw_data "
162 						   "moves to: %d\n",
163 						   rec->sw_data);
164 #endif
165 				}
166 				rec->check_alignment = 1;
167 			}
168 			/* We are at the end and are copying zeros into the
169 			 * fifo.
170 			 * Now, we have to make sure that sw_io is increased
171 			 * until the position of sw_data: Filling the fifo with
172 			 * the first zeros means, the last bytes were played.
173 			 */
174 			if (rec->sw_io != rec->sw_data) {
175 				unsigned int diff;
176 				if (rec->sw_data > rec->sw_io)
177 					diff = rec->sw_data - rec->sw_io;
178 				else
179 					diff = (rec->sw_buffer_size -
180 						rec->sw_io) +
181 						rec->sw_data;
182 				if (bytes >= diff)
183 					rec->sw_io = rec->sw_data;
184 				else {
185 					rec->sw_io += bytes;
186 					if (rec->sw_io >= rec->sw_buffer_size)
187 						rec->sw_io -=
188 						    rec->sw_buffer_size;
189 				}
190 			}
191 		}
192 		rec->min_period_count += bytes;
193 		if (rec->min_period_count >= (rec->hw_buffer_size / 2)) {
194 			rec->min_periods += (rec->min_period_count /
195 					     (rec->hw_buffer_size / 2));
196 #ifdef SND_PCM_INDIRECT2_STAT
197 			if ((rec->min_period_count /
198 			     (rec->hw_buffer_size / 2)) > 7)
199 				snd_printk(KERN_DEBUG
200 					   "STAT: more than 7 (%d) min_adds "
201 					   "at once - too big to save!\n",
202 					   (rec->min_period_count /
203 					    (rec->hw_buffer_size / 2)));
204 			else
205 				rec->min_adds[(rec->min_period_count /
206 					       (rec->hw_buffer_size / 2))]++;
207 #endif
208 			rec->min_period_count = (rec->min_period_count %
209 						 (rec->hw_buffer_size / 2));
210 		}
211 	} else if (isplay && iscopy)
212 		rec->min_periods = 0;
213 }
214 
215 /*
216  * helper function for playback/capture pointer callback
217  */
218 snd_pcm_uframes_t
snd_pcm_indirect2_pointer(struct snd_pcm_substream * substream,struct snd_pcm_indirect2 * rec)219 snd_pcm_indirect2_pointer(struct snd_pcm_substream *substream,
220 			  struct snd_pcm_indirect2 *rec)
221 {
222 #ifdef SND_PCM_INDIRECT2_STAT
223 	rec->pointer_calls++;
224 #endif
225 	return bytes_to_frames(substream->runtime, rec->sw_io);
226 }
227 
228 /*
229  * _internal_ helper function for playback interrupt callback
230  */
231 static void
snd_pcm_indirect2_playback_transfer(struct snd_pcm_substream * substream,struct snd_pcm_indirect2 * rec,snd_pcm_indirect2_copy_t copy,snd_pcm_indirect2_zero_t zero)232 snd_pcm_indirect2_playback_transfer(struct snd_pcm_substream *substream,
233 				    struct snd_pcm_indirect2 *rec,
234 				    snd_pcm_indirect2_copy_t copy,
235 				    snd_pcm_indirect2_zero_t zero)
236 {
237 	struct snd_pcm_runtime *runtime = substream->runtime;
238 	snd_pcm_uframes_t appl_ptr = runtime->control->appl_ptr;
239 
240 	/* runtime->control->appl_ptr: position where ALSA will write next time
241 	 * rec->appl_ptr: position where ALSA was last time
242 	 * diff: obviously ALSA wrote that much bytes into the intermediate
243 	 * buffer since we checked last time
244 	 */
245 	snd_pcm_sframes_t diff = appl_ptr - rec->appl_ptr;
246 
247 	if (diff) {
248 #ifdef SND_PCM_INDIRECT2_STAT
249 		rec->lastdifftime = jiffies;
250 #endif
251 		if (diff < -(snd_pcm_sframes_t) (runtime->boundary / 2))
252 			diff += runtime->boundary;
253 		/* number of bytes "added" by ALSA increases the number of
254 		 * bytes which are ready to "be transferred to HW"/"played"
255 		 * Then, set rec->appl_ptr to not count bytes twice next time.
256 		 */
257 		rec->sw_ready += (int)frames_to_bytes(runtime, diff);
258 		rec->appl_ptr = appl_ptr;
259 	}
260 	if (rec->hw_ready && (rec->sw_ready <= 0)) {
261 		unsigned int bytes;
262 
263 #ifdef SND_PCM_INDIRECT2_STAT
264 		if (rec->firstzerotime == 0) {
265 			rec->firstzerotime = jiffies;
266 			snd_printk(KERN_DEBUG
267 				   "STAT: @firstzerotime: mul_elapsed: %d, "
268 				   "min_period_count: %d\n",
269 				   rec->mul_elapsed, rec->min_period_count);
270 			snd_printk(KERN_DEBUG
271 				   "STAT: @firstzerotime: sw_io: %d, "
272 				   "sw_data: %d, appl_ptr: %u\n",
273 				   rec->sw_io, rec->sw_data,
274 				   (unsigned int)appl_ptr);
275 		}
276 		if ((jiffies - rec->firstzerotime) < 3750) {
277 			rec->zero_times[(jiffies - rec->firstzerotime)]++;
278 			rec->zero_times_saved++;
279 		} else
280 			rec->zero_times_notsaved++;
281 #endif
282 		bytes = zero(substream, rec);
283 
284 #ifdef SND_PCM_INDIRECT2_STAT
285 		rec->zeros2hw += bytes;
286 		if (bytes < 64)
287 			rec->zero_sizes[bytes]++;
288 		else
289 			snd_printk(KERN_DEBUG
290 				   "STAT: %d zero Bytes copied to hardware at "
291 				   "once - too big to save!\n",
292 				   bytes);
293 #endif
294 		snd_pcm_indirect2_increase_min_periods(substream, rec, 1, 0,
295 						       bytes);
296 		return;
297 	}
298 	while (rec->hw_ready && (rec->sw_ready > 0)) {
299 		/* sw_to_end: max. number of bytes that can be read/take from
300 		 * the current position (sw_data) in _one_ step
301 		 */
302 		unsigned int sw_to_end = rec->sw_buffer_size - rec->sw_data;
303 
304 		/* bytes: number of bytes we have available (for reading) */
305 		unsigned int bytes = rec->sw_ready;
306 
307 		if (sw_to_end < bytes)
308 			bytes = sw_to_end;
309 		if (!bytes)
310 			break;
311 
312 #ifdef SND_PCM_INDIRECT2_STAT
313 		if (rec->firstbytetime == 0)
314 			rec->firstbytetime = jiffies;
315 		rec->lastbytetime = jiffies;
316 #endif
317 		/* copy bytes from intermediate buffer position sw_data to the
318 		 * HW and return number of bytes actually written
319 		 * Furthermore, set hw_ready to 0, if the fifo isn't empty
320 		 * now => more could be transferred to fifo
321 		 */
322 		bytes = copy(substream, rec, bytes);
323 		rec->bytes2hw += bytes;
324 
325 #ifdef SND_PCM_INDIRECT2_STAT
326 		if (bytes < 64)
327 			rec->byte_sizes[bytes]++;
328 		else
329 			snd_printk(KERN_DEBUG
330 				   "STAT: %d Bytes copied to hardware at once "
331 				   "- too big to save!\n",
332 				   bytes);
333 #endif
334 		/* increase sw_data by the number of actually written bytes
335 		 * (= number of taken bytes from intermediate buffer)
336 		 */
337 		rec->sw_data += bytes;
338 		if (rec->sw_data == rec->sw_buffer_size)
339 			rec->sw_data = 0;
340 		/* now sw_data is the position where ALSA is going to write
341 		 * in the intermediate buffer next time = position we are going
342 		 * to read from next time
343 		 */
344 
345 		snd_pcm_indirect2_increase_min_periods(substream, rec, 1, 1,
346 						       bytes);
347 
348 		/* we read bytes from intermediate buffer, so we need to say
349 		 * that the number of bytes ready for transfer are decreased
350 		 * now
351 		 */
352 		rec->sw_ready -= bytes;
353 	}
354 	return;
355 }
356 
357 /*
358  * helper function for playback interrupt routine
359  */
360 void
snd_pcm_indirect2_playback_interrupt(struct snd_pcm_substream * substream,struct snd_pcm_indirect2 * rec,snd_pcm_indirect2_copy_t copy,snd_pcm_indirect2_zero_t zero)361 snd_pcm_indirect2_playback_interrupt(struct snd_pcm_substream *substream,
362 				     struct snd_pcm_indirect2 *rec,
363 				     snd_pcm_indirect2_copy_t copy,
364 				     snd_pcm_indirect2_zero_t zero)
365 {
366 #ifdef SND_PCM_INDIRECT2_STAT
367 	rec->irq_occured++;
368 #endif
369 	/* hardware played some bytes, so there is room again (in fifo) */
370 	rec->hw_ready = 1;
371 
372 	/* don't call ack() now, instead call transfer() function directly
373 	 * (normally called by ack() )
374 	 */
375 	snd_pcm_indirect2_playback_transfer(substream, rec, copy, zero);
376 
377 	if (rec->min_periods >= rec->min_multiple) {
378 #ifdef SND_PCM_INDIRECT2_STAT
379 		if ((rec->min_periods / rec->min_multiple) > 7)
380 			snd_printk(KERN_DEBUG
381 				   "STAT: more than 7 (%d) mul_adds - too big "
382 				   "to save!\n",
383 				   (rec->min_periods / rec->min_multiple));
384 		else
385 			rec->mul_adds[(rec->min_periods /
386 				       rec->min_multiple)]++;
387 		rec->mul_elapsed_real += (rec->min_periods /
388 					  rec->min_multiple);
389 		rec->mul_elapsed++;
390 #endif
391 		rec->min_periods = (rec->min_periods % rec->min_multiple);
392 		snd_pcm_period_elapsed(substream);
393 	}
394 }
395 
396 /*
397  * _internal_ helper function for capture interrupt callback
398  */
399 static void
snd_pcm_indirect2_capture_transfer(struct snd_pcm_substream * substream,struct snd_pcm_indirect2 * rec,snd_pcm_indirect2_copy_t copy,snd_pcm_indirect2_zero_t null)400 snd_pcm_indirect2_capture_transfer(struct snd_pcm_substream *substream,
401 				   struct snd_pcm_indirect2 *rec,
402 				   snd_pcm_indirect2_copy_t copy,
403 				   snd_pcm_indirect2_zero_t null)
404 {
405 	struct snd_pcm_runtime *runtime = substream->runtime;
406 	snd_pcm_uframes_t appl_ptr = runtime->control->appl_ptr;
407 	snd_pcm_sframes_t diff = appl_ptr - rec->appl_ptr;
408 
409 	if (diff) {
410 #ifdef SND_PCM_INDIRECT2_STAT
411 		rec->lastdifftime = jiffies;
412 #endif
413 		if (diff < -(snd_pcm_sframes_t) (runtime->boundary / 2))
414 			diff += runtime->boundary;
415 		rec->sw_ready -= frames_to_bytes(runtime, diff);
416 		rec->appl_ptr = appl_ptr;
417 	}
418 	/* if hardware has something, but the intermediate buffer is full
419 	 * => skip contents of buffer
420 	 */
421 	if (rec->hw_ready && (rec->sw_ready >= (int)rec->sw_buffer_size)) {
422 		unsigned int bytes;
423 
424 #ifdef SND_PCM_INDIRECT2_STAT
425 		if (rec->firstzerotime == 0) {
426 			rec->firstzerotime = jiffies;
427 			snd_printk(KERN_DEBUG "STAT: (capture) "
428 				   "@firstzerotime: mul_elapsed: %d, "
429 				   "min_period_count: %d\n",
430 				   rec->mul_elapsed, rec->min_period_count);
431 			snd_printk(KERN_DEBUG "STAT: (capture) "
432 				   "@firstzerotime: sw_io: %d, sw_data: %d, "
433 				   "appl_ptr: %u\n",
434 				   rec->sw_io, rec->sw_data,
435 				   (unsigned int)appl_ptr);
436 		}
437 		if ((jiffies - rec->firstzerotime) < 3750) {
438 			rec->zero_times[(jiffies - rec->firstzerotime)]++;
439 			rec->zero_times_saved++;
440 		} else
441 			rec->zero_times_notsaved++;
442 #endif
443 		bytes = null(substream, rec);
444 
445 #ifdef SND_PCM_INDIRECT2_STAT
446 		rec->zeros2hw += bytes;
447 		if (bytes < 64)
448 			rec->zero_sizes[bytes]++;
449 		else
450 			snd_printk(KERN_DEBUG
451 				   "STAT: (capture) %d zero Bytes copied to "
452 				   "hardware at once - too big to save!\n",
453 				   bytes);
454 #endif
455 		snd_pcm_indirect2_increase_min_periods(substream, rec, 0, 0,
456 						       bytes);
457 		/* report an overrun */
458 		rec->sw_io = SNDRV_PCM_POS_XRUN;
459 		return;
460 	}
461 	while (rec->hw_ready && (rec->sw_ready < (int)rec->sw_buffer_size)) {
462 		/* sw_to_end: max. number of bytes that we can write to the
463 		 *  intermediate buffer (until it's end)
464 		 */
465 		size_t sw_to_end = rec->sw_buffer_size - rec->sw_data;
466 
467 		/* bytes: max. number of bytes, which may be copied to the
468 		 *  intermediate buffer without overflow (in _one_ step)
469 		 */
470 		size_t bytes = rec->sw_buffer_size - rec->sw_ready;
471 
472 		/* limit number of bytes (for transfer) by available room in
473 		 * the intermediate buffer
474 		 */
475 		if (sw_to_end < bytes)
476 			bytes = sw_to_end;
477 		if (!bytes)
478 			break;
479 
480 #ifdef SND_PCM_INDIRECT2_STAT
481 		if (rec->firstbytetime == 0)
482 			rec->firstbytetime = jiffies;
483 		rec->lastbytetime = jiffies;
484 #endif
485 		/* copy bytes from the intermediate buffer (position sw_data)
486 		 * to the HW at most and return number of bytes actually copied
487 		 * from HW
488 		 * Furthermore, set hw_ready to 0, if the fifo is empty now.
489 		 */
490 		bytes = copy(substream, rec, bytes);
491 		rec->bytes2hw += bytes;
492 
493 #ifdef SND_PCM_INDIRECT2_STAT
494 		if (bytes < 64)
495 			rec->byte_sizes[bytes]++;
496 		else
497 			snd_printk(KERN_DEBUG
498 				   "STAT: (capture) %d Bytes copied to "
499 				   "hardware at once - too big to save!\n",
500 				   bytes);
501 #endif
502 		/* increase sw_data by the number of actually copied bytes from
503 		 * HW
504 		 */
505 		rec->sw_data += bytes;
506 		if (rec->sw_data == rec->sw_buffer_size)
507 			rec->sw_data = 0;
508 
509 		snd_pcm_indirect2_increase_min_periods(substream, rec, 0, 1,
510 						       bytes);
511 
512 		/* number of bytes in the intermediate buffer, which haven't
513 		 * been fetched by ALSA yet.
514 		 */
515 		rec->sw_ready += bytes;
516 	}
517 	return;
518 }
519 
520 /*
521  * helper function for capture interrupt routine
522  */
523 void
snd_pcm_indirect2_capture_interrupt(struct snd_pcm_substream * substream,struct snd_pcm_indirect2 * rec,snd_pcm_indirect2_copy_t copy,snd_pcm_indirect2_zero_t null)524 snd_pcm_indirect2_capture_interrupt(struct snd_pcm_substream *substream,
525 				    struct snd_pcm_indirect2 *rec,
526 				    snd_pcm_indirect2_copy_t copy,
527 				    snd_pcm_indirect2_zero_t null)
528 {
529 #ifdef SND_PCM_INDIRECT2_STAT
530 	rec->irq_occured++;
531 #endif
532 	/* hardware recorded some bytes, so there is something to read from the
533 	 * record fifo:
534 	 */
535 	rec->hw_ready = 1;
536 
537 	/* don't call ack() now, instead call transfer() function directly
538 	 * (normally called by ack() )
539 	 */
540 	snd_pcm_indirect2_capture_transfer(substream, rec, copy, null);
541 
542 	if (rec->min_periods >= rec->min_multiple) {
543 
544 #ifdef SND_PCM_INDIRECT2_STAT
545 		if ((rec->min_periods / rec->min_multiple) > 7)
546 			snd_printk(KERN_DEBUG
547 				   "STAT: more than 7 (%d) mul_adds - "
548 				   "too big to save!\n",
549 				   (rec->min_periods / rec->min_multiple));
550 		else
551 			rec->mul_adds[(rec->min_periods /
552 				       rec->min_multiple)]++;
553 		rec->mul_elapsed_real += (rec->min_periods /
554 					  rec->min_multiple);
555 		rec->mul_elapsed++;
556 #endif
557 		rec->min_periods = (rec->min_periods % rec->min_multiple);
558 		snd_pcm_period_elapsed(substream);
559 	}
560 }
561