Skip to content

Presentation Content and Workflow

Yocto Build

$ git clone https://github.com/AmateurECE/meta-edtwardy
$ git checkout -b presentation origin/presentation

kas file

$ mkdir build/tmp/deploy/linux
$ cd build/tmp/deploy/linux
$ tar xvf ../beaglebone/core-image-minimal-beaglebone-*.rootfs.tar.xz

SD Card Setup

Discuss TFTP server setup on build server

Program rootfs onto the board, so that we have something to boot from. Then, we'll build our kernel externally and boot it using TFTP.

$ git clone https://github.com/AmateurECE/meta-edtwardy
$ cd meta-edtwardy/tools/
$ curl tftp://192.168.1.60/beaglebone/core-image-minimal-beaglebone.wic.xz
$ xz -d core-image-minimal-beaglebone.wic.xz
$ sudo umount /dev/sdb{1,2}
$ sudo ./FlashSd.sh core-image-minimal-beaglebone.wic /dev/sdb

Need to finagle the SD card image a little. The image we created will want to boot a kernel from the SD card. We need to fool the kernel into using the SD card as the root fs, but also create a boot partition that U-boot can save its environment to. To do this, we use fdisk:

  • Unmount /dev/sdb{1,2}
  • Run fdisk /dev/sdb
  • Print the partition table and save the disk identifier, we need it later.
  • Delete partition 1
  • Create a new partition of type Linux filesystem
  • Toggle the bootable flag for partition 1
  • Write the partition table

Create a new ext4 filesystem on the SD card, then create /boot for U-boot to write the environment file into:

# mkfs.ext4 -O ^metadata_csum /dev/sdb1
# mkdir /mnt/SD
# mount /dev/sdb1 /mnt/SD
# mkdir /mnt/SD/boot
# touch /mnt/SD/boot/uboot.env

RNDIS Setup

Device side:

=> env set ethprime usb_ether

=> env print usbnet_devaddr

# Copy the MAC address and increment to set usbnet_hostaddr
=> env set usbnet_hostaddr <MAC Address>

=> env set ipaddr 192.168.5.2

=> env set gatewayip 192.168.5.1

Host side:

$ sudo dmesg -W

Device side:

=> ping 192.168.5.1

Host side:

$ sudo nmcli con add type ethernet ifname <ifname> ip4 192.168.5.1/24

Netboot Setup

Problem: Now the device can talk to my MacBook, but it can't talk to the TFTP server.

# sysctl net.ipv4.ip_forward=1
# modprobe nf_nat_tftp
# nft -f ./nat-route.nft
=> ping 192.168.1.60

Set serverip, so the U-boot tftp command knows where to connect to

=> env set serverip 192.168.1.60

Configure U-boot to download images and boot them:

# Tell U-boot where and how to download the kernel/initramfs/fdt image
=> env set get_linux 'tftp ${loadaddr} linux/zImage'
=> env set get_fdt 'tftp ${fdtaddr} linux/am335x-boneblack.dtb'
=> env set bootcmd 'run get_linux; run get_fdt; bootz ${loadaddr} - ${fdtaddr}'

# Tell kernel via cmdline to send kernel ring buffer to /dev/ttyS0
=> env set bootargs 'console=ttyS0,115200 rootwait root=PARTUUID=<disk>-02'

=> env save

Setup for NFS Boot

On Arch Linux, depends on package nfs-utils.

The caveat here is that this creates a new, distinct Ethernet interface when it boots, which needs to be uniquely configured on the host.

/export/rootfs  192.168.5.2(rw,no_root_squash,no_subtree_check) 127.0.0.1(rw,no_root_squash,no_subtree_check)
=> env set bootargs console=ttyS0,115200 root=/dev/nfs rw ip=192.168.5.2::192.168.5.1:::usb0 g_ether.dev_addr=<MAC Address> g_ether.host_addr=<MAC Address> nfsroot=192.168.5.1:/export/rootfs,nfsvers=3,tcp

Setup for Kernel Development

Discuss: differences between staging kernel and mainline kernel

Default metadata uses linux-ti-staging kernel, which is Texas Instrument's fork of the kernel--it's their "stable" staging tree for tracking development and bug fixes in platform drivers and platform-specific code. We want to use linux-stable, because we can't trust TI to keep their staging tree up-to-date with mainline.

$ git clone https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux-stable
$ cd linux/
$ git checkout -b wii-nunchuk origin/linux-rolling-stable

Discuss: Discovering omap2plus_defconfig

$ export ARCH=arm
$ export CROSS_COMPILE=arm-linux-gnueabi-
$ export O=/home/edtwardy/Git/Yocto/build/tmp/deploy/images/linux
$ export INSTALL_MOD_PATH=${O}
$ make omap2plus_defconfig
$ make menuconfig

Every time a change is made:

$ make -j8 modules
$ make modules_install
$ make -j8

Discuss: Linux subsystems

Begin with a list of them:

ls drivers/
For our example, we want to hook into the Linux input subsystem, so this would go under drivers/input, specifically drivers/input/joystick.

$ git touch drivers/input/joystick/nunchuk.c

C Driver

// SPDX-License-Identifier: GPL-2.0

#include <linux/delay.h>
#include <linux/i2c.h>
#include <linux/init.h>
#include <linux/input.h>
#include <linux/module.h>

// Per device structure
struct nunchuk_dev {
    struct i2c_client *i2c_client;
};

static int nunchuk_probe(struct i2c_client *client)
{
    return -ENOSYS;
}

static int nunchuk_remove(struct i2c_client *client)
{
}

// Specification of supported Device Tree devices
static const struct of_device_id nunchuk_dt_match[] = {
    { .compatible = "nintendo,nunchuk-white" },
    { },
};
MODULE_DEVICE_TABLE(of, nunchuk_dt_match);

// Driver declaration
static struct i2c_driver nunchuk_driver = {
    .driver = {
        .name = "nunchuk",
        .of_match_table = nunchuk_dt_match,
    },
    .probe_new = nunchuk_probe,
    .remove = nunchuk_remove,
};
module_i2c_driver(nunchuk_driver);

MODULE_LICENSE("GPLv2");
MODULE_AUTHOR("Ethan D. Twardy <ethan.twardy@plexus.com>");
MODULE_DESCRIPTION("Driver for the Nintendo Wii Nunchuk controller");

Now would be a good time to talk about Kbuild

Plug new driver into Kconfig:

diff --git a/drivers/input/joystick/Kconfig b/drivers/input/joystick/Kconfig
index 3b23078bc7b5..10ec19d62300 100644
--- a/drivers/input/joystick/Kconfig
+++ b/drivers/input/joystick/Kconfig
@@ -399,4 +399,11 @@ config JOYSTICK_N64
      Say Y here if you want enable support for the four
      built-in controller ports on the Nintendo 64 console.

+config JOYSTICK_WII_NUNCHUK
+   tristate "Driver for Nintendo Wii Nunchuk Controller"
+   depends on I2C
+   help
+     This driver adds support for the Wii Nunchuk controller. It does not
+     support hot-plug, but it does provide a device tree binding.
+
 endif

Plug new driver into Makefile:

diff --git a/drivers/input/joystick/Makefile b/drivers/input/joystick/Makefile
index 5174b8aba2dd..5b191766003d 100644
--- a/drivers/input/joystick/Makefile
+++ b/drivers/input/joystick/Makefile
@@ -39,3 +39,4 @@ obj-$(CONFIG_JOYSTICK_WARRIOR)        += warrior.o
 obj-$(CONFIG_JOYSTICK_WALKERA0701) += walkera0701.o
 obj-$(CONFIG_JOYSTICK_XPAD)        += xpad.o
 obj-$(CONFIG_JOYSTICK_ZHENHUA)     += zhenhua.o
+obj-$(CONFIG_JOYSTICK_WII_NUNCHUK) += nunchuk.o

Build check

$ cd ../../../../
$ kas-container build meta-edtwardy/kas/beaglebone-kirkstone.yaml

Add device tree integration:

// SPDX-License-Identifier: GPL-2.0-only

#include "am335x-boneblack.dts"

&am33xx_pinmux {
        i2c1_pins: pinmux_i2c1_pins {
                pinctrl-single,pins = <
                    // I2C1 SCL
                    AM33XX_PADCONF(AM335X_PIN_SPI0_D1, PIN_INPUT_PULLUP, MUX_MODE2)
                    // I2C1 SDA
                    AM33XX_PADCONF(AM335X_PIN_SPI0_CS0, PIN_INPUT_PULLUP, MUX_MODE2)
                >;
        };
};

&i2c1 {
        status = "okay";
        clock-frequency = <100000>;
        pinctrl-0 = <&i2c1_pins>;
        pinctrl-names = "default";

        joystick@52 {
                compatible = "nintendo,nunchuk-white";
                reg = <0x52>;
        };
};
diff --git a/drivers/input/joystick/nunchuk.c b/drivers/input/joystick/nunchuk.c
index 4430fdee79a4..427e0eccb557 100644
--- a/drivers/input/joystick/nunchuk.c
+++ b/drivers/input/joystick/nunchuk.c
@@ -11,13 +11,71 @@ struct nunchuk_dev {
     struct i2c_client *i2c_client;
 };

+static int nunchuk_init(struct i2c_client* client) {
+    u8 buf[2] = {0};
+    int result = 0;
+
+    buf[0] = 0xf0;
+    buf[1] = 0x55;
+
+    result = i2c_master_send(client, buf, 2);
+    if (0 > result) {
+        dev_err(&client->dev, "i2c send failed (%d)\n", result);
+        return result;
+    }
+
+    udelay(1000);
+
+    buf[0] = 0xfb;
+    buf[1] = 0x00;
+
+    result = i2c_master_send(client, buf, 2);
+    if (0 > result) {
+        dev_err(&client->dev, "i2c send failed (%d)\n", result);
+        return result;
+    }
+
+    return 0;
+}
+
 static int nunchuk_probe(struct i2c_client *client)
 {
-    return -ENOSYS;
+    struct nunchuk_dev *nunchuk = NULL;
+    struct input_dev *input = NULL;
+    int result = 0;
+
+    // Allocate per device structure
+    nunchuk = devm_kzalloc(&client->dev, sizeof(*nunchuk), GFP_KERNEL);
+    if (IS_ERR(nunchuk)) {
+        return -ENOMEM;
+    }
+
+    // Initialize device
+    result = nunchuk_init(client);
+    if (0 > result) {
+        return result;
+    }
+
+    // Allocate input device
+    input = devm_input_allocate_device(&client->dev);
+    if (IS_ERR(input)) {
+        return -ENOMEM;
+    }
+
+    // Register the input device when everything is ready
+    result = input_register_device(input);
+    if (0 > result) {
+        dev_err(&client->dev, "Cannot register input device (%d)\n", result);
+        return result;
+    }
+
+    return 0;
 }

 static int nunchuk_remove(struct i2c_client *client)
 {
+    // Nothing to do!
+    return 0;
 }

 // Specification of supported Device Tree devices