/* * Copyright 2020, Jérôme Duval, jerome.duval@gmail.com. * Distributed under the terms of the MIT License. */ #include #include #include #include #include #include #include #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 };