From patchwork Mon Feb 13 16:38:29 2017 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Arnaud POULIQUEN X-Patchwork-Id: 9570245 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 C444D6045D for ; Mon, 13 Feb 2017 16:40:15 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id B0FB326E77 for ; Mon, 13 Feb 2017 16:40:15 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id A56B72711E; Mon, 13 Feb 2017 16:40:15 +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,DKIM_SIGNED, DKIM_VALID autolearn=ham version=3.3.1 Received: from bombadil.infradead.org (bombadil.infradead.org [65.50.211.133]) (using TLSv1.2 with cipher AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mail.wl.linuxfoundation.org (Postfix) with ESMTPS id 2090726E77 for ; Mon, 13 Feb 2017 16:40:13 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=lists.infradead.org; s=bombadil.20170209; h=Sender: Content-Transfer-Encoding:Content-Type:Cc:List-Subscribe:List-Help:List-Post: List-Archive:List-Unsubscribe:List-Id:MIME-Version:References:In-Reply-To: Message-ID:Date:Subject:To:From:Reply-To:Content-ID:Content-Description: Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc:Resent-Message-ID: List-Owner; bh=ETg7t/5CwIC4fBGNLeN7TvwrgR6OGPhy/7LS4kHuQJ0=; b=qTLU36Xj60eiDQ ywkvlsdzfb5eHUb7pkj9oBU7HNbJ/1gQNHtDbrHXToTXPvVL35tP3pvIUGRNXyIitDhb8TR7OHeOy XAWTrfsETWJHXmwfZN0bw9GSrGmMMZWYEWvNlO471Ay3vgJkQqaNM3v5I2PvIHOQduJuocZrMsuEW 6Zia9rHjUNlFyvc7ym+AKCgTShoCIVE9p9Bax+6bTjg0SIvcvq+ZT50mXv8jwm8hc/CdwLW+xpl2h w2+Pxjo8pA73MyyFJrMsthGf1GGOXWxOuqjFnAO6VDBiLZ365lEtM2M94FsjMfj8cwgXgz6kA4fvz R5xbvvdMh5AzLVSeupkw==; Received: from localhost ([127.0.0.1] helo=bombadil.infradead.org) by bombadil.infradead.org with esmtp (Exim 4.87 #1 (Red Hat Linux)) id 1cdJfh-000757-5k; Mon, 13 Feb 2017 16:40:13 +0000 Received: from merlin.infradead.org ([2001:4978:20e::2]) by bombadil.infradead.org with esmtps (Exim 4.87 #1 (Red Hat Linux)) id 1cdJfK-0006CH-FO for linux-arm-kernel@bombadil.infradead.org; Mon, 13 Feb 2017 16:39:50 +0000 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=infradead.org; s=merlin.20170209; h=Content-Type:MIME-Version:References: In-Reply-To:Message-ID:Date:Subject:CC:To:From:Sender:Reply-To: Content-Transfer-Encoding:Content-ID:Content-Description:Resent-Date: Resent-From:Resent-Sender:Resent-To:Resent-Cc:Resent-Message-ID:List-Id: List-Help:List-Unsubscribe:List-Subscribe:List-Post:List-Owner:List-Archive; bh=vcm6Ya3RhgvuNiFRiAmflzFltt+C0NxpVwa59sxyomU=; b=Vjj/5V00B0DuACgLwqqYHzna/ ksq/9Ike9jSlYtuZ7SCCvG0aLe9O3x62cRWfCKJ7rM8nDmsTG2ljzAG7GoudbqFaNP/BdXtOa29U8 fZdEo2EvbiInu1lswHyLNk6SToIJShJx+ACwW8wV8T7HkxKpQPwMXw2Q0/UqO7OarlPsX4j8bchsQ IYYunDFX7TYBAQ07ivU1ad7T1QwjNyDx/i6W6PAtKvfx1rbM17+ztMTepowUBzD+ELX4KX23bi20f ONjnwWibfef+XpygRFjrEwof17gUaTvfashDcHXt8p82NVZns4w+T6MYBthudUNdOmJA+HVyCScI8 PSTE0zQKA==; Received: from mx07-00178001.pphosted.com ([62.209.51.94]) by merlin.infradead.org with esmtps (Exim 4.87 #1 (Red Hat Linux)) id 1cdJfH-0004sS-NC for linux-arm-kernel@lists.infradead.org; Mon, 13 Feb 2017 16:39:49 +0000 Received: from pps.filterd (m0046037.ppops.net [127.0.0.1]) by m0046037.ppops.net (8.16.0.11/8.16.0.11) with SMTP id v1DGYRHg016238; Mon, 13 Feb 2017 17:39:15 +0100 Received: from beta.dmz-eu.st.com (beta.dmz-eu.st.com [164.129.1.35]) by mx07-.pphosted.com with ESMTP id 28hs8wbd61-1 (version=TLSv1 cipher=ECDHE-RSA-AES256-SHA bits=256 verify=NOT); Mon, 13 Feb 2017 17:39:15 +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 3680638; Mon, 13 Feb 2017 16:39:14 +0000 (GMT) Received: from Webmail-eu.st.com (Safex1hubcas21.st.com [10.75.90.44]) by zeta.dmz-eu.st.com (STMicroelectronics) with ESMTP id D7ED32994; Mon, 13 Feb 2017 16:39:13 +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, 13 Feb 2017 17:39:13 +0100 From: Arnaud Pouliquen To: Rob Herring , Mark Rutland , Jonathan Cameron , Hartmut Knaack , Lars-Peter Clausen , Peter Meerwald-Stadler , Jaroslav Kysela , Takashi Iwai , Liam Girdwood , Mark Brown Subject: [RFC v2 7/7] IIO: ADC: add stm32 DFSDM support Date: Mon, 13 Feb 2017 17:38:29 +0100 Message-ID: <1487003909-11710-8-git-send-email-arnaud.pouliquen@st.com> X-Mailer: git-send-email 1.9.1 In-Reply-To: <1487003909-11710-1-git-send-email-arnaud.pouliquen@st.com> References: <1487003909-11710-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-02-13_08:, , signatures=0 X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20170213_113947_965513_9EF62C03 X-CRM114-Status: GOOD ( 32.09 ) X-BeenThere: linux-arm-kernel@lists.infradead.org X-Mailman-Version: 2.1.21 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: devicetree@vger.kernel.org, alsa-devel@alsa-project.org, olivier moysan , kernel@stlinux.com, linux-iio@vger.kernel.org, arnaud.pouliquen@st.com, Maxime Coquelin , linux-arm-kernel@lists.infradead.org, Alexandre Torgue Sender: "linux-arm-kernel" Errors-To: linux-arm-kernel-bounces+patchwork-linux-arm=patchwork.kernel.org@lists.infradead.org X-Virus-Scanned: ClamAV using ClamSMTP Add driver for stm32 DFSDM IP. This IP converts a sigma delta stream in n bit samples through a low pass filter and an integrator. Signed-off-by: Arnaud Pouliquen --- drivers/iio/adc/Kconfig | 13 + drivers/iio/adc/Makefile | 1 + drivers/iio/adc/stm32-dfsdm-adc.c | 483 +++++++++++++++++++++++++++++++++++++ drivers/iio/adc/stm32-dfsdm-core.c | 273 +++++++++++++++++++++ drivers/iio/adc/stm32-dfsdm.h | 141 +++++++++++ 5 files changed, 911 insertions(+) create mode 100644 drivers/iio/adc/stm32-dfsdm-adc.c create mode 100644 drivers/iio/adc/stm32-dfsdm-core.c create mode 100644 drivers/iio/adc/stm32-dfsdm.h diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig index d4366ac..ab917b6 100644 --- a/drivers/iio/adc/Kconfig +++ b/drivers/iio/adc/Kconfig @@ -452,6 +452,19 @@ config STM32_ADC This driver can also be built as a module. If so, the module will be called stm32-adc. +config STM32_DFSDM_ADC + tristate "STMicroelectronics STM32 dfsdm adc" + depends on (ARCH_STM32 && OF) || COMPILE_TEST + select REGMAP + select REGMAP_MMIO + select IIO_HW_CONSUMER + help + Select this option to enable the driver for STMicroelectronics + STM32 digital filter for sigma delta converter (ADC). + + This driver can also be built as a module. If so, the module + will be called stm32-adc-dfsdm-adc. + config STX104 tristate "Apex Embedded Systems STX104 driver" depends on X86 && ISA_BUS_API diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile index bd67144..5bcad23 100644 --- a/drivers/iio/adc/Makefile +++ b/drivers/iio/adc/Makefile @@ -43,6 +43,7 @@ obj-$(CONFIG_ROCKCHIP_SARADC) += rockchip_saradc.o obj-$(CONFIG_STX104) += stx104.o obj-$(CONFIG_STM32_ADC_CORE) += stm32-adc-core.o obj-$(CONFIG_STM32_ADC) += stm32-adc.o +obj-$(CONFIG_STM32_DFSDM_ADC) += stm32-dfsdm-adc.o stm32-dfsdm-core.o obj-$(CONFIG_TI_ADC081C) += ti-adc081c.o obj-$(CONFIG_TI_ADC0832) += ti-adc0832.o obj-$(CONFIG_TI_ADC12138) += ti-adc12138.o diff --git a/drivers/iio/adc/stm32-dfsdm-adc.c b/drivers/iio/adc/stm32-dfsdm-adc.c new file mode 100644 index 0000000..8f9c3263 --- /dev/null +++ b/drivers/iio/adc/stm32-dfsdm-adc.c @@ -0,0 +1,483 @@ +/* + * This file is part of STM32 DFSDM ADC driver + * + * Copyright (C) 2016, STMicroelectronics - All Rights Reserved + * Author: Arnaud Pouliquen . + * + * 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 + +#include +#include +#include + +#include + +#include "stm32-dfsdm.h" + +enum stm32_dfsdm_mode { + DFSDM_ADC, /* ADC mode, access through IIO ABI */ + DFSDM_AUDIO /* Audio mode, access through ASoC ABI */ +}; + +struct stm32_dfsdm_adc { + struct stm32_dfsdm *common; + + unsigned int fl_id; + unsigned int oversamp; + unsigned int clk_freq; + + enum stm32_dfsdm_mode mode; + struct platform_device *audio_pdev; + + void (*overrun_cb)(void *context); + void *cb_context; + + /* Hardware consumer structure for Front End iio */ + struct iio_hw_consumer *hwc; +}; + +static const enum stm32_dfsdm_mode stm32_dfsdm_data_adc = DFSDM_ADC; +static const enum stm32_dfsdm_mode stm32_dfsdm_data_audio = DFSDM_AUDIO; + +struct stm32_dfsdm_adc_devdata { + enum stm32_dfsdm_mode mode; + const struct iio_info *info; +}; + +static int stm32_dfsdm_set_osrs(struct stm32_dfsdm_adc *adc, bool fast, + unsigned int oversamp) +{ + /* + * TODO + * This function tries to compute filter oversampling and integrator + * oversampling, base on oversampling ratio requested by user. + */ + + return 0; +}; + +static int stm32_dfsdm_single_conv(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, int *res) +{ + /* TODO: Perform conversion instead of sending fake value */ + dev_dbg(&indio_dev->dev, "%s\n", __func__); + + *res = chan->channel + 0xFFFF00; + return 0; +} + +static int stm32_dfsdm_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct stm32_dfsdm_adc *adc = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + ret = stm32_dfsdm_set_osrs(adc, 0, val); + if (!ret) + adc->oversamp = val; + break; + case IIO_CHAN_INFO_SAMP_FREQ: + if (adc->mode == DFSDM_AUDIO) + ret = stm32_dfsdm_set_osrs(adc, 0, val); + else + ret = -EINVAL; + break; + + default: + ret = -EINVAL; + } + + return ret; +} + +static int stm32_dfsdm_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + struct stm32_dfsdm_adc *adc = iio_priv(indio_dev); + int ret; + + dev_dbg(&indio_dev->dev, "%s\n", __func__); + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (adc->hwc) { + ret = iio_hw_consumer_enable(adc->hwc); + if (ret < 0) { + dev_err(&indio_dev->dev, + "%s: iio enable failed (channel %d)\n", + __func__, chan->channel); + return ret; + } + } + ret = stm32_dfsdm_single_conv(indio_dev, chan, val); + if (ret < 0) { + dev_err(&indio_dev->dev, + "%s: conversion failed (channel %d)\n", + __func__, chan->channel); + return ret; + } + + if (adc->hwc) + iio_hw_consumer_disable(adc->hwc); + + return IIO_VAL_INT; + + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + *val = adc->oversamp; + + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SAMP_FREQ: + *val = DIV_ROUND_CLOSEST(adc->clk_freq, adc->oversamp); + + return IIO_VAL_INT; + } + + return -EINVAL; +} + +static const struct iio_info stm32_dfsdm_info_adc = { + .read_raw = stm32_dfsdm_read_raw, + .write_raw = stm32_dfsdm_write_raw, + .driver_module = THIS_MODULE, +}; + +static const struct iio_info stm32_dfsdm_info_audio = { + .read_raw = stm32_dfsdm_read_raw, + .write_raw = stm32_dfsdm_write_raw, + .driver_module = THIS_MODULE, +}; + +const struct stm32_dfsdm_adc_devdata stm32_dfsdm_devdata_adc = { + .mode = DFSDM_ADC, + .info = &stm32_dfsdm_info_adc, +}; + +const struct stm32_dfsdm_adc_devdata stm32_dfsdm_devdata_audio = { + .mode = DFSDM_AUDIO, + .info = &stm32_dfsdm_info_audio, +}; + +static irqreturn_t stm32_dfsdm_irq(int irq, void *arg) +{ + /* TODO */ + return IRQ_HANDLED; +} + +static void stm32_dfsdm_set_sysclk(struct stm32_dfsdm_adc *adc, + unsigned int freq) +{ + struct iio_dev *iio = iio_priv_to_dev(adc); + + dev_dbg(&iio->dev, "%s:\n", __func__); + + adc->clk_freq = freq; +}; + + /* Set expected audio sampling rate */ +static int stm32_dfsdm_set_hwparam(struct stm32_dfsdm_adc *adc, + struct stm32_dfsdm_hw_param *params) +{ + struct iio_dev *iio = iio_priv_to_dev(adc); + + dev_dbg(&iio->dev, "%s for rate %d\n", __func__, params->rate); + + return stm32_dfsdm_set_osrs(adc, 0, params->rate); +}; + + /* Called when ASoC starts an audio stream setup. */ +static int stm32_dfsdm_audio_startup(struct stm32_dfsdm_adc *adc) +{ + struct iio_dev *iio = iio_priv_to_dev(adc); + + dev_dbg(&iio->dev, "%s\n", __func__); + + return 0; +}; + + /* Shuts down the audio stream. */ +static void stm32_dfsdm_audio_shutdown(struct stm32_dfsdm_adc *adc) +{ + struct iio_dev *iio = iio_priv_to_dev(adc); + + dev_dbg(&iio->dev, "%s\n", __func__); +}; + + /* + * Provides DMA source physicla addr to allow ALsa to handle DMA + * transfers. + */ +static dma_addr_t stm32_dfsdm_get_dma_source(struct stm32_dfsdm_adc *adc) +{ + struct iio_dev *iio = iio_priv_to_dev(adc); + + dev_dbg(&iio->dev, "%s\n", __func__); + + return (dma_addr_t)(adc->common->phys_base + DFSDM_RDATAR(adc->fl_id)); +}; + +/* Register callback to treat underrun and overrun issues */ +static void stm32_dfsdm_register_xrun_cb(struct stm32_dfsdm_adc *adc, + void (*overrun_cb)(void *context), + void *context) +{ + struct iio_dev *iio = iio_priv_to_dev(adc); + + dev_dbg(&iio->dev, "%s\n", __func__); + adc->overrun_cb = overrun_cb; + adc->cb_context = context; +}; + +const struct stm32_adfsdm_codec_ops stm32_dfsdm_audio_ops = { + .set_sysclk = stm32_dfsdm_set_sysclk, + .set_hwparam = stm32_dfsdm_set_hwparam, + .audio_startup = stm32_dfsdm_audio_startup, + .audio_shutdown = stm32_dfsdm_audio_shutdown, + .register_xrun_cb = stm32_dfsdm_register_xrun_cb, + .get_dma_source = stm32_dfsdm_get_dma_source +}; + +static int stm32_dfsdm_adc_chan_init_one(struct iio_dev *indio_dev, + struct iio_chan_spec *chan, + int chan_idx) +{ + struct iio_chan_spec *ch = &chan[chan_idx]; + struct stm32_dfsdm_adc *adc = iio_priv(indio_dev); + int ret; + + dev_dbg(&indio_dev->dev, "%s:\n", __func__); + ret = of_property_read_u32_index(indio_dev->dev.of_node, + "st,adc-channels", chan_idx, + &ch->channel); + if (ret < 0) { + dev_err(&indio_dev->dev, + " error parsing 'st,adc-channels' for idx %d\n", + chan_idx); + return ret; + } + + ret = of_property_read_string_index(indio_dev->dev.of_node, + "st,adc-channel-names", chan_idx, + &ch->datasheet_name); + if (ret < 0) { + dev_err(&indio_dev->dev, + " error parsing 'st,adc-channel-names' for idx %d\n", + chan_idx); + return ret; + } + + ch->type = IIO_VOLTAGE; + ch->indexed = 1; + ch->scan_index = chan_idx; + if (adc->mode == DFSDM_ADC) { + /* + * IIO_CHAN_INFO_RAW: used to compute regular conversion + * IIO_CHAN_INFO_SAMP_FREQ: used to indicate sampling frequency + * IIO_CHAN_INFO_OVERSAMPLING_RATIO: used set oversampling + */ + ch->info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SAMP_FREQ) | + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO); + } + + ch->scan_type.sign = 'u'; + ch->scan_type.realbits = 24; + ch->scan_type.storagebits = 32; + + return 0; +} + +static int stm32_dfsdm_adc_chan_init(struct iio_dev *indio_dev) +{ + struct iio_chan_spec *channels; + struct stm32_dfsdm_adc *adc = iio_priv(indio_dev); + unsigned int num_ch; + int ret, chan_idx; + + num_ch = of_property_count_u32_elems(indio_dev->dev.of_node, + "st,adc-channels"); + if (num_ch < 0 || num_ch >= adc->common->num_chs) { + dev_err(&indio_dev->dev, "Bad st,adc-channels?\n"); + return num_ch < 0 ? num_ch : -EINVAL; + } + + channels = devm_kcalloc(&indio_dev->dev, num_ch, sizeof(*channels), + GFP_KERNEL); + if (!channels) + return -ENOMEM; + + if (adc->mode == DFSDM_ADC) { + /* + * Bind to sd modulator iio device for ADC only. + * For Audio the PDM microphone will be handled by ASoC + */ + adc->hwc = iio_hw_consumer_alloc(&indio_dev->dev); + if (IS_ERR(adc->hwc)) { + dev_err(&indio_dev->dev, "no backend found\n"); + return PTR_ERR(adc->hwc); + } + } + + for (chan_idx = 0; chan_idx < num_ch; chan_idx++) { + ret = stm32_dfsdm_adc_chan_init_one(indio_dev, channels, + chan_idx); + if (ret < 0) + goto free_hwc; + } + + indio_dev->num_channels = num_ch; + indio_dev->channels = channels; + + return 0; + +free_hwc: + if (adc->hwc) + iio_hw_consumer_free(adc->hwc); + return ret; +} + +static const struct of_device_id stm32_dfsdm_adc_match[] = { + { .compatible = "st,stm32-dfsdm-adc", + .data = &stm32_dfsdm_devdata_adc}, + { .compatible = "st,stm32-dfsdm-pdm", + .data = &stm32_dfsdm_devdata_audio}, + {} +}; + +static int stm32_dfsdm_adc_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct stm32_dfsdm_adc *adc; + const struct of_device_id *of_id; + struct device_node *np = dev->of_node; + const struct stm32_dfsdm_adc_devdata *devdata; + struct iio_dev *iio; + int ret, irq; + + dev_dbg(dev, "%s:\n", __func__); + + iio = devm_iio_device_alloc(dev, sizeof(*adc)); + if (IS_ERR(iio)) { + dev_err(dev, "%s: failed to allocate iio", __func__); + return PTR_ERR(iio); + } + + adc = iio_priv(iio); + if (IS_ERR(adc)) { + dev_err(dev, "%s: failed to allocate adc", __func__); + return PTR_ERR(adc); + } + adc->common = dev_get_drvdata(dev->parent); + + /* Populate data structure depending on compatibility */ + of_id = of_match_node(stm32_dfsdm_adc_match, np); + if (!of_id->data) { + dev_err(&pdev->dev, "Data associated to device is missing\n"); + return -EINVAL; + } + + devdata = (const struct stm32_dfsdm_adc_devdata *)of_id->data; + adc->mode = devdata->mode; + + iio->name = np->name; + iio->dev.parent = dev; + iio->dev.of_node = np; + iio->info = devdata->info; + iio->modes = INDIO_DIRECT_MODE; + + platform_set_drvdata(pdev, adc); + + ret = of_property_read_u32(dev->of_node, "reg", &adc->fl_id); + if (ret != 0) { + dev_err(dev, "missing reg property\n"); + return -EINVAL; + } + + /* + * In a first step IRQs generated for channels are not treated. + * So IRQ associated to filter instance 0 is dedicated to the Filter 0. + * In a second step IRQ domain should be used for filter 0 when feature + * like Watchdog, clock absence detection,... will be integrated. + */ + irq = platform_get_irq(pdev, 0); + ret = devm_request_irq(dev, irq, stm32_dfsdm_irq, + 0, pdev->name, adc); + if (ret < 0) { + dev_err(dev, "failed to request IRQ\n"); + return ret; + } + + ret = stm32_dfsdm_adc_chan_init(iio); + if (ret < 0) + return ret; + + ret = iio_device_register(iio); + if (ret) { + dev_err(dev, "failed to register iio device\n"); + return ret; + } + + if (adc->mode == DFSDM_AUDIO) { + struct stm32_adfsdm_pdata dai_data = { + .ops = &stm32_dfsdm_audio_ops, + .adc = adc, + }; + + adc->audio_pdev = platform_device_register_data( + dev, STM32_ADFSDM_DRV_NAME, + PLATFORM_DEVID_AUTO, + &dai_data, sizeof(dai_data)); + + if (IS_ERR(adc->audio_pdev)) + return PTR_ERR(adc->audio_pdev); + } + + return 0; +} + +static int stm32_dfsdm_adc_remove(struct platform_device *pdev) +{ + struct stm32_dfsdm_adc *adc = platform_get_drvdata(pdev); + struct iio_dev *iio = iio_priv_to_dev(adc); + + iio_device_unregister(iio); + + return 0; +} + +static struct platform_driver stm32_dfsdm_adc_driver = { + .driver = { + .name = "stm32-dfsdm-adc", + .of_match_table = stm32_dfsdm_adc_match, + }, + .probe = stm32_dfsdm_adc_probe, + .remove = stm32_dfsdm_adc_remove, +}; +module_platform_driver(stm32_dfsdm_adc_driver); + +MODULE_DESCRIPTION("STM32 sigma delta ADC"); +MODULE_AUTHOR("Arnaud Pouliquen "); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/adc/stm32-dfsdm-core.c b/drivers/iio/adc/stm32-dfsdm-core.c new file mode 100644 index 0000000..195245d --- /dev/null +++ b/drivers/iio/adc/stm32-dfsdm-core.c @@ -0,0 +1,273 @@ +/* + * This file is part of STM32 DFSDM mfd driver + * + * Copyright (C) 2017, STMicroelectronics - All Rights Reserved + * Author(s): Arnaud Pouliquen for STMicroelectronics. + * + * License terms: GPL V2.0. + * + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "stm32-dfsdm.h" + +struct stm32_dfsdm_dev_data { + unsigned int num_filters; + unsigned int num_channels; + const struct regmap_config *regmap_cfg; +}; + +#define STM32H7_DFSDM_NUM_FILTERS 4 +#define STM32H7_DFSDM_NUM_CHANNELS 8 + +static bool stm32_dfsdm_volatile_reg(struct device *dev, unsigned int reg) +{ + if (reg < DFSDM_FILTER_BASE_ADR) + return false; + + /* + * Mask is done on register to avoid to list registers of all them + * filter instances. + */ + switch (reg & DFSDM_FILTER_REG_MASK) { + case DFSDM_CR1(0) & DFSDM_FILTER_REG_MASK: + case DFSDM_ISR(0) & DFSDM_FILTER_REG_MASK: + case DFSDM_JDATAR(0) & DFSDM_FILTER_REG_MASK: + case DFSDM_RDATAR(0) & DFSDM_FILTER_REG_MASK: + return true; + } + + return false; +} + +static const struct regmap_config stm32h7_dfsdm_regmap_cfg = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = sizeof(u32), + .max_register = 0x2B8, + .volatile_reg = stm32_dfsdm_volatile_reg, + .fast_io = true, +}; + +static const struct stm32_dfsdm_dev_data stm32h7_dfsdm_data = { + .num_filters = STM32H7_DFSDM_NUM_FILTERS, + .num_channels = STM32H7_DFSDM_NUM_CHANNELS, + .regmap_cfg = &stm32h7_dfsdm_regmap_cfg, +}; + +/** + * struct dfsdm_priv - stm32 dfsdm private data + * @pdev: platform device + * @stm32_dfsdm: common data exported for all instances + * @regmap: register map of the device; + * @clkout_div: SPI clkout divider value. + * @n_active_ch: atomic active channel counter. + */ +struct dfsdm_priv { + struct platform_device *pdev; + + struct stm32_dfsdm dfsdm; + struct regmap *regmap; + + unsigned int clkout_div; + atomic_t n_active_ch; +}; + +/** + * stm32_dfsdm_start_dfsdm - start global dfsdm IP interface. + * + * Enable interface if n_active_ch is not null. + * @dfsdm: Handle used to retrieve dfsdm context. + */ +int stm32_dfsdm_start_dfsdm(struct stm32_dfsdm *dfsdm) +{ + struct dfsdm_priv *priv = container_of(dfsdm, struct dfsdm_priv, dfsdm); + int ret; + int div = priv->clkout_div; + + if (atomic_inc_return(&priv->n_active_ch) == 1) { + /* TODO: enable clocks */ + + /* Output the SPI CLKOUT (if clkout_div == 0 clok if OFF) */ + ret = regmap_update_bits(priv->regmap, DFSDM_CHCFGR1(0), + DFSDM_CHCFGR1_CKOUTDIV_MASK, + DFSDM_CHCFGR1_CKOUTDIV(div)); + if (ret < 0) + return ret; + + /* Global enable of DFSDM interface */ + ret = regmap_update_bits(priv->regmap, DFSDM_CHCFGR1(0), + DFSDM_CHCFGR1_DFSDMEN_MASK, + DFSDM_CHCFGR1_DFSDMEN(1)); + if (ret < 0) + return ret; + } + + dev_dbg(&priv->pdev->dev, "%s: n_active_ch %d\n", __func__, + atomic_read(&priv->n_active_ch)); + + return 0; +} + +/** + * stm32_dfsdm_stop_dfsdm - stop global DFSDM IP interface. + * + * Disable interface if n_active_ch is null + * @dfsdm: Handle used to retrieve dfsdm context. + */ +int stm32_dfsdm_stop_dfsdm(struct stm32_dfsdm *dfsdm) +{ + struct dfsdm_priv *priv = container_of(dfsdm, struct dfsdm_priv, dfsdm); + int ret; + + if (atomic_dec_and_test(&priv->n_active_ch)) { + /* Global disable of DFSDM interface */ + ret = regmap_update_bits(priv->regmap, DFSDM_CHCFGR1(0), + DFSDM_CHCFGR1_DFSDMEN_MASK, + DFSDM_CHCFGR1_DFSDMEN(0)); + if (ret < 0) + return ret; + + /* Stop SPI CLKOUT */ + ret = regmap_update_bits(priv->regmap, DFSDM_CHCFGR1(0), + DFSDM_CHCFGR1_CKOUTDIV_MASK, + DFSDM_CHCFGR1_CKOUTDIV(0)); + if (ret < 0) + return ret; + + /* TODO: disable clocks */ + } + dev_dbg(&priv->pdev->dev, "%s: n_active_ch %d\n", __func__, + atomic_read(&priv->n_active_ch)); + + return 0; +} + +static int stm32_dfsdm_parse_of(struct platform_device *pdev, + struct dfsdm_priv *priv) +{ + struct device_node *node = pdev->dev.of_node; + struct resource *res; + + if (!node) + return -EINVAL; + + /* Get resources */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&pdev->dev, "Failed to get memory resource\n"); + return -ENODEV; + } + priv->dfsdm.phys_base = res->start; + priv->dfsdm.base = devm_ioremap_resource(&pdev->dev, res); + + return 0; +}; + +static const struct of_device_id stm32_dfsdm_of_match[] = { + { + .compatible = "st,stm32h7-dfsdm", + .data = &stm32h7_dfsdm_data, + }, + {} +}; +MODULE_DEVICE_TABLE(of, stm32_dfsdm_of_match); + +static int stm32_dfsdm_remove(struct platform_device *pdev) +{ + of_platform_depopulate(&pdev->dev); + + return 0; +} + +static int stm32_dfsdm_probe(struct platform_device *pdev) +{ + struct dfsdm_priv *priv; + struct device_node *pnode = pdev->dev.of_node; + const struct of_device_id *of_id; + const struct stm32_dfsdm_dev_data *dev_data; + struct stm32_dfsdm *dfsdm; + int ret, i; + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->pdev = pdev; + + /* Populate data structure depending on compatibility */ + of_id = of_match_node(stm32_dfsdm_of_match, pnode); + if (!of_id->data) { + dev_err(&pdev->dev, "Data associated to device is missing\n"); + return -EINVAL; + } + + dev_data = (const struct stm32_dfsdm_dev_data *)of_id->data; + dfsdm = &priv->dfsdm; + dfsdm->fl_list = devm_kzalloc(&pdev->dev, sizeof(*dfsdm->fl_list), + GFP_KERNEL); + if (!dfsdm->fl_list) + return -ENOMEM; + + dfsdm->num_fls = dev_data->num_filters; + dfsdm->ch_list = devm_kzalloc(&pdev->dev, sizeof(*dfsdm->ch_list), + GFP_KERNEL); + if (!dfsdm->ch_list) + return -ENOMEM; + dfsdm->num_chs = dev_data->num_channels; + dev_err(&pdev->dev, "%s: dfsdm->num_ch: %d\n", + __func__, dfsdm->num_chs); + + ret = stm32_dfsdm_parse_of(pdev, priv); + if (ret < 0) + return ret; + + priv->regmap = devm_regmap_init_mmio(&pdev->dev, dfsdm->base, + &stm32h7_dfsdm_regmap_cfg); + if (IS_ERR(priv->regmap)) { + ret = PTR_ERR(priv->regmap); + dev_err(&pdev->dev, "%s: Failed to allocate regmap: %d\n", + __func__, ret); + return ret; + } + + for (i = 0; i < STM32H7_DFSDM_NUM_FILTERS; i++) { + struct stm32_dfsdm_filter *fl = &dfsdm->fl_list[i]; + + fl->id = i; + } + + platform_set_drvdata(pdev, dfsdm); + + return of_platform_populate(pnode, NULL, NULL, &pdev->dev); +} + +static struct platform_driver stm32_dfsdm_driver = { + .probe = stm32_dfsdm_probe, + .remove = stm32_dfsdm_remove, + .driver = { + .name = "stm32-dfsdm", + .of_match_table = stm32_dfsdm_of_match, + }, +}; + +module_platform_driver(stm32_dfsdm_driver); + +MODULE_AUTHOR("Arnaud Pouliquen "); +MODULE_DESCRIPTION("STMicroelectronics STM32 dfsdm driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/adc/stm32-dfsdm.h b/drivers/iio/adc/stm32-dfsdm.h new file mode 100644 index 0000000..38ab15e --- /dev/null +++ b/drivers/iio/adc/stm32-dfsdm.h @@ -0,0 +1,141 @@ +/* + * This file is part of STM32 DFSDM mfd driver + * + * Copyright (C) 2017, STMicroelectronics - All Rights Reserved + * Author(s): Arnaud Pouliquen . + * + * License terms: GPL V2.0. + * + * 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. + */ +#ifndef MDF_STM32_DFSDM__H +#define MDF_STM32_DFSDM__H + +#include + +/* + * STM32 DFSDM - global register map + * ________________________________________________________ + * | Offset | Registers block | + * -------------------------------------------------------- + * | 0x000 | CHANNEL 0 + COMMON CHANNEL FIELDS | + * -------------------------------------------------------- + * | 0x020 | CHANNEL 1 | + * -------------------------------------------------------- + * | ... | ..... | + * -------------------------------------------------------- + * | 0x0E0 | CHANNEL 7 | + * -------------------------------------------------------- + * | 0x100 | FILTER 0 + COMMON FILTER FIELDs | + * -------------------------------------------------------- + * | 0x200 | FILTER 1 | + * -------------------------------------------------------- + * | 0x300 | FILTER 2 | + * -------------------------------------------------------- + * | 0x400 | FILTER 3 | + * -------------------------------------------------------- + */ + +/* + * Channels register definitions + */ +#define DFSDM_CHCFGR1(y) ((y) * 0x20 + 0x00) +#define DFSDM_CHCFGR2(y) ((y) * 0x20 + 0x04) +#define DFSDM_AWSCDR(y) ((y) * 0x20 + 0x08) +#define DFSDM_CHWDATR(y) ((y) * 0x20 + 0x0C) +#define DFSDM_CHDATINR(y) ((y) * 0x20 + 0x10) + +/* CHCFGR1: Channel configuration register 1 */ +#define DFSDM_CHCFGR1_SITP_MASK GENMASK(1, 0) +#define DFSDM_CHCFGR1_SITP(v) FIELD_PREP(DFSDM_CHCFGR1_SITP_MASK, v) +#define DFSDM_CHCFGR1_SPICKSEL_MASK GENMASK(3, 2) +#define DFSDM_CHCFGR1_SPICKSEL(v) FIELD_PREP(DFSDM_CHCFGR1_SPICKSEL_MASK, v) +#define DFSDM_CHCFGR1_SCDEN_MASK BIT(5) +#define DFSDM_CHCFGR1_SCDEN(v) FIELD_PREP(DFSDM_CHCFGR1_SCDEN_MASK, v) +#define DFSDM_CHCFGR1_CKABEN_MASK BIT(6) +#define DFSDM_CHCFGR1_CKABEN(v) FIELD_PREP(DFSDM_CHCFGR1_CKABEN_MASK, v) +#define DFSDM_CHCFGR1_CHEN_MASK BIT(7) +#define DFSDM_CHCFGR1_CHEN(v) FIELD_PREP(DFSDM_CHCFGR1_CHEN_MASK, v) +#define DFSDM_CHCFGR1_CHINSEL_MASK BIT(8) +#define DFSDM_CHCFGR1_CHINSEL(v) FIELD_PREP(DFSDM_CHCFGR1_CHINSEL_MASK, v) +#define DFSDM_CHCFGR1_DATMPX_MASK GENMASK(13, 12) +#define DFSDM_CHCFGR1_DATMPX(v) FIELD_PREP(DFSDM_CHCFGR1_DATMPX_MASK, v) +#define DFSDM_CHCFGR1_DATPACK_MASK GENMASK(15, 14) +#define DFSDM_CHCFGR1_DATPACK(v) FIELD_PREP(DFSDM_CHCFGR1_DATPACK_MASK, v) +#define DFSDM_CHCFGR1_CKOUTDIV_MASK GENMASK(23, 16) +#define DFSDM_CHCFGR1_CKOUTDIV(v) FIELD_PREP(DFSDM_CHCFGR1_CKOUTDIV_MASK, v) +#define DFSDM_CHCFGR1_CKOUTSRC_MASK BIT(30) +#define DFSDM_CHCFGR1_CKOUTSRC(v) FIELD_PREP(DFSDM_CHCFGR1_CKOUTSRC_MASK, v) +#define DFSDM_CHCFGR1_DFSDMEN_MASK BIT(31) +#define DFSDM_CHCFGR1_DFSDMEN(v) FIELD_PREP(DFSDM_CHCFGR1_DFSDMEN_MASK, v) + +/* + * Filters register definitions + */ +#define DFSDM_FILTER_BASE_ADR 0x100 +#define DFSDM_FILTER_REG_MASK 0x7F +#define DFSDM_FILTER_X_BASE_ADR(x) ((x) * 0x80 + DFSDM_FILTER_BASE_ADR) + +#define DFSDM_CR1(x) (DFSDM_FILTER_X_BASE_ADR(x) + 0x00) +#define DFSDM_CR2(x) (DFSDM_FILTER_X_BASE_ADR(x) + 0x04) +#define DFSDM_ISR(x) (DFSDM_FILTER_X_BASE_ADR(x) + 0x08) +#define DFSDM_ICR(x) (DFSDM_FILTER_X_BASE_ADR(x) + 0x0C) +#define DFSDM_JCHGR(x) (DFSDM_FILTER_X_BASE_ADR(x) + 0x10) +#define DFSDM_FCR(x) (DFSDM_FILTER_X_BASE_ADR(x) + 0x14) +#define DFSDM_JDATAR(x) (DFSDM_FILTER_X_BASE_ADR(x) + 0x18) +#define DFSDM_RDATAR(x) (DFSDM_FILTER_X_BASE_ADR(x) + 0x1C) +#define DFSDM_AWHTR(x) (DFSDM_FILTER_X_BASE_ADR(x) + 0x20) +#define DFSDM_AWLTR(x) (DFSDM_FILTER_X_BASE_ADR(x) + 0x24) +#define DFSDM_AWSR(x) (DFSDM_FILTER_X_BASE_ADR(x) + 0x28) +#define DFSDM_AWCFR(x) (DFSDM_FILTER_X_BASE_ADR(x) + 0x2C) +#define DFSDM_EXMAX(x) (DFSDM_FILTER_X_BASE_ADR(x) + 0x30) +#define DFSDM_EXMIN(x) (DFSDM_FILTER_X_BASE_ADR(x) + 0x34) +#define DFSDM_CNVTIMR(x) (DFSDM_FILTER_X_BASE_ADR(x) + 0x38) + +/** + * struct stm32_dfsdm_filter - structure relative to stm32 FDSDM filter + * TODO: complete structure. + * @id: filetr ID, + */ +struct stm32_dfsdm_filter { + unsigned int id; +}; + +/** + * struct stm32_dfsdm_channel - structure relative to stm32 FDSDM channel + * TODO: complete structure. + * @id: filetr ID, + */ +struct stm32_dfsdm_channel { + unsigned int id; +}; + +/** + * struct stm32_dfsdm - stm32 FDSDM driver common data (for all instances) + * @base: control registers base cpu addr + * @phys_base: DFSDM IP register physical address. + * @fl_list: filter resources list + * @num_fl: number of filter resources available + * @ch_list: channel resources list + * @num_chs: number of channel resources available + */ +struct stm32_dfsdm { + void __iomem *base; + phys_addr_t phys_base; + struct stm32_dfsdm_filter *fl_list; + int num_fls; + struct stm32_dfsdm_channel *ch_list; + int num_chs; +}; + +int stm32_dfsdm_start_dfsdm(struct stm32_dfsdm *dfsdm); +int stm32_dfsdm_stop_dfsdm(struct stm32_dfsdm *dfsdm); + +#endif