162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * ALSA timer back-end using hrtimer
462306a36Sopenharmony_ci * Copyright (C) 2008 Takashi Iwai
562306a36Sopenharmony_ci */
662306a36Sopenharmony_ci
762306a36Sopenharmony_ci#include <linux/init.h>
862306a36Sopenharmony_ci#include <linux/slab.h>
962306a36Sopenharmony_ci#include <linux/module.h>
1062306a36Sopenharmony_ci#include <linux/moduleparam.h>
1162306a36Sopenharmony_ci#include <linux/hrtimer.h>
1262306a36Sopenharmony_ci#include <sound/core.h>
1362306a36Sopenharmony_ci#include <sound/timer.h>
1462306a36Sopenharmony_ci
1562306a36Sopenharmony_ciMODULE_AUTHOR("Takashi Iwai <tiwai@suse.de>");
1662306a36Sopenharmony_ciMODULE_DESCRIPTION("ALSA hrtimer backend");
1762306a36Sopenharmony_ciMODULE_LICENSE("GPL");
1862306a36Sopenharmony_ci
1962306a36Sopenharmony_ciMODULE_ALIAS("snd-timer-" __stringify(SNDRV_TIMER_GLOBAL_HRTIMER));
2062306a36Sopenharmony_ci
2162306a36Sopenharmony_ci#define NANO_SEC	1000000000UL	/* 10^9 in sec */
2262306a36Sopenharmony_cistatic unsigned int resolution;
2362306a36Sopenharmony_ci
2462306a36Sopenharmony_cistruct snd_hrtimer {
2562306a36Sopenharmony_ci	struct snd_timer *timer;
2662306a36Sopenharmony_ci	struct hrtimer hrt;
2762306a36Sopenharmony_ci	bool in_callback;
2862306a36Sopenharmony_ci};
2962306a36Sopenharmony_ci
3062306a36Sopenharmony_cistatic enum hrtimer_restart snd_hrtimer_callback(struct hrtimer *hrt)
3162306a36Sopenharmony_ci{
3262306a36Sopenharmony_ci	struct snd_hrtimer *stime = container_of(hrt, struct snd_hrtimer, hrt);
3362306a36Sopenharmony_ci	struct snd_timer *t = stime->timer;
3462306a36Sopenharmony_ci	ktime_t delta;
3562306a36Sopenharmony_ci	unsigned long ticks;
3662306a36Sopenharmony_ci	enum hrtimer_restart ret = HRTIMER_NORESTART;
3762306a36Sopenharmony_ci
3862306a36Sopenharmony_ci	spin_lock(&t->lock);
3962306a36Sopenharmony_ci	if (!t->running)
4062306a36Sopenharmony_ci		goto out; /* fast path */
4162306a36Sopenharmony_ci	stime->in_callback = true;
4262306a36Sopenharmony_ci	ticks = t->sticks;
4362306a36Sopenharmony_ci	spin_unlock(&t->lock);
4462306a36Sopenharmony_ci
4562306a36Sopenharmony_ci	/* calculate the drift */
4662306a36Sopenharmony_ci	delta = ktime_sub(hrt->base->get_time(), hrtimer_get_expires(hrt));
4762306a36Sopenharmony_ci	if (delta > 0)
4862306a36Sopenharmony_ci		ticks += ktime_divns(delta, ticks * resolution);
4962306a36Sopenharmony_ci
5062306a36Sopenharmony_ci	snd_timer_interrupt(stime->timer, ticks);
5162306a36Sopenharmony_ci
5262306a36Sopenharmony_ci	spin_lock(&t->lock);
5362306a36Sopenharmony_ci	if (t->running) {
5462306a36Sopenharmony_ci		hrtimer_add_expires_ns(hrt, t->sticks * resolution);
5562306a36Sopenharmony_ci		ret = HRTIMER_RESTART;
5662306a36Sopenharmony_ci	}
5762306a36Sopenharmony_ci
5862306a36Sopenharmony_ci	stime->in_callback = false;
5962306a36Sopenharmony_ci out:
6062306a36Sopenharmony_ci	spin_unlock(&t->lock);
6162306a36Sopenharmony_ci	return ret;
6262306a36Sopenharmony_ci}
6362306a36Sopenharmony_ci
6462306a36Sopenharmony_cistatic int snd_hrtimer_open(struct snd_timer *t)
6562306a36Sopenharmony_ci{
6662306a36Sopenharmony_ci	struct snd_hrtimer *stime;
6762306a36Sopenharmony_ci
6862306a36Sopenharmony_ci	stime = kzalloc(sizeof(*stime), GFP_KERNEL);
6962306a36Sopenharmony_ci	if (!stime)
7062306a36Sopenharmony_ci		return -ENOMEM;
7162306a36Sopenharmony_ci	hrtimer_init(&stime->hrt, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
7262306a36Sopenharmony_ci	stime->timer = t;
7362306a36Sopenharmony_ci	stime->hrt.function = snd_hrtimer_callback;
7462306a36Sopenharmony_ci	t->private_data = stime;
7562306a36Sopenharmony_ci	return 0;
7662306a36Sopenharmony_ci}
7762306a36Sopenharmony_ci
7862306a36Sopenharmony_cistatic int snd_hrtimer_close(struct snd_timer *t)
7962306a36Sopenharmony_ci{
8062306a36Sopenharmony_ci	struct snd_hrtimer *stime = t->private_data;
8162306a36Sopenharmony_ci
8262306a36Sopenharmony_ci	if (stime) {
8362306a36Sopenharmony_ci		spin_lock_irq(&t->lock);
8462306a36Sopenharmony_ci		t->running = 0; /* just to be sure */
8562306a36Sopenharmony_ci		stime->in_callback = 1; /* skip start/stop */
8662306a36Sopenharmony_ci		spin_unlock_irq(&t->lock);
8762306a36Sopenharmony_ci
8862306a36Sopenharmony_ci		hrtimer_cancel(&stime->hrt);
8962306a36Sopenharmony_ci		kfree(stime);
9062306a36Sopenharmony_ci		t->private_data = NULL;
9162306a36Sopenharmony_ci	}
9262306a36Sopenharmony_ci	return 0;
9362306a36Sopenharmony_ci}
9462306a36Sopenharmony_ci
9562306a36Sopenharmony_cistatic int snd_hrtimer_start(struct snd_timer *t)
9662306a36Sopenharmony_ci{
9762306a36Sopenharmony_ci	struct snd_hrtimer *stime = t->private_data;
9862306a36Sopenharmony_ci
9962306a36Sopenharmony_ci	if (stime->in_callback)
10062306a36Sopenharmony_ci		return 0;
10162306a36Sopenharmony_ci	hrtimer_start(&stime->hrt, ns_to_ktime(t->sticks * resolution),
10262306a36Sopenharmony_ci		      HRTIMER_MODE_REL);
10362306a36Sopenharmony_ci	return 0;
10462306a36Sopenharmony_ci}
10562306a36Sopenharmony_ci
10662306a36Sopenharmony_cistatic int snd_hrtimer_stop(struct snd_timer *t)
10762306a36Sopenharmony_ci{
10862306a36Sopenharmony_ci	struct snd_hrtimer *stime = t->private_data;
10962306a36Sopenharmony_ci
11062306a36Sopenharmony_ci	if (stime->in_callback)
11162306a36Sopenharmony_ci		return 0;
11262306a36Sopenharmony_ci	hrtimer_try_to_cancel(&stime->hrt);
11362306a36Sopenharmony_ci	return 0;
11462306a36Sopenharmony_ci}
11562306a36Sopenharmony_ci
11662306a36Sopenharmony_cistatic const struct snd_timer_hardware hrtimer_hw __initconst = {
11762306a36Sopenharmony_ci	.flags =	SNDRV_TIMER_HW_AUTO | SNDRV_TIMER_HW_WORK,
11862306a36Sopenharmony_ci	.open =		snd_hrtimer_open,
11962306a36Sopenharmony_ci	.close =	snd_hrtimer_close,
12062306a36Sopenharmony_ci	.start =	snd_hrtimer_start,
12162306a36Sopenharmony_ci	.stop =		snd_hrtimer_stop,
12262306a36Sopenharmony_ci};
12362306a36Sopenharmony_ci
12462306a36Sopenharmony_ci/*
12562306a36Sopenharmony_ci * entry functions
12662306a36Sopenharmony_ci */
12762306a36Sopenharmony_ci
12862306a36Sopenharmony_cistatic struct snd_timer *mytimer;
12962306a36Sopenharmony_ci
13062306a36Sopenharmony_cistatic int __init snd_hrtimer_init(void)
13162306a36Sopenharmony_ci{
13262306a36Sopenharmony_ci	struct snd_timer *timer;
13362306a36Sopenharmony_ci	int err;
13462306a36Sopenharmony_ci
13562306a36Sopenharmony_ci	resolution = hrtimer_resolution;
13662306a36Sopenharmony_ci
13762306a36Sopenharmony_ci	/* Create a new timer and set up the fields */
13862306a36Sopenharmony_ci	err = snd_timer_global_new("hrtimer", SNDRV_TIMER_GLOBAL_HRTIMER,
13962306a36Sopenharmony_ci				   &timer);
14062306a36Sopenharmony_ci	if (err < 0)
14162306a36Sopenharmony_ci		return err;
14262306a36Sopenharmony_ci
14362306a36Sopenharmony_ci	timer->module = THIS_MODULE;
14462306a36Sopenharmony_ci	strcpy(timer->name, "HR timer");
14562306a36Sopenharmony_ci	timer->hw = hrtimer_hw;
14662306a36Sopenharmony_ci	timer->hw.resolution = resolution;
14762306a36Sopenharmony_ci	timer->hw.ticks = NANO_SEC / resolution;
14862306a36Sopenharmony_ci	timer->max_instances = 100; /* lower the limit */
14962306a36Sopenharmony_ci
15062306a36Sopenharmony_ci	err = snd_timer_global_register(timer);
15162306a36Sopenharmony_ci	if (err < 0) {
15262306a36Sopenharmony_ci		snd_timer_global_free(timer);
15362306a36Sopenharmony_ci		return err;
15462306a36Sopenharmony_ci	}
15562306a36Sopenharmony_ci	mytimer = timer; /* remember this */
15662306a36Sopenharmony_ci
15762306a36Sopenharmony_ci	return 0;
15862306a36Sopenharmony_ci}
15962306a36Sopenharmony_ci
16062306a36Sopenharmony_cistatic void __exit snd_hrtimer_exit(void)
16162306a36Sopenharmony_ci{
16262306a36Sopenharmony_ci	if (mytimer) {
16362306a36Sopenharmony_ci		snd_timer_global_free(mytimer);
16462306a36Sopenharmony_ci		mytimer = NULL;
16562306a36Sopenharmony_ci	}
16662306a36Sopenharmony_ci}
16762306a36Sopenharmony_ci
16862306a36Sopenharmony_cimodule_init(snd_hrtimer_init);
16962306a36Sopenharmony_cimodule_exit(snd_hrtimer_exit);
170