/* 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 , 2005-6. Cleaned up and adapted to platform_device by Felix Fietkau This work is redistributed under the terms of the GNU General Public License. */ #include /* printk() */ #include /* module to be loadable */ #include #include #include #include /* request_mem_region() */ #include /* ioremap() */ #include /* ioremap() */ #include #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; static void *baddr = 0; #define DBUF32 ((volatile u32 *)((unsigned long)dev->baddr | ATA_DBUF_OFFSET)) 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 get_gpio_bit(gpio_func ofs, struct cf_mips_dev *dev) { return (gpio_get(ofs) >> dev->pin) & 1; } static inline void set_gpio_bit(int bit, gpio_func ofs, struct cf_mips_dev *dev) { gpio_set(ofs, (1 << dev->pin), ((bit & 1) << dev->pin)); } static inline int cfrdy(struct cf_mips_dev *dev) { return get_gpio_bit(DATA, dev); } static inline void prepare_cf_irq(struct cf_mips_dev *dev) { set_gpio_bit(1, ILEVEL, dev); /* interrupt on cf ready (not busy) */ set_gpio_bit(0, ISTAT, dev); /* 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 */ int i; /* setup CFRDY GPIO as input */ set_gpio_bit(0, FUNC, dev); set_gpio_bit(0, CFG, dev); 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; set_gpio_bit(0, ILEVEL, dev); set_gpio_bit(0, ISTAT, dev); 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*/