mirror of
https://github.com/torvalds/linux.git
synced 2024-11-01 13:03:25 +01:00
5f60d5f6bb
asm/unaligned.h is always an include of asm-generic/unaligned.h; might as well move that thing to linux/unaligned.h and include that - there's nothing arch-specific in that header. auto-generated by the following: for i in `git grep -l -w asm/unaligned.h`; do sed -i -e "s/asm\/unaligned.h/linux\/unaligned.h/" $i done for i in `git grep -l -w asm-generic/unaligned.h`; do sed -i -e "s/asm-generic\/unaligned.h/linux\/unaligned.h/" $i done git mv include/asm-generic/unaligned.h include/linux/unaligned.h git mv tools/include/asm-generic/unaligned.h tools/include/linux/unaligned.h sed -i -e "/unaligned.h/d" include/asm-generic/Kbuild sed -i -e "s/__ASM_GENERIC/__LINUX/" include/linux/unaligned.h tools/include/linux/unaligned.h
957 lines
23 KiB
C
957 lines
23 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* Driver for Murata IRS-D200 PIR sensor.
|
|
*
|
|
* Copyright (C) 2023 Axis Communications AB
|
|
*/
|
|
|
|
#include <linux/unaligned.h>
|
|
#include <linux/bitfield.h>
|
|
#include <linux/i2c.h>
|
|
#include <linux/module.h>
|
|
#include <linux/regmap.h>
|
|
|
|
#include <linux/iio/buffer.h>
|
|
#include <linux/iio/events.h>
|
|
#include <linux/iio/iio.h>
|
|
#include <linux/iio/trigger.h>
|
|
#include <linux/iio/trigger_consumer.h>
|
|
#include <linux/iio/triggered_buffer.h>
|
|
#include <linux/iio/types.h>
|
|
|
|
#define IRS_DRV_NAME "irsd200"
|
|
|
|
/* Registers. */
|
|
#define IRS_REG_OP 0x00 /* Operation mode. */
|
|
#define IRS_REG_DATA_LO 0x02 /* Sensor data LSB. */
|
|
#define IRS_REG_DATA_HI 0x03 /* Sensor data MSB. */
|
|
#define IRS_REG_STATUS 0x04 /* Interrupt status. */
|
|
#define IRS_REG_COUNT 0x05 /* Count of exceeding threshold. */
|
|
#define IRS_REG_DATA_RATE 0x06 /* Output data rate. */
|
|
#define IRS_REG_FILTER 0x07 /* High-pass and low-pass filter. */
|
|
#define IRS_REG_INTR 0x09 /* Interrupt mode. */
|
|
#define IRS_REG_NR_COUNT 0x0a /* Number of counts before interrupt. */
|
|
#define IRS_REG_THR_HI 0x0b /* Upper threshold. */
|
|
#define IRS_REG_THR_LO 0x0c /* Lower threshold. */
|
|
#define IRS_REG_TIMER_LO 0x0d /* Timer setting LSB. */
|
|
#define IRS_REG_TIMER_HI 0x0e /* Timer setting MSB. */
|
|
|
|
/* Interrupt status bits. */
|
|
#define IRS_INTR_DATA 0 /* Data update. */
|
|
#define IRS_INTR_TIMER 1 /* Timer expiration. */
|
|
#define IRS_INTR_COUNT_THR_AND 2 /* Count "AND" threshold. */
|
|
#define IRS_INTR_COUNT_THR_OR 3 /* Count "OR" threshold. */
|
|
|
|
/* Operation states. */
|
|
#define IRS_OP_ACTIVE 0x00
|
|
#define IRS_OP_SLEEP 0x01
|
|
|
|
/*
|
|
* Quantization scale value for threshold. Used for conversion from/to register
|
|
* value.
|
|
*/
|
|
#define IRS_THR_QUANT_SCALE 128
|
|
|
|
#define IRS_UPPER_COUNT(count) FIELD_GET(GENMASK(7, 4), count)
|
|
#define IRS_LOWER_COUNT(count) FIELD_GET(GENMASK(3, 0), count)
|
|
|
|
/* Index corresponds to the value of IRS_REG_DATA_RATE register. */
|
|
static const int irsd200_data_rates[] = {
|
|
50,
|
|
100,
|
|
};
|
|
|
|
/* Index corresponds to the (field) value of IRS_REG_FILTER register. */
|
|
static const unsigned int irsd200_lp_filter_freq[] = {
|
|
10,
|
|
7,
|
|
};
|
|
|
|
/*
|
|
* Index corresponds to the (field) value of IRS_REG_FILTER register. Note that
|
|
* this represents a fractional value (e.g the first value corresponds to 3 / 10
|
|
* = 0.3 Hz).
|
|
*/
|
|
static const unsigned int irsd200_hp_filter_freq[][2] = {
|
|
{ 3, 10 },
|
|
{ 5, 10 },
|
|
};
|
|
|
|
/* Register fields. */
|
|
enum irsd200_regfield {
|
|
/* Data interrupt. */
|
|
IRS_REGF_INTR_DATA,
|
|
/* Timer interrupt. */
|
|
IRS_REGF_INTR_TIMER,
|
|
/* AND count threshold interrupt. */
|
|
IRS_REGF_INTR_COUNT_THR_AND,
|
|
/* OR count threshold interrupt. */
|
|
IRS_REGF_INTR_COUNT_THR_OR,
|
|
|
|
/* Low-pass filter frequency. */
|
|
IRS_REGF_LP_FILTER,
|
|
/* High-pass filter frequency. */
|
|
IRS_REGF_HP_FILTER,
|
|
|
|
/* Sentinel value. */
|
|
IRS_REGF_MAX
|
|
};
|
|
|
|
static const struct reg_field irsd200_regfields[] = {
|
|
[IRS_REGF_INTR_DATA] =
|
|
REG_FIELD(IRS_REG_INTR, IRS_INTR_DATA, IRS_INTR_DATA),
|
|
[IRS_REGF_INTR_TIMER] =
|
|
REG_FIELD(IRS_REG_INTR, IRS_INTR_TIMER, IRS_INTR_TIMER),
|
|
[IRS_REGF_INTR_COUNT_THR_AND] = REG_FIELD(
|
|
IRS_REG_INTR, IRS_INTR_COUNT_THR_AND, IRS_INTR_COUNT_THR_AND),
|
|
[IRS_REGF_INTR_COUNT_THR_OR] = REG_FIELD(
|
|
IRS_REG_INTR, IRS_INTR_COUNT_THR_OR, IRS_INTR_COUNT_THR_OR),
|
|
|
|
[IRS_REGF_LP_FILTER] = REG_FIELD(IRS_REG_FILTER, 1, 1),
|
|
[IRS_REGF_HP_FILTER] = REG_FIELD(IRS_REG_FILTER, 0, 0),
|
|
};
|
|
|
|
static const struct regmap_config irsd200_regmap_config = {
|
|
.reg_bits = 8,
|
|
.val_bits = 8,
|
|
.max_register = IRS_REG_TIMER_HI,
|
|
};
|
|
|
|
struct irsd200_data {
|
|
struct regmap *regmap;
|
|
struct regmap_field *regfields[IRS_REGF_MAX];
|
|
struct device *dev;
|
|
};
|
|
|
|
static int irsd200_setup(struct irsd200_data *data)
|
|
{
|
|
unsigned int val;
|
|
int ret;
|
|
|
|
/* Disable all interrupt sources. */
|
|
ret = regmap_write(data->regmap, IRS_REG_INTR, 0);
|
|
if (ret) {
|
|
dev_err(data->dev, "Could not set interrupt sources (%d)\n",
|
|
ret);
|
|
return ret;
|
|
}
|
|
|
|
/* Set operation to active. */
|
|
ret = regmap_write(data->regmap, IRS_REG_OP, IRS_OP_ACTIVE);
|
|
if (ret) {
|
|
dev_err(data->dev, "Could not set operation mode (%d)\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
/* Clear threshold count. */
|
|
ret = regmap_read(data->regmap, IRS_REG_COUNT, &val);
|
|
if (ret) {
|
|
dev_err(data->dev, "Could not clear threshold count (%d)\n",
|
|
ret);
|
|
return ret;
|
|
}
|
|
|
|
/* Clear status. */
|
|
ret = regmap_write(data->regmap, IRS_REG_STATUS, 0x0f);
|
|
if (ret) {
|
|
dev_err(data->dev, "Could not clear status (%d)\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int irsd200_read_threshold(struct irsd200_data *data,
|
|
enum iio_event_direction dir, int *val)
|
|
{
|
|
unsigned int regval;
|
|
unsigned int reg;
|
|
int scale;
|
|
int ret;
|
|
|
|
/* Set quantization scale. */
|
|
if (dir == IIO_EV_DIR_RISING) {
|
|
scale = IRS_THR_QUANT_SCALE;
|
|
reg = IRS_REG_THR_HI;
|
|
} else if (dir == IIO_EV_DIR_FALLING) {
|
|
scale = -IRS_THR_QUANT_SCALE;
|
|
reg = IRS_REG_THR_LO;
|
|
} else {
|
|
return -EINVAL;
|
|
}
|
|
|
|
ret = regmap_read(data->regmap, reg, ®val);
|
|
if (ret) {
|
|
dev_err(data->dev, "Could not read threshold (%d)\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
*val = ((int)regval) * scale;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int irsd200_write_threshold(struct irsd200_data *data,
|
|
enum iio_event_direction dir, int val)
|
|
{
|
|
unsigned int regval;
|
|
unsigned int reg;
|
|
int scale;
|
|
int ret;
|
|
|
|
/* Set quantization scale. */
|
|
if (dir == IIO_EV_DIR_RISING) {
|
|
if (val < 0)
|
|
return -ERANGE;
|
|
|
|
scale = IRS_THR_QUANT_SCALE;
|
|
reg = IRS_REG_THR_HI;
|
|
} else if (dir == IIO_EV_DIR_FALLING) {
|
|
if (val > 0)
|
|
return -ERANGE;
|
|
|
|
scale = -IRS_THR_QUANT_SCALE;
|
|
reg = IRS_REG_THR_LO;
|
|
} else {
|
|
return -EINVAL;
|
|
}
|
|
|
|
regval = val / scale;
|
|
|
|
if (regval >= BIT(8))
|
|
return -ERANGE;
|
|
|
|
ret = regmap_write(data->regmap, reg, regval);
|
|
if (ret) {
|
|
dev_err(data->dev, "Could not write threshold (%d)\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int irsd200_read_data(struct irsd200_data *data, s16 *val)
|
|
{
|
|
__le16 buf;
|
|
int ret;
|
|
|
|
ret = regmap_bulk_read(data->regmap, IRS_REG_DATA_LO, &buf,
|
|
sizeof(buf));
|
|
if (ret) {
|
|
dev_err(data->dev, "Could not bulk read data (%d)\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
*val = le16_to_cpu(buf);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int irsd200_read_data_rate(struct irsd200_data *data, int *val)
|
|
{
|
|
unsigned int regval;
|
|
int ret;
|
|
|
|
ret = regmap_read(data->regmap, IRS_REG_DATA_RATE, ®val);
|
|
if (ret) {
|
|
dev_err(data->dev, "Could not read data rate (%d)\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
if (regval >= ARRAY_SIZE(irsd200_data_rates))
|
|
return -ERANGE;
|
|
|
|
*val = irsd200_data_rates[regval];
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int irsd200_write_data_rate(struct irsd200_data *data, int val)
|
|
{
|
|
size_t idx;
|
|
int ret;
|
|
|
|
for (idx = 0; idx < ARRAY_SIZE(irsd200_data_rates); ++idx) {
|
|
if (irsd200_data_rates[idx] == val)
|
|
break;
|
|
}
|
|
|
|
if (idx == ARRAY_SIZE(irsd200_data_rates))
|
|
return -ERANGE;
|
|
|
|
ret = regmap_write(data->regmap, IRS_REG_DATA_RATE, idx);
|
|
if (ret) {
|
|
dev_err(data->dev, "Could not write data rate (%d)\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Data sheet says the device needs 3 seconds of settling time. The
|
|
* device operates normally during this period though. This is more of a
|
|
* "guarantee" than trying to prevent other user space reads/writes.
|
|
*/
|
|
ssleep(3);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int irsd200_read_timer(struct irsd200_data *data, int *val, int *val2)
|
|
{
|
|
__le16 buf;
|
|
int ret;
|
|
|
|
ret = regmap_bulk_read(data->regmap, IRS_REG_TIMER_LO, &buf,
|
|
sizeof(buf));
|
|
if (ret) {
|
|
dev_err(data->dev, "Could not bulk read timer (%d)\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
ret = irsd200_read_data_rate(data, val2);
|
|
if (ret)
|
|
return ret;
|
|
|
|
*val = le16_to_cpu(buf);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int irsd200_write_timer(struct irsd200_data *data, int val, int val2)
|
|
{
|
|
unsigned int regval;
|
|
int data_rate;
|
|
__le16 buf;
|
|
int ret;
|
|
|
|
if (val < 0 || val2 < 0)
|
|
return -ERANGE;
|
|
|
|
ret = irsd200_read_data_rate(data, &data_rate);
|
|
if (ret)
|
|
return ret;
|
|
|
|
/* Quantize from seconds. */
|
|
regval = val * data_rate + (val2 * data_rate) / 1000000;
|
|
|
|
/* Value is 10 bits. */
|
|
if (regval >= BIT(10))
|
|
return -ERANGE;
|
|
|
|
buf = cpu_to_le16((u16)regval);
|
|
|
|
ret = regmap_bulk_write(data->regmap, IRS_REG_TIMER_LO, &buf,
|
|
sizeof(buf));
|
|
if (ret) {
|
|
dev_err(data->dev, "Could not bulk write timer (%d)\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int irsd200_read_nr_count(struct irsd200_data *data, int *val)
|
|
{
|
|
unsigned int regval;
|
|
int ret;
|
|
|
|
ret = regmap_read(data->regmap, IRS_REG_NR_COUNT, ®val);
|
|
if (ret) {
|
|
dev_err(data->dev, "Could not read nr count (%d)\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
*val = regval;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int irsd200_write_nr_count(struct irsd200_data *data, int val)
|
|
{
|
|
unsigned int regval;
|
|
int ret;
|
|
|
|
/* A value of zero means that IRS_REG_STATUS is never set. */
|
|
if (val <= 0 || val >= 8)
|
|
return -ERANGE;
|
|
|
|
regval = val;
|
|
|
|
if (regval >= 2) {
|
|
/*
|
|
* According to the data sheet, timer must be also set in this
|
|
* case (i.e. be non-zero). Check and enforce that.
|
|
*/
|
|
ret = irsd200_read_timer(data, &val, &val);
|
|
if (ret)
|
|
return ret;
|
|
|
|
if (val == 0) {
|
|
dev_err(data->dev,
|
|
"Timer must be non-zero when nr count is %u\n",
|
|
regval);
|
|
return -EPERM;
|
|
}
|
|
}
|
|
|
|
ret = regmap_write(data->regmap, IRS_REG_NR_COUNT, regval);
|
|
if (ret) {
|
|
dev_err(data->dev, "Could not write nr count (%d)\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int irsd200_read_lp_filter(struct irsd200_data *data, int *val)
|
|
{
|
|
unsigned int regval;
|
|
int ret;
|
|
|
|
ret = regmap_field_read(data->regfields[IRS_REGF_LP_FILTER], ®val);
|
|
if (ret) {
|
|
dev_err(data->dev, "Could not read lp filter frequency (%d)\n",
|
|
ret);
|
|
return ret;
|
|
}
|
|
|
|
*val = irsd200_lp_filter_freq[regval];
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int irsd200_write_lp_filter(struct irsd200_data *data, int val)
|
|
{
|
|
size_t idx;
|
|
int ret;
|
|
|
|
for (idx = 0; idx < ARRAY_SIZE(irsd200_lp_filter_freq); ++idx) {
|
|
if (irsd200_lp_filter_freq[idx] == val)
|
|
break;
|
|
}
|
|
|
|
if (idx == ARRAY_SIZE(irsd200_lp_filter_freq))
|
|
return -ERANGE;
|
|
|
|
ret = regmap_field_write(data->regfields[IRS_REGF_LP_FILTER], idx);
|
|
if (ret) {
|
|
dev_err(data->dev, "Could not write lp filter frequency (%d)\n",
|
|
ret);
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int irsd200_read_hp_filter(struct irsd200_data *data, int *val,
|
|
int *val2)
|
|
{
|
|
unsigned int regval;
|
|
int ret;
|
|
|
|
ret = regmap_field_read(data->regfields[IRS_REGF_HP_FILTER], ®val);
|
|
if (ret) {
|
|
dev_err(data->dev, "Could not read hp filter frequency (%d)\n",
|
|
ret);
|
|
return ret;
|
|
}
|
|
|
|
*val = irsd200_hp_filter_freq[regval][0];
|
|
*val2 = irsd200_hp_filter_freq[regval][1];
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int irsd200_write_hp_filter(struct irsd200_data *data, int val, int val2)
|
|
{
|
|
size_t idx;
|
|
int ret;
|
|
|
|
/* Truncate fractional part to one digit. */
|
|
val2 /= 100000;
|
|
|
|
for (idx = 0; idx < ARRAY_SIZE(irsd200_hp_filter_freq); ++idx) {
|
|
if (irsd200_hp_filter_freq[idx][0] == val2)
|
|
break;
|
|
}
|
|
|
|
if (idx == ARRAY_SIZE(irsd200_hp_filter_freq) || val != 0)
|
|
return -ERANGE;
|
|
|
|
ret = regmap_field_write(data->regfields[IRS_REGF_HP_FILTER], idx);
|
|
if (ret) {
|
|
dev_err(data->dev, "Could not write hp filter frequency (%d)\n",
|
|
ret);
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int irsd200_read_raw(struct iio_dev *indio_dev,
|
|
struct iio_chan_spec const *chan, int *val,
|
|
int *val2, long mask)
|
|
{
|
|
struct irsd200_data *data = iio_priv(indio_dev);
|
|
int ret;
|
|
s16 buf;
|
|
|
|
switch (mask) {
|
|
case IIO_CHAN_INFO_RAW:
|
|
ret = irsd200_read_data(data, &buf);
|
|
if (ret)
|
|
return ret;
|
|
|
|
*val = buf;
|
|
return IIO_VAL_INT;
|
|
case IIO_CHAN_INFO_SAMP_FREQ:
|
|
ret = irsd200_read_data_rate(data, val);
|
|
if (ret)
|
|
return ret;
|
|
|
|
return IIO_VAL_INT;
|
|
case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY:
|
|
ret = irsd200_read_lp_filter(data, val);
|
|
if (ret)
|
|
return ret;
|
|
|
|
return IIO_VAL_INT;
|
|
case IIO_CHAN_INFO_HIGH_PASS_FILTER_3DB_FREQUENCY:
|
|
ret = irsd200_read_hp_filter(data, val, val2);
|
|
if (ret)
|
|
return ret;
|
|
|
|
return IIO_VAL_FRACTIONAL;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
static int irsd200_read_avail(struct iio_dev *indio_dev,
|
|
struct iio_chan_spec const *chan,
|
|
const int **vals, int *type, int *length,
|
|
long mask)
|
|
{
|
|
switch (mask) {
|
|
case IIO_CHAN_INFO_SAMP_FREQ:
|
|
*vals = irsd200_data_rates;
|
|
*type = IIO_VAL_INT;
|
|
*length = ARRAY_SIZE(irsd200_data_rates);
|
|
return IIO_AVAIL_LIST;
|
|
case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY:
|
|
*vals = irsd200_lp_filter_freq;
|
|
*type = IIO_VAL_INT;
|
|
*length = ARRAY_SIZE(irsd200_lp_filter_freq);
|
|
return IIO_AVAIL_LIST;
|
|
case IIO_CHAN_INFO_HIGH_PASS_FILTER_3DB_FREQUENCY:
|
|
*vals = (int *)irsd200_hp_filter_freq;
|
|
*type = IIO_VAL_FRACTIONAL;
|
|
*length = 2 * ARRAY_SIZE(irsd200_hp_filter_freq);
|
|
return IIO_AVAIL_LIST;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
static int irsd200_write_raw(struct iio_dev *indio_dev,
|
|
struct iio_chan_spec const *chan, int val,
|
|
int val2, long mask)
|
|
{
|
|
struct irsd200_data *data = iio_priv(indio_dev);
|
|
|
|
switch (mask) {
|
|
case IIO_CHAN_INFO_SAMP_FREQ:
|
|
return irsd200_write_data_rate(data, val);
|
|
case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY:
|
|
return irsd200_write_lp_filter(data, val);
|
|
case IIO_CHAN_INFO_HIGH_PASS_FILTER_3DB_FREQUENCY:
|
|
return irsd200_write_hp_filter(data, val, val2);
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
static int irsd200_read_event(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 irsd200_data *data = iio_priv(indio_dev);
|
|
int ret;
|
|
|
|
switch (info) {
|
|
case IIO_EV_INFO_VALUE:
|
|
ret = irsd200_read_threshold(data, dir, val);
|
|
if (ret)
|
|
return ret;
|
|
|
|
return IIO_VAL_INT;
|
|
case IIO_EV_INFO_RUNNING_PERIOD:
|
|
ret = irsd200_read_timer(data, val, val2);
|
|
if (ret)
|
|
return ret;
|
|
|
|
return IIO_VAL_FRACTIONAL;
|
|
case IIO_EV_INFO_RUNNING_COUNT:
|
|
ret = irsd200_read_nr_count(data, val);
|
|
if (ret)
|
|
return ret;
|
|
|
|
return IIO_VAL_INT;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
static int irsd200_write_event(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 irsd200_data *data = iio_priv(indio_dev);
|
|
|
|
switch (info) {
|
|
case IIO_EV_INFO_VALUE:
|
|
return irsd200_write_threshold(data, dir, val);
|
|
case IIO_EV_INFO_RUNNING_PERIOD:
|
|
return irsd200_write_timer(data, val, val2);
|
|
case IIO_EV_INFO_RUNNING_COUNT:
|
|
return irsd200_write_nr_count(data, val);
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
static int irsd200_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 irsd200_data *data = iio_priv(indio_dev);
|
|
unsigned int val;
|
|
int ret;
|
|
|
|
switch (type) {
|
|
case IIO_EV_TYPE_THRESH:
|
|
ret = regmap_field_read(
|
|
data->regfields[IRS_REGF_INTR_COUNT_THR_OR], &val);
|
|
if (ret)
|
|
return ret;
|
|
|
|
return val;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
static int irsd200_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 irsd200_data *data = iio_priv(indio_dev);
|
|
unsigned int tmp;
|
|
int ret;
|
|
|
|
switch (type) {
|
|
case IIO_EV_TYPE_THRESH:
|
|
/* Clear the count register (by reading from it). */
|
|
ret = regmap_read(data->regmap, IRS_REG_COUNT, &tmp);
|
|
if (ret)
|
|
return ret;
|
|
|
|
return regmap_field_write(
|
|
data->regfields[IRS_REGF_INTR_COUNT_THR_OR], !!state);
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
static irqreturn_t irsd200_irq_thread(int irq, void *dev_id)
|
|
{
|
|
struct iio_dev *indio_dev = dev_id;
|
|
struct irsd200_data *data = iio_priv(indio_dev);
|
|
enum iio_event_direction dir;
|
|
unsigned int lower_count;
|
|
unsigned int upper_count;
|
|
unsigned int status = 0;
|
|
unsigned int source = 0;
|
|
unsigned int clear = 0;
|
|
unsigned int count = 0;
|
|
int ret;
|
|
|
|
ret = regmap_read(data->regmap, IRS_REG_INTR, &source);
|
|
if (ret) {
|
|
dev_err(data->dev, "Could not read interrupt source (%d)\n",
|
|
ret);
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
ret = regmap_read(data->regmap, IRS_REG_STATUS, &status);
|
|
if (ret) {
|
|
dev_err(data->dev, "Could not acknowledge interrupt (%d)\n",
|
|
ret);
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
if (status & BIT(IRS_INTR_DATA) && iio_buffer_enabled(indio_dev)) {
|
|
iio_trigger_poll_nested(indio_dev->trig);
|
|
clear |= BIT(IRS_INTR_DATA);
|
|
}
|
|
|
|
if (status & BIT(IRS_INTR_COUNT_THR_OR) &&
|
|
source & BIT(IRS_INTR_COUNT_THR_OR)) {
|
|
/*
|
|
* The register value resets to zero after reading. We therefore
|
|
* need to read once and manually extract the lower and upper
|
|
* count register fields.
|
|
*/
|
|
ret = regmap_read(data->regmap, IRS_REG_COUNT, &count);
|
|
if (ret)
|
|
dev_err(data->dev, "Could not read count (%d)\n", ret);
|
|
|
|
upper_count = IRS_UPPER_COUNT(count);
|
|
lower_count = IRS_LOWER_COUNT(count);
|
|
|
|
/*
|
|
* We only check the OR mode to be able to push events for
|
|
* rising and falling thresholds. AND mode is covered when both
|
|
* upper and lower count is non-zero, and is signaled with
|
|
* IIO_EV_DIR_EITHER.
|
|
*/
|
|
if (upper_count && !lower_count)
|
|
dir = IIO_EV_DIR_RISING;
|
|
else if (!upper_count && lower_count)
|
|
dir = IIO_EV_DIR_FALLING;
|
|
else
|
|
dir = IIO_EV_DIR_EITHER;
|
|
|
|
iio_push_event(indio_dev,
|
|
IIO_UNMOD_EVENT_CODE(IIO_PROXIMITY, 0,
|
|
IIO_EV_TYPE_THRESH, dir),
|
|
iio_get_time_ns(indio_dev));
|
|
|
|
/*
|
|
* The OR mode will always trigger when the AND mode does, but
|
|
* not vice versa. However, it seems like the AND bit needs to
|
|
* be cleared if data capture _and_ threshold count interrupts
|
|
* are desirable, even though it hasn't explicitly been selected
|
|
* (with IRS_REG_INTR). Either way, it doesn't hurt...
|
|
*/
|
|
clear |= BIT(IRS_INTR_COUNT_THR_OR) |
|
|
BIT(IRS_INTR_COUNT_THR_AND);
|
|
}
|
|
|
|
if (!clear)
|
|
return IRQ_NONE;
|
|
|
|
ret = regmap_write(data->regmap, IRS_REG_STATUS, clear);
|
|
if (ret)
|
|
dev_err(data->dev,
|
|
"Could not clear interrupt status (%d)\n", ret);
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static irqreturn_t irsd200_trigger_handler(int irq, void *pollf)
|
|
{
|
|
struct iio_dev *indio_dev = ((struct iio_poll_func *)pollf)->indio_dev;
|
|
struct irsd200_data *data = iio_priv(indio_dev);
|
|
s64 buf[2] = {};
|
|
int ret;
|
|
|
|
ret = irsd200_read_data(data, (s16 *)buf);
|
|
if (ret)
|
|
goto end;
|
|
|
|
iio_push_to_buffers_with_timestamp(indio_dev, buf,
|
|
iio_get_time_ns(indio_dev));
|
|
|
|
end:
|
|
iio_trigger_notify_done(indio_dev->trig);
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static int irsd200_set_trigger_state(struct iio_trigger *trig, bool state)
|
|
{
|
|
struct irsd200_data *data = iio_trigger_get_drvdata(trig);
|
|
int ret;
|
|
|
|
ret = regmap_field_write(data->regfields[IRS_REGF_INTR_DATA], state);
|
|
if (ret) {
|
|
dev_err(data->dev, "Could not %s data interrupt source (%d)\n",
|
|
state ? "enable" : "disable", ret);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static const struct iio_info irsd200_info = {
|
|
.read_raw = irsd200_read_raw,
|
|
.read_avail = irsd200_read_avail,
|
|
.write_raw = irsd200_write_raw,
|
|
.read_event_value = irsd200_read_event,
|
|
.write_event_value = irsd200_write_event,
|
|
.read_event_config = irsd200_read_event_config,
|
|
.write_event_config = irsd200_write_event_config,
|
|
};
|
|
|
|
static const struct iio_trigger_ops irsd200_trigger_ops = {
|
|
.set_trigger_state = irsd200_set_trigger_state,
|
|
.validate_device = iio_trigger_validate_own_device,
|
|
};
|
|
|
|
static const struct iio_event_spec irsd200_event_spec[] = {
|
|
{
|
|
.type = IIO_EV_TYPE_THRESH,
|
|
.dir = IIO_EV_DIR_RISING,
|
|
.mask_separate = BIT(IIO_EV_INFO_VALUE),
|
|
},
|
|
{
|
|
.type = IIO_EV_TYPE_THRESH,
|
|
.dir = IIO_EV_DIR_FALLING,
|
|
.mask_separate = BIT(IIO_EV_INFO_VALUE),
|
|
},
|
|
{
|
|
.type = IIO_EV_TYPE_THRESH,
|
|
.dir = IIO_EV_DIR_EITHER,
|
|
.mask_separate =
|
|
BIT(IIO_EV_INFO_RUNNING_PERIOD) |
|
|
BIT(IIO_EV_INFO_RUNNING_COUNT) |
|
|
BIT(IIO_EV_INFO_ENABLE),
|
|
},
|
|
};
|
|
|
|
static const struct iio_chan_spec irsd200_channels[] = {
|
|
{
|
|
.type = IIO_PROXIMITY,
|
|
.info_mask_separate =
|
|
BIT(IIO_CHAN_INFO_RAW) |
|
|
BIT(IIO_CHAN_INFO_SAMP_FREQ) |
|
|
BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY) |
|
|
BIT(IIO_CHAN_INFO_HIGH_PASS_FILTER_3DB_FREQUENCY),
|
|
.info_mask_separate_available =
|
|
BIT(IIO_CHAN_INFO_SAMP_FREQ) |
|
|
BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY) |
|
|
BIT(IIO_CHAN_INFO_HIGH_PASS_FILTER_3DB_FREQUENCY),
|
|
.event_spec = irsd200_event_spec,
|
|
.num_event_specs = ARRAY_SIZE(irsd200_event_spec),
|
|
.scan_type = {
|
|
.sign = 's',
|
|
.realbits = 16,
|
|
.storagebits = 16,
|
|
.endianness = IIO_CPU,
|
|
},
|
|
},
|
|
};
|
|
|
|
static int irsd200_probe(struct i2c_client *client)
|
|
{
|
|
struct iio_trigger *trigger;
|
|
struct irsd200_data *data;
|
|
struct iio_dev *indio_dev;
|
|
size_t i;
|
|
int ret;
|
|
|
|
indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
|
|
if (!indio_dev)
|
|
return dev_err_probe(&client->dev, -ENOMEM,
|
|
"Could not allocate iio device\n");
|
|
|
|
data = iio_priv(indio_dev);
|
|
data->dev = &client->dev;
|
|
|
|
data->regmap = devm_regmap_init_i2c(client, &irsd200_regmap_config);
|
|
if (IS_ERR(data->regmap))
|
|
return dev_err_probe(data->dev, PTR_ERR(data->regmap),
|
|
"Could not initialize regmap\n");
|
|
|
|
for (i = 0; i < IRS_REGF_MAX; ++i) {
|
|
data->regfields[i] = devm_regmap_field_alloc(
|
|
data->dev, data->regmap, irsd200_regfields[i]);
|
|
if (IS_ERR(data->regfields[i]))
|
|
return dev_err_probe(
|
|
data->dev, PTR_ERR(data->regfields[i]),
|
|
"Could not allocate register field %zu\n", i);
|
|
}
|
|
|
|
ret = devm_regulator_get_enable(data->dev, "vdd");
|
|
if (ret)
|
|
return dev_err_probe(
|
|
data->dev, ret,
|
|
"Could not get and enable regulator (%d)\n", ret);
|
|
|
|
ret = irsd200_setup(data);
|
|
if (ret)
|
|
return ret;
|
|
|
|
indio_dev->info = &irsd200_info;
|
|
indio_dev->name = IRS_DRV_NAME;
|
|
indio_dev->channels = irsd200_channels;
|
|
indio_dev->num_channels = ARRAY_SIZE(irsd200_channels);
|
|
indio_dev->modes = INDIO_DIRECT_MODE;
|
|
|
|
if (!client->irq)
|
|
return dev_err_probe(data->dev, -ENXIO, "No irq available\n");
|
|
|
|
ret = devm_iio_triggered_buffer_setup(data->dev, indio_dev, NULL,
|
|
irsd200_trigger_handler, NULL);
|
|
if (ret)
|
|
return dev_err_probe(
|
|
data->dev, ret,
|
|
"Could not setup iio triggered buffer (%d)\n", ret);
|
|
|
|
ret = devm_request_threaded_irq(data->dev, client->irq, NULL,
|
|
irsd200_irq_thread,
|
|
IRQF_TRIGGER_RISING | IRQF_ONESHOT,
|
|
NULL, indio_dev);
|
|
if (ret)
|
|
return dev_err_probe(data->dev, ret,
|
|
"Could not request irq (%d)\n", ret);
|
|
|
|
trigger = devm_iio_trigger_alloc(data->dev, "%s-dev%d", indio_dev->name,
|
|
iio_device_id(indio_dev));
|
|
if (!trigger)
|
|
return dev_err_probe(data->dev, -ENOMEM,
|
|
"Could not allocate iio trigger\n");
|
|
|
|
trigger->ops = &irsd200_trigger_ops;
|
|
iio_trigger_set_drvdata(trigger, data);
|
|
|
|
ret = devm_iio_trigger_register(data->dev, trigger);
|
|
if (ret)
|
|
return dev_err_probe(data->dev, ret,
|
|
"Could not register iio trigger (%d)\n",
|
|
ret);
|
|
|
|
ret = devm_iio_device_register(data->dev, indio_dev);
|
|
if (ret)
|
|
return dev_err_probe(data->dev, ret,
|
|
"Could not register iio device (%d)\n",
|
|
ret);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct of_device_id irsd200_of_match[] = {
|
|
{
|
|
.compatible = "murata,irsd200",
|
|
},
|
|
{}
|
|
};
|
|
MODULE_DEVICE_TABLE(of, irsd200_of_match);
|
|
|
|
static struct i2c_driver irsd200_driver = {
|
|
.driver = {
|
|
.name = IRS_DRV_NAME,
|
|
.of_match_table = irsd200_of_match,
|
|
},
|
|
.probe = irsd200_probe,
|
|
};
|
|
module_i2c_driver(irsd200_driver);
|
|
|
|
MODULE_AUTHOR("Waqar Hameed <waqar.hameed@axis.com>");
|
|
MODULE_DESCRIPTION("Murata IRS-D200 PIR sensor driver");
|
|
MODULE_LICENSE("GPL");
|