1 // SPDX-License-Identifier: GPL-2.0
2 /*
3  * Helpers for ChromeOS HID Vivaldi keyboards
4  *
5  * Copyright (C) 2022 Google, Inc
6  */
7 
8 #include <linux/export.h>
9 #include <linux/hid.h>
10 #include <linux/input/vivaldi-fmap.h>
11 #include <linux/kernel.h>
12 #include <linux/module.h>
13 #include <linux/types.h>
14 
15 #include "hid-vivaldi-common.h"
16 
17 #define MIN_FN_ROW_KEY 1
18 #define MAX_FN_ROW_KEY VIVALDI_MAX_FUNCTION_ROW_KEYS
19 #define HID_VD_FN_ROW_PHYSMAP 0x00000001
20 #define HID_USAGE_FN_ROW_PHYSMAP (HID_UP_GOOGLEVENDOR | HID_VD_FN_ROW_PHYSMAP)
21 
22 /**
23  * vivaldi_feature_mapping - Fill out vivaldi keymap data exposed via HID
24  * @hdev: HID device to parse
25  * @field: HID field to parse
26  * @usage: HID usage to parse
27  *
28  * Note: this function assumes that driver data attached to @hdev contains an
29  * instance of &struct vivaldi_data at the very beginning.
30  */
31 void vivaldi_feature_mapping(struct hid_device *hdev,
32 			     struct hid_field *field, struct hid_usage *usage)
33 {
34 	struct vivaldi_data *data = hid_get_drvdata(hdev);
35 	struct hid_report *report = field->report;
36 	u8 *report_data, *buf;
37 	u32 report_len;
38 	unsigned int fn_key;
39 	int ret;
40 
41 	if (field->logical != HID_USAGE_FN_ROW_PHYSMAP ||
42 	    (usage->hid & HID_USAGE_PAGE) != HID_UP_ORDINAL)
43 		return;
44 
45 	fn_key = usage->hid & HID_USAGE;
46 	if (fn_key < MIN_FN_ROW_KEY || fn_key > MAX_FN_ROW_KEY)
47 		return;
48 
49 	if (fn_key > data->num_function_row_keys)
50 		data->num_function_row_keys = fn_key;
51 
52 	report_data = buf = hid_alloc_report_buf(report, GFP_KERNEL);
53 	if (!report_data)
54 		return;
55 
56 	report_len = hid_report_len(report);
57 	if (!report->id) {
58 		/*
59 		 * hid_hw_raw_request() will stuff report ID (which will be 0)
60 		 * into the first byte of the buffer even for unnumbered
61 		 * reports, so we need to account for this to avoid getting
62 		 * -EOVERFLOW in return.
63 		 * Note that hid_alloc_report_buf() adds 7 bytes to the size
64 		 * so we can safely say that we have space for an extra byte.
65 		 */
66 		report_len++;
67 	}
68 
69 	ret = hid_hw_raw_request(hdev, report->id, report_data,
70 				 report_len, HID_FEATURE_REPORT,
71 				 HID_REQ_GET_REPORT);
72 	if (ret < 0) {
73 		dev_warn(&hdev->dev, "failed to fetch feature %d\n",
74 			 field->report->id);
75 		goto out;
76 	}
77 
78 	if (!report->id) {
79 		/*
80 		 * Undo the damage from hid_hw_raw_request() for unnumbered
81 		 * reports.
82 		 */
83 		report_data++;
84 		report_len--;
85 	}
86 
87 	ret = hid_report_raw_event(hdev, HID_FEATURE_REPORT, report_data,
88 				   report_len, 0);
89 	if (ret) {
90 		dev_warn(&hdev->dev, "failed to report feature %d\n",
91 			 field->report->id);
92 		goto out;
93 	}
94 
95 	data->function_row_physmap[fn_key - MIN_FN_ROW_KEY] =
96 		field->value[usage->usage_index];
97 
98 out:
99 	kfree(buf);
100 }
101 EXPORT_SYMBOL_GPL(vivaldi_feature_mapping);
102 
103 static ssize_t function_row_physmap_show(struct device *dev,
104 					 struct device_attribute *attr,
105 					 char *buf)
106 {
107 	struct hid_device *hdev = to_hid_device(dev);
108 	struct vivaldi_data *data = hid_get_drvdata(hdev);
109 
110 	return vivaldi_function_row_physmap_show(data, buf);
111 }
112 
113 static DEVICE_ATTR_RO(function_row_physmap);
114 static struct attribute *vivaldi_sysfs_attrs[] = {
115 	&dev_attr_function_row_physmap.attr,
116 	NULL
117 };
118 
119 static const struct attribute_group vivaldi_attribute_group = {
120 	.attrs = vivaldi_sysfs_attrs,
121 };
122 
123 /**
124  * vivaldi_input_configured - Complete initialization of device using vivaldi map
125  * @hdev: HID device to which vivaldi attributes should be attached
126  * @hidinput: HID input device (unused)
127  */
128 int vivaldi_input_configured(struct hid_device *hdev,
129 			     struct hid_input *hidinput)
130 {
131 	struct vivaldi_data *data = hid_get_drvdata(hdev);
132 
133 	if (!data->num_function_row_keys)
134 		return 0;
135 
136 	return devm_device_add_group(&hdev->dev, &vivaldi_attribute_group);
137 }
138 EXPORT_SYMBOL_GPL(vivaldi_input_configured);
139 
140 MODULE_LICENSE("GPL");
141