Index: drivers/block/Kconfig
===================================================================
RCS file: /cvs/eps/dev_eng/sw/products/Linux/PXAEngine/pxa/linux-2.6.17-rc5/drivers/block/Kconfig,v
retrieving revision 1.1.1.1
retrieving revision 1.2
diff -c -3 -p -r1.1.1.1 -r1.2
*** drivers/block/Kconfig	29 May 2006 00:55:20 -0000	1.1.1.1
--- drivers/block/Kconfig	1 Jun 2006 17:05:41 -0000	1.2
***************
*** 4,9 ****
--- 4,16 ----
  
  menu "Block devices"
  
+ config BLK_DEV_LOGICPD_CF
+        bool "LogicPD memory-mapped CompactFlash card support"
+        depends on MACH_LOGICPD_PXA270
+        ---help---
+          If you want to use the memory-mapped comapct flash card on
+          the LogicPD SDK, say Y.
+ 
  config BLK_DEV_FD
  	tristate "Normal floppy disk support"
  	depends on ARCH_MAY_HAVE_PC_FDC
Index: drivers/block/Makefile
===================================================================
RCS file: /cvs/eps/dev_eng/sw/products/Linux/PXAEngine/pxa/linux-2.6.17-rc5/drivers/block/Makefile,v
retrieving revision 1.1.1.1
retrieving revision 1.2
diff -c -3 -p -r1.1.1.1 -r1.2
*** drivers/block/Makefile	29 May 2006 00:55:20 -0000	1.1.1.1
--- drivers/block/Makefile	1 Jun 2006 17:05:59 -0000	1.2
***************
*** 5,10 ****
--- 5,12 ----
  # Rewritten to use lists instead of if-statements.
  # 
  
+ obj-$(CONFIG_BLK_DEV_LOGICPD_CF)+= lpd270-cf.o
+ 
  obj-$(CONFIG_MAC_FLOPPY)	+= swim3.o
  obj-$(CONFIG_BLK_DEV_FD)	+= floppy.o
  obj-$(CONFIG_AMIGA_FLOPPY)	+= amiflop.o
Index: drivers/block/lpd270-cf.c
===================================================================
RCS file: drivers/block/lpd270-cf.c
diff -N drivers/block/lpd270-cf.c
*** /dev/null	1 Jan 1970 00:00:00 -0000
--- drivers/block/lpd270-cf.c	1 Jun 2006 16:23:35 -0000	1.1
***************
*** 0 ****
--- 1,675 ----
+ /*
+  * Support for LogicPD SDK Memory-mapped CompactFlash interface
+  *
+  * Copyright 2006 Logic Product Development <peterb@logicpd.com>
+  *
+  * This file is subject to the terms and conditions of the GNU General Public
+  * License.  See the file COPYING in the main directory of this archive
+  * for more details.
+  */
+ 
+ 
+ /* Uncomment the following if you want verbose error reports. */
+ /* #define VERBOSE_ERRORS */
+ 
+ #include <linux/blkdev.h>
+ #include <linux/errno.h>
+ #include <linux/signal.h>
+ #include <linux/interrupt.h>
+ #include <linux/timer.h>
+ #include <linux/fs.h>
+ #include <linux/kernel.h>
+ #include <linux/genhd.h>
+ #include <linux/slab.h>
+ #include <linux/string.h>
+ #include <linux/ioport.h>
+ #include <linux/mc146818rtc.h> /* CMOS defines */
+ #include <linux/init.h>
+ #include <linux/blkpg.h>
+ #include <linux/hdreg.h>
+ 
+ #define REALLY_SLOW_IO
+ #include <asm/system.h>
+ #include <asm/io.h>
+ #include <asm/uaccess.h>
+ #include <asm/delay.h>
+ 
+ #ifdef __arm__
+ #undef  HD_IRQ
+ #endif
+ #include <asm/irq.h>
+ #ifdef __arm__
+ #define HD_IRQ IRQ_HARDDISK
+ #endif
+ 
+ #define DEBUG
+ 
+ /* Hd controller regster ports */
+ 
+ #define HD_DATA		0x1f0		/* _CTL when writing */
+ #define HD_ERROR	0x1f1		/* see err-bits */
+ #define HD_NSECTOR	0x1f2		/* nr of sectors to read/write */
+ #define HD_SECTOR	0x1f3		/* starting sector */
+ #define HD_LCYL		0x1f4		/* starting cylinder */
+ #define HD_HCYL		0x1f5		/* high byte of starting cyl */
+ #define HD_CURENT	0x1f6		/* 101dhhhh , d=drive, hhhh=head */
+ #define HD_STATUS	0x1f7		/* see status-bits */
+ #define HD_FEATURE	HD_ERROR	/* same io address, read=error, write=feature */
+ #define HD_PRECOMP	HD_FEATURE	/* obsolete use of this port - predates IDE */
+ #define HD_COMMAND	HD_STATUS	/* same io address, read=status, write=cmd */
+ 
+ #define HD_CMD		0x3f6		/* used for resets */
+ #define HD_ALTSTATUS	0x3f6		/* same as HD_STATUS but doesn't clear irq */
+ 
+ /* Bits of HD_STATUS */
+ #define ERR_STAT		0x01
+ #define INDEX_STAT		0x02
+ #define ECC_STAT		0x04	/* Corrected error */
+ #define DRQ_STAT		0x08
+ #define SEEK_STAT		0x10
+ #define SERVICE_STAT		SEEK_STAT
+ #define WRERR_STAT		0x20
+ #define READY_STAT		0x40
+ #define BUSY_STAT		0x80
+ 
+ /* Bits for HD_ERROR */
+ #define MARK_ERR		0x01	/* Bad address mark */
+ #define TRK0_ERR		0x02	/* couldn't find track 0 */
+ #define ABRT_ERR		0x04	/* Command aborted */
+ #define MCR_ERR			0x08	/* media change request */
+ #define ID_ERR			0x10	/* ID field not found */
+ #define MC_ERR			0x20	/* media changed */
+ #define ECC_ERR			0x40	/* Uncorrectable ECC error */
+ #define BBD_ERR			0x80	/* pre-EIDE meaning:  block marked bad */
+ #define ICRC_ERR		0x80	/* new meaning:  CRC error during transfer */
+ 
+ static DEFINE_SPINLOCK(hd_lock);
+ static struct request_queue *hd_queue;
+ 
+ #define MAJOR_NR HD_MAJOR
+ #define QUEUE (hd_queue)
+ #define CURRENT elv_next_request(hd_queue)
+ 
+ #define TIMEOUT_VALUE	(6*HZ)
+ #define	HD_DELAY	0
+ 
+ #define MAX_ERRORS     16	/* Max read/write errors/sector */
+ #define RESET_FREQ      8	/* Reset controller every 8th retry */
+ #define RECAL_FREQ      4	/* Recalibrate every 4th retry */
+ #define MAX_HD		2
+ 
+ #define STAT_OK		(READY_STAT|SEEK_STAT)
+ #define OK_STATUS(s)	(((s)&(STAT_OK|(BUSY_STAT|WRERR_STAT|ERR_STAT)))==STAT_OK)
+ 
+ static int driveno = 0;
+ static int debug = 0;
+ 
+ // Start of CF registers
+ #define CPLD_ATA_REG_BASE 0x14001800
+ static unsigned char *reg_base;
+ 
+ static inline unsigned int read_reg(unsigned char *base, unsigned int reg)
+ {
+   volatile unsigned short val;
+ 
+   if (reg & 1)
+ 	val = *((volatile unsigned short *)(base + reg - 1)) >> 8;
+   else
+ 	val = *((volatile unsigned short *)(base + reg));
+ 
+   if (debug)
+ 	printk("%s: %02x %04x \n", __FUNCTION__, reg, val);
+ 
+   return val;
+ }
+ 
+ static inline void write_reg(unsigned char *base, unsigned int reg, unsigned int val)
+ {
+   if (debug)
+ 	printk("%s: %02x %04x\n", __FUNCTION__, reg, val);
+   if (reg & 1)
+ 	*((volatile unsigned short *)(base + reg - 1)) = (val << 8);
+   else
+ 	*((volatile unsigned short *)(base + reg)) = val;
+ }
+ 
+ #define CB_DATA		0x08
+ #define CB_ERR		0x0d
+ #define CB_SC_SN	0x02
+ #define CB_CYL		0x04
+ #define CB_STAT		0x07
+ #define CB_DH_CMD	0x06
+ #define CB_ASTAT	0x0e
+ #define CB_DC		0x0e
+ #define CB_DA		0x0f
+ 
+ #define CB_STAT_BSY 0x80
+ #define CB_STAT_DRQ 0x08
+ #define CB_STAT_SEEK 0x10
+ #define CB_STAT_DF 0x20
+ #define CB_STAT_READY 0x40
+ #define CB_STAT_ERR 0x01
+ #define CB_DC_HD15 0x08
+ #define CB_DC_NIEN 0x02
+ 
+ #define CMD_IDENTIFY_DEVICE 0xec
+ #define CMD_READ_SECTORS 0x20
+ #define CMD_WRITE_SECTORS 0x30
+ 
+ #define TIMEOUT 0x800000
+ 
+ int cfide_card_present(void)
+ {
+   unsigned char data, data1, data2;
+ 
+   /* Flip Sector Count */
+   data = read_reg(reg_base, CB_SC_SN);
+   data1 = (~data) & 0xff;
+   write_reg(reg_base, CB_SC_SN, data1);
+ 
+   /* write to the data register to waggle the bus */
+   write_reg(reg_base, CB_DATA, data);
+ 
+   /* Read back the sector count and if it matches what we put there
+ 	 then the CF is present */
+   data2 = read_reg(reg_base, CB_SC_SN);
+   if (data2 == data1)
+ 	return 1;
+   else {
+ 	printk("data %02x data1 %02x data2 %02x\n", data, data1, data2);
+ 	return 0;
+   }
+ }
+ 
+ /*
+  *  This struct defines the HD's and their types.
+  */
+ struct hd_i_struct {
+ 	unsigned int head,sect,cyl,wpcom,lzone,ctl;
+ 	int unit;
+ 	int recalibrate;
+ 	int special_op;
+ };
+ 	
+ #ifdef HD_TYPE
+ static struct hd_i_struct hd_info[] = { HD_TYPE };
+ static int NR_HD = ((sizeof (hd_info))/(sizeof (struct hd_i_struct)));
+ #else
+ static struct hd_i_struct hd_info[MAX_HD];
+ static int NR_HD;
+ #endif
+ 
+ static struct gendisk *hd_gendisk[MAX_HD];
+ 
+ 
+ 
+ #if (HD_DELAY > 0)
+ 
+ #include <asm/i8253.h>
+ 
+ unsigned long last_req;
+ 
+ unsigned long read_timer(void)
+ {
+ 	unsigned long t, flags;
+ 	int i;
+ 
+ 	spin_lock_irqsave(&i8253_lock, flags);
+ 	t = jiffies * 11932;
+     	outb_p(0, 0x43);
+ 	i = inb_p(0x40);
+ 	i |= inb(0x40) << 8;
+ 	spin_unlock_irqrestore(&i8253_lock, flags);
+ 	return(t - i);
+ }
+ #endif
+ 
+ static void __init hd_setup(char *str, int *ints)
+ {
+ 	int hdind = 0;
+ 
+ 	if (ints[0] != 3)
+ 		return;
+ 	if (hd_info[0].head != 0)
+ 		hdind=1;
+ 	hd_info[hdind].head = ints[2];
+ 	hd_info[hdind].sect = ints[3];
+ 	hd_info[hdind].cyl = ints[1];
+ 	hd_info[hdind].wpcom = 0;
+ 	hd_info[hdind].lzone = ints[1];
+ 	hd_info[hdind].ctl = (ints[2] > 8 ? 8 : 0);
+ 	NR_HD = hdind+1;
+ }
+ 
+ 
+ 
+ void cfide_wait_fin(void)
+ {
+   unsigned long timer;
+ 
+   // printk("%s:%d\n", __FUNCTION__, __LINE__);
+ 
+   udelay(500);  // wait 500us
+ 
+   for (timer = 0;
+ 	   timer < TIMEOUT && (read_reg(reg_base, CB_STAT) & CB_STAT_BSY); ++timer)
+ 	yield();
+ 
+   if (timer == TIMEOUT)
+ 	printk("%s:%d\n", __FUNCTION__, __LINE__);
+ }
+ 
+ void cfide_wait_drq(void)
+ {
+   unsigned long timer;
+ 
+   for (timer = 0;
+ 	   timer < TIMEOUT && !(read_reg(reg_base, CB_STAT) & CB_STAT_DRQ); ++timer)
+ 	yield();
+ 
+   if (timer == TIMEOUT)
+ 	printk("%s:%d\n", __FUNCTION__, __LINE__);
+ }
+ 
+ static union {
+   struct hd_driveid id;
+   short sh[512/2];
+ } info_buf;
+ 
+ 
+ /* Read cnt sectors from the flash, starting at lba, storing the data
+    at dest */
+ static int cfide_read_sectors(uint8_t *dest, uint32_t lba, uint32_t cnt)
+ {
+   uint8_t sect, head, devHead, status, devCtrl;
+   uint16_t cyl;
+   uint32_t orig_lba = lba;
+   uint32_t i,j;
+   uint16_t data;
+ 
+   // printk("%s: dest %p lba %u cnt %u\n", __FUNCTION__, dest, lba, cnt);
+ 
+   if (lba + cnt > info_buf.id.lba_capacity) {
+ 	printk("%s: %u+%u is larger than %u\n", __FUNCTION__, lba, cnt, info_buf.id.lba_capacity);
+ 	return -EINVAL;
+   }
+ 
+   if (cnt > 255) {
+ 	printk("%s: cnt %u is too large\n", __FUNCTION__, cnt);
+ 	return -EINVAL;
+   }
+ 
+ 
+   /* translate from LBA */
+   sect = lba & 0xff;
+   lba >>= 8;
+   cyl = lba & 0xffff;
+   lba >>= 16;
+   head = (lba & 0x0f) | 0x40;
+ 
+   devCtrl = CB_DC_HD15 | CB_DC_NIEN;
+   devHead = driveno | head;
+ 
+   write_reg(reg_base, CB_DC, devCtrl);
+   write_reg(reg_base, CB_SC_SN, ((uint16_t)cnt & 0xff) | ((uint16_t)sect << 8));
+   write_reg(reg_base, CB_CYL, cyl);
+ 
+   write_reg(reg_base, CB_DH_CMD, devHead | (CMD_READ_SECTORS << 8));
+ 
+   for (j=0; j<cnt; ++j) {
+ 	udelay(1);  // spin for a moment to let the controller raise BSY
+ 
+ 	cfide_wait_fin();
+ 	cfide_wait_drq();
+ 	for (i=0; i<256; ++i) {
+ 	  data = read_reg(reg_base, CB_DATA);
+ #if 0
+ 	  *dest++ = data>>8;
+ 	  *dest++ = data;
+ #else
+ 	  *dest++ = data;
+ 	  *dest++ = data>>8;
+ #endif
+ 	}
+ 
+ 
+ 	cfide_wait_fin();
+ 
+ 	status = read_reg(reg_base, CB_STAT);
+ 	if (status & (CB_STAT_DF|CB_STAT_ERR)) {
+ 	  printk("%s: error at block %d status %#x\n", __FUNCTION__, orig_lba+j, status);
+ 	  break;
+ 	}
+ 
+   }
+ 
+   return 0;
+ }
+ 
+ /* Write cnt sectors to the flash, starting at lba, reading the data
+    from src */
+ static int cfide_write_sectors(uint8_t *src, uint32_t lba, uint32_t cnt)
+ {
+   uint8_t sect, head, devHead, status, devCtrl;
+   uint16_t cyl;
+   uint32_t orig_lba = lba;
+   uint32_t i,j;
+   uint16_t data;
+ 
+   if (lba + cnt > info_buf.id.lba_capacity) {
+ 	printk("%s: %u+%u is larger than %u\n", __FUNCTION__, lba, cnt, info_buf.id.lba_capacity);
+ 	return -EINVAL;
+   }
+ 
+   if (cnt > 255) {
+ 	printk("%s: cnt %u is too large\n", __FUNCTION__, cnt);
+ 	return -EINVAL;
+   }
+ 
+   /* translate from LBA */
+   sect = lba & 0xff;
+   lba >>= 8;
+   cyl = lba & 0xffff;
+   lba >>= 16;
+   head = (lba & 0x0f) | 0x40;
+ 
+   devCtrl = CB_DC_HD15 | CB_DC_NIEN;
+   devHead = driveno | head;
+ 
+   write_reg(reg_base, CB_DC, devCtrl);
+   write_reg(reg_base, CB_SC_SN, ((uint16_t)cnt & 0xff) | ((uint16_t)sect << 8));
+   write_reg(reg_base, CB_CYL, cyl);
+ 
+   write_reg(reg_base, CB_DH_CMD, devHead | (CMD_WRITE_SECTORS << 8));
+ 
+   for (j=0; j<cnt; ++j) {
+ 	udelay(1);  // spin for a moment to let the controller raise BSY
+ 
+ 	cfide_wait_fin();
+ 
+ 	cfide_wait_drq();
+ 
+ 	for (i=0; i<256; ++i) {
+ #if 0
+ 	  data = (*src++ << 8);
+ 	  data |= *src++;
+ #else
+ 	  data = *src++;
+ 	  data |= (*src++ << 8);
+ #endif
+ 	  write_reg(reg_base, CB_DATA, data);
+ 	}
+ 	
+ 	cfide_wait_fin();
+ 
+ 	status = read_reg(reg_base, CB_STAT);
+ 	if (status & (CB_STAT_DF|CB_STAT_ERR)) {
+ 	  printk("%s: error at block %d status %#x\n", __FUNCTION__, orig_lba+j, status);
+ 	  break;
+ 	}
+ 
+   }
+   return 0;
+ }
+ 
+ 
+ static void cfide_transfer(unsigned long sector,
+         unsigned long nsect, char *buffer, int write)
+ {
+   int ret;
+     if (write)
+ 	  ret = cfide_write_sectors(buffer, sector, nsect);
+ 	else
+ 	  ret = cfide_read_sectors(buffer, sector, nsect);
+ 	if (ret)
+ 	  printk("%s:%d\n", __FUNCTION__, __LINE__);
+ 	  
+ }
+ 
+ 
+ static void do_hd_request (request_queue_t * q)
+ {
+   struct request *req;
+ 
+   // printk("%s:%d q %p\n", __FUNCTION__, __LINE__, q);
+ 
+   while ((req = elv_next_request(q)) != NULL) {
+     if (blk_fs_request(req)) {
+ 	  cfide_transfer(req->sector, req->current_nr_sectors,
+ 					 req->buffer, rq_data_dir(req));
+ 	  end_request(req, 1);
+ 	} else {
+ 	  printk (KERN_NOTICE "Skip non-fs request\n");
+ 	  end_request(req, 0);
+ 	  continue;
+ 	}
+   }
+ }
+ 
+ static int hd_getgeo(struct block_device *bdev, struct hd_geometry *geo)
+ {
+ 	struct hd_i_struct *disk = bdev->bd_disk->private_data;
+ 
+ 	geo->heads = disk->head;
+ 	geo->sectors = disk->sect;
+ 	geo->cylinders = disk->cyl;
+ 	return 0;
+ }
+ 
+ 
+ static struct block_device_operations hd_fops = {
+ 	.getgeo =	hd_getgeo,
+ };
+ 
+ void cfide_fetch_info(void)
+ {
+   int i;
+ 
+   // printk("%s:%d\n", __FUNCTION__, __LINE__);
+ 
+   /* Select the drive and wait for it to finish */
+   driveno &= 1;
+   write_reg(reg_base, CB_DH_CMD, driveno);
+   cfide_wait_fin();
+ 
+   // printk("%s:%d\n", __FUNCTION__, __LINE__);
+ 
+   write_reg(reg_base, CB_DH_CMD, driveno | (CMD_IDENTIFY_DEVICE << 8));
+   cfide_wait_fin();
+ 
+   // printk("%s:%d\n", __FUNCTION__, __LINE__);
+ 
+ 
+   cfide_wait_drq();
+ 
+   // printk("%s:%d\n", __FUNCTION__, __LINE__);
+ 
+   for (i=0; i<512; i+=2)
+ 	info_buf.sh[i/2] = read_reg(reg_base, CB_DATA);
+ 
+   /* Fix lba_capcity */
+   info_buf.id.lba_capacity = (info_buf.id.lba_capacity>>16) | (info_buf.id.lba_capacity<<16);
+   // printk("%s:%d lba_capacity %#x\n", __FUNCTION__, __LINE__, info_buf.id.lba_capacity);
+ }
+ 
+ /*
+  * This is the hard disk IRQ description. The SA_INTERRUPT in sa_flags
+  * means we run the IRQ-handler with interrupts disabled:  this is bad for
+  * interrupt latency, but anything else has led to problems on some
+  * machines.
+  *
+  * We enable interrupts in some of the routines after making sure it's
+  * safe.
+  */
+ 
+ static int __init hd_init(void)
+ {
+ 	int drive;
+ 
+ 	printk("%s:%d MAJOR_NR %d\n", __FUNCTION__, __LINE__, MAJOR_NR);
+ 
+ 	if (register_blkdev(MAJOR_NR,"hd"))
+ 		return -1;
+ 
+ 	reg_base = (unsigned char *) ioremap_nocache(CPLD_ATA_REG_BASE, 0x1000);
+ 	if (!reg_base) {
+ 	  printk("%s:%d\n", __FUNCTION__, __LINE__);
+ 	  return -ENOMEM;
+ 	}
+ 
+ 	/* If no card present, return */
+ 	if (!cfide_card_present()) {
+ 	  printk("No CompactFlash card detected\n");
+ 	  iounmap(reg_base);
+ 	  return 0;
+ 	}
+ 
+ 	/* Fetchthe device info */
+ 	cfide_fetch_info();
+ 
+ 	hd_queue = blk_init_queue(do_hd_request, &hd_lock);
+ 	printk("%s:%d hd_queue %p\n", __FUNCTION__, __LINE__, hd_queue);
+ 	if (!hd_queue) {
+ 		unregister_blkdev(MAJOR_NR,"hd");
+ 		return -ENOMEM;
+ 	}
+ 
+ 
+ 	blk_queue_max_sectors(hd_queue, 255);
+ 	blk_queue_hardsect_size(hd_queue, 512);
+ 
+ #if 1
+ 	hd_info[0].cyl = info_buf.id.cyls;
+ 	hd_info[0].head = info_buf.id.heads;
+ 	hd_info[0].wpcom = 0;
+ 	hd_info[0].ctl = 0;
+ 	hd_info[0].lzone = 0;
+ 	hd_info[0].sect = info_buf.id.sectors;
+ 	NR_HD++;
+ 
+ 	printk("%s:%d NR_HD %d\n", __FUNCTION__, __LINE__, NR_HD);
+ 
+ #else
+ #ifdef __i386__
+ 	if (!NR_HD) {
+ 		extern struct drive_info drive_info;
+ 		unsigned char *BIOS = (unsigned char *) &drive_info;
+ 		unsigned long flags;
+ 		int cmos_disks;
+ 
+ 		for (drive=0 ; drive<2 ; drive++) {
+ 			hd_info[drive].cyl = *(unsigned short *) BIOS;
+ 			hd_info[drive].head = *(2+BIOS);
+ 			hd_info[drive].wpcom = *(unsigned short *) (5+BIOS);
+ 			hd_info[drive].ctl = *(8+BIOS);
+ 			hd_info[drive].lzone = *(unsigned short *) (12+BIOS);
+ 			hd_info[drive].sect = *(14+BIOS);
+ #ifdef does_not_work_for_everybody_with_scsi_but_helps_ibm_vp
+ 			if (hd_info[drive].cyl && NR_HD == drive)
+ 				NR_HD++;
+ #endif
+ 			BIOS += 16;
+ 		}
+ 
+ 	/*
+ 		We query CMOS about hard disks : it could be that 
+ 		we have a SCSI/ESDI/etc controller that is BIOS
+ 		compatible with ST-506, and thus showing up in our
+ 		BIOS table, but not register compatible, and therefore
+ 		not present in CMOS.
+ 
+ 		Furthermore, we will assume that our ST-506 drives
+ 		<if any> are the primary drives in the system, and 
+ 		the ones reflected as drive 1 or 2.
+ 
+ 		The first drive is stored in the high nibble of CMOS
+ 		byte 0x12, the second in the low nibble.  This will be
+ 		either a 4 bit drive type or 0xf indicating use byte 0x19 
+ 		for an 8 bit type, drive 1, 0x1a for drive 2 in CMOS.
+ 
+ 		Needless to say, a non-zero value means we have 
+ 		an AT controller hard disk for that drive.
+ 
+ 		Currently the rtc_lock is a bit academic since this
+ 		driver is non-modular, but someday... ?         Paul G.
+ 	*/
+ 
+ 		spin_lock_irqsave(&rtc_lock, flags);
+ 		cmos_disks = CMOS_READ(0x12);
+ 		spin_unlock_irqrestore(&rtc_lock, flags);
+ 
+ 		if (cmos_disks & 0xf0) {
+ 			if (cmos_disks & 0x0f)
+ 				NR_HD = 2;
+ 			else
+ 				NR_HD = 1;
+ 		}
+ 	}
+ #endif /* __i386__ */
+ #ifdef __arm__
+ 	if (!NR_HD) {
+ 		/* We don't know anything about the drive.  This means
+ 		 * that you *MUST* specify the drive parameters to the
+ 		 * kernel yourself.
+ 		 */
+ 		printk("hd: no drives specified - use hd=cyl,head,sectors"
+ 			" on kernel command line\n");
+ 	}
+ #endif
+ #endif
+ 	if (!NR_HD)
+ 		goto out;
+ 
+ 	for (drive=0 ; drive < NR_HD ; drive++) {
+ 		struct gendisk *disk = alloc_disk(64);
+ 		struct hd_i_struct *p = &hd_info[drive];
+ 		if (!disk)
+ 			goto Enomem;
+ 		disk->major = MAJOR_NR;
+ 		disk->first_minor = drive << 6;
+ 		disk->fops = &hd_fops;
+ 		sprintf(disk->disk_name, "hd%c", 'a'+drive);
+ 		disk->private_data = p;
+ 		set_capacity(disk, p->head * p->sect * p->cyl);
+ 		disk->queue = hd_queue;
+ 		p->unit = drive;
+ 		hd_gendisk[drive] = disk;
+ 		printk ("%s: %luMB, CHS=%d/%d/%d\n",
+ 			disk->disk_name, (unsigned long)get_capacity(disk)/2048,
+ 			p->cyl, p->head, p->sect);
+ 	}
+ 
+ 	/* Let them fly */
+ 	for(drive=0; drive < NR_HD; drive++)
+ 		add_disk(hd_gendisk[drive]);
+ 
+ 	return 0;
+ 
+ out:
+ 	unregister_blkdev(MAJOR_NR,"hd");
+ 	blk_cleanup_queue(hd_queue);
+ 	return -1;
+ Enomem:
+ 	while (drive--)
+ 		put_disk(hd_gendisk[drive]);
+ 	goto out;
+ }
+ 
+ static int __init parse_hd_setup (char *line) {
+ 	int ints[6];
+ 
+ 	(void) get_options(line, ARRAY_SIZE(ints), ints);
+ 	hd_setup(NULL, ints);
+ 
+ 	return 1;
+ }
+ __setup("hd=", parse_hd_setup);
+ 
+ module_init(hd_init);
+ 
+ /*
+  * Local variables:
+  *  c-indent-level: 4
+  *  tab-width: 4
+  * End:
+  */