Skip to content

add support for Prestige 16 AI Studio B1VEG with EC version 15A2EMS1.114 - #803

Open
GliAcopo wants to merge 1 commit into
BeardOverflow:mainfrom
GliAcopo:add-15a2ems1.114
Open

GliAcopo wants to merge 1 commit into
BeardOverflow:mainfrom
GliAcopo:add-15a2ems1.114

Conversation

@GliAcopo

@GliAcopo GliAcopo commented Aug 10, 2026

Copy link
Copy Markdown

Adds a configuration for the MSI Prestige 16 AI Studio B1VEG (board MS-15A2), EC firmware 15A2EMS1.114.

This board is not currently matched by any config, so the module refuses to load on it:

msi_ec: Firmware version is not supported: '15A2EMS1.114'

The config is deliberately minimal: it enables only what I could actually verify on the machine. Everything else is MSI_EC_ADDR_UNSUPP rather than inherited from a neighbouring config, because an unverified address would mean writing to the wrong EC register.

Device

Model Prestige 16 AI Studio B1VEG
Board (DMI board_name) MS-15A2
EC firmware (0xA0) 15A2EMS1.114 (release date field: 0527202510:09:58)
BIOS (DMI bios_version) E15A2IMS.110
CPU Intel Core Ultra (Meteor Lake-P)
GPU NVIDIA RTX 4050 Max-Q + Intel Arc iGPU
Distro / kernel Ubuntu 26.04 LTS, 7.0.0-29-generic

Note the EC string and the BIOS string are different values that move independently — 15A2EMS1.114 vs E15A2IMS.110. The allow-list needs the EC one. Adding the BIOS string there is a silent no-op: it can never match and produces no error. I made that mistake once early on.

Read back with:

sudo modprobe ec_sys
sudo dd if=/sys/kernel/debug/ec/ec0/io bs=1 skip=160 count=12 status=none

What this config enables

  • charge_control_address = 0xd7 — battery charge limit, encoding 0x80 | percent.
  • cpu.rt_temp_address = 0x68 — verified against coretemp, see below.
  • cpu.rt_fan_speed_address = 0x71 — the G2 family convention; see the honest caveat below.

Everything else is UNSUPP. Two different reasons:

  1. leds and kbd_bl must stay UNSUPP on this config. led_classdev_register() with a .default_trigger writes to the EC immediately after insmod. Leaving them unset is what keeps module loading a read-only operation, which I verified (see Testing).
  2. cooler_boost, shift_mode, fan_mode, super_battery, fn_win_swap, webcam — I have no Windows install on this machine and therefore no way to A/B these against MSI Center. I could have copied the addresses from a neighbouring G2 config and they would probably be right, but "probably right" on a write path is not something I want to submit.

I'm happy to fold this into an existing config if you prefer — I can see from the history that configs get merged (G2_7/G2_8/G2_9 were merged away). I kept it separate only because the UNSUPP fields differ from every existing G2 config, and merging would mean asserting addresses I haven't tested.

How 0xd7 was identified and verified

0xd7 is already used as charge_control_address by 8 existing G2 configs, so this is the family convention rather than a new discovery. What follows is the verification that it is correct on this board.

1. Cross-checked against MSI Center under Windows. The limit was set from MSI Center on Windows, then the machine was rebooted into Linux and the register read. This is the part that makes it a confirmation rather than an inference:

Set in MSI Center 0xd7 read from Linux
100% 0xe4
80% 0xd0
60% 0xbc

0xd0 = 0x80 | 80, 0xbc = 0x80 | 60, 0xe4 = 0x80 | 100.

2. Write table, via charge_control_end_threshold after applying this config:

Written 0xd7 Written 0xd7
99 0xe3 67 0xc3
80 0xd0 61 0xbd
75 0xcb 60 0xbc

Values below 10 are rejected by the driver (-EINVAL from set_end_threshold()), as expected.

3. The firmware honors the value, it does not merely store it. With the cap at 67% and the charger attached, charging stopped at exactly 67% with current_now = 0 and ADP1/online = 1. Same at 80%. 67 was chosen deliberately at ≥3 points away from 60 and 80, because capacity is a rounded integer and a nearby value could have been coincidence. Raising the cap 67 → 80 restarted charging within 10 seconds.

Limitation I want to state explicitly: I only tested large changes. Small raises (e.g. 80 → 81) were never tried, so I don't know the dead-band behaviour near the current charge level.

4. The value lives in battery-backed EC RAM. It survives rmmod and it survives a reboot — both observed. A practical consequence for users: the module is only needed to change the threshold, not to keep it in force. That also means a wrong value persists across reboots, so recovery is: rewrite it, or MSI Center under Windows, or an EC reset (power button held ~1 minute).

Register observations

Sampled while running a full-load loop on all 22 threads, on battery. First column is idle before the loop started:

idle +15s +30s +45s
0x68 45 50 52 53
coretemp Package id 0 (°C) 45 51 53 54
0x71 32 32 32 32
0xC9 246 241 245 240

0x68 is verified. It tracks Package id 0 consistently, roughly 1 °C below it, across the whole sweep. After loading the module, cpu/realtime_temperature read 45 while sensors read +45.0°C.

0x71 is not independently verified, and I'd rather say so than pretend. It stayed at 32 throughout. I initially read that as evidence the address was wrong, but the fan never left its minimum regime in the thermal range I can reach on battery (0xC9 only moved ±2%), so a constant value is exactly what you'd expect — it isn't evidence either way. I kept 0x71 because all 21 existing configs use it, 32 is plausible as a percentage (~1935 RPM against a typical full scale), and it is a read-only field where a wrong address costs a wrong number and nothing more. If you'd prefer UNSUPP until someone can confirm it under a heavy thermal load on AC, I'll change it.

Bonus observation, not used by this config: 0xC9 appears to satisfy RPM = 480000 / value248 → 1935, 246 → 1951, 241 → 1991, matching fan1_input each time. That's RPM, not the percentage rt_fan_speed_address expects, so I did not wire it up. Recording it in case it's useful for the family.

EC dump

Taken with the ec_sys method from the support guide, with the charge limit at 80% (0xd7 = 0xd0):

00000000  00 80 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000010  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000020  00 00 00 00 00 00 00 00  0a 05 00 00 08 2c 0b 4b  |.............,.K|
00000030  02 05 00 0d 01 00 50 81  9c 18 78 3c 76 02 c0 00  |......P...x<v...|
00000040  9c 18 40 00 be 17 f5 fc  1b 0f ed 3d ef 0b 88 45  |..@........=...E|
00000050  1e 1e 5a 00 00 01 00 00  25 26 00 00 00 00 00 00  |..Z.....%&......|
00000060  00 00 00 00 00 00 00 00  2f 00 32 37 3d 43 50 5a  |......../.27=CPZ|
00000070  78 20 20 20 2d 37 41 50  5a 5a 2a 34 3b 3f 48 52  |x   -7APZZ*4;?HR|
00000080  26 00 2a 2f 33 3a 4b 50  64 00 00 20 28 2d 3c 46  |&.*/3:KPd.. (-<F|
00000090  50 00 27 2b 31 35 3e 4d  06 3c 00 02 00 00 2a 00  |P.'+15>M.<....*.|
000000a0  31 35 41 32 45 4d 53 31  2e 31 31 34 30 35 32 37  |15A2EMS1.1140527|
000000b0  32 30 32 35 31 30 3a 30  39 3a 35 38 00 00 00 08  |202510:09:58....|
000000c0  00 00 01 25 00 00 1e 00  00 f0 00 ed 00 00 00 00  |...%............|
000000d0  00 00 c1 81 0d 00 05 d0  00 03 00 00 00 05 00 00  |................|
000000e0  e2 0a 00 be 17 00 00 c1  00 00 00 00 00 c0 00 00  |................|
000000f0  40 00 70 00 27 7f 05 28  64 00 00 00 00 00 00 00  |@.p.'..(d.......|
00000100

Relation to #621

#621 is an open support request for the same board (MS-15A2, Prestige 16 AI Studio B1VFG) with EC firmware 15A2EMS1.111. Comparing that dump with mine:

  • 0xd7 = 0xe4 there, which decodes as 0x80 | 100 — i.e. the default, no cap set. Consistent with the encoding proposed here, on a different unit and a different firmware revision.
  • 209 of 256 bytes are identical between the two dumps; the differences are in the ranges you'd expect to be dynamic (temperatures, fan, battery state).

I deliberately did not add 15A2EMS1.111 to the allow-list. I don't have that firmware and can't test it, and adding it would be presenting an inference as a confirmation. If the reporter of #621 can build this branch and confirm, adding one line covers that revision too. I've commented on #621 pointing here.

Testing done

  • Built against current main (d7fbbd8) on kernel 7.0.0-29-generic, no new warnings.
  • Module signed with an enrolled MOK key and loaded under Secure Boot (lockdown integrity).
  • insmod succeeds; fw_version reads 15A2EMS1.114, fw_release_date reads 2025-05-27T10:09:58.
  • 0xd7 is byte-identical before and after insmod (0xd0 both times) — loading this config performs no EC write. This is the property the UNSUPP leds/kbd_bl fields are protecting.
  • charge_control_end_threshold reads back 80, matching 0xd7 = 0xd0.
  • Writing 75 gives 0xcb; writing 80 gives 0xd0. Verified by re-reading the EC directly, not by reading sysfs back.
  • cpu/realtime_temperature = 45 against sensors +45.0°C.

One thing I noticed but deliberately left out of this PR

charge_control_start_threshold_store() calls set_end_threshold(threshold + 10), so writing start actually writes end. The battery attribute group has no is_visible, so start is always exposed even on configs where only the end threshold is meaningful. That affects every model, not just this one, so it doesn't belong in a device-support PR — mentioning it only in case it's not already known, and happy to open a separate issue.


Template answers, for the record. GPU: Nvidia (RTX 4050 Max-Q) plus an Intel Arc iGPU. No mic/speaker mute LEDs are exposed under Linux — /sys/class/leds shows only capslock/numlock/scrolllock and a PHY LED. For the remaining physical characteristics (fan count, backlight type, webcam slider, Fn/Win placement) I'd point at #621, which covers the same board; I haven't independently confirmed them, and none of them affect this config since all the corresponding features are left UNSUPP.

The battery charge threshold address (0xd7, encoded as 0x80 | percent) was
verified A/B against MSI Center under Windows, and by observing charging
actually stop at the configured cap with the charger attached. The CPU
temperature address (0x68) was verified against coretemp under load.

Features that could not be verified on this unit are left UNSUPP rather than
inherited from a neighbouring config, since an unverified address on a write
path would mean writing to the wrong EC register. leds and kbd_bl in
particular must stay UNSUPP: led_classdev_register() with a .default_trigger
writes to the EC right after insmod, and leaving them unset is what keeps
loading the module a read-only operation.

The exception is cpu.rt_fan_speed_address (0x71), taken from the G2 family
convention rather than measured: the fan never left its minimum regime in the
thermal range reachable on battery, so the value could not be moved. It is a
read-only field; see the PR description for the details and an offer to set it
UNSUPP instead if preferred.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant