px4-firmware/platforms/common/i2c_spi_buses.cpp

831 lines
19 KiB
C++

/****************************************************************************
*
* Copyright (C) 2020, 2021 PX4 Development Team. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
* 3. Neither the name PX4 nor the names of its contributors may be
* used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
****************************************************************************/
#include <board_config.h>
#ifndef MODULE_NAME
#define MODULE_NAME "SPI_I2C"
#endif
#include <lib/drivers/device/Device.hpp>
#include <px4_platform_common/i2c_spi_buses.h>
#include <px4_platform_common/px4_work_queue/WorkItemSingleShot.hpp>
#include <px4_platform_common/log.h>
#include <px4_platform_common/getopt.h>
#include <pthread.h>
static List<I2CSPIInstance *> i2c_spi_module_instances; ///< list of currently running instances
static pthread_mutex_t i2c_spi_module_instances_mutex = PTHREAD_MUTEX_INITIALIZER;
I2CSPIDriverConfig::I2CSPIDriverConfig(const BusCLIArguments &cli, const BusInstanceIterator &iterator,
const px4::wq_config_t &wq_config_)
: module_name(iterator.moduleName()),
devid_driver_index(iterator.devidDriverIndex()),
bus_option(iterator.configuredBusOption()),
bus_type(iterator.busType()),
bus(iterator.bus()),
#if defined(CONFIG_I2C)
i2c_address(cli.i2c_address),
#endif // CONFIG_I2C
bus_frequency(cli.bus_frequency),
#if defined(CONFIG_SPI)
drdy_gpio(iterator.DRDYGPIO()),
spi_mode(cli.spi_mode),
spi_devid(iterator.devid()),
#endif // CONFIG_SPI
bus_device_index(iterator.busDeviceIndex()),
rotation(cli.rotation),
quiet_start(cli.quiet_start),
keep_running(cli.keep_running),
custom1(cli.custom1),
custom2(cli.custom2),
custom_data(cli.custom_data),
wq_config(wq_config_)
{
}
const char *BusCLIArguments::parseDefaultArguments(int argc, char *argv[])
{
if (getOpt(argc, argv, "") == EOF) {
return optArg();
}
// unexpected arguments
return nullptr;
}
int BusCLIArguments::getOpt(int argc, char *argv[], const char *options)
{
if (_options[0] == 0) { // need to initialize
if (!validateConfiguration()) {
return EOF;
}
char *p = (char *)&_options;
#if defined(CONFIG_I2C)
if (_i2c_support) {
*(p++) = 'X'; // external
*(p++) = 'I'; // internal
if (i2c_address != 0) {
*(p++) = 'a'; *(p++) = ':'; // I2C address
}
}
#endif // CONFIG_I2C
#if defined(CONFIG_SPI)
if (_spi_support) {
*(p++) = 'S'; // external
*(p++) = 's'; // internal
*(p++) = 'c'; *(p++) = ':'; // chip-select
*(p++) = 'm'; *(p++) = ':'; // spi mode
}
#endif // CONFIG_SPI
if (support_keep_running) {
*(p++) = 'k';
}
*(p++) = 'b'; *(p++) = ':'; // bus
*(p++) = 'f'; *(p++) = ':'; // frequency
*(p++) = 'q'; // quiet flag
// copy all options
const char *option = options;
while (p != _options + sizeof(_options) && *option) {
if (*option != ':') {
// check for duplicates
for (const char *c = _options; c != p; ++c) {
if (*c == *option) {
PX4_ERR("conflicting option: %c", *c);
_options[0] = 0;
return EOF;
}
}
}
*(p++) = *(option++);
}
if (p == _options + sizeof(_options)) {
PX4_ERR("too many options");
_options[0] = 0;
return EOF;
}
*p = '\0';
}
int ch;
while ((ch = px4_getopt(argc, argv, _options, &_optind, &_optarg)) != EOF) {
switch (ch) {
#if defined(CONFIG_I2C)
case 'X':
bus_option = I2CSPIBusOption::I2CExternal;
break;
case 'I':
bus_option = I2CSPIBusOption::I2CInternal;
break;
case 'a':
if (i2c_address == 0) {
return ch;
}
i2c_address = (int)strtol(_optarg, nullptr, 0);
break;
#endif // CONFIG_I2C
#if defined(CONFIG_SPI)
case 'S':
bus_option = I2CSPIBusOption::SPIExternal;
break;
case 's':
bus_option = I2CSPIBusOption::SPIInternal;
break;
case 'c':
chipselect = atoi(_optarg);
break;
#endif // CONFIG_SPI
case 'b':
requested_bus = atoi(_optarg);
break;
case 'f':
bus_frequency = 1000 * atoi(_optarg);
break;
#if defined(CONFIG_SPI)
case 'm':
spi_mode = (spi_mode_e)atoi(_optarg);
break;
#endif // CONFIG_SPI
case 'q':
quiet_start = true;
break;
case 'k':
if (!support_keep_running) {
return ch;
}
keep_running = true;
break;
default:
if (ch == '?') {
// abort further parsing on unknown arguments
_optarg = nullptr;
return EOF;
}
return ch;
}
}
if (ch == EOF) {
_optarg = argv[_optind];
// apply defaults if not provided
if (bus_frequency == 0) {
#if defined(CONFIG_I2C)
if (bus_option == I2CSPIBusOption::I2CExternal || bus_option == I2CSPIBusOption::I2CInternal) {
bus_frequency = default_i2c_frequency;
}
#endif // CONFIG_I2C
#if defined(CONFIG_SPI)
if (bus_option == I2CSPIBusOption::SPIExternal || bus_option == I2CSPIBusOption::SPIInternal) {
bus_frequency = default_spi_frequency;
}
#endif // CONFIG_SPI
}
}
return ch;
}
bool BusCLIArguments::validateConfiguration()
{
bool success = true;
#if defined(CONFIG_I2C)
if (_i2c_support && default_i2c_frequency == -1) {
PX4_ERR("Bug: driver %s does not set default_i2c_frequency", px4_get_taskname());
success = false;
}
#endif // CONFIG_I2C
#if defined(CONFIG_SPI)
if (_spi_support && default_spi_frequency == -1) {
PX4_ERR("Bug: driver %s does not set default_spi_frequency", px4_get_taskname());
success = false;
}
#endif // CONFIG_SPI
return success;
}
BusInstanceIterator::BusInstanceIterator(const char *module_name,
const BusCLIArguments &cli_arguments, uint16_t devid_driver_index)
: _module_name(module_name), _bus_option(cli_arguments.bus_option), _devid_driver_index(devid_driver_index),
#if defined(CONFIG_I2C)
_i2c_address(cli_arguments.i2c_address),
#endif // CONFIG_I2C
#if defined(CONFIG_SPI)
_spi_bus_iterator(spiFilter(cli_arguments.bus_option),
devid_driver_index, cli_arguments.chipselect,
cli_arguments.requested_bus),
#endif // CONFIG_SPI
#if defined(CONFIG_I2C)
_i2c_bus_iterator(i2cFilter(cli_arguments.bus_option), cli_arguments.requested_bus),
#endif // CONFIG_I2C
_current_instance(i2c_spi_module_instances.end())
{
// We lock the module instance list as long as this object is alive, since we iterate over the list.
// Locking could be a bit more fine-grained, but the iterator is mostly only used sequentially, so not an issue.
pthread_mutex_lock(&i2c_spi_module_instances_mutex);
_current_instance = i2c_spi_module_instances.end();
}
BusInstanceIterator::~BusInstanceIterator()
{
pthread_mutex_unlock(&i2c_spi_module_instances_mutex);
}
bool BusInstanceIterator::next()
{
int bus = -1;
if (busType() == BOARD_INVALID_BUS) {
if (_current_instance == i2c_spi_module_instances.end()) { // either not initialized, or the first instance was removed
_current_instance = i2c_spi_module_instances.begin();
} else {
++_current_instance;
}
while (_current_instance != i2c_spi_module_instances.end()) {
if (strcmp((*_current_instance)->_module_name, _module_name) == 0 &&
_devid_driver_index == (*_current_instance)->_devid_driver_index) {
return true;
}
++_current_instance;
}
return false;
#if defined(CONFIG_SPI)
} else if (busType() == BOARD_SPI_BUS) {
if (_spi_bus_iterator.next()) {
bus = _spi_bus_iterator.bus().bus;
}
#endif // CONFIG_SPI
#if defined(CONFIG_I2C)
} else if (busType() == BOARD_I2C_BUS) {
if (_i2c_bus_iterator.next()) {
bus = _i2c_bus_iterator.bus().bus;
}
#endif // CONFIG_I2C
}
if (bus != -1) {
// find matching runtime instance
#if defined(CONFIG_I2C)
bool is_i2c = busType() == BOARD_I2C_BUS;
#else
bool is_i2c = false;
#endif
for (_current_instance = i2c_spi_module_instances.begin(); _current_instance != i2c_spi_module_instances.end();
++_current_instance) {
if (strcmp((*_current_instance)->_module_name, _module_name) != 0) {
continue;
}
if (_bus_option == (*_current_instance)->_bus_option && bus == (*_current_instance)->_bus &&
_devid_driver_index == (*_current_instance)->_devid_driver_index &&
busDeviceIndex() == (*_current_instance)->_bus_device_index &&
(!is_i2c
#if defined(CONFIG_I2C)
|| _i2c_address == (*_current_instance)->_i2c_address
#endif // CONFIG_I2C
)
) {
break;
}
}
return true;
}
return false;
}
int BusInstanceIterator::runningInstancesCount() const
{
int num_instances = 0;
for (const auto &modules : i2c_spi_module_instances) {
if (strcmp(modules->_module_name, _module_name) == 0) {
++num_instances;
}
}
return num_instances;
}
I2CSPIInstance *BusInstanceIterator::instance() const
{
if (_current_instance == i2c_spi_module_instances.end()) {
return nullptr;
}
return *_current_instance;
}
void BusInstanceIterator::removeInstance()
{
// find previous node
List<I2CSPIInstance *>::Iterator previous = i2c_spi_module_instances.begin();
while (previous != i2c_spi_module_instances.end() && (*previous)->getSibling() != *_current_instance) {
++previous;
}
i2c_spi_module_instances.remove(*_current_instance);
_current_instance = previous; // previous can be i2c_spi_module_instances.end(), which means we removed the first item
}
void BusInstanceIterator::addInstance(I2CSPIInstance *instance)
{
i2c_spi_module_instances.add(instance);
}
board_bus_types BusInstanceIterator::busType() const
{
switch (_bus_option) {
case I2CSPIBusOption::All:
return BOARD_INVALID_BUS;
#if defined(CONFIG_I2C)
case I2CSPIBusOption::I2CInternal:
case I2CSPIBusOption::I2CExternal:
return BOARD_I2C_BUS;
#endif // CONFIG_I2C
#if defined(CONFIG_SPI)
case I2CSPIBusOption::SPIInternal:
case I2CSPIBusOption::SPIExternal:
return BOARD_SPI_BUS;
#endif // CONFIG_SPI
}
return BOARD_INVALID_BUS;
}
int BusInstanceIterator::bus() const
{
#if defined(CONFIG_SPI)
if (busType() == BOARD_SPI_BUS) {
return _spi_bus_iterator.bus().bus;
}
#endif // CONFIG_SPI
#if defined(CONFIG_I2C)
if (busType() == BOARD_I2C_BUS) {
return _i2c_bus_iterator.bus().bus;
}
#endif // CONFIG_I2C
return -1;
}
uint32_t BusInstanceIterator::devid() const
{
#if defined(CONFIG_SPI)
if (busType() == BOARD_SPI_BUS) {
return _spi_bus_iterator.devid();
}
#endif // CONFIG_SPI
return 0;
}
#if defined(CONFIG_SPI)
spi_drdy_gpio_t BusInstanceIterator::DRDYGPIO() const
{
if (busType() == BOARD_SPI_BUS) {
return _spi_bus_iterator.DRDYGPIO();
}
return 0;
}
#endif // CONFIG_SPI
bool BusInstanceIterator::external() const
{
#if defined(CONFIG_SPI)
if (busType() == BOARD_SPI_BUS) {
return _spi_bus_iterator.external();
}
#endif // CONFIG_SPI
#if defined(CONFIG_I2C)
if (busType() == BOARD_I2C_BUS) {
return _i2c_bus_iterator.external();
}
#endif // CONFIG_I2C
return false;
}
int BusInstanceIterator::externalBusIndex() const
{
#if defined(CONFIG_SPI)
if (busType() == BOARD_SPI_BUS) {
return _spi_bus_iterator.externalBusIndex();
}
#endif // CONFIG_SPI
#if defined(CONFIG_I2C)
if (busType() == BOARD_I2C_BUS) {
return _i2c_bus_iterator.externalBusIndex();
}
#endif // CONFIG_I2C
return 0;
}
int BusInstanceIterator::busDeviceIndex() const
{
#if defined(CONFIG_SPI)
if (busType() == BOARD_SPI_BUS) {
return _spi_bus_iterator.busDeviceIndex();
}
#endif // CONFIG_SPI
return -1;
}
#if defined(CONFIG_I2C)
I2CBusIterator::FilterType BusInstanceIterator::i2cFilter(I2CSPIBusOption bus_option)
{
switch (bus_option) {
case I2CSPIBusOption::All: return I2CBusIterator::FilterType::All;
case I2CSPIBusOption::I2CExternal: return I2CBusIterator::FilterType::ExternalBus;
case I2CSPIBusOption::I2CInternal: return I2CBusIterator::FilterType::InternalBus;
default: break;
}
return I2CBusIterator::FilterType::All;
}
#endif // CONFIG_I2C
#if defined(CONFIG_SPI)
SPIBusIterator::FilterType BusInstanceIterator::spiFilter(I2CSPIBusOption bus_option)
{
switch (bus_option) {
case I2CSPIBusOption::SPIExternal: return SPIBusIterator::FilterType::ExternalBus;
case I2CSPIBusOption::SPIInternal: return SPIBusIterator::FilterType::InternalBus;
default: break;
}
return SPIBusIterator::FilterType::InternalBus;
}
#endif // CONFIG_SPI
struct I2CSPIDriverInitializing {
const I2CSPIDriverConfig &config;
I2CSPIDriverBase::instantiate_method instantiate;
int runtime_instance;
I2CSPIDriverBase *instance{nullptr};
};
static void initializer_trampoline(void *argument)
{
I2CSPIDriverInitializing *data = (I2CSPIDriverInitializing *)argument;
data->instance = data->instantiate(data->config, data->runtime_instance);
}
int I2CSPIDriverBase::module_start(const BusCLIArguments &cli, BusInstanceIterator &iterator,
void(*print_usage)(), instantiate_method instantiate)
{
if (iterator.configuredBusOption() == I2CSPIBusOption::All) {
PX4_ERR("need to specify a bus type");
print_usage();
return -1;
}
bool started = false;
while (iterator.next()) {
if (iterator.instance()) {
PX4_WARN("Already running on bus %i", iterator.bus());
continue;
}
device::Device::DeviceId device_id{};
device_id.devid_s.bus = iterator.bus();
switch (iterator.busType()) {
#if defined(CONFIG_I2C)
case BOARD_I2C_BUS: device_id.devid_s.bus_type = device::Device::DeviceBusType_I2C; break;
#endif // CONFIG_I2C
#if defined(CONFIG_SPI)
case BOARD_SPI_BUS: device_id.devid_s.bus_type = device::Device::DeviceBusType_SPI; break;
#endif // CONFIG_SPI
case BOARD_INVALID_BUS: device_id.devid_s.bus_type = device::Device::DeviceBusType_UNKNOWN; break;
}
const px4::wq_config_t &wq_config = px4::device_bus_to_wq(device_id.devid);
I2CSPIDriverConfig driver_config{cli, iterator, wq_config};
const int runtime_instance = iterator.runningInstancesCount();
I2CSPIDriverInitializing initializer_data{driver_config, instantiate, runtime_instance};
// initialize the object and bus on the work queue thread - this will also probe for the device
px4::WorkItemSingleShot initializer(wq_config, initializer_trampoline, &initializer_data);
initializer.ScheduleNow();
initializer.wait();
I2CSPIDriverBase *instance = initializer_data.instance;
if (!instance) {
PX4_DEBUG("instantiate failed (no device on bus %i (devid 0x%x)?)", iterator.bus(), iterator.devid());
continue;
}
#if defined(CONFIG_I2C)
if (cli.i2c_address != 0 && instance->_i2c_address == 0) {
PX4_ERR("Bug: driver %s does not pass the I2C address to I2CSPIDriverBase", instance->ItemName());
}
#endif // CONFIG_I2C
iterator.addInstance(instance);
started = true;
// print some info that we are running
switch (iterator.busType()) {
#if defined(CONFIG_I2C)
case BOARD_I2C_BUS:
PX4_INFO_RAW("%s #%i on I2C bus %d", instance->ItemName(), runtime_instance, iterator.bus());
if (iterator.external()) {
PX4_INFO_RAW(" (external)");
}
if (cli.i2c_address != 0) {
PX4_INFO_RAW(" address 0x%X", cli.i2c_address);
}
if (cli.rotation != 0) {
PX4_INFO_RAW(" rotation %d", cli.rotation);
}
PX4_INFO_RAW("\n");
break;
#endif // CONFIG_I2C
#if defined(CONFIG_SPI)
case BOARD_SPI_BUS:
PX4_INFO_RAW("%s #%i on SPI bus %d", instance->ItemName(), runtime_instance, iterator.bus());
if (iterator.external()) {
PX4_INFO_RAW(" (external, equal to '-b %i')", iterator.externalBusIndex());
}
if (cli.rotation != 0) {
PX4_INFO_RAW(" rotation %d", cli.rotation);
}
PX4_INFO_RAW("\n");
break;
#endif // CONFIG_SPI
case BOARD_INVALID_BUS:
break;
}
}
if (!started && !cli.quiet_start) {
static constexpr char no_instance_started[] {"no instance started (no device on bus?)"};
if (iterator.external()) {
PX4_WARN("%s: %s", px4_get_taskname(), no_instance_started);
} else {
PX4_ERR("%s: %s", px4_get_taskname(), no_instance_started);
}
#if defined(CONFIG_I2C)
if (iterator.busType() == BOARD_I2C_BUS && cli.i2c_address == 0) {
PX4_ERR("%s: driver does not set i2c address", px4_get_taskname());
}
#endif // CONFIG_I2C
}
return started ? 0 : -1;
}
int I2CSPIDriverBase::module_stop(BusInstanceIterator &iterator)
{
bool is_running = false;
while (iterator.next()) {
if (iterator.instance()) {
I2CSPIDriverBase *instance = (I2CSPIDriverBase *)iterator.instance();
instance->request_stop_and_wait();
delete iterator.instance();
iterator.removeInstance();
is_running = true;
}
}
if (!is_running) {
PX4_ERR("Not running");
return -1;
}
return 0;
}
int I2CSPIDriverBase::module_status(BusInstanceIterator &iterator)
{
bool is_running = false;
while (iterator.next()) {
if (iterator.instance()) {
I2CSPIDriverBase *instance = (I2CSPIDriverBase *)iterator.instance();
instance->print_status();
is_running = true;
}
}
if (!is_running) {
PX4_INFO("Not running");
return -1;
}
return 0;
}
struct custom_method_data_t {
I2CSPIDriverBase *instance;
const BusCLIArguments &cli;
};
void I2CSPIDriverBase::custom_method_trampoline(void *argument)
{
custom_method_data_t *data = (custom_method_data_t *)argument;
data->instance->custom_method(data->cli);
}
int I2CSPIDriverBase::module_custom_method(const BusCLIArguments &cli, BusInstanceIterator &iterator,
bool run_on_work_queue)
{
while (iterator.next()) {
if (iterator.instance()) {
I2CSPIDriverBase *instance = (I2CSPIDriverBase *)iterator.instance();
if (run_on_work_queue) {
custom_method_data_t data{instance, cli};
px4::WorkItemSingleShot runner(*instance, custom_method_trampoline, &data);
runner.ScheduleNow();
runner.wait();
} else {
instance->custom_method(cli);
}
}
}
return 0;
}
void I2CSPIDriverBase::print_status()
{
#if defined(CONFIG_I2C)
if (_bus_option == I2CSPIBusOption::I2CExternal || _bus_option == I2CSPIBusOption::I2CInternal) {
PX4_INFO("Running on I2C Bus %i, Address 0x%02X", _bus, get_i2c_address());
return;
}
#endif // CONFIG_I2C
#if defined(CONFIG_SPI)
if (_bus_option == I2CSPIBusOption::SPIExternal || _bus_option == I2CSPIBusOption::SPIInternal) {
PX4_INFO("Running on SPI Bus %i", _bus);
return;
}
#endif // CONFIG_SPI
}
void I2CSPIDriverBase::request_stop_and_wait()
{
_task_should_exit.store(true);
ScheduleNow(); // wake up the task (in case it is not scheduled anymore or just to be faster)
unsigned int i = 0;
do {
px4_usleep(20000); // 20 ms
// wait at most 2 sec
} while (++i < 100 && !_task_exited.load());
if (i >= 100) {
PX4_ERR("Module did not respond to stop request");
}
}