summaryrefslogtreecommitdiff
path: root/packages/linux/logicpd-pxa270-2.6.19.2/logicpd-pxa270-cf-hack.patch
diff options
context:
space:
mode:
Diffstat (limited to 'packages/linux/logicpd-pxa270-2.6.19.2/logicpd-pxa270-cf-hack.patch')
-rw-r--r--packages/linux/logicpd-pxa270-2.6.19.2/logicpd-pxa270-cf-hack.patch727
1 files changed, 727 insertions, 0 deletions
diff --git a/packages/linux/logicpd-pxa270-2.6.19.2/logicpd-pxa270-cf-hack.patch b/packages/linux/logicpd-pxa270-2.6.19.2/logicpd-pxa270-cf-hack.patch
new file mode 100644
index 0000000000..ec1256261b
--- /dev/null
+++ b/packages/linux/logicpd-pxa270-2.6.19.2/logicpd-pxa270-cf-hack.patch
@@ -0,0 +1,727 @@
+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:
++ */