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

CLinuxLinux KernelLinux Device-Driver

C Problem Overview


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.

C Solutions


Solution 1 - C

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.

Solution 2 - C

Minimal module code examples

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

Platform device example

Code:

Further integration notes at: https://stackoverflow.com/a/44612957/895245

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:

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

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionkzsView Question on Stackoverflow
Solution 1 - Cm-ricView Answer on Stackoverflow
Solution 2 - CCiro Santilli Путлер Капут 六四事View Answer on Stackoverflow