// 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 * * For details about the device see: * https://www.maxbotix.com/documents/I2CXL-MaxSonar-EZ_Datasheet.pdf */ #include #include #include #include #include #include #include #include #include #include #include /* 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 "); MODULE_DESCRIPTION("Maxbotix I2CXL-MaxSonar i2c ultrasonic ranger driver"); MODULE_LICENSE("GPL");