⛏️ index : haiku.git

/*
 * Copyright 2020, Jérôme Duval, jerome.duval@gmail.com.
 * Distributed under the terms of the MIT license.
 */


#define DRIVER_NAME "wmi_asus"
#include "WMIPrivate.h"


#define ACPI_ASUS_WMI_MGMT_GUID		"97845ED0-4E6D-11DE-8A39-0800200C9A66"
#define ACPI_ASUS_WMI_EVENT_GUID	"0B3CBB35-E3C2-45ED-91C2-4C5A6D195D1C"
#define ACPI_ASUS_UID_ASUSWMI		"ASUSWMI"

#define ASUS_WMI_METHODID_INIT		0x54494E49
#define ASUS_WMI_METHODID_SPEC		0x43455053
#define ASUS_WMI_METHODID_SFUN		0x4E554653
#define ASUS_WMI_METHODID_DCTS		0x53544344
#define ASUS_WMI_METHODID_DSTS		0x53545344
#define ASUS_WMI_METHODID_DEVS		0x53564544


#define ASUS_WMI_DEVID_ALS_ENABLE		0x00050001
#define ASUS_WMI_DEVID_BRIGHTNESS		0x00050012
#define ASUS_WMI_DEVID_KBD_BACKLIGHT	0x00050021
#define ASUS_WMI_DEVID_FN_LOCK			0x00100023

#define ASUS_WMI_DSTS_PRESENCE_BIT		0x10000


class WMIAsus {
public:
								WMIAsus(device_node *node);
								~WMIAsus();

private:
			status_t 			_EvaluateMethod(uint32 methodId, uint32 arg0,
									uint32 arg1, uint32 *returnValue);
			status_t			_GetDevState(uint32 devId, uint32* value);
			status_t			_SetDevState(uint32 devId, uint32 arg,
									uint32* value);
	static	void				_NotifyHandler(acpi_handle handle,
									uint32 notify, void *context);
			void				_Notify(acpi_handle handle, uint32 notify);
			status_t			_EnableAls(uint32 enable);
private:
			device_node*		fNode;
			wmi_device_interface* wmi;
			wmi_device			wmi_cookie;
			uint32				fDstsID;
			const char*			fEventGuidString;
			bool				fEnableALS;
			bool				fFnLock;
};



WMIAsus::WMIAsus(device_node *node)
	:
	fNode(node),
	fDstsID(ASUS_WMI_METHODID_DSTS),
	fEventGuidString(NULL),
	fEnableALS(false),
	fFnLock(true)
{
	CALLED();

	device_node *parent;
	parent = gDeviceManager->get_parent_node(node);
	gDeviceManager->get_driver(parent, (driver_module_info **)&wmi,
		(void **)&wmi_cookie);

	gDeviceManager->put_node(parent);

	const char* uid = wmi->get_uid(wmi_cookie);
	if (uid != NULL && strcmp(uid, ACPI_ASUS_UID_ASUSWMI) == 0)
		fDstsID = ASUS_WMI_METHODID_DCTS;
	TRACE("WMIAsus using _UID %s\n", uid);
	uint32 value;
	if (_EvaluateMethod(ASUS_WMI_METHODID_INIT, 0, 0, &value) == B_OK) {
		TRACE("_INIT: %x\n", value);
	}
	if (_EvaluateMethod(ASUS_WMI_METHODID_SPEC, 0, 9, &value) == B_OK) {
		TRACE("_SPEC: %x\n", value);
	}
	if (_EvaluateMethod(ASUS_WMI_METHODID_SFUN, 0, 0, &value) == B_OK) {
		TRACE("_SFUN: %x\n", value);
	}

	// some ASUS laptops need to be ALS forced
	fEnableALS =
		gSMBios->match_vendor_product("ASUSTeK COMPUTER INC.", "UX430UAR");
	if (fEnableALS && _EnableAls(1) == B_OK) {
		TRACE("ALSC enabled\n");
	}

	if (_GetDevState(ASUS_WMI_DEVID_FN_LOCK, &value) == B_OK
		&& (value & ASUS_WMI_DSTS_PRESENCE_BIT) != 0) {
		// set fn lock
		_SetDevState(ASUS_WMI_DEVID_FN_LOCK, fFnLock, NULL);
	}

	// install event handler
	if (wmi->install_event_handler(wmi_cookie, ACPI_ASUS_WMI_EVENT_GUID,
		_NotifyHandler, this) == B_OK) {
		fEventGuidString = ACPI_ASUS_WMI_EVENT_GUID;
	}
}


WMIAsus::~WMIAsus()
{
	// for ALS
	if (fEnableALS && _EnableAls(0) == B_OK) {
		TRACE("ALSC disabled\n");
	}

	if (fEventGuidString != NULL)
		wmi->remove_event_handler(wmi_cookie, fEventGuidString);
}


status_t
WMIAsus::_EvaluateMethod(uint32 methodId, uint32 arg0, uint32 arg1,
	uint32 *returnValue)
{
	CALLED();
	uint32 params[] = { arg0, arg1, 0, 0, 0 };
	acpi_data inBuffer = { sizeof(params), params };
	acpi_data outBuffer = { ACPI_ALLOCATE_BUFFER, NULL };
	status_t status = wmi->evaluate_method(wmi_cookie, 0, methodId, &inBuffer,
		&outBuffer);
	if (status != B_OK)
		return status;

	acpi_object_type* object = (acpi_object_type*)outBuffer.pointer;
	uint32 result = 0;
	if (object != NULL) {
		if (object->object_type == ACPI_TYPE_INTEGER)
			result = object->integer.integer;
		free(object);
	}
	if (returnValue != NULL)
		*returnValue = result;

	return B_OK;
}


status_t
WMIAsus::_EnableAls(uint32 enable)
{
	CALLED();
	return _SetDevState(ASUS_WMI_DEVID_ALS_ENABLE, enable, NULL);
}


status_t
WMIAsus::_GetDevState(uint32 devId, uint32 *value)
{
	return _EvaluateMethod(fDstsID, devId, 0, value);
}


status_t
WMIAsus::_SetDevState(uint32 devId, uint32 arg, uint32 *value)
{
	return _EvaluateMethod(ASUS_WMI_METHODID_DEVS, devId, arg, value);
}


void
WMIAsus::_NotifyHandler(acpi_handle handle, uint32 notify, void *context)
{
	WMIAsus* device = (WMIAsus*)context;
	device->_Notify(handle, notify);
}


void
WMIAsus::_Notify(acpi_handle handle, uint32 notify)
{
	CALLED();

	acpi_data response = { ACPI_ALLOCATE_BUFFER, NULL };
	wmi->get_event_data(wmi_cookie, notify, &response);

	acpi_object_type* object = (acpi_object_type*)response.pointer;
	uint32 result = 0;
	if (object != NULL) {
		if (object->object_type == ACPI_TYPE_INTEGER)
			result = object->integer.integer;
		free(object);
	}
	if (result != 0) {
		if (result == 0x4e) {
			TRACE("WMIAsus::_Notify() keyboard fnlock key\n");
			fFnLock = !fFnLock;
			_SetDevState(ASUS_WMI_DEVID_FN_LOCK, fFnLock, NULL);
			TRACE("WMIAsus::_Notify() keyboard fnlock key %" B_PRIx32 "\n",
				fFnLock);
		} else if (result == 0xc4 || result == 0xc5 || result == 0xc7) {
			TRACE("WMIAsus::_Notify() keyboard backlight key\n");
			uint32 value;
			if (_GetDevState(ASUS_WMI_DEVID_KBD_BACKLIGHT, &value) == B_OK) {
				TRACE("WMIAsus::_Notify() getkeyboard backlight key %"
					B_PRIx32 "\n", value);
				value &= 0x7;
				if (result == 0xc4) {
					if (value < 3)
						value++;
				} else if (result == 0xc5) {
					if (value > 0)
						value--;
				} else {
					value++;
					value &= 0x3;
				}
				TRACE("WMIAsus::_Notify() set keyboard backlight key %"
					B_PRIx32 "\n", value);
				_SetDevState(ASUS_WMI_DEVID_KBD_BACKLIGHT, value | 0x80, NULL);
			}
		} else if (result == 0x6b) {
			TRACE("WMIAsus::_Notify() touchpad control\n");
		} else {
			TRACE("WMIAsus::_Notify() key 0x%" B_PRIx32 "\n", result);
		}
	}
}


//	#pragma mark - driver module API


static float
wmi_asus_support(device_node *parent)
{
	// make sure parent is really a wmi device
	const char *bus;
	if (gDeviceManager->get_attr_string(parent, B_DEVICE_BUS, &bus, false))
		return -1;

	if (strcmp(bus, "wmi"))
		return 0.0;

	// check whether it's an asus wmi device
	const char *guid;
	if (gDeviceManager->get_attr_string(parent, WMI_GUID_STRING_ITEM, &guid,
		false) != B_OK || strcmp(guid, ACPI_ASUS_WMI_MGMT_GUID) != 0) {
		return 0.0;
	}

	TRACE("found an asus wmi device\n");

	return 0.6;
}


static status_t
wmi_asus_register_device(device_node *node)
{
	CALLED();
	device_attr attrs[] = {
		{ B_DEVICE_PRETTY_NAME, B_STRING_TYPE, { .string = "WMI ASUS" }},
		{ NULL }
	};

	return gDeviceManager->register_node(node, WMI_ASUS_DRIVER_NAME, attrs,
		NULL, NULL);
}


static status_t
wmi_asus_init_driver(device_node *node, void **driverCookie)
{
	CALLED();

	WMIAsus* device = new(std::nothrow) WMIAsus(node);
	if (device == NULL)
		return B_NO_MEMORY;
	*driverCookie = device;

	return B_OK;
}


static void
wmi_asus_uninit_driver(void *driverCookie)
{
	CALLED();
	WMIAsus* device = (WMIAsus*)driverCookie;
	delete device;
}


static status_t
wmi_asus_register_child_devices(void *cookie)
{
	CALLED();
	return B_OK;
}


driver_module_info gWMIAsusDriverModule = {
	{
		WMI_ASUS_DRIVER_NAME,
		0,
		NULL
	},

	wmi_asus_support,
	wmi_asus_register_device,
	wmi_asus_init_driver,
	wmi_asus_uninit_driver,
	wmi_asus_register_child_devices,
	NULL,	// rescan
	NULL,	// removed
};