/*
 * Copyright (c) 2006 Advanced Micro Devices, Inc.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 *
 * Neither the name of the Advanced Micro Devices, Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from this
 * software without specific prior written permission.
 */

 /*
  * Cimarron initialization routines.  These routines detect a Geode LX and
  * read all hardware base addresses.
  */

CIMARRON_STATIC unsigned long init_video_base = 0x80000900;

/*---------------------------------------------------------------------------
 * init_detect_cpu
 *
 * This routine verifies that a Geode LX is present and returns the processor
 * revision ID.  For compatibility, this routine can also detect a Redcloud
 * processor.
 *     bits[24:16] = minor version
 *     bits[15:8]  = major version
 *     bits[7:0]   = type (1 = Geode GX, 2 = Geode LX)
 *---------------------------------------------------------------------------*/

int
init_detect_cpu(unsigned long *cpu_revision, unsigned long *companion_revision)
{
    unsigned long bus, device, i;
    unsigned long cpu_bus = 0, cpu_device = 0;
    unsigned long address, data;
    unsigned long num_bars, function;
    int cpu_found, sb_found;
    Q_WORD msr_value;

    /* SEARCH THROUGH PCI BUS                                          */
    /* We search the PCI bus for the Geode LX or Geode GX northbridge. */
    /* We then verify that one of its functions is the graphics        */
    /* controller and that all bars are filled in.                     */

    cpu_found = sb_found = 0;
    for (bus = 0; bus < 256; bus++) {
        for (device = 0; device < 21; device++) {
            address = 0x80000000 | (bus << 16) | (device << 11);

            data = init_read_pci(address);

            if (data == PCI_VENDOR_DEVICE_GEODEGX
                || data == PCI_VENDOR_DEVICE_GEODELX) {
                cpu_found = 1;
                cpu_device = device;
                cpu_bus = bus;
                if (data == PCI_VENDOR_DEVICE_GEODEGX)
                    *cpu_revision = CIM_CPU_GEODEGX;
                else
                    *cpu_revision = CIM_CPU_GEODELX;
            }
            else if (data == PCI_VENDOR_5535 || data == PCI_VENDOR_5536) {
                sb_found = 1;
                if (data == PCI_VENDOR_5535)
                    *companion_revision = CIM_SB_5535;
                else
                    *companion_revision = CIM_SB_5536;
            }

            if (cpu_found && sb_found)
                break;
        }
        if (device != 21)
            break;
    }

    if (bus == 256) {
        *cpu_revision = 0;
        return CIM_STATUS_CPUNOTFOUND;
    }

    msr_init_table();

    if (msr_read64(MSR_DEVICE_GEODELX_GLCP, GLCP_REVID,
                   &msr_value) != CIM_STATUS_OK) {
        *cpu_revision = 0;
        return CIM_STATUS_CPUNOTFOUND;
    }

    *cpu_revision |= ((msr_value.low & 0xF0) << 4) |
        ((msr_value.low & 0x0F) << 16);

    if (msr_read64(MSR_DEVICE_5535_GLCP, GLCP_REVID,
                   &msr_value) != CIM_STATUS_OK) {
        *cpu_revision = 0;
        return CIM_STATUS_CPUNOTFOUND;
    }

    *companion_revision |= ((msr_value.low & 0xF0) << 4) |
        ((msr_value.low & 0x0F) << 16);

    /* SEARCH ALL FUNCTIONS FOR INTEGRATED GRAPHICS */

    num_bars = 0;
    for (function = 0; function < 7; function++) {
        address = 0x80000000 | (cpu_bus << 16) | (cpu_device << 11) |
            (function << 8);
        data = init_read_pci(address);

        if (data == PCI_VENDOR_DEVICE_GEODEGX_VIDEO) {
            num_bars = 4;
            break;
        }
        else if (data == PCI_VENDOR_DEVICE_GEODELX_VIDEO) {
            num_bars = 5;
            break;
        }
    }

    /* VERIFY THAT ALL BARS ARE PRESENT */

    if (function == 7)
        return CIM_STATUS_DISPLAYUNAVAILABLE;

    for (i = 0; i < num_bars; i++) {
        data = init_read_pci(address + 0x10 + (i << 2));

        if (data == 0 || data == 0xFFFFFFFF)
            break;
    }

    if (i != num_bars)
        return CIM_STATUS_DISPLAYUNAVAILABLE;

    /* SAVE VIDEO BASE ADDRESS FOR FUTURE CALLS */

    init_video_base = address;

    return CIM_STATUS_OK;
}

/*---------------------------------------------------------------------------
 * init_read_pci
 *
 * This routine reads an unsigned long value from a PCI address.
 *---------------------------------------------------------------------------*/

unsigned long
init_read_pci(unsigned long address)
{
    OUTD(0xCF8, address);
    return IND(0xCFC);
}

/*---------------------------------------------------------------------------
 * init_read_base_addresses
 *
 * This routine reads all base addresses for the peripherals from the PCI
 * BARs.
 *---------------------------------------------------------------------------*/

int
init_read_base_addresses(INIT_BASE_ADDRESSES * base_addresses)
{
    unsigned long value;

    /* READ ALL BASE ADDRESSES */

    base_addresses->framebuffer_base = init_read_pci(init_video_base + 0x10);
    base_addresses->gp_register_base = init_read_pci(init_video_base + 0x14);
    base_addresses->vg_register_base = init_read_pci(init_video_base + 0x18);
    base_addresses->df_register_base = init_read_pci(init_video_base + 0x1C);
    base_addresses->vip_register_base = init_read_pci(init_video_base + 0x20);

    /* READ FRAME BUFFER SIZE */
    /* The frame buffer size is reported by a VSM in VSA II */
    /* Virtual Register Class    = 0x02                    */
    /* VG_MEM_SIZE (1MB units)   = 0x00                    */

    OUTW(0xAC1C, 0xFC53);
    OUTW(0xAC1C, 0x0200);

    value = (unsigned long) (INW(0xAC1E)) & 0xFE;

    base_addresses->framebuffer_size = value << 20;

    return CIM_STATUS_OK;
}

/*---------------------------------------------------------------------------
 * init_read_cpu_frequency
 *
 * This routine returns the current CPU core frequency, in MHz.
 *---------------------------------------------------------------------------*/

int
init_read_cpu_frequency(unsigned long *cpu_frequency)
{
    /* CPU SPEED IS REPORTED BY A VSM IN VSA II */
    /* Virtual Register Class = 0x12 (Sysinfo)  */
    /* CPU Speed Register     = 0x01            */

    OUTW(0xAC1C, 0xFC53);
    OUTW(0xAC1C, 0x1201);

    *cpu_frequency = (unsigned long) (INW(0xAC1E));

    return CIM_STATUS_OK;
}
