diff --git a/CMakeLists.txt b/CMakeLists.txt index 52ed022a7..af4bc2e31 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -24,6 +24,7 @@ option (BUILD_BLUETOOTH "BUILD_BLUETOOTH" OFF) option (BUILD_BLE "BUILD_BLE" OFF) option (BUILD_ONNX "BUILD_ONNX" OFF) option (BUILD_TESTS "BUILD_TESTS" OFF) +option (BUILD_PERIPHERY "BUILD_PERIPHERY" OFF) include (${CMAKE_CURRENT_SOURCE_DIR}/cmake/macros.cmake) configure_msvc_runtime () diff --git a/csharp_package/brainflow/brainflow/board_controller_library.cs b/csharp_package/brainflow/brainflow/board_controller_library.cs index 35de08f0f..ca545b3aa 100644 --- a/csharp_package/brainflow/brainflow/board_controller_library.cs +++ b/csharp_package/brainflow/brainflow/board_controller_library.cs @@ -114,7 +114,8 @@ public enum BoardIds FREEEEG128_BOARD = 52, AAVAA_V3_BOARD = 53, EXPLORE_PLUS_8_CHAN_BOARD = 54, - EXPLORE_PLUS_32_CHAN_BOARD = 55 + EXPLORE_PLUS_32_CHAN_BOARD = 55, + PIEEG_BOARD = 56 }; diff --git a/docs/SupportedBoards.rst b/docs/SupportedBoards.rst index 770b3ac33..4bf7681c2 100644 --- a/docs/SupportedBoards.rst +++ b/docs/SupportedBoards.rst @@ -1268,3 +1268,40 @@ Available :ref:`presets-label`: - :code:`BrainFlowPresets.DEFAULT_PRESET`, it contains accelerometer, gyroscope and magnetometer data - :code:`BrainFlowPresets.AUXILIARY_PRESET`, it contains PPG data - :code:`BrainFlowPresets.ANCILLARY_PRESET`, it contains EDA and temperature data + +PiEEG +------ + +PiEEG Board +~~~~~~~~~~~ + +PiEEG (Measure EEG with RaspberryPi) – Brain-computer interface (EEG, EMG, and ECG bio-signals) is an open-source Raspberry Pi shield that measures biosignals such as those used in electroencephalography (EEG), electromyography (EMG), and electrocardiography (ECG). It integrates seamlessly with BrainFlow's API, allowing for easy data streaming, processing, and analysis. + +.. image:: https://live.staticflickr.com/65535/53823500137_3bf2e27dbf_z.jpg + :width: 640px + :height: 384px + +`PiEEG Website `_ + +To create such a board, you need to specify the following board ID and fields of the BrainFlowInputParams object: + +- :code:`BoardIds.PIEEG_BOARD` +- :code:`serial_port`(optional), e.g., COM3, /dev/spidev0.0, etc. + +Initialization Example: + +.. code-block:: python + from brainflow.board_shim import BoardShim, BrainFlowInputParams, BoardIds + params = BrainFlowInputParams() + params.serial_port = "/dev/spidev0.0" + board = BoardShim(BoardIds.PIEEG_BOARD, params) + board.prepare_session() + board.start_stream() + +Supported platforms: + +- Raspberry Pi + +**Note**: Ensure that you have the necessary permissions to access the serial port on your operating system. For Unix-like systems, you may need to configure permissions for the serial port or run with sudo. + +**To use this board you need to compile BrainFlow from the source code right on your Raspbery Pi device with flag --build-periphery(build.py) or with -DBUILD_PERIPHERY=ON(CMake) and install desired bindings using local libraries.** \ No newline at end of file diff --git a/java_package/brainflow/src/main/java/brainflow/BoardIds.java b/java_package/brainflow/src/main/java/brainflow/BoardIds.java index 20bfa815d..6377c20af 100644 --- a/java_package/brainflow/src/main/java/brainflow/BoardIds.java +++ b/java_package/brainflow/src/main/java/brainflow/BoardIds.java @@ -64,7 +64,8 @@ public enum BoardIds FREEEEG128_BOARD (52), AAVAA_V3_BOARD(53), EXPLORE_PLUS_8_CHAN_BOARD(54), - EXPLORE_PLUS_32_CHAN_BOARD(55); + EXPLORE_PLUS_32_CHAN_BOARD(55), + PIEEG_BOARD(56); private final int board_id; private static final Map bi_map = new HashMap (); diff --git a/julia_package/brainflow/src/board_shim.jl b/julia_package/brainflow/src/board_shim.jl index a4ccbd5a0..c21b94c83 100644 --- a/julia_package/brainflow/src/board_shim.jl +++ b/julia_package/brainflow/src/board_shim.jl @@ -60,6 +60,7 @@ export BrainFlowInputParams AAVAA_V3_BOARD = 53 EXPLORE_PLUS_8_CHAN_BOARD = 54 EXPLORE_PLUS_32_CHAN_BOARD = 55 + PIEEG_BOARD = 56 end diff --git a/matlab_package/brainflow/BoardIds.m b/matlab_package/brainflow/BoardIds.m index 0fba24e7f..ceb601a86 100644 --- a/matlab_package/brainflow/BoardIds.m +++ b/matlab_package/brainflow/BoardIds.m @@ -58,5 +58,6 @@ AAVAA_V3_BOARD(53) EXPLORE_PLUS_8_CHAN_BOARD(54) EXPLORE_PLUS_32_CHAN_BOARD(55) + PIEEG_BOARD(56) end end \ No newline at end of file diff --git a/nodejs_package/brainflow/brainflow.types.ts b/nodejs_package/brainflow/brainflow.types.ts index 21929592f..c95ec7d1c 100644 --- a/nodejs_package/brainflow/brainflow.types.ts +++ b/nodejs_package/brainflow/brainflow.types.ts @@ -67,7 +67,8 @@ export enum BoardIds { NTL_WIFI_BOARD = 50, ANT_NEURO_EE_511_BOARD = 51, EXPLORE_PLUS_8_CHAN_BOARD = 54, - EXPLORE_PLUS_32_CHAN_BOARD = 55 + EXPLORE_PLUS_32_CHAN_BOARD = 55, + PIEEG_BOARD = 56 } export enum IpProtocolTypes { diff --git a/python_package/brainflow/board_shim.py b/python_package/brainflow/board_shim.py index 4c6613f64..ced6ec9d5 100644 --- a/python_package/brainflow/board_shim.py +++ b/python_package/brainflow/board_shim.py @@ -73,6 +73,7 @@ class BoardIds(enum.IntEnum): AAVAA_V3_BOARD = 53 #: EXPLORE_PLUS_8_CHAN_BOARD = 54 #: EXPLORE_PLUS_32_CHAN_BOARD = 55 #: + PIEEG_BOARD = 56 #: class IpProtocolTypes(enum.IntEnum): diff --git a/src/board_controller/board_controller.cpp b/src/board_controller/board_controller.cpp index 055bd949b..623499463 100644 --- a/src/board_controller/board_controller.cpp +++ b/src/board_controller/board_controller.cpp @@ -49,6 +49,7 @@ #include "muse_bled.h" #include "notion_osc.h" #include "ntl_wifi.h" +#include "pieeg_board.h" #include "playback_file_board.h" #include "streaming_board.h" #include "synthetic_board.h" @@ -277,6 +278,9 @@ int prepare_session (int board_id, const char *json_brainflow_input_params) case BoardIds::EXPLORE_PLUS_32_CHAN_BOARD: board = std::shared_ptr (new Explore (board_id, params)); break; + case BoardIds::PIEEG_BOARD: + board = std::shared_ptr (new PIEEGBoard (board_id, params)); + break; default: return (int)BrainFlowExitCodes::UNSUPPORTED_BOARD_ERROR; } diff --git a/src/board_controller/brainflow_boards.cpp b/src/board_controller/brainflow_boards.cpp index 0057d82d8..22d5c91f4 100644 --- a/src/board_controller/brainflow_boards.cpp +++ b/src/board_controller/brainflow_boards.cpp @@ -71,7 +71,10 @@ BrainFlowBoards::BrainFlowBoards() {"50", json::object()}, {"51", json::object()}, {"52", json::object()}, - {"53", json::object()} + {"53", json::object()}, + {"54", json::object()}, + {"55", json::object()}, + {"56", json::object()} } }}; @@ -1082,6 +1085,16 @@ BrainFlowBoards::BrainFlowBoards() {"battery_channel", 2}, {"other_channels", {3}} }; + brainflow_boards_json["boards"]["56"]["default"] = { + {"name", "PIEEG"}, + {"sampling_rate", 250}, + {"package_num_channel", 0}, + {"timestamp_channel", 9}, + {"marker_channel", 10}, + {"num_rows", 11}, + {"eeg_channels", {1, 2, 3, 4, 5, 6, 7, 8}}, + {"eeg_names", "Fp1,Fp2,C3,C4,P7,P8,O1,O2"} + }; } BrainFlowBoards boards_struct; diff --git a/src/board_controller/build.cmake b/src/board_controller/build.cmake index 4dad340b3..aeeedbaa7 100644 --- a/src/board_controller/build.cmake +++ b/src/board_controller/build.cmake @@ -84,6 +84,7 @@ SET (BOARD_CONTROLLER_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/board_controller/emotibit/emotibit.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/board_controller/ntl/ntl_wifi.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/board_controller/aavaa/aavaa_v3.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/board_controller/pieeg/pieeg_board.cpp ) include (${CMAKE_CURRENT_SOURCE_DIR}/src/board_controller/ant_neuro/build.cmake) @@ -140,6 +141,7 @@ target_include_directories ( ${CMAKE_CURRENT_SOURCE_DIR}/src/board_controller/emotibit/inc ${CMAKE_CURRENT_SOURCE_DIR}/src/board_controller/ntl/inc ${CMAKE_CURRENT_SOURCE_DIR}/src/board_controller/aavaa/inc + ${CMAKE_CURRENT_SOURCE_DIR}/src/board_controller/pieeg/inc ) target_compile_definitions(${BOARD_CONTROLLER_NAME} PRIVATE NOMINMAX BRAINFLOW_VERSION=${BRAINFLOW_VERSION}) @@ -168,6 +170,13 @@ if (USE_LIBFTDI) endif (LibFTDI1_FOUND) endif (USE_LIBFTDI) +if (BUILD_PERIPHERY) + add_subdirectory (${CMAKE_CURRENT_SOURCE_DIR}/third_party/periphery) + target_compile_definitions (${BOARD_CONTROLLER_NAME} PRIVATE USE_PERIPHERY) + include_directories (${CMAKE_CURRENT_SOURCE_DIR}/third_party/periphery) + target_link_libraries (${BOARD_CONTROLLER_NAME} PRIVATE periphery ${CMAKE_THREAD_LIBS_INIT} dl) +endif (BUILD_PERIPHERY) + if (MSVC) add_custom_command (TARGET ${BOARD_CONTROLLER_NAME} POST_BUILD COMMAND "${CMAKE_COMMAND}" -E copy_if_different "${CMAKE_CURRENT_SOURCE_DIR}/compiled/$/${BOARD_CONTROLLER_COMPILED_NAME}" "${CMAKE_CURRENT_SOURCE_DIR}/nodejs_package/brainflow/lib/${BOARD_CONTROLLER_COMPILED_NAME}" diff --git a/src/board_controller/pieeg/inc/pieeg_board.h b/src/board_controller/pieeg/inc/pieeg_board.h new file mode 100644 index 000000000..b6b37b6e6 --- /dev/null +++ b/src/board_controller/pieeg/inc/pieeg_board.h @@ -0,0 +1,40 @@ +#pragma once + +#include +#include + +#include "board.h" +#include "board_controller.h" +#include "math.h" +#include "socket_server_tcp.h" + +#ifdef USE_PERIPHERY +#include "gpio.h" +#include "spi.h" +#endif + +class PIEEGBoard : public Board +{ +protected: +#ifdef USE_PERIPHERY + volatile bool keep_alive; + bool initialized; + std::thread streaming_thread; + spi_t *spi; + gpio_t *gpio_in; + SocketServerTCP *server_socket; + void read_thread (); + int write_reg (uint8_t reg_address, uint8_t val); + int send_command (uint8_t command); +#endif + +public: + PIEEGBoard (int board_id, struct BrainFlowInputParams params); + ~PIEEGBoard (); + + int prepare_session (); + int start_stream (int buffer_size, const char *streamer_params); + int stop_stream (); + int release_session (); + int config_board (std::string config, std::string &response); +}; diff --git a/src/board_controller/pieeg/pieeg_board.cpp b/src/board_controller/pieeg/pieeg_board.cpp new file mode 100644 index 000000000..41e9fda44 --- /dev/null +++ b/src/board_controller/pieeg/pieeg_board.cpp @@ -0,0 +1,345 @@ +#include "pieeg_board.h" + +#include +#include +#include + +#include "custom_cast.h" +#include "timestamp.h" + +#ifdef USE_PERIPHERY + +PIEEGBoard::PIEEGBoard (int board_id, struct BrainFlowInputParams params) : Board (board_id, params) +{ + spi = NULL; + gpio_in = NULL; + keep_alive = false; + initialized = false; +} + +PIEEGBoard::~PIEEGBoard () +{ + skip_logs = true; + release_session (); +} + +int PIEEGBoard::prepare_session () +{ + if (initialized) + { + safe_logger (spdlog::level::info, "Session already prepared"); + return (int)BrainFlowExitCodes::STATUS_OK; + } + if (params.serial_port.empty ()) + { + params.serial_port = "/dev/spidev0.0"; + safe_logger (spdlog::level::info, "Use serial port {}", params.serial_port.c_str ()); + } + + gpio_in = gpio_new (); + if (gpio_open (gpio_in, "/dev/gpiochip0", 26, GPIO_DIR_IN) < 0) + { + safe_logger (spdlog::level::err, "failed to open gpio"); + gpio_free (gpio_in); + gpio_in = NULL; + return (int)BrainFlowExitCodes::UNABLE_TO_OPEN_PORT_ERROR; + } + spi = spi_new (); + if (spi_open_advanced (spi, params.serial_port.c_str (), 0b01, 1000000, MSB_FIRST, 8, 1) < 0) + { + safe_logger (spdlog::level::err, "failed to open spi dev"); + spi_free (spi); + spi = NULL; + gpio_free (gpio_in); + gpio_in = NULL; + return (int)BrainFlowExitCodes::UNABLE_TO_OPEN_PORT_ERROR; + } + int gpio_res = gpio_set_edge (gpio_in, GPIO_EDGE_FALLING); + if (gpio_res != 0) + { + safe_logger (spdlog::level::err, "failed to set gpio edge event handler: {}", gpio_res); + spi_free (spi); + spi = NULL; + gpio_free (gpio_in); + gpio_in = NULL; + return (int)BrainFlowExitCodes::UNABLE_TO_OPEN_PORT_ERROR; + } + + initialized = true; + return (int)BrainFlowExitCodes::STATUS_OK; +} + +int PIEEGBoard::start_stream (int buffer_size, const char *streamer_params) +{ + if (keep_alive) + { + safe_logger (spdlog::level::err, "Streaming thread already running"); + return (int)BrainFlowExitCodes::STREAM_ALREADY_RUN_ERROR; + } + int res = prepare_for_acquisition (buffer_size, streamer_params); + if (res != (int)BrainFlowExitCodes::STATUS_OK) + { + return res; + } + + int spi_res = write_reg (0x14, 0x80); // led + if (spi_res == (int)BrainFlowExitCodes::STATUS_OK) + { + spi_res = write_reg (0x05, 0x00); // ch1 + } + if (spi_res == (int)BrainFlowExitCodes::STATUS_OK) + { + spi_res = write_reg (0x06, 0x0); // ch2 + } + if (spi_res == (int)BrainFlowExitCodes::STATUS_OK) + { + spi_res = write_reg (0x07, 0x00); // ch3 + } + if (spi_res == (int)BrainFlowExitCodes::STATUS_OK) + { + spi_res = write_reg (0x08, 0x00); // ch4 + } + if (spi_res == (int)BrainFlowExitCodes::STATUS_OK) + { + spi_res = write_reg (0x09, 0x00); // ch5 + } + if (spi_res == (int)BrainFlowExitCodes::STATUS_OK) + { + spi_res = write_reg (0x0A, 0x00); // ch6 + } + if (spi_res == (int)BrainFlowExitCodes::STATUS_OK) + { + spi_res = write_reg (0x0B, 0x00); // ch7 + } + if (spi_res == (int)BrainFlowExitCodes::STATUS_OK) + { + spi_res = write_reg (0x0C, 0x00); // ch8 + } + if (spi_res == (int)BrainFlowExitCodes::STATUS_OK) + { + spi_res = write_reg (0x15, 0x20); // mics + } + if (spi_res == (int)BrainFlowExitCodes::STATUS_OK) + { + spi_res = write_reg (0x01, 0x96); // reg1 + } + if (spi_res == (int)BrainFlowExitCodes::STATUS_OK) + { + spi_res = write_reg (0x02, 0xD4); // reg2 + } + if (spi_res == (int)BrainFlowExitCodes::STATUS_OK) + { + spi_res = write_reg (0x03, 0xFF); // reg3 + } + if (spi_res == (int)BrainFlowExitCodes::STATUS_OK) + { + spi_res = send_command (0x10); // sdatac + } + if (spi_res == (int)BrainFlowExitCodes::STATUS_OK) + { + spi_res = send_command (0x08); // start + } + + if (spi_res != (int)BrainFlowExitCodes::STATUS_OK) + { + return spi_res; + } + + keep_alive = true; + streaming_thread = std::thread ([this] { this->read_thread (); }); + return (int)BrainFlowExitCodes::STATUS_OK; +} + +int PIEEGBoard::stop_stream () +{ + if (keep_alive) + { + keep_alive = false; + if (streaming_thread.joinable ()) + { + streaming_thread.join (); + } + return send_command (0x0A); // stop + } + else + { + return (int)BrainFlowExitCodes::STREAM_THREAD_IS_NOT_RUNNING; + } +} + +int PIEEGBoard::release_session () +{ + if (initialized) + { + if (keep_alive) + { + stop_stream (); + } + free_packages (); + initialized = false; + } + if (spi) + { + spi_close (spi); + spi_free (spi); + spi = NULL; + } + if (gpio_in) + { + gpio_close (gpio_in); + gpio_free (gpio_in); + gpio_in = NULL; + } + + return (int)BrainFlowExitCodes::STATUS_OK; +} + +int PIEEGBoard::config_board (std::string config, std::string &response) +{ + if (!initialized) + { + return (int)BrainFlowExitCodes::BOARD_NOT_READY_ERROR; + } + return (int)BrainFlowExitCodes::UNSUPPORTED_BOARD_ERROR; +} + +void PIEEGBoard::read_thread () +{ + uint8_t buf[27] = {0}; + uint8_t zero27[27] = {0}; + int num_rows = board_descr["default"]["num_rows"]; + double *package = new double[num_rows]; + for (int i = 0; i < num_rows; i++) + { + package[i] = 0.0; + } + + std::vector eeg_channels = board_descr["default"]["eeg_channels"]; + + double eeg_scale = 4.5 / float ((pow (2, 23) - 1)) / 8 * 1000000.; + double timestamp = 0; + unsigned int counter = 0; + int timeout_ms = 1000; + uint32_t data_test = 0x7FFFFF; + uint32_t data_check = 0xFFFFFF; + + while (keep_alive) + { + int gpio_res = gpio_poll (gpio_in, timeout_ms); + timestamp = get_timestamp (); + if (gpio_res == 0) + { + safe_logger (spdlog::level::trace, "no gpio event in {} ms", timeout_ms); + } + else if (gpio_res < 0) + { + safe_logger (spdlog::level::warn, "error in gpio_poll: {}", gpio_res); + } + else + { + gpio_edge_t edge = GPIO_EDGE_NONE; + gpio_res = gpio_read_event (gpio_in, &edge, NULL); + if (gpio_res != 0) + { + safe_logger (spdlog::level::warn, "failed to get gpio event: {}", gpio_res); + continue; + } + if (edge != GPIO_EDGE_FALLING) + { + safe_logger (spdlog::level::warn, "unexpected gpio event"); + continue; + } + int spi_res = spi_transfer (spi, zero27, buf, 27); + if (spi_res != 0) + { + safe_logger (spdlog::level::warn, "failed to read from spi: {}", spi_res); + continue; + } + for (size_t i = 0; i < eeg_channels.size (); i++) + { + int offset = 3 * i + 3; + uint32_t voltage = (buf[offset] << 8) | buf[offset + 1]; + voltage = (voltage << 8) | buf[offset + 2]; + uint32_t voltage_test = voltage | data_test; + if (voltage_test == data_check) + { + voltage = 16777214 - voltage; + } + + package[eeg_channels[i]] = 0.27 * voltage; + } + package[board_descr["default"]["timestamp_channel"].get ()] = timestamp; + package[board_descr["default"]["package_num_channel"].get ()] = (double)counter++; + push_package (package); + } + } + delete[] package; +} + +int PIEEGBoard::write_reg (uint8_t reg_address, uint8_t val) +{ + uint8_t zero3[3] = {0, 0, 0}; + uint8_t reg_address_shift = 0x40 | reg_address; + uint8_t write[3] = {reg_address_shift, 0x00, val}; + int spi_res = spi_transfer (spi, write, zero3, 3); + if (spi_res < 0) + { + safe_logger ( + spdlog::level::err, "failed to write reg {}, error: {}", (int)reg_address, spi_res); + return (int)BrainFlowExitCodes::BOARD_WRITE_ERROR; + } + return (int)BrainFlowExitCodes::STATUS_OK; +} + +int PIEEGBoard::send_command (uint8_t command) +{ + uint8_t zero = 0; + int spi_res = spi_transfer (spi, &command, &zero, 1); + if (spi_res < 0) + { + safe_logger ( + spdlog::level::err, "failed to write command {}, error: {}", (int)command, spi_res); + return (int)BrainFlowExitCodes::BOARD_WRITE_ERROR; + } + return (int)BrainFlowExitCodes::STATUS_OK; +} + +#else + +PIEEGBoard::PIEEGBoard (int board_id, struct BrainFlowInputParams params) : Board (board_id, params) +{ +} + +PIEEGBoard::~PIEEGBoard () +{ +} + +int PIEEGBoard::prepare_session () +{ + safe_logger (spdlog::level::err, + "BrainFlow should be rebuild with BUILD_PERIPHERY CMake option or with --build-periphery " + "flag in build.py"); + return (int)BrainFlowExitCodes::UNSUPPORTED_BOARD_ERROR; +} + +int PIEEGBoard::config_board (std::string config, std::string &response) +{ + return (int)BrainFlowExitCodes::UNSUPPORTED_BOARD_ERROR; +} + +int PIEEGBoard::release_session () +{ + return (int)BrainFlowExitCodes::UNSUPPORTED_BOARD_ERROR; +} + +int PIEEGBoard::stop_stream () +{ + return (int)BrainFlowExitCodes::UNSUPPORTED_BOARD_ERROR; +} + +int PIEEGBoard::start_stream (int buffer_size, const char *streamer_params) +{ + return (int)BrainFlowExitCodes::UNSUPPORTED_BOARD_ERROR; +} + +#endif diff --git a/src/utils/inc/brainflow_constants.h b/src/utils/inc/brainflow_constants.h index 185be7794..26034af5b 100644 --- a/src/utils/inc/brainflow_constants.h +++ b/src/utils/inc/brainflow_constants.h @@ -87,9 +87,10 @@ enum class BoardIds : int AAVAA_V3_BOARD = 53, EXPLORE_PLUS_8_CHAN_BOARD = 54, EXPLORE_PLUS_32_CHAN_BOARD = 55, + PIEEG_BOARD = 56, // use it to iterate FIRST = PLAYBACK_FILE_BOARD, - LAST = EXPLORE_PLUS_32_CHAN_BOARD + LAST = PIEEG_BOARD }; enum class IpProtocolTypes : int diff --git a/third_party/periphery/CHANGELOG.md b/third_party/periphery/CHANGELOG.md new file mode 100644 index 000000000..0fb272bf2 --- /dev/null +++ b/third_party/periphery/CHANGELOG.md @@ -0,0 +1,184 @@ +* v2.4.2 - 07/05/2023 + * GPIO + * Fix building under Linux kernel headers missing realtime event + timestamp support in the gpio-cdev v2 ABI. + +* v2.4.1 - 04/21/2023 + * GPIO + * Fix realtime timestamp reporting for line events in gpio-cdev v2 + implementation. + +* v2.4.0 - 03/31/2023 + * GPIO + * Add support for gpio-cdev v2 ABI. + +* v2.3.1 - 01/05/2021 + * SPI + * Fix compilation error and unused variable/parameter warnings when + building under Linux kernel headers without 32-bit SPI mode flags + support. + * Contributors + * Ryan Barnett, @rjbarnet - 708f7fe, 21c1b7a + +* v2.3.0 - 12/16/2020 + * MMIO + * Add advanced open function with device path for use with + alternate memory character devices (e.g. `/dev/gpiomem`). + * SPI + * Add getter and setter for 32-bit extra flags. + * Add advanced open function with 32-bit extra flags. + * Build + * Enable unused parameter warning. + * Contributors + * Rémy Dziemiaszko, @remdzi - b8adb42 + +* v2.2.5 - 11/19/2020 + * GPIO + * Add direction checks for improved error reporting to `gpio_write()`, + `gpio_read_event()`, and `gpio_poll()` for character device GPIOs. + * Improve string handling in `gpio_open()` and in getters for + sysfs and character device GPIOs. + * LED + * Improve string handling in `led_open()` and `led_name()`. + * Build + * Add default optimization to CFLAGS in Makefile. + * Add debug and release CFLAGS to CMakeLists.txt. + +* v2.2.4 - 09/11/2020 + * Fix future spurious close caused by uncleared handle state after an error + during open in GPIO, I2C, SPI, Serial, and MMIO modules. + +* v2.2.3 - 09/03/2020 + * GPIO + * Disable character device GPIO support when building with older Linux + kernel headers missing line event support in the gpio-cdev ABI. + * SPI + * Fix formatted bits per word truncation in `spi_tostring()`. + * Build + * Add test for character device GPIO support in Linux kernel headers to + Makefile. + * Contributors + * Fabrice Fontaine, @ffontaine - 5b81b89 + +* v2.2.2 - 07/24/2020 + * GPIO + * Add conditional compilation of character device GPIO support to allow + build under older Linux kernel headers. + * Increase feature test macro version to fix missing definition + warnings. + * Build + * Fix directory paths for pkg-config pc file generation and + installation under CMake. + * Fix COMMIT_ID identification when building within a parent git + repository under CMake. + * Add CMake build option for tests. + * Contributors + * oficsu, @oficsu - 80bc63d + * Ryan Barnett, @rjbarnet - ea1e0da, 05262e6, 50fcd0a + * Fabrice Fontaine, @ffontaine - caadb46 + +* v2.2.1 - 05/31/2020 + * GPIO + * Add feature test macro for POLLRDNORM flag to fix build with uClibc. + * Fix argument name in prototype for `gpio_set_bias()`. + * Contributors + * Joris Offouga, @jorisoffouga - cfc722e + +* v2.2.0 - 05/29/2020 + * GPIO + * Add `gpio_poll_multiple()` function. + * Add getter for line consumer label. + * Add getters and setters for line bias, line drive, and inverted + properties. + * Add advanced open functions with additional properties for character + device GPIOs. + * Only unexport GPIO in `gpio_close()` if exported in + `gpio_open_sysfs()` for sysfs GPIOs. + * Add retry loop to direction write after export to accommodate delayed + udev permission rule application in `gpio_open_sysfs()` for sysfs + GPIOs. + * Improve wording and fix typos in documentation. + * Serial + * Add getters and setters for vmin and vtime termios settings. + * Add support for termios timeout with `serial_read()`. + * Improve wording in documentation. + * Build + * Add CMake build support. + * Add pkg-config pc file generation. + * Contributors + * Joris Offouga, @jorisoffouga - 952e1e9, 671e618 + +* v2.1.0 - 01/07/2020 + * Add LED module. + * Add PWM module. + * Clean up internal string handling in SPI and GPIO modules. + +* v2.0.1 - 10/08/2019 + * Initialize handle state in new functions of all modules. + * Fix performance of blocking read in `serial_read()`. + * Return error on unexpected empty read in `serial_read()`, which may be + caused by a serial port disconnect. + * Improve formatting of `spi_tostring()`. + * Fix typo in GPIO module documentation. + * Fix cross-compilation support in Makefile to allow override of CC + variable. + +* v2.0.0 - 09/30/2019 + * Add support for character device GPIOs (`gpio-cdev`) to the GPIO module. + * Remove support for preserve direction in `gpio_open()`. + * Remove problematic dummy read with sysfs GPIOs from `gpio_poll()`. + * Unexport sysfs GPIOs in `gpio_close()`. + * Migrate to opaque handles with new/free functions in all modules. + * Simplify error codes for MMIO, I2C, and Serial modules. + * Fix typos in GPIO module documentation. + * Update tests with running hints for Raspberry Pi 3. + * Improve cross-compilation support in Makefile. + * Contributors + * longsky, @wangqiang1588 - d880ef7 + * jhlim, @johlim - 742d983 + +* v1.1.3 - 04/28/2018 + * Fix data's most significant bit getting stripped when opening a serial + port with parity enabled in `serial_open_advanced()`. + * Contributors + * Ryan Barnett, @rjbarnet - 537eeac + +* v1.1.2 - 04/01/2018 + * Add handling for delayed pin directory export on some platforms in + `gpio_open()`. + * Fix supported functions query for 64-bit in `i2c_open()`. + * Add support for building with C++. + * Contributors + * Jared Bents, @jmbents - 304faf4 + * Ryan Barnett, @rjbarnet - 82ebb4f + +* v1.1.1 - 04/25/2017 + * Fix blocking `gpio_poll()` for some platforms. + * Add library version macros and functions. + * Contributors + * Михаил Корнилов, @iTiky - 0643fe9 + +* v1.1.0 - 09/27/2016 + * Add support for preserving pin direction to `gpio_open()`. + * Fix enabling input parity check in `serial_set_parity()`. + * Fix enabling hardware flow control in `serial_set_rtscts()`. + * Include missing header to fix build with musl libc. + * Omit unsupported serial baudrates to fix build on SPARC. + * Contributors + * Thomas Petazzoni - 27a9552, 114c715 + +* v1.0.3 - 05/25/2015 + * Fix portability of serial baud rate set/get with termios-provided baud rate functions. + * Fix unlikely bug in `spi_tostring()` formatting. + * Clean up integer argument signedness in serial API. + +* v1.0.2 - 01/31/2015 + * Fix `gpio_supports_interrupts()` so it does not return an error if interrupts are not supported. + * Fix `errno` preservation in a few error paths, mostly in the open functions. + +* v1.0.1 - 12/26/2014 + * Improve Makefile. + * Fix _BSD_SOURCE compilation warnings. + +* v1.0.0 - 05/15/2014 + * Initial release. diff --git a/third_party/periphery/CMakeLists.txt b/third_party/periphery/CMakeLists.txt new file mode 100644 index 000000000..16e92e385 --- /dev/null +++ b/third_party/periphery/CMakeLists.txt @@ -0,0 +1,77 @@ +cmake_minimum_required(VERSION 2.6) +project(periphery C) + +option(BUILD_TESTS "Build test programs" ON) + +if(NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE Release) +endif() + +# Check Linux kernel header files for character device GPIO support +include(CheckSourceCompiles) +check_source_compiles(C "#include \nint main(void) { GPIO_V2_LINE_FLAG_EVENT_CLOCK_REALTIME; return 0; }" HAVE_GPIO_CDEV_V2) +include(CheckSymbolExists) +check_symbol_exists(GPIO_GET_LINEEVENT_IOCTL linux/gpio.h HAVE_GPIO_CDEV_V1) +if(HAVE_GPIO_CDEV_V2) + set(GPIO_CDEV_SUPPORT 2) +elseif(HAVE_GPIO_CDEV_V1) + set(GPIO_CDEV_SUPPORT 1) +else() + set(GPIO_CDEV_SUPPORT 0) + message(WARNING "Missing character device GPIO support in Linux kernel header files. c-periphery will be built with legacy sysfs GPIO support only.") +endif() +add_definitions(-DPERIPHERY_GPIO_CDEV_SUPPORT=${GPIO_CDEV_SUPPORT}) + +# Library version +set(VERSION "2.4.2") +set(SOVERSION ${VERSION}) + +# Glob sources, headers, tests +file(GLOB_RECURSE periphery_SOURCES src/*.c) +file(GLOB_RECURSE periphery_HEADERS src/*.h) +file(GLOB_RECURSE periphery_TESTS tests/*.c) + +# Expose git commit id into COMMIT_ID variable +execute_process( + COMMAND git --git-dir="${CMAKE_CURRENT_SOURCE_DIR}/.git" describe --abbrev --always --tags --dirty + WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" + OUTPUT_VARIABLE COMMIT_ID + ERROR_QUIET + OUTPUT_STRIP_TRAILING_WHITESPACE) + +# Define C flags and include directories +add_definitions(-DPERIPHERY_VERSION_COMMIT="${COMMIT_ID}") +# Conditional flags for different compilers +if(MSVC) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /W3 /WX") +else() + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=gnu99 -pedantic -Wall -Wextra -Wno-stringop-truncation -fPIC") +endif() +set(CMAKE_C_FLAGS_DEBUG "-g") +set(CMAKE_C_FLAGS_RELEASE "-O3") +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/src) + +# Declare library target +add_library(periphery ${periphery_SOURCES} ${periphery_HEADERS}) +set_target_properties(periphery PROPERTIES SOVERSION ${VERSION}) + +include(GNUInstallDirs) + +# Generate pkg-config pc file +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/src/libperiphery.pc.in ${CMAKE_CURRENT_BINARY_DIR}/libperiphery.pc @ONLY) + +# Declare install targets +install(TARGETS periphery DESTINATION ${CMAKE_INSTALL_LIBDIR}) +install(FILES ${periphery_HEADERS} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}) +install(FILES ${CMAKE_CURRENT_BINARY_DIR}/libperiphery.pc DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig) + +# Declare test targets if enabled +if(BUILD_TESTS) + foreach(TEST_SOURCE ${periphery_TESTS}) + get_filename_component(TEST_PROGRAM ${TEST_SOURCE} NAME_WE) + add_executable(${TEST_PROGRAM} ${TEST_SOURCE}) + target_link_libraries(${TEST_PROGRAM} periphery pthread) + set(TEST_PROGRAMS ${TEST_PROGRAMS} ${TEST_PROGRAM}) + endforeach() + add_custom_target(tests DEPENDS periphery ${TEST_PROGRAMS}) +endif() diff --git a/third_party/periphery/LICENSE b/third_party/periphery/LICENSE new file mode 100644 index 000000000..90a7586de --- /dev/null +++ b/third_party/periphery/LICENSE @@ -0,0 +1,19 @@ + Copyright (c) 2014-2023 vsergeev / Ivan (Vanya) A. Sergeev + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. diff --git a/third_party/periphery/README.md b/third_party/periphery/README.md new file mode 100644 index 000000000..fe7deddbd --- /dev/null +++ b/third_party/periphery/README.md @@ -0,0 +1,485 @@ +# c-periphery [![Build Status](https://github.com/vsergeev/c-periphery/actions/workflows/build.yml/badge.svg)](https://github.com/vsergeev/c-periphery/actions/workflows/build.yml) [![GitHub release](https://img.shields.io/github/release/vsergeev/c-periphery.svg?maxAge=7200)](https://github.com/vsergeev/c-periphery) [![License](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/vsergeev/c-periphery/blob/master/LICENSE) + +## C Library for Linux Peripheral I/O (GPIO, LED, PWM, SPI, I2C, MMIO, Serial) + +c-periphery is a small C library for GPIO, LED, PWM, SPI, I2C, MMIO, and Serial peripheral I/O interface access in userspace Linux. c-periphery simplifies and consolidates the native Linux APIs to these interfaces. c-periphery is useful in embedded Linux environments (including Raspberry Pi, BeagleBone, etc. platforms) for interfacing with external peripherals. c-periphery is re-entrant, has no dependencies outside the standard C library and Linux, compiles into a static library for easy integration with other projects, and is MIT licensed. + +Using Python or Lua? Check out the [python-periphery](https://github.com/vsergeev/python-periphery) and [lua-periphery](https://github.com/vsergeev/lua-periphery) projects. + +Contributed libraries: [java-periphery](https://github.com/sgjava/java-periphery), [dart_periphery](https://github.com/pezi/dart_periphery) + +## Examples + +### GPIO + +``` c +#include +#include +#include + +#include "gpio.h" + +int main(void) { + gpio_t *gpio_in, *gpio_out; + bool value; + + gpio_in = gpio_new(); + gpio_out = gpio_new(); + + /* Open GPIO /dev/gpiochip0 line 10 with input direction */ + if (gpio_open(gpio_in, "/dev/gpiochip0", 10, GPIO_DIR_IN) < 0) { + fprintf(stderr, "gpio_open(): %s\n", gpio_errmsg(gpio_in)); + exit(1); + } + + /* Open GPIO /dev/gpiochip0 line 12 with output direction */ + if (gpio_open(gpio_out, "/dev/gpiochip0", 12, GPIO_DIR_OUT) < 0) { + fprintf(stderr, "gpio_open(): %s\n", gpio_errmsg(gpio_out)); + exit(1); + } + + /* Read input GPIO into value */ + if (gpio_read(gpio_in, &value) < 0) { + fprintf(stderr, "gpio_read(): %s\n", gpio_errmsg(gpio_in)); + exit(1); + } + + /* Write output GPIO with !value */ + if (gpio_write(gpio_out, !value) < 0) { + fprintf(stderr, "gpio_write(): %s\n", gpio_errmsg(gpio_out)); + exit(1); + } + + gpio_close(gpio_in); + gpio_close(gpio_out); + + gpio_free(gpio_in); + gpio_free(gpio_out); + + return 0; +} +``` + +[Go to GPIO documentation.](docs/gpio.md) + +### LED + +``` c +#include +#include +#include + +#include "led.h" + +int main(void) { + led_t *led; + unsigned int max_brightness; + + led = led_new(); + + /* Open LED led0 */ + if (led_open(led, "led0") < 0) { + fprintf(stderr, "led_open(): %s\n", led_errmsg(led)); + exit(1); + } + + /* Turn on LED (set max brightness) */ + if (led_write(led, true) < 0) { + fprintf(stderr, "led_write(): %s\n", led_errmsg(led)); + exit(1); + } + + /* Get max brightness */ + if (led_get_max_brightness(led, &max_brightness) < 0) { + fprintf(stderr, "led_get_max_brightness(): %s\n", led_errmsg(led)); + exit(1); + } + + /* Set half brightness */ + if (led_set_brightness(led, max_brightness / 2) < 0) { + fprintf(stderr, "led_set_brightness(): %s\n", led_errmsg(led)); + exit(1); + } + + led_close(led); + + led_free(led); + + return 0; +} +``` + +[Go to LED documentation.](docs/led.md) + +### PWM + +``` c +#include +#include + +#include "pwm.h" + +int main(void) { + pwm_t *pwm; + + pwm = pwm_new(); + + /* Open PWM chip 0, channel 10 */ + if (pwm_open(pwm, 0, 10) < 0) { + fprintf(stderr, "pwm_open(): %s\n", pwm_errmsg(pwm)); + exit(1); + } + + /* Set frequency to 1 kHz */ + if (pwm_set_frequency(pwm, 1e3) < 0) { + fprintf(stderr, "pwm_set_frequency(): %s\n", pwm_errmsg(pwm)); + exit(1); + } + + /* Set duty cycle to 75% */ + if (pwm_set_duty_cycle(pwm, 0.75) < 0) { + fprintf(stderr, "pwm_set_duty_cycle(): %s\n", pwm_errmsg(pwm)); + exit(1); + } + + /* Enable PWM */ + if (pwm_enable(pwm) < 0) { + fprintf(stderr, "pwm_enable(): %s\n", pwm_errmsg(pwm)); + exit(1); + } + + /* Change duty cycle to 50% */ + if (pwm_set_duty_cycle(pwm, 0.50) < 0) { + fprintf(stderr, "pwm_set_duty_cycle(): %s\n", pwm_errmsg(pwm)); + exit(1); + } + + pwm_close(pwm); + + pwm_free(pwm); + + return 0; +} +``` + +[Go to PWM documentation.](docs/pwm.md) + +### SPI + +``` c +#include +#include +#include + +#include "spi.h" + +int main(void) { + spi_t *spi; + uint8_t buf[4] = { 0xaa, 0xbb, 0xcc, 0xdd }; + + spi = spi_new(); + + /* Open spidev1.0 with mode 0 and max speed 1MHz */ + if (spi_open(spi, "/dev/spidev1.0", 0, 1000000) < 0) { + fprintf(stderr, "spi_open(): %s\n", spi_errmsg(spi)); + exit(1); + } + + /* Shift out and in 4 bytes */ + if (spi_transfer(spi, buf, buf, sizeof(buf)) < 0) { + fprintf(stderr, "spi_transfer(): %s\n", spi_errmsg(spi)); + exit(1); + } + + printf("shifted in: 0x%02x 0x%02x 0x%02x 0x%02x\n", buf[0], buf[1], buf[2], buf[3]); + + spi_close(spi); + + spi_free(spi); + + return 0; +} +``` + +[Go to SPI documentation.](docs/spi.md) + +### I2C + +``` c +#include +#include +#include + +#include "i2c.h" + +#define EEPROM_I2C_ADDR 0x50 + +int main(void) { + i2c_t *i2c; + + i2c = i2c_new(); + + /* Open the i2c-0 bus */ + if (i2c_open(i2c, "/dev/i2c-0") < 0) { + fprintf(stderr, "i2c_open(): %s\n", i2c_errmsg(i2c)); + exit(1); + } + + /* Read byte at address 0x100 of EEPROM */ + uint8_t msg_addr[2] = { 0x01, 0x00 }; + uint8_t msg_data[1] = { 0xff, }; + struct i2c_msg msgs[2] = + { + /* Write 16-bit address */ + { .addr = EEPROM_I2C_ADDR, .flags = 0, .len = 2, .buf = msg_addr }, + /* Read 8-bit data */ + { .addr = EEPROM_I2C_ADDR, .flags = I2C_M_RD, .len = 1, .buf = msg_data}, + }; + + /* Transfer a transaction with two I2C messages */ + if (i2c_transfer(i2c, msgs, 2) < 0) { + fprintf(stderr, "i2c_transfer(): %s\n", i2c_errmsg(i2c)); + exit(1); + } + + printf("0x%02x%02x: %02x\n", msg_addr[0], msg_addr[1], msg_data[0]); + + i2c_close(i2c); + + i2c_free(i2c); + + return 0; +} +``` + +[Go to I2C documentation.](docs/i2c.md) + +### MMIO + +``` c +#include +#include +#include +#include + +#include "mmio.h" + +struct am335x_rtcss_registers { + uint32_t seconds; /* 0x00 */ + uint32_t minutes; /* 0x04 */ + uint32_t hours; /* 0x08 */ + /* ... */ +}; + +int main(void) { + mmio_t *mmio; + uint32_t mac_id0_lo, mac_id0_hi; + volatile struct am335x_rtcss_registers *regs; + + mmio = mmio_new(); + + /* Open Control Module */ + if (mmio_open(mmio, 0x44E10000, 0x1000) < 0) { + fprintf(stderr, "mmio_open(): %s\n", mmio_errmsg(mmio)); + exit(1); + } + + /* Read lower 2 bytes of MAC address */ + if (mmio_read32(mmio, 0x630, &mac_id0_lo) < 0) { + fprintf(stderr, "mmio_read32(): %s\n", mmio_errmsg(mmio)); + exit(1); + } + + /* Read upper 4 bytes of MAC address */ + if (mmio_read32(mmio, 0x634, &mac_id0_hi) < 0) { + fprintf(stderr, "mmio_read32(): %s\n", mmio_errmsg(mmio)); + exit(1); + } + + printf("MAC address: %08X%04X\n", __bswap_32(mac_id0_hi), __bswap_16(mac_id0_lo)); + + mmio_close(mmio); + + /* Open RTC subsystem */ + if (mmio_open(mmio, 0x44E3E000, 0x1000) < 0) { + fprintf(stderr, "mmio_open(): %s\n", mmio_errmsg(mmio)); + exit(1); + } + + regs = mmio_ptr(mmio); + + /* Read current RTC time */ + printf("hours: %02x minutes: %02x seconds %02x\n", regs->hours, regs->minutes, regs->seconds); + + mmio_close(mmio); + + mmio_free(mmio); + + return 0; +} +``` + +[Go to MMIO documentation.](docs/mmio.md) + +### Serial + +``` c +#include +#include +#include + +#include "serial.h" + +int main(void) { + serial_t *serial; + uint8_t s[] = "Hello World!"; + uint8_t buf[128]; + int ret; + + serial = serial_new(); + + /* Open /dev/ttyUSB0 with baudrate 115200, and defaults of 8N1, no flow control */ + if (serial_open(serial, "/dev/ttyUSB0", 115200) < 0) { + fprintf(stderr, "serial_open(): %s\n", serial_errmsg(serial)); + exit(1); + } + + /* Write to the serial port */ + if (serial_write(serial, s, sizeof(s)) < 0) { + fprintf(stderr, "serial_write(): %s\n", serial_errmsg(serial)); + exit(1); + } + + /* Read up to buf size or 2000ms timeout */ + if ((ret = serial_read(serial, buf, sizeof(buf), 2000)) < 0) { + fprintf(stderr, "serial_read(): %s\n", serial_errmsg(serial)); + exit(1); + } + + printf("read %d bytes: _%s_\n", ret, buf); + + serial_close(serial); + + serial_free(serial); + + return 0; +} +``` + +[Go to Serial documentation.](docs/serial.md) + +## Building c-periphery with CMake + +### Static library + +Build c-periphery into a static library: + +``` console +$ mkdir build +$ cd build +$ cmake .. +$ make +``` + +### Shared Library + +Build c-periphery into a shared library: + +``` console +$ mkdir build +$ cd build +$ cmake -DBUILD_SHARED_LIBS=ON .. +$ make +``` + +Install the shared library and headers: + +``` console +$ sudo make install +``` + +### Tests + +Build c-periphery tests from the build directory: + +``` console +$ make tests +``` + +### Cross-compilation + +Set the `CC` environment variable with the cross-compiler prior to build: + +``` console +$ export CC=arm-linux-gnueabihf-gcc +$ mkdir build +$ cd build +$ cmake .. +$ make +``` + +If additional cross-compiler tools are needed, use a `CMAKE_TOOLCHAIN_FILE` to fully specify the toolchain parameters: + +``` console +$ mkdir build +$ cd build +$ cmake -DCMAKE_TOOLCHAIN_FILE=/path/to/arm-linux-gnueabihf.cmake .. +$ make +``` + +## Building c-periphery with vanilla Make + +### Static library + +Build c-periphery into a static library: + +``` console +$ make +``` + +### Tests + +Build c-periphery tests: + +``` console +$ make tests +``` + +### Cross-compilation + +Set the `CROSS_COMPILE` environment variable with the cross-compiler prefix when building: + +``` console +$ CROSS_COMPILE=arm-linux-gnueabihf- make +``` + +## Building c-periphery into another project statically + +Include the header files from `src/` and link in the `periphery.a` static library: + +``` console +$ gcc -I/path/to/periphery/src myprog.c /path/to/periphery/periphery.a -o myprog +``` + +## Building c-periphery into another project dynamically + +If the header files and shared library are installed on the system, simply link with `-lperiphery`: + +``` console +$ gcc myprog.c -lperiphery -o myprog +``` + +Otherwise, additional include (`-I`) and library (`-L`) paths may be required. + +## Documentation + +`man` page style documentation for each interface wrapper is available in [docs](docs/) folder. + +## Testing + +The tests located in the [tests](tests/) folder may be run to test the correctness and functionality of c-periphery. Some tests require interactive probing (e.g. with an oscilloscope), the installation of a physical loopback, or the existence of a particular device on a bus. See the usage of each test for more details on the required test setup. + +## License + +c-periphery is MIT licensed. See the included [LICENSE](LICENSE) file. + diff --git a/third_party/periphery/docs/gpio.md b/third_party/periphery/docs/gpio.md new file mode 100644 index 000000000..769ca4630 --- /dev/null +++ b/third_party/periphery/docs/gpio.md @@ -0,0 +1,464 @@ +### NAME + +GPIO wrapper functions for Linux userspace character device `gpio-cdev` and sysfs GPIOs. + +Character device GPIOs were introduced in Linux kernel version 4.8. If the toolchain used to compiled c-periphery contains Linux kernel headers older than 4.8 (i.e. `linux/gpio.h` is missing), then only legacy sysfs GPIOs will be supported. + +### SYNOPSIS + +``` c +#include + +/* Primary Functions */ +gpio_t *gpio_new(void); +int gpio_open(gpio_t *gpio, const char *path, unsigned int line, gpio_direction_t direction); +int gpio_open_name(gpio_t *gpio, const char *path, const char *name, gpio_direction_t direction); +int gpio_open_advanced(gpio_t *gpio, const char *path, unsigned int line, const gpio_config_t *config); +int gpio_open_name_advanced(gpio_t *gpio, const char *path, const char *name, const gpio_config_t *config); +int gpio_open_sysfs(gpio_t *gpio, unsigned int line, gpio_direction_t direction); +int gpio_read(gpio_t *gpio, bool *value); +int gpio_write(gpio_t *gpio, bool value); +int gpio_poll(gpio_t *gpio, int timeout_ms); +int gpio_close(gpio_t *gpio); +void gpio_free(gpio_t *gpio); + +/* Read Event (for character device GPIOs) */ +int gpio_read_event(gpio_t *gpio, gpio_edge_t *edge, uint64_t *timestamp); + +/* Poll Multiple */ +int gpio_poll_multiple(gpio_t **gpios, size_t count, int timeout_ms, bool *gpios_ready); + +/* Getters */ +int gpio_get_direction(gpio_t *gpio, gpio_direction_t *direction); +int gpio_get_edge(gpio_t *gpio, gpio_edge_t *edge); +int gpio_get_bias(gpio_t *gpio, gpio_bias_t *bias); +int gpio_get_drive(gpio_t *gpio, gpio_drive_t *drive); +int gpio_get_inverted(gpio_t *gpio, bool *inverted); + +/* Setters */ +int gpio_set_direction(gpio_t *gpio, gpio_direction_t direction); +int gpio_set_edge(gpio_t *gpio, gpio_edge_t edge); +int gpio_set_bias(gpio_t *gpio, gpio_bias_t bias); +int gpio_set_drive(gpio_t *gpio, gpio_drive_t drive); +int gpio_set_inverted(gpio_t *gpio, bool inverted); + +/* Miscellaneous Properties */ +unsigned int gpio_line(gpio_t *gpio); +int gpio_fd(gpio_t *gpio); +int gpio_name(gpio_t *gpio, char *str, size_t len); +int gpio_label(gpio_t *gpio, char *str, size_t len); +int gpio_chip_fd(gpio_t *gpio); +int gpio_chip_name(gpio_t *gpio, char *str, size_t len); +int gpio_chip_label(gpio_t *gpio, char *str, size_t len); +int gpio_tostring(gpio_t *gpio, char *str, size_t len); + +/* Error Handling */ +int gpio_errno(gpio_t *gpio); +const char *gpio_errmsg(gpio_t *gpio); +``` + +### ENUMERATIONS + +* `gpio_direction_t` + * `GPIO_DIR_IN`: Input + * `GPIO_DIR_OUT`: Output, initialized to low + * `GPIO_DIR_OUT_LOW`: Output, initialized to low + * `GPIO_DIR_OUT_HIGH`: Output, initialized to high + +* `gpio_edge_t` + * `GPIO_EDGE_NONE`: No interrupt edge + * `GPIO_EDGE_RISING`: Rising edge (0 -> 1 transition) + * `GPIO_EDGE_FALLING`: Falling edge (1 -> 0 transition) + * `GPIO_EDGE_BOTH`: Both edges (X -> !X transition) + +* `gpio_bias_t` + * `GPIO_BIAS_DEFAULT`: Default line bias + * `GPIO_BIAS_PULL_UP`: Pull-up + * `GPIO_BIAS_PULL_DOWN`: Pull-down + * `GPIO_BIAS_DISABLE`: Disable line bias + +* `gpio_drive_t` + * `GPIO_DRIVE_DEFAULT`: Default line drive (push-pull) + * `GPIO_DRIVE_OPEN_DRAIN`: Open drain + * `GPIO_DRIVE_OPEN_SOURCE`: Open source + +### DESCRIPTION + +``` c +gpio_t *gpio_new(void); +``` +Allocate a GPIO handle. + +Returns a valid handle on success, or NULL on failure. + +------ + +``` c +int gpio_open(gpio_t *gpio, const char *path, unsigned int line, gpio_direction_t direction); +``` +Open the character device GPIO with the specified GPIO line and direction at the specified character device GPIO chip path (e.g. `/dev/gpiochip0`). + +`gpio` should be a valid pointer to an allocated GPIO handle structure. `path` is the GPIO chip character device path. `line` is the GPIO line number. `direction` is one of the direction values enumerated [above](#enumerations). + +Returns 0 on success, or a negative [GPIO error code](#return-value) on failure. + +------ + +``` c +int gpio_open_name(gpio_t *gpio, const char *path, const char *name, gpio_direction_t direction); +``` +Open the character device GPIO with the specified GPIO name and direction at the specified character device GPIO chip path (e.g. `/dev/gpiochip0`). + +`gpio` should be a valid pointer to an allocated GPIO handle structure. `path` is the GPIO chip character device path. `name` is the GPIO line name. `direction` is one of the direction values enumerated [above](#enumerations). + +Returns 0 on success, or a negative [GPIO error code](#return-value) on failure. + +------ + +``` c +typedef struct gpio_config { + gpio_direction_t direction; + gpio_edge_t edge; + gpio_bias_t bias; + gpio_drive_t drive; + bool inverted; + const char *label; +} gpio_config_t; + +int gpio_open_advanced(gpio_t *gpio, const char *path, unsigned int line, const gpio_config_t *config); +``` +Open the character device GPIO with the specified GPIO line and configuration at the specified character device GPIO chip path (e.g. `/dev/gpiochip0`). + +`gpio` should be a valid pointer to an allocated GPIO handle structure. `path` is the GPIO chip character device path. `line` is the GPIO line number. `config` should be a valid pointer to a `gpio_config_t` structure with valid values. `label` can be `NULL` for a default consumer label. + +Returns 0 on success, or a negative [GPIO error code](#return-value) on failure. + +------ + +``` c +typedef struct gpio_config { + gpio_direction_t direction; + gpio_edge_t edge; + gpio_bias_t bias; + gpio_drive_t drive; + bool inverted; + const char *label; +} gpio_config_t; + +int gpio_open_name_advanced(gpio_t *gpio, const char *path, const char *name, const gpio_config_t *config); +``` +Open the character device GPIO with the specified GPIO name and configuration at the specified character device GPIO chip path (e.g. `/dev/gpiochip0`). + +`gpio` should be a valid pointer to an allocated GPIO handle structure. `path` is the GPIO chip character device path. `name` is the GPIO line name. `config` should be a valid pointer to a `gpio_config_t` structure with valid values. `label` can be `NULL` for a default consumer label. + +Returns 0 on success, or a negative [GPIO error code](#return-value) on failure. + +------ + +``` c +int gpio_open_sysfs(gpio_t *gpio, unsigned int line, gpio_direction_t direction); +``` +Open the sysfs GPIO with the specified line and direction. + +`gpio` should be a valid pointer to an allocated GPIO handle structure. `line` is the Linux GPIO line number. `direction` is one of the direction values enumerated [above](#enumerations). + +Returns 0 on success, or a negative [GPIO error code](#return-value) on failure. + +------ + +``` c +int gpio_read(gpio_t *gpio, bool *value); +``` +Read the state of the GPIO into `value`. + +`gpio` should be a valid pointer to a GPIO handle opened with one of the `gpio_open*()` functions. `value` should be a pointer to an allocated bool. + +Returns 0 on success, or a negative [GPIO error code](#return-value) on failure. + +------ + +``` c +int gpio_write(gpio_t *gpio, bool value); +``` +Set the state of the GPIO to `value`. + +`gpio` should be a valid pointer to a GPIO handle opened with one of the `gpio_open*()` functions. + +Returns 0 on success, or a negative [GPIO error code](#return-value) on failure. + +------ + +``` c +int gpio_poll(gpio_t *gpio, int timeout_ms); +``` +Poll a GPIO for the edge event configured with `gpio_set_edge()`. + +For character device GPIOs, the edge event should be consumed with `gpio_read_event()`. For sysfs GPIOs, the edge event should be consumed with `gpio_read()`. + +`gpio` should be a valid pointer to a GPIO handle opened with one of the `gpio_open*()` functions. `timeout_ms` can be positive for a timeout in milliseconds, zero for a non-blocking poll, or negative for a blocking poll. + +Returns 1 on success (an edge event occurred), 0 on timeout, or a negative [GPIO error code](#return-value) on failure. + +------ + +``` c +int gpio_read_event(gpio_t *gpio, gpio_edge_t *edge, uint64_t *timestamp); +``` +Read the edge event that occurred with the GPIO. + +This method is intended for use with character device GPIOs and is unsupported by sysfs GPIOs. + +`gpio` should be a valid pointer to a GPIO handle opened with one of the `gpio_open*()` functions. `timestamp` is event time reported by Linux, in nanoseconds. + +Returns 0 on success, or a negative [GPIO error code](#return-value) on failure. + +------ + +``` c +int gpio_poll_multiple(gpio_t **gpios, size_t count, int timeout_ms, bool *gpios_ready); +``` + +Poll multiple GPIOs for an edge event configured with `gpio_set_edge()`. + +For character device GPIOs, the edge event should be consumed with `gpio_read_event()`. For sysfs GPIOs, the edge event should be consumed with `gpio_read()`. + +`gpios` should be a valid pointer to a size `count` array of GPIO handles opened with one of the `gpio_open*()` functions. `timeout_ms` can be positive for a timeout in milliseconds, zero for a non-blocking poll, or negative for a blocking poll. `gpios_ready` is an optional pointer to a size `count` array of `bool` that will be populated with `true` for the corresponding GPIO in the `gpios` array if an edge event occurred, or `false` if none occurred. + +Returns number of GPIOs for which an edge event occurred, 0 on timeout, or a negative [GPIO error code](#return-value) on failure. + +------ + +``` c +int gpio_close(gpio_t *gpio); +``` +Close the GPIO. + +`gpio` should be a valid pointer to a GPIO handle opened with one of the `gpio_open*()` functions. + +Returns 0 on success, or a negative [GPIO error code](#return-value) on failure. + +------ + +``` c +void gpio_free(gpio_t *gpio); +``` +Free a GPIO handle. + +------ + +```c +int gpio_get_direction(gpio_t *gpio, gpio_direction_t *direction); +int gpio_get_edge(gpio_t *gpio, gpio_edge_t *edge); +int gpio_get_bias(gpio_t *gpio, gpio_bias_t *bias); +int gpio_get_drive(gpio_t *gpio, gpio_drive_t *drive); +int gpio_get_inverted(gpio_t *gpio, bool *inverted); +``` +Get the configured direction, interrupt edge, line bias, line drive, inverted (active low) properties, respectively, of the GPIO. + +Line bias and line drive properties are not supported by sysfs GPIOs. + +`gpio` should be a valid pointer to a GPIO handle opened with one of the `gpio_open*()` functions. + +Returns 0 on success, or a negative [GPIO error code](#return-value) on failure. + +------ + +```c +int gpio_set_direction(gpio_t *gpio, gpio_direction_t direction); +int gpio_set_edge(gpio_t *gpio, gpio_edge_t edge); +int gpio_set_bias(gpio_t *gpio, gpio_bias_t bias); +int gpio_set_drive(gpio_t *gpio, gpio_drive_t drive); +int gpio_set_inverted(gpio_t *gpio, bool inverted); +``` +Set the direction, interrupt edge, line bias, line drive, inverted (active low) properties, respectively, of the GPIO. + +Line bias and line drive properties are not supported by sysfs GPIOs. + +`gpio` should be a valid pointer to a GPIO handle opened with one of the `gpio_open*()` functions. + +Returns 0 on success, or a negative [GPIO error code](#return-value) on failure. + +------ + +``` c +unsigned int gpio_line(gpio_t *gpio); +``` +Return the line the GPIO handle was opened with. + +`gpio` should be a valid pointer to a GPIO handle opened with one of the `gpio_open*()` functions. + +This function is a simple accessor to the GPIO handle structure and always succeeds. + +------ + +``` c +int gpio_fd(gpio_t *gpio); +``` +Return the line file descriptor of the GPIO handle. + +`gpio` should be a valid pointer to a GPIO handle opened with one of the `gpio_open*()` functions. + +This function is a simple accessor to the GPIO handle structure and always succeeds. + +------ + +``` c +int gpio_name(gpio_t *gpio, char *str, size_t len); +``` +Return the line name of the GPIO. + +This method is intended for use with character device GPIOs and always returns the empty string for sysfs GPIOs. + +`gpio` should be a valid pointer to a GPIO handle opened with one of the `gpio_open*()` functions. + +Returns 0 on success, or a negative [GPIO error code](#return-value) on failure. + +------ + +``` c +int gpio_label(gpio_t *gpio, char *str, size_t len); +``` +Return the line consumer label of the GPIO. + +This method is intended for use with character device GPIOs and always returns the empty string for sysfs GPIOs. + +`gpio` should be a valid pointer to a GPIO handle opened with one of the `gpio_open*()` functions. + +Returns 0 on success, or a negative [GPIO error code](#return-value) on failure. + +------ + +``` c +int gpio_chip_fd(gpio_t *gpio); +``` +Return the GPIO chip file descriptor of the GPIO handle. + +This method is intended for use with character device GPIOs and is unsupported by sysfs GPIOs. + +`gpio` should be a valid pointer to a GPIO handled with one of the `gpio_open*()` functions. + +Returns a non-negative file descriptor on success, or a negative [GPIO error code](#return-value) on failure. + +------ + +``` c +int gpio_chip_name(gpio_t *gpio, char *str, size_t len); +``` +Return the name of the GPIO chip associated with the GPIO. + +`gpio` should be a valid pointer to a GPIO handle opened with one of the `gpio_open*()` functions. + +Returns 0 on success, or a negative [GPIO error code](#return-value) on failure. + +------ + +``` c +int gpio_chip_label(gpio_t *gpio, char *str, size_t len); +``` +Return the label of the GPIO chip associated with the GPIO. + +`gpio` should be a valid pointer to a GPIO handle opened with one of the `gpio_open*()` functions. + +Returns 0 on success, or a negative [GPIO error code](#return-value) on failure. + +------ + +``` c +int gpio_tostring(gpio_t *gpio, char *str, size_t len); +``` +Return a string representation of the GPIO handle. + +`gpio` should be a valid pointer to a GPIO handle opened with one of the `gpio_open*()` functions. + +This function behaves and returns like `snprintf()`. + +------ + +``` c +int gpio_errno(gpio_t *gpio); +``` +Return the libc errno of the last failure that occurred. + +`gpio` should be a valid pointer to a GPIO handle opened with one of the `gpio_open*()` functions. + +This function is a simple accessor to the GPIO handle structure and always succeeds. + +------ + +``` c +const char *gpio_errmsg(gpio_t *gpio); +``` +Return a human readable error message of the last failure that occurred. The returned string should not be modified by the application. + +`gpio` should be a valid pointer to a GPIO handle opened with one of the `gpio_open*()` functions. + +This function is a simple accessor to the GPIO handle structure and always succeeds. + +### RETURN VALUE + +The periphery GPIO functions return 0 on success or one of the negative error codes below on failure. + +The libc errno of the failure in an underlying libc library call can be obtained with the `gpio_errno()` helper function. A human readable error message can be obtained with the `gpio_errmsg()` helper function. + +| Error Code | Description | +|-----------------------------------|---------------------------------------| +| `GPIO_ERROR_ARG` | Invalid arguments | +| `GPIO_ERROR_OPEN` | Opening GPIO | +| `GPIO_ERROR_NOT_FOUND` | Line name not found | +| `GPIO_ERROR_QUERY` | Querying GPIO attributes | +| `GPIO_ERROR_CONFIGURE` | Configuring GPIO attributes | +| `GPIO_ERROR_UNSUPPORTED` | Unsupported attribute or operation | +| `GPIO_ERROR_INVALID_OPERATION` | Invalid operation | +| `GPIO_ERROR_IO` | Reading/writing GPIO | +| `GPIO_ERROR_CLOSE` | Closing GPIO | + +### EXAMPLE + +``` c +#include +#include +#include + +#include "gpio.h" + +int main(void) { + gpio_t *gpio_in, *gpio_out; + bool value; + + gpio_in = gpio_new(); + gpio_out = gpio_new(); + + /* Open GPIO /dev/gpiochip0 line 10 with input direction */ + if (gpio_open(gpio_in, "/dev/gpiochip0", 10, GPIO_DIR_IN) < 0) { + fprintf(stderr, "gpio_open(): %s\n", gpio_errmsg(gpio_in)); + exit(1); + } + + /* Open GPIO /dev/gpiochip0 line 12 with output direction */ + if (gpio_open(gpio_out, "/dev/gpiochip0", 12, GPIO_DIR_OUT) < 0) { + fprintf(stderr, "gpio_open(): %s\n", gpio_errmsg(gpio_out)); + exit(1); + } + + /* Read input GPIO into value */ + if (gpio_read(gpio_in, &value) < 0) { + fprintf(stderr, "gpio_read(): %s\n", gpio_errmsg(gpio_in)); + exit(1); + } + + /* Write output GPIO with !value */ + if (gpio_write(gpio_out, !value) < 0) { + fprintf(stderr, "gpio_write(): %s\n", gpio_errmsg(gpio_out)); + exit(1); + } + + gpio_close(gpio_in); + gpio_close(gpio_out); + + gpio_free(gpio_in); + gpio_free(gpio_out); + + return 0; +} +``` + diff --git a/third_party/periphery/docs/i2c.md b/third_party/periphery/docs/i2c.md new file mode 100644 index 000000000..256703294 --- /dev/null +++ b/third_party/periphery/docs/i2c.md @@ -0,0 +1,198 @@ +### NAME + +I2C wrapper functions for Linux userspace `i2c-dev` devices. + +### SYNOPSIS + +``` c +#include + +/* Primary Functions */ +i2c_t *i2c_new(void); +int i2c_open(i2c_t *i2c, const char *device); +int i2c_transfer(i2c_t *i2c, struct i2c_msg *msgs, size_t count); +int i2c_close(i2c_t *i2c); +void i2c_free(i2c_t *i2c); + +/* Miscellaneous */ +int i2c_fd(i2c_t *i2c); +int i2c_tostring(i2c_t *i2c, char *str, size_t len); + +/* Error Handling */ +int i2c_errno(i2c_t *i2c); +const char *i2c_errmsg(i2c_t *i2c); + +/* struct i2c_msg from : + + struct i2c_msg { + __u16 addr; + __u16 flags; + #define I2C_M_TEN 0x0010 + #define I2C_M_RD 0x0001 + #define I2C_M_STOP 0x8000 + #define I2C_M_NOSTART 0x4000 + #define I2C_M_REV_DIR_ADDR 0x2000 + #define I2C_M_IGNORE_NAK 0x1000 + #define I2C_M_NO_RD_ACK 0x0800 + #define I2C_M_RECV_LEN 0x0400 + __u16 len; + __u8 *buf; + }; +*/ +``` + +### DESCRIPTION + +``` c +i2c_t *i2c_new(void); +``` +Allocate an I2C handle. + +Returns a valid handle on success, or NULL on failure. + +------ + +``` c +int i2c_open(i2c_t *i2c, const char *device); +``` +Open the `i2c-dev` device at the specified path (e.g. "/dev/i2c-1"). + +`i2c` should be a valid pointer to an allocated I2C handle structure. + +Returns 0 on success, or a negative [I2C error code](#return-value) on failure. + +------ + +``` c +int i2c_transfer(i2c_t *i2c, struct i2c_msg *msgs, size_t count); +``` +Transfer `count` number of `struct i2c_msg` I2C messages. + +`i2c` should be a valid pointer to an I2C handle opened with `i2c_open()`. `msgs` should be a pointer to an array of `struct i2c_msg` (defined in linux/i2c.h). + +Each I2C message structure (see [above](#synopsis)) specifies the transfer of a consecutive number of bytes to a slave address. The slave address, message flags, buffer length, and pointer to a byte buffer should be specified in each message. The message flags specify whether the message is a read (I2C_M_RD) or write (0) transaction, as well as additional options selected by the bitwise OR of their bitmasks. + +Returns 0 on success, or a negative [I2C error code](#return-value) on failure. + +------ + +``` c +int i2c_close(i2c_t *i2c); +``` +Close the `i2c-dev` device. + +`i2c` should be a valid pointer to an I2C handle opened with `i2c_open()`. + +Returns 0 on success, or a negative [I2C error code](#return-value) on failure. + +------ + +``` c +void i2c_free(i2c_t *i2c); +``` +Free an I2C handle. + +------ + +``` c +int i2c_fd(i2c_t *i2c); +``` +Return the file descriptor (for the underlying `i2c-dev` device) of the I2C handle. + +`i2c` should be a valid pointer to an I2C handle opened with `i2c_open()`. + +This function is a simple accessor to the I2C handle structure and always succeeds. + +------ + +``` c +int i2c_tostring(i2c_t *i2c, char *str, size_t len); +``` +Return a string representation of the I2C handle. + +`i2c` should be a valid pointer to an I2C handle opened with `i2c_open()`. + +This function behaves and returns like `snprintf()`. + +------ + +``` c +int i2c_errno(i2c_t *i2c); +``` +Return the libc errno of the last failure that occurred. + +`i2c` should be a valid pointer to an I2C handle opened with `i2c_open()`. + +------ + +``` c +const char *i2c_errmsg(i2c_t *i2c); +``` +Return a human readable error message of the last failure that occurred. + +`i2c` should be a valid pointer to an I2C handle opened with `i2c_open()`. + +### RETURN VALUE + +The periphery I2C functions return 0 on success or one of the negative error codes below on failure. + +The libc errno of the failure in an underlying libc library call can be obtained with the `i2c_errno()` helper function. A human readable error message can be obtained with the `i2c_errmsg()` helper function. + +| Error Code | Description | +|---------------------------|-----------------------------------| +| `I2C_ERROR_ARG` | Invalid arguments | +| `I2C_ERROR_OPEN` | Opening I2C device | +| `I2C_ERROR_QUERY` | Querying I2C device attribtues | +| `I2C_ERROR_NOT_SUPPORTED` | I2C not supported on this device | +| `I2C_ERROR_TRANSFER` | I2C transfer | +| `I2C_ERROR_CLOSE` | Closing I2C device | + +### EXAMPLE + +``` c +#include +#include +#include + +#include "i2c.h" + +#define EEPROM_I2C_ADDR 0x50 + +int main(void) { + i2c_t *i2c; + + i2c = i2c_new(); + + /* Open the i2c-0 bus */ + if (i2c_open(i2c, "/dev/i2c-0") < 0) { + fprintf(stderr, "i2c_open(): %s\n", i2c_errmsg(i2c)); + exit(1); + } + + /* Read byte at address 0x100 of EEPROM */ + uint8_t msg_addr[2] = { 0x01, 0x00 }; + uint8_t msg_data[1] = { 0xff, }; + struct i2c_msg msgs[2] = + { + /* Write 16-bit address */ + { .addr = EEPROM_I2C_ADDR, .flags = 0, .len = 2, .buf = msg_addr }, + /* Read 8-bit data */ + { .addr = EEPROM_I2C_ADDR, .flags = I2C_M_RD, .len = 1, .buf = msg_data}, + }; + + /* Transfer a transaction with two I2C messages */ + if (i2c_transfer(i2c, msgs, 2) < 0) { + fprintf(stderr, "i2c_transfer(): %s\n", i2c_errmsg(i2c)); + exit(1); + } + + printf("0x%02x%02x: %02x\n", msg_addr[0], msg_addr[1], msg_data[0]); + + i2c_close(i2c); + + i2c_free(i2c); + + return 0; +} +``` + diff --git a/third_party/periphery/docs/led.md b/third_party/periphery/docs/led.md new file mode 100644 index 000000000..9970a61eb --- /dev/null +++ b/third_party/periphery/docs/led.md @@ -0,0 +1,227 @@ +### NAME + +LED wrapper functions for Linux userspace sysfs LEDs. + +### SYNOPSIS + +``` c +#include + +/* Primary Functions */ +led_t *led_new(void); +int led_open(led_t *led, const char *name); +int led_read(led_t *led, bool *value); +int led_write(led_t *led, bool value); +int led_close(led_t *led); +void led_free(led_t *led); + +/* Getters */ +int led_get_brightness(led_t *led, unsigned int *brightness); +int led_get_max_brightness(led_t *led, unsigned int *max_brightness); + +/* Setters */ +int led_set_brightness(led_t *led, unsigned int brightness); + +/* Miscellaneous */ +int led_name(led_t *led, char *str, size_t len); +int led_tostring(led_t *led, char *str, size_t len); + +/* Error Handling */ +int led_errno(led_t *led); +const char *led_errmsg(led_t *led); +``` + +### DESCRIPTION + +``` c +led_t *led_new(void); +``` +Allocate an LED handle. + +Returns a valid handle on success, or NULL on failure. + +------ + +``` c +int led_open(led_t *led, const char *name); +``` +Open the sysfs LED with the specified name. + +`led` should be a valid pointer to an allocated LED handle structure. + +Returns 0 on success, or a negative [LED error code](#return-value) on failure. + +------ + +``` c +int led_read(led_t *led, bool *value); +``` +Read the state of the LED into `value`, where `true` is non-zero brightness, and `false` is zero brightness. + +`led` should be a valid pointer to an LED handle opened with `led_open()`. `value` should be a pointer to an allocated bool. + +Returns 0 on success, or a negative [LED error code](#return-value) on failure. + +------ + +``` c +int led_write(led_t *led, bool value); +``` +Write the state of the LED to `value`, where `true` is max brightness, and `false` is zero brightness. + +`led` should be a valid pointer to an LED handle opened with `led_open()`. + +Returns 0 on success, or a negative [LED error code](#return-value) on failure. + +------ + +``` c +int led_close(led_t *led); +``` +Close the LED. + +`led` should be a valid pointer to an LED handle opened with `led_open()`. + +Returns 0 on success, or a negative [LED error code](#return-value) on failure. + +------ + +``` c +void led_free(led_t *led); +``` +Free an LED handle. + +------ + +``` c +int led_get_brightness(led_t *led, unsigned int *brightness); +``` +Get the brightness of the LED. + +`led` should be a valid pointer to an LED handle opened with `led_open()`. + +Returns 0 on success, or a negative [LED error code](#return-value) on failure. + +------ + +``` c +int led_get_max_brightness(led_t *led, unsigned int *max_brightness); +``` +Get the max brightness of the LED. + +`led` should be a valid pointer to an LED handle opened with `led_open()`. + +Returns 0 on success, or a negative [LED error code](#return-value) on failure. + +------ + +``` c +int led_set_brightness(led_t *led, unsigned int brightness); +``` +Set the brightness of the LED. + +`led` should be a valid pointer to an LED handle opened with `led_open()`. + +Returns 0 on success, or a negative [LED error code](#return-value) on failure. + +------ + +``` c +int led_name(led_t *led, char *str, size_t len); +``` +Return the name of the sysfs LED. + +`led` should be a valid pointer to an LED handle opened with `led_open()`. + +Returns 0 on success, or a negative [LED error code](#return-value) on failure. + +------ + +``` c +int led_tostring(led_t *led, char *str, size_t len); +``` +Return a string representation of the LED handle. + +`led` should be a valid pointer to an LED handle opened with `led_open()`. + +This function behaves and returns like `snprintf()`. + +------ + +``` c +int led_errno(led_t *led); +``` +Return the libc errno of the last failure that occurred. + +`led` should be a valid pointer to an LED handle opened with `led_open()`. + +------ + +``` c +const char *led_errmsg(led_t *led); +``` +Return a human readable error message of the last failure that occurred. + +`led` should be a valid pointer to an LED handle opened with `led_open()`. + +### RETURN VALUE + +The periphery LED functions return 0 on success or one of the negative error codes below on failure. + +The libc errno of the failure in an underlying libc library call can be obtained with the `led_errno()` helper function. A human readable error message can be obtained with the `led_errmsg()` helper function. + +| Error Code | Description | +|-----------------------|-----------------------------------| +| `LED_ERROR_ARG` | Invalid arguments | +| `LED_ERROR_OPEN` | Opening LED | +| `LED_ERROR_QUERY` | Querying LED attributes | +| `LED_ERROR_IO` | Reading/writing LED brightness | +| `LED_ERROR_CLOSE` | Closing LED | + +### EXAMPLE + +``` c +#include +#include +#include + +#include "led.h" + +int main(void) { + led_t *led; + unsigned int max_brightness; + + led = led_new(); + + /* Open LED led0 */ + if (led_open(led, "led0") < 0) { + fprintf(stderr, "led_open(): %s\n", led_errmsg(led)); + exit(1); + } + + /* Turn on LED (set max brightness) */ + if (led_write(led, true) < 0) { + fprintf(stderr, "led_write(): %s\n", led_errmsg(led)); + exit(1); + } + + /* Get max brightness */ + if (led_get_max_brightness(led, &max_brightness) < 0) { + fprintf(stderr, "led_get_max_brightness(): %s\n", led_errmsg(led)); + exit(1); + } + + /* Set half brightness */ + if (led_set_brightness(led, max_brightness / 2) < 0) { + fprintf(stderr, "led_set_brightness(): %s\n", led_errmsg(led)); + exit(1); + } + + led_close(led); + + led_free(led); + + return 0; +} +``` + diff --git a/third_party/periphery/docs/mmio.md b/third_party/periphery/docs/mmio.md new file mode 100644 index 000000000..49542f359 --- /dev/null +++ b/third_party/periphery/docs/mmio.md @@ -0,0 +1,248 @@ +### NAME + +MMIO wrapper functions for the Linux userspace `/dev/mem` device. + +### SYNOPSIS + +``` c +#include + +/* Primary Functions */ +mmio_t *mmio_new(void); +int mmio_open(mmio_t *mmio, uintptr_t base, size_t size); +int mmio_open_advanced(mmio_t *mmio, uintptr_t base, size_t size, const char *path); +void *mmio_ptr(mmio_t *mmio); +int mmio_read32(mmio_t *mmio, uintptr_t offset, uint32_t *value); +int mmio_read16(mmio_t *mmio, uintptr_t offset, uint16_t *value); +int mmio_read8(mmio_t *mmio, uintptr_t offset, uint8_t *value); +int mmio_read(mmio_t *mmio, uintptr_t offset, uint8_t *buf, size_t len); +int mmio_write32(mmio_t *mmio, uintptr_t offset, uint32_t value); +int mmio_write16(mmio_t *mmio, uintptr_t offset, uint16_t value); +int mmio_write8(mmio_t *mmio, uintptr_t offset, uint8_t value); +int mmio_write(mmio_t *mmio, uintptr_t offset, const uint8_t *buf, size_t len); +int mmio_close(mmio_t *mmio); +void mmio_free(mmio_t *mmio); + +/* Miscellaneous */ +uintptr_t mmio_base(mmio_t *mmio); +size_t mmio_size(mmio_t *mmio); +int mmio_tostring(mmio_t *mmio, char *str, size_t len); + +/* Error Handling */ +int mmio_errno(mmio_t *mmio); +const char *mmio_errmsg(mmio_t *mmio); +``` + +### DESCRIPTION + +``` c +mmio_t *mmio_new(void); +``` +Allocate a MMIO handle. + +Returns a valid handle on success, or NULL on failure. + +------ + +``` c +int mmio_open(mmio_t *mmio, uintptr_t base, size_t size); +``` +Map the region of physical memory specified by the `base` physical address and `size` size in bytes, using the default `/dev/mem` memory character device. + +`mmio` should be a valid pointer to an allocated MMIO handle structure. Neither `base` nor `size` need be aligned to a page boundary. + +Returns 0 on success, or a negative [MMIO error code](#return-value) on failure. + +------ + +``` c +int mmio_open_advanced(mmio_t *mmio, uintptr_t base, size_t size, const char *path); +``` +Map the region of physical memory specified by the `base` physical address and `size` size in bytes, using the specified memory character device. This open function can be used with sandboxed memory character devices, e.g. `/dev/gpiomem`. + +`mmio` should be a valid pointer to an allocated MMIO handle structure. Neither `base` nor `size` need be aligned to a page boundary. + +Returns 0 on success, or a negative [MMIO error code](#return-value) on failure. + +------ + +``` c +void *mmio_ptr(mmio_t *mmio); +``` +Return the pointer to the mapped physical memory. + +This function is a simple accessor to the MMIO handle structure and always succeeds. + +------ + +``` c +int mmio_read32(mmio_t *mmio, uintptr_t offset, uint32_t *value); +int mmio_read16(mmio_t *mmio, uintptr_t offset, uint16_t *value); +int mmio_read8(mmio_t *mmio, uintptr_t offset, uint8_t *value); +int mmio_read(mmio_t *mmio, uintptr_t offset, uint8_t *buf, size_t len); +``` +Read 32-bits, 16-bits, 8-bits, or an array of bytes, respectively, from mapped physical memory, starting at the specified byte offset, relative to the base address the MMIO handle was opened with. + +`mmio` should be a valid pointer to an MMIO handle opened with one of the `mmio_open*()` functions. + +Returns 0 on success, or a negative [MMIO error code](#return-value) on failure. + +------ + +``` c +int mmio_write32(mmio_t *mmio, uintptr_t offset, uint32_t value); +int mmio_write16(mmio_t *mmio, uintptr_t offset, uint16_t value); +int mmio_write8(mmio_t *mmio, uintptr_t offset, uint8_t value); +int mmio_write(mmio_t *mmio, uintptr_t offset, const uint8_t *buf, size_t len); +``` +Write 32-bits, 16-bits, 8-bits, or an array of bytes, respectively, to mapped physical memory, starting at the specified byte offset, relative to the base address the MMIO handle was opened with. + +`mmio` should be a valid pointer to an MMIO handle opened with one of the `mmio_open*()` functions. + +Returns 0 on success, or a negative [MMIO error code](#return-value) on failure. + +------ + +``` c +int mmio_close(mmio_t *mmio); +``` +Unmap mapped physical memory. + +`mmio` should be a valid pointer to an MMIO handle opened with one of the `mmio_open*()` functions. + +Returns 0 on success, or a negative [MMIO error code](#return-value) on failure. + +------ + +``` c +void mmio_free(mmio_t *mmio); +``` +Free a MMIO handle. + +------ + +``` c +uintptr_t mmio_base(mmio_t *mmio); +``` +Return the base address the MMIO handle was opened with. + +`mmio` should be a valid pointer to an MMIO handle opened with one of the `mmio_open*()` functions. + +This function is a simple accessor to the MMIO handle structure and always succeeds. + +------ + +``` c +size_t mmio_size(mmio_t *mmio); +``` +Return the size the MMIO handle was opened with. + +`mmio` should be a valid pointer to an MMIO handle opened with one of the `mmio_open*()` functions. + +This function is a simple accessor to the MMIO handle structure and always succeeds. + +------ + +``` c +int mmio_tostring(mmio_t *mmio, char *str, size_t len); +``` +Return a string representation of the MMIO handle. + +`mmio` should be a valid pointer to an MMIO handle opened with one of the `mmio_open*()` functions. + +This function behaves and returns like `snprintf()`. + +------ + +``` c +int mmio_errno(mmio_t *mmio); +``` +Return the libc errno of the last failure that occurred. + +`mmio` should be a valid pointer to an MMIO handle opened with one of the `mmio_open*()` functions. + +------ + +``` c +const char *mmio_errmsg(mmio_t *mmio); +``` +Return a human readable error message of the last failure that occurred. + +`mmio` should be a valid pointer to an MMIO handle opened with one of the `mmio_open*()` functions. + +### RETURN VALUE + +The periphery MMIO functions return 0 on success or one of the negative error codes below on failure. + +The libc errno of the failure in an underlying libc library call can be obtained with the `mmio_errno()` helper function. A human readable error message can be obtained with the `mmio_errmsg()` helper function. + +| Error Code | Description | +|-----------------------|-----------------------| +| `MMIO_ERROR_ARG` | Invalid arguments | +| `MMIO_ERROR_OPEN` | Opening MMIO | +| `MMIO_ERROR_CLOSE` | Closing MMIO | + +### EXAMPLE + +``` c +#include +#include +#include + +#include "mmio.h" + +struct am335x_rtcss_registers { + uint32_t seconds; /* 0x00 */ + uint32_t minutes; /* 0x04 */ + uint32_t hours; /* 0x08 */ + /* ... */ +}; + +int main(void) { + mmio_t *mmio; + uint32_t mac_id0_lo, mac_id0_hi; + volatile struct am335x_rtcss_registers *regs; + + mmio = mmio_new(); + + /* Open control module */ + if (mmio_open(mmio, 0x44E10000, 0x1000) < 0) { + fprintf(stderr, "mmio_open(): %s\n", mmio_errmsg(mmio)); + exit(1); + } + + /* Read lower 2 bytes of MAC address */ + if (mmio_read32(mmio, 0x630, &mac_id0_lo) < 0) { + fprintf(stderr, "mmio_read32(): %s\n", mmio_errmsg(mmio)); + exit(1); + } + + /* Read upper 4 bytes of MAC address */ + if (mmio_read32(mmio, 0x634, &mac_id0_hi) < 0) { + fprintf(stderr, "mmio_read32(): %s\n", mmio_errmsg(mmio)); + exit(1); + } + + printf("MAC address: %04x%08x\n", mac_id0_lo, mac_id0_hi); + + mmio_close(mmio); + + /* Open RTC subsystem */ + if (mmio_open(mmio, 0x44E3E000, 0x1000) < 0) { + fprintf(stderr, "mmio_open(): %s\n", mmio_errmsg(mmio)); + exit(1); + } + + regs = mmio_ptr(mmio); + + /* Read current RTC time */ + printf("hours: %02x minutes: %02x seconds %02x\n", regs->hours, regs->minutes, regs->seconds); + + mmio_close(mmio); + + mmio_free(mmio); + + return 0; +} +``` + diff --git a/third_party/periphery/docs/pwm.md b/third_party/periphery/docs/pwm.md new file mode 100644 index 000000000..70ac9fa61 --- /dev/null +++ b/third_party/periphery/docs/pwm.md @@ -0,0 +1,321 @@ +### NAME + +PWM wrapper functions for Linux userspace sysfs PWMs. + +### SYNOPSIS + +``` c +#include + +/* Primary Functions */ +pwm_t *pwm_new(void); +int pwm_open(pwm_t *pwm, unsigned int chip, unsigned int channel); +int pwm_enable(pwm_t *pwm); +int pwm_disable(pwm_t *pwm); +int pwm_close(pwm_t *pwm); +void pwm_free(pwm_t *pwm); + +/* Getters */ +int pwm_get_enabled(pwm_t *pwm, bool *enabled); +int pwm_get_period_ns(pwm_t *pwm, uint64_t *period_ns); +int pwm_get_duty_cycle_ns(pwm_t *pwm, uint64_t *duty_cycle_ns); +int pwm_get_period(pwm_t *pwm, double *period); +int pwm_get_duty_cycle(pwm_t *pwm, double *duty_cycle); +int pwm_get_frequency(pwm_t *pwm, double *frequency); +int pwm_get_polarity(pwm_t *pwm, pwm_polarity_t *polarity); + +/* Setters */ +int pwm_set_enabled(pwm_t *pwm, bool enabled); +int pwm_set_period_ns(pwm_t *pwm, uint64_t period_ns); +int pwm_set_duty_cycle_ns(pwm_t *pwm, uint64_t duty_cycle_ns); +int pwm_set_period(pwm_t *pwm, double period); +int pwm_set_duty_cycle(pwm_t *pwm, double duty_cycle); +int pwm_set_frequency(pwm_t *pwm, double frequency); +int pwm_set_polarity(pwm_t *pwm, pwm_polarity_t polarity); + +/* Miscellaneous */ +unsigned int pwm_chip(pwm_t *pwm); +unsigned int pwm_channel(pwm_t *pwm); +int pwm_tostring(pwm_t *pwm, char *str, size_t len); + +/* Error Handling */ +int pwm_errno(pwm_t *pwm); +const char *pwm_errmsg(pwm_t *pwm); +``` + +### ENUMERATIONS + +* `pwm_polarity_t` + * `PWM_POLARITY_NORMAL`: Normal polarity + * `PWM_POLARITY_INVERSED`: Inversed polarity + +### DESCRIPTION + +``` c +pwm_t *pwm_new(void); +``` +Allocate a PWM handle. + +Returns a valid handle on success, or NULL on failure. + +------ + +``` c +int pwm_open(pwm_t *pwm, unsigned int chip, unsigned int channel); +``` +Open the sysfs PWM with the specified chip and channel. + +`pwm` should be a valid pointer to an allocated PWM handle structure. + +Returns 0 on success, or a negative [PWM error code](#return-value) on failure. + +------ + +``` c +int pwm_enable(pwm_t *pwm); +``` +Enable the PWM output. + +`pwm` should be a valid pointer to a PWM handle opened with `pwm_open()`. + +Returns 0 on success, or a negative [PWM error code](#return-value) on failure. + +------ + +``` c +int pwm_disable(pwm_t *pwm); +``` +Disable the PWM output. + +`pwm` should be a valid pointer to a PWM handle opened with `pwm_open()`. + +Returns 0 on success, or a negative [PWM error code](#return-value) on failure. + +------ + +``` c +int pwm_close(pwm_t *pwm); +``` +Close the PWM. + +`pwm` should be a valid pointer to a PWM handle opened with `pwm_open()`. + +Returns 0 on success, or a negative [PWM error code](#return-value) on failure. + +------ + +``` c +void pwm_free(pwm_t *pwm); +``` +Free a PWM handle. + +------ + +``` c +int pwm_get_enabled(pwm_t *pwm, bool *enabled); +``` +Get the output state of the PWM. + +`pwm` should be a valid pointer to a PWM handle opened with `pwm_open()`. + +Returns 0 on success, or a negative [PWM error code](#return-value) on failure. + +------ + +``` c +int pwm_get_period_ns(pwm_t *pwm, uint64_t *period_ns); +int pwm_get_duty_cycle_ns(pwm_t *pwm, uint64_t *duty_cycle_ns); +``` +Get the period in nanoseconds or duty cycle in nanoseconds, respectively, of the PWM. + +`pwm` should be a valid pointer to a PWM handle opened with `pwm_open()`. + +Returns 0 on success, or a negative [PWM error code](#return-value) on failure. + +------ + +``` c +int pwm_get_period(pwm_t *pwm, double *period); +int pwm_get_duty_cycle(pwm_t *pwm, double *duty_cycle); +int pwm_get_frequency(pwm_t *pwm, double *frequency); +``` +Get the period in seconds, duty cycle as a ratio between 0.0 to 1.0, or frequency in Hz, respectively, of the PWM. + +`pwm` should be a valid pointer to a PWM handle opened with `pwm_open()`. + +Returns 0 on success, or a negative [PWM error code](#return-value) on failure. + +------ + +``` c +int pwm_get_polarity(pwm_t *pwm, pwm_polarity_t *polarity); +``` +Get the output polarity of the PWM. + +`pwm` should be a valid pointer to a PWM handle opened with `pwm_open()`. + +Returns 0 on success, or a negative [PWM error code](#return-value) on failure. + +------ + +``` c +int pwm_set_enabled(pwm_t *pwm, bool enabled); +``` +Set the output state of the PWM. + +`pwm` should be a valid pointer to a PWM handle opened with `pwm_open()`. + +Returns 0 on success, or a negative [PWM error code](#return-value) on failure. + +------ + +``` c +int pwm_set_period_ns(pwm_t *pwm, uint64_t period_ns); +int pwm_set_duty_cycle_ns(pwm_t *pwm, uint64_t duty_cycle_ns); +``` +Set the period in nanoseconds or duty cycle in nanoseconds, respectively, of the PWM. + +`pwm` should be a valid pointer to a PWM handle opened with `pwm_open()`. + +Returns 0 on success, or a negative [PWM error code](#return-value) on failure. + +------ + +``` c +int pwm_set_period(pwm_t *pwm, double period); +int pwm_set_duty_cycle(pwm_t *pwm, double duty_cycle); +int pwm_set_frequency(pwm_t *pwm, double frequency); +``` +Set the period in seconds, duty cycle as a ratio between 0.0 to 1.0, or frequency in Hz, respectively, of the PWM. + +`pwm` should be a valid pointer to a PWM handle opened with `pwm_open()`. + +Returns 0 on success, or a negative [PWM error code](#return-value) on failure. + +------ + +``` c +int pwm_set_polarity(pwm_t *pwm, pwm_polarity_t *polarity); +``` +Set the output polarity of the PWM. + +`pwm` should be a valid pointer to a PWM handle opened with `pwm_open()`. + +Returns 0 on success, or a negative [PWM error code](#return-value) on failure. + +------ + +``` c +unsigned int pwm_chip(pwm_t *pwm); +``` +Return the chip number of the PWM handle. + +`pwm` should be a valid pointer to a PWM handle opened with `pwm_open()`. + +This function is a simple accessor to the PWM handle structure and always succeeds. + +------ + +``` c +unsigned int pwm_channel(pwm_t *pwm); +``` +Return the channel number of the PWM handle. + +`pwm` should be a valid pointer to a PWM handle opened with `pwm_open()`. + +This function is a simple accessor to the PWM handle structure and always succeeds. + +------ + +``` c +int pwm_tostring(pwm_t *pwm, char *str, size_t len); +``` +Return a string representation of the PWM handle. + +`pwm` should be a valid pointer to a PWM handle opened with `pwm_open()`. + +This function behaves and returns like `snprintf()`. + +------ + +``` c +int pwm_errno(pwm_t *pwm); +``` +Return the libc errno of the last failure that occurred. + +`pwm` should be a valid pointer to a PWM handle opened with `pwm_open()`. + +------ + +``` c +const char *pwm_errmsg(pwm_t *pwm); +``` +Return a human readable error message of the last failure that occurred. + +`pwm` should be a valid pointer to a PWM handle opened with `pwm_open()`. + +### RETURN VALUE + +The periphery PWM functions return 0 on success or one of the negative error codes below on failure. + +The libc errno of the failure in an underlying libc library call can be obtained with the `pwm_errno()` helper function. A human readable error message can be obtained with the `pwm_errmsg()` helper function. + +| Error Code | Description | +|-----------------------|-----------------------------------| +| `PWM_ERROR_ARG` | Invalid arguments | +| `PWM_ERROR_OPEN` | Opening PWM | +| `PWM_ERROR_QUERY` | Querying PWM attributes | +| `PWM_ERROR_CONFIGURE` | Configuring PWM attributes | +| `PWM_ERROR_CLOSE` | Closing PWM | + +### EXAMPLE + +``` c +#include +#include + +#include "pwm.h" + +int main(void) { + pwm_t *pwm; + + pwm = pwm_new(); + + /* Open PWM chip 0, channel 10 */ + if (pwm_open(pwm, 0, 10) < 0) { + fprintf(stderr, "pwm_open(): %s\n", pwm_errmsg(pwm)); + exit(1); + } + + /* Set frequency to 1 kHz */ + if (pwm_set_frequency(pwm, 1e3) < 0) { + fprintf(stderr, "pwm_set_frequency(): %s\n", pwm_errmsg(pwm)); + exit(1); + } + + /* Set duty cycle to 75% */ + if (pwm_set_duty_cycle(pwm, 0.75) < 0) { + fprintf(stderr, "pwm_set_duty_cycle(): %s\n", pwm_errmsg(pwm)); + exit(1); + } + + /* Enable PWM */ + if (pwm_enable(pwm) < 0) { + fprintf(stderr, "pwm_enable(): %s\n", pwm_errmsg(pwm)); + exit(1); + } + + /* Change duty cycle to 50% */ + if (pwm_set_duty_cycle(pwm, 0.50) < 0) { + fprintf(stderr, "pwm_set_duty_cycle(): %s\n", pwm_errmsg(pwm)); + exit(1); + } + + pwm_close(pwm); + + pwm_free(pwm); + + return 0; +} +``` + diff --git a/third_party/periphery/docs/serial.md b/third_party/periphery/docs/serial.md new file mode 100644 index 000000000..1b5bc0bd2 --- /dev/null +++ b/third_party/periphery/docs/serial.md @@ -0,0 +1,326 @@ +### NAME + +Serial wrapper functions for Linux userspace termios `tty` devices. + +### SYNOPSIS + +``` c +#include + +/* Primary Functions */ +serial_t *serial_new(void); +int serial_open(serial_t *serial, const char *path, uint32_t baudrate); +int serial_open_advanced(serial_t *serial, const char *path, uint32_t baudrate, + unsigned int databits, serial_parity_t parity, + unsigned int stopbits, bool xonxoff, bool rtscts); +int serial_read(serial_t *serial, uint8_t *buf, size_t len, int timeout_ms); +int serial_write(serial_t *serial, const uint8_t *buf, size_t len); +int serial_flush(serial_t *serial); +int serial_input_waiting(serial_t *serial, unsigned int *count); +int serial_output_waiting(serial_t *serial, unsigned int *count); +int serial_poll(serial_t *serial, int timeout_ms); +int serial_close(serial_t *serial); +void serial_free(serial_t *serial); + +/* Getters */ +int serial_get_baudrate(serial_t *serial, uint32_t *baudrate); +int serial_get_databits(serial_t *serial, unsigned int *databits); +int serial_get_parity(serial_t *serial, serial_parity_t *parity); +int serial_get_stopbits(serial_t *serial, unsigned int *stopbits); +int serial_get_xonxoff(serial_t *serial, bool *xonxoff); +int serial_get_rtscts(serial_t *serial, bool *rtscts); + +/* Setters */ +int serial_set_baudrate(serial_t *serial, uint32_t baudrate); +int serial_set_databits(serial_t *serial, unsigned int databits); +int serial_set_parity(serial_t *serial, enum serial_parity parity); +int serial_set_stopbits(serial_t *serial, unsigned int stopbits); +int serial_set_xonxoff(serial_t *serial, bool enabled); +int serial_set_rtscts(serial_t *serial, bool enabled); + +/* Miscellaneous */ +int serial_fd(serial_t *serial); +int serial_tostring(serial_t *serial, char *str, size_t len); + +/* Error Handling */ +int serial_errno(serial_t *serial); +const char *serial_errmsg(serial_t *serial); +``` + +### ENUMERATIONS + +* `serial_parity_t` + * `PARITY_NONE`: No parity + * `PARITY_ODD`: Odd parity + * `PARITY_EVEN`: Even parity + +### DESCRIPTION + +``` c +serial_t *serial_new(void); +``` +Allocate a Serial handle. + +Returns a valid handle on success, or NULL on failure. + +------ + +``` c +int serial_open(serial_t *serial, const char *path, uint32_t baudrate); +``` +Open the `tty` device at the specified path (e.g. "/dev/ttyUSB0"), with the specified baudrate, and the defaults of 8 data bits, no parity, 1 stop bit, software flow control (xonxoff) off, hardware flow control (rtscts) off. + +`serial` should be a valid pointer to an allocated Serial handle structure. + +Returns 0 on success, or a negative [Serial error code](#return-value) on failure. + +------ + +``` c +int serial_open_advanced(serial_t *serial, const char *path, uint32_t baudrate, + unsigned int databits, serial_parity_t parity, + unsigned int stopbits, bool xonxoff, bool rtscts); +``` +Open the `tty` device at the specified path (e.g. "/dev/ttyUSB0"), with the specified baudrate, data bits, parity, stop bits, software flow control (xonxoff), and hardware flow control (rtscts) settings. + +`serial` should be a valid pointer to an allocated Serial handle structure. `databits` can be 5, 6, 7, or 8. `parity` can be `PARITY_NONE`, `PARITY_ODD`, or `PARITY_EVEN` as defined [above](#enumerations). `stopbits` can be 1 or 2. + +Returns 0 on success, or a negative [Serial error code](#return-value) on failure. + +------ + +``` c +int serial_read(serial_t *serial, uint8_t *buf, size_t len, int timeout_ms); +``` +Read up to `len` number of bytes from the serial port into the `buf` buffer with the specified millisecond timeout. `timeout_ms` can be positive for a blocking read with a timeout in milliseconds, zero for a non-blocking read, or negative for a blocking read that will block until `length` number of bytes are read. + +For a non-blocking or timeout-bound read, `serial_read()` may return less than the requested number of bytes. + +For a blocking read with the VMIN setting configured, `serial_read()` will block until at least VMIN bytes are read. For a blocking read with both VMIN and VTIME settings configured, `serial_read()` will block until at least VMIN bytes are read or the VTIME interbyte timeout expires after the last byte read. In either case, `serial_read()` may return less than the requested number of bytes. + +`serial` should be a valid pointer to a Serial handle opened with `serial_open()` or `serial_open_advanced()`. `timeout_ms` can be positive for a blocking read with a timeout in milliseconds, zero for a non-blocking read, or negative for a blocking read. + +Returns the number of bytes read on success, 0 on timeout, or a negative [Serial error code](#return-value) on failure. + +------ + +``` c +int serial_write(serial_t *serial, const uint8_t *buf, size_t len); +``` +Write `len` number of bytes from the `buf` buffer to the serial port. + +`serial` should be a valid pointer to a Serial handle opened with `serial_open()` or `serial_open_advanced()`. + +Returns the number of bytes written on success, or a negative [Serial error code](#return-value) on failure. + +------ + +``` c +int serial_flush(serial_t *serial); +``` +Flush the write buffer of the serial port (i.e. force its write immediately). + +`serial` should be a valid pointer to a Serial handle opened with `serial_open()` or `serial_open_advanced()`. + +Returns 0 on success, or a negative [Serial error code](#return-value) on failure. + +------ + +``` c +int serial_input_waiting(serial_t *serial, unsigned int *count); +``` +Get the number of bytes waiting to be read from the serial port. + +`serial` should be a valid pointer to a Serial handle opened with `serial_open()` or `serial_open_advanced()`. + +Returns 0 on success, or a negative [Serial error code](#return-value) on failure. + +------ + +``` c +int serial_output_waiting(serial_t *serial, unsigned int *count); +``` +Get the number of bytes waiting to be written to the serial port. + +`serial` should be a valid pointer to a Serial handle opened with `serial_open()` or `serial_open_advanced()`. + +Returns 0 on success, or a negative [Serial error code](#return-value) on failure. + +------ + +``` c +bool serial_poll(serial_t *serial, int timeout_ms); +``` +Poll for data available for reading from the serial port. + +`serial` should be a valid pointer to a Serial handle opened with `serial_open()` or `serial_open_advanced()`. `timeout_ms` can be positive for a timeout in milliseconds, zero for a non-blocking poll, or negative for a blocking poll. + +Returns 1 on success (data available for reading), 0 on timeout, or a negative [Serial error code](#return-value) on failure. + +------ + +``` c +int serial_close(serial_t *serial); +``` +Close the `tty` device. + +`serial` should be a valid pointer to a Serial handle opened with `serial_open()` or `serial_open_advanced()`. + +Returns 0 on success, or a negative [Serial error code](#return-value) on failure. + +------ + +``` c +void serial_free(serial_t *serial); +``` +Free a Serial handle. + +------ + +``` c +int serial_get_baudrate(serial_t *serial, uint32_t *baudrate); +int serial_get_databits(serial_t *serial, unsigned int *databits); +int serial_get_parity(serial_t *serial, serial_parity_t *parity); +int serial_get_stopbits(serial_t *serial, unsigned int *stopbits); +int serial_get_xonxoff(serial_t *serial, bool *xonxoff); +int serial_get_rtscts(serial_t *serial, bool *rtscts); +``` +Get the baudrate, data bits, parity, stop bits, software flow control (xonxoff), or hardware flow control (rtscts), respectively, of the underlying `tty` device. + +`serial` should be a valid pointer to a Serial handle opened with `serial_open()` or `serial_open_advanced()`. + +Returns 0 on success, or a negative [Serial error code](#return-value) on failure. + +------ + +``` c +int serial_set_baudrate(serial_t *serial, uint32_t baudrate); +int serial_set_databits(serial_t *serial, unsigned int databits); +int serial_set_parity(serial_t *serial, enum serial_parity parity); +int serial_set_stopbits(serial_t *serial, unsigned int stopbits); +int serial_set_xonxoff(serial_t *serial, bool enabled); +int serial_set_rtscts(serial_t *serial, bool enabled); +``` +Set the baudrate, data bits, parity, stop bits, software flow control (xonxoff), or hardware flow control (rtscts), respectively, on the underlying `tty` device. + +`serial` should be a valid pointer to a Serial handle opened with `serial_open()` or `serial_open_advanced()`. + +Returns 0 on success, or a negative [Serial error code](#return-value) on failure. + +------ + +``` c +int serial_get_vmin(serial_t *serial, unsigned int *vmin); +int serial_get_vtime(serial_t *serial, float *vtime); +int serial_set_vmin(serial_t *serial, unsigned int vmin); +int serial_set_vtime(serial_t *serial, float vtime); +``` +Get or set the termios VMIN and VTIME settings, respectively, of the underlying `tty` device. + +VMIN specifies the minimum number of bytes returned from a blocking read. VTIME specifies the timeout in seconds of a blocking read. + +When both VMIN and VTIME settings are configured, VTIME acts as an interbyte timeout that restarts on every byte received, and a blocking read will block until either VMIN bytes are read or the VTIME timeout expires after the last byte read. See the `termios` man page for more information. + +`serial` should be a valid pointer to a Serial handle opened with `serial_open()` or `serial_open_advanced()`. `vmin` can be between 0 and 255. `vtime` can be between 0 and 25.5 seconds, with a resolution of 0.1 seconds. + +Returns 1 on success, or a negative [Serial error code](#return-value) on failure. + +------ + +``` c +int serial_fd(serial_t *serial); +``` +Return the file descriptor (for the underlying `tty` device) of the Serial handle. + +`serial` should be a valid pointer to a Serial handle opened with `serial_open()` or `serial_open_advanced()`. + +This function is a simple accessor to the Serial handle structure and always succeeds. + +------ + +``` c +int serial_tostring(serial_t *serial, char *str, size_t len); +``` +Return a string representation of the Serial handle. + +`serial` should be a valid pointer to a Serial handle opened with `serial_open()` or `serial_open_advanced()`. + +This function behaves and returns like `snprintf()`. + +------ + +``` c +int serial_errno(serial_t *serial); +``` +Return the libc errno of the last failure that occurred. + +`serial` should be a valid pointer to a Serial handle opened with `serial_open()` or `serial_open_advanced()`. + +------ + +``` c +const char *serial_errmsg(serial_t *serial); +``` +Return a human readable error message of the last failure that occurred. + +`serial` should be a valid pointer to a Serial handle opened with `serial_open()` or `serial_open_advanced()`. + +### RETURN VALUE + +The periphery Serial functions return 0 on success or one of the negative error codes below on failure. + +The libc errno of the failure in an underlying libc library call can be obtained with the `serial_errno()` helper function. A human readable error message can be obtained with the `serial_errmsg()` helper function. + +| Error Code | Description | +|---------------------------|---------------------------------------| +| `SERIAL_ERROR_ARG` | Invalid arguments | +| `SERIAL_ERROR_OPEN` | Opening serial port | +| `SERIAL_ERROR_QUERY` | Querying serial port attributes | +| `SERIAL_ERROR_CONFIGURE` | Configuring serial port attributes | +| `SERIAL_ERROR_IO` | Reading/writing serial port | +| `SERIAL_ERROR_CLOSE` | Closing serial port | + +### EXAMPLE + +``` c +#include +#include + +#include "serial.h" + +int main(void) { + serial_t *serial; + const char *s = "Hello World!"; + char buf[128]; + int ret; + + serial = serial_new(); + + /* Open /dev/ttyUSB0 with baudrate 115200, and defaults of 8N1, no flow control */ + if (serial_open(serial, "/dev/ttyUSB0", 115200) < 0) { + fprintf(stderr, "serial_open(): %s\n", serial_errmsg(serial)); + exit(1); + } + + /* Write to the serial port */ + if (serial_write(serial, s, strlen(s)) < 0) { + fprintf(stderr, "serial_write(): %s\n", serial_errmsg(serial)); + exit(1); + } + + /* Read up to buf size or 2000ms timeout */ + if ((ret = serial_read(serial, buf, sizeof(buf), 2000)) < 0) { + fprintf(stderr, "serial_read(): %s\n", serial_errmsg(serial)); + exit(1); + } + + printf("read %d bytes: _%s_\n", ret, buf); + + serial_close(serial); + + serial_free(serial); + + return 0; +} +``` + diff --git a/third_party/periphery/docs/spi.md b/third_party/periphery/docs/spi.md new file mode 100644 index 000000000..5986621c8 --- /dev/null +++ b/third_party/periphery/docs/spi.md @@ -0,0 +1,250 @@ +### NAME + +SPI wrapper functions for Linux userspace `spidev` devices. + +### SYNOPSIS + +``` c +#include + +/* Primary Functions */ +spi_t *spi_new(void); +int spi_open(spi_t *spi, const char *path, unsigned int mode, uint32_t max_speed); +int spi_open_advanced(spi_t *spi, const char *path, unsigned int mode, uint32_t max_speed, + spi_bit_order_t bit_order, uint8_t bits_per_word, uint8_t extra_flags); +int spi_open_advanced2(spi_t *spi, const char *path, unsigned int mode, uint32_t max_speed, + spi_bit_order_t bit_order, uint8_t bits_per_word, uint32_t extra_flags); +int spi_transfer(spi_t *spi, const uint8_t *txbuf, uint8_t *rxbuf, size_t len); +int spi_close(spi_t *spi); +void spi_free(spi_t *spi); + +/* Getters */ +int spi_get_mode(spi_t *spi, unsigned int *mode); +int spi_get_max_speed(spi_t *spi, uint32_t *max_speed); +int spi_get_bit_order(spi_t *spi, spi_bit_order_t *bit_order); +int spi_get_bits_per_word(spi_t *spi, uint8_t *bits_per_word); +int spi_get_extra_flags(spi_t *spi, uint8_t *extra_flags); +int spi_get_extra_flags32(spi_t *spi, uint32_t *extra_flags); + +/* Setters */ +int spi_set_mode(spi_t *spi, unsigned int mode); +int spi_set_max_speed(spi_t *spi, uint32_t max_speed); +int spi_set_bit_order(spi_t *spi, spi_bit_order_t bit_order); +int spi_set_bits_per_word(spi_t *spi, uint8_t bits_per_word); +int spi_set_extra_flags(spi_t *spi, uint8_t extra_flags); +int spi_set_extra_flags32(spi_t *spi, uint32_t extra_flags); + +/* Miscellaneous */ +int spi_fd(spi_t *spi); +int spi_tostring(spi_t *spi, char *str, size_t len); + +/* Error Handling */ +int spi_errno(spi_t *spi); +const char *spi_errmsg(spi_t *spi); +``` + +### ENUMERATIONS + +* `spi_bit_order_t` + * `MSB_FIRST`: Most significant bit first transfer (typical) + * `LSB_FIRST`: Least significant bit first transfer + +### DESCRIPTION + +``` c +spi_t *spi_new(void); +``` +Allocate a SPI handle. + +Returns a valid handle on success, or NULL on failure. + +------ + +``` c +int spi_open(spi_t *spi, const char *path, unsigned int mode, uint32_t max_speed); +``` +Open the `spidev` device at the specified path (e.g. "/dev/spidev1.0"), with the specified SPI mode, specified max speed in hertz, and the defaults of `MSB_FIRST` bit order, and 8 bits per word. + +`spi` should be a valid pointer to an allocated SPI handle structure. SPI mode can be 0, 1, 2, or 3. + +Returns 0 on success, or a negative [SPI error code](#return-value) on failure. + +------ + +``` c +int spi_open_advanced(spi_t *spi, const char *path, unsigned int mode, uint32_t max_speed, + spi_bit_order_t bit_order, uint8_t bits_per_word, uint8_t extra_flags); +``` +Open the `spidev` device at the specified path, with the specified SPI mode, max speed in hertz, bit order, bits per word, and extra flags. + +`spi` should be a valid pointer to an allocated SPI handle structure. SPI mode can be 0, 1, 2, or 3. Bit order can be `MSB_FIRST` or `LSB_FIRST`, as defined [above](#enumerations). Bits per word specifies the transfer word size. Extra flags specified additional flags bitwise-ORed with the SPI mode. + +Returns 0 on success, or a negative [SPI error code](#return-value) on failure. + +------ + +``` c +int spi_open_advanced2(spi_t *spi, const char *path, unsigned int mode, uint32_t max_speed, + spi_bit_order_t bit_order, uint8_t bits_per_word, uint32_t extra_flags); +``` +Open the `spidev` device at the specified path, with the specified SPI mode, max speed in hertz, bit order, bits per word, and extra flags. This open function is the same as `spi_open_advanced()`, except that `extra_flags` can be 32-bits. + +`spi` should be a valid pointer to an allocated SPI handle structure. SPI mode can be 0, 1, 2, or 3. Bit order can be `MSB_FIRST` or `LSB_FIRST`, as defined [above](#enumerations). Bits per word specifies the transfer word size. Extra flags specified additional flags bitwise-ORed with the SPI mode. + +Returns 0 on success, or a negative [SPI error code](#return-value) on failure. + +------ + +``` c +int spi_transfer(spi_t *spi, const uint8_t *txbuf, uint8_t *rxbuf, size_t len); +``` +Shift out `len` word counts of the `txbuf` buffer, while shifting in `len` word counts to the `rxbuf` buffer. + +`spi` should be a valid pointer to an SPI handle opened with `spi_open()` or `spi_open_advanced()`. + +`rxbuf` may be NULL. `txbuf` and `rxbuf` may point to the same buffer. + +Returns 0 on success, or a negative [SPI error code](#return-value) on failure. + +------ + +``` c +int spi_close(spi_t *spi); +``` +Close the `spidev` device. + +`spi` should be a valid pointer to an SPI handle opened with `spi_open()` or `spi_open_advanced()`. + +Returns 0 on success, or a negative [SPI error code](#return-value) on failure. + +------ + +``` c +void spi_free(spi_t *spi); +``` +Free a SPI handle. + +------ + +``` c +int spi_get_mode(spi_t *spi, unsigned int *mode); +int spi_get_max_speed(spi_t *spi, uint32_t *max_speed); +int spi_get_bit_order(spi_t *spi, spi_bit_order_t *bit_order); +int spi_get_bits_per_word(spi_t *spi, uint8_t *bits_per_word); +int spi_get_extra_flags(spi_t *spi, uint8_t *extra_flags); +int spi_get_extra_flags32(spi_t *spi, uint32_t *extra_flags); +``` +Get the mode, max speed, bit order, bits per word, or extra flags, respectively, of the underlying `spidev` device. + +`spi` should be a valid pointer to a SPI handle opened with `spi_open()` or `spi_open_advanced()`. + +Returns 0 on success, or a negative [SPI error code](#return-value) on failure. + +------ + +``` c +int spi_set_mode(spi_t *spi, unsigned int mode); +int spi_set_max_speed(spi_t *spi, uint32_t max_speed); +int spi_set_bit_order(spi_t *spi, spi_bit_order_t bit_order); +int spi_set_bits_per_word(spi_t *spi, uint8_t bits_per_word); +int spi_set_extra_flags(spi_t *spi, uint8_t extra_flags); +int spi_set_extra_flags32(spi_t *spi, uint32_t extra_flags); +``` +Set the mode, max speed, bit order, bits per word, or extra flags, respectively, on the underlying `spidev` device. + +`spi` should be a valid pointer to a SPI handle opened with `spi_open()` or `spi_open_advanced()`. + +Returns 0 on success, or a negative [SPI error code](#return-value) on failure. + +------ + +``` c +int spi_fd(spi_t *spi); +``` +Return the file descriptor (for the underlying `spidev` device) of the SPI handle. + +`spi` should be a valid pointer to a SPI handle opened with `spi_open()` or `spi_open_advanced()`. + +This function is a simple accessor to the SPI handle structure and always succeeds. + +------ + +``` c +int spi_tostring(spi_t *spi, char *str, size_t len); +``` +Return a string representation of the SPI handle. + +`spi` should be a valid pointer to a SPI handle opened with `spi_open()` or `spi_open_advanced()`. + +This function behaves and returns like `snprintf()`. + +------ + +``` c +int spi_errno(spi_t *spi); +``` +Return the libc errno of the last failure that occurred. + +`spi` should be a valid pointer to a SPI handle opened with `spi_open()` or `spi_open_advanced()`. + +------ + +``` c +const char *spi_errmsg(spi_t *spi); +``` +Return a human readable error message of the last failure that occurred. + +`spi` should be a valid pointer to a SPI handle opened with `spi_open()` or `spi_open_advanced()`. + +### RETURN VALUE + +The periphery SPI functions return 0 on success or one of the negative error codes below on failure. + +The libc errno of the failure in an underlying libc library call can be obtained with the `spi_errno()` helper function. A human readable error message can be obtained with the `spi_errmsg()` helper function. + +| Error Code | Description | +|-----------------------|-----------------------------------| +| `SPI_ERROR_ARG` | Invalid arguments | +| `SPI_ERROR_OPEN` | Opening SPI device | +| `SPI_ERROR_QUERY` | Querying SPI device attributes | +| `SPI_ERROR_CONFIGURE` | Configuring SPI device attributes | +| `SPI_ERROR_TRANSFER` | SPI transfer | +| `SPI_ERROR_CLOSE` | Closing SPI device | + +### EXAMPLE + +``` c +#include +#include +#include + +#include "spi.h" + +int main(void) { + spi_t *spi; + uint8_t buf[4] = { 0xaa, 0xbb, 0xcc, 0xdd }; + + spi = spi_new(); + + /* Open spidev1.0 with mode 0 and max speed 1MHz */ + if (spi_open(spi, "/dev/spidev1.0", 0, 1000000) < 0) { + fprintf(stderr, "spi_open(): %s\n", spi_errmsg(spi)); + exit(1); + } + + /* Shift out and in 4 bytes */ + if (spi_transfer(spi, buf, buf, sizeof(buf)) < 0) { + fprintf(stderr, "spi_transfer(): %s\n", spi_errmsg(spi)); + exit(1); + } + + printf("shifted in: 0x%02x 0x%02x 0x%02x 0x%02x\n", buf[0], buf[1], buf[2], buf[3]); + + spi_close(spi); + + spi_free(spi); + + return 0; +} +``` + diff --git a/third_party/periphery/gpio.h b/third_party/periphery/gpio.h new file mode 100644 index 000000000..242c64d17 --- /dev/null +++ b/third_party/periphery/gpio.h @@ -0,0 +1,120 @@ +/* + * c-periphery + * https://github.com/vsergeev/c-periphery + * License: MIT + */ + +#ifndef _PERIPHERY_GPIO_H +#define _PERIPHERY_GPIO_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include + +enum gpio_error_code { + GPIO_ERROR_ARG = -1, /* Invalid arguments */ + GPIO_ERROR_OPEN = -2, /* Opening GPIO */ + GPIO_ERROR_NOT_FOUND = -3, /* Line name not found */ + GPIO_ERROR_QUERY = -4, /* Querying GPIO attributes */ + GPIO_ERROR_CONFIGURE = -5, /* Configuring GPIO attributes */ + GPIO_ERROR_UNSUPPORTED = -6, /* Unsupported attribute or operation */ + GPIO_ERROR_INVALID_OPERATION = -7, /* Invalid operation */ + GPIO_ERROR_IO = -8, /* Reading/writing GPIO */ + GPIO_ERROR_CLOSE = -9, /* Closing GPIO */ +}; + +typedef enum gpio_direction { + GPIO_DIR_IN, /* Input */ + GPIO_DIR_OUT, /* Output, initialized to low */ + GPIO_DIR_OUT_LOW, /* Output, initialized to low */ + GPIO_DIR_OUT_HIGH, /* Output, initialized to high */ +} gpio_direction_t; + +typedef enum gpio_edge { + GPIO_EDGE_NONE, /* No interrupt edge */ + GPIO_EDGE_RISING, /* Rising edge 0 -> 1 */ + GPIO_EDGE_FALLING, /* Falling edge 1 -> 0 */ + GPIO_EDGE_BOTH /* Both edges X -> !X */ +} gpio_edge_t; + +typedef enum gpio_bias { + GPIO_BIAS_DEFAULT, /* Default line bias */ + GPIO_BIAS_PULL_UP, /* Pull-up */ + GPIO_BIAS_PULL_DOWN, /* Pull-down */ + GPIO_BIAS_DISABLE, /* Disable line bias */ +} gpio_bias_t; + +typedef enum gpio_drive { + GPIO_DRIVE_DEFAULT, /* Default line drive (push-pull) */ + GPIO_DRIVE_OPEN_DRAIN, /* Open drain */ + GPIO_DRIVE_OPEN_SOURCE, /* Open source */ +} gpio_drive_t; + +/* Configuration structure for gpio_open_*advanced() functions */ +typedef struct gpio_config { + gpio_direction_t direction; + gpio_edge_t edge; + gpio_bias_t bias; + gpio_drive_t drive; + bool inverted; + const char *label; /* Can be NULL for default consumer label */ +} gpio_config_t; + +typedef struct gpio_handle gpio_t; + +/* Primary Functions */ +gpio_t *gpio_new(void); +int gpio_open(gpio_t *gpio, const char *path, unsigned int line, gpio_direction_t direction); +int gpio_open_name(gpio_t *gpio, const char *path, const char *name, gpio_direction_t direction); +int gpio_open_advanced(gpio_t *gpio, const char *path, unsigned int line, const gpio_config_t *config); +int gpio_open_name_advanced(gpio_t *gpio, const char *path, const char *name, const gpio_config_t *config); +int gpio_open_sysfs(gpio_t *gpio, unsigned int line, gpio_direction_t direction); +int gpio_read(gpio_t *gpio, bool *value); +int gpio_write(gpio_t *gpio, bool value); +int gpio_poll(gpio_t *gpio, int timeout_ms); +int gpio_close(gpio_t *gpio); +void gpio_free(gpio_t *gpio); + +/* Read Event (for character device GPIOs) */ +int gpio_read_event(gpio_t *gpio, gpio_edge_t *edge, uint64_t *timestamp); + +/* Poll Multiple */ +int gpio_poll_multiple(gpio_t **gpios, size_t count, int timeout_ms, bool *gpios_ready); + +/* Getters */ +int gpio_get_direction(gpio_t *gpio, gpio_direction_t *direction); +int gpio_get_edge(gpio_t *gpio, gpio_edge_t *edge); +int gpio_get_bias(gpio_t *gpio, gpio_bias_t *bias); +int gpio_get_drive(gpio_t *gpio, gpio_drive_t *drive); +int gpio_get_inverted(gpio_t *gpio, bool *inverted); + +/* Setters */ +int gpio_set_direction(gpio_t *gpio, gpio_direction_t direction); +int gpio_set_edge(gpio_t *gpio, gpio_edge_t edge); +int gpio_set_bias(gpio_t *gpio, gpio_bias_t bias); +int gpio_set_drive(gpio_t *gpio, gpio_drive_t drive); +int gpio_set_inverted(gpio_t *gpio, bool inverted); + +/* Miscellaneous Properties */ +unsigned int gpio_line(gpio_t *gpio); +int gpio_fd(gpio_t *gpio); +int gpio_name(gpio_t *gpio, char *str, size_t len); +int gpio_label(gpio_t *gpio, char *str, size_t len); +int gpio_chip_fd(gpio_t *gpio); +int gpio_chip_name(gpio_t *gpio, char *str, size_t len); +int gpio_chip_label(gpio_t *gpio, char *str, size_t len); +int gpio_tostring(gpio_t *gpio, char *str, size_t len); + +/* Error Handling */ +int gpio_errno(gpio_t *gpio); +const char *gpio_errmsg(gpio_t *gpio); + +#ifdef __cplusplus +} +#endif + +#endif \ No newline at end of file diff --git a/third_party/periphery/spi.h b/third_party/periphery/spi.h new file mode 100644 index 000000000..c9aad4de8 --- /dev/null +++ b/third_party/periphery/spi.h @@ -0,0 +1,76 @@ +/* + * c-periphery + * https://github.com/vsergeev/c-periphery + * License: MIT + */ + +#ifndef _PERIPHERY_SPI_H +#define _PERIPHERY_SPI_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +enum spi_error_code { + SPI_ERROR_ARG = -1, /* Invalid arguments */ + SPI_ERROR_OPEN = -2, /* Opening SPI device */ + SPI_ERROR_QUERY = -3, /* Querying SPI device attributes */ + SPI_ERROR_CONFIGURE = -4, /* Configuring SPI device attributes */ + SPI_ERROR_TRANSFER = -5, /* SPI transfer */ + SPI_ERROR_CLOSE = -6, /* Closing SPI device */ + SPI_ERROR_UNSUPPORTED = -7, /* Unsupported attribute or operation */ +}; + +typedef enum spi_bit_order { + MSB_FIRST, + LSB_FIRST, +} spi_bit_order_t; + +typedef struct spi_handle spi_t; + +/* Primary Functions */ +spi_t *spi_new(void); +int spi_open(spi_t *spi, const char *path, unsigned int mode, + uint32_t max_speed); +int spi_open_advanced(spi_t *spi, const char *path, unsigned int mode, + uint32_t max_speed, spi_bit_order_t bit_order, + uint8_t bits_per_word, uint8_t extra_flags); +int spi_open_advanced2(spi_t *spi, const char *path, unsigned int mode, + uint32_t max_speed, spi_bit_order_t bit_order, + uint8_t bits_per_word, uint32_t extra_flags); +int spi_transfer(spi_t *spi, const uint8_t *txbuf, uint8_t *rxbuf, size_t len); +int spi_close(spi_t *spi); +void spi_free(spi_t *spi); + +/* Getters */ +int spi_get_mode(spi_t *spi, unsigned int *mode); +int spi_get_max_speed(spi_t *spi, uint32_t *max_speed); +int spi_get_bit_order(spi_t *spi, spi_bit_order_t *bit_order); +int spi_get_bits_per_word(spi_t *spi, uint8_t *bits_per_word); +int spi_get_extra_flags(spi_t *spi, uint8_t *extra_flags); +int spi_get_extra_flags32(spi_t *spi, uint32_t *extra_flags); + +/* Setters */ +int spi_set_mode(spi_t *spi, unsigned int mode); +int spi_set_max_speed(spi_t *spi, uint32_t max_speed); +int spi_set_bit_order(spi_t *spi, spi_bit_order_t bit_order); +int spi_set_bits_per_word(spi_t *spi, uint8_t bits_per_word); +int spi_set_extra_flags(spi_t *spi, uint8_t extra_flags); +int spi_set_extra_flags32(spi_t *spi, uint32_t extra_flags); + +/* Miscellaneous */ +int spi_fd(spi_t *spi); +int spi_tostring(spi_t *spi, char *str, size_t len); + +/* Error Handling */ +int spi_errno(spi_t *spi); +const char *spi_errmsg(spi_t *spi); + +#ifdef __cplusplus +} +#endif + +#endif \ No newline at end of file diff --git a/third_party/periphery/src/gpio.c b/third_party/periphery/src/gpio.c new file mode 100644 index 000000000..403ed2257 --- /dev/null +++ b/third_party/periphery/src/gpio.c @@ -0,0 +1,218 @@ +/* + * c-periphery + * https://github.com/vsergeev/c-periphery + * License: MIT + */ + +#define _XOPEN_SOURCE 600 /* for POLLRDNORM */ + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include "gpio.h" +#include "gpio_internal.h" + +extern const struct gpio_ops gpio_cdev_ops; +extern const struct gpio_ops gpio_sysfs_ops; + +gpio_t *gpio_new(void) { + gpio_t *gpio = calloc(1, sizeof(gpio_t)); + if (gpio == NULL) + return NULL; + +#if PERIPHERY_GPIO_CDEV_SUPPORT + gpio->ops = &gpio_cdev_ops; + gpio->u.cdev.line_fd = -1; + gpio->u.cdev.chip_fd = -1; +#else + gpio->ops = &gpio_sysfs_ops; + gpio->u.sysfs.line_fd = -1; +#endif + + return gpio; +} + +int gpio_read(gpio_t *gpio, bool *value) { + return gpio->ops->read(gpio, value); +} + +int gpio_write(gpio_t *gpio, bool value) { + return gpio->ops->write(gpio, value); +} + +int gpio_poll(gpio_t *gpio, int timeout_ms) { + return gpio->ops->poll(gpio, timeout_ms); +} + +int gpio_close(gpio_t *gpio) { + return gpio->ops->close(gpio); +} + +void gpio_free(gpio_t *gpio) { + free(gpio); +} + +int gpio_read_event(gpio_t *gpio, gpio_edge_t *edge, uint64_t *timestamp) { + return gpio->ops->read_event(gpio, edge, timestamp); +} + +int gpio_poll_multiple(gpio_t **gpios, size_t count, int timeout_ms, bool *gpios_ready) { + struct pollfd fds[count]; + int ret; + + /* Setup pollfd structs */ + for (size_t i = 0; i < count; i++) { + fds[i].fd = gpio_fd(gpios[i]); + fds[i].events = (gpios[i]->ops == &gpio_sysfs_ops) ? + (POLLPRI | POLLERR) : (POLLIN | POLLRDNORM); + if (gpios_ready) + gpios_ready[i] = false; + } + + /* Poll */ + if ((ret = poll(fds, count, timeout_ms)) < 0) + return GPIO_ERROR_IO; + + /* Event occurred */ + if (ret) { + for (size_t i = 0; i < count; i++) { + /* Set ready GPIOs */ + if (gpios_ready) + gpios_ready[i] = fds[i].revents != 0; + + /* Rewind GPIO if it is a sysfs GPIO */ + if (gpios[i]->ops == &gpio_sysfs_ops) { + if (lseek(gpios[i]->u.sysfs.line_fd, 0, SEEK_SET) < 0) + return GPIO_ERROR_IO; + } + } + + return ret; + } + + /* Timed out */ + return 0; +} + +int gpio_get_direction(gpio_t *gpio, gpio_direction_t *direction) { + return gpio->ops->get_direction(gpio, direction); +} + +int gpio_get_edge(gpio_t *gpio, gpio_edge_t *edge) { + return gpio->ops->get_edge(gpio, edge); +} + +int gpio_get_bias(gpio_t *gpio, gpio_bias_t *bias) { + return gpio->ops->get_bias(gpio, bias); +} + +int gpio_get_drive(gpio_t *gpio, gpio_drive_t *drive) { + return gpio->ops->get_drive(gpio, drive); +} + +int gpio_get_inverted(gpio_t *gpio, bool *inverted) { + return gpio->ops->get_inverted(gpio, inverted); +} + +int gpio_set_direction(gpio_t *gpio, gpio_direction_t direction) { + return gpio->ops->set_direction(gpio, direction); +} + +int gpio_set_edge(gpio_t *gpio, gpio_edge_t edge) { + return gpio->ops->set_edge(gpio, edge); +} + +int gpio_set_bias(gpio_t *gpio, gpio_bias_t bias) { + return gpio->ops->set_bias(gpio, bias); +} + +int gpio_set_drive(gpio_t *gpio, gpio_drive_t drive) { + return gpio->ops->set_drive(gpio, drive); +} + +int gpio_set_inverted(gpio_t *gpio, bool inverted) { + return gpio->ops->set_inverted(gpio, inverted); +} + +unsigned int gpio_line(gpio_t *gpio) { + return gpio->ops->line(gpio); +} + +int gpio_fd(gpio_t *gpio) { + return gpio->ops->fd(gpio); +} + +int gpio_name(gpio_t *gpio, char *str, size_t len) { + return gpio->ops->name(gpio, str, len); +} + +int gpio_label(gpio_t *gpio, char *str, size_t len) { + return gpio->ops->label(gpio, str, len); +} + +int gpio_chip_fd(gpio_t *gpio) { + return gpio->ops->chip_fd(gpio); +} + +int gpio_chip_name(gpio_t *gpio, char *str, size_t len) { + return gpio->ops->chip_name(gpio, str, len); +} + +int gpio_chip_label(gpio_t *gpio, char *str, size_t len) { + return gpio->ops->chip_label(gpio, str, len); +} + +int gpio_tostring(gpio_t *gpio, char *str, size_t len) { + return gpio->ops->tostring(gpio, str, len); +} + +int gpio_errno(gpio_t *gpio) { + return gpio->error.c_errno; +} + +const char *gpio_errmsg(gpio_t *gpio) { + return gpio->error.errmsg; +} + +#if !PERIPHERY_GPIO_CDEV_SUPPORT + +int gpio_open(gpio_t *gpio, const char *path, unsigned int line, gpio_direction_t direction) { + (void)path; + (void)line; + (void)direction; + return _gpio_error(gpio, GPIO_ERROR_UNSUPPORTED, 0, "c-periphery library built without character device GPIO support."); +} + +int gpio_open_name(gpio_t *gpio, const char *path, const char *name, gpio_direction_t direction) { + (void)gpio; + (void)path; + (void)name; + (void)direction; + return _gpio_error(gpio, GPIO_ERROR_UNSUPPORTED, 0, "c-periphery library built without character device GPIO support."); +} + +int gpio_open_advanced(gpio_t *gpio, const char *path, unsigned int line, const gpio_config_t *config) { + (void)path; + (void)line; + (void)config; + return _gpio_error(gpio, GPIO_ERROR_UNSUPPORTED, 0, "c-periphery library built without character device GPIO support."); +} + +int gpio_open_name_advanced(gpio_t *gpio, const char *path, const char *name, const gpio_config_t *config) { + (void)path; + (void)name; + (void)config; + return _gpio_error(gpio, GPIO_ERROR_UNSUPPORTED, 0, "c-periphery library built without character device GPIO support."); +} + +#endif + diff --git a/third_party/periphery/src/gpio.h b/third_party/periphery/src/gpio.h new file mode 100644 index 000000000..e18e7e525 --- /dev/null +++ b/third_party/periphery/src/gpio.h @@ -0,0 +1,121 @@ +/* + * c-periphery + * https://github.com/vsergeev/c-periphery + * License: MIT + */ + +#ifndef _PERIPHERY_GPIO_H +#define _PERIPHERY_GPIO_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include + +enum gpio_error_code { + GPIO_ERROR_ARG = -1, /* Invalid arguments */ + GPIO_ERROR_OPEN = -2, /* Opening GPIO */ + GPIO_ERROR_NOT_FOUND = -3, /* Line name not found */ + GPIO_ERROR_QUERY = -4, /* Querying GPIO attributes */ + GPIO_ERROR_CONFIGURE = -5, /* Configuring GPIO attributes */ + GPIO_ERROR_UNSUPPORTED = -6, /* Unsupported attribute or operation */ + GPIO_ERROR_INVALID_OPERATION = -7, /* Invalid operation */ + GPIO_ERROR_IO = -8, /* Reading/writing GPIO */ + GPIO_ERROR_CLOSE = -9, /* Closing GPIO */ +}; + +typedef enum gpio_direction { + GPIO_DIR_IN, /* Input */ + GPIO_DIR_OUT, /* Output, initialized to low */ + GPIO_DIR_OUT_LOW, /* Output, initialized to low */ + GPIO_DIR_OUT_HIGH, /* Output, initialized to high */ +} gpio_direction_t; + +typedef enum gpio_edge { + GPIO_EDGE_NONE, /* No interrupt edge */ + GPIO_EDGE_RISING, /* Rising edge 0 -> 1 */ + GPIO_EDGE_FALLING, /* Falling edge 1 -> 0 */ + GPIO_EDGE_BOTH /* Both edges X -> !X */ +} gpio_edge_t; + +typedef enum gpio_bias { + GPIO_BIAS_DEFAULT, /* Default line bias */ + GPIO_BIAS_PULL_UP, /* Pull-up */ + GPIO_BIAS_PULL_DOWN, /* Pull-down */ + GPIO_BIAS_DISABLE, /* Disable line bias */ +} gpio_bias_t; + +typedef enum gpio_drive { + GPIO_DRIVE_DEFAULT, /* Default line drive (push-pull) */ + GPIO_DRIVE_OPEN_DRAIN, /* Open drain */ + GPIO_DRIVE_OPEN_SOURCE, /* Open source */ +} gpio_drive_t; + +/* Configuration structure for gpio_open_*advanced() functions */ +typedef struct gpio_config { + gpio_direction_t direction; + gpio_edge_t edge; + gpio_bias_t bias; + gpio_drive_t drive; + bool inverted; + const char *label; /* Can be NULL for default consumer label */ +} gpio_config_t; + +typedef struct gpio_handle gpio_t; + +/* Primary Functions */ +gpio_t *gpio_new(void); +int gpio_open(gpio_t *gpio, const char *path, unsigned int line, gpio_direction_t direction); +int gpio_open_name(gpio_t *gpio, const char *path, const char *name, gpio_direction_t direction); +int gpio_open_advanced(gpio_t *gpio, const char *path, unsigned int line, const gpio_config_t *config); +int gpio_open_name_advanced(gpio_t *gpio, const char *path, const char *name, const gpio_config_t *config); +int gpio_open_sysfs(gpio_t *gpio, unsigned int line, gpio_direction_t direction); +int gpio_read(gpio_t *gpio, bool *value); +int gpio_write(gpio_t *gpio, bool value); +int gpio_poll(gpio_t *gpio, int timeout_ms); +int gpio_close(gpio_t *gpio); +void gpio_free(gpio_t *gpio); + +/* Read Event (for character device GPIOs) */ +int gpio_read_event(gpio_t *gpio, gpio_edge_t *edge, uint64_t *timestamp); + +/* Poll Multiple */ +int gpio_poll_multiple(gpio_t **gpios, size_t count, int timeout_ms, bool *gpios_ready); + +/* Getters */ +int gpio_get_direction(gpio_t *gpio, gpio_direction_t *direction); +int gpio_get_edge(gpio_t *gpio, gpio_edge_t *edge); +int gpio_get_bias(gpio_t *gpio, gpio_bias_t *bias); +int gpio_get_drive(gpio_t *gpio, gpio_drive_t *drive); +int gpio_get_inverted(gpio_t *gpio, bool *inverted); + +/* Setters */ +int gpio_set_direction(gpio_t *gpio, gpio_direction_t direction); +int gpio_set_edge(gpio_t *gpio, gpio_edge_t edge); +int gpio_set_bias(gpio_t *gpio, gpio_bias_t bias); +int gpio_set_drive(gpio_t *gpio, gpio_drive_t drive); +int gpio_set_inverted(gpio_t *gpio, bool inverted); + +/* Miscellaneous Properties */ +unsigned int gpio_line(gpio_t *gpio); +int gpio_fd(gpio_t *gpio); +int gpio_name(gpio_t *gpio, char *str, size_t len); +int gpio_label(gpio_t *gpio, char *str, size_t len); +int gpio_chip_fd(gpio_t *gpio); +int gpio_chip_name(gpio_t *gpio, char *str, size_t len); +int gpio_chip_label(gpio_t *gpio, char *str, size_t len); +int gpio_tostring(gpio_t *gpio, char *str, size_t len); + +/* Error Handling */ +int gpio_errno(gpio_t *gpio); +const char *gpio_errmsg(gpio_t *gpio); + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/third_party/periphery/src/gpio_cdev_v1.c b/third_party/periphery/src/gpio_cdev_v1.c new file mode 100644 index 000000000..cf9879b94 --- /dev/null +++ b/third_party/periphery/src/gpio_cdev_v1.c @@ -0,0 +1,583 @@ +/* + * c-periphery + * https://github.com/vsergeev/c-periphery + * License: MIT + */ + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "gpio.h" +#include "gpio_internal.h" + +#if PERIPHERY_GPIO_CDEV_SUPPORT == 1 +#include +#endif + +/*********************************************************************************/ +/* cdev v1 implementation */ +/*********************************************************************************/ + +#if PERIPHERY_GPIO_CDEV_SUPPORT == 1 + +static int _gpio_cdev_reopen(gpio_t *gpio, gpio_direction_t direction, gpio_edge_t edge, gpio_bias_t bias, gpio_drive_t drive, bool inverted) { + uint32_t flags = 0; + + #ifdef GPIOHANDLE_REQUEST_BIAS_PULL_UP + if (bias == GPIO_BIAS_PULL_UP) + flags |= GPIOHANDLE_REQUEST_BIAS_PULL_UP; + else if (bias == GPIO_BIAS_PULL_DOWN) + flags |= GPIOHANDLE_REQUEST_BIAS_PULL_DOWN; + else if (bias == GPIO_BIAS_DISABLE) + flags |= GPIOHANDLE_REQUEST_BIAS_DISABLE; + #else + if (bias != GPIO_BIAS_DEFAULT) + return _gpio_error(gpio, GPIO_ERROR_UNSUPPORTED, 0, "Kernel version does not support configuring GPIO line bias"); + #endif + + #ifdef GPIOHANDLE_REQUEST_OPEN_DRAIN + if (drive == GPIO_DRIVE_OPEN_DRAIN) + flags |= GPIOHANDLE_REQUEST_OPEN_DRAIN; + else if (drive == GPIO_DRIVE_OPEN_SOURCE) + flags |= GPIOHANDLE_REQUEST_OPEN_SOURCE; + #else + if (drive != GPIO_DRIVE_DEFAULT) + return _gpio_error(gpio, GPIO_ERROR_UNSUPPORTED, 0, "Kernel version does not support configuring GPIO line drive"); + #endif + + if (inverted) + flags |= GPIOHANDLE_REQUEST_ACTIVE_LOW; + + /* FIXME this should really use GPIOHANDLE_SET_CONFIG_IOCTL instead of + * closing and reopening, especially to preserve output value on + * configuration changes */ + + if (gpio->u.cdev.line_fd >= 0) { + if (close(gpio->u.cdev.line_fd) < 0) + return _gpio_error(gpio, GPIO_ERROR_CLOSE, errno, "Closing GPIO line"); + + gpio->u.cdev.line_fd = -1; + } + + if (direction == GPIO_DIR_IN) { + if (edge == GPIO_EDGE_NONE) { + struct gpiohandle_request request = {0}; + + request.lineoffsets[0] = gpio->u.cdev.line; + request.flags = flags | GPIOHANDLE_REQUEST_INPUT; + strncpy(request.consumer_label, gpio->u.cdev.label, sizeof(request.consumer_label) - 1); + request.consumer_label[sizeof(request.consumer_label) - 1] = '\0'; + request.lines = 1; + + if (ioctl(gpio->u.cdev.chip_fd, GPIO_GET_LINEHANDLE_IOCTL, &request) < 0) + return _gpio_error(gpio, GPIO_ERROR_OPEN, errno, "Opening input line handle"); + + gpio->u.cdev.line_fd = request.fd; + } else { + struct gpioevent_request request = {0}; + + request.lineoffset = gpio->u.cdev.line; + request.handleflags = flags | GPIOHANDLE_REQUEST_INPUT; + request.eventflags = (edge == GPIO_EDGE_RISING) ? GPIOEVENT_REQUEST_RISING_EDGE : + (edge == GPIO_EDGE_FALLING) ? GPIOEVENT_REQUEST_FALLING_EDGE : + GPIOEVENT_REQUEST_BOTH_EDGES; + strncpy(request.consumer_label, gpio->u.cdev.label, sizeof(request.consumer_label) - 1); + request.consumer_label[sizeof(request.consumer_label) - 1] = '\0'; + + if (ioctl(gpio->u.cdev.chip_fd, GPIO_GET_LINEEVENT_IOCTL, &request) < 0) + return _gpio_error(gpio, GPIO_ERROR_OPEN, errno, "Opening input event line handle"); + + gpio->u.cdev.line_fd = request.fd; + } + } else { + struct gpiohandle_request request = {0}; + bool initial_value = (direction == GPIO_DIR_OUT_HIGH) ? true : false; + initial_value ^= inverted; + + request.lineoffsets[0] = gpio->u.cdev.line; + request.flags = flags | GPIOHANDLE_REQUEST_OUTPUT; + request.default_values[0] = initial_value; + strncpy(request.consumer_label, gpio->u.cdev.label, sizeof(request.consumer_label) - 1); + request.consumer_label[sizeof(request.consumer_label) - 1] = '\0'; + request.lines = 1; + + if (ioctl(gpio->u.cdev.chip_fd, GPIO_GET_LINEHANDLE_IOCTL, &request) < 0) + return _gpio_error(gpio, GPIO_ERROR_OPEN, errno, "Opening output line handle"); + + gpio->u.cdev.line_fd = request.fd; + } + + gpio->u.cdev.direction = (direction == GPIO_DIR_IN) ? GPIO_DIR_IN : GPIO_DIR_OUT; + gpio->u.cdev.edge = edge; + gpio->u.cdev.bias = bias; + gpio->u.cdev.drive = drive; + gpio->u.cdev.inverted = inverted; + + return 0; +} + +static int gpio_cdev_read(gpio_t *gpio, bool *value) { + struct gpiohandle_data data = {0}; + + if (ioctl(gpio->u.cdev.line_fd, GPIOHANDLE_GET_LINE_VALUES_IOCTL, &data) < 0) + return _gpio_error(gpio, GPIO_ERROR_IO, errno, "Getting line value"); + + *value = data.values[0]; + + return 0; +} + +static int gpio_cdev_write(gpio_t *gpio, bool value) { + struct gpiohandle_data data = {0}; + + if (gpio->u.cdev.direction != GPIO_DIR_OUT) + return _gpio_error(gpio, GPIO_ERROR_INVALID_OPERATION, 0, "Invalid operation: cannot write to input GPIO"); + + data.values[0] = value; + + if (ioctl(gpio->u.cdev.line_fd, GPIOHANDLE_SET_LINE_VALUES_IOCTL, &data) < 0) + return _gpio_error(gpio, GPIO_ERROR_IO, errno, "Setting line value"); + + return 0; +} + +static int gpio_cdev_read_event(gpio_t *gpio, gpio_edge_t *edge, uint64_t *timestamp) { + struct gpioevent_data event_data = {0}; + + if (gpio->u.cdev.direction != GPIO_DIR_IN) + return _gpio_error(gpio, GPIO_ERROR_INVALID_OPERATION, 0, "Invalid operation: cannot read event of output GPIO"); + else if (gpio->u.cdev.edge == GPIO_EDGE_NONE) + return _gpio_error(gpio, GPIO_ERROR_INVALID_OPERATION, 0, "Invalid operation: GPIO edge not set"); + + if (read(gpio->u.cdev.line_fd, &event_data, sizeof(event_data)) < (ssize_t)sizeof(event_data)) + return _gpio_error(gpio, GPIO_ERROR_IO, errno, "Reading GPIO event"); + + if (edge) + *edge = (event_data.id == GPIOEVENT_EVENT_RISING_EDGE) ? GPIO_EDGE_RISING : + (event_data.id == GPIOEVENT_EVENT_FALLING_EDGE) ? GPIO_EDGE_FALLING : GPIO_EDGE_NONE; + if (timestamp) + *timestamp = event_data.timestamp; + + return 0; +} + +static int gpio_cdev_poll(gpio_t *gpio, int timeout_ms) { + struct pollfd fds[1]; + int ret; + + if (gpio->u.cdev.direction != GPIO_DIR_IN) + return _gpio_error(gpio, GPIO_ERROR_INVALID_OPERATION, 0, "Invalid operation: cannot poll output GPIO"); + + fds[0].fd = gpio->u.cdev.line_fd; + fds[0].events = POLLIN | POLLPRI | POLLERR; + if ((ret = poll(fds, 1, timeout_ms)) < 0) + return _gpio_error(gpio, GPIO_ERROR_IO, errno, "Polling GPIO line"); + + return ret > 0; +} + +static int gpio_cdev_close(gpio_t *gpio) { + /* Close line fd */ + if (gpio->u.cdev.line_fd >= 0) { + if (close(gpio->u.cdev.line_fd) < 0) + return _gpio_error(gpio, GPIO_ERROR_CLOSE, errno, "Closing GPIO line"); + + gpio->u.cdev.line_fd = -1; + } + + /* Close chip fd */ + if (gpio->u.cdev.chip_fd >= 0) { + if (close(gpio->u.cdev.chip_fd) < 0) + return _gpio_error(gpio, GPIO_ERROR_CLOSE, errno, "Closing GPIO chip"); + + gpio->u.cdev.chip_fd = -1; + } + + gpio->u.cdev.edge = GPIO_EDGE_NONE; + gpio->u.cdev.direction = GPIO_DIR_IN; + + return 0; +} + +static int gpio_cdev_get_direction(gpio_t *gpio, gpio_direction_t *direction) { + *direction = gpio->u.cdev.direction; + return 0; +} + +static int gpio_cdev_get_edge(gpio_t *gpio, gpio_edge_t *edge) { + *edge = gpio->u.cdev.edge; + return 0; +} + +static int gpio_cdev_get_bias(gpio_t *gpio, gpio_bias_t *bias) { + *bias = gpio->u.cdev.bias; + return 0; +} + +static int gpio_cdev_get_drive(gpio_t *gpio, gpio_drive_t *drive) { + *drive = gpio->u.cdev.drive; + return 0; +} + +static int gpio_cdev_get_inverted(gpio_t *gpio, bool *inverted) { + *inverted = gpio->u.cdev.inverted; + return 0; +} + +static int gpio_cdev_set_direction(gpio_t *gpio, gpio_direction_t direction) { + if (direction != GPIO_DIR_IN && direction != GPIO_DIR_OUT && direction != GPIO_DIR_OUT_LOW && direction != GPIO_DIR_OUT_HIGH) + return _gpio_error(gpio, GPIO_ERROR_ARG, 0, "Invalid GPIO direction (can be in, out, low, high)"); + + if (gpio->u.cdev.direction == direction) + return 0; + + return _gpio_cdev_reopen(gpio, direction, GPIO_EDGE_NONE, gpio->u.cdev.bias, gpio->u.cdev.drive, gpio->u.cdev.inverted); +} + +static int gpio_cdev_set_edge(gpio_t *gpio, gpio_edge_t edge) { + if (edge != GPIO_EDGE_NONE && edge != GPIO_EDGE_RISING && edge != GPIO_EDGE_FALLING && edge != GPIO_EDGE_BOTH) + return _gpio_error(gpio, GPIO_ERROR_ARG, 0, "Invalid GPIO interrupt edge (can be none, rising, falling, both)"); + + if (gpio->u.cdev.direction != GPIO_DIR_IN) + return _gpio_error(gpio, GPIO_ERROR_INVALID_OPERATION, 0, "Invalid operation: cannot set edge on output GPIO"); + + if (gpio->u.cdev.edge == edge) + return 0; + + return _gpio_cdev_reopen(gpio, gpio->u.cdev.direction, edge, gpio->u.cdev.bias, gpio->u.cdev.drive, gpio->u.cdev.inverted); +} + +static int gpio_cdev_set_bias(gpio_t *gpio, gpio_bias_t bias) { + if (bias != GPIO_BIAS_DEFAULT && bias != GPIO_BIAS_PULL_UP && bias != GPIO_BIAS_PULL_DOWN && bias != GPIO_BIAS_DISABLE) + return _gpio_error(gpio, GPIO_ERROR_ARG, 0, "Invalid GPIO line bias (can be default, pull_up, pull_down, disable)"); + + if (gpio->u.cdev.bias == bias) + return 0; + + return _gpio_cdev_reopen(gpio, gpio->u.cdev.direction, gpio->u.cdev.edge, bias, gpio->u.cdev.drive, gpio->u.cdev.inverted); +} + +static int gpio_cdev_set_drive(gpio_t *gpio, gpio_drive_t drive) { + if (drive != GPIO_DRIVE_DEFAULT && drive != GPIO_DRIVE_OPEN_DRAIN && drive != GPIO_DRIVE_OPEN_SOURCE) + return _gpio_error(gpio, GPIO_ERROR_ARG, 0, "Invalid GPIO line drive (can be default, open_drain, open_source)"); + + if (gpio->u.cdev.direction != GPIO_DIR_OUT && drive != GPIO_DRIVE_DEFAULT) + return _gpio_error(gpio, GPIO_ERROR_INVALID_OPERATION, 0, "Invalid operation: cannot set line drive on input GPIO"); + + if (gpio->u.cdev.drive == drive) + return 0; + + return _gpio_cdev_reopen(gpio, gpio->u.cdev.direction, gpio->u.cdev.edge, gpio->u.cdev.bias, drive, gpio->u.cdev.inverted); +} + +static int gpio_cdev_set_inverted(gpio_t *gpio, bool inverted) { + if (gpio->u.cdev.inverted == inverted) + return 0; + + return _gpio_cdev_reopen(gpio, gpio->u.cdev.direction, gpio->u.cdev.edge, gpio->u.cdev.bias, gpio->u.cdev.drive, inverted); +} + +static unsigned int gpio_cdev_line(gpio_t *gpio) { + return gpio->u.cdev.line; +} + +static int gpio_cdev_fd(gpio_t *gpio) { + return gpio->u.cdev.line_fd; +} + +static int gpio_cdev_name(gpio_t *gpio, char *str, size_t len) { + struct gpioline_info line_info = {0}; + + if (!len) + return 0; + + line_info.line_offset = gpio->u.cdev.line; + + if (ioctl(gpio->u.cdev.chip_fd, GPIO_GET_LINEINFO_IOCTL, &line_info) < 0) + return _gpio_error(gpio, GPIO_ERROR_QUERY, errno, "Querying GPIO line info for line %u", gpio->u.cdev.line); + + strncpy(str, line_info.name, len - 1); + str[len - 1] = '\0'; + + return 0; +} + +static int gpio_cdev_label(gpio_t *gpio, char *str, size_t len) { + struct gpioline_info line_info = {0}; + + if (!len) + return 0; + + line_info.line_offset = gpio->u.cdev.line; + + if (ioctl(gpio->u.cdev.chip_fd, GPIO_GET_LINEINFO_IOCTL, &line_info) < 0) + return _gpio_error(gpio, GPIO_ERROR_QUERY, errno, "Querying GPIO line info for line %u", gpio->u.cdev.line); + + strncpy(str, line_info.consumer, len - 1); + str[len - 1] = '\0'; + + return 0; +} + +static int gpio_cdev_chip_fd(gpio_t *gpio) { + return gpio->u.cdev.chip_fd; +} + +static int gpio_cdev_chip_name(gpio_t *gpio, char *str, size_t len) { + struct gpiochip_info chip_info = {0}; + + if (!len) + return 0; + + if (ioctl(gpio->u.cdev.chip_fd, GPIO_GET_CHIPINFO_IOCTL, &chip_info) < 0) + return _gpio_error(gpio, GPIO_ERROR_QUERY, errno, "Querying GPIO chip info"); + + strncpy(str, chip_info.name, len - 1); + str[len - 1] = '\0'; + + return 0; +} + +static int gpio_cdev_chip_label(gpio_t *gpio, char *str, size_t len) { + struct gpiochip_info chip_info = {0}; + + if (!len) + return 0; + + if (ioctl(gpio->u.cdev.chip_fd, GPIO_GET_CHIPINFO_IOCTL, &chip_info) < 0) + return _gpio_error(gpio, GPIO_ERROR_QUERY, errno, "Querying GPIO chip info"); + + strncpy(str, chip_info.label, len - 1); + str[len - 1] = '\0'; + + return 0; +} + +static int gpio_cdev_tostring(gpio_t *gpio, char *str, size_t len) { + gpio_direction_t direction; + const char *direction_str; + gpio_edge_t edge; + const char *edge_str; + gpio_bias_t bias; + const char *bias_str; + gpio_drive_t drive; + const char *drive_str; + bool inverted; + const char *inverted_str; + char line_name[32]; + const char *line_name_str; + char line_label[32]; + const char *line_label_str; + char chip_name[32]; + const char *chip_name_str; + char chip_label[32]; + const char *chip_label_str; + + if (gpio_cdev_get_direction(gpio, &direction) < 0) + direction_str = ""; + else + direction_str = (direction == GPIO_DIR_IN) ? "in" : + (direction == GPIO_DIR_OUT) ? "out" : "unknown"; + + if (gpio_cdev_get_edge(gpio, &edge) < 0) + edge_str = ""; + else + edge_str = (edge == GPIO_EDGE_NONE) ? "none" : + (edge == GPIO_EDGE_RISING) ? "rising" : + (edge == GPIO_EDGE_FALLING) ? "falling" : + (edge == GPIO_EDGE_BOTH) ? "both" : "unknown"; + + if (gpio_cdev_get_bias(gpio, &bias) < 0) + bias_str = ""; + else + bias_str = (bias == GPIO_BIAS_DEFAULT) ? "default" : + (bias == GPIO_BIAS_PULL_UP) ? "pull_up" : + (bias == GPIO_BIAS_PULL_DOWN) ? "pull_down" : + (bias == GPIO_BIAS_DISABLE) ? "disable" : "unknown"; + + if (gpio_cdev_get_drive(gpio, &drive) < 0) + drive_str = ""; + else + drive_str = (drive == GPIO_DRIVE_DEFAULT) ? "default" : + (drive == GPIO_DRIVE_OPEN_DRAIN) ? "open_drain" : + (drive == GPIO_DRIVE_OPEN_SOURCE) ? "open_source" : "unknown"; + + if (gpio_cdev_get_inverted(gpio, &inverted) < 0) + inverted_str = ""; + else + inverted_str = inverted ? "true" : "false"; + + if (gpio_cdev_name(gpio, line_name, sizeof(line_name)) < 0) + line_name_str = ""; + else + line_name_str = line_name; + + if (gpio_cdev_label(gpio, line_label, sizeof(line_label)) < 0) + line_label_str = ""; + else + line_label_str = line_label; + + if (gpio_cdev_chip_name(gpio, chip_name, sizeof(chip_name)) < 0) + chip_name_str = ""; + else + chip_name_str = chip_name; + + if (gpio_cdev_chip_label(gpio, chip_label, sizeof(chip_label)) < 0) + chip_label_str = ""; + else + chip_label_str = chip_label; + + return snprintf(str, len, "GPIO %u (name=\"%s\", label=\"%s\", line_fd=%d, chip_fd=%d, direction=%s, edge=%s, bias=%s, drive=%s, inverted=%s, chip_name=\"%s\", chip_label=\"%s\", type=cdev)", + gpio->u.cdev.line, line_name_str, line_label_str, gpio->u.cdev.line_fd, gpio->u.cdev.chip_fd, direction_str, edge_str, bias_str, drive_str, inverted_str, chip_name_str, chip_label_str); +} + +const struct gpio_ops gpio_cdev_ops = { + .read = gpio_cdev_read, + .write = gpio_cdev_write, + .read_event = gpio_cdev_read_event, + .poll = gpio_cdev_poll, + .close = gpio_cdev_close, + .get_direction = gpio_cdev_get_direction, + .get_edge = gpio_cdev_get_edge, + .get_bias = gpio_cdev_get_bias, + .get_drive = gpio_cdev_get_drive, + .get_inverted = gpio_cdev_get_inverted, + .set_direction = gpio_cdev_set_direction, + .set_edge = gpio_cdev_set_edge, + .set_bias = gpio_cdev_set_bias, + .set_drive = gpio_cdev_set_drive, + .set_inverted = gpio_cdev_set_inverted, + .line = gpio_cdev_line, + .fd = gpio_cdev_fd, + .name = gpio_cdev_name, + .label = gpio_cdev_label, + .chip_fd = gpio_cdev_chip_fd, + .chip_name = gpio_cdev_chip_name, + .chip_label = gpio_cdev_chip_label, + .tostring = gpio_cdev_tostring, +}; + +int gpio_open_advanced(gpio_t *gpio, const char *path, unsigned int line, const gpio_config_t *config) { + int ret, fd; + + if (config->direction != GPIO_DIR_IN && config->direction != GPIO_DIR_OUT && config->direction != GPIO_DIR_OUT_LOW && config->direction != GPIO_DIR_OUT_HIGH) + return _gpio_error(gpio, GPIO_ERROR_ARG, 0, "Invalid GPIO direction (can be in, out, low, high)"); + + if (config->edge != GPIO_EDGE_NONE && config->edge != GPIO_EDGE_RISING && config->edge != GPIO_EDGE_FALLING && config->edge != GPIO_EDGE_BOTH) + return _gpio_error(gpio, GPIO_ERROR_ARG, 0, "Invalid GPIO interrupt edge (can be none, rising, falling, both)"); + + if (config->direction != GPIO_DIR_IN && config->edge != GPIO_EDGE_NONE) + return _gpio_error(gpio, GPIO_ERROR_ARG, 0, "Invalid GPIO edge for output GPIO"); + + if (config->bias != GPIO_BIAS_DEFAULT && config->bias != GPIO_BIAS_PULL_UP && config->bias != GPIO_BIAS_PULL_DOWN && config->bias != GPIO_BIAS_DISABLE) + return _gpio_error(gpio, GPIO_ERROR_ARG, 0, "Invalid GPIO line bias (can be default, pull_up, pull_down, disable)"); + + if (config->drive != GPIO_DRIVE_DEFAULT && config->drive != GPIO_DRIVE_OPEN_DRAIN && config->drive != GPIO_DRIVE_OPEN_SOURCE) + return _gpio_error(gpio, GPIO_ERROR_ARG, 0, "Invalid GPIO line drive (can be default, open_drain, open_source)"); + + if (config->direction == GPIO_DIR_IN && config->drive != GPIO_DRIVE_DEFAULT) + return _gpio_error(gpio, GPIO_ERROR_ARG, 0, "Invalid GPIO line drive for input GPIO"); + + /* Open GPIO chip */ + if ((fd = open(path, 0)) < 0) + return _gpio_error(gpio, GPIO_ERROR_OPEN, errno, "Opening GPIO chip"); + + memset(gpio, 0, sizeof(gpio_t)); + gpio->ops = &gpio_cdev_ops; + gpio->u.cdev.line = line; + gpio->u.cdev.line_fd = -1; + gpio->u.cdev.chip_fd = fd; + strncpy(gpio->u.cdev.label, config->label ? config->label : "periphery", sizeof(gpio->u.cdev.label) - 1); + gpio->u.cdev.label[sizeof(gpio->u.cdev.label) - 1] = '\0'; + + /* Open GPIO line */ + ret = _gpio_cdev_reopen(gpio, config->direction, config->edge, config->bias, config->drive, config->inverted); + if (ret < 0) { + close(gpio->u.cdev.chip_fd); + gpio->u.cdev.chip_fd = -1; + return ret; + } + + return 0; +} + +int gpio_open_name_advanced(gpio_t *gpio, const char *path, const char *name, const gpio_config_t *config) { + int fd; + + /* Open GPIO chip */ + if ((fd = open(path, 0)) < 0) + return _gpio_error(gpio, GPIO_ERROR_OPEN, errno, "Opening GPIO chip"); + + /* Get chip info for number of lines */ + struct gpiochip_info chip_info = {0}; + if (ioctl(fd, GPIO_GET_CHIPINFO_IOCTL, &chip_info) < 0) { + int errsv = errno; + close(fd); + return _gpio_error(gpio, GPIO_ERROR_QUERY, errsv, "Querying GPIO chip info"); + } + + /* Loop through every line */ + struct gpioline_info line_info = {0}; + unsigned int line; + for (line = 0; line < chip_info.lines; line++) { + line_info.line_offset = line; + + /* Get the line info */ + if (ioctl(fd, GPIO_GET_LINEINFO_IOCTL, &line_info) < 0) { + int errsv = errno; + close(fd); + return _gpio_error(gpio, GPIO_ERROR_QUERY, errsv, "Querying GPIO line info for line %u", line); + } + + /* Compare the name */ + if (strcmp(line_info.name, name) == 0) + break; + } + + /* If no matching line name was found */ + if (line == chip_info.lines) { + close(fd); + return _gpio_error(gpio, GPIO_ERROR_NOT_FOUND, 0, "GPIO line \"%s\" not found by name", name); + } + + if (close(fd) < 0) + return _gpio_error(gpio, GPIO_ERROR_CLOSE, errno, "Closing GPIO chip"); + + return gpio_open_advanced(gpio, path, line, config); +} + +int gpio_open(gpio_t *gpio, const char *path, unsigned int line, gpio_direction_t direction) { + gpio_config_t config = { + .direction = direction, + .edge = GPIO_EDGE_NONE, + .bias = GPIO_BIAS_DEFAULT, + .drive = GPIO_DRIVE_DEFAULT, + .inverted = false, + .label = NULL, + }; + + return gpio_open_advanced(gpio, path, line, &config); +} + +int gpio_open_name(gpio_t *gpio, const char *path, const char *name, gpio_direction_t direction) { + gpio_config_t config = { + .direction = direction, + .edge = GPIO_EDGE_NONE, + .bias = GPIO_BIAS_DEFAULT, + .drive = GPIO_DRIVE_DEFAULT, + .inverted = false, + .label = NULL, + }; + + return gpio_open_name_advanced(gpio, path, name, &config); +} + +#endif + diff --git a/third_party/periphery/src/gpio_cdev_v2.c b/third_party/periphery/src/gpio_cdev_v2.c new file mode 100644 index 000000000..761c74dec --- /dev/null +++ b/third_party/periphery/src/gpio_cdev_v2.c @@ -0,0 +1,568 @@ +/* + * c-periphery + * https://github.com/vsergeev/c-periphery + * License: MIT + */ + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "gpio.h" +#include "gpio_internal.h" + +#if PERIPHERY_GPIO_CDEV_SUPPORT == 2 +#include +#endif + +/*********************************************************************************/ +/* cdev v2 implementation */ +/*********************************************************************************/ + +#if PERIPHERY_GPIO_CDEV_SUPPORT == 2 + +static int _gpio_cdev_reopen(gpio_t *gpio, gpio_direction_t direction, gpio_edge_t edge, gpio_bias_t bias, gpio_drive_t drive, bool inverted) { + uint32_t flags = 0; + + if (bias == GPIO_BIAS_PULL_UP) + flags |= GPIO_V2_LINE_FLAG_BIAS_PULL_UP; + else if (bias == GPIO_BIAS_PULL_DOWN) + flags |= GPIO_V2_LINE_FLAG_BIAS_PULL_DOWN; + else if (bias == GPIO_BIAS_DISABLE) + flags |= GPIO_V2_LINE_FLAG_BIAS_DISABLED; + + if (drive == GPIO_DRIVE_OPEN_DRAIN) + flags |= GPIO_V2_LINE_FLAG_OPEN_DRAIN; + else if (drive == GPIO_DRIVE_OPEN_SOURCE) + flags |= GPIO_V2_LINE_FLAG_OPEN_SOURCE; + + if (inverted) + flags |= GPIO_V2_LINE_FLAG_ACTIVE_LOW; + + /* FIXME this should really use GPIO_V2_LINE_SET_CONFIG_IOCTL instead of + * closing and reopening, especially to preserve output value on + * configuration changes */ + + if (gpio->u.cdev.line_fd >= 0) { + if (close(gpio->u.cdev.line_fd) < 0) + return _gpio_error(gpio, GPIO_ERROR_CLOSE, errno, "Closing GPIO line"); + + gpio->u.cdev.line_fd = -1; + } + + if (direction == GPIO_DIR_IN) { + struct gpio_v2_line_request line_request = {0}; + + flags |= GPIO_V2_LINE_FLAG_INPUT; + flags |= (edge == GPIO_EDGE_RISING) ? GPIO_V2_LINE_FLAG_EDGE_RISING : + (edge == GPIO_EDGE_FALLING) ? GPIO_V2_LINE_FLAG_EDGE_FALLING : + (edge == GPIO_EDGE_BOTH) ? (GPIO_V2_LINE_FLAG_EDGE_RISING | GPIO_V2_LINE_FLAG_EDGE_FALLING) : 0; + flags |= (edge != GPIO_EDGE_NONE) ? GPIO_V2_LINE_FLAG_EVENT_CLOCK_REALTIME : 0; + + line_request.offsets[0] = gpio->u.cdev.line; + strncpy(line_request.consumer, gpio->u.cdev.label, sizeof(line_request.consumer) - 1); + line_request.consumer[sizeof(line_request.consumer) - 1] = '\0'; + line_request.config.flags = flags; + line_request.num_lines = 1; + + if (ioctl(gpio->u.cdev.chip_fd, GPIO_V2_GET_LINE_IOCTL, &line_request) < 0) + return _gpio_error(gpio, GPIO_ERROR_OPEN, errno, "Opening input line handle"); + + gpio->u.cdev.line_fd = line_request.fd; + } else { + struct gpio_v2_line_request line_request = {0}; + + bool initial_value = (direction == GPIO_DIR_OUT_HIGH) ? true : false; + initial_value ^= inverted; + + flags |= GPIO_V2_LINE_FLAG_OUTPUT; + + line_request.offsets[0] = gpio->u.cdev.line; + strncpy(line_request.consumer, gpio->u.cdev.label, sizeof(line_request.consumer) - 1); + line_request.consumer[sizeof(line_request.consumer) - 1] = '\0'; + line_request.config.flags = flags; + line_request.config.num_attrs = 1; + line_request.config.attrs[0].attr.id = GPIO_V2_LINE_ATTR_ID_OUTPUT_VALUES; + line_request.config.attrs[0].attr.values = initial_value & 0x1; + line_request.config.attrs[0].mask = 1; + line_request.num_lines = 1; + + if (ioctl(gpio->u.cdev.chip_fd, GPIO_V2_GET_LINE_IOCTL, &line_request) < 0) + return _gpio_error(gpio, GPIO_ERROR_OPEN, errno, "Opening output line handle"); + + gpio->u.cdev.line_fd = line_request.fd; + } + + gpio->u.cdev.direction = (direction == GPIO_DIR_IN) ? GPIO_DIR_IN : GPIO_DIR_OUT; + gpio->u.cdev.edge = edge; + gpio->u.cdev.bias = bias; + gpio->u.cdev.drive = drive; + gpio->u.cdev.inverted = inverted; + + return 0; +} + +static int gpio_cdev_read(gpio_t *gpio, bool *value) { + struct gpio_v2_line_values line_values = {0, 1}; + + if (ioctl(gpio->u.cdev.line_fd, GPIO_V2_LINE_GET_VALUES_IOCTL, &line_values) < 0) + return _gpio_error(gpio, GPIO_ERROR_IO, errno, "Getting line value"); + + *value = line_values.bits & 0x1; + + return 0; +} + +static int gpio_cdev_write(gpio_t *gpio, bool value) { + struct gpio_v2_line_values line_values = {0, 1}; + + if (gpio->u.cdev.direction != GPIO_DIR_OUT) + return _gpio_error(gpio, GPIO_ERROR_INVALID_OPERATION, 0, "Invalid operation: cannot write to input GPIO"); + + line_values.bits = value & 0x1; + + if (ioctl(gpio->u.cdev.line_fd, GPIO_V2_LINE_SET_VALUES_IOCTL, &line_values) < 0) + return _gpio_error(gpio, GPIO_ERROR_IO, errno, "Setting line value"); + + return 0; +} + +static int gpio_cdev_read_event(gpio_t *gpio, gpio_edge_t *edge, uint64_t *timestamp) { + struct gpio_v2_line_event line_event = {0}; + + if (gpio->u.cdev.direction != GPIO_DIR_IN) + return _gpio_error(gpio, GPIO_ERROR_INVALID_OPERATION, 0, "Invalid operation: cannot read event of output GPIO"); + else if (gpio->u.cdev.edge == GPIO_EDGE_NONE) + return _gpio_error(gpio, GPIO_ERROR_INVALID_OPERATION, 0, "Invalid operation: GPIO edge not set"); + + if (read(gpio->u.cdev.line_fd, &line_event, sizeof(line_event)) < (ssize_t)sizeof(line_event)) + return _gpio_error(gpio, GPIO_ERROR_IO, errno, "Reading GPIO event"); + + if (edge) + *edge = (line_event.id == GPIO_V2_LINE_EVENT_RISING_EDGE) ? GPIO_EDGE_RISING : + (line_event.id == GPIO_V2_LINE_EVENT_FALLING_EDGE) ? GPIO_EDGE_FALLING : GPIO_EDGE_NONE; + if (timestamp) + *timestamp = line_event.timestamp_ns; + + return 0; +} + +static int gpio_cdev_poll(gpio_t *gpio, int timeout_ms) { + struct pollfd fds[1]; + int ret; + + if (gpio->u.cdev.direction != GPIO_DIR_IN) + return _gpio_error(gpio, GPIO_ERROR_INVALID_OPERATION, 0, "Invalid operation: cannot poll output GPIO"); + + fds[0].fd = gpio->u.cdev.line_fd; + fds[0].events = POLLIN | POLLPRI | POLLERR; + if ((ret = poll(fds, 1, timeout_ms)) < 0) + return _gpio_error(gpio, GPIO_ERROR_IO, errno, "Polling GPIO line"); + + return ret > 0; +} + +static int gpio_cdev_close(gpio_t *gpio) { + /* Close line fd */ + if (gpio->u.cdev.line_fd >= 0) { + if (close(gpio->u.cdev.line_fd) < 0) + return _gpio_error(gpio, GPIO_ERROR_CLOSE, errno, "Closing GPIO line"); + + gpio->u.cdev.line_fd = -1; + } + + /* Close chip fd */ + if (gpio->u.cdev.chip_fd >= 0) { + if (close(gpio->u.cdev.chip_fd) < 0) + return _gpio_error(gpio, GPIO_ERROR_CLOSE, errno, "Closing GPIO chip"); + + gpio->u.cdev.chip_fd = -1; + } + + gpio->u.cdev.edge = GPIO_EDGE_NONE; + gpio->u.cdev.direction = GPIO_DIR_IN; + + return 0; +} + +static int gpio_cdev_get_direction(gpio_t *gpio, gpio_direction_t *direction) { + *direction = gpio->u.cdev.direction; + return 0; +} + +static int gpio_cdev_get_edge(gpio_t *gpio, gpio_edge_t *edge) { + *edge = gpio->u.cdev.edge; + return 0; +} + +static int gpio_cdev_get_bias(gpio_t *gpio, gpio_bias_t *bias) { + *bias = gpio->u.cdev.bias; + return 0; +} + +static int gpio_cdev_get_drive(gpio_t *gpio, gpio_drive_t *drive) { + *drive = gpio->u.cdev.drive; + return 0; +} + +static int gpio_cdev_get_inverted(gpio_t *gpio, bool *inverted) { + *inverted = gpio->u.cdev.inverted; + return 0; +} + +static int gpio_cdev_set_direction(gpio_t *gpio, gpio_direction_t direction) { + if (direction != GPIO_DIR_IN && direction != GPIO_DIR_OUT && direction != GPIO_DIR_OUT_LOW && direction != GPIO_DIR_OUT_HIGH) + return _gpio_error(gpio, GPIO_ERROR_ARG, 0, "Invalid GPIO direction (can be in, out, low, high)"); + + if (gpio->u.cdev.direction == direction) + return 0; + + return _gpio_cdev_reopen(gpio, direction, GPIO_EDGE_NONE, gpio->u.cdev.bias, gpio->u.cdev.drive, gpio->u.cdev.inverted); +} + +static int gpio_cdev_set_edge(gpio_t *gpio, gpio_edge_t edge) { + if (edge != GPIO_EDGE_NONE && edge != GPIO_EDGE_RISING && edge != GPIO_EDGE_FALLING && edge != GPIO_EDGE_BOTH) + return _gpio_error(gpio, GPIO_ERROR_ARG, 0, "Invalid GPIO interrupt edge (can be none, rising, falling, both)"); + + if (gpio->u.cdev.direction != GPIO_DIR_IN) + return _gpio_error(gpio, GPIO_ERROR_INVALID_OPERATION, 0, "Invalid operation: cannot set edge on output GPIO"); + + if (gpio->u.cdev.edge == edge) + return 0; + + return _gpio_cdev_reopen(gpio, gpio->u.cdev.direction, edge, gpio->u.cdev.bias, gpio->u.cdev.drive, gpio->u.cdev.inverted); +} + +static int gpio_cdev_set_bias(gpio_t *gpio, gpio_bias_t bias) { + if (bias != GPIO_BIAS_DEFAULT && bias != GPIO_BIAS_PULL_UP && bias != GPIO_BIAS_PULL_DOWN && bias != GPIO_BIAS_DISABLE) + return _gpio_error(gpio, GPIO_ERROR_ARG, 0, "Invalid GPIO line bias (can be default, pull_up, pull_down, disable)"); + + if (gpio->u.cdev.bias == bias) + return 0; + + return _gpio_cdev_reopen(gpio, gpio->u.cdev.direction, gpio->u.cdev.edge, bias, gpio->u.cdev.drive, gpio->u.cdev.inverted); +} + +static int gpio_cdev_set_drive(gpio_t *gpio, gpio_drive_t drive) { + if (drive != GPIO_DRIVE_DEFAULT && drive != GPIO_DRIVE_OPEN_DRAIN && drive != GPIO_DRIVE_OPEN_SOURCE) + return _gpio_error(gpio, GPIO_ERROR_ARG, 0, "Invalid GPIO line drive (can be default, open_drain, open_source)"); + + if (gpio->u.cdev.direction != GPIO_DIR_OUT && drive != GPIO_DRIVE_DEFAULT) + return _gpio_error(gpio, GPIO_ERROR_INVALID_OPERATION, 0, "Invalid operation: cannot set line drive on input GPIO"); + + if (gpio->u.cdev.drive == drive) + return 0; + + return _gpio_cdev_reopen(gpio, gpio->u.cdev.direction, gpio->u.cdev.edge, gpio->u.cdev.bias, drive, gpio->u.cdev.inverted); +} + +static int gpio_cdev_set_inverted(gpio_t *gpio, bool inverted) { + if (gpio->u.cdev.inverted == inverted) + return 0; + + return _gpio_cdev_reopen(gpio, gpio->u.cdev.direction, gpio->u.cdev.edge, gpio->u.cdev.bias, gpio->u.cdev.drive, inverted); +} + +static unsigned int gpio_cdev_line(gpio_t *gpio) { + return gpio->u.cdev.line; +} + +static int gpio_cdev_fd(gpio_t *gpio) { + return gpio->u.cdev.line_fd; +} + +static int gpio_cdev_name(gpio_t *gpio, char *str, size_t len) { + struct gpio_v2_line_info line_info = {0}; + + if (!len) + return 0; + + line_info.offset = gpio->u.cdev.line; + + if (ioctl(gpio->u.cdev.chip_fd, GPIO_V2_GET_LINEINFO_IOCTL, &line_info) < 0) + return _gpio_error(gpio, GPIO_ERROR_QUERY, errno, "Querying GPIO line info for line %u", gpio->u.cdev.line); + + strncpy(str, line_info.name, len - 1); + str[len - 1] = '\0'; + + return 0; +} + +static int gpio_cdev_label(gpio_t *gpio, char *str, size_t len) { + struct gpio_v2_line_info line_info = {0}; + + if (!len) + return 0; + + line_info.offset = gpio->u.cdev.line; + + if (ioctl(gpio->u.cdev.chip_fd, GPIO_V2_GET_LINEINFO_IOCTL, &line_info) < 0) + return _gpio_error(gpio, GPIO_ERROR_QUERY, errno, "Querying GPIO line info for line %u", gpio->u.cdev.line); + + strncpy(str, line_info.consumer, len - 1); + str[len - 1] = '\0'; + + return 0; +} + +static int gpio_cdev_chip_fd(gpio_t *gpio) { + return gpio->u.cdev.chip_fd; +} + +static int gpio_cdev_chip_name(gpio_t *gpio, char *str, size_t len) { + struct gpiochip_info chip_info = {0}; + + if (!len) + return 0; + + if (ioctl(gpio->u.cdev.chip_fd, GPIO_GET_CHIPINFO_IOCTL, &chip_info) < 0) + return _gpio_error(gpio, GPIO_ERROR_QUERY, errno, "Querying GPIO chip info"); + + strncpy(str, chip_info.name, len - 1); + str[len - 1] = '\0'; + + return 0; +} + +static int gpio_cdev_chip_label(gpio_t *gpio, char *str, size_t len) { + struct gpiochip_info chip_info = {0}; + + if (!len) + return 0; + + if (ioctl(gpio->u.cdev.chip_fd, GPIO_GET_CHIPINFO_IOCTL, &chip_info) < 0) + return _gpio_error(gpio, GPIO_ERROR_QUERY, errno, "Querying GPIO chip info"); + + strncpy(str, chip_info.label, len - 1); + str[len - 1] = '\0'; + + return 0; +} + +static int gpio_cdev_tostring(gpio_t *gpio, char *str, size_t len) { + gpio_direction_t direction; + const char *direction_str; + gpio_edge_t edge; + const char *edge_str; + gpio_bias_t bias; + const char *bias_str; + gpio_drive_t drive; + const char *drive_str; + bool inverted; + const char *inverted_str; + char line_name[32]; + const char *line_name_str; + char line_label[32]; + const char *line_label_str; + char chip_name[32]; + const char *chip_name_str; + char chip_label[32]; + const char *chip_label_str; + + if (gpio_cdev_get_direction(gpio, &direction) < 0) + direction_str = ""; + else + direction_str = (direction == GPIO_DIR_IN) ? "in" : + (direction == GPIO_DIR_OUT) ? "out" : "unknown"; + + if (gpio_cdev_get_edge(gpio, &edge) < 0) + edge_str = ""; + else + edge_str = (edge == GPIO_EDGE_NONE) ? "none" : + (edge == GPIO_EDGE_RISING) ? "rising" : + (edge == GPIO_EDGE_FALLING) ? "falling" : + (edge == GPIO_EDGE_BOTH) ? "both" : "unknown"; + + if (gpio_cdev_get_bias(gpio, &bias) < 0) + bias_str = ""; + else + bias_str = (bias == GPIO_BIAS_DEFAULT) ? "default" : + (bias == GPIO_BIAS_PULL_UP) ? "pull_up" : + (bias == GPIO_BIAS_PULL_DOWN) ? "pull_down" : + (bias == GPIO_BIAS_DISABLE) ? "disable" : "unknown"; + + if (gpio_cdev_get_drive(gpio, &drive) < 0) + drive_str = ""; + else + drive_str = (drive == GPIO_DRIVE_DEFAULT) ? "default" : + (drive == GPIO_DRIVE_OPEN_DRAIN) ? "open_drain" : + (drive == GPIO_DRIVE_OPEN_SOURCE) ? "open_source" : "unknown"; + + if (gpio_cdev_get_inverted(gpio, &inverted) < 0) + inverted_str = ""; + else + inverted_str = inverted ? "true" : "false"; + + if (gpio_cdev_name(gpio, line_name, sizeof(line_name)) < 0) + line_name_str = ""; + else + line_name_str = line_name; + + if (gpio_cdev_label(gpio, line_label, sizeof(line_label)) < 0) + line_label_str = ""; + else + line_label_str = line_label; + + if (gpio_cdev_chip_name(gpio, chip_name, sizeof(chip_name)) < 0) + chip_name_str = ""; + else + chip_name_str = chip_name; + + if (gpio_cdev_chip_label(gpio, chip_label, sizeof(chip_label)) < 0) + chip_label_str = ""; + else + chip_label_str = chip_label; + + return snprintf(str, len, "GPIO %u (name=\"%s\", label=\"%s\", line_fd=%d, chip_fd=%d, direction=%s, edge=%s, bias=%s, drive=%s, inverted=%s, chip_name=\"%s\", chip_label=\"%s\", type=cdev)", + gpio->u.cdev.line, line_name_str, line_label_str, gpio->u.cdev.line_fd, gpio->u.cdev.chip_fd, direction_str, edge_str, bias_str, drive_str, inverted_str, chip_name_str, chip_label_str); +} + +const struct gpio_ops gpio_cdev_ops = { + .read = gpio_cdev_read, + .write = gpio_cdev_write, + .read_event = gpio_cdev_read_event, + .poll = gpio_cdev_poll, + .close = gpio_cdev_close, + .get_direction = gpio_cdev_get_direction, + .get_edge = gpio_cdev_get_edge, + .get_bias = gpio_cdev_get_bias, + .get_drive = gpio_cdev_get_drive, + .get_inverted = gpio_cdev_get_inverted, + .set_direction = gpio_cdev_set_direction, + .set_edge = gpio_cdev_set_edge, + .set_bias = gpio_cdev_set_bias, + .set_drive = gpio_cdev_set_drive, + .set_inverted = gpio_cdev_set_inverted, + .line = gpio_cdev_line, + .fd = gpio_cdev_fd, + .name = gpio_cdev_name, + .label = gpio_cdev_label, + .chip_fd = gpio_cdev_chip_fd, + .chip_name = gpio_cdev_chip_name, + .chip_label = gpio_cdev_chip_label, + .tostring = gpio_cdev_tostring, +}; + +int gpio_open_advanced(gpio_t *gpio, const char *path, unsigned int line, const gpio_config_t *config) { + int ret, fd; + + if (config->direction != GPIO_DIR_IN && config->direction != GPIO_DIR_OUT && config->direction != GPIO_DIR_OUT_LOW && config->direction != GPIO_DIR_OUT_HIGH) + return _gpio_error(gpio, GPIO_ERROR_ARG, 0, "Invalid GPIO direction (can be in, out, low, high)"); + + if (config->edge != GPIO_EDGE_NONE && config->edge != GPIO_EDGE_RISING && config->edge != GPIO_EDGE_FALLING && config->edge != GPIO_EDGE_BOTH) + return _gpio_error(gpio, GPIO_ERROR_ARG, 0, "Invalid GPIO interrupt edge (can be none, rising, falling, both)"); + + if (config->direction != GPIO_DIR_IN && config->edge != GPIO_EDGE_NONE) + return _gpio_error(gpio, GPIO_ERROR_ARG, 0, "Invalid GPIO edge for output GPIO"); + + if (config->bias != GPIO_BIAS_DEFAULT && config->bias != GPIO_BIAS_PULL_UP && config->bias != GPIO_BIAS_PULL_DOWN && config->bias != GPIO_BIAS_DISABLE) + return _gpio_error(gpio, GPIO_ERROR_ARG, 0, "Invalid GPIO line bias (can be default, pull_up, pull_down, disable)"); + + if (config->drive != GPIO_DRIVE_DEFAULT && config->drive != GPIO_DRIVE_OPEN_DRAIN && config->drive != GPIO_DRIVE_OPEN_SOURCE) + return _gpio_error(gpio, GPIO_ERROR_ARG, 0, "Invalid GPIO line drive (can be default, open_drain, open_source)"); + + if (config->direction == GPIO_DIR_IN && config->drive != GPIO_DRIVE_DEFAULT) + return _gpio_error(gpio, GPIO_ERROR_ARG, 0, "Invalid GPIO line drive for input GPIO"); + + /* Open GPIO chip */ + if ((fd = open(path, 0)) < 0) + return _gpio_error(gpio, GPIO_ERROR_OPEN, errno, "Opening GPIO chip"); + + memset(gpio, 0, sizeof(gpio_t)); + gpio->ops = &gpio_cdev_ops; + gpio->u.cdev.line = line; + gpio->u.cdev.line_fd = -1; + gpio->u.cdev.chip_fd = fd; + strncpy(gpio->u.cdev.label, config->label ? config->label : "periphery", sizeof(gpio->u.cdev.label) - 1); + gpio->u.cdev.label[sizeof(gpio->u.cdev.label) - 1] = '\0'; + + /* Open GPIO line */ + ret = _gpio_cdev_reopen(gpio, config->direction, config->edge, config->bias, config->drive, config->inverted); + if (ret < 0) { + close(gpio->u.cdev.chip_fd); + gpio->u.cdev.chip_fd = -1; + return ret; + } + + return 0; +} + +int gpio_open_name_advanced(gpio_t *gpio, const char *path, const char *name, const gpio_config_t *config) { + int fd; + + /* Open GPIO chip */ + if ((fd = open(path, 0)) < 0) + return _gpio_error(gpio, GPIO_ERROR_OPEN, errno, "Opening GPIO chip"); + + /* Get chip info for number of lines */ + struct gpiochip_info chip_info = {0}; + if (ioctl(fd, GPIO_GET_CHIPINFO_IOCTL, &chip_info) < 0) { + int errsv = errno; + close(fd); + return _gpio_error(gpio, GPIO_ERROR_QUERY, errsv, "Querying GPIO chip info"); + } + + /* Loop through every line */ + struct gpio_v2_line_info line_info = {0}; + unsigned int line; + for (line = 0; line < chip_info.lines; line++) { + line_info.offset = line; + + /* Get the line info */ + if (ioctl(fd, GPIO_V2_GET_LINEINFO_IOCTL, &line_info) < 0) { + int errsv = errno; + close(fd); + return _gpio_error(gpio, GPIO_ERROR_QUERY, errsv, "Querying GPIO line info for line %u", line); + } + + /* Compare the name */ + if (strcmp(line_info.name, name) == 0) + break; + } + + /* If no matching line name was found */ + if (line == chip_info.lines) { + close(fd); + return _gpio_error(gpio, GPIO_ERROR_NOT_FOUND, 0, "GPIO line \"%s\" not found by name", name); + } + + if (close(fd) < 0) + return _gpio_error(gpio, GPIO_ERROR_CLOSE, errno, "Closing GPIO chip"); + + return gpio_open_advanced(gpio, path, line, config); +} + +int gpio_open(gpio_t *gpio, const char *path, unsigned int line, gpio_direction_t direction) { + gpio_config_t config = { + .direction = direction, + .edge = GPIO_EDGE_NONE, + .bias = GPIO_BIAS_DEFAULT, + .drive = GPIO_DRIVE_DEFAULT, + .inverted = false, + .label = NULL, + }; + + return gpio_open_advanced(gpio, path, line, &config); +} + +int gpio_open_name(gpio_t *gpio, const char *path, const char *name, gpio_direction_t direction) { + gpio_config_t config = { + .direction = direction, + .edge = GPIO_EDGE_NONE, + .bias = GPIO_BIAS_DEFAULT, + .drive = GPIO_DRIVE_DEFAULT, + .inverted = false, + .label = NULL, + }; + + return gpio_open_name_advanced(gpio, path, name, &config); +} + +#endif + diff --git a/third_party/periphery/src/gpio_internal.h b/third_party/periphery/src/gpio_internal.h new file mode 100644 index 000000000..d542402f3 --- /dev/null +++ b/third_party/periphery/src/gpio_internal.h @@ -0,0 +1,97 @@ +/* + * c-periphery + * https://github.com/vsergeev/c-periphery + * License: MIT + */ + +#ifndef _PERIPHERY_GPIO_INTERNAL_H +#define _PERIPHERY_GPIO_INTERNAL_H + +#include + +#include "gpio.h" + +/*********************************************************************************/ +/* Operations table and handle structure */ +/*********************************************************************************/ + +struct gpio_ops { + int (*read)(gpio_t *gpio, bool *value); + int (*write)(gpio_t *gpio, bool value); + int (*read_event)(gpio_t *gpio, gpio_edge_t *edge, uint64_t *timestamp); + int (*poll)(gpio_t *gpio, int timeout_ms); + int (*close)(gpio_t *gpio); + int (*get_direction)(gpio_t *gpio, gpio_direction_t *direction); + int (*get_edge)(gpio_t *gpio, gpio_edge_t *edge); + int (*get_bias)(gpio_t *gpio, gpio_bias_t *bias); + int (*get_drive)(gpio_t *gpio, gpio_drive_t *drive); + int (*get_inverted)(gpio_t *gpio, bool *inverted); + int (*set_direction)(gpio_t *gpio, gpio_direction_t direction); + int (*set_edge)(gpio_t *gpio, gpio_edge_t edge); + int (*set_bias)(gpio_t *gpio, gpio_bias_t bias); + int (*set_drive)(gpio_t *gpio, gpio_drive_t drive); + int (*set_inverted)(gpio_t *gpio, bool inverted); + unsigned int (*line)(gpio_t *gpio); + int (*fd)(gpio_t *gpio); + int (*name)(gpio_t *gpio, char *str, size_t len); + int (*label)(gpio_t *gpio, char *str, size_t len); + int (*chip_fd)(gpio_t *gpio); + int (*chip_name)(gpio_t *gpio, char *str, size_t len); + int (*chip_label)(gpio_t *gpio, char *str, size_t len); + int (*tostring)(gpio_t *gpio, char *str, size_t len); +}; + +struct gpio_handle { + const struct gpio_ops *ops; + + union { + struct { + unsigned int line; + int line_fd; + int chip_fd; + gpio_direction_t direction; + gpio_edge_t edge; + gpio_bias_t bias; + gpio_drive_t drive; + bool inverted; + char label[32]; + } cdev; + struct { + unsigned int line; + int line_fd; + bool exported; + } sysfs; + } u; + + /* error state */ + struct { + int c_errno; + char errmsg[96]; + } error; +}; + +/*********************************************************************************/ +/* Common error formatting function */ +/*********************************************************************************/ + +inline static int _gpio_error(gpio_t *gpio, int code, int c_errno, const char *fmt, ...) { + va_list ap; + + gpio->error.c_errno = c_errno; + + va_start(ap, fmt); + vsnprintf(gpio->error.errmsg, sizeof(gpio->error.errmsg), fmt, ap); + va_end(ap); + + /* Tack on strerror() and errno */ + if (c_errno) { + char buf[64]; + strerror_r(c_errno, buf, sizeof(buf)); + snprintf(gpio->error.errmsg+strlen(gpio->error.errmsg), sizeof(gpio->error.errmsg)-strlen(gpio->error.errmsg), ": %s [errno %d]", buf, c_errno); + } + + return code; +} + +#endif + diff --git a/third_party/periphery/src/gpio_sysfs.c b/third_party/periphery/src/gpio_sysfs.c new file mode 100644 index 000000000..f40708c31 --- /dev/null +++ b/third_party/periphery/src/gpio_sysfs.c @@ -0,0 +1,601 @@ +/* + * c-periphery + * https://github.com/vsergeev/c-periphery + * License: MIT + */ + +#define _XOPEN_SOURCE 600 /* for usleep() */ + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "gpio.h" +#include "gpio_internal.h" + +/*********************************************************************************/ +/* sysfs implementation */ +/*********************************************************************************/ + +#define P_PATH_MAX 256 + +/* Delay between checks for successful GPIO export (100ms) */ +#define GPIO_SYSFS_OPEN_DELAY 100000 +/* Number of retries to check for successful GPIO exports */ +#define GPIO_SYSFS_OPEN_RETRIES 10 + +static int gpio_sysfs_close(gpio_t *gpio) { + char buf[16]; + int len, fd; + + if (gpio->u.sysfs.line_fd < 0) + return 0; + + /* Close fd */ + if (close(gpio->u.sysfs.line_fd) < 0) + return _gpio_error(gpio, GPIO_ERROR_CLOSE, errno, "Closing GPIO 'value'"); + + gpio->u.sysfs.line_fd = -1; + + /* Unexport the GPIO, if we exported it */ + if (gpio->u.sysfs.exported) { + len = snprintf(buf, sizeof(buf), "%u\n", gpio->u.sysfs.line); + + if ((fd = open("/sys/class/gpio/unexport", O_WRONLY)) < 0) + return _gpio_error(gpio, GPIO_ERROR_CLOSE, errno, "Closing GPIO: opening 'unexport'"); + + if (write(fd, buf, len) < 0) { + int errsv = errno; + close(fd); + return _gpio_error(gpio, GPIO_ERROR_CLOSE, errsv, "Closing GPIO: writing 'unexport'"); + } + + if (close(fd) < 0) + return _gpio_error(gpio, GPIO_ERROR_CLOSE, errno, "Closing GPIO: closing 'unexport'"); + + gpio->u.sysfs.exported = false; + } + + return 0; +} + +static int gpio_sysfs_read(gpio_t *gpio, bool *value) { + char buf[2]; + + /* Read fd */ + if (read(gpio->u.sysfs.line_fd, buf, 2) < 0) + return _gpio_error(gpio, GPIO_ERROR_IO, errno, "Reading GPIO 'value'"); + + /* Rewind */ + if (lseek(gpio->u.sysfs.line_fd, 0, SEEK_SET) < 0) + return _gpio_error(gpio, GPIO_ERROR_IO, errno, "Rewinding GPIO 'value'"); + + if (buf[0] == '0') + *value = false; + else if (buf[0] == '1') + *value = true; + else + return _gpio_error(gpio, GPIO_ERROR_IO, 0, "Unknown GPIO value"); + + return 0; +} + +static int gpio_sysfs_write(gpio_t *gpio, bool value) { + static const char *value_str[2] = {"0\n", "1\n"}; + + /* Write fd */ + if (write(gpio->u.sysfs.line_fd, value_str[value], 2) < 0) + return _gpio_error(gpio, GPIO_ERROR_IO, errno, "Writing GPIO 'value'"); + + /* Rewind */ + if (lseek(gpio->u.sysfs.line_fd, 0, SEEK_SET) < 0) + return _gpio_error(gpio, GPIO_ERROR_IO, errno, "Rewinding GPIO 'value'"); + + return 0; +} + +static int gpio_sysfs_read_event(gpio_t *gpio, gpio_edge_t *edge, uint64_t *timestamp) { + (void)edge; + (void)timestamp; + return _gpio_error(gpio, GPIO_ERROR_UNSUPPORTED, 0, "GPIO of type sysfs does not support read event"); +} + +static int gpio_sysfs_poll(gpio_t *gpio, int timeout_ms) { + struct pollfd fds[1]; + int ret; + + /* Poll */ + fds[0].fd = gpio->u.sysfs.line_fd; + fds[0].events = POLLPRI | POLLERR; + if ((ret = poll(fds, 1, timeout_ms)) < 0) + return _gpio_error(gpio, GPIO_ERROR_IO, errno, "Polling GPIO 'value'"); + + /* GPIO edge interrupt occurred */ + if (ret) { + /* Rewind */ + if (lseek(gpio->u.sysfs.line_fd, 0, SEEK_SET) < 0) + return _gpio_error(gpio, GPIO_ERROR_IO, errno, "Rewinding GPIO 'value'"); + + return 1; + } + + /* Timed out */ + return 0; +} + +static int gpio_sysfs_set_direction(gpio_t *gpio, gpio_direction_t direction) { + char gpio_path[P_PATH_MAX]; + const char *buf; + int fd; + + if (direction == GPIO_DIR_IN) + buf = "in\n"; + else if (direction == GPIO_DIR_OUT) + buf = "out\n"; + else if (direction == GPIO_DIR_OUT_LOW) + buf = "low\n"; + else if (direction == GPIO_DIR_OUT_HIGH) + buf = "high\n"; + else + return _gpio_error(gpio, GPIO_ERROR_ARG, 0, "Invalid GPIO direction (can be in, out, low, high)"); + + /* Write direction */ + snprintf(gpio_path, sizeof(gpio_path), "/sys/class/gpio/gpio%u/direction", gpio->u.sysfs.line); + + if ((fd = open(gpio_path, O_WRONLY)) < 0) + return _gpio_error(gpio, GPIO_ERROR_CONFIGURE, errno, "Opening GPIO 'direction'"); + + if (write(fd, buf, strlen(buf)) < 0) { + int errsv = errno; + close(fd); + return _gpio_error(gpio, GPIO_ERROR_CONFIGURE, errsv, "Writing GPIO 'direction'"); + } + + if (close(fd) < 0) + return _gpio_error(gpio, GPIO_ERROR_CONFIGURE, errno, "Closing GPIO 'direction'"); + + return 0; +} + +static int gpio_sysfs_get_direction(gpio_t *gpio, gpio_direction_t *direction) { + char gpio_path[P_PATH_MAX]; + char buf[8]; + int fd, ret; + + /* Read direction */ + snprintf(gpio_path, sizeof(gpio_path), "/sys/class/gpio/gpio%u/direction", gpio->u.sysfs.line); + + if ((fd = open(gpio_path, O_RDONLY)) < 0) + return _gpio_error(gpio, GPIO_ERROR_QUERY, errno, "Opening GPIO 'direction'"); + + if ((ret = read(fd, buf, sizeof(buf))) < 0) { + int errsv = errno; + close(fd); + return _gpio_error(gpio, GPIO_ERROR_QUERY, errsv, "Reading GPIO 'direction'"); + } + + if (close(fd) < 0) + return _gpio_error(gpio, GPIO_ERROR_QUERY, errno, "Closing GPIO 'direction'"); + + buf[ret] = '\0'; + + if (strcmp(buf, "in\n") == 0) + *direction = GPIO_DIR_IN; + else if (strcmp(buf, "out\n") == 0) + *direction = GPIO_DIR_OUT; + else + return _gpio_error(gpio, GPIO_ERROR_QUERY, 0, "Unknown GPIO direction"); + + return 0; +} + +static int gpio_sysfs_set_edge(gpio_t *gpio, gpio_edge_t edge) { + char gpio_path[P_PATH_MAX]; + const char *buf; + int fd; + + if (edge == GPIO_EDGE_NONE) + buf = "none\n"; + else if (edge == GPIO_EDGE_RISING) + buf = "rising\n"; + else if (edge == GPIO_EDGE_FALLING) + buf = "falling\n"; + else if (edge == GPIO_EDGE_BOTH) + buf = "both\n"; + else + return _gpio_error(gpio, GPIO_ERROR_ARG, 0, "Invalid GPIO interrupt edge (can be none, rising, falling, both)"); + + /* Write edge */ + snprintf(gpio_path, sizeof(gpio_path), "/sys/class/gpio/gpio%u/edge", gpio->u.sysfs.line); + + if ((fd = open(gpio_path, O_WRONLY)) < 0) + return _gpio_error(gpio, GPIO_ERROR_CONFIGURE, errno, "Opening GPIO 'edge'"); + + if (write(fd, buf, strlen(buf)) < 0) { + int errsv = errno; + close(fd); + return _gpio_error(gpio, GPIO_ERROR_CONFIGURE, errsv, "Writing GPIO 'edge'"); + } + + if (close(fd) < 0) + return _gpio_error(gpio, GPIO_ERROR_CONFIGURE, errno, "Closing GPIO 'edge'"); + + return 0; +} + +static int gpio_sysfs_get_edge(gpio_t *gpio, gpio_edge_t *edge) { + char gpio_path[P_PATH_MAX]; + char buf[16]; + int fd, ret; + + /* Read edge */ + snprintf(gpio_path, sizeof(gpio_path), "/sys/class/gpio/gpio%u/edge", gpio->u.sysfs.line); + + if ((fd = open(gpio_path, O_RDONLY)) < 0) + return _gpio_error(gpio, GPIO_ERROR_QUERY, errno, "Opening GPIO 'edge'"); + + if ((ret = read(fd, buf, sizeof(buf))) < 0) { + int errsv = errno; + close(fd); + return _gpio_error(gpio, GPIO_ERROR_QUERY, errsv, "Reading GPIO 'edge'"); + } + + if (close(fd) < 0) + return _gpio_error(gpio, GPIO_ERROR_QUERY, errno, "Closing GPIO 'edge'"); + + buf[ret] = '\0'; + + if (strcmp(buf, "none\n") == 0) + *edge = GPIO_EDGE_NONE; + else if (strcmp(buf, "rising\n") == 0) + *edge = GPIO_EDGE_RISING; + else if (strcmp(buf, "falling\n") == 0) + *edge = GPIO_EDGE_FALLING; + else if (strcmp(buf, "both\n") == 0) + *edge = GPIO_EDGE_BOTH; + else + return _gpio_error(gpio, GPIO_ERROR_QUERY, 0, "Unknown GPIO edge"); + + return 0; +} + +static int gpio_sysfs_set_bias(gpio_t *gpio, gpio_bias_t bias) { + (void)bias; + return _gpio_error(gpio, GPIO_ERROR_UNSUPPORTED, 0, "GPIO of type sysfs does not support line bias attribute"); +} + +static int gpio_sysfs_get_bias(gpio_t *gpio, gpio_bias_t *bias) { + (void)bias; + return _gpio_error(gpio, GPIO_ERROR_UNSUPPORTED, 0, "GPIO of type sysfs does not support line bias attribute"); +} + +static int gpio_sysfs_set_drive(gpio_t *gpio, gpio_drive_t drive) { + (void)drive; + return _gpio_error(gpio, GPIO_ERROR_UNSUPPORTED, 0, "GPIO of type sysfs does not support line drive attribute"); +} + +static int gpio_sysfs_get_drive(gpio_t *gpio, gpio_drive_t *drive) { + (void)drive; + return _gpio_error(gpio, GPIO_ERROR_UNSUPPORTED, 0, "GPIO of type sysfs does not support line drive attribute"); +} + +static int gpio_sysfs_set_inverted(gpio_t *gpio, bool inverted) { + char gpio_path[P_PATH_MAX]; + static const char *inverted_str[2] = {"0\n", "1\n"}; + int fd; + + /* Write active_low */ + snprintf(gpio_path, sizeof(gpio_path), "/sys/class/gpio/gpio%u/active_low", gpio->u.sysfs.line); + + if ((fd = open(gpio_path, O_WRONLY)) < 0) + return _gpio_error(gpio, GPIO_ERROR_CONFIGURE, errno, "Opening GPIO 'active_low'"); + + if (write(fd, inverted_str[inverted], strlen(inverted_str[inverted])) < 0) { + int errsv = errno; + close(fd); + return _gpio_error(gpio, GPIO_ERROR_CONFIGURE, errsv, "Writing GPIO 'active_low'"); + } + + if (close(fd) < 0) + return _gpio_error(gpio, GPIO_ERROR_CONFIGURE, errno, "Closing GPIO 'active_low'"); + + return 0; +} + +static int gpio_sysfs_get_inverted(gpio_t *gpio, bool *inverted) { + char gpio_path[P_PATH_MAX]; + char buf[4]; + int fd, ret; + + /* Read active_low */ + snprintf(gpio_path, sizeof(gpio_path), "/sys/class/gpio/gpio%u/active_low", gpio->u.sysfs.line); + + if ((fd = open(gpio_path, O_RDONLY)) < 0) + return _gpio_error(gpio, GPIO_ERROR_QUERY, errno, "Opening GPIO 'active_low'"); + + if ((ret = read(fd, buf, sizeof(buf))) < 0) { + int errsv = errno; + close(fd); + return _gpio_error(gpio, GPIO_ERROR_QUERY, errsv, "Reading GPIO 'active_low'"); + } + + if (close(fd) < 0) + return _gpio_error(gpio, GPIO_ERROR_QUERY, errno, "Closing GPIO 'active_low'"); + + buf[ret] = '\0'; + + if (buf[0] == '0') + *inverted = false; + else if (buf[0] == '1') + *inverted = true; + else + return _gpio_error(gpio, GPIO_ERROR_QUERY, 0, "Unknown GPIO active_low value"); + + return 0; +} + +static unsigned int gpio_sysfs_line(gpio_t *gpio) { + return gpio->u.sysfs.line; +} + +static int gpio_sysfs_fd(gpio_t *gpio) { + return gpio->u.sysfs.line_fd; +} + +static int gpio_sysfs_name(gpio_t *gpio, char *str, size_t len) { + (void)gpio; + if (len) + str[0] = '\0'; + + return 0; +} + +static int gpio_sysfs_label(gpio_t *gpio, char *str, size_t len) { + (void)gpio; + if (len) + str[0] = '\0'; + + return 0; +} + +static int gpio_sysfs_chip_fd(gpio_t *gpio) { + return _gpio_error(gpio, GPIO_ERROR_UNSUPPORTED, 0, "GPIO of type sysfs has no chip fd"); +} + +static int gpio_sysfs_chip_name(gpio_t *gpio, char *str, size_t len) { + int ret; + char gpio_path[P_PATH_MAX]; + char gpiochip_path[P_PATH_MAX]; + + if (!len) + return 0; + + /* Form path to device */ + snprintf(gpio_path, sizeof(gpio_path), "/sys/class/gpio/gpio%u/device", gpio->u.sysfs.line); + + /* Resolve symlink to gpiochip */ + if ((ret = readlink(gpio_path, gpiochip_path, sizeof(gpiochip_path))) < 0) + return _gpio_error(gpio, GPIO_ERROR_QUERY, errno, "Reading GPIO chip symlink"); + + /* Null-terminate symlink path */ + gpiochip_path[(ret < P_PATH_MAX) ? ret : (P_PATH_MAX - 1)] = '\0'; + + /* Find last / in symlink path */ + const char *sep = strrchr(gpiochip_path, '/'); + if (!sep) + return _gpio_error(gpio, GPIO_ERROR_QUERY, 0, "Invalid GPIO chip symlink"); + + strncpy(str, sep + 1, len - 1); + str[len - 1] = '\0'; + + return 0; +} + +static int gpio_sysfs_chip_label(gpio_t *gpio, char *str, size_t len) { + char gpio_path[P_PATH_MAX]; + char chip_name[32]; + int fd, ret; + + if (!len) + return 0; + + if ((ret = gpio_sysfs_chip_name(gpio, chip_name, sizeof(chip_name))) < 0) + return ret; + + /* Read gpiochip label */ + snprintf(gpio_path, sizeof(gpio_path), "/sys/class/gpio/%s/label", chip_name); + + if ((fd = open(gpio_path, O_RDONLY)) < 0) + return _gpio_error(gpio, GPIO_ERROR_QUERY, errno, "Opening GPIO chip 'label'"); + + if ((ret = read(fd, str, len)) < 0) { + int errsv = errno; + close(fd); + return _gpio_error(gpio, GPIO_ERROR_QUERY, errsv, "Reading GPIO chip 'label'"); + } + + if (close(fd) < 0) + return _gpio_error(gpio, GPIO_ERROR_QUERY, errno, "Closing GPIO 'label'"); + + str[ret - 1] = '\0'; + + return 0; +} + +static int gpio_sysfs_tostring(gpio_t *gpio, char *str, size_t len) { + gpio_direction_t direction; + const char *direction_str; + gpio_edge_t edge; + const char *edge_str; + bool inverted; + const char *inverted_str; + char chip_name[32]; + const char *chip_name_str; + char chip_label[32]; + const char *chip_label_str; + + if (gpio_sysfs_get_direction(gpio, &direction) < 0) + direction_str = ""; + else + direction_str = (direction == GPIO_DIR_IN) ? "in" : + (direction == GPIO_DIR_OUT) ? "out" : "unknown"; + + if (gpio_sysfs_get_edge(gpio, &edge) < 0) + edge_str = ""; + else + edge_str = (edge == GPIO_EDGE_NONE) ? "none" : + (edge == GPIO_EDGE_RISING) ? "rising" : + (edge == GPIO_EDGE_FALLING) ? "falling" : + (edge == GPIO_EDGE_BOTH) ? "both" : "unknown"; + + if (gpio_sysfs_get_inverted(gpio, &inverted) < 0) + inverted_str = ""; + else + inverted_str = inverted ? "true" : "false"; + + if (gpio_sysfs_chip_name(gpio, chip_name, sizeof(chip_name)) < 0) + chip_name_str = ""; + else + chip_name_str = chip_name; + + if (gpio_sysfs_chip_label(gpio, chip_label, sizeof(chip_label)) < 0) + chip_label_str = ""; + else + chip_label_str = chip_label; + + return snprintf(str, len, "GPIO %u (fd=%d, direction=%s, edge=%s, inverted=%s, chip_name=\"%s\", chip_label=\"%s\", type=sysfs)", + gpio->u.sysfs.line, gpio->u.sysfs.line_fd, direction_str, edge_str, inverted_str, chip_name_str, chip_label_str); +} + +const struct gpio_ops gpio_sysfs_ops = { + .read = gpio_sysfs_read, + .write = gpio_sysfs_write, + .read_event = gpio_sysfs_read_event, + .poll = gpio_sysfs_poll, + .close = gpio_sysfs_close, + .get_direction = gpio_sysfs_get_direction, + .get_edge = gpio_sysfs_get_edge, + .get_bias = gpio_sysfs_get_bias, + .get_drive = gpio_sysfs_get_drive, + .get_inverted = gpio_sysfs_get_inverted, + .set_direction = gpio_sysfs_set_direction, + .set_edge = gpio_sysfs_set_edge, + .set_bias = gpio_sysfs_set_bias, + .set_drive = gpio_sysfs_set_drive, + .set_inverted = gpio_sysfs_set_inverted, + .line = gpio_sysfs_line, + .fd = gpio_sysfs_fd, + .name = gpio_sysfs_name, + .label = gpio_sysfs_label, + .chip_fd = gpio_sysfs_chip_fd, + .chip_name = gpio_sysfs_chip_name, + .chip_label = gpio_sysfs_chip_label, + .tostring = gpio_sysfs_tostring, +}; + +int gpio_open_sysfs(gpio_t *gpio, unsigned int line, gpio_direction_t direction) { + char gpio_path[P_PATH_MAX]; + struct stat stat_buf; + char buf[16]; + int len, fd, ret; + bool exported = false; + + if (direction != GPIO_DIR_IN && direction != GPIO_DIR_OUT && direction != GPIO_DIR_OUT_LOW && direction != GPIO_DIR_OUT_HIGH) + return _gpio_error(gpio, GPIO_ERROR_ARG, 0, "Invalid GPIO direction (can be in, out, low, high)"); + + /* Check if GPIO directory exists */ + snprintf(gpio_path, sizeof(gpio_path), "/sys/class/gpio/gpio%u", line); + if (stat(gpio_path, &stat_buf) < 0) { + /* Write line number to export file */ + len = snprintf(buf, sizeof(buf), "%u\n", line); + + if ((fd = open("/sys/class/gpio/export", O_WRONLY)) < 0) + return _gpio_error(gpio, GPIO_ERROR_OPEN, errno, "Opening GPIO: opening 'export'"); + + if (write(fd, buf, len) < 0) { + int errsv = errno; + close(fd); + return _gpio_error(gpio, GPIO_ERROR_OPEN, errsv, "Opening GPIO: writing 'export'"); + } + + if (close(fd) < 0) + return _gpio_error(gpio, GPIO_ERROR_OPEN, errno, "Opening GPIO: closing 'export'"); + + /* Wait until GPIO directory appears */ + unsigned int retry_count; + for (retry_count = 0; retry_count < GPIO_SYSFS_OPEN_RETRIES; retry_count++) { + int ret = stat(gpio_path, &stat_buf); + if (ret == 0) { + exported = true; + break; + } else if (ret < 0 && errno != ENOENT) { + return _gpio_error(gpio, GPIO_ERROR_OPEN, errno, "Opening GPIO: stat 'gpio%u/' after export", line); + } + + usleep(GPIO_SYSFS_OPEN_DELAY); + } + + if (retry_count == GPIO_SYSFS_OPEN_RETRIES) + return _gpio_error(gpio, GPIO_ERROR_OPEN, 0, "Opening GPIO: waiting for 'gpio%u/' timed out", line); + + /* Write direction, looping in case of EACCES errors due to delayed + * udev permission rule application after export */ + const char *dir = (direction == GPIO_DIR_OUT) ? "out\n" : + (direction == GPIO_DIR_OUT_HIGH) ? "high\n" : + (direction == GPIO_DIR_OUT_LOW) ? "low\n" : "in\n"; + + snprintf(gpio_path, sizeof(gpio_path), "/sys/class/gpio/gpio%u/direction", line); + + for (retry_count = 0; retry_count < GPIO_SYSFS_OPEN_RETRIES; retry_count++) { + if ((fd = open(gpio_path, O_WRONLY)) >= 0) { + if (write(fd, dir, strlen(dir)) < 0) { + int errsv = errno; + close(fd); + return _gpio_error(gpio, GPIO_ERROR_CONFIGURE, errsv, "Writing GPIO 'direction'"); + } + + if (close(fd) < 0) + return _gpio_error(gpio, GPIO_ERROR_CONFIGURE, errno, "Closing GPIO 'direction'"); + + break; + } else if (errno != EACCES) { + return _gpio_error(gpio, GPIO_ERROR_OPEN, errno, "Opening GPIO: opening 'gpio%u/direction'", line); + } + + usleep(GPIO_SYSFS_OPEN_DELAY); + } + + if (retry_count == GPIO_SYSFS_OPEN_RETRIES) + return _gpio_error(gpio, GPIO_ERROR_OPEN, errno, "Opening GPIO: opening 'gpio%u/direction'", line); + } + + /* Open value */ + snprintf(gpio_path, sizeof(gpio_path), "/sys/class/gpio/gpio%u/value", line); + if ((fd = open(gpio_path, O_RDWR)) < 0) + return _gpio_error(gpio, GPIO_ERROR_OPEN, errno, "Opening GPIO 'gpio%u/value'", line); + + memset(gpio, 0, sizeof(gpio_t)); + gpio->ops = &gpio_sysfs_ops; + gpio->u.sysfs.line = line; + gpio->u.sysfs.line_fd = fd; + gpio->u.sysfs.exported = exported; + + if (!exported) { + ret = gpio_sysfs_set_direction(gpio, direction); + if (ret < 0) + return ret; + } + + ret = gpio_sysfs_set_inverted(gpio, false); + if (ret < 0) + return ret; + + return 0; +} + diff --git a/third_party/periphery/src/i2c.c b/third_party/periphery/src/i2c.c new file mode 100644 index 000000000..539297157 --- /dev/null +++ b/third_party/periphery/src/i2c.c @@ -0,0 +1,136 @@ +/* + * c-periphery + * https://github.com/vsergeev/c-periphery + * License: MIT + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include "i2c.h" + +struct i2c_handle { + int fd; + + struct { + int c_errno; + char errmsg[96]; + } error; +}; + +static int _i2c_error(i2c_t *i2c, int code, int c_errno, const char *fmt, ...) { + va_list ap; + + i2c->error.c_errno = c_errno; + + va_start(ap, fmt); + vsnprintf(i2c->error.errmsg, sizeof(i2c->error.errmsg), fmt, ap); + va_end(ap); + + /* Tack on strerror() and errno */ + if (c_errno) { + char buf[64]; + strerror_r(c_errno, buf, sizeof(buf)); + snprintf(i2c->error.errmsg+strlen(i2c->error.errmsg), sizeof(i2c->error.errmsg)-strlen(i2c->error.errmsg), ": %s [errno %d]", buf, c_errno); + } + + return code; +} + +i2c_t *i2c_new(void) { + i2c_t *i2c = calloc(1, sizeof(i2c_t)); + if (i2c == NULL) + return NULL; + + i2c->fd = -1; + + return i2c; +} + +void i2c_free(i2c_t *i2c) { + free(i2c); +} + +int i2c_open(i2c_t *i2c, const char *path) { + unsigned long supported_funcs; + + memset(i2c, 0, sizeof(i2c_t)); + + /* Open device */ + if ((i2c->fd = open(path, O_RDWR)) < 0) + return _i2c_error(i2c, I2C_ERROR_OPEN, errno, "Opening I2C device \"%s\"", path); + + /* Query supported functions */ + if (ioctl(i2c->fd, I2C_FUNCS, &supported_funcs) < 0) { + int errsv = errno; + close(i2c->fd); + i2c->fd = -1; + return _i2c_error(i2c, I2C_ERROR_QUERY, errsv, "Querying I2C functions"); + } + + if (!(supported_funcs & I2C_FUNC_I2C)) { + close(i2c->fd); + i2c->fd = -1; + return _i2c_error(i2c, I2C_ERROR_NOT_SUPPORTED, 0, "I2C not supported on %s", path); + } + + return 0; +} + +int i2c_transfer(i2c_t *i2c, struct i2c_msg *msgs, size_t count) { + struct i2c_rdwr_ioctl_data i2c_rdwr_data; + + /* Prepare I2C transfer structure */ + memset(&i2c_rdwr_data, 0, sizeof(struct i2c_rdwr_ioctl_data)); + i2c_rdwr_data.msgs = msgs; + i2c_rdwr_data.nmsgs = count; + + /* Transfer */ + if (ioctl(i2c->fd, I2C_RDWR, &i2c_rdwr_data) < 0) + return _i2c_error(i2c, I2C_ERROR_TRANSFER, errno, "I2C transfer"); + + return 0; +} + +int i2c_close(i2c_t *i2c) { + if (i2c->fd < 0) + return 0; + + /* Close fd */ + if (close(i2c->fd) < 0) + return _i2c_error(i2c, I2C_ERROR_CLOSE, errno, "Closing I2C device"); + + i2c->fd = -1; + + return 0; +} + +int i2c_tostring(i2c_t *i2c, char *str, size_t len) { + return snprintf(str, len, "I2C (fd=%d)", i2c->fd); +} + +const char *i2c_errmsg(i2c_t *i2c) { + return i2c->error.errmsg; +} + +int i2c_errno(i2c_t *i2c) { + return i2c->error.c_errno; +} + +int i2c_fd(i2c_t *i2c) { + return i2c->fd; +} + diff --git a/third_party/periphery/src/i2c.h b/third_party/periphery/src/i2c.h new file mode 100644 index 000000000..7383f5ea9 --- /dev/null +++ b/third_party/periphery/src/i2c.h @@ -0,0 +1,70 @@ +/* + * c-periphery + * https://github.com/vsergeev/c-periphery + * License: MIT + */ + +#ifndef _PERIPHERY_I2C_H +#define _PERIPHERY_I2C_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include + +#include +#include + +enum i2c_error_code { + I2C_ERROR_ARG = -1, /* Invalid arguments */ + I2C_ERROR_OPEN = -2, /* Opening I2C device */ + I2C_ERROR_QUERY = -3, /* Querying I2C device attributes */ + I2C_ERROR_NOT_SUPPORTED = -4, /* I2C not supported on this device */ + I2C_ERROR_TRANSFER = -5, /* I2C transfer */ + I2C_ERROR_CLOSE = -6, /* Closing I2C device */ +}; + +typedef struct i2c_handle i2c_t; + +/* Primary Functions */ +i2c_t *i2c_new(void); +int i2c_open(i2c_t *i2c, const char *path); +int i2c_transfer(i2c_t *i2c, struct i2c_msg *msgs, size_t count); +int i2c_close(i2c_t *i2c); +void i2c_free(i2c_t *i2c); + +/* Miscellaneous */ +int i2c_fd(i2c_t *i2c); +int i2c_tostring(i2c_t *i2c, char *str, size_t len); + +/* Error Handling */ +int i2c_errno(i2c_t *i2c); +const char *i2c_errmsg(i2c_t *i2c); + +/* struct i2c_msg from : + + struct i2c_msg { + __u16 addr; + __u16 flags; + #define I2C_M_TEN 0x0010 + #define I2C_M_RD 0x0001 + #define I2C_M_STOP 0x8000 + #define I2C_M_NOSTART 0x4000 + #define I2C_M_REV_DIR_ADDR 0x2000 + #define I2C_M_IGNORE_NAK 0x1000 + #define I2C_M_NO_RD_ACK 0x0800 + #define I2C_M_RECV_LEN 0x0400 + __u16 len; + __u8 *buf; + }; +*/ + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/third_party/periphery/src/led.c b/third_party/periphery/src/led.c new file mode 100644 index 000000000..b3f4cf3a1 --- /dev/null +++ b/third_party/periphery/src/led.c @@ -0,0 +1,222 @@ +/* + * c-periphery + * https://github.com/vsergeev/c-periphery + * License: MIT + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "led.h" + +#define P_PATH_MAX 256 + +struct led_handle { + char name[64]; + unsigned int max_brightness; + + struct { + int c_errno; + char errmsg[96]; + } error; +}; + +static int _led_error(led_t *led, int code, int c_errno, const char *fmt, ...) { + va_list ap; + + led->error.c_errno = c_errno; + + va_start(ap, fmt); + vsnprintf(led->error.errmsg, sizeof(led->error.errmsg), fmt, ap); + va_end(ap); + + /* Tack on strerror() and errno */ + if (c_errno) { + char buf[64]; + strerror_r(c_errno, buf, sizeof(buf)); + snprintf(led->error.errmsg+strlen(led->error.errmsg), sizeof(led->error.errmsg)-strlen(led->error.errmsg), ": %s [errno %d]", buf, c_errno); + } + + return code; +} + +led_t *led_new(void) { + led_t *led = calloc(1, sizeof(led_t)); + if (led == NULL) + return NULL; + + return led; +} + +int led_open(led_t *led, const char *name) { + char led_path[P_PATH_MAX]; + int fd, ret; + + snprintf(led_path, sizeof(led_path), "/sys/class/leds/%s/brightness", name); + + if ((fd = open(led_path, O_RDWR)) < 0) + return _led_error(led, LED_ERROR_OPEN, errno, "Opening LED: opening 'brightness'"); + + close(fd); + + strncpy(led->name, name, sizeof(led->name) - 1); + led->name[sizeof(led->name) - 1] = '\0'; + + if ((ret = led_get_max_brightness(led, &led->max_brightness)) < 0) + return ret; + + return 0; +} + +int led_read(led_t *led, bool *value) { + int ret; + unsigned int brightness; + + if ((ret = led_get_brightness(led, &brightness)) < 0) + return ret; + + *value = brightness != 0; + + return 0; +} + +int led_write(led_t *led, bool value) { + return led_set_brightness(led, value ? led->max_brightness : 0); +} + +int led_close(led_t *led) { + (void)led; + return 0; +} + +void led_free(led_t *led) { + free(led); +} + +int led_get_brightness(led_t *led, unsigned int *brightness) { + char led_path[P_PATH_MAX]; + char buf[16]; + int fd, ret; + + snprintf(led_path, sizeof(led_path), "/sys/class/leds/%s/brightness", led->name); + + if ((fd = open(led_path, O_RDONLY)) < 0) + return _led_error(led, LED_ERROR_IO, errno, "Opening LED 'brightness'"); + + if ((ret = read(fd, buf, sizeof(buf))) < 0) { + int errsv = errno; + close(fd); + return _led_error(led, LED_ERROR_IO, errsv, "Reading LED 'brightness'"); + } + + if (close(fd) < 0) + return _led_error(led, LED_ERROR_IO, errno, "Closing LED 'brightness'"); + + /* Null-terminate over newline */ + buf[ret] = '\0'; + + *brightness = strtoul(buf, NULL, 10); + + return 0; +} + +int led_get_max_brightness(led_t *led, unsigned int *max_brightness) { + char led_path[P_PATH_MAX]; + char buf[16]; + int fd, ret; + + snprintf(led_path, sizeof(led_path), "/sys/class/leds/%s/max_brightness", led->name); + + if ((fd = open(led_path, O_RDONLY)) < 0) + return _led_error(led, LED_ERROR_QUERY, errno, "Opening LED 'max_brightness'"); + + if ((ret = read(fd, buf, sizeof(buf))) < 0) { + int errsv = errno; + close(fd); + return _led_error(led, LED_ERROR_QUERY, errsv, "Reading LED 'max_brightness'"); + } + + if (close(fd) < 0) + return _led_error(led, LED_ERROR_QUERY, errno, "Closing LED 'max_brightness'"); + + /* Null-terminate over newline */ + buf[ret] = '\0'; + + led->max_brightness = strtoul(buf, NULL, 10); + + *max_brightness = led->max_brightness; + + return 0; +} + +int led_set_brightness(led_t *led, unsigned int brightness) { + char led_path[P_PATH_MAX]; + char buf[16]; + int fd, len; + + if (brightness > led->max_brightness) + return _led_error(led, LED_ERROR_ARG, 0, "Brightness out of bounds (max is %u)", led->max_brightness); + + snprintf(led_path, sizeof(led_path), "/sys/class/leds/%s/brightness", led->name); + + if ((fd = open(led_path, O_WRONLY)) < 0) + return _led_error(led, LED_ERROR_IO, errno, "Opening LED 'brightness'"); + + len = snprintf(buf, sizeof(buf), "%u\n", brightness); + + if (write(fd, buf, len) < 0) { + int errsv = errno; + close(fd); + return _led_error(led, LED_ERROR_IO, errsv, "Writing LED 'brightness'"); + } + + if (close(fd) < 0) + return _led_error(led, LED_ERROR_IO, errno, "Closing LED 'brightness'"); + + return 0; +} + +int led_name(led_t *led, char *str, size_t len) { + if (!len) + return 0; + + strncpy(str, led->name, len - 1); + str[len - 1] = '\0'; + + return 0; +} + +int led_tostring(led_t *led, char *str, size_t len) { + unsigned int brightness; + char brightness_str[16]; + unsigned int max_brightness; + char max_brightness_str[16]; + + if (led_get_brightness(led, &brightness) < 0) + strcpy(brightness_str, ""); + else + snprintf(brightness_str, sizeof(brightness_str), "%u", brightness); + + if (led_get_max_brightness(led, &max_brightness) < 0) + strcpy(max_brightness_str, ""); + else + snprintf(max_brightness_str, sizeof(max_brightness_str), "%u", max_brightness); + + return snprintf(str, len, "LED %s (brightness=%s, max_brightness=%s)", led->name, brightness_str, max_brightness_str); +} + +int led_errno(led_t *led) { + return led->error.c_errno; +} + +const char *led_errmsg(led_t *led) { + return led->error.errmsg; +} diff --git a/third_party/periphery/src/led.h b/third_party/periphery/src/led.h new file mode 100644 index 000000000..f0bd8e706 --- /dev/null +++ b/third_party/periphery/src/led.h @@ -0,0 +1,56 @@ +/* + * c-periphery + * https://github.com/vsergeev/c-periphery + * License: MIT + */ + +#ifndef _PERIPHERY_LED_H +#define _PERIPHERY_LED_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include + +enum led_error_code { + LED_ERROR_ARG = -1, /* Invalid arguments */ + LED_ERROR_OPEN = -2, /* Opening LED */ + LED_ERROR_QUERY = -3, /* Querying LED attributes */ + LED_ERROR_IO = -4, /* Reading/writing LED brightness */ + LED_ERROR_CLOSE = -5, /* Closing LED */ +}; + +typedef struct led_handle led_t; + +/* Primary Functions */ +led_t *led_new(void); +int led_open(led_t *led, const char *name); +int led_read(led_t *led, bool *value); +int led_write(led_t *led, bool value); +int led_close(led_t *led); +void led_free(led_t *led); + +/* Getters */ +int led_get_brightness(led_t *led, unsigned int *brightness); +int led_get_max_brightness(led_t *led, unsigned int *max_brightness); + +/* Setters */ +int led_set_brightness(led_t *led, unsigned int brightness); + +/* Miscellaneous */ +int led_name(led_t *led, char *str, size_t len); +int led_tostring(led_t *led, char *str, size_t len); + +/* Error Handling */ +int led_errno(led_t *led); +const char *led_errmsg(led_t *led); + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/third_party/periphery/src/libperiphery.pc.in b/third_party/periphery/src/libperiphery.pc.in new file mode 100644 index 000000000..876b1ef2b --- /dev/null +++ b/third_party/periphery/src/libperiphery.pc.in @@ -0,0 +1,10 @@ +prefix=@CMAKE_INSTALL_PREFIX@ +exec_prefix=${prefix} +libdir=${exec_prefix}/@CMAKE_INSTALL_LIBDIR@ +includedir=${prefix}/@CMAKE_INSTALL_INCLUDEDIR@/@PROJECT_NAME@ + +Name: libperiphery +Description: Library for peripheral I/O (GPIO, LED, PWM, SPI, I2C, MMIO, Serial) in Linux +Version: @VERSION@ +Libs: -L${libdir} -lperiphery +Cflags: -I${includedir} diff --git a/third_party/periphery/src/mmio.c b/third_party/periphery/src/mmio.c new file mode 100644 index 000000000..34f8d694f --- /dev/null +++ b/third_party/periphery/src/mmio.c @@ -0,0 +1,205 @@ +/* + * c-periphery + * https://github.com/vsergeev/c-periphery + * License: MIT + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "mmio.h" + +struct mmio_handle { + uintptr_t base, aligned_base; + size_t size, aligned_size; + void *ptr; + + struct { + int c_errno; + char errmsg[96]; + } error; +}; + +static int _mmio_error(mmio_t *mmio, int code, int c_errno, const char *fmt, ...) { + va_list ap; + + mmio->error.c_errno = c_errno; + + va_start(ap, fmt); + vsnprintf(mmio->error.errmsg, sizeof(mmio->error.errmsg), fmt, ap); + va_end(ap); + + /* Tack on strerror() and errno */ + if (c_errno) { + char buf[64]; + strerror_r(c_errno, buf, sizeof(buf)); + snprintf(mmio->error.errmsg+strlen(mmio->error.errmsg), sizeof(mmio->error.errmsg)-strlen(mmio->error.errmsg), ": %s [errno %d]", buf, c_errno); + } + + return code; +} + +mmio_t *mmio_new(void) { + return calloc(1, sizeof(mmio_t)); +} + +void mmio_free(mmio_t *mmio) { + free(mmio); +} + +int mmio_open(mmio_t *mmio, uintptr_t base, size_t size) { + return mmio_open_advanced(mmio, base, size, "/dev/mem"); +} + +int mmio_open_advanced(mmio_t *mmio, uintptr_t base, size_t size, const char *path) { + int fd; + + memset(mmio, 0, sizeof(mmio_t)); + mmio->base = base; + mmio->size = size; + mmio->aligned_base = mmio->base - (mmio->base % sysconf(_SC_PAGESIZE)); + mmio->aligned_size = mmio->size + (mmio->base - mmio->aligned_base); + + /* Open memory */ + if ((fd = open(path, O_RDWR | O_SYNC)) < 0) + return _mmio_error(mmio, MMIO_ERROR_OPEN, errno, "Opening %s", path); + + /* Map memory */ + if ((mmio->ptr = mmap(0, mmio->aligned_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, mmio->aligned_base)) == MAP_FAILED) { + int errsv = errno; + close(fd); + return _mmio_error(mmio, MMIO_ERROR_OPEN, errsv, "Mapping memory"); + } + + /* Close memory */ + if (close(fd) < 0) { + int errsv = errno; + munmap(mmio->ptr, mmio->aligned_size); + mmio->ptr = 0; + return _mmio_error(mmio, MMIO_ERROR_OPEN, errsv, "Closing %s", path); + } + + return 0; +} + +void *mmio_ptr(mmio_t *mmio) { + return (void *)((uint8_t *)mmio->ptr + (mmio->base - mmio->aligned_base)); +} + +/* WARNING: These functions may trigger a bus fault on some CPUs if an + * unaligned address is accessed! */ + +int mmio_read32(mmio_t *mmio, uintptr_t offset, uint32_t *value) { + offset += (mmio->base - mmio->aligned_base); + if ((offset+4) > mmio->aligned_size) + return _mmio_error(mmio, MMIO_ERROR_ARG, 0, "Offset out of bounds"); + + *value = *(volatile uint32_t *)(((volatile uint8_t *)mmio->ptr) + offset); + return 0; +} + +int mmio_read16(mmio_t *mmio, uintptr_t offset, uint16_t *value) { + offset += (mmio->base - mmio->aligned_base); + if ((offset+2) > mmio->aligned_size) + return _mmio_error(mmio, MMIO_ERROR_ARG, 0, "Offset out of bounds"); + + *value = *(volatile uint16_t *)(((volatile uint8_t *)mmio->ptr) + offset); + return 0; +} + +int mmio_read8(mmio_t *mmio, uintptr_t offset, uint8_t *value) { + offset += (mmio->base - mmio->aligned_base); + if ((offset+1) > mmio->aligned_size) + return _mmio_error(mmio, MMIO_ERROR_ARG, 0, "Offset out of bounds"); + + *value = *(volatile uint8_t *)(((volatile uint8_t *)mmio->ptr) + offset); + return 0; +} + +int mmio_read(mmio_t *mmio, uintptr_t offset, uint8_t *buf, size_t len) { + offset += (mmio->base - mmio->aligned_base); + if ((offset+len) > mmio->aligned_size) + return _mmio_error(mmio, MMIO_ERROR_ARG, 0, "Offset out of bounds"); + + memcpy((void *)buf, (const void *)(((volatile uint8_t *)mmio->ptr) + offset), len); + return 0; +} + +int mmio_write32(mmio_t *mmio, uintptr_t offset, uint32_t value) { + offset += (mmio->base - mmio->aligned_base); + if ((offset+4) > mmio->aligned_size) + return _mmio_error(mmio, MMIO_ERROR_ARG, 0, "Offset out of bounds"); + + *(volatile uint32_t *)(((volatile uint8_t *)mmio->ptr) + offset) = value; + return 0; +} + +int mmio_write16(mmio_t *mmio, uintptr_t offset, uint16_t value) { + offset += (mmio->base - mmio->aligned_base); + if ((offset+2) > mmio->aligned_size) + return _mmio_error(mmio, MMIO_ERROR_ARG, 0, "Offset out of bounds"); + + *(volatile uint16_t *)(((volatile uint8_t *)mmio->ptr) + offset) = value; + return 0; +} + +int mmio_write8(mmio_t *mmio, uintptr_t offset, uint8_t value) { + offset += (mmio->base - mmio->aligned_base); + if ((offset+1) > mmio->aligned_size) + return _mmio_error(mmio, MMIO_ERROR_ARG, 0, "Offset out of bounds"); + + *(volatile uint8_t *)(((volatile uint8_t *)mmio->ptr) + offset) = value; + return 0; +} + +int mmio_write(mmio_t *mmio, uintptr_t offset, const uint8_t *buf, size_t len) { + offset += (mmio->base - mmio->aligned_base); + if ((offset+len) > mmio->aligned_size) + return _mmio_error(mmio, MMIO_ERROR_ARG, 0, "Offset out of bounds"); + + memcpy((void *)(((volatile uint8_t *)mmio->ptr) + offset), (const void *)buf, len); + return 0; +} + +int mmio_close(mmio_t *mmio) { + if (!mmio->ptr) + return 0; + + /* Unmap memory */ + if (munmap(mmio->ptr, mmio->aligned_size) < 0) + return _mmio_error(mmio, MMIO_ERROR_CLOSE, errno, "Unmapping memory"); + + mmio->ptr = 0; + + return 0; +} + +int mmio_tostring(mmio_t *mmio, char *str, size_t len) { + return snprintf(str, len, "MMIO 0x%08zx (ptr=%p, size=%zu)", mmio->base, mmio->ptr, mmio->size); +} + +const char *mmio_errmsg(mmio_t *mmio) { + return mmio->error.errmsg; +} + +int mmio_errno(mmio_t *mmio) { + return mmio->error.c_errno; +} + +uintptr_t mmio_base(mmio_t *mmio) { + return mmio->base; +} + +size_t mmio_size(mmio_t *mmio) { + return mmio->size; +} + diff --git a/third_party/periphery/src/mmio.h b/third_party/periphery/src/mmio.h new file mode 100644 index 000000000..64be1525f --- /dev/null +++ b/third_party/periphery/src/mmio.h @@ -0,0 +1,56 @@ +/* + * c-periphery + * https://github.com/vsergeev/c-periphery + * License: MIT + */ + +#ifndef _PERIPHERY_MMIO_H +#define _PERIPHERY_MMIO_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include + +enum mmio_error_code { + MMIO_ERROR_ARG = -1, /* Invalid arguments */ + MMIO_ERROR_OPEN = -2, /* Opening MMIO */ + MMIO_ERROR_CLOSE = -3, /* Closing MMIO */ +}; + +typedef struct mmio_handle mmio_t; + +/* Primary Functions */ +mmio_t *mmio_new(void); +int mmio_open(mmio_t *mmio, uintptr_t base, size_t size); +int mmio_open_advanced(mmio_t *mmio, uintptr_t base, size_t size, const char *path); +void *mmio_ptr(mmio_t *mmio); +int mmio_read32(mmio_t *mmio, uintptr_t offset, uint32_t *value); +int mmio_read16(mmio_t *mmio, uintptr_t offset, uint16_t *value); +int mmio_read8(mmio_t *mmio, uintptr_t offset, uint8_t *value); +int mmio_read(mmio_t *mmio, uintptr_t offset, uint8_t *buf, size_t len); +int mmio_write32(mmio_t *mmio, uintptr_t offset, uint32_t value); +int mmio_write16(mmio_t *mmio, uintptr_t offset, uint16_t value); +int mmio_write8(mmio_t *mmio, uintptr_t offset, uint8_t value); +int mmio_write(mmio_t *mmio, uintptr_t offset, const uint8_t *buf, size_t len); +int mmio_close(mmio_t *mmio); +void mmio_free(mmio_t *mmio); + +/* Miscellaneous */ +uintptr_t mmio_base(mmio_t *mmio); +size_t mmio_size(mmio_t *mmio); +int mmio_tostring(mmio_t *mmio, char *str, size_t len); + +/* Error Handling */ +int mmio_errno(mmio_t *mmio); +const char *mmio_errmsg(mmio_t *mmio); + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/third_party/periphery/src/pwm.c b/third_party/periphery/src/pwm.c new file mode 100644 index 000000000..4db3a2453 --- /dev/null +++ b/third_party/periphery/src/pwm.c @@ -0,0 +1,451 @@ +/* + * c-periphery + * https://github.com/vsergeev/c-periphery + * License: MIT + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "pwm.h" + +#define P_PATH_MAX 256 +/* Delay between checks for successful PWM export (100ms) */ +#define PWM_EXPORT_STAT_DELAY 100000 +/* Number of retries to check for successful PWM exports */ +#define PWM_EXPORT_STAT_RETRIES 10 + +struct pwm_handle { + unsigned int chip; + unsigned int channel; + uint64_t period_ns; + + struct { + int c_errno; + char errmsg[96]; + } error; +}; + +static int _pwm_error(pwm_t *pwm, int code, int c_errno, const char *fmt, ...) { + va_list ap; + + pwm->error.c_errno = c_errno; + + va_start(ap, fmt); + vsnprintf(pwm->error.errmsg, sizeof(pwm->error.errmsg), fmt, ap); + va_end(ap); + + /* Tack on strerror() and errno */ + if (c_errno) { + char buf[64]; + strerror_r(c_errno, buf, sizeof(buf)); + snprintf(pwm->error.errmsg+strlen(pwm->error.errmsg), sizeof(pwm->error.errmsg)-strlen(pwm->error.errmsg), ": %s [errno %d]", buf, c_errno); + } + + return code; +} + +pwm_t *pwm_new(void) { + pwm_t *pwm = calloc(1, sizeof(pwm_t)); + if (pwm == NULL) + return NULL; + + pwm->chip = -1; + pwm->channel = -1; + + return pwm; +} + +int pwm_open(pwm_t *pwm, unsigned int chip, unsigned int channel) { + char channel_path[P_PATH_MAX]; + struct stat stat_buf; + int ret; + + snprintf(channel_path, sizeof(channel_path), "/sys/class/pwm/pwmchip%u/pwm%u", chip, channel); + + /* Check if PWM channel exists */ + if (stat(channel_path, &stat_buf) < 0) { + char path[P_PATH_MAX]; + char buf[16]; + int fd, len; + + /* Export the PWM channel */ + snprintf(path, sizeof(path), "/sys/class/pwm/pwmchip%u/export", chip); + + len = snprintf(buf, sizeof(buf), "%u\n", channel); + + if ((fd = open(path, O_WRONLY)) < 0) + return _pwm_error(pwm, PWM_ERROR_OPEN, errno, "Opening PWM: opening 'export'"); + + if (write(fd, buf, len) < 0) { + int errsv = errno; + close(fd); + return _pwm_error(pwm, PWM_ERROR_OPEN, errsv, "Opening PWM: writing 'export'"); + } + + if (close(fd) < 0) + return _pwm_error(pwm, PWM_ERROR_OPEN, errno, "Opening PWM: closing 'export'"); + + /* Wait until PWM channel appears */ + unsigned int retry_count; + for (retry_count = 0; retry_count < PWM_EXPORT_STAT_RETRIES; retry_count++) { + if ((ret = stat(path, &stat_buf)) < 0 && errno != ENOENT) + return _pwm_error(pwm, PWM_ERROR_OPEN, errno, "Opening PWM: stat 'pwm%u/' after export", channel); + else if (ret == 0) + break; + + usleep(PWM_EXPORT_STAT_DELAY); + } + + if (retry_count == PWM_EXPORT_STAT_RETRIES) + return _pwm_error(pwm, PWM_ERROR_OPEN, 0, "Opening PWM: waiting for 'pwm%u/' timed out", channel); + + snprintf(path, sizeof(path), "/sys/class/pwm/pwmchip%u/pwm%u/period", chip, channel); + + /* Loop until period is writable. This could take some time after + * export as application of udev rules after export is asynchronous. */ + for (retry_count = 0; retry_count < PWM_EXPORT_STAT_RETRIES; retry_count++) { + if ((fd = open(path, O_WRONLY)) < 0) { + if (errno != EACCES || (errno == EACCES && retry_count == PWM_EXPORT_STAT_RETRIES - 1)) + return _pwm_error(pwm, PWM_ERROR_OPEN, errno, "Opening PWM: opening 'pwm%u/period' after export", channel); + } else { + close(fd); + break; + } + + usleep(PWM_EXPORT_STAT_DELAY); + } + } + + memset(pwm, 0, sizeof(pwm_t)); + pwm->chip = chip; + pwm->channel = channel; + + ret = pwm_get_period_ns(pwm, &pwm->period_ns); + if (ret < 0) + return ret; + + return 0; +} + +int pwm_enable(pwm_t *pwm) { + return pwm_set_enabled(pwm, true); +} + +int pwm_disable(pwm_t *pwm) { + return pwm_set_enabled(pwm, false); +} + +int pwm_close(pwm_t *pwm) { + char path[P_PATH_MAX]; + char buf[16]; + int len; + int fd; + + if (pwm->channel == ((unsigned int) -1)) + return 0; + + /* Unexport the PWM */ + snprintf(path, sizeof(path), "/sys/class/pwm/pwmchip%u/unexport", pwm->chip); + + len = snprintf(buf, sizeof(buf), "%u\n", pwm->channel); + + if ((fd = open(path, O_WRONLY)) < 0) + return _pwm_error(pwm, PWM_ERROR_CLOSE, errno, "Closing PWM: opening 'unexport'"); + + if (write(fd, buf, len) < 0) { + int errsv = errno; + close(fd); + return _pwm_error(pwm, PWM_ERROR_CLOSE, errsv, "Closing PWM: writing 'unexport'"); + } + + if (close(fd) < 0) + return _pwm_error(pwm, PWM_ERROR_CLOSE, errno, "Closing PWM: closing 'unexport'"); + + pwm->chip = -1; + pwm->channel = -1; + + return 0; +} + +void pwm_free(pwm_t *pwm) { + free(pwm); +} + +static int pwm_read_attribute(pwm_t *pwm, const char *name, char *buf, size_t len) { + char path[P_PATH_MAX]; + int fd, ret; + + snprintf(path, sizeof(path), "/sys/class/pwm/pwmchip%u/pwm%u/%s", pwm->chip, pwm->channel, name); + + if ((fd = open(path, O_RDONLY)) < 0) + return _pwm_error(pwm, PWM_ERROR_QUERY, errno, "Opening PWM '%s'", name); + + if ((ret = read(fd, buf, len)) < 0) { + int errsv = errno; + close(fd); + return _pwm_error(pwm, PWM_ERROR_QUERY, errsv, "Reading PWM '%s'", name); + } + + if (close(fd) < 0) + return _pwm_error(pwm, PWM_ERROR_QUERY, errno, "Closing PWM '%s'", name); + + buf[ret] = '\0'; + + return 0; +} + +static int pwm_write_attribute(pwm_t *pwm, const char *name, const char *buf, size_t len) { + char path[P_PATH_MAX]; + int fd; + + snprintf(path, sizeof(path), "/sys/class/pwm/pwmchip%u/pwm%u/%s", pwm->chip, pwm->channel, name); + + if ((fd = open(path, O_WRONLY)) < 0) + return _pwm_error(pwm, PWM_ERROR_CONFIGURE, errno, "Opening PWM '%s'", name); + + if (write(fd, buf, len) < 0) { + int errsv = errno; + close(fd); + return _pwm_error(pwm, PWM_ERROR_CONFIGURE, errsv, "Writing PWM '%s'", name); + } + + if (close(fd) < 0) + return _pwm_error(pwm, PWM_ERROR_CONFIGURE, errno, "Closing PWM '%s'", name); + + return 0; +} + +int pwm_get_enabled(pwm_t *pwm, bool *enabled) { + char buf[2]; + int ret; + + if ((ret = pwm_read_attribute(pwm, "enable", buf, sizeof(buf))) < 0) + return ret; + + if (buf[0] == '0') + *enabled = false; + else if (buf[0] == '1') + *enabled = true; + else + return _pwm_error(pwm, PWM_ERROR_QUERY, errno, "Unknown PWM 'enabled' value"); + + return 0; +} + +int pwm_get_period_ns(pwm_t *pwm, uint64_t *period_ns) { + char buf[32]; + int ret; + uint64_t value; + + if ((ret = pwm_read_attribute(pwm, "period", buf, sizeof(buf))) < 0) + return ret; + + errno = 0; + value = strtoul(buf, NULL, 10); + if (errno != 0) + return _pwm_error(pwm, PWM_ERROR_QUERY, errno, "Unknown PWM 'period' value"); + + /* Cache the period for fast duty cycle updates */ + pwm->period_ns = value; + + *period_ns = value; + + return 0; +} + +int pwm_get_duty_cycle_ns(pwm_t *pwm, uint64_t *duty_cycle_ns) { + char buf[32]; + int ret; + uint64_t value; + + if ((ret = pwm_read_attribute(pwm, "duty_cycle", buf, sizeof(buf))) < 0) + return ret; + + errno = 0; + value = strtoul(buf, NULL, 10); + if (errno != 0) + return _pwm_error(pwm, PWM_ERROR_QUERY, errno, "Unknown PWM 'duty_cycle' value"); + + *duty_cycle_ns = value; + + return 0; +} + +int pwm_get_period(pwm_t *pwm, double *period) { + int ret; + uint64_t period_ns; + + if ((ret = pwm_get_period_ns(pwm, &period_ns)) < 0) + return ret; + + *period = ((double) period_ns) / 1e9; + + return 0; +} + +int pwm_get_duty_cycle(pwm_t *pwm, double *duty_cycle) { + int ret; + uint64_t duty_cycle_ns; + + if ((ret = pwm_get_duty_cycle_ns(pwm, &duty_cycle_ns)) < 0) + return ret; + + *duty_cycle = ((double) duty_cycle_ns) / ((double) pwm->period_ns); + + return 0; +} + +int pwm_get_frequency(pwm_t *pwm, double *frequency) { + int ret; + uint64_t period_ns; + + if ((ret = pwm_get_period_ns(pwm, &period_ns)) < 0) + return ret; + + *frequency = 1e9 / ((double) period_ns); + + return 0; +} + +int pwm_get_polarity(pwm_t *pwm, pwm_polarity_t *polarity) { + int ret; + char buf[16]; + + if ((ret = pwm_read_attribute(pwm, "polarity", buf, sizeof(buf))) < 0) + return ret; + + if (strcmp(buf, "normal\n") == 0) + *polarity = PWM_POLARITY_NORMAL; + else if (strcmp(buf, "inversed\n") == 0) + *polarity = PWM_POLARITY_INVERSED; + else + return _pwm_error(pwm, PWM_ERROR_QUERY, errno, "Unknown PWM 'polarity' value"); + + return 0; +} + +int pwm_set_enabled(pwm_t *pwm, bool enabled) { + return pwm_write_attribute(pwm, "enable", enabled ? "1\n" : "0\n", 2); +} + +int pwm_set_period_ns(pwm_t *pwm, uint64_t period_ns) { + char buf[32]; + int len; + int ret; + + len = snprintf(buf, sizeof(buf), "%" PRId64 "\n", period_ns); + + if ((ret = pwm_write_attribute(pwm, "period", buf, len)) < 0) + return ret; + + /* Cache the period for fast duty cycle updates */ + pwm->period_ns = period_ns; + + return 0; +} + +int pwm_set_duty_cycle_ns(pwm_t *pwm, uint64_t duty_cycle_ns) { + char buf[32]; + int len; + + len = snprintf(buf, sizeof(buf), "%" PRId64 "\n", duty_cycle_ns); + + return pwm_write_attribute(pwm, "duty_cycle", buf, len); +} + +int pwm_set_period(pwm_t *pwm, double period) { + uint64_t period_ns = (uint64_t)(period * 1e9); + + return pwm_set_period_ns(pwm, period_ns); +} + +int pwm_set_duty_cycle(pwm_t *pwm, double duty_cycle) { + uint64_t duty_cycle_ns; + + if (duty_cycle < 0 || duty_cycle > 1) + return _pwm_error(pwm, PWM_ERROR_ARG, 0, "PWM duty cycle out of bounds (should be between 0.0 and 1.0)"); + + duty_cycle_ns = (uint64_t)(((double) pwm->period_ns) * duty_cycle); + + return pwm_set_duty_cycle_ns(pwm, duty_cycle_ns); +} + +int pwm_set_frequency(pwm_t *pwm, double frequency) { + uint64_t period_ns = (uint64_t)(1e9 / frequency); + + return pwm_set_period_ns(pwm, period_ns); +} + +int pwm_set_polarity(pwm_t *pwm, pwm_polarity_t polarity) { + const char *buf; + + if (polarity == PWM_POLARITY_NORMAL) + buf = "normal\n"; + else if (polarity == PWM_POLARITY_INVERSED) + buf = "inversed\n"; + else + return _pwm_error(pwm, PWM_ERROR_ARG, 0, "Invalid PWM polarity (can be normal, inversed)"); + + return pwm_write_attribute(pwm, "polarity", buf, strlen(buf)); +} + +unsigned int pwm_chip(pwm_t *pwm) { + return pwm->chip; +} + +unsigned int pwm_channel(pwm_t *pwm) { + return pwm->channel; +} + +int pwm_tostring(pwm_t *pwm, char *str, size_t len) { + double period; + char period_str[16]; + double duty_cycle; + char duty_cycle_str[16]; + pwm_polarity_t polarity; + const char *polarity_str; + bool enabled; + const char *enabled_str; + + if (pwm_get_period(pwm, &period) < 0) + strcpy(period_str, ""); + else + snprintf(period_str, sizeof(period_str), "%f", period); + + if (pwm_get_duty_cycle(pwm, &duty_cycle) < 0) + strcpy(duty_cycle_str, ""); + else + snprintf(duty_cycle_str, sizeof(duty_cycle_str), "%f", duty_cycle); + + if (pwm_get_polarity(pwm, &polarity) < 0) + polarity_str = ""; + else + polarity_str = (polarity == PWM_POLARITY_NORMAL) ? "normal" : + (polarity == PWM_POLARITY_INVERSED) ? "inversed" : "unknown"; + + if (pwm_get_enabled(pwm, &enabled) < 0) + enabled_str = ""; + else + enabled_str = enabled ? "true" : "false"; + + return snprintf(str, len, "PWM %u, chip %u (period=%s sec, duty_cycle=%s%%, polarity=%s, enabled=%s)", pwm->channel, pwm->chip, period_str, duty_cycle_str, polarity_str, enabled_str); +} + +int pwm_errno(pwm_t *pwm) { + return pwm->error.c_errno; +} + +const char *pwm_errmsg(pwm_t *pwm) { + return pwm->error.errmsg; +} diff --git a/third_party/periphery/src/pwm.h b/third_party/periphery/src/pwm.h new file mode 100644 index 000000000..ebc443e13 --- /dev/null +++ b/third_party/periphery/src/pwm.h @@ -0,0 +1,73 @@ +/* + * c-periphery + * https://github.com/vsergeev/c-periphery + * License: MIT + */ + +#ifndef _PERIPHERY_PWM_H +#define _PERIPHERY_PWM_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include + +enum pwm_error_code { + PWM_ERROR_ARG = -1, /* Invalid arguments */ + PWM_ERROR_OPEN = -2, /* Opening PWM */ + PWM_ERROR_QUERY = -3, /* Querying PWM attributes */ + PWM_ERROR_CONFIGURE = -4, /* Configuring PWM attributes */ + PWM_ERROR_CLOSE = -5, /* Closing PWM */ +}; + +typedef enum pwm_polarity { + PWM_POLARITY_NORMAL, /* Normal polarity */ + PWM_POLARITY_INVERSED, /* Inversed polarity */ +} pwm_polarity_t; + +typedef struct pwm_handle pwm_t; + +/* Primary Functions */ +pwm_t *pwm_new(void); +int pwm_open(pwm_t *pwm, unsigned int chip, unsigned int channel); +int pwm_enable(pwm_t *pwm); +int pwm_disable(pwm_t *pwm); +int pwm_close(pwm_t *pwm); +void pwm_free(pwm_t *pwm); + +/* Getters */ +int pwm_get_enabled(pwm_t *pwm, bool *enabled); +int pwm_get_period_ns(pwm_t *pwm, uint64_t *period_ns); +int pwm_get_duty_cycle_ns(pwm_t *pwm, uint64_t *duty_cycle_ns); +int pwm_get_period(pwm_t *pwm, double *period); +int pwm_get_duty_cycle(pwm_t *pwm, double *duty_cycle); +int pwm_get_frequency(pwm_t *pwm, double *frequency); +int pwm_get_polarity(pwm_t *pwm, pwm_polarity_t *polarity); + +/* Setters */ +int pwm_set_enabled(pwm_t *pwm, bool enabled); +int pwm_set_period_ns(pwm_t *pwm, uint64_t period_ns); +int pwm_set_duty_cycle_ns(pwm_t *pwm, uint64_t duty_cycle_ns); +int pwm_set_period(pwm_t *pwm, double period); +int pwm_set_duty_cycle(pwm_t *pwm, double duty_cycle); +int pwm_set_frequency(pwm_t *pwm, double frequency); +int pwm_set_polarity(pwm_t *pwm, pwm_polarity_t polarity); + +/* Miscellaneous */ +unsigned int pwm_chip(pwm_t *pwm); +unsigned int pwm_channel(pwm_t *pwm); +int pwm_tostring(pwm_t *pwm, char *str, size_t len); + +/* Error Handling */ +int pwm_errno(pwm_t *pwm); +const char *pwm_errmsg(pwm_t *pwm); + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/third_party/periphery/src/serial.c b/third_party/periphery/src/serial.c new file mode 100644 index 000000000..877add633 --- /dev/null +++ b/third_party/periphery/src/serial.c @@ -0,0 +1,672 @@ +/* + * c-periphery + * https://github.com/vsergeev/c-periphery + * License: MIT + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include "serial.h" + +struct serial_handle { + int fd; + bool use_termios_timeout; + + struct { + int c_errno; + char errmsg[96]; + } error; +}; + +static int _serial_error(serial_t *serial, int code, int c_errno, const char *fmt, ...) { + va_list ap; + + serial->error.c_errno = c_errno; + + va_start(ap, fmt); + vsnprintf(serial->error.errmsg, sizeof(serial->error.errmsg), fmt, ap); + va_end(ap); + + /* Tack on strerror() and errno */ + if (c_errno) { + char buf[64]; + strerror_r(c_errno, buf, sizeof(buf)); + snprintf(serial->error.errmsg+strlen(serial->error.errmsg), sizeof(serial->error.errmsg)-strlen(serial->error.errmsg), ": %s [errno %d]", buf, c_errno); + } + + return code; +} + +serial_t *serial_new(void) { + serial_t *serial = calloc(1, sizeof(serial_t)); + if (serial == NULL) + return NULL; + + serial->fd = -1; + + return serial; +} + +void serial_free(serial_t *serial) { + free(serial); +} + +static int _serial_baudrate_to_bits(uint32_t baudrate) { + switch (baudrate) { + case 50: return B50; + case 75: return B75; + case 110: return B110; + case 134: return B134; + case 150: return B150; + case 200: return B200; + case 300: return B300; + case 600: return B600; + case 1200: return B1200; + case 1800: return B1800; + case 2400: return B2400; + case 4800: return B4800; + case 9600: return B9600; + case 19200: return B19200; + case 38400: return B38400; + case 57600: return B57600; + case 115200: return B115200; + case 230400: return B230400; + case 460800: return B460800; + case 500000: return B500000; + case 576000: return B576000; + case 921600: return B921600; + case 1000000: return B1000000; + case 1152000: return B1152000; + case 1500000: return B1500000; + case 2000000: return B2000000; +#ifdef B2500000 + case 2500000: return B2500000; +#endif +#ifdef B3000000 + case 3000000: return B3000000; +#endif +#ifdef B3500000 + case 3500000: return B3500000; +#endif +#ifdef B4000000 + case 4000000: return B4000000; +#endif + default: return -1; + } +} + +static int _serial_bits_to_baudrate(uint32_t bits) { + switch (bits) { + case B0: return 0; + case B50: return 50; + case B75: return 75; + case B110: return 110; + case B134: return 134; + case B150: return 150; + case B200: return 200; + case B300: return 300; + case B600: return 600; + case B1200: return 1200; + case B1800: return 1800; + case B2400: return 2400; + case B4800: return 4800; + case B9600: return 9600; + case B19200: return 19200; + case B38400: return 38400; + case B57600: return 57600; + case B115200: return 115200; + case B230400: return 230400; + case B460800: return 460800; + case B500000: return 500000; + case B576000: return 576000; + case B921600: return 921600; + case B1000000: return 1000000; + case B1152000: return 1152000; + case B1500000: return 1500000; + case B2000000: return 2000000; +#ifdef B2500000 + case B2500000: return 2500000; +#endif +#ifdef B3000000 + case B3000000: return 3000000; +#endif +#ifdef B3500000 + case B3500000: return 3500000; +#endif +#ifdef B4000000 + case B4000000: return 4000000; +#endif + default: return -1; + } +} + +int serial_open(serial_t *serial, const char *path, uint32_t baudrate) { + return serial_open_advanced(serial, path, baudrate, 8, PARITY_NONE, 1, false, false); +} + +int serial_open_advanced(serial_t *serial, const char *path, uint32_t baudrate, unsigned int databits, serial_parity_t parity, unsigned int stopbits, bool xonxoff, bool rtscts) { + struct termios termios_settings; + + /* Validate args */ + if (databits != 5 && databits != 6 && databits != 7 && databits != 8) + return _serial_error(serial, SERIAL_ERROR_ARG, 0, "Invalid data bits (can be 5,6,7,8)"); + if (parity != PARITY_NONE && parity != PARITY_ODD && parity != PARITY_EVEN) + return _serial_error(serial, SERIAL_ERROR_ARG, 0, "Invalid parity (can be PARITY_NONE,PARITY_ODD,PARITY_EVEN)"); + if (stopbits != 1 && stopbits != 2) + return _serial_error(serial, SERIAL_ERROR_ARG, 0, "Invalid stop bits (can be 1,2)"); + + memset(serial, 0, sizeof(serial_t)); + + /* Open serial port */ + if ((serial->fd = open(path, O_RDWR | O_NOCTTY)) < 0) + return _serial_error(serial, SERIAL_ERROR_OPEN, errno, "Opening serial port \"%s\"", path); + + memset(&termios_settings, 0, sizeof(termios_settings)); + + /* c_iflag */ + + /* Ignore break characters */ + termios_settings.c_iflag = IGNBRK; + if (parity != PARITY_NONE) + termios_settings.c_iflag |= INPCK; + /* Only use ISTRIP when less than 8 bits as it strips the 8th bit */ + if (parity != PARITY_NONE && databits != 8) + termios_settings.c_iflag |= ISTRIP; + if (xonxoff) + termios_settings.c_iflag |= (IXON | IXOFF); + + /* c_oflag */ + termios_settings.c_oflag = 0; + + /* c_lflag */ + termios_settings.c_lflag = 0; + + /* c_cflag */ + /* Enable receiver, ignore modem control lines */ + termios_settings.c_cflag = CREAD | CLOCAL; + + /* Databits */ + if (databits == 5) + termios_settings.c_cflag |= CS5; + else if (databits == 6) + termios_settings.c_cflag |= CS6; + else if (databits == 7) + termios_settings.c_cflag |= CS7; + else if (databits == 8) + termios_settings.c_cflag |= CS8; + + /* Parity */ + if (parity == PARITY_EVEN) + termios_settings.c_cflag |= PARENB; + else if (parity == PARITY_ODD) + termios_settings.c_cflag |= (PARENB | PARODD); + + /* Stopbits */ + if (stopbits == 2) + termios_settings.c_cflag |= CSTOPB; + + /* RTS/CTS */ + if (rtscts) + termios_settings.c_cflag |= CRTSCTS; + + /* Baudrate */ + cfsetispeed(&termios_settings, _serial_baudrate_to_bits(baudrate)); + cfsetospeed(&termios_settings, _serial_baudrate_to_bits(baudrate)); + + /* Set termios attributes */ + if (tcsetattr(serial->fd, TCSANOW, &termios_settings) < 0) { + int errsv = errno; + close(serial->fd); + serial->fd = -1; + return _serial_error(serial, SERIAL_ERROR_CONFIGURE, errsv, "Setting serial port attributes"); + } + + serial->use_termios_timeout = false; + + return 0; +} + +int serial_read(serial_t *serial, uint8_t *buf, size_t len, int timeout_ms) { + ssize_t ret; + + struct timeval tv_timeout; + tv_timeout.tv_sec = timeout_ms / 1000; + tv_timeout.tv_usec = (timeout_ms % 1000) * 1000; + + size_t bytes_read = 0; + + while (bytes_read < len) { + fd_set rfds; + FD_ZERO(&rfds); + FD_SET(serial->fd, &rfds); + + if ((ret = select(serial->fd+1, &rfds, NULL, NULL, (timeout_ms < 0) ? NULL : &tv_timeout)) < 0) + return _serial_error(serial, SERIAL_ERROR_IO, errno, "select() on serial port"); + + /* Timeout */ + if (ret == 0) + break; + + if ((ret = read(serial->fd, buf + bytes_read, len - bytes_read)) < 0) + return _serial_error(serial, SERIAL_ERROR_IO, errno, "Reading serial port"); + + /* If we're using VMIN or VMIN+VTIME semantics for end of read, return now */ + if (serial->use_termios_timeout) + return ret; + + /* Empty read */ + if (ret == 0 && len != 0) + return _serial_error(serial, SERIAL_ERROR_IO, 0, "Reading serial port: unexpected empty read"); + + bytes_read += ret; + } + + return bytes_read; +} + +int serial_write(serial_t *serial, const uint8_t *buf, size_t len) { + ssize_t ret; + + if ((ret = write(serial->fd, buf, len)) < 0) + return _serial_error(serial, SERIAL_ERROR_IO, errno, "Writing serial port"); + + return ret; +} + +int serial_flush(serial_t *serial) { + + if (tcdrain(serial->fd) < 0) + return _serial_error(serial, SERIAL_ERROR_IO, errno, "Flushing serial port"); + + return 0; +} + +int serial_input_waiting(serial_t *serial, unsigned int *count) { + if (ioctl(serial->fd, TIOCINQ, count) < 0) + return _serial_error(serial, SERIAL_ERROR_IO, errno, "TIOCINQ query"); + + return 0; +} + +int serial_output_waiting(serial_t *serial, unsigned int *count) { + if (ioctl(serial->fd, TIOCOUTQ, count) < 0) + return _serial_error(serial, SERIAL_ERROR_IO, errno, "TIOCOUTQ query"); + + return 0; +} + +int serial_poll(serial_t *serial, int timeout_ms) { + struct pollfd fds[1]; + int ret; + + /* Poll */ + fds[0].fd = serial->fd; + fds[0].events = POLLIN | POLLPRI; + if ((ret = poll(fds, 1, timeout_ms)) < 0) + return _serial_error(serial, SERIAL_ERROR_IO, errno, "Polling serial port"); + + if (ret) + return 1; + + /* Timed out */ + return 0; +} + +int serial_close(serial_t *serial) { + if (serial->fd < 0) + return 0; + + if (close(serial->fd) < 0) + return _serial_error(serial, SERIAL_ERROR_CLOSE, errno, "Closing serial port"); + + serial->fd = -1; + + return 0; +} + +int serial_get_baudrate(serial_t *serial, uint32_t *baudrate) { + struct termios termios_settings; + + if (tcgetattr(serial->fd, &termios_settings) < 0) + return _serial_error(serial, SERIAL_ERROR_QUERY, errno, "Getting serial port attributes"); + + *baudrate = _serial_bits_to_baudrate(cfgetospeed(&termios_settings)); + + return 0; +} + +int serial_get_databits(serial_t *serial, unsigned int *databits) { + struct termios termios_settings; + + if (tcgetattr(serial->fd, &termios_settings) < 0) + return _serial_error(serial, SERIAL_ERROR_QUERY, errno, "Getting serial port attributes"); + + switch (termios_settings.c_cflag & CSIZE) { + case CS5: + *databits = 5; + break; + case CS6: + *databits = 6; + break; + case CS7: + *databits = 7; + break; + case CS8: + *databits = 8; + break; + } + + return 0; +} + +int serial_get_parity(serial_t *serial, serial_parity_t *parity) { + struct termios termios_settings; + + if (tcgetattr(serial->fd, &termios_settings) < 0) + return _serial_error(serial, SERIAL_ERROR_QUERY, errno, "Getting serial port attributes"); + + if ((termios_settings.c_cflag & PARENB) == 0) + *parity = PARITY_NONE; + else if ((termios_settings.c_cflag & PARODD) == 0) + *parity = PARITY_EVEN; + else + *parity = PARITY_ODD; + + return 0; +} + +int serial_get_stopbits(serial_t *serial, unsigned int *stopbits) { + struct termios termios_settings; + + if (tcgetattr(serial->fd, &termios_settings) < 0) + return _serial_error(serial, SERIAL_ERROR_QUERY, errno, "Getting serial port attributes"); + + if (termios_settings.c_cflag & CSTOPB) + *stopbits = 2; + else + *stopbits = 1; + + return 0; +} + +int serial_get_xonxoff(serial_t *serial, bool *xonxoff) { + struct termios termios_settings; + + if (tcgetattr(serial->fd, &termios_settings) < 0) + return _serial_error(serial, SERIAL_ERROR_QUERY, errno, "Getting serial port attributes"); + + if (termios_settings.c_iflag & (IXON | IXOFF)) + *xonxoff = true; + else + *xonxoff = false; + + return 0; +} + +int serial_get_rtscts(serial_t *serial, bool *rtscts) { + struct termios termios_settings; + + if (tcgetattr(serial->fd, &termios_settings) < 0) + return _serial_error(serial, SERIAL_ERROR_QUERY, errno, "Getting serial port attributes"); + + if (termios_settings.c_cflag & CRTSCTS) + *rtscts = true; + else + *rtscts = false; + + return 0; +} + +int serial_get_vmin(serial_t *serial, unsigned int *vmin) { + struct termios termios_settings; + + if (tcgetattr(serial->fd, &termios_settings) < 0) + return _serial_error(serial, SERIAL_ERROR_QUERY, errno, "Getting serial port attributes"); + + *vmin = termios_settings.c_cc[VMIN]; + + return 0; +} + +int serial_get_vtime(serial_t *serial, float *vtime) { + struct termios termios_settings; + + if (tcgetattr(serial->fd, &termios_settings) < 0) + return _serial_error(serial, SERIAL_ERROR_QUERY, errno, "Getting serial port attributes"); + + *vtime = ((float)termios_settings.c_cc[VTIME]) / 10; + + return 0; +} + +int serial_set_baudrate(serial_t *serial, uint32_t baudrate) { + struct termios termios_settings; + + if (tcgetattr(serial->fd, &termios_settings) < 0) + return _serial_error(serial, SERIAL_ERROR_QUERY, errno, "Getting serial port attributes"); + + cfsetispeed(&termios_settings, _serial_baudrate_to_bits(baudrate)); + cfsetospeed(&termios_settings, _serial_baudrate_to_bits(baudrate)); + + if (tcsetattr(serial->fd, TCSANOW, &termios_settings) < 0) + return _serial_error(serial, SERIAL_ERROR_CONFIGURE, errno, "Setting serial port attributes"); + + return 0; +} + +int serial_set_databits(serial_t *serial, unsigned int databits) { + struct termios termios_settings; + + if (databits != 5 && databits != 6 && databits != 7 && databits != 8) + return _serial_error(serial, SERIAL_ERROR_ARG, 0, "Invalid data bits (can be 5,6,7,8)"); + + if (tcgetattr(serial->fd, &termios_settings) < 0) + return _serial_error(serial, SERIAL_ERROR_QUERY, errno, "Getting serial port attributes"); + + termios_settings.c_cflag &= ~CSIZE; + if (databits == 5) + termios_settings.c_cflag |= CS5; + else if (databits == 6) + termios_settings.c_cflag |= CS6; + else if (databits == 7) + termios_settings.c_cflag |= CS7; + else if (databits == 8) + termios_settings.c_cflag |= CS8; + + if (tcsetattr(serial->fd, TCSANOW, &termios_settings) < 0) + return _serial_error(serial, SERIAL_ERROR_CONFIGURE, errno, "Setting serial port attributes"); + + return 0; +} + +int serial_set_parity(serial_t *serial, enum serial_parity parity) { + struct termios termios_settings; + + if (parity != PARITY_NONE && parity != PARITY_ODD && parity != PARITY_EVEN) + return _serial_error(serial, SERIAL_ERROR_ARG, 0, "Invalid parity (can be PARITY_NONE,PARITY_ODD,PARITY_EVEN)"); + + if (tcgetattr(serial->fd, &termios_settings) < 0) + return _serial_error(serial, SERIAL_ERROR_QUERY, errno, "Getting serial port attributes"); + + termios_settings.c_iflag &= ~(INPCK | ISTRIP); + if (parity != PARITY_NONE) + termios_settings.c_iflag |= (INPCK | ISTRIP); + + termios_settings.c_cflag &= ~(PARENB | PARODD); + if (parity == PARITY_EVEN) + termios_settings.c_cflag |= PARENB; + else if (parity == PARITY_ODD) + termios_settings.c_cflag |= (PARENB | PARODD); + + if (tcsetattr(serial->fd, TCSANOW, &termios_settings) < 0) + return _serial_error(serial, SERIAL_ERROR_CONFIGURE, errno, "Setting serial port attributes"); + + return 0; +} + +int serial_set_stopbits(serial_t *serial, unsigned int stopbits) { + struct termios termios_settings; + + if (stopbits != 1 && stopbits != 2) + return _serial_error(serial, SERIAL_ERROR_ARG, 0, "Invalid stop bits (can be 1,2)"); + + if (tcgetattr(serial->fd, &termios_settings) < 0) + return _serial_error(serial, SERIAL_ERROR_QUERY, errno, "Getting serial port attributes"); + + termios_settings.c_cflag &= ~(CSTOPB); + if (stopbits == 2) + termios_settings.c_cflag |= CSTOPB; + + if (tcsetattr(serial->fd, TCSANOW, &termios_settings) < 0) + return _serial_error(serial, SERIAL_ERROR_CONFIGURE, errno, "Setting serial port attributes"); + + return 0; +} + +int serial_set_xonxoff(serial_t *serial, bool enabled) { + struct termios termios_settings; + + if (tcgetattr(serial->fd, &termios_settings) < 0) + return _serial_error(serial, SERIAL_ERROR_QUERY, errno, "Getting serial port attributes"); + + termios_settings.c_iflag &= ~(IXON | IXOFF | IXANY); + if (enabled) + termios_settings.c_iflag |= (IXON | IXOFF); + + if (tcsetattr(serial->fd, TCSANOW, &termios_settings) < 0) + return _serial_error(serial, SERIAL_ERROR_CONFIGURE, errno, "Setting serial port attributes"); + + return 0; +} + +int serial_set_rtscts(serial_t *serial, bool enabled) { + struct termios termios_settings; + + if (tcgetattr(serial->fd, &termios_settings) < 0) + return _serial_error(serial, SERIAL_ERROR_QUERY, errno, "Getting serial port attributes"); + + termios_settings.c_cflag &= ~CRTSCTS; + if (enabled) + termios_settings.c_cflag |= CRTSCTS; + + if (tcsetattr(serial->fd, TCSANOW, &termios_settings) < 0) + return _serial_error(serial, SERIAL_ERROR_CONFIGURE, errno, "Setting serial port attributes"); + + return 0; +} + +int serial_set_vmin(serial_t *serial, unsigned int vmin) { + struct termios termios_settings; + + if (vmin > 255) + return _serial_error(serial, SERIAL_ERROR_ARG, 0, "Invalid vmin (can be 0-255)"); + + if (tcgetattr(serial->fd, &termios_settings) < 0) + return _serial_error(serial, SERIAL_ERROR_QUERY, errno, "Getting serial port attributes"); + + termios_settings.c_cc[VMIN] = vmin; + + if (tcsetattr(serial->fd, TCSANOW, &termios_settings) < 0) + return _serial_error(serial, SERIAL_ERROR_CONFIGURE, errno, "Setting serial port attributes"); + + serial->use_termios_timeout = vmin > 0; + + return 0; +} + +int serial_set_vtime(serial_t *serial, float vtime) { + struct termios termios_settings; + + if (vtime < 0.0 || vtime > 25.5) + return _serial_error(serial, SERIAL_ERROR_ARG, 0, "Invalid vtime (can be 0-25.5)"); + + if (tcgetattr(serial->fd, &termios_settings) < 0) + return _serial_error(serial, SERIAL_ERROR_QUERY, errno, "Getting serial port attributes"); + + termios_settings.c_cc[VTIME] = ((unsigned int)(vtime * 10)); + + if (tcsetattr(serial->fd, TCSANOW, &termios_settings) < 0) + return _serial_error(serial, SERIAL_ERROR_CONFIGURE, errno, "Setting serial port attributes"); + + return 0; +} + +int serial_tostring(serial_t *serial, char *str, size_t len) { + struct termios termios_settings; + uint32_t baudrate; + const char *databits_str, *parity_str, *stopbits_str, *xonxoff_str, *rtscts_str; + unsigned int vmin; + float vtime; + + /* Instead of calling all of our individual getter functions, let's poll + * termios attributes once to be efficient. */ + + if (tcgetattr(serial->fd, &termios_settings) < 0) + return snprintf(str, len, "Serial (baudrate=?, databits=?, parity=?, stopbits=?, xonxoff=?, rtscts=?)"); + + baudrate = _serial_bits_to_baudrate(cfgetospeed(&termios_settings)); + + switch (termios_settings.c_cflag & CSIZE) { + case CS5: databits_str = "5"; break; + case CS6: databits_str = "6"; break; + case CS7: databits_str = "7"; break; + case CS8: databits_str = "8"; break; + default: databits_str = "?"; + } + + if ((termios_settings.c_cflag & PARENB) == 0) + parity_str = "none"; + else if ((termios_settings.c_cflag & PARODD) == 0) + parity_str = "even"; + else + parity_str = "odd"; + + if (termios_settings.c_cflag & CSTOPB) + stopbits_str = "2"; + else + stopbits_str = "1"; + + if (termios_settings.c_iflag & (IXON | IXOFF)) + xonxoff_str = "true"; + else + xonxoff_str = "false"; + + if (termios_settings.c_cflag & CRTSCTS) + rtscts_str = "true"; + else + rtscts_str = "false"; + + vmin = termios_settings.c_cc[VMIN]; + vtime = ((float)termios_settings.c_cc[VTIME]) / 10; + + return snprintf(str, len, "Serial (fd=%d, baudrate=%u, databits=%s, parity=%s, stopbits=%s, xonxoff=%s, rtscts=%s, vmin=%u, vtime=%.1f)", + serial->fd, baudrate, databits_str, parity_str, stopbits_str, xonxoff_str, rtscts_str, vmin, vtime); +} + +const char *serial_errmsg(serial_t *serial) { + return serial->error.errmsg; +} + +int serial_errno(serial_t *serial) { + return serial->error.c_errno; +} + +int serial_fd(serial_t *serial) { + return serial->fd; +} + diff --git a/third_party/periphery/src/serial.h b/third_party/periphery/src/serial.h new file mode 100644 index 000000000..095d5c412 --- /dev/null +++ b/third_party/periphery/src/serial.h @@ -0,0 +1,84 @@ +/* + * c-periphery + * https://github.com/vsergeev/c-periphery + * License: MIT + */ + +#ifndef _PERIPHERY_SERIAL_H +#define _PERIPHERY_SERIAL_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include + +enum serial_error_code { + SERIAL_ERROR_ARG = -1, /* Invalid arguments */ + SERIAL_ERROR_OPEN = -2, /* Opening serial port */ + SERIAL_ERROR_QUERY = -3, /* Querying serial port attributes */ + SERIAL_ERROR_CONFIGURE = -4, /* Configuring serial port attributes */ + SERIAL_ERROR_IO = -5, /* Reading/writing serial port */ + SERIAL_ERROR_CLOSE = -6, /* Closing serial port */ +}; + +typedef enum serial_parity { + PARITY_NONE, + PARITY_ODD, + PARITY_EVEN, +} serial_parity_t; + +typedef struct serial_handle serial_t; + +/* Primary Functions */ +serial_t *serial_new(void); +int serial_open(serial_t *serial, const char *path, uint32_t baudrate); +int serial_open_advanced(serial_t *serial, const char *path, + uint32_t baudrate, unsigned int databits, + serial_parity_t parity, unsigned int stopbits, + bool xonxoff, bool rtscts); +int serial_read(serial_t *serial, uint8_t *buf, size_t len, int timeout_ms); +int serial_write(serial_t *serial, const uint8_t *buf, size_t len); +int serial_flush(serial_t *serial); +int serial_input_waiting(serial_t *serial, unsigned int *count); +int serial_output_waiting(serial_t *serial, unsigned int *count); +int serial_poll(serial_t *serial, int timeout_ms); +int serial_close(serial_t *serial); +void serial_free(serial_t *serial); + +/* Getters */ +int serial_get_baudrate(serial_t *serial, uint32_t *baudrate); +int serial_get_databits(serial_t *serial, unsigned int *databits); +int serial_get_parity(serial_t *serial, serial_parity_t *parity); +int serial_get_stopbits(serial_t *serial, unsigned int *stopbits); +int serial_get_xonxoff(serial_t *serial, bool *xonxoff); +int serial_get_rtscts(serial_t *serial, bool *rtscts); +int serial_get_vmin(serial_t *serial, unsigned int *vmin); +int serial_get_vtime(serial_t *serial, float* vtime); + +/* Setters */ +int serial_set_baudrate(serial_t *serial, uint32_t baudrate); +int serial_set_databits(serial_t *serial, unsigned int databits); +int serial_set_parity(serial_t *serial, enum serial_parity parity); +int serial_set_stopbits(serial_t *serial, unsigned int stopbits); +int serial_set_xonxoff(serial_t *serial, bool enabled); +int serial_set_rtscts(serial_t *serial, bool enabled); +int serial_set_vmin(serial_t *serial, unsigned int vmin); +int serial_set_vtime(serial_t *serial, float vtime); + +/* Miscellaneous */ +int serial_fd(serial_t *serial); +int serial_tostring(serial_t *serial, char *str, size_t len); + +/* Error Handling */ +int serial_errno(serial_t *serial); +const char *serial_errmsg(serial_t *serial); + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/third_party/periphery/src/spi.c b/third_party/periphery/src/spi.c new file mode 100644 index 000000000..d71937eba --- /dev/null +++ b/third_party/periphery/src/spi.c @@ -0,0 +1,404 @@ +/* + * c-periphery + * https://github.com/vsergeev/c-periphery + * License: MIT + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +#include "spi.h" + +struct spi_handle { + int fd; + + struct { + int c_errno; + char errmsg[96]; + } error; +}; + +static int _spi_error(spi_t *spi, int code, int c_errno, const char *fmt, ...) { + va_list ap; + + spi->error.c_errno = c_errno; + + va_start(ap, fmt); + vsnprintf(spi->error.errmsg, sizeof(spi->error.errmsg), fmt, ap); + va_end(ap); + + /* Tack on strerror() and errno */ + if (c_errno) { + char buf[64]; + strerror_r(c_errno, buf, sizeof(buf)); + snprintf(spi->error.errmsg+strlen(spi->error.errmsg), sizeof(spi->error.errmsg)-strlen(spi->error.errmsg), ": %s [errno %d]", buf, c_errno); + } + + return code; +} + +spi_t *spi_new(void) { + spi_t *spi = calloc(1, sizeof(spi_t)); + if (spi == NULL) + return NULL; + + spi->fd = -1; + + return spi; +} + +void spi_free(spi_t *spi) { + free(spi); +} + +int spi_open(spi_t *spi, const char *path, unsigned int mode, uint32_t max_speed) { + return spi_open_advanced(spi, path, mode, max_speed, MSB_FIRST, 8, 0); +} + +int spi_open_advanced(spi_t *spi, const char *path, unsigned int mode, uint32_t max_speed, spi_bit_order_t bit_order, uint8_t bits_per_word, uint8_t extra_flags) { + return spi_open_advanced2(spi, path, mode, max_speed, bit_order, bits_per_word, extra_flags); +} + +int spi_open_advanced2(spi_t *spi, const char *path, unsigned int mode, uint32_t max_speed, spi_bit_order_t bit_order, uint8_t bits_per_word, uint32_t extra_flags) { + uint32_t data32; + uint8_t data8; + + /* Validate arguments */ + if (mode & ~0x3) + return _spi_error(spi, SPI_ERROR_ARG, 0, "Invalid mode (can be 0,1,2,3)"); + if (bit_order != MSB_FIRST && bit_order != LSB_FIRST) + return _spi_error(spi, SPI_ERROR_ARG, 0, "Invalid bit order (can be MSB_FIRST,LSB_FIRST)"); +#ifndef SPI_IOC_WR_MODE32 + if (extra_flags > 0xff) + return _spi_error(spi, SPI_ERROR_UNSUPPORTED, 0, "Kernel version does not support 32-bit SPI mode flags"); +#endif + + memset(spi, 0, sizeof(spi_t)); + + /* Open device */ + if ((spi->fd = open(path, O_RDWR)) < 0) + return _spi_error(spi, SPI_ERROR_OPEN, errno, "Opening SPI device \"%s\"", path); + + /* Set mode, bit order, extra flags */ +#ifndef SPI_IOC_WR_MODE32 + (void)data32; + + data8 = mode | ((bit_order == LSB_FIRST) ? SPI_LSB_FIRST : 0) | extra_flags; + if (ioctl(spi->fd, SPI_IOC_WR_MODE, &data8) < 0) { + int errsv = errno; + close(spi->fd); + spi->fd = -1; + return _spi_error(spi, SPI_ERROR_CONFIGURE, errsv, "Setting SPI mode"); + } +#else + if (extra_flags > 0xff) { + /* Use 32-bit mode if extra_flags is wider than 8-bits */ + data32 = mode | ((bit_order == LSB_FIRST) ? SPI_LSB_FIRST : 0) | extra_flags; + if (ioctl(spi->fd, SPI_IOC_WR_MODE32, &data32) < 0) { + int errsv = errno; + close(spi->fd); + spi->fd = -1; + return _spi_error(spi, SPI_ERROR_CONFIGURE, errsv, "Setting SPI mode"); + } + } else { + /* Prefer 8-bit mode, in case this library is inadvertently used on an + * older kernel. */ + data8 = mode | ((bit_order == LSB_FIRST) ? SPI_LSB_FIRST : 0) | extra_flags; + if (ioctl(spi->fd, SPI_IOC_WR_MODE, &data8) < 0) { + int errsv = errno; + close(spi->fd); + spi->fd = -1; + return _spi_error(spi, SPI_ERROR_CONFIGURE, errsv, "Setting SPI mode"); + } + } +#endif + + /* Set max speed */ + if (ioctl(spi->fd, SPI_IOC_WR_MAX_SPEED_HZ, &max_speed) < 0) { + int errsv = errno; + close(spi->fd); + spi->fd = -1; + return _spi_error(spi, SPI_ERROR_CONFIGURE, errsv, "Setting SPI max speed"); + } + + /* Set bits per word */ + if (ioctl(spi->fd, SPI_IOC_WR_BITS_PER_WORD, &bits_per_word) < 0) { + int errsv = errno; + close(spi->fd); + spi->fd = -1; + return _spi_error(spi, SPI_ERROR_CONFIGURE, errsv, "Setting SPI bits per word"); + } + + return 0; +} + +int spi_transfer(spi_t *spi, const uint8_t *txbuf, uint8_t *rxbuf, size_t len) { + struct spi_ioc_transfer spi_xfer; + + /* Prepare SPI transfer structure */ + memset(&spi_xfer, 0, sizeof(struct spi_ioc_transfer)); + spi_xfer.tx_buf = (uintptr_t)txbuf; + spi_xfer.rx_buf = (uintptr_t)rxbuf; + spi_xfer.len = len; + spi_xfer.delay_usecs = 0; + spi_xfer.speed_hz = 0; + spi_xfer.bits_per_word = 0; + spi_xfer.cs_change = 0; + + /* Transfer */ + if (ioctl(spi->fd, SPI_IOC_MESSAGE(1), &spi_xfer) < 1) + return _spi_error(spi, SPI_ERROR_TRANSFER, errno, "SPI transfer"); + + return 0; +} + +int spi_close(spi_t *spi) { + if (spi->fd < 0) + return 0; + + /* Close fd */ + if (close(spi->fd) < 0) + return _spi_error(spi, SPI_ERROR_CLOSE, errno, "Closing SPI device"); + + spi->fd = -1; + + return 0; +} + +int spi_get_mode(spi_t *spi, unsigned int *mode) { + uint8_t data8; + + if (ioctl(spi->fd, SPI_IOC_RD_MODE, &data8) < 0) + return _spi_error(spi, SPI_ERROR_QUERY, errno, "Getting SPI mode"); + + *mode = data8 & (SPI_CPHA | SPI_CPOL); + + return 0; +} + +int spi_get_max_speed(spi_t *spi, uint32_t *max_speed) { + uint32_t data32; + + if (ioctl(spi->fd, SPI_IOC_RD_MAX_SPEED_HZ, &data32) < 0) + return _spi_error(spi, SPI_ERROR_QUERY, errno, "Getting SPI max speed"); + + *max_speed = data32; + + return 0; +} + +int spi_get_bit_order(spi_t *spi, spi_bit_order_t *bit_order) { + uint8_t data8; + + if (ioctl(spi->fd, SPI_IOC_RD_LSB_FIRST, &data8) < 0) + return _spi_error(spi, SPI_ERROR_QUERY, errno, "Getting SPI bit order"); + + if (data8) + *bit_order = LSB_FIRST; + else + *bit_order = MSB_FIRST; + + return 0; +} + +int spi_get_bits_per_word(spi_t *spi, uint8_t *bits_per_word) { + uint8_t data8; + + if (ioctl(spi->fd, SPI_IOC_RD_BITS_PER_WORD, &data8) < 0) + return _spi_error(spi, SPI_ERROR_QUERY, errno, "Getting SPI bits per word"); + + *bits_per_word = data8; + + return 0; +} + +int spi_get_extra_flags(spi_t *spi, uint8_t *extra_flags) { + uint8_t data8; + + if (ioctl(spi->fd, SPI_IOC_RD_MODE, &data8) < 0) + return _spi_error(spi, SPI_ERROR_QUERY, errno, "Getting SPI mode flags"); + + /* Extra mode flags without mode 0-3 and bit order */ + *extra_flags = data8 & ~( SPI_CPOL | SPI_CPHA | SPI_LSB_FIRST ); + + return 0; +} + +int spi_get_extra_flags32(spi_t *spi, uint32_t *extra_flags) { +#ifdef SPI_IOC_RD_MODE32 + uint32_t mode32; + + if (ioctl(spi->fd, SPI_IOC_RD_MODE32, &mode32) < 0) + return _spi_error(spi, SPI_ERROR_QUERY, errno, "Getting 32-bit SPI mode flags"); + + /* Extra mode flags without mode 0-3 and bit order */ + *extra_flags = mode32 & ~(SPI_CPOL | SPI_CPHA | SPI_LSB_FIRST); + + return 0; +#else + (void)extra_flags; + + return _spi_error(spi, SPI_ERROR_UNSUPPORTED, 0, "Kernel version does not support 32-bit SPI mode flags"); +#endif +} + +int spi_set_mode(spi_t *spi, unsigned int mode) { + uint8_t data8; + + if (mode & ~0x3) + return _spi_error(spi, SPI_ERROR_ARG, 0, "Invalid mode (can be 0,1,2,3)"); + + if (ioctl(spi->fd, SPI_IOC_RD_MODE, &data8) < 0) + return _spi_error(spi, SPI_ERROR_QUERY, errno, "Getting SPI mode"); + + data8 &= ~(SPI_CPOL | SPI_CPHA); + data8 |= mode; + + if (ioctl(spi->fd, SPI_IOC_WR_MODE, &data8) < 0) + return _spi_error(spi, SPI_ERROR_CONFIGURE, errno, "Setting SPI mode"); + + return 0; +} + +int spi_set_bit_order(spi_t *spi, spi_bit_order_t bit_order) { + uint8_t data8; + + if (bit_order != MSB_FIRST && bit_order != LSB_FIRST) + return _spi_error(spi, SPI_ERROR_ARG, 0, "Invalid bit order (can be MSB_FIRST,LSB_FIRST)"); + + if (bit_order == LSB_FIRST) + data8 = 1; + else + data8 = 0; + + if (ioctl(spi->fd, SPI_IOC_WR_LSB_FIRST, &data8) < 0) + return _spi_error(spi, SPI_ERROR_CONFIGURE, errno, "Setting SPI bit order"); + + return 0; +} + +int spi_set_extra_flags(spi_t *spi, uint8_t extra_flags) { + uint8_t data8; + + if (ioctl(spi->fd, SPI_IOC_RD_MODE, &data8) < 0) + return _spi_error(spi, SPI_ERROR_QUERY, errno, "Getting SPI mode flags"); + + /* Keep mode 0-3 and bit order */ + data8 &= (SPI_CPOL | SPI_CPHA | SPI_LSB_FIRST); + /* Set extra flags */ + data8 |= extra_flags; + + if (ioctl(spi->fd, SPI_IOC_WR_MODE, &data8) < 0) + return _spi_error(spi, SPI_ERROR_CONFIGURE, errno, "Setting SPI mode flags"); + + return 0; +} + +int spi_set_extra_flags32(spi_t *spi, uint32_t extra_flags) { +#ifdef SPI_IOC_WR_MODE32 + uint32_t mode32; + + if (ioctl(spi->fd, SPI_IOC_RD_MODE32, &mode32) < 0) + return _spi_error(spi, SPI_ERROR_QUERY, errno, "Getting 32-bit SPI mode flags"); + + /* Keep mode 0-3 and bit order */ + mode32 &= (SPI_CPOL | SPI_CPHA | SPI_LSB_FIRST); + /* Set extra flags */ + mode32 |= extra_flags; + + if (ioctl(spi->fd, SPI_IOC_WR_MODE32, &mode32) < 0) + return _spi_error(spi, SPI_ERROR_CONFIGURE, errno, "Setting 32-bit SPI mode flags"); + + return 0; +#else + (void)extra_flags; + + return _spi_error(spi, SPI_ERROR_UNSUPPORTED, 0, "Kernel version does not support 32-bit SPI mode flags"); +#endif +} + +int spi_set_max_speed(spi_t *spi, uint32_t max_speed) { + + if (ioctl(spi->fd, SPI_IOC_WR_MAX_SPEED_HZ, &max_speed) < 0) + return _spi_error(spi, SPI_ERROR_CONFIGURE, errno, "Setting SPI max speed"); + + return 0; +} + +int spi_set_bits_per_word(spi_t *spi, uint8_t bits_per_word) { + + if (ioctl(spi->fd, SPI_IOC_WR_BITS_PER_WORD, &bits_per_word) < 0) + return _spi_error(spi, SPI_ERROR_CONFIGURE, errno, "Setting SPI bits per word"); + + return 0; +} + +int spi_tostring(spi_t *spi, char *str, size_t len) { + unsigned int mode; + char mode_str[2]; + uint32_t max_speed; + char max_speed_str[16]; + uint8_t bits_per_word; + char bits_per_word_str[4]; + spi_bit_order_t bit_order; + const char *bit_order_str; + uint8_t extra_flags8; + uint32_t extra_flags32; + char extra_flags_str[11]; + + if (spi_get_mode(spi, &mode) < 0) + strcpy(mode_str, "?"); + else + snprintf(mode_str, sizeof(mode_str), "%u", mode); + + if (spi_get_max_speed(spi, &max_speed) < 0) + strcpy(max_speed_str, "?"); + else + snprintf(max_speed_str, sizeof(max_speed_str), "%u", max_speed); + + if (spi_get_bit_order(spi, &bit_order) < 0) + bit_order_str = "?"; + else + bit_order_str = (bit_order == LSB_FIRST) ? "LSB_FIRST" : "MSB_FIRST"; + + if (spi_get_bits_per_word(spi, &bits_per_word) < 0) + strcpy(bits_per_word_str, "?"); + else + snprintf(bits_per_word_str, sizeof(bits_per_word_str), "%u", bits_per_word); + + if (spi_get_extra_flags32(spi, &extra_flags32) < 0) { + if (spi_get_extra_flags(spi, &extra_flags8) < 0) + strcpy(extra_flags_str, "?"); + else + snprintf(extra_flags_str, sizeof(extra_flags_str), "0x%02x", extra_flags8); + } else { + snprintf(extra_flags_str, sizeof(extra_flags_str), "0x%08x", extra_flags32); + } + + return snprintf(str, len, "SPI (fd=%d, mode=%s, max_speed=%s, bit_order=%s, bits_per_word=%s, extra_flags=%s)", spi->fd, mode_str, max_speed_str, bit_order_str, bits_per_word_str, extra_flags_str); +} + +const char *spi_errmsg(spi_t *spi) { + return spi->error.errmsg; +} + +int spi_errno(spi_t *spi) { + return spi->error.c_errno; +} + +int spi_fd(spi_t *spi) { + return spi->fd; +} + diff --git a/third_party/periphery/src/spi.h b/third_party/periphery/src/spi.h new file mode 100644 index 000000000..5b761c2eb --- /dev/null +++ b/third_party/periphery/src/spi.h @@ -0,0 +1,77 @@ +/* + * c-periphery + * https://github.com/vsergeev/c-periphery + * License: MIT + */ + +#ifndef _PERIPHERY_SPI_H +#define _PERIPHERY_SPI_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +enum spi_error_code { + SPI_ERROR_ARG = -1, /* Invalid arguments */ + SPI_ERROR_OPEN = -2, /* Opening SPI device */ + SPI_ERROR_QUERY = -3, /* Querying SPI device attributes */ + SPI_ERROR_CONFIGURE = -4, /* Configuring SPI device attributes */ + SPI_ERROR_TRANSFER = -5, /* SPI transfer */ + SPI_ERROR_CLOSE = -6, /* Closing SPI device */ + SPI_ERROR_UNSUPPORTED = -7, /* Unsupported attribute or operation */ +}; + +typedef enum spi_bit_order { + MSB_FIRST, + LSB_FIRST, +} spi_bit_order_t; + +typedef struct spi_handle spi_t; + +/* Primary Functions */ +spi_t *spi_new(void); +int spi_open(spi_t *spi, const char *path, unsigned int mode, + uint32_t max_speed); +int spi_open_advanced(spi_t *spi, const char *path, unsigned int mode, + uint32_t max_speed, spi_bit_order_t bit_order, + uint8_t bits_per_word, uint8_t extra_flags); +int spi_open_advanced2(spi_t *spi, const char *path, unsigned int mode, + uint32_t max_speed, spi_bit_order_t bit_order, + uint8_t bits_per_word, uint32_t extra_flags); +int spi_transfer(spi_t *spi, const uint8_t *txbuf, uint8_t *rxbuf, size_t len); +int spi_close(spi_t *spi); +void spi_free(spi_t *spi); + +/* Getters */ +int spi_get_mode(spi_t *spi, unsigned int *mode); +int spi_get_max_speed(spi_t *spi, uint32_t *max_speed); +int spi_get_bit_order(spi_t *spi, spi_bit_order_t *bit_order); +int spi_get_bits_per_word(spi_t *spi, uint8_t *bits_per_word); +int spi_get_extra_flags(spi_t *spi, uint8_t *extra_flags); +int spi_get_extra_flags32(spi_t *spi, uint32_t *extra_flags); + +/* Setters */ +int spi_set_mode(spi_t *spi, unsigned int mode); +int spi_set_max_speed(spi_t *spi, uint32_t max_speed); +int spi_set_bit_order(spi_t *spi, spi_bit_order_t bit_order); +int spi_set_bits_per_word(spi_t *spi, uint8_t bits_per_word); +int spi_set_extra_flags(spi_t *spi, uint8_t extra_flags); +int spi_set_extra_flags32(spi_t *spi, uint32_t extra_flags); + +/* Miscellaneous */ +int spi_fd(spi_t *spi); +int spi_tostring(spi_t *spi, char *str, size_t len); + +/* Error Handling */ +int spi_errno(spi_t *spi); +const char *spi_errmsg(spi_t *spi); + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/third_party/periphery/src/version.c b/third_party/periphery/src/version.c new file mode 100644 index 000000000..78cf76409 --- /dev/null +++ b/third_party/periphery/src/version.c @@ -0,0 +1,23 @@ +/* + * c-periphery + * https://github.com/vsergeev/c-periphery + * License: MIT + */ + +#include "version.h" + +const char *periphery_version(void) { + #define _STRINGIFY(s) #s + #define STRINGIFY(s) _STRINGIFY(s) + return "v" STRINGIFY(PERIPHERY_VERSION_MAJOR) "." STRINGIFY(PERIPHERY_VERSION_MINOR) "." STRINGIFY(PERIPHERY_VERSION_PATCH); +} + +const periphery_version_t *periphery_version_info(void) { + static const periphery_version_t version = { + .major = PERIPHERY_VERSION_MAJOR, + .minor = PERIPHERY_VERSION_MINOR, + .patch = PERIPHERY_VERSION_PATCH, + .commit_id = PERIPHERY_VERSION_COMMIT, + }; + return &version; +} diff --git a/third_party/periphery/src/version.h b/third_party/periphery/src/version.h new file mode 100644 index 000000000..600c7dcb4 --- /dev/null +++ b/third_party/periphery/src/version.h @@ -0,0 +1,38 @@ +/* + * c-periphery + * https://github.com/vsergeev/c-periphery + * License: MIT + */ + +#ifndef _PERIPHERY_VERSION_H +#define _PERIPHERY_VERSION_H + +#ifdef __cplusplus +extern "C" { +#endif + +#define PERIPHERY_VERSION_MAJOR 2 +#define PERIPHERY_VERSION_MINOR 4 +#define PERIPHERY_VERSION_PATCH 2 + +#ifndef PERIPHERY_VERSION_COMMIT +#define PERIPHERY_VERSION_COMMIT "unknown" +#endif + +typedef struct periphery_version { + unsigned int major; + unsigned int minor; + unsigned int patch; + const char *commit_id; +} periphery_version_t; + +const char *periphery_version(void); + +const periphery_version_t *periphery_version_info(void); + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/third_party/periphery/tests/test.h b/third_party/periphery/tests/test.h new file mode 100644 index 000000000..193bc77a1 --- /dev/null +++ b/third_party/periphery/tests/test.h @@ -0,0 +1,25 @@ +/* + * c-periphery + * https://github.com/vsergeev/c-periphery + * License: MIT + */ + +#include +#include + +#define STR_OK "[\x1b[1;32m OK \x1b[0m]" +#define STR_FAIL "[\x1b[1;31mFAIL\x1b[0m]" + +#define passert(c) \ + do { \ + int r = (c); \ + if (r) \ + printf(" " STR_OK " %s %s():%d %s\n", __FILE__, __func__, __LINE__, #c); \ + else \ + printf(" " STR_FAIL " %s %s():%d %s\n", __FILE__, __func__, __LINE__, #c); \ + assert(r); \ + } while(0) + +#define ptest() \ + printf("\nStarting test %s():%d\n", __func__, __LINE__) + diff --git a/third_party/periphery/tests/test_gpio.c b/third_party/periphery/tests/test_gpio.c new file mode 100644 index 000000000..76875ad59 --- /dev/null +++ b/third_party/periphery/tests/test_gpio.c @@ -0,0 +1,458 @@ +/* + * c-periphery + * https://github.com/vsergeev/c-periphery + * License: MIT + */ + +#include "test.h" + +#include +#include +#include + +#include +#include +#include +#include + +#include "../src/gpio.h" + +const char *device; +unsigned int pin_input, pin_output; + +void test_arguments(void) { + gpio_t *gpio; + + ptest(); + + /* Allocate GPIO */ + gpio = gpio_new(); + passert(gpio != NULL); + + /* Invalid direction */ + passert(gpio_open(gpio, device, pin_input, 5) == GPIO_ERROR_ARG); + + /* Free GPIO */ + gpio_free(gpio); +} + +void test_open_config_close(void) { + gpio_t *gpio; + bool value; + gpio_direction_t direction; + gpio_edge_t edge; + char label[32]; + gpio_bias_t bias; + gpio_drive_t drive; + bool inverted; + + ptest(); + + /* Allocate GPIO */ + gpio = gpio_new(); + passert(gpio != NULL); + + /* Open non-existent GPIO */ + passert(gpio_open(gpio, device, -1, GPIO_DIR_IN) == GPIO_ERROR_OPEN); + passert(gpio_errno(gpio) == EINVAL); + + /* Open legitimate GPIO */ + passert(gpio_open(gpio, device, pin_output, GPIO_DIR_IN) == 0); + + /* Check properties */ + passert(gpio_line(gpio) == pin_output); + passert(gpio_fd(gpio) >= 0); + passert(gpio_chip_fd(gpio) >= 0); + + /* Check default label */ + passert(gpio_label(gpio, label, sizeof(label)) == 0); + passert(strncmp(label, "periphery", sizeof(label)) == 0); + + /* Invalid direction */ + passert(gpio_set_direction(gpio, 5) == GPIO_ERROR_ARG); + /* Invalid interrupt edge */ + passert(gpio_set_edge(gpio, 5) == GPIO_ERROR_ARG); + /* Invalid bias */ + passert(gpio_set_bias(gpio, 5) == GPIO_ERROR_ARG); + /* Invalid drive */ + passert(gpio_set_drive(gpio, 5) == GPIO_ERROR_ARG); + + /* Set direction out, check direction out, check value low */ + passert(gpio_set_direction(gpio, GPIO_DIR_OUT) == 0); + passert(gpio_get_direction(gpio, &direction) == 0); + passert(direction == GPIO_DIR_OUT); + passert(gpio_read(gpio, &value) == 0); + passert(value == false); + /* Set direction out low, check direction out, check value low */ + passert(gpio_set_direction(gpio, GPIO_DIR_OUT_LOW) == 0); + passert(gpio_get_direction(gpio, &direction) == 0); + passert(direction == GPIO_DIR_OUT); + passert(gpio_read(gpio, &value) == 0); + passert(value == false); + /* Set direction out high, check direction out, check value high */ + passert(gpio_set_direction(gpio, GPIO_DIR_OUT_HIGH) == 0); + passert(gpio_get_direction(gpio, &direction) == 0); + passert(direction == GPIO_DIR_OUT); + passert(gpio_read(gpio, &value) == 0); + passert(value == true); + + /* Set drive open drain, check drive open drain */ + passert(gpio_set_drive(gpio, GPIO_DRIVE_OPEN_DRAIN) == 0); + passert(gpio_get_drive(gpio, &drive) == 0); + passert(drive == GPIO_DRIVE_OPEN_DRAIN); + /* Set drive open source, check drive open source */ + passert(gpio_set_drive(gpio, GPIO_DRIVE_OPEN_SOURCE) == 0); + passert(gpio_get_drive(gpio, &drive) == 0); + passert(drive == GPIO_DRIVE_OPEN_SOURCE); + /* Set drive default, check drive default */ + passert(gpio_set_drive(gpio, GPIO_DRIVE_DEFAULT) == 0); + passert(gpio_get_drive(gpio, &drive) == 0); + passert(drive == GPIO_DRIVE_DEFAULT); + + /* Set inverted true, check inverted true */ + passert(gpio_set_inverted(gpio, true) == 0); + passert(gpio_get_inverted(gpio, &inverted) == 0); + passert(inverted == true); + /* Set inverted false, check inverted false */ + passert(gpio_set_inverted(gpio, false) == 0); + passert(gpio_get_inverted(gpio, &inverted) == 0); + passert(inverted == false); + + /* Attempt to set interrupt edge on output GPIO */ + passert(gpio_set_edge(gpio, GPIO_EDGE_RISING) == GPIO_ERROR_INVALID_OPERATION); + /* Attempt to read event on output GPIO */ + passert(gpio_read_event(gpio, &edge, NULL) == GPIO_ERROR_INVALID_OPERATION); + + /* Set direction in, check direction in */ + passert(gpio_set_direction(gpio, GPIO_DIR_IN) == 0); + passert(gpio_get_direction(gpio, &direction) == 0); + passert(direction == GPIO_DIR_IN); + passert(gpio_read(gpio, &value) == 0); + + /* Set edge none, check edge none */ + passert(gpio_set_edge(gpio, GPIO_EDGE_NONE) == 0); + passert(gpio_get_edge(gpio, &edge) == 0); + passert(edge == GPIO_EDGE_NONE); + /* Set edge rising, check edge rising */ + passert(gpio_set_edge(gpio, GPIO_EDGE_RISING) == 0); + passert(gpio_get_edge(gpio, &edge) == 0); + passert(edge == GPIO_EDGE_RISING); + /* Set edge falling, check edge falling */ + passert(gpio_set_edge(gpio, GPIO_EDGE_FALLING) == 0); + passert(gpio_get_edge(gpio, &edge) == 0); + passert(edge == GPIO_EDGE_FALLING); + /* Set edge both, check edge both */ + passert(gpio_set_edge(gpio, GPIO_EDGE_BOTH) == 0); + passert(gpio_get_edge(gpio, &edge) == 0); + passert(edge == GPIO_EDGE_BOTH); + /* Set edge none, check edge none */ + passert(gpio_set_edge(gpio, GPIO_EDGE_NONE) == 0); + passert(gpio_get_edge(gpio, &edge) == 0); + passert(edge == GPIO_EDGE_NONE); + + /* Set bias pull up, check bias pull up */ + passert(gpio_set_bias(gpio, GPIO_BIAS_PULL_UP) == 0); + passert(gpio_get_bias(gpio, &bias) == 0); + passert(bias == GPIO_BIAS_PULL_UP); + /* Set bias pull down, check bias pull down */ + passert(gpio_set_bias(gpio, GPIO_BIAS_PULL_DOWN) == 0); + passert(gpio_get_bias(gpio, &bias) == 0); + passert(bias == GPIO_BIAS_PULL_DOWN); + /* Set bias disable, check bias disable */ + passert(gpio_set_bias(gpio, GPIO_BIAS_DISABLE) == 0); + passert(gpio_get_bias(gpio, &bias) == 0); + passert(bias == GPIO_BIAS_DISABLE); + /* Set bias default, check bias default */ + passert(gpio_set_bias(gpio, GPIO_BIAS_DEFAULT) == 0); + passert(gpio_get_bias(gpio, &bias) == 0); + passert(bias == GPIO_BIAS_DEFAULT); + + /* Attempt to set drive on input GPIO */ + passert(gpio_set_drive(gpio, GPIO_DRIVE_OPEN_DRAIN) == GPIO_ERROR_INVALID_OPERATION); + + /* Close GPIO */ + passert(gpio_close(gpio) == 0); + + /* Open GPIO with advanced open */ + gpio_config_t config = { + .direction = GPIO_DIR_IN, + .edge = GPIO_EDGE_RISING, + .bias = GPIO_BIAS_DEFAULT, + .drive = GPIO_DRIVE_DEFAULT, + .inverted = false, + .label = "test123", + }; + passert(gpio_open_advanced(gpio, device, pin_input, &config) == 0); + + /* Check properties */ + passert(gpio_line(gpio) == pin_input); + passert(gpio_fd(gpio) >= 0); + passert(gpio_chip_fd(gpio) >= 0); + /* Check direction */ + passert(gpio_get_direction(gpio, &direction) == 0); + passert(direction == GPIO_DIR_IN); + /* Check edge */ + passert(gpio_get_edge(gpio, &edge) == 0); + passert(edge == GPIO_EDGE_RISING); + /* Check bias */ + passert(gpio_get_bias(gpio, &bias) == 0); + passert(bias == GPIO_BIAS_DEFAULT); + /* Check drive */ + passert(gpio_get_drive(gpio, &drive) == 0); + passert(drive == GPIO_DRIVE_DEFAULT); + /* Check inverted */ + passert(gpio_get_inverted(gpio, &inverted) == 0); + passert(inverted == false); + /* Check label */ + passert(gpio_label(gpio, label, sizeof(label)) == 0); + passert(strncmp(label, "test123", sizeof(label)) == 0); + + /* Close GPIO */ + passert(gpio_close(gpio) == 0); + + /* Free GPIO */ + gpio_free(gpio); +} + +/* Threaded poll helper functions */ + +typedef struct { + sem_t *sem; + gpio_t *gpio; + int timeout_ms; +} gpio_poll_args_t; + +void *gpio_poll_thread(void *arg) { + gpio_poll_args_t *args = (gpio_poll_args_t *)arg; + gpio_t *gpio = args->gpio; + int timeout_ms = args->timeout_ms; + + assert(sem_post(args->sem) == 0); + + intptr_t ret = gpio_poll(gpio, timeout_ms); + + return (void *)ret; +} + +void gpio_poll_start(pthread_t *thread, gpio_t *gpio, int timeout_ms) { + sem_t sem; + gpio_poll_args_t args = {.sem = &sem, .gpio = gpio, .timeout_ms = timeout_ms}; + + assert(sem_init(&sem, 0, 0) == 0); + assert(pthread_create(thread, NULL, &gpio_poll_thread, &args) == 0); + assert(sem_wait(&sem) == 0); +} + +int gpio_poll_join(pthread_t thread) { + void *ret; + + assert(pthread_join(thread, &ret) == 0); + + return (intptr_t)ret; +} + +void test_loopback(void) { + gpio_t *gpio_in, *gpio_out; + pthread_t poll_thread; + bool value; + gpio_edge_t edge; + + ptest(); + + /* Allocate GPIO */ + gpio_in = gpio_new(); + passert(gpio_in != NULL); + gpio_out = gpio_new(); + passert(gpio_out != NULL); + + /* Open in and out pins */ + passert(gpio_open(gpio_in, device, pin_input, GPIO_DIR_IN) == 0); + passert(gpio_open(gpio_out, device, pin_output, GPIO_DIR_OUT) == 0); + + /* Drive out low, check in low */ + passert(gpio_write(gpio_out, false) == 0); + passert(gpio_read(gpio_in, &value) == 0); + passert(value == false); + + /* Drive out high, check in high */ + passert(gpio_write(gpio_out, true) == 0); + passert(gpio_read(gpio_in, &value) == 0); + passert(value == true); + + /* Check poll falling 1 -> 0 interrupt */ + passert(gpio_set_edge(gpio_in, GPIO_EDGE_FALLING) == 0); + gpio_poll_start(&poll_thread, gpio_in, 1000); + passert(gpio_write(gpio_out, false) == 0); + passert(gpio_poll_join(poll_thread) == 1); + passert(gpio_read(gpio_in, &value) == 0); + passert(value == false); + passert(gpio_read_event(gpio_in, &edge, NULL) == 0); + passert(edge == GPIO_EDGE_FALLING); + + /* Check poll rising 0 -> 1 interrupt */ + passert(gpio_set_edge(gpio_in, GPIO_EDGE_RISING) == 0); + gpio_poll_start(&poll_thread, gpio_in, 1000); + passert(gpio_write(gpio_out, true) == 0); + passert(gpio_poll_join(poll_thread) == 1); + passert(gpio_read(gpio_in, &value) == 0); + passert(value == true); + passert(gpio_read_event(gpio_in, &edge, NULL) == 0); + passert(edge == GPIO_EDGE_RISING); + + /* Set both edge */ + passert(gpio_set_edge(gpio_in, GPIO_EDGE_BOTH) == 0); + + /* Check poll falling 1 -> 0 interrupt */ + gpio_poll_start(&poll_thread, gpio_in, 1000); + passert(gpio_write(gpio_out, false) == 0); + passert(gpio_poll_join(poll_thread) == 1); + passert(gpio_read(gpio_in, &value) == 0); + passert(value == false); + passert(gpio_read_event(gpio_in, &edge, NULL) == 0); + passert(edge == GPIO_EDGE_FALLING); + + /* Check poll rising 0 -> 1 interrupt */ + gpio_poll_start(&poll_thread, gpio_in, 1000); + passert(gpio_write(gpio_out, true) == 0); + passert(gpio_poll_join(poll_thread) == 1); + passert(gpio_read(gpio_in, &value) == 0); + passert(value == true); + passert(gpio_read_event(gpio_in, &edge, NULL) == 0); + passert(edge == GPIO_EDGE_RISING); + + /* Check poll timeout */ + passert(gpio_poll(gpio_in, 1000) == 0); + + /* Test gpio_poll_multiple() API with one GPIO */ + gpio_t *gpios[1] = {gpio_in}; + bool gpios_ready[1] = {false}; + + /* Check poll falling 1 -> 0 interrupt */ + passert(gpio_write(gpio_out, false) == 0); + passert(gpio_poll_multiple(gpios, 1, 1000, gpios_ready) == 1); + passert(gpios_ready[0] == true); + passert(gpio_read(gpio_in, &value) == 0); + passert(value == false); + passert(gpio_read_event(gpio_in, &edge, NULL) == 0); + passert(edge == GPIO_EDGE_FALLING); + + /* Check poll rising 0 -> 1 interrupt */ + passert(gpio_write(gpio_out, true) == 0); + passert(gpio_poll_multiple(gpios, 1, 1000, gpios_ready) == 1); + passert(gpios_ready[0] == true); + passert(gpio_read(gpio_in, &value) == 0); + passert(value == true); + passert(gpio_read_event(gpio_in, &edge, NULL) == 0); + passert(edge == GPIO_EDGE_RISING); + + /* Check poll timeout */ + passert(gpio_poll_multiple(gpios, 1, 1000, gpios_ready) == 0); + passert(gpios_ready[0] == false); + + passert(gpio_close(gpio_in) == 0); + passert(gpio_close(gpio_out) == 0); + + /* Open both GPIOs as inputs */ + passert(gpio_open(gpio_in, device, pin_input, GPIO_DIR_IN) == 0); + passert(gpio_open(gpio_out, device, pin_output, GPIO_DIR_IN) == 0); + + /* Set bias pull-up, check value is high */ + passert(gpio_set_bias(gpio_in, GPIO_BIAS_PULL_UP) == 0); + usleep(1000); + passert(gpio_read(gpio_in, &value) == 0); + passert(value == true); + + /* Set bias pull-down, check value is low */ + passert(gpio_set_bias(gpio_in, GPIO_BIAS_PULL_DOWN) == 0); + usleep(1000); + passert(gpio_read(gpio_in, &value) == 0); + passert(value == false); + + passert(gpio_close(gpio_in) == 0); + passert(gpio_close(gpio_out) == 0); + + /* Free GPIO */ + gpio_free(gpio_in); + gpio_free(gpio_out); +} + +bool getc_yes(void) { + char buf[4]; + fgets(buf, sizeof(buf), stdin); + return (buf[0] == 'y' || buf[0] == 'Y'); +} + +void test_interactive(void) { + char str[256]; + gpio_t *gpio; + + ptest(); + + /* Allocate GPIO */ + gpio = gpio_new(); + passert(gpio != NULL); + + passert(gpio_open(gpio, device, pin_output, GPIO_DIR_OUT) == 0); + + printf("Starting interactive test. Get out your multimeter, buddy!\n"); + printf("Press enter to continue...\n"); + getc(stdin); + + /* Check tostring */ + passert(gpio_tostring(gpio, str, sizeof(str)) > 0); + printf("GPIO description: %s\n", str); + printf("GPIO description looks OK? y/n\n"); + passert(getc_yes()); + + /* Drive GPIO out low */ + passert(gpio_write(gpio, false) == 0); + printf("GPIO out is low? y/n\n"); + passert(getc_yes()); + + /* Drive GPIO out high */ + passert(gpio_write(gpio, true) == 0); + printf("GPIO out is high? y/n\n"); + passert(getc_yes()); + + /* Drive GPIO out low */ + passert(gpio_write(gpio, false) == 0); + printf("GPIO out is low? y/n\n"); + passert(getc_yes()); + + passert(gpio_close(gpio) == 0); + + /* Free GPIO */ + gpio_free(gpio); +} + +int main(int argc, char *argv[]) { + if (argc < 4) { + fprintf(stderr, "Usage: %s \n\n", argv[0]); + fprintf(stderr, "[1/4] Argument test: No requirements.\n"); + fprintf(stderr, "[2/4] Open/close test: GPIO #2 should be real.\n"); + fprintf(stderr, "[3/4] Loopback test: GPIOs #1 and #2 should be connected with a wire.\n"); + fprintf(stderr, "[4/4] Interactive test: GPIO #2 should be observed with a multimeter.\n\n"); + fprintf(stderr, "Hint: for Raspberry Pi 3,\n"); + fprintf(stderr, "Use GPIO 17 (header pin 11) and GPIO 27 (header pin 13),\n"); + fprintf(stderr, "connect a loopback between them, and run this test with:\n"); + fprintf(stderr, " %s /dev/gpiochip0 17 27\n\n", argv[0]); + exit(1); + } + + device = argv[1]; + pin_input = strtoul(argv[2], NULL, 10); + pin_output = strtoul(argv[3], NULL, 10); + + test_arguments(); + printf(" " STR_OK " Arguments test passed.\n\n"); + test_open_config_close(); + printf(" " STR_OK " Open/close test passed.\n\n"); + test_loopback(); + printf(" " STR_OK " Loopback test passed.\n\n"); + test_interactive(); + printf(" " STR_OK " Interactive test passed.\n\n"); + + printf("All tests passed!\n"); + return 0; +} + diff --git a/third_party/periphery/tests/test_gpio_sysfs.c b/third_party/periphery/tests/test_gpio_sysfs.c new file mode 100644 index 000000000..26b9ce647 --- /dev/null +++ b/third_party/periphery/tests/test_gpio_sysfs.c @@ -0,0 +1,333 @@ +/* + * c-periphery + * https://github.com/vsergeev/c-periphery + * License: MIT + */ + +#include "test.h" + +#include +#include + +#include +#include +#include +#include + +#include "../src/gpio.h" + +unsigned int pin_input, pin_output; + +void test_arguments(void) { + gpio_t *gpio; + + ptest(); + + /* Allocate GPIO */ + gpio = gpio_new(); + passert(gpio != NULL); + + /* Invalid direction */ + passert(gpio_open_sysfs(gpio, pin_input, 5) == GPIO_ERROR_ARG); + + /* Free GPIO */ + gpio_free(gpio); +} + +void test_open_config_close(void) { + gpio_t *gpio; + bool value; + gpio_direction_t direction; + gpio_edge_t edge; + gpio_bias_t bias; + gpio_drive_t drive; + bool inverted; + + ptest(); + + /* Allocate GPIO */ + gpio = gpio_new(); + passert(gpio != NULL); + + /* Open non-existent GPIO -- export should fail with EINVAL */ + passert(gpio_open_sysfs(gpio, 9999, GPIO_DIR_IN) == GPIO_ERROR_OPEN); + passert(gpio_errno(gpio) == EINVAL); + + /* Open legitimate GPIO */ + passert(gpio_open_sysfs(gpio, pin_output, GPIO_DIR_IN) == 0); + + /* Check properties */ + passert(gpio_line(gpio) == pin_output); + passert(gpio_fd(gpio) >= 0); + + /* Invalid direction */ + passert(gpio_set_direction(gpio, 5) == GPIO_ERROR_ARG); + /* Invalid interrupt edge */ + passert(gpio_set_edge(gpio, 5) == GPIO_ERROR_ARG); + /* Unsupported setting bias */ + passert(gpio_set_bias(gpio, GPIO_BIAS_PULL_UP) == GPIO_ERROR_UNSUPPORTED); + passert(gpio_get_bias(gpio, &bias) == GPIO_ERROR_UNSUPPORTED); + /* Unsupported setting drive */ + passert(gpio_set_drive(gpio, GPIO_DRIVE_OPEN_DRAIN) == GPIO_ERROR_UNSUPPORTED); + passert(gpio_get_drive(gpio, &drive) == GPIO_ERROR_UNSUPPORTED); + /* Unsupported property */ + passert(gpio_chip_fd(gpio) == GPIO_ERROR_UNSUPPORTED); + /* Unsupported method */ + passert(gpio_read_event(gpio, &edge, NULL) == GPIO_ERROR_UNSUPPORTED); + + /* Set direction out, check direction out, check value low */ + passert(gpio_set_direction(gpio, GPIO_DIR_OUT) == 0); + passert(gpio_get_direction(gpio, &direction) == 0); + passert(direction == GPIO_DIR_OUT); + passert(gpio_read(gpio, &value) == 0); + passert(value == false); + /* Set direction out low, check direction out, check value low */ + passert(gpio_set_direction(gpio, GPIO_DIR_OUT_LOW) == 0); + passert(gpio_get_direction(gpio, &direction) == 0); + passert(direction == GPIO_DIR_OUT); + passert(gpio_read(gpio, &value) == 0); + passert(value == false); + /* Set direction out high, check direction out, check value high */ + passert(gpio_set_direction(gpio, GPIO_DIR_OUT_HIGH) == 0); + passert(gpio_get_direction(gpio, &direction) == 0); + passert(direction == GPIO_DIR_OUT); + passert(gpio_read(gpio, &value) == 0); + passert(value == true); + + /* Set inverted true, check inverted */ + passert(gpio_set_inverted(gpio, true) == 0); + passert(gpio_get_inverted(gpio, &inverted) == 0); + passert(inverted == true); + /* Set inverted false, check inverted */ + passert(gpio_set_inverted(gpio, false) == 0); + passert(gpio_get_inverted(gpio, &inverted) == 0); + passert(inverted == false); + + /* Set direction in, check direction in */ + passert(gpio_set_direction(gpio, GPIO_DIR_IN) == 0); + passert(gpio_get_direction(gpio, &direction) == 0); + passert(direction == GPIO_DIR_IN); + passert(gpio_read(gpio, &value) == 0); + + /* Set edge none, check edge none */ + passert(gpio_set_edge(gpio, GPIO_EDGE_NONE) == 0); + passert(gpio_get_edge(gpio, &edge) == 0); + passert(edge == GPIO_EDGE_NONE); + /* Set edge rising, check edge rising */ + passert(gpio_set_edge(gpio, GPIO_EDGE_RISING) == 0); + passert(gpio_get_edge(gpio, &edge) == 0); + passert(edge == GPIO_EDGE_RISING); + /* Set edge falling, check edge falling */ + passert(gpio_set_edge(gpio, GPIO_EDGE_FALLING) == 0); + passert(gpio_get_edge(gpio, &edge) == 0); + passert(edge == GPIO_EDGE_FALLING); + /* Set edge both, check edge both */ + passert(gpio_set_edge(gpio, GPIO_EDGE_BOTH) == 0); + passert(gpio_get_edge(gpio, &edge) == 0); + passert(edge == GPIO_EDGE_BOTH); + /* Set edge none, check edge none */ + passert(gpio_set_edge(gpio, GPIO_EDGE_NONE) == 0); + passert(gpio_get_edge(gpio, &edge) == 0); + passert(edge == GPIO_EDGE_NONE); + + /* Close GPIO */ + passert(gpio_close(gpio) == 0); + + /* Free GPIO */ + gpio_free(gpio); +} + +/* Threaded poll helper functions */ + +typedef struct { + sem_t *sem; + gpio_t *gpio; + int timeout_ms; +} gpio_poll_args_t; + +void *gpio_poll_thread(void *arg) { + gpio_poll_args_t *args = (gpio_poll_args_t *)arg; + gpio_t *gpio = args->gpio; + int timeout_ms = args->timeout_ms; + + assert(sem_post(args->sem) == 0); + + intptr_t ret = gpio_poll(gpio, timeout_ms); + + return (void *)ret; +} + +void gpio_poll_start(pthread_t *thread, gpio_t *gpio, int timeout_ms) { + sem_t sem; + gpio_poll_args_t args = {.sem = &sem, .gpio = gpio, .timeout_ms = timeout_ms}; + + assert(sem_init(&sem, 0, 0) == 0); + assert(pthread_create(thread, NULL, &gpio_poll_thread, &args) == 0); + assert(sem_wait(&sem) == 0); +} + +int gpio_poll_join(pthread_t thread) { + void *ret; + + assert(pthread_join(thread, &ret) == 0); + + return (intptr_t)ret; +} + +void test_loopback(void) { + gpio_t *gpio_in, *gpio_out; + pthread_t poll_thread; + bool value; + + ptest(); + + /* Allocate GPIO */ + gpio_in = gpio_new(); + passert(gpio_in != NULL); + gpio_out = gpio_new(); + passert(gpio_out != NULL); + + /* Open in and out pins */ + passert(gpio_open_sysfs(gpio_in, pin_input, GPIO_DIR_IN) == 0); + passert(gpio_open_sysfs(gpio_out, pin_output, GPIO_DIR_OUT) == 0); + + /* Drive out low, check in low */ + passert(gpio_write(gpio_out, false) == 0); + passert(gpio_read(gpio_in, &value) == 0); + passert(value == false); + + /* Drive out high, check in high */ + passert(gpio_write(gpio_out, true) == 0); + passert(gpio_read(gpio_in, &value) == 0); + passert(value == true); + + /* Check poll falling 1 -> 0 interrupt */ + passert(gpio_set_edge(gpio_in, GPIO_EDGE_FALLING) == 0); + gpio_poll_start(&poll_thread, gpio_in, 1000); + passert(gpio_write(gpio_out, false) == 0); + passert(gpio_poll_join(poll_thread) == 1); + passert(gpio_read(gpio_in, &value) == 0); + passert(value == false); + + /* Check poll rising 0 -> 1 interrupt */ + passert(gpio_set_edge(gpio_in, GPIO_EDGE_RISING) == 0); + gpio_poll_start(&poll_thread, gpio_in, 1000); + passert(gpio_write(gpio_out, true) == 0); + passert(gpio_poll_join(poll_thread) == 1); + passert(gpio_read(gpio_in, &value) == 0); + passert(value == true); + + /* Check poll timeout */ + passert(gpio_poll(gpio_in, 1000) == 0); + + /* Test gpio_poll_multiple() API with one GPIO */ + gpio_t *gpios[1] = {gpio_in}; + bool gpios_ready[1] = {false}; + + passert(gpio_set_edge(gpio_in, GPIO_EDGE_BOTH) == 0); + + /* Check poll falling 1 -> 0 interrupt */ + passert(gpio_write(gpio_out, false) == 0); + passert(gpio_poll_multiple(gpios, 1, 1000, gpios_ready) == 1); + passert(gpios_ready[0] == true); + passert(gpio_read(gpio_in, &value) == 0); + passert(value == false); + + /* Check poll rising 0 -> 1 interrupt */ + passert(gpio_write(gpio_out, true) == 0); + passert(gpio_poll_multiple(gpios, 1, 1000, gpios_ready) == 1); + passert(gpios_ready[0] == true); + passert(gpio_read(gpio_in, &value) == 0); + passert(value == true); + + /* Check poll timeout */ + passert(gpio_poll_multiple(gpios, 1, 1000, gpios_ready) == 0); + passert(gpios_ready[0] == false); + + passert(gpio_close(gpio_in) == 0); + passert(gpio_close(gpio_out) == 0); + + /* Free GPIO */ + gpio_free(gpio_in); + gpio_free(gpio_out); +} + +bool getc_yes(void) { + char buf[4]; + fgets(buf, sizeof(buf), stdin); + return (buf[0] == 'y' || buf[0] == 'Y'); +} + +void test_interactive(void) { + char str[256]; + gpio_t *gpio; + + ptest(); + + /* Allocate GPIO */ + gpio = gpio_new(); + passert(gpio != NULL); + + passert(gpio_open_sysfs(gpio, pin_output, GPIO_DIR_OUT) == 0); + + printf("Starting interactive test. Get out your multimeter, buddy!\n"); + printf("Press enter to continue...\n"); + getc(stdin); + + /* Check tostring */ + passert(gpio_tostring(gpio, str, sizeof(str)) > 0); + printf("GPIO description: %s\n", str); + printf("GPIO description looks OK? y/n\n"); + passert(getc_yes()); + + /* Drive GPIO out low */ + passert(gpio_write(gpio, false) == 0); + printf("GPIO out is low? y/n\n"); + passert(getc_yes()); + + /* Drive GPIO out high */ + passert(gpio_write(gpio, true) == 0); + printf("GPIO out is high? y/n\n"); + passert(getc_yes()); + + /* Drive GPIO out low */ + passert(gpio_write(gpio, false) == 0); + printf("GPIO out is low? y/n\n"); + passert(getc_yes()); + + passert(gpio_close(gpio) == 0); + + /* Free GPIO */ + gpio_free(gpio); +} + +int main(int argc, char *argv[]) { + if (argc < 3) { + fprintf(stderr, "Usage: %s \n\n", argv[0]); + fprintf(stderr, "[1/4] Argument test: No requirements.\n"); + fprintf(stderr, "[2/4] Open/close test: GPIO #2 should be real.\n"); + fprintf(stderr, "[3/4] Loopback test: GPIOs #1 and #2 should be connected with a wire.\n"); + fprintf(stderr, "[4/4] Interactive test: GPIO #2 should be observed with a multimeter.\n\n"); + fprintf(stderr, "Hint: for Raspberry Pi 3,\n"); + fprintf(stderr, "Use GPIO 17 (header pin 11) and GPIO 27 (header pin 13),\n"); + fprintf(stderr, "connect a loopback between them, and run this test with:\n"); + fprintf(stderr, " %s 17 27\n\n", argv[0]); + exit(1); + } + + pin_input = strtoul(argv[1], NULL, 10); + pin_output = strtoul(argv[2], NULL, 10); + + test_arguments(); + printf(" " STR_OK " Arguments test passed.\n\n"); + test_open_config_close(); + printf(" " STR_OK " Open/close test passed.\n\n"); + test_loopback(); + printf(" " STR_OK " Loopback test passed.\n\n"); + test_interactive(); + printf(" " STR_OK " Interactive test passed.\n\n"); + + printf("All tests passed!\n"); + return 0; +} + diff --git a/third_party/periphery/tests/test_i2c.c b/third_party/periphery/tests/test_i2c.c new file mode 100644 index 000000000..58c71d055 --- /dev/null +++ b/third_party/periphery/tests/test_i2c.c @@ -0,0 +1,137 @@ +/* + * c-periphery + * https://github.com/vsergeev/c-periphery + * License: MIT + */ + +#include "test.h" + +#include +#include +#include +#include + +#include +#include +#include + +#include "../src/i2c.h" + +const char *i2c_bus_path; + +void test_arguments(void) { + ptest(); + + /* No real argument validation needed in the i2c wrapper */ +} + +void test_open_config_close(void) { + i2c_t *i2c; + + ptest(); + + /* Allocate I2C */ + i2c = i2c_new(); + passert(i2c != NULL); + + /* Open invalid i2c bus */ + passert(i2c_open(i2c, "/foo/bar") == I2C_ERROR_OPEN); + + /* Open legitimate i2c bus */ + passert(i2c_open(i2c, i2c_bus_path) == 0); + passert(i2c_close(i2c) == 0); + + /* Free I2C */ + i2c_free(i2c); +} + +void test_loopback(void) { + ptest(); + + printf("No general way to do a loopback test for I2C without a real component, skipping...\n"); +} + +bool getc_yes(void) { + char buf[4]; + fgets(buf, sizeof(buf), stdin); + return (buf[0] == 'y' || buf[0] == 'Y'); +} + +void test_interactive(void) { + char str[256]; + i2c_t *i2c; + uint8_t msg1[] = { 0xaa, 0xbb, 0xcc, 0xdd }; + struct i2c_msg msgs[1]; + + ptest(); + + /* Allocate I2C */ + i2c = i2c_new(); + passert(i2c != NULL); + + passert(i2c_open(i2c, i2c_bus_path) == 0); + + printf("Starting interactive test. Get out your logic analyzer, buddy!\n"); + printf("Press enter to continue...\n"); + getc(stdin); + + /* Check tostring */ + passert(i2c_tostring(i2c, str, sizeof(str)) > 0); + printf("I2C description: %s\n", str); + printf("I2C description looks OK? y/n\n"); + passert(getc_yes()); + + /* There isn't much we can do without assuming a device on the other end, + * because I2C needs an acknowledgement bit on each transferred byte. + * + * But we can send a transaction and expect it to time out. */ + + /* S [ 0x7a W ] [0xaa] [0xbb] [0xcc] [0xdd] */ + msgs[0].addr = 0x7a; + msgs[0].flags = 0; /* Write */ + msgs[0].len = sizeof(msg1); + msgs[0].buf = msg1; + + printf("Press enter to start transfer..."); + getc(stdin); + passert(i2c_transfer(i2c, msgs, 1) < 0); + passert(i2c_errno(i2c) == EREMOTEIO); + passert(i2c_close(i2c) == 0); + printf("I2C transfer occurred? y/n\n"); + passert(getc_yes()); + + /* Free I2C */ + i2c_free(i2c); +} + +int main(int argc, char *argv[]) { + if (argc < 2) { + fprintf(stderr, "Usage: %s \n\n", argv[0]); + fprintf(stderr, "[1/4] Arguments test: No requirements.\n"); + fprintf(stderr, "[2/4] Open/close test: I2C device should be real.\n"); + fprintf(stderr, "[3/4] Loopback test: No test.\n"); + fprintf(stderr, "[4/4] Interactive test: I2C bus should be observed with an oscilloscope or logic analyzer.\n\n"); + fprintf(stderr, "Hint: for Raspberry Pi 3, enable I2C1 with:\n"); + fprintf(stderr, " $ echo \"dtparam=i2c_arm=on\" | sudo tee -a /boot/config.txt\n"); + fprintf(stderr, " $ sudo reboot\n"); + fprintf(stderr, "Use pins I2C1 SDA (header pin 2) and I2C1 SCL (header pin 3),\n"); + fprintf(stderr, "and run this test with:\n"); + fprintf(stderr, " %s /dev/i2c-1\n\n", argv[0]); + exit(1); + } + + i2c_bus_path = argv[1]; + + test_arguments(); + printf(" " STR_OK " Arguments test passed.\n\n"); + test_open_config_close(); + printf(" " STR_OK " Open/close test passed.\n\n"); + test_loopback(); + printf(" " STR_OK " Loopback test passed.\n\n"); + test_interactive(); + printf(" " STR_OK " Interactive test passed.\n\n"); + + printf("All tests passed!\n"); + return 0; +} + diff --git a/third_party/periphery/tests/test_led.c b/third_party/periphery/tests/test_led.c new file mode 100644 index 000000000..4ccdc4d09 --- /dev/null +++ b/third_party/periphery/tests/test_led.c @@ -0,0 +1,174 @@ +/* + * c-periphery + * https://github.com/vsergeev/c-periphery + * License: MIT + */ + +#include "test.h" + +#include +#include +#include +#include + +#include "../src/led.h" + +const char *device; + +void test_arguments(void) { + ptest(); + + /* No real argument validation needed in the LED wrapper */ +} + +void test_open_config_close(void) { + led_t *led; + char name[16]; + unsigned int max_brightness; + unsigned int brightness; + bool value; + + ptest(); + + /* Allocate LED */ + led = led_new(); + passert(led != NULL); + + /* Open non-existent LED */ + passert(led_open(led, "nonexistent") == LED_ERROR_OPEN); + + /* Open legitimate LED */ + passert(led_open(led, device) == 0); + + /* Check properties */ + passert(led_name(led, name, sizeof(name)) == 0); + passert(strcmp(name, device) == 0); + + /* Check max brightness */ + passert(led_get_max_brightness(led, &max_brightness) == 0); + passert(max_brightness > 0); + + /* Check setting invalid brightness */ + passert(led_set_brightness(led, max_brightness + 1) == LED_ERROR_ARG); + + /* Write true, read true, check brightness is max */ + passert(led_write(led, true) == 0); + usleep(10000); + passert(led_read(led, &value) == 0); + passert(value == true); + passert(led_get_brightness(led, &brightness) == 0); + passert(brightness == max_brightness); + + /* Write false, read false, check brightness is zero */ + passert(led_write(led, false) == 0); + usleep(10000); + passert(led_read(led, &value) == 0); + passert(value == false); + passert(led_get_brightness(led, &brightness) == 0); + passert(brightness == 0); + + /* Set brightness to 1, check brightness */ + passert(led_set_brightness(led, 1) == 0); + usleep(10000); + passert(led_get_brightness(led, &brightness) == 0); + passert(brightness >= 1); + + /* Set brightness to 0, check brightness */ + passert(led_set_brightness(led, 0) == 0); + usleep(10000); + passert(led_get_brightness(led, &brightness) == 0); + passert(brightness == 0); + + passert(led_close(led) == 0); + + /* Free LED */ + led_free(led); +} + +void test_loopback(void) { + ptest(); +} + +bool getc_yes(void) { + char buf[4]; + fgets(buf, sizeof(buf), stdin); + return (buf[0] == 'y' || buf[0] == 'Y'); +} + +void test_interactive(void) { + char str[256]; + led_t *led; + + ptest(); + + /* Allocate LED */ + led = led_new(); + passert(led != NULL); + + passert(led_open(led, device) == 0); + + printf("Starting interactive test...\n"); + printf("Press enter to continue...\n"); + getc(stdin); + + /* Check tostring */ + passert(led_tostring(led, str, sizeof(str)) > 0); + printf("LED description: %s\n", str); + printf("LED description looks OK? y/n\n"); + passert(getc_yes()); + + /* Turn LED off */ + passert(led_write(led, false) == 0); + printf("LED is off? y/n\n"); + passert(getc_yes()); + + /* Turn LED on */ + passert(led_write(led, true) == 0); + printf("LED is on? y/n\n"); + passert(getc_yes()); + + /* Turn LED off */ + passert(led_write(led, false) == 0); + printf("LED is off? y/n\n"); + passert(getc_yes()); + + /* Turn LED on */ + passert(led_write(led, true) == 0); + printf("LED is on? y/n\n"); + passert(getc_yes()); + + passert(led_close(led) == 0); + + /* Free LED */ + led_free(led); +} + +int main(int argc, char *argv[]) { + if (argc < 2) { + fprintf(stderr, "Usage: %s \n\n", argv[0]); + fprintf(stderr, "[1/4] Arguments test: No requirements.\n"); + fprintf(stderr, "[2/4] Open/close test: LED should be real.\n"); + fprintf(stderr, "[3/4] Loopback test: No test.\n"); + fprintf(stderr, "[4/4] Interactive test: LED should be observed.\n\n"); + fprintf(stderr, "Hint: for Raspberry Pi 3, disable triggers for led1:\n"); + fprintf(stderr, " $ echo none > /sys/class/leds/led1/trigger\n"); + fprintf(stderr, "Observe led1 (red power LED), and run this test:\n"); + fprintf(stderr, " %s led1\n\n", argv[0]); + exit(1); + } + + device = argv[1]; + + test_arguments(); + printf(" " STR_OK " Arguments test passed.\n\n"); + test_open_config_close(); + printf(" " STR_OK " Open/close test passed.\n\n"); + test_loopback(); + printf(" " STR_OK " Loopback test passed.\n\n"); + test_interactive(); + printf(" " STR_OK " Interactive test passed.\n\n"); + + printf("All tests passed!\n"); + return 0; +} + diff --git a/third_party/periphery/tests/test_mmio.c b/third_party/periphery/tests/test_mmio.c new file mode 100644 index 000000000..373f9dcf8 --- /dev/null +++ b/third_party/periphery/tests/test_mmio.c @@ -0,0 +1,241 @@ +/* + * c-periphery + * https://github.com/vsergeev/c-periphery + * License: MIT + */ + +#include "test.h" + +#include +#include +#include +#include + +#include "../src/mmio.h" + +#define PAGE_SIZE 4096 + +#define CONTROL_MODULE_BASE 0x44e10000 +#define USB_VID_PID_OFFSET 0x7f4 +#define USB_VID_PID 0x04516141 + +#define RTCSS_BASE 0x44e3e000 +#define RTC_SCRATCH2_REG_OFFSET 0x68 + +void test_arguments(void) { + ptest(); + + /* Check offset out of bounds in test_open_config_close() */ +} + +void test_open_config_close(void) { + mmio_t *mmio; + uintptr_t address; + uint32_t value32; + + ptest(); + + /* Allocate MMIO */ + mmio = mmio_new(); + passert(mmio != NULL); + + /* Open aligned base */ + passert(mmio_open(mmio, CONTROL_MODULE_BASE, PAGE_SIZE) == 0); + passert(mmio_base(mmio) == CONTROL_MODULE_BASE); + passert(mmio_size(mmio) == PAGE_SIZE); + passert(mmio_ptr(mmio) != NULL); + + struct mmio_handle { + uintptr_t base, aligned_base; + size_t size, aligned_size; + void *ptr; + + struct { + int c_errno; + char errmsg[96]; + } error; + }; + + /* Check alignment math */ + passert(((struct mmio_handle *)mmio)->base == CONTROL_MODULE_BASE); + passert(((struct mmio_handle *)mmio)->size == PAGE_SIZE); + passert(((struct mmio_handle *)mmio)->aligned_base == CONTROL_MODULE_BASE); + passert(((struct mmio_handle *)mmio)->aligned_size == PAGE_SIZE); + passert(mmio_ptr(mmio) == ((struct mmio_handle *)mmio)->ptr); + + passert(mmio_read32(mmio, PAGE_SIZE-3, &value32) == MMIO_ERROR_ARG); + passert(mmio_read32(mmio, PAGE_SIZE-2, &value32) == MMIO_ERROR_ARG); + passert(mmio_read32(mmio, PAGE_SIZE-1, &value32) == MMIO_ERROR_ARG); + passert(mmio_read32(mmio, PAGE_SIZE, &value32) == MMIO_ERROR_ARG); + passert(mmio_close(mmio) == 0); + + /* Open unaligned base */ + address = CONTROL_MODULE_BASE + 123; + passert(mmio_open(mmio, address, PAGE_SIZE) == 0); + passert(mmio_base(mmio) == address); + passert(mmio_size(mmio) == PAGE_SIZE); + passert(mmio_ptr(mmio) != NULL); + + /* Check alignment math */ + passert(((struct mmio_handle *)mmio)->base == address); + passert(((struct mmio_handle *)mmio)->size == PAGE_SIZE); + passert(((struct mmio_handle *)mmio)->aligned_base == (address - (address % sysconf(_SC_PAGESIZE)))); + passert(((struct mmio_handle *)mmio)->aligned_size == (PAGE_SIZE + (address % sysconf(_SC_PAGESIZE)))); + passert((size_t)((uint8_t *)mmio_ptr(mmio) - (uint8_t *)((struct mmio_handle *)mmio)->ptr) == + (size_t)(((struct mmio_handle *)mmio)->base - ((struct mmio_handle *)mmio)->aligned_base)); + + passert(mmio_read32(mmio, PAGE_SIZE-3, &value32) == MMIO_ERROR_ARG); + passert(mmio_read32(mmio, PAGE_SIZE-2, &value32) == MMIO_ERROR_ARG); + passert(mmio_read32(mmio, PAGE_SIZE-1, &value32) == MMIO_ERROR_ARG); + passert(mmio_read32(mmio, PAGE_SIZE, &value32) == MMIO_ERROR_ARG); + passert(mmio_close(mmio) == 0); + + /* Free MMIO */ + mmio_free(mmio); +} + +void test_loopback(void) { + mmio_t *mmio; + uint32_t value32; + uint8_t data[4]; + uint8_t vector[] = { 0xaa, 0xbb, 0xcc, 0xdd }; + + ptest(); + + /* Allocate MMIO */ + mmio = mmio_new(); + passert(mmio != NULL); + + /* Read USB VID/PID */ + passert(mmio_open(mmio, CONTROL_MODULE_BASE, PAGE_SIZE) == 0); + passert(mmio_read32(mmio, USB_VID_PID_OFFSET, &value32) == 0); + passert(value32 == USB_VID_PID); + passert(mmio_close(mmio) == 0); + + /* Read USB VID/PID via byte read */ + passert(mmio_open(mmio, CONTROL_MODULE_BASE, PAGE_SIZE) == 0); + passert(mmio_read(mmio, USB_VID_PID_OFFSET, data, 4) == 0); + passert(data[0] == (USB_VID_PID & 0xff)); + passert(data[1] == ((USB_VID_PID >> 8) & 0xff)); + passert(data[2] == ((USB_VID_PID >> 16) & 0xff)); + passert(data[3] == ((USB_VID_PID >> 24) & 0xff)); + passert(mmio_close(mmio) == 0); + + /* Write/Read RTC Scratch2 Register */ + passert(mmio_open(mmio, RTCSS_BASE, PAGE_SIZE) == 0); + passert(mmio_write32(mmio, RTC_SCRATCH2_REG_OFFSET, 0xdeadbeef) == 0); + passert(mmio_read32(mmio, RTC_SCRATCH2_REG_OFFSET, &value32) == 0); + passert(value32 == 0xdeadbeef); + passert(mmio_close(mmio) == 0); + + /* Write/Read RTC Scratch2 Register via byte write */ + passert(mmio_open(mmio, RTCSS_BASE, PAGE_SIZE) == 0); + passert(mmio_write(mmio, RTC_SCRATCH2_REG_OFFSET, vector, 4) == 0); + passert(mmio_read32(mmio, RTC_SCRATCH2_REG_OFFSET, &value32) == 0); + passert(value32 == 0xddccbbaa); + passert(mmio_read(mmio, RTC_SCRATCH2_REG_OFFSET, data, 4) == 0); + passert(memcmp(data, vector, 4) == 0); + passert(mmio_close(mmio) == 0); + + /* Free MMIO */ + mmio_free(mmio); +} + +struct rtc_ss { + volatile uint32_t seconds; /* 0x00 */ + volatile uint32_t minutes; /* 0x04 */ + volatile uint32_t hours; /* 0x08 */ + volatile uint32_t days; /* 0x0C */ + volatile uint32_t months; /* 0x10 */ + volatile uint32_t years; /* 0x14 */ + volatile uint32_t weeks; /* 0x18 */ + + volatile uint32_t reserved1; /* 0x1C */ + + volatile uint32_t alarm_seconds; /* 0x20 */ + volatile uint32_t alarm_minutes; /* 0x24 */ + volatile uint32_t alarm_hours; /* 0x28 */ + volatile uint32_t alarm_days; /* 0x2C */ + volatile uint32_t alarm_months; /* 0x30 */ + volatile uint32_t alarm_years; /* 0x34 */ + + volatile uint32_t reserved2; /* 0x38 */ + volatile uint32_t reserved3; /* 0x3C */ + + volatile uint32_t rtc_ctrl; /* 0x40 */ + volatile uint32_t rtc_status; /* 0x44 */ + volatile uint32_t rtc_interrupts;/* 0x48 */ +}; + +#define BCD_HI(x) (((x) >> 4) & 0xf) +#define BCD_LO(x) ((x) & 0xf) +#define BCD2DEC(x) (10*BCD_HI(x) + BCD_LO(x)) + +void test_interactive(void) { + mmio_t *mmio; + uint32_t rtc_start, rtc_stop; + time_t start, stop; + struct rtc_ss *rtc; + + ptest(); + + /* Allocate MMIO */ + mmio = mmio_new(); + passert(mmio != NULL); + + passert(mmio_open(mmio, RTCSS_BASE, PAGE_SIZE) == 0); + rtc = (struct rtc_ss *)mmio_ptr(mmio); + + printf("Waiting for seconds ones digit to reset to 0...\n"); + + start = time(NULL); + /* Wait until seconds low go to 0, so we don't have to deal with overflows + * in comparing times */ + while (BCD_LO(rtc->seconds) != 0) { + usleep(500000); + passert((time(NULL) - start) < 12); + } + + start = time(NULL); + rtc_start = rtc->seconds; + + printf("Date: %04d-%02d-%02d\n", 2000 + BCD2DEC(rtc->years), BCD2DEC(rtc->months), BCD2DEC(rtc->days)); + printf("Time: %02d:%02d:%02d %s\n", BCD2DEC(rtc->hours & 0x7f), BCD2DEC(rtc->minutes), BCD2DEC(rtc->seconds), (rtc->hours & 0x80) ? "PM" : "AM"); + + sleep(3); + + printf("Date: %02d-%02d-%02d\n", 2000 + BCD2DEC(rtc->years), BCD2DEC(rtc->months), BCD2DEC(rtc->days)); + printf("Time: %02d:%02d:%02d %s\n", BCD2DEC(rtc->hours & 0x7f), BCD2DEC(rtc->minutes), BCD2DEC(rtc->seconds), (rtc->hours & 0x80) ? "PM" : "AM"); + + rtc_stop = rtc->seconds; + stop = time(NULL); + + /* Check that time has passed */ + passert((stop - start) > 2); + passert((rtc_stop - rtc_start) > 2); + + passert(mmio_close(mmio) == 0); + + /* Free MMIO */ + mmio_free(mmio); +} + +int main(void) { + printf("WARNING: This test suite assumes a BeagleBone Black (AM335x) host!\n"); + printf("Other systems may experience unintended and dire consequences!\n"); + printf("Press enter to continue!\n"); + getc(stdin); + + test_arguments(); + printf(" " STR_OK " Arguments test passed.\n\n"); + test_open_config_close(); + printf(" " STR_OK " Open/close test passed.\n\n"); + test_loopback(); + printf(" " STR_OK " Loopback test passed.\n\n"); + test_interactive(); + printf(" " STR_OK " Interactive test passed.\n\n"); + + printf("All tests passed!\n"); + return 0; +} + diff --git a/third_party/periphery/tests/test_pwm.c b/third_party/periphery/tests/test_pwm.c new file mode 100644 index 000000000..0b7c41add --- /dev/null +++ b/third_party/periphery/tests/test_pwm.c @@ -0,0 +1,292 @@ +/* + * c-periphery + * https://github.com/vsergeev/c-periphery + * License: MIT + */ + +#include "test.h" + +#include +#include +#include +#include + +#include "../src/pwm.h" + +unsigned int chip; +unsigned int channel; + +void test_arguments(void) { + ptest(); + + /* No real argument validation needed in the PWM wrapper */ +} + +static double fabs(double x) { + return (x < 0) ? -x : x; +} + +void test_open_config_close(void) { + pwm_t *pwm; + uint64_t period_ns; + uint64_t duty_cycle_ns; + double period; + double frequency; + double duty_cycle; + pwm_polarity_t polarity; + bool enabled; + + ptest(); + + /* Allocate PWM */ + pwm = pwm_new(); + passert(pwm != NULL); + + /* Open non-existent PWM chip */ + passert(pwm_open(pwm, 9999, channel) == PWM_ERROR_OPEN); + + /* Open non-existent PWM channel */ + passert(pwm_open(pwm, chip, 9999) == PWM_ERROR_OPEN); + + /* Open legitimate PWM chip/channel */ + passert(pwm_open(pwm, chip, channel) == 0); + + /* Check properties */ + passert(pwm_chip(pwm) == chip); + passert(pwm_channel(pwm) == channel); + + /* Initialize period and duty cycle */ + passert(pwm_set_period(pwm, 5e-3) == 0); + passert(pwm_set_duty_cycle(pwm, 0) == 0); + + /* Set period, check period, check period_ns, check frequency */ + passert(pwm_set_period(pwm, 1e-3) == 0); + passert(pwm_get_period(pwm, &period) == 0); + passert(fabs(period - 1e-3) < 1e-4); + passert(pwm_get_period_ns(pwm, &period_ns) == 0); + passert(fabs(period_ns - 1000000) < 1e5); + passert(pwm_get_frequency(pwm, &frequency) == 0); + passert(fabs(frequency - 1000) < 100); + + passert(pwm_set_period(pwm, 5e-4) == 0); + passert(pwm_get_period(pwm, &period) == 0); + passert(fabs(period - 5e-4) < 1e-5); + passert(pwm_get_period_ns(pwm, &period_ns) == 0); + passert(fabs(period_ns - 500000) < 1e4); + passert(pwm_get_frequency(pwm, &frequency) == 0); + passert(fabs(frequency - 2000) < 100); + + /* Set frequency, check frequency, check period, check period_ns */ + passert(pwm_set_frequency(pwm, 1000) == 0); + passert(pwm_get_frequency(pwm, &frequency) == 0); + passert(fabs(frequency - 1000) < 100); + passert(pwm_get_period(pwm, &period) == 0); + passert(fabs(period - 1e-3) < 1e-4); + passert(pwm_get_period_ns(pwm, &period_ns) == 0); + passert(fabs(period_ns - 1000000) < 1e5); + + passert(pwm_set_frequency(pwm, 2000) == 0); + passert(pwm_get_frequency(pwm, &frequency) == 0); + passert(fabs(frequency - 2000) < 100); + passert(pwm_get_period(pwm, &period) == 0); + passert(fabs(period - 5e-4) < 1e-5); + passert(pwm_get_period_ns(pwm, &period_ns) == 0); + passert(fabs(period_ns - 500000) < 1e4); + + /* Set period_ns, check period_ns, check period, check frequency */ + passert(pwm_set_period_ns(pwm, 1000000) == 0); + passert(pwm_get_period_ns(pwm, &period_ns) == 0); + passert(fabs(period_ns - 1000000) < 1e5); + passert(pwm_get_period(pwm, &period) == 0); + passert(fabs(period - 1e-3) < 1e-4); + passert(pwm_get_frequency(pwm, &frequency) == 0); + passert(fabs(frequency - 1000) < 100); + + passert(pwm_set_period_ns(pwm, 500000) == 0); + passert(pwm_get_period_ns(pwm, &period_ns) == 0); + passert(fabs(period_ns - 500000) < 1e4); + passert(pwm_get_period(pwm, &period) == 0); + passert(fabs(period - 5e-4) < 1e-5); + passert(pwm_get_frequency(pwm, &frequency) == 0); + passert(fabs(frequency - 2000) < 100); + + passert(pwm_set_period_ns(pwm, 1000000) == 0); + + /* Set duty cycle, check duty cycle, check duty_cycle_ns */ + passert(pwm_set_duty_cycle(pwm, 0.25) == 0); + passert(pwm_get_duty_cycle(pwm, &duty_cycle) == 0); + passert(fabs(duty_cycle - 0.25) < 1e-3); + passert(pwm_get_duty_cycle_ns(pwm, &duty_cycle_ns) == 0); + passert(fabs(duty_cycle_ns - 250000) < 1e4); + + passert(pwm_set_duty_cycle(pwm, 0.50) == 0); + passert(pwm_get_duty_cycle(pwm, &duty_cycle) == 0); + passert(fabs(duty_cycle - 0.50) < 1e-3); + passert(pwm_get_duty_cycle_ns(pwm, &duty_cycle_ns) == 0); + passert(fabs(duty_cycle_ns - 500000) < 1e4); + + passert(pwm_set_duty_cycle(pwm, 0.75) == 0); + passert(pwm_get_duty_cycle(pwm, &duty_cycle) == 0); + passert(fabs(duty_cycle - 0.75) < 1e-3); + passert(pwm_get_duty_cycle_ns(pwm, &duty_cycle_ns) == 0); + passert(fabs(duty_cycle_ns - 750000) < 1e4); + + /* Set duty_cycle_ns, check duty_cycle_ns, check duty_cycle */ + passert(pwm_set_duty_cycle_ns(pwm, 250000) == 0); + passert(pwm_get_duty_cycle_ns(pwm, &duty_cycle_ns) == 0); + passert(fabs(duty_cycle_ns - 250000) < 1e4); + passert(pwm_get_duty_cycle(pwm, &duty_cycle) == 0); + passert(fabs(duty_cycle - 0.25) < 1e-3); + + passert(pwm_set_duty_cycle_ns(pwm, 500000) == 0); + passert(pwm_get_duty_cycle_ns(pwm, &duty_cycle_ns) == 0); + passert(fabs(duty_cycle_ns - 500000) < 1e4); + passert(pwm_get_duty_cycle(pwm, &duty_cycle) == 0); + passert(fabs(duty_cycle - 0.50) < 1e-3); + + passert(pwm_set_duty_cycle_ns(pwm, 750000) == 0); + passert(pwm_get_duty_cycle_ns(pwm, &duty_cycle_ns) == 0); + passert(fabs(duty_cycle_ns - 750000) < 1e4); + passert(pwm_get_duty_cycle(pwm, &duty_cycle) == 0); + passert(fabs(duty_cycle - 0.75) < 1e-3); + + /* Set polarity, check polarity */ + passert(pwm_set_polarity(pwm, PWM_POLARITY_NORMAL) == 0); + passert(pwm_get_polarity(pwm, &polarity) == 0); + passert(polarity == PWM_POLARITY_NORMAL); + + passert(pwm_set_polarity(pwm, PWM_POLARITY_INVERSED) == 0); + passert(pwm_get_polarity(pwm, &polarity) == 0); + passert(polarity == PWM_POLARITY_INVERSED); + + /* Set enabled, check enabled */ + passert(pwm_set_enabled(pwm, true) == 0); + passert(pwm_get_enabled(pwm, &enabled) == 0); + passert(enabled == true); + + passert(pwm_set_enabled(pwm, false) == 0); + passert(pwm_get_enabled(pwm, &enabled) == 0); + passert(enabled == false); + + /* Use pwm_enable()/pwm_disable(), check enabled */ + passert(pwm_enable(pwm) == 0); + passert(pwm_get_enabled(pwm, &enabled) == 0); + passert(enabled == true); + + passert(pwm_disable(pwm) == 0); + passert(pwm_get_enabled(pwm, &enabled) == 0); + passert(enabled == false); + + /* Set invalid polarity */ + passert(pwm_set_polarity(pwm, 123) == PWM_ERROR_ARG); + + passert(pwm_close(pwm) == 0); + + /* Free PWM */ + pwm_free(pwm); +} + +void test_loopback(void) { + ptest(); +} + +bool getc_yes(void) { + char buf[4]; + fgets(buf, sizeof(buf), stdin); + return (buf[0] == 'y' || buf[0] == 'Y'); +} + +void test_interactive(void) { + char str[256]; + pwm_t *pwm; + + ptest(); + + /* Allocate PWM */ + pwm = pwm_new(); + passert(pwm != NULL); + + passert(pwm_open(pwm, chip, channel) == 0); + + printf("Starting interactive test. Get out your oscilloscope, buddy!\n"); + printf("Press enter to continue...\n"); + getc(stdin); + + /* Set initial parameters and enable PWM */ + passert(pwm_set_duty_cycle(pwm, 0.0) == 0); + passert(pwm_set_frequency(pwm, 1e3) == 0); + passert(pwm_set_polarity(pwm, PWM_POLARITY_NORMAL) == 0); + passert(pwm_enable(pwm) == 0); + + /* Check tostring */ + passert(pwm_tostring(pwm, str, sizeof(str)) > 0); + printf("PWM description: %s\n", str); + printf("PWM description looks OK? y/n\n"); + passert(getc_yes()); + + /* Set 1 kHz frequency, 0.25 duty cycle */ + passert(pwm_set_frequency(pwm, 1e3) == 0); + passert(pwm_set_duty_cycle(pwm, 0.25) == 0); + printf("Frequency is 1 kHz, duty cycle is 25%%? y/n\n"); + passert(getc_yes()); + + /* Set 1 kHz frequency, 0.50 duty cycle */ + passert(pwm_set_frequency(pwm, 1e3) == 0); + passert(pwm_set_duty_cycle(pwm, 0.50) == 0); + printf("Frequency is 1 kHz, duty cycle is 50%%? y/n\n"); + passert(getc_yes()); + + /* Set 2 kHz frequency, 0.25 duty cycle */ + passert(pwm_set_frequency(pwm, 2e3) == 0); + passert(pwm_set_duty_cycle(pwm, 0.25) == 0); + printf("Frequency is 2 kHz, duty cycle is 25%%? y/n\n"); + passert(getc_yes()); + + /* Set 2 kHz frequency, 0.50 duty cycle */ + passert(pwm_set_frequency(pwm, 2e3) == 0); + passert(pwm_set_duty_cycle(pwm, 0.50) == 0); + printf("Frequency is 2 kHz, duty cycle is 50%%? y/n\n"); + passert(getc_yes()); + + passert(pwm_set_duty_cycle(pwm, 0.0) == 0); + passert(pwm_disable(pwm) == 0); + + passert(pwm_close(pwm) == 0); + + /* Free PWM */ + pwm_free(pwm); +} + +int main(int argc, char *argv[]) { + if (argc < 2) { + fprintf(stderr, "Usage: %s \n\n", argv[0]); + fprintf(stderr, "[1/4] Arguments test: No requirements.\n"); + fprintf(stderr, "[2/4] Open/close test: PWM channel should be real.\n"); + fprintf(stderr, "[3/4] Loopback test: No test.\n"); + fprintf(stderr, "[4/4] Interactive test: PWM channel should be observed with an oscilloscope or logic analyzer.\n\n"); + fprintf(stderr, "Hint: for Raspberry Pi 3, enable PWM0 and PWM1 with:\n"); + fprintf(stderr, " $ echo \"dtoverlay=pwm-2chan,pin=18,func=2,pin2=13,func2=4\" | sudo tee -a /boot/config.txt\n"); + fprintf(stderr, " $ sudo reboot\n"); + fprintf(stderr, "Monitor GPIO 18 (header pin 12), and run this test with:\n"); + fprintf(stderr, " %s 0 0\n", argv[0]); + fprintf(stderr, "or, monitor GPIO 13 (header pin 33), and run this test with:\n"); + fprintf(stderr, " %s 0 1\n\n", argv[0]); + exit(1); + } + + chip = strtoul(argv[1], NULL, 10); + channel = strtoul(argv[2], NULL, 10); + + test_arguments(); + printf(" " STR_OK " Arguments test passed.\n\n"); + test_open_config_close(); + printf(" " STR_OK " Open/close test passed.\n\n"); + test_loopback(); + printf(" " STR_OK " Loopback test passed.\n\n"); + test_interactive(); + printf(" " STR_OK " Interactive test passed.\n\n"); + + printf("All tests passed!\n"); + return 0; +} + diff --git a/third_party/periphery/tests/test_serial.c b/third_party/periphery/tests/test_serial.c new file mode 100644 index 000000000..9290da7b5 --- /dev/null +++ b/third_party/periphery/tests/test_serial.c @@ -0,0 +1,289 @@ +/* + * c-periphery + * https://github.com/vsergeev/c-periphery + * License: MIT + */ + +#include "test.h" + +#include +#include + +#include +#include + +#include "../src/serial.h" + +#define ABS(x) ((x < 0) ? -x : x) + +const char *device; + +void test_arguments(void) { + serial_t *serial; + + ptest(); + + /* Allocate serial */ + serial = serial_new(); + passert(serial != NULL); + + /* Invalid data bits (4 and 9) */ + passert(serial_open_advanced(serial, device, 115200, 4, PARITY_NONE, 1, false, false) == SERIAL_ERROR_ARG); + passert(serial_open_advanced(serial, device, 115200, 9, PARITY_NONE, 1, false, false) == SERIAL_ERROR_ARG); + /* Invalid parity */ + passert(serial_open_advanced(serial, device, 115200, 8, PARITY_EVEN+1, 1, false, false) == SERIAL_ERROR_ARG); + /* Invalid stopbits */ + passert(serial_open_advanced(serial, device, 115200, 8, PARITY_NONE, 0, false, false) == SERIAL_ERROR_ARG); + passert(serial_open_advanced(serial, device, 115200, 8, PARITY_NONE, 3, false, false) == SERIAL_ERROR_ARG); + + /* Everything else is fair game, although termios might not like it. */ + + /* Free serial */ + serial_free(serial); +} + +void test_open_config_close(void) { + serial_t *serial; + uint32_t baudrate; + unsigned int databits; + serial_parity_t parity; + unsigned int stopbits; + bool xonxoff; + bool rtscts; + unsigned int vmin; + float vtime; + + ptest(); + + /* Allocate serial */ + serial = serial_new(); + passert(serial != NULL); + + passert(serial_open(serial, device, 115200) == 0); + + /* Check default settings */ + passert(serial_get_baudrate(serial, &baudrate) == 0); + passert(baudrate == 115200); + passert(serial_get_databits(serial, &databits) == 0); + passert(databits == 8); + passert(serial_get_parity(serial, &parity) == 0); + passert(parity == PARITY_NONE); + passert(serial_get_stopbits(serial, &stopbits) == 0); + passert(stopbits == 1); + passert(serial_get_xonxoff(serial, &xonxoff) == 0); + passert(xonxoff == false); + passert(serial_get_rtscts(serial, &rtscts) == 0); + passert(rtscts == false); + passert(serial_get_vmin(serial, &vmin) == 0); + passert(vmin == 0); + passert(serial_get_vtime(serial, &vtime) == 0); + passert(vtime == 0); + + /* Change some stuff around */ + passert(serial_set_baudrate(serial, 4800) == 0); + passert(serial_get_baudrate(serial, &baudrate) == 0); + passert(baudrate == 4800); + passert(serial_set_baudrate(serial, 9600) == 0); + passert(serial_get_baudrate(serial, &baudrate) == 0); + passert(baudrate == 9600); + passert(serial_set_databits(serial, 7) == 0); + passert(serial_get_databits(serial, &databits) == 0); + passert(databits == 7); + passert(serial_set_parity(serial, PARITY_ODD) == 0); + passert(serial_get_parity(serial, &parity) == 0); + passert(parity == PARITY_ODD); + passert(serial_set_stopbits(serial, 2) == 0); + passert(serial_get_stopbits(serial, &stopbits) == 0); + passert(stopbits == 2); + passert(serial_set_xonxoff(serial, true) == 0); + passert(serial_get_xonxoff(serial, &xonxoff) == 0); + passert(xonxoff == true); + #if 0 /* Test serial port may not support rtscts */ + passert(serial_set_rtscts(serial, true) == 0); + passert(serial_get_rtscts(serial, &rtscts) == 0); + passert(rtscts == true); + #endif + passert(serial_set_vmin(serial, 50) == 0); + passert(serial_get_vmin(serial, &vmin) == 0); + passert(vmin == 50); + passert(serial_set_vtime(serial, 15.3) == 0); + passert(serial_get_vtime(serial, &vtime) == 0); + passert(ABS(vtime - 15.3) < 0.1); + + passert(serial_close(serial) == 0); + + /* Free serial */ + serial_free(serial); +} + +void test_loopback(void) { + serial_t *serial; + unsigned int count; + time_t start, stop; + uint8_t lorem_ipsum[] = "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."; + uint8_t lorem_hugesum[4096*3]; + uint8_t buf[sizeof(lorem_hugesum)]; + + ptest(); + + /* Allocate serial */ + serial = serial_new(); + passert(serial != NULL); + + passert(serial_open(serial, device, 115200) == 0); + + /* Test write/flush/read */ + passert(serial_write(serial, lorem_ipsum, sizeof(lorem_ipsum)) == sizeof(lorem_ipsum)); + passert(serial_flush(serial) == 0); + passert(serial_read(serial, buf, sizeof(lorem_ipsum), -1) == sizeof(lorem_ipsum)); + passert(memcmp(lorem_ipsum, buf, sizeof(lorem_ipsum)) == 0); + + /* Test poll/write/flush/poll/input waiting/read */ + passert(serial_poll(serial, 500) == 0); /* Should timeout */ + passert(serial_write(serial, lorem_ipsum, sizeof(lorem_ipsum)) == sizeof(lorem_ipsum)); + passert(serial_flush(serial) == 0); + passert(serial_poll(serial, 500) == 1); + usleep(500000); + passert(serial_input_waiting(serial, &count) == 0); + passert(count == sizeof(lorem_ipsum)); + passert(serial_read(serial, buf, sizeof(lorem_ipsum), -1) == sizeof(lorem_ipsum)); + passert(memcmp(lorem_ipsum, buf, sizeof(lorem_ipsum)) == 0); + + /* Test non-blocking poll */ + passert(serial_poll(serial, 0) == 0); + + /* Test a very large read-write (likely to exceed internal buffer size (~4096)) */ + memset(lorem_hugesum, 0xAA, sizeof(lorem_hugesum)); + passert(serial_write(serial, lorem_hugesum, sizeof(lorem_hugesum)) == sizeof(lorem_hugesum)); + passert(serial_flush(serial) == 0); + passert(serial_read(serial, buf, sizeof(lorem_hugesum), -1) == sizeof(lorem_hugesum)); + passert(memcmp(lorem_hugesum, buf, sizeof(lorem_hugesum)) == 0); + + /* Test read timeout */ + start = time(NULL); + passert(serial_read(serial, buf, sizeof(buf), 2000) == 0); + stop = time(NULL); + passert((stop - start) > 1); + + /* Test non-blocking read */ + start = time(NULL); + passert(serial_read(serial, buf, sizeof(buf), 0) == 0); + stop = time(NULL); + /* Assuming we weren't context switched out for a second and weren't on a + * thin time boundary ;) */ + passert((stop - start) == 0); + + /* Test blocking read with vmin=5 termios timeout */ + passert(serial_set_vmin(serial, 5) == 0); + /* Write 5, read back 5 (== vmin) */ + passert(serial_write(serial, lorem_ipsum, 5) == 5); + passert(serial_flush(serial) == 0); + passert(serial_read(serial, buf, sizeof(buf), -1) == 5); + passert(memcmp(lorem_ipsum, buf, 5) == 0); + + /* Test blocking read with vmin=5, vtime=2 termios timeout */ + passert(serial_set_vtime(serial, 2) == 0); + /* Write 3, read back 3 (< vmin, but > vtime interbyte timeout) */ + passert(serial_write(serial, lorem_ipsum, 3) == 3); + passert(serial_flush(serial) == 0); + start = time(NULL); + passert(serial_read(serial, buf, sizeof(buf), -1) == 3); + stop = time(NULL); + passert(memcmp(lorem_ipsum, buf, 3) == 0); + passert((stop - start) > 1); + + passert(serial_close(serial) == 0); + + /* Free serial */ + serial_free(serial); +} + +bool getc_yes(void) { + char buf[4]; + fgets(buf, sizeof(buf), stdin); + return (buf[0] == 'y' || buf[0] == 'Y'); +} + +void test_interactive(void) { + char str[256]; + serial_t *serial; + uint8_t buf[] = "Hello World"; + + ptest(); + + /* Allocate serial */ + serial = serial_new(); + passert(serial != NULL); + passert(serial_open(serial, device, 4800) == 0); + + printf("Starting interactive test. Get out your logic analyzer, buddy!\n"); + printf("Press enter to continue...\n"); + getc(stdin); + + /* Check tostring */ + passert(serial_tostring(serial, str, sizeof(str)) > 0); + printf("Serial description: %s\n", str); + printf("Serial description looks OK? y/n\n"); + passert(getc_yes()); + + printf("Press enter to start transfer..."); + getc(stdin); + passert(serial_write(serial, buf, sizeof(buf)) == sizeof(buf)); + printf("Serial transfer baudrate 4800, 8n1 occurred? y/n\n"); + passert(getc_yes()); + + passert(serial_set_baudrate(serial, 9600) == 0); + + printf("Press enter to start transfer..."); + getc(stdin); + passert(serial_write(serial, buf, sizeof(buf)) == sizeof(buf)); + printf("Serial transfer baudrate 9600, 8n1 occurred? y/n\n"); + passert(getc_yes()); + + passert(serial_set_baudrate(serial, 115200) == 0); + + printf("Press enter to start transfer..."); + getc(stdin); + passert(serial_write(serial, buf, sizeof(buf)) == sizeof(buf)); + printf("Serial transfer baudrate 115200, 8n1 occurred? y/n\n"); + passert(getc_yes()); + + passert(serial_close(serial) == 0); + + /* Free serial */ + serial_free(serial); +} + +int main(int argc, char *argv[]) { + if (argc < 2) { + fprintf(stderr, "Usage: %s \n\n", argv[0]); + fprintf(stderr, "[1/4] Arguments test: No requirements.\n"); + fprintf(stderr, "[2/4] Open/close test: Serial port device should be real.\n"); + fprintf(stderr, "[3/4] Loopback test: Serial TX and RX should be connected with a wire.\n"); + fprintf(stderr, "[4/4] Interactive test: Serial TX should be observed with an oscilloscope or logic analyzer.\n\n"); + fprintf(stderr, "Hint: for Raspberry Pi 3, enable UART0 with:\n"); + fprintf(stderr, " $ echo \"dtoverlay=pi3-disable-bt\" | sudo tee -a /boot/config.txt\n"); + fprintf(stderr, " $ sudo systemctl disable hciuart\n"); + fprintf(stderr, " $ sudo reboot\n"); + fprintf(stderr, " (Note that this will disable Bluetooth)\n"); + fprintf(stderr, "Use pins UART0 TXD (header pin 8) and UART0 RXD (header pin 10),\n"); + fprintf(stderr, "connect a loopback between TXD and RXD, and run this test with:\n"); + fprintf(stderr, " %s /dev/ttyAMA0\n\n", argv[0]); + exit(1); + } + + device = argv[1]; + + test_arguments(); + printf(" " STR_OK " Arguments test passed.\n\n"); + test_open_config_close(); + printf(" " STR_OK " Open/close test passed.\n\n"); + test_loopback(); + printf(" " STR_OK " Loopback test passed.\n\n"); + test_interactive(); + printf(" " STR_OK " Interactive test passed.\n\n"); + + printf("All tests passed!\n"); + return 0; +} + diff --git a/third_party/periphery/tests/test_spi.c b/third_party/periphery/tests/test_spi.c new file mode 100644 index 000000000..ae5ae7d52 --- /dev/null +++ b/third_party/periphery/tests/test_spi.c @@ -0,0 +1,236 @@ +/* + * c-periphery + * https://github.com/vsergeev/c-periphery + * License: MIT + */ + +#include "test.h" + +#include +#include + +#include "../src/spi.h" + +const char *device; + +void test_arguments(void) { + spi_t *spi; + + ptest(); + + /* Allocate SPI */ + spi = spi_new(); + passert(spi != NULL); + + /* Invalid mode */ + passert(spi_open(spi, device, 4, 1e6) == SPI_ERROR_ARG); + /* Invalid bit order */ + passert(spi_open_advanced(spi, device, 0, 1e6, LSB_FIRST+1, 8, 0) == SPI_ERROR_ARG); + + /* Free SPI */ + spi_free(spi); +} + +void test_open_config_close(void) { + spi_t *spi; + unsigned int mode; + spi_bit_order_t bit_order; + uint8_t bits_per_word; + uint32_t max_speed; + + ptest(); + + /* Allocate SPI */ + spi = spi_new(); + passert(spi != NULL); + + passert(spi_open(spi, device, 0, 100000) == 0); + + /* Confirm bit_order = MSB first, bits_per_word = 8 */ + passert(spi_get_bit_order(spi, &bit_order) == 0); + passert(bit_order == MSB_FIRST); + passert(spi_get_bits_per_word(spi, &bits_per_word) == 0); + passert(bits_per_word == 8); + + /* Not going to try different bit order or bits per word, because not all + * SPI controllers support them */ + + /* Try modes 1,2,3,0 */ + passert(spi_set_mode(spi, 1) == 0); + passert(spi_get_mode(spi, &mode) == 0); + passert(mode == 1); + passert(spi_set_mode(spi, 2) == 0); + passert(spi_get_mode(spi, &mode) == 0); + passert(mode == 2); + passert(spi_set_mode(spi, 3) == 0); + passert(spi_get_mode(spi, &mode) == 0); + passert(mode == 3); + passert(spi_set_mode(spi, 0) == 0); + passert(spi_get_mode(spi, &mode) == 0); + passert(mode == 0); + + /* Try 100KHz, 500KHz, 1MHz */ + passert(spi_set_max_speed(spi, 100000) == 0); + passert(spi_get_max_speed(spi, &max_speed) == 0); + passert(max_speed == 100000); + passert(spi_set_max_speed(spi, 500000) == 0); + passert(spi_get_max_speed(spi, &max_speed) == 0); + passert(max_speed == 500000); + passert(spi_set_max_speed(spi, 1000000) == 0); + passert(spi_get_max_speed(spi, &max_speed) == 0); + passert(max_speed == 1000000); + + passert(spi_close(spi) == 0); + + /* Free SPI */ + spi_free(spi); +} + +void test_loopback(void) { + spi_t *spi; + uint8_t buf[32]; + unsigned int i; + + ptest(); + + /* Allocate SPI */ + spi = spi_new(); + passert(spi != NULL); + + passert(spi_open(spi, device, 0, 100000) == 0); + + for (i = 0; i < sizeof(buf); i++) + buf[i] = i; + + passert(spi_transfer(spi, buf, buf, sizeof(buf)) == 0); + + for (i = 0; i < sizeof(buf); i++) + passert(buf[i] == i); + + passert(spi_close(spi) == 0); + + /* Free SPI */ + spi_free(spi); +} + +bool getc_yes(void) { + char buf[4]; + fgets(buf, sizeof(buf), stdin); + return (buf[0] == 'y' || buf[0] == 'Y'); +} + +void test_interactive(void) { + char str[256]; + spi_t *spi; + uint8_t buf[] = { 0x55, 0xaa, 0x0f, 0xf0 }; + + ptest(); + + /* Allocate SPI */ + spi = spi_new(); + passert(spi != NULL); + + passert(spi_open(spi, device, 0, 100000) == 0); + + printf("Starting interactive test. Get out your logic analyzer, buddy!\n"); + printf("Press enter to continue...\n"); + getc(stdin); + + /* Check tostring */ + passert(spi_tostring(spi, str, sizeof(str)) > 0); + printf("SPI description: %s\n", str); + printf("SPI description looks OK? y/n\n"); + passert(getc_yes()); + + /* Mode 0 transfer */ + printf("Press enter to start transfer..."); + getc(stdin); + passert(spi_transfer(spi, buf, NULL, sizeof(buf)) == 0); + printf("SPI data 0x55, 0xaa, 0x0f, 0xf0\n"); + printf("SPI transfer speed <= 100KHz, mode 0 occurred? y/n\n"); + passert(getc_yes()); + + /* Mode 1 transfer */ + passert(spi_set_mode(spi, 1) == 0); + printf("Press enter to start transfer..."); + getc(stdin); + passert(spi_transfer(spi, buf, NULL, sizeof(buf)) == 0); + printf("SPI data 0x55, 0xaa, 0x0f, 0xf0\n"); + printf("SPI transfer speed <= 100KHz, mode 1 occurred? y/n\n"); + passert(getc_yes()); + + /* Mode 2 transfer */ + passert(spi_set_mode(spi, 2) == 0); + printf("Press enter to start transfer..."); + getc(stdin); + passert(spi_transfer(spi, buf, NULL, sizeof(buf)) == 0); + printf("SPI data 0x55, 0xaa, 0x0f, 0xf0\n"); + printf("SPI transfer speed <= 100KHz, mode 2 occurred? y/n\n"); + passert(getc_yes()); + + /* Mode 3 transfer */ + passert(spi_set_mode(spi, 3) == 0); + printf("Press enter to start transfer..."); + getc(stdin); + passert(spi_transfer(spi, buf, NULL, sizeof(buf)) == 0); + printf("SPI data 0x55, 0xaa, 0x0f, 0xf0\n"); + printf("SPI transfer speed <= 100KHz, mode 3 occurred? y/n\n"); + passert(getc_yes()); + + passert(spi_set_mode(spi, 0) == 0); + + /* 500KHz transfer */ + passert(spi_set_max_speed(spi, 500000) == 0); + printf("Press enter to start transfer..."); + getc(stdin); + passert(spi_transfer(spi, buf, NULL, sizeof(buf)) == 0); + printf("SPI data 0x55, 0xaa, 0x0f, 0xf0\n"); + printf("SPI transfer speed <= 500KHz, mode 0 occurred? y/n\n"); + passert(getc_yes()); + + /* 1MHz transfer */ + passert(spi_set_max_speed(spi, 1000000) == 0); + printf("Press enter to start transfer..."); + getc(stdin); + passert(spi_transfer(spi, buf, NULL, sizeof(buf)) == 0); + printf("SPI data 0x55, 0xaa, 0x0f, 0xf0\n"); + printf("SPI transfer speed <= 1MHz, mode 0 occurred? y/n\n"); + passert(getc_yes()); + + passert(spi_close(spi) == 0); + + /* Free SPI */ + spi_free(spi); +} + +int main(int argc, char *argv[]) { + if (argc < 2) { + fprintf(stderr, "Usage: %s \n\n", argv[0]); + fprintf(stderr, "[1/4] Arguments test: No requirements.\n"); + fprintf(stderr, "[2/4] Open/close test: SPI device should be real.\n"); + fprintf(stderr, "[3/4] Loopback test: SPI MISO and MOSI should be connected with a wire.\n"); + fprintf(stderr, "[4/4] Interactive test: SPI MOSI, CLK, CS should be observed with an oscilloscope or logic analyzer.\n\n"); + fprintf(stderr, "Hint: for Raspberry Pi 3, enable SPI0 with:\n"); + fprintf(stderr, " $ echo \"dtparam=spi=on\" | sudo tee -a /boot/config.txt\n"); + fprintf(stderr, " $ sudo reboot\n"); + fprintf(stderr, "Use pins SPI0 MOSI (header pin 19), SPI0 MISO (header pin 21), SPI0 SCLK (header pin 23),\n"); + fprintf(stderr, "connect a loopback between MOSI and MISO, and run this test with:\n"); + fprintf(stderr, " %s /dev/spidev0.0\n\n", argv[0]); + exit(1); + } + + device = argv[1]; + + test_arguments(); + printf(" " STR_OK " Arguments test passed.\n\n"); + test_open_config_close(); + printf(" " STR_OK " Open/close test passed.\n\n"); + test_loopback(); + printf(" " STR_OK " Loopback test passed.\n\n"); + test_interactive(); + printf(" " STR_OK " Interactive test passed.\n\n"); + + printf("All tests passed!\n"); + return 0; +} + diff --git a/third_party/periphery/wrappers.c b/third_party/periphery/wrappers.c new file mode 100644 index 000000000..9f87fc71b --- /dev/null +++ b/third_party/periphery/wrappers.c @@ -0,0 +1,20 @@ +#include +#include +#include +#include + +// Wrapper for strerror_r +char* __xpg_strerror_r(int errnum, char* buf, size_t buflen) { + strerror_r(errnum, buf, buflen); + return buf; +} + +// Wrapper for __errno_location +int* __errno_location(void) { + return &errno; +} + +// Wrapper for __xstat +int __xstat(int ver, const char * path, struct stat * stat_buf) { + return stat(path, stat_buf); +} diff --git a/tools/build.py b/tools/build.py index b17debf88..5e38bcb4b 100644 --- a/tools/build.py +++ b/tools/build.py @@ -193,6 +193,7 @@ def prepare_args(): parser.add_argument('--generator', type=str, help='CMake generator', required=False) parser.add_argument('--use-libftdi', action='store_true') + parser.add_argument('--build-periphery', action='store_true') parser.add_argument('--build-dir', type=str, help='build folder', required=False, default=os.path.join(cur_folder, '..', 'build')) @@ -242,6 +243,8 @@ def config(args): cmd_config.append('-DBRAINFLOW_VERSION=%s' % args.brainflow_version) if hasattr(args, 'use_libftdi') and args.use_libftdi: cmd_config.append('-DUSE_LIBFTDI=ON') + if hasattr(args, 'build_periphery') and args.build_periphery: + cmd_config.append('-DBUILD_PERIPHERY=ON') if args.warnings_as_errors: cmd_config.append('-DWARNINGS_AS_ERRORS=ON') if args.use_openmp: