# HG changeset patch # User Paul Boddie # Date 1697401465 -7200 # Node ID 494ab106b785fc216c495518e43b5f413e7e7d1a # Parent c032280871f30b43a171db4c7dced780740f305c Added GPIO-based SPI driver support. diff -r c032280871f3 -r 494ab106b785 pkg/devices/Control --- a/pkg/devices/Control Sat Oct 14 22:02:07 2023 +0200 +++ b/pkg/devices/Control Sun Oct 15 22:24:25 2023 +0200 @@ -39,5 +39,6 @@ provides: libdrivers-panel-loader provides: libdrivers-panel-qi_lb60 provides: libdrivers-pwm +provides: libdrivers-spi requires: libc libc_be_l4re libdl l4re_c libio libipc Maintainer: paul@boddie.org.uk diff -r c032280871f3 -r 494ab106b785 pkg/devices/lib/Makefile --- a/pkg/devices/lib/Makefile Sat Oct 14 22:02:07 2023 +0200 +++ b/pkg/devices/lib/Makefile Sun Oct 15 22:24:25 2023 +0200 @@ -1,7 +1,7 @@ PKGDIR ?= .. L4DIR ?= $(PKGDIR)/../.. -TARGET := common cpm dma gpio hdmi i2c keypad lcd panel pwm +TARGET := common cpm dma gpio hdmi i2c keypad lcd panel pwm spi include $(L4DIR)/mk/subdir.mk @@ -14,3 +14,4 @@ lcd: common panel: lcd pwm: common +spi: common diff -r c032280871f3 -r 494ab106b785 pkg/devices/lib/spi/Makefile --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pkg/devices/lib/spi/Makefile Sun Oct 15 22:24:25 2023 +0200 @@ -0,0 +1,8 @@ +PKGDIR ?= ../.. +L4DIR ?= $(PKGDIR)/../.. + +TARGET := include src + +include $(L4DIR)/mk/subdir.mk + +src: include diff -r c032280871f3 -r 494ab106b785 pkg/devices/lib/spi/include/Makefile --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pkg/devices/lib/spi/include/Makefile Sun Oct 15 22:24:25 2023 +0200 @@ -0,0 +1,4 @@ +PKGDIR = ../../.. +L4DIR ?= $(PKGDIR)/../.. + +include $(L4DIR)/mk/include.mk diff -r c032280871f3 -r 494ab106b785 pkg/devices/lib/spi/include/spi-gpio.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pkg/devices/lib/spi/include/spi-gpio.h Sun Oct 15 22:24:25 2023 +0200 @@ -0,0 +1,69 @@ +/* + * Perform SPI communication using GPIO operations. + * + * Copyright (C) 2018, 2020, 2023 Paul Boddie + * + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA + */ + +#include +#include + +#pragma once + + + +#ifdef __cplusplus + +/* SPI peripheral device. */ + +class Spi_gpio +{ + Hw::Gpio_chip *_clock_device; + int _clock_pin; + Hw::Gpio_chip *_data_device; + int _data_pin; + Hw::Gpio_chip *_enable_device; + int _enable_pin; + uint32_t _frequency; + +public: + /* Associate the device with a particular memory region. */ + + explicit Spi_gpio(Hw::Gpio_chip *clock_device, int clock_pin, + Hw::Gpio_chip *data_device, int data_pin, + Hw::Gpio_chip *enable_device, int enable_pin, + uint32_t frequency = 0); + + void send(int bytes, uint8_t data[]); +}; + +#endif /* __cplusplus */ + + + +/* C language interface. */ + +EXTERN_C_BEGIN + +void *spi_gpio_get_channel(void *clock_chip, int clock_pin, + void *data_chip, int data_pin, + void *enable_chip, int enable_pin, + uint32_t frequency); + +void spi_gpio_send(void *channel, int bytes, uint8_t data[]); + +EXTERN_C_END diff -r c032280871f3 -r 494ab106b785 pkg/devices/lib/spi/src/Makefile --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pkg/devices/lib/spi/src/Makefile Sun Oct 15 22:24:25 2023 +0200 @@ -0,0 +1,13 @@ +PKGDIR ?= ../../.. +L4DIR ?= $(PKGDIR)/../.. + +TARGET = libspi.o.a libspi.o.so +PC_FILENAME := libdrivers-spi + +SRC_CC := gpio.cc + +PRIVATE_INCDIR += $(PKGDIR)/lib/spi/include + +REQUIRES_LIBS := l4re_c l4re_c-util libdrivers-common libdrivers-gpio + +include $(L4DIR)/mk/lib.mk diff -r c032280871f3 -r 494ab106b785 pkg/devices/lib/spi/src/gpio.cc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pkg/devices/lib/spi/src/gpio.cc Sun Oct 15 22:24:25 2023 +0200 @@ -0,0 +1,116 @@ +/* + * Perform SPI communication using GPIO operations. + * + * Copyright (C) 2018, 2020, 2023 Paul Boddie + * + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA + */ + +#include +#include + + + +Spi_gpio::Spi_gpio(Hw::Gpio_chip *clock_device, int clock_pin, + Hw::Gpio_chip *data_device, int data_pin, + Hw::Gpio_chip *enable_device, int enable_pin, + uint32_t frequency) +: _clock_device(clock_device), + _clock_pin(clock_pin), + _data_device(data_device), + _data_pin(data_pin), + _enable_device(enable_device), + _enable_pin(enable_pin), + _frequency(frequency) +{ + _clock_device->setup(_clock_pin, Hw::Gpio_chip::Output, 1); + _data_device->setup(_data_pin, Hw::Gpio_chip::Output, 0); + _enable_device->setup(_enable_pin, Hw::Gpio_chip::Output, 1); +} + +/* Send a SPI command. */ + +void Spi_gpio::send(int bytes, uint8_t data[]) +{ + struct timespec ts; + uint8_t mask; + int bit, byte; + + if (_frequency) + { + ts.tv_sec = 0; + ts.tv_nsec = 1000000000 / _frequency; + } + + /* Initialise pin levels. */ + + _enable_device->set(_enable_pin, 1); + _clock_device->set(_clock_pin, 1); + _data_device->set(_data_pin, 0); + + /* Enter the transmission state. */ + + _enable_device->set(_enable_pin, 0); + + /* Clock data using the clock and data outputs. */ + + for (byte = 0; byte < bytes; byte++) + { + mask = 0x80; + + for (bit = 0; bit < 8; bit++) + { + /* NOTE: Data presented on falling clock level and sampled on rising clock + level. This is SPI mode 3, or 0 given that the enable level is + driven low immediately before the first bit is presented. */ + + _clock_device->set(_clock_pin, 0); + _data_device->set(_data_pin, data[byte] & mask ? 1 : 0); + + if (_frequency) + nanosleep(&ts, NULL); + + _clock_device->set(_clock_pin, 1); + + if (_frequency) + nanosleep(&ts, NULL); + + mask >>= 1; + } + } + + _enable_device->set(_enable_pin, 1); +} + + + +/* C language interface. */ + +void *spi_gpio_get_channel(void *clock_chip, int clock_pin, + void *data_chip, int data_pin, + void *enable_chip, int enable_pin, + uint32_t frequency) +{ + return (void *) new Spi_gpio(reinterpret_cast(clock_chip), clock_pin, + reinterpret_cast(data_chip), data_pin, + reinterpret_cast(enable_chip), enable_pin, + frequency); +} + +void spi_gpio_send(void *channel, int bytes, uint8_t data[]) +{ + static_cast(channel)->send(bytes, data); +} diff -r c032280871f3 -r 494ab106b785 pkg/devices/spi/src/jz4740/Makefile --- a/pkg/devices/spi/src/jz4740/Makefile Sat Oct 14 22:02:07 2023 +0200 +++ b/pkg/devices/spi/src/jz4740/Makefile Sun Oct 15 22:24:25 2023 +0200 @@ -27,7 +27,7 @@ SRC_CC = $(SERVER_INTERFACES_SRC_CC) $(PLAIN_SRC_CC) -REQUIRES_LIBS = l4re_c l4re_c-util libdevice-util libdrivers-gpio libipc +REQUIRES_LIBS = l4re_c l4re_c-util libdevice-util libdrivers-gpio libdrivers-spi libipc PRIVATE_INCDIR = $(PKGDIR)/spi/include $(PKGDIR)/util/include \ $(IDL_BUILD_DIR) $(IDL_EXPORT_DIR) diff -r c032280871f3 -r 494ab106b785 pkg/devices/spi/src/jz4740/spi-jz4740.cc --- a/pkg/devices/spi/src/jz4740/spi-jz4740.cc Sat Oct 14 22:02:07 2023 +0200 +++ b/pkg/devices/spi/src/jz4740/spi-jz4740.cc Sun Oct 15 22:24:25 2023 +0200 @@ -20,6 +20,7 @@ */ #include +#include #include #include @@ -54,20 +55,13 @@ class SPI_server : public SPI { - Gpio_jz4740_chip *_clock_device = 0, *_data_device = 0, *_enable_device = 0; - int _clock_pin, _data_pin, _enable_pin; + Spi_gpio *_spi; public: /* Associate the device with a particular memory region. */ - explicit SPI_server(Gpio_jz4740_chip *clock_device, - Gpio_jz4740_chip *data_device, - Gpio_jz4740_chip *enable_device, - int clock_pin, int data_pin, int enable_pin) - : _clock_device(clock_device), - _data_device(data_device), - _enable_device(enable_device), - _clock_pin(clock_pin), _data_pin(data_pin), _enable_pin(enable_pin) + explicit SPI_server(Spi_gpio *spi) + : _spi(spi) { } @@ -75,31 +69,15 @@ long send(int bits, int data) { - uint32_t mask = 1 << (bits - 1); - int bit; - - /* Initialise pin levels. */ + int bytes = (bits + 7) / 8; + uint8_t buffer[bytes]; - _enable_device->set(_enable_pin, 1); - _clock_device->set(_clock_pin, 1); - _data_device->set(_data_pin, 0); - - /* Enter the transmission state. */ + /* Convert the data into a sequence of bytes. */ - _enable_device->set(_enable_pin, 0); - - /* Clock data using the clock and data outputs. */ - - for (bit = 0; bit < bits; bit++) - { - _clock_device->set(_clock_pin, 0); - _data_device->set(_data_pin, data & mask ? 1 : 0); - _clock_device->set(_clock_pin, 1); - mask >>= 1; - } + for (int byte = bytes; byte > 0; byte--) + buffer[byte - 1] = (data >> ((byte - 1) * 8)) & 0xff; - _enable_device->set(_enable_pin, 1); - + _spi->send(bytes, buffer); return L4_EOK; } }; @@ -182,10 +160,14 @@ gpio_port_enable.setup(enable_pin, Hw::Gpio_chip::Output, 0); + /* Create an object for SPI communication. */ + + Spi_gpio spi(&gpio_port_clock, clock_pin, &gpio_port_data, data_pin, + &gpio_port_enable, enable_pin); + /* Initialise and register a new server object. */ - SPI_server obj(&gpio_port_clock, &gpio_port_data, &gpio_port_enable, - clock_pin, data_pin, enable_pin); + SPI_server obj(&spi); /* Bind and start the IPC server loop. */