2016-01-03 96 views
1

我正在使用Altera Cyclone V SoC(運行Linux的ARM Cortex-A9耦合到FPGA)爲電路板設計軟件。 Linux操作系統運行良好,支持板載外圍設備(以太網,SD卡等),我可以從用戶空間程序訪問FPGA,但目前使用mmap()。
現在,該主板還可以用作SDR(軟件定義無線電)平臺,所以作爲一個噱頭,我已經實現了一個非常簡單的FM發射器。這已經運行良好,但它只是FPGA內部的靜態配置,沒有真正連接到操作系統。爲Cyclone V SoC創建簡單的音頻驅動程序(Linux)

我想要做的是將發射器連接到操作系統,理想的是它可以用作標準音頻輸出設備,即聲卡。硬件方面是靈活的,目前非常簡單:

  • 狀態寄存器告訴是否正在播放樣本。
  • 存儲器地址寄存器將當前讀指針保存在系統存儲器中取樣的地方(DMA)。
  • 一個包含剩餘樣本數量的寄存器。
  • 設置硬件採樣率的寄存器。

這個接口不是固定的,如果有必要,它可以很容易地改變。如果明智的話,我也可以將界面更新爲類似於現有設備的界面,以便我可以使用它的驅動程序。

現在的問題是,我應該如何開始與Linux集成,即如何獲取/構建合適的內核模塊。我對內核模塊開發相當陌生,所以我不知道從哪裏開始。 documentation of the Linux sound subsystem肯定會包含很多有用的信息,但我錯過了「大圖片」,例如什麼應該進入驅動程序的哪個部分,或者系統的實際接口(如果我正確地得到了ALSA),看起來應該如何。

btw:我知道LDD3書,並有一個內核模塊應該如何看待和工作的一般想法,但我不知道如何建立一個與音頻子系統一起播放的很好。

感謝您的意見,建議,鏈接等

回答

0

隨着在presentation on Free ElectronsALSA driver tutorial找到的信息,都是由@Mali提到的,​​我終於成功地建立我的簡單的驅動程序。我將發佈下面的代碼,也許它對其他人有用。它直接基於Linux內核源代碼中的sound/drivers/dummy.c驅動程序。其中很重要的一點是,從

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

改變snd_card_fmplayer_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); 

沒有這個變化,駕駛員基本工作,但播放數據幾乎類似於實際樣本,似乎是由緩存效應引起的。

該模塊仍然需要內核定時器(無論是系統時間還是高分辨率定時器),因爲硬件未配置爲在播放時生成中斷。如果它被用於某些嚴重的應用程序,這應該是固定的。

再見,
菲利普

/* 
* 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)