273 lines
		
	
	
		
			6.4 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			273 lines
		
	
	
		
			6.4 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| // 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/err.h>
 | |
| #include <linux/i2c.h>
 | |
| #include <linux/of_irq.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 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;
 | |
| 	/* Ensure correct alignment of data to push to IIO buffer */
 | |
| 	struct {
 | |
| 		s16 distance;
 | |
| 		s64 ts __aligned(8);
 | |
| 	} scan;
 | |
| };
 | |
| 
 | |
| 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);
 | |
| 
 | |
| 	data->scan.distance = mb1232_read_distance(data);
 | |
| 	if (data->scan.distance < 0)
 | |
| 		goto err;
 | |
| 
 | |
| 	iio_push_to_buffers_with_timestamp(indio_dev, &data->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)
 | |
| {
 | |
| 	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 = irq_of_parse_and_map(dev->of_node, 0);
 | |
| 	if (data->irqnr <= 0) {
 | |
| 		/* usage of interrupt is optional */
 | |
| 		data->irqnr = -1;
 | |
| 	} else {
 | |
| 		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[] = {
 | |
| 	{ "maxbotix-mb1202", },
 | |
| 	{ "maxbotix-mb1212", },
 | |
| 	{ "maxbotix-mb1222", },
 | |
| 	{ "maxbotix-mb1232", },
 | |
| 	{ "maxbotix-mb1242", },
 | |
| 	{ "maxbotix-mb7040", },
 | |
| 	{ "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");
 |