1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * Digital Audio (PCM) abstract layer |
4 | * Copyright (c) by Jaroslav Kysela <perex@perex.cz> |
5 | */ |
6 | |
7 | #include <linux/time.h> |
8 | #include <linux/gcd.h> |
9 | #include <sound/core.h> |
10 | #include <sound/pcm.h> |
11 | #include <sound/timer.h> |
12 | |
13 | #include "pcm_local.h" |
14 | |
15 | /* |
16 | * Timer functions |
17 | */ |
18 | |
19 | void snd_pcm_timer_resolution_change(struct snd_pcm_substream *substream) |
20 | { |
21 | unsigned long rate, mult, fsize, l, post; |
22 | struct snd_pcm_runtime *runtime = substream->runtime; |
23 | |
24 | mult = 1000000000; |
25 | rate = runtime->rate; |
26 | if (snd_BUG_ON(!rate)) |
27 | return; |
28 | l = gcd(a: mult, b: rate); |
29 | mult /= l; |
30 | rate /= l; |
31 | fsize = runtime->period_size; |
32 | if (snd_BUG_ON(!fsize)) |
33 | return; |
34 | l = gcd(a: rate, b: fsize); |
35 | rate /= l; |
36 | fsize /= l; |
37 | post = 1; |
38 | while ((mult * fsize) / fsize != mult) { |
39 | mult /= 2; |
40 | post *= 2; |
41 | } |
42 | if (rate == 0) { |
43 | pcm_err(substream->pcm, |
44 | "pcm timer resolution out of range (rate = %u, period_size = %lu)\n" , |
45 | runtime->rate, runtime->period_size); |
46 | runtime->timer_resolution = -1; |
47 | return; |
48 | } |
49 | runtime->timer_resolution = (mult * fsize / rate) * post; |
50 | } |
51 | |
52 | static unsigned long snd_pcm_timer_resolution(struct snd_timer * timer) |
53 | { |
54 | struct snd_pcm_substream *substream; |
55 | |
56 | substream = timer->private_data; |
57 | return substream->runtime ? substream->runtime->timer_resolution : 0; |
58 | } |
59 | |
60 | static int snd_pcm_timer_start(struct snd_timer * timer) |
61 | { |
62 | struct snd_pcm_substream *substream; |
63 | |
64 | substream = snd_timer_chip(timer); |
65 | substream->timer_running = 1; |
66 | return 0; |
67 | } |
68 | |
69 | static int snd_pcm_timer_stop(struct snd_timer * timer) |
70 | { |
71 | struct snd_pcm_substream *substream; |
72 | |
73 | substream = snd_timer_chip(timer); |
74 | substream->timer_running = 0; |
75 | return 0; |
76 | } |
77 | |
78 | static const struct snd_timer_hardware snd_pcm_timer = |
79 | { |
80 | .flags = SNDRV_TIMER_HW_AUTO | SNDRV_TIMER_HW_SLAVE, |
81 | .resolution = 0, |
82 | .ticks = 1, |
83 | .c_resolution = snd_pcm_timer_resolution, |
84 | .start = snd_pcm_timer_start, |
85 | .stop = snd_pcm_timer_stop, |
86 | }; |
87 | |
88 | /* |
89 | * Init functions |
90 | */ |
91 | |
92 | static void snd_pcm_timer_free(struct snd_timer *timer) |
93 | { |
94 | struct snd_pcm_substream *substream = timer->private_data; |
95 | substream->timer = NULL; |
96 | } |
97 | |
98 | void snd_pcm_timer_init(struct snd_pcm_substream *substream) |
99 | { |
100 | struct snd_timer_id tid; |
101 | struct snd_timer *timer; |
102 | |
103 | tid.dev_sclass = SNDRV_TIMER_SCLASS_NONE; |
104 | tid.dev_class = SNDRV_TIMER_CLASS_PCM; |
105 | tid.card = substream->pcm->card->number; |
106 | tid.device = substream->pcm->device; |
107 | tid.subdevice = (substream->number << 1) | (substream->stream & 1); |
108 | if (snd_timer_new(card: substream->pcm->card, id: "PCM" , tid: &tid, rtimer: &timer) < 0) |
109 | return; |
110 | sprintf(buf: timer->name, fmt: "PCM %s %i-%i-%i" , |
111 | substream->stream == SNDRV_PCM_STREAM_CAPTURE ? |
112 | "capture" : "playback" , |
113 | tid.card, tid.device, tid.subdevice); |
114 | timer->hw = snd_pcm_timer; |
115 | if (snd_device_register(card: timer->card, device_data: timer) < 0) { |
116 | snd_device_free(card: timer->card, device_data: timer); |
117 | return; |
118 | } |
119 | timer->private_data = substream; |
120 | timer->private_free = snd_pcm_timer_free; |
121 | substream->timer = timer; |
122 | } |
123 | |
124 | void snd_pcm_timer_done(struct snd_pcm_substream *substream) |
125 | { |
126 | if (substream->timer) { |
127 | snd_device_free(card: substream->pcm->card, device_data: substream->timer); |
128 | substream->timer = NULL; |
129 | } |
130 | } |
131 | |