Writing a PCI device driver

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

 

Leave a comment

Your email address will not be published. Required fields are marked *