Search by Tags

Using DIP Switches to change the boot process

 

Subscribe for this article updates

Introduction

One of the most commonly used mechanisms of swapping between different stablished boot processes or configurations is by using a DIP Switch. This small manual electric switch contains n different setting values which would lead to 2^n different status: A 2 bit DS, would let swapping between 4 different states; a 3 bit DS, 8, and so on.

The idea behind a dip switch circuit is very simple and can vary depending on the default state you want the circuit to have. For instance, if the DS will be closed by default, leaving a path to GND, the pin connected to it will read a 0, while, if the DS is opened, a path to VCC will open and this will change to a 1.

Warning: Remember using pull-up/pull-down resistors in your circuit to avoid creating a short between VCC and GND in any case.

In our example, we will be using a 2 bit dip switch, in order to have up to 4 different boot processes, with our Colibri iMX6ULL and our Iris Carrier Board. This inexpensive combination makes it suitable for DIY projects, projects where not much computational power is required or no need for a dedicated GPU/OpenGL processor.

Adding the logic to our boot process.

Setting up the environment

In order to change the boot process, we will be making some changes in the bootloader logic of our boards. The bootloader is the first piece of code executed, and it is usually located in a hardcoded address defined by the SoC vendor and the fusing of the chip. Toradex's modules use U-boot as Linux bootloader and is where we will be applying our changes to in this example.

Before jumping directly to the software changes, make sure you have a cross-compilation environment ready. If you don't, make sure to check the Toradex Getting Started guide.

Once you have downloaded and setup the environment with your toolchain and your environment variables (ARCH, CROSS_COMPILE and PATH should suffice), install Git (if you don't have it already) and clone Toradex U-boot repository:

cd
mkdir DIPexample
git clone git://git.toradex.com/u-boot-toradex.git
cd u-boot-toradex/
git checkout 2016.11-toradex

Note: If you have problems with the download, check with "git clone https://..." or "git clone http://... instead of git://. It may be capped by your firewall rules"

With this, you will have Toradex U-boot repository with the Colibri iMX6ULL (and others) board specific files in it.

In order to quickly check if everything is in order, let's try a compilation with the default settings of U-boot. Additionally, in the U-boot compilation, the device tree is also compiled, which is what keeps the hardware information and is used by U-boot to know the detailed system information.

$ sudo apt-get install device-tree-compiler

Note: This applies for Ubuntu. For other distros, use the advised package manager.

u-boot-toradex
$ make colibri-imx6ull_defconfig $ make -j4

Note: You can ignore the DTB format warnings that may appear.

If you are having an error at this point, is likely that you haven't set the environment variables. Make sure that the toolchain is correctly pointed and the architecture selected:

$ echo $ARCH
arm
$ echo $CROSS_COMPILE
arm-linux-gnueabihf-
$ echo $PATH
/home/alvaro/gcc-linaro-7.3.1-2018.05-x86_64_arm-linux-gnueabihf/bin/:/home/alvaro/platform-tools:/home/alvaro/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin

Note: Your PATH and/or toolchain version may differ.

Knowing the files used

In this example, we will be changing the board files for the Colibri iMX6ULL:

u-boot-toradex
board/toradex/colibri-imx6ull/colibri_imx6ull.c

Note: You can expect similar paths for other modules.

This file contains the board-specific boot-process logic, and where we will be adding our changes.

In order to use this file correctly, we should take into account 3 additional c:

  1. The defined name of the pin we will be changing
  2. The function of the pin we will be setting (In this case as GPIO).
  3. Whether the pin is being already used or not.

In this case, we will be using 2 of the default GPIO pins found in the Colibri iMX6ULL and the Iris Carrier board. However, take into account that if required, you can use many of the others pins available from unused functions.

In order to better choose the pins, we recommend going through the Iris Carrier Board datasheet and Colibri iMX6ULL datasheet: Colibri iMX6ULL Datasheet Iris Carrier Board Datasheet

For this example, we will be using the following pins from the X16 Extension Connector of the Iris:

  • X16_13 from the Iris, which is pin SODIMM_98 in the Colibri iMX6ULL
  • X16_14 from the Iris, which is pin SODIMM_133 in the Colibri iMX6ULL

The pinmux with the pin definitions and its different functions available is covered in the following file:

u-boot-toradex
arch/arm/include/asm/arch-mx6/mx6ull_pins.h

While the pinmux definitions and bit fields are covered in:

u-boot-toradex
arch/arm/include/asm/imx-common/iomux-v3.h

Note: Pin refers to the physical pin of the SoC/SoM, while pinmux stands for Pin Multiplexing, and its a convenient way of supporting more functions than available pins. This documentation is nothing more than showing how to use the pinmux in order to change the default function to a GPIO (if required) and then read the value of the selected GPIO during the bootloader phase.

You may find the field information in Toradex Device Tree customization page and in the Colibri iMX6ULL Datasheet: https://developer.toradex.com/device-tree-customization#pinmux-imx6

Note: The detailed meaning of all these fields can be found in the i.MX6 ULL Reference Guide from NXP.

Going back to our pins, according to the Colibri iMX6ULL datasheet, we obtain the following information:

  • The i.MX 6ULL for SODIMM_98 is CSI_DATA05
  • The i.MX 6ULL for SODIMM_133 is NAND_CE1_B
  • As expected, both pins are already defined as GPIO at reset and/or don't have a default function (since they are defined by the Colibri standard as GPIO)
  • The ALT function for the GPIO in the Colibri iMX6ULL is number 5.
  • The GPIO register value for SODIMM_98 is (4,26) (gpio4.IO[26])
  • The GPIO register value for SODIMM_133 is (4,14) (gpio4.IO[14])
  • Both pins have by default the Keeper function, which means that the pin is able to keep the previous output value when the output driver is disabled.

Note: The IOMUX_PAD macro function is just a convenient way to convert the different field values into the corresponding bit fields.

Making our changes

With the above information clear, we can go back to our board file and add our changes.

Every function in the board file defines the routine at a certain point of the booting process (board_eth_init, board_mmc_init...). In this case, we decided to add our logic after other more critical options, so we choose the board_late_init function, but it is completely up to the user where to add their logic.

In order to change the GPIO values of a GPIO pin, we will have to go through 3 steps:

  1. Define the GPIO pins to use (Although you can use the GPIO function directly). We will use the GPIO register values from the datasheet.
#define DIP_SW0   IMX_GPIO_NR(4, 26)
#define DIP_SW1   IMX_GPIO_NR(4, 14)
  1. Configure the pins as GPIO. We will have to use the pinmux names for those pins, which we obtain from mx6ull_pins.h, with the pinmux definition of GPIO (which for the i.MX 6ULL is NO_PAD_CTRL)
static iomux_v3_cfg_t const dip_switch_pads[] = {
    MX6_PAD_CSI_DATA05__GPIO4_IO26 | MUX_PAD_CTRL(NO_PAD_CTRL),
    MX6_PAD_NAND_CE1_B__GPIO4_IO14 | MUX_PAD_CTRL(NO_PAD_CTRL),
};
  1. Allocate the pins and read the value. We will use the GPIO Kernel API. You can find more in the kernel documentation: https://www.kernel.org/doc/Documentation/gpio/sysfs.txt https://www.kernel.org/doc/html/latest/driver-api/gpio/index.html
imx_iomux_v3_setup_multiple_pads(dip_switch_pads, ARRAY_SIZE(dip_switch_pads));
gpio_request(DIP_SW0, "DIP_SW0");
gpio_request(DIP_SW1, "DIP_SW1");
 
int dip0;
int dip1;
 
dip0 = gpio_get_value(DIP_SW0);
dip1 = gpio_get_value(DIP_SW1);

At this point, you can add pretty much any logic you want depending on the values read in the DIP Switch.

One quick test you can do is simply adding a printf with both values in the U-boot output:

printf("DIP Switch: %x%x\n", dip0, dip1);

Another desired change would be to add or change the environment variables found in U-boot. Some examples would be:

Note: Toradex modules feature 2 different flash technologies: NAND-RAW and eMMC. Colibri iMX6ULL modules uses NAND-RAW.

All these can be achieved with the setenv(name, value), and many of these above examples can be actually found in the Toradex Developer site. In this example, we are going to use the DIP_SW0 to change between framebuffer configurations (in case we have 2 different LCD options).

Compiling and flashing our changes

In order to flash the new U-boot, the easiest way is to use Toradex Easy Installer (TEZI).

We will be using an already formatted Colibri iMX6ULL TEZI image, where we will simply overwrite the U-boot binary before flashing.

  1. Download a Colibri iMX6ULL TEZI image from the Toradex Easy installer page.
  2. Extract the content of the compressed file in your USB drive/SD card.
Removable Device
$ tar xvf <Path of your tar>/colibri-imx6ull_lxde-image-tezi_2.8...tar --no-sameowner
  1. Substitute the original U-boot binary of the TEZI image from the one with the compiled changes.
Removable Device/Colibri-iMX6ULL...
$ sudo cp <uboot-toradex folder>/u-boot-nand.imx u-boot-nand.imx $ sudo sync
  1. Launch TEZI and install the image.

Note: In modules with an eMMC flash, you can use the UMS function of U-boot to easily modify the contents of the flash memory without having to use TEZI. You can check more information about this function in "How to Clone Embedded Linux on eMMC Based Toradex Modules" documentation.

Finished code

#define DIP_SW0   IMX_GPIO_NR(4, 26)
#define DIP_SW1   IMX_GPIO_NR(4, 14)
 
static iomux_v3_cfg_t const dip_switch_pads[] = {
    MX6_PAD_CSI_DATA05__GPIO4_IO26 | MUX_PAD_CTRL(NO_PAD_CTRL),
    MX6_PAD_NAND_CE1_B__GPIO4_IO14 | MUX_PAD_CTRL(NO_PAD_CTRL),
};
 
int board_late_init(void)
{
...
    // DIP Switch routine
    imx_iomux_v3_setup_multiple_pads(dip_switch_pads, ARRAY_SIZE(dip_switch_pads));
    gpio_request(DIP_SW0, "DIP_SW0");
    gpio_request(DIP_SW1, "DIP_SW1");
 
    int dip0;
    int dip1;
 
    dip0 = gpio_get_value(DIP_SW0);
    dip1 = gpio_get_value(DIP_SW1);
 
    printf("DIP Switch: %x%x\n", dip0, dip1);
 
    // EDT 5.7" TFT VGA
    if (dip0 == 0) {
        setenv("vidargs", "video='mxsfb:640x480M-16@60'");
    }
    // EDT 7.0" TFT WVGA
    else if (dip0 == 1) {
        setenv("vidargs", "video='mxsfb:800x480M-16@60'");
    }
 
    return 0;
}