refactor(core): move power_manager from sys to io
What changed, and why it matters
This commit is a large code refactor that moves the power management subsystem from one internal directory (`sys`) to another (`io`) in the Trezor firmware. It renames include paths, reorganizes files, and adds new battery fuel-gauge and PMIC driver code, but the commit message explicitly labels it as a refactor with '[no changelog]'. There is no indication of a security fix or vulnerability being addressed.
No security action required. Treat as routine refactoring; review new fuel-gauge and PMIC code for correctness during normal QA if desired.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The commit moves power_manager and related PMIC/fuel-gauge/wireless-charging code from core/embed/sys/ to core/embed/io/, updating all #include directives and build references accordingly. It introduces new battery model data files, a fuel-gauge implementation using an Extended Kalman Filter, and NPM1300 PMIC register definitions. The diff is purely structural/organizational plus new driver code; no bug fixes, bounds-check additions, privilege changes, or security-relevant corrections are visible.
Changed components
core/embed/io/power_managercore/embed/io/ble/stm32/ble.ccore/embed/projects/boardloader/main.ccore/embed/projects/bootloader/main.ccore/embed/projects/kernel/main.ccore/embed/sys/syscall/stm32core/embed/upymod/modtrezoriocore/site_scons/models/T3W1Inspect captured patch +8649 / −8649
diff --git a/core/embed/io/ble/stm32/ble.c b/core/embed/io/ble/stm32/ble.c
index 1756f05e..f23a2b6d 100644
--- a/core/embed/io/ble/stm32/ble.c
+++ b/core/embed/io/ble/stm32/ble.c
@@ -36,7 +36,7 @@
#include <util/unit_properties.h>
#ifdef USE_POWER_MANAGER
-#include <sys/power_manager.h>
+#include <io/power_manager.h>
#endif
#include "ble_comm_defs.h"
diff --git a/core/embed/io/power_manager/fuel_gauge/battery_data_hcf343837ncz.h b/core/embed/io/power_manager/fuel_gauge/battery_data_hcf343837ncz.h
new file mode 100644
index 00000000..9f9d16c9
--- /dev/null
+++ b/core/embed/io/power_manager/fuel_gauge/battery_data_hcf343837ncz.h
@@ -0,0 +1,232 @@
+/*
+ * This file is part of the Trezor project, https://trezor.io/
+ *
+ * Copyright (c) SatoshiLabs
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * Battery Data: HCF343837NCZ
+ * Auto-generated from battery characterization data
+ * Contains lookup tables and parameters for the specific battery model
+ */
+
+#pragma once
+
+#include <trezor_types.h>
+
+/**
+ * Battery Specifications:
+ * Model: HCF343837NCZ
+ * Chemistry: LiFePO4
+ */
+
+// Configuration
+#define BATTERY_HCF343837NCZ_NUM_TEMP_POINTS 9
+
+// SOC breakpoints for piecewise functions
+#define BATTERY_HCF343837NCZ_SOC_BREAKPOINT_1 0.2f
+#define BATTERY_HCF343837NCZ_SOC_BREAKPOINT_2 0.7f
+
+// Temperature points arrays (in Celsius)
+// Discharge temperatures
+static const float BATTERY_HCF343837NCZ_TEMP_POINTS_DISCHG
+ [BATTERY_HCF343837NCZ_NUM_TEMP_POINTS] = {
+ 1.02f, 5.86f, 10.72f, 15.56f, 20.49f, 30.36f, 35.31f, 40.32f, 45.34f};
+
+// Charge temperatures
+static const float
+ BATTERY_HCF343837NCZ_TEMP_POINTS_CHG[BATTERY_HCF343837NCZ_NUM_TEMP_POINTS] =
+ {2.39f, 7.37f, 12.68f, 17.53f, 22.46f, 32.26f, 37.22f, 42.17f, 47.15f};
+
+// Internal resistance curve parameters (rational function parameters
+// a+b*t)/(c+d*t)
+static const float BATTERY_HCF343837NCZ_R_INT_PARAMS[4] = {
+ // a, b, c, d for rational function (a + b*t)/(c + d*t)
+ 5148.694149f, 126.612310f, 6446.087576f, 437.006759f};
+
+// Discharge OCV curve parameters for each temperature
+static const float BATTERY_HCF343837NCZ_OCV_DISCHARGE_PARAMS
+ [BATTERY_HCF343837NCZ_NUM_TEMP_POINTS][10] = {
+ // Temperature: 1.02°C (key: 0)
+ {
+ 0.132744f, 3.209158f, // m, b (linear segment)
+ -377.860239f, -5173.820403f, -123.475291f,
+ -1567.959576f, // a1, b1, c1, d1 (first rational segment)
+ 6416.385096f, -6367.610930f, 1943.981986f,
+ -1931.427092f // a3, b3, c3, d3 (third rational segment)
+ },
+ // Temperature: 5.86°C (key: 5)
+ {
+ 0.126718f, 3.219663f, // m, b (linear segment)
+ -658.352709f, -9465.436646f, -215.671511f,
+ -2858.499585f, // a1, b1, c1, d1 (first rational segment)
+ 1103.603880f, -1089.643805f, 333.742552f,
+ -329.897901f // a3, b3, c3, d3 (third rational segment)
+ },
+ // Temperature: 10.72°C (key: 10)
+ {
+ 0.108352f, 3.229992f, // m, b (linear segment)
+ -2493.910531f, -28133.328290f, -817.138793f,
+ -8431.792420f, // a1, b1, c1, d1 (first rational segment)
+ 1615.858515f, -4201.535059f, 475.291094f,
+ -1251.915058f // a3, b3, c3, d3 (third rational segment)
+ },
+ // Temperature: 15.56°C (key: 15)
+ {
+ 0.111289f, 3.231867f, // m, b (linear segment)
+ -4966.693381f, -49873.913621f, -1623.977390f,
+ -14898.978255f, // a1, b1, c1, d1 (first rational segment)
+ 1012.376908f, -1993.599184f, 301.394188f,
+ -595.999103f // a3, b3, c3, d3 (third rational segment)
+ },
+ // Temperature: 20.49°C (key: 20)
+ {
+ 0.120106f, 3.229563f, // m, b (linear segment)
+ 268.096564f, 2874.958069f, 87.433893f,
+ 861.258620f, // a1, b1, c1, d1 (first rational segment)
+ -1770.509656f, 3559.122578f, -527.259884f,
+ 1063.803643f // a3, b3, c3, d3 (third rational segment)
+ },
+ // Temperature: 30.36°C (key: 30)
+ {
+ 0.137237f, 3.223399f, // m, b (linear segment)
+ 1038.166704f, 18767.842731f, 339.512092f,
+ 5677.727929f, // a1, b1, c1, d1 (first rational segment)
+ -1351.860989f, 2612.204208f, -403.454434f,
+ 781.043397f // a3, b3, c3, d3 (third rational segment)
+ },
+ // Temperature: 35.31°C (key: 35)
+ {
+ 0.143897f, 3.223448f, // m, b (linear segment)
+ 13894.623462f, 290634.247388f, 4549.590583f,
+ 88020.681469f, // a1, b1, c1, d1 (first rational segment)
+ -2937.654993f, 148.254576f, -891.816102f,
+ 59.282724f // a3, b3, c3, d3 (third rational segment)
+ },
+ // Temperature: 40.32°C (key: 40)
+ {
+ 0.156721f, 3.219657f, // m, b (linear segment)
+ 486.002705f, 11027.269400f, 159.266245f,
+ 3343.274892f, // a1, b1, c1, d1 (first rational segment)
+ -362.733040f, 361.915501f, -108.955783f,
+ 108.840505f // a3, b3, c3, d3 (third rational segment)
+ },
+ // Temperature: 45.34°C (key: 45)
+ {
+ 0.156192f, 3.221027f, // m, b (linear segment)
+ 477.405525f, 10988.255232f, 156.524863f,
+ 3330.213529f, // a1, b1, c1, d1 (first rational segment)
+ 496.936214f, -496.194305f, 149.219279f,
+ -149.173026f // a3, b3, c3, d3 (third rational segment)
+ }};
+
+// Charge OCV curve parameters for each temperature
+static const float BATTERY_HCF343837NCZ_OCV_CHARGE_PARAMS
+ [BATTERY_HCF343837NCZ_NUM_TEMP_POINTS][10] = {
+ // Temperature: 2.39°C (key: 0)
+ {
+ 0.087208f, 3.332748f, // m, b (linear segment)
+ 262.388264f, 33277.522403f, 82.663155f,
+ 9902.336513f, // a1, b1, c1, d1 (first rational segment)
+ 350.518328f, -324.259695f, 104.652569f,
+ -97.343933f // a3, b3, c3, d3 (third rational segment)
+ },
+ // Temperature: 7.37°C (key: 5)
+ {
+ 0.137945f, 3.291426f, // m, b (linear segment)
+ 244.061830f, 14327.472407f, 78.683519f,
+ 4286.625957f, // a1, b1, c1, d1 (first rational segment)
+ 120.553563f, -109.825804f, 36.170685f,
+ -33.200196f // a3, b3, c3, d3 (third rational segment)
+ },
+ // Temperature: 12.68°C (key: 10)
+ {
+ 0.145137f, 3.277093f, // m, b (linear segment)
+ 63.104078f, 2993.710560f, 20.495998f,
+ 897.719518f, // a1, b1, c1, d1 (first rational segment)
+ 648.380265f, -559.549859f, 196.709317f,
+ -171.937183f // a3, b3, c3, d3 (third rational segment)
+ },
+ // Temperature: 17.53°C (key: 15)
+ {
+ 0.136263f, 3.272912f, // m, b (linear segment)
+ 410.756037f, 20687.122117f, 133.039482f,
+ 6220.274827f, // a1, b1, c1, d1 (first rational segment)
+ 354.112527f, -327.223549f, 106.665466f,
+ -99.204169f // a3, b3, c3, d3 (third rational segment)
+ },
+ // Temperature: 22.46°C (key: 20)
+ {
+ 0.134281f, 3.270871f, // m, b (linear segment)
+ 518.429721f, 24701.422489f, 168.372867f,
+ 7429.281380f, // a1, b1, c1, d1 (first rational segment)
+ 253.931799f, -239.537581f, 76.297951f,
+ -72.320239f // a3, b3, c3, d3 (third rational segment)
+ },
+ // Temperature: 32.26°C (key: 30)
+ {
+ 0.127118f, 3.265582f, // m, b (linear segment)
+ 266.663194f, 9172.306429f, 86.480023f,
+ 2759.277240f, // a1, b1, c1, d1 (first rational segment)
+ 161.967344f, -159.021597f, 48.451837f,
+ -47.664881f // a3, b3, c3, d3 (third rational segment)
+ },
+ // Temperature: 37.22°C (key: 35)
+ {
+ 0.120619f, 3.268397f, // m, b (linear segment)
+ 173.617347f, 5728.679569f, 56.323639f,
+ 1721.968776f, // a1, b1, c1, d1 (first rational segment)
+ 80.505082f, -79.040169f, 24.096966f,
+ -23.704603f // a3, b3, c3, d3 (third rational segment)
+ },
+ // Temperature: 42.17°C (key: 40)
+ {
+ 0.092222f, 3.287151f, // m, b (linear segment)
+ -57.313130f, -822.995842f, -17.883998f,
+ -246.343999f, // a1, b1, c1, d1 (first rational segment)
+ 440.636027f, -435.083990f, 131.783395f,
+ -130.300948f // a3, b3, c3, d3 (third rational segment)
+ },
+ // Temperature: 47.15°C (key: 45)
+ {
+ 0.128932f, 3.261763f, // m, b (linear segment)
+ 120.858087f, 3927.216714f, 39.505813f,
+ 1180.973085f, // a1, b1, c1, d1 (first rational segment)
+ 447.508970f, -441.668629f, 133.782446f,
+ -132.244176f // a3, b3, c3, d3 (third rational segment)
+ }};
+
+// Battery capacity data for each temperature
+static const float
+ BATTERY_HCF343837NCZ_CAPACITY[BATTERY_HCF343837NCZ_NUM_TEMP_POINTS][2] = {
+ // Temperature: 1.02°C (key: 0)
+ {274.60f, 301.37f},
+ // Temperature: 5.86°C (key: 5)
+ {305.00f, 362.34f},
+ // Temperature: 10.72°C (key: 10)
+ {327.00f, 382.02f},
+ // Temperature: 15.56°C (key: 15)
+ {338.20f, 384.26f},
+ // Temperature: 20.49°C (key: 20)
+ {354.99f, 389.48f},
+ // Temperature: 30.36°C (key: 30)
+ {362.31f, 389.79f},
+ // Temperature: 35.31°C (key: 35)
+ {364.07f, 389.98f},
+ // Temperature: 40.32°C (key: 40)
+ {363.84f, 353.93f},
+ // Temperature: 45.34°C (key: 45)
+ {364.62f, 391.60f}};
diff --git a/core/embed/io/power_manager/fuel_gauge/battery_data_jyhpfl333838.h b/core/embed/io/power_manager/fuel_gauge/battery_data_jyhpfl333838.h
new file mode 100644
index 00000000..5dd8073b
--- /dev/null
+++ b/core/embed/io/power_manager/fuel_gauge/battery_data_jyhpfl333838.h
@@ -0,0 +1,252 @@
+/*
+ * This file is part of the Trezor project, https://trezor.io/
+ *
+ * Copyright (c) SatoshiLabs
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * Battery Data: JYHPFL333838
+ * Auto-generated from battery characterization data
+ * Contains lookup tables and parameters for the specific battery model
+ */
+
+#pragma once
+
+#include <trezor_types.h>
+
+/**
+ * Battery Specifications:
+ * Model: JYHPFL333838
+ * Chemistry: LiFePO4
+ */
+
+// Configuration
+#define BATTERY_JYHPFL333838_NUM_TEMP_POINTS 10
+
+// SOC breakpoints for piecewise functions
+#define BATTERY_JYHPFL333838_SOC_BREAKPOINT_1 0.25f
+#define BATTERY_JYHPFL333838_SOC_BREAKPOINT_2 0.8f
+
+// Temperature points arrays (in Celsius)
+// Discharge temperatures
+static const float BATTERY_JYHPFL333838_TEMP_POINTS_DISCHG
+ [BATTERY_JYHPFL333838_NUM_TEMP_POINTS] = {0.80f, 5.78f, 10.64f, 15.55f,
+ 20.65f, 25.43f, 31.41f, 35.41f,
+ 40.38f, 45.28f};
+
+// Charge temperatures
+static const float
+ BATTERY_JYHPFL333838_TEMP_POINTS_CHG[BATTERY_JYHPFL333838_NUM_TEMP_POINTS] =
+ {2.32f, 7.28f, 12.60f, 17.51f, 22.50f,
+ 27.38f, 32.31f, 37.36f, 42.34f, 47.37f};
+
+// Internal resistance curve parameters (rational function parameters
+// a+b*t)/(c+d*t)
+static const float BATTERY_JYHPFL333838_R_INT_PARAMS[4] = {
+ // a, b, c, d for rational function (a + b*t)/(c + d*t)
+ 3.700987f, 0.063115f, 4.059870f, 0.273364f};
+
+// Discharge OCV curve parameters for each temperature
+static const float BATTERY_JYHPFL333838_OCV_DISCHARGE_PARAMS
+ [BATTERY_JYHPFL333838_NUM_TEMP_POINTS][10] = {
+ // Temperature: 0.80°C (key: 0)
+ {
+ 0.126550f, 3.211777f, // m, b (linear segment)
+ 19.098931f, 149.532130f, 6.226951f,
+ 44.749610f, // a1, b1, c1, d1 (first rational segment)
+ 1209.541847f, -1204.643391f, 365.238423f,
+ -363.797538f // a3, b3, c3, d3 (third rational segment)
+ },
+ // Temperature: 5.78°C (key: 5)
+ {
+ 0.118854f, 3.221712f, // m, b (linear segment)
+ 510.794524f, 4147.718053f, 167.121531f,
+ 1235.569720f, // a1, b1, c1, d1 (first rational segment)
+ 2461.131197f, -2454.069820f, 742.268667f,
+ -740.201672f // a3, b3, c3, d3 (third rational segment)
+ },
+ // Temperature: 10.64°C (key: 10)
+ {
+ 0.116679f, 3.228096f, // m, b (linear segment)
+ 4.902604f, 39.346060f, 1.602605f,
+ 11.689570f, // a1, b1, c1, d1 (first rational segment)
+ 1195.141551f, -1192.318620f, 359.914069f,
+ -359.086078f // a3, b3, c3, d3 (third rational segment)
+ },
+ // Temperature: 15.55°C (key: 15)
+ {
+ 0.114185f, 3.232650f, // m, b (linear segment)
+ 60.048245f, 556.913282f, 19.591820f,
+ 166.054156f, // a1, b1, c1, d1 (first rational segment)
+ 3200.736593f, -3191.682291f, 963.249686f,
+ -960.608735f // a3, b3, c3, d3 (third rational segment)
+ },
+ // Temperature: 20.65°C (key: 20)
+ {
+ 0.114688f, 3.233887f, // m, b (linear segment)
+ 62.865854f, 711.447068f, 20.495367f,
+ 213.158188f, // a1, b1, c1, d1 (first rational segment)
+ 1180.804882f, -1178.074609f, 355.167808f,
+ -354.373464f // a3, b3, c3, d3 (third rational segment)
+ },
+ // Temperature: 25.43°C (key: 25)
+ {
+ 0.117543f, 3.236008f, // m, b (linear segment)
+ 78.102339f, 1077.942159f, 25.484299f,
+ 323.846567f, // a1, b1, c1, d1 (first rational segment)
+ 1130.318095f, -1127.865886f, 339.544458f,
+ -338.836536f // a3, b3, c3, d3 (third rational segment)
+ },
+ // Temperature: 31.41°C (key: 30)
+ {
+ 0.117363f, 3.238875f, // m, b (linear segment)
+ 88.813605f, 1434.949559f, 29.026877f,
+ 431.654409f, // a1, b1, c1, d1 (first rational segment)
+ 2953.178047f, -2947.773461f, 886.333650f,
+ -884.769279f // a3, b3, c3, d3 (third rational segment)
+ },
+ // Temperature: 35.41°C (key: 35)
+ {
+ 0.123731f, 3.236223f, // m, b (linear segment)
+ 129.239103f, 2182.616715f, 42.262528f,
+ 657.226142f, // a1, b1, c1, d1 (first rational segment)
+ 4774.132278f, -4767.725320f, 1431.719239f,
+ -1429.869351f // a3, b3, c3, d3 (third rational segment)
+ },
+ // Temperature: 40.38°C (key: 40)
+ {
+ 0.123696f, 3.237016f, // m, b (linear segment)
+ 221.044970f, 3715.383819f, 72.272903f,
+ 1118.389323f, // a1, b1, c1, d1 (first rational segment)
+ -4039.944265f, 4034.063152f, -1211.294227f,
+ 1209.598810f // a3, b3, c3, d3 (third rational segment)
+ },
+ // Temperature: 45.28°C (key: 45)
+ {
+ 0.125006f, 3.235912f, // m, b (linear segment)
+ 151.467401f, 2582.526900f, 49.550052f,
+ 777.691108f, // a1, b1, c1, d1 (first rational segment)
+ 1112.445343f, -1110.999778f, 333.537185f,
+ -333.119361f // a3, b3, c3, d3 (third rational segment)
+ }};
+
+// Charge OCV curve parameters for each temperature
+static const float BATTERY_JYHPFL333838_OCV_CHARGE_PARAMS
+ [BATTERY_JYHPFL333838_NUM_TEMP_POINTS][10] = {
+ // Temperature: 2.32°C (key: 0)
+ {
+ 0.133654f, 3.292145f, // m, b (linear segment)
+ 2424.212366f, 87282.185143f, 753.227933f,
+ 26148.817273f, // a1, b1, c1, d1 (first rational segment)
+ -20885.884413f, 19421.324650f, -6263.752097f,
+ 5862.671202f // a3, b3, c3, d3 (third rational segment)
+ },
+ // Temperature: 7.28°C (key: 5)
+ {
+ 0.119964f, 3.293413f, // m, b (linear segment)
+ 2732.271006f, 75783.249716f, 850.221080f,
+ 22690.534179f, // a1, b1, c1, d1 (first rational segment)
+ -4317.842520f, 4121.656010f, -1290.625974f,
+ 1236.917022f // a3, b3, c3, d3 (third rational segment)
+ },
+ // Temperature: 12.60°C (key: 10)
+ {
+ 0.129891f, 3.273207f, // m, b (linear segment)
+ 846.503340f, 15988.928725f, 265.726589f,
+ 4798.200588f, // a1, b1, c1, d1 (first rational segment)
+ -60068.019107f, 55820.453762f, -18158.693048f,
+ 16993.928616f // a3, b3, c3, d3 (third rational segment)
+ },
+ // Temperature: 17.51°C (key: 15)
+ {
+ 0.115653f, 3.274031f, // m, b (linear segment)
+ 237.696958f, 1964.826751f, 74.429637f,
+ 585.013431f, // a1, b1, c1, d1 (first rational segment)
+ 968.862408f, -935.338129f, 290.869232f,
+ -281.680851f // a3, b3, c3, d3 (third rational segment)
+ },
+ // Temperature: 22.50°C (key: 20)
+ {
+ 0.118277f, 3.272331f, // m, b (linear segment)
+ -26.011330f, -198.086479f, -8.180144f,
+ -58.781818f, // a1, b1, c1, d1 (first rational segment)
+ 983.725464f, -950.493228f, 295.209174f,
+ -286.098635f // a3, b3, c3, d3 (third rational segment)
+ },
+ // Temperature: 27.38°C (key: 25)
+ {
+ 0.111950f, 3.273751f, // m, b (linear segment)
+ -25.521502f, -274.597696f, -8.095046f,
+ -81.706277f, // a1, b1, c1, d1 (first rational segment)
+ 1073.930068f, -1053.131703f, 321.219989f,
+ -315.514254f // a3, b3, c3, d3 (third rational segment)
+ },
+ // Temperature: 32.31°C (key: 30)
+ {
+ 0.105879f, 3.276268f, // m, b (linear segment)
+ 154.842986f, 1451.215812f, 48.865130f,
+ 431.470258f, // a1, b1, c1, d1 (first rational segment)
+ -3747.632139f, 3694.953159f, -1119.887101f,
+ 1105.424025f // a3, b3, c3, d3 (third rational segment)
+ },
+ // Temperature: 37.36°C (key: 35)
+ {
+ 0.103781f, 3.277949f, // m, b (linear segment)
+ 42.648170f, 582.113263f, 13.465663f,
+ 173.961241f, // a1, b1, c1, d1 (first rational segment)
+ 1109.000316f, -1096.490080f, 331.110598f,
+ -327.675032f // a3, b3, c3, d3 (third rational segment)
+ },
+ // Temperature: 42.34°C (key: 40)
+ {
+ 0.105248f, 3.278675f, // m, b (linear segment)
+ 86.330934f, 1157.715172f, 27.596520f,
+ 344.393006f, // a1, b1, c1, d1 (first rational segment)
+ 1018.218867f, -1006.960134f, 303.810937f,
+ -300.720120f // a3, b3, c3, d3 (third rational segment)
+ },
+ // Temperature: 47.37°C (key: 45)
+ {
+ 0.102922f, 3.281457f, // m, b (linear segment)
+ 133.236246f, 1680.463569f, 42.563832f,
+ 499.016826f, // a1, b1, c1, d1 (first rational segment)
+ 731.525161f, -722.630154f, 218.130924f,
+ -215.651760f // a3, b3, c3, d3 (third rational segment)
+ }};
+
+// Battery capacity data for each temperature
+static const float
+ BATTERY_JYHPFL333838_CAPACITY[BATTERY_JYHPFL333838_NUM_TEMP_POINTS][2] = {
+ // Temperature: 0.80°C (key: 0)
+ {297.56f, 315.21f},
+ // Temperature: 5.78°C (key: 5)
+ {325.07f, 336.55f},
+ // Temperature: 10.64°C (key: 10)
+ {343.23f, 366.44f},
+ // Temperature: 15.55°C (key: 15)
+ {355.86f, 378.79f},
+ // Temperature: 20.65°C (key: 20)
+ {362.69f, 394.38f},
+ // Temperature: 25.43°C (key: 25)
+ {357.80f, 383.75f},
+ // Temperature: 31.41°C (key: 30)
+ {361.17f, 379.75f},
+ // Temperature: 35.41°C (key: 35)
+ {357.76f, 366.75f},
+ // Temperature: 40.38°C (key: 40)
+ {357.29f, 383.63f},
+ // Temperature: 45.28°C (key: 45)
+ {353.41f, 377.66f}};
diff --git a/core/embed/io/power_manager/fuel_gauge/battery_model.c b/core/embed/io/power_manager/fuel_gauge/battery_model.c
new file mode 100644
index 00000000..a03b0724
--- /dev/null
+++ b/core/embed/io/power_manager/fuel_gauge/battery_model.c
@@ -0,0 +1,346 @@
+/*
+ * This file is part of the Trezor project, https://trezor.io/
+ *
+ * Copyright (c) SatoshiLabs
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+#ifdef KERNEL_MODE
+
+#include <math.h>
+
+#include <util/unit_properties.h>
+
+#include "battery_model.h"
+
+// Helper function for linear interpolation
+static float linear_interpolate(float x, float x1, float y1, float x2,
+ float y2) {
+ // Prevent division by zero
+ if (fabsf(x2 - x1) < 1e-6f) {
+ return (y1 + y2) / 2.0f; // Return average if x values are too close
+ }
+ return y1 + (x - x1) * (y2 - y1) / (x2 - x1);
+}
+
+// Calculate OCV for specific parameters and SOC
+static float calc_ocv(const battery_model_t* model, const float* params,
+ float soc) {
+ if (soc < model->soc_breakpoint_1) {
+ // First segment (rational function): (a1 + b1*x)/(c1 + d1*x)
+ float a1 = params[2];
+ float b1 = params[3];
+ float c1 = params[4];
+ float d1 = params[5];
+ return (a1 + b1 * soc) / (c1 + d1 * soc);
+ } else if (soc <= model->soc_breakpoint_2) {
+ // Middle segment (linear function): m*x + b
+ float m = params[0];
+ float b = params[1];
+ return m * soc + b;
+ } else {
+ // Third segment (rational function): (a3 + b3*x)/(c3 + d3*x)
+ float a3 = params[6];
+ float b3 = params[7];
+ float c3 = params[8];
+ float d3 = params[9];
+ return (a3 + b3 * soc) / (c3 + d3 * soc);
+ }
+}
+
+// Calculate OCV slope for specific parameters and SOC
+static float calc_ocv_slope(const battery_model_t* model, const float* params,
+ float soc) {
+ if (soc < model->soc_breakpoint_1) {
+ // First segment (rational function derivative)
+ float a1 = params[2];
+ float b1 = params[3];
+ float c1 = params[4];
+ float d1 = params[5];
+ float denominator = c1 + d1 * soc;
+ return (b1 * c1 - a1 * d1) / (denominator * denominator);
+ } else if (soc <= model->soc_breakpoint_2) {
+ // Middle segment (linear function derivative)
+ float m = params[0];
+ return m;
+ } else {
+ // Third segment (rational function derivative)
+ float a3 = params[6];
+ float b3 = params[7];
+ float c3 = params[8];
+ float d3 = params[9];
+ float denominator = c3 + d3 * soc;
+ return (b3 * c3 - a3 * d3) / (denominator * denominator);
+ }
+}
+
+// Calculate SOC from OCV for specific parameters
+static float calc_soc_from_ocv(const battery_model_t* model,
+ const float* params, float ocv) {
+ // Calculate breakpoint voltages
+ float ocv_breakpoint_1 = calc_ocv(model, params, model->soc_breakpoint_1);
+ float ocv_breakpoint_2 = calc_ocv(model, params, model->soc_breakpoint_2);
+
+ // Extract parameters
+ float m = params[0];
+ float b = params[1];
+ float a1 = params[2];
+ float b1 = params[3];
+ float c1 = params[4];
+ float d1 = params[5];
+ float a3 = params[6];
+ float b3 = params[7];
+ float c3 = params[8];
+ float d3 = params[9];
+
+ if (ocv < ocv_breakpoint_1) {
+ // First segment (rational function inverse)
+ return (a1 - c1 * ocv) / (d1 * ocv - b1);
+ } else if (ocv <= ocv_breakpoint_2) {
+ // Middle segment (linear function inverse)
+ return (ocv - b) / m;
+ } else {
+ // Third segment (rational function inverse)
+ return (a3 - c3 * ocv) / (d3 * ocv - b3);
+ }
+}
+
+float battery_rint(const battery_model_t* model, float temperature) {
+ // Calculate R_int using rational function: (a + b*t)/(c + d*t)
+ float a = model->r_int_params[0];
+ float b = model->r_int_params[1];
+ float c = model->r_int_params[2];
+ float d = model->r_int_params[3];
+
+ return (a + b * temperature) / (c + d * temperature);
+}
+
+float battery_total_capacity(const battery_model_t* model, float temperature,
+ bool discharging_mode) {
+ // Select appropriate temperature array based on mode
+ const float* temp_points = discharging_mode ? model->temp_points_discharge
+ : model->temp_points_charge;
+
+ // Handle out-of-bounds temperatures
+ if (temperature <= temp_points[0]) {
+ return model->capacity[0][discharging_mode ? 0 : 1];
+ }
+
+ if (temperature >= temp_points[model->num_temp_points - 1]) {
+ return model
+ ->capacity[model->num_temp_points - 1][discharging_mode ? 0 : 1];
+ }
+
+ // Find temperature bracket
+ for (int i = 0; i < model->num_temp_points - 1; i++) {
+ if (temperature < temp_points[i + 1]) {
+ return linear_interpolate(
+ temperature, temp_points[i],
+ model->capacity[i][discharging_mode ? 0 : 1], temp_points[i + 1],
+ model->capacity[i + 1][discharging_mode ? 0 : 1]);
+ }
+ }
+
+ // Should never reach here
+ return model->capacity[0][discharging_mode ? 0 : 1];
+}
+
+float battery_meas_to_ocv(const battery_model_t* model, float voltage_V,
+ float current_mA, float temperature) {
+ // Convert mA to A by dividing by 1000
+ float current_A = current_mA / 1000.0f;
+
+ // Calculate OCV: V_OC = V_term + I * R_int
+ return voltage_V + (current_A * battery_rint(model, temperature));
+}
+
+float battery_ocv(const battery_model_t* model, float soc, float temperature,
+ bool discharging_mode) {
+ // Clamp SOC to valid range
+ soc = (soc < 0.0f) ? 0.0f : ((soc > 1.0f) ? 1.0f : soc);
+
+ // Select appropriate temperature array based on mode
+ const float* temp_points = discharging_mode ? model->temp_points_discharge
+ : model->temp_points_charge;
+
+ // Handle out-of-bounds temperatures
+ if (temperature <= temp_points[0]) {
+ const float* params = discharging_mode ? model->ocv_discharge_params[0]
+ : model->ocv_charge_params[0];
+ return calc_ocv(model, params, soc);
+ }
+
+ if (temperature >= temp_points[model->num_temp_points - 1]) {
+ const float* params =
+ discharging_mode
+ ? model->ocv_discharge_params[model->num_temp_points - 1]
+ : model->ocv_charge_params[model->num_temp_points - 1];
+ return calc_ocv(model, params, soc);
+ }
+
+ // Find temperature bracket and interpolate
+ for (int i = 0; i < model->num_temp_points - 1; i++) {
+ if (temperature < temp_points[i + 1]) {
+ const float* params_low = discharging_mode
+ ? model->ocv_discharge_params[i]
+ : model->ocv_charge_params[i];
+
+ const float* params_high = discharging_mode
+ ? model->ocv_discharge_params[i + 1]
+ : model->ocv_charge_params[i + 1];
+
+ float ocv_low = calc_ocv(model, params_low, soc);
+ float ocv_high = calc_ocv(model, params_high, soc);
+
+ return linear_interpolate(temperature, temp_points[i], ocv_low,
+ temp_points[i + 1], ocv_high);
+ }
+ }
+
+ // Should never reach here
+ const float* params = discharging_mode ? model->ocv_discharge_params[0]
+ : model->ocv_charge_params[0];
+ return calc_ocv(model, params, soc);
+}
+
+float battery_ocv_slope(const battery_model_t* model, float soc,
+ float temperature, bool discharging_mode) {
+ // Clamp SOC to valid range
+ soc = (soc < 0.0f) ? 0.0f : ((soc > 1.0f) ? 1.0f : soc);
+
+ // Select appropriate temperature array based on mode
+ const float* temp_points = discharging_mode ? model->temp_points_discharge
+ : model->temp_points_charge;
+
+ // Handle out-of-bounds temperatures
+ if (temperature <= temp_points[0]) {
+ const float* params = discharging_mode ? model->ocv_discharge_params[0]
+ : model->ocv_charge_params[0];
+ return calc_ocv_slope(model, params, soc);
+ }
+
+ if (temperature >= temp_points[model->num_temp_points - 1]) {
+ const float* params =
+ discharging_mode
+ ? model->ocv_discharge_params[model->num_temp_points - 1]
+ : model->ocv_charge_params[model->num_temp_points - 1];
+ return calc_ocv_slope(model, params, soc);
+ }
+
+ // Find temperature bracket and interpolate
+ for (int i = 0; i < model->num_temp_points - 1; i++) {
+ if (temperature < temp_points[i + 1]) {
+ const float* params_low = discharging_mode
+ ? model->ocv_discharge_params[i]
+ : model->ocv_charge_params[i];
+
+ const float* params_high = discharging_mode
+ ? model->ocv_discharge_params[i + 1]
+ : model->ocv_charge_params[i + 1];
+
+ float slope_low = calc_ocv_slope(model, params_low, soc);
+ float slope_high = calc_ocv_slope(model, params_high, soc);
+
+ return linear_interpolate(temperature, temp_points[i], slope_low,
+ temp_points[i + 1], slope_high);
+ }
+ }
+
+ // Should never reach here
+ const float* params = discharging_mode ? model->ocv_discharge_params[0]
+ : model->ocv_charge_params[0];
+ return calc_ocv_slope(model, params, soc);
+}
+
+float battery_soc(const battery_model_t* model, float ocv, float temperature,
+ bool discharging_mode) {
+ // Select appropriate temperature array based on mode
+ const float* temp_points = discharging_mode ? model->temp_points_discharge
+ : model->temp_points_charge;
+
+ // Handle out-of-bounds temperatures
+ if (temperature <= temp_points[0]) {
+ const float* params = discharging_mode ? model->ocv_discharge_params[0]
+ : model->ocv_charge_params[0];
+ return calc_soc_from_ocv(model, params, ocv);
+ }
+
+ if (temperature >= temp_points[model->num_temp_points - 1]) {
+ const float* params =
+ discharging_mode
+ ? model->ocv_discharge_params[model->num_temp_points - 1]
+ : model->ocv_charge_params[model->num_temp_points - 1];
+ return calc_soc_from_ocv(model, params, ocv);
+ }
+
+ // Find temperature bracket and interpolate
+ for (int i = 0; i < model->num_temp_points - 1; i++) {
+ if (temperature < temp_points[i + 1]) {
+ const float* params_low = discharging_mode
+ ? model->ocv_discharge_params[i]
+ : model->ocv_charge_params[i];
+
+ const float* params_high = discharging_mode
+ ? model->ocv_discharge_params[i + 1]
+ : model->ocv_charge_params[i + 1];
+
+ float soc_low = calc_soc_from_ocv(model, params_low, ocv);
+ float soc_high = calc_soc_from_ocv(model, params_high, ocv);
+
+ return linear_interpolate(temperature, temp_points[i], soc_low,
+ temp_points[i + 1], soc_high);
+ }
+ }
+
+ // Should never reach here
+ const float* params = discharging_mode ? model->ocv_discharge_params[0]
+ : model->ocv_charge_params[0];
+ return calc_soc_from_ocv(model, params, ocv);
+}
+
+void battery_model_init(battery_model_t* model) {
+ unit_properties_t props = {0};
+ unit_properties_get(&props);
+
+ // todo: this is model specific, should probably be handled somewhere outside
+ // of this module but since we currently only have one model we can live with
+ // this for a while
+ switch (props.battery_type) {
+ case 0:
+ default:
+ model->soc_breakpoint_1 = BATTERY_JYHPFL333838_SOC_BREAKPOINT_1;
+ model->soc_breakpoint_2 = BATTERY_JYHPFL333838_SOC_BREAKPOINT_2;
+ model->num_temp_points = BATTERY_JYHPFL333838_NUM_TEMP_POINTS;
+ model->temp_points_charge = BATTERY_JYHPFL333838_TEMP_POINTS_CHG;
+ model->temp_points_discharge = BATTERY_JYHPFL333838_TEMP_POINTS_DISCHG;
+ model->r_int_params = BATTERY_JYHPFL333838_R_INT_PARAMS;
+ model->ocv_charge_params = BATTERY_JYHPFL333838_OCV_CHARGE_PARAMS;
+ model->ocv_discharge_params = BATTERY_JYHPFL333838_OCV_DISCHARGE_PARAMS;
+ model->capacity = BATTERY_JYHPFL333838_CAPACITY;
+ break;
+ case 1:
+ model->soc_breakpoint_1 = BATTERY_HCF343837NCZ_SOC_BREAKPOINT_1;
+ model->soc_breakpoint_2 = BATTERY_HCF343837NCZ_SOC_BREAKPOINT_2;
+ model->num_temp_points = BATTERY_HCF343837NCZ_NUM_TEMP_POINTS;
+ model->temp_points_charge = BATTERY_HCF343837NCZ_TEMP_POINTS_CHG;
+ model->temp_points_discharge = BATTERY_HCF343837NCZ_TEMP_POINTS_DISCHG;
+ model->r_int_params = BATTERY_HCF343837NCZ_R_INT_PARAMS;
+ model->ocv_charge_params = BATTERY_HCF343837NCZ_OCV_CHARGE_PARAMS;
+ model->ocv_discharge_params = BATTERY_HCF343837NCZ_OCV_DISCHARGE_PARAMS;
+ model->capacity = BATTERY_HCF343837NCZ_CAPACITY;
+ break;
+ }
+}
+
+#endif
diff --git a/core/embed/io/power_manager/fuel_gauge/battery_model.h b/core/embed/io/power_manager/fuel_gauge/battery_model.h
new file mode 100644
index 00000000..56115d72
--- /dev/null
+++ b/core/embed/io/power_manager/fuel_gauge/battery_model.h
@@ -0,0 +1,102 @@
+/*
+ * This file is part of the Trezor project, https://trezor.io/
+ *
+ * Copyright (c) SatoshiLabs
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <trezor_types.h>
+
+// Include the battery data header - this will be selected at compile time
+// based on which battery is being used
+#include "battery_data_hcf343837ncz.h"
+#include "battery_data_jyhpfl333838.h"
+
+typedef struct {
+ uint8_t num_temp_points;
+ float soc_breakpoint_1;
+ float soc_breakpoint_2;
+ const float* temp_points_discharge;
+ const float* temp_points_charge;
+ const float* r_int_params;
+ const float (*ocv_discharge_params)[10];
+ const float (*ocv_charge_params)[10];
+ const float (*capacity)[2];
+} battery_model_t;
+
+/**
+ * Calculate internal resistance at the given temperature
+ * @param temperature Battery temperature in Celsius
+ * @return Internal resistance in ohms
+ */
+float battery_rint(const battery_model_t* model, float temperature);
+
+/**
+ * Get battery total capacity at the given temperature and discharge mode
+ * @param temperature Battery temperature in Celsius
+ * @param discharging_mode true if discharging, false if charging
+ * @return Total capacity in mAh
+ */
+float battery_total_capacity(const battery_model_t* model, float temperature,
+ bool discharging_mode);
+
+/**
+ * Calculate OCV from measured voltage and current
+ * @param voltage_V Measured battery voltage in volts
+ * @param current_mA Measured battery current in mA (positive for discharge)
+ * @param temperature Battery temperature in Celsius
+ * @return Open circuit voltage (OCV) in volts
+ */
+float battery_meas_to_ocv(const battery_model_t* model, float voltage_V,
+ float current_mA, float temperature);
+
+/**
+ * Get OCV for given SOC and temperature
+ * @param soc State of charge (0.0 to 1.0)
+ * @param temperature Battery temperature in Celsius
+ * @param discharging_mode true if discharging, false if charging
+ * @return Open circuit voltage in volts
+ */
+float battery_ocv(const battery_model_t* model, float soc, float temperature,
+ bool discharging_mode);
+
+/**
+ * Get the slope of the OCV curve at a given SOC and temperature
+ * @param soc State of charge (0.0 to 1.0)
+ * @param temperature Battery temperature in Celsius
+ * @param discharging_mode true if discharging, false if charging
+ * @return Slope of OCV curve (dOCV/dSOC) in volts
+ */
+float battery_ocv_slope(const battery_model_t* model, float soc,
+ float temperature, bool discharging_mode);
+
+/**
+ * Get SOC for given OCV and temperature
+ * @param ocv Open circuit voltage in volts
+ * @param temperature Battery temperature in Celsius
+ * @param discharging_mode true if discharging, false if charging
+ * @return State of charge (0.0 to 1.0)
+ */
+float battery_soc(const battery_model_t* model, float ocv, float temperature,
+ bool discharging_mode);
+
+/**
+ * @brief Initializes the battery model structure based on used battery type
+ *
+ * @param model Pointer to the battery model structure to be initialized
+ */
+void battery_model_init(battery_model_t* model);
diff --git a/core/embed/io/power_manager/fuel_gauge/fuel_gauge.c b/core/embed/io/power_manager/fuel_gauge/fuel_gauge.c
new file mode 100644
index 00000000..3f07ff88
--- /dev/null
+++ b/core/embed/io/power_manager/fuel_gauge/fuel_gauge.c
@@ -0,0 +1,157 @@
+/*
+ * This file is part of the Trezor project, https://trezor.io/
+ *
+ * Copyright (c) SatoshiLabs
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+#ifdef KERNEL_MODE
+
+#include <math.h>
+
+#include "battery_model.h"
+#include "fuel_gauge.h"
+
+void fuel_gauge_init(fuel_gauge_state_t* state, float R, float Q,
+ float R_aggressive, float Q_aggressive, float P_init) {
+ state->R = R;
+ state->Q = Q;
+ state->R_aggressive = R_aggressive;
+ state->Q_aggressive = Q_aggressive;
+
+ // Initialize state
+ state->soc = 0.0f;
+ state->soc_latched = 0.0f;
+ state->P = P_init; // Initial error covariance
+
+ battery_model_init(&state->model);
+}
+
+void fuel_gauge_reset(fuel_gauge_state_t* state) {
+ // Reset state but keep filter parameters
+ state->soc = 0.0f;
+ state->soc_latched = 0.0f;
+}
+
+void fuel_gauge_set_soc(fuel_gauge_state_t* state, float soc, float P) {
+ soc = fmaxf(0.0f, fminf(soc, 1.0f)); // Clamp SOC to [0, 1]
+
+ // Set SOC directly
+ state->soc = soc;
+ state->soc_latched = soc;
+ state->P = P; // Set error covariance
+}
+
+void fuel_gauge_initial_guess(fuel_gauge_state_t* state, float voltage_V,
+ float current_mA, float temperature) {
+ // Determine if we're in discharge mode
+ bool discharging_mode = current_mA >= 0.0f;
+
+ // Calculate OCV from terminal voltage and current
+ float ocv =
+ battery_meas_to_ocv(&state->model, voltage_V, current_mA, temperature);
+
+ // Extract SoC from battery model
+ state->soc = battery_soc(&state->model, ocv, temperature, discharging_mode);
+ state->soc = fmaxf(0.0f, fminf(state->soc, 1.0f)); // Clamp SOC to [0, 1]
+ state->soc_latched = state->soc;
+}
+
+float fuel_gauge_update(fuel_gauge_state_t* state, uint32_t dt_ms,
+ float voltage_V, float current_mA, float temperature) {
+ if (current_mA == 0.0f) {
+ // No current flow, return latched SOC without updating
+ return state->soc_latched;
+ }
+
+ // Determine if we're in discharge mode
+ bool discharging_mode = current_mA >= 0.0f;
+
+ // Choose filter parameters based on temperature and SOC
+ float R = state->R;
+ float Q = state->Q;
+
+ // When in low temperature or at the edge of the charging/dischargins
+ // profile, use more agressive EKF settings to rely more on the ocv
+ // curves rather then on current model
+ if (temperature < 10.0f) {
+ R = state->R_aggressive;
+ Q = state->Q_aggressive;
+ } else {
+ if (discharging_mode && state->soc_latched < 0.2f) {
+ R = state->R_aggressive;
+ Q = state->Q_aggressive;
+ } else if (!discharging_mode && state->soc_latched > 0.8f) {
+ R = state->R_aggressive;
+ Q = state->Q_aggressive;
+ }
+ }
+
+ // Convert milliseconds to seconds
+ float dt_sec = dt_ms / 1000.0f;
+
+ // Get total capacity at current temperature
+ float total_capacity =
+ battery_total_capacity(&state->model, temperature, discharging_mode);
+
+ // State prediction (coulomb counting)
+ // SOC_k+1 = SOC_k - (I*dt)/(3600*capacity)
+ float x_k1_k =
+ state->soc - (current_mA / (3600.0f * total_capacity)) * dt_sec;
+
+ // Calculate Jacobian of measurement function h(x) = dOCV/dSOC
+ float h_jacobian =
+ battery_ocv_slope(&state->model, x_k1_k, temperature, discharging_mode);
+
+ // Error covariance prediction
+ float P_k1_k = state->P + Q;
+
+ // Calculate innovation covariance
+ float S = h_jacobian * P_k1_k * h_jacobian + R;
+
+ // Calculate Kalman gain
+ float K_k1_k = P_k1_k * h_jacobian / S;
+
+ // Calculate predicted terminal voltage
+ float v_pred =
+ battery_ocv(&state->model, x_k1_k, temperature, discharging_mode) -
+ (current_mA / 1000.0f) * battery_rint(&state->model, temperature);
+
+ // State update
+ float x_k1_k1 = x_k1_k + K_k1_k * (voltage_V - v_pred);
+
+ // Error covariance update
+ float P_k1_k1 = (1.0f - K_k1_k * h_jacobian) * P_k1_k;
+
+ // Enforce SOC boundaries
+ state->soc = (x_k1_k1 < 0.0f) ? 0.0f : ((x_k1_k1 > 1.0f) ? 1.0f : x_k1_k1);
+ state->P = P_k1_k1;
+
+ // Update latched SOC based on current direction
+ if (current_mA > 0.0f) {
+ // Discharging, SOC should move only in negative direction
+ if (state->soc < state->soc_latched) {
+ state->soc_latched = state->soc;
+ }
+ } else {
+ // Charging, SOC should move only in positive direction
+ if (state->soc > state->soc_latched) {
+ state->soc_latched = state->soc;
+ }
+ }
+
+ return state->soc_latched;
+}
+
+#endif
diff --git a/core/embed/io/power_manager/fuel_gauge/fuel_gauge.h b/core/embed/io/power_manager/fuel_gauge/fuel_gauge.h
new file mode 100644
index 00000000..2e8b1a89
--- /dev/null
+++ b/core/embed/io/power_manager/fuel_gauge/fuel_gauge.h
@@ -0,0 +1,98 @@
+/*
+ * This file is part of the Trezor project, https://trezor.io/
+ *
+ * Copyright (c) SatoshiLabs
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <trezor_types.h>
+
+#include "battery_model.h"
+
+/**
+ * @brief Fuel gauge state structure
+ */
+typedef struct {
+ battery_model_t model; ///< Battery model parameters
+
+ /** @name State estimates */
+ /**@{*/
+ float soc; ///< State of charge estimate (0.0 to 1.0)
+ float soc_latched; ///< Latched SOC (the one that gets reported)
+ float P; ///< Error covariance
+ /**@}*/
+
+ /** @name Filter parameters */
+ /**@{*/
+ float R; ///< Measurement noise variance
+ float Q; ///< Process noise variance
+ float R_aggressive; ///< Aggressive measurement noise variance
+ float Q_aggressive; ///< Aggressive process noise variance
+ /**@}*/
+} fuel_gauge_state_t;
+
+/**
+ * @brief Initialize the fuel gauge state
+ *
+ * @param state Pointer to EKF state structure
+ * @param R Measurement noise variance
+ * @param Q Process noise variance
+ * @param R_aggressive Aggressive mode measurement noise variance
+ * @param Q_aggressive Aggressive mode process noise variance
+ * @param P_init Initial error covariance
+ */
+void fuel_gauge_init(fuel_gauge_state_t* state, float R, float Q,
+ float R_aggressive, float Q_aggressive, float P_init);
+
+/**
+ * @brief Reset the EKF state
+ *
+ * @param state Pointer to EKF state structure
+ */
+void fuel_gauge_reset(fuel_gauge_state_t* state);
+
+/**
+ * @brief Set SOC directly
+ *
+ * @param state Pointer to EKF state structure
+ * @param soc State of charge (0.0 to 1.0)
+ */
+void fuel_gauge_set_soc(fuel_gauge_state_t* state, float soc, float P);
+
+/**
+ * @brief Make initial SOC guess based on OCV
+ *
+ * @param state Pointer to EKF state structure
+ * @param voltage_V Current battery voltage (V)
+ * @param current_mA Current battery current (mA), positive for discharge
+ * @param temperature Battery temperature (°C)
+ */
+void fuel_gauge_initial_guess(fuel_gauge_state_t* state, float voltage_V,
+ float current_mA, float temperature);
+
+/**
+ * @brief Update the fuel gauge with new measurements
+ *
+ * @param state Pointer to EKF state structure
+ * @param dt_ms Time step in milliseconds
+ * @param voltage_V Current battery voltage (V)
+ * @param current_mA Current battery current (mA), positive for discharge
+ * @param temperature Battery temperature (°C)
+ * @return Updated SOC estimate (0.0 to 1.0)
+ */
+float fuel_gauge_update(fuel_gauge_state_t* state, uint32_t dt_ms,
+ float voltage_V, float current_mA, float temperature);
diff --git a/core/embed/io/power_manager/inc/io/pmic.h b/core/embed/io/power_manager/inc/io/pmic.h
new file mode 100644
index 00000000..392cd7ac
--- /dev/null
+++ b/core/embed/io/power_manager/inc/io/pmic.h
@@ -0,0 +1,151 @@
+/*
+ * This file is part of the Trezor project, https://trezor.io/
+ *
+ * Copyright (c) SatoshiLabs
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <trezor_types.h>
+
+// Charging current limits
+// - range of pmic is 32-800mA
+// - used battery limit is 180mA
+#define PMIC_CHARGING_LIMIT_MIN 32 // mA
+#define PMIC_CHARGING_LIMIT_MAX 180 // mA
+#define PMIC_CHARGING_LIMIT_DEFAULT 180 // mA
+
+typedef struct {
+ // Battery voltage [V]
+ float vbat;
+ // System voltage [V]
+ float vsys;
+ // Battery current [mA]
+ // - positive value means discharging
+ // - negative value means charging
+ float ibat;
+ // NTC temperature [°C]
+ float ntc_temp;
+ // Die temperature [°C]
+ float die_temp;
+ // IBAT_MEAS_STATUS register value
+ // (for debugging purposes, see the datasheet)
+ uint8_t ibat_meas_status;
+ // BCHGCHARGESTATUS register value
+ // (for debugging purposes, see the datasheet)
+ uint8_t charge_status;
+ uint8_t charge_err;
+ uint8_t charge_sensor_err;
+ uint8_t buck_status;
+ uint8_t usb_status;
+ // NTC disconnection flag
+ bool ntc_disconnected;
+ // battery disconnected flag
+ bool battery_disconnected;
+ // Charging phase flags decoded from charge_status
+ // - cc_phase: Constant-Current phase (charge_status bit 3)
+ // - cv_phase: Constant-Voltage phase (charge_status bit 5)
+ bool cc_phase;
+ bool cv_phase;
+} pmic_report_t;
+
+typedef void (*pmic_report_callback_t)(void* context, pmic_report_t* report);
+
+// Initializes PMIC driver
+bool pmic_init(void);
+
+// Deinitializes PMIC driver
+void pmic_deinit(void);
+
+// Suspends driver activity so the CPU can enter low-power mode.
+//
+// Suspending may take some time if the driver is currently
+// performing an operation. Caller may check the status by
+// pmic_is_suspended().
+bool pmic_suspend(void);
+
+// Resumes the driver operation after it has been suspended.
+bool pmic_resume(void);
+
+// Checks whether the driver is suspended.
+bool pmic_is_suspended(void);
+
+// Gets the cause of the last restart
+uint8_t pmic_restart_cause(void);
+
+// Switches the device to ship mode.
+//
+// In tge ship mode, the CPU is powered off, and only the VBAT domain remains
+// active. The device can be woken by pressing the power button, triggering
+// a full boot sequence.
+//
+// Ship mode can only be entered if the device is not connected to a USB or
+// wireless charger. If the device is charging, the function returns `true`,
+// and the device state remains unchanged.
+//
+// If the function succeeds, the device will not be powered off immediately,
+// but after some time (typically a few milliseconds).
+//
+// Returns `false` if the operation fails (likely due to uninitialized power
+// management).
+bool pmic_enter_shipmode(void);
+
+// Starts the asynchronous measurement
+//
+// The measurement is started as soon as possible and finished in
+// hundreds of milliseconds. The result is reported using the callback.
+//
+// The function returns `false` if the measurement cannot be started
+// (e.g. because the previous measurement is still in progress or
+// the the driver is not initialized).
+bool pmic_measure(pmic_report_callback_t callback, void* context);
+
+// Synchroneous version of the `pmic_measure` function.
+//
+// Use only for testing purposes, as it blocks the execution until
+// the measurement is done.
+//
+// Returns `true` if the measurement was successful and the report
+// is stored in the `report` structure.
+bool pmic_measure_sync(pmic_report_t* report);
+
+// Enables or disables the charging.
+//
+// The function returns `false` if the operation cannot be performed.
+bool pmic_set_charging(bool enable);
+
+// Sets the charging current limit [mA].
+//
+// The current value must be in the range defined by the
+// `NPM1300_CHARGING_LIMIT_MIN` and `NPM1300_CHARGING_LIMIT_MAX` constants.
+//
+// The function returns `false` if the operation cannot be performed.
+bool pmic_set_charging_limit(int i_charge);
+
+// Gets the charging current limit [mA].
+int pmic_get_charging_limit(void);
+
+typedef enum {
+ PMIC_BUCK_MODE_AUTO,
+ PMIC_BUCK_MODE_PWM,
+ PMIC_BUCK_MODE_PFM,
+} pmic_buck_mode_t;
+
+// Set the buck voltage regulator mode
+bool pmic_set_buck_mode(pmic_buck_mode_t buck_mode);
+
+// Clears all battery charger errors.
+bool pmic_clear_charger_errors(void);
diff --git a/core/embed/io/power_manager/inc/io/power_manager.h b/core/embed/io/power_manager/inc/io/power_manager.h
new file mode 100644
index 00000000..6d79c7f8
--- /dev/null
+++ b/core/embed/io/power_manager/inc/io/power_manager.h
@@ -0,0 +1,263 @@
+/*
+ * This file is part of the Trezor project, https://trezor.io/
+ *
+ * Copyright (c) SatoshiLabs
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <trezor_types.h>
+
+#include <sys/suspend.h>
+
+/* power manager status codes */
+typedef enum {
+ PM_OK = 0,
+ PM_NOT_INITIALIZED,
+ PM_REQUEST_REJECTED,
+ PM_TIMEOUT,
+ PM_ERROR,
+} pm_status_t;
+
+/* Power manager charging status */
+typedef enum {
+ PM_BATTERY_IDLE = 0,
+ PM_BATTERY_DISCHARGING,
+ PM_BATTERY_CHARGING,
+} pm_charging_status_t;
+
+/* Power manager internal states */
+typedef enum {
+ PM_STATE_HIBERNATE,
+ PM_STATE_CHARGING,
+ PM_STATE_SUSPEND,
+ PM_STATE_SHUTTING_DOWN,
+ PM_STATE_POWER_SAVE,
+ PM_STATE_ACTIVE,
+} pm_power_status_t;
+
+/* Power manager events */
+typedef union {
+ uint32_t all;
+ struct {
+ bool power_status_changed : 1;
+ bool charging_status_changed : 1;
+ bool usb_connected_changed : 1;
+ bool wireless_connected_changed : 1;
+ bool ntc_connected_changed : 1;
+ bool charging_limited_changed : 1;
+ bool temp_control_active_changed : 1;
+
+ // Jump detection events (fast changes within a short time window)
+ bool battery_temp_jump_detected : 1;
+ bool battery_ocv_jump_detected : 1;
+ bool soc_updated : 1;
+ } flags;
+} pm_event_t;
+
+/* Power manager state */
+typedef struct {
+ bool usb_connected;
+ bool wireless_connected;
+ bool ntc_connected;
+ bool charging_limited;
+ bool temp_control_active;
+ pm_charging_status_t charging_status;
+ pm_power_status_t power_status;
+ uint8_t soc;
+
+ // used for detection of unexpected changes
+ float battery_ocv;
+ float battery_temp;
+
+} pm_state_t;
+
+/* Power system report */
+typedef struct {
+ pm_power_status_t power_state;
+ bool usb_connected;
+ bool wireless_charger_connected;
+ float system_voltage_v;
+ float battery_voltage_v;
+ float battery_current_ma;
+ float battery_temp_c;
+ float battery_soc;
+ float battery_soc_latched;
+ float pmic_temp_c;
+ float wireless_rectifier_voltage_v;
+ float wireless_output_voltage_v;
+ float wireless_current_ma;
+ float wireless_temp_c;
+} pm_report_t;
+
+// Current version of the power management recovery data structure
+#define PM_RECOVERY_DATA_VERSION 0x0001
+
+/**
+ * @brief Structure for power management data stored in backup RAM
+ *
+ * This structure contains critical power management information that needs to
+ * persist across power cycles and resets. It stores battery state of charge
+ * (SOC), timing information, and system state data required for proper power
+ * management.
+ *
+ * If the structure is changed, the version must be incremented and
+ * proper migration logic must be implemented.
+ */
+typedef struct {
+ /** Data version */
+ uint16_t version;
+ /** Fuel gauge state of charge <0, 1> */
+ float soc;
+ /** Fuel gauge covariance */
+ float P;
+ bool bat_critical;
+ /** RTC time at which SOC was captured */
+ uint32_t last_capture_timestamp;
+ /** Power manager state at bootloader exit so it could be correctly
+ restored in the firwmare. */
+ uint32_t bootloader_exit_state;
+} pm_recovery_data_t;
+
+/**
+ * @brief Initialize the power manager
+ * @param inherit_state Whether to inherit previous power state from backup
+ * memory
+ * @return pm_status_t Status code indicating success or failure
+ */
+pm_status_t pm_init(bool inherit_state);
+
+/**
+ * Deinitialize the power manager
+ */
+void pm_deinit(void);
+
+/**
+ * @brief Read power manager pending events. Events are cleared after reading.
+ * @param event_flags Pointer to store the event flags
+ * @return bool True if any events were pending
+ */
+bool pm_get_events(pm_event_t* event_flags);
+
+/**
+ * @brief Get the current power management state
+ * @param state Pointer to store the power state information
+ * @return pm_status_t Status code indicating success or failure
+ */
+pm_status_t pm_get_state(pm_state_t* state);
+
+/**
+ * @brief Request device to enter suspend mode
+ * @param wakeup_reason Pointer to store wakeup flags (reason for wakeup).
+ * Can be NULL if not needed.
+ * @return pm_status_t Status code indicating success or failure
+ */
+pm_status_t pm_suspend(wakeup_flags_t* wakeup_reason);
+
+/**
+ * @brief Request device to enter hibernation mode
+ * @return pm_status_t Status code indicating success or failure
+ */
+pm_status_t pm_hibernate(void);
+
+/**
+ * @brief Request the device to turn on and transition from CHARGING/HIBERNATE
+ * to higher power state.
+ * @return pm_status_t Status code indicating success or failure
+ */
+pm_status_t pm_turn_on(void);
+
+/**
+ * @brief Get power manager report
+ * @param report Pointer to store power manager report
+ * @return pm_status_t Status code indicating success or failure
+ */
+pm_status_t pm_get_report(pm_report_t* report);
+
+/**
+ * @brief Enable battery charging
+ * @return pm_status_t Status code indicating success or failure
+ */
+pm_status_t pm_charging_enable(void);
+
+/**
+ * @brief Disable battery charging
+ * @return pm_status_t Status code indicating success or failure
+ */
+pm_status_t pm_charging_disable(void);
+
+/**
+ * @brief Set maximum charging current
+ * @param current_ma Maximum charging current in mA
+ * @return pm_status_t Status code indicating success or failure
+ */
+pm_status_t pm_charging_set_max_current(uint16_t current_ma);
+
+/**
+ * @brief Set the battery State of Charge precharge target.
+ *
+ * Charging controller will continously compare taget SoC charging voltage from
+ * battery model (temperature dependant) with the actual battery voltage and if
+ * the battery voltage is above the target, charging is stopped. If the battery
+ * voltage also cross the charging voltage target, Fuel gauge SoC estimate is
+ * enforced with the target value.
+ *
+ * Setting value to `100` disables the precharge target and charging cycle will
+ * be driven with PMIC driver.
+ *
+ * @param target SoC target percentage (0-100%)
+ * @return pm_status_t Status code indicating success or failure
+ */
+pm_status_t pm_set_soc_target(uint8_t target);
+
+/**
+ * @brief Check if the device is currently charging the battery
+ *
+ * @return true if the device is charging, false otherwise
+ */
+bool pm_is_charging(void);
+
+/**
+ * @brief Check if the USB is connected
+ *
+ * @return true if USB is connected, false otherwise
+ */
+bool pm_usb_is_connected(void);
+
+/**
+ * @brief Suspends driver activity so the CPU can enter low-power mode.
+ *
+ * Suspending may take some time if the driver is currently
+ * performing an operation. Caller may check the status by
+ * pm_is_suspended().
+ *
+ * @return true if the power manager was successfully suspended, false otherwise
+ */
+bool pm_driver_suspend(void);
+
+/**
+ * @brief Resume the power manager after it has been suspended
+ *
+ * @return true if the power manager was successfully resumed, false otherwise
+ * */
+bool pm_driver_resume(void);
+
+/**
+ * @brief Check if the power manager is suspended
+ *
+ * @return true if the power manager is suspended, false otherwise
+ */
+bool pm_driver_is_suspended(void);
diff --git a/core/embed/io/power_manager/npm1300/npm1300.c b/core/embed/io/power_manager/npm1300/npm1300.c
new file mode 100644
index 00000000..37ca3669
--- /dev/null
+++ b/core/embed/io/power_manager/npm1300/npm1300.c
@@ -0,0 +1,1101 @@
+/*
+ * This file is part of the Trezor project, https://trezor.io/
+ *
+ * Copyright (c) SatoshiLabs
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <trezor_bsp.h>
+#include <trezor_rtl.h>
+
+#include <math.h>
+
+#include <io/i2c_bus.h>
+#include <io/pmic.h>
+#include <sys/irq.h>
+#include <sys/mpu.h>
+#include <sys/systimer.h>
+
+#ifdef USE_SUSPEND
+#include <sys/suspend.h>
+#endif
+
+#include "npm1300_defs.h"
+
+#ifdef KERNEL_MODE
+
+// Default timeout for all I2C operations
+#define NPM1300_I2C_TIMEOUT 10
+
+// Maximum number of consecutive I2C errors after we report a fatal error
+#define NPM1300_I2C_ERROR_LIMIT 3
+
+// Delay inserted between the ADC trigger and the readout [ms]
+#define NPM1300_ADC_READOUT_DELAY 80
+
+// Minimum temperature that counts as valid data
+#define NPM1300_NTC_TEMP_VALID_MIN (-80.0)
+
+// Minimum temperature that counts as valid data
+#define NPM1300_NTC_TEMP_VALID_MAX (100.0)
+
+// Minimum battery voltage that counts as valid data
+#define NPM1300_BATT_VOLTAGE_VALID_MIN (0.5)
+
+// NPM1300 FSM states
+typedef enum {
+ NPM1300_STATE_IDLE = 0,
+ NPM1300_STATE_CLEAR_EVENTS,
+ NPM1300_STATE_CHARGING_ENABLE,
+ NPM1300_STATE_CHARGING_DISABLE,
+ NPM1300_STATE_CHARGING_LIMIT,
+ NPM1300_STATE_BUCK_MODE_SET,
+ NPM1300_STATE_ENTER_SHIPMODE,
+ NPM1300_STATE_ADC_TRIGGER,
+ NPM1300_STATE_ADC_WAIT,
+ NPM1300_STATE_ADC_READOUT,
+} npm1300_fsm_state_t;
+
+typedef struct {
+ uint8_t adc_gp0_result_lsbs;
+ uint8_t adc_vbat_result_msb;
+ uint8_t adc_nt_result_msb;
+ uint8_t adc_temp_result_msb;
+ uint8_t adc_vsys_result_msb;
+ uint8_t adc_gp1_result_lsbs;
+ uint8_t adc_vbat2_result_msb;
+ uint8_t adc_ibat_meas_status;
+ uint8_t charging_status;
+ uint8_t charging_err;
+ uint8_t charging_sensor_err;
+ uint8_t buck_status;
+ uint8_t usb_status;
+} npm1300_adc_regs_t;
+
+typedef struct {
+ uint8_t bchg_iset_msb;
+ uint8_t bchg_iset_lsb;
+
+} npm1300_chlimit_regs_t;
+
+typedef struct {
+ uint8_t vbusin;
+
+} npm1300_event_regs_t;
+
+// NPM1300 PMIC driver state
+typedef struct {
+ // Set if the PMIC driver is initialized
+ bool initialized;
+
+ // EXTI handle
+ EXTI_HandleTypeDef exti_handle;
+
+ // I2C bus where the PMIC is connected
+ i2c_bus_t* i2c_bus;
+ // Number of consecutive I2C errors
+ int i2c_errors;
+ // Storage for the pending I2C packet
+ i2c_packet_t pending_i2c_packet;
+
+ // Timer used for waiting for the ADC conversion
+ systimer_t* timer;
+
+ // Content of RTSCAUSE register read during driver initialization
+ uint8_t restart_cause;
+
+ // Current state of the FSM
+ npm1300_fsm_state_t state;
+
+ // Set if the driver was requested to suspend background operations.
+ // IF so, the driver waits until the last operation is finished,
+ // then enters suspended mode.
+ bool suspending;
+
+ // Set if the driver's background operations are suspended.
+ // In suspended mode, the driver does not start any new operations.
+ bool suspended;
+
+ // ADC register (global buffer used for ADC measurements)
+ npm1300_adc_regs_t adc_regs;
+ // Charging limit registers (global buffer used for charging limit)
+ npm1300_chlimit_regs_t chlimit_regs;
+ // Event registers (global buffer used for events readout)
+ npm1300_event_regs_t event_regs;
+
+ // Discharge current limit [mA]
+ uint16_t i_limit;
+
+ // Charge current limit [mA]
+ uint16_t i_charge; // written value
+ uint16_t i_charge_requested; // requested value
+ uint16_t i_charge_set; // value beeing written
+
+ // Set if the charging is enabled
+ bool charging;
+ bool charging_requested;
+
+ // Buck voltage regulator mode
+ pmic_buck_mode_t buck_mode; // written value
+ pmic_buck_mode_t buck_mode_requested; // requested value
+ pmic_buck_mode_t buck_mode_set; // value beeing written
+
+ // Enter ship mode
+ bool shipmode_requested;
+
+ // Request flags for ADC measurements
+ bool adc_trigger_requested;
+ bool adc_readout_requested;
+
+ // Request flag for clearing events and releasing INT line
+ bool clear_events_requested;
+
+ // Report callback used for asynchronous measurements
+ pmic_report_callback_t report_callback;
+ void* report_callback_context;
+
+} npm1300_driver_t;
+
+// PMIC driver instance
+npm1300_driver_t g_npm1300_driver = {
+ .initialized = false,
+};
+
+// forward declarations
+static void npm1300_timer_callback(void* context);
+static void npm1300_i2c_callback(void* context, i2c_packet_t* packet);
+static void npm1300_fsm_continue(npm1300_driver_t* drv);
+
+// Writes a value to the NPM1300 register
+//
+// This function is used only during driver initialization because
+// it's synchronous and blocks the execution.
+static bool npm1300_set_reg(i2c_bus_t* bus, uint16_t addr, uint8_t value) {
+ i2c_op_t ops[] = {
+ {
+ .flags = I2C_FLAG_TX | I2C_FLAG_EMBED,
+ .size = 3,
+ .data = {addr >> 8, addr & 0xFF, value},
+ },
+ };
+
+ i2c_packet_t pkt = {
+ .address = NPM1300_I2C_ADDRESS,
+ .timeout = NPM1300_I2C_TIMEOUT,
+ .op_count = ARRAY_LENGTH(ops),
+ .ops = ops,
+ };
+
+ if (I2C_STATUS_OK != i2c_bus_submit_and_wait(bus, &pkt)) {
+ return false;
+ }
+
+ return true;
+}
+
+// Reads a value from the NPM1300 register
+//
+// This function is used only during driver initialization because
+// it's synchronous and blocks the execution.
+static bool npm1300_get_reg(i2c_bus_t* bus, uint16_t addr, uint8_t* data) {
+ i2c_op_t ops[] = {
+ {
+ .flags = I2C_FLAG_TX | I2C_FLAG_EMBED,
+ .size = 2,
+ .data = {addr >> 8, addr & 0xFF},
+ },
+ {
+ .flags = I2C_FLAG_RX,
+ .size = sizeof(*data),
+ .ptr = data,
+ },
+ };
+
+ i2c_packet_t pkt = {
+ .address = NPM1300_I2C_ADDRESS,
+ .timeout = NPM1300_I2C_TIMEOUT,
+ .op_count = ARRAY_LENGTH(ops),
+ .ops = ops,
+ };
+
+ if (I2C_STATUS_OK != i2c_bus_submit_and_wait(bus, &pkt)) {
+ return false;
+ }
+
+ return true;
+}
+
+// Initializes the NPM1300 driver to the default state
+static bool npm1300_initialize(i2c_bus_t* bus, uint16_t i_charge,
+ uint16_t i_limit) {
+ uint16_t bchg_iset = i_charge / 2; // 2mA steps
+ uint16_t bchg_iset_discharge = (i_limit * 100) / 323; // 3.23mA steps
+ uint16_t die_temp_stop = 360; // 110°C
+ uint16_t die_temp_resume = 372; // 100°C
+ uint16_t ntc_cold = 749; // 0°C
+ uint16_t ntc_cool = 658; // 10°C
+ uint16_t ntc_warm = 337; // 45°C
+ uint16_t ntc_hot = 237; // 60°C
+
+ struct {
+ uint16_t addr;
+ uint8_t value;
+ } table[] = {
+ {NPM1300_SCRATCH0, 0x00},
+ {NPM1300_SCRATCH1, 0x00},
+ // SYSREG
+ {NPM1300_VBUSINILIM0, NPM1300_VBUSINILIM0_500MA},
+ {NPM1300_VBUSINILIMSTARTUP, NPM1300_VBUSINILIM0_500MA},
+ {NPM1300_VBUSSUSPEND, 0x00},
+ {NPM1300_TASKUPDATEILIMSW, NPM1300_TASKUPDATEILIM_SELVBUSILIM0},
+ // LOADSW/LDO
+ {NPM1300_LDSW1GPISEL, 0x00},
+ {NPM1300_LDSW2GPISEL, 0x00},
+ {NPM1300_TASKLDSW1CLR, 0x01},
+ {NPM1300_TASKLDSW2CLR, 0x01},
+ // BUCK regulators
+
+ // NOTE: NPM1300 ERRATA #27
+ // this settings adds 900uA on VBAT in case the BUCK1NORMVOUT selected
+ // the same voltage as it is selected by external resistor.
+ {NPM1300_BUCK1NORMVOUT, 19}, // 2.9V
+ {NPM1300_BUCKSWCTRLSEL, 1},
+
+ // Buck auto mode, Pull downs disabled
+ {NPM1300_BUCKCTRL0, 0}, // Auto mode
+ //
+ // TODO
+ // ADC settings
+ {NPM1300_ADCNTCRSEL, NPM1300_ADCNTCRSEL_10K},
+ {NPM1300_ADCCONFIG, 0x00},
+ {NPM1300_ADCIBATMEASEN, NPM1300_ADCIBATMEASEN_IBATMEASENABLE},
+ // Charger settings
+ {NPM1300_BCHGVTERM, NPM1300_BCHGVTERM_3V65},
+ {NPM1300_BCHGVTERMR, NPM1300_BCHGVTERM_3V60},
+ {NPM1300_BCHGVTRICKLESEL, NPM1300_BCHGVTRICKLESEL_2V5},
+ {NPM1300_BCHGITERMSEL, NPM1300_BCHGITERMSEL_SEL10},
+ {NPM1300_BCHGISETMSB, bchg_iset >> 1},
+ {NPM1300_BCHGISETLSB, bchg_iset & 1},
+ {NPM1300_BCHGISETDISCHARGEMSB, bchg_iset_discharge >> 1},
+ {NPM1300_BCHGISETDISCHARGELSB, bchg_iset_discharge & 1},
+ {NPM1300_BCHGDISABLECLR, NPM1300_BCHGDISABLECLR_USENTC},
+ {NPM1300_BCHGDISABLECLR, NPM1300_BCHGDISABLECLR_ENABLERCHRG},
+ {NPM1300_BCHGCONFIG, 0},
+ // Disable charging
+ {NPM1300_BCHGENABLECLR, NPM1300_BCHGENABLECLR_DISABLECHG},
+ // NTC thresholds
+ {NPM1300_NTCCOLD, ntc_cold >> 2},
+ {NPM1300_NTCCOLDLSB, ntc_cold & 0x3},
+ {NPM1300_NTCCOOL, ntc_cool >> 2},
+ {NPM1300_NTCCOOLLSB, ntc_cool & 0x3},
+ {NPM1300_NTCWARM, ntc_warm >> 2},
+ {NPM1300_NTCWARMLSB, ntc_warm & 0x3},
+ {NPM1300_NTCHOT, ntc_hot >> 2},
+ {NPM1300_NTCHOTLSB, ntc_hot & 0x3},
+ // Die tempererature thresholds
+ {NPM1300_DIETEMPSTOP, die_temp_stop >> 2},
+ {NPM1300_DIETEMPSTOPLSB, die_temp_stop & 0x03},
+ {NPM1300_DIETEMPRESUME, die_temp_resume >> 2},
+ {NPM1300_DIETEMPRESUMELSB, die_temp_resume & 0x03},
+ // LEDS
+ {NPM1300_LEDDRV0MODESEL, NPM1300_LEDDRVMODESEL_NOTUSED},
+ {NPM1300_LEDDRV1MODESEL, NPM1300_LEDDRVMODESEL_NOTUSED},
+ {NPM1300_LEDDRV2MODESEL, NPM1300_LEDDRVMODESEL_NOTUSED},
+ // GPIO0
+ {NPM1300_GPIOMODE0, NPM1300_GPIOMODE_GPOIRQ}, // GPIO0 as IRQ
+ {NPM1300_GPIODRIVE0, 0x00}, // 1mA
+ {NPM1300_GPIOOPENDRAIN0, 0x00}, // Push-pull output
+ // GPIO1-4
+ {NPM1300_GPIOMODE1, NPM1300_GPIOMODE_GPIINPUT},
+ {NPM1300_GPIOMODE2, NPM1300_GPIOMODE_GPIINPUT},
+ {NPM1300_GPIOMODE3, NPM1300_GPIOMODE_GPIINPUT},
+ {NPM1300_GPIOMODE4, NPM1300_GPIOMODE_GPIINPUT},
+ // POF
+ {NPM1300_POFCONFIG, 0x00},
+ // TIMER
+ {NPM1300_TIMERCLR, 0x01},
+ // Ship and hibernate mode
+ // {NPM1300_SHPHLDCONFIG, .. },
+ // {NPM1300_TASKSHPHLDCFGSTROBE, 0x01},
+ // Clear all events
+ {NPM1300_EVENTSADCCLR, 0xFF},
+ {NPM1300_EVENTSBCHARGER0CLR, 0x3F},
+ {NPM1300_EVENTSBCHARGER1CLR, 0x3F},
+ {NPM1300_EVENTSBCHARGER2CLR, 0x07},
+ {NPM1300_EVENTSSHPHLDCLR, 0x0F},
+ {NPM1300_EVENTSVBUSIN0CLR, 0x3F},
+ {NPM1300_EVENTSVBUSIN1CLR, 0x3F},
+ {NPM1300_EVENTSGPIOCLR, 0x1F},
+ // Disable all interrupts
+ {NPM1300_INTENEVENTSADCCLR, 0xFF},
+ {NPM1300_INTENEVENTSBCHARGER0CLR, 0x3F},
+ {NPM1300_INTENEVENTSBCHARGER1CLR, 0x3F},
+ {NPM1300_INTENEVENTSBCHARGER2CLR, 0x07},
+ {NPM1300_INTENEVENTSSHPHLDCLR, 0x0F},
+ {NPM1300_INTENEVENTSVBUSIN0CLR, 0x3F},
+ {NPM1300_INTENEVENTSVBUSIN1CLR, 0x3F},
+ {NPM1300_INTENEVENTSGPIOCLR, 0x1F},
+ // Enable interrupts we are interested in
+ {NPM1300_INTENEVENTSVBUSIN0SET, 0x01}, // VBUS detected
+
+ // TODO automatic temp measurement during charging
+ };
+
+ for (int i = 0; i < sizeof(table) / sizeof(table[0]); i++) {
+ if (!npm1300_set_reg(bus, table[i].addr, table[i].value)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool pmic_init(void) {
+ npm1300_driver_t* drv = &g_npm1300_driver;
+
+ if (drv->initialized) {
+ return true;
+ }
+
+ memset(drv, 0, sizeof(npm1300_driver_t));
+
+ drv->i_charge = PMIC_CHARGING_LIMIT_DEFAULT; // mA
+ drv->i_limit = 500; // mA (268mA-1340mA)
+
+ drv->i_charge_set = drv->i_charge;
+ drv->i_charge_requested = drv->i_charge;
+
+ drv->buck_mode_requested = PMIC_BUCK_MODE_AUTO;
+ drv->buck_mode_set = PMIC_BUCK_MODE_AUTO;
+ drv->buck_mode = PMIC_BUCK_MODE_AUTO;
+
+ drv->i2c_bus = i2c_bus_open(NPM1300_I2C_INSTANCE);
+ if (drv->i2c_bus == NULL) {
+ goto cleanup;
+ }
+
+ drv->timer = systimer_create(npm1300_timer_callback, drv);
+ if (drv->timer == NULL) {
+ goto cleanup;
+ }
+
+ GPIO_InitTypeDef GPIO_InitStructure = {0};
+
+ // INT pin, active low, external pull-up
+ NPM1300_INT_PIN_CLK_ENA();
+ GPIO_InitStructure.Mode = GPIO_MODE_INPUT;
+ GPIO_InitStructure.Pull = GPIO_NOPULL;
+ GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_LOW;
+ GPIO_InitStructure.Pin = NPM1300_INT_PIN;
+ HAL_GPIO_Init(NPM1300_INT_PORT, &GPIO_InitStructure);
+
+ // Setup interrupt line for the NPM1300
+ EXTI_ConfigTypeDef EXTI_Config = {0};
+ EXTI_Config.GPIOSel = NPM1300_EXTI_INTERRUPT_GPIOSEL;
+ EXTI_Config.Line = NPM1300_EXTI_INTERRUPT_LINE;
+ EXTI_Config.Mode = EXTI_MODE_INTERRUPT;
+ EXTI_Config.Trigger = EXTI_TRIGGER_RISING;
+ HAL_EXTI_SetConfigLine(&drv->exti_handle, &EXTI_Config);
+
+ if (!npm1300_get_reg(drv->i2c_bus, NPM1300_RSTCAUSE, &drv->restart_cause)) {
+ goto cleanup;
+ }
+
+ if (!npm1300_initialize(drv->i2c_bus, drv->i_charge, drv->i_limit)) {
+ goto cleanup;
+ }
+
+ // Enable interrupt line
+ NVIC_SetPriority(NPM1300_EXTI_INTERRUPT_NUM, IRQ_PRI_NORMAL);
+ __HAL_GPIO_EXTI_CLEAR_FLAG(NPM1300_INT_PIN);
+ NVIC_EnableIRQ(NPM1300_EXTI_INTERRUPT_NUM);
+
+ drv->initialized = true;
+
+ return true;
+
+cleanup:
+ pmic_deinit();
+ return false;
+}
+
+void pmic_deinit(void) {
+ npm1300_driver_t* drv = &g_npm1300_driver;
+
+ NVIC_DisableIRQ(NPM1300_EXTI_INTERRUPT_NUM);
+ HAL_EXTI_ClearConfigLine(&drv->exti_handle);
+
+ i2c_bus_close(drv->i2c_bus);
+ systimer_delete(drv->timer);
+
+ memset(drv, 0, sizeof(npm1300_driver_t));
+}
+
+bool pmic_suspend(void) {
+ npm1300_driver_t* drv = &g_npm1300_driver;
+
+ if (!drv->initialized) {
+ return false;
+ }
+
+ irq_key_t irq_key = irq_lock();
+ drv->suspending = true;
+ npm1300_fsm_continue(drv);
+ irq_unlock(irq_key);
+
+ return true;
+}
+
+bool pmic_resume(void) {
+ npm1300_driver_t* drv = &g_npm1300_driver;
+
+ if (!drv->initialized) {
+ return false;
+ }
+
+ irq_key_t irq_key = irq_lock();
+ drv->suspending = false;
+ drv->suspended = false;
+ npm1300_fsm_continue(drv);
+ irq_unlock(irq_key);
+
+ return true;
+}
+
+bool pmic_is_suspended(void) {
+ npm1300_driver_t* drv = &g_npm1300_driver;
+
+ if (!drv->initialized) {
+ return false;
+ }
+
+ bool is_suspended;
+
+ irq_key_t irq_key = irq_lock();
+ is_suspended = drv->suspended;
+ irq_unlock(irq_key);
+
+ return is_suspended;
+}
+
+bool pmic_enter_shipmode(void) {
+ npm1300_driver_t* drv = &g_npm1300_driver;
+
+ if (!drv->initialized) {
+ return false;
+ }
+
+ irq_key_t irq_key = irq_lock();
+ drv->shipmode_requested = true;
+ npm1300_fsm_continue(drv);
+ irq_unlock(irq_key);
+
+ return true;
+}
+
+int pmic_get_charging_limit(void) {
+ npm1300_driver_t* drv = &g_npm1300_driver;
+
+ if (!drv->initialized) {
+ return 0;
+ }
+
+ return drv->i_charge_requested;
+}
+
+bool pmic_set_charging_limit(int i_charge) {
+ npm1300_driver_t* drv = &g_npm1300_driver;
+
+ if (!drv->initialized) {
+ return false;
+ }
+
+ if (i_charge < PMIC_CHARGING_LIMIT_MIN ||
+ i_charge > PMIC_CHARGING_LIMIT_MAX) {
+ // The value is out of range
+ return false;
+ }
+
+ irq_key_t irq_key = irq_lock();
+ drv->i_charge_requested = i_charge;
+ npm1300_fsm_continue(drv);
+ irq_unlock(irq_key);
+
+ return true;
+}
+
+bool pmic_set_charging(bool enable) {
+ npm1300_driver_t* drv = &g_npm1300_driver;
+
+ if (!drv->initialized) {
+ return false;
+ }
+
+ irq_key_t irq_key = irq_lock();
+ drv->charging_requested = enable;
+ npm1300_fsm_continue(drv);
+ irq_unlock(irq_key);
+
+ return true;
+}
+
+bool pmic_set_buck_mode(pmic_buck_mode_t buck_mode) {
+ npm1300_driver_t* drv = &g_npm1300_driver;
+
+ if (!drv->initialized) {
+ return false;
+ }
+
+ irq_key_t irq_key = irq_lock();
+ drv->buck_mode_requested = buck_mode;
+ npm1300_fsm_continue(drv);
+ irq_unlock(irq_key);
+
+ return true;
+}
+
+uint8_t pmic_restart_cause(void) {
+ npm1300_driver_t* drv = &g_npm1300_driver;
+
+ if (!drv->initialized) {
+ return 0;
+ }
+
+ return drv->restart_cause;
+}
+
+bool pmic_measure(pmic_report_callback_t callback, void* context) {
+ npm1300_driver_t* drv = &g_npm1300_driver;
+
+ if (!drv->initialized) {
+ return false;
+ }
+
+ irq_key_t irq_key = irq_lock();
+
+ if (drv->report_callback != NULL && callback != NULL) {
+ // Cannot start another measurement while the previous one is in progress
+ irq_unlock(irq_key);
+ return false;
+ }
+
+ drv->report_callback = callback;
+ drv->report_callback_context = context;
+
+ if (drv->report_callback != NULL) {
+ drv->adc_trigger_requested = true;
+ npm1300_fsm_continue(drv);
+ }
+
+ irq_unlock(irq_key);
+
+ return true;
+}
+
+// Synchronous measurement context structure
+// (used internally within the `npm1300_measure_sync` function)
+typedef struct {
+ // Set when the measurement is done
+ volatile bool done;
+ // Report structure where the measurement is stored
+ pmic_report_t* report;
+} npm1300_sync_measure_t;
+
+// Callback for the synchronous measurement
+static void npm1300_sync_measure_callback(void* context,
+ pmic_report_t* report) {
+ npm1300_sync_measure_t* ctx = (npm1300_sync_measure_t*)context;
+ *ctx->report = *report;
+ ctx->done = true;
+}
+
+bool pmic_measure_sync(pmic_report_t* report) {
+ npm1300_sync_measure_t measure = {
+ .done = false,
+ .report = report,
+ };
+
+ // Start asynchronous measurement
+ if (!pmic_measure(npm1300_sync_measure_callback, &measure)) {
+ return false;
+ }
+
+ // Wait for the measurement to finish
+ while (!measure.done) {
+ __WFI();
+ }
+
+ return true;
+}
+
+// Prepares PMIC report from the last readout of the ADC values
+// stored in `drv->adc_regs`
+//
+// This function is called in the irq context.
+static void npm1300_calculate_report(npm1300_driver_t* drv,
+ pmic_report_t* report) {
+ memset(report, 0, sizeof(pmic_report_t));
+
+ npm1300_adc_regs_t* r = &drv->adc_regs;
+
+ // Gather measured values from the ADC registers
+
+ uint16_t vbat_adc =
+ (r->adc_vbat_result_msb << 2) + (r->adc_gp0_result_lsbs & 0x03);
+
+ uint16_t ntc_adc =
+ (r->adc_nt_result_msb << 2) + ((r->adc_gp0_result_lsbs >> 2) & 0x3);
+
+ uint16_t die_adc =
+ (r->adc_temp_result_msb << 2) + ((r->adc_gp0_result_lsbs >> 4) & 0x3);
+
+ uint16_t vsys_adc =
+ (r->adc_vsys_result_msb << 2) + ((r->adc_gp0_result_lsbs >> 6) & 0x03);
+
+ uint16_t ibat_adc =
+ (r->adc_vbat2_result_msb << 2) + ((r->adc_gp1_result_lsbs >> 4) & 0x03);
+ // IBAT_MEAS_STATUS register isn't well documented in the NPM1300 datasheet.
+ // The following is based partially on observation.
+ //
+ // 00100 - discharge
+ // 01000 - usb powered, not charging
+ // 01100 - charge trickle
+ // 01110 - charge cool
+ // 01111 - charge normal
+ // 1XXXX - invalid value, start measure again
+
+ // bool ibat_invalid = (ibat_meas_status & 0x10) != 0;
+ bool ibat_discharging = ((r->adc_ibat_meas_status >> 2) & 0x03) == 1;
+ bool ibat_charging = ((r->adc_ibat_meas_status >> 2) & 0x03) == 3;
+
+ // Calculate the battery current based on the ADC reading and operating state.
+ // If discharging, use the discharge current limit (i_limit).
+ // If charging, use the charge current limit (i_charge).
+ // See the NPM1300 datasheet for details.
+ if (ibat_discharging) {
+ report->ibat = ((int)ibat_adc * drv->i_limit) / 1250.0;
+ } else if (ibat_charging) {
+ report->ibat = -((int)ibat_adc * drv->i_charge) / 800.0;
+ } else {
+ report->ibat = 0;
+ }
+
+ // Calculate the battery voltage (VBAT) from the ADC value.
+ // VBAT is scaled by the voltage divider ratio and ADC resolution.
+ report->vbat = (vbat_adc * 5.0) / 1023.0;
+
+ // if the battery voltage is below the accepted minimum, flag the battery as
+ // disconnected
+ report->battery_disconnected =
+ (report->vbat < NPM1300_BATT_VOLTAGE_VALID_MIN);
+
+ // Calculate the temperature from the NTC (thermistor).
+ // Beta value for the thermistor is specified as 3380.
+ // The equation is derived from the NPM1300 datasheet.
+ float beta = 3380;
+ report->ntc_temp =
+ 1 / (1 / 298.15 - (1 / beta) * logf(1024.0 / ntc_adc - 1)) - 298.15 +
+ 25.0;
+
+ // if the temperature is below the accepted minimum, flag the NTC as
+ // disconnected
+ report->ntc_disconnected = (report->ntc_temp < NPM1300_NTC_TEMP_VALID_MIN ||
+ report->ntc_temp > NPM1300_NTC_TEMP_VALID_MAX);
+
+ // Calculate the die temperature from the die ADC reading.
+ // The equation is derived from the NPM1300 datasheet.
+ report->die_temp = 394.67 - 0.7926 * die_adc;
+
+ // Calculate the system voltage (VSYS) from the ADC value.
+ // VSYS is scaled based on the system voltage divider ratio and ADC
+ // resolution.
+ report->vsys = (vsys_adc * 6.375) / 1023.0;
+
+ // Populate measurement and status flags from the raw data
+ report->ibat_meas_status = r->adc_ibat_meas_status;
+ report->buck_status = r->buck_status;
+ report->usb_status = r->usb_status;
+ report->charge_status = r->charging_status;
+ // Decode and expose charging phase flags
+ // Bit 3 -> Constant-Current phase, Bit 4 -> Constant-Voltage phase
+ report->cc_phase = (r->charging_status & 0x08) != 0;
+ report->cv_phase = (r->charging_status & 0x10) != 0;
+ report->charge_err = r->charging_err;
+ report->charge_sensor_err = r->charging_sensor_err;
+}
+
+// I2C operation for writing constant value to the npm1300 register
+#define NPM_WRITE_CONST(reg, value) \
+ { \
+ .flags = I2C_FLAG_TX | I2C_FLAG_EMBED | I2C_FLAG_START, .size = 3, \
+ .data = {(reg) >> 8, (reg) & 0xFF, (value)}, \
+ }
+
+// I2C operations for the value of specified uint8_t field
+// in `g_npm1300_driver` structure into npm1300 register
+#define NPM_WRITE_FIELD(reg, field) \
+ { \
+ .flags = I2C_FLAG_TX | I2C_FLAG_EMBED | I2C_FLAG_START, \
+ .size = 2, \
+ .data = {(reg) >> 8, (reg) & 0xFF}, \
+ }, \
+ { \
+ .flags = I2C_FLAG_TX, .size = 1, .ptr = &g_npm1300_driver.field, \
+ }
+
+// I2C operations for reading npm1300 register into the specified
+// field in `g_npm1300_driver` structure
+#define NPM_READ_FIELD(reg, field) \
+ { \
+ .flags = I2C_FLAG_TX | I2C_FLAG_EMBED | I2C_FLAG_START, \
+ .size = 2, \
+ .data = {(reg) >> 8, (reg) & 0xFF}, \
+ }, \
+ { \
+ .flags = I2C_FLAG_RX, .size = 1, .ptr = &g_npm1300_driver.field, \
+ }
+
+// I2C operations for enabling of the charging
+static const i2c_op_t npm1300_ops_charging_enable[] = {
+ NPM_WRITE_CONST(NPM1300_BCHGENABLESET, NPM1300_BCHGENABLESET_ENABLECHG),
+};
+
+// I2C operations for disabling of the charging
+static const i2c_op_t npm1300_ops_charging_disable[] = {
+ NPM_WRITE_CONST(NPM1300_BCHGENABLECLR, NPM1300_BCHGENABLECLR_DISABLECHG),
+};
+
+// I2C operations for setting of the charging limit from
+// `g_npm1300_driver.chlimit_regs` structure
+static const i2c_op_t npm1300_ops_charging_limit[] = {
+ NPM_WRITE_FIELD(NPM1300_BCHGISETMSB, chlimit_regs.bchg_iset_msb),
+ NPM_WRITE_FIELD(NPM1300_BCHGISETLSB, chlimit_regs.bchg_iset_lsb),
+};
+
+static const i2c_op_t npm1300_ops_buck_auto[] = {
+ NPM_WRITE_CONST(NPM1300_BUCKCTRL0, 0),
+ NPM_WRITE_CONST(NPM1300_BUCK1PWMCLR, 1),
+};
+
+static const i2c_op_t npm1300_ops_buck_pwm[] = {
+ NPM_WRITE_CONST(NPM1300_BUCKCTRL0, 0),
+ NPM_WRITE_CONST(NPM1300_BUCK1PWMSET, 1),
+};
+
+static const i2c_op_t npm1300_ops_buck_pfm[] = {
+ NPM_WRITE_CONST(NPM1300_BUCK1PWMCLR, 1),
+ NPM_WRITE_CONST(NPM1300_BUCKCTRL0, 1), // Auto mode
+};
+
+static const i2c_op_t npm1300_ops_enter_shipmode[] = {
+ NPM_WRITE_CONST(NPM1300_TASKENTERSHIPMODE, 1),
+};
+
+// I2C operations for setting of the charging limit from
+// `g_npm1300_driver.chlimit_regs` structure together with
+// disabling and re-enabling of the charging
+static const i2c_op_t npm1300_ops_charging_limit_reenable[] = {
+ NPM_WRITE_CONST(NPM1300_BCHGENABLECLR, NPM1300_BCHGENABLECLR_DISABLECHG),
+ NPM_WRITE_FIELD(NPM1300_BCHGISETMSB, chlimit_regs.bchg_iset_msb),
+ NPM_WRITE_FIELD(NPM1300_BCHGISETLSB, chlimit_regs.bchg_iset_lsb),
+ NPM_WRITE_CONST(NPM1300_BCHGENABLESET, NPM1300_BCHGENABLESET_ENABLECHG),
+};
+
+// I2C operations for triggering of the ADC measurements
+static const i2c_op_t npm1300_ops_adc_trigger[] = {
+ NPM_WRITE_CONST(NPM1300_TASKVBATMEASURE, 1),
+ NPM_WRITE_CONST(NPM1300_TASKVSYSMEASURE, 1),
+ NPM_WRITE_CONST(NPM1300_TASKNTCMEASURE, 1),
+ NPM_WRITE_CONST(NPM1300_TASKTEMPMEASURE, 1),
+};
+
+// I2C operations for readout of the ADC values into the
+// `g_npm1300_driver.adc_regs` structure
+static const i2c_op_t npm1300_ops_adc_readout[] = {
+ NPM_READ_FIELD(NPM1300_ADCGP0RESULTLSBS, adc_regs.adc_gp0_result_lsbs),
+ NPM_READ_FIELD(NPM1300_ADCVBATRESULTMSB, adc_regs.adc_vbat_result_msb),
+ NPM_READ_FIELD(NPM1300_ADCNTCRESULTMSB, adc_regs.adc_nt_result_msb),
+ NPM_READ_FIELD(NPM1300_ADCTEMPRESULTMSB, adc_regs.adc_temp_result_msb),
+ NPM_READ_FIELD(NPM1300_ADCVSYSRESULTMSB, adc_regs.adc_vsys_result_msb),
+ NPM_READ_FIELD(NPM1300_ADCGP1RESULTLSBS, adc_regs.adc_gp1_result_lsbs),
+ NPM_READ_FIELD(NPM1300_ADCVBAT2RESULTMSB, adc_regs.adc_vbat2_result_msb),
+ NPM_READ_FIELD(NPM1300_ADCIBATMEASSTATUS, adc_regs.adc_ibat_meas_status),
+ NPM_READ_FIELD(NPM1300_BCHGCHARGESTATUS, adc_regs.charging_status),
+ NPM_READ_FIELD(NPM1300_BCHGERRREASON, adc_regs.charging_err),
+ NPM_READ_FIELD(NPM1300_BCHGERRSENSOR, adc_regs.charging_sensor_err),
+ NPM_READ_FIELD(NPM1300_BUCKSTATUS, adc_regs.buck_status),
+ NPM_READ_FIELD(NPM1300_USBCDETECTSTATUS, adc_regs.usb_status),
+};
+
+// I2C operation that read & clears event flags and releases INT line
+static const i2c_op_t npm1300_ops_clear_events[] = {
+ NPM_READ_FIELD(NPM1300_VBUSINSTATUS, event_regs.vbusin),
+ NPM_WRITE_CONST(NPM1300_EVENTSVBUSIN0CLR, 0x3F),
+};
+
+// Clear charger errors and release charging from error state
+static const i2c_op_t npm1300_ops_clear_charger_errors[] = {
+ NPM_WRITE_CONST(NPM1300_TASKCLEARCHGERR, 1),
+ NPM_WRITE_CONST(NPM1300_TASKRELEASEERR, 1),
+};
+
+#define npm1300_i2c_submit(drv, ops) \
+ _npm1300_i2c_submit(drv, ops, ARRAY_LENGTH(ops))
+
+// helper function for submitting I2C operations
+static void _npm1300_i2c_submit(npm1300_driver_t* drv, const i2c_op_t* ops,
+ size_t op_count) {
+ i2c_packet_t* pkt = &drv->pending_i2c_packet;
+
+ memset(pkt, 0, sizeof(i2c_packet_t));
+ pkt->address = NPM1300_I2C_ADDRESS;
+ pkt->context = drv;
+ pkt->callback = npm1300_i2c_callback;
+ pkt->timeout = NPM1300_I2C_TIMEOUT;
+ pkt->ops = (i2c_op_t*)ops;
+ pkt->op_count = op_count;
+
+ i2c_status_t status = i2c_bus_submit(drv->i2c_bus, pkt);
+
+ if (status != I2C_STATUS_OK) {
+ // This should never happen
+ error_shutdown("npm1300 I2C submit error");
+ }
+}
+
+bool pmic_clear_charger_errors(void) {
+ npm1300_driver_t* drv = &g_npm1300_driver;
+
+ if (!drv->initialized) {
+ return false;
+ }
+
+ irq_key_t irq_key = irq_lock();
+ npm1300_i2c_submit(drv, npm1300_ops_clear_charger_errors);
+ irq_unlock(irq_key);
+
+ return true;
+}
+
+// npm1300 driver timer callback invoked when `drv->timer` expires.
+//
+// This function is called in the irq context.
+static void npm1300_timer_callback(void* context) {
+ npm1300_driver_t* drv = (npm1300_driver_t*)context;
+
+ switch (drv->state) {
+ case NPM1300_STATE_ADC_WAIT:
+ // The ADC conversion is done, read the values
+ drv->adc_readout_requested = true;
+ drv->state = NPM1300_STATE_IDLE;
+ break;
+
+ default:
+ // we should never get here
+ drv->state = NPM1300_STATE_IDLE;
+ break;
+ }
+
+ npm1300_fsm_continue(drv);
+}
+
+// npm1300 driver I2C completion callback invoked when
+// `drv->pending_i2c_packet` is completed
+//
+// This function is called in the irq context.
+static void npm1300_i2c_callback(void* context, i2c_packet_t* packet) {
+ npm1300_driver_t* drv = (npm1300_driver_t*)context;
+
+ if (packet->status != I2C_STATUS_OK) {
+ drv->i2c_errors++;
+
+ if (drv->i2c_errors > NPM1300_I2C_ERROR_LIMIT) {
+ error_shutdown("npm1300 I2C error");
+ }
+
+ drv->state = NPM1300_STATE_IDLE;
+
+ // I2C operation will be retried until it succeeds or
+ // the error limit is reached
+ npm1300_fsm_continue(drv);
+ return;
+ }
+
+ // If the I2C operation was successful, reset the error counter
+ drv->i2c_errors = 0;
+
+ switch (drv->state) {
+ case NPM1300_STATE_CLEAR_EVENTS:
+ drv->clear_events_requested = false;
+ drv->state = NPM1300_STATE_IDLE;
+#ifdef USE_SUSPEND
+ wakeup_flags_set(WAKEUP_FLAG_POWER);
+#endif
+ break;
+
+ case NPM1300_STATE_CHARGING_ENABLE:
+ drv->charging = true;
+ drv->state = NPM1300_STATE_IDLE;
+ break;
+
+ case NPM1300_STATE_CHARGING_DISABLE:
+ drv->charging = false;
+ drv->state = NPM1300_STATE_IDLE;
+ break;
+
+ case NPM1300_STATE_CHARGING_LIMIT:
+ drv->i_charge = drv->i_charge_set;
+ drv->state = NPM1300_STATE_IDLE;
+ break;
+
+ case NPM1300_STATE_BUCK_MODE_SET:
+ drv->buck_mode = drv->buck_mode_set;
+ drv->state = NPM1300_STATE_IDLE;
+ break;
+
+ case NPM1300_STATE_ENTER_SHIPMODE:
+ drv->state = NPM1300_STATE_IDLE;
+ break;
+
+ case NPM1300_STATE_ADC_TRIGGER:
+ drv->adc_trigger_requested = false;
+ systimer_set(drv->timer, NPM1300_ADC_READOUT_DELAY);
+ drv->state = NPM1300_STATE_ADC_WAIT;
+ break;
+
+ case NPM1300_STATE_ADC_READOUT:
+ drv->adc_readout_requested = false;
+
+ pmic_report_t report;
+ npm1300_calculate_report(drv, &report);
+
+ // Invoke report callback
+ pmic_report_callback_t report_callback = drv->report_callback;
+ void* report_callback_context = drv->report_callback_context;
+
+ // Clear the report callback before invoking it
+ // to allow the new measurement to be scheduled in the callback
+ drv->report_callback = NULL;
+ drv->report_callback_context = NULL;
+
+ if (report_callback != NULL) {
+ report_callback(report_callback_context, &report);
+ }
+
+ drv->state = NPM1300_STATE_IDLE;
+ break;
+
+ default:
+ // we should never get here
+ drv->state = NPM1300_STATE_IDLE;
+ break;
+ }
+
+ npm1300_fsm_continue(drv);
+}
+
+void NPM1300_EXTI_INTERRUPT_HANDLER(void) {
+ IRQ_LOG_ENTER();
+ mpu_mode_t mpu_mode = mpu_reconfig(MPU_MODE_DEFAULT);
+ npm1300_driver_t* drv = &g_npm1300_driver;
+
+ // Clear the EXTI line pending bit
+ __HAL_GPIO_EXTI_CLEAR_FLAG(NPM1300_INT_PIN);
+
+ if (!drv->initialized) {
+ mpu_restore(mpu_mode);
+ IRQ_LOG_EXIT();
+ return;
+ }
+
+ drv->clear_events_requested = true;
+ npm1300_fsm_continue(drv);
+ mpu_restore(mpu_mode);
+ IRQ_LOG_EXIT();
+}
+
+// npm1300 driver FSM continuation function that decides what to do next
+//
+// This function is called in the irq context or when interrupts are disabled.
+static void npm1300_fsm_continue(npm1300_driver_t* drv) {
+ if (drv->state != NPM1300_STATE_IDLE || drv->suspended) {
+ return;
+ }
+
+ // The order of the following conditions defines the priority
+
+ if (drv->clear_events_requested) {
+ npm1300_i2c_submit(drv, npm1300_ops_clear_events);
+ drv->state = NPM1300_STATE_CLEAR_EVENTS;
+ } else if (drv->i_charge != drv->i_charge_requested) {
+ // Change charging limit
+ uint16_t bchg_iset = drv->i_charge / 2; // 2mA steps
+ drv->chlimit_regs.bchg_iset_msb = bchg_iset >> 1;
+ drv->chlimit_regs.bchg_iset_lsb = bchg_iset & 1;
+ drv->i_charge_set = drv->i_charge_requested;
+
+ if (drv->charging) {
+ // When charging is enabled, we need to disable it first
+ // and then re-enable it after changing the limit
+ npm1300_i2c_submit(drv, npm1300_ops_charging_limit_reenable);
+ } else {
+ npm1300_i2c_submit(drv, npm1300_ops_charging_limit);
+ }
+ drv->state = NPM1300_STATE_CHARGING_LIMIT;
+ } else if (drv->charging != drv->charging_requested) {
+ // Change charging state
+ if (drv->charging_requested) {
+ npm1300_i2c_submit(drv, npm1300_ops_charging_enable);
+ drv->state = NPM1300_STATE_CHARGING_ENABLE;
+ } else {
+ npm1300_i2c_submit(drv, npm1300_ops_charging_disable);
+ drv->state = NPM1300_STATE_CHARGING_DISABLE;
+ }
+ } else if (drv->buck_mode != drv->buck_mode_requested) {
+ drv->buck_mode_set = drv->buck_mode_requested;
+ if (drv->buck_mode_set == PMIC_BUCK_MODE_PWM) {
+ npm1300_i2c_submit(drv, npm1300_ops_buck_pwm);
+ } else if (drv->buck_mode_set == PMIC_BUCK_MODE_PFM) {
+ npm1300_i2c_submit(drv, npm1300_ops_buck_pfm);
+ } else {
+ npm1300_i2c_submit(drv, npm1300_ops_buck_auto);
+ }
+ drv->state = NPM1300_STATE_BUCK_MODE_SET;
+ } else if (drv->adc_readout_requested) {
+ // Read ADC values
+ npm1300_i2c_submit(drv, npm1300_ops_adc_readout);
+ drv->state = NPM1300_STATE_ADC_READOUT;
+ } else if (drv->adc_trigger_requested) {
+ // Trigger ADC conversion
+ npm1300_i2c_submit(drv, npm1300_ops_adc_trigger);
+ drv->state = NPM1300_STATE_ADC_TRIGGER;
+ } else if (drv->shipmode_requested) {
+ npm1300_i2c_submit(drv, npm1300_ops_enter_shipmode);
+ drv->shipmode_requested = false;
+ drv->state = NPM1300_STATE_ENTER_SHIPMODE;
+ }
+
+ // After processing all requests, check if we need to
+ // suspend the driver
+ if (drv->state == NPM1300_STATE_IDLE) {
+ // No more requests to process
+ if (drv->suspending) {
+ drv->suspending = false;
+ drv->suspended = true;
+ }
+ }
+}
+
+#endif // KERNEL_MODE
diff --git a/core/embed/io/power_manager/npm1300/npm1300_defs.h b/core/embed/io/power_manager/npm1300/npm1300_defs.h
new file mode 100644
index 00000000..b0a055fe
--- /dev/null
+++ b/core/embed/io/power_manager/npm1300/npm1300_defs.h
@@ -0,0 +1,320 @@
+/*
+ * This file is part of the Trezor project, https://trezor.io/
+ *
+ * Copyright (c) SatoshiLabs
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef TREZORHAL_NPM1300_DEFS_H
+#define TREZORHAL_NPM1300_DEFS_H
+
+// I2C address of the NPM1300 on the I2C bus.
+#define NPM1300_I2C_ADDRESS 0x6B
+
+// Event and interrupt registers
+
+#define NPM1300_TASKSWRESET 0x0001
+#define NPM1300_EVENTSADCSET 0x0002
+#define NPM1300_EVENTSADCCLR 0x0003
+#define NPM1300_INTENEVENTSADCSET 0x0004
+#define NPM1300_INTENEVENTSADCCLR 0x0005
+#define NPM1300_EVENTSBCHARGER0SET 0x0006
+#define NPM1300_EVENTSBCHARGER0CLR 0x0007
+#define NPM1300_INTENEVENTSBCHARGER0SET 0x0008
+#define NPM1300_INTENEVENTSBCHARGER0CLR 0x0009
+#define NPM1300_EVENTSBCHARGER1SET 0x000A
+#define NPM1300_EVENTSBCHARGER1CLR 0x000B
+#define NPM1300_INTENEVENTSBCHARGER1SET 0x000C
+#define NPM1300_INTENEVENTSBCHARGER1CLR 0x000D
+#define NPM1300_EVENTSBCHARGER2SET 0x000E
+#define NPM1300_EVENTSBCHARGER2CLR 0x000F
+#define NPM1300_INTENEVENTSBCHARGER2SET 0x0010
+#define NPM1300_INTENEVENTSBCHARGER2CLR 0x0011
+#define NPM1300_EVENTSSHPHLDSET 0x0012
+#define NPM1300_EVENTSSHPHLDCLR 0x0013
+#define NPM1300_INTENEVENTSSHPHLDSET 0x0014
+#define NPM1300_INTENEVENTSSHPHLDCLR 0x0015
+#define NPM1300_EVENTSVBUSIN0SET 0x0016
+#define NPM1300_EVENTSVBUSIN0CLR 0x0017
+#define NPM1300_INTENEVENTSVBUSIN0SET 0x0018
+#define NPM1300_INTENEVENTSVBUSIN0CLR 0x0019
+#define NPM1300_EVENTSVBUSIN1SET 0x001A
+#define NPM1300_EVENTSVBUSIN1CLR 0x001B
+#define NPM1300_INTENEVENTSVBUSIN1SET 0x001C
+#define NPM1300_INTENEVENTSVBUSIN1CLR 0x001D
+#define NPM1300_EVENTSGPIOSET 0x0022
+#define NPM1300_EVENTSGPIOCLR 0x0023
+#define NPM1300_INTENEVENTSGPIOSET 0x0024
+#define NPM1300_INTENEVENTSGPIOCLR 0x0025
+
+// SYSREG registers
+
+#define NPM1300_TASKUPDATEILIMSW 0x0200
+#define NPM1300_VBUSINILIM0 0x0201
+#define NPM1300_VBUSINILIMSTARTUP 0x0202
+#define NPM1300_VBUSSUSPEND 0x0203
+#define NPM1300_USBCDETECTSTATUS 0x0205
+#define NPM1300_VBUSINSTATUS 0x0207
+
+// Charger registers
+
+#define NPM1300_TASKRELEASEERR 0x0300
+#define NPM1300_TASKCLEARCHGERR 0x0301
+#define NPM1300_TASKCLEARSAFETYTIMER 0x0302
+#define NPM1300_BCHGENABLESET 0x0304
+#define NPM1300_BCHGENABLECLR 0x0305
+#define NPM1300_BCHGDISABLESET 0x0306
+#define NPM1300_BCHGDISABLECLR 0x0307
+#define NPM1300_BCHGISETMSB 0x0308
+#define NPM1300_BCHGISETLSB 0x0309
+#define NPM1300_BCHGISETDISCHARGEMSB 0x030A
+#define NPM1300_BCHGISETDISCHARGELSB 0x030B
+#define NPM1300_BCHGVTERM 0x030C
+#define NPM1300_BCHGVTERMR 0x030D
+#define NPM1300_BCHGVTRICKLESEL 0x030E
+#define NPM1300_BCHGITERMSEL 0x030F
+#define NPM1300_NTCCOLD 0x0310
+#define NPM1300_NTCCOLDLSB 0x0311
+#define NPM1300_NTCCOOL 0x0312
+#define NPM1300_NTCCOOLLSB 0x0313
+#define NPM1300_NTCWARM 0x0314
+#define NPM1300_NTCWARMLSB 0x0315
+#define NPM1300_NTCHOT 0x0316
+#define NPM1300_NTCHOTLSB 0x0317
+#define NPM1300_DIETEMPSTOP 0x0318
+#define NPM1300_DIETEMPSTOPLSB 0x0319
+#define NPM1300_DIETEMPRESUME 0x031A
+#define NPM1300_DIETEMPRESUMELSB 0x031B
+#define NPM1300_BCHGILIMSTATUS 0x032D
+#define NPM1300_NTCSTATUS 0x0332
+#define NPM1300_DIETEMPSTATUS 0x0333
+#define NPM1300_BCHGCHARGESTATUS 0x0334
+#define NPM1300_BCHGERRREASON 0x0336
+#define NPM1300_BCHGERRSENSOR 0x0337
+#define NPM1300_BCHGCONFIG 0x033C
+
+// Buck regulator registers
+
+#define NPM1300_BUCK1ENASET 0x0400
+#define NPM1300_BUCK1ENACLR 0x0401
+#define NPM1300_BUCK2ENASET 0x0402
+#define NPM1300_BUCK2ENACLR 0x0403
+#define NPM1300_BUCK1PWMSET 0x0404
+#define NPM1300_BUCK1PWMCLR 0x0405
+#define NPM1300_BUCK2PWMSET 0x0406
+#define NPM1300_BUCK2PWMCLR 0x0407
+#define NPM1300_BUCK1NORMVOUT 0x0408
+#define NPM1300_BUCK1RETVOUT 0x0409
+#define NPM1300_BUCK2NORMVOUT 0x040A
+#define NPM1300_BUCK2RETVOUT 0x040B
+#define NPM1300_BUCKENCTRL 0x040C
+#define NPM1300_BUCKVRETCTRL 0x040D
+#define NPM1300_BUCKPWMCTRL 0x040E
+#define NPM1300_BUCKSWCTRLSEL 0x040F
+#define NPM1300_BUCK1VOUTSTATUS 0x0410
+#define NPM1300_BUCK2VOUTSTATUS 0x0411
+#define NPM1300_BUCKCTRL0 0x0415
+#define NPM1300_BUCKSTATUS 0x0434
+
+// System monitor registers
+
+#define NPM1300_TASKVBATMEASURE 0x0500
+#define NPM1300_TASKNTCMEASURE 0x0501
+#define NPM1300_TASKTEMPMEASURE 0x0502
+#define NPM1300_TASKVSYSMEASURE 0x0503
+#define NPM1300_TASKIBATMEASURE 0x0506
+#define NPM1300_TASKVBUS7MEASURE 0x0507
+#define NPM1300_TASKDELAYEDVBATMEASURE 0x0508
+#define NPM1300_ADCCONFIG 0x0509
+#define NPM1300_ADCNTCRSEL 0x050A
+#define NPM1300_ADCAUTOTIMCONF 0x050B
+#define NPM1300_TASKAUTOTIMUPDATE 0x050C
+#define NPM1300_ADCDELTIMCONF 0x050D
+#define NPM1300_ADCIBATMEASSTATUS 0x0510
+#define NPM1300_ADCVBATRESULTMSB 0x0511
+#define NPM1300_ADCNTCRESULTMSB 0x0512
+#define NPM1300_ADCTEMPRESULTMSB 0x0513
+#define NPM1300_ADCVSYSRESULTMSB 0x0514
+#define NPM1300_ADCGP0RESULTLSBS 0x0515
+#define NPM1300_ADCVBAT0RESULTMSB 0x0516
+#define NPM1300_ADCVBAT1RESULTMSB 0x0517
+#define NPM1300_ADCVBAT2RESULTMSB 0x0518
+#define NPM1300_ADCVBAT3RESULTMSB 0x0519
+#define NPM1300_ADCGP1RESULTLSBS 0x051A
+#define NPM1300_ADCIBATMEASEN 0x0524
+
+// GPIO registers
+
+#define NPM1300_GPIOMODE0 0x0600
+#define NPM1300_GPIOMODE1 0x0601
+#define NPM1300_GPIOMODE2 0x0602
+#define NPM1300_GPIOMODE3 0x0603
+#define NPM1300_GPIOMODE4 0x0604
+#define NPM1300_GPIODRIVE0 0x0605
+#define NPM1300_GPIODRIVE1 0x0606
+#define NPM1300_GPIODRIVE2 0x0607
+#define NPM1300_GPIODRIVE3 0x0608
+#define NPM1300_GPIODRIVE4 0x0609
+#define NPM1300_GPIOPUEN0 0x060A
+#define NPM1300_GPIOPUEN1 0x060B
+#define NPM1300_GPIOPUEN2 0x060C
+#define NPM1300_GPIOPUEN3 0x060D
+#define NPM1300_GPIOPUEN4 0x060E
+#define NPM1300_GPIOPDEN0 0x060F
+#define NPM1300_GPIOPDEN1 0x0610
+#define NPM1300_GPIOPDEN2 0x0611
+#define NPM1300_GPIOPDEN3 0x0612
+#define NPM1300_GPIOPDEN4 0x0613
+#define NPM1300_GPIOOPENDRAIN0 0x0614
+#define NPM1300_GPIOOPENDRAIN1 0x0615
+#define NPM1300_GPIOOPENDRAIN2 0x0616
+#define NPM1300_GPIOOPENDRAIN3 0x0617
+#define NPM1300_GPIOOPENDRAIN4 0x0618
+#define NPM1300_GPIODEBOUNCE0 0x0619
+#define NPM1300_GPIODEBOUNCE1 0x061A
+#define NPM1300_GPIODEBOUNCE2 0x061B
+#define NPM1300_GPIODEBOUNCE3 0x061C
+#define NPM1300_GPIODEBOUNCE4 0x061D
+#define NPM1300_GPIOSTATUS 0x061E
+
+// Timer registers
+
+#define NPM1300_TIMERSET 0x0700
+#define NPM1300_TIMERCLR 0x0701
+#define NPM1300_TIMERTARGETSTROBE 0x0703
+#define NPM1300_WATCHDOGKICK 0x0704
+#define NPM1300_TIMERCONFIG 0x0705
+#define NPM1300_TIMERSTATUS 0x0706
+#define NPM1300_TIMERHIBYTE 0x0708
+#define NPM1300_TIMERMIDBYTE 0x0709
+#define NPM1300_TIMERLOBYTE 0x070A
+
+// LOADSW/LDO registers
+
+#define NPM1300_TASKLDSW1SET 0x0800
+#define NPM1300_TASKLDSW1CLR 0x0801
+#define NPM1300_TASKLDSW2SET 0x0802
+#define NPM1300_TASKLDSW2CLR 0x0803
+#define NPM1300_LDSWSTATUS 0x0804
+#define NPM1300_LDSW1GPISEL 0x0805
+#define NPM1300_LDSW2GPISEL 0x0806
+#define NPM1300_LDSWCONFIG 0x0807
+#define NPM1300_LDSW1LDOSEL 0x0808
+#define NPM1300_LDSW2LDOSEL 0x0809
+#define NPM1300_LDSW1VOUTSEL 0x080C
+#define NPM1300_LDSW2VOUTSEL 0x080D
+
+// POF registers
+
+#define NPM1300_POFCONFIG 0x0900
+
+// LED registers
+
+#define NPM1300_LEDDRV0MODESEL 0x0A00
+#define NPM1300_LEDDRV1MODESEL 0x0A01
+#define NPM1300_LEDDRV2MODESEL 0x0A02
+#define NPM1300_LEDDRV0SET 0x0A03
+#define NPM1300_LEDDRV0CLR 0x0A04
+#define NPM1300_LEDDRV1SET 0x0A05
+#define NPM1300_LEDDRV1CLR 0x0A06
+#define NPM1300_LEDDRV2SET 0x0A07
+#define NPM1300_LEDDRV2CLR 0x0A08
+
+// Reset and hibernation mode registers
+
+#define NPM1300_TASKENTERHIBERNATE 0x0B00
+#define NPM1300_TASKSHPHLDCFGSTROBE 0x0B01
+#define NPM1300_TASKENTERSHIPMODE 0x0B02
+#define NPM1300_TASKRESETCFG 0x0B03
+#define NPM1300_SHPHLDCONFIG 0x0B04
+#define NPM1300_SHPHLDSTATUS 0x0B05
+#define NPM1300_LPRESETCONFIG 0x0B06
+
+// Reset and error registers
+
+#define NPM1300_TASKCLRERRLOG 0x0E00
+#define NPM1300_SCRATCH0 0x0E01
+#define NPM1300_SCRATCH1 0x0E02
+#define NPM1300_RSTCAUSE 0x0E03
+#define NPM1300_CHARGERERRREASON 0x0E04
+#define NPM1300_CHARGERERRSENSOR 0x0E05
+
+// ------------------------------------------------------
+
+// TASKSWRESET
+#define NPM1300_TASKSWRESET_TRIGGER 0x01
+
+// TASKUPDATEILIM
+#define NPM1300_TASKUPDATEILIM_SELVBUSILIM0 1
+
+// VBUSINILIM0
+#define NPM1300_VBUSINILIM0_500MA 5 // Default
+
+// BCHGENABLESET
+#define NPM1300_BCHGENABLESET_ENABLECHG 0x01
+
+// BCHGENABLECLR
+#define NPM1300_BCHGENABLECLR_DISABLECHG 0x01
+
+// BGCDISABLECLR
+#define NPM1300_BCHGDISABLECLR_ENABLERCHRG 0x01
+#define NPM1300_BCHGDISABLECLR_USENTC 0x02
+
+// BCHGVTERM, BCHGVTERMR
+#define NPM1300_BCHGVTERM_3V60 2 // Default
+#define NPM1300_BCHGVTERM_3V65 3
+
+// ADCCONFIG
+#define NPM1300_ADCCONFIG_AUTOENABLE 0x01
+#define NPM1300_ADCCONFIG_BURSTMODE 0x02
+
+// ADCNTCRSEL
+#define NPM1300_ADCNTCRSEL_HI_Z 0
+#define NPM1300_ADCNTCRSEL_10K 1 // Default
+#define NPM1300_ADCNTCRSEL_47K 2
+#define NPM1300_ADCNTCRSEL_100K 3
+
+// ADCIBATMEASEN
+#define NPM1300_ADCIBATMEASEN_IBATMEASENABLE 0x01
+
+// BCHGVTRICKLESEL
+#define NPM1300_BCHGVTRICKLESEL_2V9 0 // Default
+#define NPM1300_BCHGVTRICKLESEL_2V5 1
+
+// BCHGITERMSEL
+#define NPM1300_BCHGITERMSEL_SEL10 0 // Default
+#define NPM1300_BCHGITERMSEL_SEL20 1
+
+// LEDDRVxMODESEL
+
+#define NPM1300_LEDDRVMODESEL_ERROR 0x00
+#define NPM1300_LEDDRVMODESEL_CHARGING 0x01
+#define NPM1300_LEDDRVMODESEL_HOST 0x02
+#define NPM1300_LEDDRVMODESEL_NOTUSED 0x03
+
+// GPIOMODEx
+
+#define NPM1300_GPIOMODE_GPIINPUT 0x00
+#define NPM1300_GPIOMODE_GPILOGIC1 0x01
+#define NPM1300_GPIOMODE_GPILOGIC0 0x02
+#define NPM1300_GPIOMODE_GPIEVENTRISE 0x03
+#define NPM1300_GPIOMODE_GPIEVENTFALL 0x04
+#define NPM1300_GPIOMODE_GPOIRQ 0x05
+#define NPM1300_GPIOMODE_GPORESET 0x06
+#define NPM1300_GPIOMODE_GPOPLW 0x07
+#define NPM1300_GPIOMODE_GPOLOGIC1 0x08
+#define NPM1300_GPIOMODE_GPOLOGIC0 0x09
+
+#endif // TREZORHAL_NPM1300_DEFS_H
diff --git a/core/embed/io/power_manager/power_manager_poll.c b/core/embed/io/power_manager/power_manager_poll.c
new file mode 100644
index 00000000..0d411f0b
--- /dev/null
+++ b/core/embed/io/power_manager/power_manager_poll.c
@@ -0,0 +1,196 @@
+/*
+ * This file is part of the Trezor project, https://trezor.io/
+ *
+ * Copyright (c) SatoshiLabs
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef KERNEL_MODE
+
+#include <trezor_rtl.h>
+
+#include <io/power_manager.h>
+#include <sys/sysevent_source.h>
+#include <sys/systick.h>
+
+#include "power_manager_poll.h"
+
+typedef struct {
+ float filtered;
+ uint32_t t_last_ms;
+} pm_jump_detector_t;
+
+typedef struct {
+ // Last state
+ pm_state_t last_state;
+ // Pending events
+ pm_event_t events;
+
+ // Jump detection state for battery temperature
+ pm_jump_detector_t temp_detector;
+
+ // Jump detection state for battery open-circuit voltage (OCV)
+ pm_jump_detector_t ocv_detector;
+
+} pm_fsm_t;
+
+// State machine for each task
+static pm_fsm_t g_pm_tls[SYSTASK_MAX_TASKS] = {0};
+
+// Forward declarations
+static const syshandle_vmt_t g_pm_handle_vmt;
+
+bool pm_poll_init(void) {
+ return syshandle_register(SYSHANDLE_POWER_MANAGER, &g_pm_handle_vmt, NULL);
+}
+
+void pm_poll_deinit(void) { syshandle_unregister(SYSHANDLE_POWER_MANAGER); }
+
+bool pm_get_events(pm_event_t* events) {
+ pm_fsm_t* fsm = &g_pm_tls[systask_id(systask_active())];
+
+ if (fsm->events.all != 0) {
+ *events = fsm->events;
+ memset(&fsm->events, 0, sizeof(pm_event_t));
+ return true;
+ }
+
+ return false;
+}
+
+static bool pm_detect_jump(pm_jump_detector_t* detector, float value,
+ float threshold, uint32_t tau_ms) {
+ const uint32_t now = systick_ms();
+
+ if (detector->t_last_ms == 0U) {
+ detector->filtered = value;
+ detector->t_last_ms = now;
+ return false;
+ }
+
+ const uint32_t dt_ms = now - detector->t_last_ms;
+ detector->t_last_ms = now;
+
+ if (dt_ms == 0U) {
+ return false;
+ }
+
+ // Use Exponential Moving Average (EMA) to detect jumps.
+ // The EMA provides a smooth baseline that follows the signal with a lag.
+ // If the difference between the current value and the baseline exceeds
+ // the threshold, we consider it a jump.
+ // alpha = dt / (tau + dt)
+ const float alpha = (float)dt_ms / (float)(tau_ms + dt_ms);
+
+ const float diff = value - detector->filtered;
+ float abs_diff = (diff < 0.0f) ? -diff : diff;
+
+ if (abs_diff >= threshold) {
+ // Jump detected! Reset filter to current value to avoid multiple triggers
+ detector->filtered = value;
+ return true;
+ }
+
+ // Update filtered value (low-pass filter)
+ detector->filtered += alpha * diff;
+
+ return false;
+}
+
+static bool pm_fsm_update(pm_fsm_t* fsm, pm_state_t* new_state) {
+ // Return true if there are any state changes
+ if (new_state->soc != fsm->last_state.soc) {
+ fsm->events.flags.soc_updated = true;
+ }
+
+ // Detect battery temperature jump
+ const float TEMP_JUMP_THRESHOLD_C = 5.0f;
+ const uint32_t TEMP_JUMP_WINDOW_MS = 5000; // 5 seconds
+ if (pm_detect_jump(&fsm->temp_detector, new_state->battery_temp,
+ TEMP_JUMP_THRESHOLD_C, TEMP_JUMP_WINDOW_MS)) {
+ fsm->events.flags.battery_temp_jump_detected = true;
+ }
+
+ // Detect battery OCV jump
+ const float OCV_JUMP_THRESHOLD_V = 0.50f; // 500 mV
+ const uint32_t OCV_JUMP_WINDOW_MS = 5000; // 5 seconds
+ if (pm_detect_jump(&fsm->ocv_detector, new_state->battery_ocv,
+ OCV_JUMP_THRESHOLD_V, OCV_JUMP_WINDOW_MS)) {
+ fsm->events.flags.battery_ocv_jump_detected = true;
+ }
+
+ if (new_state->usb_connected != fsm->last_state.usb_connected) {
+ fsm->events.flags.usb_connected_changed = true;
+ }
+
+ if (new_state->wireless_connected != fsm->last_state.wireless_connected) {
+ fsm->events.flags.wireless_connected_changed = true;
+ }
+
+ if (new_state->power_status != fsm->last_state.power_status) {
+ fsm->events.flags.power_status_changed = true;
+ }
+
+ if (new_state->charging_status != fsm->last_state.charging_status) {
+ fsm->events.flags.charging_status_changed = true;
+ }
+
+ if (new_state->ntc_connected != fsm->last_state.ntc_connected) {
+ fsm->events.flags.ntc_connected_changed = true;
+ }
+
+ if (new_state->charging_limited != fsm->last_state.charging_limited) {
+ fsm->events.flags.charging_limited_changed = true;
+ }
+
+ fsm->last_state = *new_state;
+
+ return fsm->events.all != 0;
+}
+
+static void on_task_created(void* context, systask_id_t task_id) {
+ pm_fsm_t* fsm = &g_pm_tls[task_id];
+ memset(fsm, 0, sizeof(pm_fsm_t));
+}
+
+static void on_event_poll(void* context, bool read_awaited,
+ bool write_awaited) {
+ UNUSED(write_awaited);
+
+ if (read_awaited) {
+ pm_state_t state = {0};
+ pm_get_state(&state);
+ syshandle_signal_read_ready(SYSHANDLE_POWER_MANAGER, &state);
+ }
+}
+
+static bool on_check_read_ready(void* context, systask_id_t task_id,
+ void* param) {
+ pm_fsm_t* fsm = &g_pm_tls[task_id];
+
+ pm_state_t* new_state = (pm_state_t*)param;
+
+ return pm_fsm_update(fsm, new_state);
+}
+
+static const syshandle_vmt_t g_pm_handle_vmt = {
+ .task_created = on_task_created,
+ .task_killed = NULL,
+ .check_read_ready = on_check_read_ready,
+ .check_write_ready = NULL,
+ .poll = on_event_poll,
+};
+
+#endif // KERNEL_MODE
diff --git a/core/embed/io/power_manager/power_manager_poll.h b/core/embed/io/power_manager/power_manager_poll.h
new file mode 100644
index 00000000..29492701
--- /dev/null
+++ b/core/embed/io/power_manager/power_manager_poll.h
@@ -0,0 +1,27 @@
+/*
+ * This file is part of the Trezor project, https://trezor.io/
+ *
+ * Copyright (c) SatoshiLabs
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+// This module implements system event evaluation & polling mechanism for power
+// manager.
+
+bool pm_poll_init(void);
+
+void pm_poll_deinit(void);
diff --git a/core/embed/io/power_manager/stm32u5/power_manager.c b/core/embed/io/power_manager/stm32u5/power_manager.c
new file mode 100644
index 00000000..2fb01937
--- /dev/null
+++ b/core/embed/io/power_manager/stm32u5/power_manager.c
@@ -0,0 +1,776 @@
+/*
+ * This file is part of the Trezor project, https://trezor.io/
+ *
+ * Copyright (c) SatoshiLabs
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+#ifdef KERNEL_MODE
+
+#include <trezor_rtl.h>
+
+#include <io/pmic.h>
+#include <sec/backup_ram.h>
+#include <sys/irq.h>
+#include <sys/suspend.h>
+#include <sys/systick.h>
+#include <sys/systimer.h>
+
+#ifdef USE_RTC
+#include <sys/rtc.h>
+#include <sys/rtc_scheduler.h>
+#endif
+
+#include "../fuel_gauge/battery_model.h"
+#include "../power_manager_poll.h"
+#include "../stwlc38/stwlc38.h"
+#include "power_manager_internal.h"
+
+// Global driver instance
+pm_driver_t g_pm = {
+ .initialized = false,
+};
+
+// Forward declarations of static functions
+static void pm_monitoring_timer_handler(void* context);
+static void pm_shutdown_timer_handler(void* context);
+static bool pm_load_recovery_data(pm_recovery_data_t* recovery);
+static pm_status_t pm_wait_to_stabilize(pm_driver_t* drv, uint32_t timeout_ms);
+
+pm_status_t pm_init(bool inherit_state) {
+ pm_driver_t* drv = &g_pm;
+
+ if (drv->initialized) {
+ return PM_OK;
+ }
+
+ // Clear driver instance
+ memset(drv, 0, sizeof(pm_driver_t));
+
+ // Initialize hardware subsystems
+ if (!pmic_init() || !stwlc38_init()) {
+ pm_deinit();
+ return PM_ERROR;
+ }
+
+ if (!pm_poll_init()) {
+ pm_deinit();
+ return PM_ERROR;
+ }
+
+ // Initialize fuel gauge
+ fuel_gauge_init(&drv->fuel_gauge, PM_FUEL_GAUGE_R, PM_FUEL_GAUGE_Q,
+ PM_FUEL_GAUGE_R_AGGRESSIVE, PM_FUEL_GAUGE_Q_AGGRESSIVE,
+ PM_FUEL_GAUGE_P_INIT);
+
+ // Create monitoring timer
+ drv->monitoring_timer = systimer_create(pm_monitoring_timer_handler, NULL);
+ if (drv->monitoring_timer == NULL) {
+ pm_deinit();
+ return PM_ERROR;
+ }
+
+ // Create shutdown timer
+ drv->shutdown_timer = systimer_create(pm_shutdown_timer_handler, NULL);
+ if (drv->shutdown_timer == NULL) {
+ pm_deinit();
+ return PM_ERROR;
+ }
+
+ systimer_set_periodic(drv->monitoring_timer, PM_TIMER_PERIOD_MS);
+
+ // Initial power source measurement
+ pmic_measure(pm_pmic_data_ready, NULL);
+
+ // Try to recover SoC from the backup RAM
+ pm_recovery_data_t recovery;
+ bool recovery_ok = pm_load_recovery_data(&recovery);
+
+ if (!recovery_ok) {
+ // Wait for 1s to sample battery data
+ systick_delay_ms(1000);
+ }
+
+ // In this part of the code, power monitoring timer is already running, so
+ // we have to prevent simultaneous access to the driver instance by locking
+ // the IRQs.
+ irq_key_t irq_key = irq_lock();
+
+ if (recovery_ok) {
+#ifdef USE_RTC
+
+ // RTC compensation should happen only during initialization in bootloader
+ if (!inherit_state) {
+ // Get RTC timestamp and compare it with the timestamp from recovery data
+ // to estimate time off and compensate self-discharge of the battery.
+ uint32_t rtc_timestamp;
+ if (recovery.last_capture_timestamp != 0 &&
+ rtc_get_timestamp(&rtc_timestamp)) {
+ // If the RTC timestamp is older than the last captured timestamp,
+ // we will not use it.
+ if (rtc_timestamp >= recovery.last_capture_timestamp) {
+ pm_compensate_fuel_gauge(
+ &recovery.soc, rtc_timestamp - recovery.last_capture_timestamp,
+ PM_SELF_DISG_RATE_HIBERNATION_MA, 25.0f);
+ }
+ }
+ }
+
+#endif
+
+ drv->battery_critical = recovery.bat_critical;
+ fuel_gauge_set_soc(&drv->fuel_gauge, recovery.soc, recovery.P);
+ } else {
+ pm_battery_initial_soc_guess();
+ }
+
+ if (inherit_state) {
+ // Inherit power manager state left in backup RAM from bootloader.
+ // in case of error, start with PM_STATE_POWER_SAVE as a lowest state in
+ // active mode.
+ if (!recovery_ok &&
+ (recovery.bootloader_exit_state != PM_STATE_POWER_SAVE &&
+ recovery.bootloader_exit_state != PM_STATE_ACTIVE)) {
+ drv->state = PM_STATE_POWER_SAVE;
+
+ } else {
+ // Backup RAM contain valid data
+ drv->state = recovery.bootloader_exit_state;
+ }
+
+ } else {
+ // Start in lowest state and wait for the bootup sequence to
+ // finish (call of pm_turn_on())
+ drv->state = PM_STATE_HIBERNATE;
+ }
+
+ // Enable charging by default to max current
+ drv->charging_enabled = true;
+
+ // Set default SOC target and max charging current limit
+ drv->soc_target = 100;
+ drv->i_chg_max_limit_ma = PM_BATTERY_CHARGING_CURRENT_MAX;
+
+#ifdef PM_ENABLE_TEMP_CONTROL
+ drv->i_chg_temp_limit_ma = PM_BATTERY_CHARGING_CURRENT_MAX;
+#endif
+
+ // Fuel gauge SoC available, set fuel_gauge initialized.
+ drv->fuel_gauge_initialized = true;
+
+ irq_unlock(irq_key);
+
+ // Wait to stabilize the state machine
+ pm_status_t status = pm_wait_to_stabilize(drv, PM_STABILIZATION_TIMEOUT_MS);
+ if (status != PM_OK) {
+ pm_deinit();
+ return status;
+ }
+
+ drv->initialized = true;
+
+ return PM_OK;
+}
+
+void pm_deinit(void) {
+ pm_driver_t* drv = &g_pm;
+
+ pm_poll_deinit();
+
+ if (drv->monitoring_timer) {
+ systimer_delete(drv->monitoring_timer);
+ drv->monitoring_timer = NULL;
+ }
+
+ if (drv->shutdown_timer) {
+ systimer_delete(drv->shutdown_timer);
+ drv->shutdown_timer = NULL;
+ }
+
+ if (drv->fuel_gauge_initialized) {
+ pm_store_data_to_backup_ram();
+ }
+
+ pmic_deinit();
+ stwlc38_deinit();
+
+ drv->initialized = false;
+}
+
+pm_status_t pm_get_state(pm_state_t* state) {
+ pm_driver_t* drv = &g_pm;
+
+ if (!drv->initialized) {
+ return PM_NOT_INITIALIZED;
+ }
+
+ irq_key_t irq_key = irq_lock();
+
+ state->usb_connected = drv->usb_connected;
+ state->wireless_connected = drv->wireless_connected;
+ state->ntc_connected = !drv->pmic_data.ntc_disconnected;
+
+ if (pm_is_charging()) {
+ state->charging_status = PM_BATTERY_CHARGING;
+ } else if (drv->pmic_data.ibat > 0.0f) {
+ state->charging_status = PM_BATTERY_DISCHARGING;
+ } else {
+ state->charging_status = PM_BATTERY_IDLE;
+ }
+
+ // Charging-limited detection with 5s filter
+ // Conditions to consider:
+ // - Only when charging
+ // - Only when PMIC reports constant-current phase (decoded flag)
+ // - Consider measured current vs target with a small margin
+ // - Assert after predicate holds continuously for >= 5000 ms
+ // - Clear immediately when predicate breaks or not charging
+ const bool is_charging = (state->charging_status == PM_BATTERY_CHARGING);
+ const float MAX_DIFF_MA = 15; // tolerance below target current
+ const uint32_t FILTER_ASSERT_MS = 5000;
+
+ bool predicate = false;
+ if (is_charging) {
+ const bool cc_phase = drv->pmic_data.cc_phase;
+ float iabs_ma = drv->pmic_data.ibat;
+ if (iabs_ma < 0.0f) {
+ iabs_ma = -iabs_ma; // ibat < 0 => charging
+ }
+ predicate = cc_phase && (iabs_ma < (drv->i_chg_target_ma - MAX_DIFF_MA));
+ }
+
+ if (predicate) {
+ uint32_t now = systick_ms();
+ if (drv->charging_limited_start_ms == 0U) {
+ drv->charging_limited_start_ms = now;
+ } else if (!drv->charging_limited_latched &&
+ (now - drv->charging_limited_start_ms) >= FILTER_ASSERT_MS) {
+ drv->charging_limited_latched = true;
+ }
+ } else {
+ drv->charging_limited_start_ms = 0U;
+ drv->charging_limited_latched = false;
+ }
+
+ state->charging_limited = drv->charging_limited_latched;
+
+ state->power_status = drv->state;
+ state->soc = drv->soc_ceiled;
+ state->battery_temp = drv->pmic_data.ntc_temp;
+ state->battery_ocv = drv->battery_ocv;
+
+ irq_unlock(irq_key);
+
+ return PM_OK;
+}
+
+// This callback is called from inside the system_suspend() function
+// when the rtc wake-up timer expires.
+#ifdef USE_RTC
+void pm_rtc_wakeup_callback(void* context) {
+ pm_driver_t* drv = &g_pm;
+
+ // Clear autohibernate event reference
+ drv->autohibernate_event_id = 0;
+}
+#endif
+
+pm_status_t pm_suspend(wakeup_flags_t* wakeup_reason) {
+ pm_driver_t* drv = &g_pm;
+
+ if (wakeup_reason != NULL) {
+ *wakeup_reason = 0;
+ }
+
+ if (!drv->initialized) {
+ return PM_NOT_INITIALIZED;
+ }
+
+ irq_key_t irq_key = irq_lock();
+ drv->request_suspend = true;
+
+ pm_process_state_machine();
+
+ // Something went wrong, suspend request was not accepted
+ if (drv->request_suspend == true || drv->state != PM_STATE_SUSPEND) {
+ drv->request_suspend = false;
+ irq_unlock(irq_key);
+ return PM_REQUEST_REJECTED;
+ }
+
+ irq_unlock(irq_key);
+
+#ifdef USE_RTC
+ // Read the current timestamp before entering suspend mode
+ if (!rtc_get_timestamp(&drv->suspend_timestamp)) {
+ return PM_ERROR;
+ }
+#endif
+
+ wakeup_flags_t wakeup_flags = system_suspend();
+
+#ifdef USE_RTC
+ // Cancel autohibernate event if scheduled
+ if (drv->autohibernate_event_id != 0) {
+ rtc_cancel_wakeup_event(drv->autohibernate_event_id);
+ drv->autohibernate_event_id = 0;
+ }
+#endif
+
+ // Wait for pmic measurements to stabilize the fuel gauge estimation.
+ pm_status_t status = pm_wait_to_stabilize(drv, PM_STABILIZATION_TIMEOUT_MS);
+ if (status != PM_OK) {
+ // timeout during state machine stabilization
+ return PM_TIMEOUT;
+ }
+
+ // TODO: Handle wake-up flags
+ // UNUSED(wakeup_flags);
+
+ // Exit hibernation state if it was requested
+ irq_key = irq_lock();
+ drv->request_exit_suspend = true;
+ pm_process_state_machine();
+ irq_unlock(irq_key);
+
+ if (wakeup_reason != NULL) {
+ *wakeup_reason = wakeup_flags;
+ }
+
+ return PM_OK;
+}
+
+pm_status_t pm_hibernate(void) {
+ pm_driver_t* drv = &g_pm;
+
+ if (!drv->initialized) {
+ return PM_NOT_INITIALIZED;
+ }
+
+ irq_key_t irq_key = irq_lock();
+ drv->request_hibernate = true;
+ pm_process_state_machine();
+ irq_unlock(irq_key);
+
+ systick_delay_ms(50);
+
+ // Whenever hibernation request fall through, request was rejected
+ return PM_REQUEST_REJECTED;
+}
+
+pm_status_t pm_turn_on(void) {
+ pm_driver_t* drv = &g_pm;
+
+ if (!drv->initialized) {
+ return PM_NOT_INITIALIZED;
+ }
+
+ // Poll until at least single PMIC measurement is done
+ uint32_t pmic_last_update_us;
+ do {
+ irq_key_t irq_key = irq_lock();
+ pmic_last_update_us = drv->pmic_last_update_us;
+ irq_unlock(irq_key);
+ } while (pmic_last_update_us == 0);
+
+ // Check if device has enough power to startup
+ if (drv->battery_critical) {
+ irq_key_t irq_key = irq_lock();
+ drv->battery_critical = true;
+ pm_store_data_to_backup_ram();
+ irq_unlock(irq_key);
+
+ return PM_REQUEST_REJECTED;
+ }
+
+ irq_key_t irq_key = irq_lock();
+ drv->request_turn_on = true;
+ pm_process_state_machine();
+ irq_unlock(irq_key);
+
+ if (drv->state == PM_STATE_HIBERNATE || drv->state == PM_STATE_CHARGING) {
+ return PM_REQUEST_REJECTED;
+ }
+
+ return PM_OK;
+}
+
+pm_status_t pm_get_report(pm_report_t* report) {
+ pm_driver_t* drv = &g_pm;
+
+ if (!drv->initialized) {
+ return PM_NOT_INITIALIZED;
+ }
+
+ irq_key_t irq_key = irq_lock();
+
+ // Copy current data into report
+ report->power_state = drv->state;
+ report->usb_connected = drv->usb_connected;
+ report->wireless_charger_connected = drv->wireless_connected;
+ report->system_voltage_v = drv->pmic_data.vsys;
+ report->battery_voltage_v = drv->pmic_data.vbat;
+ report->battery_current_ma = drv->pmic_data.ibat;
+ report->battery_temp_c = drv->pmic_data.ntc_temp;
+ report->battery_soc = drv->fuel_gauge.soc;
+ report->battery_soc_latched = drv->fuel_gauge.soc_latched;
+ report->pmic_temp_c = drv->pmic_data.die_temp;
+ report->wireless_rectifier_voltage_v = drv->wireless_data.vrect;
+ report->wireless_output_voltage_v = drv->wireless_data.vout;
+ report->wireless_current_ma = drv->wireless_data.icur;
+ report->wireless_temp_c = drv->wireless_data.tmeas;
+
+ irq_unlock(irq_key);
+
+ return PM_OK;
+}
+
+pm_status_t pm_charging_enable(void) {
+ pm_driver_t* drv = &g_pm;
+
+ if (!drv->initialized) {
+ return PM_NOT_INITIALIZED;
+ }
+
+ irq_key_t irq_key = irq_lock();
+ drv->charging_enabled = true;
+ pm_charging_controller(drv);
+ irq_unlock(irq_key);
+
+ return PM_OK;
+}
+
+pm_status_t pm_charging_disable(void) {
+ pm_driver_t* drv = &g_pm;
+
+ if (!drv->initialized) {
+ return PM_NOT_INITIALIZED;
+ }
+
+ irq_key_t irq_key = irq_lock();
+ drv->charging_enabled = false;
+ pm_charging_controller(drv);
+ irq_unlock(irq_key);
+
+ return PM_OK;
+}
+
+pm_status_t pm_charging_set_max_current(uint16_t current_ma) {
+ pm_driver_t* drv = &g_pm;
+
+ if (!drv->initialized) {
+ return PM_NOT_INITIALIZED;
+ }
+
+ if (current_ma > PM_BATTERY_CHARGING_CURRENT_MAX) {
+ return PM_REQUEST_REJECTED;
+ }
+
+ if (current_ma < PM_BATTERY_CHARGING_CURRENT_MIN) {
+ return PM_REQUEST_REJECTED;
+ }
+
+ irq_key_t irq_key = irq_lock();
+ drv->i_chg_max_limit_ma = current_ma;
+ pm_charging_controller(drv);
+ irq_unlock(irq_key);
+
+ return PM_OK;
+}
+
+pm_status_t pm_store_data_to_backup_ram() {
+ pm_driver_t* drv = &g_pm;
+
+ if (!drv->initialized) {
+ return PM_NOT_INITIALIZED;
+ }
+
+ irq_key_t irq_key = irq_lock();
+
+ pm_recovery_data_t recovery = {.version = PM_RECOVERY_DATA_VERSION};
+
+ recovery.soc = drv->fuel_gauge.soc;
+ recovery.P = drv->fuel_gauge.P;
+
+ // Power manager state
+ recovery.bat_critical = drv->battery_critical;
+ recovery.bootloader_exit_state = drv->state;
+
+#ifdef USE_RTC
+ if (!rtc_get_timestamp(&recovery.last_capture_timestamp)) {
+ // If RTC timestamp cannot be obtained, set it to 0
+ recovery.last_capture_timestamp = 0;
+ }
+#endif
+
+ irq_unlock(irq_key);
+
+ bool write_ok =
+ backup_ram_write(BACKUP_RAM_KEY_PM_RECOVERY, BACKUP_RAM_ITEM_PUBLIC,
+ &recovery, sizeof(recovery));
+
+ if (!write_ok) {
+ return PM_ERROR;
+ }
+
+ return PM_OK;
+}
+
+static bool pm_load_recovery_data(pm_recovery_data_t* recovery) {
+ union {
+ uint16_t version;
+ pm_recovery_data_t v1; // v1 is the only version currently supported
+ // pm_recovery_data_t v2;
+ } data;
+
+ size_t data_size = 0;
+
+ memset(recovery, 0, sizeof(*recovery));
+
+ bool read_ok = backup_ram_read(BACKUP_RAM_KEY_PM_RECOVERY, &data,
+ sizeof(data), &data_size);
+
+ if (!read_ok) {
+ return false;
+ }
+
+ // Incremental migration logic can be added here if needed
+ // if (data.version == PM_RECOVERY_DATA_VERSION_V1) {
+ // migrate_pm_recovery_data_v1_to_v2(&data.v1, &date_v2);
+ // }
+
+ if (data.version != PM_RECOVERY_DATA_VERSION) {
+ return false;
+ }
+
+ *recovery = data.v1;
+
+ if (recovery->soc < 0.0f || recovery->soc > 1.0f) {
+ return false;
+ }
+
+ return true;
+}
+
+pm_status_t pm_set_soc_target(uint8_t target) {
+ pm_driver_t* drv = &g_pm;
+
+ if (!drv->initialized) {
+ return PM_NOT_INITIALIZED;
+ }
+
+ if (target > 100) {
+ return PM_ERROR;
+ }
+
+ irq_key_t irq_key = irq_lock();
+ drv->soc_target = target;
+ irq_unlock(irq_key);
+
+ return PM_OK;
+}
+
+// Timer handlers
+static void pm_monitoring_timer_handler(void* context) {
+ pm_monitor_power_sources();
+}
+
+static void pm_shutdown_timer_handler(void* context) {
+ pm_driver_t* drv = &g_pm;
+ drv->shutdown_timer_elapsed = true;
+ pm_process_state_machine();
+}
+
+bool pm_driver_suspend(void) {
+ pm_driver_t* drv = &g_pm;
+
+ if (!drv->initialized) {
+ return false;
+ }
+
+ irq_key_t irq_key = irq_lock();
+
+ if (drv->woke_up_from_suspend) {
+ // Driver just woke up from suspend and have no data available yet.
+ // Request the suspend but wait for the next pmic_meausrement
+ drv->suspending = true;
+ } else {
+#ifdef USE_RTC
+ // Schedule auto-hibernation rtc event
+ pm_schedule_rtc_wakeup();
+#endif
+ drv->suspended = true;
+ }
+
+ // Delete the monitoring timer to stop the periodic sampling
+ systimer_delete(drv->monitoring_timer);
+
+ irq_unlock(irq_key);
+
+ return true;
+}
+
+#ifdef USE_RTC
+
+bool pm_schedule_rtc_wakeup(void) {
+ pm_driver_t* drv = &g_pm;
+
+ if (!drv->initialized) {
+ return false;
+ }
+
+ // Capture the timestamp when device was active for the last time.
+ if (!rtc_get_timestamp(&drv->last_active_timestamp)) {
+ return false;
+ }
+
+ if ((drv->last_active_timestamp - drv->suspend_timestamp) >=
+ PM_AUTO_HIBERNATE_TIMEOUT_S) {
+ // Device is very long time in suspend mode without external power source,
+ // hibernate it to save power.
+ pm_hibernate();
+ }
+
+ if (drv->autohibernate_event_id == 0) {
+ rtc_schedule_wakeup_event(
+ drv->suspend_timestamp + PM_AUTO_HIBERNATE_TIMEOUT_S,
+ pm_rtc_wakeup_callback, NULL, &drv->autohibernate_event_id);
+ }
+
+ return true;
+}
+
+#endif
+
+bool pm_is_charging(void) {
+ pm_driver_t* drv = &g_pm;
+
+ if (!drv->initialized) {
+ return false;
+ }
+
+ bool is_charging = false;
+
+ irq_key_t irq_key = irq_lock();
+ if (drv->charging_enabled &&
+ (!drv->fully_charged && !drv->soc_target_reached) &&
+ (drv->usb_connected || drv->wireless_connected)) {
+ is_charging = true;
+ }
+ irq_unlock(irq_key);
+
+ return is_charging;
+}
+
+bool pm_usb_is_connected(void) {
+ pm_driver_t* drv = &g_pm;
+
+ if (!drv->initialized) {
+ return false;
+ }
+
+ bool usb_connected;
+ irq_key_t irq_key = irq_lock();
+ usb_connected = drv->usb_connected;
+ irq_unlock(irq_key);
+
+ return usb_connected;
+}
+
+bool pm_driver_resume(void) {
+ pm_driver_t* drv = &g_pm;
+
+ if (!drv->initialized) {
+ return false;
+ }
+
+ if (!drv->suspended && !drv->suspending) {
+ // Already resumed, nothing to do
+ return true;
+ }
+
+ drv->suspended = false;
+ drv->suspending = false;
+ drv->woke_up_from_suspend = true;
+ drv->state_machine_stabilized = false;
+
+#ifdef USE_RTC
+
+ uint32_t rtc_timestamp;
+ rtc_get_timestamp(&rtc_timestamp);
+ drv->time_in_suspend_s = (rtc_timestamp - drv->last_active_timestamp);
+
+#endif
+
+ // Recreate the monitoring timer
+ drv->monitoring_timer = systimer_create(pm_monitoring_timer_handler, NULL);
+ if (drv->monitoring_timer == NULL) {
+ return false;
+ }
+
+ // Request new pmic measurement
+ pmic_measure(pm_pmic_data_ready, NULL);
+
+ // Set the periodic sampling period
+ systimer_set_periodic(drv->monitoring_timer, PM_TIMER_PERIOD_MS);
+
+ return true;
+}
+
+bool pm_driver_is_suspended(void) {
+ pm_driver_t* drv = &g_pm;
+
+ bool suspended;
+ irq_key_t irq_key = irq_lock();
+ suspended = drv->suspended;
+ irq_unlock(irq_key);
+
+ return suspended;
+}
+
+void pm_compensate_fuel_gauge(float* soc, uint32_t elapsed_s,
+ float battery_current_ma, float bat_temp_c) {
+ pm_driver_t* drv = &g_pm;
+
+ if (!drv->initialized) {
+ return;
+ }
+
+ float compensation_mah = ((battery_current_ma)*elapsed_s) / 3600.0f;
+ bool discharging_mode = battery_current_ma >= 0.0f;
+ *soc -=
+ (compensation_mah / battery_total_capacity(&drv->fuel_gauge.model,
+ bat_temp_c, discharging_mode));
+}
+
+static pm_status_t pm_wait_to_stabilize(pm_driver_t* drv, uint32_t timeout_ms) {
+ uint32_t expire_time = ticks_timeout(timeout_ms);
+
+ // Poll until fuel_gauge is initialized and first PMIC & WLC measurements
+ // propagates into power_monitor.
+ bool state_machine_stabilized;
+ do {
+ if (ticks_expired(expire_time)) {
+ return PM_TIMEOUT;
+ }
+
+ irq_key_t irq_key = irq_lock();
+ state_machine_stabilized = drv->state_machine_stabilized;
+ irq_unlock(irq_key);
+ } while (!state_machine_stabilized);
+
+ return PM_OK;
+}
+
+#endif
diff --git a/core/embed/io/power_manager/stm32u5/power_manager_internal.h b/core/embed/io/power_manager/stm32u5/power_manager_internal.h
new file mode 100644
index 00000000..4b23e23f
--- /dev/null
+++ b/core/embed/io/power_manager/stm32u5/power_manager_internal.h
@@ -0,0 +1,197 @@
+/*
+ * This file is part of the Trezor project, https://trezor.io/
+ *
+ * Copyright (c) SatoshiLabs
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <trezor_types.h>
+
+#include <io/pmic.h>
+#include <io/power_manager.h>
+#include <sys/rtc_scheduler.h>
+#include <sys/systimer.h>
+
+#include "../fuel_gauge/fuel_gauge.h"
+#include "../stwlc38/stwlc38.h"
+
+// Power manager thresholds & timings
+#define PM_TIMER_PERIOD_MS 100
+#define PM_SHUTDOWN_TIMEOUT_MS 15000
+#define PM_BATTERY_UNDERVOLT_THR_V 3.0f
+#define PM_BATTERY_CRITICAL_RECOVERY_SOC 0.02f
+#define PM_BATTERY_LOW_THRESHOLD_SOC 15
+#define PM_BATTERY_CHARGING_CURRENT_MAX PMIC_CHARGING_LIMIT_MAX
+#define PM_BATTERY_CHARGING_CURRENT_MIN PMIC_CHARGING_LIMIT_MIN
+#define PM_BATTERY_SAMPLING_BUF_SIZE 10
+
+#define PM_SELF_DISG_RATE_HIBERNATION_MA 0.004f
+#define PM_SELF_DISG_RATE_SUSPEND_MA 0.032f
+
+// Fuel gauge extended kalman filter parameters
+#define PM_FUEL_GAUGE_R 3500.0f
+#define PM_FUEL_GAUGE_Q 0.0001f
+#define PM_FUEL_GAUGE_R_AGGRESSIVE 3000.0f
+#define PM_FUEL_GAUGE_Q_AGGRESSIVE 0.0002f
+#define PM_FUEL_GAUGE_P_INIT 0.1f
+
+// Timeout after which the device automatically transit from suspend to
+// hibernation
+#define PM_AUTO_HIBERNATE_TIMEOUT_S (2 * 60 * 60) // 2 hours
+
+#define PM_STABILIZATION_TIMEOUT_MS 2000
+
+// Thermal controller switch, comment out to disable the thermal controller
+#define PM_ENABLE_TEMP_CONTROL
+
+// Temperature controller parameters
+#define PM_TEMP_CONTROL_IDLE_PERIOD_MS 2 * 60 * 1000 // 2 minutes
+#define PM_TEMP_CONTROL_BAND_1_MAX_TEMP 39.0f
+#define PM_TEMP_CONTROL_BAND_2_MAX_TEMP 43.0f
+#define PM_TEMP_CONTROL_BAND_3_MAX_TEMP 45.0f
+#define PM_TEMP_CONTROL_BAND_4_MAX_TEMP 47.0f
+
+// Power manager battery sampling data structure
+typedef struct {
+ float vbat; // Battery voltage [V]
+ float ibat; // Battery current [mA]
+ float ntc_temp; // NTC temperature [°C]
+} pm_sampling_data_t;
+
+// Power manager core driver structure
+typedef struct {
+ bool initialized;
+ bool state_machine_stabilized;
+ pm_power_status_t state;
+
+ // Set if the driver was requested to suspend background operations.
+ // IF so, the driver waits until the last operation is finished,
+ // then enters suspended mode.
+ bool suspending;
+
+ // Set if the driver's background operations are suspended.
+ bool suspended;
+
+ // Fuel gauge
+ fuel_gauge_state_t fuel_gauge;
+ bool fuel_gauge_initialized;
+ pm_sampling_data_t bat_sampling_buf[PM_BATTERY_SAMPLING_BUF_SIZE];
+ uint8_t bat_sampling_buf_tail_idx;
+ uint8_t bat_sampling_buf_head_idx;
+ uint8_t soc_ceiled;
+
+ uint8_t soc_target;
+ bool soc_target_reached;
+ float target_battery_ocv_v_tau;
+ float battery_ocv;
+
+ // Battery charging state
+ bool charging_enabled;
+ uint16_t i_chg_target_ma;
+ uint16_t i_chg_max_limit_ma;
+
+ // Charging-limited detection filter state
+ // - charging_limited_latched: current filtered state exposed to pm_state_t
+ // - charging_limited_start_ms: timestamp when low-current-in-CC predicate
+ // started being true (0 when not timing)
+ bool charging_limited_latched;
+ uint32_t charging_limited_start_ms;
+
+#ifdef PM_ENABLE_TEMP_CONTROL
+ // Temp controller
+ uint32_t temp_control_timeout;
+ uint16_t i_chg_temp_limit_ma;
+ bool temp_control_active;
+#endif
+
+ // Power source hardware state
+ pmic_report_t pmic_data;
+ stwlc38_report_t wireless_data;
+ uint64_t pmic_last_update_us;
+ uint32_t pmic_sampling_period_ms;
+ bool pmic_measurement_ready;
+ bool woke_up_from_suspend;
+
+ // Power source logical state
+ bool usb_connected;
+ bool wireless_connected;
+ bool fully_charged;
+ bool battery_low;
+ bool battery_critical;
+
+ // Power mode request flags
+ bool request_suspend;
+ bool request_exit_suspend;
+ bool request_hibernate;
+ bool request_turn_on;
+ bool shutdown_timer_elapsed;
+
+ // Timers and timestamps
+ systimer_t* monitoring_timer;
+ systimer_t* shutdown_timer;
+ uint32_t suspend_timestamp;
+ uint32_t last_active_timestamp;
+ uint32_t time_in_suspend_s;
+ rtc_event_id_t autohibernate_event_id;
+
+} pm_driver_t;
+
+// State handler function definition
+typedef struct {
+ void (*enter)(pm_driver_t* drv);
+ pm_power_status_t (*handle)(pm_driver_t* drv);
+ void (*exit)(pm_driver_t* drv);
+} pm_state_handler_t;
+
+// Shared global driver instance
+extern pm_driver_t g_pm;
+
+// Power manager monitoring function called periodically to process data from
+// PMIC and WLC, run fuel gauge, run charging controller and stimulates
+// internal state machine.
+void pm_monitor_power_sources(void);
+
+// Power manager state machine automat driving internal state machine
+// transitions.
+void pm_process_state_machine(void);
+
+// PMIC callback function called when PMIC measurement acquisition is ready.
+void pm_pmic_data_ready(void* context, pmic_report_t* report);
+
+// Power manager charging controller function called periodically from
+// pm_monitor_power_sources() to control the charging current and state.
+void pm_charging_controller(pm_driver_t* drv);
+
+// Battery initial state of charge guess function. This function uses the
+// sampled battery data to guess the initial state of charge in case its
+// unknown.
+void pm_battery_initial_soc_guess(void);
+
+// Store power manager data to backup RAM
+pm_status_t pm_store_data_to_backup_ram(void);
+
+// Direct coulomb counter compensation of the SoC based on the battery current,
+// temp and elapsed time, this function is used to compensate the fuel gauge
+// estimation during the periods where the EKF could not be used, such as
+// suspend or hibernation.
+void pm_compensate_fuel_gauge(float* soc, uint32_t elapsed_s,
+ float battery_current_mah, float bat_temp_c);
+
+// Schedule the RTC wakeup when going into suspend mode.
+// Return false if the driver was not initialized or the RTC timestamp is
+// not available.
+bool pm_schedule_rtc_wakeup(void);
diff --git a/core/embed/io/power_manager/stm32u5/power_monitoring.c b/core/embed/io/power_manager/stm32u5/power_monitoring.c
new file mode 100644
index 00000000..f8040798
--- /dev/null
+++ b/core/embed/io/power_manager/stm32u5/power_monitoring.c
@@ -0,0 +1,404 @@
+/*
+ * This file is part of the Trezor project, https://trezor.io/
+ *
+ * Copyright (c) SatoshiLabs
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+#ifdef KERNEL_MODE
+
+#include <io/pmic.h>
+#include <sec/backup_ram.h>
+#include <sys/irq.h>
+#include <sys/notify.h>
+#include <sys/systick.h>
+#include <trezor_rtl.h>
+
+#ifdef USE_TELEMETRY
+#include <util/telemetry.h>
+#endif
+
+#include "../fuel_gauge/battery_model.h"
+#include "../fuel_gauge/fuel_gauge.h"
+#include "../stwlc38/stwlc38.h"
+#include "power_manager_internal.h"
+
+#ifdef PM_ENABLE_TEMP_CONTROL
+static void pm_temperature_controller(pm_driver_t* drv);
+#endif
+
+static void pm_battery_sampling(float vbat, float ibat, float ntc_temp);
+static void pm_parse_power_source_state(pm_driver_t* drv);
+
+#ifdef PM_ENABLE_TEMP_CONTROL
+
+// Temperature controller LUT
+static const struct {
+ float max_temp;
+ float current_limit_factor;
+} temp_bands[] = {
+ {PM_TEMP_CONTROL_BAND_1_MAX_TEMP, 1.0},
+ {PM_TEMP_CONTROL_BAND_2_MAX_TEMP, 0.7},
+ {PM_TEMP_CONTROL_BAND_3_MAX_TEMP, 0.5},
+ {PM_TEMP_CONTROL_BAND_4_MAX_TEMP, 0.3},
+};
+
+#endif
+
+void pm_monitor_power_sources(void) {
+ // Periodically called timer to request PMIC measurements. PMIC will call
+ // pm_pmic_data_ready() callback when the measurements are ready.
+ pmic_measure(pm_pmic_data_ready, NULL);
+}
+
+// pmic measurement callback
+void pm_pmic_data_ready(void* context, pmic_report_t* report) {
+ pm_driver_t* drv = &g_pm;
+
+ // Store measurement timestamp
+ if (drv->pmic_last_update_us == 0) {
+ drv->pmic_sampling_period_ms = PM_TIMER_PERIOD_MS;
+ } else {
+ // Calculate the time since the last PMIC update
+ drv->pmic_sampling_period_ms =
+ (systick_us() - drv->pmic_last_update_us) / 1000;
+ }
+ drv->pmic_last_update_us = systick_us();
+ // Copy pmic data
+ memcpy(&drv->pmic_data, report, sizeof(pmic_report_t));
+
+ // Get wireless charger data
+ stwlc38_get_report(&drv->wireless_data);
+
+#ifdef USE_TELEMETRY
+ // Update telemetry with current battery temperature
+ telemetry_update_battery_temp(drv->pmic_data.ntc_temp);
+#endif
+
+ pm_parse_power_source_state(drv);
+
+ // Run battery charging controller
+ pm_charging_controller(drv);
+
+ drv->battery_ocv =
+ battery_meas_to_ocv(&drv->fuel_gauge.model, drv->pmic_data.vbat,
+ drv->pmic_data.ibat, drv->pmic_data.ntc_temp);
+
+ if (!drv->fuel_gauge_initialized) {
+ // Fuel gauge not initialized yet, battery SoC not available, sample the
+ // battery data into the circular buffer.
+ pm_battery_sampling(drv->pmic_data.vbat, drv->pmic_data.ibat,
+ drv->pmic_data.ntc_temp);
+
+ } else {
+ if (drv->woke_up_from_suspend) {
+#ifdef USE_RTC
+
+ // Use known battery self-discharge rate to compensate the fuel gauge
+ // estimation during the suspend period. Since this period may be very
+ // long and the battery temperature may vary, use the average ambient
+ // temperature.
+ pm_compensate_fuel_gauge(&drv->fuel_gauge.soc, drv->time_in_suspend_s,
+ PM_SELF_DISG_RATE_SUSPEND_MA, 25.0f);
+
+ // TODO: Currently in suspend mode we use single self-discharge rate
+ // but in practice the discharge rate may change in case some components
+ // remains active. Since the device is very likely to stay in suspend
+ // mode for limited time, for now we decided to neglect this. but in
+ // the future we may want to distinguish between different suspend modes
+ // and use different self-discharge rates.
+
+ fuel_gauge_set_soc(&drv->fuel_gauge, drv->fuel_gauge.soc,
+ drv->fuel_gauge.P);
+
+#endif // USE_RTC
+
+ // clear the flag
+ drv->woke_up_from_suspend = false;
+
+ } else {
+ fuel_gauge_update(&drv->fuel_gauge, drv->pmic_sampling_period_ms,
+ drv->pmic_data.vbat, drv->pmic_data.ibat,
+ drv->pmic_data.ntc_temp);
+ }
+
+ // Charging completed flag from PMIC controller
+ if (drv->pmic_data.charge_status & 0x2) {
+ // Force fuel gauge to 100%, keep the covariance
+ drv->fully_charged = true;
+ fuel_gauge_set_soc(&drv->fuel_gauge, 1.0f, drv->fuel_gauge.P);
+ } else {
+ if (drv->pmic_data.ibat > 0) {
+ drv->fully_charged = false;
+ }
+ }
+
+ // Ceil the float soc to user-friendly integer
+ drv->soc_ceiled = (uint8_t)(drv->fuel_gauge.soc_latched * 100 + 0.999f);
+
+ // Check battery voltage for low threshold
+ if (drv->soc_ceiled <= PM_BATTERY_LOW_THRESHOLD_SOC && !drv->battery_low) {
+ drv->battery_low = true;
+ } else if (drv->soc_ceiled > PM_BATTERY_LOW_THRESHOLD_SOC &&
+ drv->battery_low) {
+ drv->battery_low = false;
+ }
+
+ // Process state machine with updated battery and power source information
+ pm_process_state_machine();
+
+ pm_store_data_to_backup_ram();
+
+ if (drv->suspending) {
+#ifdef USE_RTC
+ // Schedule auto-hibernation rtc event
+ pm_schedule_rtc_wakeup();
+#endif
+ drv->suspending = false;
+ drv->suspended = true;
+ }
+
+ drv->state_machine_stabilized = true;
+ }
+}
+
+void pm_charging_controller(pm_driver_t* drv) {
+ if (drv->charging_enabled == false) {
+ // Charging is disabled
+ if (drv->i_chg_target_ma != 0) {
+ drv->i_chg_target_ma = 0;
+ } else {
+ // No action required
+ return;
+ }
+ } else if (drv->usb_connected) {
+ drv->i_chg_target_ma = PM_BATTERY_CHARGING_CURRENT_MAX;
+
+ } else if (drv->wireless_connected) {
+ drv->i_chg_target_ma = PM_BATTERY_CHARGING_CURRENT_MAX;
+
+ } else {
+ // Charging enabled but no external power source, clear charging target
+ drv->i_chg_target_ma = 0;
+ }
+
+ // charging current software limit
+ if (drv->i_chg_target_ma > drv->i_chg_max_limit_ma) {
+ drv->i_chg_target_ma = drv->i_chg_max_limit_ma;
+ }
+
+#ifdef PM_ENABLE_TEMP_CONTROL
+ pm_temperature_controller(drv);
+#endif
+
+ if (drv->pmic_data.ntc_disconnected) {
+ drv->i_chg_target_ma = 0;
+ }
+
+ if (drv->soc_target == 100) {
+ drv->soc_target_reached = false;
+ } else if (fabsf((-drv->pmic_data.ibat) - (float)drv->i_chg_target_ma) <=
+ 20.0f) {
+ // Translate SoC target to charging voltage via battery model
+ float target_ocv_voltage_v =
+ battery_ocv(&drv->fuel_gauge.model, drv->soc_target / 100.0f,
+ drv->pmic_data.ntc_temp, false);
+
+ float battery_ocv_v =
+ battery_meas_to_ocv(&drv->fuel_gauge.model, drv->pmic_data.vbat,
+ drv->pmic_data.ibat, drv->pmic_data.ntc_temp);
+
+ drv->target_battery_ocv_v_tau =
+ (drv->target_battery_ocv_v_tau * 0.95f) +
+ (battery_ocv_v * 0.05f); // Exponential smoothing
+
+ if (drv->target_battery_ocv_v_tau > target_ocv_voltage_v) {
+ // current voltage is within tight bounds of target voltage,
+ // we may also force SoC estimate to target value.
+ if (drv->target_battery_ocv_v_tau < target_ocv_voltage_v + 0.15) {
+ fuel_gauge_set_soc(&drv->fuel_gauge,
+ (drv->soc_target / 100.0f) - 0.0001f,
+ drv->fuel_gauge.P);
+ }
+
+ drv->soc_target_reached = true;
+ }
+ } else if (drv->soc_ceiled < drv->soc_target) {
+ drv->soc_target_reached = false;
+ }
+
+ if (drv->soc_target_reached) {
+ drv->i_chg_target_ma = 0;
+ }
+
+ // Set charging target
+ if (drv->i_chg_target_ma != pmic_get_charging_limit()) {
+ // Set charging current limit
+ pmic_set_charging_limit(drv->i_chg_target_ma);
+ }
+
+ if (drv->i_chg_target_ma == 0) {
+ pmic_set_charging(false);
+ } else {
+ // Clear and release charger if it has any errors
+ if (drv->pmic_data.charge_err || drv->pmic_data.charge_sensor_err) {
+ pmic_clear_charger_errors();
+ }
+
+ pmic_set_charging(true);
+ }
+}
+
+#ifdef PM_ENABLE_TEMP_CONTROL
+
+static void pm_temperature_controller(pm_driver_t* drv) {
+ if (ticks_expired(drv->temp_control_timeout)) {
+ uint16_t i_chg_temp_limit_ma = 0;
+
+ i_chg_temp_limit_ma = 0; // Default to safety limit
+ for (size_t i = 0; i < sizeof(temp_bands) / sizeof(temp_bands[0]); ++i) {
+ if (drv->pmic_data.ntc_temp < temp_bands[i].max_temp) {
+ i_chg_temp_limit_ma = PM_BATTERY_CHARGING_CURRENT_MAX *
+ temp_bands[i].current_limit_factor;
+ break;
+ }
+ }
+
+ // If the temperature limit has changed, update the limit and reset the
+ // debounce timer
+ if (drv->i_chg_temp_limit_ma != i_chg_temp_limit_ma) {
+ drv->i_chg_temp_limit_ma = i_chg_temp_limit_ma;
+ drv->temp_control_timeout = ticks_timeout(PM_TEMP_CONTROL_IDLE_PERIOD_MS);
+ }
+ }
+
+ if (drv->i_chg_target_ma > drv->i_chg_temp_limit_ma) {
+ // Limit the charging current by temperature controller
+ drv->i_chg_target_ma = drv->i_chg_temp_limit_ma;
+ drv->temp_control_active = true;
+ } else {
+ drv->temp_control_active = false;
+ }
+}
+
+#endif
+
+static void pm_battery_sampling(float vbat, float ibat, float ntc_temp) {
+ pm_driver_t* drv = &g_pm;
+
+ // Store battery data in the buffer
+ drv->bat_sampling_buf[drv->bat_sampling_buf_head_idx].vbat = vbat;
+ drv->bat_sampling_buf[drv->bat_sampling_buf_head_idx].ibat = ibat;
+ drv->bat_sampling_buf[drv->bat_sampling_buf_head_idx].ntc_temp = ntc_temp;
+
+ // Update head index
+ drv->bat_sampling_buf_head_idx++;
+ if (drv->bat_sampling_buf_head_idx >= PM_BATTERY_SAMPLING_BUF_SIZE) {
+ drv->bat_sampling_buf_head_idx = 0;
+ }
+
+ // Check if the buffer is full
+ if (drv->bat_sampling_buf_head_idx == drv->bat_sampling_buf_tail_idx) {
+ // Buffer is full, move tail index forward
+ drv->bat_sampling_buf_tail_idx++;
+ if (drv->bat_sampling_buf_tail_idx >= PM_BATTERY_SAMPLING_BUF_SIZE) {
+ drv->bat_sampling_buf_tail_idx = 0;
+ }
+ }
+}
+
+static void pm_parse_power_source_state(pm_driver_t* drv) {
+ // Check USB power source status
+ if (drv->pmic_data.usb_status != 0x0) {
+ if (!drv->usb_connected) {
+ drv->usb_connected = true;
+ notify_send(NOTIFY_POWER_STATUS_CHANGE);
+ }
+ } else {
+ if (drv->usb_connected) {
+ drv->usb_connected = false;
+ notify_send(NOTIFY_POWER_STATUS_CHANGE);
+ }
+ }
+
+ // Check wireless charger status
+ if (drv->wireless_data.vout_ready) {
+ if (!drv->wireless_connected) {
+ drv->wireless_connected = true;
+ notify_send(NOTIFY_POWER_STATUS_CHANGE);
+ }
+ } else {
+ if (drv->wireless_connected) {
+ drv->wireless_connected = false;
+ notify_send(NOTIFY_POWER_STATUS_CHANGE);
+ }
+ }
+
+ // Check battery voltage for critical (undervoltage) threshold
+ if ((drv->pmic_data.vbat < PM_BATTERY_UNDERVOLT_THR_V) &&
+ !drv->battery_critical && !drv->usb_connected) {
+ // Force Fuel gauge to 0, keep the covariance
+ fuel_gauge_set_soc(&drv->fuel_gauge, 0.0f, drv->fuel_gauge.P);
+ drv->battery_critical = true;
+
+ } else if (drv->fuel_gauge.soc_latched >=
+ (PM_BATTERY_CRITICAL_RECOVERY_SOC) ||
+ drv->usb_connected) {
+ // Restore the battery critical state
+ drv->battery_critical = false;
+ }
+}
+
+void pm_battery_initial_soc_guess(void) {
+ pm_driver_t* drv = &g_pm;
+
+ irq_key_t irq_key = irq_lock();
+
+ // Check if the buffer is full
+ if (drv->bat_sampling_buf_head_idx == drv->bat_sampling_buf_tail_idx) {
+ // Buffer is empty, no data to process
+ return;
+ }
+
+ // Calculate average voltage, current and temperature from the sampling
+ // buffer and run the fuel gauge initial guess
+ uint8_t buf_idx = drv->bat_sampling_buf_tail_idx;
+ uint8_t samples_count = 0;
+ float vbat_g = 0.0f;
+ float ibat_g = 0.0f;
+ float ntc_temp_g = 0.0f;
+ while (drv->bat_sampling_buf_head_idx != buf_idx) {
+ vbat_g += drv->bat_sampling_buf[buf_idx].vbat;
+ ibat_g += drv->bat_sampling_buf[buf_idx].ibat;
+ ntc_temp_g += drv->bat_sampling_buf[buf_idx].ntc_temp;
+
+ buf_idx++;
+ if (buf_idx >= PM_BATTERY_SAMPLING_BUF_SIZE) {
+ buf_idx = 0;
+ }
+
+ samples_count++;
+ }
+
+ // Calculate average values
+ vbat_g /= samples_count;
+ ibat_g /= samples_count;
+ ntc_temp_g /= samples_count;
+
+ fuel_gauge_initial_guess(&drv->fuel_gauge, vbat_g, ibat_g, ntc_temp_g);
+
+ irq_unlock(irq_key);
+}
+
+#endif
diff --git a/core/embed/io/power_manager/stm32u5/power_states.c b/core/embed/io/power_manager/stm32u5/power_states.c
new file mode 100644
index 00000000..d084e054
--- /dev/null
+++ b/core/embed/io/power_manager/stm32u5/power_states.c
@@ -0,0 +1,284 @@
+/*
+ * This file is part of the Trezor project, https://trezor.io/
+ *
+ * Copyright (c) SatoshiLabs
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+#ifdef KERNEL_MODE
+
+#include <io/backlight.h>
+#include <io/pmic.h>
+#include <sys/bootutils.h>
+#include <sys/systick.h>
+#include <sys/systimer.h>
+
+#include "power_manager_internal.h"
+
+// Power manager internal state machine handlers and entry/exit funtions
+static pm_power_status_t pm_handle_state_active(pm_driver_t* drv);
+static pm_power_status_t pm_handle_state_power_save(pm_driver_t* drv);
+static pm_power_status_t pm_handle_state_shutting_down(pm_driver_t* drv);
+static pm_power_status_t pm_handle_state_suspend(pm_driver_t* drv);
+static pm_power_status_t pm_handle_state_charging(pm_driver_t* drv);
+static pm_power_status_t pm_handle_state_hibernate(pm_driver_t* drv);
+
+static void pm_enter_hibernate(pm_driver_t* drv);
+static void pm_enter_charging(pm_driver_t* drv);
+static void pm_enter_shutting_down(pm_driver_t* drv);
+static void pm_enter_power_save(pm_driver_t* drv);
+static void pm_enter_active(pm_driver_t* drv);
+static void pm_exit_shutting_down(pm_driver_t* drv);
+
+// State handler lookup table
+static const pm_state_handler_t state_handlers[] = {
+ [PM_STATE_ACTIVE] =
+ {
+ .enter = pm_enter_active,
+ .handle = pm_handle_state_active,
+ .exit = NULL,
+ },
+ [PM_STATE_POWER_SAVE] =
+ {
+ .enter = pm_enter_power_save,
+ .handle = pm_handle_state_power_save,
+ .exit = NULL,
+ },
+ [PM_STATE_SHUTTING_DOWN] =
+ {
+ .enter = pm_enter_shutting_down,
+ .handle = pm_handle_state_shutting_down,
+ .exit = pm_exit_shutting_down,
+ },
+ [PM_STATE_SUSPEND] =
+ {
+ .enter = NULL,
+ .handle = pm_handle_state_suspend,
+ .exit = NULL,
+ },
+ [PM_STATE_CHARGING] =
+ {
+ .enter = pm_enter_charging,
+ .handle = pm_handle_state_charging,
+ .exit = NULL,
+ },
+ [PM_STATE_HIBERNATE] =
+ {
+ .enter = pm_enter_hibernate,
+ .handle = pm_handle_state_hibernate,
+ .exit = NULL,
+ },
+};
+
+void pm_process_state_machine(void) {
+ pm_driver_t* drv = &g_pm;
+ pm_power_status_t old_state;
+ pm_power_status_t new_state;
+
+ // Loop until state machine converge to a stable state
+ while (true) {
+ // Get current state
+ old_state = drv->state;
+
+ // Call state handler to process the current state
+ new_state = state_handlers[old_state].handle(drv);
+
+ // Check if the state has changed
+ if (new_state != old_state) {
+ // Exit old state
+ if (state_handlers[old_state].exit != NULL) {
+ state_handlers[old_state].exit(drv);
+ }
+
+ // Update state
+ drv->state = new_state;
+
+ // Enter new state
+ if (state_handlers[new_state].enter != NULL) {
+ state_handlers[new_state].enter(drv);
+ }
+
+ } else {
+ // State has not changed, exit the loop
+ break;
+ }
+ }
+}
+
+// State handler implementations
+
+static pm_power_status_t pm_handle_state_hibernate(pm_driver_t* drv) {
+ if (drv->request_turn_on) {
+ drv->request_turn_on = false;
+ return PM_STATE_POWER_SAVE;
+ }
+
+ // External power source, start charging
+ if (drv->usb_connected || drv->wireless_connected) {
+ return PM_STATE_CHARGING;
+ }
+
+ // Hibernate again
+ if (drv->request_hibernate) {
+ drv->request_hibernate = false;
+
+ // Put PMIC into ship mode (ultra-low power)
+ pmic_enter_shipmode();
+ return PM_STATE_HIBERNATE;
+ }
+
+ return drv->state;
+}
+
+static pm_power_status_t pm_handle_state_charging(pm_driver_t* drv) {
+ if (drv->request_turn_on) {
+ drv->request_turn_on = false;
+ return PM_STATE_POWER_SAVE;
+ }
+
+ // Go back to hibernate if external power was removed.
+ if (!drv->usb_connected && !drv->wireless_connected) {
+ return PM_STATE_HIBERNATE;
+ }
+
+ // Hibernate again
+ if (drv->request_hibernate) {
+ drv->request_hibernate = false;
+
+ // Device is charging, request is rejected with no action
+ return PM_STATE_CHARGING;
+ }
+
+ return drv->state;
+}
+
+static pm_power_status_t pm_handle_state_suspend(pm_driver_t* drv) {
+ if (drv->request_hibernate) {
+ drv->request_hibernate = false;
+ return PM_STATE_HIBERNATE;
+ }
+
+ if (drv->request_exit_suspend) {
+ drv->request_exit_suspend = false;
+ return PM_STATE_POWER_SAVE;
+ }
+
+ return drv->state;
+}
+
+static pm_power_status_t pm_handle_state_shutting_down(pm_driver_t* drv) {
+ // System is shutting down, but user can still hibernate the device early.
+ if (drv->request_hibernate) {
+ drv->request_hibernate = false;
+ return PM_STATE_HIBERNATE;
+ }
+
+ if (drv->request_suspend) {
+ drv->request_suspend = false;
+ return PM_STATE_SUSPEND;
+ }
+
+ // Return to power save if external power or battery recovered
+ if (drv->usb_connected || !drv->battery_critical) {
+ return PM_STATE_POWER_SAVE;
+ }
+
+ // Enter hibernate when shutdown timer elapses
+ if (drv->shutdown_timer_elapsed) {
+ return PM_STATE_HIBERNATE;
+ }
+
+ return drv->state;
+}
+
+static pm_power_status_t pm_handle_state_power_save(pm_driver_t* drv) {
+ // Handle hibernate request
+ if (drv->request_hibernate) {
+ drv->request_hibernate = false;
+ return PM_STATE_HIBERNATE;
+ }
+
+ // Handle suspend request
+ if (drv->request_suspend) {
+ drv->request_suspend = false;
+ return PM_STATE_SUSPEND;
+ }
+
+ // Return to active if external power or battery recovered
+ if (drv->usb_connected || !drv->battery_low) {
+ return PM_STATE_ACTIVE;
+ }
+
+ // Go to shutdown if battery critical
+ if (!drv->usb_connected && drv->battery_critical) {
+ return PM_STATE_SHUTTING_DOWN;
+ }
+
+ return drv->state;
+}
+
+static pm_power_status_t pm_handle_state_active(pm_driver_t* drv) {
+ // Handle hibernate request
+ if (drv->request_hibernate) {
+ drv->request_hibernate = false;
+ return PM_STATE_HIBERNATE;
+ }
+
+ // Handle suspend request
+ if (drv->request_suspend) {
+ drv->request_suspend = false;
+ return PM_STATE_SUSPEND;
+ }
+
+ // Handle low battery with no external power
+ if (!drv->usb_connected && drv->battery_low) {
+ return PM_STATE_POWER_SAVE;
+ }
+
+ return drv->state;
+}
+
+// State enter/exit actions
+
+static void pm_enter_hibernate(pm_driver_t* drv) {
+ // Store power manager data with request to hibernate, power manager
+ // will try to hibernate immediately after reboot.
+ pm_store_data_to_backup_ram();
+ reboot_to_off();
+}
+
+static void pm_enter_charging(pm_driver_t* drv) {}
+
+static void pm_enter_shutting_down(pm_driver_t* drv) {
+ // Set shutdown timer
+ systimer_set(drv->shutdown_timer, PM_SHUTDOWN_TIMEOUT_MS);
+}
+
+static void pm_enter_power_save(pm_driver_t* drv) {
+ // Limit backlight
+ backlight_set_max_level(130);
+}
+
+static void pm_enter_active(pm_driver_t* drv) {
+ // Set unlimited backlight
+ backlight_set_max_level(255);
+}
+
+static void pm_exit_shutting_down(pm_driver_t* drv) {
+ // Stop the shutdown timer
+ systimer_unset(drv->shutdown_timer);
+ drv->shutdown_timer_elapsed = false;
+}
+
+#endif
diff --git a/core/embed/io/power_manager/stwlc38/nvm_data_cfg_v4_cut_1_2.h b/core/embed/io/power_manager/stwlc38/nvm_data_cfg_v4_cut_1_2.h
new file mode 100644
index 00000000..6e7b62eb
--- /dev/null
+++ b/core/embed/io/power_manager/stwlc38/nvm_data_cfg_v4_cut_1_2.h
@@ -0,0 +1,51 @@
+/**
+ ******************************************************************************
+ * @brief : This is auto-generated header file which contains patch and
+ * configuration data to be programmed into chip.
+ ******************************************************************************
+ * @attention
+ *
+ * Copyright (c) 2023 STMicroelectronics.
+ * All rights reserved.
+ *
+ * This software is licensed under terms that can be found in the LICENSE file
+ * in the root directory of this software component.
+ * If no LICENSE file comes with this software, it is provided AS-IS.
+ *
+ ******************************************************************************
+ */
+
+const uint8_t cfg_data_cut_1_2[] = {
+ 0x7C, 0x1D, 0x00, 0x00, 0xEF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0xC8, 0x44, 0x44, 0x44, 0x00, 0x00, 0x00, 0x46, 0x01, 0x00,
+ 0x63, 0x00, 0x01, 0x0D, 0x06, 0x02, 0x00, 0xFF, 0xC3, 0x0F, 0x05, 0x3F,
+ 0x14, 0x0C, 0x28, 0x1E, 0x50, 0x46, 0x05, 0x01, 0x82, 0x82, 0x82, 0x82,
+ 0x40, 0x02, 0x02, 0x02, 0x02, 0x50, 0x07, 0x06, 0x12, 0x00, 0x2D, 0x00,
+ 0x90, 0x00, 0x00, 0x40, 0x23, 0x32, 0x14, 0x2F, 0x41, 0x45, 0x55, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x28, 0x26, 0x40, 0x5B, 0x60, 0x56, 0xBC,
+ 0xB4, 0x3C, 0x08, 0x08, 0x14, 0x32, 0x46, 0x02, 0x02, 0x02, 0x03, 0x00,
+ 0x14, 0x05, 0x46, 0xFF, 0x00, 0x0A, 0x00, 0x00, 0x09, 0x84, 0x03, 0x00,
+ 0x01, 0x00, 0x01, 0x00, 0x09, 0x00, 0x03, 0x00, 0x7E, 0x7D, 0xA5, 0x0A,
+ 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0xD6, 0x06, 0x52, 0x03, 0x00, 0x19,
+ 0x00, 0x00, 0x07, 0x19, 0x11, 0x00, 0x0B, 0x00, 0x16, 0x11, 0x22, 0x33,
+ 0x44, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x05, 0x1E, 0x00,
+ 0x12, 0x00, 0x1E, 0x3D, 0x5D, 0x54, 0x8A, 0xB7, 0x9C, 0x14, 0x8E, 0x10,
+ 0x1E, 0x03, 0x03, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0E,
+ 0x00, 0x01, 0x64, 0x00, 0xC1, 0x00, 0x00, 0x00, 0xF0, 0x28, 0x0E, 0x05,
+ 0xD4, 0x00, 0x28, 0x00, 0x00, 0x01, 0x3F, 0x03, 0x00, 0x00, 0x00, 0x41,
+ 0x2F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC1, 0x00,
+ 0x22, 0xC2, 0x05, 0x06, 0x09, 0x0D, 0x03, 0x1E, 0x96, 0x01, 0x03, 0x2D,
+ 0x00, 0x01, 0x00, 0x40, 0x00, 0x13, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x7C, 0x63, 0x20, 0x32, 0x5F, 0xFF, 0x03, 0x00, 0x22, 0x24, 0xDB, 0x1A,
+ 0xF6, 0x20, 0x32, 0x30, 0x32, 0x32, 0x50, 0x70, 0x14, 0x0C, 0x55, 0xA3,
+ 0x00, 0x03, 0x1E, 0xC8, 0xA5, 0x0F, 0x14, 0x1E, 0x32, 0x32, 0x19, 0x28,
+ 0x37, 0x02, 0x00, 0x0A, 0x1E, 0x1E, 0x0C, 0x00, 0x00, 0x00, 0x18, 0x00,
+ 0xFF, 0x50, 0x38, 0x00, 0x0F, 0x2C, 0x51, 0x64, 0x04, 0x3E, 0x0A, 0x53,
+ 0x0A, 0x66, 0x00, 0x00, 0x11, 0x2E, 0x53, 0x7B, 0xB2, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x0C, 0x14, 0x19, 0x18, 0x19, 0x16, 0xA2, 0x00, 0x55,
+ 0x1A, 0x07, 0x2C, 0x42, 0x58, 0x6E, 0x84, 0x04, 0x04, 0x04, 0x04, 0x04,
+ 0x04, 0x7D, 0x7D, 0x7D, 0x7D, 0x7D, 0x7D, 0xC8, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE4, 0x17, 0x63, 0x7B,
+};
diff --git a/core/embed/io/power_manager/stwlc38/nvm_data_cfg_v4_cut_1_3.h b/core/embed/io/power_manager/stwlc38/nvm_data_cfg_v4_cut_1_3.h
new file mode 100644
index 00000000..d88b3619
--- /dev/null
+++ b/core/embed/io/power_manager/stwlc38/nvm_data_cfg_v4_cut_1_3.h
@@ -0,0 +1,51 @@
+/**
+ ******************************************************************************
+ * @brief : This is auto-generated header file which contains patch and
+ * configuration data to be programmed into chip.
+ ******************************************************************************
+ * @attention
+ *
+ * Copyright (c) 2023 STMicroelectronics.
+ * All rights reserved.
+ *
+ * This software is licensed under terms that can be found in the LICENSE file
+ * in the root directory of this software component.
+ * If no LICENSE file comes with this software, it is provided AS-IS.
+ *
+ ******************************************************************************
+ */
+
+const uint8_t cfg_data_cut_1_3[] = {
+ 0x7C, 0x1D, 0x00, 0x00, 0xEF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0xC8, 0x44, 0x44, 0x44, 0x00, 0x00, 0x00, 0x46, 0x01, 0x00,
+ 0x63, 0x00, 0x01, 0x0D, 0x06, 0x02, 0x00, 0xFF, 0x83, 0x0F, 0x05, 0x3F,
+ 0x14, 0x0C, 0x28, 0x1E, 0x50, 0x46, 0x05, 0x01, 0x82, 0x82, 0x82, 0x82,
+ 0x40, 0x42, 0x42, 0x42, 0x42, 0x50, 0x07, 0x06, 0x12, 0x00, 0x2D, 0x00,
+ 0x90, 0x00, 0x00, 0x40, 0x23, 0x32, 0x14, 0x2F, 0x41, 0x45, 0x55, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x28, 0x26, 0x40, 0x5B, 0x60, 0x56, 0xBC,
+ 0xB4, 0x3C, 0x08, 0x08, 0x14, 0x32, 0x46, 0x02, 0x02, 0x02, 0x03, 0x00,
+ 0x14, 0x05, 0x46, 0xFF, 0x00, 0x0A, 0x00, 0x00, 0x09, 0x84, 0x03, 0x00,
+ 0x01, 0x00, 0x01, 0x00, 0x09, 0x00, 0x03, 0x00, 0x7E, 0x7D, 0xA5, 0x0A,
+ 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0xD6, 0x06, 0x52, 0x03, 0x00, 0x19,
+ 0x00, 0x00, 0x07, 0x19, 0x11, 0x00, 0x0B, 0x00, 0x16, 0x11, 0x22, 0x33,
+ 0x44, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x05, 0x1E, 0x00,
+ 0x12, 0x00, 0x1E, 0x3D, 0x5D, 0x54, 0x8A, 0xB7, 0x9C, 0x14, 0x8E, 0x10,
+ 0x1E, 0x03, 0x03, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0E,
+ 0x00, 0x01, 0x64, 0x00, 0xC1, 0x00, 0x00, 0x00, 0xF0, 0x28, 0x0E, 0x05,
+ 0xD4, 0x00, 0x28, 0x00, 0x00, 0x01, 0x3F, 0x03, 0x00, 0x00, 0x00, 0x41,
+ 0x2F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC1, 0x00,
+ 0x22, 0xC2, 0x05, 0x06, 0x09, 0x0D, 0x03, 0x1E, 0x96, 0x01, 0x03, 0x2D,
+ 0x00, 0x01, 0x00, 0x40, 0x00, 0x13, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x7C, 0x63, 0x20, 0x32, 0x5F, 0xFF, 0x03, 0x00, 0x22, 0x24, 0xDB, 0x1A,
+ 0xF6, 0x20, 0x32, 0x30, 0x32, 0x32, 0x50, 0x70, 0x14, 0x0C, 0x55, 0xA3,
+ 0x00, 0x03, 0x1E, 0xC8, 0xA5, 0x0F, 0x14, 0x1E, 0x32, 0x32, 0x19, 0x28,
+ 0x37, 0x02, 0x00, 0x0A, 0x1E, 0x1E, 0x0C, 0x00, 0x00, 0x00, 0x18, 0x00,
+ 0xFF, 0x50, 0x38, 0x00, 0x0F, 0x2C, 0x51, 0x64, 0x04, 0x3E, 0x0A, 0x53,
+ 0x0A, 0x66, 0x00, 0x00, 0x11, 0x2E, 0x53, 0x7B, 0xB2, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x0C, 0x14, 0x19, 0x18, 0x19, 0x16, 0xA2, 0x00, 0x55,
+ 0x1A, 0x07, 0x2C, 0x42, 0x58, 0x6E, 0x84, 0x04, 0x04, 0x04, 0x04, 0x04,
+ 0x04, 0x7D, 0x7D, 0x7D, 0x7D, 0x7D, 0x7D, 0xC8, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x85, 0xF6, 0x2D, 0x74,
+};
diff --git a/core/embed/io/power_manager/stwlc38/nvm_data_fw_patch_v4.h b/core/embed/io/power_manager/stwlc38/nvm_data_fw_patch_v4.h
new file mode 100644
index 00000000..84df0f85
--- /dev/null
+++ b/core/embed/io/power_manager/stwlc38/nvm_data_fw_patch_v4.h
@@ -0,0 +1,2309 @@
+/**
+ ******************************************************************************
+ * @brief : This is auto-generated header file which contains patch and
+ * configuration data to be programmed into chip.
+ ******************************************************************************
+ * @attention
+ *
+ * Copyright (c) 2023 STMicroelectronics.
+ * All rights reserved.
+ *
+ * This software is licensed under terms that can be found in the LICENSE file
+ * in the root directory of this software component.
+ * If no LICENSE file comes with this software, it is provided AS-IS.
+ *
+ ******************************************************************************
+ */
+
+#pragma once
+
+#define NVM_TARGET_CHIP_ID 38
+#define NVM_CFG_SIZE 384
+#define NVM_CFG_VERSION_ID 0x1D7C
+#define NVM_PATCH_SIZE 27348
+#define NVM_PATCH_VERSION_ID 0x0004
+
+#include "nvm_data_cfg_v4_cut_1_2.h"
+#include "nvm_data_cfg_v4_cut_1_3.h"
+
+const uint8_t patch_data[] = {
+ 0x18, 0x00, 0xE7, 0xFF, 0x00, 0xB5, 0x00, 0xF0, 0x07, 0xF8, 0x02, 0xBC,
+ 0x8E, 0x46, 0x01, 0x49, 0x08, 0x47, 0x00, 0x00, 0xDB, 0x64, 0x04, 0x00,
+ 0x10, 0xB5, 0x0B, 0x49, 0x02, 0x20, 0x48, 0x76, 0x0A, 0x48, 0xFC, 0x21,
+ 0x41, 0x72, 0xC0, 0x7A, 0x09, 0x4C, 0x80, 0x08, 0x00, 0xD0, 0xA0, 0x47,
+ 0x08, 0x48, 0x28, 0x21, 0x41, 0x63, 0x07, 0x49, 0x80, 0x31, 0x4C, 0x63,
+ 0x01, 0x21, 0x01, 0x63, 0x06, 0x49, 0x05, 0x48, 0x88, 0x47, 0x10, 0xBD,
+ 0x80, 0xC1, 0x01, 0x20, 0x00, 0xF3, 0x01, 0x20, 0x9F, 0x08, 0x04, 0x00,
+ 0x00, 0xC0, 0x01, 0x20, 0x64, 0x00, 0x08, 0x00, 0x85, 0x64, 0x04, 0x00,
+ 0xEE, 0x6A, 0x77, 0x8D, 0x9B, 0x1A, 0x64, 0xE5, 0xF8, 0xB5, 0x55, 0x48,
+ 0x81, 0x69, 0xC9, 0x06, 0x04, 0xD5, 0x81, 0x69, 0x10, 0x22, 0x91, 0x43,
+ 0x81, 0x61, 0xF8, 0xBD, 0x52, 0x4F, 0x51, 0x48, 0x79, 0x68, 0x51, 0x4D,
+ 0x88, 0x71, 0x00, 0x0A, 0xC8, 0x71, 0x40, 0x3D, 0x28, 0x6B, 0x00, 0x1D,
+ 0x81, 0x08, 0x4E, 0x48, 0x81, 0x63, 0x4D, 0x49, 0x4D, 0x4A, 0x80, 0x31,
+ 0x8A, 0x63, 0x4D, 0x4A, 0x00, 0x24, 0x14, 0x60, 0x4C, 0x4A, 0x14, 0x80,
+ 0x27, 0x22, 0xC2, 0x63, 0x4B, 0x4A, 0xCA, 0x63, 0x29, 0x68, 0x14, 0x31,
+ 0x8A, 0x08, 0x45, 0x49, 0x40, 0x31, 0x0A, 0x60, 0x43, 0x4A, 0x48, 0x4B,
+ 0xC0, 0x32, 0x13, 0x60, 0x2B, 0x69, 0x9B, 0x08, 0x4B, 0x60, 0x46, 0x49,
+ 0x51, 0x60, 0x1F, 0x21, 0x01, 0x63, 0x3D, 0x48, 0x44, 0x49, 0xC0, 0x38,
+ 0x41, 0x63, 0x46, 0x4B, 0x9C, 0x22, 0x43, 0x49, 0x43, 0x48, 0x98, 0x47,
+ 0x44, 0x48, 0x45, 0x49, 0x04, 0x70, 0x41, 0x48, 0x36, 0x4E, 0x80, 0x30,
+ 0x01, 0x60, 0x43, 0x49, 0x80, 0x38, 0xC1, 0x60, 0x42, 0x49, 0x01, 0x61,
+ 0x42, 0x49, 0x41, 0x66, 0x42, 0x49, 0x41, 0x61, 0x42, 0x49, 0x81, 0x61,
+ 0x42, 0x49, 0x41, 0x63, 0x42, 0x49, 0x81, 0x60, 0x42, 0x49, 0xC1, 0x67,
+ 0x42, 0x49, 0x80, 0x3E, 0x0C, 0x70, 0x42, 0x49, 0x41, 0x64, 0x42, 0x49,
+ 0x81, 0x64, 0x42, 0x49, 0xC1, 0x61, 0x30, 0x60, 0x27, 0x48, 0x41, 0x49,
+ 0xC0, 0x38, 0xC1, 0x61, 0x25, 0x48, 0x40, 0x49, 0x40, 0x30, 0x01, 0x60,
+ 0x3F, 0x48, 0x70, 0x61, 0x2D, 0x4B, 0x3C, 0x22, 0x3E, 0x49, 0x3F, 0x48,
+ 0x98, 0x47, 0x3E, 0x48, 0x3E, 0x49, 0xC1, 0x62, 0x3E, 0x49, 0x41, 0x61,
+ 0x3E, 0x49, 0xC1, 0x61, 0x3E, 0x49, 0x01, 0x62, 0x3E, 0x49, 0x81, 0x63,
+ 0x3E, 0x49, 0x41, 0x63, 0x3E, 0x49, 0x81, 0x60, 0x3E, 0x49, 0x01, 0x60,
+ 0x3E, 0x49, 0x0C, 0x70, 0x3E, 0x49, 0x0C, 0x60, 0xE8, 0x63, 0x04, 0xF0,
+ 0xEB, 0xFD, 0x3D, 0x48, 0xC1, 0x7C, 0xFA, 0x68, 0xC9, 0x09, 0x12, 0x7A,
+ 0xC9, 0x01, 0x52, 0x06, 0x52, 0x0E, 0x11, 0x43, 0xC1, 0x74, 0x39, 0x48,
+ 0x3A, 0x49, 0x04, 0x70, 0x38, 0x48, 0x04, 0x70, 0x0C, 0x48, 0x40, 0x30,
+ 0xC1, 0x60, 0x39, 0x48, 0x37, 0x49, 0x41, 0x61, 0x38, 0x49, 0xC1, 0x60,
+ 0x38, 0x49, 0xB9, 0x62, 0x38, 0x49, 0x81, 0x62, 0x38, 0x48, 0xB0, 0x63,
+ 0x38, 0x48, 0x04, 0x70, 0x38, 0x48, 0xB0, 0x62, 0x38, 0x48, 0xE8, 0x61,
+ 0xF8, 0xBD, 0x00, 0x00, 0x00, 0xFC, 0x01, 0x20, 0x45, 0x16, 0x00, 0x00,
+ 0x80, 0x05, 0x05, 0x00, 0x00, 0xC0, 0x01, 0x20, 0x81, 0x1B, 0x05, 0x00,
+ 0x08, 0x0E, 0x05, 0x00, 0x24, 0x0E, 0x05, 0x00, 0x3B, 0x11, 0x05, 0x00,
+ 0xDB, 0x3D, 0x08, 0x00, 0x55, 0x42, 0x08, 0x00, 0x90, 0x55, 0x08, 0x00,
+ 0x50, 0x7C, 0x04, 0x00, 0x34, 0x0E, 0x05, 0x00, 0x47, 0x6E, 0x04, 0x00,
+ 0xF1, 0x0D, 0x05, 0x00, 0x31, 0x2F, 0x08, 0x00, 0x8D, 0x30, 0x08, 0x00,
+ 0x87, 0x2F, 0x08, 0x00, 0xED, 0x30, 0x08, 0x00, 0x23, 0x31, 0x08, 0x00,
+ 0x01, 0x1B, 0x05, 0x00, 0x49, 0x31, 0x08, 0x00, 0x69, 0x31, 0x08, 0x00,
+ 0xC7, 0x31, 0x08, 0x00, 0xF0, 0x0D, 0x05, 0x00, 0xD3, 0x31, 0x08, 0x00,
+ 0x01, 0x32, 0x08, 0x00, 0x0D, 0x32, 0x08, 0x00, 0xF0, 0x54, 0x08, 0x00,
+ 0x8C, 0x57, 0x08, 0x00, 0x08, 0x56, 0x08, 0x00, 0xBC, 0x7E, 0x04, 0x00,
+ 0xD0, 0x0E, 0x05, 0x00, 0x09, 0x47, 0x08, 0x00, 0x1B, 0x1D, 0x05, 0x00,
+ 0x27, 0x1D, 0x05, 0x00, 0x43, 0x1D, 0x05, 0x00, 0x81, 0x1D, 0x05, 0x00,
+ 0xFF, 0x1D, 0x05, 0x00, 0xAB, 0x1D, 0x05, 0x00, 0x55, 0x1C, 0x05, 0x00,
+ 0xF8, 0x0D, 0x05, 0x00, 0x00, 0x0E, 0x05, 0x00, 0x40, 0xC1, 0x01, 0x20,
+ 0xDA, 0x0D, 0x05, 0x00, 0xDB, 0x0D, 0x05, 0x00, 0xAC, 0x57, 0x08, 0x00,
+ 0x5C, 0x54, 0x08, 0x00, 0x80, 0x04, 0x05, 0x00, 0x54, 0x54, 0x08, 0x00,
+ 0x30, 0x57, 0x08, 0x00, 0x7C, 0x54, 0x08, 0x00, 0x58, 0x56, 0x08, 0x00,
+ 0xF2, 0x0D, 0x05, 0x00, 0x20, 0x56, 0x08, 0x00, 0xD0, 0x56, 0x08, 0x00,
+ 0x70, 0xB5, 0x05, 0x46, 0x0C, 0x46, 0x16, 0x46, 0x02, 0xE0, 0x0F, 0xCC,
+ 0x0F, 0xC5, 0x10, 0x3E, 0x10, 0x2E, 0xFA, 0xD2, 0x08, 0x2E, 0x02, 0xD3,
+ 0x03, 0xCC, 0x03, 0xC5, 0x08, 0x3E, 0x04, 0x2E, 0x07, 0xD3, 0x01, 0xCC,
+ 0x01, 0xC5, 0x36, 0x1F, 0x03, 0xE0, 0x21, 0x78, 0x29, 0x70, 0x64, 0x1C,
+ 0x6D, 0x1C, 0x76, 0x1E, 0xF9, 0xD2, 0x70, 0xBD, 0x40, 0xBA, 0x70, 0x47,
+ 0x40, 0xBA, 0x70, 0x47, 0x40, 0xBA, 0x70, 0x47, 0x40, 0xBA, 0x70, 0x47,
+ 0xC0, 0xBA, 0x70, 0x47, 0xC0, 0xBA, 0x70, 0x47, 0xC0, 0xBA, 0x70, 0x47,
+ 0xC0, 0xBA, 0x70, 0x47, 0xF0, 0xB5, 0x01, 0x26, 0x32, 0x46, 0x82, 0x40,
+ 0x21, 0x4C, 0x45, 0x00, 0x03, 0x20, 0x23, 0x7B, 0x92, 0xB2, 0xA8, 0x40,
+ 0x93, 0x43, 0x23, 0x73, 0x1D, 0x4F, 0x0B, 0x00, 0x40, 0x3F, 0x05, 0xF0,
+ 0x6E, 0xF8, 0x06, 0x04, 0x07, 0x0E, 0x28, 0x14, 0x1D, 0x28, 0x39, 0x8A,
+ 0x91, 0x43, 0x02, 0xE0, 0xB9, 0x8A, 0x11, 0x43, 0x05, 0xE0, 0x39, 0x82,
+ 0x21, 0x7C, 0x81, 0x43, 0x17, 0xE0, 0xB9, 0x8A, 0x91, 0x43, 0xB9, 0x82,
+ 0x39, 0x8A, 0x11, 0x43, 0xF5, 0xE7, 0x39, 0x8A, 0x91, 0x43, 0x39, 0x82,
+ 0x21, 0x7C, 0x81, 0x43, 0x02, 0x20, 0xA8, 0x40, 0x01, 0x43, 0x08, 0xE0,
+ 0xB9, 0x8A, 0x91, 0x43, 0xB9, 0x82, 0x39, 0x8A, 0x11, 0x43, 0x39, 0x82,
+ 0x21, 0x7C, 0x81, 0x43, 0x01, 0x43, 0x21, 0x74, 0xF0, 0xBD, 0x39, 0x8A,
+ 0x91, 0x43, 0x39, 0x82, 0x21, 0x7C, 0xAE, 0x40, 0x81, 0x43, 0x31, 0x43,
+ 0xF5, 0xE7, 0x03, 0x49, 0x0B, 0x7B, 0x01, 0x22, 0x82, 0x40, 0x13, 0x43,
+ 0x0B, 0x73, 0x70, 0x47, 0x40, 0xA0, 0x01, 0x20, 0x2F, 0x49, 0x0A, 0x78,
+ 0x52, 0x08, 0x52, 0x00, 0x0A, 0x70, 0x00, 0x28, 0x03, 0xD0, 0x08, 0x78,
+ 0x04, 0x22, 0x10, 0x43, 0x08, 0x70, 0x02, 0x20, 0x08, 0x72, 0x2A, 0x49,
+ 0x00, 0x20, 0x48, 0x60, 0x48, 0x70, 0x70, 0x47, 0xF8, 0xB5, 0x26, 0x4C,
+ 0x05, 0x21, 0x61, 0x70, 0xC8, 0x21, 0x21, 0x70, 0x88, 0x21, 0x21, 0x70,
+ 0x00, 0x25, 0xA5, 0x70, 0x65, 0x81, 0x22, 0x4E, 0x22, 0x4F, 0xB0, 0x60,
+ 0x39, 0x46, 0x04, 0xF0, 0x65, 0xFF, 0x40, 0x1E, 0xA0, 0x81, 0xE5, 0x81,
+ 0x25, 0x82, 0xA5, 0x74, 0xA5, 0x82, 0x00, 0x20, 0xFF, 0xF7, 0xD4, 0xFF,
+ 0xA0, 0x79, 0x20, 0x72, 0x02, 0x20, 0x20, 0x71, 0x01, 0x20, 0x30, 0x70,
+ 0x19, 0x48, 0x45, 0x60, 0x07, 0x60, 0x85, 0x60, 0xF8, 0xBD, 0x17, 0x49,
+ 0x0E, 0xC9, 0x0E, 0xC0, 0x70, 0x47, 0x12, 0x49, 0x00, 0x28, 0x48, 0x78,
+ 0x03, 0xD0, 0x80, 0x22, 0x10, 0x43, 0x48, 0x70, 0x70, 0x47, 0x40, 0x06,
+ 0x40, 0x0E, 0xFA, 0xE7, 0x0C, 0x49, 0x00, 0x28, 0x48, 0x78, 0x03, 0xD0,
+ 0x02, 0x22, 0x10, 0x43, 0x48, 0x70, 0x70, 0x47, 0xFD, 0x22, 0x10, 0x40,
+ 0xFA, 0xE7, 0x07, 0x48, 0x01, 0x78, 0x05, 0x22, 0x11, 0x43, 0x01, 0x70,
+ 0x70, 0x47, 0x01, 0x20, 0xA6, 0xE7, 0x04, 0x49, 0x08, 0x70, 0x70, 0x47,
+ 0x02, 0x48, 0x00, 0x78, 0x70, 0x47, 0x00, 0x00, 0x00, 0x58, 0x01, 0x20,
+ 0xA4, 0x0D, 0x05, 0x00, 0x40, 0x42, 0x0F, 0x00, 0x28, 0x0E, 0x05, 0x00,
+ 0x10, 0xB5, 0x72, 0xB6, 0x1B, 0x48, 0x1C, 0x4C, 0x90, 0x21, 0x60, 0x60,
+ 0x1B, 0x48, 0x04, 0xF0, 0x0D, 0xFF, 0x1B, 0x48, 0x81, 0x68, 0x19, 0x48,
+ 0xFF, 0x31, 0x21, 0x31, 0xCA, 0x7A, 0x20, 0x30, 0x13, 0x07, 0x1B, 0x0F,
+ 0x03, 0x72, 0x12, 0x09, 0xC2, 0x71, 0x42, 0x71, 0x8A, 0x7A, 0x02, 0x71,
+ 0x1C, 0x22, 0x42, 0x72, 0x4A, 0x7B, 0x13, 0x07, 0x1B, 0x0F, 0x03, 0x77,
+ 0x12, 0x09, 0xC2, 0x76, 0x42, 0x76, 0x0A, 0x7B, 0x02, 0x76, 0x1A, 0x22,
+ 0x42, 0x77, 0x62, 0xB6, 0x48, 0x7A, 0x41, 0x00, 0x40, 0x18, 0xE0, 0x60,
+ 0x01, 0x20, 0x20, 0x70, 0x00, 0x20, 0xA0, 0x60, 0x60, 0x80, 0x10, 0xBD,
+ 0x05, 0x48, 0x01, 0x78, 0x01, 0x29, 0x02, 0xD0, 0x01, 0x21, 0x01, 0x70,
+ 0x70, 0x47, 0x02, 0x21, 0xFB, 0xE7, 0x00, 0x00, 0x10, 0x54, 0x08, 0x00,
+ 0xB0, 0x0D, 0x05, 0x00, 0x3C, 0x0F, 0x05, 0x00, 0x80, 0x05, 0x05, 0x00,
+ 0x70, 0xB5, 0x06, 0x46, 0x6E, 0x4C, 0x06, 0x20, 0xA0, 0x47, 0x07, 0x20,
+ 0xA0, 0x47, 0xDC, 0x21, 0x6C, 0x48, 0x04, 0xF0, 0xC9, 0xFE, 0x6C, 0x4D,
+ 0x6C, 0x4C, 0xA9, 0x68, 0xFF, 0x31, 0x21, 0x31, 0x4A, 0x79, 0x48, 0x7A,
+ 0x12, 0x18, 0x22, 0x80, 0x89, 0x79, 0x08, 0x18, 0x60, 0x80, 0x65, 0x48,
+ 0x80, 0x30, 0x86, 0x64, 0xFF, 0xF7, 0xA2, 0xFF, 0x00, 0x20, 0xA0, 0x60,
+ 0x60, 0x60, 0xA9, 0x68, 0x01, 0x23, 0xFF, 0x31, 0x21, 0x31, 0x0A, 0x7A,
+ 0x08, 0xE0, 0x61, 0x68, 0x49, 0x00, 0x19, 0x43, 0x61, 0x60, 0xA1, 0x68,
+ 0x49, 0x00, 0x19, 0x43, 0x40, 0x1C, 0xA1, 0x60, 0x82, 0x42, 0xF4, 0xD8,
+ 0x5B, 0x49, 0x10, 0x20, 0x08, 0x60, 0xBF, 0xF3, 0x4F, 0x8F, 0xBF, 0xF3,
+ 0x6F, 0x8F, 0x59, 0x48, 0x86, 0x21, 0x01, 0x74, 0x00, 0x21, 0xC9, 0x43,
+ 0x41, 0x82, 0x68, 0x69, 0x20, 0x21, 0x88, 0x43, 0x68, 0x61, 0x68, 0x69,
+ 0x40, 0x22, 0x90, 0x43, 0x68, 0x61, 0x68, 0x69, 0x80, 0x22, 0x90, 0x43,
+ 0x68, 0x61, 0x68, 0x69, 0xCA, 0x00, 0x90, 0x43, 0x68, 0x61, 0x68, 0x69,
+ 0x0A, 0x01, 0x90, 0x43, 0x68, 0x61, 0x4D, 0x4A, 0x10, 0x68, 0x88, 0x43,
+ 0x03, 0x21, 0xC9, 0x03, 0x88, 0x43, 0x10, 0x60, 0x70, 0xBD, 0x70, 0xB5,
+ 0x43, 0x4C, 0xC0, 0x34, 0xE0, 0x7D, 0x00, 0x28, 0x13, 0xD1, 0x42, 0x4D,
+ 0xA8, 0x68, 0xE0, 0x30, 0x40, 0x7C, 0x80, 0x06, 0x01, 0xD5, 0xD0, 0xF7,
+ 0xA0, 0xFF, 0xA8, 0x68, 0xE0, 0x30, 0x40, 0x7C, 0x40, 0x06, 0x01, 0xD5,
+ 0xD0, 0xF7, 0x86, 0xFF, 0x3D, 0x49, 0x87, 0x20, 0x08, 0x74, 0x01, 0x20,
+ 0xE0, 0x75, 0x70, 0xBD, 0x01, 0x20, 0x3C, 0x49, 0x40, 0x02, 0xC8, 0x60,
+ 0x70, 0x47, 0xFF, 0x20, 0x39, 0x49, 0x01, 0x30, 0xC8, 0x60, 0x70, 0x47,
+ 0xF8, 0xB5, 0x30, 0x4F, 0x31, 0x4E, 0x0D, 0x46, 0x04, 0x00, 0x06, 0xD0,
+ 0x01, 0x46, 0x2D, 0x4B, 0x06, 0x20, 0x34, 0x4A, 0x2A, 0x3B, 0x98, 0x47,
+ 0x06, 0xE0, 0x06, 0x20, 0xB8, 0x47, 0x70, 0x69, 0xFF, 0x21, 0x01, 0x31,
+ 0x88, 0x43, 0x70, 0x61, 0x00, 0x2D, 0x06, 0xD0, 0x25, 0x4B, 0x61, 0x19,
+ 0x07, 0x20, 0x2D, 0x4A, 0x2A, 0x3B, 0x98, 0x47, 0xF8, 0xBD, 0x07, 0x20,
+ 0xB8, 0x47, 0x70, 0x69, 0x01, 0x21, 0x49, 0x02, 0x88, 0x43, 0x70, 0x61,
+ 0xF8, 0xBD, 0x1F, 0x48, 0x80, 0x30, 0x80, 0x6C, 0x58, 0xE7, 0x1D, 0x49,
+ 0x00, 0x20, 0x80, 0x31, 0x88, 0x84, 0x40, 0x31, 0x08, 0x80, 0x70, 0x47,
+ 0x19, 0x48, 0x19, 0x49, 0x80, 0x30, 0xC0, 0x31, 0x80, 0x8C, 0x09, 0x88,
+ 0x88, 0x42, 0x00, 0xD8, 0x08, 0x46, 0x70, 0x47, 0x10, 0xB5, 0x19, 0x49,
+ 0x08, 0x68, 0xC2, 0x07, 0x20, 0xD0, 0x40, 0x08, 0x40, 0x00, 0x08, 0x60,
+ 0x00, 0xF0, 0xA1, 0xF9, 0x10, 0x48, 0x40, 0x68, 0xFF, 0x30, 0x21, 0x30,
+ 0xC0, 0x7A, 0x40, 0x07, 0xC4, 0x0F, 0x15, 0x48, 0xC0, 0x7D, 0xA0, 0x42,
+ 0x07, 0xD0, 0x20, 0x46, 0x01, 0x21, 0x48, 0x40, 0xFF, 0xF7, 0xDD, 0xFE,
+ 0x20, 0x46, 0x00, 0xF0, 0x33, 0xFB, 0x0F, 0x48, 0x00, 0x23, 0x40, 0x38,
+ 0x81, 0x8D, 0x40, 0x38, 0x40, 0x6E, 0x1A, 0x46, 0x00, 0xF0, 0x51, 0xFA,
+ 0x10, 0xBD, 0x00, 0x00, 0x77, 0x0A, 0x04, 0x00, 0x3C, 0x0F, 0x05, 0x00,
+ 0x80, 0x05, 0x05, 0x00, 0xC0, 0x0D, 0x05, 0x00, 0x80, 0xE1, 0x00, 0xE0,
+ 0x00, 0x80, 0x01, 0x20, 0x08, 0x0E, 0x05, 0x00, 0x00, 0xFC, 0x01, 0x20,
+ 0xD3, 0x05, 0x08, 0x00, 0xC9, 0x05, 0x08, 0x00, 0x3C, 0x10, 0x05, 0x00,
+ 0x70, 0xB5, 0x00, 0x20, 0xFF, 0xF7, 0xA2, 0xFE, 0x01, 0x20, 0xFF, 0xF7,
+ 0x94, 0xFE, 0xFF, 0xF7, 0xAE, 0xFE, 0xFF, 0xF7, 0xA2, 0xFF, 0xFF, 0x4D,
+ 0x01, 0x24, 0xE0, 0xB2, 0xA8, 0x47, 0x64, 0x1C, 0x09, 0x2C, 0xFA, 0xD3,
+ 0xFB, 0x4D, 0x00, 0x24, 0xBC, 0x35, 0xE0, 0xB2, 0xA8, 0x47, 0x64, 0x1C,
+ 0x08, 0x2C, 0xFA, 0xD3, 0x70, 0xBD, 0x70, 0xB5, 0xF7, 0x48, 0x42, 0x7D,
+ 0x01, 0x21, 0x0A, 0x43, 0x42, 0x75, 0x02, 0x22, 0x02, 0x71, 0xF4, 0x48,
+ 0x00, 0x25, 0x40, 0x30, 0xC5, 0x71, 0x01, 0x73, 0xC5, 0x72, 0x5F, 0x21,
+ 0x01, 0x72, 0xF0, 0x4C, 0x20, 0x34, 0x60, 0x78, 0x80, 0x21, 0x08, 0x43,
+ 0x60, 0x70, 0x3F, 0x20, 0xE0, 0x70, 0xA0, 0x7B, 0x30, 0x21, 0x08, 0x43,
+ 0xA0, 0x73, 0x00, 0x20, 0xFF, 0xF7, 0x6C, 0xFE, 0x20, 0x78, 0x10, 0x21,
+ 0x08, 0x43, 0x20, 0x70, 0x01, 0x20, 0xFF, 0xF7, 0x5A, 0xFE, 0x0B, 0x20,
+ 0xA0, 0x71, 0xA5, 0x71, 0x70, 0xBD, 0x10, 0xB5, 0xFF, 0xF7, 0xD3, 0xFF,
+ 0xE1, 0x49, 0xC0, 0x20, 0x40, 0x31, 0xC8, 0x72, 0xDF, 0x48, 0x20, 0x30,
+ 0x81, 0x7B, 0xCF, 0x22, 0x11, 0x40, 0x81, 0x73, 0xBF, 0x21, 0xC1, 0x70,
+ 0xDC, 0x48, 0xFF, 0xF7, 0xB3, 0xFE, 0x00, 0x20, 0x04, 0xF0, 0x60, 0xF9,
+ 0x01, 0x20, 0x03, 0xF0, 0xE7, 0xFF, 0x19, 0x20, 0xD8, 0x49, 0x00, 0x02,
+ 0x88, 0x47, 0x10, 0xBD, 0x10, 0xB5, 0xFF, 0xF7, 0xB6, 0xFF, 0x19, 0x20,
+ 0xD4, 0x49, 0x00, 0x02, 0x88, 0x47, 0xD2, 0x48, 0xFF, 0xF7, 0x9E, 0xFE,
+ 0x00, 0x20, 0x04, 0xF0, 0x4B, 0xF9, 0x00, 0x20, 0x03, 0xF0, 0xD2, 0xFF,
+ 0x10, 0xBD, 0x10, 0xB5, 0xCE, 0x4C, 0xA0, 0x68, 0xE0, 0x30, 0x41, 0x7E,
+ 0x02, 0x7E, 0x08, 0x02, 0x61, 0x68, 0x10, 0x43, 0xFF, 0x31, 0x03, 0x31,
+ 0x08, 0x74, 0x00, 0x0A, 0x48, 0x74, 0xA0, 0x68, 0xE2, 0x30, 0x41, 0x7E,
+ 0x02, 0x7E, 0x08, 0x02, 0x61, 0x68, 0x10, 0x43, 0xFF, 0x31, 0x01, 0x31,
+ 0x08, 0x75, 0x00, 0x0A, 0x48, 0x75, 0xA0, 0x68, 0xE0, 0x30, 0x41, 0x7F,
+ 0x02, 0x7F, 0x08, 0x02, 0x61, 0x68, 0x10, 0x43, 0xFF, 0x31, 0x03, 0x31,
+ 0x08, 0x75, 0x00, 0x0A, 0x48, 0x75, 0xA0, 0x68, 0xFF, 0x30, 0x01, 0x30,
+ 0x41, 0x78, 0x60, 0x68, 0xFF, 0x30, 0x01, 0x30, 0xC1, 0x76, 0xA0, 0x68,
+ 0xFF, 0x30, 0x01, 0x30, 0x01, 0x7A, 0x60, 0x68, 0xFF, 0x30, 0x21, 0x30,
+ 0x41, 0x71, 0xA0, 0x68, 0xE0, 0x30, 0x81, 0x7F, 0x60, 0x68, 0xFF, 0x30,
+ 0x01, 0x30, 0x01, 0x76, 0xA0, 0x68, 0xE0, 0x30, 0xC1, 0x7F, 0x60, 0x68,
+ 0xFF, 0x30, 0x01, 0x30, 0x41, 0x76, 0xA0, 0x68, 0xFF, 0x30, 0x01, 0x30,
+ 0x01, 0x78, 0x60, 0x68, 0xFF, 0x30, 0x01, 0x30, 0x81, 0x76, 0xA0, 0x68,
+ 0xFB, 0x22, 0xFF, 0x30, 0x01, 0x30, 0x81, 0x78, 0x60, 0x68, 0xFF, 0x30,
+ 0x01, 0x30, 0x01, 0x77, 0x61, 0x68, 0xFF, 0x31, 0x21, 0x31, 0xC8, 0x7A,
+ 0x10, 0x40, 0xA2, 0x68, 0xFF, 0x32, 0x01, 0x32, 0x52, 0x7F, 0x52, 0x07,
+ 0xD2, 0x0F, 0x92, 0x00, 0x10, 0x43, 0xC8, 0x72, 0xA2, 0x68, 0x61, 0x68,
+ 0xFF, 0x32, 0x01, 0x32, 0x52, 0x7F, 0x80, 0x08, 0x92, 0x07, 0x80, 0x00,
+ 0x92, 0x0F, 0xFF, 0x31, 0x10, 0x43, 0x21, 0x31, 0xC8, 0x72, 0xA0, 0x68,
+ 0xFF, 0x30, 0x01, 0x30, 0x81, 0x7F, 0x60, 0x68, 0xFF, 0x30, 0x21, 0x30,
+ 0x81, 0x71, 0xA0, 0x68, 0xFF, 0x30, 0x01, 0x30, 0x01, 0x79, 0x60, 0x68,
+ 0xFF, 0x30, 0x01, 0x30, 0x41, 0x77, 0xA0, 0x68, 0xFF, 0x30, 0x01, 0x30,
+ 0x41, 0x79, 0x60, 0x68, 0xFF, 0x30, 0x01, 0x30, 0x81, 0x77, 0xA0, 0x68,
+ 0xFF, 0x30, 0x01, 0x30, 0x81, 0x79, 0x60, 0x68, 0xFF, 0x30, 0x01, 0x30,
+ 0xC1, 0x77, 0xA0, 0x68, 0xFF, 0x30, 0x01, 0x30, 0xC1, 0x7E, 0x60, 0x68,
+ 0xFF, 0x30, 0x21, 0x30, 0x01, 0x70, 0xA0, 0x68, 0x12, 0x22, 0xFF, 0x30,
+ 0x01, 0x30, 0x01, 0x7F, 0x60, 0x68, 0xFF, 0x30, 0x21, 0x30, 0x41, 0x70,
+ 0xA1, 0x68, 0x60, 0x68, 0xFF, 0x31, 0xB6, 0x30, 0x47, 0x31, 0x04, 0xF0,
+ 0x64, 0xFC, 0x7B, 0x49, 0x60, 0x68, 0xC0, 0x39, 0xB6, 0x30, 0x48, 0x62,
+ 0x10, 0xBD, 0x0E, 0xB5, 0x78, 0x48, 0xFF, 0xF7, 0x4D, 0xFD, 0x73, 0x49,
+ 0x04, 0x20, 0x20, 0x31, 0x48, 0x74, 0x74, 0x48, 0x75, 0x49, 0x80, 0x68,
+ 0xFF, 0x30, 0x41, 0x30, 0x40, 0x79, 0x02, 0x28, 0x00, 0xD2, 0x02, 0x20,
+ 0x88, 0x74, 0x00, 0x20, 0x01, 0x90, 0x02, 0x90, 0x70, 0x48, 0x69, 0x46,
+ 0x40, 0x6E, 0x00, 0x90, 0x6E, 0x48, 0x01, 0x22, 0x40, 0x30, 0x80, 0x8D,
+ 0x88, 0x80, 0x00, 0x21, 0x68, 0x46, 0xD0, 0xF7, 0x83, 0xFB, 0x01, 0x21,
+ 0x6A, 0x48, 0xC9, 0x05, 0x01, 0x60, 0x6A, 0x48, 0x02, 0x68, 0x12, 0x02,
+ 0x12, 0x0A, 0x43, 0x07, 0x1A, 0x43, 0x02, 0x60, 0x67, 0x48, 0x01, 0x60,
+ 0x0E, 0xBD, 0x60, 0x48, 0x42, 0x68, 0xD0, 0x1D, 0xFB, 0x30, 0x41, 0x7C,
+ 0x03, 0x7C, 0x08, 0x02, 0x18, 0x43, 0x5F, 0x49, 0x00, 0x01, 0xC8, 0x65,
+ 0xD0, 0x1D, 0xF9, 0x30, 0x42, 0x7D, 0x03, 0x7D, 0x12, 0x02, 0x1A, 0x43,
+ 0x12, 0x01, 0x0A, 0x66, 0x01, 0x7E, 0x0A, 0x01, 0x58, 0x49, 0x40, 0x31,
+ 0x0A, 0x85, 0x40, 0x7E, 0x00, 0x01, 0x48, 0x85, 0x70, 0x47, 0x10, 0xB5,
+ 0xFF, 0xF7, 0xE1, 0xFF, 0x50, 0x48, 0x41, 0x68, 0xC8, 0x1D, 0xFB, 0x30,
+ 0x42, 0x7D, 0x03, 0x7D, 0x10, 0x02, 0x18, 0x43, 0x02, 0x01, 0x4F, 0x48,
+ 0xFF, 0x31, 0x01, 0x31, 0x42, 0x66, 0x88, 0x7E, 0x01, 0x01, 0x4C, 0x48,
+ 0x40, 0x30, 0x81, 0x85, 0x10, 0xBD, 0x4A, 0x48, 0x10, 0xB5, 0xFF, 0x21,
+ 0x1D, 0x31, 0x80, 0x38, 0x04, 0xF0, 0x54, 0xFC, 0xFF, 0xF7, 0xE7, 0xFE,
+ 0xFF, 0xF7, 0xDF, 0xFF, 0xFF, 0xF7, 0x91, 0xFF, 0x40, 0x48, 0x3D, 0x4C,
+ 0xC0, 0x68, 0xC1, 0x7E, 0xC9, 0x06, 0xC9, 0x0E, 0x61, 0x74, 0x00, 0x7F,
+ 0x40, 0x06, 0x40, 0x0E, 0xE0, 0x74, 0x03, 0xF0, 0xC4, 0xFE, 0x01, 0xF0,
+ 0x7B, 0xF9, 0x60, 0x7D, 0xEF, 0x21, 0x08, 0x40, 0x60, 0x75, 0x3E, 0x49,
+ 0x60, 0x15, 0x88, 0x60, 0x10, 0xBD, 0x10, 0xB5, 0x34, 0x4C, 0x20, 0x69,
+ 0xC0, 0x09, 0xC0, 0x07, 0x04, 0xD0, 0xD1, 0xF7, 0xF5, 0xFA, 0x39, 0x49,
+ 0x10, 0x20, 0x88, 0x47, 0x20, 0x69, 0x80, 0x0F, 0xC0, 0x07, 0x06, 0xD0,
+ 0x20, 0x69, 0x01, 0x21, 0x89, 0x07, 0x88, 0x43, 0x20, 0x61, 0xFF, 0xF7,
+ 0x94, 0xFF, 0x10, 0xBD, 0x10, 0xB5, 0x08, 0x20, 0x04, 0xF0, 0x44, 0xFA,
+ 0x27, 0x48, 0x40, 0x22, 0x40, 0x68, 0xFF, 0x30, 0x01, 0x30, 0x01, 0x78,
+ 0x11, 0x43, 0x01, 0x70, 0x10, 0xBD, 0x7F, 0xB5, 0x22, 0x4C, 0x05, 0x46,
+ 0x61, 0x68, 0xFF, 0x31, 0x81, 0x31, 0x08, 0x78, 0xFF, 0x28, 0x08, 0xD0,
+ 0x55, 0x28, 0x06, 0xD0, 0x33, 0x28, 0x04, 0xD0, 0x00, 0x28, 0x04, 0xD1,
+ 0x49, 0x78, 0x00, 0x29, 0x01, 0xD0, 0x01, 0x22, 0x03, 0xE0, 0x22, 0x49,
+ 0x88, 0x47, 0x80, 0x1C, 0xC2, 0xB2, 0x00, 0x20, 0x00, 0x90, 0x01, 0x90,
+ 0x02, 0x90, 0x03, 0x90, 0x68, 0x46, 0x02, 0x70, 0x61, 0x68, 0x40, 0x1C,
+ 0xFF, 0x31, 0x81, 0x31, 0x04, 0xF0, 0x8F, 0xFB, 0x29, 0x46, 0x68, 0x46,
+ 0x04, 0xF0, 0xAD, 0xF8, 0x04, 0xB0, 0x70, 0xBD, 0x10, 0xB5, 0x17, 0x4C,
+ 0x20, 0x78, 0xC0, 0x08, 0xC0, 0x07, 0x08, 0xD0, 0x15, 0x48, 0xFF, 0xF7,
+ 0xCC, 0xFF, 0x00, 0x28, 0x03, 0xD0, 0x20, 0x68, 0x08, 0x21, 0x88, 0x43,
+ 0x20, 0x60, 0x06, 0x4C, 0x20, 0x69, 0xC0, 0x09, 0xC0, 0x07, 0x1F, 0xE0,
+ 0x77, 0x0A, 0x04, 0x00, 0x40, 0xC1, 0x01, 0x20, 0x7F, 0x1E, 0x08, 0x00,
+ 0x51, 0x01, 0x04, 0x00, 0x80, 0x05, 0x05, 0x00, 0x00, 0x90, 0xD0, 0x03,
+ 0x00, 0x58, 0x01, 0x20, 0xBC, 0x0F, 0x05, 0x00, 0x80, 0xE2, 0x00, 0xE0,
+ 0x14, 0xE4, 0x00, 0xE0, 0x00, 0xE1, 0x00, 0xE0, 0x00, 0xFC, 0x01, 0x20,
+ 0x99, 0x5E, 0x04, 0x00, 0xAD, 0x55, 0x04, 0x00, 0x08, 0x0E, 0x05, 0x00,
+ 0x49, 0x0A, 0x08, 0x00, 0x09, 0xD0, 0x00, 0xF0, 0x7A, 0xF9, 0x00, 0xF0,
+ 0x3F, 0xF9, 0x00, 0xF0, 0x06, 0xF9, 0x20, 0x69, 0x80, 0x21, 0x88, 0x43,
+ 0x20, 0x61, 0xA0, 0x6A, 0xC0, 0x6C, 0x80, 0x47, 0x10, 0xBD, 0xFE, 0xB5,
+ 0x05, 0x46, 0x1F, 0x46, 0x16, 0x46, 0x0C, 0x46, 0x68, 0x46, 0xFF, 0xF7,
+ 0x56, 0xFC, 0x68, 0x46, 0x00, 0x95, 0x84, 0x80, 0x06, 0x72, 0x01, 0x22,
+ 0x39, 0x46, 0xD0, 0xF7, 0x7D, 0xFA, 0x28, 0x03, 0xCA, 0x4A, 0x01, 0x0C,
+ 0x50, 0x68, 0x82, 0x30, 0x01, 0x76, 0x09, 0x0A, 0x41, 0x76, 0x51, 0x68,
+ 0x20, 0x09, 0xFF, 0x31, 0x21, 0x31, 0x88, 0x72, 0xFE, 0xBD, 0xF0, 0xB5,
+ 0xC3, 0x49, 0xFF, 0x22, 0x48, 0x68, 0x02, 0x32, 0x12, 0x5C, 0x52, 0x07,
+ 0x0B, 0xD4, 0xFF, 0x30, 0x00, 0x22, 0x21, 0x30, 0xC2, 0x71, 0x48, 0x68,
+ 0xFF, 0x30, 0x21, 0x30, 0x02, 0x72, 0x48, 0x68, 0xFF, 0x30, 0x21, 0x30,
+ 0x42, 0x72, 0xBB, 0x48, 0x10, 0x24, 0x00, 0x7A, 0x20, 0x27, 0x01, 0x22,
+ 0x08, 0x25, 0x04, 0x23, 0x35, 0x28, 0x6E, 0xD0, 0x1E, 0xDC, 0x40, 0x26,
+ 0x22, 0x28, 0x26, 0xD0, 0x0C, 0xDC, 0x12, 0x28, 0x59, 0xD0, 0x04, 0xDC,
+ 0x10, 0x28, 0x50, 0xD0, 0x11, 0x28, 0x7C, 0xD1, 0x4D, 0xE0, 0x20, 0x28,
+ 0x58, 0xD0, 0x21, 0x28, 0x77, 0xD1, 0x5D, 0xE0, 0x80, 0x24, 0x25, 0x28,
+ 0x61, 0xD0, 0x04, 0xDC, 0x23, 0x28, 0x6A, 0xD0, 0x24, 0x28, 0x6E, 0xD1,
+ 0x4C, 0xE0, 0x33, 0x28, 0x5F, 0xD0, 0x34, 0x28, 0x69, 0xD1, 0x19, 0xE0,
+ 0x02, 0x26, 0xE5, 0x28, 0x2A, 0xD0, 0x0C, 0xDC, 0xE2, 0x28, 0x13, 0xD0,
+ 0x04, 0xDC, 0x36, 0x28, 0x59, 0xD0, 0xE1, 0x28, 0x5D, 0xD1, 0x6B, 0xE0,
+ 0xE3, 0x28, 0x0B, 0xD0, 0xE4, 0x28, 0x58, 0xD1, 0x08, 0xE0, 0xE6, 0x28,
+ 0x49, 0xD0, 0xE7, 0x28, 0x11, 0xD0, 0xE8, 0x28, 0x1D, 0xD0, 0xE9, 0x28,
+ 0x4F, 0xD1, 0x06, 0xE0, 0x4A, 0x68, 0xFF, 0x32, 0x21, 0x32, 0xD5, 0x79,
+ 0x25, 0x43, 0xD5, 0x71, 0x47, 0xE0, 0x4A, 0x68, 0xFF, 0x32, 0x21, 0x32,
+ 0xD4, 0x79, 0x3C, 0x43, 0x04, 0xE0, 0x4A, 0x68, 0xFF, 0x32, 0x21, 0x32,
+ 0xD4, 0x79, 0x1C, 0x43, 0xD4, 0x71, 0x3A, 0xE0, 0x4C, 0x68, 0xFF, 0x34,
+ 0x21, 0x34, 0xE5, 0x79, 0x15, 0x43, 0xE5, 0x71, 0x33, 0xE0, 0x4A, 0x68,
+ 0xFF, 0x32, 0x21, 0x32, 0xD4, 0x79, 0x2C, 0x43, 0xF0, 0xE7, 0x4A, 0x68,
+ 0xFF, 0x32, 0x21, 0x32, 0x14, 0x7A, 0x1C, 0x43, 0x04, 0xE0, 0x4A, 0x68,
+ 0xFF, 0x32, 0x21, 0x32, 0x14, 0x7A, 0x2C, 0x43, 0x14, 0x72, 0x20, 0xE0,
+ 0x4A, 0x68, 0xFF, 0x32, 0x21, 0x32, 0x15, 0x7A, 0x25, 0x43, 0x15, 0x72,
+ 0x19, 0xE0, 0x12, 0xE0, 0x4C, 0x68, 0xFF, 0x34, 0x21, 0x34, 0x65, 0x7A,
+ 0x15, 0x43, 0x65, 0x72, 0x11, 0xE0, 0x4A, 0x68, 0xFF, 0x32, 0x21, 0x32,
+ 0x14, 0x7A, 0x3C, 0x43, 0xE8, 0xE7, 0x4A, 0x68, 0xFF, 0x32, 0x21, 0x32,
+ 0xD4, 0x79, 0x34, 0x43, 0xC8, 0xE7, 0x4C, 0x68, 0xFF, 0x34, 0x21, 0x34,
+ 0x25, 0x7A, 0x15, 0x43, 0x25, 0x72, 0x10, 0x28, 0x0B, 0xD0, 0x11, 0x28,
+ 0x09, 0xD0, 0x12, 0x28, 0x07, 0xD0, 0xE0, 0x28, 0x05, 0xD0, 0x48, 0x68,
+ 0xFF, 0x30, 0x01, 0x30, 0x41, 0x78, 0x19, 0x43, 0x41, 0x70, 0xF0, 0xBD,
+ 0x4A, 0x68, 0xFF, 0x32, 0x21, 0x32, 0x14, 0x7A, 0x34, 0x43, 0xC7, 0xE7,
+ 0x68, 0x49, 0x00, 0x28, 0x01, 0xD0, 0xFF, 0x22, 0x00, 0xE0, 0xBF, 0x22,
+ 0xCA, 0x70, 0x63, 0x4A, 0x51, 0x68, 0xFF, 0x31, 0x01, 0x31, 0x8B, 0x7B,
+ 0x5B, 0x08, 0x5B, 0x00, 0x03, 0x43, 0x8B, 0x73, 0x5F, 0x49, 0xCB, 0x7D,
+ 0x83, 0x42, 0x0F, 0xD0, 0xC8, 0x75, 0x51, 0x68, 0xFF, 0x31, 0x01, 0x31,
+ 0x8B, 0x7B, 0x5B, 0x08, 0x5B, 0x00, 0x03, 0x43, 0x8B, 0x73, 0x50, 0x68,
+ 0x01, 0x22, 0xFF, 0x30, 0x01, 0x30, 0x81, 0x78, 0x11, 0x43, 0x81, 0x70,
+ 0x70, 0x47, 0x10, 0xB5, 0x53, 0x4B, 0x58, 0x68, 0xFF, 0x30, 0x01, 0x30,
+ 0xC2, 0x7F, 0x00, 0x2A, 0x23, 0xD0, 0x53, 0x49, 0x1C, 0x24, 0x0C, 0x5F,
+ 0x0A, 0x21, 0x4A, 0x43, 0x4E, 0x49, 0x94, 0x42, 0x1F, 0xDD, 0x9C, 0x68,
+ 0x0A, 0x7B, 0xFF, 0x34, 0x01, 0x34, 0xE4, 0x79, 0x24, 0x09, 0xA2, 0x42,
+ 0x14, 0xD9, 0x01, 0x7B, 0x01, 0x22, 0x11, 0x43, 0x01, 0x73, 0x58, 0x68,
+ 0xFF, 0x30, 0x01, 0x30, 0x01, 0x78, 0x11, 0x43, 0x01, 0x70, 0x98, 0x68,
+ 0xE0, 0x30, 0x00, 0x7C, 0x40, 0x06, 0x04, 0xD5, 0x44, 0x48, 0x12, 0x02,
+ 0x01, 0x68, 0x11, 0x43, 0x01, 0x60, 0x10, 0xBD, 0x52, 0x1C, 0x0A, 0x73,
+ 0x10, 0xBD, 0x00, 0x22, 0x0A, 0x73, 0x01, 0x7B, 0x49, 0x08, 0x49, 0x00,
+ 0x01, 0x73, 0x10, 0xBD, 0x10, 0xB5, 0x38, 0x4B, 0x58, 0x68, 0xFF, 0x30,
+ 0x01, 0x30, 0x82, 0x7F, 0x00, 0x2A, 0x25, 0xD0, 0x37, 0x49, 0x02, 0x24,
+ 0x20, 0x31, 0x0C, 0x5F, 0x64, 0x21, 0x4A, 0x43, 0x32, 0x49, 0x94, 0x42,
+ 0x20, 0xDD, 0x9C, 0x68, 0xCA, 0x7A, 0xFF, 0x34, 0x01, 0x34, 0xE4, 0x79,
+ 0x24, 0x07, 0x24, 0x0F, 0xA2, 0x42, 0x14, 0xD9, 0x02, 0x7B, 0x02, 0x21,
+ 0x0A, 0x43, 0x02, 0x73, 0x58, 0x68, 0xFF, 0x30, 0x01, 0x30, 0x02, 0x78,
+ 0x0A, 0x43, 0x02, 0x70, 0x98, 0x68, 0xE0, 0x30, 0x00, 0x7C, 0x80, 0x06,
+ 0x04, 0xD5, 0x28, 0x49, 0x80, 0x22, 0x08, 0x68, 0x10, 0x43, 0x08, 0x60,
+ 0x10, 0xBD, 0x52, 0x1C, 0xCA, 0x72, 0x10, 0xBD, 0x00, 0x22, 0xCA, 0x72,
+ 0x01, 0x7B, 0xFD, 0x22, 0x11, 0x40, 0x01, 0x73, 0x10, 0xBD, 0x10, 0xB5,
+ 0x1B, 0x4B, 0x58, 0x68, 0xFF, 0x30, 0x01, 0x30, 0x42, 0x7F, 0x00, 0x2A,
+ 0x25, 0xD0, 0x1B, 0x49, 0x1A, 0x24, 0x0C, 0x5F, 0xFF, 0x21, 0xF5, 0x31,
+ 0x4A, 0x43, 0x16, 0x49, 0x94, 0x42, 0x20, 0xDD, 0x9C, 0x68, 0x8A, 0x7A,
+ 0xFF, 0x34, 0x01, 0x34, 0xE4, 0x79, 0x24, 0x07, 0x24, 0x0F, 0xA2, 0x42,
+ 0x14, 0xD9, 0x02, 0x7B, 0x04, 0x21, 0x0A, 0x43, 0x02, 0x73, 0x58, 0x68,
+ 0xFF, 0x30, 0x01, 0x30, 0x02, 0x78, 0x0A, 0x43, 0x02, 0x70, 0x98, 0x68,
+ 0xE0, 0x30, 0x00, 0x7C, 0xC0, 0x06, 0x04, 0xD5, 0x0B, 0x49, 0x40, 0x22,
+ 0x08, 0x68, 0x10, 0x43, 0x08, 0x60, 0x10, 0xBD, 0x52, 0x1C, 0x8A, 0x72,
+ 0x10, 0xBD, 0x00, 0x22, 0x8A, 0x72, 0x01, 0x7B, 0xFB, 0x22, 0x11, 0x40,
+ 0x01, 0x73, 0x10, 0xBD, 0x80, 0x05, 0x05, 0x00, 0x3C, 0x10, 0x05, 0x00,
+ 0x60, 0xC1, 0x01, 0x20, 0x20, 0x08, 0x05, 0x00, 0x08, 0x0E, 0x05, 0x00,
+ 0x70, 0xB5, 0x1C, 0x4C, 0x1C, 0x48, 0x21, 0x78, 0x00, 0x29, 0x23, 0xD1,
+ 0x1B, 0x4D, 0x29, 0x69, 0xC9, 0x09, 0xC9, 0x07, 0x1E, 0xD0, 0x80, 0x47,
+ 0x20, 0x78, 0x1A, 0x21, 0x40, 0x1C, 0x20, 0x70, 0x17, 0x48, 0x18, 0x22,
+ 0x41, 0x5E, 0x82, 0x5E, 0x91, 0x42, 0x12, 0xDA, 0xA8, 0x68, 0x15, 0x4A,
+ 0x20, 0x30, 0x00, 0x78, 0xC1, 0x07, 0x08, 0xD0, 0x91, 0x7B, 0xCF, 0x23,
+ 0x80, 0x09, 0x19, 0x40, 0x00, 0x01, 0x08, 0x43, 0x02, 0x21, 0x08, 0x43,
+ 0x02, 0xE0, 0x90, 0x7B, 0xFD, 0x21, 0x08, 0x40, 0x90, 0x73, 0x70, 0xBD,
+ 0x80, 0x47, 0x70, 0xBD, 0x07, 0x48, 0x10, 0xB5, 0x20, 0x38, 0x80, 0x47,
+ 0x08, 0x48, 0x41, 0x7D, 0xEF, 0x22, 0x11, 0x40, 0x41, 0x75, 0x02, 0x49,
+ 0x00, 0x20, 0x08, 0x70, 0x10, 0xBD, 0x00, 0x00, 0xCC, 0x0D, 0x05, 0x00,
+ 0x79, 0x0C, 0x04, 0x00, 0x80, 0x05, 0x05, 0x00, 0x20, 0x08, 0x05, 0x00,
+ 0x40, 0xC1, 0x01, 0x20, 0x70, 0xB5, 0x0C, 0x46, 0x5C, 0x49, 0x80, 0x02,
+ 0x40, 0x18, 0x15, 0x46, 0xC1, 0x17, 0x5C, 0x4E, 0x5A, 0x4A, 0x00, 0x23,
+ 0xB0, 0x47, 0x2A, 0x1B, 0xD3, 0x17, 0x04, 0xF0, 0x2A, 0xF9, 0xA1, 0x02,
+ 0x40, 0x18, 0xFF, 0x30, 0xFF, 0x30, 0x02, 0x30, 0x80, 0x12, 0xC8, 0x28,
+ 0x01, 0xDA, 0xC8, 0x20, 0x70, 0xBD, 0x7D, 0x21, 0xC9, 0x00, 0x88, 0x42,
+ 0xFA, 0xDD, 0x08, 0x46, 0x70, 0xBD, 0x70, 0xB5, 0x04, 0x46, 0x50, 0x48,
+ 0x50, 0x4D, 0x00, 0x78, 0x00, 0x28, 0x1D, 0xD0, 0x68, 0x68, 0x4F, 0x4E,
+ 0xE0, 0x30, 0x00, 0x7C, 0xC0, 0x07, 0x16, 0xD0, 0x4D, 0x48, 0x01, 0x8B,
+ 0xA8, 0x68, 0xA2, 0x30, 0x42, 0x7D, 0x03, 0x7D, 0x10, 0x02, 0x18, 0x43,
+ 0x08, 0x18, 0x00, 0xB2, 0x00, 0xF0, 0x42, 0xF8, 0x48, 0x49, 0xC8, 0x72,
+ 0xAA, 0x68, 0x80, 0x05, 0xA0, 0x32, 0x12, 0x7E, 0xD2, 0x00, 0x80, 0x0F,
+ 0x02, 0x43, 0x0A, 0x73, 0x02, 0x20, 0xB0, 0x47, 0xA8, 0x68, 0xC0, 0x30,
+ 0x41, 0x7A, 0x00, 0x29, 0x1B, 0xD0, 0x40, 0x4D, 0x20, 0x35, 0x28, 0x7A,
+ 0x3F, 0x4A, 0x3E, 0x4B, 0x10, 0x80, 0x00, 0x04, 0x80, 0x0B, 0x20, 0x3B,
+ 0x5E, 0x7D, 0x36, 0x07, 0xB6, 0x0F, 0x30, 0x43, 0x80, 0xB2, 0x10, 0x80,
+ 0x81, 0x42, 0x01, 0xD2, 0x40, 0x1A, 0x80, 0xB2, 0x59, 0x7D, 0xF3, 0x22,
+ 0x11, 0x40, 0x82, 0x07, 0x12, 0x0F, 0x11, 0x43, 0x59, 0x75, 0x80, 0x08,
+ 0x28, 0x72, 0x34, 0x49, 0x20, 0x46, 0x88, 0x47, 0x70, 0xBD, 0x32, 0x48,
+ 0x10, 0xB5, 0xEC, 0x38, 0x80, 0x47, 0x2B, 0x48, 0x01, 0x69, 0x09, 0x0B,
+ 0xC9, 0x07, 0x06, 0xD0, 0x80, 0x68, 0x2F, 0x4B, 0xA0, 0x30, 0x41, 0x7D,
+ 0x2C, 0x4A, 0x08, 0x20, 0x98, 0x47, 0x10, 0xBD, 0x10, 0xB5, 0x24, 0x49,
+ 0xC9, 0x68, 0x4A, 0x78, 0x0B, 0x78, 0x12, 0x02, 0x1A, 0x43, 0x08, 0x2A,
+ 0x30, 0xD3, 0x20, 0x4A, 0xC0, 0x3A, 0xD2, 0x79, 0x03, 0x2A, 0x13, 0xD0,
+ 0x02, 0x2A, 0x1D, 0xD0, 0x0A, 0x46, 0x40, 0x32, 0x53, 0x7D, 0x14, 0x7D,
+ 0x1A, 0x02, 0x22, 0x43, 0x92, 0x05, 0x92, 0x0D, 0x42, 0x31, 0x4B, 0x7C,
+ 0x0C, 0x7C, 0x19, 0x02, 0x21, 0x43, 0x89, 0x05, 0x89, 0x0D, 0xFF, 0xF7,
+ 0x65, 0xFF, 0x1D, 0xE0, 0x0A, 0x46, 0x40, 0x32, 0x53, 0x7B, 0x14, 0x7B,
+ 0x1A, 0x02, 0x22, 0x43, 0x92, 0x05, 0x92, 0x0D, 0x42, 0x31, 0x4B, 0x7A,
+ 0x0C, 0x7A, 0xEC, 0xE7, 0x0A, 0x46, 0x40, 0x32, 0x53, 0x7C, 0x14, 0x7C,
+ 0x1A, 0x02, 0x22, 0x43, 0x92, 0x05, 0x92, 0x0D, 0x42, 0x31, 0x4B, 0x7B,
+ 0x0C, 0x7B, 0xE0, 0xE7, 0x80, 0x02, 0xC1, 0x17, 0x04, 0x4C, 0x0E, 0x4A,
+ 0x00, 0x23, 0xA0, 0x47, 0x80, 0xB2, 0x10, 0xBD, 0x00, 0xE0, 0xB1, 0xFF,
+ 0x58, 0x1B, 0x00, 0x00, 0xE9, 0x6D, 0x04, 0x00, 0xE0, 0x0D, 0x05, 0x00,
+ 0x80, 0x05, 0x05, 0x00, 0x15, 0x07, 0x04, 0x00, 0x20, 0x08, 0x05, 0x00,
+ 0x60, 0xC1, 0x01, 0x20, 0xE4, 0x0D, 0x05, 0x00, 0x31, 0x0E, 0x04, 0x00,
+ 0x05, 0x19, 0x05, 0x00, 0x4D, 0x0A, 0x04, 0x00, 0x48, 0x4E, 0x00, 0x00,
+ 0x01, 0x20, 0xBE, 0x49, 0x00, 0x05, 0xC8, 0x60, 0x70, 0x47, 0x10, 0xB5,
+ 0xBC, 0x48, 0xFD, 0x22, 0x01, 0x79, 0x11, 0x40, 0x01, 0x71, 0xBB, 0x49,
+ 0x04, 0x20, 0x88, 0x47, 0xBA, 0x48, 0x00, 0x68, 0xC1, 0x68, 0x40, 0x20,
+ 0x88, 0x47, 0x10, 0xBD, 0x30, 0xB5, 0xB5, 0x48, 0x18, 0x21, 0x00, 0x1F,
+ 0x43, 0x88, 0x04, 0x1D, 0xB4, 0x48, 0x23, 0x80, 0x40, 0x38, 0x40, 0x68,
+ 0xC0, 0x30, 0x80, 0x7C, 0x42, 0x01, 0xB2, 0x48, 0xC5, 0x89, 0xB2, 0x48,
+ 0x9D, 0x42, 0x41, 0x5E, 0x20, 0x46, 0x00, 0x79, 0x04, 0xD2, 0x40, 0x08,
+ 0x40, 0x00, 0x20, 0x71, 0x88, 0x18, 0x03, 0xE0, 0x01, 0x23, 0x18, 0x43,
+ 0x20, 0x71, 0x88, 0x1A, 0x60, 0x80, 0x20, 0x79, 0x02, 0x21, 0x08, 0x43,
+ 0x20, 0x71, 0x30, 0xBD, 0x70, 0xB5, 0xA5, 0x4D, 0x04, 0x46, 0x40, 0x3D,
+ 0x68, 0x68, 0xC0, 0x30, 0x40, 0x7D, 0x80, 0x09, 0x18, 0xD1, 0xE0, 0x7A,
+ 0x00, 0x28, 0x18, 0xD1, 0x20, 0x78, 0x03, 0x28, 0x1A, 0xD1, 0x60, 0x78,
+ 0x1E, 0x28, 0x17, 0xD1, 0xA0, 0x78, 0xF0, 0x28, 0x02, 0xD0, 0xFF, 0x28,
+ 0x03, 0xD0, 0x11, 0xE0, 0xFF, 0xF7, 0xB3, 0xFF, 0x06, 0xE0, 0xA8, 0x68,
+ 0xC0, 0x30, 0x40, 0x7B, 0x80, 0x07, 0x09, 0xD4, 0xFF, 0xF7, 0xBA, 0xFF,
+ 0xE0, 0x7A, 0x00, 0x28, 0x04, 0xD0, 0x68, 0x68, 0x20, 0x30, 0xC0, 0x7F,
+ 0x40, 0x07, 0x0C, 0xD5, 0x68, 0x68, 0x09, 0x22, 0xFF, 0x30, 0x61, 0x1C,
+ 0x91, 0x30, 0x04, 0xF0, 0x12, 0xF8, 0xE0, 0x7A, 0x00, 0x28, 0x02, 0xD1,
+ 0x8E, 0x49, 0x20, 0x20, 0x88, 0x47, 0x70, 0xBD, 0x89, 0x49, 0x40, 0x39,
+ 0x48, 0x68, 0xC0, 0x30, 0x40, 0x7D, 0x82, 0x09, 0x03, 0xD0, 0x80, 0x09,
+ 0x01, 0x28, 0x05, 0xD1, 0x98, 0xE7, 0x88, 0x68, 0xC0, 0x30, 0x40, 0x7B,
+ 0x80, 0x07, 0xF9, 0xD5, 0x70, 0x47, 0x70, 0xB5, 0x80, 0x4D, 0x40, 0x3D,
+ 0xA8, 0x68, 0xC0, 0x30, 0x40, 0x7B, 0xC0, 0x07, 0x01, 0xD1, 0xFF, 0xF7,
+ 0x7A, 0xFF, 0x68, 0x68, 0xC0, 0x30, 0x41, 0x7C, 0x02, 0x7C, 0x08, 0x02,
+ 0x77, 0x49, 0x10, 0x43, 0x09, 0x1F, 0x48, 0x80, 0x00, 0xF0, 0xB6, 0xF8,
+ 0x04, 0x00, 0x0C, 0xD0, 0xA8, 0x68, 0xC0, 0x30, 0x81, 0x7B, 0x08, 0x09,
+ 0x07, 0xD0, 0x09, 0x09, 0x14, 0x20, 0x41, 0x43, 0x70, 0x4B, 0x04, 0x20,
+ 0x74, 0x4A, 0x2A, 0x3B, 0x98, 0x47, 0x20, 0x46, 0x70, 0xBD, 0x01, 0x20,
+ 0x6A, 0x49, 0x40, 0x05, 0xC8, 0x60, 0x70, 0x47, 0xF8, 0xB5, 0x6B, 0x4C,
+ 0x68, 0x4D, 0x40, 0x3C, 0x61, 0x68, 0x2D, 0x1F, 0xC0, 0x31, 0xCA, 0x7C,
+ 0x00, 0x26, 0x28, 0x78, 0x00, 0x2A, 0x08, 0xD0, 0x66, 0x4A, 0x64, 0x27,
+ 0xD3, 0x89, 0xA2, 0x68, 0xC0, 0x32, 0xD2, 0x7A, 0x7A, 0x43, 0x93, 0x42,
+ 0x0B, 0xD8, 0x00, 0x28, 0x08, 0xD0, 0x5F, 0x49, 0x03, 0x20, 0x88, 0x47,
+ 0x2E, 0x70, 0x61, 0x69, 0x01, 0x20, 0x40, 0x05, 0x81, 0x43, 0x61, 0x61,
+ 0xF8, 0xBD, 0x0A, 0x7D, 0x40, 0x1E, 0x0E, 0x75, 0x02, 0x43, 0x09, 0xD0,
+ 0x60, 0x68, 0x57, 0x4B, 0xC0, 0x30, 0xC1, 0x7C, 0x0A, 0x20, 0x41, 0x43,
+ 0x03, 0x20, 0x5A, 0x4A, 0x2A, 0x3B, 0x98, 0x47, 0x60, 0x69, 0x40, 0x0D,
+ 0xC0, 0x07, 0x29, 0xD0, 0xA0, 0x68, 0xC0, 0x30, 0x40, 0x7B, 0x40, 0x07,
+ 0x03, 0xD5, 0x01, 0x20, 0x54, 0x49, 0x80, 0x04, 0x88, 0x47, 0xA1, 0x68,
+ 0xCD, 0x20, 0x40, 0x5C, 0x00, 0x07, 0x16, 0xD5, 0x20, 0x31, 0x88, 0x7E,
+ 0x49, 0x7E, 0x80, 0x00, 0x89, 0x09, 0x08, 0x43, 0x00, 0xF0, 0x8E, 0xFB,
+ 0x61, 0x68, 0xC0, 0x31, 0x08, 0x74, 0x00, 0x0A, 0x48, 0x74, 0xA0, 0x68,
+ 0xC0, 0x30, 0x01, 0x7B, 0x60, 0x68, 0xC0, 0x30, 0x81, 0x74, 0xFF, 0xF7,
+ 0x7E, 0xFF, 0x00, 0x28, 0x04, 0xD0, 0x60, 0x69, 0x01, 0x21, 0x49, 0x05,
+ 0x88, 0x43, 0x60, 0x61, 0x01, 0x20, 0x28, 0x70, 0xF8, 0xBD, 0x10, 0xB5,
+ 0x3A, 0x4C, 0x40, 0x3C, 0xA0, 0x68, 0xC0, 0x30, 0x40, 0x7B, 0xC0, 0x07,
+ 0x32, 0xD0, 0x60, 0x69, 0x00, 0x0D, 0xC0, 0x07, 0x06, 0xD0, 0x60, 0x69,
+ 0x01, 0x21, 0x09, 0x05, 0x88, 0x43, 0x60, 0x61, 0xFF, 0xF7, 0xE5, 0xFE,
+ 0xA0, 0x69, 0x00, 0x0E, 0xC0, 0x07, 0x21, 0xD0, 0x2D, 0x49, 0x08, 0x79,
+ 0x82, 0x07, 0x1D, 0xD5, 0xC2, 0x07, 0x34, 0x48, 0x34, 0x4A, 0x82, 0x5E,
+ 0x48, 0x88, 0x1A, 0xD0, 0x82, 0x42, 0x15, 0xDC, 0x08, 0x88, 0x00, 0xF0,
+ 0x49, 0xFB, 0x62, 0x68, 0x81, 0x08, 0xA0, 0x32, 0x91, 0x74, 0x61, 0x68,
+ 0xA0, 0x31, 0x4A, 0x7C, 0x92, 0x06, 0x92, 0x0E, 0x80, 0x01, 0x02, 0x43,
+ 0x4A, 0x74, 0xA0, 0x69, 0x01, 0x21, 0x89, 0x06, 0x08, 0x43, 0xA0, 0x61,
+ 0xFF, 0xF7, 0xBF, 0xFE, 0xFF, 0xF7, 0x64, 0xFF, 0x10, 0xBD, 0x82, 0x42,
+ 0xE4, 0xDC, 0xF9, 0xE7, 0x1F, 0xB5, 0x00, 0x20, 0x00, 0x90, 0x01, 0x90,
+ 0x02, 0x90, 0x03, 0x90, 0x18, 0x48, 0x19, 0x4C, 0x40, 0x38, 0x41, 0x68,
+ 0xD5, 0x20, 0x40, 0x5C, 0x82, 0x09, 0x07, 0xD0, 0x80, 0x09, 0x01, 0x28,
+ 0x12, 0xD0, 0xFF, 0xF7, 0xA6, 0xFE, 0x00, 0x20, 0x04, 0xB0, 0x10, 0xBD,
+ 0x38, 0x20, 0x69, 0x46, 0x48, 0x70, 0x0A, 0x20, 0x88, 0x70, 0x0C, 0x48,
+ 0x00, 0x1F, 0x40, 0x88, 0xC8, 0x70, 0x00, 0x0A, 0x08, 0x71, 0x01, 0x20,
+ 0x08, 0x73, 0x06, 0xE0, 0x00, 0xA8, 0xFF, 0x31, 0x09, 0x22, 0x01, 0x30,
+ 0x81, 0x31, 0x03, 0xF0, 0x04, 0xFF, 0xE0, 0x69, 0x0E, 0x4A, 0x83, 0x6E,
+ 0x0E, 0x49, 0x68, 0x46, 0x98, 0x47, 0xE1, 0xE7, 0x00, 0xFC, 0x01, 0x20,
+ 0xD2, 0x0D, 0x05, 0x00, 0x33, 0x0B, 0x04, 0x00, 0xC0, 0x05, 0x05, 0x00,
+ 0xC0, 0x04, 0x05, 0x00, 0x20, 0x08, 0x05, 0x00, 0xED, 0x61, 0x04, 0x00,
+ 0xD5, 0x10, 0x08, 0x00, 0x23, 0x12, 0x08, 0x00, 0xF7, 0x25, 0x04, 0x00,
+ 0x40, 0x08, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x45, 0x11, 0x08, 0x00,
+ 0xB5, 0x11, 0x08, 0x00, 0x7C, 0xB5, 0x16, 0x4C, 0x05, 0x46, 0xA0, 0x6A,
+ 0x0E, 0x46, 0x00, 0x6D, 0x80, 0x47, 0x14, 0x48, 0xA6, 0x63, 0x65, 0x63,
+ 0x01, 0x78, 0x13, 0x48, 0x00, 0x29, 0xA1, 0x6A, 0x49, 0x6B, 0x00, 0x91,
+ 0x80, 0x68, 0x09, 0xD0, 0xE0, 0x30, 0x41, 0x78, 0x6A, 0x46, 0x11, 0x71,
+ 0x81, 0x78, 0x51, 0x71, 0xC1, 0x78, 0x91, 0x71, 0x00, 0x79, 0x08, 0xE0,
+ 0xE0, 0x30, 0x41, 0x79, 0x6A, 0x46, 0x11, 0x71, 0x81, 0x79, 0x51, 0x71,
+ 0xC1, 0x79, 0x91, 0x71, 0x00, 0x7A, 0xD0, 0x71, 0x06, 0x49, 0x10, 0x46,
+ 0x88, 0x47, 0x05, 0x48, 0x4E, 0x30, 0x80, 0x47, 0x7C, 0xBD, 0x00, 0x00,
+ 0x80, 0x04, 0x05, 0x00, 0xE2, 0x0D, 0x05, 0x00, 0x80, 0x05, 0x05, 0x00,
+ 0xD5, 0x04, 0x04, 0x00, 0xFE, 0x49, 0xC8, 0x68, 0x42, 0x78, 0x03, 0x78,
+ 0x12, 0x02, 0x1A, 0x43, 0x07, 0x2A, 0x2D, 0xD3, 0xFA, 0x49, 0xFB, 0x4A,
+ 0xC0, 0x39, 0xC9, 0x79, 0x03, 0x29, 0x0E, 0xD0, 0x02, 0x29, 0x91, 0x7A,
+ 0x18, 0xD0, 0xC0, 0x7F, 0x49, 0x09, 0x49, 0x01, 0xC0, 0x06, 0xC0, 0x0E,
+ 0x01, 0x43, 0x91, 0x72, 0x50, 0x7A, 0x0F, 0x21, 0x08, 0x43, 0x50, 0x72,
+ 0x70, 0x47, 0x91, 0x7A, 0x40, 0x7F, 0x49, 0x09, 0x49, 0x01, 0xC0, 0x06,
+ 0xC0, 0x0E, 0x01, 0x43, 0x91, 0x72, 0x50, 0x7A, 0x00, 0x09, 0x00, 0x01,
+ 0xC0, 0x1D, 0xF0, 0xE7, 0x80, 0x7F, 0x49, 0x09, 0x49, 0x01, 0xC0, 0x06,
+ 0xC0, 0x0E, 0x01, 0x43, 0x91, 0x72, 0x50, 0x7A, 0x00, 0x09, 0x00, 0x01,
+ 0x0B, 0x30, 0xE4, 0xE7, 0x48, 0x68, 0xE0, 0x30, 0x01, 0x7C, 0x49, 0x08,
+ 0x49, 0x00, 0x01, 0x74, 0x70, 0x47, 0xF8, 0xB5, 0xDF, 0x48, 0xA0, 0x38,
+ 0x00, 0x7B, 0x01, 0x28, 0x7E, 0xD0, 0xDD, 0x4E, 0xDE, 0x4D, 0xB1, 0x68,
+ 0x6A, 0x78, 0x08, 0x7F, 0x00, 0x2A, 0x01, 0xD0, 0x49, 0x7F, 0x08, 0x18,
+ 0xDB, 0x4C, 0x02, 0x22, 0xA2, 0x5E, 0xC0, 0x00, 0x00, 0x27, 0x82, 0x42,
+ 0x01, 0xDD, 0x6F, 0x70, 0x01, 0xE0, 0x01, 0x20, 0x68, 0x70, 0xD3, 0x49,
+ 0x68, 0x78, 0xC0, 0x39, 0x08, 0x70, 0xD5, 0x48, 0x80, 0x47, 0xB0, 0x68,
+ 0x2B, 0x78, 0x02, 0x46, 0x60, 0x30, 0x01, 0x78, 0x00, 0x2B, 0x01, 0xD0,
+ 0x43, 0x78, 0x59, 0x18, 0xCE, 0x4C, 0x02, 0x23, 0xE3, 0x5E, 0xC9, 0x00,
+ 0x8B, 0x42, 0x0C, 0xDD, 0xCB, 0x49, 0x2F, 0x70, 0x20, 0x39, 0x18, 0x23,
+ 0xCB, 0x5E, 0x80, 0x78, 0x64, 0x21, 0x48, 0x43, 0x83, 0x42, 0x10, 0xDD,
+ 0x32, 0x20, 0x84, 0x5C, 0x12, 0xE0, 0x01, 0x21, 0x29, 0x70, 0xC4, 0x49,
+ 0x18, 0x23, 0x20, 0x39, 0xCB, 0x5E, 0x80, 0x78, 0x64, 0x21, 0x48, 0x43,
+ 0x83, 0x42, 0x05, 0xDD, 0x34, 0x20, 0x84, 0x5C, 0x04, 0xE0, 0x31, 0x20,
+ 0x84, 0x5C, 0x01, 0xE0, 0x33, 0x20, 0x84, 0x5C, 0xB9, 0x48, 0xA1, 0x07,
+ 0x81, 0x7A, 0x02, 0xD0, 0x40, 0x23, 0x19, 0x43, 0x01, 0xE0, 0xBF, 0x23,
+ 0x19, 0x40, 0x81, 0x72, 0x21, 0x07, 0x89, 0x0F, 0x81, 0x7A, 0x02, 0xD0,
+ 0x80, 0x23, 0x19, 0x43, 0x01, 0xE0, 0x49, 0x06, 0x49, 0x0E, 0x81, 0x72,
+ 0xAF, 0x48, 0x20, 0x38, 0x41, 0x7D, 0x9F, 0x23, 0x19, 0x40, 0xE3, 0x09,
+ 0x9B, 0x01, 0x19, 0x43, 0x63, 0x06, 0xDB, 0x0F, 0x5B, 0x01, 0x19, 0x43,
+ 0x41, 0x75, 0x40, 0x7D, 0xC0, 0x07, 0xC0, 0x17, 0x40, 0x1C, 0x46, 0xD0,
+ 0x70, 0x68, 0xC0, 0x30, 0xC0, 0x7D, 0x00, 0x28, 0x41, 0xD0, 0x64, 0x21,
+ 0x48, 0x43, 0xA5, 0x49, 0x18, 0x23, 0x20, 0x39, 0xCB, 0x5E, 0x83, 0x42,
+ 0x4C, 0xDD, 0xA0, 0x4B, 0x20, 0x3B, 0x00, 0xE0, 0x5B, 0xE0, 0x58, 0x7D,
+ 0x01, 0x21, 0x08, 0x43, 0x58, 0x75, 0x9B, 0x48, 0x40, 0x38, 0x47, 0x83,
+ 0x03, 0x20, 0xE8, 0x71, 0x90, 0x7D, 0xC0, 0x07, 0x05, 0xD0, 0x0A, 0x21,
+ 0x9C, 0x4B, 0x9B, 0x4A, 0x02, 0x20, 0x98, 0x47, 0x36, 0xE0, 0x20, 0x32,
+ 0x10, 0x7B, 0xA9, 0x78, 0x80, 0x06, 0x80, 0x0E, 0x89, 0x01, 0x08, 0x43,
+ 0x10, 0x73, 0xB2, 0x68, 0xE9, 0x78, 0x20, 0x32, 0x50, 0x7B, 0x89, 0x01,
+ 0x80, 0x06, 0x80, 0x0E, 0x08, 0x43, 0x50, 0x73, 0xB2, 0x68, 0x29, 0x79,
+ 0x20, 0x32, 0x90, 0x7B, 0x89, 0x01, 0x80, 0x06, 0x80, 0x0E, 0x08, 0x43,
+ 0x90, 0x73, 0xB0, 0x68, 0x6A, 0x79, 0x20, 0x30, 0xC1, 0x7B, 0x89, 0x06,
+ 0x89, 0x0E, 0x92, 0x01, 0x11, 0x43, 0xC1, 0x73, 0x12, 0xE0, 0x70, 0x69,
+ 0x80, 0x0C, 0xC0, 0x07, 0x0E, 0xD0, 0x70, 0x69, 0x01, 0x21, 0x89, 0x04,
+ 0x88, 0x43, 0x70, 0x61, 0xC8, 0x11, 0x84, 0x49, 0x88, 0x47, 0x01, 0x20,
+ 0x82, 0x49, 0x00, 0x05, 0x40, 0x31, 0x88, 0x47, 0xB2, 0x68, 0xC8, 0xE7,
+ 0x80, 0x48, 0x84, 0x70, 0xFF, 0xF7, 0xF0, 0xFE, 0xB1, 0x68, 0x2A, 0x7A,
+ 0xE0, 0x31, 0x48, 0x7A, 0x00, 0x2A, 0x01, 0xD0, 0x89, 0x7A, 0x08, 0x18,
+ 0x75, 0x49, 0x02, 0x22, 0x8A, 0x5E, 0xC0, 0x00, 0x82, 0x42, 0x01, 0xDD,
+ 0x2F, 0x72, 0xF8, 0xBD, 0x01, 0x20, 0x28, 0x72, 0xF8, 0xBD, 0x10, 0xB5,
+ 0x75, 0x48, 0x80, 0x47, 0x00, 0x21, 0x6D, 0x48, 0x4A, 0x1E, 0x81, 0x71,
+ 0xC2, 0x71, 0x82, 0x81, 0x72, 0x48, 0x01, 0x70, 0xFF, 0xF7, 0x05, 0xFD,
+ 0x10, 0xBD, 0x10, 0xB5, 0x70, 0x48, 0x80, 0x47, 0x64, 0x48, 0x81, 0x68,
+ 0x20, 0x31, 0x8A, 0x7E, 0x41, 0x68, 0xA0, 0x31, 0x8A, 0x74, 0x41, 0x68,
+ 0x83, 0x68, 0xA0, 0x31, 0x20, 0x33, 0x4A, 0x7C, 0x5B, 0x7E, 0x92, 0x06,
+ 0x9B, 0x09, 0x92, 0x0E, 0x9B, 0x01, 0x1A, 0x43, 0x4A, 0x74, 0x41, 0x68,
+ 0x83, 0x68, 0xE0, 0x31, 0xA0, 0x33, 0x0A, 0x7C, 0x1B, 0x7D, 0x52, 0x08,
+ 0xDB, 0x07, 0x52, 0x00, 0xDB, 0x0F, 0x1A, 0x43, 0x0A, 0x74, 0x81, 0x68,
+ 0xC0, 0x31, 0x0A, 0x7B, 0x41, 0x68, 0xC0, 0x31, 0x8A, 0x74, 0x81, 0x68,
+ 0xC0, 0x31, 0xCA, 0x7B, 0x41, 0x68, 0xC0, 0x31, 0xCA, 0x74, 0x41, 0x68,
+ 0x80, 0x68, 0xC0, 0x31, 0x4A, 0x7D, 0xC0, 0x30, 0x40, 0x7B, 0x92, 0x06,
+ 0x92, 0x0E, 0x80, 0x09, 0x80, 0x01, 0x02, 0x43, 0x4A, 0x75, 0x10, 0xBD,
+ 0x70, 0xB5, 0x54, 0x48, 0x80, 0x47, 0x48, 0x48, 0x20, 0x38, 0x41, 0x7D,
+ 0xEF, 0x25, 0x29, 0x40, 0x41, 0x75, 0x51, 0x49, 0x00, 0x20, 0x88, 0x47,
+ 0x50, 0x4A, 0x00, 0x20, 0x10, 0x77, 0x42, 0x4C, 0x24, 0x20, 0x60, 0x73,
+ 0x3F, 0x4E, 0xB0, 0x68, 0xA0, 0x30, 0x00, 0x7E, 0x41, 0x06, 0x89, 0x0F,
+ 0xC0, 0x09, 0x89, 0x00, 0xC0, 0x01, 0x01, 0x43, 0x51, 0x70, 0xFF, 0xF7,
+ 0x75, 0xFE, 0x20, 0x78, 0xB1, 0x68, 0x28, 0x40, 0x20, 0x31, 0xC9, 0x7A,
+ 0xC9, 0x07, 0xC9, 0x0E, 0x08, 0x43, 0x20, 0x70, 0x70, 0xBD, 0x70, 0xB5,
+ 0x33, 0x4C, 0x60, 0x69, 0x33, 0x4D, 0xC0, 0x0C, 0xC0, 0x07, 0x20, 0x3D,
+ 0xF3, 0x26, 0x00, 0x28, 0x19, 0xD0, 0x60, 0x69, 0x01, 0x21, 0xC9, 0x04,
+ 0x88, 0x43, 0x60, 0x61, 0x39, 0x49, 0x00, 0x20, 0x28, 0x31, 0x88, 0x47,
+ 0xA0, 0x68, 0xC0, 0x30, 0x40, 0x7A, 0x00, 0x28, 0x0B, 0xD0, 0x2A, 0x48,
+ 0x28, 0x4A, 0x40, 0x89, 0x81, 0x08, 0x20, 0x32, 0x11, 0x72, 0x69, 0x7D,
+ 0x80, 0x07, 0x31, 0x40, 0x00, 0x0F, 0x01, 0x43, 0x69, 0x75, 0x31, 0x48,
+ 0x80, 0x47, 0x00, 0x28, 0x0B, 0xD1, 0x24, 0x48, 0x54, 0x38, 0x80, 0x47,
+ 0x68, 0x7D, 0x61, 0x68, 0x30, 0x40, 0xA0, 0x31, 0x49, 0x7C, 0x89, 0x09,
+ 0x89, 0x00, 0x08, 0x43, 0x68, 0x75, 0x70, 0xBD, 0x70, 0xB5, 0x22, 0x48,
+ 0x8C, 0x30, 0x80, 0x47, 0x17, 0x4D, 0xA8, 0x69, 0x00, 0x0E, 0xC0, 0x07,
+ 0x4D, 0xD0, 0x17, 0x4C, 0x07, 0x20, 0x20, 0x56, 0x00, 0x28, 0x07, 0xD0,
+ 0x01, 0xDB, 0x40, 0x1E, 0xE0, 0x71, 0x21, 0x48, 0x00, 0x7C, 0x01, 0x28,
+ 0x03, 0xD0, 0x11, 0xE0, 0x01, 0x21, 0xA1, 0x71, 0xF5, 0xE7, 0x10, 0x48,
+ 0x00, 0x21, 0x41, 0x5E, 0xA8, 0x68, 0x64, 0x22, 0x60, 0x30, 0xC0, 0x78,
+ 0x50, 0x43, 0x81, 0x42, 0x04, 0xDD, 0x09, 0x48, 0x81, 0x7A, 0x49, 0x06,
+ 0x49, 0x0E, 0x81, 0x72, 0xA0, 0x89, 0x00, 0x28, 0x02, 0xD1, 0x0B, 0x48,
+ 0x3E, 0x38, 0x80, 0x47, 0x0C, 0x20, 0x20, 0x5E, 0x00, 0x28, 0x24, 0xDB,
+ 0x40, 0x1E, 0x21, 0xE0, 0x80, 0x05, 0x05, 0x00, 0x60, 0xC1, 0x01, 0x20,
+ 0xDA, 0x0D, 0x05, 0x00, 0x40, 0x08, 0x05, 0x00, 0x91, 0x1F, 0x04, 0x00,
+ 0x3D, 0x19, 0x05, 0x00, 0x09, 0x0B, 0x04, 0x00, 0xF7, 0x25, 0x04, 0x00,
+ 0x00, 0x20, 0x01, 0x20, 0x0D, 0x19, 0x04, 0x00, 0xCE, 0x0D, 0x05, 0x00,
+ 0x69, 0x1D, 0x04, 0x00, 0x05, 0x1C, 0x04, 0x00, 0xED, 0x06, 0x04, 0x00,
+ 0x00, 0x50, 0x01, 0x20, 0x39, 0x0D, 0x04, 0x00, 0x80, 0x04, 0x05, 0x00,
+ 0xA0, 0x81, 0xE8, 0x69, 0x80, 0x09, 0xC0, 0x07, 0x07, 0xD0, 0xFF, 0xF7,
+ 0x8A, 0xFC, 0x00, 0x28, 0x03, 0xD0, 0xE8, 0x69, 0x40, 0x21, 0x88, 0x43,
+ 0xE8, 0x61, 0xFF, 0xF7, 0x0E, 0xFD, 0x70, 0xBD, 0xF8, 0xB5, 0x44, 0x4F,
+ 0x3D, 0x7A, 0x43, 0x48, 0x20, 0x38, 0x81, 0x7B, 0xDF, 0x22, 0x11, 0x40,
+ 0x81, 0x73, 0xC1, 0x78, 0x40, 0x4A, 0x03, 0x23, 0xD3, 0x71, 0xCF, 0x22,
+ 0x11, 0x40, 0x30, 0x22, 0x0A, 0x43, 0xC2, 0x70, 0x00, 0x20, 0x38, 0x72,
+ 0x3C, 0x49, 0x01, 0x20, 0x88, 0x47, 0x3A, 0x48, 0x3C, 0x7A, 0xC0, 0x30,
+ 0x80, 0x68, 0xC0, 0x30, 0x80, 0x79, 0x80, 0x06, 0x80, 0x0E, 0x01, 0x26,
+ 0x00, 0x28, 0x0C, 0xD0, 0xAC, 0x42, 0x0A, 0xD2, 0x00, 0x01, 0x00, 0x90,
+ 0xA0, 0x19, 0xA8, 0x42, 0x05, 0xD2, 0xC4, 0xB2, 0x3C, 0x72, 0x32, 0x49,
+ 0x00, 0x98, 0x88, 0x47, 0xF6, 0xE7, 0x3D, 0x72, 0x2D, 0x48, 0x01, 0x21,
+ 0x81, 0x71, 0xF8, 0xBD, 0x70, 0xB5, 0x04, 0x00, 0x25, 0xD0, 0x2A, 0x4D,
+ 0xE0, 0x7A, 0xC0, 0x35, 0x00, 0x28, 0x04, 0xD0, 0xA8, 0x68, 0xC0, 0x30,
+ 0x80, 0x7D, 0x80, 0x06, 0x09, 0xD5, 0x68, 0x68, 0x0A, 0x22, 0xFF, 0x30,
+ 0x61, 0x1C, 0x91, 0x30, 0x03, 0xF0, 0x33, 0xFC, 0xE0, 0x7A, 0x00, 0x28,
+ 0x04, 0xD0, 0xA8, 0x6A, 0xC1, 0x6A, 0x01, 0x20, 0x40, 0x02, 0x0B, 0xE0,
+ 0xA8, 0x68, 0xA0, 0x30, 0xC0, 0x79, 0x40, 0x06, 0x03, 0xD5, 0xA8, 0x69,
+ 0x10, 0x21, 0x08, 0x43, 0xA8, 0x61, 0xA8, 0x6A, 0xC1, 0x6A, 0x20, 0x20,
+ 0x88, 0x47, 0x70, 0xBD, 0x10, 0xB5, 0x16, 0x48, 0x80, 0x6A, 0x00, 0x28,
+ 0x14, 0xD1, 0x17, 0x48, 0x80, 0x47, 0x00, 0x28, 0x10, 0xD1, 0x12, 0x48,
+ 0xC0, 0x30, 0x80, 0x68, 0xC0, 0x30, 0x80, 0x7A, 0x01, 0x07, 0x0A, 0xD0,
+ 0x12, 0x49, 0x0C, 0x22, 0x8A, 0x5E, 0x52, 0x1C, 0x04, 0xD1, 0x00, 0x07,
+ 0x00, 0x0F, 0x64, 0x22, 0x50, 0x43, 0x88, 0x81, 0x10, 0xBD, 0x0E, 0x48,
+ 0x80, 0x47, 0x10, 0xBD, 0x10, 0xB5, 0xFF, 0x38, 0x19, 0x21, 0xF5, 0x38,
+ 0x03, 0xF0, 0x6C, 0xFC, 0x80, 0xB2, 0x10, 0xBD, 0x19, 0x21, 0x48, 0x43,
+ 0xFF, 0x30, 0xF5, 0x30, 0x80, 0xB2, 0x70, 0x47, 0x80, 0xC1, 0x01, 0x20,
+ 0xC0, 0x04, 0x05, 0x00, 0x77, 0x1E, 0x04, 0x00, 0x51, 0x01, 0x04, 0x00,
+ 0x8F, 0x22, 0x04, 0x00, 0xDA, 0x0D, 0x05, 0x00, 0xB9, 0x25, 0x04, 0x00,
+ 0x9D, 0x49, 0x00, 0x20, 0x89, 0x79, 0x01, 0x29, 0x04, 0xD0, 0x9B, 0x4A,
+ 0x01, 0x21, 0x20, 0x32, 0x51, 0x73, 0x70, 0x47, 0x01, 0x20, 0x70, 0x47,
+ 0x97, 0x49, 0xC0, 0x31, 0x89, 0x68, 0x60, 0x31, 0xCA, 0x7D, 0xD2, 0x07,
+ 0x08, 0xD0, 0x49, 0x7A, 0x49, 0x07, 0x05, 0xD5, 0x93, 0x49, 0x8A, 0x7B,
+ 0x20, 0x23, 0x1A, 0x43, 0x8A, 0x73, 0x88, 0x74, 0x70, 0x47, 0x10, 0xB5,
+ 0x8E, 0x4C, 0x68, 0x22, 0xC0, 0x34, 0xA1, 0x68, 0x52, 0x5C, 0xD2, 0x07,
+ 0x2E, 0xD0, 0x80, 0x31, 0x4A, 0x78, 0x0B, 0x78, 0x11, 0x02, 0x89, 0x4A,
+ 0x19, 0x43, 0xE0, 0x32, 0x12, 0x7B, 0xD2, 0x07, 0x02, 0xD0, 0xCA, 0x00,
+ 0x51, 0x1A, 0xC9, 0x10, 0x88, 0x42, 0x12, 0xDD, 0x85, 0x49, 0x01, 0x20,
+ 0x88, 0x47, 0xA0, 0x68, 0x60, 0x30, 0x00, 0x7B, 0xC0, 0x07, 0x02, 0xD0,
+ 0x82, 0x49, 0x03, 0x20, 0x88, 0x47, 0xA0, 0x68, 0x60, 0x30, 0x00, 0x7C,
+ 0xC0, 0x07, 0x0F, 0xD0, 0x01, 0x20, 0x7F, 0x49, 0x0B, 0xE0, 0x7C, 0x49,
+ 0x01, 0x20, 0x22, 0x31, 0x88, 0x47, 0x60, 0x68, 0x80, 0x30, 0x00, 0x7A,
+ 0xC0, 0x07, 0x03, 0xD1, 0x79, 0x49, 0x01, 0x20, 0x40, 0x31, 0x88, 0x47,
+ 0x10, 0xBD, 0x10, 0xB5, 0x72, 0x4C, 0x68, 0x22, 0xC0, 0x34, 0xA1, 0x68,
+ 0x52, 0x5C, 0x52, 0x07, 0x2F, 0xD5, 0x62, 0x31, 0x4A, 0x7E, 0x0B, 0x7E,
+ 0x11, 0x06, 0x6D, 0x4A, 0x09, 0x14, 0xE0, 0x32, 0x12, 0x7B, 0x19, 0x43,
+ 0x92, 0x07, 0x02, 0xD5, 0xCA, 0x00, 0x89, 0x18, 0xC9, 0x10, 0x88, 0x42,
+ 0x12, 0xDA, 0x69, 0x49, 0x02, 0x20, 0x88, 0x47, 0xA0, 0x68, 0x60, 0x30,
+ 0x00, 0x7B, 0x40, 0x07, 0x02, 0xD5, 0x66, 0x49, 0x03, 0x20, 0x88, 0x47,
+ 0xA0, 0x68, 0x60, 0x30, 0x00, 0x7C, 0x40, 0x07, 0x0F, 0xD5, 0x02, 0x20,
+ 0x62, 0x49, 0x0B, 0xE0, 0x5F, 0x49, 0x02, 0x20, 0x22, 0x31, 0x88, 0x47,
+ 0x60, 0x68, 0x80, 0x30, 0x00, 0x7A, 0xC0, 0x07, 0x03, 0xD1, 0x5D, 0x49,
+ 0x02, 0x20, 0x40, 0x31, 0x88, 0x47, 0x10, 0xBD, 0x10, 0xB5, 0x56, 0x4C,
+ 0xC0, 0x34, 0xA1, 0x68, 0x60, 0x31, 0x4A, 0x7A, 0xD2, 0x07, 0x2E, 0xD0,
+ 0x4A, 0x7F, 0x0B, 0x7F, 0x11, 0x02, 0x51, 0x4A, 0x19, 0x43, 0xE0, 0x32,
+ 0x52, 0x7B, 0xD2, 0x07, 0x02, 0xD0, 0xCA, 0x00, 0x51, 0x1A, 0xC9, 0x10,
+ 0x88, 0x42, 0x13, 0xDD, 0x4D, 0x49, 0x01, 0x20, 0x3C, 0x31, 0x88, 0x47,
+ 0xA0, 0x68, 0x60, 0x30, 0x40, 0x7B, 0xC0, 0x07, 0x02, 0xD0, 0x4A, 0x49,
+ 0x04, 0x20, 0x88, 0x47, 0xA0, 0x68, 0x60, 0x30, 0x40, 0x7C, 0xC0, 0x07,
+ 0x0F, 0xD0, 0x10, 0x20, 0x46, 0x49, 0x0B, 0xE0, 0x43, 0x49, 0x01, 0x20,
+ 0x5E, 0x31, 0x88, 0x47, 0x60, 0x68, 0x80, 0x30, 0x00, 0x7A, 0x40, 0x07,
+ 0x03, 0xD4, 0x41, 0x49, 0x10, 0x20, 0x40, 0x31, 0x88, 0x47, 0x10, 0xBD,
+ 0x70, 0xB5, 0x3A, 0x4C, 0x02, 0x46, 0xC0, 0x34, 0xA0, 0x68, 0x6A, 0x21,
+ 0x09, 0x5C, 0xC9, 0x07, 0x31, 0xD0, 0x62, 0x30, 0x41, 0x7F, 0x00, 0x7F,
+ 0x09, 0x02, 0x01, 0x43, 0x33, 0x48, 0xE0, 0x30, 0x80, 0x7B, 0xC0, 0x07,
+ 0x02, 0xD0, 0xC8, 0x00, 0x40, 0x1A, 0xC1, 0x10, 0x01, 0x25, 0x6D, 0x02,
+ 0x8A, 0x42, 0x13, 0xDD, 0x2F, 0x49, 0x01, 0x20, 0x78, 0x31, 0x88, 0x47,
+ 0xA0, 0x68, 0x60, 0x30, 0x80, 0x7B, 0xC0, 0x07, 0x02, 0xD0, 0x2C, 0x49,
+ 0x05, 0x20, 0x88, 0x47, 0xA0, 0x68, 0x60, 0x30, 0x80, 0x7C, 0xC0, 0x07,
+ 0x0F, 0xD0, 0x28, 0x46, 0x28, 0x49, 0x0B, 0xE0, 0x25, 0x49, 0x01, 0x20,
+ 0xA0, 0x31, 0x88, 0x47, 0x60, 0x68, 0x80, 0x30, 0x00, 0x7A, 0x80, 0x07,
+ 0x03, 0xD4, 0x23, 0x49, 0x28, 0x46, 0x40, 0x31, 0x88, 0x47, 0x70, 0xBD,
+ 0x10, 0xB5, 0x21, 0x48, 0x80, 0x47, 0x1C, 0x48, 0x20, 0x30, 0xC1, 0x78,
+ 0xDF, 0x22, 0x11, 0x40, 0xC1, 0x70, 0x82, 0x8B, 0x01, 0x15, 0x8A, 0x43,
+ 0x82, 0x83, 0x17, 0x48, 0x40, 0x30, 0x42, 0x88, 0x8A, 0x43, 0x42, 0x80,
+ 0x10, 0xBD, 0x70, 0x47, 0x70, 0x47, 0x10, 0xB5, 0x17, 0x48, 0x80, 0x47,
+ 0x17, 0x49, 0x01, 0x20, 0x88, 0x47, 0x10, 0xBD, 0x14, 0x48, 0x10, 0xB5,
+ 0xC4, 0x30, 0x80, 0x47, 0x13, 0x49, 0x01, 0x20, 0x28, 0x31, 0x88, 0x47,
+ 0x10, 0xBD, 0x0D, 0x48, 0x10, 0xB5, 0x62, 0x30, 0x80, 0x47, 0x09, 0x48,
+ 0x41, 0x78, 0x40, 0x22, 0x11, 0x43, 0x41, 0x70, 0x10, 0xBD, 0x09, 0x48,
+ 0x10, 0xB5, 0xAA, 0x30, 0x80, 0x47, 0x04, 0x48, 0x0A, 0x49, 0x40, 0x30,
+ 0x01, 0x80, 0xC1, 0x80, 0x10, 0xBD, 0x00, 0x00, 0xC0, 0x04, 0x05, 0x00,
+ 0x60, 0xC1, 0x01, 0x20, 0xFD, 0x5F, 0x04, 0x00, 0xAB, 0x29, 0x04, 0x00,
+ 0xF7, 0x25, 0x04, 0x00, 0x2B, 0x2B, 0x04, 0x00, 0xEB, 0x2C, 0x04, 0x00,
+ 0x01, 0x61, 0x04, 0x00, 0x75, 0x2A, 0x00, 0x00, 0x10, 0xB5, 0xC3, 0x4A,
+ 0xC3, 0x49, 0x50, 0x68, 0x0A, 0x23, 0xFF, 0x30, 0x31, 0x30, 0x08, 0x60,
+ 0xC3, 0x70, 0x05, 0x21, 0x41, 0x70, 0x04, 0x21, 0x81, 0x70, 0x41, 0x79,
+ 0x8F, 0x24, 0x21, 0x40, 0x49, 0x06, 0x49, 0x0E, 0x41, 0x71, 0x83, 0x71,
+ 0x91, 0x68, 0x0D, 0x23, 0xFF, 0x31, 0x01, 0x31, 0x49, 0x78, 0x59, 0x43,
+ 0x09, 0x0A, 0xC1, 0x71, 0x93, 0x68, 0x01, 0x79, 0x80, 0x33, 0x1B, 0x7F,
+ 0xC9, 0x08, 0x5B, 0x07, 0xC9, 0x00, 0x5B, 0x0F, 0x19, 0x43, 0x01, 0x71,
+ 0x92, 0x68, 0x49, 0x07, 0x80, 0x32, 0x12, 0x7F, 0x49, 0x0F, 0xD2, 0x08,
+ 0xD2, 0x00, 0x11, 0x43, 0x01, 0x71, 0x10, 0xBD, 0x10, 0xB5, 0xFF, 0xF7,
+ 0xCD, 0xFF, 0xAC, 0x48, 0x00, 0x21, 0x81, 0x71, 0xC1, 0x71, 0xE0, 0x21,
+ 0x01, 0x72, 0xAA, 0x49, 0x01, 0x22, 0x08, 0x68, 0x92, 0x03, 0x10, 0x43,
+ 0xA4, 0x4A, 0x08, 0x60, 0x92, 0x68, 0xE0, 0x32, 0x12, 0x7C, 0xD2, 0x07,
+ 0x02, 0xD0, 0x01, 0x22, 0x10, 0x43, 0x08, 0x60, 0x10, 0xBD, 0x10, 0xB5,
+ 0x00, 0xF0, 0xD5, 0xF8, 0xCF, 0xF7, 0x9B, 0xFD, 0x02, 0xF0, 0xF5, 0xFE,
+ 0x10, 0xBD, 0x10, 0xB5, 0x9D, 0x4B, 0x00, 0x21, 0x1A, 0x68, 0x50, 0x0A,
+ 0xC4, 0x07, 0x9A, 0x48, 0x00, 0x2C, 0x06, 0xD0, 0x01, 0x24, 0x64, 0x02,
+ 0xA2, 0x43, 0x1A, 0x60, 0xC1, 0x71, 0xE2, 0x22, 0x02, 0x72, 0x1A, 0x68,
+ 0x94, 0x09, 0xE4, 0x07, 0x05, 0xD0, 0x40, 0x24, 0xA2, 0x43, 0x1A, 0x60,
+ 0xC1, 0x71, 0xE5, 0x22, 0x02, 0x72, 0x1A, 0x68, 0xD4, 0x09, 0xE4, 0x07,
+ 0x05, 0xD0, 0x80, 0x24, 0xA2, 0x43, 0x1A, 0x60, 0xC1, 0x71, 0xE6, 0x22,
+ 0x02, 0x72, 0x1A, 0x68, 0x14, 0x0A, 0xE4, 0x07, 0x06, 0xD0, 0xFF, 0x24,
+ 0x01, 0x34, 0xA2, 0x43, 0x1A, 0x60, 0xC1, 0x71, 0xE7, 0x22, 0x02, 0x72,
+ 0x1A, 0x68, 0x94, 0x0A, 0xE4, 0x07, 0x0A, 0xD0, 0x01, 0x24, 0xA4, 0x02,
+ 0xA2, 0x43, 0x1A, 0x60, 0x42, 0x7C, 0x05, 0x2A, 0x04, 0xD2, 0x52, 0x1C,
+ 0x42, 0x74, 0x00, 0xF0, 0x50, 0xFB, 0x10, 0xBD, 0xC1, 0x71, 0xE9, 0x21,
+ 0x01, 0x72, 0x10, 0xBD, 0x79, 0x48, 0x41, 0x69, 0x04, 0x22, 0x91, 0x43,
+ 0x41, 0x61, 0x41, 0x69, 0x92, 0x03, 0x91, 0x43, 0x41, 0x61, 0x41, 0x69,
+ 0x52, 0x00, 0x91, 0x43, 0x41, 0x61, 0x41, 0x69, 0x08, 0x22, 0x91, 0x43,
+ 0x41, 0x61, 0x41, 0x69, 0x10, 0x22, 0x91, 0x43, 0x41, 0x61, 0x41, 0x69,
+ 0x20, 0x22, 0x91, 0x43, 0x41, 0x61, 0x41, 0x69, 0x40, 0x22, 0x91, 0x43,
+ 0x41, 0x61, 0x41, 0x69, 0x80, 0x22, 0x91, 0x43, 0x41, 0x61, 0x41, 0x69,
+ 0x52, 0x00, 0x91, 0x43, 0x41, 0x61, 0x41, 0x69, 0x52, 0x00, 0x91, 0x43,
+ 0x41, 0x61, 0x41, 0x69, 0x52, 0x00, 0x91, 0x43, 0x41, 0x61, 0x41, 0x69,
+ 0x52, 0x00, 0x91, 0x43, 0x41, 0x61, 0x41, 0x69, 0x52, 0x00, 0x91, 0x43,
+ 0x41, 0x61, 0x41, 0x69, 0x52, 0x00, 0x91, 0x43, 0x41, 0x61, 0x41, 0x69,
+ 0x52, 0x00, 0x91, 0x43, 0x41, 0x61, 0x41, 0x69, 0x52, 0x00, 0x91, 0x43,
+ 0x41, 0x61, 0x5C, 0x48, 0x5C, 0x4A, 0x01, 0x68, 0x11, 0x40, 0x01, 0x60,
+ 0x70, 0x47, 0x08, 0xB5, 0x00, 0x20, 0x00, 0x90, 0x55, 0x48, 0x59, 0x4A,
+ 0x00, 0x68, 0x40, 0x79, 0x41, 0x06, 0x80, 0x06, 0x80, 0x0F, 0x10, 0x56,
+ 0x00, 0x29, 0x00, 0xDB, 0x40, 0x42, 0x69, 0x46, 0x08, 0x70, 0x00, 0x98,
+ 0x02, 0xF0, 0xDA, 0xFE, 0x08, 0xBD, 0x70, 0xB5, 0x0C, 0x46, 0x06, 0x46,
+ 0x01, 0x46, 0x4C, 0x48, 0x0B, 0x22, 0x10, 0x38, 0x03, 0xF0, 0x9D, 0xF9,
+ 0x49, 0x48, 0x47, 0x4D, 0x44, 0x72, 0x00, 0x2C, 0x06, 0xD1, 0x68, 0x68,
+ 0x09, 0x22, 0xFF, 0x30, 0x31, 0x46, 0x91, 0x30, 0x03, 0xF0, 0x91, 0xF9,
+ 0xA8, 0x68, 0xE0, 0x30, 0x00, 0x7C, 0x80, 0x07, 0x05, 0xD4, 0x42, 0x48,
+ 0x01, 0x22, 0x01, 0x68, 0xD2, 0x03, 0x11, 0x43, 0x01, 0x60, 0x70, 0xBD,
+ 0x70, 0xB5, 0x15, 0x46, 0x04, 0x46, 0x01, 0x29, 0x01, 0xD1, 0x3F, 0x49,
+ 0x88, 0x47, 0xE0, 0x7A, 0x00, 0x28, 0x01, 0xD1, 0x06, 0x20, 0xE0, 0x72,
+ 0x29, 0x46, 0x20, 0x46, 0x02, 0xF0, 0x97, 0xFE, 0x70, 0xBD, 0x70, 0xB5,
+ 0x00, 0xF0, 0x18, 0xF8, 0x34, 0x4B, 0x01, 0x24, 0x1A, 0x68, 0xA4, 0x03,
+ 0xA2, 0x43, 0x31, 0x48, 0x1A, 0x60, 0x85, 0x79, 0xC1, 0x79, 0x8D, 0x42,
+ 0x0B, 0xD0, 0x2C, 0x4D, 0x6D, 0x68, 0xE9, 0x73, 0x85, 0x79, 0xC1, 0x79,
+ 0x8D, 0x42, 0x02, 0xD0, 0x81, 0x71, 0x22, 0x43, 0x1A, 0x60, 0x81, 0x79,
+ 0xC1, 0x71, 0x70, 0xBD, 0x10, 0xB5, 0x27, 0x48, 0x80, 0x79, 0x20, 0x28,
+ 0x37, 0xD0, 0x14, 0xDC, 0x03, 0x28, 0x2B, 0xD0, 0x08, 0xDC, 0x00, 0x28,
+ 0x22, 0xD0, 0x01, 0x28, 0x23, 0xD0, 0x02, 0x28, 0x01, 0xD1, 0x00, 0xF0,
+ 0xE3, 0xFF, 0x10, 0xBD, 0x04, 0x28, 0x22, 0xD0, 0x10, 0x28, 0x23, 0xD0,
+ 0x12, 0x28, 0xF8, 0xD1, 0x00, 0xF0, 0xCD, 0xF9, 0x10, 0xBD, 0x40, 0x28,
+ 0x28, 0xD0, 0x08, 0xDC, 0x21, 0x28, 0x1F, 0xD0, 0x22, 0x28, 0x20, 0xD0,
+ 0x23, 0x28, 0xF5, 0xD1, 0x00, 0xF0, 0x16, 0xF9, 0x10, 0xBD, 0x41, 0x28,
+ 0x1F, 0xD0, 0x42, 0x28, 0xFA, 0xD1, 0x00, 0xF0, 0x44, 0xFE, 0x10, 0xBD,
+ 0x00, 0xF0, 0x28, 0xFF, 0x10, 0xBD, 0x00, 0xF0, 0x41, 0xFF, 0x10, 0xBD,
+ 0x00, 0xF0, 0xAF, 0xFF, 0x10, 0xBD, 0x00, 0xF0, 0x8E, 0xFF, 0x10, 0xBD,
+ 0x00, 0xF0, 0x4E, 0xF9, 0x10, 0xBD, 0x00, 0xF0, 0x1B, 0xF8, 0x10, 0xBD,
+ 0x00, 0xF0, 0x27, 0xF8, 0x10, 0xBD, 0x00, 0xF0, 0x33, 0xF8, 0x10, 0xBD,
+ 0x00, 0xF0, 0x06, 0xFA, 0x10, 0xBD, 0x00, 0xF0, 0xBF, 0xFA, 0x10, 0xBD,
+ 0x80, 0x05, 0x05, 0x00, 0xF4, 0x0D, 0x05, 0x00, 0x3C, 0x10, 0x05, 0x00,
+ 0x08, 0x0E, 0x05, 0x00, 0x03, 0x00, 0xF0, 0xFF, 0x88, 0x57, 0x08, 0x00,
+ 0x8F, 0x55, 0x04, 0x00, 0x10, 0xB5, 0x91, 0x48, 0xAA, 0x21, 0x80, 0x68,
+ 0xFF, 0x30, 0x41, 0x30, 0x00, 0x79, 0xFE, 0xF7, 0xFF, 0xFA, 0x8E, 0x48,
+ 0x00, 0x21, 0x41, 0x73, 0x21, 0x21, 0xC1, 0x71, 0x10, 0xBD, 0x10, 0xB5,
+ 0x8B, 0x49, 0x08, 0x68, 0xC2, 0x0B, 0xD2, 0x07, 0x05, 0xD0, 0x01, 0x22,
+ 0xD2, 0x03, 0x90, 0x43, 0x08, 0x60, 0x00, 0xF0, 0xD6, 0xF8, 0xFF, 0xF7,
+ 0x9E, 0xFE, 0x10, 0xBD, 0x10, 0xB5, 0x84, 0x4A, 0x10, 0x68, 0xC1, 0x0B,
+ 0xC9, 0x07, 0x12, 0xD0, 0x01, 0x21, 0xC9, 0x03, 0x88, 0x43, 0x7F, 0x4C,
+ 0x10, 0x60, 0x61, 0x7A, 0x00, 0x29, 0x06, 0xD1, 0x21, 0x46, 0x20, 0x39,
+ 0x49, 0x7C, 0x81, 0x29, 0x08, 0xD0, 0x02, 0x29, 0x11, 0xD0, 0x00, 0x20,
+ 0xE0, 0x71, 0x21, 0x20, 0x20, 0x72, 0xFF, 0xF7, 0x82, 0xFE, 0x10, 0xBD,
+ 0x74, 0x48, 0xAA, 0x21, 0x80, 0x68, 0xFF, 0x30, 0x41, 0x30, 0x00, 0x79,
+ 0xFE, 0xF7, 0xC6, 0xFA, 0x23, 0x20, 0xE0, 0x71, 0xF1, 0xE7, 0x01, 0x21,
+ 0x89, 0x02, 0x08, 0x43, 0x10, 0x60, 0xEC, 0xE7, 0x70, 0xB5, 0x6D, 0x4C,
+ 0x00, 0x25, 0x60, 0x7A, 0x00, 0x28, 0x02, 0xD0, 0xE5, 0x71, 0x25, 0x20,
+ 0x2C, 0xE0, 0x69, 0x4A, 0x6A, 0x49, 0x20, 0x3A, 0x50, 0x7C, 0x66, 0x4E,
+ 0x09, 0x68, 0x51, 0x28, 0x0B, 0xD0, 0x06, 0x28, 0x68, 0xD0, 0x02, 0x28,
+ 0x78, 0xD0, 0x02, 0xF0, 0xB3, 0xFA, 0x00, 0x28, 0x7B, 0xD0, 0x60, 0x7B,
+ 0x40, 0x1C, 0x60, 0x73, 0x66, 0xE0, 0x70, 0x68, 0x40, 0x26, 0xFF, 0x30,
+ 0x01, 0x30, 0x42, 0x78, 0x32, 0x43, 0x42, 0x70, 0x5B, 0x48, 0x10, 0x38,
+ 0x02, 0x79, 0x53, 0x07, 0x62, 0x7B, 0x5B, 0x0F, 0x93, 0x42, 0x01, 0xD1,
+ 0x07, 0x2A, 0x02, 0xD9, 0xE5, 0x71, 0x22, 0x20, 0x04, 0xE0, 0x82, 0x78,
+ 0x93, 0x09, 0x03, 0xD0, 0xE5, 0x71, 0x23, 0x20, 0x20, 0x72, 0x70, 0xBD,
+ 0xCA, 0x70, 0x05, 0x79, 0x0A, 0x78, 0xED, 0x06, 0xBF, 0x23, 0xED, 0x0F,
+ 0x1A, 0x40, 0xAD, 0x01, 0x2A, 0x43, 0x0A, 0x70, 0x05, 0x79, 0x52, 0x06,
+ 0x6D, 0x06, 0xED, 0x0F, 0x52, 0x0E, 0xED, 0x01, 0x2A, 0x43, 0x0A, 0x70,
+ 0x0A, 0x79, 0x45, 0x79, 0xD2, 0x08, 0x6D, 0x07, 0xD2, 0x00, 0x6D, 0x0F,
+ 0x2A, 0x43, 0x0A, 0x71, 0x45, 0x79, 0x52, 0x07, 0xED, 0x08, 0x52, 0x0F,
+ 0xED, 0x00, 0x2A, 0x43, 0x0A, 0x71, 0x4A, 0x79, 0x85, 0x79, 0x52, 0x08,
+ 0xED, 0x07, 0x52, 0x00, 0xED, 0x0F, 0x2A, 0x43, 0x4A, 0x71, 0xF1, 0x25,
+ 0x2A, 0x40, 0x85, 0x79, 0x2D, 0x07, 0x6D, 0x0F, 0x6D, 0x00, 0x2A, 0x43,
+ 0x4A, 0x71, 0xCF, 0x25, 0x2A, 0x40, 0x85, 0x79, 0xAD, 0x06, 0xAD, 0x0F,
+ 0x2D, 0x01, 0x2A, 0x43, 0x4A, 0x71, 0x80, 0x79, 0x1A, 0x40, 0x40, 0x06,
+ 0xC0, 0x0F, 0x80, 0x01, 0x02, 0x43, 0x4A, 0x71, 0xFF, 0xF7, 0x81, 0xFE,
+ 0xE6, 0x71, 0x70, 0xBD, 0x60, 0x7B, 0x40, 0x1C, 0x60, 0x73, 0x90, 0x7C,
+ 0x42, 0x1F, 0xC8, 0x2A, 0x00, 0xD8, 0x48, 0x70, 0xB0, 0x68, 0xAA, 0x21,
+ 0xFF, 0x30, 0x41, 0x30, 0x00, 0x79, 0xFE, 0xF7, 0x33, 0xFA, 0x70, 0xBD,
+ 0x00, 0xE0, 0x06, 0xE0, 0x27, 0x48, 0x01, 0x22, 0x01, 0x68, 0x92, 0x02,
+ 0x11, 0x43, 0x01, 0x60, 0xF5, 0xE7, 0xE5, 0x71, 0x24, 0x20, 0x9D, 0xE7,
+ 0x10, 0xB5, 0x22, 0x49, 0x08, 0x68, 0xC2, 0x0B, 0xD2, 0x07, 0x05, 0xD0,
+ 0x01, 0x22, 0xD2, 0x03, 0x90, 0x43, 0x08, 0x60, 0xFF, 0xF7, 0x5C, 0xFF,
+ 0xFF, 0xF7, 0xCB, 0xFD, 0x10, 0xBD, 0x70, 0xB5, 0x19, 0x4C, 0x20, 0x23,
+ 0x60, 0x7A, 0x00, 0x28, 0x05, 0xD1, 0xE5, 0x1A, 0x68, 0x7C, 0x71, 0x28,
+ 0x05, 0xD0, 0x02, 0x28, 0x1E, 0xD0, 0x00, 0x20, 0xE0, 0x71, 0x23, 0x72,
+ 0xD3, 0xE7, 0x11, 0x4A, 0x51, 0x68, 0xFF, 0x31, 0x01, 0x31, 0x48, 0x78,
+ 0x18, 0x43, 0x48, 0x70, 0x51, 0x68, 0x18, 0x43, 0xFF, 0x31, 0x01, 0x31,
+ 0x48, 0x70, 0x90, 0x68, 0xAA, 0x21, 0xFF, 0x30, 0x41, 0x30, 0x00, 0x79,
+ 0xFE, 0xF7, 0xF4, 0xF9, 0x68, 0x7D, 0xC0, 0x09, 0x01, 0xD0, 0x22, 0x20,
+ 0x00, 0xE0, 0x23, 0x20, 0xE0, 0x71, 0xB8, 0xE7, 0x05, 0x49, 0x01, 0x22,
+ 0x08, 0x68, 0x92, 0x02, 0x10, 0x43, 0x08, 0x60, 0xB1, 0xE7, 0x00, 0x00,
+ 0x80, 0x05, 0x05, 0x00, 0x3C, 0x10, 0x05, 0x00, 0x08, 0x0E, 0x05, 0x00,
+ 0xF4, 0x0D, 0x05, 0x00, 0x57, 0x49, 0x10, 0x20, 0xC8, 0x60, 0x70, 0x47,
+ 0xFE, 0xB5, 0x56, 0x49, 0x00, 0x25, 0x08, 0x68, 0x08, 0x27, 0xC2, 0x0A,
+ 0xD2, 0x07, 0x54, 0x4E, 0x54, 0x4C, 0x00, 0x2A, 0x3E, 0xD0, 0x3A, 0x02,
+ 0x90, 0x43, 0x08, 0x60, 0xFE, 0xF7, 0x85, 0xFA, 0xFE, 0xF7, 0xAB, 0xFB,
+ 0x20, 0x46, 0x40, 0x38, 0x81, 0x8D, 0x40, 0x38, 0x00, 0x23, 0x40, 0x6E,
+ 0x1A, 0x46, 0xFE, 0xF7, 0x6E, 0xFC, 0xA5, 0x75, 0x65, 0x74, 0x20, 0x46,
+ 0xA5, 0x74, 0x0B, 0x21, 0x10, 0x38, 0x03, 0xF0, 0x0B, 0xF8, 0x68, 0x46,
+ 0xFE, 0xF7, 0xC1, 0xF8, 0xE5, 0x75, 0xB0, 0x68, 0x6A, 0x46, 0xFF, 0x30,
+ 0x01, 0x30, 0xC1, 0x78, 0xC9, 0x06, 0x49, 0x0E, 0xD1, 0x80, 0xC0, 0x78,
+ 0x00, 0x21, 0x40, 0x09, 0x00, 0x01, 0x90, 0x80, 0x01, 0x22, 0x68, 0x46,
+ 0xCE, 0xF7, 0xDE, 0xFE, 0x01, 0x20, 0xFE, 0xF7, 0xBB, 0xF8, 0xFE, 0xF7,
+ 0xC4, 0xF8, 0x6A, 0x46, 0xD5, 0x80, 0x01, 0x22, 0x39, 0x49, 0x68, 0x46,
+ 0xCE, 0xF7, 0xD2, 0xFE, 0x70, 0x68, 0xFF, 0x30, 0x01, 0x30, 0x41, 0x78,
+ 0x39, 0x43, 0x41, 0x70, 0x70, 0x69, 0xC0, 0x08, 0xC0, 0x07, 0x0D, 0xD0,
+ 0x70, 0x69, 0xB8, 0x43, 0x70, 0x61, 0x65, 0x76, 0xB0, 0x68, 0x32, 0x4B,
+ 0xFF, 0x30, 0x01, 0x30, 0x81, 0x7E, 0x2F, 0x4A, 0x03, 0x20, 0x98, 0x47,
+ 0x12, 0x20, 0xE0, 0x71, 0xFE, 0xBD, 0x10, 0xB5, 0x28, 0x48, 0x41, 0x69,
+ 0x26, 0x4C, 0x09, 0x09, 0xC9, 0x07, 0x0E, 0xD0, 0x41, 0x69, 0x10, 0x22,
+ 0x91, 0x43, 0x41, 0x61, 0x21, 0x68, 0x20, 0x22, 0x11, 0x43, 0x21, 0x60,
+ 0x40, 0x68, 0x21, 0x21, 0xFF, 0x30, 0x01, 0x30, 0x00, 0x7F, 0xFE, 0xF7,
+ 0x65, 0xF9, 0x20, 0x68, 0xC1, 0x0B, 0xC9, 0x07, 0x05, 0xD0, 0x01, 0x21,
+ 0xC9, 0x03, 0x88, 0x43, 0x20, 0x60, 0x00, 0xF0, 0x03, 0xF8, 0xFF, 0xF7,
+ 0x0C, 0xFD, 0x10, 0xBD, 0x18, 0x48, 0x41, 0x7A, 0x00, 0x29, 0x06, 0xD1,
+ 0x02, 0x46, 0x20, 0x3A, 0x53, 0x7C, 0x01, 0x2B, 0x0A, 0xD0, 0x02, 0x2B,
+ 0x17, 0xD0, 0x00, 0x22, 0xC2, 0x71, 0x07, 0x29, 0x1A, 0xD0, 0x05, 0x29,
+ 0x18, 0xD0, 0x12, 0x21, 0x01, 0x72, 0x70, 0x47, 0x0D, 0x49, 0x93, 0x7C,
+ 0x4A, 0x68, 0xA0, 0x32, 0x13, 0x72, 0x49, 0x68, 0x10, 0x23, 0xFF, 0x31,
+ 0x01, 0x31, 0x4A, 0x78, 0x1A, 0x43, 0x4A, 0x70, 0x20, 0x21, 0xC1, 0x71,
+ 0x70, 0x47, 0x05, 0x49, 0x01, 0x22, 0x08, 0x68, 0x92, 0x02, 0x10, 0x43,
+ 0x08, 0x60, 0x70, 0x47, 0x11, 0x21, 0xE5, 0xE7, 0x00, 0xFC, 0x01, 0x20,
+ 0x08, 0x0E, 0x05, 0x00, 0x80, 0x05, 0x05, 0x00, 0x3C, 0x10, 0x05, 0x00,
+ 0x3B, 0x1A, 0x05, 0x00, 0x25, 0x22, 0x08, 0x00, 0x4D, 0x0A, 0x04, 0x00,
+ 0x01, 0x20, 0xFF, 0x49, 0xC0, 0x02, 0xC8, 0x60, 0x70, 0x47, 0x01, 0x20,
+ 0xFC, 0x49, 0x80, 0x02, 0xC8, 0x60, 0x70, 0x47, 0x10, 0xB5, 0x00, 0xF0,
+ 0x03, 0xF9, 0x00, 0x21, 0x08, 0x46, 0xFE, 0xF7, 0x0D, 0xF9, 0x00, 0xF0,
+ 0xF8, 0xF8, 0x00, 0xF0, 0xF1, 0xF8, 0xFF, 0xF7, 0x44, 0xFD, 0xF5, 0x48,
+ 0x41, 0x21, 0xC1, 0x71, 0x01, 0x46, 0x80, 0x39, 0x4A, 0x6E, 0xCA, 0x67,
+ 0x00, 0x21, 0xC1, 0x74, 0x01, 0x75, 0x41, 0x75, 0x10, 0xBD, 0xF0, 0xB5,
+ 0xEF, 0x4F, 0x00, 0x20, 0x7A, 0x68, 0x15, 0x46, 0xFF, 0x35, 0x21, 0x35,
+ 0x2E, 0x78, 0x00, 0x2E, 0x27, 0xD0, 0xEA, 0x49, 0x4B, 0x88, 0x0C, 0x88,
+ 0xA3, 0x42, 0x23, 0xD9, 0x1B, 0x1B, 0x74, 0x01, 0xA3, 0x42, 0x1F, 0xDD,
+ 0x0B, 0x7C, 0x6C, 0x78, 0xA3, 0x42, 0x01, 0xD2, 0x5B, 0x1C, 0x0B, 0x74,
+ 0xDB, 0xB2, 0xA3, 0x42, 0x15, 0xD3, 0xFF, 0x32, 0x01, 0x32, 0x90, 0x7B,
+ 0x02, 0x23, 0x18, 0x43, 0x90, 0x73, 0x78, 0x68, 0xFF, 0x30, 0x01, 0x30,
+ 0x82, 0x78, 0x1A, 0x43, 0x82, 0x70, 0xB8, 0x68, 0xE0, 0x30, 0x40, 0x7C,
+ 0x80, 0x07, 0x03, 0xD5, 0xE8, 0x20, 0x08, 0x72, 0x00, 0x20, 0xC8, 0x71,
+ 0x01, 0x20, 0xF0, 0xBD, 0x00, 0x23, 0xFF, 0x32, 0x0B, 0x74, 0x01, 0x32,
+ 0x91, 0x7B, 0xFD, 0x23, 0x19, 0x40, 0x91, 0x73, 0xF0, 0xBD, 0xD2, 0x48,
+ 0x80, 0x22, 0x40, 0x68, 0xFF, 0x30, 0x01, 0x30, 0x41, 0x78, 0x11, 0x43,
+ 0x41, 0x70, 0x70, 0x47, 0xF0, 0xB5, 0xCC, 0x48, 0xC1, 0x79, 0x41, 0x29,
+ 0x30, 0xD1, 0x00, 0x21, 0x01, 0x76, 0xCB, 0x49, 0x01, 0x25, 0x0A, 0x68,
+ 0xC8, 0x4E, 0xD1, 0x0B, 0xCB, 0x07, 0x01, 0x46, 0xC9, 0x7D, 0x00, 0x2B,
+ 0x25, 0xD0, 0x43, 0x7A, 0x00, 0x2B, 0x22, 0xD1, 0x03, 0x46, 0x20, 0x3B,
+ 0x5B, 0x7C, 0x03, 0x2B, 0x1D, 0xD1, 0x83, 0x88, 0x00, 0x2B, 0x0D, 0xD1,
+ 0x73, 0x68, 0xFF, 0x33, 0x21, 0x33, 0xDB, 0x7A, 0x9B, 0x07, 0x9B, 0x0F,
+ 0x01, 0x2B, 0x01, 0xD1, 0x00, 0x29, 0x03, 0xD0, 0x02, 0x2B, 0x03, 0xD1,
+ 0x00, 0x29, 0x01, 0xD0, 0x05, 0x76, 0x02, 0xE0, 0x13, 0x09, 0xDB, 0x07,
+ 0x07, 0xD0, 0x01, 0x23, 0xB6, 0x49, 0x1B, 0x03, 0x1A, 0x43, 0x0A, 0x60,
+ 0x42, 0x21, 0xC1, 0x71, 0xF0, 0xBD, 0xB1, 0x4B, 0x80, 0x3B, 0xDF, 0x6D,
+ 0x5C, 0x6E, 0xBC, 0x42, 0xEC, 0xD8, 0x1B, 0x6E, 0x9C, 0x42, 0xE9, 0xD3,
+ 0xAC, 0x4C, 0x40, 0x3C, 0xA3, 0x8D, 0x24, 0x8D, 0xA3, 0x42, 0xE3, 0xD8,
+ 0xA9, 0x4C, 0x40, 0x3C, 0x64, 0x8D, 0x00, 0x29, 0x0E, 0xD0, 0xA3, 0x42,
+ 0xDC, 0xD3, 0x71, 0x68, 0xFF, 0x31, 0x21, 0x31, 0x49, 0x79, 0x00, 0x29,
+ 0xE2, 0xD0, 0xA6, 0x4C, 0x02, 0x23, 0xE3, 0x5E, 0x09, 0x01, 0x8B, 0x42,
+ 0xD0, 0xDC, 0xF0, 0xBD, 0x61, 0x08, 0x8B, 0x42, 0xCC, 0xD3, 0xEE, 0xE7,
+ 0x70, 0xB5, 0x9F, 0x4D, 0x28, 0x88, 0xC0, 0x0B, 0xC0, 0x07, 0x01, 0xD0,
+ 0x00, 0xF0, 0x8C, 0xF8, 0x9A, 0x4C, 0x60, 0x69, 0x80, 0x0A, 0xC0, 0x07,
+ 0x01, 0xD0, 0x00, 0xF0, 0x51, 0xF8, 0x60, 0x69, 0xC0, 0x0A, 0xC0, 0x07,
+ 0x1A, 0xD0, 0x94, 0x48, 0xFF, 0x23, 0xC1, 0x7B, 0x24, 0x33, 0x49, 0x1C,
+ 0xCA, 0xB2, 0xC2, 0x73, 0x61, 0x68, 0x5B, 0x5C, 0x9A, 0x42, 0x07, 0xD3,
+ 0x00, 0x2B, 0x05, 0xD0, 0x00, 0x21, 0xC1, 0x71, 0x34, 0x22, 0x02, 0x72,
+ 0xC1, 0x73, 0x07, 0xE0, 0xFF, 0x31, 0x01, 0x31, 0x48, 0x78, 0x02, 0x22,
+ 0x10, 0x43, 0x48, 0x70, 0x00, 0xF0, 0x12, 0xF8, 0xFF, 0xF7, 0x72, 0xFF,
+ 0xFF, 0xF7, 0xD9, 0xFB, 0x60, 0x69, 0x01, 0x21, 0x89, 0x02, 0x88, 0x43,
+ 0x60, 0x61, 0x60, 0x69, 0x49, 0x00, 0x88, 0x43, 0x60, 0x61, 0x28, 0x68,
+ 0x09, 0x01, 0x88, 0x43, 0x28, 0x60, 0x70, 0xBD, 0x83, 0x4B, 0x81, 0x4A,
+ 0x81, 0x49, 0x02, 0x20, 0x18, 0x47, 0x50, 0x21, 0x80, 0x4B, 0x81, 0x4A,
+ 0x01, 0x20, 0x18, 0x47, 0x78, 0x48, 0x00, 0x21, 0x81, 0x80, 0x81, 0x73,
+ 0xC1, 0x73, 0x01, 0x80, 0x41, 0x80, 0x01, 0x74, 0xC1, 0x74, 0x01, 0x75,
+ 0x41, 0x75, 0x74, 0x48, 0x41, 0x69, 0x01, 0x22, 0x92, 0x02, 0x91, 0x43,
+ 0x41, 0x61, 0x41, 0x69, 0x52, 0x00, 0x91, 0x43, 0x41, 0x61, 0x70, 0x47,
+ 0x70, 0xB5, 0x6D, 0x4C, 0x00, 0x26, 0xA0, 0x7B, 0x6C, 0x4D, 0x40, 0x1C,
+ 0xC0, 0xB2, 0xA0, 0x73, 0x00, 0x28, 0x0F, 0xD0, 0x69, 0x68, 0xFF, 0x31,
+ 0x21, 0x31, 0x89, 0x78, 0x88, 0x42, 0x01, 0xD3, 0x00, 0x29, 0x1D, 0xD1,
+ 0xFE, 0xF7, 0x14, 0xF8, 0xA9, 0x68, 0xFF, 0x31, 0x01, 0x31, 0xC9, 0x7D,
+ 0x88, 0x42, 0x15, 0xD3, 0xA6, 0x80, 0xFE, 0xF7, 0x04, 0xF8, 0xA8, 0x68,
+ 0xE0, 0x30, 0x00, 0x7C, 0x40, 0x07, 0x01, 0xD5, 0x00, 0xF0, 0x26, 0xF9,
+ 0xFD, 0xF7, 0x42, 0xFF, 0xFF, 0xF7, 0xBD, 0xFF, 0x68, 0x68, 0x01, 0x22,
+ 0xFF, 0x30, 0x01, 0x30, 0x41, 0x78, 0x11, 0x43, 0x41, 0x70, 0x70, 0xBD,
+ 0xE6, 0x71, 0x33, 0x20, 0x20, 0x72, 0x70, 0xBD, 0x10, 0xB5, 0x53, 0x48,
+ 0x40, 0x7A, 0x00, 0x28, 0x15, 0xD1, 0x51, 0x48, 0x80, 0x38, 0x41, 0x6E,
+ 0xC1, 0x67, 0x60, 0x30, 0x40, 0x7C, 0x01, 0x28, 0x0E, 0xD0, 0x71, 0x28,
+ 0x0C, 0xD0, 0x51, 0x28, 0x0A, 0xD0, 0x02, 0x28, 0x0B, 0xD0, 0x03, 0x28,
+ 0x10, 0xD0, 0x04, 0x28, 0x11, 0xD0, 0x31, 0x28, 0x12, 0xD0, 0xFF, 0xF7,
+ 0xEA, 0xFE, 0x10, 0xBD, 0x00, 0xF0, 0xC1, 0xF8, 0x10, 0xBD, 0x46, 0x48,
+ 0x01, 0x22, 0x01, 0x68, 0x92, 0x02, 0x11, 0x43, 0x01, 0x60, 0x10, 0xBD,
+ 0x00, 0xF0, 0x07, 0xF8, 0x10, 0xBD, 0x00, 0xF0, 0x31, 0xF8, 0x10, 0xBD,
+ 0x00, 0xF0, 0x64, 0xF8, 0x10, 0xBD, 0x70, 0xB5, 0x3B, 0x48, 0x3B, 0x4C,
+ 0x20, 0x38, 0x80, 0x7C, 0x3A, 0x4A, 0x00, 0x02, 0xA0, 0x80, 0x81, 0xB2,
+ 0x50, 0x68, 0x20, 0x23, 0xFF, 0x30, 0x21, 0x30, 0x01, 0x77, 0x09, 0x0A,
+ 0x41, 0x77, 0x50, 0x68, 0xFF, 0x30, 0x01, 0x30, 0x01, 0x78, 0x19, 0x43,
+ 0x01, 0x70, 0x00, 0x25, 0xA5, 0x73, 0x50, 0x69, 0x59, 0x01, 0x88, 0x43,
+ 0x50, 0x61, 0xFF, 0xF7, 0x64, 0xFF, 0x60, 0x7C, 0x00, 0x28, 0x08, 0xD0,
+ 0xA1, 0x7C, 0x09, 0x29, 0x02, 0xD2, 0x49, 0x1C, 0xA1, 0x74, 0x02, 0xE0,
+ 0x40, 0x1E, 0x60, 0x74, 0xA5, 0x74, 0xFD, 0xF7, 0x92, 0xFF, 0x70, 0xBD,
+ 0x70, 0xB5, 0x26, 0x4C, 0x10, 0x22, 0x60, 0x68, 0x23, 0x4D, 0xFF, 0x30,
+ 0x01, 0x30, 0x01, 0x78, 0x11, 0x43, 0x01, 0x70, 0x20, 0x48, 0x62, 0x68,
+ 0x20, 0x38, 0x80, 0x7C, 0xFF, 0x32, 0x01, 0x02, 0x23, 0x32, 0x11, 0x76,
+ 0x09, 0x0A, 0x51, 0x76, 0x23, 0x49, 0xFF, 0x22, 0x09, 0x68, 0xF5, 0x32,
+ 0xC9, 0x78, 0x50, 0x43, 0x41, 0x43, 0xC8, 0x09, 0x28, 0x80, 0x61, 0x68,
+ 0xA2, 0x31, 0x08, 0x71, 0x00, 0x0A, 0x48, 0x71, 0x60, 0x69, 0x01, 0x21,
+ 0xC9, 0x02, 0x88, 0x43, 0x60, 0x61, 0xFF, 0xF7, 0x27, 0xFF, 0x01, 0x20,
+ 0xCF, 0xF7, 0xE0, 0xF9, 0x68, 0x80, 0x61, 0x68, 0xFF, 0x31, 0x21, 0x31,
+ 0x08, 0x76, 0x00, 0x0A, 0x48, 0x76, 0xFF, 0xF7, 0x38, 0xFE, 0x70, 0xBD,
+ 0x70, 0xB5, 0x0B, 0x4C, 0x10, 0x22, 0x60, 0x68, 0xFF, 0x30, 0x01, 0x30,
+ 0x01, 0x78, 0x11, 0x43, 0x01, 0x70, 0x06, 0x48, 0x20, 0x38, 0xC1, 0x7C,
+ 0x00, 0x7D, 0x09, 0x02, 0x40, 0x18, 0x61, 0x68, 0xFF, 0x31, 0x23, 0x31,
+ 0x08, 0x76, 0x13, 0xE0, 0x00, 0xFC, 0x01, 0x20, 0x3C, 0x10, 0x05, 0x00,
+ 0x80, 0x05, 0x05, 0x00, 0x08, 0x0E, 0x05, 0x00, 0x40, 0x08, 0x05, 0x00,
+ 0xA1, 0x23, 0x08, 0x00, 0xFC, 0x08, 0x00, 0x00, 0x09, 0x0B, 0x04, 0x00,
+ 0xAB, 0x23, 0x08, 0x00, 0xF4, 0x0D, 0x05, 0x00, 0x02, 0x0A, 0x4A, 0x76,
+ 0x41, 0x49, 0xFF, 0x22, 0x09, 0x68, 0xF5, 0x32, 0xC9, 0x78, 0x50, 0x43,
+ 0x41, 0x43, 0x3F, 0x4D, 0xC8, 0x0B, 0x28, 0x80, 0x61, 0x68, 0xA2, 0x31,
+ 0x08, 0x71, 0x00, 0x0A, 0x48, 0x71, 0x60, 0x69, 0x01, 0x21, 0xC9, 0x02,
+ 0x88, 0x43, 0x60, 0x61, 0xFF, 0xF7, 0xDA, 0xFE, 0x01, 0x20, 0xCF, 0xF7,
+ 0x93, 0xF9, 0x68, 0x80, 0x61, 0x68, 0xFF, 0x31, 0x21, 0x31, 0x08, 0x76,
+ 0x00, 0x0A, 0x48, 0x76, 0xFF, 0xF7, 0xEB, 0xFD, 0x70, 0xBD, 0x32, 0x4A,
+ 0x30, 0x49, 0x01, 0x28, 0x04, 0xD0, 0x71, 0x28, 0x0D, 0xD0, 0x51, 0x28,
+ 0x16, 0xD0, 0x1F, 0xE0, 0x50, 0x68, 0x10, 0x23, 0xFF, 0x30, 0x01, 0x30,
+ 0x42, 0x78, 0x1A, 0x43, 0x42, 0x70, 0xC8, 0x7C, 0x40, 0x1C, 0xC8, 0x74,
+ 0x14, 0xE0, 0x50, 0x68, 0x20, 0x23, 0xFF, 0x30, 0x01, 0x30, 0x42, 0x78,
+ 0x1A, 0x43, 0x42, 0x70, 0x08, 0x7D, 0x40, 0x1C, 0x08, 0x75, 0x09, 0xE0,
+ 0x50, 0x68, 0x40, 0x23, 0xFF, 0x30, 0x01, 0x30, 0x42, 0x78, 0x1A, 0x43,
+ 0x42, 0x70, 0x48, 0x7D, 0x40, 0x1C, 0x48, 0x75, 0xC8, 0x7C, 0x05, 0x28,
+ 0x05, 0xD2, 0x08, 0x7D, 0x05, 0x28, 0x02, 0xD2, 0x48, 0x7D, 0x05, 0x28,
+ 0x03, 0xD3, 0x35, 0x20, 0x08, 0x72, 0x00, 0x20, 0xC8, 0x71, 0x70, 0x47,
+ 0x10, 0xB5, 0x15, 0x49, 0x14, 0x48, 0x80, 0x39, 0x80, 0x7B, 0xCA, 0x6F,
+ 0xC0, 0x07, 0x13, 0xD0, 0x12, 0x48, 0x0C, 0x6E, 0x40, 0x68, 0xFF, 0x30,
+ 0x21, 0x30, 0x00, 0x79, 0x43, 0x02, 0x18, 0x1A, 0xC0, 0x01, 0x00, 0xB2,
+ 0x13, 0x18, 0xA3, 0x42, 0x01, 0xD2, 0x40, 0x42, 0x00, 0xB2, 0xCC, 0x6D,
+ 0x13, 0x18, 0xA3, 0x42, 0x00, 0xD9, 0x00, 0x20, 0x10, 0x18, 0x4A, 0x6E,
+ 0x82, 0x42, 0x07, 0xD0, 0x48, 0x66, 0x05, 0x49, 0x00, 0x23, 0x40, 0x39,
+ 0x89, 0x8D, 0x1A, 0x46, 0xFE, 0xF7, 0x37, 0xF9, 0x10, 0xBD, 0x00, 0x00,
+ 0xF4, 0x0D, 0x05, 0x00, 0x3C, 0x10, 0x05, 0x00, 0x80, 0x05, 0x05, 0x00,
+ 0xFE, 0xB5, 0xFF, 0x4D, 0xFF, 0x4A, 0x6C, 0x68, 0x02, 0x94, 0xFF, 0x34,
+ 0x21, 0x34, 0xE3, 0x7A, 0x00, 0x20, 0x99, 0x07, 0x89, 0x0F, 0xD2, 0x7D,
+ 0x01, 0x29, 0x01, 0xD1, 0x00, 0x2A, 0x03, 0xD0, 0x02, 0x29, 0x03, 0xD1,
+ 0x00, 0x2A, 0x01, 0xD0, 0x01, 0x20, 0xFE, 0xBD, 0xD9, 0x43, 0x89, 0x07,
+ 0xFB, 0xD1, 0xFF, 0x21, 0xAB, 0x68, 0x20, 0x31, 0xC9, 0x5C, 0xF4, 0x4E,
+ 0xCF, 0x00, 0xF2, 0x49, 0x35, 0x46, 0x09, 0x68, 0xC0, 0x35, 0x01, 0x95,
+ 0xAD, 0x8D, 0xAE, 0x46, 0x35, 0x46, 0x80, 0x35, 0x00, 0x95, 0x6D, 0x6E,
+ 0xAC, 0x46, 0xFF, 0x25, 0x05, 0x35, 0x75, 0x5F, 0xEC, 0x4E, 0x2D, 0x12,
+ 0x8F, 0x42, 0x17, 0xDA, 0x02, 0x27, 0xF7, 0x5F, 0x8F, 0x42, 0x13, 0xDA,
+ 0x00, 0x2A, 0x11, 0xD0, 0x01, 0x9E, 0x36, 0x8D, 0xB6, 0x45, 0xD8, 0xD3,
+ 0x00, 0x9E, 0x36, 0x6E, 0xB4, 0x45, 0xD4, 0xD8, 0xA2, 0x79, 0xBA, 0x18,
+ 0x8A, 0x42, 0xCF, 0xDB, 0xFF, 0x33, 0x21, 0x33, 0x19, 0x79, 0x8D, 0x42,
+ 0xCA, 0xDC, 0xFE, 0xBD, 0xFF, 0x33, 0x21, 0x33, 0x1F, 0x78, 0xFF, 0x00,
+ 0x8F, 0x42, 0xF8, 0xDD, 0x02, 0x27, 0xF7, 0x5F, 0x8F, 0x42, 0xF4, 0xDD,
+ 0x00, 0x2A, 0xF2, 0xD1, 0x02, 0x9A, 0xFF, 0x32, 0x01, 0x32, 0x52, 0x7E,
+ 0x12, 0x01, 0x96, 0x45, 0xEB, 0xD8, 0x00, 0x9E, 0xF6, 0x6D, 0xB4, 0x45,
+ 0xE7, 0xD3, 0xA2, 0x79, 0x51, 0x18, 0xB9, 0x42, 0xAE, 0xDB, 0x19, 0x79,
+ 0xCD, 0x42, 0xAB, 0xD4, 0xFE, 0xBD, 0x01, 0x20, 0xCF, 0x49, 0x00, 0x03,
+ 0xC8, 0x60, 0x70, 0x47, 0x10, 0xB5, 0xC9, 0x48, 0xC0, 0x7D, 0xFE, 0xF7,
+ 0x95, 0xF9, 0x10, 0xBD, 0xC6, 0x48, 0x40, 0x38, 0x82, 0x8D, 0x01, 0x8D,
+ 0x8A, 0x42, 0x08, 0xD8, 0xC3, 0x49, 0xCB, 0x7D, 0x01, 0x46, 0x49, 0x8D,
+ 0x00, 0x2B, 0x00, 0xD1, 0x49, 0x08, 0x8A, 0x42, 0x00, 0xD2, 0x81, 0x85,
+ 0xBE, 0x48, 0x80, 0x38, 0xC2, 0x6D, 0x41, 0x6E, 0x91, 0x42, 0x02, 0xD8,
+ 0x02, 0x6E, 0x91, 0x42, 0x00, 0xD2, 0x42, 0x66, 0x70, 0x47, 0xB8, 0x4A,
+ 0xFF, 0x23, 0x92, 0x68, 0x41, 0x33, 0xFF, 0x32, 0x01, 0x32, 0x98, 0x42,
+ 0x01, 0xD8, 0x92, 0x7C, 0x06, 0xE0, 0x01, 0x23, 0x5B, 0x02, 0x98, 0x42,
+ 0x01, 0xD8, 0xD2, 0x7C, 0x00, 0xE0, 0x12, 0x7D, 0x4A, 0x43, 0x91, 0x12,
+ 0x41, 0x18, 0x00, 0x29, 0x00, 0xDD, 0x88, 0xB2, 0x70, 0x47, 0x70, 0xB5,
+ 0xAF, 0x48, 0x02, 0x21, 0x41, 0x5E, 0xAA, 0x48, 0x02, 0x24, 0x82, 0x68,
+ 0xA9, 0x4E, 0x10, 0x46, 0xFF, 0x30, 0x21, 0x30, 0x43, 0x78, 0x04, 0x57,
+ 0xCB, 0x1A, 0x5C, 0x43, 0x23, 0x12, 0x03, 0x24, 0x04, 0x57, 0xE0, 0x18,
+ 0xA4, 0x4B, 0x1C, 0x68, 0x20, 0x18, 0xA4, 0x4C, 0x18, 0x60, 0x25, 0x46,
+ 0xF3, 0x7D, 0xC0, 0x35, 0x80, 0x34, 0x00, 0x2B, 0x0F, 0xD0, 0xE3, 0x6D,
+ 0xFF, 0x32, 0x40, 0x1A, 0x01, 0x32, 0x63, 0x66, 0x11, 0x7B, 0x09, 0x09,
+ 0x41, 0x43, 0x68, 0x8D, 0x49, 0x00, 0x40, 0x08, 0xFF, 0xF7, 0xBD, 0xFF,
+ 0xA8, 0x85, 0x00, 0x20, 0x04, 0xE0, 0x28, 0x8D, 0xA8, 0x85, 0x20, 0x6E,
+ 0x60, 0x66, 0x01, 0x20, 0xF0, 0x75, 0xFF, 0xF7, 0x97, 0xFF, 0xF0, 0x7D,
+ 0x00, 0x28, 0x09, 0xD0, 0x00, 0x20, 0xFD, 0xF7, 0xCC, 0xFC, 0xA9, 0x8D,
+ 0x93, 0x4B, 0x00, 0x22, 0x60, 0x6E, 0xFE, 0xF7, 0x46, 0xF8, 0x70, 0xBD,
+ 0x01, 0x20, 0xF4, 0xE7, 0x01, 0x20, 0x8E, 0x49, 0x40, 0x03, 0xC8, 0x60,
+ 0x70, 0x47, 0xF8, 0xB5, 0x8A, 0x49, 0x02, 0x20, 0x08, 0x5E, 0x87, 0x49,
+ 0x0A, 0x27, 0x0A, 0x68, 0x10, 0x1A, 0x83, 0x4A, 0x93, 0x68, 0xDA, 0x1D,
+ 0xF9, 0x32, 0x14, 0x7B, 0x55, 0x7A, 0x26, 0x07, 0x36, 0x0F, 0x45, 0x43,
+ 0x7E, 0x43, 0x75, 0x43, 0x4E, 0x68, 0x84, 0x4F, 0xAD, 0x19, 0x4D, 0x60,
+ 0x96, 0x7A, 0x7E, 0x43, 0xB5, 0x42, 0x02, 0xDC, 0x76, 0x42, 0xB5, 0x42,
+ 0x00, 0xDA, 0x4E, 0x60, 0x24, 0x09, 0x7D, 0x25, 0x44, 0x43, 0xED, 0x00,
+ 0x6C, 0x43, 0x49, 0x68, 0x64, 0x25, 0x61, 0x18, 0xD4, 0x7A, 0x89, 0x12,
+ 0x6C, 0x43, 0xA1, 0x42, 0x02, 0xDC, 0x64, 0x42, 0xA1, 0x42, 0x00, 0xDA,
+ 0x21, 0x46, 0x00, 0x28, 0x01, 0xDD, 0x01, 0x27, 0x00, 0xE0, 0x00, 0x27,
+ 0x6F, 0x4D, 0xE0, 0x33, 0x2E, 0x46, 0x58, 0x7C, 0xC0, 0x36, 0xC3, 0x07,
+ 0x80, 0x35, 0xB0, 0x8D, 0x6C, 0x6E, 0x00, 0x2B, 0x0B, 0xD0, 0x00, 0x2F,
+ 0x03, 0xD0, 0x33, 0x8D, 0x98, 0x42, 0x05, 0xD3, 0x02, 0xE0, 0xEB, 0x6D,
+ 0x9C, 0x42, 0x01, 0xD2, 0x00, 0x23, 0x00, 0xE0, 0x01, 0x23, 0x00, 0x2B,
+ 0x03, 0xD0, 0xFF, 0xF7, 0x4E, 0xFF, 0xB0, 0x85, 0x1D, 0xE0, 0x66, 0x48,
+ 0x84, 0x42, 0x01, 0xD8, 0x50, 0x7B, 0x0F, 0xE0, 0x64, 0x48, 0x84, 0x42,
+ 0x01, 0xD8, 0x90, 0x7B, 0x0A, 0xE0, 0x63, 0x48, 0x84, 0x42, 0x01, 0xD8,
+ 0xD0, 0x7B, 0x05, 0xE0, 0x61, 0x48, 0x84, 0x42, 0x01, 0xD8, 0x10, 0x7C,
+ 0x00, 0xE0, 0x50, 0x7C, 0x48, 0x43, 0x0A, 0x21, 0x02, 0xF0, 0x8C, 0xFB,
+ 0x20, 0x1A, 0x00, 0x28, 0x00, 0xDD, 0x04, 0x46, 0x6C, 0x66, 0xFF, 0xF7,
+ 0x11, 0xFF, 0x00, 0x23, 0xB1, 0x8D, 0x1A, 0x46, 0x68, 0x6E, 0xFD, 0xF7,
+ 0xC6, 0xFF, 0xF8, 0xBD, 0x10, 0xB5, 0x10, 0x20, 0x02, 0xF0, 0x8E, 0xF9,
+ 0x48, 0x48, 0x40, 0x22, 0x40, 0x68, 0xFF, 0x30, 0x01, 0x30, 0x01, 0x78,
+ 0x11, 0x43, 0x01, 0x70, 0x45, 0x48, 0x01, 0x7E, 0xD0, 0x01, 0x00, 0x29,
+ 0x04, 0xD0, 0x4E, 0x49, 0x0A, 0x68, 0x02, 0x43, 0x0A, 0x60, 0x10, 0xBD,
+ 0x44, 0x49, 0xC8, 0x60, 0x10, 0xBD, 0x01, 0x20, 0x42, 0x49, 0xC0, 0x03,
+ 0xC8, 0x60, 0x70, 0x47, 0x01, 0x20, 0x40, 0x49, 0x80, 0x03, 0xC8, 0x60,
+ 0x70, 0x47, 0xF8, 0xB5, 0x44, 0x4E, 0x45, 0x4D, 0x30, 0x68, 0x01, 0x0B,
+ 0xC9, 0x07, 0x0C, 0xD0, 0x01, 0x21, 0x09, 0x03, 0x88, 0x43, 0x30, 0x60,
+ 0x41, 0x48, 0x43, 0x4B, 0x00, 0x68, 0x41, 0x4A, 0x41, 0x78, 0x02, 0x20,
+ 0x98, 0x47, 0x20, 0x20, 0xA8, 0x47, 0x2F, 0x4F, 0x78, 0x69, 0x2F, 0x4C,
+ 0x80, 0x0B, 0xC0, 0x07, 0x00, 0x28, 0x1F, 0xD0, 0x78, 0x69, 0x01, 0x21,
+ 0x89, 0x03, 0x88, 0x43, 0x78, 0x61, 0x30, 0x68, 0x01, 0x09, 0xC9, 0x07,
+ 0x0D, 0xD0, 0x38, 0x48, 0xFD, 0xF7, 0x0D, 0xFF, 0x00, 0x28, 0x03, 0xD0,
+ 0x30, 0x68, 0x10, 0x21, 0x88, 0x43, 0x0A, 0xE0, 0x01, 0x20, 0x27, 0x49,
+ 0xC0, 0x03, 0xC8, 0x60, 0x06, 0xE0, 0x21, 0x7E, 0x00, 0x29, 0xF7, 0xD0,
+ 0x01, 0x21, 0x49, 0x03, 0x08, 0x43, 0x30, 0x60, 0x20, 0x20, 0xA8, 0x47,
+ 0x30, 0x68, 0x41, 0x0B, 0xC9, 0x07, 0x07, 0xD0, 0x01, 0x21, 0x49, 0x03,
+ 0x88, 0x43, 0x30, 0x60, 0x01, 0x20, 0x60, 0x76, 0x00, 0xF0, 0x66, 0xF8,
+ 0x78, 0x69, 0x40, 0x0B, 0xC0, 0x07, 0x13, 0xD0, 0x23, 0x49, 0x05, 0x20,
+ 0x2A, 0x31, 0x88, 0x47, 0x79, 0x69, 0x01, 0x20, 0x40, 0x03, 0x81, 0x43,
+ 0x79, 0x61, 0x79, 0x69, 0x40, 0x10, 0x81, 0x43, 0x79, 0x61, 0x05, 0x21,
+ 0x1C, 0x4B, 0x1E, 0x4A, 0x04, 0x20, 0x98, 0x47, 0x20, 0x20, 0xA8, 0x47,
+ 0x78, 0x69, 0xC0, 0x0B, 0xC0, 0x07, 0x0A, 0xD0, 0x79, 0x69, 0x01, 0x20,
+ 0xC0, 0x03, 0x81, 0x43, 0x79, 0x61, 0x00, 0x20, 0x60, 0x76, 0x20, 0x20,
+ 0xA8, 0x47, 0x41, 0x20, 0xE0, 0x71, 0x78, 0x69, 0x00, 0x0B, 0xC0, 0x07,
+ 0x37, 0xD0, 0x25, 0xE0, 0x80, 0x05, 0x05, 0x00, 0x3C, 0x10, 0x05, 0x00,
+ 0xE8, 0x0D, 0x05, 0x00, 0x3C, 0x0F, 0x05, 0x00, 0x40, 0x08, 0x05, 0x00,
+ 0x00, 0xFC, 0x01, 0x20, 0xBD, 0x29, 0x08, 0x00, 0xA0, 0x86, 0x01, 0x00,
+ 0xE0, 0x22, 0x02, 0x00, 0x00, 0x71, 0x02, 0x00, 0x20, 0xBF, 0x02, 0x00,
+ 0xC8, 0x20, 0x03, 0x00, 0x08, 0x0E, 0x05, 0x00, 0x95, 0x5F, 0x04, 0x00,
+ 0xF4, 0x0D, 0x05, 0x00, 0xF1, 0x2B, 0x08, 0x00, 0x4D, 0x0A, 0x04, 0x00,
+ 0xB5, 0x2B, 0x08, 0x00, 0xE7, 0x2B, 0x08, 0x00, 0x79, 0x69, 0x01, 0x20,
+ 0x00, 0x03, 0x81, 0x43, 0x79, 0x61, 0xB8, 0x68, 0x1D, 0x4B, 0xFF, 0x30,
+ 0x01, 0x30, 0x41, 0x7A, 0x1A, 0x4A, 0x05, 0x20, 0x98, 0x47, 0xFF, 0xF7,
+ 0xBC, 0xFE, 0x21, 0x20, 0xA8, 0x47, 0xFE, 0xF7, 0xF8, 0xFF, 0xF8, 0xBD,
+ 0xF8, 0xB5, 0x18, 0x4A, 0x16, 0x4D, 0x02, 0x21, 0x04, 0x20, 0x51, 0x5E,
+ 0x28, 0x5E, 0x01, 0x22, 0xD2, 0x03, 0x80, 0x18, 0x41, 0x43, 0x14, 0x4C,
+ 0xC9, 0x13, 0x14, 0x48, 0x21, 0x60, 0x40, 0x68, 0xFF, 0x30, 0x21, 0x30,
+ 0x40, 0x79, 0x00, 0x28, 0x03, 0xD0, 0x00, 0x01, 0x81, 0x42, 0x00, 0xDD,
+ 0x20, 0x60, 0x14, 0x21, 0x09, 0x4E, 0x0E, 0x4A, 0x03, 0x20, 0xB0, 0x47,
+ 0x00, 0x27, 0x67, 0x60, 0xFF, 0xF7, 0xA4, 0xFD, 0x00, 0x28, 0x01, 0xD0,
+ 0xFF, 0xF7, 0x47, 0xFE, 0x01, 0x21, 0x02, 0x4A, 0x05, 0x20, 0xB0, 0x47,
+ 0x2F, 0x76, 0xF8, 0xBD, 0xB3, 0x29, 0x08, 0x00, 0x4D, 0x0A, 0x04, 0x00,
+ 0x3C, 0x10, 0x05, 0x00, 0x40, 0x08, 0x05, 0x00, 0xE8, 0x0D, 0x05, 0x00,
+ 0x80, 0x05, 0x05, 0x00, 0xB9, 0x2A, 0x08, 0x00, 0x10, 0xB5, 0xFD, 0xF7,
+ 0x7F, 0xFC, 0xFE, 0xF7, 0xFB, 0xFF, 0xFD, 0xF7, 0xDD, 0xFC, 0xFD, 0xF7,
+ 0xD0, 0xFE, 0x4E, 0x48, 0x01, 0x7A, 0xE0, 0x29, 0x02, 0xD3, 0xE0, 0x21,
+ 0x01, 0x72, 0x04, 0xE0, 0x4B, 0x49, 0x04, 0x23, 0x0A, 0x68, 0x1A, 0x43,
+ 0x0A, 0x60, 0x01, 0x21, 0xC1, 0x71, 0x10, 0xBD, 0x48, 0x49, 0x04, 0x20,
+ 0xC8, 0x60, 0x70, 0x47, 0xF8, 0xB5, 0x45, 0x4E, 0x43, 0x4D, 0x30, 0x68,
+ 0x41, 0x08, 0xC9, 0x07, 0x00, 0x29, 0x05, 0xD0, 0x02, 0x22, 0x90, 0x43,
+ 0x51, 0x04, 0x08, 0x43, 0x30, 0x60, 0xEA, 0x71, 0xC1, 0x07, 0x04, 0x27,
+ 0x00, 0x29, 0x04, 0xD0, 0x40, 0x08, 0x40, 0x00, 0x30, 0x60, 0x3C, 0x48,
+ 0xC7, 0x60, 0x3C, 0x4C, 0x60, 0x69, 0x80, 0x08, 0xC0, 0x07, 0x0C, 0xD0,
+ 0x3A, 0x49, 0x00, 0x20, 0x88, 0x47, 0x60, 0x69, 0xB8, 0x43, 0x60, 0x61,
+ 0x01, 0x21, 0x30, 0x68, 0x89, 0x04, 0x08, 0x43, 0x30, 0x60, 0x03, 0x20,
+ 0xE8, 0x71, 0x30, 0x68, 0x81, 0x08, 0xC9, 0x07, 0x0A, 0xD0, 0xB8, 0x43,
+ 0x30, 0x60, 0x60, 0x68, 0x30, 0x4B, 0xFF, 0x30, 0x01, 0x30, 0xC1, 0x7E,
+ 0x00, 0x20, 0x2F, 0x4A, 0x2A, 0x3B, 0x98, 0x47, 0x2E, 0x48, 0x00, 0x78,
+ 0x00, 0x06, 0x60, 0x68, 0x0C, 0xD5, 0xFF, 0x30, 0x01, 0x30, 0x01, 0x7B,
+ 0x80, 0x22, 0x11, 0x43, 0x01, 0x73, 0x60, 0x68, 0xFF, 0x30, 0x01, 0x30,
+ 0x01, 0x78, 0x11, 0x43, 0x01, 0x70, 0x05, 0xE0, 0xFF, 0x30, 0x01, 0x30,
+ 0x01, 0x7B, 0x49, 0x06, 0x49, 0x0E, 0x01, 0x73, 0xFE, 0xF7, 0x4F, 0xFF,
+ 0xF8, 0xBD, 0x10, 0xB5, 0x1A, 0x48, 0x81, 0x7D, 0xFF, 0x29, 0x01, 0xD2,
+ 0x49, 0x1C, 0x81, 0x75, 0x1A, 0x49, 0x10, 0x23, 0x89, 0x68, 0xE0, 0x31,
+ 0x89, 0x7C, 0x4A, 0x09, 0x00, 0x2A, 0x0A, 0xD0, 0x82, 0x7D, 0x49, 0x09,
+ 0x8A, 0x42, 0x06, 0xD3, 0x12, 0x4A, 0xDC, 0x01, 0x11, 0x68, 0x21, 0x43,
+ 0x11, 0x60, 0xC3, 0x71, 0x10, 0xBD, 0x03, 0x72, 0x00, 0x21, 0xC1, 0x71,
+ 0x10, 0xBD, 0x10, 0xB5, 0x0C, 0x49, 0x01, 0x22, 0x08, 0x68, 0x92, 0x04,
+ 0x90, 0x43, 0xD2, 0x11, 0x10, 0x43, 0x08, 0x60, 0x07, 0x48, 0x10, 0x21,
+ 0xC1, 0x71, 0xFE, 0xF7, 0x22, 0xFF, 0x10, 0xBD, 0x10, 0xB5, 0x04, 0x48,
+ 0xE3, 0x21, 0x01, 0x72, 0x00, 0x21, 0xC1, 0x71, 0xFE, 0xF7, 0x19, 0xFF,
+ 0x10, 0xBD, 0x00, 0x00, 0x3C, 0x10, 0x05, 0x00, 0x08, 0x0E, 0x05, 0x00,
+ 0x00, 0xFC, 0x01, 0x20, 0x80, 0x05, 0x05, 0x00, 0x33, 0x0B, 0x04, 0x00,
+ 0xF9, 0x2D, 0x08, 0x00, 0x60, 0xC1, 0x01, 0x20, 0x10, 0xB5, 0x04, 0x46,
+ 0xBB, 0x48, 0x41, 0x7B, 0x00, 0x29, 0x04, 0xD1, 0x61, 0x1C, 0x02, 0x29,
+ 0x01Why this scored 15/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.