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