diff options
author | Andy Walls <awalls@md.metrocast.net> | 2012-09-02 18:13:14 -0400 |
---|---|---|
committer | Mauro Carvalho Chehab <mchehab@redhat.com> | 2012-09-18 12:26:50 -0400 |
commit | 269c11fbac4f7b4ed58e77f3049b64b55a342234 (patch) | |
tree | 53e01d0a8f9cd1cd488f2d3e2838a79edf1ccfe6 /drivers/media/pci/ivtv/ivtv-alsa-main.c | |
parent | 6d60805fd2e8103fafa02fcf6448446229ebd511 (diff) |
[media] ivtv, ivtv-alsa: Add initial ivtv-alsa interface driver for ivtv
This is a cut-and-paste port of the cx18-alsa driver to
create an ivtv-alsa interface module for the ivtv driver.
It is not actually hooked-up to the PCM stream DMA buffers
from the ivtv driver yet. That will be done in a coming change,
since that portion is so very different from the cx18 driver.
This code has all or more of the bugs and shortcomings of the
cx18-alsa interface driver: inconsistent use of itvsc->slock,
ivtv-alsa-mixer.c is dead code, assumes 48 ksps regardless
of the actual setting of the audio capture, problems with
proper struct ivtv and struct ivtv_stream housekeeping,
struct ivtv_open_id.v4l2_fh abuse, and $DIETY knows what else.
Signed-off-by: Andy Walls <awalls@md.metrocast.net>
Signed-off-by: Mauro Carvalho Chehab <mchehab@redhat.com>
Diffstat (limited to 'drivers/media/pci/ivtv/ivtv-alsa-main.c')
-rw-r--r-- | drivers/media/pci/ivtv/ivtv-alsa-main.c | 303 |
1 files changed, 303 insertions, 0 deletions
diff --git a/drivers/media/pci/ivtv/ivtv-alsa-main.c b/drivers/media/pci/ivtv/ivtv-alsa-main.c new file mode 100644 index 000000000000..8deab1629b3b --- /dev/null +++ b/drivers/media/pci/ivtv/ivtv-alsa-main.c | |||
@@ -0,0 +1,303 @@ | |||
1 | /* | ||
2 | * ALSA interface to ivtv PCM capture streams | ||
3 | * | ||
4 | * Copyright (C) 2009,2012 Andy Walls <awalls@md.metrocast.net> | ||
5 | * Copyright (C) 2009 Devin Heitmueller <dheitmueller@kernellabs.com> | ||
6 | * | ||
7 | * Portions of this work were sponsored by ONELAN Limited for the cx18 driver | ||
8 | * | ||
9 | * This program is free software; you can redistribute it and/or modify | ||
10 | * it under the terms of the GNU General Public License as published by | ||
11 | * the Free Software Foundation; either version 2 of the License, or | ||
12 | * (at your option) any later version. | ||
13 | * | ||
14 | * This program is distributed in the hope that it will be useful, | ||
15 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
16 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
17 | * GNU General Public License for more details. | ||
18 | * | ||
19 | * You should have received a copy of the GNU General Public License | ||
20 | * along with this program; if not, write to the Free Software | ||
21 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA | ||
22 | * 02111-1307 USA | ||
23 | */ | ||
24 | |||
25 | #include <linux/init.h> | ||
26 | #include <linux/slab.h> | ||
27 | #include <linux/module.h> | ||
28 | #include <linux/kernel.h> | ||
29 | #include <linux/device.h> | ||
30 | #include <linux/spinlock.h> | ||
31 | |||
32 | #include <media/v4l2-device.h> | ||
33 | |||
34 | #include <sound/core.h> | ||
35 | #include <sound/initval.h> | ||
36 | |||
37 | #include "ivtv-driver.h" | ||
38 | #include "ivtv-version.h" | ||
39 | #include "ivtv-alsa.h" | ||
40 | #include "ivtv-alsa-mixer.h" | ||
41 | #include "ivtv-alsa-pcm.h" | ||
42 | |||
43 | int ivtv_alsa_debug; | ||
44 | |||
45 | #define IVTV_DEBUG_ALSA_INFO(fmt, arg...) \ | ||
46 | do { \ | ||
47 | if (ivtv_alsa_debug & 2) \ | ||
48 | pr_info("%s: " fmt, "ivtv-alsa", ## arg); \ | ||
49 | } while (0) | ||
50 | |||
51 | module_param_named(debug, ivtv_alsa_debug, int, 0644); | ||
52 | MODULE_PARM_DESC(debug, | ||
53 | "Debug level (bitmask). Default: 0\n" | ||
54 | "\t\t\t 1/0x0001: warning\n" | ||
55 | "\t\t\t 2/0x0002: info\n"); | ||
56 | |||
57 | MODULE_AUTHOR("Andy Walls"); | ||
58 | MODULE_DESCRIPTION("CX23415/CX23416 ALSA Interface"); | ||
59 | MODULE_SUPPORTED_DEVICE("CX23415/CX23416 MPEG2 encoder"); | ||
60 | MODULE_LICENSE("GPL"); | ||
61 | |||
62 | MODULE_VERSION(IVTV_VERSION); | ||
63 | |||
64 | static inline | ||
65 | struct snd_ivtv_card *to_snd_ivtv_card(struct v4l2_device *v4l2_dev) | ||
66 | { | ||
67 | return to_ivtv(v4l2_dev)->alsa; | ||
68 | } | ||
69 | |||
70 | static inline | ||
71 | struct snd_ivtv_card *p_to_snd_ivtv_card(struct v4l2_device **v4l2_dev) | ||
72 | { | ||
73 | return container_of(v4l2_dev, struct snd_ivtv_card, v4l2_dev); | ||
74 | } | ||
75 | |||
76 | static void snd_ivtv_card_free(struct snd_ivtv_card *itvsc) | ||
77 | { | ||
78 | if (itvsc == NULL) | ||
79 | return; | ||
80 | |||
81 | if (itvsc->v4l2_dev != NULL) | ||
82 | to_ivtv(itvsc->v4l2_dev)->alsa = NULL; | ||
83 | |||
84 | /* FIXME - take any other stopping actions needed */ | ||
85 | |||
86 | kfree(itvsc); | ||
87 | } | ||
88 | |||
89 | static void snd_ivtv_card_private_free(struct snd_card *sc) | ||
90 | { | ||
91 | if (sc == NULL) | ||
92 | return; | ||
93 | snd_ivtv_card_free(sc->private_data); | ||
94 | sc->private_data = NULL; | ||
95 | sc->private_free = NULL; | ||
96 | } | ||
97 | |||
98 | static int snd_ivtv_card_create(struct v4l2_device *v4l2_dev, | ||
99 | struct snd_card *sc, | ||
100 | struct snd_ivtv_card **itvsc) | ||
101 | { | ||
102 | *itvsc = kzalloc(sizeof(struct snd_ivtv_card), GFP_KERNEL); | ||
103 | if (*itvsc == NULL) | ||
104 | return -ENOMEM; | ||
105 | |||
106 | (*itvsc)->v4l2_dev = v4l2_dev; | ||
107 | (*itvsc)->sc = sc; | ||
108 | |||
109 | sc->private_data = *itvsc; | ||
110 | sc->private_free = snd_ivtv_card_private_free; | ||
111 | |||
112 | return 0; | ||
113 | } | ||
114 | |||
115 | static int snd_ivtv_card_set_names(struct snd_ivtv_card *itvsc) | ||
116 | { | ||
117 | struct ivtv *itv = to_ivtv(itvsc->v4l2_dev); | ||
118 | struct snd_card *sc = itvsc->sc; | ||
119 | |||
120 | /* sc->driver is used by alsa-lib's configurator: simple, unique */ | ||
121 | strlcpy(sc->driver, "CX2341[56]", sizeof(sc->driver)); | ||
122 | |||
123 | /* sc->shortname is a symlink in /proc/asound: IVTV-M -> cardN */ | ||
124 | snprintf(sc->shortname, sizeof(sc->shortname), "IVTV-%d", | ||
125 | itv->instance); | ||
126 | |||
127 | /* sc->longname is read from /proc/asound/cards */ | ||
128 | snprintf(sc->longname, sizeof(sc->longname), | ||
129 | "CX2341[56] #%d %s TV/FM Radio/Line-In Capture", | ||
130 | itv->instance, itv->card_name); | ||
131 | |||
132 | return 0; | ||
133 | } | ||
134 | |||
135 | static int snd_ivtv_init(struct v4l2_device *v4l2_dev) | ||
136 | { | ||
137 | struct ivtv *itv = to_ivtv(v4l2_dev); | ||
138 | struct snd_card *sc = NULL; | ||
139 | struct snd_ivtv_card *itvsc; | ||
140 | int ret; | ||
141 | |||
142 | /* Numbrs steps from "Writing an ALSA Driver" by Takashi Iwai */ | ||
143 | |||
144 | /* (1) Check and increment the device index */ | ||
145 | /* This is a no-op for us. We'll use the itv->instance */ | ||
146 | |||
147 | /* (2) Create a card instance */ | ||
148 | ret = snd_card_create(SNDRV_DEFAULT_IDX1, /* use first available id */ | ||
149 | SNDRV_DEFAULT_STR1, /* xid from end of shortname*/ | ||
150 | THIS_MODULE, 0, &sc); | ||
151 | if (ret) { | ||
152 | IVTV_ALSA_ERR("%s: snd_card_create() failed with err %d\n", | ||
153 | __func__, ret); | ||
154 | goto err_exit; | ||
155 | } | ||
156 | |||
157 | /* (3) Create a main component */ | ||
158 | ret = snd_ivtv_card_create(v4l2_dev, sc, &itvsc); | ||
159 | if (ret) { | ||
160 | IVTV_ALSA_ERR("%s: snd_ivtv_card_create() failed with err %d\n", | ||
161 | __func__, ret); | ||
162 | goto err_exit_free; | ||
163 | } | ||
164 | |||
165 | /* (4) Set the driver ID and name strings */ | ||
166 | snd_ivtv_card_set_names(itvsc); | ||
167 | |||
168 | /* (5) Create other components: mixer, PCM, & proc files */ | ||
169 | #if 0 | ||
170 | ret = snd_ivtv_mixer_create(itvsc); | ||
171 | if (ret) { | ||
172 | IVTV_ALSA_WARN("%s: snd_ivtv_mixer_create() failed with err %d:" | ||
173 | " proceeding anyway\n", __func__, ret); | ||
174 | } | ||
175 | #endif | ||
176 | |||
177 | ret = snd_ivtv_pcm_create(itvsc); | ||
178 | if (ret) { | ||
179 | IVTV_ALSA_ERR("%s: snd_ivtv_pcm_create() failed with err %d\n", | ||
180 | __func__, ret); | ||
181 | goto err_exit_free; | ||
182 | } | ||
183 | /* FIXME - proc files */ | ||
184 | |||
185 | /* (7) Set the driver data and return 0 */ | ||
186 | /* We do this out of normal order for PCI drivers to avoid races */ | ||
187 | itv->alsa = itvsc; | ||
188 | |||
189 | /* (6) Register the card instance */ | ||
190 | ret = snd_card_register(sc); | ||
191 | if (ret) { | ||
192 | itv->alsa = NULL; | ||
193 | IVTV_ALSA_ERR("%s: snd_card_register() failed with err %d\n", | ||
194 | __func__, ret); | ||
195 | goto err_exit_free; | ||
196 | } | ||
197 | |||
198 | return 0; | ||
199 | |||
200 | err_exit_free: | ||
201 | if (sc != NULL) | ||
202 | snd_card_free(sc); | ||
203 | kfree(itvsc); | ||
204 | err_exit: | ||
205 | return ret; | ||
206 | } | ||
207 | |||
208 | int ivtv_alsa_load(struct ivtv *itv) | ||
209 | { | ||
210 | struct v4l2_device *v4l2_dev = &itv->v4l2_dev; | ||
211 | struct ivtv_stream *s; | ||
212 | |||
213 | if (v4l2_dev == NULL) { | ||
214 | pr_err("ivtv-alsa: %s: struct v4l2_device * is NULL\n", | ||
215 | __func__); | ||
216 | return 0; | ||
217 | } | ||
218 | |||
219 | itv = to_ivtv(v4l2_dev); | ||
220 | if (itv == NULL) { | ||
221 | pr_err("ivtv-alsa itv is NULL\n"); | ||
222 | return 0; | ||
223 | } | ||
224 | |||
225 | s = &itv->streams[IVTV_ENC_STREAM_TYPE_PCM]; | ||
226 | if (s->vdev == NULL) { | ||
227 | IVTV_DEBUG_ALSA_INFO("%s: PCM stream for card is disabled - " | ||
228 | "skipping\n", __func__); | ||
229 | return 0; | ||
230 | } | ||
231 | |||
232 | if (itv->alsa != NULL) { | ||
233 | IVTV_ALSA_ERR("%s: struct snd_ivtv_card * already exists\n", | ||
234 | __func__); | ||
235 | return 0; | ||
236 | } | ||
237 | |||
238 | if (snd_ivtv_init(v4l2_dev)) { | ||
239 | IVTV_ALSA_ERR("%s: failed to create struct snd_ivtv_card\n", | ||
240 | __func__); | ||
241 | } else { | ||
242 | IVTV_DEBUG_ALSA_INFO("%s: created ivtv ALSA interface instance " | ||
243 | "\n", __func__); | ||
244 | } | ||
245 | return 0; | ||
246 | } | ||
247 | |||
248 | static int __init ivtv_alsa_init(void) | ||
249 | { | ||
250 | pr_info("ivtv-alsa: module loading...\n"); | ||
251 | ivtv_ext_init = &ivtv_alsa_load; | ||
252 | return 0; | ||
253 | } | ||
254 | |||
255 | static void __exit snd_ivtv_exit(struct snd_ivtv_card *itvsc) | ||
256 | { | ||
257 | struct ivtv *itv = to_ivtv(itvsc->v4l2_dev); | ||
258 | |||
259 | /* FIXME - pointer checks & shutdown itvsc */ | ||
260 | |||
261 | snd_card_free(itvsc->sc); | ||
262 | itv->alsa = NULL; | ||
263 | } | ||
264 | |||
265 | static int __exit ivtv_alsa_exit_callback(struct device *dev, void *data) | ||
266 | { | ||
267 | struct v4l2_device *v4l2_dev = dev_get_drvdata(dev); | ||
268 | struct snd_ivtv_card *itvsc; | ||
269 | |||
270 | if (v4l2_dev == NULL) { | ||
271 | pr_err("ivtv-alsa: %s: struct v4l2_device * is NULL\n", | ||
272 | __func__); | ||
273 | return 0; | ||
274 | } | ||
275 | |||
276 | itvsc = to_snd_ivtv_card(v4l2_dev); | ||
277 | if (itvsc == NULL) { | ||
278 | IVTV_ALSA_WARN("%s: struct snd_ivtv_card * is NULL\n", | ||
279 | __func__); | ||
280 | return 0; | ||
281 | } | ||
282 | |||
283 | snd_ivtv_exit(itvsc); | ||
284 | return 0; | ||
285 | } | ||
286 | |||
287 | static void __exit ivtv_alsa_exit(void) | ||
288 | { | ||
289 | struct device_driver *drv; | ||
290 | int ret; | ||
291 | |||
292 | pr_info("ivtv-alsa: module unloading...\n"); | ||
293 | |||
294 | drv = driver_find("ivtv", &pci_bus_type); | ||
295 | ret = driver_for_each_device(drv, NULL, NULL, ivtv_alsa_exit_callback); | ||
296 | (void)ret; /* suppress compiler warning */ | ||
297 | |||
298 | ivtv_ext_init = NULL; | ||
299 | pr_info("ivtv-alsa: module unload complete\n"); | ||
300 | } | ||
301 | |||
302 | module_init(ivtv_alsa_init); | ||
303 | module_exit(ivtv_alsa_exit); | ||