[PATCH] char/isicom: Firmware loading

Firmware loading via hotplug added.
Cleanup firmware old-way fields in header file.

Signed-off-by: Jiri Slaby <xslaby@fi.muni.cz>
Cc: Greg KH <greg@kroah.com>
Signed-off-by: Andrew Morton <akpm@osdl.org>
Signed-off-by: Linus Torvalds <torvalds@osdl.org>
diff --git a/drivers/char/isicom.c b/drivers/char/isicom.c
index 9ef8ab3..55a47b3 100644
--- a/drivers/char/isicom.c
+++ b/drivers/char/isicom.c
@@ -112,6 +112,7 @@
  */
 
 #include <linux/module.h>
+#include <linux/firmware.h>
 #include <linux/kernel.h>
 #include <linux/tty.h>
 #include <linux/tty_flip.h>
@@ -120,7 +121,6 @@
 #include <linux/sched.h>
 #include <linux/serial.h>
 #include <linux/mm.h>
-#include <linux/miscdevice.h>
 #include <linux/interrupt.h>
 #include <linux/timer.h>
 #include <linux/delay.h>
@@ -175,8 +175,6 @@
 static struct timer_list tx;
 static char re_schedule = 1;
 
-static int ISILoad_ioctl(struct inode *inode, struct file *filp, unsigned  int cmd, unsigned long arg);
-
 static void isicom_tx(unsigned long _data);
 static void isicom_start(struct tty_struct *tty);
 
@@ -384,233 +382,6 @@
 	unlock_card(card);
 }
 
-
-/*
- *  Firmware loader driver specific routines. This needs to mostly die
- *  and be replaced with request_firmware.
- */
-
-static struct file_operations ISILoad_fops = {
-	.owner		= THIS_MODULE,
-	.ioctl		= ISILoad_ioctl,
-};
-
-static struct miscdevice isiloader_device = {
-	ISILOAD_MISC_MINOR, "isictl", &ISILoad_fops
-};
-
-
-static inline int WaitTillCardIsFree(unsigned long base)
-{
-	unsigned long count=0;
-	while( (!(inw(base+0xe) & 0x1)) && (count++ < 6000000));
-	if (inw(base+0xe)&0x1)
-		return 0;
-	else
-		return 1;
-}
-
-static int ISILoad_ioctl(struct inode *inode, struct file *filp,
-	unsigned int cmd, unsigned long arg)
-{
-	unsigned int card, i, j, signature, status, portcount = 0;
-	unsigned long t, base;
-	u16 word_count;
-	bin_frame frame;
-	void __user *argp = (void __user *)arg;
-	/* exec_record exec_rec; */
-
-	if (get_user(card, (int __user *)argp))
-		return -EFAULT;
-
-	if (card < 0 || card >= BOARD_COUNT)
-		return -ENXIO;
-
-	base=isi_card[card].base;
-
-	if (base==0)
-		return -ENXIO;	/* disabled or not used */
-
-	switch(cmd) {
-	case MIOCTL_RESET_CARD:
-		if (!capable(CAP_SYS_ADMIN))
-			return -EPERM;
-		printk(KERN_DEBUG "ISILoad:Resetting Card%d at 0x%lx ",card+1,base);
-
-		inw(base+0x8);
-
-		for (t=jiffies+HZ/100;time_before(jiffies, t););
-
-		outw(0,base+0x8); /* Reset */
-
-		for (j=1;j<=3;j++) {
-			for (t=jiffies+HZ;time_before(jiffies, t););
-			printk(".");
-		}
-		signature=(inw(base+0x4)) & 0xff;
-		if (isi_card[card].isa) {
-
-			if (!(inw(base+0xe) & 0x1) || (inw(base+0x2))) {
-#ifdef ISICOM_DEBUG
-				printk("\nbase+0x2=0x%x , base+0xe=0x%x",inw(base+0x2),inw(base+0xe));
-#endif
-				printk("\nISILoad:ISA Card%d reset failure (Possible bad I/O Port Address 0x%lx).\n",card+1,base);
-				return -EIO;
-			}
-		}
-		else {
-			portcount = inw(base+0x2);
-			if (!(inw(base+0xe) & 0x1) || ((portcount!=0) && (portcount!=4) && (portcount!=8))) {
-#ifdef ISICOM_DEBUG
-				printk("\nbase+0x2=0x%x , base+0xe=0x%x",inw(base+0x2),inw(base+0xe));
-#endif
-				printk("\nISILoad:PCI Card%d reset failure (Possible bad I/O Port Address 0x%lx).\n",card+1,base);
-				return -EIO;
-			}
-		}
-		switch(signature) {
-		case	0xa5:
-		case	0xbb:
-		case	0xdd:
-				if (isi_card[card].isa)
-					isi_card[card].port_count = 8;
-				else {
-					if (portcount == 4)
-						isi_card[card].port_count = 4;
-					else
-						isi_card[card].port_count = 8;
-				}
-				isi_card[card].shift_count = 12;
-				break;
-
-		case	0xcc:	isi_card[card].port_count = 16;
-				isi_card[card].shift_count = 11;
-				break;
-
-		default: printk("ISILoad:Card%d reset failure (Possible bad I/O Port Address 0x%lx).\n",card+1,base);
-#ifdef ISICOM_DEBUG
-			printk("Sig=0x%x\n",signature);
-#endif
-			return -EIO;
-		}
-		printk("-Done\n");
-		return put_user(signature,(unsigned __user *)argp);
-
-	case	MIOCTL_LOAD_FIRMWARE:
-			if (!capable(CAP_SYS_ADMIN))
-				return -EPERM;
-
-			if (copy_from_user(&frame, argp, sizeof(bin_frame)))
-				return -EFAULT;
-
-			if (WaitTillCardIsFree(base))
-				return -EIO;
-
-			outw(0xf0,base); /* start upload sequence */
-			outw(0x00,base);
-			outw((frame.addr), base); /* lsb of adderess */
-
-			word_count=(frame.count >> 1) + frame.count % 2;
-			outw(word_count, base);
-			InterruptTheCard(base);
-
-			for (i=0;i<=0x2f;i++);	/* a wee bit of delay */
-
-			if (WaitTillCardIsFree(base))
-				return -EIO;
-
-			if ((status=inw(base+0x4))!=0) {
-				printk(KERN_WARNING "ISILoad:Card%d rejected load header:\nAddress:0x%x \nCount:0x%x \nStatus:0x%x \n",
-				card+1, frame.addr, frame.count, status);
-				return -EIO;
-			}
-			outsw(base, (void *) frame.bin_data, word_count);
-
-			InterruptTheCard(base);
-
-			for (i=0;i<=0x0f;i++);	/* another wee bit of delay */
-
-			if (WaitTillCardIsFree(base))
-				return -EIO;
-
-			if ((status=inw(base+0x4))!=0) {
-				printk(KERN_ERR "ISILoad:Card%d got out of sync.Card Status:0x%x\n",card+1, status);
-				return -EIO;
-			}
-			return 0;
-
-	case	MIOCTL_READ_FIRMWARE:
-			if (!capable(CAP_SYS_ADMIN))
-				return -EPERM;
-
-			if (copy_from_user(&frame, argp, sizeof(bin_header)))
-				return -EFAULT;
-
-			if (WaitTillCardIsFree(base))
-				return -EIO;
-
-			outw(0xf1,base); /* start download sequence */
-			outw(0x00,base);
-			outw((frame.addr), base); /* lsb of adderess */
-
-			word_count=(frame.count >> 1) + frame.count % 2;
-			outw(word_count+1, base);
-			InterruptTheCard(base);
-
-			for (i=0;i<=0xf;i++);	/* a wee bit of delay */
-
-			if (WaitTillCardIsFree(base))
-				return -EIO;
-
-			if ((status=inw(base+0x4))!=0) {
-				printk(KERN_WARNING "ISILoad:Card%d rejected verify header:\nAddress:0x%x \nCount:0x%x \nStatus:0x%x \n",
-				card+1, frame.addr, frame.count, status);
-				return -EIO;
-			}
-
-			inw(base);
-			insw(base, frame.bin_data, word_count);
-			InterruptTheCard(base);
-
-			for (i=0;i<=0x0f;i++);	/* another wee bit of delay */
-
-			if (WaitTillCardIsFree(base))
-				return -EIO;
-
-			if ((status=inw(base+0x4))!=0) {
-				printk(KERN_ERR "ISILoad:Card%d verify got out of sync.Card Status:0x%x\n",card+1, status);
-				return -EIO;
-			}
-
-			if (copy_to_user(argp, &frame, sizeof(bin_frame)))
-				return -EFAULT;
-			return 0;
-
-	case	MIOCTL_XFER_CTRL:
-			if (!capable(CAP_SYS_ADMIN))
-				return -EPERM;
-			if (WaitTillCardIsFree(base))
-				return -EIO;
-
-			outw(0xf2, base);
-			outw(0x800, base);
-			outw(0x0, base);
-			outw(0x0, base);
-			InterruptTheCard(base);
-			outw(0x0, base+0x4); /* for ISI4608 cards */
-
-			isi_card[card].status |= FIRMWARE_LOADED;
-			return 0;
-
-	default:
-#ifdef ISICOM_DEBUG
-		printk(KERN_DEBUG "ISILoad: Received Ioctl cmd 0x%x.\n", cmd);
-#endif
-		return -ENOIOCTLCMD;
-	}
-}
-
-
 /*
  *	ISICOM Driver specific routines ...
  *
@@ -1927,6 +1698,175 @@
 	return retval;
 }
 
+static inline int WaitTillCardIsFree(u16 base)
+{
+	unsigned long count = 0;
+
+	while (!(inw(base + 0xe) & 0x1) && count++ < 100)
+		msleep(5);
+
+	return !(inw(base + 0xe) & 0x1);
+}
+
+static int __devinit load_firmware(struct pci_dev *pdev,
+	const unsigned int index, const unsigned int signature)
+{
+	struct isi_board *board = pci_get_drvdata(pdev);
+	const struct firmware *fw;
+	unsigned long base = board->base;
+	unsigned int a;
+	u16 word_count, status;
+	int retval = -EIO;
+	char *name;
+	u8 *data;
+
+	struct stframe {
+		u16	addr;
+		u16	count;
+		u8	data[0];
+	} *frame;
+
+	switch (signature) {
+	case 0xa5:
+		name = "isi608.bin";
+		break;
+	case 0xbb:
+		name = "isi608em.bin";
+		break;
+	case 0xcc:
+		name = "isi616em.bin";
+		break;
+	case 0xdd:
+		name = "isi4608.bin";
+		break;
+	case 0xee:
+		name = "isi4616.bin";
+		break;
+	default:
+		dev_err(&pdev->dev, "Unknown signature.\n");
+		goto end;
+ 	}
+
+	retval = request_firmware(&fw, name, &pdev->dev);
+	if (retval)
+		goto end;
+
+	for (frame = (struct stframe *)fw->data;
+			frame < (struct stframe *)(fw->data + fw->size);
+			frame++) {
+		if (WaitTillCardIsFree(base))
+			goto errrelfw;
+
+		outw(0xf0, base);	/* start upload sequence */
+		outw(0x00, base);
+		outw(frame->addr, base); /* lsb of address */
+
+		word_count = frame->count / 2 + frame->count % 2;
+		outw(word_count, base);
+		InterruptTheCard(base);
+
+		udelay(100); /* 0x2f */
+
+		if (WaitTillCardIsFree(base))
+			goto errrelfw;
+
+		if ((status = inw(base + 0x4)) != 0) {
+			dev_warn(&pdev->dev, "Card%d rejected load header:\n"
+				"Address:0x%x\nCount:0x%x\nStatus:0x%x\n",
+				index + 1, frame->addr, frame->count, status);
+			goto errrelfw;
+		}
+		outsw(base, frame->data, word_count);
+
+		InterruptTheCard(base);
+
+		udelay(50); /* 0x0f */
+
+		if (WaitTillCardIsFree(base))
+			goto errrelfw;
+
+		if ((status = inw(base + 0x4)) != 0) {
+			dev_err(&pdev->dev, "Card%d got out of sync.Card "
+				"Status:0x%x\n", index + 1, status);
+			goto errrelfw;
+		}
+ 	}
+
+	retval = -EIO;
+
+	if (WaitTillCardIsFree(base))
+		goto errrelfw;
+
+	outw(0xf2, base);
+	outw(0x800, base);
+	outw(0x0, base);
+	outw(0x0, base);
+	InterruptTheCard(base);
+	outw(0x0, base + 0x4); /* for ISI4608 cards */
+
+/* XXX: should we test it by reading it back and comparing with original like
+ * in load firmware package? */
+	for (frame = (struct stframe*)fw->data;
+			frame < (struct stframe*)(fw->data + fw->size);
+			frame++) {
+		if (WaitTillCardIsFree(base))
+			goto errrelfw;
+
+		outw(0xf1, base); /* start download sequence */
+		outw(0x00, base);
+		outw(frame->addr, base); /* lsb of address */
+
+		word_count = (frame->count >> 1) + frame->count % 2;
+		outw(word_count + 1, base);
+		InterruptTheCard(base);
+
+		udelay(50); /* 0xf */
+
+		if (WaitTillCardIsFree(base))
+			goto errrelfw;
+
+		if ((status = inw(base + 0x4)) != 0) {
+			dev_warn(&pdev->dev, "Card%d rejected verify header:\n"
+				"Address:0x%x\nCount:0x%x\nStatus: 0x%x\n",
+				index + 1, frame->addr, frame->count, status);
+			goto errrelfw;
+		}
+
+		data = kmalloc(word_count * 2, GFP_KERNEL);
+		inw(base);
+		insw(base, data, word_count);
+		InterruptTheCard(base);
+
+		for (a = 0; a < frame->count; a++)
+			if (data[a] != frame->data[a]) {
+				kfree(data);
+				dev_err(&pdev->dev, "Card%d, firmware upload "
+					"failed\n", index + 1);
+				goto errrelfw;
+			}
+		kfree(data);
+
+		udelay(50); /* 0xf */
+
+		if (WaitTillCardIsFree(base))
+			goto errrelfw;
+
+		if ((status = inw(base + 0x4)) != 0) {
+			dev_err(&pdev->dev, "Card%d verify got out of sync. "
+				"Card Status:0x%x\n", index + 1, status);
+			goto errrelfw;
+		}
+	}
+
+	board->status |= FIRMWARE_LOADED;
+	retval = 0;
+
+errrelfw:
+	release_firmware(fw);
+end:
+	return retval;
+}
+
 /*
  *	Insmod can set static symbols so keep these static
  */
@@ -1976,6 +1916,10 @@
 	if (retval < 0)
 		goto errunri;
 
+	retval = load_firmware(pdev, index, signature);
+	if (retval < 0)
+		goto errunri;
+
 	return 0;
 
 errunri:
@@ -2048,10 +1992,6 @@
 		goto errtty;
 	}
 
-	retval = misc_register(&isiloader_device);
-	if (retval < 0)
-		goto errpci;
-
 	init_timer(&tx);
 	tx.expires = jiffies + 1;
 	tx.data = 0;
@@ -2060,8 +2000,6 @@
 	add_timer(&tx);
 
 	return 0;
-errpci:
-	pci_unregister_driver(&isicom_driver);
 errtty:
 	isicom_unregister_tty_driver();
 error: