diff --git a/bsp/ESP/ESP32_C6/SConstruct b/bsp/ESP/ESP32_C6/SConstruct index a85917dabeb..35ee346b4b9 100644 --- a/bsp/ESP/ESP32_C6/SConstruct +++ b/bsp/ESP/ESP32_C6/SConstruct @@ -111,6 +111,158 @@ def _use_c6_idf_when_official_has_none(): _use_c6_idf_when_official_has_none() +def _match_idf_spi_lock_stub_to_spi_config(): + """components/esp_hw_support/port/esp32c6/spi_lock_stub.c defines + spicommon_bus_using_iomux() and spi_bus_lock_get_by_id() as + always-true/always-0, and the package's C6 block compiles it because its + directory scan takes every .c under that port directory that is not in + skip_name. It is there because the package does not build + components/driver/spi_common.c for C6, and components/spi_flash/ + esp_flash_spi_init.c needs those two names: with SPI off this BSP has the + same hole, so the stub stays. + + idf_port/SConscript compiles spi_common.c itself once BSP_USING_SPI is + selected, and spi_common.c:726 and :743 define the same two names -- the + real ones, the second of which hands the master driver a bus lock instead of + the stub's 0. Two strong definitions of both names in one image is a link + error, so the scan has to skip the stub in that configuration only. The + switch is written into the package's own SConscript because that scan lives + nowhere else; packages/ is not in the repository, so this is redone on every + build and on every re-fetch of the package, and it is written as bytes to + keep the file's own line endings. + """ + import re + from env_package import find_package_path + + with open(os.path.join(SDK_ROOT, 'rtconfig.h'), 'r', + encoding='utf-8', errors='replace') as f: + spi_selected = re.search(r'^[ \t]*#[ \t]*define[ \t]+BSP_USING_SPI[ \t]*$', + f.read(), re.M) is not None + + sc = os.path.join(os.path.abspath( + find_package_path(SDK_ROOT, 'ESP-IDF-latest')), 'SConscript') + if not os.path.isfile(sc): + return + with open(sc, 'rb') as f: + text = f.read() + entry = b"\n 'spi_lock_stub'," + have = entry in text + if have == spi_selected: + return + if spi_selected: + marker = b'skip_name = (' + if marker not in text: + raise RuntimeError('%s has no %s to skip spi_lock_stub.c in' + % (sc, marker.decode())) + text = text.replace(marker, marker + entry, 1) + else: + text = text.replace(entry, b'', 1) + with open(sc, 'wb') as f: + f.write(text) + +_match_idf_spi_lock_stub_to_spi_config() + +def _adapt_idf_gdma_to_c6_ll(): + """idf_port/SConscript compiles the package's components/esp_hw_support/ + gdma.c when BSP_USING_SPI is on, because spi_common.c's + SOC_GDMA_SUPPORTED section calls gdma_new_channel() and friends. That gdma.c + is written against the C3 generation of hal/gdma_ll.h and this package's + components/hal/esp32c6/include/hal/gdma_ll.h is a different one: + + - gdma_ll_enable_m2m_mode() (gdma.c:259) does not exist on C6. C6 folds + the same thing into the two connect helpers: gdma_ll_rx_connect_to_periph + (gdma_ll.h:297) writes in_conf0.mem_trans_en = (periph == + GDMA_TRIG_PERIPH_M2M) itself, and gdma_ll_rx_disconnect_from_periph + (gdma_ll.h:306) clears it. So the call goes away rather than being + renamed to something C6 does not do. + - gdma_ll_tx/rx_connect_to_periph take four arguments on C6 (gdma_ll.h:520 + and :297) -- dev, channel, periph, periph_id -- while gdma.c:264/:269 + pass three. gdma_trigger_t (esp_hw_support/include/esp_private/ + gdma.h:130) keeps .periph and .instance_id apart, and .instance_id is + what the C3 header wrote into the same select field, so the fix is to + hand C6 the periph it also asks for and keep that value as periph_id. + - gdma.c:289/:291 disconnect by feeding GDMA_INVALID_PERIPH_TRIG back into + connect. C6 has no such sentinel path: it has + gdma_ll_tx_disconnect_from_periph (gdma_ll.h:529) and + gdma_ll_rx_disconnect_from_periph, which write GDMA_LL_INVALID_PERIPH_ID + and clear mem_trans_en. + - the RX connect is left inside its `periph != GDMA_TRIG_PERIPH_M2M` + guard, which on C6 also skips the only writer of mem_trans_en; it is + unguarded here and the TX guard is left alone. Rewritten below, after + these four. + + The rewrite is here, in a tracked file, and it is a whole call expression + swapped for a whole call expression, so a second run finds nothing left to + match and cannot stack a second copy. packages/ is not in the repository. + """ + import re + from env_package import find_package_path + + gdma = os.path.join(os.path.abspath( + find_package_path(SDK_ROOT, 'ESP-IDF-latest')), + 'components', 'esp_hw_support', 'gdma.c') + if not os.path.isfile(gdma): + return + with open(gdma, 'rb') as f: + text = f.read() + + fixed = text + # C6 has no m2m setter: its rx connect sets mem_trans_en, its rx disconnect + # clears it. Drop the call, keep the comment above it, which still says what + # the connect below now does. + fixed = re.sub(rb'^[ \t]*gdma_ll_enable_m2m_mode\([^\r\n]*\r?\n', b'', + fixed, flags=re.M) + for old, new in [ + (b'gdma_ll_tx_connect_to_periph(group->hal.dev, pair->pair_id, ' + b'trig_periph.instance_id)', + b'gdma_ll_tx_connect_to_periph(group->hal.dev, pair->pair_id, ' + b'trig_periph.periph, trig_periph.instance_id)'), + (b'gdma_ll_rx_connect_to_periph(group->hal.dev, pair->pair_id, ' + b'trig_periph.instance_id)', + b'gdma_ll_rx_connect_to_periph(group->hal.dev, pair->pair_id, ' + b'trig_periph.periph, trig_periph.instance_id)'), + (b'gdma_ll_tx_connect_to_periph(group->hal.dev, pair->pair_id, ' + b'GDMA_INVALID_PERIPH_TRIG)', + b'gdma_ll_tx_disconnect_from_periph(group->hal.dev, pair->pair_id)'), + (b'gdma_ll_rx_connect_to_periph(group->hal.dev, pair->pair_id, ' + b'GDMA_INVALID_PERIPH_TRIG)', + b'gdma_ll_rx_disconnect_from_periph(group->hal.dev, pair->pair_id)'), + ]: + fixed = fixed.replace(old, new) + + # The RX connect stays inside the same `periph != M2M` guard gdma.c uses for + # TX, and on C6 that guard is exactly what decides mem_trans_en: the only + # writer of that bit is gdma_ll_rx_connect_to_periph (gdma_ll.h:297-301), and + # it writes mem_trans_en = (periph == GDMA_TRIG_PERIPH_M2M) while doing the + # selection. Guarded, a memory-to-memory channel never reaches the call that + # would set its own mode bit, so mem_trans_en stays 0 forever. C3's + # gdma_ll_enable_m2m_mode -- the call removed above -- wrote that one RX bit + # and nothing else, so the guard replaces a call that used to be made + # unconditionally. TX keeps its guard: gdma_ll_tx_connect_to_periph + # (gdma_ll.h:520-523) throws `periph` away and writes only peri_out_sel, so + # for a TX channel skipping the call loses nothing. + rx_guarded = ( + b' if (trig_periph.periph != GDMA_TRIG_PERIPH_M2M) {\n' + b' gdma_ll_rx_connect_to_periph(group->hal.dev, pair->pair_id,' + b' trig_periph.periph, trig_periph.instance_id);\n' + b' }\n') + rx_unguarded = ( + b' gdma_ll_rx_connect_to_periph(group->hal.dev, pair->pair_id,' + b' trig_periph.periph, trig_periph.instance_id);\n') + for old, new in ((rx_guarded, rx_unguarded), + (rx_guarded.replace(b'\n', b'\r\n'), + rx_unguarded.replace(b'\n', b'\r\n'))): + if old in fixed: + fixed = fixed.replace(old, new, 1) + break + + if fixed != text: + with open(gdma, 'wb') as f: + f.write(fixed) + +_adapt_idf_gdma_to_c6_ll() + + objs = PrepareBuilding(env, RTT_ROOT, remove_components = ['libc']) objs.extend(SConscript(os.path.join(libraries_path_prefix, 'drivers', 'SConscript'), variant_dir='build/libraries/drivers', duplicate=0)) diff --git a/bsp/ESP/ESP32_C6/idf_port/SConscript b/bsp/ESP/ESP32_C6/idf_port/SConscript index 6eee3b883d1..fb32081446c 100644 --- a/bsp/ESP/ESP32_C6/idf_port/SConscript +++ b/bsp/ESP/ESP32_C6/idf_port/SConscript @@ -16,4 +16,122 @@ CPPPATH = Split( group = DefineGroup('idf_port', src, depend = [''], CPPPATH = CPPPATH) +# bsp/ESP/libraries/drivers/drv_spi.c calls the ESP-IDF master mode SPI driver: +# spi_bus_initialize() lives in components/driver/spi_common.c and +# spi_bus_add_device()/spi_device_acquire_bus()/spi_device_polling_transmit()/ +# spi_device_release_bus() in components/driver/spi_master.c. The package's own +# driver group drops both for C6 (packages/ESP-IDF-latest/SConscript:999 keeps +# only gpio.c, rtc_io.c, uart.c and spi_bus_lock.c out of components/driver), so +# they are compiled here -- and only with BSP_USING_SPI selected, so a default +# build adds nothing. +if GetDepend(['BSP_USING_SPI']): + from env_package import find_package_path + + bsp_root = os.path.normpath(os.path.join(GetCurrentDir(), '..')) + idf = find_package_path(bsp_root, 'ESP-IDF-latest', + relative_to=bsp_root, pathsep='/') + wrapper = find_package_path(bsp_root, 'FreeRTOS-Wrapper-latest', + relative_to=bsp_root, pathsep='/') + + # scons resolves these against the BSP root (the variant dir 'build' mirrors + # them), so '..' steps out of this idf_port directory; the include + # directories below are absolute to stay independent of that. + spi_src = [ + '../%s/components/driver/spi_common.c' % idf, + '../%s/components/driver/spi_master.c' % idf, + # spi_common.c and spi_master.c are written against the HAL layer, and + # this package's C6 hal group drops spi_hal.c; without it the ten + # spi_hal_* calls those two files make have no definition. + '../%s/components/hal/spi_hal.c' % idf, + # spi_common.c reaches GDMA through its own SOC_GDMA_SUPPORTED section + # (soc/esp32c6/include/soc/soc_caps.h defines it as 1), and that code is + # compiled into the object no matter what the third argument of + # spi_bus_initialize() is. The package drops every path containing + # 'gdma' from its C6 source list, so the driver and its hal wrapper are + # compiled here instead. That gdma.c is written against the C3 + # generation of hal/gdma_ll.h; SConstruct rewrites its gdma_ll_* call + # sites to the prototypes in this package's C6 header before anything is + # compiled. + '../%s/components/esp_hw_support/gdma.c' % idf, + '../%s/components/hal/gdma_hal.c' % idf, + # gdma.c reads gdma_periph_signals (gdma.c:541 and on) to map a + # GDMA_TRIG_PERIPH_* onto the signal slots of a channel; the one object + # file defining it is this SoC table, which the package drops for the + # same 'gdma' reason as the two files above. + '../%s/components/soc/esp32c6/gdma_periph.c' % idf, + # this gcc has no __atomic_compare_exchange_1 (see the file); the byte + # sized compare-exchange of spi_common.c's atomic_bool needs one. + 'src/rt_atomic_cas_stub.c', + ] + + # headers the two files include. freertos/queue.h and freertos/task.h come + # from the RT-Thread FreeRTOS wrapper, like everywhere else in this BSP: the + # kernel copy inside the package wants an IDF sdkconfig this BSP does not + # carry. + spi_path = [os.path.join(bsp_root, idf, 'components', p) for p in ( + 'driver/include', + 'esp_common/include', + 'esp_hw_support/include', + 'esp_hw_support/port/esp32c6/include', + 'esp_rom/include', + 'esp_system/include', + 'esp_system/port/include', + 'esp_system/port/include/private', + 'esp_timer/include', + 'hal/include', + 'hal/esp32c6/include', + 'hal/platform_port/include', + 'heap/include', + 'log/include', + 'newlib/platform_include', + 'riscv/include', + 'soc/include', + 'soc/esp32c6/include', + )] + [os.path.join(bsp_root, wrapper, 'FreeRTOS/include')] + + # hal/esp32c6/include/hal/spi_ll.h names SPI_CLK_SRC_XTAL/SPI_CLK_SRC_RC_FAST + # (enumerators of soc/clk_tree_defs.h) and spi_command_t with its + # SPI_CMD_HD_* values, and this package snapshot defines neither of the + # latter anywhere -- only spi_ll.h uses them, hal/include/hal/spi_types.h has + # no spi_command_t. drv_spi.c carries the declarations for its own + # translation unit; these flags give the package's own sources the same + # thing. The real home of both is the package's hal/include/hal/spi_types.h. + # One string, because building.py appends LOCAL_CFLAGS to a CFLAGS string. + spi_flags = ( + '-include soc/clk_tree_defs.h' + ' -Dspi_command_t=int' + ' -DSPI_CMD_HD_WRBUF=0x01' + ' -DSPI_CMD_HD_RDBUF=0x02' + ' -DSPI_CMD_HD_WRDMA=0x03' + ' -DSPI_CMD_HD_RDDMA=0x04' + ' -DSPI_CMD_HD_SEG_END=0x05' + ' -DSPI_CMD_HD_EN_QPI=0x06' + ' -DSPI_CMD_HD_WR_END=0x07' + ' -DSPI_CMD_HD_INT0=0x08' + ' -DSPI_CMD_HD_INT1=0x09' + ' -DSPI_CMD_HD_INT2=0x0A' + ) + + group += DefineGroup('idf_port_spi', spi_src, depend = ['BSP_USING_SPI'], + LOCAL_CPPPATH = spi_path, LOCAL_CFLAGS = spi_flags) + + # spi_hal.c is only init/deinit/cal_clock. spi_hal_setup_device, + # spi_hal_setup_trans, spi_hal_prepare_data, spi_hal_user_start and + # spi_hal_fetch_result -- everything spi_master.c drives a transfer with -- + # are in spi_hal_iram.c, the second file of that pair, which this package's + # C6 drop_hal list removes as well. + # + # It is a group of its own because it is the only source that may see + # include/spi_ll_master_start_compat.h: that header defines a macro named + # spi_ll_master_user_start, and giving it to spi_common.c, spi_master.c or + # gdma.c would only shadow the C6 header's own helpers in files that never + # ask for that name. + iram_path = [os.path.join(bsp_root, 'idf_port', 'include')] + spi_path + group += DefineGroup('idf_port_spi_iram', + ['../%s/components/hal/spi_hal_iram.c' % idf], + depend = ['BSP_USING_SPI'], + LOCAL_CPPPATH = iram_path, + LOCAL_CFLAGS = spi_flags + + ' -include spi_ll_master_start_compat.h') + Return('group') diff --git a/bsp/ESP/ESP32_C6/idf_port/include/spi_ll_master_start_compat.h b/bsp/ESP/ESP32_C6/idf_port/include/spi_ll_master_start_compat.h new file mode 100644 index 00000000000..91150f4e862 --- /dev/null +++ b/bsp/ESP/ESP32_C6/idf_port/include/spi_ll_master_start_compat.h @@ -0,0 +1,29 @@ +#ifndef RT_ESP_IDF_PORT_SPI_LL_MASTER_START_COMPAT_H +#define RT_ESP_IDF_PORT_SPI_LL_MASTER_START_COMPAT_H + +#include "hal/spi_ll.h" + +/* + * components/hal/spi_hal_iram.c:188 starts the transaction with + * spi_ll_master_user_start(hal->hw), a name this package's + * hal/esp32c6/include/hal/spi_ll.h does not have. C6 splits it in two: + * spi_ll_apply_config() (spi_ll.h:208, sets cmd.update and waits until the + * peripheral has taken the configuration) and spi_ll_user_start() (spi_ll.h:231, + * sets cmd.usr). The six targets whose spi_ll.h does define + * spi_ll_master_user_start -- esp32, esp32c2, esp32c3, esp32h2, esp32s2, + * esp32s3 -- all define it as those same two writes, in this order. So this is + * not a substitute for the C6 sequence, it is the C6 sequence under the name the + * shared spi_hal_iram.c asks for. + * + * Included with -include, for that one source file only (idf_port/SConscript), + * so no other translation unit sees the name. packages/ is not in the + * repository, which is why the fix lives here. + */ +#define spi_ll_master_user_start(hw) \ + do \ + { \ + spi_ll_apply_config(hw); \ + spi_ll_user_start(hw); \ + } while (0) + +#endif /* RT_ESP_IDF_PORT_SPI_LL_MASTER_START_COMPAT_H */ diff --git a/bsp/ESP/ESP32_C6/idf_port/src/rt_atomic_cas_stub.c b/bsp/ESP/ESP32_C6/idf_port/src/rt_atomic_cas_stub.c new file mode 100644 index 00000000000..1f8016c66cd --- /dev/null +++ b/bsp/ESP/ESP32_C6/idf_port/src/rt_atomic_cas_stub.c @@ -0,0 +1,55 @@ +#include + +/* + * spi_common.c claims a bus with + * atomic_compare_exchange_strong(&spi_periph_claimed[host], &expected, value) + * on an _Atomic bool, and an ESP32-C6 core can only reserve a whole word, so + * this gcc emits a call to the out-of-line helper instead of an lr/sc sequence. + * The install has no __atomic_compare_exchange_1: + * riscv32-esp-elf/bin/../lib/gcc/riscv32-esp-elf/11.2.0/libgcc.a -> 0 hits + * and there is no libatomic.a next to it, so the name stays undefined at link + * time. The prototype below is the one the compiler itself declares -- it + * rejects any other spelling with -Wbuiltin-declaration-mismatch -- and it + * matches what the call site really passes: the address of the object, the + * address of the expected byte, the desired byte, and two memory orders. The + * `weak` argument is not passed in a register at all (an atomic_compare_exchange + * weak/strong pair compiles to the same five loads), which is why it is ignored + * here; the helper's answer is always the strong one, so a weak CAS merely gets + * one it did not ask for. + * + * Masking interrupts is enough for this SoC: it has one core, so the only other + * writer of these bytes is an interrupt handler. + */ +_Bool __atomic_compare_exchange_1(volatile void *object, + void *expected, + unsigned char desired, + _Bool weak, + int success, + int failure) +{ + volatile unsigned char *addr = (volatile unsigned char *)object; + unsigned char *compare = (unsigned char *)expected; + rt_base_t level; + _Bool exchanged; + + (void)weak; + (void)success; + (void)failure; + + level = rt_hw_interrupt_disable(); + + if (*addr == *compare) + { + *addr = desired; + exchanged = 1; + } + else + { + *compare = *addr; + exchanged = 0; + } + + rt_hw_interrupt_enable(level); + + return exchanged; +} diff --git a/bsp/ESP/libraries/drivers/Kconfig b/bsp/ESP/libraries/drivers/Kconfig index aa8c664423f..4cbbcddd917 100644 --- a/bsp/ESP/libraries/drivers/Kconfig +++ b/bsp/ESP/libraries/drivers/Kconfig @@ -123,7 +123,7 @@ menu "On-chip Peripheral Drivers" menuconfig BSP_USING_SPI bool "Enable SPI" - depends on SOC_ESP32_C3 + depends on SOC_ESP32_C3 || SOC_ESP32_C6 default n select RT_USING_SPI if BSP_USING_SPI diff --git a/bsp/ESP/libraries/drivers/drv_spi.c b/bsp/ESP/libraries/drivers/drv_spi.c index 389e75a7ac5..3f46a095d24 100644 --- a/bsp/ESP/libraries/drivers/drv_spi.c +++ b/bsp/ESP/libraries/drivers/drv_spi.c @@ -17,6 +17,37 @@ #include "rttypes.h" #include "sdkconfig.h" +#ifdef SOC_ESP32_C6 +/* + * Two things hal/esp32c6/include/hal/spi_ll.h needs are not reachable through + * the headers this driver includes, so they come in before hal/spi_hal.h: + * - SPI_CLK_SRC_RC_FAST / SPI_CLK_SRC_XTAL, used by its clock-selection + * helpers; they are enumerators of soc/clk_tree_defs.h, and the common + * hal/include/hal/spi_types.h does not carry them; + * - spi_command_t, the type of the command argument at spi_ll.h:1130 and + * spi_ll.h:1177, which this ESP-IDF package does not define at all. The + * values below are the base commands spi_ll.h:79-91 already lists + * (as spi_ll_base_command_t), so the two agree. + * packages/ is not touched; C3 has both names where it needs them, hence the + * SoC guard. + */ +#include "soc/clk_tree_defs.h" + +typedef enum +{ + SPI_CMD_HD_WRBUF = 0x01, /*!< Half-duplex write of the WRBUF */ + SPI_CMD_HD_RDBUF = 0x02, /*!< Half-duplex read of the RDBUF */ + SPI_CMD_HD_WRDMA = 0x03, /*!< Half-duplex write through DMA */ + SPI_CMD_HD_RDDMA = 0x04, /*!< Half-duplex read through DMA */ + SPI_CMD_HD_SEG_END = 0x05, /*!< End of a half-duplex segment */ + SPI_CMD_HD_EN_QPI = 0x06, /*!< Enter QPI mode */ + SPI_CMD_HD_WR_END = 0x07, /*!< End of a half-duplex write */ + SPI_CMD_HD_INT0 = 0x08, /*!< Internal operation 0 */ + SPI_CMD_HD_INT1 = 0x09, /*!< Internal operation 1 */ + SPI_CMD_HD_INT2 = 0x0A, /*!< Internal operation 2 */ +} spi_command_t; +#endif /* SOC_ESP32_C6 */ + #include "hal/spi_hal.h" /*bsp/ESP32_C3/packages/ESP-IDF-latest/components/hal/include/hal/spi_types.h*/ #include "driver/gpio.h" /*bsp/ESP32_C3/packages/ESP-IDF-latest/components/driver/include/driver/gpio.h*/ #include "driver/spi_master.h" @@ -34,6 +65,25 @@ static struct rt_spi_bus spi_bus2; static spi_device_handle_t spi; static spi_bus_config_t buscfg; +/* + * Width of one data unit of the transaction, in bits. spi_transaction_t counts + * length/rxlength in bits, so spixfer() needs this to size the transfer and to + * know how wide the received units it copies out are. + * Only 8-bit words are supported; spi_configure() rejects anything else. + */ +static rt_uint32_t spi_bits_per_word = 8; + +/* + * Receive staging array. driver/spi_master.h says of spi_transaction_t.rx_buffer + * "Written by 4 bytes-unit if DMA is used", so the received bytes are taken into + * an aligned array of our own and only message->length of them are handed to the + * caller -- whose buffer may be any size and alignment. Sized for + * buscfg.max_transfer_sz. Where those bytes come from is chip specific and told + * where the chip decides it: see spi_configure(). + */ +#define RT_ESP_SPI_MAX_RX_BYTES 4092 +static rt_uint32_t spi_rx_units[(RT_ESP_SPI_MAX_RX_BYTES + 3) / 4] __attribute__((aligned(4))); + static struct esp32_spi spi_bus_obj[] = { #ifdef BSP_USING_SPI2 { @@ -54,19 +104,14 @@ static struct rt_spi_ops esp32_spi_ops = .xfer = spixfer, }; -/** -* @brief SPI Initialization -* @param esp32_spi: SPI BUS -* @retval None -*/ -static void esp32_spi_init(struct esp32_spi *esp32_spi) -{ - spi_configure(NULL,NULL); -} - static rt_err_t spi_configure(struct rt_spi_device* device, struct rt_spi_configuration* configuration) { + if (configuration == NULL) + { + return -RT_EINVAL; + } + static spi_bus_config_t buscfg = { .miso_io_num=SPI2_IOMUX_PIN_NUM_MISO, /*MISO*/ @@ -77,14 +122,34 @@ static rt_err_t spi_configure(struct rt_spi_device* device, .max_transfer_sz=4092 /*最大传送数据长度*/ }; - esp_err_t err = spi_bus_initialize(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO); + esp_err_t err; + +#ifdef SOC_ESP32_C6 + /* SPI_DMA_DISABLED: this transfer does not go through GDMA, even though GDMA + is in the image -- BSP_USING_SPI makes idf_port/SConscript compile the + package's components/esp_hw_support/gdma.c, because spi_common.c's + SOC_GDMA_SUPPORTED section calls it. The received bytes are instead read + out by spi_ll_read_buffer() (hal/esp32c6/include/hal/spi_ll.h:425), which + memcpys the low byte of each 32-bit data_buf word to the start of the + buffer -- its length is (len + 7) / 8, which is 1 for an 8-bit word -- and + that is what fills the staging array above. */ + err = spi_bus_initialize(SPI2_HOST, &buscfg, SPI_DMA_DISABLED); +#else + err = spi_bus_initialize(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO); +#endif /* SOC_ESP32_C6 */ ESP_ERROR_CHECK(err); static spi_device_interface_config_t devcfg; if(configuration->data_width == 8) { - size_t length; /*/< Total data length, in bits*/ - size_t rxlength; /*/< Total data length received, should be not greater than ``length`` in full-duplex mode (0 defaults this to the value of ``length``)*/ + /* One RT-Thread byte is 8 bits of transaction: this branch only sets + * spi_bits_per_word, which is what length and rxlength are counted in. */ + spi_bits_per_word = 8; + } + else + { + LOG_E("data width %u is not supported\n", (rt_uint32_t)configuration->data_width); + return -RT_ENOSYS; } LOG_W("configuration->max_hz = %d \n",configuration->max_hz); @@ -133,14 +198,19 @@ static rt_err_t spi_configure(struct rt_spi_device* device, { case RT_SPI_MODE_0: /*!< CPOL = 0, CPHA = 0 */ devcfg.mode = 0; + break; case RT_SPI_MODE_1: /*!< CPOL = 0, CPHA = 1 */ devcfg.mode = 1; + break; case RT_SPI_MODE_2: /*!< CPOL = 1, CPHA = 0 */ devcfg.mode = 2; + break; case RT_SPI_MODE_3: /*!< CPOL = 1, CPHA = 1 */ devcfg.mode = 3; + break; default: devcfg.mode = 0; + break; } /* todo: support changing cs_pin,queue_size or specifing spi_device_interface_config_t and @@ -148,7 +218,7 @@ static rt_err_t spi_configure(struct rt_spi_device* device, * callback function and dma. */ - devcfg.spics_io_num = RT_BSP_SPI_CS_PIN; + devcfg.spics_io_num = SPI2_IOMUX_PIN_NUM_CS; devcfg.queue_size = 7; err = spi_bus_add_device(SPI2_HOST, &devcfg, &spi); @@ -171,12 +241,27 @@ static rt_ssize_t spixfer(struct rt_spi_device* device, struct rt_spi_message* m RT_ASSERT(device != NULL); RT_ASSERT(message != NULL); + /* Zeroed per transfer: this transaction is reused, and cmd/addr/flags/user + left over from the previous one would be sent out again. */ static spi_transaction_t trans; + rt_memset(&trans, 0, sizeof(trans)); + trans.length = (message->length) * spi_bits_per_word; trans.tx_buffer = message->send_buf; - trans.rx_buffer = message->recv_buf; - trans.length = (message->length)*8; - trans.rxlength = (message->length)*8; + + if (message->recv_buf != RT_NULL) + { + if (message->length > sizeof(spi_rx_units)) + { + LOG_E("rx length %u is larger than %u\n", + (rt_uint32_t)message->length, (rt_uint32_t)RT_ESP_SPI_MAX_RX_BYTES); + return -RT_EINVAL; + } + /* The driver writes the received units into this 4-byte aligned array, + not into the caller's buffer. */ + trans.rx_buffer = spi_rx_units; + trans.rxlength = trans.length; + } spi_device_acquire_bus(spi, portMAX_DELAY); esp_err_t err = spi_device_polling_transmit(spi, &trans); @@ -184,7 +269,15 @@ static rt_ssize_t spixfer(struct rt_spi_device* device, struct rt_spi_message* m spi_device_release_bus(spi); ESP_ERROR_CHECK(err); - return RT_EOK; + + if (message->recv_buf != RT_NULL) + { + /* Verbatim: the aligned array holds the received bytes in the order they + came off the bus, and only the count the caller asked for is copied. */ + rt_memcpy(message->recv_buf, spi_rx_units, message->length); + } + + return message->length; }; /**