Earlier I had assumed that :
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.
Platform drivers are dedicated to devices not based on conventional buses. I2C devices or SPI devices are platform devices, but respectively rely on I2C or SPI buses not on the platform bus. Everything needs to be done manually with the platform driver.
There are various types of drivers present in GNU/Linux such as Character, Block, Network and USB drivers. In this column, we will explore only character drivers. Character drivers are the most common drivers.
The software that handles or manages a hardware controller is known as a device driver. The Linux kernel device drivers are, essentially, a shared library of privileged, memory resident, low level hardware handling routines. It is Linux's device drivers that handle the peculiarities of the devices they are managing.
Windows device driver architecture is different from the one used in Linux drivers, with either of them having their own pros and cons. Differences are mainly influenced by the fact that Windows is a closed-source OS while Linux is open-source.
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:
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.
Platform devices are bound to drivers by matching names,
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:
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
.
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:
-M versatilepb
machine description, which represents the SoCcompatible
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:
vendor:device
ID (QEMU_VENDOR_ID, EDU_DEVICE_ID
on example). This is baked into every device, and vendors must ensure uniqueness.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);
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With