|  | // 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; | 
|  | }; | 
|  |  | 
|  | 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); | 
|  | /* | 
|  | * triggered buffer | 
|  | * 16-bit channel + 48-bit padding + 64-bit timestamp | 
|  | */ | 
|  | s16 buffer[8] = { 0 }; | 
|  |  | 
|  | buffer[0] = mb1232_read_distance(data); | 
|  | if (buffer[0] < 0) | 
|  | goto err; | 
|  |  | 
|  | iio_push_to_buffers_with_timestamp(indio_dev, buffer, 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->dev.parent = dev; | 
|  | 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"); |