├── .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 |
--------------------------------------------------------------------------------