Commit ac78c6aa authored by Tomislav Denis's avatar Tomislav Denis Committed by Jonathan Cameron

iio: pressure: Add driver for DLH pressure sensors

All Sensors DLH is series of low voltage digital pressure sensors.
Additionally to pressure value sensors deliver a temperature value.
Sensors can be accessed over I2C and SPI, this driver supports
only I2C access.
Signed-off-by: default avatarTomislav Denis <tomislav.denis@avl.com>
Signed-off-by: default avatarJonathan Cameron <Jonathan.Cameron@huawei.com>
parent 0c040d1d
...@@ -668,6 +668,13 @@ S: Maintained ...@@ -668,6 +668,13 @@ S: Maintained
F: Documentation/i2c/busses/i2c-ali1563.rst F: Documentation/i2c/busses/i2c-ali1563.rst
F: drivers/i2c/busses/i2c-ali1563.c F: drivers/i2c/busses/i2c-ali1563.c
ALL SENSORS DLH SERIES PRESSURE SENSORS DRIVER
M: Tomislav Denis <tomislav.denis@avl.com>
W: http://www.allsensors.com/
S: Maintained
L: linux-iio@vger.kernel.org
F: drivers/iio/pressure/dlhl60d.c
ALLEGRO DVT VIDEO IP CORE DRIVER ALLEGRO DVT VIDEO IP CORE DRIVER
M: Michael Tretter <m.tretter@pengutronix.de> M: Michael Tretter <m.tretter@pengutronix.de>
R: Pengutronix Kernel Team <kernel@pengutronix.de> R: Pengutronix Kernel Team <kernel@pengutronix.de>
......
...@@ -53,6 +53,18 @@ config IIO_CROS_EC_BARO ...@@ -53,6 +53,18 @@ config IIO_CROS_EC_BARO
To compile this driver as a module, choose M here: the module To compile this driver as a module, choose M here: the module
will be called cros_ec_baro. will be called cros_ec_baro.
config DLHL60D
tristate "All Sensors DLHL60D and DLHL60G low voltage digital pressure sensors"
depends on I2C
select IIO_BUFFER
select IIO_TRIGGERED_BUFFER
help
Say yes here to build support for the All Sensors DLH series
pressure sensors driver.
To compile this driver as a module, choose M here: the module
will be called dlhl60d.
config DPS310 config DPS310
tristate "Infineon DPS310 pressure and temperature sensor" tristate "Infineon DPS310 pressure and temperature sensor"
depends on I2C depends on I2C
......
...@@ -9,6 +9,7 @@ obj-$(CONFIG_BMP280) += bmp280.o ...@@ -9,6 +9,7 @@ obj-$(CONFIG_BMP280) += bmp280.o
bmp280-objs := bmp280-core.o bmp280-regmap.o bmp280-objs := bmp280-core.o bmp280-regmap.o
obj-$(CONFIG_BMP280_I2C) += bmp280-i2c.o obj-$(CONFIG_BMP280_I2C) += bmp280-i2c.o
obj-$(CONFIG_BMP280_SPI) += bmp280-spi.o obj-$(CONFIG_BMP280_SPI) += bmp280-spi.o
obj-$(CONFIG_DLHL60D) += dlhl60d.o
obj-$(CONFIG_DPS310) += dps310.o obj-$(CONFIG_DPS310) += dps310.o
obj-$(CONFIG_IIO_CROS_EC_BARO) += cros_ec_baro.o obj-$(CONFIG_IIO_CROS_EC_BARO) += cros_ec_baro.o
obj-$(CONFIG_HID_SENSOR_PRESS) += hid-sensor-press.o obj-$(CONFIG_HID_SENSOR_PRESS) += hid-sensor-press.o
......
// SPDX-License-Identifier: GPL-2.0
/*
* All Sensors DLH series low voltage digital pressure sensors
*
* Copyright (c) 2019 AVL DiTEST GmbH
* Tomislav Denis <tomislav.denis@avl.com>
*
* Datasheet: http://www.allsensors.com/cad/DS-0355_Rev_B.PDF
*/
#include <linux/module.h>
#include <linux/delay.h>
#include <linux/i2c.h>
#include <linux/iio/iio.h>
#include <linux/iio/buffer.h>
#include <linux/iio/trigger_consumer.h>
#include <linux/iio/triggered_buffer.h>
#include <asm/unaligned.h>
/* Commands */
#define DLH_START_SINGLE 0xAA
/* Status bits */
#define DLH_STATUS_OK 0x40
/* DLH data format */
#define DLH_NUM_READ_BYTES 7
#define DLH_NUM_DATA_BYTES 3
#define DLH_NUM_PR_BITS 24
#define DLH_NUM_TEMP_BITS 24
/* DLH timings */
#define DLH_SINGLE_DUT_MS 5
enum dhl_ids {
dlhl60d,
dlhl60g,
};
struct dlh_info {
u8 osdig; /* digital offset factor */
unsigned int fss; /* full scale span (inch H2O) */
};
struct dlh_state {
struct i2c_client *client;
struct dlh_info info;
bool use_interrupt;
struct completion completion;
u8 rx_buf[DLH_NUM_READ_BYTES] ____cacheline_aligned;
};
static struct dlh_info dlh_info_tbl[] = {
[dlhl60d] = {
.osdig = 2,
.fss = 120,
},
[dlhl60g] = {
.osdig = 10,
.fss = 60,
},
};
static int dlh_cmd_start_single(struct dlh_state *st)
{
int ret;
ret = i2c_smbus_write_byte(st->client, DLH_START_SINGLE);
if (ret)
dev_err(&st->client->dev,
"%s: I2C write byte failed\n", __func__);
return ret;
}
static int dlh_cmd_read_data(struct dlh_state *st)
{
int ret;
ret = i2c_master_recv(st->client, st->rx_buf, DLH_NUM_READ_BYTES);
if (ret < 0) {
dev_err(&st->client->dev,
"%s: I2C read block failed\n", __func__);
return ret;
}
if (st->rx_buf[0] != DLH_STATUS_OK) {
dev_err(&st->client->dev,
"%s: invalid status 0x%02x\n", __func__, st->rx_buf[0]);
return -EBUSY;
}
return 0;
}
static int dlh_start_capture_and_read(struct dlh_state *st)
{
int ret;
if (st->use_interrupt)
reinit_completion(&st->completion);
ret = dlh_cmd_start_single(st);
if (ret)
return ret;
if (st->use_interrupt) {
ret = wait_for_completion_timeout(&st->completion,
msecs_to_jiffies(DLH_SINGLE_DUT_MS));
if (!ret) {
dev_err(&st->client->dev,
"%s: conversion timed out\n", __func__);
return -ETIMEDOUT;
}
} else {
mdelay(DLH_SINGLE_DUT_MS);
}
return dlh_cmd_read_data(st);
}
static int dlh_read_direct(struct dlh_state *st,
unsigned int *pressure, unsigned int *temperature)
{
int ret;
ret = dlh_start_capture_and_read(st);
if (ret)
return ret;
*pressure = get_unaligned_be32(&st->rx_buf[1]) >> 8;
*temperature = get_unaligned_be32(&st->rx_buf[3]) &
GENMASK(DLH_NUM_TEMP_BITS - 1, 0);
return 0;
}
static int dlh_read_raw(struct iio_dev *indio_dev,
struct iio_chan_spec const *channel, int *value,
int *value2, long mask)
{
struct dlh_state *st = iio_priv(indio_dev);
unsigned int pressure, temperature;
int ret;
s64 tmp;
s32 rem;
switch (mask) {
case IIO_CHAN_INFO_RAW:
ret = iio_device_claim_direct_mode(indio_dev);
if (ret)
return ret;
ret = dlh_read_direct(st, &pressure, &temperature);
iio_device_release_direct_mode(indio_dev);
if (ret)
return ret;
switch (channel->type) {
case IIO_PRESSURE:
*value = pressure;
return IIO_VAL_INT;
case IIO_TEMP:
*value = temperature;
return IIO_VAL_INT;
default:
return -EINVAL;
}
case IIO_CHAN_INFO_SCALE:
switch (channel->type) {
case IIO_PRESSURE:
tmp = div_s64(125LL * st->info.fss * 24909 * 100,
1 << DLH_NUM_PR_BITS);
tmp = div_s64_rem(tmp, 1000000000LL, &rem);
*value = tmp;
*value2 = rem;
return IIO_VAL_INT_PLUS_NANO;
case IIO_TEMP:
*value = 125 * 1000;
*value2 = DLH_NUM_TEMP_BITS;
return IIO_VAL_FRACTIONAL_LOG2;
default:
return -EINVAL;
}
case IIO_CHAN_INFO_OFFSET:
switch (channel->type) {
case IIO_PRESSURE:
*value = -125 * st->info.fss * 24909;
*value2 = 100 * st->info.osdig * 100000;
return IIO_VAL_FRACTIONAL;
case IIO_TEMP:
*value = -40 * 1000;
return IIO_VAL_INT;
default:
return -EINVAL;
}
}
return -EINVAL;
}
static const struct iio_info dlh_info = {
.read_raw = dlh_read_raw,
};
static const struct iio_chan_spec dlh_channels[] = {
{
.type = IIO_PRESSURE,
.indexed = 1,
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
.info_mask_shared_by_type =
BIT(IIO_CHAN_INFO_SCALE) |
BIT(IIO_CHAN_INFO_OFFSET),
.scan_index = 0,
.scan_type = {
.sign = 'u',
.realbits = DLH_NUM_PR_BITS,
.storagebits = 32,
.shift = 8,
.endianness = IIO_BE,
},
}, {
.type = IIO_TEMP,
.indexed = 1,
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
.info_mask_shared_by_type =
BIT(IIO_CHAN_INFO_SCALE) |
BIT(IIO_CHAN_INFO_OFFSET),
.scan_index = 1,
.scan_type = {
.sign = 'u',
.realbits = DLH_NUM_TEMP_BITS,
.storagebits = 32,
.shift = 8,
.endianness = IIO_BE,
},
}
};
static irqreturn_t dlh_trigger_handler(int irq, void *private)
{
struct iio_poll_func *pf = private;
struct iio_dev *indio_dev = pf->indio_dev;
struct dlh_state *st = iio_priv(indio_dev);
int ret;
unsigned int chn, i = 0;
__be32 tmp_buf[2];
ret = dlh_start_capture_and_read(st);
if (ret)
goto out;
for_each_set_bit(chn, indio_dev->active_scan_mask,
indio_dev->masklength) {
memcpy(tmp_buf + i,
&st->rx_buf[1] + chn * DLH_NUM_DATA_BYTES,
DLH_NUM_DATA_BYTES);
i++;
}
iio_push_to_buffers(indio_dev, tmp_buf);
out:
iio_trigger_notify_done(indio_dev->trig);
return IRQ_HANDLED;
}
static irqreturn_t dlh_interrupt(int irq, void *private)
{
struct iio_dev *indio_dev = private;
struct dlh_state *st = iio_priv(indio_dev);
complete(&st->completion);
return IRQ_HANDLED;
};
static int dlh_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct dlh_state *st;
struct iio_dev *indio_dev;
int ret;
if (!i2c_check_functionality(client->adapter,
I2C_FUNC_I2C | I2C_FUNC_SMBUS_WRITE_BYTE)) {
dev_err(&client->dev,
"adapter doesn't support required i2c functionality\n");
return -EOPNOTSUPP;
}
indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*st));
if (!indio_dev) {
dev_err(&client->dev, "failed to allocate iio device\n");
return -ENOMEM;
}
i2c_set_clientdata(client, indio_dev);
st = iio_priv(indio_dev);
st->info = dlh_info_tbl[id->driver_data];
st->client = client;
st->use_interrupt = false;
indio_dev->name = id->name;
indio_dev->dev.parent = &client->dev;
indio_dev->dev.of_node = client->dev.of_node;
indio_dev->info = &dlh_info;
indio_dev->modes = INDIO_DIRECT_MODE;
indio_dev->channels = dlh_channels;
indio_dev->num_channels = ARRAY_SIZE(dlh_channels);
if (client->irq > 0) {
ret = devm_request_threaded_irq(&client->dev, client->irq,
dlh_interrupt, NULL,
IRQF_TRIGGER_RISING | IRQF_ONESHOT,
id->name, indio_dev);
if (ret) {
dev_err(&client->dev, "failed to allocate threaded irq");
return ret;
}
st->use_interrupt = true;
init_completion(&st->completion);
}
ret = devm_iio_triggered_buffer_setup(&client->dev, indio_dev,
NULL, &dlh_trigger_handler, NULL);
if (ret) {
dev_err(&client->dev, "failed to setup iio buffer\n");
return ret;
}
ret = devm_iio_device_register(&client->dev, indio_dev);
if (ret)
dev_err(&client->dev, "failed to register iio device\n");
return ret;
}
static const struct of_device_id dlh_of_match[] = {
{ .compatible = "asc,dlhl60d" },
{ .compatible = "asc,dlhl60g" },
{}
};
MODULE_DEVICE_TABLE(of, dlh_of_match);
static const struct i2c_device_id dlh_id[] = {
{ "dlhl60d", dlhl60d },
{ "dlhl60g", dlhl60g },
{}
};
MODULE_DEVICE_TABLE(i2c, dlh_id);
static struct i2c_driver dlh_driver = {
.driver = {
.name = "dlhl60d",
.of_match_table = dlh_of_match,
},
.probe = dlh_probe,
.id_table = dlh_id,
};
module_i2c_driver(dlh_driver);
MODULE_AUTHOR("Tomislav Denis <tomislav.denis@avl.com>");
MODULE_DESCRIPTION("Driver for All Sensors DLH series pressure sensors");
MODULE_LICENSE("GPL v2");
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment