[PATCH 2/7] MFD: add STM32 DFSDM support

Jonathan Cameron jic23 at kernel.org
Mon Jan 30 12:44:31 PST 2017


On 30/01/17 15:08, Arnaud Pouliquen wrote:
> Hello Jonathan,
> 
> Thanks for the review. This drivers should disappear,
> but i will integrate you comment/remark in my redesign.
> 
> Please find some comments below, on specific points
> 
> Regards
> Arnaud
> 
> 
> On 01/29/2017 12:53 PM, Jonathan Cameron wrote:
>> On 23/01/17 16:32, Arnaud Pouliquen wrote:
>>> DFSDM hardware IP can be used at the same time for ADC sigma delta
>>> conversion and audio PDM microphone.
>>> MFD driver is in charge of configuring IP registers and managing IP clocks.
>>> For this it exports an API to handles filters and channels resources.
>>>
>>> Signed-off-by: Arnaud Pouliquen <arnaud.pouliquen at st.com>
>> This is somewhat of a beast.  I would be tempted to build it up in more
>> bite sized chunks.
>>
>> Obvious things to drop from a first version (basically to make it easier
>> to review) would be injected supported.  There may well be others but you'll
>> have a better feel for that than me.
> i will pay attention for next version.
>>
>> I really don't like the wrappers round the regmap functions though.
>> They mean you are papering over errors if they occur and make the code
>> slightly harder to read by implying that something else is going on.
>>
> One aim of the wrapper was to simplify code review, seems that just the
> opposite...
> As i have around 50 regmap accesses, adding  a return/goto for each
> error and at least an associated error messages for each function,
> should add 100 to 150 extra lines...
Keep error message to a minimum. Likely to never occur as you say!
> 
>> Yes the code will be longer without them, but you will also be forced to think
>> properly about error paths.
> 
> I have a question around this. What should be the action if a register
> access return an error?
> Possible root causes:
> 	- bad address of the IP (DT)
> 	- IP not clocked or in reset (driver BUG).
> 	- IP is out of order (hardware issue)
> 	- bug in driver that access to an invalid address.
> 
> So except for the last root cause,we can suppose that every register
> accesses should always be OK or KO...
> This is also a reason of the wrapper. Detect driver bug, without adding
> a test on each register access return.
> 
> Perhaps a compromise could be that test is done only during some
> specific phase (probe, after reset deassertion, clock enabling...) and
> then trust access without test?
> Or simply add error message in regmap helper routines...
> 
> Please just tell/confirm me your preference.
Always assume an error can occur anywhere and handle it as best possible (usually
drop straight out of the function with an error).

I agree it doesn't always give that much information, but trying to paper over
a problem and continue is usually a worse idea.
> 
>>
>> Anyhow, was mostly reading this to get a feel for what was going on in the
>> whole series so not really a terribly thorough review I'm afraid. Sorry about
>> that!
> More than enough for this first version :)
> 
>>
>> Jonathan
>>> ---
>>>  drivers/mfd/Kconfig             |   11 +
>>>  drivers/mfd/Makefile            |    2 +
>>>  drivers/mfd/stm32-dfsdm-reg.h   |  220 +++++++++
>>>  drivers/mfd/stm32-dfsdm.c       | 1044 +++++++++++++++++++++++++++++++++++++++
>>>  include/linux/mfd/stm32-dfsdm.h |  324 ++++++++++++
>>>  5 files changed, 1601 insertions(+)
>>>  create mode 100644 drivers/mfd/stm32-dfsdm-reg.h
>>>  create mode 100644 drivers/mfd/stm32-dfsdm.c
>>>  create mode 100644 include/linux/mfd/stm32-dfsdm.h
> 
> 
> [...]
> 
>>> diff --git a/drivers/mfd/stm32-dfsdm.c b/drivers/mfd/stm32-dfsdm.c
>>> new file mode 100644
>>> index 0000000..81ca29c
>>> --- /dev/null
>>> +++ b/drivers/mfd/stm32-dfsdm.c
>>> @@ -0,0 +1,1044 @@
>>> +/*
>>> + * This file is part of STM32 DFSDM mfd driver
>>> + *
>>> + * Copyright (C) 2017, STMicroelectronics - All Rights Reserved
>>> + * Author(s): Arnaud Pouliquen <arnaud.pouliquen at st.com> 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 <linux/clk.h>
>>> +#include <linux/interrupt.h>
>>> +#include <linux/mfd/core.h>
>>> +#include <linux/module.h>
>>> +#include <linux/of_device.h>
>>> +#include <linux/regmap.h>
>>> +#include <linux/slab.h>
>>> +
>>> +#include <linux/mfd/stm32-dfsdm.h>
>>> +
>>> +#include "stm32-dfsdm-reg.h"
>>> +
>>> +#define DFSDM_UPDATE_BITS(regm, reg, mask, val) \
>>> +		WARN_ON(regmap_update_bits(regm, reg, mask, val))
>> Don't do these wrappers please. Handle error correctly in all cases.
>> Reviewing as I tend to do backwards through the driver, I thought these
>> were doing something interesting.
>>
>> Effectively you have no error handling as a result of these which needs
>> fixing.
>>
>>> +
>>> +#define DFSDM_REG_READ(regm, reg, val) \
>>> +		WARN_ON(regmap_read(regm, reg, val))
>>> +
>>> +#define DFSDM_REG_WRITE(regm, reg, val) \
>>> +		WARN_ON(regmap_write(regm, reg, val))
>>> +
>>> +#define STM32H7_DFSDM_NUM_FILTERS	4
>>> +#define STM32H7_DFSDM_NUM_INPUTS	8
>>> +
>>> +enum dfsdm_clkout_src {
>>> +	DFSDM_CLK,
>>> +	AUDIO_CLK
>>> +};
>>> +
>>> +struct stm32_dev_data {
>>> +	const struct stm32_dfsdm dfsdm;
>>> +	const struct regmap_config *regmap_cfg;
>>> +};
>>> +
>>> +struct dfsdm_priv;
>>> +
>>> +struct filter_params {
>>> +	unsigned int id;
>>> +	int irq;
>>> +	struct stm32_dfsdm_fl_event event;
>>> +	u32 event_mask;
>>> +	struct dfsdm_priv *priv; /* Cross ref for context */
>>> +	unsigned int ext_ch_mask;
>>> +	unsigned int scan_ch;
>>> +};
>>> +
>>> +struct ch_params {
>>> +	struct stm32_dfsdm_channel ch;
>>> +};
>>> +
>> I'd like to see a lot more comments in here.  Perhaps full kernel-doc
>> as some elements are not that obvious at least to a fairly casual read.
>>
> Description in device-tree tree bindings and cover-letter is not
> sufficient? you would a doc in Document/arm/stm32?
Sorry, just meant on the following structure.  Internal comments would
make it easier to follow what the elements of this structure are for.
> 
>>> +struct dfsdm_priv {
>>> +	struct platform_device *pdev;
>>> +	struct stm32_dfsdm dfsdm;
>>> +
>>> +	spinlock_t lock; /* Used for resource sharing & interrupt lock */
>>> +
>>> +	/* Filters */
>>> +	struct filter_params *filters;
>>> +	unsigned int free_filter_mask;
>>> +	unsigned int scd_filter_mask;
>>> +	unsigned int ckab_filter_mask;
>>> +
>>> +	/* Channels */
>>> +	struct stm32_dfsdm_channel *channels;
>>> +	unsigned int free_channel_mask;
>>> +	atomic_t n_active_ch;
>>> +
>>> +	/* Clock */
>>> +	struct clk *clk;
>>> +	struct clk *aclk;
>>> +	unsigned int clkout_div;
>>> +	unsigned int clkout_freq_req;
>>> +
>>> +	/* Registers*/
>>> +	void __iomem *base;
>>> +	struct regmap *regmap;
>>> +	phys_addr_t phys_base;
>>> +};
>>> +
>>> +/*
>>> + * Common
>>> + */
>>> +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 = DFSDM_CNVTIMR(STM32H7_DFSDM_NUM_FILTERS - 1),
>>> +	.volatile_reg = stm32_dfsdm_volatile_reg,
>>> +	.fast_io = true,
>>> +};
>>> +
>>> +static const struct stm32_dev_data stm32h7_data = {
>>> +	.dfsdm.max_channels = STM32H7_DFSDM_NUM_INPUTS,
>>> +	.dfsdm.max_filters = STM32H7_DFSDM_NUM_FILTERS,
>>> +	.regmap_cfg = &stm32h7_dfsdm_regmap_cfg,
>>> +};
>>> +
> 
> [...]
> 
>>> +
>>> +/**
>>> + * stm32_dfsdm_get_filter_dma_addr - Get register address for dma transfer.
>>> + *
>>> + * @dfsdm: Handle used to retrieve dfsdm context.
>>> + * @fl_id: Filter id.
>>> + * @conv: Conversion type.
>>> + */
>>> +dma_addr_t stm32_dfsdm_get_filter_dma_phy_addr(struct stm32_dfsdm *dfsdm,
>>> +					       unsigned int fl_id,
>>> +					       enum stm32_dfsdm_conv_type conv)
>>> +{
>>> +	struct dfsdm_priv *priv = container_of(dfsdm, struct dfsdm_priv, dfsdm);
>>> +
>>> +	if (conv == DFSDM_FILTER_REG_CONV)
>>> +		return (dma_addr_t)(priv->phys_base + DFSDM_RDATAR(fl_id));
>>> +	else
>>> +		return (dma_addr_t)(priv->phys_base + DFSDM_JDATAR(fl_id));
>>> +}
>>> +
>>> +/**
>>> + * stm32_dfsdm_register_fl_event - Register filter event.
>> What is a filter event?  More details good on things that are very
>> device specific like this.
> Filter events correspond to filter IRQ status, will be handled in a
> different way in IIO.
>>> + *
>>> + * @dfsdm: Handle used to retrieve dfsdm context.
>>> + * @fl_id: Filter id.
>>> + * @event: Event to unregister.
>>> + * @chan_mask: Mask of channels associated to filter.
>>> + *
>>> + * The function enables associated IRQ.
>>> + */
>>> +int stm32_dfsdm_register_fl_event(struct stm32_dfsdm *dfsdm, unsigned int fl_id,
>>> +				  enum stm32_dfsdm_events event,
>>> +				  unsigned int chan_mask)
>>> +{
>>> +	struct dfsdm_priv *priv = container_of(dfsdm, struct dfsdm_priv, dfsdm);
>>> +	unsigned long flags, ulmask = chan_mask;
>>> +	int ret, i;
>>> +
>>> +	dev_dbg(&priv->pdev->dev, "%s:for filter %d: event %#x ch_mask %#x\n",
>>> +		__func__, fl_id, event, chan_mask);
>>> +
>>> +	if (event > DFSDM_EVENT_CKA)
>>> +		return -EINVAL;
>>> +
>>> +	/* Clear interrupt before enable them */
>>> +	ret = stm32_dfsdm_clear_event(priv, fl_id, event, chan_mask);
>>> +	if (ret < 0)
>>> +		return ret;
>>> +
>>> +	spin_lock_irqsave(&priv->lock, flags);
>>> +	/* Enable interrupts */
>>> +	switch (event) {
>>> +	case DFSDM_EVENT_SCD:
>>> +		for_each_set_bit(i, &ulmask, priv->dfsdm.max_channels) {
>>> +			DFSDM_UPDATE_BITS(priv->regmap, DFSDM_CHCFGR1(i),
>>> +					  DFSDM_CHCFGR1_SCDEN_MASK,
>>> +					  DFSDM_CHCFGR1_SCDEN(1));
>>> +		}
>>> +		if (!priv->scd_filter_mask)
>>> +			DFSDM_UPDATE_BITS(priv->regmap, DFSDM_CR2(0),
>>> +					  DFSDM_CR2_SCDIE_MASK,
>>> +					  DFSDM_CR2_SCDIE(1));
>>> +		priv->scd_filter_mask |= BIT(fl_id);
>>> +		break;
>>> +	case DFSDM_EVENT_CKA:
>>> +		for_each_set_bit(i, &ulmask, priv->dfsdm.max_channels) {
>>> +			DFSDM_UPDATE_BITS(priv->regmap, DFSDM_CHCFGR1(i),
>>> +					  DFSDM_CHCFGR1_CKABEN_MASK,
>>> +					  DFSDM_CHCFGR1_CKABEN(1));
>>> +		}
>>> +		if (!priv->ckab_filter_mask)
>>> +			DFSDM_UPDATE_BITS(priv->regmap, DFSDM_CR2(0),
>>> +					  DFSDM_CR2_CKABIE_MASK,
>>> +					  DFSDM_CR2_CKABIE(1));
>>> +		priv->ckab_filter_mask |= BIT(fl_id);
>>> +		break;
>>> +	default:
>>> +		DFSDM_UPDATE_BITS(priv->regmap, DFSDM_CR2(fl_id), event, event);
>>> +	}
>>> +	priv->filters[fl_id].event_mask |= event;
>>> +	spin_unlock_irqrestore(&priv->lock, flags);
>>> +
>>> +	return 0;
>>> +}
>>> +EXPORT_SYMBOL_GPL(dfsdm_register_fl_event);
>>> +
>>> +/**
>>> + * stm32_dfsdm_unregister_fl_event - Unregister filter event.
>>> + *
>>> + * @dfsdm: Handle used to retrieve dfsdm context.
>>> + * @fl_id: Filter id.
>>> + * @event: Event to unregister.
>>> + * @chan_mask: Mask of channels associated to filter.
>>> + *
>>> + * The function disables associated IRQ.
>>> + */
>>> +int stm32_dfsdm_unregister_fl_event(struct stm32_dfsdm *dfsdm,
>>> +				    unsigned int fl_id,
>>> +				    enum stm32_dfsdm_events event,
>>> +				    unsigned int chan_mask)
>>> +{
>>> +	struct dfsdm_priv *priv = container_of(dfsdm, struct dfsdm_priv, dfsdm);
>>> +	unsigned long flags, ulmask = chan_mask;
>>> +	int i;
>>> +
>>> +	dev_dbg(&priv->pdev->dev, "%s:for filter %d: event %#x ch_mask %#x\n",
>>> +		__func__, fl_id, event, chan_mask);
>>> +
>>> +	if (event > DFSDM_EVENT_CKA)
>>> +		return -EINVAL;
>>> +
>>> +	spin_lock_irqsave(&priv->lock, flags);
>>> +	/* Disable interrupts */
>>> +	switch (event) {
>>> +	case DFSDM_EVENT_SCD:
>>> +		for_each_set_bit(i, &ulmask, priv->dfsdm.max_channels) {
>>> +			DFSDM_UPDATE_BITS(priv->regmap, DFSDM_CHCFGR1(i),
>>> +					  DFSDM_CHCFGR1_SCDEN_MASK,
>>> +					  DFSDM_CHCFGR1_SCDEN(0));
>>> +		}
>>> +		priv->scd_filter_mask &= ~BIT(fl_id);
>>> +		if (!priv->scd_filter_mask)
>>> +			DFSDM_UPDATE_BITS(priv->regmap, DFSDM_CR2(0),
>>> +					  DFSDM_CR2_SCDIE_MASK,
>>> +					  DFSDM_CR2_SCDIE(0));
>>> +		break;
>>> +	case DFSDM_EVENT_CKA:
>>> +		for_each_set_bit(i, &ulmask, priv->dfsdm.max_channels) {
>>> +			DFSDM_UPDATE_BITS(priv->regmap, DFSDM_CHCFGR1(i),
>>> +					  DFSDM_CHCFGR1_CKABEN_MASK,
>>> +					  DFSDM_CHCFGR1_CKABEN(0));
>>> +		}
>>> +		priv->ckab_filter_mask &= ~BIT(fl_id);
>>> +		if (!priv->ckab_filter_mask)
>>> +			DFSDM_UPDATE_BITS(priv->regmap, DFSDM_CR2(0),
>>> +					  DFSDM_CR2_CKABIE_MASK,
>>> +					  DFSDM_CR2_CKABIE(0));
>>> +		break;
>>> +	default:
>>> +		DFSDM_UPDATE_BITS(priv->regmap, DFSDM_CR2(fl_id), event, 0);
>>> +	}
>>> +
>>> +	priv->filters[fl_id].event_mask &= ~event;
>>> +	spin_unlock_irqrestore(&priv->lock, flags);
>>> +
>>> +	return 0;
>>> +}
>>> +EXPORT_SYMBOL_GPL(dfsdm_unregister_fl_event);
> 
> [...]
>>> +/* DFSDM filter conversion type */
>>> +enum stm32_dfsdm_conv_type {
>>> +	DFSDM_FILTER_REG_CONV,      /* Regular conversion */
>>> +	DFSDM_FILTER_SW_INJ_CONV,   /* Injected conversion */
>>> +	DFSDM_FILTER_TRIG_INJ_CONV, /* Injected conversion */
>>> +};
>>> +
>>> +/* DFSDM filter regular synchronous mode */
>>> +enum stm32_dfsdm_conv_rsync {
>>> +	DFSDM_FILTER_RSYNC_OFF, /* regular conversion asynchronous */
>>> +	DFSDM_FILTER_RSYNC_ON,  /* regular conversion synchronous with filter0*/
>> stray 0?
> Should read "filter instance 0"...
Cool.
> This corresponds to a specificity of the DFSDM hardware. DFSDM can offer
> possibility to synchronize each filter output with the filter 0 instance
> output.
> As example, this can be used to synchronize several audio microphones.
> Filter 0 is allocated to main microphones and the other filters for
> background microphones (notice that we need one filter per 1-bit PDM stream)
Makes sense, thanks.
> 
> [...]
> --
> To unsubscribe from this list: send the line "unsubscribe linux-iio" in
> the body of a message to majordomo at vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html
> 




More information about the linux-arm-kernel mailing list