From 3873e30843634f7243d811b70375350f692150e9 Mon Sep 17 00:00:00 2001 From: Bertho Stultiens Date: Mon, 2 Dec 2024 16:12:11 +0100 Subject: [PATCH 01/18] Add a unified HM2 SPI driver supporting RPI3, RPi4, RPi5 and spidev. --- src/hal/drivers/mesa-hostmot2/dtcboards.h | 82 ++ src/hal/drivers/mesa-hostmot2/eshellf.c | 62 ++ src/hal/drivers/mesa-hostmot2/eshellf.h | 28 + src/hal/drivers/mesa-hostmot2/hm2_rpspi.c | 22 - src/hal/drivers/mesa-hostmot2/hm2_spix.c | 687 ++++++++++++++ src/hal/drivers/mesa-hostmot2/hwregaccess.h | 81 ++ src/hal/drivers/mesa-hostmot2/llio_info.c | 257 ++++++ src/hal/drivers/mesa-hostmot2/llio_info.h | 32 + src/hal/drivers/mesa-hostmot2/rp1dev.h | 414 +++++++++ .../drivers/mesa-hostmot2/spi_common_rpspi.h | 14 +- src/hal/drivers/mesa-hostmot2/spix.h | 174 ++++ src/hal/drivers/mesa-hostmot2/spix_rpi3.c | 852 ++++++++++++++++++ src/hal/drivers/mesa-hostmot2/spix_rpi5.c | 674 ++++++++++++++ src/hal/drivers/mesa-hostmot2/spix_spidev.c | 330 +++++++ 14 files changed, 3675 insertions(+), 34 deletions(-) create mode 100644 src/hal/drivers/mesa-hostmot2/dtcboards.h create mode 100644 src/hal/drivers/mesa-hostmot2/eshellf.c create mode 100644 src/hal/drivers/mesa-hostmot2/eshellf.h create mode 100644 src/hal/drivers/mesa-hostmot2/hm2_spix.c create mode 100644 src/hal/drivers/mesa-hostmot2/hwregaccess.h create mode 100644 src/hal/drivers/mesa-hostmot2/llio_info.c create mode 100644 src/hal/drivers/mesa-hostmot2/llio_info.h create mode 100644 src/hal/drivers/mesa-hostmot2/rp1dev.h create mode 100644 src/hal/drivers/mesa-hostmot2/spix.h create mode 100644 src/hal/drivers/mesa-hostmot2/spix_rpi3.c create mode 100644 src/hal/drivers/mesa-hostmot2/spix_rpi5.c create mode 100644 src/hal/drivers/mesa-hostmot2/spix_spidev.c diff --git a/src/hal/drivers/mesa-hostmot2/dtcboards.h b/src/hal/drivers/mesa-hostmot2/dtcboards.h new file mode 100644 index 00000000000..3b16e54c75b --- /dev/null +++ b/src/hal/drivers/mesa-hostmot2/dtcboards.h @@ -0,0 +1,82 @@ +/* + * This is a component for RaspberryPi and other boards for linuxcnc. + * Copyright (c) 2024 B.Stultiens + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, see . + */ +#ifndef HAL_DTCBOARDS_H +#define HAL_DTCBOARDS_H + +/* + * Info about the hardware platform, see: + * https://www.raspberrypi.com/documentation/computers/raspberry-pi.html#best-practices-for-revision-code-usage + * https://www.raspberrypi.com/documentation/computers/raspberry-pi.html#check-raspberry-pi-model-and-cpu-across-distributions + * + * Reading /proc/device-tree/compatible should contain the relevant + * information. We should get a buffer containing a string-list like: + * "raspberrypi,5-model-b\0brcm,bcm2712\0" + * And yes, it has embedded NULs. + * + * The idea is to match one of the strings to assign the correct driver for the + * specific board. + */ +#define DTC_BOARD_MAKE_RPI "raspberrypi" + +#define DTC_BOARD_MODEL_5CM "5-compute-module" +#define DTC_BOARD_MODEL_5B "5-model-b" +#define DTC_BOARD_MODEL_4CM "4-compute-module" +#define DTC_BOARD_MODEL_4B "4-model-b" +#define DTC_BOARD_MODEL_3CM "3-compute-module" +#define DTC_BOARD_MODEL_3BP "3-model-b-plus" +#define DTC_BOARD_MODEL_3AP "3-model-a-plus" +#define DTC_BOARD_MODEL_3B "3-model-b" +#define DTC_BOARD_MODEL_2B "2-model-b" +#define DTC_BOARD_MODEL_CM "compute-module" +#define DTC_BOARD_MODEL_BP "model-b-plus" +#define DTC_BOARD_MODEL_AP "model-a-plus" +#define DTC_BOARD_MODEL_BR2 "model-b-rev2" +#define DTC_BOARD_MODEL_B "model-b" +#define DTC_BOARD_MODEL_A "model-a" +#define DTC_BOARD_MODEL_ZERO_2W "model-zero-2-w" +#define DTC_BOARD_MODEL_ZERO_W "model-zero-w" +#define DTC_BOARD_MODEL_ZERO "model-zero" + +#define DTC_SOC_MAKE_BRCM "brcm" + +#define DTC_SOC_MODEL_BCM2712 "bcm2712" +#define DTC_SOC_MODEL_BCM2711 "bcm2711" +#define DTC_SOC_MODEL_BCM2837 "bcm2837" +#define DTC_SOC_MODEL_BCM2836 "bcm2836" +#define DTC_SOC_MODEL_BCM2835 "bcm2835" + +/* The device-tree compatible strings for the boards */ +#define DTC_RPI_SOC_BCM2712 DTC_SOC_MAKE_RPI "," DTC_SOC_MODEL_BCM2712 +#define DTC_RPI_MODEL_5CM DTC_BOARD_MAKE_RPI "," DTC_BOARD_MODEL_5CM +#define DTC_RPI_MODEL_5B DTC_BOARD_MAKE_RPI "," DTC_BOARD_MODEL_5B + +#define DTC_RPI_SOC_BCM2711 DTC_SOC_MAKE_RPI "," DTC_SOC_MODEL_BCM2711 +#define DTC_RPI_MODEL_4CM DTC_BOARD_MAKE_RPI "," DTC_BOARD_MODEL_4CM +#define DTC_RPI_MODEL_4B DTC_BOARD_MAKE_RPI "," DTC_BOARD_MODEL_4B + +#define DTC_RPI_SOC_BCM2837 DTC_SOC_MAKE_BRCM "," DTC_SOC_MODEL_BCM2837 +#define DTC_RPI_MODEL_3CM DTC_BOARD_MAKE_RPI "," DTC_BOARD_MODEL_3CM +#define DTC_RPI_MODEL_3BP DTC_BOARD_MAKE_RPI "," DTC_BOARD_MODEL_3BP +#define DTC_RPI_MODEL_3AP DTC_BOARD_MAKE_RPI "," DTC_BOARD_MODEL_3AP +#define DTC_RPI_MODEL_3B DTC_BOARD_MAKE_RPI "," DTC_BOARD_MODEL_3B +#define DTC_RPI_MODEL_ZERO_2W DTC_BOARD_MAKE_RPI "," DTC_BOARD_MODEL_ZERO_2W + +/* Older than a RPi3 (bcm2836 and bcm2835) is probably not a good idea to use. */ + +#endif +// vim: ts=4 diff --git a/src/hal/drivers/mesa-hostmot2/eshellf.c b/src/hal/drivers/mesa-hostmot2/eshellf.c new file mode 100644 index 00000000000..1198bd8c773 --- /dev/null +++ b/src/hal/drivers/mesa-hostmot2/eshellf.c @@ -0,0 +1,62 @@ +/* + * This is a component for hostmot2 board drivers + * Copyright (c) 2013,2014,2020,2024 Michael Geszkiewicz , + * Jeff Epler + * B.Stultiens + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, see . + */ + +#include +#include +#include +#include +#include + +#include + +int shell(char *command) +{ + char *const argv[] = {"sh", "-c", command, NULL}; + pid_t pid; + int res = rtapi_spawn_as_root(&pid, "/bin/sh", NULL, NULL, argv, environ); + if(res < 0) + perror("rtapi_spawn_as_root"); + int status; + waitpid(pid, &status, 0); + if(WIFEXITED(status)) + return WEXITSTATUS(status); + else if(WIFSTOPPED(status)) + return WTERMSIG(status)+128; + return status; +} + +int eshellf(const char *errpfx, const char *fmt, ...) +{ + char commandbuf[1024]; + va_list ap; + va_start(ap, fmt); + vsnprintf(commandbuf, sizeof(commandbuf), fmt, ap); + va_end(ap); + + int res = shell(commandbuf); + if(res == EXIT_SUCCESS) + return 0; + + rtapi_print_msg(RTAPI_MSG_ERR, "%s: ERROR: Failed to execute '%s'\n", errpfx ? errpfx : "eshellf()", commandbuf); + return -EINVAL; +} + +/* vim: ts=4 + */ diff --git a/src/hal/drivers/mesa-hostmot2/eshellf.h b/src/hal/drivers/mesa-hostmot2/eshellf.h new file mode 100644 index 00000000000..283b9f93427 --- /dev/null +++ b/src/hal/drivers/mesa-hostmot2/eshellf.h @@ -0,0 +1,28 @@ +/* + * This is a component for hostmot2 board drivers + * Copyright (c) 2013,2014,2020,2024 Michael Geszkiewicz , + * Jeff Epler + * B.Stultiens + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, see . + */ +#ifndef HAL_HM2_ESHELLF_H +#define HAL_HM2_ESHELLF_H + +int shell(char *command); +int eshellf(const char *errpfx, const char *fmt, ...); + +#endif +/* vim: ts=4 + */ diff --git a/src/hal/drivers/mesa-hostmot2/hm2_rpspi.c b/src/hal/drivers/mesa-hostmot2/hm2_rpspi.c index 53a12a9c4ee..32fa367d212 100644 --- a/src/hal/drivers/mesa-hostmot2/hm2_rpspi.c +++ b/src/hal/drivers/mesa-hostmot2/hm2_rpspi.c @@ -343,28 +343,6 @@ RTAPI_MP_INT(spi_probe, "Bit-field to select which SPI/CE combinations to probe static int spi_debug = -1; RTAPI_MP_INT(spi_debug, "Set message level for debugging purpose [0...5] where 0=none and 5=all (default: -1; upstream defined)") -/*********************************************************************/ -/* - * Synchronized read and write to peripheral memory. - * Ensures coherency between cores, cache and peripherals - */ -#define rmb() __sync_synchronize() // Read sync (finish all reads before continuing) -#define wmb() __sync_synchronize() // Write sync (finish all write before continuing) - -RPSPI_ALWAYS_INLINE static inline uint32_t reg_rd(const volatile void *addr) -{ - uint32_t val; - val = *(volatile uint32_t *)addr; - rmb(); - return val; -} - -RPSPI_ALWAYS_INLINE static inline void reg_wr(const volatile void *addr, uint32_t val) -{ - wmb(); - *(volatile uint32_t *)addr = val; -} - /*********************************************************************/ #if defined(RPSPI_DEBUG_PIN) /* diff --git a/src/hal/drivers/mesa-hostmot2/hm2_spix.c b/src/hal/drivers/mesa-hostmot2/hm2_spix.c new file mode 100644 index 00000000000..06d1359b990 --- /dev/null +++ b/src/hal/drivers/mesa-hostmot2/hm2_spix.c @@ -0,0 +1,687 @@ +/* + * This is a component for hostmot2 over SPI for linuxcnc. + * Copyright (c) 2024 B.Stultiens + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, see . + */ + +/* Without Source Tree */ +#undef WOST + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#define HM2_LLIO_NAME "hm2_spix" + +#include "hostmot2-lowlevel.h" +#include "hostmot2.h" + +#include "llio_info.h" +#include "spix.h" + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("B.Stultiens"); +MODULE_DESCRIPTION("Driver for HostMot2 devices connected via SPI"); +MODULE_SUPPORTED_DEVICE("Mesa-AnythingIO-7i90,7c80,7c81,7i43"); + +#define NELEM(x) (sizeof(x) / sizeof(*(x))) + +/* + * Buffer for queued transfers + */ +typedef struct __buffer_t { + void *ptr; // Actual buffer + size_t n; // Number of elements in buffer + size_t na; // Allocated size of buffer in number of elements +} buffer_t; + +/* + * Buffer reference for copying read data back to original buffers + */ +typedef struct __rxref_t { + void *ptr; // The read buffer from the queue_read call + int size; // Size of read buffer from the queue_read call + int idx; // Data position index into the board's rbuf data +} rxref_t; + +/* + * Our connected HM2 board data container + */ +typedef struct __spix_board_t { + hm2_lowlevel_io_t llio; // Upstream container + int nr; // Board number + buffer_t wbuf; // Queued writes buffer + buffer_t rbuf; // Queued reads buffer + buffer_t rref; // Queued read buffer references + const spix_port_t *port; // The low-level hardware port +} spix_board_t; + +static spix_board_t boards[SPIX_MAX_BOARDS]; // Connected boards +static int comp_id; // Upstream assigned component ID + +/* + * Supported hardware drivers + * These are defined in spix_XXX source files. + */ +extern spix_driver_t spix_rpi3; +extern spix_driver_t spix_rpi5; +extern spix_driver_t spix_spidev; + +static const spix_driver_t *drivers[] = { + &spix_rpi3, // RPi3, RPi3[ab]+, RPi4b and RPi4CM + &spix_rpi5, // RPi5 and RPi5CM + // TODO: orange pi + // TODO: banana pi + &spix_spidev, +}; + +static const spix_driver_t *hwdriver; // The active driver + +// The value of the cookie when read from the board. An actual cookie read +// consists of four words. The fourth value is the idrom address, which may +// vary per board. +static const uint32_t iocookie[3] = { + HM2_IOCOOKIE, + // The following words spell HOSTMOT2 + 0x54534f48, // TSOH + 0x32544f4d // 2TOM +}; + +/* + * Configuration parameters forwarded to hostmot2 hm2_register() call + */ +static char *config[SPIX_MAX_BOARDS]; +RTAPI_MP_ARRAY_STRING(config, SPIX_MAX_BOARDS, "config string for the AnyIO boards (see hostmot2(9) manpage)") + +/* + * SPI clock rates for read and write. + */ +static int spiclk_rate[SPIX_MAX_BOARDS] = { 25000 }; +static int spiclk_rate_rd[SPIX_MAX_BOARDS]; +RTAPI_MP_ARRAY_INT(spiclk_rate, SPIX_MAX_BOARDS, "SPI clock rates in kHz (default 25000 kHz)") +RTAPI_MP_ARRAY_INT(spiclk_rate_rd, SPIX_MAX_BOARDS, "SPI clock rates for reading in kHz (default same as spiclk_rate)") + +/* + * Forcefully specify the hardware driver + */ +static const char *force_driver = NULL; +RTAPI_MP_STRING(force_driver, "Force one specific hardware driver (default empty, auto detecting hardware))") + +/* + * Which SPI port(s) to probe + */ +static int spi_probe = SPIX_PROBE_SPI0_CE0; +RTAPI_MP_INT(spi_probe, "Bit-field to select which SPI/CE combinations to probe (default 1 (SPI0/CE0))") + +/* + * Normally, all requests are queued if requested by upstream and sent in one + * bulk transfer. This reduces overhead significantly. Disabling the queue make + * each transfer visible and more easily debugable. + */ +static int spi_noqueue = 0; +RTAPI_MP_INT(spi_noqueue, "Disable queued SPI requests, use for debugging only (default 0 (off))") + +/* + * Set the message level for debugging purpose. This has the (side-)effect that + * all modules within this process will start spitting out messages at the + * requested level. + * The upstream message level is not touched if spi_debug == -1. + */ +static int spi_debug = -1; +RTAPI_MP_INT(spi_debug, "Set message level for debugging purpose [0...5] where 0=none and 5=all (default: -1; upstream defined)") + +/*********************************************************************/ +/* + * Buffer management for queued transfers. + */ +static int buffer_check_room(buffer_t *b, size_t n, size_t elmsize) +{ + if(!b->ptr || !b->na) { + b->na = 64; // Default to this many elements + b->n = 0; + b->ptr = rtapi_kmalloc(elmsize * b->na, RTAPI_GPF_KERNEL); + return b->ptr == NULL; + } + + if(b->n + n > b->na) { + do { + b->na *= 2; // Double storage capacity + } while(b->n + n > b->na); // Until we have enough room + void *p = rtapi_krealloc(b->ptr, elmsize * b->na, RTAPI_GPF_KERNEL); + if(!p) + return 1; + b->ptr = p; + } + return 0; +} + +static void buffer_free(buffer_t *b) +{ + if(b->ptr) { + rtapi_kfree(b->ptr); + b->ptr = NULL; + b->n = b->na = 0; + } +} + +/*********************************************************************/ +/* + * HM2 interface: Write buffer to SPI + * Writes the buffer to SPI, prepended with a command word. + */ +static int hm2_spix_write(hm2_lowlevel_io_t *llio, rtapi_u32 addr, const void *buffer, int size) +{ + spix_board_t *brd = (spix_board_t *)llio; + int txlen = size / sizeof(uint32_t); // uint32_t words to transmit + uint32_t txbuf[SPIX_MAX_MSG]; // local buffer for entire transfer + + if(size == 0) + return 1; // Nothing to do, return success + if((size % sizeof(uint32_t)) || txlen + 1 > SPIX_MAX_MSG) + return 0; // -EINVAL; + + txbuf[0] = spix_cmd_write(addr, txlen, true); // Setup write command + memcpy(&txbuf[1], buffer, size); // Setup write data + return brd->port->transfer(brd->port, txbuf, txlen + 1, 0); // Do transfer +} + +/* + * HM2 interface: Read buffer from SPI + * Reads from SPI after sending the appropriate command. Sends one word with + * the command followed by writing zeros while reading. + */ +static int hm2_spix_read(hm2_lowlevel_io_t *llio, rtapi_u32 addr, void *buffer, int size) +{ + spix_board_t *brd = (spix_board_t *)llio; + int rxlen = size / sizeof(uint32_t); // uint32_t words to receive + uint32_t rxbuf[SPIX_MAX_MSG]; // local buffer for entire transfer + int rv; + + if(size == 0) + return 1; // Nothing to do, return success + if((size % sizeof(uint32_t)) || rxlen + 1 > SPIX_MAX_MSG) + return 0; // -EINVAL; + + memset(rxbuf, 0, sizeof(rxbuf)); // Clear buffer; reads stuff zero writes + rxbuf[0] = spix_cmd_read(addr, rxlen, true); // Setup read command + rv = brd->port->transfer(brd->port, rxbuf, rxlen + 1, 1); // Do transfer + memcpy(buffer, &rxbuf[1], size); // Copy received data (even with errors...) + return rv; +} + +/* + * HM2 interface: Queue read + * Collects the read address and buffer for bulk-read later on. + */ +static int hm2_spix_queue_read(hm2_lowlevel_io_t *llio, rtapi_u32 addr, void *buffer, int size) +{ + spix_board_t *brd = (spix_board_t *)llio; + int rxlen = size / sizeof(uint32_t); + + if(size == 0) + return 1; // Nothing to do, return success + if((size % sizeof(uint32_t)) || rxlen + 1 > SPIX_MAX_MSG) + return 0; // -EINVAL; + + if(buffer_check_room(&brd->rbuf, rxlen + 1, sizeof(uint32_t))) { + LL_ERR("Failed to allocate read buffer memory\n"); + return 0; // -ENOMEM; + } + + if(buffer_check_room(&brd->rref, 1, sizeof(rxref_t))) { + LL_ERR("Failed to allocate read queue reference memory\n"); + return 0; // -ENOMEM; + } + + // Add a reference control structure to remember where the data will be put + // after the read is executed + rxref_t *ref = &((rxref_t *)brd->rref.ptr)[brd->rref.n]; + ref->ptr = buffer; + ref->size = size; + ref->idx = brd->rbuf.n + 1; // offset 0 is command, 1 is data + brd->rref.n += 1; + + uint32_t *rbptr = (uint32_t *)brd->rbuf.ptr; + rbptr[brd->rbuf.n] = spix_cmd_read(addr, rxlen, true); // The read command + memset(&rbptr[brd->rbuf.n + 1], 0, rxlen * sizeof(uint32_t)); // Fill zeros as data + brd->rbuf.n += rxlen + 1; + + return 1; +} + +/* + * HM2 interface: Send queued reads + * Performs a SPI transfer of all collected read requests in one burst and + * copies back the data received in the individual buffers. + */ +static int hm2_spix_send_queued_reads(hm2_lowlevel_io_t *llio) +{ + uint32_t cookie[3] = {0, 0, 0}; + spix_board_t *brd = (spix_board_t *)llio; + int rv; + + // Add a cookie read at the end of the queued reads to verify comms + hm2_spix_queue_read(llio, HM2_ADDR_IOCOOKIE, cookie, sizeof(cookie)); + + rv = brd->port->transfer(brd->port, brd->rbuf.ptr, brd->rbuf.n, 1); + if(rv > 0) { + // The transfer read the data into the read buffer. Now copy it into + // the individual read requests' buffers. + size_t i; + for(i = 0; i < brd->rref.n; i++) { + rxref_t *rxref = &((rxref_t *)brd->rref.ptr)[i]; + memcpy(rxref->ptr, &((uint32_t *)brd->rbuf.ptr)[rxref->idx], rxref->size); + } + + // Check the cookie read. Its an IO error if it does not match + if(memcmp(cookie, iocookie, sizeof(iocookie))) + rv = 0; //-EIO; + } + brd->rbuf.n = 0; // Reset the queue buffers + brd->rref.n = 0; + + return rv; +} + +/* + * HM2 interface: Receive queued reads + * This is a no-op in SPI. The data was already received when the transfer was + * performed in hm2_spix_send_queued_reads() above. The data was copied to + * the requester(s) immediately after the transfer. + */ +static int hm2_spix_receive_queued_reads(hm2_lowlevel_io_t *llio) +{ + (void)llio; + return 1; +} + +/* + * HM2 interface: Queue write + * Collects the write address and data for bulk-write later on. + */ +static int hm2_spix_queue_write(hm2_lowlevel_io_t *llio, rtapi_u32 addr, const void *buffer, int size) +{ + spix_board_t *brd = (spix_board_t *)llio; + int txlen = size / sizeof(uint32_t); + + if(size == 0) + return 1; // Nothing to do, return success + if((size % sizeof(uint32_t)) || txlen + 1 > SPIX_MAX_MSG) + return 0; // -EINVAL; + + if(buffer_check_room(&brd->wbuf, txlen + 1, sizeof(uint32_t))) { + LL_ERR("Failed to allocate write buffer memory\n"); + return 0; // -ENOMEM; + } + + uint32_t *wbptr = (uint32_t *)brd->wbuf.ptr; + wbptr[brd->wbuf.n] = spix_cmd_write(addr, txlen, true); // The write command + memcpy(&wbptr[brd->wbuf.n + 1], buffer, txlen * sizeof(uint32_t)); // The data + brd->wbuf.n += txlen + 1; + return 1; +} + +/* + * HM2 interface: Send queued writes + * Performs a SPI transfer of all collected write requests in one burst. + */ +static int hm2_spix_send_queued_writes(hm2_lowlevel_io_t *llio) +{ + spix_board_t *brd = (spix_board_t *)llio; + int rv = brd->port->transfer(brd->port, brd->wbuf.ptr, brd->wbuf.n, 0); + brd->wbuf.n = 0; // Reset the queue buffer + return rv; +} + +/*********************************************************************/ + +// Counting ones in a word is the also known as the Hamming weight or +// population count. The "best" algorithm is SWAR. The nibble-lookup seems to +// be a good alternative. Anyway, this routine is only called on a cookie +// error and has no real speed criteria. +// Newer GCC has a __builtin_popcount() to get the right number, but that may +// not be available on the current compiler. +static inline unsigned count_ones(uint32_t val) +{ + // Number of ones in a nibble + static const unsigned nibble_table[16] = { 0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4 }; + unsigned i; + for(i = 0; val; val >>= 4) { + i += nibble_table[val & 0x0f]; + } + return i; +} + +static int32_t check_cookie(spix_board_t *board) +{ + uint32_t cookie[4] = {0, 0, 0, 0}; + uint32_t ca; + uint32_t co; + const spix_port_t *port = board->port; + + // We read four (4) 32-bit words. The first three are the cookie and + // the fourth entry is the idrom address offset. The offset is used in + // the call to get the idrom if we successfully match a cookie. + if(!board->llio.read(&board->llio, HM2_ADDR_IOCOOKIE, cookie, sizeof(cookie))) + return -ENODEV; + + if(!memcmp(cookie, iocookie, sizeof(iocookie)) && cookie[3] < 0x10000) { + LL_INFO("Cookie read: %08x %08x %08x, idrom@%04x\n", cookie[0], cookie[1], cookie[2], cookie[3]); + return (int32_t)cookie[3]; // The cookie got read correctly + } + + LL_ERR("%s: Invalid cookie, read: %08x %08x %08x %08x," + " expected: %08x %08x %08x followed by a value less than 0x10000\n", + port->name, + cookie[0], cookie[1], cookie[2], cookie[3], + iocookie[0], iocookie[1], iocookie[2]); + + // Lets see if we can tell why it went wrong + ca = cookie[0] & cookie[1] & cookie[2]; // All ones -> ca == ones + co = cookie[0] | cookie[1] | cookie[2]; // All zero -> co == zero + + if((!co && port->miso_pull == SPIX_PULL_DOWN) || (ca == 0xffffffff && port->miso_pull == SPIX_PULL_UP)) { + LL_ERR("%s: No drive seen on MISO line (kept at pull-%s level)." + " No board connected or bad connection?\n", + port->name, port->miso_pull == SPIX_PULL_DOWN ? "down" : "up"); + } else if(!co || ca == 0xffffffff) { + LL_ERR("%s MISO line stuck at %s level." + " Maybe bad connection, a short-circuit or no board attached?\n", + port->name, !co ? "low" : "high"); + } else { + // If you read too fast, then the bit-setup times are not satisfied and + // the input may be shifted by one bit-clock. Depending transfer width, + // every 8th, 16th or 32nd bit may not arrive soon enough and the + // previous data will be clocked in. + // + // We can detect this eventuality by checking the cookie against a + // bit-shifted version and mask the bits that may have fallen off the + // cliff. If the cookie matches (all zeroes in the XOR result), then it + // is most likely that the read-clock frequency is too high. + // + unsigned ones; + unsigned i; + uint32_t mask = ~0x00000001; // for 32-bit transfers + if(port->width == 8) + mask = ~0x01010101; + else if(port->width == 16) + mask = ~0x00010001; + + for(ones = i = 0; i < 3; i++) { + ones += count_ones((iocookie[i] ^ (cookie[i] << 1)) & mask); + } + if(!ones) { + // No ones in the XOR result -> the cookie is probably bit-shifted + LL_ERR("%s: MISO input is bit-shifted by one bit." + " SPI read clock frequency probably too high.\n", + port->name); + } else { + // More bits are wrong, erratic behaviour + LL_ERR("%s: MISO input does not match any expected bit-pattern (>= %u bit difference)." + " Maybe SPI read clock frequency too high or noise on the input?\n", + port->name, ones); + } + } + return -ENODEV; +} + +/*************************************************/ +static int probe_board(spix_board_t *board) +{ + const spix_port_t *port = board->port; + int32_t ret; + hm2_idrom_t idrom; + const char *base; + + if((ret = check_cookie(board)) < 0) + return ret; + + LL_INFO("%s: Valid cookie matched, idrom@%04x\n", port->name, ret); + + // Read the board identification. + // The IDROM address offset is returned in the cookie check and the + // board_name offset is added (see hm2_idrom_t in hostmot2.h) + if(!board->llio.read(&board->llio, (uint32_t)ret, &idrom, sizeof(hm2_idrom_t))) { + LL_ERR("%s: Board idrom read failed\n", port->name); + return -EIO; // Cookie could be read, so this is a comms error + } + + // Detect board name and fill in informational values + if(!(base = set_llio_info_spi(&board->llio, &idrom))) + return -ENOENT; + + LL_INFO("%s: Base: %s.%d\n", port->name, base, board->nr); + + rtapi_snprintf(board->llio.name, sizeof(board->llio.name), "%s.%d", base, board->nr); + board->llio.comp_id = comp_id; + board->llio.private = board; // Self reference + + return 0; +} + +/*************************************************/ + +/* Read at most bufsize-1 bytes from fname */ +ssize_t spix_read_file(const char *fname, void *buffer, size_t bufsize) +{ + int fd; + ssize_t len; + + memset(buffer, 0, bufsize); + + if(!(fd = open(fname, O_RDONLY))) { + LL_ERR("Cannot open '%s' for read (errno=%d: %s)\n", fname, errno, strerror(errno)); + return -errno; + } + + while(1) { + len = read(fd, buffer, bufsize - 1); + if(len == 0) { + LL_ERR("Nothing read from '%s', file contains no data\n", fname); + } else if(len < 0) { + if(errno == EINTR) + continue; // Interrupted syscall, retry read + LL_ERR("Error reading from '%s' (errno=%d: %s)\n", fname, errno, strerror(errno)); + return -errno; + } + break; + } + close(fd); + return len; +} + +/*************************************************/ + +static int spix_setup(void) +{ + int i, j; + char buf[256]; + ssize_t buflen; + char *cptr; + const int DTC_MAX = 8; + const char *dtcs[DTC_MAX + 1]; // Last entry will always be NULL + + // Setup the clock rate settings from the arguments. + // The driver is responsible for actual checking min/max clock frequency. + for(i = 0; i < SPIX_MAX_BOARDS; i++) { + if(spiclk_rate[i] < 1) // If not specified + spiclk_rate[i] = spiclk_rate[0]; // use first + + if(spiclk_rate_rd[i] < 1) // If not specified + spiclk_rate_rd[i] = spiclk_rate[i]; // use write rate as read rate + + } + + // Set process-level message level if requested + if(spi_debug >= RTAPI_MSG_NONE && spi_debug <= RTAPI_MSG_ALL) + rtapi_set_msg_level(spi_debug); + + // Read the 'compatible' string-list from the device-tree + buflen = spix_read_file("/proc/device-tree/compatible", buf, sizeof(buf)); + if(buflen <= 0) { + LL_ERR("Failed to read platform identity.\n"); + return buflen; // negative errno from read_file() + } + + // Decompose the device-tree buffer into a string-list with the pointers to + // each string in dtcs. Don't go beyond the buffer's size. + memset(dtcs, 0, sizeof(dtcs)); + for(i = 0, cptr = buf; i < DTC_MAX && cptr; i++) { + dtcs[i] = cptr; + j = strlen(cptr); + if((cptr - buf) + j + 1 < buflen) + cptr += j + 1; + else + cptr = NULL; + } + + // If the driver is forced, check if it actually exists + if(force_driver) { + for(i = 0; i < NELEM(drivers); i++) { + if(!strcmp(force_driver, drivers[i]->name)) + break; + } + if(i >= NELEM(drivers)) { + LL_ERR("Unsupported hardware driver '%s' passed to force_driver option\n", force_driver); + return -ENODEV; + } + } + + // Let each driver do a detect and stop when a match is found. + for(i = 0; i < NELEM(drivers); i++) { + if(force_driver && strcmp(force_driver, drivers[i]->name)) + continue; + if(!drivers[i]->detect(dtcs)) { + hwdriver = drivers[i]; + break; + } + } + + if(!hwdriver) { + if(force_driver) { + LL_ERR("Unsupported platform: '%s' for forced driver '%s'\n", buf, hwdriver->name); + } else { + LL_ERR("Unsupported platform: '%s'\n", buf); + } + return -ENODEV; + } + + if((i = hwdriver->setup(spi_probe)) < 0) { // Let the hardware driver do its thing + LL_INFO("Failed to initialize hardware driver\n"); + return i; + } + + LL_INFO("Platform: '%s' (%s)\n", hwdriver->model, hwdriver->dtc); + + memset(boards, 0, sizeof(boards)); + + // Follows SPI0/CE0, SPI0/CE1, SPI1/CE0, SPI1/CE1 and SPI1/CE2 + for(j = i = 0; i < SPIX_MAX_BOARDS; i++) { + const spix_port_t *port; + + if(!(spi_probe & (1 << i))) // Only probe if enabled + continue; + + if(NULL == (port = hwdriver->open(i, spiclk_rate[j], spiclk_rate_rd[j]))) { + LL_INFO("Failed to open hardware port index %d\n", i); + return i; + } + + LL_INFO("%s opened\n", port->name); + + boards[j].nr = j; + boards[j].port = port; + boards[j].llio.read = hm2_spix_read; + boards[j].llio.write = hm2_spix_write; + if(!spi_noqueue) { + boards[j].llio.queue_read = hm2_spix_queue_read; + boards[j].llio.send_queued_reads = hm2_spix_send_queued_reads; + boards[j].llio.receive_queued_reads = hm2_spix_receive_queued_reads; + boards[j].llio.queue_write = hm2_spix_queue_write; + boards[j].llio.send_queued_writes = hm2_spix_send_queued_writes; + } + + if((i = probe_board(&boards[j])) < 0) { + return i; + } + + if((i = hm2_register(&boards[j].llio, config[j])) < 0) { + LL_ERR("%s: hm2_register() failed.\n", port->name); + return i; + } + + j++; // Next board + } + + return j > 0 ? 0 : -ENODEV; +} + +/*************************************************/ +static void spix_cleanup(void) +{ + int i; + // Cleanup memory allocations + for(i = 0; i < SPIX_MAX_BOARDS; i++) { + buffer_free(&boards[i].wbuf); + buffer_free(&boards[i].rbuf); + buffer_free(&boards[i].rref); + hwdriver->close(boards[i].port); + } + + if(hwdriver) { + hwdriver->cleanup(); + hwdriver = NULL; + } +} + +/*************************************************/ +int rtapi_app_main() +{ + int ret; + + if((comp_id = ret = hal_init(HM2_LLIO_NAME)) < 0) + goto fail; + + if((ret = spix_setup()) < 0) + goto fail; + + hal_ready(comp_id); + return 0; + +fail: + spix_cleanup(); + return ret; +} + +/*************************************************/ +void rtapi_app_exit(void) +{ + spix_cleanup(); + hal_exit(comp_id); +} + +// vim: ts=4 diff --git a/src/hal/drivers/mesa-hostmot2/hwregaccess.h b/src/hal/drivers/mesa-hostmot2/hwregaccess.h new file mode 100644 index 00000000000..c925c8fdb4c --- /dev/null +++ b/src/hal/drivers/mesa-hostmot2/hwregaccess.h @@ -0,0 +1,81 @@ +/* + * This is a component for RaspberryPi support for linuxcnc. + * Copyright (c) 2024 B.Stultiens + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, see . + */ +#ifndef HAL_HM2_HWREGACCESS_H +#define HAL_HM2_HWREGACCESS_H + +/* Register access modifiers */ +#if !defined(__I) && !defined(__O) && !defined(__IO) +#ifdef __cplusplus +#define __I volatile /* read only permission */ +#else +#define __I volatile const /* read only permission */ +#endif +#define __O volatile /* write only permission */ +#define __IO volatile /* read/write permission */ +#else +#error "Possible define collision for __I, __O and __IO" +#endif + +/* Forced inline expansion */ +#define HWREGACCESS_ALWAYS_INLINE __attribute__((always_inline)) + +/* + * Synchronisation primitives + * - rmb(): Read memory barrier + * - wmb(): Write memory barrier + */ +HWREGACCESS_ALWAYS_INLINE static inline void rmb(void) { __sync_synchronize(); } +HWREGACCESS_ALWAYS_INLINE static inline void wmb(void) { __sync_synchronize(); } + +/* + * Synchronized read and write to peripheral memory. + * Ensures coherency between cores, cache and peripherals + */ +HWREGACCESS_ALWAYS_INLINE static inline uint32_t reg_rd(const volatile void *addr) +{ + uint32_t val; + val = *(volatile uint32_t *)addr; + rmb(); + return val; +} + +HWREGACCESS_ALWAYS_INLINE static inline void reg_wr(const volatile void *addr, uint32_t val) +{ + wmb(); + *(volatile uint32_t *)addr = val; +} + +/* + * These are the _unsynchronised_ versions. These may limit the latency of + * reads and writes. For example, PCIe transactions may possibly be pipe-lined + * with multiple subsequent raw read/write calls. + */ +HWREGACCESS_ALWAYS_INLINE static inline uint32_t reg_rd_raw(const volatile void *addr) +{ + uint32_t val; + val = *(volatile uint32_t *)addr; + return val; +} + +HWREGACCESS_ALWAYS_INLINE static inline void reg_wr_raw(const volatile void *addr, uint32_t val) +{ + *(volatile uint32_t *)addr = val; +} + +#endif +// vim: ts=4 diff --git a/src/hal/drivers/mesa-hostmot2/llio_info.c b/src/hal/drivers/mesa-hostmot2/llio_info.c new file mode 100644 index 00000000000..8e6a506ce91 --- /dev/null +++ b/src/hal/drivers/mesa-hostmot2/llio_info.c @@ -0,0 +1,257 @@ +/* + * This is a component for hostmot2 board identification + * Copyright (c) 2024 B.Stultiens + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, see . + */ + +#include +#include + +#include + +#include "hostmot2.h" +#include "hostmot2-lowlevel.h" +#include "llio_info.h" + +typedef struct __info_entry_t { + char board_name[8]; + const char *base_name; + int num_ioport_connectors; + int pins_per_connector; + const char *ioport_connector_name[ANYIO_MAX_IOPORT_CONNECTORS]; + const char **io_connector_pin_names; + int num_leds; + const char *fpga_part_number; + int (*hook)(hm2_lowlevel_io_t *llio, const hm2_idrom_t *idrom); +} info_entry_t; + +static const char *hm2_7c80_pin_names[] = { + "TB07-02/TB07-03", /* Step/Dir/Misc 5V out */ + "TB07-04/TB07-05", + "TB08-02/TB08-03", + "TB08-04/TB08-05", + "TB09-02/TB09-03", + "TB09-04/TB09-05", + "TB10-02/TB10-03", + "TB10-04/TB10-05", + "TB11-02/TB11-03", + "TB11-04/TB11-05", + "TB12-02/TB12-03", + "TB12-04/TB12-05", + "TB03-03/TB04-04", /* RS-422/RS-485 interface */ + "TB03-05/TB04-06", + "TB03-05/TB03-06", + "TB04-01/TB04-02", /* Encoder */ + "TB04-04/TB04-05", + "TB04-07/TB04-08", + "TB05-02", /* Spindle */ + "TB05-02", + "TB05-05/TB05-06", + "TB05-07/TB05-08", + "Internal InMux0", /* InMux */ + "Internal InMux1", + "Internal InMux2", + "Internal InMux3", + "Internal InMux4", + + "Internal InMuxData", + "TB13-01/TB13-02", /* SSR */ + "TB13-03/TB13-04", + "TB13-05/TB13-06", + "TB13-07/TB13-08", + "TB14-01/TB14-02", + "TB14-03/TB14-04", + "TB14-05/TB14-06", + "TB14-07/TB14-08", + "Internal SSR", + "P1-01/DB25-01", /* P1 parallel expansion */ + "P1-02/DB25-14", + "P1-03/DB25-02", + "P1-04/DB25-15", + "P1-05/DB25-03", + "P1-06/DB25-16", + "P1-07/DB25-04", + "P1-08/DB25-17", + "P1-09/DB25-05", + "P1-11/DB25-06", + "P1-13/DB25-07", + "P1-15/DB25-08", + "P1-17/DB25-09", + "P1-19/DB25-10", + "P1-21/DB25-11", + "P1-23/DB25-12", + "P1-25/DB25-13", +}; + +static const char *hm2_7c81_pin_names[] = { + "P1-01/DB25-01", + "P1-02/DB25-14", + "P1-03/DB25-02", + "P1-04/DB25-15", + "P1-05/DB25-03", + "P1-06/DB25-16", + "P1-07/DB25-04", + "P1-08/DB25-17", + "P1-09/DB25-05", + "P1-11/DB25-06", + "P1-13/DB25-07", + "P1-15/DB25-08", + "P1-17/DB25-09", + "P1-19/DB25-10", + "P1-21/DB25-11", + "P1-23/DB25-12", + "P1-25/DB25-13", + "J5-TX0", + "J6-TX1", + + "P2-01/DB25-01", + "P2-02/DB25-14", + "P2-03/DB25-02", + "P2-04/DB25-15", + "P2-05/DB25-03", + "P2-06/DB25-16", + "P2-07/DB25-04", + "P2-08/DB25-17", + "P2-09/DB25-05", + "P2-11/DB25-06", + "P2-13/DB25-07", + "P2-15/DB25-08", + "P2-17/DB25-09", + "P2-19/DB25-10", + "P2-21/DB25-11", + "P2-23/DB25-12", + "P2-25/DB25-13", + "J5-TXEN0", + "J6-TXEN1", + + "P7-01/DB25-01", + "P7-02/DB25-14", + "P7-03/DB25-02", + "P7-04/DB25-15", + "P7-05/DB25-03", + "P7-06/DB25-16", + "P7-07/DB25-04", + "P7-08/DB25-17", + "P7-09/DB25-05", + "P7-11/DB25-06", + "P7-13/DB25-07", + "P7-15/DB25-08", + "P7-17/DB25-09", + "P7-19/DB25-10", + "P7-21/DB25-11", + "P7-23/DB25-12", + "P7-25/DB25-13", + "P5-RX0", + "P6-RX1" +}; + +int hook_7i43(hm2_lowlevel_io_t *llio, const hm2_idrom_t *idrom) +{ + switch(idrom->fpga_size) { + case 200: llio->fpga_part_number = "3s200tq144"; return 0; + default: + rtapi_print_msg(RTAPI_MSG_WARN, "hook_7i43(): Unknown fpga_size: %d\n", idrom->fpga_size); + /* Fallthrough */ + case 400: llio->fpga_part_number = "3s400tq144"; return 0; + } +} + +static const info_entry_t spiboards[] = { + { + .board_name = "MESA7C80", + .base_name = "hm2_7c80", + .num_ioport_connectors = 2, + .pins_per_connector = 27, + .ioport_connector_name = { "Embedded I/O", "Embedded I/O + P1 expansion" }, + .io_connector_pin_names = hm2_7c80_pin_names, + .num_leds = 4, + .fpga_part_number = "xc6slx9tq144", + .hook = NULL, + }, + { + .board_name = "MESA7C81", + .base_name = "hm2_7c81", + .num_ioport_connectors = 3, + .pins_per_connector = 19, + .ioport_connector_name = { "P1", "P2", "P7" }, + .io_connector_pin_names = hm2_7c81_pin_names, + .num_leds = 4, + .fpga_part_number = "xc6slx9tq144", + .hook = NULL, + }, + { + .board_name = "MESA7I90", + .base_name = "hm2_7i90", + .num_ioport_connectors = 3, + .pins_per_connector = 24, + .ioport_connector_name = { "P1", "P2", "P3" }, + .io_connector_pin_names = NULL, + .num_leds = 2, + .fpga_part_number ="xc6slx9tq144", + .hook = NULL, + }, + { + .board_name = "MESA7I43", + .base_name = "hm2_7i43", + .num_ioport_connectors = 2, + .pins_per_connector = 24, + .ioport_connector_name = { "P4", "P3" }, + .io_connector_pin_names = NULL, + .num_leds = 8, + .fpga_part_number = "3s400tq144", + .hook = hook_7i43, + }, +}; + +#define NELEM(x) (sizeof(x) / sizeof(*(x))) + +const char *set_llio_info_spi(hm2_lowlevel_io_t *llio, const hm2_idrom_t *idrom) +{ + int i, j; + char buf[sizeof(idrom->board_name)+1]; + + /* In the far future, when there are too many boards, use bsearch */ + /* With few boards, linear search is faster */ + for(i = 0; i < NELEM(spiboards); i++) { + if(!memcmp(idrom->board_name, spiboards[i].board_name, sizeof(idrom->board_name))) { + llio->num_ioport_connectors = spiboards[i].num_ioport_connectors; + llio->pins_per_connector = spiboards[i].pins_per_connector; + for(j = 0; j < ANYIO_MAX_IOPORT_CONNECTORS; j++) + llio->ioport_connector_name[j] = spiboards[i].ioport_connector_name[j]; + llio->io_connector_pin_names = spiboards[i].io_connector_pin_names; + llio->num_leds = spiboards[i].num_leds; + llio->fpga_part_number = spiboards[i].fpga_part_number; + /* Call the hook if defined */ + if(spiboards[i].hook) { + /* Hooks should return zero on success */ + if(0 != spiboards[i].hook(llio, idrom)) + return NULL; + } + return spiboards[i].base_name; + } + } + + memcpy(buf, idrom->board_name, sizeof(idrom->board_name)); + buf[sizeof(idrom->board_name)] = 0; + for(i = 0; i < sizeof(idrom->board_name); i++) { + if(!isprint(buf[i])) + buf[i] = '?'; + } + rtapi_print_msg(RTAPI_MSG_ERR, "set_llio_info_spi(): Unknown hostmot2 board name: %.8s\n", buf); + return NULL; +} + +/* vim: ts=4 + */ diff --git a/src/hal/drivers/mesa-hostmot2/llio_info.h b/src/hal/drivers/mesa-hostmot2/llio_info.h new file mode 100644 index 00000000000..c4d035a4a96 --- /dev/null +++ b/src/hal/drivers/mesa-hostmot2/llio_info.h @@ -0,0 +1,32 @@ +/* + * This is a component for hostmot2 board identification + * Copyright (c) 2024 B.Stultiens + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, see . + */ +#ifndef HAL_HM2_LLIO_INFO_H +#define HAL_HM2_LLIO_INFO_H + +/* + * Search the list of supported boards for the idrom.board_name and sets all + * administrative parameters of the hm2_lowlevel_t structure. + * + * Returns the name of the board that can be used as a base name for + * hm2_register(). + */ +const char *set_llio_info_spi(hm2_lowlevel_io_t *llio, const hm2_idrom_t *idrom); + +#endif +/* vim: ts=4 + */ diff --git a/src/hal/drivers/mesa-hostmot2/rp1dev.h b/src/hal/drivers/mesa-hostmot2/rp1dev.h new file mode 100644 index 00000000000..2e28432bd65 --- /dev/null +++ b/src/hal/drivers/mesa-hostmot2/rp1dev.h @@ -0,0 +1,414 @@ +/* + * This is a component for RaspberryPi 5 to hostmot2 over SPI for linuxcnc. + * Copyright (c) 2024 B.Stultiens + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, see . + */ + +#ifndef HAL_HM2_RP1DEV_H +#define HAL_HM2_RP1DEV_H + +#include "hwregaccess.h" + +/* + * RP1 peripheral description see: + * - https://datasheets.raspberrypi.com/rp1/rp1-peripherals.pdf + * + * *** Note: + * Lots of RP1 stuff is not used by the driver and is ommitted in the + * defines in this header file. + */ + +/* Bit and mask helpers */ +#define RP1BIT(x) (1 << (x)) +#define RP1MASK(x,y) ((x) << (y)) + +/* + * The RP1 is PCIe connected and memory mapped. There are several devices + * attached to the RP1 PCIe connection spanning two "bars". Bar0 has 64k of + * shared RAM attached. Bar1 is where the RP1 peripherals are mapped. The + * translation address is visible in the device-tree if you look at: + * /proc/device-tree/axi/pcie@120000/ranges + * Or, look at the linux source: + * linux-src-tree.../arch/arm64/boot/dts/broadcom/bcm2712.dts + * Setting this address here at a fixed value should not give us problems + * because of all the devices out there already (famous last words). + */ +#define RP1_PCIE_BAR1_ADDR 0x1f00000000 /* Base address in /dev/mem */ +#define RP1_PCIE_BAR1_LEN 0x0000400000 /* Map length for access */ + +/* Register offsets */ +#define RP1_IO_BANK0_OFFSET 0xd0000 /* 28 GPIOs from 40-pin header */ +#define RP1_IO_BANK1_OFFSET 0xd4000 +#define RP1_IO_BANK2_OFFSET 0xd8000 + +#define RP1_SYS_RIO0_OFFSET 0xe0000 /* GPIO access */ +#define RP1_SYS_RIO1_OFFSET 0xe4000 +#define RP1_SYS_RIO2_OFFSET 0xe8000 + +#define RP1_RW_OFFSET 0x0000 /* The address offset for bit manipulation */ +#define RP1_XOR_OFFSET 0x1000 +#define RP1_SET_OFFSET 0x2000 +#define RP1_CLR_OFFSET 0x3000 + +#define RP1_PADS_BANK0_OFFSET 0xf0000 /* Pad control, drive strength, pull-up/down, etc. */ +#define RP1_PADS_BANK1_OFFSET 0xf4000 +#define RP1_PADS_BANK2_OFFSET 0xf8000 + +#define RP1_SPI0_OFFSET 0x50000 +#define RP1_SPI1_OFFSET 0x54000 +#define RP1_SPI2_OFFSET 0x58000 +#define RP1_SPI3_OFFSET 0x5c000 +#define RP1_SPI4_OFFSET 0x60000 +#define RP1_SPI5_OFFSET 0x64000 +/* SPI6, SPI7 and SPI8 are not mappable on the 40-pin header */ + +/* + * SPI to GPIO pin mappings + * Port | CLK | MOSI | MISO | CE0 | CE1 | CE2 | CE3 |FuncSel + * ------+-----+------+------+-----+-----+-----+------------- + * SPI0 | 11 | 10 | 9 | 8 | 7 | 3 | 2 | 0x00 *** SPI0_CE2 and SPI0_CE3 overlap I2C + * SPI1 | 21 | 20 | 19 | 18 | 17 | 16 | | 0x00 + * SPI2 | 3 | 2 | 1 | 0 | 24 | | | 0x08 *** SPI2_CE0 and SPI2_MISO overlap the ID EEPROM + * SPI3 | 7 | 6 | 5 | 4 | 25 | | | 0x08 *** SPI3_CLK overlaps SPI0_CE1 + * SPI4 | 11 | 9 | 10 | 8 | | | | 0x08 *** SPI4 is slave only and overlaps SPI0 + * SPI5 | 15 | 14 | 13 | 12 | 26 | | | 0x08 *** SPI5_CLK and SPI5_MOSI overlap standard UART + * + * - SPI0 Dual mode could be supported using GPIO 0 and 1, but probably + * requires that probing the ID EEPROM be disabled in the kernel because the + * extra GPIOs are assigned to a dedicated I2C port for HAT probing. + * - SPI0 has two extra CE lines that overlap the default I2C port of the RPi. + * These cannot be used if, for example, a touch display is attached. + * - SPI2 has the problem of overlapping the ID EEPROM pins GPIO 0 and 1. + * Probably better not use it. + * - SPI3 has a clock line overlap with SPI0's CE1. Otherwise it should be + * usable is you do not need the GPCLKx lines. + * - SPI4 is a slave device and cannot be used in our context. + * - SPI5 has clock and mosi overlap with the default UART (/dev/ttyAMA0). We + * should keep access to the TTY. + * + * All considering, just using the plain old assignments is far better than + * the problems we could run into with the other ports. Therefore, the only + * supported ports are: SPI0/CE[01] and SPI1/CE[012]. + */ + +/* + * GPIO data and direction + */ +typedef struct __rp1_rio_regs_t { + __O uint32_t out; /* 0x00 read/write gpio pins */ + __IO uint32_t oe; /* 0x04 data direction (0=out, 1=in) */ + union { + __I uint32_t nosync_in; /* 0x08 direct access to input pins */ + __I uint32_t in; + }; + __I uint32_t sync_in; /* 0x0c input through 2-stage synchroniser */ +} rp1_rio_regs_t; + +typedef struct __rp1_rio_t { + rp1_rio_regs_t rw; + __I uint32_t reserved1[((RP1_XOR_OFFSET - RP1_RW_OFFSET - sizeof(rp1_rio_regs_t)) / sizeof(uint32_t))]; + rp1_rio_regs_t xor; + __I uint32_t reserved2[((RP1_SET_OFFSET - RP1_XOR_OFFSET - sizeof(rp1_rio_regs_t)) / sizeof(uint32_t))]; + rp1_rio_regs_t set; + __I uint32_t reserved3[((RP1_CLR_OFFSET - RP1_SET_OFFSET - sizeof(rp1_rio_regs_t)) / sizeof(uint32_t))]; + rp1_rio_regs_t clr; + /* There is room below, but we never need to access it */ +} rp1_rio_t; + +/* + * Alternate function specification see RP1 documentation + */ +#define RP1_FSEL_SYS_RIO 5 /* Pin as GPIO */ +#define RP1_FSEL_ALT0 0 /* The RPi default alternate assignments */ + +typedef struct __rp1_gpio_stat_ctrl_t { + __I uint32_t status; /* GPIO status */ + __IO uint32_t ctrl; /* GPIO control */ +} rp1_gpio_stat_ctrl_t; + +#define RP1_GPIO_STAT_IRQTOPROC_BIT 29 +#define RP1_GPIO_STAT_IRQCOMBINED_BIT 28 +#define RP1_GPIO_STAT_EVENT_DB_LEVEL_HIGH_BIT 27 +#define RP1_GPIO_STAT_EVENT_DB_LEVEL_LOW_BIT 26 +#define RP1_GPIO_STAT_EVENT_F_EDGE_HIGH_BIT 25 +#define RP1_GPIO_STAT_EVENT_F_EDGE_LOW_BIT 24 +#define RP1_GPIO_STAT_EVENT_LEVEL_HIGH_BIT 23 +#define RP1_GPIO_STAT_EVENT_LEVEL_LOW_BIT 22 +#define RP1_GPIO_STAT_EVENT_EDGE_HIGH_BIT 21 +#define RP1_GPIO_STAT_EVENT_EDGE_LOW_BIT 20 +#define RP1_GPIO_STAT_INTOPERI_BIT 19 +#define RP1_GPIO_STAT_INFILTERED_BIT 18 +#define RP1_GPIO_STAT_INFROMPAD_BIT 17 +#define RP1_GPIO_STAT_INISDIRECT_BIT 16 +#define RP1_GPIO_STAT_OETOPAD_BIT 13 +#define RP1_GPIO_STAT_OEFROMPERI_BIT 12 +#define RP1_GPIO_STAT_OUTTOPAD_BIT 9 +#define RP1_GPIO_STAT_OUTFROMPERI_BIT 8 + +#define RP1_GPIO_CTRL_IRQOVER_BIT 30 +#define RP1_GPIO_CTRL_IRQRESET_BIT 28 +#define RP1_GPIO_CTRL_IRQMASK_DB_LEVEL_HIGH_BIT 27 +#define RP1_GPIO_CTRL_IRQMASK_DB_LEVEL_LOW_BIT 26 +#define RP1_GPIO_CTRL_IRQMASK_F_EDGE_HIGH_BIT 25 +#define RP1_GPIO_CTRL_IRQMASK_F_EDGE_LOW_BIT 24 +#define RP1_GPIO_CTRL_IRQMASK_LEVEL_HIGH_BIT 23 +#define RP1_GPIO_CTRL_IRQMASK_LEVEL_LOW_BIT 22 +#define RP1_GPIO_CTRL_IRQMASK_EDGE_HIGH_BIT 21 +#define RP1_GPIO_CTRL_IRQMASK_EDGE_LOW_BIT 20 +#define RP1_GPIO_CTRL_INOVER_BIT 16 +#define RP1_GPIO_CTRL_OEOVER_BIT 14 +#define RP1_GPIO_CTRL_OUTOVER_BIT 12 +#define RP1_GPIO_CTRL_F_M_BIT 5 +#define RP1_GPIO_CTRL_FUNCSEL_BIT 0 + +#define RP1_GPIO_CTRL_INOVER_MASK RP1MASK(0x03, RP1_GPIO_CTRL_INOVER_BIT) +#define RP1_GPIO_CTRL_OEOVER_MASK RP1MASK(0x03, RP1_GPIO_CTRL_OEOVER_BIT) +#define RP1_GPIO_CTRL_OUTOVER_MASK RP1MASK(0x03, RP1_GPIO_CTRL_OUTOVER_BIT) +#define RP1_GPIO_CTRL_F_M_MASK RP1MASK(0x7f, RP1_GPIO_CTRL_F_M_BIT) +#define RP1_GPIO_CTRL_FUNCSEL_MASK RP1MASK(0x1f, RP1_GPIO_CTRL_FUNCSEL_BIT) + +#define RP1_GPIO_CTRL_INOVER(v) RP1MASK((v)&0x03, RP1_GPIO_CTRL_INOVER_BIT) +#define RP1_GPIO_CTRL_OEOVER(v) RP1MASK((v)&0x03, RP1_GPIO_CTRL_OEOVER_BIT) +#define RP1_GPIO_CTRL_OUTOVER(v) RP1MASK((v)&0x03, RP1_GPIO_CTRL_OUTOVER_BIT) +#define RP1_GPIO_CTRL_F_M(v) RP1MASK((v)&0x7f, RP1_GPIO_CTRL_F_M_BIT) +#define RP1_GPIO_CTRL_FUNCSEL(v) RP1MASK((v)&0x1f, RP1_GPIO_CTRL_FUNCSEL_BIT) + +#define RP1_GPIO_CTRL_FUNCSEL_ALT0 0 +#define RP1_GPIO_CTRL_FUNCSEL_ALT1 1 +#define RP1_GPIO_CTRL_FUNCSEL_ALT2 2 +#define RP1_GPIO_CTRL_FUNCSEL_ALT3 3 +#define RP1_GPIO_CTRL_FUNCSEL_ALT4 4 +#define RP1_GPIO_CTRL_FUNCSEL_ALT5 5 +#define RP1_GPIO_CTRL_FUNCSEL_ALT6 6 +#define RP1_GPIO_CTRL_FUNCSEL_ALT7 7 +#define RP1_GPIO_CTRL_FUNCSEL_ALT8 8 +#define RP1_GPIO_CTRL_FUNCSEL_DPI RP1_GPIO_CTRL_FUNCSEL_ALT1 +#define RP1_GPIO_CTRL_FUNCSEL_SYS_RIO RP1_GPIO_CTRL_FUNCSEL_ALT5 +#define RP1_GPIO_CTRL_FUNCSEL_PROC_RIO RP1_GPIO_CTRL_FUNCSEL_ALT6 +#define RP1_GPIO_CTRL_FUNCSEL_PIO RP1_GPIO_CTRL_FUNCSEL_ALT7 +#define RP1_GPIO_CTRL_FUNCSEL_NULL 31 + +typedef struct __rp1_io_bank0_t { + rp1_gpio_stat_ctrl_t gpio[28]; /* 0x000 GPIO status and control */ + __I uint32_t reserved0e0[8]; /* 0x0e0 */ + __I uint32_t intr; /* 0x100 raw interrupts */ + __IO uint32_t proc0_inte; /* 0x104 interrupt enable for proc0 */ + __IO uint32_t proc0_intf; /* 0x108 interrupt force for proc0 */ + __I uint32_t proc0_ints; /* 0x10c interrupt status after masking and forcing for proc0 */ + __IO uint32_t proc1_inte; /* 0x110 interrupt enable for proc1 */ + __IO uint32_t proc1_intf; /* 0x114 interrupt force for proc1 */ + __I uint32_t proc1_ints; /* 0x118 interrupt status after masking and forcing for proc1 */ + __IO uint32_t pcie_inte; /* 0x11c interrupt enable for pcie */ + __IO uint32_t pcie_intf; /* 0x120 interrupt force for pcie */ + __I uint32_t pcie_ints; /* 0x124 interrupt status after masking and forcing for pcie */ +} rp1_io_bank0_t; + +typedef struct __rp1_pads_bank0_t { + __IO uint32_t voltage_select; /* 0x00 bank voltage control */ + __IO uint32_t gpio[28]; /* 0x04 pad control registers for each pin */ +} rp1_pads_bank0_t; + +#define RP1_PADS_VOLTAGE_CONTROL_3V3 0 +#define RP1_PADS_VOLTAGE_CONTROL_1V8 1 + +#define RP1_PADS_OD_BIT 7 /* output disable */ +#define RP1_PADS_IE_BIT 6 /* interrupt enable */ +#define RP1_PADS_DRIVE_BIT 4 /* bits 5:4 drive strength */ +#define RP1_PADS_PUE_BIT 3 /* pull up enable */ +#define RP1_PADS_PDE_BIT 2 /* pull down enable */ +#define RP1_PADS_SCHMITT_BIT 1 /* schmitt trigger enable */ +#define RP1_PADS_SLEWFAST_BIT 0 /* slew rate control */ +#define RP1_PADS_OD RP1BIT(RP1_PADS_OD_BIT) +#define RP1_PADS_IE RP1BIT(RP1_PADS_IE_BIT) +#define RP1_PADS_DRIVE_MASK RP1MASK(0x3, RP1_PADS_DRIVE_BIT) +#define RP1_PADS_DRIVE(v) RP1MASK((v) & 0x3, RP1_PADS_DRIVE_BIT) +#define RP1_PADS_PUE RP1BIT(RP1_PADS_PUE_BIT) +#define RP1_PADS_PDE RP1BIT(RP1_PADS_PDE_BIT) +#define RP1_PADS_SCHMITT RP1BIT(RP1_PADS_SCHMITT_BIT) +#define RP1_PADS_SLEWFAST RP1BIT(RP1_PADS_SLEWFAST_BIT) +#define RP1_PADS_DRIVE_2 0 // 2mA drive +#define RP1_PADS_DRIVE_4 1 // 4mA drive +#define RP1_PADS_DRIVE_8 2 // 8mA drive +#define RP1_PADS_DRIVE_12 3 // 12mA drive + +/* + * The Designware SSI + * + * The RP1 SSI implementation has: + * - a fifo depth of 64. + * - 32 bit max. transfer word size (uses CTRLR0_DFS_32) + */ + +#define RP1_SPI_CLK 200000000 /* Apparently, master clock is at 200 MHz */ +#define RP1_SPI_FIFO_LEN 64 /* Fifo len */ + +typedef struct __dw_ssi_t { + __IO uint32_t ctrlr0; /* 0x00 serial data transfer control */ + __IO uint32_t ctrlr1; /* 0x04 data frame count (rx-only and eeprom modes) */ + __IO uint32_t ssienr; /* 0x08 ssi enable/disable */ + __IO uint32_t mwcr; /* 0x0c microwire control */ + __IO uint32_t ser; /* 0x10 slave enable bits */ + __IO uint32_t baudr; /* 0x14 baudrate divider (16 bit and must be even) */ + __IO uint32_t txftlr; /* 0x18 transmit fifo threshold level*/ + __IO uint32_t rxftlr; /* 0x1c receive fifo threshold level */ + __I uint32_t txflr; /* 0x20 transmit fifo level */ + __I uint32_t rxflr; /* 0x24 receive fifo level */ + __I uint32_t sr; /* 0x28 transfer status */ + __IO uint32_t imr; /* 0x2c interrupt mask */ + __I uint32_t isr; /* 0x30 interrupt status (masked by imr) */ + __I uint32_t risr; /* 0x34 raw interrupt status (unmasked) */ + __I uint32_t txoicr; /* 0x38 transmit fifo overflow interrupt clear */ + __I uint32_t rxoicr; /* 0x3c receive fifo overflow interrupt clear */ + __I uint32_t rxuicr; /* 0x40 receive fifo underflow interrupt clear */ + __I uint32_t msticr; /* 0x44 multi-master interrupt clear */ + __I uint32_t icr; /* 0x48 interrupt clear */ + __IO uint32_t dmacr; /* 0x4c dma control */ + __IO uint32_t dmatdlr; /* 0x50 dma transmit data level */ + __IO uint32_t dmardlr; /* 0x54 dma receive data level */ + __I uint32_t idr; /* 0x58 peripheral identification code */ + __I uint32_t ssi_version_id; /* 0x5c dw ssi hardware version ident */ + union { + struct { + __IO uint32_t dr0; /* 0x60-0xec data register 16/32-bit */ + __I uint32_t reserved_dr[35]; + }; + __IO uint32_t drx[36]; /* All these act the same according to the docs, use dr0 */ + }; + __IO uint32_t rx_sample_dly; /* 0xf0 receive sample delay */ + __IO uint32_t spi_ctrlr0; /* 0xf4 multimode control*/ + __IO uint32_t tx_drive_edge; /* 0xf8 transmit drive edge */ + __I uint32_t rsvd; /* 0xfc reserved */ +} dw_ssi_t; + +#define DW_SSI_CTRLR0_SSTE_BIT 24 /* slave select toggle enable */ +#define DW_SSI_CTRLR0_SPI_FRF_BIT 21 /* SPI frame format */ +#define DW_SSI_CTRLR0_DFS_32_BIT 16 /* data frame size */ +#define DW_SSI_CTRLR0_CFS_BIT 12 /* control frame size */ +#define DW_SSI_CTRLR0_SRL_BIT 11 /* shift register loop */ +#define DW_SSI_CTRLR0_SLV_OE_BIT 10 /* slave output enable */ +#define DW_SSI_CTRLR0_TMOD_BIT 8 /* transfer mode */ +#define DW_SSI_CTRLR0_SCPOL_BIT 7 /* serial clock polarity */ +#define DW_SSI_CTRLR0_SCPH_BIT 6 /* serial clock phase */ +#define DW_SSI_CTRLR0_FRF_BIT 4 /* frame format */ +#define DW_SSI_CTRLR0_DFS_BIT 0 /* data frame size */ + +#define DW_SSI_CTRLR0_SSTE RP1BIT(DW_SSI_CTRLR0_SSTE_BIT) +#define DW_SSI_CTRLR0_SPI_FRF_MASK RP1MASK(0x03, DW_SSI_CTRLR0_SPI_FRF_BIT) +#define DW_SSI_CTRLR0_DFS_32_MASK RP1MASK(0x1f, DW_SSI_CTRLR0_DFS_32_BIT) +#define DW_SSI_CTRLR0_CFS_MASK RP1MASK(0x0f, DW_SSI_CTRLR0_CFS_BIT) +#define DW_SSI_CTRLR0_SRL RP1BIT(DW_SSI_CTRLR0_SRL_BIT) +#define DW_SSI_CTRLR0_SLV_OE RP1BIT(DW_SSI_CTRLR0_SLV_OE_BIT) +#define DW_SSI_CTRLR0_TMOD_MASK RP1MASK(0x03, DW_SSI_CTRLR0_TMOD_BIT) +#define DW_SSI_CTRLR0_SCPOL RP1BIT(DW_SSI_CTRLR0_SCPOL_BIT) +#define DW_SSI_CTRLR0_SCPH RP1BIT(DW_SSI_CTRLR0_SCPH_BIT) +#define DW_SSI_CTRLR0_FRF_MASK RP1MASK(0x03, DW_SSI_CTRLR0_FRF_BIT) +#define DW_SSI_CTRLR0_DFS_MASK RP1MASK(0x0f, DW_SSI_CTRLR0_DFS_BIT) + +#define DW_SSI_CTRLR0_SPI_FRF(v) RP1MASK((v)&0x03, DW_SSI_CTRLR0_SPI_FRF_BIT) +#define DW_SSI_CTRLR0_DFS_32(v) RP1MASK((v)&0x1f, DW_SSI_CTRLR0_DFS_32_BIT) +#define DW_SSI_CTRLR0_CFS(v) RP1MASK((v)&0x0f, DW_SSI_CTRLR0_CFS_BIT) +#define DW_SSI_CTRLR0_TMOD(v) RP1MASK((v)&0x03, DW_SSI_CTRLR0_TMOD_BIT) +#define DW_SSI_CTRLR0_FRF(v) RP1MASK((v)&0x03, DW_SSI_CTRLR0_FRF_BIT) +#define DW_SSI_CTRLR0_DFS(v) RP1MASK((v)&0x0f, DW_SSI_CTRLR0_DFS_BIT) + +#define DW_SSI_CTRLR0_TMOD_TXRX 0 /* transmit and receive */ +#define DW_SSI_CTRLR0_TMOD_TX 1 /* transmit only */ +#define DW_SSI_CTRLR0_TMOD_RX 2 /* receive only */ +#define DW_SSI_CTRLR0_TMOD_EERD 3 /* EEPROM read */ + +#define DW_SSI_CTRLR1_NDF_BIT 0 /* number of data frames */ +#define DW_SSI_CTRLR1_NDF_MASK RP1MASK(0xffff, DW_SSI_CTRLR1_NDF_BIT) + +#define DW_SSI_SSIENR_SSI_EN_BIT 0 +#define DW_SSI_SSIENR_SSI_EN RP1BIT(DW_SSI_SSIENR_SSI_EN_BIT) + +#define DW_SSI_MWCR_MHS_BIT 2 /* microwire handshaking */ +#define DW_SSI_MWCR_MDD_BIT 1 /* microwire direction control */ +#define DW_SSI_MWCR_MWMOD_BIT 0 /* microwire transfer mode */ + +/* + * There are max. 4 chip selects (SPI0(4), SPI1(3), SPI2(2), SPI3(2), SPI4(1), + * SPI5(2), SPI6(3), SPI7(1), SPI8(2)) + */ +#define DW_SSI_SER_CS0_BIT 0 /* chip select 0 */ +#define DW_SSI_SER_CS1_BIT 1 /* chip select 1 */ +#define DW_SSI_SER_CS2_BIT 2 /* chip select 2 */ +#define DW_SSI_SER_CS3_BIT 3 /* chip select 3 */ +#define DW_SSI_SER_CS0 RP1BIT(DW_SSI_SER_CS0_BIT) +#define DW_SSI_SER_CS1 RP1BIT(DW_SSI_SER_CS1_BIT) +#define DW_SSI_SER_CS2 RP1BIT(DW_SSI_SER_CS2_BIT) +#define DW_SSI_SER_CS3 RP1BIT(DW_SSI_SER_CS3_BIT) + +#define DW_SSI_BAUDR_SCKDV_BIT 0 /* ssi clock divider (must be even) */ +#define DW_SSI_BAUDR_SCKDV_MASK RP1MASK(0xffff, DW_SSI_BAUDR_SCKDV_BIT) +#define DW_SSI_BAUDR_SCKDV(v) RP1MASK((v)&0xfffe, DW_SSI_BAUDR_SCKDV_BIT) + +#define DW_SSI_SR_DCOL_BIT 6 /* data collision error */ +#define DW_SSI_SR_TXE_BIT 5 /* transmission error */ +#define DW_SSI_SR_RFF_BIT 4 /* receive fifo full */ +#define DW_SSI_SR_RFNE_BIT 3 /* receive fifo not empty */ +#define DW_SSI_SR_TFE_BIT 2 /* transmit fifo empty */ +#define DW_SSI_SR_TFNF_BIT 1 /* transmit fifo not full */ +#define DW_SSI_SR_BUSY_BIT 0 /* ssi busy flag */ +#define DW_SSI_SR_DCOL RP1BIT(DW_SSI_SR_DCOL_BIT) +#define DW_SSI_SR_TXE RP1BIT(DW_SSI_SR_TXE_BIT) +#define DW_SSI_SR_RFF RP1BIT(DW_SSI_SR_RFF_BIT) +#define DW_SSI_SR_RFNE RP1BIT(DW_SSI_SR_RFNE_BIT) +#define DW_SSI_SR_TFE RP1BIT(DW_SSI_SR_TFE_BIT) +#define DW_SSI_SR_TFNF RP1BIT(DW_SSI_SR_TFNF_BIT) +#define DW_SSI_SR_BUSY RP1BIT(DW_SSI_SR_BUSY_BIT) + +#define DW_SSI_IMR_MSTIM_BIT 5 /* multi-master contention interrupt mask */ +#define DW_SSI_IMR_RXFIM_BIT 4 /* receive fifo full interrupt mask */ +#define DW_SSI_IMR_RXOIM_BIT 3 /* receive fifi overflow interrupt mask */ +#define DW_SSI_IMR_RXUIM_BIT 2 /* receive fifo underfow interrupt mask */ +#define DW_SSI_IMR_TXOIM_BIT 1 /* transmit fifo overflow interrupt mask */ +#define DW_SSI_IMR_TXEIM_BIT 0 /* transmit fifo empty interrupt mask */ +#define DW_SSI_IMR_MSTIM RP1BIT(DW_SSI_IMR_MSTIM_BIT) +#define DW_SSI_IMR_RXFIM RP1BIT(DW_SSI_IMR_RXFIM_BIT) +#define DW_SSI_IMR_RXOIM RP1BIT(DW_SSI_IMR_RXOIM_BIT) +#define DW_SSI_IMR_RXUIM RP1BIT(DW_SSI_IMR_RXUIM_BIT) +#define DW_SSI_IMR_TXOIM RP1BIT(DW_SSI_IMR_TXOIM_BIT) +#define DW_SSI_IMR_TXEIM RP1BIT(DW_SSI_IMR_TXEIM_BIT) + +#define DW_SSI_ISR_MSTIS_BIT 5 /* multi-master contention interrupt status */ +#define DW_SSI_ISR_RXFIS_BIT 4 /* receive fifo full interrupt status */ +#define DW_SSI_ISR_RXOIS_BIT 3 /* receive fifi overflow interrupt status */ +#define DW_SSI_ISR_RXUIS_BIT 2 /* receive fifo underfow interrupt status */ +#define DW_SSI_ISR_TXOIS_BIT 1 /* transmit fifo overflow interrupt status */ +#define DW_SSI_ISR_TXEIS_BIT 0 /* transmit fifo empty interrupt status */ +#define DW_SSI_ISR_MSTIS RP1BIT(DW_SSI_ISR_MSTIS_BIT) +#define DW_SSI_ISR_RXFIS RP1BIT(DW_SSI_ISR_RXFIS_BIT) +#define DW_SSI_ISR_RXOIS RP1BIT(DW_SSI_ISR_RXOIS_BIT) +#define DW_SSI_ISR_RXUIS RP1BIT(DW_SSI_ISR_RXUIS_BIT) +#define DW_SSI_ISR_TXOIS RP1BIT(DW_SSI_ISR_TXOIS_BIT) +#define DW_SSI_ISR_TXEIS RP1BIT(DW_SSI_ISR_TXEIS_BIT) + +#define DW_SSI_RISR_MSTIR_BIT 5 /* multi-master contention interrupt raw status */ +#define DW_SSI_RISR_RXFIR_BIT 4 /* receive fifo full interrupt raw status */ +#define DW_SSI_RISR_RXOIR_BIT 3 /* receive fifi overflow interrupt raw status */ +#define DW_SSI_RISR_RXUIR_BIT 2 /* receive fifo underfow interrupt raw status */ +#define DW_SSI_RISR_TXOIR_BIT 1 /* transmit fifo overflow interrupt raw status */ +#define DW_SSI_RISR_TXEIR_BIT 0 /* transmit fifo empty interrupt raw status */ +#define DW_SSI_RISR_MSTIR RP1BIT(DW_SSI_RISR_MSTIR_BIT) +#define DW_SSI_RISR_RXFIR RP1BIT(DW_SSI_RISR_RXFIR_BIT) +#define DW_SSI_RISR_RXOIR RP1BIT(DW_SSI_RISR_RXOIR_BIT) +#define DW_SSI_RISR_RXUIR RP1BIT(DW_SSI_RISR_RXUIR_BIT) +#define DW_SSI_RISR_TXOIR RP1BIT(DW_SSI_RISR_TXOIR_BIT) +#define DW_SSI_RISR_TXEIR RP1BIT(DW_SSI_RISR_TXEIR_BIT) + +#endif +/* vim: ts=4 + */ diff --git a/src/hal/drivers/mesa-hostmot2/spi_common_rpspi.h b/src/hal/drivers/mesa-hostmot2/spi_common_rpspi.h index 6a6f9fcf36c..ccc3335962c 100644 --- a/src/hal/drivers/mesa-hostmot2/spi_common_rpspi.h +++ b/src/hal/drivers/mesa-hostmot2/spi_common_rpspi.h @@ -23,6 +23,8 @@ #ifndef HAL_RPSPI_H #define HAL_RPSPI_H +#include "hwregaccess.h" + /* * Broadcom defines * @@ -37,18 +39,6 @@ #define BCM2835_AUX_OFFSET 0x215000 -#if !defined(__I) && !defined(__O) && !defined(__IO) -#ifdef __cplusplus -#define __I volatile /* read only permission */ -#else -#define __I volatile const /* read only permission */ -#endif -#define __O volatile /* write only permission */ -#define __IO volatile /* read/write permission */ -#else -#error "Possible define collision for __I, __O and __IO" -#endif - /* * Alternate function specification see */ diff --git a/src/hal/drivers/mesa-hostmot2/spix.h b/src/hal/drivers/mesa-hostmot2/spix.h new file mode 100644 index 00000000000..df63f1f61e9 --- /dev/null +++ b/src/hal/drivers/mesa-hostmot2/spix.h @@ -0,0 +1,174 @@ +/* + * This is a component for hostmot2 over SPI for linuxcnc. + * Copyright (c) 2024 B.Stultiens + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, see . + */ +#ifndef HAL_HM2_SPIX_H +#define HAL_HM2_SPIX_H + +/* + * Select which SPI channel(s) to probe. There are many SPI interfaces exposed + * on the 40-pin I/O header. We only use the traditional ones, SPI0 and SPI1. + * SPI0 has four chip selects and SPI1 has three chip selects. + * + * Boards will be numbered in the order found. The probe scan is ordered in the + * following way: + * - SPI0 - CE0 + * - SPI0 - CE1 + * - SPI1 - CE0 + * - SPI1 - CE1 + * - SPI1 - CE2 + * + * There are other possible SPI ports and CE combinations. However, most will + * collide in one or another way with older RPi assignments and uses. Two + * accessible ports should be more than enough for all practical uses. + */ +#define SPIX_PROBE_SPI0_CE0 (1 << 0) +#define SPIX_PROBE_SPI0_CE1 (1 << 1) +#define SPIX_PROBE_SPI0_MASK (SPIX_PROBE_SPI0_CE0 | SPIX_PROBE_SPI0_CE1) +#define SPIX_PROBE_SPI1_CE0 (1 << 2) +#define SPIX_PROBE_SPI1_CE1 (1 << 3) +#define SPIX_PROBE_SPI1_CE2 (1 << 4) +#define SPIX_PROBE_SPI1_MASK (SPIX_PROBE_SPI1_CE0 | SPIX_PROBE_SPI1_CE1 | SPIX_PROBE_SPI1_CE2) + +/* + * The driver is informed of any MISO pull-up/down for each port + * + * Note: These must follow the GPIO_GPPUD_* values defined in + * spi_common_rpspi.h but we do not want to include that header here. + */ +enum { + SPIX_PULL_OFF = 0, // GPIO_GPPUD_OFF + SPIX_PULL_DOWN = 1, // GPIO_GPPUD_PULLDOWN + SPIX_PULL_UP = 2, // GPIO_GPPUD_PULLUP +}; + +/* + * The base structure for the SPI port low level hardware driver. It may attach + * any data privately. + */ +typedef struct __spix_port_t { + int width; // The transfer width 8, 16 or 32 (to check bitshifted cookie) + int miso_pull; // Whether the MISO line is pulled in a direction + const char *name; // SPIx/CEy string (handy for messages) + + /* + * int transfer(spix_port_t *port, uint32_t *buffer, size_t nelem, int rw) + * + * Perform a complete SPI transfer. Transfer 'nelem' words from a buffer + * pointed to by 'buffer'. The argument 'rw' indicates write when 0 (zero) + * or read when non-zero. + * On success it should return 1 (one). On error it should return 0 (zero). + */ + int (*transfer)(const struct __spix_port_t *port, uint32_t *buffer, size_t nelem, int rw); +} spix_port_t; + +#define SPIX_MAX_BOARDS 5 // One on each (traditional) CE for SPI ports 0 and 1 +#define SPIX_MAX_MSG (127+1) // The docs say that the max. burstlen == 127 words (i.e. cmd+message <= 1+127) + +/* + * SPI low level interface to hardware drivers + */ +typedef struct __spix_driver_t { + const char *name; // Human indicator for board + int num_ports; // How many ports this driver supports + char model[127+1]; // Human readable platform name + char dtc[127+1]; // The device-tree matched string + + /* + * int detect(const char *dtcs[]) + * Detect the board supported by this driver on basis of the string-list + * provided via 'dtc'. The 'dtc' argument will be NULL if + * /proc/device-tree/compatible does not exist and the low-level driver + * needs to determine its own fate. + * The driver should return 0 (zero) if it finds itself fit for the + * hardware or non-zero if it does not support the hardware. + */ + int (*detect)(const char *dtcs[]); + + /* + * int setup(int probemask) + * Setup internal structures and data for the driver for all ports in the + * 'probemask' argument. The mask is an inclusive or of the SPIX_PROBE_* + * bit values. + * Returns 0 (zero) on success. + * + * Warning: calling setup() on a driver which did not acknowledge its + * capability to handle the hardware in detect() may cause your system to + * become unstable. + */ + int (*setup)(int probemask); + + /* + * int cleanup(void) + * Clean up internal structures and data for the driver. + * Returns 0 (zero) on success. + */ + int (*cleanup)(void); + + /* + * spix_port_t *open(int port, uint32_t clkw, uint32_t clkr) + * + * Open 'port' (number 0...N) with specified write clock 'clkw' and read + * clock 'clkr'. The driver will check the values. + * Return value is a reference to a the port to use or NULL on failure. + */ + const spix_port_t *(*open)(int port, uint32_t clkw, uint32_t clkr); + + /* + * close() + * Close port and free all internal resources associated with the port. + * Returns 0 (zero) on success. + */ + int (*close)(const spix_port_t *port); + +} spix_driver_t; + + +/* + * Reads file 'fname' into 'buffer' of size 'bufsize' and returns the size read + * or negative on error. At most 'bufsize' - 1 characters are read from the + * file. The buffer is always NUL-terminated. + */ +ssize_t spix_read_file(const char *fname, void *buffer, size_t bufsize); + +/* + * HM2 command interface format (all 32-bit words): + * [0..127 data-words] + * + * Command word format: + * MSB....................................LSB + * aaaa aaaa aaaa aaaa cccc i nnn nnnn 0000 + * - a: register address to read/write + * - c: read (0xa) or write (0xb) command + * - i: auto address increment enable if 1 + * - n: number of data words [1..127] to follow (burst length) + * - 0: unused, should be zero + */ +#define SPIX_HM2_CMD_READ 0x0000a000 +#define SPIX_HM2_CMD_WRITE 0x0000b000 +#define SPIX_HM2_CMD_ADDRINC 0x00000800 +__attribute__((always_inline)) static inline uint32_t spix_cmd_read(uint32_t addr, uint32_t msglen, int aib) +{ + return (addr << 16) | SPIX_HM2_CMD_READ | (aib ? SPIX_HM2_CMD_ADDRINC : 0) | ((msglen & 0x7f) << 4); +} + +__attribute__((always_inline)) static inline uint32_t spix_cmd_write(uint32_t addr, uint32_t msglen, int aib) +{ + return (addr << 16) | SPIX_HM2_CMD_WRITE | (aib ? SPIX_HM2_CMD_ADDRINC : 0) | ((msglen & 0x7f) << 4); +} + +#endif +// vim: ts=4 diff --git a/src/hal/drivers/mesa-hostmot2/spix_rpi3.c b/src/hal/drivers/mesa-hostmot2/spix_rpi3.c new file mode 100644 index 00000000000..c2d45b449c5 --- /dev/null +++ b/src/hal/drivers/mesa-hostmot2/spix_rpi3.c @@ -0,0 +1,852 @@ +/* + * This is a component for hostmot2 over SPI for linuxcnc. + * Copyright (c) 2024 B.Stultiens + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, see . + */ + +/* + * NOTE: This driver will detect and drive both Raspberry Pi 3 and 4 variants + */ + +#include +#include +#include +#include + +#include + +#define HM2_LLIO_NAME "spix_rpi3" + +#include "hostmot2-lowlevel.h" + +#include "eshellf.h" +#include "spix.h" +#include "dtcboards.h" +#include "spi_common_rpspi.h" + +//#define RPSPI_DEBUG_PIN 23 // Define for pin-debugging + +// The min/max allowed frequencies of the SPI clock +#define SCLK_FREQ_MIN 30 +#define SCLK_FREQ_MAX 50000 +#define SCLK_FREQ_DEF 25000 // Default + +#define SPI_MAX_SPI 2 // SPI0 and SPI1 + +// GPIO pin definitions // (header pin location) +#define SPI0_PIN_CE_1 7 // (pin 26) +#define SPI0_PIN_CE_0 8 // (pin 24) +#define SPI0_PIN_MISO 9 // (pin 21) +#define SPI0_PIN_MOSI 10 // (pin 19) +#define SPI0_PIN_SCLK 11 // (pin 23) +#define SPI1_PIN_CE_2 16 // (pin 36) +#define SPI1_PIN_CE_1 17 // (pin 11) +#define SPI1_PIN_CE_0 18 // (pin 12) +#define SPI1_PIN_MISO 19 // (pin 35) +#define SPI1_PIN_MOSI 20 // (pin 38) +#define SPI1_PIN_SCLK 21 // (pin 40) + +// The default pullup/pulldown on the SPI data input pin +#define SPI_PULL_MISO_DEF SPIX_PULL_DOWN +#define SPI_PULL_MOSI_DEF SPIX_PULL_OFF +#define SPI_PULL_SCLK_DEF SPIX_PULL_OFF +#define SPI_PULL_CE_X_DEF SPIX_PULL_UP + +/* + * Our SPI port descriptor extends the spix port descriptor. + */ +typedef struct __spi_port_t { + spix_port_t spix; // The upstream container + int isopen; // Non-zero if successfully opened + int spiport; // Set to 0 for SPI0 and 1 for SPI1 + uint32_t clkdivw; // Write clock divider setting + uint32_t clkdivr; // Read clock divider setting + uint32_t cemask; // Read clock divider setting +} rpi3_port_t; + +/* Forward decls */ +static int rpi3_detect(const char *dtcs[]); +static int rpi3_setup(int probemask); +static int rpi3_cleanup(void); +static const spix_port_t *rpi3_open(int port, uint32_t clkw, uint32_t clkr); +static int rpi3_close(const spix_port_t *sp); +static int spi0_transfer(const spix_port_t *sp, uint32_t *wptr, size_t txlen, int rw); +static int spi1_transfer(const spix_port_t *sp, uint32_t *wptr, size_t txlen, int rw); + +#define PORT_MAX 5 +#define PORT_SPI0 0 // port index for hardware SPI0 +#define PORT_SPI1 2 // port index for hardware SPI1 +static rpi3_port_t spi_ports[PORT_MAX] = { + { .spix = { .width = 8, .miso_pull = SPI_PULL_MISO_DEF, .name = "SPI0/CE0", .transfer = spi0_transfer }, + .spiport = 0, .cemask = 0, + }, + { .spix = { .width = 8, .miso_pull = SPI_PULL_MISO_DEF, .name = "SPI0/CE1", .transfer = spi0_transfer }, + .spiport = 0, .cemask = SPI_CS_CS_01, + }, + { .spix = { .width = 16, .miso_pull = SPI_PULL_MISO_DEF, .name = "SPI1/CE0", .transfer = spi1_transfer }, + .spiport = 1, .cemask = AUX_SPI_CNTL0_CS_1 | AUX_SPI_CNTL0_CS_2, + }, + { .spix = { .width = 16, .miso_pull = SPI_PULL_MISO_DEF, .name = "SPI1/CE1", .transfer = spi1_transfer }, + .spiport = 1, .cemask = AUX_SPI_CNTL0_CS_0 | AUX_SPI_CNTL0_CS_2, + }, + { .spix = { .width = 16, .miso_pull = SPI_PULL_MISO_DEF, .name = "SPI1/CE2", .transfer = spi1_transfer }, + .spiport = 1, .cemask = AUX_SPI_CNTL0_CS_0 | AUX_SPI_CNTL0_CS_1, + }, +}; + +/* + * The driver interface structure + */ +spix_driver_t spix_rpi3 = { + .name = HM2_LLIO_NAME, + .num_ports = PORT_MAX, + + .detect = rpi3_detect, + .setup = rpi3_setup, + .cleanup = rpi3_cleanup, + .open = rpi3_open, + .close = rpi3_close, +}; + +static int has_spi_module; // Set to non-zero when the kernel modules dw_spi and dw_spi_mmio are loaded +static int driver_enabled; // Set to non-zero ehen rpi3_setup() is successfully called +static int port_probe_mask; // Which ports are requested + +static void *peripheralmem = MAP_FAILED; // mmap'ed peripheral memory +static size_t peripheralsize; // Size of the mmap'ed block + +static bcm2835_gpio_t *gpio; // GPIO peripheral structure in mmap'ed address space +static bcm2835_spi_t *spi; // SPI peripheral structure in mmap'ed address space +static bcm2835_aux_t *aux; // AUX peripheral structure in mmap'ed address space +static uint32_t aux_enables; // Previous state of SPI1 enable + +#define F_PERI 400000000UL +static uint32_t spiclk_base = F_PERI; // The base clock (sys_clk) for the SPI port dividers + +/*********************************************************************/ +#if defined(RPSPI_DEBUG_PIN) +/* + * Set/Clear a GPIO pin + */ +HWREGACCESS_ALWAYS_INLINE static inline void gpio_set(uint32_t pin) +{ + if(pin <= 53) { /* There are 54 GPIOs */ + reg_wr(&gpio->gpset[pin / 32], 1 << (pin % 32)); + } +} + +HWREGACCESS_ALWAYS_INLINE static inline void gpio_clr(uint32_t pin) +{ + if(pin <= 53) { /* There are 54 GPIOs */ + reg_wr(&gpio->gpclr[pin / 32], 1 << (pin % 32)); + } +} + +HWREGACCESS_ALWAYS_INLINE static inline void gpio_debug_pin(bool set_reset) +{ + if(set_reset) + gpio_set(RPSPI_DEBUG_PIN); + else + gpio_clr(RPSPI_DEBUG_PIN); +} + +#else + +HWREGACCESS_ALWAYS_INLINE static inline void gpio_debug_pin(bool set_reset) +{ + (void)set_reset; +} + +#endif +/*********************************************************************/ +/* + * Calculate the clock divider for spi0 port + */ +static int32_t spi0_clkdiv_calc(uint32_t base, uint32_t rate) +{ + uint32_t clkdiv = base / rate; + // Use only even divider values + // This is what the documentation (probably) states + if(clkdiv > 65534) + clkdiv = 0; // Slowest possible + else + clkdiv += clkdiv & 1; // Must be multiple of 2 (round to lower frequency) + return clkdiv; +} + +/* + * Reset the SPI peripheral to inactive state and flushed + */ +static inline void spi0_reset(void) +{ + uint32_t x = reg_rd(&spi->cs); + // Disable all activity + x &= ~(SPI_CS_INTR | SPI_CS_INTD | SPI_CS_DMAEN | SPI_CS_TA); + // and reset RX/TX fifos + x |= SPI_CS_CLEAR_RX | SPI_CS_CLEAR_TX; + reg_wr(&spi->cs, x); + // Other registers are don't care for us, not using DMA +} + +/* + * Transfer a buffer of words to the SPI port and fill the same buffer with the + * data coming from the SPI port. + */ +static int spi0_transfer(const spix_port_t *sp, uint32_t *wptr, size_t txlen, int rw) +{ + rpi3_port_t *rp = (rpi3_port_t *)sp; + uint8_t *w8ptr = (uint8_t *)wptr; + uint8_t *r8ptr = (uint8_t *)wptr; // read into write buffer + size_t tx8len = txlen * sizeof(uint32_t); // Bytes to send + size_t rx8len = txlen * sizeof(uint32_t); // Bytes to read + size_t u; + uint32_t cs; + + gpio_debug_pin(false); + + if(!txlen) + return 1; // Nothing to do, return success + + // Using 8-bit transfers; need to byte-swap to big-endian to get the word's + // most significant bit shifted out first. + for(u = 0; u < txlen; u++) + wptr[u] = htobe32(wptr[u]); + + // Setup transfer + cs = reg_rd(&spi->cs); + cs &= ~(SPI_CS_CS_10 | SPI_CS_CS_01 | SPI_CS_REN); // Reset CE and disable 3-wire mode + cs |= rp->cemask | SPI_CS_TA; // Set proper CE_x and activate transfer + reg_wr(&spi->clk, rw ? rp->clkdivr : rp->clkdivw); // Set clock divider + reg_wr(&spi->cs, cs); // Go! + + // Fill the TX fifo with as much as it can hold + while(tx8len > 0 && (reg_rd(&spi->cs) & SPI_CS_TXD)) { + reg_wr(&spi->fifo, *w8ptr); + ++w8ptr; + --tx8len; + } + + // Read and write until all done + while(rx8len > 0) { + cs = reg_rd(&spi->cs); + if(cs & SPI_CS_RXD) { + *r8ptr = (uint8_t)reg_rd(&spi->fifo); + ++r8ptr; + --rx8len; + } + if(tx8len > 0 && (cs & SPI_CS_TXD)) { + reg_wr(&spi->fifo, *w8ptr); + ++w8ptr; + --tx8len; + } + } + + // Stop transfer, after last byte received we are done + spi0_reset(); + + // Put the received words into host order + for(u = 0; u < txlen; u++) + wptr[u] = be32toh(wptr[u]); + + gpio_debug_pin(true); + return 1; +} + +/*************************************************/ + +/* + * Calculate the clock divider for spi1 port + */ +static int32_t spi1_clkdiv_calc(uint32_t base, uint32_t rate) +{ + uint32_t clkdiv; + if(rate >= base / 2) + return 0; + clkdiv = (base) / ((rate-1500) * 2) - 1; // -1500 for same scaling as SPI0 + if(clkdiv > 4095) + clkdiv = 4095; // Slowest possible + return clkdiv; +} + +static inline void spi1_reset(void) +{ + // Disable and clear fifos + reg_wr(&aux->spi0_cntl0, 0); + reg_wr(&aux->spi0_cntl1, AUX_SPI_CNTL0_CLEARFIFO); +} + +static int spi1_transfer(const spix_port_t *sp, uint32_t *wptr, size_t txlen, int rw) +{ + rpi3_port_t *rp = (rpi3_port_t *)sp; + uint16_t *w16ptr = (uint16_t *)wptr; + uint16_t *r16ptr = (uint16_t *)wptr; + size_t tx16len = txlen * sizeof(uint16_t); + size_t rx16len = txlen * sizeof(uint16_t); + size_t u; + + if(!txlen) + return 1; // Nothing to do, return success + + gpio_debug_pin(false); + + // Word-swap to assure most significant bit to be sent first in 16-bit transfers. + for(u = 0; u < tx16len; u += 2) { + uint16_t tt = w16ptr[u+0]; + w16ptr[u+0] = w16ptr[u+1]; + w16ptr[u+1] = tt; + } + + // Setup clock speed and format + // Note: It seems that we cannot send 32 bits. Shift length 0 sends + // zero bits and there are only 6 bits available to set the length, + // giving a maximum of 31 bits. Furthermore, the shift seems to be + // delayed one clock. Therefore we need to shift 15 places (below) and + // not 16 as expected. The one-clock delay may be related to the + // OUT_RISING flag, but that is speculation. + reg_wr(&aux->spi0_cntl0, AUX_SPI_CNTL0_SPEED(rw ? rp->clkdivr : rp->clkdivw) + | AUX_SPI_CNTL0_ENABLE + | AUX_SPI_CNTL0_MSB_OUT + | AUX_SPI_CNTL0_OUT_RISING + | AUX_SPI_CNTL0_SHIFT_LENGTH(16) + | rp->cemask); + reg_wr(&aux->spi0_cntl1, AUX_SPI_CNTL1_MSB_IN); + + // Send data to the fifo + while(tx16len > 0 && !(reg_rd(&aux->spi0_stat) & AUX_SPI_STAT_TX_FULL)) { + if(tx16len > 1) + reg_wr(&aux->spi0_hold, ((uint32_t)*w16ptr) << 15); // More data to follow + else + reg_wr(&aux->spi0_io, ((uint32_t)*w16ptr) << 15); // Final write + ++w16ptr; + --tx16len; + } + + // Read and write until all done + while(rx16len > 0) { + uint32_t stat = reg_rd(&aux->spi0_stat); + if(!(stat & AUX_SPI_STAT_RX_EMPTY)) { + *r16ptr = (uint16_t)reg_rd(&aux->spi0_io); // Read available word + ++r16ptr; + --rx16len; + } + if(tx16len > 0 && !(stat & AUX_SPI_STAT_TX_FULL)) { + if(tx16len > 1) + reg_wr(&aux->spi0_hold, ((uint32_t)*w16ptr) << 15); // More data to follow + else + reg_wr(&aux->spi0_io, ((uint32_t)*w16ptr) << 15); // Final write + ++w16ptr; + --tx16len; + } + + } + + // Stop transfer + spi1_reset(); + + // Word-swap to fix the word order back to host-order. + for(u = 0; u < tx16len; u += 2) { + uint16_t tt = w16ptr[u+0]; + w16ptr[u+0] = w16ptr[u+1]; + w16ptr[u+1] = tt; + } + + gpio_debug_pin(true); + return 1; +} + +/*************************************************/ +/* + * Map peripheral I/O memory in the process' address space. + * Setup pointers to the relevant structures to access the underlying hardware. + */ +static int peripheral_map(uintptr_t membase, size_t memsize) +{ + int fd; + int err; + + peripheralsize = memsize; + + if((fd = rtapi_open_as_root("/dev/mem", O_RDWR | O_SYNC)) < 0) { + LL_ERR("Can't open /dev/mem\n"); + return -errno; + } + + /* mmap BCM2835 GPIO and SPI peripherals */ + peripheralmem = mmap(NULL, peripheralsize, PROT_READ|PROT_WRITE, MAP_SHARED, fd, (off_t)membase); + err = errno; + close(fd); + if(peripheralmem == MAP_FAILED) { + LL_ERR("Can't map peripherals: %s\n", strerror(err)); + if(err == EPERM) { + LL_ERR("Try adding 'iomem=relaxed' to your kernel command-line.\n"); + } + return -err; + } + + // The Right Way(TM) may be to extract the reg mappings for the + // specific devices at /dev/device-tree/soc/*. Then we'd need to + // inspect the gpio@... and spi@... device nodes, read the + // corresponding reg file and correct the address with respect to the + // virtual vs. physical mappings. Too much work... These are all + // compatible devices and nobody in their right mind (famous last + // words) will change that after the large quantity of devices out in + // the wild. + // Lets just say, when somebody decides to change the world, then we'll + // fix all this code too. + gpio = (bcm2835_gpio_t *)(peripheralmem + (BCM2835_GPIO_OFFSET / sizeof(*peripheralmem))); + spi = (bcm2835_spi_t *)(peripheralmem + (BCM2835_SPI_OFFSET / sizeof(*peripheralmem))); + aux = (bcm2835_aux_t *)(peripheralmem + (BCM2835_AUX_OFFSET / sizeof(*peripheralmem))); + + LL_INFO("Mapped peripherals from 0x%p (size 0x%08x) to gpio:0x%p, spi:0x%p, aux:0x%p\n", + (void *)membase, (uint32_t)peripheralsize, gpio, spi, aux); + + return 0; +} + +/*************************************************/ +static void waste_150_cycles(void) +{ + uint32_t x __attribute__((unused)); + unsigned i; + // A read, memory barrier, an increment, a test and a jump. Should be at least 150 cycles + for(i = 0; i < 40; i++) + x = reg_rd(&gpio->gplev0); // Just read the pin register, nothing interesting to do here +} + +static void gpio_fsel(uint32_t pin, uint32_t func) +{ + if(pin <= 53) { /* There are 54 GPIOs */ + uint32_t bits = pin * 3; /* Three bits per fsel field and 10 gpio per uint32_t */ + reg_wr(&gpio->gpfsel[bits / 30], (reg_rd(&gpio->gpfsel[bits / 30]) & ~(7 << (bits % 30))) | ((func & 7) << (bits % 30))); + } +} + +static void gpio_pull(unsigned pin, uint32_t pud) +{ + // Enable/disable pullups on the pins on request + reg_wr(&gpio->gppudclk0, 0); // We are not sure about the previous state, make sure + reg_wr(&gpio->gppudclk1, 0); + waste_150_cycles(); // See GPPUDCLKn description + reg_wr(&gpio->gppud, pud); + waste_150_cycles(); + if(pin <= 31) { + reg_wr(&gpio->gppudclk0, 1 << pin); + waste_150_cycles(); + reg_wr(&gpio->gppudclk0, 0); + } else if(pin <= 53) { + reg_wr(&gpio->gppudclk1, 1 << (pin - 32)); + waste_150_cycles(); + reg_wr(&gpio->gppudclk1, 0); + } +} + +static void peripheral_setup(void) +{ + // Setup SPI pins to SPI0 + if(port_probe_mask & SPIX_PROBE_SPI0_MASK) { + if(port_probe_mask & SPIX_PROBE_SPI0_CE0) { + gpio_fsel(SPI0_PIN_CE_0, GPIO_FSEL_8_SPI0_CE0_N); + gpio_pull(SPI0_PIN_CE_0, GPIO_GPPUD_PULLUP); + } + if(port_probe_mask & SPIX_PROBE_SPI0_CE1) { + gpio_fsel(SPI0_PIN_CE_1, GPIO_FSEL_7_SPI0_CE1_N); + gpio_pull(SPI0_PIN_CE_1, GPIO_GPPUD_PULLUP); + } + gpio_fsel(SPI0_PIN_MISO, GPIO_FSEL_9_SPI0_MISO); + gpio_fsel(SPI0_PIN_MOSI, GPIO_FSEL_10_SPI0_MOSI); + gpio_fsel(SPI0_PIN_SCLK, GPIO_FSEL_11_SPI0_SCLK); + // Enable/disable pullups on the pins + gpio_pull(SPI0_PIN_MISO, SPI_PULL_MISO_DEF); + gpio_pull(SPI0_PIN_MOSI, SPI_PULL_MOSI_DEF); + gpio_pull(SPI0_PIN_SCLK, SPI_PULL_SCLK_DEF); + spi0_reset(); + } + + // Setup SPI pins to SPI1 + if(port_probe_mask & SPIX_PROBE_SPI1_MASK) { + if(port_probe_mask & SPIX_PROBE_SPI1_CE0) { + gpio_fsel(SPI1_PIN_CE_0, GPIO_FSEL_18_SPI1_CE0_N); + gpio_pull(SPI1_PIN_CE_0, GPIO_GPPUD_PULLUP); + } + if(port_probe_mask & SPIX_PROBE_SPI1_CE1) { + gpio_fsel(SPI1_PIN_CE_1, GPIO_FSEL_17_SPI1_CE1_N); + gpio_pull(SPI1_PIN_CE_1, GPIO_GPPUD_PULLUP); + } + if(port_probe_mask & SPIX_PROBE_SPI1_CE2) { + gpio_fsel(SPI1_PIN_CE_1, GPIO_FSEL_16_SPI1_CE2_N); + gpio_pull(SPI1_PIN_CE_2, GPIO_GPPUD_PULLUP); + } + gpio_fsel(SPI1_PIN_MISO, GPIO_FSEL_19_SPI1_MISO); + gpio_fsel(SPI1_PIN_MOSI, GPIO_FSEL_20_SPI1_MOSI); + gpio_fsel(SPI1_PIN_SCLK, GPIO_FSEL_21_SPI1_SCLK); + // Enable/disable pullups on the pins + gpio_pull(SPI1_PIN_MISO, SPI_PULL_MISO_DEF); + gpio_pull(SPI1_PIN_MOSI, SPI_PULL_MOSI_DEF); + gpio_pull(SPI1_PIN_SCLK, SPI_PULL_SCLK_DEF); + + // Check if SPI1 needs to be enabled + aux_enables = reg_rd(&aux->enables); + if(!(aux_enables & AUX_ENABLES_SPI1)) + reg_wr(&aux->enables, aux_enables | AUX_ENABLES_SPI1); // Enable SPI1 + + spi1_reset(); + } + +#if defined(RPSPI_DEBUG_PIN) + gpio_fsel(RPSPI_DEBUG_PIN, GPIO_FSEL_X_GPIO_OUTPUT); + gpio_debug_pin(true); +#endif +} + +static void peripheral_restore(void) +{ +#if defined(RPSPI_DEBUG_PIN) + gpio_debug_pin(true); + gpio_fsel(RPSPI_DEBUG_PIN, GPIO_FSEL_X_GPIO_INPUT); +#endif + // Restore all SPI pins to inputs and enable pull-up (no dangling inputs) + if(port_probe_mask & SPIX_PROBE_SPI0_MASK) { + gpio_pull(SPI0_PIN_MISO, GPIO_GPPUD_PULLUP); + gpio_pull(SPI0_PIN_MOSI, GPIO_GPPUD_PULLUP); + gpio_pull(SPI0_PIN_SCLK, GPIO_GPPUD_PULLUP); + + // Set SPI0 pins to GPIO input + gpio_fsel(SPI0_PIN_MISO, GPIO_FSEL_X_GPIO_INPUT); + gpio_fsel(SPI0_PIN_MOSI, GPIO_FSEL_X_GPIO_INPUT); + gpio_fsel(SPI0_PIN_SCLK, GPIO_FSEL_X_GPIO_INPUT); + if(port_probe_mask & SPIX_PROBE_SPI0_CE0) + gpio_fsel(SPI0_PIN_CE_0, GPIO_FSEL_X_GPIO_INPUT); + if(port_probe_mask & SPIX_PROBE_SPI0_CE1) + gpio_fsel(SPI0_PIN_CE_1, GPIO_FSEL_X_GPIO_INPUT); + } + + if(port_probe_mask & SPIX_PROBE_SPI1_MASK) { + // Only disable SPI1 if it was disabled before + if(!(aux_enables & AUX_ENABLES_SPI1)) + reg_wr(&aux->enables, reg_rd(&aux->enables) & ~AUX_ENABLES_SPI1); + + gpio_pull(SPI1_PIN_MISO, GPIO_GPPUD_PULLUP); + gpio_pull(SPI1_PIN_MOSI, GPIO_GPPUD_PULLUP); + gpio_pull(SPI1_PIN_SCLK, GPIO_GPPUD_PULLUP); + + // Set SPI1 pins to GPIO input + gpio_fsel(SPI1_PIN_MISO, GPIO_FSEL_X_GPIO_INPUT); + gpio_fsel(SPI1_PIN_MOSI, GPIO_FSEL_X_GPIO_INPUT); + gpio_fsel(SPI1_PIN_SCLK, GPIO_FSEL_X_GPIO_INPUT); + if(port_probe_mask & SPIX_PROBE_SPI1_CE0) + gpio_fsel(SPI1_PIN_CE_0, GPIO_FSEL_X_GPIO_INPUT); + if(port_probe_mask & SPIX_PROBE_SPI1_CE1) + gpio_fsel(SPI1_PIN_CE_1, GPIO_FSEL_X_GPIO_INPUT); + if(port_probe_mask & SPIX_PROBE_SPI1_CE2) + gpio_fsel(SPI1_PIN_CE_2, GPIO_FSEL_X_GPIO_INPUT); + } +} + +/*************************************************/ +#define RPSPI_SYS_CLKVPU "/sys/kernel/debug/clk/vpu/clk_rate" // Newer kernels (4.8+, I think) have detailed info +#define RPSPI_SYS_CLKCORE "/sys/kernel/debug/clk/core/clk_rate" // Older kernels only have core-clock info + +static uint32_t read_spiclkbase(void) +{ + const char *sysclkref = RPSPI_SYS_CLKVPU; + uint32_t rate; + char buf[16]; + ssize_t err; + + if((err = spix_read_file(sysclkref, buf, sizeof(buf))) < 0) { + // Failed VPU clock, try core clock + LL_INFO("No VPU clock at '%s' (errno=%d), trying core clock as alternative.\n", sysclkref, errno); + sysclkref = RPSPI_SYS_CLKCORE; + if((err = spix_read_file(sysclkref, buf, sizeof(buf))) < 0) { + // Neither clock available, complain and use default setting + LL_ERR("Cannot open clock setting '%s' (errno=%d), using %d Hz\n", sysclkref, errno, spiclk_base); + return spiclk_base; + } + } + + if(err >= sizeof(buf)-1) { + // There are probably too many digits in the number + // 250000000 (250 MHz) has 9 digits and there is a newline + // following the number + LL_ERR("Read full buffer '%s' from '%s', number probably wrong or too large, using %d Hz\n", buf, sysclkref, spiclk_base); + return spiclk_base; + } + + if(1 != sscanf(buf, "%u", &rate)) { + LL_ERR("Cannot interpret clock setting '%s' from '%s', using %d Hz\n", buf, sysclkref, spiclk_base); + return spiclk_base; + } + return rate; +} + +/*************************************************/ +#define DTC_SOC_RANGES "/proc/device-tree/soc/ranges" + +static int read_membase(uintptr_t *pmembase, size_t *pmemsize) +{ + uint32_t buf[4+1]; + ssize_t len; + + *pmembase = 0; + *pmemsize = 0; + + // Extract the IO base and size + // The ranges file in the device-tree has the physical mappings of the + // IO space we need to map. There are several interesting values in big + // endian: + // RPi3 (BCM2836) + // [0]: Real register file address + // [1]: IO register file base address + // [2]: IO register file size + // ... + // RPi4 (BCM2838) + // [0]: Real register file address + // [1]: 0x00000000 + // [2]: IO register file base address + // [3]: IO register file size + // ... + // + // The definitions for the device-tree are in the (rpi) linux source + // tree to be found at arch/arm/boot/dts/bcm283[568]*. + // + // We read into a buffer that is large enough for more than four 32-bit + // words. These are required to be present for the RPi3 and RPi4 (and + // older versions). + if((len = spix_read_file(DTC_SOC_RANGES, buf, sizeof(buf))) < 0) { + LL_ERR("Cannot read IO base address and size from '%s'.\n", DTC_SOC_RANGES); + return len; + } + + if(len / sizeof(uint32_t) < 3) { + LL_ERR("Insufficient data read from '%s' for RPi3/RPi4 IO base address and size.\n", DTC_SOC_RANGES); + return -ENXIO; + } + + *pmembase = be32toh(buf[1]); // This should do the trick for RPi3 + *pmemsize = be32toh(buf[2]); + + if(!*pmembase) { + // This is (probably) a RPi4 and the ranges file has a zero at the base + // address. Here we need to have read four 32-bit words to get to the + // right values. + if(len / sizeof(uint32_t) < 4) { + LL_ERR("Insufficient data read from '%s' for RPi4 IO base address and size.\n", DTC_SOC_RANGES); + return -ENXIO; + } + + *pmembase = be32toh(buf[2]); + *pmemsize = be32toh(buf[3]); + } + + if(!*pmembase || !*pmemsize) { + LL_ERR("IO base address (0x%p) or size (0x%08zx) are zero.\n", (void *)*pmembase, *pmemsize); + return -ENXIO; + } + LL_INFO("Base address 0x%p size 0x%08zx\n", (void *)*pmembase, *pmemsize); + return 0; +} + +/*************************************************/ + +/* + * Detect the presence of the hardware on basis of the device-tree compatible + * string-list. + * On success returns 9 (zero) and sets the driver information to the dtc + * string and tries to set a human readable string as model. + */ +static int rpi3_detect(const char *dtcs[]) +{ + int i; + for(i = 0; dtcs[i] != NULL; i++) { + if( !strcmp(dtcs[i], DTC_RPI_MODEL_4B) + || !strcmp(dtcs[i], DTC_RPI_MODEL_4CM) + || !strcmp(dtcs[i], DTC_RPI_MODEL_3BP) + || !strcmp(dtcs[i], DTC_RPI_MODEL_3AP) + || !strcmp(dtcs[i], DTC_RPI_MODEL_3B)) { + break; // Found our supported board + } + } + + if(dtcs[i] == NULL) + return -ENODEV; // We are not the device the driver supports + + // Set the matched dtc and model informational strings + strncpy(spix_rpi3.dtc, dtcs[i], sizeof(spix_rpi3.dtc)-1); + if(spix_read_file("/proc/device-tree/model", spix_rpi3.model, sizeof(spix_rpi3.model)) < 0) + strncpy(spix_rpi3.model, "??? Unknown board ???", sizeof(spix_rpi3.model)-1); + + return 0; +} + +/* + * Setup the driver. + * - remove kernel spidev driver modules if detected + * - map the I/O memory + * - setup the GPIO pins and SPI peripheral(s) + */ +static int rpi3_setup(int probemask) +{ + int err; + uintptr_t membase; + size_t memsize; + + if(driver_enabled) { + LL_ERR("Driver is already setup.\n"); + return -EBUSY; + } + + port_probe_mask = probemask; // For peripheral_setup() and peripheral_restore() + + // Now we know what platform we are running, remove kernel SPI module if + // detected + if((has_spi_module = (0 == shell("/usr/bin/grep -qw ^spi_bcm2835 /proc/modules")))) { + if(shell("/sbin/rmmod spi_bcm2835")) + LL_ERR("Unable to remove kernel SPI module spi_bcm2835. " + "Your system may become unstable using LinuxCNC with the " HM2_LLIO_NAME " driver.\n"); + } + + spiclk_base = read_spiclkbase(); + if((err = read_membase(&membase, &memsize)) < 0) + return err; + + if((err = peripheral_map(membase, memsize)) < 0) { + LL_ERR("Cannot map peripheral memory.\n"); + return err; + } + + peripheral_setup(); + + driver_enabled = 1; + + return 0; +} + +/* + * Cleanup the driver + * - close any open ports + * - restore pripheral settings + * - unmap I/ memory + * - re-insert kernel spidev hardware module(s) is previously detected + */ +static int rpi3_cleanup(void) +{ + int i; + + if(!driver_enabled) { + LL_ERR("Driver is not setup.\n"); + return -ENODEV; + } + + // Close any ports not closed already + for(i = 0; i < PORT_MAX; i++) { + if(spi_ports[i].isopen) + spix_rpi3.close(&spi_ports[i].spix); + } + + if(peripheralmem != MAP_FAILED) { + peripheral_restore(); + munmap(peripheralmem, peripheralsize); + } + + // Restore kernel SPI module if it was detected before + if(has_spi_module) + shell("/sbin/modprobe spi_bcm2835"); + + driver_enabled = 0; + return 0; +} + +/* + * Open a SPI port at index 'port' with 'clkw' write clock and 'clkr' read + * clock frequencies. + */ +static const spix_port_t *rpi3_open(int port, uint32_t clkw, uint32_t clkr) +{ + rpi3_port_t *rpp; + + if(!driver_enabled) { + LL_ERR("Driver is not setup.\n"); + return NULL; + } + + if(port < 0 || port >= PORT_MAX) { + LL_ERR("SPI port %d out of range.\n", port); + return NULL; + } + + rpp = &spi_ports[port]; + + if(!(port_probe_mask & (1 << port))) { + LL_ERR("%s: SPI port %d not setup, was not in probe mask (%02x).\n", rpp->spix.name, port, port_probe_mask); + return NULL; + } + + if(rpp->isopen) { + LL_ERR("%s: SPI port already open.\n", rpp->spix.name); + return NULL; + } + + if(clkw < SCLK_FREQ_MIN || clkw > SCLK_FREQ_MAX) { + LL_ERR("%s: SPI write clock frequency outside acceptable range (%d..%d kHz).\n", rpp->spix.name, SCLK_FREQ_MIN, SCLK_FREQ_MAX); + return NULL; + } + + if(!rpp->spiport) { + rpp->clkdivw = spi0_clkdiv_calc(spiclk_base, clkw); + rpp->clkdivr = spi0_clkdiv_calc(spiclk_base, clkr); + } else { + rpp->clkdivw = spi1_clkdiv_calc(spiclk_base, clkw); + rpp->clkdivr = spi1_clkdiv_calc(spiclk_base, clkr); + } + LL_INFO("%s: write clock rate calculated: %u Hz (clkdiv: %u)\n", rpp->spix.name, spiclk_base / rpp->clkdivw, rpp->clkdivw); + LL_INFO("%s: read clock rate calculated: %u Hz (clkdiv: %u)\n", rpp->spix.name, spiclk_base / rpp->clkdivr, rpp->clkdivr); + + rpp->isopen = 1; + return &rpp->spix; +} + +/* + * Close a SPI port. + */ +static int rpi3_close(const spix_port_t *sp) +{ + rpi3_port_t *rpp; + + if(!driver_enabled) { + LL_ERR("Driver is not setup.\n"); + return -ENODEV; + } + + if(!sp) { + LL_ERR("Trying to close port NULL\n"); + return -EINVAL; + } + + rpp = (rpi3_port_t *)sp; + if(!rpp->isopen) { + LL_ERR("%s: SPI port not open.\n", rpp->spix.name); + return -ENODEV; + } + + if(!rpp->spiport) + spi0_reset(); + else + spi1_reset(); + + rpp->isopen = 0; + return 0; +} + +// vim: ts=4 diff --git a/src/hal/drivers/mesa-hostmot2/spix_rpi5.c b/src/hal/drivers/mesa-hostmot2/spix_rpi5.c new file mode 100644 index 00000000000..dbce2d40696 --- /dev/null +++ b/src/hal/drivers/mesa-hostmot2/spix_rpi5.c @@ -0,0 +1,674 @@ +/* + * This is a component for hostmot2 over SPI for linuxcnc. + * Copyright (c) 2024 B.Stultiens + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, see . + */ + +#include +#include +#include +#include + +#include + +#define HM2_LLIO_NAME "spix_rpi5" + +#include "hostmot2-lowlevel.h" + +#include "eshellf.h" +#include "spix.h" +#include "dtcboards.h" +#include "rp1dev.h" + +//#define RPSPI_DEBUG_PIN 23 // Define for pin-debugging + +// The min/max allowed frequencies of the SPI clock +#define SCLK_FREQ_MIN 4 +#define SCLK_FREQ_MAX 50000 +#define SCLK_FREQ_DEF 25000 // Default + +#define SPI_MAX_SPI 2 // SPI0 and SPI1 + +// GPIO pin definitions // (header pin location) +#define SPI0_PIN_CE_1 7 // (pin 26) +#define SPI0_PIN_CE_0 8 // (pin 24) +#define SPI0_PIN_MISO 9 // (pin 21) +#define SPI0_PIN_MOSI 10 // (pin 19) +#define SPI0_PIN_SCLK 11 // (pin 23) +#define SPI1_PIN_CE_2 16 // (pin 36) +#define SPI1_PIN_CE_1 17 // (pin 11) +#define SPI1_PIN_CE_0 18 // (pin 12) +#define SPI1_PIN_MISO 19 // (pin 35) +#define SPI1_PIN_MOSI 20 // (pin 38) +#define SPI1_PIN_SCLK 21 // (pin 40) + +// The default pullup/pulldown on the SPI pins +#define SPI_PULL_MISO_DEF SPIX_PULL_DOWN +#define SPI_PULL_MOSI_DEF SPIX_PULL_OFF +#define SPI_PULL_SCLK_DEF SPIX_PULL_OFF +#define SPI_PULL_CE_X_DEF SPIX_PULL_UP + +/* + * Our SPI port descriptor extends the spix port descriptor. + */ +typedef struct __spi_port_t { + spix_port_t spix; // The upstream container + int isopen; // Non-zero if successfully opened + dw_ssi_t *port; // Actual mapped I/O memory + uint32_t clkdivw; // Write clock divider setting + uint32_t clkdivr; // Read clock divider setting + uint32_t cemask; // Read clock divider setting +} rpi5_port_t; + +/* Forward decls */ +static int rpi5_detect(const char *dtcs[]); +static int rpi5_setup(int probemask); +static int rpi5_cleanup(void); +static const spix_port_t *rpi5_open(int port, uint32_t clkw, uint32_t clkr); +static int rpi5_close(const spix_port_t *sp); +static int spi_transfer(const spix_port_t *sp, uint32_t *wptr, size_t txlen, int rw); + +#define PORT_MAX 5 +#define PORT_SPI0 0 // port index for hardware SPI0 +#define PORT_SPI1 2 // port index for hardware SPI1 +static rpi5_port_t spi_ports[PORT_MAX] = { + { .spix = { .width = 32, .miso_pull = SPI_PULL_MISO_DEF, .name = "SPI0/CE0", .transfer = spi_transfer }, .cemask = (1<<0) }, + { .spix = { .width = 32, .miso_pull = SPI_PULL_MISO_DEF, .name = "SPI0/CE1", .transfer = spi_transfer }, .cemask = (1<<1) }, + { .spix = { .width = 32, .miso_pull = SPI_PULL_MISO_DEF, .name = "SPI1/CE0", .transfer = spi_transfer }, .cemask = (1<<0) }, + { .spix = { .width = 32, .miso_pull = SPI_PULL_MISO_DEF, .name = "SPI1/CE1", .transfer = spi_transfer }, .cemask = (1<<1) }, + { .spix = { .width = 32, .miso_pull = SPI_PULL_MISO_DEF, .name = "SPI1/CE2", .transfer = spi_transfer }, .cemask = (1<<2) }, +}; + +/* + * The driver interface structure + */ +spix_driver_t spix_rpi5 = { + .name = HM2_LLIO_NAME, + .num_ports = PORT_MAX, + + .detect = rpi5_detect, + .setup = rpi5_setup, + .cleanup = rpi5_cleanup, + .open = rpi5_open, + .close = rpi5_close, +}; + +// Originals of the io_bank0.gpio[X].ctrl and pads_bank0.gpio registers so they +// can be restored later on exit. +typedef struct __spisave_t { + uint32_t bank_sclk; + uint32_t bank_mosi; + uint32_t bank_miso; + uint32_t bank_ce_0; + uint32_t bank_ce_1; + uint32_t bank_ce_2; + uint32_t pads_sclk; + uint32_t pads_mosi; + uint32_t pads_miso; + uint32_t pads_ce_0; + uint32_t pads_ce_1; + uint32_t pads_ce_2; +} spisave_t; + +static spisave_t spi0save; // Settings before our setup +static spisave_t spi1save; +static int has_spi_module; // Set to non-zero when the kernel modules dw_spi and dw_spi_mmio are loaded +static int driver_enabled; // Set to non-zero ehen rpi5_setup() is successfully called +static int port_probe_mask; // Which ports are requested + +static void *peripheralmem = MAP_FAILED; // mmap'ed peripheral memory +static size_t peripheralsize; // Size of the mmap'ed block +static rp1_rio_t *rio0; // GPIO pin access structure in mmap'ed address space +static rp1_io_bank0_t *iobank0; // GPIO pin config structure in mmap'ed address space +static rp1_pads_bank0_t *padsbank0; // GPIO pin pads structure in mmap'ed address space + + +/*********************************************************************/ +#ifdef RPSPI_DEBUG_PIN +HWREGACCESS_ALWAYS_INLINE static inline void gpio_set(int pin) +{ + if(pin >= 0 && pin < 28) + reg_wr_raw(&rio0->set.out, 1 << pin); +} + +HWREGACCESS_ALWAYS_INLINE static inline void gpio_clr(int pin) +{ + if(pin >= 0 && pin < 28) + reg_wr_raw(&rio0->clr.out, 1 << pin); +} + +HWREGACCESS_ALWAYS_INLINE static inline void gpio_debug_pin(bool set_reset) +{ + if(set_reset) + gpio_set(RPSPI_DEBUG_PIN); + else + gpio_clr(RPSPI_DEBUG_PIN); +} + +#else + +HWREGACCESS_ALWAYS_INLINE static inline void gpio_debug_pin(bool set_reset) +{ + (void)set_reset; +} + +#endif + +/*********************************************************************/ +/* + * Calculate the clock divider for any spi port + */ +static inline int32_t clkdiv_calc(uint32_t rate) +{ + uint32_t clkdiv = RP1_SPI_CLK / rate; + // The documentation states: bit 0 is always zero, therefore, only even + // divider values supported. Divider value 0 disables the SCLK output. + if(clkdiv > 65534) + clkdiv = 65534; // Slowest possible + else + clkdiv += clkdiv & 1; // Must be multiple of 2 (round to lower frequency) + if(!clkdiv) + clkdiv = 2; // Do not disable, set it to absolute maximum frequency + return clkdiv; +} + +/* + * Reset the SPI peripheral to inactive state and flushed + */ +static inline void spi_reset(dw_ssi_t *port) +{ + uint32_t dummy __attribute__((unused)); + reg_wr_raw(&port->ssienr, 0); // Disable chip, will also clear fifo + reg_wr_raw(&port->ser, 0); // Clear all chip selects + dummy = reg_rd_raw(&port->icr); // Clear all interrupts + // The chip will be enabled when a transfer is started + // reg_wr(port->ssienr, DW_SSI_SSIENR_SSI_EN); // Enable chip +} + +/* + * Transfer a buffer of words to the SPI port and fill the same buffer with the + * data coming from the SPI port. + */ +static int spi_transfer(const spix_port_t *sp, uint32_t *wptr, size_t txlen, int rw) +{ + rpi5_port_t *rp = (rpi5_port_t *)sp; + dw_ssi_t *port = rp->port; + size_t rxlen = txlen; // words to receive + uint32_t *rptr = wptr; // read into write buffer + int fifo = RP1_SPI_FIFO_LEN; // fifo level counter before starting transfer + int rv = 1; // function return value + + gpio_debug_pin(false); + + if(!txlen) + return 1; // Nothing to do, return success + + // Setup transfer + // 32-bit, transmit/receive transfers, SPI mode 0 (CPHA=0, CPOL=0) + reg_wr(&port->ctrlr0, DW_SSI_CTRLR0_DFS_32(32-1) | DW_SSI_CTRLR0_TMOD(DW_SSI_CTRLR0_TMOD_TXRX)); + reg_wr_raw(&port->baudr, rw ? rp->clkdivr : rp->clkdivw); + reg_wr_raw(&port->ser, rp->cemask); + + reg_wr_raw(&port->ssienr, DW_SSI_SSIENR_SSI_EN); // Enable port + + // Stuff the fifo until full or no more data words to write + while(txlen > 0 && fifo > 0) { + reg_wr_raw(&port->dr0, *wptr); // Write data + wptr++; + txlen--; + fifo--; + } + + // We don't need to add a memory barrier. The next loop runs about rxlen + // and the code will stall on the register reads. There is no read/write + // overlap that may be problematic. + + while(rxlen > 0) { + // Get the rx fifo level and read as many as available + uint32_t tff = fifo = reg_rd_raw(&port->rxflr); + // Already get the int status register; this read will pipeline + uint32_t risr = reg_rd_raw(&port->risr); + while(rxlen > 0 && fifo > 0) { + *rptr = reg_rd_raw(&port->dr0); + rptr++; + rxlen--; + fifo--; + } + + // If we still have queued data, blindly write to stuff the tx-fifo. + // For each received word we have one less entry in the transmit fifo. + // It has to be. Therefore, we can use the receive level as a proxy for + // how many words can be written + while(txlen > 0 && tff > 0) { + reg_wr_raw(&port->dr0, *wptr); + wptr++; + txlen--; + tff--; + } + + // Check for receive errors + if(risr & (DW_SSI_RISR_RXOIR)) { + // The receive fifo overflowed. A bad sign... + // Abort the transfer + rv = 0; // Signal error + LL_ERR("%s: Receive FIFO overflow during transfer\n", rp->spix.name); + goto abort_rx; + } + } + + // We should no longer have to wait. Each word transmitted has been + // received (rxlen == 0). Therefore, the transfer must be complete and + // done. No (busy) wait required. + +abort_rx: + spi_reset(port); + + gpio_debug_pin(true); + return rv; +} + +/*************************************************/ +/* + * Map peripheral I/O memory in the process' address space. + * Setup pointers to the relevant structures to access the underlying hardware. + */ +static int peripheral_map(uintptr_t membase, size_t memsize) +{ + int fd; + int err; + + peripheralsize = memsize; + + if((fd = rtapi_open_as_root("/dev/mem", O_RDWR | O_SYNC)) < 0) { + LL_ERR("Can't open /dev/mem\n"); + return -errno; + } + + /* mmap PCIe translation address of the RP1 peripherals */ + peripheralmem = mmap(NULL, peripheralsize, PROT_READ|PROT_WRITE, MAP_SHARED, fd, (off_t)membase); + err = errno; + close(fd); + if(peripheralmem == MAP_FAILED) { + LL_ERR("Can't map peripherals: %s\n", strerror(err)); + if(err == EPERM) { + LL_ERR("Try adding 'iomem=relaxed' to your kernel command-line.\n"); + } + return -err; + } + + // Calculate the addresses of the individual peripherals + spi_ports[0].port = + spi_ports[1].port = (dw_ssi_t *)((void *)((uintptr_t)peripheralmem + RP1_SPI0_OFFSET)); + spi_ports[2].port = + spi_ports[3].port = + spi_ports[4].port = (dw_ssi_t *)((void *)((uintptr_t)peripheralmem + RP1_SPI1_OFFSET)); + iobank0 = (rp1_io_bank0_t *)((void *)((uintptr_t)peripheralmem + RP1_IO_BANK0_OFFSET)); + padsbank0 = (rp1_pads_bank0_t *)((void *)((uintptr_t)peripheralmem + RP1_PADS_BANK0_OFFSET)); + rio0 = (rp1_rio_t *)((void *)((uintptr_t)peripheralmem + RP1_SYS_RIO0_OFFSET)); + + LL_INFO("Mapped peripherals from 0x%p (size 0x%08zx)\n", (void *)membase, peripheralsize); + + return 0; +} + +/* + * Convert the pull-up/down enum into a register mask. Perform boundary checks + * on the value and set it to a valid value if it was outside valid range. + */ +static uint32_t pud_val_to_mask(uint32_t pud) +{ + switch(pud) { + default: + /* Fallthrough */ + case SPIX_PULL_OFF: return 0; + case SPIX_PULL_DOWN: return RP1_PADS_PDE; + case SPIX_PULL_UP: return RP1_PADS_PUE; + } +} + +// The required drive level in pads_bank0 registers (8mA drive and high slewrate) +#define DRIVE_LEVEL (RP1_PADS_DRIVE(RP1_PADS_DRIVE_8) | RP1_PADS_SLEWFAST) + +static void peripheral_setup(void) +{ + // + // We do not touch the voltage_select register in pads_bank0. We simply + // hope that the voltage_select register is at 3.3V. That should be a safe + // assumption, considering the normal setup. Otherwise, the hardware may + // already be fried at boot-time with high input voltages on input pins. + // Raspberry Pi RP1 Peripherals bottom of section 3.1.3: + // "Using VDDIO voltages greater than 1.8V, with the input thresholds set + // for 1.8V may result in damage to the chip." + // We are pretty sure that the stock board uses 3.3V VDDIO because of + // compatibility with existing hardware. Therefore, fiddling with the + // voltage_select register may be fatal to the hardware. + // + + static const int spi_pull_miso[SPI_MAX_SPI] = { SPI_PULL_MISO_DEF, SPI_PULL_MISO_DEF }; + static const int spi_pull_mosi[SPI_MAX_SPI] = { SPI_PULL_MOSI_DEF, SPI_PULL_MOSI_DEF }; + static const int spi_pull_sclk[SPI_MAX_SPI] = { SPI_PULL_SCLK_DEF, SPI_PULL_SCLK_DEF }; + + // Setup SPI pins for SPI0 and save the original setup + if(port_probe_mask & SPIX_PROBE_SPI0_MASK) { + uint32_t pud_miso = pud_val_to_mask(spi_pull_miso[0]); + uint32_t pud_mosi = pud_val_to_mask(spi_pull_mosi[0]); + uint32_t pud_sclk = pud_val_to_mask(spi_pull_sclk[0]); + + spi0save.bank_sclk = reg_rd(&iobank0->gpio[SPI0_PIN_SCLK].ctrl); + spi0save.bank_mosi = reg_rd(&iobank0->gpio[SPI0_PIN_MOSI].ctrl); + spi0save.bank_miso = reg_rd(&iobank0->gpio[SPI0_PIN_MISO].ctrl); + spi0save.bank_ce_0 = reg_rd(&iobank0->gpio[SPI0_PIN_CE_0].ctrl); + spi0save.bank_ce_1 = reg_rd(&iobank0->gpio[SPI0_PIN_CE_1].ctrl); + spi0save.pads_sclk = reg_rd(&padsbank0->gpio[SPI0_PIN_SCLK]); + spi0save.pads_mosi = reg_rd(&padsbank0->gpio[SPI0_PIN_MOSI]); + spi0save.pads_miso = reg_rd(&padsbank0->gpio[SPI0_PIN_MISO]); + spi0save.pads_ce_0 = reg_rd(&padsbank0->gpio[SPI0_PIN_CE_0]); + spi0save.pads_ce_1 = reg_rd(&padsbank0->gpio[SPI0_PIN_CE_1]); + + reg_wr(&iobank0->gpio[SPI0_PIN_SCLK].ctrl, RP1_GPIO_CTRL_FUNCSEL(RP1_GPIO_CTRL_FUNCSEL_ALT0)); + reg_wr(&padsbank0->gpio[SPI0_PIN_SCLK], RP1_PADS_IE | DRIVE_LEVEL | pud_sclk); + reg_wr(&iobank0->gpio[SPI0_PIN_MOSI].ctrl, RP1_GPIO_CTRL_FUNCSEL(RP1_GPIO_CTRL_FUNCSEL_ALT0)); + reg_wr(&padsbank0->gpio[SPI0_PIN_MOSI], RP1_PADS_IE | DRIVE_LEVEL | pud_mosi); + reg_wr(&iobank0->gpio[ SPI0_PIN_MISO].ctrl, RP1_GPIO_CTRL_FUNCSEL(RP1_GPIO_CTRL_FUNCSEL_ALT0)); + reg_wr(&padsbank0->gpio[SPI0_PIN_MISO], RP1_PADS_IE | DRIVE_LEVEL | pud_miso); + if(port_probe_mask & SPIX_PROBE_SPI0_CE0) { + reg_wr(&iobank0->gpio[SPI0_PIN_CE_0].ctrl, RP1_GPIO_CTRL_FUNCSEL(RP1_GPIO_CTRL_FUNCSEL_ALT0)); + reg_wr(&padsbank0->gpio[SPI0_PIN_CE_0], RP1_PADS_IE | DRIVE_LEVEL | RP1_PADS_PUE); + } + if(port_probe_mask & SPIX_PROBE_SPI0_CE1) { + reg_wr(&iobank0->gpio[SPI0_PIN_CE_1].ctrl, RP1_GPIO_CTRL_FUNCSEL(RP1_GPIO_CTRL_FUNCSEL_ALT0)); + reg_wr(&padsbank0->gpio[SPI0_PIN_CE_1], RP1_PADS_IE | DRIVE_LEVEL | RP1_PADS_PUE); + } + spi_reset(spi_ports[PORT_SPI0].port); + } + + // Setup SPI pins for SPI1 and save the original setup + if(port_probe_mask & SPIX_PROBE_SPI1_MASK) { + uint32_t pud_miso = pud_val_to_mask(spi_pull_miso[1]); + uint32_t pud_mosi = pud_val_to_mask(spi_pull_mosi[1]); + uint32_t pud_sclk = pud_val_to_mask(spi_pull_sclk[1]); + + spi1save.bank_sclk = reg_rd(&iobank0->gpio[SPI1_PIN_SCLK].ctrl); + spi1save.bank_mosi = reg_rd(&iobank0->gpio[SPI1_PIN_MOSI].ctrl); + spi1save.bank_miso = reg_rd(&iobank0->gpio[SPI1_PIN_MISO].ctrl); + spi1save.bank_ce_0 = reg_rd(&iobank0->gpio[SPI1_PIN_CE_0].ctrl); + spi1save.bank_ce_1 = reg_rd(&iobank0->gpio[SPI1_PIN_CE_1].ctrl); + spi1save.bank_ce_2 = reg_rd(&iobank0->gpio[SPI1_PIN_CE_2].ctrl); + spi1save.pads_sclk = reg_rd(&padsbank0->gpio[SPI1_PIN_SCLK]); + spi1save.pads_mosi = reg_rd(&padsbank0->gpio[SPI1_PIN_MOSI]); + spi1save.pads_miso = reg_rd(&padsbank0->gpio[SPI1_PIN_MISO]); + spi1save.pads_ce_0 = reg_rd(&padsbank0->gpio[SPI1_PIN_CE_0]); + spi1save.pads_ce_1 = reg_rd(&padsbank0->gpio[SPI1_PIN_CE_1]); + spi1save.pads_ce_2 = reg_rd(&padsbank0->gpio[SPI1_PIN_CE_2]); + + reg_wr(&iobank0->gpio[SPI1_PIN_SCLK].ctrl, RP1_GPIO_CTRL_FUNCSEL(RP1_GPIO_CTRL_FUNCSEL_ALT0)); + reg_wr(&padsbank0->gpio[SPI1_PIN_SCLK], RP1_PADS_IE | DRIVE_LEVEL | pud_sclk); + reg_wr(&iobank0->gpio[SPI1_PIN_MOSI].ctrl, RP1_GPIO_CTRL_FUNCSEL(RP1_GPIO_CTRL_FUNCSEL_ALT0)); + reg_wr(&padsbank0->gpio[SPI1_PIN_MOSI], RP1_PADS_IE | DRIVE_LEVEL | pud_mosi); + reg_wr(&iobank0->gpio[SPI1_PIN_MISO].ctrl, RP1_GPIO_CTRL_FUNCSEL(RP1_GPIO_CTRL_FUNCSEL_ALT0)); + reg_wr(&padsbank0->gpio[SPI1_PIN_MISO], RP1_PADS_IE | DRIVE_LEVEL | pud_miso); + if(port_probe_mask & SPIX_PROBE_SPI1_CE0) { + reg_wr(&iobank0->gpio[SPI1_PIN_CE_0].ctrl, RP1_GPIO_CTRL_FUNCSEL(RP1_GPIO_CTRL_FUNCSEL_ALT0)); + reg_wr(&padsbank0->gpio[SPI1_PIN_CE_0], RP1_PADS_IE | DRIVE_LEVEL | RP1_PADS_PUE); + } + if(port_probe_mask & SPIX_PROBE_SPI1_CE1) { + reg_wr(&iobank0->gpio[SPI1_PIN_CE_1].ctrl, RP1_GPIO_CTRL_FUNCSEL(RP1_GPIO_CTRL_FUNCSEL_ALT0)); + reg_wr(&padsbank0->gpio[SPI1_PIN_CE_1], RP1_PADS_IE | DRIVE_LEVEL | RP1_PADS_PUE); + } + if(port_probe_mask & SPIX_PROBE_SPI1_CE2) { + reg_wr(&iobank0->gpio[SPI1_PIN_CE_2].ctrl, RP1_GPIO_CTRL_FUNCSEL(RP1_GPIO_CTRL_FUNCSEL_ALT0)); + reg_wr(&padsbank0->gpio[SPI1_PIN_CE_2], RP1_PADS_IE | DRIVE_LEVEL | RP1_PADS_PUE); + } + spi_reset(spi_ports[PORT_SPI1].port); + } + +#ifdef RPSPI_DEBUG_PIN + reg_wr(&rio0->set.out, 1 << RPSPI_DEBUG_PIN); // Set pin high + reg_wr(&rio0->set.oe, 1 << RPSPI_DEBUG_PIN); // Set pin to output + reg_wr(&padsbank0->gpio[RPSPI_DEBUG_PIN], RP1_PADS_IE | DRIVE_LEVEL | RP1_PADS_PUE); + reg_wr(&iobank0->gpio[RPSPI_DEBUG_PIN].ctrl, RP1_GPIO_CTRL_FUNCSEL(RP1_GPIO_CTRL_FUNCSEL_SYS_RIO)); +#endif +} + +/*************************************************/ +static void peripheral_restore(void) +{ + // Restore all SPI pins to inputs and enable pull-up (no dangling inputs) + if(port_probe_mask & SPIX_PROBE_SPI0_MASK) { + spi_reset(spi_ports[PORT_SPI0].port); // Disable SPI0 peripheral + + // Restore SPI0 pins + reg_wr(&padsbank0->gpio[SPI0_PIN_SCLK], spi0save.pads_sclk); + reg_wr(&padsbank0->gpio[SPI0_PIN_MOSI], spi0save.pads_mosi); + reg_wr(&padsbank0->gpio[SPI0_PIN_MISO], spi0save.pads_miso); + reg_wr(&iobank0->gpio[SPI0_PIN_SCLK].ctrl, spi0save.bank_sclk); + reg_wr(&iobank0->gpio[SPI0_PIN_MOSI].ctrl, spi0save.bank_mosi); + reg_wr(&iobank0->gpio[SPI0_PIN_MISO].ctrl, spi0save.bank_miso); + if(port_probe_mask & SPIX_PROBE_SPI0_CE0) { + reg_wr(&padsbank0->gpio[SPI0_PIN_CE_0], spi0save.pads_ce_0); + reg_wr(&iobank0->gpio[SPI0_PIN_CE_0].ctrl, spi0save.bank_ce_0); + } + if(port_probe_mask & SPIX_PROBE_SPI0_CE1) { + reg_wr(&padsbank0->gpio[SPI0_PIN_CE_1], spi0save.pads_ce_1); + reg_wr(&iobank0->gpio[SPI0_PIN_CE_1].ctrl, spi0save.bank_ce_1); + } + } + + if(port_probe_mask & SPIX_PROBE_SPI1_MASK) { + spi_reset(spi_ports[PORT_SPI1].port); // Disable SPI1 peripheral + + // Restore SPI1 pins + reg_wr(&padsbank0->gpio[SPI1_PIN_SCLK], spi1save.pads_sclk); + reg_wr(&padsbank0->gpio[SPI1_PIN_MOSI], spi1save.pads_mosi); + reg_wr(&padsbank0->gpio[SPI1_PIN_MISO], spi1save.pads_miso); + reg_wr(&iobank0->gpio[SPI1_PIN_SCLK].ctrl, spi1save.bank_sclk); + reg_wr(&iobank0->gpio[SPI1_PIN_MOSI].ctrl, spi1save.bank_mosi); + reg_wr(&iobank0->gpio[SPI1_PIN_MISO].ctrl, spi1save.bank_miso); + if(port_probe_mask & SPIX_PROBE_SPI1_CE0) { + reg_wr(&padsbank0->gpio[SPI1_PIN_CE_0], spi1save.pads_ce_0); + reg_wr(&iobank0->gpio[SPI1_PIN_CE_0].ctrl, spi1save.bank_ce_0); + } + if(port_probe_mask & SPIX_PROBE_SPI1_CE1) { + reg_wr(&padsbank0->gpio[SPI1_PIN_CE_1], spi1save.pads_ce_1); + reg_wr(&iobank0->gpio[SPI1_PIN_CE_1].ctrl, spi1save.bank_ce_1); + } + if(port_probe_mask & SPIX_PROBE_SPI1_CE2) { + reg_wr(&padsbank0->gpio[SPI1_PIN_CE_2], spi1save.pads_ce_2); + reg_wr(&iobank0->gpio[SPI1_PIN_CE_2].ctrl, spi1save.bank_ce_2); + } + } + +#ifdef RPSPI_DEBUG_PIN + // This leaves the pin in SYS_RIO mode so we may trigger the scope on the + // next run + reg_wr(&rio0->clr.oe, 1 << RPSPI_DEBUG_PIN); // Set pin to input +#endif +} + +/*************************************************/ + +/* + * Detect the presence of the hardware on basis of the device-tree compatible + * string-list. + * On success returns 9 (zero) and sets the driver information to the dtc + * string and tries to set a human readable string as model. + */ +static int rpi5_detect(const char *dtcs[]) +{ + int i; + for(i = 0; dtcs[i] != NULL; i++) { + if(!strcmp(dtcs[i], DTC_RPI_MODEL_5B) || !strcmp(dtcs[i], DTC_RPI_MODEL_5CM)) { + break; // Found our supported board + } + } + + if(dtcs[i] == NULL) + return -ENODEV; // We are not the device the driver supports + + // Set the matched dtc and model informational strings + strncpy(spix_rpi5.dtc, dtcs[i], sizeof(spix_rpi5.dtc)-1); + if(spix_read_file("/proc/device-tree/model", spix_rpi5.model, sizeof(spix_rpi5.model)) < 0) + strncpy(spix_rpi5.model, "??? Unknown board ???", sizeof(spix_rpi5.model)-1); + + return 0; +} + +/* + * Setup the driver. + * - remove kernel spidev driver modules if detected + * - map the I/O memory + * - setup the GPIO pins and SPI peripheral(s) + */ +static int rpi5_setup(int probemask) +{ + int retval = -1; + + if(driver_enabled) { + LL_ERR("Driver is already setup.\n"); + return -EBUSY; + } + + port_probe_mask = probemask; // For peripheral_setup() and peripheral_restore() + + // Now we know what platform we are running, remove kernel SPI module if + // detected + if((has_spi_module = (0 == shell("/usr/bin/grep -qw ^dw_spi_mmio /proc/modules")))) { + if(shell("/sbin/rmmod dw_spi_mmio dw_spi")) + LL_ERR("Unable to remove kernel SPI modules dw_spi_mmio and dw_spi. " + "Your system may become unstable using LinuxCNC with the " HM2_LLIO_NAME " driver.\n"); + } + + // The IO address for the RPi5 is at a fixed address. No need to do fancy + // stuff. + if((retval = peripheral_map(RP1_PCIE_BAR1_ADDR, RP1_PCIE_BAR1_LEN)) < 0) { + LL_ERR("Cannot map peripheral memory.\n"); + return retval; + } + + peripheral_setup(); + + driver_enabled = 1; + + return 0; +} + +/* + * Cleanup the driver + * - close any open ports + * - restore pripheral settings + * - unmap I/ memory + * - re-insert kernel spidev hardware module(s) is previously detected + */ +static int rpi5_cleanup(void) +{ + int i; + + if(!driver_enabled) { + LL_ERR("Driver is not setup.\n"); + return -ENODEV; + } + + // Close any ports not closed already + for(i = 0; i < PORT_MAX; i++) { + if(spi_ports[i].isopen) + spix_rpi5.close(&spi_ports[i].spix); + } + + if(peripheralmem != MAP_FAILED) { + peripheral_restore(); + munmap(peripheralmem, peripheralsize); + } + + // Restore kernel SPI module if it was detected before + if(has_spi_module) + shell("/sbin/modprobe dw_spi_mmio"); + + driver_enabled = 0; + return 0; +} + +/* + * Open a SPI port at index 'port' with 'clkw' write clock and 'clkr' read + * clock frequencies. + */ +static const spix_port_t *rpi5_open(int port, uint32_t clkw, uint32_t clkr) +{ + rpi5_port_t *rpp; + + if(!driver_enabled) { + LL_ERR("Driver is not setup.\n"); + return NULL; + } + + if(port < 0 || port >= PORT_MAX) { + LL_ERR("open(): SPI port %d out of range.\n", port); + return NULL; + } + + rpp = &spi_ports[port]; + + if(!(port_probe_mask & (1 << port))) { + LL_ERR("%s: SPI port %d not setup, was not in probe mask (%02x).\n", rpp->spix.name, port, port_probe_mask); + return NULL; + } + + if(rpp->isopen) { + LL_ERR("%s: SPI port already open.\n", rpp->spix.name); + return NULL; + } + + if(clkw < SCLK_FREQ_MIN || clkw > SCLK_FREQ_MAX) { + LL_ERR("%s: SPI write clock frequency outside acceptable range (%d..%d kHz).\n", rpp->spix.name, SCLK_FREQ_MIN, SCLK_FREQ_MAX); + return NULL; + } + + rpp->isopen = 1; + rpp->clkdivw = clkdiv_calc(clkw); + rpp->clkdivr = clkdiv_calc(clkr); + LL_INFO("%s: write clock rate calculated: %u Hz (clkdiv: %u)\n", rpp->spix.name, RP1_SPI_CLK / rpp->clkdivw, rpp->clkdivw); + LL_INFO("%s: read clock rate calculated: %u Hz (clkdiv: %u)\n", rpp->spix.name, RP1_SPI_CLK / rpp->clkdivr, rpp->clkdivr); + + return &rpp->spix; +} + +/* + * Close a SPI port. + */ +static int rpi5_close(const spix_port_t *sp) +{ + rpi5_port_t *rpp; + + if(!driver_enabled) { + LL_ERR("Driver is not setup.\n"); + return -ENODEV; + } + + if(!sp) { + LL_ERR("close(): trying to close port NULL\n"); + return -EINVAL; + } + + rpp = (rpi5_port_t *)sp; + if(!rpp->isopen) { + LL_ERR("%s: close(): SPI port not open.\n", rpp->spix.name); + return -ENODEV; + } + + spi_reset(rpp->port); // make sure it is disabled + rpp->isopen = 0; + return 0; +} + +// vim: ts=4 diff --git a/src/hal/drivers/mesa-hostmot2/spix_spidev.c b/src/hal/drivers/mesa-hostmot2/spix_spidev.c new file mode 100644 index 00000000000..4a62325c611 --- /dev/null +++ b/src/hal/drivers/mesa-hostmot2/spix_spidev.c @@ -0,0 +1,330 @@ +/* + * This is a component for hostmot2 over SPI for linuxcnc. + * Copyright (c) 2024 B.Stultiens + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, see . + */ + +#include +#include +#include +#include +#include +#include + +#include + +#define HM2_LLIO_NAME "spix_spidev" + +#include "hostmot2-lowlevel.h" + +#include "spix.h" + + +/* + * Our SPI port descriptor extends the spix port descriptor. + */ +typedef struct __spi_port_t { + spix_port_t spix; // The upstream container + int isopen; // Non-zero if successfully opened + int fd; // Spidev file descriptor + uint32_t clkw; // Write clock divider setting + uint32_t clkr; // Read clock divider setting +} spidev_port_t; + +/* Forward decls */ +static int spidev_detect(const char *dtcs[]); +static int spidev_setup(int probemask); +static int spidev_cleanup(void); +static const spix_port_t *spidev_open(int port, uint32_t clkw, uint32_t clkr); +static int spidev_close(const spix_port_t *sp); +static int spi_transfer(const spix_port_t *sp, uint32_t *wptr, size_t txlen, int rw); + +#define PORT_MAX 5 +#define PORT_SPI0 0 // port index for hardware SPI0 +#define PORT_SPI1 2 // port index for hardware SPI1 +static spidev_port_t spi_ports[PORT_MAX] = { + { .spix = { .width = 8, .name = "/dev/spidev0.0", .transfer = spi_transfer }, .fd = -1 }, + { .spix = { .width = 8, .name = "/dev/spidev0.1", .transfer = spi_transfer }, .fd = -1 }, + { .spix = { .width = 8, .name = "/dev/spidev1.0", .transfer = spi_transfer }, .fd = -1 }, + { .spix = { .width = 8, .name = "/dev/spidev1.1", .transfer = spi_transfer }, .fd = -1 }, + { .spix = { .width = 8, .name = "/dev/spidev1.2", .transfer = spi_transfer }, .fd = -1 }, +}; + +/* + * The driver interface structure + */ +spix_driver_t spix_spidev = { + .name = HM2_LLIO_NAME, + .num_ports = PORT_MAX, + + .detect = spidev_detect, + .setup = spidev_setup, + .cleanup = spidev_cleanup, + .open = spidev_open, + .close = spidev_close, +}; + +static int driver_enabled; // Set to non-zero ehen spidev_setup() is successfully called +static int port_probe_mask; // Which ports are requested + + +/*********************************************************************/ +/* + * Transfer a buffer of words to the SPI port and fill the same buffer with the + * data coming from the SPI port. + */ +static int spi_transfer(const spix_port_t *sp, uint32_t *wptr, size_t txlen, int rw) +{ + spidev_port_t *sdp = (spidev_port_t *)sp; + struct spi_ioc_transfer sit; + uint32_t u; + + if(!txlen) + return 1; // Nothing to do, return success + if(sdp->fd < 0) + return 0; // Error, no file descriptor + + // Using 8-bit transfers; need to byte-swap to big-endian to get the word's + // most significant bit shifted out first. + for(u = 0; u < txlen; u++) + wptr[u] = htobe32(wptr[u]); + + memset(&sit, 0, sizeof(sit)); + sit.tx_buf = (uintptr_t)wptr; + sit.rx_buf = (uintptr_t)wptr; + sit.len = txlen * sizeof(uint32_t); + sit.speed_hz = rw ? sdp->clkr : sdp->clkw; + sit.bits_per_word = 8; + if(ioctl(sdp->fd, SPI_IOC_MESSAGE(1), &sit) < 0) { + int e = errno; + LL_ERR("%s: SPI transfer failed: %s", sdp->spix.name, strerror(e)); + return -e; + } + + // Put the received words into host order + for(u = 0; u < txlen; u++) + wptr[u] = be32toh(wptr[u]); + + return 1; +} + +/*************************************************/ +/* + * Open and setup a /dev/spidevX.Y port + */ +static int port_configure(spidev_port_t *sdp, uint32_t clkw, uint32_t clkr) +{ + int fd; + uint8_t b; + uint32_t w; + int e; + + if((fd = open(sdp->spix.name, O_RDWR)) < 0) { + LL_ERR("%s: Cannot open port: %s\n", sdp->spix.name, strerror(e = errno)); + return -e; + } + + w = SPI_MODE_0; // CPOL=0, CPHA=0 + if(ioctl(fd, SPI_IOC_WR_MODE32, &w) < 0) { + LL_ERR("%s: Cannot set mode 0: %s\n", sdp->spix.name, strerror(e = errno)); + close(fd); + return -e; + } + + b = 0; // Set MSB-first + if(ioctl(fd, SPI_IOC_WR_LSB_FIRST, &b) < 0) { + LL_ERR("%s: Cannot set MSB-first order: %s\n", sdp->spix.name, strerror(e = errno)); + close(fd); + return -e; + } + + b = 8; // Set 8-bit per word transfer + if(ioctl(fd, SPI_IOC_WR_BITS_PER_WORD, &b) < 0) { + LL_ERR("%s: Cannot set MSB-first order: %s\n", sdp->spix.name, strerror(e = errno)); + close(fd); + return -e; + } + + // Test setting the write speed (overridden in transfer anyway) + if(ioctl(fd, SPI_IOC_WR_MAX_SPEED_HZ, &clkw) < 0) { + LL_ERR("%s: Cannot set write clock speed to %u: %s\n", sdp->spix.name, clkw, strerror(e = errno)); + close(fd); + return -e; + } + + // Read back write clock + if(ioctl(fd, SPI_IOC_RD_MAX_SPEED_HZ, &clkw) < 0) { + LL_ERR("%s: Cannot get write clock speed: %s\n", sdp->spix.name, strerror(e = errno)); + close(fd); + return -e; + } + sdp->clkw = clkw; + + // Test setting the read speed (overridden in transfer anyway) + if(ioctl(fd, SPI_IOC_WR_MAX_SPEED_HZ, &clkr) < 0) { + LL_ERR("%s: Cannot set read clock speed to %u: %s\n", sdp->spix.name, clkr, strerror(e = errno)); + close(fd); + return -e; + } + + // Read back read clock + if(ioctl(fd, SPI_IOC_RD_MAX_SPEED_HZ, &clkr) < 0) { + LL_ERR("%s: Cannot get write clock speed: %s\n", sdp->spix.name, strerror(e = errno)); + close(fd); + return -e; + } + sdp->clkr = clkr; + + sdp->fd = fd; + return 0; +} + +/*************************************************/ +/* + * Detect the presence of the hardware on basis of the device-tree compatible + * string-list. + * On success returns 0 (zero) and sets the driver information to the dtc + * string and tries to set a human readable string as model. + * + * The spix_spidev driver always succeeds with detection. We could check the + * presence of /dev/spidev* entries, but the port opens will fail if they do + * not exists. + */ +static int spidev_detect(const char *dtcs[]) +{ + // Set the matched dtc and model informational strings + if(dtcs[0]) + strncpy(spix_spidev.dtc, dtcs[0], sizeof(spix_spidev.dtc)-1); + else + strncpy(spix_spidev.dtc, "spix_spidev-unknown-dtc", sizeof(spix_spidev.dtc)-1); + if(spix_read_file("/proc/device-tree/model", spix_spidev.model, sizeof(spix_spidev.model)) < 0) + strncpy(spix_spidev.model, "??? Unknown board ???", sizeof(spix_spidev.model)-1); + + return 0; +} + +/* + * Setup the driver. + * Not much to do for spidev. + */ +static int spidev_setup(int probemask) +{ + if(driver_enabled) { + LL_ERR("Driver is already setup.\n"); + return -EBUSY; + } + + port_probe_mask = probemask; + + driver_enabled = 1; + + return 0; +} + +/* + * Cleanup the driver + * - close any open ports + */ +static int spidev_cleanup(void) +{ + int i; + + if(!driver_enabled) { + LL_ERR("Driver is not setup.\n"); + return -ENODEV; + } + + // Close any ports not closed already + for(i = 0; i < PORT_MAX; i++) { + if(spi_ports[i].isopen) + spix_spidev.close(&spi_ports[i].spix); + } + + driver_enabled = 0; + return 0; +} + +/* + * Open a SPI port at index 'port' with 'clkw' write clock and 'clkr' read + * clock frequencies. + */ +static const spix_port_t *spidev_open(int port, uint32_t clkw, uint32_t clkr) +{ + spidev_port_t *sdp; + + if(!driver_enabled) { + LL_ERR("Driver is not setup.\n"); + return NULL; + } + + if(port < 0 || port >= PORT_MAX) { + LL_ERR("open(): SPI port %d out of range.\n", port); + return NULL; + } + + sdp = &spi_ports[port]; + + if(!(port_probe_mask & (1 << port))) { + LL_ERR("%s: SPI port %d not setup, was not in probe mask (%02x).\n", sdp->spix.name, port, port_probe_mask); + return NULL; + } + + if(sdp->isopen) { + LL_ERR("%s: SPI port already open.\n", sdp->spix.name); + return NULL; + } + + if(port_configure(sdp, clkw, clkr) < 0) + return NULL; + + sdp->isopen = 1; + LL_INFO("%s: write clock rate calculated: %u Hz\n", sdp->spix.name, sdp->clkw); + LL_INFO("%s: read clock rate calculated: %u Hz\n", sdp->spix.name, sdp->clkr); + + return &sdp->spix; +} + +/* + * Close a SPI port. + */ +static int spidev_close(const spix_port_t *sp) +{ + spidev_port_t *sdp = (spidev_port_t *)sp; + + if(!driver_enabled) { + LL_ERR("Driver is not setup.\n"); + return -ENODEV; + } + + if(!sp) { + LL_ERR("close(): trying to close port NULL\n"); + return -EINVAL; + } + + if(!sdp->isopen) { + LL_ERR("%s: close(): SPI port not open.\n", sdp->spix.name); + return -ENODEV; + } + + if(sdp->fd >= 0) { + close(sdp->fd); + sdp->fd = -1; + } + + sdp->isopen = 0; + return 0; +} + +// vim: ts=4 From 782b891fa231c9d851c4d2461f8d7bbbf4f0fc60 Mon Sep 17 00:00:00 2001 From: Bertho Stultiens Date: Mon, 2 Dec 2024 16:16:36 +0100 Subject: [PATCH 02/18] Don't forget the makefile to build the new driver. --- src/Makefile | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/Makefile b/src/Makefile index 965c93a2f5b..068581167d0 100644 --- a/src/Makefile +++ b/src/Makefile @@ -1013,7 +1013,7 @@ endif obj-$(CONFIG_HOSTMOT2) += hostmot2.o hm2_test.o hm2_pci.o hm2_7i43.o hm2_7i90.o setsserial.o ifeq ($(BUILD_SYS),uspace) -obj-$(CONFIG_HOSTMOT2) += hm2_eth.o hm2_spi.o hm2_rpspi.o +obj-$(CONFIG_HOSTMOT2) += hm2_eth.o hm2_spi.o hm2_rpspi.o hm2_spix.o endif hostmot2-objs := \ hal/drivers/mesa-hostmot2/hostmot2.o \ @@ -1065,6 +1065,14 @@ hm2_spi-objs := \ hm2_rpspi-objs := \ hal/drivers/mesa-hostmot2/hm2_rpspi.o \ $(MATHSTUB) +hm2_spix-objs := \ + hal/drivers/mesa-hostmot2/hm2_spix.o \ + hal/drivers/mesa-hostmot2/spix_rpi5.o \ + hal/drivers/mesa-hostmot2/spix_rpi3.o \ + hal/drivers/mesa-hostmot2/spix_spidev.o \ + hal/drivers/mesa-hostmot2/llio_info.o \ + hal/drivers/mesa-hostmot2/eshellf.o \ + $(MATHSTUB) hm2_test-objs := \ hal/drivers/mesa-hostmot2/hm2_test.o \ hal/drivers/mesa-hostmot2/bitfile.o \ @@ -1341,6 +1349,7 @@ endif ../rtlib/hm2_eth$(MODULE_EXT): $(addprefix objects/rt,$(hm2_eth-objs)) ../rtlib/hm2_spi$(MODULE_EXT): $(addprefix objects/rt,$(hm2_spi-objs)) ../rtlib/hm2_rpspi$(MODULE_EXT): $(addprefix objects/rt,$(hm2_rpspi-objs)) +../rtlib/hm2_spix$(MODULE_EXT): $(addprefix objects/rt,$(hm2_spix-objs)) ../rtlib/hal_bb_gpio$(MODULE_EXT): $(addprefix objects/rt,$(hal_bb_gpio-objs)) ../rtlib/hal_pi_gpio$(MODULE_EXT): $(addprefix objects/rt,$(hal_pi_gpio-objs)) ifdef LIBGPIOD_VER From 6d673c8da4bcb20c1db7beed3e56bc35d77d1e0a Mon Sep 17 00:00:00 2001 From: Bertho Stultiens Date: Mon, 2 Dec 2024 18:23:58 +0100 Subject: [PATCH 03/18] Fix Hz/kHz confusion. Don't close ports forcefully at exit, let the driver do that on cleanup. Fix some text typos. Don't add isopen to spix_spidev driver. It can be determined from the file descriptor. --- src/hal/drivers/mesa-hostmot2/hm2_spix.c | 3 +-- src/hal/drivers/mesa-hostmot2/spix.h | 4 +-- src/hal/drivers/mesa-hostmot2/spix_rpi3.c | 4 +-- src/hal/drivers/mesa-hostmot2/spix_spidev.c | 27 +++++++++------------ 4 files changed, 16 insertions(+), 22 deletions(-) diff --git a/src/hal/drivers/mesa-hostmot2/hm2_spix.c b/src/hal/drivers/mesa-hostmot2/hm2_spix.c index 06d1359b990..43f0cc4cd28 100644 --- a/src/hal/drivers/mesa-hostmot2/hm2_spix.c +++ b/src/hal/drivers/mesa-hostmot2/hm2_spix.c @@ -606,7 +606,7 @@ static int spix_setup(void) if(!(spi_probe & (1 << i))) // Only probe if enabled continue; - if(NULL == (port = hwdriver->open(i, spiclk_rate[j], spiclk_rate_rd[j]))) { + if(NULL == (port = hwdriver->open(i, spiclk_rate[j] * 1000, spiclk_rate_rd[j] * 1000))) { LL_INFO("Failed to open hardware port index %d\n", i); return i; } @@ -649,7 +649,6 @@ static void spix_cleanup(void) buffer_free(&boards[i].wbuf); buffer_free(&boards[i].rbuf); buffer_free(&boards[i].rref); - hwdriver->close(boards[i].port); } if(hwdriver) { diff --git a/src/hal/drivers/mesa-hostmot2/spix.h b/src/hal/drivers/mesa-hostmot2/spix.h index df63f1f61e9..ae61a2abf74 100644 --- a/src/hal/drivers/mesa-hostmot2/spix.h +++ b/src/hal/drivers/mesa-hostmot2/spix.h @@ -122,8 +122,8 @@ typedef struct __spix_driver_t { * spix_port_t *open(int port, uint32_t clkw, uint32_t clkr) * * Open 'port' (number 0...N) with specified write clock 'clkw' and read - * clock 'clkr'. The driver will check the values. - * Return value is a reference to a the port to use or NULL on failure. + * clock 'clkr' (both in Hz). The driver will check the values. + * Return value is a reference to the port to use or NULL on failure. */ const spix_port_t *(*open)(int port, uint32_t clkw, uint32_t clkr); diff --git a/src/hal/drivers/mesa-hostmot2/spix_rpi3.c b/src/hal/drivers/mesa-hostmot2/spix_rpi3.c index c2d45b449c5..3008ac5e9c9 100644 --- a/src/hal/drivers/mesa-hostmot2/spix_rpi3.c +++ b/src/hal/drivers/mesa-hostmot2/spix_rpi3.c @@ -120,8 +120,8 @@ spix_driver_t spix_rpi3 = { .close = rpi3_close, }; -static int has_spi_module; // Set to non-zero when the kernel modules dw_spi and dw_spi_mmio are loaded -static int driver_enabled; // Set to non-zero ehen rpi3_setup() is successfully called +static int has_spi_module; // Set to non-zero when the kernel module spi_bcm2835 is loaded +static int driver_enabled; // Set to non-zero when rpi3_setup() is successfully called static int port_probe_mask; // Which ports are requested static void *peripheralmem = MAP_FAILED; // mmap'ed peripheral memory diff --git a/src/hal/drivers/mesa-hostmot2/spix_spidev.c b/src/hal/drivers/mesa-hostmot2/spix_spidev.c index 4a62325c611..cec398b68fb 100644 --- a/src/hal/drivers/mesa-hostmot2/spix_spidev.c +++ b/src/hal/drivers/mesa-hostmot2/spix_spidev.c @@ -37,10 +37,9 @@ */ typedef struct __spi_port_t { spix_port_t spix; // The upstream container - int isopen; // Non-zero if successfully opened int fd; // Spidev file descriptor - uint32_t clkw; // Write clock divider setting - uint32_t clkr; // Read clock divider setting + uint32_t clkw; // Write clock setting + uint32_t clkr; // Read clock setting } spidev_port_t; /* Forward decls */ @@ -76,7 +75,7 @@ spix_driver_t spix_spidev = { .close = spidev_close, }; -static int driver_enabled; // Set to non-zero ehen spidev_setup() is successfully called +static int driver_enabled; // Set to non-zero when spidev_setup() is successfully called static int port_probe_mask; // Which ports are requested @@ -107,10 +106,10 @@ static int spi_transfer(const spix_port_t *sp, uint32_t *wptr, size_t txlen, int sit.len = txlen * sizeof(uint32_t); sit.speed_hz = rw ? sdp->clkr : sdp->clkw; sit.bits_per_word = 8; + sit.delay_usecs = 10; // Magic if(ioctl(sdp->fd, SPI_IOC_MESSAGE(1), &sit) < 0) { - int e = errno; - LL_ERR("%s: SPI transfer failed: %s", sdp->spix.name, strerror(e)); - return -e; + LL_ERR("%s: SPI transfer failed: %s", sdp->spix.name, strerror(errno)); + return 0; } // Put the received words into host order @@ -248,7 +247,7 @@ static int spidev_cleanup(void) // Close any ports not closed already for(i = 0; i < PORT_MAX; i++) { - if(spi_ports[i].isopen) + if(spi_ports[i].fd >= 0) spix_spidev.close(&spi_ports[i].spix); } @@ -281,7 +280,7 @@ static const spix_port_t *spidev_open(int port, uint32_t clkw, uint32_t clkr) return NULL; } - if(sdp->isopen) { + if(sdp->fd >= 0) { LL_ERR("%s: SPI port already open.\n", sdp->spix.name); return NULL; } @@ -289,7 +288,6 @@ static const spix_port_t *spidev_open(int port, uint32_t clkw, uint32_t clkr) if(port_configure(sdp, clkw, clkr) < 0) return NULL; - sdp->isopen = 1; LL_INFO("%s: write clock rate calculated: %u Hz\n", sdp->spix.name, sdp->clkw); LL_INFO("%s: read clock rate calculated: %u Hz\n", sdp->spix.name, sdp->clkr); @@ -313,17 +311,14 @@ static int spidev_close(const spix_port_t *sp) return -EINVAL; } - if(!sdp->isopen) { + if(sdp->fd < 0) { LL_ERR("%s: close(): SPI port not open.\n", sdp->spix.name); return -ENODEV; } - if(sdp->fd >= 0) { - close(sdp->fd); - sdp->fd = -1; - } + close(sdp->fd); + sdp->fd = -1; - sdp->isopen = 0; return 0; } From 9be6cd557c98c960b22c8ed3aa6ec49f76b91d10 Mon Sep 17 00:00:00 2001 From: Bertho Stultiens Date: Mon, 2 Dec 2024 18:27:08 +0100 Subject: [PATCH 04/18] Fix more Hz/kHz confusion. --- src/hal/drivers/mesa-hostmot2/spix_rpi3.c | 6 +++--- src/hal/drivers/mesa-hostmot2/spix_rpi5.c | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/hal/drivers/mesa-hostmot2/spix_rpi3.c b/src/hal/drivers/mesa-hostmot2/spix_rpi3.c index 3008ac5e9c9..3f39d82ef3e 100644 --- a/src/hal/drivers/mesa-hostmot2/spix_rpi3.c +++ b/src/hal/drivers/mesa-hostmot2/spix_rpi3.c @@ -39,9 +39,9 @@ //#define RPSPI_DEBUG_PIN 23 // Define for pin-debugging // The min/max allowed frequencies of the SPI clock -#define SCLK_FREQ_MIN 30 -#define SCLK_FREQ_MAX 50000 -#define SCLK_FREQ_DEF 25000 // Default +#define SCLK_FREQ_MIN 30000 +#define SCLK_FREQ_MAX 50000000 +#define SCLK_FREQ_DEF 25000000 // Default #define SPI_MAX_SPI 2 // SPI0 and SPI1 diff --git a/src/hal/drivers/mesa-hostmot2/spix_rpi5.c b/src/hal/drivers/mesa-hostmot2/spix_rpi5.c index dbce2d40696..6845b537d82 100644 --- a/src/hal/drivers/mesa-hostmot2/spix_rpi5.c +++ b/src/hal/drivers/mesa-hostmot2/spix_rpi5.c @@ -35,9 +35,9 @@ //#define RPSPI_DEBUG_PIN 23 // Define for pin-debugging // The min/max allowed frequencies of the SPI clock -#define SCLK_FREQ_MIN 4 -#define SCLK_FREQ_MAX 50000 -#define SCLK_FREQ_DEF 25000 // Default +#define SCLK_FREQ_MIN 4000 +#define SCLK_FREQ_MAX 50000000 +#define SCLK_FREQ_DEF 25000000 // Default #define SPI_MAX_SPI 2 // SPI0 and SPI1 From ca68c1b298db5b87a451ba94a34d452aafdd77e3 Mon Sep 17 00:00:00 2001 From: Bertho Stultiens Date: Wed, 4 Dec 2024 12:07:30 +0100 Subject: [PATCH 05/18] Remove unused includes and defines. Add spi_pull_* arguments for compatibility and warn if they are used. Use rtapi_open_as_root instead of plain open. --- src/hal/drivers/mesa-hostmot2/hm2_spix.c | 30 ++++++++++++--------- src/hal/drivers/mesa-hostmot2/spix_rpi3.c | 7 ++--- src/hal/drivers/mesa-hostmot2/spix_rpi5.c | 1 - src/hal/drivers/mesa-hostmot2/spix_spidev.c | 3 --- 4 files changed, 22 insertions(+), 19 deletions(-) diff --git a/src/hal/drivers/mesa-hostmot2/hm2_spix.c b/src/hal/drivers/mesa-hostmot2/hm2_spix.c index 43f0cc4cd28..10bdb463ebd 100644 --- a/src/hal/drivers/mesa-hostmot2/hm2_spix.c +++ b/src/hal/drivers/mesa-hostmot2/hm2_spix.c @@ -16,23 +16,12 @@ * this program; if not, see . */ -/* Without Source Tree */ -#undef WOST - -#include #include -#include #include #include -#include -#include -#include -#include #include -#include #include -#include #include #define HM2_LLIO_NAME "hm2_spix" @@ -154,6 +143,18 @@ RTAPI_MP_INT(spi_noqueue, "Disable queued SPI requests, use for debugging only ( static int spi_debug = -1; RTAPI_MP_INT(spi_debug, "Set message level for debugging purpose [0...5] where 0=none and 5=all (default: -1; upstream defined)") +/* + * We have these for compatibility with the hm2_rpspi driver. You can simply + * change the driver name in the ini/hal files without having to switch + * arguments. + */ +static int spi_pull_miso = -1; +static int spi_pull_mosi = -1; +static int spi_pull_sclk = -1; +RTAPI_MP_INT(spi_pull_miso, "Obsolete parameter") +RTAPI_MP_INT(spi_pull_mosi, "Obsolete parameter") +RTAPI_MP_INT(spi_pull_sclk, "Obsolete parameter") + /*********************************************************************/ /* * Buffer management for queued transfers. @@ -493,7 +494,7 @@ ssize_t spix_read_file(const char *fname, void *buffer, size_t bufsize) memset(buffer, 0, bufsize); - if(!(fd = open(fname, O_RDONLY))) { + if((fd = rtapi_open_as_root(fname, O_RDONLY)) < 0) { LL_ERR("Cannot open '%s' for read (errno=%d: %s)\n", fname, errno, strerror(errno)); return -errno; } @@ -536,6 +537,11 @@ static int spix_setup(void) } + // Check if spi_pull_{miso,mosi,sclk} were set and warn if they were + if(spi_pull_miso != -1 || spi_pull_mosi != -1 || spi_pull_sclk != -1) { + LL_WARN("Setting spi_pull_{miso,mosi,sclk} has no effect in the hm2_spix driver.\n"); + } + // Set process-level message level if requested if(spi_debug >= RTAPI_MSG_NONE && spi_debug <= RTAPI_MSG_ALL) rtapi_set_msg_level(spi_debug); diff --git a/src/hal/drivers/mesa-hostmot2/spix_rpi3.c b/src/hal/drivers/mesa-hostmot2/spix_rpi3.c index 3f39d82ef3e..44b64050869 100644 --- a/src/hal/drivers/mesa-hostmot2/spix_rpi3.c +++ b/src/hal/drivers/mesa-hostmot2/spix_rpi3.c @@ -41,7 +41,6 @@ // The min/max allowed frequencies of the SPI clock #define SCLK_FREQ_MIN 30000 #define SCLK_FREQ_MAX 50000000 -#define SCLK_FREQ_DEF 25000000 // Default #define SPI_MAX_SPI 2 // SPI0 and SPI1 @@ -132,7 +131,7 @@ static bcm2835_spi_t *spi; // SPI peripheral structure in mmap'ed address space static bcm2835_aux_t *aux; // AUX peripheral structure in mmap'ed address space static uint32_t aux_enables; // Previous state of SPI1 enable -#define F_PERI 400000000UL +#define F_PERI 500000000UL static uint32_t spiclk_base = F_PERI; // The base clock (sys_clk) for the SPI port dividers /*********************************************************************/ @@ -498,8 +497,10 @@ static void peripheral_setup(void) // Check if SPI1 needs to be enabled aux_enables = reg_rd(&aux->enables); - if(!(aux_enables & AUX_ENABLES_SPI1)) + if(!(aux_enables & AUX_ENABLES_SPI1)) { reg_wr(&aux->enables, aux_enables | AUX_ENABLES_SPI1); // Enable SPI1 + LL_WARN("SPI1 needed to be enabled.\n"); + } spi1_reset(); } diff --git a/src/hal/drivers/mesa-hostmot2/spix_rpi5.c b/src/hal/drivers/mesa-hostmot2/spix_rpi5.c index 6845b537d82..050f1dfdaba 100644 --- a/src/hal/drivers/mesa-hostmot2/spix_rpi5.c +++ b/src/hal/drivers/mesa-hostmot2/spix_rpi5.c @@ -37,7 +37,6 @@ // The min/max allowed frequencies of the SPI clock #define SCLK_FREQ_MIN 4000 #define SCLK_FREQ_MAX 50000000 -#define SCLK_FREQ_DEF 25000000 // Default #define SPI_MAX_SPI 2 // SPI0 and SPI1 diff --git a/src/hal/drivers/mesa-hostmot2/spix_spidev.c b/src/hal/drivers/mesa-hostmot2/spix_spidev.c index cec398b68fb..dd97d655290 100644 --- a/src/hal/drivers/mesa-hostmot2/spix_spidev.c +++ b/src/hal/drivers/mesa-hostmot2/spix_spidev.c @@ -19,7 +19,6 @@ #include #include #include -#include #include #include @@ -51,8 +50,6 @@ static int spidev_close(const spix_port_t *sp); static int spi_transfer(const spix_port_t *sp, uint32_t *wptr, size_t txlen, int rw); #define PORT_MAX 5 -#define PORT_SPI0 0 // port index for hardware SPI0 -#define PORT_SPI1 2 // port index for hardware SPI1 static spidev_port_t spi_ports[PORT_MAX] = { { .spix = { .width = 8, .name = "/dev/spidev0.0", .transfer = spi_transfer }, .fd = -1 }, { .spix = { .width = 8, .name = "/dev/spidev0.1", .transfer = spi_transfer }, .fd = -1 }, From 126b893c95611a2197a13443dd857c85543a2a8d Mon Sep 17 00:00:00 2001 From: Bertho Stultiens Date: Wed, 4 Dec 2024 17:05:50 +0100 Subject: [PATCH 06/18] Don't overwrite the loop variable. --- src/hal/drivers/mesa-hostmot2/hm2_spix.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/hal/drivers/mesa-hostmot2/hm2_spix.c b/src/hal/drivers/mesa-hostmot2/hm2_spix.c index 10bdb463ebd..ea353ab8267 100644 --- a/src/hal/drivers/mesa-hostmot2/hm2_spix.c +++ b/src/hal/drivers/mesa-hostmot2/hm2_spix.c @@ -608,6 +608,7 @@ static int spix_setup(void) // Follows SPI0/CE0, SPI0/CE1, SPI1/CE0, SPI1/CE1 and SPI1/CE2 for(j = i = 0; i < SPIX_MAX_BOARDS; i++) { const spix_port_t *port; + int err; if(!(spi_probe & (1 << i))) // Only probe if enabled continue; @@ -631,13 +632,13 @@ static int spix_setup(void) boards[j].llio.send_queued_writes = hm2_spix_send_queued_writes; } - if((i = probe_board(&boards[j])) < 0) { - return i; + if((err = probe_board(&boards[j])) < 0) { + return err; } - if((i = hm2_register(&boards[j].llio, config[j])) < 0) { + if((err = hm2_register(&boards[j].llio, config[j])) < 0) { LL_ERR("%s: hm2_register() failed.\n", port->name); - return i; + return err; } j++; // Next board From aaa404165fd0082edc20aa15a97131a4df0b95d8 Mon Sep 17 00:00:00 2001 From: Bertho Stultiens Date: Wed, 4 Dec 2024 17:07:59 +0100 Subject: [PATCH 07/18] Fix the SPI1 clock divider calculation. Use variable bit-length mode in SPI1 transfers. Fix swapping words back to host-order after transfer. --- src/hal/drivers/mesa-hostmot2/spix_rpi3.c | 45 ++++++++++++++--------- 1 file changed, 27 insertions(+), 18 deletions(-) diff --git a/src/hal/drivers/mesa-hostmot2/spix_rpi3.c b/src/hal/drivers/mesa-hostmot2/spix_rpi3.c index 44b64050869..e50ba3c834c 100644 --- a/src/hal/drivers/mesa-hostmot2/spix_rpi3.c +++ b/src/hal/drivers/mesa-hostmot2/spix_rpi3.c @@ -273,7 +273,7 @@ static int32_t spi1_clkdiv_calc(uint32_t base, uint32_t rate) uint32_t clkdiv; if(rate >= base / 2) return 0; - clkdiv = (base) / ((rate-1500) * 2) - 1; // -1500 for same scaling as SPI0 + clkdiv = base / (rate * 2) - 1; if(clkdiv > 4095) clkdiv = 4095; // Slowest possible return clkdiv; @@ -301,33 +301,35 @@ static int spi1_transfer(const spix_port_t *sp, uint32_t *wptr, size_t txlen, in gpio_debug_pin(false); // Word-swap to assure most significant bit to be sent first in 16-bit transfers. - for(u = 0; u < tx16len; u += 2) { + for(u = 0; u < txlen * sizeof(*w16ptr); u += 2) { uint16_t tt = w16ptr[u+0]; w16ptr[u+0] = w16ptr[u+1]; w16ptr[u+1] = tt; } // Setup clock speed and format - // Note: It seems that we cannot send 32 bits. Shift length 0 sends - // zero bits and there are only 6 bits available to set the length, - // giving a maximum of 31 bits. Furthermore, the shift seems to be - // delayed one clock. Therefore we need to shift 15 places (below) and - // not 16 as expected. The one-clock delay may be related to the - // OUT_RISING flag, but that is speculation. + // Note: It seems that we cannot send 32 bits. Shift length 0 sends zero + // bits and there are only 6 bits available to set the length, giving a + // maximum of 31 bits. Furthermore, the shift seems to be delayed one + // clock. + // Using variable width requires the upper byte of the data-word to hold + // the number of bits to shift. That too cannot be 32 because we cannot + // both use it for data and the shift count at the same time. The variable + // width needs to be left-aligned at bit 23. reg_wr(&aux->spi0_cntl0, AUX_SPI_CNTL0_SPEED(rw ? rp->clkdivr : rp->clkdivw) | AUX_SPI_CNTL0_ENABLE | AUX_SPI_CNTL0_MSB_OUT - | AUX_SPI_CNTL0_OUT_RISING - | AUX_SPI_CNTL0_SHIFT_LENGTH(16) + | AUX_SPI_CNTL0_IN_RISING + | AUX_SPI_CNTL0_VAR_WIDTH | rp->cemask); reg_wr(&aux->spi0_cntl1, AUX_SPI_CNTL1_MSB_IN); // Send data to the fifo while(tx16len > 0 && !(reg_rd(&aux->spi0_stat) & AUX_SPI_STAT_TX_FULL)) { if(tx16len > 1) - reg_wr(&aux->spi0_hold, ((uint32_t)*w16ptr) << 15); // More data to follow + reg_wr(&aux->spi0_hold, ((uint32_t)*w16ptr << 8) | (16 << 24)); // More data to follow else - reg_wr(&aux->spi0_io, ((uint32_t)*w16ptr) << 15); // Final write + reg_wr(&aux->spi0_io, ((uint32_t)*w16ptr << 8) | (16 << 24)); // Final write ++w16ptr; --tx16len; } @@ -342,20 +344,20 @@ static int spi1_transfer(const spix_port_t *sp, uint32_t *wptr, size_t txlen, in } if(tx16len > 0 && !(stat & AUX_SPI_STAT_TX_FULL)) { if(tx16len > 1) - reg_wr(&aux->spi0_hold, ((uint32_t)*w16ptr) << 15); // More data to follow + reg_wr(&aux->spi0_hold, ((uint32_t)*w16ptr << 8) | (16 << 24)); // More data to follow else - reg_wr(&aux->spi0_io, ((uint32_t)*w16ptr) << 15); // Final write + reg_wr(&aux->spi0_io, ((uint32_t)*w16ptr << 8) | (16 << 24)); // Final write ++w16ptr; --tx16len; } - } // Stop transfer spi1_reset(); // Word-swap to fix the word order back to host-order. - for(u = 0; u < tx16len; u += 2) { + w16ptr = (uint16_t *)wptr; + for(u = 0; u < txlen * sizeof(*w16ptr); u += 2) { uint16_t tt = w16ptr[u+0]; w16ptr[u+0] = w16ptr[u+1]; w16ptr[u+1] = tt; @@ -507,6 +509,7 @@ static void peripheral_setup(void) #if defined(RPSPI_DEBUG_PIN) gpio_fsel(RPSPI_DEBUG_PIN, GPIO_FSEL_X_GPIO_OUTPUT); + gpio_pull(RPSPI_DEBUG_PIN, GPIO_GPPUD_PULLUP); gpio_debug_pin(true); #endif } @@ -589,6 +592,7 @@ static uint32_t read_spiclkbase(void) LL_ERR("Cannot interpret clock setting '%s' from '%s', using %d Hz\n", buf, sysclkref, spiclk_base); return spiclk_base; } + LL_INFO("SPI clock base frequency: %u\n", rate); return rate; } @@ -776,6 +780,7 @@ static int rpi3_cleanup(void) static const spix_port_t *rpi3_open(int port, uint32_t clkw, uint32_t clkr) { rpi3_port_t *rpp; + uint32_t ccw, ccr; if(!driver_enabled) { LL_ERR("Driver is not setup.\n"); @@ -807,12 +812,16 @@ static const spix_port_t *rpi3_open(int port, uint32_t clkw, uint32_t clkr) if(!rpp->spiport) { rpp->clkdivw = spi0_clkdiv_calc(spiclk_base, clkw); rpp->clkdivr = spi0_clkdiv_calc(spiclk_base, clkr); + ccw = spiclk_base / rpp->clkdivw; + ccr = spiclk_base / rpp->clkdivr; } else { rpp->clkdivw = spi1_clkdiv_calc(spiclk_base, clkw); rpp->clkdivr = spi1_clkdiv_calc(spiclk_base, clkr); + ccw = spiclk_base / (2 * (rpp->clkdivw + 1)); + ccr = spiclk_base / (2 * (rpp->clkdivr + 1)); } - LL_INFO("%s: write clock rate calculated: %u Hz (clkdiv: %u)\n", rpp->spix.name, spiclk_base / rpp->clkdivw, rpp->clkdivw); - LL_INFO("%s: read clock rate calculated: %u Hz (clkdiv: %u)\n", rpp->spix.name, spiclk_base / rpp->clkdivr, rpp->clkdivr); + LL_INFO("%s: write clock rate calculated: %u Hz (clkdiv: %u)\n", rpp->spix.name, ccw, rpp->clkdivw); + LL_INFO("%s: read clock rate calculated: %u Hz (clkdiv: %u)\n", rpp->spix.name, ccr, rpp->clkdivr); rpp->isopen = 1; return &rpp->spix; From 85c7a2c59fe39f11865e8869c9ceb2c41f6428f0 Mon Sep 17 00:00:00 2001 From: Bertho Stultiens Date: Wed, 4 Dec 2024 17:20:27 +0100 Subject: [PATCH 08/18] Return an error for non-existent ports to probe and empty spi_probe. --- src/hal/drivers/mesa-hostmot2/hm2_spix.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/hal/drivers/mesa-hostmot2/hm2_spix.c b/src/hal/drivers/mesa-hostmot2/hm2_spix.c index ea353ab8267..34f05cd0c9d 100644 --- a/src/hal/drivers/mesa-hostmot2/hm2_spix.c +++ b/src/hal/drivers/mesa-hostmot2/hm2_spix.c @@ -542,6 +542,16 @@ static int spix_setup(void) LL_WARN("Setting spi_pull_{miso,mosi,sclk} has no effect in the hm2_spix driver.\n"); } + if(spi_probe > (1 << SPIX_MAX_BOARDS) - 1) { + LL_WARN("Probing with spi_probe must not have flags larger than %d included; truncating.\n", 1 << (SPIX_MAX_BOARDS - 1)); + spi_probe &= (1 << SPIX_MAX_BOARDS) - 1; + } + + if(!spi_probe) { + LL_ERR("No SPI ports to probe (spi_probe is zero).\n"); + return -EINVAL; + } + // Set process-level message level if requested if(spi_debug >= RTAPI_MSG_NONE && spi_debug <= RTAPI_MSG_ALL) rtapi_set_msg_level(spi_debug); From 8fbb4e8d6609182dc8b366c7a667f268ec896c1a Mon Sep 17 00:00:00 2001 From: Bertho Stultiens Date: Thu, 5 Dec 2024 13:47:06 +0100 Subject: [PATCH 09/18] Name driver structures better. Add more generic forwarding of module arguments when opening a port. Recalculate min/max frequencies for RPi3/4. Allow spidev device node path overrides. --- src/hal/drivers/mesa-hostmot2/hm2_spix.c | 24 +++++++---- src/hal/drivers/mesa-hostmot2/spix.h | 18 ++++++++- src/hal/drivers/mesa-hostmot2/spix_rpi3.c | 44 ++++++++++++++------- src/hal/drivers/mesa-hostmot2/spix_rpi5.c | 20 +++++----- src/hal/drivers/mesa-hostmot2/spix_spidev.c | 25 ++++++------ 5 files changed, 86 insertions(+), 45 deletions(-) diff --git a/src/hal/drivers/mesa-hostmot2/hm2_spix.c b/src/hal/drivers/mesa-hostmot2/hm2_spix.c index 34f05cd0c9d..e71a6deb0c4 100644 --- a/src/hal/drivers/mesa-hostmot2/hm2_spix.c +++ b/src/hal/drivers/mesa-hostmot2/hm2_spix.c @@ -76,16 +76,16 @@ static int comp_id; // Upstream assigned component ID * Supported hardware drivers * These are defined in spix_XXX source files. */ -extern spix_driver_t spix_rpi3; -extern spix_driver_t spix_rpi5; -extern spix_driver_t spix_spidev; +extern spix_driver_t spix_driver_rpi3; +extern spix_driver_t spix_driver_rpi5; +extern spix_driver_t spix_driver_spidev; static const spix_driver_t *drivers[] = { - &spix_rpi3, // RPi3, RPi3[ab]+, RPi4b and RPi4CM - &spix_rpi5, // RPi5 and RPi5CM + &spix_driver_rpi3, // RPi3, RPi3[ab]+, RPi4b and RPi4CM + &spix_driver_rpi5, // RPi5 and RPi5CM // TODO: orange pi // TODO: banana pi - &spix_spidev, + &spix_driver_spidev, // Default spidev driver as fallback }; static const spix_driver_t *hwdriver; // The active driver @@ -143,6 +143,12 @@ RTAPI_MP_INT(spi_noqueue, "Disable queued SPI requests, use for debugging only ( static int spi_debug = -1; RTAPI_MP_INT(spi_debug, "Set message level for debugging purpose [0...5] where 0=none and 5=all (default: -1; upstream defined)") +/* + * Spidev driver device node path overrides + */ +static char *spidev_path[SPIX_MAX_BOARDS]; +RTAPI_MP_ARRAY_STRING(spidev_path, SPIX_MAX_BOARDS, "The device node path override(s) for the spidev driver (default /dev/spidev{0.[01],1.[012]})") + /* * We have these for compatibility with the hm2_rpspi driver. You can simply * change the driver name in the ini/hal files without having to switch @@ -619,11 +625,15 @@ static int spix_setup(void) for(j = i = 0; i < SPIX_MAX_BOARDS; i++) { const spix_port_t *port; int err; + spix_args_t sa; if(!(spi_probe & (1 << i))) // Only probe if enabled continue; - if(NULL == (port = hwdriver->open(i, spiclk_rate[j] * 1000, spiclk_rate_rd[j] * 1000))) { + sa.clkw = spiclk_rate[j] * 1000; + sa.clkr = spiclk_rate_rd[j] * 1000; + sa.spidev = spidev_path[j]; + if(NULL == (port = hwdriver->open(i, &sa))) { LL_INFO("Failed to open hardware port index %d\n", i); return i; } diff --git a/src/hal/drivers/mesa-hostmot2/spix.h b/src/hal/drivers/mesa-hostmot2/spix.h index ae61a2abf74..19c48ecc558 100644 --- a/src/hal/drivers/mesa-hostmot2/spix.h +++ b/src/hal/drivers/mesa-hostmot2/spix.h @@ -78,6 +78,15 @@ typedef struct __spix_port_t { #define SPIX_MAX_BOARDS 5 // One on each (traditional) CE for SPI ports 0 and 1 #define SPIX_MAX_MSG (127+1) // The docs say that the max. burstlen == 127 words (i.e. cmd+message <= 1+127) +/* + * Module arguments passed to lower level + */ +typedef struct __spix_args_t { + uint32_t clkw; // The requested write clock + uint32_t clkr; // The requested read clock + const char *spidev; // spidev only: device node path override +} spix_args_t; + /* * SPI low level interface to hardware drivers */ @@ -119,13 +128,13 @@ typedef struct __spix_driver_t { int (*cleanup)(void); /* - * spix_port_t *open(int port, uint32_t clkw, uint32_t clkr) + * spix_port_t *open(int port, const spix_args_t *args) * * Open 'port' (number 0...N) with specified write clock 'clkw' and read * clock 'clkr' (both in Hz). The driver will check the values. * Return value is a reference to the port to use or NULL on failure. */ - const spix_port_t *(*open)(int port, uint32_t clkw, uint32_t clkr); + const spix_port_t *(*open)(int port, const spix_args_t *args); /* * close() @@ -170,5 +179,10 @@ __attribute__((always_inline)) static inline uint32_t spix_cmd_write(uint32_t ad return (addr << 16) | SPIX_HM2_CMD_WRITE | (aib ? SPIX_HM2_CMD_ADDRINC : 0) | ((msglen & 0x7f) << 4); } +__attribute__((always_inline)) static inline uint32_t spix_min(uint32_t a, uint32_t b) +{ + return a <= b ? a : b; +} + #endif // vim: ts=4 diff --git a/src/hal/drivers/mesa-hostmot2/spix_rpi3.c b/src/hal/drivers/mesa-hostmot2/spix_rpi3.c index e50ba3c834c..7fa678be8e1 100644 --- a/src/hal/drivers/mesa-hostmot2/spix_rpi3.c +++ b/src/hal/drivers/mesa-hostmot2/spix_rpi3.c @@ -38,8 +38,7 @@ //#define RPSPI_DEBUG_PIN 23 // Define for pin-debugging -// The min/max allowed frequencies of the SPI clock -#define SCLK_FREQ_MIN 30000 +// The absolute max allowed frequency of the SPI clock #define SCLK_FREQ_MAX 50000000 #define SPI_MAX_SPI 2 // SPI0 and SPI1 @@ -73,13 +72,15 @@ typedef struct __spi_port_t { uint32_t clkdivw; // Write clock divider setting uint32_t clkdivr; // Read clock divider setting uint32_t cemask; // Read clock divider setting + uint32_t freqmin; // Calculated minimal frequency for port + uint32_t freqmax; // Calculated maximum frequency for port } rpi3_port_t; /* Forward decls */ static int rpi3_detect(const char *dtcs[]); static int rpi3_setup(int probemask); static int rpi3_cleanup(void); -static const spix_port_t *rpi3_open(int port, uint32_t clkw, uint32_t clkr); +static const spix_port_t *rpi3_open(int port, const spix_args_t *args); static int rpi3_close(const spix_port_t *sp); static int spi0_transfer(const spix_port_t *sp, uint32_t *wptr, size_t txlen, int rw); static int spi1_transfer(const spix_port_t *sp, uint32_t *wptr, size_t txlen, int rw); @@ -108,7 +109,7 @@ static rpi3_port_t spi_ports[PORT_MAX] = { /* * The driver interface structure */ -spix_driver_t spix_rpi3 = { +spix_driver_t spix_driver_rpi3 = { .name = HM2_LLIO_NAME, .num_ports = PORT_MAX, @@ -688,9 +689,9 @@ static int rpi3_detect(const char *dtcs[]) return -ENODEV; // We are not the device the driver supports // Set the matched dtc and model informational strings - strncpy(spix_rpi3.dtc, dtcs[i], sizeof(spix_rpi3.dtc)-1); - if(spix_read_file("/proc/device-tree/model", spix_rpi3.model, sizeof(spix_rpi3.model)) < 0) - strncpy(spix_rpi3.model, "??? Unknown board ???", sizeof(spix_rpi3.model)-1); + strncpy(spix_driver_rpi3.dtc, dtcs[i], sizeof(spix_driver_rpi3.dtc)-1); + if(spix_read_file("/proc/device-tree/model", spix_driver_rpi3.model, sizeof(spix_driver_rpi3.model)) < 0) + strncpy(spix_driver_rpi3.model, "??? Unknown board ???", sizeof(spix_driver_rpi3.model)-1); return 0; } @@ -706,6 +707,7 @@ static int rpi3_setup(int probemask) int err; uintptr_t membase; size_t memsize; + int i; if(driver_enabled) { LL_ERR("Driver is already setup.\n"); @@ -723,6 +725,18 @@ static int rpi3_setup(int probemask) } spiclk_base = read_spiclkbase(); + + // calculate the actual min/max frequencies + for(i = 0; i < PORT_MAX; i++) { + if(!spi_ports[i].spiport) { + spi_ports[i].freqmin = spiclk_base / 65536; + spi_ports[i].freqmax = spix_min((spiclk_base / 2), SCLK_FREQ_MAX); + } else { + spi_ports[i].freqmin = spiclk_base / (2 * (4095+1)); + spi_ports[i].freqmax = spix_min((spiclk_base / 2), SCLK_FREQ_MAX); + } + } + if((err = read_membase(&membase, &memsize)) < 0) return err; @@ -757,7 +771,7 @@ static int rpi3_cleanup(void) // Close any ports not closed already for(i = 0; i < PORT_MAX; i++) { if(spi_ports[i].isopen) - spix_rpi3.close(&spi_ports[i].spix); + spix_driver_rpi3.close(&spi_ports[i].spix); } if(peripheralmem != MAP_FAILED) { @@ -777,7 +791,7 @@ static int rpi3_cleanup(void) * Open a SPI port at index 'port' with 'clkw' write clock and 'clkr' read * clock frequencies. */ -static const spix_port_t *rpi3_open(int port, uint32_t clkw, uint32_t clkr) +static const spix_port_t *rpi3_open(int port, const spix_args_t *args) { rpi3_port_t *rpp; uint32_t ccw, ccr; @@ -804,19 +818,19 @@ static const spix_port_t *rpi3_open(int port, uint32_t clkw, uint32_t clkr) return NULL; } - if(clkw < SCLK_FREQ_MIN || clkw > SCLK_FREQ_MAX) { - LL_ERR("%s: SPI write clock frequency outside acceptable range (%d..%d kHz).\n", rpp->spix.name, SCLK_FREQ_MIN, SCLK_FREQ_MAX); + if(args->clkw < rpp->freqmin || args->clkw > rpp->freqmax) { + LL_ERR("%s: SPI write clock frequency outside acceptable range (%d..%d kHz).\n", rpp->spix.name, rpp->freqmin / 1000, rpp->freqmax / 1000); return NULL; } if(!rpp->spiport) { - rpp->clkdivw = spi0_clkdiv_calc(spiclk_base, clkw); - rpp->clkdivr = spi0_clkdiv_calc(spiclk_base, clkr); + rpp->clkdivw = spi0_clkdiv_calc(spiclk_base, args->clkw); + rpp->clkdivr = spi0_clkdiv_calc(spiclk_base, args->clkr); ccw = spiclk_base / rpp->clkdivw; ccr = spiclk_base / rpp->clkdivr; } else { - rpp->clkdivw = spi1_clkdiv_calc(spiclk_base, clkw); - rpp->clkdivr = spi1_clkdiv_calc(spiclk_base, clkr); + rpp->clkdivw = spi1_clkdiv_calc(spiclk_base, args->clkw); + rpp->clkdivr = spi1_clkdiv_calc(spiclk_base, args->clkr); ccw = spiclk_base / (2 * (rpp->clkdivw + 1)); ccr = spiclk_base / (2 * (rpp->clkdivr + 1)); } diff --git a/src/hal/drivers/mesa-hostmot2/spix_rpi5.c b/src/hal/drivers/mesa-hostmot2/spix_rpi5.c index 050f1dfdaba..f90b1c68a94 100644 --- a/src/hal/drivers/mesa-hostmot2/spix_rpi5.c +++ b/src/hal/drivers/mesa-hostmot2/spix_rpi5.c @@ -75,7 +75,7 @@ typedef struct __spi_port_t { static int rpi5_detect(const char *dtcs[]); static int rpi5_setup(int probemask); static int rpi5_cleanup(void); -static const spix_port_t *rpi5_open(int port, uint32_t clkw, uint32_t clkr); +static const spix_port_t *rpi5_open(int port, const spix_args_t *args); static int rpi5_close(const spix_port_t *sp); static int spi_transfer(const spix_port_t *sp, uint32_t *wptr, size_t txlen, int rw); @@ -93,7 +93,7 @@ static rpi5_port_t spi_ports[PORT_MAX] = { /* * The driver interface structure */ -spix_driver_t spix_rpi5 = { +spix_driver_t spix_driver_rpi5 = { .name = HM2_LLIO_NAME, .num_ports = PORT_MAX, @@ -517,9 +517,9 @@ static int rpi5_detect(const char *dtcs[]) return -ENODEV; // We are not the device the driver supports // Set the matched dtc and model informational strings - strncpy(spix_rpi5.dtc, dtcs[i], sizeof(spix_rpi5.dtc)-1); - if(spix_read_file("/proc/device-tree/model", spix_rpi5.model, sizeof(spix_rpi5.model)) < 0) - strncpy(spix_rpi5.model, "??? Unknown board ???", sizeof(spix_rpi5.model)-1); + strncpy(spix_driver_rpi5.dtc, dtcs[i], sizeof(spix_driver_rpi5.dtc)-1); + if(spix_read_file("/proc/device-tree/model", spix_driver_rpi5.model, sizeof(spix_driver_rpi5.model)) < 0) + strncpy(spix_driver_rpi5.model, "??? Unknown board ???", sizeof(spix_driver_rpi5.model)-1); return 0; } @@ -582,7 +582,7 @@ static int rpi5_cleanup(void) // Close any ports not closed already for(i = 0; i < PORT_MAX; i++) { if(spi_ports[i].isopen) - spix_rpi5.close(&spi_ports[i].spix); + spix_driver_rpi5.close(&spi_ports[i].spix); } if(peripheralmem != MAP_FAILED) { @@ -602,7 +602,7 @@ static int rpi5_cleanup(void) * Open a SPI port at index 'port' with 'clkw' write clock and 'clkr' read * clock frequencies. */ -static const spix_port_t *rpi5_open(int port, uint32_t clkw, uint32_t clkr) +static const spix_port_t *rpi5_open(int port, const spix_args_t *args) { rpi5_port_t *rpp; @@ -628,14 +628,14 @@ static const spix_port_t *rpi5_open(int port, uint32_t clkw, uint32_t clkr) return NULL; } - if(clkw < SCLK_FREQ_MIN || clkw > SCLK_FREQ_MAX) { + if(args->clkw < SCLK_FREQ_MIN || args->clkw > SCLK_FREQ_MAX) { LL_ERR("%s: SPI write clock frequency outside acceptable range (%d..%d kHz).\n", rpp->spix.name, SCLK_FREQ_MIN, SCLK_FREQ_MAX); return NULL; } rpp->isopen = 1; - rpp->clkdivw = clkdiv_calc(clkw); - rpp->clkdivr = clkdiv_calc(clkr); + rpp->clkdivw = clkdiv_calc(args->clkw); + rpp->clkdivr = clkdiv_calc(args->clkr); LL_INFO("%s: write clock rate calculated: %u Hz (clkdiv: %u)\n", rpp->spix.name, RP1_SPI_CLK / rpp->clkdivw, rpp->clkdivw); LL_INFO("%s: read clock rate calculated: %u Hz (clkdiv: %u)\n", rpp->spix.name, RP1_SPI_CLK / rpp->clkdivr, rpp->clkdivr); diff --git a/src/hal/drivers/mesa-hostmot2/spix_spidev.c b/src/hal/drivers/mesa-hostmot2/spix_spidev.c index dd97d655290..2b2c5153f5c 100644 --- a/src/hal/drivers/mesa-hostmot2/spix_spidev.c +++ b/src/hal/drivers/mesa-hostmot2/spix_spidev.c @@ -45,7 +45,7 @@ typedef struct __spi_port_t { static int spidev_detect(const char *dtcs[]); static int spidev_setup(int probemask); static int spidev_cleanup(void); -static const spix_port_t *spidev_open(int port, uint32_t clkw, uint32_t clkr); +static const spix_port_t *spidev_open(int port, const spix_args_t *args); static int spidev_close(const spix_port_t *sp); static int spi_transfer(const spix_port_t *sp, uint32_t *wptr, size_t txlen, int rw); @@ -61,7 +61,7 @@ static spidev_port_t spi_ports[PORT_MAX] = { /* * The driver interface structure */ -spix_driver_t spix_spidev = { +spix_driver_t spix_driver_spidev = { .name = HM2_LLIO_NAME, .num_ports = PORT_MAX, @@ -120,14 +120,17 @@ static int spi_transfer(const spix_port_t *sp, uint32_t *wptr, size_t txlen, int /* * Open and setup a /dev/spidevX.Y port */ -static int port_configure(spidev_port_t *sdp, uint32_t clkw, uint32_t clkr) +static int port_configure(spidev_port_t *sdp, const spix_args_t *args) { int fd; uint8_t b; uint32_t w; int e; + uint32_t clkw = args->clkw; + uint32_t clkr = args->clkr; + const char *devname = args->spidev ? args->spidev : sdp->spix.name; - if((fd = open(sdp->spix.name, O_RDWR)) < 0) { + if((fd = open(devname, O_RDWR)) < 0) { LL_ERR("%s: Cannot open port: %s\n", sdp->spix.name, strerror(e = errno)); return -e; } @@ -202,11 +205,11 @@ static int spidev_detect(const char *dtcs[]) { // Set the matched dtc and model informational strings if(dtcs[0]) - strncpy(spix_spidev.dtc, dtcs[0], sizeof(spix_spidev.dtc)-1); + strncpy(spix_driver_spidev.dtc, dtcs[0], sizeof(spix_driver_spidev.dtc)-1); else - strncpy(spix_spidev.dtc, "spix_spidev-unknown-dtc", sizeof(spix_spidev.dtc)-1); - if(spix_read_file("/proc/device-tree/model", spix_spidev.model, sizeof(spix_spidev.model)) < 0) - strncpy(spix_spidev.model, "??? Unknown board ???", sizeof(spix_spidev.model)-1); + strncpy(spix_driver_spidev.dtc, "spix_spidev-unknown-dtc", sizeof(spix_driver_spidev.dtc)-1); + if(spix_read_file("/proc/device-tree/model", spix_driver_spidev.model, sizeof(spix_driver_spidev.model)) < 0) + strncpy(spix_driver_spidev.model, "??? Unknown board ???", sizeof(spix_driver_spidev.model)-1); return 0; } @@ -245,7 +248,7 @@ static int spidev_cleanup(void) // Close any ports not closed already for(i = 0; i < PORT_MAX; i++) { if(spi_ports[i].fd >= 0) - spix_spidev.close(&spi_ports[i].spix); + spix_driver_spidev.close(&spi_ports[i].spix); } driver_enabled = 0; @@ -256,7 +259,7 @@ static int spidev_cleanup(void) * Open a SPI port at index 'port' with 'clkw' write clock and 'clkr' read * clock frequencies. */ -static const spix_port_t *spidev_open(int port, uint32_t clkw, uint32_t clkr) +static const spix_port_t *spidev_open(int port, const spix_args_t *args) { spidev_port_t *sdp; @@ -282,7 +285,7 @@ static const spix_port_t *spidev_open(int port, uint32_t clkw, uint32_t clkr) return NULL; } - if(port_configure(sdp, clkw, clkr) < 0) + if(port_configure(sdp, args) < 0) return NULL; LL_INFO("%s: write clock rate calculated: %u Hz\n", sdp->spix.name, sdp->clkw); From 1b8ecb15e89810bff2a7a2e7b80d19f8400d4752 Mon Sep 17 00:00:00 2001 From: Bertho Stultiens Date: Thu, 5 Dec 2024 14:15:24 +0100 Subject: [PATCH 10/18] Need rtapi_app.h include. --- src/hal/drivers/mesa-hostmot2/hm2_spix.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/hal/drivers/mesa-hostmot2/hm2_spix.c b/src/hal/drivers/mesa-hostmot2/hm2_spix.c index e71a6deb0c4..bc9976bafd7 100644 --- a/src/hal/drivers/mesa-hostmot2/hm2_spix.c +++ b/src/hal/drivers/mesa-hostmot2/hm2_spix.c @@ -22,6 +22,7 @@ #include #include +#include #include #define HM2_LLIO_NAME "hm2_spix" From 889b264d4563dd6f7038b7ea2f3b3c470cab23db Mon Sep 17 00:00:00 2001 From: Bertho Stultiens Date: Thu, 5 Dec 2024 14:15:48 +0100 Subject: [PATCH 11/18] Use consistent double inclusion defines. --- src/hal/drivers/mesa-hostmot2/dtcboards.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/hal/drivers/mesa-hostmot2/dtcboards.h b/src/hal/drivers/mesa-hostmot2/dtcboards.h index 3b16e54c75b..84315042435 100644 --- a/src/hal/drivers/mesa-hostmot2/dtcboards.h +++ b/src/hal/drivers/mesa-hostmot2/dtcboards.h @@ -15,8 +15,8 @@ * You should have received a copy of the GNU General Public License along with * this program; if not, see . */ -#ifndef HAL_DTCBOARDS_H -#define HAL_DTCBOARDS_H +#ifndef HAL_HM2_DTCBOARDS_H +#define HAL_HM2_DTCBOARDS_H /* * Info about the hardware platform, see: From f775e3d198b33760a90f5c385d759c6001d094ed Mon Sep 17 00:00:00 2001 From: Bertho Stultiens Date: Thu, 5 Dec 2024 14:18:45 +0100 Subject: [PATCH 12/18] Make sure a device node path override is used in messages. Update comments. --- src/hal/drivers/mesa-hostmot2/spix_spidev.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/hal/drivers/mesa-hostmot2/spix_spidev.c b/src/hal/drivers/mesa-hostmot2/spix_spidev.c index 2b2c5153f5c..485d0d59a29 100644 --- a/src/hal/drivers/mesa-hostmot2/spix_spidev.c +++ b/src/hal/drivers/mesa-hostmot2/spix_spidev.c @@ -126,11 +126,14 @@ static int port_configure(spidev_port_t *sdp, const spix_args_t *args) uint8_t b; uint32_t w; int e; - uint32_t clkw = args->clkw; + uint32_t clkw = args->clkw; // Requested write and read clocks uint32_t clkr = args->clkr; - const char *devname = args->spidev ? args->spidev : sdp->spix.name; - if((fd = open(devname, O_RDWR)) < 0) { + // Module argument override of device node path + if(args->spidev) + sdp->spix.name = args->spidev; + + if((fd = open(sdp->spix.name, O_RDWR)) < 0) { LL_ERR("%s: Cannot open port: %s\n", sdp->spix.name, strerror(e = errno)); return -e; } @@ -256,8 +259,7 @@ static int spidev_cleanup(void) } /* - * Open a SPI port at index 'port' with 'clkw' write clock and 'clkr' read - * clock frequencies. + * Open a SPI port at index 'port' using 'args' to configure. */ static const spix_port_t *spidev_open(int port, const spix_args_t *args) { From d70cca7a2859ae5ccf6423c9bb0e6392f6d64ffd Mon Sep 17 00:00:00 2001 From: Bertho Stultiens Date: Thu, 5 Dec 2024 17:14:38 +0100 Subject: [PATCH 13/18] Add a man-page to the hm2_spix driver. --- docs/src/man/man9/hm2_spix.9.adoc | 240 ++++++++++++++++++++++++++++++ 1 file changed, 240 insertions(+) create mode 100644 docs/src/man/man9/hm2_spix.9.adoc diff --git a/docs/src/man/man9/hm2_spix.9.adoc b/docs/src/man/man9/hm2_spix.9.adoc new file mode 100644 index 00000000000..5c22e8dacd4 --- /dev/null +++ b/docs/src/man/man9/hm2_spix.9.adoc @@ -0,0 +1,240 @@ += hm2_spix(9) + +== NAME + +hm2_spix - LinuxCNC HAL driver for the Mesa Electronics Anything IO +boards with SPI enabled HostMot2 firmware. + +== SYNOPSIS + +*loadrt hm2_spix* + +____ +*config* [default: ""]:: + HostMot2 config strings, described in the *hostmot2*(9) manpage. +*spiclk_rate* [default: 25000]:: + Specify the SPI clock rate in kHz for each probed board. See *SPI + CLOCK RATES* below. Each entry follows the *spi_probe* setting, where + each probe takes the next value of the *spi_rate* list. A *spi_rate* + of 0 (zero) or less automatically selects the first rate in the list. + You may truncate the list to the number of boards you use. +*spiclk_rate_rd* [default: same as *spiclk_rate*]:: + Specify the SPI read clock rate in kHz. Usually you read and write at + the same speed. However, you may want to reduce the reading speed if + the round-trip is too long (see *SPI CLOCK RATES* below). You may + truncate the list to the number of boards you use. +*spi_probe* [default: 1]:: + Probe SPI port and CE lines for a card. This is a bit-field indicating + which combinations of SPI and CE should be probed: - 1 = SPI0/CE0, - 2 + = SPI0/CE1, - 4 = SPI1/CE0, - 8 = SPI1/CE1, - 16 = SPI1/CE2. + + The probe is performed exactly in above order. Any boards found will + be numbered 0...4 in the order found. It is an error if a probe fails + and the driver will abort. See also *INTERFACE CONFIGURATION* below. +*force_driver* [default: ]:: + Force a specific hardware driver to be selected. This is usually not + necessary and the hm2_spix driver will normally select the appropriate + hardware driver automatically. See also *HARDWARE DRIVERS* below. +*spidev_path* [default: ]:: + Override the device node path to the spidev device. Default is + /dev/spidevX.Y, where X.Y is {0.0, 0.1, 1.0, 1.1, 1.2} in that order. + This option has only effect if the spix_spidev hardware driver is + selected or forced to be used. +*spi_noqueue* [default: 0 (off)]:: + Force disable queued command processing. Normally, all requests are + queued if requested by upstream and sent in one bulk transfer. This + reduces overhead significantly by up to 35%. Disabling the queue makes + each transfer visible and more easily debug-able. Set to any non-zero + value to disable the queue. +*spi_debug* [default: -1]:: + Set the message level of the running process. The message level is set + if *spi_debug* is set to a positive value between 0 and 5, where 0 means + no messages at all and 5 means everything. A value of -1 does not touch + the current message level. + + Caveat Emptor: changing the message level is process-wide and all + modules within the process will spit out messages at the requested + level. This may cause quite some clutter in your terminal. +____ + +== DESCRIPTION + +hm2_spix is a device driver for all computer boards with an available +SPI port, including Raspberry Pi 3, 4 and 5. The SPI port interfaces +with Mesa's SPI based Anything I/O boards with SPI enabled HostMot2 +firmware to the LinuxCNC HAL. + +This driver unifies all previous hostmot2 SPI hal drivers in one with +dedicated hardware drivers for Raspberry Pi models 3, 4 and 5 and has a +fall-back to spidev for unknown boards. Further hardware drivers may be +created and integrated when requested. + +The supported Mesa boards are: 7I90HD, 7I43, 7C80 and 7C81. + +The board must have a compatible firmware (like: 7i90_spi_*-bit, +7c80_*.bit and 7c81_*.bit) loaded on the board by the *mesaflash*(1) +program. + +hm2_spix is only available when LinuxCNC is configured with "uspace" +realtime. It works with Raspian and PREEMPT_RT kernel. + +See also *NOTES* below. + +== HARDWARE DRIVERS + +The following hardware drivers are implemented and probed in order: +|=== +| Driver | Board + +| spix_rpi3 +| RPi3B, RPi3A+, RPi3B+, RPi4B, RPi4CM + +| spix_rpi5 +| RPi5B, RPi5CM + +| spix_spidev +| Any board not recognised +|=== + +Probing the hardware is implemented by matching known computer boards +against the device-tree compatible string-list from +/proc/device-tree/compatible. Normally, the first hardware driver giving +a match will be selected. However, it is possible to force a specific +driver to be used using the *force_driver* option with the name of the +driver you want to use. + +== INTERFACE CONFIGURATION + +Up to five device boards are supported. Two on SPI0 and three on SPI1. +It is recommended that you, at most, use two devices and each device +connected to a separate SPI port. You can choose which CE lines you +prefer or fit your design and setup. Use the *spi_probe* parameter to +instruct the driver where to search for the board(s). + +For the Mesa 7C80 and 7C81 you'll always want to configure SPI0/CE0. +These boards have a matching 40-pin header for the computer board. + +The SPI ports are located on the 40-pin header for those computer boards +with a compatible header. The GPIO numbers are only guaranteed to be +valid for Raspberry Pi boards. + +Port SPI0: +[cols=",>,>,"] +|=== +| Pin | GPIO | 40-pin | Devname +| MOSI | 10 | 19 | +| MISO | 9 | 21 | +| SCLK | 11 | 23 | +| CE0 | 8 | 24 | /dev/spidev0.0 +| CE1 | 7 | 26 | /dev/spidev0.1 +|=== + +Port SPI1: +[cols=",>,>,"] +|=== +| Pin | GPIO | 40-pin | Devname +| MOSI | 20 | 38 | +| MISO | 19 | 35 | +| SCLK | 21 | 40 | +| CE0 | 18 | 12 | /dev/spidev1.0 +| CE1 | 17 | 11 | /dev/spidev1.1 +| CE2 | 16 | 36 | /dev/spidev1.2 +|=== + +== REALTIME PERFORMANCE OF THE HM2_SPIX DRIVER + +Using a RPi3 will work, but is not the best option. Currently, the RPi4 +is known to work adequately. The newer RPi5 is a lot faster and will +normally run a servo-thread at 1 kHz without problems. + +All other computer boards and LinuxCNC configurations need to be tested +thoroughly. + +All other parameters: TBD. + +== SPI CLOCK RATES + +The SPI driver can provide frequencies beyond what is acceptable for any +board. A safe value to start with would be 12.5 MHz (spiclk_rate=12500) +and then work your way up from there. + +The SPI driver generates (very) discrete clock frequencies, especially +in the high MHz range because of a simple clock divider structure. The +base frequency is different between boards and the divider for SPI0/SPI1 +scales using discrete factors with formula f=base/(2*divider). The +following list specifies the highest possible *spiclk_rate* and +*spiclk_rate_rd* frequencies (in kHz) for discrete divider settings: +|=== +| ^| RPi3 ^| RPi4 ^| RPi5 +| Base >| 400 MHz >| 500 MHz >| 200 MHz +| Fastest >| 50000 >| 50000 >| 50000 +| >| 40000 >| 41666 >| 33333 +| >| 33333 >| 35714 >| 25000 +| >| 28571 >| 31250 >| 20000 +| >| 25000 >| 27777 >| 16666 +| >| 22222 >| 25000 >| 14285 +| >| 20000 >| 22727 >| 12500 +| >| 18181 >| 20833 >| 11111 +| >| 16666 >| 19230 >| 10000 +| >| 15384 >| 17857 >| 9090 +| >| ... >| ... >| ... +| Slowest >| SPI0:4 >| SPI0:4 >| SPI0:4 +| Slowest >| SPI1:49 >| SPI1:62 >| SPI1:4 +|=== + +The slowest selectable SPI clock frequency for SPI0 and SPI1 are not for +production systems. They can be selected for testing purposes. You +should not expect any real-time performance with such slow setting. + +The highest theoretically possible SPI clock frequency is higher than +stated in the above table. However, you will not be able to build any +reliable hardware interface at that frequency. The driver limits the +clock to 50.0 MHz (cpiclk_rate=50000). The Mesa board interface supports +frequencies up to 50 MHz and that is with good cabling in write +direction only. + +Writing to the Mesa board may be done faster than reading. This is +especially important if you have "long" wires or any buffers on the +SPI-bus path. You can set the read clock frequency to a lower value +(using *spiclk_rate_rd*) to counter the effects of the SPI-bus +round-trip needed for read actions. For example, you can write at 33.33 +MHz and read at 25.00 MHz. + +The maximum SPI clock of the spix_rpi5 driver has been tested up to +50 MHz write speed and 33 MHz read speed on the 7C80 and 7C81. However, +it is not recommended to run at the limit on production systems. A safe +setting would be to set one step below the maximum speeds. + +== NOTES + +If you know your setup and do not require the spix_spidev driver, then +it is *strongly* recommended that you unload/disable the kernel's SPI +drivers *dw_spi* and *dw_spi_mmio* for the RPi5 or *spi_bmc2835* for the +RPi3 and RPi4. The hm2_spix hardware drivers attempt to unload the +kernel driver at startup if detected and restore it at exit if initially +loaded. However, there are no guarantees about the effectiveness of the +module unload/load actions. + +*Warning*: having both kernel and user-space SPI drivers installed can +result in unexpected interactions and system instabilities. + +The Raspberry Pi *must* have an adequate power supply. At high speeds +and noise on the supply, there is the possibility of strange behaviour +if the noise gets out of hand. + +The Mesa 7C80 provides enough local power to the host via the 40-pin +interface header if your external power supply is adequate (on connector +TB6). The Mesa 7C81 needs an adequate external 5V power supply (on +connector TB1) and feeds it directly to the host interface header. + +For the Raspberry Pi 4: Be sure to have a proper heat-sink mounted on +the SoC or it will get too warm and may crash. + +For the Raspberry Pi 5: Be sure to have a proper *active* heat-sink +mounted on the SoC or it will get too warm and may crash. + +== SEE ALSO + +hostmot2(9) + +== LICENSE + +GPL From c7bbc13d6af2f534084d48512da481ab8efe4bc3 Mon Sep 17 00:00:00 2001 From: Bertho Stultiens Date: Thu, 5 Dec 2024 18:17:39 +0100 Subject: [PATCH 14/18] Fix idrom read comment. Copy errno because following code can alter it. Reading a 0-byte length file is not a real error. It will lead to a fail later. --- src/hal/drivers/mesa-hostmot2/hm2_spix.c | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/hal/drivers/mesa-hostmot2/hm2_spix.c b/src/hal/drivers/mesa-hostmot2/hm2_spix.c index bc9976bafd7..67d522690f1 100644 --- a/src/hal/drivers/mesa-hostmot2/hm2_spix.c +++ b/src/hal/drivers/mesa-hostmot2/hm2_spix.c @@ -470,9 +470,8 @@ static int probe_board(spix_board_t *board) LL_INFO("%s: Valid cookie matched, idrom@%04x\n", port->name, ret); - // Read the board identification. - // The IDROM address offset is returned in the cookie check and the - // board_name offset is added (see hm2_idrom_t in hostmot2.h) + // Read the IDROM from the board. The IDROM address offset was returned in + // the cookie check. if(!board->llio.read(&board->llio, (uint32_t)ret, &idrom, sizeof(hm2_idrom_t))) { LL_ERR("%s: Board idrom read failed\n", port->name); return -EIO; // Cookie could be read, so this is a comms error @@ -502,8 +501,9 @@ ssize_t spix_read_file(const char *fname, void *buffer, size_t bufsize) memset(buffer, 0, bufsize); if((fd = rtapi_open_as_root(fname, O_RDONLY)) < 0) { - LL_ERR("Cannot open '%s' for read (errno=%d: %s)\n", fname, errno, strerror(errno)); - return -errno; + int e = errno; + LL_ERR("Cannot open '%s' for read (errno=%d: %s)\n", fname, e, strerror(e)); + return -e; } while(1) { @@ -511,10 +511,11 @@ ssize_t spix_read_file(const char *fname, void *buffer, size_t bufsize) if(len == 0) { LL_ERR("Nothing read from '%s', file contains no data\n", fname); } else if(len < 0) { - if(errno == EINTR) + int e = errno; + if(e == EINTR) continue; // Interrupted syscall, retry read - LL_ERR("Error reading from '%s' (errno=%d: %s)\n", fname, errno, strerror(errno)); - return -errno; + LL_ERR("Error reading from '%s' (errno=%d: %s)\n", fname, e, strerror(e)); + return -e; } break; } @@ -565,7 +566,7 @@ static int spix_setup(void) // Read the 'compatible' string-list from the device-tree buflen = spix_read_file("/proc/device-tree/compatible", buf, sizeof(buf)); - if(buflen <= 0) { + if(buflen < 0) { LL_ERR("Failed to read platform identity.\n"); return buflen; // negative errno from read_file() } From ec67f0dd059fcbe9f3c5875cda5c019ce81c9509 Mon Sep 17 00:00:00 2001 From: Bertho Stultiens Date: Fri, 6 Dec 2024 14:46:14 +0100 Subject: [PATCH 15/18] Fix the stall in SPI1 on RPi3/4. Keep track of the fifo depth manually. Don't fuss, sizeof(uint32_t) = 2 * sizeof(uint16_t) and will never change. --- src/hal/drivers/mesa-hostmot2/spix_rpi3.c | 30 ++++++++++++++++++----- 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/src/hal/drivers/mesa-hostmot2/spix_rpi3.c b/src/hal/drivers/mesa-hostmot2/spix_rpi3.c index 7fa678be8e1..2a71b6f76f5 100644 --- a/src/hal/drivers/mesa-hostmot2/spix_rpi3.c +++ b/src/hal/drivers/mesa-hostmot2/spix_rpi3.c @@ -287,14 +287,29 @@ static inline void spi1_reset(void) reg_wr(&aux->spi0_cntl1, AUX_SPI_CNTL0_CLEARFIFO); } +/* + * This is needed because the SPI ports on AUX suck. It seems that the BCM2835 + * hardware cannot keep track of its own fifo depth and we need to count for + * it. Using the fifo level status bits from the status register (spi0_stat) + * seems to work at first, but will fail very soon after. This may relate to + * the AUX_SPI_STAT_TX_FULL not being updated with correct timing. We are + * apparently losing data in the transfer, which causes LinuxCNC to stall in + * the real-time thread, which is a Bad Thing(TM). + * The Linux kernel driver seems to have the number 12, but I cannot get it to + * work with that number. The depth of four seems the highest working value. + * Either way, we already busy-loop here anyway, so it shouldn't matter. + */ +#define SPI1_FIFO_MAXDEPTH 4 + static int spi1_transfer(const spix_port_t *sp, uint32_t *wptr, size_t txlen, int rw) { rpi3_port_t *rp = (rpi3_port_t *)sp; uint16_t *w16ptr = (uint16_t *)wptr; uint16_t *r16ptr = (uint16_t *)wptr; - size_t tx16len = txlen * sizeof(uint16_t); - size_t rx16len = txlen * sizeof(uint16_t); + size_t tx16len = txlen * 2; // There are twice as many 16-bit words as there are 32-bit words + size_t rx16len = txlen * 2; size_t u; + unsigned pending = 0; if(!txlen) return 1; // Nothing to do, return success @@ -302,7 +317,7 @@ static int spi1_transfer(const spix_port_t *sp, uint32_t *wptr, size_t txlen, in gpio_debug_pin(false); // Word-swap to assure most significant bit to be sent first in 16-bit transfers. - for(u = 0; u < txlen * sizeof(*w16ptr); u += 2) { + for(u = 0; u < txlen * 2; u += 2) { uint16_t tt = w16ptr[u+0]; w16ptr[u+0] = w16ptr[u+1]; w16ptr[u+1] = tt; @@ -326,13 +341,14 @@ static int spi1_transfer(const spix_port_t *sp, uint32_t *wptr, size_t txlen, in reg_wr(&aux->spi0_cntl1, AUX_SPI_CNTL1_MSB_IN); // Send data to the fifo - while(tx16len > 0 && !(reg_rd(&aux->spi0_stat) & AUX_SPI_STAT_TX_FULL)) { + while(tx16len > 0 && pending < SPI1_FIFO_MAXDEPTH && !(reg_rd(&aux->spi0_stat) & AUX_SPI_STAT_TX_FULL)) { if(tx16len > 1) reg_wr(&aux->spi0_hold, ((uint32_t)*w16ptr << 8) | (16 << 24)); // More data to follow else reg_wr(&aux->spi0_io, ((uint32_t)*w16ptr << 8) | (16 << 24)); // Final write ++w16ptr; --tx16len; + ++pending; } // Read and write until all done @@ -342,14 +358,16 @@ static int spi1_transfer(const spix_port_t *sp, uint32_t *wptr, size_t txlen, in *r16ptr = (uint16_t)reg_rd(&aux->spi0_io); // Read available word ++r16ptr; --rx16len; + --pending; } - if(tx16len > 0 && !(stat & AUX_SPI_STAT_TX_FULL)) { + if(tx16len > 0 && pending < SPI1_FIFO_MAXDEPTH && !(stat & AUX_SPI_STAT_TX_FULL)) { if(tx16len > 1) reg_wr(&aux->spi0_hold, ((uint32_t)*w16ptr << 8) | (16 << 24)); // More data to follow else reg_wr(&aux->spi0_io, ((uint32_t)*w16ptr << 8) | (16 << 24)); // Final write ++w16ptr; --tx16len; + ++pending; } } @@ -358,7 +376,7 @@ static int spi1_transfer(const spix_port_t *sp, uint32_t *wptr, size_t txlen, in // Word-swap to fix the word order back to host-order. w16ptr = (uint16_t *)wptr; - for(u = 0; u < txlen * sizeof(*w16ptr); u += 2) { + for(u = 0; u < txlen * 2; u += 2) { uint16_t tt = w16ptr[u+0]; w16ptr[u+0] = w16ptr[u+1]; w16ptr[u+1] = tt; From e42aed58b5a740059ad172dd4145bc6fa6ada73a Mon Sep 17 00:00:00 2001 From: Bertho Stultiens Date: Fri, 6 Dec 2024 15:42:24 +0100 Subject: [PATCH 16/18] Rename board defines. These are specific to Raspberry Pi. Other manufacturers will have other names, but may share the last portion. --- src/hal/drivers/mesa-hostmot2/dtcboards.h | 54 +++++++++++------------ 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/src/hal/drivers/mesa-hostmot2/dtcboards.h b/src/hal/drivers/mesa-hostmot2/dtcboards.h index 84315042435..44fffaeec2d 100644 --- a/src/hal/drivers/mesa-hostmot2/dtcboards.h +++ b/src/hal/drivers/mesa-hostmot2/dtcboards.h @@ -33,24 +33,24 @@ */ #define DTC_BOARD_MAKE_RPI "raspberrypi" -#define DTC_BOARD_MODEL_5CM "5-compute-module" -#define DTC_BOARD_MODEL_5B "5-model-b" -#define DTC_BOARD_MODEL_4CM "4-compute-module" -#define DTC_BOARD_MODEL_4B "4-model-b" -#define DTC_BOARD_MODEL_3CM "3-compute-module" -#define DTC_BOARD_MODEL_3BP "3-model-b-plus" -#define DTC_BOARD_MODEL_3AP "3-model-a-plus" -#define DTC_BOARD_MODEL_3B "3-model-b" -#define DTC_BOARD_MODEL_2B "2-model-b" -#define DTC_BOARD_MODEL_CM "compute-module" -#define DTC_BOARD_MODEL_BP "model-b-plus" -#define DTC_BOARD_MODEL_AP "model-a-plus" -#define DTC_BOARD_MODEL_BR2 "model-b-rev2" -#define DTC_BOARD_MODEL_B "model-b" -#define DTC_BOARD_MODEL_A "model-a" -#define DTC_BOARD_MODEL_ZERO_2W "model-zero-2-w" -#define DTC_BOARD_MODEL_ZERO_W "model-zero-w" -#define DTC_BOARD_MODEL_ZERO "model-zero" +#define DTC_BOARD_RPI_5CM "5-compute-module" +#define DTC_BOARD_RPI_5B "5-model-b" +#define DTC_BOARD_RPI_4CM "4-compute-module" +#define DTC_BOARD_RPI_4B "4-model-b" +#define DTC_BOARD_RPI_3CM "3-compute-module" +#define DTC_BOARD_RPI_3BP "3-model-b-plus" +#define DTC_BOARD_RPI_3AP "3-model-a-plus" +#define DTC_BOARD_RPI_3B "3-model-b" +#define DTC_BOARD_RPI_2B "2-model-b" +#define DTC_BOARD_RPI_CM "compute-module" +#define DTC_BOARD_RPI_BP "model-b-plus" +#define DTC_BOARD_RPI_AP "model-a-plus" +#define DTC_BOARD_RPI_BR2 "model-b-rev2" +#define DTC_BOARD_RPI_B "model-b" +#define DTC_BOARD_RPI_A "model-a" +#define DTC_BOARD_RPI_ZERO_2W "model-zero-2-w" +#define DTC_BOARD_RPI_ZERO_W "model-zero-w" +#define DTC_BOARD_RPI_ZERO "model-zero" #define DTC_SOC_MAKE_BRCM "brcm" @@ -62,19 +62,19 @@ /* The device-tree compatible strings for the boards */ #define DTC_RPI_SOC_BCM2712 DTC_SOC_MAKE_RPI "," DTC_SOC_MODEL_BCM2712 -#define DTC_RPI_MODEL_5CM DTC_BOARD_MAKE_RPI "," DTC_BOARD_MODEL_5CM -#define DTC_RPI_MODEL_5B DTC_BOARD_MAKE_RPI "," DTC_BOARD_MODEL_5B +#define DTC_RPI_MODEL_5CM DTC_BOARD_MAKE_RPI "," DTC_BOARD_RPI_5CM +#define DTC_RPI_MODEL_5B DTC_BOARD_MAKE_RPI "," DTC_BOARD_RPI_5B #define DTC_RPI_SOC_BCM2711 DTC_SOC_MAKE_RPI "," DTC_SOC_MODEL_BCM2711 -#define DTC_RPI_MODEL_4CM DTC_BOARD_MAKE_RPI "," DTC_BOARD_MODEL_4CM -#define DTC_RPI_MODEL_4B DTC_BOARD_MAKE_RPI "," DTC_BOARD_MODEL_4B +#define DTC_RPI_MODEL_4CM DTC_BOARD_MAKE_RPI "," DTC_BOARD_RPI_4CM +#define DTC_RPI_MODEL_4B DTC_BOARD_MAKE_RPI "," DTC_BOARD_RPI_4B #define DTC_RPI_SOC_BCM2837 DTC_SOC_MAKE_BRCM "," DTC_SOC_MODEL_BCM2837 -#define DTC_RPI_MODEL_3CM DTC_BOARD_MAKE_RPI "," DTC_BOARD_MODEL_3CM -#define DTC_RPI_MODEL_3BP DTC_BOARD_MAKE_RPI "," DTC_BOARD_MODEL_3BP -#define DTC_RPI_MODEL_3AP DTC_BOARD_MAKE_RPI "," DTC_BOARD_MODEL_3AP -#define DTC_RPI_MODEL_3B DTC_BOARD_MAKE_RPI "," DTC_BOARD_MODEL_3B -#define DTC_RPI_MODEL_ZERO_2W DTC_BOARD_MAKE_RPI "," DTC_BOARD_MODEL_ZERO_2W +#define DTC_RPI_MODEL_3CM DTC_BOARD_MAKE_RPI "," DTC_BOARD_RPI_3CM +#define DTC_RPI_MODEL_3BP DTC_BOARD_MAKE_RPI "," DTC_BOARD_RPI_3BP +#define DTC_RPI_MODEL_3AP DTC_BOARD_MAKE_RPI "," DTC_BOARD_RPI_3AP +#define DTC_RPI_MODEL_3B DTC_BOARD_MAKE_RPI "," DTC_BOARD_RPI_3B +#define DTC_RPI_MODEL_ZERO_2W DTC_BOARD_MAKE_RPI "," DTC_BOARD_RPI_ZERO_2W /* Older than a RPi3 (bcm2836 and bcm2835) is probably not a good idea to use. */ From feabf549568e7f3bdc8edc8e510f92ee803c25a8 Mon Sep 17 00:00:00 2001 From: Bertho Stultiens Date: Fri, 6 Dec 2024 15:43:35 +0100 Subject: [PATCH 17/18] Add a comment on the clock divider calculation. The user should check the result. --- docs/src/man/man9/hm2_spix.9.adoc | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/docs/src/man/man9/hm2_spix.9.adoc b/docs/src/man/man9/hm2_spix.9.adoc index 5c22e8dacd4..bddb236f37f 100644 --- a/docs/src/man/man9/hm2_spix.9.adoc +++ b/docs/src/man/man9/hm2_spix.9.adoc @@ -159,7 +159,7 @@ and then work your way up from there. The SPI driver generates (very) discrete clock frequencies, especially in the high MHz range because of a simple clock divider structure. The base frequency is different between boards and the divider for SPI0/SPI1 -scales using discrete factors with formula f=base/(2*divider). The +scales using discrete factors with formula f=trunc(base/(2*divider)). The following list specifies the highest possible *spiclk_rate* and *spiclk_rate_rd* frequencies (in kHz) for discrete divider settings: |=== @@ -180,6 +180,11 @@ following list specifies the highest possible *spiclk_rate* and | Slowest >| SPI1:49 >| SPI1:62 >| SPI1:4 |=== +Note that the clock rate setting is heavily influenced by rounding and may be +higher than expected if the divider rounds to the next lower value. You can +check the actual clock rate by enabling informational messages (set +*spi_debug*=3). + The slowest selectable SPI clock frequency for SPI0 and SPI1 are not for production systems. They can be selected for testing purposes. You should not expect any real-time performance with such slow setting. From a4f249df21fdfaaafa4209fd50fe93c4b3651af7 Mon Sep 17 00:00:00 2001 From: Bertho Stultiens Date: Sat, 7 Dec 2024 12:03:34 +0100 Subject: [PATCH 18/18] Use same clock divider calculation round-up as the kernel's spidev driver. Compensate truncated input values by adding one kHz to the rate settings. --- src/hal/drivers/mesa-hostmot2/hm2_spix.c | 13 +++++++++++-- src/hal/drivers/mesa-hostmot2/spix_rpi3.c | 4 ++-- src/hal/drivers/mesa-hostmot2/spix_rpi5.c | 2 +- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/hal/drivers/mesa-hostmot2/hm2_spix.c b/src/hal/drivers/mesa-hostmot2/hm2_spix.c index 67d522690f1..d5d2dd7c4ac 100644 --- a/src/hal/drivers/mesa-hostmot2/hm2_spix.c +++ b/src/hal/drivers/mesa-hostmot2/hm2_spix.c @@ -632,8 +632,17 @@ static int spix_setup(void) if(!(spi_probe & (1 << i))) // Only probe if enabled continue; - sa.clkw = spiclk_rate[j] * 1000; - sa.clkr = spiclk_rate_rd[j] * 1000; + // The clock is increased by 1 kHz to compensate for the truncation of + // the listed values. The clock divider calculations will always be + // rounded down, making it consistent between hardware drivers and + // kernel's spidev driver. + // For example: On the RPi5, a clock of 33333 kHz would become 25000 + // kHz without compensation because of recurring 3 behind the comma in + // 33333 kHz, which got truncated. With compensation we use 33334 kHz + // as the rate and that gets rounded down to 33333 kHz in the + // calculation. + sa.clkw = (spiclk_rate[j] + 1) * 1000; + sa.clkr = (spiclk_rate_rd[j] + 1) * 1000; sa.spidev = spidev_path[j]; if(NULL == (port = hwdriver->open(i, &sa))) { LL_INFO("Failed to open hardware port index %d\n", i); diff --git a/src/hal/drivers/mesa-hostmot2/spix_rpi3.c b/src/hal/drivers/mesa-hostmot2/spix_rpi3.c index 2a71b6f76f5..ccb3c8dc10f 100644 --- a/src/hal/drivers/mesa-hostmot2/spix_rpi3.c +++ b/src/hal/drivers/mesa-hostmot2/spix_rpi3.c @@ -176,7 +176,7 @@ HWREGACCESS_ALWAYS_INLINE static inline void gpio_debug_pin(bool set_reset) */ static int32_t spi0_clkdiv_calc(uint32_t base, uint32_t rate) { - uint32_t clkdiv = base / rate; + uint32_t clkdiv = (base + rate - 1) / rate; // Use only even divider values // This is what the documentation (probably) states if(clkdiv > 65534) @@ -274,7 +274,7 @@ static int32_t spi1_clkdiv_calc(uint32_t base, uint32_t rate) uint32_t clkdiv; if(rate >= base / 2) return 0; - clkdiv = base / (rate * 2) - 1; + clkdiv = (base + rate*2 - 1) / (rate * 2) - 1; if(clkdiv > 4095) clkdiv = 4095; // Slowest possible return clkdiv; diff --git a/src/hal/drivers/mesa-hostmot2/spix_rpi5.c b/src/hal/drivers/mesa-hostmot2/spix_rpi5.c index f90b1c68a94..cc905261236 100644 --- a/src/hal/drivers/mesa-hostmot2/spix_rpi5.c +++ b/src/hal/drivers/mesa-hostmot2/spix_rpi5.c @@ -171,7 +171,7 @@ HWREGACCESS_ALWAYS_INLINE static inline void gpio_debug_pin(bool set_reset) */ static inline int32_t clkdiv_calc(uint32_t rate) { - uint32_t clkdiv = RP1_SPI_CLK / rate; + uint32_t clkdiv = (RP1_SPI_CLK + rate - 1) / rate; // The documentation states: bit 0 is always zero, therefore, only even // divider values supported. Divider value 0 disables the SCLK output. if(clkdiv > 65534)