1/* 2 * RTC based high-frequency timer 3 * 4 * Copyright (C) 2000 Takashi Iwai 5 * based on rtctimer.c by Steve Ratcliffe 6 * 7 * This program is free software; you can redistribute it and/or modify 8 * it under the terms of the GNU General Public License as published by 9 * the Free Software Foundation; either version 2 of the License, or 10 * (at your option) any later version. 11 * 12 * This program is distributed in the hope that it will be useful, 13 * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 * GNU General Public License for more details. 16 * 17 * You should have received a copy of the GNU General Public License 18 * along with this program; if not, write to the Free Software 19 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 20 * 21 */ 22 23#include <linux/init.h> 24#include <linux/interrupt.h> 25#include <linux/module.h> 26#include <linux/log2.h> 27#include <sound/core.h> 28#include <sound/timer.h> 29 30#if IS_ENABLED(CONFIG_RTC) 31 32#include <linux/mc146818rtc.h> 33 34#define RTC_FREQ 1024 /* default frequency */ 35#define NANO_SEC 1000000000L /* 10^9 in sec */ 36 37/* 38 * prototypes 39 */ 40static int rtctimer_open(struct snd_timer *t); 41static int rtctimer_close(struct snd_timer *t); 42static int rtctimer_start(struct snd_timer *t); 43static int rtctimer_stop(struct snd_timer *t); 44 45 46/* 47 * The hardware dependent description for this timer. 48 */ 49static struct snd_timer_hardware rtc_hw = { 50 .flags = SNDRV_TIMER_HW_AUTO | 51 SNDRV_TIMER_HW_FIRST | 52 SNDRV_TIMER_HW_TASKLET, 53 .ticks = 100000000L, /* FIXME: XXX */ 54 .open = rtctimer_open, 55 .close = rtctimer_close, 56 .start = rtctimer_start, 57 .stop = rtctimer_stop, 58}; 59 60static int rtctimer_freq = RTC_FREQ; /* frequency */ 61static struct snd_timer *rtctimer; 62static struct tasklet_struct rtc_tasklet; 63static rtc_task_t rtc_task; 64 65 66static int 67rtctimer_open(struct snd_timer *t) 68{ 69 int err; 70 71 err = rtc_register(&rtc_task); 72 if (err < 0) 73 return err; 74 t->private_data = &rtc_task; 75 return 0; 76} 77 78static int 79rtctimer_close(struct snd_timer *t) 80{ 81 rtc_task_t *rtc = t->private_data; 82 if (rtc) { 83 rtc_unregister(rtc); 84 tasklet_kill(&rtc_tasklet); 85 t->private_data = NULL; 86 } 87 return 0; 88} 89 90static int 91rtctimer_start(struct snd_timer *timer) 92{ 93 rtc_task_t *rtc = timer->private_data; 94 if (snd_BUG_ON(!rtc)) 95 return -EINVAL; 96 rtc_control(rtc, RTC_IRQP_SET, rtctimer_freq); 97 rtc_control(rtc, RTC_PIE_ON, 0); 98 return 0; 99} 100 101static int 102rtctimer_stop(struct snd_timer *timer) 103{ 104 rtc_task_t *rtc = timer->private_data; 105 if (snd_BUG_ON(!rtc)) 106 return -EINVAL; 107 rtc_control(rtc, RTC_PIE_OFF, 0); 108 return 0; 109} 110 111static void rtctimer_tasklet(unsigned long data) 112{ 113 snd_timer_interrupt((struct snd_timer *)data, 1); 114} 115 116/* 117 * interrupt 118 */ 119static void rtctimer_interrupt(void *private_data) 120{ 121 tasklet_schedule(private_data); 122} 123 124 125/* 126 * ENTRY functions 127 */ 128static int __init rtctimer_init(void) 129{ 130 int err; 131 struct snd_timer *timer; 132 133 if (rtctimer_freq < 2 || rtctimer_freq > 8192 || 134 !is_power_of_2(rtctimer_freq)) { 135 pr_err("ALSA: rtctimer: invalid frequency %d\n", rtctimer_freq); 136 return -EINVAL; 137 } 138 139 /* Create a new timer and set up the fields */ 140 err = snd_timer_global_new("rtc", SNDRV_TIMER_GLOBAL_RTC, &timer); 141 if (err < 0) 142 return err; 143 144 timer->module = THIS_MODULE; 145 strcpy(timer->name, "RTC timer"); 146 timer->hw = rtc_hw; 147 timer->hw.resolution = NANO_SEC / rtctimer_freq; 148 149 tasklet_init(&rtc_tasklet, rtctimer_tasklet, (unsigned long)timer); 150 151 /* set up RTC callback */ 152 rtc_task.func = rtctimer_interrupt; 153 rtc_task.private_data = &rtc_tasklet; 154 155 err = snd_timer_global_register(timer); 156 if (err < 0) { 157 snd_timer_global_free(timer); 158 return err; 159 } 160 rtctimer = timer; /* remember this */ 161 162 return 0; 163} 164 165static void __exit rtctimer_exit(void) 166{ 167 if (rtctimer) { 168 snd_timer_global_free(rtctimer); 169 rtctimer = NULL; 170 } 171} 172 173 174/* 175 * exported stuff 176 */ 177module_init(rtctimer_init) 178module_exit(rtctimer_exit) 179 180module_param(rtctimer_freq, int, 0444); 181MODULE_PARM_DESC(rtctimer_freq, "timer frequency in Hz"); 182 183MODULE_LICENSE("GPL"); 184 185MODULE_ALIAS("snd-timer-" __stringify(SNDRV_TIMER_GLOBAL_RTC)); 186 187#endif /* IS_ENABLED(CONFIG_RTC) */ 188