mirror of
https://git.proxmox.com/git/mirror_ubuntu-kernels.git
synced 2026-01-04 09:39:03 +00:00
A nice mix this time of excellent cleanups (many to send drivers
speeding toward staging graduations) and new drivers / device support.
A good part of this is Brian Masney's never ending task on the tsl2x7x
driver. The end is in sight so hopefully we'll get that one out of
staging very soon!
New device support
* AD5686
- Support AD5685R (was wrongly present as AD5685)
- Support AD5672R, AD5676, AD5676, AD5684R and AD5686R 4 and 8 channel
SPI DACs with various precisions.
- Support AD5671R, AD5675R, AD5694, AD5694R, AD5695R, AD5696 and AD5696R
I2C DACs with various percisions and numbers of channels.
* Analog front end rescale driver - New driver.
- Support current sensing usings a shunt resistor.
- Support simple voltage dividers.
- support simple current sense amplifiers.
* TI dac5571
- New driver and device bindings supporting:
dac5571, dac6571, dac7571, dac5574, dac6574, dac7574,
dac5573, dac6573 and dac7573
* Meson-adc
- Support for Meson AXG with DT bindings.
* mpu6050
- Support the mpu9255 which only requires additional WHOAMI entry and
compatible string.
* st_lsm6dsx
- Support for lsm330dlc combinded accelerometer and gyro sensors with
DT bindings.
* stm32_adc
- Add support for STM32MP1 with bindings.
Staging graduations
* adis16201 after some excelent cleanup by Himanshu Jha.
* adis16029 after some excelent cleanup by Shreeya Patel.
New features:
* ABI docs
- Add core ABI docs for angle channels.
* inv_mpu6050
- Provide support for the full range of interrupts the device
supports.
* st_accel
- Add SMO8840 ACPI ID seen in the wild on some Lenovo machines.
* stx104
- Provide a multiple gpio get function.
Cleanups / Minor fixes
* core
- Use new nested structure support to improve kernel-doc.
* ad2s1200
- Use be16_to_cpup instead of opencoding.
* ad5686
- Indentation tidy up.
- Switch to SPDX
- Refactor to allow various numbers of channels.
- Refactor to separate core and SPI specific support, prior to
addition of i2c equivalent devices.
* ad7606
- Use drvdata directly from device rather than boucing via the
platform_device structure.
* ad7746
- Replace opencoded byte swapped i2c calls with _swapped variants.
- White space and line break readability improvements.
- Reorder includes and variable declarations where appropriate.
* ad7791
- Changes to the AD ADC library used by this driver took in the
sampling frequency. This lead to be the wrong path being the one
tied to the resulting attribute, so it didn't work, and a warning
to be printed.
* ad7780
- Remove apparent support for sampling frequency control on devices
that don't support changing the sampling attributes.
* ade7854
- Fix a read of the wrong number of bits.
- Improve error handling on i2c read/write errors.
- Rework i2c and spi code to reduce duplication.
* adis16201 (staging)
- Improve meaning inherent in some macro names by adding units etc
where relevant.
- Adjust comments to improve detail and drop the irrelevant.
- Rename register address definitions definitions to add a _REG
postfix, clearly separating them from field definitions. Reorganize
the definitions to group register address and fields.
- Use sign_extend32 rather than open coding.
- Reverse Xmas tree ordering where appropriate and align function args.
- Remove unused headers.
- Use GENMASK where appropriate instead of open coding.
* adis16209 (staging)
- Indent field definitions to visually separate them from
register address definitions.
- Use reverse xmas tree ordering where appropriate.
- Add some whitespace where it will help readability.
- Drop some unused headers.
- Use GENMASK where appropriate.
* ad2s1200
- Drop unnecessary includes and reorder alphabetically.
- Reverse xmas tree and blank line cleanups.
* atlas-ph-sensor
- Use msleep instead of usleep_range where the precise value doesn't
matter and the delays are long.
* bcm150
- Drop transaction splitting as core now handles it.
* cros_ec
- Move the shared header to the include/iio/common directory.
This brings it inline with the other multiple type devices.
- Use drvdata directly from device rather than boucing via the
platform_device structure.
* hid-sensors
- Use drvdata directly from device rather than boucing via the
platform_device structure.
* inv_mpu6050
- Clear out a second function definition for the same function.
- Don't flush fifo when the iio buffer is full but just drop excess
data.
- Tidy up set_power_itg and ensure it is used in the right places.
- Use set_power_itg rather than opencoding it again in the i2c mux
control.
- Make sure error paths disable the power if undoing power on.
- Used managed devm_ functions during probe. Delete remove function.
- Refactor to pull raw data read out of read_raw function.
- Simplify data reading error paths.
- Only enable the i2c mux for chips with the i2c aux bus (not icm20608)
- Fix a potential deadlock due to varying lock ordering.
- Fix an issue where first sample from gyro after enabling is unstable
by dropping the first sample.
- Fix an issue where the user_ctrl register is incorrectly overwritten.
- Tidy up some grammar and spelling minor issus.
* mcp320x
- Use vendor compatible strings.
* mcp4018
- Switch to using i2c .probe_new.
* mcp4351
- switch to using i2c .probe_new.
* meson-adc
- rework handing on common ADC platform data so it can be shared
across multiple families of SoCs.
* sca3000
- Fix an error handling path if the ring configure fails.
* st_lsm6dsx
- Fix a wrong fifo threshold mask (no actual effect)
* stm32-dfsdm
- Style fixes and cleanups.
- Check filter ID is in range and check spi-max-frequency.
* tsl2x7x (staging)
- Drop some unnecessary function calls, unused variables and
unnecessary local variables.
- Fix wrong interrupt type.
- Avoid unnecessary double clear of interrupt.
- Simplify proximity calibration call which did various things
unrelated to actually calibrating.
- Separate control of the proximity and ALS interrupts.
- Improve consistency of logging.
- Separate ALS and proximity persistence settings as they have
separate hardware controls.
- Tidy up variable ordering.
- Add Brian to copyright notice given consider work on this driver.
- Take advantage of hardware support for I2C address auto increment.
- Combine individuaal enable and period attributes for the two
directions on the threshold events into a single value as the
hardware doesn't separate them.
- Move integration_time* attributes from light channel to
intensity value as they effect the intensity readings directly
and the light reading only indirectly. Hence this better
reflects reality. Also move the calibscale_available.
- Avoid returning an error in the IRQ handler.
- Hard code the reg value in _clear_interrupts as it only takes
one value in the code. Result is the function has little
purpose so opencode the two remaining i2c_smbus_write_byte
calls.
- Drop some unnecessary checking of the chip status register.
- Tidy up return path in _write_interrupt_config.
- Tidy up the ID verification code.
- Move the power and diode settings defines into the header as these
are needed for platform data configuration.
- Various renames and comment cleanups for consistency and clarity.
- Use actual device defaults for default startup settings.
- SPDX
- Add some range sanity checking to sysfs attribute writes.
- Don't provide event interfaces if the interrupt line isn't available.
- Use IIO_CONST_ATTR macro for calibscale_available as it's a constant
string.
- Fix the integration time and lux equations.
- Make device IDs explicit index values in the device_channel_config array.
-----BEGIN PGP SIGNATURE-----
iQJFBAABCAAvFiEEbilms4eEBlKRJoGxVIU0mcT0FogFAlr0uigRHGppYzIzQGtl
cm5lbC5vcmcACgkQVIU0mcT0FoiRhw//bIv57xenV6WV3+7Xmrp/rG4OBVzt8riD
d9HczYKZE8/tqfB9cjF5e/xOpfin99eXBqEMWecidh3BxMNKXgnbvvCYCXF+m47D
3cYlWvGbyZ2lbvqwbYCkk4xKfPNbRHBo5NX9TB08wf7DrAjmaudxmxDSFrRYQCho
3Z3H2o54z1i/iJud61TApEONxr7H5oFdeb6uwNB5UVlG7XING9mfWbmlSzdlxW4X
lHG4hC1/Pz/F4WjiLvNKccjhuq1f60CwSppKvfihq1IZKYKyHvSWjOicwrNb3QB2
Kr0dT8yDqNKcKU1tsfgfR+YFeLKq52Ik4whulk8mkL7E4IWVIkGbL0ewkPwZ0YqO
Cj9Nv52vzYS7x9Dj7R6f23Lwq30a19WKXixybjdo9zV14S0XKfzTmU7xC/4yhEjh
yUg9QPjWxE5rAUTSjNF0C6tv786Y3aVYBp2jscIroSutipa6cUyc2rOKssM6GTZs
vyRElGiUEXbg7uI7GrwN4PJszho47ptkFGn/o88Sy90KjUJ0mP15NTlp4IeSiVc/
sM4YHHQ7tldcKqsaKeGbH8JwJNvzj9Kh20zniYd9lSZkeZusSkZUvd1FgB5uUocb
ZZEG2IzCLM/utH49yXyaUkRS/lqoGCcObDkIHbsE1OM8q2bnZxaUYEMumxIpAs7l
W2LBlr3vGF4=
=egCX
-----END PGP SIGNATURE-----
Merge tag 'iio-for-4.18a' of git://git.kernel.org/pub/scm/linux/kernel/git/jic23/iio into staging-next
Jonathan writes:
1st round of IIO new device support, features and cleanup for the 4.18 cycle
A nice mix this time of excellent cleanups (many to send drivers
speeding toward staging graduations) and new drivers / device support.
A good part of this is Brian Masney's never ending task on the tsl2x7x
driver. The end is in sight so hopefully we'll get that one out of
staging very soon!
New device support
* AD5686
- Support AD5685R (was wrongly present as AD5685)
- Support AD5672R, AD5676, AD5676, AD5684R and AD5686R 4 and 8 channel
SPI DACs with various precisions.
- Support AD5671R, AD5675R, AD5694, AD5694R, AD5695R, AD5696 and AD5696R
I2C DACs with various percisions and numbers of channels.
* Analog front end rescale driver - New driver.
- Support current sensing usings a shunt resistor.
- Support simple voltage dividers.
- support simple current sense amplifiers.
* TI dac5571
- New driver and device bindings supporting:
dac5571, dac6571, dac7571, dac5574, dac6574, dac7574,
dac5573, dac6573 and dac7573
* Meson-adc
- Support for Meson AXG with DT bindings.
* mpu6050
- Support the mpu9255 which only requires additional WHOAMI entry and
compatible string.
* st_lsm6dsx
- Support for lsm330dlc combinded accelerometer and gyro sensors with
DT bindings.
* stm32_adc
- Add support for STM32MP1 with bindings.
Staging graduations
* adis16201 after some excelent cleanup by Himanshu Jha.
* adis16029 after some excelent cleanup by Shreeya Patel.
New features:
* ABI docs
- Add core ABI docs for angle channels.
* inv_mpu6050
- Provide support for the full range of interrupts the device
supports.
* st_accel
- Add SMO8840 ACPI ID seen in the wild on some Lenovo machines.
* stx104
- Provide a multiple gpio get function.
Cleanups / Minor fixes
* core
- Use new nested structure support to improve kernel-doc.
* ad2s1200
- Use be16_to_cpup instead of opencoding.
* ad5686
- Indentation tidy up.
- Switch to SPDX
- Refactor to allow various numbers of channels.
- Refactor to separate core and SPI specific support, prior to
addition of i2c equivalent devices.
* ad7606
- Use drvdata directly from device rather than boucing via the
platform_device structure.
* ad7746
- Replace opencoded byte swapped i2c calls with _swapped variants.
- White space and line break readability improvements.
- Reorder includes and variable declarations where appropriate.
* ad7791
- Changes to the AD ADC library used by this driver took in the
sampling frequency. This lead to be the wrong path being the one
tied to the resulting attribute, so it didn't work, and a warning
to be printed.
* ad7780
- Remove apparent support for sampling frequency control on devices
that don't support changing the sampling attributes.
* ade7854
- Fix a read of the wrong number of bits.
- Improve error handling on i2c read/write errors.
- Rework i2c and spi code to reduce duplication.
* adis16201 (staging)
- Improve meaning inherent in some macro names by adding units etc
where relevant.
- Adjust comments to improve detail and drop the irrelevant.
- Rename register address definitions definitions to add a _REG
postfix, clearly separating them from field definitions. Reorganize
the definitions to group register address and fields.
- Use sign_extend32 rather than open coding.
- Reverse Xmas tree ordering where appropriate and align function args.
- Remove unused headers.
- Use GENMASK where appropriate instead of open coding.
* adis16209 (staging)
- Indent field definitions to visually separate them from
register address definitions.
- Use reverse xmas tree ordering where appropriate.
- Add some whitespace where it will help readability.
- Drop some unused headers.
- Use GENMASK where appropriate.
* ad2s1200
- Drop unnecessary includes and reorder alphabetically.
- Reverse xmas tree and blank line cleanups.
* atlas-ph-sensor
- Use msleep instead of usleep_range where the precise value doesn't
matter and the delays are long.
* bcm150
- Drop transaction splitting as core now handles it.
* cros_ec
- Move the shared header to the include/iio/common directory.
This brings it inline with the other multiple type devices.
- Use drvdata directly from device rather than boucing via the
platform_device structure.
* hid-sensors
- Use drvdata directly from device rather than boucing via the
platform_device structure.
* inv_mpu6050
- Clear out a second function definition for the same function.
- Don't flush fifo when the iio buffer is full but just drop excess
data.
- Tidy up set_power_itg and ensure it is used in the right places.
- Use set_power_itg rather than opencoding it again in the i2c mux
control.
- Make sure error paths disable the power if undoing power on.
- Used managed devm_ functions during probe. Delete remove function.
- Refactor to pull raw data read out of read_raw function.
- Simplify data reading error paths.
- Only enable the i2c mux for chips with the i2c aux bus (not icm20608)
- Fix a potential deadlock due to varying lock ordering.
- Fix an issue where first sample from gyro after enabling is unstable
by dropping the first sample.
- Fix an issue where the user_ctrl register is incorrectly overwritten.
- Tidy up some grammar and spelling minor issus.
* mcp320x
- Use vendor compatible strings.
* mcp4018
- Switch to using i2c .probe_new.
* mcp4351
- switch to using i2c .probe_new.
* meson-adc
- rework handing on common ADC platform data so it can be shared
across multiple families of SoCs.
* sca3000
- Fix an error handling path if the ring configure fails.
* st_lsm6dsx
- Fix a wrong fifo threshold mask (no actual effect)
* stm32-dfsdm
- Style fixes and cleanups.
- Check filter ID is in range and check spi-max-frequency.
* tsl2x7x (staging)
- Drop some unnecessary function calls, unused variables and
unnecessary local variables.
- Fix wrong interrupt type.
- Avoid unnecessary double clear of interrupt.
- Simplify proximity calibration call which did various things
unrelated to actually calibrating.
- Separate control of the proximity and ALS interrupts.
- Improve consistency of logging.
- Separate ALS and proximity persistence settings as they have
separate hardware controls.
- Tidy up variable ordering.
- Add Brian to copyright notice given consider work on this driver.
- Take advantage of hardware support for I2C address auto increment.
- Combine individuaal enable and period attributes for the two
directions on the threshold events into a single value as the
hardware doesn't separate them.
- Move integration_time* attributes from light channel to
intensity value as they effect the intensity readings directly
and the light reading only indirectly. Hence this better
reflects reality. Also move the calibscale_available.
- Avoid returning an error in the IRQ handler.
- Hard code the reg value in _clear_interrupts as it only takes
one value in the code. Result is the function has little
purpose so opencode the two remaining i2c_smbus_write_byte
calls.
- Drop some unnecessary checking of the chip status register.
- Tidy up return path in _write_interrupt_config.
- Tidy up the ID verification code.
- Move the power and diode settings defines into the header as these
are needed for platform data configuration.
- Various renames and comment cleanups for consistency and clarity.
- Use actual device defaults for default startup settings.
- SPDX
- Add some range sanity checking to sysfs attribute writes.
- Don't provide event interfaces if the interrupt line isn't available.
- Use IIO_CONST_ATTR macro for calibscale_available as it's a constant
string.
- Fix the integration time and lux equations.
- Make device IDs explicit index values in the device_channel_config array.
1208 lines
30 KiB
C
1208 lines
30 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* This file is the ADC part of the STM32 DFSDM driver
|
|
*
|
|
* Copyright (C) 2017, STMicroelectronics - All Rights Reserved
|
|
* Author: Arnaud Pouliquen <arnaud.pouliquen@st.com>.
|
|
*/
|
|
|
|
#include <linux/dmaengine.h>
|
|
#include <linux/dma-mapping.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/iio/buffer.h>
|
|
#include <linux/iio/hw-consumer.h>
|
|
#include <linux/iio/iio.h>
|
|
#include <linux/iio/sysfs.h>
|
|
#include <linux/module.h>
|
|
#include <linux/of_device.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/regmap.h>
|
|
#include <linux/slab.h>
|
|
|
|
#include "stm32-dfsdm.h"
|
|
|
|
#define DFSDM_DMA_BUFFER_SIZE (4 * PAGE_SIZE)
|
|
|
|
/* Conversion timeout */
|
|
#define DFSDM_TIMEOUT_US 100000
|
|
#define DFSDM_TIMEOUT (msecs_to_jiffies(DFSDM_TIMEOUT_US / 1000))
|
|
|
|
/* Oversampling attribute default */
|
|
#define DFSDM_DEFAULT_OVERSAMPLING 100
|
|
|
|
/* Oversampling max values */
|
|
#define DFSDM_MAX_INT_OVERSAMPLING 256
|
|
#define DFSDM_MAX_FL_OVERSAMPLING 1024
|
|
|
|
/* Max sample resolutions */
|
|
#define DFSDM_MAX_RES BIT(31)
|
|
#define DFSDM_DATA_RES BIT(23)
|
|
|
|
enum sd_converter_type {
|
|
DFSDM_AUDIO,
|
|
DFSDM_IIO,
|
|
};
|
|
|
|
struct stm32_dfsdm_dev_data {
|
|
int type;
|
|
int (*init)(struct iio_dev *indio_dev);
|
|
unsigned int num_channels;
|
|
const struct regmap_config *regmap_cfg;
|
|
};
|
|
|
|
struct stm32_dfsdm_adc {
|
|
struct stm32_dfsdm *dfsdm;
|
|
const struct stm32_dfsdm_dev_data *dev_data;
|
|
unsigned int fl_id;
|
|
|
|
/* ADC specific */
|
|
unsigned int oversamp;
|
|
struct iio_hw_consumer *hwc;
|
|
struct completion completion;
|
|
u32 *buffer;
|
|
|
|
/* Audio specific */
|
|
unsigned int spi_freq; /* SPI bus clock frequency */
|
|
unsigned int sample_freq; /* Sample frequency after filter decimation */
|
|
int (*cb)(const void *data, size_t size, void *cb_priv);
|
|
void *cb_priv;
|
|
|
|
/* DMA */
|
|
u8 *rx_buf;
|
|
unsigned int bufi; /* Buffer current position */
|
|
unsigned int buf_sz; /* Buffer size */
|
|
struct dma_chan *dma_chan;
|
|
dma_addr_t dma_buf;
|
|
};
|
|
|
|
struct stm32_dfsdm_str2field {
|
|
const char *name;
|
|
unsigned int val;
|
|
};
|
|
|
|
/* DFSDM channel serial interface type */
|
|
static const struct stm32_dfsdm_str2field stm32_dfsdm_chan_type[] = {
|
|
{ "SPI_R", 0 }, /* SPI with data on rising edge */
|
|
{ "SPI_F", 1 }, /* SPI with data on falling edge */
|
|
{ "MANCH_R", 2 }, /* Manchester codec, rising edge = logic 0 */
|
|
{ "MANCH_F", 3 }, /* Manchester codec, falling edge = logic 1 */
|
|
{},
|
|
};
|
|
|
|
/* DFSDM channel clock source */
|
|
static const struct stm32_dfsdm_str2field stm32_dfsdm_chan_src[] = {
|
|
/* External SPI clock (CLKIN x) */
|
|
{ "CLKIN", DFSDM_CHANNEL_SPI_CLOCK_EXTERNAL },
|
|
/* Internal SPI clock (CLKOUT) */
|
|
{ "CLKOUT", DFSDM_CHANNEL_SPI_CLOCK_INTERNAL },
|
|
/* Internal SPI clock divided by 2 (falling edge) */
|
|
{ "CLKOUT_F", DFSDM_CHANNEL_SPI_CLOCK_INTERNAL_DIV2_FALLING },
|
|
/* Internal SPI clock divided by 2 (falling edge) */
|
|
{ "CLKOUT_R", DFSDM_CHANNEL_SPI_CLOCK_INTERNAL_DIV2_RISING },
|
|
{},
|
|
};
|
|
|
|
static int stm32_dfsdm_str2val(const char *str,
|
|
const struct stm32_dfsdm_str2field *list)
|
|
{
|
|
const struct stm32_dfsdm_str2field *p = list;
|
|
|
|
for (p = list; p && p->name; p++)
|
|
if (!strcmp(p->name, str))
|
|
return p->val;
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
static int stm32_dfsdm_set_osrs(struct stm32_dfsdm_filter *fl,
|
|
unsigned int fast, unsigned int oversamp)
|
|
{
|
|
unsigned int i, d, fosr, iosr;
|
|
u64 res;
|
|
s64 delta;
|
|
unsigned int m = 1; /* multiplication factor */
|
|
unsigned int p = fl->ford; /* filter order (ford) */
|
|
|
|
pr_debug("%s: Requested oversampling: %d\n", __func__, oversamp);
|
|
/*
|
|
* This function tries to compute filter oversampling and integrator
|
|
* oversampling, base on oversampling ratio requested by user.
|
|
*
|
|
* Decimation d depends on the filter order and the oversampling ratios.
|
|
* ford: filter order
|
|
* fosr: filter over sampling ratio
|
|
* iosr: integrator over sampling ratio
|
|
*/
|
|
if (fl->ford == DFSDM_FASTSINC_ORDER) {
|
|
m = 2;
|
|
p = 2;
|
|
}
|
|
|
|
/*
|
|
* Look for filter and integrator oversampling ratios which allows
|
|
* to reach 24 bits data output resolution.
|
|
* Leave as soon as 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 (fl->ford == DFSDM_FASTSINC_ORDER)
|
|
d = fosr * (iosr + 3) + 2;
|
|
else
|
|
d = fosr * (iosr - 1 + p) + p;
|
|
|
|
if (d > oversamp)
|
|
break;
|
|
else if (d != oversamp)
|
|
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 > DFSDM_MAX_RES)
|
|
break;
|
|
}
|
|
if (res > DFSDM_MAX_RES)
|
|
continue;
|
|
res = res * (u64)m * (u64)iosr;
|
|
if (res > DFSDM_MAX_RES)
|
|
continue;
|
|
|
|
delta = res - DFSDM_DATA_RES;
|
|
|
|
if (res >= fl->res) {
|
|
fl->res = res;
|
|
fl->fosr = fosr;
|
|
fl->iosr = iosr;
|
|
fl->fast = fast;
|
|
pr_debug("%s: fosr = %d, iosr = %d\n",
|
|
__func__, fl->fosr, fl->iosr);
|
|
}
|
|
|
|
if (!delta)
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
if (!fl->fosr)
|
|
return -EINVAL;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int stm32_dfsdm_start_channel(struct stm32_dfsdm *dfsdm,
|
|
unsigned int ch_id)
|
|
{
|
|
return regmap_update_bits(dfsdm->regmap, DFSDM_CHCFGR1(ch_id),
|
|
DFSDM_CHCFGR1_CHEN_MASK,
|
|
DFSDM_CHCFGR1_CHEN(1));
|
|
}
|
|
|
|
static void stm32_dfsdm_stop_channel(struct stm32_dfsdm *dfsdm,
|
|
unsigned int ch_id)
|
|
{
|
|
regmap_update_bits(dfsdm->regmap, DFSDM_CHCFGR1(ch_id),
|
|
DFSDM_CHCFGR1_CHEN_MASK, DFSDM_CHCFGR1_CHEN(0));
|
|
}
|
|
|
|
static int stm32_dfsdm_chan_configure(struct stm32_dfsdm *dfsdm,
|
|
struct stm32_dfsdm_channel *ch)
|
|
{
|
|
unsigned int id = ch->id;
|
|
struct regmap *regmap = dfsdm->regmap;
|
|
int ret;
|
|
|
|
ret = regmap_update_bits(regmap, DFSDM_CHCFGR1(id),
|
|
DFSDM_CHCFGR1_SITP_MASK,
|
|
DFSDM_CHCFGR1_SITP(ch->type));
|
|
if (ret < 0)
|
|
return ret;
|
|
ret = regmap_update_bits(regmap, DFSDM_CHCFGR1(id),
|
|
DFSDM_CHCFGR1_SPICKSEL_MASK,
|
|
DFSDM_CHCFGR1_SPICKSEL(ch->src));
|
|
if (ret < 0)
|
|
return ret;
|
|
return regmap_update_bits(regmap, DFSDM_CHCFGR1(id),
|
|
DFSDM_CHCFGR1_CHINSEL_MASK,
|
|
DFSDM_CHCFGR1_CHINSEL(ch->alt_si));
|
|
}
|
|
|
|
static int stm32_dfsdm_start_filter(struct stm32_dfsdm *dfsdm,
|
|
unsigned int fl_id)
|
|
{
|
|
int ret;
|
|
|
|
/* Enable filter */
|
|
ret = regmap_update_bits(dfsdm->regmap, DFSDM_CR1(fl_id),
|
|
DFSDM_CR1_DFEN_MASK, DFSDM_CR1_DFEN(1));
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
/* Start conversion */
|
|
return regmap_update_bits(dfsdm->regmap, DFSDM_CR1(fl_id),
|
|
DFSDM_CR1_RSWSTART_MASK,
|
|
DFSDM_CR1_RSWSTART(1));
|
|
}
|
|
|
|
static void stm32_dfsdm_stop_filter(struct stm32_dfsdm *dfsdm,
|
|
unsigned int fl_id)
|
|
{
|
|
/* Disable conversion */
|
|
regmap_update_bits(dfsdm->regmap, DFSDM_CR1(fl_id),
|
|
DFSDM_CR1_DFEN_MASK, DFSDM_CR1_DFEN(0));
|
|
}
|
|
|
|
static int stm32_dfsdm_filter_configure(struct stm32_dfsdm *dfsdm,
|
|
unsigned int fl_id, unsigned int ch_id)
|
|
{
|
|
struct regmap *regmap = dfsdm->regmap;
|
|
struct stm32_dfsdm_filter *fl = &dfsdm->fl_list[fl_id];
|
|
int ret;
|
|
|
|
/* Average integrator oversampling */
|
|
ret = regmap_update_bits(regmap, DFSDM_FCR(fl_id), DFSDM_FCR_IOSR_MASK,
|
|
DFSDM_FCR_IOSR(fl->iosr - 1));
|
|
if (ret)
|
|
return ret;
|
|
|
|
/* Filter order and Oversampling */
|
|
ret = regmap_update_bits(regmap, DFSDM_FCR(fl_id), DFSDM_FCR_FOSR_MASK,
|
|
DFSDM_FCR_FOSR(fl->fosr - 1));
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = regmap_update_bits(regmap, DFSDM_FCR(fl_id), DFSDM_FCR_FORD_MASK,
|
|
DFSDM_FCR_FORD(fl->ford));
|
|
if (ret)
|
|
return ret;
|
|
|
|
/* No scan mode supported for the moment */
|
|
ret = regmap_update_bits(regmap, DFSDM_CR1(fl_id), DFSDM_CR1_RCH_MASK,
|
|
DFSDM_CR1_RCH(ch_id));
|
|
if (ret)
|
|
return ret;
|
|
|
|
return regmap_update_bits(regmap, DFSDM_CR1(fl_id),
|
|
DFSDM_CR1_RSYNC_MASK,
|
|
DFSDM_CR1_RSYNC(fl->sync_mode));
|
|
}
|
|
|
|
static int stm32_dfsdm_channel_parse_of(struct stm32_dfsdm *dfsdm,
|
|
struct iio_dev *indio_dev,
|
|
struct iio_chan_spec *ch)
|
|
{
|
|
struct stm32_dfsdm_channel *df_ch;
|
|
const char *of_str;
|
|
int chan_idx = ch->scan_index;
|
|
int ret, val;
|
|
|
|
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;
|
|
}
|
|
if (ch->channel >= dfsdm->num_chs) {
|
|
dev_err(&indio_dev->dev,
|
|
" Error bad channel number %d (max = %d)\n",
|
|
ch->channel, dfsdm->num_chs);
|
|
return -EINVAL;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
df_ch = &dfsdm->ch_list[ch->channel];
|
|
df_ch->id = ch->channel;
|
|
|
|
ret = of_property_read_string_index(indio_dev->dev.of_node,
|
|
"st,adc-channel-types", chan_idx,
|
|
&of_str);
|
|
if (!ret) {
|
|
val = stm32_dfsdm_str2val(of_str, stm32_dfsdm_chan_type);
|
|
if (val < 0)
|
|
return val;
|
|
} else {
|
|
val = 0;
|
|
}
|
|
df_ch->type = val;
|
|
|
|
ret = of_property_read_string_index(indio_dev->dev.of_node,
|
|
"st,adc-channel-clk-src", chan_idx,
|
|
&of_str);
|
|
if (!ret) {
|
|
val = stm32_dfsdm_str2val(of_str, stm32_dfsdm_chan_src);
|
|
if (val < 0)
|
|
return val;
|
|
} else {
|
|
val = 0;
|
|
}
|
|
df_ch->src = val;
|
|
|
|
ret = of_property_read_u32_index(indio_dev->dev.of_node,
|
|
"st,adc-alt-channel", chan_idx,
|
|
&df_ch->alt_si);
|
|
if (ret < 0)
|
|
df_ch->alt_si = 0;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static ssize_t dfsdm_adc_audio_get_spiclk(struct iio_dev *indio_dev,
|
|
uintptr_t priv,
|
|
const struct iio_chan_spec *chan,
|
|
char *buf)
|
|
{
|
|
struct stm32_dfsdm_adc *adc = iio_priv(indio_dev);
|
|
|
|
return snprintf(buf, PAGE_SIZE, "%d\n", adc->spi_freq);
|
|
}
|
|
|
|
static ssize_t dfsdm_adc_audio_set_spiclk(struct iio_dev *indio_dev,
|
|
uintptr_t priv,
|
|
const struct iio_chan_spec *chan,
|
|
const char *buf, size_t len)
|
|
{
|
|
struct stm32_dfsdm_adc *adc = iio_priv(indio_dev);
|
|
struct stm32_dfsdm_filter *fl = &adc->dfsdm->fl_list[adc->fl_id];
|
|
struct stm32_dfsdm_channel *ch = &adc->dfsdm->ch_list[chan->channel];
|
|
unsigned int sample_freq = adc->sample_freq;
|
|
unsigned int spi_freq;
|
|
int ret;
|
|
|
|
dev_err(&indio_dev->dev, "enter %s\n", __func__);
|
|
/* If DFSDM is master on SPI, SPI freq can not be updated */
|
|
if (ch->src != DFSDM_CHANNEL_SPI_CLOCK_EXTERNAL)
|
|
return -EPERM;
|
|
|
|
ret = kstrtoint(buf, 0, &spi_freq);
|
|
if (ret)
|
|
return ret;
|
|
|
|
if (!spi_freq)
|
|
return -EINVAL;
|
|
|
|
if (sample_freq) {
|
|
if (spi_freq % sample_freq)
|
|
dev_warn(&indio_dev->dev,
|
|
"Sampling rate not accurate (%d)\n",
|
|
spi_freq / (spi_freq / sample_freq));
|
|
|
|
ret = stm32_dfsdm_set_osrs(fl, 0, (spi_freq / sample_freq));
|
|
if (ret < 0) {
|
|
dev_err(&indio_dev->dev,
|
|
"No filter parameters that match!\n");
|
|
return ret;
|
|
}
|
|
}
|
|
adc->spi_freq = spi_freq;
|
|
|
|
return len;
|
|
}
|
|
|
|
static int stm32_dfsdm_start_conv(struct stm32_dfsdm_adc *adc,
|
|
const struct iio_chan_spec *chan,
|
|
bool dma)
|
|
{
|
|
struct regmap *regmap = adc->dfsdm->regmap;
|
|
int ret;
|
|
unsigned int dma_en = 0, cont_en = 0;
|
|
|
|
ret = stm32_dfsdm_start_channel(adc->dfsdm, chan->channel);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
ret = stm32_dfsdm_filter_configure(adc->dfsdm, adc->fl_id,
|
|
chan->channel);
|
|
if (ret < 0)
|
|
goto stop_channels;
|
|
|
|
if (dma) {
|
|
/* Enable DMA transfer*/
|
|
dma_en = DFSDM_CR1_RDMAEN(1);
|
|
/* Enable conversion triggered by SPI clock*/
|
|
cont_en = DFSDM_CR1_RCONT(1);
|
|
}
|
|
/* Enable DMA transfer*/
|
|
ret = regmap_update_bits(regmap, DFSDM_CR1(adc->fl_id),
|
|
DFSDM_CR1_RDMAEN_MASK, dma_en);
|
|
if (ret < 0)
|
|
goto stop_channels;
|
|
|
|
/* Enable conversion triggered by SPI clock*/
|
|
ret = regmap_update_bits(regmap, DFSDM_CR1(adc->fl_id),
|
|
DFSDM_CR1_RCONT_MASK, cont_en);
|
|
if (ret < 0)
|
|
goto stop_channels;
|
|
|
|
ret = stm32_dfsdm_start_filter(adc->dfsdm, adc->fl_id);
|
|
if (ret < 0)
|
|
goto stop_channels;
|
|
|
|
return 0;
|
|
|
|
stop_channels:
|
|
regmap_update_bits(regmap, DFSDM_CR1(adc->fl_id),
|
|
DFSDM_CR1_RDMAEN_MASK, 0);
|
|
|
|
regmap_update_bits(regmap, DFSDM_CR1(adc->fl_id),
|
|
DFSDM_CR1_RCONT_MASK, 0);
|
|
stm32_dfsdm_stop_channel(adc->dfsdm, chan->channel);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void stm32_dfsdm_stop_conv(struct stm32_dfsdm_adc *adc,
|
|
const struct iio_chan_spec *chan)
|
|
{
|
|
struct regmap *regmap = adc->dfsdm->regmap;
|
|
|
|
stm32_dfsdm_stop_filter(adc->dfsdm, adc->fl_id);
|
|
|
|
/* Clean conversion options */
|
|
regmap_update_bits(regmap, DFSDM_CR1(adc->fl_id),
|
|
DFSDM_CR1_RDMAEN_MASK, 0);
|
|
|
|
regmap_update_bits(regmap, DFSDM_CR1(adc->fl_id),
|
|
DFSDM_CR1_RCONT_MASK, 0);
|
|
|
|
stm32_dfsdm_stop_channel(adc->dfsdm, chan->channel);
|
|
}
|
|
|
|
static int stm32_dfsdm_set_watermark(struct iio_dev *indio_dev,
|
|
unsigned int val)
|
|
{
|
|
struct stm32_dfsdm_adc *adc = iio_priv(indio_dev);
|
|
unsigned int watermark = DFSDM_DMA_BUFFER_SIZE / 2;
|
|
|
|
/*
|
|
* DMA cyclic transfers are used, buffer is split into two periods.
|
|
* There should be :
|
|
* - always one buffer (period) DMA is working on
|
|
* - one buffer (period) driver pushed to ASoC side.
|
|
*/
|
|
watermark = min(watermark, val * (unsigned int)(sizeof(u32)));
|
|
adc->buf_sz = watermark * 2;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static unsigned int stm32_dfsdm_adc_dma_residue(struct stm32_dfsdm_adc *adc)
|
|
{
|
|
struct dma_tx_state state;
|
|
enum dma_status status;
|
|
|
|
status = dmaengine_tx_status(adc->dma_chan,
|
|
adc->dma_chan->cookie,
|
|
&state);
|
|
if (status == DMA_IN_PROGRESS) {
|
|
/* Residue is size in bytes from end of buffer */
|
|
unsigned int i = adc->buf_sz - state.residue;
|
|
unsigned int size;
|
|
|
|
/* Return available bytes */
|
|
if (i >= adc->bufi)
|
|
size = i - adc->bufi;
|
|
else
|
|
size = adc->buf_sz + i - adc->bufi;
|
|
|
|
return size;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void stm32_dfsdm_audio_dma_buffer_done(void *data)
|
|
{
|
|
struct iio_dev *indio_dev = data;
|
|
struct stm32_dfsdm_adc *adc = iio_priv(indio_dev);
|
|
int available = stm32_dfsdm_adc_dma_residue(adc);
|
|
size_t old_pos;
|
|
|
|
/*
|
|
* FIXME: In Kernel interface does not support cyclic DMA buffer,and
|
|
* offers only an interface to push data samples per samples.
|
|
* For this reason IIO buffer interface is not used and interface is
|
|
* bypassed using a private callback registered by ASoC.
|
|
* This should be a temporary solution waiting a cyclic DMA engine
|
|
* support in IIO.
|
|
*/
|
|
|
|
dev_dbg(&indio_dev->dev, "%s: pos = %d, available = %d\n", __func__,
|
|
adc->bufi, available);
|
|
old_pos = adc->bufi;
|
|
|
|
while (available >= indio_dev->scan_bytes) {
|
|
u32 *buffer = (u32 *)&adc->rx_buf[adc->bufi];
|
|
|
|
/* Mask 8 LSB that contains the channel ID */
|
|
*buffer = (*buffer & 0xFFFFFF00) << 8;
|
|
available -= indio_dev->scan_bytes;
|
|
adc->bufi += indio_dev->scan_bytes;
|
|
if (adc->bufi >= adc->buf_sz) {
|
|
if (adc->cb)
|
|
adc->cb(&adc->rx_buf[old_pos],
|
|
adc->buf_sz - old_pos, adc->cb_priv);
|
|
adc->bufi = 0;
|
|
old_pos = 0;
|
|
}
|
|
}
|
|
if (adc->cb)
|
|
adc->cb(&adc->rx_buf[old_pos], adc->bufi - old_pos,
|
|
adc->cb_priv);
|
|
}
|
|
|
|
static int stm32_dfsdm_adc_dma_start(struct iio_dev *indio_dev)
|
|
{
|
|
struct stm32_dfsdm_adc *adc = iio_priv(indio_dev);
|
|
struct dma_async_tx_descriptor *desc;
|
|
dma_cookie_t cookie;
|
|
int ret;
|
|
|
|
if (!adc->dma_chan)
|
|
return -EINVAL;
|
|
|
|
dev_dbg(&indio_dev->dev, "%s size=%d watermark=%d\n", __func__,
|
|
adc->buf_sz, adc->buf_sz / 2);
|
|
|
|
/* Prepare a DMA cyclic transaction */
|
|
desc = dmaengine_prep_dma_cyclic(adc->dma_chan,
|
|
adc->dma_buf,
|
|
adc->buf_sz, adc->buf_sz / 2,
|
|
DMA_DEV_TO_MEM,
|
|
DMA_PREP_INTERRUPT);
|
|
if (!desc)
|
|
return -EBUSY;
|
|
|
|
desc->callback = stm32_dfsdm_audio_dma_buffer_done;
|
|
desc->callback_param = indio_dev;
|
|
|
|
cookie = dmaengine_submit(desc);
|
|
ret = dma_submit_error(cookie);
|
|
if (ret) {
|
|
dmaengine_terminate_all(adc->dma_chan);
|
|
return ret;
|
|
}
|
|
|
|
/* Issue pending DMA requests */
|
|
dma_async_issue_pending(adc->dma_chan);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int stm32_dfsdm_postenable(struct iio_dev *indio_dev)
|
|
{
|
|
struct stm32_dfsdm_adc *adc = iio_priv(indio_dev);
|
|
const struct iio_chan_spec *chan = &indio_dev->channels[0];
|
|
int ret;
|
|
|
|
/* Reset adc buffer index */
|
|
adc->bufi = 0;
|
|
|
|
ret = stm32_dfsdm_start_dfsdm(adc->dfsdm);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
ret = stm32_dfsdm_start_conv(adc, chan, true);
|
|
if (ret) {
|
|
dev_err(&indio_dev->dev, "Can't start conversion\n");
|
|
goto stop_dfsdm;
|
|
}
|
|
|
|
if (adc->dma_chan) {
|
|
ret = stm32_dfsdm_adc_dma_start(indio_dev);
|
|
if (ret) {
|
|
dev_err(&indio_dev->dev, "Can't start DMA\n");
|
|
goto err_stop_conv;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
|
|
err_stop_conv:
|
|
stm32_dfsdm_stop_conv(adc, chan);
|
|
stop_dfsdm:
|
|
stm32_dfsdm_stop_dfsdm(adc->dfsdm);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int stm32_dfsdm_predisable(struct iio_dev *indio_dev)
|
|
{
|
|
struct stm32_dfsdm_adc *adc = iio_priv(indio_dev);
|
|
const struct iio_chan_spec *chan = &indio_dev->channels[0];
|
|
|
|
if (adc->dma_chan)
|
|
dmaengine_terminate_all(adc->dma_chan);
|
|
|
|
stm32_dfsdm_stop_conv(adc, chan);
|
|
|
|
stm32_dfsdm_stop_dfsdm(adc->dfsdm);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct iio_buffer_setup_ops stm32_dfsdm_buffer_setup_ops = {
|
|
.postenable = &stm32_dfsdm_postenable,
|
|
.predisable = &stm32_dfsdm_predisable,
|
|
};
|
|
|
|
/**
|
|
* stm32_dfsdm_get_buff_cb() - register a callback that will be called when
|
|
* DMA transfer period is achieved.
|
|
*
|
|
* @iio_dev: Handle to IIO device.
|
|
* @cb: Pointer to callback function:
|
|
* - data: pointer to data buffer
|
|
* - size: size in byte of the data buffer
|
|
* - private: pointer to consumer private structure.
|
|
* @private: Pointer to consumer private structure.
|
|
*/
|
|
int stm32_dfsdm_get_buff_cb(struct iio_dev *iio_dev,
|
|
int (*cb)(const void *data, size_t size,
|
|
void *private),
|
|
void *private)
|
|
{
|
|
struct stm32_dfsdm_adc *adc;
|
|
|
|
if (!iio_dev)
|
|
return -EINVAL;
|
|
adc = iio_priv(iio_dev);
|
|
|
|
adc->cb = cb;
|
|
adc->cb_priv = private;
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_GPL(stm32_dfsdm_get_buff_cb);
|
|
|
|
/**
|
|
* stm32_dfsdm_release_buff_cb - unregister buffer callback
|
|
*
|
|
* @iio_dev: Handle to IIO device.
|
|
*/
|
|
int stm32_dfsdm_release_buff_cb(struct iio_dev *iio_dev)
|
|
{
|
|
struct stm32_dfsdm_adc *adc;
|
|
|
|
if (!iio_dev)
|
|
return -EINVAL;
|
|
adc = iio_priv(iio_dev);
|
|
|
|
adc->cb = NULL;
|
|
adc->cb_priv = NULL;
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_GPL(stm32_dfsdm_release_buff_cb);
|
|
|
|
static int stm32_dfsdm_single_conv(struct iio_dev *indio_dev,
|
|
const struct iio_chan_spec *chan, int *res)
|
|
{
|
|
struct stm32_dfsdm_adc *adc = iio_priv(indio_dev);
|
|
long timeout;
|
|
int ret;
|
|
|
|
reinit_completion(&adc->completion);
|
|
|
|
adc->buffer = res;
|
|
|
|
ret = stm32_dfsdm_start_dfsdm(adc->dfsdm);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
ret = regmap_update_bits(adc->dfsdm->regmap, DFSDM_CR2(adc->fl_id),
|
|
DFSDM_CR2_REOCIE_MASK, DFSDM_CR2_REOCIE(1));
|
|
if (ret < 0)
|
|
goto stop_dfsdm;
|
|
|
|
ret = stm32_dfsdm_start_conv(adc, chan, false);
|
|
if (ret < 0) {
|
|
regmap_update_bits(adc->dfsdm->regmap, DFSDM_CR2(adc->fl_id),
|
|
DFSDM_CR2_REOCIE_MASK, DFSDM_CR2_REOCIE(0));
|
|
goto stop_dfsdm;
|
|
}
|
|
|
|
timeout = wait_for_completion_interruptible_timeout(&adc->completion,
|
|
DFSDM_TIMEOUT);
|
|
|
|
/* Mask IRQ for regular conversion achievement*/
|
|
regmap_update_bits(adc->dfsdm->regmap, DFSDM_CR2(adc->fl_id),
|
|
DFSDM_CR2_REOCIE_MASK, DFSDM_CR2_REOCIE(0));
|
|
|
|
if (timeout == 0)
|
|
ret = -ETIMEDOUT;
|
|
else if (timeout < 0)
|
|
ret = timeout;
|
|
else
|
|
ret = IIO_VAL_INT;
|
|
|
|
stm32_dfsdm_stop_conv(adc, chan);
|
|
|
|
stop_dfsdm:
|
|
stm32_dfsdm_stop_dfsdm(adc->dfsdm);
|
|
|
|
return ret;
|
|
}
|
|
|
|
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);
|
|
struct stm32_dfsdm_filter *fl = &adc->dfsdm->fl_list[adc->fl_id];
|
|
struct stm32_dfsdm_channel *ch = &adc->dfsdm->ch_list[chan->channel];
|
|
unsigned int spi_freq = adc->spi_freq;
|
|
int ret = -EINVAL;
|
|
|
|
switch (mask) {
|
|
case IIO_CHAN_INFO_OVERSAMPLING_RATIO:
|
|
ret = stm32_dfsdm_set_osrs(fl, 0, val);
|
|
if (!ret)
|
|
adc->oversamp = val;
|
|
|
|
return ret;
|
|
|
|
case IIO_CHAN_INFO_SAMP_FREQ:
|
|
if (!val)
|
|
return -EINVAL;
|
|
if (ch->src != DFSDM_CHANNEL_SPI_CLOCK_EXTERNAL)
|
|
spi_freq = adc->dfsdm->spi_master_freq;
|
|
|
|
if (spi_freq % val)
|
|
dev_warn(&indio_dev->dev,
|
|
"Sampling rate not accurate (%d)\n",
|
|
spi_freq / (spi_freq / val));
|
|
|
|
ret = stm32_dfsdm_set_osrs(fl, 0, (spi_freq / val));
|
|
if (ret < 0) {
|
|
dev_err(&indio_dev->dev,
|
|
"Not able to find parameter that match!\n");
|
|
return ret;
|
|
}
|
|
adc->sample_freq = val;
|
|
|
|
return 0;
|
|
}
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
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;
|
|
|
|
switch (mask) {
|
|
case IIO_CHAN_INFO_RAW:
|
|
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);
|
|
iio_hw_consumer_disable(adc->hwc);
|
|
if (ret < 0) {
|
|
dev_err(&indio_dev->dev,
|
|
"%s: Conversion failed (channel %d)\n",
|
|
__func__, chan->channel);
|
|
return ret;
|
|
}
|
|
return IIO_VAL_INT;
|
|
|
|
case IIO_CHAN_INFO_OVERSAMPLING_RATIO:
|
|
*val = adc->oversamp;
|
|
|
|
return IIO_VAL_INT;
|
|
|
|
case IIO_CHAN_INFO_SAMP_FREQ:
|
|
*val = adc->sample_freq;
|
|
|
|
return IIO_VAL_INT;
|
|
}
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
static const struct iio_info stm32_dfsdm_info_audio = {
|
|
.hwfifo_set_watermark = stm32_dfsdm_set_watermark,
|
|
.read_raw = stm32_dfsdm_read_raw,
|
|
.write_raw = stm32_dfsdm_write_raw,
|
|
};
|
|
|
|
static const struct iio_info stm32_dfsdm_info_adc = {
|
|
.read_raw = stm32_dfsdm_read_raw,
|
|
.write_raw = stm32_dfsdm_write_raw,
|
|
};
|
|
|
|
static irqreturn_t stm32_dfsdm_irq(int irq, void *arg)
|
|
{
|
|
struct stm32_dfsdm_adc *adc = arg;
|
|
struct iio_dev *indio_dev = iio_priv_to_dev(adc);
|
|
struct regmap *regmap = adc->dfsdm->regmap;
|
|
unsigned int status, int_en;
|
|
|
|
regmap_read(regmap, DFSDM_ISR(adc->fl_id), &status);
|
|
regmap_read(regmap, DFSDM_CR2(adc->fl_id), &int_en);
|
|
|
|
if (status & DFSDM_ISR_REOCF_MASK) {
|
|
/* Read the data register clean the IRQ status */
|
|
regmap_read(regmap, DFSDM_RDATAR(adc->fl_id), adc->buffer);
|
|
complete(&adc->completion);
|
|
}
|
|
|
|
if (status & DFSDM_ISR_ROVRF_MASK) {
|
|
if (int_en & DFSDM_CR2_ROVRIE_MASK)
|
|
dev_warn(&indio_dev->dev, "Overrun detected\n");
|
|
regmap_update_bits(regmap, DFSDM_ICR(adc->fl_id),
|
|
DFSDM_ICR_CLRROVRF_MASK,
|
|
DFSDM_ICR_CLRROVRF_MASK);
|
|
}
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
/*
|
|
* Define external info for SPI Frequency and audio sampling rate that can be
|
|
* configured by ASoC driver through consumer.h API
|
|
*/
|
|
static const struct iio_chan_spec_ext_info dfsdm_adc_audio_ext_info[] = {
|
|
/* spi_clk_freq : clock freq on SPI/manchester bus used by channel */
|
|
{
|
|
.name = "spi_clk_freq",
|
|
.shared = IIO_SHARED_BY_TYPE,
|
|
.read = dfsdm_adc_audio_get_spiclk,
|
|
.write = dfsdm_adc_audio_set_spiclk,
|
|
},
|
|
{},
|
|
};
|
|
|
|
static void stm32_dfsdm_dma_release(struct iio_dev *indio_dev)
|
|
{
|
|
struct stm32_dfsdm_adc *adc = iio_priv(indio_dev);
|
|
|
|
if (adc->dma_chan) {
|
|
dma_free_coherent(adc->dma_chan->device->dev,
|
|
DFSDM_DMA_BUFFER_SIZE,
|
|
adc->rx_buf, adc->dma_buf);
|
|
dma_release_channel(adc->dma_chan);
|
|
}
|
|
}
|
|
|
|
static int stm32_dfsdm_dma_request(struct iio_dev *indio_dev)
|
|
{
|
|
struct stm32_dfsdm_adc *adc = iio_priv(indio_dev);
|
|
struct dma_slave_config config = {
|
|
.src_addr = (dma_addr_t)adc->dfsdm->phys_base +
|
|
DFSDM_RDATAR(adc->fl_id),
|
|
.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES,
|
|
};
|
|
int ret;
|
|
|
|
adc->dma_chan = dma_request_slave_channel(&indio_dev->dev, "rx");
|
|
if (!adc->dma_chan)
|
|
return -EINVAL;
|
|
|
|
adc->rx_buf = dma_alloc_coherent(adc->dma_chan->device->dev,
|
|
DFSDM_DMA_BUFFER_SIZE,
|
|
&adc->dma_buf, GFP_KERNEL);
|
|
if (!adc->rx_buf) {
|
|
ret = -ENOMEM;
|
|
goto err_release;
|
|
}
|
|
|
|
ret = dmaengine_slave_config(adc->dma_chan, &config);
|
|
if (ret)
|
|
goto err_free;
|
|
|
|
return 0;
|
|
|
|
err_free:
|
|
dma_free_coherent(adc->dma_chan->device->dev, DFSDM_DMA_BUFFER_SIZE,
|
|
adc->rx_buf, adc->dma_buf);
|
|
err_release:
|
|
dma_release_channel(adc->dma_chan);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int stm32_dfsdm_adc_chan_init_one(struct iio_dev *indio_dev,
|
|
struct iio_chan_spec *ch)
|
|
{
|
|
struct stm32_dfsdm_adc *adc = iio_priv(indio_dev);
|
|
int ret;
|
|
|
|
ret = stm32_dfsdm_channel_parse_of(adc->dfsdm, indio_dev, ch);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
ch->type = IIO_VOLTAGE;
|
|
ch->indexed = 1;
|
|
|
|
/*
|
|
* IIO_CHAN_INFO_RAW: used to compute regular conversion
|
|
* IIO_CHAN_INFO_OVERSAMPLING_RATIO: used to set oversampling
|
|
*/
|
|
ch->info_mask_separate = BIT(IIO_CHAN_INFO_RAW);
|
|
ch->info_mask_shared_by_all = BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO);
|
|
|
|
if (adc->dev_data->type == DFSDM_AUDIO) {
|
|
ch->scan_type.sign = 's';
|
|
ch->ext_info = dfsdm_adc_audio_ext_info;
|
|
} else {
|
|
ch->scan_type.sign = 'u';
|
|
}
|
|
ch->scan_type.realbits = 24;
|
|
ch->scan_type.storagebits = 32;
|
|
|
|
return stm32_dfsdm_chan_configure(adc->dfsdm,
|
|
&adc->dfsdm->ch_list[ch->channel]);
|
|
}
|
|
|
|
static int stm32_dfsdm_audio_init(struct iio_dev *indio_dev)
|
|
{
|
|
struct iio_chan_spec *ch;
|
|
struct stm32_dfsdm_adc *adc = iio_priv(indio_dev);
|
|
struct stm32_dfsdm_channel *d_ch;
|
|
int ret;
|
|
|
|
indio_dev->modes |= INDIO_BUFFER_SOFTWARE;
|
|
indio_dev->setup_ops = &stm32_dfsdm_buffer_setup_ops;
|
|
|
|
ch = devm_kzalloc(&indio_dev->dev, sizeof(*ch), GFP_KERNEL);
|
|
if (!ch)
|
|
return -ENOMEM;
|
|
|
|
ch->scan_index = 0;
|
|
|
|
ret = stm32_dfsdm_adc_chan_init_one(indio_dev, ch);
|
|
if (ret < 0) {
|
|
dev_err(&indio_dev->dev, "Channels init failed\n");
|
|
return ret;
|
|
}
|
|
ch->info_mask_separate = BIT(IIO_CHAN_INFO_SAMP_FREQ);
|
|
|
|
d_ch = &adc->dfsdm->ch_list[ch->channel];
|
|
if (d_ch->src != DFSDM_CHANNEL_SPI_CLOCK_EXTERNAL)
|
|
adc->spi_freq = adc->dfsdm->spi_master_freq;
|
|
|
|
indio_dev->num_channels = 1;
|
|
indio_dev->channels = ch;
|
|
|
|
return stm32_dfsdm_dma_request(indio_dev);
|
|
}
|
|
|
|
static int stm32_dfsdm_adc_init(struct iio_dev *indio_dev)
|
|
{
|
|
struct iio_chan_spec *ch;
|
|
struct stm32_dfsdm_adc *adc = iio_priv(indio_dev);
|
|
int num_ch;
|
|
int ret, chan_idx;
|
|
|
|
adc->oversamp = DFSDM_DEFAULT_OVERSAMPLING;
|
|
ret = stm32_dfsdm_set_osrs(&adc->dfsdm->fl_list[adc->fl_id], 0,
|
|
adc->oversamp);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
num_ch = of_property_count_u32_elems(indio_dev->dev.of_node,
|
|
"st,adc-channels");
|
|
if (num_ch < 0 || num_ch > adc->dfsdm->num_chs) {
|
|
dev_err(&indio_dev->dev, "Bad st,adc-channels\n");
|
|
return num_ch < 0 ? num_ch : -EINVAL;
|
|
}
|
|
|
|
/* Bind to SD modulator IIO device */
|
|
adc->hwc = devm_iio_hw_consumer_alloc(&indio_dev->dev);
|
|
if (IS_ERR(adc->hwc))
|
|
return -EPROBE_DEFER;
|
|
|
|
ch = devm_kcalloc(&indio_dev->dev, num_ch, sizeof(*ch),
|
|
GFP_KERNEL);
|
|
if (!ch)
|
|
return -ENOMEM;
|
|
|
|
for (chan_idx = 0; chan_idx < num_ch; chan_idx++) {
|
|
ch[chan_idx].scan_index = chan_idx;
|
|
ret = stm32_dfsdm_adc_chan_init_one(indio_dev, &ch[chan_idx]);
|
|
if (ret < 0) {
|
|
dev_err(&indio_dev->dev, "Channels init failed\n");
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
indio_dev->num_channels = num_ch;
|
|
indio_dev->channels = ch;
|
|
|
|
init_completion(&adc->completion);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct stm32_dfsdm_dev_data stm32h7_dfsdm_adc_data = {
|
|
.type = DFSDM_IIO,
|
|
.init = stm32_dfsdm_adc_init,
|
|
};
|
|
|
|
static const struct stm32_dfsdm_dev_data stm32h7_dfsdm_audio_data = {
|
|
.type = DFSDM_AUDIO,
|
|
.init = stm32_dfsdm_audio_init,
|
|
};
|
|
|
|
static const struct of_device_id stm32_dfsdm_adc_match[] = {
|
|
{
|
|
.compatible = "st,stm32-dfsdm-adc",
|
|
.data = &stm32h7_dfsdm_adc_data,
|
|
},
|
|
{
|
|
.compatible = "st,stm32-dfsdm-dmic",
|
|
.data = &stm32h7_dfsdm_audio_data,
|
|
},
|
|
{}
|
|
};
|
|
|
|
static int stm32_dfsdm_adc_probe(struct platform_device *pdev)
|
|
{
|
|
struct device *dev = &pdev->dev;
|
|
struct stm32_dfsdm_adc *adc;
|
|
struct device_node *np = dev->of_node;
|
|
const struct stm32_dfsdm_dev_data *dev_data;
|
|
struct iio_dev *iio;
|
|
char *name;
|
|
int ret, irq, val;
|
|
|
|
dev_data = of_device_get_match_data(dev);
|
|
iio = devm_iio_device_alloc(dev, sizeof(*adc));
|
|
if (!iio) {
|
|
dev_err(dev, "%s: Failed to allocate IIO\n", __func__);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
adc = iio_priv(iio);
|
|
adc->dfsdm = dev_get_drvdata(dev->parent);
|
|
|
|
iio->dev.parent = dev;
|
|
iio->dev.of_node = np;
|
|
iio->modes = INDIO_DIRECT_MODE | INDIO_BUFFER_SOFTWARE;
|
|
|
|
platform_set_drvdata(pdev, adc);
|
|
|
|
ret = of_property_read_u32(dev->of_node, "reg", &adc->fl_id);
|
|
if (ret != 0 || adc->fl_id >= adc->dfsdm->num_fls) {
|
|
dev_err(dev, "Missing or bad reg property\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
name = devm_kzalloc(dev, sizeof("dfsdm-adc0"), GFP_KERNEL);
|
|
if (!name)
|
|
return -ENOMEM;
|
|
if (dev_data->type == DFSDM_AUDIO) {
|
|
iio->info = &stm32_dfsdm_info_audio;
|
|
snprintf(name, sizeof("dfsdm-pdm0"), "dfsdm-pdm%d", adc->fl_id);
|
|
} else {
|
|
iio->info = &stm32_dfsdm_info_adc;
|
|
snprintf(name, sizeof("dfsdm-adc0"), "dfsdm-adc%d", adc->fl_id);
|
|
}
|
|
iio->name = name;
|
|
|
|
/*
|
|
* In a first step IRQs generated for channels are not treated.
|
|
* So IRQ associated to filter instance 0 is dedicated to the Filter 0.
|
|
*/
|
|
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 = of_property_read_u32(dev->of_node, "st,filter-order", &val);
|
|
if (ret < 0) {
|
|
dev_err(dev, "Failed to set filter order\n");
|
|
return ret;
|
|
}
|
|
|
|
adc->dfsdm->fl_list[adc->fl_id].ford = val;
|
|
|
|
ret = of_property_read_u32(dev->of_node, "st,filter0-sync", &val);
|
|
if (!ret)
|
|
adc->dfsdm->fl_list[adc->fl_id].sync_mode = val;
|
|
|
|
adc->dev_data = dev_data;
|
|
ret = dev_data->init(iio);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
ret = iio_device_register(iio);
|
|
if (ret < 0)
|
|
goto err_cleanup;
|
|
|
|
if (dev_data->type == DFSDM_AUDIO) {
|
|
ret = of_platform_populate(np, NULL, NULL, dev);
|
|
if (ret < 0) {
|
|
dev_err(dev, "Failed to find an audio DAI\n");
|
|
goto err_unregister;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
|
|
err_unregister:
|
|
iio_device_unregister(iio);
|
|
err_cleanup:
|
|
stm32_dfsdm_dma_release(iio);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int stm32_dfsdm_adc_remove(struct platform_device *pdev)
|
|
{
|
|
struct stm32_dfsdm_adc *adc = platform_get_drvdata(pdev);
|
|
struct iio_dev *indio_dev = iio_priv_to_dev(adc);
|
|
|
|
if (adc->dev_data->type == DFSDM_AUDIO)
|
|
of_platform_depopulate(&pdev->dev);
|
|
iio_device_unregister(indio_dev);
|
|
stm32_dfsdm_dma_release(indio_dev);
|
|
|
|
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 <arnaud.pouliquen@st.com>");
|
|
MODULE_LICENSE("GPL v2");
|