linux-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH 0/3] Apple iBridge support
@ 2019-04-22  3:12 Ronald Tschalär
  2019-04-22  3:12 ` [PATCH 1/3] mfd: apple-ibridge: Add Apple iBridge MFD driver Ronald Tschalär
                   ` (2 more replies)
  0 siblings, 3 replies; 22+ messages in thread
From: Ronald Tschalär @ 2019-04-22  3:12 UTC (permalink / raw)
  To: Jiri Kosina, Benjamin Tissoires, Jonathan Cameron,
	Hartmut Knaack, Lars-Peter Clausen, Peter Meerwald-Stadler,
	Lee Jones
  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
necessitated creating a demux hid driver in the mfd driver to which
multiple hid devices can be attached, and implied not being able to make
use of the existing hid-sensor-als driver.

Ronald Tschalär (3):
  mfd: apple-ibridge: Add Apple iBridge MFD 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               |   10 +
 drivers/hid/Makefile              |    1 +
 drivers/hid/apple-ib-tb.c         | 1288 +++++++++++++++++++++++++++++
 drivers/iio/light/Kconfig         |   12 +
 drivers/iio/light/Makefile        |    1 +
 drivers/iio/light/apple-ib-als.c  |  694 ++++++++++++++++
 drivers/mfd/Kconfig               |   15 +
 drivers/mfd/Makefile              |    1 +
 drivers/mfd/apple-ibridge.c       |  883 ++++++++++++++++++++
 include/linux/mfd/apple-ibridge.h |   39 +
 10 files changed, 2944 insertions(+)
 create mode 100644 drivers/hid/apple-ib-tb.c
 create mode 100644 drivers/iio/light/apple-ib-als.c
 create mode 100644 drivers/mfd/apple-ibridge.c
 create mode 100644 include/linux/mfd/apple-ibridge.h

-- 
2.20.1


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

* [PATCH 1/3] mfd: apple-ibridge: Add Apple iBridge MFD driver.
  2019-04-22  3:12 [PATCH 0/3] Apple iBridge support Ronald Tschalär
@ 2019-04-22  3:12 ` Ronald Tschalär
  2019-04-22 11:34   ` Jonathan Cameron
                     ` (2 more replies)
  2019-04-22  3:12 ` [PATCH 2/3] HID: apple-ib-tb: Add driver for the Touch Bar on MacBook Pro's Ronald Tschalär
  2019-04-22  3:12 ` [PATCH 3/3] iio: light: apple-ib-als: Add driver for ALS on iBridge chip Ronald Tschalär
  2 siblings, 3 replies; 22+ messages in thread
From: Ronald Tschalär @ 2019-04-22  3:12 UTC (permalink / raw)
  To: Jiri Kosina, Benjamin Tissoires, Jonathan Cameron,
	Hartmut Knaack, Lars-Peter Clausen, Peter Meerwald-Stadler,
	Lee Jones
  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 provides a multiplexing layer that allows multiple HID drivers to
be registered for a given HID device. This allows the touch bar and ALS
driver to be separated out into their own modules.

Signed-off-by: Ronald Tschalär <ronald@innovation.ch>
---
 drivers/mfd/Kconfig               |  15 +
 drivers/mfd/Makefile              |   1 +
 drivers/mfd/apple-ibridge.c       | 883 ++++++++++++++++++++++++++++++
 include/linux/mfd/apple-ibridge.h |  39 ++
 4 files changed, 938 insertions(+)
 create mode 100644 drivers/mfd/apple-ibridge.c
 create mode 100644 include/linux/mfd/apple-ibridge.h

diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index 76f9909cf396..d55fa77faacf 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -1916,5 +1916,20 @@ config RAVE_SP_CORE
 	  Select this to get support for the Supervisory Processor
 	  device found on several devices in RAVE line of hardware.
 
+config MFD_APPLE_IBRIDGE
+	tristate "Apple iBridge chip"
+	depends on ACPI
+	depends on USB_HID
+	depends on X86 || COMPILE_TEST
+	select MFD_CORE
+	help
+	  This MFD provides the core support for the Apple iBridge chip
+	  found on recent MacBookPro's. 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.
+
 endmenu
 endif
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index 12980a4ad460..c364e0e9d313 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -241,4 +241,5 @@ obj-$(CONFIG_MFD_MXS_LRADC)     += mxs-lradc.o
 obj-$(CONFIG_MFD_SC27XX_PMIC)	+= sprd-sc27xx-spi.o
 obj-$(CONFIG_RAVE_SP_CORE)	+= rave-sp.o
 obj-$(CONFIG_MFD_ROHM_BD718XX)	+= rohm-bd718x7.o
+obj-$(CONFIG_MFD_APPLE_IBRIDGE)	+= apple-ibridge.o
 
diff --git a/drivers/mfd/apple-ibridge.c b/drivers/mfd/apple-ibridge.c
new file mode 100644
index 000000000000..56d325396961
--- /dev/null
+++ b/drivers/mfd/apple-ibridge.c
@@ -0,0 +1,883 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Apple iBridge Driver
+ *
+ * Copyright (c) 2018 Ronald Tschalär
+ */
+
+/**
+ * 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 (and possibly the SEP,
+ * though at this point in time nothing is known about that). 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 an MFD driver, with the touch bar and ALS
+ * functions implemented by appropriate subdrivers (mfd cells). Because
+ * both those are basically hid drivers, but the current kernel driver
+ * structure does not allow more than one driver per device, this driver
+ * implements a demuxer for hid drivers: it registers itself as a hid
+ * driver with the core, and in turn it lets the subdrivers register
+ * themselves as hid drivers with this driver; the callbacks from the core
+ * are then forwarded to the subdrivers.
+ *
+ * Lastly, this driver also takes care of the power-management for the
+ * iBridge when suspending and resuming.
+ */
+
+#include <linux/acpi.h>
+#include <linux/device.h>
+#include <linux/hid.h>
+#include <linux/list.h>
+#include <linux/mfd/apple-ibridge.h>
+#include <linux/mfd/core.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/rculist.h>
+#include <linux/slab.h>
+#include <linux/srcu.h>
+#include <linux/usb.h>
+
+#include "../hid/usbhid/usbhid.h"
+
+#define USB_ID_VENDOR_APPLE	0x05ac
+#define USB_ID_PRODUCT_IBRIDGE	0x8600
+
+#define APPLETB_BASIC_CONFIG	1
+
+#define	LOG_DEV(ib_dev)		(&(ib_dev)->acpi_dev->dev)
+
+struct appleib_device {
+	struct acpi_device	*acpi_dev;
+	acpi_handle		asoc_socw;
+	struct list_head	hid_drivers;
+	struct list_head	hid_devices;
+	struct mfd_cell		*subdevs;
+	struct mutex		update_lock; /* protect updates to all lists */
+	struct srcu_struct	lists_srcu;
+	bool			in_hid_probe;
+};
+
+struct appleib_hid_drv_info {
+	struct list_head	entry;
+	struct hid_driver	*driver;
+	void			*driver_data;
+};
+
+struct appleib_hid_dev_info {
+	struct list_head		entry;
+	struct list_head		drivers;
+	struct hid_device		*device;
+	const struct hid_device_id	*device_id;
+	bool				started;
+};
+
+static const struct mfd_cell appleib_subdevs[] = {
+	{ .name = PLAT_NAME_IB_TB },
+	{ .name = PLAT_NAME_IB_ALS },
+};
+
+static struct appleib_device *appleib_dev;
+
+#define	call_void_driver_func(drv_info, fn, ...)			\
+	do {								\
+		if ((drv_info)->driver->fn)				\
+			(drv_info)->driver->fn(__VA_ARGS__);		\
+	} while (0)
+
+#define	call_driver_func(drv_info, fn, ret_type, ...)			\
+	({								\
+		ret_type rc = 0;					\
+									\
+		if ((drv_info)->driver->fn)				\
+			rc = (drv_info)->driver->fn(__VA_ARGS__);	\
+									\
+		rc;							\
+	})
+
+static void appleib_remove_driver(struct appleib_device *ib_dev,
+				  struct appleib_hid_drv_info *drv_info,
+				  struct appleib_hid_dev_info *dev_info)
+{
+	list_del_rcu(&drv_info->entry);
+	synchronize_srcu(&ib_dev->lists_srcu);
+
+	call_void_driver_func(drv_info, remove, dev_info->device);
+
+	kfree(drv_info);
+}
+
+static int appleib_probe_driver(struct appleib_hid_drv_info *drv_info,
+				struct appleib_hid_dev_info *dev_info)
+{
+	struct appleib_hid_drv_info *d;
+	int rc = 0;
+
+	rc = call_driver_func(drv_info, probe, int, dev_info->device,
+			      dev_info->device_id);
+	if (rc)
+		return rc;
+
+	d = kmemdup(drv_info, sizeof(*drv_info), GFP_KERNEL);
+	if (!d) {
+		call_void_driver_func(drv_info, remove, dev_info->device);
+		return -ENOMEM;
+	}
+
+	list_add_tail_rcu(&d->entry, &dev_info->drivers);
+	return 0;
+}
+
+static void appleib_remove_driver_attachments(struct appleib_device *ib_dev,
+					struct appleib_hid_dev_info *dev_info,
+					struct hid_driver *driver)
+{
+	struct appleib_hid_drv_info *drv_info;
+	struct appleib_hid_drv_info *tmp;
+
+	list_for_each_entry_safe(drv_info, tmp, &dev_info->drivers, entry) {
+		if (!driver || drv_info->driver == driver)
+			appleib_remove_driver(ib_dev, drv_info, dev_info);
+	}
+}
+
+/*
+ * Find all devices that are attached to this driver and detach them.
+ *
+ * Note: this must be run with update_lock held.
+ */
+static void appleib_detach_devices(struct appleib_device *ib_dev,
+				   struct hid_driver *driver)
+{
+	struct appleib_hid_dev_info *dev_info;
+
+	list_for_each_entry(dev_info, &ib_dev->hid_devices, entry)
+		appleib_remove_driver_attachments(ib_dev, dev_info, driver);
+}
+
+/*
+ * Find all drivers that are attached to this device and detach them.
+ *
+ * Note: this must be run with update_lock held.
+ */
+static void appleib_detach_drivers(struct appleib_device *ib_dev,
+				   struct appleib_hid_dev_info *dev_info)
+{
+	appleib_remove_driver_attachments(ib_dev, dev_info, NULL);
+}
+
+/**
+ * Unregister a previously registered HID driver from us.
+ * @ib_dev: the appleib_device from which to unregister the driver
+ * @driver: the driver to unregister
+ */
+int appleib_unregister_hid_driver(struct appleib_device *ib_dev,
+				  struct hid_driver *driver)
+{
+	struct appleib_hid_drv_info *drv_info;
+
+	mutex_lock(&ib_dev->update_lock);
+
+	list_for_each_entry(drv_info, &ib_dev->hid_drivers, entry) {
+		if (drv_info->driver == driver) {
+			appleib_detach_devices(ib_dev, driver);
+			list_del_rcu(&drv_info->entry);
+			mutex_unlock(&ib_dev->update_lock);
+			synchronize_srcu(&ib_dev->lists_srcu);
+			kfree(drv_info);
+			dev_info(LOG_DEV(ib_dev), "unregistered driver '%s'\n",
+				 driver->name);
+			return 0;
+		}
+	}
+
+	mutex_unlock(&ib_dev->update_lock);
+
+	return -ENOENT;
+}
+EXPORT_SYMBOL_GPL(appleib_unregister_hid_driver);
+
+static int appleib_start_hid_events(struct appleib_hid_dev_info *dev_info)
+{
+	struct hid_device *hdev = dev_info->device;
+	int rc;
+
+	rc = hid_connect(hdev, HID_CONNECT_DEFAULT);
+	if (rc) {
+		hid_err(hdev, "ib: hid connect failed (%d)\n", rc);
+		return rc;
+	}
+
+	rc = hid_hw_open(hdev);
+	if (rc) {
+		hid_err(hdev, "ib: failed to open hid: %d\n", rc);
+		hid_disconnect(hdev);
+	}
+
+	if (!rc)
+		dev_info->started = true;
+
+	return rc;
+}
+
+static void appleib_stop_hid_events(struct appleib_hid_dev_info *dev_info)
+{
+	if (dev_info->started) {
+		hid_hw_close(dev_info->device);
+		hid_disconnect(dev_info->device);
+		dev_info->started = false;
+	}
+}
+
+/**
+ * Register a HID driver with us.
+ * @ib_dev: the appleib_device with which to register the driver
+ * @driver: the driver to register
+ * @data: the driver-data to associate with the driver; this is available
+ *        from appleib_get_drvdata(...).
+ */
+int appleib_register_hid_driver(struct appleib_device *ib_dev,
+				struct hid_driver *driver, void *data)
+{
+	struct appleib_hid_drv_info *drv_info;
+	struct appleib_hid_dev_info *dev_info;
+	int rc;
+
+	if (!driver->probe)
+		return -EINVAL;
+
+	drv_info = kzalloc(sizeof(*drv_info), GFP_KERNEL);
+	if (!drv_info)
+		return -ENOMEM;
+
+	drv_info->driver = driver;
+	drv_info->driver_data = data;
+
+	mutex_lock(&ib_dev->update_lock);
+
+	list_add_tail_rcu(&drv_info->entry, &ib_dev->hid_drivers);
+
+	list_for_each_entry(dev_info, &ib_dev->hid_devices, entry) {
+		appleib_stop_hid_events(dev_info);
+
+		appleib_probe_driver(drv_info, dev_info);
+
+		rc = appleib_start_hid_events(dev_info);
+		if (rc)
+			appleib_detach_drivers(ib_dev, dev_info);
+	}
+
+	mutex_unlock(&ib_dev->update_lock);
+
+	dev_info(LOG_DEV(ib_dev), "registered driver '%s'\n", driver->name);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(appleib_register_hid_driver);
+
+/**
+ * Get the driver-specific data associated with the given, previously
+ * registered HID driver (provided in the appleib_register_hid_driver()
+ * call).
+ * @ib_dev: the appleib_device with which the driver was registered
+ * @driver: the driver for which to get the data
+ */
+void *appleib_get_drvdata(struct appleib_device *ib_dev,
+			  struct hid_driver *driver)
+{
+	struct appleib_hid_drv_info *drv_info;
+	void *drv_data = NULL;
+	int idx;
+
+	idx = srcu_read_lock(&ib_dev->lists_srcu);
+
+	list_for_each_entry_rcu(drv_info, &ib_dev->hid_drivers, entry) {
+		if (drv_info->driver == driver) {
+			drv_data = drv_info->driver_data;
+			break;
+		}
+	}
+
+	srcu_read_unlock(&ib_dev->lists_srcu, idx);
+
+	return drv_data;
+}
+EXPORT_SYMBOL_GPL(appleib_get_drvdata);
+
+/*
+ * Forward a hid-driver callback to all registered sub-drivers. This is for
+ * callbacks that return a status as an int.
+ * @hdev the hid-device
+ * @forward a function that calls the callback on the given driver
+ * @args arguments for the forward function
+ */
+static int appleib_forward_int_op(struct hid_device *hdev,
+				  int (*forward)(struct appleib_hid_drv_info *,
+						 struct hid_device *, void *),
+				  void *args)
+{
+	struct appleib_device *ib_dev = hid_get_drvdata(hdev);
+	struct appleib_hid_dev_info *dev_info;
+	struct appleib_hid_drv_info *drv_info;
+	int idx;
+	int rc = 0;
+
+	idx = srcu_read_lock(&ib_dev->lists_srcu);
+
+	list_for_each_entry_rcu(dev_info, &ib_dev->hid_devices, entry) {
+		if (dev_info->device != hdev)
+			continue;
+
+		list_for_each_entry_rcu(drv_info, &dev_info->drivers, entry) {
+			rc = forward(drv_info, hdev, args);
+			if (rc)
+				break;
+		}
+
+		break;
+	}
+
+	srcu_read_unlock(&ib_dev->lists_srcu, idx);
+
+	return rc;
+}
+
+struct appleib_hid_event_args {
+	struct hid_field *field;
+	struct hid_usage *usage;
+	__s32 value;
+};
+
+static int appleib_hid_event_fwd(struct appleib_hid_drv_info *drv_info,
+				 struct hid_device *hdev, void *args)
+{
+	struct appleib_hid_event_args *evt_args = args;
+
+	return call_driver_func(drv_info, event, int, hdev, evt_args->field,
+				evt_args->usage, evt_args->value);
+}
+
+static int appleib_hid_event(struct hid_device *hdev, struct hid_field *field,
+			     struct hid_usage *usage, __s32 value)
+{
+	struct appleib_hid_event_args args = {
+		.field = field,
+		.usage = usage,
+		.value = value,
+	};
+
+	return appleib_forward_int_op(hdev, appleib_hid_event_fwd, &args);
+}
+
+static __u8 *appleib_report_fixup(struct hid_device *hdev, __u8 *rdesc,
+				  unsigned int *rsize)
+{
+	/* Some fields have a size of 64 bits, which according to HID 1.11
+	 * Section 8.4 is not valid ("An item field cannot span more than 4
+	 * bytes in a report"). Furthermore, hid_field_extract() complains
+	 * when encountering such a field. So turn them into two 32-bit fields
+	 * instead.
+	 */
+
+	if (*rsize == 634 &&
+	    /* Usage Page 0xff12 (vendor defined) */
+	    rdesc[212] == 0x06 && rdesc[213] == 0x12 && rdesc[214] == 0xff &&
+	    /* Usage 0x51 */
+	    rdesc[416] == 0x09 && rdesc[417] == 0x51 &&
+	    /* report size 64 */
+	    rdesc[432] == 0x75 && rdesc[433] == 64 &&
+	    /* report count 1 */
+	    rdesc[434] == 0x95 && rdesc[435] == 1) {
+		rdesc[433] = 32;
+		rdesc[435] = 2;
+		hid_dbg(hdev, "Fixed up first 64-bit field\n");
+	}
+
+	if (*rsize == 634 &&
+	    /* Usage Page 0xff12 (vendor defined) */
+	    rdesc[212] == 0x06 && rdesc[213] == 0x12 && rdesc[214] == 0xff &&
+	    /* Usage 0x51 */
+	    rdesc[611] == 0x09 && rdesc[612] == 0x51 &&
+	    /* report size 64 */
+	    rdesc[627] == 0x75 && rdesc[628] == 64 &&
+	    /* report count 1 */
+	    rdesc[629] == 0x95 && rdesc[630] == 1) {
+		rdesc[628] = 32;
+		rdesc[630] = 2;
+		hid_dbg(hdev, "Fixed up second 64-bit field\n");
+	}
+
+	return rdesc;
+}
+
+static int appleib_input_configured_fwd(struct appleib_hid_drv_info *drv_info,
+					struct hid_device *hdev, void *args)
+{
+	return call_driver_func(drv_info, input_configured, int, hdev,
+				(struct hid_input *)args);
+}
+
+static int appleib_input_configured(struct hid_device *hdev,
+				    struct hid_input *hidinput)
+{
+	return appleib_forward_int_op(hdev, appleib_input_configured_fwd,
+				      hidinput);
+}
+
+#ifdef CONFIG_PM
+static int appleib_hid_suspend_fwd(struct appleib_hid_drv_info *drv_info,
+				   struct hid_device *hdev, void *args)
+{
+	return call_driver_func(drv_info, suspend, int, hdev,
+				*(pm_message_t *)args);
+}
+
+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 appleib_hid_drv_info *drv_info,
+				  struct hid_device *hdev, void *args)
+{
+	return call_driver_func(drv_info, resume, int, hdev);
+}
+
+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 appleib_hid_drv_info *drv_info,
+					struct hid_device *hdev, void *args)
+{
+	return call_driver_func(drv_info, reset_resume, int, hdev);
+}
+
+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 */
+
+/**
+ * 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
+ */
+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);
+
+/**
+ * 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
+ */
+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);
+
+/**
+ * Return whether we're currently inside a hid_device_probe or not.
+ * @ib_dev: the appleib_device
+ */
+bool appleib_in_hid_probe(struct appleib_device *ib_dev)
+{
+	return ib_dev->in_hid_probe;
+}
+EXPORT_SYMBOL_GPL(appleib_in_hid_probe);
+
+static struct appleib_hid_dev_info *
+appleib_add_device(struct appleib_device *ib_dev, struct hid_device *hdev,
+		   const struct hid_device_id *id)
+{
+	struct appleib_hid_dev_info *dev_info;
+	struct appleib_hid_drv_info *drv_info;
+
+	/* allocate device-info for this device */
+	dev_info = kzalloc(sizeof(*dev_info), GFP_KERNEL);
+	if (!dev_info)
+		return NULL;
+
+	INIT_LIST_HEAD(&dev_info->drivers);
+	dev_info->device = hdev;
+	dev_info->device_id = id;
+
+	/* notify all our sub drivers */
+	mutex_lock(&ib_dev->update_lock);
+
+	ib_dev->in_hid_probe = true;
+
+	list_for_each_entry(drv_info, &ib_dev->hid_drivers, entry) {
+		appleib_probe_driver(drv_info, dev_info);
+	}
+
+	ib_dev->in_hid_probe = false;
+
+	/* remember this new device */
+	list_add_tail_rcu(&dev_info->entry, &ib_dev->hid_devices);
+
+	mutex_unlock(&ib_dev->update_lock);
+
+	return dev_info;
+}
+
+static void appleib_remove_device(struct appleib_device *ib_dev,
+				  struct appleib_hid_dev_info *dev_info)
+{
+	list_del_rcu(&dev_info->entry);
+	synchronize_srcu(&ib_dev->lists_srcu);
+
+	appleib_detach_drivers(ib_dev, dev_info);
+
+	kfree(dev_info);
+}
+
+static int appleib_hid_probe(struct hid_device *hdev,
+			     const struct hid_device_id *id)
+{
+	struct appleib_device *ib_dev;
+	struct appleib_hid_dev_info *dev_info;
+	struct usb_device *udev;
+	int rc;
+
+	/* check usb config first */
+	udev = hid_to_usb_dev(hdev);
+
+	if (udev->actconfig->desc.bConfigurationValue != APPLETB_BASIC_CONFIG) {
+		rc = usb_driver_set_configuration(udev, APPLETB_BASIC_CONFIG);
+		return rc ? rc : -ENODEV;
+	}
+
+	/* Assign the driver data */
+	ib_dev = appleib_dev;
+	hid_set_drvdata(hdev, ib_dev);
+
+	/* initialize the report info */
+	rc = hid_parse(hdev);
+	if (rc) {
+		hid_err(hdev, "ib: hid parse failed (%d)\n", rc);
+		goto error;
+	}
+
+	/* alloc bufs etc so probe's can send requests; but connect later */
+	rc = hid_hw_start(hdev, 0);
+	if (rc) {
+		hid_err(hdev, "ib: hw start failed (%d)\n", rc);
+		goto error;
+	}
+
+	/* add this hdev to our device list */
+	dev_info = appleib_add_device(ib_dev, hdev, id);
+	if (!dev_info) {
+		rc = -ENOMEM;
+		goto stop_hw;
+	}
+
+	/* start the hid */
+	rc = appleib_start_hid_events(dev_info);
+	if (rc)
+		goto remove_dev;
+
+	return 0;
+
+remove_dev:
+	mutex_lock(&ib_dev->update_lock);
+	appleib_remove_device(ib_dev, dev_info);
+	mutex_unlock(&ib_dev->update_lock);
+stop_hw:
+	hid_hw_stop(hdev);
+error:
+	return rc;
+}
+
+static void appleib_hid_remove(struct hid_device *hdev)
+{
+	struct appleib_device *ib_dev = hid_get_drvdata(hdev);
+	struct appleib_hid_dev_info *dev_info;
+
+	mutex_lock(&ib_dev->update_lock);
+
+	list_for_each_entry(dev_info, &ib_dev->hid_devices, entry) {
+		if (dev_info->device == hdev) {
+			appleib_stop_hid_events(dev_info);
+			appleib_remove_device(ib_dev, dev_info);
+			break;
+		}
+	}
+
+	mutex_unlock(&ib_dev->update_lock);
+
+	hid_hw_stop(hdev);
+}
+
+static const struct hid_device_id appleib_hid_devices[] = {
+	{ HID_USB_DEVICE(USB_ID_VENDOR_APPLE, USB_ID_PRODUCT_IBRIDGE) },
+	{ },
+};
+
+static struct hid_driver appleib_hid_driver = {
+	.name = "apple-ibridge-hid",
+	.id_table = appleib_hid_devices,
+	.probe = appleib_hid_probe,
+	.remove = appleib_hid_remove,
+	.event = appleib_hid_event,
+	.report_fixup = appleib_report_fixup,
+	.input_configured = appleib_input_configured,
+#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;
+	int rc;
+
+	/* allocate */
+	ib_dev = kzalloc(sizeof(*ib_dev), GFP_KERNEL);
+	if (!ib_dev)
+		return ERR_PTR(-ENOMEM);
+
+	/* init structures */
+	INIT_LIST_HEAD(&ib_dev->hid_drivers);
+	INIT_LIST_HEAD(&ib_dev->hid_devices);
+	mutex_init(&ib_dev->update_lock);
+	init_srcu_struct(&ib_dev->lists_srcu);
+
+	ib_dev->acpi_dev = acpi_dev;
+
+	/* get iBridge acpi power control method */
+	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));
+		rc = -ENXIO;
+		goto free_mem;
+	}
+
+	/* 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;
+
+free_mem:
+	kfree(ib_dev);
+	return ERR_PTR(rc);
+}
+
+static int appleib_probe(struct acpi_device *acpi)
+{
+	struct appleib_device *ib_dev;
+	struct appleib_platform_data *pdata;
+	int i;
+	int ret;
+
+	if (appleib_dev)
+		return -EBUSY;
+
+	ib_dev = appleib_alloc_device(acpi);
+	if (IS_ERR_OR_NULL(ib_dev))
+		return PTR_ERR(ib_dev);
+
+	ib_dev->subdevs = kmemdup(appleib_subdevs, sizeof(appleib_subdevs),
+				  GFP_KERNEL);
+	if (!ib_dev->subdevs) {
+		ret = -ENOMEM;
+		goto free_dev;
+	}
+
+	pdata = kzalloc(sizeof(*pdata), GFP_KERNEL);
+	if (!pdata) {
+		ret = -ENOMEM;
+		goto free_subdevs;
+	}
+
+	pdata->ib_dev = ib_dev;
+	pdata->log_dev = LOG_DEV(ib_dev);
+	for (i = 0; i < ARRAY_SIZE(appleib_subdevs); i++) {
+		ib_dev->subdevs[i].platform_data = pdata;
+		ib_dev->subdevs[i].pdata_size = sizeof(*pdata);
+	}
+
+	ret = mfd_add_devices(&acpi->dev, PLATFORM_DEVID_NONE,
+			      ib_dev->subdevs, ARRAY_SIZE(appleib_subdevs),
+			      NULL, 0, NULL);
+	if (ret) {
+		dev_err(LOG_DEV(ib_dev), "Error adding MFD devices: %d\n", ret);
+		goto free_pdata;
+	}
+
+	acpi->driver_data = ib_dev;
+	appleib_dev = ib_dev;
+
+	ret = hid_register_driver(&appleib_hid_driver);
+	if (ret) {
+		dev_err(LOG_DEV(ib_dev), "Error registering hid driver: %d\n",
+			ret);
+		goto rem_mfd_devs;
+	}
+
+	return 0;
+
+rem_mfd_devs:
+	mfd_remove_devices(&acpi->dev);
+free_pdata:
+	kfree(pdata);
+free_subdevs:
+	kfree(ib_dev->subdevs);
+free_dev:
+	appleib_dev = NULL;
+	acpi->driver_data = NULL;
+	kfree(ib_dev);
+	return ret;
+}
+
+static int appleib_remove(struct acpi_device *acpi)
+{
+	struct appleib_device *ib_dev = acpi_driver_data(acpi);
+
+	mfd_remove_devices(&acpi->dev);
+	hid_unregister_driver(&appleib_hid_driver);
+
+	if (appleib_dev == ib_dev)
+		appleib_dev = NULL;
+
+	kfree(ib_dev->subdevs[0].platform_data);
+	kfree(ib_dev->subdevs);
+	kfree(ib_dev);
+
+	return 0;
+}
+
+static int appleib_suspend(struct device *dev)
+{
+	struct acpi_device *adev;
+	struct appleib_device *ib_dev;
+	int rc;
+
+	adev = to_acpi_device(dev);
+	ib_dev = acpi_driver_data(adev);
+
+	rc = acpi_execute_simple_method(ib_dev->asoc_socw, NULL, 0);
+	if (ACPI_FAILURE(rc))
+		dev_warn(LOG_DEV(ib_dev), "SOCW(0) failed: %s\n",
+			 acpi_format_exception(rc));
+
+	return 0;
+}
+
+static int appleib_resume(struct device *dev)
+{
+	struct acpi_device *adev;
+	struct appleib_device *ib_dev;
+	int rc;
+
+	adev = to_acpi_device(dev);
+	ib_dev = acpi_driver_data(adev);
+
+	rc = acpi_execute_simple_method(ib_dev->asoc_socw, NULL, 1);
+	if (ACPI_FAILURE(rc))
+		dev_warn(LOG_DEV(ib_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/include/linux/mfd/apple-ibridge.h b/include/linux/mfd/apple-ibridge.h
new file mode 100644
index 000000000000..d321714767f7
--- /dev/null
+++ b/include/linux/mfd/apple-ibridge.h
@@ -0,0 +1,39 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Apple iBridge Driver
+ *
+ * Copyright (c) 2018 Ronald Tschalär
+ */
+
+#ifndef __LINUX_MFD_APPLE_IBRDIGE_H
+#define __LINUX_MFD_APPLE_IBRDIGE_H
+
+#include <linux/device.h>
+#include <linux/hid.h>
+
+#define PLAT_NAME_IB_TB		"apple-ib-tb"
+#define PLAT_NAME_IB_ALS	"apple-ib-als"
+
+struct appleib_device;
+
+struct appleib_platform_data {
+	struct appleib_device *ib_dev;
+	struct device *log_dev;
+};
+
+int appleib_register_hid_driver(struct appleib_device *ib_dev,
+				struct hid_driver *driver, void *data);
+int appleib_unregister_hid_driver(struct appleib_device *ib_dev,
+				  struct hid_driver *driver);
+
+void *appleib_get_drvdata(struct appleib_device *ib_dev,
+			  struct hid_driver *driver);
+bool appleib_in_hid_probe(struct appleib_device *ib_dev);
+
+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.20.1


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

* [PATCH 2/3] HID: apple-ib-tb: Add driver for the Touch Bar on MacBook Pro's.
  2019-04-22  3:12 [PATCH 0/3] Apple iBridge support Ronald Tschalär
  2019-04-22  3:12 ` [PATCH 1/3] mfd: apple-ibridge: Add Apple iBridge MFD driver Ronald Tschalär
@ 2019-04-22  3:12 ` Ronald Tschalär
  2019-04-22  3:12 ` [PATCH 3/3] iio: light: apple-ib-als: Add driver for ALS on iBridge chip Ronald Tschalär
  2 siblings, 0 replies; 22+ messages in thread
From: Ronald Tschalär @ 2019-04-22  3:12 UTC (permalink / raw)
  To: Jiri Kosina, Benjamin Tissoires, Jonathan Cameron,
	Hartmut Knaack, Lars-Peter Clausen, Peter Meerwald-Stadler,
	Lee Jones
  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 | 1288 +++++++++++++++++++++++++++++++++++++
 3 files changed, 1299 insertions(+)
 create mode 100644 drivers/hid/apple-ib-tb.c

diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
index 41e9935fc584..f0a65bb4be6e 100644
--- a/drivers/hid/Kconfig
+++ b/drivers/hid/Kconfig
@@ -135,6 +135,16 @@ 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_TB
+	tristate "Apple iBridge Touch Bar"
+	depends on MFD_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 896a51ce7ce0..dedd8049d3fb 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_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..6b72ff56b17f
--- /dev/null
+++ b/drivers/hid/apple-ib-tb.c
@@ -0,0 +1,1288 @@
+// 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/device.h>
+#include <linux/hid.h>
+#include <linux/input.h>
+#include <linux/jiffies.h>
+#include <linux/ktime.h>
+#include <linux/mfd/apple-ibridge.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/spinlock.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 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_NONE	255
+
+#define APPLETB_CMD_DISP_ON	1
+#define APPLETB_CMD_DISP_DIM	2
+#define APPLETB_CMD_DISP_OFF	4
+#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 (in seconds); 0 disables touch bar, -1 disables timeout");
+
+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 (in seconds); 0 means always dimmmed, -1 disables dimming, [-2] calculates 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: 0 = f-keys only, [1] = fn key switches from special to f-keys, 2 = inverse of 1, 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 appletb_report_info {
+		struct hid_device	*hdev;
+		struct usb_interface	*usb_iface;
+		unsigned int		usb_epnum;
+		unsigned int		report_id;
+		unsigned int		report_type;
+		bool			suspended;
+	}			mode_info, disp_info;
+
+	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 hid_driver appletb_hid_driver;
+
+static int appletb_send_hid_report(struct appletb_report_info *rinfo,
+				   __u8 requesttype, void *data, __u16 size)
+{
+	void *buffer;
+	struct usb_device *dev = interface_to_usbdev(rinfo->usb_iface);
+	u8 ifnum = rinfo->usb_iface->cur_altsetting->desc.bInterfaceNumber;
+	int tries = 0;
+	int rc;
+
+	buffer = kmemdup(data, size, GFP_KERNEL);
+	if (!buffer)
+		return -ENOMEM;
+
+	do {
+		rc = usb_control_msg(dev,
+				     usb_sndctrlpipe(dev, rinfo->usb_epnum),
+				     HID_REQ_SET_REPORT, requesttype,
+				     rinfo->report_type << 8 | rinfo->report_id,
+				     ifnum, buffer, size, 2000);
+		if (rc != -EPIPE)
+			break;
+
+		usleep_range(1000 << tries, 3000 << tries);
+	} while (++tries < 5);
+
+	kfree(buffer);
+
+	return (rc > 0) ? 0 : rc;
+}
+
+static bool appletb_disable_autopm(struct appletb_report_info *rinfo)
+{
+	int rc;
+
+	rc = usb_autopm_get_interface(rinfo->usb_iface);
+	if (rc == 0)
+		return true;
+
+	hid_err(rinfo->hdev,
+		"Failed to disable auto-pm on touch bar device (%d)\n", rc);
+	return false;
+}
+
+static int appletb_set_tb_mode(struct appletb_device *tb_dev,
+			       unsigned char mode)
+{
+	int rc;
+	bool autopm_off = false;
+
+	if (!tb_dev->mode_info.usb_iface)
+		return -ENOTCONN;
+
+	autopm_off = appletb_disable_autopm(&tb_dev->mode_info);
+
+	rc = appletb_send_hid_report(&tb_dev->mode_info,
+				     USB_DIR_OUT | USB_TYPE_VENDOR |
+							USB_RECIP_DEVICE,
+				     &mode, 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)
+		usb_autopm_put_interface(tb_dev->mode_info.usb_iface);
+
+	return rc;
+}
+
+static int appletb_set_tb_disp(struct appletb_device *tb_dev,
+			       unsigned char disp)
+{
+	unsigned char report[] = { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
+	int rc;
+
+	if (!tb_dev->disp_info.usb_iface)
+		return -ENOTCONN;
+
+	/*
+	 * Keep the USB interface powered on while the touch bar display is on
+	 * for better responsiveness.
+	 */
+	if (disp != APPLETB_CMD_DISP_OFF &&
+	    tb_dev->cur_tb_disp == APPLETB_CMD_DISP_OFF)
+		tb_dev->tb_autopm_off =
+			appletb_disable_autopm(&tb_dev->disp_info);
+
+	report[0] = tb_dev->disp_info.report_id;
+	report[2] = disp;
+
+	rc = appletb_send_hid_report(&tb_dev->disp_info,
+				     USB_DIR_OUT | USB_TYPE_CLASS |
+						USB_RECIP_INTERFACE,
+				     report, sizeof(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->cur_tb_disp != APPLETB_CMD_DISP_OFF) {
+		if (tb_dev->tb_autopm_off) {
+			usb_autopm_put_interface(tb_dev->disp_info.usb_iface);
+			tb_dev->tb_autopm_off = false;
+		}
+	}
+
+	return rc;
+}
+
+static bool appletb_any_tb_key_pressed(struct appletb_device *tb_dev)
+{
+	int idx;
+
+	for (idx = 0; idx < ARRAY_SIZE(tb_dev->last_tb_keys_pressed); idx++) {
+		if (tb_dev->last_tb_keys_pressed[idx])
+			return true;
+	}
+
+	return false;
+}
+
+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, 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_info);
+
+	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 <= 0 && tb_dev->dim_timeout <= 0)
+		min_timeout = -1;
+	else if (tb_dev->dim_timeout <= 0)
+		min_timeout = tb_dev->idle_timeout;
+	else if (tb_dev->idle_timeout <= 0)
+		min_timeout = tb_dev->dim_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;
+	}
+
+	any_tb_key_pressed = appletb_any_tb_key_pressed(tb_dev);
+
+	spin_unlock_irqrestore(&tb_dev->tb_lock, flags);
+
+	/* 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:
+	 *     -1  always on
+	 *      0  always off
+	 *     >0  turn off after idle_timeout seconds
+	 *   dim_timeout (only valid if idle_timeout != 0):
+	 *     -1  never dimmed
+	 *      0  always dimmed
+	 *     >0  dim off after dim_timeout seconds
+	 */
+	if (tb_dev->idle_timeout == 0) {
+		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->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) ||
+	     (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->dim_timeout = new - min(APPLETB_MAX_DIM_TIME, new / 3);
+}
+
+static ssize_t idle_timeout_show(struct device *dev,
+				 struct device_attribute *attr, char *buf)
+{
+	struct appletb_device *tb_dev =
+		appleib_get_drvdata(dev_get_drvdata(dev), &appletb_hid_driver);
+
+	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 =
+		appleib_get_drvdata(dev_get_drvdata(dev), &appletb_hid_driver);
+	long new;
+	int rc;
+
+	rc = kstrtol(buf, 0, &new);
+	if (rc || new > INT_MAX || new < -1)
+		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 =
+		appleib_get_drvdata(dev_get_drvdata(dev), &appletb_hid_driver);
+
+	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 =
+		appleib_get_drvdata(dev_get_drvdata(dev), &appletb_hid_driver);
+	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 =
+		appleib_get_drvdata(dev_get_drvdata(dev), &appletb_hid_driver);
+
+	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 =
+		appleib_get_drvdata(dev_get_drvdata(dev), &appletb_hid_driver);
+	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 =
+		appleib_get_drvdata(hid_get_drvdata(hdev), &appletb_hid_driver);
+	unsigned int new_code = 0;
+	unsigned long flags;
+	bool send_dummy = false;
+	bool send_trnsl = false;
+	int slot;
+	int rc = 0;
+
+	/* Only interested in keyboard events */
+	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);
+
+	/* remember which (untranslated) touch bar keys are pressed */
+	if (value != 2)
+		tb_dev->last_tb_keys_pressed[slot] = value;
+
+	/* remember last time keyboard or touchpad was touched */
+	tb_dev->last_event_time = ktime_get();
+
+	/* only switch touch bar mode when no touch bar keys are pressed */
+	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.
+	 */
+	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;
+	}
+
+	/* remember last state of FN key */
+	if (type == EV_KEY && code == KEY_FN && value != 2)
+		tb_dev->last_fn_pressed = value;
+
+	/* remember last time keyboard or touchpad was touched */
+	tb_dev->last_event_time = ktime_get();
+
+	/* only switch touch bar mode when no touch bar keys are pressed */
+	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;
+
+	/* find the usb-interface device */
+	if (!dev->bus || strcmp(dev->bus->name, "hid") != 0)
+		return NULL;
+
+	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_fill_report_info(struct appletb_device *tb_dev,
+				    struct hid_device *hdev)
+{
+	struct appletb_report_info *report_info = NULL;
+	struct usb_interface *usb_iface;
+	struct hid_field *field;
+
+	field = appleib_find_hid_field(hdev, HID_GD_KEYBOARD, HID_USAGE_MODE);
+	if (field) {
+		report_info = &tb_dev->mode_info;
+	} else {
+		field = appleib_find_hid_field(hdev, HID_USAGE_APPLE_APP,
+					       HID_USAGE_DISP);
+		if (field)
+			report_info = &tb_dev->disp_info;
+	}
+
+	if (!report_info)
+		return 0;
+
+	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;
+	}
+
+	report_info->hdev = hdev;
+
+	report_info->usb_iface = usb_get_intf(usb_iface);
+	report_info->usb_epnum = 0;
+
+	report_info->report_id = field->report->id;
+	switch (field->report->type) {
+	case HID_INPUT_REPORT:
+		report_info->report_type = 0x01; break;
+	case HID_OUTPUT_REPORT:
+		report_info->report_type = 0x02; break;
+	case HID_FEATURE_REPORT:
+		report_info->report_type = 0x03; break;
+	}
+
+	return 1;
+}
+
+static struct appletb_report_info *
+appletb_get_report_info(struct appletb_device *tb_dev, struct hid_device *hdev)
+{
+	if (hdev == tb_dev->mode_info.hdev)
+		return &tb_dev->mode_info;
+	if (hdev == tb_dev->disp_info.hdev)
+		return &tb_dev->disp_info;
+	return NULL;
+}
+
+static void appletb_mark_active(struct appletb_device *tb_dev, bool active)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&tb_dev->tb_lock, flags);
+	tb_dev->active = active;
+	spin_unlock_irqrestore(&tb_dev->tb_lock, flags);
+}
+
+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 =
+		appleib_get_drvdata(hid_get_drvdata(hdev), &appletb_hid_driver);
+	struct appletb_report_info *report_info;
+	int rc;
+
+	/* initialize the report info */
+	rc = appletb_fill_report_info(tb_dev, hdev);
+	if (rc < 0)
+		goto error;
+
+	/* do setup if we have both interfaces */
+	if (tb_dev->mode_info.hdev && tb_dev->disp_info.hdev) {
+		/* mark active */
+		appletb_mark_active(tb_dev, true);
+
+		/* 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->cur_tb_mode = APPLETB_CMD_MODE_OFF;
+		tb_dev->cur_tb_disp = APPLETB_CMD_DISP_OFF;
+
+		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_info.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_info(tb_dev->log_dev, "Touchbar activated\n");
+	}
+
+	return 0;
+
+unreg_handler:
+	input_unregister_handler(&tb_dev->inp_handler);
+mark_inactive:
+	appletb_mark_active(tb_dev, false);
+	cancel_delayed_work_sync(&tb_dev->tb_work);
+
+	report_info = appletb_get_report_info(tb_dev, hdev);
+	if (report_info) {
+		usb_put_intf(report_info->usb_iface);
+		report_info->usb_iface = NULL;
+		report_info->hdev = NULL;
+	}
+error:
+	return rc;
+}
+
+static void appletb_remove(struct hid_device *hdev)
+{
+	struct appletb_device *tb_dev =
+		appleib_get_drvdata(hid_get_drvdata(hdev), &appletb_hid_driver);
+	struct appletb_report_info *report_info;
+
+	if ((hdev == tb_dev->mode_info.hdev && tb_dev->disp_info.hdev) ||
+	    (hdev == tb_dev->disp_info.hdev && tb_dev->mode_info.hdev)) {
+		sysfs_remove_group(&tb_dev->mode_info.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)
+			usb_autopm_put_interface(tb_dev->disp_info.usb_iface);
+
+		appletb_mark_active(tb_dev, false);
+
+		dev_info(tb_dev->log_dev, "Touchbar deactivated\n");
+	}
+
+	report_info = appletb_get_report_info(tb_dev, hdev);
+	if (report_info) {
+		usb_put_intf(report_info->usb_iface);
+		report_info->usb_iface = NULL;
+		report_info->hdev = NULL;
+	}
+}
+
+#ifdef CONFIG_PM
+static int appletb_suspend(struct hid_device *hdev, pm_message_t message)
+{
+	struct appletb_device *tb_dev =
+		appleib_get_drvdata(hid_get_drvdata(hdev), &appletb_hid_driver);
+	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_info.suspended && !tb_dev->disp_info.suspended) {
+		tb_dev->active = false;
+		cancel_delayed_work(&tb_dev->tb_work);
+	}
+
+	appletb_get_report_info(tb_dev, hdev)->suspended = true;
+
+	if ((!tb_dev->mode_info.hdev || tb_dev->mode_info.suspended) &&
+	    (!tb_dev->disp_info.hdev || tb_dev->disp_info.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 =
+		appleib_get_drvdata(hid_get_drvdata(hdev), &appletb_hid_driver);
+	unsigned long flags;
+
+	/*
+	 * Restore touch bar state. Note that autopm state is preserved, no need
+	 * explicitly restore that here.
+	 */
+	spin_lock_irqsave(&tb_dev->tb_lock, flags);
+
+	appletb_get_report_info(tb_dev, hdev)->suspended = false;
+
+	if ((tb_dev->mode_info.hdev && !tb_dev->mode_info.suspended) &&
+	    (tb_dev->disp_info.hdev && !tb_dev->disp_info.suspended)) {
+		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(struct device *log_dev)
+{
+	struct appletb_device *tb_dev;
+
+	/* allocate */
+	tb_dev = kzalloc(sizeof(*tb_dev), GFP_KERNEL);
+	if (!tb_dev)
+		return NULL;
+
+	/* initialize structures */
+	spin_lock_init(&tb_dev->tb_lock);
+	INIT_DELAYED_WORK(&tb_dev->tb_work, appletb_set_tb_worker);
+	tb_dev->log_dev = log_dev;
+
+	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 struct hid_driver appletb_hid_driver = {
+	.name = "apple-ib-touchbar",
+	.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 appletb_platform_probe(struct platform_device *pdev)
+{
+	struct appleib_platform_data *pdata = pdev->dev.platform_data;
+	struct appleib_device *ib_dev = pdata->ib_dev;
+	struct appletb_device *tb_dev;
+	int rc;
+
+	tb_dev = appletb_alloc_device(pdata->log_dev);
+	if (!tb_dev)
+		return -ENOMEM;
+
+	rc = appleib_register_hid_driver(ib_dev, &appletb_hid_driver, tb_dev);
+	if (rc) {
+		dev_err(tb_dev->log_dev, "Error registering hid driver: %d\n",
+			rc);
+		goto error;
+	}
+
+	platform_set_drvdata(pdev, tb_dev);
+
+	return 0;
+
+error:
+	appletb_free_device(tb_dev);
+	return rc;
+}
+
+static int appletb_platform_remove(struct platform_device *pdev)
+{
+	struct appleib_platform_data *pdata = pdev->dev.platform_data;
+	struct appleib_device *ib_dev = pdata->ib_dev;
+	struct appletb_device *tb_dev = platform_get_drvdata(pdev);
+	int rc;
+
+	rc = appleib_unregister_hid_driver(ib_dev, &appletb_hid_driver);
+	if (rc) {
+		dev_err(tb_dev->log_dev, "Error unregistering hid driver: %d\n",
+			rc);
+		goto error;
+	}
+
+	appletb_free_device(tb_dev);
+
+	return 0;
+
+error:
+	return rc;
+}
+
+static const struct platform_device_id appletb_platform_ids[] = {
+	{ .name = PLAT_NAME_IB_TB },
+	{ }
+};
+MODULE_DEVICE_TABLE(platform, appletb_platform_ids);
+
+static struct platform_driver appletb_platform_driver = {
+	.id_table = appletb_platform_ids,
+	.driver = {
+		.name	= "apple-ib-tb",
+	},
+	.probe = appletb_platform_probe,
+	.remove = appletb_platform_remove,
+};
+
+module_platform_driver(appletb_platform_driver);
+
+MODULE_AUTHOR("Ronald Tschalär");
+MODULE_DESCRIPTION("MacBookPro Touch Bar driver");
+MODULE_LICENSE("GPL v2");
-- 
2.20.1



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

* [PATCH 3/3] iio: light: apple-ib-als: Add driver for ALS on iBridge chip.
  2019-04-22  3:12 [PATCH 0/3] Apple iBridge support Ronald Tschalär
  2019-04-22  3:12 ` [PATCH 1/3] mfd: apple-ibridge: Add Apple iBridge MFD driver Ronald Tschalär
  2019-04-22  3:12 ` [PATCH 2/3] HID: apple-ib-tb: Add driver for the Touch Bar on MacBook Pro's Ronald Tschalär
@ 2019-04-22  3:12 ` Ronald Tschalär
  2019-04-22  9:17   ` Peter Meerwald-Stadler
  2 siblings, 1 reply; 22+ messages in thread
From: Ronald Tschalär @ 2019-04-22  3:12 UTC (permalink / raw)
  To: Jiri Kosina, Benjamin Tissoires, Jonathan Cameron,
	Hartmut Knaack, Lars-Peter Clausen, Peter Meerwald-Stadler,
	Lee Jones
  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 | 694 +++++++++++++++++++++++++++++++
 3 files changed, 707 insertions(+)
 create mode 100644 drivers/iio/light/apple-ib-als.c

diff --git a/drivers/iio/light/Kconfig b/drivers/iio/light/Kconfig
index 36f458433480..49159fab1c0e 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 MFD_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 286bf3975372..144d918917f7 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..1718fcbe304f
--- /dev/null
+++ b/drivers/iio/light/apple-ib-als.c
@@ -0,0 +1,694 @@
+// 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/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/mfd/apple-ibridge.h>
+#include <linux/module.h>
+#include <linux/platform_device.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 appleib_device	*ib_dev;
+	struct device		*log_dev;
+	struct hid_device	*hid_dev;
+	struct hid_report	*cfg_report;
+	struct hid_field	*illum_field;
+	struct iio_dev		*iio_dev;
+	struct iio_trigger	*iio_trig;
+	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 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)
+{
+	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)
+{
+	hid_hw_request(als_dev->hid_dev, field->report, HID_REQ_GET_REPORT);
+	hid_hw_wait(als_dev->hid_dev);
+
+	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);
+
+	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 =
+		appleib_get_drvdata(hid_get_drvdata(hdev),
+				    &appleals_hid_driver);
+	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;
+
+	/* set the sensor's reporting state */
+	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 *als_dev =
+				*(struct appleals_device **)iio_priv(iio_dev);
+	__s32 value;
+	int rc;
+
+	*val = 0;
+	*val2 = 0;
+
+	switch (mask) {
+	case IIO_CHAN_INFO_RAW:
+	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 *als_dev =
+				*(struct appleals_device **)iio_priv(iio_dev);
+	__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));
+		break;
+
+	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);
+			}
+			rc = 0;
+			break;
+		}
+
+		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;
+		}
+		break;
+
+	default:
+		rc = -EINVAL;
+	}
+
+	return rc;
+}
+
+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) |
+			BIT(IIO_CHAN_INFO_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) |
+			BIT(IIO_CHAN_INFO_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 = 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;
+	__s32 val;
+
+	/*
+	 * We're (often) in a probe here, so need to enable input processing
+	 * in that case, but only in that case.
+	 */
+	if (appleib_in_hid_probe(als_dev->ib_dev))
+		hid_device_io_start(als_dev->hid_dev);
+
+	/* power on the sensor */
+	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);
+	hid_set_field(field, 0, val);
+
+	/* configure reporting of change events */
+	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);
+	hid_set_field(field, 0, val);
+
+	/* report change events asap */
+	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);
+	}
+
+	/* write the new config to the sensor */
+	hid_hw_request(als_dev->hid_dev, als_dev->cfg_report,
+		       HID_REQ_SET_REPORT);
+
+	if (appleib_in_hid_probe(als_dev->ib_dev))
+		hid_device_io_stop(als_dev->hid_dev);
+};
+
+static int appleals_config_iio(struct appleals_device *als_dev)
+{
+	struct iio_dev *iio_dev;
+	struct iio_trigger *iio_trig;
+	int rc;
+
+	/* create and register iio device */
+	iio_dev = iio_device_alloc(sizeof(als_dev));
+	if (!iio_dev)
+		return -ENOMEM;
+
+	*(struct appleals_device **)iio_priv(iio_dev) = als_dev;
+
+	iio_dev->channels = appleals_channels;
+	iio_dev->num_channels = ARRAY_SIZE(appleals_channels);
+	iio_dev->dev.parent = &als_dev->hid_dev->dev;
+	iio_dev->info = &appleals_info;
+	iio_dev->name = "als";
+	iio_dev->modes = INDIO_DIRECT_MODE;
+
+	rc = iio_triggered_buffer_setup(iio_dev, &iio_pollfunc_store_time, NULL,
+					NULL);
+	if (rc) {
+		dev_err(als_dev->log_dev, "failed to set up iio triggers: %d\n",
+			rc);
+		goto free_iio_dev;
+	}
+
+	iio_trig = iio_trigger_alloc("%s-dev%d", iio_dev->name, iio_dev->id);
+	if (!iio_trig) {
+		rc = -ENOMEM;
+		goto clean_trig_buf;
+	}
+
+	iio_trig->dev.parent = &als_dev->hid_dev->dev;
+	iio_trig->ops = &appleals_trigger_ops;
+	iio_trigger_set_drvdata(iio_trig, als_dev);
+
+	rc = iio_trigger_register(iio_trig);
+	if (rc) {
+		dev_err(als_dev->log_dev, "failed to register iio trigger: %d\n",
+			rc);
+		goto free_iio_trig;
+	}
+
+	als_dev->iio_trig = iio_trig;
+
+	rc = iio_device_register(iio_dev);
+	if (rc) {
+		dev_err(als_dev->log_dev, "failed to register iio device: %d\n",
+			rc);
+		goto unreg_iio_trig;
+	}
+
+	als_dev->iio_dev = iio_dev;
+
+	return 0;
+
+unreg_iio_trig:
+	iio_trigger_unregister(iio_trig);
+free_iio_trig:
+	iio_trigger_free(iio_trig);
+	als_dev->iio_trig = NULL;
+clean_trig_buf:
+	iio_triggered_buffer_cleanup(iio_dev);
+free_iio_dev:
+	iio_device_free(iio_dev);
+
+	return rc;
+}
+
+static int appleals_probe(struct hid_device *hdev,
+			  const struct hid_device_id *id)
+{
+	struct appleals_device *als_dev =
+		appleib_get_drvdata(hid_get_drvdata(hdev),
+				    &appleals_hid_driver);
+	struct hid_field *state_field;
+	struct hid_field *illum_field;
+	int rc;
+
+	/* find als fields and reports */
+	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;
+
+	if (als_dev->hid_dev) {
+		dev_warn(als_dev->log_dev,
+			 "Found duplicate ambient light sensor - ignoring\n");
+		return -EBUSY;
+	}
+
+	dev_info(als_dev->log_dev, "Found ambient light sensor\n");
+
+	/* initialize device */
+	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;
+	appleals_config_sensor(als_dev, false, als_dev->cur_sensitivity);
+
+	rc = appleals_config_iio(als_dev);
+	if (rc)
+		return rc;
+
+	return 0;
+}
+
+static void appleals_remove(struct hid_device *hdev)
+{
+	struct appleals_device *als_dev =
+		appleib_get_drvdata(hid_get_drvdata(hdev),
+				    &appleals_hid_driver);
+
+	if (als_dev->iio_dev) {
+		iio_device_unregister(als_dev->iio_dev);
+
+		iio_trigger_unregister(als_dev->iio_trig);
+		iio_trigger_free(als_dev->iio_trig);
+		als_dev->iio_trig = NULL;
+
+		iio_triggered_buffer_cleanup(als_dev->iio_dev);
+		iio_device_free(als_dev->iio_dev);
+		als_dev->iio_dev = NULL;
+	}
+
+	als_dev->hid_dev = NULL;
+}
+
+#ifdef CONFIG_PM
+static int appleals_reset_resume(struct hid_device *hdev)
+{
+	struct appleals_device *als_dev =
+		appleib_get_drvdata(hid_get_drvdata(hdev),
+				    &appleals_hid_driver);
+
+	appleals_config_sensor(als_dev, als_dev->events_enabled,
+			       als_dev->cur_sensitivity);
+
+	return 0;
+}
+#endif
+
+static struct hid_driver appleals_hid_driver = {
+	.name = "apple-ib-als",
+	.probe = appleals_probe,
+	.remove = appleals_remove,
+	.event = appleals_hid_event,
+#ifdef CONFIG_PM
+	.reset_resume = appleals_reset_resume,
+#endif
+};
+
+static int appleals_platform_probe(struct platform_device *pdev)
+{
+	struct appleib_platform_data *pdata = pdev->dev.platform_data;
+	struct appleib_device *ib_dev = pdata->ib_dev;
+	struct appleals_device *als_dev;
+	int rc;
+
+	als_dev = kzalloc(sizeof(*als_dev), GFP_KERNEL);
+	if (!als_dev)
+		return -ENOMEM;
+
+	als_dev->ib_dev = ib_dev;
+	als_dev->log_dev = pdata->log_dev;
+
+	rc = appleib_register_hid_driver(ib_dev, &appleals_hid_driver, als_dev);
+	if (rc) {
+		dev_err(als_dev->log_dev, "Error registering hid driver: %d\n",
+			rc);
+		goto error;
+	}
+
+	platform_set_drvdata(pdev, als_dev);
+
+	return 0;
+
+error:
+	kfree(als_dev);
+	return rc;
+}
+
+static int appleals_platform_remove(struct platform_device *pdev)
+{
+	struct appleib_platform_data *pdata = pdev->dev.platform_data;
+	struct appleib_device *ib_dev = pdata->ib_dev;
+	struct appleals_device *als_dev = platform_get_drvdata(pdev);
+	int rc;
+
+	rc = appleib_unregister_hid_driver(ib_dev, &appleals_hid_driver);
+	if (rc) {
+		dev_err(als_dev->log_dev,
+			"Error unregistering hid driver: %d\n", rc);
+		goto error;
+	}
+
+	kfree(als_dev);
+
+	return 0;
+
+error:
+	return rc;
+}
+
+static const struct platform_device_id appleals_platform_ids[] = {
+	{ .name = PLAT_NAME_IB_ALS },
+	{ }
+};
+MODULE_DEVICE_TABLE(platform, appleals_platform_ids);
+
+static struct platform_driver appleals_platform_driver = {
+	.id_table = appleals_platform_ids,
+	.driver = {
+		.name	= "apple-ib-als",
+	},
+	.probe = appleals_platform_probe,
+	.remove = appleals_platform_remove,
+};
+
+module_platform_driver(appleals_platform_driver);
+
+MODULE_AUTHOR("Ronald Tschalär");
+MODULE_DESCRIPTION("Apple iBridge ALS driver");
+MODULE_LICENSE("GPL v2");
-- 
2.20.1


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

* Re: [PATCH 3/3] iio: light: apple-ib-als: Add driver for ALS on iBridge chip.
  2019-04-22  3:12 ` [PATCH 3/3] iio: light: apple-ib-als: Add driver for ALS on iBridge chip Ronald Tschalär
@ 2019-04-22  9:17   ` Peter Meerwald-Stadler
  2019-04-22 12:01     ` Jonathan Cameron
  0 siblings, 1 reply; 22+ messages in thread
From: Peter Meerwald-Stadler @ 2019-04-22  9:17 UTC (permalink / raw)
  To: Ronald Tschalär
  Cc: Jiri Kosina, Benjamin Tissoires, Jonathan Cameron,
	Hartmut Knaack, Lars-Peter Clausen, Lee Jones, linux-input,
	linux-iio, linux-kernel

[-- Attachment #1: Type: text/plain, Size: 22811 bytes --]

On Sun, 21 Apr 2019, Ronald Tschalär 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.

some comments below inline
 
> 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 | 694 +++++++++++++++++++++++++++++++
>  3 files changed, 707 insertions(+)
>  create mode 100644 drivers/iio/light/apple-ib-als.c
> 
> diff --git a/drivers/iio/light/Kconfig b/drivers/iio/light/Kconfig
> index 36f458433480..49159fab1c0e 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 MFD_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 286bf3975372..144d918917f7 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..1718fcbe304f
> --- /dev/null
> +++ b/drivers/iio/light/apple-ib-als.c
> @@ -0,0 +1,694 @@
> +// 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/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/mfd/apple-ibridge.h>
> +#include <linux/module.h>
> +#include <linux/platform_device.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 appleib_device	*ib_dev;
> +	struct device		*log_dev;
> +	struct hid_device	*hid_dev;
> +	struct hid_report	*cfg_report;
> +	struct hid_field	*illum_field;
> +	struct iio_dev		*iio_dev;
> +	struct iio_trigger	*iio_trig;
> +	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 struct appleals_sensitivity_map appleals_sensitivity_map[] = {

const?

> +	{   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)
> +{
> +	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)
> +{
> +	hid_hw_request(als_dev->hid_dev, field->report, HID_REQ_GET_REPORT);
> +	hid_hw_wait(als_dev->hid_dev);
> +
> +	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);

can return -1, not checked

> +
> +	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 =
> +		appleib_get_drvdata(hid_get_drvdata(hdev),
> +				    &appleals_hid_driver);
> +	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;
> +
> +	/* set the sensor's reporting state */
> +	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 *als_dev =
> +				*(struct appleals_device **)iio_priv(iio_dev);
> +	__s32 value;
> +	int rc;
> +
> +	*val = 0;
> +	*val2 = 0;

no need to set these here

> +
> +	switch (mask) {
> +	case IIO_CHAN_INFO_RAW:
> +	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 *als_dev =
> +				*(struct appleals_device **)iio_priv(iio_dev);
> +	__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));
> +		break;

maybe return directly instead of at the end (matter of taste);
here and in the other cases below

> +
> +	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);
> +			}
> +			rc = 0;
> +			break;
> +		}
> +
> +		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;
> +		}
> +		break;
> +
> +	default:
> +		rc = -EINVAL;
> +	}
> +
> +	return rc;
> +}
> +
> +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) |
> +			BIT(IIO_CHAN_INFO_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) |
> +			BIT(IIO_CHAN_INFO_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 = 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;
> +	__s32 val;
> +
> +	/*
> +	 * We're (often) in a probe here, so need to enable input processing
> +	 * in that case, but only in that case.
> +	 */
> +	if (appleib_in_hid_probe(als_dev->ib_dev))
> +		hid_device_io_start(als_dev->hid_dev);
> +
> +	/* power on the sensor */
> +	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);

what if -1?

> +	hid_set_field(field, 0, val);
> +
> +	/* configure reporting of change events */
> +	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);
> +	hid_set_field(field, 0, val);
> +
> +	/* report change events asap */
> +	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);
> +	}
> +
> +	/* write the new config to the sensor */
> +	hid_hw_request(als_dev->hid_dev, als_dev->cfg_report,
> +		       HID_REQ_SET_REPORT);
> +
> +	if (appleib_in_hid_probe(als_dev->ib_dev))
> +		hid_device_io_stop(als_dev->hid_dev);
> +};

no semicolon at the end of a function please

> +
> +static int appleals_config_iio(struct appleals_device *als_dev)
> +{
> +	struct iio_dev *iio_dev;
> +	struct iio_trigger *iio_trig;
> +	int rc;
> +
> +	/* create and register iio device */
> +	iio_dev = iio_device_alloc(sizeof(als_dev));

how about using the devm_ variants?

> +	if (!iio_dev)
> +		return -ENOMEM;
> +
> +	*(struct appleals_device **)iio_priv(iio_dev) = als_dev;
> +
> +	iio_dev->channels = appleals_channels;
> +	iio_dev->num_channels = ARRAY_SIZE(appleals_channels);
> +	iio_dev->dev.parent = &als_dev->hid_dev->dev;
> +	iio_dev->info = &appleals_info;
> +	iio_dev->name = "als";
> +	iio_dev->modes = INDIO_DIRECT_MODE;
> +
> +	rc = iio_triggered_buffer_setup(iio_dev, &iio_pollfunc_store_time, NULL,
> +					NULL);
> +	if (rc) {
> +		dev_err(als_dev->log_dev, "failed to set up iio triggers: %d\n",

just one trigger?

> +			rc);
> +		goto free_iio_dev;
> +	}
> +
> +	iio_trig = iio_trigger_alloc("%s-dev%d", iio_dev->name, iio_dev->id);
> +	if (!iio_trig) {
> +		rc = -ENOMEM;
> +		goto clean_trig_buf;
> +	}
> +
> +	iio_trig->dev.parent = &als_dev->hid_dev->dev;
> +	iio_trig->ops = &appleals_trigger_ops;
> +	iio_trigger_set_drvdata(iio_trig, als_dev);
> +
> +	rc = iio_trigger_register(iio_trig);
> +	if (rc) {
> +		dev_err(als_dev->log_dev, "failed to register iio trigger: %d\n",

some messages start lowercase, some uppercase (nitpicking)

> +			rc);
> +		goto free_iio_trig;
> +	}
> +
> +	als_dev->iio_trig = iio_trig;
> +
> +	rc = iio_device_register(iio_dev);
> +	if (rc) {
> +		dev_err(als_dev->log_dev, "failed to register iio device: %d\n",
> +			rc);
> +		goto unreg_iio_trig;
> +	}
> +
> +	als_dev->iio_dev = iio_dev;
> +
> +	return 0;
> +
> +unreg_iio_trig:
> +	iio_trigger_unregister(iio_trig);
> +free_iio_trig:
> +	iio_trigger_free(iio_trig);
> +	als_dev->iio_trig = NULL;
> +clean_trig_buf:
> +	iio_triggered_buffer_cleanup(iio_dev);
> +free_iio_dev:
> +	iio_device_free(iio_dev);
> +
> +	return rc;
> +}
> +
> +static int appleals_probe(struct hid_device *hdev,
> +			  const struct hid_device_id *id)
> +{
> +	struct appleals_device *als_dev =
> +		appleib_get_drvdata(hid_get_drvdata(hdev),
> +				    &appleals_hid_driver);
> +	struct hid_field *state_field;
> +	struct hid_field *illum_field;
> +	int rc;
> +
> +	/* find als fields and reports */
> +	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;
> +
> +	if (als_dev->hid_dev) {
> +		dev_warn(als_dev->log_dev,
> +			 "Found duplicate ambient light sensor - ignoring\n");
> +		return -EBUSY;
> +	}
> +
> +	dev_info(als_dev->log_dev, "Found ambient light sensor\n");

in general avoid logging for the OK case, it just clutters the log

> +
> +	/* initialize device */
> +	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;
> +	appleals_config_sensor(als_dev, false, als_dev->cur_sensitivity);
> +
> +	rc = appleals_config_iio(als_dev);
> +	if (rc)
> +		return rc;
> +
> +	return 0;
> +}
> +
> +static void appleals_remove(struct hid_device *hdev)
> +{
> +	struct appleals_device *als_dev =
> +		appleib_get_drvdata(hid_get_drvdata(hdev),
> +				    &appleals_hid_driver);
> +

could be a lot less if devm_ were used?

> +	if (als_dev->iio_dev) {
> +		iio_device_unregister(als_dev->iio_dev);
> +
> +		iio_trigger_unregister(als_dev->iio_trig);
> +		iio_trigger_free(als_dev->iio_trig);
> +		als_dev->iio_trig = NULL;
> +
> +		iio_triggered_buffer_cleanup(als_dev->iio_dev);
> +		iio_device_free(als_dev->iio_dev);
> +		als_dev->iio_dev = NULL;
> +	}
> +
> +	als_dev->hid_dev = NULL;
> +}
> +
> +#ifdef CONFIG_PM
> +static int appleals_reset_resume(struct hid_device *hdev)
> +{
> +	struct appleals_device *als_dev =
> +		appleib_get_drvdata(hid_get_drvdata(hdev),
> +				    &appleals_hid_driver);
> +
> +	appleals_config_sensor(als_dev, als_dev->events_enabled,
> +			       als_dev->cur_sensitivity);
> +
> +	return 0;
> +}
> +#endif
> +
> +static struct hid_driver appleals_hid_driver = {
> +	.name = "apple-ib-als",
> +	.probe = appleals_probe,
> +	.remove = appleals_remove,
> +	.event = appleals_hid_event,
> +#ifdef CONFIG_PM
> +	.reset_resume = appleals_reset_resume,
> +#endif
> +};
> +
> +static int appleals_platform_probe(struct platform_device *pdev)
> +{
> +	struct appleib_platform_data *pdata = pdev->dev.platform_data;
> +	struct appleib_device *ib_dev = pdata->ib_dev;
> +	struct appleals_device *als_dev;
> +	int rc;
> +
> +	als_dev = kzalloc(sizeof(*als_dev), GFP_KERNEL);
> +	if (!als_dev)
> +		return -ENOMEM;
> +
> +	als_dev->ib_dev = ib_dev;
> +	als_dev->log_dev = pdata->log_dev;
> +
> +	rc = appleib_register_hid_driver(ib_dev, &appleals_hid_driver, als_dev);
> +	if (rc) {
> +		dev_err(als_dev->log_dev, "Error registering hid driver: %d\n",
> +			rc);
> +		goto error;
> +	}
> +
> +	platform_set_drvdata(pdev, als_dev);
> +
> +	return 0;
> +
> +error:
> +	kfree(als_dev);
> +	return rc;
> +}
> +
> +static int appleals_platform_remove(struct platform_device *pdev)
> +{
> +	struct appleib_platform_data *pdata = pdev->dev.platform_data;
> +	struct appleib_device *ib_dev = pdata->ib_dev;
> +	struct appleals_device *als_dev = platform_get_drvdata(pdev);
> +	int rc;
> +
> +	rc = appleib_unregister_hid_driver(ib_dev, &appleals_hid_driver);
> +	if (rc) {
> +		dev_err(als_dev->log_dev,
> +			"Error unregistering hid driver: %d\n", rc);
> +		goto error;
> +	}
> +
> +	kfree(als_dev);
> +
> +	return 0;
> +
> +error:
> +	return rc;
> +}
> +
> +static const struct platform_device_id appleals_platform_ids[] = {
> +	{ .name = PLAT_NAME_IB_ALS },
> +	{ }
> +};
> +MODULE_DEVICE_TABLE(platform, appleals_platform_ids);
> +
> +static struct platform_driver appleals_platform_driver = {
> +	.id_table = appleals_platform_ids,
> +	.driver = {
> +		.name	= "apple-ib-als",
> +	},
> +	.probe = appleals_platform_probe,
> +	.remove = appleals_platform_remove,
> +};
> +
> +module_platform_driver(appleals_platform_driver);
> +
> +MODULE_AUTHOR("Ronald Tschalär");
> +MODULE_DESCRIPTION("Apple iBridge ALS driver");
> +MODULE_LICENSE("GPL v2");
> 

-- 

Peter Meerwald-Stadler
Mobile: +43 664 24 44 418

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

* Re: [PATCH 1/3] mfd: apple-ibridge: Add Apple iBridge MFD driver.
  2019-04-22  3:12 ` [PATCH 1/3] mfd: apple-ibridge: Add Apple iBridge MFD driver Ronald Tschalär
@ 2019-04-22 11:34   ` Jonathan Cameron
  2019-04-24 10:47     ` Life is hard, and then you die
  2019-04-24 14:18   ` Benjamin Tissoires
  2019-05-07 12:24   ` Lee Jones
  2 siblings, 1 reply; 22+ messages in thread
From: Jonathan Cameron @ 2019-04-22 11:34 UTC (permalink / raw)
  To: Ronald Tschalär
  Cc: Jiri Kosina, Benjamin Tissoires, Hartmut Knaack,
	Lars-Peter Clausen, Peter Meerwald-Stadler, Lee Jones,
	linux-input, linux-iio, linux-kernel

On Sun, 21 Apr 2019 20:12:49 -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 provides a multiplexing layer that allows multiple HID drivers to
> be registered for a given HID device. This allows the touch bar and ALS
> driver to be separated out into their own modules.
> 
> Signed-off-by: Ronald Tschalär <ronald@innovation.ch
Hi Ronald,

I've only taken a fairly superficial look at this.  A few global
things to note though.

1. Please either use kernel-doc style for function descriptions, or
   do not.  Right now you are sort of half way there.
2. There is quite a complex nest of separate structures being allocated,
   so think about whether they can be simplified.  In particular
   use of container_of macros can allow a lot of forwards and backwards
   pointers to be dropped if you embed the various structures directly.

This obviously needs hid and mfd review though as neither is my
area of expertise!

Jonathan
>
> ---
>  drivers/mfd/Kconfig               |  15 +
>  drivers/mfd/Makefile              |   1 +
>  drivers/mfd/apple-ibridge.c       | 883 ++++++++++++++++++++++++++++++
>  include/linux/mfd/apple-ibridge.h |  39 ++
>  4 files changed, 938 insertions(+)
>  create mode 100644 drivers/mfd/apple-ibridge.c
>  create mode 100644 include/linux/mfd/apple-ibridge.h
> 
> diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
> index 76f9909cf396..d55fa77faacf 100644
> --- a/drivers/mfd/Kconfig
> +++ b/drivers/mfd/Kconfig
> @@ -1916,5 +1916,20 @@ config RAVE_SP_CORE
>  	  Select this to get support for the Supervisory Processor
>  	  device found on several devices in RAVE line of hardware.
>  
> +config MFD_APPLE_IBRIDGE
> +	tristate "Apple iBridge chip"
> +	depends on ACPI
> +	depends on USB_HID
> +	depends on X86 || COMPILE_TEST
> +	select MFD_CORE
> +	help
> +	  This MFD provides the core support for the Apple iBridge chip
> +	  found on recent MacBookPro's. 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.
> +
>  endmenu
>  endif
> diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
> index 12980a4ad460..c364e0e9d313 100644
> --- a/drivers/mfd/Makefile
> +++ b/drivers/mfd/Makefile
> @@ -241,4 +241,5 @@ obj-$(CONFIG_MFD_MXS_LRADC)     += mxs-lradc.o
>  obj-$(CONFIG_MFD_SC27XX_PMIC)	+= sprd-sc27xx-spi.o
>  obj-$(CONFIG_RAVE_SP_CORE)	+= rave-sp.o
>  obj-$(CONFIG_MFD_ROHM_BD718XX)	+= rohm-bd718x7.o
> +obj-$(CONFIG_MFD_APPLE_IBRIDGE)	+= apple-ibridge.o
>  
> diff --git a/drivers/mfd/apple-ibridge.c b/drivers/mfd/apple-ibridge.c
> new file mode 100644
> index 000000000000..56d325396961
> --- /dev/null
> +++ b/drivers/mfd/apple-ibridge.c
> @@ -0,0 +1,883 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Apple iBridge Driver
> + *
> + * Copyright (c) 2018 Ronald Tschalär
> + */
> +
> +/**
> + * 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 (and possibly the SEP,
> + * though at this point in time nothing is known about that). 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 an MFD driver, with the touch bar and ALS
> + * functions implemented by appropriate subdrivers (mfd cells). Because
> + * both those are basically hid drivers, but the current kernel driver
> + * structure does not allow more than one driver per device, this driver
> + * implements a demuxer for hid drivers: it registers itself as a hid
> + * driver with the core, and in turn it lets the subdrivers register
> + * themselves as hid drivers with this driver; the callbacks from the core
> + * are then forwarded to the subdrivers.
> + *
> + * Lastly, this driver also takes care of the power-management for the
> + * iBridge when suspending and resuming.
> + */
> +
> +#include <linux/acpi.h>
> +#include <linux/device.h>
> +#include <linux/hid.h>
> +#include <linux/list.h>
> +#include <linux/mfd/apple-ibridge.h>
> +#include <linux/mfd/core.h>
> +#include <linux/module.h>
> +#include <linux/mutex.h>
> +#include <linux/rculist.h>
> +#include <linux/slab.h>
> +#include <linux/srcu.h>
> +#include <linux/usb.h>
> +
> +#include "../hid/usbhid/usbhid.h"
> +
> +#define USB_ID_VENDOR_APPLE	0x05ac
> +#define USB_ID_PRODUCT_IBRIDGE	0x8600
> +
> +#define APPLETB_BASIC_CONFIG	1
> +
> +#define	LOG_DEV(ib_dev)		(&(ib_dev)->acpi_dev->dev)
> +
> +struct appleib_device {
> +	struct acpi_device	*acpi_dev;
> +	acpi_handle		asoc_socw;
> +	struct list_head	hid_drivers;
> +	struct list_head	hid_devices;
> +	struct mfd_cell		*subdevs;
> +	struct mutex		update_lock; /* protect updates to all lists */
> +	struct srcu_struct	lists_srcu;
> +	bool			in_hid_probe;
> +};
> +
> +struct appleib_hid_drv_info {
> +	struct list_head	entry;
> +	struct hid_driver	*driver;
> +	void			*driver_data;
> +};
> +
> +struct appleib_hid_dev_info {
> +	struct list_head		entry;
> +	struct list_head		drivers;
> +	struct hid_device		*device;
> +	const struct hid_device_id	*device_id;
> +	bool				started;
> +};
> +
> +static const struct mfd_cell appleib_subdevs[] = {
> +	{ .name = PLAT_NAME_IB_TB },
> +	{ .name = PLAT_NAME_IB_ALS },
> +};
> +
> +static struct appleib_device *appleib_dev;
> +
> +#define	call_void_driver_func(drv_info, fn, ...)			\

This sort of macro may seem like a good idea because it saves a few lines
of code.  However, that comes at the cost of readability, so just
put the code inline.

> +	do {								\
> +		if ((drv_info)->driver->fn)				\
> +			(drv_info)->driver->fn(__VA_ARGS__);		\
> +	} while (0)
> +
> +#define	call_driver_func(drv_info, fn, ret_type, ...)			\
> +	({								\
> +		ret_type rc = 0;					\
> +									\
> +		if ((drv_info)->driver->fn)				\
> +			rc = (drv_info)->driver->fn(__VA_ARGS__);	\
> +									\
> +		rc;							\
> +	})
> +
> +static void appleib_remove_driver(struct appleib_device *ib_dev,
> +				  struct appleib_hid_drv_info *drv_info,
> +				  struct appleib_hid_dev_info *dev_info)
> +{
> +	list_del_rcu(&drv_info->entry);
> +	synchronize_srcu(&ib_dev->lists_srcu);
> +
> +	call_void_driver_func(drv_info, remove, dev_info->device);
> +
> +	kfree(drv_info);
> +}
> +
> +static int appleib_probe_driver(struct appleib_hid_drv_info *drv_info,
> +				struct appleib_hid_dev_info *dev_info)
> +{
> +	struct appleib_hid_drv_info *d;
> +	int rc = 0;
> +
> +	rc = call_driver_func(drv_info, probe, int, dev_info->device,
> +			      dev_info->device_id);
> +	if (rc)
> +		return rc;
> +
> +	d = kmemdup(drv_info, sizeof(*drv_info), GFP_KERNEL);
> +	if (!d) {
> +		call_void_driver_func(drv_info, remove, dev_info->device);
> +		return -ENOMEM;
> +	}
> +
> +	list_add_tail_rcu(&d->entry, &dev_info->drivers);
> +	return 0;
> +}
> +
> +static void appleib_remove_driver_attachments(struct appleib_device *ib_dev,
> +					struct appleib_hid_dev_info *dev_info,
> +					struct hid_driver *driver)
> +{
> +	struct appleib_hid_drv_info *drv_info;
> +	struct appleib_hid_drv_info *tmp;
> +
> +	list_for_each_entry_safe(drv_info, tmp, &dev_info->drivers, entry) {
> +		if (!driver || drv_info->driver == driver)
> +			appleib_remove_driver(ib_dev, drv_info, dev_info);
> +	}
> +}
> +
> +/*
> + * Find all devices that are attached to this driver and detach them.
> + *
> + * Note: this must be run with update_lock held.
> + */
> +static void appleib_detach_devices(struct appleib_device *ib_dev,
> +				   struct hid_driver *driver)
> +{
> +	struct appleib_hid_dev_info *dev_info;
> +
> +	list_for_each_entry(dev_info, &ib_dev->hid_devices, entry)
> +		appleib_remove_driver_attachments(ib_dev, dev_info, driver);
> +}
> +
> +/*
> + * Find all drivers that are attached to this device and detach them.
> + *
> + * Note: this must be run with update_lock held.
> + */
> +static void appleib_detach_drivers(struct appleib_device *ib_dev,
> +				   struct appleib_hid_dev_info *dev_info)
> +{
> +	appleib_remove_driver_attachments(ib_dev, dev_info, NULL);
> +}
> +
> +/**
> + * Unregister a previously registered HID driver from us.
> + * @ib_dev: the appleib_device from which to unregister the driver
> + * @driver: the driver to unregister
> + */
> +int appleib_unregister_hid_driver(struct appleib_device *ib_dev,
> +				  struct hid_driver *driver)
> +{
> +	struct appleib_hid_drv_info *drv_info;
> +
> +	mutex_lock(&ib_dev->update_lock);
> +
> +	list_for_each_entry(drv_info, &ib_dev->hid_drivers, entry) {
> +		if (drv_info->driver == driver) {

This block does look like it perhaps should be in helper function?
Would help with readability.

> +			appleib_detach_devices(ib_dev, driver);
> +			list_del_rcu(&drv_info->entry);
> +			mutex_unlock(&ib_dev->update_lock);
> +			synchronize_srcu(&ib_dev->lists_srcu);
> +			kfree(drv_info);
> +			dev_info(LOG_DEV(ib_dev), "unregistered driver '%s'\n",
> +				 driver->name);
> +			return 0;
> +		}
> +	}
> +
> +	mutex_unlock(&ib_dev->update_lock);
> +
> +	return -ENOENT;
> +}
> +EXPORT_SYMBOL_GPL(appleib_unregister_hid_driver);
> +
> +static int appleib_start_hid_events(struct appleib_hid_dev_info *dev_info)
> +{
> +	struct hid_device *hdev = dev_info->device;
> +	int rc;
> +
> +	rc = hid_connect(hdev, HID_CONNECT_DEFAULT);
> +	if (rc) {
> +		hid_err(hdev, "ib: hid connect failed (%d)\n", rc);
> +		return rc;
> +	}
> +
> +	rc = hid_hw_open(hdev);
> +	if (rc) {
> +		hid_err(hdev, "ib: failed to open hid: %d\n", rc);
> +		hid_disconnect(hdev);
> +	}
> +
> +	if (!rc)
> +		dev_info->started = true;
> +
> +	return rc;
> +}
> +
> +static void appleib_stop_hid_events(struct appleib_hid_dev_info *dev_info)
> +{
> +	if (dev_info->started) {
> +		hid_hw_close(dev_info->device);
> +		hid_disconnect(dev_info->device);
> +		dev_info->started = false;
> +	}
> +}
> +
> +/**
> + * Register a HID driver with us.
> + * @ib_dev: the appleib_device with which to register the driver
> + * @driver: the driver to register
> + * @data: the driver-data to associate with the driver; this is available
> + *        from appleib_get_drvdata(...).
> + */
> +int appleib_register_hid_driver(struct appleib_device *ib_dev,
> +				struct hid_driver *driver, void *data)
> +{
> +	struct appleib_hid_drv_info *drv_info;
> +	struct appleib_hid_dev_info *dev_info;
> +	int rc;
> +
> +	if (!driver->probe)
> +		return -EINVAL;
> +
> +	drv_info = kzalloc(sizeof(*drv_info), GFP_KERNEL);
> +	if (!drv_info)
> +		return -ENOMEM;
> +
> +	drv_info->driver = driver;
> +	drv_info->driver_data = data;
> +
> +	mutex_lock(&ib_dev->update_lock);
> +
> +	list_add_tail_rcu(&drv_info->entry, &ib_dev->hid_drivers);
> +
> +	list_for_each_entry(dev_info, &ib_dev->hid_devices, entry) {
> +		appleib_stop_hid_events(dev_info);
> +
> +		appleib_probe_driver(drv_info, dev_info);
> +
> +		rc = appleib_start_hid_events(dev_info);
> +		if (rc)
> +			appleib_detach_drivers(ib_dev, dev_info);
> +	}
> +
> +	mutex_unlock(&ib_dev->update_lock);
> +
> +	dev_info(LOG_DEV(ib_dev), "registered driver '%s'\n", driver->name);
> +
> +	return 0;
> +}
> +EXPORT_SYMBOL_GPL(appleib_register_hid_driver);
> +
> +/**
> + * Get the driver-specific data associated with the given, previously
> + * registered HID driver (provided in the appleib_register_hid_driver()
> + * call).
> + * @ib_dev: the appleib_device with which the driver was registered
> + * @driver: the driver for which to get the data
> + */
> +void *appleib_get_drvdata(struct appleib_device *ib_dev,
> +			  struct hid_driver *driver)
> +{
> +	struct appleib_hid_drv_info *drv_info;
> +	void *drv_data = NULL;
> +	int idx;
> +
> +	idx = srcu_read_lock(&ib_dev->lists_srcu);
> +
> +	list_for_each_entry_rcu(drv_info, &ib_dev->hid_drivers, entry) {
> +		if (drv_info->driver == driver) {
> +			drv_data = drv_info->driver_data;
> +			break;
> +		}
> +	}
> +
> +	srcu_read_unlock(&ib_dev->lists_srcu, idx);
> +
> +	return drv_data;
> +}
> +EXPORT_SYMBOL_GPL(appleib_get_drvdata);
> +
> +/*
> + * Forward a hid-driver callback to all registered sub-drivers. This is for
> + * callbacks that return a status as an int.
> + * @hdev the hid-device
> + * @forward a function that calls the callback on the given driver
> + * @args arguments for the forward function
> + */
> +static int appleib_forward_int_op(struct hid_device *hdev,
> +				  int (*forward)(struct appleib_hid_drv_info *,
> +						 struct hid_device *, void *),
> +				  void *args)
> +{
> +	struct appleib_device *ib_dev = hid_get_drvdata(hdev);
> +	struct appleib_hid_dev_info *dev_info;
> +	struct appleib_hid_drv_info *drv_info;
> +	int idx;
> +	int rc = 0;
> +
> +	idx = srcu_read_lock(&ib_dev->lists_srcu);
> +
> +	list_for_each_entry_rcu(dev_info, &ib_dev->hid_devices, entry) {
> +		if (dev_info->device != hdev)
> +			continue;
> +
> +		list_for_each_entry_rcu(drv_info, &dev_info->drivers, entry) {
> +			rc = forward(drv_info, hdev, args);
> +			if (rc)
> +				break;
> +		}
> +
> +		break;
> +	}
> +
> +	srcu_read_unlock(&ib_dev->lists_srcu, idx);
> +
> +	return rc;
> +}
> +
> +struct appleib_hid_event_args {
> +	struct hid_field *field;
> +	struct hid_usage *usage;
> +	__s32 value;
> +};
> +
> +static int appleib_hid_event_fwd(struct appleib_hid_drv_info *drv_info,
> +				 struct hid_device *hdev, void *args)
> +{
> +	struct appleib_hid_event_args *evt_args = args;
> +
> +	return call_driver_func(drv_info, event, int, hdev, evt_args->field,
> +				evt_args->usage, evt_args->value);
> +}
> +
> +static int appleib_hid_event(struct hid_device *hdev, struct hid_field *field,
> +			     struct hid_usage *usage, __s32 value)
> +{
> +	struct appleib_hid_event_args args = {
> +		.field = field,
> +		.usage = usage,
> +		.value = value,
> +	};
> +
> +	return appleib_forward_int_op(hdev, appleib_hid_event_fwd, &args);
> +}
> +
> +static __u8 *appleib_report_fixup(struct hid_device *hdev, __u8 *rdesc,
> +				  unsigned int *rsize)
> +{
> +	/* Some fields have a size of 64 bits, which according to HID 1.11
> +	 * Section 8.4 is not valid ("An item field cannot span more than 4
> +	 * bytes in a report"). Furthermore, hid_field_extract() complains
> +	 * when encountering such a field. So turn them into two 32-bit fields
> +	 * instead.
> +	 */
> +
> +	if (*rsize == 634 &&
> +	    /* Usage Page 0xff12 (vendor defined) */
> +	    rdesc[212] == 0x06 && rdesc[213] == 0x12 && rdesc[214] == 0xff &&
> +	    /* Usage 0x51 */
> +	    rdesc[416] == 0x09 && rdesc[417] == 0x51 &&
> +	    /* report size 64 */
> +	    rdesc[432] == 0x75 && rdesc[433] == 64 &&
> +	    /* report count 1 */
> +	    rdesc[434] == 0x95 && rdesc[435] == 1) {
> +		rdesc[433] = 32;
> +		rdesc[435] = 2;
> +		hid_dbg(hdev, "Fixed up first 64-bit field\n");
> +	}
> +
> +	if (*rsize == 634 &&
> +	    /* Usage Page 0xff12 (vendor defined) */
> +	    rdesc[212] == 0x06 && rdesc[213] == 0x12 && rdesc[214] == 0xff &&
> +	    /* Usage 0x51 */
> +	    rdesc[611] == 0x09 && rdesc[612] == 0x51 &&
> +	    /* report size 64 */
> +	    rdesc[627] == 0x75 && rdesc[628] == 64 &&
> +	    /* report count 1 */
> +	    rdesc[629] == 0x95 && rdesc[630] == 1) {
> +		rdesc[628] = 32;
> +		rdesc[630] = 2;
> +		hid_dbg(hdev, "Fixed up second 64-bit field\n");
> +	}
> +
> +	return rdesc;
> +}
> +
> +static int appleib_input_configured_fwd(struct appleib_hid_drv_info *drv_info,
> +					struct hid_device *hdev, void *args)
> +{
> +	return call_driver_func(drv_info, input_configured, int, hdev,
> +				(struct hid_input *)args);
> +}
> +
> +static int appleib_input_configured(struct hid_device *hdev,
> +				    struct hid_input *hidinput)
> +{
> +	return appleib_forward_int_op(hdev, appleib_input_configured_fwd,
> +				      hidinput);
> +}
> +
> +#ifdef CONFIG_PM
> +static int appleib_hid_suspend_fwd(struct appleib_hid_drv_info *drv_info,
> +				   struct hid_device *hdev, void *args)
> +{
> +	return call_driver_func(drv_info, suspend, int, hdev,
> +				*(pm_message_t *)args);
> +}
> +
> +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 appleib_hid_drv_info *drv_info,
> +				  struct hid_device *hdev, void *args)
> +{
> +	return call_driver_func(drv_info, resume, int, hdev);
> +}
> +
> +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 appleib_hid_drv_info *drv_info,
> +					struct hid_device *hdev, void *args)
> +{
> +	return call_driver_func(drv_info, reset_resume, int, hdev);
> +}
> +
> +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 */
> +
> +/**
> + * 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
> + */
> +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);
> +
> +/**

Please use correct kernel-doc style rather than parts of it.

> + * 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
> + */
> +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);
> +
> +/**
> + * Return whether we're currently inside a hid_device_probe or not.
> + * @ib_dev: the appleib_device
> + */
> +bool appleib_in_hid_probe(struct appleib_device *ib_dev)
> +{
> +	return ib_dev->in_hid_probe;
> +}
> +EXPORT_SYMBOL_GPL(appleib_in_hid_probe);
> +
> +static struct appleib_hid_dev_info *
> +appleib_add_device(struct appleib_device *ib_dev, struct hid_device *hdev,
> +		   const struct hid_device_id *id)
> +{
> +	struct appleib_hid_dev_info *dev_info;
> +	struct appleib_hid_drv_info *drv_info;
> +
> +	/* allocate device-info for this device */
> +	dev_info = kzalloc(sizeof(*dev_info), GFP_KERNEL);
> +	if (!dev_info)
> +		return NULL;
> +
> +	INIT_LIST_HEAD(&dev_info->drivers);
> +	dev_info->device = hdev;
> +	dev_info->device_id = id;
> +
> +	/* notify all our sub drivers */
> +	mutex_lock(&ib_dev->update_lock);
> +
This is interesting. I'd like to see a comment here on what
this flag is going to do. 

> +	ib_dev->in_hid_probe = true;
> +
> +	list_for_each_entry(drv_info, &ib_dev->hid_drivers, entry) {
> +		appleib_probe_driver(drv_info, dev_info);
> +	}
> +
> +	ib_dev->in_hid_probe = false;
> +
> +	/* remember this new device */
> +	list_add_tail_rcu(&dev_info->entry, &ib_dev->hid_devices);
> +
> +	mutex_unlock(&ib_dev->update_lock);
> +
> +	return dev_info;
> +}
> +
> +static void appleib_remove_device(struct appleib_device *ib_dev,
> +				  struct appleib_hid_dev_info *dev_info)
> +{
> +	list_del_rcu(&dev_info->entry);
> +	synchronize_srcu(&ib_dev->lists_srcu);
> +
> +	appleib_detach_drivers(ib_dev, dev_info);
> +
> +	kfree(dev_info);
> +}
> +
> +static int appleib_hid_probe(struct hid_device *hdev,
> +			     const struct hid_device_id *id)
> +{
> +	struct appleib_device *ib_dev;
> +	struct appleib_hid_dev_info *dev_info;
> +	struct usb_device *udev;
> +	int rc;
> +
> +	/* check usb config first */
> +	udev = hid_to_usb_dev(hdev);
> +
> +	if (udev->actconfig->desc.bConfigurationValue != APPLETB_BASIC_CONFIG) {
> +		rc = usb_driver_set_configuration(udev, APPLETB_BASIC_CONFIG);
> +		return rc ? rc : -ENODEV;
> +	}
> +
> +	/* Assign the driver data */
> +	ib_dev = appleib_dev;
> +	hid_set_drvdata(hdev, ib_dev);
> +
> +	/* initialize the report info */
> +	rc = hid_parse(hdev);
> +	if (rc) {
> +		hid_err(hdev, "ib: hid parse failed (%d)\n", rc);
> +		goto error;
> +	}
> +
> +	/* alloc bufs etc so probe's can send requests; but connect later */
> +	rc = hid_hw_start(hdev, 0);
> +	if (rc) {
> +		hid_err(hdev, "ib: hw start failed (%d)\n", rc);
> +		goto error;
> +	}
> +
> +	/* add this hdev to our device list */
> +	dev_info = appleib_add_device(ib_dev, hdev, id);
> +	if (!dev_info) {
> +		rc = -ENOMEM;
> +		goto stop_hw;
> +	}
> +
> +	/* start the hid */
> +	rc = appleib_start_hid_events(dev_info);
> +	if (rc)
> +		goto remove_dev;
> +
> +	return 0;
> +
> +remove_dev:
> +	mutex_lock(&ib_dev->update_lock);
> +	appleib_remove_device(ib_dev, dev_info);
> +	mutex_unlock(&ib_dev->update_lock);
> +stop_hw:
> +	hid_hw_stop(hdev);
> +error:
> +	return rc;
> +}
> +
> +static void appleib_hid_remove(struct hid_device *hdev)
> +{
> +	struct appleib_device *ib_dev = hid_get_drvdata(hdev);
> +	struct appleib_hid_dev_info *dev_info;
> +
> +	mutex_lock(&ib_dev->update_lock);
> +
> +	list_for_each_entry(dev_info, &ib_dev->hid_devices, entry) {
> +		if (dev_info->device == hdev) {
> +			appleib_stop_hid_events(dev_info);
> +			appleib_remove_device(ib_dev, dev_info);
> +			break;
> +		}
> +	}
> +
> +	mutex_unlock(&ib_dev->update_lock);
> +
> +	hid_hw_stop(hdev);
> +}
> +
> +static const struct hid_device_id appleib_hid_devices[] = {
> +	{ HID_USB_DEVICE(USB_ID_VENDOR_APPLE, USB_ID_PRODUCT_IBRIDGE) },
> +	{ },
> +};
> +
> +static struct hid_driver appleib_hid_driver = {
> +	.name = "apple-ibridge-hid",
> +	.id_table = appleib_hid_devices,
> +	.probe = appleib_hid_probe,
> +	.remove = appleib_hid_remove,
> +	.event = appleib_hid_event,
> +	.report_fixup = appleib_report_fixup,
> +	.input_configured = appleib_input_configured,
> +#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;
> +	int rc;
> +
> +	/* allocate */

Drop comments that don't anything a quick glance at the code would tell you.

> +	ib_dev = kzalloc(sizeof(*ib_dev), GFP_KERNEL);
> +	if (!ib_dev)
> +		return ERR_PTR(-ENOMEM);
> +
> +	/* init structures */
> +	INIT_LIST_HEAD(&ib_dev->hid_drivers);
> +	INIT_LIST_HEAD(&ib_dev->hid_devices);
> +	mutex_init(&ib_dev->update_lock);
> +	init_srcu_struct(&ib_dev->lists_srcu);
> +
> +	ib_dev->acpi_dev = acpi_dev;
> +
> +	/* get iBridge acpi power control method */
> +	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));
> +		rc = -ENXIO;
> +		goto free_mem;
> +	}
> +
> +	/* 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;
> +
> +free_mem:
> +	kfree(ib_dev);
> +	return ERR_PTR(rc);
> +}
> +
> +static int appleib_probe(struct acpi_device *acpi)
> +{
> +	struct appleib_device *ib_dev;
> +	struct appleib_platform_data *pdata;
Platform_data has a lot of historical meaning in Linux.
Also you have things in here that are not platform related
at all, such as the dev pointer.  Hence I would rename it
as device_data or private or something like that.

> +	int i;
> +	int ret;
> +
> +	if (appleib_dev)
This singleton bothers me a bit. I'm really not sure why it
is necessary.  You can just put a pointer to this in
the pdata for the subdevs and I think that covers most of your
usecases.  It's generally a bad idea to limit things to one instance
of a device unless that actually major simplifications.
I'm not seeing them here.


> +		return -EBUSY;
> +
> +	ib_dev = appleib_alloc_device(acpi);
> +	if (IS_ERR_OR_NULL(ib_dev))
> +		return PTR_ERR(ib_dev);
> +
> +	ib_dev->subdevs = kmemdup(appleib_subdevs, sizeof(appleib_subdevs),
> +				  GFP_KERNEL);
Given this is fixed sized and always referenced via ib_dev->subdevs, just
put the array in there and memcpy into it.  That way you have one less
allocation and simpler code.

> +	if (!ib_dev->subdevs) {
> +		ret = -ENOMEM;
> +		goto free_dev;
> +	}
> +
> +	pdata = kzalloc(sizeof(*pdata), GFP_KERNEL);

Might as well embed this in ib_dev as well.  That would let
you used container_of to avoid having to carry the ib_dev pointer
around in side pdata.

> +	if (!pdata) {
> +		ret = -ENOMEM;
> +		goto free_subdevs;
> +	}
> +
> +	pdata->ib_dev = ib_dev;
> +	pdata->log_dev = LOG_DEV(ib_dev);
> +	for (i = 0; i < ARRAY_SIZE(appleib_subdevs); i++) {
> +		ib_dev->subdevs[i].platform_data = pdata;
> +		ib_dev->subdevs[i].pdata_size = sizeof(*pdata);
> +	}
> +
> +	ret = mfd_add_devices(&acpi->dev, PLATFORM_DEVID_NONE,
> +			      ib_dev->subdevs, ARRAY_SIZE(appleib_subdevs),
> +			      NULL, 0, NULL);
> +	if (ret) {
> +		dev_err(LOG_DEV(ib_dev), "Error adding MFD devices: %d\n", ret);
> +		goto free_pdata;
> +	}
> +
> +	acpi->driver_data = ib_dev;
> +	appleib_dev = ib_dev;
> +
> +	ret = hid_register_driver(&appleib_hid_driver);
> +	if (ret) {
> +		dev_err(LOG_DEV(ib_dev), "Error registering hid driver: %d\n",
> +			ret);
> +		goto rem_mfd_devs;
> +	}
> +
> +	return 0;
> +
> +rem_mfd_devs:
> +	mfd_remove_devices(&acpi->dev);
> +free_pdata:
> +	kfree(pdata);
> +free_subdevs:
> +	kfree(ib_dev->subdevs);
> +free_dev:
> +	appleib_dev = NULL;
> +	acpi->driver_data = NULL;
Why at this point?  It's not set to anything until much later in the
probe flow.  May be worth thinking about devm_ managed allocations
to cleanup some of these allocations automatically and simplify
the error handling.

> +	kfree(ib_dev);
> +	return ret;
> +}
> +
> +static int appleib_remove(struct acpi_device *acpi)
> +{
> +	struct appleib_device *ib_dev = acpi_driver_data(acpi);
> +
> +	mfd_remove_devices(&acpi->dev);
> +	hid_unregister_driver(&appleib_hid_driver);
> +
> +	if (appleib_dev == ib_dev)
From a general reviewability point of view, it's nice to
keep the remove in the same order as the cleanup on
error in probe (and hence reverse of probe). That measn
this should be a little further down.

I'd also like to see a comment on how this condition can be
false.

> +		appleib_dev = NULL;
> +
> +	kfree(ib_dev->subdevs[0].platform_data);
> +	kfree(ib_dev->subdevs);
> +	kfree(ib_dev);

Is it worth considering devm in here to avoid the need to
clean all these up by hand?

> +
> +	return 0;
> +}
> +
> +static int appleib_suspend(struct device *dev)
> +{
> +	struct acpi_device *adev;
> +	struct appleib_device *ib_dev;
> +	int rc;
> +
> +	adev = to_acpi_device(dev);
> +	ib_dev = acpi_driver_data(adev);
Given this appears a few times, probably worth the more compact

	ib_dev = acpi_driver_data(to_acpi_device(dev));

Allowing you to drop the adev local variable that doesn't add
any info.

> +
> +	rc = acpi_execute_simple_method(ib_dev->asoc_socw, NULL, 0);
> +	if (ACPI_FAILURE(rc))
> +		dev_warn(LOG_DEV(ib_dev), "SOCW(0) failed: %s\n",

I can sort of see you might want to do the LOG_DEV for consistency
but here I'm fairly sure it's just dev which might be clearer.

> +			 acpi_format_exception(rc));
> +
> +	return 0;
> +}
> +
> +static int appleib_resume(struct device *dev)
> +{
> +	struct acpi_device *adev;
> +	struct appleib_device *ib_dev;
> +	int rc;
> +
> +	adev = to_acpi_device(dev);
> +	ib_dev = acpi_driver_data(adev);
> +
> +	rc = acpi_execute_simple_method(ib_dev->asoc_socw, NULL, 1);
> +	if (ACPI_FAILURE(rc))
> +		dev_warn(LOG_DEV(ib_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/include/linux/mfd/apple-ibridge.h b/include/linux/mfd/apple-ibridge.h
> new file mode 100644
> index 000000000000..d321714767f7
> --- /dev/null
> +++ b/include/linux/mfd/apple-ibridge.h
> @@ -0,0 +1,39 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * Apple iBridge Driver
> + *
> + * Copyright (c) 2018 Ronald Tschalär
> + */
> +
> +#ifndef __LINUX_MFD_APPLE_IBRDIGE_H
> +#define __LINUX_MFD_APPLE_IBRDIGE_H
> +
> +#include <linux/device.h>
> +#include <linux/hid.h>
> +
> +#define PLAT_NAME_IB_TB		"apple-ib-tb"
> +#define PLAT_NAME_IB_ALS	"apple-ib-als"
> +
> +struct appleib_device;
> +
> +struct appleib_platform_data {
> +	struct appleib_device *ib_dev;
> +	struct device *log_dev;
> +};
> +
> +int appleib_register_hid_driver(struct appleib_device *ib_dev,
> +				struct hid_driver *driver, void *data);
> +int appleib_unregister_hid_driver(struct appleib_device *ib_dev,
> +				  struct hid_driver *driver);
> +
> +void *appleib_get_drvdata(struct appleib_device *ib_dev,
> +			  struct hid_driver *driver);
> +bool appleib_in_hid_probe(struct appleib_device *ib_dev);
> +
> +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] 22+ messages in thread

* Re: [PATCH 3/3] iio: light: apple-ib-als: Add driver for ALS on iBridge chip.
  2019-04-22  9:17   ` Peter Meerwald-Stadler
@ 2019-04-22 12:01     ` Jonathan Cameron
  2019-04-23 10:38       ` Life is hard, and then you die
  0 siblings, 1 reply; 22+ messages in thread
From: Jonathan Cameron @ 2019-04-22 12:01 UTC (permalink / raw)
  To: Peter Meerwald-Stadler
  Cc: Ronald Tschalär, Jiri Kosina, Benjamin Tissoires,
	Hartmut Knaack, Lars-Peter Clausen, Lee Jones, linux-input,
	linux-iio, linux-kernel

On Mon, 22 Apr 2019 11:17:27 +0200 (CEST)
Peter Meerwald-Stadler <pmeerw@pmeerw.net> wrote:

> On Sun, 21 Apr 2019, Ronald Tschalär 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.  
> 
> some comments below inline
I'll 'nest' on Peter's review to avoid repetition.

A few additional comments inline.

Thanks,

Jonathan
>  
> > 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 | 694 +++++++++++++++++++++++++++++++
> >  3 files changed, 707 insertions(+)
> >  create mode 100644 drivers/iio/light/apple-ib-als.c
> > 
> > diff --git a/drivers/iio/light/Kconfig b/drivers/iio/light/Kconfig
> > index 36f458433480..49159fab1c0e 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 MFD_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 286bf3975372..144d918917f7 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..1718fcbe304f
> > --- /dev/null
> > +++ b/drivers/iio/light/apple-ib-als.c
> > @@ -0,0 +1,694 @@
> > +// 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/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/mfd/apple-ibridge.h>
> > +#include <linux/module.h>
> > +#include <linux/platform_device.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 appleib_device	*ib_dev;
> > +	struct device		*log_dev;
> > +	struct hid_device	*hid_dev;
> > +	struct hid_report	*cfg_report;
> > +	struct hid_field	*illum_field;
> > +	struct iio_dev		*iio_dev;
> > +	struct iio_trigger	*iio_trig;
> > +	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 struct appleals_sensitivity_map appleals_sensitivity_map[] = {  
> 
> const?
> 
> > +	{   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)
> > +{
> > +	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)
> > +{
> > +	hid_hw_request(als_dev->hid_dev, field->report, HID_REQ_GET_REPORT);
> > +	hid_hw_wait(als_dev->hid_dev);
> > +
> > +	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);  
> 
> can return -1, not checked
> 
> > +
> > +	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 =
> > +		appleib_get_drvdata(hid_get_drvdata(hdev),
> > +				    &appleals_hid_driver);
> > +	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;
> > +
> > +	/* set the sensor's reporting state */
> > +	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 *als_dev =
> > +				*(struct appleals_device **)iio_priv(iio_dev);
> > +	__s32 value;
> > +	int rc;
> > +
> > +	*val = 0;
> > +	*val2 = 0;  
> 
> no need to set these here
> 
> > +
> > +	switch (mask) {
> > +	case IIO_CHAN_INFO_RAW:
> > +	case IIO_CHAN_INFO_PROCESSED:
> > +		*val = appleals_get_field_value(als_dev, als_dev->illum_field);

How can one read by both processed and raw?

> > +		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 *als_dev =
> > +				*(struct appleals_device **)iio_priv(iio_dev);
> > +	__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));
> > +		break;  
> 
> maybe return directly instead of at the end (matter of taste);
> here and in the other cases below
> 
> > +
> > +	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);

There is some debate in another thread on whether dynamic sensitivity can be
mapped to hysteresis or not...

> > +			}
> > +			rc = 0;
> > +			break;
> > +		}
> > +
> > +		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;
> > +		}
> > +		break;
> > +
> > +	default:
> > +		rc = -EINVAL;
> > +	}
> > +
> > +	return rc;
> > +}
> > +
> > +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) |
> > +			BIT(IIO_CHAN_INFO_RAW),
Why return both processed and raw?  We don't generally allow that in IIO
(there are a few historical exceptions due to us getting it wrong the
first time).

> > +		.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) |
> > +			BIT(IIO_CHAN_INFO_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 = 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;
> > +	__s32 val;
> > +
> > +	/*
> > +	 * We're (often) in a probe here, so need to enable input processing
> > +	 * in that case, but only in that case.
> > +	 */
I'd be a little happier if this wasn't termed 'in_hid_probe' but rather something
like _needs_io_start.

That way we are reflecting what needs to happen, not where we are in the code
flow that makes it true.

> > +	if (appleib_in_hid_probe(als_dev->ib_dev))
> > +		hid_device_io_start(als_dev->hid_dev);
> > +
> > +	/* power on the sensor */
> > +	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);  
> 
> what if -1?
> 
> > +	hid_set_field(field, 0, val);
> > +
> > +	/* configure reporting of change events */
> > +	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);
> > +	hid_set_field(field, 0, val);
> > +
> > +	/* report change events asap */
> > +	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);
> > +	}
> > +
> > +	/* write the new config to the sensor */
> > +	hid_hw_request(als_dev->hid_dev, als_dev->cfg_report,
> > +		       HID_REQ_SET_REPORT);
> > +
> > +	if (appleib_in_hid_probe(als_dev->ib_dev))
> > +		hid_device_io_stop(als_dev->hid_dev);
> > +};  
> 
> no semicolon at the end of a function please
> 
> > +
> > +static int appleals_config_iio(struct appleals_device *als_dev)
> > +{
> > +	struct iio_dev *iio_dev;
> > +	struct iio_trigger *iio_trig;
> > +	int rc;
> > +
> > +	/* create and register iio device */
A very good reason for not adding superficial comments.
This one is wrong or at least misleading.  It doesn't register
the iio device.  So get rid of all these comments that are
just saying what the code next to them is doing.  Keep the ones
that add extra information.


> > +	iio_dev = iio_device_alloc(sizeof(als_dev));  
> 
> how about using the devm_ variants?
> 
> > +	if (!iio_dev)
> > +		return -ENOMEM;
> > +
> > +	*(struct appleals_device **)iio_priv(iio_dev) = als_dev;

That is nasty.  Add a local variable to make to clear that iio_priv(iio_dev)
is actually some space for this pointer. Complex type casting is not
terribly readable.

> > +
> > +	iio_dev->channels = appleals_channels;
> > +	iio_dev->num_channels = ARRAY_SIZE(appleals_channels);
> > +	iio_dev->dev.parent = &als_dev->hid_dev->dev;
> > +	iio_dev->info = &appleals_info;
> > +	iio_dev->name = "als";
> > +	iio_dev->modes = INDIO_DIRECT_MODE;
> > +
> > +	rc = iio_triggered_buffer_setup(iio_dev, &iio_pollfunc_store_time, NULL,
> > +					NULL);
> > +	if (rc) {
> > +		dev_err(als_dev->log_dev, "failed to set up iio triggers: %d\n",  
> 
> just one trigger?
Also wrong as it's not actually setting up the trigger at all ;) It's setting
up the handling for what to do when a trigger event occurs.

> 
> > +			rc);
> > +		goto free_iio_dev;
> > +	}
> > +
> > +	iio_trig = iio_trigger_alloc("%s-dev%d", iio_dev->name, iio_dev->id);
> > +	if (!iio_trig) {
> > +		rc = -ENOMEM;
> > +		goto clean_trig_buf;
> > +	}
> > +
> > +	iio_trig->dev.parent = &als_dev->hid_dev->dev;
> > +	iio_trig->ops = &appleals_trigger_ops;
> > +	iio_trigger_set_drvdata(iio_trig, als_dev);
> > +
> > +	rc = iio_trigger_register(iio_trig);
> > +	if (rc) {
> > +		dev_err(als_dev->log_dev, "failed to register iio trigger: %d\n",  
> 
> some messages start lowercase, some uppercase (nitpicking)
> 
> > +			rc);
> > +		goto free_iio_trig;
> > +	}
> > +
> > +	als_dev->iio_trig = iio_trig;
> > +
> > +	rc = iio_device_register(iio_dev);
> > +	if (rc) {
> > +		dev_err(als_dev->log_dev, "failed to register iio device: %d\n",
> > +			rc);
> > +		goto unreg_iio_trig;
> > +	}
> > +
> > +	als_dev->iio_dev = iio_dev;

I really don't like nest of pointers going on in here.  I haven't dug
down to check if any of them can be remove, but they are definitely
ugly to deal with.

> > +
> > +	return 0;
> > +
> > +unreg_iio_trig:
> > +	iio_trigger_unregister(iio_trig);
> > +free_iio_trig:
> > +	iio_trigger_free(iio_trig);
> > +	als_dev->iio_trig = NULL;
> > +clean_trig_buf:
> > +	iio_triggered_buffer_cleanup(iio_dev);
> > +free_iio_dev:
> > +	iio_device_free(iio_dev);
> > +
> > +	return rc;
> > +}
> > +
> > +static int appleals_probe(struct hid_device *hdev,
> > +			  const struct hid_device_id *id)
> > +{
> > +	struct appleals_device *als_dev =
> > +		appleib_get_drvdata(hid_get_drvdata(hdev),
> > +				    &appleals_hid_driver);
> > +	struct hid_field *state_field;
> > +	struct hid_field *illum_field;
> > +	int rc;
> > +
> > +	/* find als fields and reports */
> > +	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;
> > +
> > +	if (als_dev->hid_dev) {
> > +		dev_warn(als_dev->log_dev,
> > +			 "Found duplicate ambient light sensor - ignoring\n");

I'll bite.  So how can this actually happen?  What fundamentally breaks in
your model if there really are two?  Can you fix whatever that is so
as to drop this handling?

> > +		return -EBUSY;
> > +	}
> > +
> > +	dev_info(als_dev->log_dev, "Found ambient light sensor\n");  
> 
> in general avoid logging for the OK case, it just clutters the log
> 
> > +
> > +	/* initialize device */
> > +	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;
> > +	appleals_config_sensor(als_dev, false, als_dev->cur_sensitivity);
> > +
> > +	rc = appleals_config_iio(als_dev);
> > +	if (rc)
> > +		return rc;
> > +
> > +	return 0;

return appleals_config_iio(als_dev);

> > +}
> > +
> > +static void appleals_remove(struct hid_device *hdev)
> > +{
> > +	struct appleals_device *als_dev =
> > +		appleib_get_drvdata(hid_get_drvdata(hdev),
> > +				    &appleals_hid_driver);
> > +  
> 
> could be a lot less if devm_ were used?
> 
> > +	if (als_dev->iio_dev) {
> > +		iio_device_unregister(als_dev->iio_dev);
> > +
> > +		iio_trigger_unregister(als_dev->iio_trig);
> > +		iio_trigger_free(als_dev->iio_trig);
> > +		als_dev->iio_trig = NULL;

I would be decidedly worried if you actually have any paths
where setting these to NULL do anything useful. By the time
we get here we should absolutely 'know' nothing will touch
these pointers.

It is these that are blocking Peter's suggestion of using
devm to clean all of this up automatically for you.

> > +
> > +		iio_triggered_buffer_cleanup(als_dev->iio_dev);
> > +		iio_device_free(als_dev->iio_dev);
> > +		als_dev->iio_dev = NULL;
> > +	}
> > +
> > +	als_dev->hid_dev = NULL;
> > +}
> > +
> > +#ifdef CONFIG_PM
> > +static int appleals_reset_resume(struct hid_device *hdev)
> > +{
> > +	struct appleals_device *als_dev =
> > +		appleib_get_drvdata(hid_get_drvdata(hdev),
> > +				    &appleals_hid_driver);
> > +
> > +	appleals_config_sensor(als_dev, als_dev->events_enabled,
> > +			       als_dev->cur_sensitivity);
> > +
> > +	return 0;
> > +}
> > +#endif
> > +
> > +static struct hid_driver appleals_hid_driver = {
> > +	.name = "apple-ib-als",
> > +	.probe = appleals_probe,
> > +	.remove = appleals_remove,
> > +	.event = appleals_hid_event,
> > +#ifdef CONFIG_PM
> > +	.reset_resume = appleals_reset_resume,
> > +#endif
> > +};
> > +
> > +static int appleals_platform_probe(struct platform_device *pdev)
> > +{
> > +	struct appleib_platform_data *pdata = pdev->dev.platform_data;
> > +	struct appleib_device *ib_dev = pdata->ib_dev;
> > +	struct appleals_device *als_dev;
> > +	int rc;
> > +
> > +	als_dev = kzalloc(sizeof(*als_dev), GFP_KERNEL);
> > +	if (!als_dev)
> > +		return -ENOMEM;
> > +
> > +	als_dev->ib_dev = ib_dev;
> > +	als_dev->log_dev = pdata->log_dev;
> > +
> > +	rc = appleib_register_hid_driver(ib_dev, &appleals_hid_driver, als_dev);

Hmm. This is all a bit odd.  We register a platform device, to call it's driver
so that it can then register another driver?  I'll let Lee comment on that but
it does seem overly complex.

> > +	if (rc) {
> > +		dev_err(als_dev->log_dev, "Error registering hid driver: %d\n",
> > +			rc);
> > +		goto error;
> > +	}
> > +
> > +	platform_set_drvdata(pdev, als_dev);
> > +
> > +	return 0;
> > +
> > +error:
> > +	kfree(als_dev);
> > +	return rc;
> > +}
> > +
> > +static int appleals_platform_remove(struct platform_device *pdev)
> > +{
> > +	struct appleib_platform_data *pdata = pdev->dev.platform_data;
> > +	struct appleib_device *ib_dev = pdata->ib_dev;
> > +	struct appleals_device *als_dev = platform_get_drvdata(pdev);
> > +	int rc;
> > +
> > +	rc = appleib_unregister_hid_driver(ib_dev, &appleals_hid_driver);
> > +	if (rc) {
> > +		dev_err(als_dev->log_dev,
> > +			"Error unregistering hid driver: %d\n", rc);

Given you are going to have this error printing in every sub driver and
the text is totally generic, can you just move it into the core?

Same applies for similar cases above.

> > +		goto error;
> > +	}
> > +
> > +	kfree(als_dev);

Use managed allocation to avoid needing to clean this up?

> > +
> > +	return 0;
> > +
> > +error:
> > +	return rc;
> > +}
> > +
> > +static const struct platform_device_id appleals_platform_ids[] = {
> > +	{ .name = PLAT_NAME_IB_ALS },
> > +	{ }
> > +};
> > +MODULE_DEVICE_TABLE(platform, appleals_platform_ids);
> > +
> > +static struct platform_driver appleals_platform_driver = {
> > +	.id_table = appleals_platform_ids,
> > +	.driver = {
> > +		.name	= "apple-ib-als",
> > +	},
> > +	.probe = appleals_platform_probe,
> > +	.remove = appleals_platform_remove,
> > +};
> > +
> > +module_platform_driver(appleals_platform_driver);
> > +
> > +MODULE_AUTHOR("Ronald Tschalär");
> > +MODULE_DESCRIPTION("Apple iBridge ALS driver");
> > +MODULE_LICENSE("GPL v2");
> >   
> 


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

* Re: [PATCH 3/3] iio: light: apple-ib-als: Add driver for ALS on iBridge chip.
  2019-04-22 12:01     ` Jonathan Cameron
@ 2019-04-23 10:38       ` Life is hard, and then you die
  2019-04-24 12:27         ` Jonathan Cameron
  0 siblings, 1 reply; 22+ messages in thread
From: Life is hard, and then you die @ 2019-04-23 10:38 UTC (permalink / raw)
  To: Jonathan Cameron
  Cc: Peter Meerwald-Stadler, Jiri Kosina, Benjamin Tissoires,
	Hartmut Knaack, Lars-Peter Clausen, Lee Jones, linux-input,
	linux-iio, linux-kernel


  Hi Jonathan, Peter,

On Mon, Apr 22, 2019 at 01:01:38PM +0100, Jonathan Cameron wrote:
> On Mon, 22 Apr 2019 11:17:27 +0200 (CEST)
> Peter Meerwald-Stadler <pmeerw@pmeerw.net> wrote:
> 
> > On Sun, 21 Apr 2019, Ronald Tschalär 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.  
> > 
> > some comments below inline
> I'll 'nest' on Peter's review to avoid repetition.
> 
> A few additional comments inline.

Thank you both for your reviews. I've applied most of your
suggestions, so I'm limiting my responses below to just those places
where I need further clarification or can provide some answers.

[snip]
> > > +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 *als_dev =
> > > +				*(struct appleals_device **)iio_priv(iio_dev);
> > > +	__s32 value;
> > > +	int rc;
> > > +
> > > +	*val = 0;
> > > +	*val2 = 0;  
> > 
> > no need to set these here
> > 
> > > +
> > > +	switch (mask) {
> > > +	case IIO_CHAN_INFO_RAW:
> > > +	case IIO_CHAN_INFO_PROCESSED:
> > > +		*val = appleals_get_field_value(als_dev, als_dev->illum_field);
> 
> How can one read by both processed and raw?

From my understanding, processed is a converted and normalized value
of raw? But since the raw value is already in Lux the processed value
is then the same. Furthermore, looking at userspace apps that read
these values (e.g. iio-sensor-proxy, lightsd) they seem to be all over
the map in terms of what sysfs entries they read:
in_illuminance_input, in_intensity_both_raw, etc. So I figured
providing both would provide maximal compatibility.

What then is currently the preferred approach here?

> > > +	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);
> 
> There is some debate in another thread on whether dynamic sensitivity can be
> mapped to hysteresis or not...

Is that the "drivers: iio: Add more data field for iio driver
hysteresis parsing" thread?

In any case, I'm using the value 0 here to indicate that the
pseudo-percent-relative change sensitivity should be used. Better
suggestions certainly welcome.

[snip]
> > > +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) |
> > > +			BIT(IIO_CHAN_INFO_RAW),
> Why return both processed and raw?  We don't generally allow that in IIO
> (there are a few historical exceptions due to us getting it wrong the
> first time).

Ok. But which should be used then, especially in view of the fact that
different user-space apps/daemons appear to use different values?

[snip]
> > > +static int appleals_config_iio(struct appleals_device *als_dev)
[snip]
> > > +	iio_dev = iio_device_alloc(sizeof(als_dev));  
> > 
> > how about using the devm_ variants?

The problem is that there is no device with the proper lifetime here.
In particular we can't use hid_device (the only struct device
available here) because the instances of those can (and do) outlive
the module. This is a consequence of the hid-driver demuxing: e.g.
when this als module is unloaded, the hid_device is still in use by
the touchbar driver, and if this als module is then loaded again it
will get associated with that same hid_device again.

[snip]
> > > +	rc = iio_device_register(iio_dev);
> > > +	if (rc) {
> > > +		dev_err(als_dev->log_dev, "failed to register iio device: %d\n",
> > > +			rc);
> > > +		goto unreg_iio_trig;
> > > +	}
> > > +
> > > +	als_dev->iio_dev = iio_dev;
> 
> I really don't like nest of pointers going on in here.  I haven't dug
> down to check if any of them can be remove, but they are definitely
> ugly to deal with.

I'm not sure how this can be avoided: *iio_dev is allocated by
iio_device_alloc(), and this is just storing that pointer in our data
structure for this als device.

[snip]
> > > +static int appleals_probe(struct hid_device *hdev,
> > > +			  const struct hid_device_id *id)
> > > +{
> > > +	struct appleals_device *als_dev =
> > > +		appleib_get_drvdata(hid_get_drvdata(hdev),
> > > +				    &appleals_hid_driver);
> > > +	struct hid_field *state_field;
> > > +	struct hid_field *illum_field;
> > > +	int rc;
> > > +
> > > +	/* find als fields and reports */
> > > +	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;
> > > +
> > > +	if (als_dev->hid_dev) {
> > > +		dev_warn(als_dev->log_dev,
> > > +			 "Found duplicate ambient light sensor - ignoring\n");
> 
> I'll bite.  So how can this actually happen?  What fundamentally breaks in
> your model if there really are two?  Can you fix whatever that is so
> as to drop this handling?

This should indeed never happen - the check is just defensive
programming, in case either some new device in some new model appears,
or due to some bug somewhere.

To actually handle this I'd need to split up appleals_device into a
per iBridge-device part and a per ALS-device part, and allow multiple
ALS-device parts. This is certainly doable, but seemed a bit overkill
for something that is unlikely to ever be needed.

[snip]
> > > +static void appleals_remove(struct hid_device *hdev)
> > > +{
> > > +	struct appleals_device *als_dev =
> > > +		appleib_get_drvdata(hid_get_drvdata(hdev),
> > > +				    &appleals_hid_driver);
> > > +  
> > 
> > could be a lot less if devm_ were used?

Agreed, but see explanation above of why this can't be used.

> > > +	if (als_dev->iio_dev) {
> > > +		iio_device_unregister(als_dev->iio_dev);
> > > +
> > > +		iio_trigger_unregister(als_dev->iio_trig);
> > > +		iio_trigger_free(als_dev->iio_trig);
> > > +		als_dev->iio_trig = NULL;
> 
> I would be decidedly worried if you actually have any paths
> where setting these to NULL do anything useful. By the time
> we get here we should absolutely 'know' nothing will touch
> these pointers.

I guess I was being overly paranoid here. I've removed these now.

> It is these that are blocking Peter's suggestion of using
> devm to clean all of this up automatically for you.

No, that is for a different reason - see above.

[snip]
> > > +static int appleals_platform_probe(struct platform_device *pdev)
> > > +{
> > > +	struct appleib_platform_data *pdata = pdev->dev.platform_data;
> > > +	struct appleib_device *ib_dev = pdata->ib_dev;
> > > +	struct appleals_device *als_dev;
> > > +	int rc;
> > > +
> > > +	als_dev = kzalloc(sizeof(*als_dev), GFP_KERNEL);
> > > +	if (!als_dev)
> > > +		return -ENOMEM;
> > > +
> > > +	als_dev->ib_dev = ib_dev;
> > > +	als_dev->log_dev = pdata->log_dev;
> > > +
> > > +	rc = appleib_register_hid_driver(ib_dev, &appleals_hid_driver, als_dev);
> 
> Hmm. This is all a bit odd.  We register a platform device, to call it's driver
> so that it can then register another driver?  I'll let Lee comment on that but
> it does seem overly complex.

"Normally" this call would be to hid_register_driver(). However, as I
tried to explain in the cover letter and module comments, at least one
of the hid_device's needs to be shared by both this als driver and the
touchbar driver. But the linux device/driver architecture does not
allow multiple drivers to be attached to a single device. Hence the
mfd driver implements demuxing hid_driver that allows multiple
hid_drivers to be registered for a single hid_device. And this is why
we register our hid_driver with this demuxing driver here instead of
directly via hid_register_driver().

Does this make sense?


  Cheers,

  Ronald


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

* Re: [PATCH 1/3] mfd: apple-ibridge: Add Apple iBridge MFD driver.
  2019-04-22 11:34   ` Jonathan Cameron
@ 2019-04-24 10:47     ` Life is hard, and then you die
  2019-04-24 19:13       ` Jonathan Cameron
  0 siblings, 1 reply; 22+ messages in thread
From: Life is hard, and then you die @ 2019-04-24 10:47 UTC (permalink / raw)
  To: Jonathan Cameron
  Cc: Jiri Kosina, Benjamin Tissoires, Hartmut Knaack,
	Lars-Peter Clausen, Peter Meerwald-Stadler, Lee Jones,
	linux-input, linux-iio, linux-kernel


  Hi Jonathan,

On Mon, Apr 22, 2019 at 12:34:26PM +0100, Jonathan Cameron wrote:
> On Sun, 21 Apr 2019 20:12:49 -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 provides a multiplexing layer that allows multiple HID drivers to
> > be registered for a given HID device. This allows the touch bar and ALS
> > driver to be separated out into their own modules.
> > 
> > Signed-off-by: Ronald Tschalär <ronald@innovation.ch
> Hi Ronald,
> 
> I've only taken a fairly superficial look at this.  A few global
> things to note though.

Thanks for this review.

> 1. Please either use kernel-doc style for function descriptions, or
>    do not.  Right now you are sort of half way there.

Apologies, on re-reading the docs I realize what you mean here. Should
be fixed now (next rev).

> 2. There is quite a complex nest of separate structures being allocated,
>    so think about whether they can be simplified.  In particular
>    use of container_of macros can allow a lot of forwards and backwards
>    pointers to be dropped if you embed the various structures directly.

Done (see also below).

[snip]
> > +#define	call_void_driver_func(drv_info, fn, ...)			\
> 
> This sort of macro may seem like a good idea because it saves a few lines
> of code.  However, that comes at the cost of readability, so just
> put the code inline.
> 
> > +	do {								\
> > +		if ((drv_info)->driver->fn)				\
> > +			(drv_info)->driver->fn(__VA_ARGS__);		\
> > +	} while (0)
> > +
> > +#define	call_driver_func(drv_info, fn, ret_type, ...)			\
> > +	({								\
> > +		ret_type rc = 0;					\
> > +									\
> > +		if ((drv_info)->driver->fn)				\
> > +			rc = (drv_info)->driver->fn(__VA_ARGS__);	\
> > +									\
> > +		rc;							\
> > +	})

Just to clarify, you're only talking about removing/inlining the
call_void_driver_func() macro, not the call_driver_func() macro,
right?

[snip]
> > +static struct appleib_hid_dev_info *
> > +appleib_add_device(struct appleib_device *ib_dev, struct hid_device *hdev,
> > +		   const struct hid_device_id *id)
> > +{
> > +	struct appleib_hid_dev_info *dev_info;
> > +	struct appleib_hid_drv_info *drv_info;
> > +
> > +	/* allocate device-info for this device */
> > +	dev_info = kzalloc(sizeof(*dev_info), GFP_KERNEL);
> > +	if (!dev_info)
> > +		return NULL;
> > +
> > +	INIT_LIST_HEAD(&dev_info->drivers);
> > +	dev_info->device = hdev;
> > +	dev_info->device_id = id;
> > +
> > +	/* notify all our sub drivers */
> > +	mutex_lock(&ib_dev->update_lock);
> > +
> This is interesting. I'd like to see a comment here on what
> this flag is going to do. 

I'm not sure I follow: update_lock is simply a mutex protecting all
driver and device update (i.e. add/remove) functions. Are you
therefore looking for something like:

	/* protect driver and device lists against concurrent updates */
	mutex_lock(&ib_dev->update_lock);

[snip]
> > +static int appleib_probe(struct acpi_device *acpi)
> > +{
> > +	struct appleib_device *ib_dev;
> > +	struct appleib_platform_data *pdata;
> Platform_data has a lot of historical meaning in Linux.
> Also you have things in here that are not platform related
> at all, such as the dev pointer.  Hence I would rename it
> as device_data or private or something like that.

Ok. I guess I called in platform_data because it's stored in the mfd
cell's "platform_data" field. Anyway, changed it per your suggestion.

> > +	int i;
> > +	int ret;
> > +
> > +	if (appleib_dev)
> This singleton bothers me a bit. I'm really not sure why it
> is necessary.  You can just put a pointer to this in
> the pdata for the subdevs and I think that covers most of your
> usecases.  It's generally a bad idea to limit things to one instance
> of a device unless that actually major simplifications.
> I'm not seeing them here.

Yes, this one is quite ugly. appleib_dev is static so that
appleib_hid_probe() can find it. I could not find any other way to
pass the appleib_dev instance to that probe function.

However, on looking at this again, I realized that hid_device_id has
a driver_data field which can be used for this; so if I added the
hid_driver and hid_device_id structs to the appleib_device (instead of
making them static like now) I could fill in the driver_data and avoid
this hack. This looks much cleaner.

Thanks for pointing this uglyness out again.

[snip]
> > +	if (!ib_dev->subdevs) {
> > +		ret = -ENOMEM;
> > +		goto free_dev;
> > +	}
> > +
> > +	pdata = kzalloc(sizeof(*pdata), GFP_KERNEL);
> 
> Might as well embed this in ib_dev as well.

Agreed.

> That would let
> you used container_of to avoid having to carry the ib_dev pointer
> around in side pdata.

I see. I guess my main reservation is that the functions exported to
the sub-drivers would now take a 'struct appleib_device_data *'
argument instead of a 'struct appleib_device *', which just seems a
bit unnatural. E.g.

  int appleib_register_hid_driver(struct appleib_device_data *ib_ddata,
                                  struct hid_driver *driver, void *data);

instead of (the current)

  int appleib_register_hid_driver(struct appleib_device *ib_dev,
                                  struct hid_driver *driver, void *data);

[snip]
> > +	ret = mfd_add_devices(&acpi->dev, PLATFORM_DEVID_NONE,
> > +			      ib_dev->subdevs, ARRAY_SIZE(appleib_subdevs),
> > +			      NULL, 0, NULL);
> > +	if (ret) {
> > +		dev_err(LOG_DEV(ib_dev), "Error adding MFD devices: %d\n", ret);
> > +		goto free_pdata;
> > +	}
> > +
> > +	acpi->driver_data = ib_dev;
> > +	appleib_dev = ib_dev;
> > +
> > +	ret = hid_register_driver(&appleib_hid_driver);
> > +	if (ret) {
> > +		dev_err(LOG_DEV(ib_dev), "Error registering hid driver: %d\n",
> > +			ret);
> > +		goto rem_mfd_devs;
> > +	}
> > +
> > +	return 0;
> > +
> > +rem_mfd_devs:
> > +	mfd_remove_devices(&acpi->dev);
> > +free_pdata:
> > +	kfree(pdata);
> > +free_subdevs:
> > +	kfree(ib_dev->subdevs);
> > +free_dev:
> > +	appleib_dev = NULL;
> > +	acpi->driver_data = NULL;
> Why at this point?  It's not set to anything until much later in the
> probe flow.

If the hid_register_driver() call fails, we get here after driver_data
has been assigned. However, looking at this again, acpi->driver_data
is only used by the remove, suspend, and resume callbacks, and those
will not be called until a successful return from probe; therefore I
can safely move the setting of driver_data to after the
hid_register_driver() call and avoid having to set it to NULL in the
error cleanup.

> May be worth thinking about devm_ managed allocations
> to cleanup some of these allocations automatically and simplify
> the error handling.

Good point, thanks.

[snip]
> > +
> > +	rc = acpi_execute_simple_method(ib_dev->asoc_socw, NULL, 0);
> > +	if (ACPI_FAILURE(rc))
> > +		dev_warn(LOG_DEV(ib_dev), "SOCW(0) failed: %s\n",
> 
> I can sort of see you might want to do the LOG_DEV for consistency
> but here I'm fairly sure it's just dev which might be clearer.

Sorry, you mean rename the macro LOG_DEV() to just DEV()?


  Cheers,

  Ronald


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

* Re: [PATCH 3/3] iio: light: apple-ib-als: Add driver for ALS on iBridge chip.
  2019-04-23 10:38       ` Life is hard, and then you die
@ 2019-04-24 12:27         ` Jonathan Cameron
  0 siblings, 0 replies; 22+ messages in thread
From: Jonathan Cameron @ 2019-04-24 12:27 UTC (permalink / raw)
  To: Life is hard, and then you die
  Cc: Jonathan Cameron, Peter Meerwald-Stadler, Jiri Kosina,
	Benjamin Tissoires, Hartmut Knaack, Lars-Peter Clausen,
	Lee Jones, linux-input, linux-iio, linux-kernel

On Tue, 23 Apr 2019 03:38:55 -0700
"Life is hard, and then you die" <ronald@innovation.ch> wrote:

>   Hi Jonathan, Peter,
> 
> On Mon, Apr 22, 2019 at 01:01:38PM +0100, Jonathan Cameron wrote:
> > On Mon, 22 Apr 2019 11:17:27 +0200 (CEST)
> > Peter Meerwald-Stadler <pmeerw@pmeerw.net> wrote:
> >   
> > > On Sun, 21 Apr 2019, Ronald Tschalär 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.    
> > > 
> > > some comments below inline  
> > I'll 'nest' on Peter's review to avoid repetition.
> > 
> > A few additional comments inline.  
> 
> Thank you both for your reviews. I've applied most of your
> suggestions, so I'm limiting my responses below to just those places
> where I need further clarification or can provide some answers.
> 
> [snip]
> > > > +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 *als_dev =
> > > > +				*(struct appleals_device **)iio_priv(iio_dev);
> > > > +	__s32 value;
> > > > +	int rc;
> > > > +
> > > > +	*val = 0;
> > > > +	*val2 = 0;    
> > > 
> > > no need to set these here
> > >   
> > > > +
> > > > +	switch (mask) {
> > > > +	case IIO_CHAN_INFO_RAW:
> > > > +	case IIO_CHAN_INFO_PROCESSED:
> > > > +		*val = appleals_get_field_value(als_dev, als_dev->illum_field);  
> > 
> > How can one read by both processed and raw?  
> 
> From my understanding, processed is a converted and normalized value
> of raw? But since the raw value is already in Lux the processed value
> is then the same. Furthermore, looking at userspace apps that read
> these values (e.g. iio-sensor-proxy, lightsd) they seem to be all over
> the map in terms of what sysfs entries they read:
> in_illuminance_input, in_intensity_both_raw, etc. So I figured
> providing both would provide maximal compatibility.
> 
> What then is currently the preferred approach here?

Just provide the processed value.  We may have to fix any userspace apps
that aren't checking both.  For the raw version they should be applying the
scale in userspace.

> 
> > > > +	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);  
> > 
> > There is some debate in another thread on whether dynamic sensitivity can be
> > mapped to hysteresis or not...  
> 
> Is that the "drivers: iio: Add more data field for iio driver
> hysteresis parsing" thread?

That's the one.

> 
> In any case, I'm using the value 0 here to indicate that the
> pseudo-percent-relative change sensitivity should be used. Better
> suggestions certainly welcome.

It seems we are converging on a new IIO_CHAN_INFO type for sensitivity.

> 
> [snip]
> > > > +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) |
> > > > +			BIT(IIO_CHAN_INFO_RAW),  
> > Why return both processed and raw?  We don't generally allow that in IIO
> > (there are a few historical exceptions due to us getting it wrong the
> > first time).  
> 
> Ok. But which should be used then, especially in view of the fact that
> different user-space apps/daemons appear to use different values?
 forwards.

> 
> [snip]
> > > > +static int appleals_config_iio(struct appleals_device *als_dev)  
> [snip]
> > > > +	iio_dev = iio_device_alloc(sizeof(als_dev));    
> > > 
> > > how about using the devm_ variants?  
> 
> The problem is that there is no device with the proper lifetime here.
> In particular we can't use hid_device (the only struct device
> available here) because the instances of those can (and do) outlive
> the module. This is a consequence of the hid-driver demuxing: e.g.
> when this als module is unloaded, the hid_device is still in use by
> the touchbar driver, and if this als module is then loaded again it
> will get associated with that same hid_device again.
Fair enough.

> 
> [snip]
> > > > +	rc = iio_device_register(iio_dev);
> > > > +	if (rc) {
> > > > +		dev_err(als_dev->log_dev, "failed to register iio device: %d\n",
> > > > +			rc);
> > > > +		goto unreg_iio_trig;
> > > > +	}
> > > > +
> > > > +	als_dev->iio_dev = iio_dev;  
> > 
> > I really don't like nest of pointers going on in here.  I haven't dug
> > down to check if any of them can be remove, but they are definitely
> > ugly to deal with.  
> 
> I'm not sure how this can be avoided: *iio_dev is allocated by
> iio_device_alloc(), and this is just storing that pointer in our data
> structure for this als device.

You are probably correct.  Ugly will have to stay :(
This tends to happen when we end up merging various different
driver subsystems together like this.

> 
> [snip]
> > > > +static int appleals_probe(struct hid_device *hdev,
> > > > +			  const struct hid_device_id *id)
> > > > +{
> > > > +	struct appleals_device *als_dev =
> > > > +		appleib_get_drvdata(hid_get_drvdata(hdev),
> > > > +				    &appleals_hid_driver);
> > > > +	struct hid_field *state_field;
> > > > +	struct hid_field *illum_field;
> > > > +	int rc;
> > > > +
> > > > +	/* find als fields and reports */
> > > > +	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;
> > > > +
> > > > +	if (als_dev->hid_dev) {
> > > > +		dev_warn(als_dev->log_dev,
> > > > +			 "Found duplicate ambient light sensor - ignoring\n");  
> > 
> > I'll bite.  So how can this actually happen?  What fundamentally breaks in
> > your model if there really are two?  Can you fix whatever that is so
> > as to drop this handling?  
> 
> This should indeed never happen - the check is just defensive
> programming, in case either some new device in some new model appears,
> or due to some bug somewhere.
> 
> To actually handle this I'd need to split up appleals_device into a
> per iBridge-device part and a per ALS-device part, and allow multiple
> ALS-device parts. This is certainly doable, but seemed a bit overkill
> for something that is unlikely to ever be needed.

Fair enough I guess.

> 
> [snip]
> > > > +static void appleals_remove(struct hid_device *hdev)
> > > > +{
> > > > +	struct appleals_device *als_dev =
> > > > +		appleib_get_drvdata(hid_get_drvdata(hdev),
> > > > +				    &appleals_hid_driver);
> > > > +    
> > > 
> > > could be a lot less if devm_ were used?  
> 
> Agreed, but see explanation above of why this can't be used.
> 
> > > > +	if (als_dev->iio_dev) {
> > > > +		iio_device_unregister(als_dev->iio_dev);
> > > > +
> > > > +		iio_trigger_unregister(als_dev->iio_trig);
> > > > +		iio_trigger_free(als_dev->iio_trig);
> > > > +		als_dev->iio_trig = NULL;  
> > 
> > I would be decidedly worried if you actually have any paths
> > where setting these to NULL do anything useful. By the time
> > we get here we should absolutely 'know' nothing will touch
> > these pointers.  
> 
> I guess I was being overly paranoid here. I've removed these now.
> 
> > It is these that are blocking Peter's suggestion of using
> > devm to clean all of this up automatically for you.  
> 
> No, that is for a different reason - see above.
> 
> [snip]
> > > > +static int appleals_platform_probe(struct platform_device *pdev)
> > > > +{
> > > > +	struct appleib_platform_data *pdata = pdev->dev.platform_data;
> > > > +	struct appleib_device *ib_dev = pdata->ib_dev;
> > > > +	struct appleals_device *als_dev;
> > > > +	int rc;
> > > > +
> > > > +	als_dev = kzalloc(sizeof(*als_dev), GFP_KERNEL);
> > > > +	if (!als_dev)
> > > > +		return -ENOMEM;
> > > > +
> > > > +	als_dev->ib_dev = ib_dev;
> > > > +	als_dev->log_dev = pdata->log_dev;
> > > > +
> > > > +	rc = appleib_register_hid_driver(ib_dev, &appleals_hid_driver, als_dev);  
> > 
> > Hmm. This is all a bit odd.  We register a platform device, to call it's driver
> > so that it can then register another driver?  I'll let Lee comment on that but
> > it does seem overly complex.  
> 
> "Normally" this call would be to hid_register_driver().

> However, as I
> tried to explain in the cover letter and module comments, at least one
> of the hid_device's needs to be shared by both this als driver and the
> touchbar driver. But the linux device/driver architecture does not
> allow multiple drivers to be attached to a single device. Hence the
> mfd driver implements demuxing hid_driver that allows multiple
> hid_drivers to be registered for a single hid_device. And this is why
> we register our hid_driver with this demuxing driver here instead of
> directly via hid_register_driver().

I'd expect a probe to create a device managed by the driver.  The drive would normally
be registered on module load, hence _init, rather than probe.

I can't find any instances of hid_register_driver being called from a probe function.

Jonathan


> 
> Does this make sense?
> 
> 
>   Cheers,
> 
>   Ronald
> 



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

* Re: [PATCH 1/3] mfd: apple-ibridge: Add Apple iBridge MFD driver.
  2019-04-22  3:12 ` [PATCH 1/3] mfd: apple-ibridge: Add Apple iBridge MFD driver Ronald Tschalär
  2019-04-22 11:34   ` Jonathan Cameron
@ 2019-04-24 14:18   ` Benjamin Tissoires
  2019-04-25  8:19     ` Life is hard, and then you die
  2019-05-07 12:24   ` Lee Jones
  2 siblings, 1 reply; 22+ messages in thread
From: Benjamin Tissoires @ 2019-04-24 14:18 UTC (permalink / raw)
  To: Ronald Tschalär
  Cc: Jiri Kosina, Jonathan Cameron, Hartmut Knaack,
	Lars-Peter Clausen, Peter Meerwald-Stadler, Lee Jones,
	open list:HID CORE LAYER, linux-iio, lkml

On Mon, Apr 22, 2019 at 5:13 AM 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 provides a multiplexing layer that allows multiple HID drivers to
> be registered for a given HID device. This allows the touch bar and ALS
> driver to be separated out into their own modules.

Sorry for coming late to the party, but IMO this series is far too
complex for what you need.

As I read this and the first comment of drivers/mfd/apple-ibridge.c,
you need to have a HID driver that multiplex 2 other sub drivers
through one USB communication.
For that, you are using MFD, platform driver and you own sauce instead
of creating a bus.

So, how about we reuse entirely the HID subsystem which already
provides the capability you need (assuming I am correct above).
hid-logitech-dj already does the same kind of stuff and you could:
- create drivers/hid/hid-ibridge.c that handles USB_ID_PRODUCT_IBRIDGE
- hid-ibridge will then register itself to the hid subsystem with a
call to hid_hw_start(hdev, HID_CONNECT_HIDRAW) and
hid_device_io_start(hdev) to enable the events (so you don't create
useless input nodes for it)
- then you add your 2 new devices by calling hid_allocate_device() and
then hid_add_device(). You can even create a new HID group
APPLE_IBRIDGE and allocate 2 new PIDs for them to distinguish them
from the actual USB device.
- then you have 2 brand new HID devices you can create their driver as
a regular ones.

hid-ibridge.c would just need to behave like any other hid transport
driver (see logi_dj_ll_driver in drivers/hid/hid-logitech-dj.c) and
you can get rid of at least the MFD and the platform part of your
drivers.

Does it makes sense or am I missing something obvious in the middle?


I have one other comment below.

Note that I haven't read the whole series as I'd like to first get
your feedback with my comment above.

>
> Signed-off-by: Ronald Tschalär <ronald@innovation.ch>
> ---
>  drivers/mfd/Kconfig               |  15 +
>  drivers/mfd/Makefile              |   1 +
>  drivers/mfd/apple-ibridge.c       | 883 ++++++++++++++++++++++++++++++
>  include/linux/mfd/apple-ibridge.h |  39 ++
>  4 files changed, 938 insertions(+)
>  create mode 100644 drivers/mfd/apple-ibridge.c
>  create mode 100644 include/linux/mfd/apple-ibridge.h
>
> diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
> index 76f9909cf396..d55fa77faacf 100644
> --- a/drivers/mfd/Kconfig
> +++ b/drivers/mfd/Kconfig
> @@ -1916,5 +1916,20 @@ config RAVE_SP_CORE
>           Select this to get support for the Supervisory Processor
>           device found on several devices in RAVE line of hardware.
>
> +config MFD_APPLE_IBRIDGE
> +       tristate "Apple iBridge chip"
> +       depends on ACPI
> +       depends on USB_HID
> +       depends on X86 || COMPILE_TEST
> +       select MFD_CORE
> +       help
> +         This MFD provides the core support for the Apple iBridge chip
> +         found on recent MacBookPro's. 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.
> +
>  endmenu
>  endif
> diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
> index 12980a4ad460..c364e0e9d313 100644
> --- a/drivers/mfd/Makefile
> +++ b/drivers/mfd/Makefile
> @@ -241,4 +241,5 @@ obj-$(CONFIG_MFD_MXS_LRADC)     += mxs-lradc.o
>  obj-$(CONFIG_MFD_SC27XX_PMIC)  += sprd-sc27xx-spi.o
>  obj-$(CONFIG_RAVE_SP_CORE)     += rave-sp.o
>  obj-$(CONFIG_MFD_ROHM_BD718XX) += rohm-bd718x7.o
> +obj-$(CONFIG_MFD_APPLE_IBRIDGE)        += apple-ibridge.o
>
> diff --git a/drivers/mfd/apple-ibridge.c b/drivers/mfd/apple-ibridge.c
> new file mode 100644
> index 000000000000..56d325396961
> --- /dev/null
> +++ b/drivers/mfd/apple-ibridge.c
> @@ -0,0 +1,883 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Apple iBridge Driver
> + *
> + * Copyright (c) 2018 Ronald Tschalär
> + */
> +
> +/**
> + * 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 (and possibly the SEP,
> + * though at this point in time nothing is known about that). 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 an MFD driver, with the touch bar and ALS
> + * functions implemented by appropriate subdrivers (mfd cells). Because
> + * both those are basically hid drivers, but the current kernel driver
> + * structure does not allow more than one driver per device, this driver
> + * implements a demuxer for hid drivers: it registers itself as a hid
> + * driver with the core, and in turn it lets the subdrivers register
> + * themselves as hid drivers with this driver; the callbacks from the core
> + * are then forwarded to the subdrivers.
> + *
> + * Lastly, this driver also takes care of the power-management for the
> + * iBridge when suspending and resuming.
> + */
> +
> +#include <linux/acpi.h>
> +#include <linux/device.h>
> +#include <linux/hid.h>
> +#include <linux/list.h>
> +#include <linux/mfd/apple-ibridge.h>
> +#include <linux/mfd/core.h>
> +#include <linux/module.h>
> +#include <linux/mutex.h>
> +#include <linux/rculist.h>
> +#include <linux/slab.h>
> +#include <linux/srcu.h>
> +#include <linux/usb.h>
> +
> +#include "../hid/usbhid/usbhid.h"
> +
> +#define USB_ID_VENDOR_APPLE    0x05ac
> +#define USB_ID_PRODUCT_IBRIDGE 0x8600
> +
> +#define APPLETB_BASIC_CONFIG   1
> +
> +#define        LOG_DEV(ib_dev)         (&(ib_dev)->acpi_dev->dev)
> +
> +struct appleib_device {
> +       struct acpi_device      *acpi_dev;
> +       acpi_handle             asoc_socw;
> +       struct list_head        hid_drivers;
> +       struct list_head        hid_devices;
> +       struct mfd_cell         *subdevs;
> +       struct mutex            update_lock; /* protect updates to all lists */
> +       struct srcu_struct      lists_srcu;
> +       bool                    in_hid_probe;
> +};
> +
> +struct appleib_hid_drv_info {
> +       struct list_head        entry;
> +       struct hid_driver       *driver;
> +       void                    *driver_data;
> +};
> +
> +struct appleib_hid_dev_info {
> +       struct list_head                entry;
> +       struct list_head                drivers;
> +       struct hid_device               *device;
> +       const struct hid_device_id      *device_id;
> +       bool                            started;
> +};
> +
> +static const struct mfd_cell appleib_subdevs[] = {
> +       { .name = PLAT_NAME_IB_TB },
> +       { .name = PLAT_NAME_IB_ALS },
> +};
> +
> +static struct appleib_device *appleib_dev;
> +
> +#define        call_void_driver_func(drv_info, fn, ...)                        \
> +       do {                                                            \
> +               if ((drv_info)->driver->fn)                             \
> +                       (drv_info)->driver->fn(__VA_ARGS__);            \
> +       } while (0)
> +
> +#define        call_driver_func(drv_info, fn, ret_type, ...)                   \
> +       ({                                                              \
> +               ret_type rc = 0;                                        \
> +                                                                       \
> +               if ((drv_info)->driver->fn)                             \
> +                       rc = (drv_info)->driver->fn(__VA_ARGS__);       \
> +                                                                       \
> +               rc;                                                     \
> +       })
> +
> +static void appleib_remove_driver(struct appleib_device *ib_dev,
> +                                 struct appleib_hid_drv_info *drv_info,
> +                                 struct appleib_hid_dev_info *dev_info)
> +{
> +       list_del_rcu(&drv_info->entry);
> +       synchronize_srcu(&ib_dev->lists_srcu);
> +
> +       call_void_driver_func(drv_info, remove, dev_info->device);
> +
> +       kfree(drv_info);
> +}
> +
> +static int appleib_probe_driver(struct appleib_hid_drv_info *drv_info,
> +                               struct appleib_hid_dev_info *dev_info)
> +{
> +       struct appleib_hid_drv_info *d;
> +       int rc = 0;
> +
> +       rc = call_driver_func(drv_info, probe, int, dev_info->device,
> +                             dev_info->device_id);
> +       if (rc)
> +               return rc;
> +
> +       d = kmemdup(drv_info, sizeof(*drv_info), GFP_KERNEL);
> +       if (!d) {
> +               call_void_driver_func(drv_info, remove, dev_info->device);
> +               return -ENOMEM;
> +       }
> +
> +       list_add_tail_rcu(&d->entry, &dev_info->drivers);
> +       return 0;
> +}
> +
> +static void appleib_remove_driver_attachments(struct appleib_device *ib_dev,
> +                                       struct appleib_hid_dev_info *dev_info,
> +                                       struct hid_driver *driver)
> +{
> +       struct appleib_hid_drv_info *drv_info;
> +       struct appleib_hid_drv_info *tmp;
> +
> +       list_for_each_entry_safe(drv_info, tmp, &dev_info->drivers, entry) {
> +               if (!driver || drv_info->driver == driver)
> +                       appleib_remove_driver(ib_dev, drv_info, dev_info);
> +       }
> +}
> +
> +/*
> + * Find all devices that are attached to this driver and detach them.
> + *
> + * Note: this must be run with update_lock held.
> + */
> +static void appleib_detach_devices(struct appleib_device *ib_dev,
> +                                  struct hid_driver *driver)
> +{
> +       struct appleib_hid_dev_info *dev_info;
> +
> +       list_for_each_entry(dev_info, &ib_dev->hid_devices, entry)
> +               appleib_remove_driver_attachments(ib_dev, dev_info, driver);
> +}
> +
> +/*
> + * Find all drivers that are attached to this device and detach them.
> + *
> + * Note: this must be run with update_lock held.
> + */
> +static void appleib_detach_drivers(struct appleib_device *ib_dev,
> +                                  struct appleib_hid_dev_info *dev_info)
> +{
> +       appleib_remove_driver_attachments(ib_dev, dev_info, NULL);
> +}
> +
> +/**
> + * Unregister a previously registered HID driver from us.
> + * @ib_dev: the appleib_device from which to unregister the driver
> + * @driver: the driver to unregister
> + */
> +int appleib_unregister_hid_driver(struct appleib_device *ib_dev,
> +                                 struct hid_driver *driver)
> +{
> +       struct appleib_hid_drv_info *drv_info;
> +
> +       mutex_lock(&ib_dev->update_lock);
> +
> +       list_for_each_entry(drv_info, &ib_dev->hid_drivers, entry) {
> +               if (drv_info->driver == driver) {
> +                       appleib_detach_devices(ib_dev, driver);
> +                       list_del_rcu(&drv_info->entry);
> +                       mutex_unlock(&ib_dev->update_lock);
> +                       synchronize_srcu(&ib_dev->lists_srcu);
> +                       kfree(drv_info);
> +                       dev_info(LOG_DEV(ib_dev), "unregistered driver '%s'\n",
> +                                driver->name);
> +                       return 0;
> +               }
> +       }
> +
> +       mutex_unlock(&ib_dev->update_lock);
> +
> +       return -ENOENT;
> +}
> +EXPORT_SYMBOL_GPL(appleib_unregister_hid_driver);
> +
> +static int appleib_start_hid_events(struct appleib_hid_dev_info *dev_info)
> +{
> +       struct hid_device *hdev = dev_info->device;
> +       int rc;
> +
> +       rc = hid_connect(hdev, HID_CONNECT_DEFAULT);
> +       if (rc) {
> +               hid_err(hdev, "ib: hid connect failed (%d)\n", rc);
> +               return rc;
> +       }
> +
> +       rc = hid_hw_open(hdev);
> +       if (rc) {
> +               hid_err(hdev, "ib: failed to open hid: %d\n", rc);
> +               hid_disconnect(hdev);
> +       }
> +
> +       if (!rc)
> +               dev_info->started = true;
> +
> +       return rc;
> +}
> +
> +static void appleib_stop_hid_events(struct appleib_hid_dev_info *dev_info)
> +{
> +       if (dev_info->started) {
> +               hid_hw_close(dev_info->device);
> +               hid_disconnect(dev_info->device);
> +               dev_info->started = false;
> +       }
> +}
> +
> +/**
> + * Register a HID driver with us.
> + * @ib_dev: the appleib_device with which to register the driver
> + * @driver: the driver to register
> + * @data: the driver-data to associate with the driver; this is available
> + *        from appleib_get_drvdata(...).
> + */
> +int appleib_register_hid_driver(struct appleib_device *ib_dev,
> +                               struct hid_driver *driver, void *data)
> +{
> +       struct appleib_hid_drv_info *drv_info;
> +       struct appleib_hid_dev_info *dev_info;
> +       int rc;
> +
> +       if (!driver->probe)
> +               return -EINVAL;
> +
> +       drv_info = kzalloc(sizeof(*drv_info), GFP_KERNEL);
> +       if (!drv_info)
> +               return -ENOMEM;
> +
> +       drv_info->driver = driver;
> +       drv_info->driver_data = data;
> +
> +       mutex_lock(&ib_dev->update_lock);
> +
> +       list_add_tail_rcu(&drv_info->entry, &ib_dev->hid_drivers);
> +
> +       list_for_each_entry(dev_info, &ib_dev->hid_devices, entry) {
> +               appleib_stop_hid_events(dev_info);
> +
> +               appleib_probe_driver(drv_info, dev_info);
> +
> +               rc = appleib_start_hid_events(dev_info);
> +               if (rc)
> +                       appleib_detach_drivers(ib_dev, dev_info);
> +       }
> +
> +       mutex_unlock(&ib_dev->update_lock);
> +
> +       dev_info(LOG_DEV(ib_dev), "registered driver '%s'\n", driver->name);
> +
> +       return 0;
> +}
> +EXPORT_SYMBOL_GPL(appleib_register_hid_driver);
> +
> +/**
> + * Get the driver-specific data associated with the given, previously
> + * registered HID driver (provided in the appleib_register_hid_driver()
> + * call).
> + * @ib_dev: the appleib_device with which the driver was registered
> + * @driver: the driver for which to get the data
> + */
> +void *appleib_get_drvdata(struct appleib_device *ib_dev,
> +                         struct hid_driver *driver)
> +{
> +       struct appleib_hid_drv_info *drv_info;
> +       void *drv_data = NULL;
> +       int idx;
> +
> +       idx = srcu_read_lock(&ib_dev->lists_srcu);
> +
> +       list_for_each_entry_rcu(drv_info, &ib_dev->hid_drivers, entry) {
> +               if (drv_info->driver == driver) {
> +                       drv_data = drv_info->driver_data;
> +                       break;
> +               }
> +       }
> +
> +       srcu_read_unlock(&ib_dev->lists_srcu, idx);
> +
> +       return drv_data;
> +}
> +EXPORT_SYMBOL_GPL(appleib_get_drvdata);
> +
> +/*
> + * Forward a hid-driver callback to all registered sub-drivers. This is for
> + * callbacks that return a status as an int.
> + * @hdev the hid-device
> + * @forward a function that calls the callback on the given driver
> + * @args arguments for the forward function
> + */
> +static int appleib_forward_int_op(struct hid_device *hdev,
> +                                 int (*forward)(struct appleib_hid_drv_info *,
> +                                                struct hid_device *, void *),
> +                                 void *args)
> +{
> +       struct appleib_device *ib_dev = hid_get_drvdata(hdev);
> +       struct appleib_hid_dev_info *dev_info;
> +       struct appleib_hid_drv_info *drv_info;
> +       int idx;
> +       int rc = 0;
> +
> +       idx = srcu_read_lock(&ib_dev->lists_srcu);
> +
> +       list_for_each_entry_rcu(dev_info, &ib_dev->hid_devices, entry) {
> +               if (dev_info->device != hdev)
> +                       continue;
> +
> +               list_for_each_entry_rcu(drv_info, &dev_info->drivers, entry) {
> +                       rc = forward(drv_info, hdev, args);
> +                       if (rc)
> +                               break;
> +               }
> +
> +               break;
> +       }
> +
> +       srcu_read_unlock(&ib_dev->lists_srcu, idx);
> +
> +       return rc;
> +}
> +
> +struct appleib_hid_event_args {
> +       struct hid_field *field;
> +       struct hid_usage *usage;
> +       __s32 value;
> +};
> +
> +static int appleib_hid_event_fwd(struct appleib_hid_drv_info *drv_info,
> +                                struct hid_device *hdev, void *args)
> +{
> +       struct appleib_hid_event_args *evt_args = args;
> +
> +       return call_driver_func(drv_info, event, int, hdev, evt_args->field,
> +                               evt_args->usage, evt_args->value);
> +}
> +
> +static int appleib_hid_event(struct hid_device *hdev, struct hid_field *field,
> +                            struct hid_usage *usage, __s32 value)
> +{
> +       struct appleib_hid_event_args args = {
> +               .field = field,
> +               .usage = usage,
> +               .value = value,
> +       };
> +
> +       return appleib_forward_int_op(hdev, appleib_hid_event_fwd, &args);
> +}
> +
> +static __u8 *appleib_report_fixup(struct hid_device *hdev, __u8 *rdesc,
> +                                 unsigned int *rsize)
> +{
> +       /* Some fields have a size of 64 bits, which according to HID 1.11
> +        * Section 8.4 is not valid ("An item field cannot span more than 4
> +        * bytes in a report"). Furthermore, hid_field_extract() complains

this must have been fixed in 94a9992f7dbdfb28976b565af220e0c4a117144a
which is part of v5.1, so not sure you actually need the report
descriptor fixup at all.

Cheers,
Benjamin

> +        * when encountering such a field. So turn them into two 32-bit fields
> +        * instead.
> +        */
> +
> +       if (*rsize == 634 &&
> +           /* Usage Page 0xff12 (vendor defined) */
> +           rdesc[212] == 0x06 && rdesc[213] == 0x12 && rdesc[214] == 0xff &&
> +           /* Usage 0x51 */
> +           rdesc[416] == 0x09 && rdesc[417] == 0x51 &&
> +           /* report size 64 */
> +           rdesc[432] == 0x75 && rdesc[433] == 64 &&
> +           /* report count 1 */
> +           rdesc[434] == 0x95 && rdesc[435] == 1) {
> +               rdesc[433] = 32;
> +               rdesc[435] = 2;
> +               hid_dbg(hdev, "Fixed up first 64-bit field\n");
> +       }
> +
> +       if (*rsize == 634 &&
> +           /* Usage Page 0xff12 (vendor defined) */
> +           rdesc[212] == 0x06 && rdesc[213] == 0x12 && rdesc[214] == 0xff &&
> +           /* Usage 0x51 */
> +           rdesc[611] == 0x09 && rdesc[612] == 0x51 &&
> +           /* report size 64 */
> +           rdesc[627] == 0x75 && rdesc[628] == 64 &&
> +           /* report count 1 */
> +           rdesc[629] == 0x95 && rdesc[630] == 1) {
> +               rdesc[628] = 32;
> +               rdesc[630] = 2;
> +               hid_dbg(hdev, "Fixed up second 64-bit field\n");
> +       }
> +
> +       return rdesc;
> +}
> +
> +static int appleib_input_configured_fwd(struct appleib_hid_drv_info *drv_info,
> +                                       struct hid_device *hdev, void *args)
> +{
> +       return call_driver_func(drv_info, input_configured, int, hdev,
> +                               (struct hid_input *)args);
> +}
> +
> +static int appleib_input_configured(struct hid_device *hdev,
> +                                   struct hid_input *hidinput)
> +{
> +       return appleib_forward_int_op(hdev, appleib_input_configured_fwd,
> +                                     hidinput);
> +}
> +
> +#ifdef CONFIG_PM
> +static int appleib_hid_suspend_fwd(struct appleib_hid_drv_info *drv_info,
> +                                  struct hid_device *hdev, void *args)
> +{
> +       return call_driver_func(drv_info, suspend, int, hdev,
> +                               *(pm_message_t *)args);
> +}
> +
> +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 appleib_hid_drv_info *drv_info,
> +                                 struct hid_device *hdev, void *args)
> +{
> +       return call_driver_func(drv_info, resume, int, hdev);
> +}
> +
> +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 appleib_hid_drv_info *drv_info,
> +                                       struct hid_device *hdev, void *args)
> +{
> +       return call_driver_func(drv_info, reset_resume, int, hdev);
> +}
> +
> +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 */
> +
> +/**
> + * 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
> + */
> +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);
> +
> +/**
> + * 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
> + */
> +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);
> +
> +/**
> + * Return whether we're currently inside a hid_device_probe or not.
> + * @ib_dev: the appleib_device
> + */
> +bool appleib_in_hid_probe(struct appleib_device *ib_dev)
> +{
> +       return ib_dev->in_hid_probe;
> +}
> +EXPORT_SYMBOL_GPL(appleib_in_hid_probe);
> +
> +static struct appleib_hid_dev_info *
> +appleib_add_device(struct appleib_device *ib_dev, struct hid_device *hdev,
> +                  const struct hid_device_id *id)
> +{
> +       struct appleib_hid_dev_info *dev_info;
> +       struct appleib_hid_drv_info *drv_info;
> +
> +       /* allocate device-info for this device */
> +       dev_info = kzalloc(sizeof(*dev_info), GFP_KERNEL);
> +       if (!dev_info)
> +               return NULL;
> +
> +       INIT_LIST_HEAD(&dev_info->drivers);
> +       dev_info->device = hdev;
> +       dev_info->device_id = id;
> +
> +       /* notify all our sub drivers */
> +       mutex_lock(&ib_dev->update_lock);
> +
> +       ib_dev->in_hid_probe = true;
> +
> +       list_for_each_entry(drv_info, &ib_dev->hid_drivers, entry) {
> +               appleib_probe_driver(drv_info, dev_info);
> +       }
> +
> +       ib_dev->in_hid_probe = false;
> +
> +       /* remember this new device */
> +       list_add_tail_rcu(&dev_info->entry, &ib_dev->hid_devices);
> +
> +       mutex_unlock(&ib_dev->update_lock);
> +
> +       return dev_info;
> +}
> +
> +static void appleib_remove_device(struct appleib_device *ib_dev,
> +                                 struct appleib_hid_dev_info *dev_info)
> +{
> +       list_del_rcu(&dev_info->entry);
> +       synchronize_srcu(&ib_dev->lists_srcu);
> +
> +       appleib_detach_drivers(ib_dev, dev_info);
> +
> +       kfree(dev_info);
> +}
> +
> +static int appleib_hid_probe(struct hid_device *hdev,
> +                            const struct hid_device_id *id)
> +{
> +       struct appleib_device *ib_dev;
> +       struct appleib_hid_dev_info *dev_info;
> +       struct usb_device *udev;
> +       int rc;
> +
> +       /* check usb config first */
> +       udev = hid_to_usb_dev(hdev);
> +
> +       if (udev->actconfig->desc.bConfigurationValue != APPLETB_BASIC_CONFIG) {
> +               rc = usb_driver_set_configuration(udev, APPLETB_BASIC_CONFIG);
> +               return rc ? rc : -ENODEV;
> +       }
> +
> +       /* Assign the driver data */
> +       ib_dev = appleib_dev;
> +       hid_set_drvdata(hdev, ib_dev);
> +
> +       /* initialize the report info */
> +       rc = hid_parse(hdev);
> +       if (rc) {
> +               hid_err(hdev, "ib: hid parse failed (%d)\n", rc);
> +               goto error;
> +       }
> +
> +       /* alloc bufs etc so probe's can send requests; but connect later */
> +       rc = hid_hw_start(hdev, 0);
> +       if (rc) {
> +               hid_err(hdev, "ib: hw start failed (%d)\n", rc);
> +               goto error;
> +       }
> +
> +       /* add this hdev to our device list */
> +       dev_info = appleib_add_device(ib_dev, hdev, id);
> +       if (!dev_info) {
> +               rc = -ENOMEM;
> +               goto stop_hw;
> +       }
> +
> +       /* start the hid */
> +       rc = appleib_start_hid_events(dev_info);
> +       if (rc)
> +               goto remove_dev;
> +
> +       return 0;
> +
> +remove_dev:
> +       mutex_lock(&ib_dev->update_lock);
> +       appleib_remove_device(ib_dev, dev_info);
> +       mutex_unlock(&ib_dev->update_lock);
> +stop_hw:
> +       hid_hw_stop(hdev);
> +error:
> +       return rc;
> +}
> +
> +static void appleib_hid_remove(struct hid_device *hdev)
> +{
> +       struct appleib_device *ib_dev = hid_get_drvdata(hdev);
> +       struct appleib_hid_dev_info *dev_info;
> +
> +       mutex_lock(&ib_dev->update_lock);
> +
> +       list_for_each_entry(dev_info, &ib_dev->hid_devices, entry) {
> +               if (dev_info->device == hdev) {
> +                       appleib_stop_hid_events(dev_info);
> +                       appleib_remove_device(ib_dev, dev_info);
> +                       break;
> +               }
> +       }
> +
> +       mutex_unlock(&ib_dev->update_lock);
> +
> +       hid_hw_stop(hdev);
> +}
> +
> +static const struct hid_device_id appleib_hid_devices[] = {
> +       { HID_USB_DEVICE(USB_ID_VENDOR_APPLE, USB_ID_PRODUCT_IBRIDGE) },
> +       { },
> +};
> +
> +static struct hid_driver appleib_hid_driver = {
> +       .name = "apple-ibridge-hid",
> +       .id_table = appleib_hid_devices,
> +       .probe = appleib_hid_probe,
> +       .remove = appleib_hid_remove,
> +       .event = appleib_hid_event,
> +       .report_fixup = appleib_report_fixup,
> +       .input_configured = appleib_input_configured,
> +#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;
> +       int rc;
> +
> +       /* allocate */
> +       ib_dev = kzalloc(sizeof(*ib_dev), GFP_KERNEL);
> +       if (!ib_dev)
> +               return ERR_PTR(-ENOMEM);
> +
> +       /* init structures */
> +       INIT_LIST_HEAD(&ib_dev->hid_drivers);
> +       INIT_LIST_HEAD(&ib_dev->hid_devices);
> +       mutex_init(&ib_dev->update_lock);
> +       init_srcu_struct(&ib_dev->lists_srcu);
> +
> +       ib_dev->acpi_dev = acpi_dev;
> +
> +       /* get iBridge acpi power control method */
> +       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));
> +               rc = -ENXIO;
> +               goto free_mem;
> +       }
> +
> +       /* 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;
> +
> +free_mem:
> +       kfree(ib_dev);
> +       return ERR_PTR(rc);
> +}
> +
> +static int appleib_probe(struct acpi_device *acpi)
> +{
> +       struct appleib_device *ib_dev;
> +       struct appleib_platform_data *pdata;
> +       int i;
> +       int ret;
> +
> +       if (appleib_dev)
> +               return -EBUSY;
> +
> +       ib_dev = appleib_alloc_device(acpi);
> +       if (IS_ERR_OR_NULL(ib_dev))
> +               return PTR_ERR(ib_dev);
> +
> +       ib_dev->subdevs = kmemdup(appleib_subdevs, sizeof(appleib_subdevs),
> +                                 GFP_KERNEL);
> +       if (!ib_dev->subdevs) {
> +               ret = -ENOMEM;
> +               goto free_dev;
> +       }
> +
> +       pdata = kzalloc(sizeof(*pdata), GFP_KERNEL);
> +       if (!pdata) {
> +               ret = -ENOMEM;
> +               goto free_subdevs;
> +       }
> +
> +       pdata->ib_dev = ib_dev;
> +       pdata->log_dev = LOG_DEV(ib_dev);
> +       for (i = 0; i < ARRAY_SIZE(appleib_subdevs); i++) {
> +               ib_dev->subdevs[i].platform_data = pdata;
> +               ib_dev->subdevs[i].pdata_size = sizeof(*pdata);
> +       }
> +
> +       ret = mfd_add_devices(&acpi->dev, PLATFORM_DEVID_NONE,
> +                             ib_dev->subdevs, ARRAY_SIZE(appleib_subdevs),
> +                             NULL, 0, NULL);
> +       if (ret) {
> +               dev_err(LOG_DEV(ib_dev), "Error adding MFD devices: %d\n", ret);
> +               goto free_pdata;
> +       }
> +
> +       acpi->driver_data = ib_dev;
> +       appleib_dev = ib_dev;
> +
> +       ret = hid_register_driver(&appleib_hid_driver);
> +       if (ret) {
> +               dev_err(LOG_DEV(ib_dev), "Error registering hid driver: %d\n",
> +                       ret);
> +               goto rem_mfd_devs;
> +       }
> +
> +       return 0;
> +
> +rem_mfd_devs:
> +       mfd_remove_devices(&acpi->dev);
> +free_pdata:
> +       kfree(pdata);
> +free_subdevs:
> +       kfree(ib_dev->subdevs);
> +free_dev:
> +       appleib_dev = NULL;
> +       acpi->driver_data = NULL;
> +       kfree(ib_dev);
> +       return ret;
> +}
> +
> +static int appleib_remove(struct acpi_device *acpi)
> +{
> +       struct appleib_device *ib_dev = acpi_driver_data(acpi);
> +
> +       mfd_remove_devices(&acpi->dev);
> +       hid_unregister_driver(&appleib_hid_driver);
> +
> +       if (appleib_dev == ib_dev)
> +               appleib_dev = NULL;
> +
> +       kfree(ib_dev->subdevs[0].platform_data);
> +       kfree(ib_dev->subdevs);
> +       kfree(ib_dev);
> +
> +       return 0;
> +}
> +
> +static int appleib_suspend(struct device *dev)
> +{
> +       struct acpi_device *adev;
> +       struct appleib_device *ib_dev;
> +       int rc;
> +
> +       adev = to_acpi_device(dev);
> +       ib_dev = acpi_driver_data(adev);
> +
> +       rc = acpi_execute_simple_method(ib_dev->asoc_socw, NULL, 0);
> +       if (ACPI_FAILURE(rc))
> +               dev_warn(LOG_DEV(ib_dev), "SOCW(0) failed: %s\n",
> +                        acpi_format_exception(rc));
> +
> +       return 0;
> +}
> +
> +static int appleib_resume(struct device *dev)
> +{
> +       struct acpi_device *adev;
> +       struct appleib_device *ib_dev;
> +       int rc;
> +
> +       adev = to_acpi_device(dev);
> +       ib_dev = acpi_driver_data(adev);
> +
> +       rc = acpi_execute_simple_method(ib_dev->asoc_socw, NULL, 1);
> +       if (ACPI_FAILURE(rc))
> +               dev_warn(LOG_DEV(ib_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/include/linux/mfd/apple-ibridge.h b/include/linux/mfd/apple-ibridge.h
> new file mode 100644
> index 000000000000..d321714767f7
> --- /dev/null
> +++ b/include/linux/mfd/apple-ibridge.h
> @@ -0,0 +1,39 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * Apple iBridge Driver
> + *
> + * Copyright (c) 2018 Ronald Tschalär
> + */
> +
> +#ifndef __LINUX_MFD_APPLE_IBRDIGE_H
> +#define __LINUX_MFD_APPLE_IBRDIGE_H
> +
> +#include <linux/device.h>
> +#include <linux/hid.h>
> +
> +#define PLAT_NAME_IB_TB                "apple-ib-tb"
> +#define PLAT_NAME_IB_ALS       "apple-ib-als"
> +
> +struct appleib_device;
> +
> +struct appleib_platform_data {
> +       struct appleib_device *ib_dev;
> +       struct device *log_dev;
> +};
> +
> +int appleib_register_hid_driver(struct appleib_device *ib_dev,
> +                               struct hid_driver *driver, void *data);
> +int appleib_unregister_hid_driver(struct appleib_device *ib_dev,
> +                                 struct hid_driver *driver);
> +
> +void *appleib_get_drvdata(struct appleib_device *ib_dev,
> +                         struct hid_driver *driver);
> +bool appleib_in_hid_probe(struct appleib_device *ib_dev);
> +
> +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.20.1
>

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

* Re: [PATCH 1/3] mfd: apple-ibridge: Add Apple iBridge MFD driver.
  2019-04-24 10:47     ` Life is hard, and then you die
@ 2019-04-24 19:13       ` Jonathan Cameron
  2019-04-26  5:34         ` Life is hard, and then you die
  0 siblings, 1 reply; 22+ messages in thread
From: Jonathan Cameron @ 2019-04-24 19:13 UTC (permalink / raw)
  To: Life is hard, and then you die
  Cc: Jiri Kosina, Benjamin Tissoires, Hartmut Knaack,
	Lars-Peter Clausen, Peter Meerwald-Stadler, Lee Jones,
	linux-input, linux-iio, linux-kernel

On Wed, 24 Apr 2019 03:47:18 -0700
"Life is hard, and then you die" <ronald@innovation.ch> wrote:

>   Hi Jonathan,
> 
> On Mon, Apr 22, 2019 at 12:34:26PM +0100, Jonathan Cameron wrote:
> > On Sun, 21 Apr 2019 20:12:49 -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 provides a multiplexing layer that allows multiple HID drivers to
> > > be registered for a given HID device. This allows the touch bar and ALS
> > > driver to be separated out into their own modules.
> > > 
> > > Signed-off-by: Ronald Tschalär <ronald@innovation.ch  
> > Hi Ronald,
> > 
> > I've only taken a fairly superficial look at this.  A few global
> > things to note though.  
> 
> Thanks for this review.
> 
> > 1. Please either use kernel-doc style for function descriptions, or
> >    do not.  Right now you are sort of half way there.  
> 
> Apologies, on re-reading the docs I realize what you mean here. Should
> be fixed now (next rev).
> 
> > 2. There is quite a complex nest of separate structures being allocated,
> >    so think about whether they can be simplified.  In particular
> >    use of container_of macros can allow a lot of forwards and backwards
> >    pointers to be dropped if you embed the various structures directly.  
> 
> Done (see also below).
> 
> [snip]
> > > +#define	call_void_driver_func(drv_info, fn, ...)			\  
> > 
> > This sort of macro may seem like a good idea because it saves a few lines
> > of code.  However, that comes at the cost of readability, so just
> > put the code inline.
> >   
> > > +	do {								\
> > > +		if ((drv_info)->driver->fn)				\
> > > +			(drv_info)->driver->fn(__VA_ARGS__);		\
> > > +	} while (0)
> > > +
> > > +#define	call_driver_func(drv_info, fn, ret_type, ...)			\
> > > +	({								\
> > > +		ret_type rc = 0;					\
> > > +									\
> > > +		if ((drv_info)->driver->fn)				\
> > > +			rc = (drv_info)->driver->fn(__VA_ARGS__);	\
> > > +									\
> > > +		rc;							\
> > > +	})  
> 
> Just to clarify, you're only talking about removing/inlining the
> call_void_driver_func() macro, not the call_driver_func() macro,
> right?

Both please. Neither adds much.

> 
> [snip]
> > > +static struct appleib_hid_dev_info *
> > > +appleib_add_device(struct appleib_device *ib_dev, struct hid_device *hdev,
> > > +		   const struct hid_device_id *id)
> > > +{
> > > +	struct appleib_hid_dev_info *dev_info;
> > > +	struct appleib_hid_drv_info *drv_info;
> > > +
> > > +	/* allocate device-info for this device */
> > > +	dev_info = kzalloc(sizeof(*dev_info), GFP_KERNEL);
> > > +	if (!dev_info)
> > > +		return NULL;
> > > +
> > > +	INIT_LIST_HEAD(&dev_info->drivers);
> > > +	dev_info->device = hdev;
> > > +	dev_info->device_id = id;
> > > +
> > > +	/* notify all our sub drivers */
> > > +	mutex_lock(&ib_dev->update_lock);
> > > +  
> > This is interesting. I'd like to see a comment here on what
> > this flag is going to do.   
> 
> I'm not sure I follow: update_lock is simply a mutex protecting all
> driver and device update (i.e. add/remove) functions. Are you
> therefore looking for something like:

That ended up in the wrong place...
It was the in_hid_probe just after here that I was referring to.

> 
> 	/* protect driver and device lists against concurrent updates */
> 	mutex_lock(&ib_dev->update_lock);
> 
> [snip]
> > > +static int appleib_probe(struct acpi_device *acpi)
> > > +{
> > > +	struct appleib_device *ib_dev;
> > > +	struct appleib_platform_data *pdata;  
> > Platform_data has a lot of historical meaning in Linux.
> > Also you have things in here that are not platform related
> > at all, such as the dev pointer.  Hence I would rename it
> > as device_data or private or something like that.  
> 
> Ok. I guess I called in platform_data because it's stored in the mfd
> cell's "platform_data" field. Anyway, changed it per your suggestion.
> 
> > > +	int i;
> > > +	int ret;
> > > +
> > > +	if (appleib_dev)  
> > This singleton bothers me a bit. I'm really not sure why it
> > is necessary.  You can just put a pointer to this in
> > the pdata for the subdevs and I think that covers most of your
> > usecases.  It's generally a bad idea to limit things to one instance
> > of a device unless that actually major simplifications.
> > I'm not seeing them here.  
> 
> Yes, this one is quite ugly. appleib_dev is static so that
> appleib_hid_probe() can find it. I could not find any other way to
> pass the appleib_dev instance to that probe function.
> 
> However, on looking at this again, I realized that hid_device_id has
> a driver_data field which can be used for this; so if I added the
> hid_driver and hid_device_id structs to the appleib_device (instead of
> making them static like now) I could fill in the driver_data and avoid
> this hack. This looks much cleaner.
> 
> Thanks for pointing this uglyness out again.
> 
> [snip]
> > > +	if (!ib_dev->subdevs) {
> > > +		ret = -ENOMEM;
> > > +		goto free_dev;
> > > +	}
> > > +
> > > +	pdata = kzalloc(sizeof(*pdata), GFP_KERNEL);  
> > 
> > Might as well embed this in ib_dev as well.  
> 
> Agreed.
> 
> > That would let
> > you used container_of to avoid having to carry the ib_dev pointer
> > around in side pdata.  
> 
> I see. I guess my main reservation is that the functions exported to
> the sub-drivers would now take a 'struct appleib_device_data *'
> argument instead of a 'struct appleib_device *', which just seems a
> bit unnatural. E.g.
> 
>   int appleib_register_hid_driver(struct appleib_device_data *ib_ddata,
>                                   struct hid_driver *driver, void *data);
> 
> instead of (the current)
> 
>   int appleib_register_hid_driver(struct appleib_device *ib_dev,
>                                   struct hid_driver *driver, void *data)

I'm not totally sure I can see why.  You can go from backwards and forwards
from any of the pointers...

> 
> [snip]
> > > +	ret = mfd_add_devices(&acpi->dev, PLATFORM_DEVID_NONE,
> > > +			      ib_dev->subdevs, ARRAY_SIZE(appleib_subdevs),
> > > +			      NULL, 0, NULL);
> > > +	if (ret) {
> > > +		dev_err(LOG_DEV(ib_dev), "Error adding MFD devices: %d\n", ret);
> > > +		goto free_pdata;
> > > +	}
> > > +
> > > +	acpi->driver_data = ib_dev;
> > > +	appleib_dev = ib_dev;
> > > +
> > > +	ret = hid_register_driver(&appleib_hid_driver);
> > > +	if (ret) {
> > > +		dev_err(LOG_DEV(ib_dev), "Error registering hid driver: %d\n",
> > > +			ret);
> > > +		goto rem_mfd_devs;
> > > +	}
> > > +
> > > +	return 0;
> > > +
> > > +rem_mfd_devs:
> > > +	mfd_remove_devices(&acpi->dev);
> > > +free_pdata:
> > > +	kfree(pdata);
> > > +free_subdevs:
> > > +	kfree(ib_dev->subdevs);
> > > +free_dev:
> > > +	appleib_dev = NULL;
> > > +	acpi->driver_data = NULL;  
> > Why at this point?  It's not set to anything until much later in the
> > probe flow.  
> 
> If the hid_register_driver() call fails, we get here after driver_data
> has been assigned. However, looking at this again, acpi->driver_data
> is only used by the remove, suspend, and resume callbacks, and those
> will not be called until a successful return from probe; therefore I
> can safely move the setting of driver_data to after the
> hid_register_driver() call and avoid having to set it to NULL in the
> error cleanup.
> 
> > May be worth thinking about devm_ managed allocations
> > to cleanup some of these allocations automatically and simplify
> > the error handling.  
> 
> Good point, thanks.
> 
> [snip]
> > > +
> > > +	rc = acpi_execute_simple_method(ib_dev->asoc_socw, NULL, 0);
> > > +	if (ACPI_FAILURE(rc))
> > > +		dev_warn(LOG_DEV(ib_dev), "SOCW(0) failed: %s\n",  
> > 
> > I can sort of see you might want to do the LOG_DEV for consistency
> > but here I'm fairly sure it's just dev which might be clearer.  
> 
> Sorry, you mean rename the macro LOG_DEV() to just DEV()?
No, just dev_warn(dev,....)

It's the same one I think at this particular location.
> 
> 
>   Cheers,
> 
>   Ronald
> 


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

* Re: [PATCH 1/3] mfd: apple-ibridge: Add Apple iBridge MFD driver.
  2019-04-24 14:18   ` Benjamin Tissoires
@ 2019-04-25  8:19     ` Life is hard, and then you die
  2019-04-25  9:39       ` Benjamin Tissoires
  0 siblings, 1 reply; 22+ messages in thread
From: Life is hard, and then you die @ 2019-04-25  8:19 UTC (permalink / raw)
  To: Benjamin Tissoires
  Cc: Jiri Kosina, Jonathan Cameron, Hartmut Knaack,
	Lars-Peter Clausen, Peter Meerwald-Stadler, Lee Jones,
	open list:HID CORE LAYER, linux-iio, lkml


  Hi Benjamin,

Thank you for looking at this.

On Wed, Apr 24, 2019 at 04:18:23PM +0200, Benjamin Tissoires wrote:
> On Mon, Apr 22, 2019 at 5:13 AM 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 provides a multiplexing layer that allows multiple HID drivers to
> > be registered for a given HID device. This allows the touch bar and ALS
> > driver to be separated out into their own modules.
>
> Sorry for coming late to the party, but IMO this series is far too
> complex for what you need.
> 
> As I read this and the first comment of drivers/mfd/apple-ibridge.c,
> you need to have a HID driver that multiplex 2 other sub drivers
> through one USB communication.
> For that, you are using MFD, platform driver and you own sauce instead
> of creating a bus.

Basically correct. To be a bit more precise, there are currently two
hid-devices and two drivers (touchbar and als) involved, with
connections as follows (pardon the ugly ascii art):

  hdev1  ---  tb-drv
           /
          /
         /
  hdev2  ---  als-drv

i.e. the touchbar driver talks to both hdev's, and hdev2's events
(reports) are processed by both drivers (though each handles different
reports).

> So, how about we reuse entirely the HID subsystem which already
> provides the capability you need (assuming I am correct above).
> hid-logitech-dj already does the same kind of stuff and you could:
> - create drivers/hid/hid-ibridge.c that handles USB_ID_PRODUCT_IBRIDGE
> - hid-ibridge will then register itself to the hid subsystem with a
> call to hid_hw_start(hdev, HID_CONNECT_HIDRAW) and
> hid_device_io_start(hdev) to enable the events (so you don't create
> useless input nodes for it)
> - then you add your 2 new devices by calling hid_allocate_device() and
> then hid_add_device(). You can even create a new HID group
> APPLE_IBRIDGE and allocate 2 new PIDs for them to distinguish them
> from the actual USB device.
> - then you have 2 brand new HID devices you can create their driver as
> a regular ones.
> 
> hid-ibridge.c would just need to behave like any other hid transport
> driver (see logi_dj_ll_driver in drivers/hid/hid-logitech-dj.c) and
> you can get rid of at least the MFD and the platform part of your
> drivers.
> 
> Does it makes sense or am I missing something obvious in the middle?

Yes, I think I understand, and I think this can work. Basically,
instead of demux'ing at the hid-driver level as I am doing now (i.e.
the iBridge hid-driver forwarding calls to the sub-hid-drivers), we
demux at the hid-device level (events forwarded from iBridge hdev to
all "virtual" sub-hdev's, and requests from sub-hdev's forwarded to
the original hdev via an iBridge ll_driver attached to the
sub-hdev's).

So I would need to create 3 new "virtual" hid-devices (instances) as
follows:

  hdev1  ---  vhdev1  ---  tb-drv
                        /
          --  vhdev2  --
         /
  hdev2  ---  vhdev3  ---  als-drv

(vhdev1 is probably not strictly necessary, but makes things more
consistent).

> I have one other comment below.
> 
> Note that I haven't read the whole series as I'd like to first get
> your feedback with my comment above.

Agreed: let's first get the overall strategy stabilized (also so I
can avoid having to rewrite the code too many more times ;-) ).

[snip]
> > +static __u8 *appleib_report_fixup(struct hid_device *hdev, __u8 *rdesc,
> > +                                 unsigned int *rsize)
> > +{
> > +       /* Some fields have a size of 64 bits, which according to HID 1.11
> > +        * Section 8.4 is not valid ("An item field cannot span more than 4
> > +        * bytes in a report"). Furthermore, hid_field_extract() complains
> 
> this must have been fixed in 94a9992f7dbdfb28976b565af220e0c4a117144a
> which is part of v5.1, so not sure you actually need the report
> descriptor fixup at all.

Wasn't aware of this change - thanks. Yes, with that the warning
message should be gone and this fixup can be avoided.

One thing I find strange, though, is that while 94a9992f7dbd changes
the condition at which the warning is emitted, it still truncates the
value to 32 bits, albeit completely silently now for lengths between
32 and 256 bits. I.e. I'm somewhat surprised that hid_field_extract()
(and __extract() ) weren't updated to actually return the full values
for longer fields. Either that, or the callers of hid_field_extract()
changed to read longer fields in 32 bit chunks.


  Cheers,

  Ronald


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

* Re: [PATCH 1/3] mfd: apple-ibridge: Add Apple iBridge MFD driver.
  2019-04-25  8:19     ` Life is hard, and then you die
@ 2019-04-25  9:39       ` Benjamin Tissoires
  2019-04-26  5:56         ` Life is hard, and then you die
  0 siblings, 1 reply; 22+ messages in thread
From: Benjamin Tissoires @ 2019-04-25  9:39 UTC (permalink / raw)
  To: Life is hard, and then you die
  Cc: Jiri Kosina, Jonathan Cameron, Hartmut Knaack,
	Lars-Peter Clausen, Peter Meerwald-Stadler, Lee Jones,
	open list:HID CORE LAYER, linux-iio, lkml

On Thu, Apr 25, 2019 at 10:19 AM Life is hard, and then you die
<ronald@innovation.ch> wrote:
>
>
>   Hi Benjamin,
>
> Thank you for looking at this.
>
> On Wed, Apr 24, 2019 at 04:18:23PM +0200, Benjamin Tissoires wrote:
> > On Mon, Apr 22, 2019 at 5:13 AM 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 provides a multiplexing layer that allows multiple HID drivers to
> > > be registered for a given HID device. This allows the touch bar and ALS
> > > driver to be separated out into their own modules.
> >
> > Sorry for coming late to the party, but IMO this series is far too
> > complex for what you need.
> >
> > As I read this and the first comment of drivers/mfd/apple-ibridge.c,
> > you need to have a HID driver that multiplex 2 other sub drivers
> > through one USB communication.
> > For that, you are using MFD, platform driver and you own sauce instead
> > of creating a bus.
>
> Basically correct. To be a bit more precise, there are currently two
> hid-devices and two drivers (touchbar and als) involved, with
> connections as follows (pardon the ugly ascii art):
>
>   hdev1  ---  tb-drv
>            /
>           /
>          /
>   hdev2  ---  als-drv
>
> i.e. the touchbar driver talks to both hdev's, and hdev2's events
> (reports) are processed by both drivers (though each handles different
> reports).
>
> > So, how about we reuse entirely the HID subsystem which already
> > provides the capability you need (assuming I am correct above).
> > hid-logitech-dj already does the same kind of stuff and you could:
> > - create drivers/hid/hid-ibridge.c that handles USB_ID_PRODUCT_IBRIDGE
> > - hid-ibridge will then register itself to the hid subsystem with a
> > call to hid_hw_start(hdev, HID_CONNECT_HIDRAW) and
> > hid_device_io_start(hdev) to enable the events (so you don't create
> > useless input nodes for it)
> > - then you add your 2 new devices by calling hid_allocate_device() and
> > then hid_add_device(). You can even create a new HID group
> > APPLE_IBRIDGE and allocate 2 new PIDs for them to distinguish them
> > from the actual USB device.
> > - then you have 2 brand new HID devices you can create their driver as
> > a regular ones.
> >
> > hid-ibridge.c would just need to behave like any other hid transport
> > driver (see logi_dj_ll_driver in drivers/hid/hid-logitech-dj.c) and
> > you can get rid of at least the MFD and the platform part of your
> > drivers.
> >
> > Does it makes sense or am I missing something obvious in the middle?
>
> Yes, I think I understand, and I think this can work. Basically,
> instead of demux'ing at the hid-driver level as I am doing now (i.e.
> the iBridge hid-driver forwarding calls to the sub-hid-drivers), we
> demux at the hid-device level (events forwarded from iBridge hdev to
> all "virtual" sub-hdev's, and requests from sub-hdev's forwarded to
> the original hdev via an iBridge ll_driver attached to the
> sub-hdev's).
>
> So I would need to create 3 new "virtual" hid-devices (instances) as
> follows:
>
>   hdev1  ---  vhdev1  ---  tb-drv
>                         /
>           --  vhdev2  --
>          /
>   hdev2  ---  vhdev3  ---  als-drv
>
> (vhdev1 is probably not strictly necessary, but makes things more
> consistent).

Oh, ok.

How about the following:

hdev1 and hdev2 are merged together in hid-apple-ibridge.c, and then
this driver creates 2 virtual hid drivers that are consistent

like

hdev1---ibridge-drv---vhdev1---tb-drv
hdev2--/           \--vhdev2---als-drv
>
> > I have one other comment below.
> >
> > Note that I haven't read the whole series as I'd like to first get
> > your feedback with my comment above.
>
> Agreed: let's first get the overall strategy stabilized (also so I
> can avoid having to rewrite the code too many more times ;-) ).
>
> [snip]
> > > +static __u8 *appleib_report_fixup(struct hid_device *hdev, __u8 *rdesc,
> > > +                                 unsigned int *rsize)
> > > +{
> > > +       /* Some fields have a size of 64 bits, which according to HID 1.11
> > > +        * Section 8.4 is not valid ("An item field cannot span more than 4
> > > +        * bytes in a report"). Furthermore, hid_field_extract() complains
> >
> > this must have been fixed in 94a9992f7dbdfb28976b565af220e0c4a117144a
> > which is part of v5.1, so not sure you actually need the report
> > descriptor fixup at all.
>
> Wasn't aware of this change - thanks. Yes, with that the warning
> message should be gone and this fixup can be avoided.
>
> One thing I find strange, though, is that while 94a9992f7dbd changes
> the condition at which the warning is emitted, it still truncates the
> value to 32 bits, albeit completely silently now for lengths between
> 32 and 256 bits. I.e. I'm somewhat surprised that hid_field_extract()
> (and __extract() ) weren't updated to actually return the full values
> for longer fields. Either that, or the callers of hid_field_extract()
> changed to read longer fields in 32 bit chunks.

facepalm.

Yep this commit is just hiding the dust under the carpet. Let's raise
that and request a revert :/

Cheers,
Benjamin

>
>
>   Cheers,
>
>   Ronald
>

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

* Re: [PATCH 1/3] mfd: apple-ibridge: Add Apple iBridge MFD driver.
  2019-04-24 19:13       ` Jonathan Cameron
@ 2019-04-26  5:34         ` Life is hard, and then you die
  0 siblings, 0 replies; 22+ messages in thread
From: Life is hard, and then you die @ 2019-04-26  5:34 UTC (permalink / raw)
  To: Jonathan Cameron
  Cc: Jiri Kosina, Benjamin Tissoires, Hartmut Knaack,
	Lars-Peter Clausen, Peter Meerwald-Stadler, Lee Jones,
	linux-input, linux-iio, linux-kernel


  Hi Jonathan,

On Wed, Apr 24, 2019 at 08:13:17PM +0100, Jonathan Cameron wrote:
> On Wed, 24 Apr 2019 03:47:18 -0700
> "Life is hard, and then you die" <ronald@innovation.ch> wrote:
> 
> >   Hi Jonathan,
> > 
> > On Mon, Apr 22, 2019 at 12:34:26PM +0100, Jonathan Cameron wrote:
> > > On Sun, 21 Apr 2019 20:12:49 -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 provides a multiplexing layer that allows multiple HID drivers to
> > > > be registered for a given HID device. This allows the touch bar and ALS
> > > > driver to be separated out into their own modules.
> > > > 
> > > > Signed-off-by: Ronald Tschalär <ronald@innovation.ch  
> > > Hi Ronald,
> > > 
> > > I've only taken a fairly superficial look at this.  A few global
> > > things to note though.  
> > 
> > Thanks for this review.
[snip]

I've applied all your feedback in my tree, but it now looks like this
module is going to be redone differently. I'll try to keep all your
comments in mind during the rewrite, though, so they're not wasted.


  Cheers,

  Ronald


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

* Re: [PATCH 1/3] mfd: apple-ibridge: Add Apple iBridge MFD driver.
  2019-04-25  9:39       ` Benjamin Tissoires
@ 2019-04-26  5:56         ` Life is hard, and then you die
  2019-04-26  6:26           ` Benjamin Tissoires
  0 siblings, 1 reply; 22+ messages in thread
From: Life is hard, and then you die @ 2019-04-26  5:56 UTC (permalink / raw)
  To: Benjamin Tissoires
  Cc: Jiri Kosina, Jonathan Cameron, Hartmut Knaack,
	Lars-Peter Clausen, Peter Meerwald-Stadler, Lee Jones,
	open list:HID CORE LAYER, linux-iio, lkml


  Hi Benjamin,

On Thu, Apr 25, 2019 at 11:39:12AM +0200, Benjamin Tissoires wrote:
> On Thu, Apr 25, 2019 at 10:19 AM Life is hard, and then you die
> <ronald@innovation.ch> wrote:
> >
> >   Hi Benjamin,
> >
> > Thank you for looking at this.
> >
> > On Wed, Apr 24, 2019 at 04:18:23PM +0200, Benjamin Tissoires wrote:
> > > On Mon, Apr 22, 2019 at 5:13 AM 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 provides a multiplexing layer that allows multiple HID drivers to
> > > > be registered for a given HID device. This allows the touch bar and ALS
> > > > driver to be separated out into their own modules.
> > >
> > > Sorry for coming late to the party, but IMO this series is far too
> > > complex for what you need.
> > >
> > > As I read this and the first comment of drivers/mfd/apple-ibridge.c,
> > > you need to have a HID driver that multiplex 2 other sub drivers
> > > through one USB communication.
> > > For that, you are using MFD, platform driver and you own sauce instead
> > > of creating a bus.
> >
> > Basically correct. To be a bit more precise, there are currently two
> > hid-devices and two drivers (touchbar and als) involved, with
> > connections as follows (pardon the ugly ascii art):
> >
> >   hdev1  ---  tb-drv
> >            /
> >           /
> >          /
> >   hdev2  ---  als-drv
> >
> > i.e. the touchbar driver talks to both hdev's, and hdev2's events
> > (reports) are processed by both drivers (though each handles different
> > reports).
> >
> > > So, how about we reuse entirely the HID subsystem which already
> > > provides the capability you need (assuming I am correct above).
> > > hid-logitech-dj already does the same kind of stuff and you could:
> > > - create drivers/hid/hid-ibridge.c that handles USB_ID_PRODUCT_IBRIDGE
> > > - hid-ibridge will then register itself to the hid subsystem with a
> > > call to hid_hw_start(hdev, HID_CONNECT_HIDRAW) and
> > > hid_device_io_start(hdev) to enable the events (so you don't create
> > > useless input nodes for it)
> > > - then you add your 2 new devices by calling hid_allocate_device() and
> > > then hid_add_device(). You can even create a new HID group
> > > APPLE_IBRIDGE and allocate 2 new PIDs for them to distinguish them
> > > from the actual USB device.
> > > - then you have 2 brand new HID devices you can create their driver as
> > > a regular ones.
> > >
> > > hid-ibridge.c would just need to behave like any other hid transport
> > > driver (see logi_dj_ll_driver in drivers/hid/hid-logitech-dj.c) and
> > > you can get rid of at least the MFD and the platform part of your
> > > drivers.
> > >
> > > Does it makes sense or am I missing something obvious in the middle?
> >
> > Yes, I think I understand, and I think this can work. Basically,
> > instead of demux'ing at the hid-driver level as I am doing now (i.e.
> > the iBridge hid-driver forwarding calls to the sub-hid-drivers), we
> > demux at the hid-device level (events forwarded from iBridge hdev to
> > all "virtual" sub-hdev's, and requests from sub-hdev's forwarded to
> > the original hdev via an iBridge ll_driver attached to the
> > sub-hdev's).
> >
> > So I would need to create 3 new "virtual" hid-devices (instances) as
> > follows:
> >
> >   hdev1  ---  vhdev1  ---  tb-drv
> >                         /
> >           --  vhdev2  --
> >          /
> >   hdev2  ---  vhdev3  ---  als-drv
> >
> > (vhdev1 is probably not strictly necessary, but makes things more
> > consistent).
> 
> Oh, ok.
> 
> How about the following:
> 
> hdev1 and hdev2 are merged together in hid-apple-ibridge.c, and then
> this driver creates 2 virtual hid drivers that are consistent
> 
> like
> 
> hdev1---ibridge-drv---vhdev1---tb-drv
> hdev2--/           \--vhdev2---als-drv

I don't think this will work. The problem is when the sub-drivers need
to send a report or usb-command: how to they specify which hdev the
report/command is destined for? While we could store the original hdev
in each report (the hid_report's device field), that only works for
hid_hw_request(), but not for things like hid_hw_raw_request() or
hid_hw_output_report(). Now, currently I don't use the latter two; but
I do need to send raw usb control messages in the touchbar driver
(some commands are not proper hid reports), so it definitely breaks
down there.

Or am I missing something?


  Cheers,

  Ronald


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

* Re: [PATCH 1/3] mfd: apple-ibridge: Add Apple iBridge MFD driver.
  2019-04-26  5:56         ` Life is hard, and then you die
@ 2019-04-26  6:26           ` Benjamin Tissoires
  2019-06-10  9:19             ` Life is hard, and then you die
  0 siblings, 1 reply; 22+ messages in thread
From: Benjamin Tissoires @ 2019-04-26  6:26 UTC (permalink / raw)
  To: Life is hard, and then you die
  Cc: Jiri Kosina, Jonathan Cameron, Hartmut Knaack,
	Lars-Peter Clausen, Peter Meerwald-Stadler, Lee Jones,
	open list:HID CORE LAYER, linux-iio, lkml

On Fri, Apr 26, 2019 at 7:56 AM Life is hard, and then you die
<ronald@innovation.ch> wrote:
>
>
>   Hi Benjamin,
>
> On Thu, Apr 25, 2019 at 11:39:12AM +0200, Benjamin Tissoires wrote:
> > On Thu, Apr 25, 2019 at 10:19 AM Life is hard, and then you die
> > <ronald@innovation.ch> wrote:
> > >
> > >   Hi Benjamin,
> > >
> > > Thank you for looking at this.
> > >
> > > On Wed, Apr 24, 2019 at 04:18:23PM +0200, Benjamin Tissoires wrote:
> > > > On Mon, Apr 22, 2019 at 5:13 AM 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 provides a multiplexing layer that allows multiple HID drivers to
> > > > > be registered for a given HID device. This allows the touch bar and ALS
> > > > > driver to be separated out into their own modules.
> > > >
> > > > Sorry for coming late to the party, but IMO this series is far too
> > > > complex for what you need.
> > > >
> > > > As I read this and the first comment of drivers/mfd/apple-ibridge.c,
> > > > you need to have a HID driver that multiplex 2 other sub drivers
> > > > through one USB communication.
> > > > For that, you are using MFD, platform driver and you own sauce instead
> > > > of creating a bus.
> > >
> > > Basically correct. To be a bit more precise, there are currently two
> > > hid-devices and two drivers (touchbar and als) involved, with
> > > connections as follows (pardon the ugly ascii art):
> > >
> > >   hdev1  ---  tb-drv
> > >            /
> > >           /
> > >          /
> > >   hdev2  ---  als-drv
> > >
> > > i.e. the touchbar driver talks to both hdev's, and hdev2's events
> > > (reports) are processed by both drivers (though each handles different
> > > reports).
> > >
> > > > So, how about we reuse entirely the HID subsystem which already
> > > > provides the capability you need (assuming I am correct above).
> > > > hid-logitech-dj already does the same kind of stuff and you could:
> > > > - create drivers/hid/hid-ibridge.c that handles USB_ID_PRODUCT_IBRIDGE
> > > > - hid-ibridge will then register itself to the hid subsystem with a
> > > > call to hid_hw_start(hdev, HID_CONNECT_HIDRAW) and
> > > > hid_device_io_start(hdev) to enable the events (so you don't create
> > > > useless input nodes for it)
> > > > - then you add your 2 new devices by calling hid_allocate_device() and
> > > > then hid_add_device(). You can even create a new HID group
> > > > APPLE_IBRIDGE and allocate 2 new PIDs for them to distinguish them
> > > > from the actual USB device.
> > > > - then you have 2 brand new HID devices you can create their driver as
> > > > a regular ones.
> > > >
> > > > hid-ibridge.c would just need to behave like any other hid transport
> > > > driver (see logi_dj_ll_driver in drivers/hid/hid-logitech-dj.c) and
> > > > you can get rid of at least the MFD and the platform part of your
> > > > drivers.
> > > >
> > > > Does it makes sense or am I missing something obvious in the middle?
> > >
> > > Yes, I think I understand, and I think this can work. Basically,
> > > instead of demux'ing at the hid-driver level as I am doing now (i.e.
> > > the iBridge hid-driver forwarding calls to the sub-hid-drivers), we
> > > demux at the hid-device level (events forwarded from iBridge hdev to
> > > all "virtual" sub-hdev's, and requests from sub-hdev's forwarded to
> > > the original hdev via an iBridge ll_driver attached to the
> > > sub-hdev's).
> > >
> > > So I would need to create 3 new "virtual" hid-devices (instances) as
> > > follows:
> > >
> > >   hdev1  ---  vhdev1  ---  tb-drv
> > >                         /
> > >           --  vhdev2  --
> > >          /
> > >   hdev2  ---  vhdev3  ---  als-drv
> > >
> > > (vhdev1 is probably not strictly necessary, but makes things more
> > > consistent).
> >
> > Oh, ok.
> >
> > How about the following:
> >
> > hdev1 and hdev2 are merged together in hid-apple-ibridge.c, and then
> > this driver creates 2 virtual hid drivers that are consistent
> >
> > like
> >
> > hdev1---ibridge-drv---vhdev1---tb-drv
> > hdev2--/           \--vhdev2---als-drv
>
> I don't think this will work. The problem is when the sub-drivers need
> to send a report or usb-command: how to they specify which hdev the
> report/command is destined for? While we could store the original hdev
> in each report (the hid_report's device field), that only works for
> hid_hw_request(), but not for things like hid_hw_raw_request() or
> hid_hw_output_report(). Now, currently I don't use the latter two; but
> I do need to send raw usb control messages in the touchbar driver
> (some commands are not proper hid reports), so it definitely breaks
> down there.
>
> Or am I missing something?
>

I'd need to have a deeper look at the protocol, but you can emulate
pure HID devices by having your ibridge handling a translation from
set/get features/input to the usb control messages. Likewise, nothing
prevents you to slightly rewrite the report descriptors you present to
the als and touchbar to have a clear separation with the report ID.

For example, if both hdev1 and hdev2 use a report ID of 0x01, you
could rewrite the report descriptor so that when you receive a report
with an id of 0x01 you send this to hdev1, but you can also translate
0x11 to a report ID 0x01 to hdev2.
Likewise, report ID 0x42 could be a raw USB control message to the USB
under hdev2.

Note that you will have to write 2 report descriptors for your new
devices, but you can take what makes sense from the original ones, and
just add a new collection with a vendor application with with an
opaque meaning (for the USB control messages).

Cheers,
Benjamin

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

* Re: [PATCH 1/3] mfd: apple-ibridge: Add Apple iBridge MFD driver.
  2019-04-22  3:12 ` [PATCH 1/3] mfd: apple-ibridge: Add Apple iBridge MFD driver Ronald Tschalär
  2019-04-22 11:34   ` Jonathan Cameron
  2019-04-24 14:18   ` Benjamin Tissoires
@ 2019-05-07 12:24   ` Lee Jones
  2019-06-09 23:49     ` Life is hard, and then you die
  2 siblings, 1 reply; 22+ messages in thread
From: Lee Jones @ 2019-05-07 12:24 UTC (permalink / raw)
  To: Ronald Tschalär
  Cc: Jiri Kosina, Benjamin Tissoires, Jonathan Cameron,
	Hartmut Knaack, Lars-Peter Clausen, Peter Meerwald-Stadler,
	linux-input, linux-iio, linux-kernel

On Sun, 21 Apr 2019, Ronald Tschalär 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 provides a multiplexing layer that allows multiple HID drivers to
> be registered for a given HID device. This allows the touch bar and ALS
> driver to be separated out into their own modules.
> 
> Signed-off-by: Ronald Tschalär <ronald@innovation.ch>
> ---
>  drivers/mfd/Kconfig               |  15 +
>  drivers/mfd/Makefile              |   1 +
>  drivers/mfd/apple-ibridge.c       | 883 ++++++++++++++++++++++++++++++

I haven't taken a thorough look through, but I can tell you that the
vast majority of what you're trying to do here does not belong in
MFD.  MFD drivers are used to register child devices.  Almost all
functionality or 'real work' should be contained in the drivers the
MFD registers, not in the MFD parent itself.  You will need to move
all 'real work' out into the subordinate device drivers for
acceptance.

>  include/linux/mfd/apple-ibridge.h |  39 ++
>  4 files changed, 938 insertions(+)
>  create mode 100644 drivers/mfd/apple-ibridge.c
>  create mode 100644 include/linux/mfd/apple-ibridge.h

-- 
Lee Jones [李琼斯]
Linaro Services Technical Lead
Linaro.org │ Open source software for ARM SoCs
Follow Linaro: Facebook | Twitter | Blog

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

* Re: [PATCH 1/3] mfd: apple-ibridge: Add Apple iBridge MFD driver.
  2019-05-07 12:24   ` Lee Jones
@ 2019-06-09 23:49     ` Life is hard, and then you die
  2019-06-10  5:45       ` Lee Jones
  0 siblings, 1 reply; 22+ messages in thread
From: Life is hard, and then you die @ 2019-06-09 23:49 UTC (permalink / raw)
  To: Lee Jones
  Cc: Jiri Kosina, Benjamin Tissoires, Jonathan Cameron,
	Hartmut Knaack, Lars-Peter Clausen, Peter Meerwald-Stadler,
	linux-input, linux-iio, linux-kernel


On Tue, May 07, 2019 at 01:24:15PM +0100, Lee Jones wrote:
> On Sun, 21 Apr 2019, Ronald Tschalär 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 provides a multiplexing layer that allows multiple HID drivers to
> > be registered for a given HID device. This allows the touch bar and ALS
> > driver to be separated out into their own modules.
> > 
> > Signed-off-by: Ronald Tschalär <ronald@innovation.ch>
> > ---
> >  drivers/mfd/Kconfig               |  15 +
> >  drivers/mfd/Makefile              |   1 +
> >  drivers/mfd/apple-ibridge.c       | 883 ++++++++++++++++++++++++++++++
> 
> I haven't taken a thorough look through, but I can tell you that the
> vast majority of what you're trying to do here does not belong in
> MFD.  MFD drivers are used to register child devices.  Almost all
> functionality or 'real work' should be contained in the drivers the
> MFD registers, not in the MFD parent itself.  You will need to move
> all 'real work' out into the subordinate device drivers for
> acceptance.

Thanks for your feedback. That was/is the idea: the actual Touch Bar
and ALS driver code is in separate modules - what is left in the
appple-ibridge mfd driver is a fairly generic hid driver
demultiplexer. However, that could be moved out into it's own
helper/module.

Having said that, it looks like the preference is to do all of this as
a hid driver with virtual hid devices instead of as an mfd driver.


  Cheers,

  Ronald


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

* Re: [PATCH 1/3] mfd: apple-ibridge: Add Apple iBridge MFD driver.
  2019-06-09 23:49     ` Life is hard, and then you die
@ 2019-06-10  5:45       ` Lee Jones
  0 siblings, 0 replies; 22+ messages in thread
From: Lee Jones @ 2019-06-10  5:45 UTC (permalink / raw)
  To: Life is hard, and then you die
  Cc: Jiri Kosina, Benjamin Tissoires, Jonathan Cameron,
	Hartmut Knaack, Lars-Peter Clausen, Peter Meerwald-Stadler,
	linux-input, linux-iio, linux-kernel

On Sun, 09 Jun 2019, Life is hard, and then you die wrote:

> 
> On Tue, May 07, 2019 at 01:24:15PM +0100, Lee Jones wrote:
> > On Sun, 21 Apr 2019, Ronald Tschalär 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 provides a multiplexing layer that allows multiple HID drivers to
> > > be registered for a given HID device. This allows the touch bar and ALS
> > > driver to be separated out into their own modules.
> > > 
> > > Signed-off-by: Ronald Tschalär <ronald@innovation.ch>
> > > ---
> > >  drivers/mfd/Kconfig               |  15 +
> > >  drivers/mfd/Makefile              |   1 +
> > >  drivers/mfd/apple-ibridge.c       | 883 ++++++++++++++++++++++++++++++
> > 
> > I haven't taken a thorough look through, but I can tell you that the
> > vast majority of what you're trying to do here does not belong in
> > MFD.  MFD drivers are used to register child devices.  Almost all
> > functionality or 'real work' should be contained in the drivers the
> > MFD registers, not in the MFD parent itself.  You will need to move
> > all 'real work' out into the subordinate device drivers for
> > acceptance.
> 
> Thanks for your feedback. That was/is the idea: the actual Touch Bar
> and ALS driver code is in separate modules - what is left in the
> appple-ibridge mfd driver is a fairly generic hid driver
> demultiplexer. However, that could be moved out into it's own
> helper/module.
> 
> Having said that, it looks like the preference is to do all of this as
> a hid driver with virtual hid devices instead of as an mfd driver.

Sounds like a better approach.

-- 
Lee Jones [李琼斯]
Linaro Services Technical Lead
Linaro.org │ Open source software for ARM SoCs
Follow Linaro: Facebook | Twitter | Blog

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

* Re: [PATCH 1/3] mfd: apple-ibridge: Add Apple iBridge MFD driver.
  2019-04-26  6:26           ` Benjamin Tissoires
@ 2019-06-10  9:19             ` Life is hard, and then you die
  2019-06-11  9:03               ` Benjamin Tissoires
  0 siblings, 1 reply; 22+ messages in thread
From: Life is hard, and then you die @ 2019-06-10  9:19 UTC (permalink / raw)
  To: Benjamin Tissoires
  Cc: Jiri Kosina, Jonathan Cameron, Hartmut Knaack,
	Lars-Peter Clausen, Peter Meerwald-Stadler, Lee Jones,
	open list:HID CORE LAYER, linux-iio, lkml


  Hi Benjamin,

Sorry for the extremely late reply - RL etc.

On Fri, Apr 26, 2019 at 08:26:25AM +0200, Benjamin Tissoires wrote:
> On Fri, Apr 26, 2019 at 7:56 AM Life is hard, and then you die
> <ronald@innovation.ch> wrote:
> >
> > On Thu, Apr 25, 2019 at 11:39:12AM +0200, Benjamin Tissoires wrote:
> > > On Thu, Apr 25, 2019 at 10:19 AM Life is hard, and then you die
> > > <ronald@innovation.ch> wrote:
> > > >
> > > >   Hi Benjamin,
> > > >
> > > > Thank you for looking at this.
> > > >
> > > > On Wed, Apr 24, 2019 at 04:18:23PM +0200, Benjamin Tissoires wrote:
> > > > > On Mon, Apr 22, 2019 at 5:13 AM 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 provides a multiplexing layer that allows multiple HID drivers to
> > > > > > be registered for a given HID device. This allows the touch bar and ALS
> > > > > > driver to be separated out into their own modules.
> > > > >
> > > > > Sorry for coming late to the party, but IMO this series is far too
> > > > > complex for what you need.
> > > > >
> > > > > As I read this and the first comment of drivers/mfd/apple-ibridge.c,
> > > > > you need to have a HID driver that multiplex 2 other sub drivers
> > > > > through one USB communication.
> > > > > For that, you are using MFD, platform driver and you own sauce instead
> > > > > of creating a bus.
> > > >
> > > > Basically correct. To be a bit more precise, there are currently two
> > > > hid-devices and two drivers (touchbar and als) involved, with
> > > > connections as follows (pardon the ugly ascii art):
> > > >
> > > >   hdev1  ---  tb-drv
> > > >            /
> > > >           /
> > > >          /
> > > >   hdev2  ---  als-drv
> > > >
> > > > i.e. the touchbar driver talks to both hdev's, and hdev2's events
> > > > (reports) are processed by both drivers (though each handles different
> > > > reports).
> > > >
> > > > > So, how about we reuse entirely the HID subsystem which already
> > > > > provides the capability you need (assuming I am correct above).
> > > > > hid-logitech-dj already does the same kind of stuff and you could:
> > > > > - create drivers/hid/hid-ibridge.c that handles USB_ID_PRODUCT_IBRIDGE
> > > > > - hid-ibridge will then register itself to the hid subsystem with a
> > > > > call to hid_hw_start(hdev, HID_CONNECT_HIDRAW) and
> > > > > hid_device_io_start(hdev) to enable the events (so you don't create
> > > > > useless input nodes for it)
> > > > > - then you add your 2 new devices by calling hid_allocate_device() and
> > > > > then hid_add_device(). You can even create a new HID group
> > > > > APPLE_IBRIDGE and allocate 2 new PIDs for them to distinguish them
> > > > > from the actual USB device.
> > > > > - then you have 2 brand new HID devices you can create their driver as
> > > > > a regular ones.
> > > > >
> > > > > hid-ibridge.c would just need to behave like any other hid transport
> > > > > driver (see logi_dj_ll_driver in drivers/hid/hid-logitech-dj.c) and
> > > > > you can get rid of at least the MFD and the platform part of your
> > > > > drivers.
> > > > >
> > > > > Does it makes sense or am I missing something obvious in the middle?
> > > >
> > > > Yes, I think I understand, and I think this can work. Basically,
> > > > instead of demux'ing at the hid-driver level as I am doing now (i.e.
> > > > the iBridge hid-driver forwarding calls to the sub-hid-drivers), we
> > > > demux at the hid-device level (events forwarded from iBridge hdev to
> > > > all "virtual" sub-hdev's, and requests from sub-hdev's forwarded to
> > > > the original hdev via an iBridge ll_driver attached to the
> > > > sub-hdev's).
> > > >
> > > > So I would need to create 3 new "virtual" hid-devices (instances) as
> > > > follows:
> > > >
> > > >   hdev1  ---  vhdev1  ---  tb-drv
> > > >                         /
> > > >           --  vhdev2  --
> > > >          /
> > > >   hdev2  ---  vhdev3  ---  als-drv
> > > >
> > > > (vhdev1 is probably not strictly necessary, but makes things more
> > > > consistent).
> > >
> > > Oh, ok.
> > >
> > > How about the following:
> > >
> > > hdev1 and hdev2 are merged together in hid-apple-ibridge.c, and then
> > > this driver creates 2 virtual hid drivers that are consistent
> > >
> > > like
> > >
> > > hdev1---ibridge-drv---vhdev1---tb-drv
> > > hdev2--/           \--vhdev2---als-drv
> >
> > I don't think this will work. The problem is when the sub-drivers need
> > to send a report or usb-command: how to they specify which hdev the
> > report/command is destined for? While we could store the original hdev
> > in each report (the hid_report's device field), that only works for
> > hid_hw_request(), but not for things like hid_hw_raw_request() or
> > hid_hw_output_report(). Now, currently I don't use the latter two; but
> > I do need to send raw usb control messages in the touchbar driver
> > (some commands are not proper hid reports), so it definitely breaks
> > down there.
> >
> > Or am I missing something?
> 
> I'd need to have a deeper look at the protocol, but you can emulate
> pure HID devices by having your ibridge handling a translation from
> set/get features/input to the usb control messages. Likewise, nothing
> prevents you to slightly rewrite the report descriptors you present to
> the als and touchbar to have a clear separation with the report ID.
> 
> For example, if both hdev1 and hdev2 use a report ID of 0x01, you
> could rewrite the report descriptor so that when you receive a report
> with an id of 0x01 you send this to hdev1, but you can also translate
> 0x11 to a report ID 0x01 to hdev2.
> Likewise, report ID 0x42 could be a raw USB control message to the USB
> under hdev2.
> 
> Note that you will have to write 2 report descriptors for your new
> devices, but you can take what makes sense from the original ones, and
> just add a new collection with a vendor application with with an
> opaque meaning (for the USB control messages).

A couple things here. First of all, I went and rewrote the mfd driver
with the hid-driver demultiplexer as a straight hid driver with 3
(well, 4 actually) virtual hid devices, as first discussed above.
This overall led to some simplifications, with only smaller
adjustments in the Touch Bar and ALS drivers (the diff stat shows 468
insertions, 825 deletions), so this looks good. Importantly (IMO),
this leaves the whole awareness of the fact that the Touch Bar driver
is talking to multiple usb interfaces and needs to coordinate
appropriately between them (including things like which order they are
accessed, sleep times between those accesses, and different power
management) clearly in the Touch Bar driver, and the ibridge driver is
still fairly generic unaware of any of the details that the
sub-drivers need to worry about.

Then I started looking more closely at your last suggestion above of
creating only 2 virtual hid devices, with report descriptor
merging/mangling and the addition of 3 custom reports (for the
set-power, io-wait, and usb-control functionality), and I'm having
trouble seeing the justification for it. AFAICT, the only advantage of
this approach is that there are fewer virtual hid devices. But the
disadvantages are significantly more code (especially in the ibridge
driver) and more leakage of knowledge from the Touch Bar driver into
the ibridge driver. In particular:

* this leads to additional work, synchronization, and state management
  in the ibridge driver to deal with the fact that we have to wait for
  all (real) hid devices to be probed before we can start creating the
  virtual hid devices (and visa versa on removal).
* merging and mangling those report descriptors requires re-parsing
  of the descriptors and dealing with various corner cases, which adds
  a bunch of code.
* while the custom set-power and io-wait reports are simple, the
  custom usb-control report is ugly, because it actually ends up
  needing to compute some of the parameters and rewrite the data, as
  both those have values that require knowledge of the real underlying
  reports and usb interfaces (i.e. it's not a report for a generic
  usb-control call, but a very Touch Bar specific one, i.e. leaking
  particular Touch Bar driver knowledge into the ibridge driver).
* In addition, while creating especially the custom usb-control
  report, I started to wonder if it's really worth serializing the
  parameters for the custom functions/report into an actual report
  buffer, just to be deserialized again two function calls down the
  stack. This became obvious when I was adding a helper function to
  the ibridge driver to serialize the function parameters, and at the
  same time writing a function right below it to deserialize those
  parameters again, all so we can call hid_hw_request() to pass them
  from the Touch Bar driver to the ibridge driver - but since the
  Touch Bar driver is already calling a custom function in the ibridge
  driver to serialize the parameters, it seems like that function
  might just as well make the desired underlying function call
  directly in the first place. Alternatively, the serializing
  functionality can be put in the Touch Bar driver instead, with the
  disadvantage that the implicit knowledge of the structure of the
  custom report is now spread over two modules. All of this seems
  somewhat ugly to me.

Lastly, as hinted earlier, while this tries to hide the fact that
there are actually multiple hid devices (aka usb interfaces) that are
being driven by the Touch Bar driver, the Touch Bar driver still needs
to be acutely aware of that fact because it cannot treat them equally.
So now instead it clearly dealing with two different devices, it now
has to do so indirectly by figuring out which reports (in the same
virtual hid device) belong to which underlying real hid devices so it
can treat those reports accordingly (e.g. when creating the reports to
trigger set-power or usb-ctrl, instead of the desired device/interface
being targeted directly, that info has to instead be passed somewhat
opaquely via a report-id of a report that it happens to know is mapped
to the desired device/interface).

Sorry for the long-winded response. I hope it isn't too cryptic. But
basically it boils down to: going for single virtual hid-device per
real device adds a bunch of complexity and knowledge leaking from the
Touch Bar driver the ibridge driver with AFAICT only a small advantage
(namely fewer virtual devices).


  Cheers,

  Ronald


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

* Re: [PATCH 1/3] mfd: apple-ibridge: Add Apple iBridge MFD driver.
  2019-06-10  9:19             ` Life is hard, and then you die
@ 2019-06-11  9:03               ` Benjamin Tissoires
  0 siblings, 0 replies; 22+ messages in thread
From: Benjamin Tissoires @ 2019-06-11  9:03 UTC (permalink / raw)
  To: Life is hard, and then you die
  Cc: Jiri Kosina, Jonathan Cameron, Hartmut Knaack,
	Lars-Peter Clausen, Peter Meerwald-Stadler, Lee Jones,
	open list:HID CORE LAYER, linux-iio, lkml

Hi ronald,

On Mon, Jun 10, 2019 at 11:20 AM Life is hard, and then you die
<ronald@innovation.ch> wrote:
>
>
>   Hi Benjamin,
>
> Sorry for the extremely late reply - RL etc.
>
> On Fri, Apr 26, 2019 at 08:26:25AM +0200, Benjamin Tissoires wrote:
> > On Fri, Apr 26, 2019 at 7:56 AM Life is hard, and then you die
> > <ronald@innovation.ch> wrote:
> > >
> > > On Thu, Apr 25, 2019 at 11:39:12AM +0200, Benjamin Tissoires wrote:
> > > > On Thu, Apr 25, 2019 at 10:19 AM Life is hard, and then you die
> > > > <ronald@innovation.ch> wrote:
> > > > >
> > > > >   Hi Benjamin,
> > > > >
> > > > > Thank you for looking at this.
> > > > >
> > > > > On Wed, Apr 24, 2019 at 04:18:23PM +0200, Benjamin Tissoires wrote:
> > > > > > On Mon, Apr 22, 2019 at 5:13 AM 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 provides a multiplexing layer that allows multiple HID drivers to
> > > > > > > be registered for a given HID device. This allows the touch bar and ALS
> > > > > > > driver to be separated out into their own modules.
> > > > > >
> > > > > > Sorry for coming late to the party, but IMO this series is far too
> > > > > > complex for what you need.
> > > > > >
> > > > > > As I read this and the first comment of drivers/mfd/apple-ibridge.c,
> > > > > > you need to have a HID driver that multiplex 2 other sub drivers
> > > > > > through one USB communication.
> > > > > > For that, you are using MFD, platform driver and you own sauce instead
> > > > > > of creating a bus.
> > > > >
> > > > > Basically correct. To be a bit more precise, there are currently two
> > > > > hid-devices and two drivers (touchbar and als) involved, with
> > > > > connections as follows (pardon the ugly ascii art):
> > > > >
> > > > >   hdev1  ---  tb-drv
> > > > >            /
> > > > >           /
> > > > >          /
> > > > >   hdev2  ---  als-drv
> > > > >
> > > > > i.e. the touchbar driver talks to both hdev's, and hdev2's events
> > > > > (reports) are processed by both drivers (though each handles different
> > > > > reports).
> > > > >
> > > > > > So, how about we reuse entirely the HID subsystem which already
> > > > > > provides the capability you need (assuming I am correct above).
> > > > > > hid-logitech-dj already does the same kind of stuff and you could:
> > > > > > - create drivers/hid/hid-ibridge.c that handles USB_ID_PRODUCT_IBRIDGE
> > > > > > - hid-ibridge will then register itself to the hid subsystem with a
> > > > > > call to hid_hw_start(hdev, HID_CONNECT_HIDRAW) and
> > > > > > hid_device_io_start(hdev) to enable the events (so you don't create
> > > > > > useless input nodes for it)
> > > > > > - then you add your 2 new devices by calling hid_allocate_device() and
> > > > > > then hid_add_device(). You can even create a new HID group
> > > > > > APPLE_IBRIDGE and allocate 2 new PIDs for them to distinguish them
> > > > > > from the actual USB device.
> > > > > > - then you have 2 brand new HID devices you can create their driver as
> > > > > > a regular ones.
> > > > > >
> > > > > > hid-ibridge.c would just need to behave like any other hid transport
> > > > > > driver (see logi_dj_ll_driver in drivers/hid/hid-logitech-dj.c) and
> > > > > > you can get rid of at least the MFD and the platform part of your
> > > > > > drivers.
> > > > > >
> > > > > > Does it makes sense or am I missing something obvious in the middle?
> > > > >
> > > > > Yes, I think I understand, and I think this can work. Basically,
> > > > > instead of demux'ing at the hid-driver level as I am doing now (i.e.
> > > > > the iBridge hid-driver forwarding calls to the sub-hid-drivers), we
> > > > > demux at the hid-device level (events forwarded from iBridge hdev to
> > > > > all "virtual" sub-hdev's, and requests from sub-hdev's forwarded to
> > > > > the original hdev via an iBridge ll_driver attached to the
> > > > > sub-hdev's).
> > > > >
> > > > > So I would need to create 3 new "virtual" hid-devices (instances) as
> > > > > follows:
> > > > >
> > > > >   hdev1  ---  vhdev1  ---  tb-drv
> > > > >                         /
> > > > >           --  vhdev2  --
> > > > >          /
> > > > >   hdev2  ---  vhdev3  ---  als-drv
> > > > >
> > > > > (vhdev1 is probably not strictly necessary, but makes things more
> > > > > consistent).
> > > >
> > > > Oh, ok.
> > > >
> > > > How about the following:
> > > >
> > > > hdev1 and hdev2 are merged together in hid-apple-ibridge.c, and then
> > > > this driver creates 2 virtual hid drivers that are consistent
> > > >
> > > > like
> > > >
> > > > hdev1---ibridge-drv---vhdev1---tb-drv
> > > > hdev2--/           \--vhdev2---als-drv
> > >
> > > I don't think this will work. The problem is when the sub-drivers need
> > > to send a report or usb-command: how to they specify which hdev the
> > > report/command is destined for? While we could store the original hdev
> > > in each report (the hid_report's device field), that only works for
> > > hid_hw_request(), but not for things like hid_hw_raw_request() or
> > > hid_hw_output_report(). Now, currently I don't use the latter two; but
> > > I do need to send raw usb control messages in the touchbar driver
> > > (some commands are not proper hid reports), so it definitely breaks
> > > down there.
> > >
> > > Or am I missing something?
> >
> > I'd need to have a deeper look at the protocol, but you can emulate
> > pure HID devices by having your ibridge handling a translation from
> > set/get features/input to the usb control messages. Likewise, nothing
> > prevents you to slightly rewrite the report descriptors you present to
> > the als and touchbar to have a clear separation with the report ID.
> >
> > For example, if both hdev1 and hdev2 use a report ID of 0x01, you
> > could rewrite the report descriptor so that when you receive a report
> > with an id of 0x01 you send this to hdev1, but you can also translate
> > 0x11 to a report ID 0x01 to hdev2.
> > Likewise, report ID 0x42 could be a raw USB control message to the USB
> > under hdev2.
> >
> > Note that you will have to write 2 report descriptors for your new
> > devices, but you can take what makes sense from the original ones, and
> > just add a new collection with a vendor application with with an
> > opaque meaning (for the USB control messages).
>
> A couple things here. First of all, I went and rewrote the mfd driver
> with the hid-driver demultiplexer as a straight hid driver with 3
> (well, 4 actually) virtual hid devices, as first discussed above.
> This overall led to some simplifications, with only smaller
> adjustments in the Touch Bar and ALS drivers (the diff stat shows 468
> insertions, 825 deletions), so this looks good. Importantly (IMO),
> this leaves the whole awareness of the fact that the Touch Bar driver
> is talking to multiple usb interfaces and needs to coordinate
> appropriately between them (including things like which order they are
> accessed, sleep times between those accesses, and different power
> management) clearly in the Touch Bar driver, and the ibridge driver is
> still fairly generic unaware of any of the details that the
> sub-drivers need to worry about.
>
> Then I started looking more closely at your last suggestion above of
> creating only 2 virtual hid devices, with report descriptor
> merging/mangling and the addition of 3 custom reports (for the
> set-power, io-wait, and usb-control functionality), and I'm having
> trouble seeing the justification for it. AFAICT, the only advantage of
> this approach is that there are fewer virtual hid devices. But the
> disadvantages are significantly more code (especially in the ibridge
> driver) and more leakage of knowledge from the Touch Bar driver into
> the ibridge driver. In particular:
>
> * this leads to additional work, synchronization, and state management
>   in the ibridge driver to deal with the fact that we have to wait for
>   all (real) hid devices to be probed before we can start creating the
>   virtual hid devices (and visa versa on removal).
> * merging and mangling those report descriptors requires re-parsing
>   of the descriptors and dealing with various corner cases, which adds
>   a bunch of code.
> * while the custom set-power and io-wait reports are simple, the
>   custom usb-control report is ugly, because it actually ends up
>   needing to compute some of the parameters and rewrite the data, as
>   both those have values that require knowledge of the real underlying
>   reports and usb interfaces (i.e. it's not a report for a generic
>   usb-control call, but a very Touch Bar specific one, i.e. leaking
>   particular Touch Bar driver knowledge into the ibridge driver).
> * In addition, while creating especially the custom usb-control
>   report, I started to wonder if it's really worth serializing the
>   parameters for the custom functions/report into an actual report
>   buffer, just to be deserialized again two function calls down the
>   stack. This became obvious when I was adding a helper function to
>   the ibridge driver to serialize the function parameters, and at the
>   same time writing a function right below it to deserialize those
>   parameters again, all so we can call hid_hw_request() to pass them
>   from the Touch Bar driver to the ibridge driver - but since the
>   Touch Bar driver is already calling a custom function in the ibridge
>   driver to serialize the parameters, it seems like that function
>   might just as well make the desired underlying function call
>   directly in the first place. Alternatively, the serializing
>   functionality can be put in the Touch Bar driver instead, with the
>   disadvantage that the implicit knowledge of the structure of the
>   custom report is now spread over two modules. All of this seems
>   somewhat ugly to me.
>
> Lastly, as hinted earlier, while this tries to hide the fact that
> there are actually multiple hid devices (aka usb interfaces) that are
> being driven by the Touch Bar driver, the Touch Bar driver still needs
> to be acutely aware of that fact because it cannot treat them equally.
> So now instead it clearly dealing with two different devices, it now
> has to do so indirectly by figuring out which reports (in the same
> virtual hid device) belong to which underlying real hid devices so it
> can treat those reports accordingly (e.g. when creating the reports to
> trigger set-power or usb-ctrl, instead of the desired device/interface
> being targeted directly, that info has to instead be passed somewhat
> opaquely via a report-id of a report that it happens to know is mapped
> to the desired device/interface).
>
> Sorry for the long-winded response. I hope it isn't too cryptic. But
> basically it boils down to: going for single virtual hid-device per
> real device adds a bunch of complexity and knowledge leaking from the
> Touch Bar driver the ibridge driver with AFAICT only a small advantage
> (namely fewer virtual devices).
>

I must confess that understanding all the details above without seeing
the code is rather hard.
However, if you have a simple and elegant solution right now that
doesn't imply the MFD driver, how about posting it now so we can
discuss it by looking at the code?

I am fine putting the above explanation in a commit message to justify
the current approach, but we are already talking about revision 3 when
I haven't seen revision 2.

Anyway, I can be convinced a design is better than the one I suggested.
And sometime it's better to not abstract too much if the overall gets
a little bit too complex.

So can you post your current WIP?

Cheers,
Benjamin

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

end of thread, other threads:[~2019-06-11  9:04 UTC | newest]

Thread overview: 22+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2019-04-22  3:12 [PATCH 0/3] Apple iBridge support Ronald Tschalär
2019-04-22  3:12 ` [PATCH 1/3] mfd: apple-ibridge: Add Apple iBridge MFD driver Ronald Tschalär
2019-04-22 11:34   ` Jonathan Cameron
2019-04-24 10:47     ` Life is hard, and then you die
2019-04-24 19:13       ` Jonathan Cameron
2019-04-26  5:34         ` Life is hard, and then you die
2019-04-24 14:18   ` Benjamin Tissoires
2019-04-25  8:19     ` Life is hard, and then you die
2019-04-25  9:39       ` Benjamin Tissoires
2019-04-26  5:56         ` Life is hard, and then you die
2019-04-26  6:26           ` Benjamin Tissoires
2019-06-10  9:19             ` Life is hard, and then you die
2019-06-11  9:03               ` Benjamin Tissoires
2019-05-07 12:24   ` Lee Jones
2019-06-09 23:49     ` Life is hard, and then you die
2019-06-10  5:45       ` Lee Jones
2019-04-22  3:12 ` [PATCH 2/3] HID: apple-ib-tb: Add driver for the Touch Bar on MacBook Pro's Ronald Tschalär
2019-04-22  3:12 ` [PATCH 3/3] iio: light: apple-ib-als: Add driver for ALS on iBridge chip Ronald Tschalär
2019-04-22  9:17   ` Peter Meerwald-Stadler
2019-04-22 12:01     ` Jonathan Cameron
2019-04-23 10:38       ` Life is hard, and then you die
2019-04-24 12:27         ` 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).