From patchwork Mon Jan 23 16:32:25 2017 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Arnaud POULIQUEN X-Patchwork-Id: 9533379 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork.web.codeaurora.org (Postfix) with ESMTP id 8EE5160434 for ; Mon, 23 Jan 2017 19:04:28 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 74FFB27F17 for ; Mon, 23 Jan 2017 19:04:28 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id 693042838D; Mon, 23 Jan 2017 19:04:28 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-1.9 required=2.0 tests=BAYES_00, RCVD_IN_DNSWL_NONE autolearn=unavailable version=3.3.1 Received: from alsa0.perex.cz (alsa0.perex.cz [77.48.224.243]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id ADC2127F17 for ; Mon, 23 Jan 2017 19:04:26 +0000 (UTC) Received: by alsa0.perex.cz (Postfix, from userid 1000) id 0F30F2670DA; Mon, 23 Jan 2017 20:04:25 +0100 (CET) Received: from alsa0.perex.cz (localhost [127.0.0.1]) by alsa0.perex.cz (Postfix) with ESMTP id 16E7326709C; Mon, 23 Jan 2017 20:02:05 +0100 (CET) X-Original-To: alsa-devel@alsa-project.org Delivered-To: alsa-devel@alsa-project.org Received: by alsa0.perex.cz (Postfix, from userid 1000) id 7295526669C; Mon, 23 Jan 2017 17:34:13 +0100 (CET) Received: from mx07-00178001.pphosted.com (mx07-00178001.pphosted.com [62.209.51.94]) by alsa0.perex.cz (Postfix) with ESMTP id 6D5CC267092 for ; Mon, 23 Jan 2017 17:33:57 +0100 (CET) Received: from pps.filterd (m0046668.ppops.net [127.0.0.1]) by mx07-00178001.pphosted.com (8.16.0.11/8.16.0.11) with SMTP id v0NGRTNk021084; Mon, 23 Jan 2017 17:33:57 +0100 Received: from beta.dmz-eu.st.com (beta.dmz-eu.st.com [164.129.1.35]) by mx07-.pphosted.com with ESMTP id 283x024ykk-1 (version=TLSv1 cipher=ECDHE-RSA-AES256-SHA bits=256 verify=NOT); Mon, 23 Jan 2017 17:33:57 +0100 Received: from zeta.dmz-eu.st.com (zeta.dmz-eu.st.com [164.129.230.9]) by beta.dmz-eu.st.com (STMicroelectronics) with ESMTP id 4123931; Mon, 23 Jan 2017 16:33:56 +0000 (GMT) Received: from Webmail-eu.st.com (Safex1hubcas23.st.com [10.75.90.46]) by zeta.dmz-eu.st.com (STMicroelectronics) with ESMTP id F411429CA; Mon, 23 Jan 2017 16:33:55 +0000 (GMT) Received: from localhost (10.201.23.162) by webmail-ga.st.com (10.75.90.48) with Microsoft SMTP Server (TLS) id 14.3.319.2; Mon, 23 Jan 2017 17:33:55 +0100 From: Arnaud Pouliquen To: , , , , Lee Jones , Rob Herring , Mark Rutland , Jonathan Cameron , Hartmut Knaack , Lars-Peter Clausen , Peter Meerwald-Stadler , Jaroslav Kysela , Takashi Iwai , Liam Girdwood , Mark Brown Date: Mon, 23 Jan 2017 17:32:25 +0100 Message-ID: <1485189145-29576-8-git-send-email-arnaud.pouliquen@st.com> X-Mailer: git-send-email 1.9.1 In-Reply-To: <1485189145-29576-1-git-send-email-arnaud.pouliquen@st.com> References: <1485189145-29576-1-git-send-email-arnaud.pouliquen@st.com> MIME-Version: 1.0 X-Originating-IP: [10.201.23.162] X-Proofpoint-Virus-Version: vendor=fsecure engine=2.50.10432:, , definitions=2017-01-23_15:, , signatures=0 Cc: arnaud.pouliquen@st.com, Alexandre Torgue , Maxime Coquelin Subject: [alsa-devel] [PATCH 7/7] ASoC: add STM32 DFSDM support X-BeenThere: alsa-devel@alsa-project.org X-Mailman-Version: 2.1.14 Precedence: list List-Id: "Alsa-devel mailing list for ALSA developers - http://www.alsa-project.org" List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: alsa-devel-bounces@alsa-project.org Sender: alsa-devel-bounces@alsa-project.org X-Virus-Scanned: ClamAV using ClamSMTP Add driver to handle PDM microphones connected to DFSDM IP. Signed-off-by: Arnaud Pouliquen --- sound/soc/Kconfig | 1 + sound/soc/Makefile | 1 + sound/soc/stm/Kconfig | 10 + sound/soc/stm/Makefile | 2 + sound/soc/stm/stm32_adfsdm.c | 686 +++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 700 insertions(+) create mode 100644 sound/soc/stm/Kconfig create mode 100644 sound/soc/stm/Makefile create mode 100644 sound/soc/stm/stm32_adfsdm.c diff --git a/sound/soc/Kconfig b/sound/soc/Kconfig index 182d92e..3836ebe 100644 --- a/sound/soc/Kconfig +++ b/sound/soc/Kconfig @@ -63,6 +63,7 @@ source "sound/soc/sh/Kconfig" source "sound/soc/sirf/Kconfig" source "sound/soc/spear/Kconfig" source "sound/soc/sti/Kconfig" +source "sound/soc/stm/Kconfig" source "sound/soc/sunxi/Kconfig" source "sound/soc/tegra/Kconfig" source "sound/soc/txx9/Kconfig" diff --git a/sound/soc/Makefile b/sound/soc/Makefile index 9a30f21..5440cf7 100644 --- a/sound/soc/Makefile +++ b/sound/soc/Makefile @@ -43,6 +43,7 @@ obj-$(CONFIG_SND_SOC) += sh/ obj-$(CONFIG_SND_SOC) += sirf/ obj-$(CONFIG_SND_SOC) += spear/ obj-$(CONFIG_SND_SOC) += sti/ +obj-$(CONFIG_SND_SOC) += stm/ obj-$(CONFIG_SND_SOC) += sunxi/ obj-$(CONFIG_SND_SOC) += tegra/ obj-$(CONFIG_SND_SOC) += txx9/ diff --git a/sound/soc/stm/Kconfig b/sound/soc/stm/Kconfig new file mode 100644 index 0000000..79aee4e --- /dev/null +++ b/sound/soc/stm/Kconfig @@ -0,0 +1,10 @@ +menuconfig SND_SOC_STM32_DFSDM + tristate "SoC Audio support for STM32 DFSDM" + depends on (ARCH_STM32 && OF && MFD_STM32_DFSDM) || COMPILE_TEST + depends on SND_SOC + select SND_SOC_GENERIC_DMAENGINE_PCM + select SND_SOC_DMIC + help + Select this option to enable the STM32 Digital Filter + for Sigma Delta Modulators (DFSDM) driver used + in various STM32 series for digital microphone capture. \ No newline at end of file diff --git a/sound/soc/stm/Makefile b/sound/soc/stm/Makefile new file mode 100644 index 0000000..ea90240 --- /dev/null +++ b/sound/soc/stm/Makefile @@ -0,0 +1,2 @@ +#DFSDM +obj-$(CONFIG_SND_SOC_STM32_DFSDM) += stm32_adfsdm.o diff --git a/sound/soc/stm/stm32_adfsdm.c b/sound/soc/stm/stm32_adfsdm.c new file mode 100644 index 0000000..9d34bb7 --- /dev/null +++ b/sound/soc/stm/stm32_adfsdm.c @@ -0,0 +1,686 @@ +/* + * This file is part of STM32 DFSDM ASoC DAI driver + * + * Copyright (C) 2017, STMicroelectronics - All Rights Reserved + * Authors: Arnaud Pouliquen + * Olivier Moysan + * + * License type: GPLv2 + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * 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, see . + */ + +#include +#include +#include +#include + +#include + +#include + +/* + * Set data output resolution to 23 bits max to keep 1 extra bit for sign, + * as filter output is symmetric +/-2^(n-1). + */ +#define STM32_ADFSDM_DATA_RES BIT(23) +#define STM32_ADFSDM_MAX_RES BIT(31) +#define STM32_ADFSDM_DATAR_DATA_MASK GENMASK(31, 8) + +struct stm32_adfsdm_data { + unsigned int rate; /* SNDRV_PCM_RATE value */ + unsigned int freq; /* frequency in Hz */ + unsigned int fosr; /* filter over sampling ratio */ + unsigned int iosr; /* integrator over sampling ratio */ + unsigned int fast; /* filter fast mode */ + unsigned long res; /* output data resolution */ + int shift; /* shift on data output */ + bool h_res_found; /* preferred resolution higher than expected */ +}; + +static const struct stm32_adfsdm_data stm32_dfsdm_filter[] = { + { .rate = SNDRV_PCM_RATE_8000, .freq = 8000 }, + { .rate = SNDRV_PCM_RATE_16000, .freq = 16000 }, + { .rate = SNDRV_PCM_RATE_32000, .freq = 32000 }, +}; + +static const unsigned int stm32_dfsdm_sr_val[] = { + 8000, + 16000, + 32000, +}; + +struct stm32_adfsdm_priv { + struct snd_soc_dai_driver dai; + struct snd_dmaengine_dai_dma_data dma_data; + struct snd_pcm_substream *substream; + struct stm32_dfsdm_sinc_filter fl; + struct stm32_dfsdm_channel channel; + struct stm32_dfsdm_ch_cfg ch_cfg; + struct stm32_dfsdm *dfsdm; + struct stm32_adfsdm_data *f_param; + struct device *dev; + struct snd_pcm_hw_constraint_list rates_const; + unsigned long dmic_clk; + unsigned int input_id; + unsigned int fl_id; + unsigned int order; /* filter order */ + int synchro; +}; + +static const struct snd_pcm_hardware stm32_adfsdm_pcm_hw = { + .info = SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID, + .formats = SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE, + + .rate_min = 8000, + .rate_max = 48000, + + .channels_min = 1, + .channels_max = 1, + + .periods_min = 2, + .periods_max = 48, + + .period_bytes_min = 40, /* 8 khz 5 ms */ + .period_bytes_max = 4 * PAGE_SIZE, + .buffer_bytes_max = 16 * PAGE_SIZE +}; + +static inline void stm32_adfsdm_get_param(struct stm32_adfsdm_priv *priv, + unsigned int rate, + struct stm32_adfsdm_data **fparam) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(stm32_dfsdm_filter); i++) { + if (rate == priv->f_param[i].freq) { + *fparam = &priv->f_param[i]; + break; + } + } +} + +static int stm32_adfsdm_compute_shift(struct stm32_adfsdm_priv *priv, + struct stm32_adfsdm_data *param) +{ + int shift = 0; + u32 r = param->res; + + if (!r) { + dev_err(priv->dev, "%s: resolution undefined\n", __func__); + return -EINVAL; + } + + /* + * If filter resolution is higher than data output resolution + * compute right shift required to match data resolution. + * Otherwise compute left shift to align MSB on data resolution. + */ + if (r >= STM32_ADFSDM_DATA_RES) + while ((r >> -shift) >= STM32_ADFSDM_DATA_RES) + shift--; + else + while ((r << shift) < STM32_ADFSDM_DATA_RES) + shift++; + + param->shift = shift; + dev_dbg(priv->dev, "%s: output shift: %d\n", __func__, shift); + + return 0; +} + +static int stm32_adfsdm_get_best_osr(struct stm32_adfsdm_priv *priv, + unsigned int decim, bool fast, + struct stm32_adfsdm_data *param) +{ + unsigned int i, d, fosr, iosr; + u64 res; + s64 delta; + unsigned int m = 1; /* multiplication factor */ + unsigned int p = priv->order; /* filter order (ford) */ + + /* + * Decimation d depends on the filter order and the oversampling ratios. + * ford: filter order + * fosr: filter over sampling ratio + * iosr: integrator over sampling ratio + */ + dev_dbg(priv->dev, "%s: decim = %d fast = %d\n", __func__, decim, fast); + if (priv->order == DFSDM_FASTSINC_ORDER) { + m = 2; + p = 2; + } + + /* + * Looks for filter and integrator oversampling ratios which allow + * to reach 24 bits data output resolution. + * Leave at once if exact resolution if reached. + * Otherwise the higher resolution below 32 bits is kept. + */ + for (fosr = 1; fosr <= DFSDM_MAX_FL_OVERSAMPLING; fosr++) { + for (iosr = 1; iosr <= DFSDM_MAX_INT_OVERSAMPLING; iosr++) { + if (fast) + d = fosr * iosr; + else if (priv->order == DFSDM_FASTSINC_ORDER) + d = fosr * (iosr + 3) + 2; + else + d = fosr * (iosr - 1 + p) + p; + + if (d > decim) + break; + else if (d != decim) + continue; + /* + * Check resolution (limited to signed 32 bits) + * res <= 2^31 + * Sincx filters: + * res = m * fosr^p x iosr (with m=1, p=ford) + * FastSinc filter + * res = m * fosr^p x iosr (with m=2, p=2) + */ + res = fosr; + for (i = p - 1; i > 0; i--) { + res = res * (u64)fosr; + if (res > STM32_ADFSDM_MAX_RES) + break; + } + if (res > STM32_ADFSDM_MAX_RES) + continue; + res = res * (u64)m * (u64)iosr; + if (res > STM32_ADFSDM_MAX_RES) + continue; + + delta = res - STM32_ADFSDM_DATA_RES; + + if (res >= param->res) { + param->res = res; + param->fosr = fosr; + param->iosr = iosr; + param->fast = fast; + } + + if (!delta) + return 0; + } + } + + if (!param->fosr) + return -EINVAL; + + return 0; +} + +static int stm32_adfsdm_get_supported_rates(struct stm32_adfsdm_priv *priv, + unsigned int *rates) +{ + unsigned long fs = priv->dmic_clk; + unsigned int i, decim; + int ret; + + *rates = 0; + + for (i = 0; i < ARRAY_SIZE(stm32_dfsdm_filter); i++) { + /* check that clkout_freq is compatible */ + if ((fs % priv->f_param[i].freq) != 0) + continue; + + decim = fs / priv->f_param[i].freq; + + /* + * Try to find one solution for filter and integrator + * oversampling ratio with fast mode ON or OFF. + * Fast mode on is the preferred solution. + */ + ret = stm32_adfsdm_get_best_osr(priv, decim, 0, + &priv->f_param[i]); + ret &= stm32_adfsdm_get_best_osr(priv, decim, 1, + &priv->f_param[i]); + if (!ret) { + ret = stm32_adfsdm_compute_shift(priv, + &priv->f_param[i]); + if (ret) + continue; + + *rates |= 1 << i; + dev_dbg(priv->dev, "%s: %d rate supported\n", __func__, + priv->f_param[i].freq); + } + } + + if (!*rates) { + dev_err(priv->dev, "%s: no matched rate found\n", __func__); + return -EINVAL; + } + + return 0; +} + +static void stm32_dfsdm_xrun(struct stm32_dfsdm *dfsdm, int flt_id, + enum stm32_dfsdm_events ev, unsigned int param, + void *context) +{ + struct stm32_adfsdm_priv *priv = context; + + snd_pcm_stream_lock(priv->substream); + dev_err(priv->dev, "%s:unexpected underrun\n", __func__); + /* Stop the player */ + stm32_dfsdm_unregister_fl_event(priv->dfsdm, priv->fl_id, + DFSDM_EVENT_REG_XRUN, 0); + snd_pcm_stop(priv->substream, SNDRV_PCM_STATE_XRUN); + snd_pcm_stream_unlock(priv->substream); +} + +static int stm32_adfsdm_copy(struct snd_pcm_substream *substream, int channel, + snd_pcm_uframes_t pos, + void __user *buf, snd_pcm_uframes_t count) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct stm32_adfsdm_priv *priv = snd_soc_dai_get_drvdata(rtd->cpu_dai); + struct stm32_adfsdm_data *f_param; + int *ptr = (int *)(runtime->dma_area + frames_to_bytes(runtime, pos)); + char *hwbuf = runtime->dma_area + frames_to_bytes(runtime, pos); + ssize_t bytes = frames_to_bytes(runtime, count); + ssize_t sample_cnt = bytes_to_samples(runtime, bytes); + + stm32_adfsdm_get_param(priv, runtime->rate, &f_param); + + /* + * Audio samples are available on 24 MSBs of the DFSDM DATAR register. + * We need to mask 8 LSB control bits... + * Additionnaly precision depends on decimation and can need shift + * to be aligned on 32-bit word MSB. + */ + if (f_param->shift > 0) { + do { + *ptr <<= f_param->shift & STM32_ADFSDM_DATAR_DATA_MASK; + ptr++; + } while (--sample_cnt); + } else { + do { + *ptr &= STM32_ADFSDM_DATAR_DATA_MASK; + ptr++; + } while (--sample_cnt); + } + + return copy_to_user(buf, hwbuf, bytes); +} + +static int stm32_adfsdm_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct stm32_adfsdm_priv *priv = snd_soc_dai_get_drvdata(dai); + + priv->substream = substream; + + /* Fix available rate depending on CLKOUT or CKIN value */ + return snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + &priv->rates_const); +} + +static void stm32_adfsdm_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct stm32_adfsdm_priv *priv = snd_soc_dai_get_drvdata(dai); + + priv->substream = NULL; +} + +static int stm32_adfsdm_dai_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_dmaengine_dai_dma_data *dma_data; + + dev_dbg(dai->dev, "%s: enter\n", __func__); + dma_data = snd_soc_dai_get_dma_data(dai, substream); + dma_data->maxburst = 1; + + return 0; +} + +static int stm32_adfsdm_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct stm32_adfsdm_priv *priv = snd_soc_dai_get_drvdata(dai); + struct snd_pcm_runtime *runtime = substream->runtime; + struct stm32_adfsdm_data *f_param; + struct stm32_dfsdm_filter filter; + struct stm32_dfsdm_regular params; + int ret; + + dev_dbg(dai->dev, "%s: enter\n", __func__); + + stm32_adfsdm_get_param(priv, runtime->rate, &f_param); + + memset(&filter, 0, sizeof(filter)); + memset(¶ms, 0, sizeof(params)); + + params.ch_src = priv->channel.id; + params.dma_mode = 1; + params.cont_mode = 1; + params.fast_mode = f_param->fast; + params.sync_mode = priv->synchro ? + DFSDM_FILTER_RSYNC_ON : DFSDM_FILTER_RSYNC_OFF; + filter.reg_params = ¶ms; + filter.sinc_params.order = priv->order; + filter.sinc_params.oversampling = f_param->fosr; + filter.int_oversampling = f_param->iosr; + + filter.event.cb = stm32_dfsdm_xrun; + filter.event.context = priv; + + ret = stm32_dfsdm_configure_filter(priv->dfsdm, priv->fl_id, &filter); + if (ret < 0) + return ret; + + ret = stm32_dfsdm_register_fl_event(priv->dfsdm, priv->fl_id, + DFSDM_EVENT_REG_XRUN, 0); + if (ret < 0) + dev_err(priv->dev, "Failed to register xrun event\n"); + + return ret; +} + +static int stm32_adfsdm_start(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct stm32_adfsdm_priv *priv = snd_soc_dai_get_drvdata(dai); + struct snd_pcm_runtime *runtime = substream->runtime; + struct stm32_adfsdm_data *f_param; + int ret; + + dev_dbg(dai->dev, "%s: enter\n", __func__); + + stm32_adfsdm_get_param(priv, runtime->rate, &f_param); + if (f_param->shift < 0) + priv->ch_cfg.right_bit_shift = -f_param->shift; + + ret = stm32_dfsdm_start_channel(priv->dfsdm, priv->channel.id, + &priv->ch_cfg); + if (ret < 0) + return ret; + + stm32_dfsdm_start_filter(priv->dfsdm, priv->fl_id, + DFSDM_FILTER_REG_CONV); + + return 0; +} + +static void stm32_adfsdm_stop(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct stm32_adfsdm_priv *priv = snd_soc_dai_get_drvdata(dai); + + dev_dbg(dai->dev, "%s: enter\n", __func__); + + stm32_dfsdm_unregister_fl_event(priv->dfsdm, priv->fl_id, + DFSDM_EVENT_REG_XRUN, 0); + stm32_dfsdm_stop_filter(priv->dfsdm, priv->fl_id); + stm32_dfsdm_stop_channel(priv->dfsdm, priv->channel.id); +} + +static int stm32_adfsdm_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + return stm32_adfsdm_start(substream, dai); + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_STOP: + stm32_adfsdm_stop(substream, dai); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int stm32_adfsdm_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct stm32_adfsdm_priv *priv = snd_soc_dai_get_drvdata(dai); + unsigned int inv = fmt & SND_SOC_DAIFMT_INV_MASK; + unsigned int cb = fmt & SND_SOC_DAIFMT_MASTER_MASK; + int ret; + + dev_dbg(dai->dev, "%s: enter\n", __func__); + + /* DAI clock strobing */ + if ((inv == SND_SOC_DAIFMT_IB_NF) || (inv == SND_SOC_DAIFMT_IB_IF)) { + priv->channel.serial_if.type = DFSDM_CHANNEL_SPI_FALLING; + priv->channel.serial_if.pins = DFSDM_CHANNEL_NEXT_CHANNEL_PINS; + /* + * if data on falling egde SPI connected to channel n - 1. + * if data on rising egde SPI connected to channel n. + */ + if (priv->input_id) + priv->channel.id = priv->input_id - 1; + else + priv->channel.id = priv->dfsdm->max_channels - 1; + } else { + priv->channel.serial_if.type = DFSDM_CHANNEL_SPI_RISING; + priv->channel.serial_if.pins = DFSDM_CHANNEL_SAME_CHANNEL_PINS; + priv->channel.id = priv->input_id; + } + + dev_dbg(dai->dev, "%s: channel %d on input %d\n", __func__, + priv->channel.id, priv->input_id); + + if ((cb == SND_SOC_DAIFMT_CBS_CFM) || (cb == SND_SOC_DAIFMT_CBS_CFS)) { + /* Digital microphone is clocked by CLKOUT */ + stm32_dfsdm_get_clk_out_rate(priv->dfsdm, &priv->dmic_clk); + } else { + /* Digital microphone is clocked by external clock */ + if (!priv->dmic_clk) { + dev_err(priv->dev, + "system-clock-frequency not defined\n"); + return -EINVAL; + } + } + + priv->rates_const.count = ARRAY_SIZE(stm32_dfsdm_sr_val); + priv->rates_const.list = stm32_dfsdm_sr_val; + ret = stm32_adfsdm_get_supported_rates(priv, &priv->rates_const.mask); + if (ret < 0) + return ret; + + return stm32_dfsdm_get_channel(priv->dfsdm, &priv->channel); +} + +static int stm32_adfsdm_set_sysclk(struct snd_soc_dai *dai, int clk_id, + unsigned int freq, int dir) +{ + struct stm32_adfsdm_priv *priv = snd_soc_dai_get_drvdata(dai); + + dev_dbg(dai->dev, "%s: enter for dai %d\n", __func__, dai->id); + if (dir == SND_SOC_CLOCK_IN) + priv->dmic_clk = freq; + + return 0; +} + +static const struct snd_soc_dai_ops stm32_adfsdm_dai_ops = { + .startup = stm32_adfsdm_startup, + .shutdown = stm32_adfsdm_shutdown, + .hw_params = stm32_adfsdm_dai_hw_params, + .set_fmt = stm32_adfsdm_set_dai_fmt, + .set_sysclk = stm32_adfsdm_set_sysclk, + .prepare = stm32_adfsdm_prepare, + .trigger = stm32_adfsdm_trigger, +}; + +static int stm32_adfsdm_dai_probe(struct snd_soc_dai *dai) +{ + struct stm32_adfsdm_priv *priv = snd_soc_dai_get_drvdata(dai); + struct snd_dmaengine_dai_dma_data *dma = &priv->dma_data; + int ret; + + dev_dbg(dai->dev, "%s: enter for dai %d\n", __func__, dai->id); + + /* filter settings */ + ret = stm32_dfsdm_get_filter(priv->dfsdm, priv->fl_id); + if (ret < 0) + return -EBUSY; + + /* DMA settings */ + snd_soc_dai_init_dma_data(dai, NULL, dma); + dma->addr = stm32_dfsdm_get_filter_dma_phy_addr(priv->dfsdm, + priv->fl_id, + DFSDM_FILTER_REG_CONV); + dma->addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + + return 0; +} + +static int stm32_adfsdm_dai_remove(struct snd_soc_dai *dai) +{ + struct stm32_adfsdm_priv *priv = snd_soc_dai_get_drvdata(dai); + + dev_dbg(dai->dev, "%s: enter for dai %d\n", __func__, dai->id); + + stm32_dfsdm_release_filter(priv->dfsdm, priv->fl_id); + stm32_dfsdm_release_channel(priv->dfsdm, priv->channel.id); + + return 0; +} + +static const struct snd_soc_dai_driver stm32_adfsdm_dai = { + .capture = { + .channels_min = 1, + .channels_max = 1, + .formats = SNDRV_PCM_FMTBIT_S24_LE | + SNDRV_PCM_FMTBIT_S32_LE, + .rates = SNDRV_PCM_RATE_8000_48000, + }, + .probe = stm32_adfsdm_dai_probe, + .remove = stm32_adfsdm_dai_remove, + .ops = &stm32_adfsdm_dai_ops, +}; + +static const struct snd_soc_component_driver stm32_adfsdm_dai_component = { + .name = "sti_cpu_dai", +}; + +static const struct snd_dmaengine_pcm_config dmaengine_pcm_config = { + .pcm_hardware = &stm32_adfsdm_pcm_hw, + .prepare_slave_config = snd_dmaengine_pcm_prepare_slave_config, + .copy = stm32_adfsdm_copy, +}; + +static int stm32_adfsdm_probe(struct platform_device *pdev) +{ + struct stm32_adfsdm_priv *priv; + struct device_node *np = pdev->dev.of_node; + char *_name; + int ret; + + dev_dbg(&pdev->dev, "%s: enter for node %s\n", __func__, + np->name); + + if (!np) { + dev_err(&pdev->dev, "No DT found\n"); + return -EINVAL; + } + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->dev = &pdev->dev; + priv->dfsdm = dev_get_drvdata(pdev->dev.parent); + + if (of_property_read_u32(np, "reg", &priv->fl_id)) { + dev_err(&pdev->dev, "missing reg property\n"); + return -EINVAL; + } + + ret = of_property_read_u32(np, "st,dai-filter-order", &priv->order); + if (ret < 0) { + dev_warn(&pdev->dev, "Default filter order selected\n"); + priv->order = DFSDM_SINC5_ORDER; + } + + ret = of_property_read_u32(np, "st,input-id", &priv->input_id); + if (ret < 0) { + dev_err(&pdev->dev, "st,input-id property missing\n"); + return ret; + } + + ret = of_property_read_u32(np, "st,dai0-synchronized", &priv->synchro); + if (ret < 0) + /* default case if property not defined */ + priv->synchro = 0; + + priv->channel.type.DataPacking = DFSDM_CHANNEL_STANDARD_MODE; + priv->channel.type.source = DFSDM_CHANNEL_EXTERNAL_INPUTS; + priv->channel.serial_if.spi_clk = DFSDM_CHANNEL_SPI_CLOCK_INTERNAL; + + /* DAI settings */ + _name = devm_kzalloc(&pdev->dev, sizeof("dfsdm_pdm_0"), GFP_KERNEL); + if (!_name) + return -ENOMEM; + + priv->dai = stm32_adfsdm_dai; + + priv->f_param = devm_kcalloc(&pdev->dev, + ARRAY_SIZE(stm32_dfsdm_filter), + sizeof(stm32_dfsdm_filter[0]), GFP_KERNEL); + if (!priv->f_param) + return -ENOMEM; + + memcpy(priv->f_param, stm32_dfsdm_filter, + ARRAY_SIZE(stm32_dfsdm_filter) * sizeof(stm32_dfsdm_filter[0])); + + snprintf(_name, sizeof("dfsdm_pdm_0"), "dfsdm_pdm_%i", priv->fl_id); + priv->dai.name = _name; + priv->dai.capture.stream_name = _name; + + dev_set_drvdata(&pdev->dev, priv); + + ret = devm_snd_soc_register_component(&pdev->dev, + &stm32_adfsdm_dai_component, + &priv->dai, 1); + if (ret < 0) + return ret; + + ret = devm_snd_dmaengine_pcm_register(&pdev->dev, + &dmaengine_pcm_config, 0); + if (ret < 0) + dev_err(&pdev->dev, "failed to register dma pcm config\n"); + + return ret; +} + +static const struct of_device_id snd_soc_dfsdm_match[] = { + {.compatible = "st,stm32-dfsdm-audio"}, + {}, +}; + +static struct platform_driver stm32_adfsdm_driver = { + .driver = { + .name = "stm32-dfsdm-audio", + .of_match_table = snd_soc_dfsdm_match, + }, + .probe = stm32_adfsdm_probe, +}; + +module_platform_driver(stm32_adfsdm_driver); + +MODULE_DESCRIPTION("stm32 DFSDM DAI driver"); +MODULE_AUTHOR("Arnaud Pouliquen "); +MODULE_LICENSE("GPL v2");