This adds support for PixArt Imaging’s miniature low power optical navigation chip using LASER light source enabling digital surface tracking. This IIO driver allow to read delta position on 2 axis (X and Y). The values can be taken through ponctual "read_raw" which will issue a read in the device registers to return the deltas or subscribe to the data buffer feed automaticaly by a new value using a trigger gpio. The buffer payload is: |16 bits delta X|16 bits delta Y|timestamp|. The possible I2C adresses are 0x73, 0x75 and 0x79. Unfortunately, the device configuration must be hardcoded in the initialization function and can't be changed "on-the-fly" in user space due to the lack of configuration interface. The "ot" directory is added to coutain Optical Tracker drivers. Signed-off-by: Alexandre Mergnat <amergnat@xxxxxxxxxxxx> --- drivers/iio/Kconfig | 1 + drivers/iio/Makefile | 1 + drivers/iio/ot/Kconfig | 16 ++ drivers/iio/ot/Makefile | 6 + drivers/iio/ot/pat9125.c | 407 +++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 431 insertions(+) create mode 100644 drivers/iio/ot/Kconfig create mode 100644 drivers/iio/ot/Makefile create mode 100644 drivers/iio/ot/pat9125.c diff --git a/drivers/iio/Kconfig b/drivers/iio/Kconfig index d08aeb4..bdf1bd0 100644 --- a/drivers/iio/Kconfig +++ b/drivers/iio/Kconfig @@ -86,6 +86,7 @@ source "drivers/iio/light/Kconfig" source "drivers/iio/magnetometer/Kconfig" source "drivers/iio/multiplexer/Kconfig" source "drivers/iio/orientation/Kconfig" +source "drivers/iio/ot/Kconfig" if IIO_TRIGGER source "drivers/iio/trigger/Kconfig" endif #IIO_TRIGGER diff --git a/drivers/iio/Makefile b/drivers/iio/Makefile index cb59932..fdda2e1 100644 --- a/drivers/iio/Makefile +++ b/drivers/iio/Makefile @@ -32,6 +32,7 @@ obj-y += light/ obj-y += magnetometer/ obj-y += multiplexer/ obj-y += orientation/ +obj-y += ot/ obj-y += potentiometer/ obj-y += potentiostat/ obj-y += pressure/ diff --git a/drivers/iio/ot/Kconfig b/drivers/iio/ot/Kconfig new file mode 100644 index 0000000..3d17fda --- /dev/null +++ b/drivers/iio/ot/Kconfig @@ -0,0 +1,16 @@ +# +# Optical tracker sensors +# +# When adding new entries keep the list in alphabetical order + +menu "Optical tracker sensors" + +config PAT9125 + tristate "Optical tracker PAT9125 I2C driver" + depends on I2C + select IIO_BUFFER + help + Say yes here to build support for PAT9125 optical tracker + sensors. + +endmenu diff --git a/drivers/iio/ot/Makefile b/drivers/iio/ot/Makefile new file mode 100644 index 0000000..cf29491 --- /dev/null +++ b/drivers/iio/ot/Makefile @@ -0,0 +1,6 @@ +# +# Makefile for industrial I/O Optical tracker sensor drivers +# + +# When adding new entries keep the list in alphabetical order +obj-$(CONFIG_PAT9125) += pat9125.o diff --git a/drivers/iio/ot/pat9125.c b/drivers/iio/ot/pat9125.c new file mode 100644 index 0000000..f416bfa --- /dev/null +++ b/drivers/iio/ot/pat9125.c @@ -0,0 +1,407 @@ +// SPDX-License-Identifier: (GPL-2.0) +/* + * Copyright (C) 2018 BayLibre, SAS + * Author: Alexandre Mergnat <amergnat@xxxxxxxxxxxx> + */ + +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/regmap.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/slab.h> + +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/iio/events.h> +#include <linux/iio/buffer.h> +#include <linux/iio/kfifo_buf.h> + +/* I2C Address function to ID pin*/ +#define PAT9125_I2C_ADDR_HI 0x73 +#define PAT9125_I2C_ADDR_LO 0x75 +#define PAT9125_I2C_ADDR_NC 0x79 + +/* Registers */ +#define PAT9125_PRD_ID1_REG 0x00 +#define PAT9125_PRD_ID2_REG 0x01 +#define PAT9125_MOTION_STATUS_REG 0x02 +#define PAT9125_DELTA_X_LO_REG 0x03 +#define PAT9125_DELTA_Y_LO_REG 0x04 +#define PAT9125_OP_MODE_REG 0x05 +#define PAT9125_CONFIG_REG 0x06 +#define PAT9125_WRITE_PROTEC_REG 0x09 +#define PAT9125_SLEEP1_REG 0x0A +#define PAT9125_SLEEP2_REG 0x0B +#define PAT9125_RES_X_REG 0x0D +#define PAT9125_RES_Y_REG 0x0E +#define PAT9125_DELTA_XY_HI_REG 0x12 +#define PAT9125_SHUTER_REG 0x14 +#define PAT9125_FRAME_AVG_REG 0x17 +#define PAT9125_ORIENTATION_REG 0x19 + +/* Masks */ +#define PAT9125_VALID_MOTION_DATA_MASK 0x80 +#define PAT9125_RESET_MASK 0x80 + +/* Registers' values */ +#define PAT9125_SENSOR_ID_VAL 0x31 +#define PAT9125_DISABLE_WRITE_PROTECT_VAL 0x5A +#define PAT9125_ENABLE_WRITE_PROTECT_VAL 0x00 +#define PAT9125_CPI_RESOLUTION_X_VAL 0x65 +#define PAT9125_CPI_RESOLUTION_Y_VAL 0xFF + +/* Default Value of sampled value size */ +#define PAT9125_SAMPLED_VAL_BIT_SIZE 12 +#define PAT9125_SAMPLED_VAL_BYTE_SIZE 2 /* 12 bits by default */ +#define PAT9125_TIMESTAMP_BYTE_SIZE 8 /* 64 bits */ +#define PAT9125_PAYLOAD_BYTE_SIZE \ + (2 * PAT9125_SAMPLED_VAL_BYTE_SIZE + PAT9125_TIMESTAMP_BYTE_SIZE) +#define PAT9125_REALBITS_XY_VAL \ + (2 * PAT9125_SAMPLED_VAL_BIT_SIZE) +#define PAT9125_STORAGEBITS_XY_VAL \ + (2 * PAT9125_SAMPLED_VAL_BYTE_SIZE) + +struct pat9125_data { + struct i2c_client *client; + struct regmap *regmap; + s16 delta_x; + s16 delta_y; +}; + +static const struct iio_event_spec pat9125_event = { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_VALUE), +}; + +static const struct iio_chan_spec pat9125_channels[] = { + { + .type = IIO_DISTANCE, + .modified = 1, + .channel2 = IIO_MOD_X_AND_Y, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .address = 0, + .scan_index = 0, + .scan_type = { + .sign = 's', \ + .realbits = 24, \ + .storagebits = 32, \ + .shift = 8, \ + .endianness = IIO_BE, \ + }, + .event_spec = &pat9125_event, + .num_event_specs = 1, + }, + IIO_CHAN_SOFT_TIMESTAMP(1), +}; + +static int pat9125_read_delta(struct pat9125_data *data) +{ + struct regmap *regmap = data->regmap; + int status = 0; + int val_x = 0; + int val_y = 0; + int val_xy = 0; + int r; + + r = regmap_read(regmap, PAT9125_MOTION_STATUS_REG, &status); + if (r < 0) + return r; + + /* Check motion bit in bit7 */ + if (status & PAT9125_VALID_MOTION_DATA_MASK) { + r = regmap_read(regmap, PAT9125_DELTA_X_LO_REG, &val_x); + if (r < 0) + return r; + + r = regmap_read(regmap, PAT9125_DELTA_Y_LO_REG, &val_y); + if (r < 0) + return r; + + r = regmap_read(regmap, PAT9125_DELTA_XY_HI_REG, &val_xy); + if (r < 0) + return r; + + data->delta_x = val_x | ((val_xy << 4) & 0xF00); + data->delta_y = val_y | ((val_xy << 8) & 0xF00); + + if (data->delta_x & 0x800) + data->delta_x |= 0xF000; + + if (data->delta_y & 0x800) + data->delta_y |= 0xF000; + } + + return 0; +} + +/** + * pat9125_read_raw() - Sample and return the value(s) + * function to the associated channel info enum. + **/ +static int pat9125_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct pat9125_data *data = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = pat9125_read_delta(data); + if (ret) + return ret; + + *val = data->delta_x; + *val2 = data->delta_y; + return IIO_VAL_INT; + default: + return -EINVAL; + } + + return -EINVAL; +} + +/** + * pat9125_read_event_value() - return last sampled value. + **/ +static int pat9125_read_event_value(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int *val, + int *val2) +{ + struct pat9125_data *data = iio_priv(indio_dev); + + switch (info) { + case IIO_EV_INFO_VALUE: + val[0] = data->delta_x; + val[1] = data->delta_y; + *val2 = 2; + return IIO_VAL_INT_MULTIPLE; + default: + return -EINVAL; + } +} + +/** + * pat9125_event_handler() - handling ring and non ring events + * @irq: The irq being handled. + * @private: struct iio_device pointer for the device. + * + */ +static irqreturn_t pat9125_event_handler(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct pat9125_data *data = iio_priv(indio_dev); + int ret = 0; + u8 *payload; + s64 last_timestamp = iio_get_time_ns(indio_dev); + + payload = kmalloc(sizeof(s64) + 2 * + PAT9125_SAMPLED_VAL_BYTE_SIZE, GFP_KERNEL); + + ret = pat9125_read_delta(data); + if (ret) + return ret; + memcpy(&payload[0], &data->delta_x, 2); + memcpy(&payload[2], &data->delta_y, 2); + + iio_push_to_buffers_with_timestamp(indio_dev, payload, last_timestamp); + iio_push_event(indio_dev, + IIO_MOD_EVENT_CODE(IIO_DISTANCE, + 0, + IIO_MOD_X_AND_Y, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_RISING), + last_timestamp); + + return IRQ_HANDLED; +} + +static int pat9125_configure_ring(struct iio_dev *indio_dev) +{ + struct iio_buffer *buffer; + struct pat9125_data *data = iio_priv(indio_dev); + + buffer = devm_iio_kfifo_allocate(&data->client->dev); + if (!buffer) + return -ENOMEM; + + iio_device_attach_buffer(indio_dev, buffer); + indio_dev->modes |= INDIO_BUFFER_SOFTWARE; + + return 0; +} + +static const struct regmap_config pat9125_regmap_config = { + .reg_bits = 8, + .val_bits = 8, +}; + +static const struct iio_info pat9125_info = { + .read_raw = pat9125_read_raw, + .read_event_value = &pat9125_read_event_value, +}; + +static int pat9125_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct pat9125_data *data; + struct iio_dev *indio_dev; + int r, sensor_pid; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) { + dev_err(&client->dev, "IIO device allocation failed"); + return -ENOMEM; + } + + data = iio_priv(indio_dev); + data->client = client; + + indio_dev->dev.parent = &client->dev; + indio_dev->name = id->name; + indio_dev->channels = pat9125_channels; + indio_dev->num_channels = ARRAY_SIZE(pat9125_channels); + indio_dev->info = &pat9125_info; + indio_dev->modes = INDIO_DIRECT_MODE; + + r = pat9125_configure_ring(indio_dev); + if (r < 0) { + dev_err(&client->dev, "FIFO buffer allocation failed"); + return r; + } + + data->regmap = devm_regmap_init_i2c(client, &pat9125_regmap_config); + if (IS_ERR(data->regmap)) { + dev_err(&client->dev, + "regmap init failed %ld", + PTR_ERR(data->regmap)); + return PTR_ERR(data->regmap); + } + + /* Check device ID */ + r = regmap_read(data->regmap, PAT9125_PRD_ID1_REG, &sensor_pid); + if (r < 0) + goto reg_access_fail; + if (sensor_pid != PAT9125_SENSOR_ID_VAL) + return -ENODEV; + + /* Software reset (i.e. set bit7 to 1). + * It will reset to 0 automatically + */ + r = regmap_write_bits(data->regmap, + PAT9125_CONFIG_REG, + PAT9125_RESET_MASK, + 1); + if (r < 0) + goto reg_access_fail; + + /* Delay 20ms */ + msleep(20); + + /* Disable write protect */ + r = regmap_write(data->regmap, + PAT9125_WRITE_PROTEC_REG, + PAT9125_DISABLE_WRITE_PROTECT_VAL); + if (r < 0) + goto reg_access_fail; + + /* Set X-axis resolution (depends on application) */ + r = regmap_write(data->regmap, + PAT9125_RES_X_REG, + 0x0A); + if (r < 0) + goto reg_access_fail; + + /* Set Y-axis resolution (depends on application) */ + r = regmap_write(data->regmap, + PAT9125_RES_Y_REG, + 0x0A); + if (r < 0) + goto reg_access_fail; + + /* Enable write protection */ + r = regmap_write(data->regmap, + PAT9125_WRITE_PROTEC_REG, + PAT9125_ENABLE_WRITE_PROTECT_VAL); + if (r < 0) + goto reg_access_fail; + + r = devm_iio_device_register(&client->dev, indio_dev); + if (r) { + dev_err(&client->dev, "IIO device register failed"); + return r; + } + + i2c_set_clientdata(client, indio_dev); + + dev_info(&client->dev, "%s: sensor '%s'\n", + dev_name(&indio_dev->dev), + client->name); + + /* Make read to reset motion bit status */ + r = pat9125_read_delta(data); + if (r) + goto reg_access_fail; + + /* Init GPIO IRQ */ + if (client->irq) { + r = devm_request_threaded_irq(&client->dev, + client->irq, + NULL, + pat9125_event_handler, + IRQF_TRIGGER_LOW | IRQF_ONESHOT, + "pat9125", + indio_dev); + if (r) + return r; + } + return 0; + +reg_access_fail: + dev_err(&client->dev, "register access failed %d", r); + return r; +} + +static int pat9125_remove(struct i2c_client *client) +{ + dev_info(&client->dev, "PAT9125 removed\n"); + + return 0; +} + +static const struct i2c_device_id pat9125_id[] = { + { "pat9125", 0 }, + {} +}; +MODULE_DEVICE_TABLE(i2c, pat9125_id); + +static const unsigned short normal_i2c[] = { + PAT9125_I2C_ADDR_HI, + PAT9125_I2C_ADDR_LO, + PAT9125_I2C_ADDR_NC, + I2C_CLIENT_END +}; + +static struct i2c_driver pat9125_driver = { + .driver = { + .name = "pat9125", + }, + .probe = pat9125_probe, + .remove = pat9125_remove, + .address_list = normal_i2c, + .id_table = pat9125_id, +}; + +module_i2c_driver(pat9125_driver); + +MODULE_AUTHOR("Alexandre Mergnat <amergnat@xxxxxxxxxxxx>"); +MODULE_DESCRIPTION("Optical Tracking sensor"); +MODULE_LICENSE("GPL"); + -- 2.7.4