/* -*- linux-c -*- * Copyright (c) 2004-2006 Winbond Electronics Corp. All rights reserved. * * The contents of this file are subject to the Open * Software License version 1.1 that can be found at * http://www.opensource.org/licenses/osl-1.1.txt and is included herein * by reference. * * Alternatively, the contents of this file may be used under the terms * of the GNU General Public License version 2 (the "GPL") as distributed * in the kernel source COPYING file, in which case the provisions of * the GPL are applicable instead of the above. If you wish to allow * the use of your version of this file only under the terms of the * GPL and not to allow others to use your version of this file under * the OSL, indicate your decision by deleting the provisions above and * replace them with the notice and other provisions required by the GPL. * If you do not delete the provisions above, a recipient may use your * version of this file under either the OSL or the GPL. * Maintained by: Weiming Shuai Jin Yuan Mu Dezheng Shen $Author: sjin $ $Id: wbmsreader528.c,v 1.21.2.6 2006/06/22 06:06:12 sjin Exp $ --- don't modify lines above --- Description: Enviornments: (1) RedHat 9 on 32-bit Intel UP/32-bit Intel dual-core (2) Fedora Core 4 (2.6.11) on 32-bit Intel/32-bit Intel dual-core/64-bit AMD/64-bit Intel dual-core (3) RedHat Enterprise Workstatin Rel 4 UP2 (2.6.9) on 32-bit Intel 32-bit Intel dual-core/64-bit AMD/64-bit Intel dual-core */ #include "wbmsreader528.h" static struct pci_device_id wbms528_ids[] = { {W528MS_VENDOR_ID, W528MS_DEVICE_ID, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, {0,}, }; MODULE_DEVICE_TABLE(pci, wbms528_ids); static wb528ms_reader_t g_reader_info; #define WB_DUMP_REG #ifdef WB_DUMP_REG #define debug_reg() do{dump_reg(); \ printk(" -- file:"__FILE__" line:%d\n", __LINE__);}while(0) #else #define debug_reg() #endif #ifdef WB_DUMP_REG static void dump_reg(void) { WB_PRINTK("<<528reg: MSCR1:%X MSCR2:%X ICR:%X MSSR:%X PCI_ICR:%X>>", wb528ms_inb(WIO_MSCR1), wb528ms_inb(WIO_MSCR2), wb528ms_inb(WIO_ICR), wb528ms_inb(WIO_MSSR), wb528ms_pci_inl(WPCI_ICR)); } #endif static unsigned char inline wb528ms_inb(unsigned offset) { return readb((void *)(g_reader_info.mem_addr1 + offset)); } static unsigned int inline wb528ms_pci_inl(unsigned offset) { return readl((void *)(g_reader_info.mem_addr0 + offset)); } static void inline wb528ms_pci_outl(volatile unsigned int value, unsigned offset) { writel(value, (void *)(g_reader_info.mem_addr0 + offset)); } static unsigned int inline wb528ms_inl(unsigned offset) { return readl((void *)(g_reader_info.mem_addr1 + offset)); } static void inline wb528ms_outl(volatile unsigned int value, unsigned offset) { writel(value, (void *)(g_reader_info.mem_addr1 + offset)); } static unsigned short inline wb528ms_inw(unsigned offset) { return readw((void *)(g_reader_info.mem_addr1 + offset)); } static void inline wb528ms_outw(volatile unsigned short value, unsigned offset) { writew(value, (void *)g_reader_info.mem_addr1 + offset); } static void inline wb528ms_outb(volatile unsigned char value, unsigned offset) { writeb(value, (void *)(g_reader_info.mem_addr1 + offset)); } static inline void wb528ms_power_down(void) { wb528ms_outb(wb528ms_inb(WIO_MSCR2) | WMSCR2_PWRCTRL, WIO_MSCR2); } static inline void wb528ms_power_up(void) { wb528ms_outb(wb528ms_inb(WIO_MSCR2) & (~WMSCR2_PWRCTRL), WIO_MSCR2); } static inline void wb528ms_fifo_reset(void) { wb528ms_outb(wb528ms_inb(WIO_MSCR2) | WMSCR2_FIFORST, WIO_MSCR2); wb528ms_outb(wb528ms_inb(WIO_MSCR2) & ~WMSCR2_FIFORST, WIO_MSCR2); } static int wb528ms_card_exist(void) { if (wb528ms_inb(WIO_MSSR) & WMSSR_INS) { g_reader_info.card_exist = FALSE; g_reader_info.medium_changed = TRUE; wb528ms_power_down(); } else { g_reader_info.card_exist = TRUE; wb528ms_power_up(); } return g_reader_info.card_exist; } static void inline wb528ms_set_interface(unsigned int mode) { if (mode == WIF_SERIAL) wb528ms_outb(wb528ms_inb(WIO_MSCR2) & 0xBF, WIO_MSCR2); else wb528ms_outb(wb528ms_inb(WIO_MSCR2) | WMSCR2_PF, WIO_MSCR2); } static int wb528ms_init_card(void) { wb528ms_software_reset(); wb528ms_set_interface(WIF_SERIAL); wb528ms_inb(WIO_ISR1); wb528ms_inb(WIO_ISR2); wb528ms_fifo_reset(); wb528ms_outb(0x71, WIO_ICR); wb528ms_pci_outl(0x01, WPCI_ICR); return 0; } static int wb528ms_software_reset(void) { wb528ms_outb(wb528ms_inb(WIO_MSCR2) | WMSCR2_SOFTRST, WIO_MSCR2); while (wb528ms_inb(WIO_MSCR2) & WMSCR2_SOFTRST) ; wb528ms_inb(WIO_ISR1); wb528ms_inb(WIO_ISR2); wb528ms_card_exist(); return 0; } static void inline __init wb528ms_init_data(void) { memset(&g_reader_info, 0, sizeof(wb528ms_reader_t)); g_reader_info.medium_changed = TRUE; g_reader_info.card_type = WDEV_MS; g_reader_info.helper_thread_wait_queue = &(g_reader_info.space_helper_thread_wait_queue); init_waitqueue_head(g_reader_info.helper_thread_wait_queue); g_reader_info.dma = 1; } static irqreturn_t wb528ms_do_intr_handler(int irq, void *dev_id, struct pt_regs *regs) { unsigned char isr1, isr2; unsigned char dev_isr; if (0 == (wb528ms_pci_inl(WPCI_ISR) & WPCI_ISR_STATUS)) goto lab_end; wb528ms_pci_outl(0x00, WPCI_ICR); dev_isr = wb528ms_inb(WIO_DEVICE_ISR); if (0 == (dev_isr & (WDEVICE_ISR_MS | WDEVICE_ISR_DMA | WDEVICE_ISR_MSDMA_ERROR))) goto lab_enable_interrupt; if (dev_isr & WDEVICE_ISR_MS) { isr1 = wb528ms_inb(WIO_ISR1); isr2 = wb528ms_inb(WIO_ISR2); if (isr1 & WISR1_INS) { wb528ms_card_exist(); } if (isr2 & WISR2_BS0) { if (g_reader_info.sys_status & WSYS_STATUS_INTR_BS0) { g_reader_info.sys_status &= ~WSYS_STATUS_INTR_BS0; if (0 == (g_reader_info. sys_status & (WSYS_STATUS_INTR_BS0 | WSYS_STATUS_INTR_DMA))) wake_up_interruptible (g_reader_info. helper_thread_wait_queue); } } if (isr2 & WISR2_RDYTO) { g_reader_info.sys_status &= ~WSYS_STATUS_INTR_RDY_TOE; } if (isr2 & WISR2_CRC) { g_reader_info.sys_status &= ~WSYS_STATUS_INTR_CRC; } } if (dev_isr & WDEVICE_ISR_DMA) { if ((wb528ms_inb(WIO_DMA_INT_SRC) & (2 * g_reader_info.dma))) { if (wb528ms_inl(0x20 * (g_reader_info.dma + 1)) & 0x800) { if (g_reader_info. sys_status & WSYS_STATUS_INTR_DMA) { g_reader_info.sys_status &= ~WSYS_STATUS_INTR_DMA; if (0 == (g_reader_info. sys_status & (WSYS_STATUS_INTR_BS0 | WSYS_STATUS_INTR_DMA))) wake_up_interruptible (g_reader_info. helper_thread_wait_queue); } } } wb528ms_outb(0, 0x20 * (g_reader_info.dma + 1)); } if (dev_isr & WDEVICE_ISR_MSDMA_ERROR) { wb528ms_outb(0, 0x20 * (g_reader_info.dma + 1)); wake_up_interruptible(g_reader_info.helper_thread_wait_queue); } lab_enable_interrupt: wb528ms_pci_outl(0x01, WPCI_ICR); lab_end: return IRQ_HANDLED; } static void *wbreader_detect(void) { void *ret; struct pci_dev *pdev; ret = NULL; wb528ms_init_data(); pdev = NULL; pdev = pci_find_device(W528MS_VENDOR_ID, W528MS_DEVICE_ID, pdev); if (NULL == pdev) { printk("can not found 528\n"); goto lab_end; } if (pci_enable_device(pdev)) { WB_PRINTK_ERROR("can't enable 528 device\n"); goto lab_end; } g_reader_info.pci_dev = pdev; g_reader_info.irq = pdev->irq; g_reader_info.mem_addr0 = pci_resource_start(pdev, 0); g_reader_info.mem_flag0 = pci_resource_flags(pdev, 0); g_reader_info.mem_addr1 = pci_resource_start(pdev, 1); g_reader_info.mem_flag1 = pci_resource_flags(pdev, 1); g_reader_info.mem_addr0 = (unsigned long)ioremap(g_reader_info.mem_addr0, 0x0fff); g_reader_info.mem_addr1 = (unsigned long)ioremap(g_reader_info.mem_addr1, 0x0fff); if (request_irq (pdev->irq, wb528ms_do_intr_handler, SA_INTERRUPT | SA_SHIRQ, "wb528ms", &g_reader_info)) { ret = 0; goto lab_end; } wb528ms_software_reset(); wb528ms_pci_outl(0x01, WPCI_ICR); //enable insert interrupt wb528ms_outb(WICR_INSINT, WIO_ICR); wb528ms_outb(wb528ms_inb(WIO_MSCR2) & ~WMSCR2_PF, WIO_MSCR2); wb528ms_card_exist(); ret = &pdev->dev; lab_end: return ret; } static int wbreader_release(void) { wb528ms_power_down(); wb528ms_outb(0, WIO_ICR); free_irq(g_reader_info.irq, &g_reader_info); return 0; } static int wb528ms_check_cmd_ready(void) { int i; for (i = 0; i < WMAX_POLLING; i++) { if (wb528ms_inb(WIO_MSSR) & WMSSR_RDY4CMD) return 0; } return -1; } //send TPC Command and wait untill INT, but needn't justify cmd result //Result: // If CMD send (not exec) successful and MS working at Parallel Mode, // bit3 to bit0 should be |CED|ERR|BREQ|CMDNK|, or it should be 0. // If CMD send error, such as TIMEOUT,the result should below zero. static int wbreader_issue_cmd(unsigned char cmd, unsigned char *buffer, int length) { int ret, i; ret = -1; if (wb528ms_check_cmd_ready()) { WB_PRINTK_ERROR("wait for cmd RDY timeout\n"); debug_reg(); goto lab_end; } wb528ms_inb(WIO_ISR2); wb528ms_fifo_reset(); g_reader_info.sys_status = WSYS_STATUS_INTR_BS0 | WSYS_STATUS_INTR_CRC; if (length) { wb528ms_set_fifo_size(length); } for (i = 0; i < length; i++) { wb528ms_outb(buffer[i], WIO_DFR); } wb528ms_outb(cmd, WIO_CMDR); switch (cmd) { case TPC_SET_CMD: case TPC_EX_SET_CMD: wait_event_interruptible_timeout(* (g_reader_info. helper_thread_wait_queue), !(g_reader_info. sys_status & WSYS_STATUS_INTR_BS0), 200); if (g_reader_info.sys_status & WSYS_STATUS_INTR_BS0) { WB_PRINTK_ERROR("wait BS0 fail\n"); goto lab_end; } break; default: break; } ret = 0; lab_end: return ret; } static int wbreader_prepare_transfer(unsigned char *buff, unsigned int length, unsigned char read_flag) { int ret; ret = -1; // map buff memory address to bus address g_reader_info.bus_addr = pci_map_single(g_reader_info.pci_dev, buff, length, PCI_DMA_BIDIRECTIONAL); ret = 0; return ret; } static void wb528ms_set_fifo_size(unsigned int length) { unsigned char mscr2; mscr2 = wb528ms_inb(WIO_MSCR2) & 0xFC; mscr2 |= (length >> 8) & 0x3; wb528ms_outb(length & 0xFF, WIO_MSCR1); wb528ms_outb(mscr2, WIO_MSCR2); } static int inline wb528ms_pio_data(unsigned char *buffer, unsigned int length, unsigned char read_flag) { int ret, n; int i; unsigned char mssr; ret = -1; wb528ms_set_fifo_size(length); if (read_flag) { for (n = 0; n < length; n++) { for (i = 0; i < 20; i++) { mssr = wb528ms_inb(WIO_MSSR); if (0 == (mssr & WMSSR_DFE)) break; if (mssr & WMSSR_CRC) { goto lab_end; } } buffer[n] = wb528ms_inb(WIO_DFR); } ret = 0; } else { for (n = 0; n < length; n++) { for (i = 0; i < 20; i++) { mssr = wb528ms_inb(WIO_MSSR); if (0 == (mssr & WMSSR_DFF)) break; if (mssr & WMSSR_CRC) { goto lab_end; } } wb528ms_outb(buffer[n], WIO_DFR); } for (i = 0; i < WMAX_POLLING && !(wb528ms_inb(WIO_MSSR) & WMSSR_DFE); i++) ; if (i < WMAX_POLLING) { ret = 0; } } lab_end: return ret; } static int inline wb528ms_dma_data(unsigned char *buffer, unsigned int length, unsigned char read_flag) { int ret; int i; ret = -1; if (read_flag) { for (i = 0; i < WMAX_POLLING && !(wb528ms_inb(WIO_MSSR) & WMSSR_DFF); i++) ; if (i == WMAX_POLLING) { ret = -1; goto lab_out; } } g_reader_info.sys_status |= WSYS_STATUS_INTR_DMA; if (g_reader_info.card_type == WDEV_MS) { g_reader_info.sys_status &= ~WSYS_STATUS_INTR_BS0; } wb528ms_outb(2 * g_reader_info.dma, WIO_DMA_INT_MSK); wb528ms_outl(length, 4 + 0x20 * (g_reader_info.dma + 1)); if (read_flag) { wb528ms_outl(WIO_DFR, 8 + 0x20 * (g_reader_info.dma + 1)); wb528ms_outl(g_reader_info.bus_addr, 0x10 + 0x20 * (g_reader_info.dma + 1)); wb528ms_outl(WDMA_CHCSR_READ, 0x20 * (g_reader_info.dma + 1)); } else { wb528ms_outl(g_reader_info.bus_addr, 8 + 0x20 * (g_reader_info.dma + 1)); wb528ms_outl(WIO_DFR, 0x10 + 0x20 * (g_reader_info.dma + 1)); wb528ms_outl(WDMA_CHCSR_WRITE, 0x20 * (g_reader_info.dma + 1)); } wait_event_interruptible_timeout(* (g_reader_info. helper_thread_wait_queue), !(g_reader_info. sys_status & (WSYS_STATUS_INTR_DMA | WSYS_STATUS_INTR_BS0)), 200); if (0 == (g_reader_info. sys_status & (WSYS_STATUS_INTR_DMA | WSYS_STATUS_INTR_BS0))) { ret = 0; } if (read_flag == 0) { for (i = 0; i < WMAX_POLLING && !(wb528ms_inb(WIO_MSSR) & WMSSR_DFE); i++) ; if (i == WMAX_POLLING) ret = -1; } if (wb528ms_check_cmd_ready()) { debug_reg(); } pci_unmap_single(g_reader_info.pci_dev, g_reader_info.bus_addr, length, PCI_DMA_BIDIRECTIONAL); lab_out: return ret; } static int wbreader_transfer_data(unsigned char *buff, unsigned int length, unsigned char read_flag, int dma_mode) { if (dma_mode) { return wb528ms_dma_data(buff, length, read_flag); } else { return wb528ms_pio_data(buff, length, read_flag); } } static int wbreader_get_state(int which_state) { int ret; switch (which_state) { case WSTATE_PRESENT: ret = g_reader_info.card_exist; break; case WSTATE_CHANGED: ret = g_reader_info.medium_changed; break; case WSTATE_TRANSFER_CAPABILITY: ret = WIF_PARALLEL; break; default: ret = -1; WB_PRINTK("Unknow state 0x%x! \n", which_state); break; } return ret; } static int wbreader_set_state(int which_state, int state) { switch (which_state) { case WSTATE_FIFO_SIZE: wb528ms_set_fifo_size(state); break; case WSTATE_PRESENT: g_reader_info.card_exist = state; break; case WSTATE_CHANGED: g_reader_info.medium_changed = state; break; case WSTATE_CARD_TYPE: g_reader_info.card_type = state; break; default: WB_PRINTK("Unknow state 0x%x! \n", which_state); break; } return 0; } static int wbreader_set_config(unsigned char which_config, unsigned int config) { int ret; ENTER(); ret = 0; switch (which_config) { case WCONFIG_PREPARE_INIT_CARD: ret = wb528ms_init_card(); break; case WCONFIG_INTERFACE: if (wb528ms_check_cmd_ready()) { WB_PRINTK("wait for cmd RDY timeout\n"); debug_reg(); break; } if (config == WIF_PARALLEL) wb528ms_outb(wb528ms_inb(WIO_MSCR2) | WMSCR2_PF, WIO_MSCR2); else wb528ms_outb(wb528ms_inb(WIO_MSCR2) & ~WMSCR2_PF, WIO_MSCR2); ret = 0; break; default: ret = -1; break; } LEAVE(); return ret; } #include "wbmscard.c"