2016-01-03 4 views
1

Я разрабатываю программное обеспечение для платы, используя Altera Cyclone V SoC (ARM Cortex-A9 работает под управлением Linux с FPGA). ОС Linux работает нормально с поддержкой встроенных периферийных устройств (Ethernet, SD-карта и т. Д.), И я могу получить доступ к FPGA из программ пользовательского пространства, в настоящее время используя mmap().
Теперь плата также может использоваться в качестве платформы SDR (программное обеспечение, определенная радио), так что в качестве трюка я реализовал очень простой FM-передатчик. Это уже хорошо работает, но это только статическая конфигурация внутри FPGA без реального подключения к операционной системе.Создание простого аудио-драйвера для Cyclone V SoC (Linux)

Что бы я хотел сделать, это связать передатчик с операционной системой, в идеале, чтобы он мог использоваться как стандартное устройство вывода звука, то есть звуковая карта. Аппаратная часть гибкая и на данный момент очень упрощена:

  • Регистр статуса сообщает, воспроизводятся ли образцы.
  • Реестр адресов памяти удерживает текущий указатель чтения в системной памяти, откуда извлекаются выборки (DMA).
  • Реестр, содержащий оставшееся количество образцов для воспроизведения.
  • Регистр для установки частоты дискретизации аппаратного обеспечения.

Этот интерфейс не является фиксированным, при необходимости его можно легко изменить. Если бы это было разумно, я мог бы также обновить интерфейс, чтобы он напоминал интерфейс существующего устройства, чтобы я мог использовать его драйвер.

Теперь вопрос в том, как я должен начать с интеграции в Linux, то есть как получить/построить подходящий модуль ядра. Я довольно новичок в разработке модулей ядра, поэтому я не знаю, с чего начать. documentation of the Linux sound subsystem, несомненно, содержит много полезной информации, но мне не хватает «большой картины», например. что должно быть в какой части драйвера, или как должен выглядеть фактический интерфейс к системе (ALSA, если я правильно понял).

btw: Я знаю книгу LDD3 и имею представление о том, как модуль ядра должен выглядеть и работать в целом, но я не знаю, как создать тот, который хорошо играет вместе с аудиоподсистемой.

Спасибо за комментарии, предложения, ссылки и т.д.

ответ

2

Просто некоторые указатели, надеюсь, что это помогает.

Alsa Overview (Wikipedia)

Audio In Embedded Linux Systems(Free Electrons)

Writing an Alsa Driver

+0

Спасибо, это именно то, что я искал! Конечно, я все еще открыт для других комментариев и предложений, но я думаю, что я соглашусь с тем, что у меня есть сейчас :) –

+0

Я не могу больше делать, удачи :) – Mali

+0

Получил это! Еще раз спасибо :) –

0

С информацией, найденной в presentation on Free Electrons и ALSA driver tutorial, как упомянуто @Mali, я, наконец, удалось построить мой простой драйвер. Я отправлю код ниже, возможно, он будет полезен для кого-то другого. Он напрямую основан на драйвере sound/drivers/dummy.c в источниках ядра Linux.Одна очень важная вещь, чтобы изменить буфер предраспределения в snd_card_fmplayer_pcm от

snd_pcm_lib_preallocate_pages_for_all(pcm, 
    SNDRV_DMA_TYPE_CONTINUOUS, 
    snd_dma_continuous_data(GFP_KERNEL), 
    0, 64*1024); 

в

snd_pcm_lib_preallocate_pages_for_all(pcm, 
    SNDRV_DMA_TYPE_DEV, /* This type is veeery important! */ 
    NULL, 
    MAX_BUFFER_SIZE, MAX_BUFFER_SIZE); 

Без этого изменения, водитель в основном работает, но играют данные вряд ли напоминает реальные образцы, которые похоже, вызвано эффектами кеширования.

Модуль по-прежнему нуждается в таймере ядра (либо систером, либо таймером высокого разрешения), как и в реализации dummy.c, поскольку аппаратное обеспечение не настроено на создание прерываний во время игры. Это должно быть исправлено, если оно должно использоваться для какого-либо серьезного применения.

Bye,
Philipp

/* 
* ALSA soundcard kernel module to access the fmplayer FPGA core. 
* 
* This code is mostly based on the ALSA dummy soundcard in 
* sound/drivers/dummy.c written by Jaroslav Kysela <[email protected]> 
* 
* This program is free software; you can redistribute it and/or modify 
* it under the terms of the GNU General Public License as published by 
* the Free Software Foundation; either version 2 of the License, or 
* (at your option) any later version. 
* 
* This program is distributed in the hope that it will be useful, 
* but WITHOUT ANY WARRANTY; without even the implied warranty of 
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 
* GNU General Public License for more details. 
* 
* You should have received a copy of the GNU General Public License 
* along with this program; if not, write to the Free Software 
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 
* 
*/ 

#include <linux/init.h> 
#include <linux/err.h> 
#include <linux/platform_device.h> 
#include <linux/jiffies.h> 
#include <linux/slab.h> 
#include <linux/time.h> 
#include <linux/wait.h> 
#include <linux/hrtimer.h> 
#include <linux/math64.h> 
#include <linux/module.h> 
#include <sound/core.h> 
#include <sound/control.h> 
#include <sound/tlv.h> 
#include <sound/pcm.h> 
#include <sound/rawmidi.h> 
#include <sound/info.h> 
#include <sound/initval.h> 

#include <linux/io.h> 
#include <linux/delay.h> 
#include <asm/io.h> 
#include <linux/dma-mapping.h> 

MODULE_AUTHOR("Philipp Burch <[email protected]>"); 
MODULE_DESCRIPTION("FM player sound card"); 
MODULE_LICENSE("GPL"); 
MODULE_SUPPORTED_DEVICE("{{ALSA,fmplayer}}"); 

// FPGA core ------------------------------------------------------------------- 

// Slave register address map (byte address offsets): 
// 0x00 RW STAT_CTRL 
//  Bit 0: Set to enable the block. If cleared, the output is forced 
//    to all-zeros. 
// 0x04 RW MEMSTART 
//  Starting address of the sample memory (16-bit aligned). A write 
//  to this register resets and disables the player. When read, 
//  this register contains the address from where the next 
//  sample will be read. It can be used as progress information, 
//  so that one half of the memory can be overwritten by new 
//  data after it has been played. 
// 0x08 RW MEMEND 
//  Last address of the sample memory, after which the address 
//  counter wraps back to MEMSTART. 
// 0x0c RW SAMPRATE 
//  Update value for the sampling rate DDS. This should be 
//  selected according to the nominal sampling rate of the 
//  data to play. 

#define REG_BASE   0xc0003000 

#define REGNUM_RW_STAT_CTRL 0 
#define REGNUM_RW_MEMSTART 1 
#define REGNUM_RW_MEMEND 2 
#define REGNUM_RW_SAMPRATE 3 

#define REG_SIZE_BYTES  32 

#define DDS_CLK_FREQ  100000000 
#define DDS_WIDTH   32ULL 

// ----------------------------------------------------------------------------- 


#define MAX_PCM_DEVICES  1 
#define MAX_PCM_SUBSTREAMS 1 
#define MAX_MIDI_DEVICES 0 

/* defaults */ 
#define MAX_BUFFER_SIZE  (64*1024) 
#define MIN_PERIOD_SIZE  64 
#define MAX_PERIOD_SIZE  1024 
#define USE_FORMATS   (SNDRV_PCM_FMTBIT_S16_LE) 
#define USE_RATE   (SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_48000) 
#define USE_RATE_MIN  1000 
#define USE_RATE_MAX  192000 
#define USE_CHANNELS_MIN 2 
#define USE_CHANNELS_MAX 2 
#define USE_PERIODS_MIN  2 
#define USE_PERIODS_MAX  1024 

static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-MAX */ 
static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* ID for this card */ 
static bool enable[SNDRV_CARDS] = {1, [1 ... (SNDRV_CARDS - 1)] = 0}; 
#ifdef CONFIG_HIGH_RES_TIMERS 
static bool hrtimer = 1; 
#endif 

module_param_array(index, int, NULL, 0444); 
MODULE_PARM_DESC(index, "Index value for fmplayer."); 
module_param_array(id, charp, NULL, 0444); 
MODULE_PARM_DESC(id, "ID string for fmplayer."); 
module_param_array(enable, bool, NULL, 0444); 
MODULE_PARM_DESC(enable, "Enable this fmplayer."); 
#ifdef CONFIG_HIGH_RES_TIMERS 
module_param(hrtimer, bool, 0644); 
MODULE_PARM_DESC(hrtimer, "Use hrtimer as the timer source."); 
#endif 

static struct platform_device *devices[SNDRV_CARDS]; 


struct fmplayer_timer_ops { 
    int (*create)(struct snd_pcm_substream *); 
    void (*free)(struct snd_pcm_substream *); 
    int (*prepare)(struct snd_pcm_substream *); 
    int (*start)(struct snd_pcm_substream *); 
    int (*stop)(struct snd_pcm_substream *); 
    snd_pcm_uframes_t (*pointer)(struct snd_pcm_substream *); 
}; 


struct snd_fmplayer { 
    struct snd_card *card; 
    struct fmplayer_model *model; 
    struct snd_pcm *pcm; 
    struct snd_pcm_hardware pcm_hw; 
    spinlock_t mixer_lock; 
    u32 *iomem; 
    struct snd_kcontrol *cd_volume_ctl; 
    struct snd_kcontrol *cd_switch_ctl; 
    const struct fmplayer_timer_ops *timer_ops; 
}; 

/* 
* system timer interface 
*/ 

struct fmplayer_systimer_pcm { 
    spinlock_t lock; 
    struct timer_list timer; 
    unsigned long base_time; 
    unsigned int frac_pos; /* fractional sample position (based HZ) */ 
    unsigned int frac_period_rest; 
    unsigned int frac_buffer_size; /* buffer_size * HZ */ 
    unsigned int frac_period_size; /* period_size * HZ */ 
    unsigned int rate; 
    int elapsed; 
    struct snd_pcm_substream *substream; 
}; 

static void fmplayer_systimer_rearm(struct fmplayer_systimer_pcm *dpcm) 
{ 
    mod_timer(&dpcm->timer, jiffies + 
     (dpcm->frac_period_rest + dpcm->rate - 1)/dpcm->rate); 
} 

static void fmplayer_systimer_update(struct fmplayer_systimer_pcm *dpcm) 
{ 
    unsigned long delta; 

    delta = jiffies - dpcm->base_time; 
    if (!delta) 
     return; 
    dpcm->base_time += delta; 
    delta *= dpcm->rate; 
    dpcm->frac_pos += delta; 
    while (dpcm->frac_pos >= dpcm->frac_buffer_size) 
     dpcm->frac_pos -= dpcm->frac_buffer_size; 
    while (dpcm->frac_period_rest <= delta) { 
     dpcm->elapsed++; 
     dpcm->frac_period_rest += dpcm->frac_period_size; 
    } 
    dpcm->frac_period_rest -= delta; 
} 

static int fmplayer_systimer_start(struct snd_pcm_substream *substream) 
{ 
    struct fmplayer_systimer_pcm *dpcm = substream->runtime->private_data; 
    spin_lock(&dpcm->lock); 
    dpcm->base_time = jiffies; 
    fmplayer_systimer_rearm(dpcm); 
    spin_unlock(&dpcm->lock); 
    return 0; 
} 

static int fmplayer_systimer_stop(struct snd_pcm_substream *substream) 
{ 
    struct fmplayer_systimer_pcm *dpcm = substream->runtime->private_data; 
    spin_lock(&dpcm->lock); 
    del_timer(&dpcm->timer); 
    spin_unlock(&dpcm->lock); 
    return 0; 
} 

static int fmplayer_systimer_prepare(struct snd_pcm_substream *substream) 
{ 
    struct snd_pcm_runtime *runtime = substream->runtime; 
    struct fmplayer_systimer_pcm *dpcm = runtime->private_data; 

    dpcm->frac_pos = 0; 
    dpcm->rate = runtime->rate; 
    dpcm->frac_buffer_size = runtime->buffer_size * HZ; 
    dpcm->frac_period_size = runtime->period_size * HZ; 
    dpcm->frac_period_rest = dpcm->frac_period_size; 
    dpcm->elapsed = 0; 

    return 0; 
} 

static void fmplayer_systimer_callback(unsigned long data) 
{ 
    struct fmplayer_systimer_pcm *dpcm = (struct fmplayer_systimer_pcm *)data; 
    unsigned long flags; 
    int elapsed = 0; 

    spin_lock_irqsave(&dpcm->lock, flags); 
    fmplayer_systimer_update(dpcm); 
    fmplayer_systimer_rearm(dpcm); 
    elapsed = dpcm->elapsed; 
    dpcm->elapsed = 0; 
    spin_unlock_irqrestore(&dpcm->lock, flags); 
    if (elapsed) 
     snd_pcm_period_elapsed(dpcm->substream); 
} 

static snd_pcm_uframes_t 
fmplayer_systimer_pointer(struct snd_pcm_substream *substream) 
{ 
    struct fmplayer_systimer_pcm *dpcm = substream->runtime->private_data; 
    snd_pcm_uframes_t pos; 

    spin_lock(&dpcm->lock); 
    fmplayer_systimer_update(dpcm); 
    pos = dpcm->frac_pos/HZ; 
    spin_unlock(&dpcm->lock); 
    return pos; 
} 

static int fmplayer_systimer_create(struct snd_pcm_substream *substream) 
{ 
    struct fmplayer_systimer_pcm *dpcm; 

    dpcm = kzalloc(sizeof(*dpcm), GFP_KERNEL); 
    if (!dpcm) 
     return -ENOMEM; 
    substream->runtime->private_data = dpcm; 
    setup_timer(&dpcm->timer, fmplayer_systimer_callback, 
      (unsigned long) dpcm); 
    spin_lock_init(&dpcm->lock); 
    dpcm->substream = substream; 
    return 0; 
} 

static void fmplayer_systimer_free(struct snd_pcm_substream *substream) 
{ 
    kfree(substream->runtime->private_data); 
} 

static struct fmplayer_timer_ops fmplayer_systimer_ops = { 
    .create = fmplayer_systimer_create, 
    .free = fmplayer_systimer_free, 
    .prepare = fmplayer_systimer_prepare, 
    .start = fmplayer_systimer_start, 
    .stop = fmplayer_systimer_stop, 
    .pointer = fmplayer_systimer_pointer, 
}; 

#ifdef CONFIG_HIGH_RES_TIMERS 
/* 
* hrtimer interface 
*/ 

struct fmplayer_hrtimer_pcm { 
    ktime_t base_time; 
    ktime_t period_time; 
    atomic_t running; 
    struct hrtimer timer; 
    struct tasklet_struct tasklet; 
    struct snd_pcm_substream *substream; 
}; 

static void fmplayer_hrtimer_pcm_elapsed(unsigned long priv) 
{ 
    struct fmplayer_hrtimer_pcm *dpcm = (struct fmplayer_hrtimer_pcm *)priv; 
    if (atomic_read(&dpcm->running)) 
     snd_pcm_period_elapsed(dpcm->substream); 
} 

static enum hrtimer_restart fmplayer_hrtimer_callback(struct hrtimer *timer) 
{ 
    struct fmplayer_hrtimer_pcm *dpcm; 

    dpcm = container_of(timer, struct fmplayer_hrtimer_pcm, timer); 
    if (!atomic_read(&dpcm->running)) 
     return HRTIMER_NORESTART; 
    tasklet_schedule(&dpcm->tasklet); 
    hrtimer_forward_now(timer, dpcm->period_time); 
    return HRTIMER_RESTART; 
} 

static int fmplayer_hrtimer_start(struct snd_pcm_substream *substream) 
{ 
    struct fmplayer_hrtimer_pcm *dpcm = substream->runtime->private_data; 

    dpcm->base_time = hrtimer_cb_get_time(&dpcm->timer); 
    hrtimer_start(&dpcm->timer, dpcm->period_time, HRTIMER_MODE_REL); 
    atomic_set(&dpcm->running, 1); 
    return 0; 
} 

static int fmplayer_hrtimer_stop(struct snd_pcm_substream *substream) 
{ 
    struct fmplayer_hrtimer_pcm *dpcm = substream->runtime->private_data; 

    atomic_set(&dpcm->running, 0); 
    hrtimer_cancel(&dpcm->timer); 
    return 0; 
} 

static inline void fmplayer_hrtimer_sync(struct fmplayer_hrtimer_pcm *dpcm) 
{ 
    tasklet_kill(&dpcm->tasklet); 
} 

static snd_pcm_uframes_t 
fmplayer_hrtimer_pointer(struct snd_pcm_substream *substream) 
{ 
    struct snd_pcm_runtime *runtime = substream->runtime; 
    struct fmplayer_hrtimer_pcm *dpcm = runtime->private_data; 
    u64 delta; 
    u32 pos; 

    delta = ktime_us_delta(hrtimer_cb_get_time(&dpcm->timer), 
         dpcm->base_time); 
    delta = div_u64(delta * runtime->rate + 999999, 1000000); 
    div_u64_rem(delta, runtime->buffer_size, &pos); 
    return pos; 
} 

static int fmplayer_hrtimer_prepare(struct snd_pcm_substream *substream) 
{ 
    struct snd_pcm_runtime *runtime = substream->runtime; 
    struct fmplayer_hrtimer_pcm *dpcm = runtime->private_data; 
    unsigned int period, rate; 
    long sec; 
    unsigned long nsecs; 

    fmplayer_hrtimer_sync(dpcm); 
    period = runtime->period_size; 
    rate = runtime->rate; 
    sec = period/rate; 
    period %= rate; 
    nsecs = div_u64((u64)period * 1000000000UL + rate - 1, rate); 
    dpcm->period_time = ktime_set(sec, nsecs); 

    return 0; 
} 

static int fmplayer_hrtimer_create(struct snd_pcm_substream *substream) 
{ 
    struct fmplayer_hrtimer_pcm *dpcm; 

    dpcm = kzalloc(sizeof(*dpcm), GFP_KERNEL); 
    if (!dpcm) 
     return -ENOMEM; 
    substream->runtime->private_data = dpcm; 
    hrtimer_init(&dpcm->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); 
    dpcm->timer.function = fmplayer_hrtimer_callback; 
    dpcm->substream = substream; 
    atomic_set(&dpcm->running, 0); 
    tasklet_init(&dpcm->tasklet, fmplayer_hrtimer_pcm_elapsed, 
       (unsigned long)dpcm); 
    return 0; 
} 

static void fmplayer_hrtimer_free(struct snd_pcm_substream *substream) 
{ 
    struct fmplayer_hrtimer_pcm *dpcm = substream->runtime->private_data; 
    fmplayer_hrtimer_sync(dpcm); 
    kfree(dpcm); 
} 

static struct fmplayer_timer_ops fmplayer_hrtimer_ops = { 
    .create = fmplayer_hrtimer_create, 
    .free = fmplayer_hrtimer_free, 
    .prepare = fmplayer_hrtimer_prepare, 
    .start = fmplayer_hrtimer_start, 
    .stop = fmplayer_hrtimer_stop, 
    .pointer = fmplayer_hrtimer_pointer, 
}; 

#endif /* CONFIG_HIGH_RES_TIMERS */ 

/* 
* PCM interface 
*/ 

static int fmplayer_pcm_trigger(struct snd_pcm_substream *substream, int cmd) 
{ 
    struct snd_fmplayer *fmplayer = snd_pcm_substream_chip(substream); 

    switch (cmd) { 
    case SNDRV_PCM_TRIGGER_START: 
    case SNDRV_PCM_TRIGGER_RESUME: 
     iowrite32(1, &(fmplayer->iomem[REGNUM_RW_STAT_CTRL])); 
     return fmplayer->timer_ops->start(substream); 
    case SNDRV_PCM_TRIGGER_STOP: 
    case SNDRV_PCM_TRIGGER_SUSPEND: 
     iowrite32(0, &(fmplayer->iomem[REGNUM_RW_STAT_CTRL])); 
     return fmplayer->timer_ops->stop(substream); 
    } 
    return -EINVAL; 
} 

static int fmplayer_pcm_prepare(struct snd_pcm_substream *substream) 
{ 
    struct snd_fmplayer *fmplayer = snd_pcm_substream_chip(substream); 
    struct snd_pcm_runtime *runtime = substream->runtime; 
    u64 extrate = (u64)(runtime->rate) << DDS_WIDTH; 
    u32 dds_val = (u32)div_u64(extrate, (u64)(DDS_CLK_FREQ)); 
    u32 dma_startaddr = runtime->dma_addr; 
    u32 dma_endaddr = dma_startaddr + (runtime->buffer_size-1)*4; 

    /* Configure the sample rate. */ 
    iowrite32(dds_val, &(fmplayer->iomem[REGNUM_RW_SAMPRATE])); 

    /* Configure the DMA addresses. */ 
    iowrite32(dma_startaddr, &(fmplayer->iomem[REGNUM_RW_MEMSTART])); 
    iowrite32(dma_endaddr, &(fmplayer->iomem[REGNUM_RW_MEMEND])); 

    printk(KERN_DEBUG "DMA range: 0x%08x .. 0x%08x\n", dma_startaddr, dma_endaddr); 
    printk(KERN_DEBUG "DMA size: %d bytes\n", runtime->dma_bytes); 
    printk(KERN_DEBUG "Buffer size: %d frames\n", (int)runtime->buffer_size); 
    printk(KERN_DEBUG "Using %d periods of %d frames\n", 
     runtime->periods, (int)runtime->period_size); 
    printk(KERN_DEBUG "Rate: %d Hz\n", runtime->rate); 
    printk(KERN_DEBUG "Channels: %d\n", runtime->channels); 
    printk(KERN_DEBUG "%d bits/sample, %d bits/frame\n", 
     runtime->sample_bits, runtime->frame_bits); 
    printk(KERN_DEBUG "Access: %d\n", runtime->access); 
    printk(KERN_DEBUG "Format: %d\n", runtime->format); 
    printk(KERN_DEBUG "Subformat: 0x%08x\n", runtime->subformat); 

    return fmplayer->timer_ops->prepare(substream); 
} 

static snd_pcm_uframes_t fmplayer_pcm_pointer(struct snd_pcm_substream *substream) 
{ 
    struct snd_fmplayer *fmplayer = snd_pcm_substream_chip(substream); 
    u32 dma_startaddr = substream->runtime->dma_addr; 
    u32 dma_curraddr = ioread32(&(fmplayer->iomem[REGNUM_RW_MEMSTART])); 

    return (dma_curraddr - dma_startaddr)/4; 
} 

static struct snd_pcm_hardware fmplayer_pcm_hardware = { 
    .info =   (SNDRV_PCM_INFO_MMAP | 
         SNDRV_PCM_INFO_INTERLEAVED | 
         SNDRV_PCM_INFO_MMAP_VALID), 
    .formats =   USE_FORMATS, 
    .rates =   USE_RATE, 
    .rate_min =   USE_RATE_MIN, 
    .rate_max =   USE_RATE_MAX, 
    .channels_min =  USE_CHANNELS_MIN, 
    .channels_max =  USE_CHANNELS_MAX, 
    .buffer_bytes_max = MAX_BUFFER_SIZE, 
    .period_bytes_min = MIN_PERIOD_SIZE, 
    .period_bytes_max = MAX_PERIOD_SIZE, 
    .periods_min =  USE_PERIODS_MIN, 
    .periods_max =  USE_PERIODS_MAX, 
    .fifo_size =  0, 
}; 

static int fmplayer_pcm_hw_params(struct snd_pcm_substream *substream, 
         struct snd_pcm_hw_params *hw_params) 
{ 
    return snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params)); 
} 

static int fmplayer_pcm_hw_free(struct snd_pcm_substream *substream) 
{ 
    struct snd_fmplayer *fmplayer = snd_pcm_substream_chip(substream); 
    /* Make sure that no more memory is accessed by the DMA. */ 
    iowrite32(0, &(fmplayer->iomem[REGNUM_RW_STAT_CTRL])); 

    return snd_pcm_lib_free_pages(substream); 
} 

static int fmplayer_pcm_open(struct snd_pcm_substream *substream) 
{ 
    struct snd_fmplayer *fmplayer = snd_pcm_substream_chip(substream); 
    struct snd_pcm_runtime *runtime = substream->runtime; 
    int err; 

    fmplayer->timer_ops = &fmplayer_systimer_ops; 
#ifdef CONFIG_HIGH_RES_TIMERS 
    if (hrtimer) 
     fmplayer->timer_ops = &fmplayer_hrtimer_ops; 
#endif 

    err = fmplayer->timer_ops->create(substream); 
    if (err < 0) 
     return err; 

    runtime->hw = fmplayer->pcm_hw; 
    return 0; 
} 

static int fmplayer_pcm_close(struct snd_pcm_substream *substream) 
{ 
    struct snd_fmplayer *fmplayer = snd_pcm_substream_chip(substream); 
    fmplayer->timer_ops->free(substream); 
    return 0; 
} 

static struct snd_pcm_ops fmplayer_pcm_ops = { 
    .open = fmplayer_pcm_open, 
    .close = fmplayer_pcm_close, 
    .ioctl = snd_pcm_lib_ioctl, 
    .hw_params = fmplayer_pcm_hw_params, 
    .hw_free = fmplayer_pcm_hw_free, 
    .prepare = fmplayer_pcm_prepare, 
    .trigger = fmplayer_pcm_trigger, 
    .pointer = fmplayer_pcm_pointer, 
}; 

static int snd_card_fmplayer_pcm(struct snd_fmplayer *fmplayer, int device, 
         int substreams) 
{ 
    struct snd_pcm *pcm; 
    int err; 

    err = snd_pcm_new(fmplayer->card, "FM player PCM", device, 
         substreams, substreams, &pcm); 
    if (err < 0) 
     return err; 
    fmplayer->pcm = pcm; 
    snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &fmplayer_pcm_ops); 
    snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, NULL); 
    pcm->private_data = fmplayer; 
    pcm->info_flags = 0; 
    strcpy(pcm->name, "FM player PCM"); 
    snd_pcm_lib_preallocate_pages_for_all(pcm, 
     SNDRV_DMA_TYPE_DEV, /* This type is veeery important! */ 
     NULL, 
     MAX_BUFFER_SIZE, MAX_BUFFER_SIZE); 
    return 0; 
} 


static int snd_fmplayer_probe(struct platform_device *devptr) 
{ 
    struct snd_card *card; 
    struct snd_fmplayer *fmplayer; 
    struct resource *res; 
    int err; 
    int dev = devptr->id; 

    err = snd_card_new(&devptr->dev, index[dev], id[dev], THIS_MODULE, 
       sizeof(struct snd_fmplayer), &card); 
    if (err < 0) 
     return err; 
    fmplayer = card->private_data; 
    fmplayer->card = card; 

    /* Allocate and remap I/O memory for hardware (FPGA) connection. */ 
    res = request_mem_region(REG_BASE, REG_SIZE_BYTES, "fmplayer"); 
    if (res == NULL) { 
     snd_card_free(card); 
     return -ENOMEM; 
    } 
    fmplayer->iomem = ioremap(REG_BASE, REG_SIZE_BYTES); 
    if (fmplayer->iomem == NULL) { 
     release_mem_region(REG_BASE, REG_SIZE_BYTES); 
     snd_card_free(card); 
     return -ENOMEM; 
    } 

    err = snd_card_fmplayer_pcm(fmplayer, 0, 1); 
    if (err < 0) 
     goto __nodev; 

    fmplayer->pcm_hw = fmplayer_pcm_hardware; 

    strcpy(card->driver, "fmplayer"); 
    strcpy(card->shortname, "fmplayer"); 
    sprintf(card->longname, "fmplayer %i", dev + 1); 

    err = snd_card_register(card); 
    if (err == 0) { 
     platform_set_drvdata(devptr, card); 
     return 0; 
    } 
__nodev: 
    snd_card_free(card); 
    return err; 
} 

static int snd_fmplayer_remove(struct platform_device *devptr) 
{ 
    /* Release and unmap the I/O memory. */ 
    struct snd_card *card = platform_get_drvdata(devptr); 
    if (card != NULL) { 
     struct snd_fmplayer *fmplayer = card->private_data; 
     if (fmplayer != NULL) { 
      /* Make sure that the driver is stopped. */ 
      iowrite32(0, &(fmplayer->iomem[REGNUM_RW_STAT_CTRL])); 
      iounmap(fmplayer->iomem); 
      release_mem_region(REG_BASE, REG_SIZE_BYTES); 
     } 
    } 
    snd_card_free(platform_get_drvdata(devptr)); 
    return 0; 
} 

#define SND_FMPLAYER_DRIVER "snd_fmplayer" 

static struct platform_driver snd_fmplayer_driver = { 
    .probe = snd_fmplayer_probe, 
    .remove = snd_fmplayer_remove, 
    .driver = { 
     .name = SND_FMPLAYER_DRIVER, 
     .pm = NULL, 
    }, 
}; 

static void snd_fmplayer_unregister_all(void) 
{ 
    int i; 

    for (i = 0; i < ARRAY_SIZE(devices); ++i) 
     platform_device_unregister(devices[i]); 
    platform_driver_unregister(&snd_fmplayer_driver); 
} 

static int __init alsa_card_fmplayer_init(void) 
{ 
    int i, cards, err; 

    err = platform_driver_register(&snd_fmplayer_driver); 
    if (err < 0) 
     return err; 

    cards = 0; 
    for (i = 0; i < SNDRV_CARDS; i++) { 
     struct platform_device *device; 
     if (! enable[i]) 
      continue; 
     device = platform_device_register_simple(SND_FMPLAYER_DRIVER, 
          i, NULL, 0); 
     if (IS_ERR(device)) 
      continue; 
     if (!platform_get_drvdata(device)) { 
      platform_device_unregister(device); 
      continue; 
     } 
     devices[i] = device; 
     cards++; 
    } 
    if (!cards) { 
#ifdef MODULE 
     printk(KERN_ERR "FM player soundcard not found or device busy\n"); 
#endif 
     snd_fmplayer_unregister_all(); 
     return -ENODEV; 
    } 

    return 0; 
} 

static void __exit alsa_card_fmplayer_exit(void) 
{ 
    snd_fmplayer_unregister_all(); 
} 

module_init(alsa_card_fmplayer_init) 
module_exit(alsa_card_fmplayer_exit) 
Смежные вопросы