iio: proximity: Add rfd77402 driver
authorPeter Meerwald-Stadler <pmeerw@pmeerw.net>
Tue, 10 Oct 2017 13:48:47 +0000 (15:48 +0200)
committerJonathan Cameron <Jonathan.Cameron@huawei.com>
Sat, 14 Oct 2017 17:28:51 +0000 (18:28 +0100)
Driver for RF Digital RFD77402 VCSEL (vertical-cavity surface-emitting
laser) Time-of-Flight (ToF) sensor to measure distance up to 2 m with
millimeter precision

Signed-off-by: Peter Meerwald-Stadler <pmeerw@pmeerw.net>
Signed-off-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>
drivers/iio/proximity/Kconfig
drivers/iio/proximity/Makefile
drivers/iio/proximity/rfd77402.c [new file with mode: 0644]

index ae070950f920e1c902322cb7b71d007bdb4d69c0..fcb1c4ba5e4143d2c8457b5a436355ca2158899a 100644 (file)
@@ -32,6 +32,16 @@ config LIDAR_LITE_V2
          To compile this driver as a module, choose M here: the
          module will be called pulsedlight-lite-v2
 
+config RFD77402
+       tristate "RFD77402 ToF sensor"
+       depends on I2C
+       help
+         Say Y to build a driver for the RFD77420 Time-of-Flight (distance)
+         sensor module with I2C interface.
+
+         To compile this driver as a module, choose M here: the
+         module will be called rfd77402.
+
 config SRF04
        tristate "Devantech SRF04 ultrasonic ranger sensor"
        depends on GPIOLIB
index ed1b6f4cc209d914c4c3aef16349b8c4647ab610..1b195d84c611c017855a7351f2d512e116eda0c3 100644 (file)
@@ -5,6 +5,7 @@
 # When adding new entries keep the list in alphabetical order
 obj-$(CONFIG_AS3935)           += as3935.o
 obj-$(CONFIG_LIDAR_LITE_V2)    += pulsedlight-lidar-lite-v2.o
+obj-$(CONFIG_RFD77402)         += rfd77402.o
 obj-$(CONFIG_SRF04)            += srf04.o
 obj-$(CONFIG_SRF08)            += srf08.o
 obj-$(CONFIG_SX9500)           += sx9500.o
diff --git a/drivers/iio/proximity/rfd77402.c b/drivers/iio/proximity/rfd77402.c
new file mode 100644 (file)
index 0000000..fe29fb1
--- /dev/null
@@ -0,0 +1,352 @@
+/*
+ * rfd77402.c - Support for RF Digital RFD77402 Time-of-Flight (distance) sensor
+ *
+ * Copyright 2017 Peter Meerwald-Stadler <pmeerw@pmeerw.net>
+ *
+ * This file is subject to the terms and conditions of version 2 of
+ * the GNU General Public License.  See the file COPYING in the main
+ * directory of this archive for more details.
+ *
+ * 7-bit I2C slave address 0x4c
+ *
+ * TODO: interrupt
+ * https://media.digikey.com/pdf/Data%20Sheets/RF%20Digital%20PDFs/RFD77402.pdf
+ */
+
+#include <linux/module.h>
+#include <linux/i2c.h>
+#include <linux/delay.h>
+
+#include <linux/iio/iio.h>
+
+#define RFD77402_DRV_NAME "rfd77402"
+
+#define RFD77402_ICSR          0x00 /* Interrupt Control Status Register */
+#define RFD77402_ICSR_INT_MODE BIT(2)
+#define RFD77402_ICSR_INT_POL  BIT(3)
+#define RFD77402_ICSR_RESULT   BIT(4)
+#define RFD77402_ICSR_M2H_MSG  BIT(5)
+#define RFD77402_ICSR_H2M_MSG  BIT(6)
+#define RFD77402_ICSR_RESET    BIT(7)
+
+#define RFD77402_CMD_R         0x04
+#define RFD77402_CMD_SINGLE    0x01
+#define RFD77402_CMD_STANDBY   0x10
+#define RFD77402_CMD_MCPU_OFF  0x11
+#define RFD77402_CMD_MCPU_ON   0x12
+#define RFD77402_CMD_RESET     BIT(6)
+#define RFD77402_CMD_VALID     BIT(7)
+
+#define RFD77402_STATUS_R      0x06
+#define RFD77402_STATUS_PM_MASK        GENMASK(4, 0)
+#define RFD77402_STATUS_STANDBY        0x00
+#define RFD77402_STATUS_MCPU_OFF       0x10
+#define RFD77402_STATUS_MCPU_ON        0x18
+
+#define RFD77402_RESULT_R      0x08
+#define RFD77402_RESULT_DIST_MASK      GENMASK(12, 2)
+#define RFD77402_RESULT_ERR_MASK       GENMASK(14, 13)
+#define RFD77402_RESULT_VALID  BIT(15)
+
+#define RFD77402_PMU_CFG       0x14
+#define RFD77402_PMU_MCPU_INIT BIT(9)
+
+#define RFD77402_I2C_INIT_CFG  0x1c
+#define RFD77402_I2C_ADDR_INCR BIT(0)
+#define RFD77402_I2C_DATA_INCR BIT(2)
+#define RFD77402_I2C_HOST_DEBUG        BIT(5)
+#define RFD77402_I2C_MCPU_DEBUG        BIT(6)
+
+#define RFD77402_CMD_CFGR_A    0x0c
+#define RFD77402_CMD_CFGR_B    0x0e
+#define RFD77402_HFCFG_0       0x20
+#define RFD77402_HFCFG_1       0x22
+#define RFD77402_HFCFG_2       0x24
+#define RFD77402_HFCFG_3       0x26
+
+#define RFD77402_MOD_CHIP_ID   0x28
+
+/* magic configuration values from datasheet */
+static const struct {
+       u8 reg;
+       u16 val;
+} rf77402_tof_config[] = {
+       {RFD77402_CMD_CFGR_A,   0xe100},
+       {RFD77402_CMD_CFGR_B,   0x10ff},
+       {RFD77402_HFCFG_0,      0x07d0},
+       {RFD77402_HFCFG_1,      0x5008},
+       {RFD77402_HFCFG_2,      0xa041},
+       {RFD77402_HFCFG_3,      0x45d4},
+};
+
+struct rfd77402_data {
+       struct i2c_client *client;
+       /* Serialize reads from the sensor */
+       struct mutex lock;
+};
+
+static const struct iio_chan_spec rfd77402_channels[] = {
+       {
+               .type = IIO_DISTANCE,
+               .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+                                     BIT(IIO_CHAN_INFO_SCALE),
+       },
+};
+
+static int rfd77402_set_state(struct rfd77402_data *data, u8 state, u16 check)
+{
+       int ret;
+
+       ret = i2c_smbus_write_byte_data(data->client, RFD77402_CMD_R,
+                                       state | RFD77402_CMD_VALID);
+       if (ret < 0)
+               return ret;
+
+       usleep_range(10000, 20000);
+
+       ret = i2c_smbus_read_word_data(data->client, RFD77402_STATUS_R);
+       if (ret < 0)
+               return ret;
+       if ((ret & RFD77402_STATUS_PM_MASK) != check)
+               return -ENODEV;
+
+       return 0;
+}
+
+static int rfd77402_measure(struct rfd77402_data *data)
+{
+       int ret;
+       int tries = 10;
+
+       ret = rfd77402_set_state(data, RFD77402_CMD_MCPU_ON,
+                                RFD77402_STATUS_MCPU_ON);
+       if (ret < 0)
+               return ret;
+
+       ret = i2c_smbus_write_byte_data(data->client, RFD77402_CMD_R,
+                                       RFD77402_CMD_SINGLE |
+                                       RFD77402_CMD_VALID);
+       if (ret < 0)
+               goto err;
+
+       while (tries-- > 0) {
+               ret = i2c_smbus_read_byte_data(data->client, RFD77402_ICSR);
+               if (ret < 0)
+                       goto err;
+               if (ret & RFD77402_ICSR_RESULT)
+                       break;
+               msleep(20);
+       }
+
+       if (tries < 0) {
+               ret = -ETIMEDOUT;
+               goto err;
+       }
+
+       ret = i2c_smbus_read_word_data(data->client, RFD77402_RESULT_R);
+       if (ret < 0)
+               goto err;
+
+       if ((ret & RFD77402_RESULT_ERR_MASK) ||
+           !(ret & RFD77402_RESULT_VALID)) {
+               ret = -EIO;
+               goto err;
+       }
+
+       return (ret & RFD77402_RESULT_DIST_MASK) >> 2;
+
+err:
+       rfd77402_set_state(data, RFD77402_CMD_MCPU_OFF,
+                          RFD77402_STATUS_MCPU_OFF);
+       return ret;
+}
+
+static int rfd77402_read_raw(struct iio_dev *indio_dev,
+                            struct iio_chan_spec const *chan,
+                            int *val, int *val2, long mask)
+{
+       struct rfd77402_data *data = iio_priv(indio_dev);
+       int ret;
+
+       switch (mask) {
+       case IIO_CHAN_INFO_RAW:
+               mutex_lock(&data->lock);
+               ret = rfd77402_measure(data);
+               mutex_unlock(&data->lock);
+               if (ret < 0)
+                       return ret;
+               *val = ret;
+               return IIO_VAL_INT;
+       case IIO_CHAN_INFO_SCALE:
+               /* 1 LSB is 1 mm */
+               *val = 0;
+               *val2 = 1000;
+               return IIO_VAL_INT_PLUS_MICRO;
+       default:
+               return -EINVAL;
+       }
+}
+
+static const struct iio_info rfd77402_info = {
+       .read_raw = rfd77402_read_raw,
+};
+
+static int rfd77402_init(struct rfd77402_data *data)
+{
+       int ret, i;
+
+       ret = rfd77402_set_state(data, RFD77402_CMD_STANDBY,
+                                RFD77402_STATUS_STANDBY);
+       if (ret < 0)
+               return ret;
+
+       /* configure INT pad as push-pull, active low */
+       ret = i2c_smbus_write_byte_data(data->client, RFD77402_ICSR,
+                                       RFD77402_ICSR_INT_MODE);
+       if (ret < 0)
+               return ret;
+
+       /* I2C configuration */
+       ret = i2c_smbus_write_word_data(data->client, RFD77402_I2C_INIT_CFG,
+                                       RFD77402_I2C_ADDR_INCR |
+                                       RFD77402_I2C_DATA_INCR |
+                                       RFD77402_I2C_HOST_DEBUG |
+                                       RFD77402_I2C_MCPU_DEBUG);
+       if (ret < 0)
+               return ret;
+
+       /* set initialization */
+       ret = i2c_smbus_write_word_data(data->client, RFD77402_PMU_CFG, 0x0500);
+       if (ret < 0)
+               return ret;
+
+       ret = rfd77402_set_state(data, RFD77402_CMD_MCPU_OFF,
+                                RFD77402_STATUS_MCPU_OFF);
+       if (ret < 0)
+               return ret;
+
+       /* set initialization */
+       ret = i2c_smbus_write_word_data(data->client, RFD77402_PMU_CFG, 0x0600);
+       if (ret < 0)
+               return ret;
+
+       ret = rfd77402_set_state(data, RFD77402_CMD_MCPU_ON,
+                                RFD77402_STATUS_MCPU_ON);
+       if (ret < 0)
+               return ret;
+
+       for (i = 0; i < ARRAY_SIZE(rf77402_tof_config); i++) {
+               ret = i2c_smbus_write_word_data(data->client,
+                                               rf77402_tof_config[i].reg,
+                                               rf77402_tof_config[i].val);
+               if (ret < 0)
+                       return ret;
+       }
+
+       ret = rfd77402_set_state(data, RFD77402_CMD_STANDBY,
+                                RFD77402_STATUS_STANDBY);
+
+       return ret;
+}
+
+static int rfd77402_powerdown(struct rfd77402_data *data)
+{
+       return rfd77402_set_state(data, RFD77402_CMD_STANDBY,
+                                 RFD77402_STATUS_STANDBY);
+}
+
+static int rfd77402_probe(struct i2c_client *client,
+                         const struct i2c_device_id *id)
+{
+       struct rfd77402_data *data;
+       struct iio_dev *indio_dev;
+       int ret;
+
+       ret = i2c_smbus_read_word_data(client, RFD77402_MOD_CHIP_ID);
+       if (ret < 0)
+               return ret;
+       if (ret != 0xad01 && ret != 0xad02) /* known chip ids */
+               return -ENODEV;
+
+       indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
+       if (!indio_dev)
+               return -ENOMEM;
+
+       data = iio_priv(indio_dev);
+       i2c_set_clientdata(client, indio_dev);
+       data->client = client;
+       mutex_init(&data->lock);
+
+       indio_dev->dev.parent = &client->dev;
+       indio_dev->info = &rfd77402_info;
+       indio_dev->channels = rfd77402_channels;
+       indio_dev->num_channels = ARRAY_SIZE(rfd77402_channels);
+       indio_dev->name = RFD77402_DRV_NAME;
+       indio_dev->modes = INDIO_DIRECT_MODE;
+
+       ret = rfd77402_init(data);
+       if (ret < 0)
+               return ret;
+
+       ret = iio_device_register(indio_dev);
+       if (ret)
+               goto err_powerdown;
+
+       return 0;
+
+err_powerdown:
+       rfd77402_powerdown(data);
+       return ret;
+}
+
+static int rfd77402_remove(struct i2c_client *client)
+{
+       struct iio_dev *indio_dev = i2c_get_clientdata(client);
+
+       iio_device_unregister(indio_dev);
+       rfd77402_powerdown(iio_priv(indio_dev));
+
+       return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int rfd77402_suspend(struct device *dev)
+{
+       struct rfd77402_data *data = iio_priv(i2c_get_clientdata(
+                                    to_i2c_client(dev)));
+
+       return rfd77402_powerdown(data);
+}
+
+static int rfd77402_resume(struct device *dev)
+{
+       struct rfd77402_data *data = iio_priv(i2c_get_clientdata(
+                                    to_i2c_client(dev)));
+
+       return rfd77402_init(data);
+}
+#endif
+
+static SIMPLE_DEV_PM_OPS(rfd77402_pm_ops, rfd77402_suspend, rfd77402_resume);
+
+static const struct i2c_device_id rfd77402_id[] = {
+       { "rfd77402", 0},
+       { }
+};
+MODULE_DEVICE_TABLE(i2c, rfd77402_id);
+
+static struct i2c_driver rfd77402_driver = {
+       .driver = {
+               .name   = RFD77402_DRV_NAME,
+               .pm     = &rfd77402_pm_ops,
+       },
+       .probe  = rfd77402_probe,
+       .remove = rfd77402_remove,
+       .id_table = rfd77402_id,
+};
+
+module_i2c_driver(rfd77402_driver);
+
+MODULE_AUTHOR("Peter Meerwald-Stadler <pmeerw@pmeerw.net>");
+MODULE_DESCRIPTION("RFD77402 Time-of-Flight sensor driver");
+MODULE_LICENSE("GPL");