Path: blob/master/sound/pci/cs5535audio/cs5535audio_pcm.c
10818 views
/*1* Driver for audio on multifunction CS5535 companion device2* Copyright (C) Jaya Kumar3*4* Based on Jaroslav Kysela and Takashi Iwai's examples.5* This work was sponsored by CIS(M) Sdn Bhd.6*7* This program is free software; you can redistribute it and/or modify8* it under the terms of the GNU General Public License as published by9* the Free Software Foundation; either version 2 of the License, or10* (at your option) any later version.11*12* This program is distributed in the hope that it will be useful,13* but WITHOUT ANY WARRANTY; without even the implied warranty of14* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the15* GNU General Public License for more details.16*17* You should have received a copy of the GNU General Public License18* along with this program; if not, write to the Free Software19* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA20*21* todo: add be fmt support, spdif, pm22*/2324#include <linux/init.h>25#include <linux/pci.h>26#include <sound/core.h>27#include <sound/control.h>28#include <sound/initval.h>29#include <sound/asoundef.h>30#include <sound/pcm.h>31#include <sound/pcm_params.h>32#include <sound/ac97_codec.h>33#include "cs5535audio.h"3435static struct snd_pcm_hardware snd_cs5535audio_playback =36{37.info = (38SNDRV_PCM_INFO_MMAP |39SNDRV_PCM_INFO_INTERLEAVED |40SNDRV_PCM_INFO_BLOCK_TRANSFER |41SNDRV_PCM_INFO_MMAP_VALID |42SNDRV_PCM_INFO_PAUSE |43SNDRV_PCM_INFO_RESUME44),45.formats = (46SNDRV_PCM_FMTBIT_S16_LE47),48.rates = (49SNDRV_PCM_RATE_CONTINUOUS |50SNDRV_PCM_RATE_8000_4800051),52.rate_min = 4000,53.rate_max = 48000,54.channels_min = 2,55.channels_max = 2,56.buffer_bytes_max = (128*1024),57.period_bytes_min = 64,58.period_bytes_max = (64*1024 - 16),59.periods_min = 1,60.periods_max = CS5535AUDIO_MAX_DESCRIPTORS,61.fifo_size = 0,62};6364static struct snd_pcm_hardware snd_cs5535audio_capture =65{66.info = (67SNDRV_PCM_INFO_MMAP |68SNDRV_PCM_INFO_INTERLEAVED |69SNDRV_PCM_INFO_BLOCK_TRANSFER |70SNDRV_PCM_INFO_MMAP_VALID71),72.formats = (73SNDRV_PCM_FMTBIT_S16_LE74),75.rates = (76SNDRV_PCM_RATE_CONTINUOUS |77SNDRV_PCM_RATE_8000_4800078),79.rate_min = 4000,80.rate_max = 48000,81.channels_min = 2,82.channels_max = 2,83.buffer_bytes_max = (128*1024),84.period_bytes_min = 64,85.period_bytes_max = (64*1024 - 16),86.periods_min = 1,87.periods_max = CS5535AUDIO_MAX_DESCRIPTORS,88.fifo_size = 0,89};9091static int snd_cs5535audio_playback_open(struct snd_pcm_substream *substream)92{93int err;94struct cs5535audio *cs5535au = snd_pcm_substream_chip(substream);95struct snd_pcm_runtime *runtime = substream->runtime;9697runtime->hw = snd_cs5535audio_playback;98runtime->hw.rates = cs5535au->ac97->rates[AC97_RATES_FRONT_DAC];99snd_pcm_limit_hw_rates(runtime);100cs5535au->playback_substream = substream;101runtime->private_data = &(cs5535au->dmas[CS5535AUDIO_DMA_PLAYBACK]);102if ((err = snd_pcm_hw_constraint_integer(runtime,103SNDRV_PCM_HW_PARAM_PERIODS)) < 0)104return err;105106return 0;107}108109static int snd_cs5535audio_playback_close(struct snd_pcm_substream *substream)110{111return 0;112}113114#define CS5535AUDIO_DESC_LIST_SIZE \115PAGE_ALIGN(CS5535AUDIO_MAX_DESCRIPTORS * sizeof(struct cs5535audio_dma_desc))116117static int cs5535audio_build_dma_packets(struct cs5535audio *cs5535au,118struct cs5535audio_dma *dma,119struct snd_pcm_substream *substream,120unsigned int periods,121unsigned int period_bytes)122{123unsigned int i;124u32 addr, desc_addr, jmpprd_addr;125struct cs5535audio_dma_desc *lastdesc;126127if (periods > CS5535AUDIO_MAX_DESCRIPTORS)128return -ENOMEM;129130if (dma->desc_buf.area == NULL) {131if (snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV,132snd_dma_pci_data(cs5535au->pci),133CS5535AUDIO_DESC_LIST_SIZE+1,134&dma->desc_buf) < 0)135return -ENOMEM;136dma->period_bytes = dma->periods = 0;137}138139if (dma->periods == periods && dma->period_bytes == period_bytes)140return 0;141142/* the u32 cast is okay because in snd*create we successfully told143pci alloc that we're only 32 bit capable so the uppper will be 0 */144addr = (u32) substream->runtime->dma_addr;145desc_addr = (u32) dma->desc_buf.addr;146for (i = 0; i < periods; i++) {147struct cs5535audio_dma_desc *desc =148&((struct cs5535audio_dma_desc *) dma->desc_buf.area)[i];149desc->addr = cpu_to_le32(addr);150desc->size = cpu_to_le32(period_bytes);151desc->ctlreserved = cpu_to_le16(PRD_EOP);152desc_addr += sizeof(struct cs5535audio_dma_desc);153addr += period_bytes;154}155/* we reserved one dummy descriptor at the end to do the PRD jump */156lastdesc = &((struct cs5535audio_dma_desc *) dma->desc_buf.area)[periods];157lastdesc->addr = cpu_to_le32((u32) dma->desc_buf.addr);158lastdesc->size = 0;159lastdesc->ctlreserved = cpu_to_le16(PRD_JMP);160jmpprd_addr = cpu_to_le32(lastdesc->addr +161(sizeof(struct cs5535audio_dma_desc)*periods));162163dma->substream = substream;164dma->period_bytes = period_bytes;165dma->periods = periods;166spin_lock_irq(&cs5535au->reg_lock);167dma->ops->disable_dma(cs5535au);168dma->ops->setup_prd(cs5535au, jmpprd_addr);169spin_unlock_irq(&cs5535au->reg_lock);170return 0;171}172173static void cs5535audio_playback_enable_dma(struct cs5535audio *cs5535au)174{175cs_writeb(cs5535au, ACC_BM0_CMD, BM_CTL_EN);176}177178static void cs5535audio_playback_disable_dma(struct cs5535audio *cs5535au)179{180cs_writeb(cs5535au, ACC_BM0_CMD, 0);181}182183static void cs5535audio_playback_pause_dma(struct cs5535audio *cs5535au)184{185cs_writeb(cs5535au, ACC_BM0_CMD, BM_CTL_PAUSE);186}187188static void cs5535audio_playback_setup_prd(struct cs5535audio *cs5535au,189u32 prd_addr)190{191cs_writel(cs5535au, ACC_BM0_PRD, prd_addr);192}193194static u32 cs5535audio_playback_read_prd(struct cs5535audio *cs5535au)195{196return cs_readl(cs5535au, ACC_BM0_PRD);197}198199static u32 cs5535audio_playback_read_dma_pntr(struct cs5535audio *cs5535au)200{201return cs_readl(cs5535au, ACC_BM0_PNTR);202}203204static void cs5535audio_capture_enable_dma(struct cs5535audio *cs5535au)205{206cs_writeb(cs5535au, ACC_BM1_CMD, BM_CTL_EN);207}208209static void cs5535audio_capture_disable_dma(struct cs5535audio *cs5535au)210{211cs_writeb(cs5535au, ACC_BM1_CMD, 0);212}213214static void cs5535audio_capture_pause_dma(struct cs5535audio *cs5535au)215{216cs_writeb(cs5535au, ACC_BM1_CMD, BM_CTL_PAUSE);217}218219static void cs5535audio_capture_setup_prd(struct cs5535audio *cs5535au,220u32 prd_addr)221{222cs_writel(cs5535au, ACC_BM1_PRD, prd_addr);223}224225static u32 cs5535audio_capture_read_prd(struct cs5535audio *cs5535au)226{227return cs_readl(cs5535au, ACC_BM1_PRD);228}229230static u32 cs5535audio_capture_read_dma_pntr(struct cs5535audio *cs5535au)231{232return cs_readl(cs5535au, ACC_BM1_PNTR);233}234235static void cs5535audio_clear_dma_packets(struct cs5535audio *cs5535au,236struct cs5535audio_dma *dma,237struct snd_pcm_substream *substream)238{239snd_dma_free_pages(&dma->desc_buf);240dma->desc_buf.area = NULL;241dma->substream = NULL;242}243244static int snd_cs5535audio_hw_params(struct snd_pcm_substream *substream,245struct snd_pcm_hw_params *hw_params)246{247struct cs5535audio *cs5535au = snd_pcm_substream_chip(substream);248struct cs5535audio_dma *dma = substream->runtime->private_data;249int err;250251err = snd_pcm_lib_malloc_pages(substream,252params_buffer_bytes(hw_params));253if (err < 0)254return err;255dma->buf_addr = substream->runtime->dma_addr;256dma->buf_bytes = params_buffer_bytes(hw_params);257258err = cs5535audio_build_dma_packets(cs5535au, dma, substream,259params_periods(hw_params),260params_period_bytes(hw_params));261if (!err)262dma->pcm_open_flag = 1;263264return err;265}266267static int snd_cs5535audio_hw_free(struct snd_pcm_substream *substream)268{269struct cs5535audio *cs5535au = snd_pcm_substream_chip(substream);270struct cs5535audio_dma *dma = substream->runtime->private_data;271272if (dma->pcm_open_flag) {273if (substream == cs5535au->playback_substream)274snd_ac97_update_power(cs5535au->ac97,275AC97_PCM_FRONT_DAC_RATE, 0);276else277snd_ac97_update_power(cs5535au->ac97,278AC97_PCM_LR_ADC_RATE, 0);279dma->pcm_open_flag = 0;280}281cs5535audio_clear_dma_packets(cs5535au, dma, substream);282return snd_pcm_lib_free_pages(substream);283}284285static int snd_cs5535audio_playback_prepare(struct snd_pcm_substream *substream)286{287struct cs5535audio *cs5535au = snd_pcm_substream_chip(substream);288return snd_ac97_set_rate(cs5535au->ac97, AC97_PCM_FRONT_DAC_RATE,289substream->runtime->rate);290}291292static int snd_cs5535audio_trigger(struct snd_pcm_substream *substream, int cmd)293{294struct cs5535audio *cs5535au = snd_pcm_substream_chip(substream);295struct cs5535audio_dma *dma = substream->runtime->private_data;296int err = 0;297298spin_lock(&cs5535au->reg_lock);299switch (cmd) {300case SNDRV_PCM_TRIGGER_PAUSE_PUSH:301dma->ops->pause_dma(cs5535au);302break;303case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:304dma->ops->enable_dma(cs5535au);305break;306case SNDRV_PCM_TRIGGER_START:307dma->ops->enable_dma(cs5535au);308break;309case SNDRV_PCM_TRIGGER_RESUME:310dma->ops->enable_dma(cs5535au);311break;312case SNDRV_PCM_TRIGGER_STOP:313dma->ops->disable_dma(cs5535au);314break;315case SNDRV_PCM_TRIGGER_SUSPEND:316dma->ops->disable_dma(cs5535au);317break;318default:319snd_printk(KERN_ERR "unhandled trigger\n");320err = -EINVAL;321break;322}323spin_unlock(&cs5535au->reg_lock);324return err;325}326327static snd_pcm_uframes_t snd_cs5535audio_pcm_pointer(struct snd_pcm_substream328*substream)329{330struct cs5535audio *cs5535au = snd_pcm_substream_chip(substream);331u32 curdma;332struct cs5535audio_dma *dma;333334dma = substream->runtime->private_data;335curdma = dma->ops->read_dma_pntr(cs5535au);336if (curdma < dma->buf_addr) {337snd_printk(KERN_ERR "curdma=%x < %x bufaddr.\n",338curdma, dma->buf_addr);339return 0;340}341curdma -= dma->buf_addr;342if (curdma >= dma->buf_bytes) {343snd_printk(KERN_ERR "diff=%x >= %x buf_bytes.\n",344curdma, dma->buf_bytes);345return 0;346}347return bytes_to_frames(substream->runtime, curdma);348}349350static int snd_cs5535audio_capture_open(struct snd_pcm_substream *substream)351{352int err;353struct cs5535audio *cs5535au = snd_pcm_substream_chip(substream);354struct snd_pcm_runtime *runtime = substream->runtime;355356runtime->hw = snd_cs5535audio_capture;357runtime->hw.rates = cs5535au->ac97->rates[AC97_RATES_ADC];358snd_pcm_limit_hw_rates(runtime);359cs5535au->capture_substream = substream;360runtime->private_data = &(cs5535au->dmas[CS5535AUDIO_DMA_CAPTURE]);361if ((err = snd_pcm_hw_constraint_integer(runtime,362SNDRV_PCM_HW_PARAM_PERIODS)) < 0)363return err;364olpc_capture_open(cs5535au->ac97);365return 0;366}367368static int snd_cs5535audio_capture_close(struct snd_pcm_substream *substream)369{370struct cs5535audio *cs5535au = snd_pcm_substream_chip(substream);371olpc_capture_close(cs5535au->ac97);372return 0;373}374375static int snd_cs5535audio_capture_prepare(struct snd_pcm_substream *substream)376{377struct cs5535audio *cs5535au = snd_pcm_substream_chip(substream);378return snd_ac97_set_rate(cs5535au->ac97, AC97_PCM_LR_ADC_RATE,379substream->runtime->rate);380}381382static struct snd_pcm_ops snd_cs5535audio_playback_ops = {383.open = snd_cs5535audio_playback_open,384.close = snd_cs5535audio_playback_close,385.ioctl = snd_pcm_lib_ioctl,386.hw_params = snd_cs5535audio_hw_params,387.hw_free = snd_cs5535audio_hw_free,388.prepare = snd_cs5535audio_playback_prepare,389.trigger = snd_cs5535audio_trigger,390.pointer = snd_cs5535audio_pcm_pointer,391};392393static struct snd_pcm_ops snd_cs5535audio_capture_ops = {394.open = snd_cs5535audio_capture_open,395.close = snd_cs5535audio_capture_close,396.ioctl = snd_pcm_lib_ioctl,397.hw_params = snd_cs5535audio_hw_params,398.hw_free = snd_cs5535audio_hw_free,399.prepare = snd_cs5535audio_capture_prepare,400.trigger = snd_cs5535audio_trigger,401.pointer = snd_cs5535audio_pcm_pointer,402};403404static struct cs5535audio_dma_ops snd_cs5535audio_playback_dma_ops = {405.type = CS5535AUDIO_DMA_PLAYBACK,406.enable_dma = cs5535audio_playback_enable_dma,407.disable_dma = cs5535audio_playback_disable_dma,408.setup_prd = cs5535audio_playback_setup_prd,409.read_prd = cs5535audio_playback_read_prd,410.pause_dma = cs5535audio_playback_pause_dma,411.read_dma_pntr = cs5535audio_playback_read_dma_pntr,412};413414static struct cs5535audio_dma_ops snd_cs5535audio_capture_dma_ops = {415.type = CS5535AUDIO_DMA_CAPTURE,416.enable_dma = cs5535audio_capture_enable_dma,417.disable_dma = cs5535audio_capture_disable_dma,418.setup_prd = cs5535audio_capture_setup_prd,419.read_prd = cs5535audio_capture_read_prd,420.pause_dma = cs5535audio_capture_pause_dma,421.read_dma_pntr = cs5535audio_capture_read_dma_pntr,422};423424int __devinit snd_cs5535audio_pcm(struct cs5535audio *cs5535au)425{426struct snd_pcm *pcm;427int err;428429err = snd_pcm_new(cs5535au->card, "CS5535 Audio", 0, 1, 1, &pcm);430if (err < 0)431return err;432433cs5535au->dmas[CS5535AUDIO_DMA_PLAYBACK].ops =434&snd_cs5535audio_playback_dma_ops;435cs5535au->dmas[CS5535AUDIO_DMA_CAPTURE].ops =436&snd_cs5535audio_capture_dma_ops;437snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK,438&snd_cs5535audio_playback_ops);439snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE,440&snd_cs5535audio_capture_ops);441442pcm->private_data = cs5535au;443pcm->info_flags = 0;444strcpy(pcm->name, "CS5535 Audio");445446snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,447snd_dma_pci_data(cs5535au->pci),44864*1024, 128*1024);449cs5535au->pcm = pcm;450451return 0;452}453454455456