How to integrate a new GWT board into the GAP SDK
This tutorial is for Greenwaves developpers who wants to integrate a new board that embbeds a GAP chip into the SDK.
Here, you will learn how to describe the hardware of your board inside the SDK, create dependencies with low level interface drivers, get unique peripheral instances, automatically configure GAP pads, create options to control drivers compilation and more.
Here are the steps to fully integrate a new board to the SDK:
Choose a name for your board
Create an option that symbolize the board.
Link it to an existing GAP chip and all hardware peripherals embedded on the board.
Create a devicetree file dedicated to your board.
For each hardware peripheral: add a node to the devicetree.
Create BSP files for this board.
Write specific content related to your board in order to handle particular implementations.
Use your board in an application
Choose a name
The name of your board is very important because SDK scripts will look for this name during the parsing process and expect to find files including it in their name. This condition is mandatory to be able to synchronize all parts of this process.
For this tutorial, our new fictional board will be called GAP_HEADSET_V1. It will be refered as <BOARD_NAME> further in this tutorial.
Create the board’s option
This board must be selected in the SDK menuconfig interface. The point of selecting a board is to access available drivers from it and obtain the right configuration each of them.
From the SDK’s board menu location, create a new folder (you can organize directories depending on board categories…) and create a new kconfig file that will contain your board description:
cd $GAP_SDK_HOME/utils/kconfig/boards/
mkdir tutorial_boards
cd tutorial_boards
touch tutorial_boards.kconfig
Then, link this new file to the menu SDK board menu kconfig file : $GAP_SDK_HOME/utils/kconfig/boards/board.kconfig in the BOARD choice.
In your new file, create a new kconfig option respecting the following syntax : BOARD_<BOARD_NAME>
config BOARD_GAP_HEADSET_V1
bool "Gap Headset v1"
If your board is an addon to another existing one <EXISTING_BOARD>, create a dependency to this existing board with the following statement below the option:
depends on <EXISTING_BOARD>
Link your board to the SDK
Now, you have a location where you can tell what is embedded on your board, starting with the chip. In $GAP_SDK_HOME/utils/kconfig/chips/chips.kconfig, find the option related to the chip implemented on your board. For example, if your board includes a GAP9 V2 chip with WLCSP package, add the following line under your board option :
config BOARD_GAP_HEADSET_V1
bool "Gap Headset v1"
depends on CHIP_GAP9_V2_WLCSP
The process is the same for drivers. From $GAP_SDK_HOME/utils/kconfig/drivers directory, you can find a folder for a driver category which contain a kconfig file. In these kconfig file, you can find options named under the following convention :
HAS_DRIVERNAME_HARDWARE
With DRIVERNAME standing for the name of the peripheral. Such kind of options aims to tell that a board embbeds one or multiple instances of this peripheral. Browse these kconfig files to find options you need for your board. As an example, our board embbeds a MX25U51245G FLASH two SSM6515 DAC devices and one gpio expander FXL6408.
config BOARD_GAP_HEADSET_V1
bool "Gap Headset v1"
depends on CHIP_GAP9_V2_WLCSP
select HAS_MX25U51245G_HARDWARE
select HAS_SSM6515_HARDWARE
select HAS_FXL6408_HARDWARE
Devicetree file creation
When it is about GWT boards, devicetree files are located here : $GAP_SDK_HOME/utils/dt/src/boards Inside a new subfodler, create a new file with the same syntax as your option in lower case as the following:
cd $GAP_SDK_HOME/utils/dt/src/boards
mkdir tutorial_boards
cd tutorial_boards
touch gap_headset_v1.dts
As explain in GAP’s devicetree , board’s devicetree files are overlays to chip’s devicetree file. We can see it as a plugin. Here is a template of an empty devicetree file :
/dts-v1/;
/plugin/;
/{
gap_headset_v1
{
compatible = "gwt, gap9";
target-path = "/";
__overlay__
{
};
};
};
Devicetree nodes
Now we are about to fill in the devicetree file with hardware description. for each hardware peripheral, create a node respecting the following syntax:
devicetree_node_label: devicetree_node_name
{
status = "okay";
sys
{
device_name = "";
driver_name = "";
device_type = "";
device_board = "";
device_driver = "";
};
conf
{
};
pads
{
};
defs
{
};
}
Note
For more details about devicetree node, check the devicetree_source_file page in the devicetree user guide.
Note
devicetree_node_label and devicetree_node_name are not used by the GAP SDK parser. They can be arbitrary set as long as they have a unique name.
Fill the four members of the sys subnode as the following :
driver_name: Driver name as it defined in the SDK.
device_name: The device name. Used to create unique instance names in case a board embedds the same hardware twice.
device_type: The device type. Used to identify default driver.
device_board: The board on which the driver is available
device_driver: The path to include its driver in the sdk
Set the desired value of each member of the driver’s configuration structure in conf subnode. As an example, if your driver has a member name i2s_itf symbolizing the SAI interface, set it as the following :
conf
{
i2s_itf = <1>; // SAI interface 1 is selected
};
Configure the pads required by the driver as the following in pads subnode:
pads
{
my_pad = GAP9_PADMUX(<pad_id>, <pad_af>, <pad_muxgroup>)
}
pad_id : Pad identification (Pad n°34)
pad_af: Pad alternate function (Alternate function 1 : GPIO)
pad_muxgroup: Pad mux group. Set to -1 if unused
Warning
Muxgroup configuration is not yet supported by the devicetree. Please keep it set to “-1”
In the previous section we decided that our baord implement one Flash, two Dacs and one GPIO expander. Here is a possible implementation of the devicetree:
/dts-v1/;
/plugin/;
/{
gap_headset_v1
{
compatible = "gwt, gap9";
target-path = "/";
__overlay__
{
mx25u51245g: mx25u51245g
{
status = "okay";
sys
{
device_name = "mx25u51245g";
driver_name = "mx25u51245g";
device_type = "flash";
device_board = "gap_headset_v1";
device_driver = "bsp/flash/mx25u51245g.h";
};
conf
{
spi_itf = <0>;
spi_cs = <0>;
size = <0x00800000>;
sector_size = <4096>;
xip_en = <0>;
baudrate = <0>;
};
pads
{
ck = "GAP9_PADMUX(1, 0,-1)";
dq0 = "GAP9_PADMUX(2, 0,-1)";
dq1 = "GAP9_PADMUX(3, 0,-1)";
dq2 = "GAP9_PADMUX(4, 0,-1)";
dq3 = "GAP9_PADMUX(5, 0,-1)";
dq4 = "GAP9_PADMUX(6, 0,-1)";
dq5 = "GAP9_PADMUX(7, 0,-1)";
dq6 = "GAP9_PADMUX(8, 0,-1)";
dq7 = "GAP9_PADMUX(9, 0,-1)";
sn0 = "GAP9_PADMUX(10,0,-1)";
csn1 = "GAP9_PADMUX(11,0,-1)";
rwds = "GAP9_PADMUX(12,0,-1)";
};
};
ssm6515_left: ssm6515_left
{
status = "okay";
sys
{
device_name = "ssm6515_left";
driver_name = "ssm6515";
device_type = "dac";
device_board = "gap_headset_v1";
device_driver = "bsp/dac/ssm6515/ssm6515.h";
};
conf
{
i2s_itf = <2>;
i2c_itf = <1>;
i2c_slave_addr = "0b01101000"; // ADDR pin is set to GND
};
pads
{
i2c_sda = "GAP9_PADMUX(42,0,-1)";
i2c_scl = "GAP9_PADMUX(43,0,-1)";
sai_clk = "GAP9_PADMUX(56,0,-1)";
sai_ws = "GAP9_PADMUX(57,0,-1)";
sai_sdi = "GAP9_PADMUX(58,0,-1)";
sai_sdo = "GAP9_PADMUX(59,0,-1)";
};
};
ssm6515_right: ssm6515_right
{
status = "okay";
sys
{
device_name = "ssm6515_right";
driver_name = "ssm6515";
device_type = "dac";
device_board = "gap_headset_v1";
device_driver = "bsp/dac/ssm6515/ssm6515.h";
};
conf
{
i2s_itf = <1>;
i2c_itf = <1>;
i2c_slave_addr = "0b01101100"; // ADDR pin is open
bsp_open = "NULL";
bsp_close = "NULL";
bsp_conf = "NULL";
};
pads
{
i2c_sda = "GAP9_PADMUX(42,0,-1)";
i2c_scl = "GAP9_PADMUX(43,0,-1)";
sai_clk = "GAP9_PADMUX(52,0,-1)"; // SAI1_SCK
sai_ws = "GAP9_PADMUX(53,0,-1)"; // SAI1_WS
sai_sdi = "GAP9_PADMUX(54,0,-1)"; // SAI1_SDI
sai_sdo = "GAP9_PADMUX(55,0,-1)"; // SAI1_SDO
};
};
fxl6408_evk: fxl6408_evk
{
status = "okay";
sys
{
device_name = "fxl6408";
driver_name = "fxl6408";
device_type = "gpio";
device_board = "gap_headset_v1";
device_driver = "bsp/gpio/fxl6408.h";
};
conf
{
i2c_itf = <3>;
addr_input = <1>;
interrupt_pin = "-1";
bsp_open = "&bsp_gap_headset_v1_fxl6408_open";
bsp_close = "&bsp_gap_headset_v1_fxl6408_close";
bsp_conf = "(void*) &fxl6408_gap_headset_v1_bsp_conf";
};
pads
{
sda = "GAP9_PADMUX(46,2,-1)";
scl = "GAP9_PADMUX(47,2,-1)";
3v3 = "GAP9_PADMUX(0,1,-1)";
};
};
};
};
};
Note
If a subnode is empty (like defs here) it can be removed.
Special node : JTAG
You can create a node dedicated to JTAG pad initilization. For instance, Here is the definition of the jtag node form the GAP9EVK :
jtag: jtag
{
status = "okay";
sys
{
device_name = "jtag";
driver_name = "jtag";
device_type = "io";
device_board = "gap9_evk_v1_3";
};
pads
{
tck = "GAP9_PADMUX(81,0,-1)";
tdi = "GAP9_PADMUX(82,0,-1)";
tdo = "GAP9_PADMUX(83,0,-1)";
tms = "GAP9_PADMUX(84,0,-1)";
trst = "GAP9_PADMUX(85,0,-1)";
};
};
Implementing board specific content about BSP
Sometimes, a hardware peripheral requires an extra initialization process than what its driver implements. That is why drivers start to offers the possibility to link BSP related open and close function repectively called during drivers’s open and close function.
In the previous section, the fxl6408 node has three bsp related members in its conf subnode. These are pointers to open function, close function and bsp configuration structure, all three are mandatory to be able to correctly use the driver the with the board.
As an example, a peripheral might require a GPIO to be set in order to be powered on. Depending on boards, this signal can be provided by the GAP itself or another component like a GPIO expander. This mechanism is here to be as modular as possible and avoid writing as many driver as boards for one peripheral.
Such content must be written in board related BSP files located here :
Source file : $GAP_SDK_HOME/rtos/pmsis/bsp/bsp/boards
Header file : $GAP_SDK_HOME/rtos/pmsis/bsp/include/bsp/boards
To remain consistent, create files including the board name like the following:
Header file
For each driver to support here, you need to create :
An ifdef statement testing if the driver is compiled. The macro is usually defined as __GAP__<DRIVERNAME>__
Write the definition of the BSP content. It includes all needed instances and settings related to your board (such as GPIO id)
Declare an instance of the BSP content as extern. It will be defined in the source file.
Write the prototype of your open and close function. To be consistent, name them as the following : bsp_<BOARDNAME>_<DRIVERNAME>_open. They both take a pi_device_t* object as an argument.
Here is an example of a BSP implementation
Source file
The BSP source file contains :
An ifdef statement testing if the driver is compiled as the header file
The implementation of the open and close function
The definition of the BSP content instance
Note
For a better example, check the BSP implementation of audio addon V1.1 here: $GAP_SDK_HOME/rtos/pmsis/bsp/bsp/boards/bsp_audio_addon_v1_1.c where tha AK4332 DAC requires the FLX6408 being initialized before.
Link your BSP with your board option in CMake
After having created new files, they need to be compiled when you select the option corresponding to your board. To do that, add the following lines in $GAP_SDK_HOME/rtos/pmsis/bsp/CMakeLists.txt file below others of the same kind:
if(CONFIG_BOARD_GAP_HEADSET_V1)
LIST(APPEND BSP_SRCS bsp/boards/bsp_gap_headset_v1.c)
LIST(APPEND BSP_COMPILE_OPTIONS "-D__GAP_BOARD_GAP_HEADSET_V1__=1")
endif()
CONFIG_BOARD_GAP_HEADSET_V1 : Resulting name of your board option in CMake bsp/boards/bsp_gap_headset_v1.c : Your new bsp file to add to the list of compiled source files if you an app uses your board. __GAP_BOARD_GAP_HEADSET_V1__ : Macro saying to the SDK that your board has been selected.
Link to the main BSP
Finally you need to connect your new BSP file to GAP SDK’s main one only if your board is being used. In $GAP_SDK_HOME/rtos/pmsis/bsp/include/bsp/bsp.h file, insert the following lines below the others of the same kind :
#ifdef __GAP_BOARD_GAP_HEADSET_V1__
#include "bsp/boards/bsp_gap_headset_v1.h"
#endif
Use your board in a application
Finally, your board has been integrated, congratulation ! Now you can use the content that is parsed from the devicetree.
Note
This content is only valid when you have configured cmake.
The produced content is located here : $GAP_SDK_HOME/utils/dt/gen/include/dt.h and $GAP_SDK_HOME/utils/dt/gen/include/dt.c
As explained in the devicetree guide from this documentation, devicetree’s peripheral instances are available from devicetree interface : pi_open and pi_close which is generated aswell in dt.h and dt.c files.
Please check the SSM6515 pdm application to have an example of this API usage. Here : $GAP_SDK_HOME/examples/gap9/basic/bsp/ssm6515/pdm/ssm6515_pdm.c
To configure an application to be run on your board :
Go into your app source directory
Configure CMake
cmake -B build
Launch menuconfig interface
cmake --build build -t menuconfig
Go into GAP_SDK->Board and select your option
Go into GAP_SDK->Drivers and select the driver from your board needed by your application.
Quit menuconfig interface and save the configuration
Run your code
cmake --build build -t run