Initial implementation of I2C subsystem and ACPI integration

This commit is contained in:
boreddevnl 2026-05-15 01:54:08 +02:00
parent deea3eaabc
commit ca63ac5f59
16 changed files with 1446 additions and 307 deletions

View file

@ -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.
---

View file

@ -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. |

View file

@ -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);
```

18
src/core/errno.h Normal file
View file

@ -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

View file

@ -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) {

View file

@ -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);

View file

@ -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;
}

View file

@ -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 <stdint.h>
#include <stddef.h>
#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;
}

View file

@ -7,6 +7,7 @@
#include <stdint.h>
#include <stddef.h>
#include <stdbool.h>
#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

View file

@ -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;

77
src/drivers/I2C/i2c.c Normal file
View file

@ -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 <string.h>
#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);
}

51
src/drivers/I2C/i2c.h Normal file
View file

@ -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 <stdint.h>
#include <stddef.h>
#include <stdbool.h>
#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

View file

@ -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");
}

View file

@ -0,0 +1,152 @@
#ifndef I2C_DESIGNWARE_H
#define I2C_DESIGNWARE_H
#include <stdint.h>
#include <stdbool.h>
#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

471
src/drivers/I2C/i2c_lpss.c Normal file
View file

@ -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 <string.h>
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;
}

View file

@ -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 <stdint.h>
#include <stdbool.h>
#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