Standalone access to SPI modules on A20

Started by mMerlin, April 08, 2016, 01:28:21 AM

Previous topic - Next topic

mMerlin

I am working on a program to be run on an A20 that needs fast enough real time response that regular linux is not enough.  Even with a SCHED_FIFO, priority 99 thread, testing shows that the OS (apparently) manages to interrupt the thread with 40µs delays several times a second.  Due to that development is currently targeted to a standalone application to be loaded and run from u-boot.

This is part of a project where the processor and board decisions are not mine to make/change.  I have an A20-OLinuXino-MICRO board to work with.  Current environment cross compiling from Gentoo.  GPIO configuration, and bit banging ports is working fine, so I am not concerned about the environment: Gentoo 4.3.3-hardened-r4, arm-linux-gnueabihf-gcc 4.8.5

Part of the communications is intended to be SPI to a daughter ADC board.  I can not seem to get the onboard SPI module activated.  Enabling any of the SPI modules, by setting the EN pin of any and all of the SPI_CTL (offset 0x08) registers, with SPI base addresss of 0x01C05000, 0x01C06000, 0x01C17000, or 0x01C1F000 does not 'take'.  Both before and after setting the EN bit, the control register value is 0.  I've tried setting just the enable bit, or a full configuration set.  SPI_CTL does not change.

Reference:
A20_User_Manual_v1.4_20150510.pdf
https://github.com/allwinner-zh/documents/blob/master/A20/A20_User_Manual_v1.4_20150510.pdf
$ git remote --verbose
origin   git://git.denx.de/u-boot.git (fetch)
origin   git://git.denx.de/u-boot.git (push)
$ git status
HEAD detached at v2016.01
nothing to commit, working directory clean

Am I missing something that is needed to enable the SPI memory blocks, or have I got the wrong addresses, or ... ?

I should be able to bit bang the SPI communications, but with the documented onboard SPI capabilities, that seems like such a waste.

sample code spi.c
#include <common.h>

int spi (void);
void rptSpi (const char const *, const uint32_t);
void cfgSpi (const char const *, const uint32_t, const uint32_t);

int spi ()
{
  printf ("Initial values\n");
  rptSpi( "SPI0", SUNXI_SPI0_BASE);
  rptSpi( "SPI1", SUNXI_SPI1_BASE);
  rptSpi( "SPI2", SUNXI_SPI2_BASE);
  rptSpi( "SPI3", SUNXI_SPI3_BASE);

  printf ("\nEnable only with %08x\n", 1);
  cfgSpi( "SPI0", SUNXI_SPI0_BASE, 1);
  cfgSpi( "SPI1", SUNXI_SPI1_BASE, 1);
  cfgSpi( "SPI2", SUNXI_SPI2_BASE, 1);
  cfgSpi( "SPI3", SUNXI_SPI3_BASE, 1);

  printf ("\nafter enable\n");
  rptSpi( "SPI0", SUNXI_SPI0_BASE);
  rptSpi( "SPI1", SUNXI_SPI1_BASE);
  rptSpi( "SPI2", SUNXI_SPI2_BASE);
  rptSpi( "SPI3", SUNXI_SPI3_BASE);

  // 0b01 1010 0000 0000 0001 1111);
  printf ("\nconfigure with %08x\n", 0b0110100000000000011111);
  cfgSpi( "SPI0", SUNXI_SPI0_BASE, 0b0110100000000000011111);
  cfgSpi( "SPI1", SUNXI_SPI1_BASE, 0b0110100000000000011111);
  cfgSpi( "SPI2", SUNXI_SPI2_BASE, 0b0110100000000000011111);
  cfgSpi( "SPI3", SUNXI_SPI3_BASE, 0b01101000000000000111111);

  printf ("\nafter configure\n");
  rptSpi( "SPI0", SUNXI_SPI0_BASE);
  rptSpi( "SPI1", SUNXI_SPI1_BASE);
  rptSpi( "SPI2", SUNXI_SPI2_BASE);
  rptSpi( "SPI3", SUNXI_SPI3_BASE);

  return (0);
}

void rptSpi (const char const * module, const uint32_t addr)
{
  uint32_t *reg;

  printf ("\n%s module base address: %08x\n", module, addr);
  reg = (uint32_t *)(addr + 0x00);
  printf ("%s_RXDATA:   %08x\n", module, *reg);
  reg = (uint32_t *)(addr + 0x04);
  printf ("%s_TXDATA:   %08x\n", module, *reg);
  reg = (uint32_t *)(addr + 0x08);
  printf ("%s_CTL:      %08x\n", module, *reg);
  reg = (uint32_t *)(addr + 0x0c);
  printf ("%s_INTCTL:   %08x\n", module, *reg);
  reg = (uint32_t *)(addr + 0x10);
  printf ("%s_ST:       %08x\n", module, *reg);
  reg = (uint32_t *)(addr + 0x14);
  printf ("%s_DMACTL:   %08x\n", module, *reg);
  reg = (uint32_t *)(addr + 0x18);
  printf ("%s_WAIT:     %08x\n", module, *reg);
  reg = (uint32_t *)(addr + 0x1c);
  printf ("%s_CCTL:     %08x\n", module, *reg);
  reg = (uint32_t *)(addr + 0x20);
  printf ("%s_BC:       %08x\n", module, *reg);
  reg = (uint32_t *)(addr + 0x24);
  printf ("%s_TC:       %08x\n", module, *reg);
  reg = (uint32_t *)(addr + 0x28);
  printf ("%s_FIFO_STA: %08x\n", module, *reg);
}

void cfgSpi (const char const * module, const uint32_t addr, const uint32_t val)
{
  uint32_t *ctl;
  ctl = (uint32_t *)(addr + 0x08);
  printf ("%s_CTL reg addr: %08x\n", module, (uint32_t)ctl);
  *ctl = val;
}


output from sample code
## Starting application at 0x42000000 ...
Initial values

SPI0 module base address: 01c05000
SPI0_RXDATA:   00000000
SPI0_TXDATA:   00000000
SPI0_CTL:      00000000
SPI0_INTCTL:   00000000
SPI0_ST:       00000000
SPI0_DMACTL:   00000000
SPI0_WAIT:     00000000
SPI0_CCTL:     00000000
SPI0_BC:       00000000
SPI0_TC:       00000000
SPI0_FIFO_STA: 00000000

SPI1 module base address: 01c06000
SPI1_RXDATA:   00000000
SPI1_TXDATA:   00000000
SPI1_CTL:      00000000
SPI1_INTCTL:   00000000
SPI1_ST:       00000000
SPI1_DMACTL:   00000000
SPI1_WAIT:     00000000
SPI1_CCTL:     00000000
SPI1_BC:       00000000
SPI1_TC:       00000000
SPI1_FIFO_STA: 00000000

SPI2 module base address: 01c17000
SPI2_RXDATA:   00000000
SPI2_TXDATA:   00000000
SPI2_CTL:      00000000
SPI2_INTCTL:   00000000
SPI2_ST:       00000000
SPI2_DMACTL:   00000000
SPI2_WAIT:     00000000
SPI2_CCTL:     00000000
SPI2_BC:       00000000
SPI2_TC:       00000000
SPI2_FIFO_STA: 00000000

SPI3 module base address: 01c1f000
SPI3_RXDATA:   00000000
SPI3_TXDATA:   00000000
SPI3_CTL:      00000000
SPI3_INTCTL:   00000000
SPI3_ST:       00000000
SPI3_DMACTL:   00000000
SPI3_WAIT:     00000000
SPI3_CCTL:     00000000
SPI3_BC:       00000000
SPI3_TC:       00000000
SPI3_FIFO_STA: 00000000

Enable only with 00000001
SPI0_CTL reg addr: 01c05008
SPI1_CTL reg addr: 01c06008
SPI2_CTL reg addr: 01c17008
SPI3_CTL reg addr: 01c1f008

after enable

SPI0 module base address: 01c05000
SPI0_RXDATA:   00000000
SPI0_TXDATA:   00000000
SPI0_CTL:      00000000
SPI0_INTCTL:   00000000
SPI0_ST:       00000000
SPI0_DMACTL:   00000000
SPI0_WAIT:     00000000
SPI0_CCTL:     00000000
SPI0_BC:       00000000
SPI0_TC:       00000000
SPI0_FIFO_STA: 00000000

SPI1 module base address: 01c06000
SPI1_RXDATA:   00000000
SPI1_TXDATA:   00000000
SPI1_CTL:      00000000
SPI1_INTCTL:   00000000
SPI1_ST:       00000000
SPI1_DMACTL:   00000000
SPI1_WAIT:     00000000
SPI1_CCTL:     00000000
SPI1_BC:       00000000
SPI1_TC:       00000000
SPI1_FIFO_STA: 00000000

SPI2 module base address: 01c17000
SPI2_RXDATA:   00000000
SPI2_TXDATA:   00000000
SPI2_CTL:      00000000
SPI2_INTCTL:   00000000
SPI2_ST:       00000000
SPI2_DMACTL:   00000000
SPI2_WAIT:     00000000
SPI2_CCTL:     00000000
SPI2_BC:       00000000
SPI2_TC:       00000000
SPI2_FIFO_STA: 00000000

SPI3 module base address: 01c1f000
SPI3_RXDATA:   00000000
SPI3_TXDATA:   00000000
SPI3_CTL:      00000000
SPI3_INTCTL:   00000000
SPI3_ST:       00000000
SPI3_DMACTL:   00000000
SPI3_WAIT:     00000000
SPI3_CCTL:     00000000
SPI3_BC:       00000000
SPI3_TC:       00000000
SPI3_FIFO_STA: 00000000

configure with 001a001f
SPI0_CTL reg addr: 01c05008
SPI1_CTL reg addr: 01c06008
SPI2_CTL reg addr: 01c17008
SPI3_CTL reg addr: 01c1f008

after configure

SPI0 module base address: 01c05000
SPI0_RXDATA:   00000000
SPI0_TXDATA:   00000000
SPI0_CTL:      00000000
SPI0_INTCTL:   00000000
SPI0_ST:       00000000
SPI0_DMACTL:   00000000
SPI0_WAIT:     00000000
SPI0_CCTL:     00000000
SPI0_BC:       00000000
SPI0_TC:       00000000
SPI0_FIFO_STA: 00000000

SPI1 module base address: 01c06000
SPI1_RXDATA:   00000000
SPI1_TXDATA:   00000000
SPI1_CTL:      00000000
SPI1_INTCTL:   00000000
SPI1_ST:       00000000
SPI1_DMACTL:   00000000
SPI1_WAIT:     00000000
SPI1_CCTL:     00000000
SPI1_BC:       00000000
SPI1_TC:       00000000
SPI1_FIFO_STA: 00000000

SPI2 module base address: 01c17000
SPI2_RXDATA:   00000000
SPI2_TXDATA:   00000000
SPI2_CTL:      00000000
SPI2_INTCTL:   00000000
SPI2_ST:       00000000
SPI2_DMACTL:   00000000
SPI2_WAIT:     00000000
SPI2_CCTL:     00000000
SPI2_BC:       00000000
SPI2_TC:       00000000
SPI2_FIFO_STA: 00000000

SPI3 module base address: 01c1f000
SPI3_RXDATA:   00000000
SPI3_TXDATA:   00000000
SPI3_CTL:      00000000
SPI3_INTCTL:   00000000
SPI3_ST:       00000000
SPI3_DMACTL:   00000000
SPI3_WAIT:     00000000
SPI3_CCTL:     00000000
SPI3_BC:       00000000
SPI3_TC:       00000000
SPI3_FIFO_STA: 00000000
## Application terminated, rc = 0x0


Makefile used to build
# GNU universal makefile for using the gcc compiler under Linux
#
# Put the makefile in a directory with all your .c source files There
# should be no .c files which you do not wish to include in the
# compile This will look after all dependencies, and will build and
# install the project
#
# This works with a netbeans project located inside the directory The
# ant program with a jdk and IzPack must both be installed on the
# machine for the java installer to run.  Ant and jdk are installed
# with netbeans if that is present.
#
# The dll file must be compiled in windows and brought over to create
# the full installer, one must be present to create any installer.
#
# Make and compiler information and download at  http://www.gnu.org/
#
# Authors: Rick Jenkins <rick@hartmantech.com> www.hartmantech.com
#          Diane Gagne  <diane@hartmantech.com>
#
# Licensed for free use under the current GNU Public License

# Compile for generic x86 machine, should work on any such architecture.
#CPU = native
CPU = armv7-a
ARCH = arm

# File locations
TOP = .
UBOOT = /mung/u-boot
ARCHENV ?= arm-linux-gnueabihf
ARCHVER ?= 4.8.5
ARCHLIB = $(ARCHENV)/$(ARCHVER)

# Compile flags.
# Include files (.h) here
INCLS = \
-nostdinc -isystem /usr/lib/gcc/$(ARCHLIB)/include \
-I . -I$(UBOOT)/include -I$(UBOOT)/arch/$(ARCH)/include

#multiple warnings generated from u-boot include files
#-Wnested-externs -Wcast-qual -Wredundant-decls -Wunused-parameter
EXTRA_WARNINGS = \
  -W -Wcast-align -Wpointer-arith -Wreturn-type -Wshadow -Wswitch \
-Wunused -Wwrite-strings \
-Wmissing-prototypes -Wmissing-declarations \
-Wno-unused-parameter

WARNINGS = \
-Wall -Wstrict-prototypes -Wno-format-security -Wno-format-nonliteral \
$(EXTRA_WARNINGS)

#-mno-unaligned-access # default (only) for pre-ARMv6 and all ARMv6-M
ARCHSPEC = \
  -D__ARM__ -m$(ARCH) -mno-thumb-interwork \
-mabi=aapcs-linux -ffixed-r9 -march=$(CPU) \
-mno-unaligned-access -msoft-float

#-fstack-usage for development/diagnostics?
TUNING = \
-std=gnu99 \
-D__KERNEL__ -D__UBOOT__ -DCONFIG_SYS_TEXT_BASE=0x4a000000 \
-fno-builtin -ffreestanding -fno-stack-protector -fstack-usage \
-mword-relocations -fno-common -pipe -c

#-ffunction-sections -fdata-sections # possible conflict with -g
OPTIMIZING = \
-O0 -fno-delete-null-pointer-checks -fno-toplevel-reorder \
-ffunction-sections -fdata-sections

#grabs the folder of the most recent java jdk available on the linux machine
#The file is located in the /opt folder

INCLUDES = -include $(UBOOT)/include/linux/kconfig.h

DEBUGGING = -g

# Define PACKET_KILLER for imapaired packets by digital messing up of received data

CFLAGS = $(ARCHSPEC) $(TUNING) $(OPTIMIZING) $(WARNINGS) $(INCLS) $(INCLUDES) $(DEBUGGING)

# You must use -lm for math functions such as sinf(), but not for
# simple floating-point arithmetic. Flags like -lm or -lfp belong in
# $(POSTLINKFLAGS) which follows the filenames in a link command.
#LINKFLAGS = -Wl,--verbose,-static
#LINKFLAGS = -v -shared
LINKFLAGS = -e spi -g -Ttext 0x42000000
POSTLINKFLAGS = $(UBOOT)/examples/standalone/libstubs.o \
-L /usr/lib/gcc/$(ARCHLIB) -lgcc
# -lm -lrt
# m gcc c rt

# Assembler Flags
#ASFLAGS = -march=${CPU} -D_GNU_ASSEMBLER_
ASFLAGS = -D_GNU_ASSEMBLER_

#WINDOWSDIR = windowsInstall/windowsBuild
#L32DIR = linux32Build

# Name for your project
NAME = spi

# C sources, usually all the files in the current directory, plus
# others found on the vpath
sources := $(wildcard *.c)

###########################################################################

# You probably don't need to edit any line below here

###########################################################################

HOSTARCH := $(shell uname -m | \
sed -e s/i.86/x86/ \
    -e s/sun4u/sparc64/ \
    -e s/arm.*/arm/ \
    -e s/sa110/arm/ \
    -e s/ppc64/powerpc/ \
    -e s/ppc/powerpc/ \
    -e s/macppc/powerpc/\
    -e s/sh.*/sh/)

export HOSTARCH

# set default to nothing for native builds
ifeq ($(HOSTARCH),$(ARCH))
CROSS_COMPILE ?=
else
CROSS_COMPILE ?= $(ARCHENV)-
endif

# Probably not needed, but just in case...
SHELL = /bin/sh
.SUFFIXES:
.SUFFIXES: .c .o .d .ihex .elf .bin

# Programs to use
CC       = $(CROSS_COMPILE)gcc
LD       = $(CROSS_COMPILE)ld
BFD      = $(CROSS_COMPILE)ld.bfd
AR       = $(CROSS_COMPILE)ar
AS       = $(CROSS_COMPILE)gcc
GASP     = $(CROSS_COMPILE)gasp
NM       = $(CROSS_COMPILE)nm
OBJCOPY  = $(CROSS_COMPILE)objcopy
RANLIB   = $(CROSS_COMPILE)ranlib
SIZE     = $(CROSS_COMPILE)size
STRIP    = $(CROSS_COMPILE)strip
RM       = \rm -f
MD       = mkdir -p $@
RMD      = \rm -rf
WINCC    = i686-pc-mingw32-gcc

vpath %.c   $(CODEPATH)
vpath %.cod $(CODEPATH)

# Pattern rule to make a dependency file from a C source
%.d: %.c
@set -e; $(RM) $@; \
$(CC) -MM $(CFLAGS) $< > $@; \
sed -i 's,\($*\)\.o[ :]*,\1.o $@ : ,g' $@

# Pattern rule to make assembly files from c files
%.s: %.c
$(CC) -S $(CFLAGS) $<

# Pattern rule to make excutable files from o files
%: %.o
$(BFD) -E $(CFLAGS) $< -o $@

# Pattern rule to make preprocessed files from c files
%.i: %.c
$(CC) -E $(CFLAGS) $< -o $@

# C sources and their corresponding generated files
objects   := $(sources:.c=.o)
depends   := $(sources:.c=.d)
assfiles  := $(sources:.c=.s)
procfiles := $(sources:.c=.i)
sufiles   := $(sources:.c=.su)


# Default target
$(NAME).bin: $(NAME)
$(OBJCOPY) -O binary $(NAME) $(NAME).bin

$(NAME): $(objects)
$(BFD) -o $(NAME) $(LINKFLAGS) $(objects) $(POSTLINKFLAGS)

# Include all dependency files
-include $(depends)

.PHONY: clean
clean :
$(RM) $(NAME) $(NAME).bin $(objects) $(depends) $(assfiles) $(sufiles)
# @echo "arch" $(ARCH) $(HOSTARCH)


.PHONY: assembly
assembly: $(assfiles)

.PHONY: preproc
preproc: $(procfiles)

LubOlimex

Hey,

Did you try if the SPI works with the official images for the board? It has two SPI ports enabled. These are available as torrents here:

https://www.olimex.com/wiki/images/b/b4/A20_OLinuxino_Micro_debian_34_90_release_10.torrent

and here:

https://www.olimex.com/wiki/images/6/6f/A20_OLinuxino_Micro_debian_Jessie_34_103_2G_release_11.torrent


The SPI ports are:

/dev/spidev1.0

/dev/spidev2.0

there is also a simple test for SPI2 in /opt/spidev

Best regards,
Lub/OLIMEX
Technical support and documentation manager at Olimex

mimi1962

hello,

Is it possible to use module with the versions jessie 11 or 12?

best regards

michel

LubOlimex

Technical support and documentation manager at Olimex

mMerlin

Suggesting accessing SPI through the official images is not by itself particularly helpful.  My initial post said that was not possible for this case, due to the linux build inserting unacceptable delays into the time critical thread.

No I did not get around to accessing SPI through linux.  I have been working problems one at a time as they hit the 'critical' sections.  First, the documented method of accessing GPIO pins from linux was too slow.  Found that mmap got the speed up, but that needed a rebuild of the kernel
https://www.olimex.com/forum/index.php?topic=4933.0
which took time to figure out (never compiled a kernel before).  Then the new kernel did not have an SPI device driver.  Worked on another build, but before that was working/tested, discovered that although the code using mmap to access GPIO looked fast enough on the scope, it was getting delays, even with a maximum priority, SCHED_FIFO thread.  I thought that should fully take over one cpu core, but apparently the linux scheduler is still interrupting it, inserting 40 microsecond delays into something that has to run every 10 microseconds.  So I am asking how to get SPI (and other things) to run in a standalone program that does not have any access to linux device drivers.

Your suggested kernel MIGHT (probably will) provide access to SPI.  It is unlikely to give access to mmap to get the needed GPIO access speed.  Rebuilding (again) from sources will probably fix that, but might lose SPI again.  Unless I get the right repo and version.  Without additional changes, that would still not solve the issue.  I have asked the same question in a few other places.  One of those asked if I implemented the time critical section in a driver or user space, and indicated that RT kernel is needed for actual real time response.  It is user space, so implementing a driver, and getting it loaded might fix that.  I've never written a linux device driver, and only installed a few loadable modules from detailed instructions.  Are those images built from
https://github.com/linux-sunxi/linux-sunxi.git
and which tags?

I also got pointed to isolcpu as a possible solution.  If I can figure out the implementation, that looks like it goes right where I was aiming with SCHED_FIFO.  Although an answer by bofh.at, and comment from akp in thread http://stackoverflow.com/questions/13583146/whole-one-core-dedicated-to-single-process say that isolcpu and RT_PREEMPT need to be combined to get the needed result.

All of that still leaves the original question unanswered.

Where do I find out how to access the SoC devices without  linux device drivers, from a standalone program that is loaded and run from u-boot?  No linux on the system.  Those details are missing in all of the documentation I have found.  I have also been told various times that an SoC intended for smart phone use normally has extensive power management, to only power up sections that are actually being used.  The documentation I have seen does not show anything about that at all.  Just the separate power domain for the RTC.

An implementation is in the linux source code some place, but that is a lot of code to look at.  Some tips on where to look might get the needed information.  The code I have looked as so far keeps jumping to other sections of kernel or library code, with enough #ifdef blocks to keep me unsure of what path is being followed, and also not sure what might have been initialized before it gets to the code I am looking at.

mMerlin

I also posted this question on the
google+ community, and Nikolai Kondrashov (spbnick) there spotted and correctly interpreted a pieced of information in the documentation.

To get access to the SPI registers, both the Gating AHB Clock for SPI«n» bit in AHB_GATTING_REG0, and the SCLK_GATING bit in SPI«n»_CLK_REG have to be set.  Once those are set, the SPI«n» module registers themselves become accessible.

Potentially confusing side note:  Setting the AHB gating bit first, then trying to reference the SPI register before setting the SCLK gating bit causes a total freeze.  Since in a *working* application, those will normally be set together, this normally not an issue.  Only showed up with the debug output, checking to see just when the SPI registers became visible.

I still find the documentation rather lacking.  A few code examples would improve it a lot.  This discovery is probably enough to get access to most of the SoC modules/devices.

Fairly minimal example code.  This works with the same Makefile shown for the original question.  Which needs to be adjusted to provide the actual path to the u-boot source tree.

#include <common.h>

// Global memory pointers
uint32_t
(*SPIarry1)[11],
*SPI1_Cfg,
*AHB_Gate0,
*SPI1_ClkCfg;

int spi_test (void);

int spi_test (void)
{
AHB_Gate0 = (uint32_t *)(SUNXI_CCM_BASE + 0x60);
SPI1_ClkCfg = (uint32_t *)(SUNXI_CCM_BASE + 0xa4);
SPIarry1 = (uint32_t (*)[11])SUNXI_SPI1_BASE;

printf ("SPI1 Cfg Reg 0x%08x\n", (*SPIarry1)[2]);
*SPI1_ClkCfg = 0x80000000;
printf ("SPI1 Cfg Reg 0x%08x\n", (*SPIarry1)[2]);
*AHB_Gate0 |= 0x00200000;
printf ("SPI1 Cfg Reg 0x%08x\n", (*SPIarry1)[2]);
return (0);
}


captured sample program output

## Starting application at 0x42000000 ...
SPI1 Cfg Reg 0x00000000
SPI1 Cfg Reg 0x00000000
SPI1 Cfg Reg 0x0012001c
## Application terminated, rc = 0x0