linux/drivers/media/usb/tm6000/tm6000-alsa.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0
   2// Support for audio capture for tm5600/6000/6010
   3// Copyright (c) 2007-2008 Mauro Carvalho Chehab <mchehab@kernel.org>
   4//
   5// Based on cx88-alsa.c
   6
   7#include <linux/module.h>
   8#include <linux/init.h>
   9#include <linux/device.h>
  10#include <linux/interrupt.h>
  11#include <linux/usb.h>
  12#include <linux/slab.h>
  13
  14#include <linux/delay.h>
  15#include <sound/core.h>
  16#include <sound/pcm.h>
  17#include <sound/pcm_params.h>
  18#include <sound/control.h>
  19#include <sound/initval.h>
  20
  21
  22#include "tm6000.h"
  23#include "tm6000-regs.h"
  24
  25#undef dprintk
  26
  27#define dprintk(level, fmt, arg...) do {                                   \
  28        if (debug >= level)                                                \
  29                printk(KERN_INFO "%s/1: " fmt, chip->core->name , ## arg); \
  30        } while (0)
  31
  32/****************************************************************************
  33                        Module global static vars
  34 ****************************************************************************/
  35
  36static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;      /* Index 0-MAX */
  37
  38static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP;
  39
  40module_param_array(enable, bool, NULL, 0444);
  41MODULE_PARM_DESC(enable, "Enable tm6000x soundcard. default enabled.");
  42
  43module_param_array(index, int, NULL, 0444);
  44MODULE_PARM_DESC(index, "Index value for tm6000x capture interface(s).");
  45
  46
  47/****************************************************************************
  48                                Module macros
  49 ****************************************************************************/
  50
  51MODULE_DESCRIPTION("ALSA driver module for tm5600/tm6000/tm6010 based TV cards");
  52MODULE_AUTHOR("Mauro Carvalho Chehab");
  53MODULE_LICENSE("GPL v2");
  54MODULE_SUPPORTED_DEVICE("{{Trident,tm5600},{{Trident,tm6000},{{Trident,tm6010}");
  55static unsigned int debug;
  56module_param(debug, int, 0644);
  57MODULE_PARM_DESC(debug, "enable debug messages");
  58
  59/****************************************************************************
  60                        Module specific functions
  61 ****************************************************************************/
  62
  63/*
  64 * BOARD Specific: Sets audio DMA
  65 */
  66
  67static int _tm6000_start_audio_dma(struct snd_tm6000_card *chip)
  68{
  69        struct tm6000_core *core = chip->core;
  70
  71        dprintk(1, "Starting audio DMA\n");
  72
  73        /* Enables audio */
  74        tm6000_set_reg_mask(core, TM6010_REQ07_RCC_ACTIVE_IF, 0x40, 0x40);
  75
  76        tm6000_set_audio_bitrate(core, 48000);
  77
  78        return 0;
  79}
  80
  81/*
  82 * BOARD Specific: Resets audio DMA
  83 */
  84static int _tm6000_stop_audio_dma(struct snd_tm6000_card *chip)
  85{
  86        struct tm6000_core *core = chip->core;
  87
  88        dprintk(1, "Stopping audio DMA\n");
  89
  90        /* Disables audio */
  91        tm6000_set_reg_mask(core, TM6010_REQ07_RCC_ACTIVE_IF, 0x00, 0x40);
  92
  93        return 0;
  94}
  95
  96/****************************************************************************
  97                                ALSA PCM Interface
  98 ****************************************************************************/
  99
 100/*
 101 * Digital hardware definition
 102 */
 103#define DEFAULT_FIFO_SIZE       4096
 104
 105static const struct snd_pcm_hardware snd_tm6000_digital_hw = {
 106        .info = SNDRV_PCM_INFO_BATCH |
 107                SNDRV_PCM_INFO_MMAP |
 108                SNDRV_PCM_INFO_INTERLEAVED |
 109                SNDRV_PCM_INFO_BLOCK_TRANSFER |
 110                SNDRV_PCM_INFO_MMAP_VALID,
 111        .formats = SNDRV_PCM_FMTBIT_S16_LE,
 112
 113        .rates = SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_KNOT,
 114        .rate_min = 48000,
 115        .rate_max = 48000,
 116        .channels_min = 2,
 117        .channels_max = 2,
 118        .period_bytes_min = 64,
 119        .period_bytes_max = 12544,
 120        .periods_min = 2,
 121        .periods_max = 98,
 122        .buffer_bytes_max = 62720 * 8,
 123};
 124
 125/*
 126 * audio pcm capture open callback
 127 */
 128static int snd_tm6000_pcm_open(struct snd_pcm_substream *substream)
 129{
 130        struct snd_tm6000_card *chip = snd_pcm_substream_chip(substream);
 131        struct snd_pcm_runtime *runtime = substream->runtime;
 132        int err;
 133
 134        err = snd_pcm_hw_constraint_pow2(runtime, 0,
 135                                         SNDRV_PCM_HW_PARAM_PERIODS);
 136        if (err < 0)
 137                goto _error;
 138
 139        chip->substream = substream;
 140
 141        runtime->hw = snd_tm6000_digital_hw;
 142        snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS);
 143
 144        return 0;
 145_error:
 146        dprintk(1, "Error opening PCM!\n");
 147        return err;
 148}
 149
 150/*
 151 * audio close callback
 152 */
 153static int snd_tm6000_close(struct snd_pcm_substream *substream)
 154{
 155        struct snd_tm6000_card *chip = snd_pcm_substream_chip(substream);
 156        struct tm6000_core *core = chip->core;
 157
 158        if (atomic_read(&core->stream_started) > 0) {
 159                atomic_set(&core->stream_started, 0);
 160                schedule_work(&core->wq_trigger);
 161        }
 162
 163        return 0;
 164}
 165
 166static int tm6000_fillbuf(struct tm6000_core *core, char *buf, int size)
 167{
 168        struct snd_tm6000_card *chip = core->adev;
 169        struct snd_pcm_substream *substream = chip->substream;
 170        struct snd_pcm_runtime *runtime;
 171        int period_elapsed = 0;
 172        unsigned int stride, buf_pos;
 173        int length;
 174
 175        if (atomic_read(&core->stream_started) == 0)
 176                return 0;
 177
 178        if (!size || !substream) {
 179                dprintk(1, "substream was NULL\n");
 180                return -EINVAL;
 181        }
 182
 183        runtime = substream->runtime;
 184        if (!runtime || !runtime->dma_area) {
 185                dprintk(1, "runtime was NULL\n");
 186                return -EINVAL;
 187        }
 188
 189        buf_pos = chip->buf_pos;
 190        stride = runtime->frame_bits >> 3;
 191
 192        if (stride == 0) {
 193                dprintk(1, "stride is zero\n");
 194                return -EINVAL;
 195        }
 196
 197        length = size / stride;
 198        if (length == 0) {
 199                dprintk(1, "%s: length was zero\n", __func__);
 200                return -EINVAL;
 201        }
 202
 203        dprintk(1, "Copying %d bytes at %p[%d] - buf size=%d x %d\n", size,
 204                runtime->dma_area, buf_pos,
 205                (unsigned int)runtime->buffer_size, stride);
 206
 207        if (buf_pos + length >= runtime->buffer_size) {
 208                unsigned int cnt = runtime->buffer_size - buf_pos;
 209                memcpy(runtime->dma_area + buf_pos * stride, buf, cnt * stride);
 210                memcpy(runtime->dma_area, buf + cnt * stride,
 211                        length * stride - cnt * stride);
 212        } else
 213                memcpy(runtime->dma_area + buf_pos * stride, buf,
 214                        length * stride);
 215
 216        snd_pcm_stream_lock(substream);
 217
 218        chip->buf_pos += length;
 219        if (chip->buf_pos >= runtime->buffer_size)
 220                chip->buf_pos -= runtime->buffer_size;
 221
 222        chip->period_pos += length;
 223        if (chip->period_pos >= runtime->period_size) {
 224                chip->period_pos -= runtime->period_size;
 225                period_elapsed = 1;
 226        }
 227
 228        snd_pcm_stream_unlock(substream);
 229
 230        if (period_elapsed)
 231                snd_pcm_period_elapsed(substream);
 232
 233        return 0;
 234}
 235
 236/*
 237 * prepare callback
 238 */
 239static int snd_tm6000_prepare(struct snd_pcm_substream *substream)
 240{
 241        struct snd_tm6000_card *chip = snd_pcm_substream_chip(substream);
 242
 243        chip->buf_pos = 0;
 244        chip->period_pos = 0;
 245
 246        return 0;
 247}
 248
 249
 250/*
 251 * trigger callback
 252 */
 253static void audio_trigger(struct work_struct *work)
 254{
 255        struct tm6000_core *core = container_of(work, struct tm6000_core,
 256                                                wq_trigger);
 257        struct snd_tm6000_card *chip = core->adev;
 258
 259        if (atomic_read(&core->stream_started)) {
 260                dprintk(1, "starting capture");
 261                _tm6000_start_audio_dma(chip);
 262        } else {
 263                dprintk(1, "stopping capture");
 264                _tm6000_stop_audio_dma(chip);
 265        }
 266}
 267
 268static int snd_tm6000_card_trigger(struct snd_pcm_substream *substream, int cmd)
 269{
 270        struct snd_tm6000_card *chip = snd_pcm_substream_chip(substream);
 271        struct tm6000_core *core = chip->core;
 272        int err = 0;
 273
 274        switch (cmd) {
 275        case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: /* fall through */
 276        case SNDRV_PCM_TRIGGER_RESUME: /* fall through */
 277        case SNDRV_PCM_TRIGGER_START:
 278                atomic_set(&core->stream_started, 1);
 279                break;
 280        case SNDRV_PCM_TRIGGER_PAUSE_PUSH: /* fall through */
 281        case SNDRV_PCM_TRIGGER_SUSPEND: /* fall through */
 282        case SNDRV_PCM_TRIGGER_STOP:
 283                atomic_set(&core->stream_started, 0);
 284                break;
 285        default:
 286                err = -EINVAL;
 287                break;
 288        }
 289        schedule_work(&core->wq_trigger);
 290
 291        return err;
 292}
 293/*
 294 * pointer callback
 295 */
 296static snd_pcm_uframes_t snd_tm6000_pointer(struct snd_pcm_substream *substream)
 297{
 298        struct snd_tm6000_card *chip = snd_pcm_substream_chip(substream);
 299
 300        return chip->buf_pos;
 301}
 302
 303/*
 304 * operators
 305 */
 306static const struct snd_pcm_ops snd_tm6000_pcm_ops = {
 307        .open = snd_tm6000_pcm_open,
 308        .close = snd_tm6000_close,
 309        .prepare = snd_tm6000_prepare,
 310        .trigger = snd_tm6000_card_trigger,
 311        .pointer = snd_tm6000_pointer,
 312};
 313
 314/*
 315 * create a PCM device
 316 */
 317
 318/* FIXME: Control interface - How to control volume/mute? */
 319
 320/****************************************************************************
 321                        Basic Flow for Sound Devices
 322 ****************************************************************************/
 323
 324/*
 325 * Alsa Constructor - Component probe
 326 */
 327static int tm6000_audio_init(struct tm6000_core *dev)
 328{
 329        struct snd_card         *card;
 330        struct snd_tm6000_card  *chip;
 331        int                     rc;
 332        static int              devnr;
 333        char                    component[14];
 334        struct snd_pcm          *pcm;
 335
 336        if (!dev)
 337                return 0;
 338
 339        if (devnr >= SNDRV_CARDS)
 340                return -ENODEV;
 341
 342        if (!enable[devnr])
 343                return -ENOENT;
 344
 345        rc = snd_card_new(&dev->udev->dev, index[devnr], "tm6000",
 346                          THIS_MODULE, 0, &card);
 347        if (rc < 0) {
 348                snd_printk(KERN_ERR "cannot create card instance %d\n", devnr);
 349                return rc;
 350        }
 351        strscpy(card->driver, "tm6000-alsa", sizeof(card->driver));
 352        strscpy(card->shortname, "TM5600/60x0", sizeof(card->shortname));
 353        sprintf(card->longname, "TM5600/60x0 Audio at bus %d device %d",
 354                dev->udev->bus->busnum, dev->udev->devnum);
 355
 356        sprintf(component, "USB%04x:%04x",
 357                le16_to_cpu(dev->udev->descriptor.idVendor),
 358                le16_to_cpu(dev->udev->descriptor.idProduct));
 359        snd_component_add(card, component);
 360
 361        chip = kzalloc(sizeof(struct snd_tm6000_card), GFP_KERNEL);
 362        if (!chip) {
 363                rc = -ENOMEM;
 364                goto error;
 365        }
 366
 367        chip->core = dev;
 368        chip->card = card;
 369        dev->adev = chip;
 370        spin_lock_init(&chip->reg_lock);
 371
 372        rc = snd_pcm_new(card, "TM6000 Audio", 0, 0, 1, &pcm);
 373        if (rc < 0)
 374                goto error_chip;
 375
 376        pcm->info_flags = 0;
 377        pcm->private_data = chip;
 378        strscpy(pcm->name, "Trident TM5600/60x0", sizeof(pcm->name));
 379
 380        snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_tm6000_pcm_ops);
 381        snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_VMALLOC, NULL, 0, 0);
 382
 383        INIT_WORK(&dev->wq_trigger, audio_trigger);
 384        rc = snd_card_register(card);
 385        if (rc < 0)
 386                goto error_chip;
 387
 388        dprintk(1, "Registered audio driver for %s\n", card->longname);
 389
 390        return 0;
 391
 392error_chip:
 393        kfree(chip);
 394        dev->adev = NULL;
 395error:
 396        snd_card_free(card);
 397        return rc;
 398}
 399
 400static int tm6000_audio_fini(struct tm6000_core *dev)
 401{
 402        struct snd_tm6000_card *chip;
 403
 404        if (!dev)
 405                return 0;
 406        chip = dev->adev;
 407
 408        if (!chip)
 409                return 0;
 410
 411        if (!chip->card)
 412                return 0;
 413
 414        snd_card_free(chip->card);
 415        chip->card = NULL;
 416        kfree(chip);
 417        dev->adev = NULL;
 418
 419        return 0;
 420}
 421
 422static struct tm6000_ops audio_ops = {
 423        .type   = TM6000_AUDIO,
 424        .name   = "TM6000 Audio Extension",
 425        .init   = tm6000_audio_init,
 426        .fini   = tm6000_audio_fini,
 427        .fillbuf = tm6000_fillbuf,
 428};
 429
 430static int __init tm6000_alsa_register(void)
 431{
 432        return tm6000_register_extension(&audio_ops);
 433}
 434
 435static void __exit tm6000_alsa_unregister(void)
 436{
 437        tm6000_unregister_extension(&audio_ops);
 438}
 439
 440module_init(tm6000_alsa_register);
 441module_exit(tm6000_alsa_unregister);
 442