/* Copyright (C) 2007  One Stop Systems
 * Copyright (C) 2003-2005  SBE, Inc.
 *
 *   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.
 */

#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt

#include <linux/netdevice.h>
#include <linux/hdlc.h>
#include <linux/if_arp.h>
#include <asm/uaccess.h>
#include <linux/rtnetlink.h>
#include <linux/pci.h>
#include "pmcc4_sysdep.h"
#include "sbecom_inline_linux.h"
#include "libsbew.h"
#include "pmcc4_private.h"
#include "pmcc4.h"
#include "pmcc4_ioctls.h"
#include "pmc93x6_eeprom.h"
#ifdef CONFIG_PROC_FS
#include "sbeproc.h"
#endif

#ifdef SBE_INCLUDE_SYMBOLS
#define STATIC
#else
#define STATIC  static
#endif

extern int  log_level;
extern int  error_flag;
extern int  drvr_state;

/* forward references */
void        c4_stopwd (ci_t *);
struct net_device * __init c4_add_dev (hdw_info_t *, int, unsigned long, unsigned long, int, int);


struct s_hdw_info hdw_info[MAX_BOARDS];


void        __init
show_two (hdw_info_t * hi, int brdno)
{
    ci_t       *ci;
    struct pci_dev *pdev;
    char       *bid;
    char       *bp, banner[80];
    char        sn[6];

    bp = banner;
    memset (banner, 0, 80);         /* clear print buffer */

    ci = (ci_t *)(netdev_priv(hi->ndev));
    bid = sbeid_get_bdname (ci);
    switch (hi->promfmt)
    {
    case PROM_FORMAT_TYPE1:
        memcpy (sn, (FLD_TYPE1 *) (hi->mfg_info.pft1.Serial), 6);
        break;
    case PROM_FORMAT_TYPE2:
        memcpy (sn, (FLD_TYPE2 *) (hi->mfg_info.pft2.Serial), 6);
        break;
    default:
        memset (sn, 0, 6);
        break;
    }

    sprintf (banner, "%s: %s  S/N %06X, MUSYCC Rev %02X",
             hi->devname, bid,
             ((sn[3] << 16) & 0xff0000) |
              ((sn[4] << 8) & 0x00ff00) |
              (sn[5] & 0x0000ff),
             (u_int8_t) hi->revid[0]);

    pr_info("%s\n", banner);

    pdev = hi->pdev[0];
    pr_info("%s: %s at v/p=%lx/%lx (%02x:%02x.%x) irq %d\n",
            hi->devname, "MUSYCC",
            (unsigned long) hi->addr_mapped[0], hi->addr[0],
            hi->pci_busno, (u_int8_t) PCI_SLOT (pdev->devfn),
            (u_int8_t) PCI_FUNC (pdev->devfn), pdev->irq);

    pdev = hi->pdev[1];
    pr_info("%s: %s at v/p=%lx/%lx (%02x:%02x.%x) irq %d\n",
            hi->devname, "EBUS  ",
            (unsigned long) hi->addr_mapped[1], hi->addr[1],
            hi->pci_busno, (u_int8_t) PCI_SLOT (pdev->devfn),
            (u_int8_t) PCI_FUNC (pdev->devfn), pdev->irq);
}


void        __init
hdw_sn_get (hdw_info_t * hi, int brdno)
{
    /* obtain hardware EEPROM information */
    long        addr;

    addr = (long) hi->addr_mapped[1] + EEPROM_OFFSET;

    /* read EEPROM with largest known format size... */
    pmc_eeprom_read_buffer (addr, 0, (char *) hi->mfg_info.data, sizeof (FLD_TYPE2));

#if 0
    {
        unsigned char *ucp = (unsigned char *) &hi->mfg_info.data;

        pr_info("eeprom[00]:  %02x %02x %02x %02x  %02x %02x %02x %02x\n",
                *(ucp + 0), *(ucp + 1), *(ucp + 2), *(ucp + 3), *(ucp + 4), *(ucp + 5), *(ucp + 6), *(ucp + 7));
        pr_info("eeprom[08]:  %02x %02x %02x %02x  %02x %02x %02x %02x\n",
                *(ucp + 8), *(ucp + 9), *(ucp + 10), *(ucp + 11), *(ucp + 12), *(ucp + 13), *(ucp + 14), *(ucp + 15));
        pr_info("eeprom[16]:  %02x %02x %02x %02x  %02x %02x %02x %02x\n",
                *(ucp + 16), *(ucp + 17), *(ucp + 18), *(ucp + 19), *(ucp + 20), *(ucp + 21), *(ucp + 22), *(ucp + 23));
        pr_info("eeprom[24]:  %02x %02x %02x %02x  %02x %02x %02x %02x\n",
                *(ucp + 24), *(ucp + 25), *(ucp + 26), *(ucp + 27), *(ucp + 28), *(ucp + 29), *(ucp + 30), *(ucp + 31));
        pr_info("eeprom[32]:  %02x %02x %02x %02x  %02x %02x %02x %02x\n",
                *(ucp + 32), *(ucp + 33), *(ucp + 34), *(ucp + 35), *(ucp + 36), *(ucp + 37), *(ucp + 38), *(ucp + 39));
        pr_info("eeprom[40]:  %02x %02x %02x %02x  %02x %02x %02x %02x\n",
                *(ucp + 40), *(ucp + 41), *(ucp + 42), *(ucp + 43), *(ucp + 44), *(ucp + 45), *(ucp + 46), *(ucp + 47));
    }
#endif
#if 0
    pr_info("sn: %x %x %x %x %x %x\n",
            hi->mfg_info.Serial[0],
            hi->mfg_info.Serial[1],
            hi->mfg_info.Serial[2],
            hi->mfg_info.Serial[3],
            hi->mfg_info.Serial[4],
            hi->mfg_info.Serial[5]);
#endif

    if ((hi->promfmt = pmc_verify_cksum (&hi->mfg_info.data)) == PROM_FORMAT_Unk)
    {
        /* bad crc, data is suspect */
        if (log_level >= LOG_WARN)
            pr_info("%s: EEPROM cksum error\n", hi->devname);
        hi->mfg_info_sts = EEPROM_CRCERR;
    } else
        hi->mfg_info_sts = EEPROM_OK;
}


void        __init
prep_hdw_info (void)
{
    hdw_info_t *hi;
    int         i;

    for (i = 0, hi = hdw_info; i < MAX_BOARDS; i++, hi++)
    {
        hi->pci_busno = 0xff;
        hi->pci_slot = 0xff;
        hi->pci_pin[0] = 0;
        hi->pci_pin[1] = 0;
        hi->ndev = 0;
        hi->addr[0] = 0L;
        hi->addr[1] = 0L;
        hi->addr_mapped[0] = 0L;
        hi->addr_mapped[1] = 0L;
    }
}

void
cleanup_ioremap (void)
{
    hdw_info_t *hi;
    int         i;

    for (i = 0, hi = hdw_info; i < MAX_BOARDS; i++, hi++)
    {
        if (hi->pci_slot == 0xff)
            break;
        if (hi->addr_mapped[0])
        {
            iounmap ((void *) (hi->addr_mapped[0]));
            release_mem_region ((long) hi->addr[0], hi->len[0]);
            hi->addr_mapped[0] = 0;
        }
        if (hi->addr_mapped[1])
        {
            iounmap ((void *) (hi->addr_mapped[1]));
            release_mem_region ((long) hi->addr[1], hi->len[1]);
            hi->addr_mapped[1] = 0;
        }
    }
}


void
cleanup_devs (void)
{
    hdw_info_t *hi;
    int         i;

    for (i = 0, hi = hdw_info; i < MAX_BOARDS; i++, hi++)
    {
        if (hi->pci_slot == 0xff || !hi->ndev)
            break;
        c4_stopwd(netdev_priv(hi->ndev));
#ifdef CONFIG_PROC_FS
        sbecom_proc_brd_cleanup(netdev_priv(hi->ndev));
#endif
        unregister_netdev (hi->ndev);
        free_irq (hi->pdev[0]->irq, hi->ndev);
#ifdef CONFIG_SBE_PMCC4_NCOMM
        free_irq (hi->pdev[1]->irq, hi->ndev);
#endif
        OS_kfree (hi->ndev);
    }
}


STATIC int  __init
c4_hdw_init (struct pci_dev * pdev, int found)
{
    hdw_info_t *hi;
    int         i;
    int         fun, slot;
    unsigned char busno = 0xff;

    /* our MUSYCC chip supports two functions, 0 & 1 */
    if ((fun = PCI_FUNC (pdev->devfn)) > 1)
    {
        pr_warning("unexpected devfun: 0x%x\n", pdev->devfn);
        return 0;
    }
    if (pdev->bus)                  /* obtain bus number */
        busno = pdev->bus->number;
    else
        busno = 0;                  /* default for system PCI inconsistency */
    slot = pdev->devfn & ~0x07;

    /*
     * Functions 0 & 1 for a given board (identified by same bus(busno) and
     * slot(slot)) are placed into the same 'hardware' structure.  The first
     * part of the board's functionality will be placed into an unpopulated
     * element, identified by "slot==(0xff)".  The second part of a board's
     * functionality will match the previously loaded slot/busno.
     */
    for (i = 0, hi = hdw_info; i < MAX_BOARDS; i++, hi++)
    {
        /*
         * match with board's first found interface, otherwise this is first
         * found
         */
        if ((hi->pci_slot == 0xff) ||   /* new board */
            ((hi->pci_slot == slot) && (hi->bus == pdev->bus)))
            break;                  /* found for-loop exit */
    }
    if (i == MAX_BOARDS)            /* no match in above loop means MAX
                                     * exceeded */
    {
        pr_warning("exceeded number of allowed devices (>%d)?\n", MAX_BOARDS);
        return 0;
    }
    if (pdev->bus)
        hi->pci_busno = pdev->bus->number;
    else
        hi->pci_busno = 0;          /* default for system PCI inconsistency */
    hi->pci_slot = slot;
    pci_read_config_byte (pdev, PCI_INTERRUPT_PIN, &hi->pci_pin[fun]);
    pci_read_config_byte (pdev, PCI_REVISION_ID, &hi->revid[fun]);
    hi->bus = pdev->bus;
    hi->addr[fun] = pci_resource_start (pdev, 0);
    hi->len[fun] = pci_resource_end (pdev, 0) - hi->addr[fun] + 1;
    hi->pdev[fun] = pdev;

    {
        /*
         * create device name from module name, plus add the appropriate
         * board number
         */
        char       *cp = hi->devname;

        strcpy (cp, KBUILD_MODNAME);
        cp += strlen (cp);          /* reposition */
        *cp++ = '-';
        *cp++ = '0' + (found / 2);  /* there are two found interfaces per
                                     * board */
        *cp = 0;                    /* termination */
    }

    return 1;
}


status_t    __init
c4hw_attach_all (void)
{
    hdw_info_t *hi;
    struct pci_dev *pdev = NULL;
    int         found = 0, i, j;

    error_flag = 0;
    prep_hdw_info ();
    /*** scan PCI bus for all possible boards */
    while ((pdev = pci_get_device (PCI_VENDOR_ID_CONEXANT,
                                    PCI_DEVICE_ID_CN8474,
                                    pdev)))
    {
        if (c4_hdw_init (pdev, found))
            found++;
    }
    if (!found)
    {
        pr_warning("No boards found\n");
        return ENODEV;
    }
    /* sanity check for consistant hardware found */
    for (i = 0, hi = hdw_info; i < MAX_BOARDS; i++, hi++)
    {
        if (hi->pci_slot != 0xff && (!hi->addr[0] || !hi->addr[1]))
        {
            pr_warning("%s: something very wrong with pci_get_device\n",
                       hi->devname);
            return EIO;
        }
    }
    /* bring board's memory regions on/line */
    for (i = 0, hi = hdw_info; i < MAX_BOARDS; i++, hi++)
    {
        if (hi->pci_slot == 0xff)
            break;
        for (j = 0; j < 2; j++)
        {
            if (request_mem_region (hi->addr[j], hi->len[j], hi->devname) == 0)
            {
                pr_warning("%s: memory in use, addr=0x%lx, len=0x%lx ?\n",
                           hi->devname, hi->addr[j], hi->len[j]);
                cleanup_ioremap ();
                return ENOMEM;
            }
            hi->addr_mapped[j] = (unsigned long) ioremap (hi->addr[j], hi->len[j]);
            if (!hi->addr_mapped[j])
            {
                pr_warning("%s: ioremap fails, addr=0x%lx, len=0x%lx ?\n",
                           hi->devname, hi->addr[j], hi->len[j]);
                cleanup_ioremap ();
                return ENOMEM;
            }
#ifdef SBE_MAP_DEBUG
            pr_warning("%s: io remapped from phys %x to virt %x\n",
                       hi->devname, (u_int32_t) hi->addr[j], (u_int32_t) hi->addr_mapped[j]);
#endif
        }
    }

    drvr_state = SBE_DRVR_AVAILABLE;

    /* Have now memory mapped all boards.  Now allow board's access to system */
    for (i = 0, hi = hdw_info; i < MAX_BOARDS; i++, hi++)
    {
        if (hi->pci_slot == 0xff)
            break;
        if (pci_enable_device (hi->pdev[0]) ||
            pci_enable_device (hi->pdev[1]))
        {
            drvr_state = SBE_DRVR_DOWN;
            pr_warning("%s: failed to enable card %d slot %d\n",
                       hi->devname, i, hi->pci_slot);
            cleanup_devs ();
            cleanup_ioremap ();
            return EIO;
        }
        pci_set_master (hi->pdev[0]);
        pci_set_master (hi->pdev[1]);
        if (!(hi->ndev = c4_add_dev (hi, i, (long) hi->addr_mapped[0],
                                     (long) hi->addr_mapped[1],
                                     hi->pdev[0]->irq,
                                     hi->pdev[1]->irq)))
        {
            drvr_state = SBE_DRVR_DOWN;
            cleanup_ioremap ();
            /* NOTE: c4_add_dev() does its own device cleanup */
#if 0
            cleanup_devs ();
#endif
            return error_flag;      /* error_flag set w/in add_dev() */
        }
        show_two (hi, i);           /* displays found information */
    }
    return 0;
}

/***  End-of-File  ***/
