diff --git a/docs/architecture/ACPI/acpi_interface.md b/docs/architecture/ACPI/acpi_interface.md index 42ed3bb..921e257 100644 --- a/docs/architecture/ACPI/acpi_interface.md +++ b/docs/architecture/ACPI/acpi_interface.md @@ -44,28 +44,34 @@ Two functions expose this mapping to the rest of the kernel: ## AML Parsing -The DSDT and any SSDTs present in the firmware contain AML (ACPI Machine Language) bytecode describing the hardware namespace. BoredOS does not implement a full AML interpreter - instead `src/drivers/ACPI/acpi_aml.c` implements a targeted byte-stream walker sufficient for I2C device enumeration. +The DSDT and any SSDTs present in the firmware contain AML (ACPI Machine Language) bytecode describing the hardware namespace. BoredOS does not implement a full AML interpreter - instead `src/drivers/ACPI/acpi_aml.c` implements a targeted byte-stream walker sufficient for I2C device enumeration and basic power management. The walker handles: -- **`_HID`** - device identification, either as an inline string or a packed EISAID integer. -- **`_CRS`** - current resource settings; specifically scans for `I2cSerialBusV2` descriptors (large item tag `0x8E`) to extract slave address, bus speed, and addressing mode. -- **`_DSM`** - device-specific method; scans the raw method body for the I2C-HID GUID (`3cdff6f7-4267-4555-ad05-b30a3d8938de`) and extracts the HID descriptor register address from the static return package. -- **Power states** - records presence of `_PS0`, `_PS3`, `_PR0`, `_PR3` as flags. +- **`_HID` & `_CID`** - Device identification. Supports inline strings and packed EISAID integers. + - Specifically recognizes touchpads/input devices: `PNP0C50`, `SYNA`, `ELAN`, `ALPS`. +- **`_CRS`** - Current Resource Settings. Scans for `I2cSerialBusV2` descriptors (large item tag `0x8E`) to extract slave address and bus speed. +- **Deep Scanning** - If a device lacks a static `_CRS`, the walker scans AML methods for literal `Buffer` objects that contain I2C serial bus descriptors. +- **Controller Detection** - Identifies Intel I2C controllers via HIDs `INTC1040` and `INTC1043`. +- **Power States** - Records presence of `_PS0`, `_PS3`, `_PR0`, `_PR3` as flags for power management. > [!IMPORTANT] -> `_CRS` and `_DSM` must be statically defined `Name()` objects for the walker to read them. Dynamically computed resources via AML method evaluation are not supported. +> While the walker can find literal buffers inside methods, it still cannot evaluate complex AML logic or dynamic expressions. --- ## I2C Enumeration -`acpi_i2c_enumerate()` is called at the end of `acpi_init()`. It walks the DSDT via `acpi_get_dsdt()` followed by all SSDTs found in the XSDT/RSDT, feeding each into `aml_walk_table()`. +`acpi_i2c_enumerate()` is called during kernel initialization. It performs a comprehensive sweep of the ACPI namespace: -Devices are emitted into a flat table when both a `_HID` and a valid `I2cSerialBusV2` resource (non-zero `connection_speed`) are found. The result is accessible via: +1. **DSDT Walk**: Scans the main DSDT for I2C devices. +2. **SSDT Walk**: Iterates through all Secondary System Description Tables. +3. **Deep HID Search**: Performs a raw byte search across *all* system tables for `_HID` patterns and specific EISAIDs (like `SYNA`) to catch devices that might be missed by the structured walker. -- `acpi_i2c_count()` - number of enumerated devices. -- `acpi_i2c_get(index)` - pointer to an `aml_i2c_dev_t` record containing the name, HID, slave address, speed, DSM result, and power flags. +Devices are recorded in a global table, accessible via: + +- `acpi_i2c_count()` - Number of enumerated devices. +- `acpi_i2c_get(index)` - Pointer to an `aml_i2c_dev_t` record containing the name, HID, slave address, and capabilities. --- diff --git a/docs/architecture/README.md b/docs/architecture/README.md index c588d11..d632795 100644 --- a/docs/architecture/README.md +++ b/docs/architecture/README.md @@ -11,6 +11,8 @@ The documentation is split by area so you can go directly to the subsystem you w | Graphics | [`graphics/window_manager.md`](architecture/graphics/window_manager.md) | Window manager design and display composition. | | Hardware | [`hardware/input.md`](architecture/hardware/input.md) | Hardware-level input support and device wiring. | | Hardware | [`hardware/pci.md`](architecture/hardware/pci.md) | PCI bus management and device enumeration. | +| Hardware | [`hardware/i2c.md`](architecture/hardware/i2c.md) | I2C bus management and controller drivers. | +| System | [`ACPI/acpi_interface.md`](architecture/ACPI/acpi_interface.md) | Power management and ACPI hardware discovery. | | Input | [`input/keyboard.md`](architecture/input/keyboard.md) | Keyboard input handling and key mapping. | | Memory | [`memory/memory.md`](architecture/memory/memory.md) | Memory architecture, paging, and address space layout. | | Memory | [`memory/memory_manager.md`](architecture/memory/memory_manager.md) | Memory allocation and management systems. | diff --git a/docs/architecture/hardware/i2c.md b/docs/architecture/hardware/i2c.md new file mode 100644 index 0000000..a66dbe7 --- /dev/null +++ b/docs/architecture/hardware/i2c.md @@ -0,0 +1,83 @@ +# I2C Subsystem + +BoredOS provides a Linux-inspired I2C subsystem for managing I2C controllers (adapters) and communicating with slave devices. The implementation is located in `src/drivers/I2C/`. + +## Architecture + +The subsystem consists of: +- **I2C Core (`i2c.c/h`)**: Manages adapter registration and provides the master transfer API. +- **I2C Adapters**: Hardware-specific drivers that implement the actual bus transactions. +- **ACPI Integration (`acpi_i2c.c/h`)**: Enumerates I2C devices from ACPI tables and matches them to controllers. + +### Data Structures + +#### `i2c_msg_t` +Describes a single I2C transaction (read or write). +```c +typedef struct { + uint16_t addr; // 7-bit or 10-bit slave address + uint16_t flags; // I2C_M_RD, I2C_M_TEN, I2C_M_NOSTART + uint16_t len; // Number of bytes + uint8_t *buf; // Data buffer +} i2c_msg_t; +``` + +#### `i2c_adapter_t` +Represents an I2C controller. +```c +typedef struct i2c_adapter { + const char *name; + const aml_i2c_dev_t *acpi_dev; + void *priv; + i2c_master_xfer_fn master_xfer; + bool active; +} i2c_adapter_t; +``` + +--- + +## Intel LPSS I2C Driver + +The primary I2C driver in BoredOS is the Intel Low Power Subsystem (LPSS) driver (`i2c_lpss.c`), which targets Intel Core processors from Sky Lake through Meteor Lake. (Though only tested on Tiger Lake.) + +### Features +- **PCI Discovery**: Automatically scans for LPSS I2C controllers (Class 0C0300 or 1180). +- **Generation Awareness**: Handles differences between older (Sky Lake/Kaby Lake) and newer (Ice Lake/Tiger Lake+) hardware. +- **Packed Registers**: Supports the "packed" MMIO layout used in newer Intel generations, where multiple registers are compressed into single 32-bit words. +- **Power Management**: Performs D3 to D0 power state transitions required for controller initialization. +- **Clock & Reset**: Manages LPSS-specific private registers for clock gating and hardware reset. + +### Controller Matching +The driver matches physical PCI controllers to ACPI device nodes by scanning the ACPI namespace for I2C devices (HIDs like `PNP0C50`, `SYNA`, etc.) and assigning them to available controllers. + +--- + +## ACPI Enumeration + +I2C devices are discovered by walking the ACPI namespace (DSDT and SSDTs). + +- **HID Matching**: Recognizes common touchpad and input HIDs: + - `PNP0C50` (Standard HID-over-I2C) + - `SYNA` (Synaptics) + - `ELAN` (Elantech) + - `ALPS` (Alps) +- **Resource Extraction**: Parses `_CRS` (Current Resource Settings) to extract: + - Slave Address + - Connection Speed (Standard, Fast, etc.) +- **Controller Detection**: Specifically identifies Intel I2C controllers via HIDs `INTC1040` and `INTC1043`. + +--- + +## API Usage + +### Registering an Adapter +Drivers call `i2c_adapter_register()` during initialization. +```c +int i2c_adapter_register(i2c_adapter_t *adapter); +``` + +### Performing Transfers +The core provides `i2c_master_xfer()` to perform one or more messages in a single transaction (often used for "write then read" patterns). +```c +int i2c_master_xfer(i2c_adapter_t *adapter, i2c_msg_t *msgs, int num); +``` diff --git a/src/core/errno.h b/src/core/errno.h new file mode 100644 index 0000000..b345829 --- /dev/null +++ b/src/core/errno.h @@ -0,0 +1,18 @@ +// Copyright (c) 2023-2026 Chris (boreddevnl) +// This software is released under the GNU General Public License v3.0. See LICENSE file for details. +// This header needs to maintain in any file it is present in, as per the GPL license terms. + +#ifndef CORE_ERRNO_H +#define CORE_ERRNO_H + +#define EINVAL 22 // Invalid argument +#define ENOENT 2 // No such file or directory +#define EIO 5 // I/O error +#define ENXIO 6 // No such device or address +#define ENODEV 19 // No such device +#define EEXIST 17 // File exists +#define ENOSPC 28 // No space left on device +#define ENOSYS 38 // Function not implemented +#define ETIMEDOUT 110 // Connection timed out + +#endif // CORE_ERRNO_H diff --git a/src/dev/pci.c b/src/dev/pci.c index 0d137ef..2665724 100644 --- a/src/dev/pci.c +++ b/src/dev/pci.c @@ -98,10 +98,22 @@ int pci_find_device_by_class(uint8_t class_code, uint8_t subclass, pci_device_t* return 0; } -uint32_t pci_get_bar(pci_device_t *dev, int bar_num) { +uint64_t pci_get_bar(pci_device_t *dev, int bar_num) { if (!dev || bar_num < 0 || bar_num > 5) return 0; uint8_t offset = 0x10 + (bar_num * 4); - return pci_read_config(dev->bus, dev->device, dev->function, offset); + uint32_t bar_low = pci_read_config(dev->bus, dev->device, dev->function, offset); + + // Check if this is a 64-bit memory BAR (Type bits 1-2 == 2) + if ((bar_low & 0x01) == 0 && ((bar_low >> 1) & 0x03) == 0x02) { + if (bar_num < 5) { + uint32_t bar_high = pci_read_config(dev->bus, dev->device, dev->function, offset + 4); + return ((uint64_t)bar_high << 32) | (bar_low & ~0xF); + } + } + + // Standard 32-bit BAR or I/O BAR + if (bar_low & 0x01) return bar_low & ~0x3; // I/O + return bar_low & ~0xF; // Memory } void pci_enable_bus_mastering(pci_device_t *dev) { diff --git a/src/dev/pci.h b/src/dev/pci.h index a4ec1af..bff5463 100644 --- a/src/dev/pci.h +++ b/src/dev/pci.h @@ -20,11 +20,14 @@ typedef struct { uint8_t prog_if; } pci_device_t; -#define PCI_CLASS_NETWORK_CONTROLLER 0x02 -#define PCI_CLASS_ETHERNET_CONTROLLER 0x00 -#define PCI_CLASS_MASS_STORAGE 0x01 -#define PCI_SUBCLASS_SATA 0x06 -#define PCI_SUBCLASS_IDE 0x01 +#define PCI_CLASS_NETWORK_CONTROLLER 0x02 +#define PCI_CLASS_ETHERNET_CONTROLLER 0x00 +#define PCI_CLASS_MASS_STORAGE 0x01 +#define PCI_CLASS_SERIAL_BUS_CONTROLLER 0x0C + +#define PCI_SUBCLASS_SATA 0x06 +#define PCI_SUBCLASS_IDE 0x01 +#define PCI_SUBCLASS_I2C 0x03 uint32_t pci_read_config(uint8_t bus, uint8_t device, uint8_t function, uint8_t offset); void pci_write_config(uint8_t bus, uint8_t device, uint8_t function, uint8_t offset, uint32_t value); @@ -39,7 +42,7 @@ int pci_find_device(uint16_t vendor_id, uint16_t device_id, pci_device_t* device int pci_find_device_by_class(uint8_t class_code, uint8_t subclass, pci_device_t* device); // BAR access and bus mastering helpers -uint32_t pci_get_bar(pci_device_t *dev, int bar_num); +uint64_t pci_get_bar(pci_device_t *dev, int bar_num); void pci_enable_bus_mastering(pci_device_t *dev); void pci_enable_mmio(pci_device_t *dev); diff --git a/src/drivers/ACPI/acpi.c b/src/drivers/ACPI/acpi.c index 0e59a41..51a9704 100644 --- a/src/drivers/ACPI/acpi.c +++ b/src/drivers/ACPI/acpi.c @@ -7,6 +7,7 @@ #include "acpi_structures.h" #include "../I2C/acpi_i2c.h" +#include "../I2C/i2c_lpss.h" #include "acpi.h" #include "../sys/idt.h" #include "../core/limine.h" @@ -205,6 +206,7 @@ int acpi_init(void){ } acpi_i2c_enumerate(); + i2c_lpss_init(); return 0; } @@ -228,6 +230,17 @@ uint16_t acpi_irq_flags(uint32_t irq) { struct acpi_sdt *acpi_get_dsdt(void) { - if (!acpi_fadt || !acpi_fadt->dsdt) return NULL; - return (struct acpi_sdt *)p2v((uintptr_t)acpi_fadt->dsdt); + if (!acpi_fadt) return NULL; + + // Check 64-bit X_DSDT first (FADT revision >= 2 usually) + if (acpi_fadt->header.length >= 148 && acpi_fadt->x_dsdt) { + return (struct acpi_sdt *)p2v(acpi_fadt->x_dsdt); + } + + // Fallback to 32-bit DSDT + if (acpi_fadt->dsdt) { + return (struct acpi_sdt *)p2v((uintptr_t)acpi_fadt->dsdt); + } + + return NULL; } \ No newline at end of file diff --git a/src/drivers/ACPI/acpi_aml.c b/src/drivers/ACPI/acpi_aml.c index 040d711..93292cd 100644 --- a/src/drivers/ACPI/acpi_aml.c +++ b/src/drivers/ACPI/acpi_aml.c @@ -3,159 +3,120 @@ // This header needs to maintain in any file it is present in, as per the GPL license terms. #include "acpi_aml.h" -#include "kutils.h" -#include "kconsole.h" -#include -#include +#include "../core/kutils.h" +#include "../core/kconsole.h" -/// @brief Decode AML PkgLength field, advance past it -/// @param pp in/out byte pointer, advanced past the field on return -/// @param end hard bound; returns 0 if field exceeds it -/// @return total package length including the PkgLength bytes, 0 on error -static size_t decode_pkglen(const uint8_t **pp, const uint8_t *end) { +// Internal helpers for decoding AML + +static uint64_t decode_pkglen(const uint8_t **pp, const uint8_t *end) { if (*pp >= end) return 0; uint8_t b0 = *(*pp)++; - uint8_t extra = b0 >> 6; - size_t len; + uint8_t count = (b0 >> 6); + if (count == 0) return (b0 & 0x3F); - if (extra == 0) { - len = b0 & 0x3F; // 1-byte form: bits[5:0] - } else { - len = b0 & 0x0F; // multi-byte form: bits[3:0] are low nibble - if (*pp + extra > end) return 0; - for (uint8_t i = 0; i < extra; i++) - len |= (size_t)(*(*pp)++) << (4 + 8 * i); + uint64_t len = (b0 & 0x0F); + for (uint8_t i = 0; i < count; i++) { + if (*pp >= end) break; + len |= ((uint64_t)*(*pp)++) << (4 + 8 * i); } return len; } -/// @brief Decode the last NameSeg of an AML NameString into out -/// @param pp in/out byte pointer, advanced past the full name on return -/// @param end hard bound -/// @param out receives null-terminated 4-char NameSeg -static void decode_nameseg(const uint8_t **pp, const uint8_t *end, - char out[AML_NAME_LEN]) { - out[0] = '\0'; - if (*pp >= end) return; - - while (*pp < end && (**pp == '\\' || **pp == '^')) (*pp)++; - if (*pp >= end) return; - - uint8_t lead = **pp; - - if (lead == 0x00) { - (*pp)++; - return; - } else if (lead == 0x2E) { // DualNamePath two 4-char segs - (*pp)++; - if (*pp + 8 > end) return; - (*pp) += 4; - for (int i = 0; i < 4; i++) out[i] = (char)(*pp)[i]; - out[4] = '\0'; - (*pp) += 4; - } else if (lead == 0x2F) { // MultiNamePath count + N segs - (*pp)++; - if (*pp >= end) return; - uint8_t count = *(*pp)++; - if (*pp + (size_t)count * 4 > end) return; - const uint8_t *last = *pp + (count - 1) * 4; - for (int i = 0; i < 4; i++) out[i] = (char)last[i]; - out[4] = '\0'; - *pp += (size_t)count * 4; - } else { // plain 4-char NameSeg - if (*pp + 4 > end) return; - for (int i = 0; i < 4; i++) out[i] = (char)(*pp)[i]; - out[4] = '\0'; - (*pp) += 4; - } -} - -/// @brief Decode any AML integer data object (Zero, One, Byte, Word, DWord, QWord) -/// @param pp in/out byte pointer, advanced past the object on return -/// @param end hard bound -/// @return decoded integer value, 0 on unknown opcode (pointer not advanced) static uint64_t decode_integer(const uint8_t **pp, const uint8_t *end) { if (*pp >= end) return 0; uint8_t op = *(*pp)++; switch (op) { - case AML_ZERO_OP: return 0; - case AML_ONE_OP: return 1; - case AML_BYTE_PREFIX: - if (*pp + 1 > end) return 0; + case AML_ZERO_OP: return 0; + case AML_ONE_OP: return 1; + case AML_BYTE_PREFIX: { + if (*pp >= end) return 0; return *(*pp)++; + } case AML_WORD_PREFIX: { - if (*pp + 2 > end) return 0; - uint16_t v = (uint16_t)((*pp)[0]) | ((uint16_t)((*pp)[1]) << 8); - *pp += 2; return v; + if (*pp + 1 >= end) return 0; + uint16_t v = (uint16_t)(*pp)[0] | ((uint16_t)(*pp)[1] << 8); + *pp += 2; + return v; } case AML_DWORD_PREFIX: { - if (*pp + 4 > end) return 0; - uint32_t v = (uint32_t)((*pp)[0]) - | ((uint32_t)((*pp)[1]) << 8) - | ((uint32_t)((*pp)[2]) << 16) - | ((uint32_t)((*pp)[3]) << 24); - *pp += 4; return v; + if (*pp + 3 >= end) return 0; + uint32_t v = (uint32_t)(*pp)[0] | ((uint32_t)(*pp)[1] << 8) | + ((uint32_t)(*pp)[2] << 16) | ((uint32_t)(*pp)[3] << 24); + *pp += 4; + return v; } case AML_QWORD_PREFIX: { - if (*pp + 8 > end) return 0; + if (*pp + 7 >= end) return 0; uint64_t v = 0; - for (int i = 0; i < 8; i++) v |= (uint64_t)((*pp)[i]) << (8 * i); - *pp += 8; return v; + for (int i = 0; i < 8; i++) v |= ((uint64_t)(*pp)[i] << (8 * i)); + *pp += 8; + return v; } - default: - (*pp)--; - return 0; } + return 0; } -/// @brief Convert 32-bit packed EISAID value to ASCII HID string (e.g. "PNP0C0E") -/// @param id EISAID as decoded by decode_integer (little-endian from AML) -/// @param out receives null-terminated 8-char string -static void eisaid_to_str(uint32_t id, char out[AML_HID_LEN]) { - uint32_t v = ((id & 0x000000FF) << 24) - | ((id & 0x0000FF00) << 8) - | ((id & 0x00FF0000) >> 8) - | ((id & 0xFF000000) >> 24); +static void decode_nameseg(const uint8_t **pp, const uint8_t *end, char out[AML_NAME_LEN]) { + if (*pp + 3 >= end) { + memcpy(out, " ", 4); + out[4] = '\0'; + return; + } + memcpy(out, *pp, 4); + out[4] = '\0'; + *pp += 4; +} - out[0] = (char)('@' + ((v >> 26) & 0x1F)); - out[1] = (char)('@' + ((v >> 21) & 0x1F)); - out[2] = (char)('@' + ((v >> 16) & 0x1F)); +static void eisaid_to_str(uint32_t v, char out[AML_HID_LEN]) { + // Decode EISA ID: compressed 32-bit ID as per ACPI specification. + + // Some firmware swaps the bytes, so handle both + uint32_t id = v; + if (((id >> 26) & 0x1F) == 0) { + // Swap bytes + id = ((v >> 24) & 0xFF) | ((v >> 8) & 0xFF00) | ((v << 8) & 0xFF0000) | ((v << 24) & 0xFF000000); + } + out[0] = (char)('@' + ((id >> 26) & 0x1F)); + out[1] = (char)('@' + ((id >> 21) & 0x1F)); + out[2] = (char)('@' + ((id >> 16) & 0x1F)); + static const char hex[] = "0123456789ABCDEF"; - out[3] = hex[(v >> 12) & 0xF]; - out[4] = hex[(v >> 8) & 0xF]; - out[5] = hex[(v >> 4) & 0xF]; - out[6] = hex[(v >> 0) & 0xF]; + out[3] = hex[(id >> 12) & 0xF]; + out[4] = hex[(id >> 8) & 0xF]; + out[5] = hex[(id >> 4) & 0xF]; + out[6] = hex[(id >> 0) & 0xF]; out[7] = '\0'; } -/// @brief Parse _HID data object (string or EISAID DWord) into dev->hid -/// @param pp in/out byte pointer positioned at the data object -/// @param end hard bound -/// @param dev target device record -static void parse_hid(const uint8_t **pp, const uint8_t *end, - aml_i2c_dev_t *dev) { +static void parse_hid(const uint8_t **pp, const uint8_t *end, aml_i2c_dev_t *dev) { if (*pp >= end) return; + uint8_t op = **pp; - if (**pp == AML_STRING_PREFIX) { + if (op == AML_STRING_PREFIX) { (*pp)++; const char *s = (const char *)*pp; - strncpy(dev->hid, s, AML_HID_LEN - 1); - dev->hid[AML_HID_LEN - 1] = '\0'; - while (*pp < end && **pp) (*pp)++; + size_t len = 0; + while (*pp < end && **pp) { len++; (*pp)++; } if (*pp < end) (*pp)++; - } else if (**pp == AML_DWORD_PREFIX) { - uint32_t id = (uint32_t)decode_integer(pp, end); - eisaid_to_str(id, dev->hid); + + if (len >= AML_HID_LEN) len = AML_HID_LEN - 1; + memcpy(dev->hid, s, len); + dev->hid[len] = '\0'; + } else if (op == AML_DWORD_PREFIX) { + (*pp)++; + if (*pp + 3 < end) { + uint32_t v = (uint32_t)(*pp)[0] | ((uint32_t)(*pp)[1] << 8) | + ((uint32_t)(*pp)[2] << 16) | ((uint32_t)(*pp)[3] << 24); + eisaid_to_str(v, dev->hid); + *pp += 4; + } + } else { + (*pp)++; } } -/// @brief Parse _CRS Buffer for an I2cSerialBusV2 descriptor, fill dev CRS fields -/// @param pp in/out byte pointer positioned at BufferOp -/// @param end hard bound -/// @param dev receives slave_address, speed_hz, ten_bit_addr on success -static void parse_crs(const uint8_t **pp, const uint8_t *end, - aml_i2c_dev_t *dev) { +static void parse_crs(const uint8_t **pp, const uint8_t *end, aml_i2c_dev_t *dev) { if (*pp >= end || **pp != AML_BUFFER_OP) return; (*pp)++; @@ -175,14 +136,21 @@ static void parse_crs(const uint8_t **pp, const uint8_t *end, if (tag == ACPI_RESOURCE_END_TAG) break; if (tag == ACPI_LARGE_I2C_SERIAL_BUS) { - if (res + sizeof(aml_i2c_resource_t) > buf_end) break; - const aml_i2c_resource_t *r = (const aml_i2c_resource_t *)res; - if (r->serial_bus_type == ACPI_I2C_SERIAL_BUS_TYPE) { - dev->slave_address = r->slave_address; - dev->speed_hz = r->connection_speed; - dev->ten_bit_addr = (r->type_specific_flags & 0x01) ? 1 : 0; + uint16_t item_len = (uint16_t)(res[1]) | ((uint16_t)(res[2]) << 8); + if (res + 3 + item_len > buf_end) break; + + // Offset 5 is Serial Bus Type (1 = I2C) + if (res[5] == ACPI_I2C_SERIAL_BUS_TYPE && item_len >= 15) { + uint32_t speed = (uint32_t)res[12] | ((uint32_t)res[13] << 8) | + ((uint32_t)res[14] << 16) | ((uint32_t)res[15] << 24); + uint16_t addr = (uint16_t)res[16] | ((uint16_t)res[17] << 8); + + if (addr != 0) { + dev->speed_hz = speed; + dev->slave_address = addr; + } } - res += sizeof(uint8_t) + sizeof(uint16_t) + r->length; + res += 3 + item_len; continue; } @@ -194,21 +162,25 @@ static void parse_crs(const uint8_t **pp, const uint8_t *end, res += 1 + (tag & 0x07); } } - *pp = buf_end; } -static void record_power_state(const char name[AML_NAME_LEN], - aml_i2c_dev_t *dev) { +static void record_power_state(const char name[AML_NAME_LEN], aml_i2c_dev_t *dev) { if (memcmp(name, "_PS0", 4) == 0) dev->power_flags |= AML_PWR_HAS_PS0; else if (memcmp(name, "_PS3", 4) == 0) dev->power_flags |= AML_PWR_HAS_PS3; else if (memcmp(name, "_PR0", 4) == 0) dev->power_flags |= AML_PWR_HAS_PR0; else if (memcmp(name, "_PR3", 4) == 0) dev->power_flags |= AML_PWR_HAS_PR3; } -/// @brief Best-effort skip of one AML data object to keep the scan on track -/// @param pp in/out byte pointer, advanced past the object on return -/// @param end hard bound +bool is_touchpad_hid(const char *hid) { + if (!hid || !hid[0]) return false; + if (memcmp(hid, "PNP0C50", 7) == 0) return true; // Generic HID-over-I2C + if (memcmp(hid, "SYNA", 4) == 0) return true; // Synaptics + if (memcmp(hid, "ELAN", 4) == 0) return true; // Elantech + if (memcmp(hid, "ALPS", 4) == 0) return true; // Alps + return false; +} + static void skip_object(const uint8_t **pp, const uint8_t *end) { if (*pp >= end) return; uint8_t op = **pp; @@ -238,12 +210,7 @@ static void skip_object(const uint8_t **pp, const uint8_t *end) { } } -/// @brief Scan one Device() scope body for _HID, _CRS, _DSM, and power objects -/// @param p first byte of the scope body (after NameSeg) -/// @param end one past last byte of the scope -/// @param dev device record to populate -static void scan_device_scope(const uint8_t *p, const uint8_t *end, - aml_i2c_dev_t *dev) { +static void scan_device_scope(const uint8_t *p, const uint8_t *end, aml_i2c_dev_t *dev) { while (p < end) { uint8_t op = *p; @@ -252,9 +219,20 @@ static void scan_device_scope(const uint8_t *p, const uint8_t *end, char seg[AML_NAME_LEN]; decode_nameseg(&p, end, seg); - if (memcmp(seg, "_HID", 4) == 0) parse_hid(&p, end, dev); - else if (memcmp(seg, "_CRS", 4) == 0) parse_crs(&p, end, dev); - else { record_power_state(seg, dev); skip_object(&p, end); } + if (memcmp(seg, "_HID", 4) == 0) { + parse_hid(&p, end, dev); + } else if (memcmp(seg, "_CID", 4) == 0) { + aml_i2c_dev_t tmp = {0}; + parse_hid(&p, end, &tmp); + if (!dev->hid[0] || memcmp(dev->hid, "MCHP", 4) == 0) { + memcpy(dev->hid, tmp.hid, AML_HID_LEN); + } + } else if (memcmp(seg, "_CRS", 4) == 0) { + parse_crs(&p, end, dev); + } else { + record_power_state(seg, dev); + skip_object(&p, end); + } continue; } @@ -263,155 +241,160 @@ static void scan_device_scope(const uint8_t *p, const uint8_t *end, const uint8_t *method_start = p; size_t pkglen = decode_pkglen(&p, end); if (!pkglen) break; - const uint8_t *method_end = method_start + pkglen; if (method_end > end) method_end = end; - char seg[AML_NAME_LEN]; - const uint8_t *name_ptr = p; - decode_nameseg(&name_ptr, method_end, seg); - - if (memcmp(seg, "_DSM", 4) == 0) { - const uint8_t *body = name_ptr + 1; // skip MethodFlags we dont care abt it rn - const uint8_t *scan = body; - const uint8_t *guid = (const uint8_t *)ACPI_I2C_HID_DSM_GUID; - - while (scan + 16 < method_end) { - if (memcmp(scan, guid, 16) != 0) { scan++; continue; } - scan += 16; - while (scan + 4 < method_end) { - if (*scan != AML_RETURN_OP) { scan++; continue; } - scan++; - if (*scan != AML_PACKAGE_OP) { continue; } - scan++; - const uint8_t *ps = scan; - size_t pl = decode_pkglen(&scan, method_end); - if (!pl) break; - const uint8_t *pe = ps + pl; - if (pe > method_end || scan >= pe) break; - uint8_t nelem = *scan++; - if (nelem < 1) break; - uint64_t val = decode_integer(&scan, pe); - if (val <= 0xFFFF) { - dev->hid_desc_addr = (uint16_t)val; - dev->has_dsm = 1; - } - goto method_done; - } - break; + // Search the method body for a literal Buffer containing an I2C descriptor + const uint8_t *scan = p; + while (scan + 8 < method_end && dev->speed_hz == 0) { + if (*scan == AML_BUFFER_OP) { + const uint8_t *buf_ptr = scan; + parse_crs(&buf_ptr, method_end, dev); } - } else { - record_power_state(seg, dev); + scan++; } - - method_done: p = method_end; continue; } - // skip nested Device or Scope without recursing - if (op == AML_EXTOP_PREFIX && p + 1 < end && *(p + 1) == AML_DEVICE_OP) { - p += 2; - const uint8_t *s = p; - size_t l = decode_pkglen(&p, end); - if (l) p = s + l; else break; - continue; - } - - if (op == AML_SCOPE_OP) { - p++; - const uint8_t *s = p; - size_t l = decode_pkglen(&p, end); - if (l) p = s + l; else break; - continue; + // fallback: scan for I2C tag in buffers + if (op == AML_BUFFER_OP && dev->speed_hz == 0) { + const uint8_t *scan = p; + parse_crs(&scan, end, dev); } p++; } } +void aml_find_i2c_controllers(const uint8_t *aml, size_t len) { + if (!aml || !len) return; + const uint8_t *p = aml; + const uint8_t *end = aml + len; + + while (p + 10 < end) { + // Look for NameOp + "_HID" or similar pattern + if (*p == AML_NAME_OP) { + char name[AML_NAME_LEN]; + const uint8_t *scan = p + 1; + decode_nameseg(&scan, end, name); + if (memcmp(name, "_HID", 4) == 0) { + // Check if it matches INTC1040 or INTC1043 + if (scan + 8 < end && scan[0] == AML_STRING_PREFIX) { + const char *hid = (const char*)(scan + 1); + if (memcmp(hid, "INTC1040", 8) == 0 || memcmp(hid, "INTC1043", 8) == 0) { + + // Scan for Memory/DWord/QWord resource descriptors + const uint8_t *crs = scan; + while (crs + 14 < end && crs < scan + 2048) { + if (crs[0] == 0x86) { + // Memory32Fixed + } else if (crs[0] == 0x88) { + // DWord Memory + } else if (crs[0] == 0x89) { + // QWord Memory + } else if (crs[0] == 0x8A) { + // Extended Resource + } + crs++; + } + } + // Also look for the Touchpad itself + else if (is_touchpad_hid(hid)) { + } + } + } + } + p++; + } +} void aml_walk_table(const uint8_t *aml, size_t len, aml_walk_ctx_t *ctx) { if (!aml || !len || !ctx || !ctx->devices) return; - const uint8_t *p = aml; const uint8_t *end = aml + len; while (p + 2 < end) { - if (p[0] != AML_EXTOP_PREFIX || p[1] != AML_DEVICE_OP) { p++; continue; } - p += 2; + uint8_t op = *p; + if (op == AML_EXTOP_PREFIX && p[1] == AML_DEVICE_OP) { + p += 2; + const uint8_t *scope_start = p; + size_t pkglen = decode_pkglen(&p, end); + if (!pkglen) { p++; continue; } + const uint8_t *scope_end = scope_start + pkglen; + if (scope_end > end) scope_end = end; - const uint8_t *scope_start = p; - size_t pkglen = decode_pkglen(&p, end); - if (!pkglen) continue; + char dev_name[AML_NAME_LEN]; + decode_nameseg(&p, scope_end, dev_name); - const uint8_t *scope_end = scope_start + pkglen; - if (scope_end > end) scope_end = end; + if (ctx->count < ctx->capacity) { + aml_i2c_dev_t *dev = &ctx->devices[ctx->count]; + memset(dev, 0, sizeof(aml_i2c_dev_t)); + memcpy(dev->name, dev_name, AML_NAME_LEN); + scan_device_scope(p, scope_end, dev); - char dev_name[AML_NAME_LEN]; - decode_nameseg(&p, scope_end, dev_name); + if (dev->hid[0]) { + if (dev->speed_hz || is_touchpad_hid(dev->hid) || memcmp(dev->name, "TPD", 3) == 0) { + dev->valid = 1; + ctx->count++; + } + } - if (ctx->count >= ctx->capacity) break; - - aml_i2c_dev_t *dev = &ctx->devices[ctx->count]; - memset(dev, 0, sizeof(aml_i2c_dev_t)); - memcpy(dev->name, dev_name, AML_NAME_LEN); - - scan_device_scope(p, scope_end, dev); - - scan_device_scope(p, scope_end, dev); - - if (dev->hid[0] && dev->speed_hz) { - dev->valid = 1; - ctx->count++; + if (dev->slave_address == 0) { + const uint8_t *scan = p; + while (scan + 20 < scope_end && dev->slave_address == 0) { + if (*scan == ACPI_LARGE_I2C_SERIAL_BUS) { + uint16_t len = (uint16_t)(scan[1]) | ((uint16_t)(scan[2]) << 8); + if (scan + 3 + len <= scope_end && scan[5] == ACPI_I2C_SERIAL_BUS_TYPE && len >= 15) { + dev->speed_hz = (uint32_t)scan[12] | ((uint32_t)scan[13] << 8) | + ((uint32_t)scan[14] << 16) | ((uint32_t)scan[15] << 24); + dev->slave_address = (uint16_t)scan[16] | ((uint16_t)scan[17] << 8); + } + } + scan++; + } + } + } + p++; + } else if (op == AML_SCOPE_OP) { + p++; + size_t pkglen = decode_pkglen(&p, end); + // Just move into the scope and keep scanning + if (!pkglen) p++; + } else { + p++; } - - p = scope_end; } } -int aml_parse_s5(const uint8_t *aml, size_t len, - uint16_t *slp_typa, uint16_t *slp_typb) { +int aml_parse_s5(const uint8_t *aml, size_t len, uint16_t *slp_typa, uint16_t *slp_typb) { if (!aml || !len || !slp_typa || !slp_typb) return 0; - const uint8_t *end = aml + len; const uint8_t *p = aml; while (p + 4 < end) { - if (p[0] != '_' || p[1] != 'S' || p[2] != '5' || p[3] != '_') { - p++; continue; - } - p += 4; - - // scan ahead up to 4 bytes for PackageOp; on diff firmware layouts YMMV - int found_pkg = 0; - for (int skip = 0; skip < 4 && p + skip < end; skip++) { - if (p[skip] == AML_PACKAGE_OP) { - p += skip + 1; - found_pkg = 1; - break; + if (p[0] == '_' && p[1] == 'S' && p[2] == '5' && p[3] == '_') { + p += 4; + int found_pkg = 0; + for (int skip = 0; skip < 4 && p + skip < end; skip++) { + if (p[skip] == AML_PACKAGE_OP) { p += skip + 1; found_pkg = 1; break; } } + if (!found_pkg) continue; + const uint8_t *pkg_start = p; + size_t pkglen = decode_pkglen(&p, end); + if (!pkglen) continue; + const uint8_t *pkg_end = pkg_start + pkglen; + if (pkg_end > end) pkg_end = end; + if (p >= pkg_end) continue; + uint8_t num = *p++; + if (num < 2) continue; + uint64_t typa = decode_integer(&p, pkg_end); + uint64_t typb = decode_integer(&p, pkg_end); + *slp_typa = (uint16_t)((typa & 0x7) << 10); + *slp_typb = (uint16_t)((typb & 0x7) << 10); + return 1; } - if (!found_pkg) continue; - - const uint8_t *pkg_start = p; - size_t pkglen = decode_pkglen(&p, end); - if (!pkglen) continue; - - const uint8_t *pkg_end = pkg_start + pkglen; - if (pkg_end > end) pkg_end = end; - - if (p >= pkg_end) continue; - uint8_t num = *p++; - if (num < 2) continue; - - uint64_t typa = decode_integer(&p, pkg_end); - uint64_t typb = decode_integer(&p, pkg_end); - - *slp_typa = (uint16_t)((typa & 0x7) << 10); - *slp_typb = (uint16_t)((typb & 0x7) << 10); - return 1; + p++; } - return 0; } diff --git a/src/drivers/ACPI/acpi_aml.h b/src/drivers/ACPI/acpi_aml.h index beb4fea..7a3a6d1 100644 --- a/src/drivers/ACPI/acpi_aml.h +++ b/src/drivers/ACPI/acpi_aml.h @@ -7,6 +7,7 @@ #include #include +#include #define AML_ZERO_OP 0x00 #define AML_ONE_OP 0x01 @@ -82,8 +83,11 @@ typedef struct { /// @param aml first AML byte (SDT base + 36) /// @param len byte length of the AML region /// @param ctx output context; devices array must be pre-allocated +void aml_find_i2c_controllers(const uint8_t *aml, size_t len); void aml_walk_table(const uint8_t *aml, size_t len, aml_walk_ctx_t *ctx); +bool is_touchpad_hid(const char *hid); + /// @brief Scan DSDT AML for _S5_ and extract SLP_TYPa/b for S5 power-off /// @param aml first AML byte (DSDT base + 36) /// @param len byte length of the AML region diff --git a/src/drivers/I2C/acpi_i2c.c b/src/drivers/I2C/acpi_i2c.c index 609be2a..e1f2d14 100644 --- a/src/drivers/I2C/acpi_i2c.c +++ b/src/drivers/I2C/acpi_i2c.c @@ -8,6 +8,7 @@ #include "../ACPI/acpi_structures.h" #include "../core/kconsole.h" #include "../core/platform.h" +#include "../core/kutils.h" static aml_i2c_dev_t i2c_devices[ACPI_I2C_MAX_DEVICES]; static size_t i2c_device_count = 0; @@ -28,28 +29,74 @@ static void walk_all_ssdts(aml_walk_ctx_t *ctx) { struct acpi_rsdp *rsdp = acpi_get_rsdp(); if (!rsdp) return; + serial_write("[acpi_i2c] RSDP revision "); + serial_write_num(rsdp->revision); + serial_write("\n"); + + // Scan XSDT if available if (rsdp->revision >= 2 && rsdp->xsdt_address) { struct acpi_xsdt *xsdt = (struct acpi_xsdt *)p2v(rsdp->xsdt_address); size_t entries = (xsdt->header.length - sizeof(struct acpi_sdt)) / 8; + serial_write("[acpi_i2c] XSDT entries: "); serial_write_num(entries); serial_write("\n"); for (size_t i = 0; i < entries; i++) { struct acpi_sdt *sdt = (struct acpi_sdt *)p2v(xsdt->tables[i]); if (!sdt) continue; - if (__builtin_memcmp(sdt->signature, "SSDT", 4) == 0) + + serial_write("[acpi_i2c] Table: "); + char sig[5] = {0}; memcpy(sig, sdt->signature, 4); + char oem[7] = {0}; memcpy(oem, sdt->oem_id, 6); + char oem_t[9] = {0}; memcpy(oem_t, sdt->oem_table_id, 8); + serial_write(sig); serial_write(" "); serial_write(oem); + serial_write(":"); serial_write(oem_t); + serial_write(" len="); serial_write_num(sdt->length); + serial_write("\n"); + + // Deep search for _HID on ALL tables + const uint8_t *aml = (const uint8_t *)sdt + ACPI_SDT_HEADER_LEN; + size_t len = sdt->length - ACPI_SDT_HEADER_LEN; + for (size_t k = 0; k + 8 < len; k++) { + if (memcmp(&aml[k], "_HID", 4) == 0) { + serial_write("[acpi_i2c] FOUND _HID in "); serial_write(sig); + serial_write(" offset 0x"); serial_write_hex((uint32_t)k); + serial_write(" ID: "); + const uint8_t *id_ptr = &aml[k + 4]; + if (*id_ptr == AML_STRING_PREFIX) serial_write((const char *)id_ptr + 1); + else if (*id_ptr == AML_DWORD_PREFIX) serial_write_hex(*(uint32_t*)(id_ptr + 1)); + serial_write("\n"); + } + // Also search for raw 'SYNA' bytes (EISAID encoding) + if (memcmp(&aml[k], "\x53\x59\x4E\x41", 4) == 0) { + serial_write("[acpi_i2c] Found SYNA bytes at offset 0x"); + serial_write_hex((uint32_t)k); + serial_write("\n"); + } + } + + if (__builtin_memcmp(sdt->signature, "SSDT", 4) == 0) { + const uint8_t *aml_ptr = (const uint8_t *)sdt + ACPI_SDT_HEADER_LEN; + size_t aml_len = sdt->length - ACPI_SDT_HEADER_LEN; + aml_find_i2c_controllers(aml_ptr, aml_len); walk_sdt(sdt, ctx); + } } - return; } - // RSDT fallback - if (!rsdp->rsdt_address) return; - struct acpi_sdt *rsdt = (struct acpi_sdt *)p2v(rsdp->rsdt_address); - uint32_t *tables = (uint32_t *)((uint8_t *)rsdt + sizeof(struct acpi_sdt)); - size_t entries = (rsdt->length - sizeof(struct acpi_sdt)) / 4; - for (size_t i = 0; i < entries; i++) { - struct acpi_sdt *sdt = (struct acpi_sdt *)p2v(tables[i]); - if (!sdt) continue; - if (__builtin_memcmp(sdt->signature, "SSDT", 4) == 0) - walk_sdt(sdt, ctx); + // Also scan RSDT + if (rsdp->rsdt_address) { + struct acpi_sdt *rsdt = (struct acpi_sdt *)p2v(rsdp->rsdt_address); + uint32_t *tables = (uint32_t *)((uint8_t *)rsdt + sizeof(struct acpi_sdt)); + size_t entries = (rsdt->length - sizeof(struct acpi_sdt)) / 4; + serial_write("[acpi_i2c] RSDT entries: "); serial_write_num(entries); serial_write("\n"); + for (size_t i = 0; i < entries; i++) { + struct acpi_sdt *sdt = (struct acpi_sdt *)p2v(tables[i]); + if (!sdt) continue; + if (__builtin_memcmp(sdt->signature, "SSDT", 4) == 0) { + const uint8_t *aml_ptr = (const uint8_t *)sdt + ACPI_SDT_HEADER_LEN; + size_t aml_len = sdt->length - ACPI_SDT_HEADER_LEN; + aml_find_i2c_controllers(aml_ptr, aml_len); + walk_sdt(sdt, ctx); + } + } } } @@ -62,9 +109,12 @@ int acpi_i2c_enumerate(void) { .count = 0, }; - // Walk DSDT - pointed to by FADT, not listed in XSDT/RSDT + // Walk DSDT - check both 32-bit and 64-bit pointers struct acpi_sdt *dsdt = acpi_get_dsdt(); if (dsdt) { + serial_write("[acpi_i2c] Walking DSDT (len="); + serial_write_num(dsdt->length); + serial_write(")\n"); walk_sdt(dsdt, &ctx); } else { serial_write("[acpi_i2c] warning: DSDT not found\n"); @@ -75,31 +125,10 @@ int acpi_i2c_enumerate(void) { i2c_device_count = ctx.count; - for (size_t i = 0; i < i2c_device_count; i++) { - const aml_i2c_dev_t *d = &i2c_devices[i]; - serial_write("[acpi_i2c] "); - serial_write(d->name); - serial_write(" hid="); - serial_write(d->hid); - serial_write(" addr=0x"); - serial_write_hex(d->slave_address); - serial_write(" speed="); - serial_write_num(d->speed_hz); - serial_write(" Hz"); - if (d->ten_bit_addr) serial_write(" 10-bit"); - if (d->power_flags & (AML_PWR_HAS_PS0 | AML_PWR_HAS_PS3)) serial_write(" PS0/3"); - if (d->power_flags & (AML_PWR_HAS_PR0 | AML_PWR_HAS_PR3)) serial_write(" PR0/3"); - if (d->has_dsm) { - serial_write(" hid_desc_reg=0x"); - serial_write_hex(d->hid_desc_addr); - } - serial_write("\n"); - } - if (i2c_device_count != 0) { - log_ok("I2C enumeration complete"); - } else { - log_fail("No I2C devices found in ACPI tables"); + serial_write("[acpi_i2c] Enumerated "); + serial_write_num(i2c_device_count); + serial_write(" I2C devices from ACPI\n"); } return (int)i2c_device_count; diff --git a/src/drivers/I2C/i2c.c b/src/drivers/I2C/i2c.c new file mode 100644 index 0000000..18e15fb --- /dev/null +++ b/src/drivers/I2C/i2c.c @@ -0,0 +1,77 @@ +// Copyright (c) 2023-2026 Chris (boreddevnl) +// This software is released under the GNU General Public License v3.0. See LICENSE file for details. +// This header needs to maintain in any file it is present in, as per the GPL license terms. + +#include "i2c.h" +#include + +#define MAX_I2C_ADAPTERS 8 + +static i2c_adapter_t *g_i2c_adapters[MAX_I2C_ADAPTERS]; +static size_t g_i2c_adapter_count = 0; + +int i2c_adapter_register(i2c_adapter_t *adapter) { + if (!adapter || !adapter->master_xfer || !adapter->name) return -EINVAL; + if (g_i2c_adapter_count >= MAX_I2C_ADAPTERS) return -ENOSPC; + for (size_t i = 0; i < g_i2c_adapter_count; i++) { + if (g_i2c_adapters[i] == adapter) return -EEXIST; + if (g_i2c_adapters[i]->name && adapter->name && strcmp(g_i2c_adapters[i]->name, adapter->name) == 0) + return -EEXIST; + } + g_i2c_adapters[g_i2c_adapter_count++] = adapter; + adapter->active = true; + return 0; +} + +int i2c_adapter_unregister(i2c_adapter_t *adapter) { + if (!adapter) return -EINVAL; + for (size_t i = 0; i < g_i2c_adapter_count; i++) { + if (g_i2c_adapters[i] == adapter) { + adapter->active = false; + for (size_t j = i; j + 1 < g_i2c_adapter_count; j++) { + g_i2c_adapters[j] = g_i2c_adapters[j + 1]; + } + g_i2c_adapters[--g_i2c_adapter_count] = NULL; + return 0; + } + } + return -ENOENT; +} + +size_t i2c_adapter_count(void) { + return g_i2c_adapter_count; +} + +i2c_adapter_t *i2c_adapter_get(size_t index) { + if (index >= g_i2c_adapter_count) return NULL; + return g_i2c_adapters[index]; +} + +i2c_adapter_t *i2c_adapter_find_by_acpi(const char *hid) { + if (!hid) return NULL; + for (size_t i = 0; i < g_i2c_adapter_count; i++) { + const aml_i2c_dev_t *dev = g_i2c_adapters[i]->acpi_dev; + if (!dev || !dev->valid) continue; + if (memcmp(dev->hid, hid, AML_HID_LEN - 1) == 0) { + return g_i2c_adapters[i]; + } + } + return NULL; +} + +i2c_adapter_t *i2c_adapter_find_by_name(const char *name) { + if (!name) return NULL; + for (size_t i = 0; i < g_i2c_adapter_count; i++) { + if (g_i2c_adapters[i]->name && strcmp(g_i2c_adapters[i]->name, name) == 0) { + return g_i2c_adapters[i]; + } + } + return NULL; +} + +int i2c_master_xfer(i2c_adapter_t *adapter, i2c_msg_t *msgs, int num) { + if (!adapter || !msgs || num <= 0) return -EINVAL; + if (!adapter->active) return -ENODEV; + if (!adapter->master_xfer) return -ENOSYS; + return adapter->master_xfer(adapter, msgs, num); +} diff --git a/src/drivers/I2C/i2c.h b/src/drivers/I2C/i2c.h new file mode 100644 index 0000000..7709eab --- /dev/null +++ b/src/drivers/I2C/i2c.h @@ -0,0 +1,51 @@ +// Copyright (c) 2023-2026 Chris (boreddevnl) +// This software is released under the GNU General Public License v3.0. See LICENSE file for details. +// This header needs to maintain in any file it is present in, as per the GPL license terms. + +#ifndef I2C_H +#define I2C_H + +#include +#include +#include +#include "../core/errno.h" +#include "../ACPI/acpi_aml.h" + +#define I2C_M_RD (1 << 0) +#define I2C_M_TEN (1 << 1) +#define I2C_M_NOSTART (1 << 2) + +typedef enum { + I2C_SPEED_STANDARD = 100000, + I2C_SPEED_FAST = 400000, + I2C_SPEED_FAST_PLUS = 1000000, + I2C_SPEED_HIGH = 3400000 +} i2c_speed_t; + +typedef struct { + uint16_t addr; // 7-bit or 10-bit slave address + uint16_t flags; // I2C_M_* flags + uint16_t len; // number of bytes in buffer + uint8_t *buf; // data buffer +} i2c_msg_t; + +struct i2c_adapter; +typedef int (*i2c_master_xfer_fn)(struct i2c_adapter *adapter, i2c_msg_t *msgs, int num); + +typedef struct i2c_adapter { + const char *name; + const aml_i2c_dev_t *acpi_dev; + void *priv; + i2c_master_xfer_fn master_xfer; + bool active; +} i2c_adapter_t; + +int i2c_adapter_register(i2c_adapter_t *adapter); +int i2c_adapter_unregister(i2c_adapter_t *adapter); +size_t i2c_adapter_count(void); +i2c_adapter_t *i2c_adapter_get(size_t index); +i2c_adapter_t *i2c_adapter_find_by_acpi(const char *hid); +i2c_adapter_t *i2c_adapter_find_by_name(const char *name); +int i2c_master_xfer(i2c_adapter_t *adapter, i2c_msg_t *msgs, int num); + +#endif // I2C_H diff --git a/src/drivers/I2C/i2c_designware.c b/src/drivers/I2C/i2c_designware.c new file mode 100644 index 0000000..816628d --- /dev/null +++ b/src/drivers/I2C/i2c_designware.c @@ -0,0 +1,193 @@ +// Copyright (c) 2023-2026 Chris (boreddevnl) +// This software is released under the GNU General Public License v3.0. See LICENSE file for details. +// This header needs to maintain in any file it is present in, as per the GPL license terms. + +#include "i2c_designware.h" +#include "../../core/kutils.h" + +extern void serial_write(const char *str); +extern void serial_write_hex(uint32_t val); +extern void serial_write_num(uint64_t num); + +int dwi2c_init(volatile uint8_t *base, i2c_speed_t speed_hz, uint32_t input_clock_hz) { + if (!base) return -EINVAL; + if (!input_clock_hz) return -EINVAL; + + dwi2c_enable(base, false); + int timeout = 1000000; + while (timeout-- > 0) { + if (!dwi2c_is_enabled(base)) break; + } + if (timeout <= 0) return -ETIMEDOUT; + uint32_t con = IC_CON_MASTER | IC_CON_RESTART_EN | IC_CON_SLAVE_DISABLE; + if (speed_hz >= 1000000) { + con |= (3 << 1); // Fast-plus (1 MHz) + } else if (speed_hz >= 400000) { + con |= (2 << 1); // Fast (400 kHz) + } else { + con |= (1 << 1); // Standard (100 kHz) + } + + dwi2c_write_reg(base, IC_CON, con); + uint32_t hcnt, lcnt; + + if (speed_hz >= 1000000) { + // Fast-plus (1 MHz) — roughly 50% duty cycle + hcnt = input_clock_hz / (2 * 1000000); + lcnt = input_clock_hz / (2 * 1000000); + } else if (speed_hz >= 400000) { + // Fast (400 kHz) — roughly 50% duty cycle + hcnt = input_clock_hz / (2 * 400000); + lcnt = input_clock_hz / (2 * 400000); + } else { + // Standard (100 kHz) — roughly 50% duty cycle + hcnt = input_clock_hz / (2 * 100000); + lcnt = input_clock_hz / (2 * 100000); + } + + // Clamp to reasonable ranges (8-bit on some implementations) + if (hcnt > 0xFFFF) hcnt = 0xFFFF; + if (lcnt > 0xFFFF) lcnt = 0xFFFF; + if (hcnt < 6) hcnt = 6; // Minimum per spec + if (lcnt < 8) lcnt = 8; // Minimum per spec + + dwi2c_write_reg(base, IC_SS_SCL_HCNT, hcnt); + dwi2c_write_reg(base, IC_SS_SCL_LCNT, lcnt); + dwi2c_write_reg(base, IC_FS_SCL_HCNT, hcnt); + dwi2c_write_reg(base, IC_FS_SCL_LCNT, lcnt); + + uint32_t sda_hold = (input_clock_hz / 1000000) / 3; // Rough: ~300ns + if (sda_hold < 1) sda_hold = 1; + if (sda_hold > 0xFFFF) sda_hold = 0xFFFF; + dwi2c_write_reg(base, IC_SDA_HOLD, sda_hold); + + dwi2c_write_reg(base, IC_INTR_MASK, 0); + dwi2c_write_reg(base, IC_CLR_INTR, ~0); // Clear all pending + + uint8_t tx_depth = dwi2c_get_tx_fifo_depth(base); + uint8_t rx_depth = dwi2c_get_rx_fifo_depth(base); + dwi2c_write_reg(base, IC_TX_TL, tx_depth / 2); + dwi2c_write_reg(base, IC_RX_TL, rx_depth / 2); + dwi2c_enable(base, true); + + return 0; +} + +int dwi2c_write_byte(volatile uint8_t *base, uint8_t byte, bool send_stop) { + if (!dwi2c_is_enabled(base)) return -EINVAL; + + // Wait for TX FIFO not full + int timeout = 1000000; + while (!dwi2c_tx_fifo_not_full(base) && --timeout > 0); + if (timeout <= 0) return -ETIMEDOUT; + + // Write byte to data register + uint32_t cmd = IC_DATA_CMD_DAT(byte) | IC_DATA_CMD_CMD_WRITE; + if (send_stop) cmd |= IC_DATA_CMD_STOP; + + dwi2c_write_reg(base, IC_DATA_CMD, cmd); + + return 0; +} + +int dwi2c_read_byte(volatile uint8_t *base, uint8_t *byte, bool send_stop) { + if (!dwi2c_is_enabled(base) || !byte) return -EINVAL; + + // Issue read command + uint32_t cmd = IC_DATA_CMD_CMD_READ; + if (send_stop) cmd |= IC_DATA_CMD_STOP; + + dwi2c_write_reg(base, IC_DATA_CMD, cmd); + + // Check for aborts + uint32_t abort = dwi2c_read_reg(base, IC_TX_ABRT_SOURCE); + if (abort) { + return -EIO; + } + + // Wait for RX FIFO not empty + int timeout = 100000; + while (!dwi2c_rx_fifo_not_empty(base) && timeout-- > 0); + if (timeout <= 0) return -ETIMEDOUT; + + // Read byte + *byte = (uint8_t)(dwi2c_read_reg(base, IC_DATA_CMD) & 0xFF); + + return 0; +} + +int dwi2c_write_bytes(volatile uint8_t *base, const uint8_t *buf, size_t len) { + if (!dwi2c_is_enabled(base) || !buf || len == 0) return -EINVAL; + + uint8_t tx_depth = dwi2c_get_tx_fifo_depth(base); + size_t written = 0; + + while (written < len) { + // Check TX FIFO level + uint32_t tx_level = dwi2c_tx_fifo_level(base); + if (tx_level >= tx_depth) { + // Wait for space + int timeout = 1000000; + while (dwi2c_tx_fifo_level(base) >= tx_depth && --timeout > 0); + if (timeout <= 0) return -ETIMEDOUT; + } + + // Write byte + uint32_t cmd = IC_DATA_CMD_DAT(buf[written]) | IC_DATA_CMD_CMD_WRITE; + dwi2c_write_reg(base, IC_DATA_CMD, cmd); + written++; + } + + return (int)written; +} + +int dwi2c_read_bytes(volatile uint8_t *base, uint8_t *buf, size_t len) { + if (!dwi2c_is_enabled(base) || !buf || len == 0) return -EINVAL; + + size_t read_count = 0; + + while (read_count < len) { + // Issue read command + uint32_t cmd = IC_DATA_CMD_CMD_READ; + dwi2c_write_reg(base, IC_DATA_CMD, cmd); + + // Wait for data + int timeout = 1000000; + while (!dwi2c_rx_fifo_not_empty(base) && --timeout > 0); + if (timeout <= 0) return -ETIMEDOUT; + + // Read byte + buf[read_count] = (uint8_t)(dwi2c_read_reg(base, IC_DATA_CMD) & 0xFF); + read_count++; + } + + return (int)read_count; +} + +int dwi2c_wait_for_idle(volatile uint8_t *base) { + int timeout = 5000000; // ~5 second timeout + while (timeout-- > 0) { + uint32_t status = dwi2c_read_reg(base, IC_STATUS); + if (!(status & IC_STATUS_ACTIVITY)) return 0; + + // Check for errors + uint32_t raw_intr = dwi2c_read_reg(base, IC_RAW_INTR_STAT); + if (raw_intr & IC_INTR_TX_ABRT) { + dwi2c_write_reg(base, IC_CLR_TX_ABRT, 1); + return -EIO; + } + } + return -ETIMEDOUT; +} + +void dwi2c_dump_status(volatile uint8_t *base) { + serial_write("[DWI2C] Status: "); + serial_write_hex(dwi2c_read_reg(base, IC_STATUS)); + serial_write(" Enabled: "); + serial_write_hex(dwi2c_is_enabled(base) ? 1 : 0); + serial_write(" TX_LEVEL: "); + serial_write_num(dwi2c_tx_fifo_level(base)); + serial_write(" RX_LEVEL: "); + serial_write_num(dwi2c_rx_fifo_level(base)); + serial_write("\n"); +} diff --git a/src/drivers/I2C/i2c_designware.h b/src/drivers/I2C/i2c_designware.h new file mode 100644 index 0000000..46353ae --- /dev/null +++ b/src/drivers/I2C/i2c_designware.h @@ -0,0 +1,152 @@ +#ifndef I2C_DESIGNWARE_H +#define I2C_DESIGNWARE_H + +#include +#include +#include "i2c.h" + +// Register Offsets +#define IC_CON 0x00 +#define IC_TAR 0x04 +#define IC_SAR 0x08 +#define IC_DATA_CMD 0x10 +#define IC_SS_SCL_HCNT 0x14 +#define IC_SS_SCL_LCNT 0x18 +#define IC_FS_SCL_HCNT 0x1c +#define IC_FS_SCL_LCNT 0x20 +#define IC_INTR_STAT 0x2c +#define IC_INTR_MASK 0x30 +#define IC_RAW_INTR_STAT 0x34 +#define IC_RX_TL 0x38 +#define IC_TX_TL 0x3c +#define IC_CLR_INTR 0x40 +#define IC_CLR_RX_UNDER 0x44 +#define IC_CLR_RX_OVER 0x48 +#define IC_CLR_TX_OVER 0x4c +#define IC_CLR_RD_REQ 0x50 +#define IC_CLR_TX_ABRT 0x54 +#define IC_CLR_RX_DONE 0x58 +#define IC_CLR_ACTIVITY 0x5c +#define IC_CLR_STOP_DET 0x60 +#define IC_CLR_START_DET 0x64 +#define IC_CLR_GEN_CALL 0x68 +#define IC_ENABLE 0x6c +#define IC_STATUS 0x70 +#define IC_TXFLR 0x74 +#define IC_RXFLR 0x78 +#define IC_SDA_HOLD 0x7c +#define IC_TX_ABRT_SOURCE 0x80 +#define IC_ENABLE_STATUS 0x9c +#define IC_COMP_PARAM_1 0xf4 +#define IC_COMP_VERSION 0xf8 +#define IC_COMP_TYPE 0xfc + +// IC_CON bits +#define IC_CON_MASTER (1 << 0) +#define IC_CON_SPEED_STD (1 << 1) +#define IC_CON_SPEED_FAST (2 << 1) +#define IC_CON_SPEED_HIGH (3 << 1) +#define IC_CON_10BITADDR_SLAVE (1 << 3) +#define IC_CON_10BITADDR_MASTER (1 << 4) +#define IC_CON_RESTART_EN (1 << 5) +#define IC_CON_SLAVE_DISABLE (1 << 6) + +// IC_DATA_CMD bits +#define IC_DATA_CMD_DAT(x) ((x) & 0xFF) +#define IC_DATA_CMD_CMD_WRITE (0 << 8) +#define IC_DATA_CMD_CMD_READ (1 << 8) +#define IC_DATA_CMD_STOP (1 << 9) +#define IC_DATA_CMD_RESTART (1 << 10) + +// IC_STATUS bits +#define IC_STATUS_ACTIVITY (1 << 0) +#define IC_STATUS_TFNF (1 << 1) +#define IC_STATUS_TFEE (1 << 2) +#define IC_STATUS_RFNE (1 << 3) +#define IC_STATUS_RFF (1 << 4) +#define IC_STATUS_MST_ACTIVITY (1 << 5) + +// IC_RAW_INTR_STAT bits +#define IC_INTR_RX_UNDER (1 << 0) +#define IC_INTR_RX_OVER (1 << 1) +#define IC_INTR_RX_FULL (1 << 2) +#define IC_INTR_TX_OVER (1 << 3) +#define IC_INTR_TX_EMPTY (1 << 4) +#define IC_INTR_RD_REQ (1 << 5) +#define IC_INTR_TX_ABRT (1 << 6) +#define IC_INTR_RX_DONE (1 << 7) +#define IC_INTR_ACTIVITY (1 << 8) +#define IC_INTR_STOP_DET (1 << 9) +#define IC_INTR_START_DET (1 << 10) +#define IC_INTR_GEN_CALL (1 << 11) + +// Error codes +#ifndef EINVAL +#define EINVAL 22 +#endif +#ifndef ETIMEDOUT +#define ETIMEDOUT 110 +#endif +#ifndef EIO +#define EIO 5 +#endif + +// Inline register access +static inline void dwi2c_write_reg(volatile uint8_t *base, uint32_t reg, uint32_t val) { + *(volatile uint32_t*)(base + reg) = val; +} + +static inline uint32_t dwi2c_read_reg(volatile uint8_t *base, uint32_t reg) { + return *(volatile uint32_t*)(base + reg); +} + +static inline void dwi2c_enable(volatile uint8_t *base, bool enable) { + dwi2c_write_reg(base, IC_ENABLE, enable ? 1 : 0); +} + +static inline bool dwi2c_is_enabled(volatile uint8_t *base) { + return (dwi2c_read_reg(base, IC_ENABLE_STATUS) & 1) != 0; +} + +static inline bool dwi2c_tx_fifo_not_full(volatile uint8_t *base) { + return (dwi2c_read_reg(base, IC_STATUS) & IC_STATUS_TFNF) != 0; +} + +static inline bool dwi2c_rx_fifo_not_empty(volatile uint8_t *base) { + return (dwi2c_read_reg(base, IC_STATUS) & IC_STATUS_RFNE) != 0; +} + +static inline uint32_t dwi2c_tx_fifo_level(volatile uint8_t *base) { + return dwi2c_read_reg(base, IC_TXFLR); +} + +static inline uint32_t dwi2c_rx_fifo_level(volatile uint8_t *base) { + return dwi2c_read_reg(base, IC_RXFLR); +} + +static inline uint8_t dwi2c_get_tx_fifo_depth(volatile uint8_t *base) { + return (uint8_t)((dwi2c_read_reg(base, IC_COMP_PARAM_1) >> 16) & 0xFF) + 1; +} + +static inline uint8_t dwi2c_get_rx_fifo_depth(volatile uint8_t *base) { + return (uint8_t)((dwi2c_read_reg(base, IC_COMP_PARAM_1) >> 8) & 0xFF) + 1; +} + +static inline void dwi2c_set_target_addr(volatile uint8_t *base, uint16_t addr, bool ten_bit) { + uint32_t con = dwi2c_read_reg(base, IC_CON); + if (ten_bit) con |= IC_CON_10BITADDR_MASTER; + else con &= ~IC_CON_10BITADDR_MASTER; + dwi2c_write_reg(base, IC_CON, con); + dwi2c_write_reg(base, IC_TAR, addr); +} + +// Function prototypes +int dwi2c_init(volatile uint8_t *base, i2c_speed_t speed_hz, uint32_t input_clock_hz); +int dwi2c_write_byte(volatile uint8_t *base, uint8_t byte, bool send_stop); +int dwi2c_read_byte(volatile uint8_t *base, uint8_t *byte, bool send_stop); +int dwi2c_write_bytes(volatile uint8_t *base, const uint8_t *buf, size_t len); +int dwi2c_read_bytes(volatile uint8_t *base, uint8_t *buf, size_t len); +int dwi2c_wait_for_idle(volatile uint8_t *base); +void dwi2c_dump_status(volatile uint8_t *base); + +#endif diff --git a/src/drivers/I2C/i2c_lpss.c b/src/drivers/I2C/i2c_lpss.c new file mode 100644 index 0000000..2dcf2a6 --- /dev/null +++ b/src/drivers/I2C/i2c_lpss.c @@ -0,0 +1,471 @@ +// Copyright (c) 2023-2026 Chris (boreddevnl) +// This software is released under the GNU General Public License v3.0. See LICENSE file for details. +// This header needs to maintain in any file it is present in, as per the GPL license terms. + +#include "i2c_lpss.h" +#include "i2c_designware.h" +#include "acpi_i2c.h" +#include "../dev/pci.h" +#include "../core/platform.h" +#include "memory_manager.h" +#include "paging.h" +#include "../../core/kutils.h" +#include + +extern void serial_write(const char *str); +extern void serial_write_hex(uint32_t val); +extern void serial_write_num(uint64_t num); + +#define MAX_LPSS_I2C_CONTROLLERS 8 + +static i2c_lpss_controller_t controllers[MAX_LPSS_I2C_CONTROLLERS]; +static int controller_count = 0; + +static const aml_i2c_dev_t *acpi_devices[MAX_LPSS_I2C_CONTROLLERS]; + +static int i2c_lpss_master_xfer(i2c_adapter_t *adapter, i2c_msg_t *msgs, int num) { + if (!adapter || !msgs || num <= 0) return -EINVAL; + + i2c_lpss_controller_t *ctrl = (i2c_lpss_controller_t *)adapter->priv; + if (!ctrl || !ctrl->active) return -ENODEV; + + volatile uint8_t *base = (volatile uint8_t *)ctrl->base; + int rc = 0; + + for (int i = 0; i < num; i++) { + i2c_msg_t *msg = &msgs[i]; + if (!msg->buf || msg->len == 0) return -EINVAL; + + bool ten_bit = (msg->flags & I2C_M_TEN) != 0; + bool read = (msg->flags & I2C_M_RD) != 0; + bool stop = (i == num - 1); + bool restart = (i > 0); + + if (ctrl->is_packed) { + uint32_t con = 0x61; + if (ten_bit) con |= (1 << 4); + *(volatile uint32_t *)(ctrl->base + 0x00) = (msg->addr << 16) | con; + } else { + dwi2c_set_target_addr(base, msg->addr, ten_bit); + } + + if (read) { + for (uint16_t j = 0; j < msg->len; j++) { + uint32_t cmd = IC_DATA_CMD_CMD_READ; + if (j == 0 && restart) cmd |= IC_DATA_CMD_RESTART; + if (j == msg->len - 1 && stop) cmd |= IC_DATA_CMD_STOP; + + if (ctrl->is_packed) { + *(volatile uint32_t *)(ctrl->base + 0x04) = cmd; + int timeout = 1000000; + while (!((*(volatile uint32_t *)(ctrl->base + 0x04) >> 16) & (1 << 3)) && --timeout > 0); + if (timeout > 0) { + msg->buf[j] = (uint8_t)(*(volatile uint32_t *)(ctrl->base + 0x04) & 0xFF); + } else { + rc = -ETIMEDOUT; + break; + } + } else { + dwi2c_write_reg(base, IC_DATA_CMD, cmd); + uint32_t abort = dwi2c_read_reg(base, IC_TX_ABRT_SOURCE); + if (abort) { rc = -EIO; break; } + + int timeout = 100000; + while (!dwi2c_rx_fifo_not_empty(base) && timeout-- > 0); + if (timeout <= 0) { rc = -ETIMEDOUT; break; } + msg->buf[j] = (uint8_t)(dwi2c_read_reg(base, IC_DATA_CMD) & 0xFF); + } + } + if (rc < 0) break; + } else { + bool next_is_read = false; + if (i + 1 < num && (msgs[i + 1].flags & I2C_M_RD)) + next_is_read = true; + + for (uint16_t j = 0; j < msg->len; j++) { + uint32_t cmd = IC_DATA_CMD_DAT(msg->buf[j]) | IC_DATA_CMD_CMD_WRITE; + if (j == msg->len - 1 && stop && !next_is_read) cmd |= IC_DATA_CMD_STOP; + if (j == 0 && restart) cmd |= IC_DATA_CMD_RESTART; + + if (ctrl->is_packed) { + int timeout = 1000000; + while (!((*(volatile uint32_t *)(ctrl->base + 0x04) >> 16) & (1 << 1)) && --timeout > 0); // TFNF + if (timeout > 0) *(volatile uint32_t *)(ctrl->base + 0x04) = cmd; + else { rc = -ETIMEDOUT; break; } + } else { + int timeout = 1000000; + while (!dwi2c_tx_fifo_not_full(base) && --timeout > 0); + if (timeout <= 0) { rc = -ETIMEDOUT; break; } + dwi2c_write_reg(base, IC_DATA_CMD, cmd); + } + } + if (rc < 0) break; + } + + if (stop) { + rc = dwi2c_wait_for_idle(base); + if (rc != 0) return rc; + } + } + + return 0; +} + +static uint32_t lpss_read_packed(uintptr_t base, uint32_t dw_offset) { + if (dw_offset == 0x00) return *(volatile uint32_t *)(base + 0x00) & 0xFFFF; // IC_CON + if (dw_offset == 0x04) return (*(volatile uint32_t *)(base + 0x00) >> 16) & 0xFFFF; // IC_TAR + if (dw_offset == 0x10) return *(volatile uint32_t *)(base + 0x04) & 0xFFFF; // IC_DATA_CMD + if (dw_offset == 0x70) return *(volatile uint32_t *)(base + 0x04) >> 16; // IC_STATUS + return *(volatile uint32_t *)(base + dw_offset); // Fallback +} + +static void lpss_write_packed(uintptr_t base, uint32_t dw_offset, uint32_t val) { + if (dw_offset == 0x00) { + uint32_t tmp = *(volatile uint32_t *)(base + 0x00) & 0xFFFF0000; + *(volatile uint32_t *)(base + 0x00) = tmp | (val & 0xFFFF); + } else if (dw_offset == 0x04) { + uint32_t tmp = *(volatile uint32_t *)(base + 0x00) & 0x0000FFFF; + *(volatile uint32_t *)(base + 0x00) = tmp | ((val & 0xFFFF) << 16); + } else if (dw_offset == 0x10) { + uint32_t tmp = *(volatile uint32_t *)(base + 0x04) & 0xFFFF0000; + *(volatile uint32_t *)(base + 0x04) = tmp | (val & 0xFFFF); + } else { + *(volatile uint32_t *)(base + dw_offset) = val; + } +} + +static uint32_t lpss_read_private(uintptr_t base, uint32_t offset) { + return *(volatile uint32_t *)(base + offset); +} + +static void lpss_write_private(uintptr_t base, uint32_t offset, uint32_t value) { + *(volatile uint32_t *)(base + offset) = value; +} + +static int lpss_i2c_private_init(volatile uint8_t *base) { + if (!base) return -EINVAL; + + uint32_t clk = lpss_read_private((uintptr_t)base, LPSS_PRIVATE_CLOCK_GATE); + clk |= LPSS_CLOCK_GATE_CLK_EN; + lpss_write_private((uintptr_t)base, LPSS_PRIVATE_CLOCK_GATE, clk); + + uint32_t rst = lpss_read_private((uintptr_t)base, LPSS_PRIVATE_RESET); + rst |= LPSS_RESET_RESET_REL; + lpss_write_private((uintptr_t)base, LPSS_PRIVATE_RESET, rst); + + return 0; +} + + +static bool is_lpss_i2c_device(pci_device_t *dev) { + if (dev->vendor_id != 0x8086) return false; // Intel only + + // Standard I2C class + if (dev->class_code == PCI_CLASS_SERIAL_BUS_CONTROLLER && dev->subclass == PCI_SUBCLASS_I2C) + return true; + + // Signal Processing / Other (0x1180) - used by some LPSS implementations + if (dev->class_code == 0x11 && dev->subclass == 0x80) { + uint16_t id = dev->device_id; + // Sky Lake / Kaby Lake + if (id >= 0x9D60 && id <= 0x9D6F) return true; + if (id >= 0xA160 && id <= 0xA16F) return true; + // Whiskey / Coffee / Comet Lake + if (id >= 0x9DE8 && id <= 0x9DEB) return true; + if (id >= 0xA368 && id <= 0xA36B) return true; + if (id >= 0x02E8 && id <= 0x02EB) return true; + if (id >= 0x06E8 && id <= 0x06EB) return true; + // Ice Lake / Tiger Lake / Alder Lake / Raptor Lake + if (id >= 0x34E8 && id <= 0x34EB) return true; + if (id >= 0x9A00 && id <= 0x9AFF) return true; + if (id >= 0xA000 && id <= 0xA0FF) return true; + if (id >= 0x43E8 && id <= 0x43EB) return true; + if (id >= 0x51E8 && id <= 0x51EB) return true; + if (id >= 0x54E8 && id <= 0x54EB) return true; + if (id >= 0x7A50 && id <= 0x7A7D) return true; + if (id >= 0x7E50 && id <= 0x7E51) return true; + } + + return false; +} + +static int map_bar0_to_kernel(uint64_t bar0_phys, uintptr_t *kernel_addr) { + if (!kernel_addr) return -EINVAL; + if (bar0_phys == 0) return -EINVAL; + + // Map 4KB pages starting from bar0_phys + // Assume bar0 is at least 4KB + uint64_t virt_base = p2v(bar0_phys); + + // Map first 0x2000 bytes (8KB) to cover search range + registers + paging_map_page(paging_get_pml4_phys(), virt_base, bar0_phys, + PT_PRESENT | PT_RW | PT_CACHE_DISABLE); + paging_map_page(paging_get_pml4_phys(), virt_base + 0x1000, bar0_phys + 0x1000, + PT_PRESENT | PT_RW | PT_CACHE_DISABLE); + + *kernel_addr = virt_base; + return 0; +} + +static int scan_pci_for_lpss_i2c(void) { + pci_device_t pci_devs[256]; + int count = pci_enumerate_devices(pci_devs, 256); + + controller_count = 0; + for (int i = 0; i < count && controller_count < MAX_LPSS_I2C_CONTROLLERS; i++) { + if (!is_lpss_i2c_device(&pci_devs[i])) continue; + + pci_device_t *dev = &pci_devs[i]; + + // Enable MMIO and bus mastering + pci_enable_mmio(dev); + pci_enable_bus_mastering(dev); + + // Wake up device (D3 -> D0) + uint32_t status = pci_read_config(dev->bus, dev->device, dev->function, 0x06); + if (status & (1 << 4)) { // Capabilities List + uint8_t cap_ptr = (uint8_t)(pci_read_config(dev->bus, dev->device, dev->function, 0x34) & 0xFF); + while (cap_ptr) { + uint32_t cap = pci_read_config(dev->bus, dev->device, dev->function, cap_ptr); + if ((cap & 0xFF) == 0x01) { // Power Management + uint32_t pmcsr = pci_read_config(dev->bus, dev->device, dev->function, cap_ptr + 4); + if ((pmcsr & 0x03) != 0) { + serial_write("[I2C-LPSS] Performing Intel D0 Power Transition...\n"); + pmcsr &= ~0x03; // Set D0 + pmcsr |= (1 << 15); // Clear PME_Status (Intel requirement (fuck intel)) + pci_write_config(dev->bus, dev->device, dev->function, cap_ptr + 4, pmcsr); + + // Intel requirement: Wait at least 10ms after D3->D0 + for(volatile int j=0; j<10000000; j++); + } + break; + } + cap_ptr = (uint8_t)((cap >> 8) & 0xFF); + } + } + + // Enable Bus Master + MMIO + uint32_t cmd = pci_read_config(dev->bus, dev->device, dev->function, 0x04); + pci_write_config(dev->bus, dev->device, dev->function, 0x04, cmd | 0x06); + + // Read BAR0 (64-bit) + uint64_t bar0_phys = pci_get_bar(dev, 0); + uint64_t real_phys = bar0_phys; + + if (real_phys == 0) { + serial_write("[I2C-LPSS] Invalid BAR\n"); + continue; + } + + // Map to kernel address space + uintptr_t kernel_addr; + if (map_bar0_to_kernel(real_phys, &kernel_addr) != 0) { + serial_write("[I2C-LPSS] Failed to map BAR\n"); + continue; + } + + // Store controller info + i2c_lpss_controller_t *ctrl = &controllers[controller_count]; + ctrl->base = kernel_addr; + ctrl->base_phys = bar0_phys; + ctrl->pci_bus = dev->bus; + ctrl->pci_dev = dev->device; + ctrl->pci_fn = dev->function; + ctrl->vendor_id = dev->vendor_id; + ctrl->device_id = dev->device_id; + // Generation-specific configuration + uint16_t id = dev->device_id; + if ((id >= 0x9D60 && id <= 0x9D6F) || (id >= 0xA160 && id <= 0xA16F) || + (id >= 0x9DE8 && id <= 0x9DEB) || (id >= 0xA368 && id <= 0xA36B) || + (id >= 0x02E8 && id <= 0x02EB) || (id >= 0x06E8 && id <= 0x06EB)) { + // Sky Lake / Kaby Lake / Coffee Lake / Comet Lake + ctrl->input_clock_hz = 120000000; + ctrl->is_packed = false; + } else { + // Ice Lake / Tiger Lake / Alder Lake / Raptor Lake / Meteor Lake + ctrl->input_clock_hz = 133333333; + ctrl->is_packed = true; + } + ctrl->adapter.active = false; + ctrl->adapter.priv = ctrl; + ctrl->adapter.master_xfer = NULL; + ctrl->adapter.acpi_dev = NULL; + ctrl->adapter.name = ctrl->name; + memset(ctrl->name, 0, sizeof(ctrl->name)); + + char tmp[3]; + char *p = ctrl->name; + const char prefix[] = "lpss-i2c-"; + memcpy(p, prefix, sizeof(prefix) - 1); + p += sizeof(prefix) - 1; + + itoa_hex(dev->bus, tmp); + if (tmp[1] == '\0') { *p++ = '0'; *p++ = tmp[0]; } else { *p++ = tmp[0]; *p++ = tmp[1]; } + *p++ = ':'; + + itoa_hex(dev->device, tmp); + if (tmp[1] == '\0') { *p++ = '0'; *p++ = tmp[0]; } else { *p++ = tmp[0]; *p++ = tmp[1]; } + *p++ = '.'; + + itoa_hex(dev->function, tmp); + if (tmp[1] == '\0') { *p++ = '0'; *p++ = tmp[0]; } else { *p++ = tmp[0]; *p++ = tmp[1]; } + *p = '\0'; + + // Initialize LPSS private registers + if (lpss_i2c_private_init((volatile uint8_t *)kernel_addr) != 0) { + continue; + } + + // Initialize DesignWare core + if (dwi2c_init((volatile uint8_t *)kernel_addr, I2C_SPEED_STANDARD, ctrl->input_clock_hz) != 0) { + continue; + } + + ctrl->active = true; + acpi_devices[controller_count] = NULL; + ctrl->adapter.master_xfer = i2c_lpss_master_xfer; + + if (i2c_adapter_register(&ctrl->adapter) != 0) { + continue; + } + + controller_count++; + } + + return controller_count; +} + +static inline bool acpi_i2c_dev_is_hid(const aml_i2c_dev_t *dev) { + return dev && dev->valid && dev->has_dsm; +} + +static int match_acpi_devices(void) { + size_t acpi_count = acpi_i2c_count(); + + serial_write("[I2C-LPSS] Attempting to match "); + serial_write_num(acpi_count); + serial_write(" ACPI device(s) to "); + serial_write_num(controller_count); + serial_write(" controller(s)\n"); + + for (size_t acpi_idx = 0; acpi_idx < acpi_count; acpi_idx++) { + const aml_i2c_dev_t *acpi_dev = acpi_i2c_get(acpi_idx); + if (!acpi_dev || !acpi_dev->valid) { + serial_write("[I2C-LPSS] Skipping invalid ACPI device at index "); + serial_write_num(acpi_idx); + serial_write("\n"); + continue; + } + + serial_write("[I2C-LPSS] Considering ACPI device: "); + serial_write(acpi_dev->hid); + serial_write(" at slave 0x"); + serial_write_hex(acpi_dev->slave_address); + serial_write(" speed "); + serial_write_num(acpi_dev->speed_hz); + serial_write(" Hz\n"); + } + + for (size_t acpi_idx = 0; acpi_idx < acpi_count; acpi_idx++) { + const aml_i2c_dev_t *acpi_dev = acpi_i2c_get(acpi_idx); + if (!acpi_dev || !acpi_dev->valid) continue; + + bool is_touchpad = is_touchpad_hid(acpi_dev->hid) || + (memcmp(acpi_dev->name, "TPD", 3) == 0); + if (!is_touchpad) continue; + + for (int ctrl_idx = 0; ctrl_idx < controller_count; ctrl_idx++) { + i2c_lpss_controller_t *ctrl = &controllers[ctrl_idx]; + if (acpi_devices[ctrl_idx] != NULL) continue; + + acpi_devices[ctrl_idx] = acpi_dev; + ctrl->adapter.acpi_dev = acpi_dev; + serial_write("[I2C-LPSS] Priority match for touchpad: "); + serial_write(acpi_dev->hid); + serial_write(" to controller "); + serial_write(ctrl->name); + serial_write("\n"); + break; + } + } + + for (size_t acpi_idx = 0; acpi_idx < acpi_count; acpi_idx++) { + const aml_i2c_dev_t *acpi_dev = acpi_i2c_get(acpi_idx); + if (!acpi_dev || !acpi_dev->valid) continue; + + bool already_matched = false; + for (int j = 0; j < controller_count; j++) { + if (acpi_devices[j] == acpi_dev) { already_matched = true; break; } + } + if (already_matched) continue; + + for (int ctrl_idx = 0; ctrl_idx < controller_count; ctrl_idx++) { + i2c_lpss_controller_t *ctrl = &controllers[ctrl_idx]; + if (acpi_devices[ctrl_idx] != NULL) continue; + + acpi_devices[ctrl_idx] = acpi_dev; + ctrl->adapter.acpi_dev = acpi_dev; + serial_write("[I2C-LPSS] Matched device "); + serial_write(acpi_dev->hid); + serial_write(" to controller "); + serial_write(ctrl->name); + serial_write("\n"); + break; + } + } + + return 0; +} + +int i2c_lpss_init(void) { + serial_write("[I2C-LPSS] Initializing...\n"); + + int count = scan_pci_for_lpss_i2c(); + if (count <= 0) { + serial_write("[I2C-LPSS] No controllers found\n"); + return 0; + } + + serial_write("[I2C-LPSS] Found "); + serial_write_num(count); + serial_write(" controller(s)\n"); + match_acpi_devices(); + + for (int i = 0; i < controller_count; i++) { + if (controllers[i].active) { + volatile uint32_t *lpss_priv = (volatile uint32_t*)((uintptr_t)controllers[i].base + 0x200); + lpss_priv[1] = 3; // Release reset + lpss_priv[0] = 1; // Enable clock + lpss_priv[16] = 0x110; // LPSS_PRIVATE_REMAP_ADDR_EN + } + } + + return controller_count; +} + +int i2c_lpss_get_count(void) { + return controller_count; +} + +i2c_lpss_controller_t* i2c_lpss_get(int index) { + if (index < 0 || index >= controller_count) return NULL; + return &controllers[index]; +} + +i2c_lpss_controller_t* i2c_lpss_get_by_base(uint64_t base_phys) { + for (int i = 0; i < controller_count; i++) { + if (controllers[i].base_phys == base_phys) return &controllers[i]; + } + return NULL; +} + +const aml_i2c_dev_t* i2c_lpss_get_acpi_device(i2c_lpss_controller_t *ctrl) { + if (!ctrl) return NULL; + + for (int i = 0; i < controller_count; i++) { + if (&controllers[i] == ctrl) { + return acpi_devices[i]; + } + } + return NULL; +} diff --git a/src/drivers/I2C/i2c_lpss.h b/src/drivers/I2C/i2c_lpss.h new file mode 100644 index 0000000..72a150e --- /dev/null +++ b/src/drivers/I2C/i2c_lpss.h @@ -0,0 +1,42 @@ +// Copyright (c) 2023-2026 Chris (boreddevnl) +// This software is released under the GNU General Public License v3.0. See LICENSE file for details. +// This header needs to maintain in any file it is present in, as per the GPL license terms. + +#ifndef I2C_LPSS_H +#define I2C_LPSS_H + +#include +#include +#include "i2c.h" +#include "../dev/pci.h" + +typedef struct { + uintptr_t base; // MMIO base address (kernel virtual) + uint64_t base_phys; // MMIO physical address + uint8_t pci_bus; + uint8_t pci_dev; + uint8_t pci_fn; + uint16_t vendor_id; + uint16_t device_id; + uint32_t input_clock_hz; // Input clock frequency (typically 133 MHz on Tiger Lake) + bool active; + bool is_packed; // True for Tiger Lake 16-bit MMIO + char name[32]; + i2c_adapter_t adapter; +} i2c_lpss_controller_t; + + +#define LPSS_PRIVATE_CLOCK_GATE 0x200 // Clock gate control +#define LPSS_PRIVATE_RESET 0x204 // Software reset + +#define LPSS_CLOCK_GATE_CLK_EN (1 << 0) // Enable clock gating +#define LPSS_RESET_RESET_REL (1 << 0) // Release reset + + +int i2c_lpss_init(void); +int i2c_lpss_get_count(void); +i2c_lpss_controller_t* i2c_lpss_get(int index); +i2c_lpss_controller_t* i2c_lpss_get_by_base(uint64_t base_phys); +const aml_i2c_dev_t* i2c_lpss_get_acpi_device(i2c_lpss_controller_t *ctrl); + +#endif