Path: blob/master/sound/pci/cs5535audio/cs5535audio_pcm.c
26424 views
// SPDX-License-Identifier: GPL-2.0-or-later1/*2* Driver for audio on multifunction CS5535 companion device3* Copyright (C) Jaya Kumar4*5* Based on Jaroslav Kysela and Takashi Iwai's examples.6* This work was sponsored by CIS(M) Sdn Bhd.7*8* todo: add be fmt support, spdif, pm9*/1011#include <linux/init.h>12#include <linux/pci.h>13#include <sound/core.h>14#include <sound/control.h>15#include <sound/initval.h>16#include <sound/asoundef.h>17#include <sound/pcm.h>18#include <sound/pcm_params.h>19#include <sound/ac97_codec.h>20#include "cs5535audio.h"2122static const struct snd_pcm_hardware snd_cs5535audio_playback =23{24.info = (25SNDRV_PCM_INFO_MMAP |26SNDRV_PCM_INFO_INTERLEAVED |27SNDRV_PCM_INFO_BLOCK_TRANSFER |28SNDRV_PCM_INFO_MMAP_VALID |29SNDRV_PCM_INFO_PAUSE |30SNDRV_PCM_INFO_RESUME31),32.formats = (33SNDRV_PCM_FMTBIT_S16_LE34),35.rates = (36SNDRV_PCM_RATE_CONTINUOUS |37SNDRV_PCM_RATE_8000_4800038),39.rate_min = 4000,40.rate_max = 48000,41.channels_min = 2,42.channels_max = 2,43.buffer_bytes_max = (128*1024),44.period_bytes_min = 64,45.period_bytes_max = (64*1024 - 16),46.periods_min = 1,47.periods_max = CS5535AUDIO_MAX_DESCRIPTORS,48.fifo_size = 0,49};5051static const struct snd_pcm_hardware snd_cs5535audio_capture =52{53.info = (54SNDRV_PCM_INFO_MMAP |55SNDRV_PCM_INFO_INTERLEAVED |56SNDRV_PCM_INFO_BLOCK_TRANSFER |57SNDRV_PCM_INFO_MMAP_VALID58),59.formats = (60SNDRV_PCM_FMTBIT_S16_LE61),62.rates = (63SNDRV_PCM_RATE_CONTINUOUS |64SNDRV_PCM_RATE_8000_4800065),66.rate_min = 4000,67.rate_max = 48000,68.channels_min = 2,69.channels_max = 2,70.buffer_bytes_max = (128*1024),71.period_bytes_min = 64,72.period_bytes_max = (64*1024 - 16),73.periods_min = 1,74.periods_max = CS5535AUDIO_MAX_DESCRIPTORS,75.fifo_size = 0,76};7778static int snd_cs5535audio_playback_open(struct snd_pcm_substream *substream)79{80int err;81struct cs5535audio *cs5535au = snd_pcm_substream_chip(substream);82struct snd_pcm_runtime *runtime = substream->runtime;8384runtime->hw = snd_cs5535audio_playback;85runtime->hw.rates = cs5535au->ac97->rates[AC97_RATES_FRONT_DAC];86snd_pcm_limit_hw_rates(runtime);87cs5535au->playback_substream = substream;88runtime->private_data = &(cs5535au->dmas[CS5535AUDIO_DMA_PLAYBACK]);89err = snd_pcm_hw_constraint_integer(runtime,90SNDRV_PCM_HW_PARAM_PERIODS);91if (err < 0)92return err;9394return 0;95}9697static int snd_cs5535audio_playback_close(struct snd_pcm_substream *substream)98{99return 0;100}101102#define CS5535AUDIO_DESC_LIST_SIZE \103PAGE_ALIGN(CS5535AUDIO_MAX_DESCRIPTORS * sizeof(struct cs5535audio_dma_desc))104105static int cs5535audio_build_dma_packets(struct cs5535audio *cs5535au,106struct cs5535audio_dma *dma,107struct snd_pcm_substream *substream,108unsigned int periods,109unsigned int period_bytes)110{111unsigned int i;112u32 addr, jmpprd_addr;113struct cs5535audio_dma_desc *lastdesc;114115if (periods > CS5535AUDIO_MAX_DESCRIPTORS)116return -ENOMEM;117118if (dma->desc_buf.area == NULL) {119if (snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV,120&cs5535au->pci->dev,121CS5535AUDIO_DESC_LIST_SIZE+1,122&dma->desc_buf) < 0)123return -ENOMEM;124dma->period_bytes = dma->periods = 0;125}126127if (dma->periods == periods && dma->period_bytes == period_bytes)128return 0;129130/* the u32 cast is okay because in snd*create we successfully told131pci alloc that we're only 32 bit capable so the upper will be 0 */132addr = (u32) substream->runtime->dma_addr;133for (i = 0; i < periods; i++) {134struct cs5535audio_dma_desc *desc =135&((struct cs5535audio_dma_desc *) dma->desc_buf.area)[i];136desc->addr = cpu_to_le32(addr);137desc->size = cpu_to_le16(period_bytes);138desc->ctlreserved = cpu_to_le16(PRD_EOP);139addr += period_bytes;140}141/* we reserved one dummy descriptor at the end to do the PRD jump */142lastdesc = &((struct cs5535audio_dma_desc *) dma->desc_buf.area)[periods];143lastdesc->addr = cpu_to_le32((u32) dma->desc_buf.addr);144lastdesc->size = 0;145lastdesc->ctlreserved = cpu_to_le16(PRD_JMP);146jmpprd_addr = (u32)dma->desc_buf.addr +147sizeof(struct cs5535audio_dma_desc) * periods;148149dma->substream = substream;150dma->period_bytes = period_bytes;151dma->periods = periods;152spin_lock_irq(&cs5535au->reg_lock);153dma->ops->disable_dma(cs5535au);154dma->ops->setup_prd(cs5535au, jmpprd_addr);155spin_unlock_irq(&cs5535au->reg_lock);156return 0;157}158159static void cs5535audio_playback_enable_dma(struct cs5535audio *cs5535au)160{161cs_writeb(cs5535au, ACC_BM0_CMD, BM_CTL_EN);162}163164static void cs5535audio_playback_disable_dma(struct cs5535audio *cs5535au)165{166cs_writeb(cs5535au, ACC_BM0_CMD, 0);167}168169static void cs5535audio_playback_pause_dma(struct cs5535audio *cs5535au)170{171cs_writeb(cs5535au, ACC_BM0_CMD, BM_CTL_PAUSE);172}173174static void cs5535audio_playback_setup_prd(struct cs5535audio *cs5535au,175u32 prd_addr)176{177cs_writel(cs5535au, ACC_BM0_PRD, prd_addr);178}179180static u32 cs5535audio_playback_read_prd(struct cs5535audio *cs5535au)181{182return cs_readl(cs5535au, ACC_BM0_PRD);183}184185static u32 cs5535audio_playback_read_dma_pntr(struct cs5535audio *cs5535au)186{187return cs_readl(cs5535au, ACC_BM0_PNTR);188}189190static void cs5535audio_capture_enable_dma(struct cs5535audio *cs5535au)191{192cs_writeb(cs5535au, ACC_BM1_CMD, BM_CTL_EN);193}194195static void cs5535audio_capture_disable_dma(struct cs5535audio *cs5535au)196{197cs_writeb(cs5535au, ACC_BM1_CMD, 0);198}199200static void cs5535audio_capture_pause_dma(struct cs5535audio *cs5535au)201{202cs_writeb(cs5535au, ACC_BM1_CMD, BM_CTL_PAUSE);203}204205static void cs5535audio_capture_setup_prd(struct cs5535audio *cs5535au,206u32 prd_addr)207{208cs_writel(cs5535au, ACC_BM1_PRD, prd_addr);209}210211static u32 cs5535audio_capture_read_prd(struct cs5535audio *cs5535au)212{213return cs_readl(cs5535au, ACC_BM1_PRD);214}215216static u32 cs5535audio_capture_read_dma_pntr(struct cs5535audio *cs5535au)217{218return cs_readl(cs5535au, ACC_BM1_PNTR);219}220221static void cs5535audio_clear_dma_packets(struct cs5535audio *cs5535au,222struct cs5535audio_dma *dma,223struct snd_pcm_substream *substream)224{225snd_dma_free_pages(&dma->desc_buf);226dma->desc_buf.area = NULL;227dma->substream = NULL;228}229230static int snd_cs5535audio_hw_params(struct snd_pcm_substream *substream,231struct snd_pcm_hw_params *hw_params)232{233struct cs5535audio *cs5535au = snd_pcm_substream_chip(substream);234struct cs5535audio_dma *dma = substream->runtime->private_data;235int err;236237dma->buf_addr = substream->runtime->dma_addr;238dma->buf_bytes = params_buffer_bytes(hw_params);239240err = cs5535audio_build_dma_packets(cs5535au, dma, substream,241params_periods(hw_params),242params_period_bytes(hw_params));243if (!err)244dma->pcm_open_flag = 1;245246return err;247}248249static int snd_cs5535audio_hw_free(struct snd_pcm_substream *substream)250{251struct cs5535audio *cs5535au = snd_pcm_substream_chip(substream);252struct cs5535audio_dma *dma = substream->runtime->private_data;253254if (dma->pcm_open_flag) {255if (substream == cs5535au->playback_substream)256snd_ac97_update_power(cs5535au->ac97,257AC97_PCM_FRONT_DAC_RATE, 0);258else259snd_ac97_update_power(cs5535au->ac97,260AC97_PCM_LR_ADC_RATE, 0);261dma->pcm_open_flag = 0;262}263cs5535audio_clear_dma_packets(cs5535au, dma, substream);264return 0;265}266267static int snd_cs5535audio_playback_prepare(struct snd_pcm_substream *substream)268{269struct cs5535audio *cs5535au = snd_pcm_substream_chip(substream);270return snd_ac97_set_rate(cs5535au->ac97, AC97_PCM_FRONT_DAC_RATE,271substream->runtime->rate);272}273274static int snd_cs5535audio_trigger(struct snd_pcm_substream *substream, int cmd)275{276struct cs5535audio *cs5535au = snd_pcm_substream_chip(substream);277struct cs5535audio_dma *dma = substream->runtime->private_data;278int err = 0;279280spin_lock(&cs5535au->reg_lock);281switch (cmd) {282case SNDRV_PCM_TRIGGER_PAUSE_PUSH:283dma->ops->pause_dma(cs5535au);284break;285case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:286dma->ops->enable_dma(cs5535au);287break;288case SNDRV_PCM_TRIGGER_START:289dma->ops->enable_dma(cs5535au);290break;291case SNDRV_PCM_TRIGGER_RESUME:292dma->ops->enable_dma(cs5535au);293break;294case SNDRV_PCM_TRIGGER_STOP:295dma->ops->disable_dma(cs5535au);296break;297case SNDRV_PCM_TRIGGER_SUSPEND:298dma->ops->disable_dma(cs5535au);299break;300default:301dev_err(cs5535au->card->dev, "unhandled trigger\n");302err = -EINVAL;303break;304}305spin_unlock(&cs5535au->reg_lock);306return err;307}308309static snd_pcm_uframes_t snd_cs5535audio_pcm_pointer(struct snd_pcm_substream310*substream)311{312struct cs5535audio *cs5535au = snd_pcm_substream_chip(substream);313u32 curdma;314struct cs5535audio_dma *dma;315316dma = substream->runtime->private_data;317curdma = dma->ops->read_dma_pntr(cs5535au);318if (curdma < dma->buf_addr) {319dev_err(cs5535au->card->dev, "curdma=%x < %x bufaddr.\n",320curdma, dma->buf_addr);321return 0;322}323curdma -= dma->buf_addr;324if (curdma >= dma->buf_bytes) {325dev_err(cs5535au->card->dev, "diff=%x >= %x buf_bytes.\n",326curdma, dma->buf_bytes);327return 0;328}329return bytes_to_frames(substream->runtime, curdma);330}331332static int snd_cs5535audio_capture_open(struct snd_pcm_substream *substream)333{334int err;335struct cs5535audio *cs5535au = snd_pcm_substream_chip(substream);336struct snd_pcm_runtime *runtime = substream->runtime;337338runtime->hw = snd_cs5535audio_capture;339runtime->hw.rates = cs5535au->ac97->rates[AC97_RATES_ADC];340snd_pcm_limit_hw_rates(runtime);341cs5535au->capture_substream = substream;342runtime->private_data = &(cs5535au->dmas[CS5535AUDIO_DMA_CAPTURE]);343err = snd_pcm_hw_constraint_integer(runtime,344SNDRV_PCM_HW_PARAM_PERIODS);345if (err < 0)346return err;347olpc_capture_open(cs5535au->ac97);348return 0;349}350351static int snd_cs5535audio_capture_close(struct snd_pcm_substream *substream)352{353struct cs5535audio *cs5535au = snd_pcm_substream_chip(substream);354olpc_capture_close(cs5535au->ac97);355return 0;356}357358static int snd_cs5535audio_capture_prepare(struct snd_pcm_substream *substream)359{360struct cs5535audio *cs5535au = snd_pcm_substream_chip(substream);361return snd_ac97_set_rate(cs5535au->ac97, AC97_PCM_LR_ADC_RATE,362substream->runtime->rate);363}364365static const struct snd_pcm_ops snd_cs5535audio_playback_ops = {366.open = snd_cs5535audio_playback_open,367.close = snd_cs5535audio_playback_close,368.hw_params = snd_cs5535audio_hw_params,369.hw_free = snd_cs5535audio_hw_free,370.prepare = snd_cs5535audio_playback_prepare,371.trigger = snd_cs5535audio_trigger,372.pointer = snd_cs5535audio_pcm_pointer,373};374375static const struct snd_pcm_ops snd_cs5535audio_capture_ops = {376.open = snd_cs5535audio_capture_open,377.close = snd_cs5535audio_capture_close,378.hw_params = snd_cs5535audio_hw_params,379.hw_free = snd_cs5535audio_hw_free,380.prepare = snd_cs5535audio_capture_prepare,381.trigger = snd_cs5535audio_trigger,382.pointer = snd_cs5535audio_pcm_pointer,383};384385static const struct cs5535audio_dma_ops snd_cs5535audio_playback_dma_ops = {386.type = CS5535AUDIO_DMA_PLAYBACK,387.enable_dma = cs5535audio_playback_enable_dma,388.disable_dma = cs5535audio_playback_disable_dma,389.setup_prd = cs5535audio_playback_setup_prd,390.read_prd = cs5535audio_playback_read_prd,391.pause_dma = cs5535audio_playback_pause_dma,392.read_dma_pntr = cs5535audio_playback_read_dma_pntr,393};394395static const struct cs5535audio_dma_ops snd_cs5535audio_capture_dma_ops = {396.type = CS5535AUDIO_DMA_CAPTURE,397.enable_dma = cs5535audio_capture_enable_dma,398.disable_dma = cs5535audio_capture_disable_dma,399.setup_prd = cs5535audio_capture_setup_prd,400.read_prd = cs5535audio_capture_read_prd,401.pause_dma = cs5535audio_capture_pause_dma,402.read_dma_pntr = cs5535audio_capture_read_dma_pntr,403};404405int snd_cs5535audio_pcm(struct cs5535audio *cs5535au)406{407struct snd_pcm *pcm;408int err;409410err = snd_pcm_new(cs5535au->card, "CS5535 Audio", 0, 1, 1, &pcm);411if (err < 0)412return err;413414cs5535au->dmas[CS5535AUDIO_DMA_PLAYBACK].ops =415&snd_cs5535audio_playback_dma_ops;416cs5535au->dmas[CS5535AUDIO_DMA_CAPTURE].ops =417&snd_cs5535audio_capture_dma_ops;418snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK,419&snd_cs5535audio_playback_ops);420snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE,421&snd_cs5535audio_capture_ops);422423pcm->private_data = cs5535au;424pcm->info_flags = 0;425strscpy(pcm->name, "CS5535 Audio");426427snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV,428&cs5535au->pci->dev,42964*1024, 128*1024);430cs5535au->pcm = pcm;431432return 0;433}434435436437