18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+ 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * HID driver for Google Hammer device. 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright (c) 2017 Google Inc. 68c2ecf20Sopenharmony_ci * Author: Wei-Ning Huang <wnhuang@google.com> 78c2ecf20Sopenharmony_ci */ 88c2ecf20Sopenharmony_ci 98c2ecf20Sopenharmony_ci/* 108c2ecf20Sopenharmony_ci * This program is free software; you can redistribute it and/or modify it 118c2ecf20Sopenharmony_ci * under the terms of the GNU General Public License as published by the Free 128c2ecf20Sopenharmony_ci * Software Foundation; either version 2 of the License, or (at your option) 138c2ecf20Sopenharmony_ci * any later version. 148c2ecf20Sopenharmony_ci */ 158c2ecf20Sopenharmony_ci 168c2ecf20Sopenharmony_ci#include <linux/acpi.h> 178c2ecf20Sopenharmony_ci#include <linux/hid.h> 188c2ecf20Sopenharmony_ci#include <linux/leds.h> 198c2ecf20Sopenharmony_ci#include <linux/module.h> 208c2ecf20Sopenharmony_ci#include <linux/platform_data/cros_ec_commands.h> 218c2ecf20Sopenharmony_ci#include <linux/platform_data/cros_ec_proto.h> 228c2ecf20Sopenharmony_ci#include <linux/platform_device.h> 238c2ecf20Sopenharmony_ci#include <linux/pm_wakeup.h> 248c2ecf20Sopenharmony_ci#include <asm/unaligned.h> 258c2ecf20Sopenharmony_ci 268c2ecf20Sopenharmony_ci#include "hid-ids.h" 278c2ecf20Sopenharmony_ci 288c2ecf20Sopenharmony_ci/* 298c2ecf20Sopenharmony_ci * C(hrome)B(ase)A(ttached)S(witch) - switch exported by Chrome EC and reporting 308c2ecf20Sopenharmony_ci * state of the "Whiskers" base - attached or detached. Whiskers USB device also 318c2ecf20Sopenharmony_ci * reports position of the keyboard - folded or not. Combining base state and 328c2ecf20Sopenharmony_ci * position allows us to generate proper "Tablet mode" events. 338c2ecf20Sopenharmony_ci */ 348c2ecf20Sopenharmony_cistruct cbas_ec { 358c2ecf20Sopenharmony_ci struct device *dev; /* The platform device (EC) */ 368c2ecf20Sopenharmony_ci struct input_dev *input; 378c2ecf20Sopenharmony_ci bool base_present; 388c2ecf20Sopenharmony_ci bool base_folded; 398c2ecf20Sopenharmony_ci struct notifier_block notifier; 408c2ecf20Sopenharmony_ci}; 418c2ecf20Sopenharmony_ci 428c2ecf20Sopenharmony_cistatic struct cbas_ec cbas_ec; 438c2ecf20Sopenharmony_cistatic DEFINE_SPINLOCK(cbas_ec_lock); 448c2ecf20Sopenharmony_cistatic DEFINE_MUTEX(cbas_ec_reglock); 458c2ecf20Sopenharmony_ci 468c2ecf20Sopenharmony_cistatic bool cbas_parse_base_state(const void *data) 478c2ecf20Sopenharmony_ci{ 488c2ecf20Sopenharmony_ci u32 switches = get_unaligned_le32(data); 498c2ecf20Sopenharmony_ci 508c2ecf20Sopenharmony_ci return !!(switches & BIT(EC_MKBP_BASE_ATTACHED)); 518c2ecf20Sopenharmony_ci} 528c2ecf20Sopenharmony_ci 538c2ecf20Sopenharmony_cistatic int cbas_ec_query_base(struct cros_ec_device *ec_dev, bool get_state, 548c2ecf20Sopenharmony_ci bool *state) 558c2ecf20Sopenharmony_ci{ 568c2ecf20Sopenharmony_ci struct ec_params_mkbp_info *params; 578c2ecf20Sopenharmony_ci struct cros_ec_command *msg; 588c2ecf20Sopenharmony_ci int ret; 598c2ecf20Sopenharmony_ci 608c2ecf20Sopenharmony_ci msg = kzalloc(sizeof(*msg) + max(sizeof(u32), sizeof(*params)), 618c2ecf20Sopenharmony_ci GFP_KERNEL); 628c2ecf20Sopenharmony_ci if (!msg) 638c2ecf20Sopenharmony_ci return -ENOMEM; 648c2ecf20Sopenharmony_ci 658c2ecf20Sopenharmony_ci msg->command = EC_CMD_MKBP_INFO; 668c2ecf20Sopenharmony_ci msg->version = 1; 678c2ecf20Sopenharmony_ci msg->outsize = sizeof(*params); 688c2ecf20Sopenharmony_ci msg->insize = sizeof(u32); 698c2ecf20Sopenharmony_ci params = (struct ec_params_mkbp_info *)msg->data; 708c2ecf20Sopenharmony_ci params->info_type = get_state ? 718c2ecf20Sopenharmony_ci EC_MKBP_INFO_CURRENT : EC_MKBP_INFO_SUPPORTED; 728c2ecf20Sopenharmony_ci params->event_type = EC_MKBP_EVENT_SWITCH; 738c2ecf20Sopenharmony_ci 748c2ecf20Sopenharmony_ci ret = cros_ec_cmd_xfer_status(ec_dev, msg); 758c2ecf20Sopenharmony_ci if (ret >= 0) { 768c2ecf20Sopenharmony_ci if (ret != sizeof(u32)) { 778c2ecf20Sopenharmony_ci dev_warn(ec_dev->dev, "wrong result size: %d != %zu\n", 788c2ecf20Sopenharmony_ci ret, sizeof(u32)); 798c2ecf20Sopenharmony_ci ret = -EPROTO; 808c2ecf20Sopenharmony_ci } else { 818c2ecf20Sopenharmony_ci *state = cbas_parse_base_state(msg->data); 828c2ecf20Sopenharmony_ci ret = 0; 838c2ecf20Sopenharmony_ci } 848c2ecf20Sopenharmony_ci } 858c2ecf20Sopenharmony_ci 868c2ecf20Sopenharmony_ci kfree(msg); 878c2ecf20Sopenharmony_ci 888c2ecf20Sopenharmony_ci return ret; 898c2ecf20Sopenharmony_ci} 908c2ecf20Sopenharmony_ci 918c2ecf20Sopenharmony_cistatic int cbas_ec_notify(struct notifier_block *nb, 928c2ecf20Sopenharmony_ci unsigned long queued_during_suspend, 938c2ecf20Sopenharmony_ci void *_notify) 948c2ecf20Sopenharmony_ci{ 958c2ecf20Sopenharmony_ci struct cros_ec_device *ec = _notify; 968c2ecf20Sopenharmony_ci unsigned long flags; 978c2ecf20Sopenharmony_ci bool base_present; 988c2ecf20Sopenharmony_ci 998c2ecf20Sopenharmony_ci if (ec->event_data.event_type == EC_MKBP_EVENT_SWITCH) { 1008c2ecf20Sopenharmony_ci base_present = cbas_parse_base_state( 1018c2ecf20Sopenharmony_ci &ec->event_data.data.switches); 1028c2ecf20Sopenharmony_ci dev_dbg(cbas_ec.dev, 1038c2ecf20Sopenharmony_ci "%s: base: %d\n", __func__, base_present); 1048c2ecf20Sopenharmony_ci 1058c2ecf20Sopenharmony_ci if (device_may_wakeup(cbas_ec.dev) || 1068c2ecf20Sopenharmony_ci !queued_during_suspend) { 1078c2ecf20Sopenharmony_ci 1088c2ecf20Sopenharmony_ci pm_wakeup_event(cbas_ec.dev, 0); 1098c2ecf20Sopenharmony_ci 1108c2ecf20Sopenharmony_ci spin_lock_irqsave(&cbas_ec_lock, flags); 1118c2ecf20Sopenharmony_ci 1128c2ecf20Sopenharmony_ci /* 1138c2ecf20Sopenharmony_ci * While input layer dedupes the events, we do not want 1148c2ecf20Sopenharmony_ci * to disrupt the state reported by the base by 1158c2ecf20Sopenharmony_ci * overriding it with state reported by the LID. Only 1168c2ecf20Sopenharmony_ci * report changes, as we assume that on attach the base 1178c2ecf20Sopenharmony_ci * is not folded. 1188c2ecf20Sopenharmony_ci */ 1198c2ecf20Sopenharmony_ci if (base_present != cbas_ec.base_present) { 1208c2ecf20Sopenharmony_ci input_report_switch(cbas_ec.input, 1218c2ecf20Sopenharmony_ci SW_TABLET_MODE, 1228c2ecf20Sopenharmony_ci !base_present); 1238c2ecf20Sopenharmony_ci input_sync(cbas_ec.input); 1248c2ecf20Sopenharmony_ci cbas_ec.base_present = base_present; 1258c2ecf20Sopenharmony_ci } 1268c2ecf20Sopenharmony_ci 1278c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&cbas_ec_lock, flags); 1288c2ecf20Sopenharmony_ci } 1298c2ecf20Sopenharmony_ci } 1308c2ecf20Sopenharmony_ci 1318c2ecf20Sopenharmony_ci return NOTIFY_OK; 1328c2ecf20Sopenharmony_ci} 1338c2ecf20Sopenharmony_ci 1348c2ecf20Sopenharmony_cistatic __maybe_unused int cbas_ec_resume(struct device *dev) 1358c2ecf20Sopenharmony_ci{ 1368c2ecf20Sopenharmony_ci struct cros_ec_device *ec = dev_get_drvdata(dev->parent); 1378c2ecf20Sopenharmony_ci bool base_present; 1388c2ecf20Sopenharmony_ci int error; 1398c2ecf20Sopenharmony_ci 1408c2ecf20Sopenharmony_ci error = cbas_ec_query_base(ec, true, &base_present); 1418c2ecf20Sopenharmony_ci if (error) { 1428c2ecf20Sopenharmony_ci dev_warn(dev, "failed to fetch base state on resume: %d\n", 1438c2ecf20Sopenharmony_ci error); 1448c2ecf20Sopenharmony_ci } else { 1458c2ecf20Sopenharmony_ci spin_lock_irq(&cbas_ec_lock); 1468c2ecf20Sopenharmony_ci 1478c2ecf20Sopenharmony_ci cbas_ec.base_present = base_present; 1488c2ecf20Sopenharmony_ci 1498c2ecf20Sopenharmony_ci /* 1508c2ecf20Sopenharmony_ci * Only report if base is disconnected. If base is connected, 1518c2ecf20Sopenharmony_ci * it will resend its state on resume, and we'll update it 1528c2ecf20Sopenharmony_ci * in hammer_event(). 1538c2ecf20Sopenharmony_ci */ 1548c2ecf20Sopenharmony_ci if (!cbas_ec.base_present) { 1558c2ecf20Sopenharmony_ci input_report_switch(cbas_ec.input, SW_TABLET_MODE, 1); 1568c2ecf20Sopenharmony_ci input_sync(cbas_ec.input); 1578c2ecf20Sopenharmony_ci } 1588c2ecf20Sopenharmony_ci 1598c2ecf20Sopenharmony_ci spin_unlock_irq(&cbas_ec_lock); 1608c2ecf20Sopenharmony_ci } 1618c2ecf20Sopenharmony_ci 1628c2ecf20Sopenharmony_ci return 0; 1638c2ecf20Sopenharmony_ci} 1648c2ecf20Sopenharmony_ci 1658c2ecf20Sopenharmony_cistatic SIMPLE_DEV_PM_OPS(cbas_ec_pm_ops, NULL, cbas_ec_resume); 1668c2ecf20Sopenharmony_ci 1678c2ecf20Sopenharmony_cistatic void cbas_ec_set_input(struct input_dev *input) 1688c2ecf20Sopenharmony_ci{ 1698c2ecf20Sopenharmony_ci /* Take the lock so hammer_event() does not race with us here */ 1708c2ecf20Sopenharmony_ci spin_lock_irq(&cbas_ec_lock); 1718c2ecf20Sopenharmony_ci cbas_ec.input = input; 1728c2ecf20Sopenharmony_ci spin_unlock_irq(&cbas_ec_lock); 1738c2ecf20Sopenharmony_ci} 1748c2ecf20Sopenharmony_ci 1758c2ecf20Sopenharmony_cistatic int __cbas_ec_probe(struct platform_device *pdev) 1768c2ecf20Sopenharmony_ci{ 1778c2ecf20Sopenharmony_ci struct cros_ec_device *ec = dev_get_drvdata(pdev->dev.parent); 1788c2ecf20Sopenharmony_ci struct input_dev *input; 1798c2ecf20Sopenharmony_ci bool base_supported; 1808c2ecf20Sopenharmony_ci int error; 1818c2ecf20Sopenharmony_ci 1828c2ecf20Sopenharmony_ci error = cbas_ec_query_base(ec, false, &base_supported); 1838c2ecf20Sopenharmony_ci if (error) 1848c2ecf20Sopenharmony_ci return error; 1858c2ecf20Sopenharmony_ci 1868c2ecf20Sopenharmony_ci if (!base_supported) 1878c2ecf20Sopenharmony_ci return -ENXIO; 1888c2ecf20Sopenharmony_ci 1898c2ecf20Sopenharmony_ci input = devm_input_allocate_device(&pdev->dev); 1908c2ecf20Sopenharmony_ci if (!input) 1918c2ecf20Sopenharmony_ci return -ENOMEM; 1928c2ecf20Sopenharmony_ci 1938c2ecf20Sopenharmony_ci input->name = "Whiskers Tablet Mode Switch"; 1948c2ecf20Sopenharmony_ci input->id.bustype = BUS_HOST; 1958c2ecf20Sopenharmony_ci 1968c2ecf20Sopenharmony_ci input_set_capability(input, EV_SW, SW_TABLET_MODE); 1978c2ecf20Sopenharmony_ci 1988c2ecf20Sopenharmony_ci error = input_register_device(input); 1998c2ecf20Sopenharmony_ci if (error) { 2008c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "cannot register input device: %d\n", 2018c2ecf20Sopenharmony_ci error); 2028c2ecf20Sopenharmony_ci return error; 2038c2ecf20Sopenharmony_ci } 2048c2ecf20Sopenharmony_ci 2058c2ecf20Sopenharmony_ci /* Seed the state */ 2068c2ecf20Sopenharmony_ci error = cbas_ec_query_base(ec, true, &cbas_ec.base_present); 2078c2ecf20Sopenharmony_ci if (error) { 2088c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "cannot query base state: %d\n", error); 2098c2ecf20Sopenharmony_ci return error; 2108c2ecf20Sopenharmony_ci } 2118c2ecf20Sopenharmony_ci 2128c2ecf20Sopenharmony_ci if (!cbas_ec.base_present) 2138c2ecf20Sopenharmony_ci cbas_ec.base_folded = false; 2148c2ecf20Sopenharmony_ci 2158c2ecf20Sopenharmony_ci dev_dbg(&pdev->dev, "%s: base: %d, folded: %d\n", __func__, 2168c2ecf20Sopenharmony_ci cbas_ec.base_present, cbas_ec.base_folded); 2178c2ecf20Sopenharmony_ci 2188c2ecf20Sopenharmony_ci input_report_switch(input, SW_TABLET_MODE, 2198c2ecf20Sopenharmony_ci !cbas_ec.base_present || cbas_ec.base_folded); 2208c2ecf20Sopenharmony_ci 2218c2ecf20Sopenharmony_ci cbas_ec_set_input(input); 2228c2ecf20Sopenharmony_ci 2238c2ecf20Sopenharmony_ci cbas_ec.dev = &pdev->dev; 2248c2ecf20Sopenharmony_ci cbas_ec.notifier.notifier_call = cbas_ec_notify; 2258c2ecf20Sopenharmony_ci error = blocking_notifier_chain_register(&ec->event_notifier, 2268c2ecf20Sopenharmony_ci &cbas_ec.notifier); 2278c2ecf20Sopenharmony_ci if (error) { 2288c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "cannot register notifier: %d\n", error); 2298c2ecf20Sopenharmony_ci cbas_ec_set_input(NULL); 2308c2ecf20Sopenharmony_ci return error; 2318c2ecf20Sopenharmony_ci } 2328c2ecf20Sopenharmony_ci 2338c2ecf20Sopenharmony_ci device_init_wakeup(&pdev->dev, true); 2348c2ecf20Sopenharmony_ci return 0; 2358c2ecf20Sopenharmony_ci} 2368c2ecf20Sopenharmony_ci 2378c2ecf20Sopenharmony_cistatic int cbas_ec_probe(struct platform_device *pdev) 2388c2ecf20Sopenharmony_ci{ 2398c2ecf20Sopenharmony_ci int retval; 2408c2ecf20Sopenharmony_ci 2418c2ecf20Sopenharmony_ci mutex_lock(&cbas_ec_reglock); 2428c2ecf20Sopenharmony_ci 2438c2ecf20Sopenharmony_ci if (cbas_ec.input) { 2448c2ecf20Sopenharmony_ci retval = -EBUSY; 2458c2ecf20Sopenharmony_ci goto out; 2468c2ecf20Sopenharmony_ci } 2478c2ecf20Sopenharmony_ci 2488c2ecf20Sopenharmony_ci retval = __cbas_ec_probe(pdev); 2498c2ecf20Sopenharmony_ci 2508c2ecf20Sopenharmony_ciout: 2518c2ecf20Sopenharmony_ci mutex_unlock(&cbas_ec_reglock); 2528c2ecf20Sopenharmony_ci return retval; 2538c2ecf20Sopenharmony_ci} 2548c2ecf20Sopenharmony_ci 2558c2ecf20Sopenharmony_cistatic int cbas_ec_remove(struct platform_device *pdev) 2568c2ecf20Sopenharmony_ci{ 2578c2ecf20Sopenharmony_ci struct cros_ec_device *ec = dev_get_drvdata(pdev->dev.parent); 2588c2ecf20Sopenharmony_ci 2598c2ecf20Sopenharmony_ci mutex_lock(&cbas_ec_reglock); 2608c2ecf20Sopenharmony_ci 2618c2ecf20Sopenharmony_ci blocking_notifier_chain_unregister(&ec->event_notifier, 2628c2ecf20Sopenharmony_ci &cbas_ec.notifier); 2638c2ecf20Sopenharmony_ci cbas_ec_set_input(NULL); 2648c2ecf20Sopenharmony_ci 2658c2ecf20Sopenharmony_ci mutex_unlock(&cbas_ec_reglock); 2668c2ecf20Sopenharmony_ci return 0; 2678c2ecf20Sopenharmony_ci} 2688c2ecf20Sopenharmony_ci 2698c2ecf20Sopenharmony_cistatic const struct acpi_device_id cbas_ec_acpi_ids[] = { 2708c2ecf20Sopenharmony_ci { "GOOG000B", 0 }, 2718c2ecf20Sopenharmony_ci { } 2728c2ecf20Sopenharmony_ci}; 2738c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(acpi, cbas_ec_acpi_ids); 2748c2ecf20Sopenharmony_ci 2758c2ecf20Sopenharmony_cistatic struct platform_driver cbas_ec_driver = { 2768c2ecf20Sopenharmony_ci .probe = cbas_ec_probe, 2778c2ecf20Sopenharmony_ci .remove = cbas_ec_remove, 2788c2ecf20Sopenharmony_ci .driver = { 2798c2ecf20Sopenharmony_ci .name = "cbas_ec", 2808c2ecf20Sopenharmony_ci .acpi_match_table = ACPI_PTR(cbas_ec_acpi_ids), 2818c2ecf20Sopenharmony_ci .pm = &cbas_ec_pm_ops, 2828c2ecf20Sopenharmony_ci }, 2838c2ecf20Sopenharmony_ci}; 2848c2ecf20Sopenharmony_ci 2858c2ecf20Sopenharmony_ci#define MAX_BRIGHTNESS 100 2868c2ecf20Sopenharmony_ci 2878c2ecf20Sopenharmony_cistruct hammer_kbd_leds { 2888c2ecf20Sopenharmony_ci struct led_classdev cdev; 2898c2ecf20Sopenharmony_ci struct hid_device *hdev; 2908c2ecf20Sopenharmony_ci u8 buf[2] ____cacheline_aligned; 2918c2ecf20Sopenharmony_ci}; 2928c2ecf20Sopenharmony_ci 2938c2ecf20Sopenharmony_cistatic int hammer_kbd_brightness_set_blocking(struct led_classdev *cdev, 2948c2ecf20Sopenharmony_ci enum led_brightness br) 2958c2ecf20Sopenharmony_ci{ 2968c2ecf20Sopenharmony_ci struct hammer_kbd_leds *led = container_of(cdev, 2978c2ecf20Sopenharmony_ci struct hammer_kbd_leds, 2988c2ecf20Sopenharmony_ci cdev); 2998c2ecf20Sopenharmony_ci int ret; 3008c2ecf20Sopenharmony_ci 3018c2ecf20Sopenharmony_ci led->buf[0] = 0; 3028c2ecf20Sopenharmony_ci led->buf[1] = br; 3038c2ecf20Sopenharmony_ci 3048c2ecf20Sopenharmony_ci /* 3058c2ecf20Sopenharmony_ci * Request USB HID device to be in Full On mode, so that sending 3068c2ecf20Sopenharmony_ci * hardware output report and hardware raw request won't fail. 3078c2ecf20Sopenharmony_ci */ 3088c2ecf20Sopenharmony_ci ret = hid_hw_power(led->hdev, PM_HINT_FULLON); 3098c2ecf20Sopenharmony_ci if (ret < 0) { 3108c2ecf20Sopenharmony_ci hid_err(led->hdev, "failed: device not resumed %d\n", ret); 3118c2ecf20Sopenharmony_ci return ret; 3128c2ecf20Sopenharmony_ci } 3138c2ecf20Sopenharmony_ci 3148c2ecf20Sopenharmony_ci ret = hid_hw_output_report(led->hdev, led->buf, sizeof(led->buf)); 3158c2ecf20Sopenharmony_ci if (ret == -ENOSYS) 3168c2ecf20Sopenharmony_ci ret = hid_hw_raw_request(led->hdev, 0, led->buf, 3178c2ecf20Sopenharmony_ci sizeof(led->buf), 3188c2ecf20Sopenharmony_ci HID_OUTPUT_REPORT, 3198c2ecf20Sopenharmony_ci HID_REQ_SET_REPORT); 3208c2ecf20Sopenharmony_ci if (ret < 0) 3218c2ecf20Sopenharmony_ci hid_err(led->hdev, "failed to set keyboard backlight: %d\n", 3228c2ecf20Sopenharmony_ci ret); 3238c2ecf20Sopenharmony_ci 3248c2ecf20Sopenharmony_ci /* Request USB HID device back to Normal Mode. */ 3258c2ecf20Sopenharmony_ci hid_hw_power(led->hdev, PM_HINT_NORMAL); 3268c2ecf20Sopenharmony_ci 3278c2ecf20Sopenharmony_ci return ret; 3288c2ecf20Sopenharmony_ci} 3298c2ecf20Sopenharmony_ci 3308c2ecf20Sopenharmony_cistatic int hammer_register_leds(struct hid_device *hdev) 3318c2ecf20Sopenharmony_ci{ 3328c2ecf20Sopenharmony_ci struct hammer_kbd_leds *kbd_backlight; 3338c2ecf20Sopenharmony_ci int error; 3348c2ecf20Sopenharmony_ci 3358c2ecf20Sopenharmony_ci kbd_backlight = kzalloc(sizeof(*kbd_backlight), GFP_KERNEL); 3368c2ecf20Sopenharmony_ci if (!kbd_backlight) 3378c2ecf20Sopenharmony_ci return -ENOMEM; 3388c2ecf20Sopenharmony_ci 3398c2ecf20Sopenharmony_ci kbd_backlight->hdev = hdev; 3408c2ecf20Sopenharmony_ci kbd_backlight->cdev.name = "hammer::kbd_backlight"; 3418c2ecf20Sopenharmony_ci kbd_backlight->cdev.max_brightness = MAX_BRIGHTNESS; 3428c2ecf20Sopenharmony_ci kbd_backlight->cdev.brightness_set_blocking = 3438c2ecf20Sopenharmony_ci hammer_kbd_brightness_set_blocking; 3448c2ecf20Sopenharmony_ci kbd_backlight->cdev.flags = LED_HW_PLUGGABLE; 3458c2ecf20Sopenharmony_ci 3468c2ecf20Sopenharmony_ci /* Set backlight to 0% initially. */ 3478c2ecf20Sopenharmony_ci hammer_kbd_brightness_set_blocking(&kbd_backlight->cdev, 0); 3488c2ecf20Sopenharmony_ci 3498c2ecf20Sopenharmony_ci error = led_classdev_register(&hdev->dev, &kbd_backlight->cdev); 3508c2ecf20Sopenharmony_ci if (error) 3518c2ecf20Sopenharmony_ci goto err_free_mem; 3528c2ecf20Sopenharmony_ci 3538c2ecf20Sopenharmony_ci hid_set_drvdata(hdev, kbd_backlight); 3548c2ecf20Sopenharmony_ci return 0; 3558c2ecf20Sopenharmony_ci 3568c2ecf20Sopenharmony_cierr_free_mem: 3578c2ecf20Sopenharmony_ci kfree(kbd_backlight); 3588c2ecf20Sopenharmony_ci return error; 3598c2ecf20Sopenharmony_ci} 3608c2ecf20Sopenharmony_ci 3618c2ecf20Sopenharmony_cistatic void hammer_unregister_leds(struct hid_device *hdev) 3628c2ecf20Sopenharmony_ci{ 3638c2ecf20Sopenharmony_ci struct hammer_kbd_leds *kbd_backlight = hid_get_drvdata(hdev); 3648c2ecf20Sopenharmony_ci 3658c2ecf20Sopenharmony_ci if (kbd_backlight) { 3668c2ecf20Sopenharmony_ci led_classdev_unregister(&kbd_backlight->cdev); 3678c2ecf20Sopenharmony_ci kfree(kbd_backlight); 3688c2ecf20Sopenharmony_ci } 3698c2ecf20Sopenharmony_ci} 3708c2ecf20Sopenharmony_ci 3718c2ecf20Sopenharmony_ci#define HID_UP_GOOGLEVENDOR 0xffd10000 3728c2ecf20Sopenharmony_ci#define HID_VD_KBD_FOLDED 0x00000019 3738c2ecf20Sopenharmony_ci#define HID_USAGE_KBD_FOLDED (HID_UP_GOOGLEVENDOR | HID_VD_KBD_FOLDED) 3748c2ecf20Sopenharmony_ci 3758c2ecf20Sopenharmony_ci/* HID usage for keyboard backlight (Alphanumeric display brightness) */ 3768c2ecf20Sopenharmony_ci#define HID_AD_BRIGHTNESS 0x00140046 3778c2ecf20Sopenharmony_ci 3788c2ecf20Sopenharmony_cistatic int hammer_input_mapping(struct hid_device *hdev, struct hid_input *hi, 3798c2ecf20Sopenharmony_ci struct hid_field *field, 3808c2ecf20Sopenharmony_ci struct hid_usage *usage, 3818c2ecf20Sopenharmony_ci unsigned long **bit, int *max) 3828c2ecf20Sopenharmony_ci{ 3838c2ecf20Sopenharmony_ci if (usage->hid == HID_USAGE_KBD_FOLDED) { 3848c2ecf20Sopenharmony_ci /* 3858c2ecf20Sopenharmony_ci * We do not want to have this usage mapped as it will get 3868c2ecf20Sopenharmony_ci * mixed in with "base attached" signal and delivered over 3878c2ecf20Sopenharmony_ci * separate input device for tablet switch mode. 3888c2ecf20Sopenharmony_ci */ 3898c2ecf20Sopenharmony_ci return -1; 3908c2ecf20Sopenharmony_ci } 3918c2ecf20Sopenharmony_ci 3928c2ecf20Sopenharmony_ci return 0; 3938c2ecf20Sopenharmony_ci} 3948c2ecf20Sopenharmony_ci 3958c2ecf20Sopenharmony_cistatic int hammer_event(struct hid_device *hid, struct hid_field *field, 3968c2ecf20Sopenharmony_ci struct hid_usage *usage, __s32 value) 3978c2ecf20Sopenharmony_ci{ 3988c2ecf20Sopenharmony_ci unsigned long flags; 3998c2ecf20Sopenharmony_ci 4008c2ecf20Sopenharmony_ci if (usage->hid == HID_USAGE_KBD_FOLDED) { 4018c2ecf20Sopenharmony_ci spin_lock_irqsave(&cbas_ec_lock, flags); 4028c2ecf20Sopenharmony_ci 4038c2ecf20Sopenharmony_ci /* 4048c2ecf20Sopenharmony_ci * If we are getting events from Whiskers that means that it 4058c2ecf20Sopenharmony_ci * is attached to the lid. 4068c2ecf20Sopenharmony_ci */ 4078c2ecf20Sopenharmony_ci cbas_ec.base_present = true; 4088c2ecf20Sopenharmony_ci cbas_ec.base_folded = value; 4098c2ecf20Sopenharmony_ci hid_dbg(hid, "%s: base: %d, folded: %d\n", __func__, 4108c2ecf20Sopenharmony_ci cbas_ec.base_present, cbas_ec.base_folded); 4118c2ecf20Sopenharmony_ci 4128c2ecf20Sopenharmony_ci if (cbas_ec.input) { 4138c2ecf20Sopenharmony_ci input_report_switch(cbas_ec.input, 4148c2ecf20Sopenharmony_ci SW_TABLET_MODE, value); 4158c2ecf20Sopenharmony_ci input_sync(cbas_ec.input); 4168c2ecf20Sopenharmony_ci } 4178c2ecf20Sopenharmony_ci 4188c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&cbas_ec_lock, flags); 4198c2ecf20Sopenharmony_ci return 1; /* We handled this event */ 4208c2ecf20Sopenharmony_ci } 4218c2ecf20Sopenharmony_ci 4228c2ecf20Sopenharmony_ci return 0; 4238c2ecf20Sopenharmony_ci} 4248c2ecf20Sopenharmony_ci 4258c2ecf20Sopenharmony_cistatic bool hammer_has_usage(struct hid_device *hdev, unsigned int report_type, 4268c2ecf20Sopenharmony_ci unsigned application, unsigned usage) 4278c2ecf20Sopenharmony_ci{ 4288c2ecf20Sopenharmony_ci struct hid_report_enum *re = &hdev->report_enum[report_type]; 4298c2ecf20Sopenharmony_ci struct hid_report *report; 4308c2ecf20Sopenharmony_ci int i, j; 4318c2ecf20Sopenharmony_ci 4328c2ecf20Sopenharmony_ci list_for_each_entry(report, &re->report_list, list) { 4338c2ecf20Sopenharmony_ci if (report->application != application) 4348c2ecf20Sopenharmony_ci continue; 4358c2ecf20Sopenharmony_ci 4368c2ecf20Sopenharmony_ci for (i = 0; i < report->maxfield; i++) { 4378c2ecf20Sopenharmony_ci struct hid_field *field = report->field[i]; 4388c2ecf20Sopenharmony_ci 4398c2ecf20Sopenharmony_ci for (j = 0; j < field->maxusage; j++) 4408c2ecf20Sopenharmony_ci if (field->usage[j].hid == usage) 4418c2ecf20Sopenharmony_ci return true; 4428c2ecf20Sopenharmony_ci } 4438c2ecf20Sopenharmony_ci } 4448c2ecf20Sopenharmony_ci 4458c2ecf20Sopenharmony_ci return false; 4468c2ecf20Sopenharmony_ci} 4478c2ecf20Sopenharmony_ci 4488c2ecf20Sopenharmony_cistatic bool hammer_has_folded_event(struct hid_device *hdev) 4498c2ecf20Sopenharmony_ci{ 4508c2ecf20Sopenharmony_ci return hammer_has_usage(hdev, HID_INPUT_REPORT, 4518c2ecf20Sopenharmony_ci HID_GD_KEYBOARD, HID_USAGE_KBD_FOLDED); 4528c2ecf20Sopenharmony_ci} 4538c2ecf20Sopenharmony_ci 4548c2ecf20Sopenharmony_cistatic bool hammer_has_backlight_control(struct hid_device *hdev) 4558c2ecf20Sopenharmony_ci{ 4568c2ecf20Sopenharmony_ci return hammer_has_usage(hdev, HID_OUTPUT_REPORT, 4578c2ecf20Sopenharmony_ci HID_GD_KEYBOARD, HID_AD_BRIGHTNESS); 4588c2ecf20Sopenharmony_ci} 4598c2ecf20Sopenharmony_ci 4608c2ecf20Sopenharmony_cistatic int hammer_probe(struct hid_device *hdev, 4618c2ecf20Sopenharmony_ci const struct hid_device_id *id) 4628c2ecf20Sopenharmony_ci{ 4638c2ecf20Sopenharmony_ci int error; 4648c2ecf20Sopenharmony_ci 4658c2ecf20Sopenharmony_ci error = hid_parse(hdev); 4668c2ecf20Sopenharmony_ci if (error) 4678c2ecf20Sopenharmony_ci return error; 4688c2ecf20Sopenharmony_ci 4698c2ecf20Sopenharmony_ci error = hid_hw_start(hdev, HID_CONNECT_DEFAULT); 4708c2ecf20Sopenharmony_ci if (error) 4718c2ecf20Sopenharmony_ci return error; 4728c2ecf20Sopenharmony_ci 4738c2ecf20Sopenharmony_ci /* 4748c2ecf20Sopenharmony_ci * We always want to poll for, and handle tablet mode events from 4758c2ecf20Sopenharmony_ci * devices that have folded usage, even when nobody has opened the input 4768c2ecf20Sopenharmony_ci * device. This also prevents the hid core from dropping early tablet 4778c2ecf20Sopenharmony_ci * mode events from the device. 4788c2ecf20Sopenharmony_ci */ 4798c2ecf20Sopenharmony_ci if (hammer_has_folded_event(hdev)) { 4808c2ecf20Sopenharmony_ci hdev->quirks |= HID_QUIRK_ALWAYS_POLL; 4818c2ecf20Sopenharmony_ci error = hid_hw_open(hdev); 4828c2ecf20Sopenharmony_ci if (error) 4838c2ecf20Sopenharmony_ci return error; 4848c2ecf20Sopenharmony_ci } 4858c2ecf20Sopenharmony_ci 4868c2ecf20Sopenharmony_ci if (hammer_has_backlight_control(hdev)) { 4878c2ecf20Sopenharmony_ci error = hammer_register_leds(hdev); 4888c2ecf20Sopenharmony_ci if (error) 4898c2ecf20Sopenharmony_ci hid_warn(hdev, 4908c2ecf20Sopenharmony_ci "Failed to register keyboard backlight: %d\n", 4918c2ecf20Sopenharmony_ci error); 4928c2ecf20Sopenharmony_ci } 4938c2ecf20Sopenharmony_ci 4948c2ecf20Sopenharmony_ci return 0; 4958c2ecf20Sopenharmony_ci} 4968c2ecf20Sopenharmony_ci 4978c2ecf20Sopenharmony_cistatic void hammer_remove(struct hid_device *hdev) 4988c2ecf20Sopenharmony_ci{ 4998c2ecf20Sopenharmony_ci unsigned long flags; 5008c2ecf20Sopenharmony_ci 5018c2ecf20Sopenharmony_ci if (hammer_has_folded_event(hdev)) { 5028c2ecf20Sopenharmony_ci hid_hw_close(hdev); 5038c2ecf20Sopenharmony_ci 5048c2ecf20Sopenharmony_ci /* 5058c2ecf20Sopenharmony_ci * If we are disconnecting then most likely Whiskers is 5068c2ecf20Sopenharmony_ci * being removed. Even if it is not removed, without proper 5078c2ecf20Sopenharmony_ci * keyboard we should not stay in clamshell mode. 5088c2ecf20Sopenharmony_ci * 5098c2ecf20Sopenharmony_ci * The reason for doing it here and not waiting for signal 5108c2ecf20Sopenharmony_ci * from EC, is that on some devices there are high leakage 5118c2ecf20Sopenharmony_ci * on Whiskers pins and we do not detect disconnect reliably, 5128c2ecf20Sopenharmony_ci * resulting in devices being stuck in clamshell mode. 5138c2ecf20Sopenharmony_ci */ 5148c2ecf20Sopenharmony_ci spin_lock_irqsave(&cbas_ec_lock, flags); 5158c2ecf20Sopenharmony_ci if (cbas_ec.input && cbas_ec.base_present) { 5168c2ecf20Sopenharmony_ci input_report_switch(cbas_ec.input, SW_TABLET_MODE, 1); 5178c2ecf20Sopenharmony_ci input_sync(cbas_ec.input); 5188c2ecf20Sopenharmony_ci } 5198c2ecf20Sopenharmony_ci cbas_ec.base_present = false; 5208c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&cbas_ec_lock, flags); 5218c2ecf20Sopenharmony_ci } 5228c2ecf20Sopenharmony_ci 5238c2ecf20Sopenharmony_ci hammer_unregister_leds(hdev); 5248c2ecf20Sopenharmony_ci 5258c2ecf20Sopenharmony_ci hid_hw_stop(hdev); 5268c2ecf20Sopenharmony_ci} 5278c2ecf20Sopenharmony_ci 5288c2ecf20Sopenharmony_cistatic const struct hid_device_id hammer_devices[] = { 5298c2ecf20Sopenharmony_ci { HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, 5308c2ecf20Sopenharmony_ci USB_VENDOR_ID_GOOGLE, USB_DEVICE_ID_GOOGLE_DON) }, 5318c2ecf20Sopenharmony_ci { HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, 5328c2ecf20Sopenharmony_ci USB_VENDOR_ID_GOOGLE, USB_DEVICE_ID_GOOGLE_EEL) }, 5338c2ecf20Sopenharmony_ci { HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, 5348c2ecf20Sopenharmony_ci USB_VENDOR_ID_GOOGLE, USB_DEVICE_ID_GOOGLE_HAMMER) }, 5358c2ecf20Sopenharmony_ci { HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, 5368c2ecf20Sopenharmony_ci USB_VENDOR_ID_GOOGLE, USB_DEVICE_ID_GOOGLE_JEWEL) }, 5378c2ecf20Sopenharmony_ci { HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, 5388c2ecf20Sopenharmony_ci USB_VENDOR_ID_GOOGLE, USB_DEVICE_ID_GOOGLE_MAGNEMITE) }, 5398c2ecf20Sopenharmony_ci { HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, 5408c2ecf20Sopenharmony_ci USB_VENDOR_ID_GOOGLE, USB_DEVICE_ID_GOOGLE_MASTERBALL) }, 5418c2ecf20Sopenharmony_ci { HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, 5428c2ecf20Sopenharmony_ci USB_VENDOR_ID_GOOGLE, USB_DEVICE_ID_GOOGLE_MOONBALL) }, 5438c2ecf20Sopenharmony_ci { HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, 5448c2ecf20Sopenharmony_ci USB_VENDOR_ID_GOOGLE, USB_DEVICE_ID_GOOGLE_STAFF) }, 5458c2ecf20Sopenharmony_ci { HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, 5468c2ecf20Sopenharmony_ci USB_VENDOR_ID_GOOGLE, USB_DEVICE_ID_GOOGLE_WAND) }, 5478c2ecf20Sopenharmony_ci { HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, 5488c2ecf20Sopenharmony_ci USB_VENDOR_ID_GOOGLE, USB_DEVICE_ID_GOOGLE_WHISKERS) }, 5498c2ecf20Sopenharmony_ci { } 5508c2ecf20Sopenharmony_ci}; 5518c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(hid, hammer_devices); 5528c2ecf20Sopenharmony_ci 5538c2ecf20Sopenharmony_cistatic struct hid_driver hammer_driver = { 5548c2ecf20Sopenharmony_ci .name = "hammer", 5558c2ecf20Sopenharmony_ci .id_table = hammer_devices, 5568c2ecf20Sopenharmony_ci .probe = hammer_probe, 5578c2ecf20Sopenharmony_ci .remove = hammer_remove, 5588c2ecf20Sopenharmony_ci .input_mapping = hammer_input_mapping, 5598c2ecf20Sopenharmony_ci .event = hammer_event, 5608c2ecf20Sopenharmony_ci}; 5618c2ecf20Sopenharmony_ci 5628c2ecf20Sopenharmony_cistatic int __init hammer_init(void) 5638c2ecf20Sopenharmony_ci{ 5648c2ecf20Sopenharmony_ci int error; 5658c2ecf20Sopenharmony_ci 5668c2ecf20Sopenharmony_ci error = platform_driver_register(&cbas_ec_driver); 5678c2ecf20Sopenharmony_ci if (error) 5688c2ecf20Sopenharmony_ci return error; 5698c2ecf20Sopenharmony_ci 5708c2ecf20Sopenharmony_ci error = hid_register_driver(&hammer_driver); 5718c2ecf20Sopenharmony_ci if (error) { 5728c2ecf20Sopenharmony_ci platform_driver_unregister(&cbas_ec_driver); 5738c2ecf20Sopenharmony_ci return error; 5748c2ecf20Sopenharmony_ci } 5758c2ecf20Sopenharmony_ci 5768c2ecf20Sopenharmony_ci return 0; 5778c2ecf20Sopenharmony_ci} 5788c2ecf20Sopenharmony_cimodule_init(hammer_init); 5798c2ecf20Sopenharmony_ci 5808c2ecf20Sopenharmony_cistatic void __exit hammer_exit(void) 5818c2ecf20Sopenharmony_ci{ 5828c2ecf20Sopenharmony_ci hid_unregister_driver(&hammer_driver); 5838c2ecf20Sopenharmony_ci platform_driver_unregister(&cbas_ec_driver); 5848c2ecf20Sopenharmony_ci} 5858c2ecf20Sopenharmony_cimodule_exit(hammer_exit); 5868c2ecf20Sopenharmony_ci 5878c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 588