platform/x86: thinkpad_acpi: Add ThinkPad PrivacyGuard
authorAlexander Schremmer <alex@alexanderweb.de>
Thu, 22 Aug 2019 11:48:33 +0000 (13:48 +0200)
committerAndy Shevchenko <andriy.shevchenko@linux.intel.com>
Sat, 7 Sep 2019 18:16:09 +0000 (21:16 +0300)
This feature is found optionally in T480s, T490, T490s.

The feature is called lcdshadow and visible via
/proc/acpi/ibm/lcdshadow.

The ACPI methods \_SB.PCI0.LPCB.EC.HKEY.{GSSS,SSSS,TSSS,CSSS} are
available in these machines. They get, set, toggle or change the state
apparently.

The patch was tested on a 5.0 series kernel on a T480s.

Signed-off-by: Alexander Schremmer <alex@alexanderweb.de>
Signed-off-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
Documentation/admin-guide/laptops/thinkpad-acpi.rst
drivers/platform/x86/thinkpad_acpi.c

index adea0bf2acc5e5a00a9e93ce260bf2d69c2bb069..822907dcc84532eafaf75c00ca3a9df68d7fabc1 100644 (file)
@@ -49,6 +49,7 @@ detailed description):
        - Fan control and monitoring: fan speed, fan enable/disable
        - WAN enable and disable
        - UWB enable and disable
+       - LCD Shadow (PrivacyGuard) enable and disable
 
 A compatibility table by model and feature is maintained on the web
 site, http://ibm-acpi.sf.net/. I appreciate any success or failure
@@ -1409,6 +1410,28 @@ Sysfs notes
        Documentation/driver-api/rfkill.rst for details.
 
 
+LCD Shadow control
+------------------
+
+procfs: /proc/acpi/ibm/lcdshadow
+
+Some newer T480s and T490s ThinkPads provide a feature called
+PrivacyGuard. By turning this feature on, the usable vertical and
+horizontal viewing angles of the LCD can be limited (as if some privacy
+screen was applied manually in front of the display).
+
+procfs notes
+^^^^^^^^^^^^
+
+The available commands are::
+
+       echo '0' >/proc/acpi/ibm/lcdshadow
+       echo '1' >/proc/acpi/ibm/lcdshadow
+
+The first command ensures the best viewing angle and the latter one turns
+on the feature, restricting the viewing angles.
+
+
 EXPERIMENTAL: UWB
 -----------------
 
index d379bdf98a0fd66a801450970471dff85c8da7c1..da794dcfdd928da30ebcc928bfc8864fa1e711a0 100644 (file)
@@ -9711,6 +9711,107 @@ static struct ibm_struct battery_driver_data = {
        .exit = tpacpi_battery_exit,
 };
 
+/*************************************************************************
+ * LCD Shadow subdriver, for the Lenovo PrivacyGuard feature
+ */
+
+static int lcdshadow_state;
+
+static int lcdshadow_on_off(bool state)
+{
+       acpi_handle set_shadow_handle;
+       int output;
+
+       if (ACPI_FAILURE(acpi_get_handle(hkey_handle, "SSSS", &set_shadow_handle))) {
+               pr_warn("Thinkpad ACPI has no %s interface.\n", "SSSS");
+               return -EIO;
+       }
+
+       if (!acpi_evalf(set_shadow_handle, &output, NULL, "dd", (int)state))
+               return -EIO;
+
+       lcdshadow_state = state;
+       return 0;
+}
+
+static int lcdshadow_set(bool on)
+{
+       if (lcdshadow_state < 0)
+               return lcdshadow_state;
+       if (lcdshadow_state == on)
+               return 0;
+       return lcdshadow_on_off(on);
+}
+
+static int tpacpi_lcdshadow_init(struct ibm_init_struct *iibm)
+{
+       acpi_handle get_shadow_handle;
+       int output;
+
+       if (ACPI_FAILURE(acpi_get_handle(hkey_handle, "GSSS", &get_shadow_handle))) {
+               lcdshadow_state = -ENODEV;
+               return 0;
+       }
+
+       if (!acpi_evalf(get_shadow_handle, &output, NULL, "dd", 0)) {
+               lcdshadow_state = -EIO;
+               return -EIO;
+       }
+       if (!(output & 0x10000)) {
+               lcdshadow_state = -ENODEV;
+               return 0;
+       }
+       lcdshadow_state = output & 0x1;
+
+       return 0;
+}
+
+static void lcdshadow_resume(void)
+{
+       if (lcdshadow_state >= 0)
+               lcdshadow_on_off(lcdshadow_state);
+}
+
+static int lcdshadow_read(struct seq_file *m)
+{
+       if (lcdshadow_state < 0) {
+               seq_puts(m, "status:\t\tnot supported\n");
+       } else {
+               seq_printf(m, "status:\t\t%d\n", lcdshadow_state);
+               seq_puts(m, "commands:\t0, 1\n");
+       }
+
+       return 0;
+}
+
+static int lcdshadow_write(char *buf)
+{
+       char *cmd;
+       int state = -1;
+
+       if (lcdshadow_state < 0)
+               return -ENODEV;
+
+       while ((cmd = next_cmd(&buf))) {
+               if (strlencmp(cmd, "0") == 0)
+                       state = 0;
+               else if (strlencmp(cmd, "1") == 0)
+                       state = 1;
+       }
+
+       if (state == -1)
+               return -EINVAL;
+
+       return lcdshadow_set(state);
+}
+
+static struct ibm_struct lcdshadow_driver_data = {
+       .name = "lcdshadow",
+       .resume = lcdshadow_resume,
+       .read = lcdshadow_read,
+       .write = lcdshadow_write,
+};
+
 /****************************************************************************
  ****************************************************************************
  *
@@ -10192,6 +10293,10 @@ static struct ibm_init_struct ibms_init[] __initdata = {
                .init = tpacpi_battery_init,
                .data = &battery_driver_data,
        },
+       {
+               .init = tpacpi_lcdshadow_init,
+               .data = &lcdshadow_driver_data,
+       },
 };
 
 static int __init set_ibm_param(const char *val, const struct kernel_param *kp)