?? wbsd.c
字號:
DBGF("Ending data transfer (%d bytes)\n", data->bytes_xfered); wbsd_request_end(host, host->mrq);}/* * MMC Callbacks */static void wbsd_request(struct mmc_host* mmc, struct mmc_request* mrq){ struct wbsd_host* host = mmc_priv(mmc); struct mmc_command* cmd; /* * Disable tasklets to avoid a deadlock. */ spin_lock_bh(&host->lock); BUG_ON(host->mrq != NULL); cmd = mrq->cmd; host->mrq = mrq; /* * If there is no card in the slot then * timeout immediatly. */ if (!(inb(host->base + WBSD_CSR) & WBSD_CARDPRESENT)) { cmd->error = MMC_ERR_TIMEOUT; goto done; } /* * Does the request include data? */ if (cmd->data) { wbsd_prepare_data(host, cmd->data); if (cmd->data->error != MMC_ERR_NONE) goto done; } wbsd_send_command(host, cmd); /* * If this is a data transfer the request * will be finished after the data has * transfered. */ if (cmd->data && (cmd->error == MMC_ERR_NONE)) { spin_unlock_bh(&host->lock); return; } done: wbsd_request_end(host, mrq); spin_unlock_bh(&host->lock);}static void wbsd_set_ios(struct mmc_host* mmc, struct mmc_ios* ios){ struct wbsd_host* host = mmc_priv(mmc); u8 clk, setup, pwr; DBGF("clock %uHz busmode %u powermode %u Vdd %u\n", ios->clock, ios->bus_mode, ios->power_mode, ios->vdd); spin_lock_bh(&host->lock); /* * Reset the chip on each power off. * Should clear out any weird states. */ if (ios->power_mode == MMC_POWER_OFF) wbsd_init_device(host); if (ios->clock >= 24000000) clk = WBSD_CLK_24M; else if (ios->clock >= 16000000) clk = WBSD_CLK_16M; else if (ios->clock >= 12000000) clk = WBSD_CLK_12M; else clk = WBSD_CLK_375K; /* * Only write to the clock register when * there is an actual change. */ if (clk != host->clk) { wbsd_write_index(host, WBSD_IDX_CLK, clk); host->clk = clk; } if (ios->power_mode != MMC_POWER_OFF) { /* * Power up card. */ pwr = inb(host->base + WBSD_CSR); pwr &= ~WBSD_POWER_N; outb(pwr, host->base + WBSD_CSR); /* * This behaviour is stolen from the * Windows driver. Don't know why, but * it is needed. */ setup = wbsd_read_index(host, WBSD_IDX_SETUP); if (ios->bus_mode == MMC_BUSMODE_OPENDRAIN) setup |= WBSD_DAT3_H; else setup &= ~WBSD_DAT3_H; wbsd_write_index(host, WBSD_IDX_SETUP, setup); mdelay(1); } spin_unlock_bh(&host->lock);}/* * Tasklets */inline static struct mmc_data* wbsd_get_data(struct wbsd_host* host){ WARN_ON(!host->mrq); if (!host->mrq) return NULL; WARN_ON(!host->mrq->cmd); if (!host->mrq->cmd) return NULL; WARN_ON(!host->mrq->cmd->data); if (!host->mrq->cmd->data) return NULL; return host->mrq->cmd->data;}static void wbsd_tasklet_card(unsigned long param){ struct wbsd_host* host = (struct wbsd_host*)param; u8 csr; spin_lock(&host->lock); csr = inb(host->base + WBSD_CSR); WARN_ON(csr == 0xff); if (csr & WBSD_CARDPRESENT) DBG("Card inserted\n"); else { DBG("Card removed\n"); if (host->mrq) { printk(KERN_ERR DRIVER_NAME ": Card removed during transfer!\n"); wbsd_reset(host); host->mrq->cmd->error = MMC_ERR_FAILED; tasklet_schedule(&host->finish_tasklet); } } /* * Unlock first since we might get a call back. */ spin_unlock(&host->lock); mmc_detect_change(host->mmc, 0);}static void wbsd_tasklet_fifo(unsigned long param){ struct wbsd_host* host = (struct wbsd_host*)param; struct mmc_data* data; spin_lock(&host->lock); if (!host->mrq) goto end; data = wbsd_get_data(host); if (!data) goto end; if (data->flags & MMC_DATA_WRITE) wbsd_fill_fifo(host); else wbsd_empty_fifo(host); /* * Done? */ if (host->size == data->bytes_xfered) { wbsd_write_index(host, WBSD_IDX_FIFOEN, 0); tasklet_schedule(&host->finish_tasklet); }end: spin_unlock(&host->lock);}static void wbsd_tasklet_crc(unsigned long param){ struct wbsd_host* host = (struct wbsd_host*)param; struct mmc_data* data; spin_lock(&host->lock); WARN_ON(!host->mrq); if (!host->mrq) goto end; data = wbsd_get_data(host); if (!data) goto end; DBGF("CRC error\n"); data->error = MMC_ERR_BADCRC; tasklet_schedule(&host->finish_tasklet);end: spin_unlock(&host->lock);}static void wbsd_tasklet_timeout(unsigned long param){ struct wbsd_host* host = (struct wbsd_host*)param; struct mmc_data* data; spin_lock(&host->lock); WARN_ON(!host->mrq); if (!host->mrq) goto end; data = wbsd_get_data(host); if (!data) goto end; DBGF("Timeout\n"); data->error = MMC_ERR_TIMEOUT; tasklet_schedule(&host->finish_tasklet);end: spin_unlock(&host->lock);}static void wbsd_tasklet_finish(unsigned long param){ struct wbsd_host* host = (struct wbsd_host*)param; struct mmc_data* data; spin_lock(&host->lock); WARN_ON(!host->mrq); if (!host->mrq) goto end; data = wbsd_get_data(host); if (!data) goto end; wbsd_finish_data(host, data); end: spin_unlock(&host->lock);}static void wbsd_tasklet_block(unsigned long param){ struct wbsd_host* host = (struct wbsd_host*)param; struct mmc_data* data; spin_lock(&host->lock); if ((wbsd_read_index(host, WBSD_IDX_CRCSTATUS) & WBSD_CRC_MASK) != WBSD_CRC_OK) { data = wbsd_get_data(host); if (!data) goto end; DBGF("CRC error\n"); data->error = MMC_ERR_BADCRC; tasklet_schedule(&host->finish_tasklet); }end: spin_unlock(&host->lock);}/* * Interrupt handling */static irqreturn_t wbsd_irq(int irq, void *dev_id, struct pt_regs *regs){ struct wbsd_host* host = dev_id; int isr; isr = inb(host->base + WBSD_ISR); /* * Was it actually our hardware that caused the interrupt? */ if (isr == 0xff || isr == 0x00) return IRQ_NONE; /* * Schedule tasklets as needed. */ if (isr & WBSD_INT_CARD) tasklet_schedule(&host->card_tasklet); if (isr & WBSD_INT_FIFO_THRE) tasklet_hi_schedule(&host->fifo_tasklet); if (isr & WBSD_INT_CRC) tasklet_hi_schedule(&host->crc_tasklet); if (isr & WBSD_INT_TIMEOUT) tasklet_hi_schedule(&host->timeout_tasklet); if (isr & WBSD_INT_BUSYEND) tasklet_hi_schedule(&host->block_tasklet); if (isr & WBSD_INT_TC) tasklet_schedule(&host->finish_tasklet); return IRQ_HANDLED;}/* * Support functions for probe */static int wbsd_scan(struct wbsd_host* host){ int i, j, k; int id; /* * Iterate through all ports, all codes to * find hardware that is in our known list. */ for (i = 0;i < sizeof(config_ports)/sizeof(int);i++) { if (!request_region(config_ports[i], 2, DRIVER_NAME)) continue; for (j = 0;j < sizeof(unlock_codes)/sizeof(int);j++) { id = 0xFFFF; outb(unlock_codes[j], config_ports[i]); outb(unlock_codes[j], config_ports[i]); outb(WBSD_CONF_ID_HI, config_ports[i]); id = inb(config_ports[i] + 1) << 8; outb(WBSD_CONF_ID_LO, config_ports[i]); id |= inb(config_ports[i] + 1); for (k = 0;k < sizeof(valid_ids)/sizeof(int);k++) { if (id == valid_ids[k]) { host->chip_id = id; host->config = config_ports[i]; host->unlock_code = unlock_codes[i]; return 0; } } if (id != 0xFFFF) { DBG("Unknown hardware (id %x) found at %x\n", id, config_ports[i]); } outb(LOCK_CODE, config_ports[i]); } release_region(config_ports[i], 2); } return -ENODEV;}static int wbsd_request_regions(struct wbsd_host* host){ if (io & 0x7) return -EINVAL; if (!request_region(io, 8, DRIVER_NAME)) return -EIO; host->base = io; return 0;}static void wbsd_release_regions(struct wbsd_host* host){ if (host->base) release_region(host->base, 8); if (host->config) release_region(host->config, 2);}static void wbsd_init_dma(struct wbsd_host* host){ host->dma = -1; if (dma < 0) return; if (request_dma(dma, DRIVER_NAME)) goto err; /* * We need to allocate a special buffer in * order for ISA to be able to DMA to it. */ host->dma_buffer = kmalloc(65536, GFP_NOIO | GFP_DMA | __GFP_REPEAT | __GFP_NOWARN); if (!host->dma_buffer) goto free; /* * Translate the address to a physical address. */ host->dma_addr = isa_virt_to_bus(host->dma_buffer); /* * ISA DMA must be aligned on a 64k basis. */ if ((host->dma_addr & 0xffff) != 0) goto kfree; /* * ISA cannot access memory above 16 MB. */ else if (host->dma_addr >= 0x1000000) goto kfree; host->dma = dma; return; kfree: /* * If we've gotten here then there is some kind of alignment bug */ BUG_ON(1); kfree(host->dma_buffer); host->dma_buffer = NULL;free: free_dma(dma);err: printk(KERN_WARNING DRIVER_NAME ": Unable to allocate DMA %d. " "Falling back on FIFO.\n", dma);}static struct mmc_host_ops wbsd_ops = { .request = wbsd_request, .set_ios = wbsd_set_ios,};/* * Device probe */static int wbsd_probe(struct device* dev){ struct wbsd_host* host = NULL; struct mmc_host* mmc = NULL; int ret; /* * Allocate MMC structure. */ mmc = mmc_alloc_host(sizeof(struct wbsd_host), dev); if (!mmc) return -ENOMEM; host = mmc_priv(mmc); host->mmc = mmc; /* * Scan for hardware. */ ret = wbsd_scan(host); if (ret) goto freemmc; /* * Reset the chip. */ wbsd_write_config(host, WBSD_CONF_SWRST, 1); wbsd_write_config(host, WBSD_CONF_SWRST, 0); /* * Allocate I/O ports. */ ret = wbsd_request_regions(host); if (ret) goto release; /* * Set host parameters. */ mmc->ops = &wbsd_ops; mmc->f_min = 375000; mmc->f_max = 24000000; mmc->ocr_avail = MMC_VDD_32_33|MMC_VDD_33_34; spin_lock_init(&host->lock); /* * Select SD/MMC function. */ wbsd_write_config(host, WBSD_CONF_DEVICE, DEVICE_SD); /* * Set up card detection. */ wbsd_write_config(host, WBSD_CONF_PINS, 0x02); /* * Configure I/O port. */ wbsd_write_config(host, WBSD_CONF_PORT_HI, host->base >> 8); wbsd_write_config(host, WBSD_CONF_PORT_LO, host->base & 0xff); /* * Allocate interrupt. */ ret = request_irq(irq, wbsd_irq, SA_SHIRQ, DRIVER_NAME, host); if (ret) goto release; host->irq = irq; /* * Set up tasklets. */ tasklet_init(&host->card_tasklet, wbsd_tasklet_card, (unsigned long)host); tasklet_init(&host->fifo_tasklet, wbsd_tasklet_fifo, (unsigned long)host); tasklet_init(&host->crc_tasklet, wbsd_tasklet_crc, (unsigned long)host); tasklet_init(&host->timeout_tasklet, wbsd_tasklet_timeout, (unsigned long)host); tasklet_init(&host->finish_tasklet, wbsd_tasklet_finish, (unsigned long)host); tasklet_init(&host->block_tasklet, wbsd_tasklet_block, (unsigned long)host); /* * Configure interrupt. */ wbsd_write_config(host, WBSD_CONF_IRQ, host->irq); /* * Allocate DMA. */ wbsd_init_dma(host); /* * If all went well, then configure DMA. */ if (host->dma >= 0) wbsd_write_config(host, WBSD_CONF_DRQ, host->dma); /* * Maximum number of segments. Worst case is one sector per segment * so this will be 64kB/512. */ mmc->max_hw_segs = NR_SG; mmc->max_phys_segs = NR_SG; /* * Maximum number of sectors in one transfer. Also limited by 64kB * buffer. */ mmc->max_sectors = 128; /* * Maximum segment size. Could be one segment with the maximum number * of segments. */ mmc->max_seg_size = mmc->max_sectors * 512; /* * Enable chip. */ wbsd_write_config(host, WBSD_CONF_ENABLE, 1); /* * Power up chip. */ wbsd_write_config(host, WBSD_CONF_POWER, 0x20); /* * Power Management stuff. No idea how this works. * Not tested. */#ifdef CONFIG_PM wbsd_write_config(host, WBSD_CONF_PME, 0xA0);#endif /* * Reset the chip into a known state. */ wbsd_init_device(host); dev_set_drvdata(dev, mmc); /* * Add host to MMC layer. */ mmc_add_host(mmc); printk(KERN_INFO "%s: W83L51xD id %x at 0x%x irq %d dma %d\n", mmc_hostname(mmc), (int)host->chip_id, (int)host->base, (int)host->irq, (int)host->dma); return 0;release: wbsd_release_regions(host);freemmc: mmc_free_host(mmc); return ret;}/* * Device remove */static int wbsd_remove(struct device* dev){ struct mmc_host* mmc = dev_get_drvdata(dev); struct wbsd_host* host; if (!mmc) return 0; host = mmc_priv(mmc); /* * Unregister host with MMC layer. */ mmc_remove_host(mmc); /* * Power down the SD/MMC function. */ wbsd_unlock_config(host); wbsd_write_config(host, WBSD_CONF_DEVICE, DEVICE_SD); wbsd_write_config(host, WBSD_CONF_ENABLE, 0); wbsd_lock_config(host); /* * Free resources. */ if (host->dma_buffer) kfree(host->dma_buffer); if (host->dma >= 0) free_dma(host->dma); free_irq(host->irq, host); tasklet_kill(&host->card_tasklet); tasklet_kill(&host->fifo_tasklet); tasklet_kill(&host->crc_tasklet); tasklet_kill(&host->timeout_tasklet); tasklet_kill(&host->finish_tasklet); tasklet_kill(&host->block_tasklet); wbsd_release_regions(host); mmc_free_host(mmc); return 0;}/* * Power management */#ifdef CONFIG_PMstatic int wbsd_suspend(struct device *dev, u32 state, u32 level){ DBGF("Not yet supported\n"); return 0;}static int wbsd_resume(struct device *dev, u32 level){ DBGF("Not yet supported\n"); return 0;}#else#define wbsd_suspend NULL#define wbsd_resume NULL#endifstatic void wbsd_release(struct device *dev){}static struct platform_device wbsd_device = { .name = DRIVER_NAME, .id = -1, .dev = { .release = wbsd_release, },};static struct device_driver wbsd_driver = { .name = DRIVER_NAME, .bus = &platform_bus_type, .probe = wbsd_probe, .remove = wbsd_remove, .suspend = wbsd_suspend, .resume = wbsd_resume,};/* * Module loading/unloading */static int __init wbsd_drv_init(void){ int result; printk(KERN_INFO DRIVER_NAME ": Winbond W83L51xD SD/MMC card interface driver, " DRIVER_VERSION "\n"); printk(KERN_INFO DRIVER_NAME ": Copyright(c) Pierre Ossman\n"); result = driver_register(&wbsd_driver); if (result < 0) return result; result = platform_device_register(&wbsd_device); if (result < 0) return result; return 0;}static void __exit wbsd_drv_exit(void){ platform_device_unregister(&wbsd_device); driver_unregister(&wbsd_driver); DBG("unloaded\n");}module_init(wbsd_drv_init);module_exit(wbsd_drv_exit);module_param(io, uint, 0444);module_param(irq, uint, 0444);module_param(dma, int, 0444);MODULE_LICENSE("GPL");MODULE_DESCRIPTION("Winbond W83L51xD SD/MMC card interface driver");MODULE_PARM_DESC(io, "I/O base to allocate. Must be 8 byte aligned. (default 0x248)");MODULE_PARM_DESC(irq, "IRQ to allocate. (default 6)");MODULE_PARM_DESC(dma, "DMA channel to allocate. -1 for no DMA. (default 2)");
?? 快捷鍵說明
復(fù)制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -