;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;                                                                 ;;
;; Copyright (C) KolibriOS team 2018-2020. All rights reserved.    ;;
;; Distributed under terms of the GNU General Public License       ;;
;;                                                                 ;;
;;  AR81XX driver for KolibriOS                                    ;;
;;                                                                 ;;
;;  based on alx driver from TI-OpenLink                           ;;
;;                                                                 ;;
;;  Written by hidnplayr (hidnplayr@gmail.com)                     ;;
;;                                                                 ;;
;;  Thanks to: floppy121 for kindly providing me with the hardware ;;
;;              that made the development of this driver possible. ;;
;;                                                                 ;;
;;          GNU GENERAL PUBLIC LICENSE                             ;;
;;             Version 2, June 1991                                ;;
;;                                                                 ;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

format PE DLL native
entry START

        CURRENT_API             = 0x0200
        COMPATIBLE_API          = 0x0100
        API_VERSION             = (COMPATIBLE_API shl 16) + CURRENT_API

        MAX_DEVICES             = 16

        __DEBUG__               = 1
        __DEBUG_LEVEL__         = 2

        TX_RING_SIZE            = 128             ; RING sizes must be a power of 2
        RX_RING_SIZE            = 128

        RX_BUFFER_SIZE          = 1536

        SMB_TIMER               = 400
        IMT                     = 200
        ITH_TPD                 = TX_RING_SIZE / 3

section '.flat' readable writable executable

include '../proc32.inc'
include '../struct.inc'
include '../macros.inc'
include '../fdo.inc'
include '../netdrv.inc'

include 'ar81xx.inc'

; Transmit Packet Descriptor

struct alx_tpd
        length          dw ?
        vlan_tag        dw ?
        word1           dd ?
        addr_l          dd ?
        addr_h          dd ?
ends

; Receive Return Descriptor

struct alx_rrd
        word0           dd ?    ; IP payload cksum + number of RFDs + start index of RFD-ring
        rss_hash        dd ?
        word2           dd ?    ; VLAN tag + Protocol ID + RSS Q num + RSS Hash algorithm
        word3           dd ?    ; Packet length + status
ends

; Receive Free Descriptor

struct alx_rfd
        addr_l          dd ?
        addr_h          dd ?
ends


struct  device          ETH_DEVICE

        io_addr         dd ?
        pci_bus         dd ?
        pci_dev         dd ?
        pci_vid         dw ?    ; Vendor ID
        pci_did         dw ?    ; Device ID
        irq_line        dd ?
        pci_rev         dd ?
        chip_rev        dd ?
        mmio_addr       dd ?

;        dma_chnl        dd ?
        max_dma_chnl    dd ?

        int_mask        dd ?
        rx_ctrl         dd ?

        rxq_read_idx            dd ?
        rxq_write_idx           dd ?
;        rxq_rrd_read_idx        dd ?
        txq_read_idx            dd ?
        txq_write_idx           dd ?

        rb 0x100 - ($ and 0xff) ; align 256
        tpd_ring        rb ((TX_RING_SIZE*sizeof.alx_tpd+16) and 0xfffffff0)
        rrd_ring        rb ((RX_RING_SIZE*sizeof.alx_rrd+16) and 0xfffffff0)
        rfd_ring        rb ((RX_RING_SIZE*sizeof.alx_rfd+16) and 0xfffffff0)
        tpd_ring_virt   rd TX_RING_SIZE
        rfd_ring_virt   rd RX_RING_SIZE

ends



;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;                        ;;
;; proc START             ;;
;;                        ;;
;; (standard driver proc) ;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;

proc START c, reason:dword, cmdline:dword

        cmp     [reason], DRV_ENTRY
        jne     .fail

        DEBUGF  2,"Loading driver\n"
        invoke  RegService, my_service, service_proc
        ret

  .fail:
        xor     eax, eax
        ret

endp


;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;                        ;;
;; proc SERVICE_PROC      ;;
;;                        ;;
;; (standard driver proc) ;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;

proc service_proc stdcall, ioctl:dword

        mov     edx, [ioctl]
        mov     eax, [edx + IOCTL.io_code]

;------------------------------------------------------

        cmp     eax, 0 ;SRV_GETVERSION
        jne     @F

        cmp     [edx + IOCTL.out_size], 4
        jb      .fail
        mov     eax, [edx + IOCTL.output]
        mov     [eax], dword API_VERSION

        xor     eax, eax
        ret

;------------------------------------------------------
  @@:
        cmp     eax, 1 ;SRV_HOOK
        jne     .fail

        cmp     [edx + IOCTL.inp_size], 3               ; Data input must be at least 3 bytes
        jb      .fail

        mov     eax, [edx + IOCTL.input]
        cmp     byte [eax], 1                           ; 1 means device number and bus number (pci) are given
        jne     .fail                                   ; other types arent supported for this card yet

; check if the device is already listed

        mov     esi, device_list
        mov     ecx, [devices]
        test    ecx, ecx
        jz      .firstdevice

;        mov     eax, [edx + IOCTL.input]               ; get the pci bus and device numbers
        mov     ax , [eax+1]                            ;
  .nextdevice:
        mov     ebx, [esi]
        cmp     al, byte[ebx + device.pci_bus]
        jne     @f
        cmp     ah, byte[ebx + device.pci_dev]
        je      .find_devicenum                         ; Device is already loaded, let's find it's device number
       @@:
        add     esi, 4
        loop    .nextdevice


; This device doesnt have its own eth_device structure yet, lets create one
  .firstdevice:
        cmp     [devices], MAX_DEVICES                  ; First check if the driver can handle one more card
        jae     .fail

        allocate_and_clear ebx, sizeof.device, .fail    ; Allocate the buffer for device structure

; Fill in the direct call addresses into the struct

        mov     [ebx + device.reset], reset
        mov     [ebx + device.transmit], transmit
        mov     [ebx + device.unload], unload
        mov     [ebx + device.name], my_service

; save the pci bus and device numbers

        mov     eax, [edx + IOCTL.input]
        movzx   ecx, byte[eax+1]
        mov     [ebx + device.pci_bus], ecx
        movzx   ecx, byte[eax+2]
        mov     [ebx + device.pci_dev], ecx

; Now, it's time to find the base mmio addres of the PCI device

        stdcall PCI_find_mmio, [ebx + device.pci_bus], [ebx + device.pci_dev] ; returns in eax
        test    eax, eax
        jz      .destroy

; Create virtual mapping of the physical memory

        invoke  MapIoMem, eax, 10000h, PG_SW+PG_NOCACHE
        mov     [ebx + device.mmio_addr], eax

; We've found the mmio address, find IRQ now
        invoke  PciRead8, [ebx + device.pci_bus], [ebx + device.pci_dev], PCI_header00.interrupt_line
        and     eax, 0xff
        mov     [ebx + device.irq_line], eax

        DEBUGF  1,"Hooking into device, dev:%x, bus:%x, irq:%x, addr:%x\n",\
        [ebx + device.pci_dev]:1, [ebx + device.pci_bus]:1, [ebx + device.irq_line]:1, [ebx + device.mmio_addr]:8

; Ok, the eth_device structure is ready, let's probe the device

        mov     eax, [devices]                                          ; Add the device structure to our device list
        mov     [device_list+4*eax], ebx                                ; (IRQ handler uses this list to find device)
        inc     [devices]                                               ;

        call    probe                                                   ; this function will output in eax
        test    eax, eax
        jnz     .err2

        DEBUGF  2,"Initialised OK\n"

        mov     [ebx + device.type], NET_TYPE_ETH
        invoke  NetRegDev

        cmp     eax, -1
        je      .destroy

        ret

; If the device was already loaded, find the device number and return it in eax

  .find_devicenum:
        DEBUGF  2,"Trying to find device number of already registered device\n"
        invoke  NetPtrToNum                                             ; This kernel procedure converts a pointer to device struct in ebx
                                                                        ; into a device number in edi
        mov     eax, edi                                                ; Application wants it in eax instead
        DEBUGF  2,"Kernel says: %u\n", eax
        ret

; If an error occured, remove all allocated data and exit (returning -1 in eax)

  .destroy:
        ; todo: reset device into virgin state

  .err2:
        dec     [devices]
  .err:
        invoke  KernelFree, ebx
  .fail:
        DEBUGF  2, "Failed to load\n"
        or      eax, -1
        ret

;------------------------------------------------------
endp


;;/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\;;
;;                                                                        ;;
;;        Actual Hardware dependent code starts here                      ;;
;;                                                                        ;;
;;/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\;;


align 4
unload:
        ; TODO: (in this particular order)
        ;
        ; - Stop the device
        ; - Detach int handler
        ; - Remove device from local list (device_list)
        ; - call unregister function in kernel
        ; - Remove all allocated structures and buffers the card used

        or      eax, -1
        ret


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;;  probe: enables the device (if it really is AR81XX)
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

align 4
probe:
        DEBUGF  1,"Probing\n"

; Make the device a bus master
        invoke  PciRead16, [ebx + device.pci_bus], [ebx + device.pci_dev], PCI_header00.command
        or      al, PCI_CMD_MASTER + PCI_CMD_MMIO + PCI_CMD_PIO
        and     ax, not(PCI_CMD_INTX_DISABLE)
        invoke  PciWrite16, [ebx + device.pci_bus], [ebx + device.pci_dev], PCI_header00.command, eax

; get device id
        invoke  PciRead16, [ebx + device.pci_bus], [ebx + device.pci_dev], PCI_header00.device_id
        mov     [ebx + device.pci_did], ax

        mov     esi, chiplist
  .loop:
        cmp     word[esi+2], ax
        je      .got_it
        add     esi, 8
        cmp     esi, chiplist + 6*8
        jbe     .loop
        DEBUGF  2, "Unknown chip: 0x%x, continuing anyway\n", ax
        jmp     .done
  .got_it:
        mov     eax, dword[esi+4]
        mov     [ebx + device.name], eax
        DEBUGF  1, "Chip type = %s\n", eax
  .done:

; get revision id.
        invoke  PciRead8, [ebx + device.pci_bus], [ebx + device.pci_dev], PCI_header00.revision_id
        and     eax, 0xff
        mov     [ebx + device.pci_rev], eax
        shr     al, ALX_PCI_REVID_SHIFT
        mov     [ebx + device.chip_rev], eax

        DEBUGF  1,"Revision: %u\n", al

;;;        call    alx_reset_pcie

        mov     ecx, (ALX_PMCTRL_L0S_EN or ALX_PMCTRL_L1_EN or ALX_PMCTRL_ASPM_FCEN)
        call    alx_enable_aspm

        call    alx_reset_phy

        call    alx_reset_mac

        call    alx_get_perm_macaddr

align 4
reset:

        DEBUGF  1,"Resetting\n"

; alx init_sw

        call    alx_identify_hw

;        mov     eax, [ebx + device.max_dma_chnl]
;        mov     [ebx + device.dma_chnl], eax

        mov     [ebx + device.int_mask], ALX_ISR_MISC
        mov     [ebx + device.rx_ctrl], ALX_MAC_CTRL_WOLSPED_SWEN or ALX_MAC_CTRL_MHASH_ALG_HI5B or ALX_MAC_CTRL_BRD_EN or ALX_MAC_CTRL_PCRCE or ALX_MAC_CTRL_CRCE or ALX_MAC_CTRL_RXFC_EN or ALX_MAC_CTRL_TXFC_EN or (7 shl ALX_MAC_CTRL_PRMBLEN_SHIFT)

        call    alx_alloc_rings

        call    alx_configure

        call    alx_request_irq

; attach interrupt handler

        mov     eax, [ebx + device.irq_line]
        DEBUGF  1,"Attaching int handler to irq %x\n", eax:1
        invoke  AttachIntHandler, eax, int_handler, ebx
        test    eax, eax
        jnz     @f
        DEBUGF  2,"Could not attach int handler!\n"
        or      eax, -1
        ret
  @@:

; Clear old interrupts
        mov     edi, [ebx + device.mmio_addr]
        mov     eax, not ALX_ISR_DIS
        mov     [edi + ALX_ISR], eax

        call    alx_irq_enable

; Set the mtu, kernel will be able to send now
        mov     [ebx + device.mtu], 1514

        call    alx_check_link

        DEBUGF  1,"Reset ok\n"
        xor     eax, eax
        ret



;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;                                         ;;
;; Transmit                                ;;
;;                                         ;;
;; In: pointer to device structure in ebx  ;;
;;                                         ;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;; alx_start_xmit
;; alx_map_tx_skb

proc transmit stdcall bufferptr

        pushf
        cli

        mov     esi, [bufferptr]
        DEBUGF  1,"Transmitting packet, buffer:%x, size:%u\n", [bufferptr], [esi + NET_BUFF.length]
        lea     eax, [esi + NET_BUFF.data]
        DEBUGF  1,"To: %x-%x-%x-%x-%x-%x From: %x-%x-%x-%x-%x-%x Type:%x%x\n",\
        [eax+00]:2,[eax+01]:2,[eax+02]:2,[eax+03]:2,[eax+04]:2,[eax+05]:2,\
        [eax+06]:2,[eax+07]:2,[eax+08]:2,[eax+09]:2,[eax+10]:2,[eax+11]:2,\
        [eax+13]:2,[eax+12]:2

        cmp     [esi + NET_BUFF.length], 1514
        ja      .fail
        cmp     [esi + NET_BUFF.length], 60
        jb      .fail

; Program the descriptor
        mov     edi, [ebx + device.txq_write_idx]
        DEBUGF  1, "Using TPD: %u\n", edi
        mov     dword[ebx + device.tpd_ring_virt + edi*4], esi
        shl     edi, 4
        lea     edi, [ebx + device.tpd_ring + edi]
        mov     eax, esi
        add     eax, [esi + NET_BUFF.offset]
        invoke  GetPhysAddr
        mov     [edi + alx_tpd.addr_l], eax
        mov     [edi + alx_tpd.addr_h], 0

        mov     ecx, [esi + NET_BUFF.length]
        mov     [edi + alx_tpd.length], cx

        mov     [edi + alx_tpd.word1], 1 shl TPD_EOP_SHIFT

; Update Producer Index
        mov     eax, [ebx + device.txq_write_idx]
        inc     eax
        and     eax, TX_RING_SIZE - 1
        mov     [ebx + device.txq_write_idx], eax

        mov     edi, [ebx + device.mmio_addr]
        mov     word[edi + ALX_TPD_PRI0_PIDX], ax

; Update stats
        inc     [ebx + device.packets_tx]
        mov     eax, [esi + NET_BUFF.length]
        add     dword[ebx + device.bytes_tx], eax
        adc     dword[ebx + device.bytes_tx + 4], 0

        popf
        xor     eax, eax
        ret

  .fail:
        DEBUGF  2,"Send failed\n"
        invoke  NetFree, [bufferptr]
        popf
        or      eax, -1
        ret

endp



;;;;;;;;;;;;;;;;;;;;;;;
;;                   ;;
;; Interrupt handler ;;
;;                   ;;
;;;;;;;;;;;;;;;;;;;;;;;

align 4
int_handler:

        push    ebx esi edi

        DEBUGF  1,"int\n"

; Find pointer of device wich made IRQ occur

        mov     ecx, [devices]
        test    ecx, ecx
        jz      .nothing
        mov     esi, device_list
  .nextdevice:
        mov     ebx, [esi]
        mov     edi, [ebx + device.mmio_addr]
        mov     eax, [edi + ALX_ISR]
        test    eax, eax
        jnz     .got_it
  .continue:
        add     esi, 4
        dec     ecx
        jnz     .nextdevice
  .nothing:
        pop     edi esi ebx
        xor     eax, eax

        ret                             ; If no device was found, abort

; At this point, test for all possible reasons, and handle accordingly

  .got_it:
        or      eax, ALX_ISR_DIS
        mov     [edi + ALX_ISR], eax    ; ACK interrupt
        DEBUGF  1,"Device: %x Status: %x\n", ebx, eax

        test    eax, ALX_ISR_TX_Q0
        jz      .no_tx
        DEBUGF  1,"TX interrupt\n"
        pusha
        call    alx_clean_tx_irq
        popa
  .no_tx:

        test    eax, ALX_ISR_RX_Q0
        jz      .no_rx
        DEBUGF  1,"RX interrupt\n"
        pusha
        call    alx_clean_rx_irq
        popa

  .no_rx:
        test    eax, ALX_ISR_PHY
        jz      .no_phy
        DEBUGF  1,"PHY interrupt\n"
        pusha
; TODO: queue link check and disable this interrupt cause meanwhile??
        call    alx_check_link
        popa

  .no_phy:
        mov     dword[edi + ALX_ISR], 0
        pop     edi esi ebx
        ret


align 16
alx_identify_hw:

        cmp     [ebx + device.chip_rev], ALX_REV_C0
        ja      .einval

        mov     [ebx + device.max_dma_chnl], 2
        cmp     [ebx + device.chip_rev], ALX_REV_B0
        jb       @f
        mov     [ebx + device.max_dma_chnl], 4
  @@:
        xor     eax, eax
        ret

  .einval:
        DEBUGF  1, "Invalid revision 0x%x\n", [ebx + device.chip_rev]

        xor     eax, eax
        dec     eax
        ret



align 16
alx_clean_tx_irq:

        mov     eax, [ebx + device.txq_read_idx]
        movzx   ecx, word[edi + ALX_TPD_PRI0_CIDX]

  .loop:
        cmp     eax, ecx
        je      .done

        DEBUGF  1,"Cleaning TX desc %u buffer 0x%x\n", eax, [ebx + device.tpd_ring_virt + eax*4]
        push    eax ecx
        invoke  NetFree, [ebx + device.tpd_ring_virt + eax*4]
        pop     ecx eax

        inc     eax
        and     eax, TX_RING_SIZE-1
        jmp     .loop
  .done:
        mov     [ebx + device.txq_read_idx], eax

        ret


align 16
alx_clean_rx_irq:

        mov     ecx, [ebx + device.rxq_read_idx]
  .loop:
        shl     ecx, 2
        lea     esi, [ebx + device.rrd_ring + ecx*4]
        test    [esi + alx_rrd.word3], 1 shl RRD_UPDATED_SHIFT
        jz      .done
        and     [esi + alx_rrd.word3], not(1 shl RRD_UPDATED_SHIFT)
        DEBUGF  1,"RRD=%u updated\n", [ebx + device.rxq_read_idx]

        mov     eax, [esi + alx_rrd.word0]
        shr     eax, RRD_SI_SHIFT
        and     eax, RRD_SI_MASK
        cmp     eax, [ebx + device.rxq_read_idx]
;        jne     .error
        DEBUGF  1,"RFD=%u\n", eax

        mov     eax, [esi + alx_rrd.word0]
        shr     eax, RRD_NOR_SHIFT
        and     eax, RRD_NOR_MASK
        cmp     eax, 1
;        jne     .error

        mov     eax, [esi + alx_rrd.word3]
;        shr     eax, RRD_PKTLEN_SHIFT
        and     eax, RRD_PKTLEN_MASK
        sub     eax, 4                          ;;;;;
        mov     edx, [ebx + device.rfd_ring_virt + ecx]
        DEBUGF  1,"Received %u bytes in buffer 0x%x\n", eax, edx

        mov     [edx + NET_BUFF.length], eax
        mov     [edx + NET_BUFF.device], ebx
        mov     [edx + NET_BUFF.offset], NET_BUFF.data

; Update stats
        add     dword[ebx + device.bytes_rx], eax
        adc     dword[ebx + device.bytes_rx + 4], 0
        inc     [ebx + device.packets_rx]

; Allocate new descriptor
        push    esi ecx edx
        invoke  NetAlloc, RX_BUFFER_SIZE+NET_BUFF.data
        pop     edx ecx esi
        test    eax, eax
;        jz      .out_of_mem
        mov     [ebx + device.rfd_ring_virt + ecx], eax
        add     eax, NET_BUFF.data
        invoke  GetPhysAddr
        mov     dword[ebx + device.rfd_ring + ecx*2], eax

        push    ecx ebx
        push    .retaddr
        push    edx
        jmp     [EthInput]
  .retaddr:
        pop     ebx ecx

        shr     ecx, 2
        inc     ecx
        and     ecx, RX_RING_SIZE-1
        jmp     .loop

  .done:
        shr     ecx, 2
        mov     [ebx + device.rxq_read_idx], ecx

; Update producer index
        mov     esi, [ebx + device.mmio_addr]
        mov     [esi + ALX_RFD_PIDX], cx

        ret


align 16
; ecx = additional bit flags (ALX_PMCTRL_L0S_EN, ALX_PMCTRL_L1_EN, ALX_PMCTRL_ASPM_FCEN)
alx_enable_aspm:

        DEBUGF  1,"alx_enable_aspm (0x%x)\n", ecx

        mov     esi, [ebx + device.mmio_addr]
        mov     eax, dword[esi + ALX_PMCTRL]

        and     eax, not(ALX_PMCTRL_LCKDET_TIMER_MASK shl ALX_PMCTRL_LCKDET_TIMER_SHIFT)
        or      eax, (ALX_PMCTRL_LCKDET_TIMER_DEF shl ALX_PMCTRL_LCKDET_TIMER_SHIFT)

        or      eax, (ALX_PMCTRL_RCVR_WT_1US or ALX_PMCTRL_L1_CLKSW_EN or ALX_PMCTRL_L1_SRDSRX_PWD)

        and     eax, not(ALX_PMCTRL_L1REQ_TO_MASK shl ALX_PMCTRL_L1REQ_TO_SHIFT)
        or      eax, (ALX_PMCTRL_L1REG_TO_DEF shl ALX_PMCTRL_L1REQ_TO_SHIFT)

        and     eax, not(ALX_PMCTRL_L1_TIMER_MASK shl ALX_PMCTRL_L1_TIMER_SHIFT)
        or      eax, (ALX_PMCTRL_L1_TIMER_16US shl ALX_PMCTRL_L1_TIMER_SHIFT)

        and     eax, not(ALX_PMCTRL_L1_SRDS_EN  or ALX_PMCTRL_L1_SRDSPLL_EN or ALX_PMCTRL_L1_BUFSRX_EN or ALX_PMCTRL_SADLY_EN or ALX_PMCTRL_HOTRST_WTEN or ALX_PMCTRL_L0S_EN  or ALX_PMCTRL_L1_EN  or ALX_PMCTRL_ASPM_FCEN  or ALX_PMCTRL_TXL1_AFTER_L0S  or ALX_PMCTRL_RXL1_AFTER_L0S)

        cmp     [ebx + device.chip_rev], ALX_REV_A1
        ja      @f
        test    [ebx + device.pci_rev], 1
        jz      @f
        or      eax, ALX_PMCTRL_L1_SRDS_EN or ALX_PMCTRL_L1_SRDSPLL_EN
  @@:

        or      eax, ecx
        mov     dword[esi + ALX_PMCTRL], eax

        ret

align 16
alx_reset_mac:

        DEBUGF  1, "reset mac\n"

; disable all interrupts, RXQ/TXQ
        mov     esi, [ebx + device.mmio_addr]
        mov     dword[esi + ALX_MSIX_MASK], 0xffffffff
        mov     dword[esi + ALX_IMR], 0x0
        mov     dword[esi + ALX_ISR], ALX_ISR_DIS

        call    alx_stop_mac

; mac reset workaround
        mov     dword[esi + ALX_RFD_PIDX], 1

; disable l0s/l1 before mac reset on some chips
        cmp     [ebx + device.chip_rev], ALX_REV_A1
        ja      @f
        test    [ebx + device.pci_rev], 1       ; Card reader function? FIXME: according register definitions, this should be bit 1 ISO 0
        jz      @f
        mov     eax, [esi + ALX_PMCTRL]
        mov     edx, eax
        and     eax, not(ALX_PMCTRL_L1_EN or ALX_PMCTRL_L0S_EN)
        mov     [esi + ALX_PMCTRL], eax
  @@:

; reset whole mac safely
        mov     eax, [esi + ALX_MASTER]
        or      eax, ALX_MASTER_DMA_MAC_RST + ALX_MASTER_OOB_DIS
        mov     [esi + ALX_MASTER], eax

; make sure it's real idle
        push    esi ecx edx
        xor     esi, esi
        inc     esi
        invoke  Sleep           ; FIXME
        pop     edx ecx esi

        mov     ecx, ALX_DMA_MAC_RST_TO
  .loop1:
        mov     eax, dword[esi + ALX_RFD_PIDX]
        test    eax, eax
        jz      @f
        dec     ecx
        jnz     .loop1
        jmp     .error
  @@:
  .loop2:
        mov     eax, dword[esi + ALX_MASTER]
        test    eax, ALX_MASTER_DMA_MAC_RST
        jz      @f
        dec     ecx
        jnz     .loop2
        jmp     .error
  @@:

; restore l0s/l1
        cmp     [ebx + device.chip_rev], ALX_REV_A1
        ja      @f
        test    [ebx + device.pci_rev], 1        ; Card reader function? FIXME: according register definitions, this should be bit 1 ISO 0
        jz      @f
        or      eax, ALX_MASTER_PCLKSEL_SRDS
        mov     [esi + ALX_MASTER], eax

        mov     [esi + ALX_PMCTRL], edx
  @@:

        call    alx_reset_osc

; clear Internal OSC settings, switching OSC by hw itself, disable isolate for rev A devices

        mov     eax, [esi + ALX_MISC3]
        and     eax, not (ALX_MISC3_25M_BY_SW)
        or      eax, ALX_MISC3_25M_NOTO_INTNL
        mov     [esi + ALX_MISC3], eax

        mov     eax, [esi + ALX_MISC]
        and     eax, not (ALX_MISC_INTNLOSC_OPEN)

        cmp     [ebx + device.chip_rev], ALX_REV_A1
        ja      @f
        and     eax, not ALX_MISC_ISO_EN
  @@:
        mov     [esi + ALX_MISC], eax

        push    esi
        xor     esi, esi
        inc     esi
        invoke  Sleep           ;; FIXME: udelay(20);
        pop     esi

; driver control speed/duplex, hash-alg
        mov     eax, [ebx + device.rx_ctrl]
        mov     [esi + ALX_MAC_CTRL], eax

        mov     eax, dword[esi + ALX_SERDES]
        or      eax, ALX_SERDES_MACCLK_SLWDWN or ALX_SERDES_PHYCLK_SLWDWN
        mov     dword[esi + ALX_SERDES], eax

        DEBUGF  1, "OK\n"
        xor     eax, eax
        ret

  .error:
        DEBUGF  1, "error\n"
        xor     eax, eax
        dec     eax
        ret


align 16
alx_reset_phy:

        DEBUGF  1, "Reset phy\n"

        mov     esi, [ebx + device.mmio_addr]

;; TODO

; set phy interrupt mask
        stdcall alx_read_phy_reg, 0, ALX_MII_IER
        or      eax, ALX_IER_LINK_UP or ALX_IER_LINK_DOWN
        stdcall alx_write_phy_reg, 0, ALX_MII_IER , eax

        DEBUGF  1, "OK\n"
        xor     eax, eax
        ret

  .error:
        DEBUGF  1, "error\n"
        xor     eax, eax
        dec     eax
        ret


;align 16
;alx_enable_osc:
;
;        mov     esi, [ebx + device.mmio_addr]
;
;; rising edge
;        mov     eax, dword[esi + ALX_MISC]
;        and     eax, not ALX_MISC_INTNLOSC_OPEN
;        mov     dword[esi + ALX_MISC], eax
;        or      eax, ALX_MISC_INTNLOSC_OPEN
;        mov     dword[esi + ALX_MISC], eax
;
;        ret


align 16
alx_reset_osc:

        mov     esi, [ebx + device.mmio_addr]

; clear Internal OSC settings, switching OSC by hw itself
        mov     eax, dword[esi + ALX_MISC3]
        and     eax, not ALX_MISC3_25M_BY_SW
        or      eax, ALX_MISC3_25M_NOTO_INTNL
        mov     dword[esi + ALX_MISC3], eax

; clk from chipset may be unstable 1s after de-assert of
; PERST, driver need re-calibrate before enter Sleep for WoL
        mov     eax, dword[esi + ALX_MISC]
        cmp     [ebx + device.chip_rev], ALX_REV_B0
        jb      .rev_a

; restore over current protection def-val, this val could be reset by MAC-RST
        and     eax, not (ALX_MISC_PSW_OCP_MASK shl ALX_MISC_PSW_OCP_SHIFT)
        or      eax, ALX_MISC_PSW_OCP_DEF shl ALX_MISC_PSW_OCP_SHIFT
; a 0->1 change will update the internal val of osc
        and     eax, not ALX_MISC_INTNLOSC_OPEN
        mov     dword[esi + ALX_MISC], eax
        or      eax, ALX_MISC_INTNLOSC_OPEN
        mov     dword[esi + ALX_MISC], eax

; hw will automatically dis OSC after cab
        mov     eax, dword[esi + ALX_MSIC2]
        and     eax, not ALX_MSIC2_CALB_START
        mov     dword[esi + ALX_MSIC2], eax
        or      eax, ALX_MSIC2_CALB_START
        mov     dword[esi + ALX_MSIC2], eax

        push    esi ecx
        xor     esi, esi
        inc     esi
        invoke  Sleep           ;; FIXME: udelay(20)
        pop     ecx esi

        ret

  .rev_a:

;  disable isolate for rev A devices
        and     eax, not ( ALX_MISC_ISO_EN)
        or      eax, ALX_MISC_INTNLOSC_OPEN
        mov     dword[esi + ALX_MISC], eax
        and     eax, not ALX_MISC_INTNLOSC_OPEN
        mov     dword[esi + ALX_MISC], eax

        push    esi ecx
        xor     esi, esi
        inc     esi
        invoke  Sleep           ;; FIXME: udelay(20)
        pop     ecx esi

        ret

align 16
alx_read_macaddr:

        mov     esi, [ebx + device.mmio_addr]
        mov     eax, dword[esi + ALX_STAD0]
        bswap   eax
        mov     dword[ebx + device.mac + 2], eax
        mov     ax, word[esi + ALX_STAD1]
        xchg    al, ah
        mov     word[ebx + device.mac], ax

        DEBUGF  1,"MAC = %x-%x-%x-%x-%x-%x\n",\
        [ebx + device.mac+0]:2, [ebx + device.mac+1]:2, [ebx + device.mac+2]:2, [ebx + device.mac+3]:2, [ebx + device.mac+4]:2, [ebx + device.mac+5]:2

; check if it is a valid MAC
        cmp     dword[ebx + device.mac], 0x0
        jne     @f
        cmp     word[ebx + device.mac + 4], 0x0
        je      .invalid
  @@:
        cmp     dword[ebx + device.mac], 0xffffffff
        jne     @f
        cmp     word[ebx + device.mac + 4], 0xffff
        je      .invalid
  @@:
; TODO: check if it's not a multicast
        xor     eax, eax
        ret

  .invalid:
        DEBUGF  1, "Invalid MAC!\n"
        xor     eax, eax
        inc     eax
        ret


align 16
alx_get_perm_macaddr:

; try to get it from register first
        call    alx_read_macaddr
        test    eax, eax
        jz      .done

; try to load from efuse
        mov     esi, [ebx + device.mmio_addr]
        mov     ecx, ALX_SLD_MAX_TO
  .loop1:
        mov     eax, dword[esi + ALX_SLD]
        test    eax, ALX_SLD_STAT or ALX_SLD_START
        jz      @f

        dec     ecx
        jz      .error

        push    esi ecx
        xor     esi, esi
        inc     esi
        invoke  Sleep
        pop     ecx esi
        jmp     .loop1
  @@:
        or      eax, ALX_SLD_START
        mov     dword[esi + ALX_SLD], eax

        mov     ecx, ALX_SLD_MAX_TO
  .loop2:
        mov     eax, dword[esi + ALX_SLD]
        test    eax, ALX_SLD_START
        jz      @f

        dec     ecx
        jz      .error

        push    esi ecx
        xor     esi, esi
        inc     esi
        invoke  Sleep
        pop     ecx esi
        jmp     .loop2
  @@:

        call    alx_read_macaddr
        test    eax, eax
        jz      .done

; try to load from flash/eeprom (if present)
        set_io  [ebx + device.io_addr], 0
        set_io  [ebx + device.io_addr], ALX_EFLD
        in      eax, dx
        test    eax, ALX_EFLD_F_EXIST or ALX_EFLD_E_EXIST
        jz      .error

        mov     ecx, ALX_SLD_MAX_TO
  .loop3:
        in      eax, dx
        test    eax, ALX_EFLD_STAT or ALX_EFLD_START
        jz      @f

        dec     ecx
        jz      .error

        push    esi edx ecx
        xor     esi, esi
        inc     esi
        invoke  Sleep
        pop     ecx edx esi
        jmp     .loop3
  @@:
        or      eax, ALX_EFLD_START
        out     dx, eax

        mov     ecx, ALX_SLD_MAX_TO
  .loop4:
        in      eax, dx
        test    eax, ALX_EFLD_START
        jz      @f

        dec     ecx
        jz      .error

        push    esi edx ecx
        xor     esi, esi
        inc     esi
        invoke  Sleep
        pop     ecx edx esi
        jmp     .loop4
  @@:

        call    alx_read_macaddr
        test    eax, eax
        jz      .done

  .error:
        DEBUGF  1, "error obtaining MAC\n"
        xor     eax, eax
        dec     eax
        ret

  .done:
        DEBUGF  1, "MAC OK\n"
        xor     eax, eax
        ret

align 16
alx_stop_mac:

        DEBUGF  1,"alx_stop_mac\n"

        mov     esi, [ebx + device.mmio_addr]

        mov     eax, dword[esi + ALX_RXQ0]
        and     eax, not ALX_RXQ0_EN
        mov     dword[esi + ALX_RXQ0], eax

        mov     eax, dword[esi + ALX_TXQ0]
        and     eax, not ALX_TXQ0_EN
        mov     dword[esi + ALX_TXQ0], eax

        push    esi
        xor     esi, esi
        inc     esi
        invoke  Sleep   ; FIME: udelay(40)
        pop     esi

        mov     eax, [ebx + device.rx_ctrl]
        and     eax, not(ALX_MAC_CTRL_TX_EN or ALX_MAC_CTRL_RX_EN)
        mov     [ebx + device.rx_ctrl], eax
        mov     [esi + ALX_MAC_CTRL], eax

        mov     ecx, ALX_DMA_MAC_RST_TO
  .loop:
        mov     eax, [esi + ALX_MAC_STS]
        test    eax, ALX_MAC_STS_IDLE
        jz      .done

        push    esi
        xor     esi, esi
        inc     esi
        invoke  Sleep   ; FIME: udelay(10)
        pop     esi

        dec     ecx
        jnz     .loop

        DEBUGF  1,"alx_stop_mac timeout!\n"
        xor     eax, eax
        dec     eax
        ret

  .done:
        DEBUGF  1,"alx_stop_mac ok\n"
        xor     eax, eax
        ret


align 16
alx_start_mac:

        DEBUGF  1,"alx_start_mac\n"

        mov     esi, [ebx + device.mmio_addr]

        mov     eax, dword[esi + ALX_RXQ0]
        or      eax, ALX_RXQ0_EN
        mov     dword[esi + ALX_RXQ0], eax

        mov     eax, dword[esi + ALX_TXQ0]
        or      eax, ALX_TXQ0_EN
        mov     dword[esi + ALX_TXQ0], eax

        mov     eax, [ebx + device.rx_ctrl]
        or      eax, ALX_MAC_CTRL_TX_EN or ALX_MAC_CTRL_RX_EN
        and     eax, not ALX_MAC_CTRL_FULLD
        test    [ebx + device.state], ETH_LINK_FD
        jz      @f
        or      eax, ALX_MAC_CTRL_FULLD
  @@:
        and     eax, not (ALX_MAC_CTRL_SPEED_MASK shl ALX_MAC_CTRL_SPEED_SHIFT)
        test    [ebx + device.state], ETH_LINK_1G
        jz      .10_100
        or      eax, (ALX_MAC_CTRL_SPEED_1000 shl ALX_MAC_CTRL_SPEED_SHIFT)

        mov     [ebx + device.rx_ctrl], eax
        mov     [esi + ALX_MAC_CTRL], eax

        ret

  .10_100:
        or      eax, (ALX_MAC_CTRL_SPEED_10_100 shl ALX_MAC_CTRL_SPEED_SHIFT)

        mov     [ebx + device.rx_ctrl], eax
        mov     [esi + ALX_MAC_CTRL], eax

        ret



align 16
alx_init_ring_ptrs:

        DEBUGF  1,"alx_init_ring_ptrs\n"

        mov     esi, [ebx + device.mmio_addr]

; Receive descriptors
        mov     [ebx + device.rxq_read_idx], 0
        mov     [ebx + device.rxq_write_idx], 0
;        mov     [ebx + device.rxq_rrd_read_idx], 0

        mov     dword[esi + ALX_RX_BASE_ADDR_HI], 0

        lea     eax, [ebx + device.rrd_ring]
        invoke  GetPhysAddr
        mov     dword[esi + ALX_RRD_ADDR_LO], eax
        mov     dword[esi + ALX_RRD_RING_SZ], RX_RING_SIZE

        lea     eax, [ebx + device.rfd_ring]
        invoke  GetPhysAddr
        mov     dword[esi + ALX_RFD_ADDR_LO], eax
        mov     dword[esi + ALX_RFD_RING_SZ], RX_RING_SIZE
        mov     dword[esi + ALX_RFD_BUF_SZ], RX_BUFFER_SIZE

; Transmit descriptors
        mov     [ebx + device.txq_read_idx], 0
        mov     [ebx + device.txq_write_idx], 0

        mov     dword[esi + ALX_TX_BASE_ADDR_HI], 0

        lea     eax, [ebx + device.tpd_ring]
        invoke  GetPhysAddr
        mov     dword[esi + ALX_TPD_PRI0_ADDR_LO], eax
        mov     dword[esi + ALX_TPD_RING_SZ], TX_RING_SIZE

; Load these pointers into the chip
        mov     dword[esi + ALX_SRAM9], ALX_SRAM_LOAD_PTR

        xor     eax, eax

        ret


align 16
alx_alloc_descriptors:

        DEBUGF  1,"alx_alloc_descriptors\n"

; physical tx/rx ring descriptors

;        alx->descmem.size = sizeof.tx_desc * TX_RING_SIZE + sizeof.rx_desc * RX_RING_SIZE + sizeof(struct alx_rfd) * RX_RING_SIZE;
;        alx->descmem.virt = dma_zalloc_coherent(&alx->hw.pdev->dev, alx->descmem.size, &alx->descmem.dma, GFP_KERNEL);
;        if (!alx->descmem.virt)
;                goto out_free;
;
;        alx->txq.tpd = (void *)alx->descmem.virt;
;        alx->txq.tpd_dma = alx->descmem.dma;

; alignment requirement for next block
;        BUILD_BUG_ON(tx_desc.sizeof % 8);
;
;        alx->rxq.rrd = (void *)((u8 *)alx->descmem.virt + tx_desc.sizeof * TX_RING_SIZE);
;        alx->rxq.rrd_dma = alx->descmem.dma + sizeof.tx_desc * TX_RING_SIZE;
;
; alignment requirement for next block
;        BUILD_BUG_ON(rx_desc.sizeof % 8);
;
;        alx->rxq.rfd = (void *)((u8 *)alx->descmem.virt + sizeof.tx_desx * TX_RING_SIZE + sizeof.rx_desc * RX_RING_SIZE);
;        alx->rxq.rfd_dma = alx->descmem.dma + sizeof.tx_desc * TX_RING_SIZE + sizeof.rx_desc * RX_RING_SIZE;

        xor     eax, eax
        ret


align 16
alx_alloc_rings:

        DEBUGF  1,"alx_alloc_rings\n"

        call    alx_alloc_descriptors
        test    eax, eax
        jnz     .ret_err

        and     [ebx + device.int_mask], not ALX_ISR_ALL_QUEUES
        or      [ebx + device.int_mask], ALX_ISR_TX_Q0 or ALX_ISR_RX_Q0
;        netif_napi_add(alx->dev, &alx->napi, alx_poll, 64);

        call    alx_reinit_rings
  .ret_err:
        ret


align 16
alx_reinit_rings:

        DEBUGF  1,"alx_reinit_rings\n"

        call    alx_free_rx_ring
        call    alx_init_ring_ptrs
        call    alx_refill_rx_ring

        ret


align 16
alx_refill_rx_ring:

        DEBUGF  1,"alx_refill_rx_ring\n"

        mov     ecx, [ebx + device.rxq_write_idx]
  .loop:
        cmp     dword[ebx + device.rfd_ring+ecx*sizeof.alx_rfd + alx_rfd.addr_l], 0
        jne     .done

        invoke  NetAlloc, NET_BUFF.data+RX_BUFFER_SIZE
        test    eax, eax
        jz      .done
        mov     [ebx + device.rfd_ring_virt + ecx*4], eax
        add     eax, NET_BUFF.data
        invoke  GetPhysAddr
        mov     dword[ebx + device.rfd_ring+ecx*sizeof.alx_rfd + alx_rfd.addr_l], eax
        mov     dword[ebx + device.rfd_ring+ecx*sizeof.alx_rfd + alx_rfd.addr_h], 0

        mov     eax, ecx
        inc     ecx
        and     ecx, RX_RING_SIZE - 1

        cmp     ecx, [ebx + device.rxq_read_idx]
        jne     .loop

        mov     ecx, eax

  .done:
        cmp     ecx, [ebx + device.rxq_write_idx]
        je      .none

        mov     [ebx + device.rxq_write_idx], ecx
        mov     esi, [ebx + device.mmio_addr]
        mov     [esi + ALX_RFD_PIDX], cx

  .none:
        xor     eax, eax

        ret


align 16
alx_free_rx_ring:

        DEBUGF  1,"alx_free_rx_ring\n"

        xor     ecx, ecx
  .loop:
        mov     eax, [ebx + device.rfd_ring_virt + ecx*4]
        test    eax, eax
        jz      .next

        invoke  NetFree, eax

        xor     eax, eax
        mov     dword[ebx + device.rfd_ring+ecx*sizeof.alx_rfd + alx_rfd.addr_l], eax
        mov     dword[ebx + device.rfd_ring+ecx*sizeof.alx_rfd + alx_rfd.addr_h], eax
        mov     [ebx + device.rfd_ring_virt + ecx*4], eax

  .next:
        inc     ecx
        cmp     ecx, RX_RING_SIZE
        jb      .loop

        ret


align 16
alx_configure:

        DEBUGF  1,"alx_configure\n"

        call    alx_configure_basic
        call    alx_disable_rss
        call    __alx_set_rx_mode

        mov     esi, [ebx + device.mmio_addr]
        mov     eax, [ebx + device.rx_ctrl]
        mov     [esi + ALX_MAC_CTRL], eax

        xor     eax, eax
        ret


align 16
alx_irq_enable:

        DEBUGF  1,"alx_irq_enable\n"

        mov     esi, [ebx + device.mmio_addr]
        mov     dword[esi + ALX_ISR], 0
        mov     eax, [ebx + device.int_mask]
        mov     [esi + ALX_IMR], eax

        call    alx_post_write

        ret

align 16
alx_irq_disable:

        DEBUGF  1,"alx_irq_disable\n"

        mov     esi, [ebx + device.mmio_addr]
        mov     dword[esi + ALX_ISR], ALX_ISR_DIS
        mov     dword[esi + ALX_IMR], 0

        call    alx_post_write

        ret


align 16
alx_post_write:

        push    eax
        mov     esi, [ebx + device.mmio_addr]
        mov     eax, [esi]
        pop     eax

        ret


align 16
alx_configure_basic:

        DEBUGF  1,"alx_configure_basic\n"

        mov     esi, [ebx + device.mmio_addr]

;;;        call    alx_set_macaddr

        mov     dword[esi + ALX_CLK_GATE], ALX_CLK_GATE_ALL

; idle timeout to switch clk_125M

        cmp     [ebx + device.chip_rev], ALX_REV_B0
        jb      @f
        mov     dword[esi + ALX_IDLE_DECISN_TIMER], ALX_IDLE_DECISN_TIMER_DEF
  @@:

        mov     dword[esi + ALX_SMB_TIMER], SMB_TIMER * 500

        mov     eax, [esi + ALX_MASTER]
        or      eax, ALX_MASTER_IRQMOD2_EN or ALX_MASTER_IRQMOD1_EN or ALX_MASTER_SYSALVTIMER_EN
        mov     [esi + ALX_MASTER], eax

        mov     dword[esi + ALX_IRQ_MODU_TIMER], (IMT / 2) shl ALX_IRQ_MODU_TIMER1_SHIFT

; intr re-trig timeout
        mov     dword[esi + ALX_INT_RETRIG], ALX_INT_RETRIG_TO

; tpd threshold to trig int
        mov     dword[esi + ALX_TINT_TPD_THRSHLD], ITH_TPD
        mov     dword[esi + ALX_TINT_TIMER], IMT

        mov     dword[esi + ALX_MTU], RX_BUFFER_SIZE + 8 ;;;;

        mov     dword[esi + ALX_TXQ1], (((RX_BUFFER_SIZE + 8 + 7) shr 3) or ALX_TXQ1_ERRLGPKT_DROP_EN)

; rxq, flow control

; TODO set ALX_RXQ2

; RXQ0
        mov     eax, (ALX_RXQ0_NUM_RFD_PREF_DEF shl ALX_RXQ0_NUM_RFD_PREF_SHIFT) \
                or (ALX_RXQ0_RSS_MODE_DIS shl ALX_RXQ0_RSS_MODE_SHIFT) \
                or (ALX_RXQ0_IDT_TBL_SIZE_DEF shl ALX_RXQ0_IDT_TBL_SIZE_SHIFT) \
                or ALX_RXQ0_RSS_HSTYP_ALL or ALX_RXQ0_RSS_HASH_EN or ALX_RXQ0_IPV6_PARSE_EN

        test    [ebx + device.pci_did], 1       ;;; FIXME: is gigabit device?
        jz      @f
        or      eax, ALX_RXQ0_ASPM_THRESH_100M shl ALX_RXQ0_ASPM_THRESH_SHIFT
  @@:
        mov     dword[esi + ALX_RXQ0], eax

; TODO: DMA
;        mov     eax, [esi + ALX_DMA]    ; read and ignore?
;        mov     eax, [ebx + device.dma_chnl]
;        dec     eax
;        shl     eax, ALX_DMA_RCHNL_SEL_SHIFT
;        or      eax, (ALX_DMA_RORDER_MODE_OUT shl ALX_DMA_RORDER_MODE_SHIFT) \
;                or ALX_DMA_RREQ_PRI_DATA \
;                or (max_payload shl ALX_DMA_RREQ_BLEN_SHIFT ) \
;                or (ALX_DMA_WDLY_CNT_DEF shl ALX_DMA_WDLY_CNT_SHIFT ) \
;                or (ALX_DMA_RDLY_CNT_DEF shl ALX_DMA_RDLY_CNT_SHIFT )
;        mov     [esi + ALX_DMA], eax


; default multi-tx-q weights
        mov      eax, (ALX_WRR_PRI_RESTRICT_NONE shl ALX_WRR_PRI_SHIFT) \
                 or (4 shl ALX_WRR_PRI0_SHIFT) \
                 or (4 shl ALX_WRR_PRI1_SHIFT) \
                 or (4 shl ALX_WRR_PRI2_SHIFT) \
                 or (4 shl ALX_WRR_PRI3_SHIFT)
        mov      [esi + ALX_WRR], eax

        ret


align 16
alx_disable_rss:

        DEBUGF  1,"alx_disable_rss\n"

        mov     esi, [ebx + device.mmio_addr]

        mov     eax, [esi + ALX_RXQ0]
        and     eax, not (ALX_RXQ0_RSS_HASH_EN)
        mov     [esi + ALX_RXQ0] , eax

        ret

align 16
__alx_set_rx_mode:

        DEBUGF  1,"__alx_set_rx_mode\n"

        mov     esi, [ebx + device.mmio_addr]

;        if (!(netdev->flags & IFF_ALLMULTI)) {
;                netdev_for_each_mc_addr(ha, netdev)
;                        alx_add_mc_addr(hw, ha->addr, mc_hash);
;
;                alx_write_mem32(hw, ALX_HASH_TBL0, mc_hash[0]);
;                alx_write_mem32(hw, ALX_HASH_TBL1, mc_hash[1]);
;        }

        mov     eax, [ebx + device.rx_ctrl]
        or      eax, ALX_MAC_CTRL_PROMISC_EN or ALX_MAC_CTRL_MULTIALL_EN
        mov     [ebx + device.rx_ctrl], eax
        mov     dword[esi + ALX_MAC_CTRL], eax

        ret


align 16
alx_check_link:

        call    alx_clear_phy_intr

        mov     edx, [ebx + device.state]

        call    alx_get_phy_link
        cmp     eax, 0
        jl      .reset

        mov     esi, [ebx + device.mmio_addr]

        spin_lock_irqsave
        or      [ebx + device.int_mask], ALX_ISR_PHY
        mov     eax, [ebx + device.int_mask]
        mov     [esi + ALX_IMR], eax
        spin_unlock_irqrestore

        cmp     edx, [ebx + device.state]
        je      .no_change

        cmp     [ebx + device.state], ETH_LINK_DOWN
        je      .link_down

        call    alx_post_phy_link
        mov     ecx, (ALX_PMCTRL_L0S_EN or ALX_PMCTRL_L1_EN or ALX_PMCTRL_ASPM_FCEN)
        call    alx_enable_aspm
        call    alx_start_mac

        invoke  NetLinkChanged

        ret

  .no_change:
        DEBUGF  1, "link state unchanged\n"

        ret

  .link_down:
; Link is now down

        call    alx_reset_mac
        test    eax, eax
        jnz     .reset

        call    alx_irq_disable

; MAC reset causes all HW settings to be lost, restore all
        call    alx_reinit_rings
        test    eax, eax
        jnz     .reset

        call    alx_configure
        mov     ecx, (ALX_PMCTRL_L1_EN or ALX_PMCTRL_ASPM_FCEN)
        call    alx_enable_aspm
        call    alx_post_phy_link
        call    alx_irq_enable

        invoke  NetLinkChanged

        ret

  .reset:
        DEBUGF  1, "alx_schedule_reset"
;;;        call    alx_schedule_reset

        ret


align 16
alx_post_phy_link:

        cmp     [ebx + device.chip_rev], ALX_REV_B0
        ja      .done

        cmp     [ebx + device.state], ETH_LINK_UNKNOWN
        jae     @f

; TODO
;        stdcall alx_read_phy_ext, ALX_MIIEXT_AFE, ALX_MIIEXT_ANEG
;        and     eax, not (ALX_AFE_10BT_100M_TH)
;        stdcall alx_write_phy_ext, ALX_MIIEXT_AFE, ALX_MIIEXT_ANEG, eax

        ret
  @@:

  .done:

        ret


align 16
alx_clear_phy_intr:

        stdcall alx_read_phy_reg, 0, ALX_MII_ISR

        ret


align 16
alx_get_phy_link:

        DEBUGF  1,"alx_get_phy_link\n"

        stdcall alx_read_phy_reg, 0, MII_BMSR
        stdcall alx_read_phy_reg, 0, MII_BMSR

        mov     [ebx + device.state], ETH_LINK_DOWN

        test    ax, BMSR_LSTATUS
        jnz     @f
        DEBUGF  1,"link is down\n"
        xor     eax, eax
        ret
  @@:
        stdcall alx_read_phy_reg, 0, ALX_MII_GIGA_PSSR
        test    ax, ALX_GIGA_PSSR_SPD_DPLX_RESOLVED
        jz      .wrong_speed

        DEBUGF  1,"link is up\n"

        test    ax, ALX_GIGA_PSSR_DPLX
        jz      @f
        or      [ebx + device.state], ETH_LINK_FD
        DEBUGF  1,"full duplex\n"
  @@:

        and     ax, ALX_GIGA_PSSR_SPEED
        cmp     ax, ALX_GIGA_PSSR_1000MBS
        jne     @f
        or      [ebx + device.state], ETH_LINK_1G
        DEBUGF  1,"1 gigabit\n"
        ret

  @@:
        cmp     ax, ALX_GIGA_PSSR_100MBS
        jne     @f
        or      [ebx + device.state], ETH_LINK_100M
        DEBUGF  1,"100 mbit\n"
        ret

  @@:
        cmp     ax, ALX_GIGA_PSSR_10MBS
        jne     @f
        or      [ebx + device.state], ETH_LINK_10M
        DEBUGF  1,"10 mbit\n"
        ret

  @@:
        mov     [ebx + device.state], ETH_LINK_UNKNOWN
        DEBUGF  1,"speed unknown\n"
        ret

  .wrong_speed:
        DEBUGF  1,"wrong speed\n"
        xor     eax, eax
        dec     eax
        ret




align 16
proc  alx_read_phy_reg stdcall, phy_addr:dword, reg:dword

; FIXME: Only internal PHY for now, fixed clock

        DEBUGF  1,"PHY read, addr=0x%x reg=0x%x\n", [phy_addr]:8, [reg]:8

        mov     esi, [ebx + device.mmio_addr]

        mov     eax, [reg]
        shl     eax, ALX_MDIO_REG_SHIFT
        or      eax, ALX_MDIO_SPRES_PRMBL or (ALX_MDIO_CLK_SEL_25MD4 shl ALX_MDIO_CLK_SEL_SHIFT) or ALX_MDIO_START or ALX_MDIO_OP_READ
        mov     dword[esi + ALX_MDIO], eax

        mov     ecx, ALX_MDIO_MAX_AC_TO
  .loop:
        mov     eax, dword[esi + ALX_MDIO]
        test    eax, ALX_MDIO_BUSY
        jz      .ready

        push    esi ecx
        xor     esi, esi
        inc     esi
        invoke  Sleep           ;; FIXME: udelay(10)
        pop     ecx esi

        dec     ecx
        jnz     .loop

        DEBUGF  1,"PHY read timeout!\n"
        xor     eax, eax
        dec     eax
        ret

  .ready:
;        shr     eax, ALX_MDIO_DATA_SHIFT
        and     eax, ALX_MDIO_DATA_MASK

        DEBUGF  1,"PHY read, val=0x%x\n", eax:4

        ret

endp




align 16
proc  alx_write_phy_reg stdcall, phy_addr:dword, reg:dword, val:dword

; FIXME: Only internal PHY for now, fixed clock

        DEBUGF  1,"PHY write, addr=0x%x reg=0x%x, data=0x%x\n", [phy_addr]:8, [reg]:8, [val]:8

        mov     esi, [ebx + device.mmio_addr]

        mov     eax, [reg]
        shl     eax, ALX_MDIO_REG_SHIFT
        mov     ax, word[val]                   ; data must be in 16 lower bits :)
        or      eax, ALX_MDIO_SPRES_PRMBL or (ALX_MDIO_CLK_SEL_25MD4 shl ALX_MDIO_CLK_SEL_SHIFT) or ALX_MDIO_START
        mov     dword[esi + ALX_MDIO], eax

        mov     ecx, ALX_MDIO_MAX_AC_TO
  .loop:
        mov     eax, dword[esi + ALX_MDIO]
        test    eax, ALX_MDIO_BUSY
        jz      .ready

        push    esi ecx
        xor     esi, esi
        inc     esi
        invoke  Sleep           ;; FIXME: udelay(10)
        pop     ecx esi

        dec     ecx
        jnz     .loop

        DEBUGF  1,"PHY write timeout!\n"
        xor     eax, eax
        dec     eax
        ret

  .ready:
        DEBUGF  1,"PHY write OK\n"
        xor     eax, eax

        ret
endp

align 16
alx_request_irq:

        DEBUGF  1,"Request IRQ\n"

        mov     esi, [ebx + device.mmio_addr]

; Only legacy interrupts supported for now.
        mov     dword[esi + ALX_MSI_RETRANS_TIMER], 0

        ret


; End of code

data fixups
end data

include '../peimport.inc'

my_service      db 'AR81XX',0                    ; max 16 chars include zero


chiplist:
                dd (ALX_DEV_ID_AR8131 shl 16) or ALX_VEN_ID, ar8131_sz
                dd (ALX_DEV_ID_AR8161 shl 16) or ALX_VEN_ID, ar8161_sz
                dd (ALX_DEV_ID_AR8162 shl 16) or ALX_VEN_ID, ar8162_sz
                dd (ALX_DEV_ID_AR8171 shl 16) or ALX_VEN_ID, ar8171_sz
                dd (ALX_DEV_ID_AR8172 shl 16) or ALX_VEN_ID, ar8172_sz
                dd (ALX_DEV_ID_E2200 shl 16) or ALX_VEN_ID, e2200_sz
                dd (ALX_DEV_ID_E2400 shl 16) or ALX_VEN_ID, e2400_sz
                dd (ALX_DEV_ID_E2500 shl 16) or ALX_VEN_ID, e2500_sz
                dd 0

ar8131_sz       db "AR8131", 0
ar8161_sz       db "AR8161", 0
ar8162_sz       db "AR8162", 0
ar8171_sz       db "QCA8171", 0
ar8172_sz       db "QCA8172", 0
e2200_sz        db "Killer E2200", 0
e2400_sz        db "Killer E2400", 0
e2500_sz        db "Killer E2500", 0

include_debug_strings

align 4
devices         dd 0
device_list     rd MAX_DEVICES                  ; This list contains all pointers to device structures the driver is handling