iio: adc: ad7091r: Allow users to configure device events

AD7091R-5 devices are supported by the ad7091r-5 driver together with
the ad7091r-base driver. Those drivers declared iio events for notifying
user space when ADC readings fall bellow the thresholds of low limit
registers or above the values set in high limit registers.
However, to configure iio events and their thresholds, a set of callback
functions must be implemented and those were not present until now.
The consequence of trying to configure ad7091r-5 events without the
proper callback functions was a null pointer dereference in the kernel
because the pointers to the callback functions were not set.

Implement event configuration callbacks allowing users to read/write
event thresholds and enable/disable event generation.

Since the event spec structs are generic to AD7091R devices, also move
those from the ad7091r-5 driver the base driver so they can be reused
when support for ad7091r-2/-4/-8 be added.

Fixes: ca69300173 ("iio: adc: Add support for AD7091R5 ADC")
Suggested-by: David Lechner <dlechner@baylibre.com>
Signed-off-by: Marcelo Schmitt <marcelo.schmitt@analog.com>
Link: https://lore.kernel.org/r/59552d3548dabd56adc3107b7b4869afee2b0c3c.1703013352.git.marcelo.schmitt1@gmail.com
Cc: <Stable@vger.kernel.org>
Signed-off-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>
This commit is contained in:
Marcelo Schmitt 2023-12-19 17:26:01 -03:00 committed by Jonathan Cameron
parent cdf3ecb0d8
commit 020e71c7ff
3 changed files with 166 additions and 24 deletions

View File

@ -6,6 +6,7 @@
*/
#include <linux/bitops.h>
#include <linux/bitfield.h>
#include <linux/iio/events.h>
#include <linux/iio/iio.h>
#include <linux/interrupt.h>
@ -50,6 +51,27 @@ struct ad7091r_state {
struct mutex lock; /*lock to prevent concurent reads */
};
const struct iio_event_spec ad7091r_events[] = {
{
.type = IIO_EV_TYPE_THRESH,
.dir = IIO_EV_DIR_RISING,
.mask_separate = BIT(IIO_EV_INFO_VALUE) |
BIT(IIO_EV_INFO_ENABLE),
},
{
.type = IIO_EV_TYPE_THRESH,
.dir = IIO_EV_DIR_FALLING,
.mask_separate = BIT(IIO_EV_INFO_VALUE) |
BIT(IIO_EV_INFO_ENABLE),
},
{
.type = IIO_EV_TYPE_THRESH,
.dir = IIO_EV_DIR_EITHER,
.mask_separate = BIT(IIO_EV_INFO_HYSTERESIS),
},
};
EXPORT_SYMBOL_NS_GPL(ad7091r_events, IIO_AD7091R);
static int ad7091r_set_mode(struct ad7091r_state *st, enum ad7091r_mode mode)
{
int ret, conf;
@ -169,8 +191,142 @@ unlock:
return ret;
}
static int ad7091r_read_event_config(struct iio_dev *indio_dev,
const struct iio_chan_spec *chan,
enum iio_event_type type,
enum iio_event_direction dir)
{
struct ad7091r_state *st = iio_priv(indio_dev);
int val, ret;
switch (dir) {
case IIO_EV_DIR_RISING:
ret = regmap_read(st->map,
AD7091R_REG_CH_HIGH_LIMIT(chan->channel),
&val);
if (ret)
return ret;
return val != AD7091R_HIGH_LIMIT;
case IIO_EV_DIR_FALLING:
ret = regmap_read(st->map,
AD7091R_REG_CH_LOW_LIMIT(chan->channel),
&val);
if (ret)
return ret;
return val != AD7091R_LOW_LIMIT;
default:
return -EINVAL;
}
}
static int ad7091r_write_event_config(struct iio_dev *indio_dev,
const struct iio_chan_spec *chan,
enum iio_event_type type,
enum iio_event_direction dir, int state)
{
struct ad7091r_state *st = iio_priv(indio_dev);
if (state) {
return regmap_set_bits(st->map, AD7091R_REG_CONF,
AD7091R_REG_CONF_ALERT_EN);
} else {
/*
* Set thresholds either to 0 or to 2^12 - 1 as appropriate to
* prevent alerts and thus disable event generation.
*/
switch (dir) {
case IIO_EV_DIR_RISING:
return regmap_write(st->map,
AD7091R_REG_CH_HIGH_LIMIT(chan->channel),
AD7091R_HIGH_LIMIT);
case IIO_EV_DIR_FALLING:
return regmap_write(st->map,
AD7091R_REG_CH_LOW_LIMIT(chan->channel),
AD7091R_LOW_LIMIT);
default:
return -EINVAL;
}
}
}
static int ad7091r_read_event_value(struct iio_dev *indio_dev,
const struct iio_chan_spec *chan,
enum iio_event_type type,
enum iio_event_direction dir,
enum iio_event_info info, int *val, int *val2)
{
struct ad7091r_state *st = iio_priv(indio_dev);
int ret;
switch (info) {
case IIO_EV_INFO_VALUE:
switch (dir) {
case IIO_EV_DIR_RISING:
ret = regmap_read(st->map,
AD7091R_REG_CH_HIGH_LIMIT(chan->channel),
val);
if (ret)
return ret;
return IIO_VAL_INT;
case IIO_EV_DIR_FALLING:
ret = regmap_read(st->map,
AD7091R_REG_CH_LOW_LIMIT(chan->channel),
val);
if (ret)
return ret;
return IIO_VAL_INT;
default:
return -EINVAL;
}
case IIO_EV_INFO_HYSTERESIS:
ret = regmap_read(st->map,
AD7091R_REG_CH_HYSTERESIS(chan->channel),
val);
if (ret)
return ret;
return IIO_VAL_INT;
default:
return -EINVAL;
}
}
static int ad7091r_write_event_value(struct iio_dev *indio_dev,
const struct iio_chan_spec *chan,
enum iio_event_type type,
enum iio_event_direction dir,
enum iio_event_info info, int val, int val2)
{
struct ad7091r_state *st = iio_priv(indio_dev);
switch (info) {
case IIO_EV_INFO_VALUE:
switch (dir) {
case IIO_EV_DIR_RISING:
return regmap_write(st->map,
AD7091R_REG_CH_HIGH_LIMIT(chan->channel),
val);
case IIO_EV_DIR_FALLING:
return regmap_write(st->map,
AD7091R_REG_CH_LOW_LIMIT(chan->channel),
val);
default:
return -EINVAL;
}
case IIO_EV_INFO_HYSTERESIS:
return regmap_write(st->map,
AD7091R_REG_CH_HYSTERESIS(chan->channel),
val);
default:
return -EINVAL;
}
}
static const struct iio_info ad7091r_info = {
.read_raw = ad7091r_read_raw,
.read_event_config = &ad7091r_read_event_config,
.write_event_config = &ad7091r_write_event_config,
.read_event_value = &ad7091r_read_event_value,
.write_event_value = &ad7091r_write_event_value,
};
static irqreturn_t ad7091r_event_handler(int irq, void *private)

View File

@ -8,6 +8,10 @@
#ifndef __DRIVERS_IIO_ADC_AD7091R_BASE_H__
#define __DRIVERS_IIO_ADC_AD7091R_BASE_H__
/* AD7091R_REG_CH_LIMIT */
#define AD7091R_HIGH_LIMIT 0xFFF
#define AD7091R_LOW_LIMIT 0x0
struct device;
struct ad7091r_state;
@ -17,6 +21,8 @@ struct ad7091r_chip_info {
unsigned int vref_mV;
};
extern const struct iio_event_spec ad7091r_events[3];
extern const struct regmap_config ad7091r_regmap_config;
int ad7091r_probe(struct device *dev, const char *name,

View File

@ -12,26 +12,6 @@
#include "ad7091r-base.h"
static const struct iio_event_spec ad7091r5_events[] = {
{
.type = IIO_EV_TYPE_THRESH,
.dir = IIO_EV_DIR_RISING,
.mask_separate = BIT(IIO_EV_INFO_VALUE) |
BIT(IIO_EV_INFO_ENABLE),
},
{
.type = IIO_EV_TYPE_THRESH,
.dir = IIO_EV_DIR_FALLING,
.mask_separate = BIT(IIO_EV_INFO_VALUE) |
BIT(IIO_EV_INFO_ENABLE),
},
{
.type = IIO_EV_TYPE_THRESH,
.dir = IIO_EV_DIR_EITHER,
.mask_separate = BIT(IIO_EV_INFO_HYSTERESIS),
},
};
#define AD7091R_CHANNEL(idx, bits, ev, num_ev) { \
.type = IIO_VOLTAGE, \
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
@ -44,10 +24,10 @@ static const struct iio_event_spec ad7091r5_events[] = {
.scan_type.realbits = bits, \
}
static const struct iio_chan_spec ad7091r5_channels_irq[] = {
AD7091R_CHANNEL(0, 12, ad7091r5_events, ARRAY_SIZE(ad7091r5_events)),
AD7091R_CHANNEL(1, 12, ad7091r5_events, ARRAY_SIZE(ad7091r5_events)),
AD7091R_CHANNEL(2, 12, ad7091r5_events, ARRAY_SIZE(ad7091r5_events)),
AD7091R_CHANNEL(3, 12, ad7091r5_events, ARRAY_SIZE(ad7091r5_events)),
AD7091R_CHANNEL(0, 12, ad7091r_events, ARRAY_SIZE(ad7091r_events)),
AD7091R_CHANNEL(1, 12, ad7091r_events, ARRAY_SIZE(ad7091r_events)),
AD7091R_CHANNEL(2, 12, ad7091r_events, ARRAY_SIZE(ad7091r_events)),
AD7091R_CHANNEL(3, 12, ad7091r_events, ARRAY_SIZE(ad7091r_events)),
};
static const struct iio_chan_spec ad7091r5_channels_noirq[] = {