1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * ALSA interface to ivtv PCM capture streams |
4 | * |
5 | * Copyright (C) 2009,2012 Andy Walls <awalls@md.metrocast.net> |
6 | * Copyright (C) 2009 Devin Heitmueller <dheitmueller@kernellabs.com> |
7 | * |
8 | * Portions of this work were sponsored by ONELAN Limited for the cx18 driver |
9 | */ |
10 | |
11 | #include "ivtv-driver.h" |
12 | #include "ivtv-version.h" |
13 | #include "ivtv-alsa.h" |
14 | #include "ivtv-alsa-pcm.h" |
15 | |
16 | #include <sound/core.h> |
17 | #include <sound/initval.h> |
18 | |
19 | int ivtv_alsa_debug; |
20 | static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; |
21 | |
22 | #define IVTV_DEBUG_ALSA_INFO(__fmt, __arg...) \ |
23 | do { \ |
24 | if (ivtv_alsa_debug & 2) \ |
25 | printk(KERN_INFO pr_fmt("%s: alsa:" __fmt), \ |
26 | __func__, ##__arg); \ |
27 | } while (0) |
28 | |
29 | module_param_named(debug, ivtv_alsa_debug, int, 0644); |
30 | MODULE_PARM_DESC(debug, |
31 | "Debug level (bitmask). Default: 0\n" |
32 | "\t\t\t 1/0x0001: warning\n" |
33 | "\t\t\t 2/0x0002: info\n" ); |
34 | |
35 | module_param_array(index, int, NULL, 0444); |
36 | MODULE_PARM_DESC(index, |
37 | "Index value for IVTV ALSA capture interface(s).\n" ); |
38 | |
39 | MODULE_AUTHOR("Andy Walls" ); |
40 | MODULE_DESCRIPTION("CX23415/CX23416 ALSA Interface" ); |
41 | MODULE_LICENSE("GPL" ); |
42 | |
43 | MODULE_VERSION(IVTV_VERSION); |
44 | |
45 | static inline |
46 | struct snd_ivtv_card *to_snd_ivtv_card(struct v4l2_device *v4l2_dev) |
47 | { |
48 | return to_ivtv(v4l2_dev)->alsa; |
49 | } |
50 | |
51 | static void snd_ivtv_card_free(struct snd_ivtv_card *itvsc) |
52 | { |
53 | if (itvsc == NULL) |
54 | return; |
55 | |
56 | if (itvsc->v4l2_dev != NULL) |
57 | to_ivtv(v4l2_dev: itvsc->v4l2_dev)->alsa = NULL; |
58 | |
59 | /* FIXME - take any other stopping actions needed */ |
60 | |
61 | kfree(objp: itvsc); |
62 | } |
63 | |
64 | static void snd_ivtv_card_private_free(struct snd_card *sc) |
65 | { |
66 | if (sc == NULL) |
67 | return; |
68 | snd_ivtv_card_free(itvsc: sc->private_data); |
69 | sc->private_data = NULL; |
70 | sc->private_free = NULL; |
71 | } |
72 | |
73 | static int snd_ivtv_card_create(struct v4l2_device *v4l2_dev, |
74 | struct snd_card *sc, |
75 | struct snd_ivtv_card **itvsc) |
76 | { |
77 | *itvsc = kzalloc(size: sizeof(struct snd_ivtv_card), GFP_KERNEL); |
78 | if (*itvsc == NULL) |
79 | return -ENOMEM; |
80 | |
81 | (*itvsc)->v4l2_dev = v4l2_dev; |
82 | (*itvsc)->sc = sc; |
83 | |
84 | sc->private_data = *itvsc; |
85 | sc->private_free = snd_ivtv_card_private_free; |
86 | |
87 | return 0; |
88 | } |
89 | |
90 | static int snd_ivtv_card_set_names(struct snd_ivtv_card *itvsc) |
91 | { |
92 | struct ivtv *itv = to_ivtv(v4l2_dev: itvsc->v4l2_dev); |
93 | struct snd_card *sc = itvsc->sc; |
94 | |
95 | /* sc->driver is used by alsa-lib's configurator: simple, unique */ |
96 | strscpy(sc->driver, "CX2341[56]" , sizeof(sc->driver)); |
97 | |
98 | /* sc->shortname is a symlink in /proc/asound: IVTV-M -> cardN */ |
99 | snprintf(buf: sc->shortname, size: sizeof(sc->shortname), fmt: "IVTV-%d" , |
100 | itv->instance); |
101 | |
102 | /* sc->longname is read from /proc/asound/cards */ |
103 | snprintf(buf: sc->longname, size: sizeof(sc->longname), |
104 | fmt: "CX2341[56] #%d %s TV/FM Radio/Line-In Capture" , |
105 | itv->instance, itv->card_name); |
106 | |
107 | return 0; |
108 | } |
109 | |
110 | static int snd_ivtv_init(struct v4l2_device *v4l2_dev) |
111 | { |
112 | struct ivtv *itv = to_ivtv(v4l2_dev); |
113 | struct snd_card *sc = NULL; |
114 | struct snd_ivtv_card *itvsc; |
115 | int ret, idx; |
116 | |
117 | /* Numbrs steps from "Writing an ALSA Driver" by Takashi Iwai */ |
118 | |
119 | /* (1) Check and increment the device index */ |
120 | /* This is a no-op for us. We'll use the itv->instance */ |
121 | |
122 | /* (2) Create a card instance */ |
123 | /* use first available id if not specified otherwise*/ |
124 | idx = index[itv->instance] == -1 ? SNDRV_DEFAULT_IDX1 : index[itv->instance]; |
125 | ret = snd_card_new(parent: &itv->pdev->dev, |
126 | idx, |
127 | SNDRV_DEFAULT_STR1, /* xid from end of shortname*/ |
128 | THIS_MODULE, extra_size: 0, card_ret: &sc); |
129 | if (ret) { |
130 | IVTV_ALSA_ERR("%s: snd_card_new() failed with err %d\n" , |
131 | __func__, ret); |
132 | goto err_exit; |
133 | } |
134 | |
135 | /* (3) Create a main component */ |
136 | ret = snd_ivtv_card_create(v4l2_dev, sc, itvsc: &itvsc); |
137 | if (ret) { |
138 | IVTV_ALSA_ERR("%s: snd_ivtv_card_create() failed with err %d\n" , |
139 | __func__, ret); |
140 | goto err_exit_free; |
141 | } |
142 | |
143 | /* (4) Set the driver ID and name strings */ |
144 | snd_ivtv_card_set_names(itvsc); |
145 | |
146 | /* (5) Create other components: PCM, & proc files */ |
147 | ret = snd_ivtv_pcm_create(itvsc); |
148 | if (ret) { |
149 | IVTV_ALSA_ERR("%s: snd_ivtv_pcm_create() failed with err %d\n" , |
150 | __func__, ret); |
151 | goto err_exit_free; |
152 | } |
153 | /* FIXME - proc files */ |
154 | |
155 | /* (7) Set the driver data and return 0 */ |
156 | /* We do this out of normal order for PCI drivers to avoid races */ |
157 | itv->alsa = itvsc; |
158 | |
159 | /* (6) Register the card instance */ |
160 | ret = snd_card_register(card: sc); |
161 | if (ret) { |
162 | itv->alsa = NULL; |
163 | IVTV_ALSA_ERR("%s: snd_card_register() failed with err %d\n" , |
164 | __func__, ret); |
165 | goto err_exit_free; |
166 | } |
167 | |
168 | IVTV_ALSA_INFO("%s: Instance %d registered as ALSA card %d\n" , |
169 | __func__, itv->instance, sc->number); |
170 | |
171 | return 0; |
172 | |
173 | err_exit_free: |
174 | if (sc != NULL) |
175 | snd_card_free(card: sc); |
176 | kfree(objp: itvsc); |
177 | err_exit: |
178 | return ret; |
179 | } |
180 | |
181 | static int ivtv_alsa_load(struct ivtv *itv) |
182 | { |
183 | struct v4l2_device *v4l2_dev = &itv->v4l2_dev; |
184 | struct ivtv_stream *s; |
185 | |
186 | if (v4l2_dev == NULL) { |
187 | pr_err("ivtv-alsa: %s: struct v4l2_device * is NULL\n" , |
188 | __func__); |
189 | return 0; |
190 | } |
191 | |
192 | itv = to_ivtv(v4l2_dev); |
193 | if (itv == NULL) { |
194 | pr_err("ivtv-alsa itv is NULL\n" ); |
195 | return 0; |
196 | } |
197 | |
198 | s = &itv->streams[IVTV_ENC_STREAM_TYPE_PCM]; |
199 | if (s->vdev.v4l2_dev == NULL) { |
200 | IVTV_DEBUG_ALSA_INFO("PCM stream for card is disabled - skipping\n" ); |
201 | return 0; |
202 | } |
203 | |
204 | if (itv->alsa != NULL) { |
205 | IVTV_ALSA_ERR("%s: struct snd_ivtv_card * already exists\n" , |
206 | __func__); |
207 | return 0; |
208 | } |
209 | |
210 | if (snd_ivtv_init(v4l2_dev)) { |
211 | IVTV_ALSA_ERR("%s: failed to create struct snd_ivtv_card\n" , |
212 | __func__); |
213 | } else { |
214 | IVTV_DEBUG_ALSA_INFO("created ivtv ALSA interface instance\n" ); |
215 | } |
216 | return 0; |
217 | } |
218 | |
219 | static int __init ivtv_alsa_init(void) |
220 | { |
221 | pr_info("ivtv-alsa: module loading...\n" ); |
222 | ivtv_ext_init = &ivtv_alsa_load; |
223 | return 0; |
224 | } |
225 | |
226 | static void __exit snd_ivtv_exit(struct snd_ivtv_card *itvsc) |
227 | { |
228 | struct ivtv *itv = to_ivtv(v4l2_dev: itvsc->v4l2_dev); |
229 | |
230 | /* FIXME - pointer checks & shutdown itvsc */ |
231 | |
232 | snd_card_free(card: itvsc->sc); |
233 | itv->alsa = NULL; |
234 | } |
235 | |
236 | static int __exit ivtv_alsa_exit_callback(struct device *dev, void *data) |
237 | { |
238 | struct v4l2_device *v4l2_dev = dev_get_drvdata(dev); |
239 | struct snd_ivtv_card *itvsc; |
240 | |
241 | if (v4l2_dev == NULL) { |
242 | pr_err("ivtv-alsa: %s: struct v4l2_device * is NULL\n" , |
243 | __func__); |
244 | return 0; |
245 | } |
246 | |
247 | itvsc = to_snd_ivtv_card(v4l2_dev); |
248 | if (itvsc == NULL) { |
249 | IVTV_ALSA_WARN("%s: struct snd_ivtv_card * is NULL\n" , |
250 | __func__); |
251 | return 0; |
252 | } |
253 | |
254 | snd_ivtv_exit(itvsc); |
255 | return 0; |
256 | } |
257 | |
258 | static void __exit ivtv_alsa_exit(void) |
259 | { |
260 | struct device_driver *drv; |
261 | int ret; |
262 | |
263 | pr_info("ivtv-alsa: module unloading...\n" ); |
264 | |
265 | drv = driver_find(name: "ivtv" , bus: &pci_bus_type); |
266 | ret = driver_for_each_device(drv, NULL, NULL, fn: ivtv_alsa_exit_callback); |
267 | (void)ret; /* suppress compiler warning */ |
268 | |
269 | ivtv_ext_init = NULL; |
270 | pr_info("ivtv-alsa: module unload complete\n" ); |
271 | } |
272 | |
273 | module_init(ivtv_alsa_init); |
274 | module_exit(ivtv_alsa_exit); |
275 | |