From 2c800f4cc1ff59019a8a72873c079313bab78b40 Mon Sep 17 00:00:00 2001 From: Tom Aarts Date: Fri, 27 Dec 2024 10:44:57 -0600 Subject: [PATCH 1/4] linuxfs used for SPI operations. .. Most this code base is from mpilone. I had no access to Mikes repo so I copied the files to ours. From Mikes initial work I made a few fixes for code he had not uniuti tested, and changes to config parms. Also parm bit shift direction and LSB first are not complete. Question whether we update the spi config. I do not want to use/allow the flags parm as it has bits no longe supported and would confuse things. I did read write and transfer testing for basic operation. I know there is a problem when read() is called mutiple times in succession. Al pushing this for discussion --- .../pi4j/io/spi/impl/DefaultSpiConfig.java | 2 +- plugins/pi4j-plugin-linuxfs/pom.xml | 5 + .../pi4j/plugin/linuxfs/LinuxFsPlugin.java | 13 +- .../plugin/linuxfs/internal/LinuxLibC.java | 88 +++++ .../linuxfs/provider/spi/LinuxFsSpi.java | 359 ++++++++++++++++++ .../provider/spi/LinuxFsSpiProvider.java | 62 +++ .../provider/spi/LinuxFsSpiProviderImpl.java | 81 ++++ .../src/main/java/module-info.java | 2 + pom.xml | 1 + 9 files changed, 607 insertions(+), 6 deletions(-) create mode 100644 plugins/pi4j-plugin-linuxfs/src/main/java/com/pi4j/plugin/linuxfs/internal/LinuxLibC.java create mode 100644 plugins/pi4j-plugin-linuxfs/src/main/java/com/pi4j/plugin/linuxfs/provider/spi/LinuxFsSpi.java create mode 100644 plugins/pi4j-plugin-linuxfs/src/main/java/com/pi4j/plugin/linuxfs/provider/spi/LinuxFsSpiProvider.java create mode 100644 plugins/pi4j-plugin-linuxfs/src/main/java/com/pi4j/plugin/linuxfs/provider/spi/LinuxFsSpiProviderImpl.java diff --git a/pi4j-core/src/main/java/com/pi4j/io/spi/impl/DefaultSpiConfig.java b/pi4j-core/src/main/java/com/pi4j/io/spi/impl/DefaultSpiConfig.java index 60ae6e45..bfc4a7f4 100644 --- a/pi4j-core/src/main/java/com/pi4j/io/spi/impl/DefaultSpiConfig.java +++ b/pi4j-core/src/main/java/com/pi4j/io/spi/impl/DefaultSpiConfig.java @@ -86,7 +86,7 @@ protected DefaultSpiConfig(Map properties){ if(properties.containsKey(FLAGS_KEY)){ this.flags = StringUtil.parseLong(properties.get(FLAGS_KEY), null); } else { - this.flags = 0L; // default flags (0) + this.flags = null; // set null, same as parseLong would } // define default property values if any are missing (based on the required address value) diff --git a/plugins/pi4j-plugin-linuxfs/pom.xml b/plugins/pi4j-plugin-linuxfs/pom.xml index 1374ed75..d9dc07d6 100644 --- a/plugins/pi4j-plugin-linuxfs/pom.xml +++ b/plugins/pi4j-plugin-linuxfs/pom.xml @@ -21,6 +21,11 @@ jsch ${jsch.version} + + net.java.dev.jna + jna + ${jna.version} + com.pi4j pi4j-library-linuxfs diff --git a/plugins/pi4j-plugin-linuxfs/src/main/java/com/pi4j/plugin/linuxfs/LinuxFsPlugin.java b/plugins/pi4j-plugin-linuxfs/src/main/java/com/pi4j/plugin/linuxfs/LinuxFsPlugin.java index e4702db2..a21d2a37 100644 --- a/plugins/pi4j-plugin-linuxfs/src/main/java/com/pi4j/plugin/linuxfs/LinuxFsPlugin.java +++ b/plugins/pi4j-plugin-linuxfs/src/main/java/com/pi4j/plugin/linuxfs/LinuxFsPlugin.java @@ -37,6 +37,7 @@ import com.pi4j.plugin.linuxfs.provider.gpio.digital.LinuxFsDigitalOutputProvider; import com.pi4j.plugin.linuxfs.provider.pwm.LinuxFsPwmProvider; import com.pi4j.plugin.linuxfs.internal.LinuxPwm; +import com.pi4j.plugin.linuxfs.provider.spi.LinuxFsSpiProvider; import com.pi4j.provider.Provider; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -100,14 +101,15 @@ public class LinuxFsPlugin implements Plugin { public static final String I2C_PROVIDER_NAME = NAME + " I2C Provider"; public static final String I2C_PROVIDER_ID = ID + "-i2c"; -// // SPI Provider name and unique ID -// public static final String SPI_PROVIDER_NAME = NAME + " SPI Provider"; -// public static final String SPI_PROVIDER_ID = ID + "-spi"; -// + // // Serial Provider name and unique ID // public static final String SERIAL_PROVIDER_NAME = NAME + " Serial Provider"; // public static final String SERIAL_PROVIDER_ID = ID + "-serial"; + // SPI Provider name and unique ID + public static final String SPI_PROVIDER_NAME = NAME + " SPI Provider"; + public static final String SPI_PROVIDER_ID = ID + "-spi"; + public static String DEFAULT_GPIO_FILESYSTEM_PATH = LinuxGpio.DEFAULT_SYSTEM_PATH; public static String DEFAULT_PWM_FILESYSTEM_PATH = LinuxPwm.DEFAULT_SYSTEM_PATH; @@ -155,7 +157,8 @@ public void initialize(PluginService service) { LinuxFsDigitalInputProvider.newInstance(gpioFileSystemPath), LinuxFsDigitalOutputProvider.newInstance(gpioFileSystemPath), LinuxFsPwmProvider.newInstance(pwmFileSystemPath, pwmChip), - LinuxFsI2CProvider.newInstance() + LinuxFsI2CProvider.newInstance(), + LinuxFsSpiProvider.newInstance() }; // register the LinuxFS I/O Providers with the plugin service diff --git a/plugins/pi4j-plugin-linuxfs/src/main/java/com/pi4j/plugin/linuxfs/internal/LinuxLibC.java b/plugins/pi4j-plugin-linuxfs/src/main/java/com/pi4j/plugin/linuxfs/internal/LinuxLibC.java new file mode 100644 index 00000000..e57f23d8 --- /dev/null +++ b/plugins/pi4j-plugin-linuxfs/src/main/java/com/pi4j/plugin/linuxfs/internal/LinuxLibC.java @@ -0,0 +1,88 @@ +/* + * * Copyright (C) 2012 - 2024 Pi4J + * * %% + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * - + * #%L + * ********************************************************************** + * ORGANIZATION : Pi4J + * PROJECT : Pi4J :: EXTENSION + * FILENAME : LinuxLibC.java + * + * This file is part of the Pi4J project. More information about + * this project can be found here: https://pi4j.com/ + * ********************************************************************** + * %% + */ + +package com.pi4j.plugin.linuxfs.internal; + +import com.sun.jna.Library; +import com.sun.jna.Native; + +/** + * C library functions. + * + * @author mpilone + * @since 10/3/24. + */ +public interface LinuxLibC extends Library { + + // This class could extend c.s.j.platform.linux.LibC, but we're not using any + // of that functionality right now so we can avoid the jna-platform dependency + // until we need it. + + LinuxLibC INSTANCE = LinuxLibC.LibLoader.load(); + + class LibLoader { + static LinuxLibC load() { + return Native.load("c", LinuxLibC.class); + } + } + + /////////////////////////////////// + // fcntl.h + int O_WRONLY = 00000001; + int O_RDWR = 00000002; + int O_NONBLOCK = 00004000; + + /////////////////////////////////// + // ioctl.h + int _IOC_NRBITS = 8; + int _IOC_TYPEBITS = 8; + int _IOC_SIZEBITS = 14; + + int _IOC_NRSHIFT = 0; + int _IOC_TYPESHIFT = (_IOC_NRSHIFT + _IOC_NRBITS); + int _IOC_SIZESHIFT = (_IOC_TYPESHIFT + _IOC_TYPEBITS); + int _IOC_DIRSHIFT = (_IOC_SIZESHIFT + _IOC_SIZEBITS); + + byte _IOC_NONE = 0; + byte _IOC_WRITE = 1; + byte _IOC_READ = 2; + + static int _IOC(byte dir, byte type, byte nr, int size) { + return (((dir) << _IOC_DIRSHIFT) | + ((type) << _IOC_TYPESHIFT) | + ((nr) << _IOC_NRSHIFT) | + ((size) << _IOC_SIZESHIFT)); + } + + int ioctl(int filedes, long op, Object... args); + + int open(String pathname, int flags); + + int close(int fd); +} \ No newline at end of file diff --git a/plugins/pi4j-plugin-linuxfs/src/main/java/com/pi4j/plugin/linuxfs/provider/spi/LinuxFsSpi.java b/plugins/pi4j-plugin-linuxfs/src/main/java/com/pi4j/plugin/linuxfs/provider/spi/LinuxFsSpi.java new file mode 100644 index 00000000..f36e0a64 --- /dev/null +++ b/plugins/pi4j-plugin-linuxfs/src/main/java/com/pi4j/plugin/linuxfs/provider/spi/LinuxFsSpi.java @@ -0,0 +1,359 @@ +/* + * * Copyright (C) 2012 - 2024 Pi4J + * * %% + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * - + * #%L + * ********************************************************************** + * ORGANIZATION : Pi4J + * PROJECT : Pi4J :: EXTENSION + * FILENAME : LinuxFsSpi.java + * + * This file is part of the Pi4J project. More information about + * this project can be found here: https://pi4j.com/ + * ********************************************************************** + * %% + */ + + +package com.pi4j.plugin.linuxfs.provider.spi; + +import com.pi4j.io.exception.IOException; +import com.pi4j.io.spi.Spi; +import com.pi4j.io.spi.SpiBase; +import com.pi4j.io.spi.SpiConfig; +import com.pi4j.plugin.linuxfs.internal.LinuxLibC; +import com.sun.jna.Memory; +import com.sun.jna.Native; +import com.sun.jna.Structure; +import com.sun.jna.ptr.IntByReference; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static com.pi4j.plugin.linuxfs.internal.LinuxLibC.*; + +/** + * SPI implementation that uses JNA bindings to the Linux SPI device (i.e. /dev/spidev0.0). Only supports writing but + * it works to drive an SSD1306 OLED display. + * + * @author mpilone + * @see + * spi-c-ioctl + * @see spidev.h + * @see spi.h + * @see spidev_fdx.c + * @see SPI userspace API + * @since 10/3/24. + */ +public class LinuxFsSpi extends SpiBase implements Spi { + + private static final Logger LOG = LoggerFactory.getLogger(LinuxFsSpi.class); + + /////////////////////////////////// + // From spidev.h + private final static byte SPI_IOC_MAGIC = 'k'; + private final static byte SIZE_OF_BYTE = 1; + private final static byte SIZE_OF_INT = 4; + + private static int SPI_IOC_MESSAGE(int n) { + + // Even though we will pass the structure to ioctl as a pointer, the command needs to know + // the actual size of the structure (i.e. sizeof). Therefore, we use the ByValue interface + // when getting the struct size. + int structSize = Native.getNativeSize(spi_ioc_transfer.ByValue.class); + int msgSize = ((((n)*(structSize)) < (1 << _IOC_SIZEBITS)) + ? ((n)*(structSize)) : 0); + + return _IOC(_IOC_WRITE, SPI_IOC_MAGIC, (byte)0, msgSize); + } + + // These could be replaced with the specific values generated from the _IOC method (a macro in the native C), + // but I think it is useful to see where the values come from. + + /* Read / Write of SPI mode (SPI_MODE_0..SPI_MODE_3) (limited to 8 bits) */ + private final static int SPI_IOC_RD_MODE = _IOC(_IOC_READ, SPI_IOC_MAGIC, (byte)1, SIZE_OF_BYTE); + private final static int SPI_IOC_WR_MODE = _IOC(_IOC_WRITE, SPI_IOC_MAGIC, (byte)1, SIZE_OF_BYTE); + + /* Read / Write SPI bit justification */ + private final static int SPI_IOC_RD_LSB_FIRST = _IOC(_IOC_READ, SPI_IOC_MAGIC, (byte)2, SIZE_OF_BYTE); + private final static int SPI_IOC_WR_LSB_FIRST = _IOC(_IOC_WRITE, SPI_IOC_MAGIC, (byte)2, SIZE_OF_BYTE); + + /* Read / Write SPI device word length (1..N) */ + private final static int SPI_IOC_RD_BITS_PER_WORD = _IOC(_IOC_READ, SPI_IOC_MAGIC, (byte)3, SIZE_OF_BYTE); + private final static int SPI_IOC_WR_BITS_PER_WORD = _IOC(_IOC_WRITE, SPI_IOC_MAGIC, (byte)3, SIZE_OF_BYTE); + + /* Read / Write SPI device default max speed hz */ + private final static int SPI_IOC_RD_MAX_SPEED_HZ = _IOC(_IOC_READ, SPI_IOC_MAGIC, (byte)4, SIZE_OF_INT); + private final static int SPI_IOC_WR_MAX_SPEED_HZ = _IOC(_IOC_WRITE, SPI_IOC_MAGIC, (byte)4, SIZE_OF_INT); + + /* Read / Write of the SPI mode field */ + private final static int SPI_IOC_RD_MODE32 = _IOC(_IOC_READ, SPI_IOC_MAGIC, (byte)5, SIZE_OF_INT); + private final static int SPI_IOC_WR_MODE32 = _IOC(_IOC_WRITE, SPI_IOC_MAGIC, (byte)5, SIZE_OF_INT); + + @Structure.FieldOrder({"tx_buf", "rx_buf", + "len", "speed_hz", + "delay_usecs", "bits_per_word", "cs_change", "tx_nbits", "rx_nbits", "word_delay_usecs", "pad"}) + public static class spi_ioc_transfer extends Structure { + public long tx_buf; + public long rx_buf; + + public int len; + public int speed_hz; + + public short delay_usecs; + public byte bits_per_word; + public byte cs_change; + public byte tx_nbits; + public byte rx_nbits; + public byte word_delay_usecs; + public byte pad; + + public static class ByValue extends spi_ioc_transfer implements Structure.ByValue {} + } + + /////////////////////////////////// + // spi.h + private final byte SPI_CPHA = 1; /* clock phase */ + private final byte SPI_CPOL = 1 << 1; /* clock polarity */ + + private final byte SPI_MODE_0 = (0|0); /* (original MicroWire) */ + private final byte SPI_MODE_1 = (0|SPI_CPHA); + private final byte SPI_MODE_2 = (SPI_CPOL|0); + private final byte SPI_MODE_3 = (SPI_CPOL|SPI_CPHA); + + + private final byte BITS8 = 8; + + private final static String SPI_DEVICE_BASE = "/dev/spidev"; + private final LinuxLibC libc = LinuxLibC.INSTANCE; + private int fd; + + public LinuxFsSpi(LinuxFsSpiProviderImpl provider, SpiConfig config) { + super(provider, config); + } + + @Override + public void open() { + super.open(); + + // From the docs (https://www.kernel.org/doc/html/v6.12/spi/spidev.html): + // For a SPI device with chipselect C on bus B, you should see: + // + // /dev/spidevB.C ... + // character special device, major number 153 with a dynamically chosen minor device number. + // This is the node that userspace programs will open, created by “udev” or “mdev”. + String spiDev = SPI_DEVICE_BASE + config().bus().getBus() + "." + config().getChipSelect().getChipSelect(); + fd = libc.open(spiDev, LinuxLibC.O_RDWR); + if (fd < 0) { + throw new RuntimeException("Failed to open SPI device " + spiDev); + } + + IntByReference intPtr = new IntByReference(); + int ret = libc.ioctl(fd, SPI_IOC_RD_MODE32, intPtr); + if(ret != 0) { + libc.close(fd); + throw new RuntimeException("Could not read SPI mode."); + } + // if 'flags' were provided error + if(config().flags() != null){ + throw new IOException("Unsupported SPI Pi5 parameter flags" ); + } + switch (config().mode()) { + case MODE_0: + intPtr.setValue(intPtr.getValue() | SPI_MODE_0); + break; + case MODE_1: + intPtr.setValue(intPtr.getValue() | SPI_MODE_1); + break; + case MODE_2: + intPtr.setValue(intPtr.getValue() | SPI_MODE_2); + break; + case MODE_3: + intPtr.setValue(intPtr.getValue() | SPI_MODE_3); + break; + } + + ret = libc.ioctl(fd, SPI_IOC_WR_MODE32, intPtr); + if(ret != 0) { + libc.close(fd); + throw new RuntimeException("Could not write SPI mode.."); + } + + intPtr.setValue(config().baud()); + ret = libc.ioctl(fd, SPI_IOC_RD_MAX_SPEED_HZ, intPtr); + if(ret != 0) { + libc.close(fd); + throw new RuntimeException("Could not read the SPI max speed."); + } + + intPtr.setValue(config().baud()); + ret = libc.ioctl(fd, SPI_IOC_WR_MAX_SPEED_HZ, intPtr); + if(ret != 0) { + libc.close(fd); + throw new RuntimeException("Could not write the SPI max speed."); + } + + // Bits per word + intPtr.setValue(BITS8); + ret = libc.ioctl(fd, SPI_IOC_WR_BITS_PER_WORD, intPtr); + if(ret != 0) { + libc.close(fd); + throw new RuntimeException("Could not write the SPI BITS per write."); + } + // Bits per word + intPtr.setValue(BITS8); + ret = libc.ioctl(fd, SPI_IOC_RD_BITS_PER_WORD, intPtr); + if(ret != 0) { + libc.close(fd); + throw new RuntimeException("Could not write the SPI BITS per read."); + } + + // BIT shift direction + // TODO was in 'flags' add to config ??? + intPtr.setValue(0); // was in 'flags' add to config ??? + ret = libc.ioctl(fd, SPI_IOC_RD_LSB_FIRST, intPtr); + if(ret != 0) { + libc.close(fd); + throw new RuntimeException("Could not write the SPI SHIFT read."); + } + intPtr.setValue(0); // was in 'flags' add to config ??? + ret = libc.ioctl(fd, SPI_IOC_WR_LSB_FIRST, intPtr); + if(ret != 0) { + libc.close(fd); + throw new RuntimeException("Could not write the SPI SHIFT write."); + } + + + } + + @Override + public void close() { + libc.close(fd); + + super.close(); + } + + @Override + public int transfer(byte[] write, int writeOffset, byte[] read, int readOffset, int numberOfBytes) { + PeerAccessibleMemory buf = new PeerAccessibleMemory(numberOfBytes); + buf.write(0, write, writeOffset, numberOfBytes); + + // According to the docs you can use the same buffer for tx/rx. + spi_ioc_transfer transfer = new spi_ioc_transfer(); + transfer.tx_buf = buf.getPeer(); + transfer.rx_buf = buf.getPeer(); + transfer.bits_per_word = BITS8; + transfer.speed_hz = config.baud(); + transfer.delay_usecs = 0; + transfer.len = numberOfBytes; + + int ret = libc.ioctl(fd, SPI_IOC_MESSAGE(1), transfer); + if (ret < 0) { + LOG.error("Could not write SPI message. ret {}, error: {}", ret, Native.getLastError()); + numberOfBytes = -1; + } + else { + buf.read(0, read, readOffset, numberOfBytes); + } + + buf.close(); + + return numberOfBytes; + } + + @Override + public int read() { + byte[] buf = new byte[1]; + if (read(buf, 0, 1) == 1) { + return buf[0]; + } + else { + return -1; + } + } + + @Override + public int read(byte[] read, int offset, int length) { + PeerAccessibleMemory buf = new PeerAccessibleMemory(length); + + spi_ioc_transfer transfer = new spi_ioc_transfer(); + transfer.tx_buf = 0; + transfer.rx_buf = buf.getPeer(); + transfer.bits_per_word = BITS8; + transfer.speed_hz = config.baud(); + transfer.delay_usecs = 0; + transfer.len = length; + + int ret = libc.ioctl(fd, SPI_IOC_MESSAGE(1), transfer); + if (ret < 0) { + LOG.error("Could not write SPIIOC message. ret {}, error: {}", ret, Native.getLastError()); + length = -1; + }else{ + buf.read(0, read, offset, length); + } + + buf.close(); + + return length; + } + + @Override + public int write(byte b) { + return write(new byte[] {b}, 0, 1); + } + + @Override + public int write(byte[] data, int offset, int length) { + // We could reuse this buffer for all requests as long as it is large enough. + // For now we'll alloc/free a new one on each request. + PeerAccessibleMemory buf = new PeerAccessibleMemory(length); + buf.write(0, data, offset, length); + + spi_ioc_transfer transfer = new spi_ioc_transfer(); + transfer.tx_buf = buf.getPeer(); + transfer.rx_buf = 0; + transfer.bits_per_word = BITS8; + transfer.speed_hz = config.baud(); + transfer.delay_usecs = 0; + transfer.len = length; + + int ret = libc.ioctl(fd, SPI_IOC_MESSAGE(1), transfer); + if (ret < 0) { + LOG.error("Could not write SPI message. ret {}, error: {}", ret, Native.getLastError()); + length = 0; + } + + buf.close(); + + return length; + } + + /** + * Extension of Memory to allow direct access to the native peer pointer. This is required because + * the {@link spi_ioc_transfer} structure uses a long for the tx_buf and rx_buf fields but a + * native pointer is normally only 32 bitsso we can't let JNA do the mapping for us. There may be a better + * way to do this with JNA. We fetch the peer and put it in a long in the struct to maintain proper struct + * alignment. + */ + private static class PeerAccessibleMemory extends Memory { + public PeerAccessibleMemory(long size) { + super(size); + } + + long getPeer() { + return peer; + } + } +} \ No newline at end of file diff --git a/plugins/pi4j-plugin-linuxfs/src/main/java/com/pi4j/plugin/linuxfs/provider/spi/LinuxFsSpiProvider.java b/plugins/pi4j-plugin-linuxfs/src/main/java/com/pi4j/plugin/linuxfs/provider/spi/LinuxFsSpiProvider.java new file mode 100644 index 00000000..c4835c7b --- /dev/null +++ b/plugins/pi4j-plugin-linuxfs/src/main/java/com/pi4j/plugin/linuxfs/provider/spi/LinuxFsSpiProvider.java @@ -0,0 +1,62 @@ +/* + * * Copyright (C) 2012 - 2024 Pi4J + * * %% + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * - + * #%L + * ********************************************************************** + * ORGANIZATION : Pi4J + * PROJECT : Pi4J :: EXTENSION + * FILENAME : LinuxFsSpiProvider.java + * + * This file is part of the Pi4J project. More information about + * this project can be found here: https://pi4j.com/ + * ********************************************************************** + * %% + */ + + +package com.pi4j.plugin.linuxfs.provider.spi; + +import com.pi4j.io.spi.SpiProvider; +import com.pi4j.plugin.linuxfs.LinuxFsPlugin; + +/** + *

LinuxFsSpiProvider interface.

+ * + * @author mpilone + * @since 10/4/24. + */ +public interface LinuxFsSpiProvider extends SpiProvider { + + + + + + /** {@link LinuxFsPlugin#SPI_PROVIDER_NAME} */ + String NAME = LinuxFsPlugin.SPI_PROVIDER_NAME; + /** {@link LinuxFsPlugin#SPI_PROVIDER_ID} */ + String ID = LinuxFsPlugin.SPI_PROVIDER_ID; + + /** + *

newInstance.

+ * + * @return a {@link LinuxFsSpiProviderImpl} object. + */ + static LinuxFsSpiProviderImpl newInstance() { + return new LinuxFsSpiProviderImpl(); + } + +} \ No newline at end of file diff --git a/plugins/pi4j-plugin-linuxfs/src/main/java/com/pi4j/plugin/linuxfs/provider/spi/LinuxFsSpiProviderImpl.java b/plugins/pi4j-plugin-linuxfs/src/main/java/com/pi4j/plugin/linuxfs/provider/spi/LinuxFsSpiProviderImpl.java new file mode 100644 index 00000000..5e064b5f --- /dev/null +++ b/plugins/pi4j-plugin-linuxfs/src/main/java/com/pi4j/plugin/linuxfs/provider/spi/LinuxFsSpiProviderImpl.java @@ -0,0 +1,81 @@ +/* + * * Copyright (C) 2012 - 2024 Pi4J + * * %% + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * - + * #%L + * ********************************************************************** + * ORGANIZATION : Pi4J + * PROJECT : Pi4J :: EXTENSION + * FILENAME : LinuxFsSpiProviderImpl.java + * + * This file is part of the Pi4J project. More information about + * this project can be found here: https://pi4j.com/ + * ********************************************************************** + * %% + */ + + +package com.pi4j.plugin.linuxfs.provider.spi; + +import com.pi4j.boardinfo.util.BoardInfoHelper; +import com.pi4j.context.Context; +import com.pi4j.exception.ShutdownException; +import com.pi4j.io.spi.Spi; +import com.pi4j.io.spi.SpiConfig; +import com.pi4j.io.spi.SpiProvider; +import com.pi4j.io.spi.SpiProviderBase; + +/** + * @author mpilone + * @since 10/3/24. + */ +public class LinuxFsSpiProviderImpl extends SpiProviderBase + implements LinuxFsSpiProvider { + + public LinuxFsSpiProviderImpl() { + this.id = ID; + this.name = NAME; + } + + @Override + public int getPriority() { + // the linux FS driver should always be higher priority + return BoardInfoHelper.usesRP1() ? 150 : 50; + } + + @Override + public Spi create(SpiConfig config) { + Spi spi = new LinuxFsSpi(this, config); + + // Is this the right place to call open? Should we have a shared spi device like the I2CBus? + spi.open(); + + this.context.registry().add(spi); + return spi; + } + + @Override + public SpiProvider shutdown(Context context) throws ShutdownException { + + // Is this the right place to call close? + this.context.registry().allByType(LinuxFsSpi.class).values() + .forEach(LinuxFsSpi::close); + + return super.shutdown(context); + } + + +} \ No newline at end of file diff --git a/plugins/pi4j-plugin-linuxfs/src/main/java/module-info.java b/plugins/pi4j-plugin-linuxfs/src/main/java/module-info.java index 365fd0ee..a9bcdde6 100644 --- a/plugins/pi4j-plugin-linuxfs/src/main/java/module-info.java +++ b/plugins/pi4j-plugin-linuxfs/src/main/java/module-info.java @@ -34,11 +34,13 @@ requires com.pi4j; requires com.pi4j.library.linuxfs; requires jsch; + requires com.sun.jna; exports com.pi4j.plugin.linuxfs; exports com.pi4j.plugin.linuxfs.provider.gpio.digital; exports com.pi4j.plugin.linuxfs.provider.pwm; exports com.pi4j.plugin.linuxfs.provider.i2c; + exports com.pi4j.plugin.linuxfs.provider.spi; provides com.pi4j.extension.Plugin with LinuxFsPlugin; diff --git a/pom.xml b/pom.xml index 3e8717da..10888cf0 100644 --- a/pom.xml +++ b/pom.xml @@ -265,6 +265,7 @@ 5.10.2 2.0.12 2.10.4 + 5.15.0 3.2.0 From 0e900d06809a667ae2388c361d55eb5b1f7fc496 Mon Sep 17 00:00:00 2001 From: Tom Aarts Date: Sun, 29 Dec 2024 17:43:24 -0600 Subject: [PATCH 2/4] identify config changes that are different from the PIGPIO SPI implementation --- .../java/com/pi4j/plugin/linuxfs/provider/spi/LinuxFsSpi.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/plugins/pi4j-plugin-linuxfs/src/main/java/com/pi4j/plugin/linuxfs/provider/spi/LinuxFsSpi.java b/plugins/pi4j-plugin-linuxfs/src/main/java/com/pi4j/plugin/linuxfs/provider/spi/LinuxFsSpi.java index f36e0a64..e5a6fa33 100644 --- a/plugins/pi4j-plugin-linuxfs/src/main/java/com/pi4j/plugin/linuxfs/provider/spi/LinuxFsSpi.java +++ b/plugins/pi4j-plugin-linuxfs/src/main/java/com/pi4j/plugin/linuxfs/provider/spi/LinuxFsSpi.java @@ -166,6 +166,8 @@ public void open() { libc.close(fd); throw new RuntimeException("Could not read SPI mode."); } + + // TODO we can't use the flags as it only allows two SPI // if 'flags' were provided error if(config().flags() != null){ throw new IOException("Unsupported SPI Pi5 parameter flags" ); @@ -228,7 +230,7 @@ public void open() { libc.close(fd); throw new RuntimeException("Could not write the SPI SHIFT read."); } - intPtr.setValue(0); // was in 'flags' add to config ??? + intPtr.setValue(0); // TODO was in 'flags' add to config ??? ret = libc.ioctl(fd, SPI_IOC_WR_LSB_FIRST, intPtr); if(ret != 0) { libc.close(fd); From fca4bc12a2afe6dd687b7753c54daa422c72666e Mon Sep 17 00:00:00 2001 From: Tom Aarts Date: Fri, 10 Jan 2025 20:07:58 -0600 Subject: [PATCH 3/4] added two config parms MOSI LSB first writeLsbFirst and MISO LSB first readLsbFirst --- .../src/main/java/com/pi4j/io/spi/Spi.java | 5 +- .../main/java/com/pi4j/io/spi/SpiConfig.java | 72 +++++++++++++++++++ .../com/pi4j/io/spi/SpiConfigBuilder.java | 19 +++++ .../pi4j/io/spi/impl/DefaultSpiConfig.java | 39 ++++++++++ .../io/spi/impl/DefaultSpiConfigBuilder.java | 12 ++++ .../linuxfs/provider/spi/LinuxFsSpi.java | 6 +- .../plugin/pigpio/provider/spi/PiGpioSpi.java | 17 +++++ 7 files changed, 165 insertions(+), 5 deletions(-) diff --git a/pi4j-core/src/main/java/com/pi4j/io/spi/Spi.java b/pi4j-core/src/main/java/com/pi4j/io/spi/Spi.java index c47c0bdc..20e410ff 100644 --- a/pi4j-core/src/main/java/com/pi4j/io/spi/Spi.java +++ b/pi4j-core/src/main/java/com/pi4j/io/spi/Spi.java @@ -48,7 +48,10 @@ public interface Spi extends IO, AutoCloseable, IOD SpiChipSelect DEFAULT_CHIP_SELECT = SpiChipSelect.CS_0; /** Constant DEFAULT_BAUD=1000000 */ int DEFAULT_BAUD = 1000000; // 1MHz (range is 500kHz - 32MHz) - + /** Constant DEFAULT_WRITE_LSB_FIRST */ + int DEFAULT_WRITE_LSB_FIRST = 0; + /** Constant DEFAULT_READ_LSB_FIRST */ + int DEFAULT_READ_LSB_FIRST = 0; /** *

newConfigBuilder.

* diff --git a/pi4j-core/src/main/java/com/pi4j/io/spi/SpiConfig.java b/pi4j-core/src/main/java/com/pi4j/io/spi/SpiConfig.java index c4fce94a..0f9a2d05 100644 --- a/pi4j-core/src/main/java/com/pi4j/io/spi/SpiConfig.java +++ b/pi4j-core/src/main/java/com/pi4j/io/spi/SpiConfig.java @@ -44,6 +44,12 @@ public interface SpiConfig extends AddressConfig, IOConfig String MODE_KEY = "mode"; /** Constant FLAGS_KEY="flags" */ String FLAGS_KEY = "flags"; + /** Constant WRITE_LSB_KEY="baud" */ + String WRITE_LSB_KEY = "write_lsb"; + /** Constant READ_LSB_KEY="baud" */ + String READ_LSB_KEY = "read_lsb"; + + /** *

newBuilder.

@@ -68,6 +74,42 @@ static SpiConfigBuilder newBuilder(Context context) { */ default Integer getBaud() { return baud(); } + /** + *

ReadLsbFirst.

+ * In accordance with the flags parm, Read operations + * 0 is LSB bit shifted first, 1 MSB bit shifted first + * + * @return a {@link java.lang.Integer} object. + */ + Integer readLsbFirst(); + + /** + *

getreadLsbfISRT.

+ * + * @return a {@link java.lang.Integer} object. + */ + default Integer getReadLsbFirst() { return readLsbFirst(); } + + + + /** + *

WriteLsbFirst.

+ * In accordance with the flags parm, Write operations + * 0 is LSB bit shifted first, 1 MSB bit shifted first + * + * @return a {@link java.lang.Integer} object. + */ + Integer writeLsbFirst(); + + /** + *

getreadLsbfISRT.

+ * + * @return a {@link java.lang.Integer} object. + */ + default Integer getWriteLsbFirst() { return writeLsbFirst(); } + + + /** *

bus.

*

If the Bus value is configured, that SpiBus @@ -100,6 +142,36 @@ default boolean getBusUserProvided(){ return busUserProvided(); } + /** + *

writeLsbFirstserProvided.

+ * @return a boolean. + */ + boolean writeLsbFirstUserProvided(); + + /** + *

getWriteLsbFirstUserProvided.

+ * @return a {@link java.lang.Boolean} object. + */ + default boolean getWriteLsbFIrstUserProvided(){ + return writeLsbFirstUserProvided(); + } + + /** + *

writeLsbFirstserProvided.

+ * @return a boolean. + */ + boolean readLsbFirstUserProvided(); + + /** + *

getReadLsbFirstUserProvided.

+ * @return a {@link java.lang.Boolean} object. + */ + default boolean getReadLsbFIrstUserProvided(){ + return readLsbFirstUserProvided(); + } + + + /** *

mode.

diff --git a/pi4j-core/src/main/java/com/pi4j/io/spi/SpiConfigBuilder.java b/pi4j-core/src/main/java/com/pi4j/io/spi/SpiConfigBuilder.java index 2e418b06..7b4959e2 100644 --- a/pi4j-core/src/main/java/com/pi4j/io/spi/SpiConfigBuilder.java +++ b/pi4j-core/src/main/java/com/pi4j/io/spi/SpiConfigBuilder.java @@ -47,6 +47,24 @@ static SpiConfigBuilder newInstance(Context context) { return DefaultSpiConfigBuilder.newInstance(context); } + /** + *

readLsbFirst.

+ * + * @param shift a {@link java.lang.Integer} object. + * @return a {@link com.pi4j.io.spi.SpiConfigBuilder} object. + */ + SpiConfigBuilder readLsbFirst(Integer shift); + + /** + *

writeLsbFirst.

+ * + * @param shift a {@link java.lang.Integer} object. + * @return a {@link com.pi4j.io.spi.SpiConfigBuilder} object. + */ + SpiConfigBuilder writeLsbFirst(Integer shift); + + + /** *

baud.

* @@ -55,6 +73,7 @@ static SpiConfigBuilder newInstance(Context context) { */ SpiConfigBuilder baud(Integer rate); + /** *

bus.

*

If the Bus value is configured, that SpiBus diff --git a/pi4j-core/src/main/java/com/pi4j/io/spi/impl/DefaultSpiConfig.java b/pi4j-core/src/main/java/com/pi4j/io/spi/impl/DefaultSpiConfig.java index bfc4a7f4..88106168 100644 --- a/pi4j-core/src/main/java/com/pi4j/io/spi/impl/DefaultSpiConfig.java +++ b/pi4j-core/src/main/java/com/pi4j/io/spi/impl/DefaultSpiConfig.java @@ -48,6 +48,10 @@ public class DefaultSpiConfig protected final SpiBus bus; protected boolean busUserProvided = false; // indicate user supplied the value protected final Long flags; + protected final int readLsbFirst; + protected final int writeLsbFirst; + protected boolean readLsbFirstUserProvided; + protected boolean writeLsbFirstUserProvided; /** * PRIVATE CONSTRUCTOR @@ -73,6 +77,20 @@ protected DefaultSpiConfig(Map properties){ this.busUserProvided = false; } + if(properties.containsKey(WRITE_LSB_KEY)){ + this.writeLsbFirst = StringUtil.parseInteger(properties.get(WRITE_LSB_KEY),Spi.DEFAULT_WRITE_LSB_FIRST); + this.writeLsbFirstUserProvided = true; + }else { + this.writeLsbFirst = 0; + this.writeLsbFirstUserProvided = false; + } + if(properties.containsKey(READ_LSB_KEY)){ + this.readLsbFirst = StringUtil.parseInteger(properties.get(READ_LSB_KEY),Spi.DEFAULT_READ_LSB_FIRST); + this.readLsbFirstUserProvided = true; + }else { + this.readLsbFirst = 0; + this.readLsbFirstUserProvided = false; + } // load optional MODE from properties if(properties.containsKey(MODE_KEY)){ this.mode = SpiMode.parse(properties.get(MODE_KEY)); @@ -101,6 +119,16 @@ public Integer baud() { return this.baud; } + @Override + public Integer readLsbFirst() { + return this.readLsbFirst; + } + + @Override + public Integer writeLsbFirst() { + return this.writeLsbFirst; + } + /** {@inheritDoc} */ @Override public boolean busUserProvided() { @@ -108,6 +136,17 @@ public boolean busUserProvided() { } + /** {@inheritDoc} */ + @Override + public boolean writeLsbFirstUserProvided() { + return this.writeLsbFirstUserProvided; + } + /** {@inheritDoc} */ + @Override + public boolean readLsbFirstUserProvided() { + return this.readLsbFirstUserProvided; + } + /** {@inheritDoc} */ @Override diff --git a/pi4j-core/src/main/java/com/pi4j/io/spi/impl/DefaultSpiConfigBuilder.java b/pi4j-core/src/main/java/com/pi4j/io/spi/impl/DefaultSpiConfigBuilder.java index cca1a4d5..085988bc 100644 --- a/pi4j-core/src/main/java/com/pi4j/io/spi/impl/DefaultSpiConfigBuilder.java +++ b/pi4j-core/src/main/java/com/pi4j/io/spi/impl/DefaultSpiConfigBuilder.java @@ -56,6 +56,18 @@ public static SpiConfigBuilder newInstance(Context context) { return new DefaultSpiConfigBuilder(context); } + @Override + public SpiConfigBuilder readLsbFirst(Integer shift) { + this.properties.put(SpiConfig.READ_LSB_KEY, shift.toString()); + return this; + } + + @Override + public SpiConfigBuilder writeLsbFirst(Integer shift) { + this.properties.put(SpiConfig.WRITE_LSB_KEY, shift.toString()); + return this; + } + /** {@inheritDoc} */ @Override public SpiConfigBuilder baud(Integer rate) { diff --git a/plugins/pi4j-plugin-linuxfs/src/main/java/com/pi4j/plugin/linuxfs/provider/spi/LinuxFsSpi.java b/plugins/pi4j-plugin-linuxfs/src/main/java/com/pi4j/plugin/linuxfs/provider/spi/LinuxFsSpi.java index e5a6fa33..878cbdfe 100644 --- a/plugins/pi4j-plugin-linuxfs/src/main/java/com/pi4j/plugin/linuxfs/provider/spi/LinuxFsSpi.java +++ b/plugins/pi4j-plugin-linuxfs/src/main/java/com/pi4j/plugin/linuxfs/provider/spi/LinuxFsSpi.java @@ -167,7 +167,6 @@ public void open() { throw new RuntimeException("Could not read SPI mode."); } - // TODO we can't use the flags as it only allows two SPI // if 'flags' were provided error if(config().flags() != null){ throw new IOException("Unsupported SPI Pi5 parameter flags" ); @@ -223,14 +222,13 @@ public void open() { } // BIT shift direction - // TODO was in 'flags' add to config ??? - intPtr.setValue(0); // was in 'flags' add to config ??? + intPtr.setValue(config().readLsbFirst()); ret = libc.ioctl(fd, SPI_IOC_RD_LSB_FIRST, intPtr); if(ret != 0) { libc.close(fd); throw new RuntimeException("Could not write the SPI SHIFT read."); } - intPtr.setValue(0); // TODO was in 'flags' add to config ??? + intPtr.setValue(config().writeLsbFirst()); ret = libc.ioctl(fd, SPI_IOC_WR_LSB_FIRST, intPtr); if(ret != 0) { libc.close(fd); diff --git a/plugins/pi4j-plugin-pigpio/src/main/java/com/pi4j/plugin/pigpio/provider/spi/PiGpioSpi.java b/plugins/pi4j-plugin-pigpio/src/main/java/com/pi4j/plugin/pigpio/provider/spi/PiGpioSpi.java index 13fcad7e..95769280 100644 --- a/plugins/pi4j-plugin-pigpio/src/main/java/com/pi4j/plugin/pigpio/provider/spi/PiGpioSpi.java +++ b/plugins/pi4j-plugin-pigpio/src/main/java/com/pi4j/plugin/pigpio/provider/spi/PiGpioSpi.java @@ -46,6 +46,8 @@ public class PiGpioSpi extends SpiBase implements Spi { protected final int handle; protected static int SPI_BUS_MASK = 0x0100; protected static int SPI_MODE_MASK = 0x0003; + protected static int SPI_WRITE_LSB_FIRST_MASK = 0x4000; + protected static int SPI_READ_LSB_FIRST_MASK = 0x8000; /** *

Constructor for PiGpioSpi.

@@ -119,6 +121,21 @@ public PiGpioSpi(PiGpio piGpio, SpiProvider provider, SpiConfig config) { throw new IOException("Unsupported SPI mode on AUX SPI BUS_1: mode=" + mode.toString()); } + if(config.writeLsbFirstUserProvided()) { // user provided, overwrite flags + if (config().getWriteLsbFirst() == 0) { + flags = (flags | (0xFFFFFFFF ^ SPI_WRITE_LSB_FIRST_MASK)); // clear bit + }else { + flags = (flags | (0xFFFFFFFF ^ SPI_WRITE_LSB_FIRST_MASK)) |SPI_WRITE_LSB_FIRST_MASK; // clear AUX bit + } + } + + if(config.readLsbFirstUserProvided()) { // user provided, overwrite flags + if (config().getReadLsbFirst() == 0) { + flags = (flags | (0xFFFFFFFF ^ SPI_READ_LSB_FIRST_MASK)); // clear bit + }else { + flags = (flags | (0xFFFFFFFF ^ SPI_READ_LSB_FIRST_MASK)) |SPI_READ_LSB_FIRST_MASK; // clear AUX bit + } + } if(config.busUserProvided()) { // user provided, overwrite flags // update flags value with BUS bit ('A' 0x0000=BUS0; 0x0100=BUS1) From bcb735a57a8d89869008eb3e2ec7fcd1ebed2ba4 Mon Sep 17 00:00:00 2001 From: Tom Aarts Date: Mon, 13 Jan 2025 21:55:40 -0600 Subject: [PATCH 4/4] remove mention of JNA library --- .../main/java/com/pi4j/plugin/linuxfs/internal/LinuxLibC.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/plugins/pi4j-plugin-linuxfs/src/main/java/com/pi4j/plugin/linuxfs/internal/LinuxLibC.java b/plugins/pi4j-plugin-linuxfs/src/main/java/com/pi4j/plugin/linuxfs/internal/LinuxLibC.java index e57f23d8..fc246a52 100644 --- a/plugins/pi4j-plugin-linuxfs/src/main/java/com/pi4j/plugin/linuxfs/internal/LinuxLibC.java +++ b/plugins/pi4j-plugin-linuxfs/src/main/java/com/pi4j/plugin/linuxfs/internal/LinuxLibC.java @@ -40,10 +40,6 @@ */ public interface LinuxLibC extends Library { - // This class could extend c.s.j.platform.linux.LibC, but we're not using any - // of that functionality right now so we can avoid the jna-platform dependency - // until we need it. - LinuxLibC INSTANCE = LinuxLibC.LibLoader.load(); class LibLoader {