Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit afa7c886 authored by Corentin Chary's avatar Corentin Chary Committed by Matthew Garrett
Browse files

eeepc-wmi: add hotplug code for Eeepc 1000H



Implement wireless like hotplug handling (code stolen from eeepc-laptop).

Reminder: on some models rfkill is implemented by logically unplugging the
wireless card from the PCI bus. Despite sending ACPI notifications, this does
not appear to be implemented using standard ACPI hotplug - nor does the
firmware provide the _OSC method required to support native PCIe hotplug.
The only sensible choice appears to be to handle the hotplugging directly in
the platform driver.

Signed-off-by: default avatarCorentin Chary <corentincj@iksaif.net>
Signed-off-by: default avatarMatthew Garrett <mjg@redhat.com>
parent bc40cce2
Loading
Loading
Loading
Loading
+273 −1
Original line number Diff line number Diff line
@@ -37,9 +37,12 @@
#include <linux/backlight.h>
#include <linux/leds.h>
#include <linux/rfkill.h>
#include <linux/pci.h>
#include <linux/pci_hotplug.h>
#include <linux/debugfs.h>
#include <linux/seq_file.h>
#include <linux/platform_device.h>
#include <linux/dmi.h>
#include <acpi/acpi_bus.h>
#include <acpi/acpi_drivers.h>

@@ -72,6 +75,14 @@ MODULE_ALIAS("wmi:"EEEPC_WMI_MGMT_GUID);
#define EEEPC_WMI_DEVID_BLUETOOTH	0x00010013
#define EEEPC_WMI_DEVID_WWAN3G		0x00010019

static bool hotplug_wireless;

module_param(hotplug_wireless, bool, 0444);
MODULE_PARM_DESC(hotplug_wireless,
		 "Enable hotplug for wireless device. "
		 "If your laptop needs that, please report to "
		 "acpi4asus-user@lists.sourceforge.net.");

static const struct key_entry eeepc_wmi_keymap[] = {
	/* Sleep already handled via generic ACPI code */
	{ KE_IGNORE, NOTIFY_BRNDOWN_MIN, { KEY_BRIGHTNESSDOWN } },
@@ -109,6 +120,8 @@ struct eeepc_wmi_debug {
};

struct eeepc_wmi {
	bool hotplug_wireless;

	struct input_dev *inputdev;
	struct backlight_device *backlight_device;
	struct platform_device *platform_device;
@@ -122,6 +135,9 @@ struct eeepc_wmi {
	struct rfkill *bluetooth_rfkill;
	struct rfkill *wwan3g_rfkill;

	struct hotplug_slot *hotplug_slot;
	struct mutex hotplug_lock;

	struct eeepc_wmi_debug debug;
};

@@ -177,7 +193,8 @@ static acpi_status eeepc_wmi_get_devstate(u32 dev_id, u32 *retval)
	u32 tmp;

	status = wmi_evaluate_method(EEEPC_WMI_MGMT_GUID,
			1, EEEPC_WMI_METHODID_DSTS, &input, &output);
				     1, EEEPC_WMI_METHODID_DSTS,
				     &input, &output);

	if (ACPI_FAILURE(status))
		return status;
@@ -333,6 +350,206 @@ static void eeepc_wmi_led_exit(struct eeepc_wmi *eeepc)
		destroy_workqueue(eeepc->led_workqueue);
}

/*
 * PCI hotplug (for wlan rfkill)
 */
static bool eeepc_wlan_rfkill_blocked(struct eeepc_wmi *eeepc)
{
	u32 retval;
	acpi_status status;

	status = eeepc_wmi_get_devstate(EEEPC_WMI_DEVID_WLAN, &retval);

	if (ACPI_FAILURE(status))
		return false;

	return !(retval & 0x1);
}

static void eeepc_rfkill_hotplug(struct eeepc_wmi *eeepc)
{
	struct pci_dev *dev;
	struct pci_bus *bus;
	bool blocked = eeepc_wlan_rfkill_blocked(eeepc);
	bool absent;
	u32 l;

	if (eeepc->wlan_rfkill)
		rfkill_set_sw_state(eeepc->wlan_rfkill, blocked);

	mutex_lock(&eeepc->hotplug_lock);

	if (eeepc->hotplug_slot) {
		bus = pci_find_bus(0, 1);
		if (!bus) {
			pr_warning("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_warning("BIOS says wireless lan is %s, "
					"but the pci device is %s\n",
				blocked ? "blocked" : "unblocked",
				absent ? "absent" : "present");
			pr_warning("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);
				if (pci_bus_add_device(dev))
					pr_err("Unable to hotplug wifi\n");
			}
		} else {
			dev = pci_get_slot(bus, 0);
			if (dev) {
				pci_remove_bus_device(dev);
				pci_dev_put(dev);
			}
		}
	}

out_unlock:
	mutex_unlock(&eeepc->hotplug_lock);
}

static void eeepc_rfkill_notify(acpi_handle handle, u32 event, void *data)
{
	struct eeepc_wmi *eeepc = data;

	if (event != ACPI_NOTIFY_BUS_CHECK)
		return;

	eeepc_rfkill_hotplug(eeepc);
}

static int eeepc_register_rfkill_notifier(struct eeepc_wmi *eeepc,
					  char *node)
{
	acpi_status status;
	acpi_handle handle;

	status = acpi_get_handle(NULL, node, &handle);

	if (ACPI_SUCCESS(status)) {
		status = acpi_install_notify_handler(handle,
						     ACPI_SYSTEM_NOTIFY,
						     eeepc_rfkill_notify,
						     eeepc);
		if (ACPI_FAILURE(status))
			pr_warning("Failed to register notify on %s\n", node);
	} else
		return -ENODEV;

	return 0;
}

static void eeepc_unregister_rfkill_notifier(struct eeepc_wmi *eeepc,
					     char *node)
{
	acpi_status status = AE_OK;
	acpi_handle handle;

	status = acpi_get_handle(NULL, node, &handle);

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

static int eeepc_get_adapter_status(struct hotplug_slot *hotplug_slot,
				    u8 *value)
{
	u32 retval;
	acpi_status status;

	status = eeepc_wmi_get_devstate(EEEPC_WMI_DEVID_WLAN, &retval);

	if (ACPI_FAILURE(status))
		return -EIO;

	if (!retval || retval == 0x00060000)
		return -ENODEV;
	else
		*value = (retval & 0x1);

	return 0;
}

static void eeepc_cleanup_pci_hotplug(struct hotplug_slot *hotplug_slot)
{
	kfree(hotplug_slot->info);
	kfree(hotplug_slot);
}

static struct hotplug_slot_ops eeepc_hotplug_slot_ops = {
	.owner = THIS_MODULE,
	.get_adapter_status = eeepc_get_adapter_status,
	.get_power_status = eeepc_get_adapter_status,
};

static int eeepc_setup_pci_hotplug(struct eeepc_wmi *eeepc)
{
	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;
	}

	eeepc->hotplug_slot = kzalloc(sizeof(struct hotplug_slot), GFP_KERNEL);
	if (!eeepc->hotplug_slot)
		goto error_slot;

	eeepc->hotplug_slot->info = kzalloc(sizeof(struct hotplug_slot_info),
					    GFP_KERNEL);
	if (!eeepc->hotplug_slot->info)
		goto error_info;

	eeepc->hotplug_slot->private = eeepc;
	eeepc->hotplug_slot->release = &eeepc_cleanup_pci_hotplug;
	eeepc->hotplug_slot->ops = &eeepc_hotplug_slot_ops;
	eeepc_get_adapter_status(eeepc->hotplug_slot,
				 &eeepc->hotplug_slot->info->adapter_status);

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

	return 0;

error_register:
	kfree(eeepc->hotplug_slot->info);
error_info:
	kfree(eeepc->hotplug_slot);
	eeepc->hotplug_slot = NULL;
error_slot:
	return ret;
}

/*
 * Rfkill devices
 */
@@ -404,11 +621,22 @@ static int eeepc_new_rfkill(struct eeepc_wmi *eeepc,

static void eeepc_wmi_rfkill_exit(struct eeepc_wmi *eeepc)
{
	eeepc_unregister_rfkill_notifier(eeepc, "\\_SB.PCI0.P0P5");
	eeepc_unregister_rfkill_notifier(eeepc, "\\_SB.PCI0.P0P6");
	eeepc_unregister_rfkill_notifier(eeepc, "\\_SB.PCI0.P0P7");
	if (eeepc->wlan_rfkill) {
		rfkill_unregister(eeepc->wlan_rfkill);
		rfkill_destroy(eeepc->wlan_rfkill);
		eeepc->wlan_rfkill = NULL;
	}
	/*
	 * Refresh pci hotplug in case the rfkill state was changed after
	 * eeepc_unregister_rfkill_notifier()
	 */
	eeepc_rfkill_hotplug(eeepc);
	if (eeepc->hotplug_slot)
		pci_hp_deregister(eeepc->hotplug_slot);

	if (eeepc->bluetooth_rfkill) {
		rfkill_unregister(eeepc->bluetooth_rfkill);
		rfkill_destroy(eeepc->bluetooth_rfkill);
@@ -425,6 +653,8 @@ static int eeepc_wmi_rfkill_init(struct eeepc_wmi *eeepc)
{
	int result = 0;

	mutex_init(&eeepc->hotplug_lock);

	result = eeepc_new_rfkill(eeepc, &eeepc->wlan_rfkill,
				  "eeepc-wlan", RFKILL_TYPE_WLAN,
				  EEEPC_WMI_DEVID_WLAN);
@@ -446,6 +676,23 @@ static int eeepc_wmi_rfkill_init(struct eeepc_wmi *eeepc)
	if (result && result != -ENODEV)
		goto exit;

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

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

exit:
	if (result && result != -ENODEV)
		eeepc_wmi_rfkill_exit(eeepc);
@@ -771,6 +1018,28 @@ static int eeepc_wmi_debugfs_init(struct eeepc_wmi *eeepc)
/*
 * WMI Driver
 */
static void eeepc_dmi_check(struct eeepc_wmi *eeepc)
{
	const char *model;

	model = dmi_get_system_info(DMI_PRODUCT_NAME);
	if (!model)
		return;

	/*
	 * Whitelist for wlan hotplug
	 *
	 * Eeepc 1000H needs the current hotplug code to handle
	 * Fn+F2 correctly. We may add other Eeepc here later, but
	 * it seems that most of the laptops supported by eeepc-wmi
	 * don't need to be on this list
	 */
	if (strcmp(model, "1000H") == 0) {
		eeepc->hotplug_wireless = true;
		pr_info("wlan hotplug enabled\n");
	}
}

static struct platform_device * __init eeepc_wmi_add(void)
{
	struct eeepc_wmi *eeepc;
@@ -781,6 +1050,9 @@ static struct platform_device * __init eeepc_wmi_add(void)
	if (!eeepc)
		return ERR_PTR(-ENOMEM);

	eeepc->hotplug_wireless = hotplug_wireless;
	eeepc_dmi_check(eeepc);

	/*
	 * Register the platform device first.  It is used as a parent for the
	 * sub-devices below.