On 06/04/2017 06:15 AM, john mcintyre wrote:
> Your saga continues, you must be related to a bulldog😊 ,once locked
> on  it cannot let go.

Isn't this what you call a challenge?


Anyway, I've fixed a couple of things, like endianness, which I did
wrong in the previous versions. This implementation is hard-coded to use
the RPI hardware peripherals. Also, you can now attach to SPI0 or SPI1
ports on the 40-pin header and you may choose any CE line you wish
(updated man page also attached).

My tests so far have seen stable results, timing wise, although I cannot
test functionally on a real system (lacking a 7i90 in my inventory).
Though, the timing has improved with respect to the original version.

Read and write SPI clock frequencies may be different now. This will
help to overcome the SPI round-trip delay while reading (just lower the
read frequency setting), or when you want buffers on the SPI lines.

So, if anybody wants to have a go and test this, please do so and tell
me how it goes. In the meantime... need to get my hands on a 7i90 (and
upgrade the mill at the local hackerspace).

-- 
Greetings Bertho

(disclaimers are disclaimed)
/*    This is a component for RaspberryPi to hostmot2 over SPI for linuxcnc.
 *    Copyright 2016 Matsche <tin...@play-pla.net>
 *    Copyright 2017 B.Stultiens <l...@vagrearg.org>
 *
 *    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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

/* Without Source Tree */
#undef WOST

#include <ctype.h>
#include <fcntl.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/mman.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/fsuid.h>

#include "hal.h"
#include "rtapi.h"
#include "rtapi_app.h"

#include "rtapi_stdint.h"
#include "rtapi_bool.h"
#include "rtapi_gfp.h"
#include "rtapi_slab.h"

#include "rtapi_bool.h"

#if defined (WOST)
#include "include/hostmot2-lowlevel.h"
#include "include/hostmot2.h"
#else
#include "hostmot2-lowlevel.h"
#include "hostmot2.h"
#endif

#include "spi_common_rpspi.h"

//#define RPSPI_DEBUG		1	// Uncomment for debugging
#define RPSPI_DEBUG_WRITE	1	// Debug write command before cookie probe

#if defined(RPSPI_DEBUG)
#define RPSPI_DEBUG_PIN		23	// Debug timing on GPIO 23
#endif

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Matsche");
MODULE_DESCRIPTION("Driver for HostMot2 devices connected via SPI to RaspberryPi");
MODULE_SUPPORTED_DEVICE("Mesa-AnythingIO-7i90");

#define MAX_BOARDS	5
#define MAX_MSG		512		// FIXME: The 7i90 docs say that the max. burstlen == 127 (i.e. cmd+message <= 1+127)

// GPIO pin definitions
#define SPI0_PIN_CE_1	7
#define SPI0_PIN_CE_0	8
#define SPI0_PIN_MISO	9
#define SPI0_PIN_MOSI	10
#define SPI0_PIN_SCLK	11
#define SPI1_PIN_CE_2	16		// AUX SPI0 in docs
#define SPI1_PIN_CE_1	17
#define SPI1_PIN_CE_0	18
#define SPI1_PIN_MISO	19
#define SPI1_PIN_MOSI	20
#define SPI1_PIN_SCLK	21

typedef struct hm2_rpspi_struct {
	hm2_lowlevel_io_t llio;		// Upstream container
	int		nr;		// Board number
	uint32_t	spiclkratew;	// SPI write clock for divider calculation
	uint32_t	spiclkrater;	// SPI read clock for divider calculation
	uint32_t	spiclkbase;	// SPI base clock for divider calculation
	uint32_t	spice;		// Chip enable mask for this board
	int		spidevid;	// The SPI device id [01]
	int		spiceid;	// The SPI CE id [012]
} hm2_rpspi_t;

typedef enum {
	RPI_UNSUPPORTED,
	RPI_1,		// Version 1
	RPI_2		// Version 2 and 3
} platform_t;

static uint32_t *peripheralmem = (uint32_t *)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
static platform_t platform;					// The RPI version

static hm2_rpspi_t boards[MAX_BOARDS];	// Connected boards
static int comp_id;			// Upstream assigned component ID

/*
 * Configuration parameters
 */
static char *config[MAX_BOARDS];
RTAPI_MP_ARRAY_STRING(config, MAX_BOARDS, "config string for the AnyIO boards (see hostmot2(9) manpage)")

/*
 * RPI3 NOTE:
 * The SPI frequency is wildly variable when the ondemand cpufreq governor is
 * active. This may result in changing SPI frequencies depending on the system
 * load. You must set the performance governor for stable frequency. To set a
 * stable 1.2GHz core frequency, across all cores, put something like this in
 * /etc/rc.local:
 *   echo -n 1200000 > /sys/devices/system/cpu/cpufreq/policy0/scaling_min_freq
 *   echo -n performance > /sys/devices/system/cpu/cpufreq/policy0/scaling_governor
 *
 * We've seen both 400MHz and 250MHz base rates. It is a bit unclear how this
 * gets switched, and /what/ is responsible for switching. The kernel is doing
 * the actual work, but under which conditions is unknown at this moment.
 * A suggestion may be power supply issues, where the controlling PLL is thrown
 * off a bit if there is too much noise on the 5V supply. This can be countered
 * by adding a low-ESR decoupling to 5V pins on 40-pin I/O header.
 *
 * The documentation is a bit sparse, but the clock seems to follow the VPU
 * rate as a subordinate of PLLC. The peripheral documentation states that the
 * APB clock is used, but that is set at 126MHz, which does not result in the
 * seen frequencies at different divider settings.
 *
 * Using 400MHz as the "safe" value is better than 250MHz. Better to get a
 * slower SPI rate than one too fast, which would result in communication
 * problems all around. If we assume a base-clock of 400MHz, derive the
 * divider from there, and the clock flips between 400MHz and 250MHz, then we
 * still should be ok. The current setup works just fine by getting the value
 * at program start. It simply means that we sometimes are at only 62.5% of the
 * speed we actually want.
 *
 * We should get the /actual/ peripheral clock setting before calculating the
 * divider for SPI transfers. However, how do we get the peripheral clock from
 * userland in an effective way? Reading a file, as is done now, is just too
 * darn slow.
 *
 * See also read_spiclkbase() below.
 */
static int spiclk_rate = 31250;
static int spiclk_rate_rd = -1;
RTAPI_MP_INT(spiclk_rate, "SPI clock rate in kHz (default 31250 kHz, slowest 6 kHz)")
RTAPI_MP_INT(spiclk_rate_rd, "SPI clock rate for reading in kHz (default same as spiclk_rate)")

/*
 * Override the "safe" base frequency of the SPI peripheral.
 * The clock speed is normally read from /sys/kernel/debug/clk/vpu/clk_rate and
 * should give the correct current clock speed.
 */
#define F_PERI	400000000UL
static int spiclk_base = F_PERI;
RTAPI_MP_INT(spiclk_base, "SPI clock base rate in Hz (default 400000000 Hz)")

/*
 * Enable/disable pullup/pulldown on the SPI pins
 */
#define SPI_PULL_OFF	0	// == GPIO_GPPUD_OFF
#define SPI_PULL_DOWN	1	// == GPIO_GPPUD_PULLDOWN
#define SPI_PULL_UP	2	// == GPIO_GPPUD_PULLUP
static int spi_pull_miso = SPI_PULL_DOWN;
static int spi_pull_mosi = SPI_PULL_DOWN;
static int spi_pull_sclk = SPI_PULL_DOWN;
RTAPI_MP_INT(spi_pull_miso, "Enable/disable pull-{up,down} on SPI MISO (default pulldown, 0=off, 1=pulldown, 2=pullup)")
RTAPI_MP_INT(spi_pull_mosi, "Enable/disable pull-{up,down} on SPI MOSI (default pulldown, 0=off, 1=pulldown, 2=pullup)")
RTAPI_MP_INT(spi_pull_sclk, "Enable/disable pull-{up,down} on SPI SCLK (default pulldown, 0=off, 1=pulldown, 2=pullup)")

/*
 * Select which SPI channel(s) to probe. There are two SPI interfaces exposed
 * on the 40-pin I/O header, SPI0 and SPI1. SPI0 has two chip selects and SPI1
 * has three chip selects.
 *
 * GPIO pin setup:
 *      | MOSI | MISO | SCLK | CE0 | CE1 | CE2
 * -----+------+------+------+-----+-----+-----
 * SPI0 |  10  |   9  |  11  |   8 |   7 |
 * SPI1 |  20  |  19  |  21  |  18 |  17 |  16
 *
 * 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
 */
#define SPI0_PROBE_CE0	(1 << 0)
#define SPI0_PROBE_CE1	(1 << 1)
#define SPI0_PROBE_MASK	(SPI0_PROBE_CE0 | SPI0_PROBE_CE1)
#define SPI1_PROBE_CE0	(1 << 2)
#define SPI1_PROBE_CE1	(1 << 3)
#define SPI1_PROBE_CE2	(1 << 4)
#define SPI1_PROBE_MASK	(SPI1_PROBE_CE0 | SPI1_PROBE_CE1 | SPI1_PROBE_CE2)
static int spi_probe = SPI0_PROBE_CE0;
RTAPI_MP_INT(spi_probe, "Bit-field to select which SPI/CE combinations to probe (default 1 (SPI0/CE0))")

// Redirect message class for easy and noisy debugging purpose
#if defined(RPSPI_DEBUG)
#define MSG_ERR		RTAPI_MSG_ERR
#define MSG_INFO	RTAPI_MSG_ERR
#else
#define MSG_ERR		RTAPI_MSG_ERR
#define MSG_INFO	RTAPI_MSG_INFO
#endif

/*********************************************************************/
/*
 * 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)

static inline uint32_t reg_rd(const volatile void *addr)
{
	uint32_t val;
	val = *(volatile uint32_t *)addr;
	rmb();
	return val;
}

static inline void reg_wr(const volatile void *addr, uint32_t val)
{
	wmb();
	*(volatile uint32_t *)addr = val;
}

/*********************************************************************/
#if defined(RPSPI_DEBUG_PIN)
/*
 * Set/Clear a GPIO pin
 */
static inline void gpio_set(uint32_t pin)
{
	if(pin <= 53) {	/* There are 54 GPIOs */
		reg_wr(&gpio->gpset[pin / 32], 1 << (pin % 32));
	}
}

static inline void gpio_clr(uint32_t pin)
{
	if(pin <= 53) {	/* There are 54 GPIOs */
		reg_wr(&gpio->gpclr[pin / 32], 1 << (pin % 32));
	}
}
#endif

/*********************************************************************/
#define CMD_7I90_READ		(0x0a << 12)
#define CMD_7I90_WRITE		(0x0b << 12)
#define CMD_7I90_ADDRINC	(1 << 11)
// aib (address increment bit)
static inline uint32_t mk_read_cmd(uint32_t addr, uint32_t msglen, bool aib)
{
	return (addr << 16) | CMD_7I90_READ | (aib ? CMD_7I90_ADDRINC : 0) | (msglen << 4);
}

static inline uint32_t mk_write_cmd(uint32_t addr, uint32_t msglen, bool aib)
{
	return (addr << 16) | CMD_7I90_WRITE | (aib ? CMD_7I90_ADDRINC : 0) | (msglen << 4);
}

/*********************************************************************/
static inline int32_t spi1_clkdiv_calc(uint32_t base, uint32_t rate)
{
	uint32_t clkdiv;
	if(rate >= base / 2)
		return 0;
	clkdiv = (base + rate - 1) / ((rate-1) * 2) - 1;	// Rounded up (towards slower clock)
	if(clkdiv > 4095)
		clkdiv = 4095;		// Slowest possible
	return clkdiv;
}

/*
 * Initialize/reset the SPI peripheral to inactive state and flushed
 */
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);
}

/*
 * HM2 interface: Write buffer to SPI1
 */
static int hm2_rpspi_write_spi1(hm2_lowlevel_io_t *llio, uint32_t addr, void *buffer, int size)
{
	hm2_rpspi_t *hm2 = (hm2_rpspi_t *)llio;
	uint32_t cmd;
	int txlen;
	int rxlen;
	uint16_t *bptr;
	uint32_t *wptr;

	if(size == 0)
		return 0;
	if((size % 4) || size / 4 >= MAX_MSG)
		return -EINVAL;

#if defined(RPSPI_DEBUG_PIN)
	gpio_clr(RPSPI_DEBUG_PIN);
#endif

	cmd = htobe32(mk_write_cmd(addr, size / 4, true));			// Setup write command

	// Set 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(spi1_clkdiv_calc(hm2->spiclkbase, hm2->spiclkratew))
				| AUX_SPI_CNTL0_ENABLE
				| AUX_SPI_CNTL0_MSB_OUT
				| AUX_SPI_CNTL0_OUT_RISING
				| AUX_SPI_CNTL0_SHIFT_LENGTH(16)
				| hm2->spice);
	reg_wr(&aux->spi0_cntl1, AUX_SPI_CNTL1_MSB_IN);

	// Send the command. The fifo is big enough to hold the data
	txlen = 2;			// Command is two 16bit words
	bptr = (uint16_t *)&cmd;	// The command
	while(txlen) {
		uint32_t stat = reg_rd(&aux->spi0_stat);
		if(txlen && !(stat & AUX_SPI_STAT_TX_FULL)) {
			reg_wr(&aux->spi0_hold, (uint32_t)htobe16(*bptr++) << 15);	// More data to follow
			--txlen;
		}
	}

	rxlen = size / 2 + 2;		// We read back anything to be discarded
	txlen = size / 2;		// Number of data words to write
	wptr = (uint32_t *)buffer;	// And get it from here
	while(rxlen) {
		uint32_t stat = reg_rd(&aux->spi0_stat);
		if(txlen && !(stat & AUX_SPI_STAT_TX_FULL)) {
			// For each 32 bit read the buffer and convert
			if(!(txlen & 1)) {
				cmd = htobe32(*wptr++);
				bptr = (uint16_t *)&cmd;
			}
			if(--txlen)
				reg_wr(&aux->spi0_hold, (uint32_t)htobe16(*bptr++) << 15);	// Write while still more data to follow
			else
				reg_wr(&aux->spi0_io, (uint32_t)htobe16(*bptr++) << 15);	// Final write (maybe reached on very short reads)
		}

		if(!(stat & AUX_SPI_STAT_RX_EMPTY)) {
			// Discard the read data
			uint32_t dummy __attribute__((unused));
			dummy = reg_rd(&aux->spi0_io);
			rxlen--;
		}
	}

	// Stop transfer
	spi1_reset();

#if defined(RPSPI_DEBUG_PIN)
	gpio_set(RPSPI_DEBUG_PIN);
#endif
	return 1;
}

/*
 * HM2 interface: Read buffer from SPI1
 */
static int hm2_rpspi_read_spi1(hm2_lowlevel_io_t *llio, uint32_t addr, void *buffer, int size)
{
	hm2_rpspi_t *hm2 = (hm2_rpspi_t *)llio;
	uint32_t cmd;
	int txlen;
	int rxlen;
	uint16_t *bptr;
	uint32_t *wptr;

	if(size == 0)
		return 0;
	if((size % 4) || size / 4 >= MAX_MSG)
		return -EINVAL;

#if defined(RPSPI_DEBUG_PIN)
	gpio_clr(RPSPI_DEBUG_PIN);
#endif

	cmd = htobe32(mk_read_cmd(addr, size / 4, true));			// Setup read command

	// Set 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(spi1_clkdiv_calc(hm2->spiclkbase, hm2->spiclkrater))
				| AUX_SPI_CNTL0_ENABLE
				| AUX_SPI_CNTL0_MSB_OUT
				| AUX_SPI_CNTL0_OUT_RISING
				| AUX_SPI_CNTL0_SHIFT_LENGTH(16)
				| hm2->spice);
	reg_wr(&aux->spi0_cntl1, AUX_SPI_CNTL1_MSB_IN);

	// Send the command. The fifo is big enough to hold the data
	txlen = 2;			// Command is two 16bit words
	bptr = (uint16_t *)&cmd;	// The command
	while(txlen) {
		uint32_t stat = reg_rd(&aux->spi0_stat);
		if(txlen && !(stat & AUX_SPI_STAT_TX_FULL)) {
			reg_wr(&aux->spi0_hold, (uint32_t)be16toh(*bptr++) << 15);	// More data to follow
			--txlen;
		}
	}

	rxlen = 2;		// We read back the command to be discarded
	txlen = size / 2;	// Number of zeroes to write for the read command
	while(rxlen) {
		uint32_t stat = reg_rd(&aux->spi0_stat);
		if(txlen && !(stat & AUX_SPI_STAT_TX_FULL)) {
			if(--txlen)
				reg_wr(&aux->spi0_hold, 0);	// Write while still more data to follow
			else
				reg_wr(&aux->spi0_io, 0);	// Final write (maybe reached on very short reads)
		}

		if(!(stat & AUX_SPI_STAT_RX_EMPTY)) {
			uint32_t dummy __attribute__((unused));
			dummy = reg_rd(&aux->spi0_io);
			rxlen--;
		}
	}

	rxlen = size / 2;		// We read the data
	wptr = (uint32_t *)buffer;	// And save it here
	bptr = (uint16_t *)&cmd;	// Temporary store for byteswap
	while(rxlen) {
		uint32_t stat = reg_rd(&aux->spi0_stat);
		if(txlen && !(stat & AUX_SPI_STAT_TX_FULL)) {
			if(--txlen)
				reg_wr(&aux->spi0_hold, 0);	// Write while still more data to follow
			else
				reg_wr(&aux->spi0_io, 0);	// Final write
		}

		if(!(stat & AUX_SPI_STAT_RX_EMPTY)) {
			*bptr++ = be16toh(reg_rd(&aux->spi0_io));
			rxlen--;
			// Read 32 bits, convert and store
			if(!(rxlen & 1)) {
				*wptr++ = be32toh(cmd);
				bptr = (uint16_t *)&cmd;
			}
		}
	}

	// Stop transfer
	spi1_reset();

#if defined(RPSPI_DEBUG_PIN)
	gpio_set(RPSPI_DEBUG_PIN);
#endif
	return 1;
}

/*********************************************************************/
static inline int32_t spi0_clkdiv_calc(uint32_t base, uint32_t rate)
{
	uint32_t clkdiv = (base + rate - 1) / rate;	// Rounded up (towards slower clock)
	if(clkdiv > 65534)
		clkdiv = 0;		// Slowest possible
	else
		clkdiv += clkdiv & 1;	// Must be multiple of 2 (round to lower frequency)
	return clkdiv;
}

/*
 * Initialize/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
}

/*
 * HM2 interface: Write buffer to SPI0
 */
static int hm2_rpspi_write_spi0(hm2_lowlevel_io_t *llio, uint32_t addr, void *buffer, int size)
{
	hm2_rpspi_t *hm2 = (hm2_rpspi_t *)llio;
	uint32_t cs;
	uint32_t cmd;
	int txlen;
	int rxlen;
	uint8_t *bptr;
	uint32_t *wptr;

	if(size == 0)
		return 0;
	if((size % 4) || size / 4 >= MAX_MSG)
		return -EINVAL;

#if defined(RPSPI_DEBUG_PIN)
	gpio_clr(RPSPI_DEBUG_PIN);
#endif

	cmd = htobe32(mk_write_cmd(addr, size / 4, true));	// Setup write command

	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 |= hm2->spice | SPI_CS_TA;				// Set proper CE_x and activate transfer
	reg_wr(&spi->clk, spi0_clkdiv_calc(hm2->spiclkbase, hm2->spiclkratew));	// Set clock divider
	reg_wr(&spi->cs, cs);					// Go!

	// Fill the TX fifo with the command
	// The fifo is large enough to hold the command and the returning RX
	// data while we write the command.
	bptr = (uint8_t *)&cmd;	// Command data
	txlen = 4;		// Command length
	while(txlen && (reg_rd(&spi->cs) & SPI_CS_TXD)) {
		reg_wr(&spi->fifo, *bptr++);
		--txlen;
	}

	// Read and write until all done
	// The read data is discarded. When the txlen reaches zero, then we
	// must keep reading until the rxlen also reaches zero to ensure that
	// all data has been written on the SPI bus.
	txlen = size;			// Buffer size to transmit
	rxlen = size + 4;		// Buffer size plus command length
	wptr = (uint32_t *)buffer;	// Actual write data
	while(rxlen) {
		cs = reg_rd(&spi->cs);
		if(cs & SPI_CS_RXD) {
			uint32_t dummy __attribute__((unused));
			dummy = reg_rd(&spi->fifo);
			--rxlen;
		}
		if(txlen && (cs & SPI_CS_TXD)) {
			// For each 32 bit do conversion to big endian
			if(!(txlen & 3)) {
				cmd = htobe32(*wptr++);
				bptr = (uint8_t *)&cmd;
			}
			reg_wr(&spi->fifo, *bptr++);
			--txlen;
		}
	}

	// We're done, there is no return data

	// Stop transfer
	spi0_reset();

#if defined(RPSPI_DEBUG_PIN)
	gpio_set(RPSPI_DEBUG_PIN);
#endif
	return 1;
}

/*
 * HM2 interface: Read buffer from SPI0
 */
static int hm2_rpspi_read_spi0(hm2_lowlevel_io_t *llio, uint32_t addr, void *buffer, int size)
{
	hm2_rpspi_t *hm2 = (hm2_rpspi_t *)llio;
	uint32_t cs;
	uint32_t cmd;
	int txlen;
	int rxlen;
	uint8_t *bptr;
	uint32_t *wptr;

	if(size == 0)
		return 0;
	if((size % 4) || size / 4 >= MAX_MSG)
		return -EINVAL;

#if defined(RPSPI_DEBUG_PIN)
	gpio_clr(RPSPI_DEBUG_PIN);
#endif

	cmd = htobe32(mk_read_cmd(addr, size / 4, true));	// Setup read command

	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 |= hm2->spice | SPI_CS_TA;				// Set proper CE_x and activate transfer
	reg_wr(&spi->clk, spi0_clkdiv_calc(hm2->spiclkbase, hm2->spiclkrater));	// Set clock divider
	reg_wr(&spi->cs, cs);					// Go!

	// Fill the TX fifo with the command
	// The fifo is large enough to hold the command and the returning RX
	// data while we write the command.
	bptr = (uint8_t *)&cmd;	// Command data
	txlen = 4;		// Command length
	while(txlen && (reg_rd(&spi->cs) & SPI_CS_TXD)) {
		reg_wr(&spi->fifo, *bptr++);
		--txlen;
	}

	// Read the command return and discard it. Simultaneously, we write
	// zeroes for as long as the read data goes.
	txlen = size;	// Now we write zeros for the read length
	rxlen = 4;	// The command read back needs to be discarded
	while(rxlen) {
		cs = reg_rd(&spi->cs);
		if(cs & SPI_CS_RXD) {
			uint32_t dummy __attribute__((unused));
			dummy = reg_rd(&spi->fifo);
			--rxlen;
		}
		if(txlen && (cs & SPI_CS_TXD)) {
			reg_wr(&spi->fifo, 0);	// Write zeroes
			--txlen;
		}
	}

	// The command (4 bytes) has been discarded. Read the rest of the data
	// into the return buffer and keep writing zeroes for as long as the
	// read requires.
	rxlen = size;	// Now we read the actual data
	wptr = (uint32_t *)buffer;
	bptr = (uint8_t *)&cmd;
	while(rxlen) {
		cs = reg_rd(&spi->cs);
		if(cs & SPI_CS_RXD) {
			*bptr++ = reg_rd(&spi->fifo);
			--rxlen;
			// Once we have 4 bytes, convert to little endian and save
			if(!(rxlen & 3)) {
				*wptr++ = be32toh(cmd);
				bptr = (uint8_t *)&cmd;
			}
		}
		if(txlen && (cs & SPI_CS_TXD)) {
			reg_wr(&spi->fifo, 0);	// Keep writing zeros
			--txlen;
		}
	}

	// The return data is now in the buffer

	// Stop transfer
	spi0_reset();

#if defined(RPSPI_DEBUG_PIN)
	gpio_set(RPSPI_DEBUG_PIN);
#endif
	return 1;
}

/*************************************************/
static int check_cookie(hm2_rpspi_t *board)
{
	const uint32_t xcookie[4] = {0x55aacafe, 0x54534f48, 0x32544f4d, 0x00000400};
	uint32_t cookie[4];
	int r = board->llio.read(&board->llio, 0x100, cookie, 16);
	if(r < 0)
		return r;

	if(memcmp(cookie, xcookie, sizeof(cookie))) {
		rtapi_print_msg(MSG_ERR, "hm2_rpspi: SPI%d/CE%d Invalid cookie, read: %08x %08x %08x %08x\n",
			board->spidevid, board->spiceid, cookie[0], cookie[1], cookie[2], cookie[3]);
		return -ENODEV;
	}
	return 0;
}

/*************************************************/
static int read_ident(hm2_rpspi_t *board, char *ident)
{
	int retval = board->llio.read(&board->llio, 0x40c, ident, 8);
	if(retval < 0)
		rtapi_print_msg(MSG_ERR, "hm2_rpspi: SPI%d/CE%d Board ident read failed\n", board->spidevid, board->spiceid);
	return retval;
}

/*************************************************/
static uint32_t read_spiclkbase(const char *sysclkref)
{
	uint32_t rate;
	int fd;
	char buf[16];
	ssize_t err;

	if((fd = rtapi_open_as_root(sysclkref, O_RDONLY)) < 0) {
		rtapi_print_msg(MSG_ERR, "hm2_rpspi: Cannot open clock setting '%s' (errno=%d), using %d Hz\n", sysclkref, errno, spiclk_base);
		return spiclk_base;
	}
	memset(buf, 0, sizeof(buf));
	if((err = read(fd, buf, sizeof(buf)-1)) < 0) {
		rtapi_print_msg(MSG_ERR, "hm2_rpspi: Cannot read clock setting '%s' (errno=%d), using %d Hz\n", sysclkref, errno, spiclk_base);
		close(fd);
		return spiclk_base;
	}

	close(fd);

	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
		rtapi_print_msg(MSG_ERR, "hm2_rpspi: Read full buffer '%s' from '%s', number probably wrong or too large, using %d Hz\n", buf, sysclkref, spiclk_base);
		rate = spiclk_base;
	}

	if(1 != sscanf(buf, "%u", &rate)) {
		rtapi_print_msg(MSG_ERR, "hm2_rpspi: Cannot interpret clock setting '%s' from '%s', using %d Hz\n", buf, sysclkref, spiclk_base);
		rate = spiclk_base;
	}
	return rate;
}

/*************************************************/
static int probe_board(hm2_rpspi_t *board) {
	int ret;
	char ident[8+1];
	char *base;

#if defined(RPSPI_DEBUG) && defined(RPSPI_DEBUG_WRITE)
	uint32_t buf[8] = {0x00010203, 0x04050607, 0x08090a0b, 0x0c0d0e0f, 0x10111213, 0x14151617, 0x18191a1b, 0x1c1d1e1f};
	board->llio.write(&board->llio, 0x5ac3, buf, sizeof(buf));
#endif

	if((ret = check_cookie(board)) < 0)
		return ret;

	if((ret = read_ident(board, ident)) < 0)
		return ret;
	ident[sizeof(ident)-1] = 0;	// Because it is may be used in printf, regardless format limits

	if(!memcmp(ident, "MESA7I90", 8)) {
		base = "hm2_7i90";
		board->llio.num_ioport_connectors = 3;
		board->llio.pins_per_connector = 24;
		board->llio.ioport_connector_name[0] = "P1";
		board->llio.ioport_connector_name[1] = "P2";
		board->llio.ioport_connector_name[2] = "P3";
		board->llio.num_leds = 2;
		board->llio.fpga_part_number = "xc6slx9tq144";
	} else {
		int i;
		for(i = 0; i < sizeof(ident) - 1; i++) {
			if(!isprint(ident[i]))
				ident[i] = '?';
		}
		rtapi_print_msg(MSG_ERR, "hm2_rpspi: Unknown board at SPI%d/CE%d: %.8s\n", board->spidevid, board->spiceid, ident);
		return -1;
	}

	rtapi_print_msg(MSG_INFO, "hm2_rpspi: SPI%d/CE%d Base: %s.%d", board->spidevid, board->spiceid, 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;

	return 0;
}

/*************************************************/
static int peripheral_map(void)
{
	int fd;
	int err;
	uint32_t membase = BCM2835_GPIO_BASE;	// We know that the GPIO peripheral is first in the map
	long pagesize = sysconf(_SC_PAGESIZE);	// Default mapped page size multiple

	// error, zero or not power of two
	if(-1 == pagesize || !pagesize || (pagesize & (pagesize-1))) {
		rtapi_print_msg(MSG_ERR, "hm2_rpspi: Pagesize is bad (%ld), assuming 4kByte\n", pagesize);
		pagesize = 4096;	// We assume this is a 12-bit (4k) page system, pretty safe
	}

	// Size is independent of the Pi we are running on
	peripheralsize = BCM2835_AUX_END - membase;	// We know that the AUX peripheral is last in the map
	peripheralsize += pagesize - 1;			// Round up to next full page
	peripheralsize &= ~(uint32_t)(pagesize - 1);	// Round up to next full page

	switch(platform) {
	case RPI_1:
		break;
	case RPI_2:	// for RPI 3 too
		membase += BCM2709_OFFSET;
		break;
	default:
		rtapi_print_msg(MSG_ERR, "hm2_rpspi: Platform not supported\n");
		return -1;
	}

	if((fd = rtapi_open_as_root("/dev/mem", O_RDWR | O_SYNC)) < 0) {
		rtapi_print_msg(MSG_ERR, "hm2_rpspi: 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, membase);
	err = errno;
	close(fd);

	if(peripheralmem == MAP_FAILED) {
		rtapi_print_msg(MSG_ERR, "hm2_rpspi: Can't map peripherals\n");
		return -err;
	}

	gpio = (bcm2835_gpio_t *)peripheralmem;
	spi  = (bcm2835_spi_t *)(peripheralmem + (BCM2835_SPI_OFFSET - BCM2835_GPIO_OFFSET) / sizeof(*peripheralmem));
	aux  = (bcm2835_aux_t *)(peripheralmem + (BCM2835_AUX_OFFSET - BCM2835_GPIO_OFFSET) / sizeof(*peripheralmem));

	rtapi_print_msg(MSG_INFO, "hm2_rpspi: Mapped peripherals from 0x%08x (size 0x%08x) to gpio:0x%p, spi:0x%p, aux:0x%p\n",
			membase, 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 inline 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 inline 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)
{
	// Do bounds check on the parameters
	// The value follows GPIO_GPPUD_xxx definitions
	if(spi_pull_miso < 0 || spi_pull_miso > 2)	spi_pull_miso = 0;
	if(spi_pull_mosi < 0 || spi_pull_mosi > 2)	spi_pull_mosi = 0;
	if(spi_pull_sclk < 0 || spi_pull_sclk > 2)	spi_pull_sclk = 0;

	// Setup SPI pins to SPI0
	if(spi_probe & SPI0_PROBE_MASK) {
		if(spi_probe & SPI0_PROBE_CE0) {
			gpio_fsel(SPI0_PIN_CE_0, GPIO_FSEL_8_SPI0_CE0_N);
			gpio_pull(SPI0_PIN_CE_0, GPIO_GPPUD_PULLUP);
		}
		if(spi_probe & SPI0_PROBE_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 on request
		gpio_pull(SPI0_PIN_MISO, spi_pull_miso);
		gpio_pull(SPI0_PIN_MOSI, spi_pull_mosi);
		gpio_pull(SPI0_PIN_SCLK, spi_pull_sclk);
		spi0_reset();
	}

	// Setup SPI pins to SPI1
	if(spi_probe & SPI1_PROBE_MASK) {
		if(spi_probe & SPI1_PROBE_CE0) {
			gpio_fsel(SPI1_PIN_CE_0, GPIO_FSEL_18_SPI1_CE0_N);
			gpio_pull(SPI1_PIN_CE_0, GPIO_GPPUD_PULLUP);
		}
		if(spi_probe & SPI1_PROBE_CE1) {
			gpio_fsel(SPI1_PIN_CE_1, GPIO_FSEL_17_SPI1_CE1_N);
			gpio_pull(SPI1_PIN_CE_1, GPIO_GPPUD_PULLUP);
		}
		if(spi_probe & SPI1_PROBE_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 on request
		gpio_pull(SPI1_PIN_MISO, spi_pull_miso);
		gpio_pull(SPI1_PIN_MOSI, spi_pull_mosi);
		gpio_pull(SPI1_PIN_SCLK, spi_pull_sclk);

		// 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_set(RPSPI_DEBUG_PIN);
#endif
}

/*************************************************/
static void peripheral_restore(void)
{
#if defined(RPSPI_DEBUG_PIN)
	gpio_set(RPSPI_DEBUG_PIN);
	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(spi_probe & SPI0_PROBE_MASK) {
		gpio_pull(SPI0_PIN_MISO, GPIO_GPPUD_PULLUP);	// MISO
		gpio_pull(SPI0_PIN_MOSI, GPIO_GPPUD_PULLUP);	// MOSI
		gpio_pull(SPI0_PIN_SCLK, GPIO_GPPUD_PULLUP);	// SCLK

		// 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(spi_probe & SPI0_PROBE_CE0)
			gpio_fsel(SPI0_PIN_CE_0, GPIO_FSEL_X_GPIO_INPUT);
		if(spi_probe & SPI0_PROBE_CE1)
			gpio_fsel(SPI0_PIN_CE_1, GPIO_FSEL_X_GPIO_INPUT);
	}

	if(spi_probe & SPI1_PROBE_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);	// MISO
		gpio_pull(SPI1_PIN_MOSI, GPIO_GPPUD_PULLUP);	// MOSI
		gpio_pull(SPI1_PIN_SCLK, GPIO_GPPUD_PULLUP);	// SCLK

		// 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(spi_probe & SPI1_PROBE_CE0)
			gpio_fsel(SPI1_PIN_CE_0, GPIO_FSEL_X_GPIO_INPUT);
		if(spi_probe & SPI1_PROBE_CE1)
			gpio_fsel(SPI1_PIN_CE_1, GPIO_FSEL_X_GPIO_INPUT);
		if(spi_probe & SPI1_PROBE_CE2)
			gpio_fsel(SPI1_PIN_CE_2, GPIO_FSEL_X_GPIO_INPUT);
	}
}

/*************************************************/
#define CPUINFO_BUFSIZE	(16*1024)	// Should be large enough for now
static platform_t check_platform(void)
{
	FILE *fp;
	char *buf;
	size_t fsize;
	platform_t rv = RPI_UNSUPPORTED;

	if(!(buf = rtapi_kmalloc(CPUINFO_BUFSIZE, GFP_KERNEL))) {
		rtapi_print_msg(MSG_ERR, "hm2_rpspi: No dynamic memory\n");
		return RPI_UNSUPPORTED;
	}
	if(!(fp = fopen("/proc/cpuinfo", "r"))) {
		rtapi_print_msg(MSG_ERR, "hm2_rpspi: Failed to open /proc/cpuinfo\n");
		goto check_exit;
	}
	fsize = fread(buf, 1, CPUINFO_BUFSIZE - 1, fp);
	fclose(fp);

	// we have truncated cpuinfo return unsupported
	if(!fsize || fsize == sizeof(buf) - 1) {
		rtapi_print_msg(MSG_ERR, "hm2_rpspi: Platform detection memory buffer too small\n");
		goto check_exit;
	}

	/* NUL terminate the buffer */
	buf[fsize] = '\0';

	if(strstr(buf, "BCM2708")) {
		rv = RPI_1;
	} else if(strstr(buf, "BCM2709")) {
		rv = RPI_2;	//for RPI 3 too
	} else if(strstr(buf, "BCM2835")) {	// starting with 4.8 kernels revision tag has board details
		char *rev_val = strstr(buf, "Revision");
		if(rev_val) {
			char *rev_start = strstr(rev_val, ": ");
			unsigned long rev = strtol(rev_start + 2, NULL, 16);

			if(rev <= 0xffff)
				rv = RPI_1; // pre pi2 revision scheme
			else {
				switch((rev & 0xf000) >> 12) {
				case 0: //bcm2835
					rv = RPI_1;
					break;
				case 1: //bcm2836
				case 2: //bcm2837
					rv = RPI_2;	// peripheral base is same on pi2/3
					break;
				default:
					break;
				}
			}
		}
	}

check_exit:
	rtapi_kfree(buf);
	return rv;
}

/*************************************************/
static inline int spi_devid(int index)
{
	return index < 2 ? 0 : 1;
}

static inline int spi_ceid(int index)
{
	return index < 2 ? index : index - 2;
}

static int hm2_rpspi_setup(void)
{
	int i, j;
	int retval = -1;

	platform = check_platform();

	if(platform == RPI_UNSUPPORTED) {
		rtapi_print_msg(MSG_ERR, "hm2_rpspi: Unsupported Platform, only Raspberry1/2/3 supported.\n");
		return -1;
	}

	if(-1 == spiclk_rate_rd)
		spiclk_rate_rd = spiclk_rate;

	if(spiclk_rate < 6 || spiclk_rate > 66000) {
		rtapi_print_msg(MSG_ERR, "hm2_rpspi: SPI clock rate '%d' too slow/fast. Must be >= 6 kHz and <= 66000 kHz\n", spiclk_rate);
		return -EINVAL;
	}

	if(spiclk_rate_rd < 6 || spiclk_rate_rd > 66000) {
		rtapi_print_msg(MSG_ERR, "hm2_rpspi: SPI clock rate for reading '%d' too slow/fast. Must be >= 6 kHz and <= 66000 kHz\n", spiclk_rate_rd);
		return -EINVAL;
	}

	if((retval = peripheral_map()) < 0) {
		rtapi_print_msg(MSG_ERR, "hm2_rpspi: cannot map peripheral memory.\n");
		return retval;
	}

	peripheral_setup();

	memset(boards, 0, sizeof(boards));

	for(j = i = 0; i < MAX_BOARDS; i++) {
		int iddev = spi_devid(i);
		int idce = spi_ceid(i);
		uint32_t clkdiv;
		uint32_t clkratew;
		uint32_t clkrater;
		uint32_t clkbase;

		if(!(spi_probe & (1 << i)))		// Only probe if enabled
			continue;

		clkratew = spiclk_rate * 1000;		// Command-line specifies in kHz
		clkrater = spiclk_rate_rd * 1000;	// Command-line specifies in kHz
		clkbase  = read_spiclkbase("/sys/kernel/debug/clk/vpu/clk_rate");

		boards[j].nr = j;
		boards[j].spiclkratew = clkratew;
		boards[j].spiclkrater = clkrater;
		boards[j].spiclkbase  = clkbase;
		boards[j].spidevid    = iddev;
		boards[j].spiceid     = idce;

		rtapi_print_msg(MSG_INFO, "hm2_rpspi: SPI%d/CE%d clock rate: %d/%d Hz, VPU clock rate: %u Hz\n",
				iddev, idce, clkratew, clkrater, clkbase);
		if(!iddev) {
			switch(idce) {
			case 0: boards[j].spice = 0; break;			// Set SPI0 CE_0
			case 1: boards[j].spice = SPI_CS_CS_01; break;		// Set SPI0 CE_1
			}
			boards[j].llio.read  = hm2_rpspi_read_spi0;
			boards[j].llio.write = hm2_rpspi_write_spi0;
			if(!(clkdiv = spi0_clkdiv_calc(clkbase, clkratew)))
				clkdiv = 65536;
			rtapi_print_msg(MSG_INFO, "hm2_rpspi: SPI%d/CE%d write clock rate calculated: %d Hz\n", iddev, idce, clkbase / clkdiv);
			if(!(clkdiv = spi0_clkdiv_calc(clkbase, clkrater)))
				clkdiv = 65536;
			rtapi_print_msg(MSG_INFO, "hm2_rpspi: SPI%d/CE%d read clock rate calculated: %d Hz\n", iddev, idce, clkbase / clkdiv);
		} else {
			switch(idce) {
			case 0: boards[j].spice = AUX_SPI_CNTL0_CS_1 | AUX_SPI_CNTL0_CS_2; break;	// Set SPI1 CE_0
			case 1: boards[j].spice = AUX_SPI_CNTL0_CS_0 | AUX_SPI_CNTL0_CS_2; break;	// Set SPI1 CE_1
			case 2: boards[j].spice = AUX_SPI_CNTL0_CS_0 | AUX_SPI_CNTL0_CS_1; break;	// Set SPI1 CE_2
			}
			boards[j].llio.read  = hm2_rpspi_read_spi1;
			boards[j].llio.write = hm2_rpspi_write_spi1;
			clkdiv = 2 * (spi1_clkdiv_calc(clkbase, clkratew) + 1);
			rtapi_print_msg(MSG_INFO, "hm2_rpspi: SPI%d/CE%d write clock rate calculated: %d Hz\n", iddev, idce, clkbase / clkdiv);
			clkdiv = 2 * (spi1_clkdiv_calc(clkbase, clkrater) + 1);
			rtapi_print_msg(MSG_INFO, "hm2_rpspi: SPI%d/CE%d read clock rate calculated: %d Hz\n", iddev, idce, clkbase / clkdiv);
		}

		if((retval = probe_board(&boards[j])) < 0) {
			return retval;
		}

		if((retval = hm2_register(&boards[i].llio, config[j])) < 0) {
			rtapi_print_msg(MSG_ERR, "hm2_rpspi: hm2_register() failed for SPI%d/CE%d.\n", iddev, idce);
			return retval;
		}
	}

	return j > 0 ? 0 : -ENODEV;
}

/*************************************************/
static void hm2_rpspi_cleanup(void)
{
	if((void *)peripheralmem != MAP_FAILED) {
		peripheral_restore();
		munmap(peripheralmem, peripheralsize);
	}
}

/*************************************************/
int rtapi_app_main()
{
	int ret;

	if((comp_id = ret = hal_init("hm2_rpspi")) < 0)
		goto fail;

	if((ret = hm2_rpspi_setup()) < 0)
		goto fail;

	hal_ready(comp_id);
	return 0;

fail:
	hm2_rpspi_cleanup();
	return ret;
}

/*************************************************/
void rtapi_app_exit(void)
{
	hm2_rpspi_cleanup();
	hal_exit(comp_id);
}
/*   This is a component  for RaspberryPi to FPGA over SPI for linuxcnc.
 *    Copyright 2013 Matsche <tin...@play-pla.net>
 *                               based on GP Orcullo's picnc driver and
 *				 based on the pluto_common.h from Jeff Epler <jep...@unpythonic.net>
 *    Copyright 2017 B.Stultiens <l...@vagrearg.org>
 *
 *
// 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 */

#ifndef HAL_RPSPI_H
#define HAL_RPSPI_H

/*
 * Broadcom defines
 *
 * Peripheral description see:
 * - https://www.raspberrypi.org/documentation/hardware/raspberrypi/bcm2835/BCM2835-ARM-Peripherals.pdf
 * - http://elinux.org/RPi_BCM2835_GPIOs
 * - http://elinux.org/BCM2835_datasheet_errata
 */

#define BCM2835_PERI_BASE	0x20000000
#define BCM2835_GPIO_OFFSET	0x200000
#define BCM2835_SPI_OFFSET	0x204000
#define BCM2835_AUX_OFFSET	0x215000

#define BCM2835_GPIO_BASE	(BCM2835_PERI_BASE + BCM2835_GPIO_OFFSET)	/* GPIO controller */
#define BCM2835_SPI_BASE	(BCM2835_PERI_BASE + BCM2835_SPI_OFFSET)	/* SPI controller */
#define BCM2835_AUX_BASE	(BCM2835_PERI_BASE + BCM2835_AUX_OFFSET)	/* AUX controller */
#define BCM2835_GPIO_END	(BCM2835_GPIO_BASE + sizeof(bcm2835_gpio_t))
#define BCM2835_SPI_END		(BCM2835_SPI_BASE + sizeof(bcm2835_spi_t))
#define BCM2835_AUX_END		(BCM2835_AUX_BASE + sizeof(bcm2835_aux_t))

#define BCM2709_OFFSET		0x1F000000	// For RPI 2 and 3

#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
 */
#define GPIO_FSEL_X_GPIO_INPUT		0	/* All pin's function 0 is input */
#define GPIO_FSEL_X_GPIO_OUTPUT		1	/* All pin's function 1 is output */

#define GPIO_FSEL_0_SDA0		4
#define GPIO_FSEL_0_SA5			5
#define GPIO_FSEL_0_PCLK		6
#define GPIO_FSEL_0_SDA0		4
#define GPIO_FSEL_0_SA5			5
#define GPIO_FSEL_0_PCLK		6
#define GPIO_FSEL_1_SCL0		4
#define GPIO_FSEL_1_SA4			5
#define GPIO_FSEL_1_DE			6
#define GPIO_FSEL_2_SDA1		4
#define GPIO_FSEL_2_SA3			5
#define GPIO_FSEL_2_LCD_VSYNC		6
#define GPIO_FSEL_3_SCL1		4
#define GPIO_FSEL_3_SA2			5
#define GPIO_FSEL_3_LCD_HSYNC		6
#define GPIO_FSEL_4_GPCLK0		4
#define GPIO_FSEL_4_SA1			5
#define GPIO_FSEL_4_DPI_D0		6
#define GPIO_FSEL_4_ARM_TDI		2
#define GPIO_FSEL_5_GPCLK1		4
#define GPIO_FSEL_5_SA0			5
#define GPIO_FSEL_5_DPI_D1		6
#define GPIO_FSEL_5_ARM_TDO		2
#define GPIO_FSEL_6_GPCLK2		4
#define GPIO_FSEL_6_SOE_N		5
#define GPIO_FSEL_6_SE			5
#define GPIO_FSEL_6_DPI_D2		6
#define GPIO_FSEL_6_ARM_RTCK		2
#define GPIO_FSEL_7_SPI0_CE1_N		4
#define GPIO_FSEL_7_SWE_N		5
#define GPIO_FSEL_7_SRW_N		5
#define GPIO_FSEL_7_DPI_D3		6
#define GPIO_FSEL_8_SPI0_CE0_N		4
#define GPIO_FSEL_8_SD0			5
#define GPIO_FSEL_8_DPI_D4		6
#define GPIO_FSEL_9_SPI0_MISO		4
#define GPIO_FSEL_9_SD1			5
#define GPIO_FSEL_9_DPI_D5		6
#define GPIO_FSEL_10_SPI0_MOSI		4
#define GPIO_FSEL_10_SD2		5
#define GPIO_FSEL_10_DPI_D6		6
#define GPIO_FSEL_11_SPI0_SCLK		4
#define GPIO_FSEL_11_SD3		5
#define GPIO_FSEL_11_DPI_D7		6
#define GPIO_FSEL_12_PWM0		4
#define GPIO_FSEL_12_SD4		5
#define GPIO_FSEL_12_DPI_D8		6
#define GPIO_FSEL_12_ARM_TMS		2
#define GPIO_FSEL_13_PWM1		4
#define GPIO_FSEL_13_SD5		5
#define GPIO_FSEL_13_DPI_D9		6
#define GPIO_FSEL_13_ARM_TCK		2
#define GPIO_FSEL_14_TXD0		4
#define GPIO_FSEL_14_SD6		5
#define GPIO_FSEL_14_DPI_D10		6
#define GPIO_FSEL_14_TXD1		2
#define GPIO_FSEL_15_RXD0		4
#define GPIO_FSEL_15_SD7		5
#define GPIO_FSEL_15_DPI_D11		6
#define GPIO_FSEL_15_RXD1		2
#define GPIO_FSEL_16_FL0		4
#define GPIO_FSEL_16_SD8		5
#define GPIO_FSEL_16_DPI_D12		6
#define GPIO_FSEL_16_CTS0		7
#define GPIO_FSEL_16_SPI1_CE2_N		3
#define GPIO_FSEL_16_CTS1		2
#define GPIO_FSEL_17_FL1		4
#define GPIO_FSEL_17_SD9		5
#define GPIO_FSEL_17_DPI_D13		6
#define GPIO_FSEL_17_RTS0		7
#define GPIO_FSEL_17_SPI1_CE1_N		3
#define GPIO_FSEL_17_RTS1		2
#define GPIO_FSEL_18_PCM_CLK		4
#define GPIO_FSEL_18_SD10		5
#define GPIO_FSEL_18_DPI_D14		6
#define GPIO_FSEL_18_BSCSLSDA		7
#define GPIO_FSEL_18_MOSI		7
#define GPIO_FSEL_18_SPI1_CE0_N		3
#define GPIO_FSEL_18_PWM0		2
#define GPIO_FSEL_19_PCM_FS		4
#define GPIO_FSEL_19_SD11		5
#define GPIO_FSEL_19_DPI_D15		6
#define GPIO_FSEL_19_BSCSLSCL		7
#define GPIO_FSEL_19_SCLK		7
#define GPIO_FSEL_19_SPI1_MISO		3
#define GPIO_FSEL_19_PWM1		2
#define GPIO_FSEL_20_PCM_DIN		4
#define GPIO_FSEL_20_SD12		5
#define GPIO_FSEL_20_DPI_D16		6
#define GPIO_FSEL_20_BSCSL		7
#define GPIO_FSEL_20_MISO		7
#define GPIO_FSEL_20_SPI1_MOSI		3
#define GPIO_FSEL_20_GPCLK0		2
#define GPIO_FSEL_21_PCM_DOUT		4
#define GPIO_FSEL_21_SD13		5
#define GPIO_FSEL_21_DPI_D17		6
#define GPIO_FSEL_21_BSCSL		7
#define GPIO_FSEL_21_CE_N		7
#define GPIO_FSEL_21_SPI1_SCLK		3
#define GPIO_FSEL_21_GPCLK1		2
#define GPIO_FSEL_22_SD0_CLK		4
#define GPIO_FSEL_22_SD14		5
#define GPIO_FSEL_22_DPI_D18		6
#define GPIO_FSEL_22_SD1_CLK		7
#define GPIO_FSEL_22_ARM_TRST		3
#define GPIO_FSEL_23_SD0_CMD		4
#define GPIO_FSEL_23_SD15		5
#define GPIO_FSEL_23_DPI_D19		6
#define GPIO_FSEL_23_SD1_CMD		7
#define GPIO_FSEL_23_ARM_RTCK		3
#define GPIO_FSEL_24_SD0_DAT0		4
#define GPIO_FSEL_24_SD16		5
#define GPIO_FSEL_24_DPI_D20		6
#define GPIO_FSEL_24_SD1_DAT0		7
#define GPIO_FSEL_24_ARM_TDO		3
#define GPIO_FSEL_25_SD0_DAT1		4
#define GPIO_FSEL_25_SD17		5
#define GPIO_FSEL_25_DPI_D21		6
#define GPIO_FSEL_25_SD1_DAT1		7
#define GPIO_FSEL_25_ARM_TCK		3
#define GPIO_FSEL_26_SD0_DAT2		4
#define GPIO_FSEL_26_TE0		5
#define GPIO_FSEL_26_DPI_D22		6
#define GPIO_FSEL_26_SD1_DAT2		7
#define GPIO_FSEL_26_ARM_TDI		3
#define GPIO_FSEL_27_SD0_DAT3		4
#define GPIO_FSEL_27_TE1		5
#define GPIO_FSEL_27_DPI_D23		6
#define GPIO_FSEL_27_SD1_DAT3		7
#define GPIO_FSEL_27_ARM_TMS		3
#define GPIO_FSEL_28_SDA0		4
#define GPIO_FSEL_28_SA5		5
#define GPIO_FSEL_28_PCM_CLK		6
#define GPIO_FSEL_28_FL0		7
#define GPIO_FSEL_29_SCL0		4
#define GPIO_FSEL_29_SA4		5
#define GPIO_FSEL_29_PCM_FS		6
#define GPIO_FSEL_29_FL1		7
#define GPIO_FSEL_30_TE0		4
#define GPIO_FSEL_30_SA3		5
#define GPIO_FSEL_30_PCM_DIN		6
#define GPIO_FSEL_30_CTS0		7
#define GPIO_FSEL_30_CTS1		2
#define GPIO_FSEL_31_FL0		4
#define GPIO_FSEL_31_SA2		5
#define GPIO_FSEL_31_PCM_DOUT		6
#define GPIO_FSEL_31_RTS0		7
#define GPIO_FSEL_31_RTS1		2
#define GPIO_FSEL_32_GPCLK0		4
#define GPIO_FSEL_32_SA1		5
#define GPIO_FSEL_32_RING_OCLK		6
#define GPIO_FSEL_32_TXD0		7
#define GPIO_FSEL_32_TXD1		2
#define GPIO_FSEL_33_FL1		4
#define GPIO_FSEL_33_SA0		5
#define GPIO_FSEL_33_TE1		6
#define GPIO_FSEL_33_RXD0		7
#define GPIO_FSEL_33_RXD1		2
#define GPIO_FSEL_34_GPCLK0		4
#define GPIO_FSEL_34_SOE_N		5
#define GPIO_FSEL_34_SE			5
#define GPIO_FSEL_34_TE2		6
#define GPIO_FSEL_34_SD1_CLK		7
#define GPIO_FSEL_35_SPI0_CE1_N		4
#define GPIO_FSEL_35_SWE_N		5
#define GPIO_FSEL_35_SRW_N		5
#define GPIO_FSEL_35_SD1_CMD		7
#define GPIO_FSEL_36_SPI0_CE0_N		4
#define GPIO_FSEL_36_SD0		5
#define GPIO_FSEL_36_TXD0		6
#define GPIO_FSEL_36_SD1_DAT0		7
#define GPIO_FSEL_37_SPI0_MISO		4
#define GPIO_FSEL_37_SD1		5
#define GPIO_FSEL_37_RXD0		6
#define GPIO_FSEL_37_SD1_DAT1		7
#define GPIO_FSEL_38_SPI0_MOSI		4
#define GPIO_FSEL_38_SD2		5
#define GPIO_FSEL_38_RTS0		6
#define GPIO_FSEL_38_SD1_DAT2		7
#define GPIO_FSEL_39_SPI0_SCLK		4
#define GPIO_FSEL_39_SD3		5
#define GPIO_FSEL_39_CTS0		6
#define GPIO_FSEL_39_SD1_DAT3		7
#define GPIO_FSEL_40_PWM0		4
#define GPIO_FSEL_40_SD4		5
#define GPIO_FSEL_40_SD1_DAT4		7
#define GPIO_FSEL_40_SPI2_MISO		3
#define GPIO_FSEL_40_TXD1		2
#define GPIO_FSEL_41_PWM1		4
#define GPIO_FSEL_41_SD5		5
#define GPIO_FSEL_41_TE0		6
#define GPIO_FSEL_41_SD1_DAT5		7
#define GPIO_FSEL_41_SPI2_MOSI		3
#define GPIO_FSEL_41_RXD1		2
#define GPIO_FSEL_42_GPCLK1		4
#define GPIO_FSEL_42_SD6		5
#define GPIO_FSEL_42_TE1		6
#define GPIO_FSEL_42_SD1_DAT6		7
#define GPIO_FSEL_42_SPI2_SCLK		3
#define GPIO_FSEL_42_RTS1		2
#define GPIO_FSEL_43_GPCLK2		4
#define GPIO_FSEL_43_SD7		5
#define GPIO_FSEL_43_TE2		6
#define GPIO_FSEL_43_SD1_DAT7		7
#define GPIO_FSEL_43_SPI2_CE0_N		3
#define GPIO_FSEL_43_CTS1		2
#define GPIO_FSEL_44_GPCLK1		4
#define GPIO_FSEL_44_SDA0		5
#define GPIO_FSEL_44_SDA1		6
#define GPIO_FSEL_44_TE0		7
#define GPIO_FSEL_44_SPI2_CE1_N		3
#define GPIO_FSEL_45_PWM1		4
#define GPIO_FSEL_45_SCL0		5
#define GPIO_FSEL_45_SCL1		6
#define GPIO_FSEL_45_TE1		7
#define GPIO_FSEL_45_SPI2_CE2_N		3
/*
 * GPIO 46..53 are on port 2, but only available on the compute module. Anyway,
 * these are SD-card interface lines and better not meddled with.
 */

#define GPIO_GPPUD_OFF		0
#define GPIO_GPPUD_PULLDOWN	1
#define GPIO_GPPUD_PULLUP	2

/* BCM2835 GPIO peripheral register map specification */
typedef struct __bcm2835_gpio_t {
	union {
		struct {
			__IO	uint32_t	gpfsel0;	/* 0x000 GPIO Function Select 0 */
			__IO	uint32_t	gpfsel1;	/* 0x004 GPIO Function Select 1 */
			__IO	uint32_t	gpfsel2;	/* 0x008 GPIO Function Select 2 */
			__IO	uint32_t	gpfsel3;	/* 0x00c GPIO Function Select 3 */
			__IO	uint32_t	gpfsel4;	/* 0x010 GPIO Function Select 4 */
			__IO	uint32_t	gpfsel5;	/* 0x014 GPIO Function Select 5 */
		};
		__IO	uint32_t	gpfsel[6];
	};
	__I	uint32_t	reserved018;
	union {
		struct {
			__O	uint32_t	gpset0;		/* 0x01c GPIO Pin Output Set 0 */
			__O	uint32_t	gpset1;		/* 0x020 GPIO Pin Output Set 1 */
		};
		__O	uint32_t	gpset[2];
	};
	__I	uint32_t	reserved024;
	union {
		struct {
			__O	uint32_t	gpclr0;		/* 0x028 GPIO Pin Output Clear 0 */
			__O	uint32_t	gpclr1;		/* 0x02c GPIO Pin Output Clear 1 */
		};
		__O	uint32_t	gpclr[2];
	};
	__I	uint32_t	reserved030;
	__I	uint32_t	gplev0;		/* 0x034 GPIO Pin Level 0 */
	__I	uint32_t	gplev1;		/* 0x038 GPIO Pin Level 1 */
	__I	uint32_t	reserved03c;
	__IO	uint32_t	gpeds0;		/* 0x040 GPIO Pin Event Detect Status 0 */
	__IO	uint32_t	gpeds1;		/* 0x044 GPIO Pin Event Detect Status 1 */
	__I	uint32_t	reserved048;
	__IO	uint32_t	gpren0;		/* 0x04c GPIO Pin Rising Edge Detect Enable 0 */
	__IO	uint32_t	gpren1;		/* 0x050 GPIO Pin Rising Edge Detect Enable 1 */
	__I	uint32_t	reserved054;
	__IO	uint32_t	gpfen0;		/* 0x058 GPIO Pin Falling Edge Detect Enable 0 */
	__IO	uint32_t	gpfen1;		/* 0x05c GPIO Pin Falling Edge Detect Enable 1 */
	__I	uint32_t	reserved060;
	__IO	uint32_t	gphen0;		/* 0x064 GPIO Pin High Detect Enable 0 */
	__IO	uint32_t	gphen1;		/* 0x068 GPIO Pin High Detect Enable 1 */
	__I	uint32_t	reserved06c;
	__IO	uint32_t	gplen0;		/* 0x070 GPIO Pin Low Detect Enable 0 */
	__IO	uint32_t	gplen1;		/* 0x074 GPIO Pin Low Detect Enable 1 */
	__I	uint32_t	reserved078;
	__IO	uint32_t	gparen0;	/* 0x07c GPIO Pin Async. Rising Edge Detect 0 */
	__IO	uint32_t	gparen1;	/* 0x080 GPIO Pin Async. Rising Edge Detect 1 */
	__I	uint32_t	reserved084;
	__IO	uint32_t	gpafen0;	/* 0x088 GPIO Pin Async. Falling Edge Detect 0 */
	__IO	uint32_t	gpafen1;	/* 0x08c GPIO Pin Async. Falling Edge Detect 1 */
	__I	uint32_t	reserved090;
	__IO	uint32_t	gppud;		/* 0x094 GPIO Pin Pull-up/down Enable */
	__IO	uint32_t	gppudclk0;	/* 0x098 GPIO Pin Pull-up/down Enable Clock 0 */
	__IO	uint32_t	gppudclk1;	/* 0x09c GPIO Pin Pull-up/down Enable Clock 1 */
	__I	uint32_t	reserved0a0[4];
	__IO	uint32_t	test;		/* 0x0b0 Test */
} bcm2835_gpio_t;


#define SPI_CS_CS_01		(1 << 0)
#define SPI_CS_CS_10		(1 << 1)
#define SPI_CS_CPHA		(1 << 2)
#define SPI_CS_CPOL		(1 << 3)
#define SPI_CS_CLEAR_TX		(1 << 4)
#define SPI_CS_CLEAR_RX		(1 << 5)
#define SPI_CS_CSPOL		(1 << 6)
#define SPI_CS_TA		(1 << 7)
#define SPI_CS_DMAEN		(1 << 8)
#define SPI_CS_INTD		(1 << 9)
#define SPI_CS_INTR		(1 << 10)
#define SPI_CS_ADCS		(1 << 11)
#define SPI_CS_REN		(1 << 12)
#define SPI_CS_LEN		(1 << 13)
#define SPI_CS_DONE		(1 << 16)
#define SPI_CS_RXD		(1 << 17)
#define SPI_CS_TXD		(1 << 18)
#define SPI_CS_RXR		(1 << 19)
#define SPI_CS_RXF		(1 << 20)
#define SPI_CS_CSPOL0		(1 << 21)
#define SPI_CS_CSPOL1		(1 << 22)
#define SPI_CS_CSPOL2		(1 << 23)
#define SPI_CS_DMA_LEN		(1 << 24)
#define SPI_CS_LEN_LONG		(1 << 25)

/* BCM2835 SPI peripheral register map specification */
typedef struct __bcm2835_spi_t {
	__IO	uint32_t	cs;	/* 0x000 SPI Master Control and Status */
	__IO	uint32_t	fifo;	/* 0x004 SPI Master TX and RX FIFOs */
	__IO	uint32_t	clk;	/* 0x008 SPI Master Clock Divider */
	__IO	uint32_t	dlen;	/* 0x00c SPI Master Data Length */
	__IO	uint32_t	ltoh;	/* 0x010 SPI LOSSI mode TOH */
	__IO	uint32_t	dc;	/* 0x014 SPI DMA DREQ Controls */
} bcm2835_spi_t;


/*
 * AUX peripheral, contains:
 * - mini-uart
 * - SPI1
 * - SPI2
 */
#define AUX_ENABLES_MU				(1 << 0)
#define AUX_ENABLES_SPI1			(1 << 1)
#define AUX_ENABLES_SPI2			(1 << 2)

/* Bits 0...5 are shift length */
#define AUX_SPI_CNTL0_SHIFT_LENGTH_MASK_U	0x0000003f
#define AUX_SPI_CNTL0_SHIFT_LENGTH_MASK		(AUX_SPI_CNTL0_SHIFT_LENGTH_MASK_U << 0)
#define AUX_SPI_CNTL0_SHIFT_LENGTH(x)		(((x) & AUX_SPI_CNTL0_SHIFT_LENGTH_MASK_U) << 0)
#define AUX_SPI_CNTL0_MSB_OUT			(1 << 6)
#define AUX_SPI_CNTL0_LSB_OUT			(0 << 6)
#define AUX_SPI_CNTL0_CPOL			(1 << 7)
#define AUX_SPI_CNTL0_OUT_RISING		(1 << 8)
#define AUX_SPI_CNTL0_CLEARFIFO			(1 << 9)
#define AUX_SPI_CNTL0_IN_RISING			(1 << 10)
#define AUX_SPI_CNTL0_ENABLE			(1 << 11)
#define AUX_SPI_CNTL0_DOUT_HOLD_0		(0 << 12)
#define AUX_SPI_CNTL0_DOUT_HOLD_1		(1 << 12)
#define AUX_SPI_CNTL0_DOUT_HOLD_4		(2 << 12)
#define AUX_SPI_CNTL0_DOUT_HOLD_7		(3 << 12)
#define AUX_SPI_CNTL0_VAR_WIDTH			(0 << 14)
#define AUX_SPI_CNTL0_VAR_CS			(0 << 15)
#define AUX_SPI_CNTL0_POST_INPUT		(0 << 16)
#define AUX_SPI_CNTL0_CS_0			(1 << 17)
#define AUX_SPI_CNTL0_CS_1			(1 << 18)
#define AUX_SPI_CNTL0_CS_2			(1 << 19)
/* Bits 20...31 are divider */
#define AUX_SPI_CNTL0_SPEED_MASK_U		0x00000fff
#define AUX_SPI_CNTL0_SPEED_MASK		(AUX_SPI_CNTL0_SPEED_MASK_U << 20)
#define AUX_SPI_CNTL0_SPEED(x)			(((x) & AUX_SPI_CNTL0_SPEED_MASK_U) << 20)

#define AUX_SPI_CNTL1_KEEP_INPUT		(1 << 0)
#define AUX_SPI_CNTL1_MSB_IN			(1 << 1)
#define AUX_SPI_CNTL1_LSB_IN			(0 << 1)
#define AUX_SPI_CNTL1_DONEIRQ			(1 << 6)
#define AUX_SPI_CNTL1_TXIRQ			(1 << 7)
#define AUX_SPI_CNTL1_CSHIGH_MASK_U		0x00000003
#define AUX_SPI_CNTL1_CSHIGH_MASK		(AUX_SPI_CNTL1_CSHIGH_MASK_U << 8)
#define AUX_SPI_CNTL1_CSHIGH(x)			(((x) & AUX_SPI_CNTL1_CSHIGH_MASK_U) << 8)

#define AUX_SPI_STAT_COUNT_MASK			0x0000003f
#define AUX_SPI_STAT_BUSY			(1 << 6)
#define AUX_SPI_STAT_RX_EMPTY			(1 << 7)
#define AUX_SPI_STAT_RX_FULL			(1 << 8)
#define AUX_SPI_STAT_TX_EMPTY			(1 << 9)
#define AUX_SPI_STAT_TX_FULL			(1 << 10)
#define AUX_SPI_STAT_RX_LVL_MASK		0x00ff0000
#define AUX_SPI_STAT_TX_LVL_MASK		0xff000000

typedef struct __bcm2835_aux_t {
	__IO	uint32_t	irq;		/* 0x000 Auxiliary Interrupt status */
	__IO	uint32_t	enables;	/* 0x004 Auxiliary enables */
	__I	uint32_t	reserved008[14];
	__IO	uint32_t	mu_io;		/* 0x040 Mini Uart I/O Data */
	__IO	uint32_t	mu_ier;		/* 0x044 Mini Uart Interrupt Enable */
	__IO	uint32_t	mu_iir;		/* 0x048 Mini Uart Interrupt Identify */
	__IO	uint32_t	mu_lcr;		/* 0x04c Mini Uart Line Control */
	__IO	uint32_t	mu_mcr;		/* 0x050 Mini Uart Modem Control */
	__IO	uint32_t	mu_lsr;		/* 0x054 Mini Uart Line Status */
	__IO	uint32_t	mu_msr;		/* 0x058 Mini Uart Modem Status */
	__IO	uint32_t	mu_scratch;	/* 0x05c Mini Uart Scratch */
	__IO	uint32_t	mu_cntl;	/* 0x060 Mini Uart Extra Control */
	__IO	uint32_t	mu_stat;	/* 0x064 Mini Uart Extra Status */
	__IO	uint32_t	mu_baud;	/* 0x068 Mini Uart Baudrate */
	__I	uint32_t	reserved06c[5];
	// XXX: The SPI registers are different than what the documentation
	// states. These offsets are taken from the Linux kernel module
	// spi-bcm2835aux.c, which are most likely the correct offsets.
	// The kernel module actually makes a reference to "garbage" in the
	// official documentation.
	__IO	uint32_t	spi0_cntl0;	/* 0x080 SPI 1 Control register 0 */
	__IO	uint32_t	spi0_cntl1;	/* 0x084 SPI 1 Control register 1 */
	__IO	uint32_t	spi0_stat;	/* 0x088 SPI 1 Status */
	__IO	uint32_t	spi0_peek;	/* 0x08c SPI 1 Peek */
	__I	uint32_t	reserved090[4];
	__IO	uint32_t	spi0_io;	/* 0x0a0 SPI 1 Data */
	__I	uint32_t	reserved0a4[3];
	__IO	uint32_t	spi0_hold;	/* 0x0b0 SPI 1 Hold */
	__I	uint32_t	reserved0b4[3];
	__IO	uint32_t	spi1_cntl0;	/* 0x0c0 SPI 2 Control register 0 */
	__IO	uint32_t	spi1_cntl1;	/* 0x0c4 SPI 2 Control register 1 */
	__IO	uint32_t	spi1_stat;	/* 0x0c8 SPI 2 Status */
	__IO	uint32_t	spi1_peek;	/* 0x0cc SPI 2 Peek */
	__I	uint32_t	reserved0d0[4];
	__IO	uint32_t	spi1_io;	/* 0x0e0 SPI 2 Data */
	__I	uint32_t	reserved0e4[3];
	__IO	uint32_t	spi1_hold;	/* 0x0f0 SPI 2 Hold */
} bcm2835_aux_t;

#endif
.\" Copyright (c) 2016 W. Martinjak
.\" Copyright (c) 2017 B. Stultiens
.\"
.\" This is free documentation; 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.
.\"
.\" The GNU General Public License's references to "object code"
.\" and "executables" are to be interpreted as the output of any
.\" document formatting or typesetting system, including
.\" intermediate and printed output.
.\"
.\" This manual 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 manual; if not, write to the Free
.\" Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111,
.\" USA.
.TH HM2_RPSPI "9" "2017-06-05" "LinuxCNC Documentation" "HAL Component"
.de TQ
.br
.ns
.TP \\$1
..

.SH NAME

hm2_rpspi \- LinuxCNC HAL driver for the Mesa Electronics SPI Anything IO 
boards, with HostMot2 firmware.
.SH SYNOPSIS

.HP
.B loadrt hm2_rpspi
.RS 4
.TP
\fBconfig\fR [default: ""]
HostMot2 config strings, described in the hostmot2(9) manpage.
.TP
\fBspiclk_rate\fR [default: 31250]
Specify the SPI clock rate in kHz.
.TP
\fBspiclk_rate_rd\fR [default: -1 (same as \fBspiclk_rate\fR)]
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 below).
.TP
\fBspiclk_base\fR [default: 400000000]
This is the SPI clock divider calculation fallback value. Usually, the base
rate is read from /sys/kernel/debug/clk/vpu/clk_rate and used in the divider
calculation (for the Rpi3 it should be 250\ MHz). The \fBspiclk_base\fR is
\fIonly\fR used as a fallback if the system's cannot be read. It is safe (and
recommended) that you leave this parameter as is.
.TP
\fBspi_pull_miso\fR [default: 1 (pull\-down)]
.TP
\fBspi_pull_mosi\fR [default: 1 (pull\-down)]
.TP
\fBspi_pull_sclk\fR [default: 1 (pull\-down)]
Enable or disable pull\-up/pull-down on the SPI lines. A value of 0 disables
any pull\-up/down from the pin. A value of 1 means pull\-down and 2 means
pull\-up. The chip enable line(s) are always pull\-up enabled.
.TP
\fBspi_probe\fR [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.

It is an error if a probe fails and the driver will abort.
The SPI0/SPI1 peripherals are located at gpio pins:
 \- SPI0: MOSI=10, MISO=9, SCLK=11, CE0=8, CE1=7
 \- SPI1: MOSI=20, MISO=19, SCLK=21, CE0=18, CE1=17, CE2=16

Multiple boards (up to 5) are supported. However, it is recommended that you
have at most two and each on a different SPI bus.

.SH DESCRIPTION

hm2_rpspi is a device driver for the Raspberry Pi 2/3 that interfaces Mesa's SPI
based Anything I/O boards (with the HostMot2 firmware) to the LinuxCNC
HAL.
This driver is not based on the linux spidev driver, but on a dedicated 
BCM2835-SPI driver.

The supported boards are: 7I90HD.

The board must have a compatible firmware (ie.: 7i90_spi_svst4_8.bit) loaded on 
the board by the
mesaflash(1) program.

hm2_rpspi is only available when linuxcnc is configured with "uspace" realtime.
It works with Raspian and PREEMPT_RT kernel.

.SH INTERFACE CONFIGURATION

At the moment just one device (7I90 card) is supported.

.SH REALTIME PERFORMANCE OF THE BCM2835-SPI DRIVER

TBD.
.\"As of kernel 3.8, most or all kernel SPI drivers do not achieve the high"
.\"realtime response rate required for a typical linuxcnc configuration.  The"
.\"driver was tested with ..."

.SH SPI CLOCK RATES
The maximum SPI clock of the BCM2835-SPI driver and the 7i90 is documented over
32MHz. The SPI driver can provide frequencies well beyond what is acceptable
for the 7i90. 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 has (very) discrete clock frequency values, especially in the
MHz range because of a simple clock divider structure. The base frequency is
250\ MHz and the divider for SPI0/SPI1 scales in discrete factors resulting in
frequencies:
 \- 62.50\ MHz,
 \- 41.67\ MHz,
 \- 31.25\ MHz,
 \- 25.00\ MHz,
 \- 20.83\ MHz,
 \- 17.86\ MHz,
 \- 15.63\ MHz,
 \- 13.89\ MHz,
 \- 12.50\ MHz,
 \- ....

Writing to the 7i90 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 \fBspiclk_rate_rd\fR) to
counter the effects of the SPI\-bus round-trip needed for read actions. For
example, you can write at 41.67\ MHz and read at 25.00\ MHz.

It should be noted that the Rpi3 must have an adequate 5V power supply and the
power should be properly decoupled right on the 40\-pin I/O header. At high
speeds and noise on the supply, there is the possibility of noise throwing off
the SoC's PLL(s), resulting in strange behaviour.

For optimal performance on the Rpi3, you must disable the "ondemand" CPU
frequency governor. You may add the following to your /etc/rc.local file:
 echo -n 1200000 > /sys/devices/system/cpu/cpufreq/policy0/scaling_min_freq
 echo -n performance > /sys/devices/system/cpu/cpufreq/policy0/scaling_governor

Be sure to have a proper heatsink mounted on the SoC or it will get too warm
and crash.

.SH SEE ALSO

hostmot2(9)
.SH LICENSE

GPL
------------------------------------------------------------------------------
Check out the vibrant tech community on one of the world's most
engaging tech sites, Slashdot.org! http://sdm.link/slashdot
_______________________________________________
Emc-users mailing list
Emc-users@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/emc-users

Reply via email to