root/src/add-ons/kernel/bus_managers/pci/pci_fixup.cpp
/*
 * Copyright 2007, Marcus Overhagen. All rights reserved.
 * Distributed under the terms of the MIT License.
 */

#include "pci.h"
#include "pci_fixup.h"

#include <arch/int.h>

#include <KernelExport.h>


/*
 * We need to force controllers that have both SATA and PATA controllers to use
 * the split mode with the SATA controller at function 0 and the PATA
 * controller at function 1. This way the SATA controller will be picked up by
 * the AHCI driver and the IDE controller by the generic IDE driver.
 *
 * TODO(bga): This does not work when the SATA controller is configured for IDE
 * mode but this seems to be a problem with the device manager (it tries to load
 * the IDE driver for the AHCI controller for some reason).
 */
static void
jmicron_fixup_ahci(PCI *pci, uint8 domain, uint8 bus, uint8 device,
        uint8 function, uint16 deviceId)
{
        // We only care about function 0.
        if (function != 0)
                return;

        // And only devices with combined SATA/PATA.
        switch (deviceId) {
                case 0x2361: // 1 SATA, 1 PATA
                case 0x2363: // 2 SATA, 1 PATA
                case 0x2366: // 2 SATA, 2 PATA
                        break;
                default:
                        return;
        }

        dprintf("jmicron_fixup_ahci: domain %u, bus %u, device %u, function %u, "
                "deviceId 0x%04x\n", domain, bus, device, function, deviceId);

        // Read controller control register (0x40).
        uint32 val = pci->ReadConfig(domain, bus, device, function, 0x40, 4);
        dprintf("jmicron_fixup_ahci: Register 0x40 : 0x%08" B_PRIx32 "\n", val);

        // Clear bits.
        val &= ~(1 << 1);
        val &= ~(1 << 9);
        val &= ~(1 << 13);
        val &= ~(1 << 15);
        val &= ~(1 << 16);
        val &= ~(1 << 17);
        val &= ~(1 << 18);
        val &= ~(1 << 19);
        val &= ~(1 << 22);

        //Set bits.
        val |= (1 << 0);
        val |= (1 << 4);
        val |= (1 << 5);
        val |= (1 << 7);
        val |= (1 << 8);
        val |= (1 << 12);
        val |= (1 << 14);
        val |= (1 << 23);

        dprintf("jmicron_fixup_ahci: Register 0x40 : 0x%08" B_PRIx32 "\n", val);
        pci->WriteConfig(domain, bus, device, function, 0x40, 4, val);

        // Read IRQ from controller at function 0 and assign this IRQ to the
        // controller at function 1.
        uint8 irq = pci->ReadConfig(domain, bus, device, function, 0x3c, 1);
        dprintf("jmicron_fixup_ahci: Assigning IRQ %d at device "
                "function 1.\n", irq);
        pci->WriteConfig(domain, bus, device, 1, 0x3c, 1, irq);
}


static void
intel_fixup_ahci(PCI *pci, uint8 domain, uint8 bus, uint8 device,
        uint8 function, uint16 deviceId)
{
        // TODO(bga): disabled until the PCI manager can assign new resources.
        return;

        switch (deviceId) {
                case 0x2825: // ICH8 Desktop when in IDE emulation mode
                        dprintf("intel_fixup_ahci: WARNING found ICH8 device id 0x2825\n");
                        return;
                case 0x2926: // ICH9 Desktop when in IDE emulation mode
                        dprintf("intel_fixup_ahci: WARNING found ICH9 device id 0x2926\n");
                        return;

                case 0x27c0: // ICH7 Desktop non-AHCI and non-RAID mode
                case 0x27c4: // ICH7 Mobile non-AHCI and non-RAID mode
                case 0x2820: // ICH8 Desktop non-AHCI and non-RAID mode
                case 0x2828: // ICH8 Mobile non-AHCI and non-RAID mode
                case 0x2920: // ICH9 Desktop non-AHCI and non-RAID mode (4 ports)
                case 0x2921: // ICH9 Desktop non-AHCI and non-RAID mode (2 ports)
                        break;
                default:
                        return;
        }

        dprintf("intel_fixup_ahci: domain %u, bus %u, device %u, function %u, "
                "deviceId 0x%04x\n", domain, bus, device, function, deviceId);

        dprintf("intel_fixup_ahci: 0x24: 0x%08" B_PRIx32 "\n",
                pci->ReadConfig(domain, bus, device, function, 0x24, 4));
        dprintf("intel_fixup_ahci: 0x90: 0x%02" B_PRIx32 "\n",
                pci->ReadConfig(domain, bus, device, function, 0x90, 1));

        uint8 map = pci->ReadConfig(domain, bus, device, function, 0x90, 1);
        if ((map >> 6) == 0) {
                uint32 bar5 = pci->ReadConfig(domain, bus, device, function, 0x24, 4);
                uint16 pcicmd = pci->ReadConfig(domain, bus, device, function,
                        PCI_command, 2);

                dprintf("intel_fixup_ahci: switching from IDE to AHCI mode\n");

                pci->WriteConfig(domain, bus, device, function, PCI_command, 2,
                        pcicmd & ~(PCI_command_io | PCI_command_memory));

                pci->WriteConfig(domain, bus, device, function, 0x24, 4, 0xffffffff);
                dprintf("intel_fixup_ahci: ide-bar5 bits-1: 0x%08" B_PRIx32 "\n",
                        pci->ReadConfig(domain, bus, device, function, 0x24, 4));
                pci->WriteConfig(domain, bus, device, function, 0x24, 4, 0);
                dprintf("intel_fixup_ahci: ide-bar5 bits-0: 0x%08" B_PRIx32 "\n",
                        pci->ReadConfig(domain, bus, device, function, 0x24, 4));

                map &= ~0x03;
                map |= 0x40;
                pci->WriteConfig(domain, bus, device, function, 0x90, 1, map);

                pci->WriteConfig(domain, bus, device, function, 0x24, 4, 0xffffffff);
                dprintf("intel_fixup_ahci: ahci-bar5 bits-1: 0x%08" B_PRIx32 "\n",
                        pci->ReadConfig(domain, bus, device, function, 0x24, 4));
                pci->WriteConfig(domain, bus, device, function, 0x24, 4, 0);
                dprintf("intel_fixup_ahci: ahci-bar5 bits-0: 0x%08" B_PRIx32 "\n",
                        pci->ReadConfig(domain, bus, device, function, 0x24, 4));

                if (deviceId == 0x27c0 || deviceId == 0x27c4) // restore on ICH7
                        pci->WriteConfig(domain, bus, device, function, 0x24, 4, bar5);

                pci->WriteConfig(domain, bus, device, function, PCI_command, 2, pcicmd);
        }

        dprintf("intel_fixup_ahci: 0x24: 0x%08" B_PRIx32 "\n",
                pci->ReadConfig(domain, bus, device, function, 0x24, 4));
        dprintf("intel_fixup_ahci: 0x90: 0x%02" B_PRIx32 "\n",
                pci->ReadConfig(domain, bus, device, function, 0x90, 1));
}


static void
ati_fixup_ixp(PCI *pci, uint8 domain, uint8 bus, uint8 device, uint8 function,
        uint16 deviceId)
{
#if defined(__i386__) || defined(__x86_64__)
        /* ATI Technologies Inc, IXP chipset:
         * This chipset seems broken, at least on my laptop I must force 
         * the timer IRQ trigger mode, else no interrupt comes in.
         * mmu_man.
         */
        // XXX: should I use host or isa bridges for detection ??
        switch (deviceId) {
                // Host bridges
                case 0x5950:    // RS480 Host Bridge
                case 0x5830:
                        break;
                // ISA bridges
                case 0x4377:    // IXP SB400 PCI-ISA Bridge 
                default:
                        return;
        }
        dprintf("ati_fixup_ixp: domain %u, bus %u, device %u, function %u, deviceId 0x%04x\n",
                domain, bus, device, function, deviceId);

        dprintf("ati_fixup_ixp: found IXP chipset, forcing IRQ 0 as level triggered.\n");
        // XXX: maybe use pic_*() ?
        arch_int_configure_io_interrupt(0, B_LEVEL_TRIGGERED);

#endif
}


void
pci_fixup_device(PCI *pci, uint8 domain, uint8 bus, uint8 device,
        uint8 function)
{
        uint16 vendorId = pci->ReadConfig(domain, bus, device, function,
                PCI_vendor_id, 2);
        uint16 deviceId = pci->ReadConfig(domain, bus, device, function,
                PCI_device_id, 2);

//      dprintf("pci_fixup_device: domain %u, bus %u, device %u, function %u\n",
//              domain, bus, device, function);

        switch (vendorId) {
                case 0x197b:
                        jmicron_fixup_ahci(pci, domain, bus, device, function, deviceId);
                        break;

                case 0x8086:
                        intel_fixup_ahci(pci, domain, bus, device, function, deviceId);
                        break;

                case 0x1002:
                        ati_fixup_ixp(pci, domain, bus, device, function, deviceId);
                        break;
        }
}