diff options
author | florian <florian@3c298f89-4303-0410-b956-a3cf2f4a3e73> | 2007-10-22 20:56:34 +0000 |
---|---|---|
committer | florian <florian@3c298f89-4303-0410-b956-a3cf2f4a3e73> | 2007-10-22 20:56:34 +0000 |
commit | 663af573eb897f7df39e85a652cd4f7cfb834f29 (patch) | |
tree | 27d471d824bb7099552b1889328cd863ed88efbb /target/linux/adm5120/files/drivers/block | |
parent | 01535e65a277f46aa658637a6a9a79f8212b237f (diff) |
Add preliminary support for the Routerboard 153 CF slot (#2550)
git-svn-id: svn://svn.openwrt.org/openwrt/trunk@9403 3c298f89-4303-0410-b956-a3cf2f4a3e73
Diffstat (limited to 'target/linux/adm5120/files/drivers/block')
-rw-r--r-- | target/linux/adm5120/files/drivers/block/rb1xx/Makefile | 2 | ||||
-rw-r--r-- | target/linux/adm5120/files/drivers/block/rb1xx/ata.c | 507 | ||||
-rw-r--r-- | target/linux/adm5120/files/drivers/block/rb1xx/ata.h | 143 | ||||
-rw-r--r-- | target/linux/adm5120/files/drivers/block/rb1xx/bdev.c | 338 |
4 files changed, 990 insertions, 0 deletions
diff --git a/target/linux/adm5120/files/drivers/block/rb1xx/Makefile b/target/linux/adm5120/files/drivers/block/rb1xx/Makefile new file mode 100644 index 000000000..672aaa697 --- /dev/null +++ b/target/linux/adm5120/files/drivers/block/rb1xx/Makefile @@ -0,0 +1,2 @@ +## Makefile for the RB1xx CF port +obj-y += bdev.o ata.o diff --git a/target/linux/adm5120/files/drivers/block/rb1xx/ata.c b/target/linux/adm5120/files/drivers/block/rb1xx/ata.c new file mode 100644 index 000000000..c73d21958 --- /dev/null +++ b/target/linux/adm5120/files/drivers/block/rb1xx/ata.c @@ -0,0 +1,507 @@ +/* CF-mips driver + This is a block driver for the direct (mmaped) interface to the CF-slot, + found in Routerboard.com's RB532 board + See SDK provided from routerboard.com. + + Module adapted By P.Christeas <p_christeas@yahoo.com>, 2005-6. + Cleaned up and adapted to platform_device by Felix Fietkau <nbd@openwrt.org> + + This work is redistributed under the terms of the GNU General Public License. +*/ + +#include <linux/kernel.h> /* printk() */ +#include <linux/module.h> /* module to be loadable */ +#include <linux/delay.h> +#include <linux/sched.h> +#include <linux/pci.h> +#include <linux/ioport.h> /* request_mem_region() */ + +#include <asm/unaligned.h> /* ioremap() */ +#include <asm/io.h> /* ioremap() */ + +#include <gpio.h> +#include <adm5120_defs.h> +#include <adm5120_irq.h> +#include <adm5120_switch.h> +#include <adm5120_intc.h> +#include <adm5120_mpmc.h> +#include <adm5120_cf.h> + +#include "ata.h" + +#define REQUEST_MEM_REGION 0 +#define DEBUG 1 + +#if DEBUG +#define DEBUGP printk +#else +#define DEBUGP(format, args...) +#endif + +#define SECS 1000000 /* unit for wait_not_busy() is 1us */ + +unsigned cf_head = 0; +unsigned cf_cyl = 0; +unsigned cf_spt = 0; +unsigned cf_sectors = 0; +static unsigned cf_block_size = 1; + +#define DBUF32 ((volatile u32 *)((unsigned long)dev->baddr | ATA_DBUF_OFFSET)) + +#define INTC_WRITE(reg, val) __raw_writel((val), \ + (void __iomem *)(KSEG1ADDR(ADM5120_INTC_BASE) + reg)) + +#define INTC_READ(reg) __raw_readl(\ + (void __iomem *)(KSEG1ADDR(ADM5120_INTC_BASE) + reg)) + +static void cf_do_tasklet(unsigned long dev_l); + + +static inline void wareg(u8 val, unsigned reg, struct cf_mips_dev* dev) +{ + writeb(val, dev->baddr + ATA_REG_OFFSET + reg); +} + +static inline u8 rareg(unsigned reg, struct cf_mips_dev* dev) +{ + return readb(dev->baddr + ATA_REG_OFFSET + reg); +} + +static inline int cfrdy(struct cf_mips_dev *dev) +{ + return (SW_READ_REG(GPIO_CONF0) & (1 << ADM5120_GPIO_PIN4)); +} + +static inline void prepare_cf_irq(struct cf_mips_dev *dev) +{ + /* interrupt on cf ready (not busy) */ + INTC_WRITE(INTC_REG_INT_LEVEL, INTC_READ(INTC_REG_INT_LEVEL) | ADM5120_CF_IRQ_LEVEL_BIT); + + /* FIXME: how to clear interrupt status? */ +} + +static inline int cf_present(struct cf_mips_dev* dev) +{ + /* TODO: read and configure CIS into memory mapped mode + * TODO: parse CISTPL_CONFIG on CF+ cards to get base address (0x200) + * TODO: maybe adjust power saving setting for Hitachi Microdrive + */ + + /* FIXME: enabling of EXTIO will be done by BIOS in future */ + unsigned cmd = EXTIO_CS0_INT0_EN; + int i; + + /* on RB100 WAIT is LOW all the time => read will hang */ + if (SW_READ_REG(GPIO_CONF0) & (1 << ADM5120_GPIO_PIN0)) + cmd |= EXTIO_WAIT_EN; + + SW_WRITE_REG(GPIO_CONF2, cmd); + SW_WRITE_REG(GPIO_CONF0, (SW_READ_REG(GPIO_CONF0) & ~(1 << (16 + ADM5120_CF_GPIO_NUM)))); + + /* FIXME: timings will be set by BIOS in future - remove this */ + MPMC_WRITE_REG(SC2, 0x88); + MPMC_WRITE_REG(WEN2, 0x02); + MPMC_WRITE_REG(OEN2, 0x03); + MPMC_WRITE_REG(RD2, 0x1a); + MPMC_WRITE_REG(PG2, 0x1d); + MPMC_WRITE_REG(WR2, 0x14); + MPMC_WRITE_REG(TN2, 0x09); + + for (i = 0; i < 0x10; ++i) { + if (rareg(i,dev) != 0xff) + return 1; + } + return 0; +} + +static inline int is_busy(struct cf_mips_dev *dev) +{ + return !cfrdy(dev); +} + +static int wait_not_busy(int to_us, int wait_for_busy,struct cf_mips_dev *dev) +{ + int us_passed = 0; + if (wait_for_busy && !is_busy(dev)) { + /* busy must appear within 400ns, + * but it may dissapear before we see it + * => must not wait for busy in a loop + */ + ndelay(400); + } + + do { + if (us_passed) + udelay(1); /* never reached in async mode */ + if (!is_busy(dev)) { + if (us_passed > 1 * SECS) { + printk(KERN_WARNING "cf-mips: not busy ok (after %dus)" + ", status 0x%02x\n", us_passed, (unsigned) rareg(ATA_REG_ST,dev)); + } + return CF_TRANS_OK; + } + if (us_passed == 1 * SECS) { + printk(KERN_WARNING "cf-mips: wait not busy %dus..\n", to_us); + } + if (dev->async_mode) { + dev->to_timer.expires = jiffies + (to_us * HZ / SECS); + dev->irq_enable_time = jiffies; + prepare_cf_irq(dev); + if (is_busy(dev)) { + add_timer(&dev->to_timer); + enable_irq(dev->irq); + return CF_TRANS_IN_PROGRESS; + } + continue; + } + ++us_passed; + } while (us_passed < to_us); + + printk(KERN_ERR "cf-mips: wait not busy timeout (%dus)" + ", status 0x%02x, state %d\n", + to_us, (unsigned) rareg(ATA_REG_ST,dev), dev->tstate); + return CF_TRANS_FAILED; +} + +static irqreturn_t cf_irq_handler(int irq, void *dev_id) +{ + /* While tasklet has not disabled irq, irq will be retried all the time + * because of ILEVEL matching GPIO pin status => deadlock. + * To avoid this, we change ILEVEL to 0. + */ + + struct cf_mips_dev *dev=dev_id; + + if (!cfrdy(dev)) + return; // false interrupt (only for ADM5120) + + INTC_WRITE(INTC_REG_INT_LEVEL, (INTC_READ(INTC_REG_INT_LEVEL) & (~ADM5120_CF_IRQ_LEVEL_BIT))); + + del_timer(&dev->to_timer); + tasklet_schedule(&dev->tasklet); + return IRQ_HANDLED; +} + +static int do_reset(struct cf_mips_dev *dev) +{ + printk(KERN_INFO "cf-mips: resetting..\n"); + + wareg(ATA_REG_DC_SRST, ATA_REG_DC,dev); + udelay(1); /* FIXME: how long should we wait here? */ + wareg(0, ATA_REG_DC,dev); + + return wait_not_busy(30 * SECS, 1,dev); +} + +static int set_multiple(struct cf_mips_dev *dev) +{ + if (dev->block_size <= 1) + return CF_TRANS_OK; + + wareg(dev->block_size, ATA_REG_SC,dev); + wareg(ATA_REG_DH_BASE | ATA_REG_DH_LBA, ATA_REG_DH,dev); + wareg(ATA_CMD_SET_MULTIPLE, ATA_REG_CMD,dev); + + return wait_not_busy(10 * SECS, 1,dev); +} + +static int set_cmd(struct cf_mips_dev *dev) +{ + //DEBUGP(KERN_INFO "cf-mips: ata cmd 0x%02x\n", dev->tcmd); + // sector_count should be <=24 bits.. + BUG_ON(dev->tsect_start>=0x10000000); + // This way, it addresses 2^24 * 512 = 128G + + if (dev->tsector_count) { + wareg(dev->tsector_count & 0xff, ATA_REG_SC,dev); + wareg(dev->tsect_start & 0xff, ATA_REG_SN,dev); + wareg((dev->tsect_start >> 8) & 0xff, ATA_REG_CL,dev); + wareg((dev->tsect_start >> 16) & 0xff, ATA_REG_CH,dev); + } + wareg(((dev->tsect_start >> 24) & 0x0f) | ATA_REG_DH_BASE | ATA_REG_DH_LBA, + ATA_REG_DH,dev); /* select drive on all commands */ + wareg(dev->tcmd, ATA_REG_CMD,dev); + return wait_not_busy(10 * SECS, 1,dev); +} + +static int do_trans(struct cf_mips_dev *dev) +{ + int res; + unsigned st; + int transfered; + + //printk("do_trans: %d sectors left\n",dev->tsectors_left); + while (dev->tsectors_left) { + transfered = 0; + + st = rareg(ATA_REG_ST,dev); + if (!(st & ATA_REG_ST_DRQ)) { + printk(KERN_ERR "cf-mips: do_trans without DRQ (status 0x%x)!\n", st); + if (st & ATA_REG_ST_ERR) { + int errId = rareg(ATA_REG_ERR,dev); + printk(KERN_ERR "cf-mips: %s error, status 0x%x, errid 0x%x\n", + (dev->tread ? "read" : "write"), st, errId); + } + return CF_TRANS_FAILED; + } + do { /* Fill/read the buffer one block */ + u32 *qbuf, *qend; + qbuf = (u32 *)dev->tbuf; + qend = qbuf + CF_SECT_SIZE / sizeof(u32); + if (dev->tread) { + while (qbuf!=qend) + put_unaligned(*DBUF32,qbuf++); + //*(qbuf++) = *DBUF32; + } + else { + while(qbuf!=qend) + *DBUF32 = get_unaligned(qbuf++); + } + + dev->tsectors_left--; + dev->tbuf += CF_SECT_SIZE; + dev->tbuf_size -= CF_SECT_SIZE; + transfered++; + } while (transfered != dev->block_size && dev->tsectors_left > 0); + + res = wait_not_busy(10 * SECS, 1,dev); + if (res != CF_TRANS_OK) + return res; + }; + + st = rareg(ATA_REG_ST,dev); + if (st & (ATA_REG_ST_DRQ | ATA_REG_ST_DWF | ATA_REG_ST_ERR)) { + if (st & ATA_REG_ST_DRQ) { + printk(KERN_ERR "cf-mips: DRQ after all %d sectors are %s" + ", status 0x%x\n", dev->tsector_count, (dev->tread ? "read" : "written"), st); + } else if (st & ATA_REG_ST_DWF) { + printk(KERN_ERR "cf-mips: write fault, status 0x%x\n", st); + } else { + int errId = rareg(ATA_REG_ERR,dev); + printk(KERN_ERR "cf-mips: %s error, status 0x%x, errid 0x%x\n", + (dev->tread ? "read" : "write"), st, errId); + } + return CF_TRANS_FAILED; + } + return CF_TRANS_OK; +} + +static int cf_do_state(struct cf_mips_dev *dev) +{ + int res; + switch (dev->tstate) { /* fall through everywhere */ + case TS_IDLE: + dev->tstate = TS_READY; + if (is_busy(dev)) { + dev->tstate = TS_AFTER_RESET; + res = do_reset(dev); + if (res != CF_TRANS_OK) + break; + } + case TS_AFTER_RESET: + if (dev->tstate == TS_AFTER_RESET) { + dev->tstate = TS_READY; + res = set_multiple(dev); + if (res != CF_TRANS_OK) + break; + } + case TS_READY: + dev->tstate = TS_CMD; + res = set_cmd(dev); + if (res != CF_TRANS_OK) + break;; + case TS_CMD: + dev->tstate = TS_TRANS; + case TS_TRANS: + res = do_trans(dev); + break; + default: + printk(KERN_ERR "cf-mips: BUG: unknown tstate %d\n", dev->tstate); + return CF_TRANS_FAILED; + } + if (res != CF_TRANS_IN_PROGRESS) + dev->tstate = TS_IDLE; + return res; +} + +static void cf_do_tasklet(unsigned long dev_l) +{ + struct cf_mips_dev* dev=(struct cf_mips_dev*) dev_l; + int res; + + disable_irq(dev->irq); + + if (dev->tstate == TS_IDLE) + return; /* can happen when irq is first registered */ + +#if 0 + DEBUGP(KERN_WARNING "cf-mips: not busy ok (tasklet) status 0x%02x\n", + (unsigned) rareg(ATA_REG_ST,dev)); +#endif + + res = cf_do_state(dev); + if (res == CF_TRANS_IN_PROGRESS) + return; + cf_async_trans_done(dev,res); +} + +static void cf_async_timeout(unsigned long dev_l) +{ + struct cf_mips_dev* dev=(struct cf_mips_dev*) dev_l; + disable_irq(dev->irq); + /* Perhaps send abort to the device? */ + printk(KERN_ERR "cf-mips: wait not busy timeout (%lus)" + ", status 0x%02x, state %d\n", + jiffies - dev->irq_enable_time, (unsigned) rareg(ATA_REG_ST,dev), dev->tstate); + dev->tstate = TS_IDLE; + cf_async_trans_done(dev,CF_TRANS_FAILED); +} + +int cf_do_transfer(struct cf_mips_dev* dev,sector_t sector, unsigned long nsect, + char* buffer, int is_write) +{ + BUG_ON(dev->tstate!=TS_IDLE); + if (nsect > ATA_MAX_SECT_PER_CMD) { + printk(KERN_WARNING "cf-mips: sector count %lu out of range\n",nsect); + return CF_TRANS_FAILED; + } + if (sector + nsect > dev->sectors) { + printk(KERN_WARNING "cf-mips: sector %lu out of range\n",sector); + return CF_TRANS_FAILED; + } + dev->tbuf = buffer; + dev->tbuf_size = nsect*512; + dev->tsect_start = sector; + dev->tsector_count = nsect; + dev->tsectors_left = dev->tsector_count; + dev->tread = (is_write)?0:1; + + dev->tcmd = (dev->block_size == 1 ? + (is_write ? ATA_CMD_WRITE_SECTORS : ATA_CMD_READ_SECTORS) : + (is_write ? ATA_CMD_WRITE_MULTIPLE : ATA_CMD_READ_MULTIPLE)); + + return cf_do_state(dev); +} + +static int do_identify(struct cf_mips_dev *dev) +{ + u16 sbuf[CF_SECT_SIZE >> 1]; + int res; + char tstr[17]; //serial + BUG_ON(dev->tstate!=TS_IDLE); + dev->tbuf = (char *) sbuf; + dev->tbuf_size = CF_SECT_SIZE; + dev->tsect_start = 0; + dev->tsector_count = 0; + dev->tsectors_left = 1; + dev->tread = 1; + dev->tcmd = ATA_CMD_IDENTIFY_DRIVE; + + DEBUGP(KERN_INFO "cf-mips: identify drive..\n"); + res = cf_do_state(dev); + if (res == CF_TRANS_IN_PROGRESS) { + printk(KERN_ERR "cf-mips: BUG: async identify cmd\n"); + return CF_TRANS_FAILED; + } + if (res != CF_TRANS_OK) + return 0; + + dev->head = sbuf[3]; + dev->cyl = sbuf[1]; + dev->spt = sbuf[6]; + dev->sectors = ((unsigned long) sbuf[7] << 16) | sbuf[8]; + dev->dtype=sbuf[0]; + memcpy(tstr,&sbuf[12],16); + tstr[16]=0; + printk(KERN_INFO "cf-mips: %s detected, C/H/S=%d/%d/%d sectors=%u (%uMB) Serial=%s\n", + (sbuf[0] == 0x848A ? "CF card" : "ATA drive"), dev->cyl, dev->head, + dev->spt, dev->sectors, dev->sectors >> 11,tstr); + return 1; +} + +static void init_multiple(struct cf_mips_dev * dev) +{ + int res; + DEBUGP(KERN_INFO "cf-mips: detecting block size\n"); + + dev->block_size = 128; /* max block size = 128 sectors (64KB) */ + do { + wareg(dev->block_size, ATA_REG_SC,dev); + wareg(ATA_REG_DH_BASE | ATA_REG_DH_LBA, ATA_REG_DH,dev); + wareg(ATA_CMD_SET_MULTIPLE, ATA_REG_CMD,dev); + + res = wait_not_busy(10 * SECS, 1,dev); + if (res != CF_TRANS_OK) { + printk(KERN_ERR "cf-mips: failed to detect block size: busy!\n"); + dev->block_size = 1; + return; + } + if ((rareg(ATA_REG_ST,dev) & ATA_REG_ST_ERR) == 0) + break; + dev->block_size /= 2; + } while (dev->block_size > 1); + + printk(KERN_INFO "cf-mips: multiple sectors = %d\n", dev->block_size); +} + +int cf_init(struct cf_mips_dev *dev) +{ + tasklet_init(&dev->tasklet,cf_do_tasklet,(unsigned long)dev); + dev->baddr = ioremap_nocache((unsigned long)dev->base, CFDEV_BUF_SIZE); + if (!dev->baddr) { + printk(KERN_ERR "cf-mips: cf_init: ioremap for (%lx,%x) failed\n", + (unsigned long) dev->base, CFDEV_BUF_SIZE); + return -EBUSY; + } + + if (!cf_present(dev)) { + printk(KERN_WARNING "cf-mips: cf card not present\n"); + iounmap(dev->baddr); + return -ENODEV; + } + + if (do_reset(dev) != CF_TRANS_OK) { + printk(KERN_ERR "cf-mips: cf reset failed\n"); + iounmap(dev->baddr); + return -EBUSY; + } + + if (!do_identify(dev)) { + printk(KERN_ERR "cf-mips: cf identify failed\n"); + iounmap(dev->baddr); + return -EBUSY; + } + +/* set_apm_level(ATA_APM_WITH_STANDBY); */ + init_multiple(dev); + + init_timer(&dev->to_timer); + dev->to_timer.function = cf_async_timeout; + dev->to_timer.data = (unsigned long)dev; + + prepare_cf_irq(dev); + if (request_irq(dev->irq, cf_irq_handler, 0, "CF Mips", dev)) { + printk(KERN_ERR "cf-mips: failed to get irq\n"); + iounmap(dev->baddr); + return -EBUSY; + } + /* Disable below would be odd, because request will enable, and the tasklet + will disable it itself */ + //disable_irq(dev->irq); + + dev->async_mode = 1; + + return 0; +} + +void cf_cleanup(struct cf_mips_dev *dev) +{ + iounmap(dev->baddr); + free_irq(dev->irq, NULL); +#if REQUEST_MEM_REGION + release_mem_region((unsigned long)dev->base, CFDEV_BUF_SIZE); +#endif +} + + +/*eof*/ diff --git a/target/linux/adm5120/files/drivers/block/rb1xx/ata.h b/target/linux/adm5120/files/drivers/block/rb1xx/ata.h new file mode 100644 index 000000000..15e882630 --- /dev/null +++ b/target/linux/adm5120/files/drivers/block/rb1xx/ata.h @@ -0,0 +1,143 @@ +/* CF-mips driver + This is a block driver for the direct (mmaped) interface to the CF-slot, + found in Routerboard.com's RB532 board + See SDK provided from routerboard.com. + + Module adapted By P.Christeas <p_christeas@yahoo.com>, 2005-6. + Cleaned up and adapted to platform_device by Felix Fietkau <nbd@openwrt.org> + + This work is redistributed under the terms of the GNU General Public License. +*/ + +#ifndef __CFMIPS_ATA_H__ +#define __CFMIPS_ATA_H__ + +#include <linux/interrupt.h> + +#define CFG_DC_DEV1 (void*)0xb8010010 +#define CFG_DC_DEVBASE 0x0 +#define CFG_DC_DEVMASK 0x4 +#define CFG_DC_DEVC 0x8 +#define CFG_DC_DEVTC 0xC + +#define CFDEV_BUF_SIZE 0x1000 +#define ATA_CIS_OFFSET 0x200 +#define ATA_REG_OFFSET 0x800 +#define ATA_DBUF_OFFSET 0xC00 + +#define ATA_REG_FEAT 0x1 +#define ATA_REG_SC 0x2 +#define ATA_REG_SN 0x3 +#define ATA_REG_CL 0x4 +#define ATA_REG_CH 0x5 +#define ATA_REG_DH 0x6 +#define ATA_REG_DH_BASE 0xa0 +#define ATA_REG_DH_LBA 0x40 +#define ATA_REG_DH_DRV 0x10 +#define ATA_REG_CMD 0x7 +#define ATA_REG_ST 0x7 +#define ATA_REG_ST_BUSY 0x80 +#define ATA_REG_ST_RDY 0x40 +#define ATA_REG_ST_DWF 0x20 +#define ATA_REG_ST_DSC 0x10 +#define ATA_REG_ST_DRQ 0x08 +#define ATA_REG_ST_CORR 0x04 +#define ATA_REG_ST_ERR 0x01 +#define ATA_REG_ERR 0xd +#define ATA_REG_DC 0xe +#define ATA_REG_DC_IEN 0x02 +#define ATA_REG_DC_SRST 0x04 + +#define ATA_CMD_READ_SECTORS 0x20 +#define ATA_CMD_WRITE_SECTORS 0x30 +#define ATA_CMD_EXEC_DRIVE_DIAG 0x90 +#define ATA_CMD_READ_MULTIPLE 0xC4 +#define ATA_CMD_WRITE_MULTIPLE 0xC5 +#define ATA_CMD_SET_MULTIPLE 0xC6 +#define ATA_CMD_IDENTIFY_DRIVE 0xEC +#define ATA_CMD_SET_FEATURES 0xEF + +#define ATA_FEATURE_ENABLE_APM 0x05 +#define ATA_FEATURE_DISABLE_APM 0x85 +#define ATA_APM_DISABLED 0x00 +#define ATA_APM_MIN_POWER 0x01 +#define ATA_APM_WITH_STANDBY 0x7f +#define ATA_APM_WITHOUT_STANDBY 0x80 +#define ATA_APM_MAX_PERFORMANCE 0xfe + +#define CF_SECT_SIZE 0x200 +/* That is the ratio CF_SECT_SIZE/512 (the kernel sector size) */ +#define CF_KERNEL_MUL 1 +#define ATA_MAX_SECT_PER_CMD 0x100 + +#define CF_TRANS_FAILED 0 +#define CF_TRANS_OK 1 +#define CF_TRANS_IN_PROGRESS 2 + + +enum trans_state { + TS_IDLE = 0, + TS_AFTER_RESET, + TS_READY, + TS_CMD, + TS_TRANS +}; + +// +// #if DEBUG +// static unsigned long busy_time; +// #endif + +/** Struct to hold the cfdev +Actually, all the data here only has one instance. However, for +reasons of programming conformity, it is passed around as a pointer +*/ +struct cf_mips_dev { + void *base; /* base address for I/O */ + void *baddr; /* remapped address */ + + int pin; /* gpio pin */ + int irq; /* gpio irq */ + + unsigned head; + unsigned cyl; + unsigned spt; + unsigned sectors; + + unsigned short block_size; + unsigned dtype ; // ATA or CF + struct request_queue *queue; + struct gendisk *gd; + + /* Transaction state */ + enum trans_state tstate; + char *tbuf; + unsigned long tbuf_size; + sector_t tsect_start; + unsigned tsector_count; + unsigned tsectors_left; + int tread; + unsigned tcmd; + int async_mode; + unsigned long irq_enable_time; + + struct request *active_req; /* A request is being carried out. Is that different from tstate? */ + int users; + struct timer_list to_timer; + struct tasklet_struct tasklet; + + /** This lock ensures that the requests to this device are all done + atomically. Transfers can run in parallel, requests are all queued + one-by-one */ + spinlock_t lock; +}; + +int cf_do_transfer(struct cf_mips_dev* dev,sector_t sector, unsigned long nsect, + char* buffer, int is_write); +int cf_init(struct cf_mips_dev* dev); +void cf_cleanup(struct cf_mips_dev* dev); + +void cf_async_trans_done(struct cf_mips_dev* dev, int result); +// void *cf_get_next_buf(unsigned long *buf_size); + +#endif diff --git a/target/linux/adm5120/files/drivers/block/rb1xx/bdev.c b/target/linux/adm5120/files/drivers/block/rb1xx/bdev.c new file mode 100644 index 000000000..12c938505 --- /dev/null +++ b/target/linux/adm5120/files/drivers/block/rb1xx/bdev.c @@ -0,0 +1,338 @@ +/* CF-mips driver + This is a block driver for the direct (mmaped) interface to the CF-slot, + found in Routerboard.com's RB532 board + See SDK provided from routerboard.com. + + Module adapted By P.Christeas <p_christeas@yahoo.com>, 2005-6. + Cleaned up and adapted to platform_device by Felix Fietkau <nbd@openwrt.org> + + This work is redistributed under the terms of the GNU General Public License. +*/ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/time.h> +#include <linux/wait.h> +#include <linux/fs.h> +#include <linux/genhd.h> +#include <linux/blkdev.h> +#include <linux/blkpg.h> +#include <linux/hdreg.h> +#include <linux/platform_device.h> + +#include <asm/uaccess.h> +#include <asm/io.h> +#include <asm/mach-adm5120/adm5120_cf.h> + +#ifdef DEBUG +#define DEBUGP printk +#define DLEVEL 1 +#else +#define DEBUGP(format, args...) +#define DLEVEL 0 +#endif + +#define CF_MIPS_MAJOR 13 +#define MAJOR_NR CF_MIPS_MAJOR +#define CF_MAX_PART 16 /* max 15 partitions */ + +#include "ata.h" + +//extern struct block_device_operations cf_bdops; + +// static struct hd_struct cf_parts[CF_MAX_PART]; +// static int cf_part_sizes[CF_MAX_PART]; +// static int cf_hsect_sizes[CF_MAX_PART]; +// static int cf_max_sectors[CF_MAX_PART]; +// static int cf_blksize_sizes[CF_MAX_PART]; + +// static spinlock_t lock = SPIN_LOCK_UNLOCKED; + +// volatile int cf_busy = 0; + +static struct request *active_req = NULL; + +static int cf_open (struct inode *, struct file *); +static int cf_release (struct inode *, struct file *); +static int cf_ioctl (struct inode *, struct file *, unsigned, unsigned long); + +static void cf_request(request_queue_t * q); +static int cf_transfer(const struct request *req); + +/*long (*unlocked_ioctl) (struct file *, unsigned, unsigned long); +long (*compat_ioctl) (struct file *, unsigned, unsigned long);*/ +// int (*direct_access) (struct block_device *, sector_t, unsigned long *); +// int (*media_changed) (struct gendisk *); +// int (*revalidate_disk) (struct gendisk *); + +static struct block_device_operations cf_bdops = { + .owner = THIS_MODULE, + .open = cf_open, + .release = cf_release, + .ioctl = cf_ioctl, + .media_changed = NULL, + .unlocked_ioctl = NULL, + .revalidate_disk = NULL, + .compat_ioctl = NULL, + .direct_access = NULL +}; + + +int cf_mips_probe(struct platform_device *pdev) +{ + struct gendisk* cf_gendisk=NULL; + struct cf_device *cdev = (struct cf_device *) pdev->dev.platform_data; + struct cf_mips_dev *dev; + struct resource *r; + int reg_result; + + reg_result = register_blkdev(MAJOR_NR, "cf-mips"); + if (reg_result < 0) { + printk(KERN_WARNING "cf-mips: can't get major %d\n", MAJOR_NR); + return reg_result; + } + + dev = (struct cf_mips_dev *)kmalloc(sizeof(struct cf_mips_dev),GFP_KERNEL); + if (!dev) + goto out_err; + memset(dev, 0, sizeof(struct cf_mips_dev)); + cdev->dev = dev; + + dev->pin = cdev->gpio_pin; + dev->irq = platform_get_irq_byname(pdev, "cf_irq"); + r = platform_get_resource_byname(pdev, IORESOURCE_MEM, "cf_membase"); + dev->base = (void *) r->start; + + if (cf_init(dev)) goto out_err; + printk("init done"); + + spin_lock_init(&dev->lock); + dev->queue = blk_init_queue(cf_request,&dev->lock); + if (!dev->queue){ + printk(KERN_ERR "cf-mips: no mem for queue\n"); + goto out_err; + } + blk_queue_max_sectors(dev->queue,ATA_MAX_SECT_PER_CMD); + + /* For memory devices, it is always better to avoid crossing segments + inside the same request. */ +/* if (dev->dtype==0x848A){ + printk(KERN_INFO "Setting boundary for cf to 0x%x",(dev->block_size*512)-1); + blk_queue_segment_boundary(dev->queue, (dev->block_size*512)-1); + }*/ + + dev->gd = alloc_disk(CF_MAX_PART); + cf_gendisk = dev->gd; + cdev->gd = dev->gd; + if (!cf_gendisk) goto out_err; /* Last of these goto's */ + + cf_gendisk->major = MAJOR_NR; + cf_gendisk->first_minor = 0; + cf_gendisk->queue=dev->queue; + BUG_ON(cf_gendisk->minors != CF_MAX_PART); + strcpy(cf_gendisk->disk_name,"cfa"); + cf_gendisk->fops = &cf_bdops; + cf_gendisk->flags = 0 ; /* is not yet GENHD_FL_REMOVABLE */ + cf_gendisk->private_data=dev; + + set_capacity(cf_gendisk,dev->sectors * CF_KERNEL_MUL); + + /* Let the disk go live */ + add_disk(cf_gendisk); +#if 0 + result = cf_init(); + + /* default cfg for all partitions */ + memset(cf_parts, 0, sizeof (cf_parts[0]) * CF_MAX_PART); + memset(cf_part_sizes, 0, sizeof (cf_part_sizes[0]) * CF_MAX_PART); + for (i = 0; i < CF_MAX_PART; ++i) { + cf_hsect_sizes[i] = CF_SECT_SIZE; + cf_max_sectors[i] = ATA_MAX_SECT_PER_CMD; + cf_blksize_sizes[i] = BLOCK_SIZE; + } + + /* setup info for whole disk (partition 0) */ + cf_part_sizes[0] = cf_sectors / 2; + cf_parts[0].nr_sects = cf_sectors; + + blk_size[MAJOR_NR] = cf_part_sizes; + blksize_size[MAJOR_NR] = cf_blksize_sizes; + max_sectors[MAJOR_NR] = cf_max_sectors; + hardsect_size[MAJOR_NR] = cf_hsect_sizes; + read_ahead[MAJOR_NR] = 8; /* (4kB) */ + + blk_init_queue(BLK_DEFAULT_QUEUE(MAJOR_NR), DEVICE_REQUEST); + + add_gendisk(&cf_gendisk); +#endif +// printk(KERN_INFO "cf-mips partition check: \n"); +// register_disk(cf_gendisk, MKDEV(MAJOR_NR, 0), CF_MAX_PART, +// &cf_bdops, dev->sectors); + return 0; + +out_err: + if (dev->queue){ + blk_cleanup_queue(dev->queue); + } + if (reg_result) { + unregister_blkdev(MAJOR_NR, "cf-mips"); + return reg_result; + } + if (dev){ + cf_cleanup(dev); + kfree(dev); + } + return 1; +} + +static int +cf_mips_remove(struct platform_device *pdev) +{ + struct cf_device *cdev = (struct cf_device *) pdev->dev.platform_data; + struct cf_mips_dev *dev = (struct cf_mips_dev *) cdev->dev; + + unregister_blkdev(MAJOR_NR, "cf-mips"); + blk_cleanup_queue(dev->queue); + + del_gendisk(dev->gd); + cf_cleanup(dev); + return 0; +} + + +static struct platform_driver cf_driver = { + .driver.name = "rb153-cf", + .probe = cf_mips_probe, + .remove = cf_mips_remove, +}; + +static int __init cf_mips_init(void) +{ + printk(KERN_INFO "cf-mips module loaded\n"); + return platform_driver_register(&cf_driver); +} + +static void cf_mips_cleanup(void) +{ + platform_driver_unregister(&cf_driver); + printk(KERN_INFO "cf-mips module removed\n"); +} + +module_init(cf_mips_init); +module_exit(cf_mips_cleanup); + +MODULE_LICENSE("GPL"); +MODULE_ALIAS_BLOCKDEV_MAJOR(CF_MIPS_MAJOR); + + +static int cf_open(struct inode *inode, struct file *filp) +{ + struct cf_mips_dev *dev=inode->i_bdev->bd_disk->private_data; + int minor = MINOR(inode->i_rdev); + + if (minor >= CF_MAX_PART) + return -ENODEV; + //DEBUGP(KERN_INFO "cf-mips module opened, minor %d\n", minor); + spin_lock(&dev->lock); + dev->users++; + spin_unlock(&dev->lock); + filp->private_data=dev; + + /* dirty workaround to set CFRDY GPIO as an input when some other + program sets it as an output */ + //gpio_set(CFG, (1 << dev->pin), 0); + return 0; /* success */ +} + +static int cf_release(struct inode *inode, struct file *filp) +{ + int minor = MINOR(inode->i_rdev); + struct cf_mips_dev *dev=inode->i_bdev->bd_disk->private_data; + spin_lock(&dev->lock); + dev->users--; + spin_unlock(&dev->lock); + return 0; +} + +static int cf_ioctl(struct inode *inode, struct file *filp, + unsigned int cmd, unsigned long arg) +{ + unsigned minor = MINOR(inode->i_rdev); + struct cf_mips_dev *dev=inode->i_bdev->bd_disk->private_data; + + DEBUGP(KERN_INFO "cf_ioctl cmd %u\n", cmd); + switch (cmd) { + case BLKRRPART: /* re-read partition table */ + if (!capable(CAP_SYS_ADMIN)) + return -EACCES; + printk(KERN_INFO "cf-mips partition check: \n"); + register_disk(dev->gd); + return 0; + + case HDIO_GETGEO: + { + struct hd_geometry geo; + geo.cylinders = dev->cyl; + geo.heads = dev->head; + geo.sectors = dev->spt; + geo.start = (*dev->gd->part)[minor].start_sect; + if (copy_to_user((void *) arg, &geo, sizeof (geo))) + return -EFAULT; + } + return 0; + } + + return -EINVAL; /* unknown command */ +} + +static void cf_request(request_queue_t * q) +{ + struct cf_mips_dev* dev; + + struct request * req; + int status; + + /* We could have q->queuedata = dev , but haven't yet. */ + if (active_req) + return; + + while ((req=elv_next_request(q))!=NULL){ + dev=req->rq_disk->private_data; + status=cf_transfer(req); + if (status==CF_TRANS_IN_PROGRESS){ + active_req=req; + return; + } + end_request(req,status); + } +} + +static int cf_transfer(const struct request *req) +{ + struct cf_mips_dev* dev=req->rq_disk->private_data; + + if (!blk_fs_request(req)){ + if (printk_ratelimit()) + printk(KERN_WARNING "cf-mips: skipping non-fs request 0x%x\n",req->cmd[0]); + return CF_TRANS_FAILED; + } + + return cf_do_transfer(dev,req->sector,req->current_nr_sectors,req->buffer,rq_data_dir(req)); +} + +void cf_async_trans_done(struct cf_mips_dev * dev,int result) +{ + struct request *req; + + spin_lock(&dev->lock); + req=active_req; + active_req=NULL; + end_request(req,result); + spin_unlock(&dev->lock); + + spin_lock(&dev->lock); + cf_request(dev->queue); + spin_unlock(&dev->lock); +} + |