18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * ALSA timer back-end using hrtimer
48c2ecf20Sopenharmony_ci * Copyright (C) 2008 Takashi Iwai
58c2ecf20Sopenharmony_ci */
68c2ecf20Sopenharmony_ci
78c2ecf20Sopenharmony_ci#include <linux/init.h>
88c2ecf20Sopenharmony_ci#include <linux/slab.h>
98c2ecf20Sopenharmony_ci#include <linux/module.h>
108c2ecf20Sopenharmony_ci#include <linux/moduleparam.h>
118c2ecf20Sopenharmony_ci#include <linux/hrtimer.h>
128c2ecf20Sopenharmony_ci#include <sound/core.h>
138c2ecf20Sopenharmony_ci#include <sound/timer.h>
148c2ecf20Sopenharmony_ci
158c2ecf20Sopenharmony_ciMODULE_AUTHOR("Takashi Iwai <tiwai@suse.de>");
168c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("ALSA hrtimer backend");
178c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL");
188c2ecf20Sopenharmony_ci
198c2ecf20Sopenharmony_ciMODULE_ALIAS("snd-timer-" __stringify(SNDRV_TIMER_GLOBAL_HRTIMER));
208c2ecf20Sopenharmony_ci
218c2ecf20Sopenharmony_ci#define NANO_SEC	1000000000UL	/* 10^9 in sec */
228c2ecf20Sopenharmony_cistatic unsigned int resolution;
238c2ecf20Sopenharmony_ci
248c2ecf20Sopenharmony_cistruct snd_hrtimer {
258c2ecf20Sopenharmony_ci	struct snd_timer *timer;
268c2ecf20Sopenharmony_ci	struct hrtimer hrt;
278c2ecf20Sopenharmony_ci	bool in_callback;
288c2ecf20Sopenharmony_ci};
298c2ecf20Sopenharmony_ci
308c2ecf20Sopenharmony_cistatic enum hrtimer_restart snd_hrtimer_callback(struct hrtimer *hrt)
318c2ecf20Sopenharmony_ci{
328c2ecf20Sopenharmony_ci	struct snd_hrtimer *stime = container_of(hrt, struct snd_hrtimer, hrt);
338c2ecf20Sopenharmony_ci	struct snd_timer *t = stime->timer;
348c2ecf20Sopenharmony_ci	ktime_t delta;
358c2ecf20Sopenharmony_ci	unsigned long ticks;
368c2ecf20Sopenharmony_ci	enum hrtimer_restart ret = HRTIMER_NORESTART;
378c2ecf20Sopenharmony_ci
388c2ecf20Sopenharmony_ci	spin_lock(&t->lock);
398c2ecf20Sopenharmony_ci	if (!t->running)
408c2ecf20Sopenharmony_ci		goto out; /* fast path */
418c2ecf20Sopenharmony_ci	stime->in_callback = true;
428c2ecf20Sopenharmony_ci	ticks = t->sticks;
438c2ecf20Sopenharmony_ci	spin_unlock(&t->lock);
448c2ecf20Sopenharmony_ci
458c2ecf20Sopenharmony_ci	/* calculate the drift */
468c2ecf20Sopenharmony_ci	delta = ktime_sub(hrt->base->get_time(), hrtimer_get_expires(hrt));
478c2ecf20Sopenharmony_ci	if (delta > 0)
488c2ecf20Sopenharmony_ci		ticks += ktime_divns(delta, ticks * resolution);
498c2ecf20Sopenharmony_ci
508c2ecf20Sopenharmony_ci	snd_timer_interrupt(stime->timer, ticks);
518c2ecf20Sopenharmony_ci
528c2ecf20Sopenharmony_ci	spin_lock(&t->lock);
538c2ecf20Sopenharmony_ci	if (t->running) {
548c2ecf20Sopenharmony_ci		hrtimer_add_expires_ns(hrt, t->sticks * resolution);
558c2ecf20Sopenharmony_ci		ret = HRTIMER_RESTART;
568c2ecf20Sopenharmony_ci	}
578c2ecf20Sopenharmony_ci
588c2ecf20Sopenharmony_ci	stime->in_callback = false;
598c2ecf20Sopenharmony_ci out:
608c2ecf20Sopenharmony_ci	spin_unlock(&t->lock);
618c2ecf20Sopenharmony_ci	return ret;
628c2ecf20Sopenharmony_ci}
638c2ecf20Sopenharmony_ci
648c2ecf20Sopenharmony_cistatic int snd_hrtimer_open(struct snd_timer *t)
658c2ecf20Sopenharmony_ci{
668c2ecf20Sopenharmony_ci	struct snd_hrtimer *stime;
678c2ecf20Sopenharmony_ci
688c2ecf20Sopenharmony_ci	stime = kzalloc(sizeof(*stime), GFP_KERNEL);
698c2ecf20Sopenharmony_ci	if (!stime)
708c2ecf20Sopenharmony_ci		return -ENOMEM;
718c2ecf20Sopenharmony_ci	hrtimer_init(&stime->hrt, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
728c2ecf20Sopenharmony_ci	stime->timer = t;
738c2ecf20Sopenharmony_ci	stime->hrt.function = snd_hrtimer_callback;
748c2ecf20Sopenharmony_ci	t->private_data = stime;
758c2ecf20Sopenharmony_ci	return 0;
768c2ecf20Sopenharmony_ci}
778c2ecf20Sopenharmony_ci
788c2ecf20Sopenharmony_cistatic int snd_hrtimer_close(struct snd_timer *t)
798c2ecf20Sopenharmony_ci{
808c2ecf20Sopenharmony_ci	struct snd_hrtimer *stime = t->private_data;
818c2ecf20Sopenharmony_ci
828c2ecf20Sopenharmony_ci	if (stime) {
838c2ecf20Sopenharmony_ci		spin_lock_irq(&t->lock);
848c2ecf20Sopenharmony_ci		t->running = 0; /* just to be sure */
858c2ecf20Sopenharmony_ci		stime->in_callback = 1; /* skip start/stop */
868c2ecf20Sopenharmony_ci		spin_unlock_irq(&t->lock);
878c2ecf20Sopenharmony_ci
888c2ecf20Sopenharmony_ci		hrtimer_cancel(&stime->hrt);
898c2ecf20Sopenharmony_ci		kfree(stime);
908c2ecf20Sopenharmony_ci		t->private_data = NULL;
918c2ecf20Sopenharmony_ci	}
928c2ecf20Sopenharmony_ci	return 0;
938c2ecf20Sopenharmony_ci}
948c2ecf20Sopenharmony_ci
958c2ecf20Sopenharmony_cistatic int snd_hrtimer_start(struct snd_timer *t)
968c2ecf20Sopenharmony_ci{
978c2ecf20Sopenharmony_ci	struct snd_hrtimer *stime = t->private_data;
988c2ecf20Sopenharmony_ci
998c2ecf20Sopenharmony_ci	if (stime->in_callback)
1008c2ecf20Sopenharmony_ci		return 0;
1018c2ecf20Sopenharmony_ci	hrtimer_start(&stime->hrt, ns_to_ktime(t->sticks * resolution),
1028c2ecf20Sopenharmony_ci		      HRTIMER_MODE_REL);
1038c2ecf20Sopenharmony_ci	return 0;
1048c2ecf20Sopenharmony_ci}
1058c2ecf20Sopenharmony_ci
1068c2ecf20Sopenharmony_cistatic int snd_hrtimer_stop(struct snd_timer *t)
1078c2ecf20Sopenharmony_ci{
1088c2ecf20Sopenharmony_ci	struct snd_hrtimer *stime = t->private_data;
1098c2ecf20Sopenharmony_ci
1108c2ecf20Sopenharmony_ci	if (stime->in_callback)
1118c2ecf20Sopenharmony_ci		return 0;
1128c2ecf20Sopenharmony_ci	hrtimer_try_to_cancel(&stime->hrt);
1138c2ecf20Sopenharmony_ci	return 0;
1148c2ecf20Sopenharmony_ci}
1158c2ecf20Sopenharmony_ci
1168c2ecf20Sopenharmony_cistatic const struct snd_timer_hardware hrtimer_hw __initconst = {
1178c2ecf20Sopenharmony_ci	.flags =	SNDRV_TIMER_HW_AUTO | SNDRV_TIMER_HW_WORK,
1188c2ecf20Sopenharmony_ci	.open =		snd_hrtimer_open,
1198c2ecf20Sopenharmony_ci	.close =	snd_hrtimer_close,
1208c2ecf20Sopenharmony_ci	.start =	snd_hrtimer_start,
1218c2ecf20Sopenharmony_ci	.stop =		snd_hrtimer_stop,
1228c2ecf20Sopenharmony_ci};
1238c2ecf20Sopenharmony_ci
1248c2ecf20Sopenharmony_ci/*
1258c2ecf20Sopenharmony_ci * entry functions
1268c2ecf20Sopenharmony_ci */
1278c2ecf20Sopenharmony_ci
1288c2ecf20Sopenharmony_cistatic struct snd_timer *mytimer;
1298c2ecf20Sopenharmony_ci
1308c2ecf20Sopenharmony_cistatic int __init snd_hrtimer_init(void)
1318c2ecf20Sopenharmony_ci{
1328c2ecf20Sopenharmony_ci	struct snd_timer *timer;
1338c2ecf20Sopenharmony_ci	int err;
1348c2ecf20Sopenharmony_ci
1358c2ecf20Sopenharmony_ci	resolution = hrtimer_resolution;
1368c2ecf20Sopenharmony_ci
1378c2ecf20Sopenharmony_ci	/* Create a new timer and set up the fields */
1388c2ecf20Sopenharmony_ci	err = snd_timer_global_new("hrtimer", SNDRV_TIMER_GLOBAL_HRTIMER,
1398c2ecf20Sopenharmony_ci				   &timer);
1408c2ecf20Sopenharmony_ci	if (err < 0)
1418c2ecf20Sopenharmony_ci		return err;
1428c2ecf20Sopenharmony_ci
1438c2ecf20Sopenharmony_ci	timer->module = THIS_MODULE;
1448c2ecf20Sopenharmony_ci	strcpy(timer->name, "HR timer");
1458c2ecf20Sopenharmony_ci	timer->hw = hrtimer_hw;
1468c2ecf20Sopenharmony_ci	timer->hw.resolution = resolution;
1478c2ecf20Sopenharmony_ci	timer->hw.ticks = NANO_SEC / resolution;
1488c2ecf20Sopenharmony_ci	timer->max_instances = 100; /* lower the limit */
1498c2ecf20Sopenharmony_ci
1508c2ecf20Sopenharmony_ci	err = snd_timer_global_register(timer);
1518c2ecf20Sopenharmony_ci	if (err < 0) {
1528c2ecf20Sopenharmony_ci		snd_timer_global_free(timer);
1538c2ecf20Sopenharmony_ci		return err;
1548c2ecf20Sopenharmony_ci	}
1558c2ecf20Sopenharmony_ci	mytimer = timer; /* remember this */
1568c2ecf20Sopenharmony_ci
1578c2ecf20Sopenharmony_ci	return 0;
1588c2ecf20Sopenharmony_ci}
1598c2ecf20Sopenharmony_ci
1608c2ecf20Sopenharmony_cistatic void __exit snd_hrtimer_exit(void)
1618c2ecf20Sopenharmony_ci{
1628c2ecf20Sopenharmony_ci	if (mytimer) {
1638c2ecf20Sopenharmony_ci		snd_timer_global_free(mytimer);
1648c2ecf20Sopenharmony_ci		mytimer = NULL;
1658c2ecf20Sopenharmony_ci	}
1668c2ecf20Sopenharmony_ci}
1678c2ecf20Sopenharmony_ci
1688c2ecf20Sopenharmony_cimodule_init(snd_hrtimer_init);
1698c2ecf20Sopenharmony_cimodule_exit(snd_hrtimer_exit);
170