root/src/add-ons/kernel/busses/i2c/pch/pch_i2c_pci.cpp
/*
 * Copyright 2020, Jérôme Duval, jerome.duval@gmail.com.
 * Distributed under the terms of the MIT License.
 */


#include <new>
#include <stdio.h>
#include <string.h>

#include <ACPI.h>
#include <ByteOrder.h>
#include <condition_variable.h>
#include <bus/PCI.h>


#include "pch_i2c.h"


struct {
        uint16 id;
        pch_version version;
} pch_pci_devices [] = {

        /* Unknown */
        {0x0aac, PCH_NONE},
        {0x0aae, PCH_NONE},
        {0x0ab0, PCH_NONE},
        {0x0ab2, PCH_NONE},
        {0x0ab4, PCH_NONE},
        {0x0ab6, PCH_NONE},
        {0x0ab8, PCH_NONE},
        {0x0aba, PCH_NONE},

        {0x1aac, PCH_NONE},
        {0x1aae, PCH_NONE},

        {0x1ab0, PCH_NONE},
        {0x1ab2, PCH_NONE},
        {0x1ab4, PCH_NONE},
        {0x1ab6, PCH_NONE},
        {0x1ab8, PCH_NONE},
        {0x1aba, PCH_NONE},

        {0x4b44, PCH_NONE},
        {0x4b45, PCH_NONE},
        {0x4b4b, PCH_NONE},
        {0x4b4c, PCH_NONE},
        {0x4b78, PCH_NONE},
        {0x4b79, PCH_NONE},
        {0x4b7a, PCH_NONE},
        {0x4b7b, PCH_NONE},

        {0x4dc5, PCH_NONE},
        {0x4dc6, PCH_NONE},
        {0x4de8, PCH_NONE},
        {0x4de9, PCH_NONE},
        {0x4dea, PCH_NONE},
        {0x4deb, PCH_NONE},

        {0x7a4c, PCH_NONE},
        {0x7a4d, PCH_NONE},
        {0x7a4e, PCH_NONE},
        {0x7a4f, PCH_NONE},
        {0x7a7c, PCH_NONE},
        {0x7a7d, PCH_NONE},
        
        {0x98c5, PCH_NONE},
        {0x98c6, PCH_NONE},
        {0x98e8, PCH_NONE},
        {0x98e9, PCH_NONE},
        {0x98ea, PCH_NONE},
        {0x98eb, PCH_NONE},

        {0xa2e0, PCH_NONE},
        {0xa2e1, PCH_NONE},
        {0xa2e2, PCH_NONE},
        {0xa2e3, PCH_NONE},

        /* Baytrail */
        {0x0f41, PCH_ATOM},
        {0x0f42, PCH_ATOM},
        {0x0f43, PCH_ATOM},
        {0x0f44, PCH_ATOM},
        {0x0f45, PCH_ATOM},
        {0x0f46, PCH_ATOM},
        {0x0f47, PCH_ATOM},

        /* Haswell */
        {0x9c61, PCH_HASWELL},
        {0x9c62, PCH_HASWELL},

        /* Braswell */
        {0x22c1, PCH_ATOM},
        {0x22c2, PCH_ATOM},
        {0x22c3, PCH_ATOM},
        {0x22c4, PCH_ATOM},
        {0x22c5, PCH_ATOM},
        {0x22c6, PCH_ATOM},
        {0x22c7, PCH_ATOM},

        /* Skylake */
        {0x9d60, PCH_SKYLAKE},
        {0x9d61, PCH_SKYLAKE},
        {0x9d62, PCH_SKYLAKE},
        {0x9d63, PCH_SKYLAKE},
        {0x9d64, PCH_SKYLAKE},
        {0x9d65, PCH_SKYLAKE},

        /* Kaby Lake */
        {0xa160, PCH_SKYLAKE},
        {0xa161, PCH_SKYLAKE},
        {0xa162, PCH_SKYLAKE}, // ?

        /* Apolo Lake */
        {0x5aac, PCH_APL},
        {0x5aae, PCH_APL},
        {0x5ab0, PCH_APL},
        {0x5ab2, PCH_APL},
        {0x5ab4, PCH_APL},
        {0x5ab6, PCH_APL},
        {0x5ab8, PCH_APL},
        {0x5aba, PCH_APL},

        /* Cannon Lake */
        {0x9dc5, PCH_CANNONLAKE},
        {0x9dc6, PCH_CANNONLAKE},
        {0x9de8, PCH_CANNONLAKE},
        {0x9de9, PCH_CANNONLAKE},
        {0x9dea, PCH_CANNONLAKE},
        {0x9deb, PCH_CANNONLAKE},
        {0xa368, PCH_CANNONLAKE},
        {0xa369, PCH_CANNONLAKE},
        {0xa36a, PCH_CANNONLAKE},
        {0xa36b, PCH_CANNONLAKE},

        /* Comet Lake */
        {0x02e8, PCH_CANNONLAKE},
        {0x02e9, PCH_CANNONLAKE},
        {0x02ea, PCH_CANNONLAKE},
        {0x02eb, PCH_CANNONLAKE},
        {0x02c5, PCH_CANNONLAKE},
        {0x02c6, PCH_CANNONLAKE},
        {0x06e8, PCH_CANNONLAKE},
        {0x06e9, PCH_CANNONLAKE},
        {0x06ea, PCH_CANNONLAKE},
        {0x06eb, PCH_CANNONLAKE},
        {0xa3e0, PCH_CANNONLAKE},
        {0xa3e1, PCH_CANNONLAKE},
        {0xa3e2, PCH_CANNONLAKE},
        {0xa3e3, PCH_CANNONLAKE},

        /* Ice Lake */
        {0x34e8, PCH_TIGERLAKE},
        {0x34e9, PCH_TIGERLAKE},
        {0x34ea, PCH_TIGERLAKE},
        {0x34eb, PCH_TIGERLAKE},
        {0x34c5, PCH_TIGERLAKE},
        {0x34c6, PCH_TIGERLAKE},

        /* Tiger Lake */
        {0x43d8, PCH_TIGERLAKE},
        {0x43e8, PCH_TIGERLAKE},
        {0x43e9, PCH_TIGERLAKE},
        {0x43ea, PCH_TIGERLAKE},
        {0x43eb, PCH_TIGERLAKE},
        {0x43ad, PCH_TIGERLAKE},
        {0x43ae, PCH_TIGERLAKE},
        {0xa0c5, PCH_SKYLAKE},
        {0xa0c6, PCH_SKYLAKE},
        {0xa0d8, PCH_SKYLAKE},
        {0xa0d9, PCH_SKYLAKE},
        {0xa0e8, PCH_SKYLAKE},
        {0xa0e9, PCH_SKYLAKE},
        {0xa0ea, PCH_SKYLAKE},
        {0xa0eb, PCH_SKYLAKE},

        /* Gemini Lake */
        {0x31ac, PCH_GEMINILAKE},
        {0x31ae, PCH_GEMINILAKE},
        {0x31b0, PCH_GEMINILAKE},
        {0x31b2, PCH_GEMINILAKE},
        {0x31b4, PCH_GEMINILAKE},
        {0x31b6, PCH_GEMINILAKE},
        {0x31b8, PCH_GEMINILAKE},
        {0x31ba, PCH_GEMINILAKE},

        /* Alder Lake */
        {0x51e8, PCH_TIGERLAKE},
        {0x51e9, PCH_TIGERLAKE},
        {0x51ea, PCH_TIGERLAKE},
        {0x51eb, PCH_TIGERLAKE},
        {0x51c5, PCH_TIGERLAKE},
        {0x51c6, PCH_TIGERLAKE},
        {0x51d8, PCH_TIGERLAKE},
        {0x51d9, PCH_TIGERLAKE},
        {0x7acc, PCH_TIGERLAKE},
        {0x7acd, PCH_TIGERLAKE},
        {0x7ace, PCH_TIGERLAKE},
        {0x7acf, PCH_TIGERLAKE},
        {0x7afc, PCH_TIGERLAKE},
        {0x7afd, PCH_TIGERLAKE},
        {0x54e8, PCH_TIGERLAKE},
        {0x54e9, PCH_TIGERLAKE},
        {0x54ea, PCH_TIGERLAKE},
        {0x54eb, PCH_TIGERLAKE},
        {0x54c5, PCH_TIGERLAKE},
        {0x54c6, PCH_TIGERLAKE},

        /* Meteor Lake */
        {0x7e50, PCH_TIGERLAKE},
        {0x7e51, PCH_TIGERLAKE},
        {0x7e78, PCH_TIGERLAKE},
        {0x7e79, PCH_TIGERLAKE},
        {0x7e7a, PCH_TIGERLAKE},
        {0x7e7b, PCH_TIGERLAKE},

        {0, PCH_NONE}
};

typedef struct {
        pch_i2c_sim_info info;
        pci_device_module_info* pci;
        pci_device* device;
        pch_i2c_irq_type irq_type;

        pci_info pciinfo;
} pch_i2c_pci_sim_info;


//      #pragma mark -


static status_t
pci_scan_bus(i2c_bus_cookie cookie)
{
        CALLED();
        pch_i2c_pci_sim_info* bus = (pch_i2c_pci_sim_info*)cookie;
        device_node *acpiNode = NULL;

        pci_info *pciInfo = &bus->pciinfo;

        // search ACPI I2C nodes for this device
        {
                device_node* deviceRoot = gDeviceManager->get_root_node();
                uint32 addr = (pciInfo->device << 16) | pciInfo->function;
                device_attr acpiAttrs[] = {
                        { B_DEVICE_BUS, B_STRING_TYPE, { .string = "acpi" }},
                        { ACPI_DEVICE_ADDR_ITEM, B_UINT32_TYPE, {.ui32 = addr}},
                        { NULL }
                };
                if (addr != 0 && gDeviceManager->find_child_node(deviceRoot, acpiAttrs,
                                &acpiNode) != B_OK) {
                        ERROR("init_bus() acpi device not found\n");
                        return B_DEV_CONFIGURATION_ERROR;
                }
        }

        TRACE("init_bus() find_child_node() found %x %x %p\n",
                pciInfo->device, pciInfo->function, acpiNode);
        // TODO eventually check timings on acpi
        acpi_device_module_info *acpi;
        acpi_device     acpiDevice;
        if (gDeviceManager->get_driver(acpiNode, (driver_module_info **)&acpi,
                (void **)&acpiDevice) == B_OK) {
                // find out I2C device nodes
                acpi->walk_namespace(acpiDevice, ACPI_TYPE_DEVICE, 1,
                        pch_i2c_scan_bus_callback, NULL, bus, NULL);
        }

        return B_OK;
}


static status_t
register_child_devices(void* cookie)
{
        CALLED();

        pch_i2c_pci_sim_info* bus = (pch_i2c_pci_sim_info*)cookie;
        device_node* node = bus->info.driver_node;

        char prettyName[25];
        sprintf(prettyName, "PCH I2C Controller %" B_PRIu16, 0);

        device_attr attrs[] = {
                // properties of this controller for i2c bus manager
                { B_DEVICE_PRETTY_NAME, B_STRING_TYPE,
                        { .string = prettyName }},
                { B_DEVICE_FIXED_CHILD, B_STRING_TYPE,
                        { .string = I2C_FOR_CONTROLLER_MODULE_NAME }},

                // private data to identify the device
                { NULL }
        };

        return gDeviceManager->register_node(node, PCH_I2C_SIM_MODULE_NAME,
                attrs, NULL, NULL);
}


static status_t
init_device(device_node* node, void** device_cookie)
{
        CALLED();
        status_t status = B_OK;

        pch_i2c_pci_sim_info* bus = (pch_i2c_pci_sim_info*)calloc(1,
                sizeof(pch_i2c_pci_sim_info));
        if (bus == NULL)
                return B_NO_MEMORY;

        pci_device_module_info* pci;
        pci_device* device;
        {
                device_node* pciParent = gDeviceManager->get_parent_node(node);
                gDeviceManager->get_driver(pciParent, (driver_module_info**)&pci,
                        (void**)&device);
                gDeviceManager->put_node(pciParent);
        }

        bus->pci = pci;
        bus->device = device;
        bus->info.driver_node = node;
        bus->info.scan_bus = pci_scan_bus;

        pci_info *pciInfo = &bus->pciinfo;
        pci->get_pci_info(device, pciInfo);

        size_t dev = 0;
        while (pch_pci_devices[dev].id != 0) {
                if (pch_pci_devices[dev].id == pciInfo->device_id) {
                        bus->info.version = pch_pci_devices[dev].version;
                        break;
                }
                dev++;
        }

        bus->info.base_addr = pciInfo->u.h0.base_registers[0];
        bus->info.map_size = pciInfo->u.h0.base_register_sizes[0];
        if ((pciInfo->u.h0.base_register_flags[0] & PCI_address_type)
                        == PCI_address_type_64) {
                bus->info.base_addr |= (uint64)pciInfo->u.h0.base_registers[1] << 32;
                bus->info.map_size |= (uint64)pciInfo->u.h0.base_register_sizes[1] << 32;
        }

        if (bus->info.base_addr == 0) {
                ERROR("PCI BAR not assigned\n");
                free(bus);
                return B_ERROR;
        }

        // enable power
        pci->set_powerstate(device, PCI_pm_state_d0);

        // enable bus master and memory
        uint16 pcicmd = pci->read_pci_config(device, PCI_command, 2);
        pcicmd |= PCI_command_master | PCI_command_memory;
        pci->write_pci_config(device, PCI_command, 2, pcicmd);

        // try MSI-X
        if (pci->get_msix_count(device) >= 1) {
                uint32 vector;
                if (pci->configure_msix(device, 1, &vector) == B_OK
                        && pci->enable_msix(device) == B_OK) {
                        TRACE_ALWAYS("using MSI-X vector %" B_PRIu32 "\n", vector);
                        bus->info.irq = vector;
                        bus->irq_type = PCH_I2C_IRQ_MSI_X_SHARED;
                } else {
                        ERROR("couldn't use MSI-X SHARED\n");
                }
        } else if (pci->get_msi_count(device) >= 1) {
                // try MSI
                uint32 vector;
                if (pci->configure_msi(device, 1, &vector) == B_OK
                        && pci->enable_msi(device) == B_OK) {
                        TRACE_ALWAYS("using MSI vector %" B_PRIu32 "\n", vector);
                        bus->info.irq = vector;
                        bus->irq_type = PCH_I2C_IRQ_MSI;
                } else {
                        ERROR("couldn't use MSI\n");
                }
        }
        if (bus->irq_type == PCH_I2C_IRQ_LEGACY) {
                bus->info.irq = pciInfo->u.h0.interrupt_line;
                if (bus->info.irq == 0xff)
                        bus->info.irq = 0;

                TRACE_ALWAYS("using legacy interrupt %" B_PRIu32 "\n", bus->info.irq);
        }
        if (bus->info.irq == 0) {
                ERROR("PCI IRQ not assigned\n");
                status = B_ERROR;
                goto err;
        }

        *device_cookie = bus;
        return B_OK;

err:
        free(bus);
        return status;
}


static void
uninit_device(void* device_cookie)
{
        pch_i2c_pci_sim_info* bus = (pch_i2c_pci_sim_info*)device_cookie;
        if (bus->irq_type != PCH_I2C_IRQ_LEGACY) {
                bus->pci->disable_msi(bus->device);
                bus->pci->unconfigure_msi(bus->device);
        }
        free(bus);
}


static status_t
register_device(device_node* parent)
{
        device_attr attrs[] = {
                {B_DEVICE_PRETTY_NAME, B_STRING_TYPE, {.string = "PCH I2C PCI"}},
                {}
        };

        return gDeviceManager->register_node(parent,
                PCH_I2C_PCI_DEVICE_MODULE_NAME, attrs, NULL, NULL);
}


static float
supports_device(device_node* parent)
{
        CALLED();
        const char* bus;
        uint16 vendorID, deviceID;

        // make sure parent is a PCH I2C PCI device node
        if (gDeviceManager->get_attr_string(parent, B_DEVICE_BUS, &bus, false)
                < B_OK || gDeviceManager->get_attr_uint16(parent, B_DEVICE_VENDOR_ID,
                                &vendorID, false) < B_OK
                || gDeviceManager->get_attr_uint16(parent, B_DEVICE_ID, &deviceID,
                                false) < B_OK) {
                return -1;
        }

        if (strcmp(bus, "pci") != 0)
                return 0.0f;

        if (vendorID == 0x8086) {
                size_t dev = 0;
                bool found = false;

                while (pch_pci_devices[dev].id != 0) {
                        if (pch_pci_devices[dev].id == deviceID) {
                                found = true;
                                break;
                        }
                        dev++;
                }

                if (!found)
                        return 0.0f;

                pci_device_module_info* pci;
                pci_device* device;
                gDeviceManager->get_driver(parent, (driver_module_info**)&pci,
                        (void**)&device);
#ifdef TRACE_PCH_I2C
                uint8 pciSubDeviceId = pci->read_pci_config(device, PCI_revision,
                        1);

                TRACE("PCH I2C device found! vendor 0x%04x, device 0x%04x, subdevice 0x%02x\n", vendorID,
                        deviceID, pciSubDeviceId);
#endif
                return 0.8f;
        }

        return 0.0f;
}


//      #pragma mark -


driver_module_info gPchI2cPciDevice = {
        {
                PCH_I2C_PCI_DEVICE_MODULE_NAME,
                0,
                NULL
        },

        supports_device,
        register_device,
        init_device,
        uninit_device,
        register_child_devices,
        NULL,   // rescan
        NULL,   // device removed
};