#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 <hal.h>
#include <rtapi.h>
#include <rtapi_app.h>
#include <rtapi_slab.h>
#include "hostmot2-lowlevel.h"
#include "hostmot2.h"
#include "spi_common_rpspi.h"
#if defined(RPSPI_DEBUG)
#define RPSPI_DEBUG_MSG 1
#endif
#if defined(RPSPI_DEBUG_MSG)
#define RPSPI_ERR RTAPI_MSG_ERR
#define RPSPI_WARN RTAPI_MSG_ERR
#define RPSPI_INFO RTAPI_MSG_ERR
#else
#define RPSPI_ERR RTAPI_MSG_ERR
#define RPSPI_WARN RTAPI_MSG_WARN
#define RPSPI_INFO RTAPI_MSG_INFO
#endif
#define RPSPI_ALWAYS_INLINE __attribute__((always_inline))
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Matsche");
MODULE_DESCRIPTION("Driver for HostMot2 devices connected via SPI to RaspberryPi");
MODULE_SUPPORTED_DEVICE("Mesa-AnythingIO-7i90");
#define RPSPI_MAX_BOARDS 5
#define RPSPI_MAX_MSG (127+1)
#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
#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; int nr; uint32_t spiclkratew; uint32_t spiclkrater; uint32_t spiclkbase; uint32_t spice; int spidevid; int spiceid; } hm2_rpspi_t;
static uint32_t *peripheralmem = (uint32_t *)MAP_FAILED; static size_t peripheralsize; static bcm2835_gpio_t *gpio; static bcm2835_spi_t *spi; static bcm2835_aux_t *aux; static uint32_t aux_enables;
static hm2_rpspi_t boards[RPSPI_MAX_BOARDS]; static int comp_id;
static char *hm2_7c80_pin_names[] = {
"TB07-02/TB07-03",
"TB07-04/TB07-05",
"TB08-02/TB08-03",
"TB08-04/TB08-05",
"TB09-02/TB09-03",
"TB09-04/TB09-05",
"TB10-02/TB10-03",
"TB10-04/TB10-05",
"TB11-02/TB11-03",
"TB11-04/TB11-05",
"TB12-02/TB12-03",
"TB12-04/TB12-05",
"TB03-03/TB04-04",
"TB03-05/TB04-06",
"TB03-05/TB03-06",
"TB04-01/TB04-02",
"TB04-04/TB04-05",
"TB04-07/TB04-08",
"TB05-02",
"TB05-02",
"TB05-05/TB05-06",
"TB05-07/TB05-08",
"Internal InMux0",
"Internal InMux1",
"Internal InMux2",
"Internal InMux3",
"Internal InMux4",
"Internal InMuxData",
"TB13-01/TB13-02",
"TB13-03/TB13-04",
"TB13-05/TB13-06",
"TB13-07/TB13-08",
"TB14-01/TB14-02",
"TB14-03/TB14-04",
"TB14-05/TB14-06",
"TB14-07/TB14-08",
"Internal SSR",
"P1-01",
"P1-02",
"P1-03",
"P1-04",
"P1-05",
"P1-06",
"P1-07",
"P1-08",
"P1-09",
"P1-11",
"P1-13",
"P1-15",
"P1-17",
"P1-19",
"P1-21",
"P1-23",
"P1-25"
};
static char *hm2_7c81_pin_names[] = {
"P1-01",
"P1-02",
"P1-03",
"P1-04",
"P1-05",
"P1-06",
"P1-07",
"P1-08",
"P1-09",
"P1-11",
"P1-13",
"P1-15",
"P1-17",
"P1-19",
"P1-21",
"P1-23",
"P1-25",
"J5-TX0",
"J6-TX1",
"P2-01",
"P2-02",
"P2-03",
"P2-04",
"P2-05",
"P2-06",
"P2-07",
"P2-08",
"P2-09",
"P2-11",
"P2-13",
"P2-15",
"P2-17",
"P2-19",
"P2-21",
"P2-23",
"P2-25",
"J5-TXEN0",
"J6-TXEN1",
"P7-01",
"P7-02",
"P7-03",
"P7-04",
"P7-05",
"P7-06",
"P7-07",
"P7-08",
"P7-09",
"P7-11",
"P7-13",
"P7-15",
"P7-17",
"P7-19",
"P7-21",
"P7-23",
"P7-25",
"P5-RX0",
"P6-RX1"
};
static char *config[RPSPI_MAX_BOARDS];
RTAPI_MP_ARRAY_STRING(config, RPSPI_MAX_BOARDS, "config string for the AnyIO boards (see hostmot2(9) manpage)")
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 3 kHz)")
RTAPI_MP_INT(spiclk_rate_rd, "SPI clock rate for reading in kHz (default same as spiclk_rate)")
#define F_PERI 400000000UL
static int spiclk_base = F_PERI;
RTAPI_MP_INT(spiclk_base, "SPI clock base rate in Hz (default 400000000 Hz)")
#define SPI_PULL_OFF 0
#define SPI_PULL_DOWN 1
#define SPI_PULL_UP 2
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)")
#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))")
static int spi_debug = -1;
RTAPI_MP_INT(spi_debug, "Set message level for debugging purpose [0...5] where 0=none and 5=all (default: -1; upstream defined)")
#define rmb() __sync_synchronize()
#define wmb() __sync_synchronize()
RPSPI_ALWAYS_INLINE static inline uint32_t reg_rd(const volatile void *addr)
{
uint32_t val;
val = *(volatile uint32_t *)addr;
rmb();
return val;
}
RPSPI_ALWAYS_INLINE static inline void reg_wr(const volatile void *addr, uint32_t val)
{
wmb();
*(volatile uint32_t *)addr = val;
}
#if defined(RPSPI_DEBUG_PIN)
RPSPI_ALWAYS_INLINE static inline void gpio_set(uint32_t pin)
{
if(pin <= 53) {
reg_wr(&gpio->gpset[pin / 32], 1 << (pin % 32));
}
}
RPSPI_ALWAYS_INLINE static inline void gpio_clr(uint32_t pin)
{
if(pin <= 53) {
reg_wr(&gpio->gpclr[pin / 32], 1 << (pin % 32));
}
}
RPSPI_ALWAYS_INLINE static inline void gpio_debug_pin(bool set_reset)
{
if(set_reset)
gpio_set(RPSPI_DEBUG_PIN);
else
gpio_clr(RPSPI_DEBUG_PIN);
}
#else
RPSPI_ALWAYS_INLINE static inline void gpio_debug_pin(bool set_reset)
{
(void)set_reset;
}
#endif
#define CMD_7I90_READ (0x0a << 12)
#define CMD_7I90_WRITE (0x0b << 12)
#define CMD_7I90_ADDRINC (1 << 11)
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-1500) * 2) - 1; if(clkdiv > 4095)
clkdiv = 4095; return clkdiv;
}
static inline void spi1_reset(void)
{
reg_wr(&aux->spi0_cntl0, 0);
reg_wr(&aux->spi0_cntl1, AUX_SPI_CNTL0_CLEARFIFO);
}
static inline void spi1_xfer_setup(hm2_rpspi_t *hm2, bool rd)
{
reg_wr(&aux->spi0_cntl0, AUX_SPI_CNTL0_SPEED(spi1_clkdiv_calc(hm2->spiclkbase, rd ? hm2->spiclkrater : 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);
}
static int hm2_rpspi_write_spi1(hm2_lowlevel_io_t *llio, uint32_t addr, const 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 >= RPSPI_MAX_MSG)
return -EINVAL;
gpio_debug_pin(false);
cmd = htobe32(mk_write_cmd(addr, size / 4, true)); spi1_xfer_setup(hm2, false);
txlen = 2; bptr = (uint16_t *)&cmd; 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); --txlen;
}
}
rxlen = size / 2 + 2; txlen = size / 2; wptr = (uint32_t *)buffer; while(rxlen) {
uint32_t stat = reg_rd(&aux->spi0_stat);
if(txlen && !(stat & AUX_SPI_STAT_TX_FULL)) {
if(!(txlen & 1)) {
cmd = htobe32(*wptr++);
bptr = (uint16_t *)&cmd;
}
if(--txlen)
reg_wr(&aux->spi0_hold, (uint32_t)htobe16(*bptr++) << 15); else
reg_wr(&aux->spi0_io, (uint32_t)htobe16(*bptr++) << 15); }
if(!(stat & AUX_SPI_STAT_RX_EMPTY)) {
uint32_t dummy __attribute__((unused));
dummy = reg_rd(&aux->spi0_io);
rxlen--;
}
}
spi1_reset();
gpio_debug_pin(true);
return 1;
}
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 >= RPSPI_MAX_MSG)
return -EINVAL;
gpio_debug_pin(false);
cmd = htobe32(mk_read_cmd(addr, size / 4, true)); spi1_xfer_setup(hm2, true);
txlen = 2; bptr = (uint16_t *)&cmd; 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); --txlen;
}
}
rxlen = 2; txlen = size / 2; 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); else
reg_wr(&aux->spi0_io, 0); }
if(!(stat & AUX_SPI_STAT_RX_EMPTY)) {
uint32_t dummy __attribute__((unused));
dummy = reg_rd(&aux->spi0_io);
rxlen--;
}
}
rxlen = size / 2; wptr = (uint32_t *)buffer; bptr = (uint16_t *)&cmd; 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); else
reg_wr(&aux->spi0_io, 0); }
if(!(stat & AUX_SPI_STAT_RX_EMPTY)) {
*bptr++ = be16toh(reg_rd(&aux->spi0_io));
rxlen--;
if(!(rxlen & 1)) {
*wptr++ = be32toh(cmd);
bptr = (uint16_t *)&cmd;
}
}
}
spi1_reset();
gpio_debug_pin(true);
return 1;
}
static inline int32_t spi0_clkdiv_calc(uint32_t base, uint32_t rate)
{
uint32_t clkdiv = base / rate;
if(clkdiv > 65534)
clkdiv = 0; else
clkdiv += clkdiv & 1; return clkdiv;
}
static inline void spi0_reset(void)
{
uint32_t x = reg_rd(&spi->cs);
x &= ~(SPI_CS_INTR | SPI_CS_INTD | SPI_CS_DMAEN | SPI_CS_TA);
x |= SPI_CS_CLEAR_RX | SPI_CS_CLEAR_TX;
reg_wr(&spi->cs, x);
}
static inline void spi0_xfer_setup(hm2_rpspi_t *hm2, bool rd)
{
uint32_t cs = reg_rd(&spi->cs);
cs &= ~(SPI_CS_CS_10 | SPI_CS_CS_01 | SPI_CS_REN); cs |= hm2->spice | SPI_CS_TA; reg_wr(&spi->clk, spi0_clkdiv_calc(hm2->spiclkbase, rd ? hm2->spiclkrater : hm2->spiclkratew)); reg_wr(&spi->cs, cs); }
static int hm2_rpspi_write_spi0(hm2_lowlevel_io_t *llio, uint32_t addr, const 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 >= RPSPI_MAX_MSG)
return -EINVAL;
gpio_debug_pin(false);
cmd = htobe32(mk_write_cmd(addr, size / 4, true)); spi0_xfer_setup(hm2, false);
bptr = (uint8_t *)&cmd; txlen = 4; while(txlen && (reg_rd(&spi->cs) & SPI_CS_TXD)) {
reg_wr(&spi->fifo, *bptr++);
--txlen;
}
txlen = size; rxlen = size + 4; wptr = (uint32_t *)buffer; 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)) {
if(!(txlen & 3)) {
cmd = htobe32(*wptr++);
bptr = (uint8_t *)&cmd;
}
reg_wr(&spi->fifo, *bptr++);
--txlen;
}
}
spi0_reset();
gpio_debug_pin(true);
return 1;
}
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 >= RPSPI_MAX_MSG)
return -EINVAL;
gpio_debug_pin(false);
cmd = htobe32(mk_read_cmd(addr, size / 4, true)); spi0_xfer_setup(hm2, true);
bptr = (uint8_t *)&cmd; txlen = 4; while(txlen && (reg_rd(&spi->cs) & SPI_CS_TXD)) {
reg_wr(&spi->fifo, *bptr++);
--txlen;
}
txlen = size; rxlen = 4; 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); --txlen;
}
}
rxlen = size; 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;
if(!(rxlen & 3)) {
*wptr++ = be32toh(cmd);
bptr = (uint8_t *)&cmd;
}
}
if(txlen && (cs & SPI_CS_TXD)) {
reg_wr(&spi->fifo, 0); --txlen;
}
}
spi0_reset();
gpio_debug_pin(true);
return 1;
}
static inline unsigned count_ones(uint32_t val)
{
static const unsigned nibble_table[16] = { 0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4 };
unsigned i;
for(i = 0; val; val >>= 4) {
i += nibble_table[val & 0x0f];
}
return i;
}
static int32_t check_cookie(hm2_rpspi_t *board)
{
static const uint32_t xcookie[3] = {HM2_IOCOOKIE, 0x54534f48, 0x32544f4d};
uint32_t cookie[4];
uint32_t ca;
uint32_t co;
if(!board->llio.read(&board->llio, HM2_ADDR_IOCOOKIE, cookie, 16))
return -ENODEV;
if(!memcmp(cookie, xcookie, sizeof(xcookie)))
return (int32_t)cookie[3];
rtapi_print_msg(RPSPI_ERR, "hm2_rpspi: SPI%d/CE%d Invalid cookie, read: %08x %08x %08x,"
" expected: %08x %08x %08x\n",
board->spidevid, board->spiceid,
cookie[0], cookie[1], cookie[2],
xcookie[0], xcookie[1], xcookie[2]);
ca = cookie[0] & cookie[1] & cookie[2]; co = cookie[0] | cookie[1] | cookie[2];
if((!co && spi_pull_miso == SPI_PULL_DOWN) || (ca == 0xffffffff && spi_pull_miso == SPI_PULL_UP)) {
rtapi_print_msg(RPSPI_ERR, "hm2_rpspi: SPI%d/CE%d No drive seen on MISO line (kept at pull-%s level)."
" No board connected or bad connection?\n",
board->spidevid, board->spiceid, spi_pull_miso == SPI_PULL_DOWN ? "down" : "up");
} else if(!co || ca == 0xffffffff) {
rtapi_print_msg(RPSPI_ERR, "hm2_rpspi: SPI%d/CE%d MISO line stuck at %s level."
" Maybe bad connection, a short-circuit or no board attached?\n",
board->spidevid, board->spiceid, !co ? "low" : "high");
} else {
uint32_t mask = board->spidevid ? ~0x00010001 : ~0x01010101;
unsigned ones;
unsigned i;
for(ones = i = 0; i < 3; i++) {
ones += count_ones((xcookie[i] ^ (cookie[i] << 1)) & mask);
}
if(!ones) {
rtapi_print_msg(RPSPI_ERR, "hm2_rpspi: SPI%d/CE%d MISO input is bit-shifted by one bit."
" SPI read clock frequency probably too high.\n",
board->spidevid, board->spiceid);
} else {
rtapi_print_msg(RPSPI_ERR, "hm2_rpspi: SPI%d/CE%d MISO input does not match any expected bit-pattern (>= %u bit difference)."
" Maybe SPI read clock frequency too high or noise on the input?\n",
board->spidevid, board->spiceid, ones);
}
}
return -ENODEV;
}
#define RPSPI_SYS_CLKVPU "/sys/kernel/debug/clk/vpu/clk_rate"
#define RPSPI_SYS_CLKCORE "/sys/kernel/debug/clk/core/clk_rate"
static uint32_t read_spiclkbase(void)
{
const char *sysclkref = RPSPI_SYS_CLKVPU;
uint32_t rate;
int fd;
char buf[16];
ssize_t err;
if((fd = rtapi_open_as_root(sysclkref, O_RDONLY)) < 0) {
rtapi_print_msg(RPSPI_INFO, "hm2_rpspi: No VPU clock at '%s' (errno=%d), trying core clock as alternative.\n", sysclkref, errno);
sysclkref = RPSPI_SYS_CLKCORE;
if((fd = rtapi_open_as_root(sysclkref, O_RDONLY)) < 0) {
rtapi_print_msg(RPSPI_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(RPSPI_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) {
rtapi_print_msg(RPSPI_ERR, "hm2_rpspi: Read full buffer '%s' from '%s', number probably wrong or too large, using %d Hz\n", buf, sysclkref, spiclk_base);
return spiclk_base;
}
if(1 != sscanf(buf, "%u", &rate)) {
rtapi_print_msg(RPSPI_ERR, "hm2_rpspi: Cannot interpret clock setting '%s' from '%s', using %d Hz\n", buf, sysclkref, spiclk_base);
return spiclk_base;
}
return rate;
}
static int probe_board(hm2_rpspi_t *board) {
int32_t ret;
char ident[8+1];
char *base;
#if 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;
rtapi_print_msg(RPSPI_INFO, "hm2_rpspi: SPI%d/CE%d Valid cookie matched\n", board->spidevid, board->spiceid);
if(!board->llio.read(&board->llio, (uint32_t)ret + 0x000c, ident, 8)) {
rtapi_print_msg(RPSPI_ERR, "hm2_rpspi: SPI%d/CE%d Board ident read failed\n", board->spidevid, board->spiceid);
return -EIO; }
ident[sizeof(ident)-1] = 0;
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 if(!memcmp(ident, "MESA7C80", 8)){
base = "hm2_7c80";
board->llio.num_ioport_connectors = 2;
board->llio.pins_per_connector = 27;
board->llio.ioport_connector_name[0] = "Embedded I/O";
board->llio.ioport_connector_name[1] = "Embedded I/O + P1 expansion";
board->llio.io_connector_pin_names = hm2_7c80_pin_names;
board->llio.num_leds = 4;
board->llio.fpga_part_number = "xc6slx9tq144";
} else if(!memcmp(ident, "MESA7C81", 8)){
base = "hm2_7c81";
board->llio.num_ioport_connectors = 3;
board->llio.pins_per_connector = 19;
board->llio.ioport_connector_name[0] = "P1";
board->llio.ioport_connector_name[1] = "P2";
board->llio.ioport_connector_name[2] = "P7";
board->llio.io_connector_pin_names = hm2_7c81_pin_names;
board->llio.num_leds = 4;
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(RPSPI_ERR, "hm2_rpspi: Unknown board at SPI%d/CE%d: %.8s\n", board->spidevid, board->spiceid, ident);
return -1;
}
rtapi_print_msg(RPSPI_INFO, "hm2_rpspi: SPI%d/CE%d Base: %s.%d\n", 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(uint32_t membase, uint32_t memsize)
{
int fd;
int err;
peripheralsize = memsize;
if((fd = rtapi_open_as_root("/dev/mem", O_RDWR | O_SYNC)) < 0) {
rtapi_print_msg(RPSPI_ERR, "hm2_rpspi: can't open /dev/mem\n");
return -errno;
}
peripheralmem = mmap(NULL, peripheralsize, PROT_READ|PROT_WRITE, MAP_SHARED, fd, (off_t)membase);
err = errno;
close(fd);
if(peripheralmem == MAP_FAILED) {
rtapi_print_msg(RPSPI_ERR, "hm2_rpspi: Can't map peripherals\n");
return -err;
}
gpio = (bcm2835_gpio_t *)(peripheralmem + (BCM2835_GPIO_OFFSET / sizeof(*peripheralmem)));
spi = (bcm2835_spi_t *)(peripheralmem + (BCM2835_SPI_OFFSET / sizeof(*peripheralmem)));
aux = (bcm2835_aux_t *)(peripheralmem + (BCM2835_AUX_OFFSET / sizeof(*peripheralmem)));
rtapi_print_msg(RPSPI_INFO, "hm2_rpspi: Mapped peripherals from 0x%08x (size 0x%08x) to gpio:0x%p, spi:0x%p, aux:0x%p\n",
membase, (uint32_t)peripheralsize, gpio, spi, aux);
return 0;
}
static void waste_150_cycles(void)
{
uint32_t x __attribute__((unused));
unsigned i;
for(i = 0; i < 40; i++)
x = reg_rd(&gpio->gplev0); }
static inline void gpio_fsel(uint32_t pin, uint32_t func)
{
if(pin <= 53) {
uint32_t bits = pin * 3;
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)
{
reg_wr(&gpio->gppudclk0, 0); reg_wr(&gpio->gppudclk1, 0);
waste_150_cycles(); 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)
{
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;
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);
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();
}
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);
gpio_pull(SPI1_PIN_MISO, spi_pull_miso);
gpio_pull(SPI1_PIN_MOSI, spi_pull_mosi);
gpio_pull(SPI1_PIN_SCLK, spi_pull_sclk);
aux_enables = reg_rd(&aux->enables);
if(!(aux_enables & AUX_ENABLES_SPI1))
reg_wr(&aux->enables, aux_enables | AUX_ENABLES_SPI1);
spi1_reset();
}
#if defined(RPSPI_DEBUG_PIN)
gpio_fsel(RPSPI_DEBUG_PIN, GPIO_FSEL_X_GPIO_OUTPUT);
gpio_debug_pin(true);
#endif
}
static void peripheral_restore(void)
{
#if defined(RPSPI_DEBUG_PIN)
gpio_debug_pin(true);
gpio_fsel(RPSPI_DEBUG_PIN, GPIO_FSEL_X_GPIO_INPUT);
#endif
if(spi_probe & SPI0_PROBE_MASK) {
gpio_pull(SPI0_PIN_MISO, GPIO_GPPUD_PULLUP); gpio_pull(SPI0_PIN_MOSI, GPIO_GPPUD_PULLUP); gpio_pull(SPI0_PIN_SCLK, GPIO_GPPUD_PULLUP);
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) {
if(!(aux_enables & AUX_ENABLES_SPI1))
reg_wr(&aux->enables, reg_rd(&aux->enables) & ~AUX_ENABLES_SPI1);
gpio_pull(SPI1_PIN_MISO, GPIO_GPPUD_PULLUP); gpio_pull(SPI1_PIN_MOSI, GPIO_GPPUD_PULLUP); gpio_pull(SPI1_PIN_SCLK, GPIO_GPPUD_PULLUP);
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);
}
}
static uint8_t *read_file(const char *fname, size_t maxsize, size_t minsize)
{
FILE *fp;
struct stat sb;
uint8_t *buf;
size_t nn, fsize;
if(-1 == stat(fname, &sb)) {
rtapi_print_msg(RPSPI_ERR, "hm2_rpspi: Cannot stat '%s'\n", fname);
return NULL;
}
if((size_t)sb.st_size < minsize) {
rtapi_print_msg(RPSPI_ERR, "hm2_rpspi: Target file '%s' stat's less than minimum size of %zu bytes (st_size=%zu)\n", fname, minsize, (size_t)sb.st_size);
return NULL;
}
nn = sb.st_size > maxsize ? maxsize : sb.st_size;
if(!(buf = rtapi_kmalloc(nn+1, RTAPI_GFP_KERNEL))) {
rtapi_print_msg(RPSPI_ERR, "hm2_rpspi: No dynamic memory\n");
return NULL;
}
memset(buf, 0, nn+1);
if(!(fp = fopen(fname, "r"))) {
rtapi_print_msg(RPSPI_ERR, "hm2_rpspi: Cannot open '%s' for read\n", fname);
rtapi_kfree(buf);
return NULL;
}
fsize = fread(buf, 1, nn, fp);
fclose(fp);
if(!fsize) {
rtapi_print_msg(RPSPI_ERR, "hm2_rpspi: Nothing read from '%s' (errno=%d)\n", fname, errno);
rtapi_kfree(buf);
return NULL;
}
if(fsize < nn) {
rtapi_print_msg(RPSPI_ERR, "hm2_rpspi: Short read from '%s'; read=%zu required>=%zu\n", fname, fsize, nn);
rtapi_kfree(buf);
return NULL;
}
return buf;
}
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;
uint8_t *buf;
uint32_t pmembase;
uint32_t pmemsize;
if(spi_debug >= RTAPI_MSG_NONE && spi_debug <= RTAPI_MSG_ALL)
rtapi_set_msg_level(spi_debug);
if(!(buf = read_file("/proc/device-tree/model", 4095, 0))) {
rtapi_print_msg(RPSPI_ERR, "hm2_rpspi: Unsupported Platform.\n");
return -1;
}
rtapi_print_msg(RPSPI_INFO, "hm2_rpspi: Platform: %s\n", *buf ? (const char *)buf : "<no data from /proc/device-tree/model>");
rtapi_kfree(buf);
if(!(buf = read_file("/proc/device-tree/soc/ranges", 4095, 12))) {
rtapi_print_msg(RPSPI_ERR, "hm2_rpspi: Cannot determine IO base address and size.\n");
return -1;
}
pmembase = be32toh(((uint32_t *)buf)[1]); pmemsize = be32toh(((uint32_t *)buf)[2]);
if(!pmembase) {
rtapi_kfree(buf);
if(!(buf = read_file("/proc/device-tree/soc/ranges", 4095, 16))) {
rtapi_print_msg(RPSPI_ERR, "hm2_rpspi: Cannot determine IO base address and size.\n");
return -1;
}
pmembase = be32toh(((uint32_t *)buf)[2]);
pmemsize = be32toh(((uint32_t *)buf)[3]);
}
rtapi_kfree(buf);
if(!pmembase || !pmemsize) {
rtapi_print_msg(RPSPI_ERR, "hm2_rpspi: IO base address (0x%08x) or size (0x%08x) are zero.\n", pmembase, pmemsize);
return -1;
}
rtapi_print_msg(RPSPI_INFO, "hm2_rpspi: Base address 0x%08x size 0x%08x\n", pmembase, pmemsize);
if(-1 == spiclk_rate_rd)
spiclk_rate_rd = spiclk_rate;
if(spiclk_rate < 30 || spiclk_rate > 63000) {
rtapi_print_msg(RPSPI_ERR, "hm2_rpspi: SPI clock rate '%d' too slow/fast. Must be >= 30 kHz and <= 63000 kHz\n", spiclk_rate);
return -EINVAL;
}
if(spiclk_rate_rd < 30 || spiclk_rate_rd > 63000) {
rtapi_print_msg(RPSPI_ERR, "hm2_rpspi: SPI clock rate for reading '%d' too slow/fast. Must be >= 30 kHz and <= 63000 kHz\n", spiclk_rate_rd);
return -EINVAL;
}
if((retval = peripheral_map(pmembase, pmemsize)) < 0) {
rtapi_print_msg(RPSPI_ERR, "hm2_rpspi: cannot map peripheral memory.\n");
return retval;
}
peripheral_setup();
memset(boards, 0, sizeof(boards));
for(j = i = 0; i < RPSPI_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))) continue;
clkratew = spiclk_rate * 1000; clkrater = spiclk_rate_rd * 1000; clkbase = read_spiclkbase();
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(RPSPI_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; case 1: boards[j].spice = SPI_CS_CS_01; break; }
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(RPSPI_INFO, "hm2_rpspi: SPI%d/CE%d write clock rate calculated: %d Hz (clkdiv=%u)\n", iddev, idce, clkbase / clkdiv, clkdiv);
if(!(clkdiv = spi0_clkdiv_calc(clkbase, clkrater)))
clkdiv = 65536;
rtapi_print_msg(RPSPI_INFO, "hm2_rpspi: SPI%d/CE%d read clock rate calculated: %d Hz (clkdiv=%u)\n", iddev, idce, clkbase / clkdiv, clkdiv);
} else {
switch(idce) {
case 0: boards[j].spice = AUX_SPI_CNTL0_CS_1 | AUX_SPI_CNTL0_CS_2; break; case 1: boards[j].spice = AUX_SPI_CNTL0_CS_0 | AUX_SPI_CNTL0_CS_2; break; case 2: boards[j].spice = AUX_SPI_CNTL0_CS_0 | AUX_SPI_CNTL0_CS_1; break; }
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(RPSPI_INFO, "hm2_rpspi: SPI%d/CE%d write clock rate calculated: %d Hz (clkdiv=%u)\n", iddev, idce, clkbase / clkdiv, clkdiv);
clkdiv = 2 * (spi1_clkdiv_calc(clkbase, clkrater) + 1);
rtapi_print_msg(RPSPI_INFO, "hm2_rpspi: SPI%d/CE%d read clock rate calculated: %d Hz (clkdiv=%u)\n", iddev, idce, clkbase / clkdiv, clkdiv);
}
if((retval = probe_board(&boards[j])) < 0) {
return retval;
}
if((retval = hm2_register(&boards[j].llio, config[j])) < 0) {
rtapi_print_msg(RPSPI_ERR, "hm2_rpspi: hm2_register() failed for SPI%d/CE%d.\n", iddev, idce);
return retval;
}
j++; }
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);
}