Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
+273
View File
@@ -0,0 +1,273 @@
# SPDX-License-Identifier: GPL-2.0-only
#
# Proximity sensors
#
menu "Lightning sensors"
config AS3935
tristate "AS3935 Franklin lightning sensor"
select IIO_BUFFER
select IIO_TRIGGERED_BUFFER
depends on SPI
help
Say Y here to build SPI interface support for the Austrian
Microsystems AS3935 lightning detection sensor.
To compile this driver as a module, choose M here: the
module will be called as3935
endmenu
menu "Proximity and distance sensors"
config CROS_EC_MKBP_PROXIMITY
tristate "ChromeOS EC MKBP Proximity sensor"
depends on CROS_EC
help
Say Y here to enable the proximity sensor implemented via the ChromeOS EC MKBP
switches protocol. You must enable one bus option (CROS_EC_I2C or CROS_EC_SPI)
to use this.
To compile this driver as a module, choose M here: the
module will be called cros_ec_mkbp_proximity.
config D3323AA
tristate "Nicera (Nippon Ceramic Co.) D3-323-AA PIR sensor"
depends on GPIOLIB
help
Say Y here to build a driver for the Nicera D3-323-AA PIR sensor.
To compile this driver as a module, choose M here: the module will be
called d3323aa.
config HX9023S
tristate "TYHX HX9023S SAR sensor"
select IIO_BUFFER
select IIO_TRIGGERED_BUFFER
select REGMAP_I2C
depends on I2C
help
Say Y here to build a driver for TYHX HX9023S capacitive SAR sensor.
This driver supports the TYHX HX9023S capacitive
SAR sensors. This sensors is used for proximity detection applications.
To compile this driver as a module, choose M here: the
module will be called hx9023s.
config IRSD200
tristate "Murata IRS-D200 PIR sensor"
select IIO_BUFFER
select IIO_TRIGGERED_BUFFER
select REGMAP_I2C
depends on I2C
help
Say Y here to build a driver for the Murata IRS-D200 PIR sensor.
To compile this driver as a module, choose M here: the module will be
called irsd200.
config ISL29501
tristate "Intersil ISL29501 Time Of Flight sensor"
depends on I2C
select IIO_BUFFER
select IIO_TRIGGERED_BUFFER
select IIO_KFIFO_BUF
help
Say Y here if you want to build a driver for the Intersil ISL29501
Time of Flight sensor.
To compile this driver as a module, choose M here: the module will be
called isl29501.
config LIDAR_LITE_V2
tristate "PulsedLight LIDAR sensor"
select IIO_BUFFER
select IIO_TRIGGERED_BUFFER
depends on I2C
help
Say Y to build a driver for PulsedLight LIDAR range finding
sensor.
To compile this driver as a module, choose M here: the
module will be called pulsedlight-lite-v2
config MB1232
tristate "MaxSonar I2CXL family ultrasonic sensors"
depends on I2C
select IIO_BUFFER
select IIO_TRIGGERED_BUFFER
help
Say Y to build a driver for the ultrasonic sensors I2CXL of
MaxBotix which have an i2c interface. It can be used to measure
the distance of objects. Supported types are mb1202, mb1212,
mb1222, mb1232, mb1242, mb7040, mb7137
To compile this driver as a module, choose M here: the
module will be called mb1232.
config PING
tristate "Parallax GPIO bitbanged ranger sensors"
depends on GPIOLIB
help
Say Y here to build a driver for GPIO bitbanged ranger sensors
with just one GPIO for the trigger and echo. This driver can be
used to measure the distance of objects.
Actually supported are:
- Parallax PING))) (ultrasonic)
- Parallax LaserPING (time-of-flight)
To compile this driver as a module, choose M here: the
module will be called ping.
config RFD77402
tristate "RFD77402 ToF sensor"
depends on I2C
help
Say Y to build a driver for the RFD77402 Time-of-Flight (distance)
sensor module with I2C interface.
To compile this driver as a module, choose M here: the
module will be called rfd77402.
config SRF04
tristate "GPIO bitbanged ultrasonic ranger sensor (SRF04, MB1000)"
depends on GPIOLIB
help
Say Y here to build a driver for GPIO bitbanged ultrasonic
ranger sensor. This driver can be used to measure the distance
of objects. It is using two GPIOs.
Actually Supported types are:
- Devantech SRF04
- Maxbotix mb1000
- Maxbotix mb1010
- Maxbotix mb1020
- Maxbotix mb1030
- Maxbotix mb1040
To compile this driver as a module, choose M here: the
module will be called srf04.
config SX_COMMON
tristate
help
Common Semtech proximity sensor code.
config SX9310
tristate "SX9310/SX9311 Semtech proximity sensor"
select IIO_BUFFER
select IIO_TRIGGERED_BUFFER
select REGMAP_I2C
select SX_COMMON
depends on I2C
help
Say Y here to build a driver for Semtech's SX9310/SX9311 capacitive
proximity/button sensor.
To compile this driver as a module, choose M here: the
module will be called sx9310.
config SX9324
tristate "SX9324 Semtech proximity sensor"
select IIO_BUFFER
select IIO_TRIGGERED_BUFFER
select REGMAP_I2C
select SX_COMMON
depends on I2C
help
Say Y here to build a driver for Semtech's SX9324
proximity/button sensor.
To compile this driver as a module, choose M here: the
module will be called sx9324.
config SX9360
tristate "SX9360 Semtech proximity sensor"
select IIO_BUFFER
select IIO_TRIGGERED_BUFFER
select REGMAP_I2C
select SX_COMMON
depends on I2C
help
Say Y here to build a driver for Semtech's SX9360
proximity/button sensor, a simplified SX9324.
To compile this driver as a module, choose M here: the
module will be called sx9360.
config SX9500
tristate "SX9500 Semtech proximity sensor"
select IIO_BUFFER
select IIO_TRIGGERED_BUFFER
select REGMAP_I2C
depends on I2C
help
Say Y here to build a driver for Semtech's SX9500 capacitive
proximity/button sensor.
To compile this driver as a module, choose M here: the
module will be called sx9500.
config SRF08
tristate "Devantech SRF02/SRF08/SRF10 ultrasonic ranger sensor"
select IIO_BUFFER
select IIO_TRIGGERED_BUFFER
depends on I2C
help
Say Y here to build a driver for Devantech SRF02/SRF08/SRF10
ultrasonic ranger sensors with i2c interface.
This driver can be used to measure the distance of objects.
To compile this driver as a module, choose M here: the
module will be called srf08.
config VCNL3020
tristate "VCNL3020 proximity sensor"
select REGMAP_I2C
depends on I2C
help
Say Y here if you want to build a driver for the Vishay VCNL3020
proximity sensor.
To compile this driver as a module, choose M here: the
module will be called vcnl3020.
config VL53L0X_I2C
tristate "STMicroelectronics VL53L0X ToF ranger sensor (I2C)"
depends on I2C
help
Say Y here to build a driver for STMicroelectronics VL53L0X
ToF ranger sensors with i2c interface.
This driver can be used to measure the distance of objects.
To compile this driver as a module, choose M here: the
module will be called vl53l0x-i2c.
config VL53L1X_I2C
tristate "STMicroelectronics VL53L1X ToF ranger sensor (I2C)"
depends on I2C
select IIO_BUFFER
select IIO_TRIGGERED_BUFFER
select REGMAP_I2C
select RESET_CONTROLLER
help
Say Y here to build a driver for STMicroelectronics VL53L1X
ToF ranger sensors with i2c interface.
This driver can be used to measure the distance of objects.
To compile this driver as a module, choose M here: the
module will be called vl53l1x-i2c.
config AW96103
tristate "AW96103/AW96105 Awinic proximity sensor"
select REGMAP_I2C
depends on I2C
help
Say Y here to build a driver for Awinic's AW96103/AW96105 capacitive
proximity sensor.
To compile this driver as a module, choose M here: the
module will be called aw96103.
endmenu
@@ -0,0 +1,28 @@
# SPDX-License-Identifier: GPL-2.0
#
# Makefile for IIO proximity sensors
#
# When adding new entries keep the list in alphabetical order
obj-$(CONFIG_AS3935) += as3935.o
obj-$(CONFIG_CROS_EC_MKBP_PROXIMITY) += cros_ec_mkbp_proximity.o
obj-$(CONFIG_D3323AA) += d3323aa.o
obj-$(CONFIG_HX9023S) += hx9023s.o
obj-$(CONFIG_IRSD200) += irsd200.o
obj-$(CONFIG_ISL29501) += isl29501.o
obj-$(CONFIG_LIDAR_LITE_V2) += pulsedlight-lidar-lite-v2.o
obj-$(CONFIG_MB1232) += mb1232.o
obj-$(CONFIG_PING) += ping.o
obj-$(CONFIG_RFD77402) += rfd77402.o
obj-$(CONFIG_SRF04) += srf04.o
obj-$(CONFIG_SRF08) += srf08.o
obj-$(CONFIG_SX9310) += sx9310.o
obj-$(CONFIG_SX9324) += sx9324.o
obj-$(CONFIG_SX9360) += sx9360.o
obj-$(CONFIG_SX_COMMON) += sx_common.o
obj-$(CONFIG_SX9500) += sx9500.o
obj-$(CONFIG_VCNL3020) += vcnl3020.o
obj-$(CONFIG_VL53L0X_I2C) += vl53l0x-i2c.o
obj-$(CONFIG_VL53L1X_I2C) += vl53l1x-i2c.o
obj-$(CONFIG_AW96103) += aw96103.o
+469
View File
@@ -0,0 +1,469 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* as3935.c - Support for AS3935 Franklin lightning sensor
*
* Copyright (C) 2014, 2017-2018
* Author: Matt Ranostay <matt.ranostay@konsulko.com>
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/delay.h>
#include <linux/workqueue.h>
#include <linux/devm-helpers.h>
#include <linux/mutex.h>
#include <linux/err.h>
#include <linux/irq.h>
#include <linux/spi/spi.h>
#include <linux/iio/iio.h>
#include <linux/iio/sysfs.h>
#include <linux/iio/trigger.h>
#include <linux/iio/trigger_consumer.h>
#include <linux/iio/buffer.h>
#include <linux/iio/triggered_buffer.h>
#define AS3935_AFE_GAIN 0x00
#define AS3935_AFE_MASK 0x3F
#define AS3935_AFE_GAIN_MAX 0x1F
#define AS3935_AFE_PWR_BIT BIT(0)
#define AS3935_NFLWDTH 0x01
#define AS3935_NFLWDTH_MASK 0x7f
#define AS3935_INT 0x03
#define AS3935_INT_MASK 0x0f
#define AS3935_DISTURB_INT BIT(2)
#define AS3935_EVENT_INT BIT(3)
#define AS3935_NOISE_INT BIT(0)
#define AS3935_DATA 0x07
#define AS3935_DATA_MASK 0x3F
#define AS3935_TUNE_CAP 0x08
#define AS3935_DEFAULTS 0x3C
#define AS3935_CALIBRATE 0x3D
#define AS3935_READ_DATA BIT(14)
#define AS3935_ADDRESS(x) ((x) << 8)
#define MAX_PF_CAP 120
#define TUNE_CAP_DIV 8
struct as3935_state {
struct spi_device *spi;
struct iio_trigger *trig;
struct mutex lock;
struct delayed_work work;
unsigned long noise_tripped;
u32 tune_cap;
u32 nflwdth_reg;
/* Ensure timestamp is naturally aligned */
struct {
u8 chan;
aligned_s64 timestamp;
} scan;
u8 buf[2] __aligned(IIO_DMA_MINALIGN);
};
static const struct iio_chan_spec as3935_channels[] = {
{
.type = IIO_PROXIMITY,
.info_mask_separate =
BIT(IIO_CHAN_INFO_RAW) |
BIT(IIO_CHAN_INFO_PROCESSED) |
BIT(IIO_CHAN_INFO_SCALE),
.scan_index = 0,
.scan_type = {
.sign = 'u',
.realbits = 6,
.storagebits = 8,
},
},
IIO_CHAN_SOFT_TIMESTAMP(1),
};
static int as3935_read(struct as3935_state *st, unsigned int reg, int *val)
{
u8 cmd;
int ret;
cmd = (AS3935_READ_DATA | AS3935_ADDRESS(reg)) >> 8;
ret = spi_w8r8(st->spi, cmd);
if (ret < 0)
return ret;
*val = ret;
return 0;
}
static int as3935_write(struct as3935_state *st,
unsigned int reg,
unsigned int val)
{
u8 *buf = st->buf;
buf[0] = AS3935_ADDRESS(reg) >> 8;
buf[1] = val;
return spi_write(st->spi, buf, 2);
}
static ssize_t as3935_sensor_sensitivity_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct as3935_state *st = iio_priv(dev_to_iio_dev(dev));
int val, ret;
ret = as3935_read(st, AS3935_AFE_GAIN, &val);
if (ret)
return ret;
val = (val & AS3935_AFE_MASK) >> 1;
return sysfs_emit(buf, "%d\n", val);
}
static ssize_t as3935_sensor_sensitivity_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t len)
{
struct as3935_state *st = iio_priv(dev_to_iio_dev(dev));
unsigned long val;
int ret;
ret = kstrtoul(buf, 10, &val);
if (ret)
return -EINVAL;
if (val > AS3935_AFE_GAIN_MAX)
return -EINVAL;
as3935_write(st, AS3935_AFE_GAIN, val << 1);
return len;
}
static ssize_t as3935_noise_level_tripped_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct as3935_state *st = iio_priv(dev_to_iio_dev(dev));
int ret;
mutex_lock(&st->lock);
ret = sysfs_emit(buf, "%d\n", !time_after(jiffies, st->noise_tripped + HZ));
mutex_unlock(&st->lock);
return ret;
}
static IIO_DEVICE_ATTR(sensor_sensitivity, S_IRUGO | S_IWUSR,
as3935_sensor_sensitivity_show, as3935_sensor_sensitivity_store, 0);
static IIO_DEVICE_ATTR(noise_level_tripped, S_IRUGO,
as3935_noise_level_tripped_show, NULL, 0);
static struct attribute *as3935_attributes[] = {
&iio_dev_attr_sensor_sensitivity.dev_attr.attr,
&iio_dev_attr_noise_level_tripped.dev_attr.attr,
NULL,
};
static const struct attribute_group as3935_attribute_group = {
.attrs = as3935_attributes,
};
static int as3935_read_raw(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan,
int *val,
int *val2,
long m)
{
struct as3935_state *st = iio_priv(indio_dev);
int ret;
switch (m) {
case IIO_CHAN_INFO_PROCESSED:
case IIO_CHAN_INFO_RAW:
*val2 = 0;
ret = as3935_read(st, AS3935_DATA, val);
if (ret)
return ret;
/* storm out of range */
if (*val == AS3935_DATA_MASK)
return -EINVAL;
if (m == IIO_CHAN_INFO_RAW)
return IIO_VAL_INT;
if (m == IIO_CHAN_INFO_PROCESSED)
*val *= 1000;
break;
case IIO_CHAN_INFO_SCALE:
*val = 1000;
break;
default:
return -EINVAL;
}
return IIO_VAL_INT;
}
static const struct iio_info as3935_info = {
.attrs = &as3935_attribute_group,
.read_raw = &as3935_read_raw,
};
static irqreturn_t as3935_trigger_handler(int irq, void *private)
{
struct iio_poll_func *pf = private;
struct iio_dev *indio_dev = pf->indio_dev;
struct as3935_state *st = iio_priv(indio_dev);
int val, ret;
ret = as3935_read(st, AS3935_DATA, &val);
if (ret)
goto err_read;
st->scan.chan = val & AS3935_DATA_MASK;
iio_push_to_buffers_with_ts(indio_dev, &st->scan, sizeof(st->scan),
iio_get_time_ns(indio_dev));
err_read:
iio_trigger_notify_done(indio_dev->trig);
return IRQ_HANDLED;
}
static void as3935_event_work(struct work_struct *work)
{
struct as3935_state *st;
int val;
int ret;
st = container_of(work, struct as3935_state, work.work);
ret = as3935_read(st, AS3935_INT, &val);
if (ret) {
dev_warn(&st->spi->dev, "read error\n");
return;
}
val &= AS3935_INT_MASK;
switch (val) {
case AS3935_EVENT_INT:
iio_trigger_poll_nested(st->trig);
break;
case AS3935_DISTURB_INT:
case AS3935_NOISE_INT:
mutex_lock(&st->lock);
st->noise_tripped = jiffies;
mutex_unlock(&st->lock);
dev_warn(&st->spi->dev, "noise level is too high\n");
break;
}
}
static irqreturn_t as3935_interrupt_handler(int irq, void *private)
{
struct iio_dev *indio_dev = private;
struct as3935_state *st = iio_priv(indio_dev);
/*
* Delay work for >2 milliseconds after an interrupt to allow
* estimated distance to recalculated.
*/
schedule_delayed_work(&st->work, msecs_to_jiffies(3));
return IRQ_HANDLED;
}
static void calibrate_as3935(struct as3935_state *st)
{
as3935_write(st, AS3935_DEFAULTS, 0x96);
as3935_write(st, AS3935_CALIBRATE, 0x96);
as3935_write(st, AS3935_TUNE_CAP,
BIT(5) | (st->tune_cap / TUNE_CAP_DIV));
mdelay(2);
as3935_write(st, AS3935_TUNE_CAP, (st->tune_cap / TUNE_CAP_DIV));
as3935_write(st, AS3935_NFLWDTH, st->nflwdth_reg);
}
static int as3935_suspend(struct device *dev)
{
struct iio_dev *indio_dev = dev_get_drvdata(dev);
struct as3935_state *st = iio_priv(indio_dev);
int val, ret;
mutex_lock(&st->lock);
ret = as3935_read(st, AS3935_AFE_GAIN, &val);
if (ret)
goto err_suspend;
val |= AS3935_AFE_PWR_BIT;
ret = as3935_write(st, AS3935_AFE_GAIN, val);
err_suspend:
mutex_unlock(&st->lock);
return ret;
}
static int as3935_resume(struct device *dev)
{
struct iio_dev *indio_dev = dev_get_drvdata(dev);
struct as3935_state *st = iio_priv(indio_dev);
int val, ret;
mutex_lock(&st->lock);
ret = as3935_read(st, AS3935_AFE_GAIN, &val);
if (ret)
goto err_resume;
val &= ~AS3935_AFE_PWR_BIT;
ret = as3935_write(st, AS3935_AFE_GAIN, val);
calibrate_as3935(st);
err_resume:
mutex_unlock(&st->lock);
return ret;
}
static DEFINE_SIMPLE_DEV_PM_OPS(as3935_pm_ops, as3935_suspend, as3935_resume);
static int as3935_probe(struct spi_device *spi)
{
struct device *dev = &spi->dev;
struct iio_dev *indio_dev;
struct iio_trigger *trig;
struct as3935_state *st;
int ret;
/* Be sure lightning event interrupt is specified */
if (!spi->irq) {
dev_err(dev, "unable to get event interrupt\n");
return -EINVAL;
}
indio_dev = devm_iio_device_alloc(dev, sizeof(*st));
if (!indio_dev)
return -ENOMEM;
st = iio_priv(indio_dev);
st->spi = spi;
spi_set_drvdata(spi, indio_dev);
mutex_init(&st->lock);
ret = device_property_read_u32(dev,
"ams,tuning-capacitor-pf", &st->tune_cap);
if (ret) {
st->tune_cap = 0;
dev_warn(dev, "no tuning-capacitor-pf set, defaulting to %d",
st->tune_cap);
}
if (st->tune_cap > MAX_PF_CAP) {
dev_err(dev, "wrong tuning-capacitor-pf setting of %d\n",
st->tune_cap);
return -EINVAL;
}
ret = device_property_read_u32(dev,
"ams,nflwdth", &st->nflwdth_reg);
if (!ret && st->nflwdth_reg > AS3935_NFLWDTH_MASK) {
dev_err(dev, "invalid nflwdth setting of %d\n",
st->nflwdth_reg);
return -EINVAL;
}
indio_dev->name = spi_get_device_id(spi)->name;
indio_dev->channels = as3935_channels;
indio_dev->num_channels = ARRAY_SIZE(as3935_channels);
indio_dev->modes = INDIO_DIRECT_MODE;
indio_dev->info = &as3935_info;
trig = devm_iio_trigger_alloc(dev, "%s-dev%d",
indio_dev->name,
iio_device_id(indio_dev));
if (!trig)
return -ENOMEM;
st->trig = trig;
st->noise_tripped = jiffies - HZ;
iio_trigger_set_drvdata(trig, indio_dev);
ret = devm_iio_trigger_register(dev, trig);
if (ret) {
dev_err(dev, "failed to register trigger\n");
return ret;
}
ret = devm_iio_triggered_buffer_setup(dev, indio_dev,
iio_pollfunc_store_time,
as3935_trigger_handler, NULL);
if (ret) {
dev_err(dev, "cannot setup iio trigger\n");
return ret;
}
calibrate_as3935(st);
ret = devm_delayed_work_autocancel(dev, &st->work, as3935_event_work);
if (ret)
return ret;
ret = devm_request_irq(dev, spi->irq,
&as3935_interrupt_handler,
IRQF_TRIGGER_RISING,
dev_name(dev),
indio_dev);
if (ret) {
dev_err(dev, "unable to request irq\n");
return ret;
}
ret = devm_iio_device_register(dev, indio_dev);
if (ret < 0) {
dev_err(dev, "unable to register device\n");
return ret;
}
return 0;
}
static const struct of_device_id as3935_of_match[] = {
{ .compatible = "ams,as3935", },
{ }
};
MODULE_DEVICE_TABLE(of, as3935_of_match);
static const struct spi_device_id as3935_id[] = {
{"as3935", 0},
{ }
};
MODULE_DEVICE_TABLE(spi, as3935_id);
static struct spi_driver as3935_driver = {
.driver = {
.name = "as3935",
.of_match_table = as3935_of_match,
.pm = pm_sleep_ptr(&as3935_pm_ops),
},
.probe = as3935_probe,
.id_table = as3935_id,
};
module_spi_driver(as3935_driver);
MODULE_AUTHOR("Matt Ranostay <matt.ranostay@konsulko.com>");
MODULE_DESCRIPTION("AS3935 lightning sensor");
MODULE_LICENSE("GPL");
+846
View File
@@ -0,0 +1,846 @@
// SPDX-License-Identifier: GPL-2.0
/*
* AWINIC aw96103 proximity sensor driver
*
* Author: Wang Shuaijie <wangshuaijie@awinic.com>
*
* Copyright (c) 2024 awinic Technology CO., LTD
*/
#include <linux/bits.h>
#include <linux/bitfield.h>
#include <linux/delay.h>
#include <linux/firmware.h>
#include <linux/i2c.h>
#include <linux/interrupt.h>
#include <linux/iio/events.h>
#include <linux/iio/iio.h>
#include <linux/regulator/consumer.h>
#include <linux/regmap.h>
#include <linux/slab.h>
#include <linux/unaligned.h>
#define AW_DATA_PROCESS_FACTOR 1024
#define AW96103_CHIP_ID 0xa961
#define AW96103_BIN_VALID_DATA_OFFSET 64
#define AW96103_BIN_DATA_LEN_OFFSET 16
#define AW96103_BIN_DATA_REG_NUM_SIZE 4
#define AW96103_BIN_CHIP_TYPE_SIZE 8
#define AW96103_BIN_CHIP_TYPE_OFFSET 24
#define AW96103_REG_SCANCTRL0 0x0000
#define AW96103_REG_STAT0 0x0090
#define AW96103_REG_BLFILT_CH0 0x00A8
#define AW96103_REG_BLRSTRNG_CH0 0x00B4
#define AW96103_REG_DIFF_CH0 0x0240
#define AW96103_REG_FWVER2 0x0410
#define AW96103_REG_CMD 0xF008
#define AW96103_REG_IRQSRC 0xF080
#define AW96103_REG_IRQEN 0xF084
#define AW96103_REG_RESET 0xFF0C
#define AW96103_REG_CHIPID 0xFF10
#define AW96103_REG_EEDA0 0x0408
#define AW96103_REG_EEDA1 0x040C
#define AW96103_REG_PROXCTRL_CH0 0x00B0
#define AW96103_REG_PROXTH0_CH0 0x00B8
#define AW96103_PROXTH_CH_STEP 0x3C
#define AW96103_THHYST_MASK GENMASK(13, 12)
#define AW96103_INDEB_MASK GENMASK(11, 10)
#define AW96103_OUTDEB_MASK GENMASK(9, 8)
#define AW96103_INITOVERIRQ_MASK BIT(0)
#define AW96103_BLFILT_CH_STEP 0x3C
#define AW96103_BLRSTRNG_MASK GENMASK(5, 0)
#define AW96103_CHIPID_MASK GENMASK(31, 16)
#define AW96103_BLERRTRIG_MASK BIT(25)
#define AW96103_CHAN_EN_MASK GENMASK(5, 0)
#define AW96103_REG_PROXCTRL_CH(x) \
(AW96103_REG_PROXCTRL_CH0 + (x) * AW96103_PROXTH_CH_STEP)
#define AW96103_REG_PROXTH0_CH(x) \
(AW96103_REG_PROXTH0_CH0 + (x) * AW96103_PROXTH_CH_STEP)
/**
* struct aw_bin - Store the data obtained from parsing the configuration file.
* @chip_type: Frame header information-chip type
* @valid_data_len: Length of valid data obtained after parsing
* @valid_data_addr: The offset address of the valid data obtained
* after parsing relative to info
* @len: The size of the bin file obtained from the firmware
* @data: Store the bin file obtained from the firmware
*/
struct aw_bin {
unsigned char chip_type[8];
unsigned int valid_data_len;
unsigned int valid_data_addr;
unsigned int len;
unsigned char data[] __counted_by(len);
};
enum aw96103_sar_vers {
AW96103 = 2,
AW96103A = 6,
AW96103B = 0xa,
};
enum aw96103_operation_mode {
AW96103_ACTIVE_MODE = 1,
AW96103_SLEEP_MODE = 2,
AW96103_DEEPSLEEP_MODE = 3,
AW96103B_DEEPSLEEP_MODE = 4,
};
enum aw96103_sensor_type {
AW96103_VAL,
AW96105_VAL,
};
struct aw_channels_info {
bool used;
unsigned int old_irq_status;
};
struct aw_chip_info {
const char *name;
struct iio_chan_spec const *channels;
int num_channels;
};
struct aw96103 {
unsigned int hostirqen;
struct regmap *regmap;
struct device *dev;
/*
* There is one more logical channel than the actual channels,
* and the extra logical channel is used for temperature detection
* but not for status detection. The specific channel used for
* temperature detection is determined by the register configuration.
*/
struct aw_channels_info channels_arr[6];
unsigned int max_channels;
unsigned int chan_en;
};
static const unsigned int aw96103_reg_default[] = {
0x0000, 0x00003f3f, 0x0004, 0x00000064, 0x0008, 0x0017c11e,
0x000c, 0x05000000, 0x0010, 0x00093ffd, 0x0014, 0x19240009,
0x0018, 0xd81c0207, 0x001c, 0xff000000, 0x0020, 0x00241900,
0x0024, 0x00093ff7, 0x0028, 0x58020009, 0x002c, 0xd81c0207,
0x0030, 0xff000000, 0x0034, 0x00025800, 0x0038, 0x00093fdf,
0x003c, 0x7d3b0009, 0x0040, 0xd81c0207, 0x0044, 0xff000000,
0x0048, 0x003b7d00, 0x004c, 0x00093f7f, 0x0050, 0xe9310009,
0x0054, 0xd81c0207, 0x0058, 0xff000000, 0x005c, 0x0031e900,
0x0060, 0x00093dff, 0x0064, 0x1a0c0009, 0x0068, 0xd81c0207,
0x006c, 0xff000000, 0x0070, 0x000c1a00, 0x0074, 0x80093fff,
0x0078, 0x043d0009, 0x007c, 0xd81c0207, 0x0080, 0xff000000,
0x0084, 0x003d0400, 0x00a0, 0xe6400000, 0x00a4, 0x00000000,
0x00a8, 0x010408d2, 0x00ac, 0x00000000, 0x00b0, 0x00000000,
0x00b8, 0x00005fff, 0x00bc, 0x00000000, 0x00c0, 0x00000000,
0x00c4, 0x00000000, 0x00c8, 0x00000000, 0x00cc, 0x00000000,
0x00d0, 0x00000000, 0x00d4, 0x00000000, 0x00d8, 0x00000000,
0x00dc, 0xe6447800, 0x00e0, 0x78000000, 0x00e4, 0x010408d2,
0x00e8, 0x00000000, 0x00ec, 0x00000000, 0x00f4, 0x00005fff,
0x00f8, 0x00000000, 0x00fc, 0x00000000, 0x0100, 0x00000000,
0x0104, 0x00000000, 0x0108, 0x00000000, 0x010c, 0x02000000,
0x0110, 0x00000000, 0x0114, 0x00000000, 0x0118, 0xe6447800,
0x011c, 0x78000000, 0x0120, 0x010408d2, 0x0124, 0x00000000,
0x0128, 0x00000000, 0x0130, 0x00005fff, 0x0134, 0x00000000,
0x0138, 0x00000000, 0x013c, 0x00000000, 0x0140, 0x00000000,
0x0144, 0x00000000, 0x0148, 0x02000000, 0x014c, 0x00000000,
0x0150, 0x00000000, 0x0154, 0xe6447800, 0x0158, 0x78000000,
0x015c, 0x010408d2, 0x0160, 0x00000000, 0x0164, 0x00000000,
0x016c, 0x00005fff, 0x0170, 0x00000000, 0x0174, 0x00000000,
0x0178, 0x00000000, 0x017c, 0x00000000, 0x0180, 0x00000000,
0x0184, 0x02000000, 0x0188, 0x00000000, 0x018c, 0x00000000,
0x0190, 0xe6447800, 0x0194, 0x78000000, 0x0198, 0x010408d2,
0x019c, 0x00000000, 0x01a0, 0x00000000, 0x01a8, 0x00005fff,
0x01ac, 0x00000000, 0x01b0, 0x00000000, 0x01b4, 0x00000000,
0x01b8, 0x00000000, 0x01bc, 0x00000000, 0x01c0, 0x02000000,
0x01c4, 0x00000000, 0x01c8, 0x00000000, 0x01cc, 0xe6407800,
0x01d0, 0x78000000, 0x01d4, 0x010408d2, 0x01d8, 0x00000000,
0x01dc, 0x00000000, 0x01e4, 0x00005fff, 0x01e8, 0x00000000,
0x01ec, 0x00000000, 0x01f0, 0x00000000, 0x01f4, 0x00000000,
0x01f8, 0x00000000, 0x01fc, 0x02000000, 0x0200, 0x00000000,
0x0204, 0x00000000, 0x0208, 0x00000008, 0x020c, 0x0000000d,
0x41fc, 0x00000000, 0x4400, 0x00000000, 0x4410, 0x00000000,
0x4420, 0x00000000, 0x4430, 0x00000000, 0x4440, 0x00000000,
0x4450, 0x00000000, 0x4460, 0x00000000, 0x4470, 0x00000000,
0xf080, 0x00003018, 0xf084, 0x00000fff, 0xf800, 0x00000000,
0xf804, 0x00002e00, 0xf8d0, 0x00000001, 0xf8d4, 0x00000000,
0xff00, 0x00000301, 0xff0c, 0x01000000, 0xffe0, 0x00000000,
0xfff4, 0x00004011, 0x0090, 0x00000000, 0x0094, 0x00000000,
0x0098, 0x00000000, 0x009c, 0x3f3f3f3f,
};
static const struct iio_event_spec aw_common_events[3] = {
{
.type = IIO_EV_TYPE_THRESH,
.dir = IIO_EV_DIR_RISING,
.mask_separate = BIT(IIO_EV_INFO_PERIOD),
},
{
.type = IIO_EV_TYPE_THRESH,
.dir = IIO_EV_DIR_FALLING,
.mask_separate = BIT(IIO_EV_INFO_PERIOD),
},
{
.type = IIO_EV_TYPE_THRESH,
.dir = IIO_EV_DIR_EITHER,
.mask_separate = BIT(IIO_EV_INFO_ENABLE) |
BIT(IIO_EV_INFO_HYSTERESIS) |
BIT(IIO_EV_INFO_VALUE),
}
};
#define AW_IIO_CHANNEL(idx) \
{ \
.type = IIO_PROXIMITY, \
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
.indexed = 1, \
.channel = idx, \
.event_spec = aw_common_events, \
.num_event_specs = ARRAY_SIZE(aw_common_events), \
} \
static const struct iio_chan_spec aw96103_channels[] = {
AW_IIO_CHANNEL(0),
AW_IIO_CHANNEL(1),
AW_IIO_CHANNEL(2),
AW_IIO_CHANNEL(3),
};
static const struct iio_chan_spec aw96105_channels[] = {
AW_IIO_CHANNEL(0),
AW_IIO_CHANNEL(1),
AW_IIO_CHANNEL(2),
AW_IIO_CHANNEL(3),
AW_IIO_CHANNEL(4),
AW_IIO_CHANNEL(5),
};
static const struct aw_chip_info aw_chip_info_tbl[] = {
[AW96103_VAL] = {
.name = "aw96103_sensor",
.channels = aw96103_channels,
.num_channels = ARRAY_SIZE(aw96103_channels),
},
[AW96105_VAL] = {
.name = "aw96105_sensor",
.channels = aw96105_channels,
.num_channels = ARRAY_SIZE(aw96105_channels),
},
};
static void aw96103_parsing_bin_file(struct aw_bin *bin)
{
bin->valid_data_addr = AW96103_BIN_VALID_DATA_OFFSET;
bin->valid_data_len =
*(unsigned int *)(bin->data + AW96103_BIN_DATA_LEN_OFFSET) -
AW96103_BIN_DATA_REG_NUM_SIZE;
memcpy(bin->chip_type, bin->data + AW96103_BIN_CHIP_TYPE_OFFSET,
AW96103_BIN_CHIP_TYPE_SIZE);
}
static const struct regmap_config aw96103_regmap_confg = {
.reg_bits = 16,
.val_bits = 32,
};
static int aw96103_get_diff_raw(struct aw96103 *aw96103, unsigned int chan,
int *buf)
{
u32 data;
int ret;
ret = regmap_read(aw96103->regmap,
AW96103_REG_DIFF_CH0 + chan * 4, &data);
if (ret)
return ret;
*buf = (int)(data / AW_DATA_PROCESS_FACTOR);
return 0;
}
static int aw96103_read_raw(struct iio_dev *indio_dev,
const struct iio_chan_spec *chan,
int *val, int *val2, long mask)
{
struct aw96103 *aw96103 = iio_priv(indio_dev);
int ret;
switch (mask) {
case IIO_CHAN_INFO_RAW:
ret = aw96103_get_diff_raw(aw96103, chan->channel, val);
if (ret)
return ret;
return IIO_VAL_INT;
default:
return -EINVAL;
}
}
static int aw96103_read_thresh(struct aw96103 *aw96103,
const struct iio_chan_spec *chan, int *val)
{
int ret;
ret = regmap_read(aw96103->regmap,
AW96103_REG_PROXTH0_CH(chan->channel), val);
if (ret)
return ret;
return IIO_VAL_INT;
}
static int aw96103_read_out_debounce(struct aw96103 *aw96103,
const struct iio_chan_spec *chan,
int *val)
{
unsigned int reg_val;
int ret;
ret = regmap_read(aw96103->regmap,
AW96103_REG_PROXCTRL_CH(chan->channel), &reg_val);
if (ret)
return ret;
*val = FIELD_GET(AW96103_OUTDEB_MASK, reg_val);
return IIO_VAL_INT;
}
static int aw96103_read_in_debounce(struct aw96103 *aw96103,
const struct iio_chan_spec *chan, int *val)
{
unsigned int reg_val;
int ret;
ret = regmap_read(aw96103->regmap,
AW96103_REG_PROXCTRL_CH(chan->channel), &reg_val);
if (ret)
return ret;
*val = FIELD_GET(AW96103_INDEB_MASK, reg_val);
return IIO_VAL_INT;
}
static int aw96103_read_hysteresis(struct aw96103 *aw96103,
const struct iio_chan_spec *chan, int *val)
{
unsigned int reg_val;
int ret;
ret = regmap_read(aw96103->regmap,
AW96103_REG_PROXCTRL_CH(chan->channel), &reg_val);
if (ret)
return ret;
*val = FIELD_GET(AW96103_THHYST_MASK, reg_val);
return IIO_VAL_INT;
}
static int aw96103_read_event_val(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 aw96103 *aw96103 = iio_priv(indio_dev);
if (chan->type != IIO_PROXIMITY)
return -EINVAL;
switch (info) {
case IIO_EV_INFO_VALUE:
return aw96103_read_thresh(aw96103, chan, val);
case IIO_EV_INFO_PERIOD:
switch (dir) {
case IIO_EV_DIR_RISING:
return aw96103_read_out_debounce(aw96103, chan, val);
case IIO_EV_DIR_FALLING:
return aw96103_read_in_debounce(aw96103, chan, val);
default:
return -EINVAL;
}
case IIO_EV_INFO_HYSTERESIS:
return aw96103_read_hysteresis(aw96103, chan, val);
default:
return -EINVAL;
}
}
static int aw96103_write_event_val(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 aw96103 *aw96103 = iio_priv(indio_dev);
if (chan->type != IIO_PROXIMITY)
return -EINVAL;
switch (info) {
case IIO_EV_INFO_VALUE:
return regmap_write(aw96103->regmap,
AW96103_REG_PROXTH0_CH(chan->channel), val);
case IIO_EV_INFO_PERIOD:
switch (dir) {
case IIO_EV_DIR_RISING:
return regmap_update_bits(aw96103->regmap,
AW96103_REG_PROXCTRL_CH(chan->channel),
AW96103_OUTDEB_MASK,
FIELD_PREP(AW96103_OUTDEB_MASK, val));
case IIO_EV_DIR_FALLING:
return regmap_update_bits(aw96103->regmap,
AW96103_REG_PROXCTRL_CH(chan->channel),
AW96103_INDEB_MASK,
FIELD_PREP(AW96103_INDEB_MASK, val));
default:
return -EINVAL;
}
case IIO_EV_INFO_HYSTERESIS:
return regmap_update_bits(aw96103->regmap,
AW96103_REG_PROXCTRL_CH(chan->channel),
AW96103_THHYST_MASK,
FIELD_PREP(AW96103_THHYST_MASK, val));
default:
return -EINVAL;
}
}
static int aw96103_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 aw96103 *aw96103 = iio_priv(indio_dev);
return aw96103->channels_arr[chan->channel].used;
}
static int aw96103_write_event_config(struct iio_dev *indio_dev,
const struct iio_chan_spec *chan,
enum iio_event_type type,
enum iio_event_direction dir, bool state)
{
struct aw96103 *aw96103 = iio_priv(indio_dev);
aw96103->channels_arr[chan->channel].used = !!state;
return regmap_update_bits(aw96103->regmap, AW96103_REG_SCANCTRL0,
BIT(chan->channel),
state ? BIT(chan->channel) : 0);
}
static const struct iio_info iio_info = {
.read_raw = aw96103_read_raw,
.read_event_value = aw96103_read_event_val,
.write_event_value = aw96103_write_event_val,
.read_event_config = aw96103_read_event_config,
.write_event_config = aw96103_write_event_config,
};
static int aw96103_channel_scan_start(struct aw96103 *aw96103)
{
int ret;
ret = regmap_write(aw96103->regmap, AW96103_REG_CMD,
AW96103_ACTIVE_MODE);
if (ret)
return ret;
return regmap_write(aw96103->regmap, AW96103_REG_IRQEN,
aw96103->hostirqen);
}
static int aw96103_reg_version_comp(struct aw96103 *aw96103,
struct aw_bin *aw_bin)
{
u32 blfilt1_data, fw_ver;
unsigned char i;
int ret;
ret = regmap_read(aw96103->regmap, AW96103_REG_FWVER2, &fw_ver);
if (ret)
return ret;
/*
* If the chip version is AW96103A and the loaded register
* configuration file is for AW96103, special handling of the
* AW96103_REG_BLRSTRNG_CH0 register is required.
*/
if ((fw_ver != AW96103A) || (aw_bin->chip_type[7] != '\0'))
return 0;
for (i = 0; i < aw96103->max_channels; i++) {
ret = regmap_read(aw96103->regmap,
AW96103_REG_BLFILT_CH0 + (AW96103_BLFILT_CH_STEP * i),
&blfilt1_data);
if (ret)
return ret;
if (FIELD_GET(AW96103_BLERRTRIG_MASK, blfilt1_data) != 1)
return 0;
ret = regmap_update_bits(aw96103->regmap,
AW96103_REG_BLRSTRNG_CH0 + (AW96103_BLFILT_CH_STEP * i),
AW96103_BLRSTRNG_MASK, 1 << i);
if (ret)
return ret;
}
return 0;
}
static int aw96103_bin_valid_loaded(struct aw96103 *aw96103,
struct aw_bin *aw_bin_data_s)
{
unsigned int start_addr = aw_bin_data_s->valid_data_addr;
u32 i, reg_data;
u16 reg_addr;
int ret;
for (i = 0; i < aw_bin_data_s->valid_data_len;
i += 6, start_addr += 6) {
reg_addr = get_unaligned_le16(aw_bin_data_s->data + start_addr);
reg_data = get_unaligned_le32(aw_bin_data_s->data +
start_addr + 2);
if ((reg_addr == AW96103_REG_EEDA0) ||
(reg_addr == AW96103_REG_EEDA1))
continue;
if (reg_addr == AW96103_REG_IRQEN) {
aw96103->hostirqen = reg_data;
continue;
}
if (reg_addr == AW96103_REG_SCANCTRL0)
aw96103->chan_en = FIELD_GET(AW96103_CHAN_EN_MASK,
reg_data);
ret = regmap_write(aw96103->regmap, reg_addr, reg_data);
if (ret < 0)
return ret;
}
ret = aw96103_reg_version_comp(aw96103, aw_bin_data_s);
if (ret)
return ret;
return aw96103_channel_scan_start(aw96103);
}
static int aw96103_para_loaded(struct aw96103 *aw96103)
{
int i, ret;
for (i = 0; i < ARRAY_SIZE(aw96103_reg_default); i += 2) {
ret = regmap_write(aw96103->regmap,
(u16)aw96103_reg_default[i],
(u32)aw96103_reg_default[i + 1]);
if (ret)
return ret;
if (aw96103_reg_default[i] == AW96103_REG_IRQEN)
aw96103->hostirqen = aw96103_reg_default[i + 1];
else if (aw96103_reg_default[i] == AW96103_REG_SCANCTRL0)
aw96103->chan_en = FIELD_GET(AW96103_CHAN_EN_MASK,
aw96103_reg_default[i + 1]);
}
return aw96103_channel_scan_start(aw96103);
}
static int aw96103_cfg_all_loaded(const struct firmware *cont,
struct aw96103 *aw96103)
{
if (!cont)
return -EINVAL;
struct aw_bin *aw_bin __free(kfree) =
kzalloc(cont->size + sizeof(*aw_bin), GFP_KERNEL);
if (!aw_bin)
return -ENOMEM;
aw_bin->len = cont->size;
memcpy(aw_bin->data, cont->data, cont->size);
release_firmware(cont);
aw96103_parsing_bin_file(aw_bin);
return aw96103_bin_valid_loaded(aw96103, aw_bin);
}
static void aw96103_cfg_update(const struct firmware *fw, void *data)
{
struct aw96103 *aw96103 = data;
int ret, i;
if (!fw || !fw->data) {
dev_err(aw96103->dev, "No firmware.\n");
return;
}
ret = aw96103_cfg_all_loaded(fw, aw96103);
/*
* If loading the register configuration file fails,
* load the default register configuration in the driver to
* ensure the basic functionality of the device.
*/
if (ret) {
ret = aw96103_para_loaded(aw96103);
if (ret) {
dev_err(aw96103->dev, "load param error.\n");
return;
}
}
for (i = 0; i < aw96103->max_channels; i++) {
if ((aw96103->chan_en >> i) & 0x01)
aw96103->channels_arr[i].used = true;
else
aw96103->channels_arr[i].used = false;
}
}
static int aw96103_sw_reset(struct aw96103 *aw96103)
{
int ret;
ret = regmap_write(aw96103->regmap, AW96103_REG_RESET, 0);
/*
* After reset, the initialization process starts to perform and
* it will last for a bout 20ms.
*/
msleep(20);
return ret;
}
enum aw96103_irq_trigger_position {
FAR = 0,
TRIGGER_TH0 = 0x01,
TRIGGER_TH1 = 0x03,
TRIGGER_TH2 = 0x07,
TRIGGER_TH3 = 0x0f,
};
static irqreturn_t aw96103_irq(int irq, void *data)
{
unsigned int irq_status, curr_status_val, curr_status;
struct iio_dev *indio_dev = data;
struct aw96103 *aw96103 = iio_priv(indio_dev);
int ret, i;
ret = regmap_read(aw96103->regmap, AW96103_REG_IRQSRC, &irq_status);
if (ret)
return IRQ_HANDLED;
ret = regmap_read(aw96103->regmap, AW96103_REG_STAT0, &curr_status_val);
if (ret)
return IRQ_HANDLED;
/*
* Iteratively analyze the interrupt status of different channels,
* with each channel having 4 interrupt states.
*/
for (i = 0; i < aw96103->max_channels; i++) {
if (!aw96103->channels_arr[i].used)
continue;
curr_status = (((curr_status_val >> (24 + i)) & 0x1)) |
(((curr_status_val >> (16 + i)) & 0x1) << 1) |
(((curr_status_val >> (8 + i)) & 0x1) << 2) |
(((curr_status_val >> i) & 0x1) << 3);
if (aw96103->channels_arr[i].old_irq_status == curr_status)
continue;
switch (curr_status) {
case FAR:
iio_push_event(indio_dev,
IIO_UNMOD_EVENT_CODE(IIO_PROXIMITY, i,
IIO_EV_TYPE_THRESH,
IIO_EV_DIR_RISING),
iio_get_time_ns(indio_dev));
break;
case TRIGGER_TH0:
case TRIGGER_TH1:
case TRIGGER_TH2:
case TRIGGER_TH3:
iio_push_event(indio_dev,
IIO_UNMOD_EVENT_CODE(IIO_PROXIMITY, i,
IIO_EV_TYPE_THRESH,
IIO_EV_DIR_FALLING),
iio_get_time_ns(indio_dev));
break;
default:
return IRQ_HANDLED;
}
aw96103->channels_arr[i].old_irq_status = curr_status;
}
return IRQ_HANDLED;
}
static int aw96103_interrupt_init(struct iio_dev *indio_dev,
struct i2c_client *i2c)
{
struct aw96103 *aw96103 = iio_priv(indio_dev);
unsigned int irq_status;
int ret;
ret = regmap_write(aw96103->regmap, AW96103_REG_IRQEN, 0);
if (ret)
return ret;
ret = regmap_read(aw96103->regmap, AW96103_REG_IRQSRC, &irq_status);
if (ret)
return ret;
ret = devm_request_threaded_irq(aw96103->dev, i2c->irq, NULL,
aw96103_irq, IRQF_ONESHOT,
"aw96103_irq", indio_dev);
if (ret)
return ret;
return regmap_write(aw96103->regmap, AW96103_REG_IRQEN,
aw96103->hostirqen);
}
static int aw96103_wait_chip_init(struct aw96103 *aw96103)
{
unsigned int cnt = 20;
u32 reg_data;
int ret;
while (cnt--) {
/*
* The device should generate an initialization completion
* interrupt within 20ms.
*/
ret = regmap_read(aw96103->regmap, AW96103_REG_IRQSRC,
&reg_data);
if (ret)
return ret;
if (FIELD_GET(AW96103_INITOVERIRQ_MASK, reg_data))
return 0;
fsleep(1000);
}
return -ETIMEDOUT;
}
static int aw96103_read_chipid(struct aw96103 *aw96103)
{
unsigned char cnt = 0;
u32 reg_val = 0;
int ret;
while (cnt < 3) {
/*
* This retry mechanism and the subsequent delay are just
* attempts to read the chip ID as much as possible,
* preventing occasional communication failures from causing
* the chip ID read to fail.
*/
ret = regmap_read(aw96103->regmap, AW96103_REG_CHIPID,
&reg_val);
if (ret < 0) {
cnt++;
fsleep(2000);
continue;
}
break;
}
if (cnt == 3)
return -ETIMEDOUT;
if (FIELD_GET(AW96103_CHIPID_MASK, reg_val) != AW96103_CHIP_ID)
dev_info(aw96103->dev,
"unexpected chipid, id=0x%08X\n", reg_val);
return 0;
}
static int aw96103_i2c_probe(struct i2c_client *i2c)
{
const struct aw_chip_info *chip_info;
struct iio_dev *indio_dev;
struct aw96103 *aw96103;
int ret;
indio_dev = devm_iio_device_alloc(&i2c->dev, sizeof(*aw96103));
if (!indio_dev)
return -ENOMEM;
aw96103 = iio_priv(indio_dev);
aw96103->dev = &i2c->dev;
chip_info = i2c_get_match_data(i2c);
aw96103->max_channels = chip_info->num_channels;
aw96103->regmap = devm_regmap_init_i2c(i2c, &aw96103_regmap_confg);
if (IS_ERR(aw96103->regmap))
return PTR_ERR(aw96103->regmap);
ret = devm_regulator_get_enable(aw96103->dev, "vcc");
if (ret < 0)
return ret;
ret = aw96103_read_chipid(aw96103);
if (ret)
return ret;
ret = aw96103_sw_reset(aw96103);
if (ret)
return ret;
ret = aw96103_wait_chip_init(aw96103);
if (ret)
return ret;
ret = request_firmware_nowait(THIS_MODULE, true, "aw96103_0.bin",
aw96103->dev, GFP_KERNEL, aw96103,
aw96103_cfg_update);
if (ret)
return ret;
ret = aw96103_interrupt_init(indio_dev, i2c);
if (ret)
return ret;
indio_dev->modes = INDIO_DIRECT_MODE;
indio_dev->num_channels = chip_info->num_channels;
indio_dev->channels = chip_info->channels;
indio_dev->info = &iio_info;
indio_dev->name = chip_info->name;
return devm_iio_device_register(aw96103->dev, indio_dev);
}
static const struct of_device_id aw96103_dt_match[] = {
{
.compatible = "awinic,aw96103",
.data = &aw_chip_info_tbl[AW96103_VAL]
},
{
.compatible = "awinic,aw96105",
.data = &aw_chip_info_tbl[AW96105_VAL]
},
{ }
};
MODULE_DEVICE_TABLE(of, aw96103_dt_match);
static const struct i2c_device_id aw96103_i2c_id[] = {
{ .name = "aw96103", .driver_data = (kernel_ulong_t)&aw_chip_info_tbl[AW96103_VAL] },
{ .name = "aw96105", .driver_data = (kernel_ulong_t)&aw_chip_info_tbl[AW96105_VAL] },
{ }
};
MODULE_DEVICE_TABLE(i2c, aw96103_i2c_id);
static struct i2c_driver aw96103_i2c_driver = {
.driver = {
.name = "aw96103_sensor",
.of_match_table = aw96103_dt_match,
},
.probe = aw96103_i2c_probe,
.id_table = aw96103_i2c_id,
};
module_i2c_driver(aw96103_i2c_driver);
MODULE_AUTHOR("Wang Shuaijie <wangshuaijie@awinic.com>");
MODULE_DESCRIPTION("Driver for Awinic AW96103 proximity sensor");
MODULE_LICENSE("GPL v2");
@@ -0,0 +1,263 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Driver for cros-ec proximity sensor exposed through MKBP switch
*
* Copyright 2021 Google LLC.
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/notifier.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/types.h>
#include <linux/platform_data/cros_ec_commands.h>
#include <linux/platform_data/cros_ec_proto.h>
#include <linux/iio/events.h>
#include <linux/iio/iio.h>
#include <linux/iio/sysfs.h>
#include <linux/unaligned.h>
struct cros_ec_mkbp_proximity_data {
struct cros_ec_device *ec;
struct iio_dev *indio_dev;
struct mutex lock;
struct notifier_block notifier;
int last_proximity;
bool enabled;
};
static const struct iio_event_spec cros_ec_mkbp_proximity_events[] = {
{
.type = IIO_EV_TYPE_THRESH,
.dir = IIO_EV_DIR_EITHER,
.mask_separate = BIT(IIO_EV_INFO_ENABLE),
},
};
static const struct iio_chan_spec cros_ec_mkbp_proximity_chan_spec[] = {
{
.type = IIO_PROXIMITY,
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
.event_spec = cros_ec_mkbp_proximity_events,
.num_event_specs = ARRAY_SIZE(cros_ec_mkbp_proximity_events),
},
};
static int cros_ec_mkbp_proximity_parse_state(const void *data)
{
u32 switches = get_unaligned_le32(data);
return !!(switches & BIT(EC_MKBP_FRONT_PROXIMITY));
}
static int cros_ec_mkbp_proximity_query(struct cros_ec_device *ec_dev,
int *state)
{
DEFINE_RAW_FLEX(struct cros_ec_command, buf, data,
MAX(sizeof(u32), sizeof(struct ec_params_mkbp_info)));
struct ec_params_mkbp_info *params = (struct ec_params_mkbp_info *)buf->data;
struct cros_ec_command *msg = buf;
u32 *switches = (u32 *)buf->data;
size_t insize = sizeof(*switches);
int ret;
msg->command = EC_CMD_MKBP_INFO;
msg->version = 1;
msg->outsize = sizeof(*params);
msg->insize = insize;
params->info_type = EC_MKBP_INFO_CURRENT;
params->event_type = EC_MKBP_EVENT_SWITCH;
ret = cros_ec_cmd_xfer_status(ec_dev, msg);
if (ret < 0)
return ret;
if (ret != insize) {
dev_warn(ec_dev->dev, "wrong result size: %d != %zu\n", ret,
insize);
return -EPROTO;
}
*state = cros_ec_mkbp_proximity_parse_state(switches);
return IIO_VAL_INT;
}
static void cros_ec_mkbp_proximity_push_event(struct cros_ec_mkbp_proximity_data *data, int state)
{
s64 timestamp;
u64 ev;
int dir;
struct iio_dev *indio_dev = data->indio_dev;
struct cros_ec_device *ec = data->ec;
mutex_lock(&data->lock);
if (state != data->last_proximity) {
if (data->enabled) {
timestamp = ktime_to_ns(ec->last_event_time);
if (iio_device_get_clock(indio_dev) != CLOCK_BOOTTIME)
timestamp = iio_get_time_ns(indio_dev);
dir = state ? IIO_EV_DIR_FALLING : IIO_EV_DIR_RISING;
ev = IIO_UNMOD_EVENT_CODE(IIO_PROXIMITY, 0,
IIO_EV_TYPE_THRESH, dir);
iio_push_event(indio_dev, ev, timestamp);
}
data->last_proximity = state;
}
mutex_unlock(&data->lock);
}
static int cros_ec_mkbp_proximity_notify(struct notifier_block *nb,
unsigned long queued_during_suspend,
void *_ec)
{
struct cros_ec_mkbp_proximity_data *data;
struct cros_ec_device *ec = _ec;
u8 event_type = ec->event_data.event_type & EC_MKBP_EVENT_TYPE_MASK;
void *switches;
int state;
if (event_type == EC_MKBP_EVENT_SWITCH) {
data = container_of(nb, struct cros_ec_mkbp_proximity_data,
notifier);
switches = &ec->event_data.data.switches;
state = cros_ec_mkbp_proximity_parse_state(switches);
cros_ec_mkbp_proximity_push_event(data, state);
}
return NOTIFY_OK;
}
static int cros_ec_mkbp_proximity_read_raw(struct iio_dev *indio_dev,
const struct iio_chan_spec *chan, int *val,
int *val2, long mask)
{
struct cros_ec_mkbp_proximity_data *data = iio_priv(indio_dev);
struct cros_ec_device *ec = data->ec;
if (chan->type == IIO_PROXIMITY && mask == IIO_CHAN_INFO_RAW)
return cros_ec_mkbp_proximity_query(ec, val);
return -EINVAL;
}
static int cros_ec_mkbp_proximity_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 cros_ec_mkbp_proximity_data *data = iio_priv(indio_dev);
return data->enabled;
}
static int cros_ec_mkbp_proximity_write_event_config(struct iio_dev *indio_dev,
const struct iio_chan_spec *chan,
enum iio_event_type type,
enum iio_event_direction dir, bool state)
{
struct cros_ec_mkbp_proximity_data *data = iio_priv(indio_dev);
mutex_lock(&data->lock);
data->enabled = state;
mutex_unlock(&data->lock);
return 0;
}
static const struct iio_info cros_ec_mkbp_proximity_info = {
.read_raw = cros_ec_mkbp_proximity_read_raw,
.read_event_config = cros_ec_mkbp_proximity_read_event_config,
.write_event_config = cros_ec_mkbp_proximity_write_event_config,
};
static int cros_ec_mkbp_proximity_resume(struct device *dev)
{
struct cros_ec_mkbp_proximity_data *data = dev_get_drvdata(dev);
struct cros_ec_device *ec = data->ec;
int ret, state;
ret = cros_ec_mkbp_proximity_query(ec, &state);
if (ret < 0) {
dev_warn(dev, "failed to fetch proximity state on resume: %d\n",
ret);
} else {
cros_ec_mkbp_proximity_push_event(data, state);
}
return 0;
}
static DEFINE_SIMPLE_DEV_PM_OPS(cros_ec_mkbp_proximity_pm_ops, NULL,
cros_ec_mkbp_proximity_resume);
static int cros_ec_mkbp_proximity_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct cros_ec_device *ec = dev_get_drvdata(dev->parent);
struct iio_dev *indio_dev;
struct cros_ec_mkbp_proximity_data *data;
int ret;
indio_dev = devm_iio_device_alloc(dev, sizeof(*data));
if (!indio_dev)
return -ENOMEM;
data = iio_priv(indio_dev);
data->ec = ec;
data->indio_dev = indio_dev;
data->last_proximity = -1; /* Unknown to start */
mutex_init(&data->lock);
platform_set_drvdata(pdev, data);
indio_dev->name = dev->driver->name;
indio_dev->info = &cros_ec_mkbp_proximity_info;
indio_dev->modes = INDIO_DIRECT_MODE;
indio_dev->channels = cros_ec_mkbp_proximity_chan_spec;
indio_dev->num_channels = ARRAY_SIZE(cros_ec_mkbp_proximity_chan_spec);
ret = devm_iio_device_register(dev, indio_dev);
if (ret)
return ret;
data->notifier.notifier_call = cros_ec_mkbp_proximity_notify;
blocking_notifier_chain_register(&ec->event_notifier, &data->notifier);
return 0;
}
static void cros_ec_mkbp_proximity_remove(struct platform_device *pdev)
{
struct cros_ec_mkbp_proximity_data *data = platform_get_drvdata(pdev);
struct cros_ec_device *ec = data->ec;
blocking_notifier_chain_unregister(&ec->event_notifier,
&data->notifier);
}
static const struct of_device_id cros_ec_mkbp_proximity_of_match[] = {
{ .compatible = "google,cros-ec-mkbp-proximity" },
{ }
};
MODULE_DEVICE_TABLE(of, cros_ec_mkbp_proximity_of_match);
static struct platform_driver cros_ec_mkbp_proximity_driver = {
.driver = {
.name = "cros-ec-mkbp-proximity",
.of_match_table = cros_ec_mkbp_proximity_of_match,
.pm = pm_sleep_ptr(&cros_ec_mkbp_proximity_pm_ops),
},
.probe = cros_ec_mkbp_proximity_probe,
.remove = cros_ec_mkbp_proximity_remove,
};
module_platform_driver(cros_ec_mkbp_proximity_driver);
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("ChromeOS EC MKBP proximity sensor driver");
+814
View File
@@ -0,0 +1,814 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Driver for Nicera D3-323-AA PIR sensor.
*
* Copyright (C) 2025 Axis Communications AB
*/
#include <linux/bitmap.h>
#include <linux/cleanup.h>
#include <linux/completion.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/gpio/consumer.h>
#include <linux/interrupt.h>
#include <linux/jiffies.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/platform_device.h>
#include <linux/regulator/consumer.h>
#include <linux/types.h>
#include <linux/iio/events.h>
#include <linux/iio/iio.h>
/*
* Register bitmap.
* For some reason the first bit is denoted as F37 in the datasheet, the second
* as F38 and so on. Note the gap between F60 and F64.
*/
#define D3323AA_REG_BIT_SLAVEA1 0 /* F37. */
#define D3323AA_REG_BIT_SLAVEA2 1 /* F38. */
#define D3323AA_REG_BIT_SLAVEA3 2 /* F39. */
#define D3323AA_REG_BIT_SLAVEA4 3 /* F40. */
#define D3323AA_REG_BIT_SLAVEA5 4 /* F41. */
#define D3323AA_REG_BIT_SLAVEA6 5 /* F42. */
#define D3323AA_REG_BIT_SLAVEA7 6 /* F43. */
#define D3323AA_REG_BIT_SLAVEA8 7 /* F44. */
#define D3323AA_REG_BIT_SLAVEA9 8 /* F45. */
#define D3323AA_REG_BIT_SLAVEA10 9 /* F46. */
#define D3323AA_REG_BIT_DETLVLABS0 10 /* F47. */
#define D3323AA_REG_BIT_DETLVLABS1 11 /* F48. */
#define D3323AA_REG_BIT_DETLVLABS2 12 /* F49. */
#define D3323AA_REG_BIT_DETLVLABS3 13 /* F50. */
#define D3323AA_REG_BIT_DETLVLABS4 14 /* F51. */
#define D3323AA_REG_BIT_DETLVLABS5 15 /* F52. */
#define D3323AA_REG_BIT_DETLVLABS6 16 /* F53. */
#define D3323AA_REG_BIT_DETLVLABS7 17 /* F54. */
#define D3323AA_REG_BIT_DSLP 18 /* F55. */
#define D3323AA_REG_BIT_FSTEP0 19 /* F56. */
#define D3323AA_REG_BIT_FSTEP1 20 /* F57. */
#define D3323AA_REG_BIT_FILSEL0 21 /* F58. */
#define D3323AA_REG_BIT_FILSEL1 22 /* F59. */
#define D3323AA_REG_BIT_FILSEL2 23 /* F60. */
#define D3323AA_REG_BIT_FDSET 24 /* F64. */
#define D3323AA_REG_BIT_F65 25
#define D3323AA_REG_BIT_F87 (D3323AA_REG_BIT_F65 + (87 - 65))
#define D3323AA_REG_NR_BITS (D3323AA_REG_BIT_F87 - D3323AA_REG_BIT_SLAVEA1 + 1)
#define D3323AA_THRESH_REG_NR_BITS \
(D3323AA_REG_BIT_DETLVLABS7 - D3323AA_REG_BIT_DETLVLABS0 + 1)
#define D3323AA_FILTER_TYPE_NR_BITS \
(D3323AA_REG_BIT_FILSEL2 - D3323AA_REG_BIT_FILSEL0 + 1)
#define D3323AA_FILTER_GAIN_REG_NR_BITS \
(D3323AA_REG_BIT_FSTEP1 - D3323AA_REG_BIT_FSTEP0 + 1)
#define D3323AA_THRESH_DEFAULT_VAL 56
#define D3323AA_FILTER_GAIN_DEFAULT_IDX 1
#define D3323AA_LP_FILTER_FREQ_DEFAULT_IDX 1
/*
* The pattern is 0b01101, but store it reversed (0b10110) due to writing from
* LSB on the wire (c.f. d3323aa_write_settings()).
*/
#define D3323AA_SETTING_END_PATTERN 0x16
#define D3323AA_SETTING_END_PATTERN_NR_BITS 5
/*
* Device should be ready for configuration after this many milliseconds.
* Datasheet mentions "approx. 1.2 s". Measurements show around 1.23 s,
* therefore add 100 ms of slack.
*/
#define D3323AA_RESET_TIMEOUT (1200 + 100)
/*
* The configuration of the device (write and read) should be done within this
* many milliseconds.
*/
#define D3323AA_CONFIG_TIMEOUT 1400
/* Number of IRQs needed for configuration stage after reset. */
#define D3323AA_IRQ_RESET_COUNT 2
/*
* High-pass filter cutoff frequency for the band-pass filter. There is a
* corresponding low-pass cutoff frequency for each of the filter types
* (denoted A, B, C and D in the datasheet). The index in this array matches
* that corresponding value in d3323aa_lp_filter_freq.
* Note that this represents a fractional value (e.g. the first value
* corresponds to 40 / 100 = 0.4 Hz).
*/
static const int d3323aa_hp_filter_freq[][2] = {
{ 40, 100 },
{ 30, 100 },
{ 30, 100 },
{ 1, 100 },
};
/*
* Low-pass filter cutoff frequency for the band-pass filter. There is a
* corresponding high-pass cutoff frequency for each of the filter types
* (denoted A, B, C and D in the datasheet). The index in this array matches
* that corresponding value in d3323aa_hp_filter_freq.
* Note that this represents a fractional value (e.g. the first value
* corresponds to 27 / 10 = 2.7 Hz).
*/
static const int d3323aa_lp_filter_freq[][2] = {
{ 27, 10 },
{ 15, 10 },
{ 5, 1 },
{ 100, 1 },
};
/*
* Register bitmap values for filter types (denoted A, B, C and D in the
* datasheet). The index in this array matches the corresponding value in
* d3323aa_lp_filter_freq (which in turn matches d3323aa_hp_filter_freq). For
* example, the first value 7 corresponds to 2.7 Hz low-pass and 0.4 Hz
* high-pass cutoff frequency.
*/
static const int d3323aa_lp_filter_regval[] = {
7,
0,
1,
2,
};
/*
* This is denoted as "step" in datasheet and corresponds to the gain at peak
* for the band-pass filter. The index in this array is the corresponding index
* in d3323aa_filter_gain_regval for the register bitmap value.
*/
static const int d3323aa_filter_gain[] = { 1, 2, 3 };
/*
* Register bitmap values for the filter gain. The index in this array is the
* corresponding index in d3323aa_filter_gain for the gain value.
*/
static const u8 d3323aa_filter_gain_regval[] = { 1, 3, 0 };
struct d3323aa_data {
struct completion reset_completion;
/*
* Since the setup process always requires a complete write of _all_
* the state variables, we need to synchronize them with a lock.
*/
struct mutex statevar_lock;
struct device *dev;
/* Supply voltage. */
struct regulator *regulator_vdd;
/* Input clock or output detection signal (Vout). */
struct gpio_desc *gpiod_clkin_detectout;
/* Input (setting) or output data. */
struct gpio_desc *gpiod_data;
/*
* We only need the low-pass cutoff frequency to unambiguously choose
* the type of band-pass filter. For example, both filter type B and C
* have 0.3 Hz as high-pass cutoff frequency (see
* d3323aa_hp_filter_freq).
*/
size_t lp_filter_freq_idx;
size_t filter_gain_idx;
u8 detect_thresh;
u8 irq_reset_count;
/* Indicator for operational mode (configuring or detecting). */
bool detecting;
};
static int d3323aa_read_settings(struct iio_dev *indio_dev,
unsigned long *regbitmap)
{
struct d3323aa_data *data = iio_priv(indio_dev);
size_t i;
int ret;
/* Bit bang the clock and data pins. */
ret = gpiod_direction_output(data->gpiod_clkin_detectout, 0);
if (ret)
return ret;
ret = gpiod_direction_input(data->gpiod_data);
if (ret)
return ret;
dev_dbg(data->dev, "Reading settings...\n");
for (i = 0; i < D3323AA_REG_NR_BITS; ++i) {
/* Clock frequency needs to be 1 kHz. */
gpiod_set_value(data->gpiod_clkin_detectout, 1);
udelay(500);
/* The data seems to change when clock signal is high. */
if (gpiod_get_value(data->gpiod_data))
set_bit(i, regbitmap);
gpiod_set_value(data->gpiod_clkin_detectout, 0);
udelay(500);
}
/* The first bit (F37) is just dummy data. Discard it. */
clear_bit(0, regbitmap);
/* Datasheet says to wait 30 ms after reading the settings. */
msleep(30);
return 0;
}
static int d3323aa_write_settings(struct iio_dev *indio_dev,
unsigned long *written_regbitmap)
{
#define REGBITMAP_LEN \
(D3323AA_REG_NR_BITS + D3323AA_SETTING_END_PATTERN_NR_BITS)
DECLARE_BITMAP(regbitmap, REGBITMAP_LEN);
struct d3323aa_data *data = iio_priv(indio_dev);
size_t i;
int ret;
/* Build the register bitmap. */
bitmap_zero(regbitmap, REGBITMAP_LEN);
bitmap_write(regbitmap, data->detect_thresh, D3323AA_REG_BIT_DETLVLABS0,
D3323AA_REG_BIT_DETLVLABS7 - D3323AA_REG_BIT_DETLVLABS0 +
1);
bitmap_write(regbitmap,
d3323aa_filter_gain_regval[data->filter_gain_idx],
D3323AA_REG_BIT_FSTEP0,
D3323AA_REG_BIT_FSTEP1 - D3323AA_REG_BIT_FSTEP0 + 1);
bitmap_write(regbitmap,
d3323aa_lp_filter_regval[data->lp_filter_freq_idx],
D3323AA_REG_BIT_FILSEL0,
D3323AA_REG_BIT_FILSEL2 - D3323AA_REG_BIT_FILSEL0 + 1);
/* Compulsory end pattern. */
bitmap_write(regbitmap, D3323AA_SETTING_END_PATTERN,
D3323AA_REG_NR_BITS, D3323AA_SETTING_END_PATTERN_NR_BITS);
/* Bit bang the clock and data pins. */
ret = gpiod_direction_output(data->gpiod_clkin_detectout, 0);
if (ret)
return ret;
ret = gpiod_direction_output(data->gpiod_data, 0);
if (ret)
return ret;
dev_dbg(data->dev, "Writing settings...\n");
/* First bit (F37) is not used when writing the register bitmap. */
for (i = 1; i < REGBITMAP_LEN; ++i) {
gpiod_set_value(data->gpiod_data, test_bit(i, regbitmap));
/* Clock frequency needs to be 1 kHz. */
gpiod_set_value(data->gpiod_clkin_detectout, 1);
udelay(500);
gpiod_set_value(data->gpiod_clkin_detectout, 0);
udelay(500);
}
/* Datasheet says to wait 30 ms after writing the settings. */
msleep(30);
bitmap_copy(written_regbitmap, regbitmap, D3323AA_REG_NR_BITS);
return 0;
}
static irqreturn_t d3323aa_irq_handler(int irq, void *dev_id)
{
struct iio_dev *indio_dev = dev_id;
struct d3323aa_data *data = iio_priv(indio_dev);
enum iio_event_direction dir;
int val;
val = gpiod_get_value(data->gpiod_clkin_detectout);
if (val < 0) {
dev_err_ratelimited(data->dev,
"Could not read from GPIO vout-clk (%d)\n",
val);
return IRQ_HANDLED;
}
if (!data->detecting) {
/* Reset interrupt counting falling edges. */
if (!val && ++data->irq_reset_count == D3323AA_IRQ_RESET_COUNT)
complete(&data->reset_completion);
return IRQ_HANDLED;
}
/* Detection interrupt. */
dir = val ? IIO_EV_DIR_RISING : IIO_EV_DIR_FALLING;
iio_push_event(indio_dev,
IIO_UNMOD_EVENT_CODE(IIO_PROXIMITY, 0,
IIO_EV_TYPE_THRESH, dir),
iio_get_time_ns(indio_dev));
return IRQ_HANDLED;
}
static int d3323aa_reset(struct iio_dev *indio_dev)
{
struct d3323aa_data *data = iio_priv(indio_dev);
long time;
int ret;
/* During probe() the regulator may already be disabled. */
if (regulator_is_enabled(data->regulator_vdd)) {
ret = regulator_disable(data->regulator_vdd);
if (ret)
return ret;
}
/*
* Datasheet says VDD needs to be low at least for 30 ms. Let's add a
* couple more to allow VDD to completely discharge as well.
*/
fsleep((30 + 5) * USEC_PER_MSEC);
/*
* When later enabling VDD, the device will signal with
* D3323AA_IRQ_RESET_COUNT falling edges on Vout/CLK that it is now
* ready for configuration. Datasheet says that this should happen
* within D3323AA_RESET_TIMEOUT ms. Count these two edges within that
* timeout.
*/
data->irq_reset_count = 0;
reinit_completion(&data->reset_completion);
data->detecting = false;
ret = gpiod_direction_input(data->gpiod_clkin_detectout);
if (ret)
return ret;
dev_dbg(data->dev, "Resetting...\n");
ret = regulator_enable(data->regulator_vdd);
if (ret)
return ret;
/*
* Wait for VDD to completely charge up. Measurements have shown that
* Vout/CLK signal slowly ramps up during this period. Thus, the digital
* signal will have bogus values. It is therefore necessary to wait
* before we can count the "real" falling edges.
*/
fsleep(2000);
time = wait_for_completion_killable_timeout(
&data->reset_completion,
msecs_to_jiffies(D3323AA_RESET_TIMEOUT));
if (time == 0) {
return -ETIMEDOUT;
} else if (time < 0) {
/* Got interrupted. */
return time;
}
dev_dbg(data->dev, "Reset completed\n");
return 0;
}
static int d3323aa_setup(struct iio_dev *indio_dev, size_t lp_filter_freq_idx,
size_t filter_gain_idx, u8 detect_thresh)
{
DECLARE_BITMAP(write_regbitmap, D3323AA_REG_NR_BITS);
DECLARE_BITMAP(read_regbitmap, D3323AA_REG_NR_BITS);
struct d3323aa_data *data = iio_priv(indio_dev);
unsigned long start_time;
int ret;
ret = d3323aa_reset(indio_dev);
if (ret) {
if (ret != -ERESTARTSYS)
dev_err(data->dev, "Could not reset device (%d)\n",
ret);
return ret;
}
/*
* Datasheet says to wait 10 us before setting the configuration.
* Moreover, the total configuration should be done within
* D3323AA_CONFIG_TIMEOUT ms. Clock it.
*/
fsleep(10);
start_time = jiffies;
ret = d3323aa_write_settings(indio_dev, write_regbitmap);
if (ret) {
dev_err(data->dev, "Could not write settings (%d)\n", ret);
return ret;
}
ret = d3323aa_read_settings(indio_dev, read_regbitmap);
if (ret) {
dev_err(data->dev, "Could not read settings (%d)\n", ret);
return ret;
}
if (time_is_before_jiffies(start_time +
msecs_to_jiffies(D3323AA_CONFIG_TIMEOUT))) {
dev_err(data->dev, "Could not set up configuration in time\n");
return -EAGAIN;
}
/* Check if settings were set successfully. */
if (!bitmap_equal(write_regbitmap, read_regbitmap,
D3323AA_REG_NR_BITS)) {
dev_err(data->dev, "Settings data mismatch\n");
return -EIO;
}
/* Now in operational mode. */
ret = gpiod_direction_input(data->gpiod_clkin_detectout);
if (ret) {
dev_err(data->dev,
"Could not set GPIO vout-clk as input (%d)\n", ret);
return ret;
}
ret = gpiod_direction_input(data->gpiod_data);
if (ret) {
dev_err(data->dev, "Could not set GPIO data as input (%d)\n",
ret);
return ret;
}
data->lp_filter_freq_idx = lp_filter_freq_idx;
data->filter_gain_idx = filter_gain_idx;
data->detect_thresh = detect_thresh;
data->detecting = true;
dev_dbg(data->dev, "Setup done\n");
return 0;
}
static int d3323aa_set_lp_filter_freq(struct iio_dev *indio_dev, const int val,
int val2)
{
struct d3323aa_data *data = iio_priv(indio_dev);
size_t idx;
/* Truncate fractional part to one digit. */
val2 /= 100000;
for (idx = 0; idx < ARRAY_SIZE(d3323aa_lp_filter_freq); ++idx) {
int integer = d3323aa_lp_filter_freq[idx][0] /
d3323aa_lp_filter_freq[idx][1];
int fract = d3323aa_lp_filter_freq[idx][0] %
d3323aa_lp_filter_freq[idx][1];
if (val == integer && val2 == fract)
break;
}
if (idx == ARRAY_SIZE(d3323aa_lp_filter_freq))
return -EINVAL;
return d3323aa_setup(indio_dev, idx, data->filter_gain_idx,
data->detect_thresh);
}
static int d3323aa_set_hp_filter_freq(struct iio_dev *indio_dev, const int val,
int val2)
{
struct d3323aa_data *data = iio_priv(indio_dev);
size_t idx;
/* Truncate fractional part to two digits. */
val2 /= 10000;
for (idx = 0; idx < ARRAY_SIZE(d3323aa_hp_filter_freq); ++idx) {
int integer = d3323aa_hp_filter_freq[idx][0] /
d3323aa_hp_filter_freq[idx][1];
int fract = d3323aa_hp_filter_freq[idx][0] %
d3323aa_hp_filter_freq[idx][1];
if (val == integer && val2 == fract)
break;
}
if (idx == ARRAY_SIZE(d3323aa_hp_filter_freq))
return -EINVAL;
if (idx == data->lp_filter_freq_idx) {
/* Corresponding filter frequency already set. */
return 0;
}
if (idx == 1 && data->lp_filter_freq_idx == 2) {
/*
* The low-pass cutoff frequency is the only way to
* unambiguously choose the type of band-pass filter. For
* example, both filter type B (index 1) and C (index 2) have
* 0.3 Hz as high-pass cutoff frequency (see
* d3323aa_hp_filter_freq). Therefore, if one of these are
* requested _and_ the corresponding low-pass filter frequency
* is already set, we can't know which filter type is the wanted
* one. The low-pass filter frequency is the decider (i.e. in
* this case index 2).
*/
return 0;
}
return d3323aa_setup(indio_dev, idx, data->filter_gain_idx,
data->detect_thresh);
}
static int d3323aa_set_filter_gain(struct iio_dev *indio_dev, const int val)
{
struct d3323aa_data *data = iio_priv(indio_dev);
size_t idx;
for (idx = 0; idx < ARRAY_SIZE(d3323aa_filter_gain); ++idx) {
if (d3323aa_filter_gain[idx] == val)
break;
}
if (idx == ARRAY_SIZE(d3323aa_filter_gain))
return -EINVAL;
return d3323aa_setup(indio_dev, data->lp_filter_freq_idx, idx,
data->detect_thresh);
}
static int d3323aa_set_threshold(struct iio_dev *indio_dev, const int val)
{
struct d3323aa_data *data = iio_priv(indio_dev);
if (val > ((1 << D3323AA_THRESH_REG_NR_BITS) - 1))
return -EINVAL;
return d3323aa_setup(indio_dev, data->lp_filter_freq_idx,
data->filter_gain_idx, val);
}
static int d3323aa_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_HIGH_PASS_FILTER_3DB_FREQUENCY:
*vals = (int *)d3323aa_hp_filter_freq;
*type = IIO_VAL_FRACTIONAL;
*length = 2 * ARRAY_SIZE(d3323aa_hp_filter_freq);
return IIO_AVAIL_LIST;
case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY:
*vals = (int *)d3323aa_lp_filter_freq;
*type = IIO_VAL_FRACTIONAL;
*length = 2 * ARRAY_SIZE(d3323aa_lp_filter_freq);
return IIO_AVAIL_LIST;
case IIO_CHAN_INFO_HARDWAREGAIN:
*vals = (int *)d3323aa_filter_gain;
*type = IIO_VAL_INT;
*length = ARRAY_SIZE(d3323aa_filter_gain);
return IIO_AVAIL_LIST;
default:
return -EINVAL;
}
}
static int d3323aa_read_raw(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan, int *val,
int *val2, long mask)
{
struct d3323aa_data *data = iio_priv(indio_dev);
guard(mutex)(&data->statevar_lock);
switch (mask) {
case IIO_CHAN_INFO_HIGH_PASS_FILTER_3DB_FREQUENCY:
*val = d3323aa_hp_filter_freq[data->lp_filter_freq_idx][0];
*val2 = d3323aa_hp_filter_freq[data->lp_filter_freq_idx][1];
return IIO_VAL_FRACTIONAL;
case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY:
*val = d3323aa_lp_filter_freq[data->lp_filter_freq_idx][0];
*val2 = d3323aa_lp_filter_freq[data->lp_filter_freq_idx][1];
return IIO_VAL_FRACTIONAL;
case IIO_CHAN_INFO_HARDWAREGAIN:
*val = d3323aa_filter_gain[data->filter_gain_idx];
return IIO_VAL_INT;
default:
return -EINVAL;
}
}
static int d3323aa_write_raw(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan, int val,
int val2, long mask)
{
struct d3323aa_data *data = iio_priv(indio_dev);
guard(mutex)(&data->statevar_lock);
switch (mask) {
case IIO_CHAN_INFO_HIGH_PASS_FILTER_3DB_FREQUENCY:
return d3323aa_set_hp_filter_freq(indio_dev, val, val2);
case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY:
return d3323aa_set_lp_filter_freq(indio_dev, val, val2);
case IIO_CHAN_INFO_HARDWAREGAIN:
return d3323aa_set_filter_gain(indio_dev, val);
default:
return -EINVAL;
}
}
static int d3323aa_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 d3323aa_data *data = iio_priv(indio_dev);
guard(mutex)(&data->statevar_lock);
switch (info) {
case IIO_EV_INFO_VALUE:
*val = data->detect_thresh;
return IIO_VAL_INT;
default:
return -EINVAL;
}
}
static int d3323aa_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 d3323aa_data *data = iio_priv(indio_dev);
guard(mutex)(&data->statevar_lock);
switch (info) {
case IIO_EV_INFO_VALUE:
return d3323aa_set_threshold(indio_dev, val);
default:
return -EINVAL;
}
}
static const struct iio_info d3323aa_info = {
.read_avail = d3323aa_read_avail,
.read_raw = d3323aa_read_raw,
.write_raw = d3323aa_write_raw,
.read_event_value = d3323aa_read_event,
.write_event_value = d3323aa_write_event,
};
static const struct iio_event_spec d3323aa_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),
},
};
static const struct iio_chan_spec d3323aa_channels[] = {
{
.type = IIO_PROXIMITY,
.info_mask_separate =
BIT(IIO_CHAN_INFO_HIGH_PASS_FILTER_3DB_FREQUENCY) |
BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY) |
BIT(IIO_CHAN_INFO_HARDWAREGAIN),
.info_mask_separate_available =
BIT(IIO_CHAN_INFO_HIGH_PASS_FILTER_3DB_FREQUENCY) |
BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY) |
BIT(IIO_CHAN_INFO_HARDWAREGAIN),
.event_spec = d3323aa_event_spec,
.num_event_specs = ARRAY_SIZE(d3323aa_event_spec),
},
};
static void d3323aa_disable_regulator(void *indata)
{
struct d3323aa_data *data = indata;
int ret;
/*
* During probe() the regulator may be disabled. It is enabled during
* device setup (in d3323aa_reset(), where it is also briefly disabled).
* The check is therefore needed in order to have balanced
* regulator_enable/disable() calls.
*/
if (!regulator_is_enabled(data->regulator_vdd))
return;
ret = regulator_disable(data->regulator_vdd);
if (ret)
dev_err(data->dev, "Could not disable regulator (%d)\n", ret);
}
static int d3323aa_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct d3323aa_data *data;
struct iio_dev *indio_dev;
int ret;
indio_dev = devm_iio_device_alloc(dev, sizeof(*data));
if (!indio_dev)
return -ENOMEM;
data = iio_priv(indio_dev);
data->dev = dev;
init_completion(&data->reset_completion);
ret = devm_mutex_init(dev, &data->statevar_lock);
if (ret)
return dev_err_probe(dev, ret, "Could not initialize mutex\n");
data->regulator_vdd = devm_regulator_get_exclusive(dev, "vdd");
if (IS_ERR(data->regulator_vdd))
return dev_err_probe(dev, PTR_ERR(data->regulator_vdd),
"Could not get regulator\n");
/*
* The regulator will be enabled for the first time during the
* device setup below (in d3323aa_reset()). However parameter changes
* from userspace can require a temporary disable of the regulator.
* To avoid complex handling of state, use a callback that will disable
* the regulator if it happens to be enabled at time of devm unwind.
*/
ret = devm_add_action_or_reset(dev, d3323aa_disable_regulator, data);
if (ret)
return ret;
data->gpiod_clkin_detectout =
devm_gpiod_get(dev, "vout-clk", GPIOD_OUT_LOW);
if (IS_ERR(data->gpiod_clkin_detectout))
return dev_err_probe(dev, PTR_ERR(data->gpiod_clkin_detectout),
"Could not get GPIO vout-clk\n");
data->gpiod_data = devm_gpiod_get(dev, "data", GPIOD_OUT_LOW);
if (IS_ERR(data->gpiod_data))
return dev_err_probe(dev, PTR_ERR(data->gpiod_data),
"Could not get GPIO data\n");
ret = gpiod_to_irq(data->gpiod_clkin_detectout);
if (ret < 0)
return dev_err_probe(dev, ret, "Could not get IRQ\n");
/*
* Device signals with a rising or falling detection signal when the
* proximity data is above or below the threshold, respectively.
*/
ret = devm_request_irq(dev, ret, d3323aa_irq_handler,
IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
dev_name(dev), indio_dev);
if (ret)
return dev_err_probe(dev, ret, "Could not request IRQ\n");
ret = d3323aa_setup(indio_dev, D3323AA_LP_FILTER_FREQ_DEFAULT_IDX,
D3323AA_FILTER_GAIN_DEFAULT_IDX,
D3323AA_THRESH_DEFAULT_VAL);
if (ret)
return ret;
indio_dev->info = &d3323aa_info;
indio_dev->name = "d3323aa";
indio_dev->channels = d3323aa_channels;
indio_dev->num_channels = ARRAY_SIZE(d3323aa_channels);
ret = devm_iio_device_register(dev, indio_dev);
if (ret)
return dev_err_probe(dev, ret,
"Could not register iio device\n");
return 0;
}
static const struct of_device_id d3323aa_of_match[] = {
{
.compatible = "nicera,d3323aa",
},
{ }
};
MODULE_DEVICE_TABLE(of, d3323aa_of_match);
static struct platform_driver d3323aa_driver = {
.probe = d3323aa_probe,
.driver = {
.name = "d3323aa",
.of_match_table = d3323aa_of_match,
},
};
module_platform_driver(d3323aa_driver);
MODULE_AUTHOR("Waqar Hameed <waqar.hameed@axis.com>");
MODULE_DESCRIPTION("Nicera D3-323-AA PIR sensor driver");
MODULE_LICENSE("GPL");
File diff suppressed because it is too large Load Diff
+955
View File
@@ -0,0 +1,955 @@
// 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/string_choices.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, &regval);
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, &regval);
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, &regval);
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], &regval);
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], &regval);
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,
bool 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);
struct {
s16 channel;
aligned_s64 ts;
} scan = { };
int ret;
ret = irsd200_read_data(data, &scan.channel);
if (ret)
goto end;
iio_push_to_buffers_with_ts(indio_dev, &scan, sizeof(scan),
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",
str_enable_disable(state), 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 -ENOMEM;
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\n");
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\n");
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\n");
trigger = devm_iio_trigger_alloc(data->dev, "%s-dev%d", indio_dev->name,
iio_device_id(indio_dev));
if (!trigger)
return -ENOMEM;
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\n");
ret = devm_iio_device_register(data->dev, indio_dev);
if (ret)
return dev_err_probe(data->dev, ret,
"Could not register iio device\n");
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");
File diff suppressed because it is too large Load Diff
+269
View File
@@ -0,0 +1,269 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* mb1232.c - Support for MaxBotix I2CXL-MaxSonar-EZ series ultrasonic
* ranger with i2c interface
* actually tested with mb1232 type
*
* Copyright (c) 2019 Andreas Klinger <ak@it-klinger.de>
*
* For details about the device see:
* https://www.maxbotix.com/documents/I2CXL-MaxSonar-EZ_Datasheet.pdf
*/
#include <linux/bitops.h>
#include <linux/err.h>
#include <linux/i2c.h>
#include <linux/delay.h>
#include <linux/module.h>
#include <linux/property.h>
#include <linux/iio/iio.h>
#include <linux/iio/sysfs.h>
#include <linux/iio/buffer.h>
#include <linux/iio/trigger_consumer.h>
#include <linux/iio/triggered_buffer.h>
/* registers of MaxSonar device */
#define MB1232_RANGE_COMMAND 0x51 /* Command for reading range */
#define MB1232_ADDR_UNLOCK_1 0xAA /* Command 1 for changing address */
#define MB1232_ADDR_UNLOCK_2 0xA5 /* Command 2 for changing address */
struct mb1232_data {
struct i2c_client *client;
struct mutex lock;
/*
* optionally a gpio can be used to announce when ranging has
* finished
* since we are just using the falling trigger of it we request
* only the interrupt for announcing when data is ready to be read
*/
struct completion ranging;
int irqnr;
};
static irqreturn_t mb1232_handle_irq(int irq, void *dev_id)
{
struct iio_dev *indio_dev = dev_id;
struct mb1232_data *data = iio_priv(indio_dev);
complete(&data->ranging);
return IRQ_HANDLED;
}
static s16 mb1232_read_distance(struct mb1232_data *data)
{
struct i2c_client *client = data->client;
int ret;
s16 distance;
__be16 buf;
mutex_lock(&data->lock);
reinit_completion(&data->ranging);
ret = i2c_smbus_write_byte(client, MB1232_RANGE_COMMAND);
if (ret < 0) {
dev_err(&client->dev, "write command - err: %d\n", ret);
goto error_unlock;
}
if (data->irqnr > 0) {
/* it cannot take more than 100 ms */
ret = wait_for_completion_killable_timeout(&data->ranging,
HZ/10);
if (ret < 0)
goto error_unlock;
else if (ret == 0) {
ret = -ETIMEDOUT;
goto error_unlock;
}
} else {
/* use simple sleep if announce irq is not connected */
msleep(15);
}
ret = i2c_master_recv(client, (char *)&buf, sizeof(buf));
if (ret < 0) {
dev_err(&client->dev, "i2c_master_recv: ret=%d\n", ret);
goto error_unlock;
}
distance = __be16_to_cpu(buf);
/* check for not returning misleading error codes */
if (distance < 0) {
dev_err(&client->dev, "distance=%d\n", distance);
ret = -EINVAL;
goto error_unlock;
}
mutex_unlock(&data->lock);
return distance;
error_unlock:
mutex_unlock(&data->lock);
return ret;
}
static irqreturn_t mb1232_trigger_handler(int irq, void *p)
{
struct iio_poll_func *pf = p;
struct iio_dev *indio_dev = pf->indio_dev;
struct mb1232_data *data = iio_priv(indio_dev);
struct {
s16 distance;
aligned_s64 ts;
} scan = { };
scan.distance = mb1232_read_distance(data);
if (scan.distance < 0)
goto err;
iio_push_to_buffers_with_ts(indio_dev, &scan, sizeof(scan),
pf->timestamp);
err:
iio_trigger_notify_done(indio_dev->trig);
return IRQ_HANDLED;
}
static int mb1232_read_raw(struct iio_dev *indio_dev,
struct iio_chan_spec const *channel, int *val,
int *val2, long mask)
{
struct mb1232_data *data = iio_priv(indio_dev);
int ret;
if (channel->type != IIO_DISTANCE)
return -EINVAL;
switch (mask) {
case IIO_CHAN_INFO_RAW:
ret = mb1232_read_distance(data);
if (ret < 0)
return ret;
*val = ret;
return IIO_VAL_INT;
case IIO_CHAN_INFO_SCALE:
/* 1 LSB is 1 cm */
*val = 0;
*val2 = 10000;
return IIO_VAL_INT_PLUS_MICRO;
default:
return -EINVAL;
}
}
static const struct iio_chan_spec mb1232_channels[] = {
{
.type = IIO_DISTANCE,
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
BIT(IIO_CHAN_INFO_SCALE),
.scan_index = 0,
.scan_type = {
.sign = 's',
.realbits = 16,
.storagebits = 16,
.endianness = IIO_CPU,
},
},
IIO_CHAN_SOFT_TIMESTAMP(1),
};
static const struct iio_info mb1232_info = {
.read_raw = mb1232_read_raw,
};
static int mb1232_probe(struct i2c_client *client)
{
const struct i2c_device_id *id = i2c_client_get_device_id(client);
struct iio_dev *indio_dev;
struct mb1232_data *data;
int ret;
struct device *dev = &client->dev;
if (!i2c_check_functionality(client->adapter,
I2C_FUNC_SMBUS_READ_BYTE |
I2C_FUNC_SMBUS_WRITE_BYTE))
return -ENODEV;
indio_dev = devm_iio_device_alloc(dev, sizeof(*data));
if (!indio_dev)
return -ENOMEM;
data = iio_priv(indio_dev);
i2c_set_clientdata(client, indio_dev);
data->client = client;
indio_dev->info = &mb1232_info;
indio_dev->name = id->name;
indio_dev->modes = INDIO_DIRECT_MODE;
indio_dev->channels = mb1232_channels;
indio_dev->num_channels = ARRAY_SIZE(mb1232_channels);
mutex_init(&data->lock);
init_completion(&data->ranging);
data->irqnr = fwnode_irq_get(dev_fwnode(&client->dev), 0);
if (data->irqnr > 0) {
ret = devm_request_irq(dev, data->irqnr, mb1232_handle_irq,
IRQF_TRIGGER_FALLING, id->name, indio_dev);
if (ret < 0) {
dev_err(dev, "request_irq: %d\n", ret);
return ret;
}
}
ret = devm_iio_triggered_buffer_setup(dev, indio_dev,
iio_pollfunc_store_time, mb1232_trigger_handler, NULL);
if (ret < 0) {
dev_err(dev, "setup of iio triggered buffer failed\n");
return ret;
}
return devm_iio_device_register(dev, indio_dev);
}
static const struct of_device_id of_mb1232_match[] = {
{ .compatible = "maxbotix,mb1202", },
{ .compatible = "maxbotix,mb1212", },
{ .compatible = "maxbotix,mb1222", },
{ .compatible = "maxbotix,mb1232", },
{ .compatible = "maxbotix,mb1242", },
{ .compatible = "maxbotix,mb7040", },
{ .compatible = "maxbotix,mb7137", },
{ }
};
MODULE_DEVICE_TABLE(of, of_mb1232_match);
static const struct i2c_device_id mb1232_id[] = {
{ .name = "maxbotix-mb1202" },
{ .name = "maxbotix-mb1212" },
{ .name = "maxbotix-mb1222" },
{ .name = "maxbotix-mb1232" },
{ .name = "maxbotix-mb1242" },
{ .name = "maxbotix-mb7040" },
{ .name = "maxbotix-mb7137" },
{ }
};
MODULE_DEVICE_TABLE(i2c, mb1232_id);
static struct i2c_driver mb1232_driver = {
.driver = {
.name = "maxbotix-mb1232",
.of_match_table = of_mb1232_match,
},
.probe = mb1232_probe,
.id_table = mb1232_id,
};
module_i2c_driver(mb1232_driver);
MODULE_AUTHOR("Andreas Klinger <ak@it-klinger.de>");
MODULE_DESCRIPTION("Maxbotix I2CXL-MaxSonar i2c ultrasonic ranger driver");
MODULE_LICENSE("GPL");
+329
View File
@@ -0,0 +1,329 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* PING: ultrasonic sensor for distance measuring by using only one GPIOs
*
* Copyright (c) 2019 Andreas Klinger <ak@it-klinger.de>
*
* For details about the devices see:
* http://parallax.com/sites/default/files/downloads/28041-LaserPING-2m-Rangefinder-Guide.pdf
* http://parallax.com/sites/default/files/downloads/28015-PING-Documentation-v1.6.pdf
*
* the measurement cycle as timing diagram looks like:
*
* GPIO ___ ________________________
* ping: __/ \____________/ \________________
* ^ ^ ^ ^
* |<->| interrupt interrupt
* udelay(5) (ts_rising) (ts_falling)
* |<---------------------->|
* . pulse time measured .
* . --> one round trip of ultra sonic waves
* ultra . .
* sonic _ _ _. .
* burst: _________/ \_/ \_/ \_________________________________________
* .
* ultra .
* sonic _ _ _.
* echo: __________________________________/ \_/ \_/ \________________
*/
#include <linux/err.h>
#include <linux/gpio/consumer.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/property.h>
#include <linux/sched.h>
#include <linux/interrupt.h>
#include <linux/delay.h>
#include <linux/iio/iio.h>
#include <linux/iio/sysfs.h>
struct ping_cfg {
unsigned long trigger_pulse_us; /* length of trigger pulse */
int laserping_error; /* support error code in */
/* pulse width of laser */
/* ping sensors */
s64 timeout_ns; /* timeout in ns */
};
struct ping_data {
struct device *dev;
struct gpio_desc *gpiod_ping;
struct mutex lock;
int irqnr;
ktime_t ts_rising;
ktime_t ts_falling;
struct completion rising;
struct completion falling;
const struct ping_cfg *cfg;
};
static const struct ping_cfg pa_ping_cfg = {
.trigger_pulse_us = 5,
.laserping_error = 0,
.timeout_ns = 18500000, /* 3 meters */
};
static const struct ping_cfg pa_laser_ping_cfg = {
.trigger_pulse_us = 5,
.laserping_error = 1,
.timeout_ns = 15500000, /* 2 meters plus error codes */
};
static irqreturn_t ping_handle_irq(int irq, void *dev_id)
{
struct iio_dev *indio_dev = dev_id;
struct ping_data *data = iio_priv(indio_dev);
ktime_t now = ktime_get();
if (gpiod_get_value(data->gpiod_ping)) {
data->ts_rising = now;
complete(&data->rising);
} else {
data->ts_falling = now;
complete(&data->falling);
}
return IRQ_HANDLED;
}
static int ping_read(struct iio_dev *indio_dev)
{
struct ping_data *data = iio_priv(indio_dev);
int ret;
ktime_t ktime_dt;
s64 dt_ns;
u32 time_ns, distance_mm;
struct platform_device *pdev = to_platform_device(data->dev);
/*
* just one read-echo-cycle can take place at a time
* ==> lock against concurrent reading calls
*/
mutex_lock(&data->lock);
reinit_completion(&data->rising);
reinit_completion(&data->falling);
gpiod_set_value(data->gpiod_ping, 1);
udelay(data->cfg->trigger_pulse_us);
gpiod_set_value(data->gpiod_ping, 0);
ret = gpiod_direction_input(data->gpiod_ping);
if (ret < 0) {
mutex_unlock(&data->lock);
return ret;
}
data->irqnr = gpiod_to_irq(data->gpiod_ping);
if (data->irqnr < 0) {
dev_err(data->dev, "gpiod_to_irq: %d\n", data->irqnr);
mutex_unlock(&data->lock);
return data->irqnr;
}
ret = request_irq(data->irqnr, ping_handle_irq,
IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
pdev->name, indio_dev);
if (ret < 0) {
dev_err(data->dev, "request_irq: %d\n", ret);
mutex_unlock(&data->lock);
return ret;
}
/* it should not take more than 20 ms until echo is rising */
ret = wait_for_completion_killable_timeout(&data->rising, HZ/50);
if (ret < 0)
goto err_reset_direction;
else if (ret == 0) {
ret = -ETIMEDOUT;
goto err_reset_direction;
}
/* it cannot take more than 50 ms until echo is falling */
ret = wait_for_completion_killable_timeout(&data->falling, HZ/20);
if (ret < 0)
goto err_reset_direction;
else if (ret == 0) {
ret = -ETIMEDOUT;
goto err_reset_direction;
}
ktime_dt = ktime_sub(data->ts_falling, data->ts_rising);
free_irq(data->irqnr, indio_dev);
ret = gpiod_direction_output(data->gpiod_ping, GPIOD_OUT_LOW);
if (ret < 0) {
mutex_unlock(&data->lock);
return ret;
}
mutex_unlock(&data->lock);
dt_ns = ktime_to_ns(ktime_dt);
if (dt_ns > data->cfg->timeout_ns) {
dev_dbg(data->dev, "distance out of range: dt=%lldns\n",
dt_ns);
return -EIO;
}
time_ns = dt_ns;
/*
* read error code of laser ping sensor and give users chance to
* figure out error by using dynamic debugging
*/
if (data->cfg->laserping_error) {
if ((time_ns > 12500000) && (time_ns <= 13500000)) {
dev_dbg(data->dev, "target too close or to far\n");
return -EIO;
}
if ((time_ns > 13500000) && (time_ns <= 14500000)) {
dev_dbg(data->dev, "internal sensor error\n");
return -EIO;
}
if ((time_ns > 14500000) && (time_ns <= 15500000)) {
dev_dbg(data->dev, "internal sensor timeout\n");
return -EIO;
}
}
/*
* the speed as function of the temperature is approximately:
*
* speed = 331,5 + 0,6 * Temp
* with Temp in °C
* and speed in m/s
*
* use 343,5 m/s as ultrasonic speed at 20 °C here in absence of the
* temperature
*
* therefore:
* time 343,5 time * 232
* distance = ------ * ------- = ------------
* 10^6 2 1350800
* with time in ns
* and distance in mm (one way)
*
* because we limit to 3 meters the multiplication with 232 just
* fits into 32 bit
*/
distance_mm = time_ns * 232 / 1350800;
return distance_mm;
err_reset_direction:
free_irq(data->irqnr, indio_dev);
mutex_unlock(&data->lock);
if (gpiod_direction_output(data->gpiod_ping, GPIOD_OUT_LOW))
dev_dbg(data->dev, "error in gpiod_direction_output\n");
return ret;
}
static int ping_read_raw(struct iio_dev *indio_dev,
struct iio_chan_spec const *channel, int *val,
int *val2, long info)
{
int ret;
if (channel->type != IIO_DISTANCE)
return -EINVAL;
switch (info) {
case IIO_CHAN_INFO_RAW:
ret = ping_read(indio_dev);
if (ret < 0)
return ret;
*val = ret;
return IIO_VAL_INT;
case IIO_CHAN_INFO_SCALE:
/*
* maximum resolution in datasheet is 1 mm
* 1 LSB is 1 mm
*/
*val = 0;
*val2 = 1000;
return IIO_VAL_INT_PLUS_MICRO;
default:
return -EINVAL;
}
}
static const struct iio_info ping_iio_info = {
.read_raw = ping_read_raw,
};
static const struct iio_chan_spec ping_chan_spec[] = {
{
.type = IIO_DISTANCE,
.info_mask_separate =
BIT(IIO_CHAN_INFO_RAW) |
BIT(IIO_CHAN_INFO_SCALE),
},
};
static const struct of_device_id of_ping_match[] = {
{ .compatible = "parallax,ping", .data = &pa_ping_cfg },
{ .compatible = "parallax,laserping", .data = &pa_laser_ping_cfg },
{ }
};
MODULE_DEVICE_TABLE(of, of_ping_match);
static int ping_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct ping_data *data;
struct iio_dev *indio_dev;
indio_dev = devm_iio_device_alloc(dev, sizeof(struct ping_data));
if (!indio_dev)
return -ENOMEM;
data = iio_priv(indio_dev);
data->dev = dev;
data->cfg = device_get_match_data(dev);
mutex_init(&data->lock);
init_completion(&data->rising);
init_completion(&data->falling);
data->gpiod_ping = devm_gpiod_get(dev, "ping", GPIOD_OUT_LOW);
if (IS_ERR(data->gpiod_ping)) {
dev_err(dev, "failed to get ping-gpios: err=%ld\n",
PTR_ERR(data->gpiod_ping));
return PTR_ERR(data->gpiod_ping);
}
if (gpiod_cansleep(data->gpiod_ping)) {
dev_err(data->dev, "cansleep-GPIOs not supported\n");
return -ENODEV;
}
platform_set_drvdata(pdev, indio_dev);
indio_dev->name = "ping";
indio_dev->info = &ping_iio_info;
indio_dev->modes = INDIO_DIRECT_MODE;
indio_dev->channels = ping_chan_spec;
indio_dev->num_channels = ARRAY_SIZE(ping_chan_spec);
return devm_iio_device_register(dev, indio_dev);
}
static struct platform_driver ping_driver = {
.probe = ping_probe,
.driver = {
.name = "ping-gpio",
.of_match_table = of_ping_match,
},
};
module_platform_driver(ping_driver);
MODULE_AUTHOR("Andreas Klinger <ak@it-klinger.de>");
MODULE_DESCRIPTION("PING sensors for distance measuring using one GPIOs");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:ping");
@@ -0,0 +1,372 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* pulsedlight-lidar-lite-v2.c - Support for PulsedLight LIDAR sensor
*
* Copyright (C) 2015, 2017-2018
* Author: Matt Ranostay <matt.ranostay@konsulko.com>
*
* TODO: interrupt mode, and signal strength reporting
*/
#include <linux/err.h>
#include <linux/init.h>
#include <linux/i2c.h>
#include <linux/delay.h>
#include <linux/module.h>
#include <linux/pm_runtime.h>
#include <linux/iio/iio.h>
#include <linux/iio/sysfs.h>
#include <linux/iio/buffer.h>
#include <linux/iio/trigger.h>
#include <linux/iio/triggered_buffer.h>
#include <linux/iio/trigger_consumer.h>
#define LIDAR_REG_CONTROL 0x00
#define LIDAR_REG_CONTROL_ACQUIRE BIT(2)
#define LIDAR_REG_STATUS 0x01
#define LIDAR_REG_STATUS_INVALID BIT(3)
#define LIDAR_REG_STATUS_READY BIT(0)
#define LIDAR_REG_DATA_HBYTE 0x0f
#define LIDAR_REG_DATA_LBYTE 0x10
#define LIDAR_REG_DATA_WORD_READ BIT(7)
#define LIDAR_REG_PWR_CONTROL 0x65
#define LIDAR_DRV_NAME "lidar"
struct lidar_data {
struct iio_dev *indio_dev;
struct i2c_client *client;
int (*xfer)(struct lidar_data *data, u8 reg, u8 *val, int len);
int i2c_enabled;
};
static const struct iio_chan_spec lidar_channels[] = {
{
.type = IIO_DISTANCE,
.info_mask_separate =
BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE),
.scan_index = 0,
.scan_type = {
.sign = 'u',
.realbits = 16,
.storagebits = 16,
},
},
IIO_CHAN_SOFT_TIMESTAMP(1),
};
static int lidar_i2c_xfer(struct lidar_data *data, u8 reg, u8 *val, int len)
{
struct i2c_client *client = data->client;
struct i2c_msg msg[2];
int ret;
msg[0].addr = client->addr;
msg[0].flags = client->flags | I2C_M_STOP;
msg[0].len = 1;
msg[0].buf = (char *) &reg;
msg[1].addr = client->addr;
msg[1].flags = client->flags | I2C_M_RD;
msg[1].len = len;
msg[1].buf = (char *) val;
ret = i2c_transfer(client->adapter, msg, 2);
return (ret == 2) ? 0 : -EIO;
}
static int lidar_smbus_xfer(struct lidar_data *data, u8 reg, u8 *val, int len)
{
struct i2c_client *client = data->client;
int ret;
/*
* Device needs a STOP condition between address write, and data read
* so in turn i2c_smbus_read_byte_data cannot be used
*/
while (len--) {
ret = i2c_smbus_write_byte(client, reg++);
if (ret < 0) {
dev_err(&client->dev, "cannot write addr value");
return ret;
}
ret = i2c_smbus_read_byte(client);
if (ret < 0) {
dev_err(&client->dev, "cannot read data value");
return ret;
}
*(val++) = ret;
}
return 0;
}
static int lidar_read_byte(struct lidar_data *data, u8 reg)
{
int ret;
u8 val;
ret = data->xfer(data, reg, &val, 1);
if (ret < 0)
return ret;
return val;
}
static inline int lidar_write_control(struct lidar_data *data, int val)
{
return i2c_smbus_write_byte_data(data->client, LIDAR_REG_CONTROL, val);
}
static inline int lidar_write_power(struct lidar_data *data, int val)
{
return i2c_smbus_write_byte_data(data->client,
LIDAR_REG_PWR_CONTROL, val);
}
static int lidar_read_measurement(struct lidar_data *data, u16 *reg)
{
__be16 value;
int ret = data->xfer(data, LIDAR_REG_DATA_HBYTE |
(data->i2c_enabled ? LIDAR_REG_DATA_WORD_READ : 0),
(u8 *) &value, 2);
if (!ret)
*reg = be16_to_cpu(value);
return ret;
}
static int lidar_get_measurement(struct lidar_data *data, u16 *reg)
{
struct i2c_client *client = data->client;
int tries = 10;
int ret;
ret = pm_runtime_resume_and_get(&client->dev);
if (ret < 0)
return ret;
/* start sample */
ret = lidar_write_control(data, LIDAR_REG_CONTROL_ACQUIRE);
if (ret < 0) {
dev_err(&client->dev, "cannot send start measurement command");
pm_runtime_put_noidle(&client->dev);
return ret;
}
while (tries--) {
usleep_range(1000, 2000);
ret = lidar_read_byte(data, LIDAR_REG_STATUS);
if (ret < 0)
break;
/* return -EINVAL since laser is likely pointed out of range */
if (ret & LIDAR_REG_STATUS_INVALID) {
*reg = 0;
ret = -EINVAL;
break;
}
/* sample ready to read */
if (!(ret & LIDAR_REG_STATUS_READY)) {
ret = lidar_read_measurement(data, reg);
break;
}
ret = -EIO;
}
pm_runtime_put_autosuspend(&client->dev);
return ret;
}
static int lidar_read_raw(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan,
int *val, int *val2, long mask)
{
struct lidar_data *data = iio_priv(indio_dev);
int ret = -EINVAL;
switch (mask) {
case IIO_CHAN_INFO_RAW: {
u16 reg;
if (!iio_device_claim_direct(indio_dev))
return -EBUSY;
ret = lidar_get_measurement(data, &reg);
if (!ret) {
*val = reg;
ret = IIO_VAL_INT;
}
iio_device_release_direct(indio_dev);
break;
}
case IIO_CHAN_INFO_SCALE:
*val = 0;
*val2 = 10000;
ret = IIO_VAL_INT_PLUS_MICRO;
break;
}
return ret;
}
static irqreturn_t lidar_trigger_handler(int irq, void *private)
{
struct iio_poll_func *pf = private;
struct iio_dev *indio_dev = pf->indio_dev;
struct lidar_data *data = iio_priv(indio_dev);
int ret;
struct {
u16 chan;
aligned_s64 timestamp;
} scan = { };
ret = lidar_get_measurement(data, &scan.chan);
if (!ret) {
iio_push_to_buffers_with_ts(indio_dev, &scan, sizeof(scan),
iio_get_time_ns(indio_dev));
} else if (ret != -EINVAL) {
dev_err(&data->client->dev, "cannot read LIDAR measurement");
}
iio_trigger_notify_done(indio_dev->trig);
return IRQ_HANDLED;
}
static const struct iio_info lidar_info = {
.read_raw = lidar_read_raw,
};
static int lidar_probe(struct i2c_client *client)
{
struct lidar_data *data;
struct iio_dev *indio_dev;
int ret;
indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
if (!indio_dev)
return -ENOMEM;
data = iio_priv(indio_dev);
if (i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
data->xfer = lidar_i2c_xfer;
data->i2c_enabled = 1;
} else if (i2c_check_functionality(client->adapter,
I2C_FUNC_SMBUS_WORD_DATA | I2C_FUNC_SMBUS_BYTE))
data->xfer = lidar_smbus_xfer;
else
return -EOPNOTSUPP;
indio_dev->info = &lidar_info;
indio_dev->name = LIDAR_DRV_NAME;
indio_dev->channels = lidar_channels;
indio_dev->num_channels = ARRAY_SIZE(lidar_channels);
indio_dev->modes = INDIO_DIRECT_MODE;
i2c_set_clientdata(client, indio_dev);
data->client = client;
data->indio_dev = indio_dev;
ret = iio_triggered_buffer_setup(indio_dev, NULL,
lidar_trigger_handler, NULL);
if (ret)
return ret;
ret = iio_device_register(indio_dev);
if (ret)
goto error_unreg_buffer;
pm_runtime_set_autosuspend_delay(&client->dev, 1000);
pm_runtime_use_autosuspend(&client->dev);
ret = pm_runtime_set_active(&client->dev);
if (ret)
goto error_unreg_buffer;
pm_runtime_enable(&client->dev);
pm_runtime_idle(&client->dev);
return 0;
error_unreg_buffer:
iio_triggered_buffer_cleanup(indio_dev);
return ret;
}
static void lidar_remove(struct i2c_client *client)
{
struct iio_dev *indio_dev = i2c_get_clientdata(client);
iio_device_unregister(indio_dev);
iio_triggered_buffer_cleanup(indio_dev);
pm_runtime_disable(&client->dev);
pm_runtime_set_suspended(&client->dev);
}
static const struct i2c_device_id lidar_id[] = {
{ .name = "lidar-lite-v2" },
{ .name = "lidar-lite-v3" },
{ }
};
MODULE_DEVICE_TABLE(i2c, lidar_id);
static const struct of_device_id lidar_dt_ids[] = {
{ .compatible = "pulsedlight,lidar-lite-v2" },
{ .compatible = "grmn,lidar-lite-v3" },
{ }
};
MODULE_DEVICE_TABLE(of, lidar_dt_ids);
static int lidar_pm_runtime_suspend(struct device *dev)
{
struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev));
struct lidar_data *data = iio_priv(indio_dev);
return lidar_write_power(data, 0x0f);
}
static int lidar_pm_runtime_resume(struct device *dev)
{
struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev));
struct lidar_data *data = iio_priv(indio_dev);
int ret = lidar_write_power(data, 0);
/* regulator and FPGA needs settling time */
usleep_range(15000, 20000);
return ret;
}
static const struct dev_pm_ops lidar_pm_ops = {
RUNTIME_PM_OPS(lidar_pm_runtime_suspend, lidar_pm_runtime_resume, NULL)
};
static struct i2c_driver lidar_driver = {
.driver = {
.name = LIDAR_DRV_NAME,
.of_match_table = lidar_dt_ids,
.pm = pm_ptr(&lidar_pm_ops),
},
.probe = lidar_probe,
.remove = lidar_remove,
.id_table = lidar_id,
};
module_i2c_driver(lidar_driver);
MODULE_AUTHOR("Matt Ranostay <matt.ranostay@konsulko.com>");
MODULE_DESCRIPTION("PulsedLight LIDAR sensor");
MODULE_LICENSE("GPL");
@@ -0,0 +1,461 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* rfd77402.c - Support for RF Digital RFD77402 Time-of-Flight (distance) sensor
*
* Copyright 2017 Peter Meerwald-Stadler <pmeerw@pmeerw.net>
*
* 7-bit I2C slave address 0x4c
*
* https://media.digikey.com/pdf/Data%20Sheets/RF%20Digital%20PDFs/RFD77402.pdf
*/
#include <linux/bits.h>
#include <linux/completion.h>
#include <linux/delay.h>
#include <linux/dev_printk.h>
#include <linux/errno.h>
#include <linux/i2c.h>
#include <linux/interrupt.h>
#include <linux/iopoll.h>
#include <linux/jiffies.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/iio/iio.h>
#define RFD77402_DRV_NAME "rfd77402"
#define RFD77402_ICSR 0x00 /* Interrupt Control Status Register */
#define RFD77402_ICSR_CLR_CFG BIT(0)
#define RFD77402_ICSR_CLR_TYPE BIT(1)
#define RFD77402_ICSR_INT_MODE BIT(2)
#define RFD77402_ICSR_INT_POL BIT(3)
#define RFD77402_ICSR_RESULT BIT(4)
#define RFD77402_ICSR_M2H_MSG BIT(5)
#define RFD77402_ICSR_H2M_MSG BIT(6)
#define RFD77402_ICSR_RESET BIT(7)
#define RFD77402_IER 0x02
#define RFD77402_IER_RESULT BIT(0)
#define RFD77402_IER_M2H_MSG BIT(1)
#define RFD77402_IER_H2M_MSG BIT(2)
#define RFD77402_IER_RESET BIT(3)
#define RFD77402_CMD_R 0x04
#define RFD77402_CMD_SINGLE 0x01
#define RFD77402_CMD_STANDBY 0x10
#define RFD77402_CMD_MCPU_OFF 0x11
#define RFD77402_CMD_MCPU_ON 0x12
#define RFD77402_CMD_RESET BIT(6)
#define RFD77402_CMD_VALID BIT(7)
#define RFD77402_STATUS_R 0x06
#define RFD77402_STATUS_PM_MASK GENMASK(4, 0)
#define RFD77402_STATUS_STANDBY 0x00
#define RFD77402_STATUS_MCPU_OFF 0x10
#define RFD77402_STATUS_MCPU_ON 0x18
#define RFD77402_RESULT_R 0x08
#define RFD77402_RESULT_DIST_MASK GENMASK(12, 2)
#define RFD77402_RESULT_ERR_MASK GENMASK(14, 13)
#define RFD77402_RESULT_VALID BIT(15)
#define RFD77402_PMU_CFG 0x14
#define RFD77402_PMU_MCPU_INIT BIT(9)
#define RFD77402_I2C_INIT_CFG 0x1c
#define RFD77402_I2C_ADDR_INCR BIT(0)
#define RFD77402_I2C_DATA_INCR BIT(2)
#define RFD77402_I2C_HOST_DEBUG BIT(5)
#define RFD77402_I2C_MCPU_DEBUG BIT(6)
#define RFD77402_CMD_CFGR_A 0x0c
#define RFD77402_CMD_CFGR_B 0x0e
#define RFD77402_HFCFG_0 0x20
#define RFD77402_HFCFG_1 0x22
#define RFD77402_HFCFG_2 0x24
#define RFD77402_HFCFG_3 0x26
#define RFD77402_MOD_CHIP_ID 0x28
/* magic configuration values from datasheet */
static const struct {
u8 reg;
u16 val;
} rf77402_tof_config[] = {
{RFD77402_CMD_CFGR_A, 0xe100},
{RFD77402_CMD_CFGR_B, 0x10ff},
{RFD77402_HFCFG_0, 0x07d0},
{RFD77402_HFCFG_1, 0x5008},
{RFD77402_HFCFG_2, 0xa041},
{RFD77402_HFCFG_3, 0x45d4},
};
/**
* struct rfd77402_data - device-specific data for the RFD77402 sensor
* @client: I2C client handle
* @lock: mutex to serialize sensor reads
* @completion: completion used for interrupt-driven measurements
* @irq_en: indicates whether interrupt mode is enabled
*/
struct rfd77402_data {
struct i2c_client *client;
struct mutex lock;
struct completion completion;
bool irq_en;
};
static const struct iio_chan_spec rfd77402_channels[] = {
{
.type = IIO_DISTANCE,
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
BIT(IIO_CHAN_INFO_SCALE),
},
};
static irqreturn_t rfd77402_interrupt_handler(int irq, void *pdata)
{
struct rfd77402_data *data = pdata;
int ret;
ret = i2c_smbus_read_byte_data(data->client, RFD77402_ICSR);
if (ret < 0)
return IRQ_NONE;
/* Check if the interrupt is from our device */
if (!(ret & RFD77402_ICSR_RESULT))
return IRQ_NONE;
/* Signal completion of measurement */
complete(&data->completion);
return IRQ_HANDLED;
}
static int rfd77402_wait_for_irq(struct rfd77402_data *data)
{
int ret;
/*
* According to RFD77402 Datasheet v1.8,
* Section 3.1.1 "Single Measure" (Figure: Single Measure Flow Chart),
* the suggested timeout for single measure is 100 ms.
*/
ret = wait_for_completion_timeout(&data->completion,
msecs_to_jiffies(100));
if (ret == 0)
return -ETIMEDOUT;
return 0;
}
static int rfd77402_set_state(struct i2c_client *client, u8 state, u16 check)
{
int ret;
ret = i2c_smbus_write_byte_data(client, RFD77402_CMD_R,
state | RFD77402_CMD_VALID);
if (ret < 0)
return ret;
usleep_range(10000, 20000);
ret = i2c_smbus_read_word_data(client, RFD77402_STATUS_R);
if (ret < 0)
return ret;
if ((ret & RFD77402_STATUS_PM_MASK) != check)
return -ENODEV;
return 0;
}
static int rfd77402_wait_for_result(struct rfd77402_data *data)
{
struct i2c_client *client = data->client;
int val, ret;
if (data->irq_en)
return rfd77402_wait_for_irq(data);
/*
* As per RFD77402 datasheet section '3.1.1 Single Measure', the
* suggested timeout value for single measure is 100ms.
*/
ret = read_poll_timeout(i2c_smbus_read_byte_data, val,
(val < 0) || (val & RFD77402_ICSR_RESULT),
10 * USEC_PER_MSEC,
10 * 10 * USEC_PER_MSEC,
false,
client, RFD77402_ICSR);
if (val < 0)
return val;
return ret;
}
static int rfd77402_measure(struct rfd77402_data *data)
{
struct i2c_client *client = data->client;
int ret;
ret = rfd77402_set_state(client, RFD77402_CMD_MCPU_ON,
RFD77402_STATUS_MCPU_ON);
if (ret < 0)
return ret;
if (data->irq_en)
reinit_completion(&data->completion);
ret = i2c_smbus_write_byte_data(client, RFD77402_CMD_R,
RFD77402_CMD_SINGLE |
RFD77402_CMD_VALID);
if (ret < 0)
goto err;
ret = rfd77402_wait_for_result(data);
if (ret < 0)
goto err;
ret = i2c_smbus_read_word_data(client, RFD77402_RESULT_R);
if (ret < 0)
goto err;
if ((ret & RFD77402_RESULT_ERR_MASK) ||
!(ret & RFD77402_RESULT_VALID)) {
ret = -EIO;
goto err;
}
return (ret & RFD77402_RESULT_DIST_MASK) >> 2;
err:
rfd77402_set_state(client, RFD77402_CMD_MCPU_OFF,
RFD77402_STATUS_MCPU_OFF);
return ret;
}
static int rfd77402_read_raw(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan,
int *val, int *val2, long mask)
{
struct rfd77402_data *data = iio_priv(indio_dev);
int ret;
switch (mask) {
case IIO_CHAN_INFO_RAW:
mutex_lock(&data->lock);
ret = rfd77402_measure(data);
mutex_unlock(&data->lock);
if (ret < 0)
return ret;
*val = ret;
return IIO_VAL_INT;
case IIO_CHAN_INFO_SCALE:
/* 1 LSB is 1 mm */
*val = 0;
*val2 = 1000;
return IIO_VAL_INT_PLUS_MICRO;
default:
return -EINVAL;
}
}
static const struct iio_info rfd77402_info = {
.read_raw = rfd77402_read_raw,
};
static int rfd77402_config_irq(struct i2c_client *client, u8 csr, u8 ier)
{
int ret;
ret = i2c_smbus_write_byte_data(client, RFD77402_ICSR, csr);
if (ret)
return ret;
return i2c_smbus_write_byte_data(client, RFD77402_IER, ier);
}
static int rfd77402_init(struct rfd77402_data *data)
{
struct i2c_client *client = data->client;
int ret, i;
ret = rfd77402_set_state(client, RFD77402_CMD_STANDBY,
RFD77402_STATUS_STANDBY);
if (ret < 0)
return ret;
if (data->irq_en) {
/*
* Enable interrupt mode:
* - Configure ICSR for auto-clear on read and
* push-pull output
* - Enable "result ready" interrupt in IER
*/
ret = rfd77402_config_irq(client,
RFD77402_ICSR_CLR_CFG |
RFD77402_ICSR_INT_MODE,
RFD77402_IER_RESULT);
} else {
/*
* Disable all interrupts:
* - Clear ICSR configuration
* - Disable all interrupts in IER
*/
ret = rfd77402_config_irq(client, 0, 0);
}
if (ret)
return ret;
/* I2C configuration */
ret = i2c_smbus_write_word_data(client, RFD77402_I2C_INIT_CFG,
RFD77402_I2C_ADDR_INCR |
RFD77402_I2C_DATA_INCR |
RFD77402_I2C_HOST_DEBUG |
RFD77402_I2C_MCPU_DEBUG);
if (ret < 0)
return ret;
/* set initialization */
ret = i2c_smbus_write_word_data(client, RFD77402_PMU_CFG, 0x0500);
if (ret < 0)
return ret;
ret = rfd77402_set_state(client, RFD77402_CMD_MCPU_OFF,
RFD77402_STATUS_MCPU_OFF);
if (ret < 0)
return ret;
/* set initialization */
ret = i2c_smbus_write_word_data(client, RFD77402_PMU_CFG, 0x0600);
if (ret < 0)
return ret;
ret = rfd77402_set_state(client, RFD77402_CMD_MCPU_ON,
RFD77402_STATUS_MCPU_ON);
if (ret < 0)
return ret;
for (i = 0; i < ARRAY_SIZE(rf77402_tof_config); i++) {
ret = i2c_smbus_write_word_data(client,
rf77402_tof_config[i].reg,
rf77402_tof_config[i].val);
if (ret < 0)
return ret;
}
ret = rfd77402_set_state(client, RFD77402_CMD_STANDBY,
RFD77402_STATUS_STANDBY);
return ret;
}
static int rfd77402_powerdown(struct i2c_client *client)
{
return rfd77402_set_state(client, RFD77402_CMD_STANDBY,
RFD77402_STATUS_STANDBY);
}
static void rfd77402_disable(void *client)
{
rfd77402_powerdown(client);
}
static int rfd77402_probe(struct i2c_client *client)
{
struct rfd77402_data *data;
struct iio_dev *indio_dev;
int ret;
ret = i2c_smbus_read_word_data(client, RFD77402_MOD_CHIP_ID);
if (ret < 0)
return ret;
if (ret != 0xad01 && ret != 0xad02) /* known chip ids */
return -ENODEV;
indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
if (!indio_dev)
return -ENOMEM;
data = iio_priv(indio_dev);
data->client = client;
ret = devm_mutex_init(&client->dev, &data->lock);
if (ret)
return ret;
init_completion(&data->completion);
if (client->irq > 0) {
ret = devm_request_threaded_irq(&client->dev, client->irq,
NULL, rfd77402_interrupt_handler,
IRQF_ONESHOT,
"rfd77402", data);
if (ret)
return ret;
data->irq_en = true;
dev_dbg(&client->dev, "Using interrupt mode\n");
} else {
dev_dbg(&client->dev, "Using polling mode\n");
}
indio_dev->info = &rfd77402_info;
indio_dev->channels = rfd77402_channels;
indio_dev->num_channels = ARRAY_SIZE(rfd77402_channels);
indio_dev->name = RFD77402_DRV_NAME;
indio_dev->modes = INDIO_DIRECT_MODE;
ret = rfd77402_init(data);
if (ret < 0)
return ret;
ret = devm_add_action_or_reset(&client->dev, rfd77402_disable, client);
if (ret)
return ret;
return devm_iio_device_register(&client->dev, indio_dev);
}
static int rfd77402_suspend(struct device *dev)
{
return rfd77402_powerdown(to_i2c_client(dev));
}
static int rfd77402_resume(struct device *dev)
{
struct iio_dev *indio_dev = dev_get_drvdata(dev);
struct rfd77402_data *data = iio_priv(indio_dev);
return rfd77402_init(data);
}
static DEFINE_SIMPLE_DEV_PM_OPS(rfd77402_pm_ops, rfd77402_suspend,
rfd77402_resume);
static const struct i2c_device_id rfd77402_id[] = {
{ .name = "rfd77402" },
{ }
};
MODULE_DEVICE_TABLE(i2c, rfd77402_id);
static const struct of_device_id rfd77402_of_match[] = {
{ .compatible = "rfdigital,rfd77402" },
{ }
};
MODULE_DEVICE_TABLE(of, rfd77402_of_match);
static struct i2c_driver rfd77402_driver = {
.driver = {
.name = RFD77402_DRV_NAME,
.pm = pm_sleep_ptr(&rfd77402_pm_ops),
.of_match_table = rfd77402_of_match,
},
.probe = rfd77402_probe,
.id_table = rfd77402_id,
};
module_i2c_driver(rfd77402_driver);
MODULE_AUTHOR("Peter Meerwald-Stadler <pmeerw@pmeerw.net>");
MODULE_DESCRIPTION("RFD77402 Time-of-Flight sensor driver");
MODULE_LICENSE("GPL");
+400
View File
@@ -0,0 +1,400 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* SRF04: ultrasonic sensor for distance measuring by using GPIOs
*
* Copyright (c) 2017 Andreas Klinger <ak@it-klinger.de>
*
* For details about the device see:
* https://www.robot-electronics.co.uk/htm/srf04tech.htm
*
* the measurement cycle as timing diagram looks like:
*
* +---+
* GPIO | |
* trig: --+ +------------------------------------------------------
* ^ ^
* |<->|
* udelay(trigger_pulse_us)
*
* ultra +-+ +-+ +-+
* sonic | | | | | |
* burst: ---------+ +-+ +-+ +-----------------------------------------
* .
* ultra . +-+ +-+ +-+
* sonic . | | | | | |
* echo: ----------------------------------+ +-+ +-+ +----------------
* . .
* +------------------------+
* GPIO | |
* echo: -------------------+ +---------------
* ^ ^
* interrupt interrupt
* (ts_rising) (ts_falling)
* |<---------------------->|
* pulse time measured
* --> one round trip of ultra sonic waves
*/
#include <linux/err.h>
#include <linux/gpio/consumer.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/property.h>
#include <linux/sched.h>
#include <linux/interrupt.h>
#include <linux/delay.h>
#include <linux/pm_runtime.h>
#include <linux/iio/iio.h>
#include <linux/iio/sysfs.h>
struct srf04_cfg {
unsigned long trigger_pulse_us;
};
struct srf04_data {
struct device *dev;
struct gpio_desc *gpiod_trig;
struct gpio_desc *gpiod_echo;
struct gpio_desc *gpiod_power;
struct mutex lock;
int irqnr;
ktime_t ts_rising;
ktime_t ts_falling;
struct completion rising;
struct completion falling;
const struct srf04_cfg *cfg;
int startup_time_ms;
};
static const struct srf04_cfg srf04_cfg = {
.trigger_pulse_us = 10,
};
static const struct srf04_cfg mb_lv_cfg = {
.trigger_pulse_us = 20,
};
static irqreturn_t srf04_handle_irq(int irq, void *dev_id)
{
struct iio_dev *indio_dev = dev_id;
struct srf04_data *data = iio_priv(indio_dev);
ktime_t now = ktime_get();
if (gpiod_get_value(data->gpiod_echo)) {
data->ts_rising = now;
complete(&data->rising);
} else {
data->ts_falling = now;
complete(&data->falling);
}
return IRQ_HANDLED;
}
static int srf04_read(struct srf04_data *data)
{
int ret;
ktime_t ktime_dt;
u64 dt_ns;
u32 time_ns, distance_mm;
if (data->gpiod_power) {
ret = pm_runtime_resume_and_get(data->dev);
if (ret < 0)
return ret;
}
/*
* just one read-echo-cycle can take place at a time
* ==> lock against concurrent reading calls
*/
mutex_lock(&data->lock);
reinit_completion(&data->rising);
reinit_completion(&data->falling);
gpiod_set_value(data->gpiod_trig, 1);
udelay(data->cfg->trigger_pulse_us);
gpiod_set_value(data->gpiod_trig, 0);
if (data->gpiod_power)
pm_runtime_put_autosuspend(data->dev);
/* it should not take more than 20 ms until echo is rising */
ret = wait_for_completion_killable_timeout(&data->rising, HZ/50);
if (ret < 0) {
mutex_unlock(&data->lock);
return ret;
} else if (ret == 0) {
mutex_unlock(&data->lock);
return -ETIMEDOUT;
}
/* it cannot take more than 50 ms until echo is falling */
ret = wait_for_completion_killable_timeout(&data->falling, HZ/20);
if (ret < 0) {
mutex_unlock(&data->lock);
return ret;
} else if (ret == 0) {
mutex_unlock(&data->lock);
return -ETIMEDOUT;
}
ktime_dt = ktime_sub(data->ts_falling, data->ts_rising);
mutex_unlock(&data->lock);
dt_ns = ktime_to_ns(ktime_dt);
/*
* measuring more than 6,45 meters is beyond the capabilities of
* the supported sensors
* ==> filter out invalid results for not measuring echos of
* another us sensor
*
* formula:
* distance 6,45 * 2 m
* time = ---------- = ------------ = 40438871 ns
* speed 319 m/s
*
* using a minimum speed at -20 °C of 319 m/s
*/
if (dt_ns > 40438871)
return -EIO;
time_ns = dt_ns;
/*
* the speed as function of the temperature is approximately:
*
* speed = 331,5 + 0,6 * Temp
* with Temp in °C
* and speed in m/s
*
* use 343,5 m/s as ultrasonic speed at 20 °C here in absence of the
* temperature
*
* therefore:
* time 343,5 time * 106
* distance = ------ * ------- = ------------
* 10^6 2 617176
* with time in ns
* and distance in mm (one way)
*
* because we limit to 6,45 meters the multiplication with 106 just
* fits into 32 bit
*/
distance_mm = time_ns * 106 / 617176;
return distance_mm;
}
static int srf04_read_raw(struct iio_dev *indio_dev,
struct iio_chan_spec const *channel, int *val,
int *val2, long info)
{
struct srf04_data *data = iio_priv(indio_dev);
int ret;
if (channel->type != IIO_DISTANCE)
return -EINVAL;
switch (info) {
case IIO_CHAN_INFO_RAW:
ret = srf04_read(data);
if (ret < 0)
return ret;
*val = ret;
return IIO_VAL_INT;
case IIO_CHAN_INFO_SCALE:
/*
* theoretical maximum resolution is 3 mm
* 1 LSB is 1 mm
*/
*val = 0;
*val2 = 1000;
return IIO_VAL_INT_PLUS_MICRO;
default:
return -EINVAL;
}
}
static const struct iio_info srf04_iio_info = {
.read_raw = srf04_read_raw,
};
static const struct iio_chan_spec srf04_chan_spec[] = {
{
.type = IIO_DISTANCE,
.info_mask_separate =
BIT(IIO_CHAN_INFO_RAW) |
BIT(IIO_CHAN_INFO_SCALE),
},
};
static const struct of_device_id of_srf04_match[] = {
{ .compatible = "devantech,srf04", .data = &srf04_cfg },
{ .compatible = "maxbotix,mb1000", .data = &mb_lv_cfg },
{ .compatible = "maxbotix,mb1010", .data = &mb_lv_cfg },
{ .compatible = "maxbotix,mb1020", .data = &mb_lv_cfg },
{ .compatible = "maxbotix,mb1030", .data = &mb_lv_cfg },
{ .compatible = "maxbotix,mb1040", .data = &mb_lv_cfg },
{ }
};
MODULE_DEVICE_TABLE(of, of_srf04_match);
static int srf04_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct srf04_data *data;
struct iio_dev *indio_dev;
int ret;
indio_dev = devm_iio_device_alloc(dev, sizeof(struct srf04_data));
if (!indio_dev)
return -ENOMEM;
data = iio_priv(indio_dev);
data->dev = dev;
data->cfg = device_get_match_data(dev);
mutex_init(&data->lock);
init_completion(&data->rising);
init_completion(&data->falling);
data->gpiod_trig = devm_gpiod_get(dev, "trig", GPIOD_OUT_LOW);
if (IS_ERR(data->gpiod_trig)) {
dev_err(dev, "failed to get trig-gpios: err=%ld\n",
PTR_ERR(data->gpiod_trig));
return PTR_ERR(data->gpiod_trig);
}
data->gpiod_echo = devm_gpiod_get(dev, "echo", GPIOD_IN);
if (IS_ERR(data->gpiod_echo)) {
dev_err(dev, "failed to get echo-gpios: err=%ld\n",
PTR_ERR(data->gpiod_echo));
return PTR_ERR(data->gpiod_echo);
}
data->gpiod_power = devm_gpiod_get_optional(dev, "power",
GPIOD_OUT_LOW);
if (IS_ERR(data->gpiod_power)) {
dev_err(dev, "failed to get power-gpios: err=%ld\n",
PTR_ERR(data->gpiod_power));
return PTR_ERR(data->gpiod_power);
}
if (data->gpiod_power) {
data->startup_time_ms = 100;
device_property_read_u32(dev, "startup-time-ms", &data->startup_time_ms);
dev_dbg(dev, "using power gpio: startup-time-ms=%d\n",
data->startup_time_ms);
}
if (gpiod_cansleep(data->gpiod_echo)) {
dev_err(data->dev, "cansleep-GPIOs not supported\n");
return -ENODEV;
}
data->irqnr = gpiod_to_irq(data->gpiod_echo);
if (data->irqnr < 0) {
dev_err(data->dev, "gpiod_to_irq: %d\n", data->irqnr);
return data->irqnr;
}
ret = devm_request_irq(dev, data->irqnr, srf04_handle_irq,
IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
pdev->name, indio_dev);
if (ret < 0) {
dev_err(data->dev, "request_irq: %d\n", ret);
return ret;
}
platform_set_drvdata(pdev, indio_dev);
indio_dev->name = "srf04";
indio_dev->info = &srf04_iio_info;
indio_dev->modes = INDIO_DIRECT_MODE;
indio_dev->channels = srf04_chan_spec;
indio_dev->num_channels = ARRAY_SIZE(srf04_chan_spec);
ret = iio_device_register(indio_dev);
if (ret < 0) {
dev_err(data->dev, "iio_device_register: %d\n", ret);
return ret;
}
if (data->gpiod_power) {
pm_runtime_set_autosuspend_delay(data->dev, 1000);
pm_runtime_use_autosuspend(data->dev);
ret = pm_runtime_set_active(data->dev);
if (ret) {
dev_err(data->dev, "pm_runtime_set_active: %d\n", ret);
iio_device_unregister(indio_dev);
}
pm_runtime_enable(data->dev);
pm_runtime_idle(data->dev);
}
return ret;
}
static void srf04_remove(struct platform_device *pdev)
{
struct iio_dev *indio_dev = platform_get_drvdata(pdev);
struct srf04_data *data = iio_priv(indio_dev);
iio_device_unregister(indio_dev);
if (data->gpiod_power) {
pm_runtime_disable(data->dev);
pm_runtime_set_suspended(data->dev);
}
}
static int srf04_pm_runtime_suspend(struct device *dev)
{
struct platform_device *pdev = container_of(dev,
struct platform_device, dev);
struct iio_dev *indio_dev = platform_get_drvdata(pdev);
struct srf04_data *data = iio_priv(indio_dev);
gpiod_set_value(data->gpiod_power, 0);
return 0;
}
static int srf04_pm_runtime_resume(struct device *dev)
{
struct platform_device *pdev = container_of(dev,
struct platform_device, dev);
struct iio_dev *indio_dev = platform_get_drvdata(pdev);
struct srf04_data *data = iio_priv(indio_dev);
gpiod_set_value(data->gpiod_power, 1);
msleep(data->startup_time_ms);
return 0;
}
static const struct dev_pm_ops srf04_pm_ops = {
RUNTIME_PM_OPS(srf04_pm_runtime_suspend,
srf04_pm_runtime_resume, NULL)
};
static struct platform_driver srf04_driver = {
.probe = srf04_probe,
.remove = srf04_remove,
.driver = {
.name = "srf04-gpio",
.of_match_table = of_srf04_match,
.pm = pm_ptr(&srf04_pm_ops),
},
};
module_platform_driver(srf04_driver);
MODULE_AUTHOR("Andreas Klinger <ak@it-klinger.de>");
MODULE_DESCRIPTION("SRF04 ultrasonic sensor for distance measuring using GPIOs");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:srf04");
+552
View File
@@ -0,0 +1,552 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* srf08.c - Support for Devantech SRFxx ultrasonic ranger
* with i2c interface
* actually supported are srf02, srf08, srf10
*
* Copyright (c) 2016, 2017 Andreas Klinger <ak@it-klinger.de>
*
* For details about the device see:
* https://www.robot-electronics.co.uk/htm/srf08tech.html
* https://www.robot-electronics.co.uk/htm/srf10tech.htm
* https://www.robot-electronics.co.uk/htm/srf02tech.htm
*/
#include <linux/err.h>
#include <linux/i2c.h>
#include <linux/delay.h>
#include <linux/module.h>
#include <linux/bitops.h>
#include <linux/iio/iio.h>
#include <linux/iio/sysfs.h>
#include <linux/iio/buffer.h>
#include <linux/iio/trigger_consumer.h>
#include <linux/iio/triggered_buffer.h>
/* registers of SRF08 device */
#define SRF08_WRITE_COMMAND 0x00 /* Command Register */
#define SRF08_WRITE_MAX_GAIN 0x01 /* Max Gain Register: 0 .. 31 */
#define SRF08_WRITE_RANGE 0x02 /* Range Register: 0 .. 255 */
#define SRF08_READ_SW_REVISION 0x00 /* Software Revision */
#define SRF08_READ_LIGHT 0x01 /* Light Sensor during last echo */
#define SRF08_READ_ECHO_1_HIGH 0x02 /* Range of first echo received */
#define SRF08_READ_ECHO_1_LOW 0x03 /* Range of first echo received */
#define SRF08_CMD_RANGING_CM 0x51 /* Ranging Mode - Result in cm */
enum srf08_sensor_type {
SRF02,
SRF08,
SRF10,
SRF_MAX_TYPE
};
struct srf08_chip_info {
const int *sensitivity_avail;
int num_sensitivity_avail;
int sensitivity_default;
/* default value of Range in mm */
int range_default;
};
struct srf08_data {
struct i2c_client *client;
/*
* Gain in the datasheet is called sensitivity here to distinct it
* from the gain used with amplifiers of adc's
*/
int sensitivity;
/* max. Range in mm */
int range_mm;
struct mutex lock;
/* Sensor-Type */
enum srf08_sensor_type sensor_type;
/* Chip-specific information */
const struct srf08_chip_info *chip_info;
};
/*
* in the documentation one can read about the "Gain" of the device
* which is used here for amplifying the signal and filtering out unwanted
* ones.
* But with ADC's this term is already used differently and that's why it
* is called "Sensitivity" here.
*/
static const struct srf08_chip_info srf02_chip_info = {
.sensitivity_avail = NULL,
.num_sensitivity_avail = 0,
.sensitivity_default = 0,
.range_default = 0,
};
static const int srf08_sensitivity_avail[] = {
94, 97, 100, 103, 107, 110, 114, 118,
123, 128, 133, 139, 145, 152, 159, 168,
177, 187, 199, 212, 227, 245, 265, 288,
317, 352, 395, 450, 524, 626, 777, 1025
};
static const struct srf08_chip_info srf08_chip_info = {
.sensitivity_avail = srf08_sensitivity_avail,
.num_sensitivity_avail = ARRAY_SIZE(srf08_sensitivity_avail),
.sensitivity_default = 1025,
.range_default = 6020,
};
static const int srf10_sensitivity_avail[] = {
40, 40, 50, 60, 70, 80, 100, 120,
140, 200, 250, 300, 350, 400, 500, 600,
700,
};
static const struct srf08_chip_info srf10_chip_info = {
.sensitivity_avail = srf10_sensitivity_avail,
.num_sensitivity_avail = ARRAY_SIZE(srf10_sensitivity_avail),
.sensitivity_default = 700,
.range_default = 6020,
};
static int srf08_read_ranging(struct srf08_data *data)
{
struct i2c_client *client = data->client;
int ret, i;
int waittime;
mutex_lock(&data->lock);
ret = i2c_smbus_write_byte_data(data->client,
SRF08_WRITE_COMMAND, SRF08_CMD_RANGING_CM);
if (ret < 0) {
dev_err(&client->dev, "write command - err: %d\n", ret);
mutex_unlock(&data->lock);
return ret;
}
/*
* we read here until a correct version number shows up as
* suggested by the documentation
*
* with an ultrasonic speed of 343 m/s and a roundtrip of it
* sleep the expected duration and try to read from the device
* if nothing useful is read try it in a shorter grid
*
* polling for not more than 20 ms should be enough
*/
waittime = 1 + data->range_mm / 172;
msleep(waittime);
for (i = 0; i < 4; i++) {
ret = i2c_smbus_read_byte_data(data->client,
SRF08_READ_SW_REVISION);
/* check if a valid version number is read */
if (ret < 255 && ret > 0)
break;
msleep(5);
}
if (ret >= 255 || ret <= 0) {
dev_err(&client->dev, "device not ready\n");
mutex_unlock(&data->lock);
return -EIO;
}
ret = i2c_smbus_read_word_swapped(data->client,
SRF08_READ_ECHO_1_HIGH);
if (ret < 0) {
dev_err(&client->dev, "cannot read distance: ret=%d\n", ret);
mutex_unlock(&data->lock);
return ret;
}
mutex_unlock(&data->lock);
return ret;
}
static irqreturn_t srf08_trigger_handler(int irq, void *p)
{
struct iio_poll_func *pf = p;
struct iio_dev *indio_dev = pf->indio_dev;
struct srf08_data *data = iio_priv(indio_dev);
struct {
s16 chan;
aligned_s64 timestamp;
} scan = { };
scan.chan = srf08_read_ranging(data);
if (scan.chan < 0)
goto err;
mutex_lock(&data->lock);
iio_push_to_buffers_with_ts(indio_dev, &scan, sizeof(scan),
pf->timestamp);
mutex_unlock(&data->lock);
err:
iio_trigger_notify_done(indio_dev->trig);
return IRQ_HANDLED;
}
static int srf08_read_raw(struct iio_dev *indio_dev,
struct iio_chan_spec const *channel, int *val,
int *val2, long mask)
{
struct srf08_data *data = iio_priv(indio_dev);
int ret;
if (channel->type != IIO_DISTANCE)
return -EINVAL;
switch (mask) {
case IIO_CHAN_INFO_RAW:
ret = srf08_read_ranging(data);
if (ret < 0)
return ret;
*val = ret;
return IIO_VAL_INT;
case IIO_CHAN_INFO_SCALE:
/* 1 LSB is 1 cm */
*val = 0;
*val2 = 10000;
return IIO_VAL_INT_PLUS_MICRO;
default:
return -EINVAL;
}
}
static ssize_t srf08_show_range_mm_available(struct device *dev,
struct device_attribute *attr, char *buf)
{
return sysfs_emit(buf, "[0.043 0.043 11.008]\n");
}
static IIO_DEVICE_ATTR(sensor_max_range_available, S_IRUGO,
srf08_show_range_mm_available, NULL, 0);
static ssize_t srf08_show_range_mm(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct iio_dev *indio_dev = dev_to_iio_dev(dev);
struct srf08_data *data = iio_priv(indio_dev);
return sysfs_emit(buf, "%d.%03d\n",
data->range_mm / 1000, data->range_mm % 1000);
}
/*
* set the range of the sensor to an even multiple of 43 mm
* which corresponds to 1 LSB in the register
*
* register value corresponding range
* 0x00 43 mm
* 0x01 86 mm
* 0x02 129 mm
* ...
* 0xFF 11008 mm
*/
static ssize_t srf08_write_range_mm(struct srf08_data *data, unsigned int val)
{
int ret;
struct i2c_client *client = data->client;
unsigned int mod;
u8 regval;
ret = val / 43 - 1;
mod = val % 43;
if (mod || (ret < 0) || (ret > 255))
return -EINVAL;
regval = ret;
mutex_lock(&data->lock);
ret = i2c_smbus_write_byte_data(client, SRF08_WRITE_RANGE, regval);
if (ret < 0) {
dev_err(&client->dev, "write_range - err: %d\n", ret);
mutex_unlock(&data->lock);
return ret;
}
data->range_mm = val;
mutex_unlock(&data->lock);
return 0;
}
static ssize_t srf08_store_range_mm(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t len)
{
struct iio_dev *indio_dev = dev_to_iio_dev(dev);
struct srf08_data *data = iio_priv(indio_dev);
int ret;
int integer, fract;
ret = iio_str_to_fixpoint(buf, 100, &integer, &fract);
if (ret)
return ret;
ret = srf08_write_range_mm(data, integer * 1000 + fract);
if (ret < 0)
return ret;
return len;
}
static IIO_DEVICE_ATTR(sensor_max_range, S_IRUGO | S_IWUSR,
srf08_show_range_mm, srf08_store_range_mm, 0);
static ssize_t srf08_show_sensitivity_available(struct device *dev,
struct device_attribute *attr, char *buf)
{
int i, len = 0;
struct iio_dev *indio_dev = dev_to_iio_dev(dev);
struct srf08_data *data = iio_priv(indio_dev);
for (i = 0; i < data->chip_info->num_sensitivity_avail; i++)
if (data->chip_info->sensitivity_avail[i])
len += sysfs_emit_at(buf, len, "%d ",
data->chip_info->sensitivity_avail[i]);
len += sysfs_emit_at(buf, len, "\n");
return len;
}
static IIO_DEVICE_ATTR(sensor_sensitivity_available, S_IRUGO,
srf08_show_sensitivity_available, NULL, 0);
static ssize_t srf08_show_sensitivity(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct iio_dev *indio_dev = dev_to_iio_dev(dev);
struct srf08_data *data = iio_priv(indio_dev);
return sysfs_emit(buf, "%d\n", data->sensitivity);
}
static ssize_t srf08_write_sensitivity(struct srf08_data *data,
unsigned int val)
{
struct i2c_client *client = data->client;
int ret, i;
u8 regval;
if (!val)
return -EINVAL;
for (i = 0; i < data->chip_info->num_sensitivity_avail; i++)
if (val == data->chip_info->sensitivity_avail[i]) {
regval = i;
break;
}
if (i >= data->chip_info->num_sensitivity_avail)
return -EINVAL;
mutex_lock(&data->lock);
ret = i2c_smbus_write_byte_data(client, SRF08_WRITE_MAX_GAIN, regval);
if (ret < 0) {
dev_err(&client->dev, "write_sensitivity - err: %d\n", ret);
mutex_unlock(&data->lock);
return ret;
}
data->sensitivity = val;
mutex_unlock(&data->lock);
return 0;
}
static ssize_t srf08_store_sensitivity(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t len)
{
struct iio_dev *indio_dev = dev_to_iio_dev(dev);
struct srf08_data *data = iio_priv(indio_dev);
int ret;
unsigned int val;
ret = kstrtouint(buf, 10, &val);
if (ret)
return ret;
ret = srf08_write_sensitivity(data, val);
if (ret < 0)
return ret;
return len;
}
static IIO_DEVICE_ATTR(sensor_sensitivity, S_IRUGO | S_IWUSR,
srf08_show_sensitivity, srf08_store_sensitivity, 0);
static struct attribute *srf08_attributes[] = {
&iio_dev_attr_sensor_max_range.dev_attr.attr,
&iio_dev_attr_sensor_max_range_available.dev_attr.attr,
&iio_dev_attr_sensor_sensitivity.dev_attr.attr,
&iio_dev_attr_sensor_sensitivity_available.dev_attr.attr,
NULL,
};
static const struct attribute_group srf08_attribute_group = {
.attrs = srf08_attributes,
};
static const struct iio_chan_spec srf08_channels[] = {
{
.type = IIO_DISTANCE,
.info_mask_separate =
BIT(IIO_CHAN_INFO_RAW) |
BIT(IIO_CHAN_INFO_SCALE),
.scan_index = 0,
.scan_type = {
.sign = 's',
.realbits = 16,
.storagebits = 16,
.endianness = IIO_CPU,
},
},
IIO_CHAN_SOFT_TIMESTAMP(1),
};
static const struct iio_info srf08_info = {
.read_raw = srf08_read_raw,
.attrs = &srf08_attribute_group,
};
/*
* srf02 don't have an adjustable range or sensitivity,
* so we don't need attributes at all
*/
static const struct iio_info srf02_info = {
.read_raw = srf08_read_raw,
};
static int srf08_probe(struct i2c_client *client)
{
const struct i2c_device_id *id = i2c_client_get_device_id(client);
struct iio_dev *indio_dev;
struct srf08_data *data;
int ret;
if (!i2c_check_functionality(client->adapter,
I2C_FUNC_SMBUS_READ_BYTE_DATA |
I2C_FUNC_SMBUS_WRITE_BYTE_DATA |
I2C_FUNC_SMBUS_READ_WORD_DATA))
return -ENODEV;
indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
if (!indio_dev)
return -ENOMEM;
data = iio_priv(indio_dev);
i2c_set_clientdata(client, indio_dev);
data->client = client;
data->sensor_type = (enum srf08_sensor_type)id->driver_data;
switch (data->sensor_type) {
case SRF02:
data->chip_info = &srf02_chip_info;
indio_dev->info = &srf02_info;
break;
case SRF08:
data->chip_info = &srf08_chip_info;
indio_dev->info = &srf08_info;
break;
case SRF10:
data->chip_info = &srf10_chip_info;
indio_dev->info = &srf08_info;
break;
default:
return -EINVAL;
}
indio_dev->name = id->name;
indio_dev->modes = INDIO_DIRECT_MODE;
indio_dev->channels = srf08_channels;
indio_dev->num_channels = ARRAY_SIZE(srf08_channels);
mutex_init(&data->lock);
ret = devm_iio_triggered_buffer_setup(&client->dev, indio_dev,
iio_pollfunc_store_time, srf08_trigger_handler, NULL);
if (ret < 0) {
dev_err(&client->dev, "setup of iio triggered buffer failed\n");
return ret;
}
if (data->chip_info->range_default) {
/*
* set default range of device in mm here
* these register values cannot be read from the hardware
* therefore set driver specific default values
*
* srf02 don't have a default value so it'll be omitted
*/
ret = srf08_write_range_mm(data,
data->chip_info->range_default);
if (ret < 0)
return ret;
}
if (data->chip_info->sensitivity_default) {
/*
* set default sensitivity of device here
* these register values cannot be read from the hardware
* therefore set driver specific default values
*
* srf02 don't have a default value so it'll be omitted
*/
ret = srf08_write_sensitivity(data,
data->chip_info->sensitivity_default);
if (ret < 0)
return ret;
}
return devm_iio_device_register(&client->dev, indio_dev);
}
static const struct of_device_id of_srf08_match[] = {
{ .compatible = "devantech,srf02", (void *)SRF02 },
{ .compatible = "devantech,srf08", (void *)SRF08 },
{ .compatible = "devantech,srf10", (void *)SRF10 },
{ }
};
MODULE_DEVICE_TABLE(of, of_srf08_match);
static const struct i2c_device_id srf08_id[] = {
{ .name = "srf02", .driver_data = SRF02 },
{ .name = "srf08", .driver_data = SRF08 },
{ .name = "srf10", .driver_data = SRF10 },
{ }
};
MODULE_DEVICE_TABLE(i2c, srf08_id);
static struct i2c_driver srf08_driver = {
.driver = {
.name = "srf08",
.of_match_table = of_srf08_match,
},
.probe = srf08_probe,
.id_table = srf08_id,
};
module_i2c_driver(srf08_driver);
MODULE_AUTHOR("Andreas Klinger <ak@it-klinger.de>");
MODULE_DESCRIPTION("Devantech SRF02/SRF08/SRF10 i2c ultrasonic ranger driver");
MODULE_LICENSE("GPL");
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+874
View File
@@ -0,0 +1,874 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright 2021 Google LLC.
*
* Driver for Semtech's SX9360 capacitive proximity/button solution.
* Based on SX9360 driver and copy of datasheet at:
* https://edit.wpgdadawant.com/uploads/news_file/program/2019/30184/tech_files/program_30184_suggest_other_file.pdf
*/
#include <linux/bits.h>
#include <linux/bitfield.h>
#include <linux/delay.h>
#include <linux/i2c.h>
#include <linux/interrupt.h>
#include <linux/kernel.h>
#include <linux/log2.h>
#include <linux/module.h>
#include <linux/pm.h>
#include <linux/property.h>
#include <linux/regmap.h>
#include <linux/iio/iio.h>
#include "sx_common.h"
/* Nominal Oscillator Frequency. */
#define SX9360_FOSC_MHZ 4
#define SX9360_FOSC_HZ (SX9360_FOSC_MHZ * 1000000)
/* Register definitions. */
#define SX9360_REG_IRQ_SRC SX_COMMON_REG_IRQ_SRC
#define SX9360_REG_STAT 0x01
#define SX9360_REG_STAT_COMPSTAT_MASK GENMASK(2, 1)
#define SX9360_REG_IRQ_MSK 0x02
#define SX9360_CONVDONE_IRQ BIT(0)
#define SX9360_FAR_IRQ BIT(2)
#define SX9360_CLOSE_IRQ BIT(3)
#define SX9360_REG_IRQ_CFG 0x03
#define SX9360_REG_GNRL_CTRL0 0x10
#define SX9360_REG_GNRL_CTRL0_PHEN_MASK GENMASK(1, 0)
#define SX9360_REG_GNRL_CTRL1 0x11
#define SX9360_REG_GNRL_CTRL1_SCANPERIOD_MASK GENMASK(2, 0)
#define SX9360_REG_GNRL_CTRL2 0x12
#define SX9360_REG_GNRL_CTRL2_PERIOD_102MS 0x32
#define SX9360_REG_GNRL_REG_2_PERIOD_MS(_r) \
(((_r) * 8192) / (SX9360_FOSC_HZ / 1000))
#define SX9360_REG_GNRL_FREQ_2_REG(_f) (((_f) * 8192) / SX9360_FOSC_HZ)
#define SX9360_REG_GNRL_REG_2_FREQ(_r) (SX9360_FOSC_HZ / ((_r) * 8192))
#define SX9360_REG_AFE_CTRL1 0x21
#define SX9360_REG_AFE_CTRL1_RESFILTIN_MASK GENMASK(3, 0)
#define SX9360_REG_AFE_CTRL1_RESFILTIN_0OHMS 0
#define SX9360_REG_AFE_PARAM0_PHR 0x22
#define SX9360_REG_AFE_PARAM1_PHR 0x23
#define SX9360_REG_AFE_PARAM0_PHM 0x24
#define SX9360_REG_AFE_PARAM0_RSVD 0x08
#define SX9360_REG_AFE_PARAM0_RESOLUTION_MASK GENMASK(2, 0)
#define SX9360_REG_AFE_PARAM0_RESOLUTION_128 0x02
#define SX9360_REG_AFE_PARAM1_PHM 0x25
#define SX9360_REG_AFE_PARAM1_AGAIN_PHM_6PF 0x40
#define SX9360_REG_AFE_PARAM1_FREQ_83_33HZ 0x06
#define SX9360_REG_PROX_CTRL0_PHR 0x40
#define SX9360_REG_PROX_CTRL0_PHM 0x41
#define SX9360_REG_PROX_CTRL0_GAIN_MASK GENMASK(5, 3)
#define SX9360_REG_PROX_CTRL0_GAIN_1 0x80
#define SX9360_REG_PROX_CTRL0_RAWFILT_MASK GENMASK(2, 0)
#define SX9360_REG_PROX_CTRL0_RAWFILT_1P50 0x01
#define SX9360_REG_PROX_CTRL1 0x42
#define SX9360_REG_PROX_CTRL1_AVGNEG_THRESH_MASK GENMASK(5, 3)
#define SX9360_REG_PROX_CTRL1_AVGNEG_THRESH_16K 0x20
#define SX9360_REG_PROX_CTRL2 0x43
#define SX9360_REG_PROX_CTRL2_AVGDEB_MASK GENMASK(7, 6)
#define SX9360_REG_PROX_CTRL2_AVGDEB_2SAMPLES 0x40
#define SX9360_REG_PROX_CTRL2_AVGPOS_THRESH_16K 0x20
#define SX9360_REG_PROX_CTRL3 0x44
#define SX9360_REG_PROX_CTRL3_AVGNEG_FILT_MASK GENMASK(5, 3)
#define SX9360_REG_PROX_CTRL3_AVGNEG_FILT_2 0x08
#define SX9360_REG_PROX_CTRL3_AVGPOS_FILT_MASK GENMASK(2, 0)
#define SX9360_REG_PROX_CTRL3_AVGPOS_FILT_256 0x04
#define SX9360_REG_PROX_CTRL4 0x45
#define SX9360_REG_PROX_CTRL4_HYST_MASK GENMASK(5, 4)
#define SX9360_REG_PROX_CTRL4_CLOSE_DEBOUNCE_MASK GENMASK(3, 2)
#define SX9360_REG_PROX_CTRL4_FAR_DEBOUNCE_MASK GENMASK(1, 0)
#define SX9360_REG_PROX_CTRL5 0x46
#define SX9360_REG_PROX_CTRL5_PROXTHRESH_32 0x08
#define SX9360_REG_REF_CORR0 0x60
#define SX9360_REG_REF_CORR1 0x61
#define SX9360_REG_USEFUL_PHR_MSB 0x90
#define SX9360_REG_USEFUL_PHR_LSB 0x91
#define SX9360_REG_OFFSET_PMR_MSB 0x92
#define SX9360_REG_OFFSET_PMR_LSB 0x93
#define SX9360_REG_USEFUL_PHM_MSB 0x94
#define SX9360_REG_USEFUL_PHM_LSB 0x95
#define SX9360_REG_AVG_PHM_MSB 0x96
#define SX9360_REG_AVG_PHM_LSB 0x97
#define SX9360_REG_DIFF_PHM_MSB 0x98
#define SX9360_REG_DIFF_PHM_LSB 0x99
#define SX9360_REG_OFFSET_PHM_MSB 0x9a
#define SX9360_REG_OFFSET_PHM_LSB 0x9b
#define SX9360_REG_USE_FILTER_MSB 0x9a
#define SX9360_REG_USE_FILTER_LSB 0x9b
#define SX9360_REG_RESET 0xcf
/* Write this to REG_RESET to do a soft reset. */
#define SX9360_SOFT_RESET 0xde
#define SX9360_REG_WHOAMI 0xfa
#define SX9360_WHOAMI_VALUE 0x60
#define SX9360_REG_REVISION 0xfe
/* 2 channels, Phase Reference and Measurement. */
#define SX9360_NUM_CHANNELS 2
static const struct iio_chan_spec sx9360_channels[] = {
{
.type = IIO_PROXIMITY,
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
BIT(IIO_CHAN_INFO_HARDWAREGAIN),
.info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ),
.info_mask_separate_available =
BIT(IIO_CHAN_INFO_HARDWAREGAIN),
.info_mask_shared_by_all_available =
BIT(IIO_CHAN_INFO_SAMP_FREQ),
.indexed = 1,
.address = SX9360_REG_USEFUL_PHR_MSB,
.channel = 0,
.scan_index = 0,
.scan_type = {
.sign = 's',
.realbits = 12,
.storagebits = 16,
.endianness = IIO_BE,
},
},
{
.type = IIO_PROXIMITY,
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
BIT(IIO_CHAN_INFO_HARDWAREGAIN),
.info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ),
.info_mask_separate_available =
BIT(IIO_CHAN_INFO_HARDWAREGAIN),
.info_mask_shared_by_all_available =
BIT(IIO_CHAN_INFO_SAMP_FREQ),
.indexed = 1,
.address = SX9360_REG_USEFUL_PHM_MSB,
.event_spec = sx_common_events,
.num_event_specs = ARRAY_SIZE(sx_common_events),
.channel = 1,
.scan_index = 1,
.scan_type = {
.sign = 's',
.realbits = 12,
.storagebits = 16,
.endianness = IIO_BE,
},
},
IIO_CHAN_SOFT_TIMESTAMP(2),
};
/*
* Each entry contains the integer part (val) and the fractional part, in micro
* seconds. It conforms to the IIO output IIO_VAL_INT_PLUS_MICRO.
*
* The frequency control register holds the period, with a ~2ms increment.
* Therefore the smallest frequency is 4MHz / (2047 * 8192),
* The fastest is 4MHz / 8192.
* The interval is not linear, but given there is 2047 possible value,
* Returns the fake increment of (Max-Min)/2047
*/
static const struct {
int val;
int val2;
} sx9360_samp_freq_interval[] = {
{ 0, 281250 }, /* 4MHz / (8192 * 2047) */
{ 0, 281250 },
{ 448, 281250 }, /* 4MHz / 8192 */
};
static const struct regmap_range sx9360_writable_reg_ranges[] = {
/*
* To set COMPSTAT for compensation, even if datasheet says register is
* RO.
*/
regmap_reg_range(SX9360_REG_STAT, SX9360_REG_IRQ_CFG),
regmap_reg_range(SX9360_REG_GNRL_CTRL0, SX9360_REG_GNRL_CTRL2),
regmap_reg_range(SX9360_REG_AFE_CTRL1, SX9360_REG_AFE_PARAM1_PHM),
regmap_reg_range(SX9360_REG_PROX_CTRL0_PHR, SX9360_REG_PROX_CTRL5),
regmap_reg_range(SX9360_REG_REF_CORR0, SX9360_REG_REF_CORR1),
regmap_reg_range(SX9360_REG_OFFSET_PMR_MSB, SX9360_REG_OFFSET_PMR_LSB),
regmap_reg_range(SX9360_REG_RESET, SX9360_REG_RESET),
};
static const struct regmap_access_table sx9360_writeable_regs = {
.yes_ranges = sx9360_writable_reg_ranges,
.n_yes_ranges = ARRAY_SIZE(sx9360_writable_reg_ranges),
};
/*
* All allocated registers are readable, so we just list unallocated
* ones.
*/
static const struct regmap_range sx9360_non_readable_reg_ranges[] = {
regmap_reg_range(SX9360_REG_IRQ_CFG + 1, SX9360_REG_GNRL_CTRL0 - 1),
regmap_reg_range(SX9360_REG_GNRL_CTRL2 + 1, SX9360_REG_AFE_CTRL1 - 1),
regmap_reg_range(SX9360_REG_AFE_PARAM1_PHM + 1,
SX9360_REG_PROX_CTRL0_PHR - 1),
regmap_reg_range(SX9360_REG_PROX_CTRL5 + 1, SX9360_REG_REF_CORR0 - 1),
regmap_reg_range(SX9360_REG_REF_CORR1 + 1,
SX9360_REG_USEFUL_PHR_MSB - 1),
regmap_reg_range(SX9360_REG_USE_FILTER_LSB + 1, SX9360_REG_RESET - 1),
regmap_reg_range(SX9360_REG_RESET + 1, SX9360_REG_WHOAMI - 1),
regmap_reg_range(SX9360_REG_WHOAMI + 1, SX9360_REG_REVISION - 1),
};
static const struct regmap_access_table sx9360_readable_regs = {
.no_ranges = sx9360_non_readable_reg_ranges,
.n_no_ranges = ARRAY_SIZE(sx9360_non_readable_reg_ranges),
};
static const struct regmap_range sx9360_volatile_reg_ranges[] = {
regmap_reg_range(SX9360_REG_IRQ_SRC, SX9360_REG_STAT),
regmap_reg_range(SX9360_REG_USEFUL_PHR_MSB, SX9360_REG_USE_FILTER_LSB),
regmap_reg_range(SX9360_REG_WHOAMI, SX9360_REG_WHOAMI),
regmap_reg_range(SX9360_REG_REVISION, SX9360_REG_REVISION),
};
static const struct regmap_access_table sx9360_volatile_regs = {
.yes_ranges = sx9360_volatile_reg_ranges,
.n_yes_ranges = ARRAY_SIZE(sx9360_volatile_reg_ranges),
};
static const struct regmap_config sx9360_regmap_config = {
.reg_bits = 8,
.val_bits = 8,
.max_register = SX9360_REG_REVISION,
.cache_type = REGCACHE_RBTREE,
.wr_table = &sx9360_writeable_regs,
.rd_table = &sx9360_readable_regs,
.volatile_table = &sx9360_volatile_regs,
};
static int sx9360_read_prox_data(struct sx_common_data *data,
const struct iio_chan_spec *chan,
__be16 *val)
{
return regmap_bulk_read(data->regmap, chan->address, val, sizeof(*val));
}
/*
* If we have no interrupt support, we have to wait for a scan period
* after enabling a channel to get a result.
*/
static int sx9360_wait_for_sample(struct sx_common_data *data)
{
int ret;
__be16 buf;
ret = regmap_bulk_read(data->regmap, SX9360_REG_GNRL_CTRL1,
&buf, sizeof(buf));
if (ret < 0)
return ret;
msleep(SX9360_REG_GNRL_REG_2_PERIOD_MS(be16_to_cpu(buf)));
return 0;
}
static int sx9360_read_gain(struct sx_common_data *data,
const struct iio_chan_spec *chan, int *val)
{
unsigned int reg, regval;
int ret;
reg = SX9360_REG_PROX_CTRL0_PHR + chan->channel;
ret = regmap_read(data->regmap, reg, &regval);
if (ret)
return ret;
*val = 1 << FIELD_GET(SX9360_REG_PROX_CTRL0_GAIN_MASK, regval);
return IIO_VAL_INT;
}
static int sx9360_read_samp_freq(struct sx_common_data *data,
int *val, int *val2)
{
int ret, divisor;
__be16 buf;
ret = regmap_bulk_read(data->regmap, SX9360_REG_GNRL_CTRL1,
&buf, sizeof(buf));
if (ret < 0)
return ret;
divisor = be16_to_cpu(buf);
if (divisor == 0) {
*val = 0;
return IIO_VAL_INT;
}
*val = SX9360_FOSC_HZ;
*val2 = divisor * 8192;
return IIO_VAL_FRACTIONAL;
}
static int sx9360_read_raw(struct iio_dev *indio_dev,
const struct iio_chan_spec *chan,
int *val, int *val2, long mask)
{
struct sx_common_data *data = iio_priv(indio_dev);
int ret;
switch (mask) {
case IIO_CHAN_INFO_RAW:
if (!iio_device_claim_direct(indio_dev))
return -EBUSY;
ret = sx_common_read_proximity(data, chan, val);
iio_device_release_direct(indio_dev);
return ret;
case IIO_CHAN_INFO_HARDWAREGAIN:
if (!iio_device_claim_direct(indio_dev))
return -EBUSY;
ret = sx9360_read_gain(data, chan, val);
iio_device_release_direct(indio_dev);
return ret;
case IIO_CHAN_INFO_SAMP_FREQ:
return sx9360_read_samp_freq(data, val, val2);
default:
return -EINVAL;
}
}
static const char *sx9360_channel_labels[SX9360_NUM_CHANNELS] = {
"reference", "main",
};
static int sx9360_read_label(struct iio_dev *iio_dev, const struct iio_chan_spec *chan,
char *label)
{
return sysfs_emit(label, "%s\n", sx9360_channel_labels[chan->channel]);
}
static const int sx9360_gain_vals[] = { 1, 2, 4, 8 };
static int sx9360_read_avail(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan,
const int **vals, int *type, int *length,
long mask)
{
if (chan->type != IIO_PROXIMITY)
return -EINVAL;
switch (mask) {
case IIO_CHAN_INFO_HARDWAREGAIN:
*type = IIO_VAL_INT;
*length = ARRAY_SIZE(sx9360_gain_vals);
*vals = sx9360_gain_vals;
return IIO_AVAIL_LIST;
case IIO_CHAN_INFO_SAMP_FREQ:
*type = IIO_VAL_INT_PLUS_MICRO;
*length = ARRAY_SIZE(sx9360_samp_freq_interval) * 2;
*vals = (int *)sx9360_samp_freq_interval;
return IIO_AVAIL_RANGE;
default:
return -EINVAL;
}
}
static int sx9360_set_samp_freq(struct sx_common_data *data,
int val, int val2)
{
int reg;
__be16 buf;
reg = val * 8192 / SX9360_FOSC_HZ + val2 * 8192 / (SX9360_FOSC_MHZ);
buf = cpu_to_be16(reg);
guard(mutex)(&data->mutex);
return regmap_bulk_write(data->regmap, SX9360_REG_GNRL_CTRL1, &buf,
sizeof(buf));
}
static int sx9360_read_thresh(struct sx_common_data *data, int *val)
{
unsigned int regval;
int ret;
ret = regmap_read(data->regmap, SX9360_REG_PROX_CTRL5, &regval);
if (ret)
return ret;
if (regval <= 1)
*val = regval;
else
*val = (regval * regval) / 2;
return IIO_VAL_INT;
}
static int sx9360_read_hysteresis(struct sx_common_data *data, int *val)
{
unsigned int regval, pthresh;
int ret;
ret = sx9360_read_thresh(data, &pthresh);
if (ret < 0)
return ret;
ret = regmap_read(data->regmap, SX9360_REG_PROX_CTRL4, &regval);
if (ret)
return ret;
regval = FIELD_GET(SX9360_REG_PROX_CTRL4_HYST_MASK, regval);
if (!regval)
*val = 0;
else
*val = pthresh >> (5 - regval);
return IIO_VAL_INT;
}
static int sx9360_read_far_debounce(struct sx_common_data *data, int *val)
{
unsigned int regval;
int ret;
ret = regmap_read(data->regmap, SX9360_REG_PROX_CTRL4, &regval);
if (ret)
return ret;
regval = FIELD_GET(SX9360_REG_PROX_CTRL4_FAR_DEBOUNCE_MASK, regval);
if (regval)
*val = 1 << regval;
else
*val = 0;
return IIO_VAL_INT;
}
static int sx9360_read_close_debounce(struct sx_common_data *data, int *val)
{
unsigned int regval;
int ret;
ret = regmap_read(data->regmap, SX9360_REG_PROX_CTRL4, &regval);
if (ret)
return ret;
regval = FIELD_GET(SX9360_REG_PROX_CTRL4_CLOSE_DEBOUNCE_MASK, regval);
if (regval)
*val = 1 << regval;
else
*val = 0;
return IIO_VAL_INT;
}
static int sx9360_read_event_val(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 sx_common_data *data = iio_priv(indio_dev);
if (chan->type != IIO_PROXIMITY)
return -EINVAL;
switch (info) {
case IIO_EV_INFO_VALUE:
return sx9360_read_thresh(data, val);
case IIO_EV_INFO_PERIOD:
switch (dir) {
case IIO_EV_DIR_RISING:
return sx9360_read_far_debounce(data, val);
case IIO_EV_DIR_FALLING:
return sx9360_read_close_debounce(data, val);
default:
return -EINVAL;
}
case IIO_EV_INFO_HYSTERESIS:
return sx9360_read_hysteresis(data, val);
default:
return -EINVAL;
}
}
static int sx9360_write_thresh(struct sx_common_data *data, int _val)
{
unsigned int val = _val;
if (val >= 1)
val = int_sqrt(2 * val);
if (val > 0xff)
return -EINVAL;
guard(mutex)(&data->mutex);
return regmap_write(data->regmap, SX9360_REG_PROX_CTRL5, val);
}
static int sx9360_write_hysteresis(struct sx_common_data *data, int _val)
{
unsigned int hyst, val = _val;
int ret, pthresh;
ret = sx9360_read_thresh(data, &pthresh);
if (ret < 0)
return ret;
if (val == 0)
hyst = 0;
else if (val >= pthresh >> 2)
hyst = 3;
else if (val >= pthresh >> 3)
hyst = 2;
else if (val >= pthresh >> 4)
hyst = 1;
else
return -EINVAL;
hyst = FIELD_PREP(SX9360_REG_PROX_CTRL4_HYST_MASK, hyst);
guard(mutex)(&data->mutex);
return regmap_update_bits(data->regmap, SX9360_REG_PROX_CTRL4,
SX9360_REG_PROX_CTRL4_HYST_MASK, hyst);
}
static int sx9360_write_far_debounce(struct sx_common_data *data, int _val)
{
unsigned int regval, val = _val;
if (val > 0)
val = ilog2(val);
if (!FIELD_FIT(SX9360_REG_PROX_CTRL4_FAR_DEBOUNCE_MASK, val))
return -EINVAL;
regval = FIELD_PREP(SX9360_REG_PROX_CTRL4_FAR_DEBOUNCE_MASK, val);
guard(mutex)(&data->mutex);
return regmap_update_bits(data->regmap, SX9360_REG_PROX_CTRL4,
SX9360_REG_PROX_CTRL4_FAR_DEBOUNCE_MASK,
regval);
}
static int sx9360_write_close_debounce(struct sx_common_data *data, int _val)
{
unsigned int regval, val = _val;
if (val > 0)
val = ilog2(val);
if (!FIELD_FIT(SX9360_REG_PROX_CTRL4_CLOSE_DEBOUNCE_MASK, val))
return -EINVAL;
regval = FIELD_PREP(SX9360_REG_PROX_CTRL4_CLOSE_DEBOUNCE_MASK, val);
guard(mutex)(&data->mutex);
return regmap_update_bits(data->regmap, SX9360_REG_PROX_CTRL4,
SX9360_REG_PROX_CTRL4_CLOSE_DEBOUNCE_MASK,
regval);
}
static int sx9360_write_event_val(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 sx_common_data *data = iio_priv(indio_dev);
if (chan->type != IIO_PROXIMITY)
return -EINVAL;
switch (info) {
case IIO_EV_INFO_VALUE:
return sx9360_write_thresh(data, val);
case IIO_EV_INFO_PERIOD:
switch (dir) {
case IIO_EV_DIR_RISING:
return sx9360_write_far_debounce(data, val);
case IIO_EV_DIR_FALLING:
return sx9360_write_close_debounce(data, val);
default:
return -EINVAL;
}
case IIO_EV_INFO_HYSTERESIS:
return sx9360_write_hysteresis(data, val);
default:
return -EINVAL;
}
}
static int sx9360_write_gain(struct sx_common_data *data,
const struct iio_chan_spec *chan, int val)
{
unsigned int gain, reg;
gain = ilog2(val);
reg = SX9360_REG_PROX_CTRL0_PHR + chan->channel;
gain = FIELD_PREP(SX9360_REG_PROX_CTRL0_GAIN_MASK, gain);
guard(mutex)(&data->mutex);
return regmap_update_bits(data->regmap, reg,
SX9360_REG_PROX_CTRL0_GAIN_MASK,
gain);
}
static int sx9360_write_raw(struct iio_dev *indio_dev,
const struct iio_chan_spec *chan, int val, int val2,
long mask)
{
struct sx_common_data *data = iio_priv(indio_dev);
switch (mask) {
case IIO_CHAN_INFO_SAMP_FREQ:
return sx9360_set_samp_freq(data, val, val2);
case IIO_CHAN_INFO_HARDWAREGAIN:
return sx9360_write_gain(data, chan, val);
default:
return -EINVAL;
}
}
static const struct sx_common_reg_default sx9360_default_regs[] = {
{ SX9360_REG_IRQ_MSK, 0x00 },
{ SX9360_REG_IRQ_CFG, 0x00, "irq_cfg" },
/*
* The lower 2 bits should not be set as it enable sensors measurements.
* Turning the detection on before the configuration values are set to
* good values can cause the device to return erroneous readings.
*/
{ SX9360_REG_GNRL_CTRL0, 0x00, "gnrl_ctrl0" },
{ SX9360_REG_GNRL_CTRL1, 0x00, "gnrl_ctrl1" },
{ SX9360_REG_GNRL_CTRL2, SX9360_REG_GNRL_CTRL2_PERIOD_102MS, "gnrl_ctrl2" },
{ SX9360_REG_AFE_CTRL1, SX9360_REG_AFE_CTRL1_RESFILTIN_0OHMS, "afe_ctrl0" },
{ SX9360_REG_AFE_PARAM0_PHR, SX9360_REG_AFE_PARAM0_RSVD |
SX9360_REG_AFE_PARAM0_RESOLUTION_128, "afe_param0_phr" },
{ SX9360_REG_AFE_PARAM1_PHR, SX9360_REG_AFE_PARAM1_AGAIN_PHM_6PF |
SX9360_REG_AFE_PARAM1_FREQ_83_33HZ, "afe_param1_phr" },
{ SX9360_REG_AFE_PARAM0_PHM, SX9360_REG_AFE_PARAM0_RSVD |
SX9360_REG_AFE_PARAM0_RESOLUTION_128, "afe_param0_phm" },
{ SX9360_REG_AFE_PARAM1_PHM, SX9360_REG_AFE_PARAM1_AGAIN_PHM_6PF |
SX9360_REG_AFE_PARAM1_FREQ_83_33HZ, "afe_param1_phm" },
{ SX9360_REG_PROX_CTRL0_PHR, SX9360_REG_PROX_CTRL0_GAIN_1 |
SX9360_REG_PROX_CTRL0_RAWFILT_1P50, "prox_ctrl0_phr" },
{ SX9360_REG_PROX_CTRL0_PHM, SX9360_REG_PROX_CTRL0_GAIN_1 |
SX9360_REG_PROX_CTRL0_RAWFILT_1P50, "prox_ctrl0_phm" },
{ SX9360_REG_PROX_CTRL1, SX9360_REG_PROX_CTRL1_AVGNEG_THRESH_16K, "prox_ctrl1" },
{ SX9360_REG_PROX_CTRL2, SX9360_REG_PROX_CTRL2_AVGDEB_2SAMPLES |
SX9360_REG_PROX_CTRL2_AVGPOS_THRESH_16K, "prox_ctrl2" },
{ SX9360_REG_PROX_CTRL3, SX9360_REG_PROX_CTRL3_AVGNEG_FILT_2 |
SX9360_REG_PROX_CTRL3_AVGPOS_FILT_256, "prox_ctrl3" },
{ SX9360_REG_PROX_CTRL4, 0x00, "prox_ctrl4" },
{ SX9360_REG_PROX_CTRL5, SX9360_REG_PROX_CTRL5_PROXTHRESH_32, "prox_ctrl5" },
};
/* Activate all channels and perform an initial compensation. */
static int sx9360_init_compensation(struct iio_dev *indio_dev)
{
struct sx_common_data *data = iio_priv(indio_dev);
unsigned int val;
int ret;
/* run the compensation phase on all channels */
ret = regmap_set_bits(data->regmap, SX9360_REG_STAT,
SX9360_REG_STAT_COMPSTAT_MASK);
if (ret)
return ret;
return regmap_read_poll_timeout(data->regmap, SX9360_REG_STAT, val,
!(val & SX9360_REG_STAT_COMPSTAT_MASK),
20000, 2000000);
}
static const struct sx_common_reg_default *
sx9360_get_default_reg(struct device *dev, int idx,
struct sx_common_reg_default *reg_def)
{
u32 raw = 0, pos = 0;
int ret;
memcpy(reg_def, &sx9360_default_regs[idx], sizeof(*reg_def));
switch (reg_def->reg) {
case SX9360_REG_AFE_CTRL1:
ret = device_property_read_u32(dev,
"semtech,input-precharge-resistor-ohms",
&raw);
if (ret)
break;
reg_def->def &= ~SX9360_REG_AFE_CTRL1_RESFILTIN_MASK;
reg_def->def |= FIELD_PREP(SX9360_REG_AFE_CTRL1_RESFILTIN_MASK,
raw / 2000);
break;
case SX9360_REG_AFE_PARAM0_PHR:
case SX9360_REG_AFE_PARAM0_PHM:
ret = device_property_read_u32(dev, "semtech,resolution", &raw);
if (ret)
break;
raw = ilog2(raw) - 3;
reg_def->def &= ~SX9360_REG_AFE_PARAM0_RESOLUTION_MASK;
reg_def->def |= FIELD_PREP(SX9360_REG_AFE_PARAM0_RESOLUTION_MASK, raw);
break;
case SX9360_REG_PROX_CTRL0_PHR:
case SX9360_REG_PROX_CTRL0_PHM:
ret = device_property_read_u32(dev, "semtech,proxraw-strength", &raw);
if (ret)
break;
reg_def->def &= ~SX9360_REG_PROX_CTRL0_RAWFILT_MASK;
reg_def->def |= FIELD_PREP(SX9360_REG_PROX_CTRL0_RAWFILT_MASK, raw);
break;
case SX9360_REG_PROX_CTRL3:
ret = device_property_read_u32(dev, "semtech,avg-pos-strength",
&pos);
if (ret)
break;
/* Powers of 2, except for a gap between 16 and 64 */
raw = clamp(ilog2(pos), 3, 11) - (pos >= 32 ? 4 : 3);
reg_def->def &= ~SX9360_REG_PROX_CTRL3_AVGPOS_FILT_MASK;
reg_def->def |= FIELD_PREP(SX9360_REG_PROX_CTRL3_AVGPOS_FILT_MASK, raw);
break;
}
return reg_def;
}
static int sx9360_check_whoami(struct device *dev, struct iio_dev *indio_dev)
{
/*
* Only one sensor for this driver. Assuming the device tree
* is correct, just set the sensor name.
*/
indio_dev->name = "sx9360";
return 0;
}
static const struct sx_common_chip_info sx9360_chip_info = {
.reg_stat = SX9360_REG_STAT,
.reg_irq_msk = SX9360_REG_IRQ_MSK,
.reg_enable_chan = SX9360_REG_GNRL_CTRL0,
.reg_reset = SX9360_REG_RESET,
.mask_enable_chan = SX9360_REG_GNRL_CTRL0_PHEN_MASK,
.stat_offset = 2,
.num_channels = SX9360_NUM_CHANNELS,
.num_default_regs = ARRAY_SIZE(sx9360_default_regs),
.ops = {
.read_prox_data = sx9360_read_prox_data,
.check_whoami = sx9360_check_whoami,
.init_compensation = sx9360_init_compensation,
.wait_for_sample = sx9360_wait_for_sample,
.get_default_reg = sx9360_get_default_reg,
},
.iio_channels = sx9360_channels,
.num_iio_channels = ARRAY_SIZE(sx9360_channels),
.iio_info = {
.read_raw = sx9360_read_raw,
.read_avail = sx9360_read_avail,
.read_label = sx9360_read_label,
.read_event_value = sx9360_read_event_val,
.write_event_value = sx9360_write_event_val,
.write_raw = sx9360_write_raw,
.read_event_config = sx_common_read_event_config,
.write_event_config = sx_common_write_event_config,
},
};
static int sx9360_probe(struct i2c_client *client)
{
return sx_common_probe(client, &sx9360_chip_info, &sx9360_regmap_config);
}
static int sx9360_suspend(struct device *dev)
{
struct sx_common_data *data = iio_priv(dev_get_drvdata(dev));
unsigned int regval;
int ret;
disable_irq_nosync(data->client->irq);
guard(mutex)(&data->mutex);
ret = regmap_read(data->regmap, SX9360_REG_GNRL_CTRL0, &regval);
if (ret < 0)
return ret;
data->suspend_ctrl =
FIELD_GET(SX9360_REG_GNRL_CTRL0_PHEN_MASK, regval);
/* Disable all phases, send the device to sleep. */
return regmap_write(data->regmap, SX9360_REG_GNRL_CTRL0, 0);
}
static int sx9360_resume(struct device *dev)
{
struct sx_common_data *data = iio_priv(dev_get_drvdata(dev));
scoped_guard(mutex, &data->mutex) {
int ret = regmap_update_bits(data->regmap,
SX9360_REG_GNRL_CTRL0,
SX9360_REG_GNRL_CTRL0_PHEN_MASK,
data->suspend_ctrl);
if (ret)
return ret;
}
enable_irq(data->client->irq);
return 0;
}
static DEFINE_SIMPLE_DEV_PM_OPS(sx9360_pm_ops, sx9360_suspend, sx9360_resume);
static const struct acpi_device_id sx9360_acpi_match[] = {
{ .id = "STH9360" },
{ .id = "SAMM0208" },
{ }
};
MODULE_DEVICE_TABLE(acpi, sx9360_acpi_match);
static const struct of_device_id sx9360_of_match[] = {
{ .compatible = "semtech,sx9360" },
{ }
};
MODULE_DEVICE_TABLE(of, sx9360_of_match);
static const struct i2c_device_id sx9360_id[] = {
{ .name = "sx9360" },
{ }
};
MODULE_DEVICE_TABLE(i2c, sx9360_id);
static struct i2c_driver sx9360_driver = {
.driver = {
.name = "sx9360",
.acpi_match_table = sx9360_acpi_match,
.of_match_table = sx9360_of_match,
.pm = pm_sleep_ptr(&sx9360_pm_ops),
/*
* Lots of i2c transfers in probe + over 200 ms waiting in
* sx9360_init_compensation() mean a slow probe; prefer async
* so we don't delay boot if we're builtin to the kernel.
*/
.probe_type = PROBE_PREFER_ASYNCHRONOUS,
},
.probe = sx9360_probe,
.id_table = sx9360_id,
};
module_i2c_driver(sx9360_driver);
MODULE_AUTHOR("Gwendal Grignou <gwendal@chromium.org>");
MODULE_DESCRIPTION("Driver for Semtech SX9360 proximity sensor");
MODULE_LICENSE("GPL v2");
MODULE_IMPORT_NS("SEMTECH_PROX");
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,549 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright 2021 Google LLC.
*
* Common part of most Semtech SAR sensor.
*/
#include <linux/bitops.h>
#include <linux/byteorder/generic.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/err.h>
#include <linux/export.h>
#include <linux/interrupt.h>
#include <linux/irqreturn.h>
#include <linux/i2c.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/regmap.h>
#include <linux/regulator/consumer.h>
#include <vdso/bits.h>
#include <linux/iio/buffer.h>
#include <linux/iio/events.h>
#include <linux/iio/iio.h>
#include <linux/iio/trigger.h>
#include <linux/iio/triggered_buffer.h>
#include <linux/iio/trigger_consumer.h>
#include "sx_common.h"
/* All Semtech SAR sensors have IRQ bit in the same order. */
#define SX_COMMON_CONVDONE_IRQ BIT(0)
#define SX_COMMON_FAR_IRQ BIT(2)
#define SX_COMMON_CLOSE_IRQ BIT(3)
const struct iio_event_spec sx_common_events[3] = {
{
.type = IIO_EV_TYPE_THRESH,
.dir = IIO_EV_DIR_RISING,
.mask_shared_by_all = BIT(IIO_EV_INFO_PERIOD),
},
{
.type = IIO_EV_TYPE_THRESH,
.dir = IIO_EV_DIR_FALLING,
.mask_shared_by_all = BIT(IIO_EV_INFO_PERIOD),
},
{
.type = IIO_EV_TYPE_THRESH,
.dir = IIO_EV_DIR_EITHER,
.mask_separate = BIT(IIO_EV_INFO_ENABLE) |
BIT(IIO_EV_INFO_HYSTERESIS) |
BIT(IIO_EV_INFO_VALUE),
},
};
EXPORT_SYMBOL_NS_GPL(sx_common_events, "SEMTECH_PROX");
static irqreturn_t sx_common_irq_handler(int irq, void *private)
{
struct iio_dev *indio_dev = private;
struct sx_common_data *data = iio_priv(indio_dev);
if (data->trigger_enabled)
iio_trigger_poll(data->trig);
/*
* Even if no event is enabled, we need to wake the thread to clear the
* interrupt state by reading SX_COMMON_REG_IRQ_SRC.
* It is not possible to do that here because regmap_read takes a mutex.
*/
return IRQ_WAKE_THREAD;
}
static void sx_common_push_events(struct iio_dev *indio_dev)
{
int ret;
unsigned int val, chan;
struct sx_common_data *data = iio_priv(indio_dev);
s64 timestamp = iio_get_time_ns(indio_dev);
unsigned long prox_changed;
/* Read proximity state on all channels */
ret = regmap_read(data->regmap, data->chip_info->reg_stat, &val);
if (ret) {
dev_err(&data->client->dev, "i2c transfer error in irq\n");
return;
}
val >>= data->chip_info->stat_offset;
/*
* Only iterate over channels with changes on proximity status that have
* events enabled.
*/
prox_changed = (data->chan_prox_stat ^ val) & data->chan_event;
for_each_set_bit(chan, &prox_changed, data->chip_info->num_channels) {
int dir;
u64 ev;
dir = (val & BIT(chan)) ? IIO_EV_DIR_FALLING : IIO_EV_DIR_RISING;
ev = IIO_UNMOD_EVENT_CODE(IIO_PROXIMITY, chan,
IIO_EV_TYPE_THRESH, dir);
iio_push_event(indio_dev, ev, timestamp);
}
data->chan_prox_stat = val;
}
static int sx_common_enable_irq(struct sx_common_data *data, unsigned int irq)
{
if (!data->client->irq)
return 0;
return regmap_set_bits(data->regmap, data->chip_info->reg_irq_msk,
irq << data->chip_info->irq_msk_offset);
}
static int sx_common_disable_irq(struct sx_common_data *data, unsigned int irq)
{
if (!data->client->irq)
return 0;
return regmap_clear_bits(data->regmap, data->chip_info->reg_irq_msk,
irq << data->chip_info->irq_msk_offset);
}
static int sx_common_update_chan_en(struct sx_common_data *data,
unsigned long chan_read,
unsigned long chan_event)
{
int ret;
unsigned long channels = chan_read | chan_event;
if ((data->chan_read | data->chan_event) != channels) {
ret = regmap_update_bits(data->regmap,
data->chip_info->reg_enable_chan,
data->chip_info->mask_enable_chan,
channels);
if (ret)
return ret;
}
data->chan_read = chan_read;
data->chan_event = chan_event;
return 0;
}
static int sx_common_get_read_channel(struct sx_common_data *data, int channel)
{
return sx_common_update_chan_en(data, data->chan_read | BIT(channel),
data->chan_event);
}
static int sx_common_put_read_channel(struct sx_common_data *data, int channel)
{
return sx_common_update_chan_en(data, data->chan_read & ~BIT(channel),
data->chan_event);
}
static int sx_common_get_event_channel(struct sx_common_data *data, int channel)
{
return sx_common_update_chan_en(data, data->chan_read,
data->chan_event | BIT(channel));
}
static int sx_common_put_event_channel(struct sx_common_data *data, int channel)
{
return sx_common_update_chan_en(data, data->chan_read,
data->chan_event & ~BIT(channel));
}
/**
* sx_common_read_proximity() - Read raw proximity value.
* @data: Internal data
* @chan: Channel to read
* @val: pointer to return read value.
*
* Request a conversion, wait for the sensor to be ready and
* return the raw proximity value.
*/
int sx_common_read_proximity(struct sx_common_data *data,
const struct iio_chan_spec *chan, int *val)
{
int ret;
__be16 rawval;
mutex_lock(&data->mutex);
ret = sx_common_get_read_channel(data, chan->channel);
if (ret)
goto out;
ret = sx_common_enable_irq(data, SX_COMMON_CONVDONE_IRQ);
if (ret)
goto out_put_channel;
mutex_unlock(&data->mutex);
if (data->client->irq) {
ret = wait_for_completion_interruptible(&data->completion);
reinit_completion(&data->completion);
} else {
ret = data->chip_info->ops.wait_for_sample(data);
}
mutex_lock(&data->mutex);
if (ret)
goto out_disable_irq;
ret = data->chip_info->ops.read_prox_data(data, chan, &rawval);
if (ret)
goto out_disable_irq;
*val = sign_extend32(be16_to_cpu(rawval), chan->scan_type.realbits - 1);
ret = sx_common_disable_irq(data, SX_COMMON_CONVDONE_IRQ);
if (ret)
goto out_put_channel;
ret = sx_common_put_read_channel(data, chan->channel);
if (ret)
goto out;
mutex_unlock(&data->mutex);
return IIO_VAL_INT;
out_disable_irq:
sx_common_disable_irq(data, SX_COMMON_CONVDONE_IRQ);
out_put_channel:
sx_common_put_read_channel(data, chan->channel);
out:
mutex_unlock(&data->mutex);
return ret;
}
EXPORT_SYMBOL_NS_GPL(sx_common_read_proximity, "SEMTECH_PROX");
/**
* sx_common_read_event_config() - Configure event setting.
* @indio_dev: iio device object
* @chan: Channel to read
* @type: Type of event (unused)
* @dir: Direction of event (unused)
*
* return if the given channel is used for event gathering.
*/
int sx_common_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 sx_common_data *data = iio_priv(indio_dev);
return !!(data->chan_event & BIT(chan->channel));
}
EXPORT_SYMBOL_NS_GPL(sx_common_read_event_config, "SEMTECH_PROX");
/**
* sx_common_write_event_config() - Configure event setting.
* @indio_dev: iio device object
* @chan: Channel to enable
* @type: Type of event (unused)
* @dir: Direction of event (unused)
* @state: State of the event.
*
* Enable/Disable event on a given channel.
*/
int sx_common_write_event_config(struct iio_dev *indio_dev,
const struct iio_chan_spec *chan,
enum iio_event_type type,
enum iio_event_direction dir, bool state)
{
struct sx_common_data *data = iio_priv(indio_dev);
unsigned int eventirq = SX_COMMON_FAR_IRQ | SX_COMMON_CLOSE_IRQ;
int ret;
/* If the state hasn't changed, there's nothing to do. */
if (!!(data->chan_event & BIT(chan->channel)) == state)
return 0;
mutex_lock(&data->mutex);
if (state) {
ret = sx_common_get_event_channel(data, chan->channel);
if (ret)
goto out_unlock;
if (!(data->chan_event & ~BIT(chan->channel))) {
ret = sx_common_enable_irq(data, eventirq);
if (ret)
sx_common_put_event_channel(data, chan->channel);
}
} else {
ret = sx_common_put_event_channel(data, chan->channel);
if (ret)
goto out_unlock;
if (!data->chan_event) {
ret = sx_common_disable_irq(data, eventirq);
if (ret)
sx_common_get_event_channel(data, chan->channel);
}
}
out_unlock:
mutex_unlock(&data->mutex);
return ret;
}
EXPORT_SYMBOL_NS_GPL(sx_common_write_event_config, "SEMTECH_PROX");
static int sx_common_set_trigger_state(struct iio_trigger *trig, bool state)
{
struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig);
struct sx_common_data *data = iio_priv(indio_dev);
int ret = 0;
mutex_lock(&data->mutex);
if (state)
ret = sx_common_enable_irq(data, SX_COMMON_CONVDONE_IRQ);
else if (!data->chan_read)
ret = sx_common_disable_irq(data, SX_COMMON_CONVDONE_IRQ);
if (ret)
goto out;
data->trigger_enabled = state;
out:
mutex_unlock(&data->mutex);
return ret;
}
static const struct iio_trigger_ops sx_common_trigger_ops = {
.set_trigger_state = sx_common_set_trigger_state,
};
static irqreturn_t sx_common_irq_thread_handler(int irq, void *private)
{
struct iio_dev *indio_dev = private;
struct sx_common_data *data = iio_priv(indio_dev);
int ret;
unsigned int val;
mutex_lock(&data->mutex);
ret = regmap_read(data->regmap, SX_COMMON_REG_IRQ_SRC, &val);
if (ret) {
dev_err(&data->client->dev, "i2c transfer error in irq\n");
goto out;
}
if (val & ((SX_COMMON_FAR_IRQ | SX_COMMON_CLOSE_IRQ) << data->chip_info->irq_msk_offset))
sx_common_push_events(indio_dev);
if (val & (SX_COMMON_CONVDONE_IRQ << data->chip_info->irq_msk_offset))
complete(&data->completion);
out:
mutex_unlock(&data->mutex);
return IRQ_HANDLED;
}
static irqreturn_t sx_common_trigger_handler(int irq, void *private)
{
struct iio_poll_func *pf = private;
struct iio_dev *indio_dev = pf->indio_dev;
struct sx_common_data *data = iio_priv(indio_dev);
__be16 val;
int bit, ret, i = 0;
mutex_lock(&data->mutex);
iio_for_each_active_channel(indio_dev, bit) {
ret = data->chip_info->ops.read_prox_data(data,
&indio_dev->channels[bit],
&val);
if (ret)
goto out;
data->buffer.channels[i++] = val;
}
iio_push_to_buffers_with_ts(indio_dev, &data->buffer,
sizeof(data->buffer), pf->timestamp);
out:
mutex_unlock(&data->mutex);
iio_trigger_notify_done(indio_dev->trig);
return IRQ_HANDLED;
}
static int sx_common_buffer_preenable(struct iio_dev *indio_dev)
{
struct sx_common_data *data = iio_priv(indio_dev);
unsigned long channels = 0;
int bit, ret;
mutex_lock(&data->mutex);
iio_for_each_active_channel(indio_dev, bit)
__set_bit(indio_dev->channels[bit].channel, &channels);
ret = sx_common_update_chan_en(data, channels, data->chan_event);
mutex_unlock(&data->mutex);
return ret;
}
static int sx_common_buffer_postdisable(struct iio_dev *indio_dev)
{
struct sx_common_data *data = iio_priv(indio_dev);
int ret;
mutex_lock(&data->mutex);
ret = sx_common_update_chan_en(data, 0, data->chan_event);
mutex_unlock(&data->mutex);
return ret;
}
static const struct iio_buffer_setup_ops sx_common_buffer_setup_ops = {
.preenable = sx_common_buffer_preenable,
.postdisable = sx_common_buffer_postdisable,
};
#define SX_COMMON_SOFT_RESET 0xde
static int sx_common_init_device(struct device *dev, struct iio_dev *indio_dev)
{
struct sx_common_data *data = iio_priv(indio_dev);
struct sx_common_reg_default tmp;
const struct sx_common_reg_default *initval;
int ret;
unsigned int i, val;
ret = regmap_write(data->regmap, data->chip_info->reg_reset,
SX_COMMON_SOFT_RESET);
if (ret)
return ret;
usleep_range(1000, 2000); /* power-up time is ~1ms. */
/* Clear reset interrupt state by reading SX_COMMON_REG_IRQ_SRC. */
ret = regmap_read(data->regmap, SX_COMMON_REG_IRQ_SRC, &val);
if (ret)
return ret;
/* Program defaults from constant or BIOS. */
for (i = 0; i < data->chip_info->num_default_regs; i++) {
initval = data->chip_info->ops.get_default_reg(dev, i, &tmp);
ret = regmap_write(data->regmap, initval->reg, initval->def);
if (ret)
return ret;
}
return data->chip_info->ops.init_compensation(indio_dev);
}
/**
* sx_common_probe() - Common setup for Semtech SAR sensor
* @client: I2C client object
* @chip_info: Semtech sensor chip information.
* @regmap_config: Sensor registers map configuration.
*/
int sx_common_probe(struct i2c_client *client,
const struct sx_common_chip_info *chip_info,
const struct regmap_config *regmap_config)
{
static const char * const regulator_names[] = { "vdd", "svdd" };
struct device *dev = &client->dev;
struct iio_dev *indio_dev;
struct sx_common_data *data;
int ret;
indio_dev = devm_iio_device_alloc(dev, sizeof(*data));
if (!indio_dev)
return -ENOMEM;
data = iio_priv(indio_dev);
data->chip_info = chip_info;
data->client = client;
mutex_init(&data->mutex);
init_completion(&data->completion);
data->regmap = devm_regmap_init_i2c(client, regmap_config);
if (IS_ERR(data->regmap))
return dev_err_probe(dev, PTR_ERR(data->regmap),
"Could init register map\n");
ret = devm_regulator_bulk_get_enable(dev, ARRAY_SIZE(regulator_names),
regulator_names);
if (ret)
return dev_err_probe(dev, ret, "Unable to get regulators\n");
/* Must wait for Tpor time after initial power up */
usleep_range(1000, 1100);
ret = data->chip_info->ops.check_whoami(dev, indio_dev);
if (ret)
return dev_err_probe(dev, ret, "error reading WHOAMI\n");
indio_dev->modes = INDIO_DIRECT_MODE;
indio_dev->channels = data->chip_info->iio_channels;
indio_dev->num_channels = data->chip_info->num_iio_channels;
indio_dev->info = &data->chip_info->iio_info;
i2c_set_clientdata(client, indio_dev);
ret = sx_common_init_device(dev, indio_dev);
if (ret)
return dev_err_probe(dev, ret, "Unable to initialize sensor\n");
if (client->irq) {
ret = devm_request_threaded_irq(dev, client->irq,
sx_common_irq_handler,
sx_common_irq_thread_handler,
IRQF_ONESHOT,
"sx_event", indio_dev);
if (ret)
return dev_err_probe(dev, ret, "No IRQ\n");
data->trig = devm_iio_trigger_alloc(dev, "%s-dev%d",
indio_dev->name,
iio_device_id(indio_dev));
if (!data->trig)
return -ENOMEM;
data->trig->ops = &sx_common_trigger_ops;
iio_trigger_set_drvdata(data->trig, indio_dev);
ret = devm_iio_trigger_register(dev, data->trig);
if (ret)
return ret;
}
ret = devm_iio_triggered_buffer_setup(dev, indio_dev,
iio_pollfunc_store_time,
sx_common_trigger_handler,
&sx_common_buffer_setup_ops);
if (ret)
return ret;
return devm_iio_device_register(dev, indio_dev);
}
EXPORT_SYMBOL_NS_GPL(sx_common_probe, "SEMTECH_PROX");
MODULE_AUTHOR("Gwendal Grignou <gwendal@chromium.org>");
MODULE_DESCRIPTION("Common functions and structures for Semtech sensor");
MODULE_LICENSE("GPL v2");
@@ -0,0 +1,155 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright 2021 Google LLC.
*
* Code shared between most Semtech SAR sensor driver.
*/
#ifndef IIO_SX_COMMON_H
#define IIO_SX_COMMON_H
#include <linux/iio/iio.h>
#include <linux/iio/types.h>
#include <linux/regulator/consumer.h>
#include <linux/types.h>
struct device;
struct i2c_client;
struct regmap_config;
struct sx_common_data;
#define SX_COMMON_REG_IRQ_SRC 0x00
#define SX_COMMON_MAX_NUM_CHANNELS 4
static_assert(SX_COMMON_MAX_NUM_CHANNELS < BITS_PER_LONG);
struct sx_common_reg_default {
u8 reg;
u8 def;
const char *property;
};
/**
* struct sx_common_ops: function pointers needed by common code
*
* List functions needed by common code to gather information or configure
* the sensor.
*
* @read_prox_data: Function to read raw proximity data.
* @check_whoami: Set device name based on whoami register.
* @init_compensation: Function to set initial compensation.
* @wait_for_sample: When there are no physical IRQ, function to wait for a
* sample to be ready.
* @get_default_reg: Populate the initial value for a given register.
*/
struct sx_common_ops {
int (*read_prox_data)(struct sx_common_data *data,
const struct iio_chan_spec *chan, __be16 *val);
int (*check_whoami)(struct device *dev, struct iio_dev *indio_dev);
int (*init_compensation)(struct iio_dev *indio_dev);
int (*wait_for_sample)(struct sx_common_data *data);
const struct sx_common_reg_default *
(*get_default_reg)(struct device *dev, int idx,
struct sx_common_reg_default *reg_def);
};
/**
* struct sx_common_chip_info: Semtech Sensor private chip information
*
* @reg_stat: Main status register address.
* @reg_irq_msk: IRQ mask register address.
* @reg_enable_chan: Address to enable/disable channels.
* Each phase presented by the sensor is an IIO channel..
* @reg_reset: Reset register address.
* @mask_enable_chan: Mask over the channels bits in the enable channel
* register.
* @stat_offset: Offset to check phase status.
* @irq_msk_offset: Offset to enable interrupt in the IRQ mask
* register.
* @num_channels: Number of channels.
* @num_default_regs: Number of internal registers that can be configured.
*
* @ops: Private functions pointers.
* @iio_channels: Description of exposed iio channels.
* @num_iio_channels: Number of iio_channels.
* @iio_info: iio_info structure for this driver.
*/
struct sx_common_chip_info {
unsigned int reg_stat;
unsigned int reg_irq_msk;
unsigned int reg_enable_chan;
unsigned int reg_reset;
unsigned int mask_enable_chan;
unsigned int stat_offset;
unsigned int irq_msk_offset;
unsigned int num_channels;
int num_default_regs;
struct sx_common_ops ops;
const struct iio_chan_spec *iio_channels;
int num_iio_channels;
struct iio_info iio_info;
};
/**
* struct sx_common_data: Semtech Sensor private data structure.
*
* @chip_info: Structure defining sensor internals.
* @mutex: Serialize access to registers and channel configuration.
* @completion: completion object to wait for data acquisition.
* @client: I2C client structure.
* @trig: IIO trigger object.
* @regmap: Register map.
* @chan_prox_stat: Last reading of the proximity status for each channel.
* We only send an event to user space when this changes.
* @trigger_enabled: True when the device trigger is enabled.
* @buffer: Buffer to store raw samples.
* @suspend_ctrl: Remember enabled channels and sample rate during suspend.
* @chan_read: Bit field for each raw channel enabled.
* @chan_event: Bit field for each event enabled.
*/
struct sx_common_data {
const struct sx_common_chip_info *chip_info;
struct mutex mutex;
struct completion completion;
struct i2c_client *client;
struct iio_trigger *trig;
struct regmap *regmap;
unsigned long chan_prox_stat;
bool trigger_enabled;
/* Ensure correct alignment of timestamp when present. */
struct {
__be16 channels[SX_COMMON_MAX_NUM_CHANNELS];
aligned_s64 ts;
} buffer;
unsigned int suspend_ctrl;
unsigned long chan_read;
unsigned long chan_event;
};
int sx_common_read_proximity(struct sx_common_data *data,
const struct iio_chan_spec *chan, int *val);
int sx_common_read_event_config(struct iio_dev *indio_dev,
const struct iio_chan_spec *chan,
enum iio_event_type type,
enum iio_event_direction dir);
int sx_common_write_event_config(struct iio_dev *indio_dev,
const struct iio_chan_spec *chan,
enum iio_event_type type,
enum iio_event_direction dir, bool state);
int sx_common_probe(struct i2c_client *client,
const struct sx_common_chip_info *chip_info,
const struct regmap_config *regmap_config);
/* 3 is the number of events defined by a single phase. */
extern const struct iio_event_spec sx_common_events[3];
#endif /* IIO_SX_COMMON_H */
@@ -0,0 +1,671 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Support for Vishay VCNL3020 proximity sensor on i2c bus.
* Based on Vishay VCNL4000 driver code.
*/
#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/err.h>
#include <linux/delay.h>
#include <linux/regmap.h>
#include <linux/interrupt.h>
#include <linux/iio/iio.h>
#include <linux/iio/events.h>
#define VCNL3020_PROD_ID 0x21
#define VCNL_COMMAND 0x80 /* Command register */
#define VCNL_PROD_REV 0x81 /* Product ID and Revision ID */
#define VCNL_PROXIMITY_RATE 0x82 /* Rate of Proximity Measurement */
#define VCNL_LED_CURRENT 0x83 /* IR LED current for proximity mode */
#define VCNL_PS_RESULT_HI 0x87 /* Proximity result register, MSB */
#define VCNL_PS_RESULT_LO 0x88 /* Proximity result register, LSB */
#define VCNL_PS_ICR 0x89 /* Interrupt Control Register */
#define VCNL_PS_LO_THR_HI 0x8a /* High byte of low threshold value */
#define VCNL_PS_LO_THR_LO 0x8b /* Low byte of low threshold value */
#define VCNL_PS_HI_THR_HI 0x8c /* High byte of high threshold value */
#define VCNL_PS_HI_THR_LO 0x8d /* Low byte of high threshold value */
#define VCNL_ISR 0x8e /* Interrupt Status Register */
#define VCNL_PS_MOD_ADJ 0x8f /* Proximity Modulator Timing Adjustment */
/* Bit masks for COMMAND register */
#define VCNL_PS_RDY BIT(5) /* proximity data ready? */
#define VCNL_PS_OD BIT(3) /* start on-demand proximity
* measurement
*/
/* Enables periodic proximity measurement */
#define VCNL_PS_EN BIT(1)
/* Enables state machine and LP oscillator for self timed measurements */
#define VCNL_PS_SELFTIMED_EN BIT(0)
/* Bit masks for ICR */
/* Enable interrupts on low or high thresholds */
#define VCNL_ICR_THRES_EN BIT(1)
/* Bit masks for ISR */
#define VCNL_INT_TH_HI BIT(0) /* High threshold hit */
#define VCNL_INT_TH_LOW BIT(1) /* Low threshold hit */
#define VCNL_ON_DEMAND_TIMEOUT_US 100000
#define VCNL_POLL_US 20000
static const int vcnl3020_prox_sampling_frequency[][2] = {
{1, 950000},
{3, 906250},
{7, 812500},
{16, 625000},
{31, 250000},
{62, 500000},
{125, 0},
{250, 0},
};
/**
* struct vcnl3020_data - vcnl3020 specific data.
* @regmap: device register map.
* @dev: vcnl3020 device.
* @rev: revision id.
* @lock: lock for protecting access to device hardware registers.
* @buf: __be16 buffer.
*/
struct vcnl3020_data {
struct regmap *regmap;
struct device *dev;
u8 rev;
struct mutex lock;
__be16 buf;
};
/**
* struct vcnl3020_property - vcnl3020 property.
* @name: property name.
* @reg: i2c register offset.
* @conversion_func: conversion function.
*/
struct vcnl3020_property {
const char *name;
u32 reg;
u32 (*conversion_func)(u32 *val);
};
static u32 microamp_to_reg(u32 *val)
{
/*
* An example of conversion from uA to reg val:
* 200000 uA == 200 mA == 20
*/
return *val /= 10000;
};
static const struct vcnl3020_property vcnl3020_led_current_property = {
.name = "vishay,led-current-microamp",
.reg = VCNL_LED_CURRENT,
.conversion_func = microamp_to_reg,
};
static int vcnl3020_get_and_apply_property(struct vcnl3020_data *data,
const struct vcnl3020_property *prop)
{
int rc;
u32 val;
rc = device_property_read_u32(data->dev, prop->name, &val);
if (rc)
return 0;
if (prop->conversion_func)
prop->conversion_func(&val);
rc = regmap_write(data->regmap, prop->reg, val);
if (rc) {
dev_err(data->dev, "Error (%d) setting property (%s)\n",
rc, prop->name);
}
return rc;
}
static int vcnl3020_init(struct vcnl3020_data *data)
{
int rc;
unsigned int reg;
rc = regmap_read(data->regmap, VCNL_PROD_REV, &reg);
if (rc) {
dev_err(data->dev,
"Error (%d) reading product revision\n", rc);
return rc;
}
if (reg != VCNL3020_PROD_ID) {
dev_err(data->dev,
"Product id (%x) did not match vcnl3020 (%x)\n", reg,
VCNL3020_PROD_ID);
return -ENODEV;
}
data->rev = reg;
mutex_init(&data->lock);
return vcnl3020_get_and_apply_property(data,
&vcnl3020_led_current_property);
};
static bool vcnl3020_is_in_periodic_mode(struct vcnl3020_data *data)
{
int rc;
unsigned int cmd;
rc = regmap_read(data->regmap, VCNL_COMMAND, &cmd);
if (rc) {
dev_err(data->dev,
"Error (%d) reading command register\n", rc);
return false;
}
return !!(cmd & VCNL_PS_SELFTIMED_EN);
}
static int vcnl3020_measure_proximity(struct vcnl3020_data *data, int *val)
{
int rc;
unsigned int reg;
mutex_lock(&data->lock);
/* Protect against event capture. */
if (vcnl3020_is_in_periodic_mode(data)) {
rc = -EBUSY;
goto err_unlock;
}
rc = regmap_write(data->regmap, VCNL_COMMAND, VCNL_PS_OD);
if (rc)
goto err_unlock;
/* wait for data to become ready */
rc = regmap_read_poll_timeout(data->regmap, VCNL_COMMAND, reg,
reg & VCNL_PS_RDY, VCNL_POLL_US,
VCNL_ON_DEMAND_TIMEOUT_US);
if (rc) {
dev_err(data->dev,
"Error (%d) reading vcnl3020 command register\n", rc);
goto err_unlock;
}
/* high & low result bytes read */
rc = regmap_bulk_read(data->regmap, VCNL_PS_RESULT_HI, &data->buf,
sizeof(data->buf));
if (rc)
goto err_unlock;
*val = be16_to_cpu(data->buf);
err_unlock:
mutex_unlock(&data->lock);
return rc;
}
static int vcnl3020_read_proxy_samp_freq(struct vcnl3020_data *data, int *val,
int *val2)
{
int rc;
unsigned int prox_rate;
rc = regmap_read(data->regmap, VCNL_PROXIMITY_RATE, &prox_rate);
if (rc)
return rc;
if (prox_rate >= ARRAY_SIZE(vcnl3020_prox_sampling_frequency))
return -EINVAL;
*val = vcnl3020_prox_sampling_frequency[prox_rate][0];
*val2 = vcnl3020_prox_sampling_frequency[prox_rate][1];
return 0;
}
static int vcnl3020_write_proxy_samp_freq(struct vcnl3020_data *data, int val,
int val2)
{
unsigned int i;
int index = -1;
int rc;
mutex_lock(&data->lock);
/* Protect against event capture. */
if (vcnl3020_is_in_periodic_mode(data)) {
rc = -EBUSY;
goto err_unlock;
}
for (i = 0; i < ARRAY_SIZE(vcnl3020_prox_sampling_frequency); i++) {
if (val == vcnl3020_prox_sampling_frequency[i][0] &&
val2 == vcnl3020_prox_sampling_frequency[i][1]) {
index = i;
break;
}
}
if (index < 0) {
rc = -EINVAL;
goto err_unlock;
}
rc = regmap_write(data->regmap, VCNL_PROXIMITY_RATE, index);
if (rc)
dev_err(data->dev,
"Error (%d) writing proximity rate register\n", rc);
err_unlock:
mutex_unlock(&data->lock);
return rc;
}
static bool vcnl3020_is_thr_enabled(struct vcnl3020_data *data)
{
int rc;
unsigned int icr;
rc = regmap_read(data->regmap, VCNL_PS_ICR, &icr);
if (rc) {
dev_err(data->dev,
"Error (%d) reading ICR register\n", rc);
return false;
}
return !!(icr & VCNL_ICR_THRES_EN);
}
static int vcnl3020_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)
{
int rc;
struct vcnl3020_data *data = iio_priv(indio_dev);
switch (info) {
case IIO_EV_INFO_VALUE:
switch (dir) {
case IIO_EV_DIR_RISING:
rc = regmap_bulk_read(data->regmap, VCNL_PS_HI_THR_HI,
&data->buf, sizeof(data->buf));
if (rc < 0)
return rc;
*val = be16_to_cpu(data->buf);
return IIO_VAL_INT;
case IIO_EV_DIR_FALLING:
rc = regmap_bulk_read(data->regmap, VCNL_PS_LO_THR_HI,
&data->buf, sizeof(data->buf));
if (rc < 0)
return rc;
*val = be16_to_cpu(data->buf);
return IIO_VAL_INT;
default:
return -EINVAL;
}
default:
return -EINVAL;
}
}
static int vcnl3020_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)
{
int rc;
struct vcnl3020_data *data = iio_priv(indio_dev);
mutex_lock(&data->lock);
switch (info) {
case IIO_EV_INFO_VALUE:
switch (dir) {
case IIO_EV_DIR_RISING:
/* 16 bit word/ low * high */
data->buf = cpu_to_be16(val);
rc = regmap_bulk_write(data->regmap, VCNL_PS_HI_THR_HI,
&data->buf, sizeof(data->buf));
if (rc < 0)
goto err_unlock;
rc = IIO_VAL_INT;
goto err_unlock;
case IIO_EV_DIR_FALLING:
data->buf = cpu_to_be16(val);
rc = regmap_bulk_write(data->regmap, VCNL_PS_LO_THR_HI,
&data->buf, sizeof(data->buf));
if (rc < 0)
goto err_unlock;
rc = IIO_VAL_INT;
goto err_unlock;
default:
rc = -EINVAL;
goto err_unlock;
}
default:
rc = -EINVAL;
goto err_unlock;
}
err_unlock:
mutex_unlock(&data->lock);
return rc;
}
static int vcnl3020_enable_periodic(struct iio_dev *indio_dev,
struct vcnl3020_data *data)
{
int rc;
int cmd;
mutex_lock(&data->lock);
/* Enable periodic measurement of proximity data. */
cmd = VCNL_PS_EN | VCNL_PS_SELFTIMED_EN;
rc = regmap_write(data->regmap, VCNL_COMMAND, cmd);
if (rc) {
dev_err(data->dev,
"Error (%d) writing command register\n", rc);
goto err_unlock;
}
/*
* Enable interrupts on threshold, for proximity data by
* default.
*/
rc = regmap_write(data->regmap, VCNL_PS_ICR, VCNL_ICR_THRES_EN);
if (rc)
dev_err(data->dev,
"Error (%d) reading ICR register\n", rc);
err_unlock:
mutex_unlock(&data->lock);
return rc;
}
static int vcnl3020_disable_periodic(struct iio_dev *indio_dev,
struct vcnl3020_data *data)
{
int rc;
mutex_lock(&data->lock);
rc = regmap_write(data->regmap, VCNL_COMMAND, 0);
if (rc) {
dev_err(data->dev,
"Error (%d) writing command register\n", rc);
goto err_unlock;
}
rc = regmap_write(data->regmap, VCNL_PS_ICR, 0);
if (rc) {
dev_err(data->dev,
"Error (%d) writing ICR register\n", rc);
goto err_unlock;
}
/* Clear interrupt flag bit */
rc = regmap_write(data->regmap, VCNL_ISR, 0);
if (rc)
dev_err(data->dev,
"Error (%d) writing ISR register\n", rc);
err_unlock:
mutex_unlock(&data->lock);
return rc;
}
static int vcnl3020_config_threshold(struct iio_dev *indio_dev, bool state)
{
struct vcnl3020_data *data = iio_priv(indio_dev);
if (state) {
return vcnl3020_enable_periodic(indio_dev, data);
} else {
if (!vcnl3020_is_thr_enabled(data))
return 0;
return vcnl3020_disable_periodic(indio_dev, data);
}
}
static int vcnl3020_write_event_config(struct iio_dev *indio_dev,
const struct iio_chan_spec *chan,
enum iio_event_type type,
enum iio_event_direction dir,
bool state)
{
switch (chan->type) {
case IIO_PROXIMITY:
return vcnl3020_config_threshold(indio_dev, state);
default:
return -EINVAL;
}
}
static int vcnl3020_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 vcnl3020_data *data = iio_priv(indio_dev);
switch (chan->type) {
case IIO_PROXIMITY:
return vcnl3020_is_thr_enabled(data);
default:
return -EINVAL;
}
}
static const struct iio_event_spec vcnl3020_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_ENABLE),
},
};
static const struct iio_chan_spec vcnl3020_channels[] = {
{
.type = IIO_PROXIMITY,
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
BIT(IIO_CHAN_INFO_SAMP_FREQ),
.info_mask_separate_available = BIT(IIO_CHAN_INFO_SAMP_FREQ),
.event_spec = vcnl3020_event_spec,
.num_event_specs = ARRAY_SIZE(vcnl3020_event_spec),
},
};
static int vcnl3020_read_raw(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan, int *val,
int *val2, long mask)
{
int rc;
struct vcnl3020_data *data = iio_priv(indio_dev);
switch (mask) {
case IIO_CHAN_INFO_RAW:
rc = vcnl3020_measure_proximity(data, val);
if (rc)
return rc;
return IIO_VAL_INT;
case IIO_CHAN_INFO_SAMP_FREQ:
rc = vcnl3020_read_proxy_samp_freq(data, val, val2);
if (rc < 0)
return rc;
return IIO_VAL_INT_PLUS_MICRO;
default:
return -EINVAL;
}
}
static int vcnl3020_write_raw(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan,
int val, int val2, long mask)
{
struct vcnl3020_data *data = iio_priv(indio_dev);
switch (mask) {
case IIO_CHAN_INFO_SAMP_FREQ:
return vcnl3020_write_proxy_samp_freq(data, val, val2);
default:
return -EINVAL;
}
}
static int vcnl3020_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 = (int *)vcnl3020_prox_sampling_frequency;
*type = IIO_VAL_INT_PLUS_MICRO;
*length = 2 * ARRAY_SIZE(vcnl3020_prox_sampling_frequency);
return IIO_AVAIL_LIST;
default:
return -EINVAL;
}
}
static const struct iio_info vcnl3020_info = {
.read_raw = vcnl3020_read_raw,
.write_raw = vcnl3020_write_raw,
.read_avail = vcnl3020_read_avail,
.read_event_value = vcnl3020_read_event,
.write_event_value = vcnl3020_write_event,
.read_event_config = vcnl3020_read_event_config,
.write_event_config = vcnl3020_write_event_config,
};
static const struct regmap_config vcnl3020_regmap_config = {
.reg_bits = 8,
.val_bits = 8,
.max_register = VCNL_PS_MOD_ADJ,
};
static irqreturn_t vcnl3020_handle_irq_thread(int irq, void *p)
{
struct iio_dev *indio_dev = p;
struct vcnl3020_data *data = iio_priv(indio_dev);
unsigned int isr;
int rc;
rc = regmap_read(data->regmap, VCNL_ISR, &isr);
if (rc) {
dev_err(data->dev, "Error (%d) reading reg (0x%x)\n",
rc, VCNL_ISR);
return IRQ_HANDLED;
}
if (!(isr & VCNL_ICR_THRES_EN))
return IRQ_NONE;
iio_push_event(indio_dev,
IIO_UNMOD_EVENT_CODE(IIO_PROXIMITY, 1,
IIO_EV_TYPE_THRESH,
IIO_EV_DIR_RISING),
iio_get_time_ns(indio_dev));
rc = regmap_write(data->regmap, VCNL_ISR, isr & VCNL_ICR_THRES_EN);
if (rc)
dev_err(data->dev, "Error (%d) writing in reg (0x%x)\n",
rc, VCNL_ISR);
return IRQ_HANDLED;
}
static int vcnl3020_probe(struct i2c_client *client)
{
struct vcnl3020_data *data;
struct iio_dev *indio_dev;
struct regmap *regmap;
int rc;
regmap = devm_regmap_init_i2c(client, &vcnl3020_regmap_config);
if (IS_ERR(regmap)) {
dev_err(&client->dev, "regmap_init failed\n");
return PTR_ERR(regmap);
}
indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
if (!indio_dev)
return -ENOMEM;
data = iio_priv(indio_dev);
i2c_set_clientdata(client, indio_dev);
data->regmap = regmap;
data->dev = &client->dev;
rc = vcnl3020_init(data);
if (rc)
return rc;
indio_dev->info = &vcnl3020_info;
indio_dev->channels = vcnl3020_channels;
indio_dev->num_channels = ARRAY_SIZE(vcnl3020_channels);
indio_dev->name = "vcnl3020";
indio_dev->modes = INDIO_DIRECT_MODE;
if (client->irq) {
rc = devm_request_threaded_irq(&client->dev, client->irq,
NULL, vcnl3020_handle_irq_thread,
IRQF_ONESHOT, indio_dev->name,
indio_dev);
if (rc) {
dev_err(&client->dev,
"Error (%d) irq request failed (%u)\n", rc,
client->irq);
return rc;
}
}
return devm_iio_device_register(&client->dev, indio_dev);
}
static const struct of_device_id vcnl3020_of_match[] = {
{
.compatible = "vishay,vcnl3020",
},
{ }
};
MODULE_DEVICE_TABLE(of, vcnl3020_of_match);
static struct i2c_driver vcnl3020_driver = {
.driver = {
.name = "vcnl3020",
.of_match_table = vcnl3020_of_match,
},
.probe = vcnl3020_probe,
};
module_i2c_driver(vcnl3020_driver);
MODULE_AUTHOR("Ivan Mikhaylov <i.mikhaylov@yadro.com>");
MODULE_DESCRIPTION("Vishay VCNL3020 proximity sensor driver");
MODULE_LICENSE("GPL");
@@ -0,0 +1,418 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Support for ST VL53L0X FlightSense ToF Ranging Sensor on a i2c bus.
*
* Copyright (C) 2016 STMicroelectronics Imaging Division.
* Copyright (C) 2018 Song Qiang <songqiang1304521@gmail.com>
* Copyright (C) 2020 Ivan Drobyshevskyi <drobyshevskyi@gmail.com>
*
* Datasheet available at
* <https://www.st.com/resource/en/datasheet/vl53l0x.pdf>
*
* Default 7-bit i2c slave address 0x29.
*
* TODO: FIFO buffer, continuous mode, range selection, sensor ID check.
*/
#include <linux/delay.h>
#include <linux/gpio/consumer.h>
#include <linux/i2c.h>
#include <linux/irq.h>
#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/unaligned.h>
#include <linux/iio/iio.h>
#include <linux/iio/buffer.h>
#include <linux/iio/trigger.h>
#include <linux/iio/trigger_consumer.h>
#include <linux/iio/triggered_buffer.h>
#define VL_REG_SYSRANGE_START 0x00
#define VL_REG_SYSRANGE_MODE_MASK GENMASK(3, 0)
#define VL_REG_SYSRANGE_MODE_SINGLESHOT 0x00
#define VL_REG_SYSRANGE_MODE_START_STOP BIT(0)
#define VL_REG_SYSRANGE_MODE_BACKTOBACK BIT(1)
#define VL_REG_SYSRANGE_MODE_TIMED BIT(2)
#define VL_REG_SYSRANGE_MODE_HISTOGRAM BIT(3)
#define VL_REG_SYSTEM_INTERRUPT_CONFIG_GPIO 0x0A
#define VL_REG_SYSTEM_INTERRUPT_GPIO_NEW_SAMPLE_READY BIT(2)
#define VL_REG_SYSTEM_INTERRUPT_CLEAR 0x0B
#define VL_REG_RESULT_INT_STATUS 0x13
#define VL_REG_RESULT_RANGE_STATUS 0x14
#define VL_REG_IDENTIFICATION_MODEL_ID 0xC0
#define VL_REG_RESULT_RANGE_STATUS_COMPLETE BIT(0)
#define VL53L0X_MODEL_ID_VAL 0xEE
#define VL53L0X_CONTINUOUS_MODE 0x02
#define VL53L0X_SINGLE_MODE 0x01
struct vl53l0x_data {
struct i2c_client *client;
struct completion completion;
struct regulator *vdd_supply;
struct gpio_desc *reset_gpio;
struct iio_trigger *trig;
};
static int vl53l0x_clear_irq(struct vl53l0x_data *data)
{ int ret;
ret = i2c_smbus_write_byte_data(data->client,
VL_REG_SYSTEM_INTERRUPT_CLEAR, 1);
if (ret < 0) {
dev_err(&data->client->dev, "failed to clear irq: %d\n", ret);
return -EINVAL;
}
return 0;
}
static irqreturn_t vl53l0x_trigger_handler(int irq, void *priv)
{
struct iio_poll_func *pf = priv;
struct iio_dev *indio_dev = pf->indio_dev;
struct vl53l0x_data *data = iio_priv(indio_dev);
u8 buffer[12];
int ret;
struct {
u16 chan;
aligned_s64 timestamp;
} scan = { };
ret = i2c_smbus_read_i2c_block_data(data->client,
VL_REG_RESULT_RANGE_STATUS,
sizeof(buffer), buffer);
if (ret != 12)
goto done;
scan.chan = get_unaligned_be16(&buffer[10]);
iio_push_to_buffers_with_ts(indio_dev, &scan, sizeof(scan),
iio_get_time_ns(indio_dev));
done:
iio_trigger_notify_done(indio_dev->trig);
vl53l0x_clear_irq(data);
return IRQ_HANDLED;
}
static irqreturn_t vl53l0x_threaded_irq(int irq, void *priv)
{
struct iio_dev *indio_dev = priv;
struct vl53l0x_data *data = iio_priv(indio_dev);
if (iio_buffer_enabled(indio_dev))
iio_trigger_poll_nested(indio_dev->trig);
else
complete(&data->completion);
return IRQ_HANDLED;
}
static int vl53l0x_configure_irq(struct i2c_client *client,
struct iio_dev *indio_dev)
{
int irq_flags = irq_get_trigger_type(client->irq);
struct vl53l0x_data *data = iio_priv(indio_dev);
int ret;
if (!irq_flags)
irq_flags = IRQF_TRIGGER_FALLING;
ret = devm_request_threaded_irq(&client->dev, client->irq,
NULL, vl53l0x_threaded_irq,
irq_flags | IRQF_ONESHOT, indio_dev->name, indio_dev);
if (ret) {
dev_err(&client->dev, "devm_request_irq error: %d\n", ret);
return ret;
}
ret = i2c_smbus_write_byte_data(data->client,
VL_REG_SYSTEM_INTERRUPT_CONFIG_GPIO,
VL_REG_SYSTEM_INTERRUPT_GPIO_NEW_SAMPLE_READY);
if (ret < 0)
dev_err(&client->dev, "failed to configure IRQ: %d\n", ret);
return ret;
}
static int vl53l0x_read_proximity(struct vl53l0x_data *data,
const struct iio_chan_spec *chan,
int *val)
{
struct i2c_client *client = data->client;
u16 tries = 20;
u8 buffer[12];
int ret;
unsigned long time_left;
ret = i2c_smbus_write_byte_data(client, VL_REG_SYSRANGE_START, 1);
if (ret < 0)
return ret;
if (data->client->irq) {
reinit_completion(&data->completion);
time_left = wait_for_completion_timeout(&data->completion, HZ/10);
if (time_left == 0)
return -ETIMEDOUT;
ret = vl53l0x_clear_irq(data);
if (ret < 0)
return ret;
} else {
do {
ret = i2c_smbus_read_byte_data(client,
VL_REG_RESULT_RANGE_STATUS);
if (ret < 0)
return ret;
if (ret & VL_REG_RESULT_RANGE_STATUS_COMPLETE)
break;
usleep_range(1000, 5000);
} while (--tries);
if (!tries)
return -ETIMEDOUT;
}
ret = i2c_smbus_read_i2c_block_data(client, VL_REG_RESULT_RANGE_STATUS,
12, buffer);
if (ret < 0)
return ret;
else if (ret != 12)
return -EREMOTEIO;
/* Values should be between 30~1200 in millimeters. */
*val = get_unaligned_be16(&buffer[10]);
return 0;
}
static const struct iio_chan_spec vl53l0x_channels[] = {
{
.type = IIO_DISTANCE,
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
BIT(IIO_CHAN_INFO_SCALE),
.scan_index = 0,
.scan_type = {
.sign = 'u',
.realbits = 12,
.storagebits = 16,
},
},
IIO_CHAN_SOFT_TIMESTAMP(1),
};
static int vl53l0x_read_raw(struct iio_dev *indio_dev,
const struct iio_chan_spec *chan,
int *val, int *val2, long mask)
{
struct vl53l0x_data *data = iio_priv(indio_dev);
int ret;
if (chan->type != IIO_DISTANCE)
return -EINVAL;
switch (mask) {
case IIO_CHAN_INFO_RAW:
ret = vl53l0x_read_proximity(data, chan, val);
if (ret < 0)
return ret;
return IIO_VAL_INT;
case IIO_CHAN_INFO_SCALE:
*val = 0;
*val2 = 1000;
return IIO_VAL_INT_PLUS_MICRO;
default:
return -EINVAL;
}
}
static int vl53l0x_validate_trigger(struct iio_dev *indio_dev, struct iio_trigger *trig)
{
struct vl53l0x_data *data = iio_priv(indio_dev);
return data->trig == trig ? 0 : -EINVAL;
}
static const struct iio_info vl53l0x_info = {
.read_raw = vl53l0x_read_raw,
.validate_trigger = vl53l0x_validate_trigger,
};
static void vl53l0x_power_off(void *_data)
{
struct vl53l0x_data *data = _data;
gpiod_set_value_cansleep(data->reset_gpio, 1);
regulator_disable(data->vdd_supply);
}
static int vl53l0x_power_on(struct vl53l0x_data *data)
{
int ret;
ret = regulator_enable(data->vdd_supply);
if (ret)
return ret;
gpiod_set_value_cansleep(data->reset_gpio, 0);
usleep_range(3200, 5000);
return 0;
}
static int vl53l0x_buffer_postenable(struct iio_dev *indio_dev)
{
struct vl53l0x_data *data = iio_priv(indio_dev);
return i2c_smbus_write_byte_data(data->client, VL_REG_SYSRANGE_START,
VL53L0X_CONTINUOUS_MODE);
}
static int vl53l0x_buffer_postdisable(struct iio_dev *indio_dev)
{
struct vl53l0x_data *data = iio_priv(indio_dev);
int ret;
ret = i2c_smbus_write_byte_data(data->client, VL_REG_SYSRANGE_START,
VL53L0X_SINGLE_MODE);
if (ret < 0)
return ret;
/* Let the ongoing reading finish */
reinit_completion(&data->completion);
wait_for_completion_timeout(&data->completion, HZ / 10);
return vl53l0x_clear_irq(data);
}
static const struct iio_buffer_setup_ops iio_triggered_buffer_setup_ops = {
.postenable = &vl53l0x_buffer_postenable,
.postdisable = &vl53l0x_buffer_postdisable,
};
static const struct iio_trigger_ops vl53l0x_trigger_ops = {
.validate_device = iio_trigger_validate_own_device,
};
static int vl53l0x_probe(struct i2c_client *client)
{
struct vl53l0x_data *data;
struct iio_dev *indio_dev;
int ret;
indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
if (!indio_dev)
return -ENOMEM;
data = iio_priv(indio_dev);
data->client = client;
i2c_set_clientdata(client, indio_dev);
if (!i2c_check_functionality(client->adapter,
I2C_FUNC_SMBUS_READ_I2C_BLOCK |
I2C_FUNC_SMBUS_BYTE_DATA))
return -EOPNOTSUPP;
ret = i2c_smbus_read_byte_data(data->client, VL_REG_IDENTIFICATION_MODEL_ID);
if (ret < 0)
return -EINVAL;
if (ret != VL53L0X_MODEL_ID_VAL)
dev_info(&client->dev, "Unknown model id: 0x%x", ret);
data->vdd_supply = devm_regulator_get(&client->dev, "vdd");
if (IS_ERR(data->vdd_supply))
return dev_err_probe(&client->dev, PTR_ERR(data->vdd_supply),
"Unable to get VDD regulator\n");
data->reset_gpio = devm_gpiod_get_optional(&client->dev, "reset", GPIOD_OUT_HIGH);
if (IS_ERR(data->reset_gpio))
return dev_err_probe(&client->dev, PTR_ERR(data->reset_gpio),
"Cannot get reset GPIO\n");
ret = vl53l0x_power_on(data);
if (ret)
return dev_err_probe(&client->dev, ret,
"Failed to power on the chip\n");
ret = devm_add_action_or_reset(&client->dev, vl53l0x_power_off, data);
if (ret)
return ret;
indio_dev->name = "vl53l0x";
indio_dev->info = &vl53l0x_info;
indio_dev->channels = vl53l0x_channels;
indio_dev->num_channels = ARRAY_SIZE(vl53l0x_channels);
indio_dev->modes = INDIO_DIRECT_MODE;
/* usage of interrupt is optional */
if (client->irq) {
init_completion(&data->completion);
data->trig = devm_iio_trigger_alloc(&client->dev, "%s-dev%d",
indio_dev->name,
iio_device_id(indio_dev));
if (!data->trig)
return -ENOMEM;
data->trig->ops = &vl53l0x_trigger_ops;
iio_trigger_set_drvdata(data->trig, indio_dev);
ret = devm_iio_trigger_register(&client->dev, data->trig);
if (ret)
return ret;
indio_dev->trig = iio_trigger_get(data->trig);
ret = vl53l0x_configure_irq(client, indio_dev);
if (ret)
return ret;
ret = devm_iio_triggered_buffer_setup(&client->dev,
indio_dev,
NULL,
&vl53l0x_trigger_handler,
&iio_triggered_buffer_setup_ops);
if (ret)
return ret;
}
return devm_iio_device_register(&client->dev, indio_dev);
}
static const struct i2c_device_id vl53l0x_id[] = {
{ .name = "vl53l0x" },
{ }
};
MODULE_DEVICE_TABLE(i2c, vl53l0x_id);
static const struct of_device_id st_vl53l0x_dt_match[] = {
{ .compatible = "st,vl53l0x", },
{ }
};
MODULE_DEVICE_TABLE(of, st_vl53l0x_dt_match);
static struct i2c_driver vl53l0x_driver = {
.driver = {
.name = "vl53l0x-i2c",
.of_match_table = st_vl53l0x_dt_match,
},
.probe = vl53l0x_probe,
.id_table = vl53l0x_id,
};
module_i2c_driver(vl53l0x_driver);
MODULE_AUTHOR("Song Qiang <songqiang1304521@gmail.com>");
MODULE_DESCRIPTION("ST vl53l0x ToF ranging sensor driver");
MODULE_LICENSE("GPL v2");
@@ -0,0 +1,755 @@
// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause
/*
* Support for ST VL53L1X FlightSense ToF Ranging Sensor on a i2c bus.
*
* Copyright (C) 2026 Siratul Islam <email@sirat.me>
*
* Datasheet available at
* <https://www.st.com/resource/en/datasheet/vl53l1x.pdf>
*
* Default 7-bit i2c slave address 0x29.
*
* The VL53L1X requires a firmware configuration blob to be loaded at boot.
* Register values for the default configuration are taken from
* ST's VL53L1X Ultra Lite Driver (STSW-IMG009).
*/
#include <linux/array_size.h>
#include <linux/bits.h>
#include <linux/bitfield.h>
#include <linux/completion.h>
#include <linux/delay.h>
#include <linux/dev_printk.h>
#include <linux/err.h>
#include <linux/i2c.h>
#include <linux/interrupt.h>
#include <linux/math.h>
#include <linux/module.h>
#include <linux/regmap.h>
#include <linux/regulator/consumer.h>
#include <linux/reset.h>
#include <linux/time.h>
#include <linux/types.h>
#include <asm/byteorder.h>
#include <linux/iio/buffer.h>
#include <linux/iio/iio.h>
#include <linux/iio/trigger.h>
#include <linux/iio/trigger_consumer.h>
#include <linux/iio/triggered_buffer.h>
#define VL53L1X_REG_SOFT_RESET 0x0000
#define VL53L1X_REG_VHV_CONFIG__TIMEOUT_MACROP_LOOP_BOUND 0x0008
#define VL53L1X_REG_VHV_CONFIG__INIT 0x000B
#define VL53L1X_REG_GPIO_HV_MUX__CTRL 0x0030
#define VL53L1X_REG_GPIO__TIO_HV_STATUS 0x0031
#define VL53L1X_REG_SYSTEM__INTERRUPT_CONFIG_GPIO 0x0046
#define VL53L1X_REG_PHASECAL_CONFIG__TIMEOUT_MACROP 0x004B
#define VL53L1X_REG_RANGE_CONFIG__TIMEOUT_MACROP_A 0x005E
#define VL53L1X_REG_RANGE_CONFIG__VCSEL_PERIOD_A 0x0060
#define VL53L1X_REG_RANGE_CONFIG__TIMEOUT_MACROP_B 0x0061
#define VL53L1X_REG_RANGE_CONFIG__VCSEL_PERIOD_B 0x0063
#define VL53L1X_REG_RANGE_CONFIG__VALID_PHASE_HIGH 0x0069
#define VL53L1X_REG_SYSTEM__INTERMEASUREMENT_PERIOD 0x006C
#define VL53L1X_REG_SD_CONFIG__WOI_SD0 0x0078
#define VL53L1X_REG_SD_CONFIG__WOI_SD1 0x0079
#define VL53L1X_REG_SD_CONFIG__INITIAL_PHASE_SD0 0x007A
#define VL53L1X_REG_SD_CONFIG__INITIAL_PHASE_SD1 0x007B
#define VL53L1X_REG_SYSTEM__INTERRUPT_CLEAR 0x0086
#define VL53L1X_REG_SYSTEM__MODE_START 0x0087
#define VL53L1X_REG_RESULT__RANGE_STATUS 0x0089
#define VL53L1X_REG_RESULT__FINAL_CROSSTALK_CORRECTED_RANGE_MM_SD0 0x0096
#define VL53L1X_REG_RESULT__OSC_CALIBRATE_VAL 0x00DE
#define VL53L1X_REG_FIRMWARE__SYSTEM_STATUS 0x00E5
#define VL53L1X_REG_IDENTIFICATION__MODEL_ID 0x010F
#define VL53L1X_REG_DEFAULT_CONFIG 0x002D
#define VL53L1X_MODEL_ID_VAL 0xEACC
#define VL53L1X_MODE_START_TIMED 0x40
#define VL53L1X_MODE_START_STOP 0x00
#define VL53L1X_INT_NEW_SAMPLE_READY 0x02
#define VL53L1X_GPIO_HV_MUX_POLARITY BIT(4)
#define VL53L1X_VHV_LOOP_BOUND_TWO 0x09
#define VL53L1X_RANGE_STATUS_MASK GENMASK(4, 0)
#define VL53L1X_RANGE_STATUS_VALID 9
#define VL53L1X_OSC_CALIBRATE_MASK GENMASK(9, 0)
/* Inter-measurement period uses PLL divider with 1.075 oscillator correction */
static const struct u32_fract vl53l1x_osc_correction = {
.numerator = 1075,
.denominator = 1000,
};
enum vl53l1x_distance_mode {
VL53L1X_SHORT,
VL53L1X_LONG,
};
struct vl53l1x_data {
struct regmap *regmap;
struct completion completion;
struct reset_control *xshut_reset;
enum vl53l1x_distance_mode distance_mode;
u8 gpio_polarity;
int irq;
};
static const struct regmap_range vl53l1x_volatile_ranges[] = {
regmap_reg_range(VL53L1X_REG_GPIO__TIO_HV_STATUS,
VL53L1X_REG_GPIO__TIO_HV_STATUS),
regmap_reg_range(VL53L1X_REG_RESULT__RANGE_STATUS,
VL53L1X_REG_RESULT__RANGE_STATUS),
regmap_reg_range(VL53L1X_REG_RESULT__FINAL_CROSSTALK_CORRECTED_RANGE_MM_SD0,
VL53L1X_REG_RESULT__FINAL_CROSSTALK_CORRECTED_RANGE_MM_SD0 + 1),
regmap_reg_range(VL53L1X_REG_RESULT__OSC_CALIBRATE_VAL,
VL53L1X_REG_RESULT__OSC_CALIBRATE_VAL + 1),
regmap_reg_range(VL53L1X_REG_FIRMWARE__SYSTEM_STATUS,
VL53L1X_REG_FIRMWARE__SYSTEM_STATUS),
};
static const struct regmap_access_table vl53l1x_volatile_table = {
.yes_ranges = vl53l1x_volatile_ranges,
.n_yes_ranges = ARRAY_SIZE(vl53l1x_volatile_ranges),
};
static const struct regmap_range vl53l1x_write_only_ranges[] = {
regmap_reg_range(VL53L1X_REG_SOFT_RESET, VL53L1X_REG_SOFT_RESET),
regmap_reg_range(VL53L1X_REG_SYSTEM__INTERRUPT_CLEAR,
VL53L1X_REG_SYSTEM__MODE_START),
};
static const struct regmap_access_table vl53l1x_readable_table = {
.no_ranges = vl53l1x_write_only_ranges,
.n_no_ranges = ARRAY_SIZE(vl53l1x_write_only_ranges),
};
static const struct regmap_config vl53l1x_regmap_config = {
.reg_bits = 16,
.val_bits = 8,
/* MODEL_ID is 16-bit. +1 covers the second byte at 0x0110 */
.max_register = VL53L1X_REG_IDENTIFICATION__MODEL_ID + 1,
.cache_type = REGCACHE_MAPLE,
.volatile_table = &vl53l1x_volatile_table,
.rd_table = &vl53l1x_readable_table,
};
static int vl53l1x_read_u16(struct vl53l1x_data *data, u16 reg, u16 *val)
{
__be16 buf;
int ret;
ret = regmap_bulk_read(data->regmap, reg, &buf, sizeof(buf));
if (ret)
return ret;
*val = be16_to_cpu(buf);
return 0;
}
static int vl53l1x_write_u16(struct vl53l1x_data *data, u16 reg, u16 val)
{
__be16 buf = cpu_to_be16(val);
return regmap_bulk_write(data->regmap, reg, &buf, sizeof(buf));
}
static int vl53l1x_write_u32(struct vl53l1x_data *data, u16 reg, u32 val)
{
__be32 buf = cpu_to_be32(val);
return regmap_bulk_write(data->regmap, reg, &buf, sizeof(buf));
}
static int vl53l1x_clear_irq(struct vl53l1x_data *data)
{
return regmap_write(data->regmap, VL53L1X_REG_SYSTEM__INTERRUPT_CLEAR, 0x01);
}
static int vl53l1x_start_ranging(struct vl53l1x_data *data)
{
int ret;
ret = vl53l1x_clear_irq(data);
if (ret)
return ret;
return regmap_write(data->regmap, VL53L1X_REG_SYSTEM__MODE_START,
VL53L1X_MODE_START_TIMED);
}
static int vl53l1x_stop_ranging(struct vl53l1x_data *data)
{
return regmap_write(data->regmap, VL53L1X_REG_SYSTEM__MODE_START,
VL53L1X_MODE_START_STOP);
}
/*
* Default configuration blob from ST's VL53L1X Ultra Lite Driver
* (STSW-IMG009).
*/
static const u8 vl53l1x_default_config[] = {
0x00, 0x00, 0x00, 0x01, 0x02, 0x00, 0x02, 0x08, /* reg 0x2d..0x34 */
0x00, 0x08, 0x10, 0x01, 0x01, 0x00, 0x00, 0x00, /* reg 0x35..0x3c */
0x00, 0xFF, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x00, /* reg 0x3d..0x44 */
0x00, 0x20, 0x0B, 0x00, 0x00, 0x02, 0x0A, 0x21, /* reg 0x45..0x4c */
0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0xC8, /* reg 0x4d..0x54 */
0x00, 0x00, 0x38, 0xFF, 0x01, 0x00, 0x08, 0x00, /* reg 0x55..0x5c */
0x00, 0x01, 0xCC, 0x0F, 0x01, 0xF1, 0x0D, 0x01, /* reg 0x5d..0x64 */
0x68, 0x00, 0x80, 0x08, 0xB8, 0x00, 0x00, 0x00, /* reg 0x65..0x6c */
0x00, 0x0F, 0x89, 0x00, 0x00, 0x00, 0x00, 0x00, /* reg 0x6d..0x74 */
0x00, 0x00, 0x01, 0x0F, 0x0D, 0x0E, 0x0E, 0x00, /* reg 0x75..0x7c */
0x00, 0x02, 0xC7, 0xFF, 0x9B, 0x00, 0x00, 0x00, /* reg 0x7d..0x84 */
0x01, 0x00, 0x00, /* reg 0x85..0x87 */
};
static int vl53l1x_chip_init(struct vl53l1x_data *data)
{
struct device *dev = regmap_get_device(data->regmap);
unsigned int val;
u16 model_id;
int ret;
if (!data->xshut_reset) {
ret = regmap_write(data->regmap, VL53L1X_REG_SOFT_RESET, 0x00);
if (ret)
return ret;
fsleep(100); /* conservative reset pulse, no spec */
ret = regmap_write(data->regmap, VL53L1X_REG_SOFT_RESET, 0x01);
if (ret)
return ret;
fsleep(1000); /* conservative boot wait, no spec */
}
ret = regmap_read_poll_timeout(data->regmap,
VL53L1X_REG_FIRMWARE__SYSTEM_STATUS, val,
val & BIT(0),
1 * USEC_PER_MSEC,
100 * USEC_PER_MSEC);
if (ret)
return dev_err_probe(dev, ret, "firmware boot timeout\n");
ret = vl53l1x_read_u16(data, VL53L1X_REG_IDENTIFICATION__MODEL_ID,
&model_id);
if (ret)
return ret;
if (model_id != VL53L1X_MODEL_ID_VAL)
dev_info(dev, "unknown model id: 0x%04x, continuing\n", model_id);
ret = regmap_bulk_write(data->regmap, VL53L1X_REG_DEFAULT_CONFIG,
vl53l1x_default_config,
sizeof(vl53l1x_default_config));
if (ret)
return ret;
ret = regmap_read(data->regmap, VL53L1X_REG_GPIO_HV_MUX__CTRL, &val);
if (ret)
return ret;
data->gpio_polarity = !!(val & VL53L1X_GPIO_HV_MUX_POLARITY);
/* Initial ranging cycle for VHV calibration */
ret = vl53l1x_start_ranging(data);
if (ret)
return ret;
/* 1ms poll, 1s timeout covers max timing budgets (per ST Ultra Lite Driver) */
ret = regmap_read_poll_timeout(data->regmap,
VL53L1X_REG_GPIO__TIO_HV_STATUS, val,
(val & 1) != data->gpio_polarity,
1 * USEC_PER_MSEC,
1000 * USEC_PER_MSEC);
if (ret)
return ret;
ret = vl53l1x_clear_irq(data);
if (ret)
return ret;
ret = vl53l1x_stop_ranging(data);
if (ret)
return ret;
ret = regmap_write(data->regmap,
VL53L1X_REG_VHV_CONFIG__TIMEOUT_MACROP_LOOP_BOUND,
VL53L1X_VHV_LOOP_BOUND_TWO);
if (ret)
return ret;
return regmap_write(data->regmap, VL53L1X_REG_VHV_CONFIG__INIT, 0x00);
}
static const struct reg_sequence vl53l1x_mode_short[] = {
{ VL53L1X_REG_PHASECAL_CONFIG__TIMEOUT_MACROP, 0x14 },
{ VL53L1X_REG_RANGE_CONFIG__VCSEL_PERIOD_A, 0x07 },
{ VL53L1X_REG_RANGE_CONFIG__VCSEL_PERIOD_B, 0x05 },
{ VL53L1X_REG_RANGE_CONFIG__VALID_PHASE_HIGH, 0x38 },
{ VL53L1X_REG_SD_CONFIG__WOI_SD0, 0x07 },
{ VL53L1X_REG_SD_CONFIG__WOI_SD1, 0x05 },
{ VL53L1X_REG_SD_CONFIG__INITIAL_PHASE_SD0, 0x06 },
{ VL53L1X_REG_SD_CONFIG__INITIAL_PHASE_SD1, 0x06 },
};
static const struct reg_sequence vl53l1x_mode_long[] = {
{ VL53L1X_REG_PHASECAL_CONFIG__TIMEOUT_MACROP, 0x0A },
{ VL53L1X_REG_RANGE_CONFIG__VCSEL_PERIOD_A, 0x0F },
{ VL53L1X_REG_RANGE_CONFIG__VCSEL_PERIOD_B, 0x0D },
{ VL53L1X_REG_RANGE_CONFIG__VALID_PHASE_HIGH, 0xB8 },
{ VL53L1X_REG_SD_CONFIG__WOI_SD0, 0x0F },
{ VL53L1X_REG_SD_CONFIG__WOI_SD1, 0x0D },
{ VL53L1X_REG_SD_CONFIG__INITIAL_PHASE_SD0, 0x0E },
{ VL53L1X_REG_SD_CONFIG__INITIAL_PHASE_SD1, 0x0E },
};
static const struct {
const struct reg_sequence *regs;
size_t num_regs;
} vl53l1x_mode_configs[] = {
[VL53L1X_SHORT] = { vl53l1x_mode_short, ARRAY_SIZE(vl53l1x_mode_short) },
[VL53L1X_LONG] = { vl53l1x_mode_long, ARRAY_SIZE(vl53l1x_mode_long) },
};
static int vl53l1x_set_distance_mode(struct vl53l1x_data *data,
enum vl53l1x_distance_mode mode)
{
int ret;
if (mode >= ARRAY_SIZE(vl53l1x_mode_configs))
return -EINVAL;
ret = regmap_multi_reg_write(data->regmap,
vl53l1x_mode_configs[mode].regs,
vl53l1x_mode_configs[mode].num_regs);
if (ret)
return ret;
data->distance_mode = mode;
return 0;
}
/*
* The timing budget controls how long the sensor spends collecting
* a single range measurement. Pre-computed TIMEOUT_MACROP register
* values from ST's VL53L1X Ultra Lite Driver.
*/
static int vl53l1x_set_timing_budget(struct vl53l1x_data *data, u16 budget_ms)
{
u16 timeout_a, timeout_b;
int ret;
switch (data->distance_mode) {
case VL53L1X_SHORT:
switch (budget_ms) {
case 15:
timeout_a = 0x001D;
timeout_b = 0x0027;
break;
case 20:
timeout_a = 0x0051;
timeout_b = 0x006E;
break;
case 33:
timeout_a = 0x00D6;
timeout_b = 0x006E;
break;
case 50:
timeout_a = 0x01AE;
timeout_b = 0x01E8;
break;
case 100:
timeout_a = 0x02E1;
timeout_b = 0x0388;
break;
case 200:
timeout_a = 0x03E1;
timeout_b = 0x0496;
break;
case 500:
timeout_a = 0x0591;
timeout_b = 0x05C1;
break;
default:
return -EINVAL;
}
break;
case VL53L1X_LONG:
switch (budget_ms) {
case 20:
timeout_a = 0x001E;
timeout_b = 0x0022;
break;
case 33:
timeout_a = 0x0060;
timeout_b = 0x006E;
break;
case 50:
timeout_a = 0x00AD;
timeout_b = 0x00C6;
break;
case 100:
timeout_a = 0x01CC;
timeout_b = 0x01EA;
break;
case 200:
timeout_a = 0x02D9;
timeout_b = 0x02F8;
break;
case 500:
timeout_a = 0x048F;
timeout_b = 0x04A4;
break;
default:
return -EINVAL;
}
break;
default:
return -EINVAL;
}
ret = vl53l1x_write_u16(data, VL53L1X_REG_RANGE_CONFIG__TIMEOUT_MACROP_A,
timeout_a);
if (ret)
return ret;
return vl53l1x_write_u16(data, VL53L1X_REG_RANGE_CONFIG__TIMEOUT_MACROP_B,
timeout_b);
}
static int vl53l1x_set_inter_measurement_ms(struct vl53l1x_data *data,
u16 period_ms)
{
u16 osc_calibrate_val;
u16 clock_pll;
u32 inter_meas;
int ret;
ret = vl53l1x_read_u16(data, VL53L1X_REG_RESULT__OSC_CALIBRATE_VAL,
&osc_calibrate_val);
if (ret)
return ret;
clock_pll = osc_calibrate_val & VL53L1X_OSC_CALIBRATE_MASK;
inter_meas = (clock_pll * period_ms * vl53l1x_osc_correction.numerator) /
vl53l1x_osc_correction.denominator;
return vl53l1x_write_u32(data,
VL53L1X_REG_SYSTEM__INTERMEASUREMENT_PERIOD,
inter_meas);
}
static int vl53l1x_read_proximity(struct vl53l1x_data *data, int *val)
{
unsigned int range_status;
u16 distance;
int ret;
if (data->irq) {
reinit_completion(&data->completion);
ret = vl53l1x_clear_irq(data);
if (ret)
return ret;
if (!wait_for_completion_timeout(&data->completion, HZ))
return -ETIMEDOUT;
} else {
unsigned int rdy;
/* 1ms poll, 1s timeout covers max timing budgets (per ST Ultra Lite Driver) */
ret = regmap_read_poll_timeout(data->regmap,
VL53L1X_REG_GPIO__TIO_HV_STATUS, rdy,
(rdy & 1) != data->gpio_polarity,
1 * USEC_PER_MSEC,
1000 * USEC_PER_MSEC);
if (ret)
return ret;
}
ret = regmap_read(data->regmap, VL53L1X_REG_RESULT__RANGE_STATUS,
&range_status);
if (ret)
goto clear_irq;
if (FIELD_GET(VL53L1X_RANGE_STATUS_MASK, range_status) !=
VL53L1X_RANGE_STATUS_VALID) {
ret = -EIO;
goto clear_irq;
}
ret = vl53l1x_read_u16(data,
VL53L1X_REG_RESULT__FINAL_CROSSTALK_CORRECTED_RANGE_MM_SD0,
&distance);
if (ret)
goto clear_irq;
*val = distance;
clear_irq:
vl53l1x_clear_irq(data);
return ret;
}
static const struct iio_chan_spec vl53l1x_channels[] = {
{
.type = IIO_DISTANCE,
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
BIT(IIO_CHAN_INFO_SCALE),
.scan_index = 0,
.scan_type = {
.sign = 'u',
.realbits = 16,
.storagebits = 16,
},
},
IIO_CHAN_SOFT_TIMESTAMP(1),
};
static int vl53l1x_read_raw(struct iio_dev *indio_dev,
const struct iio_chan_spec *chan,
int *val, int *val2, long mask)
{
struct vl53l1x_data *data = iio_priv(indio_dev);
int ret;
if (chan->type != IIO_DISTANCE)
return -EINVAL;
switch (mask) {
case IIO_CHAN_INFO_RAW:
if (!iio_device_claim_direct(indio_dev))
return -EBUSY;
ret = vl53l1x_read_proximity(data, val);
iio_device_release_direct(indio_dev);
if (ret)
return ret;
return IIO_VAL_INT;
case IIO_CHAN_INFO_SCALE:
*val = 0;
*val2 = 1000;
return IIO_VAL_INT_PLUS_MICRO;
default:
return -EINVAL;
}
}
static const struct iio_info vl53l1x_info = {
.read_raw = vl53l1x_read_raw,
.validate_trigger = iio_validate_own_trigger,
};
static irqreturn_t vl53l1x_trigger_handler(int irq, void *priv)
{
struct iio_poll_func *pf = priv;
struct iio_dev *indio_dev = pf->indio_dev;
struct vl53l1x_data *data = iio_priv(indio_dev);
struct {
u16 distance;
aligned_s64 timestamp;
} scan = { };
unsigned int range_status;
int ret;
ret = regmap_read(data->regmap, VL53L1X_REG_RESULT__RANGE_STATUS,
&range_status);
if (ret)
goto notify_and_clear_irq;
if (FIELD_GET(VL53L1X_RANGE_STATUS_MASK, range_status) !=
VL53L1X_RANGE_STATUS_VALID)
goto notify_and_clear_irq;
ret = vl53l1x_read_u16(data,
VL53L1X_REG_RESULT__FINAL_CROSSTALK_CORRECTED_RANGE_MM_SD0,
&scan.distance);
if (ret)
goto notify_and_clear_irq;
iio_push_to_buffers_with_ts(indio_dev, &scan, sizeof(scan),
iio_get_time_ns(indio_dev));
notify_and_clear_irq:
iio_trigger_notify_done(indio_dev->trig);
vl53l1x_clear_irq(data);
return IRQ_HANDLED;
}
static irqreturn_t vl53l1x_irq_handler(int irq, void *priv)
{
struct iio_dev *indio_dev = priv;
struct vl53l1x_data *data = iio_priv(indio_dev);
if (iio_buffer_enabled(indio_dev))
iio_trigger_poll(indio_dev->trig);
else
complete(&data->completion);
return IRQ_HANDLED;
}
static const struct iio_trigger_ops vl53l1x_trigger_ops = {
.validate_device = iio_trigger_validate_own_device,
};
static void vl53l1x_stop_ranging_action(void *priv)
{
vl53l1x_stop_ranging(priv);
}
static int vl53l1x_configure_irq(struct device *dev, int irq,
struct iio_dev *indio_dev)
{
struct vl53l1x_data *data = iio_priv(indio_dev);
int ret;
ret = devm_request_irq(dev, irq, vl53l1x_irq_handler, IRQF_NO_THREAD,
indio_dev->name, indio_dev);
if (ret)
return ret;
ret = regmap_write(data->regmap, VL53L1X_REG_SYSTEM__INTERRUPT_CONFIG_GPIO,
VL53L1X_INT_NEW_SAMPLE_READY);
if (ret)
return dev_err_probe(dev, ret, "failed to configure IRQ\n");
return 0;
}
static int vl53l1x_probe(struct i2c_client *client)
{
struct device *dev = &client->dev;
struct vl53l1x_data *data;
struct iio_dev *indio_dev;
int ret;
indio_dev = devm_iio_device_alloc(dev, sizeof(*data));
if (!indio_dev)
return -ENOMEM;
data = iio_priv(indio_dev);
data->irq = client->irq;
data->regmap = devm_regmap_init_i2c(client, &vl53l1x_regmap_config);
if (IS_ERR(data->regmap))
return dev_err_probe(dev, PTR_ERR(data->regmap),
"regmap initialization failed\n");
ret = devm_regulator_get_enable(dev, "vdd");
if (ret)
return dev_err_probe(dev, ret, "Failed to enable VDD regulator\n");
/*
* XSHUT held low puts the chip in hardware standby. All register
* state is lost on de-assert so this is functionally a reset.
*/
data->xshut_reset = devm_reset_control_get_optional_exclusive_deasserted(dev, NULL);
if (IS_ERR(data->xshut_reset))
return dev_err_probe(dev, PTR_ERR(data->xshut_reset),
"Cannot get reset control\n");
/*
* 1.2 ms max boot duration.
* Datasheet Section 3.6 "Power up and boot sequence".
*/
fsleep(1200);
ret = vl53l1x_chip_init(data);
if (ret)
return ret;
ret = vl53l1x_set_distance_mode(data, VL53L1X_LONG);
if (ret)
return ret;
/* 50 ms timing budget (per ST Ultra Lite Driver) */
ret = vl53l1x_set_timing_budget(data, 50);
if (ret)
return ret;
/* 50 ms inter-measurement period (per ST Ultra Lite Driver) */
ret = vl53l1x_set_inter_measurement_ms(data, 50);
if (ret)
return ret;
/*
* The hardware only supports "autonomous" continuous ranging mode.
* Start ranging here and leave it running for the lifetime of
* the device. Both direct reads and the buffer path rely on this.
*/
ret = vl53l1x_start_ranging(data);
if (ret)
return ret;
ret = devm_add_action_or_reset(dev, vl53l1x_stop_ranging_action, data);
if (ret)
return ret;
indio_dev->name = "vl53l1x";
indio_dev->info = &vl53l1x_info;
indio_dev->channels = vl53l1x_channels;
indio_dev->num_channels = ARRAY_SIZE(vl53l1x_channels);
indio_dev->modes = INDIO_DIRECT_MODE;
if (client->irq) {
struct iio_trigger *trig;
init_completion(&data->completion);
trig = devm_iio_trigger_alloc(dev, "%s-dev%d", indio_dev->name,
iio_device_id(indio_dev));
if (!trig)
return -ENOMEM;
trig->ops = &vl53l1x_trigger_ops;
iio_trigger_set_drvdata(trig, indio_dev);
ret = devm_iio_trigger_register(dev, trig);
if (ret)
return ret;
indio_dev->trig = iio_trigger_get(trig);
ret = vl53l1x_configure_irq(dev, client->irq, indio_dev);
if (ret)
return ret;
ret = devm_iio_triggered_buffer_setup(dev, indio_dev, NULL,
&vl53l1x_trigger_handler,
NULL);
if (ret)
return ret;
}
return devm_iio_device_register(dev, indio_dev);
}
static const struct i2c_device_id vl53l1x_id[] = {
{ .name = "vl53l1x" },
{ }
};
MODULE_DEVICE_TABLE(i2c, vl53l1x_id);
static const struct of_device_id st_vl53l1x_dt_match[] = {
{ .compatible = "st,vl53l1x" },
{ }
};
MODULE_DEVICE_TABLE(of, st_vl53l1x_dt_match);
static struct i2c_driver vl53l1x_driver = {
.driver = {
.name = "vl53l1x-i2c",
.of_match_table = st_vl53l1x_dt_match,
},
.probe = vl53l1x_probe,
.id_table = vl53l1x_id,
};
module_i2c_driver(vl53l1x_driver);
MODULE_AUTHOR("Siratul Islam <email@sirat.me>");
MODULE_DESCRIPTION("ST VL53L1X ToF ranging sensor driver");
MODULE_LICENSE("Dual BSD/GPL");