blob: 961f9f990faffa67fccf54dfa01118ce4339f3b9 [file] [log] [blame]
/*
* pulsedlight-lidar-lite-v2.c - Support for PulsedLight LIDAR sensor
*
* Copyright (C) 2015 Matt Ranostay <mranostay@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* TODO: runtime pm, 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/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_DRV_NAME "lidar"
struct lidar_data {
struct iio_dev *indio_dev;
struct i2c_client *client;
u16 buffer[8]; /* 2 byte distance + 8 byte timestamp */
};
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_read_byte(struct lidar_data *data, int reg)
{
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
*/
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;
}
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 int lidar_read_measurement(struct lidar_data *data, u16 *reg)
{
int ret;
int val;
ret = lidar_read_byte(data, LIDAR_REG_DATA_HBYTE);
if (ret < 0)
return ret;
val = ret << 8;
ret = lidar_read_byte(data, LIDAR_REG_DATA_LBYTE);
if (ret < 0)
return ret;
val |= ret;
*reg = val;
return 0;
}
static int lidar_get_measurement(struct lidar_data *data, u16 *reg)
{
struct i2c_client *client = data->client;
int tries = 10;
int ret;
/* start sample */
ret = lidar_write_control(data, LIDAR_REG_CONTROL_ACQUIRE);
if (ret < 0) {
dev_err(&client->dev, "cannot send start measurement command");
return ret;
}
while (tries--) {
usleep_range(1000, 2000);
ret = lidar_read_byte(data, LIDAR_REG_STATUS);
if (ret < 0)
break;
/* return 0 since laser is likely pointed out of range */
if (ret & LIDAR_REG_STATUS_INVALID) {
*reg = 0;
ret = 0;
break;
}
/* sample ready to read */
if (!(ret & LIDAR_REG_STATUS_READY)) {
ret = lidar_read_measurement(data, reg);
break;
}
ret = -EIO;
}
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;
mutex_lock(&indio_dev->mlock);
if (iio_buffer_enabled(indio_dev) && mask == IIO_CHAN_INFO_RAW) {
ret = -EBUSY;
goto error_busy;
}
switch (mask) {
case IIO_CHAN_INFO_RAW: {
u16 reg;
ret = lidar_get_measurement(data, &reg);
if (!ret) {
*val = reg;
ret = IIO_VAL_INT;
}
break;
}
case IIO_CHAN_INFO_SCALE:
*val = 0;
*val2 = 10000;
ret = IIO_VAL_INT_PLUS_MICRO;
break;
}
error_busy:
mutex_unlock(&indio_dev->mlock);
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;
ret = lidar_get_measurement(data, data->buffer);
if (!ret) {
iio_push_to_buffers_with_timestamp(indio_dev, data->buffer,
iio_get_time_ns());
} else {
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 = {
.driver_module = THIS_MODULE,
.read_raw = lidar_read_raw,
};
static int lidar_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
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;
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;
data = iio_priv(indio_dev);
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;
return 0;
error_unreg_buffer:
iio_triggered_buffer_cleanup(indio_dev);
return ret;
}
static int 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);
return 0;
}
static const struct i2c_device_id lidar_id[] = {
{"lidar-lite-v2", 0},
{ },
};
MODULE_DEVICE_TABLE(i2c, lidar_id);
static const struct of_device_id lidar_dt_ids[] = {
{ .compatible = "pulsedlight,lidar-lite-v2" },
{ }
};
MODULE_DEVICE_TABLE(of, lidar_dt_ids);
static struct i2c_driver lidar_driver = {
.driver = {
.name = LIDAR_DRV_NAME,
.of_match_table = of_match_ptr(lidar_dt_ids),
},
.probe = lidar_probe,
.remove = lidar_remove,
.id_table = lidar_id,
};
module_i2c_driver(lidar_driver);
MODULE_AUTHOR("Matt Ranostay <mranostay@gmail.com>");
MODULE_DESCRIPTION("PulsedLight LIDAR sensor");
MODULE_LICENSE("GPL");