// 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
* + - - - - - - + - - - - - - + - - - - - + - - - - - + - - - - - . . . - - - - +
* | 0 xAA | 0 xOO | REG | LEN | payload [ LEN ] |
* + - - - - - - + - - - - - - + - - - - - + - - - - - + - - - - - . . . - - - - +
*
* Register write responses have the following format
* + - - - - - - + - - - - - - - - - - +
* | 0 xEE | 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
* + - - - - - - + - - - - - - + - - - - - + - - - - - +
* | 0 xAA | 0 xO1 | REG | LEN |
* + - - - - - - + - - - - - - + - - - - - + - - - - - +
*
* Successful register read response have the following format
* + - - - - - - + - - - - - + - - - - - . . . - - - - +
* | 0 xBB | LEN | payload [ LEN ] |
* + - - - - - - + - - - - - + - - - - - . . . - - - - +
*
* Failed register read response have the following format
* + - - - - - - + - - - - - - - - +
* | 0 xEE | 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 30 mS )
*
*
* * * 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 > 30 mS 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 1 st
* 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 + 2 byte resp hdr .
* 6 bytes = 60 bit ( considering 1 start + 1 stop bits ) .
* 60 / 115200 = ~ 520 uS + about 2500 mS delay - > ~ 3 mS
* In 3 mS 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[] = {0 xAA, 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 size_t bno055_ser_receive_buf(struct serdev_device *serdev,
const u8 *buf, size_t size)
{
int status;
struct bno055_ser_priv *priv = serdev_device_get_drvdata(serdev);
size_t remaining = size;
if (size == 0 )
return 0 ;
trace_recv(size, buf);
switch (priv->rx.state) {
case RX_IDLE:
/*
* New packet .
* Check for its 1 st byte that identifies the pkt type .
*/
if (buf[0 ] != 0 xEE && buf[0 ] != 0 xBB) {
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 == 0 xEE) {
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 const 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" );
Messung V0.5 in Prozent C=92 H=94 G=92
¤ Dauer der Verarbeitung: 0.12 Sekunden
(vorverarbeitet am 2026-10-11)
¤
*© Formatika GbR, Deutschland