linux-iio.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH v2 0/3] Apple iBridge support
@ 2019-06-12  8:33 Ronald Tschalär
  2019-06-12  8:33 ` [PATCH v2 1/3] HID: apple-ibridge: Add Apple iBridge HID driver Ronald Tschalär
                   ` (2 more replies)
  0 siblings, 3 replies; 7+ messages in thread
From: Ronald Tschalär @ 2019-06-12  8:33 UTC (permalink / raw)
  To: Jiri Kosina, Benjamin Tissoires, Jonathan Cameron,
	Hartmut Knaack, Lars-Peter Clausen, Peter Meerwald-Stadler
  Cc: linux-input, linux-iio, linux-kernel

2016 and 2017 MacBook Pro's have a T1 chip that drives the Touch Bar,
ambient light sensor, webcam, and fingerprint sensor; this shows up
as an iBridge USB device in the system. These patches provide initial
support for the Touch Bar and ALS - the webcam is already handled by
existing drivers, and no information is currently known on how to access
the fingerprint sensor (other than it's apparently via one of the extra
interfaces available in the OS X USB configuration).

One thing of note here is that both the ALS and (some of) the Touch Bar
functionality are exposed via the same USB interface (and hence same
hid_device), so both drivers need to share this device. This is solved
by having the iBridge driver create multiple virtual HID devices for
each real HID device to which the Touch Bar and ALS drivers attach, and 
then forwarding the calls between the real and virtual HID devices, so
we end up with a structure like this:

    iBridge (HID) driver    Sub drivers

           --  vhdev0  --   (unbound)
          /
   hdev1  ---  vhdev1  ---  tb-drv
                         /
           --  vhdev2  --
          /
   hdev2  ---  vhdev3  ---  als-drv


Changes in v2:
  - Changed iBridge driver from an MFD driver to a HID driver. Instead
    of creating virtual HID drivers and (de)multiplexing their calls,
    this now create virtual HID devices and (de)multiplexing the
    operations on them. This is from the feedback by Benjamin Tissoires.
  - Applied all feedback on ALS driver from Jonathan Cameron
  - Applied all feedback on Touch Bar driver from Jonathan Cameron
    and Peter Meerwald-Stadler
  - various smaller cleanups

Ronald Tschalär (3):
  HID: apple-ibridge: Add Apple iBridge HID driver.
  HID: apple-ib-tb: Add driver for the Touch Bar on MacBook Pro's.
  iio: light: apple-ib-als: Add driver for ALS on iBridge chip.

 drivers/hid/Kconfig              |   24 +
 drivers/hid/Makefile             |    2 +
 drivers/hid/apple-ib-tb.c        | 1389 ++++++++++++++++++++++++++++++
 drivers/hid/apple-ibridge.c      |  588 +++++++++++++
 drivers/hid/hid-ids.h            |    1 +
 drivers/iio/light/Kconfig        |   12 +
 drivers/iio/light/Makefile       |    1 +
 drivers/iio/light/apple-ib-als.c |  607 +++++++++++++
 include/linux/apple-ibridge.h    |   23 +
 9 files changed, 2647 insertions(+)
 create mode 100644 drivers/hid/apple-ib-tb.c
 create mode 100644 drivers/hid/apple-ibridge.c
 create mode 100644 drivers/iio/light/apple-ib-als.c
 create mode 100644 include/linux/apple-ibridge.h

-- 
2.21.0


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

* [PATCH v2 1/3] HID: apple-ibridge: Add Apple iBridge HID driver.
  2019-06-12  8:33 [PATCH v2 0/3] Apple iBridge support Ronald Tschalär
@ 2019-06-12  8:33 ` Ronald Tschalär
  2019-06-16 14:01   ` Jonathan Cameron
  2019-06-12  8:33 ` [PATCH v2 2/3] HID: apple-ib-tb: Add driver for the Touch Bar on MacBook Pro's Ronald Tschalär
  2019-06-12  8:34 ` [PATCH v2 3/3] iio: light: apple-ib-als: Add driver for ALS on iBridge chip Ronald Tschalär
  2 siblings, 1 reply; 7+ messages in thread
From: Ronald Tschalär @ 2019-06-12  8:33 UTC (permalink / raw)
  To: Jiri Kosina, Benjamin Tissoires, Jonathan Cameron,
	Hartmut Knaack, Lars-Peter Clausen, Peter Meerwald-Stadler
  Cc: linux-input, linux-iio, linux-kernel

The iBridge device provides access to several devices, including:
- the Touch Bar
- the iSight webcam
- the light sensor
- the fingerprint sensor

This driver provides the core support for managing the iBridge device
and the access to the underlying devices. In particular, since the
functionality for the touch bar and light sensor is exposed via USB HID
interfaces, and the same HID device is used for multiple functions, this
driver creates virtual HID devices, one per real HID device and
sub-driver pair (for a total of 4 virtual HID devices). The sub-drivers
then bind to these virtual HID devices.

This way the Touch Bar and ALS drivers can be kept in their own modules,
while at the same time making them look very much like as if they were
connected to the real HID devices; e.g. in particular the Touch Bar
driver is aware of the fact that it is dealing with two HID devices that
need to managed differently.

Signed-off-by: Ronald Tschalär <ronald@innovation.ch>
---
 drivers/hid/Kconfig           |  14 +
 drivers/hid/Makefile          |   1 +
 drivers/hid/apple-ibridge.c   | 585 ++++++++++++++++++++++++++++++++++
 drivers/hid/hid-ids.h         |   1 +
 include/linux/apple-ibridge.h |  23 ++
 5 files changed, 624 insertions(+)
 create mode 100644 drivers/hid/apple-ibridge.c
 create mode 100644 include/linux/apple-ibridge.h

diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
index 4ca0cdfa6b33..545d3691fc1c 100644
--- a/drivers/hid/Kconfig
+++ b/drivers/hid/Kconfig
@@ -135,6 +135,20 @@ config HID_APPLE
 	Say Y here if you want support for keyboards of	Apple iBooks, PowerBooks,
 	MacBooks, MacBook Pros and Apple Aluminum.
 
+config HID_APPLE_IBRIDGE
+	tristate "Apple iBridge"
+	depends on ACPI
+	depends on USB_HID
+	depends on X86 || COMPILE_TEST
+	help
+	  This module provides the core support for the Apple T1 chip found
+	  on recent MacBookPro's, also known as the iBridge. The drivers for
+	  the Touch Bar (apple-ib-tb) and light sensor (apple-ib-als) need to
+	  be enabled separately.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called apple-ibridge.
+
 config HID_APPLEIR
 	tristate "Apple infrared receiver"
 	depends on (USB_HID)
diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile
index 170163b41303..a4da5663a541 100644
--- a/drivers/hid/Makefile
+++ b/drivers/hid/Makefile
@@ -26,6 +26,7 @@ obj-$(CONFIG_HID_ACCUTOUCH)	+= hid-accutouch.o
 obj-$(CONFIG_HID_ALPS)		+= hid-alps.o
 obj-$(CONFIG_HID_ACRUX)		+= hid-axff.o
 obj-$(CONFIG_HID_APPLE)		+= hid-apple.o
+obj-$(CONFIG_HID_APPLE_IBRIDGE)	+= apple-ibridge.o
 obj-$(CONFIG_HID_APPLEIR)	+= hid-appleir.o
 obj-$(CONFIG_HID_ASUS)		+= hid-asus.o
 obj-$(CONFIG_HID_AUREAL)	+= hid-aureal.o
diff --git a/drivers/hid/apple-ibridge.c b/drivers/hid/apple-ibridge.c
new file mode 100644
index 000000000000..565f080a38d6
--- /dev/null
+++ b/drivers/hid/apple-ibridge.c
@@ -0,0 +1,585 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Apple iBridge Driver
+ *
+ * Copyright (c) 2018 Ronald Tschalär
+ */
+
+/**
+ * DOC: Overview
+ *
+ * MacBookPro models with a Touch Bar (13,[23] and 14,[23]) have an Apple
+ * iBridge chip (also known as T1 chip) which exposes the touch bar,
+ * built-in webcam (iSight), ambient light sensor, and Secure Enclave
+ * Processor (SEP) for TouchID. It shows up in the system as a USB device
+ * with 3 configurations: 'Default iBridge Interfaces', 'Default iBridge
+ * Interfaces(OS X)', and 'Default iBridge Interfaces(Recovery)'. While
+ * the second one is used by MacOS to provide the fancy touch bar
+ * functionality with custom buttons etc, this driver just uses the first.
+ *
+ * In the first (default after boot) configuration, 4 usb interfaces are
+ * exposed: 2 related to the webcam, and 2 USB HID interfaces representing
+ * the touch bar and the ambient light sensor. The webcam interfaces are
+ * already handled by the uvcvideo driver; furthermore, the handling of the
+ * input reports when "keys" on the touch bar are pressed is already handled
+ * properly by the generic USB HID core. This leaves the management of the
+ * touch bar modes (e.g. switching between function and special keys when the
+ * FN key is pressed), the touch bar display (dimming and turning off), the
+ * key-remapping when the FN key is pressed, and handling of the light sensor.
+ *
+ * This driver is implemented as a HID driver that creates virtual HID devices
+ * for the ALS and touch bar functionality, and the ALS and touch bar drivers
+ * are HID drivers which in turn attach to these virtual HID devices. This
+ * driver then relays the calls on the real HID devices to the virtual ones,
+ * and visa versa.
+ *
+ * Lastly, this driver also takes care of the power-management for the
+ * iBridge when suspending and resuming.
+ */
+
+#include <linux/acpi.h>
+#include <linux/apple-ibridge.h>
+#include <linux/device.h>
+#include <linux/hid.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/usb.h>
+
+#include "hid-ids.h"
+#include "../hid/usbhid/usbhid.h"
+
+#define APPLEIB_BASIC_CONFIG	1
+
+#define	LOG_DEV(ib_dev)		(&(ib_dev)->acpi_dev->dev)
+
+static struct hid_device_id appleib_sub_hid_ids[] = {
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LINUX_FOUNDATION,
+			 USB_DEVICE_ID_IBRIDGE_TB) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LINUX_FOUNDATION,
+			 USB_DEVICE_ID_IBRIDGE_ALS) },
+};
+
+struct appleib_device {
+	struct acpi_device		*acpi_dev;
+	acpi_handle			asoc_socw;
+};
+
+struct appleib_hid_dev_info {
+	struct hid_device	*hdev;
+	struct hid_device	*sub_hdevs[ARRAY_SIZE(appleib_sub_hid_ids)];
+};
+
+static void appleib_remove_device(struct hid_device *hdev)
+{
+	struct appleib_hid_dev_info *hdev_info = hid_get_drvdata(hdev);
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(hdev_info->sub_hdevs); i++)
+		hid_destroy_device(hdev_info->sub_hdevs[i]);
+
+	hid_set_drvdata(hdev, NULL);
+}
+
+/**
+ * appleib_forward_int_op() - Forward a hid-driver callback to all drivers on
+ * all virtual HID devices attached to the given real HID device.
+ * @hdev the real hid-device
+ * @forward a function that calls the callback on the given driver
+ * @args arguments for the forward function
+ *
+ * This is for callbacks that return a status as an int.
+ *
+ * Returns: 0 on success, or the first error returned by the @forward function.
+ */
+static int appleib_forward_int_op(struct hid_device *hdev,
+				  int (*forward)(struct hid_driver *,
+						 struct hid_device *, void *),
+				  void *args)
+{
+	struct appleib_hid_dev_info *hdev_info = hid_get_drvdata(hdev);
+	struct hid_device *sub_hdev;
+	int rc = 0;
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(hdev_info->sub_hdevs); i++) {
+		sub_hdev = hdev_info->sub_hdevs[i];
+		if (sub_hdev->driver) {
+			rc = forward(sub_hdev->driver, sub_hdev, args);
+			if (rc)
+				break;
+		}
+
+		break;
+	}
+
+	return rc;
+}
+
+static int appleib_hid_raw_event(struct hid_device *hdev,
+				 struct hid_report *report, u8 *data, int size)
+{
+	struct appleib_hid_dev_info *hdev_info = hid_get_drvdata(hdev);
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(hdev_info->sub_hdevs); i++)
+		hid_input_report(hdev_info->sub_hdevs[i], report->type, data,
+				 size, 0);
+
+	return 0;
+}
+
+#ifdef CONFIG_PM
+static int appleib_hid_suspend_fwd(struct hid_driver *drv,
+				   struct hid_device *hdev, void *args)
+{
+	int rc = 0;
+
+	if (drv->suspend)
+		rc = drv->suspend(hdev, *(pm_message_t *)args);
+
+	return rc;
+}
+
+static int appleib_hid_suspend(struct hid_device *hdev, pm_message_t message)
+{
+	return appleib_forward_int_op(hdev, appleib_hid_suspend_fwd, &message);
+}
+
+static int appleib_hid_resume_fwd(struct hid_driver *drv,
+				  struct hid_device *hdev, void *args)
+{
+	int rc = 0;
+
+	if (drv->resume)
+		rc = drv->resume(hdev);
+
+	return rc;
+}
+
+static int appleib_hid_resume(struct hid_device *hdev)
+{
+	return appleib_forward_int_op(hdev, appleib_hid_resume_fwd, NULL);
+}
+
+static int appleib_hid_reset_resume_fwd(struct hid_driver *drv,
+					struct hid_device *hdev, void *args)
+{
+	int rc = 0;
+
+	if (drv->reset_resume)
+		rc = drv->reset_resume(hdev);
+
+	return rc;
+}
+
+static int appleib_hid_reset_resume(struct hid_device *hdev)
+{
+	return appleib_forward_int_op(hdev, appleib_hid_reset_resume_fwd, NULL);
+}
+#endif /* CONFIG_PM */
+
+/**
+ * appleib_find_report_field() - Find the field in the report with the given
+ * usage.
+ * @report: the report to search
+ * @field_usage: the usage of the field to search for
+ *
+ * Returns: the hid field if found, or NULL if none found.
+ */
+struct hid_field *appleib_find_report_field(struct hid_report *report,
+					    unsigned int field_usage)
+{
+	int f, u;
+
+	for (f = 0; f < report->maxfield; f++) {
+		struct hid_field *field = report->field[f];
+
+		if (field->logical == field_usage)
+			return field;
+
+		for (u = 0; u < field->maxusage; u++) {
+			if (field->usage[u].hid == field_usage)
+				return field;
+		}
+	}
+
+	return NULL;
+}
+EXPORT_SYMBOL_GPL(appleib_find_report_field);
+
+/**
+ * appleib_find_hid_field() - Search all the reports of the device for the
+ * field with the given usage.
+ * @hdev: the device whose reports to search
+ * @application: the usage of application collection that the field must
+ *               belong to
+ * @field_usage: the usage of the field to search for
+ *
+ * Returns: the hid field if found, or NULL if none found.
+ */
+struct hid_field *appleib_find_hid_field(struct hid_device *hdev,
+					 unsigned int application,
+					 unsigned int field_usage)
+{
+	static const int report_types[] = { HID_INPUT_REPORT, HID_OUTPUT_REPORT,
+					    HID_FEATURE_REPORT };
+	struct hid_report *report;
+	struct hid_field *field;
+	int t;
+
+	for (t = 0; t < ARRAY_SIZE(report_types); t++) {
+		struct list_head *report_list =
+			    &hdev->report_enum[report_types[t]].report_list;
+		list_for_each_entry(report, report_list, list) {
+			if (report->application != application)
+				continue;
+
+			field = appleib_find_report_field(report, field_usage);
+			if (field)
+				return field;
+		}
+	}
+
+	return NULL;
+}
+EXPORT_SYMBOL_GPL(appleib_find_hid_field);
+
+static int appleib_ll_start(struct hid_device *hdev)
+{
+	return 0;
+}
+
+static void appleib_ll_stop(struct hid_device *hdev)
+{
+}
+
+static int appleib_ll_open(struct hid_device *hdev)
+{
+	// TODO: allow event reporting
+	return 0;
+}
+
+static void appleib_ll_close(struct hid_device *hdev)
+{
+	// TODO: disallow event reporting
+}
+
+static int appleib_ll_power(struct hid_device *hdev, int level)
+{
+	struct appleib_hid_dev_info *hdev_info = hdev->driver_data;
+
+	return hid_hw_power(hdev_info->hdev, level);
+}
+
+static int appleib_ll_parse(struct hid_device *hdev)
+{
+	struct appleib_hid_dev_info *hdev_info = hdev->driver_data;
+
+	return hid_parse_report(hdev, hdev_info->hdev->rdesc,
+				hdev_info->hdev->rsize);
+}
+
+static void appleib_ll_request(struct hid_device *hdev,
+			       struct hid_report *report, int reqtype)
+{
+	struct appleib_hid_dev_info *hdev_info = hdev->driver_data;
+
+	hid_hw_request(hdev_info->hdev, report, reqtype);
+}
+
+static int appleib_ll_wait(struct hid_device *hdev)
+{
+	struct appleib_hid_dev_info *hdev_info = hdev->driver_data;
+
+	hid_hw_wait(hdev_info->hdev);
+	return 0;
+}
+
+static int appleib_ll_raw_request(struct hid_device *hdev,
+				  unsigned char reportnum, __u8 *buf,
+				  size_t len, unsigned char rtype, int reqtype)
+{
+	struct appleib_hid_dev_info *hdev_info = hdev->driver_data;
+
+	return hid_hw_raw_request(hdev_info->hdev, reportnum, buf, len, rtype,
+				  reqtype);
+}
+
+static int appleib_ll_output_report(struct hid_device *hdev, __u8 *buf,
+				    size_t len)
+{
+	struct appleib_hid_dev_info *hdev_info = hdev->driver_data;
+
+	return hid_hw_output_report(hdev_info->hdev, buf, len);
+}
+
+static struct hid_ll_driver appleib_ll_driver = {
+	.start = appleib_ll_start,
+	.stop = appleib_ll_stop,
+	.open = appleib_ll_open,
+	.close = appleib_ll_close,
+	.power = appleib_ll_power,
+	.parse = appleib_ll_parse,
+	.request = appleib_ll_request,
+	.wait = appleib_ll_wait,
+	.raw_request = appleib_ll_raw_request,
+	.output_report = appleib_ll_output_report,
+};
+
+static struct hid_device *
+appleib_add_sub_dev(struct appleib_hid_dev_info *hdev_info,
+		    struct hid_device_id *dev_id)
+{
+	struct hid_device *sub_hdev;
+	int rc;
+
+	sub_hdev = hid_allocate_device();
+	if (IS_ERR(sub_hdev))
+		return sub_hdev;
+
+	sub_hdev->dev.parent = &hdev_info->hdev->dev;
+
+	sub_hdev->bus = dev_id->bus;
+	sub_hdev->group = dev_id->group;
+	sub_hdev->vendor = dev_id->vendor;
+	sub_hdev->product = dev_id->product;
+
+	sub_hdev->ll_driver = &appleib_ll_driver;
+
+	snprintf(sub_hdev->name, sizeof(sub_hdev->name),
+		 "iBridge Virtual HID %s/%04x:%04x",
+		 dev_name(sub_hdev->dev.parent), sub_hdev->vendor,
+		 sub_hdev->product);
+
+	sub_hdev->driver_data = hdev_info;
+
+	rc = hid_add_device(sub_hdev);
+	if (rc) {
+		hid_destroy_device(sub_hdev);
+		return ERR_PTR(rc);
+	}
+
+	return sub_hdev;
+}
+
+static struct appleib_hid_dev_info *appleib_add_device(struct hid_device *hdev)
+{
+	struct appleib_hid_dev_info *hdev_info;
+	int i;
+
+	hdev_info = devm_kzalloc(&hdev->dev, sizeof(*hdev_info), GFP_KERNEL);
+	if (!hdev_info)
+		return ERR_PTR(-ENOMEM);
+
+	hdev_info->hdev = hdev;
+
+	for (i = 0; i < ARRAY_SIZE(hdev_info->sub_hdevs); i++) {
+		hdev_info->sub_hdevs[i] =
+			appleib_add_sub_dev(hdev_info, &appleib_sub_hid_ids[i]);
+
+		if (IS_ERR(hdev_info->sub_hdevs[i])) {
+			while (i-- > 0)
+				hid_destroy_device(hdev_info->sub_hdevs[i]);
+			return (void *)hdev_info->sub_hdevs[i];
+		}
+	}
+
+	return hdev_info;
+}
+
+static int appleib_hid_probe(struct hid_device *hdev,
+			     const struct hid_device_id *id)
+{
+	struct appleib_hid_dev_info *hdev_info;
+	struct usb_device *udev;
+	int rc;
+
+	/* check and set usb config first */
+	udev = hid_to_usb_dev(hdev);
+
+	if (udev->actconfig->desc.bConfigurationValue != APPLEIB_BASIC_CONFIG) {
+		rc = usb_driver_set_configuration(udev, APPLEIB_BASIC_CONFIG);
+		return rc ? rc : -ENODEV;
+	}
+
+	rc = hid_parse(hdev);
+	if (rc) {
+		hid_err(hdev, "ib: hid parse failed (%d)\n", rc);
+		goto error;
+	}
+
+	rc = hid_hw_start(hdev, HID_CONNECT_DRIVER);
+	if (rc) {
+		hid_err(hdev, "ib: hw start failed (%d)\n", rc);
+		goto error;
+	}
+
+	hdev_info = appleib_add_device(hdev);
+	if (IS_ERR(hdev_info)) {
+		rc = PTR_ERR(hdev_info);
+		goto stop_hw;
+	}
+
+	hid_set_drvdata(hdev, hdev_info);
+
+	rc = hid_hw_open(hdev);
+	if (rc) {
+		hid_err(hdev, "ib: failed to open hid: %d\n", rc);
+		goto remove_dev;
+	}
+
+	return 0;
+
+remove_dev:
+	appleib_remove_device(hdev);
+stop_hw:
+	hid_hw_stop(hdev);
+error:
+	return rc;
+}
+
+static void appleib_hid_remove(struct hid_device *hdev)
+{
+	hid_hw_close(hdev);
+	appleib_remove_device(hdev);
+	hid_hw_stop(hdev);
+}
+
+static const struct hid_device_id appleib_hid_ids[] = {
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_IBRIDGE) },
+	{ },
+};
+
+static struct hid_driver appleib_hid_driver = {
+	.name = "apple-ibridge-hid",
+	.id_table = appleib_hid_ids,
+	.probe = appleib_hid_probe,
+	.remove = appleib_hid_remove,
+	.raw_event = appleib_hid_raw_event,
+#ifdef CONFIG_PM
+	.suspend = appleib_hid_suspend,
+	.resume = appleib_hid_resume,
+	.reset_resume = appleib_hid_reset_resume,
+#endif
+};
+
+static struct appleib_device *appleib_alloc_device(struct acpi_device *acpi_dev)
+{
+	struct appleib_device *ib_dev;
+	acpi_status sts;
+
+	ib_dev = devm_kzalloc(&acpi_dev->dev, sizeof(*ib_dev), GFP_KERNEL);
+	if (!ib_dev)
+		return ERR_PTR(-ENOMEM);
+
+	ib_dev->acpi_dev = acpi_dev;
+
+	/* get iBridge acpi power control method for suspend/resume */
+	sts = acpi_get_handle(acpi_dev->handle, "SOCW", &ib_dev->asoc_socw);
+	if (ACPI_FAILURE(sts)) {
+		dev_err(LOG_DEV(ib_dev),
+			"Error getting handle for ASOC.SOCW method: %s\n",
+			acpi_format_exception(sts));
+		return ERR_PTR(-ENXIO);
+	}
+
+	/* ensure iBridge is powered on */
+	sts = acpi_execute_simple_method(ib_dev->asoc_socw, NULL, 1);
+	if (ACPI_FAILURE(sts))
+		dev_warn(LOG_DEV(ib_dev), "SOCW(1) failed: %s\n",
+			 acpi_format_exception(sts));
+
+	return ib_dev;
+}
+
+static int appleib_probe(struct acpi_device *acpi)
+{
+	struct appleib_device *ib_dev;
+	int ret;
+
+	ib_dev = appleib_alloc_device(acpi);
+	if (IS_ERR(ib_dev))
+		return PTR_ERR(ib_dev);
+
+	ret = hid_register_driver(&appleib_hid_driver);
+	if (ret) {
+		dev_err(LOG_DEV(ib_dev), "Error registering hid driver: %d\n",
+			ret);
+		return ret;
+	}
+
+	acpi->driver_data = ib_dev;
+
+	return 0;
+}
+
+static int appleib_remove(struct acpi_device *acpi)
+{
+	hid_unregister_driver(&appleib_hid_driver);
+
+	return 0;
+}
+
+static int appleib_suspend(struct device *dev)
+{
+	struct appleib_device *ib_dev;
+	int rc;
+
+	ib_dev = acpi_driver_data(to_acpi_device(dev));
+
+	rc = acpi_execute_simple_method(ib_dev->asoc_socw, NULL, 0);
+	if (ACPI_FAILURE(rc))
+		dev_warn(dev, "SOCW(0) failed: %s\n",
+			 acpi_format_exception(rc));
+
+	return 0;
+}
+
+static int appleib_resume(struct device *dev)
+{
+	struct appleib_device *ib_dev;
+	int rc;
+
+	ib_dev = acpi_driver_data(to_acpi_device(dev));
+
+	rc = acpi_execute_simple_method(ib_dev->asoc_socw, NULL, 1);
+	if (ACPI_FAILURE(rc))
+		dev_warn(dev, "SOCW(1) failed: %s\n",
+			 acpi_format_exception(rc));
+
+	return 0;
+}
+
+static const struct dev_pm_ops appleib_pm = {
+	.suspend = appleib_suspend,
+	.resume = appleib_resume,
+	.restore = appleib_resume,
+};
+
+static const struct acpi_device_id appleib_acpi_match[] = {
+	{ "APP7777", 0 },
+	{ },
+};
+
+MODULE_DEVICE_TABLE(acpi, appleib_acpi_match);
+
+static struct acpi_driver appleib_driver = {
+	.name		= "apple-ibridge",
+	.class		= "topcase", /* ? */
+	.owner		= THIS_MODULE,
+	.ids		= appleib_acpi_match,
+	.ops		= {
+		.add		= appleib_probe,
+		.remove		= appleib_remove,
+	},
+	.drv		= {
+		.pm		= &appleib_pm,
+	},
+};
+
+module_acpi_driver(appleib_driver)
+
+MODULE_AUTHOR("Ronald Tschalär");
+MODULE_DESCRIPTION("Apple iBridge driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
index adce58f24f76..f963bb02c477 100644
--- a/drivers/hid/hid-ids.h
+++ b/drivers/hid/hid-ids.h
@@ -177,6 +177,7 @@
 #define USB_DEVICE_ID_APPLE_IRCONTROL3	0x8241
 #define USB_DEVICE_ID_APPLE_IRCONTROL4	0x8242
 #define USB_DEVICE_ID_APPLE_IRCONTROL5	0x8243
+#define USB_DEVICE_ID_APPLE_IBRIDGE	0x8600
 
 #define USB_VENDOR_ID_ASUS		0x0486
 #define USB_DEVICE_ID_ASUS_T91MT	0x0185
diff --git a/include/linux/apple-ibridge.h b/include/linux/apple-ibridge.h
new file mode 100644
index 000000000000..07ded8c68776
--- /dev/null
+++ b/include/linux/apple-ibridge.h
@@ -0,0 +1,23 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Apple iBridge Driver
+ *
+ * Copyright (c) 2018 Ronald Tschalär
+ */
+
+#ifndef __LINUX_APPLE_IBRDIGE_H
+#define __LINUX_APPLE_IBRDIGE_H
+
+#include <linux/hid.h>
+
+#define USB_VENDOR_ID_LINUX_FOUNDATION	0x1d6b
+#define USB_DEVICE_ID_IBRIDGE_TB	0x0301
+#define USB_DEVICE_ID_IBRIDGE_ALS	0x0302
+
+struct hid_field *appleib_find_report_field(struct hid_report *report,
+					    unsigned int field_usage);
+struct hid_field *appleib_find_hid_field(struct hid_device *hdev,
+					 unsigned int application,
+					 unsigned int field_usage);
+
+#endif
-- 
2.21.0


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

* [PATCH v2 2/3] HID: apple-ib-tb: Add driver for the Touch Bar on MacBook Pro's.
  2019-06-12  8:33 [PATCH v2 0/3] Apple iBridge support Ronald Tschalär
  2019-06-12  8:33 ` [PATCH v2 1/3] HID: apple-ibridge: Add Apple iBridge HID driver Ronald Tschalär
@ 2019-06-12  8:33 ` Ronald Tschalär
  2019-06-16 14:28   ` Jonathan Cameron
  2019-06-12  8:34 ` [PATCH v2 3/3] iio: light: apple-ib-als: Add driver for ALS on iBridge chip Ronald Tschalär
  2 siblings, 1 reply; 7+ messages in thread
From: Ronald Tschalär @ 2019-06-12  8:33 UTC (permalink / raw)
  To: Jiri Kosina, Benjamin Tissoires, Jonathan Cameron,
	Hartmut Knaack, Lars-Peter Clausen, Peter Meerwald-Stadler
  Cc: linux-input, linux-iio, linux-kernel

This driver enables basic touch bar functionality: enabling it, switching
between modes on FN key press, and dimming and turning the display
off/on when idle/active.

Signed-off-by: Ronald Tschalär <ronald@innovation.ch>
---
 drivers/hid/Kconfig       |   10 +
 drivers/hid/Makefile      |    1 +
 drivers/hid/apple-ib-tb.c | 1389 +++++++++++++++++++++++++++++++++++++
 3 files changed, 1400 insertions(+)
 create mode 100644 drivers/hid/apple-ib-tb.c

diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
index 545d3691fc1c..7621c2500d71 100644
--- a/drivers/hid/Kconfig
+++ b/drivers/hid/Kconfig
@@ -149,6 +149,16 @@ config HID_APPLE_IBRIDGE
 	  To compile this driver as a module, choose M here: the
 	  module will be called apple-ibridge.
 
+config HID_APPLE_IBRIDGE_TB
+	tristate "Apple iBridge Touch Bar"
+	depends on HID_APPLE_IBRIDGE
+	---help---
+	Say Y here if you want support for the Touch Bar on recent
+	MacBook Pros.
+
+	To compile this driver as a module, choose M here: the
+	module will be called apple-ib-tb.
+
 config HID_APPLEIR
 	tristate "Apple infrared receiver"
 	depends on (USB_HID)
diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile
index a4da5663a541..0c46e5f70db1 100644
--- a/drivers/hid/Makefile
+++ b/drivers/hid/Makefile
@@ -27,6 +27,7 @@ obj-$(CONFIG_HID_ALPS)		+= hid-alps.o
 obj-$(CONFIG_HID_ACRUX)		+= hid-axff.o
 obj-$(CONFIG_HID_APPLE)		+= hid-apple.o
 obj-$(CONFIG_HID_APPLE_IBRIDGE)	+= apple-ibridge.o
+obj-$(CONFIG_HID_APPLE_IBRIDGE_TB)	+= apple-ib-tb.o
 obj-$(CONFIG_HID_APPLEIR)	+= hid-appleir.o
 obj-$(CONFIG_HID_ASUS)		+= hid-asus.o
 obj-$(CONFIG_HID_AUREAL)	+= hid-aureal.o
diff --git a/drivers/hid/apple-ib-tb.c b/drivers/hid/apple-ib-tb.c
new file mode 100644
index 000000000000..6daee80060ce
--- /dev/null
+++ b/drivers/hid/apple-ib-tb.c
@@ -0,0 +1,1389 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Apple Touch Bar Driver
+ *
+ * Copyright (c) 2017-2018 Ronald Tschalär
+ */
+
+/*
+ * Recent MacBookPro models (13,[23] and 14,[23]) have a touch bar, which
+ * is exposed via several USB interfaces. MacOS supports a fancy mode
+ * where arbitrary buttons can be defined; this driver currently only
+ * supports the simple mode that consists of 3 predefined layouts
+ * (escape-only, esc + special keys, and esc + function keys).
+ *
+ * The first USB HID interface supports two reports, an input report that
+ * is used to report the key presses, and an output report which can be
+ * used to set the touch bar "mode": touch bar off (in which case no touches
+ * are reported at all), escape key only, escape + 12 function keys, and
+ * escape + several special keys (including brightness, audio volume,
+ * etc). The second interface supports several, complex reports, most of
+ * which are unknown at this time, but one of which has been determined to
+ * allow for controlling of the touch bar's brightness: off (though touches
+ * are still reported), dimmed, and full brightness. This driver makes
+ * use of these two reports.
+ */
+
+#define dev_fmt(fmt) "tb: " fmt
+
+#include <linux/apple-ibridge.h>
+#include <linux/device.h>
+#include <linux/hid.h>
+#include <linux/input.h>
+#include <linux/jiffies.h>
+#include <linux/ktime.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/string.h>
+#include <linux/sysfs.h>
+#include <linux/usb/ch9.h>
+#include <linux/usb.h>
+#include <linux/workqueue.h>
+
+#define HID_UP_APPLE		0xff120000
+#define HID_USAGE_MODE		(HID_UP_CUSTOM | 0x0004)
+#define HID_USAGE_APPLE_APP	(HID_UP_APPLE  | 0x0001)
+#define HID_USAGE_DISP		(HID_UP_APPLE  | 0x0021)
+#define HID_USAGE_DISP_AUX1	(HID_UP_APPLE  | 0x0020)
+
+#define APPLETB_MAX_TB_KEYS	13	/* ESC, F1-F12 */
+
+#define APPLETB_CMD_MODE_ESC	0
+#define APPLETB_CMD_MODE_FN	1
+#define APPLETB_CMD_MODE_SPCL	2
+#define APPLETB_CMD_MODE_OFF	3
+#define APPLETB_CMD_MODE_UPD	254
+#define APPLETB_CMD_MODE_NONE	255
+
+#define APPLETB_CMD_DISP_ON	1
+#define APPLETB_CMD_DISP_DIM	2
+#define APPLETB_CMD_DISP_OFF	4
+#define APPLETB_CMD_DISP_UPD	254
+#define APPLETB_CMD_DISP_NONE	255
+
+#define APPLETB_FN_MODE_FKEYS	0
+#define APPLETB_FN_MODE_NORM	1
+#define APPLETB_FN_MODE_INV	2
+#define APPLETB_FN_MODE_SPCL	3
+#define APPLETB_FN_MODE_MAX	APPLETB_FN_MODE_SPCL
+
+#define APPLETB_DEVID_KEYBOARD	1
+#define APPLETB_DEVID_TOUCHPAD	2
+
+#define APPLETB_MAX_DIM_TIME	30
+
+static int appletb_tb_def_idle_timeout = 5 * 60;
+module_param_named(idle_timeout, appletb_tb_def_idle_timeout, int, 0444);
+MODULE_PARM_DESC(idle_timeout, "Default touch bar idle timeout:\n"
+			       "    >0 - turn touch bar display off after no keyboard, trackpad, or touch bar input has been received for this many seconds;\n"
+			       "         the display will be turned back on as soon as new input is received\n"
+			       "     0 - turn touch bar display off (input does not turn it on again)\n"
+			       "    -1 - turn touch bar display on (does not turn off automatically)\n"
+			       "    -2 - disable touch bar completely");
+
+static int appletb_tb_def_dim_timeout = -2;
+module_param_named(dim_timeout, appletb_tb_def_dim_timeout, int, 0444);
+MODULE_PARM_DESC(dim_timeout, "Default touch bar dim timeout:\n"
+			      "    >0 - dim touch bar display after no keyboard, trackpad, or touch bar input has been received for this many seconds\n"
+			      "         the display will be returned to full brightness as soon as new input is received\n"
+			      "     0 - dim touch bar display (input does not return it to full brightness)\n"
+			      "    -1 - disable timeout (touch bar never dimmed)\n"
+			      "    [-2] - calculate timeout based on idle-timeout");
+
+static int appletb_tb_def_fn_mode = APPLETB_FN_MODE_NORM;
+module_param_named(fnmode, appletb_tb_def_fn_mode, int, 0444);
+MODULE_PARM_DESC(fnmode, "Default Fn key mode:\n"
+			 "    0 - function-keys only\n"
+			 "    [1] - fn key switches from special to function-keys\n"
+			 "    2 - inverse of 1\n"
+			 "    3 - special keys only");
+
+static ssize_t idle_timeout_show(struct device *dev,
+				 struct device_attribute *attr, char *buf);
+static ssize_t idle_timeout_store(struct device *dev,
+				  struct device_attribute *attr,
+				  const char *buf, size_t size);
+static DEVICE_ATTR_RW(idle_timeout);
+
+static ssize_t dim_timeout_show(struct device *dev,
+				struct device_attribute *attr, char *buf);
+static ssize_t dim_timeout_store(struct device *dev,
+				 struct device_attribute *attr,
+				 const char *buf, size_t size);
+static DEVICE_ATTR_RW(dim_timeout);
+
+static ssize_t fnmode_show(struct device *dev, struct device_attribute *attr,
+			   char *buf);
+static ssize_t fnmode_store(struct device *dev, struct device_attribute *attr,
+			    const char *buf, size_t size);
+static DEVICE_ATTR_RW(fnmode);
+
+static struct attribute *appletb_attrs[] = {
+	&dev_attr_idle_timeout.attr,
+	&dev_attr_dim_timeout.attr,
+	&dev_attr_fnmode.attr,
+	NULL,
+};
+
+static const struct attribute_group appletb_attr_group = {
+	.attrs = appletb_attrs,
+};
+
+struct appletb_device {
+	bool			active;
+	struct device		*log_dev;
+
+	struct hid_field	*mode_field;
+	struct hid_field	*disp_field;
+	struct hid_field	*disp_field_aux1;
+	struct appletb_iface_info {
+		struct hid_device	*hdev;
+		struct usb_interface	*usb_iface;
+		bool			suspended;
+	}			mode_iface, disp_iface;
+
+	struct input_handler	inp_handler;
+	struct input_handle	kbd_handle;
+	struct input_handle	tpd_handle;
+
+	bool			last_tb_keys_pressed[APPLETB_MAX_TB_KEYS];
+	bool			last_tb_keys_translated[APPLETB_MAX_TB_KEYS];
+	bool			last_fn_pressed;
+
+	ktime_t			last_event_time;
+
+	unsigned char		cur_tb_mode;
+	unsigned char		pnd_tb_mode;
+	unsigned char		cur_tb_disp;
+	unsigned char		pnd_tb_disp;
+	bool			tb_autopm_off;
+	bool			restore_autopm;
+	struct delayed_work	tb_work;
+	/* protects most of the above */
+	spinlock_t		tb_lock;
+
+	int			dim_timeout;
+	int			idle_timeout;
+	bool			dim_to_is_calc;
+	int			fn_mode;
+};
+
+struct appletb_key_translation {
+	u16 from;
+	u16 to;
+};
+
+static const struct appletb_key_translation appletb_fn_codes[] = {
+	{ KEY_F1,  KEY_BRIGHTNESSDOWN },
+	{ KEY_F2,  KEY_BRIGHTNESSUP },
+	{ KEY_F3,  KEY_SCALE },		/* not used */
+	{ KEY_F4,  KEY_DASHBOARD },	/* not used */
+	{ KEY_F5,  KEY_KBDILLUMDOWN },
+	{ KEY_F6,  KEY_KBDILLUMUP },
+	{ KEY_F7,  KEY_PREVIOUSSONG },
+	{ KEY_F8,  KEY_PLAYPAUSE },
+	{ KEY_F9,  KEY_NEXTSONG },
+	{ KEY_F10, KEY_MUTE },
+	{ KEY_F11, KEY_VOLUMEDOWN },
+	{ KEY_F12, KEY_VOLUMEUP },
+};
+
+static struct appletb_device *appletb_dev;
+
+static int appletb_send_usb_ctrl(struct appletb_iface_info *iface_info,
+				 __u8 requesttype, struct hid_report *report,
+				 void *data, __u16 size)
+{
+	struct usb_device *dev = interface_to_usbdev(iface_info->usb_iface);
+	u8 ifnum = iface_info->usb_iface->cur_altsetting->desc.bInterfaceNumber;
+	int tries = 0;
+	int rc;
+
+	do {
+		rc = usb_control_msg(dev, usb_sndctrlpipe(dev, 0),
+				     HID_REQ_SET_REPORT, requesttype,
+				     (report->type + 1) << 8 | report->id,
+				     ifnum, data, size, 2000);
+		if (rc != -EPIPE)
+			break;
+
+		usleep_range(1000 << tries, 3000 << tries);
+	} while (++tries < 5);
+
+	return (rc > 0) ? 0 : rc;
+}
+
+static bool appletb_disable_autopm(struct hid_device *hdev)
+{
+	int rc;
+
+	rc = hid_hw_power(hdev, PM_HINT_FULLON);
+
+	if (rc == 0)
+		return true;
+
+	hid_err(hdev,
+		"Failed to disable auto-pm on touch bar device (%d)\n", rc);
+	return false;
+}
+
+/*
+ * While the mode functionality is listed as a valid hid report in the usb
+ * interface descriptor, it's not sent that way. Instead it's sent with
+ * different request-type and without a leading report-id in the data. Hence
+ * we need to send it as a custom usb control message rather via any of the
+ * standard hid_hw_*request() functions.
+ */
+static int appletb_set_tb_mode(struct appletb_device *tb_dev,
+			       unsigned char mode)
+{
+	void *buf;
+	bool autopm_off = false;
+	int rc;
+
+	if (!tb_dev->mode_iface.hdev)
+		return -ENOTCONN;
+
+	buf = kmemdup(&mode, 1, GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
+
+	autopm_off = appletb_disable_autopm(tb_dev->mode_iface.hdev);
+
+	rc = appletb_send_usb_ctrl(&tb_dev->mode_iface,
+				   USB_DIR_OUT | USB_TYPE_VENDOR |
+							USB_RECIP_DEVICE,
+				   tb_dev->mode_field->report, buf, 1);
+	if (rc < 0)
+		dev_err(tb_dev->log_dev,
+			"Failed to set touch bar mode to %u (%d)\n", mode, rc);
+
+	if (autopm_off)
+		hid_hw_power(tb_dev->mode_iface.hdev, PM_HINT_NORMAL);
+
+	kfree(buf);
+
+	return rc;
+}
+
+/*
+ * We don't use hid_hw_request() because that doesn't allow us to get the
+ * returned status from the usb-control request; we also don't use
+ * hid_hw_raw_request() because would mean duplicating the retry-on-EPIPE
+ * in our appletb_send_usb_ctrl().
+ */
+static int appletb_send_hid_report(struct appletb_iface_info *iface_info,
+				   struct hid_report *report)
+{
+	unsigned char *buf;
+	int rc;
+
+	buf = hid_alloc_report_buf(report, GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
+
+	hid_output_report(report, buf);
+
+	rc = appletb_send_usb_ctrl(iface_info,
+				   USB_DIR_OUT | USB_TYPE_CLASS |
+							USB_RECIP_INTERFACE,
+				   report, buf, hid_report_len(report));
+
+	kfree(buf);
+
+	return rc;
+}
+
+static int appletb_set_tb_disp(struct appletb_device *tb_dev,
+			       unsigned char disp)
+{
+	struct hid_report *report;
+	int rc;
+
+	if (!tb_dev->disp_iface.hdev)
+		return -ENOTCONN;
+
+	report = tb_dev->disp_field->report;
+
+	rc = hid_set_field(tb_dev->disp_field_aux1, 0, 1);
+	if (!rc)
+		rc = hid_set_field(tb_dev->disp_field, 0, disp);
+	if (rc) {
+		dev_err(tb_dev->log_dev,
+			"Failed to set display report fields (%d)\n", rc);
+		return rc;
+	}
+
+	/*
+	 * Keep the USB interface powered on while the touch bar display is on
+	 * for better responsiveness.
+	 */
+	if (disp != APPLETB_CMD_DISP_OFF && !tb_dev->tb_autopm_off)
+		tb_dev->tb_autopm_off =
+			appletb_disable_autopm(report->device);
+
+	rc = appletb_send_hid_report(&tb_dev->disp_iface, report);
+
+	if (rc < 0)
+		dev_err(tb_dev->log_dev,
+			"Failed to set touch bar display to %u (%d)\n", disp,
+			rc);
+
+	if (disp == APPLETB_CMD_DISP_OFF && tb_dev->tb_autopm_off) {
+		hid_hw_power(tb_dev->disp_iface.hdev, PM_HINT_NORMAL);
+		tb_dev->tb_autopm_off = false;
+	}
+
+	return rc;
+}
+
+static bool appletb_any_tb_key_pressed(struct appletb_device *tb_dev)
+{
+	return !!memchr_inv(tb_dev->last_tb_keys_pressed, 0,
+			    sizeof(tb_dev->last_tb_keys_pressed));
+}
+
+static void appletb_schedule_tb_update(struct appletb_device *tb_dev, s64 secs)
+{
+	schedule_delayed_work(&tb_dev->tb_work, msecs_to_jiffies(secs * 1000));
+}
+
+static void appletb_set_tb_worker(struct work_struct *work)
+{
+	struct appletb_device *tb_dev =
+		container_of(work, struct appletb_device, tb_work.work);
+	s64 time_left = 0, min_timeout, time_to_off;
+	unsigned char pending_mode;
+	unsigned char pending_disp;
+	unsigned char current_disp;
+	bool restore_autopm;
+	bool any_tb_key_pressed, need_reschedule;
+	int rc1 = 1, rc2 = 1;
+	unsigned long flags;
+
+	spin_lock_irqsave(&tb_dev->tb_lock, flags);
+
+	/* handle explicit mode-change request */
+	pending_mode = tb_dev->pnd_tb_mode;
+	pending_disp = tb_dev->pnd_tb_disp;
+	restore_autopm = tb_dev->restore_autopm;
+
+	spin_unlock_irqrestore(&tb_dev->tb_lock, flags);
+
+	if (pending_mode != APPLETB_CMD_MODE_NONE)
+		rc1 = appletb_set_tb_mode(tb_dev, pending_mode);
+	if (pending_mode != APPLETB_CMD_MODE_NONE &&
+	    pending_disp != APPLETB_CMD_DISP_NONE)
+		msleep(25);
+	if (pending_disp != APPLETB_CMD_DISP_NONE)
+		rc2 = appletb_set_tb_disp(tb_dev, pending_disp);
+
+	if (restore_autopm && tb_dev->tb_autopm_off)
+		appletb_disable_autopm(tb_dev->disp_field->report->device);
+
+	spin_lock_irqsave(&tb_dev->tb_lock, flags);
+
+	need_reschedule = false;
+
+	if (rc1 == 0) {
+		tb_dev->cur_tb_mode = pending_mode;
+
+		if (tb_dev->pnd_tb_mode == pending_mode)
+			tb_dev->pnd_tb_mode = APPLETB_CMD_MODE_NONE;
+		else
+			need_reschedule = true;
+	}
+
+	if (rc2 == 0) {
+		tb_dev->cur_tb_disp = pending_disp;
+
+		if (tb_dev->pnd_tb_disp == pending_disp)
+			tb_dev->pnd_tb_disp = APPLETB_CMD_DISP_NONE;
+		else
+			need_reschedule = true;
+	}
+	current_disp = tb_dev->cur_tb_disp;
+
+	tb_dev->restore_autopm = false;
+
+	/* calculate time left to next timeout */
+	if (tb_dev->idle_timeout == -2 || tb_dev->idle_timeout == 0)
+		min_timeout = -1;
+	else if (tb_dev->idle_timeout == -1)
+		min_timeout = tb_dev->dim_timeout;
+	else if (tb_dev->dim_timeout <= 0)
+		min_timeout = tb_dev->idle_timeout;
+	else
+		min_timeout = min(tb_dev->dim_timeout, tb_dev->idle_timeout);
+
+	if (min_timeout > 0) {
+		s64 idle_time =
+			(ktime_ms_delta(ktime_get(), tb_dev->last_event_time) +
+			 500) / 1000;
+
+		time_left = max(min_timeout - idle_time, 0LL);
+		if (tb_dev->idle_timeout <= 0)
+			time_to_off = -1;
+		else if (idle_time >= tb_dev->idle_timeout)
+			time_to_off = 0;
+		else
+			time_to_off = tb_dev->idle_timeout - idle_time;
+	} else {
+		/* not used - just to appease the compiler */
+		time_to_off = 0;
+	}
+
+	any_tb_key_pressed = appletb_any_tb_key_pressed(tb_dev);
+
+	spin_unlock_irqrestore(&tb_dev->tb_lock, flags);
+
+	dev_dbg(tb_dev->log_dev, "timeout calc: idle_timeout=%d dim_timeout=%d min_timeout=%lld time_left=%lld need_reschedule=%d any_tb_key_pressed=%d\n",
+		tb_dev->idle_timeout, tb_dev->dim_timeout, min_timeout,
+		time_left, need_reschedule, any_tb_key_pressed);
+
+	/* a new command arrived while we were busy - handle it */
+	if (need_reschedule) {
+		appletb_schedule_tb_update(tb_dev, 0);
+		return;
+	}
+
+	/* if no idle/dim timeout, we're done */
+	if (min_timeout <= 0)
+		return;
+
+	/* manage idle/dim timeout */
+	if (time_left > 0) {
+		/* we fired too soon or had a mode-change - re-schedule */
+		appletb_schedule_tb_update(tb_dev, time_left);
+	} else if (any_tb_key_pressed) {
+		/* keys are still pressed - re-schedule */
+		appletb_schedule_tb_update(tb_dev, min_timeout);
+	} else {
+		/* dim or idle timeout reached */
+		int next_disp = (time_to_off == 0) ? APPLETB_CMD_DISP_OFF :
+						     APPLETB_CMD_DISP_DIM;
+		if (next_disp != current_disp &&
+		    appletb_set_tb_disp(tb_dev, next_disp) == 0) {
+			spin_lock_irqsave(&tb_dev->tb_lock, flags);
+			tb_dev->cur_tb_disp = next_disp;
+			spin_unlock_irqrestore(&tb_dev->tb_lock, flags);
+		}
+
+		if (time_to_off > 0)
+			appletb_schedule_tb_update(tb_dev, time_to_off);
+	}
+}
+
+static u16 appletb_fn_to_special(u16 code)
+{
+	int idx;
+
+	for (idx = 0; idx < ARRAY_SIZE(appletb_fn_codes); idx++) {
+		if (appletb_fn_codes[idx].from == code)
+			return appletb_fn_codes[idx].to;
+	}
+
+	return 0;
+}
+
+static unsigned char appletb_get_cur_tb_mode(struct appletb_device *tb_dev)
+{
+	return tb_dev->pnd_tb_mode != APPLETB_CMD_MODE_NONE ?
+				tb_dev->pnd_tb_mode : tb_dev->cur_tb_mode;
+}
+
+static unsigned char appletb_get_cur_tb_disp(struct appletb_device *tb_dev)
+{
+	return tb_dev->pnd_tb_disp != APPLETB_CMD_DISP_NONE ?
+				tb_dev->pnd_tb_disp : tb_dev->cur_tb_disp;
+}
+
+static unsigned char appletb_get_fn_tb_mode(struct appletb_device *tb_dev)
+{
+	switch (tb_dev->fn_mode) {
+	case APPLETB_FN_MODE_FKEYS:
+		return APPLETB_CMD_MODE_FN;
+
+	case APPLETB_FN_MODE_SPCL:
+		return APPLETB_CMD_MODE_SPCL;
+
+	case APPLETB_FN_MODE_INV:
+		return (tb_dev->last_fn_pressed) ? APPLETB_CMD_MODE_SPCL :
+						   APPLETB_CMD_MODE_FN;
+
+	case APPLETB_FN_MODE_NORM:
+	default:
+		return (tb_dev->last_fn_pressed) ? APPLETB_CMD_MODE_FN :
+						   APPLETB_CMD_MODE_SPCL;
+	}
+}
+
+/*
+ * Switch touch bar mode and display when mode or display not the desired ones.
+ */
+static void appletb_update_touchbar_no_lock(struct appletb_device *tb_dev,
+					    bool force)
+{
+	unsigned char want_mode;
+	unsigned char want_disp;
+	bool need_update = false;
+
+	/*
+	 * Calculate the new modes:
+	 *   idle_timeout:
+	 *     -2  mode/disp off
+	 *     -1  mode on, disp on/dim
+	 *      0  mode on, disp off
+	 *     >0  mode on, disp off after idle_timeout seconds
+	 *   dim_timeout (only valid if idle_timeout > 0 || idle_timeout == -1):
+	 *     -1  disp never dimmed
+	 *      0  disp always dimmed
+	 *     >0  disp dim after dim_timeout seconds
+	 */
+	if (tb_dev->idle_timeout == -2) {
+		want_mode = APPLETB_CMD_MODE_OFF;
+		want_disp = APPLETB_CMD_DISP_OFF;
+	} else {
+		want_mode = appletb_get_fn_tb_mode(tb_dev);
+		want_disp = tb_dev->idle_timeout ==  0 ? APPLETB_CMD_DISP_OFF :
+			    tb_dev->dim_timeout  ==  0 ? APPLETB_CMD_DISP_DIM :
+							 APPLETB_CMD_DISP_ON;
+	}
+
+	/*
+	 * See if we need to update the touch bar, taking into account that we
+	 * generally don't want to switch modes while a touch bar key is
+	 * pressed.
+	 */
+	if (appletb_get_cur_tb_mode(tb_dev) != want_mode &&
+	    !appletb_any_tb_key_pressed(tb_dev)) {
+		tb_dev->pnd_tb_mode = want_mode;
+		need_update = true;
+	}
+
+	if (appletb_get_cur_tb_disp(tb_dev) != want_disp &&
+	    (!appletb_any_tb_key_pressed(tb_dev) ||
+	     want_disp != APPLETB_CMD_DISP_OFF)) {
+		tb_dev->pnd_tb_disp = want_disp;
+		need_update = true;
+	}
+
+	if (force)
+		need_update = true;
+
+	/* schedule the update if desired */
+	dev_dbg_ratelimited(tb_dev->log_dev,
+			    "update: need_update=%d, want_mode=%d, cur-mode=%d, want_disp=%d, cur-disp=%d\n",
+			    need_update, want_mode, tb_dev->cur_tb_mode,
+			    want_disp, tb_dev->cur_tb_disp);
+
+	if (need_update) {
+		cancel_delayed_work(&tb_dev->tb_work);
+		appletb_schedule_tb_update(tb_dev, 0);
+	}
+}
+
+static void appletb_update_touchbar(struct appletb_device *tb_dev, bool force)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&tb_dev->tb_lock, flags);
+
+	if (tb_dev->active)
+		appletb_update_touchbar_no_lock(tb_dev, force);
+
+	spin_unlock_irqrestore(&tb_dev->tb_lock, flags);
+}
+
+static void appletb_set_idle_timeout(struct appletb_device *tb_dev, int new)
+{
+	tb_dev->idle_timeout = new;
+
+	if (tb_dev->dim_to_is_calc && tb_dev->idle_timeout > 0)
+		tb_dev->dim_timeout = new - min(APPLETB_MAX_DIM_TIME, new / 3);
+	else if (tb_dev->dim_to_is_calc)
+		tb_dev->dim_timeout = -1;
+}
+
+static ssize_t idle_timeout_show(struct device *dev,
+				 struct device_attribute *attr, char *buf)
+{
+	struct appletb_device *tb_dev = dev_get_drvdata(dev);
+
+	return snprintf(buf, PAGE_SIZE, "%d\n", tb_dev->idle_timeout);
+}
+
+static ssize_t idle_timeout_store(struct device *dev,
+				  struct device_attribute *attr,
+				  const char *buf, size_t size)
+{
+	struct appletb_device *tb_dev = dev_get_drvdata(dev);
+	long new;
+	int rc;
+
+	rc = kstrtol(buf, 0, &new);
+	if (rc || new > INT_MAX || new < -2)
+		return -EINVAL;
+
+	appletb_set_idle_timeout(tb_dev, new);
+	appletb_update_touchbar(tb_dev, true);
+
+	return size;
+}
+
+static void appletb_set_dim_timeout(struct appletb_device *tb_dev, int new)
+{
+	if (new == -2) {
+		tb_dev->dim_to_is_calc = true;
+		appletb_set_idle_timeout(tb_dev, tb_dev->idle_timeout);
+	} else {
+		tb_dev->dim_to_is_calc = false;
+		tb_dev->dim_timeout = new;
+	}
+}
+
+static ssize_t dim_timeout_show(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	struct appletb_device *tb_dev = dev_get_drvdata(dev);
+
+	return snprintf(buf, PAGE_SIZE, "%d\n",
+			tb_dev->dim_to_is_calc ? -2 : tb_dev->dim_timeout);
+}
+
+static ssize_t dim_timeout_store(struct device *dev,
+				 struct device_attribute *attr,
+				 const char *buf, size_t size)
+{
+	struct appletb_device *tb_dev = dev_get_drvdata(dev);
+	long new;
+	int rc;
+
+	rc = kstrtol(buf, 0, &new);
+	if (rc || new > INT_MAX || new < -2)
+		return -EINVAL;
+
+	appletb_set_dim_timeout(tb_dev, new);
+	appletb_update_touchbar(tb_dev, true);
+
+	return size;
+}
+
+static ssize_t fnmode_show(struct device *dev, struct device_attribute *attr,
+			   char *buf)
+{
+	struct appletb_device *tb_dev = dev_get_drvdata(dev);
+
+	return snprintf(buf, PAGE_SIZE, "%d\n", tb_dev->fn_mode);
+}
+
+static ssize_t fnmode_store(struct device *dev, struct device_attribute *attr,
+			    const char *buf, size_t size)
+{
+	struct appletb_device *tb_dev = dev_get_drvdata(dev);
+	long new;
+	int rc;
+
+	rc = kstrtol(buf, 0, &new);
+	if (rc || new > APPLETB_FN_MODE_MAX || new < 0)
+		return -EINVAL;
+
+	tb_dev->fn_mode = new;
+	appletb_update_touchbar(tb_dev, false);
+
+	return size;
+}
+
+static int appletb_tb_key_to_slot(unsigned int code)
+{
+	switch (code) {
+	case KEY_ESC:
+		return 0;
+	case KEY_F1:
+	case KEY_F2:
+	case KEY_F3:
+	case KEY_F4:
+	case KEY_F5:
+	case KEY_F6:
+	case KEY_F7:
+	case KEY_F8:
+	case KEY_F9:
+	case KEY_F10:
+		return code - KEY_F1 + 1;
+	case KEY_F11:
+	case KEY_F12:
+		return code - KEY_F11 + 11;
+	default:
+		return -1;
+	}
+}
+
+static int appletb_hid_event(struct hid_device *hdev, struct hid_field *field,
+			     struct hid_usage *usage, __s32 value)
+{
+	struct appletb_device *tb_dev = hid_get_drvdata(hdev);
+	unsigned int new_code = 0;
+	unsigned long flags;
+	bool send_dummy = false;
+	bool send_trnsl = false;
+	int slot;
+	int rc = 0;
+
+	if ((usage->hid & HID_USAGE_PAGE) != HID_UP_KEYBOARD ||
+	    usage->type != EV_KEY)
+		return 0;
+
+	/*
+	 * Skip non-touch-bar keys.
+	 *
+	 * Either the touch bar itself or usbhid generate a slew of key-down
+	 * events for all the meta keys. None of which we're at all interested
+	 * in.
+	 */
+	slot = appletb_tb_key_to_slot(usage->code);
+	if (slot < 0)
+		return 0;
+
+	spin_lock_irqsave(&tb_dev->tb_lock, flags);
+
+	if (!tb_dev->active) {
+		spin_unlock_irqrestore(&tb_dev->tb_lock, flags);
+		return 0;
+	}
+
+	new_code = appletb_fn_to_special(usage->code);
+
+	if (value != 2)
+		tb_dev->last_tb_keys_pressed[slot] = value;
+
+	tb_dev->last_event_time = ktime_get();
+
+	appletb_update_touchbar_no_lock(tb_dev, false);
+
+	/*
+	 * We want to suppress touch bar keys while the touch bar is off, but
+	 * we do want to wake up the screen if it's asleep, so generate a dummy
+	 * event in that case.
+	 */
+	if (tb_dev->cur_tb_mode == APPLETB_CMD_MODE_OFF ||
+	    tb_dev->cur_tb_disp == APPLETB_CMD_DISP_OFF) {
+		send_dummy = true;
+		rc = 1;
+	/* translate special keys */
+	} else if (new_code &&
+		   ((value > 0 &&
+		     appletb_get_cur_tb_mode(tb_dev) == APPLETB_CMD_MODE_SPCL)
+		    ||
+		    (value == 0 && tb_dev->last_tb_keys_translated[slot]))) {
+		tb_dev->last_tb_keys_translated[slot] = true;
+		send_trnsl = true;
+		rc = 1;
+	/* everything else handled normally */
+	} else {
+		tb_dev->last_tb_keys_translated[slot] = false;
+	}
+
+	spin_unlock_irqrestore(&tb_dev->tb_lock, flags);
+
+	/*
+	 * Need to send these input events outside of the lock, as otherwise
+	 * we can run into the following deadlock:
+	 *            Task 1                         Task 2
+	 *     appletb_hid_event()            input_event()
+	 *       acquire tb_lock                acquire dev->event_lock
+	 *       input_event()                  appletb_inp_event()
+	 *         acquire dev->event_lock        acquire tb_lock
+	 */
+	if (send_dummy) {
+		input_event(field->hidinput->input, EV_KEY, KEY_UNKNOWN, 1);
+		input_event(field->hidinput->input, EV_KEY, KEY_UNKNOWN, 0);
+	} else if (send_trnsl) {
+		input_event(field->hidinput->input, usage->type, new_code,
+			    value);
+	}
+
+	return rc;
+}
+
+static void appletb_inp_event(struct input_handle *handle, unsigned int type,
+			      unsigned int code, int value)
+{
+	struct appletb_device *tb_dev = handle->private;
+	unsigned long flags;
+
+	spin_lock_irqsave(&tb_dev->tb_lock, flags);
+
+	if (!tb_dev->active) {
+		spin_unlock_irqrestore(&tb_dev->tb_lock, flags);
+		return;
+	}
+
+	if (type == EV_KEY && code == KEY_FN && value != 2)
+		tb_dev->last_fn_pressed = value;
+
+	tb_dev->last_event_time = ktime_get();
+
+	appletb_update_touchbar_no_lock(tb_dev, false);
+
+	spin_unlock_irqrestore(&tb_dev->tb_lock, flags);
+}
+
+/* Find and save the usb-device associated with the touch bar input device */
+static struct usb_interface *appletb_get_usb_iface(struct hid_device *hdev)
+{
+	struct device *dev = &hdev->dev;
+
+	/* iBridge virt-hid */
+	if (!dev->bus || strcmp(dev->bus->name, "hid") != 0)
+		return NULL;
+
+	/* real hid */
+	dev = dev->parent;
+	if (!dev || !dev->bus || strcmp(dev->bus->name, "hid") != 0)
+		return NULL;
+
+	/* usb dev */
+	dev = dev->parent;
+	if (!dev || !dev->bus || strcmp(dev->bus->name, "usb") != 0)
+		return NULL;
+
+	return to_usb_interface(dev);
+}
+
+static int appletb_inp_connect(struct input_handler *handler,
+			       struct input_dev *dev,
+			       const struct input_device_id *id)
+{
+	struct appletb_device *tb_dev = handler->private;
+	struct input_handle *handle;
+	int rc;
+
+	if (id->driver_info == APPLETB_DEVID_KEYBOARD) {
+		handle = &tb_dev->kbd_handle;
+		handle->name = "tbkbd";
+	} else if (id->driver_info == APPLETB_DEVID_TOUCHPAD) {
+		handle = &tb_dev->tpd_handle;
+		handle->name = "tbtpad";
+	} else {
+		dev_err(tb_dev->log_dev, "Unknown device id (%lu)\n",
+			id->driver_info);
+		return -ENOENT;
+	}
+
+	if (handle->dev) {
+		dev_err(tb_dev->log_dev,
+			"Duplicate connect to %s input device\n", handle->name);
+		return -EEXIST;
+	}
+
+	handle->open = 0;
+	handle->dev = input_get_device(dev);
+	handle->handler = handler;
+	handle->private = tb_dev;
+
+	rc = input_register_handle(handle);
+	if (rc)
+		goto err_free_dev;
+
+	rc = input_open_device(handle);
+	if (rc)
+		goto err_unregister_handle;
+
+	dev_dbg(tb_dev->log_dev, "Connected to %s input device\n",
+		handle == &tb_dev->kbd_handle ? "keyboard" : "touchpad");
+
+	return 0;
+
+ err_unregister_handle:
+	input_unregister_handle(handle);
+ err_free_dev:
+	input_put_device(handle->dev);
+	handle->dev = NULL;
+	return rc;
+}
+
+static void appletb_inp_disconnect(struct input_handle *handle)
+{
+	struct appletb_device *tb_dev = handle->private;
+
+	input_close_device(handle);
+	input_unregister_handle(handle);
+
+	dev_dbg(tb_dev->log_dev, "Disconnected from %s input device\n",
+		handle == &tb_dev->kbd_handle ? "keyboard" : "touchpad");
+
+	input_put_device(handle->dev);
+	handle->dev = NULL;
+}
+
+static int appletb_input_configured(struct hid_device *hdev,
+				    struct hid_input *hidinput)
+{
+	int idx;
+	struct input_dev *input = hidinput->input;
+
+	/*
+	 * Clear various input capabilities that are blindly set by the hid
+	 * driver (usbkbd.c)
+	 */
+	memset(input->evbit, 0, sizeof(input->evbit));
+	memset(input->keybit, 0, sizeof(input->keybit));
+	memset(input->ledbit, 0, sizeof(input->ledbit));
+
+	/* set our actual capabilities */
+	__set_bit(EV_KEY, input->evbit);
+	__set_bit(EV_REP, input->evbit);
+	__set_bit(EV_MSC, input->evbit);  /* hid-input generates MSC_SCAN */
+
+	for (idx = 0; idx < ARRAY_SIZE(appletb_fn_codes); idx++) {
+		input_set_capability(input, EV_KEY, appletb_fn_codes[idx].from);
+		input_set_capability(input, EV_KEY, appletb_fn_codes[idx].to);
+	}
+
+	input_set_capability(input, EV_KEY, KEY_ESC);
+	input_set_capability(input, EV_KEY, KEY_UNKNOWN);
+
+	return 0;
+}
+
+static int appletb_extract_report_info(struct appletb_device *tb_dev,
+				       struct hid_device *hdev)
+{
+	struct appletb_iface_info *iface_info;
+	struct usb_interface *usb_iface;
+	struct hid_field *field;
+
+	field = appleib_find_hid_field(hdev, HID_GD_KEYBOARD, HID_USAGE_MODE);
+	if (field) {
+		iface_info = &tb_dev->mode_iface;
+		tb_dev->mode_field = field;
+	} else {
+		field = appleib_find_hid_field(hdev, HID_USAGE_APPLE_APP,
+					       HID_USAGE_DISP);
+		if (!field)
+			return 0;
+
+		iface_info = &tb_dev->disp_iface;
+		tb_dev->disp_field = field;
+		tb_dev->disp_field_aux1 =
+			appleib_find_hid_field(hdev, HID_USAGE_APPLE_APP,
+					       HID_USAGE_DISP_AUX1);
+
+		if (!tb_dev->disp_field_aux1 ||
+		    tb_dev->disp_field_aux1->report !=
+						tb_dev->disp_field->report) {
+			dev_err(tb_dev->log_dev,
+				"Unexpected report structure for report %u in device %s\n",
+				tb_dev->disp_field->report->id,
+				dev_name(&hdev->dev));
+			return -ENODEV;
+		}
+	}
+
+	usb_iface = appletb_get_usb_iface(hdev);
+	if (!usb_iface) {
+		dev_err(tb_dev->log_dev,
+			"Failed to find usb interface for hid device %s\n",
+			dev_name(&hdev->dev));
+		return -ENODEV;
+	}
+
+	iface_info->hdev = hdev;
+	iface_info->usb_iface = usb_get_intf(usb_iface);
+	iface_info->suspended = false;
+
+	return 1;
+}
+
+static struct appletb_iface_info *
+appletb_get_iface_info(struct appletb_device *tb_dev, struct hid_device *hdev)
+{
+	if (hdev == tb_dev->mode_iface.hdev)
+		return &tb_dev->mode_iface;
+	if (hdev == tb_dev->disp_iface.hdev)
+		return &tb_dev->disp_iface;
+	return NULL;
+}
+
+static bool appletb_test_and_mark_active(struct appletb_device *tb_dev)
+{
+	unsigned long flags;
+	bool activated = false;
+
+	spin_lock_irqsave(&tb_dev->tb_lock, flags);
+
+	if (tb_dev->mode_iface.hdev && tb_dev->disp_iface.hdev &&
+	    !tb_dev->active) {
+		tb_dev->active = true;
+		activated = true;
+	}
+
+	spin_unlock_irqrestore(&tb_dev->tb_lock, flags);
+
+	return activated;
+}
+
+static bool appletb_test_and_mark_inactive(struct appletb_device *tb_dev,
+					   struct hid_device *hdev)
+{
+	unsigned long flags;
+	bool deactivated = false;
+
+	spin_lock_irqsave(&tb_dev->tb_lock, flags);
+
+	if (tb_dev->mode_iface.hdev && tb_dev->disp_iface.hdev &&
+	    tb_dev->active &&
+	    (hdev == tb_dev->mode_iface.hdev ||
+	     hdev == tb_dev->disp_iface.hdev)) {
+		tb_dev->active = false;
+		deactivated = true;
+	}
+
+	spin_unlock_irqrestore(&tb_dev->tb_lock, flags);
+
+	return deactivated;
+}
+
+static const struct input_device_id appletb_input_devices[] = {
+	{
+		.flags = INPUT_DEVICE_ID_MATCH_BUS |
+			INPUT_DEVICE_ID_MATCH_KEYBIT,
+		.bustype = BUS_SPI,
+		.keybit = { [BIT_WORD(KEY_FN)] = BIT_MASK(KEY_FN) },
+		.driver_info = APPLETB_DEVID_KEYBOARD,
+	},			/* Builtin keyboard device */
+	{
+		.flags = INPUT_DEVICE_ID_MATCH_BUS |
+			INPUT_DEVICE_ID_MATCH_KEYBIT,
+		.bustype = BUS_SPI,
+		.keybit = { [BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH) },
+		.driver_info = APPLETB_DEVID_TOUCHPAD,
+	},			/* Builtin touchpad device */
+	{ },			/* Terminating zero entry */
+};
+
+static int appletb_probe(struct hid_device *hdev,
+			 const struct hid_device_id *id)
+{
+	struct appletb_device *tb_dev = appletb_dev;
+	struct appletb_iface_info *iface_info;
+	unsigned long flags;
+	int rc;
+
+	spin_lock_irqsave(&tb_dev->tb_lock, flags);
+
+	if (!tb_dev->log_dev)
+		tb_dev->log_dev = &hdev->dev;
+
+	spin_unlock_irqrestore(&tb_dev->tb_lock, flags);
+
+	hid_set_drvdata(hdev, tb_dev);
+
+	/* initialize the report info */
+	rc = hid_parse(hdev);
+	if (rc) {
+		dev_err(tb_dev->log_dev, "als: hid parse failed (%d)\n", rc);
+		goto error;
+	}
+
+	rc = appletb_extract_report_info(tb_dev, hdev);
+	if (rc < 0)
+		goto error;
+
+	rc = hid_hw_start(hdev, HID_CONNECT_DRIVER | HID_CONNECT_HIDINPUT);
+	if (rc) {
+		dev_err(tb_dev->log_dev, "hw start failed (%d)\n", rc);
+		goto clear_iface_info;
+	}
+
+	rc = hid_hw_open(hdev);
+	if (rc) {
+		dev_err(tb_dev->log_dev, "hw open failed (%d)\n", rc);
+		goto stop_hid;
+	}
+
+	/* do setup if we have both interfaces */
+	if (appletb_test_and_mark_active(tb_dev)) {
+		/* initialize the touch bar */
+		if (appletb_tb_def_fn_mode >= 0 &&
+		    appletb_tb_def_fn_mode <= APPLETB_FN_MODE_MAX)
+			tb_dev->fn_mode = appletb_tb_def_fn_mode;
+		else
+			tb_dev->fn_mode = APPLETB_FN_MODE_NORM;
+		appletb_set_idle_timeout(tb_dev, appletb_tb_def_idle_timeout);
+		appletb_set_dim_timeout(tb_dev, appletb_tb_def_dim_timeout);
+		tb_dev->last_event_time = ktime_get();
+
+		tb_dev->pnd_tb_mode = APPLETB_CMD_MODE_UPD;
+		tb_dev->pnd_tb_disp = APPLETB_CMD_DISP_UPD;
+
+		appletb_update_touchbar(tb_dev, false);
+
+		/* set up the input handler */
+		tb_dev->inp_handler.event = appletb_inp_event;
+		tb_dev->inp_handler.connect = appletb_inp_connect;
+		tb_dev->inp_handler.disconnect = appletb_inp_disconnect;
+		tb_dev->inp_handler.name = "appletb";
+		tb_dev->inp_handler.id_table = appletb_input_devices;
+		tb_dev->inp_handler.private = tb_dev;
+
+		rc = input_register_handler(&tb_dev->inp_handler);
+		if (rc) {
+			dev_err(tb_dev->log_dev,
+				"Unable to register keyboard handler (%d)\n",
+				rc);
+			goto mark_inactive;
+		}
+
+		/* initialize sysfs attributes */
+		rc = sysfs_create_group(&tb_dev->mode_iface.hdev->dev.kobj,
+					&appletb_attr_group);
+		if (rc) {
+			dev_err(tb_dev->log_dev,
+				"Failed to create sysfs attributes (%d)\n", rc);
+			goto unreg_handler;
+		}
+
+		dev_dbg(tb_dev->log_dev, "Touchbar activated\n");
+	}
+
+	return 0;
+
+unreg_handler:
+	input_unregister_handler(&tb_dev->inp_handler);
+mark_inactive:
+	appletb_test_and_mark_inactive(tb_dev, hdev);
+	cancel_delayed_work_sync(&tb_dev->tb_work);
+	hid_hw_close(hdev);
+stop_hid:
+	hid_hw_stop(hdev);
+clear_iface_info:
+	iface_info = appletb_get_iface_info(tb_dev, hdev);
+	if (iface_info) {
+		usb_put_intf(iface_info->usb_iface);
+		iface_info->usb_iface = NULL;
+		iface_info->hdev = NULL;
+	}
+error:
+	return rc;
+}
+
+static void appletb_remove(struct hid_device *hdev)
+{
+	struct appletb_device *tb_dev = hid_get_drvdata(hdev);
+	struct appletb_iface_info *iface_info;
+	unsigned long flags;
+
+	if (appletb_test_and_mark_inactive(tb_dev, hdev)) {
+		sysfs_remove_group(&tb_dev->mode_iface.hdev->dev.kobj,
+				   &appletb_attr_group);
+
+		input_unregister_handler(&tb_dev->inp_handler);
+
+		cancel_delayed_work_sync(&tb_dev->tb_work);
+		appletb_set_tb_mode(tb_dev, APPLETB_CMD_MODE_OFF);
+		appletb_set_tb_disp(tb_dev, APPLETB_CMD_DISP_ON);
+
+		if (tb_dev->tb_autopm_off)
+			hid_hw_power(tb_dev->disp_iface.hdev, PM_HINT_NORMAL);
+
+		dev_info(tb_dev->log_dev, "Touchbar deactivated\n");
+	}
+
+	hid_hw_close(hdev);
+	hid_hw_stop(hdev);
+
+	iface_info = appletb_get_iface_info(tb_dev, hdev);
+	if (iface_info) {
+		usb_put_intf(iface_info->usb_iface);
+		iface_info->usb_iface = NULL;
+		iface_info->hdev = NULL;
+	}
+
+	spin_lock_irqsave(&tb_dev->tb_lock, flags);
+
+	if (tb_dev->log_dev == &hdev->dev) {
+		if (tb_dev->mode_iface.hdev)
+			tb_dev->log_dev = &tb_dev->mode_iface.hdev->dev;
+		else if (tb_dev->disp_iface.hdev)
+			tb_dev->log_dev = &tb_dev->disp_iface.hdev->dev;
+		else
+			tb_dev->log_dev = NULL;
+	}
+
+	spin_unlock_irqrestore(&tb_dev->tb_lock, flags);
+}
+
+#ifdef CONFIG_PM
+static int appletb_suspend(struct hid_device *hdev, pm_message_t message)
+{
+	struct appletb_device *tb_dev = hid_get_drvdata(hdev);
+	struct appletb_iface_info *iface_info;
+	unsigned long flags;
+	bool all_suspended = false;
+
+	if (message.event != PM_EVENT_SUSPEND &&
+	    message.event != PM_EVENT_FREEZE)
+		return 0;
+
+	/*
+	 * Wait for both interfaces to be suspended and no more async work
+	 * in progress.
+	 */
+	spin_lock_irqsave(&tb_dev->tb_lock, flags);
+
+	if (!tb_dev->mode_iface.suspended && !tb_dev->disp_iface.suspended) {
+		tb_dev->active = false;
+		cancel_delayed_work(&tb_dev->tb_work);
+	}
+
+	iface_info = appletb_get_iface_info(tb_dev, hdev);
+	if (iface_info)
+		iface_info->suspended = true;
+
+	if ((!tb_dev->mode_iface.hdev || tb_dev->mode_iface.suspended) &&
+	    (!tb_dev->disp_iface.hdev || tb_dev->disp_iface.suspended))
+		all_suspended = true;
+
+	spin_unlock_irqrestore(&tb_dev->tb_lock, flags);
+
+	flush_delayed_work(&tb_dev->tb_work);
+
+	if (!all_suspended)
+		return 0;
+
+	/*
+	 * The touch bar device itself remembers the last state when suspended
+	 * in some cases, but in others (e.g. when mode != off and disp == off)
+	 * it resumes with a different state; furthermore it may be only
+	 * partially responsive in that state. By turning both mode and disp
+	 * off we ensure it is in a good state when resuming (and this happens
+	 * to be the same state after booting/resuming-from-hibernate, so less
+	 * special casing between the two).
+	 */
+	if (message.event == PM_EVENT_SUSPEND) {
+		appletb_set_tb_mode(tb_dev, APPLETB_CMD_MODE_OFF);
+		appletb_set_tb_disp(tb_dev, APPLETB_CMD_DISP_OFF);
+	}
+
+	spin_lock_irqsave(&tb_dev->tb_lock, flags);
+
+	tb_dev->cur_tb_mode = APPLETB_CMD_MODE_OFF;
+	tb_dev->cur_tb_disp = APPLETB_CMD_DISP_OFF;
+
+	spin_unlock_irqrestore(&tb_dev->tb_lock, flags);
+
+	dev_info(tb_dev->log_dev, "Touchbar suspended.\n");
+
+	return 0;
+}
+
+static int appletb_reset_resume(struct hid_device *hdev)
+{
+	struct appletb_device *tb_dev = hid_get_drvdata(hdev);
+	struct appletb_iface_info *iface_info;
+	unsigned long flags;
+
+	spin_lock_irqsave(&tb_dev->tb_lock, flags);
+
+	iface_info = appletb_get_iface_info(tb_dev, hdev);
+	if (iface_info)
+		iface_info->suspended = false;
+
+	if ((tb_dev->mode_iface.hdev && !tb_dev->mode_iface.suspended) &&
+	    (tb_dev->disp_iface.hdev && !tb_dev->disp_iface.suspended)) {
+		/*
+		 * Restore touch bar state. Note that autopm state is not
+		 * preserved, so need explicitly restore that here.
+		 */
+		tb_dev->active = true;
+		tb_dev->restore_autopm = true;
+		tb_dev->last_event_time = ktime_get();
+
+		appletb_update_touchbar_no_lock(tb_dev, true);
+
+		dev_info(tb_dev->log_dev, "Touchbar resumed.\n");
+	}
+
+	spin_unlock_irqrestore(&tb_dev->tb_lock, flags);
+
+	return 0;
+}
+#endif
+
+static struct appletb_device *appletb_alloc_device(void)
+{
+	struct appletb_device *tb_dev;
+
+	tb_dev = kzalloc(sizeof(*tb_dev), GFP_KERNEL);
+	if (!tb_dev)
+		return NULL;
+
+	spin_lock_init(&tb_dev->tb_lock);
+	INIT_DELAYED_WORK(&tb_dev->tb_work, appletb_set_tb_worker);
+
+	return tb_dev;
+}
+
+static void appletb_free_device(struct appletb_device *tb_dev)
+{
+	cancel_delayed_work_sync(&tb_dev->tb_work);
+	kfree(tb_dev);
+}
+
+static const struct hid_device_id appletb_hid_ids[] = {
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LINUX_FOUNDATION,
+			 USB_DEVICE_ID_IBRIDGE_TB) },
+	{ },
+};
+
+MODULE_DEVICE_TABLE(hid, appletb_hid_ids);
+
+static struct hid_driver appletb_hid_driver = {
+	.name = "apple-ib-touchbar",
+	.id_table = appletb_hid_ids,
+	.probe = appletb_probe,
+	.remove = appletb_remove,
+	.event = appletb_hid_event,
+	.input_configured = appletb_input_configured,
+#ifdef CONFIG_PM
+	.suspend = appletb_suspend,
+	.reset_resume = appletb_reset_resume,
+#endif
+};
+
+static int __init appletb_init(void)
+{
+	struct appletb_device *tb_dev;
+	int rc;
+
+	tb_dev = appletb_alloc_device();
+	if (!tb_dev)
+		return -ENOMEM;
+
+	appletb_dev = tb_dev;
+
+	rc = hid_register_driver(&appletb_hid_driver);
+	if (rc)
+		goto error;
+
+	return 0;
+
+error:
+	appletb_free_device(tb_dev);
+	return rc;
+}
+
+static void __exit appletb_exit(void)
+{
+	hid_unregister_driver(&appletb_hid_driver);
+	appletb_free_device(appletb_dev);
+}
+
+module_init(appletb_init);
+module_exit(appletb_exit);
+
+MODULE_AUTHOR("Ronald Tschalär");
+MODULE_DESCRIPTION("MacBookPro Touch Bar driver");
+MODULE_LICENSE("GPL v2");
-- 
2.21.0



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

* [PATCH v2 3/3] iio: light: apple-ib-als: Add driver for ALS on iBridge chip.
  2019-06-12  8:33 [PATCH v2 0/3] Apple iBridge support Ronald Tschalär
  2019-06-12  8:33 ` [PATCH v2 1/3] HID: apple-ibridge: Add Apple iBridge HID driver Ronald Tschalär
  2019-06-12  8:33 ` [PATCH v2 2/3] HID: apple-ib-tb: Add driver for the Touch Bar on MacBook Pro's Ronald Tschalär
@ 2019-06-12  8:34 ` Ronald Tschalär
  2019-06-16 14:52   ` Jonathan Cameron
  2 siblings, 1 reply; 7+ messages in thread
From: Ronald Tschalär @ 2019-06-12  8:34 UTC (permalink / raw)
  To: Jiri Kosina, Benjamin Tissoires, Jonathan Cameron,
	Hartmut Knaack, Lars-Peter Clausen, Peter Meerwald-Stadler
  Cc: linux-input, linux-iio, linux-kernel

On 2016/2017 MacBook Pro's with a Touch Bar the ALS is attached to,
and exposed via the iBridge device. This provides the driver for that
sensor.

Signed-off-by: Ronald Tschalär <ronald@innovation.ch>
---
 drivers/iio/light/Kconfig        |  12 +
 drivers/iio/light/Makefile       |   1 +
 drivers/iio/light/apple-ib-als.c | 607 +++++++++++++++++++++++++++++++
 3 files changed, 620 insertions(+)
 create mode 100644 drivers/iio/light/apple-ib-als.c

diff --git a/drivers/iio/light/Kconfig b/drivers/iio/light/Kconfig
index 5190eacfeb0a..b477aa5d2024 100644
--- a/drivers/iio/light/Kconfig
+++ b/drivers/iio/light/Kconfig
@@ -64,6 +64,18 @@ config APDS9960
 	  To compile this driver as a module, choose M here: the
 	  module will be called apds9960
 
+config APPLE_IBRIDGE_ALS
+	tristate "Apple iBridge ambient light sensor"
+	select IIO_BUFFER
+	select IIO_TRIGGERED_BUFFER
+	depends on HID_APPLE_IBRIDGE
+	help
+	  Say Y here to build the driver for the Apple iBridge ALS
+	  sensor.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called apple-ib-als.
+
 config BH1750
 	tristate "ROHM BH1750 ambient light sensor"
 	depends on I2C
diff --git a/drivers/iio/light/Makefile b/drivers/iio/light/Makefile
index e40794fbb435..cd6cd5ba6da5 100644
--- a/drivers/iio/light/Makefile
+++ b/drivers/iio/light/Makefile
@@ -9,6 +9,7 @@ obj-$(CONFIG_ADJD_S311)		+= adjd_s311.o
 obj-$(CONFIG_AL3320A)		+= al3320a.o
 obj-$(CONFIG_APDS9300)		+= apds9300.o
 obj-$(CONFIG_APDS9960)		+= apds9960.o
+obj-$(CONFIG_APPLE_IBRIDGE_ALS)	+= apple-ib-als.o
 obj-$(CONFIG_BH1750)		+= bh1750.o
 obj-$(CONFIG_BH1780)		+= bh1780.o
 obj-$(CONFIG_CM32181)		+= cm32181.o
diff --git a/drivers/iio/light/apple-ib-als.c b/drivers/iio/light/apple-ib-als.c
new file mode 100644
index 000000000000..b84be0076e0f
--- /dev/null
+++ b/drivers/iio/light/apple-ib-als.c
@@ -0,0 +1,607 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Apple Ambient Light Sensor Driver
+ *
+ * Copyright (c) 2017-2018 Ronald Tschalär
+ */
+
+/*
+ * MacBookPro models with an iBridge chip (13,[23] and 14,[23]) have an
+ * ambient light sensor that is exposed via one of the USB interfaces on
+ * the iBridge as a standard HID light sensor. However, we cannot use the
+ * existing hid-sensor-als driver, for two reasons:
+ *
+ * 1. The hid-sensor-als driver is part of the hid-sensor-hub which in turn
+ *    is a hid driver, but you can't have more than one hid driver per hid
+ *    device, which is a problem because the touch bar also needs to
+ *    register as a driver for this hid device.
+ *
+ * 2. While the hid-sensors-als driver stores sensor readings received via
+ *    interrupt in an iio buffer, reads on the sysfs
+ *    .../iio:deviceX/in_illuminance_YYY attribute result in a get of the
+ *    feature report; however, in the case of this sensor here the
+ *    illuminance field of that report is always 0. Instead, the input
+ *    report needs to be requested.
+ */
+
+#define dev_fmt(fmt) "als: " fmt
+
+#include <linux/apple-ibridge.h>
+#include <linux/device.h>
+#include <linux/hid.h>
+#include <linux/hid-sensor-ids.h>
+#include <linux/iio/buffer.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/trigger_consumer.h>
+#include <linux/iio/triggered_buffer.h>
+#include <linux/iio/trigger.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+
+#define APPLEALS_DYN_SENS		0	/* our dynamic sensitivity */
+#define APPLEALS_DEF_CHANGE_SENS	APPLEALS_DYN_SENS
+
+struct appleals_device {
+	struct hid_device	*hid_dev;
+	struct hid_report	*cfg_report;
+	struct hid_field	*illum_field;
+	struct iio_dev		*iio_dev;
+	int			cur_sensitivity;
+	int			cur_hysteresis;
+	bool			events_enabled;
+};
+
+static struct hid_driver appleals_hid_driver;
+
+/*
+ * This is a primitive way to get a relative sensitivity, one where we get
+ * notified when the value changes by a certain percentage rather than some
+ * absolute value. MacOS somehow manages to configure the sensor to work this
+ * way (with a 15% relative sensitivity), but I haven't been able to figure
+ * out how so far. So until we do, this provides a less-than-perfect
+ * simulation.
+ *
+ * When the brightness value is within one of the ranges, the sensitivity is
+ * set to that range's sensitivity. But in order to reduce flapping when the
+ * brightness is right on the border between two ranges, the ranges overlap
+ * somewhat (by at least one sensitivity), and sensitivity is only changed if
+ * the value leaves the current sensitivity's range.
+ *
+ * The values chosen for the map are somewhat arbitrary: a compromise of not
+ * too many ranges (and hence changing the sensitivity) but not too small or
+ * large of a percentage of the min and max values in the range (currently
+ * from 7.5% to 30%, i.e. within a factor of 2 of 15%), as well as just plain
+ * "this feels reasonable to me".
+ */
+struct appleals_sensitivity_map {
+	int	sensitivity;
+	int	illum_low;
+	int	illum_high;
+};
+
+static const struct appleals_sensitivity_map appleals_sensitivity_map[] = {
+	{   1,    0,   14 },
+	{   3,   10,   40 },
+	{   9,   30,  120 },
+	{  27,   90,  360 },
+	{  81,  270, 1080 },
+	{ 243,  810, 3240 },
+	{ 729, 2430, 9720 },
+};
+
+static int appleals_compute_sensitivity(int cur_illum, int cur_sens)
+{
+	const struct appleals_sensitivity_map *entry;
+	int i;
+
+	/* see if we're still in current range */
+	for (i = 0; i < ARRAY_SIZE(appleals_sensitivity_map); i++) {
+		entry = &appleals_sensitivity_map[i];
+
+		if (entry->sensitivity == cur_sens &&
+		    entry->illum_low <= cur_illum &&
+		    entry->illum_high >= cur_illum)
+			return cur_sens;
+		else if (entry->sensitivity > cur_sens)
+			break;
+	}
+
+	/* not in current range, so find new sensitivity */
+	for (i = 0; i < ARRAY_SIZE(appleals_sensitivity_map); i++) {
+		entry = &appleals_sensitivity_map[i];
+
+		if (entry->illum_low <= cur_illum &&
+		    entry->illum_high >= cur_illum)
+			return entry->sensitivity;
+	}
+
+	/* hmm, not in table, so assume we are above highest range */
+	i = ARRAY_SIZE(appleals_sensitivity_map) - 1;
+	return appleals_sensitivity_map[i].sensitivity;
+}
+
+static int appleals_get_field_value_for_usage(struct hid_field *field,
+					      unsigned int usage)
+{
+	int u;
+
+	if (!field)
+		return -1;
+
+	for (u = 0; u < field->maxusage; u++) {
+		if (field->usage[u].hid == usage)
+			return u + field->logical_minimum;
+	}
+
+	return -1;
+}
+
+static __s32 appleals_get_field_value(struct appleals_device *als_dev,
+				      struct hid_field *field)
+{
+	bool powered_on = !hid_hw_power(als_dev->hid_dev, PM_HINT_FULLON);
+
+	hid_hw_request(als_dev->hid_dev, field->report, HID_REQ_GET_REPORT);
+	hid_hw_wait(als_dev->hid_dev);
+
+	if (powered_on)
+		hid_hw_power(als_dev->hid_dev, PM_HINT_NORMAL);
+
+	return field->value[0];
+}
+
+static void appleals_set_field_value(struct appleals_device *als_dev,
+				     struct hid_field *field, __s32 value)
+{
+	hid_set_field(field, 0, value);
+	hid_hw_request(als_dev->hid_dev, field->report, HID_REQ_SET_REPORT);
+}
+
+static int appleals_get_config(struct appleals_device *als_dev,
+			       unsigned int field_usage, __s32 *value)
+{
+	struct hid_field *field;
+
+	field = appleib_find_report_field(als_dev->cfg_report, field_usage);
+	if (!field)
+		return -EINVAL;
+
+	*value = appleals_get_field_value(als_dev, field);
+
+	return 0;
+}
+
+static int appleals_set_config(struct appleals_device *als_dev,
+			       unsigned int field_usage, __s32 value)
+{
+	struct hid_field *field;
+
+	field = appleib_find_report_field(als_dev->cfg_report, field_usage);
+	if (!field)
+		return -EINVAL;
+
+	appleals_set_field_value(als_dev, field, value);
+
+	return 0;
+}
+
+static int appleals_set_enum_config(struct appleals_device *als_dev,
+				    unsigned int field_usage,
+				    unsigned int value_usage)
+{
+	struct hid_field *field;
+	int value;
+
+	field = appleib_find_report_field(als_dev->cfg_report, field_usage);
+	if (!field)
+		return -EINVAL;
+
+	value = appleals_get_field_value_for_usage(field, value_usage);
+	if (value >= 0)
+		appleals_set_field_value(als_dev, field, value);
+
+	return 0;
+}
+
+static void appleals_update_dyn_sensitivity(struct appleals_device *als_dev,
+					    __s32 value)
+{
+	int new_sens;
+	int rc;
+
+	new_sens = appleals_compute_sensitivity(value,
+						als_dev->cur_sensitivity);
+	if (new_sens != als_dev->cur_sensitivity) {
+		rc = appleals_set_config(als_dev,
+			HID_USAGE_SENSOR_LIGHT_ILLUM |
+			HID_USAGE_SENSOR_DATA_MOD_CHANGE_SENSITIVITY_ABS,
+			new_sens);
+		if (!rc)
+			als_dev->cur_sensitivity = new_sens;
+	}
+}
+
+static void appleals_push_new_value(struct appleals_device *als_dev,
+				    __s32 value)
+{
+	__s32 buf[2] = { value, value };
+
+	iio_push_to_buffers(als_dev->iio_dev, buf);
+
+	if (als_dev->cur_hysteresis == APPLEALS_DYN_SENS)
+		appleals_update_dyn_sensitivity(als_dev, value);
+}
+
+static int appleals_hid_event(struct hid_device *hdev, struct hid_field *field,
+			      struct hid_usage *usage, __s32 value)
+{
+	struct appleals_device *als_dev = hid_get_drvdata(hdev);
+	int rc = 0;
+
+	if ((usage->hid & HID_USAGE_PAGE) != HID_UP_SENSOR)
+		return 0;
+
+	if (usage->hid == HID_USAGE_SENSOR_LIGHT_ILLUM) {
+		appleals_push_new_value(als_dev, value);
+		rc = 1;
+	}
+
+	return rc;
+}
+
+static int appleals_enable_events(struct iio_trigger *trig, bool enable)
+{
+	struct appleals_device *als_dev = iio_trigger_get_drvdata(trig);
+	int value;
+
+	appleals_set_enum_config(als_dev, HID_USAGE_SENSOR_PROP_REPORT_STATE,
+		enable ? HID_USAGE_SENSOR_PROP_REPORTING_STATE_ALL_EVENTS_ENUM :
+			 HID_USAGE_SENSOR_PROP_REPORTING_STATE_NO_EVENTS_ENUM);
+	als_dev->events_enabled = enable;
+
+	/* if the sensor was enabled, push an initial value */
+	if (enable) {
+		value = appleals_get_field_value(als_dev, als_dev->illum_field);
+		appleals_push_new_value(als_dev, value);
+	}
+
+	return 0;
+}
+
+static int appleals_read_raw(struct iio_dev *iio_dev,
+			     struct iio_chan_spec const *chan,
+			     int *val, int *val2, long mask)
+{
+	struct appleals_device **priv = iio_priv(iio_dev);
+	struct appleals_device *als_dev = *priv;
+	__s32 value;
+	int rc;
+
+	switch (mask) {
+	case IIO_CHAN_INFO_PROCESSED:
+		*val = appleals_get_field_value(als_dev, als_dev->illum_field);
+		return IIO_VAL_INT;
+
+	case IIO_CHAN_INFO_SAMP_FREQ:
+		rc = appleals_get_config(als_dev,
+					 HID_USAGE_SENSOR_PROP_REPORT_INTERVAL,
+					 &value);
+		if (rc)
+			return rc;
+
+		/* interval is in ms; val is in HZ, val2 in µHZ */
+		value = 1000000000 / value;
+		*val = value / 1000000;
+		*val2 = value - (*val * 1000000);
+
+		return IIO_VAL_INT_PLUS_MICRO;
+
+	case IIO_CHAN_INFO_HYSTERESIS:
+		if (als_dev->cur_hysteresis == APPLEALS_DYN_SENS) {
+			*val = als_dev->cur_hysteresis;
+			return IIO_VAL_INT;
+		}
+
+		rc = appleals_get_config(als_dev,
+			HID_USAGE_SENSOR_LIGHT_ILLUM |
+			HID_USAGE_SENSOR_DATA_MOD_CHANGE_SENSITIVITY_ABS,
+			val);
+		if (!rc) {
+			als_dev->cur_sensitivity = *val;
+			als_dev->cur_hysteresis = *val;
+		}
+		return rc ? rc : IIO_VAL_INT;
+
+	default:
+		return -EINVAL;
+	}
+}
+
+static int appleals_write_raw(struct iio_dev *iio_dev,
+			      struct iio_chan_spec const *chan,
+			      int val, int val2, long mask)
+{
+	struct appleals_device **priv = iio_priv(iio_dev);
+	struct appleals_device *als_dev = *priv;
+	__s32 illum;
+	int rc;
+
+	switch (mask) {
+	case IIO_CHAN_INFO_SAMP_FREQ:
+		rc = appleals_set_config(als_dev,
+					 HID_USAGE_SENSOR_PROP_REPORT_INTERVAL,
+					 1000000000 / (val * 1000000 + val2));
+		return rc;
+
+	case IIO_CHAN_INFO_HYSTERESIS:
+		if (val == APPLEALS_DYN_SENS) {
+			if (als_dev->cur_hysteresis != APPLEALS_DYN_SENS) {
+				als_dev->cur_hysteresis = val;
+				illum = appleals_get_field_value(als_dev,
+							als_dev->illum_field);
+				appleals_update_dyn_sensitivity(als_dev, illum);
+			}
+
+			return 0;
+		}
+
+		rc = appleals_set_config(als_dev,
+			HID_USAGE_SENSOR_LIGHT_ILLUM |
+			HID_USAGE_SENSOR_DATA_MOD_CHANGE_SENSITIVITY_ABS,
+			val);
+		if (!rc) {
+			als_dev->cur_sensitivity = val;
+			als_dev->cur_hysteresis = val;
+		}
+
+		return rc;
+
+	default:
+		return -EINVAL;
+	}
+}
+
+static const struct iio_chan_spec appleals_channels[] = {
+	{
+		.type = IIO_INTENSITY,
+		.modified = 1,
+		.channel2 = IIO_MOD_LIGHT_BOTH,
+		.info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED),
+		.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SAMP_FREQ) |
+			BIT(IIO_CHAN_INFO_HYSTERESIS),
+		.scan_type = {
+			.sign = 'u',
+			.realbits = 32,
+			.storagebits = 32,
+		},
+		.scan_index = 0,
+	},
+	{
+		.type = IIO_LIGHT,
+		.info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED),
+		.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SAMP_FREQ) |
+			BIT(IIO_CHAN_INFO_HYSTERESIS),
+		.scan_type = {
+			.sign = 'u',
+			.realbits = 32,
+			.storagebits = 32,
+		},
+		.scan_index = 1,
+	}
+};
+
+static const struct iio_trigger_ops appleals_trigger_ops = {
+	.set_trigger_state = &appleals_enable_events,
+};
+
+static const struct iio_info appleals_info = {
+	.read_raw = &appleals_read_raw,
+	.write_raw = &appleals_write_raw,
+};
+
+static void appleals_config_sensor(struct appleals_device *als_dev,
+				   bool events_enabled, int sensitivity)
+{
+	struct hid_field *field;
+	bool powered_on;
+	__s32 val;
+
+	powered_on = !hid_hw_power(als_dev->hid_dev, PM_HINT_FULLON);
+
+	hid_hw_request(als_dev->hid_dev, als_dev->cfg_report,
+		       HID_REQ_GET_REPORT);
+	hid_hw_wait(als_dev->hid_dev);
+
+	field = appleib_find_report_field(als_dev->cfg_report,
+					  HID_USAGE_SENSOR_PROY_POWER_STATE);
+	val = appleals_get_field_value_for_usage(field,
+			HID_USAGE_SENSOR_PROP_POWER_STATE_D0_FULL_POWER_ENUM);
+	if (val >= 0)
+		hid_set_field(field, 0, val);
+
+	field = appleib_find_report_field(als_dev->cfg_report,
+					  HID_USAGE_SENSOR_PROP_REPORT_STATE);
+	val = appleals_get_field_value_for_usage(field,
+		events_enabled ?
+			HID_USAGE_SENSOR_PROP_REPORTING_STATE_ALL_EVENTS_ENUM :
+			HID_USAGE_SENSOR_PROP_REPORTING_STATE_NO_EVENTS_ENUM);
+	if (val >= 0)
+		hid_set_field(field, 0, val);
+
+	field = appleib_find_report_field(als_dev->cfg_report,
+					 HID_USAGE_SENSOR_PROP_REPORT_INTERVAL);
+	hid_set_field(field, 0, field->logical_minimum);
+
+	/*
+	 * Set initial change sensitivity; if dynamic, enabling trigger will set
+	 * it instead.
+	 */
+	if (sensitivity != APPLEALS_DYN_SENS) {
+		field = appleib_find_report_field(als_dev->cfg_report,
+			HID_USAGE_SENSOR_LIGHT_ILLUM |
+			HID_USAGE_SENSOR_DATA_MOD_CHANGE_SENSITIVITY_ABS);
+
+		hid_set_field(field, 0, sensitivity);
+	}
+
+	hid_hw_request(als_dev->hid_dev, als_dev->cfg_report,
+		       HID_REQ_SET_REPORT);
+
+	if (powered_on)
+		hid_hw_power(als_dev->hid_dev, PM_HINT_NORMAL);
+}
+
+static int appleals_config_iio(struct appleals_device *als_dev)
+{
+	struct iio_dev *iio_dev;
+	struct iio_trigger *iio_trig;
+	struct appleals_device **priv;
+	struct device *parent = &als_dev->hid_dev->dev;
+	int rc;
+
+	iio_dev = devm_iio_device_alloc(parent, sizeof(als_dev));
+	if (!iio_dev)
+		return -ENOMEM;
+
+	priv = iio_priv(iio_dev);
+	*priv = als_dev;
+
+	iio_dev->channels = appleals_channels;
+	iio_dev->num_channels = ARRAY_SIZE(appleals_channels);
+	iio_dev->dev.parent = parent;
+	iio_dev->info = &appleals_info;
+	iio_dev->name = "als";
+	iio_dev->modes = INDIO_DIRECT_MODE;
+
+	rc = devm_iio_triggered_buffer_setup(parent, iio_dev,
+					     &iio_pollfunc_store_time, NULL,
+					     NULL);
+	if (rc) {
+		hid_err(als_dev->hid_dev,
+			"Failed to set up iio triggered buffer: %d\n", rc);
+		return rc;
+	}
+
+	iio_trig = devm_iio_trigger_alloc(parent, "%s-dev%d", iio_dev->name,
+					  iio_dev->id);
+	if (!iio_trig)
+		return -ENOMEM;
+
+	iio_trig->dev.parent = parent;
+	iio_trig->ops = &appleals_trigger_ops;
+	iio_trigger_set_drvdata(iio_trig, als_dev);
+
+	rc = devm_iio_trigger_register(parent, iio_trig);
+	if (rc) {
+		hid_err(als_dev->hid_dev,
+			"Failed to register iio trigger: %d\n",
+			rc);
+		return rc;
+	}
+
+	rc = devm_iio_device_register(parent, iio_dev);
+	if (rc) {
+		hid_err(als_dev->hid_dev, "Failed to register iio device: %d\n",
+			rc);
+		return rc;
+	}
+
+	als_dev->iio_dev = iio_dev;
+
+	return 0;
+}
+
+static int appleals_probe(struct hid_device *hdev,
+			  const struct hid_device_id *id)
+{
+	struct appleals_device *als_dev;
+	struct hid_field *state_field;
+	struct hid_field *illum_field;
+	int rc;
+
+	/* find als fields and reports */
+	rc = hid_parse(hdev);
+	if (rc) {
+		hid_err(hdev, "als: hid parse failed (%d)\n", rc);
+		return rc;
+	}
+
+	state_field = appleib_find_hid_field(hdev, HID_USAGE_SENSOR_ALS,
+					    HID_USAGE_SENSOR_PROP_REPORT_STATE);
+	illum_field = appleib_find_hid_field(hdev, HID_USAGE_SENSOR_ALS,
+					     HID_USAGE_SENSOR_LIGHT_ILLUM);
+	if (!state_field || !illum_field)
+		return -ENODEV;
+
+	hid_dbg(hdev, "Found ambient light sensor\n");
+
+	/* initialize device */
+	als_dev = devm_kzalloc(&hdev->dev, sizeof(*als_dev), GFP_KERNEL);
+	if (!als_dev)
+		return -ENOMEM;
+
+	als_dev->hid_dev = hdev;
+	als_dev->cfg_report = state_field->report;
+	als_dev->illum_field = illum_field;
+
+	als_dev->cur_hysteresis = APPLEALS_DEF_CHANGE_SENS;
+	als_dev->cur_sensitivity = APPLEALS_DEF_CHANGE_SENS;
+
+	hid_set_drvdata(hdev, als_dev);
+
+	rc = hid_hw_start(hdev, HID_CONNECT_DRIVER);
+	if (rc) {
+		hid_err(hdev, "als: hw start failed (%d)\n", rc);
+		return rc;
+	}
+
+	hid_device_io_start(hdev);
+	appleals_config_sensor(als_dev, false, als_dev->cur_sensitivity);
+	hid_device_io_stop(hdev);
+
+	rc = appleals_config_iio(als_dev);
+	if (rc)
+		return rc;
+
+	return hid_hw_open(hdev);
+}
+
+#ifdef CONFIG_PM
+static int appleals_reset_resume(struct hid_device *hdev)
+{
+	struct appleals_device *als_dev = hid_get_drvdata(hdev);
+	__s32 illum;
+
+	appleals_config_sensor(als_dev, als_dev->events_enabled,
+			       als_dev->cur_sensitivity);
+
+	illum = appleals_get_field_value(als_dev, als_dev->illum_field);
+	appleals_push_new_value(als_dev, illum);
+
+	return 0;
+}
+#endif
+
+static const struct hid_device_id appleals_hid_ids[] = {
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LINUX_FOUNDATION,
+			 USB_DEVICE_ID_IBRIDGE_ALS) },
+	{ },
+};
+
+MODULE_DEVICE_TABLE(hid, appleals_hid_ids);
+
+static struct hid_driver appleals_hid_driver = {
+	.name = "apple-ib-als",
+	.id_table = appleals_hid_ids,
+	.probe = appleals_probe,
+	.event = appleals_hid_event,
+#ifdef CONFIG_PM
+	.reset_resume = appleals_reset_resume,
+#endif
+};
+
+module_hid_driver(appleals_hid_driver);
+
+MODULE_AUTHOR("Ronald Tschalär");
+MODULE_DESCRIPTION("Apple iBridge ALS driver");
+MODULE_LICENSE("GPL v2");
-- 
2.21.0


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

* Re: [PATCH v2 1/3] HID: apple-ibridge: Add Apple iBridge HID driver.
  2019-06-12  8:33 ` [PATCH v2 1/3] HID: apple-ibridge: Add Apple iBridge HID driver Ronald Tschalär
@ 2019-06-16 14:01   ` Jonathan Cameron
  0 siblings, 0 replies; 7+ messages in thread
From: Jonathan Cameron @ 2019-06-16 14:01 UTC (permalink / raw)
  To: Ronald Tschalär
  Cc: Jiri Kosina, Benjamin Tissoires, Hartmut Knaack,
	Lars-Peter Clausen, Peter Meerwald-Stadler, linux-input,
	linux-iio, linux-kernel

On Wed, 12 Jun 2019 01:33:58 -0700
Ronald Tschalär <ronald@innovation.ch> wrote:

> The iBridge device provides access to several devices, including:
> - the Touch Bar
> - the iSight webcam
> - the light sensor
> - the fingerprint sensor
> 
> This driver provides the core support for managing the iBridge device
> and the access to the underlying devices. In particular, since the
> functionality for the touch bar and light sensor is exposed via USB HID
> interfaces, and the same HID device is used for multiple functions, this
> driver creates virtual HID devices, one per real HID device and
> sub-driver pair (for a total of 4 virtual HID devices). The sub-drivers
> then bind to these virtual HID devices.
> 
> This way the Touch Bar and ALS drivers can be kept in their own modules,
> while at the same time making them look very much like as if they were
> connected to the real HID devices; e.g. in particular the Touch Bar
> driver is aware of the fact that it is dealing with two HID devices that
> need to managed differently.
> 
> Signed-off-by: Ronald Tschalär <ronald@innovation.ch>
Hi Ronald,

I'm far from a hid expert and was only reading this for background on
the ALS sensor driver...  Anyhow, there are some comments in here
that needs some follow up or formatting into kernel comment style.

Nitpicks inline!

Jonathan
> ---
>  drivers/hid/Kconfig           |  14 +
>  drivers/hid/Makefile          |   1 +
>  drivers/hid/apple-ibridge.c   | 585 ++++++++++++++++++++++++++++++++++
>  drivers/hid/hid-ids.h         |   1 +
>  include/linux/apple-ibridge.h |  23 ++
>  5 files changed, 624 insertions(+)
>  create mode 100644 drivers/hid/apple-ibridge.c
>  create mode 100644 include/linux/apple-ibridge.h
> 
> diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
> index 4ca0cdfa6b33..545d3691fc1c 100644
> --- a/drivers/hid/Kconfig
> +++ b/drivers/hid/Kconfig
> @@ -135,6 +135,20 @@ config HID_APPLE
>  	Say Y here if you want support for keyboards of	Apple iBooks, PowerBooks,
>  	MacBooks, MacBook Pros and Apple Aluminum.
>  
> +config HID_APPLE_IBRIDGE
> +	tristate "Apple iBridge"
> +	depends on ACPI
> +	depends on USB_HID
> +	depends on X86 || COMPILE_TEST
> +	help
> +	  This module provides the core support for the Apple T1 chip found
> +	  on recent MacBookPro's, also known as the iBridge. The drivers for
> +	  the Touch Bar (apple-ib-tb) and light sensor (apple-ib-als) need to
> +	  be enabled separately.
> +
> +	  To compile this driver as a module, choose M here: the
> +	  module will be called apple-ibridge.
> +
>  config HID_APPLEIR
>  	tristate "Apple infrared receiver"
>  	depends on (USB_HID)
> diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile
> index 170163b41303..a4da5663a541 100644
> --- a/drivers/hid/Makefile
> +++ b/drivers/hid/Makefile
> @@ -26,6 +26,7 @@ obj-$(CONFIG_HID_ACCUTOUCH)	+= hid-accutouch.o
>  obj-$(CONFIG_HID_ALPS)		+= hid-alps.o
>  obj-$(CONFIG_HID_ACRUX)		+= hid-axff.o
>  obj-$(CONFIG_HID_APPLE)		+= hid-apple.o
> +obj-$(CONFIG_HID_APPLE_IBRIDGE)	+= apple-ibridge.o
>  obj-$(CONFIG_HID_APPLEIR)	+= hid-appleir.o
>  obj-$(CONFIG_HID_ASUS)		+= hid-asus.o
>  obj-$(CONFIG_HID_AUREAL)	+= hid-aureal.o
> diff --git a/drivers/hid/apple-ibridge.c b/drivers/hid/apple-ibridge.c
> new file mode 100644
> index 000000000000..565f080a38d6
> --- /dev/null
> +++ b/drivers/hid/apple-ibridge.c
> @@ -0,0 +1,585 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Apple iBridge Driver
> + *
> + * Copyright (c) 2018 Ronald Tschalär
> + */
> +
> +/**
> + * DOC: Overview
> + *
> + * MacBookPro models with a Touch Bar (13,[23] and 14,[23]) have an Apple
> + * iBridge chip (also known as T1 chip) which exposes the touch bar,
> + * built-in webcam (iSight), ambient light sensor, and Secure Enclave
> + * Processor (SEP) for TouchID. It shows up in the system as a USB device
> + * with 3 configurations: 'Default iBridge Interfaces', 'Default iBridge
> + * Interfaces(OS X)', and 'Default iBridge Interfaces(Recovery)'. While
> + * the second one is used by MacOS to provide the fancy touch bar
> + * functionality with custom buttons etc, this driver just uses the first.
> + *
> + * In the first (default after boot) configuration, 4 usb interfaces are
> + * exposed: 2 related to the webcam, and 2 USB HID interfaces representing
> + * the touch bar and the ambient light sensor. The webcam interfaces are
> + * already handled by the uvcvideo driver; furthermore, the handling of the
> + * input reports when "keys" on the touch bar are pressed is already handled
> + * properly by the generic USB HID core. This leaves the management of the
> + * touch bar modes (e.g. switching between function and special keys when the
> + * FN key is pressed), the touch bar display (dimming and turning off), the
> + * key-remapping when the FN key is pressed, and handling of the light sensor.
> + *
> + * This driver is implemented as a HID driver that creates virtual HID devices
> + * for the ALS and touch bar functionality, and the ALS and touch bar drivers
> + * are HID drivers which in turn attach to these virtual HID devices. This
> + * driver then relays the calls on the real HID devices to the virtual ones,
> + * and visa versa.
> + *
> + * Lastly, this driver also takes care of the power-management for the
> + * iBridge when suspending and resuming.
> + */
> +
> +#include <linux/acpi.h>
> +#include <linux/apple-ibridge.h>
> +#include <linux/device.h>
> +#include <linux/hid.h>
> +#include <linux/list.h>
> +#include <linux/module.h>
> +#include <linux/slab.h>
> +#include <linux/usb.h>
> +
> +#include "hid-ids.h"
> +#include "../hid/usbhid/usbhid.h"
> +
> +#define APPLEIB_BASIC_CONFIG	1
> +
> +#define	LOG_DEV(ib_dev)		(&(ib_dev)->acpi_dev->dev)
> +
> +static struct hid_device_id appleib_sub_hid_ids[] = {
> +	{ HID_USB_DEVICE(USB_VENDOR_ID_LINUX_FOUNDATION,
> +			 USB_DEVICE_ID_IBRIDGE_TB) },
> +	{ HID_USB_DEVICE(USB_VENDOR_ID_LINUX_FOUNDATION,
> +			 USB_DEVICE_ID_IBRIDGE_ALS) },
> +};
> +
> +struct appleib_device {
> +	struct acpi_device		*acpi_dev;
> +	acpi_handle			asoc_socw;
> +};
> +
> +struct appleib_hid_dev_info {
> +	struct hid_device	*hdev;
> +	struct hid_device	*sub_hdevs[ARRAY_SIZE(appleib_sub_hid_ids)];
> +};
> +
> +static void appleib_remove_device(struct hid_device *hdev)
> +{
> +	struct appleib_hid_dev_info *hdev_info = hid_get_drvdata(hdev);
> +	int i;
> +
> +	for (i = 0; i < ARRAY_SIZE(hdev_info->sub_hdevs); i++)
> +		hid_destroy_device(hdev_info->sub_hdevs[i]);
> +
> +	hid_set_drvdata(hdev, NULL);
> +}
> +
> +/**
> + * appleib_forward_int_op() - Forward a hid-driver callback to all drivers on
> + * all virtual HID devices attached to the given real HID device.
> + * @hdev the real hid-device
> + * @forward a function that calls the callback on the given driver
> + * @args arguments for the forward function
> + *
> + * This is for callbacks that return a status as an int.
> + *
> + * Returns: 0 on success, or the first error returned by the @forward function.
> + */
> +static int appleib_forward_int_op(struct hid_device *hdev,
> +				  int (*forward)(struct hid_driver *,
> +						 struct hid_device *, void *),
> +				  void *args)
> +{
> +	struct appleib_hid_dev_info *hdev_info = hid_get_drvdata(hdev);
> +	struct hid_device *sub_hdev;
> +	int rc = 0;
> +	int i;
> +
> +	for (i = 0; i < ARRAY_SIZE(hdev_info->sub_hdevs); i++) {
> +		sub_hdev = hdev_info->sub_hdevs[i];
> +		if (sub_hdev->driver) {
> +			rc = forward(sub_hdev->driver, sub_hdev, args);
> +			if (rc)
> +				break;
> +		}
> +
> +		break;
> +	}
> +
> +	return rc;
> +}
> +
> +static int appleib_hid_raw_event(struct hid_device *hdev,
> +				 struct hid_report *report, u8 *data, int size)
> +{
> +	struct appleib_hid_dev_info *hdev_info = hid_get_drvdata(hdev);
> +	int i;
> +
> +	for (i = 0; i < ARRAY_SIZE(hdev_info->sub_hdevs); i++)
> +		hid_input_report(hdev_info->sub_hdevs[i], report->type, data,
> +				 size, 0);
> +
> +	return 0;
> +}
> +
> +#ifdef CONFIG_PM
> +static int appleib_hid_suspend_fwd(struct hid_driver *drv,
> +				   struct hid_device *hdev, void *args)
> +{
> +	int rc = 0;
> +
> +	if (drv->suspend)
> +		rc = drv->suspend(hdev, *(pm_message_t *)args);
> +
> +	return rc;
> +}
> +
> +static int appleib_hid_suspend(struct hid_device *hdev, pm_message_t message)
> +{
> +	return appleib_forward_int_op(hdev, appleib_hid_suspend_fwd, &message);
> +}
> +
> +static int appleib_hid_resume_fwd(struct hid_driver *drv,
> +				  struct hid_device *hdev, void *args)
> +{
> +	int rc = 0;
> +
> +	if (drv->resume)
> +		rc = drv->resume(hdev);
> +
> +	return rc;
> +}
> +
> +static int appleib_hid_resume(struct hid_device *hdev)
> +{
> +	return appleib_forward_int_op(hdev, appleib_hid_resume_fwd, NULL);
> +}
> +
> +static int appleib_hid_reset_resume_fwd(struct hid_driver *drv,
> +					struct hid_device *hdev, void *args)
> +{
> +	int rc = 0;
> +
> +	if (drv->reset_resume)
> +		rc = drv->reset_resume(hdev);
> +
> +	return rc;
> +}
> +
> +static int appleib_hid_reset_resume(struct hid_device *hdev)
> +{
> +	return appleib_forward_int_op(hdev, appleib_hid_reset_resume_fwd, NULL);
> +}
> +#endif /* CONFIG_PM */
> +
> +/**
> + * appleib_find_report_field() - Find the field in the report with the given
> + * usage.
> + * @report: the report to search
> + * @field_usage: the usage of the field to search for
> + *
> + * Returns: the hid field if found, or NULL if none found.
> + */
> +struct hid_field *appleib_find_report_field(struct hid_report *report,
> +					    unsigned int field_usage)
> +{
> +	int f, u;
> +
> +	for (f = 0; f < report->maxfield; f++) {
> +		struct hid_field *field = report->field[f];
> +
> +		if (field->logical == field_usage)
> +			return field;
> +
> +		for (u = 0; u < field->maxusage; u++) {
> +			if (field->usage[u].hid == field_usage)
> +				return field;
> +		}
> +	}
> +
> +	return NULL;
> +}
> +EXPORT_SYMBOL_GPL(appleib_find_report_field);
> +
> +/**
> + * appleib_find_hid_field() - Search all the reports of the device for the
> + * field with the given usage.
> + * @hdev: the device whose reports to search
> + * @application: the usage of application collection that the field must
> + *               belong to
> + * @field_usage: the usage of the field to search for
> + *
> + * Returns: the hid field if found, or NULL if none found.
> + */
> +struct hid_field *appleib_find_hid_field(struct hid_device *hdev,
> +					 unsigned int application,
> +					 unsigned int field_usage)
> +{
> +	static const int report_types[] = { HID_INPUT_REPORT, HID_OUTPUT_REPORT,
> +					    HID_FEATURE_REPORT };
> +	struct hid_report *report;
> +	struct hid_field *field;
> +	int t;
> +
> +	for (t = 0; t < ARRAY_SIZE(report_types); t++) {
> +		struct list_head *report_list =
> +			    &hdev->report_enum[report_types[t]].report_list;
> +		list_for_each_entry(report, report_list, list) {
> +			if (report->application != application)
> +				continue;
> +
> +			field = appleib_find_report_field(report, field_usage);
> +			if (field)
> +				return field;
> +		}
> +	}
> +
> +	return NULL;
> +}
> +EXPORT_SYMBOL_GPL(appleib_find_hid_field);
> +
> +static int appleib_ll_start(struct hid_device *hdev)
> +{
> +	return 0;
> +}
> +
> +static void appleib_ll_stop(struct hid_device *hdev)
> +{
> +}
> +
> +static int appleib_ll_open(struct hid_device *hdev)
> +{
> +	// TODO: allow event reporting
Comment syntax :)

> +	return 0;
> +}
> +
> +static void appleib_ll_close(struct hid_device *hdev)
> +{
> +	// TODO: disallow event reporting
> +}
> +
> +static int appleib_ll_power(struct hid_device *hdev, int level)
> +{
> +	struct appleib_hid_dev_info *hdev_info = hdev->driver_data;
> +
> +	return hid_hw_power(hdev_info->hdev, level);
> +}
> +
> +static int appleib_ll_parse(struct hid_device *hdev)
> +{
> +	struct appleib_hid_dev_info *hdev_info = hdev->driver_data;
> +
> +	return hid_parse_report(hdev, hdev_info->hdev->rdesc,
> +				hdev_info->hdev->rsize);
> +}
> +
> +static void appleib_ll_request(struct hid_device *hdev,
> +			       struct hid_report *report, int reqtype)
> +{
> +	struct appleib_hid_dev_info *hdev_info = hdev->driver_data;
> +
> +	hid_hw_request(hdev_info->hdev, report, reqtype);
> +}
> +
> +static int appleib_ll_wait(struct hid_device *hdev)
> +{
> +	struct appleib_hid_dev_info *hdev_info = hdev->driver_data;
> +
> +	hid_hw_wait(hdev_info->hdev);
> +	return 0;
> +}
> +
> +static int appleib_ll_raw_request(struct hid_device *hdev,
> +				  unsigned char reportnum, __u8 *buf,
> +				  size_t len, unsigned char rtype, int reqtype)
> +{
> +	struct appleib_hid_dev_info *hdev_info = hdev->driver_data;
> +
> +	return hid_hw_raw_request(hdev_info->hdev, reportnum, buf, len, rtype,
> +				  reqtype);
> +}
> +
> +static int appleib_ll_output_report(struct hid_device *hdev, __u8 *buf,
> +				    size_t len)
> +{
> +	struct appleib_hid_dev_info *hdev_info = hdev->driver_data;
> +
> +	return hid_hw_output_report(hdev_info->hdev, buf, len);
> +}
> +
> +static struct hid_ll_driver appleib_ll_driver = {
> +	.start = appleib_ll_start,
> +	.stop = appleib_ll_stop,
> +	.open = appleib_ll_open,
> +	.close = appleib_ll_close,
> +	.power = appleib_ll_power,
> +	.parse = appleib_ll_parse,
> +	.request = appleib_ll_request,
> +	.wait = appleib_ll_wait,
> +	.raw_request = appleib_ll_raw_request,
> +	.output_report = appleib_ll_output_report,
> +};
> +
> +static struct hid_device *
> +appleib_add_sub_dev(struct appleib_hid_dev_info *hdev_info,
> +		    struct hid_device_id *dev_id)
> +{
> +	struct hid_device *sub_hdev;
> +	int rc;
> +
> +	sub_hdev = hid_allocate_device();
> +	if (IS_ERR(sub_hdev))
> +		return sub_hdev;
> +
> +	sub_hdev->dev.parent = &hdev_info->hdev->dev;
> +
> +	sub_hdev->bus = dev_id->bus;
> +	sub_hdev->group = dev_id->group;
> +	sub_hdev->vendor = dev_id->vendor;
> +	sub_hdev->product = dev_id->product;
> +
> +	sub_hdev->ll_driver = &appleib_ll_driver;
> +
> +	snprintf(sub_hdev->name, sizeof(sub_hdev->name),
> +		 "iBridge Virtual HID %s/%04x:%04x",
> +		 dev_name(sub_hdev->dev.parent), sub_hdev->vendor,
> +		 sub_hdev->product);
> +
> +	sub_hdev->driver_data = hdev_info;
> +
> +	rc = hid_add_device(sub_hdev);
> +	if (rc) {
> +		hid_destroy_device(sub_hdev);
> +		return ERR_PTR(rc);
> +	}
> +
> +	return sub_hdev;
> +}
> +
> +static struct appleib_hid_dev_info *appleib_add_device(struct hid_device *hdev)
> +{
> +	struct appleib_hid_dev_info *hdev_info;
> +	int i;
> +
> +	hdev_info = devm_kzalloc(&hdev->dev, sizeof(*hdev_info), GFP_KERNEL);
> +	if (!hdev_info)
> +		return ERR_PTR(-ENOMEM);
> +
> +	hdev_info->hdev = hdev;
> +
> +	for (i = 0; i < ARRAY_SIZE(hdev_info->sub_hdevs); i++) {
> +		hdev_info->sub_hdevs[i] =
> +			appleib_add_sub_dev(hdev_info, &appleib_sub_hid_ids[i]);
> +
> +		if (IS_ERR(hdev_info->sub_hdevs[i])) {
> +			while (i-- > 0)
> +				hid_destroy_device(hdev_info->sub_hdevs[i]);
> +			return (void *)hdev_info->sub_hdevs[i];
> +		}
> +	}
> +
> +	return hdev_info;
> +}
> +
> +static int appleib_hid_probe(struct hid_device *hdev,
> +			     const struct hid_device_id *id)
> +{
> +	struct appleib_hid_dev_info *hdev_info;
> +	struct usb_device *udev;
> +	int rc;
> +
> +	/* check and set usb config first */
> +	udev = hid_to_usb_dev(hdev);
> +
> +	if (udev->actconfig->desc.bConfigurationValue != APPLEIB_BASIC_CONFIG) {
> +		rc = usb_driver_set_configuration(udev, APPLEIB_BASIC_CONFIG);
> +		return rc ? rc : -ENODEV;
> +	}
> +
> +	rc = hid_parse(hdev);
> +	if (rc) {
> +		hid_err(hdev, "ib: hid parse failed (%d)\n", rc);
> +		goto error;
> +	}
> +
> +	rc = hid_hw_start(hdev, HID_CONNECT_DRIVER);
> +	if (rc) {
> +		hid_err(hdev, "ib: hw start failed (%d)\n", rc);
> +		goto error;
> +	}
> +
> +	hdev_info = appleib_add_device(hdev);
> +	if (IS_ERR(hdev_info)) {
> +		rc = PTR_ERR(hdev_info);
> +		goto stop_hw;
> +	}
> +
> +	hid_set_drvdata(hdev, hdev_info);
> +
> +	rc = hid_hw_open(hdev);
> +	if (rc) {
> +		hid_err(hdev, "ib: failed to open hid: %d\n", rc);
> +		goto remove_dev;
> +	}
> +
> +	return 0;
> +
> +remove_dev:
> +	appleib_remove_device(hdev);
> +stop_hw:
> +	hid_hw_stop(hdev);
> +error:
> +	return rc;
> +}
> +
> +static void appleib_hid_remove(struct hid_device *hdev)
> +{
> +	hid_hw_close(hdev);
> +	appleib_remove_device(hdev);
> +	hid_hw_stop(hdev);
> +}
> +
> +static const struct hid_device_id appleib_hid_ids[] = {
> +	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_IBRIDGE) },
> +	{ },
> +};
> +
> +static struct hid_driver appleib_hid_driver = {
> +	.name = "apple-ibridge-hid",
> +	.id_table = appleib_hid_ids,
> +	.probe = appleib_hid_probe,
> +	.remove = appleib_hid_remove,
> +	.raw_event = appleib_hid_raw_event,
> +#ifdef CONFIG_PM
> +	.suspend = appleib_hid_suspend,
> +	.resume = appleib_hid_resume,
> +	.reset_resume = appleib_hid_reset_resume,
> +#endif
> +};
> +
> +static struct appleib_device *appleib_alloc_device(struct acpi_device *acpi_dev)
> +{
> +	struct appleib_device *ib_dev;
> +	acpi_status sts;
> +
> +	ib_dev = devm_kzalloc(&acpi_dev->dev, sizeof(*ib_dev), GFP_KERNEL);
> +	if (!ib_dev)
> +		return ERR_PTR(-ENOMEM);
> +
> +	ib_dev->acpi_dev = acpi_dev;
> +
> +	/* get iBridge acpi power control method for suspend/resume */
> +	sts = acpi_get_handle(acpi_dev->handle, "SOCW", &ib_dev->asoc_socw);
> +	if (ACPI_FAILURE(sts)) {
> +		dev_err(LOG_DEV(ib_dev),
> +			"Error getting handle for ASOC.SOCW method: %s\n",
> +			acpi_format_exception(sts));
> +		return ERR_PTR(-ENXIO);
> +	}
> +
> +	/* ensure iBridge is powered on */
> +	sts = acpi_execute_simple_method(ib_dev->asoc_socw, NULL, 1);
> +	if (ACPI_FAILURE(sts))
> +		dev_warn(LOG_DEV(ib_dev), "SOCW(1) failed: %s\n",
> +			 acpi_format_exception(sts));
> +
> +	return ib_dev;
> +}
> +
> +static int appleib_probe(struct acpi_device *acpi)
> +{
> +	struct appleib_device *ib_dev;
> +	int ret;
> +
> +	ib_dev = appleib_alloc_device(acpi);
> +	if (IS_ERR(ib_dev))
> +		return PTR_ERR(ib_dev);
> +
> +	ret = hid_register_driver(&appleib_hid_driver);
> +	if (ret) {
> +		dev_err(LOG_DEV(ib_dev), "Error registering hid driver: %d\n",
> +			ret);
> +		return ret;
> +	}
> +
> +	acpi->driver_data = ib_dev;
> +
> +	return 0;
> +}
> +
> +static int appleib_remove(struct acpi_device *acpi)
> +{
> +	hid_unregister_driver(&appleib_hid_driver);
> +
> +	return 0;
> +}
> +
> +static int appleib_suspend(struct device *dev)
> +{
> +	struct appleib_device *ib_dev;
> +	int rc;
> +
> +	ib_dev = acpi_driver_data(to_acpi_device(dev));
> +
> +	rc = acpi_execute_simple_method(ib_dev->asoc_socw, NULL, 0);
> +	if (ACPI_FAILURE(rc))
> +		dev_warn(dev, "SOCW(0) failed: %s\n",
> +			 acpi_format_exception(rc));
> +
> +	return 0;
> +}
> +
> +static int appleib_resume(struct device *dev)
> +{
> +	struct appleib_device *ib_dev;
> +	int rc;
> +
> +	ib_dev = acpi_driver_data(to_acpi_device(dev));
> +
> +	rc = acpi_execute_simple_method(ib_dev->asoc_socw, NULL, 1);
> +	if (ACPI_FAILURE(rc))
> +		dev_warn(dev, "SOCW(1) failed: %s\n",
> +			 acpi_format_exception(rc));
> +
> +	return 0;
> +}
> +
> +static const struct dev_pm_ops appleib_pm = {
> +	.suspend = appleib_suspend,
> +	.resume = appleib_resume,
> +	.restore = appleib_resume,
> +};
> +
> +static const struct acpi_device_id appleib_acpi_match[] = {
> +	{ "APP7777", 0 },
> +	{ },
> +};
> +
> +MODULE_DEVICE_TABLE(acpi, appleib_acpi_match);
> +
> +static struct acpi_driver appleib_driver = {
> +	.name		= "apple-ibridge",
> +	.class		= "topcase", /* ? */

It's always nice to have a ? but what is the question?

> +	.owner		= THIS_MODULE,
> +	.ids		= appleib_acpi_match,
> +	.ops		= {
> +		.add		= appleib_probe,
> +		.remove		= appleib_remove,
> +	},
> +	.drv		= {
> +		.pm		= &appleib_pm,
> +	},
> +};
> +
> +module_acpi_driver(appleib_driver)
> +
> +MODULE_AUTHOR("Ronald Tschalär");
> +MODULE_DESCRIPTION("Apple iBridge driver");
> +MODULE_LICENSE("GPL v2");
> diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
> index adce58f24f76..f963bb02c477 100644
> --- a/drivers/hid/hid-ids.h
> +++ b/drivers/hid/hid-ids.h
> @@ -177,6 +177,7 @@
>  #define USB_DEVICE_ID_APPLE_IRCONTROL3	0x8241
>  #define USB_DEVICE_ID_APPLE_IRCONTROL4	0x8242
>  #define USB_DEVICE_ID_APPLE_IRCONTROL5	0x8243
> +#define USB_DEVICE_ID_APPLE_IBRIDGE	0x8600
>  
>  #define USB_VENDOR_ID_ASUS		0x0486
>  #define USB_DEVICE_ID_ASUS_T91MT	0x0185
> diff --git a/include/linux/apple-ibridge.h b/include/linux/apple-ibridge.h
> new file mode 100644
> index 000000000000..07ded8c68776
> --- /dev/null
> +++ b/include/linux/apple-ibridge.h
> @@ -0,0 +1,23 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * Apple iBridge Driver
> + *
> + * Copyright (c) 2018 Ronald Tschalär
> + */
> +
> +#ifndef __LINUX_APPLE_IBRDIGE_H
> +#define __LINUX_APPLE_IBRDIGE_H
> +
> +#include <linux/hid.h>
> +
> +#define USB_VENDOR_ID_LINUX_FOUNDATION	0x1d6b
> +#define USB_DEVICE_ID_IBRIDGE_TB	0x0301
> +#define USB_DEVICE_ID_IBRIDGE_ALS	0x0302
> +
> +struct hid_field *appleib_find_report_field(struct hid_report *report,
> +					    unsigned int field_usage);
> +struct hid_field *appleib_find_hid_field(struct hid_device *hdev,
> +					 unsigned int application,
> +					 unsigned int field_usage);
> +
> +#endif


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

* Re: [PATCH v2 2/3] HID: apple-ib-tb: Add driver for the Touch Bar on MacBook Pro's.
  2019-06-12  8:33 ` [PATCH v2 2/3] HID: apple-ib-tb: Add driver for the Touch Bar on MacBook Pro's Ronald Tschalär
@ 2019-06-16 14:28   ` Jonathan Cameron
  0 siblings, 0 replies; 7+ messages in thread
From: Jonathan Cameron @ 2019-06-16 14:28 UTC (permalink / raw)
  To: Ronald Tschalär
  Cc: Jiri Kosina, Benjamin Tissoires, Hartmut Knaack,
	Lars-Peter Clausen, Peter Meerwald-Stadler, linux-input,
	linux-iio, linux-kernel

On Wed, 12 Jun 2019 01:33:59 -0700
Ronald Tschalär <ronald@innovation.ch> wrote:

> This driver enables basic touch bar functionality: enabling it, switching
> between modes on FN key press, and dimming and turning the display
> off/on when idle/active.
> 
> Signed-off-by: Ronald Tschalär <ronald@innovation.ch>
A few minor comments inline from me, but as before well outside of my
areas of knowledge!

Jonathan

> ---
>  drivers/hid/Kconfig       |   10 +
>  drivers/hid/Makefile      |    1 +
>  drivers/hid/apple-ib-tb.c | 1389 +++++++++++++++++++++++++++++++++++++
>  3 files changed, 1400 insertions(+)
>  create mode 100644 drivers/hid/apple-ib-tb.c
> 
> diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
> index 545d3691fc1c..7621c2500d71 100644
> --- a/drivers/hid/Kconfig
> +++ b/drivers/hid/Kconfig
> @@ -149,6 +149,16 @@ config HID_APPLE_IBRIDGE
>  	  To compile this driver as a module, choose M here: the
>  	  module will be called apple-ibridge.
>  
> +config HID_APPLE_IBRIDGE_TB
> +	tristate "Apple iBridge Touch Bar"
> +	depends on HID_APPLE_IBRIDGE
> +	---help---
> +	Say Y here if you want support for the Touch Bar on recent
> +	MacBook Pros.
> +
> +	To compile this driver as a module, choose M here: the
> +	module will be called apple-ib-tb.
> +
>  config HID_APPLEIR
>  	tristate "Apple infrared receiver"
>  	depends on (USB_HID)
> diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile
> index a4da5663a541..0c46e5f70db1 100644
> --- a/drivers/hid/Makefile
> +++ b/drivers/hid/Makefile
> @@ -27,6 +27,7 @@ obj-$(CONFIG_HID_ALPS)		+= hid-alps.o
>  obj-$(CONFIG_HID_ACRUX)		+= hid-axff.o
>  obj-$(CONFIG_HID_APPLE)		+= hid-apple.o
>  obj-$(CONFIG_HID_APPLE_IBRIDGE)	+= apple-ibridge.o
> +obj-$(CONFIG_HID_APPLE_IBRIDGE_TB)	+= apple-ib-tb.o
>  obj-$(CONFIG_HID_APPLEIR)	+= hid-appleir.o
>  obj-$(CONFIG_HID_ASUS)		+= hid-asus.o
>  obj-$(CONFIG_HID_AUREAL)	+= hid-aureal.o
> diff --git a/drivers/hid/apple-ib-tb.c b/drivers/hid/apple-ib-tb.c
> new file mode 100644
> index 000000000000..6daee80060ce
> --- /dev/null
> +++ b/drivers/hid/apple-ib-tb.c
> @@ -0,0 +1,1389 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Apple Touch Bar Driver
> + *
> + * Copyright (c) 2017-2018 Ronald Tschalär
> + */
> +
> +/*
> + * Recent MacBookPro models (13,[23] and 14,[23]) have a touch bar, which
> + * is exposed via several USB interfaces. MacOS supports a fancy mode
> + * where arbitrary buttons can be defined; this driver currently only
> + * supports the simple mode that consists of 3 predefined layouts
> + * (escape-only, esc + special keys, and esc + function keys).
> + *
> + * The first USB HID interface supports two reports, an input report that
> + * is used to report the key presses, and an output report which can be
> + * used to set the touch bar "mode": touch bar off (in which case no touches
> + * are reported at all), escape key only, escape + 12 function keys, and
> + * escape + several special keys (including brightness, audio volume,
> + * etc). The second interface supports several, complex reports, most of
> + * which are unknown at this time, but one of which has been determined to
> + * allow for controlling of the touch bar's brightness: off (though touches
> + * are still reported), dimmed, and full brightness. This driver makes
> + * use of these two reports.
> + */
> +
> +#define dev_fmt(fmt) "tb: " fmt
> +
> +#include <linux/apple-ibridge.h>
> +#include <linux/device.h>
> +#include <linux/hid.h>
> +#include <linux/input.h>
> +#include <linux/jiffies.h>
> +#include <linux/ktime.h>
> +#include <linux/module.h>
> +#include <linux/slab.h>
> +#include <linux/spinlock.h>
> +#include <linux/string.h>
> +#include <linux/sysfs.h>
> +#include <linux/usb/ch9.h>
> +#include <linux/usb.h>
> +#include <linux/workqueue.h>
> +
> +#define HID_UP_APPLE		0xff120000
> +#define HID_USAGE_MODE		(HID_UP_CUSTOM | 0x0004)
> +#define HID_USAGE_APPLE_APP	(HID_UP_APPLE  | 0x0001)
> +#define HID_USAGE_DISP		(HID_UP_APPLE  | 0x0021)
> +#define HID_USAGE_DISP_AUX1	(HID_UP_APPLE  | 0x0020)
> +
> +#define APPLETB_MAX_TB_KEYS	13	/* ESC, F1-F12 */
> +
> +#define APPLETB_CMD_MODE_ESC	0
> +#define APPLETB_CMD_MODE_FN	1
> +#define APPLETB_CMD_MODE_SPCL	2
> +#define APPLETB_CMD_MODE_OFF	3
> +#define APPLETB_CMD_MODE_UPD	254
> +#define APPLETB_CMD_MODE_NONE	255
> +
> +#define APPLETB_CMD_DISP_ON	1
> +#define APPLETB_CMD_DISP_DIM	2
> +#define APPLETB_CMD_DISP_OFF	4
> +#define APPLETB_CMD_DISP_UPD	254
> +#define APPLETB_CMD_DISP_NONE	255
> +
> +#define APPLETB_FN_MODE_FKEYS	0
> +#define APPLETB_FN_MODE_NORM	1
> +#define APPLETB_FN_MODE_INV	2
> +#define APPLETB_FN_MODE_SPCL	3
> +#define APPLETB_FN_MODE_MAX	APPLETB_FN_MODE_SPCL
> +
> +#define APPLETB_DEVID_KEYBOARD	1
> +#define APPLETB_DEVID_TOUCHPAD	2
> +
> +#define APPLETB_MAX_DIM_TIME	30
> +
> +static int appletb_tb_def_idle_timeout = 5 * 60;
> +module_param_named(idle_timeout, appletb_tb_def_idle_timeout, int, 0444);
> +MODULE_PARM_DESC(idle_timeout, "Default touch bar idle timeout:\n"
> +			       "    >0 - turn touch bar display off after no keyboard, trackpad, or touch bar input has been received for this many seconds;\n"
> +			       "         the display will be turned back on as soon as new input is received\n"
> +			       "     0 - turn touch bar display off (input does not turn it on again)\n"
> +			       "    -1 - turn touch bar display on (does not turn off automatically)\n"
> +			       "    -2 - disable touch bar completely");
> +
> +static int appletb_tb_def_dim_timeout = -2;
> +module_param_named(dim_timeout, appletb_tb_def_dim_timeout, int, 0444);
> +MODULE_PARM_DESC(dim_timeout, "Default touch bar dim timeout:\n"
> +			      "    >0 - dim touch bar display after no keyboard, trackpad, or touch bar input has been received for this many seconds\n"
> +			      "         the display will be returned to full brightness as soon as new input is received\n"
> +			      "     0 - dim touch bar display (input does not return it to full brightness)\n"
> +			      "    -1 - disable timeout (touch bar never dimmed)\n"
> +			      "    [-2] - calculate timeout based on idle-timeout");
> +
> +static int appletb_tb_def_fn_mode = APPLETB_FN_MODE_NORM;
> +module_param_named(fnmode, appletb_tb_def_fn_mode, int, 0444);
> +MODULE_PARM_DESC(fnmode, "Default Fn key mode:\n"
> +			 "    0 - function-keys only\n"
> +			 "    [1] - fn key switches from special to function-keys\n"
> +			 "    2 - inverse of 1\n"
> +			 "    3 - special keys only");
> +
> +static ssize_t idle_timeout_show(struct device *dev,
> +				 struct device_attribute *attr, char *buf);
> +static ssize_t idle_timeout_store(struct device *dev,
> +				  struct device_attribute *attr,
> +				  const char *buf, size_t size);
> +static DEVICE_ATTR_RW(idle_timeout);
> +
> +static ssize_t dim_timeout_show(struct device *dev,
> +				struct device_attribute *attr, char *buf);
> +static ssize_t dim_timeout_store(struct device *dev,
> +				 struct device_attribute *attr,
> +				 const char *buf, size_t size);
> +static DEVICE_ATTR_RW(dim_timeout);
> +
> +static ssize_t fnmode_show(struct device *dev, struct device_attribute *attr,
> +			   char *buf);
> +static ssize_t fnmode_store(struct device *dev, struct device_attribute *attr,
> +			    const char *buf, size_t size);
> +static DEVICE_ATTR_RW(fnmode);
> +
> +static struct attribute *appletb_attrs[] = {
> +	&dev_attr_idle_timeout.attr,
> +	&dev_attr_dim_timeout.attr,
> +	&dev_attr_fnmode.attr,
> +	NULL,

Documentation for these? 

> +};
> +
> +static const struct attribute_group appletb_attr_group = {
> +	.attrs = appletb_attrs,
> +};
> +
...

> +/*
> + * While the mode functionality is listed as a valid hid report in the usb
> + * interface descriptor, it's not sent that way. Instead it's sent with
> + * different request-type and without a leading report-id in the data. Hence
> + * we need to send it as a custom usb control message rather via any of the
> + * standard hid_hw_*request() functions.
> + */
> +static int appletb_set_tb_mode(struct appletb_device *tb_dev,
> +			       unsigned char mode)
> +{
> +	void *buf;
> +	bool autopm_off = false;
> +	int rc;
> +
> +	if (!tb_dev->mode_iface.hdev)
> +		return -ENOTCONN;
> +
> +	buf = kmemdup(&mode, 1, GFP_KERNEL);
> +	if (!buf)
> +		return -ENOMEM;
> +
> +	autopm_off = appletb_disable_autopm(tb_dev->mode_iface.hdev);
> +
> +	rc = appletb_send_usb_ctrl(&tb_dev->mode_iface,
> +				   USB_DIR_OUT | USB_TYPE_VENDOR |
> +							USB_RECIP_DEVICE,
Odd indentation.

> +				   tb_dev->mode_field->report, buf, 1);
> +	if (rc < 0)
> +		dev_err(tb_dev->log_dev,
> +			"Failed to set touch bar mode to %u (%d)\n", mode, rc);
> +
> +	if (autopm_off)
> +		hid_hw_power(tb_dev->mode_iface.hdev, PM_HINT_NORMAL);
> +
> +	kfree(buf);
> +
> +	return rc;
> +}
...

> +
> +static int appletb_set_tb_disp(struct appletb_device *tb_dev,
> +			       unsigned char disp)
> +{
> +	struct hid_report *report;
> +	int rc;
> +
> +	if (!tb_dev->disp_iface.hdev)
> +		return -ENOTCONN;
> +
> +	report = tb_dev->disp_field->report;
> +
> +	rc = hid_set_field(tb_dev->disp_field_aux1, 0, 1);
> +	if (!rc)
> +		rc = hid_set_field(tb_dev->disp_field, 0, disp);

This stacked error handling is less readable than just having two
separate error blocks with very similar comments.

> +	if (rc) {
> +		dev_err(tb_dev->log_dev,
> +			"Failed to set display report fields (%d)\n", rc);
> +		return rc;
> +	}
> +
> +	/*
> +	 * Keep the USB interface powered on while the touch bar display is on
> +	 * for better responsiveness.
> +	 */
> +	if (disp != APPLETB_CMD_DISP_OFF && !tb_dev->tb_autopm_off)
> +		tb_dev->tb_autopm_off =
> +			appletb_disable_autopm(report->device);
> +
> +	rc = appletb_send_hid_report(&tb_dev->disp_iface, report);
> +
Nitpick. For consistency should probably not be a blank line here.
> +	if (rc < 0)
> +		dev_err(tb_dev->log_dev,
> +			"Failed to set touch bar display to %u (%d)\n", disp,
> +			rc);
> +
> +	if (disp == APPLETB_CMD_DISP_OFF && tb_dev->tb_autopm_off) {
> +		hid_hw_power(tb_dev->disp_iface.hdev, PM_HINT_NORMAL);
> +		tb_dev->tb_autopm_off = false;
> +	}
> +
> +	return rc;
> +}
> +
...

> +static int appletb_probe(struct hid_device *hdev,
> +			 const struct hid_device_id *id)
> +{
> +	struct appletb_device *tb_dev = appletb_dev;
> +	struct appletb_iface_info *iface_info;
> +	unsigned long flags;
> +	int rc;
> +
> +	spin_lock_irqsave(&tb_dev->tb_lock, flags);
> +
> +	if (!tb_dev->log_dev)
> +		tb_dev->log_dev = &hdev->dev;
> +
> +	spin_unlock_irqrestore(&tb_dev->tb_lock, flags);
> +
> +	hid_set_drvdata(hdev, tb_dev);
> +
> +	/* initialize the report info */
> +	rc = hid_parse(hdev);
> +	if (rc) {
> +		dev_err(tb_dev->log_dev, "als: hid parse failed (%d)\n", rc);
> +		goto error;
> +	}
> +
> +	rc = appletb_extract_report_info(tb_dev, hdev);
> +	if (rc < 0)
> +		goto error;
> +
> +	rc = hid_hw_start(hdev, HID_CONNECT_DRIVER | HID_CONNECT_HIDINPUT);
> +	if (rc) {
> +		dev_err(tb_dev->log_dev, "hw start failed (%d)\n", rc);
> +		goto clear_iface_info;
> +	}
> +
> +	rc = hid_hw_open(hdev);
> +	if (rc) {
> +		dev_err(tb_dev->log_dev, "hw open failed (%d)\n", rc);
> +		goto stop_hid;
> +	}
> +
> +	/* do setup if we have both interfaces */
> +	if (appletb_test_and_mark_active(tb_dev)) {
> +		/* initialize the touch bar */
> +		if (appletb_tb_def_fn_mode >= 0 &&
> +		    appletb_tb_def_fn_mode <= APPLETB_FN_MODE_MAX)
> +			tb_dev->fn_mode = appletb_tb_def_fn_mode;
> +		else
> +			tb_dev->fn_mode = APPLETB_FN_MODE_NORM;
> +		appletb_set_idle_timeout(tb_dev, appletb_tb_def_idle_timeout);
> +		appletb_set_dim_timeout(tb_dev, appletb_tb_def_dim_timeout);
> +		tb_dev->last_event_time = ktime_get();
> +
> +		tb_dev->pnd_tb_mode = APPLETB_CMD_MODE_UPD;
> +		tb_dev->pnd_tb_disp = APPLETB_CMD_DISP_UPD;
> +
> +		appletb_update_touchbar(tb_dev, false);
> +
> +		/* set up the input handler */
> +		tb_dev->inp_handler.event = appletb_inp_event;
> +		tb_dev->inp_handler.connect = appletb_inp_connect;
> +		tb_dev->inp_handler.disconnect = appletb_inp_disconnect;
> +		tb_dev->inp_handler.name = "appletb";
> +		tb_dev->inp_handler.id_table = appletb_input_devices;
> +		tb_dev->inp_handler.private = tb_dev;
> +
> +		rc = input_register_handler(&tb_dev->inp_handler);
> +		if (rc) {
> +			dev_err(tb_dev->log_dev,
> +				"Unable to register keyboard handler (%d)\n",
> +				rc);
> +			goto mark_inactive;
> +		}
> +
> +		/* initialize sysfs attributes */
> +		rc = sysfs_create_group(&tb_dev->mode_iface.hdev->dev.kobj,
> +					&appletb_attr_group);
> +		if (rc) {
> +			dev_err(tb_dev->log_dev,
> +				"Failed to create sysfs attributes (%d)\n", rc);
> +			goto unreg_handler;
> +		}
> +
> +		dev_dbg(tb_dev->log_dev, "Touchbar activated\n");
> +	}
> +
> +	return 0;
> +
> +unreg_handler:
> +	input_unregister_handler(&tb_dev->inp_handler);
> +mark_inactive:
> +	appletb_test_and_mark_inactive(tb_dev, hdev);
> +	cancel_delayed_work_sync(&tb_dev->tb_work);
> +	hid_hw_close(hdev);
> +stop_hid:
> +	hid_hw_stop(hdev);
> +clear_iface_info:
> +	iface_info = appletb_get_iface_info(tb_dev, hdev);
> +	if (iface_info) {
> +		usb_put_intf(iface_info->usb_iface);
> +		iface_info->usb_iface = NULL;
> +		iface_info->hdev = NULL;
> +	}

It might be cleaner to put this block into a utility function
named appropriately to make it clear it's undoing stuff from
the *_extract_report_info call.  Initially I wondered where
this stuff we are undoing came from.

> +error:
> +	return rc;
> +}
> +
> +static void appletb_remove(struct hid_device *hdev)
> +{
> +	struct appletb_device *tb_dev = hid_get_drvdata(hdev);
> +	struct appletb_iface_info *iface_info;
> +	unsigned long flags;
> +
> +	if (appletb_test_and_mark_inactive(tb_dev, hdev)) {
> +		sysfs_remove_group(&tb_dev->mode_iface.hdev->dev.kobj,
> +				   &appletb_attr_group);
> +
> +		input_unregister_handler(&tb_dev->inp_handler);
> +
> +		cancel_delayed_work_sync(&tb_dev->tb_work);
> +		appletb_set_tb_mode(tb_dev, APPLETB_CMD_MODE_OFF);
> +		appletb_set_tb_disp(tb_dev, APPLETB_CMD_DISP_ON);
> +
> +		if (tb_dev->tb_autopm_off)
> +			hid_hw_power(tb_dev->disp_iface.hdev, PM_HINT_NORMAL);
> +
> +		dev_info(tb_dev->log_dev, "Touchbar deactivated\n");
> +	}
> +
> +	hid_hw_close(hdev);
> +	hid_hw_stop(hdev);
> +
> +	iface_info = appletb_get_iface_info(tb_dev, hdev);
> +	if (iface_info) {
> +		usb_put_intf(iface_info->usb_iface);
> +		iface_info->usb_iface = NULL;
> +		iface_info->hdev = NULL;
> +	}
> +
> +	spin_lock_irqsave(&tb_dev->tb_lock, flags);
> +
> +	if (tb_dev->log_dev == &hdev->dev) {
> +		if (tb_dev->mode_iface.hdev)
> +			tb_dev->log_dev = &tb_dev->mode_iface.hdev->dev;
> +		else if (tb_dev->disp_iface.hdev)
> +			tb_dev->log_dev = &tb_dev->disp_iface.hdev->dev;
> +		else
> +			tb_dev->log_dev = NULL;
> +	}
> +
> +	spin_unlock_irqrestore(&tb_dev->tb_lock, flags);
> +}
...


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

* Re: [PATCH v2 3/3] iio: light: apple-ib-als: Add driver for ALS on iBridge chip.
  2019-06-12  8:34 ` [PATCH v2 3/3] iio: light: apple-ib-als: Add driver for ALS on iBridge chip Ronald Tschalär
@ 2019-06-16 14:52   ` Jonathan Cameron
  0 siblings, 0 replies; 7+ messages in thread
From: Jonathan Cameron @ 2019-06-16 14:52 UTC (permalink / raw)
  To: Ronald Tschalär
  Cc: Jiri Kosina, Benjamin Tissoires, Hartmut Knaack,
	Lars-Peter Clausen, Peter Meerwald-Stadler, linux-input,
	linux-iio, linux-kernel

On Wed, 12 Jun 2019 01:34:00 -0700
Ronald Tschalär <ronald@innovation.ch> wrote:

> On 2016/2017 MacBook Pro's with a Touch Bar the ALS is attached to,
> and exposed via the iBridge device. This provides the driver for that
> sensor.
> 
> Signed-off-by: Ronald Tschalär <ronald@innovation.ch>
Hi Ronald,

One thing that we should perhaps document more clearly in IIO is that
it is acceptable to not have triggers if they don't make any sense.
In this particular case, you have one basically to give a way of saying
to move into a more continuous sampling mode from a polled one (I think).
For that just use the buffer enable callbacks.

It'll be much cleaner without the trigger.

A few other suggestions inline.  In particularly I'm not that keen on the
appleals_device having a pointer to the iio device which then has
a pointer back again.  I 'think' you can just reorder things a bit and
embed the appleals_device structure in the iio_dev private field directly
and avoid the dance between the different structures.

Thanks,

Jonathan

> ---
>  drivers/iio/light/Kconfig        |  12 +
>  drivers/iio/light/Makefile       |   1 +
>  drivers/iio/light/apple-ib-als.c | 607 +++++++++++++++++++++++++++++++
>  3 files changed, 620 insertions(+)
>  create mode 100644 drivers/iio/light/apple-ib-als.c
> 
> diff --git a/drivers/iio/light/Kconfig b/drivers/iio/light/Kconfig
> index 5190eacfeb0a..b477aa5d2024 100644
> --- a/drivers/iio/light/Kconfig
> +++ b/drivers/iio/light/Kconfig
> @@ -64,6 +64,18 @@ config APDS9960
>  	  To compile this driver as a module, choose M here: the
>  	  module will be called apds9960
>  
> +config APPLE_IBRIDGE_ALS
> +	tristate "Apple iBridge ambient light sensor"
> +	select IIO_BUFFER
> +	select IIO_TRIGGERED_BUFFER
> +	depends on HID_APPLE_IBRIDGE
> +	help
> +	  Say Y here to build the driver for the Apple iBridge ALS
> +	  sensor.
> +
> +	  To compile this driver as a module, choose M here: the
> +	  module will be called apple-ib-als.
> +
>  config BH1750
>  	tristate "ROHM BH1750 ambient light sensor"
>  	depends on I2C
> diff --git a/drivers/iio/light/Makefile b/drivers/iio/light/Makefile
> index e40794fbb435..cd6cd5ba6da5 100644
> --- a/drivers/iio/light/Makefile
> +++ b/drivers/iio/light/Makefile
> @@ -9,6 +9,7 @@ obj-$(CONFIG_ADJD_S311)		+= adjd_s311.o
>  obj-$(CONFIG_AL3320A)		+= al3320a.o
>  obj-$(CONFIG_APDS9300)		+= apds9300.o
>  obj-$(CONFIG_APDS9960)		+= apds9960.o
> +obj-$(CONFIG_APPLE_IBRIDGE_ALS)	+= apple-ib-als.o
>  obj-$(CONFIG_BH1750)		+= bh1750.o
>  obj-$(CONFIG_BH1780)		+= bh1780.o
>  obj-$(CONFIG_CM32181)		+= cm32181.o
> diff --git a/drivers/iio/light/apple-ib-als.c b/drivers/iio/light/apple-ib-als.c
> new file mode 100644
> index 000000000000..b84be0076e0f
> --- /dev/null
> +++ b/drivers/iio/light/apple-ib-als.c
> @@ -0,0 +1,607 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Apple Ambient Light Sensor Driver
> + *
> + * Copyright (c) 2017-2018 Ronald Tschalär
> + */
> +
> +/*
> + * MacBookPro models with an iBridge chip (13,[23] and 14,[23]) have an
> + * ambient light sensor that is exposed via one of the USB interfaces on
> + * the iBridge as a standard HID light sensor. However, we cannot use the
> + * existing hid-sensor-als driver, for two reasons:
> + *
> + * 1. The hid-sensor-als driver is part of the hid-sensor-hub which in turn
> + *    is a hid driver, but you can't have more than one hid driver per hid
> + *    device, which is a problem because the touch bar also needs to
> + *    register as a driver for this hid device.
> + *
> + * 2. While the hid-sensors-als driver stores sensor readings received via
> + *    interrupt in an iio buffer, reads on the sysfs
> + *    .../iio:deviceX/in_illuminance_YYY attribute result in a get of the
> + *    feature report; however, in the case of this sensor here the
> + *    illuminance field of that report is always 0. Instead, the input
> + *    report needs to be requested.
> + */
> +
> +#define dev_fmt(fmt) "als: " fmt
> +
> +#include <linux/apple-ibridge.h>
> +#include <linux/device.h>
> +#include <linux/hid.h>
> +#include <linux/hid-sensor-ids.h>
> +#include <linux/iio/buffer.h>
> +#include <linux/iio/iio.h>
> +#include <linux/iio/trigger_consumer.h>
> +#include <linux/iio/triggered_buffer.h>
> +#include <linux/iio/trigger.h>
> +#include <linux/module.h>
> +#include <linux/slab.h>
> +
> +#define APPLEALS_DYN_SENS		0	/* our dynamic sensitivity */
> +#define APPLEALS_DEF_CHANGE_SENS	APPLEALS_DYN_SENS
> +
> +struct appleals_device {
> +	struct hid_device	*hid_dev;
> +	struct hid_report	*cfg_report;
> +	struct hid_field	*illum_field;
> +	struct iio_dev		*iio_dev;
> +	int			cur_sensitivity;
> +	int			cur_hysteresis;
> +	bool			events_enabled;
> +};
> +
> +static struct hid_driver appleals_hid_driver;
> +
> +/*
> + * This is a primitive way to get a relative sensitivity, one where we get
> + * notified when the value changes by a certain percentage rather than some
> + * absolute value. MacOS somehow manages to configure the sensor to work this
> + * way (with a 15% relative sensitivity), but I haven't been able to figure
> + * out how so far. So until we do, this provides a less-than-perfect
> + * simulation.
> + *
> + * When the brightness value is within one of the ranges, the sensitivity is
> + * set to that range's sensitivity. But in order to reduce flapping when the
> + * brightness is right on the border between two ranges, the ranges overlap
> + * somewhat (by at least one sensitivity), and sensitivity is only changed if
> + * the value leaves the current sensitivity's range.
> + *
> + * The values chosen for the map are somewhat arbitrary: a compromise of not
> + * too many ranges (and hence changing the sensitivity) but not too small or
> + * large of a percentage of the min and max values in the range (currently
> + * from 7.5% to 30%, i.e. within a factor of 2 of 15%), as well as just plain
> + * "this feels reasonable to me".
> + */
> +struct appleals_sensitivity_map {
> +	int	sensitivity;
> +	int	illum_low;
> +	int	illum_high;
> +};
> +
> +static const struct appleals_sensitivity_map appleals_sensitivity_map[] = {
> +	{   1,    0,   14 },
> +	{   3,   10,   40 },
> +	{   9,   30,  120 },
> +	{  27,   90,  360 },
> +	{  81,  270, 1080 },
> +	{ 243,  810, 3240 },
> +	{ 729, 2430, 9720 },
> +};
> +
> +static int appleals_compute_sensitivity(int cur_illum, int cur_sens)
> +{
> +	const struct appleals_sensitivity_map *entry;
> +	int i;
> +
> +	/* see if we're still in current range */
> +	for (i = 0; i < ARRAY_SIZE(appleals_sensitivity_map); i++) {
> +		entry = &appleals_sensitivity_map[i];
> +
> +		if (entry->sensitivity == cur_sens &&
> +		    entry->illum_low <= cur_illum &&
> +		    entry->illum_high >= cur_illum)
> +			return cur_sens;
> +		else if (entry->sensitivity > cur_sens)
> +			break;
> +	}
> +
> +	/* not in current range, so find new sensitivity */
> +	for (i = 0; i < ARRAY_SIZE(appleals_sensitivity_map); i++) {
> +		entry = &appleals_sensitivity_map[i];
> +
> +		if (entry->illum_low <= cur_illum &&
> +		    entry->illum_high >= cur_illum)
> +			return entry->sensitivity;
> +	}
> +
> +	/* hmm, not in table, so assume we are above highest range */
> +	i = ARRAY_SIZE(appleals_sensitivity_map) - 1;
> +	return appleals_sensitivity_map[i].sensitivity;
> +}
> +
> +static int appleals_get_field_value_for_usage(struct hid_field *field,
> +					      unsigned int usage)
> +{
> +	int u;
> +
> +	if (!field)
> +		return -1;
> +
> +	for (u = 0; u < field->maxusage; u++) {
> +		if (field->usage[u].hid == usage)
> +			return u + field->logical_minimum;
> +	}
> +
> +	return -1;
> +}
> +
> +static __s32 appleals_get_field_value(struct appleals_device *als_dev,
> +				      struct hid_field *field)
> +{
> +	bool powered_on = !hid_hw_power(als_dev->hid_dev, PM_HINT_FULLON);
> +
> +	hid_hw_request(als_dev->hid_dev, field->report, HID_REQ_GET_REPORT);
> +	hid_hw_wait(als_dev->hid_dev);
> +
> +	if (powered_on)
> +		hid_hw_power(als_dev->hid_dev, PM_HINT_NORMAL);
> +
> +	return field->value[0];
> +}
> +
> +static void appleals_set_field_value(struct appleals_device *als_dev,
> +				     struct hid_field *field, __s32 value)
> +{
> +	hid_set_field(field, 0, value);
> +	hid_hw_request(als_dev->hid_dev, field->report, HID_REQ_SET_REPORT);
> +}
> +
> +static int appleals_get_config(struct appleals_device *als_dev,
> +			       unsigned int field_usage, __s32 *value)
> +{
> +	struct hid_field *field;
> +
> +	field = appleib_find_report_field(als_dev->cfg_report, field_usage);
> +	if (!field)
> +		return -EINVAL;
> +
> +	*value = appleals_get_field_value(als_dev, field);
> +
> +	return 0;
> +}
> +
> +static int appleals_set_config(struct appleals_device *als_dev,
> +			       unsigned int field_usage, __s32 value)
> +{
> +	struct hid_field *field;
> +
> +	field = appleib_find_report_field(als_dev->cfg_report, field_usage);
> +	if (!field)
> +		return -EINVAL;
> +
> +	appleals_set_field_value(als_dev, field, value);
> +
> +	return 0;
> +}
> +
> +static int appleals_set_enum_config(struct appleals_device *als_dev,
> +				    unsigned int field_usage,
> +				    unsigned int value_usage)
> +{
> +	struct hid_field *field;
> +	int value;
> +
> +	field = appleib_find_report_field(als_dev->cfg_report, field_usage);
> +	if (!field)
> +		return -EINVAL;
> +
> +	value = appleals_get_field_value_for_usage(field, value_usage);
> +	if (value >= 0)
> +		appleals_set_field_value(als_dev, field, value);
> +
> +	return 0;
> +}
> +
> +static void appleals_update_dyn_sensitivity(struct appleals_device *als_dev,
> +					    __s32 value)
> +{
> +	int new_sens;
> +	int rc;
> +
> +	new_sens = appleals_compute_sensitivity(value,
> +						als_dev->cur_sensitivity);
> +	if (new_sens != als_dev->cur_sensitivity) {
> +		rc = appleals_set_config(als_dev,
> +			HID_USAGE_SENSOR_LIGHT_ILLUM |
> +			HID_USAGE_SENSOR_DATA_MOD_CHANGE_SENSITIVITY_ABS,
> +			new_sens);
> +		if (!rc)
> +			als_dev->cur_sensitivity = new_sens;
> +	}
> +}
> +
> +static void appleals_push_new_value(struct appleals_device *als_dev,
> +				    __s32 value)
> +{
> +	__s32 buf[2] = { value, value };
> +
> +	iio_push_to_buffers(als_dev->iio_dev, buf);
> +
> +	if (als_dev->cur_hysteresis == APPLEALS_DYN_SENS)
> +		appleals_update_dyn_sensitivity(als_dev, value);
> +}
> +
> +static int appleals_hid_event(struct hid_device *hdev, struct hid_field *field,
> +			      struct hid_usage *usage, __s32 value)
> +{
> +	struct appleals_device *als_dev = hid_get_drvdata(hdev);
> +	int rc = 0;
> +
> +	if ((usage->hid & HID_USAGE_PAGE) != HID_UP_SENSOR)
> +		return 0;
> +
> +	if (usage->hid == HID_USAGE_SENSOR_LIGHT_ILLUM) {
> +		appleals_push_new_value(als_dev, value);
> +		rc = 1;
Direct return here would be more readable, then return 0 below.
> +	}
> +
> +	return rc;
> +}
> +
> +static int appleals_enable_events(struct iio_trigger *trig, bool enable)
> +{
> +	struct appleals_device *als_dev = iio_trigger_get_drvdata(trig);
> +	int value;
> +
> +	appleals_set_enum_config(als_dev, HID_USAGE_SENSOR_PROP_REPORT_STATE,
> +		enable ? HID_USAGE_SENSOR_PROP_REPORTING_STATE_ALL_EVENTS_ENUM :
> +			 HID_USAGE_SENSOR_PROP_REPORTING_STATE_NO_EVENTS_ENUM);
> +	als_dev->events_enabled = enable;
> +
> +	/* if the sensor was enabled, push an initial value */
> +	if (enable) {
> +		value = appleals_get_field_value(als_dev, als_dev->illum_field);
> +		appleals_push_new_value(als_dev, value);
> +	}
> +
> +	return 0;
> +}
> +
> +static int appleals_read_raw(struct iio_dev *iio_dev,
> +			     struct iio_chan_spec const *chan,
> +			     int *val, int *val2, long mask)
> +{
> +	struct appleals_device **priv = iio_priv(iio_dev);
> +	struct appleals_device *als_dev = *priv;
> +	__s32 value;
> +	int rc;
> +
> +	switch (mask) {
> +	case IIO_CHAN_INFO_PROCESSED:
> +		*val = appleals_get_field_value(als_dev, als_dev->illum_field);
> +		return IIO_VAL_INT;
> +
> +	case IIO_CHAN_INFO_SAMP_FREQ:
> +		rc = appleals_get_config(als_dev,
> +					 HID_USAGE_SENSOR_PROP_REPORT_INTERVAL,
> +					 &value);
> +		if (rc)
> +			return rc;
> +
> +		/* interval is in ms; val is in HZ, val2 in µHZ */
> +		value = 1000000000 / value;
> +		*val = value / 1000000;
> +		*val2 = value - (*val * 1000000);
> +
> +		return IIO_VAL_INT_PLUS_MICRO;
> +
> +	case IIO_CHAN_INFO_HYSTERESIS:
> +		if (als_dev->cur_hysteresis == APPLEALS_DYN_SENS) {
> +			*val = als_dev->cur_hysteresis;
> +			return IIO_VAL_INT;
> +		}
> +
> +		rc = appleals_get_config(als_dev,
> +			HID_USAGE_SENSOR_LIGHT_ILLUM |
> +			HID_USAGE_SENSOR_DATA_MOD_CHANGE_SENSITIVITY_ABS,
> +			val);
> +		if (!rc) {
> +			als_dev->cur_sensitivity = *val;
> +			als_dev->cur_hysteresis = *val;
> +		}
> +		return rc ? rc : IIO_VAL_INT;
> +
> +	default:
> +		return -EINVAL;
> +	}
> +}
> +
> +static int appleals_write_raw(struct iio_dev *iio_dev,
> +			      struct iio_chan_spec const *chan,
> +			      int val, int val2, long mask)
> +{
> +	struct appleals_device **priv = iio_priv(iio_dev);
> +	struct appleals_device *als_dev = *priv;
> +	__s32 illum;
> +	int rc;
> +
> +	switch (mask) {
> +	case IIO_CHAN_INFO_SAMP_FREQ:
> +		rc = appleals_set_config(als_dev,
> +					 HID_USAGE_SENSOR_PROP_REPORT_INTERVAL,
> +					 1000000000 / (val * 1000000 + val2));
> +		return rc;
> +
> +	case IIO_CHAN_INFO_HYSTERESIS:
> +		if (val == APPLEALS_DYN_SENS) {

Hysteresis normally takes a value, this looks like a magic number being
pushed through the interface?

> +			if (als_dev->cur_hysteresis != APPLEALS_DYN_SENS) {
> +				als_dev->cur_hysteresis = val;
> +				illum = appleals_get_field_value(als_dev,
> +							als_dev->illum_field);
> +				appleals_update_dyn_sensitivity(als_dev, illum);
> +			}
> +
> +			return 0;
> +		}
> +
> +		rc = appleals_set_config(als_dev,
> +			HID_USAGE_SENSOR_LIGHT_ILLUM |
> +			HID_USAGE_SENSOR_DATA_MOD_CHANGE_SENSITIVITY_ABS,
> +			val);
> +		if (!rc) {
> +			als_dev->cur_sensitivity = val;
> +			als_dev->cur_hysteresis = val;
> +		}
> +
> +		return rc;
> +
> +	default:
> +		return -EINVAL;
> +	}
> +}
> +
> +static const struct iio_chan_spec appleals_channels[] = {
> +	{
> +		.type = IIO_INTENSITY,
> +		.modified = 1,
> +		.channel2 = IIO_MOD_LIGHT_BOTH,
> +		.info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED),

This would be unusual.  What are the units of this intensity measurement?
Mostly these are the values prior to a proprietary algorithm and have no
particular assigned units that are documented anywhere.  As such for
most light sensors this is raw.

> +		.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SAMP_FREQ) |
> +			BIT(IIO_CHAN_INFO_HYSTERESIS),
> +		.scan_type = {
> +			.sign = 'u',
> +			.realbits = 32,
> +			.storagebits = 32,
> +		},
> +		.scan_index = 0,
> +	},
> +	{
> +		.type = IIO_LIGHT,
> +		.info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED),
> +		.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SAMP_FREQ) |
> +			BIT(IIO_CHAN_INFO_HYSTERESIS),
> +		.scan_type = {
> +			.sign = 'u',
> +			.realbits = 32,
> +			.storagebits = 32,
> +		},
> +		.scan_index = 1,
> +	}
> +};
> +
> +static const struct iio_trigger_ops appleals_trigger_ops = {
> +	.set_trigger_state = &appleals_enable_events,
> +};
> +
> +static const struct iio_info appleals_info = {
> +	.read_raw = &appleals_read_raw,
> +	.write_raw = &appleals_write_raw,
> +};
> +
> +static void appleals_config_sensor(struct appleals_device *als_dev,
> +				   bool events_enabled, int sensitivity)
> +{
> +	struct hid_field *field;
> +	bool powered_on;
> +	__s32 val;
> +
> +	powered_on = !hid_hw_power(als_dev->hid_dev, PM_HINT_FULLON);
> +
> +	hid_hw_request(als_dev->hid_dev, als_dev->cfg_report,
> +		       HID_REQ_GET_REPORT);
> +	hid_hw_wait(als_dev->hid_dev);
> +
> +	field = appleib_find_report_field(als_dev->cfg_report,
> +					  HID_USAGE_SENSOR_PROY_POWER_STATE);
> +	val = appleals_get_field_value_for_usage(field,
> +			HID_USAGE_SENSOR_PROP_POWER_STATE_D0_FULL_POWER_ENUM);
> +	if (val >= 0)
> +		hid_set_field(field, 0, val);
> +
> +	field = appleib_find_report_field(als_dev->cfg_report,
> +					  HID_USAGE_SENSOR_PROP_REPORT_STATE);
> +	val = appleals_get_field_value_for_usage(field,
> +		events_enabled ?
> +			HID_USAGE_SENSOR_PROP_REPORTING_STATE_ALL_EVENTS_ENUM :
> +			HID_USAGE_SENSOR_PROP_REPORTING_STATE_NO_EVENTS_ENUM);
> +	if (val >= 0)
> +		hid_set_field(field, 0, val);
> +
> +	field = appleib_find_report_field(als_dev->cfg_report,
> +					 HID_USAGE_SENSOR_PROP_REPORT_INTERVAL);
> +	hid_set_field(field, 0, field->logical_minimum);
> +
> +	/*
> +	 * Set initial change sensitivity; if dynamic, enabling trigger will set
> +	 * it instead.
> +	 */
> +	if (sensitivity != APPLEALS_DYN_SENS) {
> +		field = appleib_find_report_field(als_dev->cfg_report,
> +			HID_USAGE_SENSOR_LIGHT_ILLUM |
> +			HID_USAGE_SENSOR_DATA_MOD_CHANGE_SENSITIVITY_ABS);
> +
> +		hid_set_field(field, 0, sensitivity);
> +	}
> +
> +	hid_hw_request(als_dev->hid_dev, als_dev->cfg_report,
> +		       HID_REQ_SET_REPORT);
> +
> +	if (powered_on)
> +		hid_hw_power(als_dev->hid_dev, PM_HINT_NORMAL);
> +}
> +
> +static int appleals_config_iio(struct appleals_device *als_dev)
> +{
> +	struct iio_dev *iio_dev;
> +	struct iio_trigger *iio_trig;
> +	struct appleals_device **priv;
> +	struct device *parent = &als_dev->hid_dev->dev;
> +	int rc;
> +
> +	iio_dev = devm_iio_device_alloc(parent, sizeof(als_dev));
> +	if (!iio_dev)
> +		return -ENOMEM;

Hmm. So we are using the private space of the iio device to just
hold a pointer...
Normally we try to avoid this if at all possible as we end
up with loops of pointers that normally indicate a someone
convoluted driver structure.  I've not checked all the paths
here to be sure if we can just embed the actual als_dev
structure in here and always have the iio_dev available in
any callbacks etc.

> +
> +	priv = iio_priv(iio_dev);
> +	*priv = als_dev;
> +
> +	iio_dev->channels = appleals_channels;
> +	iio_dev->num_channels = ARRAY_SIZE(appleals_channels);
> +	iio_dev->dev.parent = parent;
> +	iio_dev->info = &appleals_info;
> +	iio_dev->name = "als";
Whilst I suppose not many people will have additional ALS devices connected
to these laptops, we normally try to give a slightly more specific name
than this.

> +	iio_dev->modes = INDIO_DIRECT_MODE;
> +
> +	rc = devm_iio_triggered_buffer_setup(parent, iio_dev,
> +					     &iio_pollfunc_store_time, NULL,
This is unusual.  You have registered all the infrastructure for a triggered
buffer, but then didn't actually provide a function to put any data in it.

It is perfectly acceptable to just skip the trigger if it doesn't make sense
for a given device. If you are doing that, then register the buffer directly
not using this helper.

> +					     NULL);
> +	if (rc) {
> +		hid_err(als_dev->hid_dev,
> +			"Failed to set up iio triggered buffer: %d\n", rc);
> +		return rc;
> +	}
> +
> +	iio_trig = devm_iio_trigger_alloc(parent, "%s-dev%d", iio_dev->name,
> +					  iio_dev->id);
> +	if (!iio_trig)
> +		return -ENOMEM;
> +
> +	iio_trig->dev.parent = parent;
> +	iio_trig->ops = &appleals_trigger_ops;
> +	iio_trigger_set_drvdata(iio_trig, als_dev);
> +
> +	rc = devm_iio_trigger_register(parent, iio_trig);
> +	if (rc) {
> +		hid_err(als_dev->hid_dev,
> +			"Failed to register iio trigger: %d\n",
> +			rc);
> +		return rc;
> +	}

What is the purpose of this trigger? It doesn't seem to 'do' anything. There
is no function calling iio_trigger_poll* so it's not acting as a trigger.

> +
> +	rc = devm_iio_device_register(parent, iio_dev);
> +	if (rc) {
> +		hid_err(als_dev->hid_dev, "Failed to register iio device: %d\n",
> +			rc);
> +		return rc;
> +	}
> +
> +	als_dev->iio_dev = iio_dev;
> +
> +	return 0;
> +}
> +
> +static int appleals_probe(struct hid_device *hdev,
> +			  const struct hid_device_id *id)
> +{
> +	struct appleals_device *als_dev;
> +	struct hid_field *state_field;
> +	struct hid_field *illum_field;
> +	int rc;
> +
> +	/* find als fields and reports */
> +	rc = hid_parse(hdev);
> +	if (rc) {
> +		hid_err(hdev, "als: hid parse failed (%d)\n", rc);
> +		return rc;
> +	}
> +
> +	state_field = appleib_find_hid_field(hdev, HID_USAGE_SENSOR_ALS,
> +					    HID_USAGE_SENSOR_PROP_REPORT_STATE);
> +	illum_field = appleib_find_hid_field(hdev, HID_USAGE_SENSOR_ALS,
> +					     HID_USAGE_SENSOR_LIGHT_ILLUM);
> +	if (!state_field || !illum_field)
> +		return -ENODEV;
> +
> +	hid_dbg(hdev, "Found ambient light sensor\n");
> +
> +	/* initialize device */
> +	als_dev = devm_kzalloc(&hdev->dev, sizeof(*als_dev), GFP_KERNEL);
> +	if (!als_dev)
> +		return -ENOMEM;
> +
> +	als_dev->hid_dev = hdev;
> +	als_dev->cfg_report = state_field->report;
> +	als_dev->illum_field = illum_field;
> +
> +	als_dev->cur_hysteresis = APPLEALS_DEF_CHANGE_SENS;
> +	als_dev->cur_sensitivity = APPLEALS_DEF_CHANGE_SENS;
> +
> +	hid_set_drvdata(hdev, als_dev);
> +
> +	rc = hid_hw_start(hdev, HID_CONNECT_DRIVER);
> +	if (rc) {
> +		hid_err(hdev, "als: hw start failed (%d)\n", rc);
> +		return rc;
> +	}
> +
> +	hid_device_io_start(hdev);
> +	appleals_config_sensor(als_dev, false, als_dev->cur_sensitivity);
> +	hid_device_io_stop(hdev);
> +
> +	rc = appleals_config_iio(als_dev);
> +	if (rc)
> +		return rc;
> +
> +	return hid_hw_open(hdev);
> +}
> +
> +#ifdef CONFIG_PM
> +static int appleals_reset_resume(struct hid_device *hdev)
> +{
> +	struct appleals_device *als_dev = hid_get_drvdata(hdev);
> +	__s32 illum;
> +
> +	appleals_config_sensor(als_dev, als_dev->events_enabled,
> +			       als_dev->cur_sensitivity);
> +
> +	illum = appleals_get_field_value(als_dev, als_dev->illum_field);
> +	appleals_push_new_value(als_dev, illum);
> +
> +	return 0;
> +}
> +#endif
> +
> +static const struct hid_device_id appleals_hid_ids[] = {
> +	{ HID_USB_DEVICE(USB_VENDOR_ID_LINUX_FOUNDATION,
> +			 USB_DEVICE_ID_IBRIDGE_ALS) },
> +	{ },
> +};
> +
> +MODULE_DEVICE_TABLE(hid, appleals_hid_ids);
> +
> +static struct hid_driver appleals_hid_driver = {
> +	.name = "apple-ib-als",
> +	.id_table = appleals_hid_ids,
> +	.probe = appleals_probe,
> +	.event = appleals_hid_event,
> +#ifdef CONFIG_PM
> +	.reset_resume = appleals_reset_resume,
> +#endif
> +};
> +
> +module_hid_driver(appleals_hid_driver);
> +
> +MODULE_AUTHOR("Ronald Tschalär");
> +MODULE_DESCRIPTION("Apple iBridge ALS driver");
> +MODULE_LICENSE("GPL v2");


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

end of thread, other threads:[~2019-06-16 14:52 UTC | newest]

Thread overview: 7+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2019-06-12  8:33 [PATCH v2 0/3] Apple iBridge support Ronald Tschalär
2019-06-12  8:33 ` [PATCH v2 1/3] HID: apple-ibridge: Add Apple iBridge HID driver Ronald Tschalär
2019-06-16 14:01   ` Jonathan Cameron
2019-06-12  8:33 ` [PATCH v2 2/3] HID: apple-ib-tb: Add driver for the Touch Bar on MacBook Pro's Ronald Tschalär
2019-06-16 14:28   ` Jonathan Cameron
2019-06-12  8:34 ` [PATCH v2 3/3] iio: light: apple-ib-als: Add driver for ALS on iBridge chip Ronald Tschalär
2019-06-16 14:52   ` Jonathan Cameron

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).