diff mbox series

[1/3] mfd: apple-ibridge: Add Apple iBridge MFD driver.

Message ID 20190422031251.11968-2-ronald@innovation.ch (mailing list archive)
State New, archived
Headers show
Series Apple iBridge support | expand

Commit Message

Life is hard, and then you die April 22, 2019, 3:12 a.m. UTC
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

Comments

Jonathan Cameron April 22, 2019, 11:34 a.m. UTC | #1
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
Life is hard, and then you die April 24, 2019, 10:47 a.m. UTC | #2
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
Benjamin Tissoires April 24, 2019, 2:18 p.m. UTC | #3
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
>
Jonathan Cameron April 24, 2019, 7:13 p.m. UTC | #4
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
>
Life is hard, and then you die April 25, 2019, 8:19 a.m. UTC | #5
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
Benjamin Tissoires April 25, 2019, 9:39 a.m. UTC | #6
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
>
Life is hard, and then you die April 26, 2019, 5:34 a.m. UTC | #7
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
Life is hard, and then you die April 26, 2019, 5:56 a.m. UTC | #8
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
Benjamin Tissoires April 26, 2019, 6:26 a.m. UTC | #9
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
Lee Jones May 7, 2019, 12:24 p.m. UTC | #10
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
Life is hard, and then you die June 9, 2019, 11:49 p.m. UTC | #11
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
Lee Jones June 10, 2019, 5:45 a.m. UTC | #12
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.
Life is hard, and then you die June 10, 2019, 9:19 a.m. UTC | #13
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
Benjamin Tissoires June 11, 2019, 9:03 a.m. UTC | #14
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
diff mbox series

Patch

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