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