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