/* ################################################################################ # # RTL8198 MDIO char driver # # Copyright(c) 2010 Realtek Semiconductor Corp. All rights reserved. # # This program is free software; you can redistribute it and/or modify it # under the terms of the GNU General Public License as published by the Free # Software Foundation; either version 2 of the License, or (at your option) # any later version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for # more details. # # You should have received a copy of the GNU General Public License along with # this program; if not, see . # # Author: # Realtek WiFi AP software team # No. 2, Innovation Road II, Hsinchu Science Park, Hsinchu 300, Taiwan # ################################################################################ */ /*================================================================*/ /* System Include Files */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "bspchip.h" /*================================================================*/ /* Local Include Files */ #include "rtl_mdio.h" //#define CONFIG_RTL8197B_B_CUT_PATCH /*================================================================*/ /* Global Variables */ int cpu_suspend_enabled = 0; /*================================================================*/ /* Local Variables */ static struct mdio_priv *dev_priv=NULL; #ifdef SIMULATION static unsigned char data_in[256]; static int data_in_len = 0, data_in_read_idx = 0; static unsigned short data_out; static int data_out_len = 0, msg_is_fetched = 0; static unsigned long reg_scr =0, reg_isr=0; #endif /*================================================================*/ #if 0 int pre_jiffies=1; void* MAX_FUNCT; unsigned int MAX_STACK=8*1024; unsigned int PRINT_STACK=2428; unsigned int COPY_STACK=2428; unsigned int MAX_FUNCT_call[40]; #define PRINT_INTV (1*60*100) /* print max sp ever 3min */ //extern volatile int jiffies; void __attribute__((__no_instrument_function__)) __cyg_profile_func_enter(void *this_func, void *call_site) { unsigned int sp_addr; unsigned int sp_size; #if 0 if (this_func == rtk_voip_dsp_init) { printk("rtk_voip_dsp_init enter\n"); } #endif __asm__ __volatile__("ori %0, $29, 0": "=r"(sp_addr) ); sp_size = sp_addr & 8191; if(MAX_STACK > sp_size) { MAX_STACK = sp_size; MAX_FUNCT = this_func; if (COPY_STACK > sp_size) { COPY_STACK = sp_size; copy_trace(sp_addr); } } if ( ((int)jiffies - pre_jiffies) > PRINT_INTV ) { pre_jiffies = jiffies; printk("MAXSP,%x,%d.", MAX_FUNCT, MAX_STACK); if (PRINT_STACK > MAX_STACK) { int i; unsigned long flags; save_flags(flags); cli(); PRINT_STACK = MAX_STACK; printk("\nCall Trace:"); for (i=0 ; i<40 ; i++) { if (0==MAX_FUNCT_call[i]) break; printk(" [<%08lx>]", MAX_FUNCT_call[i]); if ( 4==(i%5)) printk("\n"); } restore_flags(flags); } MAX_STACK = 8*1024; } } void __attribute__((__no_instrument_function__)) __cyg_profile_func_exit(void *this_func, void *call_site) { #if 0 if (this_func == rtk_voip_dsp_init) { printk("rtk_voip_dsp_init exit\n"); } #endif } #include void __attribute__((__no_instrument_function__)) copy_trace(unsigned int *sp) { int i; //int column = 0; unsigned int *stack; unsigned long kernel_start, kernel_end; extern char _stext, _etext; stack = sp ; i = 0; kernel_start = (unsigned long) &_stext; kernel_end = (unsigned long) &_etext; //printk("\nCall Trace:"); while ((unsigned long) stack & (PAGE_SIZE -1)) { unsigned long addr; if (__get_user(addr, stack++)) { printk(" (Bad stack address)\n"); break; } /* * If the address is either in the text segment of the * kernel, or in the region which contains vmalloc'ed * memory, it *may* be the address of a calling * routine; if so, print it so that someone tracing * down the cause of the crash will be able to figure * out the call path that was taken. */ if (addr >= kernel_start && addr < kernel_end) { MAX_FUNCT_call[i]=addr; //printk(" [<%08lx>]", addr); //if (column++ == 5) { // printk("\n"); // column = 0; //} if (++i > 40) { //printk(" ..."); break; } } } //if (column != 0) // printk("\n"); for ( ; i<40; i++) MAX_FUNCT_call[i] = 0; } #endif /*================================================================*/ /*================================================================*/ /* Routine Implementations */ #ifdef SIMULATION static unsigned long register_read_dw(int offset) { unsigned long status = 0; unsigned short wdata = 0; if (offset == REG_ISR) { status = reg_isr; if (data_in_len > data_in_read_idx) status |= IP_NEWMSG; if (msg_is_fetched) { status |= IP_MSGFETCH; msg_is_fetched = 0; } return status; } else if (offset == REG_RCR) { ASSERT(data_in_len > data_in_read_idx); memcpy(&wdata, &data_in[data_in_read_idx], 2); data_in_read_idx += 2; status |= wdata; return status; } else if (offset == REG_SYSCR) { return reg_scr; } else { ASSERT(0); return status; } } static void register_write_dw(int offset, unsigned long data) { if (offset == REG_SSR) { unsigned short wData = (unsigned short)data; memcpy(&data_out, &wData, 2); data_out_len = 2; } else if (offset == REG_ISR) { reg_isr &= ~data ; } } #endif // SIMULATION #ifdef KDB_MSG static void inline debugk_out(unsigned char *label, unsigned char *data, int data_length) { int i,j; int num_blocks; int block_remainder; num_blocks = data_length >> 4; block_remainder = data_length & 15; if (label) DEBUGK_OUT("%s\n", label); if (data==NULL || data_length==0) return; for (i=0; i 0) { printk("\t"); for (j=0; jevt_que_head, priv->evt_que_tail, EV_QUE_MAX); if (size == 0) { DEBUGK_ERR("Indication queue full, drop event!\n"); #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,27) if (priv->host_pid != -1) kill_proc(priv->host_pid, SIGUSR1, 1); #else if (priv->host_pid != NULL) kill_pid(priv->host_pid, SIGUSR1, 1); #endif return 0; } ASSERT(data_len < MDIO_BUFSIZE); priv->ind_evt_que[priv->evt_que_head].id = id; priv->ind_evt_que[priv->evt_que_head].len = data_len; memcpy(&priv->ind_evt_que[priv->evt_que_head].buf, data, data_len); priv->evt_que_head = (priv->evt_que_head + 1) & (EV_QUE_MAX - 1); #if 0 //mark_nfbi , now we use polling instead of singal in HCD #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,27) if (priv->host_pid != -1) kill_proc(priv->host_pid, SIGUSR1, 1); #else if (priv->host_pid != NULL) kill_pid(priv->host_pid, SIGUSR1, 1); #endif #endif return 1; } static int retrieve_evt(struct mdio_priv *priv, unsigned char *out) { int len = 0; if (CIRC_CNT(priv->evt_que_head, priv->evt_que_tail, EV_QUE_MAX) > 0) { // more than one evt pending len = EVT_BUF_OFFSET + priv->ind_evt_que[priv->evt_que_tail].len; memcpy(out, &priv->ind_evt_que[priv->evt_que_tail], len); priv->evt_que_tail = (priv->evt_que_tail + 1) & (EV_QUE_MAX - 1); } return len; } static void process_rx_cmd(struct mdio_priv *priv, unsigned short data) { if (priv->rx_cmd_time && (priv->rx_cmd_state == STATE_RX_WAIT_LEN || priv->rx_cmd_state == STATE_RX_WAIT_DATA) && (priv->cmd_timeout && TIME_DIFF(jiffies, priv->rx_cmd_time) > priv->cmd_timeout)) { DEBUGK_ERR("Rx cmd timeout [%ld][0x%x], discard pending cmd!\n", TIME_DIFF(jiffies, priv->rx_cmd_time), data); RESET_RX_STATE; return; } if ((data & FIRST_CMD_MASK) && (priv->rx_cmd_state != STATE_RX_INIT)) { DEBUGK_ERR("Rx Sync bit but not in INIT_STATE [%d][0x%x], discard pending cmd!\n", priv->rx_cmd_state, data); RESET_RX_STATE; } if (priv->rx_cmd_state == STATE_RX_INIT) { ASSERT(priv->data_in.len == 0); if (!(data & FIRST_CMD_MASK)) { DEBUGK_ERR("Got invalid rx cmd id [0x%x], discard it!\n", data); goto invalid_cmd; } PUT_IN_DATA(data); // cmd id priv->rx_cmd_state = STATE_RX_WAIT_LEN; priv->rx_cmd_time = jiffies; } else { // STATE_RX_WAIT_LEN or STATE_RX_WAIT_DATA // check if first cmd byte is '0' if (data & 0xff00) { DEBUGK_ERR("1st byte of rx cmd not zero [%x]!\n", data >> 8); goto invalid_cmd; } if (priv->rx_cmd_state == STATE_RX_WAIT_LEN) { PUT_IN_DATA(data); priv->rx_cmd_state = STATE_RX_WAIT_DATA; priv->rx_cmd_remain_len = (data + 1)*2; // including checksum priv->rx_cmd_time = jiffies; } else { // in STATE_RX_WAIT_DATA ASSERT (priv->rx_cmd_remain_len > 0); PUT_IN_DATA(data); priv->rx_cmd_remain_len -= 2; if (priv->rx_cmd_remain_len <= 0) { // rx last bye, calcuate checksum if (!is_checksum_ok(priv->data_in.buf, priv->data_in.len)) { DEBUGK_ERR("Rx cmd cheksum error!\n"); goto invalid_cmd; } priv->data_in.len -= 2; // substract checksum length indicate_evt(priv, IND_CMD_EV, priv->data_in.buf, priv->data_in.len); RESET_RX_STATE; } else priv->rx_cmd_time = jiffies; } } return; invalid_cmd: RESET_RX_STATE; } static void transmit_msg(struct mdio_priv *priv) { unsigned short data; if (priv->data_out.len <= 0 || priv->tx_status_transmitting_len >= priv->data_out.len) return; memcpy(&data, priv->data_out.buf+priv->tx_status_transmitting_len, 2); register_write_dw(REG_SSR, (unsigned long)data); priv->tx_status_transmitting_len += 2; if (priv->tx_status_transmitting_len >= priv->data_out.len) priv->tx_status_state = STATE_TX_INIT; else priv->tx_status_state = STATE_TX_IN_PROGRESS; } #ifdef CONFIG_RTK_VOIP_ETHERNET_DSP_IS_DSP static irqreturn_t mdio_interrupt(int irq, void *dev_id) { struct mdio_priv *priv = (struct mdio_priv *)dev_id; unsigned long status, data; while (1) { status = register_read_dw(REG_ISR); if (!status) break; register_write_dw(REG_ISR, status); // clear interrupt unsigned long st_dsp_id; extern int Set_Ethernet_DSP_ID(unsigned char dsp_id); extern unsigned int Get_Ethernet_DSP_ID(void); printk("in mdio_interrupt, status = 0x%x\n", status); if (status & 0x8)//bit3 { data = register_read_dw(REG_SYSCR) & 0xF;// use SYSCR bit 0~3 to decide DSP ID for each DSP. up to 16 DSP. if((data>=0) && (data<=3)) { Set_Ethernet_DSP_ID((unsigned char)data); //printk("Set DSP ID to %d\n", data); // Singal AP process to chage DSP MAC addr printk("signal to hcd to change MAC Addr\n"); #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,27) if (priv->host_pid != -1) kill_proc(priv->host_pid, SIGUSR2, 1); #else if (priv->host_pid != -1) kill_pid(priv->host_pid, SIGUSR2, 1); #endif } else printk("%s: NOT support dsp_id=%d\n", __FUNCTION__, data); } else { printk("Get unknown mdio interrupt status = 0x%x\n", status); } if (status & IP_NEWMSG) { data = register_read_dw(REG_RCR); process_rx_cmd(priv, (unsigned short)data); } if (status & IP_MSGFETCH) { transmit_msg(priv); } if (status & (IP_ISOLATION|IP_ETHMAC|IP_WLANMAC|IP_ETHPHY |IP_WLANPHY|IP_SELMIICLK)) { data = register_read_dw(REG_SYSCR); data &= (CR_ISOLATION |CR_ETHMAC|CR_WLANMAC|CR_ETHPHY|CR_WLANPHY|CR_SELMIICLK); indicate_evt(priv, IND_SYSCTL_EV, (unsigned char *)&data, sizeof(data)); } #ifdef CONFIG_RTK_VOIP_ETHERNET_DSP_IS_DSP if (status & ~(IP_MSGFETCH|IP_NEWMSG|IP_ISOLATION|IP_ETHMAC| IP_WLANMAC|IP_ETHPHY |IP_WLANPHY |IP_SELMIICLK |IP_CUSTOM3)) { #else if (status & ~(IP_MSGFETCH|IP_NEWMSG|IP_ISOLATION|IP_ETHMAC| IP_WLANMAC|IP_ETHPHY |IP_WLANPHY|IP_SELMIICLK)) { #endif DEBUGK_ERR("Got satus=0x%x, not supported yet!\n", (unsigned int)status); } } return IRQ_HANDLED; } #else //static void mdio_interrupt(int irq, void *dev_id, struct pt_regs *regs) static irqreturn_t mdio_interrupt(int irq, void *dev_id) { struct mdio_priv *priv = (struct mdio_priv *)dev_id; unsigned long status, data; while (1) { status = register_read_dw(REG_ISR); if (!status) break; register_write_dw(REG_ISR, status); // clear interrupt if (status & IP_NEWMSG) { data = register_read_dw(REG_RCR); process_rx_cmd(priv, (unsigned short)data); } if (status & IP_MSGFETCH) { transmit_msg(priv); } if (status & (IP_ISOLATION|IP_ETHMAC|IP_WLANMAC|IP_ETHPHY |IP_WLANPHY|IP_SELMIICLK)) { data = register_read_dw(REG_SYSCR); data &= (CR_ISOLATION |CR_ETHMAC|CR_WLANMAC|CR_ETHPHY|CR_WLANPHY|CR_SELMIICLK); indicate_evt(priv, IND_SYSCTL_EV, (unsigned char *)&data, sizeof(data)); } if (status & ~(IP_MSGFETCH|IP_NEWMSG|IP_ISOLATION|IP_ETHMAC| IP_WLANMAC|IP_ETHPHY |IP_WLANPHY|IP_SELMIICLK)) { DEBUGK_ERR("Got satus=0x%x, not supported yet!\n", (unsigned int)status); } } return IRQ_HANDLED; } #endif void toggle_usb_device_insert_bit(void) { unsigned long flags; if (dev_priv == NULL) return; spin_lock_irqsave(&dev_priv->reglock, flags); if (REG32(REG_SYSSR) & SR_USBInsertStatus) REG32(REG_SYSSR) = REG32(REG_SYSSR) & (~SR_USBInsertStatus); else REG32(REG_SYSSR) = REG32(REG_SYSSR) | SR_USBInsertStatus; spin_unlock_irqrestore(&dev_priv->reglock, flags); //printk("usb device inserted\n"); } void toggle_usb_device_remove_bit(void) { unsigned long flags; if (dev_priv == NULL) return; spin_lock_irqsave(&dev_priv->reglock, flags); if (REG32(REG_SYSSR) & SR_USBRemoveStatus) REG32(REG_SYSSR) = REG32(REG_SYSSR) & (~SR_USBRemoveStatus); else REG32(REG_SYSSR) = REG32(REG_SYSSR) | SR_USBRemoveStatus; spin_unlock_irqrestore(&dev_priv->reglock, flags); //printk("usb device removed\n"); } void set_wlanlink_bit(int val) { unsigned long flags; if (dev_priv == NULL) return; spin_lock_irqsave(&dev_priv->reglock, flags); if (val) REG32(REG_SYSSR) = REG32(REG_SYSSR) | SR_WLANLink; else REG32(REG_SYSSR) = REG32(REG_SYSSR) & (~SR_WLANLink); spin_unlock_irqrestore(&dev_priv->reglock, flags); } static void mdio_reg_poll_timer(unsigned long task_priv) { unsigned long flags; unsigned long reg1, reg2; struct mdio_priv *priv = (struct mdio_priv *)task_priv; if (!priv->poll_timer_up) return; if (priv->reg_BMCR_write != REG32(REG_BMCR)) { //printk("1. priv->reg_BMCR_write=%x\n", priv->reg_BMCR_write); reg1 = reg2 = REG32(REG_BMCR); if ((reg1&0x8000) && (priv->reg_BMCR_write&0x8000)) //check "reset" bit reg1 &= 0x7fff; if ((reg1&0x0200) && (priv->reg_BMCR_write&0x0200)) //check "restart auto negotiation" bit reg1 &= 0xfdff; if (priv->force_power_down) reg1 |= 0x0800; //power down priv->reg_BMCR_write = reg2; set_ether_phy_reg(ETH_PORT_NUM, 0, reg1); if (reg1 & 0x1000) { if ((priv->reg_ANAR_write & 0x0180) == 0x0000) set_ether_phy_reg(6, 0, 0x120c); else set_ether_phy_reg(6, 0, 0x1208); } #ifdef CONFIG_RTL8197B_B_CUT_PATCH else { if (reg1 & 0x2000) //100M set_ether_phy_reg(6, 0, 0x120c); else //10M set_ether_phy_reg(6, 0, 0x1208); } #endif //printk("2. priv->reg_BMCR_write=%x\n", priv->reg_BMCR_write); } if (priv->reg_BMCR_read != get_ether_phy_reg(ETH_PORT_NUM, 0)) { //printk("1. priv->reg_BMCR_read=%x\n", priv->reg_BMCR_read); priv->reg_BMCR_read = get_ether_phy_reg(ETH_PORT_NUM, 0); if (priv->reg_BMCR_write & 0x0800) //power down REG32(REG_BMCR) = priv->reg_BMCR_read; else REG32(REG_BMCR) = priv->reg_BMCR_read & 0xf7ff; //printk("2. priv->reg_BMCR_read=%x\n", priv->reg_BMCR_read); } if (priv->reg_BMSR_read != get_ether_phy_reg(ETH_PORT_NUM, 1)) { reg1 = get_ether_phy_reg(ETH_PORT_NUM, 1); if ((priv->reg_BMSR_read ^ reg1) & (~BIT(2))) { //toggle EthPHYStatusChange bit to generate interrupt spin_lock_irqsave(&priv->reglock, flags); if (REG32(REG_SYSSR) & SR_EthPHYStatusChange) REG32(REG_SYSSR) = REG32(REG_SYSSR) & (~SR_EthPHYStatusChange); else REG32(REG_SYSSR) = REG32(REG_SYSSR) | SR_EthPHYStatusChange; spin_unlock_irqrestore(&priv->reglock, flags); //printk("EthPhy status changed!\n"); } priv->reg_BMSR_read = reg1; REG32(REG_BMSR) = priv->reg_BMSR_read; if (priv->eth_phy_link_status != ((priv->reg_BMSR_read & BIT(2)) ? 1 : 0)) { priv->eth_phy_link_status = ((priv->reg_BMSR_read & BIT(2)) ? 1 : 0); spin_lock_irqsave(&priv->reglock, flags); if (priv->eth_phy_link_status) REG32(REG_SYSSR) = REG32(REG_SYSSR) | SR_EthLink; else REG32(REG_SYSSR) = REG32(REG_SYSSR) & (~SR_EthLink); spin_unlock_irqrestore(&priv->reglock, flags); //DEBUGK_OUT("Ether Link changed [%d]!\n", priv->eth_phy_link_status); //printk("Ether Link changed [%d]!\n", priv->eth_phy_link_status); } } if (!(priv->reg_BMCR_write&0x8000)) { if (priv->reg_ANAR_write != REG32(REG_ANAR)) { //printk("1. priv->reg_ANAR_write=%x\n", priv->reg_ANAR_write); priv->reg_ANAR_write = REG32(REG_ANAR); //printk("2. priv->reg_ANAR_write=%x\n", priv->reg_ANAR_write); set_ether_phy_reg(ETH_PORT_NUM, 4, priv->reg_ANAR_write); //printk("3. priv->reg_ANAR_write=%x\n", priv->reg_ANAR_write); if (priv->reg_BMCR_write&0x1000) { if ((priv->reg_ANAR_write & 0x0180) == 0x0000) set_ether_phy_reg(6, 0, 0x120c); else set_ether_phy_reg(6, 0, 0x1208); } } } if (priv->reg_ANAR_read != get_ether_phy_reg(ETH_PORT_NUM, 4)) { //printk("1. priv->reg_ANAR_read=%x\n", priv->reg_ANAR_read); priv->reg_ANAR_read = get_ether_phy_reg(ETH_PORT_NUM, 4); //printk("2. priv->reg_ANAR_read=%x\n", priv->reg_ANAR_read); REG32(REG_ANAR) = priv->reg_ANAR_read; } if (priv->reg_ANLPAR_read != get_ether_phy_reg(ETH_PORT_NUM, 5)) { priv->reg_ANLPAR_read = get_ether_phy_reg(ETH_PORT_NUM, 5); REG32(REG_ANLPAR) = priv->reg_ANLPAR_read; } mod_timer(&priv->reg_poll_timer, jiffies + priv->phy_reg_poll_time); } static int mdio_open(struct inode *inode, struct file *filp) { filp->private_data = dev_priv; //MOD_INC_USE_COUNT; return 0; } static int mdio_close(struct inode *inode, struct file *filp) { //MOD_DEC_USE_COUNT; return 0; } static ssize_t mdio_read (struct file *filp, char *buf, size_t count, loff_t *offset) { struct mdio_priv *priv = (struct mdio_priv *)filp->private_data; if (!buf) return 0; count = retrieve_evt(priv, buf); return count; } static ssize_t mdio_write (struct file *filp, const char *buf, size_t count, loff_t *offset) { struct mdio_priv *priv = (struct mdio_priv *)filp->private_data; unsigned short last_word = 0; if (!buf) { DEBUGK_ERR("buf = NULL!\n"); goto ret; } if (count > MDIO_BUFSIZE) { DEBUGK_ERR("write length too big!\n"); count = -EFAULT; goto ret; } if (priv->tx_status_state != STATE_TX_INIT) { DEBUGK_ERR("Transmit status, but not in valid state [%d]. Reset state!\n", priv->tx_status_state); priv->tx_status_state = STATE_TX_INIT; } if (count %2) { DEBUGK_ERR("Invalid Tx size [%d]!\n", count); count = -EFAULT; goto ret; } if (copy_from_user((void *)priv->data_out.buf, buf, count)) { DEBUGK_ERR("copy_from_user() error!\n"); count = -EFAULT; goto ret; } #ifdef KDB_MSG debugk_out("write data", priv->data_out.buf, count); #endif last_word = (unsigned short) append_checksum(priv->data_out.buf, count); memcpy(&priv->data_out.buf[count], &last_word, 2); priv->data_out.len = count + sizeof(last_word); priv->tx_status_transmitting_len = 0; priv->tx_status_state = STATE_TX_INIT; transmit_msg(priv); ret: return count; } static void dump_private_data(void) { //int i; printk("cmd_timeout=%d\n", dev_priv->cmd_timeout); printk("poll_timer_up=%d\n", dev_priv->poll_timer_up); printk("phy_reg_poll_time=%d\n", dev_priv->phy_reg_poll_time); #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,27) printk("host_pid=%d\n", dev_priv->host_pid); #else printk("host_pid=0x%p\n", dev_priv->host_pid); #endif printk("force_power_down=%d\n", dev_priv->force_power_down); printk("eth_phy_link_status=%d\n", dev_priv->eth_phy_link_status); printk("\nreg_BMCR_write=0x%04lx\n", dev_priv->reg_BMCR_write); printk("reg_BMCR_read=0x%04lx\n", dev_priv->reg_BMCR_read); printk("reg_BMSR_read=0x%04lx\n", dev_priv->reg_BMSR_read); printk("reg_ANAR_write=0x%04lx\n", dev_priv->reg_ANAR_write); printk("reg_ANAR_read=0x%04lx\n", dev_priv->reg_ANAR_read); printk("reg_ANLPAR_read=0x%04lx\n", dev_priv->reg_ANLPAR_read); printk("\nevt_que_head=%d\n", dev_priv->evt_que_head); printk("evt_que_tail=%d\n", dev_priv->evt_que_tail); printk("evt_que count=%d\n", CIRC_CNT(dev_priv->evt_que_head, dev_priv->evt_que_tail, EV_QUE_MAX)); printk("evt_que space=%d\n", CIRC_SPACE(dev_priv->evt_que_head, dev_priv->evt_que_tail, EV_QUE_MAX)); printk("\ntx_status_state:"); switch(dev_priv->tx_status_state) { case STATE_TX_INIT: printk("STATE_TX_INIT\n"); break; case STATE_TX_IN_PROGRESS: printk("STATE_TX_IN_PROGRESS\n"); break; } printk("tx_status_transmitting_len=%d\n", dev_priv->tx_status_transmitting_len); printk("rx_cmd_state:"); switch(dev_priv->rx_cmd_state) { case STATE_RX_INIT: printk("STATE_RX_INIT\n"); break; case STATE_RX_WAIT_LEN: printk("STATE_RX_WAIT_LEN\n"); break; case STATE_RX_WAIT_DATA: printk("STATE_RX_WAIT_DATA\n"); break; case STATE_RX_WAIT_DAEMON: printk("STATE_RX_WAIT_DAEMON\n"); break; } printk("rx_cmd_remain_len=%d\n", dev_priv->rx_cmd_remain_len); printk("rx_cmd_time=%lx jiffies=%lx (%d sec)\n", dev_priv->rx_cmd_time, jiffies, (int)(jiffies-dev_priv->rx_cmd_time)/HZ); } void mdio_private_command(int type) { switch(type) { case 0: //stop reg poll timer dev_priv->poll_timer_up = 0; break; case 1: //start reg poll timer dev_priv->poll_timer_up = 1; mod_timer(&dev_priv->reg_poll_timer, jiffies + dev_priv->phy_reg_poll_time); break; case 2: //force power down: on dev_priv->force_power_down = 1; dev_priv->reg_BMCR_write = 0xf0000; //force poll timer to do power down break; case 3: //force power down: off dev_priv->force_power_down = 0; dev_priv->reg_BMCR_write = 0xf0000; //force poll timer to do power down break; case 4: dump_private_data(); break; case 5: //WLAN link up set_wlanlink_bit(1); break; case 6: //WLAN link down set_wlanlink_bit(0); break; } } static int mdio_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg) { unsigned long flags; struct mdio_priv *priv = (struct mdio_priv *)filp->private_data; int val, retval = 0; unsigned char bval; struct mdio_mem32_param mem_param; struct reg_param regparam; /* * extract the type and number bitfields, and don't decode * wrong cmds: return ENOTTY (inappropriate ioctl) before access_ok() */ if (_IOC_TYPE(cmd) != MDIO_IOC_MAGIC) return -ENOTTY; if (_IOC_NR(cmd) > MDIO_IOCTL_MAXNR) return -ENOTTY; switch (cmd) { case MDIO_IOCTL_PRIV_CMD: retval = get_user(val, (int *)arg); if (retval == 0) { mdio_private_command(val); } break; case MDIO_IOCTL_GET_REG: retval = copy_from_user(®param, (struct reg_param *)arg, sizeof(struct reg_param)); if (retval == 0) { //printk("1regparam.addr=%x regparam.val=%x\n", regparam.addr, regparam.val); regparam.val = get_ether_phy_reg(((regparam.addr>>16)&0xffff), (regparam.addr&0xffff)); //printk("2regparam.addr=%x regparam.val=%x\n", regparam.addr, regparam.val); } retval = copy_to_user((struct reg_param *)arg, ®param, sizeof(struct reg_param)); break; case MDIO_IOCTL_SET_REG: retval = copy_from_user(®param, (struct reg_param *)arg, sizeof(struct reg_param)); if (retval == 0) { //printk("regparam.addr=%x regparam.val=%x\n", regparam.addr, regparam.val); set_ether_phy_reg(((regparam.addr>>16)&0xffff), (regparam.addr&0xffff), regparam.val); } break; case MDIO_IOCTL_SET_HOST_PID: retval = copy_from_user((void *)&val, (void *)arg, 4); if (retval == 0) { #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,27) priv->host_pid = val; #elif LINUX_VERSION_CODE > KERNEL_VERSION(2,6,30) priv->host_pid = find_pid_ns(val,0); #else // TODO : Check in stable 2.6.27 priv->host_pid = task_pid(find_task_by_vpid(val)); //priv->host_pid = task_pid(current); #endif DEBUGK_OUT("set pid=%d\n", val); printk("set pid=%d priv->host_pid=%p\n", val, priv->host_pid); } spin_lock_irqsave(&priv->reglock, flags); REG32(REG_SYSSR) = REG32(REG_SYSSR) | SR_AllSoftwareReady; spin_unlock_irqrestore(&priv->reglock, flags); break; case MDIO_IOCTL_SET_CMD_TIMEOUT: retval = copy_from_user((void *)&bval, (void *)arg, 1); if (retval == 0) { priv->cmd_timeout = (int)bval; DEBUGK_OUT("set cmd_timeout=%d\n", priv->cmd_timeout); } break; case MDIO_IOCTL_SET_PHY_POLL_TIME: retval = copy_from_user( (void *)&bval, (void *)arg, 1); if (retval == 0) { priv->phy_reg_poll_time = (int)bval; mod_timer(&priv->reg_poll_timer, jiffies + priv->phy_reg_poll_time); DEBUGK_OUT("set poll_time=%d\n", priv->phy_reg_poll_time); } break; case MDIO_IOCTL_READ_MEM: retval = copy_from_user( (void *)&mem_param.addr, (void *)arg, 4); if (retval == 0) { mem_param.val = READ_MEM32(mem_param.addr); retval = copy_to_user((void *)arg, (void *)&mem_param.val, 4); DEBUGK_OUT("read_mem: addr=0x%x, data=0x%x\n", (int)mem_param.addr, (int)mem_param.val); } break; case MDIO_IOCTL_WRITE_MEM: retval = copy_from_user((void *)&mem_param, (void *)arg, sizeof(mem_param)); if (retval == 0) { WRITE_MEM32(mem_param.addr, mem_param.val); DEBUGK_OUT("write_mem: addr=0x%x, data=0x%x\n", (int)mem_param.addr, (int)mem_param.val); } break; case MDIO_IOCTL_SET_MII_PAUSE: retval = copy_from_user( (void *)&bval, (void *)arg, 1); if (retval == 0) { if (bval == 0) // disable pause WRITE_MEM32(PCRP0, (~(0x3< 0) { fetch_again: while (data_out_len > 0) { data_out_len = 0; size += sprintf(&buf[size], "%04x ", data_out); } msg_is_fetched = 1; mdio_interrupt(0, (void *)dev_priv, (struct pt_regs *)NULL); if (data_out_len > 0) goto fetch_again; strcat(&buf[size++], "\n"); } return size; } static unsigned short _atoi(char *s, int base) { int k = 0; k = 0; if (base == 10) { while (*s != '\0' && *s >= '0' && *s <= '9') { k = 10 * k + (*s - '0'); s++; } } else { while (*s != '\0') { int v; if ( *s >= '0' && *s <= '9') v = *s - '0'; else if ( *s >= 'a' && *s <= 'f') v = *s - 'a' + 10; else if ( *s >= 'A' && *s <= 'F') v = *s - 'A' + 10; else { DEBUGK_ERR("error hex format [%x]!\n", *s); return 0; } k = 16 * k + v; s++; } } return (unsigned short)k; } static int write_proc(struct file *file, const char *buffer, unsigned long count, void *data) { char tmp[100]; int len=count; unsigned short in_data; if (!memcmp(buffer, "cmd: ", 5)) { buffer += 5; len -= 5; data_in_len = 0; while (len > 0) { memcpy(tmp, buffer, 4); tmp[4] = '\0'; in_data = _atoi(tmp, 16); memcpy(&data_in[data_in_len], &in_data, 2); data_in_len += 2; len -= 5; buffer += 5; } data_in_read_idx = 0; mdio_interrupt(0, (void *)dev_priv, (struct pt_regs *)NULL); } else if (!memcmp(buffer, "scr: ", 5)) { buffer += 5; memcpy(tmp, buffer, 4); tmp[4] = '\0'; reg_scr = _atoi(tmp, 16); } else if (!memcmp(buffer, "isr: ", 5)) { buffer += 5; memcpy(tmp, buffer, 4); tmp[4] = '\0'; reg_isr = _atoi(tmp, 16); mdio_interrupt(0, (void *)dev_priv, (struct pt_regs *)NULL); } else { printk("Invalid cmd!\n"); } return count; } #endif static void __exit mdio_exit(void) { //DEBUGK_OUT("%s: major=%d, minor=%d\n", __FUNCTION__, MAJOR(inode->i_rdev), MINOR(inode->i_rdev)); REG32(REG_IMR) = 0; free_irq(BSP_NFBI_IRQ, dev_priv); del_timer_sync(&dev_priv->reg_poll_timer); kfree(dev_priv); dev_priv = NULL; } static struct file_operations mdio_fops = { read: mdio_read, write: mdio_write, ioctl: mdio_ioctl, open: mdio_open, release: mdio_close, }; static int __init mdio_init(void) { struct mdio_priv *priv; if (register_chrdev(DRIVER_MAJOR, DRIVER_NAME, &mdio_fops)) { DEBUGK_ERR(KERN_ERR DRIVER_NAME": unable to get major %d\n", DRIVER_MAJOR); return -EIO; } #ifdef SIMULATION struct proc_dir_entry *res; res = create_proc_entry("mdio_flag", 0, NULL); if (res) { res->read_proc = read_proc; res->write_proc = write_proc; } else { DEBUGK_ERR(KERN_ERR DRIVER_NAME": unable to create /proc/mdio_flag\n"); return -1; } #endif printk(KERN_INFO DRIVER_NAME" driver "DRIVER_VER" at %x (Interrupt %d)\n", NFBI_BASE, BSP_NFBI_IRQ); DEBUGK_OUT("%s: major=%d, minor=%d\n", __FUNCTION__, MAJOR(inode->i_rdev), MINOR(inode->i_rdev)); priv = (struct mdio_priv *)kmalloc(sizeof (struct mdio_priv), GFP_KERNEL); if(!priv) return -ENOMEM; memset((void *)priv, 0, sizeof (struct mdio_priv)); priv->reglock = SPIN_LOCK_UNLOCKED; //if (request_irq(BSP_NFBI_IRQ, mdio_interrupt, SA_INTERRUPT, DRIVER_NAME, (void *)priv)) { //if (request_irq(BSP_NFBI_IRQ, mdio_interrupt, IRQF_DISABLED, DRIVER_NAME, (void *)priv)) { if (request_irq(BSP_NFBI_IRQ, mdio_interrupt, 0, DRIVER_NAME, (void *)priv)) { DEBUGK_ERR(KERN_ERR DRIVER_NAME": IRQ %d is not free.\n", BSP_NFBI_IRQ); return -1; } else printk("Request BSP_NFBI_IRQ successfully.\n"); REG32(REG_IMR) = NEEDED_IRQ_MASK; #if 1 //force the poll time to update the register priv->reg_BMCR_write = 0xf0000; priv->reg_BMCR_read = 0xf0000; priv->reg_BMSR_read = 0xf0000; priv->reg_ANAR_write = 0xf0000; priv->reg_ANAR_read = 0xf0000; priv->reg_ANLPAR_read = 0xf0000; priv->eth_phy_link_status = -1; #else priv->reg_BMCR_write = REG32(REG_BMCR); set_ether_phy_reg(ETH_PORT_NUM, 0, priv->reg_BMCR_write); priv->reg_BMCR_read = get_ether_phy_reg(ETH_PORT_NUM, 0); REG32(REG_BMCR) = priv->reg_BMCR_read; priv->reg_BMSR_read = get_ether_phy_reg(ETH_PORT_NUM, 1); REG32(REG_BMSR) = priv->reg_BMSR_read; priv->reg_ANAR_write = REG32(REG_ANAR); set_ether_phy_reg(ETH_PORT_NUM, 4, priv->reg_ANAR_write); //printk("priv->reg_ANAR_write=%x\n", priv->reg_ANAR_write); priv->reg_ANAR_read = get_ether_phy_reg(ETH_PORT_NUM, 4); REG32(REG_ANAR) = priv->reg_ANAR_read; //printk("priv->reg_ANAR_read=%x\n", priv->reg_ANAR_read); priv->reg_ANLPAR_read = get_ether_phy_reg(ETH_PORT_NUM, 5); REG32(REG_ANLPAR) = priv->reg_ANLPAR_read; priv->eth_phy_link_status = ((priv->reg_BMSR_read & BIT(2)) ? 1 : 0); #endif init_timer(&priv->reg_poll_timer); priv->reg_poll_timer.data = (unsigned long)priv; priv->reg_poll_timer.function = mdio_reg_poll_timer; priv->poll_timer_up = 0; priv->force_power_down = 0; #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,27) priv->host_pid = -1; #else priv->host_pid = NULL; #endif dev_priv = priv; return 0; } /*================================================================*/ module_init(mdio_init); module_exit(mdio_exit); MODULE_DESCRIPTION("Driver for RTL8197B MDC/MDIO"); MODULE_LICENSE("none-GPL"); //EXPORT_NO_SYMBOLS;