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);
}