diff options
Diffstat (limited to 'target/linux/adm5120/files/drivers/block/rb1xx/ata.c')
-rw-r--r-- | target/linux/adm5120/files/drivers/block/rb1xx/ata.c | 507 |
1 files changed, 507 insertions, 0 deletions
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*/ |