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:

  1. Choose a name for your board

  2. Create an option that symbolize the board.

  3. Link it to an existing GAP chip and all hardware peripherals embedded on the board.

  4. Create a devicetree file dedicated to your board.

  5. For each hardware peripheral: add a node to the devicetree.

  6. Create BSP files for this board.

  7. Write specific content related to your board in order to handle particular implementations.

  8. 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>

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>)
}
  1. pad_id : Pad identification (Pad n°34)

  2. pad_af: Pad alternate function (Alternate function 1 : GPIO)

  3. 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.

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