/**************************************************************************** * * 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 #ifndef MODULE_NAME #define MODULE_NAME "SPI_I2C" #endif #include #include #include #include #include #include static List 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; } int BusInstanceIterator::runningInstancesOnBusCount() const { int num_instances = 0; for (const auto &modules : i2c_spi_module_instances) { if (modules->_bus == bus() && 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::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"); } }