├── .gitignore ├── .gitmodules ├── CMakeLists.txt ├── LICENSE ├── README.md ├── build.sh ├── cdc-uart.c ├── cdc-uart.h ├── dln2-adc.c ├── dln2-devices.h ├── dln2-gpio.c ├── dln2-i2c.c ├── dln2-pin.c ├── dln2-spi.c ├── dln2.c ├── dln2.h ├── driver.c ├── i2c-at24-flash.c ├── i2c-at24.c ├── i2c-at24.h ├── main.c ├── pinout.svg ├── tests ├── gpio_pulse │ ├── LICENCE.txt │ ├── Makefile │ ├── README.md │ ├── c_gpio.c │ ├── c_gpio.h │ └── main.c ├── test_adc.py ├── test_gpio.py ├── test_i2c.py ├── test_spi.py └── test_uart.py ├── tusb_config.h └── usb_descriptors.c /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | __pycache__ 3 | 4 | *.sublime-project 5 | *.sublime-workspace 6 | 7 | gpio_pulse 8 | !gpio_pulse/ 9 | *.o 10 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "pico-sdk"] 2 | path = pico-sdk 3 | url = https://github.com/raspberrypi/pico-sdk 4 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.13) 2 | 3 | #set(TINYUSB_DEBUG_LEVEL 2) 4 | 5 | include(pico-sdk/pico_sdk_init.cmake) 6 | 7 | project(dln2_project) 8 | 9 | pico_sdk_init() 10 | 11 | add_executable(dln2 12 | main.c 13 | usb_descriptors.c 14 | driver.c 15 | dln2.c 16 | dln2-pin.c 17 | dln2-gpio.c 18 | dln2-i2c.c 19 | dln2-spi.c 20 | dln2-adc.c 21 | cdc-uart.c 22 | i2c-at24.c 23 | i2c-at24-flash.c 24 | ) 25 | 26 | target_include_directories(dln2 PRIVATE ${CMAKE_CURRENT_LIST_DIR}) 27 | 28 | # enable=1 to get debug output on uart0 29 | pico_enable_stdio_uart(dln2 0) 30 | 31 | target_link_libraries(dln2 PRIVATE 32 | pico_stdlib 33 | pico_unique_id 34 | tinyusb_board 35 | tinyusb_device 36 | hardware_adc 37 | hardware_gpio 38 | hardware_i2c 39 | hardware_spi 40 | ) 41 | 42 | pico_add_extra_outputs(dln2) 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Creative Commons Legal Code 2 | 3 | CC0 1.0 Universal 4 | 5 | CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE 6 | LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN 7 | ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS 8 | INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES 9 | REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS 10 | PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM 11 | THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED 12 | HEREUNDER. 13 | 14 | Statement of Purpose 15 | 16 | The laws of most jurisdictions throughout the world automatically confer 17 | exclusive Copyright and Related Rights (defined below) upon the creator 18 | and subsequent owner(s) (each and all, an "owner") of an original work of 19 | authorship and/or a database (each, a "Work"). 20 | 21 | Certain owners wish to permanently relinquish those rights to a Work for 22 | the purpose of contributing to a commons of creative, cultural and 23 | scientific works ("Commons") that the public can reliably and without fear 24 | of later claims of infringement build upon, modify, incorporate in other 25 | works, reuse and redistribute as freely as possible in any form whatsoever 26 | and for any purposes, including without limitation commercial purposes. 27 | These owners may contribute to the Commons to promote the ideal of a free 28 | culture and the further production of creative, cultural and scientific 29 | works, or to gain reputation or greater distribution for their Work in 30 | part through the use and efforts of others. 31 | 32 | For these and/or other purposes and motivations, and without any 33 | expectation of additional consideration or compensation, the person 34 | associating CC0 with a Work (the "Affirmer"), to the extent that he or she 35 | is an owner of Copyright and Related Rights in the Work, voluntarily 36 | elects to apply CC0 to the Work and publicly distribute the Work under its 37 | terms, with knowledge of his or her Copyright and Related Rights in the 38 | Work and the meaning and intended legal effect of CC0 on those rights. 39 | 40 | 1. Copyright and Related Rights. A Work made available under CC0 may be 41 | protected by copyright and related or neighboring rights ("Copyright and 42 | Related Rights"). Copyright and Related Rights include, but are not 43 | limited to, the following: 44 | 45 | i. the right to reproduce, adapt, distribute, perform, display, 46 | communicate, and translate a Work; 47 | ii. moral rights retained by the original author(s) and/or performer(s); 48 | iii. publicity and privacy rights pertaining to a person's image or 49 | likeness depicted in a Work; 50 | iv. rights protecting against unfair competition in regards to a Work, 51 | subject to the limitations in paragraph 4(a), below; 52 | v. rights protecting the extraction, dissemination, use and reuse of data 53 | in a Work; 54 | vi. database rights (such as those arising under Directive 96/9/EC of the 55 | European Parliament and of the Council of 11 March 1996 on the legal 56 | protection of databases, and under any national implementation 57 | thereof, including any amended or successor version of such 58 | directive); and 59 | vii. other similar, equivalent or corresponding rights throughout the 60 | world based on applicable law or treaty, and any national 61 | implementations thereof. 62 | 63 | 2. Waiver. To the greatest extent permitted by, but not in contravention 64 | of, applicable law, Affirmer hereby overtly, fully, permanently, 65 | irrevocably and unconditionally waives, abandons, and surrenders all of 66 | Affirmer's Copyright and Related Rights and associated claims and causes 67 | of action, whether now known or unknown (including existing as well as 68 | future claims and causes of action), in the Work (i) in all territories 69 | worldwide, (ii) for the maximum duration provided by applicable law or 70 | treaty (including future time extensions), (iii) in any current or future 71 | medium and for any number of copies, and (iv) for any purpose whatsoever, 72 | including without limitation commercial, advertising or promotional 73 | purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each 74 | member of the public at large and to the detriment of Affirmer's heirs and 75 | successors, fully intending that such Waiver shall not be subject to 76 | revocation, rescission, cancellation, termination, or any other legal or 77 | equitable action to disrupt the quiet enjoyment of the Work by the public 78 | as contemplated by Affirmer's express Statement of Purpose. 79 | 80 | 3. Public License Fallback. Should any part of the Waiver for any reason 81 | be judged legally invalid or ineffective under applicable law, then the 82 | Waiver shall be preserved to the maximum extent permitted taking into 83 | account Affirmer's express Statement of Purpose. In addition, to the 84 | extent the Waiver is so judged Affirmer hereby grants to each affected 85 | person a royalty-free, non transferable, non sublicensable, non exclusive, 86 | irrevocable and unconditional license to exercise Affirmer's Copyright and 87 | Related Rights in the Work (i) in all territories worldwide, (ii) for the 88 | maximum duration provided by applicable law or treaty (including future 89 | time extensions), (iii) in any current or future medium and for any number 90 | of copies, and (iv) for any purpose whatsoever, including without 91 | limitation commercial, advertising or promotional purposes (the 92 | "License"). The License shall be deemed effective as of the date CC0 was 93 | applied by Affirmer to the Work. Should any part of the License for any 94 | reason be judged legally invalid or ineffective under applicable law, such 95 | partial invalidity or ineffectiveness shall not invalidate the remainder 96 | of the License, and in such case Affirmer hereby affirms that he or she 97 | will not (i) exercise any of his or her remaining Copyright and Related 98 | Rights in the Work or (ii) assert any associated claims and causes of 99 | action with respect to the Work, in either case contrary to Affirmer's 100 | express Statement of Purpose. 101 | 102 | 4. Limitations and Disclaimers. 103 | 104 | a. No trademark or patent rights held by Affirmer are waived, abandoned, 105 | surrendered, licensed or otherwise affected by this document. 106 | b. Affirmer offers the Work as-is and makes no representations or 107 | warranties of any kind concerning the Work, express, implied, 108 | statutory or otherwise, including without limitation warranties of 109 | title, merchantability, fitness for a particular purpose, non 110 | infringement, or the absence of latent or other defects, accuracy, or 111 | the present or absence of errors, whether or not discoverable, all to 112 | the greatest extent permissible under applicable law. 113 | c. Affirmer disclaims responsibility for clearing rights of other persons 114 | that may apply to the Work or any use thereof, including without 115 | limitation any person's Copyright and Related Rights in the Work. 116 | Further, Affirmer disclaims responsibility for obtaining any necessary 117 | consents, permissions or other rights required for any use of the 118 | Work. 119 | d. Affirmer understands and acknowledges that Creative Commons is not a 120 | party to this document and has no duty or obligation with respect to 121 | this CC0 or use of the Work. 122 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Raspberry Pi Pico USB I/O Board 2 | 3 | This project turns the Raspberry Pi Pico into a USB I/O Board. 4 | 5 | It implements the USB protocol used by the dln2 Linux drivers and in addition it supports 2 CDC UARTS. 6 | 7 | 8 | 9 | See [wiki](https://github.com/notro/pico-usb-io-board/wiki) for more information. 10 | 11 | 12 | # Build 13 | ``` 14 | $ cd pico-usb-io-board 15 | $ ./build.sh 16 | 17 | ``` 18 | 19 | The ```BUILD_DIR``` environment variable can be used to put the build files elsewhere. 20 | 21 | 22 | # License 23 | 24 | Unless otherwise stated, all code and data is licensed under a [CC0 license](https://creativecommons.org/publicdomain/zero/1.0/). 25 | 26 | The [Openmoko Product ID](https://github.com/openmoko/openmoko-usb-oui) [1d50:6170](https://github.com/openmoko/openmoko-usb-oui/pull/35) can only be used under a [FOSS license](https://github.com/openmoko/openmoko-usb-oui#conditions). 27 | 28 | ["Raspberry Pi Pico Pinout Diagram"](https://www.raspberrypi.com/documentation/microcontrollers/images/Pico-R3-SDK11-Pinout.svg) by [Raspberry Pi (Trading) Ltd](https://www.raspberrypi.com/), used under [CC BY-SA](https://creativecommons.org/licenses/by-sa/4.0/) / Pinout simplified from original 29 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | BASE_DIR="$(dirname ${BASH_SOURCE[0]})" 4 | PICO_SDK_DIR=$BASE_DIR/pico-sdk 5 | BUILD_DIR="${BUILD_DIR:-${BASE_DIR}/build}" 6 | 7 | if [ ! -e "$PICO_SDK_DIR/.git" ]; then 8 | # don't do --recursive as it pulls in a lot of things we don't need 9 | git submodule update --init 10 | (cd "$PICO_SDK_DIR" && git submodule update --init) 11 | fi 12 | 13 | cmake -B $BUILD_DIR -S $BASE_DIR 14 | make -C $BUILD_DIR -j$(nproc) 15 | -------------------------------------------------------------------------------- /cdc-uart.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: CC0-1.0 2 | /* 3 | * Written in 2023 by Noralf Trønnes 4 | * 5 | * To the extent possible under law, the author(s) have dedicated all copyright and related and 6 | * neighboring rights to this software to the public domain worldwide. This software is 7 | * distributed without any warranty. 8 | * 9 | * You should have received a copy of the CC0 Public Domain Dedication along with this software. 10 | * If not, see . 11 | */ 12 | 13 | #include "bsp/board.h" 14 | #include 15 | #include 16 | #include 17 | 18 | #include "dln2.h" 19 | #include "cdc-uart.h" 20 | 21 | void tud_cdc_line_coding_cb(uint8_t itf, cdc_line_coding_t const* p_line_coding) 22 | { 23 | uart_inst_t *uart = uart_get_instance(itf); 24 | uint8_t data_bits = p_line_coding->data_bits; 25 | uint8_t stop_bits = p_line_coding->stop_bits; 26 | uint8_t parity; 27 | 28 | // tinyusb: can be 5, 6, 7, 8 or 16 29 | // RP2040: Number of bits of data. 5..8 30 | if (data_bits < 5 || data_bits > 8) 31 | data_bits = 8; 32 | 33 | // tinyusb: 0: 1 stop bit - 1: 1.5 stop bits - 2: 2 stop bits 34 | // RP2040: Number of stop bits 1..2 35 | if (stop_bits != 2) 36 | stop_bits = 1; 37 | 38 | // tinyusb: 0: None - 1: Odd - 2: Even - 3: Mark - 4: Space 39 | // RP2040: UART_PARITY_NONE=0, UART_PARITY_EVEN=1, UART_PARITY_ODD=2 40 | switch (p_line_coding->parity) { 41 | case 1: 42 | parity = UART_PARITY_ODD; 43 | break; 44 | case 2: 45 | parity = UART_PARITY_EVEN; 46 | break; 47 | default: 48 | parity = UART_PARITY_NONE; 49 | break; 50 | } 51 | 52 | // p_line_coding is const so looks like it's not expected to set the actual baud rate. 53 | // The Linux driver does not support USB_CDC_REQ_GET_LINE_CODING so it doesn't matter. 54 | uint __unused actual = uart_set_baudrate(uart, p_line_coding->bit_rate); 55 | uart_set_format(uart, data_bits, stop_bits, parity); 56 | } 57 | 58 | // When a port is opened using pyserial this function is called with dtr=true and rts=true. 59 | // It is called before tud_cdc_line_coding_cb() 60 | // When the port is closed it is called with dtr=false and rts=false. 61 | void tud_cdc_line_state_cb(uint8_t itf, bool dtr, bool rts) 62 | { 63 | const uint tx_pin = !itf ? UART0_TX_PIN : UART1_TX_PIN; 64 | const uint rx_pin = !itf ? UART0_RX_PIN : UART1_RX_PIN; 65 | uart_inst_t *uart = uart_get_instance(itf); 66 | cdc_line_coding_t line_coding; 67 | 68 | if (!dtr || dln2_pin_is_requested(tx_pin, DLN2_MODULE_UART)) 69 | return; 70 | 71 | // There's no way to tell the host that the pins are in use 72 | if (dln2_pin_request(tx_pin, DLN2_MODULE_UART)) 73 | return; 74 | 75 | if (dln2_pin_request(rx_pin, DLN2_MODULE_UART)) { 76 | dln2_pin_free(tx_pin, DLN2_MODULE_UART); 77 | return; 78 | } 79 | 80 | gpio_set_function(tx_pin, GPIO_FUNC_UART); 81 | gpio_set_function(rx_pin, GPIO_FUNC_UART); 82 | 83 | tud_cdc_n_get_line_coding(itf, &line_coding); 84 | uart_init(uart, line_coding.bit_rate); 85 | uart_set_hw_flow(uart, false, false); 86 | // TODO: This might not be necessary, maybe the host driver always sets the line coding when 87 | // opening the port. 88 | tud_cdc_line_coding_cb(itf, &line_coding); 89 | } 90 | 91 | void tud_cdc_send_break_cb(uint8_t itf, uint16_t duration_ms) 92 | { 93 | uart_inst_t *uart = uart_get_instance(itf); 94 | 95 | // Linux handles the duration by first sending 0xffff, wait and then sending 0. 96 | // drivers/tty/tty_io.c:send_break() 97 | // drivers/usb/class/cdc-acm.c:acm_tty_break_ctl() 98 | if (duration_ms == 0xffff) 99 | uart_set_break(uart, true); 100 | else if (duration_ms == 0) 101 | uart_set_break(uart, false); 102 | } 103 | 104 | static void uart_write_bytes(uint8_t itf) 105 | { 106 | uart_inst_t *uart = uart_get_instance(itf); 107 | uint8_t chr; 108 | 109 | while (uart_is_writable(uart) && tud_cdc_n_read(itf, &chr, 1)) 110 | uart_putc_raw(uart, chr); 111 | } 112 | 113 | static void cdc_write_bytes(uint8_t itf) 114 | { 115 | uart_inst_t *uart = uart_get_instance(itf); 116 | unsigned int uart_count = 0, cdc_count; 117 | uint8_t buf[32]; 118 | 119 | while (uart_is_readable(uart) && uart_count < sizeof(buf)) 120 | buf[uart_count++] = uart_getc(uart); 121 | 122 | if (!uart_count) 123 | return; 124 | 125 | // Light up the onboard LED as an RX FIFO overflow warning 126 | if (uart_count == 32) 127 | gpio_put(PICO_DEFAULT_LED_PIN, 1); 128 | 129 | cdc_count = tud_cdc_n_write(itf, buf, uart_count); 130 | if (cdc_count) 131 | tud_cdc_n_write_flush(itf); 132 | } 133 | 134 | void cdc_uart_task(void) 135 | { 136 | for (int itf = 0; itf < CFG_TUD_CDC; itf++) { 137 | if (!tud_cdc_n_connected(itf)) 138 | continue; 139 | cdc_write_bytes(itf); 140 | uart_write_bytes(itf); 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /cdc-uart.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: CC0-1.0 2 | /* 3 | * Written in 2023 by Noralf Trønnes 4 | * 5 | * To the extent possible under law, the author(s) have dedicated all copyright and related and 6 | * neighboring rights to this software to the public domain worldwide. This software is 7 | * distributed without any warranty. 8 | * 9 | * You should have received a copy of the CC0 Public Domain Dedication along with this software. 10 | * If not, see . 11 | */ 12 | 13 | #ifndef _CDC_UART_H_ 14 | #define _CDC_UART_H_ 15 | 16 | #ifndef UART0_TX_PIN 17 | #define UART0_TX_PIN 0 18 | #endif 19 | #ifndef UART0_RX_PIN 20 | #define UART0_RX_PIN 1 21 | #endif 22 | #ifndef UART1_TX_PIN 23 | #define UART1_TX_PIN 8 24 | #endif 25 | #ifndef UART1_RX_PIN 26 | #define UART1_RX_PIN 9 27 | #endif 28 | 29 | void cdc_uart_task(void); 30 | 31 | #endif 32 | -------------------------------------------------------------------------------- /dln2-adc.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: CC0-1.0 2 | /* 3 | * Written in 2021 by Noralf Trønnes 4 | * 5 | * To the extent possible under law, the author(s) have dedicated all copyright and related and 6 | * neighboring rights to this software to the public domain worldwide. This software is 7 | * distributed without any warranty. 8 | * 9 | * You should have received a copy of the CC0 Public Domain Dedication along with this software. 10 | * If not, see . 11 | */ 12 | 13 | #include 14 | #include "pico/stdlib.h" 15 | #include "hardware/adc.h" 16 | #include "dln2.h" 17 | 18 | #define LOG1 //printf 19 | 20 | #define DLN2_ADC_CMD(cmd) ((DLN2_MODULE_ADC << 8) | (cmd)) 21 | 22 | #define DLN2_ADC_GET_CHANNEL_COUNT DLN2_ADC_CMD(0x01) 23 | #define DLN2_ADC_ENABLE DLN2_ADC_CMD(0x02) 24 | #define DLN2_ADC_DISABLE DLN2_ADC_CMD(0x03) 25 | #define DLN2_ADC_CHANNEL_ENABLE DLN2_ADC_CMD(0x05) 26 | #define DLN2_ADC_CHANNEL_DISABLE DLN2_ADC_CMD(0x06) 27 | #define DLN2_ADC_SET_RESOLUTION DLN2_ADC_CMD(0x08) 28 | #define DLN2_ADC_CHANNEL_GET_VAL DLN2_ADC_CMD(0x0A) 29 | #define DLN2_ADC_CHANNEL_GET_ALL_VAL DLN2_ADC_CMD(0x0B) 30 | #define DLN2_ADC_CHANNEL_SET_CFG DLN2_ADC_CMD(0x0C) 31 | #define DLN2_ADC_CONDITION_MET_EV DLN2_ADC_CMD(0x10) 32 | 33 | #define DLN2_ADC_EVENT_NONE 0 34 | #define DLN2_ADC_EVENT_ALWAYS 5 35 | 36 | #define DLN2_ADC_NUM_CHANNELS 3 37 | #define DLN2_ADC_MAX_CHANNELS 8 38 | //#define DLN2_ADC_DATA_BITS 10 39 | 40 | struct dln2_adc_port_chan { 41 | uint8_t port; 42 | uint8_t chan; 43 | } TU_ATTR_PACKED; 44 | 45 | struct dln2_adc_get_all_vals { 46 | uint16_t channel_mask; 47 | uint16_t values[DLN2_ADC_MAX_CHANNELS]; 48 | } TU_ATTR_PACKED; 49 | 50 | static repeating_timer_t dln2_adc_event_timer; 51 | 52 | static uint16_t dln2_adc_read(uint input) 53 | { 54 | adc_select_input(input); 55 | // The Linux driver has a fixed 10-bit resolution 56 | // It is possible to return the full value, but userspace might choke on the out of bounds value 57 | return adc_read() >> 2; 58 | } 59 | 60 | static bool dln2_adc_channel_enable(struct dln2_slot *slot, bool enable) 61 | { 62 | struct dln2_adc_port_chan *port_chan = dln2_slot_header_data(slot); 63 | 64 | DLN2_VERIFY_COMMAND_SIZE(slot, sizeof(*port_chan)); 65 | LOG1("%s: port=%u chan=%u\n", enable ? "DLN2_ADC_CHANNEL_ENABLE" : "DLN2_ADC_CHANNEL_DISABLE", port_chan->port, port_chan->chan); 66 | 67 | if (port_chan->chan >= DLN2_ADC_NUM_CHANNELS) 68 | dln2_response_error(slot, DLN2_RES_INVALID_CHANNEL_NUMBER); 69 | 70 | uint16_t pin = port_chan->chan + 26; 71 | 72 | if (enable) { 73 | int res = dln2_pin_request(pin, DLN2_MODULE_ADC); 74 | if (res) 75 | return dln2_response_error(slot, res); 76 | 77 | adc_gpio_init(pin); 78 | } else if (dln2_pin_is_requested(pin, DLN2_MODULE_ADC)) { 79 | int res = dln2_pin_free(pin, DLN2_MODULE_ADC); 80 | if (res) 81 | return dln2_response_error(slot, res); 82 | 83 | gpio_set_function(pin, GPIO_FUNC_NULL); 84 | } 85 | 86 | return dln2_response(slot, 0); 87 | } 88 | 89 | static bool dln2_adc_enable(struct dln2_slot *slot, bool enable) 90 | { 91 | uint8_t *port = dln2_slot_header_data(slot); 92 | uint16_t conflict = 0; 93 | 94 | DLN2_VERIFY_COMMAND_SIZE(slot, sizeof(*port)); 95 | LOG1("%s: port=%u\n", enable ? "DLN2_ADC_ENABLE" : "DLN2_ADC_DISABLE", *port); 96 | 97 | if (!enable) { 98 | cancel_repeating_timer(&dln2_adc_event_timer); 99 | for (uint pin = 26; pin <= 28; pin++) 100 | dln2_pin_free(pin, DLN2_MODULE_ADC); 101 | } 102 | 103 | put_unaligned_le16(conflict, dln2_slot_response_data(slot)); 104 | return dln2_response(slot, sizeof(conflict)); 105 | } 106 | 107 | static bool dln2_adc_channel_get_val(struct dln2_slot *slot) 108 | { 109 | struct dln2_adc_port_chan *port_chan = dln2_slot_header_data(slot); 110 | 111 | DLN2_VERIFY_COMMAND_SIZE(slot, sizeof(*port_chan)); 112 | LOG1("DLN2_ADC_CHANNEL_GET_VAL: port=%u chan=%u\n", port_chan->port, port_chan->chan); 113 | 114 | if (port_chan->chan >= DLN2_ADC_NUM_CHANNELS) 115 | dln2_response_error(slot, DLN2_RES_INVALID_CHANNEL_NUMBER); 116 | 117 | uint16_t value = dln2_adc_read(port_chan->chan); 118 | put_unaligned_le16(value, dln2_slot_response_data(slot)); 119 | return dln2_response(slot, sizeof(value)); 120 | } 121 | 122 | static bool dln2_adc_channel_get_all_val(struct dln2_slot *slot) 123 | { 124 | uint8_t *port = dln2_slot_header_data(slot); 125 | uint16_t *channel_mask = dln2_slot_response_data(slot); 126 | uint16_t *values = channel_mask + 1; 127 | size_t len = sizeof(uint16_t) * (DLN2_ADC_MAX_CHANNELS + 1); 128 | 129 | DLN2_VERIFY_COMMAND_SIZE(slot, sizeof(*port)); 130 | LOG1("DLN2_ADC_CHANNEL_GET_ALL_VAL: port=%u\n", *port); 131 | 132 | // zero the buffer to ease debugging 133 | memset(channel_mask, 0, len); 134 | 135 | // The Linux driver ignores channel_mask 136 | put_unaligned_le16(0x0000, channel_mask); 137 | 138 | // Sample time: 3x 2us ~= 6us 139 | for (uint i = 0; i < DLN2_ADC_NUM_CHANNELS; i++) { 140 | values[i] = dln2_adc_read(i); 141 | } 142 | 143 | return dln2_response(slot, len); 144 | } 145 | 146 | static void dln2_adc_event(void) 147 | { 148 | // the Linux driver ignores these values entirely... 149 | struct { 150 | uint16_t count; 151 | uint8_t port; 152 | uint8_t chan; 153 | uint16_t value; 154 | uint8_t type; 155 | } TU_ATTR_PACKED *event; 156 | 157 | LOG1("%s:\n", __func__); 158 | 159 | struct dln2_slot *slot = dln2_get_slot(); 160 | if (!slot) { 161 | LOG1("Run out of slots!\n"); 162 | return; 163 | } 164 | 165 | struct dln2_header *hdr = dln2_slot_header(slot); 166 | hdr->size = sizeof(*hdr) + sizeof(*event); 167 | hdr->id = DLN2_ADC_CONDITION_MET_EV; 168 | hdr->echo = 0; 169 | hdr->handle = DLN2_HANDLE_EVENT; 170 | 171 | event = dln2_slot_header_data(slot); 172 | event->count = 0; 173 | event->port = 0; 174 | event->chan = 0; 175 | event->value = 0; 176 | event->type = 0; 177 | 178 | dln2_print_slot(slot); 179 | dln2_queue_slot_in(slot); 180 | } 181 | 182 | static bool dln2_adc_event_timer_callback(repeating_timer_t *rt) { 183 | LOG1("%s\n", __func__); 184 | dln2_adc_event(); 185 | return true; // keep repeating 186 | } 187 | 188 | static bool dln2_adc_channel_set_cfg(struct dln2_slot *slot) 189 | { 190 | struct { 191 | struct dln2_adc_port_chan port_chan; 192 | uint8_t type; 193 | uint16_t period; 194 | uint16_t low; 195 | uint16_t high; 196 | } TU_ATTR_PACKED *cfg = dln2_slot_header_data(slot); 197 | 198 | DLN2_VERIFY_COMMAND_SIZE(slot, sizeof(*cfg)); 199 | 200 | LOG1("DLN2_ADC_CHANNEL_SET_CFG: port=%u chan=%u type=%u period=%ums low=%u high=%u\n", 201 | cfg->port_chan.port, cfg->port_chan.chan, cfg->type, cfg->period, cfg->low, cfg->high); 202 | 203 | if (cfg->type != DLN2_ADC_EVENT_NONE && cfg->type != DLN2_ADC_EVENT_ALWAYS) { 204 | LOG1("ADC event type not implemented\n"); 205 | return dln2_response_error(slot, DLN2_RES_NOT_IMPLEMENTED); 206 | } 207 | 208 | /* 209 | * FIXME: 210 | * DLN_RES_INVALID_EVENT_PERIOD (0xAC) 211 | * Defining the event configuration, you specified the invalid event period. 212 | * It may occur, for example, if you set the zero event period for the ALWAYS event type. 213 | * For details, read Digital Input Events. 214 | */ 215 | 216 | if (!dln2_response(slot, 0)) 217 | return false; 218 | 219 | if (cfg->type == DLN2_ADC_EVENT_NONE && !cfg->period) { 220 | cancel_repeating_timer(&dln2_adc_event_timer); 221 | // send a single event 222 | dln2_adc_event(); 223 | } else if (cfg->type == DLN2_ADC_EVENT_ALWAYS) { 224 | // negative timeout means exact delay (rather than delay between callbacks) 225 | if (!add_repeating_timer_us(-1000 * cfg->period, dln2_adc_event_timer_callback, NULL, &dln2_adc_event_timer)) { 226 | LOG1("ADC: Failed to add timer\n"); 227 | // what must happen for this to fail? 228 | return false; 229 | } 230 | } 231 | 232 | return true; 233 | } 234 | 235 | bool dln2_handle_adc(struct dln2_slot *slot) 236 | { 237 | struct dln2_header *hdr = dln2_slot_header(slot); 238 | 239 | switch (hdr->id) { 240 | case DLN2_ADC_GET_CHANNEL_COUNT: 241 | LOG1("DLN2_ADC_GET_CHANNEL_COUNT\n"); 242 | DLN2_VERIFY_COMMAND_SIZE(slot, 1); 243 | // TODO: check port 244 | adc_init(); 245 | return dln2_response_u8(slot, DLN2_ADC_NUM_CHANNELS); 246 | case DLN2_ADC_ENABLE: 247 | return dln2_adc_enable(slot, true); 248 | case DLN2_ADC_DISABLE: 249 | return dln2_adc_enable(slot, false); 250 | case DLN2_ADC_CHANNEL_ENABLE: 251 | return dln2_adc_channel_enable(slot, true); 252 | case DLN2_ADC_CHANNEL_DISABLE: 253 | return dln2_adc_channel_enable(slot, false); 254 | case DLN2_ADC_SET_RESOLUTION: 255 | LOG1("DLN2_ADC_SET_RESOLUTION\n"); 256 | DLN2_VERIFY_COMMAND_SIZE(slot, 2); 257 | // TODO: check port and resolution 258 | return dln2_response(slot, 0); 259 | case DLN2_ADC_CHANNEL_GET_VAL: 260 | return dln2_adc_channel_get_val(slot); 261 | case DLN2_ADC_CHANNEL_GET_ALL_VAL: 262 | return dln2_adc_channel_get_all_val(slot); 263 | case DLN2_ADC_CHANNEL_SET_CFG: 264 | return dln2_adc_channel_set_cfg(slot); 265 | default: 266 | LOG1("ADC command not supported: 0x%04x\n", hdr->id); 267 | return dln2_response_error(slot, DLN2_RES_COMMAND_NOT_SUPPORTED); 268 | } 269 | } 270 | -------------------------------------------------------------------------------- /dln2-devices.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: CC0-1.0 2 | /* 3 | * Written in 2021 by Noralf Trønnes 4 | * 5 | * To the extent possible under law, the author(s) have dedicated all copyright and related and 6 | * neighboring rights to this software to the public domain worldwide. This software is 7 | * distributed without any warranty. 8 | * 9 | * You should have received a copy of the CC0 Public Domain Dedication along with this software. 10 | * If not, see . 11 | */ 12 | 13 | #ifndef _DLN2_DEVICES_H_ 14 | #define _DLN2_DEVICES_H_ 15 | 16 | #include "dln2.h" 17 | 18 | struct dln2_i2c_device { 19 | const char *name; 20 | uint16_t address; 21 | 22 | // @buf is unaligned 23 | bool (*read)(const struct dln2_i2c_device *dev, uint16_t address, void *buf, size_t len); 24 | // @buf is aligned 25 | bool (*write)(const struct dln2_i2c_device *dev, uint16_t address, const void *buf, size_t len); 26 | 27 | }; 28 | 29 | void dln2_i2c_set_devices(struct dln2_i2c_device **devs); 30 | 31 | #endif 32 | -------------------------------------------------------------------------------- /dln2-gpio.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: CC0-1.0 2 | /* 3 | * Written in 2021 by Noralf Trønnes 4 | * 5 | * To the extent possible under law, the author(s) have dedicated all copyright and related and 6 | * neighboring rights to this software to the public domain worldwide. This software is 7 | * distributed without any warranty. 8 | * 9 | * You should have received a copy of the CC0 Public Domain Dedication along with this software. 10 | * If not, see . 11 | */ 12 | 13 | #include 14 | #include "pico/stdlib.h" 15 | #include "hardware/sync.h" 16 | #include "dln2.h" 17 | 18 | #define LOG1 //printf 19 | #define LOG2 //printf 20 | 21 | #define DLN2_GPIO_CMD(cmd) DLN2_CMD(cmd, DLN2_MODULE_GPIO) 22 | 23 | #define DLN2_GPIO_GET_PIN_COUNT DLN2_GPIO_CMD(0x01) 24 | #define DLN2_GPIO_SET_DEBOUNCE DLN2_GPIO_CMD(0x04) 25 | #define DLN2_GPIO_PIN_GET_VAL DLN2_GPIO_CMD(0x0B) 26 | #define DLN2_GPIO_PIN_SET_OUT_VAL DLN2_GPIO_CMD(0x0C) 27 | #define DLN2_GPIO_PIN_GET_OUT_VAL DLN2_GPIO_CMD(0x0D) 28 | #define DLN2_GPIO_CONDITION_MET_EV DLN2_GPIO_CMD(0x0F) 29 | #define DLN2_GPIO_PIN_ENABLE DLN2_GPIO_CMD(0x10) 30 | #define DLN2_GPIO_PIN_DISABLE DLN2_GPIO_CMD(0x11) 31 | #define DLN2_GPIO_PIN_SET_DIRECTION DLN2_GPIO_CMD(0x13) 32 | #define DLN2_GPIO_PIN_GET_DIRECTION DLN2_GPIO_CMD(0x14) 33 | #define DLN2_GPIO_PIN_SET_EVENT_CFG DLN2_GPIO_CMD(0x1E) 34 | 35 | #define DLN2_GPIO_EVENT_NONE 0 36 | #define DLN2_GPIO_EVENT_CHANGE 1 37 | #define DLN2_GPIO_EVENT_LVL_HIGH 2 38 | #define DLN2_GPIO_EVENT_LVL_LOW 3 39 | 40 | #define DLN2_GPIO_NUM_PINS 29 41 | 42 | #ifdef PICO_DEFAULT_LED_PIN 43 | #define LED_PIN PICO_DEFAULT_LED_PIN 44 | #else 45 | #define LED_PIN 0xff // out of bounds value that will never match 46 | #endif 47 | 48 | #define get_bit(n, var) ((var >> (n)) & 1U) 49 | 50 | #define assign_bit(n, var, val) \ 51 | do{ \ 52 | if (val) \ 53 | (var) |= 1U << (n); \ 54 | else \ 55 | (var) &= ~(1U << (n)); \ 56 | } while (0) 57 | 58 | static uint32_t prev_values; 59 | 60 | struct dln2_gpio_event { 61 | uint8_t gpio; 62 | uint8_t events; 63 | uint8_t value; 64 | }; 65 | 66 | #define DLN2_GPIO_MAX_EVENTS 32 67 | static struct dln2_gpio_event dln2_gpio_events[DLN2_GPIO_MAX_EVENTS]; 68 | uint16_t dln2_gpio_event_count; 69 | 70 | static const char *dln2_gpio_id_to_name(uint16_t id) 71 | { 72 | switch (id) { 73 | case DLN2_GPIO_GET_PIN_COUNT: 74 | return "GPIO_GET_PIN_COUNT"; 75 | case DLN2_GPIO_SET_DEBOUNCE: 76 | return "GPIO_SET_DEBOUNCE"; 77 | case DLN2_GPIO_PIN_GET_VAL: 78 | return "GPIO_PIN_GET_VAL"; 79 | case DLN2_GPIO_PIN_SET_OUT_VAL: 80 | return "GPIO_PIN_SET_OUT_VAL"; 81 | case DLN2_GPIO_PIN_GET_OUT_VAL: 82 | return "GPIO_PIN_GET_OUT_VAL"; 83 | case DLN2_GPIO_CONDITION_MET_EV: 84 | return "GPIO_CONDITION_MET_EV"; 85 | case DLN2_GPIO_PIN_ENABLE: 86 | return "GPIO_PIN_ENABLE"; 87 | case DLN2_GPIO_PIN_DISABLE: 88 | return "GPIO_PIN_DISABLE"; 89 | case DLN2_GPIO_PIN_SET_DIRECTION: 90 | return "GPIO_PIN_SET_DIRECTION"; 91 | case DLN2_GPIO_PIN_GET_DIRECTION: 92 | return "GPIO_PIN_GET_DIRECTION"; 93 | case DLN2_GPIO_PIN_SET_EVENT_CFG: 94 | return "GPIO_PIN_SET_EVENT_CFG"; 95 | } 96 | return NULL; 97 | } 98 | 99 | static int dln2_gpio_slot_pin_val(struct dln2_slot *slot, uint8_t *val) 100 | { 101 | size_t len = dln2_slot_header_data_size(slot); 102 | if ((!val && len != 2) || (val && len != 3)) 103 | return -1; 104 | 105 | void *data = dln2_slot_header_data(slot); 106 | uint16_t *pin = data; 107 | if (*pin > (DLN2_GPIO_NUM_PINS - 1)) 108 | return -1; 109 | 110 | if (val) 111 | *val = *(uint8_t *)(data + 2); 112 | 113 | const struct dln2_header *hdr = dln2_slot_header(slot); 114 | const char *name = dln2_gpio_id_to_name(hdr->id); 115 | if (name) 116 | LOG1("\n%s: ", name); 117 | else 118 | LOG1("DLN_GPIO UNKNOWN 0x%02x: ", hdr->id); 119 | LOG1("pin=%u val=%d\n", *pin, val ? *val : -1); 120 | 121 | return *pin; 122 | } 123 | 124 | #define DLN2_GPIO_GET_PIN_VERIFY(_slot, _pin, _val) \ 125 | (_pin) = dln2_gpio_slot_pin_val((_slot), (_val)); \ 126 | if ((_pin) < 0 || !dln2_pin_is_requested((_pin), DLN2_MODULE_GPIO)) \ 127 | return dln2_response_error((_slot), DLN2_RES_INVALID_PIN_NUMBER) 128 | 129 | static bool dln2_gpio_response_pin_val(struct dln2_slot *slot, uint16_t pin, uint8_t *val) 130 | { 131 | uint8_t *data = dln2_slot_response_data(slot); 132 | put_unaligned_le16(pin, data); 133 | if (val) 134 | data[2] = *val; 135 | 136 | LOG1("GPIO RSP: pin=%u val=%d\n", pin, val ? *val : -1); 137 | 138 | return dln2_response(slot, val ? 3 : 2); 139 | } 140 | 141 | static bool dln2_gpio_pin_enable(struct dln2_slot *slot, bool enable) 142 | { 143 | int pin = dln2_gpio_slot_pin_val(slot, NULL); 144 | if (pin < 0) 145 | return dln2_response_error(slot, DLN2_RES_INVALID_PIN_NUMBER); 146 | 147 | LOG1("%s: pin=%u enable=%u\n", __func__, pin, enable); 148 | if (enable) { 149 | int res = dln2_pin_request(pin, DLN2_MODULE_GPIO); 150 | if (res) 151 | return dln2_response_error(slot, res); 152 | 153 | enum gpio_function fn = gpio_get_function(pin); 154 | LOG1(" gpio_get_function=%u\n", fn); 155 | 156 | if (pin != LED_PIN) { 157 | gpio_init(pin); 158 | gpio_pull_down(pin); // Some other function could have changed this (adc) 159 | } 160 | } else { 161 | int res = dln2_pin_free(pin, DLN2_MODULE_GPIO); 162 | if (res) 163 | return dln2_response_error(slot, res); 164 | if (pin != LED_PIN) 165 | gpio_deinit(pin); 166 | } 167 | return dln2_response(slot, 0); 168 | } 169 | 170 | static bool dln2_gpio_pin_set_event_cfg(struct dln2_slot *slot) 171 | { 172 | struct { 173 | uint16_t pin; 174 | uint8_t type; 175 | uint16_t period; 176 | } TU_ATTR_PACKED *cmd = dln2_slot_header_data(slot); 177 | 178 | DLN2_VERIFY_COMMAND_SIZE(slot, sizeof(*cmd)); 179 | 180 | LOG1("\nDLN2_GPIO_PIN_SET_EVENT_CFG: pin=%u type=%u period=%u\n", cmd->pin, cmd->type, cmd->period); 181 | 182 | if (!dln2_pin_is_requested(cmd->pin, DLN2_MODULE_GPIO)) 183 | return dln2_response_error(slot, DLN2_RES_INVALID_PIN_NUMBER); 184 | 185 | if (cmd->period) 186 | return dln2_response_error(slot, DLN2_RES_INVALID_EVENT_PERIOD); 187 | 188 | if (cmd->pin == LED_PIN) 189 | return dln2_response_error(slot, DLN2_RES_INVALID_VALUE); 190 | 191 | assign_bit(cmd->pin, prev_values, gpio_get(cmd->pin)); 192 | 193 | switch (cmd->type) { 194 | case DLN2_GPIO_EVENT_NONE: 195 | gpio_set_irq_enabled(cmd->pin, GPIO_IRQ_LEVEL_LOW | GPIO_IRQ_LEVEL_HIGH | GPIO_IRQ_EDGE_FALL | GPIO_IRQ_EDGE_RISE, false); 196 | break; 197 | // The Linux driver always uses this so we don't know which edge(s) it actually cares about. 198 | case DLN2_GPIO_EVENT_CHANGE: 199 | gpio_set_irq_enabled(cmd->pin, GPIO_IRQ_EDGE_FALL | GPIO_IRQ_EDGE_RISE, true); 200 | break; 201 | // The Linux driver doesn't use these, maybe because they were mistaken to be only level 202 | // interrupts, but with period=0 they are actually edge interrupts according to the docs: 203 | // http://dlnware.com/dll/DLN_GPIO_EVENT_LEVEL_HIGH-Events 204 | case DLN2_GPIO_EVENT_LVL_HIGH: 205 | gpio_set_irq_enabled(cmd->pin, GPIO_IRQ_EDGE_RISE, true); 206 | break; 207 | case DLN2_GPIO_EVENT_LVL_LOW: 208 | gpio_set_irq_enabled(cmd->pin, GPIO_IRQ_EDGE_FALL, true); 209 | break; 210 | default: 211 | return dln2_response_error(slot, DLN2_RES_INVALID_EVENT_TYPE); 212 | } 213 | 214 | return dln2_response(slot, 0); 215 | } 216 | 217 | bool dln2_handle_gpio(struct dln2_slot *slot) 218 | { 219 | struct dln2_header *hdr = dln2_slot_header(slot); 220 | uint8_t val; 221 | int pin; 222 | 223 | switch (hdr->id) { 224 | case DLN2_GPIO_GET_PIN_COUNT: 225 | LOG1("DLN2_GPIO_GET_PIN_COUNT\n"); 226 | if (dln2_slot_header_data_size(slot)) 227 | return dln2_response_error(slot, DLN2_RES_INVALID_COMMAND_SIZE); 228 | return dln2_response_u16(slot, DLN2_GPIO_NUM_PINS); 229 | case DLN2_GPIO_SET_DEBOUNCE: 230 | // The Linux driver can set the default debounce value, but it does not enable it for the pin?! 231 | // The DLN-2 adapter does not support debounce, but 4M and 4S do. 232 | LOG1("DLN2_GPIO_SET_DEBOUNCE\n"); 233 | return dln2_response_error(slot, DLN2_RES_COMMAND_NOT_SUPPORTED); 234 | case DLN2_GPIO_PIN_GET_VAL: 235 | DLN2_GPIO_GET_PIN_VERIFY(slot, pin, NULL); 236 | val = gpio_get(pin); 237 | return dln2_gpio_response_pin_val(slot, pin, &val); 238 | case DLN2_GPIO_PIN_SET_OUT_VAL: 239 | DLN2_GPIO_GET_PIN_VERIFY(slot, pin, &val); 240 | gpio_put(pin, val); 241 | return dln2_gpio_response_pin_val(slot, pin, NULL); 242 | case DLN2_GPIO_PIN_GET_OUT_VAL: 243 | DLN2_GPIO_GET_PIN_VERIFY(slot, pin, NULL); 244 | val = gpio_get_out_level(pin); 245 | return dln2_gpio_response_pin_val(slot, pin, &val); 246 | case DLN2_GPIO_PIN_ENABLE: 247 | return dln2_gpio_pin_enable(slot, true); 248 | case DLN2_GPIO_PIN_DISABLE: 249 | return dln2_gpio_pin_enable(slot, false); 250 | case DLN2_GPIO_PIN_SET_DIRECTION: 251 | DLN2_GPIO_GET_PIN_VERIFY(slot, pin, &val); 252 | if (pin == LED_PIN && !val) 253 | return dln2_response_error(slot, DLN2_RES_INVALID_VALUE); 254 | gpio_set_dir(pin, val); 255 | return dln2_gpio_response_pin_val(slot, pin, NULL); 256 | case DLN2_GPIO_PIN_GET_DIRECTION: 257 | DLN2_GPIO_GET_PIN_VERIFY(slot, pin, NULL); 258 | val = gpio_get_dir(pin); 259 | return dln2_gpio_response_pin_val(slot, pin, &val); 260 | case DLN2_GPIO_PIN_SET_EVENT_CFG: 261 | return dln2_gpio_pin_set_event_cfg(slot); 262 | default: 263 | LOG1("GPIO command not supported: 0x%04x\n", hdr->id); 264 | return dln2_response_error(slot, DLN2_RES_COMMAND_NOT_SUPPORTED); 265 | } 266 | } 267 | 268 | static bool dln2_gpio_queue_event(struct dln2_gpio_event *event) 269 | { 270 | struct { 271 | uint16_t count; 272 | uint8_t type; 273 | uint16_t pin; 274 | uint8_t value; 275 | } TU_ATTR_PACKED *ev; 276 | 277 | LOG1("%s(gpio=%u, value=%u)\n", __func__, event->gpio, event->value); 278 | 279 | struct dln2_slot *slot = dln2_get_slot(); 280 | if (!slot) { 281 | LOG1("Run out of slots!\n"); 282 | LOG2("-\n"); 283 | return false; 284 | } 285 | 286 | struct dln2_header *hdr = dln2_slot_header(slot); 287 | hdr->size = sizeof(*hdr) + sizeof(*ev); 288 | hdr->id = DLN2_GPIO_CONDITION_MET_EV; 289 | hdr->echo = 0; 290 | hdr->handle = DLN2_HANDLE_EVENT; 291 | 292 | ev = dln2_slot_header_data(slot); 293 | // The Linux driver ignores count and type 294 | ev->count = dln2_gpio_event_count; 295 | ev->type = 0; 296 | ev->pin = event->gpio; 297 | ev->value = event->value; 298 | 299 | dln2_print_slot(slot); 300 | dln2_queue_slot_in(slot); 301 | 302 | return true; 303 | } 304 | 305 | void dln2_gpio_task(void) 306 | { 307 | bool queued; 308 | 309 | do { 310 | queued = false; 311 | uint32_t ints = save_and_disable_interrupts(); 312 | 313 | if (dln2_gpio_events[0].events) { 314 | if (dln2_gpio_queue_event(&dln2_gpio_events[0])) { 315 | for (int i = 0; i < (DLN2_GPIO_MAX_EVENTS - 1); i++) { 316 | dln2_gpio_events[i] = dln2_gpio_events[i + 1]; 317 | } 318 | dln2_gpio_events[DLN2_GPIO_MAX_EVENTS - 1].events = 0; 319 | queued = true; 320 | } 321 | } 322 | restore_interrupts(ints); 323 | } while (queued); 324 | } 325 | 326 | static void dln2_gpio_irq_callback(uint gpio, uint32_t events) 327 | { 328 | if (gpio >= DLN2_GPIO_NUM_PINS) 329 | return; 330 | 331 | bool prev_value = get_bit(gpio, prev_values); 332 | bool value; 333 | 334 | if (events == (GPIO_IRQ_EDGE_FALL | GPIO_IRQ_EDGE_RISE)) 335 | value = gpio_get(gpio); 336 | else if (events == GPIO_IRQ_EDGE_FALL) 337 | value = 0; 338 | else if (events == GPIO_IRQ_EDGE_RISE) 339 | value = 1; 340 | else 341 | return; 342 | 343 | if (events == (GPIO_IRQ_EDGE_FALL | GPIO_IRQ_EDGE_RISE)) 344 | LOG2("B"); 345 | else if (events == GPIO_IRQ_EDGE_FALL) 346 | LOG2("F"); 347 | else if (events == GPIO_IRQ_EDGE_RISE) 348 | LOG2("R"); 349 | else { 350 | LOG2("N\n"); 351 | return; 352 | } 353 | 354 | LOG1("%s: gpio=%u events=0x%x value=%u prev_value=%u %s\n", 355 | __func__, gpio, events, value, prev_value, prev_value == value ? "SKIP" : ""); 356 | 357 | if (prev_value == value) { 358 | LOG2(" X\n"); 359 | return; 360 | } 361 | 362 | assign_bit(gpio, prev_values, value); 363 | dln2_gpio_event_count++; 364 | 365 | uint i; 366 | for (i = 0; i < DLN2_GPIO_MAX_EVENTS; i++) { 367 | if (!dln2_gpio_events[i].events) 368 | break; 369 | } 370 | 371 | if (i == DLN2_GPIO_MAX_EVENTS) { 372 | LOG1("dln2_gpio_events is FULL\n"); 373 | return; 374 | } 375 | 376 | dln2_gpio_events[i].gpio = gpio; 377 | dln2_gpio_events[i].events = events; 378 | dln2_gpio_events[i].value = value; 379 | LOG2("%u\n", value); 380 | } 381 | 382 | void dln2_gpio_init(void) 383 | { 384 | gpio_set_irq_callback(&dln2_gpio_irq_callback); 385 | irq_set_enabled(IO_IRQ_BANK0, true); 386 | } 387 | -------------------------------------------------------------------------------- /dln2-i2c.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: CC0-1.0 2 | /* 3 | * Written in 2021 by Noralf Trønnes 4 | * 5 | * To the extent possible under law, the author(s) have dedicated all copyright and related and 6 | * neighboring rights to this software to the public domain worldwide. This software is 7 | * distributed without any warranty. 8 | * 9 | * You should have received a copy of the CC0 Public Domain Dedication along with this software. 10 | * If not, see . 11 | */ 12 | 13 | #include 14 | #include "hardware/gpio.h" 15 | #include "hardware/i2c.h" 16 | #include "dln2.h" 17 | #include "dln2-devices.h" 18 | 19 | #define LOG1 //printf 20 | #define LOG2 //printf 21 | 22 | static struct dln2_i2c_device **dln2_i2c_devices; 23 | 24 | #define DLN2_I2C_CMD(cmd) DLN2_CMD(cmd, DLN2_MODULE_I2C) 25 | 26 | #define DLN2_I2C_ENABLE DLN2_I2C_CMD(0x01) 27 | #define DLN2_I2C_DISABLE DLN2_I2C_CMD(0x02) 28 | #define DLN2_I2C_WRITE DLN2_I2C_CMD(0x06) 29 | #define DLN2_I2C_READ DLN2_I2C_CMD(0x07) 30 | 31 | // Linux driver timeout is 200ms 32 | #define DLN2_I2C_TIMEOUT_US (150 * 1000) 33 | 34 | static bool dln2_i2c_enable(struct dln2_slot *slot, bool enable) 35 | { 36 | uint8_t *port = dln2_slot_header_data(slot); 37 | uint scl = PICO_DEFAULT_I2C_SCL_PIN; 38 | uint sda = PICO_DEFAULT_I2C_SDA_PIN; 39 | int res; 40 | 41 | LOG1(" %s: port=%u enable=%u\n", __func__, *port, enable); 42 | 43 | if (dln2_slot_header_data_size(slot) != sizeof(*port)) 44 | return dln2_response_error(slot, DLN2_RES_INVALID_COMMAND_SIZE); 45 | if (*port) 46 | return dln2_response_error(slot, DLN2_RES_INVALID_PORT_NUMBER); 47 | 48 | if (enable) { 49 | res = dln2_pin_request(scl, DLN2_MODULE_I2C); 50 | if (res) 51 | return dln2_response_error(slot, res); 52 | 53 | res = dln2_pin_request(sda, DLN2_MODULE_I2C); 54 | if (res) { 55 | dln2_pin_free(scl, DLN2_MODULE_I2C); 56 | return dln2_response_error(slot, res); 57 | } 58 | 59 | i2c_init(i2c_default, 100 * 1000); 60 | gpio_set_function(scl, GPIO_FUNC_I2C); 61 | gpio_set_function(sda, GPIO_FUNC_I2C); 62 | } else { 63 | res = dln2_pin_free(sda, DLN2_MODULE_I2C); 64 | if (res) 65 | return dln2_response_error(slot, res); 66 | 67 | res = dln2_pin_free(scl, DLN2_MODULE_I2C); 68 | if (res) 69 | return dln2_response_error(slot, res); 70 | 71 | gpio_set_function(sda, GPIO_FUNC_NULL); 72 | gpio_set_function(scl, GPIO_FUNC_NULL); 73 | i2c_deinit(i2c_default); 74 | } 75 | 76 | return dln2_response(slot, 0); 77 | } 78 | 79 | struct dln2_i2c_read_msg_tx { 80 | uint8_t port; 81 | uint8_t addr; 82 | uint8_t mem_addr_len; 83 | uint32_t mem_addr; 84 | uint16_t buf_len; 85 | } TU_ATTR_PACKED; 86 | 87 | static bool dln2_i2c_read(struct dln2_slot *slot) 88 | { 89 | struct dln2_i2c_read_msg_tx *msg = dln2_slot_header_data(slot); 90 | uint8_t *rx = dln2_slot_response_data(slot); 91 | size_t len = msg->buf_len; 92 | 93 | LOG1(" %s: port=%u addr=0x%02x buf_len=%u\n", __func__, msg->port, msg->addr, msg->buf_len); 94 | 95 | if (dln2_slot_header_data_size(slot) != sizeof(*msg)) 96 | return dln2_response_error(slot, DLN2_RES_INVALID_COMMAND_SIZE); 97 | if (msg->port) 98 | return dln2_response_error(slot, DLN2_RES_INVALID_PORT_NUMBER); 99 | 100 | if (dln2_i2c_devices) { 101 | for (struct dln2_i2c_device **ptr = dln2_i2c_devices; *ptr; ptr++) { 102 | struct dln2_i2c_device *dev = *ptr; 103 | if (dev->address && dev->address != msg->addr) 104 | continue; 105 | 106 | if (!dev->read(dev, msg->addr, rx + 2, len)) 107 | break; 108 | put_unaligned_le16(len, rx); 109 | return dln2_response(slot, len + 2); 110 | } 111 | } 112 | 113 | int ret = i2c_read_timeout_us(i2c_default, msg->addr, rx + 2, len, false, DLN2_I2C_TIMEOUT_US); 114 | if (ret < 0) 115 | return dln2_response_error(slot, DLN2_RES_I2C_MASTER_SENDING_ADDRESS_FAILED); 116 | // The linux driver returns -EPROTO if length differs, so use a descriptive error (there was no read error code) 117 | if (ret != len) 118 | return dln2_response_error(slot, DLN2_RES_I2C_MASTER_SENDING_DATA_FAILED); 119 | 120 | put_unaligned_le16(len, rx); 121 | return dln2_response(slot, len + 2); 122 | } 123 | 124 | struct dln2_i2c_write_msg { 125 | uint8_t port; 126 | uint8_t addr; 127 | uint8_t mem_addr_len; 128 | uint32_t mem_addr; 129 | uint16_t buf_len; 130 | uint8_t buf[]; 131 | } TU_ATTR_PACKED; 132 | 133 | static bool dln2_i2c_write(struct dln2_slot *slot) 134 | { 135 | struct dln2_i2c_write_msg *msg = dln2_slot_header_data(slot); 136 | 137 | LOG1(" %s: port=%u addr=0x%02x buf_len=%u\n", __func__, msg->port, msg->addr, msg->buf_len); 138 | 139 | if (dln2_slot_header_data_size(slot) < sizeof(*msg)) 140 | return dln2_response_error(slot, DLN2_RES_INVALID_COMMAND_SIZE); 141 | if (msg->port) 142 | return dln2_response_error(slot, DLN2_RES_INVALID_PORT_NUMBER); 143 | 144 | if (dln2_i2c_devices) { 145 | for (struct dln2_i2c_device **ptr = dln2_i2c_devices; *ptr; ptr++) { 146 | struct dln2_i2c_device *dev = *ptr; 147 | if (dev->address && dev->address != msg->addr) 148 | continue; 149 | 150 | if (!dev->write(dev, msg->addr, msg->buf, msg->buf_len)) 151 | break; 152 | return dln2_response(slot, msg->buf_len); 153 | } 154 | } 155 | 156 | int ret = i2c_write_timeout_us(i2c_default, msg->addr, msg->buf, msg->buf_len, false, DLN2_I2C_TIMEOUT_US); 157 | LOG2(" i2c_write_timeout_us: ret =%d\n", ret); 158 | if (ret < 0) 159 | return dln2_response_error(slot, DLN2_RES_I2C_MASTER_SENDING_ADDRESS_FAILED); 160 | if (ret != msg->buf_len) 161 | return dln2_response_error(slot, DLN2_RES_I2C_MASTER_SENDING_DATA_FAILED); 162 | 163 | return dln2_response(slot, msg->buf_len); 164 | } 165 | 166 | bool dln2_handle_i2c(struct dln2_slot *slot) 167 | { 168 | struct dln2_header *hdr = dln2_slot_header(slot); 169 | 170 | switch (hdr->id) { 171 | case DLN2_I2C_ENABLE: 172 | return dln2_i2c_enable(slot, true); 173 | case DLN2_I2C_DISABLE: 174 | return dln2_i2c_enable(slot, false); 175 | case DLN2_I2C_WRITE: 176 | return dln2_i2c_write(slot); 177 | case DLN2_I2C_READ: 178 | return dln2_i2c_read(slot); 179 | default: 180 | LOG1("I2C: unknown command 0x%02x\n", hdr->id); 181 | return dln2_response_error(slot, DLN2_RES_COMMAND_NOT_SUPPORTED); 182 | } 183 | } 184 | 185 | void dln2_i2c_set_devices(struct dln2_i2c_device **devs) 186 | { 187 | dln2_i2c_devices = devs; 188 | } 189 | -------------------------------------------------------------------------------- /dln2-pin.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: CC0-1.0 2 | /* 3 | * Written in 2021 by Noralf Trønnes 4 | * 5 | * To the extent possible under law, the author(s) have dedicated all copyright and related and 6 | * neighboring rights to this software to the public domain worldwide. This software is 7 | * distributed without any warranty. 8 | * 9 | * You should have received a copy of the CC0 Public Domain Dedication along with this software. 10 | * If not, see . 11 | */ 12 | 13 | #include 14 | #include "dln2.h" 15 | 16 | #define LOG1 //printf 17 | 18 | #define DLN2_PIN_MAX 32 19 | #define DLN2_PIN_NOT_AVAILABLE 0xff 20 | 21 | struct dln2_pin_state { 22 | uint8_t module; 23 | }; 24 | 25 | static struct dln2_pin_state dln2_pin_states[DLN2_PIN_MAX]; 26 | 27 | bool dln2_pin_is_requested(uint16_t pin, uint8_t module) 28 | { 29 | if (pin >= DLN2_PIN_MAX) 30 | return false; 31 | 32 | struct dln2_pin_state *state = &dln2_pin_states[pin]; 33 | return state->module == module; 34 | } 35 | 36 | uint16_t dln2_pin_request(uint16_t pin, uint8_t module) 37 | { 38 | if (pin >= DLN2_PIN_MAX) 39 | return DLN2_RES_INVALID_PIN_NUMBER; 40 | 41 | struct dln2_pin_state *state = &dln2_pin_states[pin]; 42 | if (state->module && state->module != module) 43 | return DLN2_RES_PIN_IN_USE; 44 | 45 | state->module = module; 46 | return 0; 47 | } 48 | 49 | uint16_t dln2_pin_free(uint16_t pin, uint8_t module) 50 | { 51 | if (pin >= DLN2_PIN_MAX) 52 | return DLN2_RES_INVALID_PIN_NUMBER; 53 | 54 | struct dln2_pin_state *state = &dln2_pin_states[pin]; 55 | if (state->module && state->module != module) 56 | return DLN2_RES_PIN_NOT_CONNECTED_TO_MODULE; 57 | 58 | state->module = 0; 59 | return 0; 60 | } 61 | 62 | void dln2_pin_set_available(uint32_t mask) 63 | { 64 | for (uint i = 0; i < DLN2_PIN_MAX; i++) { 65 | if (!(mask & 1)) 66 | dln2_pin_states[i].module = DLN2_PIN_NOT_AVAILABLE; 67 | mask >>= 1; 68 | } 69 | dln2_pin_states[30].module = DLN2_PIN_NOT_AVAILABLE; 70 | dln2_pin_states[31].module = DLN2_PIN_NOT_AVAILABLE; 71 | } 72 | -------------------------------------------------------------------------------- /dln2-spi.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: CC0-1.0 2 | /* 3 | * Written in 2021 by Noralf Trønnes 4 | * 5 | * To the extent possible under law, the author(s) have dedicated all copyright and related and 6 | * neighboring rights to this software to the public domain worldwide. This software is 7 | * distributed without any warranty. 8 | * 9 | * You should have received a copy of the CC0 Public Domain Dedication along with this software. 10 | * If not, see . 11 | */ 12 | 13 | #include 14 | #include "dln2.h" 15 | #include "hardware/clocks.h" 16 | #include "hardware/gpio.h" 17 | #include "hardware/spi.h" 18 | 19 | #define LOG1 //printf 20 | 21 | #define DLN2_SPI_DEFAULT_FREQUENCY (1 * 1000 * 1000) // 1MHz 22 | 23 | #define DLN2_SPI_CMD(cmd) DLN2_CMD(cmd, DLN2_MODULE_SPI) 24 | 25 | /* SPI commands used by the Linux driver */ 26 | #define DLN2_SPI_ENABLE DLN2_SPI_CMD(0x11) 27 | #define DLN2_SPI_DISABLE DLN2_SPI_CMD(0x12) 28 | #define DLN2_SPI_SET_MODE DLN2_SPI_CMD(0x14) 29 | #define DLN2_SPI_SET_FRAME_SIZE DLN2_SPI_CMD(0x16) 30 | #define DLN2_SPI_SET_FREQUENCY DLN2_SPI_CMD(0x18) 31 | #define DLN2_SPI_READ_WRITE DLN2_SPI_CMD(0x1A) 32 | #define DLN2_SPI_READ DLN2_SPI_CMD(0x1B) 33 | #define DLN2_SPI_WRITE DLN2_SPI_CMD(0x1C) 34 | #define DLN2_SPI_SET_SS DLN2_SPI_CMD(0x26) 35 | #define DLN2_SPI_SS_MULTI_ENABLE DLN2_SPI_CMD(0x38) 36 | #define DLN2_SPI_SS_MULTI_DISABLE DLN2_SPI_CMD(0x39) 37 | #define DLN2_SPI_GET_SUPPORTED_FRAME_SIZES DLN2_SPI_CMD(0x43) 38 | #define DLN2_SPI_GET_SS_COUNT DLN2_SPI_CMD(0x44) 39 | #define DLN2_SPI_GET_MIN_FREQUENCY DLN2_SPI_CMD(0x45) 40 | #define DLN2_SPI_GET_MAX_FREQUENCY DLN2_SPI_CMD(0x46) 41 | 42 | #define DLN2_SPI_CPHA (1 << 0) 43 | #define DLN2_SPI_CPOL (1 << 1) 44 | 45 | #define DLN2_SPI_MAX_XFER_SIZE 256 46 | #define DLN2_SPI_ATTR_LEAVE_SS_LOW (1 << 0) 47 | 48 | #define div_round_up(n,d) (((n) + (d) - 1) / (d)) 49 | 50 | struct { 51 | uint32_t freq; 52 | uint8_t mode; 53 | uint8_t bpw; 54 | } dln2_spi_config; 55 | 56 | static uint8_t dln2_spi_tmp_buf[DLN2_SPI_MAX_XFER_SIZE]; 57 | 58 | static bool dln2_spi_enable(struct dln2_slot *slot, bool enable) 59 | { 60 | uint8_t *port = dln2_slot_header_data(slot); 61 | // wait_for_completion is always DLN2_TRANSFERS_WAIT_COMPLETE in the Linux driver 62 | //uint8_t *wait_for_completion = dln2_slot_header_data(slot) + 1; 63 | uint sck = PICO_DEFAULT_SPI_SCK_PIN; 64 | uint mosi = PICO_DEFAULT_SPI_TX_PIN; 65 | uint miso = PICO_DEFAULT_SPI_RX_PIN; 66 | int res; 67 | 68 | LOG1("%s: port=%u\n", enable ? "DLN2_SPI_ENABLE" : "DLN2_SPI_DISABLE", *port); 69 | 70 | if (enable) 71 | DLN2_VERIFY_COMMAND_SIZE(slot, 1); 72 | else 73 | DLN2_VERIFY_COMMAND_SIZE(slot, 2); 74 | 75 | if (*port) 76 | return dln2_response_error(slot, DLN2_RES_INVALID_PORT_NUMBER); 77 | 78 | if (enable) { 79 | res = dln2_pin_request(sck, DLN2_MODULE_SPI); 80 | if (res) 81 | return dln2_response_error(slot, res); 82 | 83 | res = dln2_pin_request(mosi, DLN2_MODULE_SPI); 84 | if (res) { 85 | dln2_pin_free(sck, DLN2_MODULE_SPI); 86 | return dln2_response_error(slot, res); 87 | } 88 | 89 | res = dln2_pin_request(miso, DLN2_MODULE_SPI); 90 | if (res) { 91 | dln2_pin_free(sck, DLN2_MODULE_SPI); 92 | dln2_pin_free(mosi, DLN2_MODULE_SPI); 93 | return dln2_response_error(slot, res); 94 | } 95 | 96 | uint freq = spi_init(spi_default, dln2_spi_config.freq); 97 | LOG1("SPI: actual frequency: %uHz\n", freq); 98 | 99 | spi_set_format(spi_default, dln2_spi_config.bpw, dln2_spi_config.mode & DLN2_SPI_CPOL, 100 | dln2_spi_config.mode & DLN2_SPI_CPHA, SPI_MSB_FIRST); 101 | 102 | gpio_set_function(sck, GPIO_FUNC_SPI); 103 | gpio_set_function(mosi, GPIO_FUNC_SPI); 104 | gpio_set_function(miso, GPIO_FUNC_SPI); 105 | } else { 106 | res = dln2_pin_free(sck, DLN2_MODULE_SPI); 107 | if (res) 108 | return dln2_response_error(slot, res); 109 | gpio_set_function(sck, GPIO_FUNC_NULL); 110 | 111 | res = dln2_pin_free(mosi, DLN2_MODULE_SPI); 112 | if (res) 113 | return dln2_response_error(slot, res); 114 | gpio_set_function(mosi, GPIO_FUNC_NULL); 115 | 116 | res = dln2_pin_free(miso, DLN2_MODULE_SPI); 117 | if (res) 118 | return dln2_response_error(slot, res); 119 | gpio_set_function(miso, GPIO_FUNC_NULL); 120 | } 121 | 122 | return dln2_response(slot, 0); 123 | } 124 | 125 | static bool dln2_spi_set_mode(struct dln2_slot *slot) 126 | { 127 | struct { 128 | uint8_t port; 129 | uint8_t mode; 130 | } *cmd = dln2_slot_header_data(slot); 131 | uint8_t mask = DLN2_SPI_CPOL | DLN2_SPI_CPHA; 132 | 133 | DLN2_VERIFY_COMMAND_SIZE(slot, sizeof(*cmd)); 134 | 135 | LOG1("DLN2_SPI_SET_MODE: port=%u mode=0x%02x\n", cmd->port, cmd->mode); 136 | 137 | if (cmd->port) 138 | return dln2_response_error(slot, DLN2_RES_INVALID_PORT_NUMBER); 139 | 140 | if (cmd->mode & ~mask) 141 | return dln2_response_error(slot, DLN2_RES_INVALID_MODE); 142 | 143 | dln2_spi_config.mode = cmd->mode; 144 | 145 | return dln2_response(slot, 0); 146 | } 147 | 148 | static bool dln2_spi_set_bpw(struct dln2_slot *slot) 149 | { 150 | struct { 151 | uint8_t port; 152 | uint8_t bpw; 153 | } *cmd = dln2_slot_header_data(slot); 154 | 155 | DLN2_VERIFY_COMMAND_SIZE(slot, sizeof(*cmd)); 156 | 157 | LOG1("DLN2_SPI_SET_BPW: port=%u bpw=%u\n", cmd->port, cmd->bpw); 158 | 159 | if (cmd->port) 160 | return dln2_response_error(slot, DLN2_RES_INVALID_PORT_NUMBER); 161 | 162 | // TODO: verify 163 | // DLN2_RES_SPI_INVALID_FRAME_SIZE 164 | dln2_spi_config.bpw = cmd->bpw; 165 | 166 | return dln2_response(slot, 0); 167 | } 168 | 169 | static uint dln2_spi_min_frequency(void) 170 | { 171 | uint freq_in = clock_get_hz(clk_peri); 172 | uint prescale = 254, postdiv = 256; 173 | 174 | return freq_in / (prescale * postdiv); 175 | } 176 | 177 | static uint dln2_spi_max_frequency(void) 178 | { 179 | uint freq_in = clock_get_hz(clk_peri); 180 | uint prescale = 2, postdiv = 1; 181 | 182 | return freq_in / (prescale * postdiv); 183 | } 184 | 185 | static bool dln2_spi_set_frequency(struct dln2_slot *slot) 186 | { 187 | struct { 188 | uint8_t port; 189 | uint32_t speed; 190 | } TU_ATTR_PACKED *cmd = dln2_slot_header_data(slot); 191 | uint32_t speed; 192 | 193 | DLN2_VERIFY_COMMAND_SIZE(slot, sizeof(*cmd)); 194 | 195 | LOG1("DLN2_SPI_SET_FREQUENCY: port=%u speed=%u\n", cmd->port, cmd->speed); 196 | 197 | if (cmd->port) 198 | return dln2_response_error(slot, DLN2_RES_INVALID_PORT_NUMBER); 199 | 200 | if (cmd->speed < dln2_spi_min_frequency()) 201 | cmd->speed = dln2_spi_min_frequency(); 202 | else if (cmd->speed > dln2_spi_max_frequency()) 203 | cmd->speed = dln2_spi_max_frequency(); 204 | 205 | speed = spi_set_baudrate(spi_default, cmd->speed); 206 | LOG1("SPI: actual frequency: %uHz\n", speed); 207 | dln2_spi_config.freq = speed; 208 | 209 | // The Linux driver ignores the returned value 210 | return dln2_response_u32(slot, speed); 211 | } 212 | 213 | static void dln2_spi_cs_active(bool active) 214 | { 215 | // http://dlnware.com/dll/DlnSpiMasterSetDelayAfterSS 216 | // With a 0ns delay time, the actual delay will be equal to 1/2 of the SPI clock frequency 217 | uint64_t us = div_round_up(dln2_spi_config.freq, 2 * 1000000); 218 | LOG1(" CS=%s wait=%llu\n", active ? "activate" : "deactivate", us); 219 | 220 | if (!active) 221 | sleep_us(us); 222 | 223 | gpio_put(PICO_DEFAULT_SPI_CSN_PIN, !active); 224 | 225 | if (active) 226 | sleep_us(us); 227 | } 228 | 229 | static bool dln2_spi_read_write(struct dln2_slot *slot) 230 | { 231 | struct { 232 | uint8_t port; 233 | uint16_t size; 234 | uint8_t attr; 235 | uint8_t buf[DLN2_SPI_MAX_XFER_SIZE]; 236 | } TU_ATTR_PACKED *cmd = dln2_slot_header_data(slot); 237 | uint8_t attr = cmd->attr; 238 | uint16_t *size = dln2_slot_response_data(slot); 239 | uint8_t *buf = dln2_slot_response_data(slot) + sizeof(*size); 240 | 241 | size_t len = dln2_slot_header_data_size(slot); 242 | if (len < 4) 243 | return dln2_response_error(slot, DLN2_RES_INVALID_COMMAND_SIZE); 244 | 245 | LOG1("DLN2_SPI_READ_WRITE: port=%u size=%u attr=0x%02x\n", cmd->port, cmd->size, cmd->attr); 246 | 247 | if (cmd->port) 248 | return dln2_response_error(slot, DLN2_RES_INVALID_PORT_NUMBER); 249 | if (cmd->size > DLN2_SPI_MAX_XFER_SIZE) 250 | return dln2_response_error(slot, DLN2_RES_BAD_PARAMETER); 251 | if (cmd->size != (len - 4)) 252 | return dln2_response_error(slot, DLN2_RES_INVALID_BUFFER_SIZE); 253 | 254 | dln2_spi_cs_active(true); 255 | 256 | // The buffer addresses are 32-bit aligned and can be used directly. 257 | // It looks like txbuf can move ahead 8 bytes on rxbuf so we can't use buf directly 258 | spi_write_read_blocking(spi_default, cmd->buf, dln2_spi_tmp_buf, cmd->size); 259 | 260 | if (!(attr & DLN2_SPI_ATTR_LEAVE_SS_LOW)) 261 | dln2_spi_cs_active(false); 262 | 263 | memcpy(buf, dln2_spi_tmp_buf, cmd->size); 264 | 265 | return dln2_response(slot, sizeof(uint16_t) + cmd->size); 266 | } 267 | 268 | static bool dln2_spi_read(struct dln2_slot *slot) 269 | { 270 | struct { 271 | uint8_t port; 272 | uint16_t size; 273 | uint8_t attr; 274 | } TU_ATTR_PACKED *cmd = dln2_slot_header_data(slot); 275 | size_t len = cmd->size; 276 | uint8_t attr = cmd->attr; 277 | uint16_t *size = dln2_slot_response_data(slot); 278 | uint8_t *buf = dln2_slot_response_data(slot) + sizeof(*size); 279 | 280 | DLN2_VERIFY_COMMAND_SIZE(slot, sizeof(*cmd)); 281 | LOG1("DLN2_SPI_READ: port=%u size=%zu attr=0x%02x\n", cmd->port, len, cmd->attr); 282 | 283 | if (cmd->port) 284 | return dln2_response_error(slot, DLN2_RES_INVALID_PORT_NUMBER); 285 | if (len > DLN2_SPI_MAX_XFER_SIZE) 286 | return dln2_response_error(slot, DLN2_RES_BAD_PARAMETER); 287 | 288 | put_unaligned_le16(len, size); 289 | 290 | dln2_spi_cs_active(true); 291 | 292 | // The buffer address is 32-bit aligned and can be used directly 293 | spi_read_blocking(spi_default, 0, buf, len); 294 | 295 | if (!(attr & DLN2_SPI_ATTR_LEAVE_SS_LOW)) 296 | dln2_spi_cs_active(false); 297 | 298 | return dln2_response(slot, sizeof(uint16_t) + len); 299 | } 300 | 301 | static bool dln2_spi_write(struct dln2_slot *slot) 302 | { 303 | struct { 304 | uint8_t port; 305 | uint16_t size; 306 | uint8_t attr; 307 | uint8_t buf[DLN2_SPI_MAX_XFER_SIZE]; 308 | } TU_ATTR_PACKED *cmd = dln2_slot_header_data(slot); 309 | 310 | size_t len = dln2_slot_header_data_size(slot); 311 | if (len < 4) 312 | return dln2_response_error(slot, DLN2_RES_INVALID_COMMAND_SIZE); 313 | 314 | LOG1("DLN2_SPI_WRITE: port=%u size=%u attr=0x%02x\n", cmd->port, cmd->size, cmd->attr); 315 | 316 | if (cmd->port) 317 | return dln2_response_error(slot, DLN2_RES_INVALID_PORT_NUMBER); 318 | if (cmd->size > DLN2_SPI_MAX_XFER_SIZE) 319 | return dln2_response_error(slot, DLN2_RES_BAD_PARAMETER); 320 | if (cmd->size != (len - 4)) 321 | return dln2_response_error(slot, DLN2_RES_INVALID_BUFFER_SIZE); 322 | 323 | dln2_spi_cs_active(true); 324 | 325 | // The buffer address is 32-bit aligned and can be used directly 326 | spi_write_blocking(spi_default, cmd->buf, cmd->size); 327 | 328 | if (!(cmd->attr & DLN2_SPI_ATTR_LEAVE_SS_LOW)) 329 | dln2_spi_cs_active(false); 330 | 331 | return dln2_response(slot, 0); 332 | } 333 | 334 | static bool dln2_spi_set_ss(struct dln2_slot *slot) 335 | { 336 | struct { 337 | uint8_t port; 338 | uint8_t cs_mask; 339 | } *cmd = dln2_slot_header_data(slot); 340 | 341 | DLN2_VERIFY_COMMAND_SIZE(slot, sizeof(*cmd)); 342 | 343 | LOG1("DLN2_SPI_SET_SS: port=%u cs_mask=0x%02x\n", cmd->port, cmd->cs_mask); 344 | 345 | if (cmd->port) 346 | return dln2_response_error(slot, DLN2_RES_INVALID_PORT_NUMBER); 347 | 348 | if (cmd->cs_mask & 0xFE != 0xFE) 349 | return dln2_response_error(slot, DLN2_RES_SPI_MASTER_INVALID_SS_VALUE); 350 | 351 | // Nothing to do since there's only one chip select 352 | 353 | return dln2_response(slot, 0); 354 | } 355 | 356 | static bool dln2_spi_ss_multi_enable(struct dln2_slot *slot, bool enable) 357 | { 358 | struct { 359 | uint8_t port; 360 | uint8_t cs_mask; 361 | } *cmd = dln2_slot_header_data(slot); 362 | uint cs = PICO_DEFAULT_SPI_CSN_PIN; 363 | 364 | DLN2_VERIFY_COMMAND_SIZE(slot, sizeof(*cmd)); 365 | 366 | LOG1("%s: port=%u cs_mask=0x%02x\n", enable ? "DLN2_SPI_SS_MULTI_ENABLE" : "DLN2_SPI_SS_MULTI_DISABLE", cmd->port, cmd->cs_mask); 367 | 368 | if (cmd->port) 369 | return dln2_response_error(slot, DLN2_RES_INVALID_PORT_NUMBER); 370 | 371 | if (cmd->cs_mask != 0x01) 372 | return dln2_response_error(slot, DLN2_RES_SPI_MASTER_INVALID_SS_VALUE); 373 | 374 | if (enable) { 375 | int res = dln2_pin_request(cs, DLN2_MODULE_SPI); 376 | if (res) 377 | return dln2_response_error(slot, res); 378 | 379 | gpio_init(cs); 380 | gpio_set_dir(cs, GPIO_OUT); 381 | gpio_put(cs, 1); 382 | } else { 383 | int res = dln2_pin_free(cs, DLN2_MODULE_SPI); 384 | if (res) 385 | return dln2_response_error(slot, res); 386 | 387 | gpio_set_function(cs, GPIO_FUNC_NULL); 388 | } 389 | 390 | return dln2_response(slot, 0); 391 | } 392 | 393 | static bool dln2_spi_get_supported_frame_sizes(struct dln2_slot *slot) 394 | { 395 | uint8_t *port = dln2_slot_header_data(slot); 396 | uint8_t *data = dln2_slot_response_data(slot); 397 | int i, j; 398 | 399 | LOG1("DLN2_SPI_GET_SUPPORTED_FRAME_SIZES: port=%u\n", *port); 400 | DLN2_VERIFY_COMMAND_SIZE(slot, sizeof(*port)); 401 | 402 | if (*port) 403 | return dln2_response_error(slot, DLN2_RES_INVALID_PORT_NUMBER); 404 | 405 | memset(data, 0, 1 + 36); 406 | j = 1; 407 | for (i = 4; i <= 16; i++) 408 | data[j++] = i; 409 | data[0] = j - 1; 410 | 411 | return dln2_response(slot, 1 + 36); 412 | } 413 | 414 | static bool dln2_spi_get_ss_count(struct dln2_slot *slot) 415 | { 416 | uint8_t *port = dln2_slot_header_data(slot); 417 | 418 | LOG1("DLN2_SPI_GET_SS_COUNT: port=%u\n", *port); 419 | DLN2_VERIFY_COMMAND_SIZE(slot, sizeof(*port)); 420 | 421 | if (*port) 422 | return dln2_response_error(slot, DLN2_RES_INVALID_PORT_NUMBER); 423 | 424 | // set defaults 425 | dln2_spi_config.freq = DLN2_SPI_DEFAULT_FREQUENCY; 426 | dln2_spi_config.bpw = 8; 427 | 428 | return dln2_response_u16(slot, 1); 429 | } 430 | 431 | static uint dln2_spi_get_frequency(struct dln2_slot *slot, uint32_t freq) 432 | { 433 | struct dln2_header *hdr = dln2_slot_header(slot); 434 | uint8_t *port = dln2_slot_header_data(slot); 435 | 436 | LOG1("%s: port=%u freq=%u\n", 437 | hdr->id == DLN2_SPI_GET_MIN_FREQUENCY ? "DLN2_SPI_GET_MIN_FREQUENCY" : "DLN2_SPI_GET_MAX_FREQUENCY" , *port, freq); 438 | DLN2_VERIFY_COMMAND_SIZE(slot, 1); 439 | 440 | if (*port) 441 | return dln2_response_error(slot, DLN2_RES_INVALID_PORT_NUMBER); 442 | 443 | return dln2_response_u32(slot, freq); 444 | } 445 | 446 | bool dln2_handle_spi(struct dln2_slot *slot) 447 | { 448 | struct dln2_header *hdr = dln2_slot_header(slot); 449 | 450 | switch (hdr->id) { 451 | case DLN2_SPI_ENABLE: 452 | return dln2_spi_enable(slot, true); 453 | case DLN2_SPI_DISABLE: 454 | return dln2_spi_enable(slot, false); 455 | case DLN2_SPI_SET_MODE: 456 | return dln2_spi_set_mode(slot); 457 | case DLN2_SPI_SET_FRAME_SIZE: 458 | return dln2_spi_set_bpw(slot); 459 | case DLN2_SPI_SET_FREQUENCY: 460 | return dln2_spi_set_frequency(slot); 461 | case DLN2_SPI_READ_WRITE: 462 | return dln2_spi_read_write(slot); 463 | case DLN2_SPI_READ: 464 | return dln2_spi_read(slot); 465 | case DLN2_SPI_WRITE: 466 | return dln2_spi_write(slot); 467 | case DLN2_SPI_SET_SS: 468 | return dln2_spi_set_ss(slot); 469 | case DLN2_SPI_SS_MULTI_ENABLE: 470 | return dln2_spi_ss_multi_enable(slot, true); 471 | case DLN2_SPI_SS_MULTI_DISABLE: 472 | return dln2_spi_ss_multi_enable(slot, false); 473 | case DLN2_SPI_GET_SUPPORTED_FRAME_SIZES: 474 | return dln2_spi_get_supported_frame_sizes(slot); 475 | case DLN2_SPI_GET_SS_COUNT: 476 | return dln2_spi_get_ss_count(slot); 477 | case DLN2_SPI_GET_MIN_FREQUENCY: 478 | return dln2_spi_get_frequency(slot, dln2_spi_min_frequency()); 479 | case DLN2_SPI_GET_MAX_FREQUENCY: 480 | return dln2_spi_get_frequency(slot, dln2_spi_max_frequency()); 481 | default: 482 | LOG1("SPI: unknown command 0x%02x\n", hdr->id); 483 | return dln2_response_error(slot, DLN2_RES_COMMAND_NOT_SUPPORTED); 484 | } 485 | } 486 | -------------------------------------------------------------------------------- /dln2.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: CC0-1.0 2 | /* 3 | * Written in 2021 by Noralf Trønnes 4 | * 5 | * To the extent possible under law, the author(s) have dedicated all copyright and related and 6 | * neighboring rights to this software to the public domain worldwide. This software is 7 | * distributed without any warranty. 8 | * 9 | * You should have received a copy of the CC0 Public Domain Dedication along with this software. 10 | * If not, see . 11 | */ 12 | 13 | #include "device/usbd_pvt.h" 14 | #include "pico/unique_id.h" 15 | #include "dln2.h" 16 | 17 | #define LOG1 //printf 18 | #define LOG2 //printf 19 | 20 | #define DLN2_GENERIC_CMD(cmd) DLN2_CMD(cmd, DLN2_MODULE_GENERIC) 21 | 22 | #define DLN2_CMD_GET_DEVICE_VER DLN2_GENERIC_CMD(0x30) 23 | #define DLN2_CMD_GET_DEVICE_SN DLN2_GENERIC_CMD(0x31) 24 | 25 | #define DLN2_HW_ID 0x200 26 | 27 | static uint8_t dln2_rhport; 28 | static uint8_t dln2_ep_in; 29 | static uint8_t dln2_ep_out; 30 | 31 | static struct dln2_slot dln2_slots[DLN2_MAX_SLOTS]; 32 | static struct dln2_slot_queue dln2_slots_free; 33 | static struct dln2_slot_queue dln2_response_queue; 34 | static struct dln2_slot *dln2_slot_out; 35 | static struct dln2_slot *dln2_slot_in; 36 | 37 | static void dln2_slot_enqueue(struct dln2_slot_queue *queue, struct dln2_slot *slot) 38 | { 39 | slot->next = NULL; 40 | 41 | if (!queue->head) { 42 | queue->head = slot; 43 | return; 44 | } 45 | 46 | struct dln2_slot *cursor = queue->head; 47 | while (cursor->next) 48 | cursor = cursor->next; 49 | cursor->next = slot; 50 | } 51 | 52 | static struct dln2_slot *dln2_slot_dequeue(struct dln2_slot_queue *queue) 53 | { 54 | if (!queue->head) 55 | return NULL; 56 | 57 | struct dln2_slot *slot = queue->head; 58 | queue->head = queue->head->next; 59 | slot->next = NULL; 60 | return slot; 61 | } 62 | 63 | static void dln2_slots_init(void) 64 | { 65 | dln2_slots_free.head = NULL; 66 | dln2_response_queue.head = NULL; 67 | dln2_slot_out = NULL; 68 | dln2_slot_in = NULL; 69 | 70 | for (uint i = 0; i < DLN2_MAX_SLOTS; i++) { 71 | struct dln2_slot *slot = &dln2_slots[i]; 72 | slot->index = i; 73 | slot->len = 0; 74 | dln2_slot_header(slot)->handle = DLN2_HANDLE_UNUSED; 75 | dln2_slot_enqueue(&dln2_slots_free, slot); 76 | } 77 | } 78 | 79 | static const char *dln2_handle_names[] = { 80 | [DLN2_HANDLE_EVENT] = "EVENT", 81 | [DLN2_HANDLE_CTRL] = "CTRL", 82 | [DLN2_HANDLE_GPIO] = "GPIO", 83 | [DLN2_HANDLE_I2C] = "I2C", 84 | [DLN2_HANDLE_SPI] = "SPI", 85 | [DLN2_HANDLE_ADC] = "ADC", 86 | }; 87 | 88 | void _dln2_print_slot(struct dln2_slot *slot, uint indent, const char *caller) 89 | { 90 | struct dln2_header *hdr = dln2_slot_header(slot); 91 | 92 | const char *name = "UNKNOWN"; 93 | if (hdr->handle < DLN2_HANDLES) 94 | name = dln2_handle_names[hdr->handle]; 95 | else if (hdr->handle == DLN2_HANDLE_UNUSED) 96 | name = "UNUSED"; 97 | 98 | if (indent) 99 | LOG1("%*s", indent, ""); 100 | if (caller) 101 | LOG1("%s: ", caller); 102 | LOG1("[%u]: handle=%s[%u] id=%u size=%u echo=%u: len=%zu\n", 103 | slot->index, name, hdr->handle, hdr->id, hdr->size, hdr->echo, slot->len); 104 | } 105 | 106 | static struct dln2_slot *dln2_slot_print_queue(struct dln2_slot_queue *queue) 107 | { 108 | for (struct dln2_slot *slot = queue->head; slot; slot = slot->next) 109 | _dln2_print_slot(slot, 4, NULL); 110 | } 111 | 112 | struct dln2_slot *dln2_get_slot(void) 113 | { 114 | return dln2_slot_dequeue(&dln2_slots_free); 115 | } 116 | 117 | static void dln2_put_slot(struct dln2_slot *slot) 118 | { 119 | dln2_print_slot(slot); 120 | memset(slot->data, 0, DLN2_BUF_SIZE); 121 | dln2_slot_header(slot)->handle = DLN2_HANDLE_UNUSED; 122 | slot->len = 0; 123 | dln2_slot_enqueue(&dln2_slots_free, slot); 124 | } 125 | 126 | static void dln2_queue_slot_out(void) 127 | { 128 | struct dln2_slot *slot = dln2_get_slot(); 129 | if (!slot) { 130 | LOG1("Run out of slots!\n"); 131 | return; 132 | } 133 | 134 | dln2_print_slot(slot); 135 | 136 | bool ret = usbd_edpt_xfer(dln2_rhport, dln2_ep_out, slot->data, CFG_DLN2_BULK_ENPOINT_SIZE); 137 | if (!ret) { 138 | dln2_put_slot(slot); 139 | return; 140 | } 141 | 142 | dln2_slot_out = slot; 143 | } 144 | 145 | bool dln2_init(uint8_t rhport, uint8_t ep_out, uint8_t ep_in) 146 | { 147 | dln2_rhport = rhport; 148 | dln2_ep_out = ep_out; 149 | dln2_ep_in = ep_in; 150 | 151 | dln2_slots_init(); 152 | dln2_queue_slot_out(); 153 | 154 | return true; 155 | } 156 | 157 | static void dln2_slot_in_xfer(void) 158 | { 159 | if (dln2_slot_in) 160 | return; 161 | 162 | LOG2("%s:\n", __func__); 163 | 164 | struct dln2_slot *slot = dln2_slot_dequeue(&dln2_response_queue); 165 | if (!slot) 166 | return; 167 | 168 | struct dln2_response *response = dln2_slot_response(slot); 169 | bool ret = usbd_edpt_xfer(dln2_rhport, dln2_ep_in, slot->data, response->hdr.size); 170 | if (!ret) { 171 | dln2_put_slot(slot); 172 | return; 173 | } 174 | 175 | dln2_slot_in = slot; 176 | } 177 | 178 | // Host IN 179 | void dln2_queue_slot_in(struct dln2_slot *slot) 180 | { 181 | dln2_slot_enqueue(&dln2_response_queue, slot); 182 | dln2_slot_in_xfer(); 183 | } 184 | 185 | static bool _dln2_response(struct dln2_slot *slot, size_t len, uint16_t result) 186 | { 187 | LOG2("%s: len=%zu result=%u\n", __func__, len, result); 188 | 189 | struct dln2_response *response = dln2_slot_response(slot); 190 | response->hdr.size = sizeof(*response) + len; 191 | response->result = result; 192 | 193 | dln2_queue_slot_in(slot); 194 | 195 | return true; 196 | } 197 | 198 | bool dln2_response(struct dln2_slot *slot, size_t len) 199 | { 200 | return _dln2_response(slot, len, 0); 201 | } 202 | 203 | bool dln2_response_u8(struct dln2_slot *slot, uint8_t val) 204 | { 205 | memcpy(dln2_slot_response_data(slot), &val, sizeof(val)); 206 | return _dln2_response(slot, sizeof(val), 0); 207 | } 208 | 209 | bool dln2_response_u16(struct dln2_slot *slot, uint16_t val) 210 | { 211 | memcpy(dln2_slot_response_data(slot), &val, sizeof(val)); 212 | return _dln2_response(slot, sizeof(val), 0); 213 | } 214 | 215 | bool dln2_response_u32(struct dln2_slot *slot, uint32_t val) 216 | { 217 | memcpy(dln2_slot_response_data(slot), &val, sizeof(val)); 218 | return _dln2_response(slot, sizeof(val), 0); 219 | } 220 | 221 | bool dln2_response_error(struct dln2_slot *slot, uint16_t result) 222 | { 223 | LOG1("%s: handle=%u: result=0x%x (%u)\n", __func__, dln2_slot_header(slot)->handle, result, result); 224 | return _dln2_response(slot, 0, result); 225 | } 226 | 227 | static bool dln2_handle_ctrl(struct dln2_slot *slot) 228 | { 229 | struct dln2_header *hdr = dln2_slot_header(slot); 230 | size_t len = dln2_slot_header_data_size(slot); 231 | pico_unique_board_id_t board_id; 232 | uint64_t serial = 0; 233 | 234 | LOG1("DLN2_HANDLE_CTRL:\n"); 235 | 236 | switch (hdr->id) { 237 | case DLN2_CMD_GET_DEVICE_VER: 238 | if (len) 239 | return dln2_response_error(slot, DLN2_RES_INVALID_COMMAND_SIZE); 240 | return dln2_response_u32(slot, DLN2_HW_ID); 241 | case DLN2_CMD_GET_DEVICE_SN: 242 | if (len) 243 | return dln2_response_error(slot, DLN2_RES_INVALID_COMMAND_SIZE); 244 | pico_get_unique_board_id(&board_id); 245 | for (uint i = 0; i < PICO_UNIQUE_BOARD_ID_SIZE_BYTES; i++) { 246 | serial <<= 8; 247 | serial |= board_id.id[i]; 248 | } 249 | return dln2_response_u32(slot, serial); // truncates 250 | default: 251 | return dln2_response_error(slot, DLN2_RES_COMMAND_NOT_SUPPORTED); 252 | } 253 | } 254 | 255 | static bool dln2_handle(struct dln2_slot *slot) 256 | { 257 | struct dln2_header *hdr = dln2_slot_header(slot); 258 | 259 | dln2_print_slot(slot); 260 | 261 | switch (hdr->handle) { 262 | case DLN2_HANDLE_CTRL: 263 | return dln2_handle_ctrl(slot); 264 | case DLN2_HANDLE_GPIO: 265 | return dln2_handle_gpio(slot); 266 | case DLN2_HANDLE_I2C: 267 | return dln2_handle_i2c(slot); 268 | case DLN2_HANDLE_SPI: 269 | return dln2_handle_spi(slot); 270 | case DLN2_HANDLE_ADC: 271 | return dln2_handle_adc(slot); 272 | } 273 | 274 | return dln2_response_error(slot, DLN2_RES_INVALID_HANDLE); 275 | } 276 | 277 | bool dln2_xfer_out(size_t len) 278 | { 279 | LOG2("%s: len=%zu\n", __func__, len); 280 | 281 | struct dln2_slot *slot = dln2_slot_out; 282 | TU_ASSERT(slot); 283 | 284 | dln2_slot_out = NULL; 285 | 286 | struct dln2_header *hdr = dln2_slot_header(slot); 287 | 288 | size_t slot_len = slot->len; 289 | slot->len += len; 290 | if (!slot_len) { 291 | if (len < sizeof(struct dln2_header)) { 292 | dln2_response_error(slot, DLN2_RES_INVALID_MESSAGE_SIZE); 293 | } else if (len < CFG_DLN2_BULK_ENPOINT_SIZE) { 294 | if (hdr->size != len) 295 | dln2_response_error(slot, DLN2_RES_INVALID_MESSAGE_SIZE); 296 | else 297 | dln2_handle(slot); 298 | } else if (len > CFG_DLN2_BULK_ENPOINT_SIZE) { 299 | dln2_response_error(slot, DLN2_RES_FAIL); // shouldn't be possible... 300 | } else if (hdr->size > DLN2_BUF_SIZE) { 301 | dln2_response_error(slot, DLN2_RES_INVALID_MESSAGE_SIZE); 302 | } else if (hdr->size == CFG_DLN2_BULK_ENPOINT_SIZE) { 303 | dln2_handle(slot); 304 | } else { 305 | bool ret = usbd_edpt_xfer(dln2_rhport, dln2_ep_out, 306 | slot->data + CFG_DLN2_BULK_ENPOINT_SIZE, hdr->size - CFG_DLN2_BULK_ENPOINT_SIZE); 307 | if (!ret) { 308 | dln2_response_error(slot, DLN2_RES_FAIL); 309 | } else { 310 | // Wait for the rest of this message 311 | dln2_slot_out = slot; 312 | return true; 313 | } 314 | } 315 | } else { 316 | if (slot->len != hdr->size) 317 | dln2_response_error(slot, DLN2_RES_INVALID_MESSAGE_SIZE); 318 | else 319 | dln2_handle(slot); 320 | } 321 | 322 | dln2_queue_slot_out(); 323 | 324 | return true; 325 | } 326 | 327 | bool dln2_xfer_in(size_t len) 328 | { 329 | LOG2("%s: len=%zu\n", __func__, len); 330 | 331 | struct dln2_slot *slot = dln2_slot_in; 332 | TU_ASSERT(slot); 333 | 334 | dln2_slot_in = NULL; 335 | 336 | //dln2_print_slot(slot); 337 | 338 | struct dln2_response *response = dln2_slot_response(slot); 339 | if (len != response->hdr.size) 340 | LOG1("len != response->hdr.size\n"); 341 | 342 | dln2_put_slot(slot); 343 | 344 | if (!dln2_slot_out) 345 | dln2_queue_slot_out(); 346 | 347 | dln2_slot_in_xfer(); 348 | 349 | return true; 350 | } 351 | -------------------------------------------------------------------------------- /dln2.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: CC0-1.0 2 | /* 3 | * Written in 2021 by Noralf Trønnes 4 | * 5 | * To the extent possible under law, the author(s) have dedicated all copyright and related and 6 | * neighboring rights to this software to the public domain worldwide. This software is 7 | * distributed without any warranty. 8 | * 9 | * You should have received a copy of the CC0 Public Domain Dedication along with this software. 10 | * If not, see . 11 | */ 12 | 13 | #ifndef _DLN2_H_ 14 | #define _DLN2_H_ 15 | 16 | #include 17 | #include 18 | #include "common/tusb_common.h" 19 | 20 | #define DLN2_MODULE_GENERIC 0x00 21 | #define DLN2_MODULE_GPIO 0x01 22 | #define DLN2_MODULE_SPI 0x02 23 | #define DLN2_MODULE_I2C 0x03 24 | #define DLN2_MODULE_ADC 0x06 25 | #define DLN2_MODULE_UART 0x0e 26 | 27 | enum dln2_handle { 28 | DLN2_HANDLE_EVENT = 0, 29 | DLN2_HANDLE_CTRL, 30 | DLN2_HANDLE_GPIO, 31 | DLN2_HANDLE_I2C, 32 | DLN2_HANDLE_SPI, 33 | DLN2_HANDLE_ADC, 34 | DLN2_HANDLES, 35 | DLN2_HANDLE_UNUSED = 0xffff, 36 | }; 37 | 38 | struct dln2_header { 39 | uint16_t size; 40 | uint16_t id; 41 | uint16_t echo; 42 | uint16_t handle; 43 | } TU_ATTR_PACKED; 44 | 45 | struct dln2_response { 46 | struct dln2_header hdr; 47 | uint16_t result; 48 | } TU_ATTR_PACKED; 49 | 50 | #define DLN2_MAX_SLOTS 16 51 | #define DLN2_BUF_SIZE (256 + sizeof(struct dln2_response)) 52 | 53 | struct dln2_slot { 54 | uint8_t data[DLN2_BUF_SIZE]; 55 | uint index; 56 | size_t len; 57 | struct dln2_slot *next; 58 | }; 59 | 60 | struct dln2_slot_queue { 61 | struct dln2_slot *head; 62 | }; 63 | 64 | static inline struct dln2_header *dln2_slot_header(struct dln2_slot *slot) 65 | { 66 | return (struct dln2_header *)slot->data; 67 | } 68 | 69 | static inline void *dln2_slot_header_data(struct dln2_slot *slot) 70 | { 71 | return slot->data + sizeof(struct dln2_header); 72 | } 73 | 74 | static inline size_t dln2_slot_header_data_size(struct dln2_slot *slot) 75 | { 76 | return dln2_slot_header(slot)->size - sizeof(struct dln2_header); 77 | } 78 | 79 | static inline struct dln2_response *dln2_slot_response(struct dln2_slot *slot) 80 | { 81 | return (struct dln2_response *)slot->data; 82 | } 83 | 84 | // Returns an unaligned address 85 | static inline void *dln2_slot_response_data(struct dln2_slot *slot) 86 | { 87 | return slot->data + sizeof(struct dln2_response); 88 | } 89 | 90 | static inline size_t dln2_slot_response_data_size(struct dln2_slot *slot) 91 | { 92 | return dln2_slot_header(slot)->size - sizeof(struct dln2_response); 93 | } 94 | 95 | static inline uint16_t get_unaligned_be16(const void *p) 96 | { 97 | const uint8_t *buf = p; 98 | return (buf[0] << 8) | buf[1]; 99 | } 100 | 101 | static inline void put_unaligned_le16(uint16_t val, void *p) 102 | { 103 | uint8_t *buf = p; 104 | buf[0] = val; 105 | buf[1] = val >> 8; 106 | } 107 | 108 | // http://dlnware.com/dll/Return-Code-Reference 109 | #define DLN2_RES_SUCCESS 0 110 | #define DLN2_RES_FAIL 0x83 111 | #define DLN2_RES_BAD_PARAMETER 0x85 112 | #define DLN2_RES_INVALID_COMMAND_SIZE 0x88 113 | #define DLN2_RES_INVALID_MESSAGE_SIZE 0x8a 114 | #define DLN2_RES_INVALID_HANDLE 0x8f 115 | #define DLN2_RES_NOT_IMPLEMENTED 0x91 116 | #define DLN2_RES_COMMAND_NOT_SUPPORTED DLN2_RES_NOT_IMPLEMENTED 117 | #define DLN2_RES_PIN_IN_USE 0xa5 118 | #define DLN2_RES_INVALID_PORT_NUMBER 0xa8 119 | #define DLN2_RES_INVALID_EVENT_TYPE 0xa9 120 | #define DLN2_RES_PIN_NOT_CONNECTED_TO_MODULE 0xaa 121 | #define DLN2_RES_INVALID_PIN_NUMBER 0xab 122 | #define DLN2_RES_INVALID_EVENT_PERIOD 0xac 123 | #define DLN2_RES_INVALID_BUFFER_SIZE 0xae 124 | #define DLN2_RES_SPI_MASTER_INVALID_SS_VALUE 0xb9 125 | #define DLN2_RES_I2C_MASTER_SENDING_ADDRESS_FAILED 0xba 126 | #define DLN2_RES_I2C_MASTER_SENDING_DATA_FAILED 0xbb 127 | #define DLN2_RES_INVALID_CHANNEL_NUMBER 0xc0 128 | #define DLN2_RES_INVALID_MODE 0xc7 129 | #define DLN2_RES_INVALID_VALUE 0xe2 130 | 131 | #define DLN2_VERIFY_COMMAND_SIZE(_slot, _size) \ 132 | do { \ 133 | size_t len = dln2_slot_header_data_size(_slot); \ 134 | if (len != (_size)) \ 135 | return dln2_response_error((_slot), DLN2_RES_INVALID_COMMAND_SIZE); \ 136 | } while (0) 137 | 138 | #define DLN2_CMD(cmd, id) ((cmd) | ((id) << 8)) 139 | 140 | #define dln2_print_slot(slot) _dln2_print_slot(slot, 0, __func__) 141 | void _dln2_print_slot(struct dln2_slot *slot, uint indent, const char *caller); 142 | 143 | struct dln2_slot *dln2_get_slot(void); 144 | void dln2_queue_slot_in(struct dln2_slot *slot); 145 | 146 | bool dln2_init(uint8_t rhport, uint8_t ep_out, uint8_t ep_in); 147 | bool dln2_xfer_out(size_t len); 148 | bool dln2_xfer_in(size_t len); 149 | 150 | bool dln2_response(struct dln2_slot *slot, size_t len); 151 | bool dln2_response_u8(struct dln2_slot *slot, uint8_t val); 152 | bool dln2_response_u16(struct dln2_slot *slot, uint16_t val); 153 | bool dln2_response_u32(struct dln2_slot *slot, uint32_t val); 154 | bool dln2_response_error(struct dln2_slot *slot, uint16_t result); 155 | 156 | void dln2_pin_set_available(uint32_t mask); 157 | bool dln2_pin_is_requested(uint16_t pin, uint8_t module); 158 | uint16_t dln2_pin_request(uint16_t pin, uint8_t module); 159 | uint16_t dln2_pin_free(uint16_t pin, uint8_t module); 160 | 161 | void dln2_gpio_init(void); 162 | void dln2_gpio_task(void); 163 | bool dln2_handle_gpio(struct dln2_slot *slot); 164 | bool dln2_handle_i2c(struct dln2_slot *slot); 165 | bool dln2_handle_spi(struct dln2_slot *slot); 166 | bool dln2_handle_adc(struct dln2_slot *slot); 167 | 168 | #endif 169 | -------------------------------------------------------------------------------- /driver.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: CC0-1.0 2 | /* 3 | * Written in 2021 by Noralf Trønnes 4 | * 5 | * To the extent possible under law, the author(s) have dedicated all copyright and related and 6 | * neighboring rights to this software to the public domain worldwide. This software is 7 | * distributed without any warranty. 8 | * 9 | * You should have received a copy of the CC0 Public Domain Dedication along with this software. 10 | * If not, see . 11 | */ 12 | 13 | #include "tusb_option.h" 14 | #include "device/usbd_pvt.h" 15 | #include "dln2.h" 16 | 17 | #define LOG1 //printf 18 | #define LOG2 //printf 19 | 20 | static uint8_t _bulk_in; 21 | static uint8_t _bulk_out; 22 | 23 | static void driver_init(void) 24 | { 25 | } 26 | 27 | static void driver_reset(uint8_t rhport) 28 | { 29 | (void) rhport; 30 | } 31 | 32 | static void driver_disable_endpoint(uint8_t rhport, uint8_t *ep_addr) 33 | { 34 | if (*ep_addr) { 35 | usbd_edpt_close(rhport, *ep_addr); 36 | *ep_addr = 0; 37 | } 38 | } 39 | 40 | static uint16_t driver_open(uint8_t rhport, tusb_desc_interface_t const * itf_desc, uint16_t max_len) 41 | { 42 | LOG1("%s: bInterfaceNumber=%u max_len=%u\n", __func__, itf_desc->bInterfaceNumber, max_len); 43 | 44 | TU_VERIFY(TUSB_CLASS_VENDOR_SPECIFIC == itf_desc->bInterfaceClass, 0); 45 | 46 | uint16_t const len = sizeof(tusb_desc_interface_t) + itf_desc->bNumEndpoints * sizeof(tusb_desc_endpoint_t); 47 | TU_VERIFY(max_len >= len, 0); 48 | 49 | uint8_t const * p_desc = tu_desc_next(itf_desc); 50 | TU_ASSERT( usbd_open_edpt_pair(rhport, p_desc, 2, TUSB_XFER_BULK, &_bulk_out, &_bulk_in) ); 51 | 52 | TU_ASSERT ( dln2_init(rhport, _bulk_out, _bulk_in) ); 53 | 54 | LOG2("\n\n\n\n"); 55 | 56 | return len; 57 | } 58 | 59 | static bool driver_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t const * req) 60 | { 61 | return false; 62 | } 63 | 64 | static bool driver_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes) 65 | { 66 | LOG1("\n%s: ep_addr=0x%02x result=%u xferred_bytes=%u\n", __func__, ep_addr, result, xferred_bytes); 67 | 68 | TU_VERIFY(result == XFER_RESULT_SUCCESS); 69 | 70 | if (!xferred_bytes) 71 | LOG2(" ZLP\n"); 72 | 73 | if (ep_addr == _bulk_out) 74 | return dln2_xfer_out(xferred_bytes); 75 | else if (ep_addr == _bulk_in) 76 | return dln2_xfer_in(xferred_bytes); 77 | 78 | return true; 79 | } 80 | 81 | static usbd_class_driver_t const _driver_driver[] = 82 | { 83 | { 84 | #if CFG_TUSB_DEBUG >= 2 85 | .name = "io-board", 86 | #endif 87 | .init = driver_init, 88 | .reset = driver_reset, 89 | .open = driver_open, 90 | .control_xfer_cb = driver_control_xfer_cb, 91 | .xfer_cb = driver_xfer_cb, 92 | .sof = NULL 93 | }, 94 | }; 95 | 96 | usbd_class_driver_t const* usbd_app_driver_get_cb(uint8_t* driver_count) 97 | { 98 | *driver_count += TU_ARRAY_SIZE(_driver_driver); 99 | 100 | return _driver_driver; 101 | } 102 | -------------------------------------------------------------------------------- /i2c-at24-flash.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: CC0-1.0 2 | /* 3 | * Written in 2021 by Noralf Trønnes 4 | * 5 | * To the extent possible under law, the author(s) have dedicated all copyright and related and 6 | * neighboring rights to this software to the public domain worldwide. This software is 7 | * distributed without any warranty. 8 | * 9 | * You should have received a copy of the CC0 Public Domain Dedication along with this software. 10 | * If not, see . 11 | */ 12 | 13 | #include 14 | #include "pico/stdlib.h" 15 | #include "hardware/flash.h" 16 | #include "hardware/sync.h" 17 | #include "i2c-at24.h" 18 | 19 | #define LOG1 //printf 20 | #define LOG2 //printf 21 | 22 | 23 | //#define PICO_FLASH_SIZE_BYTES (2 * 1024 * 1024) 24 | // 25 | //#define FLASH_PAGE_SIZE (1u << 8) 26 | //#define FLASH_SECTOR_SIZE (1u << 12) 27 | //#define FLASH_BLOCK_SIZE (1u << 16) 28 | 29 | 30 | #define AT24_FLASH_SIZE (16 * 1024) 31 | #define AT24_FLASH_START (PICO_FLASH_SIZE_BYTES - AT24_FLASH_SIZE) 32 | #define AT24_FLASH_END PICO_FLASH_SIZE_BYTES 33 | #define AT24_FLASH_SECTOR_SIZE FLASH_SECTOR_SIZE 34 | #define AT24_FLASH_SECTOR_COUNT (AT24_FLASH_SIZE / AT24_FLASH_SECTOR_SIZE) 35 | 36 | #define AT24_FLASH_HEADER_MAGIC 0x224e8d1e 37 | 38 | struct at24_flash_header { 39 | uint32_t magic; 40 | uint64_t wear; 41 | uint64_t version; 42 | uint16_t address; 43 | uint8_t pad_zero[8]; 44 | uint16_t checksum; 45 | } TU_ATTR_PACKED; 46 | 47 | static_assert(sizeof(struct at24_flash_header) == 32, ""); 48 | 49 | #define AT24_FLASH_PAGE_SIZE (AT24_FLASH_SECTOR_SIZE - sizeof(struct at24_flash_header)) 50 | 51 | struct at24_flash_sector { 52 | struct at24_flash_header header; 53 | uint8_t data[AT24_FLASH_PAGE_SIZE]; 54 | }; 55 | 56 | static struct at24_flash_sector write_sector; 57 | static uint32_t allocated_sector_offset; 58 | 59 | static uint32_t flash_sector_index(const void *sector) 60 | { 61 | return ((uint32_t)sector - XIP_BASE - AT24_FLASH_START) / AT24_FLASH_SECTOR_SIZE; 62 | } 63 | 64 | static void flash_print_header(const struct at24_flash_header *hdr) 65 | { 66 | uint8_t pad_zero = 0; 67 | 68 | for (uint i = 0; i < sizeof(hdr->pad_zero); i++) 69 | pad_zero |= hdr->pad_zero[i]; 70 | 71 | LOG1("%u: magic=%s wear=%llu version=%llu address=0x%02x pad_zero=%s checksum=%u\n", 72 | flash_sector_index(hdr), hdr->magic == AT24_FLASH_HEADER_MAGIC ? "yes" : "no", 73 | hdr->wear, hdr->version, hdr->address, pad_zero ? "no" : "yes", hdr->checksum); 74 | } 75 | 76 | static const void *flash_address(uint32_t flash_offs) 77 | { 78 | return (const void *) (XIP_BASE + flash_offs); 79 | } 80 | 81 | static uint16_t flash_header_checksum(const struct at24_flash_header *hdr) 82 | { 83 | uint len = sizeof(*hdr) - sizeof(uint16_t); 84 | uint8_t *buf = (uint8_t *)hdr; 85 | uint16_t sum = 0; 86 | 87 | for (uint i = 0; i < len; i++) 88 | sum += buf[i]; 89 | return sum; 90 | } 91 | 92 | static bool flash_is_valid_sector(const struct at24_flash_sector *sector) 93 | { 94 | const struct at24_flash_header *hdr = §or->header; 95 | uint8_t pad_zero = 0; 96 | 97 | if (hdr->magic != AT24_FLASH_HEADER_MAGIC) 98 | return false; 99 | 100 | if (!hdr->wear || !hdr->version) 101 | return false; 102 | 103 | for (uint i = 0; i < sizeof(hdr->pad_zero); i++) 104 | pad_zero |= hdr->pad_zero[i]; 105 | if (pad_zero) 106 | return false; 107 | 108 | return hdr->checksum == flash_header_checksum(hdr); 109 | } 110 | 111 | #define flash_for_each_sector(_offset, _start_index) \ 112 | for (_offset = AT24_FLASH_START + (_start_index * AT24_FLASH_SECTOR_SIZE); _offset < AT24_FLASH_END; _offset += AT24_FLASH_SECTOR_SIZE) 113 | 114 | static const struct at24_flash_sector *find_flash_sector(uint16_t address) 115 | { 116 | const struct at24_flash_sector *sector, *ret = NULL; 117 | uint64_t version = 0; 118 | uint32_t flash_offs; 119 | 120 | LOG1("%s: address=0x%02x\n", __func__, address); 121 | 122 | flash_for_each_sector(flash_offs, 0) { 123 | const struct at24_flash_sector *sector = flash_address(flash_offs); 124 | 125 | //flash_print_header(§or->header); 126 | 127 | if (!flash_is_valid_sector(sector)) 128 | continue; 129 | 130 | const struct at24_flash_header *hdr = §or->header; 131 | if (hdr->address == address && hdr->version > version) { 132 | version = hdr->version; 133 | ret = sector; 134 | } 135 | } 136 | 137 | return ret; 138 | } 139 | 140 | static uint32_t find_free_flash_sector(uint64_t *wear) 141 | { 142 | const struct at24_flash_sector *sector, *ret = NULL; 143 | uint32_t flash_offs; 144 | 145 | *wear = 0; 146 | 147 | // First see if there's a sector that has never been used 148 | flash_for_each_sector(flash_offs, 0) { 149 | const struct at24_flash_sector *sector = flash_address(flash_offs); 150 | 151 | flash_print_header(§or->header); 152 | 153 | if (!flash_is_valid_sector(sector)) 154 | return flash_offs; 155 | } 156 | 157 | // It's safe to reuse write_sector here 158 | uint16_t *addresses = (uint16_t *)&write_sector; 159 | unsigned int num_addresses = 0; 160 | 161 | // Find all i2c addresses in use 162 | flash_for_each_sector(flash_offs, 0) { 163 | const struct at24_flash_sector *sector = flash_address(flash_offs); 164 | const struct at24_flash_header *hdr = §or->header; 165 | 166 | //flash_print_header(§or->header); 167 | 168 | bool found = false; 169 | for (uint i = 0; i < num_addresses; i++) { 170 | if (addresses[i] == hdr->address) { 171 | found = true; 172 | break; 173 | } 174 | } 175 | 176 | if (!found) 177 | addresses[num_addresses++] = hdr->address; 178 | } 179 | 180 | uint64_t min_wear = ~0; 181 | uint32_t min_wear_flash_offs = 0; 182 | 183 | // Find the sector with the least wear 184 | for (uint i = 0; i < num_addresses; i++) { 185 | uint64_t version = 0; 186 | 187 | LOG1(" try address: 0x%02x\n", addresses[i]); 188 | 189 | // Find the current version so we know which one to ignore 190 | sector = find_flash_sector(addresses[i]); 191 | version = sector->header.version; 192 | LOG1(" version=%llu\n", version); 193 | 194 | // Find the sector with the least wear that is not in use 195 | flash_for_each_sector(flash_offs, 0) { 196 | const struct at24_flash_sector *sector = flash_address(flash_offs); 197 | const struct at24_flash_header *hdr = §or->header; 198 | 199 | //flash_print_header(§or->header); 200 | 201 | if (hdr->address != addresses[i]) 202 | continue; 203 | 204 | if (hdr->version == version) 205 | continue; 206 | 207 | if (hdr->wear < min_wear) { 208 | min_wear = hdr->wear; 209 | min_wear_flash_offs = flash_offs; 210 | } 211 | } 212 | } 213 | 214 | if (min_wear_flash_offs) 215 | *wear = min_wear; 216 | 217 | return min_wear_flash_offs; 218 | } 219 | 220 | static uint32_t alloc_flash_sector(uint64_t *wear) 221 | { 222 | uint32_t flash_offs = find_free_flash_sector(wear); 223 | if (!flash_offs) 224 | return 0; 225 | 226 | uint32_t ints = save_and_disable_interrupts(); 227 | flash_range_erase(flash_offs, AT24_FLASH_SECTOR_SIZE); 228 | restore_interrupts (ints); 229 | 230 | return flash_offs; 231 | } 232 | 233 | static void flash_sync(void) 234 | { 235 | LOG1("FLASH SYNC:\n"); 236 | 237 | LOG1("allocated_sector_offset=0x%x index=%u\n", allocated_sector_offset, (allocated_sector_offset - AT24_FLASH_START) / AT24_FLASH_SECTOR_SIZE); 238 | uint32_t ints = save_and_disable_interrupts(); 239 | flash_range_program(allocated_sector_offset, (uint8_t *)&write_sector, AT24_FLASH_SECTOR_SIZE); 240 | restore_interrupts (ints); 241 | 242 | write_sector.header.address = 0; 243 | allocated_sector_offset = 0; 244 | } 245 | 246 | int i2c_at24_flash_read(const struct i2c_at24_device *at24, uint16_t address, unsigned int offset, void *buf, size_t len) 247 | { 248 | if (write_sector.header.address && write_sector.header.address == address) 249 | flash_sync(); 250 | 251 | const struct at24_flash_sector *sector = find_flash_sector(address); 252 | LOG1("AT24 FLASH READ: sector=%d\n", sector ? flash_sector_index(sector) : -1); 253 | if (!sector) 254 | return 0; 255 | 256 | i2c_at24_memcpy(buf, sector->data, offset, len, AT24_FLASH_PAGE_SIZE); 257 | 258 | return 1; 259 | } 260 | 261 | bool i2c_at24_flash_write(const struct i2c_at24_device *at24, uint16_t address, unsigned int offset, const void *buf, size_t len) 262 | { 263 | uint16_t writing_address = write_sector.header.address; 264 | 265 | if (writing_address && writing_address != address) 266 | return false; 267 | 268 | if (!writing_address) { 269 | uint64_t wear, version; 270 | 271 | LOG1("%s: address=0x%02x\n", __func__, address); 272 | allocated_sector_offset = alloc_flash_sector(&wear); 273 | LOG1("allocated_sector_offset=0x%x index=%u\n", allocated_sector_offset, (allocated_sector_offset - AT24_FLASH_START) / AT24_FLASH_SECTOR_SIZE); 274 | if (!allocated_sector_offset) 275 | return false; 276 | 277 | const struct at24_flash_sector *sector = find_flash_sector(address); 278 | if (sector) { 279 | LOG1("%s: Replacing sector %u\n", __func__, flash_sector_index(sector)); 280 | flash_print_header(§or->header); 281 | version = sector->header.version + 1; 282 | memcpy(write_sector.data, sector->data, AT24_FLASH_PAGE_SIZE); 283 | } else { 284 | LOG1("%s: First version for this address\n", __func__); 285 | version = 1; 286 | memset(write_sector.data, 0xff, sizeof(write_sector.data)); 287 | if (at24->initial_data && at24->initial_data_size && at24->initial_data_size <= sizeof(write_sector.data)) 288 | memcpy(write_sector.data, at24->initial_data, at24->initial_data_size); 289 | } 290 | 291 | struct at24_flash_header *hdr = &write_sector.header; 292 | hdr->magic = AT24_FLASH_HEADER_MAGIC; 293 | hdr->wear = wear + 1; 294 | hdr->version = version; 295 | hdr->address = address; 296 | memset(hdr->pad_zero, 0, sizeof(hdr->pad_zero)); 297 | hdr->checksum = flash_header_checksum(hdr); 298 | } 299 | 300 | memcpy(write_sector.data + offset, buf, len); 301 | 302 | if (offset + len == AT24_FLASH_SECTOR_SIZE) 303 | flash_sync(); 304 | 305 | return true; 306 | } 307 | -------------------------------------------------------------------------------- /i2c-at24.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: CC0-1.0 2 | /* 3 | * Written in 2021 by Noralf Trønnes 4 | * 5 | * To the extent possible under law, the author(s) have dedicated all copyright and related and 6 | * neighboring rights to this software to the public domain worldwide. This software is 7 | * distributed without any warranty. 8 | * 9 | * You should have received a copy of the CC0 Public Domain Dedication along with this software. 10 | * If not, see . 11 | */ 12 | 13 | #include 14 | #include "i2c-at24.h" 15 | 16 | #define LOG1 //printf 17 | #define LOG2 //printf 18 | 19 | void i2c_at24_memcpy(void *dst, const void *src, unsigned int offset, size_t len, size_t max_len) 20 | { 21 | size_t cpy, fill; 22 | 23 | if (max_len <= offset) { 24 | cpy = 0; 25 | fill = len; 26 | } else if (max_len >= offset + len) { 27 | cpy = len; 28 | fill = 0; 29 | } else { 30 | cpy = max_len - offset; 31 | fill = len - cpy; 32 | } 33 | 34 | memcpy(dst, src + offset, cpy); 35 | memset(dst + cpy, 0xff, fill); 36 | } 37 | 38 | bool i2c_at24_read(const struct dln2_i2c_device *dev, uint16_t address, void *buf, size_t len) 39 | { 40 | struct i2c_at24_device *at24 = (struct i2c_at24_device *)dev; 41 | unsigned int offset = at24->data->offset; 42 | size_t cpy, fill; 43 | 44 | LOG1("0x%02x: AT24 READ %zu@%u\n", address, len, offset); 45 | 46 | if (!len) 47 | return true; 48 | 49 | if (offset + len > at24->size) { 50 | LOG1("AT24 READ WRAP AROUND NOT IMPL.\n"); 51 | return false; 52 | } 53 | 54 | int ret = i2c_at24_flash_read(at24, address, offset, buf, len); 55 | if (ret < 0) 56 | return false; 57 | at24->data->offset += len; 58 | if (ret > 0) 59 | return true; 60 | 61 | TU_ASSERT(at24->initial_data && at24->initial_data_size); 62 | i2c_at24_memcpy(buf, at24->initial_data, offset, len, at24->initial_data_size); 63 | 64 | return true; 65 | } 66 | 67 | bool i2c_at24_write(const struct dln2_i2c_device *dev, uint16_t address, const void *buf, size_t len) 68 | { 69 | struct i2c_at24_device *at24 = (struct i2c_at24_device *)dev; 70 | unsigned int offset; 71 | 72 | LOG2("%s: address=0x%02x len=%zu\n", __func__, address, len); 73 | 74 | if (len < at24->addr_size) { 75 | LOG1("AT24 SHORT WRITE len=%zu\n", len); 76 | return false; 77 | } 78 | 79 | if (at24->addr_size == 1) 80 | offset = *(uint8_t *)buf; 81 | else if (at24->addr_size == 2) 82 | offset = get_unaligned_be16(buf); 83 | else 84 | return false; 85 | 86 | if (offset >= at24->size) { 87 | LOG1("0x%02x: AT24 OFFSET=%u TOO LARGE\n", address, offset); 88 | return false; 89 | } 90 | 91 | at24->data->offset = offset; 92 | 93 | if (len == at24->addr_size) { 94 | LOG2("0x%02x: AT24 WRITE OFFSET %u\n", address, offset); 95 | return true; 96 | } 97 | 98 | if (at24->readonly) 99 | return false; 100 | 101 | buf += at24->addr_size; 102 | len -= at24->addr_size; 103 | 104 | if (offset + len > at24->size) { 105 | LOG1("AT24 write WRAP AROUND NOT IMPL.\n"); 106 | return false; 107 | } 108 | 109 | LOG1("0x%02x: AT24 WRITE %zu@%u\n", address, len, offset); 110 | 111 | return i2c_at24_flash_write(at24, address, offset, buf, len); 112 | } 113 | -------------------------------------------------------------------------------- /i2c-at24.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: CC0-1.0 2 | /* 3 | * Written in 2021 by Noralf Trønnes 4 | * 5 | * To the extent possible under law, the author(s) have dedicated all copyright and related and 6 | * neighboring rights to this software to the public domain worldwide. This software is 7 | * distributed without any warranty. 8 | * 9 | * You should have received a copy of the CC0 Public Domain Dedication along with this software. 10 | * If not, see . 11 | */ 12 | 13 | #ifndef _I2C_AT24_H_ 14 | #define _I2C_AT24_H_ 15 | 16 | #include "dln2-devices.h" 17 | 18 | struct i2c_at24_device_data { 19 | unsigned int offset; 20 | }; 21 | 22 | struct i2c_at24_device { 23 | struct dln2_i2c_device base; 24 | struct i2c_at24_device_data *data; 25 | size_t size; 26 | size_t addr_size; 27 | const void *initial_data; 28 | size_t initial_data_size; 29 | bool readonly; 30 | }; 31 | 32 | #define DEFINE_I2C_AT24(_var, _addr, _idata, _isize, _name, _size, _addr_size) \ 33 | struct i2c_at24_device_data _var##_data; \ 34 | struct i2c_at24_device (_var) = { \ 35 | .base = { \ 36 | .name = _name, \ 37 | .address = (_addr), \ 38 | .read = i2c_at24_read, \ 39 | .write = i2c_at24_write, \ 40 | }, \ 41 | .data = &(_var##_data), \ 42 | .size = (_size), \ 43 | .addr_size = (_addr_size), \ 44 | .initial_data = _idata, \ 45 | .initial_data_size = _isize, \ 46 | } 47 | 48 | #define DEFINE_I2C_AT24C32(_var, _addr, _idata, _isize) DEFINE_I2C_AT24(_var, _addr, _idata, _isize, "24c32", 4 * 1024, 2) 49 | //#define DEFINE_I2C_AT24C256(_var, _addr) DEFINE_I2C_AT24(_var, _addr, "24c256", 32 * 1024, 2) 50 | 51 | bool i2c_at24_read(const struct dln2_i2c_device *dev, uint16_t address, void *buf, size_t len); 52 | bool i2c_at24_write(const struct dln2_i2c_device *dev, uint16_t address, const void *buf, size_t len); 53 | void i2c_at24_memcpy(void *dst, const void *src, unsigned int offset, size_t len, size_t max_len); 54 | 55 | int i2c_at24_flash_read(const struct i2c_at24_device *at24, uint16_t address, unsigned int offset, void *buf, size_t len); 56 | bool i2c_at24_flash_write(const struct i2c_at24_device *at24, uint16_t address, unsigned int offset, const void *buf, size_t len); 57 | 58 | #endif 59 | -------------------------------------------------------------------------------- /main.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: CC0-1.0 2 | /* 3 | * Written in 2021 by Noralf Trønnes 4 | * 5 | * To the extent possible under law, the author(s) have dedicated all copyright and related and 6 | * neighboring rights to this software to the public domain worldwide. This software is 7 | * distributed without any warranty. 8 | * 9 | * You should have received a copy of the CC0 Public Domain Dedication along with this software. 10 | * If not, see . 11 | */ 12 | 13 | #include "bsp/board.h" 14 | #include "tusb.h" 15 | #include "pico/stdlib.h" 16 | 17 | #include "dln2.h" 18 | #include "cdc-uart.h" 19 | //#include "i2c-at24.h" 20 | 21 | // pico_enable_stdio_uart in CMakeList.txt 22 | #define LOG1 //printf 23 | 24 | /* 25 | * TODO: Remove this when it's decided to drop the emulated eeprom code 26 | * 27 | static const uint8_t eeprom10[] = "HELLO"; 28 | 29 | DEFINE_I2C_AT24C32(eeprom, 0x10, eeprom10, sizeof(eeprom10)); 30 | 31 | struct dln2_i2c_device *i2c_devices[] = { 32 | &eeprom.base, 33 | NULL, 34 | }; 35 | */ 36 | 37 | int main(void) 38 | { 39 | uint32_t unavail_pins = (1 << 29) | /* IP Used in ADC mode (ADC3) to measure VSYS/3 */ 40 | (1 << 24) | /* IP VBUS sense - high if VBUS is present, else low */ 41 | (1 << 23); /* OP Controls the on-board SMPS Power Save pin */ 42 | dln2_pin_set_available(~unavail_pins); 43 | 44 | dln2_gpio_init(); 45 | // dln2_i2c_set_devices(i2c_devices); 46 | 47 | board_init(); 48 | tusb_init(); 49 | 50 | LOG1("\n\n\n\n\nPico I/O Board CFG_TUSB_DEBUG=%u\n", CFG_TUSB_DEBUG); 51 | 52 | while (1) 53 | { 54 | tud_task(); 55 | dln2_gpio_task(); 56 | cdc_uart_task(); 57 | } 58 | 59 | return 0; 60 | } 61 | -------------------------------------------------------------------------------- /tests/gpio_pulse/LICENCE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012-2014 Ben Croston 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 7 | of the Software, and to permit persons to whom the Software is furnished to do 8 | so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | 21 | -------------------------------------------------------------------------------- /tests/gpio_pulse/Makefile: -------------------------------------------------------------------------------- 1 | CC=gcc 2 | CFLAGS=-I. 3 | DEPS = c_gpio.h 4 | OBJ = main.o c_gpio.o 5 | 6 | %.o: %.c $(DEPS) 7 | $(CC) -c -o $@ $< $(CFLAGS) 8 | 9 | gpio_pulse: $(OBJ) 10 | $(CC) -o $@ $^ $(CFLAGS) 11 | -------------------------------------------------------------------------------- /tests/gpio_pulse/README.md: -------------------------------------------------------------------------------- 1 | 2 | gpio_pulse is used by one gpio interrupt test to pulse the gpio as fast as possible. 3 | 4 | c_gpio.h and c_gpio.c is taken from RPi.GPIO: https://sourceforge.net/projects/raspberry-gpio-python/ 5 | -------------------------------------------------------------------------------- /tests/gpio_pulse/c_gpio.c: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2012-2019 Ben Croston 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | this software and associated documentation files (the "Software"), to deal in 6 | the Software without restriction, including without limitation the rights to 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 8 | of the Software, and to permit persons to whom the Software is furnished to do 9 | so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. 21 | */ 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include "c_gpio.h" 30 | 31 | #define BCM2708_PERI_BASE_DEFAULT 0x20000000 32 | #define BCM2709_PERI_BASE_DEFAULT 0x3f000000 33 | #define GPIO_BASE_OFFSET 0x200000 34 | #define FSEL_OFFSET 0 // 0x0000 35 | #define SET_OFFSET 7 // 0x001c / 4 36 | #define CLR_OFFSET 10 // 0x0028 / 4 37 | #define PINLEVEL_OFFSET 13 // 0x0034 / 4 38 | #define EVENT_DETECT_OFFSET 16 // 0x0040 / 4 39 | #define RISING_ED_OFFSET 19 // 0x004c / 4 40 | #define FALLING_ED_OFFSET 22 // 0x0058 / 4 41 | #define HIGH_DETECT_OFFSET 25 // 0x0064 / 4 42 | #define LOW_DETECT_OFFSET 28 // 0x0070 / 4 43 | #define PULLUPDN_OFFSET 37 // 0x0094 / 4 44 | #define PULLUPDNCLK_OFFSET 38 // 0x0098 / 4 45 | 46 | #define PULLUPDN_OFFSET_2711_0 57 47 | #define PULLUPDN_OFFSET_2711_1 58 48 | #define PULLUPDN_OFFSET_2711_2 59 49 | #define PULLUPDN_OFFSET_2711_3 60 50 | 51 | #define PAGE_SIZE (4*1024) 52 | #define BLOCK_SIZE (4*1024) 53 | 54 | static volatile uint32_t *gpio_map; 55 | 56 | void short_wait(void) 57 | { 58 | int i; 59 | 60 | for (i=0; i<150; i++) { // wait 150 cycles 61 | asm volatile("nop"); 62 | } 63 | } 64 | 65 | int setup(void) 66 | { 67 | int mem_fd; 68 | uint8_t *gpio_mem; 69 | uint32_t peri_base = 0; 70 | uint32_t gpio_base; 71 | unsigned char buf[4]; 72 | FILE *fp; 73 | char buffer[1024]; 74 | char hardware[1024]; 75 | int found = 0; 76 | 77 | // try /dev/gpiomem first - this does not require root privs 78 | if ((mem_fd = open("/dev/gpiomem", O_RDWR|O_SYNC)) > 0) 79 | { 80 | if ((gpio_map = (uint32_t *)mmap(NULL, BLOCK_SIZE, PROT_READ|PROT_WRITE, MAP_SHARED, mem_fd, 0)) == MAP_FAILED) { 81 | return SETUP_MMAP_FAIL; 82 | } else { 83 | return SETUP_OK; 84 | } 85 | } 86 | 87 | // revert to /dev/mem method - requires root 88 | 89 | // determine peri_base 90 | if ((fp = fopen("/proc/device-tree/soc/ranges", "rb")) != NULL) { 91 | // get peri base from device tree 92 | fseek(fp, 4, SEEK_SET); 93 | if (fread(buf, 1, sizeof buf, fp) == sizeof buf) { 94 | peri_base = buf[0] << 24 | buf[1] << 16 | buf[2] << 8 | buf[3] << 0; 95 | } 96 | fclose(fp); 97 | } else { 98 | // guess peri base based on /proc/cpuinfo hardware field 99 | if ((fp = fopen("/proc/cpuinfo", "r")) == NULL) 100 | return SETUP_CPUINFO_FAIL; 101 | 102 | while(!feof(fp) && !found && fgets(buffer, sizeof(buffer), fp)) { 103 | sscanf(buffer, "Hardware : %s", hardware); 104 | if (strcmp(hardware, "BCM2708") == 0 || strcmp(hardware, "BCM2835") == 0) { 105 | // pi 1 hardware 106 | peri_base = BCM2708_PERI_BASE_DEFAULT; 107 | found = 1; 108 | } else if (strcmp(hardware, "BCM2709") == 0 || strcmp(hardware, "BCM2836") == 0) { 109 | // pi 2 hardware 110 | peri_base = BCM2709_PERI_BASE_DEFAULT; 111 | found = 1; 112 | } 113 | } 114 | fclose(fp); 115 | if (!found) 116 | return SETUP_NOT_RPI_FAIL; 117 | } 118 | 119 | if (!peri_base) 120 | return SETUP_NOT_RPI_FAIL; 121 | gpio_base = peri_base + GPIO_BASE_OFFSET; 122 | 123 | // mmap the GPIO memory registers 124 | if ((mem_fd = open("/dev/mem", O_RDWR|O_SYNC) ) < 0) 125 | return SETUP_DEVMEM_FAIL; 126 | 127 | if ((gpio_mem = malloc(BLOCK_SIZE + (PAGE_SIZE-1))) == NULL) 128 | return SETUP_MALLOC_FAIL; 129 | 130 | if ((uint32_t)gpio_mem % PAGE_SIZE) 131 | gpio_mem += PAGE_SIZE - ((uint32_t)gpio_mem % PAGE_SIZE); 132 | 133 | if ((gpio_map = (uint32_t *)mmap( (void *)gpio_mem, BLOCK_SIZE, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_FIXED, mem_fd, gpio_base)) == MAP_FAILED) 134 | return SETUP_MMAP_FAIL; 135 | 136 | return SETUP_OK; 137 | } 138 | 139 | void clear_event_detect(int gpio) 140 | { 141 | int offset = EVENT_DETECT_OFFSET + (gpio/32); 142 | int shift = (gpio%32); 143 | 144 | *(gpio_map+offset) |= (1 << shift); 145 | short_wait(); 146 | *(gpio_map+offset) = 0; 147 | } 148 | 149 | int eventdetected(int gpio) 150 | { 151 | int offset, value, bit; 152 | 153 | offset = EVENT_DETECT_OFFSET + (gpio/32); 154 | bit = (1 << (gpio%32)); 155 | value = *(gpio_map+offset) & bit; 156 | if (value) 157 | clear_event_detect(gpio); 158 | return value; 159 | } 160 | 161 | void set_rising_event(int gpio, int enable) 162 | { 163 | int offset = RISING_ED_OFFSET + (gpio/32); 164 | int shift = (gpio%32); 165 | 166 | if (enable) 167 | *(gpio_map+offset) |= 1 << shift; 168 | else 169 | *(gpio_map+offset) &= ~(1 << shift); 170 | clear_event_detect(gpio); 171 | } 172 | 173 | void set_falling_event(int gpio, int enable) 174 | { 175 | int offset = FALLING_ED_OFFSET + (gpio/32); 176 | int shift = (gpio%32); 177 | 178 | if (enable) { 179 | *(gpio_map+offset) |= (1 << shift); 180 | *(gpio_map+offset) = (1 << shift); 181 | } else { 182 | *(gpio_map+offset) &= ~(1 << shift); 183 | } 184 | clear_event_detect(gpio); 185 | } 186 | 187 | void set_high_event(int gpio, int enable) 188 | { 189 | int offset = HIGH_DETECT_OFFSET + (gpio/32); 190 | int shift = (gpio%32); 191 | 192 | if (enable) 193 | *(gpio_map+offset) |= (1 << shift); 194 | else 195 | *(gpio_map+offset) &= ~(1 << shift); 196 | clear_event_detect(gpio); 197 | } 198 | 199 | void set_low_event(int gpio, int enable) 200 | { 201 | int offset = LOW_DETECT_OFFSET + (gpio/32); 202 | int shift = (gpio%32); 203 | 204 | if (enable) 205 | *(gpio_map+offset) |= 1 << shift; 206 | else 207 | *(gpio_map+offset) &= ~(1 << shift); 208 | clear_event_detect(gpio); 209 | } 210 | 211 | void set_pullupdn(int gpio, int pud) 212 | { 213 | // Check GPIO register 214 | int is2711 = *(gpio_map+PULLUPDN_OFFSET_2711_3) != 0x6770696f; 215 | if (is2711) { 216 | // Pi 4 Pull-up/down method 217 | int pullreg = PULLUPDN_OFFSET_2711_0 + (gpio >> 4); 218 | int pullshift = (gpio & 0xf) << 1; 219 | unsigned int pullbits; 220 | unsigned int pull = 0; 221 | switch (pud) { 222 | case PUD_OFF: pull = 0; break; 223 | case PUD_UP: pull = 1; break; 224 | case PUD_DOWN: pull = 2; break; 225 | default: pull = 0; // switch PUD to OFF for other values 226 | } 227 | pullbits = *(gpio_map + pullreg); 228 | pullbits &= ~(3 << pullshift); 229 | pullbits |= (pull << pullshift); 230 | *(gpio_map + pullreg) = pullbits; 231 | } else { 232 | // Legacy Pull-up/down method 233 | int clk_offset = PULLUPDNCLK_OFFSET + (gpio/32); 234 | int shift = (gpio%32); 235 | 236 | if (pud == PUD_DOWN) { 237 | *(gpio_map+PULLUPDN_OFFSET) = (*(gpio_map+PULLUPDN_OFFSET) & ~3) | PUD_DOWN; 238 | } else if (pud == PUD_UP) { 239 | *(gpio_map+PULLUPDN_OFFSET) = (*(gpio_map+PULLUPDN_OFFSET) & ~3) | PUD_UP; 240 | } else { // pud == PUD_OFF 241 | *(gpio_map+PULLUPDN_OFFSET) &= ~3; 242 | } 243 | short_wait(); 244 | *(gpio_map+clk_offset) = 1 << shift; 245 | short_wait(); 246 | *(gpio_map+PULLUPDN_OFFSET) &= ~3; 247 | *(gpio_map+clk_offset) = 0; 248 | } 249 | } 250 | 251 | void setup_gpio(int gpio, int direction, int pud) 252 | { 253 | int offset = FSEL_OFFSET + (gpio/10); 254 | int shift = (gpio%10)*3; 255 | 256 | set_pullupdn(gpio, pud); 257 | if (direction == OUTPUT) 258 | *(gpio_map+offset) = (*(gpio_map+offset) & ~(7< 264 | int gpio_function(int gpio) 265 | { 266 | int offset = FSEL_OFFSET + (gpio/10); 267 | int shift = (gpio%10)*3; 268 | int value = *(gpio_map+offset); 269 | value >>= shift; 270 | value &= 7; 271 | return value; // 0=input, 1=output, 4=alt0 272 | } 273 | 274 | void output_gpio(int gpio, int value) 275 | { 276 | int offset, shift; 277 | 278 | if (value) // value == HIGH 279 | offset = SET_OFFSET + (gpio/32); 280 | else // value == LOW 281 | offset = CLR_OFFSET + (gpio/32); 282 | 283 | shift = (gpio%32); 284 | 285 | *(gpio_map+offset) = 1 << shift; 286 | } 287 | 288 | int input_gpio(int gpio) 289 | { 290 | int offset, value, mask; 291 | 292 | offset = PINLEVEL_OFFSET + (gpio/32); 293 | mask = (1 << gpio%32); 294 | value = *(gpio_map+offset) & mask; 295 | return value; 296 | } 297 | 298 | void cleanup(void) 299 | { 300 | munmap((void *)gpio_map, BLOCK_SIZE); 301 | } 302 | -------------------------------------------------------------------------------- /tests/gpio_pulse/c_gpio.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2012-2015 Ben Croston 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | this software and associated documentation files (the "Software"), to deal in 6 | the Software without restriction, including without limitation the rights to 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 8 | of the Software, and to permit persons to whom the Software is furnished to do 9 | so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. 21 | */ 22 | 23 | int setup(void); 24 | void setup_gpio(int gpio, int direction, int pud); 25 | int gpio_function(int gpio); 26 | void output_gpio(int gpio, int value); 27 | int input_gpio(int gpio); 28 | void set_rising_event(int gpio, int enable); 29 | void set_falling_event(int gpio, int enable); 30 | void set_high_event(int gpio, int enable); 31 | void set_low_event(int gpio, int enable); 32 | int eventdetected(int gpio); 33 | void cleanup(void); 34 | 35 | #define SETUP_OK 0 36 | #define SETUP_DEVMEM_FAIL 1 37 | #define SETUP_MALLOC_FAIL 2 38 | #define SETUP_MMAP_FAIL 3 39 | #define SETUP_CPUINFO_FAIL 4 40 | #define SETUP_NOT_RPI_FAIL 5 41 | 42 | #define INPUT 1 // is really 0 for control register! 43 | #define OUTPUT 0 // is really 1 for control register! 44 | #define ALT0 4 45 | 46 | #define HIGH 1 47 | #define LOW 0 48 | 49 | #define PUD_OFF 0 50 | #define PUD_DOWN 1 51 | #define PUD_UP 2 52 | -------------------------------------------------------------------------------- /tests/gpio_pulse/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "c_gpio.h" 5 | 6 | static int to_int(const char *s) 7 | { 8 | char *t; 9 | long l = strtol(s, &t, 10); 10 | if(s == t) 11 | return -1; 12 | return l; 13 | } 14 | 15 | static void usage(void) 16 | { 17 | printf("Usage: gpio_pulse gpio polarity\n"); 18 | } 19 | 20 | int main(int argc, char **argv) 21 | { 22 | if (argc != 3) { 23 | usage(); 24 | return 1; 25 | } 26 | 27 | int gpio = to_int(argv[1]); 28 | if (gpio < 0) { 29 | usage(); 30 | return 1; 31 | } 32 | 33 | int polarity = to_int(argv[2]); 34 | if (polarity != 0 && polarity != 1) { 35 | usage(); 36 | return 1; 37 | } 38 | 39 | // printf("gpio=%d polarity=%d\n", gpio, polarity); 40 | 41 | int result = setup(); 42 | if (result != SETUP_OK) { 43 | fprintf(stderr, "setup() failed: result=%d\n", result); 44 | return 1; 45 | } 46 | 47 | output_gpio(gpio, polarity); 48 | output_gpio(gpio, !polarity); 49 | 50 | cleanup(); 51 | 52 | return 0; 53 | } 54 | -------------------------------------------------------------------------------- /tests/test_adc.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: CC0-1.0 2 | # 3 | # Written in 2021 by Noralf Trønnes 4 | # 5 | # To the extent possible under law, the author(s) have dedicated all copyright and related and 6 | # neighboring rights to this software to the public domain worldwide. This software is 7 | # distributed without any warranty. 8 | # 9 | # You should have received a copy of the CC0 Public Domain Dedication along with this software. 10 | # If not, see . 11 | 12 | import pytest 13 | import sys 14 | import time 15 | import gpiod 16 | import iio 17 | from periphery import PWM 18 | 19 | # there's a 330 ohm resistor connected between gp27 and gp28 20 | channel_gpio_pairs = [(1, 28), (2, 27)] 21 | 22 | adc_max = 1023 # 10-bit 23 | adc_var = adc_max * 0.05 # 5 percent 24 | 25 | class LineContext: 26 | def __init__(self, chip, offset, type): 27 | self.line = chip.get_line(offset) 28 | self.type = type 29 | def __enter__(self): 30 | self.line.request(consumer=sys.argv[0], type=self.type) 31 | return self.line 32 | def __exit__(self, exc_type, exc_value, exc_tb): 33 | self.line.release() 34 | 35 | @pytest.fixture(scope='module') 36 | def _iio_dev(): 37 | ctx = iio.LocalContext() 38 | return ctx.find_device('dln2-adc') 39 | 40 | @pytest.fixture() 41 | def iio_dev(_iio_dev): 42 | for channel in _iio_dev.channels: 43 | channel.enabled = False 44 | return _iio_dev 45 | 46 | @pytest.fixture(scope='module') 47 | def gpio_dev(): 48 | return gpiod.Chip('dln2') 49 | 50 | @pytest.fixture() 51 | def pwm(): 52 | pwm = PWM(0, 0) 53 | pwm.frequency = 1000 54 | pwm.duty_cycle = 1 55 | pwm.enable() 56 | yield pwm 57 | pwm.disable() 58 | 59 | @pytest.mark.parametrize('voltage_div', [i / 10 for i in range(1, 10)]) 60 | def test_adc0(iio_dev, pwm, voltage_div): 61 | pwm.duty_cycle = voltage_div 62 | time.sleep(0.3) 63 | ch = iio_dev.find_channel('voltage0') 64 | val = int(ch.attrs['raw'].value) 65 | #print('val:', val) 66 | nom = adc_max * voltage_div 67 | assert (nom - adc_var) < val < (nom + adc_var) 68 | 69 | # only high,low testing for adc1 and 2 70 | @pytest.mark.parametrize('ch, gpio', channel_gpio_pairs, ids=[f'ch{ch}:{gpio}' for ch, gpio in channel_gpio_pairs]) 71 | def test_gpio_adc1_adc2(iio_dev, gpio_dev, ch, gpio): 72 | channel = iio_dev.find_channel(f'voltage{ch}') 73 | 74 | with LineContext(gpio_dev, gpio, gpiod.LINE_REQ_DIR_OUT) as line: 75 | line.set_value(0) 76 | val = channel.attrs['raw'].value 77 | #print('val:', val) 78 | assert int(val) < adc_var 79 | 80 | line.set_value(1) 81 | val = channel.attrs['raw'].value 82 | #print('val:', val) 83 | assert int(val) > (adc_max - adc_var) 84 | 85 | def test_buffer(iio_dev, gpio_dev, pwm): 86 | pwm.duty_cycle = 0.5 87 | time.sleep(0.3) 88 | 89 | for channel in iio_dev.channels: 90 | channel.enabled = True 91 | buf = iio.Buffer(iio_dev, 1) 92 | if buf is None: 93 | raise Exception("Unable to create buffer!\n") 94 | 95 | buf.refill() 96 | samples = buf.read() 97 | #print('samples:', len(samples), samples) 98 | ch0 = int.from_bytes(samples[:2], byteorder=sys.byteorder) 99 | ch1 = int.from_bytes(samples[2:4], byteorder=sys.byteorder) 100 | ch2 = int.from_bytes(samples[4:6], byteorder=sys.byteorder) 101 | print(f'ch0={ch0} (0x{ch0:04x}), ch1={ch1} (0x{ch1:04x}), ch2={ch2} (0x{ch2:04x})') 102 | 103 | nom = adc_max * 0.5 104 | assert (nom - adc_var) < ch0 < (nom + adc_var) 105 | 106 | time_ns = int.from_bytes(samples[-8:], byteorder=sys.byteorder) 107 | #print('timestamp:', ":".join("{:02x}".format(c) for c in samples[-8:]), time_ns) 108 | 109 | from datetime import datetime 110 | dt = datetime.fromtimestamp(time_ns // 1e9) 111 | print('dt:', dt) 112 | 113 | @pytest.mark.parametrize('ch, gpio', channel_gpio_pairs, ids=[f'ch{ch}:{gpio}' for ch, gpio in channel_gpio_pairs]) 114 | def test_buffer_adc1_adc2(iio_dev, gpio_dev, ch, gpio, pwm): 115 | pwm.duty_cycle = 0.5 116 | time.sleep(0.3) 117 | 118 | channel0 = iio_dev.find_channel(f'voltage0') 119 | channel = iio_dev.find_channel(f'voltage{ch}') 120 | 121 | with LineContext(gpio_dev, gpio, gpiod.LINE_REQ_DIR_OUT) as line: 122 | line.set_value(1) 123 | 124 | channel0.enabled = True 125 | channel.enabled = True 126 | 127 | buf = iio.Buffer(iio_dev, 1) 128 | if buf is None: 129 | raise Exception("Unable to create buffer!\n") 130 | 131 | buf.refill() 132 | samples = buf.read() 133 | channel.enabled = False 134 | 135 | #print('samples:', len(samples), samples) 136 | ch0 = int.from_bytes(samples[:2], byteorder=sys.byteorder) 137 | ch = int.from_bytes(samples[2:4], byteorder=sys.byteorder) 138 | print(f'ch0={ch0} (0x{ch0:04x}), ch={ch} (0x{ch:04x})') 139 | 140 | nom = adc_max * 0.5 141 | assert (nom - adc_var) < ch0 < (nom + adc_var) 142 | assert (adc_max - adc_var) < ch <= adc_max 143 | -------------------------------------------------------------------------------- /tests/test_gpio.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: CC0-1.0 2 | # 3 | # Written in 2021 by Noralf Trønnes 4 | # 5 | # To the extent possible under law, the author(s) have dedicated all copyright and related and 6 | # neighboring rights to this software to the public domain worldwide. This software is 7 | # distributed without any warranty. 8 | # 9 | # You should have received a copy of the CC0 Public Domain Dedication along with this software. 10 | # If not, see . 11 | 12 | import pytest 13 | import gpiod 14 | import sys 15 | import errno 16 | from pathlib import Path 17 | import subprocess 18 | import time 19 | 20 | gpio_unavail = (24, 23) 21 | 22 | # the gpios are connected through a 330 ohm resistor 23 | gpio_pairs = [(2, 3), 24 | (6, 7), 25 | (10, 11), 26 | (12, 13), 27 | (14, 15), 28 | (27, 28), 29 | ] 30 | 31 | gpio_pairs_flipped = [(pair[1], pair[0]) for pair in gpio_pairs] 32 | 33 | gpio_pairs_all = gpio_pairs + gpio_pairs_flipped 34 | 35 | @pytest.fixture(scope='module') 36 | def chip(): 37 | return gpiod.Chip('dln2') 38 | 39 | def gpio_request(chip, gpio, typ): 40 | line = chip.get_line(gpio) 41 | label = Path(sys.argv[0]).name 42 | # TODO: >=v1.5: default_vals deprecated, use default_val=0 43 | line.request(consumer=label, type=typ, default_vals=(0,)) # Set low to match inputs that are pulled low 44 | return line 45 | 46 | def gpio_request_pair(chip, in_gpio, out_gpio, interrupt=False): 47 | #print('gpio_request_pair:', in_gpio, out_gpio) 48 | in_line = gpio_request(chip, in_gpio, gpiod.LINE_REQ_EV_BOTH_EDGES if interrupt else gpiod.LINE_REQ_DIR_IN) 49 | out_line = gpio_request(chip, out_gpio, gpiod.LINE_REQ_DIR_OUT) 50 | return in_line, out_line 51 | 52 | def gpio_in_out(chip, in_gpio, out_gpio): 53 | i, o = gpio_request_pair(chip, in_gpio, out_gpio) 54 | 55 | try: 56 | for out in [0, 1]: 57 | o.set_value(out) 58 | v = i.get_value() 59 | #print('v:', v) 60 | assert v == out 61 | finally: 62 | i.release() 63 | o.release() 64 | 65 | def gpio_interrupt(chip, in_gpio, out_gpio): 66 | i, o = gpio_request_pair(chip, in_gpio, out_gpio, interrupt=True) 67 | 68 | # there's a pull-down on 'i' 69 | 70 | try: 71 | for out in [1, 0]: 72 | o.set_value(out) 73 | assert i.event_wait(1), 'No interrupt received' 74 | event = i.event_read() 75 | #print('event:', event) 76 | if out: 77 | assert event.type == gpiod.LineEvent.RISING_EDGE 78 | else: 79 | assert event.type == gpiod.LineEvent.FALLING_EDGE 80 | finally: 81 | i.release() 82 | o.release() 83 | 84 | @pytest.mark.parametrize('gpio', gpio_unavail) 85 | def test_gpio_unavail(chip, gpio): 86 | line = chip.get_line(gpio) 87 | with pytest.raises(OSError) as excinfo: 88 | line.request(consumer=sys.argv[0], type=gpiod.LINE_REQ_DIR_IN) 89 | assert excinfo.value.errno == errno.EREMOTEIO 90 | 91 | @pytest.mark.parametrize('pair', gpio_pairs_all, ids=[f'{pair[1]}->{pair[0]}' for pair in gpio_pairs_all]) 92 | def test_gpio_set_get(chip, pair): 93 | gpio_in_out(chip, pair[0], pair[1]) 94 | 95 | @pytest.mark.parametrize('pair', gpio_pairs_all, ids=[f'{pair[1]}->{pair[0]}' for pair in gpio_pairs_all]) 96 | def test_gpio_interrupt(chip, pair): 97 | gpio_interrupt(chip, pair[0], pair[1]) 98 | 99 | # Tests using an external source 100 | 101 | # GP22 is connected to GPIO27 on the Pi4 through a 330 ohm resistor 102 | ext_gpio_num = 27 103 | 104 | @pytest.fixture(scope='module') 105 | def ext_chip(): 106 | return gpiod.Chip('pinctrl-bcm2711') 107 | 108 | @pytest.fixture 109 | def ext_gpio(ext_chip): 110 | line = gpio_request(ext_chip, ext_gpio_num, gpiod.LINE_REQ_DIR_OUT) 111 | yield line 112 | line.release() 113 | 114 | def test_gpio_gp22_set_get(chip, ext_gpio): 115 | line = gpio_request(chip, 22, gpiod.LINE_REQ_DIR_IN) 116 | 117 | try: 118 | for out in [0, 1]: 119 | ext_gpio.set_value(out) 120 | v = line.get_value() 121 | #print('v:', v) 122 | assert v == out 123 | finally: 124 | line.release() 125 | 126 | def assert_event(line, expected): 127 | assert line.event_wait(1), f'Missing interrupt for event={expected}' 128 | event = line.event_read() 129 | #print('event:', event) 130 | assert event.type == expected 131 | assert not line.event_wait(1), 'Too many interrupts' 132 | 133 | edges = (gpiod.LINE_REQ_EV_BOTH_EDGES, gpiod.LINE_REQ_EV_RISING_EDGE, gpiod.LINE_REQ_EV_FALLING_EDGE) 134 | edges_ids = ['BOTH', 'RISING', 'FALLING'] 135 | 136 | @pytest.mark.parametrize('edge', edges, ids=edges_ids) 137 | def test_gpio_gp22_interrupt(chip, ext_gpio, edge): 138 | line = gpio_request(chip, 22, edge) 139 | 140 | try: 141 | ext_gpio.set_value(1) 142 | if edge in (gpiod.LINE_REQ_EV_BOTH_EDGES, gpiod.LINE_REQ_EV_RISING_EDGE): 143 | assert_event(line, gpiod.LineEvent.RISING_EDGE) 144 | else: 145 | assert not line.event_wait(1), 'Should not have received interrupt' 146 | 147 | ext_gpio.set_value(0) 148 | if edge in (gpiod.LINE_REQ_EV_BOTH_EDGES, gpiod.LINE_REQ_EV_FALLING_EDGE): 149 | assert_event(line, gpiod.LineEvent.FALLING_EDGE) 150 | else: 151 | assert not line.event_wait(1), 'Should not have received interrupt' 152 | finally: 153 | line.release() 154 | 155 | def run_gpio_pulse(req): 156 | prog = Path(req.node.fspath).parent.joinpath('gpio_pulse/gpio_pulse') 157 | if not prog.is_file(): 158 | pytest.skip('gpio_pulse is not built') 159 | res = subprocess.run([prog, str(ext_gpio_num), '1']) 160 | if res.returncode: 161 | print(res) 162 | print(res.stdout) 163 | assert res.returncode == 0 164 | 165 | # Pulse the gpio so fast that the Pico detects both a fall and a rise event before its interrupt routine is called 166 | @pytest.mark.parametrize('edge', edges, ids=edges_ids) 167 | def test_gpio_gp22_interrupt_short_pulse(request, chip, ext_gpio, edge): 168 | line = gpio_request(chip, 22, edge) 169 | 170 | try: 171 | run_gpio_pulse(request) 172 | assert not line.event_wait(1), 'Should not have received interrupt' 173 | finally: 174 | line.release() 175 | 176 | def count_interrupts(in_line, out_line, delay, num): 177 | for i in range(num): 178 | out_line.set_value(1) 179 | time.sleep(delay) 180 | out_line.set_value(0) 181 | time.sleep(delay) 182 | 183 | count = 0 184 | while True: 185 | if not in_line.event_wait(1): 186 | break 187 | event = in_line.event_read() 188 | #print('event:', event) 189 | count += 1 190 | return count 191 | 192 | @pytest.mark.parametrize('edge', edges, ids=edges_ids) 193 | def test_gpio_gp22_interrupt_multiple(chip, ext_gpio, edge): 194 | line = gpio_request(chip, 22, edge) 195 | 196 | try: 197 | # kernel event fifo size is 16, more than that and they will be dropped 198 | count = count_interrupts(line, ext_gpio, 10 / 1000, 8) 199 | if edge == gpiod.LINE_REQ_EV_BOTH_EDGES: 200 | assert count == 16 201 | else: 202 | assert count == 8 203 | finally: 204 | line.release() 205 | 206 | # Make sure the device runs out of slots and the events array is maxed out, then make sure it has recovered 207 | def test_gpio_gp22_interrupt_run_out_of_slots(chip, ext_gpio): 208 | line = gpio_request(chip, 22, gpiod.LINE_REQ_EV_RISING_EDGE) 209 | 210 | try: 211 | sleep_us = 1 212 | for i in range(10000): 213 | ext_gpio.set_value(0) 214 | time.sleep(sleep_us / 1000000) 215 | ext_gpio.set_value(1) 216 | time.sleep(sleep_us / 1000000) 217 | 218 | count = 0 219 | while True: 220 | if not line.event_wait(1): 221 | break 222 | event = line.event_read() 223 | #print('event:', event) 224 | count += 1 225 | 226 | #print('count:', count) 227 | assert count > 0 228 | 229 | for val in [0, 1]: 230 | ext_gpio.set_value(val) 231 | assert line.get_value() == val 232 | assert line.event_wait(1) 233 | event = line.event_read() 234 | #print('LAST event:', event) 235 | assert not line.event_wait(1), 'Too many interrupts' 236 | 237 | finally: 238 | line.release() 239 | -------------------------------------------------------------------------------- /tests/test_i2c.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: CC0-1.0 2 | # 3 | # Written in 2021 by Noralf Trønnes 4 | # 5 | # To the extent possible under law, the author(s) have dedicated all copyright and related and 6 | # neighboring rights to this software to the public domain worldwide. This software is 7 | # distributed without any warranty. 8 | # 9 | # You should have received a copy of the CC0 Public Domain Dedication along with this software. 10 | # If not, see . 11 | 12 | import pytest 13 | import errno 14 | from pathlib import Path 15 | import os 16 | import time 17 | 18 | def eeprom(busnum, address): 19 | #print('i2c_busnum:', busnum) 20 | path = Path(f'/sys/class/i2c-adapter/i2c-{busnum}') 21 | 22 | dev = path.joinpath(f'{busnum}-{address:04x}') 23 | if not dev.is_dir(): 24 | with path.joinpath('new_device').open(mode='w') as f: 25 | f.write(f'24c32 0x{address:x}\n') 26 | time.sleep(2) 27 | #print(f'Added 0x{address:x}') 28 | 29 | eeprom = dev.joinpath('eeprom') 30 | if not eeprom.is_file(): 31 | raise OSError(errno.ENOENT, f'No eeprom found: {eeprom}') 32 | 33 | return eeprom 34 | 35 | def eeprom_write_random(path, size): 36 | data = os.urandom(size) 37 | with path.open(mode='wb') as f: 38 | f.write(data) 39 | return data 40 | 41 | def eeprom_read(path): 42 | with path.open(mode='rb') as f: 43 | data = f.read() 44 | return data 45 | 46 | @pytest.fixture(scope='module') 47 | def i2c_busnum(): 48 | adapter_path = Path('/sys/class/i2c-adapter') 49 | for p in adapter_path.iterdir(): 50 | #print(p) 51 | with p.joinpath('name').open() as f: 52 | name = f.read() 53 | #print('name:', name) 54 | if name.startswith('dln2-i2c'): 55 | return int(p.name.split('-')[1]) 56 | 57 | raise OSError(errno.ENODEV, 'No DLN-2 i2c adapter found') 58 | 59 | @pytest.fixture(scope='module') 60 | def eeprom10(i2c_busnum): 61 | return eeprom(i2c_busnum, 0x10) 62 | 63 | @pytest.fixture(scope='module') 64 | def eeprom50(i2c_busnum): 65 | return eeprom(i2c_busnum, 0x50) 66 | 67 | @pytest.mark.skip() 68 | def test_eeprom10(eeprom10): 69 | #print('eeprom10:', eeprom10) 70 | # The last block (32 bytes) is missing (used for an internal header), read as 0xff 71 | print('write') 72 | data = eeprom_write_random(eeprom10, 4 * 1024 - 32) 73 | print('read') 74 | actual = eeprom_read(eeprom10) 75 | assert len(actual) - 32 == len(data) 76 | assert actual[:-32] == data 77 | assert actual[-32:] == b'\xff' * 32 78 | 79 | def test_eeprom50(eeprom50): 80 | #print('eeprom50:', eeprom50) 81 | print('write') 82 | data = eeprom_write_random(eeprom50, 4 * 1024) 83 | print('read') 84 | actual = eeprom_read(eeprom50) 85 | assert len(actual) == len(data) 86 | assert actual == data 87 | -------------------------------------------------------------------------------- /tests/test_spi.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: CC0-1.0 2 | # 3 | # Written in 2021 by Noralf Trønnes 4 | # 5 | # To the extent possible under law, the author(s) have dedicated all copyright and related and 6 | # neighboring rights to this software to the public domain worldwide. This software is 7 | # distributed without any warranty. 8 | # 9 | # You should have received a copy of the CC0 Public Domain Dedication along with this software. 10 | # If not, see . 11 | 12 | import pytest 13 | import errno 14 | from pathlib import Path 15 | import os 16 | import re 17 | 18 | def eeprom_write_random(path, size): 19 | data = os.urandom(size) 20 | with path.open(mode='wb') as f: 21 | f.write(data) 22 | return data 23 | 24 | def eeprom_read(path): 25 | with path.open(mode='rb') as f: 26 | data = f.read() 27 | return data 28 | 29 | @pytest.fixture(scope='module') 30 | def spi_busnum(): 31 | path = Path('/sys/class/spi_master') 32 | regex = re.compile(r'\d+') 33 | for p in path.iterdir(): 34 | #print(p) 35 | with p.joinpath('device/modalias').open() as f: 36 | alias = f.read() 37 | #print('alias:', alias) 38 | if 'dln2-spi' in alias: 39 | return int(regex.search(p.name).group(0)) 40 | 41 | raise OSError(errno.ENODEV, 'No DLN-2 SPI adapter found') 42 | 43 | @pytest.fixture(scope='module') 44 | def eeprom0(spi_busnum): 45 | #print('spi_busnum:', spi_busnum) 46 | cs = 0 47 | return Path(f'/sys/bus/spi/devices/spi{spi_busnum}.{cs}/eeprom') 48 | 49 | def test_eeprom(eeprom0): 50 | print(eeprom0) 51 | print('write') 52 | data = eeprom_write_random(eeprom0, 65536) 53 | print('read') 54 | actual = eeprom_read(eeprom0) 55 | print('done') 56 | assert len(actual) == len(data) 57 | # Do a quick test first, the diff generation for the whole buffer is insanly slow if it differs. 58 | assert actual[:16] == data[:16] 59 | assert actual == data 60 | -------------------------------------------------------------------------------- /tests/test_uart.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: CC0-1.0 2 | # 3 | # Written in 2023 by Noralf Trønnes 4 | # 5 | # To the extent possible under law, the author(s) have dedicated all copyright and related and 6 | # neighboring rights to this software to the public domain worldwide. This software is 7 | # distributed without any warranty. 8 | # 9 | # You should have received a copy of the CC0 Public Domain Dedication along with this software. 10 | # If not, see . 11 | 12 | import pytest 13 | import itertools 14 | import os 15 | from pathlib import Path 16 | import serial 17 | 18 | @pytest.fixture(scope='module') 19 | def uarts(): 20 | path = Path('/dev/serial/by-id') 21 | found = [] 22 | for p in path.iterdir(): 23 | if not 'Raspberry_Pi_Pico_USB_I_O_Board' in str(p): 24 | continue 25 | found.append(p.resolve()) 26 | 27 | if len(found) != 2: 28 | raise OSError(errno.ENODEV, f'Found {len(data)} DLN-2 UARTs, expected 2') 29 | return sorted(found) 30 | 31 | import time 32 | 33 | def assert_received(send, receive, data, bytesize): 34 | if bytesize in (5, 6, 7): 35 | mask = (0x1f, 0x3f, 0x7f)[bytesize - 5] 36 | data = bytes([d & mask for d in data]) 37 | send.write(data) 38 | received = receive.read(len(data)) 39 | assert data == received 40 | 41 | 42 | data_set_short = [ 43 | b'A', 44 | b'B' * 2, 45 | b'C' * 3, 46 | b'D' * 4, 47 | b'E' * 5, 48 | b'\0', 49 | b'\n', 50 | b'\r', 51 | b'\n\r', 52 | b'\r\n', 53 | b'Hello World', 54 | ] 55 | data_set_short_ids = [f'data{i}' for i in range(len(data_set_short))] 56 | 57 | data_set_long_sizes = itertools.chain.from_iterable((2**i - 1, 2**i, 2**i + 1) for i in range(3, 13)) # 7,8,9,15,16,17,...,4095,4096,4097 58 | data_set_long = [os.urandom(size) for size in data_set_long_sizes] 59 | data_set_long_ids = [str(len(data)) for data in data_set_long] 60 | 61 | 62 | @pytest.mark.parametrize('stopbits', [1, ]) # many test are failing when using 2 stopbits 63 | @pytest.mark.parametrize('parity', [serial.PARITY_NONE, ]) # rpi serial0 doesn't support EVEN and ODD 64 | @pytest.mark.parametrize('bytesize', [7, 8]) # rpi serial0 doesn't support 6 bits, 5 bits fails in many of the tests 65 | # No point in testing lots of baudrates since the device "supports" everything, 66 | # it just picks the closest it can do. Testing high speed is important to verify 67 | # that the RX FIFO doesn't overflow. 68 | @pytest.mark.parametrize('baudrate', [9600, 19200, 115200, 576000, 1152000]) 69 | class TestUart0: 70 | def connection(self, uarts, baudrate, bytesize, parity, stopbits, size): 71 | tty_uart0, tty_uart1 = uarts 72 | paritybits = 0 if parity == serial.PARITY_NONE else 1 73 | timeout = ((1 + bytesize + paritybits + stopbits) * size / baudrate) + 1.0 74 | uart0 = serial.Serial(str(tty_uart0), baudrate=baudrate, bytesize=bytesize, parity=parity, stopbits=stopbits, timeout=timeout) 75 | ser0 = serial.Serial('/dev/serial0', baudrate=baudrate, bytesize=bytesize, parity=parity, stopbits=stopbits, timeout=timeout) 76 | return ser0, uart0 77 | 78 | @pytest.mark.parametrize('data', data_set_short, ids=data_set_short_ids) 79 | def test_read_short(self, uarts, baudrate, bytesize, parity, stopbits, data): 80 | ser0, uart0 = self.connection(uarts, baudrate, bytesize, parity, stopbits, len(data)) 81 | assert_received(ser0, uart0, data, bytesize) 82 | 83 | @pytest.mark.parametrize('data', data_set_short, ids=data_set_short_ids) 84 | def test_write_short(self, uarts, baudrate, bytesize, parity, stopbits, data): 85 | ser0, uart0 = self.connection(uarts, baudrate, bytesize, parity, stopbits, len(data)) 86 | assert_received(uart0, ser0, data, bytesize) 87 | 88 | def test_read_write(self, uarts, baudrate, bytesize, parity, stopbits): 89 | ser0, uart0 = self.connection(uarts, baudrate, bytesize, parity, stopbits, len(data_set_short)) 90 | send_data_set = data_set_short 91 | receive_data_set = reversed(data_set_short) 92 | for send_data, receive_data in zip(send_data_set, receive_data_set): 93 | assert_received(uart0, ser0, send_data, bytesize) 94 | assert_received(ser0, uart0, receive_data, bytesize) 95 | 96 | @pytest.mark.parametrize('data', data_set_long, ids=data_set_long_ids) 97 | def test_read_long(self, uarts, baudrate, bytesize, parity, stopbits, data): 98 | ser0, uart0 = self.connection(uarts, baudrate, bytesize, parity, stopbits, len(data)) 99 | assert_received(ser0, uart0, data, bytesize) 100 | 101 | @pytest.mark.parametrize('data', data_set_long, ids=data_set_long_ids) 102 | def test_write_long(self, uarts, baudrate, bytesize, parity, stopbits, data): 103 | ser0, uart0 = self.connection(uarts, baudrate, bytesize, parity, stopbits, len(data)) 104 | assert_received(uart0, ser0, data, bytesize) 105 | 106 | # Uart1 has TX and RX connected through a 330 ohm resistor 107 | @pytest.mark.parametrize('stopbits', [1, 2]) 108 | @pytest.mark.parametrize('parity', [serial.PARITY_NONE, serial.PARITY_EVEN, serial.PARITY_ODD]) 109 | @pytest.mark.parametrize('bytesize', [5, 6, 7, 8]) 110 | # Speed up testing by skipping the (s)low baud rates since they are already tested on uart0 111 | @pytest.mark.parametrize('baudrate', [115200, 576000]) 112 | class TestUart1: 113 | def connection(self, uarts, baudrate, bytesize, parity, stopbits, size): 114 | tty_uart0, tty_uart1 = uarts 115 | paritybits = 0 if parity == serial.PARITY_NONE else 1 116 | timeout = ((1 + bytesize + paritybits + stopbits) * size / baudrate) + 1.0 117 | uart1 = serial.Serial(str(tty_uart1), baudrate=baudrate, bytesize=bytesize, parity=parity, stopbits=stopbits, timeout=timeout) 118 | return uart1 119 | 120 | @pytest.mark.parametrize('data', data_set_short, ids=data_set_short_ids) 121 | def test_short(self, uarts, baudrate, bytesize, parity, stopbits, data): 122 | uart1 = self.connection(uarts, baudrate, bytesize, parity, stopbits, len(data)) 123 | assert_received(uart1, uart1, data, bytesize) 124 | 125 | @pytest.mark.parametrize('data', data_set_long, ids=data_set_long_ids) 126 | def test_long(self, uarts, baudrate, bytesize, parity, stopbits, data): 127 | uart1 = self.connection(uarts, baudrate, bytesize, parity, stopbits, len(data)) 128 | assert_received(uart1, uart1, data, bytesize) 129 | 130 | 131 | @pytest.mark.skip() 132 | def test_break(uarts): 133 | tty_uart0, tty_uart1 = uarts 134 | uart1 = serial.Serial(str(tty_uart1), baudrate=115200) 135 | ser0 = serial.Serial('/dev/serial0', baudrate=115200) 136 | uart1.send_break() 137 | # Detecting this BREAK was not easy so I dropped it for now, here are some pointers: 138 | # https://stackoverflow.com/questions/14803434/receive-read-break-condition-on-linux-serial-port 139 | # https://stackoverflow.com/questions/41595882/detect-serial-break-linux 140 | # https://github.com/pyserial/pyserial/issues/539 141 | # https://forums.raspberrypi.com/viewtopic.php?t=302553 142 | # https://elixir.bootlin.com/linux/latest/C/ident/TTY_BREAK 143 | -------------------------------------------------------------------------------- /tusb_config.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: CC0-1.0 2 | /* 3 | * Written in 2021 by Noralf Trønnes 4 | * 5 | * To the extent possible under law, the author(s) have dedicated all copyright and related and 6 | * neighboring rights to this software to the public domain worldwide. This software is 7 | * distributed without any warranty. 8 | * 9 | * You should have received a copy of the CC0 Public Domain Dedication along with this software. 10 | * If not, see . 11 | */ 12 | 13 | #ifndef _TUSB_CONFIG_H_ 14 | #define _TUSB_CONFIG_H_ 15 | 16 | #define CFG_TUSB_RHPORT0_MODE (OPT_MODE_DEVICE | OPT_MODE_FULL_SPEED) 17 | 18 | #define CFG_DLN2_BULK_ENPOINT_SIZE 64 19 | 20 | //#undef CFG_TUSB_DEBUG 21 | //#define CFG_TUSB_DEBUG 2 22 | 23 | #define CFG_TUD_CDC 2 24 | 25 | #define CFG_TUD_CDC_RX_BUFSIZE 1024 26 | #define CFG_TUD_CDC_TX_BUFSIZE 1024 27 | #define USBD_CDC_CMD_MAX_SIZE 8 28 | #define USBD_CDC_IN_OUT_MAX_SIZE 64 29 | 30 | #define USBD_CDC_0_EP_CMD 0x81 31 | #define USBD_CDC_1_EP_CMD 0x83 32 | 33 | #define USBD_CDC_0_EP_OUT 0x01 34 | #define USBD_CDC_1_EP_OUT 0x03 35 | 36 | #define USBD_CDC_0_EP_IN 0x82 37 | #define USBD_CDC_1_EP_IN 0x84 38 | 39 | #define USBD_DLN_EP_IN 0x89 40 | #define USBD_DLN_EP_OUT 0x09 41 | 42 | #endif /* _TUSB_CONFIG_H_ */ 43 | -------------------------------------------------------------------------------- /usb_descriptors.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: CC0-1.0 2 | /* 3 | * Written in 2021 by Noralf Trønnes 4 | * 5 | * To the extent possible under law, the author(s) have dedicated all copyright and related and 6 | * neighboring rights to this software to the public domain worldwide. This software is 7 | * distributed without any warranty. 8 | * 9 | * You should have received a copy of the CC0 Public Domain Dedication along with this software. 10 | * If not, see . 11 | */ 12 | 13 | #include 14 | #include "tusb.h" 15 | #include "pico/unique_id.h" 16 | 17 | enum string_index { 18 | RESERVED_IDX = 0, 19 | MANUFACTURER_IDX, 20 | PRODUCT_IDX, 21 | SERIALNUMBER_IDX, 22 | DLN2_INTERFACE_NAME_IDX, 23 | CDC_NAME_IDX, 24 | }; 25 | 26 | enum interface_index { 27 | DLN_IFCE = 0, 28 | CDC1_CMD_IFCE, 29 | CDC1_DATA_IFCE, 30 | CDC2_CMD_IFCE, 31 | CDC2_DATA_IFCE, 32 | MAX_N_IFCE, 33 | }; 34 | 35 | tusb_desc_device_t const device_descriptor = { 36 | .bLength = sizeof(tusb_desc_device_t), 37 | .bDescriptorType = TUSB_DESC_DEVICE, 38 | .bcdUSB = 0x0110, 39 | 40 | .bDeviceClass = 0, 41 | .bDeviceSubClass = 0, 42 | .bDeviceProtocol = 0, 43 | .bMaxPacketSize0 = 64, 44 | 45 | .idVendor = 0x1d50, // https://github.com/openmoko/openmoko-usb-oui#conditions 46 | .idProduct = 0x6170, 47 | .bcdDevice = 0x0012, 48 | 49 | .iManufacturer = MANUFACTURER_IDX, 50 | .iProduct = PRODUCT_IDX, 51 | .iSerialNumber = SERIALNUMBER_IDX, 52 | 53 | .bNumConfigurations = 1, 54 | }; 55 | 56 | uint8_t const *tud_descriptor_device_cb(void) 57 | { 58 | return (uint8_t const *) &device_descriptor; 59 | } 60 | 61 | #define DLN2_BULK_DESCRIPTOR(_addr) \ 62 | { \ 63 | .bLength = sizeof(tusb_desc_endpoint_t), \ 64 | .bDescriptorType = TUSB_DESC_ENDPOINT, \ 65 | .bEndpointAddress = _addr, \ 66 | .bmAttributes = TUSB_XFER_BULK, \ 67 | .wMaxPacketSize.size = CFG_DLN2_BULK_ENPOINT_SIZE, \ 68 | .bInterval = 0, \ 69 | } 70 | 71 | typedef struct TU_ATTR_PACKED { 72 | tusb_desc_configuration_t config; 73 | tusb_desc_interface_t dln_interface; 74 | tusb_desc_endpoint_t dln_bulk_out; 75 | tusb_desc_endpoint_t dln_bulk_in; 76 | u_int8_t cdc1_ifce_desc[TUD_CDC_DESC_LEN]; 77 | u_int8_t cdc2_ifce_desc[TUD_CDC_DESC_LEN]; 78 | } config_descriptor_t; 79 | 80 | static config_descriptor_t config_descriptor = { 81 | .config = { 82 | .bLength = sizeof(tusb_desc_configuration_t), 83 | .bDescriptorType = TUSB_DESC_CONFIGURATION, 84 | .wTotalLength = sizeof(config_descriptor_t), 85 | .bNumInterfaces = MAX_N_IFCE, 86 | .bConfigurationValue = 1, 87 | .iConfiguration = DLN2_INTERFACE_NAME_IDX, 88 | .bmAttributes = TU_BIT(7) | TUSB_DESC_CONFIG_ATT_SELF_POWERED, 89 | .bMaxPower = 100 / 2, 90 | }, 91 | 92 | .dln_interface = { 93 | .bLength = sizeof(tusb_desc_interface_t), 94 | .bDescriptorType = TUSB_DESC_INTERFACE, 95 | .bInterfaceNumber = DLN_IFCE, 96 | .bAlternateSetting = 0, 97 | .bNumEndpoints = 2, 98 | .bInterfaceClass = TUSB_CLASS_VENDOR_SPECIFIC, 99 | .bInterfaceSubClass = 0x00, 100 | .bInterfaceProtocol = 0x00, 101 | .iInterface = 0, 102 | }, 103 | 104 | .dln_bulk_out = DLN2_BULK_DESCRIPTOR(USBD_DLN_EP_OUT), 105 | .dln_bulk_in = DLN2_BULK_DESCRIPTOR(USBD_DLN_EP_IN), 106 | 107 | .cdc1_ifce_desc = { 108 | TUD_CDC_DESCRIPTOR(CDC1_CMD_IFCE, CDC_NAME_IDX, 109 | USBD_CDC_0_EP_CMD, USBD_CDC_CMD_MAX_SIZE, 110 | USBD_CDC_0_EP_OUT, USBD_CDC_0_EP_IN, USBD_CDC_IN_OUT_MAX_SIZE) 111 | }, 112 | 113 | .cdc2_ifce_desc = { 114 | TUD_CDC_DESCRIPTOR(CDC2_CMD_IFCE, CDC_NAME_IDX, 115 | USBD_CDC_1_EP_CMD, USBD_CDC_CMD_MAX_SIZE, 116 | USBD_CDC_1_EP_OUT, USBD_CDC_1_EP_IN, USBD_CDC_IN_OUT_MAX_SIZE) 117 | }, 118 | }; 119 | 120 | uint8_t const * tud_descriptor_configuration_cb(uint8_t index) 121 | { 122 | return (uint8_t const *)&config_descriptor; 123 | } 124 | 125 | typedef struct TU_ATTR_PACKED { 126 | uint8_t bLength; 127 | uint8_t bDescriptorType; 128 | uint16_t unicode_string[31]; 129 | } dln2_desc_string_t; 130 | 131 | static dln2_desc_string_t string_descriptor = { 132 | .bDescriptorType = TUSB_DESC_STRING, 133 | }; 134 | 135 | uint16_t const *tud_descriptor_string_cb(uint8_t index, uint16_t langid) 136 | { 137 | (void) langid; 138 | 139 | if (index == 0) { 140 | string_descriptor.bLength = 4; 141 | string_descriptor.unicode_string[0] = 0x0409; 142 | return (uint16_t *)&string_descriptor; 143 | } 144 | 145 | const char *str; 146 | char serial[17]; 147 | 148 | if (index == MANUFACTURER_IDX) { 149 | str = "Raspberry Pi"; 150 | } else if (index == PRODUCT_IDX) { 151 | str = "Pico USB I/O Board"; 152 | } else if (index == SERIALNUMBER_IDX) { 153 | pico_get_unique_board_id_string(serial, sizeof(serial)); 154 | str = serial; 155 | } else if (index == DLN2_INTERFACE_NAME_IDX) { 156 | str = "DLN2"; 157 | } else if (index == CDC_NAME_IDX) { 158 | str = "Pico USB CDC"; 159 | } else { 160 | return NULL; 161 | } 162 | 163 | uint8_t len = strlen(str); 164 | if (len > sizeof(string_descriptor.unicode_string)) 165 | len = sizeof(string_descriptor.unicode_string); 166 | 167 | string_descriptor.bLength = 2 + 2 * len; 168 | 169 | for (uint8_t i = 0; i < len; i++) 170 | string_descriptor.unicode_string[i] = str[i]; 171 | 172 | return (uint16_t *)&string_descriptor; 173 | } 174 | --------------------------------------------------------------------------------