Friday, December 27, 2024

udev notes

In Yocto notes - 3, we did the following update to drivers/usb/musb/musb_gadget.c to notify MUSB gedget setup/cleanup

int musb_gadget_setup(struct musb *musb) will notify the gadget-setup

and

void musb_gadget_cleanup(struct musb *musb) will notify the gadget-cleanup

To get the details in writing a udev rule for setup/cleanup, we can use udevadm(8)


For example, with the config fragment update usb_configfs.cfg, we get the musb


root@beaglebone-yocto:~# lsmod
Module                  Size  Used by
musb_dsps              16384  0
musb_hdrc             135168  1 musb_dsps


Run the udevadm (in the background), then unload the musb_dsps (rmmod musb_dsps), and we'll see gadget_cleanup in action.


Similarly, if we now insmod musb_dsps, we'll see gadget_setup in action.


root@beaglebone-yocto:~# udevadm monitor --env
monitor will print the received events for:
UDEV - the event which udev sends out after rule processing
KERNEL - the kernel uevent
:

KERNEL[290.836192] change   /devices/platform/ocp/47400000.target-module/47401400.usb/musb-hdrc.0 (platform)
ACTION=change
DEVPATH=/devices/platform/ocp/47400000.target-module/47401400.usb/musb-hdrc.0
DRIVER=musb-hdrc
EVENT=gadget_setup
MODALIAS=platform:musb-hdrc
OF_ALIAS_0=usb0
OF_COMPATIBLE_0=ti,musb-am33xx
OF_COMPATIBLE_N=1
OF_FULLNAME=/ocp/target-module@47400000/usb@1400
OF_NAME=usb
SEQNUM=1790
SUBSYSTEM=platform

KERNEL[290.836524] bind     /devices/platform/ocp/47400000.target-module/47401400.usb/musb-hdrc.0 (platform)
ACTION=bind
DEVPATH=/devices/platform/ocp/47400000.target-module/47401400.usb/musb-hdrc.0
DRIVER=musb-hdrc
MODALIAS=platform:musb-hdrc
OF_ALIAS_0=usb0
OF_COMPATIBLE_0=ti,musb-am33xx
OF_COMPATIBLE_N=1
OF_FULLNAME=/ocp/target-module@47400000/usb@1400
OF_NAME=usb
SEQNUM=1791
SUBSYSTEM=platform

:


Add a udev rule for gedget-setup at startp. We are matching SUBSYSTEM, ACTION and EVENT keys, then RUN key assignment/append to run our script.


echo "SUBSYSTEM==\"platform\",ACTION==\"change\",ENV{EVENT}==\"gadget_setup\",RUN+=\"/home/root/setup.sh\"" > /etc/udev/rules.d/20-musb-setup-test.rules


setup.sh has the same content as we had in 'USB notes - 2'

#!/bin/ash

modprobe libcomposite
mount -t configfs none /sys/kernel/config/
cd /sys/kernel/config/usb_gadget/
mkdir g
cd g/
echo "0xA55A" > idVendor 
echo "0x0111" > idProduct 
mkdir strings/0x409
echo "0123" > strings/0x409/serialnumber 
echo "hello" > strings/0x409/manufacturer 
echo "ncm" > strings/0x409/product 
mkdir functions/ncm.usb0
mkdir configs/c.1
mkdir configs/c.1/strings/0x409
echo "ncm" > configs/c.1/strings/0x409/configuration 
ln -s functions/ncm.usb0 configs/c.1/
echo "musb-hdrc.0" > UDC


Now, after booting the beagle and checking


ifconfig -a


we'lll see usb0


To get the eth working, from host,


ifconfig usb0 169.254.211.10 up


Instead of using the gadget_setup EVENT we added, we can use bind ACTION and the DRIVER name to get this working:

echo "SUBSYSTEM==\"platform\",ACTION==\"bind\",DRIVER==\"musb-hdrc\",RUN+=\"/home/root/setup.sh\"" > /etc/udev/rules.d/20-musb-setup-test.rules


Driver binding is the process of associating a device with a device driver that can control it (ref: Documentation/driver-api/driver-model/binding.rst).


Refer udev(7) for udev related details.


Here's the musb-gadget update where we have added the gadget_setup for demonstration (see Upstream-Status: Denied noted in Yocto notes - 3):


meta-mylayer/recipes-kernel/linux/linux-yocto/0001-musb-gadget-udev-notification.patch


Upstream-Status: Denied
---
 drivers/usb/musb/musb_gadget.c | 13 +++++++++++++
 1 file changed, 13 insertions(+)
diff --git a/drivers/usb/musb/musb_gadget.c b/drivers/usb/musb/musb_gadget.c
index 051c6da7cf6d..e6e0fe52659a 100644
--- a/drivers/usb/musb/musb_gadget.c
+++ b/drivers/usb/musb/musb_gadget.c
@@ -1770,6 +1770,15 @@ static inline void musb_g_init_endpoints(struct musb *musb)
  }
 }
 
+static void send_gadget_uevent(struct device *dev, const char *event)
+{
+ char event_string[32];
+ char *envp[] = { event_string, NULL };
+ snprintf(event_string, sizeof(event_string), "%s", event);
+ kobject_uevent_env(&dev->kobj, KOBJ_CHANGE, envp);
+}
+
+
 /* called once during driver setup to initialize and link into
  * the driver model; memory is zeroed.
  */
@@ -1803,6 +1812,8 @@ int musb_gadget_setup(struct musb *musb)
  if (status)
  goto err;
 
+ send_gadget_uevent(musb->controller, "EVENT=gadget_setup");
+
  return 0;
 err:
  musb->g.dev.parent = NULL;
@@ -1815,6 +1826,8 @@ void musb_gadget_cleanup(struct musb *musb)
  if (musb->port_mode == MUSB_HOST)
  return;
 
+ send_gadget_uevent(musb->controller, "EVENT=gadget_cleanup");
+
  cancel_delayed_work_sync(&musb->gadget_work);
  usb_del_gadget_udc(&musb->g);
 }

Thursday, November 28, 2024

U-Boot notes

Porting u-boot

Just some simple guidelines for start porting u-boot to a new board. I'm using an NXP imx8mp as example.

Using u-boot-2021.04 as example. This is not at all a detailed report but just the initial basic steps.


Board files

Copy board/freescale/imx8mp_evk as board/freescale/imx8mp_my_board/

$ ls board/freescale/imx8mp_my_board/

boot.cmd  ddr4_timing.c  imx8mp_evk.c  Kconfig  lpddr4_timing.c  lpddr4_timing_ndm.c  MAINTAINERS  Makefile  spl.c

Rename

board/freescale/imx8mp_my_board/imx8mp_evk.c

to

board/freescale/imx8mp_my_board/imx8mp_my_board.c


Update Kconfig and Makefile


board/freescale/imx8mp_my_board/Kconfig


if TARGET_IMX8MP_MY_BOARD
config SYS_BOARD
default "imx8mp_my_board"
config SYS_VENDOR
default "freescale"
config SYS_CONFIG_NAME
default "imx8mp_my_board"
source "board/freescale/common/Kconfig"
endif


board/freescale/imx8mp_my_board/Makefile


obj-y += imx8mp_my_board.o
ifdef CONFIG_SPL_BUILD
obj-y += spl.o
ifdef CONFIG_IMX8M_LPDDR4_FREQ0_3200MTS
obj-y += lpddr4_timing_ndm.o
else
obj-$(CONFIG_IMX8M_LPDDR4) += lpddr4_timing.o
obj-$(CONFIG_IMX8M_DDR4) += ddr4_timing.o
endif
endif


Update arch/arm/mach-imx/imx8m/Kconfig to include the board files

+config TARGET_IMX8MP_MY_BOARD
+       bool "imx8mp LPDDR4 my board"
+       select IMX8MP
+       select SUPPORT_SPL
+       select IMX8M_LPDDR4
+       select FSL_CAAM
+       select FSL_BLOB
+       select MISC
+       select SPL_CRYPTO_SUPPORT if SPL
+
+source "board/freescale/imx8mp_my_board/Kconfig"


Board dts files


$ cp arch/arm/dts/imx8mp-evk.dts arch/arm/dts/imx8mp-my-board.dts

$ cp arch/arm/dts/imx8mp-evk-u-boot.dtsi arch/arm/dts/imx8mp-my-board-u-boot.dtsi


Update dts makefile arch/arm/dts/Makefile to add our new dt


+       imx8mp-my-board.dtb


Board config file


Take a copy of configs/imx8mp_evk_defconfig as configs/imx8mp_my_board_defconfig

Then make following changes


CONFIG_TARGET_IMX8MP_EVK=y  ->  CONFIG_TARGET_IMX8MP_MY_BOARD=y

replace whatever dt files, or you can just use existing ones for now

CONFIG_DEFAULT_DEVICE_TREE="imx8mp-evk" -> CONFIG_DEFAULT_DEVICE_TREE="imx8mp-my-board"

CONFIG_DEFAULT_FDT_FILE="imx8mp-evk.dtb"    ->  CONFIG_DEFAULT_FDT_FILE="imx8mp-my-board.dtb"


Board header file


$ cp include/configs/imx8mp_evk.h include/configs/imx8mp_my_board.h


Sunday, October 27, 2024

USB notes - 2

Using Linux USB Gadget


Documentation/usb/gadget_configfs.rst details how linux USB gadget is configured through configfs


To get ethernet over USB working on beaglebone-white using NCM (Network Control Model), do something like the following (put it into a script)


#!/bin/ash

modprobe libcomposite
mount -t configfs none /sys/kernel/config/
cd /sys/kernel/config/usb_gadget/
mkdir g
cd g/
echo "0xA55A" > idVendor 
echo "0x0111" > idProduct 
mkdir strings/0x409
echo "0123" > strings/0x409/serialnumber 
echo "hello" > strings/0x409/manufacturer 
echo "ncm" > strings/0x409/product 
mkdir functions/ncm.usb0
mkdir configs/c.1
mkdir configs/c.1/strings/0x409
echo "ncm" > configs/c.1/strings/0x409/configuration 
ln -s functions/ncm.usb0 configs/c.1/
echo "musb-hdrc.0" > UDC


Now, after running the script, configure the ip from the device


ifconfig usb0 169.254.211.10 up


When the ip is up and running, you might see something like


usb0      Link encap:Ethernet  HWaddr BA:E9:68:15:C1:8D  
          inet addr:169.254.211.10  Bcast:169.254.255.255  Mask:255.255.0.0
          inet6 addr: fe80::b8e9:68ff:fe15:c18d/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:19 errors:0 dropped:0 overruns:0 frame:0
          TX packets:7 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000 
          RX bytes:3027 (2.9 KiB)  TX bytes:872 (872.0 B)


from the device.


Ping it from the host


$ ping 169.254.211.10
PING 169.254.211.10 (169.254.211.10) 56(84) bytes of data.
64 bytes from 169.254.211.10: icmp_seq=1 ttl=64 time=2.43 ms
64 bytes from 169.254.211.10: icmp_seq=2 ttl=64 time=2.19 ms


Use the following to remove it


#!/bin/ash

cd /sys/kernel/config/usb_gadget/g
echo "" > UDC
rm configs/c.1/ncm.usb0
rmdir configs/c.1/strings/0x409
rmdir configs/c.1
rmdir functions/ncm.usb0
rmdir strings/0x409
cd ..
rmdir g


When you clean it up, it'll be unreachable


$ ping 169.254.211.10
PING 169.254.211.10 (169.254.211.10) 56(84) bytes of data.
64 bytes from 169.254.211.10: icmp_seq=1 ttl=64 time=2.43 ms
:
64 bytes from 169.254.211.10: icmp_seq=6 ttl=64 time=2.21 ms
From 172.17.0.1 icmp_seq=7 Destination Host Unreachable
From 172.17.0.1 icmp_seq=8 Destination Host Unreachable
:


And, here's the config fragment (usb_configfs.cfg) we used earlier


CONFIG_USB_MUSB_HDRC=m
CONFIG_USB_MUSB_OMAP2PLUS=m
CONFIG_USB_MUSB_DSPS=m
CONFIG_USB_G_NCM=n
CONFIG_USB_MASS_STORAGE=n
CONFIG_USB_CONFIGFS=m
CONFIG_USB_CONFIGFS_ACM=y
CONFIG_USB_CONFIGFS_NCM=y


NOTE: Had to use Link-Local Only option in the IPv4 on Ubuntu 20.04 to get the ip connection working

Saturday, September 28, 2024

Yocto notes - 3

Dealing with the linux kernel

Here, I'm using yocto scarthgap for my beaglebone-white.

Refer to Yocto Linux Kernel Development Manual for the version you are using.

If you just flash the default core-image-minimal image, you won't see

/lib/modules/

Update the local.conf so that MACHINE_ESSENTIAL_EXTRA_RRECOMMENDS includes kernel modules

MACHINE_ESSENTIAL_EXTRA_RRECOMMENDS += "kernel-modules"

With this, you will see /lib/modules/

Now, to do some kernel changes, let's create and add a layer

$ bitbake-layers create-layer ../meta-mylayer

$ bitbake-layers add-layer ../meta-mylayer

The new layer is added to conf/bblayers.conf 

# POKY_BBLAYERS_CONF_VERSION is increased each time build/conf/bblayers.conf
# changes incompatibly
POKY_BBLAYERS_CONF_VERSION = "2"
BBPATH = "${TOPDIR}"
BBFILES ?= ""
BBLAYERS ?= " \
  /home/dld/bbw-yocto/poky/meta \
  /home/dld/bbw-yocto/poky/meta-poky \
  /home/dld/bbw-yocto/poky/meta-yocto-bsp \
  /home/dld/bbw-yocto/meta-mylayer \
  "

Create kernel recipe in the new layer. We'll will patch the kernel by modifying a driver, and do some configuration changes by adding a config fragment

We can use devtool to patch the kernel as described in the documentation

$ devtool modify linux-yocto

It checks out the kernel source in the workspace. You'll see a layer added in conf/bblayers.conf 

# POKY_BBLAYERS_CONF_VERSION is increased each time build/conf/bblayers.conf
# changes incompatibly
POKY_BBLAYERS_CONF_VERSION = "2"
BBPATH = "${TOPDIR}"
BBFILES ?= ""
BBLAYERS ?= " \
  /home/dld/bbw-yocto/poky/meta \
  /home/dld/bbw-yocto/poky/meta-poky \
  /home/dld/bbw-yocto/poky/meta-yocto-bsp \
  /home/dld/bbw-yocto/meta-mylayer \
  /home/dld/bbw-yocto/build-bbw/workspace \
  "

Now, I do some changes to drivers/usb/musb/musb_gadget.c in the workspace sources and commit the changes

$ git status 

$ git add drivers/usb/musb/musb_gadget.c

$ git commit -m "musb gadget udev notification"

Export the patch and create an append file

$ devtool finish linux-yocto ../meta-mylayer/

and do some configuration changes by adding them as a config fragment file

With all this in place, we'll see something like

$ tree ../meta-mylayer/

../meta-mylayer/

├── conf
│   └── layer.conf
├── COPYING.MIT
├── README
└── recipes-kernel
    └── linux
        ├── linux-yocto
        │   ├── 0001-musb-gadget-udev-notification.patch
        │   └── usb_configfs.cfg
        └── linux-yocto_%.bbappend

$ cat ../meta-mylayer/recipes-kernel/linux/linux-yocto_%.bbappend 

# The path ${THISDIR}/${PN} expands to “linux-yocto” in the current directory
FILESEXTRAPATHS:prepend := "${THISDIR}/${PN}:"
SRC_URI += "file://usb_configfs.cfg \
            file://0001-musb-gadget-udev-notification.patch \
            "

Now, try to build, and you'll see it's giving an error saying something like

$ bitbake core-image-minimal

:
do_patch: QA Issue: Missing Upstream-Status in patch

and how to fix it

Please add according to https://docs.yoctoproject.org/contributor-guide/recipe-style-guide.html#patch-upstream-status . [patch-status]

So, I'll just add

Upstream-Status: Denied

to the patch file, and build, and it goes without any issue then.

We'll see in the next post the details of the patch and the config fragment, and what they do.


Sunday, August 18, 2024

USB notes

Switching USB speed

An example from imx8mp

drivers/usb/dwc3/core.c does the speed set

    dwc->maximum_speed = usb_get_maximum_speed(dev);

usb_get_maximum_speed() gets it from dt 'maximum-speed'

From usb/generic.txt

Generic USB Properties
Optional properties:
 - maximum-speed: tells USB controllers we want to work up to a certain
speed. Valid arguments are "super-speed-plus",
"super-speed", "high-speed", "full-speed" and
"low-speed". In case this isn't passed via DT, USB
controllers should default to their maximum HW
capability.

imx8mp usb controller usb_dwc3_x dt doesn't specifically specify maximum-speed

usb3_phy0: usb-phy@381f0040 {
compatible = "fsl,imx8mp-usb-phy";
:
};
usb3_0: usb@32f10100 {
compatible = "fsl,imx8mp-dwc3";
:
usb_dwc3_0: dwc3@38100000 {
compatible = "snps,dwc3";
                                :
                        };
                };


From dwc3.txt

DWC3- USB3 CONTROLLER. Complies to the generic USB binding properties as described in 'usb/generic.txt'

From imx8mp reference manual

USB1 base address: 3810_0000h

USB2 base address: 3820_0000h

To change usb speed, we can update the dt

&usb_dwc3_0 {
    maximum-speed = "high-speed";
    :
};

Or, you can use a module param passed from kernel command-line

static int usb_maximum_speed = USB_SPEED_SUPER;
module_param(usb_maximum_speed, int, 0660);
    dwc->maximum_speed = usb_maximum_speed;

Pass it from command-line

Kernel command line: console=.. dwc3.usb_maximum_speed=3

Check usb version and speed using lsusb

$ lsusb -s 3:14 -v | grep -i bcdusb
Couldn't open device, some information will be missing
  bcdUSB               2.01


$ lsusb --tree
:
/:  Bus 03.Port 1: Dev 1, Class=root_hub, Driver=xhci_hcd/12p, 480M
    :
    |__ Port 6: Dev 14, If 0, Class=Communications, Driver=rndis_host, 480M
    |__ Port 6: Dev 14, If 1, Class=CDC Data, Driver=rndis_host, 480M
    |__ Port 6: Dev 14, If 2, Class=Communications, Driver=cdc_acm, 480M
    |__ Port 6: Dev 14, If 3, Class=CDC Data, Driver=cdc_acm, 480M


Friday, July 26, 2024

More on device-tree aliases

(Note that I'm changing the codes shown here for simplicity/clarity, so it's different from the real code)

e.g. SPI on imx8mp

From imx8mp dt

ecspi1: spi@30820000 {
compatible = "fsl,imx8mp-ecspi", "fsl,imx6ul-ecspi";
};
ecspi2: spi@30830000 { .. };
ecspi3: spi@30840000 { .. };

Suppose we are using spidev from our dt to expose them to userspace

&ecspi1 {
    :
    spidev1: spi@0 {
        reg = <0>;
        compatible = "rohm,dh2228fv";
        spi-max-frequency = <10000000>; //10mhz
    };
};

Similarly for ecspi2 and ecspi3

And suppose initially we don't have any aliases for ecspi in our dt (nor in the imx8mp.dtsi)

So, it's basically like we have the following

    aliases {
        spi0 = &ecspi1;
        spi1 = &ecspi2;
        spi2 = &ecspi3;
    };

Then we'll see this

$ ls /sys/class/spidev/ -l
total 0
lrwxrwxrwx 1 root root 0 Apr  4 06:28 spidev0.0 -> ../../devices/platform/soc@0/30800000.bus/30820000.spi/spi_master/spi0/spi0.0/spidev/spidev0.0
lrwxrwxrwx 1 root root 0 Apr  4 06:28 spidev1.0 -> ../../devices/platform/soc@0/30800000.bus/30830000.spi/spi_master/spi1/spi1.0/spidev/spidev1.0
lrwxrwxrwx 1 root root 0 Apr  4 06:28 spidev2.0 -> ../../devices/platform/soc@0/30800000.bus/30840000.spi/spi_master/spi2/spi2.0/spidev/spidev2.0


$ ls /dev/spidev* -l
crw------- 1 root root 153, 0 Apr  4 06:28 /dev/spidev0.0
crw------- 1 root root 153, 1 Apr  4 06:28 /dev/spidev1.0
crw------- 1 root root 153, 2 Apr  4 06:28 /dev/spidev2.0

Now let's change aliases in our dt

    aliases {
        spi0 = &ecspi2;
        spi1 = &ecspi1;
        spi2 = &ecspi3;
    };

Then we'll be seeing this

$ ls /sys/class/spidev/ -l
total 0
lrwxrwxrwx 1 root root 0 Apr  4 06:37 spidev0.0 -> ../../devices/platform/soc@0/30800000.bus/30830000.spi/spi_master/spi0/spi0.0/spidev/spidev0.0
lrwxrwxrwx 1 root root 0 Apr  4 06:37 spidev1.0 -> ../../devices/platform/soc@0/30800000.bus/30820000.spi/spi_master/spi1/spi1.0/spidev/spidev1.0
lrwxrwxrwx 1 root root 0 Apr  4 06:37 spidev2.0 -> ../../devices/platform/soc@0/30800000.bus/30840000.spi/spi_master/spi2/spi2.0/spidev/spidev2.0


# ls /dev/spidev* -l
crw------- 1 root root 153, 1 Apr  4 06:37 /dev/spidev0.0
crw------- 1 root root 153, 0 Apr  4 06:37 /dev/spidev1.0
crw------- 1 root root 153, 2 Apr  4 06:37 /dev/spidev2.0

Let's check what linux does to try to understand how this happens

controller driver

* SPI controller driver for imx8mp ecspi is drivers/spi/spi-imc.c

* SPI controller driver probe will eventually call spi_register_controller from drivers/spi/spi.c

* drivers/spi/spi.c, spi_register_controller will update bus_num of the spi_controller using of_alias_get_id/of_alias_get_highest_id

* int of_alias_get_id(struct device_node *np, const char *stem) : gets the alias id (numeric id) for the given device_node and alias stem

* int of_alias_get_highest_id(const char *stem) : gets the highest alias id (numeric id) for the given alias stem

* So, with these APIs, now you see how stem and numeric id we saw in the previous post work

protocol driver

* We are using "rohm,dh2228fv" protocol compatible SPI slave/subnode devices

* SPI protocol driver drivers/spi/spidev.c then uses the bus-number when creating sysfs and device nodes

* What we see in sysfs and dev is spidevB.C, where

B is the bus-number

C is the chip-select that comes from the DT "reg"

e.g.

&ecspi3 {
    spidev1: spi@0 {
            reg = <0>;

* class_create creates /sys/class/spidev/

* device_create during probe creates the spidevB.C under /sys/class/spidev/

* And device_create also creates the spidevB.C under /dev/ as it's specifying the dev_t


Code

controller driver

static int spi_imx_probe(struct platform_device *pdev)
{
struct spi_controller *master = spi_alloc_master(&pdev->dev, sizeof(...));
master->bus_num = pdev->dev.of_node ? -1 : pdev->id;
spi_register_controller(master);

}

spi_controller bus_num update

int spi_register_controller(struct spi_controller *ctlr)
{
if (ctlr->bus_num >= 0) {
ctlr->bus_num = allocate an id
} else if (ctlr->dev.of_node) {
ctlr->bus_num = allocate an id based on of_alias_get_id(ctlr->dev.of_node, "spi")
}
if (ctlr->bus_num < 0) {
ctlr->bus_num = allocate an id based on of_alias_get_highest_id("spi")
}
}

protocol driver

/* The main reason to have this class is to make mdev/udev create the
 * /dev/spidevB.C character device nodes exposing our userspace API.
 * It also simplifies memory management.
 */
static struct class *spidev_class;
static const struct of_device_id spidev_dt_ids[] = {
{ .compatible = "rohm,dh2228fv" },
        :
{},
};
MODULE_DEVICE_TABLE(of, spidev_dt_ids);
struct spidev_data {
dev_t devt;
:
struct spi_device *spi;
        :

};

Creating /sys/class/spidev/ with class_create

static int __init spidev_init(void)
{
register_chrdev(SPIDEV_MAJOR, "spi", &spidev_fops);
class_create(THIS_MODULE, "spidev");
}
module_init(spidev_init);

Creating /sys/class/spidev/spidevB.C and /dev/spidevB.C with device_create

static int spidev_probe(struct spi_device *spi)
{
struct spidev_data *spidev;
struct device *dev;
spidev = kzalloc(sizeof(*spidev), GFP_KERNEL);
spidev->spi = spi;
spidev->devt = MKDEV(SPIDEV_MAJOR, minor);
dev = device_create(spidev_class, &spi->dev, spidev->devt,
    spidev, "spidev%d.%d",
    spi->master->bus_num, spi->chip_select);
}

Friday, June 28, 2024

device-tree aliases

From https://elinux.org/Device_Tree_Mysteries#Label_vs_aliases_node_property

  • The "/aliases" node contains properties that are aliases
  • The name of each property is the name of the alias
  • The value of the property is the full path to a node in the device tree
  • The aliases are not used directly in the device tree source, but are instead dereferenced by the Linux kernel
aliases {
<property name> = <property value>
:
};


As described in https://elinux.org/Device_Tree_Linux#Linux_vs_ePAPR_Version_1.1

property name is the combination of stem and the numeric id


property name=<stem><numeric id>


e.g.

aliases {
gpio0 = &gpio1;
gpio1 = &gpio2;
i2c0 = &i2c1;
i2c1 = &i2c2;
};


soc@0 {
compatible = "fsl,imx8mp-soc", "simple-bus";
aips1: bus@30000000 {
compatible = "fsl,aips-bus", "simple-bus";
gpio1: gpio@30200000 {
compatible = "fsl,imx8mp-gpio", "fsl,imx35-gpio";
};
gpio2: gpio@30210000 {
compatible = "fsl,imx8mp-gpio", "fsl,imx35-gpio";
};
};
aips3: bus@30800000 {
compatible = "fsl,aips-bus", "simple-bus";
i2c1: i2c@30a20000 {
compatible = "fsl,imx8mp-i2c", "fsl,imx21-i2c";
};
i2c2: i2c@30a30000 {
compatible = "fsl,imx8mp-i2c", "fsl,imx21-i2c";
};
};
};

Monday, May 27, 2024

A note on linux UARTs

Which /dev/tty# binds to which UART

e.g. from imx8qxp

Suppose we have enabled the lpuart2 in our device tree.

&lpuart2 {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_lpuart2>;
status = "okay";
};
&iomuxc {
pinctrl-names = "default";
pinctrl-0 = ..;
pinctrl_lpuart2: lpuart2grp {
fsl,pins = <
IMX8QXP_UART2_RX_ADMA_UART2_RX                 0x06000020
IMX8QXP_UART2_TX_ADMA_UART2_TX                 0x06000020
>;
};
};

Compatible driver for lpuart is drivers/tty/serial/fsl_lpuart.c

lpuart2: serial@5a080000 {
    compatible = "fsl,imx8qxp-lpuart", "fsl,imx7ulp-lpuart";
    reg = <0x5a080000 0x1000>;
    :
};

In drivers/tty/serial/fsl_lpuart.c we see

static const struct of_device_id lpuart_dt_ids[] = {
:
{ .compatible = "fsl,imx8qxp-lpuart", .data = &imx8qxp_data, },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, lpuart_dt_ids);


Here's how it gets the name we see in /dev/tty#

In drivers/tty/serial/fsl_lpuart.c we see

#define DEV_NAME "ttyLP"

static struct uart_driver lpuart_reg = {
.owner          = THIS_MODULE,
.driver_name = DRIVER_NAME,
.dev_name       = DEV_NAME,
.nr             = ARRAY_SIZE(lpuart_ports),
.cons           = LPUART_CONSOLE,
};

static int lpuart_probe(struct platform_device *pdev)
{
        :
ret = uart_add_one_port(&lpuart_reg, &sport->port);
        :
}


From serial_core.c

int uart_add_one_port(struct uart_driver *drv, struct uart_port *uport)
{
        :
uport->cons = drv->cons;
uport->minor = drv->tty_driver->minor_start + uport->line;
uport->name = kasprintf(GFP_KERNEL, "%s%d", drv->dev_name,
drv->tty_driver->name_base + uport->line);
        :
}


With all this in place, we'll see it in the device

$ ls /sys/class/tty/ -l
lrwxrwxrwx 1 root root 0 Mar 24 10:25 ttyLP2 -> ../../devices/platform/bus@5a000000/5a080000.serial/tty/ttyLP2


 

Friday, April 26, 2024

pinctrl in device tree

An example where we configure imx8mp GPIO1_IO15 (B5) to use as a PWM output.

Can configure it in several ways in the device-tree.

option 1

&pwm4 {
    status = "okay";
};


&iomuxc {
    pinctrl-names = "default";
    pinctrl-0 = <&..>, <&pinctrl_pwm4>; /* pinctrl_pwm4 and others */
    pinctrl_pwm4: pwm4grp {
        fsl,pins = <
            MX8MP_IOMUXC_GPIO1_IO15__PWM4_OUT          0x00000006
        >;
    };
};


option 2

&pwm4 {
    pinctrl-names = "default";
    pinctrl-0 = <&pinctrl_pwm4>;
    status = "okay";
};


&iomuxc {
    pinctrl-names = "default";
    pinctrl-0 = <&..>;          /* other pinctrls but no pinctrl_pwm4 */
    pinctrl_pwm4: pwm4grp {
        fsl,pins = <
            MX8MP_IOMUXC_GPIO1_IO15__PWM4_OUT          0x00000006
        >;
    };
};


Documentation/devicetree/bindings/pinctrl/fsl,imx8mp-pinctrl.yaml tells us how to interpret the pinmux settings we are seeing in the device-tree

fsl,pins:
description:
  each entry consists of 6 integers and represents the mux and config
  setting for one pin. The first 5 integers <mux_reg conf_reg input_reg
  mux_val input_val> are specified using a PIN_FUNC_ID macro, which can
  be found in <arch/arm64/boot/dts/freescale/imx8mp-pinfunc.h>. The last
  integer CONFIG is the pad setting value like pull-up on this pin. Please
  refer to i.MX8M Plus Reference Manual for detailed CONFIG settings.
$ref: /schemas/types.yaml#/definitions/uint32-matrix
items:
  items:
    - description: |
        "mux_reg" indicates the offset of mux register.
    - description: |
        "conf_reg" indicates the offset of pad configuration register.
    - description: |
        "input_reg" indicates the offset of select input register.
    - description: |
        "mux_val" indicates the mux value to be applied.
    - description: |
        "input_val" indicates the select input value to be applied.
    - description: |
        "pad_setting" indicates the pad configuration value to be applied..


From arch/arm64/boot/dts/freescale/imx8mp-pinfunc.h


                      pad/pin     functionality we want to
                      in imx8mp   assign to the pad
                     |--------|  |---------|
#define MX8MP_IOMUXC_GPIO1_IO15__GPIO1_IO15    0x050 0x2B0 0x000 0x0 0x0
#define MX8MP_IOMUXC_GPIO1_IO15__USB2_OC       0x050 0x2B0 0x000 0x1 0x0
#define MX8MP_IOMUXC_GPIO1_IO15__USDHC3_WP     0x050 0x2B0 0x634 0x4 0x0
#define MX8MP_IOMUXC_GPIO1_IO15__PWM4_OUT      0x050 0x2B0 0x000 0x5 0x0
#define MX8MP_IOMUXC_GPIO1_IO15__CCM_CLKO2     0x050 0x2B0 0x000 0x6 0x0


pin-mux from the imx8mp reference manual


instance    port            pad             mode

CCM         CCM_CLKO2       GPIO1_IO15      ALT6
GPIO1       GPIO1_IO15      GPIO1_IO15      ALT0
PWM4        PWM4_OUT        GPIO1_IO15      ALT5
USB2        USB2_OC         GPIO1_IO15      ALT1
USDHC3      USDHC3_WP       GPIO1_IO15      ALT4


From the imx8mp reference manual, we see mux_reg and conf_reg registers of iomuxc are


32-bit MUX Control Register IOMUXC_SW_MUX_CTL_PAD_x to configure ALT mode (MUX MODE)
32-bit PAD Control Register IOMUXC_SW_PAD_CTL_PAD_x to configure pad settings


For our GPIO1_IO15


SW_MUX_CTL_PAD_GPIO1_IO15 SW MUX Control Register
(IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO15)
Address: 3033_0000h base + 50h offset = 3033_0050h


31 .... 5   4       3      2   1  0
[reserved][SION][reserved][mux mode]


mux_val: IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO15[2:0] = 101 -  ALT5_PWM4_OUT


SW_PAD_CTL_PAD_GPIO1_IO15 SW PAD Control Register
(IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO15)
Address: 3033_0000h base + 2B0h offset = 3033_02B0h


31 .... 9  8    7    6    5    4      3      2..1    0
[reserved][PE][HYS][PUE][ODE][FSEL][reserved][DSE][reserved]


pad_setting = 0x00000006 : sets the drive strength

where

PE  : pull resistors enable/disable
HYS : cmos/schmitt
PUE : pull up/down config
ODE : open drain enable/disable
FSEL: slew rate slow/fast
DSE : drive strength


Other than linux kernel documentation, some good documentation on pin setting can be found in 

AN5078: Influence of Pin Setting on System Function and Performance