linux-input.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH 0/5] HID: lenovo: Add ThinkPad 10 Ultrabook Keyboard support
@ 2020-07-04 13:27 Hans de Goede
  2020-07-04 13:27 ` [PATCH 1/5] HID: lenovo: Merge tpkbd and cptkbd data structures Hans de Goede
                   ` (5 more replies)
  0 siblings, 6 replies; 7+ messages in thread
From: Hans de Goede @ 2020-07-04 13:27 UTC (permalink / raw)
  To: Jiri Kosina, Benjamin Tissoires; +Cc: Hans de Goede, linux-input

Hi All,

This series really only is a small(ish) change. But I had to do some
refactoring to be able to re-use the existing (mic)mute LED and fn_lock
sysfs attribute code in hid-lenovo.c. That is done in the first 3 patches
of the series. Even though there are 5 patches all 5 of them are small and
hopefully easy to review.

Patch 4 adds the main support for the ThinkPad 10 Ultrabook Keyboard,
fixing some special keys not working and adding support for the LEDs
integrated into the mute and micmute keys.

Patch 5 adds support for modifying the Fn lock setting of the kbd through
sysfs, matching the existing ThinkPad Compact Keyboard with TrackPoint
support, which also allows modifying the Fn lock setting through sysfs.

Regards,

Hans


^ permalink raw reply	[flat|nested] 7+ messages in thread

* [PATCH 1/5] HID: lenovo: Merge tpkbd and cptkbd data structures
  2020-07-04 13:27 [PATCH 0/5] HID: lenovo: Add ThinkPad 10 Ultrabook Keyboard support Hans de Goede
@ 2020-07-04 13:27 ` Hans de Goede
  2020-07-04 13:27 ` [PATCH 2/5] HID: lenovo: Factor out generic parts of the LED code Hans de Goede
                   ` (4 subsequent siblings)
  5 siblings, 0 replies; 7+ messages in thread
From: Hans de Goede @ 2020-07-04 13:27 UTC (permalink / raw)
  To: Jiri Kosina, Benjamin Tissoires; +Cc: Hans de Goede, linux-input

Merge the tpkbd and cptkbd data structures, into a single unified
structure instead of having a separate data structure per keyboard type.

This is a preparation patch for making the tpkbd LED functions more
generic, so that they can be re-used for supporting the LEDs on the
Thinkpad 10 ultrabook keyboard.

Signed-off-by: Hans de Goede <hdegoede@redhat.com>
---
 drivers/hid/hid-lenovo.c | 56 +++++++++++++++++++---------------------
 1 file changed, 26 insertions(+), 30 deletions(-)

diff --git a/drivers/hid/hid-lenovo.c b/drivers/hid/hid-lenovo.c
index 96fa2a2c2cd3..e58d270c909d 100644
--- a/drivers/hid/hid-lenovo.c
+++ b/drivers/hid/hid-lenovo.c
@@ -32,7 +32,7 @@
 
 #include "hid-ids.h"
 
-struct lenovo_drvdata_tpkbd {
+struct lenovo_drvdata {
 	int led_state;
 	struct led_classdev led_mute;
 	struct led_classdev led_micmute;
@@ -42,12 +42,8 @@ struct lenovo_drvdata_tpkbd {
 	int select_right;
 	int sensitivity;
 	int press_speed;
-};
-
-struct lenovo_drvdata_cptkbd {
 	u8 middlebutton_state; /* 0:Up, 1:Down (undecided), 2:Scrolling */
 	bool fn_lock;
-	int sensitivity;
 };
 
 #define map_key_clear(c) hid_map_usage_clear(hi, usage, bit, max, EV_KEY, (c))
@@ -242,7 +238,7 @@ static int lenovo_send_cmd_cptkbd(struct hid_device *hdev,
 static void lenovo_features_set_cptkbd(struct hid_device *hdev)
 {
 	int ret;
-	struct lenovo_drvdata_cptkbd *cptkbd_data = hid_get_drvdata(hdev);
+	struct lenovo_drvdata *cptkbd_data = hid_get_drvdata(hdev);
 
 	ret = lenovo_send_cmd_cptkbd(hdev, 0x05, cptkbd_data->fn_lock);
 	if (ret)
@@ -258,7 +254,7 @@ static ssize_t attr_fn_lock_show_cptkbd(struct device *dev,
 		char *buf)
 {
 	struct hid_device *hdev = to_hid_device(dev);
-	struct lenovo_drvdata_cptkbd *cptkbd_data = hid_get_drvdata(hdev);
+	struct lenovo_drvdata *cptkbd_data = hid_get_drvdata(hdev);
 
 	return snprintf(buf, PAGE_SIZE, "%u\n", cptkbd_data->fn_lock);
 }
@@ -269,7 +265,7 @@ static ssize_t attr_fn_lock_store_cptkbd(struct device *dev,
 		size_t count)
 {
 	struct hid_device *hdev = to_hid_device(dev);
-	struct lenovo_drvdata_cptkbd *cptkbd_data = hid_get_drvdata(hdev);
+	struct lenovo_drvdata *cptkbd_data = hid_get_drvdata(hdev);
 	int value;
 
 	if (kstrtoint(buf, 10, &value))
@@ -288,7 +284,7 @@ static ssize_t attr_sensitivity_show_cptkbd(struct device *dev,
 		char *buf)
 {
 	struct hid_device *hdev = to_hid_device(dev);
-	struct lenovo_drvdata_cptkbd *cptkbd_data = hid_get_drvdata(hdev);
+	struct lenovo_drvdata *cptkbd_data = hid_get_drvdata(hdev);
 
 	return snprintf(buf, PAGE_SIZE, "%u\n",
 		cptkbd_data->sensitivity);
@@ -300,7 +296,7 @@ static ssize_t attr_sensitivity_store_cptkbd(struct device *dev,
 		size_t count)
 {
 	struct hid_device *hdev = to_hid_device(dev);
-	struct lenovo_drvdata_cptkbd *cptkbd_data = hid_get_drvdata(hdev);
+	struct lenovo_drvdata *cptkbd_data = hid_get_drvdata(hdev);
 	int value;
 
 	if (kstrtoint(buf, 10, &value) || value < 1 || value > 255)
@@ -357,7 +353,7 @@ static int lenovo_raw_event(struct hid_device *hdev,
 static int lenovo_event_cptkbd(struct hid_device *hdev,
 		struct hid_field *field, struct hid_usage *usage, __s32 value)
 {
-	struct lenovo_drvdata_cptkbd *cptkbd_data = hid_get_drvdata(hdev);
+	struct lenovo_drvdata *cptkbd_data = hid_get_drvdata(hdev);
 
 	/* "wheel" scroll events */
 	if (usage->type == EV_REL && (usage->code == REL_WHEEL ||
@@ -404,7 +400,7 @@ static int lenovo_event(struct hid_device *hdev, struct hid_field *field,
 static int lenovo_features_set_tpkbd(struct hid_device *hdev)
 {
 	struct hid_report *report;
-	struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev);
+	struct lenovo_drvdata *data_pointer = hid_get_drvdata(hdev);
 
 	report = hdev->report_enum[HID_FEATURE_REPORT].report_id_hash[4];
 
@@ -425,7 +421,7 @@ static ssize_t attr_press_to_select_show_tpkbd(struct device *dev,
 		char *buf)
 {
 	struct hid_device *hdev = to_hid_device(dev);
-	struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev);
+	struct lenovo_drvdata *data_pointer = hid_get_drvdata(hdev);
 
 	return snprintf(buf, PAGE_SIZE, "%u\n", data_pointer->press_to_select);
 }
@@ -436,7 +432,7 @@ static ssize_t attr_press_to_select_store_tpkbd(struct device *dev,
 		size_t count)
 {
 	struct hid_device *hdev = to_hid_device(dev);
-	struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev);
+	struct lenovo_drvdata *data_pointer = hid_get_drvdata(hdev);
 	int value;
 
 	if (kstrtoint(buf, 10, &value))
@@ -455,7 +451,7 @@ static ssize_t attr_dragging_show_tpkbd(struct device *dev,
 		char *buf)
 {
 	struct hid_device *hdev = to_hid_device(dev);
-	struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev);
+	struct lenovo_drvdata *data_pointer = hid_get_drvdata(hdev);
 
 	return snprintf(buf, PAGE_SIZE, "%u\n", data_pointer->dragging);
 }
@@ -466,7 +462,7 @@ static ssize_t attr_dragging_store_tpkbd(struct device *dev,
 		size_t count)
 {
 	struct hid_device *hdev = to_hid_device(dev);
-	struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev);
+	struct lenovo_drvdata *data_pointer = hid_get_drvdata(hdev);
 	int value;
 
 	if (kstrtoint(buf, 10, &value))
@@ -485,7 +481,7 @@ static ssize_t attr_release_to_select_show_tpkbd(struct device *dev,
 		char *buf)
 {
 	struct hid_device *hdev = to_hid_device(dev);
-	struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev);
+	struct lenovo_drvdata *data_pointer = hid_get_drvdata(hdev);
 
 	return snprintf(buf, PAGE_SIZE, "%u\n", data_pointer->release_to_select);
 }
@@ -496,7 +492,7 @@ static ssize_t attr_release_to_select_store_tpkbd(struct device *dev,
 		size_t count)
 {
 	struct hid_device *hdev = to_hid_device(dev);
-	struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev);
+	struct lenovo_drvdata *data_pointer = hid_get_drvdata(hdev);
 	int value;
 
 	if (kstrtoint(buf, 10, &value))
@@ -515,7 +511,7 @@ static ssize_t attr_select_right_show_tpkbd(struct device *dev,
 		char *buf)
 {
 	struct hid_device *hdev = to_hid_device(dev);
-	struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev);
+	struct lenovo_drvdata *data_pointer = hid_get_drvdata(hdev);
 
 	return snprintf(buf, PAGE_SIZE, "%u\n", data_pointer->select_right);
 }
@@ -526,7 +522,7 @@ static ssize_t attr_select_right_store_tpkbd(struct device *dev,
 		size_t count)
 {
 	struct hid_device *hdev = to_hid_device(dev);
-	struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev);
+	struct lenovo_drvdata *data_pointer = hid_get_drvdata(hdev);
 	int value;
 
 	if (kstrtoint(buf, 10, &value))
@@ -545,7 +541,7 @@ static ssize_t attr_sensitivity_show_tpkbd(struct device *dev,
 		char *buf)
 {
 	struct hid_device *hdev = to_hid_device(dev);
-	struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev);
+	struct lenovo_drvdata *data_pointer = hid_get_drvdata(hdev);
 
 	return snprintf(buf, PAGE_SIZE, "%u\n",
 		data_pointer->sensitivity);
@@ -557,7 +553,7 @@ static ssize_t attr_sensitivity_store_tpkbd(struct device *dev,
 		size_t count)
 {
 	struct hid_device *hdev = to_hid_device(dev);
-	struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev);
+	struct lenovo_drvdata *data_pointer = hid_get_drvdata(hdev);
 	int value;
 
 	if (kstrtoint(buf, 10, &value) || value < 1 || value > 255)
@@ -574,7 +570,7 @@ static ssize_t attr_press_speed_show_tpkbd(struct device *dev,
 		char *buf)
 {
 	struct hid_device *hdev = to_hid_device(dev);
-	struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev);
+	struct lenovo_drvdata *data_pointer = hid_get_drvdata(hdev);
 
 	return snprintf(buf, PAGE_SIZE, "%u\n",
 		data_pointer->press_speed);
@@ -586,7 +582,7 @@ static ssize_t attr_press_speed_store_tpkbd(struct device *dev,
 		size_t count)
 {
 	struct hid_device *hdev = to_hid_device(dev);
-	struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev);
+	struct lenovo_drvdata *data_pointer = hid_get_drvdata(hdev);
 	int value;
 
 	if (kstrtoint(buf, 10, &value) || value < 1 || value > 255)
@@ -647,7 +643,7 @@ static enum led_brightness lenovo_led_brightness_get_tpkbd(
 {
 	struct device *dev = led_cdev->dev->parent;
 	struct hid_device *hdev = to_hid_device(dev);
-	struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev);
+	struct lenovo_drvdata *data_pointer = hid_get_drvdata(hdev);
 	int led_nr = 0;
 
 	if (led_cdev == &data_pointer->led_micmute)
@@ -663,7 +659,7 @@ static void lenovo_led_brightness_set_tpkbd(struct led_classdev *led_cdev,
 {
 	struct device *dev = led_cdev->dev->parent;
 	struct hid_device *hdev = to_hid_device(dev);
-	struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev);
+	struct lenovo_drvdata *data_pointer = hid_get_drvdata(hdev);
 	struct hid_report *report;
 	int led_nr = 0;
 
@@ -684,7 +680,7 @@ static void lenovo_led_brightness_set_tpkbd(struct led_classdev *led_cdev,
 static int lenovo_probe_tpkbd(struct hid_device *hdev)
 {
 	struct device *dev = &hdev->dev;
-	struct lenovo_drvdata_tpkbd *data_pointer;
+	struct lenovo_drvdata *data_pointer;
 	size_t name_sz = strlen(dev_name(dev)) + 16;
 	char *name_mute, *name_micmute;
 	int i;
@@ -712,7 +708,7 @@ static int lenovo_probe_tpkbd(struct hid_device *hdev)
 		hid_warn(hdev, "Could not create sysfs group: %d\n", ret);
 
 	data_pointer = devm_kzalloc(&hdev->dev,
-				    sizeof(struct lenovo_drvdata_tpkbd),
+				    sizeof(struct lenovo_drvdata),
 				    GFP_KERNEL);
 	if (data_pointer == NULL) {
 		hid_err(hdev, "Could not allocate memory for driver data\n");
@@ -767,7 +763,7 @@ static int lenovo_probe_tpkbd(struct hid_device *hdev)
 static int lenovo_probe_cptkbd(struct hid_device *hdev)
 {
 	int ret;
-	struct lenovo_drvdata_cptkbd *cptkbd_data;
+	struct lenovo_drvdata *cptkbd_data;
 
 	/* All the custom action happens on the USBMOUSE device for USB */
 	if (hdev->product == USB_DEVICE_ID_LENOVO_CUSBKBD
@@ -852,7 +848,7 @@ static int lenovo_probe(struct hid_device *hdev,
 
 static void lenovo_remove_tpkbd(struct hid_device *hdev)
 {
-	struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev);
+	struct lenovo_drvdata *data_pointer = hid_get_drvdata(hdev);
 
 	/*
 	 * Only the trackpoint half of the keyboard has drvdata and stuff that
-- 
2.26.2


^ permalink raw reply related	[flat|nested] 7+ messages in thread

* [PATCH 2/5] HID: lenovo: Factor out generic parts of the LED code
  2020-07-04 13:27 [PATCH 0/5] HID: lenovo: Add ThinkPad 10 Ultrabook Keyboard support Hans de Goede
  2020-07-04 13:27 ` [PATCH 1/5] HID: lenovo: Merge tpkbd and cptkbd data structures Hans de Goede
@ 2020-07-04 13:27 ` Hans de Goede
  2020-07-04 13:27 ` [PATCH 3/5] HID: lenovo: Rename fn_lock sysfs attr handlers to make them generic Hans de Goede
                   ` (3 subsequent siblings)
  5 siblings, 0 replies; 7+ messages in thread
From: Hans de Goede @ 2020-07-04 13:27 UTC (permalink / raw)
  To: Jiri Kosina, Benjamin Tissoires; +Cc: Hans de Goede, linux-input

Factor out the generic parts of the tpkbd LED code, so that they can be
re-used for supporting the LEDs on the Thinkpad 10 ultrabook kbd.

Signed-off-by: Hans de Goede <hdegoede@redhat.com>
---
 drivers/hid/hid-lenovo.c | 98 ++++++++++++++++++++++++----------------
 1 file changed, 58 insertions(+), 40 deletions(-)

diff --git a/drivers/hid/hid-lenovo.c b/drivers/hid/hid-lenovo.c
index e58d270c909d..feb0cbc6742e 100644
--- a/drivers/hid/hid-lenovo.c
+++ b/drivers/hid/hid-lenovo.c
@@ -638,7 +638,18 @@ static const struct attribute_group lenovo_attr_group_tpkbd = {
 	.attrs = lenovo_attributes_tpkbd,
 };
 
-static enum led_brightness lenovo_led_brightness_get_tpkbd(
+static void lenovo_led_set_tpkbd(struct hid_device *hdev)
+{
+	struct lenovo_drvdata *data_pointer = hid_get_drvdata(hdev);
+	struct hid_report *report;
+
+	report = hdev->report_enum[HID_OUTPUT_REPORT].report_id_hash[3];
+	report->field[0]->value[0] = (data_pointer->led_state >> 0) & 1;
+	report->field[0]->value[1] = (data_pointer->led_state >> 1) & 1;
+	hid_hw_request(hdev, report, HID_REQ_SET_REPORT);
+}
+
+static enum led_brightness lenovo_led_brightness_get(
 			struct led_classdev *led_cdev)
 {
 	struct device *dev = led_cdev->dev->parent;
@@ -654,13 +665,12 @@ static enum led_brightness lenovo_led_brightness_get_tpkbd(
 				: LED_OFF;
 }
 
-static void lenovo_led_brightness_set_tpkbd(struct led_classdev *led_cdev,
+static void lenovo_led_brightness_set(struct led_classdev *led_cdev,
 			enum led_brightness value)
 {
 	struct device *dev = led_cdev->dev->parent;
 	struct hid_device *hdev = to_hid_device(dev);
 	struct lenovo_drvdata *data_pointer = hid_get_drvdata(hdev);
-	struct hid_report *report;
 	int led_nr = 0;
 
 	if (led_cdev == &data_pointer->led_micmute)
@@ -671,20 +681,54 @@ static void lenovo_led_brightness_set_tpkbd(struct led_classdev *led_cdev,
 	else
 		data_pointer->led_state |= 1 << led_nr;
 
-	report = hdev->report_enum[HID_OUTPUT_REPORT].report_id_hash[3];
-	report->field[0]->value[0] = (data_pointer->led_state >> 0) & 1;
-	report->field[0]->value[1] = (data_pointer->led_state >> 1) & 1;
-	hid_hw_request(hdev, report, HID_REQ_SET_REPORT);
+	switch (hdev->product) {
+	case USB_DEVICE_ID_LENOVO_TPKBD:
+		lenovo_led_set_tpkbd(hdev);
+		break;
+	}
+}
+
+static int lenovo_register_leds(struct hid_device *hdev)
+{
+	struct lenovo_drvdata *data = hid_get_drvdata(hdev);
+	size_t name_sz = strlen(dev_name(&hdev->dev)) + 16;
+	char *name_mute, *name_micm;
+	int ret;
+
+	name_mute = devm_kzalloc(&hdev->dev, name_sz, GFP_KERNEL);
+	name_micm = devm_kzalloc(&hdev->dev, name_sz, GFP_KERNEL);
+	if (name_mute == NULL || name_micm == NULL) {
+		hid_err(hdev, "Could not allocate memory for led data\n");
+		return -ENOMEM;
+	}
+	snprintf(name_mute, name_sz, "%s:amber:mute", dev_name(&hdev->dev));
+	snprintf(name_micm, name_sz, "%s:amber:micmute", dev_name(&hdev->dev));
+
+	data->led_mute.name = name_mute;
+	data->led_mute.brightness_get = lenovo_led_brightness_get;
+	data->led_mute.brightness_set = lenovo_led_brightness_set;
+	data->led_mute.dev = &hdev->dev;
+	ret = led_classdev_register(&hdev->dev, &data->led_mute);
+	if (ret < 0)
+		return ret;
+
+	data->led_micmute.name = name_micm;
+	data->led_micmute.brightness_get = lenovo_led_brightness_get;
+	data->led_micmute.brightness_set = lenovo_led_brightness_set;
+	data->led_micmute.dev = &hdev->dev;
+	ret = led_classdev_register(&hdev->dev, &data->led_micmute);
+	if (ret < 0) {
+		led_classdev_unregister(&data->led_mute);
+		return ret;
+	}
+
+	return 0;
 }
 
 static int lenovo_probe_tpkbd(struct hid_device *hdev)
 {
-	struct device *dev = &hdev->dev;
 	struct lenovo_drvdata *data_pointer;
-	size_t name_sz = strlen(dev_name(dev)) + 16;
-	char *name_mute, *name_micmute;
-	int i;
-	int ret;
+	int i, ret;
 
 	/*
 	 * Only register extra settings against subdevice where input_mapping
@@ -720,37 +764,11 @@ static int lenovo_probe_tpkbd(struct hid_device *hdev)
 	data_pointer->sensitivity = 0xa0;
 	data_pointer->press_speed = 0x38;
 
-	name_mute = devm_kzalloc(&hdev->dev, name_sz, GFP_KERNEL);
-	name_micmute = devm_kzalloc(&hdev->dev, name_sz, GFP_KERNEL);
-	if (name_mute == NULL || name_micmute == NULL) {
-		hid_err(hdev, "Could not allocate memory for led data\n");
-		ret = -ENOMEM;
-		goto err;
-	}
-	snprintf(name_mute, name_sz, "%s:amber:mute", dev_name(dev));
-	snprintf(name_micmute, name_sz, "%s:amber:micmute", dev_name(dev));
-
 	hid_set_drvdata(hdev, data_pointer);
 
-	data_pointer->led_mute.name = name_mute;
-	data_pointer->led_mute.brightness_get = lenovo_led_brightness_get_tpkbd;
-	data_pointer->led_mute.brightness_set = lenovo_led_brightness_set_tpkbd;
-	data_pointer->led_mute.dev = dev;
-	ret = led_classdev_register(dev, &data_pointer->led_mute);
-	if (ret < 0)
-		goto err;
-
-	data_pointer->led_micmute.name = name_micmute;
-	data_pointer->led_micmute.brightness_get =
-		lenovo_led_brightness_get_tpkbd;
-	data_pointer->led_micmute.brightness_set =
-		lenovo_led_brightness_set_tpkbd;
-	data_pointer->led_micmute.dev = dev;
-	ret = led_classdev_register(dev, &data_pointer->led_micmute);
-	if (ret < 0) {
-		led_classdev_unregister(&data_pointer->led_mute);
+	ret = lenovo_register_leds(hdev);
+	if (ret)
 		goto err;
-	}
 
 	lenovo_features_set_tpkbd(hdev);
 
-- 
2.26.2


^ permalink raw reply related	[flat|nested] 7+ messages in thread

* [PATCH 3/5] HID: lenovo: Rename fn_lock sysfs attr handlers to make them generic
  2020-07-04 13:27 [PATCH 0/5] HID: lenovo: Add ThinkPad 10 Ultrabook Keyboard support Hans de Goede
  2020-07-04 13:27 ` [PATCH 1/5] HID: lenovo: Merge tpkbd and cptkbd data structures Hans de Goede
  2020-07-04 13:27 ` [PATCH 2/5] HID: lenovo: Factor out generic parts of the LED code Hans de Goede
@ 2020-07-04 13:27 ` Hans de Goede
  2020-07-04 13:27 ` [PATCH 4/5] HID: lenovo: Add ThinkPad 10 Ultrabook Keyboard support Hans de Goede
                   ` (2 subsequent siblings)
  5 siblings, 0 replies; 7+ messages in thread
From: Hans de Goede @ 2020-07-04 13:27 UTC (permalink / raw)
  To: Jiri Kosina, Benjamin Tissoires; +Cc: Hans de Goede, linux-input

Except for a single call, there is nothing keyboard-model specific about
the cptkbd fn_lock sysfs attr handlers, rename them dropping the cptkbd
post-/pre-fix, so that they can be re-used for supporting Fn-lock on the
Thinkpad 10 ultrabook kbd.

Signed-off-by: Hans de Goede <hdegoede@redhat.com>
---
 drivers/hid/hid-lenovo.c | 28 +++++++++++++++++-----------
 1 file changed, 17 insertions(+), 11 deletions(-)

diff --git a/drivers/hid/hid-lenovo.c b/drivers/hid/hid-lenovo.c
index feb0cbc6742e..f2eb91704e9c 100644
--- a/drivers/hid/hid-lenovo.c
+++ b/drivers/hid/hid-lenovo.c
@@ -249,23 +249,23 @@ static void lenovo_features_set_cptkbd(struct hid_device *hdev)
 		hid_err(hdev, "Sensitivity setting failed: %d\n", ret);
 }
 
-static ssize_t attr_fn_lock_show_cptkbd(struct device *dev,
+static ssize_t attr_fn_lock_show(struct device *dev,
 		struct device_attribute *attr,
 		char *buf)
 {
 	struct hid_device *hdev = to_hid_device(dev);
-	struct lenovo_drvdata *cptkbd_data = hid_get_drvdata(hdev);
+	struct lenovo_drvdata *data = hid_get_drvdata(hdev);
 
-	return snprintf(buf, PAGE_SIZE, "%u\n", cptkbd_data->fn_lock);
+	return snprintf(buf, PAGE_SIZE, "%u\n", data->fn_lock);
 }
 
-static ssize_t attr_fn_lock_store_cptkbd(struct device *dev,
+static ssize_t attr_fn_lock_store(struct device *dev,
 		struct device_attribute *attr,
 		const char *buf,
 		size_t count)
 {
 	struct hid_device *hdev = to_hid_device(dev);
-	struct lenovo_drvdata *cptkbd_data = hid_get_drvdata(hdev);
+	struct lenovo_drvdata *data = hid_get_drvdata(hdev);
 	int value;
 
 	if (kstrtoint(buf, 10, &value))
@@ -273,8 +273,14 @@ static ssize_t attr_fn_lock_store_cptkbd(struct device *dev,
 	if (value < 0 || value > 1)
 		return -EINVAL;
 
-	cptkbd_data->fn_lock = !!value;
-	lenovo_features_set_cptkbd(hdev);
+	data->fn_lock = !!value;
+
+	switch (hdev->product) {
+	case USB_DEVICE_ID_LENOVO_CUSBKBD:
+	case USB_DEVICE_ID_LENOVO_CBTKBD:
+		lenovo_features_set_cptkbd(hdev);
+		break;
+	}
 
 	return count;
 }
@@ -309,10 +315,10 @@ static ssize_t attr_sensitivity_store_cptkbd(struct device *dev,
 }
 
 
-static struct device_attribute dev_attr_fn_lock_cptkbd =
+static struct device_attribute dev_attr_fn_lock =
 	__ATTR(fn_lock, S_IWUSR | S_IRUGO,
-			attr_fn_lock_show_cptkbd,
-			attr_fn_lock_store_cptkbd);
+			attr_fn_lock_show,
+			attr_fn_lock_store);
 
 static struct device_attribute dev_attr_sensitivity_cptkbd =
 	__ATTR(sensitivity, S_IWUSR | S_IRUGO,
@@ -321,7 +327,7 @@ static struct device_attribute dev_attr_sensitivity_cptkbd =
 
 
 static struct attribute *lenovo_attributes_cptkbd[] = {
-	&dev_attr_fn_lock_cptkbd.attr,
+	&dev_attr_fn_lock.attr,
 	&dev_attr_sensitivity_cptkbd.attr,
 	NULL
 };
-- 
2.26.2


^ permalink raw reply related	[flat|nested] 7+ messages in thread

* [PATCH 4/5] HID: lenovo: Add ThinkPad 10 Ultrabook Keyboard support
  2020-07-04 13:27 [PATCH 0/5] HID: lenovo: Add ThinkPad 10 Ultrabook Keyboard support Hans de Goede
                   ` (2 preceding siblings ...)
  2020-07-04 13:27 ` [PATCH 3/5] HID: lenovo: Rename fn_lock sysfs attr handlers to make them generic Hans de Goede
@ 2020-07-04 13:27 ` Hans de Goede
  2020-07-04 13:27 ` [PATCH 5/5] HID: lenovo: Add ThinkPad 10 Ultrabook Keyboard fn_lock support Hans de Goede
  2020-07-07 10:43 ` [PATCH 0/5] HID: lenovo: Add ThinkPad 10 Ultrabook Keyboard support Jiri Kosina
  5 siblings, 0 replies; 7+ messages in thread
From: Hans de Goede @ 2020-07-04 13:27 UTC (permalink / raw)
  To: Jiri Kosina, Benjamin Tissoires; +Cc: Hans de Goede, linux-input

Some of the function keys special functions all use the same
0x000c0001 usage code, add a mapping for these based on the usage_index;

And add support for the Speaker and Mic mute LEDs integrated into the
F1 and F4 keys.

Signed-off-by: Hans de Goede <hdegoede@redhat.com>
---
 drivers/hid/hid-ids.h    |   1 +
 drivers/hid/hid-lenovo.c | 105 +++++++++++++++++++++++++++++++++++++++
 2 files changed, 106 insertions(+)

diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
index 874fc3791f3b..dc1a3781fe62 100644
--- a/drivers/hid/hid-ids.h
+++ b/drivers/hid/hid-ids.h
@@ -723,6 +723,7 @@
 #define USB_DEVICE_ID_LENOVO_CUSBKBD	0x6047
 #define USB_DEVICE_ID_LENOVO_CBTKBD	0x6048
 #define USB_DEVICE_ID_LENOVO_SCROLLPOINT_OPTICAL	0x6049
+#define USB_DEVICE_ID_LENOVO_TP10UBKBD	0x6062
 #define USB_DEVICE_ID_LENOVO_TPPRODOCK	0x6067
 #define USB_DEVICE_ID_LENOVO_X1_COVER	0x6085
 #define USB_DEVICE_ID_LENOVO_PIXART_USB_MOUSE_608D	0x608d
diff --git a/drivers/hid/hid-lenovo.c b/drivers/hid/hid-lenovo.c
index f2eb91704e9c..ccf41adcfa2b 100644
--- a/drivers/hid/hid-lenovo.c
+++ b/drivers/hid/hid-lenovo.c
@@ -33,7 +33,9 @@
 #include "hid-ids.h"
 
 struct lenovo_drvdata {
+	u8 led_report[3]; /* Must be first for proper alignment */
 	int led_state;
+	struct mutex led_report_mutex;
 	struct led_classdev led_mute;
 	struct led_classdev led_micmute;
 	int press_to_select;
@@ -48,6 +50,34 @@ struct lenovo_drvdata {
 
 #define map_key_clear(c) hid_map_usage_clear(hi, usage, bit, max, EV_KEY, (c))
 
+#define TP10UBKBD_LED_OUTPUT_REPORT	9
+
+#define TP10UBKBD_FN_LOCK_LED		0x54
+#define TP10UBKBD_MUTE_LED		0x64
+#define TP10UBKBD_MICMUTE_LED		0x74
+
+#define TP10UBKBD_LED_OFF		1
+#define TP10UBKBD_LED_ON		2
+
+static void lenovo_led_set_tp10ubkbd(struct hid_device *hdev, u8 led_code,
+				     enum led_brightness value)
+{
+	struct lenovo_drvdata *data = hid_get_drvdata(hdev);
+	int ret;
+
+	mutex_lock(&data->led_report_mutex);
+
+	data->led_report[0] = TP10UBKBD_LED_OUTPUT_REPORT;
+	data->led_report[1] = led_code;
+	data->led_report[2] = value ? TP10UBKBD_LED_ON : TP10UBKBD_LED_OFF;
+	ret = hid_hw_raw_request(hdev, data->led_report[0], data->led_report, 3,
+				 HID_OUTPUT_REPORT, HID_REQ_SET_REPORT);
+	if (ret)
+		hid_err(hdev, "Set LED output report error: %d\n", ret);
+
+	mutex_unlock(&data->led_report_mutex);
+}
+
 static const __u8 lenovo_pro_dock_need_fixup_collection[] = {
 	0x05, 0x88,		/* Usage Page (Vendor Usage Page 0x88)	*/
 	0x09, 0x01,		/* Usage (Vendor Usage 0x01)		*/
@@ -175,6 +205,37 @@ static int lenovo_input_mapping_scrollpoint(struct hid_device *hdev,
 	return 0;
 }
 
+static int lenovo_input_mapping_tp10_ultrabook_kbd(struct hid_device *hdev,
+		struct hid_input *hi, struct hid_field *field,
+		struct hid_usage *usage, unsigned long **bit, int *max)
+{
+	/*
+	 * The ThinkPad 10 Ultrabook Keyboard uses 0x000c0001 usage for
+	 * a bunch of keys which have no standard consumer page code.
+	 */
+	if (usage->hid == 0x000c0001) {
+		switch (usage->usage_index) {
+		case 8: /* Fn-Esc: Fn-lock toggle */
+			map_key_clear(KEY_FN_ESC);
+			return 1;
+		case 9: /* Fn-F4: Mic mute */
+			map_key_clear(KEY_MICMUTE);
+			return 1;
+		case 10: /* Fn-F7: Control panel */
+			map_key_clear(KEY_CONFIG);
+			return 1;
+		case 11: /* Fn-F8: Search (magnifier glass) */
+			map_key_clear(KEY_SEARCH);
+			return 1;
+		case 12: /* Fn-F10: Open My computer (6 boxes) */
+			map_key_clear(KEY_FILE);
+			return 1;
+		}
+	}
+
+	return 0;
+}
+
 static int lenovo_input_mapping(struct hid_device *hdev,
 		struct hid_input *hi, struct hid_field *field,
 		struct hid_usage *usage, unsigned long **bit, int *max)
@@ -195,6 +256,9 @@ static int lenovo_input_mapping(struct hid_device *hdev,
 	case USB_DEVICE_ID_LENOVO_SCROLLPOINT_OPTICAL:
 		return lenovo_input_mapping_scrollpoint(hdev, hi, field,
 							usage, bit, max);
+	case USB_DEVICE_ID_LENOVO_TP10UBKBD:
+		return lenovo_input_mapping_tp10_ultrabook_kbd(hdev, hi, field,
+							       usage, bit, max);
 	default:
 		return 0;
 	}
@@ -677,6 +741,7 @@ static void lenovo_led_brightness_set(struct led_classdev *led_cdev,
 	struct device *dev = led_cdev->dev->parent;
 	struct hid_device *hdev = to_hid_device(dev);
 	struct lenovo_drvdata *data_pointer = hid_get_drvdata(hdev);
+	u8 tp10ubkbd_led[] = { TP10UBKBD_MUTE_LED, TP10UBKBD_MICMUTE_LED };
 	int led_nr = 0;
 
 	if (led_cdev == &data_pointer->led_micmute)
@@ -691,6 +756,9 @@ static void lenovo_led_brightness_set(struct led_classdev *led_cdev,
 	case USB_DEVICE_ID_LENOVO_TPKBD:
 		lenovo_led_set_tpkbd(hdev);
 		break;
+	case USB_DEVICE_ID_LENOVO_TP10UBKBD:
+		lenovo_led_set_tp10ubkbd(hdev, tp10ubkbd_led[led_nr], value);
+		break;
 	}
 }
 
@@ -831,6 +899,25 @@ static int lenovo_probe_cptkbd(struct hid_device *hdev)
 	return 0;
 }
 
+static int lenovo_probe_tp10ubkbd(struct hid_device *hdev)
+{
+	struct lenovo_drvdata *data;
+
+	/* All the custom action happens on the USBMOUSE device for USB */
+	if (hdev->type != HID_TYPE_USBMOUSE)
+		return 0;
+
+	data = devm_kzalloc(&hdev->dev, sizeof(*data), GFP_KERNEL);
+	if (!data)
+		return -ENOMEM;
+
+	mutex_init(&data->led_report_mutex);
+
+	hid_set_drvdata(hdev, data);
+
+	return lenovo_register_leds(hdev);
+}
+
 static int lenovo_probe(struct hid_device *hdev,
 		const struct hid_device_id *id)
 {
@@ -856,6 +943,9 @@ static int lenovo_probe(struct hid_device *hdev,
 	case USB_DEVICE_ID_LENOVO_CBTKBD:
 		ret = lenovo_probe_cptkbd(hdev);
 		break;
+	case USB_DEVICE_ID_LENOVO_TP10UBKBD:
+		ret = lenovo_probe_tp10ubkbd(hdev);
+		break;
 	default:
 		ret = 0;
 		break;
@@ -894,6 +984,17 @@ static void lenovo_remove_cptkbd(struct hid_device *hdev)
 			&lenovo_attr_group_cptkbd);
 }
 
+static void lenovo_remove_tp10ubkbd(struct hid_device *hdev)
+{
+	struct lenovo_drvdata *data = hid_get_drvdata(hdev);
+
+	if (data == NULL)
+		return;
+
+	led_classdev_unregister(&data->led_micmute);
+	led_classdev_unregister(&data->led_mute);
+}
+
 static void lenovo_remove(struct hid_device *hdev)
 {
 	switch (hdev->product) {
@@ -904,6 +1005,9 @@ static void lenovo_remove(struct hid_device *hdev)
 	case USB_DEVICE_ID_LENOVO_CBTKBD:
 		lenovo_remove_cptkbd(hdev);
 		break;
+	case USB_DEVICE_ID_LENOVO_TP10UBKBD:
+		lenovo_remove_tp10ubkbd(hdev);
+		break;
 	}
 
 	hid_hw_stop(hdev);
@@ -940,6 +1044,7 @@ static const struct hid_device_id lenovo_devices[] = {
 	{ HID_USB_DEVICE(USB_VENDOR_ID_IBM, USB_DEVICE_ID_IBM_SCROLLPOINT_800DPI_OPTICAL) },
 	{ HID_USB_DEVICE(USB_VENDOR_ID_IBM, USB_DEVICE_ID_IBM_SCROLLPOINT_800DPI_OPTICAL_PRO) },
 	{ HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_SCROLLPOINT_OPTICAL) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_TP10UBKBD) },
 	{ }
 };
 
-- 
2.26.2


^ permalink raw reply related	[flat|nested] 7+ messages in thread

* [PATCH 5/5] HID: lenovo: Add ThinkPad 10 Ultrabook Keyboard fn_lock support
  2020-07-04 13:27 [PATCH 0/5] HID: lenovo: Add ThinkPad 10 Ultrabook Keyboard support Hans de Goede
                   ` (3 preceding siblings ...)
  2020-07-04 13:27 ` [PATCH 4/5] HID: lenovo: Add ThinkPad 10 Ultrabook Keyboard support Hans de Goede
@ 2020-07-04 13:27 ` Hans de Goede
  2020-07-07 10:43 ` [PATCH 0/5] HID: lenovo: Add ThinkPad 10 Ultrabook Keyboard support Jiri Kosina
  5 siblings, 0 replies; 7+ messages in thread
From: Hans de Goede @ 2020-07-04 13:27 UTC (permalink / raw)
  To: Jiri Kosina, Benjamin Tissoires; +Cc: Hans de Goede, linux-input

Add support for setting the Fn lock value of the ThinkPad 10 Ultrabook
Keyboard through sysfs, re-using the fn_lock sysfs attribute read/write
helpers from the existing ThinkPad Compact Keyboard with TrackPoint
support.

Signed-off-by: Hans de Goede <hdegoede@redhat.com>
---
 drivers/hid/hid-lenovo.c | 72 +++++++++++++++++++++++++++++++++++++++-
 1 file changed, 71 insertions(+), 1 deletion(-)

diff --git a/drivers/hid/hid-lenovo.c b/drivers/hid/hid-lenovo.c
index ccf41adcfa2b..b5122ee46801 100644
--- a/drivers/hid/hid-lenovo.c
+++ b/drivers/hid/hid-lenovo.c
@@ -29,6 +29,7 @@
 #include <linux/hid.h>
 #include <linux/input.h>
 #include <linux/leds.h>
+#include <linux/workqueue.h>
 
 #include "hid-ids.h"
 
@@ -38,6 +39,8 @@ struct lenovo_drvdata {
 	struct mutex led_report_mutex;
 	struct led_classdev led_mute;
 	struct led_classdev led_micmute;
+	struct work_struct fn_lock_sync_work;
+	struct hid_device *hdev;
 	int press_to_select;
 	int dragging;
 	int release_to_select;
@@ -78,6 +81,15 @@ static void lenovo_led_set_tp10ubkbd(struct hid_device *hdev, u8 led_code,
 	mutex_unlock(&data->led_report_mutex);
 }
 
+static void lenovo_tp10ubkbd_sync_fn_lock(struct work_struct *work)
+{
+	struct lenovo_drvdata *data =
+		container_of(work, struct lenovo_drvdata, fn_lock_sync_work);
+
+	lenovo_led_set_tp10ubkbd(data->hdev, TP10UBKBD_FN_LOCK_LED,
+				 data->fn_lock);
+}
+
 static const __u8 lenovo_pro_dock_need_fixup_collection[] = {
 	0x05, 0x88,		/* Usage Page (Vendor Usage Page 0x88)	*/
 	0x09, 0x01,		/* Usage (Vendor Usage 0x01)		*/
@@ -344,6 +356,9 @@ static ssize_t attr_fn_lock_store(struct device *dev,
 	case USB_DEVICE_ID_LENOVO_CBTKBD:
 		lenovo_features_set_cptkbd(hdev);
 		break;
+	case USB_DEVICE_ID_LENOVO_TP10UBKBD:
+		lenovo_led_set_tp10ubkbd(hdev, TP10UBKBD_FN_LOCK_LED, value);
+		break;
 	}
 
 	return count;
@@ -420,6 +435,24 @@ static int lenovo_raw_event(struct hid_device *hdev,
 	return 0;
 }
 
+static int lenovo_event_tp10ubkbd(struct hid_device *hdev,
+		struct hid_field *field, struct hid_usage *usage, __s32 value)
+{
+	struct lenovo_drvdata *data = hid_get_drvdata(hdev);
+
+	if (usage->type == EV_KEY && usage->code == KEY_FN_ESC && value == 1) {
+		/*
+		 * The user has toggled the Fn-lock state. Toggle our own
+		 * cached value of it and sync our value to the keyboard to
+		 * ensure things are in sync (the sycning should be a no-op).
+		 */
+		data->fn_lock = !data->fn_lock;
+		schedule_work(&data->fn_lock_sync_work);
+	}
+
+	return 0;
+}
+
 static int lenovo_event_cptkbd(struct hid_device *hdev,
 		struct hid_field *field, struct hid_usage *usage, __s32 value)
 {
@@ -462,6 +495,8 @@ static int lenovo_event(struct hid_device *hdev, struct hid_field *field,
 	case USB_DEVICE_ID_LENOVO_CUSBKBD:
 	case USB_DEVICE_ID_LENOVO_CBTKBD:
 		return lenovo_event_cptkbd(hdev, field, usage, value);
+	case USB_DEVICE_ID_LENOVO_TP10UBKBD:
+		return lenovo_event_tp10ubkbd(hdev, field, usage, value);
 	default:
 		return 0;
 	}
@@ -899,9 +934,19 @@ static int lenovo_probe_cptkbd(struct hid_device *hdev)
 	return 0;
 }
 
+static struct attribute *lenovo_attributes_tp10ubkbd[] = {
+	&dev_attr_fn_lock.attr,
+	NULL
+};
+
+static const struct attribute_group lenovo_attr_group_tp10ubkbd = {
+	.attrs = lenovo_attributes_tp10ubkbd,
+};
+
 static int lenovo_probe_tp10ubkbd(struct hid_device *hdev)
 {
 	struct lenovo_drvdata *data;
+	int ret;
 
 	/* All the custom action happens on the USBMOUSE device for USB */
 	if (hdev->type != HID_TYPE_USBMOUSE)
@@ -912,10 +957,32 @@ static int lenovo_probe_tp10ubkbd(struct hid_device *hdev)
 		return -ENOMEM;
 
 	mutex_init(&data->led_report_mutex);
+	INIT_WORK(&data->fn_lock_sync_work, lenovo_tp10ubkbd_sync_fn_lock);
+	data->hdev = hdev;
 
 	hid_set_drvdata(hdev, data);
 
-	return lenovo_register_leds(hdev);
+	/*
+	 * The Thinkpad 10 ultrabook USB kbd dock's Fn-lock defaults to on.
+	 * We cannot read the state, only set it, so we force it to on here
+	 * (which should be a no-op) to make sure that our state matches the
+	 * keyboard's FN-lock state. This is the same as what Windows does.
+	 */
+	data->fn_lock = true;
+	lenovo_led_set_tp10ubkbd(hdev, TP10UBKBD_FN_LOCK_LED, data->fn_lock);
+
+	ret = sysfs_create_group(&hdev->dev.kobj, &lenovo_attr_group_tp10ubkbd);
+	if (ret)
+		return ret;
+
+	ret = lenovo_register_leds(hdev);
+	if (ret)
+		goto err;
+
+	return 0;
+err:
+	sysfs_remove_group(&hdev->dev.kobj, &lenovo_attr_group_tp10ubkbd);
+	return ret;
 }
 
 static int lenovo_probe(struct hid_device *hdev,
@@ -993,6 +1060,9 @@ static void lenovo_remove_tp10ubkbd(struct hid_device *hdev)
 
 	led_classdev_unregister(&data->led_micmute);
 	led_classdev_unregister(&data->led_mute);
+
+	sysfs_remove_group(&hdev->dev.kobj, &lenovo_attr_group_tp10ubkbd);
+	cancel_work_sync(&data->fn_lock_sync_work);
 }
 
 static void lenovo_remove(struct hid_device *hdev)
-- 
2.26.2


^ permalink raw reply related	[flat|nested] 7+ messages in thread

* Re: [PATCH 0/5] HID: lenovo: Add ThinkPad 10 Ultrabook Keyboard support
  2020-07-04 13:27 [PATCH 0/5] HID: lenovo: Add ThinkPad 10 Ultrabook Keyboard support Hans de Goede
                   ` (4 preceding siblings ...)
  2020-07-04 13:27 ` [PATCH 5/5] HID: lenovo: Add ThinkPad 10 Ultrabook Keyboard fn_lock support Hans de Goede
@ 2020-07-07 10:43 ` Jiri Kosina
  5 siblings, 0 replies; 7+ messages in thread
From: Jiri Kosina @ 2020-07-07 10:43 UTC (permalink / raw)
  To: Hans de Goede; +Cc: Benjamin Tissoires, linux-input

On Sat, 4 Jul 2020, Hans de Goede wrote:

> This series really only is a small(ish) change. But I had to do some
> refactoring to be able to re-use the existing (mic)mute LED and fn_lock
> sysfs attribute code in hid-lenovo.c. That is done in the first 3 patches
> of the series. Even though there are 5 patches all 5 of them are small and
> hopefully easy to review.

Thanks, it indeed was pretty straightforward thanks to the split. Now in 
for-5.9/lenovo, including the followup fix for F23.

-- 
Jiri Kosina
SUSE Labs


^ permalink raw reply	[flat|nested] 7+ messages in thread

end of thread, other threads:[~2020-07-07 10:43 UTC | newest]

Thread overview: 7+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2020-07-04 13:27 [PATCH 0/5] HID: lenovo: Add ThinkPad 10 Ultrabook Keyboard support Hans de Goede
2020-07-04 13:27 ` [PATCH 1/5] HID: lenovo: Merge tpkbd and cptkbd data structures Hans de Goede
2020-07-04 13:27 ` [PATCH 2/5] HID: lenovo: Factor out generic parts of the LED code Hans de Goede
2020-07-04 13:27 ` [PATCH 3/5] HID: lenovo: Rename fn_lock sysfs attr handlers to make them generic Hans de Goede
2020-07-04 13:27 ` [PATCH 4/5] HID: lenovo: Add ThinkPad 10 Ultrabook Keyboard support Hans de Goede
2020-07-04 13:27 ` [PATCH 5/5] HID: lenovo: Add ThinkPad 10 Ultrabook Keyboard fn_lock support Hans de Goede
2020-07-07 10:43 ` [PATCH 0/5] HID: lenovo: Add ThinkPad 10 Ultrabook Keyboard support Jiri Kosina

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).