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, ¶ms, 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; }