parent
5db86fb47a
commit
2a0ca50ce2
@ -0,0 +1,2 @@ |
||||
## Makefile for the RB1xx CF port
|
||||
obj-y += bdev.o ata.o
|
@ -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*/ |
@ -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 |
@ -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); |
||||
} |
||||
|
@ -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; |
||||
}; |
@ -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
|
Loading…
Reference in new issue