brintos

brintos / linux-shallow public Read only

0
0
Text · 8.4 KiB · 5c95973 Raw
377 lines · c
1// SPDX-License-Identifier: GPL-2.0+2/*3 * pulsedlight-lidar-lite-v2.c - Support for PulsedLight LIDAR sensor4 *5 * Copyright (C) 2015, 2017-20186 * Author: Matt Ranostay <matt.ranostay@konsulko.com>7 *8 * TODO: interrupt mode, and signal strength reporting9 */10 11#include <linux/err.h>12#include <linux/init.h>13#include <linux/i2c.h>14#include <linux/delay.h>15#include <linux/module.h>16#include <linux/mod_devicetable.h>17#include <linux/pm_runtime.h>18#include <linux/iio/iio.h>19#include <linux/iio/sysfs.h>20#include <linux/iio/buffer.h>21#include <linux/iio/trigger.h>22#include <linux/iio/triggered_buffer.h>23#include <linux/iio/trigger_consumer.h>24 25#define LIDAR_REG_CONTROL		0x0026#define LIDAR_REG_CONTROL_ACQUIRE	BIT(2)27 28#define LIDAR_REG_STATUS		0x0129#define LIDAR_REG_STATUS_INVALID	BIT(3)30#define LIDAR_REG_STATUS_READY		BIT(0)31 32#define LIDAR_REG_DATA_HBYTE		0x0f33#define LIDAR_REG_DATA_LBYTE		0x1034#define LIDAR_REG_DATA_WORD_READ	BIT(7)35 36#define LIDAR_REG_PWR_CONTROL	0x6537 38#define LIDAR_DRV_NAME "lidar"39 40struct lidar_data {41	struct iio_dev *indio_dev;42	struct i2c_client *client;43 44	int (*xfer)(struct lidar_data *data, u8 reg, u8 *val, int len);45	int i2c_enabled;46 47	/* Ensure timestamp is naturally aligned */48	struct {49		u16 chan;50		s64 timestamp __aligned(8);51	} scan;52};53 54static const struct iio_chan_spec lidar_channels[] = {55	{56		.type = IIO_DISTANCE,57		.info_mask_separate =58			BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE),59		.scan_index = 0,60		.scan_type = {61			.sign = 'u',62			.realbits = 16,63			.storagebits = 16,64		},65	},66	IIO_CHAN_SOFT_TIMESTAMP(1),67};68 69static int lidar_i2c_xfer(struct lidar_data *data, u8 reg, u8 *val, int len)70{71	struct i2c_client *client = data->client;72	struct i2c_msg msg[2];73	int ret;74 75	msg[0].addr = client->addr;76	msg[0].flags = client->flags | I2C_M_STOP;77	msg[0].len = 1;78	msg[0].buf  = (char *) &reg;79 80	msg[1].addr = client->addr;81	msg[1].flags = client->flags | I2C_M_RD;82	msg[1].len = len;83	msg[1].buf = (char *) val;84 85	ret = i2c_transfer(client->adapter, msg, 2);86 87	return (ret == 2) ? 0 : -EIO;88}89 90static int lidar_smbus_xfer(struct lidar_data *data, u8 reg, u8 *val, int len)91{92	struct i2c_client *client = data->client;93	int ret;94 95	/*96	 * Device needs a STOP condition between address write, and data read97	 * so in turn i2c_smbus_read_byte_data cannot be used98	 */99 100	while (len--) {101		ret = i2c_smbus_write_byte(client, reg++);102		if (ret < 0) {103			dev_err(&client->dev, "cannot write addr value");104			return ret;105		}106 107		ret = i2c_smbus_read_byte(client);108		if (ret < 0) {109			dev_err(&client->dev, "cannot read data value");110			return ret;111		}112 113		*(val++) = ret;114	}115 116	return 0;117}118 119static int lidar_read_byte(struct lidar_data *data, u8 reg)120{121	int ret;122	u8 val;123 124	ret = data->xfer(data, reg, &val, 1);125	if (ret < 0)126		return ret;127 128	return val;129}130 131static inline int lidar_write_control(struct lidar_data *data, int val)132{133	return i2c_smbus_write_byte_data(data->client, LIDAR_REG_CONTROL, val);134}135 136static inline int lidar_write_power(struct lidar_data *data, int val)137{138	return i2c_smbus_write_byte_data(data->client,139					 LIDAR_REG_PWR_CONTROL, val);140}141 142static int lidar_read_measurement(struct lidar_data *data, u16 *reg)143{144	__be16 value;145	int ret = data->xfer(data, LIDAR_REG_DATA_HBYTE |146			(data->i2c_enabled ? LIDAR_REG_DATA_WORD_READ : 0),147			(u8 *) &value, 2);148 149	if (!ret)150		*reg = be16_to_cpu(value);151 152	return ret;153}154 155static int lidar_get_measurement(struct lidar_data *data, u16 *reg)156{157	struct i2c_client *client = data->client;158	int tries = 10;159	int ret;160 161	ret = pm_runtime_resume_and_get(&client->dev);162	if (ret < 0)163		return ret;164 165	/* start sample */166	ret = lidar_write_control(data, LIDAR_REG_CONTROL_ACQUIRE);167	if (ret < 0) {168		dev_err(&client->dev, "cannot send start measurement command");169		pm_runtime_put_noidle(&client->dev);170		return ret;171	}172 173	while (tries--) {174		usleep_range(1000, 2000);175 176		ret = lidar_read_byte(data, LIDAR_REG_STATUS);177		if (ret < 0)178			break;179 180		/* return -EINVAL since laser is likely pointed out of range */181		if (ret & LIDAR_REG_STATUS_INVALID) {182			*reg = 0;183			ret = -EINVAL;184			break;185		}186 187		/* sample ready to read */188		if (!(ret & LIDAR_REG_STATUS_READY)) {189			ret = lidar_read_measurement(data, reg);190			break;191		}192		ret = -EIO;193	}194	pm_runtime_mark_last_busy(&client->dev);195	pm_runtime_put_autosuspend(&client->dev);196 197	return ret;198}199 200static int lidar_read_raw(struct iio_dev *indio_dev,201			  struct iio_chan_spec const *chan,202			  int *val, int *val2, long mask)203{204	struct lidar_data *data = iio_priv(indio_dev);205	int ret = -EINVAL;206 207	switch (mask) {208	case IIO_CHAN_INFO_RAW: {209		u16 reg;210 211		if (iio_device_claim_direct_mode(indio_dev))212			return -EBUSY;213 214		ret = lidar_get_measurement(data, &reg);215		if (!ret) {216			*val = reg;217			ret = IIO_VAL_INT;218		}219		iio_device_release_direct_mode(indio_dev);220		break;221	}222	case IIO_CHAN_INFO_SCALE:223		*val = 0;224		*val2 = 10000;225		ret = IIO_VAL_INT_PLUS_MICRO;226		break;227	}228 229	return ret;230}231 232static irqreturn_t lidar_trigger_handler(int irq, void *private)233{234	struct iio_poll_func *pf = private;235	struct iio_dev *indio_dev = pf->indio_dev;236	struct lidar_data *data = iio_priv(indio_dev);237	int ret;238 239	ret = lidar_get_measurement(data, &data->scan.chan);240	if (!ret) {241		iio_push_to_buffers_with_timestamp(indio_dev, &data->scan,242						   iio_get_time_ns(indio_dev));243	} else if (ret != -EINVAL) {244		dev_err(&data->client->dev, "cannot read LIDAR measurement");245	}246 247	iio_trigger_notify_done(indio_dev->trig);248 249	return IRQ_HANDLED;250}251 252static const struct iio_info lidar_info = {253	.read_raw = lidar_read_raw,254};255 256static int lidar_probe(struct i2c_client *client)257{258	struct lidar_data *data;259	struct iio_dev *indio_dev;260	int ret;261 262	indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));263	if (!indio_dev)264		return -ENOMEM;265	data = iio_priv(indio_dev);266 267	if (i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {268		data->xfer = lidar_i2c_xfer;269		data->i2c_enabled = 1;270	} else if (i2c_check_functionality(client->adapter,271				I2C_FUNC_SMBUS_WORD_DATA | I2C_FUNC_SMBUS_BYTE))272		data->xfer = lidar_smbus_xfer;273	else274		return -EOPNOTSUPP;275 276	indio_dev->info = &lidar_info;277	indio_dev->name = LIDAR_DRV_NAME;278	indio_dev->channels = lidar_channels;279	indio_dev->num_channels = ARRAY_SIZE(lidar_channels);280	indio_dev->modes = INDIO_DIRECT_MODE;281 282	i2c_set_clientdata(client, indio_dev);283 284	data->client = client;285	data->indio_dev = indio_dev;286 287	ret = iio_triggered_buffer_setup(indio_dev, NULL,288					 lidar_trigger_handler, NULL);289	if (ret)290		return ret;291 292	ret = iio_device_register(indio_dev);293	if (ret)294		goto error_unreg_buffer;295 296	pm_runtime_set_autosuspend_delay(&client->dev, 1000);297	pm_runtime_use_autosuspend(&client->dev);298 299	ret = pm_runtime_set_active(&client->dev);300	if (ret)301		goto error_unreg_buffer;302	pm_runtime_enable(&client->dev);303	pm_runtime_idle(&client->dev);304 305	return 0;306 307error_unreg_buffer:308	iio_triggered_buffer_cleanup(indio_dev);309 310	return ret;311}312 313static void lidar_remove(struct i2c_client *client)314{315	struct iio_dev *indio_dev = i2c_get_clientdata(client);316 317	iio_device_unregister(indio_dev);318	iio_triggered_buffer_cleanup(indio_dev);319 320	pm_runtime_disable(&client->dev);321	pm_runtime_set_suspended(&client->dev);322}323 324static const struct i2c_device_id lidar_id[] = {325	{ "lidar-lite-v2" },326	{ "lidar-lite-v3" },327	{ }328};329MODULE_DEVICE_TABLE(i2c, lidar_id);330 331static const struct of_device_id lidar_dt_ids[] = {332	{ .compatible = "pulsedlight,lidar-lite-v2" },333	{ .compatible = "grmn,lidar-lite-v3" },334	{ }335};336MODULE_DEVICE_TABLE(of, lidar_dt_ids);337 338static int lidar_pm_runtime_suspend(struct device *dev)339{340	struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev));341	struct lidar_data *data = iio_priv(indio_dev);342 343	return lidar_write_power(data, 0x0f);344}345 346static int lidar_pm_runtime_resume(struct device *dev)347{348	struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev));349	struct lidar_data *data = iio_priv(indio_dev);350	int ret = lidar_write_power(data, 0);351 352	/* regulator and FPGA needs settling time */353	usleep_range(15000, 20000);354 355	return ret;356}357 358static const struct dev_pm_ops lidar_pm_ops = {359	RUNTIME_PM_OPS(lidar_pm_runtime_suspend, lidar_pm_runtime_resume, NULL)360};361 362static struct i2c_driver lidar_driver = {363	.driver = {364		.name	= LIDAR_DRV_NAME,365		.of_match_table	= lidar_dt_ids,366		.pm	= pm_ptr(&lidar_pm_ops),367	},368	.probe		= lidar_probe,369	.remove		= lidar_remove,370	.id_table	= lidar_id,371};372module_i2c_driver(lidar_driver);373 374MODULE_AUTHOR("Matt Ranostay <matt.ranostay@konsulko.com>");375MODULE_DESCRIPTION("PulsedLight LIDAR sensor");376MODULE_LICENSE("GPL");377