Path: blob/master/sound/pci/cs5535audio/cs5535audio_pcm.c
29266 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;152guard(spinlock_irq)(&cs5535au->reg_lock);153dma->ops->disable_dma(cs5535au);154dma->ops->setup_prd(cs5535au, jmpprd_addr);155return 0;156}157158static void cs5535audio_playback_enable_dma(struct cs5535audio *cs5535au)159{160cs_writeb(cs5535au, ACC_BM0_CMD, BM_CTL_EN);161}162163static void cs5535audio_playback_disable_dma(struct cs5535audio *cs5535au)164{165cs_writeb(cs5535au, ACC_BM0_CMD, 0);166}167168static void cs5535audio_playback_pause_dma(struct cs5535audio *cs5535au)169{170cs_writeb(cs5535au, ACC_BM0_CMD, BM_CTL_PAUSE);171}172173static void cs5535audio_playback_setup_prd(struct cs5535audio *cs5535au,174u32 prd_addr)175{176cs_writel(cs5535au, ACC_BM0_PRD, prd_addr);177}178179static u32 cs5535audio_playback_read_prd(struct cs5535audio *cs5535au)180{181return cs_readl(cs5535au, ACC_BM0_PRD);182}183184static u32 cs5535audio_playback_read_dma_pntr(struct cs5535audio *cs5535au)185{186return cs_readl(cs5535au, ACC_BM0_PNTR);187}188189static void cs5535audio_capture_enable_dma(struct cs5535audio *cs5535au)190{191cs_writeb(cs5535au, ACC_BM1_CMD, BM_CTL_EN);192}193194static void cs5535audio_capture_disable_dma(struct cs5535audio *cs5535au)195{196cs_writeb(cs5535au, ACC_BM1_CMD, 0);197}198199static void cs5535audio_capture_pause_dma(struct cs5535audio *cs5535au)200{201cs_writeb(cs5535au, ACC_BM1_CMD, BM_CTL_PAUSE);202}203204static void cs5535audio_capture_setup_prd(struct cs5535audio *cs5535au,205u32 prd_addr)206{207cs_writel(cs5535au, ACC_BM1_PRD, prd_addr);208}209210static u32 cs5535audio_capture_read_prd(struct cs5535audio *cs5535au)211{212return cs_readl(cs5535au, ACC_BM1_PRD);213}214215static u32 cs5535audio_capture_read_dma_pntr(struct cs5535audio *cs5535au)216{217return cs_readl(cs5535au, ACC_BM1_PNTR);218}219220static void cs5535audio_clear_dma_packets(struct cs5535audio *cs5535au,221struct cs5535audio_dma *dma,222struct snd_pcm_substream *substream)223{224snd_dma_free_pages(&dma->desc_buf);225dma->desc_buf.area = NULL;226dma->substream = NULL;227}228229static int snd_cs5535audio_hw_params(struct snd_pcm_substream *substream,230struct snd_pcm_hw_params *hw_params)231{232struct cs5535audio *cs5535au = snd_pcm_substream_chip(substream);233struct cs5535audio_dma *dma = substream->runtime->private_data;234int err;235236dma->buf_addr = substream->runtime->dma_addr;237dma->buf_bytes = params_buffer_bytes(hw_params);238239err = cs5535audio_build_dma_packets(cs5535au, dma, substream,240params_periods(hw_params),241params_period_bytes(hw_params));242if (!err)243dma->pcm_open_flag = 1;244245return err;246}247248static int snd_cs5535audio_hw_free(struct snd_pcm_substream *substream)249{250struct cs5535audio *cs5535au = snd_pcm_substream_chip(substream);251struct cs5535audio_dma *dma = substream->runtime->private_data;252253if (dma->pcm_open_flag) {254if (substream == cs5535au->playback_substream)255snd_ac97_update_power(cs5535au->ac97,256AC97_PCM_FRONT_DAC_RATE, 0);257else258snd_ac97_update_power(cs5535au->ac97,259AC97_PCM_LR_ADC_RATE, 0);260dma->pcm_open_flag = 0;261}262cs5535audio_clear_dma_packets(cs5535au, dma, substream);263return 0;264}265266static int snd_cs5535audio_playback_prepare(struct snd_pcm_substream *substream)267{268struct cs5535audio *cs5535au = snd_pcm_substream_chip(substream);269return snd_ac97_set_rate(cs5535au->ac97, AC97_PCM_FRONT_DAC_RATE,270substream->runtime->rate);271}272273static int snd_cs5535audio_trigger(struct snd_pcm_substream *substream, int cmd)274{275struct cs5535audio *cs5535au = snd_pcm_substream_chip(substream);276struct cs5535audio_dma *dma = substream->runtime->private_data;277278guard(spinlock)(&cs5535au->reg_lock);279switch (cmd) {280case SNDRV_PCM_TRIGGER_PAUSE_PUSH:281dma->ops->pause_dma(cs5535au);282break;283case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:284dma->ops->enable_dma(cs5535au);285break;286case SNDRV_PCM_TRIGGER_START:287dma->ops->enable_dma(cs5535au);288break;289case SNDRV_PCM_TRIGGER_RESUME:290dma->ops->enable_dma(cs5535au);291break;292case SNDRV_PCM_TRIGGER_STOP:293dma->ops->disable_dma(cs5535au);294break;295case SNDRV_PCM_TRIGGER_SUSPEND:296dma->ops->disable_dma(cs5535au);297break;298default:299dev_err(cs5535au->card->dev, "unhandled trigger\n");300return -EINVAL;301}302return 0;303}304305static snd_pcm_uframes_t snd_cs5535audio_pcm_pointer(struct snd_pcm_substream306*substream)307{308struct cs5535audio *cs5535au = snd_pcm_substream_chip(substream);309u32 curdma;310struct cs5535audio_dma *dma;311312dma = substream->runtime->private_data;313curdma = dma->ops->read_dma_pntr(cs5535au);314if (curdma < dma->buf_addr) {315dev_err(cs5535au->card->dev, "curdma=%x < %x bufaddr.\n",316curdma, dma->buf_addr);317return 0;318}319curdma -= dma->buf_addr;320if (curdma >= dma->buf_bytes) {321dev_err(cs5535au->card->dev, "diff=%x >= %x buf_bytes.\n",322curdma, dma->buf_bytes);323return 0;324}325return bytes_to_frames(substream->runtime, curdma);326}327328static int snd_cs5535audio_capture_open(struct snd_pcm_substream *substream)329{330int err;331struct cs5535audio *cs5535au = snd_pcm_substream_chip(substream);332struct snd_pcm_runtime *runtime = substream->runtime;333334runtime->hw = snd_cs5535audio_capture;335runtime->hw.rates = cs5535au->ac97->rates[AC97_RATES_ADC];336snd_pcm_limit_hw_rates(runtime);337cs5535au->capture_substream = substream;338runtime->private_data = &(cs5535au->dmas[CS5535AUDIO_DMA_CAPTURE]);339err = snd_pcm_hw_constraint_integer(runtime,340SNDRV_PCM_HW_PARAM_PERIODS);341if (err < 0)342return err;343olpc_capture_open(cs5535au->ac97);344return 0;345}346347static int snd_cs5535audio_capture_close(struct snd_pcm_substream *substream)348{349struct cs5535audio *cs5535au = snd_pcm_substream_chip(substream);350olpc_capture_close(cs5535au->ac97);351return 0;352}353354static int snd_cs5535audio_capture_prepare(struct snd_pcm_substream *substream)355{356struct cs5535audio *cs5535au = snd_pcm_substream_chip(substream);357return snd_ac97_set_rate(cs5535au->ac97, AC97_PCM_LR_ADC_RATE,358substream->runtime->rate);359}360361static const struct snd_pcm_ops snd_cs5535audio_playback_ops = {362.open = snd_cs5535audio_playback_open,363.close = snd_cs5535audio_playback_close,364.hw_params = snd_cs5535audio_hw_params,365.hw_free = snd_cs5535audio_hw_free,366.prepare = snd_cs5535audio_playback_prepare,367.trigger = snd_cs5535audio_trigger,368.pointer = snd_cs5535audio_pcm_pointer,369};370371static const struct snd_pcm_ops snd_cs5535audio_capture_ops = {372.open = snd_cs5535audio_capture_open,373.close = snd_cs5535audio_capture_close,374.hw_params = snd_cs5535audio_hw_params,375.hw_free = snd_cs5535audio_hw_free,376.prepare = snd_cs5535audio_capture_prepare,377.trigger = snd_cs5535audio_trigger,378.pointer = snd_cs5535audio_pcm_pointer,379};380381static const struct cs5535audio_dma_ops snd_cs5535audio_playback_dma_ops = {382.type = CS5535AUDIO_DMA_PLAYBACK,383.enable_dma = cs5535audio_playback_enable_dma,384.disable_dma = cs5535audio_playback_disable_dma,385.setup_prd = cs5535audio_playback_setup_prd,386.read_prd = cs5535audio_playback_read_prd,387.pause_dma = cs5535audio_playback_pause_dma,388.read_dma_pntr = cs5535audio_playback_read_dma_pntr,389};390391static const struct cs5535audio_dma_ops snd_cs5535audio_capture_dma_ops = {392.type = CS5535AUDIO_DMA_CAPTURE,393.enable_dma = cs5535audio_capture_enable_dma,394.disable_dma = cs5535audio_capture_disable_dma,395.setup_prd = cs5535audio_capture_setup_prd,396.read_prd = cs5535audio_capture_read_prd,397.pause_dma = cs5535audio_capture_pause_dma,398.read_dma_pntr = cs5535audio_capture_read_dma_pntr,399};400401int snd_cs5535audio_pcm(struct cs5535audio *cs5535au)402{403struct snd_pcm *pcm;404int err;405406err = snd_pcm_new(cs5535au->card, "CS5535 Audio", 0, 1, 1, &pcm);407if (err < 0)408return err;409410cs5535au->dmas[CS5535AUDIO_DMA_PLAYBACK].ops =411&snd_cs5535audio_playback_dma_ops;412cs5535au->dmas[CS5535AUDIO_DMA_CAPTURE].ops =413&snd_cs5535audio_capture_dma_ops;414snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK,415&snd_cs5535audio_playback_ops);416snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE,417&snd_cs5535audio_capture_ops);418419pcm->private_data = cs5535au;420pcm->info_flags = 0;421strscpy(pcm->name, "CS5535 Audio");422423snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV,424&cs5535au->pci->dev,42564*1024, 128*1024);426cs5535au->pcm = pcm;427428return 0;429}430431432433