mirror of
https://github.com/BoredDevNL/BoredOS.git
synced 2026-05-15 10:48:38 +00:00
Initial implementation of I2C subsystem and ACPI integration
This commit is contained in:
parent
deea3eaabc
commit
ca63ac5f59
16 changed files with 1446 additions and 307 deletions
|
|
@ -44,28 +44,34 @@ Two functions expose this mapping to the rest of the kernel:
|
||||||
|
|
||||||
## AML Parsing
|
## 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:
|
The walker handles:
|
||||||
|
|
||||||
- **`_HID`** - device identification, either as an inline string or a packed EISAID integer.
|
- **`_HID` & `_CID`** - Device identification. Supports inline strings and packed EISAID integers.
|
||||||
- **`_CRS`** - current resource settings; specifically scans for `I2cSerialBusV2` descriptors (large item tag `0x8E`) to extract slave address, bus speed, and addressing mode.
|
- Specifically recognizes touchpads/input devices: `PNP0C50`, `SYNA`, `ELAN`, `ALPS`.
|
||||||
- **`_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.
|
- **`_CRS`** - Current Resource Settings. Scans for `I2cSerialBusV2` descriptors (large item tag `0x8E`) to extract slave address and bus speed.
|
||||||
- **Power states** - records presence of `_PS0`, `_PS3`, `_PR0`, `_PR3` as flags.
|
- **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]
|
> [!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
|
## 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.
|
Devices are recorded in a global table, accessible via:
|
||||||
- `acpi_i2c_get(index)` - pointer to an `aml_i2c_dev_t` record containing the name, HID, slave address, speed, DSM result, and power flags.
|
|
||||||
|
- `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.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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. |
|
| 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/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/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. |
|
| 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.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. |
|
| Memory | [`memory/memory_manager.md`](architecture/memory/memory_manager.md) | Memory allocation and management systems. |
|
||||||
|
|
|
||||||
83
docs/architecture/hardware/i2c.md
Normal file
83
docs/architecture/hardware/i2c.md
Normal 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
18
src/core/errno.h
Normal 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
|
||||||
|
|
@ -98,10 +98,22 @@ int pci_find_device_by_class(uint8_t class_code, uint8_t subclass, pci_device_t*
|
||||||
return 0;
|
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;
|
if (!dev || bar_num < 0 || bar_num > 5) return 0;
|
||||||
uint8_t offset = 0x10 + (bar_num * 4);
|
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) {
|
void pci_enable_bus_mastering(pci_device_t *dev) {
|
||||||
|
|
|
||||||
|
|
@ -20,11 +20,14 @@ typedef struct {
|
||||||
uint8_t prog_if;
|
uint8_t prog_if;
|
||||||
} pci_device_t;
|
} pci_device_t;
|
||||||
|
|
||||||
#define PCI_CLASS_NETWORK_CONTROLLER 0x02
|
#define PCI_CLASS_NETWORK_CONTROLLER 0x02
|
||||||
#define PCI_CLASS_ETHERNET_CONTROLLER 0x00
|
#define PCI_CLASS_ETHERNET_CONTROLLER 0x00
|
||||||
#define PCI_CLASS_MASS_STORAGE 0x01
|
#define PCI_CLASS_MASS_STORAGE 0x01
|
||||||
#define PCI_SUBCLASS_SATA 0x06
|
#define PCI_CLASS_SERIAL_BUS_CONTROLLER 0x0C
|
||||||
#define PCI_SUBCLASS_IDE 0x01
|
|
||||||
|
#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);
|
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);
|
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);
|
int pci_find_device_by_class(uint8_t class_code, uint8_t subclass, pci_device_t* device);
|
||||||
|
|
||||||
// BAR access and bus mastering helpers
|
// 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_bus_mastering(pci_device_t *dev);
|
||||||
void pci_enable_mmio(pci_device_t *dev);
|
void pci_enable_mmio(pci_device_t *dev);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@
|
||||||
|
|
||||||
#include "acpi_structures.h"
|
#include "acpi_structures.h"
|
||||||
#include "../I2C/acpi_i2c.h"
|
#include "../I2C/acpi_i2c.h"
|
||||||
|
#include "../I2C/i2c_lpss.h"
|
||||||
#include "acpi.h"
|
#include "acpi.h"
|
||||||
#include "../sys/idt.h"
|
#include "../sys/idt.h"
|
||||||
#include "../core/limine.h"
|
#include "../core/limine.h"
|
||||||
|
|
@ -205,6 +206,7 @@ int acpi_init(void){
|
||||||
}
|
}
|
||||||
|
|
||||||
acpi_i2c_enumerate();
|
acpi_i2c_enumerate();
|
||||||
|
i2c_lpss_init();
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
@ -228,6 +230,17 @@ uint16_t acpi_irq_flags(uint32_t irq) {
|
||||||
|
|
||||||
|
|
||||||
struct acpi_sdt *acpi_get_dsdt(void) {
|
struct acpi_sdt *acpi_get_dsdt(void) {
|
||||||
if (!acpi_fadt || !acpi_fadt->dsdt) return NULL;
|
if (!acpi_fadt) return NULL;
|
||||||
return (struct acpi_sdt *)p2v((uintptr_t)acpi_fadt->dsdt);
|
|
||||||
|
// 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;
|
||||||
}
|
}
|
||||||
|
|
@ -3,159 +3,120 @@
|
||||||
// This header needs to maintain in any file it is present in, as per the GPL license terms.
|
// This header needs to maintain in any file it is present in, as per the GPL license terms.
|
||||||
|
|
||||||
#include "acpi_aml.h"
|
#include "acpi_aml.h"
|
||||||
#include "kutils.h"
|
#include "../core/kutils.h"
|
||||||
#include "kconsole.h"
|
#include "../core/kconsole.h"
|
||||||
#include <stdint.h>
|
|
||||||
#include <stddef.h>
|
|
||||||
|
|
||||||
/// @brief Decode AML PkgLength field, advance past it
|
// Internal helpers for decoding AML
|
||||||
/// @param pp in/out byte pointer, advanced past the field on return
|
|
||||||
/// @param end hard bound; returns 0 if field exceeds it
|
static uint64_t decode_pkglen(const uint8_t **pp, const uint8_t *end) {
|
||||||
/// @return total package length including the PkgLength bytes, 0 on error
|
|
||||||
static size_t decode_pkglen(const uint8_t **pp, const uint8_t *end) {
|
|
||||||
if (*pp >= end) return 0;
|
if (*pp >= end) return 0;
|
||||||
uint8_t b0 = *(*pp)++;
|
uint8_t b0 = *(*pp)++;
|
||||||
uint8_t extra = b0 >> 6;
|
uint8_t count = (b0 >> 6);
|
||||||
size_t len;
|
if (count == 0) return (b0 & 0x3F);
|
||||||
|
|
||||||
if (extra == 0) {
|
uint64_t len = (b0 & 0x0F);
|
||||||
len = b0 & 0x3F; // 1-byte form: bits[5:0]
|
for (uint8_t i = 0; i < count; i++) {
|
||||||
} else {
|
if (*pp >= end) break;
|
||||||
len = b0 & 0x0F; // multi-byte form: bits[3:0] are low nibble
|
len |= ((uint64_t)*(*pp)++) << (4 + 8 * i);
|
||||||
if (*pp + extra > end) return 0;
|
|
||||||
for (uint8_t i = 0; i < extra; i++)
|
|
||||||
len |= (size_t)(*(*pp)++) << (4 + 8 * i);
|
|
||||||
}
|
}
|
||||||
return len;
|
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) {
|
static uint64_t decode_integer(const uint8_t **pp, const uint8_t *end) {
|
||||||
if (*pp >= end) return 0;
|
if (*pp >= end) return 0;
|
||||||
uint8_t op = *(*pp)++;
|
uint8_t op = *(*pp)++;
|
||||||
switch (op) {
|
switch (op) {
|
||||||
case AML_ZERO_OP: return 0;
|
case AML_ZERO_OP: return 0;
|
||||||
case AML_ONE_OP: return 1;
|
case AML_ONE_OP: return 1;
|
||||||
case AML_BYTE_PREFIX:
|
case AML_BYTE_PREFIX: {
|
||||||
if (*pp + 1 > end) return 0;
|
if (*pp >= end) return 0;
|
||||||
return *(*pp)++;
|
return *(*pp)++;
|
||||||
|
}
|
||||||
case AML_WORD_PREFIX: {
|
case AML_WORD_PREFIX: {
|
||||||
if (*pp + 2 > end) return 0;
|
if (*pp + 1 >= end) return 0;
|
||||||
uint16_t v = (uint16_t)((*pp)[0]) | ((uint16_t)((*pp)[1]) << 8);
|
uint16_t v = (uint16_t)(*pp)[0] | ((uint16_t)(*pp)[1] << 8);
|
||||||
*pp += 2; return v;
|
*pp += 2;
|
||||||
|
return v;
|
||||||
}
|
}
|
||||||
case AML_DWORD_PREFIX: {
|
case AML_DWORD_PREFIX: {
|
||||||
if (*pp + 4 > end) return 0;
|
if (*pp + 3 >= end) return 0;
|
||||||
uint32_t v = (uint32_t)((*pp)[0])
|
uint32_t v = (uint32_t)(*pp)[0] | ((uint32_t)(*pp)[1] << 8) |
|
||||||
| ((uint32_t)((*pp)[1]) << 8)
|
((uint32_t)(*pp)[2] << 16) | ((uint32_t)(*pp)[3] << 24);
|
||||||
| ((uint32_t)((*pp)[2]) << 16)
|
*pp += 4;
|
||||||
| ((uint32_t)((*pp)[3]) << 24);
|
return v;
|
||||||
*pp += 4; return v;
|
|
||||||
}
|
}
|
||||||
case AML_QWORD_PREFIX: {
|
case AML_QWORD_PREFIX: {
|
||||||
if (*pp + 8 > end) return 0;
|
if (*pp + 7 >= end) return 0;
|
||||||
uint64_t v = 0;
|
uint64_t v = 0;
|
||||||
for (int i = 0; i < 8; i++) v |= (uint64_t)((*pp)[i]) << (8 * i);
|
for (int i = 0; i < 8; i++) v |= ((uint64_t)(*pp)[i] << (8 * i));
|
||||||
*pp += 8; return v;
|
*pp += 8;
|
||||||
|
return v;
|
||||||
}
|
}
|
||||||
default:
|
|
||||||
(*pp)--;
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @brief Convert 32-bit packed EISAID value to ASCII HID string (e.g. "PNP0C0E")
|
static void decode_nameseg(const uint8_t **pp, const uint8_t *end, char out[AML_NAME_LEN]) {
|
||||||
/// @param id EISAID as decoded by decode_integer (little-endian from AML)
|
if (*pp + 3 >= end) {
|
||||||
/// @param out receives null-terminated 8-char string
|
memcpy(out, " ", 4);
|
||||||
static void eisaid_to_str(uint32_t id, char out[AML_HID_LEN]) {
|
out[4] = '\0';
|
||||||
uint32_t v = ((id & 0x000000FF) << 24)
|
return;
|
||||||
| ((id & 0x0000FF00) << 8)
|
}
|
||||||
| ((id & 0x00FF0000) >> 8)
|
memcpy(out, *pp, 4);
|
||||||
| ((id & 0xFF000000) >> 24);
|
out[4] = '\0';
|
||||||
|
*pp += 4;
|
||||||
|
}
|
||||||
|
|
||||||
out[0] = (char)('@' + ((v >> 26) & 0x1F));
|
static void eisaid_to_str(uint32_t v, char out[AML_HID_LEN]) {
|
||||||
out[1] = (char)('@' + ((v >> 21) & 0x1F));
|
// Decode EISA ID: compressed 32-bit ID as per ACPI specification.
|
||||||
out[2] = (char)('@' + ((v >> 16) & 0x1F));
|
|
||||||
|
// 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";
|
static const char hex[] = "0123456789ABCDEF";
|
||||||
out[3] = hex[(v >> 12) & 0xF];
|
out[3] = hex[(id >> 12) & 0xF];
|
||||||
out[4] = hex[(v >> 8) & 0xF];
|
out[4] = hex[(id >> 8) & 0xF];
|
||||||
out[5] = hex[(v >> 4) & 0xF];
|
out[5] = hex[(id >> 4) & 0xF];
|
||||||
out[6] = hex[(v >> 0) & 0xF];
|
out[6] = hex[(id >> 0) & 0xF];
|
||||||
out[7] = '\0';
|
out[7] = '\0';
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @brief Parse _HID data object (string or EISAID DWord) into dev->hid
|
static void parse_hid(const uint8_t **pp, const uint8_t *end, aml_i2c_dev_t *dev) {
|
||||||
/// @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) {
|
|
||||||
if (*pp >= end) return;
|
if (*pp >= end) return;
|
||||||
|
uint8_t op = **pp;
|
||||||
|
|
||||||
if (**pp == AML_STRING_PREFIX) {
|
if (op == AML_STRING_PREFIX) {
|
||||||
(*pp)++;
|
(*pp)++;
|
||||||
const char *s = (const char *)*pp;
|
const char *s = (const char *)*pp;
|
||||||
strncpy(dev->hid, s, AML_HID_LEN - 1);
|
size_t len = 0;
|
||||||
dev->hid[AML_HID_LEN - 1] = '\0';
|
while (*pp < end && **pp) { len++; (*pp)++; }
|
||||||
while (*pp < end && **pp) (*pp)++;
|
|
||||||
if (*pp < end) (*pp)++;
|
if (*pp < end) (*pp)++;
|
||||||
} else if (**pp == AML_DWORD_PREFIX) {
|
|
||||||
uint32_t id = (uint32_t)decode_integer(pp, end);
|
if (len >= AML_HID_LEN) len = AML_HID_LEN - 1;
|
||||||
eisaid_to_str(id, dev->hid);
|
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
|
static void parse_crs(const uint8_t **pp, const uint8_t *end, aml_i2c_dev_t *dev) {
|
||||||
/// @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) {
|
|
||||||
if (*pp >= end || **pp != AML_BUFFER_OP) return;
|
if (*pp >= end || **pp != AML_BUFFER_OP) return;
|
||||||
(*pp)++;
|
(*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_RESOURCE_END_TAG) break;
|
||||||
|
|
||||||
if (tag == ACPI_LARGE_I2C_SERIAL_BUS) {
|
if (tag == ACPI_LARGE_I2C_SERIAL_BUS) {
|
||||||
if (res + sizeof(aml_i2c_resource_t) > buf_end) break;
|
uint16_t item_len = (uint16_t)(res[1]) | ((uint16_t)(res[2]) << 8);
|
||||||
const aml_i2c_resource_t *r = (const aml_i2c_resource_t *)res;
|
if (res + 3 + item_len > buf_end) break;
|
||||||
if (r->serial_bus_type == ACPI_I2C_SERIAL_BUS_TYPE) {
|
|
||||||
dev->slave_address = r->slave_address;
|
// Offset 5 is Serial Bus Type (1 = I2C)
|
||||||
dev->speed_hz = r->connection_speed;
|
if (res[5] == ACPI_I2C_SERIAL_BUS_TYPE && item_len >= 15) {
|
||||||
dev->ten_bit_addr = (r->type_specific_flags & 0x01) ? 1 : 0;
|
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;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -194,21 +162,25 @@ static void parse_crs(const uint8_t **pp, const uint8_t *end,
|
||||||
res += 1 + (tag & 0x07);
|
res += 1 + (tag & 0x07);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
*pp = buf_end;
|
*pp = buf_end;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void record_power_state(const char name[AML_NAME_LEN],
|
static void record_power_state(const char name[AML_NAME_LEN], aml_i2c_dev_t *dev) {
|
||||||
aml_i2c_dev_t *dev) {
|
|
||||||
if (memcmp(name, "_PS0", 4) == 0) dev->power_flags |= AML_PWR_HAS_PS0;
|
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, "_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, "_PR0", 4) == 0) dev->power_flags |= AML_PWR_HAS_PR0;
|
||||||
else if (memcmp(name, "_PR3", 4) == 0) dev->power_flags |= AML_PWR_HAS_PR3;
|
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
|
bool is_touchpad_hid(const char *hid) {
|
||||||
/// @param pp in/out byte pointer, advanced past the object on return
|
if (!hid || !hid[0]) return false;
|
||||||
/// @param end hard bound
|
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) {
|
static void skip_object(const uint8_t **pp, const uint8_t *end) {
|
||||||
if (*pp >= end) return;
|
if (*pp >= end) return;
|
||||||
uint8_t op = **pp;
|
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
|
static void scan_device_scope(const uint8_t *p, const uint8_t *end, aml_i2c_dev_t *dev) {
|
||||||
/// @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) {
|
|
||||||
while (p < end) {
|
while (p < end) {
|
||||||
uint8_t op = *p;
|
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];
|
char seg[AML_NAME_LEN];
|
||||||
decode_nameseg(&p, end, seg);
|
decode_nameseg(&p, end, seg);
|
||||||
|
|
||||||
if (memcmp(seg, "_HID", 4) == 0) parse_hid(&p, end, dev);
|
if (memcmp(seg, "_HID", 4) == 0) {
|
||||||
else if (memcmp(seg, "_CRS", 4) == 0) parse_crs(&p, end, dev);
|
parse_hid(&p, end, dev);
|
||||||
else { record_power_state(seg, dev); skip_object(&p, end); }
|
} 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;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -263,155 +241,160 @@ static void scan_device_scope(const uint8_t *p, const uint8_t *end,
|
||||||
const uint8_t *method_start = p;
|
const uint8_t *method_start = p;
|
||||||
size_t pkglen = decode_pkglen(&p, end);
|
size_t pkglen = decode_pkglen(&p, end);
|
||||||
if (!pkglen) break;
|
if (!pkglen) break;
|
||||||
|
|
||||||
const uint8_t *method_end = method_start + pkglen;
|
const uint8_t *method_end = method_start + pkglen;
|
||||||
if (method_end > end) method_end = end;
|
if (method_end > end) method_end = end;
|
||||||
|
|
||||||
char seg[AML_NAME_LEN];
|
// Search the method body for a literal Buffer containing an I2C descriptor
|
||||||
const uint8_t *name_ptr = p;
|
const uint8_t *scan = p;
|
||||||
decode_nameseg(&name_ptr, method_end, seg);
|
while (scan + 8 < method_end && dev->speed_hz == 0) {
|
||||||
|
if (*scan == AML_BUFFER_OP) {
|
||||||
if (memcmp(seg, "_DSM", 4) == 0) {
|
const uint8_t *buf_ptr = scan;
|
||||||
const uint8_t *body = name_ptr + 1; // skip MethodFlags we dont care abt it rn
|
parse_crs(&buf_ptr, method_end, dev);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
} else {
|
scan++;
|
||||||
record_power_state(seg, dev);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
method_done:
|
|
||||||
p = method_end;
|
p = method_end;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// skip nested Device or Scope without recursing
|
// fallback: scan for I2C tag in buffers
|
||||||
if (op == AML_EXTOP_PREFIX && p + 1 < end && *(p + 1) == AML_DEVICE_OP) {
|
if (op == AML_BUFFER_OP && dev->speed_hz == 0) {
|
||||||
p += 2;
|
const uint8_t *scan = p;
|
||||||
const uint8_t *s = p;
|
parse_crs(&scan, end, dev);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
p++;
|
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) {
|
void aml_walk_table(const uint8_t *aml, size_t len, aml_walk_ctx_t *ctx) {
|
||||||
if (!aml || !len || !ctx || !ctx->devices) return;
|
if (!aml || !len || !ctx || !ctx->devices) return;
|
||||||
|
|
||||||
const uint8_t *p = aml;
|
const uint8_t *p = aml;
|
||||||
const uint8_t *end = aml + len;
|
const uint8_t *end = aml + len;
|
||||||
|
|
||||||
while (p + 2 < end) {
|
while (p + 2 < end) {
|
||||||
if (p[0] != AML_EXTOP_PREFIX || p[1] != AML_DEVICE_OP) { p++; continue; }
|
uint8_t op = *p;
|
||||||
p += 2;
|
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;
|
char dev_name[AML_NAME_LEN];
|
||||||
size_t pkglen = decode_pkglen(&p, end);
|
decode_nameseg(&p, scope_end, dev_name);
|
||||||
if (!pkglen) continue;
|
|
||||||
|
|
||||||
const uint8_t *scope_end = scope_start + pkglen;
|
if (ctx->count < ctx->capacity) {
|
||||||
if (scope_end > end) scope_end = end;
|
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];
|
if (dev->hid[0]) {
|
||||||
decode_nameseg(&p, scope_end, dev_name);
|
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;
|
if (dev->slave_address == 0) {
|
||||||
|
const uint8_t *scan = p;
|
||||||
aml_i2c_dev_t *dev = &ctx->devices[ctx->count];
|
while (scan + 20 < scope_end && dev->slave_address == 0) {
|
||||||
memset(dev, 0, sizeof(aml_i2c_dev_t));
|
if (*scan == ACPI_LARGE_I2C_SERIAL_BUS) {
|
||||||
memcpy(dev->name, dev_name, AML_NAME_LEN);
|
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) {
|
||||||
scan_device_scope(p, scope_end, dev);
|
dev->speed_hz = (uint32_t)scan[12] | ((uint32_t)scan[13] << 8) |
|
||||||
|
((uint32_t)scan[14] << 16) | ((uint32_t)scan[15] << 24);
|
||||||
scan_device_scope(p, scope_end, dev);
|
dev->slave_address = (uint16_t)scan[16] | ((uint16_t)scan[17] << 8);
|
||||||
|
}
|
||||||
if (dev->hid[0] && dev->speed_hz) {
|
}
|
||||||
dev->valid = 1;
|
scan++;
|
||||||
ctx->count++;
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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,
|
int aml_parse_s5(const uint8_t *aml, size_t len, uint16_t *slp_typa, uint16_t *slp_typb) {
|
||||||
uint16_t *slp_typa, uint16_t *slp_typb) {
|
|
||||||
if (!aml || !len || !slp_typa || !slp_typb) return 0;
|
if (!aml || !len || !slp_typa || !slp_typb) return 0;
|
||||||
|
|
||||||
const uint8_t *end = aml + len;
|
const uint8_t *end = aml + len;
|
||||||
const uint8_t *p = aml;
|
const uint8_t *p = aml;
|
||||||
|
|
||||||
while (p + 4 < end) {
|
while (p + 4 < end) {
|
||||||
if (p[0] != '_' || p[1] != 'S' || p[2] != '5' || p[3] != '_') {
|
if (p[0] == '_' && p[1] == 'S' && p[2] == '5' && p[3] == '_') {
|
||||||
p++; continue;
|
p += 4;
|
||||||
}
|
int found_pkg = 0;
|
||||||
p += 4;
|
for (int skip = 0; skip < 4 && p + skip < end; skip++) {
|
||||||
|
if (p[skip] == AML_PACKAGE_OP) { p += skip + 1; found_pkg = 1; break; }
|
||||||
// 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 (!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;
|
p++;
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@
|
||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
#define AML_ZERO_OP 0x00
|
#define AML_ZERO_OP 0x00
|
||||||
#define AML_ONE_OP 0x01
|
#define AML_ONE_OP 0x01
|
||||||
|
|
@ -82,8 +83,11 @@ typedef struct {
|
||||||
/// @param aml first AML byte (SDT base + 36)
|
/// @param aml first AML byte (SDT base + 36)
|
||||||
/// @param len byte length of the AML region
|
/// @param len byte length of the AML region
|
||||||
/// @param ctx output context; devices array must be pre-allocated
|
/// @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);
|
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
|
/// @brief Scan DSDT AML for _S5_ and extract SLP_TYPa/b for S5 power-off
|
||||||
/// @param aml first AML byte (DSDT base + 36)
|
/// @param aml first AML byte (DSDT base + 36)
|
||||||
/// @param len byte length of the AML region
|
/// @param len byte length of the AML region
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@
|
||||||
#include "../ACPI/acpi_structures.h"
|
#include "../ACPI/acpi_structures.h"
|
||||||
#include "../core/kconsole.h"
|
#include "../core/kconsole.h"
|
||||||
#include "../core/platform.h"
|
#include "../core/platform.h"
|
||||||
|
#include "../core/kutils.h"
|
||||||
|
|
||||||
static aml_i2c_dev_t i2c_devices[ACPI_I2C_MAX_DEVICES];
|
static aml_i2c_dev_t i2c_devices[ACPI_I2C_MAX_DEVICES];
|
||||||
static size_t i2c_device_count = 0;
|
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();
|
struct acpi_rsdp *rsdp = acpi_get_rsdp();
|
||||||
if (!rsdp) return;
|
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) {
|
if (rsdp->revision >= 2 && rsdp->xsdt_address) {
|
||||||
struct acpi_xsdt *xsdt = (struct acpi_xsdt *)p2v(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;
|
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++) {
|
for (size_t i = 0; i < entries; i++) {
|
||||||
struct acpi_sdt *sdt = (struct acpi_sdt *)p2v(xsdt->tables[i]);
|
struct acpi_sdt *sdt = (struct acpi_sdt *)p2v(xsdt->tables[i]);
|
||||||
if (!sdt) continue;
|
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);
|
walk_sdt(sdt, ctx);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// RSDT fallback
|
// Also scan RSDT
|
||||||
if (!rsdp->rsdt_address) return;
|
if (rsdp->rsdt_address) {
|
||||||
struct acpi_sdt *rsdt = (struct acpi_sdt *)p2v(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));
|
uint32_t *tables = (uint32_t *)((uint8_t *)rsdt + sizeof(struct acpi_sdt));
|
||||||
size_t entries = (rsdt->length - sizeof(struct acpi_sdt)) / 4;
|
size_t entries = (rsdt->length - sizeof(struct acpi_sdt)) / 4;
|
||||||
for (size_t i = 0; i < entries; i++) {
|
serial_write("[acpi_i2c] RSDT entries: "); serial_write_num(entries); serial_write("\n");
|
||||||
struct acpi_sdt *sdt = (struct acpi_sdt *)p2v(tables[i]);
|
for (size_t i = 0; i < entries; i++) {
|
||||||
if (!sdt) continue;
|
struct acpi_sdt *sdt = (struct acpi_sdt *)p2v(tables[i]);
|
||||||
if (__builtin_memcmp(sdt->signature, "SSDT", 4) == 0)
|
if (!sdt) continue;
|
||||||
walk_sdt(sdt, ctx);
|
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,
|
.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();
|
struct acpi_sdt *dsdt = acpi_get_dsdt();
|
||||||
if (dsdt) {
|
if (dsdt) {
|
||||||
|
serial_write("[acpi_i2c] Walking DSDT (len=");
|
||||||
|
serial_write_num(dsdt->length);
|
||||||
|
serial_write(")\n");
|
||||||
walk_sdt(dsdt, &ctx);
|
walk_sdt(dsdt, &ctx);
|
||||||
} else {
|
} else {
|
||||||
serial_write("[acpi_i2c] warning: DSDT not found\n");
|
serial_write("[acpi_i2c] warning: DSDT not found\n");
|
||||||
|
|
@ -75,31 +125,10 @@ int acpi_i2c_enumerate(void) {
|
||||||
|
|
||||||
i2c_device_count = ctx.count;
|
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) {
|
if (i2c_device_count != 0) {
|
||||||
log_ok("I2C enumeration complete");
|
serial_write("[acpi_i2c] Enumerated ");
|
||||||
} else {
|
serial_write_num(i2c_device_count);
|
||||||
log_fail("No I2C devices found in ACPI tables");
|
serial_write(" I2C devices from ACPI\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
return (int)i2c_device_count;
|
return (int)i2c_device_count;
|
||||||
|
|
|
||||||
77
src/drivers/I2C/i2c.c
Normal file
77
src/drivers/I2C/i2c.c
Normal 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
51
src/drivers/I2C/i2c.h
Normal 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
|
||||||
193
src/drivers/I2C/i2c_designware.c
Normal file
193
src/drivers/I2C/i2c_designware.c
Normal 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");
|
||||||
|
}
|
||||||
152
src/drivers/I2C/i2c_designware.h
Normal file
152
src/drivers/I2C/i2c_designware.h
Normal 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
471
src/drivers/I2C/i2c_lpss.c
Normal 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;
|
||||||
|
}
|
||||||
42
src/drivers/I2C/i2c_lpss.h
Normal file
42
src/drivers/I2C/i2c_lpss.h
Normal 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
|
||||||
Loading…
Reference in a new issue