* 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 [] = {
{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},
{0x0f41, PCH_ATOM},
{0x0f42, PCH_ATOM},
{0x0f43, PCH_ATOM},
{0x0f44, PCH_ATOM},
{0x0f45, PCH_ATOM},
{0x0f46, PCH_ATOM},
{0x0f47, PCH_ATOM},
{0x9c61, PCH_HASWELL},
{0x9c62, PCH_HASWELL},
{0x22c1, PCH_ATOM},
{0x22c2, PCH_ATOM},
{0x22c3, PCH_ATOM},
{0x22c4, PCH_ATOM},
{0x22c5, PCH_ATOM},
{0x22c6, PCH_ATOM},
{0x22c7, PCH_ATOM},
{0x9d60, PCH_SKYLAKE},
{0x9d61, PCH_SKYLAKE},
{0x9d62, PCH_SKYLAKE},
{0x9d63, PCH_SKYLAKE},
{0x9d64, PCH_SKYLAKE},
{0x9d65, PCH_SKYLAKE},
{0xa160, PCH_SKYLAKE},
{0xa161, PCH_SKYLAKE},
{0xa162, PCH_SKYLAKE},
{0x5aac, PCH_APL},
{0x5aae, PCH_APL},
{0x5ab0, PCH_APL},
{0x5ab2, PCH_APL},
{0x5ab4, PCH_APL},
{0x5ab6, PCH_APL},
{0x5ab8, PCH_APL},
{0x5aba, PCH_APL},
{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},
{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},
{0x34e8, PCH_TIGERLAKE},
{0x34e9, PCH_TIGERLAKE},
{0x34ea, PCH_TIGERLAKE},
{0x34eb, PCH_TIGERLAKE},
{0x34c5, PCH_TIGERLAKE},
{0x34c6, PCH_TIGERLAKE},
{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},
{0x31ac, PCH_GEMINILAKE},
{0x31ae, PCH_GEMINILAKE},
{0x31b0, PCH_GEMINILAKE},
{0x31b2, PCH_GEMINILAKE},
{0x31b4, PCH_GEMINILAKE},
{0x31b6, PCH_GEMINILAKE},
{0x31b8, PCH_GEMINILAKE},
{0x31ba, PCH_GEMINILAKE},
{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},
{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;
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;
{
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);
acpi_device_module_info *acpi;
acpi_device acpiDevice;
if (gDeviceManager->get_driver(acpiNode, (driver_module_info **)&acpi,
(void **)&acpiDevice) == B_OK) {
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[] = {
{ B_DEVICE_PRETTY_NAME, B_STRING_TYPE,
{ .string = prettyName }},
{ B_DEVICE_FIXED_CHILD, B_STRING_TYPE,
{ .string = I2C_FOR_CONTROLLER_MODULE_NAME }},
{ 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;
}
pci->set_powerstate(device, PCI_pm_state_d0);
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);
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) {
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;
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;
}
driver_module_info gPchI2cPciDevice = {
{
PCH_I2C_PCI_DEVICE_MODULE_NAME,
0,
NULL
},
supports_device,
register_device,
init_device,
uninit_device,
register_child_devices,
NULL,
NULL,
};