static int lenovo_sl_procfs_init(void)
{
	struct proc_dir_entry *proc_ec;

	proc_dir = proc_mkdir(LENSL_PROC_DIRNAME, acpi_root_dir);
	if (!proc_dir) {
		vdbg_printk(LENSL_ERR,
		   "Failed to create proc dir acpi/%s/\n", LENSL_PROC_DIRNAME);
		return -ENOENT;
	}
#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,29)
	proc_dir->owner = THIS_MODULE;
#endif

#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,25)
	proc_ec = create_proc_entry(LENSL_PROC_EC, 0600, proc_dir);
#else
	proc_ec = proc_create(LENSL_PROC_EC, 0600, proc_dir, &proc_fops);
#endif
	if (!proc_ec) {
		vdbg_printk(LENSL_ERR,
			"Failed to create proc entry acpi/%s/%s\n",
			LENSL_PROC_DIRNAME, LENSL_PROC_EC);
		return -ENOENT;
	}
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,25)
	proc_ec->proc_fops = &proc_fops;
#endif
	vdbg_printk(LENSL_DEBUG, "Initialized procfs debugging interface\n");

	return 0;
}
static int lensl_radio_new_rfkill(struct lensl_radio *radio,
			struct rfkill **rfk, bool sw_blocked,
			bool hw_blocked)
{
	int res;		
		
	*rfk = rfkill_alloc(radio->rfkname, &lensl_pdev->dev, radio->rfktype,
			&rfkops, radio);
	if (!*rfk) {
		vdbg_printk(LENSL_ERR,
			"Failed to allocate memory for rfkill class\n");
		return -ENOMEM;
	}

	rfkill_set_hw_state(*rfk, hw_blocked);
	rfkill_set_sw_state(*rfk, sw_blocked);

	res = rfkill_register(*rfk);
	if (res < 0) {
		vdbg_printk(LENSL_ERR,
			"Failed to register %s rfkill switch: %d\n",
			radio->rfkname, res);
		rfkill_destroy(*rfk);
		*rfk = NULL;
		return res;
	}

	return 0;
}
static int hkey_inputdev_init(void)
{
	int result;
	struct key_entry *key;

	hkey_inputdev = input_allocate_device();
	if (!hkey_inputdev) {
		vdbg_printk(LENSL_ERR,
			"Failed to allocate hotkey input device\n");
		return -ENODEV;
	}
	hkey_inputdev->name = "Lenovo ThinkPad SL Series extra buttons";
	hkey_inputdev->phys = LENSL_HKEY_FILE "/input0";
	hkey_inputdev->uniq = LENSL_HKEY_FILE;
	hkey_inputdev->id.bustype = BUS_HOST;
	hkey_inputdev->id.vendor = PCI_VENDOR_ID_LENOVO;
	hkey_inputdev->getkeycode = hkey_inputdev_getkeycode;
	hkey_inputdev->setkeycode = hkey_inputdev_setkeycode;
	set_bit(EV_KEY, hkey_inputdev->evbit);

	for (key = ec_keymap; key->type != KE_END; key++)
		set_bit(key->keycode, hkey_inputdev->keybit);

	result = input_register_device(hkey_inputdev);
	if (result) {
		vdbg_printk(LENSL_ERR,
			"Failed to register hotkey input device\n");
		input_free_device(hkey_inputdev);
		hkey_inputdev = NULL;
		return -ENODEV;
	}
	vdbg_printk(LENSL_DEBUG, "Initialized hotkey subdriver\n");
	return 0;
}
static int hwmon_init(void)
{
	int res;

	pwm1_value = -1;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,9,0)
	lensl_hwmon_device = hwmon_device_register_with_info(&lensl_pdev->dev, "lenovo_sl_laptop", NULL, NULL, NULL);
#else
	lensl_hwmon_device = hwmon_device_register(&lensl_pdev->dev);
#endif
	if (!lensl_hwmon_device) {
		vdbg_printk(LENSL_ERR, "Failed to register hwmon device\n");
		return -ENODEV;
	}

	res = sysfs_create_group(&lensl_hwmon_device->kobj,
				 &hwmon_attr_group);
	if (res < 0) {
		vdbg_printk(LENSL_ERR, "Failed to create hwmon sysfs group\n");
		hwmon_device_unregister(lensl_hwmon_device);
		lensl_hwmon_device = NULL;
		return -ENODEV;
	}
	vdbg_printk(LENSL_DEBUG, "Initialized hwmon subdriver\n");
	return 0;
}
static int __init lenovo_sl_laptop_init(void)
{
	int ret;
	acpi_status status;

	hkey_handle = ec0_handle = NULL;

	if (acpi_disabled)
		return -ENODEV;

	lensl_wq = create_singlethread_workqueue(LENSL_WORKQUEUE_NAME);
	if (!lensl_wq) {
		vdbg_printk(LENSL_ERR, "Failed to create a workqueue\n");
		return -ENOMEM;
	}

	status = acpi_get_handle(NULL, LENSL_HKEY, &hkey_handle);
	if (ACPI_FAILURE(status)) {
		vdbg_printk(LENSL_ERR,
			"Failed to get ACPI handle for %s\n", LENSL_HKEY);
		return -ENODEV;
	}
	status = acpi_get_handle(NULL, LENSL_EC0, &ec0_handle);
	if (ACPI_FAILURE(status)) {
		vdbg_printk(LENSL_ERR,
			"Failed to get ACPI handle for %s\n", LENSL_EC0);
		return -ENODEV;
	}

	lensl_pdev = platform_device_register_simple(LENSL_DRVR_NAME, -1,
							NULL, 0);
	if (IS_ERR(lensl_pdev)) {
		ret = PTR_ERR(lensl_pdev);
		lensl_pdev = NULL;
		vdbg_printk(LENSL_ERR, "Failed to register platform device\n");
		return ret;
	}

	ret = hkey_inputdev_init();
	if (ret)
		return -ENODEV;

	radio_init(LENSL_BLUETOOTH);
	radio_init(LENSL_WWAN);
	radio_init(LENSL_UWB);

	led_init();
	mutex_init(&hkey_poll_mutex);
	hwmon_init();

	if (debug_ec)
		lenovo_sl_procfs_init();

	vdbg_printk(LENSL_INFO, "Loaded Lenovo ThinkPad SL Series driver\n");
	return 0;
}
static int get_bcl(struct lensl_vector *levels)
{
	int i, status;
	struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
	union acpi_object *o, *obj;

	if (!levels)
		return -EINVAL;
	if (levels->count) {
		levels->count = 0;
		kfree(levels->values);
	}

	/* _BCL returns an array sorted from high to low; the first two values
	   are *not* special (non-standard behavior) */
	status = acpi_evaluate_object(lcdd_handle, "_BCL", NULL, &buffer);
	if (!ACPI_SUCCESS(status))
		return status;
	obj = (union acpi_object *)buffer.pointer;
	if (!obj || (obj->type != ACPI_TYPE_PACKAGE)) {
		vdbg_printk(LENSL_ERR, "Invalid _BCL data\n");
		status = -EFAULT;
		goto out;
	}

	levels->count = obj->package.count;
	if (!levels->count)
		goto out;
	levels->values = kmalloc(levels->count * sizeof(int), GFP_KERNEL);
	if (!levels->values) {
		vdbg_printk(LENSL_ERR,
			"Failed to allocate memory for brightness levels\n");
		status = -ENOMEM;
		goto out;
	}

	for (i = 0; i < obj->package.count; i++) {
		o = (union acpi_object *)&obj->package.elements[i];
		if (o->type != ACPI_TYPE_INTEGER) {
			vdbg_printk(LENSL_ERR, "Invalid brightness data\n");
			goto err;
		}
		levels->values[i] = (int) o->integer.value;
	}
	goto out;

err:
	levels->count = 0;
	kfree(levels->values);

out:
	kfree(buffer.pointer);

	return status;
}
static int led_init(void)
{
	int res;

	memset(&led_tv, 0, sizeof(led_tv));
	led_tv.cdev.brightness_get = led_tv_brightness_get_sysfs;
	led_tv.cdev.brightness_set = led_tv_brightness_set_sysfs;
	led_tv.cdev.blink_set = led_tv_blink_set_sysfs;
	led_tv.cdev.name = LENSL_LED_TV_NAME;
	INIT_WORK(&led_tv.work, led_tv_worker);
	set_tvls(LENSL_LED_TV_OFF);
	res = led_classdev_register(&lensl_pdev->dev, &led_tv.cdev);
	if (res) {
		vdbg_printk(LENSL_WARNING, "Failed to register LED device\n");
		return res;
	}
	led_tv.supported = 1;
	vdbg_printk(LENSL_DEBUG, "Initialized LED subdriver\n");
	return 0;
}
static int backlight_init(void)
{
	int status = 0;

	lcdd_handle = NULL;
	backlight = NULL;
	backlight_levels.count = 0;
	backlight_levels.values = NULL;

	status = acpi_get_handle(NULL, LENSL_LCDD, &lcdd_handle);
	if (ACPI_FAILURE(status)) {
		vdbg_printk(LENSL_ERR,
			"Failed to get ACPI handle for %s\n", LENSL_LCDD);
		return -EIO;
	}

	status = get_bcl(&backlight_levels);
	if (status || !backlight_levels.count)
		goto err;

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,34)
	backlight = backlight_device_register(LENSL_BACKLIGHT_NAME,
			NULL, NULL, &lensl_backlight_ops, NULL);
#else
	backlight = backlight_device_register(LENSL_BACKLIGHT_NAME,
			NULL, NULL, &lensl_backlight_ops);
#endif
	backlight->props.max_brightness = backlight_levels.count - 1;
	backlight->props.brightness = lensl_bd_get_brightness(backlight);
	vdbg_printk(LENSL_INFO, "Started backlight brightness control\n");
	goto out;
err:
	if (backlight_levels.count) {
		kfree(backlight_levels.values);
		backlight_levels.count = 0;
	}
	vdbg_printk(LENSL_ERR,
		"Failed to start backlight brightness control\n");
out:
	return status;
}
static void hkey_poll_start(void)
{
	hkey_ec_prev_offset = 0;
	mutex_lock(&hkey_poll_mutex);
	hkey_poll_task = kthread_run(hkey_poll_kthread,
		NULL, LENSL_HKEY_POLL_KTHREAD_NAME);
	if (IS_ERR(hkey_poll_task)) {
		hkey_poll_task = NULL;
		vdbg_printk(LENSL_ERR,
			"Could not create kernel thread for hotkey polling\n");
	}
	mutex_unlock(&hkey_poll_mutex);
}
static int hwmon_init(void)
{
	int res;

	pwm1_value = -1;
	lensl_hwmon_device = hwmon_device_register(&lensl_pdev->dev);
	if (!lensl_hwmon_device) {
		vdbg_printk(LENSL_ERR, "Failed to register hwmon device\n");
		return -ENODEV;
	}

	res = sysfs_create_group(&lensl_hwmon_device->kobj,
				 &hwmon_attr_group);
	if (res < 0) {
		vdbg_printk(LENSL_ERR, "Failed to create hwmon sysfs group\n");
		hwmon_device_unregister(lensl_hwmon_device);
		lensl_hwmon_device = NULL;
		return -ENODEV;
	}
	vdbg_printk(LENSL_DEBUG, "Initialized hwmon subdriver\n");
	return 0;
}
static int lensl_acpi_int_func(acpi_handle handle, char *pathname, int *ret,
				int n_arg, ...)
{
	acpi_status status;
	struct acpi_object_list params;
	union acpi_object in_obj[LENSL_MAX_ACPI_ARGS], out_obj;
	struct acpi_buffer result, *resultp;
	int i;
	va_list ap;

	if (!handle)
		return -EINVAL;
	if (n_arg < 0 || n_arg > LENSL_MAX_ACPI_ARGS)
		return -EINVAL;
	va_start(ap, n_arg);
	for (i = 0; i < n_arg; i++) {
		in_obj[i].integer.value = va_arg(ap, int);
		in_obj[i].type = ACPI_TYPE_INTEGER;
	}
	va_end(ap);
	params.count = n_arg;
	params.pointer = in_obj;

	if (ret) {
		result.length = sizeof(out_obj);
		result.pointer = &out_obj;
		resultp = &result;
	} else
		resultp = NULL;

	status = acpi_evaluate_object(handle, pathname, &params, resultp);
	if (ACPI_FAILURE(status))
		return -EIO;
	if (ret)
		*ret = out_obj.integer.value;

	vdbg_printk(LENSL_DEBUG, "ACPI : %s(", pathname);
	if (dbg_level >= LENSL_DEBUG) {
		for (i = 0; i < n_arg; i++) {
			if (i)
				printk(", ");
			printk("%d", (int)in_obj[i].integer.value);
		}
		printk(")");
		if (ret)
			printk(" == %d", *ret);
		printk("\n");
	}
	return 0;
}
static void __exit lenovo_sl_laptop_exit(void)
{
	lenovo_sl_procfs_exit();
	hwmon_exit();
	hkey_poll_stop();
	led_exit();
	backlight_exit();
	bluetooth_exit();
	hkey_inputdev_exit();
	if (lensl_pdev)
		platform_device_unregister(lensl_pdev);
	destroy_workqueue(lensl_wq);
	vdbg_printk(LENSL_INFO, "Unloaded Lenovo ThinkPad SL Series driver\n");
}
static int lensl_radio_new_rfkill(struct lensl_radio *radio,
			struct rfkill **rfk, bool sw_blocked,
			bool hw_blocked)
{
	int res;

	*rfk = rfkill_allocate(&lensl_pdev->dev, radio->rfktype);
	if (!*rfk) {
		vdbg_printk(LENSL_ERR,
			"Failed to allocate memory for rfkill class\n");
		return -ENOMEM;
	}

	(*rfk)->name = radio->rfkname;
	(*rfk)->get_state = lensl_radio_rfkill_get_state;
	(*rfk)->toggle_radio = lensl_radio_rfkill_toggle_radio;
	(*rfk)->data = radio;

	if (hw_blocked)
		(*rfk)->state = RFKILL_STATE_HARD_BLOCKED;
	else if (sw_blocked)
		(*rfk)->state = RFKILL_STATE_SOFT_BLOCKED;
	else
		(*rfk)->state = RFKILL_STATE_UNBLOCKED;

	res = rfkill_register(*rfk);
	if (res < 0) {
		vdbg_printk(LENSL_ERR,
			"Failed to register %s rfkill switch: %d\n",
			radio->rfkname, res);
		rfkill_free(*rfk);
		*rfk = NULL;
		return res;
	}

	return 0;
}
static int bluetooth_init(void)
{
	int value, res;
	bluetooth_present = 0;
	if (!hkey_handle)
		return -ENODEV;
	if (get_gbdc(&value))
		return -EIO;
	if (!(value & TP_ACPI_BLUETOOTH_HWPRESENT))
		return -ENODEV;
	bluetooth_present = 1;

	res = sysfs_create_group(&lensl_pdev->dev.kobj,
				&bluetooth_attr_group);
	if (res) {
		vdbg_printk(LENSL_ERR,
			"Failed to register bluetooth sysfs group\n");
		return res;
	}

	bluetooth_pretend_blocked = !bluetooth_auto_enable;
	res = lensl_new_rfkill(LENSL_RFK_BLUETOOTH_SW_ID,
				&bluetooth_rfkill,
				RFKILL_TYPE_BLUETOOTH,
				"lensl_bluetooth_sw",
				bluetooth_rfk_set,
				bluetooth_rfk_get);
	bluetooth_pretend_blocked = 0;
	if (res) {
		bluetooth_exit();
		return res;
	}
	vdbg_printk(LENSL_DEBUG, "Initialized bluetooth subdriver\n");

	return 0;
}
static int lensl_new_rfkill(const unsigned int id,
			struct rfkill **rfk,
			const enum rfkill_type rfktype,
			const char *name,
			int (*toggle_radio)(void *, enum rfkill_state),
			int (*get_state)(void *, enum rfkill_state *))
{
	int res;
	enum rfkill_state initial_state;

	*rfk = rfkill_allocate(&lensl_pdev->dev, rfktype);
	if (!*rfk) {
		vdbg_printk(LENSL_ERR,
			"Failed to allocate memory for rfkill class\n");
		return -ENOMEM;
	}

	(*rfk)->name = name;
	(*rfk)->get_state = get_state;
	(*rfk)->toggle_radio = toggle_radio;

	if (!get_state(NULL, &initial_state))
		(*rfk)->state = initial_state;

	res = rfkill_register(*rfk);
	if (res < 0) {
		vdbg_printk(LENSL_ERR,
			"Failed to register %s rfkill switch: %d\n",
			name, res);
		rfkill_free(*rfk);
		*rfk = NULL;
		return res;
	}

	return 0;
}
static int lenovo_sl_procfs_init(void)
{
	struct proc_dir_entry *proc_ec;

	proc_dir = proc_mkdir(LENSL_PROC_DIRNAME, acpi_root_dir);
	if (!proc_dir) {
		vdbg_printk(LENSL_ERR,
		   "Failed to create proc dir acpi/%s/\n", LENSL_PROC_DIRNAME);
		return -ENOENT;
	}
	proc_dir->owner = THIS_MODULE;
	proc_ec = create_proc_entry(LENSL_PROC_EC, 0600, proc_dir);
	if (!proc_ec) {
		vdbg_printk(LENSL_ERR,
			"Failed to create proc entry acpi/%s/%s\n",
			LENSL_PROC_DIRNAME, LENSL_PROC_EC);
		return -ENOENT;
	}
	proc_ec->read_proc = lensl_ec_read_procmem;
	proc_ec->write_proc = lensl_ec_write_procmem;
	vdbg_printk(LENSL_DEBUG, "Initialized procfs debugging interface\n");

	return 0;
}
static int radio_init(lensl_radio_type type)
{
	int value, res, hw_blocked = 0, sw_blocked;

	if (!hkey_handle)
		return -ENODEV;
	lensl_radios[type].present = 1; /* need for lensl_radio_get */
	res = lensl_radio_get(&lensl_radios[type], &hw_blocked, &value);
	lensl_radios[type].present = 0;
	if (res && !hw_blocked)
		return -EIO;
	if (!(value & LENSL_RADIO_HWPRESENT))
		return -ENODEV;
	lensl_radios[type].present = 1;

	if (*lensl_radios[type].auto_enable) {
		sw_blocked = 0;
		value |= LENSL_RADIO_RADIOSSW;
		lensl_radios[type].set_acpi(value);
	} else {
		sw_blocked = 1;
		value &= ~LENSL_RADIO_RADIOSSW;
		lensl_radios[type].set_acpi(value);
	}

	res = lensl_radio_new_rfkill(&lensl_radios[type], &lensl_radios[type].rfk,
					sw_blocked, hw_blocked);

	if (res) {
		radio_exit(type);
		return res;
	}
	vdbg_printk(LENSL_DEBUG, "Initialized %s subdriver\n",
		lensl_radios[type].name);

	return 0;
}
static int hkey_poll_kthread(void *data)
{
	unsigned long t = 0;
	int offset, level;
	unsigned int keycode;
	u8 scancode;

	mutex_lock(&hkey_poll_mutex);

	offset = hkey_ec_get_offset();
	if (offset < 0) {
		vdbg_printk(LENSL_WARNING,
			"Failed to read hotkey register offset from EC\n");
		hkey_ec_prev_offset = 0;
	} else
		hkey_ec_prev_offset = offset;

	while (!kthread_should_stop() && hkey_poll_hz) {
		if (t == 0)
			t = 1000/hkey_poll_hz;
		t = msleep_interruptible(t);
		if (unlikely(kthread_should_stop()))
			break;
		try_to_freeze();
		if (t > 0)
			continue;
		offset = hkey_ec_get_offset();
		if (offset < 0) {
			vdbg_printk(LENSL_WARNING,
			   "Failed to read hotkey register offset from EC\n");
			continue;
		}
		if (offset == hkey_ec_prev_offset)
			continue;

		if (ec_read(0x0A + offset, &scancode)) {
			vdbg_printk(LENSL_WARNING,
				"Failed to read hotkey code from EC\n");
			continue;
		}
		keycode = ec_scancode_to_keycode(scancode);
		vdbg_printk(LENSL_DEBUG,
		   "Got hotkey keycode %d (scancode %d)\n", keycode, scancode);

		/* Special handling for brightness keys. We do it here and not
		   via an ACPI notifier in order to prevent possible conflicts
		   with video.c */
		if (keycode == KEY_BRIGHTNESSDOWN) {
			if (control_backlight && backlight) {
				level = lensl_bd_get_brightness(backlight);
				if (0 <= --level)
					lensl_bd_set_brightness_int(level);
			} else
				keycode = KEY_RESERVED;
		} else if (keycode == KEY_BRIGHTNESSUP) {
			if (control_backlight && backlight) {
				level = lensl_bd_get_brightness(backlight);
				if (backlight_levels.count > ++level)
					lensl_bd_set_brightness_int(level);
			} else
				keycode = KEY_RESERVED;
		}

		if (keycode != KEY_RESERVED) {
			input_report_key(hkey_inputdev, keycode, 1);
			input_sync(hkey_inputdev);
			input_report_key(hkey_inputdev, keycode, 0);
			input_sync(hkey_inputdev);
		}
		hkey_ec_prev_offset = offset;
	}

	mutex_unlock(&hkey_poll_mutex);
	return 0;
}
static int __init lenovo_sl_laptop_init(void)
{
	int ret;
	acpi_status status;

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,28)
	if (!acpi_video_backlight_support())
		control_backlight = 1;
#endif

	hkey_handle = ec0_handle = NULL;

	if (acpi_disabled)
		return -ENODEV;

	lensl_wq = create_singlethread_workqueue(LENSL_WORKQUEUE_NAME);
	if (!lensl_wq) {
		vdbg_printk(LENSL_ERR, "Failed to create a workqueue\n");
		return -ENOMEM;
	}

	status = acpi_get_handle(NULL, LENSL_HKEY, &hkey_handle);
	if (ACPI_FAILURE(status)) {
		vdbg_printk(LENSL_ERR,
			"Failed to get ACPI handle for %s\n", LENSL_HKEY);
		return -ENODEV;
	}
	status = acpi_get_handle(NULL, LENSL_EC0, &ec0_handle);
	if (ACPI_FAILURE(status)) {
		vdbg_printk(LENSL_ERR,
			"Failed to get ACPI handle for %s\n", LENSL_EC0);
		return -ENODEV;
	}

	lensl_pdev = platform_device_register_simple(LENSL_DRVR_NAME, -1,
							NULL, 0);
	if (IS_ERR(lensl_pdev)) {
		ret = PTR_ERR(lensl_pdev);
		lensl_pdev = NULL;
		vdbg_printk(LENSL_ERR, "Failed to register platform device\n");
		return ret;
	}

	ret = hkey_inputdev_init();
	if (ret)
		return -ENODEV;

	bluetooth_init();
	if (control_backlight)
		backlight_init();

	led_init();
	mutex_init(&hkey_poll_mutex);
	hkey_poll_start();
	hwmon_init();

	if (debug_ec)
		lenovo_sl_procfs_init();

	vdbg_printk(LENSL_INFO, "Loaded Lenovo ThinkPad SL Series driver\n");
	return 0;
}