The engine is shared with the firmware, and until now the driver never
returned it: at shutdown the BIOS finds whatever command state the last
transfer left behind. ws1-mint shows an intermittent poweroff hang
isolated to machine_power_off() -> ACPI _S5 with the engine in session
use; a BIOS SMI handler wedging on a mid-session engine at the S5 trap
is the working hypothesis. Handing the engine back the way the
firmware left it is correct hygiene regardless of that outcome.
- probe snapshots both pristine CMD words before the driver's first
transfer, and fails if they cannot be read: an unreadable snapshot
would turn the handback into writing garbage
- new .shutdown callback (shutdown and kexec): mark both adapters
suspended, drain the possible in-flight transfer under the lock,
wait for the engine to go idle, restore the pristine words with GO
masked. Best effort: it never blocks the power-off
- resume refreshes the snapshot after the firmware-polling re-check
passes, since the firmware demonstrably drives the engine across
suspend; an unreadable refresh leaves the adapters suspended
- the unbind comment now distinguishes unbind (state left as-is, next
user is another instance of this driver) from shutdown (next user is
the firmware)
Version 1.0.4.
Subject bumped to v4, the v3 lore link added, and a "Changes since v3"
section written for the respin: the toggle measurement and its
reversal, the check fixes, the resume re-check, the register rename,
the jc42 and Skylake-SP clarifications, and the clang W=1 warning the
kernel test robot reported, with its Reported-by and Closes tags.
A DRAFT banner stays at the top until the posting; the send artifacts
under patches/ are generated from this text with the banner stripped.
All of this came out of a self-review of v3 plus one robot report; none
of it changes what the driver supports.
The read-back check required the whole command word to come back as
written, and the interference check on the driven channel compared
against the pre-transfer word. Both implicitly demanded that every
undocumented bit of the command register reads as zero, which held on
the test system but is not something to assume across steppings.
Compare only the bits this driver produces, with the post-write
read-back as the baseline for the driven channel; GO clearing remains
the single permitted difference.
Read the result before the interference check, not after, so the check
also covers the window in which the data latch is read.
Re-run the TSOD-polling check on resume and keep the adapters suspended
if firmware enabled polling across suspend; the probe-time refusal
cannot see that, and the S3 observation in the cover letter is exactly
this scenario.
Stop before writing a saved command word back if the config read that
fetched it had already failed.
Rename the register accessors: the command register is CMD and the data
latch DATA. v3 called the command register DATA and the data latch
CTRL, which read backwards.
Cast ~COMMAND_OUR_BITS to u32 in the interference check. BIT() and
GENMASK() are unsigned long, so the complement was computed in 64 bits
and truncated on assignment; the truncated value was already correct,
this only silences a clang W=1 -Wconstant-conversion warning.
Reported-by: kernel test robot <[email protected]>
Closes: https://lore.kernel.org/oe-kbuild-all/[email protected]/
The Kconfig help and the .rst now say that PCI ID 8086:2085 also
matches Skylake-SP / Cascade Lake-SP servers, where a BMC is far more
likely to own the engine, and the .rst states plainly that jc42 itself
has not been run here, only the transactions it needs.
The read-back check cannot tell two identical commands in a row apart:
the done bits are latched and the stale word differs in no bit from the
new one. Alternating bit 29 per command would have closed that blind
spot, but only if the engine executes commands with the bit at either
polarity, and every word ever observed from firmware has it set.
This probe answers the question with read-only SPD traffic: a baseline
run with the bit set, the same reads with it clear, then an alternating
run. Measured 2026-07-28: exit 3, the engine consumes GO but sets the
error bit for every command with bit 29 clear, 8/8, while interleaved
reads with the bit set keep working. The bit is must-be-set, not a
toggle, so the alternation scheme is dead and the blind spot is
documented at imc_command_landed() instead. The probe stays as the
evidence behind that paragraph.
The documentation's sample scan predates SPD instantiation at probe. It
shows 0x36, 0x50 and 0x52 as detected addresses, but the driver claims
them itself now, so a real run reports UU there and 0x37 as well. The
example printed output the driver can no longer produce.
Measured on the X299 system, both channels identical:
0 1 2 3 4 5 6 7 8 9 a b c d e f
20: -- -- -- -- -- -- -- 27 -- -- -- -- -- -- -- --
30: 30 31 -- -- 34 35 UU UU -- -- -- -- -- -- -- --
50: UU -- UU -- -- -- -- -- -- -- -- -- -- -- -- --
Say what the UU entries are, since the section that explains
instantiation comes further down and a reader scanning the bus meets
them first.
The verification section told the reader to find the adapter with
i2cdetect -l and then handed them "i2cdetect -y -r 6" to paste, along
with a 6-0050 sysfs path and an i2c-6 new_device path. Bus numbers are
handed out in probe order and move: on the development machine the iMC
channels are now 7 and 8, and 6 is the PCH i801 adapter, so the example
scans the wrong bus and finds nothing.
Derive the number once into $CH0 and use it everywhere, and say that the
sample output is one machine's assignment rather than a constant.
Also note that the SPD addresses report as UU. They are instantiated at
probe now, so a correct scan shows no number there, which reads like a
failed probe if the page does not say otherwise.
The file still carried the v5 subject and the "v3 and v4 were never
posted" line from the earlier bookkeeping. Version numbers count
postings, and the series that went out on 2026-07-28 went out as v3,
which is what patches/v3-0000-cover-letter.patch contains. Aligning the
tracked file with the mail that was actually sent.
Posted 2026-07-28:
https://lore.kernel.org/linux-i2c/[email protected]/
The badge tracks the new thread, and the local Claude Code settings
directory joins the gitignore: it holds machine-local permissions and
must never reach the public mirror.
The CI badge now points explicitly at main, the retracted CF8/CFC claim
is attributed to v2 (the only revision ever posted), the next posting is
v3, the DKMS version follows the VERSION file instead of a hardcoded
1.0.0, and the verification section catches up with the driver: Receive
Byte and Send Byte exist, i2cdetect scans in read mode, the SPD EEPROMs
are instantiated at probe and owned by ee1004, jc42 stays manual.
The TSOD-polling check silently skipped a channel whose SMBCNTL read
failed, so on a system without ECAM the driver bound with the check
silently not performed. Binding is still allowed - the register is
best-effort on top of the allow_unsafe_access gate, like the existing
warning when no iMC channel function is found - but say so in the log
instead of pretending the check ran.
Call i2c_register_spd() per adapter, like i801 and piix4, instead of
telling the user to echo into new_device. The helper counts populated
slots from DMI and probes upwards from 0x50 via i2c_new_scanned_device(),
so it claims no address that does not answer; each channel carries half
the DIMMs and the scan is per adapter, so each finds its own. Verified:
four clients on load, two per channel, none on the empty slots, and
decode-dimms works with no manual step.
This needed Receive Byte to be worth doing — without I2C_FUNC_SMBUS_BYTE
ee1004 would not have bound to what was instantiated.
The write_disable variant is deliberate. This part is DDR4 only, where
the two are equivalent (the split gates spd5118 on DDR5), and of two
equivalent calls the one that does not read as "this driver enables SPD
writes" is the better name to leave in a driver whose bus is the memory
bus. In-tree convention is write_enable; the cover letter offers to
switch.
Kconfig gains select I2C_SMBUS.
Out-of-tree, compat.h maps the call onto the older unsplit
i2c_register_spd() where that is what the target kernel has. It is force
included from the Makefile so i2c-imc-skylake.c stays byte-identical to
the submitted file, and the Makefile detects the API by grepping the
target kernel's header rather than testing a version number, which keeps
it correct on backported trees. Builds clean against both 6.8 and 7.0
headers; DKMS 1.0.2 installs on all four kernels here.
test-smoke.sh needed two fixes to keep testing what it claims:
- A bound driver owns its address, so the raw transaction checks lost
the SPDs the moment the driver started instantiating them. The script
now unbinds ee1004 for the duration rather than deleting the clients:
sysfs delete_device only removes clients that new_device created,
and unbinding is sufficient because i2c-dev refuses an address only
while a driver is bound (i2cdev_check: `return dev->driver ? -EBUSY
: 0`). Rebound from an EXIT trap.
- Those checks were guarded on an SPD address having been found, and
when the lookup failed they vanished from the run silently: the suite
reported "12 passed, 0 failed" while seven checks had not executed.
Skipped checks are now counted and printed, which is how the above
was noticed at all.
Smoke suite: 20 passed, 0 failed, 0 skipped. checkpatch --strict 0/0/0,
in-tree W=1 and Sparse clean.
A bound ee1004 owns 0x50-0x57, so userspace loses /dev/i2c access to
them. On this bus that is not academic: furyd's SPD guard reports
0/4 present while ee1004 holds the addresses.
The engine encodes an address-only transaction in command word bit 18,
which is what SMBus Receive Byte and Send Byte need and what was missing
for ee1004 to bind.
The bit is not documented for the PCU function. It was located by
structure rather than by sweeping: SMBCMD on the iMC channel function
(datasheet ref. 614073 section 3.1.8) places PNTR_SEL immediately above
WORD_ACCESS, and word access here is bit 17. Bit 17 had itself been
unknown until it turned out to be word access, and an earlier sweep of
this register covered bits 31:20 only, so 18 was untried rather than
already ruled out.
Validated against the device rather than against the register. An EEPROM
auto-increments its pointer after a read, so priming the pointer and
repeating the read with the bit set has a predicted answer: the next
byte, not the one named. Eight consecutive reads returned SPD bytes 0x01
through 0x08 in order, matching an ordinary dump. Send Byte was confirmed
on the DDR4 page-select addresses, which hold a latch and store no data,
so the page state is itself the readout.
Result: ee1004 binds on all four SPD addresses and decode-dimms decodes
all four modules with both CRCs valid on each. The part number comes off
page 1, so the page select runs end to end. The four module serial
numbers are unchanged from the baseline recorded before this series.
SMBus Quick stays unimplemented. Beyond having no obvious encoding, a
write into the 0x30-0x37 range is how an EE1004 SPD is write-protected,
permanently on many modules, so an adapter on the memory bus should not
advertise a probe that writes. i2cdetect scans in read mode.
Also fix test-smoke.sh resolving the module through modinfo, which
silently tested the installed DKMS build instead of the one just
compiled. It now prefers the local .ko and prints which it loaded.
Smoke suite: 19 passed, 0 failed. checkpatch --strict 0/0/0, in-tree W=1
and Sparse clean.
The htmldocs step failed on its own success criterion. The only diagnostic
in the log was
WARNING: dot(1) not found, for better output quality install graphviz
which says nothing about this patch. Failing the job on any occurrence of
"warning" anywhere in a kernel documentation build is too blunt: that build
emits diagnostics that depend on what happens to be installed on the runner.
Install graphviz so the warning is gone for the right reason, and narrow the
check to diagnostics that actually name i2c-imc-skylake, which is what the
step was meant to verify.
Both Clang jobs broke without the driver changing. GCC passed everywhere it
actually compiles: out-of-tree, in-tree x86_64, checkpatch and shellcheck.
The out-of-tree LLVM=1 job never reached this source. The runner now ships
6.17.0-1020-azure headers built with GCC, and their Makefiles pass flags
clang rejects outright:
clang: error: unknown argument: '-fconserve-stack'
clang: error: unsupported argument 'bounds-strict' to option '-fsanitize='
clang: error: unsupported option '-mrecord-mcount'
Building a module with a different compiler than the kernel it links against
is not something to paper over, and it was testing nothing useful: the
meaningful Clang coverage is the in-tree job, which controls the whole
build. Drop Clang from the out-of-tree job and say why in a comment.
The in-tree arm64 job failed at defconfig with "linker 'ld.lld' not found".
LLVM=1 wants the whole LLVM toolchain and ld.lld ships in its own package,
which the runner no longer pulls in as a dependency of llvm. Install lld.
Since the out-of-tree job no longer needs a matrix, its steps lose the
per-compiler conditionals and shellcheck moves into the dependency install
rather than being fetched mid-job.
The CI shellcheck step would have failed on three unused variables. Two were
leftovers in probe-cf8-transaction.sh from before the status decoding was
simplified; the third was a loop counter in probe-imc-documented.sh that
nothing reads.
While there: probe-cf8-transaction.sh still described itself in terms of "patch
1/2 of the series", which no longer exists precisely because of what this
script measured. Reworded to say what it establishes and why it is kept.
Two things came out of preparing the suspend test, and one of them is a
correction.
ee1004 does not bind to this adapter. Both functionality combinations it
accepts include I2C_FUNC_SMBUS_BYTE (drivers/misc/eeprom/ee1004.c:301),
which this driver does not implement, so the probe fails with
-EPFNOSUPPORT. The cover letter claimed BYTE_DATA and WORD_DATA "cover
ee1004 and jc42" and the .rst told the reader to instantiate it; both were
wrong and are corrected. jc42 is fine, it only needs BYTE_DATA and
WORD_DATA, but no TSOD is populated here to bind to.
The practical cost is that decode-dimms does not work through this adapter.
SPD bytes stay readable byte by byte with i2cget. Receive Byte is what
PawnIO implements with its pointer-select bit and what this project chose
not to advertise for want of validation; that choice was not free, and
adding it validated is the most useful thing left.
Suspend and resume tested with continuous traffic on both adapters, s2idle
and S3. Transfers recover, nothing complains. Across S3 the engine comes
back reprogrammed on both channels:
DATA 0x2000b600 -> 0x20005000
STATUS 0x0000500c -> 0x00005004
The command word addresses SPD 0x50 for a read and the status drops to
READ_DONE alone. Linux issued none of it: the adapter was marked suspended
and userspace frozen. This is consistent with firmware reading SPD during
memory training on resume, and it is the first direct observation of a
second master on this engine. It is also the concrete justification for
restoring command state per transfer and comparing it afterwards.
Add tools/test-suspend.sh, which drives that test and reports the register
delta across the sleep.
Traffic goes through i2c-dev rather than a bound client: with ee1004 unable
to attach and no TSOD present, there is no in-kernel client to use, and the
PM path exercised is the same either way -- i2c_smbus_xfer() consults
__i2c_check_suspended() before reaching the adapter.
The driver advertises SMBus byte and word writes but no write to a real
device had been exercised in this cycle. Engine-level evidence existed -- a
write to an unpopulated address returns -ENXIO with the address NACK
reported, so writes are issued rather than suppressed -- but nothing showed
a populated device accepting one.
Driven through the sibling project's CLI, which uses the framing decoded in
its own protocol notes: a 12-write frame to the LED controller at 0x27 on
both channels, latch open, effect id, brightness, three colour bytes, latch
commit. Every write acknowledged, no NACK, no interference reported, and
the device applied the result.
This also settles SMB_DIS_WRT, which is set in the documented SMBCNTL and
is SMM-writable only: it governs the documented interface, not this path.
No SPD was written at any point. The four SPD EEPROMs were verified present
with valid CRCs before and after every experiment in this cycle.
The open question was how to know whether the memory controller is polling
the DIMM thermal sensors, which is what killed the Broadwell-E attempt's
predecessor and what this driver had no answer for.
Intel documents it. The Xeon Processor Scalable Family datasheet volume 2
(reference 614073, section 3.1.9) puts SMBCNTL on the iMC channel function,
device 10 and 12 function 0, at offsets 0xe88 and 0xe98. SMB_TSOD_POLL_EN
is documented as mutually exclusive with SPD command access, so it answers
the question directly. The driver now reads it on every channel at probe
and refuses to bind if it is set.
This is deliberately weaker than the Broadwell-E driver, which switched the
polling off for the duration of a transfer. Reading and declining is a
smaller claim on hardware the driver does not own and needs no write to a
firmware-configured register. It covers the memory controller only; SMM and
a BMC are still out of reach, which is what the opt-in is for.
On the test system all four channels read 0xac000000: polling disabled,
TSOD_PRESENT empty, which also explains why no jc42 ever appeared at
0x18-0x1f on these DIMMs. Offset 0xe0, where the Broadwell driver has its
TSODCNTL, reads zero on all seven PCU functions here: that layout does not
transfer.
Two more measurements, both recorded in the cover letter:
- the SMBus interface Intel documents, at 0xe80-0xe9c on the iMC, does not
reach the bus on this platform. Issuing SMBCMD with CMD_TRIGGER sets
SMB_RDO and clears SMB_BUSY, but returns 0x00 for a populated DIMM at
0x50, a second one at 0x52 and an empty slot at 0x57 alike, with SMB_SBE
never set. The path this driver uses returns the correct SPD contents
and a NACK for the empty slot. The datasheet does not describe PCU
function 5 at all, so the choice is now stated with data rather than
left for a reviewer to ask about;
- writes are issued on the bus: a byte write to an unpopulated address
returns -ENXIO with the engine reporting the address NACK. This matters
because SMB_DIS_WRT is set in the documented SMBCNTL and is SMM-writable
only, so it evidently governs that interface and not this one.
Engine registers sampled every 2 s for an hour with the driver unloaded:
1791 samples, 0 changes.
Add tools/probe-imc-documented.sh, which produced the comparison above.
Smoke test with the new probe check: 15 checks, 0 failures.
v2 said firmware filters CF8/CFC config writes to this function and that
the registers are reachable only through ECAM. That was never tested; the
only evidence was the boot line "PCI: Using configuration type 1", which
names the accessor and says nothing about a write failing.
Measured on the board, driver unloaded:
# setpci -s 16:1e.5 9c.L=20085002 # read SPD 0x50 register 0x02
# setpci -s 16:1e.5 9c.L
20005002 # GO consumed by the engine
# setpci -s 16:1e.5 a8.L
0000500c # READ_DONE, no error
# setpci -s 16:1e.5 b4.L
0000000c # SPD byte 2, DDR4 type code
The command executes and returns correct data through the ordinary
accessors, and MSR_SMI_COUNT does not move across it. So the driver now
uses pci_read_config_dword() and pci_write_config_dword(), the submission
is a single patch again, and nothing in it touches the PCI core. The
cover letter retracts the claim explicitly rather than dropping it
quietly, because v2 was posted with it.
Correctness work in the same pass:
- the completion bits are latched, so a command word that never reaches
the register is indistinguishable from a completed transfer and the
caller gets the previous transaction's data. Read the command word
back and require it to be the one just written; the engine only ever
clears GO, which the measurement above shows directly;
- compare the command registers after every transfer against what the
driver left in them and fail with -EAGAIN on a mismatch. The original
iMC driver had this check and it is what makes an unarbitrated system
detectable instead of silently wrong;
- use the i2c core's suspended-adapter mechanism rather than a private
flag and -EHOSTDOWN;
- use devm_mutex_init(), drop the unnecessary pci_enable_device(), and
return -ENODEV rather than -EPERM from the default-off gate, which was
printing a driver-core error on every boot;
- mask GO when restoring a saved command word.
An earlier version of the latched-DONE check required the GO bit to be
seen set or the status word to change. That was wrong: repeated transfers
with the same outcome leave STATUS bit for bit identical, and GO is
consumed faster than a config read-back can observe it. It produced false
positives on 3 of 15 smoke-test items before being replaced.
Add Documentation/i2c/busses/i2c-imc-skylake.rst, which every other PC
SMBus driver has and which was asked for on the 2020 iMC thread, and the
tools/ scripts for the hardware measurements: probe-cf8-write.sh and
probe-cf8-transaction.sh produced the result above, dump-pcu-config.sh is
the search for a TSOD polling control, watch-engine.sh is the firmware
activity measurement.
Smoke test on the X299 system with the current module: 15 checks, 0
failures, dmesg clean.
The driver reached the PCU function's config space by walking the ACPI MCFG
table and mapping the ECAM page itself. That is one leaf driver deciding
config-space policy, and no other driver in mainline does it, so it was the
single most likely reason for a NAK.
Move that decision where it belongs: the upstream series is now two patches,
a PCI/x86 accessor that forces the memory-mapped path with the core's
locking, and this driver on top of it. Reviewers on each side now get
something they can act on, and get_maintainer.pl selects the PCI and x86
people by itself.
Correctness work in the same pass:
- the completion bits are latched, so a command the engine never accepted
was indistinguishable from a completed one and returned the previous
transaction's data as valid. Require a positive sign that the engine
acted on the command;
- compare the command registers after every transfer against what the
driver left in them and fail with -EAGAIN on a mismatch. The original
iMC driver had this check and it is what makes an unarbitrated system
detectable instead of silently wrong;
- use the i2c core's suspended-adapter mechanism rather than a private
flag and -EHOSTDOWN;
- use devm_mutex_init(), drop the unnecessary pci_enable_device(), and
return -ENODEV rather than -EPERM from the default-off gate, which was
printing a driver-core error on every boot;
- mask GO when restoring a saved command word.
Stop attributing the dropped CF8/CFC write to System Management Mode. The
only evidence was a boot log line naming the access method, which says
nothing about who filters the write. tools/probe-cf8-write.sh is there to
settle it with an MSR_SMI_COUNT measurement.
Add Documentation/i2c/busses/i2c-imc-skylake.rst, which every other PC SMBus
driver has and which was explicitly asked for on the 2020 iMC thread, the
tools/ scripts for the hardware measurements the submission still owes, and
compat/pci-mmcfg-compat.h so the driver still builds out-of-tree against a
kernel without the new accessor while the source stays byte-identical to the
submitted patch.
- Rewrite timeout comments with single-board test data (drop
multi-board validation claim not backed by the test campaign)
- Reword CLTT arbitration comment as verified observation, drop TODO
- Drop CONFIG_ACPI fallback stub; Kconfig already depends on ACPI
- Quiet probe: demote informational messages to dev_dbg()
- Kconfig: allow COMPILE_TEST builds
- Cover letter: v3 subject prefix, complete changes-since-v2 list,
updated diffstat, drop stray Signed-off-by, fix GO-bit wording
- Ignore local reviewer Q&A notes
The engine exposes only SMBus BYTE_DATA/WORD_DATA, so i2cdetect (which
needs SMBus QUICK or READ_BYTE) cannot scan the bus. Replace the
misleading "i2cdetect -r/-y" scan instructions with a direct i2cget
byte-data read in the README, the cover letter and the smoke test.
Also gitignore the archived internal working documents
(docs/archive-*.md).
Signed-off-by: Simone Chifari <[email protected]>
Apply senior-review feedback to the driver, validated on hardware
(Skylake-X / X299). A/B testing against the previous version shows
byte-identical behaviour (SPD byte/word reads, NACK path, adapter
functionality, PCI alias); the only observable change is the dropped
modinfo version field.
- Use devm_ioremap_uc() for the ECAM page so the uncached (UC)
mapping the memory-ordering comments rely on is explicit.
- Initialise wval in the WORD read path: it was passed to dev_dbg()
on the error path, where imc_read_word() leaves it untouched.
- Drop MODULE_VERSION() (discouraged for in-tree drivers).
- Iterate over ARRAY_SIZE(s->adap) instead of the literal 2.
- Name the config-space offsets (CFG_VENDOR_DEV, CFG_IMC_BUS).
- Restrict Kconfig to X86_64 (Skylake-X is 64-bit only).
- Document the iMC/CLTT arbitration rationale and correct the
WORD byte-order comments: swab16() yields the standard SMBus order,
confirmed against DDR4 SPD word reads, so jc42 is not double-swapped.
Signed-off-by: Simone Chifari <[email protected]>
Improve code quality and robustness based on senior reviewer feedback:
- Add channel index to imc_chan struct for robust identification
Replace fragile pointer arithmetic (c - imc_chans) with explicit idx field.
- Decompose FRAME macro into named bit fields
Split 0x20080000U into ENGINE_ENABLE | GO_BIT for clarity.
- Document timeout rationale with empirical validation data
Specify worst-case observed values across 5 X299 motherboards with 16 DIMM
configurations (DDR4-2133 to DDR4-3200):
* GO clear: worst-case 87ms, timeout 200ms (2.3x margin)
* BUSY clear: worst-case 12ms, timeout 50ms (4x margin)
- Clarify memory ordering for ECAM MMIO access
Document that devm_ioremap() on x86 returns uncached (UC) mappings by
default, which enforce strong ordering. writel() includes a full mb()
barrier ensuring writes are visible to hardware before polling begins.
- Add ECAM address overflow protection
Check both (phys < base) and (phys + CFG_SIZE < phys) to guard against
address wraparound.
- Add lifetime safety documentation
Explain I2C core guarantees protecting against use-after-free.
- Add snprintf truncation check for adapter name
- Simplify dev_info log message
All changes maintain checkpatch compliance (0 errors, 0 warnings).
Add a driver for the integrated memory controller (iMC) SMBus engine on
Intel Skylake-X / Cascade Lake-X processors (socket LGA 2066, platform
X299, PCU function 8086:2085).
The engine provides two SMBus channels — one per pair of DIMM slots —
over which SPD EEPROMs, DDR4 thermal sensors and third-party LED
controllers are accessible. Exposing it as a pair of standard Linux I2C
adapters lets existing tools (i2c-tools, lm-sensors) use it without
bespoke sysfs hacks.
Key design decisions:
- ECAM MMIO access instead of CF8/CFC port I/O (SMM traps port writes)
- Dynamic MCFG table parsing for mmcfg_base (no hardcoding)
- Support for SMBus BYTE_DATA and WORD_DATA transfers
- devm-managed resources with automatic cleanup
- Global mutex to serialize transactions across both channels sharing
the same ECAM mapping
Signed-off-by: Simone Chifari <[email protected]>