I’m writing a Linux device driver for the PCI/PCIe cards 5i25 and 6i25 available from MESA electronics[1]
I’ve never written a device driver before. Currently my driver does nothing except enable the PCI device, map the BAR0 to system address space, and check the “magic value” available in BAR0 + 0x100.
It then prints out all of the probed module descriptors that correspond to available hardware functionality.
The magic value is an arbitrary number to test that your driver is accessing the PCI device correctly.
The next step is to accept module load-time parameters and configure the board as desired. Mesa has made a very capable board with the HOSTMOT2 firmware[4]. It has many options and many configuration registers.
Beyond configuration, the driver will have to accept IOCTL calls from user programs and drive the 6i25.
Here’s the code:
/* * ni25.c * * Author: Martin Klingensmith * Description: Driver for Mesa electronics 5i25 and 6i25 PCI[e] boards * */ #include <linux/interrupt.h> #include <linux/pci.h> #include <linux/slab.h> #include <linux/module.h> /* Needed by all modules */ #include <linux/kernel.h> /* Needed for KERN_INFO */ #define DRIVER_AUTHOR "Martin Klingensmith <martin@nnytech.net>" #define DRIVER_DESC "A driver for MESA electronics 5i25/6i25 IO cards" #define DRIVER_NAME "ni25" #define MAGIC_NUMBER 0x55AACAFE /* Magic number defined by Mesa */ #define LEDS_OFFSET 0x0200 #define IDROM_OFFSET 0x010C /* Location of IDROM offset value */ struct ni25_idrom { u32 IDROMType; u32 OffsetToModules; u32 OffsetToPindesc; u32 BoardNameLow; u32 BoardNameHigh; u32 FPGASize; u32 FPGAPins; u32 IOPorts; u32 IOWidth; u32 PortWidth; u32 ClockLow; u32 ClockHigh; u32 InstanceStride0; u32 InstanceStride1; u32 RegisterStride0; u32 RegisterStride1; }; struct module_descriptor { u8 GTag; /*General function tag */ u8 Version; /*module version */ u8 ClockTag; /*Whether module uses ClockHigh or ClockLow */ u8 Instances; /*Number of instances of module in configuration */ u16 BaseAddress; /*offset to module. This is also specific register = Tag*/ u8 Registers; /*Number of registers per module */ u8 Strides; /*Specifies which strides to use */ u32 MPBitmap; /*bit map of which registers are multiple '1' = multiple, LSb = reg(0) */ }; struct pin_descriptor { u8 SecPin; /*Which pin of secondary function connects here eg: A,B,IDX. Output pins have bit 7 = '1'*/ u8 SecTag; /*Secondary function type (PWM,QCTR etc). Same as module GTag*/ u8 SecUnit; /*Which secondary unit or channel connects here*/ u8 PrimaryTag; /*Primary function tag (normally I/O port)*/ }; struct ni25_brd { unsigned long paddr; void __iomem *regs; /* Board BAR0 mapped location */ u32 iolen; int irq; u16 bus_num; struct device *parent_dev; u16 num_cs; /* supported slave numbers */ }; struct ni25_dev { struct pci_dev *pdev; struct ni25_brd brd; struct ni25_idrom idrom; u32 idrom_offset; struct module_descriptor module[32]; u32 num_modules; }; /* * * ni25_pci_probe * */ static int ni25_pci_probe(struct pci_dev *pdev, const struct pci_device_id *ent) { struct ni25_dev *ni25; struct ni25_brd *brd; int pci_bar = 0; int ret; int v=0; void __iomem *regs; char name[9]; printk(KERN_INFO "NI25: found PCI card (ID: %04x:%04x)\n", pdev->vendor, pdev->device); ret = pci_enable_device(pdev); if (ret) return ret; ni25 = kzalloc(sizeof(struct ni25_dev), GFP_KERNEL); if (!ni25) { ret = -ENOMEM; goto err_disable; } ni25->pdev = pdev; brd = &ni25->brd; /* Get basic io resource and map it */ brd->paddr = pci_resource_start(pdev,pci_bar); brd->iolen = pci_resource_len(pdev, pci_bar); printk(KERN_INFO "NI25: address %04x length %04x\n",(unsigned int)brd->paddr,(unsigned int)brd->iolen); ret = pci_request_region(pdev, pci_bar, dev_name(&pdev->dev)); if (ret) goto err_kfree; regs = ioremap_nocache((unsigned long)brd->paddr, pci_resource_len(pdev, pci_bar)); brd->regs = regs; if (!brd->regs) { printk(KERN_INFO "NI25: ioremap_nocache call failed.\n"); ret = -ENOMEM; goto err_release_reg; } /* Read magic number from card register */ v = readl(regs + 0x100); if(v != MAGIC_NUMBER){ printk(KERN_INFO "NI25: unknown magic number. %04x != %04x\n",MAGIC_NUMBER,v); ret = -1; goto err_unmap; } /* Get configuration name */ for(v=0;v<8;v++){ name[v] = ioread8(regs+0x104+v); } name[8]=0; printk(KERN_INFO "NI25: config name: [%s]\n",name); ni25->idrom_offset = ioread32(regs+IDROM_OFFSET); printk(KERN_INFO "NI25: idrom offset: %04x\n",ni25->idrom_offset); /* Read IDROM */ memcpy(&ni25->idrom,regs+ni25->idrom_offset,sizeof(struct ni25_idrom)); printk(KERN_INFO "NI25: modules offset: %04x\n",ni25->idrom.OffsetToModules); /* Read all modules descriptors */ memcpy(ni25->module,regs+ni25->idrom.OffsetToModules + ni25->idrom_offset,0xC * 32); for(v=0;v<32;v++){ if(ni25->module[v].GTag==0){ ni25->num_modules = v-1; break; } printk(KERN_INFO "NI25: module %d \n\tGTag=[%04x]\n\tVersion=[%04x]\n\tClockTag=[%04x]\n\tInstances=[%04x]\n\tBaseAddress=[%04x]\n\tRegisters=[%04x]\n\tStrides=[%04x]\n\tMPBitmap=[%04x]\n"\ ,v,ni25->module[v].GTag, \ ni25->module[v].Version, \ ni25->module[v].ClockTag, \ ni25->module[v].Instances, \ ni25->module[v].BaseAddress, \ ni25->module[v].Registers,\ ni25->module[v].Strides,\ ni25->module[v].MPBitmap); } printk(KERN_INFO "NI25: found %d modules.\n",ni25->num_modules); brd->parent_dev = &pdev->dev; brd->bus_num = 0; brd->num_cs = 4; brd->irq = pdev->irq; pci_set_drvdata(pdev, ni25); return 0; err_unmap: iounmap(brd->regs); err_release_reg: pci_release_region(pdev, pci_bar); err_kfree: kfree(ni25); err_disable: pci_disable_device(pdev); return ret; } /* * * * ni25_pci_remove * * */ static void ni25_pci_remove(struct pci_dev *pdev) { struct ni25_dev *ni25 = pci_get_drvdata(pdev); printk(KERN_INFO "ni25: unloading...\n"); iounmap(ni25->brd.regs); pci_release_region(pdev, 0); kfree(ni25); pci_disable_device(pdev); } /* * * ni25_suspend */ #ifdef CONFIG_PM static int ni25_suspend(struct pci_dev *pdev, pm_message_t state) { pci_save_state(pdev); pci_disable_device(pdev); pci_set_power_state(pdev, pci_choose_state(pdev, state)); return 0; } /* ni25_resume */ static int ni25_resume(struct pci_dev *pdev) { u32 ret=-1; pci_set_power_state(pdev, PCI_D0); pci_restore_state(pdev); ret = pci_enable_device(pdev); if (ret) return ret; return 0; } #else #define spi_suspend NULL #define spi_resume NULL #endif /* Documentation */ MODULE_LICENSE("GPL"); MODULE_AUTHOR(DRIVER_AUTHOR); MODULE_DESCRIPTION(DRIVER_DESC); static DEFINE_PCI_DEVICE_TABLE(pci_ids) = { /* Intel MID platform SPI controller 0 */ { PCI_DEVICE(0x2718, 0x5125) }, {}, }; static struct pci_driver ni25_driver = { .name = DRIVER_NAME, .id_table = pci_ids, .probe = ni25_pci_probe, .remove = ni25_pci_remove, .suspend = ni25_suspend, .resume = ni25_resume, }; module_pci_driver(ni25_driver);
Here are the dmesg lines when the module is loaded:
[64520.720535] NI25: found PCI card (ID: 2718:5125) [64520.720553] NI25: address fe700000 length 10000 [64520.720606] NI25: config name: [HOSTMOT2] [64520.720610] NI25: idrom offset: 0400 [64520.720635] NI25: modules offset: 0040 [64520.720771] NI25: module 0 [64520.720771] GTag=[0002] [64520.720771] Version=[0000] [64520.720771] ClockTag=[0001] [64520.720771] Instances=[0001] [64520.720771] BaseAddress=[0c00] [64520.720771] Registers=[0003] [64520.720771] Strides=[0000] [64520.720771] MPBitmap=[0000] [64520.720783] NI25: module 1 [64520.720783] GTag=[0003] [64520.720783] Version=[0000] [64520.720783] ClockTag=[0001] [64520.720783] Instances=[0002] [64520.720783] BaseAddress=[1000] [64520.720783] Registers=[0005] [64520.720783] Strides=[0000] [64520.720783] MPBitmap=[001f] [64520.720794] NI25: module 2 [64520.720794] GTag=[0004] [64520.720794] Version=[0002] [64520.720794] ClockTag=[0001] [64520.720794] Instances=[0002] [64520.720794] BaseAddress=[3000] [64520.720794] Registers=[0005] [64520.720794] Strides=[0000] [64520.720794] MPBitmap=[0003] [64520.720805] NI25: module 3 [64520.720805] GTag=[00c1] [64520.720805] Version=[0000] [64520.720805] ClockTag=[0001] [64520.720805] Instances=[0001] [64520.720805] BaseAddress=[5a00] [64520.720805] Registers=[0006] [64520.720805] Strides=[0010] [64520.720805] MPBitmap=[003c] [64520.720816] NI25: module 4 [64520.720816] GTag=[0006] [64520.720816] Version=[0000] [64520.720816] ClockTag=[0002] [64520.720816] Instances=[0002] [64520.720816] BaseAddress=[4000] [64520.720816] Registers=[0005] [64520.720816] Strides=[0000] [64520.720816] MPBitmap=[0003] [64520.720828] NI25: module 5 [64520.720828] GTag=[0005] [64520.720828] Version=[0002] [64520.720828] ClockTag=[0001] [64520.720828] Instances=[0008] [64520.720828] BaseAddress=[2000] [64520.720828] Registers=[000a] [64520.720828] Strides=[0000] [64520.720828] MPBitmap=[01ff] [64520.720839] NI25: module 6 [64520.720839] GTag=[0080] [64520.720839] Version=[0000] [64520.720839] ClockTag=[0001] [64520.720839] Instances=[0001] [64520.720839] BaseAddress=[0200] [64520.720839] Registers=[0001] [64520.720839] Strides=[0000] [64520.720839] MPBitmap=[0000] [64520.720848] NI25: found 6 modules.
References
[1] Mesa Electronics
http://www.mesanet.com/
[2] The Linux Kernel Module Programming Guide
http://www.tldp.org/LDP/lkmpg/2.6/html/x121.html
[3] How To Write Linux PCI Drivers by Martin Mares <mj@suse.cz> on 07-Feb-2000
http://galileo.phys.virginia.edu/~rjh2j/l2beta/software/pci.txt
[4] LinuxCNC Documentation wiki
http://wiki.linuxcnc.org/cgi-bin/wiki.pl?HostMot2