What is the difference between a Linux platform driver and normal device driver?
Asked Answered
S

2

69

Earlier I had assumed that :

  • Platform driver is for those devices that are on chip.
  • Normal device driver are for those that are interfaced to the processor chip.

Before coming across one i2c driver... But here, I am reading through multi function i2c driver defined as platform driver. I had gone through https://www.kernel.org/doc/Documentation/driver-model/platform.txt. But still could not get clear idea to come to an conclusion on how to define drivers, like for both onchip as well interfaced devices.

Please somebody explain.

Savick answered 25/3, 2013 at 8:34 Comment(4)
The device is a MFD- multi function device. there is one field in platform_device; struct mfd cell which is not there in i2c_client structure. Maybe because of that reason driver is registered as platform driver. Please comment on this.!!Savick
atmel.com/Images/doc32098.pdf .....check this out...it might helpPrincedom
Yeah the document was good.. I think I could make use of that document sometime later. but I could not come to conclusion yet. I have asked one Master who is good at drivers.. I will post here once I get the answers.Savick
linuxseekernel.blogspot.com/2014/05/…Skeens
K
121

Your references are good but lack a definition of what is a platform device. There is one on LWN. What we can learn from this page:

  1. Platform devices are inherently not discoverable, i.e. the hardware cannot say "Hey! I'm present!" to the software. Typical examples are i2c devices, kernel/Documentation/i2c/instantiating-devices states:

    Unlike PCI or USB devices, I2C devices are not enumerated at the hardware level (at run time). Instead, the software must know (at compile time) which devices are connected on each I2C bus segment. So USB and PCI are not platform devices.

  2. Platform devices are bound to drivers by matching names,

  3. Platform devices should be registered very early during system boot. Because they are often critical to the rest of the system (platform) and its drivers.

So basically, the question "is it a platform device or a standard device?" is more a question of which bus it uses. To work with a particular platform device, you have to:

  1. register a platform driver that will manage this device. It should define a unique name,
  2. register your platform device, defining the same name as the driver.

Platform driver is for those devices that are on chip.

Not true (in theory, but true in practice). i2c devices are not onChip, but are platform devices because they are not discoverable. Also we can think of onChip devices which are normal devices. Example: an integrated PCI GPU chip on a modern x86 processor. It is discoverable, thus not a platform device.

Normal device driver are for those that are interfaced to the processor chip. before coming across one i2c driver.

Not true. Many normal devices are interfaced to the processor, but not through an i2c bus. Example: a USB mouse.

[EDIT] In your case, have a look to drivers/usb/host/ohci-pnx4008.c, which is a USB host controller platform device (Here the USB host controller is not discoverable, whereas USB devices, which will connect to it, are). It is a platform device registered by the board file (arch/arm/mach-pnx4008/core.c:pnx4008_init). And within its probe function, it registers its i2c device to the bus with i2c_register_driver. We can infer that the USB Host controller chipset talks to the CPU through an i2c bus.

Why that architecture? Because on one hand, this device can be considered a bare i2c device providing some functionalities to the system. On the other hand, it is a USB Host capable device. It needs to register to the USB stack (usb_create_hcd). So probing only i2c will be insufficient. Have a look to Documentation/i2c/instantiating-devices.

Keratinize answered 2/4, 2013 at 19:33 Comment(11)
Exactly you are right. I would give +1 for this:So basically, the question "is it a platform device or a standard device?" is more a question of which bus it uses. I could get and agree with all the points. but I could not understand or relate this one:Normal device driver are for those that are interfaced to the processor chip. before coming across one i2c driver. Please explain me in a better dimension so that it can make me understand.Savick
I see few drivers using i2c_driver_register and in this i2c case i see platform_driver_register. I have a question of which one to use between the two.Savick
@zair In the EDIT section of my answer, platform_driver_register registers the USB Host driver against the USB stack, whereas i2c_driver_register is used to allow the CPU talks to the USB Host Controller, via the i2c protocol. If the USB controller was SPI-capable, there would be a platform_driver_register and spi_register_driver instead.Keratinize
Excellent.. very clear. thanks for taking effort in explaining me in better way..I would give +1.Savick
I have one more doubt. I have notion that "All the Buses which donot have discoverable property like ID line will be based platform bus framework" eg. I2C has only clock and data so is based on platform bus. Is this true and if yes can u name any other bus which is based on platform architecture?Dissentious
SPI, Shodanex' answer #7648969 may help.Keratinize
@Keratinize would you please care to explain the same question in terms of embedded linux(specially Android). As there is no device to attach at runtime. Everything will be there at the time of starting of kernel. So why we have platform drivers/devices in that case ? Here I also want to ask that there are few devices which are virtual (no hardware exist), how it is characterized as character device or platform device ?Ruthannruthanne
Actually platform devices can be matched with their driver by id also. Moreover the main aim of introducing the same was to have a unified power management framework.Dissentious
Just because a bus is no discoverable doesn't make it a platform device. The following link defines a platform device as a device dirrectly connected to the CPU. see pages 36, and 45 - 2010.rmll.info/IMG/pdf/kernel-device-drivers-rmll2010.pdfKovrov
@SahilSingh and page 38 reads platform devices cannot be detected dynamically. a device directly connected to the CPU, I hope you don't confuse CPU with SoC. Is a SoC built-in, PCI connected GPU a platform device?Keratinize
Sorry, not quite understand the word 'discoverable'. Does it mean, discoverable devices notify the kernel of its existence, and platform devices are discovered by the kernel, passively?Eastsoutheast
D
8

Minimal module code examples

Maybe the difference will also become clearer with some concrete examples.

Platform device example

Code:

Further integration notes at: https://mcmap.net/q/282218/-how-to-add-a-new-device-in-qemu-source-code

See how:

  • register and interrupt addresses are hardcoded in the device tree and match the QEMU -M versatilepb machine description, which represents the SoC
  • there is no way to remove the device hardware (since it is part of the SoC)
  • the correct driver is selected by the compatible device tree property which matches platform_driver.name in the driver
  • platform_driver_register is the main register interface
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of_address.h>
#include <linux/of_device.h>
#include <linux/of_irq.h>
#include <linux/platform_device.h>

MODULE_LICENSE("GPL");

static struct resource res;
static unsigned int irq;
static void __iomem *map;

static irqreturn_t lkmc_irq_handler(int irq, void *dev)
{
    /* TODO this 34 and not 18 as in the DTS, likely the interrupt controller moves it around.
     * Understand precisely. 34 = 18 + 16. */
    pr_info("lkmc_irq_handler irq = %d dev = %llx\n", irq, *(unsigned long long *)dev);
    /* ACK the IRQ. */
    iowrite32(0x9ABCDEF0, map + 4);
    return IRQ_HANDLED;
}

static int lkmc_platform_device_probe(struct platform_device *pdev)
{
    int asdf;
    struct device *dev = &pdev->dev;
    struct device_node *np = dev->of_node;

    dev_info(dev, "probe\n");

    /* Play with our custom poperty. */
    if (of_property_read_u32(np, "lkmc-asdf", &asdf) ) {
        dev_err(dev, "of_property_read_u32\n");
        return -EINVAL;
    }
    if (asdf != 0x12345678) {
        dev_err(dev, "asdf = %llx\n", (unsigned long long)asdf);
        return -EINVAL;
    }

    /* IRQ. */
    irq = irq_of_parse_and_map(dev->of_node, 0);
    if (request_irq(irq, lkmc_irq_handler, 0, "lkmc_platform_device", dev) < 0) {
        dev_err(dev, "request_irq");
        return -EINVAL;
    }
    dev_info(dev, "irq = %u\n", irq);

    /* MMIO. */
    if (of_address_to_resource(pdev->dev.of_node, 0, &res)) {
        dev_err(dev, "of_address_to_resource");
        return -EINVAL;
    }
    if  (!request_mem_region(res.start, resource_size(&res), "lkmc_platform_device")) {
        dev_err(dev, "request_mem_region");
        return -EINVAL;
    }
    map = of_iomap(pdev->dev.of_node, 0);
    if (!map) {
        dev_err(dev, "of_iomap");
        return -EINVAL;
    }
    dev_info(dev, "res.start = %llx resource_size = %llx\n",
            (unsigned long long)res.start, (unsigned long long)resource_size(&res));

    /* Test MMIO and IRQ. */
    iowrite32(0x12345678, map);

    return 0;
}

static int lkmc_platform_device_remove(struct platform_device *pdev)
{
    dev_info(&pdev->dev, "remove\n");
    free_irq(irq, &pdev->dev);
    iounmap(map);
    release_mem_region(res.start, resource_size(&res));
    return 0;
}

static const struct of_device_id of_lkmc_platform_device_match[] = {
    { .compatible = "lkmc_platform_device", },
    {},
};

MODULE_DEVICE_TABLE(of, of_lkmc_platform_device_match);

static struct platform_driver lkmc_plaform_driver = {
    .probe      = lkmc_platform_device_probe,
    .remove     = lkmc_platform_device_remove,
    .driver     = {
        .name   = "lkmc_platform_device",
        .of_match_table = of_lkmc_platform_device_match,
        .owner = THIS_MODULE,
    },
};

static int lkmc_platform_device_init(void)
{
    pr_info("lkmc_platform_device_init\n");
    return platform_driver_register(&lkmc_plaform_driver);
}

static void lkmc_platform_device_exit(void)
{
    pr_info("lkmc_platform_device_exit\n");
    platform_driver_unregister(&lkmc_plaform_driver);
}

module_init(lkmc_platform_device_init)
module_exit(lkmc_platform_device_exit)

PCI non-platform device example

See how:

  • register and interrupt addresses are dynamically allocated by the PCI system, no device tree is used
  • the correct driver is selected by the PCI vendor:device ID (QEMU_VENDOR_ID, EDU_DEVICE_ID on example). This is baked into every device, and vendors must ensure uniqueness.
  • we can insert and remove the PCI device with device_add edu and device_del edu as we can in real life. Probing is not automatic, but can be done after boot with echo 1 > /sys/bus/pci/rescan. See also: Why is the probe method needed in Linux device drivers in addition to init?
#include <asm/uaccess.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/pci.h>

#define BAR 0
#define CDEV_NAME "lkmc_hw_pci_min"
#define EDU_DEVICE_ID 0x11e9
#define QEMU_VENDOR_ID 0x1234

MODULE_LICENSE("GPL");

static struct pci_device_id id_table[] = {
    { PCI_DEVICE(QEMU_VENDOR_ID, EDU_DEVICE_ID), },
    { 0, }
};
MODULE_DEVICE_TABLE(pci, id_table);
static int major;
static struct pci_dev *pdev;
static void __iomem *mmio;
static struct file_operations fops = {
    .owner   = THIS_MODULE,
};

static irqreturn_t irq_handler(int irq, void *dev)
{
    pr_info("irq_handler irq = %d dev = %d\n", irq, *(int *)dev);
    iowrite32(0, mmio + 4);
    return IRQ_HANDLED;
}

static int probe(struct pci_dev *dev, const struct pci_device_id *id)
{
    pr_info("probe\n");
    major = register_chrdev(0, CDEV_NAME, &fops);
    pdev = dev;
    if (pci_enable_device(dev) < 0) {
        dev_err(&(pdev->dev), "pci_enable_device\n");
        goto error;
    }
    if (pci_request_region(dev, BAR, "myregion0")) {
        dev_err(&(pdev->dev), "pci_request_region\n");
        goto error;
    }
    mmio = pci_iomap(pdev, BAR, pci_resource_len(pdev, BAR));
    pr_info("dev->irq = %u\n", dev->irq);
    if (request_irq(dev->irq, irq_handler, IRQF_SHARED, "pci_irq_handler0", &major) < 0) {
        dev_err(&(dev->dev), "request_irq\n");
        goto error;
    }
    iowrite32(0x12345678, mmio);
    return 0;
error:
    return 1;
}

static void remove(struct pci_dev *dev)
{
    pr_info("remove\n");
    free_irq(dev->irq, &major);
    pci_release_region(dev, BAR);
    unregister_chrdev(major, CDEV_NAME);
}

static struct pci_driver pci_driver = {
    .name     = CDEV_NAME,
    .id_table = id_table,
    .probe    = probe,
    .remove   = remove,
};

static int myinit(void)
{
    if (pci_register_driver(&pci_driver) < 0) {
        return 1;
    }
    return 0;
}

static void myexit(void)
{
    pci_unregister_driver(&pci_driver);
}

module_init(myinit);
module_exit(myexit);
Deicer answered 9/7, 2017 at 9:4 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.