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");
Information contained on this website is for historical information purposes only and does not indicate or represent copyright ownership.
Created with Cregit http://github.com/cregit/cregit
Version 2.0-RC1