18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci *  Digital Audio (PCM) abstract layer
48c2ecf20Sopenharmony_ci *  Copyright (c) by Jaroslav Kysela <perex@perex.cz>
58c2ecf20Sopenharmony_ci */
68c2ecf20Sopenharmony_ci
78c2ecf20Sopenharmony_ci#include <linux/time.h>
88c2ecf20Sopenharmony_ci#include <linux/gcd.h>
98c2ecf20Sopenharmony_ci#include <sound/core.h>
108c2ecf20Sopenharmony_ci#include <sound/pcm.h>
118c2ecf20Sopenharmony_ci#include <sound/timer.h>
128c2ecf20Sopenharmony_ci
138c2ecf20Sopenharmony_ci#include "pcm_local.h"
148c2ecf20Sopenharmony_ci
158c2ecf20Sopenharmony_ci/*
168c2ecf20Sopenharmony_ci *  Timer functions
178c2ecf20Sopenharmony_ci */
188c2ecf20Sopenharmony_ci
198c2ecf20Sopenharmony_civoid snd_pcm_timer_resolution_change(struct snd_pcm_substream *substream)
208c2ecf20Sopenharmony_ci{
218c2ecf20Sopenharmony_ci	unsigned long rate, mult, fsize, l, post;
228c2ecf20Sopenharmony_ci	struct snd_pcm_runtime *runtime = substream->runtime;
238c2ecf20Sopenharmony_ci
248c2ecf20Sopenharmony_ci	mult = 1000000000;
258c2ecf20Sopenharmony_ci	rate = runtime->rate;
268c2ecf20Sopenharmony_ci	if (snd_BUG_ON(!rate))
278c2ecf20Sopenharmony_ci		return;
288c2ecf20Sopenharmony_ci	l = gcd(mult, rate);
298c2ecf20Sopenharmony_ci	mult /= l;
308c2ecf20Sopenharmony_ci	rate /= l;
318c2ecf20Sopenharmony_ci	fsize = runtime->period_size;
328c2ecf20Sopenharmony_ci	if (snd_BUG_ON(!fsize))
338c2ecf20Sopenharmony_ci		return;
348c2ecf20Sopenharmony_ci	l = gcd(rate, fsize);
358c2ecf20Sopenharmony_ci	rate /= l;
368c2ecf20Sopenharmony_ci	fsize /= l;
378c2ecf20Sopenharmony_ci	post = 1;
388c2ecf20Sopenharmony_ci	while ((mult * fsize) / fsize != mult) {
398c2ecf20Sopenharmony_ci		mult /= 2;
408c2ecf20Sopenharmony_ci		post *= 2;
418c2ecf20Sopenharmony_ci	}
428c2ecf20Sopenharmony_ci	if (rate == 0) {
438c2ecf20Sopenharmony_ci		pcm_err(substream->pcm,
448c2ecf20Sopenharmony_ci			"pcm timer resolution out of range (rate = %u, period_size = %lu)\n",
458c2ecf20Sopenharmony_ci			runtime->rate, runtime->period_size);
468c2ecf20Sopenharmony_ci		runtime->timer_resolution = -1;
478c2ecf20Sopenharmony_ci		return;
488c2ecf20Sopenharmony_ci	}
498c2ecf20Sopenharmony_ci	runtime->timer_resolution = (mult * fsize / rate) * post;
508c2ecf20Sopenharmony_ci}
518c2ecf20Sopenharmony_ci
528c2ecf20Sopenharmony_cistatic unsigned long snd_pcm_timer_resolution(struct snd_timer * timer)
538c2ecf20Sopenharmony_ci{
548c2ecf20Sopenharmony_ci	struct snd_pcm_substream *substream;
558c2ecf20Sopenharmony_ci
568c2ecf20Sopenharmony_ci	substream = timer->private_data;
578c2ecf20Sopenharmony_ci	return substream->runtime ? substream->runtime->timer_resolution : 0;
588c2ecf20Sopenharmony_ci}
598c2ecf20Sopenharmony_ci
608c2ecf20Sopenharmony_cistatic int snd_pcm_timer_start(struct snd_timer * timer)
618c2ecf20Sopenharmony_ci{
628c2ecf20Sopenharmony_ci	struct snd_pcm_substream *substream;
638c2ecf20Sopenharmony_ci
648c2ecf20Sopenharmony_ci	substream = snd_timer_chip(timer);
658c2ecf20Sopenharmony_ci	substream->timer_running = 1;
668c2ecf20Sopenharmony_ci	return 0;
678c2ecf20Sopenharmony_ci}
688c2ecf20Sopenharmony_ci
698c2ecf20Sopenharmony_cistatic int snd_pcm_timer_stop(struct snd_timer * timer)
708c2ecf20Sopenharmony_ci{
718c2ecf20Sopenharmony_ci	struct snd_pcm_substream *substream;
728c2ecf20Sopenharmony_ci
738c2ecf20Sopenharmony_ci	substream = snd_timer_chip(timer);
748c2ecf20Sopenharmony_ci	substream->timer_running = 0;
758c2ecf20Sopenharmony_ci	return 0;
768c2ecf20Sopenharmony_ci}
778c2ecf20Sopenharmony_ci
788c2ecf20Sopenharmony_cistatic const struct snd_timer_hardware snd_pcm_timer =
798c2ecf20Sopenharmony_ci{
808c2ecf20Sopenharmony_ci	.flags =	SNDRV_TIMER_HW_AUTO | SNDRV_TIMER_HW_SLAVE,
818c2ecf20Sopenharmony_ci	.resolution =	0,
828c2ecf20Sopenharmony_ci	.ticks =	1,
838c2ecf20Sopenharmony_ci	.c_resolution =	snd_pcm_timer_resolution,
848c2ecf20Sopenharmony_ci	.start =	snd_pcm_timer_start,
858c2ecf20Sopenharmony_ci	.stop =		snd_pcm_timer_stop,
868c2ecf20Sopenharmony_ci};
878c2ecf20Sopenharmony_ci
888c2ecf20Sopenharmony_ci/*
898c2ecf20Sopenharmony_ci *  Init functions
908c2ecf20Sopenharmony_ci */
918c2ecf20Sopenharmony_ci
928c2ecf20Sopenharmony_cistatic void snd_pcm_timer_free(struct snd_timer *timer)
938c2ecf20Sopenharmony_ci{
948c2ecf20Sopenharmony_ci	struct snd_pcm_substream *substream = timer->private_data;
958c2ecf20Sopenharmony_ci	substream->timer = NULL;
968c2ecf20Sopenharmony_ci}
978c2ecf20Sopenharmony_ci
988c2ecf20Sopenharmony_civoid snd_pcm_timer_init(struct snd_pcm_substream *substream)
998c2ecf20Sopenharmony_ci{
1008c2ecf20Sopenharmony_ci	struct snd_timer_id tid;
1018c2ecf20Sopenharmony_ci	struct snd_timer *timer;
1028c2ecf20Sopenharmony_ci
1038c2ecf20Sopenharmony_ci	tid.dev_sclass = SNDRV_TIMER_SCLASS_NONE;
1048c2ecf20Sopenharmony_ci	tid.dev_class = SNDRV_TIMER_CLASS_PCM;
1058c2ecf20Sopenharmony_ci	tid.card = substream->pcm->card->number;
1068c2ecf20Sopenharmony_ci	tid.device = substream->pcm->device;
1078c2ecf20Sopenharmony_ci	tid.subdevice = (substream->number << 1) | (substream->stream & 1);
1088c2ecf20Sopenharmony_ci	if (snd_timer_new(substream->pcm->card, "PCM", &tid, &timer) < 0)
1098c2ecf20Sopenharmony_ci		return;
1108c2ecf20Sopenharmony_ci	sprintf(timer->name, "PCM %s %i-%i-%i",
1118c2ecf20Sopenharmony_ci			substream->stream == SNDRV_PCM_STREAM_CAPTURE ?
1128c2ecf20Sopenharmony_ci				"capture" : "playback",
1138c2ecf20Sopenharmony_ci			tid.card, tid.device, tid.subdevice);
1148c2ecf20Sopenharmony_ci	timer->hw = snd_pcm_timer;
1158c2ecf20Sopenharmony_ci	if (snd_device_register(timer->card, timer) < 0) {
1168c2ecf20Sopenharmony_ci		snd_device_free(timer->card, timer);
1178c2ecf20Sopenharmony_ci		return;
1188c2ecf20Sopenharmony_ci	}
1198c2ecf20Sopenharmony_ci	timer->private_data = substream;
1208c2ecf20Sopenharmony_ci	timer->private_free = snd_pcm_timer_free;
1218c2ecf20Sopenharmony_ci	substream->timer = timer;
1228c2ecf20Sopenharmony_ci}
1238c2ecf20Sopenharmony_ci
1248c2ecf20Sopenharmony_civoid snd_pcm_timer_done(struct snd_pcm_substream *substream)
1258c2ecf20Sopenharmony_ci{
1268c2ecf20Sopenharmony_ci	if (substream->timer) {
1278c2ecf20Sopenharmony_ci		snd_device_free(substream->pcm->card, substream->timer);
1288c2ecf20Sopenharmony_ci		substream->timer = NULL;
1298c2ecf20Sopenharmony_ci	}
1308c2ecf20Sopenharmony_ci}
131