diff --git a/README.md b/README.md
index 771675a2..ded23458 100644
--- a/README.md
+++ b/README.md
@@ -40,10 +40,11 @@ see the [Support Matrices](support-matrices.md).
   - [x] Get firmware version from system (`--versions`)
     - [x] BIOS
     - [x] EC
-    - [x] PD
+    - [x] PD Controller
     - [x] ME (Only on Linux)
     - [x] Retimer
-    - [x] Touchpad (Linux and Windows)
+    - [x] Touchpad (Linux, Windows, FreeBSD, not UEFI)
+    - [x] Touchscreen (Linux, Windows, FreeBSD, not UEFI)
   - [x] Get Expansion Card Firmware (Not on UEFI so far)
     - [x] HDMI Expansion Card (`--dp-hdmi-info`)
     - [x] DisplayPort Expansion Card (`--dp-hdmi-info`)
@@ -68,7 +69,9 @@ All of these need EC communication support in order to work.
 
 - [x] Get and set keyboard brightness (`--kblight`)
 - [x] Get and set battery charge limit (`--charge-limit`)
-- [x] Get and set fingerprint LED brightness (`--fp-brightness`)
+- [x] Get and set fingerprint LED brightness (`--fp-brightness`, `--fp-led-level`)
+- [x] Override tablet mode, instead of follow G-Sensor and hall sensor (`--tablet-mode`)
+- [x] Disable/Enable touchscreen (`--touchscreen-enable`)
 
 ###### Communication with Embedded Controller
 
@@ -176,9 +179,30 @@ Options:
       --h2o-capsule <H2O_CAPSULE>   Parse UEFI Capsule information from binary file
       --intrusion                   Show status of intrusion switch
       --inputmodules                Show status of the input modules (Framework 16 only)
+      --input-deck-mode <INPUT_DECK_MODE>
+          Set input deck power mode [possible values: auto, off, on] (Framework 16 only) [possible values: auto, off, on]
+      --charge-limit [<CHARGE_LIMIT>]
+          Get or set max charge limit
+      --get-gpio <GET_GPIO>
+          Get GPIO value by name
+      --fp-led-level [<FP_LED_LEVEL>]
+          Get or set fingerprint LED brightness level [possible values: high, medium, low, ultra-low, auto]
+      --fp-brightness [<FP_BRIGHTNESS>]
+          Get or set fingerprint LED brightness percentage
       --kblight [<KBLIGHT>]         Set keyboard backlight percentage or get, if no value provided
+      --tablet-mode <TABLET_MODE>   Set tablet mode override [possible values: auto, tablet, laptop]
+      --touchscreen-enable <TOUCHSCREEN_ENABLE>
+          Enable/disable touchscreen [possible values: true, false]
       --console <CONSOLE>           Get EC console, choose whether recent or to follow the output [possible values: recent, follow]
+      --reboot-ec <REBOOT_EC>       Control EC RO/RW jump [possible values: reboot, jump-ro, jump-rw, cancel-jump, disable-jump]
+      --hash <HASH>                 Hash a file of arbitrary data
       --driver <DRIVER>             Select which driver is used. By default portio is used [possible values: portio, cros-ec, windows]
+      --pd-addrs <PD_ADDRS> <PD_ADDRS>
+          Specify I2C addresses of the PD chips (Advanced)
+      --pd-ports <PD_PORTS> <PD_PORTS>
+          Specify I2C ports of the PD chips (Advanced)
+      --has-mec <HAS_MEC>
+          Specify the type of EC chip (MEC/MCHP or other) [possible values: true, false]
   -t, --test                        Run self-test to check if interaction with EC is possible
   -h, --help                        Print help information
 ```
diff --git a/framework_lib/Cargo.toml b/framework_lib/Cargo.toml
index fb5096ca..b0d41eec 100644
--- a/framework_lib/Cargo.toml
+++ b/framework_lib/Cargo.toml
@@ -63,7 +63,7 @@ uefi = { version = "0.20", features = ["alloc"], optional = true }
 uefi-services = { version = "0.17", optional = true }
 plain = { version = "0.2.3", optional = true }
 spin = { version = "0.9.8", optional = false }
-hidapi = { version = "2.6.3", optional = true }
+hidapi = { version = "2.6.3", optional = true, features = [ "windows-native" ] }
 rusb = { version = "0.9.4", optional = true }
 no-std-compat = { version = "0.4.1", features = [ "alloc" ] }
 guid_macros = { path = "../guid_macros" }
@@ -89,5 +89,12 @@ features = [
     "Win32_System_IO",
     "Win32_System_Ioctl",
     "Win32_System_SystemServices",
+    # For HID devices
+    "Win32_Devices_DeviceAndDriverInstallation",
+    "Win32_Devices_HumanInterfaceDevice",
+    "Win32_Devices_Properties",
+    "Win32_Storage_EnhancedStorage",
+    "Win32_System_Threading",
+    "Win32_UI_Shell_PropertiesSystem"
 ]
 
diff --git a/framework_lib/src/commandline/clap_std.rs b/framework_lib/src/commandline/clap_std.rs
index f9299e6d..6bbd6b40 100644
--- a/framework_lib/src/commandline/clap_std.rs
+++ b/framework_lib/src/commandline/clap_std.rs
@@ -162,6 +162,11 @@ struct ClapCli {
     #[arg(long)]
     tablet_mode: Option<TabletModeArg>,
 
+    /// Enable/disable touchscreen
+    #[clap(value_enum)]
+    #[arg(long)]
+    touchscreen_enable: Option<bool>,
+
     /// Get EC console, choose whether recent or to follow the output
     #[clap(value_enum)]
     #[arg(long)]
@@ -284,6 +289,7 @@ pub fn parse(args: &[String]) -> Cli {
         kblight: args.kblight,
         rgbkbd: args.rgbkbd,
         tablet_mode: args.tablet_mode,
+        touchscreen_enable: args.touchscreen_enable,
         console: args.console,
         reboot_ec: args.reboot_ec,
         hash: args.hash.map(|x| x.into_os_string().into_string().unwrap()),
diff --git a/framework_lib/src/commandline/mod.rs b/framework_lib/src/commandline/mod.rs
index fa284951..03179a1c 100644
--- a/framework_lib/src/commandline/mod.rs
+++ b/framework_lib/src/commandline/mod.rs
@@ -52,6 +52,8 @@ use crate::smbios::ConfigDigit0;
 use crate::smbios::{dmidecode_string_val, get_smbios, is_framework};
 #[cfg(feature = "hidapi")]
 use crate::touchpad::print_touchpad_fw_ver;
+#[cfg(any(feature = "hidapi", feature = "windows"))]
+use crate::touchscreen;
 #[cfg(feature = "uefi")]
 use crate::uefi::enable_page_break;
 use crate::util;
@@ -173,6 +175,7 @@ pub struct Cli {
     pub kblight: Option<Option<u8>>,
     pub rgbkbd: Vec<u64>,
     pub tablet_mode: Option<TabletModeArg>,
+    pub touchscreen_enable: Option<bool>,
     pub console: Option<ConsoleArg>,
     pub reboot_ec: Option<RebootEcArg>,
     pub hash: Option<String>,
@@ -479,6 +482,9 @@ fn print_versions(ec: &CrosEc) {
 
     #[cfg(feature = "hidapi")]
     let _ignore_err = print_touchpad_fw_ver();
+
+    #[cfg(feature = "hidapi")]
+    let _ignore_err = touchscreen::print_fw_ver();
 }
 
 fn print_esrt() {
@@ -793,6 +799,11 @@ pub fn run_with_args(args: &Cli, _allupdate: bool) -> i32 {
             TabletModeArg::Laptop => TabletModeOverride::ForceClamshell,
         };
         ec.set_tablet_mode(mode);
+    } else if let Some(_enable) = &args.touchscreen_enable {
+        #[cfg(any(feature = "hidapi", feature = "windows"))]
+        if touchscreen::enable_touch(*_enable).is_none() {
+            error!("Failed to enable/disable touch");
+        }
     } else if let Some(console_arg) = &args.console {
         match console_arg {
             ConsoleArg::Follow => {
diff --git a/framework_lib/src/commandline/uefi.rs b/framework_lib/src/commandline/uefi.rs
index 261720fc..39ea2867 100644
--- a/framework_lib/src/commandline/uefi.rs
+++ b/framework_lib/src/commandline/uefi.rs
@@ -89,6 +89,7 @@ pub fn parse(args: &[String]) -> Cli {
         kblight: None,
         rgbkbd: vec![],
         tablet_mode: None,
+        touchscreen_enable: None,
         console: None,
         reboot_ec: None,
         hash: None,
diff --git a/framework_lib/src/lib.rs b/framework_lib/src/lib.rs
index 50fa9b03..b0c92e4d 100644
--- a/framework_lib/src/lib.rs
+++ b/framework_lib/src/lib.rs
@@ -18,6 +18,10 @@ pub mod audio_card;
 pub mod camera;
 #[cfg(feature = "hidapi")]
 pub mod touchpad;
+#[cfg(any(feature = "hidapi", feature = "windows"))]
+pub mod touchscreen;
+#[cfg(feature = "windows")]
+pub mod touchscreen_win;
 
 #[cfg(feature = "uefi")]
 #[macro_use]
diff --git a/framework_lib/src/touchscreen.rs b/framework_lib/src/touchscreen.rs
new file mode 100644
index 00000000..c155c173
--- /dev/null
+++ b/framework_lib/src/touchscreen.rs
@@ -0,0 +1,156 @@
+use hidapi::{HidApi, HidDevice};
+
+#[cfg(target_os = "windows")]
+use crate::touchscreen_win;
+
+pub const ILI_VID: u16 = 0x222A;
+pub const ILI_PID: u16 = 0x5539;
+pub const USI_BITMAP: u8 = 1 << 1;
+pub const MPP_BITMAP: u8 = 1 << 2;
+
+struct HidapiTouchScreen {
+    device: HidDevice,
+}
+
+impl TouchScreen for HidapiTouchScreen {
+    fn open_device() -> Option<HidapiTouchScreen> {
+        debug!("Looking for touchscreen HID device");
+        match HidApi::new() {
+            Ok(api) => {
+                for dev_info in api.device_list() {
+                    let vid = dev_info.vendor_id();
+                    let pid = dev_info.product_id();
+                    let usage_page = dev_info.usage_page();
+                    if vid != ILI_VID {
+                        trace!("    Skipping VID:PID. Expected {:04X}:*", ILI_VID);
+                        continue;
+                    }
+                    debug!(
+                        "  Found {:04X}:{:04X} (Usage Page {:04X})",
+                        vid, pid, usage_page
+                    );
+                    if usage_page != 0xFF00 {
+                        debug!("    Skipping usage page. Expected {:04X}", 0xFF00);
+                        continue;
+                    }
+                    if pid != ILI_PID {
+                        debug!("  Warning: PID is {:04X}, expected {:04X}", pid, ILI_PID);
+                    }
+
+                    debug!("  Found matching touchscreen HID device");
+                    debug!("  Path:             {:?}", dev_info.path());
+                    debug!("  IC Type:          {:04X}", pid);
+
+                    // Unwrapping because if we can enumerate it, we should be able to open it
+                    let device = dev_info.open_device(&api).unwrap();
+                    debug!("  Opened device.");
+
+                    return Some(HidapiTouchScreen { device });
+                }
+            }
+            Err(e) => {
+                error!("Failed to open hidapi. Error: {e}");
+            }
+        };
+
+        None
+    }
+
+    fn send_message(&self, message_id: u8, read_len: usize, data: Vec<u8>) -> Option<Vec<u8>> {
+        let report_id = 0x03;
+        let data_len = data.len();
+        let mut msg = [0u8; 0x40];
+        msg[0] = report_id;
+        msg[1] = 0xA3;
+        msg[2] = data_len as u8;
+        msg[3] = read_len as u8;
+        msg[4] = message_id;
+        for (i, b) in data.into_iter().enumerate() {
+            msg[5 + i] = b;
+        }
+
+        // Not sure why, but on Windows we just have to write an output report
+        // HidApiError { message: "HidD_SetFeature: (0x00000057) The parameter is incorrect." }
+        // Still doesn't work on Windows. Need to write a byte more than the buffer is long
+        #[cfg(target_os = "windows")]
+        let send_feature_report = false;
+        #[cfg(not(target_os = "windows"))]
+        let send_feature_report = true;
+
+        if send_feature_report {
+            debug!("  send_feature_report {:X?}", msg);
+            self.device.send_feature_report(&msg).ok()?;
+        } else {
+            debug!("  Writing {:X?}", msg);
+            self.device.write(&msg).ok()?;
+        };
+
+        if read_len == 0 {
+            return Some(vec![]);
+        }
+
+        let msg_len = 3 + data_len;
+        let mut buf: [u8; 0x40] = [0; 0x40];
+        debug!("  Reading");
+        let res = self.device.read(&mut buf);
+        debug!("  res: {:?}", res);
+        debug!("  Read buf: {:X?}", buf);
+        Some(buf[msg_len..msg_len + read_len].to_vec())
+    }
+}
+
+pub trait TouchScreen {
+    fn open_device() -> Option<Self>
+    where
+        Self: std::marker::Sized;
+    fn send_message(&self, message_id: u8, read_len: usize, data: Vec<u8>) -> Option<Vec<u8>>;
+
+    fn check_fw_version(&self) -> Option<()> {
+        println!("Touchscreen");
+        let res = self.send_message(0x42, 3, vec![0])?;
+        let ver = res
+            .iter()
+            .skip(1)
+            .fold(format!("{:02X}", res[0]), |acc, &x| {
+                acc + "." + &format!("{:02X}", x)
+            });
+        // Expecting 06.00.0A
+        debug!("  Protocol Version: v{}", ver);
+
+        let res = self.send_message(0x40, 8, vec![0])?;
+        let ver = res
+            .iter()
+            .skip(1)
+            .fold(res[0].to_string(), |acc, &x| acc + "." + &x.to_string());
+        println!("  Firmware Version: v{}", ver);
+
+        let res = self.send_message(0x20, 16, vec![0])?;
+        println!("  USI Protocol:     {:?}", (res[15] & USI_BITMAP) > 0);
+        println!("  MPP Protocol:     {:?}", (res[15] & MPP_BITMAP) > 0);
+
+        Some(())
+    }
+
+    fn enable_touch(&self, enable: bool) -> Option<()> {
+        self.send_message(0x38, 0, vec![!enable as u8, 0x00])?;
+        Some(())
+    }
+}
+
+pub fn print_fw_ver() -> Option<()> {
+    #[cfg(target_os = "windows")]
+    let device = touchscreen_win::NativeWinTouchScreen::open_device()?;
+    #[cfg(not(target_os = "windows"))]
+    let device = HidapiTouchScreen::open_device()?;
+
+    device.check_fw_version()
+}
+
+pub fn enable_touch(enable: bool) -> Option<()> {
+    #[cfg(target_os = "windows")]
+    let device = touchscreen_win::NativeWinTouchScreen::open_device()?;
+    #[cfg(not(target_os = "windows"))]
+    let device = HidapiTouchScreen::open_device()?;
+
+    device.enable_touch(enable)
+}
diff --git a/framework_lib/src/touchscreen_win.rs b/framework_lib/src/touchscreen_win.rs
new file mode 100644
index 00000000..2a1be801
--- /dev/null
+++ b/framework_lib/src/touchscreen_win.rs
@@ -0,0 +1,101 @@
+use crate::touchscreen::TouchScreen;
+#[allow(unused_imports)]
+use windows::{
+    core::*,
+    Win32::{
+        Devices::HumanInterfaceDevice::*,
+        Devices::Properties::*,
+        Foundation::*,
+        Storage::FileSystem::*,
+        System::Threading::ResetEvent,
+        System::IO::{CancelIoEx, DeviceIoControl},
+        System::{Ioctl::*, IO::*},
+    },
+};
+
+pub struct NativeWinTouchScreen {
+    handle: HANDLE,
+}
+
+impl TouchScreen for NativeWinTouchScreen {
+    fn open_device() -> Option<Self> {
+        // TODO: I don't know if this might be different on other systems
+        // Should enumerate and find the right one
+        // See: https://learn.microsoft.com/en-us/windows-hardware/drivers/hid/finding-and-opening-a-hid-collection
+        let path =
+            w!(r"\\?\HID#ILIT2901&Col03#5&357cbf85&0&0002#{4d1e55b2-f16f-11cf-88cb-001111000030}");
+
+        let res = unsafe {
+            CreateFileW(
+                path,
+                FILE_GENERIC_WRITE.0 | FILE_GENERIC_READ.0,
+                FILE_SHARE_READ | FILE_SHARE_WRITE,
+                None,
+                OPEN_EXISTING,
+                // hidapi-rs is using FILE_FLAG_OVERLAPPED but it doesn't look like we need that
+                FILE_FLAGS_AND_ATTRIBUTES(0),
+                None,
+            )
+        };
+        let handle = match res {
+            Ok(h) => h,
+            Err(err) => {
+                error!("Failed to open device {:?}", err);
+                return None;
+            }
+        };
+
+        debug!("Opened {:?}", path);
+
+        Some(NativeWinTouchScreen { handle })
+    }
+
+    fn send_message(&self, message_id: u8, read_len: usize, data: Vec<u8>) -> Option<Vec<u8>> {
+        let report_id = 0x03;
+        let data_len = data.len();
+        let mut msg = [0u8; 0x40];
+        let msg_len = 3 + data_len;
+        msg[0] = report_id;
+        msg[1] = 0xA3;
+        msg[2] = data_len as u8;
+        msg[3] = read_len as u8;
+        msg[4] = message_id;
+        for (i, b) in data.into_iter().enumerate() {
+            msg[5 + i] = b;
+        }
+
+        let mut buf = [0u8; 0x40];
+        buf[0] = report_id;
+
+        unsafe {
+            debug!("  HidD_SetOutputReport {:X?}", msg);
+            let success = HidD_SetOutputReport(
+                self.handle,
+                // Microsoft docs says that the first byte of the message has to be the report ID.
+                // This is normal with HID implementations.
+                // But it seems on Windows (at least for this device's firmware) we have to set the
+                // length as one more than the buffer is long.
+                // Otherwise no data is returned in the read call later.
+                msg.as_mut_ptr() as _,
+                msg.len() as u32 + 1,
+            );
+            debug!("    Success: {}", success);
+
+            if read_len == 0 {
+                return Some(vec![]);
+            }
+
+            let mut bytes_read = 0;
+            debug!("  ReadFile");
+            // HidD_GetFeature doesn't work, have to use ReadFile
+            // Microsoft does recommend that
+            // https://learn.microsoft.com/en-us/windows-hardware/drivers/hid/obtaining-hid-reports
+            let res = ReadFile(self.handle, Some(&mut buf), Some(&mut bytes_read), None);
+            debug!("    Success: {:?}, Bytes: {}", res, bytes_read);
+            debug!("    Read buf: {:X?}", buf);
+            debug!("    Read msg: {:X?}", msg);
+        }
+
+        Some(buf[msg_len..msg_len + read_len].to_vec())
+    }
+}