Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Quelle  asus-wmi.c   Sprache: C

 
// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * Asus PC WMI hotkey driver
 *
 * Copyright(C) 2010 Intel Corporation.
 * Copyright(C) 2010-2011 Corentin Chary <corentin.chary@gmail.com>
 *
 * Portions based on wistron_btns.c:
 * Copyright (C) 2005 Miloslav Trmac <mitr@volny.cz>
 * Copyright (C) 2005 Bernhard Rosenkraenzer <bero@arklinux.org>
 * Copyright (C) 2005 Dmitry Torokhov <dtor@mail.ru>
 */


#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt

#include <linux/acpi.h>
#include <linux/backlight.h>
#include <linux/debugfs.h>
#include <linux/delay.h>
#include <linux/dmi.h>
#include <linux/hwmon.h>
#include <linux/hwmon-sysfs.h>
#include <linux/init.h>
#include <linux/input.h>
#include <linux/input/sparse-keymap.h>
#include <linux/kernel.h>
#include <linux/leds.h>
#include <linux/minmax.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/pci_hotplug.h>
#include <linux/platform_data/x86/asus-wmi.h>
#include <linux/platform_device.h>
#include <linux/platform_profile.h>
#include <linux/power_supply.h>
#include <linux/rfkill.h>
#include <linux/seq_file.h>
#include <linux/slab.h>
#include <linux/types.h>
#include <linux/units.h>

#include <acpi/battery.h>
#include <acpi/video.h>

#include "asus-wmi.h"

MODULE_AUTHOR("Corentin Chary ");
MODULE_AUTHOR("Yong Wang ");
MODULE_DESCRIPTION("Asus Generic WMI Driver");
MODULE_LICENSE("GPL");

static bool fnlock_default = true;
module_param(fnlock_default, bool, 0444);

#define to_asus_wmi_driver(pdrv)     \
 (container_of((pdrv), struct asus_wmi_driver, platform_driver))

#define ASUS_WMI_MGMT_GUID "97845ED0-4E6D-11DE-8A39-0800200C9A66"

#define NOTIFY_BRNUP_MIN  0x11
#define NOTIFY_BRNUP_MAX  0x1f
#define NOTIFY_BRNDOWN_MIN  0x20
#define NOTIFY_BRNDOWN_MAX  0x2e
#define NOTIFY_FNLOCK_TOGGLE  0x4e
#define NOTIFY_KBD_DOCK_CHANGE  0x75
#define NOTIFY_KBD_BRTUP  0xc4
#define NOTIFY_KBD_BRTDWN  0xc5
#define NOTIFY_KBD_BRTTOGGLE  0xc7
#define NOTIFY_KBD_FBM   0x99
#define NOTIFY_KBD_TTP   0xae
#define NOTIFY_LID_FLIP   0xfa
#define NOTIFY_LID_FLIP_ROG  0xbd

#define ASUS_WMI_FNLOCK_BIOS_DISABLED BIT(0)

#define ASUS_MID_FAN_DESC  "mid_fan"
#define ASUS_GPU_FAN_DESC  "gpu_fan"
#define ASUS_FAN_DESC   "cpu_fan"
#define ASUS_FAN_MFUN   0x13
#define ASUS_FAN_SFUN_READ  0x06
#define ASUS_FAN_SFUN_WRITE  0x07

/* Based on standard hwmon pwmX_enable values */
#define ASUS_FAN_CTRL_FULLSPEED  0
#define ASUS_FAN_CTRL_MANUAL  1
#define ASUS_FAN_CTRL_AUTO  2

#define ASUS_FAN_BOOST_MODE_NORMAL  0
#define ASUS_FAN_BOOST_MODE_OVERBOOST  1
#define ASUS_FAN_BOOST_MODE_OVERBOOST_MASK 0x01
#define ASUS_FAN_BOOST_MODE_SILENT  2
#define ASUS_FAN_BOOST_MODE_SILENT_MASK  0x02
#define ASUS_FAN_BOOST_MODES_MASK  0x03

#define ASUS_THROTTLE_THERMAL_POLICY_DEFAULT 0
#define ASUS_THROTTLE_THERMAL_POLICY_OVERBOOST 1
#define ASUS_THROTTLE_THERMAL_POLICY_SILENT 2

#define ASUS_THROTTLE_THERMAL_POLICY_DEFAULT_VIVO 0
#define ASUS_THROTTLE_THERMAL_POLICY_SILENT_VIVO 1
#define ASUS_THROTTLE_THERMAL_POLICY_OVERBOOST_VIVO 2

#define PLATFORM_PROFILE_MAX 2

#define USB_INTEL_XUSB2PR  0xD0
#define PCI_DEVICE_ID_INTEL_LYNXPOINT_LP_XHCI 0x9c31

#define ASUS_ACPI_UID_ASUSWMI  "ASUSWMI"

#define WMI_EVENT_MASK   0xFFFF

#define FAN_CURVE_POINTS  8
#define FAN_CURVE_BUF_LEN  32
#define FAN_CURVE_DEV_CPU  0x00
#define FAN_CURVE_DEV_GPU  0x01
#define FAN_CURVE_DEV_MID  0x02
/* Mask to determine if setting temperature or percentage */
#define FAN_CURVE_PWM_MASK  0x04

/* Limits for tunables available on ASUS ROG laptops */
#define PPT_TOTAL_MIN  5
#define PPT_TOTAL_MAX  250
#define PPT_CPU_MIN   5
#define PPT_CPU_MAX   130
#define NVIDIA_BOOST_MIN 5
#define NVIDIA_BOOST_MAX 25
#define NVIDIA_TEMP_MIN  75
#define NVIDIA_TEMP_MAX  87

#define ASUS_SCREENPAD_BRIGHT_MIN 20
#define ASUS_SCREENPAD_BRIGHT_MAX 255
#define ASUS_SCREENPAD_BRIGHT_DEFAULT 60

#define ASUS_MINI_LED_MODE_MASK  0x03
/* Standard modes for devices with only on/off */
#define ASUS_MINI_LED_OFF  0x00
#define ASUS_MINI_LED_ON  0x01
/* New mode on some devices, define here to clarify remapping later */
#define ASUS_MINI_LED_STRONG_MODE 0x02
/* New modes for devices with 3 mini-led mode types */
#define ASUS_MINI_LED_2024_WEAK  0x00
#define ASUS_MINI_LED_2024_STRONG 0x01
#define ASUS_MINI_LED_2024_OFF  0x02

#define ASUS_USB0_PWR_EC0_CSEE "\\_SB.PCI0.SBRG.EC0.CSEE"
/*
 * The period required to wait after screen off/on/s2idle.check in MS.
 * Time here greatly impacts the wake behaviour. Used in suspend/wake.
 */

#define ASUS_USB0_PWR_EC0_CSEE_WAIT 600
#define ASUS_USB0_PWR_EC0_CSEE_OFF 0xB7
#define ASUS_USB0_PWR_EC0_CSEE_ON 0xB8

static const char * const ashs_ids[] = { "ATK4001""ATK4002", NULL };

static int throttle_thermal_policy_write(struct asus_wmi *);

static const struct dmi_system_id asus_rog_ally_device[] = {
 {
  .matches = {
   DMI_MATCH(DMI_BOARD_NAME, "RC71L"),
  },
 },
 {
  .matches = {
   DMI_MATCH(DMI_BOARD_NAME, "RC72L"),
  },
 },
 { },
};

static bool ashs_present(void)
{
 int i = 0;
 while (ashs_ids[i]) {
  if (acpi_dev_found(ashs_ids[i++]))
   return true;
 }
 return false;
}

struct bios_args {
 u32 arg0;
 u32 arg1;
 u32 arg2; /* At least TUF Gaming series uses 3 dword input buffer. */
 u32 arg3;
 u32 arg4; /* Some ROG laptops require a full 5 input args */
 u32 arg5;
} __packed;

/*
 * Struct that's used for all methods called via AGFN. Naming is
 * identically to the AML code.
 */

struct agfn_args {
 u16 mfun; /* probably "Multi-function" to be called */
 u16 sfun; /* probably "Sub-function" to be called */
 u16 len;  /* size of the hole struct, including subfunction fields */
 u8 stas;  /* not used by now */
 u8 err;   /* zero on success */
} __packed;

/* struct used for calling fan read and write methods */
struct agfn_fan_args {
 struct agfn_args agfn; /* common fields */
 u8 fan;   /* fan number: 0: set auto mode 1: 1st fan */
 u32 speed;  /* read: RPM/100 - write: 0-255 */
} __packed;

/*
 * <platform>/    - debugfs root directory
 *   dev_id      - current dev_id
 *   ctrl_param  - current ctrl_param
 *   method_id   - current method_id
 *   devs        - call DEVS(dev_id, ctrl_param) and print result
 *   dsts        - call DSTS(dev_id)  and print result
 *   call        - call method_id(dev_id, ctrl_param) and print result
 */

struct asus_wmi_debug {
 struct dentry *root;
 u32 method_id;
 u32 dev_id;
 u32 ctrl_param;
};

struct asus_rfkill {
 struct asus_wmi *asus;
 struct rfkill *rfkill;
 u32 dev_id;
};

enum fan_type {
 FAN_TYPE_NONE = 0,
 FAN_TYPE_AGFN,  /* deprecated on newer platforms */
 FAN_TYPE_SPEC83, /* starting in Spec 8.3, use CPU_FAN_CTRL */
};

struct fan_curve_data {
 bool enabled;
 u32 device_id;
 u8 temps[FAN_CURVE_POINTS];
 u8 percents[FAN_CURVE_POINTS];
};

struct asus_wmi {
 int dsts_id;
 int spec;
 int sfun;

 struct input_dev *inputdev;
 struct backlight_device *backlight_device;
 struct backlight_device *screenpad_backlight_device;
 struct platform_device *platform_device;

 struct led_classdev wlan_led;
 int wlan_led_wk;
 struct led_classdev tpd_led;
 int tpd_led_wk;
 struct led_classdev kbd_led;
 int kbd_led_wk;
 struct led_classdev lightbar_led;
 int lightbar_led_wk;
 struct led_classdev micmute_led;
 struct led_classdev camera_led;
 struct workqueue_struct *led_workqueue;
 struct work_struct tpd_led_work;
 struct work_struct wlan_led_work;
 struct work_struct lightbar_led_work;

 struct asus_rfkill wlan;
 struct asus_rfkill bluetooth;
 struct asus_rfkill wimax;
 struct asus_rfkill wwan3g;
 struct asus_rfkill gps;
 struct asus_rfkill uwb;

 int tablet_switch_event_code;
 u32 tablet_switch_dev_id;
 bool tablet_switch_inverted;

 enum fan_type fan_type;
 enum fan_type gpu_fan_type;
 enum fan_type mid_fan_type;
 int fan_pwm_mode;
 int gpu_fan_pwm_mode;
 int mid_fan_pwm_mode;
 int agfn_pwm;

 bool fan_boost_mode_available;
 u8 fan_boost_mode_mask;
 u8 fan_boost_mode;

 bool egpu_enable_available;
 bool dgpu_disable_available;
 u32 gpu_mux_dev;

 /* Tunables provided by ASUS for gaming laptops */
 u32 ppt_pl2_sppt;
 u32 ppt_pl1_spl;
 u32 ppt_apu_sppt;
 u32 ppt_platform_sppt;
 u32 ppt_fppt;
 u32 nv_dynamic_boost;
 u32 nv_temp_target;

 u32 kbd_rgb_dev;
 bool kbd_rgb_state_available;
 bool oobe_state_available;

 u8 throttle_thermal_policy_mode;
 u32 throttle_thermal_policy_dev;

 bool cpu_fan_curve_available;
 bool gpu_fan_curve_available;
 bool mid_fan_curve_available;
 struct fan_curve_data custom_fan_curves[3];

 struct device *ppdev;
 bool platform_profile_support;

 // The RSOC controls the maximum charging percentage.
 bool battery_rsoc_available;

 bool panel_overdrive_available;
 u32 mini_led_dev_id;

 struct hotplug_slot hotplug_slot;
 struct mutex hotplug_lock;
 struct mutex wmi_lock;
 struct workqueue_struct *hotplug_workqueue;
 struct work_struct hotplug_work;

 bool fnlock_locked;

 struct asus_wmi_debug debug;

 struct asus_wmi_driver *driver;
};

/* Global to allow setting externally without requiring driver data */
static enum asus_ally_mcu_hack use_ally_mcu_hack = ASUS_WMI_ALLY_MCU_HACK_INIT;

/* WMI ************************************************************************/

static int asus_wmi_evaluate_method3(u32 method_id,
  u32 arg0, u32 arg1, u32 arg2, u32 *retval)
{
 struct bios_args args = {
  .arg0 = arg0,
  .arg1 = arg1,
  .arg2 = arg2,
 };
 struct acpi_buffer input = { (acpi_size) sizeof(args), &args };
 struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
 acpi_status status;
 union acpi_object *obj;
 u32 tmp = 0;

 status = wmi_evaluate_method(ASUS_WMI_MGMT_GUID, 0, method_id,
         &input, &output);

 pr_debug("%s called (0x%08x) with args: 0x%08x, 0x%08x, 0x%08x\n",
  __func__, method_id, arg0, arg1, arg2);
 if (ACPI_FAILURE(status)) {
  pr_debug("%s, (0x%08x), arg 0x%08x failed: %d\n",
   __func__, method_id, arg0, -EIO);
  return -EIO;
 }

 obj = (union acpi_object *)output.pointer;
 if (obj && obj->type == ACPI_TYPE_INTEGER)
  tmp = (u32) obj->integer.value;

 pr_debug("Result: 0x%08x\n", tmp);
 if (retval)
  *retval = tmp;

 kfree(obj);

 if (tmp == ASUS_WMI_UNSUPPORTED_METHOD) {
  pr_debug("%s, (0x%08x), arg 0x%08x failed: %d\n",
   __func__, method_id, arg0, -ENODEV);
  return -ENODEV;
 }

 return 0;
}

int asus_wmi_evaluate_method(u32 method_id, u32 arg0, u32 arg1, u32 *retval)
{
 return asus_wmi_evaluate_method3(method_id, arg0, arg1, 0, retval);
}
EXPORT_SYMBOL_GPL(asus_wmi_evaluate_method);

static int asus_wmi_evaluate_method5(u32 method_id,
  u32 arg0, u32 arg1, u32 arg2, u32 arg3, u32 arg4, u32 *retval)
{
 struct bios_args args = {
  .arg0 = arg0,
  .arg1 = arg1,
  .arg2 = arg2,
  .arg3 = arg3,
  .arg4 = arg4,
 };
 struct acpi_buffer input = { (acpi_size) sizeof(args), &args };
 struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
 acpi_status status;
 union acpi_object *obj;
 u32 tmp = 0;

 status = wmi_evaluate_method(ASUS_WMI_MGMT_GUID, 0, method_id,
         &input, &output);

 pr_debug("%s called (0x%08x) with args: 0x%08x, 0x%08x, 0x%08x, 0x%08x, 0x%08x\n",
  __func__, method_id, arg0, arg1, arg2, arg3, arg4);
 if (ACPI_FAILURE(status)) {
  pr_debug("%s, (0x%08x), arg 0x%08x failed: %d\n",
   __func__, method_id, arg0, -EIO);
  return -EIO;
 }

 obj = (union acpi_object *)output.pointer;
 if (obj && obj->type == ACPI_TYPE_INTEGER)
  tmp = (u32) obj->integer.value;

 pr_debug("Result: %x\n", tmp);
 if (retval)
  *retval = tmp;

 kfree(obj);

 if (tmp == ASUS_WMI_UNSUPPORTED_METHOD) {
  pr_debug("%s, (0x%08x), arg 0x%08x failed: %d\n",
   __func__, method_id, arg0, -ENODEV);
  return -ENODEV;
 }

 return 0;
}

/*
 * Returns as an error if the method output is not a buffer. Typically this
 * means that the method called is unsupported.
 */

static int asus_wmi_evaluate_method_buf(u32 method_id,
  u32 arg0, u32 arg1, u8 *ret_buffer, size_t size)
{
 struct bios_args args = {
  .arg0 = arg0,
  .arg1 = arg1,
  .arg2 = 0,
 };
 struct acpi_buffer input = { (acpi_size) sizeof(args), &args };
 struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
 acpi_status status;
 union acpi_object *obj;
 int err = 0;

 status = wmi_evaluate_method(ASUS_WMI_MGMT_GUID, 0, method_id,
         &input, &output);

 pr_debug("%s called (0x%08x) with args: 0x%08x, 0x%08x\n",
  __func__, method_id, arg0, arg1);
 if (ACPI_FAILURE(status)) {
  pr_debug("%s, (0x%08x), arg 0x%08x failed: %d\n",
   __func__, method_id, arg0, -EIO);
  return -EIO;
 }

 obj = (union acpi_object *)output.pointer;

 switch (obj->type) {
 case ACPI_TYPE_BUFFER:
  if (obj->buffer.length > size) {
   err = -ENOSPC;
   break;
  }
  if (obj->buffer.length == 0) {
   err = -ENODATA;
   break;
  }

  memcpy(ret_buffer, obj->buffer.pointer, obj->buffer.length);
  break;
 case ACPI_TYPE_INTEGER:
  err = (u32)obj->integer.value;

  if (err == ASUS_WMI_UNSUPPORTED_METHOD)
   err = -ENODEV;
  /*
 * At least one method returns a 0 with no buffer if no arg
 * is provided, such as ASUS_WMI_DEVID_CPU_FAN_CURVE
 */

  if (err == 0)
   err = -ENODATA;
  break;
 default:
  err = -ENODATA;
  break;
 }

 kfree(obj);

 if (err) {
  pr_debug("%s, (0x%08x), arg 0x%08x failed: %d\n",
   __func__, method_id, arg0, err);
  return err;
 }

 return 0;
}

static int asus_wmi_evaluate_method_agfn(const struct acpi_buffer args)
{
 struct acpi_buffer input;
 u64 phys_addr;
 u32 retval;
 u32 status;

 /*
 * Copy to dma capable address otherwise memory corruption occurs as
 * bios has to be able to access it.
 */

 input.pointer = kmemdup(args.pointer, args.length, GFP_DMA | GFP_KERNEL);
 input.length = args.length;
 if (!input.pointer)
  return -ENOMEM;
 phys_addr = virt_to_phys(input.pointer);

 status = asus_wmi_evaluate_method(ASUS_WMI_METHODID_AGFN,
     phys_addr, 0, &retval);
 if (!status)
  memcpy(args.pointer, input.pointer, args.length);

 kfree(input.pointer);
 if (status)
  return -ENXIO;

 return retval;
}

static int asus_wmi_get_devstate(struct asus_wmi *asus, u32 dev_id, u32 *retval)
{
 int err;

 err = asus_wmi_evaluate_method(asus->dsts_id, dev_id, 0, retval);

 if (err)
  return err;

 if (*retval == ~0)
  return -ENODEV;

 return 0;
}

int asus_wmi_set_devstate(u32 dev_id, u32 ctrl_param,
     u32 *retval)
{
 return asus_wmi_evaluate_method(ASUS_WMI_METHODID_DEVS, dev_id,
     ctrl_param, retval);
}

/* Helper for special devices with magic return codes */
static int asus_wmi_get_devstate_bits(struct asus_wmi *asus,
          u32 dev_id, u32 mask)
{
 u32 retval = 0;
 int err;

 err = asus_wmi_get_devstate(asus, dev_id, &retval);
 if (err < 0)
  return err;

 if (!(retval & ASUS_WMI_DSTS_PRESENCE_BIT))
  return -ENODEV;

 if (mask == ASUS_WMI_DSTS_STATUS_BIT) {
  if (retval & ASUS_WMI_DSTS_UNKNOWN_BIT)
   return -ENODEV;
 }

 return retval & mask;
}

static int asus_wmi_get_devstate_simple(struct asus_wmi *asus, u32 dev_id)
{
 return asus_wmi_get_devstate_bits(asus, dev_id,
       ASUS_WMI_DSTS_STATUS_BIT);
}

static bool asus_wmi_dev_is_present(struct asus_wmi *asus, u32 dev_id)
{
 u32 retval;
 int status = asus_wmi_get_devstate(asus, dev_id, &retval);
 pr_debug("%s called (0x%08x), retval: 0x%08x\n", __func__, dev_id, retval);

 return status == 0 && (retval & ASUS_WMI_DSTS_PRESENCE_BIT);
}

/* Input **********************************************************************/
static void asus_wmi_tablet_sw_report(struct asus_wmi *asus, bool value)
{
 input_report_switch(asus->inputdev, SW_TABLET_MODE,
       asus->tablet_switch_inverted ? !value : value);
 input_sync(asus->inputdev);
}

static void asus_wmi_tablet_sw_init(struct asus_wmi *asus, u32 dev_id, int event_code)
{
 struct device *dev = &asus->platform_device->dev;
 int result;

 result = asus_wmi_get_devstate_simple(asus, dev_id);
 if (result >= 0) {
  input_set_capability(asus->inputdev, EV_SW, SW_TABLET_MODE);
  asus_wmi_tablet_sw_report(asus, result);
  asus->tablet_switch_dev_id = dev_id;
  asus->tablet_switch_event_code = event_code;
 } else if (result == -ENODEV) {
  dev_err(dev, "This device has tablet-mode-switch quirk but got ENODEV checking it. This is a bug.");
 } else {
  dev_err(dev, "Error checking for tablet-mode-switch: %d\n", result);
 }
}

static int asus_wmi_input_init(struct asus_wmi *asus)
{
 struct device *dev = &asus->platform_device->dev;
 int err;

 asus->inputdev = input_allocate_device();
 if (!asus->inputdev)
  return -ENOMEM;

 asus->inputdev->name = asus->driver->input_name;
 asus->inputdev->phys = asus->driver->input_phys;
 asus->inputdev->id.bustype = BUS_HOST;
 asus->inputdev->dev.parent = dev;
 set_bit(EV_REP, asus->inputdev->evbit);

 err = sparse_keymap_setup(asus->inputdev, asus->driver->keymap, NULL);
 if (err)
  goto err_free_dev;

 switch (asus->driver->quirks->tablet_switch_mode) {
 case asus_wmi_no_tablet_switch:
  break;
 case asus_wmi_kbd_dock_devid:
  asus->tablet_switch_inverted = true;
  asus_wmi_tablet_sw_init(asus, ASUS_WMI_DEVID_KBD_DOCK, NOTIFY_KBD_DOCK_CHANGE);
  break;
 case asus_wmi_lid_flip_devid:
  asus_wmi_tablet_sw_init(asus, ASUS_WMI_DEVID_LID_FLIP, NOTIFY_LID_FLIP);
  break;
 case asus_wmi_lid_flip_rog_devid:
  asus_wmi_tablet_sw_init(asus, ASUS_WMI_DEVID_LID_FLIP_ROG, NOTIFY_LID_FLIP_ROG);
  break;
 }

 err = input_register_device(asus->inputdev);
 if (err)
  goto err_free_dev;

 return 0;

err_free_dev:
 input_free_device(asus->inputdev);
 return err;
}

static void asus_wmi_input_exit(struct asus_wmi *asus)
{
 if (asus->inputdev)
  input_unregister_device(asus->inputdev);

 asus->inputdev = NULL;
}

/* Tablet mode ****************************************************************/

static void asus_wmi_tablet_mode_get_state(struct asus_wmi *asus)
{
 int result;

 if (!asus->tablet_switch_dev_id)
  return;

 result = asus_wmi_get_devstate_simple(asus, asus->tablet_switch_dev_id);
 if (result >= 0)
  asus_wmi_tablet_sw_report(asus, result);
}

/* Charging mode, 1=Barrel, 2=USB ******************************************/
static ssize_t charge_mode_show(struct device *dev,
       struct device_attribute *attr, char *buf)
{
 struct asus_wmi *asus = dev_get_drvdata(dev);
 int result, value;

 result = asus_wmi_get_devstate(asus, ASUS_WMI_DEVID_CHARGE_MODE, &value);
 if (result < 0)
  return result;

 return sysfs_emit(buf, "%d\n", value & 0xff);
}

static DEVICE_ATTR_RO(charge_mode);

/* dGPU ********************************************************************/
static ssize_t dgpu_disable_show(struct device *dev,
       struct device_attribute *attr, char *buf)
{
 struct asus_wmi *asus = dev_get_drvdata(dev);
 int result;

 result = asus_wmi_get_devstate_simple(asus, ASUS_WMI_DEVID_DGPU);
 if (result < 0)
  return result;

 return sysfs_emit(buf, "%d\n", result);
}

/*
 * A user may be required to store the value twice, typcial store first, then
 * rescan PCI bus to activate power, then store a second time to save correctly.
 * The reason for this is that an extra code path in the ACPI is enabled when
 * the device and bus are powered.
 */

static ssize_t dgpu_disable_store(struct device *dev,
        struct device_attribute *attr,
        const char *buf, size_t count)
{
 int result, err;
 u32 disable;

 struct asus_wmi *asus = dev_get_drvdata(dev);

 result = kstrtou32(buf, 10, &disable);
 if (result)
  return result;

 if (disable > 1)
  return -EINVAL;

 if (asus->gpu_mux_dev) {
  result = asus_wmi_get_devstate_simple(asus, asus->gpu_mux_dev);
  if (result < 0)
   /* An error here may signal greater failure of GPU handling */
   return result;
  if (!result && disable) {
   err = -ENODEV;
   pr_warn("Can not disable dGPU when the MUX is in dGPU mode: %d\n", err);
   return err;
  }
 }

 err = asus_wmi_set_devstate(ASUS_WMI_DEVID_DGPU, disable, &result);
 if (err) {
  pr_warn("Failed to set dgpu disable: %d\n", err);
  return err;
 }

 if (result > 1) {
  pr_warn("Failed to set dgpu disable (result): 0x%x\n", result);
  return -EIO;
 }

 sysfs_notify(&asus->platform_device->dev.kobj, NULL, "dgpu_disable");

 return count;
}
static DEVICE_ATTR_RW(dgpu_disable);

/* eGPU ********************************************************************/
static ssize_t egpu_enable_show(struct device *dev,
       struct device_attribute *attr, char *buf)
{
 struct asus_wmi *asus = dev_get_drvdata(dev);
 int result;

 result = asus_wmi_get_devstate_simple(asus, ASUS_WMI_DEVID_EGPU);
 if (result < 0)
  return result;

 return sysfs_emit(buf, "%d\n", result);
}

/* The ACPI call to enable the eGPU also disables the internal dGPU */
static ssize_t egpu_enable_store(struct device *dev,
        struct device_attribute *attr,
        const char *buf, size_t count)
{
 int result, err;
 u32 enable;

 struct asus_wmi *asus = dev_get_drvdata(dev);

 err = kstrtou32(buf, 10, &enable);
 if (err)
  return err;

 if (enable > 1)
  return -EINVAL;

 err = asus_wmi_get_devstate_simple(asus, ASUS_WMI_DEVID_EGPU_CONNECTED);
 if (err < 0) {
  pr_warn("Failed to get egpu connection status: %d\n", err);
  return err;
 }

 if (asus->gpu_mux_dev) {
  result = asus_wmi_get_devstate_simple(asus, asus->gpu_mux_dev);
  if (result < 0) {
   /* An error here may signal greater failure of GPU handling */
   pr_warn("Failed to get gpu mux status: %d\n", result);
   return result;
  }
  if (!result && enable) {
   err = -ENODEV;
   pr_warn("Can not enable eGPU when the MUX is in dGPU mode: %d\n", err);
   return err;
  }
 }

 err = asus_wmi_set_devstate(ASUS_WMI_DEVID_EGPU, enable, &result);
 if (err) {
  pr_warn("Failed to set egpu state: %d\n", err);
  return err;
 }

 if (result > 1) {
  pr_warn("Failed to set egpu state (retval): 0x%x\n", result);
  return -EIO;
 }

 sysfs_notify(&asus->platform_device->dev.kobj, NULL, "egpu_enable");

 return count;
}
static DEVICE_ATTR_RW(egpu_enable);

/* Is eGPU connected? *********************************************************/
static ssize_t egpu_connected_show(struct device *dev,
       struct device_attribute *attr, char *buf)
{
 struct asus_wmi *asus = dev_get_drvdata(dev);
 int result;

 result = asus_wmi_get_devstate_simple(asus, ASUS_WMI_DEVID_EGPU_CONNECTED);
 if (result < 0)
  return result;

 return sysfs_emit(buf, "%d\n", result);
}

static DEVICE_ATTR_RO(egpu_connected);

/* gpu mux switch *************************************************************/
static ssize_t gpu_mux_mode_show(struct device *dev,
     struct device_attribute *attr, char *buf)
{
 struct asus_wmi *asus = dev_get_drvdata(dev);
 int result;

 result = asus_wmi_get_devstate_simple(asus, asus->gpu_mux_dev);
 if (result < 0)
  return result;

 return sysfs_emit(buf, "%d\n", result);
}

static ssize_t gpu_mux_mode_store(struct device *dev,
      struct device_attribute *attr,
      const char *buf, size_t count)
{
 struct asus_wmi *asus = dev_get_drvdata(dev);
 int result, err;
 u32 optimus;

 err = kstrtou32(buf, 10, &optimus);
 if (err)
  return err;

 if (optimus > 1)
  return -EINVAL;

 if (asus->dgpu_disable_available) {
  result = asus_wmi_get_devstate_simple(asus, ASUS_WMI_DEVID_DGPU);
  if (result < 0)
   /* An error here may signal greater failure of GPU handling */
   return result;
  if (result && !optimus) {
   err = -ENODEV;
   pr_warn("Can not switch MUX to dGPU mode when dGPU is disabled: %d\n", err);
   return err;
  }
 }

 if (asus->egpu_enable_available) {
  result = asus_wmi_get_devstate_simple(asus, ASUS_WMI_DEVID_EGPU);
  if (result < 0)
   /* An error here may signal greater failure of GPU handling */
   return result;
  if (result && !optimus) {
   err = -ENODEV;
   pr_warn("Can not switch MUX to dGPU mode when eGPU is enabled: %d\n", err);
   return err;
  }
 }

 err = asus_wmi_set_devstate(asus->gpu_mux_dev, optimus, &result);
 if (err) {
  dev_err(dev, "Failed to set GPU MUX mode: %d\n", err);
  return err;
 }
 /* !1 is considered a fail by ASUS */
 if (result != 1) {
  dev_warn(dev, "Failed to set GPU MUX mode (result): 0x%x\n", result);
  return -EIO;
 }

 sysfs_notify(&asus->platform_device->dev.kobj, NULL, "gpu_mux_mode");

 return count;
}
static DEVICE_ATTR_RW(gpu_mux_mode);

/* TUF Laptop Keyboard RGB Modes **********************************************/
static ssize_t kbd_rgb_mode_store(struct device *dev,
     struct device_attribute *attr,
     const char *buf, size_t count)
{
 u32 cmd, mode, r, g, b, speed;
 struct led_classdev *led;
 struct asus_wmi *asus;
 int err;

 led = dev_get_drvdata(dev);
 asus = container_of(led, struct asus_wmi, kbd_led);

 if (sscanf(buf, "%d %d %d %d %d %d", &cmd, &mode, &r, &g, &b, &speed) != 6)
  return -EINVAL;

 /* B3 is set and B4 is save to BIOS */
 switch (cmd) {
 case 0:
  cmd = 0xb3;
  break;
 case 1:
  cmd = 0xb4;
  break;
 default:
  return -EINVAL;
 }

 /* These are the known usable modes across all TUF/ROG */
 if (mode >= 12 || mode == 9)
  mode = 10;

 switch (speed) {
 case 0:
  speed = 0xe1;
  break;
 case 1:
  speed = 0xeb;
  break;
 case 2:
  speed = 0xf5;
  break;
 default:
  speed = 0xeb;
 }

 err = asus_wmi_evaluate_method3(ASUS_WMI_METHODID_DEVS, asus->kbd_rgb_dev,
   cmd | (mode << 8) | (r << 16) | (g << 24), b | (speed << 8), NULL);
 if (err)
  return err;

 return count;
}
static DEVICE_ATTR_WO(kbd_rgb_mode);

static DEVICE_STRING_ATTR_RO(kbd_rgb_mode_index, 0444,
        "cmd mode red green blue speed");

static struct attribute *kbd_rgb_mode_attrs[] = {
 &dev_attr_kbd_rgb_mode.attr,
 &dev_attr_kbd_rgb_mode_index.attr.attr,
 NULL,
};

static const struct attribute_group kbd_rgb_mode_group = {
 .attrs = kbd_rgb_mode_attrs,
};

/* TUF Laptop Keyboard RGB State **********************************************/
static ssize_t kbd_rgb_state_store(struct device *dev,
     struct device_attribute *attr,
     const char *buf, size_t count)
{
 u32 flags, cmd, boot, awake, sleep, keyboard;
 int err;

 if (sscanf(buf, "%d %d %d %d %d", &cmd, &boot, &awake, &sleep, &keyboard) != 5)
  return -EINVAL;

 if (cmd)
  cmd = BIT(2);

 flags = 0;
 if (boot)
  flags |= BIT(1);
 if (awake)
  flags |= BIT(3);
 if (sleep)
  flags |= BIT(5);
 if (keyboard)
  flags |= BIT(7);

 /* 0xbd is the required default arg0 for the method. Nothing happens otherwise */
 err = asus_wmi_evaluate_method3(ASUS_WMI_METHODID_DEVS,
   ASUS_WMI_DEVID_TUF_RGB_STATE, 0xbd | cmd << 8 | (flags << 16), 0, NULL);
 if (err)
  return err;

 return count;
}
static DEVICE_ATTR_WO(kbd_rgb_state);

static DEVICE_STRING_ATTR_RO(kbd_rgb_state_index, 0444,
        "cmd boot awake sleep keyboard");

static struct attribute *kbd_rgb_state_attrs[] = {
 &dev_attr_kbd_rgb_state.attr,
 &dev_attr_kbd_rgb_state_index.attr.attr,
 NULL,
};

static const struct attribute_group kbd_rgb_state_group = {
 .attrs = kbd_rgb_state_attrs,
};

static const struct attribute_group *kbd_rgb_mode_groups[] = {
 NULL,
 NULL,
 NULL,
};

/* Tunable: PPT: Intel=PL1, AMD=SPPT *****************************************/
static ssize_t ppt_pl2_sppt_store(struct device *dev,
        struct device_attribute *attr,
        const char *buf, size_t count)
{
 struct asus_wmi *asus = dev_get_drvdata(dev);
 int result, err;
 u32 value;

 result = kstrtou32(buf, 10, &value);
 if (result)
  return result;

 if (value < PPT_TOTAL_MIN || value > PPT_TOTAL_MAX)
  return -EINVAL;

 err = asus_wmi_set_devstate(ASUS_WMI_DEVID_PPT_PL2_SPPT, value, &result);
 if (err) {
  pr_warn("Failed to set ppt_pl2_sppt: %d\n", err);
  return err;
 }

 if (result > 1) {
  pr_warn("Failed to set ppt_pl2_sppt (result): 0x%x\n", result);
  return -EIO;
 }

 asus->ppt_pl2_sppt = value;
 sysfs_notify(&asus->platform_device->dev.kobj, NULL, "ppt_pl2_sppt");

 return count;
}

static ssize_t ppt_pl2_sppt_show(struct device *dev,
           struct device_attribute *attr,
           char *buf)
{
 struct asus_wmi *asus = dev_get_drvdata(dev);

 return sysfs_emit(buf, "%u\n", asus->ppt_pl2_sppt);
}
static DEVICE_ATTR_RW(ppt_pl2_sppt);

/* Tunable: PPT, Intel=PL1, AMD=SPL ******************************************/
static ssize_t ppt_pl1_spl_store(struct device *dev,
        struct device_attribute *attr,
        const char *buf, size_t count)
{
 struct asus_wmi *asus = dev_get_drvdata(dev);
 int result, err;
 u32 value;

 result = kstrtou32(buf, 10, &value);
 if (result)
  return result;

 if (value < PPT_TOTAL_MIN || value > PPT_TOTAL_MAX)
  return -EINVAL;

 err = asus_wmi_set_devstate(ASUS_WMI_DEVID_PPT_PL1_SPL, value, &result);
 if (err) {
  pr_warn("Failed to set ppt_pl1_spl: %d\n", err);
  return err;
 }

 if (result > 1) {
  pr_warn("Failed to set ppt_pl1_spl (result): 0x%x\n", result);
  return -EIO;
 }

 asus->ppt_pl1_spl = value;
 sysfs_notify(&asus->platform_device->dev.kobj, NULL, "ppt_pl1_spl");

 return count;
}
static ssize_t ppt_pl1_spl_show(struct device *dev,
     struct device_attribute *attr,
     char *buf)
{
 struct asus_wmi *asus = dev_get_drvdata(dev);

 return sysfs_emit(buf, "%u\n", asus->ppt_pl1_spl);
}
static DEVICE_ATTR_RW(ppt_pl1_spl);

/* Tunable: PPT APU FPPT ******************************************************/
static ssize_t ppt_fppt_store(struct device *dev,
        struct device_attribute *attr,
        const char *buf, size_t count)
{
 struct asus_wmi *asus = dev_get_drvdata(dev);
 int result, err;
 u32 value;

 result = kstrtou32(buf, 10, &value);
 if (result)
  return result;

 if (value < PPT_TOTAL_MIN || value > PPT_TOTAL_MAX)
  return -EINVAL;

 err = asus_wmi_set_devstate(ASUS_WMI_DEVID_PPT_FPPT, value, &result);
 if (err) {
  pr_warn("Failed to set ppt_fppt: %d\n", err);
  return err;
 }

 if (result > 1) {
  pr_warn("Failed to set ppt_fppt (result): 0x%x\n", result);
  return -EIO;
 }

 asus->ppt_fppt = value;
 sysfs_notify(&asus->platform_device->dev.kobj, NULL, "ppt_fpu_sppt");

 return count;
}

static ssize_t ppt_fppt_show(struct device *dev,
    struct device_attribute *attr,
    char *buf)
{
 struct asus_wmi *asus = dev_get_drvdata(dev);

 return sysfs_emit(buf, "%u\n", asus->ppt_fppt);
}
static DEVICE_ATTR_RW(ppt_fppt);

/* Tunable: PPT APU SPPT *****************************************************/
static ssize_t ppt_apu_sppt_store(struct device *dev,
        struct device_attribute *attr,
        const char *buf, size_t count)
{
 struct asus_wmi *asus = dev_get_drvdata(dev);
 int result, err;
 u32 value;

 result = kstrtou32(buf, 10, &value);
 if (result)
  return result;

 if (value < PPT_CPU_MIN || value > PPT_CPU_MAX)
  return -EINVAL;

 err = asus_wmi_set_devstate(ASUS_WMI_DEVID_PPT_APU_SPPT, value, &result);
 if (err) {
  pr_warn("Failed to set ppt_apu_sppt: %d\n", err);
  return err;
 }

 if (result > 1) {
  pr_warn("Failed to set ppt_apu_sppt (result): 0x%x\n", result);
  return -EIO;
 }

 asus->ppt_apu_sppt = value;
 sysfs_notify(&asus->platform_device->dev.kobj, NULL, "ppt_apu_sppt");

 return count;
}

static ssize_t ppt_apu_sppt_show(struct device *dev,
        struct device_attribute *attr,
        char *buf)
{
 struct asus_wmi *asus = dev_get_drvdata(dev);

 return sysfs_emit(buf, "%u\n", asus->ppt_apu_sppt);
}
static DEVICE_ATTR_RW(ppt_apu_sppt);

/* Tunable: PPT platform SPPT ************************************************/
static ssize_t ppt_platform_sppt_store(struct device *dev,
        struct device_attribute *attr,
        const char *buf, size_t count)
{
 struct asus_wmi *asus = dev_get_drvdata(dev);
 int result, err;
 u32 value;

 result = kstrtou32(buf, 10, &value);
 if (result)
  return result;

 if (value < PPT_CPU_MIN || value > PPT_CPU_MAX)
  return -EINVAL;

 err = asus_wmi_set_devstate(ASUS_WMI_DEVID_PPT_PLAT_SPPT, value, &result);
 if (err) {
  pr_warn("Failed to set ppt_platform_sppt: %d\n", err);
  return err;
 }

 if (result > 1) {
  pr_warn("Failed to set ppt_platform_sppt (result): 0x%x\n", result);
  return -EIO;
 }

 asus->ppt_platform_sppt = value;
 sysfs_notify(&asus->platform_device->dev.kobj, NULL, "ppt_platform_sppt");

 return count;
}

static ssize_t ppt_platform_sppt_show(struct device *dev,
     struct device_attribute *attr,
     char *buf)
{
 struct asus_wmi *asus = dev_get_drvdata(dev);

 return sysfs_emit(buf, "%u\n", asus->ppt_platform_sppt);
}
static DEVICE_ATTR_RW(ppt_platform_sppt);

/* Tunable: NVIDIA dynamic boost *********************************************/
static ssize_t nv_dynamic_boost_store(struct device *dev,
        struct device_attribute *attr,
        const char *buf, size_t count)
{
 struct asus_wmi *asus = dev_get_drvdata(dev);
 int result, err;
 u32 value;

 result = kstrtou32(buf, 10, &value);
 if (result)
  return result;

 if (value < NVIDIA_BOOST_MIN || value > NVIDIA_BOOST_MAX)
  return -EINVAL;

 err = asus_wmi_set_devstate(ASUS_WMI_DEVID_NV_DYN_BOOST, value, &result);
 if (err) {
  pr_warn("Failed to set nv_dynamic_boost: %d\n", err);
  return err;
 }

 if (result > 1) {
  pr_warn("Failed to set nv_dynamic_boost (result): 0x%x\n", result);
  return -EIO;
 }

 asus->nv_dynamic_boost = value;
 sysfs_notify(&asus->platform_device->dev.kobj, NULL, "nv_dynamic_boost");

 return count;
}

static ssize_t nv_dynamic_boost_show(struct device *dev,
          struct device_attribute *attr,
          char *buf)
{
 struct asus_wmi *asus = dev_get_drvdata(dev);

 return sysfs_emit(buf, "%u\n", asus->nv_dynamic_boost);
}
static DEVICE_ATTR_RW(nv_dynamic_boost);

/* Tunable: NVIDIA temperature target ****************************************/
static ssize_t nv_temp_target_store(struct device *dev,
        struct device_attribute *attr,
        const char *buf, size_t count)
{
 struct asus_wmi *asus = dev_get_drvdata(dev);
 int result, err;
 u32 value;

 result = kstrtou32(buf, 10, &value);
 if (result)
  return result;

 if (value < NVIDIA_TEMP_MIN || value > NVIDIA_TEMP_MAX)
  return -EINVAL;

 err = asus_wmi_set_devstate(ASUS_WMI_DEVID_NV_THERM_TARGET, value, &result);
 if (err) {
  pr_warn("Failed to set nv_temp_target: %d\n", err);
  return err;
 }

 if (result > 1) {
  pr_warn("Failed to set nv_temp_target (result): 0x%x\n", result);
  return -EIO;
 }

 asus->nv_temp_target = value;
 sysfs_notify(&asus->platform_device->dev.kobj, NULL, "nv_temp_target");

 return count;
}

static ssize_t nv_temp_target_show(struct device *dev,
         struct device_attribute *attr,
         char *buf)
{
 struct asus_wmi *asus = dev_get_drvdata(dev);

 return sysfs_emit(buf, "%u\n", asus->nv_temp_target);
}
static DEVICE_ATTR_RW(nv_temp_target);

/* Ally MCU Powersave ********************************************************/

/*
 * The HID driver needs to check MCU version and set this to false if the MCU FW
 * version is >= the minimum requirements. New FW do not need the hacks.
 */

void set_ally_mcu_hack(enum asus_ally_mcu_hack status)
{
 use_ally_mcu_hack = status;
 pr_debug("%s Ally MCU suspend quirk\n",
   status == ASUS_WMI_ALLY_MCU_HACK_ENABLED ? "Enabled" : "Disabled");
}
EXPORT_SYMBOL_NS_GPL(set_ally_mcu_hack, "ASUS_WMI");

/*
 * mcu_powersave should be enabled always, as it is fixed in MCU FW versions:
 * - v313 for Ally X
 * - v319 for Ally 1
 * The HID driver checks MCU versions and so should set this if requirements match
 */

void set_ally_mcu_powersave(bool enabled)
{
 int result, err;

 err = asus_wmi_set_devstate(ASUS_WMI_DEVID_MCU_POWERSAVE, enabled, &result);
 if (err) {
  pr_warn("Failed to set MCU powersave: %d\n", err);
  return;
 }
 if (result > 1) {
  pr_warn("Failed to set MCU powersave (result): 0x%x\n", result);
  return;
 }

 pr_debug("%s MCU Powersave\n",
   enabled ? "Enabled" : "Disabled");
}
EXPORT_SYMBOL_NS_GPL(set_ally_mcu_powersave, "ASUS_WMI");

static ssize_t mcu_powersave_show(struct device *dev,
       struct device_attribute *attr, char *buf)
{
 struct asus_wmi *asus = dev_get_drvdata(dev);
 int result;

 result = asus_wmi_get_devstate_simple(asus, ASUS_WMI_DEVID_MCU_POWERSAVE);
 if (result < 0)
  return result;

 return sysfs_emit(buf, "%d\n", result);
}

static ssize_t mcu_powersave_store(struct device *dev,
        struct device_attribute *attr,
        const char *buf, size_t count)
{
 int result, err;
 u32 enable;

 struct asus_wmi *asus = dev_get_drvdata(dev);

 result = kstrtou32(buf, 10, &enable);
 if (result)
  return result;

 if (enable > 1)
  return -EINVAL;

 err = asus_wmi_set_devstate(ASUS_WMI_DEVID_MCU_POWERSAVE, enable, &result);
 if (err) {
  pr_warn("Failed to set MCU powersave: %d\n", err);
  return err;
 }

 if (result > 1) {
  pr_warn("Failed to set MCU powersave (result): 0x%x\n", result);
  return -EIO;
 }

 sysfs_notify(&asus->platform_device->dev.kobj, NULL, "mcu_powersave");

 return count;
}
static DEVICE_ATTR_RW(mcu_powersave);

/* Battery ********************************************************************/

/* The battery maximum charging percentage */
static int charge_end_threshold;

static ssize_t charge_control_end_threshold_store(struct device *dev,
        struct device_attribute *attr,
        const char *buf, size_t count)
{
 int value, ret, rv;

 ret = kstrtouint(buf, 10, &value);
 if (ret)
  return ret;

 if (value < 0 || value > 100)
  return -EINVAL;

 ret = asus_wmi_set_devstate(ASUS_WMI_DEVID_RSOC, value, &rv);
 if (ret)
  return ret;

 if (rv != 1)
  return -EIO;

 /* There isn't any method in the DSDT to read the threshold, so we
 * save the threshold.
 */

 charge_end_threshold = value;
 return count;
}

static ssize_t charge_control_end_threshold_show(struct device *device,
       struct device_attribute *attr,
       char *buf)
{
 return sysfs_emit(buf, "%d\n", charge_end_threshold);
}

static DEVICE_ATTR_RW(charge_control_end_threshold);

static int asus_wmi_battery_add(struct power_supply *battery, struct acpi_battery_hook *hook)
{
 /* The WMI method does not provide a way to specific a battery, so we
 * just assume it is the first battery.
 * Note: On some newer ASUS laptops (Zenbook UM431DA), the primary/first
 * battery is named BATT.
 */

 if (strcmp(battery->desc->name, "BAT0") != 0 &&
     strcmp(battery->desc->name, "BAT1") != 0 &&
     strcmp(battery->desc->name, "BATC") != 0 &&
     strcmp(battery->desc->name, "BATT") != 0)
  return -ENODEV;

 if (device_create_file(&battery->dev,
     &dev_attr_charge_control_end_threshold))
  return -ENODEV;

 /* The charge threshold is only reset when the system is power cycled,
 * and we can't get the current threshold so let set it to 100% when
 * a battery is added.
 */

 asus_wmi_set_devstate(ASUS_WMI_DEVID_RSOC, 100, NULL);
 charge_end_threshold = 100;

 return 0;
}

static int asus_wmi_battery_remove(struct power_supply *battery, struct acpi_battery_hook *hook)
{
 device_remove_file(&battery->dev,
      &dev_attr_charge_control_end_threshold);
 return 0;
}

static struct acpi_battery_hook battery_hook = {
 .add_battery = asus_wmi_battery_add,
 .remove_battery = asus_wmi_battery_remove,
 .name = "ASUS Battery Extension",
};

static void asus_wmi_battery_init(struct asus_wmi *asus)
{
 asus->battery_rsoc_available = false;
 if (asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_RSOC)) {
  asus->battery_rsoc_available = true;
  battery_hook_register(&battery_hook);
 }
}

static void asus_wmi_battery_exit(struct asus_wmi *asus)
{
 if (asus->battery_rsoc_available)
  battery_hook_unregister(&battery_hook);
}

/* LEDs ***********************************************************************/

/*
 * These functions actually update the LED's, and are called from a
 * workqueue. By doing this as separate work rather than when the LED
 * subsystem asks, we avoid messing with the Asus ACPI stuff during a
 * potentially bad time, such as a timer interrupt.
 */

static void tpd_led_update(struct work_struct *work)
{
 int ctrl_param;
 struct asus_wmi *asus;

 asus = container_of(work, struct asus_wmi, tpd_led_work);

 ctrl_param = asus->tpd_led_wk;
 asus_wmi_set_devstate(ASUS_WMI_DEVID_TOUCHPAD_LED, ctrl_param, NULL);
}

static void tpd_led_set(struct led_classdev *led_cdev,
   enum led_brightness value)
{
 struct asus_wmi *asus;

 asus = container_of(led_cdev, struct asus_wmi, tpd_led);

 asus->tpd_led_wk = !!value;
 queue_work(asus->led_workqueue, &asus->tpd_led_work);
}

static int read_tpd_led_state(struct asus_wmi *asus)
{
 return asus_wmi_get_devstate_simple(asus, ASUS_WMI_DEVID_TOUCHPAD_LED);
}

static enum led_brightness tpd_led_get(struct led_classdev *led_cdev)
{
 struct asus_wmi *asus;

 asus = container_of(led_cdev, struct asus_wmi, tpd_led);

 return read_tpd_led_state(asus);
}

static void kbd_led_update(struct asus_wmi *asus)
{
 int ctrl_param = 0;

 ctrl_param = 0x80 | (asus->kbd_led_wk & 0x7F);
 asus_wmi_set_devstate(ASUS_WMI_DEVID_KBD_BACKLIGHT, ctrl_param, NULL);
}

static int kbd_led_read(struct asus_wmi *asus, int *level, int *env)
{
 int retval;

 /*
 * bits 0-2: level
 * bit 7: light on/off
 * bit 8-10: environment (0: dark, 1: normal, 2: light)
 * bit 17: status unknown
 */

 retval = asus_wmi_get_devstate_bits(asus, ASUS_WMI_DEVID_KBD_BACKLIGHT,
         0xFFFF);

 /* Unknown status is considered as off */
 if (retval == 0x8000)
  retval = 0;

 if (retval < 0)
  return retval;

 if (level)
  *level = retval & 0x7F;
 if (env)
  *env = (retval >> 8) & 0x7F;
 return 0;
}

static void do_kbd_led_set(struct led_classdev *led_cdev, int value)
{
 struct asus_wmi *asus;
 int max_level;

 asus = container_of(led_cdev, struct asus_wmi, kbd_led);
 max_level = asus->kbd_led.max_brightness;

 asus->kbd_led_wk = clamp_val(value, 0, max_level);
 kbd_led_update(asus);
}

static void kbd_led_set(struct led_classdev *led_cdev,
   enum led_brightness value)
{
 /* Prevent disabling keyboard backlight on module unregister */
 if (led_cdev->flags & LED_UNREGISTERING)
  return;

 do_kbd_led_set(led_cdev, value);
}

static void kbd_led_set_by_kbd(struct asus_wmi *asus, enum led_brightness value)
{
 struct led_classdev *led_cdev = &asus->kbd_led;

 do_kbd_led_set(led_cdev, value);
 led_classdev_notify_brightness_hw_changed(led_cdev, asus->kbd_led_wk);
}

static enum led_brightness kbd_led_get(struct led_classdev *led_cdev)
{
 struct asus_wmi *asus;
 int retval, value;

 asus = container_of(led_cdev, struct asus_wmi, kbd_led);

 retval = kbd_led_read(asus, &value, NULL);
 if (retval < 0)
  return retval;

 return value;
}

static int wlan_led_unknown_state(struct asus_wmi *asus)
{
 u32 result;

 asus_wmi_get_devstate(asus, ASUS_WMI_DEVID_WIRELESS_LED, &result);

 return result & ASUS_WMI_DSTS_UNKNOWN_BIT;
}

static void wlan_led_update(struct work_struct *work)
{
 int ctrl_param;
 struct asus_wmi *asus;

 asus = container_of(work, struct asus_wmi, wlan_led_work);

 ctrl_param = asus->wlan_led_wk;
 asus_wmi_set_devstate(ASUS_WMI_DEVID_WIRELESS_LED, ctrl_param, NULL);
}

static void wlan_led_set(struct led_classdev *led_cdev,
    enum led_brightness value)
{
 struct asus_wmi *asus;

 asus = container_of(led_cdev, struct asus_wmi, wlan_led);

 asus->wlan_led_wk = !!value;
 queue_work(asus->led_workqueue, &asus->wlan_led_work);
}

static enum led_brightness wlan_led_get(struct led_classdev *led_cdev)
{
 struct asus_wmi *asus;
 u32 result;

 asus = container_of(led_cdev, struct asus_wmi, wlan_led);
 asus_wmi_get_devstate(asus, ASUS_WMI_DEVID_WIRELESS_LED, &result);

 return result & ASUS_WMI_DSTS_BRIGHTNESS_MASK;
}

static void lightbar_led_update(struct work_struct *work)
{
 struct asus_wmi *asus;
 int ctrl_param;

 asus = container_of(work, struct asus_wmi, lightbar_led_work);

 ctrl_param = asus->lightbar_led_wk;
 asus_wmi_set_devstate(ASUS_WMI_DEVID_LIGHTBAR, ctrl_param, NULL);
}

static void lightbar_led_set(struct led_classdev *led_cdev,
        enum led_brightness value)
{
 struct asus_wmi *asus;

 asus = container_of(led_cdev, struct asus_wmi, lightbar_led);

 asus->lightbar_led_wk = !!value;
 queue_work(asus->led_workqueue, &asus->lightbar_led_work);
}

static enum led_brightness lightbar_led_get(struct led_classdev *led_cdev)
{
 struct asus_wmi *asus;
 u32 result;

 asus = container_of(led_cdev, struct asus_wmi, lightbar_led);
 asus_wmi_get_devstate(asus, ASUS_WMI_DEVID_LIGHTBAR, &result);

 return result & ASUS_WMI_DSTS_LIGHTBAR_MASK;
}

static int micmute_led_set(struct led_classdev *led_cdev,
      enum led_brightness brightness)
{
 int state = brightness != LED_OFF;
 int err;

 err = asus_wmi_set_devstate(ASUS_WMI_DEVID_MICMUTE_LED, state, NULL);
 return err < 0 ? err : 0;
}

static enum led_brightness camera_led_get(struct led_classdev *led_cdev)
{
 struct asus_wmi *asus;
 u32 result;

 asus = container_of(led_cdev, struct asus_wmi, camera_led);
 asus_wmi_get_devstate(asus, ASUS_WMI_DEVID_CAMERA_LED, &result);

 return result & ASUS_WMI_DSTS_BRIGHTNESS_MASK;
}

static int camera_led_set(struct led_classdev *led_cdev,
      enum led_brightness brightness)
{
 int state = brightness != LED_OFF;
 int err;

 err = asus_wmi_set_devstate(ASUS_WMI_DEVID_CAMERA_LED, state, NULL);
 return err < 0 ? err : 0;
}

static void asus_wmi_led_exit(struct asus_wmi *asus)
{
 led_classdev_unregister(&asus->kbd_led);
 led_classdev_unregister(&asus->tpd_led);
 led_classdev_unregister(&asus->wlan_led);
 led_classdev_unregister(&asus->lightbar_led);
 led_classdev_unregister(&asus->micmute_led);
 led_classdev_unregister(&asus->camera_led);

 if (asus->led_workqueue)
  destroy_workqueue(asus->led_workqueue);
}

static int asus_wmi_led_init(struct asus_wmi *asus)
{
 int rv = 0, num_rgb_groups = 0, led_val;

 if (asus->kbd_rgb_dev)
  kbd_rgb_mode_groups[num_rgb_groups++] = &kbd_rgb_mode_group;
 if (asus->kbd_rgb_state_available)
  kbd_rgb_mode_groups[num_rgb_groups++] = &kbd_rgb_state_group;

 asus->led_workqueue = create_singlethread_workqueue("led_workqueue");
 if (!asus->led_workqueue)
  return -ENOMEM;

 if (read_tpd_led_state(asus) >= 0) {
  INIT_WORK(&asus->tpd_led_work, tpd_led_update);

  asus->tpd_led.name = "asus::touchpad";
  asus->tpd_led.brightness_set = tpd_led_set;
  asus->tpd_led.brightness_get = tpd_led_get;
  asus->tpd_led.max_brightness = 1;

  rv = led_classdev_register(&asus->platform_device->dev,
        &asus->tpd_led);
  if (rv)
   goto error;
 }

 if (!kbd_led_read(asus, &led_val, NULL) && !dmi_check_system(asus_use_hid_led_dmi_ids)) {
  pr_info("using asus-wmi for asus::kbd_backlight\n");
  asus->kbd_led_wk = led_val;
  asus->kbd_led.name = "asus::kbd_backlight";
  asus->kbd_led.flags = LED_BRIGHT_HW_CHANGED;
  asus->kbd_led.brightness_set = kbd_led_set;
  asus->kbd_led.brightness_get = kbd_led_get;
  asus->kbd_led.max_brightness = 3;

  if (num_rgb_groups != 0)
   asus->kbd_led.groups = kbd_rgb_mode_groups;

  rv = led_classdev_register(&asus->platform_device->dev,
        &asus->kbd_led);
  if (rv)
   goto error;
 }

 if (asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_WIRELESS_LED)
   && (asus->driver->quirks->wapf > 0)) {
  INIT_WORK(&asus->wlan_led_work, wlan_led_update);

  asus->wlan_led.name = "asus::wlan";
  asus->wlan_led.brightness_set = wlan_led_set;
  if (!wlan_led_unknown_state(asus))
   asus->wlan_led.brightness_get = wlan_led_get;
  asus->wlan_led.flags = LED_CORE_SUSPENDRESUME;
  asus->wlan_led.max_brightness = 1;
  asus->wlan_led.default_trigger = "asus-wlan";

  rv = led_classdev_register(&asus->platform_device->dev,
        &asus->wlan_led);
  if (rv)
   goto error;
 }

 if (asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_LIGHTBAR)) {
  INIT_WORK(&asus->lightbar_led_work, lightbar_led_update);

  asus->lightbar_led.name = "asus::lightbar";
  asus->lightbar_led.brightness_set = lightbar_led_set;
  asus->lightbar_led.brightness_get = lightbar_led_get;
  asus->lightbar_led.max_brightness = 1;

  rv = led_classdev_register(&asus->platform_device->dev,
        &asus->lightbar_led);
 }

 if (asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_MICMUTE_LED)) {
  asus->micmute_led.name = "platform::micmute";
  asus->micmute_led.max_brightness = 1;
  asus->micmute_led.brightness_set_blocking = micmute_led_set;
  asus->micmute_led.default_trigger = "audio-micmute";

  rv = led_classdev_register(&asus->platform_device->dev,
      &asus->micmute_led);
  if (rv)
   goto error;
 }

 if (asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_CAMERA_LED)) {
  asus->camera_led.name = "asus::camera";
  asus->camera_led.max_brightness = 1;
  asus->camera_led.brightness_get = camera_led_get;
  asus->camera_led.brightness_set_blocking = camera_led_set;

  rv = led_classdev_register(&asus->platform_device->dev,
      &asus->camera_led);
  if (rv)
   goto error;
 }

 if (asus->oobe_state_available) {
  /*
 * Disable OOBE state, so that e.g. the keyboard backlight
 * works.
 */

  rv = asus_wmi_set_devstate(ASUS_WMI_DEVID_OOBE, 1, NULL);
  if (rv)
   goto error;
 }

error:
 if (rv)
  asus_wmi_led_exit(asus);

 return rv;
}

/* RF *************************************************************************/

/*
 * PCI hotplug (for wlan rfkill)
 */

static bool asus_wlan_rfkill_blocked(struct asus_wmi *asus)
{
 int result = asus_wmi_get_devstate_simple(asus, ASUS_WMI_DEVID_WLAN);

 if (result < 0)
  return false;
 return !result;
}

static void asus_rfkill_hotplug(struct asus_wmi *asus)
{
 struct pci_dev *dev;
 struct pci_bus *bus;
 bool blocked;
 bool absent;
 u32 l;

 mutex_lock(&asus->wmi_lock);
 blocked = asus_wlan_rfkill_blocked(asus);
 mutex_unlock(&asus->wmi_lock);

 mutex_lock(&asus->hotplug_lock);
 pci_lock_rescan_remove();

 if (asus->wlan.rfkill)
  rfkill_set_sw_state(asus->wlan.rfkill, blocked);

 if (asus->hotplug_slot.ops) {
  bus = pci_find_bus(0, 1);
  if (!bus) {
   pr_warn("Unable to find PCI bus 1?\n");
   goto out_unlock;
  }

  if (pci_bus_read_config_dword(bus, 0, PCI_VENDOR_ID, &l)) {
   pr_err("Unable to read PCI config space?\n");
   goto out_unlock;
  }
  absent = (l == 0xffffffff);

  if (blocked != absent) {
   pr_warn("BIOS says wireless lan is %s, but the pci device is %s\n",
    blocked ? "blocked" : "unblocked",
    absent ? "absent" : "present");
   pr_warn("skipped wireless hotplug as probably inappropriate for this model\n");
   goto out_unlock;
  }

  if (!blocked) {
   dev = pci_get_slot(bus, 0);
   if (dev) {
    /* Device already present */
    pci_dev_put(dev);
    goto out_unlock;
   }
   dev = pci_scan_single_device(bus, 0);
   if (dev) {
    pci_bus_assign_resources(bus);
    pci_bus_add_device(dev);
   }
  } else {
   dev = pci_get_slot(bus, 0);
   if (dev) {
    pci_stop_and_remove_bus_device(dev);
    pci_dev_put(dev);
   }
  }
 }

out_unlock:
 pci_unlock_rescan_remove();
 mutex_unlock(&asus->hotplug_lock);
}

static void asus_rfkill_notify(acpi_handle handle, u32 event, void *data)
{
 struct asus_wmi *asus = data;

 if (event != ACPI_NOTIFY_BUS_CHECK)
  return;

 /*
 * We can't call directly asus_rfkill_hotplug because most
 * of the time WMBC is still being executed and not reetrant.
 * There is currently no way to tell ACPICA that  we want this
 * method to be serialized, we schedule a asus_rfkill_hotplug
 * call later, in a safer context.
 */

 queue_work(asus->hotplug_workqueue, &asus->hotplug_work);
}

static int asus_register_rfkill_notifier(struct asus_wmi *asus, char *node)
{
 acpi_status status;
 acpi_handle handle;

 status = acpi_get_handle(NULL, node, &handle);
 if (ACPI_FAILURE(status))
  return -ENODEV;

 status = acpi_install_notify_handler(handle, ACPI_SYSTEM_NOTIFY,
          asus_rfkill_notify, asus);
 if (ACPI_FAILURE(status))
  pr_warn("Failed to register notify on %s\n", node);

 return 0;
}

static void asus_unregister_rfkill_notifier(struct asus_wmi *asus, char *node)
{
 acpi_status status = AE_OK;
 acpi_handle handle;

 status = acpi_get_handle(NULL, node, &handle);
 if (ACPI_FAILURE(status))
  return;

 status = acpi_remove_notify_handler(handle, ACPI_SYSTEM_NOTIFY,
         asus_rfkill_notify);
 if (ACPI_FAILURE(status))
  pr_err("Error removing rfkill notify handler %s\n", node);
}

static int asus_get_adapter_status(struct hotplug_slot *hotplug_slot,
       u8 *value)
{
 struct asus_wmi *asus = container_of(hotplug_slot,
          struct asus_wmi, hotplug_slot);
 int result = asus_wmi_get_devstate_simple(asus, ASUS_WMI_DEVID_WLAN);

 if (result < 0)
  return result;

 *value = !!result;
 return 0;
}

static const struct hotplug_slot_ops asus_hotplug_slot_ops = {
 .get_adapter_status = asus_get_adapter_status,
 .get_power_status = asus_get_adapter_status,
};

static void asus_hotplug_work(struct work_struct *work)
{
 struct asus_wmi *asus;

 asus = container_of(work, struct asus_wmi, hotplug_work);
 asus_rfkill_hotplug(asus);
}

static int asus_setup_pci_hotplug(struct asus_wmi *asus)
{
 int ret = -ENOMEM;
 struct pci_bus *bus = pci_find_bus(0, 1);

 if (!bus) {
  pr_err("Unable to find wifi PCI bus\n");
  return -ENODEV;
 }

 asus->hotplug_workqueue =
     create_singlethread_workqueue("hotplug_workqueue");
 if (!asus->hotplug_workqueue)
  goto error_workqueue;

 INIT_WORK(&asus->hotplug_work, asus_hotplug_work);

 asus->hotplug_slot.ops = &asus_hotplug_slot_ops;

 ret = pci_hp_register(&asus->hotplug_slot, bus, 0, "asus-wifi");
 if (ret) {
  pr_err("Unable to register hotplug slot - %d\n", ret);
  goto error_register;
 }

 return 0;

error_register:
 asus->hotplug_slot.ops = NULL;
 destroy_workqueue(asus->hotplug_workqueue);
error_workqueue:
 return ret;
}

/*
 * Rfkill devices
 */

static int asus_rfkill_set(void *data, bool blocked)
{
 struct asus_rfkill *priv = data;
 u32 ctrl_param = !blocked;
 u32 dev_id = priv->dev_id;

 /*
 * If the user bit is set, BIOS can't set and record the wlan status,
 * it will report the value read from id ASUS_WMI_DEVID_WLAN_LED
 * while we query the wlan status through WMI(ASUS_WMI_DEVID_WLAN).
 * So, we have to record wlan status in id ASUS_WMI_DEVID_WLAN_LED
 * while setting the wlan status through WMI.
 * This is also the behavior that windows app will do.
 */

 if ((dev_id == ASUS_WMI_DEVID_WLAN) &&
      priv->asus->driver->wlan_ctrl_by_user)
  dev_id = ASUS_WMI_DEVID_WLAN_LED;

 return asus_wmi_set_devstate(dev_id, ctrl_param, NULL);
}

static void asus_rfkill_query(struct rfkill *rfkill, void *data)
{
 struct asus_rfkill *priv = data;
 int result;

 result = asus_wmi_get_devstate_simple(priv->asus, priv->dev_id);

 if (result < 0)
  return;

 rfkill_set_sw_state(priv->rfkill, !result);
}

static int asus_rfkill_wlan_set(void *data, bool blocked)
{
 struct asus_rfkill *priv = data;
 struct asus_wmi *asus = priv->asus;
 int ret;

 /*
 * This handler is enabled only if hotplug is enabled.
 * In this case, the asus_wmi_set_devstate() will
 * trigger a wmi notification and we need to wait
 * this call to finish before being able to call
 * any wmi method
 */

 mutex_lock(&asus->wmi_lock);
 ret = asus_rfkill_set(data, blocked);
 mutex_unlock(&asus->wmi_lock);
 return ret;
}

static const struct rfkill_ops asus_rfkill_wlan_ops = {
 .set_block = asus_rfkill_wlan_set,
 .query = asus_rfkill_query,
};

static const struct rfkill_ops asus_rfkill_ops = {
 .set_block = asus_rfkill_set,
 .query = asus_rfkill_query,
};

static int asus_new_rfkill(struct asus_wmi *asus,
      struct asus_rfkill *arfkill,
      const char *name, enum rfkill_type type, int dev_id)
{
 int result = asus_wmi_get_devstate_simple(asus, dev_id);
 struct rfkill **rfkill = &arfkill->rfkill;

 if (result < 0)
  return result;

 arfkill->dev_id = dev_id;
 arfkill->asus = asus;

 if (dev_id == ASUS_WMI_DEVID_WLAN &&
     asus->driver->quirks->hotplug_wireless)
  *rfkill = rfkill_alloc(name, &asus->platform_device->dev, type,
           &asus_rfkill_wlan_ops, arfkill);
 else
  *rfkill = rfkill_alloc(name, &asus->platform_device->dev, type,
           &asus_rfkill_ops, arfkill);

 if (!*rfkill)
  return -EINVAL;

 if ((dev_id == ASUS_WMI_DEVID_WLAN) &&
   (asus->driver->quirks->wapf > 0))
  rfkill_set_led_trigger_name(*rfkill, "asus-wlan");

 rfkill_init_sw_state(*rfkill, !result);
 result = rfkill_register(*rfkill);
 if (result) {
  rfkill_destroy(*rfkill);
  *rfkill = NULL;
  return result;
 }
 return 0;
}

static void asus_wmi_rfkill_exit(struct asus_wmi *asus)
{
 if (asus->driver->wlan_ctrl_by_user && ashs_present())
  return;

 asus_unregister_rfkill_notifier(asus, "\\_SB.PCI0.P0P5");
 asus_unregister_rfkill_notifier(asus, "\\_SB.PCI0.P0P6");
 asus_unregister_rfkill_notifier(asus, "\\_SB.PCI0.P0P7");
 if (asus->wlan.rfkill) {
  rfkill_unregister(asus->wlan.rfkill);
  rfkill_destroy(asus->wlan.rfkill);
  asus->wlan.rfkill = NULL;
 }
 /*
 * Refresh pci hotplug in case the rfkill state was changed after
 * asus_unregister_rfkill_notifier()
 */

 asus_rfkill_hotplug(asus);
 if (asus->hotplug_slot.ops)
  pci_hp_deregister(&asus->hotplug_slot);
 if (asus->hotplug_workqueue)
  destroy_workqueue(asus->hotplug_workqueue);

 if (asus->bluetooth.rfkill) {
  rfkill_unregister(asus->bluetooth.rfkill);
  rfkill_destroy(asus->bluetooth.rfkill);
  asus->bluetooth.rfkill = NULL;
 }
 if (asus->wimax.rfkill) {
  rfkill_unregister(asus->wimax.rfkill);
  rfkill_destroy(asus->wimax.rfkill);
  asus->wimax.rfkill = NULL;
 }
 if (asus->wwan3g.rfkill) {
  rfkill_unregister(asus->wwan3g.rfkill);
  rfkill_destroy(asus->wwan3g.rfkill);
  asus->wwan3g.rfkill = NULL;
 }
 if (asus->gps.rfkill) {
  rfkill_unregister(asus->gps.rfkill);
  rfkill_destroy(asus->gps.rfkill);
  asus->gps.rfkill = NULL;
 }
 if (asus->uwb.rfkill) {
  rfkill_unregister(asus->uwb.rfkill);
  rfkill_destroy(asus->uwb.rfkill);
  asus->uwb.rfkill = NULL;
 }
}

static int asus_wmi_rfkill_init(struct asus_wmi *asus)
{
 int result = 0;

 mutex_init(&asus->hotplug_lock);
 mutex_init(&asus->wmi_lock);

 result = asus_new_rfkill(asus, &asus->wlan, "asus-wlan",
     RFKILL_TYPE_WLAN, ASUS_WMI_DEVID_WLAN);

 if (result && result != -ENODEV)
  goto exit;

 result = asus_new_rfkill(asus, &asus->bluetooth,
     "asus-bluetooth", RFKILL_TYPE_BLUETOOTH,
     ASUS_WMI_DEVID_BLUETOOTH);

 if (result && result != -ENODEV)
  goto exit;

 result = asus_new_rfkill(asus, &asus->wimax, "asus-wimax",
     RFKILL_TYPE_WIMAX, ASUS_WMI_DEVID_WIMAX);

 if (result && result != -ENODEV)
  goto exit;

 result = asus_new_rfkill(asus, &asus->wwan3g, "asus-wwan3g",
     RFKILL_TYPE_WWAN, ASUS_WMI_DEVID_WWAN3G);

 if (result && result != -ENODEV)
  goto exit;

 result = asus_new_rfkill(asus, &asus->gps, "asus-gps",
     RFKILL_TYPE_GPS, ASUS_WMI_DEVID_GPS);

 if (result && result != -ENODEV)
  goto exit;

 result = asus_new_rfkill(asus, &asus->uwb, "asus-uwb",
     RFKILL_TYPE_UWB, ASUS_WMI_DEVID_UWB);

 if (result && result != -ENODEV)
  goto exit;

 if (!asus->driver->quirks->hotplug_wireless)
  goto exit;

 result = asus_setup_pci_hotplug(asus);
 /*
 * If we get -EBUSY then something else is handling the PCI hotplug -
 * don't fail in this case
 */

 if (result == -EBUSY)
  result = 0;

 asus_register_rfkill_notifier(asus, "\\_SB.PCI0.P0P5");
 asus_register_rfkill_notifier(asus, "\\_SB.PCI0.P0P6");
 asus_register_rfkill_notifier(asus, "\\_SB.PCI0.P0P7");
 /*
 * Refresh pci hotplug in case the rfkill state was changed during
 * setup.
 */

 asus_rfkill_hotplug(asus);

exit:
 if (result && result != -ENODEV)
  asus_wmi_rfkill_exit(asus);

 if (result == -ENODEV)
  result = 0;

 return result;
}

/* Panel Overdrive ************************************************************/
static ssize_t panel_od_show(struct device *dev,
       struct device_attribute *attr, char *buf)
{
 struct asus_wmi *asus = dev_get_drvdata(dev);
 int result;

 result = asus_wmi_get_devstate_simple(asus, ASUS_WMI_DEVID_PANEL_OD);
 if (result < 0)
  return result;

 return sysfs_emit(buf, "%d\n", result);
}

static ssize_t panel_od_store(struct device *dev,
        struct device_attribute *attr,
        const char *buf, size_t count)
{
 int result, err;
 u32 overdrive;

 struct asus_wmi *asus = dev_get_drvdata(dev);

 result = kstrtou32(buf, 10, &overdrive);
 if (result)
  return result;

 if (overdrive > 1)
  return -EINVAL;

 err = asus_wmi_set_devstate(ASUS_WMI_DEVID_PANEL_OD, overdrive, &result);

 if (err) {
  pr_warn("Failed to set panel overdrive: %d\n", err);
  return err;
 }

 if (result > 1) {
  pr_warn("Failed to set panel overdrive (result): 0x%x\n", result);
  return -EIO;
 }

 sysfs_notify(&asus->platform_device->dev.kobj, NULL, "panel_od");

 return count;
}
static DEVICE_ATTR_RW(panel_od);

/* Bootup sound ***************************************************************/

static ssize_t boot_sound_show(struct device *dev,
        struct device_attribute *attr, char *buf)
{
 struct asus_wmi *asus = dev_get_drvdata(dev);
 int result;

 result = asus_wmi_get_devstate_simple(asus, ASUS_WMI_DEVID_BOOT_SOUND);
 if (result < 0)
  return result;

 return sysfs_emit(buf, "%d\n", result);
}

static ssize_t boot_sound_store(struct device *dev,
         struct device_attribute *attr,
         const char *buf, size_t count)
{
 int result, err;
 u32 snd;

 struct asus_wmi *asus = dev_get_drvdata(dev);

 result = kstrtou32(buf, 10, &snd);
 if (result)
  return result;

 if (snd > 1)
  return -EINVAL;

 err = asus_wmi_set_devstate(ASUS_WMI_DEVID_BOOT_SOUND, snd, &result);
 if (err) {
  pr_warn("Failed to set boot sound: %d\n", err);
  return err;
 }

 if (result > 1) {
  pr_warn("Failed to set panel boot sound (result): 0x%x\n", result);
  return -EIO;
 }

 sysfs_notify(&asus->platform_device->dev.kobj, NULL, "boot_sound");

 return count;
}
static DEVICE_ATTR_RW(boot_sound);

/* Mini-LED mode **************************************************************/
static ssize_t mini_led_mode_show(struct device *dev,
       struct device_attribute *attr, char *buf)
{
 struct asus_wmi *asus = dev_get_drvdata(dev);
 u32 value;
 int err;

 err = asus_wmi_get_devstate(asus, asus->mini_led_dev_id, &value);
 if (err < 0)
  return err;
 value = value & ASUS_MINI_LED_MODE_MASK;

 /*
 * Remap the mode values to match previous generation mini-led. The last gen
 * WMI 0 == off, while on this version WMI 2 ==off (flipped).
 */

 if (asus->mini_led_dev_id == ASUS_WMI_DEVID_MINI_LED_MODE2) {
  switch (value) {
  case ASUS_MINI_LED_2024_WEAK:
   value = ASUS_MINI_LED_ON;
   break;
  case ASUS_MINI_LED_2024_STRONG:
   value = ASUS_MINI_LED_STRONG_MODE;
   break;
  case ASUS_MINI_LED_2024_OFF:
   value = ASUS_MINI_LED_OFF;
   break;
  }
 }

 return sysfs_emit(buf, "%d\n", value);
}

static ssize_t mini_led_mode_store(struct device *dev,
        struct device_attribute *attr,
        const char *buf, size_t count)
{
 int result, err;
 u32 mode;

 struct asus_wmi *asus = dev_get_drvdata(dev);

 result = kstrtou32(buf, 10, &mode);
 if (result)
  return result;

 if (asus->mini_led_dev_id == ASUS_WMI_DEVID_MINI_LED_MODE &&
     mode > ASUS_MINI_LED_ON)
  return -EINVAL;
 if (asus->mini_led_dev_id == ASUS_WMI_DEVID_MINI_LED_MODE2 &&
     mode > ASUS_MINI_LED_STRONG_MODE)
  return -EINVAL;

 /*
 * Remap the mode values so expected behaviour is the same as the last
 * generation of mini-LED with 0 == off, 1 == on.
 */

 if (asus->mini_led_dev_id == ASUS_WMI_DEVID_MINI_LED_MODE2) {
  switch (mode) {
  case ASUS_MINI_LED_OFF:
   mode = ASUS_MINI_LED_2024_OFF;
   break;
  case ASUS_MINI_LED_ON:
   mode = ASUS_MINI_LED_2024_WEAK;
   break;
  case ASUS_MINI_LED_STRONG_MODE:
   mode = ASUS_MINI_LED_2024_STRONG;
   break;
  }
 }

 err = asus_wmi_set_devstate(asus->mini_led_dev_id, mode, &result);
 if (err) {
  pr_warn("Failed to set mini-LED: %d\n", err);
  return err;
 }

 if (result > 1) {
  pr_warn("Failed to set mini-LED mode (result): 0x%x\n", result);
  return -EIO;
 }

 sysfs_notify(&asus->platform_device->dev.kobj, NULL, "mini_led_mode");

 return count;
}
static DEVICE_ATTR_RW(mini_led_mode);

static ssize_t available_mini_led_mode_show(struct device *dev,
      struct device_attribute *attr, char *buf)
{
 struct asus_wmi *asus = dev_get_drvdata(dev);

 switch (asus->mini_led_dev_id) {
 case ASUS_WMI_DEVID_MINI_LED_MODE:
  return sysfs_emit(buf, "0 1\n");
 case ASUS_WMI_DEVID_MINI_LED_MODE2:
  return sysfs_emit(buf, "0 1 2\n");
 }

 return sysfs_emit(buf, "0\n");
}

static DEVICE_ATTR_RO(available_mini_led_mode);

/* Quirks *********************************************************************/

static void asus_wmi_set_xusb2pr(struct asus_wmi *asus)
{
 struct pci_dev *xhci_pdev;
 u32 orig_ports_available;
 u32 ports_available = asus->driver->quirks->xusb2pr;

 xhci_pdev = pci_get_device(PCI_VENDOR_ID_INTEL,
   PCI_DEVICE_ID_INTEL_LYNXPOINT_LP_XHCI,
   NULL);

 if (!xhci_pdev)
  return;

 pci_read_config_dword(xhci_pdev, USB_INTEL_XUSB2PR,
    &orig_ports_available);

 pci_write_config_dword(xhci_pdev, USB_INTEL_XUSB2PR,
    cpu_to_le32(ports_available));

 pci_dev_put(xhci_pdev);

 pr_info("set USB_INTEL_XUSB2PR old: 0x%04x, new: 0x%04x\n",
   orig_ports_available, ports_available);
}

/*
 * Some devices dont support or have borcken get_als method
 * but still support set method.
 */

static void asus_wmi_set_als(void)
{
 asus_wmi_set_devstate(ASUS_WMI_DEVID_ALS_ENABLE, 1, NULL);
}

/* Hwmon device ***************************************************************/

static int asus_agfn_fan_speed_read(struct asus_wmi *asus, int fan,
       int *speed)
{
 struct agfn_fan_args args = {
  .agfn.len = sizeof(args),
  .agfn.mfun = ASUS_FAN_MFUN,
  .agfn.sfun = ASUS_FAN_SFUN_READ,
  .fan = fan,
  .speed = 0,
 };
 struct acpi_buffer input = { (acpi_size) sizeof(args), &args };
 int status;

 if (fan != 1)
  return -EINVAL;

 status = asus_wmi_evaluate_method_agfn(input);

 if (status || args.agfn.err)
  return -ENXIO;

 if (speed)
  *speed = args.speed;

 return 0;
}

static int asus_agfn_fan_speed_write(struct asus_wmi *asus, int fan,
         int *speed)
{
 struct agfn_fan_args args = {
  .agfn.len = sizeof(args),
  .agfn.mfun = ASUS_FAN_MFUN,
  .agfn.sfun = ASUS_FAN_SFUN_WRITE,
  .fan = fan,
  .speed = speed ?  *speed : 0,
 };
 struct acpi_buffer input = { (acpi_size) sizeof(args), &args };
 int status;

 /* 1: for setting 1st fan's speed 0: setting auto mode */
 if (fan != 1 && fan != 0)
  return -EINVAL;

 status = asus_wmi_evaluate_method_agfn(input);

 if (status || args.agfn.err)
  return -ENXIO;

--> --------------------

--> maximum size reached

--> --------------------

Messung V0.5
C=97 H=97 G=96

¤ Dauer der Verarbeitung: 0.34 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Normalansicht

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge