aboutsummaryrefslogtreecommitdiffstats
path: root/target/linux/adm5120
diff options
context:
space:
mode:
authorFlorian Fainelli <florian@openwrt.org>2007-10-22 20:56:34 +0000
committerFlorian Fainelli <florian@openwrt.org>2007-10-22 20:56:34 +0000
commit2a0ca50ce2fbc3fc5b0eab9e48c08b46f0b7aa54 (patch)
treeb78e86a8da696710637c45773eea65e3649a9cbe /target/linux/adm5120
parent5db86fb47a934a9afbe6d960a67ac1821be6dd74 (diff)
downloadupstream-2a0ca50ce2fbc3fc5b0eab9e48c08b46f0b7aa54.tar.gz
upstream-2a0ca50ce2fbc3fc5b0eab9e48c08b46f0b7aa54.tar.bz2
upstream-2a0ca50ce2fbc3fc5b0eab9e48c08b46f0b7aa54.zip
Add preliminary support for the Routerboard 153 CF slot (#2550)
SVN-Revision: 9403
Diffstat (limited to 'target/linux/adm5120')
-rw-r--r--target/linux/adm5120/files/arch/mips/adm5120/boards/mikrotik.c34
-rw-r--r--target/linux/adm5120/files/drivers/block/rb1xx/Makefile2
-rw-r--r--target/linux/adm5120/files/drivers/block/rb1xx/ata.c507
-rw-r--r--target/linux/adm5120/files/drivers/block/rb1xx/ata.h143
-rw-r--r--target/linux/adm5120/files/drivers/block/rb1xx/bdev.c338
-rw-r--r--target/linux/adm5120/files/include/asm-mips/mach-adm5120/adm5120_cf.h19
-rw-r--r--target/linux/adm5120/files/include/asm-mips/mach-adm5120/adm5120_mpmc.h6
-rw-r--r--target/linux/adm5120/patches-2.6.23/120-cf.patch31
-rw-r--r--target/linux/adm5120/router_le/config-2.6.231
9 files changed, 1079 insertions, 2 deletions
diff --git a/target/linux/adm5120/files/arch/mips/adm5120/boards/mikrotik.c b/target/linux/adm5120/files/arch/mips/adm5120/boards/mikrotik.c
index 64c32c6230..74dd860dc3 100644
--- a/target/linux/adm5120/files/arch/mips/adm5120/boards/mikrotik.c
+++ b/target/linux/adm5120/files/arch/mips/adm5120/boards/mikrotik.c
@@ -42,6 +42,7 @@
#include <adm5120_nand.h>
#include <adm5120_board.h>
#include <adm5120_platform.h>
+#include <adm5120_cf.h>
#define RB1XX_NAND_CHIP_DELAY 25
@@ -178,6 +179,35 @@ static void rb150_nand_cmd_ctrl(struct mtd_info *mtd, int cmd,
}
/*--------------------------------------------------------------------------*/
+static struct resource cf_slot0_res[] = {
+ {
+ .name = "cf_membase",
+ .flags = IORESOURCE_MEM
+ }, {
+ .name = "cf_irq",
+ .start = INTC_IRQ_GPIO4, /* 5 */
+ .end = INTC_IRQ_GPIO4,
+ .flags = IORESOURCE_IRQ
+ }
+};
+
+static struct cf_device cf_slot0_data = {
+ .gpio_pin = 4
+};
+
+static struct platform_device cf_slot0 = {
+ .id = 0,
+ .name = "rb153-cf",
+ .dev.platform_data = &cf_slot0_data,
+ .resource = cf_slot0_res,
+ .num_resources = ARRAY_SIZE(cf_slot0_res),
+};
+
+static struct platform_device *rb153_devices[] __initdata = {
+ &adm5120_flash0_device,
+ &adm5120_nand_device,
+ &cf_slot0,
+};
static void __init rb1xx_mac_setup(void)
{
@@ -299,8 +329,8 @@ static struct adm5120_board rb153_board __initdata = {
.board_setup = rb1xx_setup,
.eth_num_ports = 5,
.eth_vlans = rb15x_vlans,
- .num_devices = ARRAY_SIZE(rb1xx_devices),
- .devices = rb1xx_devices,
+ .num_devices = ARRAY_SIZE(rb153_devices),
+ .devices = rb153_devices,
.pci_nr_irqs = ARRAY_SIZE(rb1xx_pci_irqs),
.pci_irq_map = rb1xx_pci_irqs,
};
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 0000000000..672aaa6974
--- /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 0000000000..c73d21958a
--- /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 0000000000..15e8826308
--- /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 0000000000..12c938505b
--- /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);
+}
+
diff --git a/target/linux/adm5120/files/include/asm-mips/mach-adm5120/adm5120_cf.h b/target/linux/adm5120/files/include/asm-mips/mach-adm5120/adm5120_cf.h
new file mode 100644
index 0000000000..e26e1d4199
--- /dev/null
+++ b/target/linux/adm5120/files/include/asm-mips/mach-adm5120/adm5120_cf.h
@@ -0,0 +1,19 @@
+#include <linux/types.h>
+#include <linux/io.h>
+
+#include <adm5120_defs.h>
+#include <adm5120_switch.h>
+
+/* CFRDY is connected to GPIO4/INTX_1 */
+#define ADM5120_CF_GPIO_NUM 4
+#define ADM5120_CF_IRQ_LEVEL_BIT 0x20 /* GPIO4 = 0x20, GPIO2 = 0x10 */
+#define ADM5120_IRQ_CFRDY 5
+#define EXTIO_WAIT_EN (0x1 << 6)
+#define EXTIO_CS1_INT1_EN (0x1 << 5)
+#define EXTIO_CS0_INT0_EN (0x1 << 4)
+
+struct cf_device {
+ int gpio_pin;
+ void *dev;
+ struct gendisk *gd;
+};
diff --git a/target/linux/adm5120/files/include/asm-mips/mach-adm5120/adm5120_mpmc.h b/target/linux/adm5120/files/include/asm-mips/mach-adm5120/adm5120_mpmc.h
index 406d6c563a..8209d88a47 100644
--- a/target/linux/adm5120/files/include/asm-mips/mach-adm5120/adm5120_mpmc.h
+++ b/target/linux/adm5120/files/include/asm-mips/mach-adm5120/adm5120_mpmc.h
@@ -49,6 +49,12 @@
#define MPMC_REG_SC0 0x0200 /* for F_CS1_N */
#define MPMC_REG_SC1 0x0220 /* for F_CS0_N */
#define MPMC_REG_SC2 0x0240
+#define MPMC_REG_WEN2 0x0244
+#define MPMC_REG_OEN2 0x0248
+#define MPMC_REG_RD2 0x024C
+#define MPMC_REG_PG2 0x0250
+#define MPMC_REG_WR2 0x0254
+#define MPMC_REG_TN2 0x0258
#define MPMC_REG_SC3 0x0260
/* Control register bits */
diff --git a/target/linux/adm5120/patches-2.6.23/120-cf.patch b/target/linux/adm5120/patches-2.6.23/120-cf.patch
new file mode 100644
index 0000000000..cbfe42d6c3
--- /dev/null
+++ b/target/linux/adm5120/patches-2.6.23/120-cf.patch
@@ -0,0 +1,31 @@
+Index: linux-2.6.23/drivers/block/Kconfig
+===================================================================
+--- linux-2.6.23/drivers/block/Kconfig 2007-10-09 22:31:38.000000000 +0200
++++ linux-2.6.23.new/drivers/block/Kconfig 2007-10-22 18:07:49.000000000 +0200
+@@ -65,6 +65,14 @@
+ To compile this driver as a module, choose M here: the
+ module will be called z2ram.
+
++config BLK_DEV_CF_MIPS
++ bool "CF slot of RB153 board"
++ depends on MIPS_ADM5120
++ default y
++ help
++ The Routerboard 153 has a CF slot on it. Enable the special block
++ device driver for it.
++
+ config BLK_DEV_XD
+ tristate "XT hard disk support"
+ depends on ISA && ISA_DMA_API
+Index: linux-2.6.23/drivers/block/Makefile
+===================================================================
+--- linux-2.6.23/drivers/block/Makefile 2007-10-09 22:31:38.000000000 +0200
++++ linux-2.6.23.new/drivers/block/Makefile.new 2007-10-22 18:08:15.000000000 +0200
+@@ -7,6 +7,7 @@
+
+ obj-$(CONFIG_MAC_FLOPPY) += swim3.o
+ obj-$(CONFIG_BLK_DEV_FD) += floppy.o
++obj-$(CONFIG_BLK_DEV_CF_MIPS) += rb1xx/
+ obj-$(CONFIG_AMIGA_FLOPPY) += amiflop.o
+ obj-$(CONFIG_PS3_DISK) += ps3disk.o
+ obj-$(CONFIG_ATARI_FLOPPY) += ataflop.o
diff --git a/target/linux/adm5120/router_le/config-2.6.23 b/target/linux/adm5120/router_le/config-2.6.23
index da96c7306f..4267f70a79 100644
--- a/target/linux/adm5120/router_le/config-2.6.23
+++ b/target/linux/adm5120/router_le/config-2.6.23
@@ -10,6 +10,7 @@ CONFIG_BAYCOM_SER_FDX=m
CONFIG_BAYCOM_SER_HDX=m
CONFIG_BINFMT_MISC=m
CONFIG_BITREVERSE=y
+CONFIG_BLK_DEV_CF_MIPS=y
CONFIG_CIFS_DEBUG2=y
CONFIG_CIFS_EXPERIMENTAL=y
CONFIG_CIFS_STATS2=y