<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[VOXL2 SPI3: sharing bus 3 between the onboard ICM-42688 and a second SPI device — SPI_NO_CS rejected, requesting cs-gpios guidance]]></title><description><![CDATA[<h2>Setup</h2>
<ul>
<li>Board: VOXL2 (M0054)</li>
<li>System image: <code>1.7.8-M0054-14.1a-perf</code>, kernel <code>4.19.125</code></li>
<li><code>libqrb5165-io</code> 0.5.0</li>
<li>Bus 3 controller: <code>qcom,spi-geni</code>, bound to the <code>spi_geni</code> platform driver, exposing a single chip select (<code>spi3.0</code> → <code>/dev/spidev3.0</code>)</li>
</ul>
<p dir="auto">We are adding an ams TMF8829 time-of-flight ranging sensor to an existing VOXL2 design. For layout reasons it is wired to SPI3, the same bus as the onboard ICM-42688 IMU that <code>voxl-imu-server</code> services. The IMU runs at <code>SPI_MODE_3</code> on the controller's native chip select; the TMF8829 runs at <code>SPI_MODE_0</code> and is selected through a TLMM GPIO (117), with its enable line on GPIO 118.</p>
<h2>The problem</h2>
<p dir="auto">SPI3 exposes exactly one native chip select, and it is wired to the IMU. The GENI controller asserts that native CS on every transfer it performs, so any transfer we issue for the TMF8829 also selects the IMU. Since the TMF8829 wire protocol begins each transaction with a write opcode (<code>0x02</code>), the IMU interprets TMF traffic as register writes addressed to itself. That is not merely a corrupted read — it can reconfigure the IMU mid-flight and take down a flight-critical data stream.</p>
<p dir="auto">The standard Linux remedy is the <code>SPI_NO_CS</code> mode flag, suppressing the controller's chip select so our GPIO alone selects the target. The kernel rejects it on this platform. Issuing the ioctl directly on the descriptor returned by <code>voxl_spi_get_fd(3)</code>:</p>
<pre><code>spi_bus3: SPI_IOC_WR_MODE32 0x40 failed: Invalid argument
</code></pre>
<p dir="auto"><code>0x40</code> is <code>SPI_MODE_0 | SPI_NO_CS</code>. Both the legacy 8-bit <code>SPI_IOC_WR_MODE</code> and the 32-bit <code>SPI_IOC_WR_MODE32</code> return <code>EINVAL</code>, which points to <code>SPI_NO_CS</code> not being advertised in the controller driver's <code>mode_bits</code>. Separately, <code>voxl_spi_init()</code> in <code>libqrb5165-io</code> validates its mode argument strictly against <code>SPI_MODE_0..3</code> and rejects any extra flag bits, so the flag cannot be requested through the library either — but we bypassed that with direct ioctls on the raw fd and the kernel is the hard stop.</p>
<p dir="auto">Our integration treats this as a safety gate: at startup it writes <code>SPI_MODE_0 | SPI_NO_CS</code>, reads it back, and if the readback fails it disables the TMF entirely and leaves IMU publication untouched rather than risk corrupting the IMU. That gate is what fires today, so the second sensor never comes up.</p>
<p dir="auto">For completeness, the surrounding work is done and validated on hardware. We have a bus-3 arbiter that serializes access with a mutex, switches mode and clock per transaction, and restores <code>SPI_MODE_3</code> with native CS on every success and error path. Matched 10-second A/B captures with the TMF path compiled in but gated off show no IMU regression: 1024 Hz in both conditions, 0.977 ms mean sample interval with no interval above 2 ms, and zero FIFO errors, logger drops, or mode-restore failures. The TMF8829 itself is known good and streams reliably at 15–18 MHz when it has a bus to itself. The only missing capability is per-device chip-select control on a shared bus 3.</p>
<h2>Path 1 (preferred): declare bus 3 with <code>cs-gpios</code> and a second spidev child node</h2>
<p dir="auto">Rather than work around chip select in userspace, we would rather let the kernel own it, which is how Linux is designed to share an SPI bus. Concretely, on the SPI3 node: raise the chip-select count to 2, add <code>cs-gpios</code> with the native CS in slot 0 and <code>&amp;tlmm 117</code> in slot 1, and add a second <code>spidev</code> child at <code>reg = &lt;1&gt;</code>. The TMF8829 then appears as its own <code>/dev/spidev3.1</code> with its own <code>spi-max-frequency</code> and mode.</p>
<p dir="auto">With that in place the kernel handles chip-select assertion, clock, and mode switching per device. On our side we delete the manual GPIO chip-select toggling and the entire mode-restore arbiter, and the IMU is never exposed to TMF traffic at all — a substantially safer arrangement than anything we can build in userspace.</p>
<p dir="auto">Questions on this path:</p>
<ol>
<li>Does the <code>spi_geni</code> driver in the 1.7.8 kernel cooperate with the SPI core's generic <code>cs-gpios</code> handling, or does its own <code>set_cs</code> implementation bypass GPIO chip selects?</li>
<li>Is there a supported way for us to apply a devicetree overlay or a patched DTB to a VOXL2 system image, or does this have to come from ModalAI as an image change?</li>
<li>Are there known constraints on SPI3 specifically — reserved chip selects, pinmux conflicts on TLMM 117, or other consumers of that controller we should know about?</li>
</ol>
<h2>Path 2 (fallback): add <code>SPI_NO_CS</code> to the controller driver's supported mode bits</h2>
<p dir="auto">If the devicetree route isn't viable, the smaller change is to advertise <code>SPI_NO_CS</code> in the GENI SPI controller's <code>mode_bits</code> and honor it in the driver's chip-select path. Our existing arbiter code already works unmodified against that interface, so this would unblock us with no changes on our side, and it would also need <code>voxl_spi_init()</code> in <code>libqrb5165-io</code> to stop rejecting mode values with flag bits set (or we continue issuing raw ioctls on the fd from <code>voxl_spi_get_fd()</code>).</p>
<p dir="auto">We consider this less idiomatic than Path 1 — chip select stays a userspace responsibility, with per-transaction GPIO toggling and mode restoration that has to be correct on every error path — and it still requires a custom kernel build. But it is a much narrower diff if that matters for your release process.</p>
<p dir="auto">Question: is <code>SPI_NO_CS</code> omitted from <code>mode_bits</code> deliberately, for a hardware reason on the GENI controller, or simply because nothing has needed it yet?</p>
<h2>What we're asking</h2>
<p dir="auto">Guidance on which of these two you'd support, and whether either can be delivered through an official system image rather than a fork we have to maintain. If neither is practical in the near term, our fallback is a hardware change moving the TMF8829 to its own SPI bus, and we'd appreciate confirmation that a shared bus 3 is simply not supported on this platform so we can commit to that redesign.</p>
<p dir="auto">Happy to share the arbiter source, the A/B log captures, or a minimal reproducer that only issues the failing ioctl if that's useful.</p>
]]></description><link>https://forum.modalai.com/topic/5399/voxl2-spi3-sharing-bus-3-between-the-onboard-icm-42688-and-a-second-spi-device-spi_no_cs-rejected-requesting-cs-gpios-guidance</link><generator>RSS for Node</generator><lastBuildDate>Thu, 27 Aug 2026 19:28:35 GMT</lastBuildDate><atom:link href="https://forum.modalai.com/topic/5399.rss" rel="self" type="application/rss+xml"/><pubDate>Thu, 27 Aug 2026 15:07:32 GMT</pubDate><ttl>60</ttl></channel></rss>