Article updated
at 29 Oct 2021
The ADC driver makes use of the Industrial Input/Output (IIO) framework.
MXM3 Pin |
K20 Input |
IIO Device |
IIO Channel |
AD0 |
PTB0\ADC0_SE8 |
iio:device0 |
in_voltage0_raw |
AD1 |
PTB1\ADC0_SE9 |
iio:device0 |
in_voltage1_raw |
AD2 |
PTB2\ADC0_SE12 |
iio:device0 |
in_voltage2_raw |
AD3 |
PTB3\ADC0_SE13 |
iio:device0 |
in_voltage3_raw |
The IIO framework exposes those inputs through the sysfs interface:
# cd /sys/bus/iio/devices/iio:device0/
# ls in*
in_voltage0_raw in_voltage0_scale in_voltage1_raw in_voltage1_scale in_voltage2_raw in_voltage2_scale in_voltage3_raw in_voltage3_scale
# cat /sys/bus/iio/devices/device0/in_voltage0_raw
755
More information about the IIO framework:
- https://wiki.analog.com/software/linux/docs/iio/iio
Touch screen controller is implemented in K20 software using internal ADC and external switching. It supports 4-wire resistive touch screens.
Input device driver is implemented in kernel.
K20 GPIOs are available via sysfs. Since it's not the only GPIO controller available on Apalis TK1 GPIO numbers start with 856.
More information on using sysfs GPIO subsystem can be found here.
To calculate the GPIO number use the following formula:
856 + 32 x (Character - 'A') + Digit
So GPIO A0 becomes 856, A7 becomes 863, B0 becomes 888, ...
Not all of the K20 GPIOs can be used, here's the list of available ones with corresponding sysfs id and edge connector pin
GPIO Assignment and MXM3 Locations
GPIO |
sysfs # |
MXM3 Pin |
PTA3 |
859 |
197 |
PTA5 |
861 |
195 |
PTA17 |
873 |
187 |
PTB10 |
902 |
297 |
PTB11 |
903 |
281 |
PTB16 |
904 |
265 |
PTB17 |
905 |
177 |
PTB18 |
906 |
295 |
PTB19 |
907 |
279 |
PTC0 |
920 |
263 |
PTC1 |
921 |
293 |
PTC2 |
922 |
277 |
PTC3 |
923 |
261 |
PTC4 |
924 |
249 |
PTC6 |
926 |
275 |
PTC7 |
927 |
259 |
PTD0 |
952 |
291 |
PTD1 |
953 |
289 |
PTD2 |
954 |
273 |
PTD3 |
955 |
257 |
PTD4 |
956 |
247 |
PTD5 |
957 |
245 |
PTD6 |
958 |
255 |
PTD7 |
959 |
243 |
PTD8 |
960 |
253 |
PTD9 |
961 |
251 |
PTD11 |
963 |
271 |
PTD12 |
964 |
269 |
PTD13 |
965 |
287 |
PTD14 |
966 |
283 |
PTD15 |
967 |
299 |
PTE0 |
984 |
175 |
PTE1 |
985 |
173 |
PTE2 |
986 |
301 |
PTE3 |
987 |
179 |
PTE4 |
988 |
185 |
PTE5 |
989 |
181 |
PTE24 |
1008 |
183 |
PTE25 |
1009 |
191 |
In order to erase K20 and leave it empty replace firmware file (/lib/firmware/apalis-tk1-k20.bin) with a 1-byte length file.
Easiest way of doing it is to use dd:
dd if=/dev/zero of=/lib/firmware/apalis-tk1-k20.bin bs=1 count=1
after firmware is replaced all you need to do is reload the kernel module or reboot the board.
You should see:
apalis-tk1-k20 spi1.1: Chip fully erased.
in kernel log.
To force the kernel module to reflash the K20 use force_fw_reload=1
module parameter when loading
Module parameter fw_ignore=1
makes the kernel module assume that k20 is running the valid up-to-date image.
- Kernel modules sources can be found in our kernel git:
http://git.toradex.com/cgit/linux-toradex.git/log/?h=toradex_tk1_l4t_r21.7
- K20 firmware is located in our FreeRTOS git:
https://github.com/toradex/FreeRTOS-Apalis-TK1-K20
Note: The previous git repository (http://git.toradex.com/cgit/freertos-toradex.git) was retired and replaced by the new repository on github
It is possible to use custom software instead of the one provided by us.
Source code provided on our Git is based on KSDK 2.0.0 (provided by NXP), that was updated to use FreeRTOS 10.1.1.
To compile the code you'll need an armv7m GCC toolchain in your search path, you can get it directly from ARM here.
git clone --recursive https://github.com/toradex/FreeRTOS-Apalis-TK1-K20 -b master
cd FreeRTOS-Apalis-TK1-K20
For the older repo location use the following line to do the clone:
git clone git://git.toradex.com/freertos-toradex.git -b apalis-tk1-k20-freertos-v10
cd freertos-toradex
To build the checked out code:
mkdir build
cd build
cmake -DCMAKE_BUILD_TYPE=RELEASE -DCROSS_COMPILE_PREFIX=arm-none-eabi- ../
make
Now you should have apalis-tk1-k20.bin
binary file that can be flashed to the k20 via EzPort. File apalis-tk1-k20
should also be present, it's an elf file that is useful for debugging.
K20 and TK1 share single SPI bus and 4 GPIO lines as well as SPI CS and EzPort CS.
TK1 Has control over K20 reset line.
Here is an example of how to flash a new binary file into the K20 via EzPort directly from the TK1. You will have to use the gcc in the TK1 or cross-compile it from the host machine (and then send it to the device). Take into account that you may have to make some changes in the source code before compiling (Like changing the binary filename).
Example C file
/*
Based on spidevlib.c - A user-space program to communicate using spidev.
Gustavo Zamboni
*/
#include <stdint.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <getopt.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/types.h>
#include <linux/spi/spidev.h>
char buf[64];
char buf2[1024];
int com_serial;
int failcount;
struct spi_ioc_transfer xfer[2];
//////////
// Init SPIdev
//////////
int spi_init(char filename[40])
{
int file;
__u8 mode, lsb, bits;
__u32 speed=3180000;
if ((file = open(filename,O_RDWR)) < 0)
{
printf("Failed to open the bus.\n");
/* ERROR HANDLING; you can check errno to see what went wrong */
com_serial=0;
exit(1);
}
///////////////
// Verifications
///////////////
//possible modes: mode |= SPI_LOOP; mode |= SPI_CPHA; mode |= SPI_CPOL; mode |= SPI_LSB_FIRST; mode |= SPI_CS_HIGH; mode |= SPI_3WIRE; mode |= SPI_NO_CS; mode |= SPI_READY;
//multiple possibilities using |
mode = SPI_MODE_0;
if (ioctl(file, SPI_IOC_WR_MODE, &mode)<0) {
perror("can't set spi mode");
return -ENOENT;
}
if (ioctl(file, SPI_IOC_RD_MODE, &mode) < 0)
{
perror("SPI rd_mode");
return -ENOENT;
}
if (ioctl(file, SPI_IOC_RD_LSB_FIRST, &lsb) < 0)
{
perror("SPI rd_lsb_fist");
return -ENOENT;
}
//sunxi supports only 8 bits
/*
if (ioctl(file, SPI_IOC_WR_BITS_PER_WORD, 8)<0)
{
perror("can't set bits per word");
return;
}
*/
if (ioctl(file, SPI_IOC_RD_BITS_PER_WORD, &bits) < 0)
{
perror("SPI bits_per_word");
return -ENOENT;
}
if (ioctl(file, SPI_IOC_WR_MAX_SPEED_HZ, &speed)<0)
{
perror("can't set max speed hz");
return -ENOENT;
}
if (ioctl(file, SPI_IOC_RD_MAX_SPEED_HZ, &speed) < 0)
{
perror("SPI max_speed_hz");
return -ENOENT;
}
printf("%s: spi mode %d, %d bits %s per word, %d Hz max\n",filename, mode, bits, lsb ? "(lsb first) " : "", speed);
//xfer[0].tx_buf = (unsigned long)buf;
xfer[0].len = 3; /* Length of command to write*/
xfer[0].cs_change = 0; /* Keep CS activated */
xfer[0].delay_usecs = 0, //delay in us
xfer[0].speed_hz = speed, //speed
xfer[0].bits_per_word = 8, // bites per word 8
//xfer[1].rx_buf = (unsigned long) buf2;
xfer[1].len = 4; /* Length of Data to read */
xfer[1].cs_change = 0; /* Keep CS activated */
xfer[1].delay_usecs = 0;
xfer[1].speed_hz = speed;
xfer[1].bits_per_word = 8;
return file;
}
//////////
// Read n bytes from the 2 bytes add1 add2 address
//////////
char * spi_read(int addr, int nbytes, int file)
{
int status;
memset(buf, 0, sizeof buf);
memset(buf2, 0, sizeof buf2);
buf[0] = addr;
xfer[0].tx_buf = (unsigned long)buf;
xfer[0].len = 1; /* Length of command to write*/
xfer[1].rx_buf = (unsigned long) buf2;
xfer[1].len = nbytes; /* Length of Data to read */
status = ioctl(file, SPI_IOC_MESSAGE(2), xfer);
if (status < 0)
{
perror("SPI_IOC_MESSAGE");
return NULL;
}
//printf("env: %02x %02x %02x\n", buf[0], buf[1], buf[2]);
//printf("ret: %02x %02x %02x %02x\n", buf2[0], buf2[1], buf2[2], buf2[3]);
com_serial=1;
failcount=0;
return buf2;
}
//////////
// Read n bytes from the 2 bytes add1 add2 address
//////////
char * spi_read_flash(int addr, int nbytes, int file)
{
int status;
memset(buf, 0, sizeof buf);
memset(buf2, 0, sizeof buf2);
buf[0] = 0x0b;
buf[1] = (addr & 0xFF0000) >> 16;
buf[2] = (addr & 0xFF00) >> 8;
buf[3] = (addr & 0xF8);
buf[4] = 0x00;
xfer[0].tx_buf = (unsigned long)buf;
xfer[0].len = 5; /* Length of command to write*/
xfer[1].rx_buf = (unsigned long) buf2;
xfer[1].len = nbytes; /* Length of Data to read */
status = ioctl(file, SPI_IOC_MESSAGE(2), xfer);
if (status < 0)
{
perror("SPI_IOC_MESSAGE");
return NULL;
}
//printf("env: %02x %02x %02x\n", buf[0], buf[1], buf[2]);
//printf("ret: %02x %02x %02x %02x\n", buf2[0], buf2[1], buf2[2], buf2[3]);
com_serial=1;
failcount=0;
return buf2;
}
//////////
// Write n bytes int the 2 bytes address add1 add2
//////////
void spi_write(int addr,int nbytes, uint8_t * out_buf, int file)
{
unsigned char buf[32], buf2[32];
int status;
if (nbytes > 31)
return;
memcpy(&buf[1], out_buf, nbytes);
buf[0] = addr;
xfer[0].tx_buf = (unsigned long)buf;
xfer[0].len = nbytes+1; /* Length of command to write*/
status = ioctl(file, SPI_IOC_MESSAGE(1), xfer);
if (status < 0)
{
perror("SPI_IOC_MESSAGE");
return;
}
com_serial=1;
failcount=0;
}
void spi_write_flash(int addr, int sector ,int nbytes,char value[64],int file)
{
unsigned char buffer[68];
int status;
memset(buffer, 0, sizeof buffer);
buf[0] = addr;
buf[1] = (sector & 0xFF0000) >> 16;
buf[2] = (sector & 0xFF00) >> 8;
buf[3] = (sector & 0xF8);
memcpy(&buf[4], value, 64);
xfer[0].tx_buf = (unsigned long)buf;
xfer[0].len = nbytes+4; /* Length of command to write*/
status = ioctl(file, SPI_IOC_MESSAGE(1), xfer);
if (status < 0)
{
perror("SPI_IOC_MESSAGE");
return;
}
com_serial=1;
failcount=0;
}
int export_gpios(){
int sysexport, sysdirection;
sysexport = open("/sys/class/gpio/export", O_WRONLY);
if (sysexport < 0)
return -2;
//exports
write(sysexport, "222", 3); // MCU Reset
write(sysexport, "178", 3); // EzPort CS
write(sysexport, "190", 3); // MCU SPI CS (must be high when MCU is being reset for SPI to work)
write(sysexport, "82", 2);
write(sysexport, "74", 2);
close(sysexport);
//direction & initial value
sysdirection = open("/sys/class/gpio/gpio222/direction", O_WRONLY);
if (sysdirection < 0)
return -2;
write(sysdirection, "high", 4);
close(sysdirection);
sysdirection = open("/sys/class/gpio/gpio178/direction", O_WRONLY);
if (sysdirection < 0)
return -2;
write(sysdirection, "high", 4);
close(sysdirection);
sysdirection = open("/sys/class/gpio/gpio190/direction", O_WRONLY);
if (sysdirection < 0)
return -2;
write(sysdirection, "high", 4);
close(sysdirection);
sysdirection = open("/sys/class/gpio/gpio82/direction", O_WRONLY);
if (sysdirection < 0)
return -2;
write(sysdirection, "in", 2);
close(sysdirection);
sysdirection = open("/sys/class/gpio/gpio74/direction", O_WRONLY);
if (sysdirection < 0)
return -2;
write(sysdirection, "in", 2);
close(sysdirection);
return 0;
}
#define CS_LOW do {buf1[0] = '0';write(cs, buf1, 1);}while(0)
#define CS_HIGH do {buf1[0] = '1';write(cs, buf1, 1);}while(0)
#define CS_APP_LOW do {buf1[0] = '0';write(cs_app, buf1, 1);}while(0)
#define CS_APP_HIGH do {buf1[0] = '1';write(cs_app, buf1, 1);}while(0)
int main(void) {
char *buffer;
char buf1[64];
char buf2[64];
char rbuf[64];
int file, cs, binary, rst;
int cs_app;
int read_cnt, file_size = 0;
int i = 0;
file = spi_init("/dev/spidev1.2"); //dev
if (export_gpios() < 0) {
printf("failed to setup GPIOs\n");
return -2;
}
//Binary file: test.bin
binary = open("k20_tester.bin", O_RDONLY);
cs = open("/sys/class/gpio/gpio178/value", O_WRONLY);
cs_app = open("/sys/class/gpio/gpio190/value", O_WRONLY);
rst = open("/sys/class/gpio/gpio222/value", O_WRONLY);
if ( file < 0) {
printf("failed to open spi\n");
return -2;
}
if ( cs < 0) {
printf("failed to open /sys/class/gpio/gpio178/value\n");
return -2;
}
if ( rst < 0) {
printf("failed to open /sys/class/gpio/gpio222/value\n");
return -2;
}
if ( binary < 0) {
printf("failed to open test.bin\n");
return -2;
}
printf("Entering EzPort mode....\t");
buf1[0] = '1';
write(rst, buf1, 1);
usleep(100);
CS_LOW;
usleep(100);
buf1[0] = '0';
write(rst, buf1, 1);
usleep(100);
buf1[0] = '1';
write(rst, buf1, 1);
usleep(100);
CS_HIGH;
usleep(100);
CS_LOW;
buffer = spi_read(0x05, 1, file);
CS_HIGH;
//printf("EzPort Status Register = 0x%X\n", buffer[0]);
CS_LOW;
spi_write(0x06, 0, buf1, file);
buf1[0] = '1';write(cs, buf1, 1);
CS_HIGH;
CS_LOW;
buffer = spi_read(0x05, 1, file);
CS_HIGH;
printf("EzPort Status Register = 0x%X\n", buffer[0]);
if (buffer[0] != 0x02) {
printf("FAIL\n");
close(binary);
close(cs);
close(file);
close(rst);
return 0;
}
printf("Done\n");
CS_LOW;
buffer = spi_read_flash(0x400, 16, file);
CS_HIGH;
printf("FTFL 0x%X%X%X%X%X%X%X%X \r\n",buffer[7],buffer[6],buffer[5],buffer[4],buffer[3],buffer[2],buffer[1],buffer[0]);
printf("FTFL 0x%X%X%X%X%X%X%X%X \r\n",buffer[15],buffer[14],buffer[13],buffer[12],buffer[11],buffer[10],buffer[9],buffer[8]);
printf("EzPort Bulk Erase...");
CS_LOW;
spi_write(0xc7, 0, buf1, file);
CS_HIGH;
buffer[0] = 0x01;
/* Wait for WIP flag to clear*/
while (buffer[0] != 0x00) {
CS_LOW;
buffer = spi_read(0x05, 1, file);
buf1[0] = '1';
write(cs, buf1, 1);
if (buffer[0] & 0x40) {
printf("FAIL\n");
printf("EzPort WEF asserted!\n");
close(binary);
close(cs);
close(file);
close(rst);
return 0;
}
}
printf(" Complete\n");
printf("EzPort Flashing ...");
while( (read_cnt = read(binary, rbuf, 64)) > 0){
/* Set WEN bit */
CS_LOW;
spi_write(0x06,0,buf1,file);
CS_HIGH;
/* Write flash sector */
CS_LOW;
spi_write_flash(0x02, 64*i, 64, rbuf, file);
CS_HIGH;
memset(rbuf, 0, sizeof(rbuf));
i++;
file_size += read_cnt;
buffer[0] = 0x01;
/* Wait for WIP flag to clear*/
while (buffer[0] != 0x00) {
CS_LOW;
buffer = spi_read(0x05, 1, file);
CS_HIGH;
if (buffer[0] & 0x40) {
printf("FAIL\n");
printf("EzPort WEF asserted!\n");
close(binary);
close(cs);
close(file);
close(rst);
return 0;
}
}
}
printf("Done\n");
printf("EzPort Flash complete, %d bytes wrote.\n", file_size);
printf("EzPort Flash verification...");
lseek(binary, 0,SEEK_SET);
for (int j = 0; j < i; j++) {
CS_LOW;
buffer = spi_read_flash(64*j, 64, file);
CS_HIGH;
read_cnt = read(binary, rbuf, 64);
if (memcmp(rbuf, buffer, read_cnt) != 0) {
printf("FAIL\n");
printf("Flash verification failed @ 0x%X\n", 64*j);
return -5;
}
}
printf("Done\n");
printf("Resetting the MCU....");
CS_HIGH;
buf1[0] = '0';
write(rst, buf1, 1);
usleep(100);
buf1[0] = '1';
write(rst, buf1, 1);
printf("Done\n");
close(binary);
close(cs);
close(file);
close(rst);
return 0;
}
It is possible to use GPIO lines on TK1 with OpenOCD bitbang mode to program\erase the MCU. It's especially useful if you manage to lock EZPort mode.
TMS pin (MXM3 197) needs to be connected to the GPIO1 outside the module, optionally you can connect TRST pin (MXM195) to GPIO2.
OpenOCD package can be build using our OE setup (more in OpenEmbedded (core)) and installed using opkg.
Warning: Make sure that your firmware is not configuring any of the K20 JTAG pins for other functions.
Note: Toradex stock firmware configures pin PTA3 as a GPIO. Just uncomment #define USE_SWO in utilities/fsl_debug_console.h to build a firmware that can be debugged via JTAG/SWD .
After installing OpenOCD and making necessary connections run:
rmmod gpio_apalis_tk1_k20 apalis_tk1_k20_ts apalis_tk1_k20_can apalis_tk1_k20_adc apalis_tk1_k20
echo 222 > /sys/class/gpio/export
echo low > /sys/class/gpio/gpio222/direction
openocd -f <interface config file> -f target/kx.cfg
If K20's reset vector is invalid you'll see (don't be alarmed by other reported errors):
Warn : **** Your Kinetis MCU is probably locked-up in RESET/WDOG loop. ****
Warn : **** Common reason is a blank flash (at least a reset vector). ****
Warn : **** Issue 'kinetis mdm halt' command or if SRST is connected ****
Warn : **** and configured, use 'reset halt' ****
Warn : **** If MCU cannot be halted, it is likely secured and running ****
Warn : **** in RESET/WDOG loop. Issue 'kinetis mdm mass_erase' ****
Just follow K20 clearing instructions below and try again
Otherwise:
Info : SysfsGPIO JTAG/SWD bitbang driver
Info : JTAG only mode enabled (specify swclk and swdio gpio to add SWD mode)
Info : This adapter doesn't support configurable speed
Info : JTAG tap: kx.cpu tap/device found: 0x4ba00477 (mfg: 0x23b (ARM Ltd.), part: 0xba00, ver: 0x4)
Info : kx.cpu: hardware has 6 breakpoints, 4 watchpoints
now you should be able to connect gdb over the network:
target remote <APALIS_TK1_IP>:3333
monitor srst_deasserted
monitor kinetis mdm halt
To clear K20 run in a separate shell:
telnet <APALIS_TK1_IP> 4444
srst_deasserted
kinetis mdm halt
kinetis mdm mass_erase
now K20 should be completely empty with EzPort operations enabled.
Since K20 SPI pins are connected to JTAG pins (JTAG pins funcion as EzPort pins).
If you're application uses SPI for communication with TK1 SoC you can't use this port and debug at the same time.
One possible workaround is to use one of the Apalis standard SPIs, an alternate muxing for SPI2 on K20 and connect them externally.
Since JTAG is bitbanged via GPIO TCK is ~100kHz, so operation is a bit sluggish.
#
# Config for debugging K20 MCU on Apalis TK1
#
interface sysfsgpio
# Each of the JTAG lines need a gpio number set: tck tms tdi tdo
# TCK -> GPIO_PX5 (sysfs 189)
# TMS -> GPIO_PFF2(sysfs 250) external loop MXM197(CAM1_HSYNC) to MXM3(GPIO1)
# TDI -> GPIO_PX4 (sysfs 188)
# TDO -> GPIO_PX7 (sysfs 191)
sysfsgpio_jtag_nums 189 250 188 191
# At least one of srst or trst needs to be specified
# MCU_RESET -> GPIO_PBB6 (sysfs 222)
# (optional) JTAG TRST -> GPIO_FF0 (sysfs 248) external loop MXM195(CAM1_VSYNC) to MXM5(GPIO2)
sysfsgpio_trst_num 248
sysfsgpio_srst_num 222
reset_config trst_and_srst separate
reset_config srst_nogate
reset_config connect_assert_srst
In order to use external debugger for development or flashing hardware modifications are required.
Both SWD and JTAG can be used with proper modifications.
Warning: Make sure that your firmware is not configuring any of the K20 JTAG pins for other functions.
Note: Toradex stock firmware configures pin PTA3 as a GPIO. Just uncomment #define USE_SWO in utilities/fsl_debug_console.h to build a firmware that can be debugged via JTAG/SWD.