162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci *  eeepc-laptop.c - Asus Eee PC extras
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci *  Based on asus_acpi.c as patched for the Eee PC by Asus:
662306a36Sopenharmony_ci *  ftp://ftp.asus.com/pub/ASUS/EeePC/701/ASUS_ACPI_071126.rar
762306a36Sopenharmony_ci *  Based on eee.c from eeepc-linux
862306a36Sopenharmony_ci */
962306a36Sopenharmony_ci
1062306a36Sopenharmony_ci#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
1162306a36Sopenharmony_ci
1262306a36Sopenharmony_ci#include <linux/kernel.h>
1362306a36Sopenharmony_ci#include <linux/module.h>
1462306a36Sopenharmony_ci#include <linux/init.h>
1562306a36Sopenharmony_ci#include <linux/types.h>
1662306a36Sopenharmony_ci#include <linux/platform_device.h>
1762306a36Sopenharmony_ci#include <linux/backlight.h>
1862306a36Sopenharmony_ci#include <linux/fb.h>
1962306a36Sopenharmony_ci#include <linux/hwmon.h>
2062306a36Sopenharmony_ci#include <linux/hwmon-sysfs.h>
2162306a36Sopenharmony_ci#include <linux/slab.h>
2262306a36Sopenharmony_ci#include <linux/acpi.h>
2362306a36Sopenharmony_ci#include <linux/uaccess.h>
2462306a36Sopenharmony_ci#include <linux/input.h>
2562306a36Sopenharmony_ci#include <linux/input/sparse-keymap.h>
2662306a36Sopenharmony_ci#include <linux/rfkill.h>
2762306a36Sopenharmony_ci#include <linux/pci.h>
2862306a36Sopenharmony_ci#include <linux/pci_hotplug.h>
2962306a36Sopenharmony_ci#include <linux/leds.h>
3062306a36Sopenharmony_ci#include <linux/dmi.h>
3162306a36Sopenharmony_ci#include <acpi/video.h>
3262306a36Sopenharmony_ci
3362306a36Sopenharmony_ci#define EEEPC_LAPTOP_VERSION	"0.1"
3462306a36Sopenharmony_ci#define EEEPC_LAPTOP_NAME	"Eee PC Hotkey Driver"
3562306a36Sopenharmony_ci#define EEEPC_LAPTOP_FILE	"eeepc"
3662306a36Sopenharmony_ci
3762306a36Sopenharmony_ci#define EEEPC_ACPI_CLASS	"hotkey"
3862306a36Sopenharmony_ci#define EEEPC_ACPI_DEVICE_NAME	"Hotkey"
3962306a36Sopenharmony_ci#define EEEPC_ACPI_HID		"ASUS010"
4062306a36Sopenharmony_ci
4162306a36Sopenharmony_ciMODULE_AUTHOR("Corentin Chary, Eric Cooper");
4262306a36Sopenharmony_ciMODULE_DESCRIPTION(EEEPC_LAPTOP_NAME);
4362306a36Sopenharmony_ciMODULE_LICENSE("GPL");
4462306a36Sopenharmony_ci
4562306a36Sopenharmony_cistatic bool hotplug_disabled;
4662306a36Sopenharmony_ci
4762306a36Sopenharmony_cimodule_param(hotplug_disabled, bool, 0444);
4862306a36Sopenharmony_ciMODULE_PARM_DESC(hotplug_disabled,
4962306a36Sopenharmony_ci		 "Disable hotplug for wireless device. "
5062306a36Sopenharmony_ci		 "If your laptop need that, please report to "
5162306a36Sopenharmony_ci		 "acpi4asus-user@lists.sourceforge.net.");
5262306a36Sopenharmony_ci
5362306a36Sopenharmony_ci/*
5462306a36Sopenharmony_ci * Definitions for Asus EeePC
5562306a36Sopenharmony_ci */
5662306a36Sopenharmony_ci#define NOTIFY_BRN_MIN	0x20
5762306a36Sopenharmony_ci#define NOTIFY_BRN_MAX	0x2f
5862306a36Sopenharmony_ci
5962306a36Sopenharmony_cienum {
6062306a36Sopenharmony_ci	DISABLE_ASL_WLAN = 0x0001,
6162306a36Sopenharmony_ci	DISABLE_ASL_BLUETOOTH = 0x0002,
6262306a36Sopenharmony_ci	DISABLE_ASL_IRDA = 0x0004,
6362306a36Sopenharmony_ci	DISABLE_ASL_CAMERA = 0x0008,
6462306a36Sopenharmony_ci	DISABLE_ASL_TV = 0x0010,
6562306a36Sopenharmony_ci	DISABLE_ASL_GPS = 0x0020,
6662306a36Sopenharmony_ci	DISABLE_ASL_DISPLAYSWITCH = 0x0040,
6762306a36Sopenharmony_ci	DISABLE_ASL_MODEM = 0x0080,
6862306a36Sopenharmony_ci	DISABLE_ASL_CARDREADER = 0x0100,
6962306a36Sopenharmony_ci	DISABLE_ASL_3G = 0x0200,
7062306a36Sopenharmony_ci	DISABLE_ASL_WIMAX = 0x0400,
7162306a36Sopenharmony_ci	DISABLE_ASL_HWCF = 0x0800
7262306a36Sopenharmony_ci};
7362306a36Sopenharmony_ci
7462306a36Sopenharmony_cienum {
7562306a36Sopenharmony_ci	CM_ASL_WLAN = 0,
7662306a36Sopenharmony_ci	CM_ASL_BLUETOOTH,
7762306a36Sopenharmony_ci	CM_ASL_IRDA,
7862306a36Sopenharmony_ci	CM_ASL_1394,
7962306a36Sopenharmony_ci	CM_ASL_CAMERA,
8062306a36Sopenharmony_ci	CM_ASL_TV,
8162306a36Sopenharmony_ci	CM_ASL_GPS,
8262306a36Sopenharmony_ci	CM_ASL_DVDROM,
8362306a36Sopenharmony_ci	CM_ASL_DISPLAYSWITCH,
8462306a36Sopenharmony_ci	CM_ASL_PANELBRIGHT,
8562306a36Sopenharmony_ci	CM_ASL_BIOSFLASH,
8662306a36Sopenharmony_ci	CM_ASL_ACPIFLASH,
8762306a36Sopenharmony_ci	CM_ASL_CPUFV,
8862306a36Sopenharmony_ci	CM_ASL_CPUTEMPERATURE,
8962306a36Sopenharmony_ci	CM_ASL_FANCPU,
9062306a36Sopenharmony_ci	CM_ASL_FANCHASSIS,
9162306a36Sopenharmony_ci	CM_ASL_USBPORT1,
9262306a36Sopenharmony_ci	CM_ASL_USBPORT2,
9362306a36Sopenharmony_ci	CM_ASL_USBPORT3,
9462306a36Sopenharmony_ci	CM_ASL_MODEM,
9562306a36Sopenharmony_ci	CM_ASL_CARDREADER,
9662306a36Sopenharmony_ci	CM_ASL_3G,
9762306a36Sopenharmony_ci	CM_ASL_WIMAX,
9862306a36Sopenharmony_ci	CM_ASL_HWCF,
9962306a36Sopenharmony_ci	CM_ASL_LID,
10062306a36Sopenharmony_ci	CM_ASL_TYPE,
10162306a36Sopenharmony_ci	CM_ASL_PANELPOWER,	/*P901*/
10262306a36Sopenharmony_ci	CM_ASL_TPD
10362306a36Sopenharmony_ci};
10462306a36Sopenharmony_ci
10562306a36Sopenharmony_cistatic const char *cm_getv[] = {
10662306a36Sopenharmony_ci	"WLDG", "BTHG", NULL, NULL,
10762306a36Sopenharmony_ci	"CAMG", NULL, NULL, NULL,
10862306a36Sopenharmony_ci	NULL, "PBLG", NULL, NULL,
10962306a36Sopenharmony_ci	"CFVG", NULL, NULL, NULL,
11062306a36Sopenharmony_ci	"USBG", NULL, NULL, "MODG",
11162306a36Sopenharmony_ci	"CRDG", "M3GG", "WIMG", "HWCF",
11262306a36Sopenharmony_ci	"LIDG",	"TYPE", "PBPG",	"TPDG"
11362306a36Sopenharmony_ci};
11462306a36Sopenharmony_ci
11562306a36Sopenharmony_cistatic const char *cm_setv[] = {
11662306a36Sopenharmony_ci	"WLDS", "BTHS", NULL, NULL,
11762306a36Sopenharmony_ci	"CAMS", NULL, NULL, NULL,
11862306a36Sopenharmony_ci	"SDSP", "PBLS", "HDPS", NULL,
11962306a36Sopenharmony_ci	"CFVS", NULL, NULL, NULL,
12062306a36Sopenharmony_ci	"USBG", NULL, NULL, "MODS",
12162306a36Sopenharmony_ci	"CRDS", "M3GS", "WIMS", NULL,
12262306a36Sopenharmony_ci	NULL, NULL, "PBPS", "TPDS"
12362306a36Sopenharmony_ci};
12462306a36Sopenharmony_ci
12562306a36Sopenharmony_cistatic const struct key_entry eeepc_keymap[] = {
12662306a36Sopenharmony_ci	{ KE_KEY, 0x10, { KEY_WLAN } },
12762306a36Sopenharmony_ci	{ KE_KEY, 0x11, { KEY_WLAN } },
12862306a36Sopenharmony_ci	{ KE_KEY, 0x12, { KEY_PROG1 } },
12962306a36Sopenharmony_ci	{ KE_KEY, 0x13, { KEY_MUTE } },
13062306a36Sopenharmony_ci	{ KE_KEY, 0x14, { KEY_VOLUMEDOWN } },
13162306a36Sopenharmony_ci	{ KE_KEY, 0x15, { KEY_VOLUMEUP } },
13262306a36Sopenharmony_ci	{ KE_KEY, 0x16, { KEY_DISPLAY_OFF } },
13362306a36Sopenharmony_ci	{ KE_KEY, 0x1a, { KEY_COFFEE } },
13462306a36Sopenharmony_ci	{ KE_KEY, 0x1b, { KEY_ZOOM } },
13562306a36Sopenharmony_ci	{ KE_KEY, 0x1c, { KEY_PROG2 } },
13662306a36Sopenharmony_ci	{ KE_KEY, 0x1d, { KEY_PROG3 } },
13762306a36Sopenharmony_ci	{ KE_KEY, NOTIFY_BRN_MIN, { KEY_BRIGHTNESSDOWN } },
13862306a36Sopenharmony_ci	{ KE_KEY, NOTIFY_BRN_MAX, { KEY_BRIGHTNESSUP } },
13962306a36Sopenharmony_ci	{ KE_KEY, 0x30, { KEY_SWITCHVIDEOMODE } },
14062306a36Sopenharmony_ci	{ KE_KEY, 0x31, { KEY_SWITCHVIDEOMODE } },
14162306a36Sopenharmony_ci	{ KE_KEY, 0x32, { KEY_SWITCHVIDEOMODE } },
14262306a36Sopenharmony_ci	{ KE_KEY, 0x37, { KEY_F13 } }, /* Disable Touchpad */
14362306a36Sopenharmony_ci	{ KE_KEY, 0x38, { KEY_F14 } },
14462306a36Sopenharmony_ci	{ KE_IGNORE, 0x50, { KEY_RESERVED } }, /* AC plugged */
14562306a36Sopenharmony_ci	{ KE_IGNORE, 0x51, { KEY_RESERVED } }, /* AC unplugged */
14662306a36Sopenharmony_ci	{ KE_END, 0 },
14762306a36Sopenharmony_ci};
14862306a36Sopenharmony_ci
14962306a36Sopenharmony_ci/*
15062306a36Sopenharmony_ci * This is the main structure, we can use it to store useful information
15162306a36Sopenharmony_ci */
15262306a36Sopenharmony_cistruct eeepc_laptop {
15362306a36Sopenharmony_ci	acpi_handle handle;		/* the handle of the acpi device */
15462306a36Sopenharmony_ci	u32 cm_supported;		/* the control methods supported
15562306a36Sopenharmony_ci					   by this BIOS */
15662306a36Sopenharmony_ci	bool cpufv_disabled;
15762306a36Sopenharmony_ci	bool hotplug_disabled;
15862306a36Sopenharmony_ci	u16 event_count[128];		/* count for each event */
15962306a36Sopenharmony_ci
16062306a36Sopenharmony_ci	struct platform_device *platform_device;
16162306a36Sopenharmony_ci	struct acpi_device *device;		/* the device we are in */
16262306a36Sopenharmony_ci	struct backlight_device *backlight_device;
16362306a36Sopenharmony_ci
16462306a36Sopenharmony_ci	struct input_dev *inputdev;
16562306a36Sopenharmony_ci
16662306a36Sopenharmony_ci	struct rfkill *wlan_rfkill;
16762306a36Sopenharmony_ci	struct rfkill *bluetooth_rfkill;
16862306a36Sopenharmony_ci	struct rfkill *wwan3g_rfkill;
16962306a36Sopenharmony_ci	struct rfkill *wimax_rfkill;
17062306a36Sopenharmony_ci
17162306a36Sopenharmony_ci	struct hotplug_slot hotplug_slot;
17262306a36Sopenharmony_ci	struct mutex hotplug_lock;
17362306a36Sopenharmony_ci
17462306a36Sopenharmony_ci	struct led_classdev tpd_led;
17562306a36Sopenharmony_ci	int tpd_led_wk;
17662306a36Sopenharmony_ci	struct workqueue_struct *led_workqueue;
17762306a36Sopenharmony_ci	struct work_struct tpd_led_work;
17862306a36Sopenharmony_ci};
17962306a36Sopenharmony_ci
18062306a36Sopenharmony_ci/*
18162306a36Sopenharmony_ci * ACPI Helpers
18262306a36Sopenharmony_ci */
18362306a36Sopenharmony_cistatic int write_acpi_int(acpi_handle handle, const char *method, int val)
18462306a36Sopenharmony_ci{
18562306a36Sopenharmony_ci	acpi_status status;
18662306a36Sopenharmony_ci
18762306a36Sopenharmony_ci	status = acpi_execute_simple_method(handle, (char *)method, val);
18862306a36Sopenharmony_ci
18962306a36Sopenharmony_ci	return (status == AE_OK ? 0 : -1);
19062306a36Sopenharmony_ci}
19162306a36Sopenharmony_ci
19262306a36Sopenharmony_cistatic int read_acpi_int(acpi_handle handle, const char *method, int *val)
19362306a36Sopenharmony_ci{
19462306a36Sopenharmony_ci	acpi_status status;
19562306a36Sopenharmony_ci	unsigned long long result;
19662306a36Sopenharmony_ci
19762306a36Sopenharmony_ci	status = acpi_evaluate_integer(handle, (char *)method, NULL, &result);
19862306a36Sopenharmony_ci	if (ACPI_FAILURE(status)) {
19962306a36Sopenharmony_ci		*val = -1;
20062306a36Sopenharmony_ci		return -1;
20162306a36Sopenharmony_ci	} else {
20262306a36Sopenharmony_ci		*val = result;
20362306a36Sopenharmony_ci		return 0;
20462306a36Sopenharmony_ci	}
20562306a36Sopenharmony_ci}
20662306a36Sopenharmony_ci
20762306a36Sopenharmony_cistatic int set_acpi(struct eeepc_laptop *eeepc, int cm, int value)
20862306a36Sopenharmony_ci{
20962306a36Sopenharmony_ci	const char *method = cm_setv[cm];
21062306a36Sopenharmony_ci
21162306a36Sopenharmony_ci	if (method == NULL)
21262306a36Sopenharmony_ci		return -ENODEV;
21362306a36Sopenharmony_ci	if ((eeepc->cm_supported & (0x1 << cm)) == 0)
21462306a36Sopenharmony_ci		return -ENODEV;
21562306a36Sopenharmony_ci
21662306a36Sopenharmony_ci	if (write_acpi_int(eeepc->handle, method, value))
21762306a36Sopenharmony_ci		pr_warn("Error writing %s\n", method);
21862306a36Sopenharmony_ci	return 0;
21962306a36Sopenharmony_ci}
22062306a36Sopenharmony_ci
22162306a36Sopenharmony_cistatic int get_acpi(struct eeepc_laptop *eeepc, int cm)
22262306a36Sopenharmony_ci{
22362306a36Sopenharmony_ci	const char *method = cm_getv[cm];
22462306a36Sopenharmony_ci	int value;
22562306a36Sopenharmony_ci
22662306a36Sopenharmony_ci	if (method == NULL)
22762306a36Sopenharmony_ci		return -ENODEV;
22862306a36Sopenharmony_ci	if ((eeepc->cm_supported & (0x1 << cm)) == 0)
22962306a36Sopenharmony_ci		return -ENODEV;
23062306a36Sopenharmony_ci
23162306a36Sopenharmony_ci	if (read_acpi_int(eeepc->handle, method, &value))
23262306a36Sopenharmony_ci		pr_warn("Error reading %s\n", method);
23362306a36Sopenharmony_ci	return value;
23462306a36Sopenharmony_ci}
23562306a36Sopenharmony_ci
23662306a36Sopenharmony_cistatic int acpi_setter_handle(struct eeepc_laptop *eeepc, int cm,
23762306a36Sopenharmony_ci			      acpi_handle *handle)
23862306a36Sopenharmony_ci{
23962306a36Sopenharmony_ci	const char *method = cm_setv[cm];
24062306a36Sopenharmony_ci	acpi_status status;
24162306a36Sopenharmony_ci
24262306a36Sopenharmony_ci	if (method == NULL)
24362306a36Sopenharmony_ci		return -ENODEV;
24462306a36Sopenharmony_ci	if ((eeepc->cm_supported & (0x1 << cm)) == 0)
24562306a36Sopenharmony_ci		return -ENODEV;
24662306a36Sopenharmony_ci
24762306a36Sopenharmony_ci	status = acpi_get_handle(eeepc->handle, (char *)method,
24862306a36Sopenharmony_ci				 handle);
24962306a36Sopenharmony_ci	if (status != AE_OK) {
25062306a36Sopenharmony_ci		pr_warn("Error finding %s\n", method);
25162306a36Sopenharmony_ci		return -ENODEV;
25262306a36Sopenharmony_ci	}
25362306a36Sopenharmony_ci	return 0;
25462306a36Sopenharmony_ci}
25562306a36Sopenharmony_ci
25662306a36Sopenharmony_ci
25762306a36Sopenharmony_ci/*
25862306a36Sopenharmony_ci * Sys helpers
25962306a36Sopenharmony_ci */
26062306a36Sopenharmony_cistatic int parse_arg(const char *buf, int *val)
26162306a36Sopenharmony_ci{
26262306a36Sopenharmony_ci	if (sscanf(buf, "%i", val) != 1)
26362306a36Sopenharmony_ci		return -EINVAL;
26462306a36Sopenharmony_ci	return 0;
26562306a36Sopenharmony_ci}
26662306a36Sopenharmony_ci
26762306a36Sopenharmony_cistatic ssize_t store_sys_acpi(struct device *dev, int cm,
26862306a36Sopenharmony_ci			      const char *buf, size_t count)
26962306a36Sopenharmony_ci{
27062306a36Sopenharmony_ci	struct eeepc_laptop *eeepc = dev_get_drvdata(dev);
27162306a36Sopenharmony_ci	int rv, value;
27262306a36Sopenharmony_ci
27362306a36Sopenharmony_ci	rv = parse_arg(buf, &value);
27462306a36Sopenharmony_ci	if (rv < 0)
27562306a36Sopenharmony_ci		return rv;
27662306a36Sopenharmony_ci	rv = set_acpi(eeepc, cm, value);
27762306a36Sopenharmony_ci	if (rv < 0)
27862306a36Sopenharmony_ci		return -EIO;
27962306a36Sopenharmony_ci	return count;
28062306a36Sopenharmony_ci}
28162306a36Sopenharmony_ci
28262306a36Sopenharmony_cistatic ssize_t show_sys_acpi(struct device *dev, int cm, char *buf)
28362306a36Sopenharmony_ci{
28462306a36Sopenharmony_ci	struct eeepc_laptop *eeepc = dev_get_drvdata(dev);
28562306a36Sopenharmony_ci	int value = get_acpi(eeepc, cm);
28662306a36Sopenharmony_ci
28762306a36Sopenharmony_ci	if (value < 0)
28862306a36Sopenharmony_ci		return -EIO;
28962306a36Sopenharmony_ci	return sprintf(buf, "%d\n", value);
29062306a36Sopenharmony_ci}
29162306a36Sopenharmony_ci
29262306a36Sopenharmony_ci#define EEEPC_ACPI_SHOW_FUNC(_name, _cm)				\
29362306a36Sopenharmony_ci	static ssize_t _name##_show(struct device *dev,			\
29462306a36Sopenharmony_ci				    struct device_attribute *attr,	\
29562306a36Sopenharmony_ci				    char *buf)				\
29662306a36Sopenharmony_ci	{								\
29762306a36Sopenharmony_ci		return show_sys_acpi(dev, _cm, buf);			\
29862306a36Sopenharmony_ci	}
29962306a36Sopenharmony_ci
30062306a36Sopenharmony_ci#define EEEPC_ACPI_STORE_FUNC(_name, _cm)				\
30162306a36Sopenharmony_ci	static ssize_t _name##_store(struct device *dev,		\
30262306a36Sopenharmony_ci				     struct device_attribute *attr,	\
30362306a36Sopenharmony_ci				     const char *buf, size_t count)	\
30462306a36Sopenharmony_ci	{								\
30562306a36Sopenharmony_ci		return store_sys_acpi(dev, _cm, buf, count);		\
30662306a36Sopenharmony_ci	}
30762306a36Sopenharmony_ci
30862306a36Sopenharmony_ci#define EEEPC_CREATE_DEVICE_ATTR_RW(_name, _cm)				\
30962306a36Sopenharmony_ci	EEEPC_ACPI_SHOW_FUNC(_name, _cm)				\
31062306a36Sopenharmony_ci	EEEPC_ACPI_STORE_FUNC(_name, _cm)				\
31162306a36Sopenharmony_ci	static DEVICE_ATTR_RW(_name)
31262306a36Sopenharmony_ci
31362306a36Sopenharmony_ci#define EEEPC_CREATE_DEVICE_ATTR_WO(_name, _cm)				\
31462306a36Sopenharmony_ci	EEEPC_ACPI_STORE_FUNC(_name, _cm)				\
31562306a36Sopenharmony_ci	static DEVICE_ATTR_WO(_name)
31662306a36Sopenharmony_ci
31762306a36Sopenharmony_ciEEEPC_CREATE_DEVICE_ATTR_RW(camera, CM_ASL_CAMERA);
31862306a36Sopenharmony_ciEEEPC_CREATE_DEVICE_ATTR_RW(cardr, CM_ASL_CARDREADER);
31962306a36Sopenharmony_ciEEEPC_CREATE_DEVICE_ATTR_WO(disp, CM_ASL_DISPLAYSWITCH);
32062306a36Sopenharmony_ci
32162306a36Sopenharmony_cistruct eeepc_cpufv {
32262306a36Sopenharmony_ci	int num;
32362306a36Sopenharmony_ci	int cur;
32462306a36Sopenharmony_ci};
32562306a36Sopenharmony_ci
32662306a36Sopenharmony_cistatic int get_cpufv(struct eeepc_laptop *eeepc, struct eeepc_cpufv *c)
32762306a36Sopenharmony_ci{
32862306a36Sopenharmony_ci	c->cur = get_acpi(eeepc, CM_ASL_CPUFV);
32962306a36Sopenharmony_ci	if (c->cur < 0)
33062306a36Sopenharmony_ci		return -ENODEV;
33162306a36Sopenharmony_ci
33262306a36Sopenharmony_ci	c->num = (c->cur >> 8) & 0xff;
33362306a36Sopenharmony_ci	c->cur &= 0xff;
33462306a36Sopenharmony_ci	if (c->num == 0 || c->num > 12)
33562306a36Sopenharmony_ci		return -ENODEV;
33662306a36Sopenharmony_ci	return 0;
33762306a36Sopenharmony_ci}
33862306a36Sopenharmony_ci
33962306a36Sopenharmony_cistatic ssize_t available_cpufv_show(struct device *dev,
34062306a36Sopenharmony_ci				    struct device_attribute *attr,
34162306a36Sopenharmony_ci				    char *buf)
34262306a36Sopenharmony_ci{
34362306a36Sopenharmony_ci	struct eeepc_laptop *eeepc = dev_get_drvdata(dev);
34462306a36Sopenharmony_ci	struct eeepc_cpufv c;
34562306a36Sopenharmony_ci	int i;
34662306a36Sopenharmony_ci	ssize_t len = 0;
34762306a36Sopenharmony_ci
34862306a36Sopenharmony_ci	if (get_cpufv(eeepc, &c))
34962306a36Sopenharmony_ci		return -ENODEV;
35062306a36Sopenharmony_ci	for (i = 0; i < c.num; i++)
35162306a36Sopenharmony_ci		len += sprintf(buf + len, "%d ", i);
35262306a36Sopenharmony_ci	len += sprintf(buf + len, "\n");
35362306a36Sopenharmony_ci	return len;
35462306a36Sopenharmony_ci}
35562306a36Sopenharmony_ci
35662306a36Sopenharmony_cistatic ssize_t cpufv_show(struct device *dev,
35762306a36Sopenharmony_ci			  struct device_attribute *attr,
35862306a36Sopenharmony_ci			  char *buf)
35962306a36Sopenharmony_ci{
36062306a36Sopenharmony_ci	struct eeepc_laptop *eeepc = dev_get_drvdata(dev);
36162306a36Sopenharmony_ci	struct eeepc_cpufv c;
36262306a36Sopenharmony_ci
36362306a36Sopenharmony_ci	if (get_cpufv(eeepc, &c))
36462306a36Sopenharmony_ci		return -ENODEV;
36562306a36Sopenharmony_ci	return sprintf(buf, "%#x\n", (c.num << 8) | c.cur);
36662306a36Sopenharmony_ci}
36762306a36Sopenharmony_ci
36862306a36Sopenharmony_cistatic ssize_t cpufv_store(struct device *dev,
36962306a36Sopenharmony_ci			   struct device_attribute *attr,
37062306a36Sopenharmony_ci			   const char *buf, size_t count)
37162306a36Sopenharmony_ci{
37262306a36Sopenharmony_ci	struct eeepc_laptop *eeepc = dev_get_drvdata(dev);
37362306a36Sopenharmony_ci	struct eeepc_cpufv c;
37462306a36Sopenharmony_ci	int rv, value;
37562306a36Sopenharmony_ci
37662306a36Sopenharmony_ci	if (eeepc->cpufv_disabled)
37762306a36Sopenharmony_ci		return -EPERM;
37862306a36Sopenharmony_ci	if (get_cpufv(eeepc, &c))
37962306a36Sopenharmony_ci		return -ENODEV;
38062306a36Sopenharmony_ci	rv = parse_arg(buf, &value);
38162306a36Sopenharmony_ci	if (rv < 0)
38262306a36Sopenharmony_ci		return rv;
38362306a36Sopenharmony_ci	if (value < 0 || value >= c.num)
38462306a36Sopenharmony_ci		return -EINVAL;
38562306a36Sopenharmony_ci	rv = set_acpi(eeepc, CM_ASL_CPUFV, value);
38662306a36Sopenharmony_ci	if (rv)
38762306a36Sopenharmony_ci		return rv;
38862306a36Sopenharmony_ci	return count;
38962306a36Sopenharmony_ci}
39062306a36Sopenharmony_ci
39162306a36Sopenharmony_cistatic ssize_t cpufv_disabled_show(struct device *dev,
39262306a36Sopenharmony_ci			  struct device_attribute *attr,
39362306a36Sopenharmony_ci			  char *buf)
39462306a36Sopenharmony_ci{
39562306a36Sopenharmony_ci	struct eeepc_laptop *eeepc = dev_get_drvdata(dev);
39662306a36Sopenharmony_ci
39762306a36Sopenharmony_ci	return sprintf(buf, "%d\n", eeepc->cpufv_disabled);
39862306a36Sopenharmony_ci}
39962306a36Sopenharmony_ci
40062306a36Sopenharmony_cistatic ssize_t cpufv_disabled_store(struct device *dev,
40162306a36Sopenharmony_ci			   struct device_attribute *attr,
40262306a36Sopenharmony_ci			   const char *buf, size_t count)
40362306a36Sopenharmony_ci{
40462306a36Sopenharmony_ci	struct eeepc_laptop *eeepc = dev_get_drvdata(dev);
40562306a36Sopenharmony_ci	int rv, value;
40662306a36Sopenharmony_ci
40762306a36Sopenharmony_ci	rv = parse_arg(buf, &value);
40862306a36Sopenharmony_ci	if (rv < 0)
40962306a36Sopenharmony_ci		return rv;
41062306a36Sopenharmony_ci
41162306a36Sopenharmony_ci	switch (value) {
41262306a36Sopenharmony_ci	case 0:
41362306a36Sopenharmony_ci		if (eeepc->cpufv_disabled)
41462306a36Sopenharmony_ci			pr_warn("cpufv enabled (not officially supported on this model)\n");
41562306a36Sopenharmony_ci		eeepc->cpufv_disabled = false;
41662306a36Sopenharmony_ci		return count;
41762306a36Sopenharmony_ci	case 1:
41862306a36Sopenharmony_ci		return -EPERM;
41962306a36Sopenharmony_ci	default:
42062306a36Sopenharmony_ci		return -EINVAL;
42162306a36Sopenharmony_ci	}
42262306a36Sopenharmony_ci}
42362306a36Sopenharmony_ci
42462306a36Sopenharmony_ci
42562306a36Sopenharmony_cistatic DEVICE_ATTR_RW(cpufv);
42662306a36Sopenharmony_cistatic DEVICE_ATTR_RO(available_cpufv);
42762306a36Sopenharmony_cistatic DEVICE_ATTR_RW(cpufv_disabled);
42862306a36Sopenharmony_ci
42962306a36Sopenharmony_cistatic struct attribute *platform_attributes[] = {
43062306a36Sopenharmony_ci	&dev_attr_camera.attr,
43162306a36Sopenharmony_ci	&dev_attr_cardr.attr,
43262306a36Sopenharmony_ci	&dev_attr_disp.attr,
43362306a36Sopenharmony_ci	&dev_attr_cpufv.attr,
43462306a36Sopenharmony_ci	&dev_attr_available_cpufv.attr,
43562306a36Sopenharmony_ci	&dev_attr_cpufv_disabled.attr,
43662306a36Sopenharmony_ci	NULL
43762306a36Sopenharmony_ci};
43862306a36Sopenharmony_ci
43962306a36Sopenharmony_cistatic const struct attribute_group platform_attribute_group = {
44062306a36Sopenharmony_ci	.attrs = platform_attributes
44162306a36Sopenharmony_ci};
44262306a36Sopenharmony_ci
44362306a36Sopenharmony_cistatic int eeepc_platform_init(struct eeepc_laptop *eeepc)
44462306a36Sopenharmony_ci{
44562306a36Sopenharmony_ci	int result;
44662306a36Sopenharmony_ci
44762306a36Sopenharmony_ci	eeepc->platform_device = platform_device_alloc(EEEPC_LAPTOP_FILE, PLATFORM_DEVID_NONE);
44862306a36Sopenharmony_ci	if (!eeepc->platform_device)
44962306a36Sopenharmony_ci		return -ENOMEM;
45062306a36Sopenharmony_ci	platform_set_drvdata(eeepc->platform_device, eeepc);
45162306a36Sopenharmony_ci
45262306a36Sopenharmony_ci	result = platform_device_add(eeepc->platform_device);
45362306a36Sopenharmony_ci	if (result)
45462306a36Sopenharmony_ci		goto fail_platform_device;
45562306a36Sopenharmony_ci
45662306a36Sopenharmony_ci	result = sysfs_create_group(&eeepc->platform_device->dev.kobj,
45762306a36Sopenharmony_ci				    &platform_attribute_group);
45862306a36Sopenharmony_ci	if (result)
45962306a36Sopenharmony_ci		goto fail_sysfs;
46062306a36Sopenharmony_ci	return 0;
46162306a36Sopenharmony_ci
46262306a36Sopenharmony_cifail_sysfs:
46362306a36Sopenharmony_ci	platform_device_del(eeepc->platform_device);
46462306a36Sopenharmony_cifail_platform_device:
46562306a36Sopenharmony_ci	platform_device_put(eeepc->platform_device);
46662306a36Sopenharmony_ci	return result;
46762306a36Sopenharmony_ci}
46862306a36Sopenharmony_ci
46962306a36Sopenharmony_cistatic void eeepc_platform_exit(struct eeepc_laptop *eeepc)
47062306a36Sopenharmony_ci{
47162306a36Sopenharmony_ci	sysfs_remove_group(&eeepc->platform_device->dev.kobj,
47262306a36Sopenharmony_ci			   &platform_attribute_group);
47362306a36Sopenharmony_ci	platform_device_unregister(eeepc->platform_device);
47462306a36Sopenharmony_ci}
47562306a36Sopenharmony_ci
47662306a36Sopenharmony_ci/*
47762306a36Sopenharmony_ci * LEDs
47862306a36Sopenharmony_ci */
47962306a36Sopenharmony_ci/*
48062306a36Sopenharmony_ci * These functions actually update the LED's, and are called from a
48162306a36Sopenharmony_ci * workqueue. By doing this as separate work rather than when the LED
48262306a36Sopenharmony_ci * subsystem asks, we avoid messing with the Asus ACPI stuff during a
48362306a36Sopenharmony_ci * potentially bad time, such as a timer interrupt.
48462306a36Sopenharmony_ci */
48562306a36Sopenharmony_cistatic void tpd_led_update(struct work_struct *work)
48662306a36Sopenharmony_ci{
48762306a36Sopenharmony_ci	struct eeepc_laptop *eeepc;
48862306a36Sopenharmony_ci
48962306a36Sopenharmony_ci	eeepc = container_of(work, struct eeepc_laptop, tpd_led_work);
49062306a36Sopenharmony_ci
49162306a36Sopenharmony_ci	set_acpi(eeepc, CM_ASL_TPD, eeepc->tpd_led_wk);
49262306a36Sopenharmony_ci}
49362306a36Sopenharmony_ci
49462306a36Sopenharmony_cistatic void tpd_led_set(struct led_classdev *led_cdev,
49562306a36Sopenharmony_ci			enum led_brightness value)
49662306a36Sopenharmony_ci{
49762306a36Sopenharmony_ci	struct eeepc_laptop *eeepc;
49862306a36Sopenharmony_ci
49962306a36Sopenharmony_ci	eeepc = container_of(led_cdev, struct eeepc_laptop, tpd_led);
50062306a36Sopenharmony_ci
50162306a36Sopenharmony_ci	eeepc->tpd_led_wk = (value > 0) ? 1 : 0;
50262306a36Sopenharmony_ci	queue_work(eeepc->led_workqueue, &eeepc->tpd_led_work);
50362306a36Sopenharmony_ci}
50462306a36Sopenharmony_ci
50562306a36Sopenharmony_cistatic enum led_brightness tpd_led_get(struct led_classdev *led_cdev)
50662306a36Sopenharmony_ci{
50762306a36Sopenharmony_ci	struct eeepc_laptop *eeepc;
50862306a36Sopenharmony_ci
50962306a36Sopenharmony_ci	eeepc = container_of(led_cdev, struct eeepc_laptop, tpd_led);
51062306a36Sopenharmony_ci
51162306a36Sopenharmony_ci	return get_acpi(eeepc, CM_ASL_TPD);
51262306a36Sopenharmony_ci}
51362306a36Sopenharmony_ci
51462306a36Sopenharmony_cistatic int eeepc_led_init(struct eeepc_laptop *eeepc)
51562306a36Sopenharmony_ci{
51662306a36Sopenharmony_ci	int rv;
51762306a36Sopenharmony_ci
51862306a36Sopenharmony_ci	if (get_acpi(eeepc, CM_ASL_TPD) == -ENODEV)
51962306a36Sopenharmony_ci		return 0;
52062306a36Sopenharmony_ci
52162306a36Sopenharmony_ci	eeepc->led_workqueue = create_singlethread_workqueue("led_workqueue");
52262306a36Sopenharmony_ci	if (!eeepc->led_workqueue)
52362306a36Sopenharmony_ci		return -ENOMEM;
52462306a36Sopenharmony_ci	INIT_WORK(&eeepc->tpd_led_work, tpd_led_update);
52562306a36Sopenharmony_ci
52662306a36Sopenharmony_ci	eeepc->tpd_led.name = "eeepc::touchpad";
52762306a36Sopenharmony_ci	eeepc->tpd_led.brightness_set = tpd_led_set;
52862306a36Sopenharmony_ci	if (get_acpi(eeepc, CM_ASL_TPD) >= 0) /* if method is available */
52962306a36Sopenharmony_ci		eeepc->tpd_led.brightness_get = tpd_led_get;
53062306a36Sopenharmony_ci	eeepc->tpd_led.max_brightness = 1;
53162306a36Sopenharmony_ci
53262306a36Sopenharmony_ci	rv = led_classdev_register(&eeepc->platform_device->dev,
53362306a36Sopenharmony_ci				   &eeepc->tpd_led);
53462306a36Sopenharmony_ci	if (rv) {
53562306a36Sopenharmony_ci		destroy_workqueue(eeepc->led_workqueue);
53662306a36Sopenharmony_ci		return rv;
53762306a36Sopenharmony_ci	}
53862306a36Sopenharmony_ci
53962306a36Sopenharmony_ci	return 0;
54062306a36Sopenharmony_ci}
54162306a36Sopenharmony_ci
54262306a36Sopenharmony_cistatic void eeepc_led_exit(struct eeepc_laptop *eeepc)
54362306a36Sopenharmony_ci{
54462306a36Sopenharmony_ci	led_classdev_unregister(&eeepc->tpd_led);
54562306a36Sopenharmony_ci	if (eeepc->led_workqueue)
54662306a36Sopenharmony_ci		destroy_workqueue(eeepc->led_workqueue);
54762306a36Sopenharmony_ci}
54862306a36Sopenharmony_ci
54962306a36Sopenharmony_ci/*
55062306a36Sopenharmony_ci * PCI hotplug (for wlan rfkill)
55162306a36Sopenharmony_ci */
55262306a36Sopenharmony_cistatic bool eeepc_wlan_rfkill_blocked(struct eeepc_laptop *eeepc)
55362306a36Sopenharmony_ci{
55462306a36Sopenharmony_ci	if (get_acpi(eeepc, CM_ASL_WLAN) == 1)
55562306a36Sopenharmony_ci		return false;
55662306a36Sopenharmony_ci	return true;
55762306a36Sopenharmony_ci}
55862306a36Sopenharmony_ci
55962306a36Sopenharmony_cistatic void eeepc_rfkill_hotplug(struct eeepc_laptop *eeepc, acpi_handle handle)
56062306a36Sopenharmony_ci{
56162306a36Sopenharmony_ci	struct pci_dev *port;
56262306a36Sopenharmony_ci	struct pci_dev *dev;
56362306a36Sopenharmony_ci	struct pci_bus *bus;
56462306a36Sopenharmony_ci	bool blocked = eeepc_wlan_rfkill_blocked(eeepc);
56562306a36Sopenharmony_ci	bool absent;
56662306a36Sopenharmony_ci	u32 l;
56762306a36Sopenharmony_ci
56862306a36Sopenharmony_ci	if (eeepc->wlan_rfkill)
56962306a36Sopenharmony_ci		rfkill_set_sw_state(eeepc->wlan_rfkill, blocked);
57062306a36Sopenharmony_ci
57162306a36Sopenharmony_ci	mutex_lock(&eeepc->hotplug_lock);
57262306a36Sopenharmony_ci	pci_lock_rescan_remove();
57362306a36Sopenharmony_ci
57462306a36Sopenharmony_ci	if (!eeepc->hotplug_slot.ops)
57562306a36Sopenharmony_ci		goto out_unlock;
57662306a36Sopenharmony_ci
57762306a36Sopenharmony_ci	port = acpi_get_pci_dev(handle);
57862306a36Sopenharmony_ci	if (!port) {
57962306a36Sopenharmony_ci		pr_warn("Unable to find port\n");
58062306a36Sopenharmony_ci		goto out_unlock;
58162306a36Sopenharmony_ci	}
58262306a36Sopenharmony_ci
58362306a36Sopenharmony_ci	bus = port->subordinate;
58462306a36Sopenharmony_ci
58562306a36Sopenharmony_ci	if (!bus) {
58662306a36Sopenharmony_ci		pr_warn("Unable to find PCI bus 1?\n");
58762306a36Sopenharmony_ci		goto out_put_dev;
58862306a36Sopenharmony_ci	}
58962306a36Sopenharmony_ci
59062306a36Sopenharmony_ci	if (pci_bus_read_config_dword(bus, 0, PCI_VENDOR_ID, &l)) {
59162306a36Sopenharmony_ci		pr_err("Unable to read PCI config space?\n");
59262306a36Sopenharmony_ci		goto out_put_dev;
59362306a36Sopenharmony_ci	}
59462306a36Sopenharmony_ci
59562306a36Sopenharmony_ci	absent = (l == 0xffffffff);
59662306a36Sopenharmony_ci
59762306a36Sopenharmony_ci	if (blocked != absent) {
59862306a36Sopenharmony_ci		pr_warn("BIOS says wireless lan is %s, but the pci device is %s\n",
59962306a36Sopenharmony_ci			blocked ? "blocked" : "unblocked",
60062306a36Sopenharmony_ci			absent ? "absent" : "present");
60162306a36Sopenharmony_ci		pr_warn("skipped wireless hotplug as probably inappropriate for this model\n");
60262306a36Sopenharmony_ci		goto out_put_dev;
60362306a36Sopenharmony_ci	}
60462306a36Sopenharmony_ci
60562306a36Sopenharmony_ci	if (!blocked) {
60662306a36Sopenharmony_ci		dev = pci_get_slot(bus, 0);
60762306a36Sopenharmony_ci		if (dev) {
60862306a36Sopenharmony_ci			/* Device already present */
60962306a36Sopenharmony_ci			pci_dev_put(dev);
61062306a36Sopenharmony_ci			goto out_put_dev;
61162306a36Sopenharmony_ci		}
61262306a36Sopenharmony_ci		dev = pci_scan_single_device(bus, 0);
61362306a36Sopenharmony_ci		if (dev) {
61462306a36Sopenharmony_ci			pci_bus_assign_resources(bus);
61562306a36Sopenharmony_ci			pci_bus_add_device(dev);
61662306a36Sopenharmony_ci		}
61762306a36Sopenharmony_ci	} else {
61862306a36Sopenharmony_ci		dev = pci_get_slot(bus, 0);
61962306a36Sopenharmony_ci		if (dev) {
62062306a36Sopenharmony_ci			pci_stop_and_remove_bus_device(dev);
62162306a36Sopenharmony_ci			pci_dev_put(dev);
62262306a36Sopenharmony_ci		}
62362306a36Sopenharmony_ci	}
62462306a36Sopenharmony_ciout_put_dev:
62562306a36Sopenharmony_ci	pci_dev_put(port);
62662306a36Sopenharmony_ci
62762306a36Sopenharmony_ciout_unlock:
62862306a36Sopenharmony_ci	pci_unlock_rescan_remove();
62962306a36Sopenharmony_ci	mutex_unlock(&eeepc->hotplug_lock);
63062306a36Sopenharmony_ci}
63162306a36Sopenharmony_ci
63262306a36Sopenharmony_cistatic void eeepc_rfkill_hotplug_update(struct eeepc_laptop *eeepc, char *node)
63362306a36Sopenharmony_ci{
63462306a36Sopenharmony_ci	acpi_status status = AE_OK;
63562306a36Sopenharmony_ci	acpi_handle handle;
63662306a36Sopenharmony_ci
63762306a36Sopenharmony_ci	status = acpi_get_handle(NULL, node, &handle);
63862306a36Sopenharmony_ci
63962306a36Sopenharmony_ci	if (ACPI_SUCCESS(status))
64062306a36Sopenharmony_ci		eeepc_rfkill_hotplug(eeepc, handle);
64162306a36Sopenharmony_ci}
64262306a36Sopenharmony_ci
64362306a36Sopenharmony_cistatic void eeepc_rfkill_notify(acpi_handle handle, u32 event, void *data)
64462306a36Sopenharmony_ci{
64562306a36Sopenharmony_ci	struct eeepc_laptop *eeepc = data;
64662306a36Sopenharmony_ci
64762306a36Sopenharmony_ci	if (event != ACPI_NOTIFY_BUS_CHECK)
64862306a36Sopenharmony_ci		return;
64962306a36Sopenharmony_ci
65062306a36Sopenharmony_ci	eeepc_rfkill_hotplug(eeepc, handle);
65162306a36Sopenharmony_ci}
65262306a36Sopenharmony_ci
65362306a36Sopenharmony_cistatic int eeepc_register_rfkill_notifier(struct eeepc_laptop *eeepc,
65462306a36Sopenharmony_ci					  char *node)
65562306a36Sopenharmony_ci{
65662306a36Sopenharmony_ci	acpi_status status;
65762306a36Sopenharmony_ci	acpi_handle handle;
65862306a36Sopenharmony_ci
65962306a36Sopenharmony_ci	status = acpi_get_handle(NULL, node, &handle);
66062306a36Sopenharmony_ci
66162306a36Sopenharmony_ci	if (ACPI_FAILURE(status))
66262306a36Sopenharmony_ci		return -ENODEV;
66362306a36Sopenharmony_ci
66462306a36Sopenharmony_ci	status = acpi_install_notify_handler(handle,
66562306a36Sopenharmony_ci					     ACPI_SYSTEM_NOTIFY,
66662306a36Sopenharmony_ci					     eeepc_rfkill_notify,
66762306a36Sopenharmony_ci					     eeepc);
66862306a36Sopenharmony_ci	if (ACPI_FAILURE(status))
66962306a36Sopenharmony_ci		pr_warn("Failed to register notify on %s\n", node);
67062306a36Sopenharmony_ci
67162306a36Sopenharmony_ci	/*
67262306a36Sopenharmony_ci	 * Refresh pci hotplug in case the rfkill state was
67362306a36Sopenharmony_ci	 * changed during setup.
67462306a36Sopenharmony_ci	 */
67562306a36Sopenharmony_ci	eeepc_rfkill_hotplug(eeepc, handle);
67662306a36Sopenharmony_ci	return 0;
67762306a36Sopenharmony_ci}
67862306a36Sopenharmony_ci
67962306a36Sopenharmony_cistatic void eeepc_unregister_rfkill_notifier(struct eeepc_laptop *eeepc,
68062306a36Sopenharmony_ci					     char *node)
68162306a36Sopenharmony_ci{
68262306a36Sopenharmony_ci	acpi_status status = AE_OK;
68362306a36Sopenharmony_ci	acpi_handle handle;
68462306a36Sopenharmony_ci
68562306a36Sopenharmony_ci	status = acpi_get_handle(NULL, node, &handle);
68662306a36Sopenharmony_ci
68762306a36Sopenharmony_ci	if (ACPI_FAILURE(status))
68862306a36Sopenharmony_ci		return;
68962306a36Sopenharmony_ci
69062306a36Sopenharmony_ci	status = acpi_remove_notify_handler(handle,
69162306a36Sopenharmony_ci					     ACPI_SYSTEM_NOTIFY,
69262306a36Sopenharmony_ci					     eeepc_rfkill_notify);
69362306a36Sopenharmony_ci	if (ACPI_FAILURE(status))
69462306a36Sopenharmony_ci		pr_err("Error removing rfkill notify handler %s\n",
69562306a36Sopenharmony_ci			node);
69662306a36Sopenharmony_ci		/*
69762306a36Sopenharmony_ci		 * Refresh pci hotplug in case the rfkill
69862306a36Sopenharmony_ci		 * state was changed after
69962306a36Sopenharmony_ci		 * eeepc_unregister_rfkill_notifier()
70062306a36Sopenharmony_ci		 */
70162306a36Sopenharmony_ci	eeepc_rfkill_hotplug(eeepc, handle);
70262306a36Sopenharmony_ci}
70362306a36Sopenharmony_ci
70462306a36Sopenharmony_cistatic int eeepc_get_adapter_status(struct hotplug_slot *hotplug_slot,
70562306a36Sopenharmony_ci				    u8 *value)
70662306a36Sopenharmony_ci{
70762306a36Sopenharmony_ci	struct eeepc_laptop *eeepc;
70862306a36Sopenharmony_ci	int val;
70962306a36Sopenharmony_ci
71062306a36Sopenharmony_ci	eeepc = container_of(hotplug_slot, struct eeepc_laptop, hotplug_slot);
71162306a36Sopenharmony_ci	val = get_acpi(eeepc, CM_ASL_WLAN);
71262306a36Sopenharmony_ci
71362306a36Sopenharmony_ci	if (val == 1 || val == 0)
71462306a36Sopenharmony_ci		*value = val;
71562306a36Sopenharmony_ci	else
71662306a36Sopenharmony_ci		return -EINVAL;
71762306a36Sopenharmony_ci
71862306a36Sopenharmony_ci	return 0;
71962306a36Sopenharmony_ci}
72062306a36Sopenharmony_ci
72162306a36Sopenharmony_cistatic const struct hotplug_slot_ops eeepc_hotplug_slot_ops = {
72262306a36Sopenharmony_ci	.get_adapter_status = eeepc_get_adapter_status,
72362306a36Sopenharmony_ci	.get_power_status = eeepc_get_adapter_status,
72462306a36Sopenharmony_ci};
72562306a36Sopenharmony_ci
72662306a36Sopenharmony_cistatic int eeepc_setup_pci_hotplug(struct eeepc_laptop *eeepc)
72762306a36Sopenharmony_ci{
72862306a36Sopenharmony_ci	int ret = -ENOMEM;
72962306a36Sopenharmony_ci	struct pci_bus *bus = pci_find_bus(0, 1);
73062306a36Sopenharmony_ci
73162306a36Sopenharmony_ci	if (!bus) {
73262306a36Sopenharmony_ci		pr_err("Unable to find wifi PCI bus\n");
73362306a36Sopenharmony_ci		return -ENODEV;
73462306a36Sopenharmony_ci	}
73562306a36Sopenharmony_ci
73662306a36Sopenharmony_ci	eeepc->hotplug_slot.ops = &eeepc_hotplug_slot_ops;
73762306a36Sopenharmony_ci
73862306a36Sopenharmony_ci	ret = pci_hp_register(&eeepc->hotplug_slot, bus, 0, "eeepc-wifi");
73962306a36Sopenharmony_ci	if (ret) {
74062306a36Sopenharmony_ci		pr_err("Unable to register hotplug slot - %d\n", ret);
74162306a36Sopenharmony_ci		goto error_register;
74262306a36Sopenharmony_ci	}
74362306a36Sopenharmony_ci
74462306a36Sopenharmony_ci	return 0;
74562306a36Sopenharmony_ci
74662306a36Sopenharmony_cierror_register:
74762306a36Sopenharmony_ci	eeepc->hotplug_slot.ops = NULL;
74862306a36Sopenharmony_ci	return ret;
74962306a36Sopenharmony_ci}
75062306a36Sopenharmony_ci
75162306a36Sopenharmony_ci/*
75262306a36Sopenharmony_ci * Rfkill devices
75362306a36Sopenharmony_ci */
75462306a36Sopenharmony_cistatic int eeepc_rfkill_set(void *data, bool blocked)
75562306a36Sopenharmony_ci{
75662306a36Sopenharmony_ci	acpi_handle handle = data;
75762306a36Sopenharmony_ci
75862306a36Sopenharmony_ci	return write_acpi_int(handle, NULL, !blocked);
75962306a36Sopenharmony_ci}
76062306a36Sopenharmony_ci
76162306a36Sopenharmony_cistatic const struct rfkill_ops eeepc_rfkill_ops = {
76262306a36Sopenharmony_ci	.set_block = eeepc_rfkill_set,
76362306a36Sopenharmony_ci};
76462306a36Sopenharmony_ci
76562306a36Sopenharmony_cistatic int eeepc_new_rfkill(struct eeepc_laptop *eeepc,
76662306a36Sopenharmony_ci			    struct rfkill **rfkill,
76762306a36Sopenharmony_ci			    const char *name,
76862306a36Sopenharmony_ci			    enum rfkill_type type, int cm)
76962306a36Sopenharmony_ci{
77062306a36Sopenharmony_ci	acpi_handle handle;
77162306a36Sopenharmony_ci	int result;
77262306a36Sopenharmony_ci
77362306a36Sopenharmony_ci	result = acpi_setter_handle(eeepc, cm, &handle);
77462306a36Sopenharmony_ci	if (result < 0)
77562306a36Sopenharmony_ci		return result;
77662306a36Sopenharmony_ci
77762306a36Sopenharmony_ci	*rfkill = rfkill_alloc(name, &eeepc->platform_device->dev, type,
77862306a36Sopenharmony_ci			       &eeepc_rfkill_ops, handle);
77962306a36Sopenharmony_ci
78062306a36Sopenharmony_ci	if (!*rfkill)
78162306a36Sopenharmony_ci		return -EINVAL;
78262306a36Sopenharmony_ci
78362306a36Sopenharmony_ci	rfkill_init_sw_state(*rfkill, get_acpi(eeepc, cm) != 1);
78462306a36Sopenharmony_ci	result = rfkill_register(*rfkill);
78562306a36Sopenharmony_ci	if (result) {
78662306a36Sopenharmony_ci		rfkill_destroy(*rfkill);
78762306a36Sopenharmony_ci		*rfkill = NULL;
78862306a36Sopenharmony_ci		return result;
78962306a36Sopenharmony_ci	}
79062306a36Sopenharmony_ci	return 0;
79162306a36Sopenharmony_ci}
79262306a36Sopenharmony_ci
79362306a36Sopenharmony_cistatic char EEEPC_RFKILL_NODE_1[] = "\\_SB.PCI0.P0P5";
79462306a36Sopenharmony_cistatic char EEEPC_RFKILL_NODE_2[] = "\\_SB.PCI0.P0P6";
79562306a36Sopenharmony_cistatic char EEEPC_RFKILL_NODE_3[] = "\\_SB.PCI0.P0P7";
79662306a36Sopenharmony_ci
79762306a36Sopenharmony_cistatic void eeepc_rfkill_exit(struct eeepc_laptop *eeepc)
79862306a36Sopenharmony_ci{
79962306a36Sopenharmony_ci	eeepc_unregister_rfkill_notifier(eeepc, EEEPC_RFKILL_NODE_1);
80062306a36Sopenharmony_ci	eeepc_unregister_rfkill_notifier(eeepc, EEEPC_RFKILL_NODE_2);
80162306a36Sopenharmony_ci	eeepc_unregister_rfkill_notifier(eeepc, EEEPC_RFKILL_NODE_3);
80262306a36Sopenharmony_ci	if (eeepc->wlan_rfkill) {
80362306a36Sopenharmony_ci		rfkill_unregister(eeepc->wlan_rfkill);
80462306a36Sopenharmony_ci		rfkill_destroy(eeepc->wlan_rfkill);
80562306a36Sopenharmony_ci		eeepc->wlan_rfkill = NULL;
80662306a36Sopenharmony_ci	}
80762306a36Sopenharmony_ci
80862306a36Sopenharmony_ci	if (eeepc->hotplug_slot.ops)
80962306a36Sopenharmony_ci		pci_hp_deregister(&eeepc->hotplug_slot);
81062306a36Sopenharmony_ci
81162306a36Sopenharmony_ci	if (eeepc->bluetooth_rfkill) {
81262306a36Sopenharmony_ci		rfkill_unregister(eeepc->bluetooth_rfkill);
81362306a36Sopenharmony_ci		rfkill_destroy(eeepc->bluetooth_rfkill);
81462306a36Sopenharmony_ci		eeepc->bluetooth_rfkill = NULL;
81562306a36Sopenharmony_ci	}
81662306a36Sopenharmony_ci	if (eeepc->wwan3g_rfkill) {
81762306a36Sopenharmony_ci		rfkill_unregister(eeepc->wwan3g_rfkill);
81862306a36Sopenharmony_ci		rfkill_destroy(eeepc->wwan3g_rfkill);
81962306a36Sopenharmony_ci		eeepc->wwan3g_rfkill = NULL;
82062306a36Sopenharmony_ci	}
82162306a36Sopenharmony_ci	if (eeepc->wimax_rfkill) {
82262306a36Sopenharmony_ci		rfkill_unregister(eeepc->wimax_rfkill);
82362306a36Sopenharmony_ci		rfkill_destroy(eeepc->wimax_rfkill);
82462306a36Sopenharmony_ci		eeepc->wimax_rfkill = NULL;
82562306a36Sopenharmony_ci	}
82662306a36Sopenharmony_ci}
82762306a36Sopenharmony_ci
82862306a36Sopenharmony_cistatic int eeepc_rfkill_init(struct eeepc_laptop *eeepc)
82962306a36Sopenharmony_ci{
83062306a36Sopenharmony_ci	int result = 0;
83162306a36Sopenharmony_ci
83262306a36Sopenharmony_ci	mutex_init(&eeepc->hotplug_lock);
83362306a36Sopenharmony_ci
83462306a36Sopenharmony_ci	result = eeepc_new_rfkill(eeepc, &eeepc->wlan_rfkill,
83562306a36Sopenharmony_ci				  "eeepc-wlan", RFKILL_TYPE_WLAN,
83662306a36Sopenharmony_ci				  CM_ASL_WLAN);
83762306a36Sopenharmony_ci
83862306a36Sopenharmony_ci	if (result && result != -ENODEV)
83962306a36Sopenharmony_ci		goto exit;
84062306a36Sopenharmony_ci
84162306a36Sopenharmony_ci	result = eeepc_new_rfkill(eeepc, &eeepc->bluetooth_rfkill,
84262306a36Sopenharmony_ci				  "eeepc-bluetooth", RFKILL_TYPE_BLUETOOTH,
84362306a36Sopenharmony_ci				  CM_ASL_BLUETOOTH);
84462306a36Sopenharmony_ci
84562306a36Sopenharmony_ci	if (result && result != -ENODEV)
84662306a36Sopenharmony_ci		goto exit;
84762306a36Sopenharmony_ci
84862306a36Sopenharmony_ci	result = eeepc_new_rfkill(eeepc, &eeepc->wwan3g_rfkill,
84962306a36Sopenharmony_ci				  "eeepc-wwan3g", RFKILL_TYPE_WWAN,
85062306a36Sopenharmony_ci				  CM_ASL_3G);
85162306a36Sopenharmony_ci
85262306a36Sopenharmony_ci	if (result && result != -ENODEV)
85362306a36Sopenharmony_ci		goto exit;
85462306a36Sopenharmony_ci
85562306a36Sopenharmony_ci	result = eeepc_new_rfkill(eeepc, &eeepc->wimax_rfkill,
85662306a36Sopenharmony_ci				  "eeepc-wimax", RFKILL_TYPE_WIMAX,
85762306a36Sopenharmony_ci				  CM_ASL_WIMAX);
85862306a36Sopenharmony_ci
85962306a36Sopenharmony_ci	if (result && result != -ENODEV)
86062306a36Sopenharmony_ci		goto exit;
86162306a36Sopenharmony_ci
86262306a36Sopenharmony_ci	if (eeepc->hotplug_disabled)
86362306a36Sopenharmony_ci		return 0;
86462306a36Sopenharmony_ci
86562306a36Sopenharmony_ci	result = eeepc_setup_pci_hotplug(eeepc);
86662306a36Sopenharmony_ci	/*
86762306a36Sopenharmony_ci	 * If we get -EBUSY then something else is handling the PCI hotplug -
86862306a36Sopenharmony_ci	 * don't fail in this case
86962306a36Sopenharmony_ci	 */
87062306a36Sopenharmony_ci	if (result == -EBUSY)
87162306a36Sopenharmony_ci		result = 0;
87262306a36Sopenharmony_ci
87362306a36Sopenharmony_ci	eeepc_register_rfkill_notifier(eeepc, EEEPC_RFKILL_NODE_1);
87462306a36Sopenharmony_ci	eeepc_register_rfkill_notifier(eeepc, EEEPC_RFKILL_NODE_2);
87562306a36Sopenharmony_ci	eeepc_register_rfkill_notifier(eeepc, EEEPC_RFKILL_NODE_3);
87662306a36Sopenharmony_ci
87762306a36Sopenharmony_ciexit:
87862306a36Sopenharmony_ci	if (result && result != -ENODEV)
87962306a36Sopenharmony_ci		eeepc_rfkill_exit(eeepc);
88062306a36Sopenharmony_ci	return result;
88162306a36Sopenharmony_ci}
88262306a36Sopenharmony_ci
88362306a36Sopenharmony_ci/*
88462306a36Sopenharmony_ci * Platform driver - hibernate/resume callbacks
88562306a36Sopenharmony_ci */
88662306a36Sopenharmony_cistatic int eeepc_hotk_thaw(struct device *device)
88762306a36Sopenharmony_ci{
88862306a36Sopenharmony_ci	struct eeepc_laptop *eeepc = dev_get_drvdata(device);
88962306a36Sopenharmony_ci
89062306a36Sopenharmony_ci	if (eeepc->wlan_rfkill) {
89162306a36Sopenharmony_ci		int wlan;
89262306a36Sopenharmony_ci
89362306a36Sopenharmony_ci		/*
89462306a36Sopenharmony_ci		 * Work around bios bug - acpi _PTS turns off the wireless led
89562306a36Sopenharmony_ci		 * during suspend.  Normally it restores it on resume, but
89662306a36Sopenharmony_ci		 * we should kick it ourselves in case hibernation is aborted.
89762306a36Sopenharmony_ci		 */
89862306a36Sopenharmony_ci		wlan = get_acpi(eeepc, CM_ASL_WLAN);
89962306a36Sopenharmony_ci		if (wlan >= 0)
90062306a36Sopenharmony_ci			set_acpi(eeepc, CM_ASL_WLAN, wlan);
90162306a36Sopenharmony_ci	}
90262306a36Sopenharmony_ci
90362306a36Sopenharmony_ci	return 0;
90462306a36Sopenharmony_ci}
90562306a36Sopenharmony_ci
90662306a36Sopenharmony_cistatic int eeepc_hotk_restore(struct device *device)
90762306a36Sopenharmony_ci{
90862306a36Sopenharmony_ci	struct eeepc_laptop *eeepc = dev_get_drvdata(device);
90962306a36Sopenharmony_ci
91062306a36Sopenharmony_ci	/* Refresh both wlan rfkill state and pci hotplug */
91162306a36Sopenharmony_ci	if (eeepc->wlan_rfkill) {
91262306a36Sopenharmony_ci		eeepc_rfkill_hotplug_update(eeepc, EEEPC_RFKILL_NODE_1);
91362306a36Sopenharmony_ci		eeepc_rfkill_hotplug_update(eeepc, EEEPC_RFKILL_NODE_2);
91462306a36Sopenharmony_ci		eeepc_rfkill_hotplug_update(eeepc, EEEPC_RFKILL_NODE_3);
91562306a36Sopenharmony_ci	}
91662306a36Sopenharmony_ci
91762306a36Sopenharmony_ci	if (eeepc->bluetooth_rfkill)
91862306a36Sopenharmony_ci		rfkill_set_sw_state(eeepc->bluetooth_rfkill,
91962306a36Sopenharmony_ci				    get_acpi(eeepc, CM_ASL_BLUETOOTH) != 1);
92062306a36Sopenharmony_ci	if (eeepc->wwan3g_rfkill)
92162306a36Sopenharmony_ci		rfkill_set_sw_state(eeepc->wwan3g_rfkill,
92262306a36Sopenharmony_ci				    get_acpi(eeepc, CM_ASL_3G) != 1);
92362306a36Sopenharmony_ci	if (eeepc->wimax_rfkill)
92462306a36Sopenharmony_ci		rfkill_set_sw_state(eeepc->wimax_rfkill,
92562306a36Sopenharmony_ci				    get_acpi(eeepc, CM_ASL_WIMAX) != 1);
92662306a36Sopenharmony_ci
92762306a36Sopenharmony_ci	return 0;
92862306a36Sopenharmony_ci}
92962306a36Sopenharmony_ci
93062306a36Sopenharmony_cistatic const struct dev_pm_ops eeepc_pm_ops = {
93162306a36Sopenharmony_ci	.thaw = eeepc_hotk_thaw,
93262306a36Sopenharmony_ci	.restore = eeepc_hotk_restore,
93362306a36Sopenharmony_ci};
93462306a36Sopenharmony_ci
93562306a36Sopenharmony_cistatic struct platform_driver platform_driver = {
93662306a36Sopenharmony_ci	.driver = {
93762306a36Sopenharmony_ci		.name = EEEPC_LAPTOP_FILE,
93862306a36Sopenharmony_ci		.pm = &eeepc_pm_ops,
93962306a36Sopenharmony_ci	}
94062306a36Sopenharmony_ci};
94162306a36Sopenharmony_ci
94262306a36Sopenharmony_ci/*
94362306a36Sopenharmony_ci * Hwmon device
94462306a36Sopenharmony_ci */
94562306a36Sopenharmony_ci
94662306a36Sopenharmony_ci#define EEEPC_EC_SC00      0x61
94762306a36Sopenharmony_ci#define EEEPC_EC_FAN_PWM   (EEEPC_EC_SC00 + 2) /* Fan PWM duty cycle (%) */
94862306a36Sopenharmony_ci#define EEEPC_EC_FAN_HRPM  (EEEPC_EC_SC00 + 5) /* High byte, fan speed (RPM) */
94962306a36Sopenharmony_ci#define EEEPC_EC_FAN_LRPM  (EEEPC_EC_SC00 + 6) /* Low byte, fan speed (RPM) */
95062306a36Sopenharmony_ci
95162306a36Sopenharmony_ci#define EEEPC_EC_SFB0      0xD0
95262306a36Sopenharmony_ci#define EEEPC_EC_FAN_CTRL  (EEEPC_EC_SFB0 + 3) /* Byte containing SF25  */
95362306a36Sopenharmony_ci
95462306a36Sopenharmony_cistatic inline int eeepc_pwm_to_lmsensors(int value)
95562306a36Sopenharmony_ci{
95662306a36Sopenharmony_ci	return value * 255 / 100;
95762306a36Sopenharmony_ci}
95862306a36Sopenharmony_ci
95962306a36Sopenharmony_cistatic inline int eeepc_lmsensors_to_pwm(int value)
96062306a36Sopenharmony_ci{
96162306a36Sopenharmony_ci	value = clamp_val(value, 0, 255);
96262306a36Sopenharmony_ci	return value * 100 / 255;
96362306a36Sopenharmony_ci}
96462306a36Sopenharmony_ci
96562306a36Sopenharmony_cistatic int eeepc_get_fan_pwm(void)
96662306a36Sopenharmony_ci{
96762306a36Sopenharmony_ci	u8 value = 0;
96862306a36Sopenharmony_ci
96962306a36Sopenharmony_ci	ec_read(EEEPC_EC_FAN_PWM, &value);
97062306a36Sopenharmony_ci	return eeepc_pwm_to_lmsensors(value);
97162306a36Sopenharmony_ci}
97262306a36Sopenharmony_ci
97362306a36Sopenharmony_cistatic void eeepc_set_fan_pwm(int value)
97462306a36Sopenharmony_ci{
97562306a36Sopenharmony_ci	value = eeepc_lmsensors_to_pwm(value);
97662306a36Sopenharmony_ci	ec_write(EEEPC_EC_FAN_PWM, value);
97762306a36Sopenharmony_ci}
97862306a36Sopenharmony_ci
97962306a36Sopenharmony_cistatic int eeepc_get_fan_rpm(void)
98062306a36Sopenharmony_ci{
98162306a36Sopenharmony_ci	u8 high = 0;
98262306a36Sopenharmony_ci	u8 low = 0;
98362306a36Sopenharmony_ci
98462306a36Sopenharmony_ci	ec_read(EEEPC_EC_FAN_HRPM, &high);
98562306a36Sopenharmony_ci	ec_read(EEEPC_EC_FAN_LRPM, &low);
98662306a36Sopenharmony_ci	return high << 8 | low;
98762306a36Sopenharmony_ci}
98862306a36Sopenharmony_ci
98962306a36Sopenharmony_ci#define EEEPC_EC_FAN_CTRL_BIT	0x02
99062306a36Sopenharmony_ci#define EEEPC_FAN_CTRL_MANUAL	1
99162306a36Sopenharmony_ci#define EEEPC_FAN_CTRL_AUTO	2
99262306a36Sopenharmony_ci
99362306a36Sopenharmony_cistatic int eeepc_get_fan_ctrl(void)
99462306a36Sopenharmony_ci{
99562306a36Sopenharmony_ci	u8 value = 0;
99662306a36Sopenharmony_ci
99762306a36Sopenharmony_ci	ec_read(EEEPC_EC_FAN_CTRL, &value);
99862306a36Sopenharmony_ci	if (value & EEEPC_EC_FAN_CTRL_BIT)
99962306a36Sopenharmony_ci		return EEEPC_FAN_CTRL_MANUAL;
100062306a36Sopenharmony_ci	else
100162306a36Sopenharmony_ci		return EEEPC_FAN_CTRL_AUTO;
100262306a36Sopenharmony_ci}
100362306a36Sopenharmony_ci
100462306a36Sopenharmony_cistatic void eeepc_set_fan_ctrl(int manual)
100562306a36Sopenharmony_ci{
100662306a36Sopenharmony_ci	u8 value = 0;
100762306a36Sopenharmony_ci
100862306a36Sopenharmony_ci	ec_read(EEEPC_EC_FAN_CTRL, &value);
100962306a36Sopenharmony_ci	if (manual == EEEPC_FAN_CTRL_MANUAL)
101062306a36Sopenharmony_ci		value |= EEEPC_EC_FAN_CTRL_BIT;
101162306a36Sopenharmony_ci	else
101262306a36Sopenharmony_ci		value &= ~EEEPC_EC_FAN_CTRL_BIT;
101362306a36Sopenharmony_ci	ec_write(EEEPC_EC_FAN_CTRL, value);
101462306a36Sopenharmony_ci}
101562306a36Sopenharmony_ci
101662306a36Sopenharmony_cistatic ssize_t store_sys_hwmon(void (*set)(int), const char *buf, size_t count)
101762306a36Sopenharmony_ci{
101862306a36Sopenharmony_ci	int rv, value;
101962306a36Sopenharmony_ci
102062306a36Sopenharmony_ci	rv = parse_arg(buf, &value);
102162306a36Sopenharmony_ci	if (rv < 0)
102262306a36Sopenharmony_ci		return rv;
102362306a36Sopenharmony_ci	set(value);
102462306a36Sopenharmony_ci	return count;
102562306a36Sopenharmony_ci}
102662306a36Sopenharmony_ci
102762306a36Sopenharmony_cistatic ssize_t show_sys_hwmon(int (*get)(void), char *buf)
102862306a36Sopenharmony_ci{
102962306a36Sopenharmony_ci	return sprintf(buf, "%d\n", get());
103062306a36Sopenharmony_ci}
103162306a36Sopenharmony_ci
103262306a36Sopenharmony_ci#define EEEPC_SENSOR_SHOW_FUNC(_name, _get)				\
103362306a36Sopenharmony_ci	static ssize_t _name##_show(struct device *dev,			\
103462306a36Sopenharmony_ci				    struct device_attribute *attr,	\
103562306a36Sopenharmony_ci				    char *buf)				\
103662306a36Sopenharmony_ci	{								\
103762306a36Sopenharmony_ci		return show_sys_hwmon(_get, buf);			\
103862306a36Sopenharmony_ci	}
103962306a36Sopenharmony_ci
104062306a36Sopenharmony_ci#define EEEPC_SENSOR_STORE_FUNC(_name, _set)				\
104162306a36Sopenharmony_ci	static ssize_t _name##_store(struct device *dev,		\
104262306a36Sopenharmony_ci				     struct device_attribute *attr,	\
104362306a36Sopenharmony_ci				     const char *buf, size_t count)	\
104462306a36Sopenharmony_ci	{								\
104562306a36Sopenharmony_ci		return store_sys_hwmon(_set, buf, count);		\
104662306a36Sopenharmony_ci	}
104762306a36Sopenharmony_ci
104862306a36Sopenharmony_ci#define EEEPC_CREATE_SENSOR_ATTR_RW(_name, _get, _set)			\
104962306a36Sopenharmony_ci	EEEPC_SENSOR_SHOW_FUNC(_name, _get)				\
105062306a36Sopenharmony_ci	EEEPC_SENSOR_STORE_FUNC(_name, _set)				\
105162306a36Sopenharmony_ci	static DEVICE_ATTR_RW(_name)
105262306a36Sopenharmony_ci
105362306a36Sopenharmony_ci#define EEEPC_CREATE_SENSOR_ATTR_RO(_name, _get)			\
105462306a36Sopenharmony_ci	EEEPC_SENSOR_SHOW_FUNC(_name, _get)				\
105562306a36Sopenharmony_ci	static DEVICE_ATTR_RO(_name)
105662306a36Sopenharmony_ci
105762306a36Sopenharmony_ciEEEPC_CREATE_SENSOR_ATTR_RO(fan1_input, eeepc_get_fan_rpm);
105862306a36Sopenharmony_ciEEEPC_CREATE_SENSOR_ATTR_RW(pwm1, eeepc_get_fan_pwm,
105962306a36Sopenharmony_ci			    eeepc_set_fan_pwm);
106062306a36Sopenharmony_ciEEEPC_CREATE_SENSOR_ATTR_RW(pwm1_enable, eeepc_get_fan_ctrl,
106162306a36Sopenharmony_ci			    eeepc_set_fan_ctrl);
106262306a36Sopenharmony_ci
106362306a36Sopenharmony_cistatic struct attribute *hwmon_attrs[] = {
106462306a36Sopenharmony_ci	&dev_attr_pwm1.attr,
106562306a36Sopenharmony_ci	&dev_attr_fan1_input.attr,
106662306a36Sopenharmony_ci	&dev_attr_pwm1_enable.attr,
106762306a36Sopenharmony_ci	NULL
106862306a36Sopenharmony_ci};
106962306a36Sopenharmony_ciATTRIBUTE_GROUPS(hwmon);
107062306a36Sopenharmony_ci
107162306a36Sopenharmony_cistatic int eeepc_hwmon_init(struct eeepc_laptop *eeepc)
107262306a36Sopenharmony_ci{
107362306a36Sopenharmony_ci	struct device *dev = &eeepc->platform_device->dev;
107462306a36Sopenharmony_ci	struct device *hwmon;
107562306a36Sopenharmony_ci
107662306a36Sopenharmony_ci	hwmon = devm_hwmon_device_register_with_groups(dev, "eeepc", NULL,
107762306a36Sopenharmony_ci						       hwmon_groups);
107862306a36Sopenharmony_ci	if (IS_ERR(hwmon)) {
107962306a36Sopenharmony_ci		pr_err("Could not register eeepc hwmon device\n");
108062306a36Sopenharmony_ci		return PTR_ERR(hwmon);
108162306a36Sopenharmony_ci	}
108262306a36Sopenharmony_ci	return 0;
108362306a36Sopenharmony_ci}
108462306a36Sopenharmony_ci
108562306a36Sopenharmony_ci/*
108662306a36Sopenharmony_ci * Backlight device
108762306a36Sopenharmony_ci */
108862306a36Sopenharmony_cistatic int read_brightness(struct backlight_device *bd)
108962306a36Sopenharmony_ci{
109062306a36Sopenharmony_ci	struct eeepc_laptop *eeepc = bl_get_data(bd);
109162306a36Sopenharmony_ci
109262306a36Sopenharmony_ci	return get_acpi(eeepc, CM_ASL_PANELBRIGHT);
109362306a36Sopenharmony_ci}
109462306a36Sopenharmony_ci
109562306a36Sopenharmony_cistatic int set_brightness(struct backlight_device *bd, int value)
109662306a36Sopenharmony_ci{
109762306a36Sopenharmony_ci	struct eeepc_laptop *eeepc = bl_get_data(bd);
109862306a36Sopenharmony_ci
109962306a36Sopenharmony_ci	return set_acpi(eeepc, CM_ASL_PANELBRIGHT, value);
110062306a36Sopenharmony_ci}
110162306a36Sopenharmony_ci
110262306a36Sopenharmony_cistatic int update_bl_status(struct backlight_device *bd)
110362306a36Sopenharmony_ci{
110462306a36Sopenharmony_ci	return set_brightness(bd, bd->props.brightness);
110562306a36Sopenharmony_ci}
110662306a36Sopenharmony_ci
110762306a36Sopenharmony_cistatic const struct backlight_ops eeepcbl_ops = {
110862306a36Sopenharmony_ci	.get_brightness = read_brightness,
110962306a36Sopenharmony_ci	.update_status = update_bl_status,
111062306a36Sopenharmony_ci};
111162306a36Sopenharmony_ci
111262306a36Sopenharmony_cistatic int eeepc_backlight_notify(struct eeepc_laptop *eeepc)
111362306a36Sopenharmony_ci{
111462306a36Sopenharmony_ci	struct backlight_device *bd = eeepc->backlight_device;
111562306a36Sopenharmony_ci	int old = bd->props.brightness;
111662306a36Sopenharmony_ci
111762306a36Sopenharmony_ci	backlight_force_update(bd, BACKLIGHT_UPDATE_HOTKEY);
111862306a36Sopenharmony_ci
111962306a36Sopenharmony_ci	return old;
112062306a36Sopenharmony_ci}
112162306a36Sopenharmony_ci
112262306a36Sopenharmony_cistatic int eeepc_backlight_init(struct eeepc_laptop *eeepc)
112362306a36Sopenharmony_ci{
112462306a36Sopenharmony_ci	struct backlight_properties props;
112562306a36Sopenharmony_ci	struct backlight_device *bd;
112662306a36Sopenharmony_ci
112762306a36Sopenharmony_ci	memset(&props, 0, sizeof(struct backlight_properties));
112862306a36Sopenharmony_ci	props.type = BACKLIGHT_PLATFORM;
112962306a36Sopenharmony_ci	props.max_brightness = 15;
113062306a36Sopenharmony_ci	bd = backlight_device_register(EEEPC_LAPTOP_FILE,
113162306a36Sopenharmony_ci				       &eeepc->platform_device->dev, eeepc,
113262306a36Sopenharmony_ci				       &eeepcbl_ops, &props);
113362306a36Sopenharmony_ci	if (IS_ERR(bd)) {
113462306a36Sopenharmony_ci		pr_err("Could not register eeepc backlight device\n");
113562306a36Sopenharmony_ci		eeepc->backlight_device = NULL;
113662306a36Sopenharmony_ci		return PTR_ERR(bd);
113762306a36Sopenharmony_ci	}
113862306a36Sopenharmony_ci	eeepc->backlight_device = bd;
113962306a36Sopenharmony_ci	bd->props.brightness = read_brightness(bd);
114062306a36Sopenharmony_ci	bd->props.power = FB_BLANK_UNBLANK;
114162306a36Sopenharmony_ci	backlight_update_status(bd);
114262306a36Sopenharmony_ci	return 0;
114362306a36Sopenharmony_ci}
114462306a36Sopenharmony_ci
114562306a36Sopenharmony_cistatic void eeepc_backlight_exit(struct eeepc_laptop *eeepc)
114662306a36Sopenharmony_ci{
114762306a36Sopenharmony_ci	backlight_device_unregister(eeepc->backlight_device);
114862306a36Sopenharmony_ci	eeepc->backlight_device = NULL;
114962306a36Sopenharmony_ci}
115062306a36Sopenharmony_ci
115162306a36Sopenharmony_ci
115262306a36Sopenharmony_ci/*
115362306a36Sopenharmony_ci * Input device (i.e. hotkeys)
115462306a36Sopenharmony_ci */
115562306a36Sopenharmony_cistatic int eeepc_input_init(struct eeepc_laptop *eeepc)
115662306a36Sopenharmony_ci{
115762306a36Sopenharmony_ci	struct input_dev *input;
115862306a36Sopenharmony_ci	int error;
115962306a36Sopenharmony_ci
116062306a36Sopenharmony_ci	input = input_allocate_device();
116162306a36Sopenharmony_ci	if (!input)
116262306a36Sopenharmony_ci		return -ENOMEM;
116362306a36Sopenharmony_ci
116462306a36Sopenharmony_ci	input->name = "Asus EeePC extra buttons";
116562306a36Sopenharmony_ci	input->phys = EEEPC_LAPTOP_FILE "/input0";
116662306a36Sopenharmony_ci	input->id.bustype = BUS_HOST;
116762306a36Sopenharmony_ci	input->dev.parent = &eeepc->platform_device->dev;
116862306a36Sopenharmony_ci
116962306a36Sopenharmony_ci	error = sparse_keymap_setup(input, eeepc_keymap, NULL);
117062306a36Sopenharmony_ci	if (error) {
117162306a36Sopenharmony_ci		pr_err("Unable to setup input device keymap\n");
117262306a36Sopenharmony_ci		goto err_free_dev;
117362306a36Sopenharmony_ci	}
117462306a36Sopenharmony_ci
117562306a36Sopenharmony_ci	error = input_register_device(input);
117662306a36Sopenharmony_ci	if (error) {
117762306a36Sopenharmony_ci		pr_err("Unable to register input device\n");
117862306a36Sopenharmony_ci		goto err_free_dev;
117962306a36Sopenharmony_ci	}
118062306a36Sopenharmony_ci
118162306a36Sopenharmony_ci	eeepc->inputdev = input;
118262306a36Sopenharmony_ci	return 0;
118362306a36Sopenharmony_ci
118462306a36Sopenharmony_cierr_free_dev:
118562306a36Sopenharmony_ci	input_free_device(input);
118662306a36Sopenharmony_ci	return error;
118762306a36Sopenharmony_ci}
118862306a36Sopenharmony_ci
118962306a36Sopenharmony_cistatic void eeepc_input_exit(struct eeepc_laptop *eeepc)
119062306a36Sopenharmony_ci{
119162306a36Sopenharmony_ci	if (eeepc->inputdev)
119262306a36Sopenharmony_ci		input_unregister_device(eeepc->inputdev);
119362306a36Sopenharmony_ci	eeepc->inputdev = NULL;
119462306a36Sopenharmony_ci}
119562306a36Sopenharmony_ci
119662306a36Sopenharmony_ci/*
119762306a36Sopenharmony_ci * ACPI driver
119862306a36Sopenharmony_ci */
119962306a36Sopenharmony_cistatic void eeepc_input_notify(struct eeepc_laptop *eeepc, int event)
120062306a36Sopenharmony_ci{
120162306a36Sopenharmony_ci	if (!eeepc->inputdev)
120262306a36Sopenharmony_ci		return;
120362306a36Sopenharmony_ci	if (!sparse_keymap_report_event(eeepc->inputdev, event, 1, true))
120462306a36Sopenharmony_ci		pr_info("Unknown key %x pressed\n", event);
120562306a36Sopenharmony_ci}
120662306a36Sopenharmony_ci
120762306a36Sopenharmony_cistatic void eeepc_acpi_notify(struct acpi_device *device, u32 event)
120862306a36Sopenharmony_ci{
120962306a36Sopenharmony_ci	struct eeepc_laptop *eeepc = acpi_driver_data(device);
121062306a36Sopenharmony_ci	int old_brightness, new_brightness;
121162306a36Sopenharmony_ci	u16 count;
121262306a36Sopenharmony_ci
121362306a36Sopenharmony_ci	if (event > ACPI_MAX_SYS_NOTIFY)
121462306a36Sopenharmony_ci		return;
121562306a36Sopenharmony_ci	count = eeepc->event_count[event % 128]++;
121662306a36Sopenharmony_ci	acpi_bus_generate_netlink_event(device->pnp.device_class,
121762306a36Sopenharmony_ci					dev_name(&device->dev), event,
121862306a36Sopenharmony_ci					count);
121962306a36Sopenharmony_ci
122062306a36Sopenharmony_ci	/* Brightness events are special */
122162306a36Sopenharmony_ci	if (event < NOTIFY_BRN_MIN || event > NOTIFY_BRN_MAX) {
122262306a36Sopenharmony_ci		eeepc_input_notify(eeepc, event);
122362306a36Sopenharmony_ci		return;
122462306a36Sopenharmony_ci	}
122562306a36Sopenharmony_ci
122662306a36Sopenharmony_ci	/* Ignore them completely if the acpi video driver is used */
122762306a36Sopenharmony_ci	if (!eeepc->backlight_device)
122862306a36Sopenharmony_ci		return;
122962306a36Sopenharmony_ci
123062306a36Sopenharmony_ci	/* Update the backlight device. */
123162306a36Sopenharmony_ci	old_brightness = eeepc_backlight_notify(eeepc);
123262306a36Sopenharmony_ci
123362306a36Sopenharmony_ci	/* Convert event to keypress (obsolescent hack) */
123462306a36Sopenharmony_ci	new_brightness = event - NOTIFY_BRN_MIN;
123562306a36Sopenharmony_ci
123662306a36Sopenharmony_ci	if (new_brightness < old_brightness) {
123762306a36Sopenharmony_ci		event = NOTIFY_BRN_MIN; /* brightness down */
123862306a36Sopenharmony_ci	} else if (new_brightness > old_brightness) {
123962306a36Sopenharmony_ci		event = NOTIFY_BRN_MAX; /* brightness up */
124062306a36Sopenharmony_ci	} else {
124162306a36Sopenharmony_ci		/*
124262306a36Sopenharmony_ci		 * no change in brightness - already at min/max,
124362306a36Sopenharmony_ci		 * event will be desired value (or else ignored)
124462306a36Sopenharmony_ci		 */
124562306a36Sopenharmony_ci	}
124662306a36Sopenharmony_ci	eeepc_input_notify(eeepc, event);
124762306a36Sopenharmony_ci}
124862306a36Sopenharmony_ci
124962306a36Sopenharmony_cistatic void eeepc_dmi_check(struct eeepc_laptop *eeepc)
125062306a36Sopenharmony_ci{
125162306a36Sopenharmony_ci	const char *model;
125262306a36Sopenharmony_ci
125362306a36Sopenharmony_ci	model = dmi_get_system_info(DMI_PRODUCT_NAME);
125462306a36Sopenharmony_ci	if (!model)
125562306a36Sopenharmony_ci		return;
125662306a36Sopenharmony_ci
125762306a36Sopenharmony_ci	/*
125862306a36Sopenharmony_ci	 * Blacklist for setting cpufv (cpu speed).
125962306a36Sopenharmony_ci	 *
126062306a36Sopenharmony_ci	 * EeePC 4G ("701") implements CFVS, but it is not supported
126162306a36Sopenharmony_ci	 * by the pre-installed OS, and the original option to change it
126262306a36Sopenharmony_ci	 * in the BIOS setup screen was removed in later versions.
126362306a36Sopenharmony_ci	 *
126462306a36Sopenharmony_ci	 * Judging by the lack of "Super Hybrid Engine" on Asus product pages,
126562306a36Sopenharmony_ci	 * this applies to all "701" models (4G/4G Surf/2G Surf).
126662306a36Sopenharmony_ci	 *
126762306a36Sopenharmony_ci	 * So Asus made a deliberate decision not to support it on this model.
126862306a36Sopenharmony_ci	 * We have several reports that using it can cause the system to hang
126962306a36Sopenharmony_ci	 *
127062306a36Sopenharmony_ci	 * The hang has also been reported on a "702" (Model name "8G"?).
127162306a36Sopenharmony_ci	 *
127262306a36Sopenharmony_ci	 * We avoid dmi_check_system() / dmi_match(), because they use
127362306a36Sopenharmony_ci	 * substring matching.  We don't want to affect the "701SD"
127462306a36Sopenharmony_ci	 * and "701SDX" models, because they do support S.H.E.
127562306a36Sopenharmony_ci	 */
127662306a36Sopenharmony_ci	if (strcmp(model, "701") == 0 || strcmp(model, "702") == 0) {
127762306a36Sopenharmony_ci		eeepc->cpufv_disabled = true;
127862306a36Sopenharmony_ci		pr_info("model %s does not officially support setting cpu speed\n",
127962306a36Sopenharmony_ci			model);
128062306a36Sopenharmony_ci		pr_info("cpufv disabled to avoid instability\n");
128162306a36Sopenharmony_ci	}
128262306a36Sopenharmony_ci
128362306a36Sopenharmony_ci	/*
128462306a36Sopenharmony_ci	 * Blacklist for wlan hotplug
128562306a36Sopenharmony_ci	 *
128662306a36Sopenharmony_ci	 * Eeepc 1005HA doesn't work like others models and don't need the
128762306a36Sopenharmony_ci	 * hotplug code. In fact, current hotplug code seems to unplug another
128862306a36Sopenharmony_ci	 * device...
128962306a36Sopenharmony_ci	 */
129062306a36Sopenharmony_ci	if (strcmp(model, "1005HA") == 0 || strcmp(model, "1201N") == 0 ||
129162306a36Sopenharmony_ci	    strcmp(model, "1005PE") == 0) {
129262306a36Sopenharmony_ci		eeepc->hotplug_disabled = true;
129362306a36Sopenharmony_ci		pr_info("wlan hotplug disabled\n");
129462306a36Sopenharmony_ci	}
129562306a36Sopenharmony_ci}
129662306a36Sopenharmony_ci
129762306a36Sopenharmony_cistatic void cmsg_quirk(struct eeepc_laptop *eeepc, int cm, const char *name)
129862306a36Sopenharmony_ci{
129962306a36Sopenharmony_ci	int dummy;
130062306a36Sopenharmony_ci
130162306a36Sopenharmony_ci	/* Some BIOSes do not report cm although it is available.
130262306a36Sopenharmony_ci	   Check if cm_getv[cm] works and, if yes, assume cm should be set. */
130362306a36Sopenharmony_ci	if (!(eeepc->cm_supported & (1 << cm))
130462306a36Sopenharmony_ci	    && !read_acpi_int(eeepc->handle, cm_getv[cm], &dummy)) {
130562306a36Sopenharmony_ci		pr_info("%s (%x) not reported by BIOS, enabling anyway\n",
130662306a36Sopenharmony_ci			name, 1 << cm);
130762306a36Sopenharmony_ci		eeepc->cm_supported |= 1 << cm;
130862306a36Sopenharmony_ci	}
130962306a36Sopenharmony_ci}
131062306a36Sopenharmony_ci
131162306a36Sopenharmony_cistatic void cmsg_quirks(struct eeepc_laptop *eeepc)
131262306a36Sopenharmony_ci{
131362306a36Sopenharmony_ci	cmsg_quirk(eeepc, CM_ASL_LID, "LID");
131462306a36Sopenharmony_ci	cmsg_quirk(eeepc, CM_ASL_TYPE, "TYPE");
131562306a36Sopenharmony_ci	cmsg_quirk(eeepc, CM_ASL_PANELPOWER, "PANELPOWER");
131662306a36Sopenharmony_ci	cmsg_quirk(eeepc, CM_ASL_TPD, "TPD");
131762306a36Sopenharmony_ci}
131862306a36Sopenharmony_ci
131962306a36Sopenharmony_cistatic int eeepc_acpi_init(struct eeepc_laptop *eeepc)
132062306a36Sopenharmony_ci{
132162306a36Sopenharmony_ci	unsigned int init_flags;
132262306a36Sopenharmony_ci	int result;
132362306a36Sopenharmony_ci
132462306a36Sopenharmony_ci	result = acpi_bus_get_status(eeepc->device);
132562306a36Sopenharmony_ci	if (result)
132662306a36Sopenharmony_ci		return result;
132762306a36Sopenharmony_ci	if (!eeepc->device->status.present) {
132862306a36Sopenharmony_ci		pr_err("Hotkey device not present, aborting\n");
132962306a36Sopenharmony_ci		return -ENODEV;
133062306a36Sopenharmony_ci	}
133162306a36Sopenharmony_ci
133262306a36Sopenharmony_ci	init_flags = DISABLE_ASL_WLAN | DISABLE_ASL_DISPLAYSWITCH;
133362306a36Sopenharmony_ci	pr_notice("Hotkey init flags 0x%x\n", init_flags);
133462306a36Sopenharmony_ci
133562306a36Sopenharmony_ci	if (write_acpi_int(eeepc->handle, "INIT", init_flags)) {
133662306a36Sopenharmony_ci		pr_err("Hotkey initialization failed\n");
133762306a36Sopenharmony_ci		return -ENODEV;
133862306a36Sopenharmony_ci	}
133962306a36Sopenharmony_ci
134062306a36Sopenharmony_ci	/* get control methods supported */
134162306a36Sopenharmony_ci	if (read_acpi_int(eeepc->handle, "CMSG", &eeepc->cm_supported)) {
134262306a36Sopenharmony_ci		pr_err("Get control methods supported failed\n");
134362306a36Sopenharmony_ci		return -ENODEV;
134462306a36Sopenharmony_ci	}
134562306a36Sopenharmony_ci	cmsg_quirks(eeepc);
134662306a36Sopenharmony_ci	pr_info("Get control methods supported: 0x%x\n", eeepc->cm_supported);
134762306a36Sopenharmony_ci
134862306a36Sopenharmony_ci	return 0;
134962306a36Sopenharmony_ci}
135062306a36Sopenharmony_ci
135162306a36Sopenharmony_cistatic void eeepc_enable_camera(struct eeepc_laptop *eeepc)
135262306a36Sopenharmony_ci{
135362306a36Sopenharmony_ci	/*
135462306a36Sopenharmony_ci	 * If the following call to set_acpi() fails, it's because there's no
135562306a36Sopenharmony_ci	 * camera so we can ignore the error.
135662306a36Sopenharmony_ci	 */
135762306a36Sopenharmony_ci	if (get_acpi(eeepc, CM_ASL_CAMERA) == 0)
135862306a36Sopenharmony_ci		set_acpi(eeepc, CM_ASL_CAMERA, 1);
135962306a36Sopenharmony_ci}
136062306a36Sopenharmony_ci
136162306a36Sopenharmony_cistatic bool eeepc_device_present;
136262306a36Sopenharmony_ci
136362306a36Sopenharmony_cistatic int eeepc_acpi_add(struct acpi_device *device)
136462306a36Sopenharmony_ci{
136562306a36Sopenharmony_ci	struct eeepc_laptop *eeepc;
136662306a36Sopenharmony_ci	int result;
136762306a36Sopenharmony_ci
136862306a36Sopenharmony_ci	pr_notice(EEEPC_LAPTOP_NAME "\n");
136962306a36Sopenharmony_ci	eeepc = kzalloc(sizeof(struct eeepc_laptop), GFP_KERNEL);
137062306a36Sopenharmony_ci	if (!eeepc)
137162306a36Sopenharmony_ci		return -ENOMEM;
137262306a36Sopenharmony_ci	eeepc->handle = device->handle;
137362306a36Sopenharmony_ci	strcpy(acpi_device_name(device), EEEPC_ACPI_DEVICE_NAME);
137462306a36Sopenharmony_ci	strcpy(acpi_device_class(device), EEEPC_ACPI_CLASS);
137562306a36Sopenharmony_ci	device->driver_data = eeepc;
137662306a36Sopenharmony_ci	eeepc->device = device;
137762306a36Sopenharmony_ci
137862306a36Sopenharmony_ci	eeepc->hotplug_disabled = hotplug_disabled;
137962306a36Sopenharmony_ci
138062306a36Sopenharmony_ci	eeepc_dmi_check(eeepc);
138162306a36Sopenharmony_ci
138262306a36Sopenharmony_ci	result = eeepc_acpi_init(eeepc);
138362306a36Sopenharmony_ci	if (result)
138462306a36Sopenharmony_ci		goto fail_platform;
138562306a36Sopenharmony_ci	eeepc_enable_camera(eeepc);
138662306a36Sopenharmony_ci
138762306a36Sopenharmony_ci	/*
138862306a36Sopenharmony_ci	 * Register the platform device first.  It is used as a parent for the
138962306a36Sopenharmony_ci	 * sub-devices below.
139062306a36Sopenharmony_ci	 *
139162306a36Sopenharmony_ci	 * Note that if there are multiple instances of this ACPI device it
139262306a36Sopenharmony_ci	 * will bail out, because the platform device is registered with a
139362306a36Sopenharmony_ci	 * fixed name.  Of course it doesn't make sense to have more than one,
139462306a36Sopenharmony_ci	 * and machine-specific scripts find the fixed name convenient.  But
139562306a36Sopenharmony_ci	 * It's also good for us to exclude multiple instances because both
139662306a36Sopenharmony_ci	 * our hwmon and our wlan rfkill subdevice use global ACPI objects
139762306a36Sopenharmony_ci	 * (the EC and the PCI wlan slot respectively).
139862306a36Sopenharmony_ci	 */
139962306a36Sopenharmony_ci	result = eeepc_platform_init(eeepc);
140062306a36Sopenharmony_ci	if (result)
140162306a36Sopenharmony_ci		goto fail_platform;
140262306a36Sopenharmony_ci
140362306a36Sopenharmony_ci	if (acpi_video_get_backlight_type() == acpi_backlight_vendor) {
140462306a36Sopenharmony_ci		result = eeepc_backlight_init(eeepc);
140562306a36Sopenharmony_ci		if (result)
140662306a36Sopenharmony_ci			goto fail_backlight;
140762306a36Sopenharmony_ci	}
140862306a36Sopenharmony_ci
140962306a36Sopenharmony_ci	result = eeepc_input_init(eeepc);
141062306a36Sopenharmony_ci	if (result)
141162306a36Sopenharmony_ci		goto fail_input;
141262306a36Sopenharmony_ci
141362306a36Sopenharmony_ci	result = eeepc_hwmon_init(eeepc);
141462306a36Sopenharmony_ci	if (result)
141562306a36Sopenharmony_ci		goto fail_hwmon;
141662306a36Sopenharmony_ci
141762306a36Sopenharmony_ci	result = eeepc_led_init(eeepc);
141862306a36Sopenharmony_ci	if (result)
141962306a36Sopenharmony_ci		goto fail_led;
142062306a36Sopenharmony_ci
142162306a36Sopenharmony_ci	result = eeepc_rfkill_init(eeepc);
142262306a36Sopenharmony_ci	if (result)
142362306a36Sopenharmony_ci		goto fail_rfkill;
142462306a36Sopenharmony_ci
142562306a36Sopenharmony_ci	eeepc_device_present = true;
142662306a36Sopenharmony_ci	return 0;
142762306a36Sopenharmony_ci
142862306a36Sopenharmony_cifail_rfkill:
142962306a36Sopenharmony_ci	eeepc_led_exit(eeepc);
143062306a36Sopenharmony_cifail_led:
143162306a36Sopenharmony_cifail_hwmon:
143262306a36Sopenharmony_ci	eeepc_input_exit(eeepc);
143362306a36Sopenharmony_cifail_input:
143462306a36Sopenharmony_ci	eeepc_backlight_exit(eeepc);
143562306a36Sopenharmony_cifail_backlight:
143662306a36Sopenharmony_ci	eeepc_platform_exit(eeepc);
143762306a36Sopenharmony_cifail_platform:
143862306a36Sopenharmony_ci	kfree(eeepc);
143962306a36Sopenharmony_ci
144062306a36Sopenharmony_ci	return result;
144162306a36Sopenharmony_ci}
144262306a36Sopenharmony_ci
144362306a36Sopenharmony_cistatic void eeepc_acpi_remove(struct acpi_device *device)
144462306a36Sopenharmony_ci{
144562306a36Sopenharmony_ci	struct eeepc_laptop *eeepc = acpi_driver_data(device);
144662306a36Sopenharmony_ci
144762306a36Sopenharmony_ci	eeepc_backlight_exit(eeepc);
144862306a36Sopenharmony_ci	eeepc_rfkill_exit(eeepc);
144962306a36Sopenharmony_ci	eeepc_input_exit(eeepc);
145062306a36Sopenharmony_ci	eeepc_led_exit(eeepc);
145162306a36Sopenharmony_ci	eeepc_platform_exit(eeepc);
145262306a36Sopenharmony_ci
145362306a36Sopenharmony_ci	kfree(eeepc);
145462306a36Sopenharmony_ci}
145562306a36Sopenharmony_ci
145662306a36Sopenharmony_ci
145762306a36Sopenharmony_cistatic const struct acpi_device_id eeepc_device_ids[] = {
145862306a36Sopenharmony_ci	{EEEPC_ACPI_HID, 0},
145962306a36Sopenharmony_ci	{"", 0},
146062306a36Sopenharmony_ci};
146162306a36Sopenharmony_ciMODULE_DEVICE_TABLE(acpi, eeepc_device_ids);
146262306a36Sopenharmony_ci
146362306a36Sopenharmony_cistatic struct acpi_driver eeepc_acpi_driver = {
146462306a36Sopenharmony_ci	.name = EEEPC_LAPTOP_NAME,
146562306a36Sopenharmony_ci	.class = EEEPC_ACPI_CLASS,
146662306a36Sopenharmony_ci	.owner = THIS_MODULE,
146762306a36Sopenharmony_ci	.ids = eeepc_device_ids,
146862306a36Sopenharmony_ci	.flags = ACPI_DRIVER_ALL_NOTIFY_EVENTS,
146962306a36Sopenharmony_ci	.ops = {
147062306a36Sopenharmony_ci		.add = eeepc_acpi_add,
147162306a36Sopenharmony_ci		.remove = eeepc_acpi_remove,
147262306a36Sopenharmony_ci		.notify = eeepc_acpi_notify,
147362306a36Sopenharmony_ci	},
147462306a36Sopenharmony_ci};
147562306a36Sopenharmony_ci
147662306a36Sopenharmony_ci
147762306a36Sopenharmony_cistatic int __init eeepc_laptop_init(void)
147862306a36Sopenharmony_ci{
147962306a36Sopenharmony_ci	int result;
148062306a36Sopenharmony_ci
148162306a36Sopenharmony_ci	result = platform_driver_register(&platform_driver);
148262306a36Sopenharmony_ci	if (result < 0)
148362306a36Sopenharmony_ci		return result;
148462306a36Sopenharmony_ci
148562306a36Sopenharmony_ci	result = acpi_bus_register_driver(&eeepc_acpi_driver);
148662306a36Sopenharmony_ci	if (result < 0)
148762306a36Sopenharmony_ci		goto fail_acpi_driver;
148862306a36Sopenharmony_ci
148962306a36Sopenharmony_ci	if (!eeepc_device_present) {
149062306a36Sopenharmony_ci		result = -ENODEV;
149162306a36Sopenharmony_ci		goto fail_no_device;
149262306a36Sopenharmony_ci	}
149362306a36Sopenharmony_ci
149462306a36Sopenharmony_ci	return 0;
149562306a36Sopenharmony_ci
149662306a36Sopenharmony_cifail_no_device:
149762306a36Sopenharmony_ci	acpi_bus_unregister_driver(&eeepc_acpi_driver);
149862306a36Sopenharmony_cifail_acpi_driver:
149962306a36Sopenharmony_ci	platform_driver_unregister(&platform_driver);
150062306a36Sopenharmony_ci	return result;
150162306a36Sopenharmony_ci}
150262306a36Sopenharmony_ci
150362306a36Sopenharmony_cistatic void __exit eeepc_laptop_exit(void)
150462306a36Sopenharmony_ci{
150562306a36Sopenharmony_ci	acpi_bus_unregister_driver(&eeepc_acpi_driver);
150662306a36Sopenharmony_ci	platform_driver_unregister(&platform_driver);
150762306a36Sopenharmony_ci}
150862306a36Sopenharmony_ci
150962306a36Sopenharmony_cimodule_init(eeepc_laptop_init);
151062306a36Sopenharmony_cimodule_exit(eeepc_laptop_exit);
1511