Contributors: 1
Author Tokens Token Proportion Commits Commit Proportion
Andrea Merello 1755 100.00% 1 100.00%
Total 1755 1


// SPDX-License-Identifier: GPL-2.0
/*
 * Serial line interface for Bosh BNO055 IMU (via serdev).
 * This file implements serial communication up to the register read/write
 * level.
 *
 * Copyright (C) 2021-2022 Istituto Italiano di Tecnologia
 * Electronic Design Laboratory
 * Written by Andrea Merello <andrea.merello@iit.it>
 *
 * This driver is based on
 *	Plantower PMS7003 particulate matter sensor driver
 *	Which is
 *	Copyright (c) Tomasz Duszynski <tduszyns@gmail.com>
 */

#include <linux/completion.h>
#include <linux/device.h>
#include <linux/errno.h>
#include <linux/jiffies.h>
#include <linux/kernel.h>
#include <linux/mod_devicetable.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/regmap.h>
#include <linux/serdev.h>

#include "bno055_ser_trace.h"
#include "bno055.h"

/*
 * Register writes cmd have the following format
 * +------+------+-----+-----+----- ... ----+
 * | 0xAA | 0xOO | REG | LEN | payload[LEN] |
 * +------+------+-----+-----+----- ... ----+
 *
 * Register write responses have the following format
 * +------+----------+
 * | 0xEE | ERROCODE |
 * +------+----------+
 *
 * .. except when writing the SYS_RST bit (i.e. triggering a system reset); in
 * case the IMU accepts the command, then it resets without responding. We don't
 * handle this (yet) here (so we inform the common bno055 code not to perform
 * sw resets - bno055 on serial bus basically requires the hw reset pin).
 *
 * Register read have the following format
 * +------+------+-----+-----+
 * | 0xAA | 0xO1 | REG | LEN |
 * +------+------+-----+-----+
 *
 * Successful register read response have the following format
 * +------+-----+----- ... ----+
 * | 0xBB | LEN | payload[LEN] |
 * +------+-----+----- ... ----+
 *
 * Failed register read response have the following format
 * +------+--------+
 * | 0xEE | ERRCODE|  (ERRCODE always > 1)
 * +------+--------+
 *
 * Error codes are
 * 01: OK
 * 02: read/write FAIL
 * 04: invalid address
 * 05: write on RO
 * 06: wrong start byte
 * 07: bus overrun
 * 08: len too high
 * 09: len too low
 * 10: bus RX byte timeout (timeout is 30mS)
 *
 *
 * **WORKAROUND ALERT**
 *
 * Serial communication seems very fragile: the BNO055 buffer seems to overflow
 * very easy; BNO055 seems able to sink few bytes, then it needs a brief pause.
 * On the other hand, it is also picky on timeout: if there is a pause > 30mS in
 * between two bytes then the transaction fails (IMU internal RX FSM resets).
 *
 * BNO055 has been seen also failing to process commands in case we send them
 * too close each other (or if it is somehow busy?)
 *
 * In particular I saw these scenarios:
 * 1) If we send 2 bytes per time, then the IMU never(?) overflows.
 * 2) If we send 4 bytes per time (i.e. the full header), then the IMU could
 *    overflow, but it seem to sink all 4 bytes, then it returns error.
 * 3) If we send more than 4 bytes, the IMU could overflow, and I saw it sending
 *    error after 4 bytes are sent; we have troubles in synchronizing again,
 *    because we are still sending data, and the IMU interprets it as the 1st
 *    byte of a new command.
 *
 * While we must avoid case 3, we could send 4 bytes per time and eventually
 * retry in case of failure; this seemed convenient for reads (which requires
 * TXing exactly 4 bytes), however it has been seen that, depending by the IMU
 * settings (e.g. LPF), failures became less or more frequent; in certain IMU
 * configurations they are very rare, but in certain others we keeps failing
 * even after like 30 retries.
 *
 * So, we just split TXes in [2-bytes + delay] steps, and still keep an eye on
 * the IMU response; in case it overflows (which is now unlikely), we retry.
 */

/*
 * Read operation overhead:
 *  4 bytes req + 2byte resp hdr.
 *  6 bytes = 60 bit (considering 1start + 1stop bits).
 *  60/115200 = ~520uS + about 2500mS delay -> ~3mS
 * In 3mS we could read back about 34 bytes that means 17 samples, this means
 * that in case of scattered reads in which the gap is 17 samples or less it is
 * still convenient to go for a burst.
 * We have to take into account also IMU response time - IMU seems to be often
 * reasonably quick to respond, but sometimes it seems to be in some "critical
 * section" in which it delays handling of serial protocol. Because of this we
 * round-up to 22, which is the max number of samples, always bursting indeed.
 */
#define BNO055_SER_XFER_BURST_BREAK_THRESHOLD 22

struct bno055_ser_priv {
	enum {
		CMD_NONE,
		CMD_READ,
		CMD_WRITE,
	} expect_response;
	int expected_data_len;
	u8 *response_buf;

	/**
	 * enum cmd_status - represent the status of a command sent to the HW.
	 * @STATUS_CRIT: The command failed: the serial communication failed.
	 * @STATUS_OK:   The command executed successfully.
	 * @STATUS_FAIL: The command failed: HW responded with an error.
	 */
	enum {
		STATUS_CRIT = -1,
		STATUS_OK = 0,
		STATUS_FAIL = 1,
	} cmd_status;

	/*
	 * Protects all the above fields, which are accessed in behalf of both
	 * the serdev RX callback and the regmap side
	 */
	struct mutex lock;

	/* Only accessed in serdev RX callback context*/
	struct {
		enum {
			RX_IDLE,
			RX_START,
			RX_DATA,
		} state;
		int databuf_count;
		int expected_len;
		int type;
	} rx;

	/* Never accessed in behalf of serdev RX callback context */
	bool cmd_stale;

	struct completion cmd_complete;
	struct serdev_device *serdev;
};

static int bno055_ser_send_chunk(struct bno055_ser_priv *priv, const u8 *data, int len)
{
	int ret;

	trace_send_chunk(len, data);
	ret = serdev_device_write(priv->serdev, data, len, msecs_to_jiffies(25));
	if (ret < 0)
		return ret;

	if (ret < len)
		return -EIO;

	return 0;
}

/*
 * Send a read or write command.
 * 'data' can be NULL (used in read case). 'len' parameter is always valid; in
 * case 'data' is non-NULL then it must match 'data' size.
 */
static int bno055_ser_do_send_cmd(struct bno055_ser_priv *priv,
				  bool read, int addr, int len, const u8 *data)
{
	u8 hdr[] = {0xAA, read, addr, len};
	int chunk_len;
	int ret;

	ret = bno055_ser_send_chunk(priv, hdr, 2);
	if (ret)
		goto fail;
	usleep_range(2000, 3000);
	ret = bno055_ser_send_chunk(priv, hdr + 2, 2);
	if (ret)
		goto fail;

	if (read)
		return 0;

	while (len) {
		chunk_len = min(len, 2);
		usleep_range(2000, 3000);
		ret = bno055_ser_send_chunk(priv, data, chunk_len);
		if (ret)
			goto fail;
		data += chunk_len;
		len -= chunk_len;
	}

	return 0;
fail:
	/* waiting more than 30mS should clear the BNO055 internal state */
	usleep_range(40000, 50000);
	return ret;
}

static int bno055_ser_send_cmd(struct bno055_ser_priv *priv,
			       bool read, int addr, int len, const u8 *data)
{
	const int retry_max = 5;
	int retry = retry_max;
	int ret = 0;

	/*
	 * In case previous command was interrupted we still need to wait it to
	 * complete before we can issue new commands
	 */
	if (priv->cmd_stale) {
		ret = wait_for_completion_interruptible_timeout(&priv->cmd_complete,
								msecs_to_jiffies(100));
		if (ret == -ERESTARTSYS)
			return -ERESTARTSYS;

		priv->cmd_stale = false;
		/* if serial protocol broke, bail out */
		if (priv->cmd_status == STATUS_CRIT)
			return -EIO;
	}

	/*
	 * Try to convince the IMU to cooperate.. as explained in the comments
	 * at the top of this file, the IMU could also refuse the command (i.e.
	 * it is not ready yet); retry in this case.
	 */
	do {
		mutex_lock(&priv->lock);
		priv->expect_response = read ? CMD_READ : CMD_WRITE;
		reinit_completion(&priv->cmd_complete);
		mutex_unlock(&priv->lock);

		if (retry != retry_max)
			trace_cmd_retry(read, addr, retry_max - retry);
		ret = bno055_ser_do_send_cmd(priv, read, addr, len, data);
		if (ret)
			continue;

		ret = wait_for_completion_interruptible_timeout(&priv->cmd_complete,
								msecs_to_jiffies(100));
		if (ret == -ERESTARTSYS) {
			priv->cmd_stale = true;
			return -ERESTARTSYS;
		}

		if (!ret)
			return -ETIMEDOUT;

		if (priv->cmd_status == STATUS_OK)
			return 0;
		if (priv->cmd_status == STATUS_CRIT)
			return -EIO;

		/* loop in case priv->cmd_status == STATUS_FAIL */
	} while (--retry);

	if (ret < 0)
		return ret;
	if (priv->cmd_status == STATUS_FAIL)
		return -EINVAL;
	return 0;
}

static int bno055_ser_write_reg(void *context, const void *_data, size_t count)
{
	const u8 *data = _data;
	struct bno055_ser_priv *priv = context;

	if (count < 2) {
		dev_err(&priv->serdev->dev, "Invalid write count %zu", count);
		return -EINVAL;
	}

	trace_write_reg(data[0], data[1]);
	return bno055_ser_send_cmd(priv, 0, data[0], count - 1, data + 1);
}

static int bno055_ser_read_reg(void *context,
			       const void *_reg, size_t reg_size,
			       void *val, size_t val_size)
{
	int ret;
	int reg_addr;
	const u8 *reg = _reg;
	struct bno055_ser_priv *priv = context;

	if (val_size > 128) {
		dev_err(&priv->serdev->dev, "Invalid read valsize %zu", val_size);
		return -EINVAL;
	}

	reg_addr = *reg;
	trace_read_reg(reg_addr, val_size);
	mutex_lock(&priv->lock);
	priv->expected_data_len = val_size;
	priv->response_buf = val;
	mutex_unlock(&priv->lock);

	ret = bno055_ser_send_cmd(priv, 1, reg_addr, val_size, NULL);

	mutex_lock(&priv->lock);
	priv->response_buf = NULL;
	mutex_unlock(&priv->lock);

	return ret;
}

/*
 * Handler for received data; this is called from the receiver callback whenever
 * it got some packet from the serial bus. The status tells us whether the
 * packet is valid (i.e. header ok && received payload len consistent wrt the
 * header). It's now our responsibility to check whether this is what we
 * expected, of whether we got some unexpected, yet valid, packet.
 */
static void bno055_ser_handle_rx(struct bno055_ser_priv *priv, int status)
{
	mutex_lock(&priv->lock);
	switch (priv->expect_response) {
	case CMD_NONE:
		dev_warn(&priv->serdev->dev, "received unexpected, yet valid, data from sensor");
		mutex_unlock(&priv->lock);
		return;

	case CMD_READ:
		priv->cmd_status = status;
		if (status == STATUS_OK &&
		    priv->rx.databuf_count != priv->expected_data_len) {
			/*
			 * If we got here, then the lower layer serial protocol
			 * seems consistent with itself; if we got an unexpected
			 * amount of data then signal it as a non critical error
			 */
			priv->cmd_status = STATUS_FAIL;
			dev_warn(&priv->serdev->dev,
				 "received an unexpected amount of, yet valid, data from sensor");
		}
		break;

	case CMD_WRITE:
		priv->cmd_status = status;
		break;
	}

	priv->expect_response = CMD_NONE;
	mutex_unlock(&priv->lock);
	complete(&priv->cmd_complete);
}

/*
 * Serdev receiver FSM. This tracks the serial communication and parse the
 * header. It pushes packets to bno055_ser_handle_rx(), eventually communicating
 * failures (i.e. malformed packets).
 * Ideally it doesn't know anything about upper layer (i.e. if this is the
 * packet we were really expecting), but since we copies the payload into the
 * receiver buffer (that is not valid when i.e. we don't expect data), we
 * snoop a bit in the upper layer..
 * Also, we assume to RX one pkt per time (i.e. the HW doesn't send anything
 * unless we require to AND we don't queue more than one request per time).
 */
static int bno055_ser_receive_buf(struct serdev_device *serdev,
				  const unsigned char *buf, size_t size)
{
	int status;
	struct bno055_ser_priv *priv = serdev_device_get_drvdata(serdev);
	int remaining = size;

	if (size == 0)
		return 0;

	trace_recv(size, buf);
	switch (priv->rx.state) {
	case RX_IDLE:
		/*
		 * New packet.
		 * Check for its 1st byte that identifies the pkt type.
		 */
		if (buf[0] != 0xEE && buf[0] != 0xBB) {
			dev_err(&priv->serdev->dev,
				"Invalid packet start %x", buf[0]);
			bno055_ser_handle_rx(priv, STATUS_CRIT);
			break;
		}
		priv->rx.type = buf[0];
		priv->rx.state = RX_START;
		remaining--;
		buf++;
		priv->rx.databuf_count = 0;
		fallthrough;

	case RX_START:
		/*
		 * Packet RX in progress, we expect either 1-byte len or 1-byte
		 * status depending by the packet type.
		 */
		if (remaining == 0)
			break;

		if (priv->rx.type == 0xEE) {
			if (remaining > 1) {
				dev_err(&priv->serdev->dev, "EE pkt. Extra data received");
				status = STATUS_CRIT;
			} else {
				status = (buf[0] == 1) ? STATUS_OK : STATUS_FAIL;
			}
			bno055_ser_handle_rx(priv, status);
			priv->rx.state = RX_IDLE;
			break;

		} else {
			/*priv->rx.type == 0xBB */
			priv->rx.state = RX_DATA;
			priv->rx.expected_len = buf[0];
			remaining--;
			buf++;
		}
		fallthrough;

	case RX_DATA:
		/* Header parsed; now receiving packet data payload */
		if (remaining == 0)
			break;

		if (priv->rx.databuf_count + remaining > priv->rx.expected_len) {
			/*
			 * This is an inconsistency in serial protocol, we lost
			 * sync and we don't know how to handle further data
			 */
			dev_err(&priv->serdev->dev, "BB pkt. Extra data received");
			bno055_ser_handle_rx(priv, STATUS_CRIT);
			priv->rx.state = RX_IDLE;
			break;
		}

		mutex_lock(&priv->lock);
		/*
		 * NULL e.g. when read cmd is stale or when no read cmd is
		 * actually pending.
		 */
		if (priv->response_buf &&
		    /*
		     * Snoop on the upper layer protocol stuff to make sure not
		     * to write to an invalid memory. Apart for this, let's the
		     * upper layer manage any inconsistency wrt expected data
		     * len (as long as the serial protocol is consistent wrt
		     * itself (i.e. response header is consistent with received
		     * response len.
		     */
		    (priv->rx.databuf_count + remaining <= priv->expected_data_len))
			memcpy(priv->response_buf + priv->rx.databuf_count,
			       buf, remaining);
		mutex_unlock(&priv->lock);

		priv->rx.databuf_count += remaining;

		/*
		 * Reached expected len advertised by the IMU for the current
		 * packet. Pass it to the upper layer (for us it is just valid).
		 */
		if (priv->rx.databuf_count == priv->rx.expected_len) {
			bno055_ser_handle_rx(priv, STATUS_OK);
			priv->rx.state = RX_IDLE;
		}
		break;
	}

	return size;
}

static const struct serdev_device_ops bno055_ser_serdev_ops = {
	.receive_buf = bno055_ser_receive_buf,
	.write_wakeup = serdev_device_write_wakeup,
};

static struct regmap_bus bno055_ser_regmap_bus = {
	.write = bno055_ser_write_reg,
	.read = bno055_ser_read_reg,
};

static int bno055_ser_probe(struct serdev_device *serdev)
{
	struct bno055_ser_priv *priv;
	struct regmap *regmap;
	int ret;

	priv = devm_kzalloc(&serdev->dev, sizeof(*priv), GFP_KERNEL);
	if (!priv)
		return -ENOMEM;

	serdev_device_set_drvdata(serdev, priv);
	priv->serdev = serdev;
	mutex_init(&priv->lock);
	init_completion(&priv->cmd_complete);

	serdev_device_set_client_ops(serdev, &bno055_ser_serdev_ops);
	ret = devm_serdev_device_open(&serdev->dev, serdev);
	if (ret)
		return ret;

	if (serdev_device_set_baudrate(serdev, 115200) != 115200) {
		dev_err(&serdev->dev, "Cannot set required baud rate");
		return -EIO;
	}

	ret = serdev_device_set_parity(serdev, SERDEV_PARITY_NONE);
	if (ret) {
		dev_err(&serdev->dev, "Cannot set required parity setting");
		return ret;
	}
	serdev_device_set_flow_control(serdev, false);

	regmap = devm_regmap_init(&serdev->dev, &bno055_ser_regmap_bus,
				  priv, &bno055_regmap_config);
	if (IS_ERR(regmap))
		return dev_err_probe(&serdev->dev, PTR_ERR(regmap),
				     "Unable to init register map");

	return bno055_probe(&serdev->dev, regmap,
			    BNO055_SER_XFER_BURST_BREAK_THRESHOLD, false);
}

static const struct of_device_id bno055_ser_of_match[] = {
	{ .compatible = "bosch,bno055" },
	{ }
};
MODULE_DEVICE_TABLE(of, bno055_ser_of_match);

static struct serdev_device_driver bno055_ser_driver = {
	.driver = {
		.name = "bno055-ser",
		.of_match_table = bno055_ser_of_match,
	},
	.probe = bno055_ser_probe,
};
module_serdev_device_driver(bno055_ser_driver);

MODULE_AUTHOR("Andrea Merello <andrea.merello@iit.it>");
MODULE_DESCRIPTION("Bosch BNO055 serdev interface");
MODULE_IMPORT_NS(IIO_BNO055);
MODULE_LICENSE("GPL");